From ed02757f7c56bdd831d2244825400e33be776765 Mon Sep 17 00:00:00 2001 From: Pablo Martin-Gomez Date: Fri, 10 Apr 2026 19:04:23 +0200 Subject: wifi: Remove invalid 128TU transition timeout constant In Table 9-417m of 802.11be-2024, Transition Timeout is defined up to value 10 for a Transition Timeout of 64TUs. The value 11 is reserved and does not correspond to a Transition Timeout of 128TUs. Signed-off-by: Pablo Martin-Gomez Link: https://patch.msgid.link/20260410170429.343617-2-pmartin-gomez@freebox.fr Signed-off-by: Johannes Berg --- include/linux/ieee80211-eht.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'include') diff --git a/include/linux/ieee80211-eht.h b/include/linux/ieee80211-eht.h index a97b1d01f3ac..bbff10a05b48 100644 --- a/include/linux/ieee80211-eht.h +++ b/include/linux/ieee80211-eht.h @@ -517,7 +517,6 @@ struct ieee80211_multi_link_elem { #define IEEE80211_EML_CAP_TRANSITION_TIMEOUT_16TU 8 #define IEEE80211_EML_CAP_TRANSITION_TIMEOUT_32TU 9 #define IEEE80211_EML_CAP_TRANSITION_TIMEOUT_64TU 10 -#define IEEE80211_EML_CAP_TRANSITION_TIMEOUT_128TU 11 #define IEEE80211_MLD_CAP_OP_MAX_SIMUL_LINKS 0x000f #define IEEE80211_MLD_CAP_OP_SRS_SUPPORT 0x0010 @@ -1178,7 +1177,7 @@ static inline u32 ieee80211_eml_trans_timeout_in_us(u16 eml_cap) IEEE80211_EML_CAP_TRANSITION_TIMEOUT); /* invalid values also just use 0 */ - if (!timeout || timeout > IEEE80211_EML_CAP_TRANSITION_TIMEOUT_128TU) + if (!timeout || timeout > IEEE80211_EML_CAP_TRANSITION_TIMEOUT_64TU) return 0; return 128 * (1 << (timeout - 1)); -- cgit v1.2.3 From 53f24921d81c97152c086ab2765969fcaa6173cc Mon Sep 17 00:00:00 2001 From: Pablo Martin-Gomez Date: Fri, 10 Apr 2026 19:04:24 +0200 Subject: wifi: Remove EMLMR Delay subfield definitions In the finalized version of 802.11be-2024, the EMLMR delay values have been merged in the EMLSR Padding/Transition Delay subfields and therefore the subfield EMLMR Delay has been converted to a reserved field. Signed-off-by: Pablo Martin-Gomez Link: https://patch.msgid.link/20260410170429.343617-3-pmartin-gomez@freebox.fr Signed-off-by: Johannes Berg --- include/linux/ieee80211-eht.h | 6 ------ 1 file changed, 6 deletions(-) (limited to 'include') diff --git a/include/linux/ieee80211-eht.h b/include/linux/ieee80211-eht.h index bbff10a05b48..6324d888073b 100644 --- a/include/linux/ieee80211-eht.h +++ b/include/linux/ieee80211-eht.h @@ -499,12 +499,6 @@ struct ieee80211_multi_link_elem { #define IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY_128US 4 #define IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY_256US 5 #define IEEE80211_EML_CAP_EMLMR_SUPPORT 0x0080 -#define IEEE80211_EML_CAP_EMLMR_DELAY 0x0700 -#define IEEE80211_EML_CAP_EMLMR_DELAY_0US 0 -#define IEEE80211_EML_CAP_EMLMR_DELAY_32US 1 -#define IEEE80211_EML_CAP_EMLMR_DELAY_64US 2 -#define IEEE80211_EML_CAP_EMLMR_DELAY_128US 3 -#define IEEE80211_EML_CAP_EMLMR_DELAY_256US 4 #define IEEE80211_EML_CAP_TRANSITION_TIMEOUT 0x7800 #define IEEE80211_EML_CAP_TRANSITION_TIMEOUT_0 0 #define IEEE80211_EML_CAP_TRANSITION_TIMEOUT_128US 1 -- cgit v1.2.3 From 5858f5e1588fab66573d50c06dbcdd12830044ca Mon Sep 17 00:00:00 2001 From: Pablo Martin-Gomez Date: Fri, 10 Apr 2026 19:04:25 +0200 Subject: wifi: Rename EMLSR delay constants and add EMLMR helpers and definitions In the final version of 802.11be-2024, Transition Delay and Padding Delay subfield are for both EMLSR and EMLMR. Depending if the mode is EMLSR or EMLMR, the interpretation of the encoded value might change. Define all the constants and helpers to interpret delay subfields both in EMLSR and EMLMR mode. Signed-off-by: Pablo Martin-Gomez Link: https://patch.msgid.link/20260410170429.343617-4-pmartin-gomez@freebox.fr Signed-off-by: Johannes Berg --- drivers/net/wireless/intel/iwlwifi/mld/mac80211.c | 6 +- drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c | 6 +- drivers/net/wireless/mediatek/mt76/mt7925/mcu.c | 4 +- drivers/net/wireless/realtek/rtw89/fw.c | 2 +- include/linux/ieee80211-eht.h | 102 +++++++++++++++++++--- net/mac80211/eht.c | 6 +- 6 files changed, 104 insertions(+), 22 deletions(-) (limited to 'include') diff --git a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c index da6fd7471568..b48ebec18dd5 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/mac80211.c @@ -114,10 +114,10 @@ static const u8 ext_capa_base[IWL_MLD_STA_EXT_CAPA_SIZE] = { }; #define IWL_MLD_EMLSR_CAPA (IEEE80211_EML_CAP_EMLSR_SUPP | \ - IEEE80211_EML_CAP_EMLSR_PADDING_DELAY_32US << \ - __bf_shf(IEEE80211_EML_CAP_EMLSR_PADDING_DELAY) | \ + IEEE80211_EML_CAP_EML_PADDING_DELAY_32US << \ + __bf_shf(IEEE80211_EML_CAP_EML_PADDING_DELAY) | \ IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY_64US << \ - __bf_shf(IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY)) + __bf_shf(IEEE80211_EML_CAP_EML_TRANSITION_DELAY)) #define IWL_MLD_CAPA_OPS (FIELD_PREP_CONST( \ IEEE80211_MLD_CAP_OP_TID_TO_LINK_MAP_NEG_SUPP, \ IEEE80211_MLD_CAP_OP_TID_TO_LINK_MAP_NEG_SUPP_SAME) | \ diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c b/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c index 1ec9807e4827..214e6d10081b 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/mac80211.c @@ -263,10 +263,10 @@ static const u8 tm_if_types_ext_capa_sta[] = { */ #define IWL_MVM_EMLSR_CAPA (IEEE80211_EML_CAP_EMLSR_SUPP | \ - IEEE80211_EML_CAP_EMLSR_PADDING_DELAY_32US << \ - __bf_shf(IEEE80211_EML_CAP_EMLSR_PADDING_DELAY) | \ + IEEE80211_EML_CAP_EML_PADDING_DELAY_32US << \ + __bf_shf(IEEE80211_EML_CAP_EML_PADDING_DELAY) | \ IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY_64US << \ - __bf_shf(IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY)) + __bf_shf(IEEE80211_EML_CAP_EML_TRANSITION_DELAY)) #define IWL_MVM_MLD_CAPA_OPS (FIELD_PREP_CONST( \ IEEE80211_MLD_CAP_OP_TID_TO_LINK_MAP_NEG_SUPP, \ IEEE80211_MLD_CAP_OP_TID_TO_LINK_MAP_NEG_SUPP_SAME) | \ diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c index 37cdf3e8a067..22bad3cba8df 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c @@ -1880,8 +1880,8 @@ mt7925_mcu_sta_eht_mld_tlv(struct sk_buff *skb, eml_cap = (vif->cfg.eml_cap & (IEEE80211_EML_CAP_EMLSR_SUPP | IEEE80211_EML_CAP_TRANSITION_TIMEOUT)) | - (ext_capa->eml_capabilities & (IEEE80211_EML_CAP_EMLSR_PADDING_DELAY | - IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY)); + (ext_capa->eml_capabilities & (IEEE80211_EML_CAP_EML_PADDING_DELAY | + IEEE80211_EML_CAP_EML_TRANSITION_DELAY)); if (eml_cap & IEEE80211_EML_CAP_EMLSR_SUPP) { eht_mld->eml_cap[0] = u16_get_bits(eml_cap, GENMASK(7, 0)); diff --git a/drivers/net/wireless/realtek/rtw89/fw.c b/drivers/net/wireless/realtek/rtw89/fw.c index 17704f054727..ffa04315a62b 100644 --- a/drivers/net/wireless/realtek/rtw89/fw.c +++ b/drivers/net/wireless/realtek/rtw89/fw.c @@ -5050,7 +5050,7 @@ int rtw89_fw_h2c_join_info(struct rtw89_dev *rtwdev, struct rtw89_vif_link *rtwv le32_encode_bits(0, RTW89_H2C_JOININFO_W1_EMLSR_CAB) | le32_encode_bits(0, RTW89_H2C_JOININFO_W1_NSTR_EN) | le32_encode_bits(init_ps, RTW89_H2C_JOININFO_W1_INIT_PWR_STATE) | - le32_encode_bits(IEEE80211_EML_CAP_EMLSR_PADDING_DELAY_256US, + le32_encode_bits(IEEE80211_EML_CAP_EML_PADDING_DELAY_256US, RTW89_H2C_JOININFO_W1_EMLSR_PADDING) | le32_encode_bits(IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY_256US, RTW89_H2C_JOININFO_W1_EMLSR_TRANS_DELAY) | diff --git a/include/linux/ieee80211-eht.h b/include/linux/ieee80211-eht.h index 6324d888073b..e24f95db6087 100644 --- a/include/linux/ieee80211-eht.h +++ b/include/linux/ieee80211-eht.h @@ -485,19 +485,27 @@ struct ieee80211_multi_link_elem { #define IEEE80211_MED_SYNC_DELAY_DEFAULT 0x10ac #define IEEE80211_EML_CAP_EMLSR_SUPP 0x0001 -#define IEEE80211_EML_CAP_EMLSR_PADDING_DELAY 0x000e -#define IEEE80211_EML_CAP_EMLSR_PADDING_DELAY_0US 0 -#define IEEE80211_EML_CAP_EMLSR_PADDING_DELAY_32US 1 -#define IEEE80211_EML_CAP_EMLSR_PADDING_DELAY_64US 2 -#define IEEE80211_EML_CAP_EMLSR_PADDING_DELAY_128US 3 -#define IEEE80211_EML_CAP_EMLSR_PADDING_DELAY_256US 4 -#define IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY 0x0070 +#define IEEE80211_EML_CAP_EML_PADDING_DELAY 0x000e +/* Described Tables 9-417i & 9-417k in 802.11be-2024, which have the same values */ +#define IEEE80211_EML_CAP_EML_PADDING_DELAY_0US 0 +#define IEEE80211_EML_CAP_EML_PADDING_DELAY_32US 1 +#define IEEE80211_EML_CAP_EML_PADDING_DELAY_64US 2 +#define IEEE80211_EML_CAP_EML_PADDING_DELAY_128US 3 +#define IEEE80211_EML_CAP_EML_PADDING_DELAY_256US 4 +#define IEEE80211_EML_CAP_EML_TRANSITION_DELAY 0x0070 +/* Described in Table 9-417j in 802.11be-2024 */ #define IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY_0US 0 #define IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY_16US 1 #define IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY_32US 2 #define IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY_64US 3 #define IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY_128US 4 #define IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY_256US 5 +/* Described in Table 9-417l in 802.11be-2024 */ +#define IEEE80211_EML_CAP_EMLMR_TRANSITION_DELAY_0US 0 +#define IEEE80211_EML_CAP_EMLMR_TRANSITION_DELAY_32US 1 +#define IEEE80211_EML_CAP_EMLMR_TRANSITION_DELAY_64US 2 +#define IEEE80211_EML_CAP_EMLMR_TRANSITION_DELAY_128US 3 +#define IEEE80211_EML_CAP_EMLMR_TRANSITION_DELAY_256US 4 #define IEEE80211_EML_CAP_EMLMR_SUPPORT 0x0080 #define IEEE80211_EML_CAP_TRANSITION_TIMEOUT 0x7800 #define IEEE80211_EML_CAP_TRANSITION_TIMEOUT_0 0 @@ -1114,14 +1122,20 @@ static inline bool ieee80211_tid_to_link_map_size_ok(const u8 *data, size_t len) static inline u32 ieee80211_emlsr_pad_delay_in_us(u16 eml_cap) { + u32 emlsr_supp = + u16_get_bits(eml_cap, IEEE80211_EML_CAP_EMLSR_SUPP); + + if (!emlsr_supp) + return 0; + /* IEEE Std 802.11be-2024 Table 9-417i—Encoding of the EMLSR * Padding Delay subfield. */ u32 pad_delay = u16_get_bits(eml_cap, - IEEE80211_EML_CAP_EMLSR_PADDING_DELAY); + IEEE80211_EML_CAP_EML_PADDING_DELAY); if (!pad_delay || - pad_delay > IEEE80211_EML_CAP_EMLSR_PADDING_DELAY_256US) + pad_delay > IEEE80211_EML_CAP_EML_PADDING_DELAY_256US) return 0; return 32 * (1 << (pad_delay - 1)); @@ -1138,12 +1152,18 @@ static inline u32 ieee80211_emlsr_pad_delay_in_us(u16 eml_cap) static inline u32 ieee80211_emlsr_trans_delay_in_us(u16 eml_cap) { + u32 emlsr_supp = + u16_get_bits(eml_cap, IEEE80211_EML_CAP_EMLSR_SUPP); + + if (!emlsr_supp) + return 0; + /* IEEE Std 802.11be-2024 Table 9-417j—Encoding of the EMLSR * Transition Delay subfield. */ u32 trans_delay = u16_get_bits(eml_cap, - IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY); + IEEE80211_EML_CAP_EML_TRANSITION_DELAY); /* invalid values also just use 0 */ if (!trans_delay || @@ -1153,6 +1173,68 @@ static inline u32 ieee80211_emlsr_trans_delay_in_us(u16 eml_cap) return 16 * (1 << (trans_delay - 1)); } +/** + * ieee80211_emlmr_pad_delay_in_us - Fetch the EMLMR Padding delay + * in microseconds + * @eml_cap: EML capabilities field value from common info field of + * the Multi-link element + * Return: the EMLMR Padding delay (in microseconds) encoded in the + * EML Capabilities field + */ + +static inline u32 ieee80211_emlmr_pad_delay_in_us(u16 eml_cap) +{ + u32 emlmr_supp = + u16_get_bits(eml_cap, IEEE80211_EML_CAP_EMLMR_SUPPORT); + + if (!emlmr_supp) + return 0; + + /* IEEE Std 802.11be-2024 Table 9-417k—Encoding of the EMLMR + * Padding Delay subfield. + */ + u32 pad_delay = u16_get_bits(eml_cap, + IEEE80211_EML_CAP_EML_PADDING_DELAY); + + if (!pad_delay || + pad_delay > IEEE80211_EML_CAP_EML_PADDING_DELAY_256US) + return 0; + + return 32 * (1 << (pad_delay - 1)); +} + +/** + * ieee80211_emlmr_trans_delay_in_us - Fetch the EMLMR Transition + * delay in microseconds + * @eml_cap: EML capabilities field value from common info field of + * the Multi-link element + * Return: the EMLMR Transition delay (in microseconds) encoded in the + * EML Capabilities field + */ + +static inline u32 ieee80211_emlmr_trans_delay_in_us(u16 eml_cap) +{ + u32 emlmr_supp = + u16_get_bits(eml_cap, IEEE80211_EML_CAP_EMLMR_SUPPORT); + + if (!emlmr_supp) + return 0; + + /* IEEE Std 802.11be-2024 Table 9-417l—Encoding of the EMLMR + * Transition Delay subfield. + */ + u32 trans_delay = + u16_get_bits(eml_cap, + IEEE80211_EML_CAP_EML_TRANSITION_DELAY); + + /* invalid values also just use 0 */ + if (!trans_delay || + trans_delay > IEEE80211_EML_CAP_EMLMR_TRANSITION_DELAY_256US) + return 0; + + return 32 * (1 << (trans_delay - 1)); +} + /** * ieee80211_eml_trans_timeout_in_us - Fetch the EMLSR Transition * timeout value in microseconds diff --git a/net/mac80211/eht.c b/net/mac80211/eht.c index 768bfc4e737d..e88f28edfd57 100644 --- a/net/mac80211/eht.c +++ b/net/mac80211/eht.c @@ -204,7 +204,7 @@ void ieee80211_rx_eml_op_mode_notif(struct ieee80211_sub_if_data *sdata, pad_delay = u8_get_bits(ptr[2], IEEE80211_EML_EMLSR_PAD_DELAY); if (pad_delay > - IEEE80211_EML_CAP_EMLSR_PADDING_DELAY_256US) + IEEE80211_EML_CAP_EML_PADDING_DELAY_256US) return; trans_delay = u8_get_bits(ptr[2], @@ -217,11 +217,11 @@ void ieee80211_rx_eml_op_mode_notif(struct ieee80211_sub_if_data *sdata, sta->sta.eml_cap = u8_replace_bits(sta->sta.eml_cap, pad_delay, - IEEE80211_EML_CAP_EMLSR_PADDING_DELAY); + IEEE80211_EML_CAP_EML_PADDING_DELAY); sta->sta.eml_cap = u8_replace_bits(sta->sta.eml_cap, trans_delay, - IEEE80211_EML_CAP_EMLSR_TRANSITION_DELAY); + IEEE80211_EML_CAP_EML_TRANSITION_DELAY); } } -- cgit v1.2.3 From 37f3787636434593833a6d03de861af4ec09cb88 Mon Sep 17 00:00:00 2001 From: Pablo Martin-Gomez Date: Fri, 10 Apr 2026 19:04:26 +0200 Subject: wifi: Update EML function documentation to remove EMLSR-specific references Transition Timeout is not specific to EMLSR, and is used by both EMLSR and EMLMR mode. Signed-off-by: Pablo Martin-Gomez Link: https://patch.msgid.link/20260410170429.343617-5-pmartin-gomez@freebox.fr Signed-off-by: Johannes Berg --- include/linux/ieee80211-eht.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/linux/ieee80211-eht.h b/include/linux/ieee80211-eht.h index e24f95db6087..335e78bd4b5d 100644 --- a/include/linux/ieee80211-eht.h +++ b/include/linux/ieee80211-eht.h @@ -1236,11 +1236,11 @@ static inline u32 ieee80211_emlmr_trans_delay_in_us(u16 eml_cap) } /** - * ieee80211_eml_trans_timeout_in_us - Fetch the EMLSR Transition + * ieee80211_eml_trans_timeout_in_us - Fetch the EML Transition * timeout value in microseconds * @eml_cap: EML capabilities field value from common info field of * the Multi-link element - * Return: the EMLSR Transition timeout (in microseconds) encoded in + * Return: the EML Transition timeout (in microseconds) encoded in * the EML Capabilities field */ -- cgit v1.2.3 From ed1e31d784cbb8db9082d553641cbb0cd2b49989 Mon Sep 17 00:00:00 2001 From: Ping-Ke Shih Date: Sat, 11 Apr 2026 15:25:09 +0800 Subject: wifi: mac80211: add __packed to union members of struct ieee80211_rx_status The arm-linux-gnueabi-gcc compiler, align the field followed by union members, causing size of struct ieee80211_rx_status over skb->cb (48 bytes). By investigation, the union member starts at offset 32, and the offset of next field rate_idx is 36 instead of expected 33, and the total size is (unexpected) 52. When compiling rtw88 driver, it throws: In file included from /work/linux-src/linux-stable/include/linux/string.h:386, from /work/linux-src/linux-stable/include/linux/bitmap.h:13, from /work/linux-src/linux-stable/include/linux/cpumask.h:11, from /work/linux-src/linux-stable/include/linux/smp.h:13, from /work/linux-src/linux-stable/include/linux/lockdep.h:14, from /work/linux-src/linux-stable/include/linux/mutex.h:17, from /work/linux-src/linux-stable/include/linux/kernfs.h:11, from /work/linux-src/linux-stable/include/linux/sysfs.h:16, from /work/linux-src/linux-stable/include/linux/kobject.h:20, from /work/linux-src/linux-stable/include/linux/dmi.h:6, from pci.c:5: In function 'fortify_memcpy_chk', inlined from 'rtw_pci_rx_napi.constprop' at pci.c:1095:4: /work/linux-src/linux-stable/include/linux/fortify-string.h:569:25: warning: call to '__write_overflow_field' declared with attribute warning: detected write beyond size of field (1st parameter); maybe use struct_group()? [-Wattribute-warning] 569 | __write_overflow_field(p_size_field, size); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ After this patch, the size of struct ieee80211_rx_status is 48. Signed-off-by: Ping-Ke Shih Link: https://patch.msgid.link/20260411072509.1556635-1-pkshih@realtek.com Signed-off-by: Johannes Berg --- include/net/mac80211.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'include') diff --git a/include/net/mac80211.h b/include/net/mac80211.h index 40cb20d9309c..02318a4be0e1 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -1747,18 +1747,18 @@ struct ieee80211_rx_status { u8 he_ru:3; u8 he_gi:2; u8 he_dcm:1; - }; + } __packed; struct { u8 ru:4; u8 gi:2; - } eht; + } __packed eht; struct { u8 ru:4; u8 gi:2; u8 elr:1; u8 im:1; - } uhr; - }; + } __packed uhr; + } __packed; u8 rate_idx; u8 nss; u8 rx_flags; @@ -1771,6 +1771,8 @@ struct ieee80211_rx_status { u8 link_valid:1, link_id:4; }; +static_assert(sizeof(struct ieee80211_rx_status) <= sizeof_field(struct sk_buff, cb)); + static inline u32 ieee80211_rx_status_to_khz(struct ieee80211_rx_status *rx_status) { -- cgit v1.2.3 From 032d1a3fc0e95ca804213df65ef57f5ced2a867b Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 15 Apr 2026 14:42:05 +0200 Subject: wifi: nl80211: document channel opmode change channel width The opmode change notification is entirely unused by existing userspace except for printing out the values. As such, there's no need to keep it perfectly accurate, and the implementation in mac80211 doesn't report it correctly today. Add a note in the documentation that it may not differentiate 80+80 and 160. Reviewed-by: Miriam Rachel Korenblit Link: https://patch.msgid.link/20260415144514.87d5b1ce688f.Ia9a0769d52dcfe56f7b0dff903ed14db3ef04920@changeid Signed-off-by: Johannes Berg --- include/uapi/linux/nl80211.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'include') diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 3d55bf4be36f..072b383d7d3c 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -1204,10 +1204,12 @@ * user space through the connect result as the user space would have * initiated the connection through the connect request. * - * @NL80211_CMD_STA_OPMODE_CHANGED: An event that notify station's - * ht opmode or vht opmode changes using any of %NL80211_ATTR_SMPS_MODE, - * %NL80211_ATTR_CHANNEL_WIDTH,%NL80211_ATTR_NSS attributes with its - * address(specified in %NL80211_ATTR_MAC). + * @NL80211_CMD_STA_OPMODE_CHANGED: An event that notifies that a station's + * HT opmode or VHT opmode changed using any of %NL80211_ATTR_SMPS_MODE, + * %NL80211_ATTR_CHANNEL_WIDTH, %NL80211_ATTR_NSS attributes with its + * address (specified in %NL80211_ATTR_MAC). + * Note that 80+80 and 160 MHz might not be differentiated, i.e. may + * report %NL80211_CHAN_WIDTH_160 instead of %NL80211_CHAN_WIDTH_80P80. * * @NL80211_CMD_GET_FTM_RESPONDER_STATS: Retrieve FTM responder statistics, in * the %NL80211_ATTR_FTM_RESPONDER_STATS attribute. -- cgit v1.2.3 From e12e20650f64d109971f27a30260f1012b7d26fe Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 15 Apr 2026 14:42:11 +0200 Subject: wifi: cfg80211: remove HE/SAE H2E required fields These are not used by any drivers, even the HT/VHT ones are only used by the qtnfmac driver. Remove the fields. Reviewed-by: Miriam Rachel Korenblit Link: https://patch.msgid.link/20260415144514.a4a3ebb0f95a.Ifadd953e13133e7a45ee3318fb04b2ff9dde62e4@changeid Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 4 +--- net/wireless/nl80211.c | 4 ---- 2 files changed, 1 insertion(+), 7 deletions(-) (limited to 'include') diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 9d3639ff9c28..8bebf45af95d 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -1529,8 +1529,6 @@ struct cfg80211_s1g_short_beacon { * @ht_required: stations must support HT * @vht_required: stations must support VHT * @twt_responder: Enable Target Wait Time - * @he_required: stations must support HE - * @sae_h2e_required: stations must support direct H2E technique in SAE * @flags: flags, as defined in &enum nl80211_ap_settings_flags * @he_obss_pd: OBSS Packet Detection settings * @he_oper: HE operation IE (or %NULL if HE isn't enabled) @@ -1566,7 +1564,7 @@ struct cfg80211_ap_settings { const struct ieee80211_eht_cap_elem *eht_cap; const struct ieee80211_eht_operation *eht_oper; const struct ieee80211_uhr_operation *uhr_oper; - bool ht_required, vht_required, he_required, sae_h2e_required; + bool ht_required, vht_required; bool twt_responder; u32 flags; struct ieee80211_he_obss_pd he_obss_pd; diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index b0bb94f29e56..a4d9b4068506 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -6641,10 +6641,6 @@ static void nl80211_check_ap_rate_selectors(struct cfg80211_ap_settings *params, params->ht_required = true; if (rates->data[i] == BSS_MEMBERSHIP_SELECTOR_VHT_PHY) params->vht_required = true; - if (rates->data[i] == BSS_MEMBERSHIP_SELECTOR_HE_PHY) - params->he_required = true; - if (rates->data[i] == BSS_MEMBERSHIP_SELECTOR_SAE_H2E) - params->sae_h2e_required = true; } } -- cgit v1.2.3 From a384ae96990250eaa676e3ff78e5ab2cd00565dd Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 15 Apr 2026 14:42:13 +0200 Subject: wifi: cfg80211: move AP HT/VHT/... operation to beacon info The HT/VHT/HE/EHT/UHR operation can change, and might thus be updated on each beacon update. Move them to the beacon struct and parse them out of the beacon also on updates, not just on starting the AP. This also fixes checks in two ways: - Regulatory checks in nl80211_validate_ap_phy_operation() are now done also on updates, disallowing enabling HE/EHT/UHR on channels that don't allow that after start. This checks only operation now, but clients can't use it without operation. - NL80211_ATTR_UHR_OPERATION is now required whenever UHR is present in the beacon, and rejected otherwise. Reviewed-by: Miriam Rachel Korenblit Link: https://patch.msgid.link/20260415144514.f70758a46904.I0d21120b41eed661eefc61d5417dadaae7145845@changeid Signed-off-by: Johannes Berg --- drivers/net/wireless/quantenna/qtnfmac/commands.c | 4 +- include/net/cfg80211.h | 19 ++-- net/mac80211/cfg.c | 10 +- net/wireless/nl80211.c | 117 ++++++++++++++-------- 4 files changed, 91 insertions(+), 59 deletions(-) (limited to 'include') diff --git a/drivers/net/wireless/quantenna/qtnfmac/commands.c b/drivers/net/wireless/quantenna/qtnfmac/commands.c index d9c4dd6beaec..77929ed9ef78 100644 --- a/drivers/net/wireless/quantenna/qtnfmac/commands.c +++ b/drivers/net/wireless/quantenna/qtnfmac/commands.c @@ -261,8 +261,8 @@ int qtnf_cmd_send_start_ap(struct qtnf_vif *vif, cmd->p2p_ctwindow = s->p2p_ctwindow; cmd->p2p_opp_ps = s->p2p_opp_ps; cmd->pbss = s->pbss; - cmd->ht_required = s->ht_required; - cmd->vht_required = s->vht_required; + cmd->ht_required = s->beacon.ht_required; + cmd->vht_required = s->beacon.vht_required; cmd->twt_responder = s->twt_responder; if (s->he_obss_pd.enable) { cmd->sr_params.sr_control |= QLINK_SR_SRG_INFORMATION_PRESENT; diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 8bebf45af95d..7070e577342b 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -1396,6 +1396,11 @@ struct cfg80211_rnr_elems { * @he_bss_color: BSS Color settings * @he_bss_color_valid: indicates whether bss color * attribute is present in beacon data or not. + * @ht_required: stations must support HT + * @vht_required: stations must support VHT + * @he_oper: HE operation IE (or %NULL if HE isn't enabled) + * @eht_oper: EHT operation IE (or %NULL if EHT isn't enabled) + * @uhr_oper: UHR operation (or %NULL if UHR isn't enabled) */ struct cfg80211_beacon_data { unsigned int link_id; @@ -1420,6 +1425,11 @@ struct cfg80211_beacon_data { size_t civicloc_len; struct cfg80211_he_bss_color he_bss_color; bool he_bss_color_valid; + + bool ht_required, vht_required; + const struct ieee80211_he_operation *he_oper; + const struct ieee80211_eht_operation *eht_oper; + const struct ieee80211_uhr_operation *uhr_oper; }; struct mac_address { @@ -1524,14 +1534,9 @@ struct cfg80211_s1g_short_beacon { * @vht_cap: VHT capabilities (or %NULL if VHT isn't enabled) * @he_cap: HE capabilities (or %NULL if HE isn't enabled) * @eht_cap: EHT capabilities (or %NULL if EHT isn't enabled) - * @eht_oper: EHT operation IE (or %NULL if EHT isn't enabled) - * @uhr_oper: UHR operation (or %NULL if UHR isn't enabled) - * @ht_required: stations must support HT - * @vht_required: stations must support VHT * @twt_responder: Enable Target Wait Time * @flags: flags, as defined in &enum nl80211_ap_settings_flags * @he_obss_pd: OBSS Packet Detection settings - * @he_oper: HE operation IE (or %NULL if HE isn't enabled) * @fils_discovery: FILS discovery transmission parameters * @unsol_bcast_probe_resp: Unsolicited broadcast probe response parameters * @mbssid_config: AP settings for multiple bssid @@ -1560,11 +1565,7 @@ struct cfg80211_ap_settings { const struct ieee80211_ht_cap *ht_cap; const struct ieee80211_vht_cap *vht_cap; const struct ieee80211_he_cap_elem *he_cap; - const struct ieee80211_he_operation *he_oper; const struct ieee80211_eht_cap_elem *eht_cap; - const struct ieee80211_eht_operation *eht_oper; - const struct ieee80211_uhr_operation *uhr_oper; - bool ht_required, vht_required; bool twt_responder; u32 flags; struct ieee80211_he_obss_pd he_obss_pd; diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 15a68d3f8e73..bc4a8e5ad039 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -1541,13 +1541,13 @@ static int ieee80211_start_ap(struct wiphy *wiphy, struct net_device *dev, cpu_to_le32(IEEE80211_VHT_CAP_MU_BEAMFORMEE_CAPABLE); } - if (params->he_cap && params->he_oper) { + if (params->he_cap && params->beacon.he_oper) { link_conf->he_support = true; link_conf->htc_trig_based_pkt_ext = - le32_get_bits(params->he_oper->he_oper_params, + le32_get_bits(params->beacon.he_oper->he_oper_params, IEEE80211_HE_OPERATION_DFLT_PE_DURATION_MASK); link_conf->frame_time_rts_th = - le32_get_bits(params->he_oper->he_oper_params, + le32_get_bits(params->beacon.he_oper->he_oper_params, IEEE80211_HE_OPERATION_RTS_THRESHOLD_MASK); changed |= BSS_CHANGED_HE_OBSS_PD; @@ -1596,7 +1596,7 @@ static int ieee80211_start_ap(struct wiphy *wiphy, struct net_device *dev, IEEE80211_EHT_PHY_CAP7_NON_OFDMA_UL_MU_MIMO_160MHZ | IEEE80211_EHT_PHY_CAP7_NON_OFDMA_UL_MU_MIMO_320MHZ); link_conf->eht_disable_mcs15 = - u8_get_bits(params->eht_oper->params, + u8_get_bits(params->beacon.eht_oper->params, IEEE80211_EHT_OPER_MCS15_DISABLE); } else { link_conf->eht_su_beamformer = false; @@ -1604,7 +1604,7 @@ static int ieee80211_start_ap(struct wiphy *wiphy, struct net_device *dev, link_conf->eht_mu_beamformer = false; } - if (params->uhr_oper) { + if (params->beacon.uhr_oper) { if (!link_conf->eht_support) return -EOPNOTSUPP; diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 9892cbc182b9..d1bede3d0a14 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -6628,7 +6628,7 @@ nl80211_parse_unsol_bcast_probe_resp(struct cfg80211_registered_device *rdev, return 0; } -static void nl80211_check_ap_rate_selectors(struct cfg80211_ap_settings *params, +static void nl80211_check_ap_rate_selectors(struct cfg80211_beacon_data *bcn, const struct element *rates) { int i; @@ -6638,31 +6638,23 @@ static void nl80211_check_ap_rate_selectors(struct cfg80211_ap_settings *params, for (i = 0; i < rates->datalen; i++) { if (rates->data[i] == BSS_MEMBERSHIP_SELECTOR_HT_PHY) - params->ht_required = true; + bcn->ht_required = true; if (rates->data[i] == BSS_MEMBERSHIP_SELECTOR_VHT_PHY) - params->vht_required = true; + bcn->vht_required = true; } } /* * Since the nl80211 API didn't include, from the beginning, attributes about - * HT/VHT requirements/capabilities, we parse them out of the IEs for the - * benefit of drivers that rebuild IEs in the firmware. + * HT/VHT/... capabilities, we parse them out of the elements and check for + * validity for use by drivers/mac80211. */ -static int nl80211_calculate_ap_params(struct cfg80211_ap_settings *params) +static int nl80211_calculate_ap_capabilities(struct cfg80211_ap_settings *params) { - const struct cfg80211_beacon_data *bcn = ¶ms->beacon; - size_t ies_len = bcn->tail_len; - const u8 *ies = bcn->tail; - const struct element *rates; + size_t ies_len = params->beacon.tail_len; + const u8 *ies = params->beacon.tail; const struct element *cap; - rates = cfg80211_find_elem(WLAN_EID_SUPP_RATES, ies, ies_len); - nl80211_check_ap_rate_selectors(params, rates); - - rates = cfg80211_find_elem(WLAN_EID_EXT_SUPP_RATES, ies, ies_len); - nl80211_check_ap_rate_selectors(params, rates); - cap = cfg80211_find_elem(WLAN_EID_HT_CAPABILITY, ies, ies_len); if (cap && cap->datalen >= sizeof(*params->ht_cap)) params->ht_cap = (void *)cap->data; @@ -6672,31 +6664,62 @@ static int nl80211_calculate_ap_params(struct cfg80211_ap_settings *params) cap = cfg80211_find_ext_elem(WLAN_EID_EXT_HE_CAPABILITY, ies, ies_len); if (cap && cap->datalen >= sizeof(*params->he_cap) + 1) params->he_cap = (void *)(cap->data + 1); - cap = cfg80211_find_ext_elem(WLAN_EID_EXT_HE_OPERATION, ies, ies_len); - if (cap && cap->datalen >= sizeof(*params->he_oper) + 1) { - params->he_oper = (void *)(cap->data + 1); - /* takes extension ID into account */ - if (cap->datalen < ieee80211_he_oper_size((void *)params->he_oper)) - return -EINVAL; - } cap = cfg80211_find_ext_elem(WLAN_EID_EXT_EHT_CAPABILITY, ies, ies_len); if (cap) { - if (!cap->datalen) - return -EINVAL; params->eht_cap = (void *)(cap->data + 1); if (!ieee80211_eht_capa_size_ok((const u8 *)params->he_cap, (const u8 *)params->eht_cap, cap->datalen - 1, true)) return -EINVAL; } - cap = cfg80211_find_ext_elem(WLAN_EID_EXT_EHT_OPERATION, ies, ies_len); - if (cap) { - if (!cap->datalen) + + return 0; +} + +/* + * Since the nl80211 API didn't include, from the beginning, attributes about + * HT/VHT/... operation, we parse them out of the elements and check for + * validity for use by drivers/mac80211. + */ +static int nl80211_calculate_ap_operation(struct genl_info *info, + struct cfg80211_beacon_data *bcn) +{ + size_t ies_len = bcn->tail_len; + const u8 *ies = bcn->tail; + const struct element *rates; + const struct element *op; + + rates = cfg80211_find_elem(WLAN_EID_SUPP_RATES, ies, ies_len); + nl80211_check_ap_rate_selectors(bcn, rates); + + rates = cfg80211_find_elem(WLAN_EID_EXT_SUPP_RATES, ies, ies_len); + nl80211_check_ap_rate_selectors(bcn, rates); + + op = cfg80211_find_ext_elem(WLAN_EID_EXT_HE_OPERATION, ies, ies_len); + if (op && op->datalen >= sizeof(*bcn->he_oper) + 1) { + bcn->he_oper = (void *)(op->data + 1); + /* takes extension ID into account */ + if (op->datalen < ieee80211_he_oper_size((void *)bcn->he_oper)) return -EINVAL; - params->eht_oper = (void *)(cap->data + 1); - if (!ieee80211_eht_oper_size_ok((const u8 *)params->eht_oper, - cap->datalen - 1)) + } + + op = cfg80211_find_ext_elem(WLAN_EID_EXT_EHT_OPERATION, ies, ies_len); + if (op) { + if (!ieee80211_eht_oper_size_ok(op->data + 1, + op->datalen - 1)) + return -EINVAL; + bcn->eht_oper = (void *)(op->data + 1); + } + + op = cfg80211_find_ext_elem(WLAN_EID_EXT_UHR_OPER, ies, ies_len); + if (op) { + /* need full UHR operation separately */ + if (!info->attrs[NL80211_ATTR_UHR_OPERATION]) return -EINVAL; + bcn->uhr_oper = nla_data(info->attrs[NL80211_ATTR_UHR_OPERATION]); + } else if (info->attrs[NL80211_ATTR_UHR_OPERATION]) { + GENL_SET_ERR_MSG(info, "unexpected UHR operation"); + return -EINVAL; } return 0; @@ -6826,19 +6849,16 @@ out: nlmsg_free(msg); } -static int nl80211_validate_ap_phy_operation(struct cfg80211_ap_settings *params) +static int nl80211_validate_ap_phy_operation(struct ieee80211_channel *channel, + struct cfg80211_beacon_data *bcn) { - struct ieee80211_channel *channel = params->chandef.chan; - - if ((params->he_cap || params->he_oper) && - (channel->flags & IEEE80211_CHAN_NO_HE)) + if (bcn->he_oper && (channel->flags & IEEE80211_CHAN_NO_HE)) return -EOPNOTSUPP; - if ((params->eht_cap || params->eht_oper) && - (channel->flags & IEEE80211_CHAN_NO_EHT)) + if (bcn->eht_oper && (channel->flags & IEEE80211_CHAN_NO_EHT)) return -EOPNOTSUPP; - if (params->uhr_oper && (channel->flags & IEEE80211_CHAN_NO_UHR)) + if (bcn->uhr_oper && (channel->flags & IEEE80211_CHAN_NO_UHR)) return -EOPNOTSUPP; return 0; @@ -7136,14 +7156,16 @@ static int nl80211_start_ap(struct sk_buff *skb, struct genl_info *info) goto out; } - err = nl80211_calculate_ap_params(params); + err = nl80211_calculate_ap_capabilities(params); if (err) goto out; - if (info->attrs[NL80211_ATTR_UHR_OPERATION]) - params->uhr_oper = nla_data(info->attrs[NL80211_ATTR_UHR_OPERATION]); + err = nl80211_calculate_ap_operation(info, ¶ms->beacon); + if (err) + goto out; - err = nl80211_validate_ap_phy_operation(params); + err = nl80211_validate_ap_phy_operation(params->chandef.chan, + ¶ms->beacon); if (err) goto out; @@ -7218,6 +7240,15 @@ static int nl80211_set_beacon(struct sk_buff *skb, struct genl_info *info) if (err) goto out; + err = nl80211_calculate_ap_operation(info, ¶ms->beacon); + if (err) + goto out; + + err = nl80211_validate_ap_phy_operation(wdev->links[link_id].ap.chandef.chan, + ¶ms->beacon); + if (err) + goto out; + /* recheck beaconing is permitted with possibly changed power type */ beacon_check.iftype = wdev->iftype; beacon_check.relax = true; -- cgit v1.2.3 From 0b8201bc23425104cf1ca4de9470bad2456bd2be Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 15 Apr 2026 14:42:15 +0200 Subject: wifi: cfg80211: provide HT/VHT operation for AP beacon In addition to providing HE/EHT/UHR operation, also check and provide HT/VHT operation, so that drivers have it and can use it, e.g. to correctly calculate station bandwidth. Reviewed-by: Miriam Rachel Korenblit Link: https://patch.msgid.link/20260415144514.32ad98454543.Ia9692671b699164edcc0bdaf4fdbdbefc50b18f8@changeid Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 4 ++++ net/wireless/nl80211.c | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+) (limited to 'include') diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 7070e577342b..a40ab36b8edb 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -1398,6 +1398,8 @@ struct cfg80211_rnr_elems { * attribute is present in beacon data or not. * @ht_required: stations must support HT * @vht_required: stations must support VHT + * @ht_oper: HT operation element (or %NULL if HT isn't enabled) + * @vht_oper: VHT operation element (or %NULL if VHT isn't enabled) * @he_oper: HE operation IE (or %NULL if HE isn't enabled) * @eht_oper: EHT operation IE (or %NULL if EHT isn't enabled) * @uhr_oper: UHR operation (or %NULL if UHR isn't enabled) @@ -1427,6 +1429,8 @@ struct cfg80211_beacon_data { bool he_bss_color_valid; bool ht_required, vht_required; + const struct ieee80211_ht_operation *ht_oper; + const struct ieee80211_vht_operation *vht_oper; const struct ieee80211_he_operation *he_oper; const struct ieee80211_eht_operation *eht_oper; const struct ieee80211_uhr_operation *uhr_oper; diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index a4dbe6ff0045..056c779fdb56 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -6730,6 +6730,24 @@ static int nl80211_calculate_ap_operation(struct genl_info *info, } } + op = cfg80211_find_elem(WLAN_EID_HT_OPERATION, ies, ies_len); + if (op) { + if (op->datalen < sizeof(*bcn->ht_oper)) { + GENL_SET_ERR_MSG(info, "bad HT operation in beacon"); + return -EINVAL; + } + bcn->ht_oper = (void *)op->data; + } + + op = cfg80211_find_elem(WLAN_EID_VHT_OPERATION, ies, ies_len); + if (op) { + if (op->datalen < sizeof(*bcn->vht_oper)) { + GENL_SET_ERR_MSG(info, "bad VHT operation in beacon"); + return -EINVAL; + } + bcn->vht_oper = (void *)op->data; + } + op = cfg80211_find_ext_elem(WLAN_EID_EXT_EHT_OPERATION, ies, ies_len); if (op) { if (!ieee80211_eht_oper_size_ok(op->data + 1, -- cgit v1.2.3 From 5ffd937ea4050e995bd1fabc54a2534bb7bfa0d9 Mon Sep 17 00:00:00 2001 From: Moshe Shemesh Date: Tue, 28 Apr 2026 08:38:48 +0300 Subject: mlx5: Rename the vport number enums for host PF and VF Rename the vport number enums MLX5_VPORT_PF to MLX5_VPORT_HOST_PF and MLX5_VPORT_FIRST_VF to MLX5_VPORT_FIRST_HOST_VF to indicate that these vport indices represent the host PF and its VFs. This prepares the code for upcoming support of an additional PF type. Signed-off-by: Moshe Shemesh Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260428053851.220089-2-tariqt@nvidia.com Signed-off-by: Leon Romanovsky --- drivers/infiniband/hw/mlx5/counters.c | 4 ++-- .../ethernet/mellanox/mlx5/core/esw/devlink_port.c | 7 ++++--- .../net/ethernet/mellanox/mlx5/core/esw/ipsec.c | 2 +- drivers/net/ethernet/mellanox/mlx5/core/eswitch.c | 22 +++++++++++----------- drivers/net/ethernet/mellanox/mlx5/core/eswitch.h | 2 +- .../ethernet/mellanox/mlx5/core/eswitch_offloads.c | 17 ++++++++++------- .../mellanox/mlx5/core/steering/hws/vport.c | 2 +- include/linux/mlx5/eswitch.h | 2 +- include/linux/mlx5/vport.h | 4 ++-- 9 files changed, 33 insertions(+), 29 deletions(-) (limited to 'include') diff --git a/drivers/infiniband/hw/mlx5/counters.c b/drivers/infiniband/hw/mlx5/counters.c index 5b4482dd6274..5a79e834ddea 100644 --- a/drivers/infiniband/hw/mlx5/counters.c +++ b/drivers/infiniband/hw/mlx5/counters.c @@ -697,7 +697,7 @@ static void mlx5_ib_fill_counters(struct mlx5_ib_dev *dev, u32 port_num) { bool is_vport = is_mdev_switchdev_mode(dev->mdev) && - port_num != MLX5_VPORT_PF; + port_num != MLX5_VPORT_HOST_PF; const struct mlx5_ib_counter *names; int j = 0, i, size; @@ -802,7 +802,7 @@ static int __mlx5_ib_alloc_counters(struct mlx5_ib_dev *dev, struct mlx5_ib_counters *cnts, u32 port_num) { bool is_vport = is_mdev_switchdev_mode(dev->mdev) && - port_num != MLX5_VPORT_PF; + port_num != MLX5_VPORT_HOST_PF; u32 num_counters, num_op_counters = 0, size; size = is_vport ? ARRAY_SIZE(vport_basic_q_cnts) : diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/devlink_port.c b/drivers/net/ethernet/mellanox/mlx5/core/esw/devlink_port.c index e1d11326af1b..8a79764345e7 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/devlink_port.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/devlink_port.c @@ -13,7 +13,8 @@ mlx5_esw_get_port_parent_id(struct mlx5_core_dev *dev, struct netdev_phys_item_i static bool mlx5_esw_devlink_port_supported(struct mlx5_eswitch *esw, u16 vport_num) { - return (mlx5_core_is_ecpf(esw->dev) && vport_num == MLX5_VPORT_PF) || + return (mlx5_core_is_ecpf(esw->dev) && + vport_num == MLX5_VPORT_HOST_PF) || mlx5_eswitch_is_vf_vport(esw, vport_num) || mlx5_core_is_ec_vf_vport(esw->dev, vport_num); } @@ -35,7 +36,7 @@ static void mlx5_esw_offloads_pf_vf_devlink_port_attrs_set(struct mlx5_eswitch * if (external) controller_num = dev->priv.eswitch->offloads.host_number + 1; - if (vport_num == MLX5_VPORT_PF) { + if (vport_num == MLX5_VPORT_HOST_PF) { memcpy(dl_port->attrs.switch_id.id, ppid.id, ppid.id_len); dl_port->attrs.switch_id.id_len = ppid.id_len; devlink_port_attrs_pci_pf_set(dl_port, controller_num, pfnum, external); @@ -216,7 +217,7 @@ int mlx5_esw_offloads_devlink_port_register(struct mlx5_eswitch *esw, struct mlx if (err) goto rate_err; - if (vport_num == MLX5_VPORT_PF) { + if (vport_num == MLX5_VPORT_HOST_PF) { err = mlx5_esw_devlink_port_res_register(esw, &dl_port->dl_port); if (err) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/esw/ipsec.c b/drivers/net/ethernet/mellanox/mlx5/core/esw/ipsec.c index da10e04777cf..8b12c3ae0cf7 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/esw/ipsec.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/esw/ipsec.c @@ -209,7 +209,7 @@ static int esw_ipsec_vf_offload_set_bytype(struct mlx5_eswitch *esw, struct mlx5 struct mlx5_core_dev *dev = esw->dev; int err; - if (vport->vport == MLX5_VPORT_PF) + if (vport->vport == MLX5_VPORT_HOST_PF) return -EOPNOTSUPP; if (type == MLX5_ESW_VPORT_IPSEC_CRYPTO_OFFLOAD) { diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c index 123c96716a54..80ba360347e7 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c @@ -926,7 +926,7 @@ int mlx5_esw_vport_enable(struct mlx5_eswitch *esw, struct mlx5_vport *vport, /* Sync with current vport context */ vport->enabled_events = enabled_events; vport->enabled = true; - if (vport->vport != MLX5_VPORT_PF && + if (vport->vport != MLX5_VPORT_HOST_PF && (vport->info.ipsec_crypto_enabled || vport->info.ipsec_packet_enabled)) esw->enabled_ipsec_vf_count++; @@ -979,7 +979,7 @@ void mlx5_esw_vport_disable(struct mlx5_eswitch *esw, struct mlx5_vport *vport) MLX5_CAP_GEN(esw->dev, vhca_resource_manager)) mlx5_esw_vport_vhca_id_unmap(esw, vport); - if (vport->vport != MLX5_VPORT_PF && + if (vport->vport != MLX5_VPORT_HOST_PF && (vport->info.ipsec_crypto_enabled || vport->info.ipsec_packet_enabled)) esw->enabled_ipsec_vf_count--; @@ -1314,7 +1314,7 @@ int mlx5_esw_host_pf_enable_hca(struct mlx5_core_dev *dev) if (!mlx5_core_is_ecpf(dev) || !mlx5_esw_allowed(esw)) return 0; - vport = mlx5_eswitch_get_vport(esw, MLX5_VPORT_PF); + vport = mlx5_eswitch_get_vport(esw, MLX5_VPORT_HOST_PF); if (IS_ERR(vport)) return PTR_ERR(vport); @@ -1340,7 +1340,7 @@ int mlx5_esw_host_pf_disable_hca(struct mlx5_core_dev *dev) if (!mlx5_core_is_ecpf(dev) || !mlx5_esw_allowed(esw)) return 0; - vport = mlx5_eswitch_get_vport(esw, MLX5_VPORT_PF); + vport = mlx5_eswitch_get_vport(esw, MLX5_VPORT_HOST_PF); if (IS_ERR(vport)) return PTR_ERR(vport); @@ -1368,7 +1368,7 @@ mlx5_eswitch_enable_pf_vf_vports(struct mlx5_eswitch *esw, /* Enable PF vport */ if (pf_needed && mlx5_esw_host_functions_enabled(esw->dev)) { - ret = mlx5_eswitch_load_pf_vf_vport(esw, MLX5_VPORT_PF, + ret = mlx5_eswitch_load_pf_vf_vport(esw, MLX5_VPORT_HOST_PF, enabled_events); if (ret) return ret; @@ -1423,7 +1423,7 @@ ecpf_err: mlx5_esw_host_pf_disable_hca(esw->dev); pf_hca_err: if (pf_needed && mlx5_esw_host_functions_enabled(esw->dev)) - mlx5_eswitch_unload_pf_vf_vport(esw, MLX5_VPORT_PF); + mlx5_eswitch_unload_pf_vf_vport(esw, MLX5_VPORT_HOST_PF); return ret; } @@ -1450,7 +1450,7 @@ void mlx5_eswitch_disable_pf_vf_vports(struct mlx5_eswitch *esw) if ((mlx5_core_is_ecpf_esw_manager(esw->dev) || esw->mode == MLX5_ESWITCH_LEGACY) && mlx5_esw_host_functions_enabled(esw->dev)) - mlx5_eswitch_unload_pf_vf_vport(esw, MLX5_VPORT_PF); + mlx5_eswitch_unload_pf_vf_vport(esw, MLX5_VPORT_HOST_PF); } static void mlx5_eswitch_get_devlink_param(struct mlx5_eswitch *esw) @@ -1822,7 +1822,7 @@ static int mlx5_query_hca_cap_host_pf(struct mlx5_core_dev *dev, void *out) MLX5_SET(query_hca_cap_in, in, opcode, MLX5_CMD_OP_QUERY_HCA_CAP); MLX5_SET(query_hca_cap_in, in, op_mod, opmod); - MLX5_SET(query_hca_cap_in, in, function_id, MLX5_VPORT_PF); + MLX5_SET(query_hca_cap_in, in, function_id, MLX5_VPORT_HOST_PF); MLX5_SET(query_hca_cap_in, in, other_function, true); return mlx5_cmd_exec_inout(dev, query_hca_cap, in, out); } @@ -1914,10 +1914,10 @@ static int mlx5_esw_vports_init(struct mlx5_eswitch *esw) xa_init(&esw->vports); if (mlx5_esw_host_functions_enabled(dev)) { - err = mlx5_esw_vport_alloc(esw, idx, MLX5_VPORT_PF); + err = mlx5_esw_vport_alloc(esw, idx, MLX5_VPORT_HOST_PF); if (err) goto err; - if (esw->first_host_vport == MLX5_VPORT_PF) + if (esw->first_host_vport == MLX5_VPORT_HOST_PF) xa_set_mark(&esw->vports, idx, MLX5_ESW_VPT_HOST_FN); idx++; for (i = 0; i < mlx5_core_max_vfs(dev); i++) { @@ -2195,7 +2195,7 @@ bool mlx5_eswitch_is_vf_vport(struct mlx5_eswitch *esw, u16 vport_num) bool mlx5_eswitch_is_pf_vf_vport(struct mlx5_eswitch *esw, u16 vport_num) { - return vport_num == MLX5_VPORT_PF || + return vport_num == MLX5_VPORT_HOST_PF || mlx5_eswitch_is_vf_vport(esw, vport_num); } diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h index 5128f5020dae..f6a23930f308 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h @@ -684,7 +684,7 @@ static inline bool mlx5_esw_is_owner(struct mlx5_eswitch *esw, u16 vport_num, static inline u16 mlx5_eswitch_first_host_vport_num(struct mlx5_core_dev *dev) { return mlx5_core_is_ecpf_esw_manager(dev) ? - MLX5_VPORT_PF : MLX5_VPORT_FIRST_VF; + MLX5_VPORT_HOST_PF : MLX5_VPORT_FIRST_HOST_VF; } static inline bool mlx5_eswitch_is_funcs_handler(const struct mlx5_core_dev *dev) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c b/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c index a078d06f4567..c32335df6b64 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c @@ -1216,9 +1216,10 @@ static int esw_add_fdb_peer_miss_rules(struct mlx5_eswitch *esw, if (mlx5_core_is_ecpf_esw_manager(peer_dev) && mlx5_esw_host_functions_enabled(peer_dev)) { - peer_vport = mlx5_eswitch_get_vport(peer_esw, MLX5_VPORT_PF); + peer_vport = mlx5_eswitch_get_vport(peer_esw, + MLX5_VPORT_HOST_PF); esw_set_peer_miss_rule_source_port(esw, peer_esw, spec, - MLX5_VPORT_PF); + MLX5_VPORT_HOST_PF); flow = mlx5_add_flow_rules(mlx5_eswitch_get_slow_fdb(esw), spec, &flow_act, &dest, 1); @@ -1300,7 +1301,8 @@ add_ecpf_flow_err: if (mlx5_core_is_ecpf_esw_manager(peer_dev) && mlx5_esw_host_functions_enabled(peer_dev)) { - peer_vport = mlx5_eswitch_get_vport(peer_esw, MLX5_VPORT_PF); + peer_vport = mlx5_eswitch_get_vport(peer_esw, + MLX5_VPORT_HOST_PF); mlx5_del_flow_rules(flows[peer_vport->index]); } add_pf_flow_err: @@ -1342,7 +1344,8 @@ static void esw_del_fdb_peer_miss_rules(struct mlx5_eswitch *esw, if (mlx5_core_is_ecpf_esw_manager(peer_dev) && mlx5_esw_host_functions_enabled(peer_dev)) { - peer_vport = mlx5_eswitch_get_vport(peer_esw, MLX5_VPORT_PF); + peer_vport = mlx5_eswitch_get_vport(peer_esw, + MLX5_VPORT_HOST_PF); mlx5_del_flow_rules(flows[peer_vport->index]); } @@ -4435,7 +4438,7 @@ static bool mlx5_eswitch_vport_has_rep(const struct mlx5_eswitch *esw, u16 vport_num) { /* Currently, only ECPF based device has representor for host PF. */ - if (vport_num == MLX5_VPORT_PF && + if (vport_num == MLX5_VPORT_HOST_PF && (!mlx5_core_is_ecpf_esw_manager(esw->dev) || !mlx5_esw_host_functions_enabled(esw->dev))) return false; @@ -4791,7 +4794,7 @@ int mlx5_devlink_pf_port_fn_state_get(struct devlink_port *port, const u32 *query_out; bool pf_disabled; - if (vport->vport != MLX5_VPORT_PF) { + if (vport->vport != MLX5_VPORT_HOST_PF) { NL_SET_ERR_MSG_MOD(extack, "State get is not supported for VF"); return -EOPNOTSUPP; } @@ -4820,7 +4823,7 @@ int mlx5_devlink_pf_port_fn_state_set(struct devlink_port *port, struct mlx5_vport *vport = mlx5_devlink_port_vport_get(port); struct mlx5_core_dev *dev; - if (vport->vport != MLX5_VPORT_PF) { + if (vport->vport != MLX5_VPORT_HOST_PF) { NL_SET_ERR_MSG_MOD(extack, "State set is not supported for VF"); return -EOPNOTSUPP; } diff --git a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/vport.c b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/vport.c index d8e382b9fa61..6dc3b11b7926 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/vport.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/steering/hws/vport.c @@ -50,7 +50,7 @@ static int hws_vport_add_gvmi(struct mlx5hws_context *ctx, u16 vport) static bool hws_vport_is_esw_mgr_vport(struct mlx5hws_context *ctx, u16 vport) { return ctx->caps->is_ecpf ? vport == MLX5_VPORT_ECPF : - vport == MLX5_VPORT_PF; + vport == MLX5_VPORT_HOST_PF; } int mlx5hws_vport_get_gvmi(struct mlx5hws_context *ctx, u16 vport, u16 *vport_gvmi) diff --git a/include/linux/mlx5/eswitch.h b/include/linux/mlx5/eswitch.h index 67256e776566..3b29a3c6794d 100644 --- a/include/linux/mlx5/eswitch.h +++ b/include/linux/mlx5/eswitch.h @@ -217,7 +217,7 @@ static inline bool is_mdev_switchdev_mode(struct mlx5_core_dev *dev) static inline u16 mlx5_eswitch_manager_vport(struct mlx5_core_dev *dev) { return mlx5_core_is_ecpf_esw_manager(dev) ? - MLX5_VPORT_ECPF : MLX5_VPORT_PF; + MLX5_VPORT_ECPF : MLX5_VPORT_HOST_PF; } #endif diff --git a/include/linux/mlx5/vport.h b/include/linux/mlx5/vport.h index dfa2fe32217a..90641f67da46 100644 --- a/include/linux/mlx5/vport.h +++ b/include/linux/mlx5/vport.h @@ -51,8 +51,8 @@ enum { /* Vport number for each function must keep unchanged */ enum { - MLX5_VPORT_PF = 0x0, - MLX5_VPORT_FIRST_VF = 0x1, + MLX5_VPORT_HOST_PF = 0x0, + MLX5_VPORT_FIRST_HOST_VF = 0x1, MLX5_VPORT_ECPF = 0xfffe, MLX5_VPORT_UPLINK = 0xffff }; -- cgit v1.2.3 From a750f4674a63382a57561257877754ff0b4b2ca6 Mon Sep 17 00:00:00 2001 From: Moshe Shemesh Date: Tue, 28 Apr 2026 08:38:49 +0300 Subject: net/mlx5: Add function_id_type for enable/disable_hca cmds Add a function_id_type field to the enable_hca and disable_hca command input layouts in mlx5_ifc.h to allow using vhca_id as the function index instead of function_id. The new field support by firmware is indicated by the function_id_type_vhca_id capability bit, which is already exposed in hca caps. Signed-off-by: Moshe Shemesh Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260428053851.220089-3-tariqt@nvidia.com Signed-off-by: Leon Romanovsky --- include/linux/mlx5/mlx5_ifc.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/linux/mlx5/mlx5_ifc.h b/include/linux/mlx5/mlx5_ifc.h index 49f3ad4b1a7c..06ec1f5d2c6c 100644 --- a/include/linux/mlx5/mlx5_ifc.h +++ b/include/linux/mlx5/mlx5_ifc.h @@ -8452,7 +8452,9 @@ struct mlx5_ifc_enable_hca_in_bits { u8 op_mod[0x10]; u8 embedded_cpu_function[0x1]; - u8 reserved_at_41[0xf]; + u8 reserved_at_41[0x2]; + u8 function_id_type[0x1]; + u8 reserved_at_44[0xc]; u8 function_id[0x10]; u8 reserved_at_60[0x20]; @@ -8497,7 +8499,9 @@ struct mlx5_ifc_disable_hca_in_bits { u8 op_mod[0x10]; u8 embedded_cpu_function[0x1]; - u8 reserved_at_41[0xf]; + u8 reserved_at_41[0x2]; + u8 function_id_type[0x1]; + u8 reserved_at_44[0xc]; u8 function_id[0x10]; u8 reserved_at_60[0x20]; -- cgit v1.2.3 From e2337517e127b7064d1cb1d49fc2d1e0e134690c Mon Sep 17 00:00:00 2001 From: Moshe Shemesh Date: Tue, 28 Apr 2026 08:38:50 +0300 Subject: net/mlx5: Remove unused host_sf_enable field Drop the unused host_sf_enable array from mlx5_ifc_query_esw_functions_out_bits layout. This field has been deprecated in firmware and is not referenced by the mlx5 driver, so it can be safely removed. Signed-off-by: Moshe Shemesh Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260428053851.220089-4-tariqt@nvidia.com Signed-off-by: Leon Romanovsky --- include/linux/mlx5/mlx5_ifc.h | 1 - 1 file changed, 1 deletion(-) (limited to 'include') diff --git a/include/linux/mlx5/mlx5_ifc.h b/include/linux/mlx5/mlx5_ifc.h index 06ec1f5d2c6c..02b57b2286da 100644 --- a/include/linux/mlx5/mlx5_ifc.h +++ b/include/linux/mlx5/mlx5_ifc.h @@ -12725,7 +12725,6 @@ struct mlx5_ifc_query_esw_functions_out_bits { struct mlx5_ifc_host_params_context_bits host_params_context; u8 reserved_at_280[0x180]; - u8 host_sf_enable[][0x40]; }; struct mlx5_ifc_sf_partition_bits { -- cgit v1.2.3 From 02c54621e81ccdc1907e2d735bcda751f2caade1 Mon Sep 17 00:00:00 2001 From: Moshe Shemesh Date: Tue, 28 Apr 2026 08:38:51 +0300 Subject: net/mlx5: Extend query_esw_functions output for multi-function support Update the query_esw_functions command to support a new response layout that can report data for multiple network functions. Setting bit 14 of the op_mod field selects the v1 layout with network_function_params entries instead of the legacy host_params_context. The query_host_net_function_v1 read-only capability indicates firmware support for layout version 1, and query_host_net_function_num_max advertises the maximum number of network function entries. Define a new network_function_params layout and a net_function_params union that groups host_params_context and network_function_params. Rework the query_esw_functions output to use a flexible array of this union, and adjust existing driver callers to use it. Signed-off-by: Moshe Shemesh Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260428053851.220089-5-tariqt@nvidia.com Signed-off-by: Leon Romanovsky --- drivers/net/ethernet/mellanox/mlx5/core/eswitch.c | 14 +++-- .../ethernet/mellanox/mlx5/core/eswitch_offloads.c | 25 ++++++--- .../mellanox/mlx5/core/sf/mlx5_ifc_vhca_event.h | 8 --- drivers/net/ethernet/mellanox/mlx5/core/sriov.c | 7 ++- include/linux/mlx5/mlx5_ifc.h | 64 ++++++++++++++++++++-- 5 files changed, 91 insertions(+), 27 deletions(-) (limited to 'include') diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c index 80ba360347e7..408f729d8914 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c @@ -1045,6 +1045,7 @@ const u32 *mlx5_esw_query_functions(struct mlx5_core_dev *dev) static int mlx5_esw_host_functions_enabled_query(struct mlx5_eswitch *esw) { const u32 *query_host_out; + void *host_params; if (!mlx5_core_is_ecpf_esw_manager(esw->dev)) return 0; @@ -1053,9 +1054,11 @@ static int mlx5_esw_host_functions_enabled_query(struct mlx5_eswitch *esw) if (IS_ERR(query_host_out)) return PTR_ERR(query_host_out); + host_params = MLX5_ADDR_OF(query_esw_functions_out, + query_host_out, net_function_params); esw->esw_funcs.host_funcs_disabled = - MLX5_GET(query_esw_functions_out, query_host_out, - host_params_context.host_pf_not_exist); + MLX5_GET(host_params_context, host_params, + host_pf_not_exist); kvfree(query_host_out); return 0; @@ -1475,6 +1478,7 @@ static void mlx5_eswitch_get_devlink_param(struct mlx5_eswitch *esw) static void mlx5_eswitch_update_num_of_vfs(struct mlx5_eswitch *esw, int num_vfs) { + void *host_params; const u32 *out; if (num_vfs < 0) @@ -1489,8 +1493,10 @@ mlx5_eswitch_update_num_of_vfs(struct mlx5_eswitch *esw, int num_vfs) if (IS_ERR(out)) return; - esw->esw_funcs.num_vfs = MLX5_GET(query_esw_functions_out, out, - host_params_context.host_num_of_vfs); + host_params = MLX5_ADDR_OF(query_esw_functions_out, out, + net_function_params); + esw->esw_funcs.num_vfs = MLX5_GET(host_params_context, host_params, + host_num_of_vfs); if (mlx5_core_ec_sriov_enabled(esw->dev)) esw->esw_funcs.num_ec_vfs = num_vfs; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c b/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c index c32335df6b64..b859aa5062ca 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c @@ -3664,6 +3664,7 @@ esw_vfs_changed_event_handler(struct mlx5_eswitch *esw, int work_gen, { struct devlink *devlink; bool host_pf_disabled; + void *host_params; u16 new_num_vfs; devlink = priv_to_devlink(esw->dev); @@ -3673,10 +3674,12 @@ esw_vfs_changed_event_handler(struct mlx5_eswitch *esw, int work_gen, if (work_gen != atomic_read(&esw->esw_funcs.generation)) goto unlock; - new_num_vfs = MLX5_GET(query_esw_functions_out, out, - host_params_context.host_num_of_vfs); - host_pf_disabled = MLX5_GET(query_esw_functions_out, out, - host_params_context.host_pf_disabled); + host_params = MLX5_ADDR_OF(query_esw_functions_out, out, + net_function_params); + new_num_vfs = MLX5_GET(host_params_context, host_params, + host_num_of_vfs); + host_pf_disabled = MLX5_GET(host_params_context, host_params, + host_pf_disabled); if (new_num_vfs == esw->esw_funcs.num_vfs || host_pf_disabled) goto unlock; @@ -3743,6 +3746,7 @@ int mlx5_esw_funcs_changed_handler(struct notifier_block *nb, unsigned long type static int mlx5_esw_host_number_init(struct mlx5_eswitch *esw) { const u32 *query_host_out; + void *host_params; if (!mlx5_core_is_ecpf_esw_manager(esw->dev)) return 0; @@ -3752,8 +3756,10 @@ static int mlx5_esw_host_number_init(struct mlx5_eswitch *esw) return PTR_ERR(query_host_out); /* Mark non local controller with non zero controller number. */ - esw->offloads.host_number = MLX5_GET(query_esw_functions_out, query_host_out, - host_params_context.host_number); + host_params = MLX5_ADDR_OF(query_esw_functions_out, + query_host_out, net_function_params); + esw->offloads.host_number = MLX5_GET(host_params_context, + host_params, host_number); kvfree(query_host_out); return 0; } @@ -4792,6 +4798,7 @@ int mlx5_devlink_pf_port_fn_state_get(struct devlink_port *port, { struct mlx5_vport *vport = mlx5_devlink_port_vport_get(port); const u32 *query_out; + void *host_params; bool pf_disabled; if (vport->vport != MLX5_VPORT_HOST_PF) { @@ -4806,8 +4813,10 @@ int mlx5_devlink_pf_port_fn_state_get(struct devlink_port *port, if (IS_ERR(query_out)) return PTR_ERR(query_out); - pf_disabled = MLX5_GET(query_esw_functions_out, query_out, - host_params_context.host_pf_disabled); + host_params = MLX5_ADDR_OF(query_esw_functions_out, query_out, + net_function_params); + pf_disabled = MLX5_GET(host_params_context, host_params, + host_pf_disabled); *opstate = pf_disabled ? DEVLINK_PORT_FN_OPSTATE_DETACHED : DEVLINK_PORT_FN_OPSTATE_ATTACHED; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/sf/mlx5_ifc_vhca_event.h b/drivers/net/ethernet/mellanox/mlx5/core/sf/mlx5_ifc_vhca_event.h index 4fc870140d71..487c94b56203 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/sf/mlx5_ifc_vhca_event.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/sf/mlx5_ifc_vhca_event.h @@ -4,14 +4,6 @@ #ifndef __MLX5_IFC_VHCA_EVENT_H__ #define __MLX5_IFC_VHCA_EVENT_H__ -enum mlx5_ifc_vhca_state { - MLX5_VHCA_STATE_INVALID = 0x0, - MLX5_VHCA_STATE_ALLOCATED = 0x1, - MLX5_VHCA_STATE_ACTIVE = 0x2, - MLX5_VHCA_STATE_IN_USE = 0x3, - MLX5_VHCA_STATE_TEARDOWN_REQUEST = 0x4, -}; - struct mlx5_ifc_vhca_state_context_bits { u8 arm_change_event[0x1]; u8 reserved_at_1[0xb]; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/sriov.c b/drivers/net/ethernet/mellanox/mlx5/core/sriov.c index bf6f631cf2ce..6eb6026eadd6 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/sriov.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/sriov.c @@ -274,6 +274,7 @@ void mlx5_sriov_detach(struct mlx5_core_dev *dev) static u16 mlx5_get_max_vfs(struct mlx5_core_dev *dev) { u16 host_total_vfs; + void *host_params; const u32 *out; if (mlx5_core_is_ecpf_esw_manager(dev)) { @@ -284,8 +285,10 @@ static u16 mlx5_get_max_vfs(struct mlx5_core_dev *dev) */ if (IS_ERR(out)) goto done; - host_total_vfs = MLX5_GET(query_esw_functions_out, out, - host_params_context.host_total_vfs); + host_params = MLX5_ADDR_OF(query_esw_functions_out, out, + net_function_params); + host_total_vfs = MLX5_GET(host_params_context, host_params, + host_total_vfs); kvfree(out); return host_total_vfs; } diff --git a/include/linux/mlx5/mlx5_ifc.h b/include/linux/mlx5/mlx5_ifc.h index 02b57b2286da..6a675f918c40 100644 --- a/include/linux/mlx5/mlx5_ifc.h +++ b/include/linux/mlx5/mlx5_ifc.h @@ -1935,7 +1935,8 @@ struct mlx5_ifc_cmd_hca_cap_bits { u8 max_flow_counter_31_16[0x10]; u8 max_wqe_sz_sq_dc[0x10]; - u8 reserved_at_2e0[0x7]; + u8 query_host_net_function_num_max[0x5]; + u8 reserved_at_2e5[0x2]; u8 max_qp_mcg[0x19]; u8 reserved_at_300[0x10]; @@ -2027,7 +2028,7 @@ struct mlx5_ifc_cmd_hca_cap_bits { u8 log_max_current_mc_list[0x5]; u8 reserved_at_3f8[0x1]; u8 silent_mode_query[0x1]; - u8 reserved_at_3fa[0x1]; + u8 query_host_net_function_v1[0x1]; u8 log_max_current_uc_list[0x5]; u8 general_obj_types[0x40]; @@ -12704,6 +12705,54 @@ struct mlx5_ifc_host_params_context_bits { u8 reserved_at_80[0x180]; }; +enum mlx5_ifc_vhca_state { + MLX5_VHCA_STATE_INVALID = 0x0, + MLX5_VHCA_STATE_ALLOCATED = 0x1, + MLX5_VHCA_STATE_ACTIVE = 0x2, + MLX5_VHCA_STATE_IN_USE = 0x3, + MLX5_VHCA_STATE_TEARDOWN_REQUEST = 0x4, +}; + +enum { + MLX5_PCI_PF_TYPE_EXTERNAL_HOST_PF = 0x0, + MLX5_PCI_PF_TYPE_SATELLITE_PF = 0x1, +}; + +struct mlx5_ifc_network_function_params_bits { + u8 host_number[0x8]; + u8 pci_pf_type[0x4]; + u8 reserved_at_c[0x4]; + u8 pci_num_vfs[0x10]; + + u8 pci_total_vfs[0x10]; + u8 pci_bus[0x8]; + u8 pci_device_function[0x8]; + + u8 vhca_id[0x10]; + u8 vhca_state[0x4]; + u8 reserved_at_54[0xc]; + + u8 reserved_at_60[0xa]; + u8 esw_vport_manual[0x1]; + u8 pci_bus_assigned[0x1]; + u8 pci_vf_info_valid[0x1]; + u8 reserved_at_6d[0x13]; + + u8 pci_vf_stride[0x10]; + u8 pci_first_vf_offset[0x10]; + + u8 reserved_at_a0[0x160]; +}; + +union mlx5_ifc_net_function_params_bits { + struct mlx5_ifc_host_params_context_bits host_params_context; + struct mlx5_ifc_network_function_params_bits network_function_params; +}; + +enum { + MLX5_QUERY_ESW_FUNC_OP_MOD_LAYOUT_V1 = BIT(14), +}; + struct mlx5_ifc_query_esw_functions_in_bits { u8 opcode[0x10]; u8 reserved_at_10[0x10]; @@ -12720,11 +12769,16 @@ struct mlx5_ifc_query_esw_functions_out_bits { u8 syndrome[0x20]; - u8 reserved_at_40[0x40]; + u8 reserved_at_40[0x20]; - struct mlx5_ifc_host_params_context_bits host_params_context; + u8 net_function_num[0x8]; + u8 reserved_at_68[0x18]; - u8 reserved_at_280[0x180]; + union { + u8 reserved_at_80[0x380]; + DECLARE_FLEX_ARRAY(union mlx5_ifc_net_function_params_bits, + net_function_params); + }; }; struct mlx5_ifc_sf_partition_bits { -- cgit v1.2.3 From 52472519ef61c62bdac8f0ffa10268b5bf123cc4 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Tue, 28 Apr 2026 07:09:19 +0000 Subject: net/sched: rename qstats_overlimit_inc() to qstats_cpu_overlimit_inc() qstats_overlimit_inc() is only used to increment per cpu overlimits. It can use this_cpu_inc() to avoid this_cpu_ptr() extra cost and avoid potential store tearing. Change qstats_overlimit_inc() name and its argument type. Also add a WRITE_ONCE() in qdisc_qstats_overlimit() to prevent store tearing. $ scripts/bloat-o-meter -t vmlinux.0 vmlinux.1 add/remove: 0/0 grow/shrink: 0/7 up/down: 0/-91 (-91) Function old new delta tcf_skbmod_act 772 764 -8 tcf_police_act 733 725 -8 tcf_gate_act 318 310 -8 tcf_pedit_act 1295 1284 -11 tcf_mirred_to_dev 1126 1114 -12 tcf_ife_act 1077 1061 -16 tcf_mirred_act 1324 1296 -28 Total: Before=24274627, After=24274536, chg -0.00% Signed-off-by: Eric Dumazet Reviewed-by: Jamal Hadi Salim Link: https://patch.msgid.link/20260428070919.3109557-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- include/net/act_api.h | 2 +- include/net/sch_generic.h | 6 +++--- net/sched/act_ife.c | 4 ++-- net/sched/act_police.c | 2 +- net/sched/act_skbmod.c | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) (limited to 'include') diff --git a/include/net/act_api.h b/include/net/act_api.h index d11b79107930..2ec4ef9a5d0c 100644 --- a/include/net/act_api.h +++ b/include/net/act_api.h @@ -250,7 +250,7 @@ static inline void tcf_action_inc_drop_qstats(struct tc_action *a) static inline void tcf_action_inc_overlimit_qstats(struct tc_action *a) { if (likely(a->cpu_qstats)) { - qstats_overlimit_inc(this_cpu_ptr(a->cpu_qstats)); + qstats_cpu_overlimit_inc(a->cpu_qstats); return; } atomic_inc(&a->tcfa_overlimits); diff --git a/include/net/sch_generic.h b/include/net/sch_generic.h index 11159a50d6a1..cbfe9ed435fd 100644 --- a/include/net/sch_generic.h +++ b/include/net/sch_generic.h @@ -1004,9 +1004,9 @@ static inline void qstats_drop_inc(struct gnet_stats_queue *qstats) qstats->drops++; } -static inline void qstats_overlimit_inc(struct gnet_stats_queue *qstats) +static inline void qstats_cpu_overlimit_inc(struct gnet_stats_queue __percpu *qstats) { - qstats->overlimits++; + this_cpu_inc(qstats->overlimits); } static inline void qdisc_qstats_drop(struct Qdisc *sch) @@ -1021,7 +1021,7 @@ static inline void qdisc_qstats_cpu_drop(struct Qdisc *sch) static inline void qdisc_qstats_overlimit(struct Qdisc *sch) { - sch->qstats.overlimits++; + WRITE_ONCE(sch->qstats.overlimits, sch->qstats.overlimits + 1); } static inline int qdisc_qstats_copy(struct gnet_dump *d, struct Qdisc *sch) diff --git a/net/sched/act_ife.c b/net/sched/act_ife.c index d5e8a91bb4eb..e1b825e14900 100644 --- a/net/sched/act_ife.c +++ b/net/sched/act_ife.c @@ -750,7 +750,7 @@ static int tcf_ife_decode(struct sk_buff *skb, const struct tc_action *a, */ pr_info_ratelimited("Unknown metaid %d dlen %d\n", mtype, dlen); - qstats_overlimit_inc(this_cpu_ptr(ife->common.cpu_qstats)); + qstats_cpu_overlimit_inc(ife->common.cpu_qstats); } } @@ -814,7 +814,7 @@ static int tcf_ife_encode(struct sk_buff *skb, const struct tc_action *a, /* abuse overlimits to count when we allow packet * with no metadata */ - qstats_overlimit_inc(this_cpu_ptr(ife->common.cpu_qstats)); + qstats_cpu_overlimit_inc(ife->common.cpu_qstats); return action; } /* could be stupid policy setup or mtu config diff --git a/net/sched/act_police.c b/net/sched/act_police.c index 12ea9e5a6005..8060f43e4d11 100644 --- a/net/sched/act_police.c +++ b/net/sched/act_police.c @@ -307,7 +307,7 @@ TC_INDIRECT_SCOPE int tcf_police_act(struct sk_buff *skb, } inc_overlimits: - qstats_overlimit_inc(this_cpu_ptr(police->common.cpu_qstats)); + qstats_cpu_overlimit_inc(police->common.cpu_qstats); inc_drops: if (ret == TC_ACT_SHOT) qstats_drop_inc(this_cpu_ptr(police->common.cpu_qstats)); diff --git a/net/sched/act_skbmod.c b/net/sched/act_skbmod.c index 23ca46138f04..a464b0a3c1b8 100644 --- a/net/sched/act_skbmod.c +++ b/net/sched/act_skbmod.c @@ -87,7 +87,7 @@ out: return p->action; drop: - qstats_overlimit_inc(this_cpu_ptr(d->common.cpu_qstats)); + qstats_cpu_overlimit_inc(d->common.cpu_qstats); return TC_ACT_SHOT; } -- cgit v1.2.3 From 28df22acc2751abf6e6316a9f1f9cd422741bd03 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 29 Apr 2026 01:08:09 +0000 Subject: tcp: add tcp_mstamp_refresh_inline() We want to inline tcp_mstamp_refresh() in fast path only: - tcp_rcv_established() - tcp_write_xmit() Add tcp_mstamp_refresh_inline() for this purpose. Add noinline qualifier on tcp_mstamp_refresh() for the other paths, to reduce bloat. $ scripts/bloat-o-meter -t vmlinux.old vmlinux.new add/remove: 0/0 grow/shrink: 1/4 up/down: 26/-123 (-97) Function old new delta tcp_rcv_established 2238 2264 +26 tcp_connect 4027 4003 -24 tcp_tsq_write 152 120 -32 tcp_send_active_reset 476 444 -32 tcp_send_window_probe 235 200 -35 Total: Before=25316710, After=25316613, chg -0.00% Signed-off-by: Eric Dumazet Reviewed-by: Neal Cardwell Link: https://patch.msgid.link/20260429010809.784315-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- include/net/tcp.h | 10 ++++++++++ net/ipv4/tcp_input.c | 2 +- net/ipv4/tcp_output.c | 12 +++--------- 3 files changed, 14 insertions(+), 10 deletions(-) (limited to 'include') diff --git a/include/net/tcp.h b/include/net/tcp.h index ecbadcb3a744..fb2bc9edc7de 100644 --- a/include/net/tcp.h +++ b/include/net/tcp.h @@ -1015,6 +1015,16 @@ static inline u32 tcp_time_stamp_ts(const struct tcp_sock *tp) return tcp_time_stamp_ms(tp); } +/* Refresh clocks of a TCP socket, + * ensuring monotically increasing values. + */ +static inline void tcp_mstamp_refresh_inline(struct tcp_sock *tp) +{ + u64 val = tcp_clock_ns(); + + tp->tcp_clock_cache = val; + tp->tcp_mstamp = div_u64(val, NSEC_PER_USEC); +} void tcp_mstamp_refresh(struct tcp_sock *tp); static inline u32 tcp_stamp_us_delta(u64 t1, u64 t0) diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c index d5c9e65d9760..7995a89bafc9 100644 --- a/net/ipv4/tcp_input.c +++ b/net/ipv4/tcp_input.c @@ -6474,7 +6474,7 @@ void tcp_rcv_established(struct sock *sk, struct sk_buff *skb) /* TCP congestion window tracking */ trace_tcp_probe(sk, skb); - tcp_mstamp_refresh(tp); + tcp_mstamp_refresh_inline(tp); if (unlikely(!rcu_access_pointer(sk->sk_rx_dst))) inet_csk(sk)->icsk_af_ops->sk_rx_dst_set(sk, skb); /* diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c index f9d8755705f7..c8d7b5d20d18 100644 --- a/net/ipv4/tcp_output.c +++ b/net/ipv4/tcp_output.c @@ -52,15 +52,9 @@ #include -/* Refresh clocks of a TCP socket, - * ensuring monotically increasing values. - */ -void tcp_mstamp_refresh(struct tcp_sock *tp) +void noinline tcp_mstamp_refresh(struct tcp_sock *tp) { - u64 val = tcp_clock_ns(); - - tp->tcp_clock_cache = val; - tp->tcp_mstamp = div_u64(val, NSEC_PER_USEC); + tcp_mstamp_refresh_inline(tp); } static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle, @@ -2971,7 +2965,7 @@ static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle, sent_pkts = 0; - tcp_mstamp_refresh(tp); + tcp_mstamp_refresh_inline(tp); /* AccECN option beacon depends on mstamp, it may change mss */ if (tcp_ecn_mode_accecn(tp) && tcp_accecn_option_beacon_check(sk)) -- cgit v1.2.3 From 068f5a00955675f10348986d4809edc4dbc0cae0 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Mon, 27 Apr 2026 10:27:24 -0700 Subject: net/tcp-ao: Use crypto library API instead of crypto_ahash Currently the kernel's TCP-AO implementation does the MAC and KDF computations using the crypto_ahash API. This API is inefficient and difficult to use, and it has required extensive workarounds in the form of per-CPU preallocated objects (tcp_sigpool) to work at all. Let's use lib/crypto/ instead. This means switching to straightforward stack-allocated structures, virtually addressed buffers, and direct function calls. It also means removing quite a bit of error handling. This makes TCP-AO quite a bit faster. This also enables many additional cleanups, which later commits will handle: removing tcp-sigpool, removing support for crypto_tfm cloning, removing more error handling, and replacing more dynamically-allocated buffers with stack buffers based on the now-statically-known limits. Reviewed-by: Ard Biesheuvel Signed-off-by: Eric Biggers Link: https://patch.msgid.link/20260427172727.9310-3-ebiggers@kernel.org Signed-off-by: Paolo Abeni --- include/net/tcp_ao.h | 32 +- net/ipv4/Kconfig | 5 +- net/ipv4/tcp_ao.c | 523 +++++++++++++++--------------- net/ipv6/tcp_ao.c | 63 ++-- tools/testing/selftests/net/tcp_ao/config | 3 - 5 files changed, 309 insertions(+), 317 deletions(-) (limited to 'include') diff --git a/include/net/tcp_ao.h b/include/net/tcp_ao.h index 1e9e27d6e06b..20997aef3b0d 100644 --- a/include/net/tcp_ao.h +++ b/include/net/tcp_ao.h @@ -2,8 +2,7 @@ #ifndef _TCP_AO_H #define _TCP_AO_H -#define TCP_AO_KEY_ALIGN 1 -#define __tcp_ao_key_align __aligned(TCP_AO_KEY_ALIGN) +#include /* for SHA256_DIGEST_SIZE */ union tcp_ao_addr { struct in_addr a4; @@ -32,11 +31,27 @@ struct tcp_ao_counters { atomic64_t dropped_icmp; }; +enum tcp_ao_algo_id { + TCP_AO_ALGO_HMAC_SHA1 = 1, /* specified by RFC 5926 */ + TCP_AO_ALGO_HMAC_SHA256, /* Linux extension */ + TCP_AO_ALGO_AES_128_CMAC, /* specified by RFC 5926 */ +}; + +/* + * This is the maximum untruncated MAC length, in bytes. Note that the MACs + * actually get truncated to 20 or fewer bytes to fit in the TCP options space. + */ +#define TCP_AO_MAX_MAC_LEN SHA256_DIGEST_SIZE + +#define TCP_AO_MAX_TRAFFIC_KEY_LEN SHA256_DIGEST_SIZE + +struct tcp_ao_mac_ctx; + struct tcp_ao_key { struct hlist_node node; union tcp_ao_addr addr; - u8 key[TCP_AO_MAXKEYLEN] __tcp_ao_key_align; - unsigned int tcp_sigpool_id; + u8 key[TCP_AO_MAXKEYLEN]; + enum tcp_ao_algo_id algo; unsigned int digest_size; int l3index; u8 prefixlen; @@ -168,7 +183,6 @@ struct tcp6_ao_context { __be32 disn; }; -struct tcp_sigpool; /* Established states are fast-path and there always is current_key/rnext_key */ #define TCP_AO_ESTABLISHED (TCPF_ESTABLISHED | TCPF_FIN_WAIT1 | TCPF_FIN_WAIT2 | \ TCPF_CLOSE_WAIT | TCPF_LAST_ACK | TCPF_CLOSING) @@ -176,6 +190,8 @@ struct tcp_sigpool; int tcp_ao_transmit_skb(struct sock *sk, struct sk_buff *skb, struct tcp_ao_key *key, struct tcphdr *th, __u8 *hash_location); +void tcp_ao_mac_update(struct tcp_ao_mac_ctx *mac_ctx, const void *data, + size_t data_len); int tcp_ao_hash_skb(unsigned short int family, char *ao_hash, struct tcp_ao_key *key, const struct sock *sk, const struct sk_buff *skb, @@ -188,8 +204,8 @@ struct tcp_ao_key *tcp_ao_established_key(const struct sock *sk, int tcp_ao_copy_all_matching(const struct sock *sk, struct sock *newsk, struct request_sock *req, struct sk_buff *skb, int family); -int tcp_ao_calc_traffic_key(struct tcp_ao_key *mkt, u8 *key, void *ctx, - unsigned int len, struct tcp_sigpool *hp); +void tcp_ao_calc_traffic_key(const struct tcp_ao_key *mkt, u8 *traffic_key, + const void *input, unsigned int input_len); void tcp_ao_destroy_sock(struct sock *sk, bool twsk); void tcp_ao_time_wait(struct tcp_timewait_sock *tcptw, struct tcp_sock *tp); bool tcp_ao_ignore_icmp(const struct sock *sk, int family, int type, int code); @@ -234,7 +250,7 @@ int tcp_v4_ao_hash_skb(char *ao_hash, struct tcp_ao_key *key, const struct sock *sk, const struct sk_buff *skb, const u8 *tkey, int hash_offset, u32 sne); /* ipv6 specific functions */ -int tcp_v6_ao_hash_pseudoheader(struct tcp_sigpool *hp, +int tcp_v6_ao_hash_pseudoheader(struct tcp_ao_mac_ctx *mac_ctx, const struct in6_addr *daddr, const struct in6_addr *saddr, int nbytes); int tcp_v6_ao_calc_key_skb(struct tcp_ao_key *mkt, u8 *key, diff --git a/net/ipv4/Kconfig b/net/ipv4/Kconfig index 21e5164e30db..77b053b445a0 100644 --- a/net/ipv4/Kconfig +++ b/net/ipv4/Kconfig @@ -746,9 +746,10 @@ config TCP_SIGPOOL config TCP_AO bool "TCP: Authentication Option (RFC5925)" - select CRYPTO + select CRYPTO_LIB_AES_CBC_MACS + select CRYPTO_LIB_SHA1 + select CRYPTO_LIB_SHA256 select CRYPTO_LIB_UTILS - select TCP_SIGPOOL depends on 64BIT # seq-number extension needs WRITE_ONCE(u64) help TCP-AO specifies the use of stronger Message Authentication Codes (MACs), diff --git a/net/ipv4/tcp_ao.c b/net/ipv4/tcp_ao.c index b21bd69b4e82..0d24cbd66c9a 100644 --- a/net/ipv4/tcp_ao.c +++ b/net/ipv4/tcp_ao.c @@ -9,7 +9,9 @@ */ #define pr_fmt(fmt) "TCP: " fmt -#include +#include +#include +#include #include #include #include @@ -21,32 +23,133 @@ DEFINE_STATIC_KEY_DEFERRED_FALSE(tcp_ao_needed, HZ); -int tcp_ao_calc_traffic_key(struct tcp_ao_key *mkt, u8 *key, void *ctx, - unsigned int len, struct tcp_sigpool *hp) -{ - struct scatterlist sg; - int ret; +static const struct tcp_ao_algo { + const char *name; + unsigned int digest_size; +} tcp_ao_algos[] = { + [TCP_AO_ALGO_HMAC_SHA1] = { + .name = "hmac(sha1)", + .digest_size = SHA1_DIGEST_SIZE, + }, + [TCP_AO_ALGO_HMAC_SHA256] = { + .name = "hmac(sha256)", + .digest_size = SHA256_DIGEST_SIZE, + }, + [TCP_AO_ALGO_AES_128_CMAC] = { + .name = "cmac(aes128)", + .digest_size = AES_BLOCK_SIZE, /* same as AES_KEYSIZE_128 */ + }, +}; + +struct tcp_ao_mac_ctx { + enum tcp_ao_algo_id algo; + union { + struct hmac_sha1_ctx hmac_sha1; + struct hmac_sha256_ctx hmac_sha256; + struct { + struct aes_cmac_key key; + struct aes_cmac_ctx ctx; + } aes_cmac; + }; +}; + +static const struct tcp_ao_algo *tcp_ao_find_algo(const char *name) +{ + for (size_t i = 0; i < ARRAY_SIZE(tcp_ao_algos); i++) { + const struct tcp_ao_algo *algo = &tcp_ao_algos[i]; + + if (!algo->name) + continue; + if (WARN_ON_ONCE(algo->digest_size > TCP_AO_MAX_MAC_LEN || + algo->digest_size > + TCP_AO_MAX_TRAFFIC_KEY_LEN)) + continue; + if (strcmp(name, algo->name) == 0) + return algo; + } + return NULL; +} - if (crypto_ahash_setkey(crypto_ahash_reqtfm(hp->req), - mkt->key, mkt->keylen)) - goto clear_hash; +static void tcp_ao_mac_init(struct tcp_ao_mac_ctx *mac_ctx, + enum tcp_ao_algo_id algo, const u8 *traffic_key) +{ + mac_ctx->algo = algo; + switch (mac_ctx->algo) { + case TCP_AO_ALGO_HMAC_SHA1: + hmac_sha1_init_usingrawkey(&mac_ctx->hmac_sha1, traffic_key, + SHA1_DIGEST_SIZE); + return; + case TCP_AO_ALGO_HMAC_SHA256: + hmac_sha256_init_usingrawkey(&mac_ctx->hmac_sha256, traffic_key, + SHA256_DIGEST_SIZE); + return; + case TCP_AO_ALGO_AES_128_CMAC: + aes_cmac_preparekey(&mac_ctx->aes_cmac.key, traffic_key, + AES_KEYSIZE_128); + aes_cmac_init(&mac_ctx->aes_cmac.ctx, &mac_ctx->aes_cmac.key); + return; + default: + WARN_ON_ONCE(1); /* algo was validated earlier. */ + } +} - ret = crypto_ahash_init(hp->req); - if (ret) - goto clear_hash; +void tcp_ao_mac_update(struct tcp_ao_mac_ctx *mac_ctx, const void *data, + size_t data_len) +{ + switch (mac_ctx->algo) { + case TCP_AO_ALGO_HMAC_SHA1: + hmac_sha1_update(&mac_ctx->hmac_sha1, data, data_len); + return; + case TCP_AO_ALGO_HMAC_SHA256: + hmac_sha256_update(&mac_ctx->hmac_sha256, data, data_len); + return; + case TCP_AO_ALGO_AES_128_CMAC: + aes_cmac_update(&mac_ctx->aes_cmac.ctx, data, data_len); + return; + default: + WARN_ON_ONCE(1); /* algo was validated earlier. */ + } +} - sg_init_one(&sg, ctx, len); - ahash_request_set_crypt(hp->req, &sg, key, len); - crypto_ahash_update(hp->req); +static void tcp_ao_mac_final(struct tcp_ao_mac_ctx *mac_ctx, u8 *out) +{ + switch (mac_ctx->algo) { + case TCP_AO_ALGO_HMAC_SHA1: + hmac_sha1_final(&mac_ctx->hmac_sha1, out); + return; + case TCP_AO_ALGO_HMAC_SHA256: + hmac_sha256_final(&mac_ctx->hmac_sha256, out); + return; + case TCP_AO_ALGO_AES_128_CMAC: + aes_cmac_final(&mac_ctx->aes_cmac.ctx, out); + return; + default: + WARN_ON_ONCE(1); /* algo was validated earlier. */ + } +} - ret = crypto_ahash_final(hp->req); - if (ret) - goto clear_hash; +void tcp_ao_calc_traffic_key(const struct tcp_ao_key *mkt, u8 *traffic_key, + const void *input, unsigned int input_len) +{ + switch (mkt->algo) { + case TCP_AO_ALGO_HMAC_SHA1: + hmac_sha1_usingrawkey(mkt->key, mkt->keylen, input, input_len, + traffic_key); + return; + case TCP_AO_ALGO_HMAC_SHA256: + hmac_sha256_usingrawkey(mkt->key, mkt->keylen, input, input_len, + traffic_key); + return; + case TCP_AO_ALGO_AES_128_CMAC: { + struct aes_cmac_key k; - return 0; -clear_hash: - memset(key, 0, tcp_ao_digest_size(mkt)); - return 1; + aes_cmac_preparekey(&k, mkt->key, AES_KEYSIZE_128); + aes_cmac(&k, input, input_len, traffic_key); + return; + } + default: + WARN_ON_ONCE(1); /* algo was validated earlier. */ + } } bool tcp_ao_ignore_icmp(const struct sock *sk, int family, int type, int code) @@ -254,7 +357,6 @@ static struct tcp_ao_key *tcp_ao_copy_key(struct sock *sk, *new_key = *key; INIT_HLIST_NODE(&new_key->node); - tcp_sigpool_get(new_key->tcp_sigpool_id); atomic64_set(&new_key->pkt_good, 0); atomic64_set(&new_key->pkt_bad, 0); @@ -265,7 +367,6 @@ static void tcp_ao_key_free_rcu(struct rcu_head *head) { struct tcp_ao_key *key = container_of(head, struct tcp_ao_key, rcu); - tcp_sigpool_release(key->tcp_sigpool_id); kfree_sensitive(key); } @@ -276,7 +377,6 @@ static void tcp_ao_info_free(struct tcp_ao_info *ao) hlist_for_each_entry_safe(key, n, &ao->head, node) { hlist_del(&key->node); - tcp_sigpool_release(key->tcp_sigpool_id); kfree_sensitive(key); } kfree(ao); @@ -346,29 +446,22 @@ static int tcp_v4_ao_calc_key(struct tcp_ao_key *mkt, u8 *key, u8 label[6]; struct tcp4_ao_context ctx; __be16 outlen; - } __packed * tmp; - struct tcp_sigpool hp; - int err; - - err = tcp_sigpool_start(mkt->tcp_sigpool_id, &hp); - if (err) - return err; - - tmp = hp.scratch; - tmp->counter = 1; - memcpy(tmp->label, "TCP-AO", 6); - tmp->ctx.saddr = saddr; - tmp->ctx.daddr = daddr; - tmp->ctx.sport = sport; - tmp->ctx.dport = dport; - tmp->ctx.sisn = sisn; - tmp->ctx.disn = disn; - tmp->outlen = htons(tcp_ao_digest_size(mkt) * 8); /* in bits */ - - err = tcp_ao_calc_traffic_key(mkt, key, tmp, sizeof(*tmp), &hp); - tcp_sigpool_end(&hp); - - return err; + } __packed input = { + .counter = 1, + .label = "TCP-AO", + .ctx = { + .saddr = saddr, + .daddr = daddr, + .sport = sport, + .dport = dport, + .sisn = sisn, + .disn = disn, + }, + .outlen = htons(tcp_ao_digest_size(mkt) * 8), /* in bits */ + }; + + tcp_ao_calc_traffic_key(mkt, key, &input, sizeof(input)); + return 0; } int tcp_v4_ao_calc_key_sk(struct tcp_ao_key *mkt, u8 *key, @@ -435,40 +528,37 @@ static int tcp_ao_calc_key_skb(struct tcp_ao_key *mkt, u8 *key, return -EAFNOSUPPORT; } -static int tcp_v4_ao_hash_pseudoheader(struct tcp_sigpool *hp, +static int tcp_v4_ao_hash_pseudoheader(struct tcp_ao_mac_ctx *mac_ctx, __be32 daddr, __be32 saddr, int nbytes) { - struct tcp4_pseudohdr *bp; - struct scatterlist sg; + struct tcp4_pseudohdr phdr = { + .saddr = saddr, + .daddr = daddr, + .pad = 0, + .protocol = IPPROTO_TCP, + .len = cpu_to_be16(nbytes), + }; - bp = hp->scratch; - bp->saddr = saddr; - bp->daddr = daddr; - bp->pad = 0; - bp->protocol = IPPROTO_TCP; - bp->len = cpu_to_be16(nbytes); - - sg_init_one(&sg, bp, sizeof(*bp)); - ahash_request_set_crypt(hp->req, &sg, NULL, sizeof(*bp)); - return crypto_ahash_update(hp->req); + tcp_ao_mac_update(mac_ctx, &phdr, sizeof(phdr)); + return 0; } static int tcp_ao_hash_pseudoheader(unsigned short int family, const struct sock *sk, const struct sk_buff *skb, - struct tcp_sigpool *hp, int nbytes) + struct tcp_ao_mac_ctx *mac_ctx, int nbytes) { const struct tcphdr *th = tcp_hdr(skb); /* TODO: Can we rely on checksum being zero to mean outbound pkt? */ if (!th->check) { if (family == AF_INET) - return tcp_v4_ao_hash_pseudoheader(hp, sk->sk_daddr, + return tcp_v4_ao_hash_pseudoheader(mac_ctx, sk->sk_daddr, sk->sk_rcv_saddr, skb->len); #if IS_ENABLED(CONFIG_IPV6) else if (family == AF_INET6) - return tcp_v6_ao_hash_pseudoheader(hp, &sk->sk_v6_daddr, + return tcp_v6_ao_hash_pseudoheader(mac_ctx, &sk->sk_v6_daddr, &sk->sk_v6_rcv_saddr, skb->len); #endif else @@ -478,13 +568,13 @@ static int tcp_ao_hash_pseudoheader(unsigned short int family, if (family == AF_INET) { const struct iphdr *iph = ip_hdr(skb); - return tcp_v4_ao_hash_pseudoheader(hp, iph->daddr, + return tcp_v4_ao_hash_pseudoheader(mac_ctx, iph->daddr, iph->saddr, skb->len); #if IS_ENABLED(CONFIG_IPV6) } else if (family == AF_INET6) { const struct ipv6hdr *iph = ipv6_hdr(skb); - return tcp_v6_ao_hash_pseudoheader(hp, &iph->daddr, + return tcp_v6_ao_hash_pseudoheader(mac_ctx, &iph->daddr, &iph->saddr, skb->len); #endif } @@ -506,31 +596,20 @@ u32 tcp_ao_compute_sne(u32 next_sne, u32 next_seq, u32 seq) return sne; } -/* tcp_ao_hash_sne(struct tcp_sigpool *hp) - * @hp - used for hashing - * @sne - sne value - */ -static int tcp_ao_hash_sne(struct tcp_sigpool *hp, u32 sne) +static void tcp_ao_hash_sne(struct tcp_ao_mac_ctx *mac_ctx, u32 sne) { - struct scatterlist sg; - __be32 *bp; - - bp = (__be32 *)hp->scratch; - *bp = htonl(sne); + __be32 sne_be32 = htonl(sne); - sg_init_one(&sg, bp, sizeof(*bp)); - ahash_request_set_crypt(hp->req, &sg, NULL, sizeof(*bp)); - return crypto_ahash_update(hp->req); + tcp_ao_mac_update(mac_ctx, &sne_be32, sizeof(sne_be32)); } -static int tcp_ao_hash_header(struct tcp_sigpool *hp, - const struct tcphdr *th, - bool exclude_options, u8 *hash, - int hash_offset, int hash_len) +static void tcp_ao_hash_header(struct tcp_ao_mac_ctx *mac_ctx, + const struct tcphdr *th, bool exclude_options, + u8 *hash, int hash_offset, int hash_len) { - struct scatterlist sg; - u8 *hdr = hp->scratch; - int err, len; + /* Full TCP header (th->doff << 2) should fit into scratch area. */ + u8 hdr[60]; + int len; /* We are not allowed to change tcphdr, make a local copy */ if (exclude_options) { @@ -550,11 +629,7 @@ static int tcp_ao_hash_header(struct tcp_sigpool *hp, memset(hdr + hash_offset, 0, hash_len); } - sg_init_one(&sg, hdr, len); - ahash_request_set_crypt(hp->req, &sg, NULL, len); - err = crypto_ahash_update(hp->req); - WARN_ON_ONCE(err != 0); - return err; + tcp_ao_mac_update(mac_ctx, hdr, len); } int tcp_ao_hash_hdr(unsigned short int family, char *ao_hash, @@ -563,109 +638,92 @@ int tcp_ao_hash_hdr(unsigned short int family, char *ao_hash, const union tcp_ao_addr *saddr, const struct tcphdr *th, u32 sne) { - int tkey_len = tcp_ao_digest_size(key); int hash_offset = ao_hash - (char *)th; - struct tcp_sigpool hp; - void *hash_buf = NULL; - - hash_buf = kmalloc(tkey_len, GFP_ATOMIC); - if (!hash_buf) - goto clear_hash_noput; - - if (tcp_sigpool_start(key->tcp_sigpool_id, &hp)) - goto clear_hash_noput; - - if (crypto_ahash_setkey(crypto_ahash_reqtfm(hp.req), tkey, tkey_len)) - goto clear_hash; - - if (crypto_ahash_init(hp.req)) - goto clear_hash; + struct tcp_ao_mac_ctx mac_ctx; + u8 hash_buf[TCP_AO_MAX_MAC_LEN]; - if (tcp_ao_hash_sne(&hp, sne)) - goto clear_hash; + tcp_ao_mac_init(&mac_ctx, key->algo, tkey); + tcp_ao_hash_sne(&mac_ctx, sne); if (family == AF_INET) { - if (tcp_v4_ao_hash_pseudoheader(&hp, daddr->a4.s_addr, - saddr->a4.s_addr, th->doff * 4)) - goto clear_hash; + tcp_v4_ao_hash_pseudoheader(&mac_ctx, daddr->a4.s_addr, + saddr->a4.s_addr, th->doff * 4); #if IS_ENABLED(CONFIG_IPV6) } else if (family == AF_INET6) { - if (tcp_v6_ao_hash_pseudoheader(&hp, &daddr->a6, - &saddr->a6, th->doff * 4)) - goto clear_hash; + tcp_v6_ao_hash_pseudoheader(&mac_ctx, &daddr->a6, + &saddr->a6, th->doff * 4); #endif } else { WARN_ON_ONCE(1); goto clear_hash; } - if (tcp_ao_hash_header(&hp, th, - !!(key->keyflags & TCP_AO_KEYF_EXCLUDE_OPT), - ao_hash, hash_offset, tcp_ao_maclen(key))) - goto clear_hash; - ahash_request_set_crypt(hp.req, NULL, hash_buf, 0); - if (crypto_ahash_final(hp.req)) - goto clear_hash; + tcp_ao_hash_header(&mac_ctx, th, + !!(key->keyflags & TCP_AO_KEYF_EXCLUDE_OPT), + ao_hash, hash_offset, tcp_ao_maclen(key)); + tcp_ao_mac_final(&mac_ctx, hash_buf); memcpy(ao_hash, hash_buf, tcp_ao_maclen(key)); - tcp_sigpool_end(&hp); - kfree(hash_buf); return 0; clear_hash: - tcp_sigpool_end(&hp); -clear_hash_noput: memset(ao_hash, 0, tcp_ao_maclen(key)); - kfree(hash_buf); return 1; } +static void tcp_ao_hash_skb_data(struct tcp_ao_mac_ctx *mac_ctx, + const struct sk_buff *skb, + unsigned int header_len) +{ + const unsigned int head_data_len = skb_headlen(skb) > header_len ? + skb_headlen(skb) - header_len : 0; + const struct skb_shared_info *shi = skb_shinfo(skb); + struct sk_buff *frag_iter; + unsigned int i; + + tcp_ao_mac_update(mac_ctx, (const u8 *)tcp_hdr(skb) + header_len, + head_data_len); + + for (i = 0; i < shi->nr_frags; ++i) { + const skb_frag_t *f = &shi->frags[i]; + u32 p_off, p_len, copied; + const void *vaddr; + struct page *p; + + skb_frag_foreach_page(f, skb_frag_off(f), skb_frag_size(f), + p, p_off, p_len, copied) { + vaddr = kmap_local_page(p); + tcp_ao_mac_update(mac_ctx, vaddr + p_off, p_len); + kunmap_local(vaddr); + } + } + + skb_walk_frags(skb, frag_iter) + tcp_ao_hash_skb_data(mac_ctx, frag_iter, 0); +} + int tcp_ao_hash_skb(unsigned short int family, char *ao_hash, struct tcp_ao_key *key, const struct sock *sk, const struct sk_buff *skb, const u8 *tkey, int hash_offset, u32 sne) { const struct tcphdr *th = tcp_hdr(skb); - int tkey_len = tcp_ao_digest_size(key); - struct tcp_sigpool hp; - void *hash_buf = NULL; - - hash_buf = kmalloc(tkey_len, GFP_ATOMIC); - if (!hash_buf) - goto clear_hash_noput; - - if (tcp_sigpool_start(key->tcp_sigpool_id, &hp)) - goto clear_hash_noput; - - if (crypto_ahash_setkey(crypto_ahash_reqtfm(hp.req), tkey, tkey_len)) - goto clear_hash; - - /* For now use sha1 by default. Depends on alg in tcp_ao_key */ - if (crypto_ahash_init(hp.req)) - goto clear_hash; + struct tcp_ao_mac_ctx mac_ctx; + u8 hash_buf[TCP_AO_MAX_MAC_LEN]; - if (tcp_ao_hash_sne(&hp, sne)) - goto clear_hash; - if (tcp_ao_hash_pseudoheader(family, sk, skb, &hp, skb->len)) - goto clear_hash; - if (tcp_ao_hash_header(&hp, th, - !!(key->keyflags & TCP_AO_KEYF_EXCLUDE_OPT), - ao_hash, hash_offset, tcp_ao_maclen(key))) - goto clear_hash; - if (tcp_sigpool_hash_skb_data(&hp, skb, th->doff << 2)) - goto clear_hash; - ahash_request_set_crypt(hp.req, NULL, hash_buf, 0); - if (crypto_ahash_final(hp.req)) + tcp_ao_mac_init(&mac_ctx, key->algo, tkey); + tcp_ao_hash_sne(&mac_ctx, sne); + if (tcp_ao_hash_pseudoheader(family, sk, skb, &mac_ctx, skb->len)) goto clear_hash; + tcp_ao_hash_header(&mac_ctx, th, + !!(key->keyflags & TCP_AO_KEYF_EXCLUDE_OPT), + ao_hash, hash_offset, tcp_ao_maclen(key)); + tcp_ao_hash_skb_data(&mac_ctx, skb, th->doff << 2); + tcp_ao_mac_final(&mac_ctx, hash_buf); memcpy(ao_hash, hash_buf, tcp_ao_maclen(key)); - tcp_sigpool_end(&hp); - kfree(hash_buf); return 0; clear_hash: - tcp_sigpool_end(&hp); -clear_hash_noput: memset(ao_hash, 0, tcp_ao_maclen(key)); - kfree(hash_buf); return 1; } @@ -1280,7 +1338,6 @@ int tcp_ao_copy_all_matching(const struct sock *sk, struct sock *newsk, free_and_exit: hlist_for_each_entry_safe(key, key_head, &new_ao->head, node) { hlist_del(&key->node); - tcp_sigpool_release(key->tcp_sigpool_id); atomic_sub(tcp_ao_sizeof_key(key), &newsk->sk_omem_alloc); kfree_sensitive(key); } @@ -1336,23 +1393,10 @@ static int tcp_ao_verify_ipv4(struct sock *sk, struct tcp_ao_add *cmd, return 0; } -static int tcp_ao_parse_crypto(struct tcp_ao_add *cmd, struct tcp_ao_key *key) +static int tcp_ao_parse_crypto(const struct tcp_ao_add *cmd, + struct tcp_ao_key *key) { unsigned int syn_tcp_option_space; - bool is_kdf_aes_128_cmac = false; - struct crypto_ahash *tfm; - struct tcp_sigpool hp; - void *tmp_key = NULL; - int err; - - /* RFC5926, 3.1.1.2. KDF_AES_128_CMAC */ - if (!strcmp("cmac(aes128)", cmd->alg_name)) { - strscpy(cmd->alg_name, "cmac(aes)", sizeof(cmd->alg_name)); - is_kdf_aes_128_cmac = (cmd->keylen != 16); - tmp_key = kmalloc(cmd->keylen, GFP_KERNEL); - if (!tmp_key) - return -ENOMEM; - } key->maclen = cmd->maclen ?: 12; /* 12 is the default in RFC5925 */ @@ -1388,64 +1432,27 @@ static int tcp_ao_parse_crypto(struct tcp_ao_add *cmd, struct tcp_ao_key *key) syn_tcp_option_space -= TCPOLEN_MSS_ALIGNED; syn_tcp_option_space -= TCPOLEN_TSTAMP_ALIGNED; syn_tcp_option_space -= TCPOLEN_WSCALE_ALIGNED; - if (tcp_ao_len_aligned(key) > syn_tcp_option_space) { - err = -EMSGSIZE; - goto err_kfree; - } - - key->keylen = cmd->keylen; - memcpy(key->key, cmd->key, cmd->keylen); - - err = tcp_sigpool_start(key->tcp_sigpool_id, &hp); - if (err) - goto err_kfree; - - tfm = crypto_ahash_reqtfm(hp.req); - if (is_kdf_aes_128_cmac) { - void *scratch = hp.scratch; - struct scatterlist sg; - - memcpy(tmp_key, cmd->key, cmd->keylen); - sg_init_one(&sg, tmp_key, cmd->keylen); - - /* Using zero-key of 16 bytes as described in RFC5926 */ - memset(scratch, 0, 16); - err = crypto_ahash_setkey(tfm, scratch, 16); - if (err) - goto err_pool_end; - - err = crypto_ahash_init(hp.req); - if (err) - goto err_pool_end; - - ahash_request_set_crypt(hp.req, &sg, key->key, cmd->keylen); - err = crypto_ahash_update(hp.req); - if (err) - goto err_pool_end; - - err |= crypto_ahash_final(hp.req); - if (err) - goto err_pool_end; - key->keylen = 16; + if (tcp_ao_len_aligned(key) > syn_tcp_option_space) + return -EMSGSIZE; + + if (key->algo == TCP_AO_ALGO_AES_128_CMAC && + cmd->keylen != AES_KEYSIZE_128) { + /* RFC5926, 3.1.1.2. KDF_AES_128_CMAC */ + static const u8 zeroes[AES_KEYSIZE_128]; + struct aes_cmac_key extractor; + + aes_cmac_preparekey(&extractor, zeroes, AES_KEYSIZE_128); + aes_cmac(&extractor, cmd->key, cmd->keylen, key->key); + key->keylen = AES_KEYSIZE_128; + } else { + memcpy(key->key, cmd->key, cmd->keylen); + key->keylen = cmd->keylen; } - err = crypto_ahash_setkey(tfm, key->key, key->keylen); - if (err) - goto err_pool_end; - - tcp_sigpool_end(&hp); - kfree_sensitive(tmp_key); - if (tcp_ao_maclen(key) > key->digest_size) return -EINVAL; return 0; - -err_pool_end: - tcp_sigpool_end(&hp); -err_kfree: - kfree_sensitive(tmp_key); - return err; } #if IS_ENABLED(CONFIG_IPV6) @@ -1549,54 +1556,33 @@ static struct tcp_ao_info *getsockopt_ao_info(struct sock *sk) static struct tcp_ao_key *tcp_ao_key_alloc(struct sock *sk, struct tcp_ao_add *cmd) { - const char *algo = cmd->alg_name; - unsigned int digest_size; - struct crypto_ahash *tfm; + const struct tcp_ao_algo *algo; struct tcp_ao_key *key; - struct tcp_sigpool hp; - int err, pool_id; size_t size; /* Force null-termination of alg_name */ cmd->alg_name[ARRAY_SIZE(cmd->alg_name) - 1] = '\0'; - /* RFC5926, 3.1.1.2. KDF_AES_128_CMAC */ - if (!strcmp("cmac(aes128)", algo)) - algo = "cmac(aes)"; - else if (strcmp("hmac(sha1)", algo) && - strcmp("hmac(sha256)", algo) && - (strcmp("cmac(aes)", algo) || cmd->keylen != 16)) - return ERR_PTR(-ENOENT); - - /* Full TCP header (th->doff << 2) should fit into scratch area, - * see tcp_ao_hash_header(). + /* + * For backwards compatibility, accept "cmac(aes)" as an alias for + * "cmac(aes128)", provided that the key length is exactly 128 bits. */ - pool_id = tcp_sigpool_alloc_ahash(algo, 60); - if (pool_id < 0) - return ERR_PTR(pool_id); - - err = tcp_sigpool_start(pool_id, &hp); - if (err) - goto err_free_pool; + if (strcmp(cmd->alg_name, "cmac(aes)") == 0 && + cmd->keylen == AES_KEYSIZE_128) + strscpy(cmd->alg_name, "cmac(aes128)"); - tfm = crypto_ahash_reqtfm(hp.req); - digest_size = crypto_ahash_digestsize(tfm); - tcp_sigpool_end(&hp); + algo = tcp_ao_find_algo(cmd->alg_name); + if (!algo) + return ERR_PTR(-ENOENT); - size = sizeof(struct tcp_ao_key) + (digest_size << 1); + size = sizeof(struct tcp_ao_key) + (algo->digest_size << 1); key = sock_kmalloc(sk, size, GFP_KERNEL); - if (!key) { - err = -ENOMEM; - goto err_free_pool; - } + if (!key) + return ERR_PTR(-ENOMEM); - key->tcp_sigpool_id = pool_id; - key->digest_size = digest_size; + key->algo = algo - tcp_ao_algos; + key->digest_size = algo->digest_size; return key; - -err_free_pool: - tcp_sigpool_release(pool_id); - return ERR_PTR(err); } static int tcp_ao_add_cmd(struct sock *sk, unsigned short int family, @@ -1757,7 +1743,6 @@ static int tcp_ao_add_cmd(struct sock *sk, unsigned short int family, err_free_sock: atomic_sub(tcp_ao_sizeof_key(key), &sk->sk_omem_alloc); - tcp_sigpool_release(key->tcp_sigpool_id); kfree_sensitive(key); err_free_ao: if (first) @@ -2288,7 +2273,11 @@ match: opt_out.pkt_good = atomic64_read(&key->pkt_good); opt_out.pkt_bad = atomic64_read(&key->pkt_bad); memcpy(&opt_out.key, key->key, key->keylen); - tcp_sigpool_algo(key->tcp_sigpool_id, opt_out.alg_name, 64); + if (key->algo == TCP_AO_ALGO_AES_128_CMAC) + /* This is needed for backwards compatibility. */ + strscpy(opt_out.alg_name, "cmac(aes)"); + else + strscpy(opt_out.alg_name, tcp_ao_algos[key->algo].name); /* Copy key to user */ if (copy_to_sockptr_offset(optval, out_offset, diff --git a/net/ipv6/tcp_ao.c b/net/ipv6/tcp_ao.c index 3c09ac26206e..2dcfe9dda7f4 100644 --- a/net/ipv6/tcp_ao.c +++ b/net/ipv6/tcp_ao.c @@ -7,7 +7,6 @@ * Francesco Ruggeri * Salam Noureddine */ -#include #include #include @@ -24,29 +23,22 @@ static int tcp_v6_ao_calc_key(struct tcp_ao_key *mkt, u8 *key, u8 label[6]; struct tcp6_ao_context ctx; __be16 outlen; - } __packed * tmp; - struct tcp_sigpool hp; - int err; - - err = tcp_sigpool_start(mkt->tcp_sigpool_id, &hp); - if (err) - return err; - - tmp = hp.scratch; - tmp->counter = 1; - memcpy(tmp->label, "TCP-AO", 6); - tmp->ctx.saddr = *saddr; - tmp->ctx.daddr = *daddr; - tmp->ctx.sport = sport; - tmp->ctx.dport = dport; - tmp->ctx.sisn = sisn; - tmp->ctx.disn = disn; - tmp->outlen = htons(tcp_ao_digest_size(mkt) * 8); /* in bits */ - - err = tcp_ao_calc_traffic_key(mkt, key, tmp, sizeof(*tmp), &hp); - tcp_sigpool_end(&hp); - - return err; + } __packed input = { + .counter = 1, + .label = "TCP-AO", + .ctx = { + .saddr = *saddr, + .daddr = *daddr, + .sport = sport, + .dport = dport, + .sisn = sisn, + .disn = disn, + }, + .outlen = htons(tcp_ao_digest_size(mkt) * 8), /* in bits */ + }; + + tcp_ao_calc_traffic_key(mkt, key, &input, sizeof(input)); + return 0; } int tcp_v6_ao_calc_key_skb(struct tcp_ao_key *mkt, u8 *key, @@ -112,23 +104,20 @@ struct tcp_ao_key *tcp_v6_ao_lookup_rsk(const struct sock *sk, AF_INET6, sndid, rcvid); } -int tcp_v6_ao_hash_pseudoheader(struct tcp_sigpool *hp, +int tcp_v6_ao_hash_pseudoheader(struct tcp_ao_mac_ctx *mac_ctx, const struct in6_addr *daddr, const struct in6_addr *saddr, int nbytes) { - struct tcp6_pseudohdr *bp; - struct scatterlist sg; - - bp = hp->scratch; /* 1. TCP pseudo-header (RFC2460) */ - bp->saddr = *saddr; - bp->daddr = *daddr; - bp->len = cpu_to_be32(nbytes); - bp->protocol = cpu_to_be32(IPPROTO_TCP); - - sg_init_one(&sg, bp, sizeof(*bp)); - ahash_request_set_crypt(hp->req, &sg, NULL, sizeof(*bp)); - return crypto_ahash_update(hp->req); + struct tcp6_pseudohdr phdr = { + .saddr = *saddr, + .daddr = *daddr, + .len = cpu_to_be32(nbytes), + .protocol = cpu_to_be32(IPPROTO_TCP), + }; + + tcp_ao_mac_update(mac_ctx, &phdr, sizeof(phdr)); + return 0; } int tcp_v6_ao_hash_skb(char *ao_hash, struct tcp_ao_key *key, diff --git a/tools/testing/selftests/net/tcp_ao/config b/tools/testing/selftests/net/tcp_ao/config index 47228a7d0b90..1b120bfd89c4 100644 --- a/tools/testing/selftests/net/tcp_ao/config +++ b/tools/testing/selftests/net/tcp_ao/config @@ -1,6 +1,3 @@ -CONFIG_CRYPTO_CMAC=y -CONFIG_CRYPTO_HMAC=y -CONFIG_CRYPTO_SHA1=y CONFIG_IPV6=y CONFIG_IPV6_MULTIPLE_TABLES=y CONFIG_NET_L3_MASTER_DEV=y -- cgit v1.2.3 From 8e4f61e431634730af2244312c783f746575905d Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Mon, 27 Apr 2026 10:27:26 -0700 Subject: net/tcp-ao: Return void from functions that can no longer fail Since tcp-ao now uses the crypto library API instead of crypto_ahash, and MACs and keys now have a statically-known maximum size, many tcp-ao functions can no longer fail. Propagate this change up into the return types of various functions. Reviewed-by: Ard Biesheuvel Signed-off-by: Eric Biggers Link: https://patch.msgid.link/20260427172727.9310-5-ebiggers@kernel.org Signed-off-by: Paolo Abeni --- include/net/tcp.h | 8 ++-- include/net/tcp_ao.h | 44 ++++++++--------- net/ipv4/tcp_ao.c | 128 ++++++++++++++++++++++++++------------------------ net/ipv4/tcp_output.c | 10 +--- net/ipv6/tcp_ao.c | 65 ++++++++++++------------- 5 files changed, 123 insertions(+), 132 deletions(-) (limited to 'include') diff --git a/include/net/tcp.h b/include/net/tcp.h index fb2bc9edc7de..35fc6b35b216 100644 --- a/include/net/tcp.h +++ b/include/net/tcp.h @@ -2520,9 +2520,9 @@ struct tcp_sock_af_ops { struct tcp_ao_key *(*ao_lookup)(const struct sock *sk, struct sock *addr_sk, int sndid, int rcvid); - int (*ao_calc_key_sk)(struct tcp_ao_key *mkt, u8 *key, - const struct sock *sk, - __be32 sisn, __be32 disn, bool send); + void (*ao_calc_key_sk)(struct tcp_ao_key *mkt, u8 *key, + const struct sock *sk, + __be32 sisn, __be32 disn, bool send); int (*calc_ao_hash)(char *location, struct tcp_ao_key *ao, const struct sock *sk, const struct sk_buff *skb, const u8 *tkey, int hash_offset, u32 sne); @@ -2543,7 +2543,7 @@ struct tcp_request_sock_ops { struct tcp_ao_key *(*ao_lookup)(const struct sock *sk, struct request_sock *req, int sndid, int rcvid); - int (*ao_calc_key)(struct tcp_ao_key *mkt, u8 *key, struct request_sock *sk); + void (*ao_calc_key)(struct tcp_ao_key *mkt, u8 *key, struct request_sock *sk); int (*ao_synack_hash)(char *ao_hash, struct tcp_ao_key *mkt, struct request_sock *req, const struct sk_buff *skb, int hash_offset, u32 sne); diff --git a/include/net/tcp_ao.h b/include/net/tcp_ao.h index 20997aef3b0d..29fd7b735afa 100644 --- a/include/net/tcp_ao.h +++ b/include/net/tcp_ao.h @@ -187,9 +187,9 @@ struct tcp6_ao_context { #define TCP_AO_ESTABLISHED (TCPF_ESTABLISHED | TCPF_FIN_WAIT1 | TCPF_FIN_WAIT2 | \ TCPF_CLOSE_WAIT | TCPF_LAST_ACK | TCPF_CLOSING) -int tcp_ao_transmit_skb(struct sock *sk, struct sk_buff *skb, - struct tcp_ao_key *key, struct tcphdr *th, - __u8 *hash_location); +void tcp_ao_transmit_skb(struct sock *sk, struct sk_buff *skb, + struct tcp_ao_key *key, struct tcphdr *th, + __u8 *hash_location); void tcp_ao_mac_update(struct tcp_ao_mac_ctx *mac_ctx, const void *data, size_t data_len); int tcp_ao_hash_skb(unsigned short int family, @@ -238,11 +238,11 @@ struct tcp_ao_key *tcp_v4_ao_lookup(const struct sock *sk, struct sock *addr_sk, int tcp_v4_ao_synack_hash(char *ao_hash, struct tcp_ao_key *mkt, struct request_sock *req, const struct sk_buff *skb, int hash_offset, u32 sne); -int tcp_v4_ao_calc_key_sk(struct tcp_ao_key *mkt, u8 *key, - const struct sock *sk, - __be32 sisn, __be32 disn, bool send); -int tcp_v4_ao_calc_key_rsk(struct tcp_ao_key *mkt, u8 *key, - struct request_sock *req); +void tcp_v4_ao_calc_key_sk(struct tcp_ao_key *mkt, u8 *key, + const struct sock *sk, + __be32 sisn, __be32 disn, bool send); +void tcp_v4_ao_calc_key_rsk(struct tcp_ao_key *mkt, u8 *key, + struct request_sock *req); struct tcp_ao_key *tcp_v4_ao_lookup_rsk(const struct sock *sk, struct request_sock *req, int sndid, int rcvid); @@ -250,16 +250,17 @@ int tcp_v4_ao_hash_skb(char *ao_hash, struct tcp_ao_key *key, const struct sock *sk, const struct sk_buff *skb, const u8 *tkey, int hash_offset, u32 sne); /* ipv6 specific functions */ -int tcp_v6_ao_hash_pseudoheader(struct tcp_ao_mac_ctx *mac_ctx, - const struct in6_addr *daddr, - const struct in6_addr *saddr, int nbytes); -int tcp_v6_ao_calc_key_skb(struct tcp_ao_key *mkt, u8 *key, - const struct sk_buff *skb, __be32 sisn, __be32 disn); -int tcp_v6_ao_calc_key_sk(struct tcp_ao_key *mkt, u8 *key, - const struct sock *sk, __be32 sisn, - __be32 disn, bool send); -int tcp_v6_ao_calc_key_rsk(struct tcp_ao_key *mkt, u8 *key, - struct request_sock *req); +void tcp_v6_ao_hash_pseudoheader(struct tcp_ao_mac_ctx *mac_ctx, + const struct in6_addr *daddr, + const struct in6_addr *saddr, int nbytes); +void tcp_v6_ao_calc_key_skb(struct tcp_ao_key *mkt, u8 *key, + const struct sk_buff *skb, __be32 sisn, + __be32 disn); +void tcp_v6_ao_calc_key_sk(struct tcp_ao_key *mkt, u8 *key, + const struct sock *sk, __be32 sisn, + __be32 disn, bool send); +void tcp_v6_ao_calc_key_rsk(struct tcp_ao_key *mkt, u8 *key, + struct request_sock *req); struct tcp_ao_key *tcp_v6_ao_lookup(const struct sock *sk, struct sock *addr_sk, int sndid, int rcvid); struct tcp_ao_key *tcp_v6_ao_lookup_rsk(const struct sock *sk, @@ -279,11 +280,10 @@ void tcp_ao_syncookie(struct sock *sk, const struct sk_buff *skb, struct request_sock *req, unsigned short int family); #else /* CONFIG_TCP_AO */ -static inline int tcp_ao_transmit_skb(struct sock *sk, struct sk_buff *skb, - struct tcp_ao_key *key, struct tcphdr *th, - __u8 *hash_location) +static inline void tcp_ao_transmit_skb(struct sock *sk, struct sk_buff *skb, + struct tcp_ao_key *key, + struct tcphdr *th, __u8 *hash_location) { - return 0; } static inline void tcp_ao_syncookie(struct sock *sk, const struct sk_buff *skb, diff --git a/net/ipv4/tcp_ao.c b/net/ipv4/tcp_ao.c index 69f1d6d26562..36a64c1cd8c9 100644 --- a/net/ipv4/tcp_ao.c +++ b/net/ipv4/tcp_ao.c @@ -435,10 +435,10 @@ void tcp_ao_time_wait(struct tcp_timewait_sock *tcptw, struct tcp_sock *tp) } /* 4 tuple and ISNs are expected in NBO */ -static int tcp_v4_ao_calc_key(struct tcp_ao_key *mkt, u8 *key, - __be32 saddr, __be32 daddr, - __be16 sport, __be16 dport, - __be32 sisn, __be32 disn) +static void tcp_v4_ao_calc_key(struct tcp_ao_key *mkt, u8 *key, + __be32 saddr, __be32 daddr, + __be16 sport, __be16 dport, + __be32 sisn, __be32 disn) { /* See RFC5926 3.1.1 */ struct kdf_input_block { @@ -461,76 +461,78 @@ static int tcp_v4_ao_calc_key(struct tcp_ao_key *mkt, u8 *key, }; tcp_ao_calc_traffic_key(mkt, key, &input, sizeof(input)); - return 0; } -int tcp_v4_ao_calc_key_sk(struct tcp_ao_key *mkt, u8 *key, - const struct sock *sk, - __be32 sisn, __be32 disn, bool send) +void tcp_v4_ao_calc_key_sk(struct tcp_ao_key *mkt, u8 *key, + const struct sock *sk, + __be32 sisn, __be32 disn, bool send) { if (send) - return tcp_v4_ao_calc_key(mkt, key, sk->sk_rcv_saddr, - sk->sk_daddr, htons(sk->sk_num), - sk->sk_dport, sisn, disn); + tcp_v4_ao_calc_key(mkt, key, sk->sk_rcv_saddr, sk->sk_daddr, + htons(sk->sk_num), sk->sk_dport, sisn, disn); else - return tcp_v4_ao_calc_key(mkt, key, sk->sk_daddr, - sk->sk_rcv_saddr, sk->sk_dport, - htons(sk->sk_num), disn, sisn); + tcp_v4_ao_calc_key(mkt, key, sk->sk_daddr, sk->sk_rcv_saddr, + sk->sk_dport, htons(sk->sk_num), disn, sisn); } static int tcp_ao_calc_key_sk(struct tcp_ao_key *mkt, u8 *key, const struct sock *sk, __be32 sisn, __be32 disn, bool send) { - if (mkt->family == AF_INET) - return tcp_v4_ao_calc_key_sk(mkt, key, sk, sisn, disn, send); + if (mkt->family == AF_INET) { + tcp_v4_ao_calc_key_sk(mkt, key, sk, sisn, disn, send); + return 0; + } #if IS_ENABLED(CONFIG_IPV6) - else if (mkt->family == AF_INET6) - return tcp_v6_ao_calc_key_sk(mkt, key, sk, sisn, disn, send); + if (mkt->family == AF_INET6) { + tcp_v6_ao_calc_key_sk(mkt, key, sk, sisn, disn, send); + return 0; + } #endif - else - return -EOPNOTSUPP; + return -EOPNOTSUPP; } -int tcp_v4_ao_calc_key_rsk(struct tcp_ao_key *mkt, u8 *key, - struct request_sock *req) +void tcp_v4_ao_calc_key_rsk(struct tcp_ao_key *mkt, u8 *key, + struct request_sock *req) { struct inet_request_sock *ireq = inet_rsk(req); - return tcp_v4_ao_calc_key(mkt, key, - ireq->ir_loc_addr, ireq->ir_rmt_addr, - htons(ireq->ir_num), ireq->ir_rmt_port, - htonl(tcp_rsk(req)->snt_isn), - htonl(tcp_rsk(req)->rcv_isn)); + tcp_v4_ao_calc_key(mkt, key, ireq->ir_loc_addr, ireq->ir_rmt_addr, + htons(ireq->ir_num), ireq->ir_rmt_port, + htonl(tcp_rsk(req)->snt_isn), + htonl(tcp_rsk(req)->rcv_isn)); } -static int tcp_v4_ao_calc_key_skb(struct tcp_ao_key *mkt, u8 *key, - const struct sk_buff *skb, - __be32 sisn, __be32 disn) +static void tcp_v4_ao_calc_key_skb(struct tcp_ao_key *mkt, u8 *key, + const struct sk_buff *skb, + __be32 sisn, __be32 disn) { const struct iphdr *iph = ip_hdr(skb); const struct tcphdr *th = tcp_hdr(skb); - return tcp_v4_ao_calc_key(mkt, key, iph->saddr, iph->daddr, - th->source, th->dest, sisn, disn); + tcp_v4_ao_calc_key(mkt, key, iph->saddr, iph->daddr, th->source, + th->dest, sisn, disn); } static int tcp_ao_calc_key_skb(struct tcp_ao_key *mkt, u8 *key, const struct sk_buff *skb, __be32 sisn, __be32 disn, int family) { - if (family == AF_INET) - return tcp_v4_ao_calc_key_skb(mkt, key, skb, sisn, disn); + if (family == AF_INET) { + tcp_v4_ao_calc_key_skb(mkt, key, skb, sisn, disn); + return 0; + } #if IS_ENABLED(CONFIG_IPV6) - else if (family == AF_INET6) - return tcp_v6_ao_calc_key_skb(mkt, key, skb, sisn, disn); + if (family == AF_INET6) { + tcp_v6_ao_calc_key_skb(mkt, key, skb, sisn, disn); + return 0; + } #endif return -EAFNOSUPPORT; } -static int tcp_v4_ao_hash_pseudoheader(struct tcp_ao_mac_ctx *mac_ctx, - __be32 daddr, __be32 saddr, - int nbytes) +static void tcp_v4_ao_hash_pseudoheader(struct tcp_ao_mac_ctx *mac_ctx, + __be32 daddr, __be32 saddr, int nbytes) { struct tcp4_pseudohdr phdr = { .saddr = saddr, @@ -541,7 +543,6 @@ static int tcp_v4_ao_hash_pseudoheader(struct tcp_ao_mac_ctx *mac_ctx, }; tcp_ao_mac_update(mac_ctx, &phdr, sizeof(phdr)); - return 0; } static int tcp_ao_hash_pseudoheader(unsigned short int family, @@ -553,31 +554,38 @@ static int tcp_ao_hash_pseudoheader(unsigned short int family, /* TODO: Can we rely on checksum being zero to mean outbound pkt? */ if (!th->check) { - if (family == AF_INET) - return tcp_v4_ao_hash_pseudoheader(mac_ctx, sk->sk_daddr, - sk->sk_rcv_saddr, skb->len); + if (family == AF_INET) { + tcp_v4_ao_hash_pseudoheader(mac_ctx, sk->sk_daddr, + sk->sk_rcv_saddr, skb->len); + return 0; + } #if IS_ENABLED(CONFIG_IPV6) - else if (family == AF_INET6) - return tcp_v6_ao_hash_pseudoheader(mac_ctx, &sk->sk_v6_daddr, - &sk->sk_v6_rcv_saddr, skb->len); + if (family == AF_INET6) { + tcp_v6_ao_hash_pseudoheader(mac_ctx, &sk->sk_v6_daddr, + &sk->sk_v6_rcv_saddr, + skb->len); + return 0; + } #endif - else - return -EAFNOSUPPORT; + return -EAFNOSUPPORT; } if (family == AF_INET) { const struct iphdr *iph = ip_hdr(skb); - return tcp_v4_ao_hash_pseudoheader(mac_ctx, iph->daddr, - iph->saddr, skb->len); + tcp_v4_ao_hash_pseudoheader(mac_ctx, iph->daddr, iph->saddr, + skb->len); + return 0; + } #if IS_ENABLED(CONFIG_IPV6) - } else if (family == AF_INET6) { + if (family == AF_INET6) { const struct ipv6hdr *iph = ipv6_hdr(skb); - return tcp_v6_ao_hash_pseudoheader(mac_ctx, &iph->daddr, - &iph->saddr, skb->len); -#endif + tcp_v6_ao_hash_pseudoheader(mac_ctx, &iph->daddr, &iph->saddr, + skb->len); + return 0; } +#endif return -EAFNOSUPPORT; } @@ -740,11 +748,8 @@ int tcp_v4_ao_synack_hash(char *ao_hash, struct tcp_ao_key *ao_key, int hash_offset, u32 sne) { u8 tkey_buf[TCP_AO_MAX_TRAFFIC_KEY_LEN]; - int err; - err = tcp_v4_ao_calc_key_rsk(ao_key, tkey_buf, req); - if (err) - return err; + tcp_v4_ao_calc_key_rsk(ao_key, tkey_buf, req); return tcp_ao_hash_skb(AF_INET, ao_hash, ao_key, req_to_sk(req), skb, tkey_buf, hash_offset, sne); @@ -857,9 +862,9 @@ int tcp_ao_prepare_reset(const struct sock *sk, struct sk_buff *skb, return 0; } -int tcp_ao_transmit_skb(struct sock *sk, struct sk_buff *skb, - struct tcp_ao_key *key, struct tcphdr *th, - __u8 *hash_location) +void tcp_ao_transmit_skb(struct sock *sk, struct sk_buff *skb, + struct tcp_ao_key *key, struct tcphdr *th, + __u8 *hash_location) { struct tcp_skb_cb *tcb = TCP_SKB_CB(skb); u8 tkey_buf[TCP_AO_MAX_TRAFFIC_KEY_LEN]; @@ -887,7 +892,6 @@ int tcp_ao_transmit_skb(struct sock *sk, struct sk_buff *skb, ntohl(th->seq)); tp->af_specific->calc_ao_hash(hash_location, key, sk, skb, traffic_key, hash_location - (u8 *)th, sne); - return 0; } static struct tcp_ao_key *tcp_ao_inbound_lookup(unsigned short int family, diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c index c8d7b5d20d18..75eb18a2ad4e 100644 --- a/net/ipv4/tcp_output.c +++ b/net/ipv4/tcp_output.c @@ -1657,14 +1657,8 @@ static int __tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, key.md5_key, sk, skb); #endif } else if (tcp_key_is_ao(&key)) { - int err; - - err = tcp_ao_transmit_skb(sk, skb, key.ao_key, th, - opts.hash_location); - if (err) { - sk_skb_reason_drop(sk, skb, SKB_DROP_REASON_NOT_SPECIFIED); - return -ENOMEM; - } + tcp_ao_transmit_skb(sk, skb, key.ao_key, th, + opts.hash_location); } /* BPF prog is the last one writing header option */ diff --git a/net/ipv6/tcp_ao.c b/net/ipv6/tcp_ao.c index bf30b970181d..01a8472805d1 100644 --- a/net/ipv6/tcp_ao.c +++ b/net/ipv6/tcp_ao.c @@ -12,11 +12,11 @@ #include #include -static int tcp_v6_ao_calc_key(struct tcp_ao_key *mkt, u8 *key, - const struct in6_addr *saddr, - const struct in6_addr *daddr, - __be16 sport, __be16 dport, - __be32 sisn, __be32 disn) +static void tcp_v6_ao_calc_key(struct tcp_ao_key *mkt, u8 *key, + const struct in6_addr *saddr, + const struct in6_addr *daddr, + __be16 sport, __be16 dport, + __be32 sisn, __be32 disn) { struct kdf_input_block { u8 counter; @@ -38,45 +38,42 @@ static int tcp_v6_ao_calc_key(struct tcp_ao_key *mkt, u8 *key, }; tcp_ao_calc_traffic_key(mkt, key, &input, sizeof(input)); - return 0; } -int tcp_v6_ao_calc_key_skb(struct tcp_ao_key *mkt, u8 *key, - const struct sk_buff *skb, - __be32 sisn, __be32 disn) +void tcp_v6_ao_calc_key_skb(struct tcp_ao_key *mkt, u8 *key, + const struct sk_buff *skb, __be32 sisn, __be32 disn) { const struct ipv6hdr *iph = ipv6_hdr(skb); const struct tcphdr *th = tcp_hdr(skb); - return tcp_v6_ao_calc_key(mkt, key, &iph->saddr, - &iph->daddr, th->source, - th->dest, sisn, disn); + tcp_v6_ao_calc_key(mkt, key, &iph->saddr, &iph->daddr, th->source, + th->dest, sisn, disn); } -int tcp_v6_ao_calc_key_sk(struct tcp_ao_key *mkt, u8 *key, - const struct sock *sk, __be32 sisn, - __be32 disn, bool send) +void tcp_v6_ao_calc_key_sk(struct tcp_ao_key *mkt, u8 *key, + const struct sock *sk, __be32 sisn, + __be32 disn, bool send) { if (send) - return tcp_v6_ao_calc_key(mkt, key, &sk->sk_v6_rcv_saddr, - &sk->sk_v6_daddr, htons(sk->sk_num), - sk->sk_dport, sisn, disn); + tcp_v6_ao_calc_key(mkt, key, &sk->sk_v6_rcv_saddr, + &sk->sk_v6_daddr, htons(sk->sk_num), + sk->sk_dport, sisn, disn); else - return tcp_v6_ao_calc_key(mkt, key, &sk->sk_v6_daddr, - &sk->sk_v6_rcv_saddr, sk->sk_dport, - htons(sk->sk_num), disn, sisn); + tcp_v6_ao_calc_key(mkt, key, &sk->sk_v6_daddr, + &sk->sk_v6_rcv_saddr, sk->sk_dport, + htons(sk->sk_num), disn, sisn); } -int tcp_v6_ao_calc_key_rsk(struct tcp_ao_key *mkt, u8 *key, - struct request_sock *req) +void tcp_v6_ao_calc_key_rsk(struct tcp_ao_key *mkt, u8 *key, + struct request_sock *req) { struct inet_request_sock *ireq = inet_rsk(req); - return tcp_v6_ao_calc_key(mkt, key, - &ireq->ir_v6_loc_addr, &ireq->ir_v6_rmt_addr, - htons(ireq->ir_num), ireq->ir_rmt_port, - htonl(tcp_rsk(req)->snt_isn), - htonl(tcp_rsk(req)->rcv_isn)); + tcp_v6_ao_calc_key(mkt, key, + &ireq->ir_v6_loc_addr, &ireq->ir_v6_rmt_addr, + htons(ireq->ir_num), ireq->ir_rmt_port, + htonl(tcp_rsk(req)->snt_isn), + htonl(tcp_rsk(req)->rcv_isn)); } struct tcp_ao_key *tcp_v6_ao_lookup(const struct sock *sk, @@ -104,9 +101,9 @@ struct tcp_ao_key *tcp_v6_ao_lookup_rsk(const struct sock *sk, AF_INET6, sndid, rcvid); } -int tcp_v6_ao_hash_pseudoheader(struct tcp_ao_mac_ctx *mac_ctx, - const struct in6_addr *daddr, - const struct in6_addr *saddr, int nbytes) +void tcp_v6_ao_hash_pseudoheader(struct tcp_ao_mac_ctx *mac_ctx, + const struct in6_addr *daddr, + const struct in6_addr *saddr, int nbytes) { /* 1. TCP pseudo-header (RFC2460) */ struct tcp6_pseudohdr phdr = { @@ -117,7 +114,6 @@ int tcp_v6_ao_hash_pseudoheader(struct tcp_ao_mac_ctx *mac_ctx, }; tcp_ao_mac_update(mac_ctx, &phdr, sizeof(phdr)); - return 0; } int tcp_v6_ao_hash_skb(char *ao_hash, struct tcp_ao_key *key, @@ -139,11 +135,8 @@ int tcp_v6_ao_synack_hash(char *ao_hash, struct tcp_ao_key *ao_key, int hash_offset, u32 sne) { u8 tkey_buf[TCP_AO_MAX_TRAFFIC_KEY_LEN]; - int err; - err = tcp_v6_ao_calc_key_rsk(ao_key, tkey_buf, req); - if (err) - return err; + tcp_v6_ao_calc_key_rsk(ao_key, tkey_buf, req); return tcp_ao_hash_skb(AF_INET6, ao_hash, ao_key, req_to_sk(req), skb, tkey_buf, hash_offset, sne); -- cgit v1.2.3 From 4baf2415992e3051cb39b831191d12cb7e85ab60 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Mon, 27 Apr 2026 10:27:27 -0700 Subject: net/tcp: Remove tcp_sigpool tcp_sigpool is no longer used. It existed only as a workaround for issues in the design of the crypto_ahash API, which have been avoided by switching to the much easier-to-use library APIs instead. Remove it. Reviewed-by: Ard Biesheuvel Signed-off-by: Eric Biggers Link: https://patch.msgid.link/20260427172727.9310-6-ebiggers@kernel.org Signed-off-by: Paolo Abeni --- include/net/tcp.h | 34 ----- net/ipv4/Kconfig | 3 - net/ipv4/Makefile | 1 - net/ipv4/tcp_sigpool.c | 366 ------------------------------------------------- 4 files changed, 404 deletions(-) delete mode 100644 net/ipv4/tcp_sigpool.c (limited to 'include') diff --git a/include/net/tcp.h b/include/net/tcp.h index 35fc6b35b216..3c4e6adb0dbd 100644 --- a/include/net/tcp.h +++ b/include/net/tcp.h @@ -2027,40 +2027,6 @@ struct tcp6_pseudohdr { __be32 protocol; /* including padding */ }; -/* - * struct tcp_sigpool - per-CPU pool of ahash_requests - * @scratch: per-CPU temporary area, that can be used between - * tcp_sigpool_start() and tcp_sigpool_end() to perform - * crypto request - * @req: pre-allocated ahash request - */ -struct tcp_sigpool { - void *scratch; - struct ahash_request *req; -}; - -int tcp_sigpool_alloc_ahash(const char *alg, size_t scratch_size); -void tcp_sigpool_get(unsigned int id); -void tcp_sigpool_release(unsigned int id); -int tcp_sigpool_hash_skb_data(struct tcp_sigpool *hp, - const struct sk_buff *skb, - unsigned int header_len); - -/** - * tcp_sigpool_start - disable bh and start using tcp_sigpool_ahash - * @id: tcp_sigpool that was previously allocated by tcp_sigpool_alloc_ahash() - * @c: returned tcp_sigpool for usage (uninitialized on failure) - * - * Returns: 0 on success, error otherwise. - */ -int tcp_sigpool_start(unsigned int id, struct tcp_sigpool *c); -/** - * tcp_sigpool_end - enable bh and stop using tcp_sigpool - * @c: tcp_sigpool context that was returned by tcp_sigpool_start() - */ -void tcp_sigpool_end(struct tcp_sigpool *c); -size_t tcp_sigpool_algo(unsigned int id, char *buf, size_t buf_len); -/* - functions */ void tcp_v4_md5_hash_skb(char *md5_hash, const struct tcp_md5sig_key *key, const struct sock *sk, const struct sk_buff *skb); int tcp_md5_do_add(struct sock *sk, const union tcp_md5_addr *addr, diff --git a/net/ipv4/Kconfig b/net/ipv4/Kconfig index 77b053b445a0..301b47660305 100644 --- a/net/ipv4/Kconfig +++ b/net/ipv4/Kconfig @@ -741,9 +741,6 @@ config DEFAULT_TCP_CONG default "bbr" if DEFAULT_BBR default "cubic" -config TCP_SIGPOOL - tristate - config TCP_AO bool "TCP: Authentication Option (RFC5925)" select CRYPTO_LIB_AES_CBC_MACS diff --git a/net/ipv4/Makefile b/net/ipv4/Makefile index 7f9f98813986..7964234f0d08 100644 --- a/net/ipv4/Makefile +++ b/net/ipv4/Makefile @@ -60,7 +60,6 @@ obj-$(CONFIG_TCP_CONG_SCALABLE) += tcp_scalable.o obj-$(CONFIG_TCP_CONG_LP) += tcp_lp.o obj-$(CONFIG_TCP_CONG_YEAH) += tcp_yeah.o obj-$(CONFIG_TCP_CONG_ILLINOIS) += tcp_illinois.o -obj-$(CONFIG_TCP_SIGPOOL) += tcp_sigpool.o obj-$(CONFIG_NET_SOCK_MSG) += tcp_bpf.o obj-$(CONFIG_BPF_SYSCALL) += udp_bpf.o obj-$(CONFIG_NETLABEL) += cipso_ipv4.o diff --git a/net/ipv4/tcp_sigpool.c b/net/ipv4/tcp_sigpool.c deleted file mode 100644 index 10b2e5970c40..000000000000 --- a/net/ipv4/tcp_sigpool.c +++ /dev/null @@ -1,366 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later - -#include -#include -#include -#include -#include -#include -#include -#include - -static size_t __scratch_size; -struct sigpool_scratch { - local_lock_t bh_lock; - void __rcu *pad; -}; - -static DEFINE_PER_CPU(struct sigpool_scratch, sigpool_scratch) = { - .bh_lock = INIT_LOCAL_LOCK(bh_lock), -}; - -struct sigpool_entry { - struct crypto_ahash *hash; - const char *alg; - struct kref kref; - uint16_t needs_key:1, - reserved:15; -}; - -#define CPOOL_SIZE (PAGE_SIZE / sizeof(struct sigpool_entry)) -static struct sigpool_entry cpool[CPOOL_SIZE]; -static unsigned int cpool_populated; -static DEFINE_MUTEX(cpool_mutex); - -/* Slow-path */ -struct scratches_to_free { - struct rcu_head rcu; - unsigned int cnt; - void *scratches[]; -}; - -static void free_old_scratches(struct rcu_head *head) -{ - struct scratches_to_free *stf; - - stf = container_of(head, struct scratches_to_free, rcu); - while (stf->cnt--) - kfree(stf->scratches[stf->cnt]); - kfree(stf); -} - -/** - * sigpool_reserve_scratch - re-allocates scratch buffer, slow-path - * @size: request size for the scratch/temp buffer - */ -static int sigpool_reserve_scratch(size_t size) -{ - struct scratches_to_free *stf; - size_t stf_sz = struct_size(stf, scratches, num_possible_cpus()); - int cpu, err = 0; - - lockdep_assert_held(&cpool_mutex); - if (__scratch_size >= size) - return 0; - - stf = kmalloc(stf_sz, GFP_KERNEL); - if (!stf) - return -ENOMEM; - stf->cnt = 0; - - size = max(size, __scratch_size); - cpus_read_lock(); - for_each_possible_cpu(cpu) { - void *scratch, *old_scratch; - - scratch = kmalloc_node(size, GFP_KERNEL, cpu_to_node(cpu)); - if (!scratch) { - err = -ENOMEM; - break; - } - - old_scratch = rcu_replace_pointer(per_cpu(sigpool_scratch.pad, cpu), - scratch, lockdep_is_held(&cpool_mutex)); - if (!cpu_online(cpu) || !old_scratch) { - kfree(old_scratch); - continue; - } - stf->scratches[stf->cnt++] = old_scratch; - } - cpus_read_unlock(); - if (!err) - __scratch_size = size; - - call_rcu(&stf->rcu, free_old_scratches); - return err; -} - -static void sigpool_scratch_free(void) -{ - int cpu; - - for_each_possible_cpu(cpu) - kfree(rcu_replace_pointer(per_cpu(sigpool_scratch.pad, cpu), - NULL, lockdep_is_held(&cpool_mutex))); - __scratch_size = 0; -} - -static int __cpool_try_clone(struct crypto_ahash *hash) -{ - struct crypto_ahash *tmp; - - tmp = crypto_clone_ahash(hash); - if (IS_ERR(tmp)) - return PTR_ERR(tmp); - - crypto_free_ahash(tmp); - return 0; -} - -static int __cpool_alloc_ahash(struct sigpool_entry *e, const char *alg) -{ - struct crypto_ahash *cpu0_hash; - int ret; - - e->alg = kstrdup(alg, GFP_KERNEL); - if (!e->alg) - return -ENOMEM; - - cpu0_hash = crypto_alloc_ahash(alg, 0, CRYPTO_ALG_ASYNC); - if (IS_ERR(cpu0_hash)) { - ret = PTR_ERR(cpu0_hash); - goto out_free_alg; - } - - e->needs_key = crypto_ahash_get_flags(cpu0_hash) & CRYPTO_TFM_NEED_KEY; - - ret = __cpool_try_clone(cpu0_hash); - if (ret) - goto out_free_cpu0_hash; - e->hash = cpu0_hash; - kref_init(&e->kref); - return 0; - -out_free_cpu0_hash: - crypto_free_ahash(cpu0_hash); -out_free_alg: - kfree(e->alg); - e->alg = NULL; - return ret; -} - -/** - * tcp_sigpool_alloc_ahash - allocates pool for ahash requests - * @alg: name of async hash algorithm - * @scratch_size: reserve a tcp_sigpool::scratch buffer of this size - */ -int tcp_sigpool_alloc_ahash(const char *alg, size_t scratch_size) -{ - int i, ret; - - /* slow-path */ - mutex_lock(&cpool_mutex); - ret = sigpool_reserve_scratch(scratch_size); - if (ret) - goto out; - for (i = 0; i < cpool_populated; i++) { - if (!cpool[i].alg) - continue; - if (strcmp(cpool[i].alg, alg)) - continue; - - /* pairs with tcp_sigpool_release() */ - if (!kref_get_unless_zero(&cpool[i].kref)) - kref_init(&cpool[i].kref); - ret = i; - goto out; - } - - for (i = 0; i < cpool_populated; i++) { - if (!cpool[i].alg) - break; - } - if (i >= CPOOL_SIZE) { - ret = -ENOSPC; - goto out; - } - - ret = __cpool_alloc_ahash(&cpool[i], alg); - if (!ret) { - ret = i; - if (i == cpool_populated) - cpool_populated++; - } -out: - mutex_unlock(&cpool_mutex); - return ret; -} -EXPORT_SYMBOL_GPL(tcp_sigpool_alloc_ahash); - -static void __cpool_free_entry(struct sigpool_entry *e) -{ - crypto_free_ahash(e->hash); - kfree(e->alg); - memset(e, 0, sizeof(*e)); -} - -static void cpool_cleanup_work_cb(struct work_struct *work) -{ - bool free_scratch = true; - unsigned int i; - - mutex_lock(&cpool_mutex); - for (i = 0; i < cpool_populated; i++) { - if (kref_read(&cpool[i].kref) > 0) { - free_scratch = false; - continue; - } - if (!cpool[i].alg) - continue; - __cpool_free_entry(&cpool[i]); - } - if (free_scratch) - sigpool_scratch_free(); - mutex_unlock(&cpool_mutex); -} - -static DECLARE_WORK(cpool_cleanup_work, cpool_cleanup_work_cb); -static void cpool_schedule_cleanup(struct kref *kref) -{ - schedule_work(&cpool_cleanup_work); -} - -/** - * tcp_sigpool_release - decreases number of users for a pool. If it was - * the last user of the pool, releases any memory that was consumed. - * @id: tcp_sigpool that was previously allocated by tcp_sigpool_alloc_ahash() - */ -void tcp_sigpool_release(unsigned int id) -{ - if (WARN_ON_ONCE(id >= cpool_populated || !cpool[id].alg)) - return; - - /* slow-path */ - kref_put(&cpool[id].kref, cpool_schedule_cleanup); -} -EXPORT_SYMBOL_GPL(tcp_sigpool_release); - -/** - * tcp_sigpool_get - increases number of users (refcounter) for a pool - * @id: tcp_sigpool that was previously allocated by tcp_sigpool_alloc_ahash() - */ -void tcp_sigpool_get(unsigned int id) -{ - if (WARN_ON_ONCE(id >= cpool_populated || !cpool[id].alg)) - return; - kref_get(&cpool[id].kref); -} -EXPORT_SYMBOL_GPL(tcp_sigpool_get); - -int tcp_sigpool_start(unsigned int id, struct tcp_sigpool *c) __cond_acquires(0, RCU_BH) -{ - struct crypto_ahash *hash; - - rcu_read_lock_bh(); - if (WARN_ON_ONCE(id >= cpool_populated || !cpool[id].alg)) { - rcu_read_unlock_bh(); - return -EINVAL; - } - - hash = crypto_clone_ahash(cpool[id].hash); - if (IS_ERR(hash)) { - rcu_read_unlock_bh(); - return PTR_ERR(hash); - } - - c->req = ahash_request_alloc(hash, GFP_ATOMIC); - if (!c->req) { - crypto_free_ahash(hash); - rcu_read_unlock_bh(); - return -ENOMEM; - } - ahash_request_set_callback(c->req, 0, NULL, NULL); - - /* Pairs with tcp_sigpool_reserve_scratch(), scratch area is - * valid (allocated) until tcp_sigpool_end(). - */ - local_lock_nested_bh(&sigpool_scratch.bh_lock); - c->scratch = rcu_dereference_bh(*this_cpu_ptr(&sigpool_scratch.pad)); - return 0; -} -EXPORT_SYMBOL_GPL(tcp_sigpool_start); - -void tcp_sigpool_end(struct tcp_sigpool *c) __releases(RCU_BH) -{ - struct crypto_ahash *hash = crypto_ahash_reqtfm(c->req); - - local_unlock_nested_bh(&sigpool_scratch.bh_lock); - rcu_read_unlock_bh(); - ahash_request_free(c->req); - crypto_free_ahash(hash); -} -EXPORT_SYMBOL_GPL(tcp_sigpool_end); - -/** - * tcp_sigpool_algo - return algorithm of tcp_sigpool - * @id: tcp_sigpool that was previously allocated by tcp_sigpool_alloc_ahash() - * @buf: buffer to return name of algorithm - * @buf_len: size of @buf - */ -size_t tcp_sigpool_algo(unsigned int id, char *buf, size_t buf_len) -{ - if (WARN_ON_ONCE(id >= cpool_populated || !cpool[id].alg)) - return -EINVAL; - - return strscpy(buf, cpool[id].alg, buf_len); -} -EXPORT_SYMBOL_GPL(tcp_sigpool_algo); - -/** - * tcp_sigpool_hash_skb_data - hash data in skb with initialized tcp_sigpool - * @hp: tcp_sigpool pointer - * @skb: buffer to add sign for - * @header_len: TCP header length for this segment - */ -int tcp_sigpool_hash_skb_data(struct tcp_sigpool *hp, - const struct sk_buff *skb, - unsigned int header_len) -{ - const unsigned int head_data_len = skb_headlen(skb) > header_len ? - skb_headlen(skb) - header_len : 0; - const struct skb_shared_info *shi = skb_shinfo(skb); - const struct tcphdr *tp = tcp_hdr(skb); - struct ahash_request *req = hp->req; - struct sk_buff *frag_iter; - struct scatterlist sg; - unsigned int i; - - sg_init_table(&sg, 1); - - sg_set_buf(&sg, ((u8 *)tp) + header_len, head_data_len); - ahash_request_set_crypt(req, &sg, NULL, head_data_len); - if (crypto_ahash_update(req)) - return 1; - - for (i = 0; i < shi->nr_frags; ++i) { - const skb_frag_t *f = &shi->frags[i]; - unsigned int offset = skb_frag_off(f); - struct page *page; - - page = skb_frag_page(f) + (offset >> PAGE_SHIFT); - sg_set_page(&sg, page, skb_frag_size(f), offset_in_page(offset)); - ahash_request_set_crypt(req, &sg, NULL, skb_frag_size(f)); - if (crypto_ahash_update(req)) - return 1; - } - - skb_walk_frags(skb, frag_iter) - if (tcp_sigpool_hash_skb_data(hp, frag_iter, 0)) - return 1; - - return 0; -} -EXPORT_SYMBOL(tcp_sigpool_hash_skb_data); - -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("Per-CPU pool of crypto requests"); -- cgit v1.2.3 From 781c8893a5da8522ae4ded93e5ef3adbe9559300 Mon Sep 17 00:00:00 2001 From: Ivan Vecera Date: Tue, 28 Apr 2026 17:49:06 +0200 Subject: dpll: add pin operational state Add pin-operstate enum and operstate_on_dpll_get callback to report the actual hardware status of a pin with respect to its parent DPLL device. Unlike pin-state (which reflects administrative intent set by the user), operstate reflects what the hardware is actually doing. Defined operational states: - active: pin is qualified and actively used by the DPLL - standby: pin is qualified but not actively used by the DPLL - no-signal: pin does not have a valid signal - qual-failed: pin signal failed qualification The operstate is reported inside the pin-parent-device nested attribute alongside the existing state and phase-offset attributes. Signed-off-by: Ivan Vecera Reviewed-by: Jiri Pirko Reviewed-by: Vadim Fedorenko Reviewed-by: Petr Oros Link: https://patch.msgid.link/20260428154907.2820654-2-ivecera@redhat.com Signed-off-by: Paolo Abeni --- Documentation/driver-api/dpll.rst | 38 +++++++++++++++++++++-------------- Documentation/netlink/specs/dpll.yaml | 31 ++++++++++++++++++++++++++++ drivers/dpll/dpll_netlink.c | 27 +++++++++++++++++++++++++ drivers/dpll/dpll_nl.c | 3 ++- drivers/dpll/dpll_nl.h | 2 +- include/linux/dpll.h | 6 ++++++ include/uapi/linux/dpll.h | 23 +++++++++++++++++++++ 7 files changed, 113 insertions(+), 17 deletions(-) (limited to 'include') diff --git a/Documentation/driver-api/dpll.rst b/Documentation/driver-api/dpll.rst index 93c191b2d089..37eaef785e30 100644 --- a/Documentation/driver-api/dpll.rst +++ b/Documentation/driver-api/dpll.rst @@ -65,35 +65,43 @@ request, where user provides attributes that result in single pin match. Pin selection ============= -In general, selected pin (the one which signal is driving the dpll -device) can be obtained from ``DPLL_A_PIN_STATE`` attribute, and only -one pin shall be in ``DPLL_PIN_STATE_CONNECTED`` state for any dpll -device. +Pin state (``DPLL_A_PIN_STATE``) reflects the administrative intent set +by the user. Pin operational state (``DPLL_A_PIN_OPERSTATE``) reflects +what the hardware is actually doing with the pin. Pin selection can be done either manually or automatically, depending on hardware capabilities and active dpll device work mode (``DPLL_A_MODE`` attribute). The consequence is that there are -differences for each mode in terms of available pin states, as well as -for the states the user can request for a dpll device. +differences for each mode in terms of available pin states the user can +request for a dpll device. -In manual mode (``DPLL_MODE_MANUAL``) the user can request or receive -one of following pin states: +In manual mode (``DPLL_MODE_MANUAL``) the user can request one of +following pin states: -- ``DPLL_PIN_STATE_CONNECTED`` - the pin is used to drive dpll device -- ``DPLL_PIN_STATE_DISCONNECTED`` - the pin is not used to drive dpll +- ``DPLL_PIN_STATE_CONNECTED`` - the pin is selected to drive dpll device +- ``DPLL_PIN_STATE_DISCONNECTED`` - the pin is not selected to drive + dpll device -In automatic mode (``DPLL_MODE_AUTOMATIC``) the user can request or -receive one of following pin states: +In automatic mode (``DPLL_MODE_AUTOMATIC``) the user can request one of +following pin states: - ``DPLL_PIN_STATE_SELECTABLE`` - the pin shall be considered as valid input for automatic selection algorithm - ``DPLL_PIN_STATE_DISCONNECTED`` - the pin shall be not considered as a valid input for automatic selection algorithm -In automatic mode (``DPLL_MODE_AUTOMATIC``) the user can only receive -pin state ``DPLL_PIN_STATE_CONNECTED`` once automatic selection -algorithm locks a dpll device with one of the inputs. +The actual hardware status of a pin is reported via the operational +state (``DPLL_A_PIN_OPERSTATE``) attribute nested under the parent +device: + +- ``DPLL_PIN_OPERSTATE_ACTIVE`` - pin is qualified and actively used + by the DPLL +- ``DPLL_PIN_OPERSTATE_STANDBY`` - pin is qualified but not actively + used by the DPLL +- ``DPLL_PIN_OPERSTATE_NO_SIGNAL`` - pin does not have a valid signal +- ``DPLL_PIN_OPERSTATE_QUAL_FAILED`` - pin signal failed qualification + checks Shared pins =========== diff --git a/Documentation/netlink/specs/dpll.yaml b/Documentation/netlink/specs/dpll.yaml index 40465a3d7fc2..c45de70a47ce 100644 --- a/Documentation/netlink/specs/dpll.yaml +++ b/Documentation/netlink/specs/dpll.yaml @@ -212,6 +212,27 @@ definitions: name: selectable doc: pin enabled for automatic input selection render-max: true + - + type: enum + name: pin-operstate + doc: | + defines possible operational states of a pin with respect to its + parent DPLL device, valid values for DPLL_A_PIN_OPERSTATE attribute + entries: + - + name: active + doc: pin is qualified and actively used by the DPLL + value: 1 + - + name: standby + doc: pin is qualified but not actively used by the DPLL + - + name: no-signal + doc: pin does not have a valid signal + - + name: qual-failed + doc: pin signal failed qualification (e.g. frequency or phase monitor) + render-max: true - type: flags name: pin-capabilities @@ -488,6 +509,14 @@ attribute-sets: Value of (DPLL_A_PIN_MEASURED_FREQUENCY % DPLL_PIN_MEASURED_FREQUENCY_DIVIDER) is a fractional part of a measured frequency value. + - + name: operstate + type: u32 + enum: pin-operstate + doc: | + Operational state of the pin with respect to its parent DPLL + device. Unlike state (which reflects the administrative intent), + operstate reflects the actual hardware status. - name: pin-parent-device @@ -501,6 +530,8 @@ attribute-sets: name: prio - name: state + - + name: operstate - name: phase-offset - diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c index af7ce62ec55c..05cf946b4be5 100644 --- a/drivers/dpll/dpll_netlink.c +++ b/drivers/dpll/dpll_netlink.c @@ -324,6 +324,30 @@ dpll_msg_add_pin_on_dpll_state(struct sk_buff *msg, struct dpll_pin *pin, return 0; } +static int +dpll_msg_add_pin_operstate(struct sk_buff *msg, struct dpll_pin *pin, + struct dpll_pin_ref *ref, + struct netlink_ext_ack *extack) +{ + const struct dpll_pin_ops *ops = dpll_pin_ops(ref); + struct dpll_device *dpll = ref->dpll; + enum dpll_pin_operstate operstate; + int ret; + + if (!ops->operstate_on_dpll_get) + return 0; + ret = ops->operstate_on_dpll_get(pin, + dpll_pin_on_dpll_priv(dpll, pin), + dpll, dpll_priv(dpll), + &operstate, extack); + if (ret) + return ret; + if (nla_put_u32(msg, DPLL_A_PIN_OPERSTATE, operstate)) + return -EMSGSIZE; + + return 0; +} + static int dpll_msg_add_pin_direction(struct sk_buff *msg, struct dpll_pin *pin, struct dpll_pin_ref *ref, @@ -650,6 +674,9 @@ dpll_msg_add_pin_dplls(struct sk_buff *msg, struct dpll_pin *pin, if (ret) goto nest_cancel; ret = dpll_msg_add_pin_on_dpll_state(msg, pin, ref, extack); + if (ret) + goto nest_cancel; + ret = dpll_msg_add_pin_operstate(msg, pin, ref, extack); if (ret) goto nest_cancel; ret = dpll_msg_add_pin_prio(msg, pin, ref, extack); diff --git a/drivers/dpll/dpll_nl.c b/drivers/dpll/dpll_nl.c index 1e652340a5d7..58235845fa3d 100644 --- a/drivers/dpll/dpll_nl.c +++ b/drivers/dpll/dpll_nl.c @@ -12,11 +12,12 @@ #include /* Common nested types */ -const struct nla_policy dpll_pin_parent_device_nl_policy[DPLL_A_PIN_PHASE_OFFSET + 1] = { +const struct nla_policy dpll_pin_parent_device_nl_policy[DPLL_A_PIN_OPERSTATE + 1] = { [DPLL_A_PIN_PARENT_ID] = { .type = NLA_U32, }, [DPLL_A_PIN_DIRECTION] = NLA_POLICY_RANGE(NLA_U32, 1, 2), [DPLL_A_PIN_PRIO] = { .type = NLA_U32, }, [DPLL_A_PIN_STATE] = NLA_POLICY_RANGE(NLA_U32, 1, 3), + [DPLL_A_PIN_OPERSTATE] = NLA_POLICY_RANGE(NLA_U32, 1, 4), [DPLL_A_PIN_PHASE_OFFSET] = { .type = NLA_S64, }, }; diff --git a/drivers/dpll/dpll_nl.h b/drivers/dpll/dpll_nl.h index 7419679b6977..fa8280e3dd14 100644 --- a/drivers/dpll/dpll_nl.h +++ b/drivers/dpll/dpll_nl.h @@ -13,7 +13,7 @@ #include /* Common nested types */ -extern const struct nla_policy dpll_pin_parent_device_nl_policy[DPLL_A_PIN_PHASE_OFFSET + 1]; +extern const struct nla_policy dpll_pin_parent_device_nl_policy[DPLL_A_PIN_OPERSTATE + 1]; extern const struct nla_policy dpll_pin_parent_pin_nl_policy[DPLL_A_PIN_STATE + 1]; extern const struct nla_policy dpll_reference_sync_nl_policy[DPLL_A_PIN_STATE + 1]; diff --git a/include/linux/dpll.h b/include/linux/dpll.h index b7277a8b484d..b6f16c884b99 100644 --- a/include/linux/dpll.h +++ b/include/linux/dpll.h @@ -85,6 +85,12 @@ struct dpll_pin_ops { const struct dpll_device *dpll, void *dpll_priv, enum dpll_pin_state *state, struct netlink_ext_ack *extack); + int (*operstate_on_dpll_get)(const struct dpll_pin *pin, + void *pin_priv, + const struct dpll_device *dpll, + void *dpll_priv, + enum dpll_pin_operstate *operstate, + struct netlink_ext_ack *extack); int (*state_on_pin_set)(const struct dpll_pin *pin, void *pin_priv, const struct dpll_pin *parent_pin, void *parent_pin_priv, diff --git a/include/uapi/linux/dpll.h b/include/uapi/linux/dpll.h index 871685f7c353..cb363cccf2e2 100644 --- a/include/uapi/linux/dpll.h +++ b/include/uapi/linux/dpll.h @@ -178,6 +178,28 @@ enum dpll_pin_state { DPLL_PIN_STATE_MAX = (__DPLL_PIN_STATE_MAX - 1) }; +/** + * enum dpll_pin_operstate - defines possible operational states of a pin with + * respect to its parent DPLL device, valid values for DPLL_A_PIN_OPERSTATE + * attribute + * @DPLL_PIN_OPERSTATE_ACTIVE: pin is qualified and actively used by the DPLL + * @DPLL_PIN_OPERSTATE_STANDBY: pin is qualified but not actively used by the + * DPLL + * @DPLL_PIN_OPERSTATE_NO_SIGNAL: pin does not have a valid signal + * @DPLL_PIN_OPERSTATE_QUAL_FAILED: pin signal failed qualification (e.g. + * frequency or phase monitor) + */ +enum dpll_pin_operstate { + DPLL_PIN_OPERSTATE_ACTIVE = 1, + DPLL_PIN_OPERSTATE_STANDBY, + DPLL_PIN_OPERSTATE_NO_SIGNAL, + DPLL_PIN_OPERSTATE_QUAL_FAILED, + + /* private: */ + __DPLL_PIN_OPERSTATE_MAX, + DPLL_PIN_OPERSTATE_MAX = (__DPLL_PIN_OPERSTATE_MAX - 1) +}; + /** * enum dpll_pin_capabilities - defines possible capabilities of a pin, valid * flags on DPLL_A_PIN_CAPABILITIES attribute @@ -257,6 +279,7 @@ enum dpll_a_pin { DPLL_A_PIN_PHASE_ADJUST_GRAN, DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET_PPT, DPLL_A_PIN_MEASURED_FREQUENCY, + DPLL_A_PIN_OPERSTATE, __DPLL_A_PIN_MAX, DPLL_A_PIN_MAX = (__DPLL_A_PIN_MAX - 1) -- cgit v1.2.3 From 93cda0c120acdfd717a66edfaf031942450d4908 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 29 Apr 2026 16:55:45 +0200 Subject: net: cs89x0: remove ISA bus probing The cs89x0 driver is really two in one, and they are mutually exclusive: - the ISA driver was used on 486-era PCs. It likely has no remaining users, like the other ethernet drivers that got removed in linux-7.1. The DMA support in here is the last device driver use of the deprecated isa_bus_to_virt() interface, all other users are either x86 specific or or got converted to the normal dma-mapping interface. The driver was maintained by Andrew Morton at the time, based on the linux-2.2 vendor driver from Cirrus Logic. - the platform_driver instance was used on some embedded Arm boards around the same time, such as the EP7211 Development Kit. This is the same chip, but uses modern devicetree based probing and no DMA. This was added by Alexander Shiyan. Remove the ISA driver as a cleanup, including all of the outdated documentation referring to its configuration. Cc: Andrew Morton Signed-off-by: Arnd Bergmann Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260429145624.2948432-1-arnd@kernel.org Signed-off-by: Jakub Kicinski --- .../device_drivers/ethernet/cirrus/cs89x0.rst | 647 --------------------- .../networking/device_drivers/ethernet/index.rst | 1 - drivers/net/Space.c | 3 - drivers/net/ethernet/cirrus/Kconfig | 20 +- drivers/net/ethernet/cirrus/cs89x0.c | 636 +------------------- include/net/Space.h | 1 - 6 files changed, 6 insertions(+), 1302 deletions(-) delete mode 100644 Documentation/networking/device_drivers/ethernet/cirrus/cs89x0.rst (limited to 'include') diff --git a/Documentation/networking/device_drivers/ethernet/cirrus/cs89x0.rst b/Documentation/networking/device_drivers/ethernet/cirrus/cs89x0.rst deleted file mode 100644 index e5c283940ac5..000000000000 --- a/Documentation/networking/device_drivers/ethernet/cirrus/cs89x0.rst +++ /dev/null @@ -1,647 +0,0 @@ -.. SPDX-License-Identifier: GPL-2.0 - -================================================ -Cirrus Logic LAN CS8900/CS8920 Ethernet Adapters -================================================ - -.. note:: - - This document was contributed by Cirrus Logic for kernel 2.2.5. This version - has been updated for 2.3.48 by Andrew Morton. - - Still, this is too outdated! A major cleanup is needed here. - -Cirrus make a copy of this driver available at their website, as -described below. In general, you should use the driver version which -comes with your Linux distribution. - - -Linux Network Interface Driver ver. 2.00 - - -.. TABLE OF CONTENTS - - 1.0 CIRRUS LOGIC LAN CS8900/CS8920 ETHERNET ADAPTERS - 1.1 Product Overview - 1.2 Driver Description - 1.2.1 Driver Name - 1.2.2 File in the Driver Package - 1.3 System Requirements - 1.4 Licensing Information - - 2.0 ADAPTER INSTALLATION and CONFIGURATION - 2.1 CS8900-based Adapter Configuration - 2.2 CS8920-based Adapter Configuration - - 3.0 LOADING THE DRIVER AS A MODULE - - 4.0 COMPILING THE DRIVER - 4.1 Compiling the Driver as a Loadable Module - 4.2 Compiling the driver to support memory mode - 4.3 Compiling the driver to support Rx DMA - - 5.0 TESTING AND TROUBLESHOOTING - 5.1 Known Defects and Limitations - 5.2 Testing the Adapter - 5.2.1 Diagnostic Self-Test - 5.2.2 Diagnostic Network Test - 5.3 Using the Adapter's LEDs - 5.4 Resolving I/O Conflicts - - 6.0 TECHNICAL SUPPORT - 6.1 Contacting Cirrus Logic's Technical Support - 6.2 Information Required Before Contacting Technical Support - 6.3 Obtaining the Latest Driver Version - 6.4 Current maintainer - 6.5 Kernel boot parameters - - -1. Cirrus Logic LAN CS8900/CS8920 Ethernet Adapters -=================================================== - - -1.1. Product Overview -===================== - -The CS8900-based ISA Ethernet Adapters from Cirrus Logic follow -IEEE 802.3 standards and support half or full-duplex operation in ISA bus -computers on 10 Mbps Ethernet networks. The adapters are designed for operation -in 16-bit ISA or EISA bus expansion slots and are available in -10BaseT-only or 3-media configurations (10BaseT, 10Base2, and AUI for 10Base-5 -or fiber networks). - -CS8920-based adapters are similar to the CS8900-based adapter with additional -features for Plug and Play (PnP) support and Wakeup Frame recognition. As -such, the configuration procedures differ somewhat between the two types of -adapters. Refer to the "Adapter Configuration" section for details on -configuring both types of adapters. - - -1.2. Driver Description -======================= - -The CS8900/CS8920 Ethernet Adapter driver for Linux supports the Linux -v2.3.48 or greater kernel. It can be compiled directly into the kernel -or loaded at run-time as a device driver module. - -1.2.1 Driver Name: cs89x0 - -1.2.2 Files in the Driver Archive: - -The files in the driver at Cirrus' website include: - - =================== ==================================================== - readme.txt this file - build batch file to compile cs89x0.c. - cs89x0.c driver C code - cs89x0.h driver header file - cs89x0.o pre-compiled module (for v2.2.5 kernel) - config/Config.in sample file to include cs89x0 driver in the kernel. - config/Makefile sample file to include cs89x0 driver in the kernel. - config/Space.c sample file to include cs89x0 driver in the kernel. - =================== ==================================================== - - - -1.3. System Requirements ------------------------- - -The following hardware is required: - - * Cirrus Logic LAN (CS8900/20-based) Ethernet ISA Adapter - - * IBM or IBM-compatible PC with: - * An 80386 or higher processor - * 16 bytes of contiguous IO space available between 210h - 370h - * One available IRQ (5,10,11,or 12 for the CS8900, 3-7,9-15 for CS8920). - - * Appropriate cable (and connector for AUI, 10BASE-2) for your network - topology. - -The following software is required: - -* LINUX kernel version 2.3.48 or higher - - * CS8900/20 Setup Utility (DOS-based) - - * LINUX kernel sources for your kernel (if compiling into kernel) - - * GNU Toolkit (gcc and make) v2.6 or above (if compiling into kernel - or a module) - - - -1.4. Licensing Information --------------------------- - -This program is free software; you can redistribute it and/or modify it under -the terms of the GNU General Public License as published by the Free Software -Foundation, version 1. - -This program is distributed in the hope that it will be useful, but WITHOUT -ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for -more details. - -For a full copy of the GNU General Public License, write to the Free Software -Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - - - -2. Adapter Installation and Configuration -========================================= - -Both the CS8900 and CS8920-based adapters can be configured using parameters -stored in an on-board EEPROM. You must use the DOS-based CS8900/20 Setup -Utility if you want to change the adapter's configuration in EEPROM. - -When loading the driver as a module, you can specify many of the adapter's -configuration parameters on the command-line to override the EEPROM's settings -or for interface configuration when an EEPROM is not used. (CS8920-based -adapters must use an EEPROM.) See Section 3.0 LOADING THE DRIVER AS A MODULE. - -Since the CS8900/20 Setup Utility is a DOS-based application, you must install -and configure the adapter in a DOS-based system using the CS8900/20 Setup -Utility before installation in the target LINUX system. (Not required if -installing a CS8900-based adapter and the default configuration is acceptable.) - - -2.1. CS8900-based Adapter Configuration ---------------------------------------- - -CS8900-based adapters shipped from Cirrus Logic have been configured -with the following "default" settings:: - - Operation Mode: Memory Mode - IRQ: 10 - Base I/O Address: 300 - Memory Base Address: D0000 - Optimization: DOS Client - Transmission Mode: Half-duplex - BootProm: None - Media Type: Autodetect (3-media cards) or - 10BASE-T (10BASE-T only adapter) - -You should only change the default configuration settings if conflicts with -another adapter exists. To change the adapter's configuration, run the -CS8900/20 Setup Utility. - - -2.2. CS8920-based Adapter Configuration ---------------------------------------- - -CS8920-based adapters are shipped from Cirrus Logic configured as Plug -and Play (PnP) enabled. However, since the cs89x0 driver does NOT -support PnP, you must install the CS8920 adapter in a DOS-based PC and -run the CS8900/20 Setup Utility to disable PnP and configure the -adapter before installation in the target Linux system. Failure to do -this will leave the adapter inactive and the driver will be unable to -communicate with the adapter. - -:: - - **************************************************************** - * CS8920-BASED ADAPTERS: * - * * - * CS8920-BASED ADAPTERS ARE PLUG and PLAY ENABLED BY DEFAULT. * - * THE CS89X0 DRIVER DOES NOT SUPPORT PnP. THEREFORE, YOU MUST * - * RUN THE CS8900/20 SETUP UTILITY TO DISABLE PnP SUPPORT AND * - * TO ACTIVATE THE ADAPTER. * - **************************************************************** - - - - -3. Loading the Driver as a Module -================================= - -If the driver is compiled as a loadable module, you can load the driver module -with the 'modprobe' command. Many of the adapter's configuration parameters can -be specified as command-line arguments to the load command. This facility -provides a means to override the EEPROM's settings or for interface -configuration when an EEPROM is not used. - -Example:: - - insmod cs89x0.o io=0x200 irq=0xA media=aui - -This example loads the module and configures the adapter to use an IO port base -address of 200h, interrupt 10, and use the AUI media connection. The following -configuration options are available on the command line:: - - io=### - specify IO address (200h-360h) - irq=## - specify interrupt level - use_dma=1 - Enable DMA - dma=# - specify dma channel (Driver is compiled to support - Rx DMA only) - dmasize=# (16 or 64) - DMA size 16K or 64K. Default value is set to 16. - media=rj45 - specify media type - or media=bnc - or media=aui - or media=auto - duplex=full - specify forced half/full/autonegotiate duplex - or duplex=half - or duplex=auto - debug=# - debug level (only available if the driver was compiled - for debugging) - -**Notes:** - -a) If an EEPROM is present, any specified command-line parameter - will override the corresponding configuration value stored in - EEPROM. - -b) The "io" parameter must be specified on the command-line. - -c) The driver's hardware probe routine is designed to avoid - writing to I/O space until it knows that there is a cs89x0 - card at the written addresses. This could cause problems - with device probing. To avoid this behaviour, add one - to the ``io=`` module parameter. This doesn't actually change - the I/O address, but it is a flag to tell the driver - to partially initialise the hardware before trying to - identify the card. This could be dangerous if you are - not sure that there is a cs89x0 card at the provided address. - - For example, to scan for an adapter located at IO base 0x300, - specify an IO address of 0x301. - -d) The "duplex=auto" parameter is only supported for the CS8920. - -e) The minimum command-line configuration required if an EEPROM is - not present is: - - io - irq - media type (no autodetect) - -f) The following additional parameters are CS89XX defaults (values - used with no EEPROM or command-line argument). - - * DMA Burst = enabled - * IOCHRDY Enabled = enabled - * UseSA = enabled - * CS8900 defaults to half-duplex if not specified on command-line - * CS8920 defaults to autoneg if not specified on command-line - * Use reset defaults for other config parameters - * dma_mode = 0 - -g) You can use ifconfig to set the adapter's Ethernet address. - -h) Many Linux distributions use the 'modprobe' command to load - modules. This program uses the '/etc/conf.modules' file to - determine configuration information which is passed to a driver - module when it is loaded. All the configuration options which are - described above may be placed within /etc/conf.modules. - - For example:: - - > cat /etc/conf.modules - ... - alias eth0 cs89x0 - options cs89x0 io=0x0200 dma=5 use_dma=1 - ... - - In this example we are telling the module system that the - ethernet driver for this machine should use the cs89x0 driver. We - are asking 'modprobe' to pass the 'io', 'dma' and 'use_dma' - arguments to the driver when it is loaded. - -i) Cirrus recommend that the cs89x0 use the ISA DMA channels 5, 6 or - 7. You will probably find that other DMA channels will not work. - -j) The cs89x0 supports DMA for receiving only. DMA mode is - significantly more efficient. Flooding a 400 MHz Celeron machine - with large ping packets consumes 82% of its CPU capacity in non-DMA - mode. With DMA this is reduced to 45%. - -k) If your Linux kernel was compiled with inbuilt plug-and-play - support you will be able to find information about the cs89x0 card - with the command:: - - cat /proc/isapnp - -l) If during DMA operation you find erratic behavior or network data - corruption you should use your PC's BIOS to slow the EISA bus clock. - -m) If the cs89x0 driver is compiled directly into the kernel - (non-modular) then its I/O address is automatically determined by - ISA bus probing. The IRQ number, media options, etc are determined - from the card's EEPROM. - -n) If the cs89x0 driver is compiled directly into the kernel, DMA - mode may be selected by providing the kernel with a boot option - 'cs89x0_dma=N' where 'N' is the desired DMA channel number (5, 6 or 7). - - Kernel boot options may be provided on the LILO command line:: - - LILO boot: linux cs89x0_dma=5 - - or they may be placed in /etc/lilo.conf:: - - image=/boot/bzImage-2.3.48 - append="cs89x0_dma=5" - label=linux - root=/dev/hda5 - read-only - - The DMA Rx buffer size is hardwired to 16 kbytes in this mode. - (64k mode is not available). - - -4. Compiling the Driver -======================= - -The cs89x0 driver can be compiled directly into the kernel or compiled into -a loadable device driver module. - -Just use the standard way to configure the driver and compile the Kernel. - - -4.1. Compiling the Driver to Support Rx DMA -------------------------------------------- - -The compile-time optionality for DMA was removed in the 2.3 kernel -series. DMA support is now unconditionally part of the driver. It is -enabled by the 'use_dma=1' module option. - - -5. Testing and Troubleshooting -============================== - -5.1. Known Defects and Limitations ----------------------------------- - -Refer to the RELEASE.TXT file distributed as part of this archive for a list of -known defects, driver limitations, and work arounds. - - -5.2. Testing the Adapter ------------------------- - -Once the adapter has been installed and configured, the diagnostic option of -the CS8900/20 Setup Utility can be used to test the functionality of the -adapter and its network connection. Use the diagnostics 'Self Test' option to -test the functionality of the adapter with the hardware configuration you have -assigned. You can use the diagnostics 'Network Test' to test the ability of the -adapter to communicate across the Ethernet with another PC equipped with a -CS8900/20-based adapter card (it must also be running the CS8900/20 Setup -Utility). - -.. note:: - - The Setup Utility's diagnostics are designed to run in a - DOS-only operating system environment. DO NOT run the diagnostics - from a DOS or command prompt session under Windows 95, Windows NT, - OS/2, or other operating system. - -To run the diagnostics tests on the CS8900/20 adapter: - - 1. Boot DOS on the PC and start the CS8900/20 Setup Utility. - - 2. The adapter's current configuration is displayed. Hit the ENTER key to - get to the main menu. - - 4. Select 'Diagnostics' (ALT-G) from the main menu. - * Select 'Self-Test' to test the adapter's basic functionality. - * Select 'Network Test' to test the network connection and cabling. - - -5.2.1. Diagnostic Self-test -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The diagnostic self-test checks the adapter's basic functionality as well as -its ability to communicate across the ISA bus based on the system resources -assigned during hardware configuration. The following tests are performed: - - * IO Register Read/Write Test - - The IO Register Read/Write test insures that the CS8900/20 can be - accessed in IO mode, and that the IO base address is correct. - - * Shared Memory Test - - The Shared Memory test insures the CS8900/20 can be accessed in memory - mode and that the range of memory addresses assigned does not conflict - with other devices in the system. - - * Interrupt Test - - The Interrupt test insures there are no conflicts with the assigned IRQ - signal. - - * EEPROM Test - - The EEPROM test insures the EEPROM can be read. - - * Chip RAM Test - - The Chip RAM test insures the 4K of memory internal to the CS8900/20 is - working properly. - - * Internal Loop-back Test - - The Internal Loop Back test insures the adapter's transmitter and - receiver are operating properly. If this test fails, make sure the - adapter's cable is connected to the network (check for LED activity for - example). - - * Boot PROM Test - - The Boot PROM test insures the Boot PROM is present, and can be read. - Failure indicates the Boot PROM was not successfully read due to a - hardware problem or due to a conflicts on the Boot PROM address - assignment. (Test only applies if the adapter is configured to use the - Boot PROM option.) - -Failure of a test item indicates a possible system resource conflict with -another device on the ISA bus. In this case, you should use the Manual Setup -option to reconfigure the adapter by selecting a different value for the system -resource that failed. - - -5.2.2. Diagnostic Network Test -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The Diagnostic Network Test verifies a working network connection by -transferring data between two CS8900/20 adapters installed in different PCs -on the same network. (Note: the diagnostic network test should not be run -between two nodes across a router.) - -This test requires that each of the two PCs have a CS8900/20-based adapter -installed and have the CS8900/20 Setup Utility running. The first PC is -configured as a Responder and the other PC is configured as an Initiator. -Once the Initiator is started, it sends data frames to the Responder which -returns the frames to the Initiator. - -The total number of frames received and transmitted are displayed on the -Initiator's display, along with a count of the number of frames received and -transmitted OK or in error. The test can be terminated anytime by the user at -either PC. - -To setup the Diagnostic Network Test: - - 1. Select a PC with a CS8900/20-based adapter and a known working network - connection to act as the Responder. Run the CS8900/20 Setup Utility - and select 'Diagnostics -> Network Test -> Responder' from the main - menu. Hit ENTER to start the Responder. - - 2. Return to the PC with the CS8900/20-based adapter you want to test and - start the CS8900/20 Setup Utility. - - 3. From the main menu, Select 'Diagnostic -> Network Test -> Initiator'. - Hit ENTER to start the test. - -You may stop the test on the Initiator at any time while allowing the Responder -to continue running. In this manner, you can move to additional PCs and test -them by starting the Initiator on another PC without having to stop/start the -Responder. - - - -5.3. Using the Adapter's LEDs ------------------------------ - -The 2 and 3-media adapters have two LEDs visible on the back end of the board -located near the 10Base-T connector. - -Link Integrity LED: A "steady" ON of the green LED indicates a valid 10Base-T -connection. (Only applies to 10Base-T. The green LED has no significance for -a 10Base-2 or AUI connection.) - -TX/RX LED: The yellow LED lights briefly each time the adapter transmits or -receives data. (The yellow LED will appear to "flicker" on a typical network.) - - -5.4. Resolving I/O Conflicts ----------------------------- - -An IO conflict occurs when two or more adapter use the same ISA resource (IO -address, memory address or IRQ). You can usually detect an IO conflict in one -of four ways after installing and or configuring the CS8900/20-based adapter: - - 1. The system does not boot properly (or at all). - - 2. The driver cannot communicate with the adapter, reporting an "Adapter - not found" error message. - - 3. You cannot connect to the network or the driver will not load. - - 4. If you have configured the adapter to run in memory mode but the driver - reports it is using IO mode when loading, this is an indication of a - memory address conflict. - -If an IO conflict occurs, run the CS8900/20 Setup Utility and perform a -diagnostic self-test. Normally, the ISA resource in conflict will fail the -self-test. If so, reconfigure the adapter selecting another choice for the -resource in conflict. Run the diagnostics again to check for further IO -conflicts. - -In some cases, such as when the PC will not boot, it may be necessary to remove -the adapter and reconfigure it by installing it in another PC to run the -CS8900/20 Setup Utility. Once reinstalled in the target system, run the -diagnostics self-test to ensure the new configuration is free of conflicts -before loading the driver again. - -When manually configuring the adapter, keep in mind the typical ISA system -resource usage as indicated in the tables below. - -:: - - I/O Address Device IRQ Device - ----------- -------- --- -------- - 200-20F Game I/O adapter 3 COM2, Bus Mouse - 230-23F Bus Mouse 4 COM1 - 270-27F LPT3: third parallel port 5 LPT2 - 2F0-2FF COM2: second serial port 6 Floppy Disk controller - 320-32F Fixed disk controller 7 LPT1 - 8 Real-time Clock - 9 EGA/VGA display adapter - 12 Mouse (PS/2) - Memory Address Device 13 Math Coprocessor - -------------- --------------------- 14 Hard Disk controller - A000-BFFF EGA Graphics Adapter - A000-C7FF VGA Graphics Adapter - B000-BFFF Mono Graphics Adapter - B800-BFFF Color Graphics Adapter - E000-FFFF AT BIOS - - - - -6. Technical Support -==================== - -6.1. Contacting Cirrus Logic's Technical Support ------------------------------------------------- - -Cirrus Logic's CS89XX Technical Application Support can be reached at:: - - Telephone :(800) 888-5016 (from inside U.S. and Canada) - :(512) 442-7555 (from outside the U.S. and Canada) - Fax :(512) 912-3871 - Email :ethernet@crystal.cirrus.com - WWW :http://www.cirrus.com - - -6.2. Information Required before Contacting Technical Support -------------------------------------------------------------- - -Before contacting Cirrus Logic for technical support, be prepared to provide as -Much of the following information as possible. - -1.) Adapter type (CRD8900, CDB8900, CDB8920, etc.) - -2.) Adapter configuration - - * IO Base, Memory Base, IO or memory mode enabled, IRQ, DMA channel - * Plug and Play enabled/disabled (CS8920-based adapters only) - * Configured for media auto-detect or specific media type (which type). - -3.) PC System's Configuration - - * Plug and Play system (yes/no) - * BIOS (make and version) - * System make and model - * CPU (type and speed) - * System RAM - * SCSI Adapter - -4.) Software - - * CS89XX driver and version - * Your network operating system and version - * Your system's OS version - * Version of all protocol support files - -5.) Any Error Message displayed. - - - -6.3 Obtaining the Latest Driver Version ---------------------------------------- - -You can obtain the latest CS89XX drivers and support software from Cirrus Logic's -Web site. You can also contact Cirrus Logic's Technical Support (email: -ethernet@crystal.cirrus.com) and request that you be registered for automatic -software-update notification. - -Cirrus Logic maintains a web page at http://www.cirrus.com with the -latest drivers and technical publications. - - -6.4. Current maintainer ------------------------ - -In February 2000 the maintenance of this driver was assumed by Andrew -Morton. - -6.5 Kernel module parameters ----------------------------- - -For use in embedded environments with no cs89x0 EEPROM, the kernel boot -parameter ``cs89x0_media=`` has been implemented. Usage is:: - - cs89x0_media=rj45 or - cs89x0_media=aui or - cs89x0_media=bnc diff --git a/Documentation/networking/device_drivers/ethernet/index.rst b/Documentation/networking/device_drivers/ethernet/index.rst index 64621c21fd78..fd3be5d20397 100644 --- a/Documentation/networking/device_drivers/ethernet/index.rst +++ b/Documentation/networking/device_drivers/ethernet/index.rst @@ -18,7 +18,6 @@ Contents: amd/pds_vfio_pci aquantia/atlantic chelsio/cxgb - cirrus/cs89x0 dlink/dl2k davicom/dm9000 dec/dmfe diff --git a/drivers/net/Space.c b/drivers/net/Space.c index 305f0a712a64..c3ce960110b9 100644 --- a/drivers/net/Space.c +++ b/drivers/net/Space.c @@ -202,9 +202,6 @@ static int __init probe_list2(int unit, struct devprobe2 *p, int autoprobe) static struct devprobe2 isa_probes[] __initdata = { #if defined(CONFIG_NE2000) /* ISA (use ne2k-pci for PCI cards) */ {ne_probe, 0}, -#endif -#ifdef CONFIG_CS89x0_ISA - {cs89x0_probe, 0}, #endif {NULL, 0}, }; diff --git a/drivers/net/ethernet/cirrus/Kconfig b/drivers/net/ethernet/cirrus/Kconfig index 5bdf731d9503..d76f9cd6bc2d 100644 --- a/drivers/net/ethernet/cirrus/Kconfig +++ b/drivers/net/ethernet/cirrus/Kconfig @@ -6,7 +6,7 @@ config NET_VENDOR_CIRRUS bool "Cirrus devices" default y - depends on ISA || EISA || ARM || MAC || COMPILE_TEST + depends on ARM || MAC || COMPILE_TEST help If you have a network (Ethernet) card belonging to this class, say Y. @@ -20,25 +20,9 @@ if NET_VENDOR_CIRRUS config CS89x0 tristate -config CS89x0_ISA - tristate "CS89x0 ISA driver support" - depends on HAS_IOPORT_MAP - depends on ISA - depends on !PPC32 - depends on CS89x0_PLATFORM=n - select NETDEV_LEGACY_INIT - select CS89x0 - help - Support for CS89x0 chipset based Ethernet cards. If you have a - network (Ethernet) card of this type, say Y and read the file - . - - To compile this driver as a module, choose M here. The module - will be called cs89x0. - config CS89x0_PLATFORM tristate "CS89x0 platform driver support" - depends on ARM || (COMPILE_TEST && !PPC) + depends on ARM || COMPILE_TEST select CS89x0 help Say Y to compile the cs89x0 platform driver. This makes this driver diff --git a/drivers/net/ethernet/cirrus/cs89x0.c b/drivers/net/ethernet/cirrus/cs89x0.c index fa5857923db4..8ab4b899d0ca 100644 --- a/drivers/net/ethernet/cirrus/cs89x0.c +++ b/drivers/net/ethernet/cirrus/cs89x0.c @@ -22,19 +22,6 @@ * Domenico Andreoli : cavokz@gmail.com */ - -/* - * Set this to zero to disable DMA code - * - * Note that even if DMA is turned off we still support the 'dma' and 'use_dma' - * module options so we don't break any startup scripts. - */ -#ifndef CONFIG_ISA_DMA_API -#define ALLOW_DMA 0 -#else -#define ALLOW_DMA 1 -#endif - /* * Set this to zero to remove all the debug statements via * dead code elimination @@ -71,13 +58,8 @@ #include #include -#include - #include #include -#if ALLOW_DMA -#include -#endif #include "cs89x0.h" @@ -91,30 +73,6 @@ static char version[] __initdata = "v2.4.3-pre1 Russell Nelson , Andrew Morton"; #define DRV_NAME "cs89x0" - -/* First, a few definitions that the brave might change. - * A zero-terminated list of I/O addresses to be probed. Some special flags.. - * Addr & 1 = Read back the address port, look for signature and reset - * the page window before probing - * Addr & 3 = Reset the page window and probe - * The CLPS eval board has the Cirrus chip at 0x80090300, in ARM IO space, - * but it is possible that a Cirrus board could be plugged into the ISA - * slots. - */ -/* The cs8900 has 4 IRQ pins, software selectable. cs8900_irq_map maps - * them to system IRQ numbers. This mapping is card specific and is set to - * the configuration of the Cirrus Eval board for this chip. - */ -#if IS_ENABLED(CONFIG_CS89x0_ISA) -static unsigned int netcard_portlist[] __used __initdata = { - 0x300, 0x320, 0x340, 0x360, 0x200, 0x220, 0x240, - 0x260, 0x280, 0x2a0, 0x2c0, 0x2e0, 0 -}; -static unsigned int cs8900_irq_map[] = { - 10, 11, 12, 5 -}; -#endif - #if DEBUGGING static unsigned int net_debug = DEBUGGING; #else @@ -149,14 +107,6 @@ struct net_local { int force; /* force various values; see FORCE* above. */ spinlock_t lock; void __iomem *virt_addr;/* CS89x0 virtual address. */ -#if ALLOW_DMA - int use_dma; /* Flag: we're using dma */ - int dma; /* DMA channel */ - int dmasize; /* 16 or 64 */ - unsigned char *dma_buff; /* points to the beginning of the buffer */ - unsigned char *end_dma_buff; /* points to the end of the buffer */ - unsigned char *rx_dma_ptr; /* points to the next packet */ -#endif }; /* Example routines you must write ;->. */ @@ -166,18 +116,6 @@ struct net_local { * Permit 'cs89x0_dma=N' in the kernel boot environment */ #if !defined(MODULE) -#if ALLOW_DMA -static int g_cs89x0_dma; - -static int __init dma_fn(char *str) -{ - g_cs89x0_dma = simple_strtol(str, NULL, 0); - return 1; -} - -__setup("cs89x0_dma=", dma_fn); -#endif /* ALLOW_DMA */ - static int g_cs89x0_media__force; static int __init media_fn(char *str) @@ -290,22 +228,9 @@ get_eeprom_cksum(int off, int len, int *buffer) static void write_irq(struct net_device *dev, int chip_type, int irq) { - int i; - if (chip_type == CS8900) { -#if IS_ENABLED(CONFIG_CS89x0_ISA) - /* Search the mapping table for the corresponding IRQ pin. */ - for (i = 0; i != ARRAY_SIZE(cs8900_irq_map); i++) - if (cs8900_irq_map[i] == irq) - break; - /* Not found */ - if (i == ARRAY_SIZE(cs8900_irq_map)) - i = 3; -#else /* INTRQ0 pin is used for interrupt generation. */ - i = 0; -#endif - writereg(dev, PP_CS8900_ISAINT, i); + writereg(dev, PP_CS8900_ISAINT, 0); } else { writereg(dev, PP_CS8920_ISAINT, irq); } @@ -326,163 +251,6 @@ count_rx_errors(int status, struct net_device *dev) dev->stats.rx_frame_errors++; } -/********************************* - * This page contains DMA routines - *********************************/ - -#if ALLOW_DMA - -#define dma_page_eq(ptr1, ptr2) ((long)(ptr1) >> 17 == (long)(ptr2) >> 17) - -static void -get_dma_channel(struct net_device *dev) -{ - struct net_local *lp = netdev_priv(dev); - - if (lp->dma) { - dev->dma = lp->dma; - lp->isa_config |= ISA_RxDMA; - } else { - if ((lp->isa_config & ANY_ISA_DMA) == 0) - return; - dev->dma = lp->isa_config & DMA_NO_MASK; - if (lp->chip_type == CS8900) - dev->dma += 5; - if (dev->dma < 5 || dev->dma > 7) { - lp->isa_config &= ~ANY_ISA_DMA; - return; - } - } -} - -static void -write_dma(struct net_device *dev, int chip_type, int dma) -{ - struct net_local *lp = netdev_priv(dev); - if ((lp->isa_config & ANY_ISA_DMA) == 0) - return; - if (chip_type == CS8900) - writereg(dev, PP_CS8900_ISADMA, dma - 5); - else - writereg(dev, PP_CS8920_ISADMA, dma); -} - -static void -set_dma_cfg(struct net_device *dev) -{ - struct net_local *lp = netdev_priv(dev); - - if (lp->use_dma) { - if ((lp->isa_config & ANY_ISA_DMA) == 0) { - cs89_dbg(3, err, "set_dma_cfg(): no DMA\n"); - return; - } - if (lp->isa_config & ISA_RxDMA) { - lp->curr_rx_cfg |= RX_DMA_ONLY; - cs89_dbg(3, info, "set_dma_cfg(): RX_DMA_ONLY\n"); - } else { - lp->curr_rx_cfg |= AUTO_RX_DMA; /* not that we support it... */ - cs89_dbg(3, info, "set_dma_cfg(): AUTO_RX_DMA\n"); - } - } -} - -static int -dma_bufcfg(struct net_device *dev) -{ - struct net_local *lp = netdev_priv(dev); - if (lp->use_dma) - return (lp->isa_config & ANY_ISA_DMA) ? RX_DMA_ENBL : 0; - else - return 0; -} - -static int -dma_busctl(struct net_device *dev) -{ - int retval = 0; - struct net_local *lp = netdev_priv(dev); - if (lp->use_dma) { - if (lp->isa_config & ANY_ISA_DMA) - retval |= RESET_RX_DMA; /* Reset the DMA pointer */ - if (lp->isa_config & DMA_BURST) - retval |= DMA_BURST_MODE; /* Does ISA config specify DMA burst ? */ - if (lp->dmasize == 64) - retval |= RX_DMA_SIZE_64K; /* did they ask for 64K? */ - retval |= MEMORY_ON; /* we need memory enabled to use DMA. */ - } - return retval; -} - -static void -dma_rx(struct net_device *dev) -{ - struct net_local *lp = netdev_priv(dev); - struct sk_buff *skb; - int status, length; - unsigned char *bp = lp->rx_dma_ptr; - - status = bp[0] + (bp[1] << 8); - length = bp[2] + (bp[3] << 8); - bp += 4; - - cs89_dbg(5, debug, "%s: receiving DMA packet at %lx, status %x, length %x\n", - dev->name, (unsigned long)bp, status, length); - - if ((status & RX_OK) == 0) { - count_rx_errors(status, dev); - goto skip_this_frame; - } - - /* Malloc up new buffer. */ - skb = netdev_alloc_skb(dev, length + 2); - if (skb == NULL) { - dev->stats.rx_dropped++; - - /* AKPM: advance bp to the next frame */ -skip_this_frame: - bp += (length + 3) & ~3; - if (bp >= lp->end_dma_buff) - bp -= lp->dmasize * 1024; - lp->rx_dma_ptr = bp; - return; - } - skb_reserve(skb, 2); /* longword align L3 header */ - - if (bp + length > lp->end_dma_buff) { - int semi_cnt = lp->end_dma_buff - bp; - skb_put_data(skb, bp, semi_cnt); - skb_put_data(skb, lp->dma_buff, length - semi_cnt); - } else { - skb_put_data(skb, bp, length); - } - bp += (length + 3) & ~3; - if (bp >= lp->end_dma_buff) - bp -= lp->dmasize*1024; - lp->rx_dma_ptr = bp; - - cs89_dbg(3, info, "%s: received %d byte DMA packet of type %x\n", - dev->name, length, - ((skb->data[ETH_ALEN + ETH_ALEN] << 8) | - skb->data[ETH_ALEN + ETH_ALEN + 1])); - - skb->protocol = eth_type_trans(skb, dev); - netif_rx(skb); - dev->stats.rx_packets++; - dev->stats.rx_bytes += length; -} - -static void release_dma_buff(struct net_local *lp) -{ - if (lp->dma_buff) { - free_pages((unsigned long)(lp->dma_buff), - get_order(lp->dmasize * 1024)); - lp->dma_buff = NULL; - } -} - -#endif /* ALLOW_DMA */ - static void control_dc_dc(struct net_device *dev, int on_not_off) { @@ -781,27 +549,6 @@ static irqreturn_t net_interrupt(int irq, void *dev_id) */ netif_wake_queue(dev); /* Inform upper layers. */ } -#if ALLOW_DMA - if (lp->use_dma && (status & RX_DMA)) { - int count = readreg(dev, PP_DmaFrameCnt); - while (count) { - cs89_dbg(5, debug, - "%s: receiving %d DMA frames\n", - dev->name, count); - if (count > 1) - cs89_dbg(2, debug, - "%s: receiving %d DMA frames\n", - dev->name, count); - dma_rx(dev); - if (--count == 0) - count = readreg(dev, PP_DmaFrameCnt); - if (count > 0) - cs89_dbg(2, debug, - "%s: continuing with %d DMA frames\n", - dev->name, count); - } - } -#endif break; case ISQ_RX_MISS_EVENT: dev->stats.rx_missed_errors += (status >> 6); @@ -860,14 +607,6 @@ net_open(struct net_device *dev) goto bad_out; } } else { -#if IS_ENABLED(CONFIG_CS89x0_ISA) - if (((1 << dev->irq) & lp->irq_map) == 0) { - pr_err("%s: IRQ %d is not in our map of allowable IRQs, which is %x\n", - dev->name, dev->irq, lp->irq_map); - ret = -EAGAIN; - goto bad_out; - } -#endif /* FIXME: Cirrus' release had this: */ writereg(dev, PP_BusCTL, readreg(dev, PP_BusCTL)|ENABLE_IRQ); /* And 2.3.47 had this: */ @@ -882,46 +621,6 @@ net_open(struct net_device *dev) } } -#if ALLOW_DMA - if (lp->use_dma && (lp->isa_config & ANY_ISA_DMA)) { - unsigned long flags; - lp->dma_buff = (unsigned char *)__get_dma_pages(GFP_KERNEL, - get_order(lp->dmasize * 1024)); - if (!lp->dma_buff) { - pr_err("%s: cannot get %dK memory for DMA\n", - dev->name, lp->dmasize); - goto release_irq; - } - cs89_dbg(1, debug, "%s: dma %lx %lx\n", - dev->name, - (unsigned long)lp->dma_buff, - (unsigned long)isa_virt_to_bus(lp->dma_buff)); - if ((unsigned long)lp->dma_buff >= MAX_DMA_ADDRESS || - !dma_page_eq(lp->dma_buff, - lp->dma_buff + lp->dmasize * 1024 - 1)) { - pr_err("%s: not usable as DMA buffer\n", dev->name); - goto release_irq; - } - memset(lp->dma_buff, 0, lp->dmasize * 1024); /* Why? */ - if (request_dma(dev->dma, dev->name)) { - pr_err("%s: cannot get dma channel %d\n", - dev->name, dev->dma); - goto release_irq; - } - write_dma(dev, lp->chip_type, dev->dma); - lp->rx_dma_ptr = lp->dma_buff; - lp->end_dma_buff = lp->dma_buff + lp->dmasize * 1024; - spin_lock_irqsave(&lp->lock, flags); - disable_dma(dev->dma); - clear_dma_ff(dev->dma); - set_dma_mode(dev->dma, DMA_RX_MODE); /* auto_init as well */ - set_dma_addr(dev->dma, isa_virt_to_bus(lp->dma_buff)); - set_dma_count(dev->dma, lp->dmasize * 1024); - enable_dma(dev->dma); - spin_unlock_irqrestore(&lp->lock, flags); - } -#endif /* ALLOW_DMA */ - /* set the Ethernet address */ for (i = 0; i < ETH_ALEN / 2; i++) writereg(dev, PP_IA + i * 2, @@ -958,11 +657,6 @@ net_open(struct net_device *dev) pr_err("%s: EEPROM is configured for unavailable media\n", dev->name); release_dma: -#if ALLOW_DMA - free_dma(dev->dma); -release_irq: - release_dma_buff(lp); -#endif writereg(dev, PP_LineCTL, readreg(dev, PP_LineCTL) & ~(SERIAL_TX_ON | SERIAL_RX_ON)); free_irq(dev->irq, dev); @@ -1048,9 +742,6 @@ release_irq: if (lp->isa_config & STREAM_TRANSFER) lp->curr_rx_cfg |= RX_STREAM_ENBL; -#if ALLOW_DMA - set_dma_cfg(dev); -#endif writereg(dev, PP_RxCFG, lp->curr_rx_cfg); writereg(dev, PP_TxCFG, (TX_LOST_CRS_ENBL | @@ -1063,19 +754,12 @@ release_irq: writereg(dev, PP_BufCFG, (READY_FOR_TX_ENBL | RX_MISS_COUNT_OVRFLOW_ENBL | -#if ALLOW_DMA - dma_bufcfg(dev) | -#endif TX_COL_COUNT_OVRFLOW_ENBL | TX_UNDERRUN_ENBL)); /* now that we've got our act together, enable everything */ - writereg(dev, PP_BusCTL, (ENABLE_IRQ - | (dev->mem_start ? MEMORY_ON : 0) /* turn memory on */ -#if ALLOW_DMA - | dma_busctl(dev) -#endif - )); + writereg(dev, PP_BusCTL, + (ENABLE_IRQ | (dev->mem_start ? MEMORY_ON : 0))); /* turn memory on */ netif_start_queue(dev); cs89_dbg(1, debug, "net_open() succeeded\n"); return 0; @@ -1087,10 +771,6 @@ bad_out: static int net_close(struct net_device *dev) { -#if ALLOW_DMA - struct net_local *lp = netdev_priv(dev); -#endif - netif_stop_queue(dev); writereg(dev, PP_RxCFG, 0); @@ -1100,13 +780,6 @@ net_close(struct net_device *dev) free_irq(dev->irq, dev); -#if ALLOW_DMA - if (lp->use_dma && lp->dma) { - free_dma(dev->dma); - release_dma_buff(lp); - } -#endif - /* Update the statistics here. */ return 0; } @@ -1323,13 +996,6 @@ cs89x0_probe1(struct net_device *dev, void __iomem *ioaddr, int modular) memset(lp, 0, sizeof(*lp)); spin_lock_init(&lp->lock); #ifndef MODULE -#if ALLOW_DMA - if (g_cs89x0_dma) { - lp->use_dma = 1; - lp->dma = g_cs89x0_dma; - lp->dmasize = 16; /* Could make this an option... */ - } -#endif lp->force = g_cs89x0_media__force; #endif } @@ -1527,40 +1193,12 @@ cs89x0_probe1(struct net_device *dev, void __iomem *ioaddr, int modular) dev->irq = i; } else { i = lp->isa_config & INT_NO_MASK; -#if IS_ENABLED(CONFIG_CS89x0_ISA) - if (lp->chip_type == CS8900) { - /* Translate the IRQ using the IRQ mapping table. */ - if (i >= ARRAY_SIZE(cs8900_irq_map)) - pr_err("invalid ISA interrupt number %d\n", i); - else - i = cs8900_irq_map[i]; - - lp->irq_map = CS8900_IRQ_MAP; /* fixed IRQ map for CS8900 */ - } else { - int irq_map_buff[IRQ_MAP_LEN/2]; - - if (get_eeprom_data(dev, IRQ_MAP_EEPROM_DATA, - IRQ_MAP_LEN / 2, - irq_map_buff) >= 0) { - if ((irq_map_buff[0] & 0xff) == PNP_IRQ_FRMT) - lp->irq_map = ((irq_map_buff[0] >> 8) | - (irq_map_buff[1] << 8)); - } - } -#endif if (!dev->irq) dev->irq = i; } pr_cont(" IRQ %d", dev->irq); - -#if ALLOW_DMA - if (lp->use_dma) { - get_dma_channel(dev); - pr_cont(", DMA %d", dev->dma); - } else -#endif - pr_cont(", programmed I/O"); + pr_cont(", programmed I/O"); /* print the ethernet address. */ pr_cont(", MAC %pM\n", dev->dev_addr); @@ -1580,270 +1218,6 @@ out1: return retval; } -#if IS_ENABLED(CONFIG_CS89x0_ISA) -/* - * This function converts the I/O port address used by the cs89x0_probe() and - * init_module() functions to the I/O memory address used by the - * cs89x0_probe1() function. - */ -static int __init -cs89x0_ioport_probe(struct net_device *dev, unsigned long ioport, int modular) -{ - struct net_local *lp = netdev_priv(dev); - int ret; - void __iomem *io_mem; - - if (!lp) - return -ENOMEM; - - dev->base_addr = ioport; - - if (!request_region(ioport, NETCARD_IO_EXTENT, DRV_NAME)) { - ret = -EBUSY; - goto out; - } - - io_mem = ioport_map(ioport & ~3, NETCARD_IO_EXTENT); - if (!io_mem) { - ret = -ENOMEM; - goto release; - } - - /* if they give us an odd I/O address, then do ONE write to - * the address port, to get it back to address zero, where we - * expect to find the EISA signature word. An IO with a base of 0x3 - * will skip the test for the ADD_PORT. - */ - if (ioport & 1) { - cs89_dbg(1, info, "%s: odd ioaddr 0x%lx\n", dev->name, ioport); - if ((ioport & 2) != 2) { - if ((ioread16(io_mem + ADD_PORT) & ADD_MASK) != - ADD_SIG) { - pr_err("%s: bad signature 0x%x\n", - dev->name, ioread16(io_mem + ADD_PORT)); - ret = -ENODEV; - goto unmap; - } - } - } - - ret = cs89x0_probe1(dev, io_mem, modular); - if (!ret) - goto out; -unmap: - ioport_unmap(io_mem); -release: - release_region(ioport, NETCARD_IO_EXTENT); -out: - return ret; -} - -#ifndef MODULE -/* Check for a network adaptor of this type, and return '0' iff one exists. - * If dev->base_addr == 0, probe all likely locations. - * If dev->base_addr == 1, always return failure. - * If dev->base_addr == 2, allocate space for the device and return success - * (detachable devices only). - * Return 0 on success. - */ - -struct net_device * __init cs89x0_probe(int unit) -{ - struct net_device *dev = alloc_etherdev(sizeof(struct net_local)); - unsigned *port; - int err = 0; - int irq; - int io; - - if (!dev) - return ERR_PTR(-ENODEV); - - sprintf(dev->name, "eth%d", unit); - netdev_boot_setup_check(dev); - io = dev->base_addr; - irq = dev->irq; - - cs89_dbg(0, info, "cs89x0_probe(0x%x)\n", io); - - if (io > 0x1ff) { /* Check a single specified location. */ - err = cs89x0_ioport_probe(dev, io, 0); - } else if (io != 0) { /* Don't probe at all. */ - err = -ENXIO; - } else { - for (port = netcard_portlist; *port; port++) { - if (cs89x0_ioport_probe(dev, *port, 0) == 0) - break; - dev->irq = irq; - } - if (!*port) - err = -ENODEV; - } - if (err) - goto out; - return dev; -out: - free_netdev(dev); - pr_warn("no cs8900 or cs8920 detected. Be sure to disable PnP with SETUP\n"); - return ERR_PTR(err); -} -#else -static struct net_device *dev_cs89x0; - -/* Support the 'debug' module parm even if we're compiled for non-debug to - * avoid breaking someone's startup scripts - */ - -static int io; -static int irq; -static int debug; -static char media[8]; -static int duplex = -1; - -static int use_dma; /* These generate unused var warnings if ALLOW_DMA = 0 */ -static int dma; -static int dmasize = 16; /* or 64 */ - -module_param_hw(io, int, ioport, 0); -module_param_hw(irq, int, irq, 0); -module_param(debug, int, 0); -module_param_string(media, media, sizeof(media), 0); -module_param(duplex, int, 0); -module_param_hw(dma , int, dma, 0); -module_param(dmasize , int, 0); -module_param(use_dma , int, 0); -MODULE_PARM_DESC(io, "cs89x0 I/O base address"); -MODULE_PARM_DESC(irq, "cs89x0 IRQ number"); -#if DEBUGGING -MODULE_PARM_DESC(debug, "cs89x0 debug level (0-6)"); -#else -MODULE_PARM_DESC(debug, "(ignored)"); -#endif -MODULE_PARM_DESC(media, "Set cs89x0 adapter(s) media type(s) (rj45,bnc,aui)"); -/* No other value than -1 for duplex seems to be currently interpreted */ -MODULE_PARM_DESC(duplex, "(ignored)"); -#if ALLOW_DMA -MODULE_PARM_DESC(dma , "cs89x0 ISA DMA channel; ignored if use_dma=0"); -MODULE_PARM_DESC(dmasize , "cs89x0 DMA size in kB (16,64); ignored if use_dma=0"); -MODULE_PARM_DESC(use_dma , "cs89x0 using DMA (0-1)"); -#else -MODULE_PARM_DESC(dma , "(ignored)"); -MODULE_PARM_DESC(dmasize , "(ignored)"); -MODULE_PARM_DESC(use_dma , "(ignored)"); -#endif - -MODULE_AUTHOR("Mike Cruse, Russwll Nelson , Andrew Morton"); -MODULE_LICENSE("GPL"); - -/* - * media=t - specify media type - * or media=2 - * or media=aui - * or medai=auto - * duplex=0 - specify forced half/full/autonegotiate duplex - * debug=# - debug level - * - * Default Chip Configuration: - * DMA Burst = enabled - * IOCHRDY Enabled = enabled - * UseSA = enabled - * CS8900 defaults to half-duplex if not specified on command-line - * CS8920 defaults to autoneg if not specified on command-line - * Use reset defaults for other config parameters - * - * Assumptions: - * media type specified is supported (circuitry is present) - * if memory address is > 1MB, then required mem decode hw is present - * if 10B-2, then agent other than driver will enable DC/DC converter - * (hw or software util) - */ - -static int __init cs89x0_isa_init_module(void) -{ - struct net_device *dev; - struct net_local *lp; - int ret = 0; - -#if DEBUGGING - net_debug = debug; -#else - debug = 0; -#endif - dev = alloc_etherdev(sizeof(struct net_local)); - if (!dev) - return -ENOMEM; - - dev->irq = irq; - dev->base_addr = io; - lp = netdev_priv(dev); - -#if ALLOW_DMA - if (use_dma) { - lp->use_dma = use_dma; - lp->dma = dma; - lp->dmasize = dmasize; - } -#endif - - spin_lock_init(&lp->lock); - - /* boy, they'd better get these right */ - if (!strcmp(media, "rj45")) - lp->adapter_cnf = A_CNF_MEDIA_10B_T | A_CNF_10B_T; - else if (!strcmp(media, "aui")) - lp->adapter_cnf = A_CNF_MEDIA_AUI | A_CNF_AUI; - else if (!strcmp(media, "bnc")) - lp->adapter_cnf = A_CNF_MEDIA_10B_2 | A_CNF_10B_2; - else - lp->adapter_cnf = A_CNF_MEDIA_10B_T | A_CNF_10B_T; - - if (duplex == -1) - lp->auto_neg_cnf = AUTO_NEG_ENABLE; - - if (io == 0) { - pr_err("Module autoprobing not allowed\n"); - pr_err("Append io=0xNNN\n"); - ret = -EPERM; - goto out; - } else if (io <= 0x1ff) { - ret = -ENXIO; - goto out; - } - -#if ALLOW_DMA - if (use_dma && dmasize != 16 && dmasize != 64) { - pr_err("dma size must be either 16K or 64K, not %dK\n", - dmasize); - ret = -EPERM; - goto out; - } -#endif - ret = cs89x0_ioport_probe(dev, io, 1); - if (ret) - goto out; - - dev_cs89x0 = dev; - return 0; -out: - free_netdev(dev); - return ret; -} -module_init(cs89x0_isa_init_module); - -static void __exit cs89x0_isa_cleanup_module(void) -{ - struct net_local *lp = netdev_priv(dev_cs89x0); - - unregister_netdev(dev_cs89x0); - iowrite16(PP_ChipID, lp->virt_addr + ADD_PORT); - ioport_unmap(lp->virt_addr); - release_region(dev_cs89x0->base_addr, NETCARD_IO_EXTENT); - free_netdev(dev_cs89x0); -} -module_exit(cs89x0_isa_cleanup_module); -#endif /* MODULE */ -#endif /* CONFIG_CS89x0_ISA */ - -#if IS_ENABLED(CONFIG_CS89x0_PLATFORM) static int __init cs89x0_platform_probe(struct platform_device *pdev) { struct net_device *dev = alloc_etherdev(sizeof(struct net_local)); @@ -1908,8 +1282,6 @@ static struct platform_driver cs89x0_driver = { module_platform_driver_probe(cs89x0_driver, cs89x0_platform_probe); -#endif /* CONFIG_CS89x0_PLATFORM */ - MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Crystal Semiconductor (Now Cirrus Logic) CS89[02]0 network driver"); MODULE_AUTHOR("Russell Nelson "); diff --git a/include/net/Space.h b/include/net/Space.h index 6a0b6674d930..1b9ab0da5a03 100644 --- a/include/net/Space.h +++ b/include/net/Space.h @@ -4,4 +4,3 @@ */ struct net_device *ne_probe(int unit); -struct net_device *cs89x0_probe(int unit); -- cgit v1.2.3 From 4fe18ddd17d842f140312b55d3a0753601613cb8 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 29 Apr 2026 16:55:46 +0200 Subject: ne2k: fold drivers/net/Space.c into ne.c drivers/net/Space.c is the last remnant of the linux-2.4.x driver model that required each subsystem and device driver init function to be called from init/main.c explicitly, before the introduction of initcall levels. In linux-7.0, this was only used for a handful of ISA network drivers, with the ne2000 driver being the last one. Fold the code into ne.c directly, with minimal changes to preserve the existing command line parsing. Signed-off-by: Arnd Bergmann Acked-by: Geert Uytterhoeven # m68k Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260429145624.2948432-2-arnd@kernel.org Signed-off-by: Jakub Kicinski --- Documentation/admin-guide/kernel-parameters.txt | 6 +- Documentation/arch/m68k/kernel-options.rst | 24 +-- drivers/net/Kconfig | 7 - drivers/net/Makefile | 1 - drivers/net/Space.c | 234 ----------------------- drivers/net/ethernet/8390/Kconfig | 1 - drivers/net/ethernet/8390/ne.c | 237 +++++++++++++++++++++--- include/linux/netdevice.h | 1 - include/net/Space.h | 6 - 9 files changed, 220 insertions(+), 297 deletions(-) delete mode 100644 drivers/net/Space.c delete mode 100644 include/net/Space.h (limited to 'include') diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index 4d0f545fb3ec..7834ee927310 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -4266,12 +4266,8 @@ Kernel parameters n2= [NET] SDL Inc. RISCom/N2 synchronous serial card - netdev= [NET] Network devices parameters + netdev= [NET] NE2000 ISA network devices parameters Format: ,,,, - Note that mem_start is often overloaded to mean - something different and driver-specific. - This usage is only documented in each driver source - file if at all. netpoll.carrier_timeout= [NET] Specifies amount of time (in seconds) that diff --git a/Documentation/arch/m68k/kernel-options.rst b/Documentation/arch/m68k/kernel-options.rst index 2008a20b4329..c59e7aaed44e 100644 --- a/Documentation/arch/m68k/kernel-options.rst +++ b/Documentation/arch/m68k/kernel-options.rst @@ -244,23 +244,7 @@ drive (with "root="). 3) General Device Options (Amiga and Atari) =========================================== -3.1) ether= ------------ - -:Syntax: ether=[[,[,[,]]]], - - is the name of a net driver, as specified in -drivers/net/Space.c in the Linux source. Most prominent are eth0, ... -eth3, sl0, ... sl3, ppp0, ..., ppp3, dummy, and lo. - -The non-ethernet drivers (sl, ppp, dummy, lo) obviously ignore the -settings by this options. Also, the existing ethernet drivers for -Linux/m68k (ariadne, a2065, hydra) don't use them because Zorro boards -are really Plug-'n-Play, so the "ether=" option is useless altogether -for Linux/m68k. - - -3.2) hd= +3.1) hd= -------- :Syntax: hd=,, @@ -273,7 +257,7 @@ itself. It exists just for the case that this fails for one of your disks. -3.3) max_scsi_luns= +3.2) max_scsi_luns= ------------------- :Syntax: max_scsi_luns= @@ -284,7 +268,7 @@ be scanned. Valid values for are between 1 and 8. Default is 8 if configuration, else 1. -3.4) st= +3.3) st= -------- :Syntax: st=,[,[]] @@ -297,7 +281,7 @@ total number of buffers. limits the total number of buffers allocated for all tape devices. -3.5) dmasound= +3.4) dmasound= -------------- :Syntax: dmasound=[,[,]] diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 8ec98f6dfef9..ff79c466712d 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -630,11 +630,4 @@ config NET_FAILOVER a VM with direct attached VF by failing over to the paravirtual datapath when the VF is unplugged. -config NETDEV_LEGACY_INIT - bool - depends on ISA - help - Drivers that call netdev_boot_setup_check() should select this - symbol, everything else no longer needs it. - endif # NETDEVICES diff --git a/drivers/net/Makefile b/drivers/net/Makefile index b87a741fc952..88e4c485d6b2 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -21,7 +21,6 @@ obj-$(CONFIG_MACVTAP) += macvtap.o obj-$(CONFIG_MII) += mii.o obj-$(CONFIG_MDIO) += mdio.o obj-$(CONFIG_NET) += loopback.o -obj-$(CONFIG_NETDEV_LEGACY_INIT) += Space.o obj-$(CONFIG_NETCONSOLE) += netconsole.o obj-$(CONFIG_NETKIT) += netkit.o obj-y += phy/ diff --git a/drivers/net/Space.c b/drivers/net/Space.c deleted file mode 100644 index c3ce960110b9..000000000000 --- a/drivers/net/Space.c +++ /dev/null @@ -1,234 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * INET An implementation of the TCP/IP protocol suite for the LINUX - * operating system. INET is implemented using the BSD Socket - * interface as the means of communication with the user level. - * - * Holds initial configuration information for devices. - * - * Version: @(#)Space.c 1.0.7 08/12/93 - * - * Authors: Ross Biro - * Fred N. van Kempen, - * Donald J. Becker, - * - * Changelog: - * Stephen Hemminger (09/2003) - * - get rid of pre-linked dev list, dynamic device allocation - * Paul Gortmaker (03/2002) - * - struct init cleanup, enable multiple ISA autoprobes. - * Arnaldo Carvalho de Melo - 09/1999 - * - fix sbni: s/device/net_device/ - * Paul Gortmaker (06/98): - * - sort probes in a sane way, make sure all (safe) probes - * get run once & failed autoprobes don't autoprobe again. - */ -#include -#include -#include -#include -#include -#include - -/* - * This structure holds boot-time configured netdevice settings. They - * are then used in the device probing. - */ -struct netdev_boot_setup { - char name[IFNAMSIZ]; - struct ifmap map; -}; -#define NETDEV_BOOT_SETUP_MAX 8 - - -/****************************************************************************** - * - * Device Boot-time Settings Routines - * - ******************************************************************************/ - -/* Boot time configuration table */ -static struct netdev_boot_setup dev_boot_setup[NETDEV_BOOT_SETUP_MAX]; - -/** - * netdev_boot_setup_add - add new setup entry - * @name: name of the device - * @map: configured settings for the device - * - * Adds new setup entry to the dev_boot_setup list. The function - * returns 0 on error and 1 on success. This is a generic routine to - * all netdevices. - */ -static int netdev_boot_setup_add(char *name, struct ifmap *map) -{ - struct netdev_boot_setup *s; - int i; - - s = dev_boot_setup; - for (i = 0; i < NETDEV_BOOT_SETUP_MAX; i++) { - if (s[i].name[0] == '\0' || s[i].name[0] == ' ') { - strscpy_pad(s[i].name, name); - memcpy(&s[i].map, map, sizeof(s[i].map)); - break; - } - } - - return i >= NETDEV_BOOT_SETUP_MAX ? 0 : 1; -} - -/** - * netdev_boot_setup_check - check boot time settings - * @dev: the netdevice - * - * Check boot time settings for the device. - * The found settings are set for the device to be used - * later in the device probing. - * Returns 0 if no settings found, 1 if they are. - */ -int netdev_boot_setup_check(struct net_device *dev) -{ - struct netdev_boot_setup *s = dev_boot_setup; - int i; - - for (i = 0; i < NETDEV_BOOT_SETUP_MAX; i++) { - if (s[i].name[0] != '\0' && s[i].name[0] != ' ' && - !strcmp(dev->name, s[i].name)) { - dev->irq = s[i].map.irq; - dev->base_addr = s[i].map.base_addr; - dev->mem_start = s[i].map.mem_start; - dev->mem_end = s[i].map.mem_end; - return 1; - } - } - return 0; -} -EXPORT_SYMBOL(netdev_boot_setup_check); - -/** - * netdev_boot_base - get address from boot time settings - * @prefix: prefix for network device - * @unit: id for network device - * - * Check boot time settings for the base address of device. - * The found settings are set for the device to be used - * later in the device probing. - * Returns 0 if no settings found. - */ -static unsigned long netdev_boot_base(const char *prefix, int unit) -{ - const struct netdev_boot_setup *s = dev_boot_setup; - char name[IFNAMSIZ]; - int i; - - sprintf(name, "%s%d", prefix, unit); - - /* - * If device already registered then return base of 1 - * to indicate not to probe for this interface - */ - if (__dev_get_by_name(&init_net, name)) - return 1; - - for (i = 0; i < NETDEV_BOOT_SETUP_MAX; i++) - if (!strcmp(name, s[i].name)) - return s[i].map.base_addr; - return 0; -} - -/* - * Saves at boot time configured settings for any netdevice. - */ -static int __init netdev_boot_setup(char *str) -{ - int ints[5]; - struct ifmap map; - - str = get_options(str, ARRAY_SIZE(ints), ints); - if (!str || !*str) - return 0; - - /* Save settings */ - memset(&map, 0, sizeof(map)); - if (ints[0] > 0) - map.irq = ints[1]; - if (ints[0] > 1) - map.base_addr = ints[2]; - if (ints[0] > 2) - map.mem_start = ints[3]; - if (ints[0] > 3) - map.mem_end = ints[4]; - - /* Add new entry to the list */ - return netdev_boot_setup_add(str, &map); -} - -__setup("netdev=", netdev_boot_setup); - -static int __init ether_boot_setup(char *str) -{ - return netdev_boot_setup(str); -} -__setup("ether=", ether_boot_setup); - - -/* A unified ethernet device probe. This is the easiest way to have every - * ethernet adaptor have the name "eth[0123...]". - */ - -struct devprobe2 { - struct net_device *(*probe)(int unit); - int status; /* non-zero if autoprobe has failed */ -}; - -static int __init probe_list2(int unit, struct devprobe2 *p, int autoprobe) -{ - struct net_device *dev; - - for (; p->probe; p++) { - if (autoprobe && p->status) - continue; - dev = p->probe(unit); - if (!IS_ERR(dev)) - return 0; - if (autoprobe) - p->status = PTR_ERR(dev); - } - return -ENODEV; -} - -/* ISA probes that touch addresses < 0x400 (including those that also - * look for EISA/PCI cards in addition to ISA cards). - */ -static struct devprobe2 isa_probes[] __initdata = { -#if defined(CONFIG_NE2000) /* ISA (use ne2k-pci for PCI cards) */ - {ne_probe, 0}, -#endif - {NULL, 0}, -}; - -/* Unified ethernet device probe, segmented per architecture and - * per bus interface. This drives the legacy devices only for now. - */ - -static void __init ethif_probe2(int unit) -{ - unsigned long base_addr = netdev_boot_base("eth", unit); - - if (base_addr == 1) - return; - - probe_list2(unit, isa_probes, base_addr == 0); -} - -/* Statically configured drivers -- order matters here. */ -static int __init net_olddevs_init(void) -{ - int num; - - for (num = 0; num < 8; ++num) - ethif_probe2(num); - - return 0; -} - -device_initcall(net_olddevs_init); diff --git a/drivers/net/ethernet/8390/Kconfig b/drivers/net/ethernet/8390/Kconfig index 5d12a595ab19..57d73a7747ec 100644 --- a/drivers/net/ethernet/8390/Kconfig +++ b/drivers/net/ethernet/8390/Kconfig @@ -90,7 +90,6 @@ config MCF8390 config NE2000 tristate "NE2000/NE1000 support" depends on (ISA || (Q40 && m) || MACH_TX49XX || ATARI_ETHERNEC) - select NETDEV_LEGACY_INIT if ISA select CRC32 help If you have a network (Ethernet) card of this type, say Y here. diff --git a/drivers/net/ethernet/8390/ne.c b/drivers/net/ethernet/8390/ne.c index 961019c32842..1cbd5a4977ce 100644 --- a/drivers/net/ethernet/8390/ne.c +++ b/drivers/net/ethernet/8390/ne.c @@ -50,7 +50,6 @@ static const char version2[] = #include #include #include -#include #include @@ -182,7 +181,6 @@ static void ne_block_input(struct net_device *dev, int count, static void ne_block_output(struct net_device *dev, const int count, const unsigned char *buf, const int start_page); - /* Probe for various non-shared-memory ethercards. NEx000-clone boards have a Station Address PROM (SAPROM) in the packet @@ -918,31 +916,47 @@ static void __init ne_add_devices(void) } } -static int __init ne_init(void) +/* + * This structure holds boot-time configured netdevice settings. They + * are then used in the device probing. + */ +struct netdev_boot_setup { + char name[IFNAMSIZ]; + struct ifmap map; +}; +#define NETDEV_BOOT_SETUP_MAX 8 + +/* Boot time configuration table */ +static struct netdev_boot_setup dev_boot_setup[NETDEV_BOOT_SETUP_MAX]; + +/** + * netdev_boot_setup_check - check boot time settings + * @dev: the netdevice + * + * Check boot time settings for the device. + * The found settings are set for the device to be used + * later in the device probing. + * Returns 0 if no settings found, 1 if they are. + */ +static int netdev_boot_setup_check(struct net_device *dev) { - int retval; - - if (IS_MODULE(CONFIG_NE2000)) - ne_add_devices(); - - retval = platform_driver_probe(&ne_driver, ne_drv_probe); + struct netdev_boot_setup *s = dev_boot_setup; + int i; - if (IS_MODULE(CONFIG_NE2000) && retval) { - if (io[0] == 0) - pr_notice("ne.c: You must supply \"io=0xNNN\"" - " value(s) for ISA cards.\n"); - ne_loop_rm_unreg(1); - return retval; + for (i = 0; i < NETDEV_BOOT_SETUP_MAX; i++) { + if (s[i].name[0] != '\0' && s[i].name[0] != ' ' && + !strcmp(dev->name, s[i].name)) { + dev->irq = s[i].map.irq; + dev->base_addr = s[i].map.base_addr; + dev->mem_start = s[i].map.mem_start; + dev->mem_end = s[i].map.mem_end; + return 1; + } } - - /* Unregister unused platform_devices. */ - ne_loop_rm_unreg(0); - return retval; + return 0; } -module_init(ne_init); -#if !defined(MODULE) && defined(CONFIG_NETDEV_LEGACY_INIT) -struct net_device * __init ne_probe(int unit) +static struct net_device * __init ne_probe(int unit) { int this_dev; struct net_device *dev; @@ -982,8 +996,187 @@ struct net_device * __init ne_probe(int unit) return ERR_PTR(-ENODEV); } + +/****************************************************************************** + * + * Device Boot-time Settings Routines + * + ******************************************************************************/ + +/** + * netdev_boot_base - get address from boot time settings + * @prefix: prefix for network device + * @unit: id for network device + * + * Check boot time settings for the base address of device. + * The found settings are set for the device to be used + * later in the device probing. + * Returns 0 if no settings found. + */ +static unsigned long netdev_boot_base(const char *prefix, int unit) +{ + const struct netdev_boot_setup *s = dev_boot_setup; + char name[IFNAMSIZ]; + int i; + + sprintf(name, "%s%d", prefix, unit); + + /* + * If device already registered then return base of 1 + * to indicate not to probe for this interface + */ + if (__dev_get_by_name(&init_net, name)) + return 1; + + for (i = 0; i < NETDEV_BOOT_SETUP_MAX; i++) + if (!strcmp(name, s[i].name)) + return s[i].map.base_addr; + return 0; +} + +#if !defined(MODULE) && defined(CONFIG_ISA) +/** + * netdev_boot_setup_add - add new setup entry + * @name: name of the device + * @map: configured settings for the device + * + * Adds new setup entry to the dev_boot_setup list. The function + * returns 0 on error and 1 on success. This is a generic routine to + * all netdevices. + */ +static int netdev_boot_setup_add(char *name, struct ifmap *map) +{ + struct netdev_boot_setup *s; + int i; + + s = dev_boot_setup; + for (i = 0; i < NETDEV_BOOT_SETUP_MAX; i++) { + if (s[i].name[0] == '\0' || s[i].name[0] == ' ') { + strscpy_pad(s[i].name, name); + memcpy(&s[i].map, map, sizeof(s[i].map)); + break; + } + } + + return i >= NETDEV_BOOT_SETUP_MAX ? 0 : 1; +} + +/* + * Saves at boot time configured settings for any netdevice. + */ +static int __init netdev_boot_setup(char *str) +{ + int ints[5]; + struct ifmap map; + + str = get_options(str, ARRAY_SIZE(ints), ints); + if (!str || !*str) + return 0; + + /* Save settings */ + memset(&map, 0, sizeof(map)); + if (ints[0] > 0) + map.irq = ints[1]; + if (ints[0] > 1) + map.base_addr = ints[2]; + if (ints[0] > 2) + map.mem_start = ints[3]; + if (ints[0] > 3) + map.mem_end = ints[4]; + + /* Add new entry to the list */ + return netdev_boot_setup_add(str, &map); +} + +__setup("netdev=", netdev_boot_setup); + +static int __init ether_boot_setup(char *str) +{ + return netdev_boot_setup(str); +} +__setup("ether=", ether_boot_setup); #endif +/* A unified ethernet device probe. This is the easiest way to have every + * ethernet adaptor have the name "eth[0123...]". + */ + +struct devprobe2 { + struct net_device *(*probe)(int unit); + int status; /* non-zero if autoprobe has failed */ +}; + +static int __init probe_list2(int unit, struct devprobe2 *p, int autoprobe) +{ + struct net_device *dev; + + for (; p->probe; p++) { + if (autoprobe && p->status) + continue; + dev = p->probe(unit); + if (!IS_ERR(dev)) + return 0; + if (autoprobe) + p->status = PTR_ERR(dev); + } + return -ENODEV; +} + +static struct devprobe2 isa_probes[] __initdata = { + {ne_probe, 0}, + {NULL, 0}, +}; + +/* Unified ethernet device probe, segmented per architecture and + * per bus interface. This drives the legacy devices only for now. + */ + +static void __init ethif_probe2(int unit) +{ + unsigned long base_addr = netdev_boot_base("eth", unit); + + if (base_addr == 1) + return; + + probe_list2(unit, isa_probes, base_addr == 0); +} + +/* Statically configured drivers -- order matters here. */ +static int __init net_olddevs_init(void) +{ + int num; + + for (num = 0; num < 8; ++num) + ethif_probe2(num); + + return 0; +} + +static int __init ne_init(void) +{ + int retval; + + if (IS_MODULE(CONFIG_NE2000)) + ne_add_devices(); + else + net_olddevs_init(); + + retval = platform_driver_probe(&ne_driver, ne_drv_probe); + + if (IS_MODULE(CONFIG_NE2000) && retval) { + if (io[0] == 0) + pr_notice("ne.c: You must supply \"io=0xNNN\"" + " value(s) for ISA cards.\n"); + ne_loop_rm_unreg(1); + return retval; + } + + /* Unregister unused platform_devices. */ + ne_loop_rm_unreg(0); + return retval; +} +module_init(ne_init); + static void __exit ne_exit(void) { platform_driver_unregister(&ne_driver); diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 0e1e581efc5a..744ffa243501 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -3378,7 +3378,6 @@ static inline struct net_device *first_net_device(struct net *net) net_device_entry(net->dev_base_head.next); } -int netdev_boot_setup_check(struct net_device *dev); struct net_device *dev_getbyhwaddr(struct net *net, unsigned short type, const char *hwaddr); struct net_device *dev_getbyhwaddr_rcu(struct net *net, unsigned short type, diff --git a/include/net/Space.h b/include/net/Space.h deleted file mode 100644 index 1b9ab0da5a03..000000000000 --- a/include/net/Space.h +++ /dev/null @@ -1,6 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* A unified ethernet device probe. This is the easiest way to have every - * ethernet adaptor have the name "eth[0123...]". - */ - -struct net_device *ne_probe(int unit); -- cgit v1.2.3 From 9f810527a343fd72671fe7661ebb3694396f45ed Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 30 Apr 2026 10:00:17 +0000 Subject: tcp: move tp->delivered and tp->delivered_ce to tcp_sock_write_tx group These counters are changed whenever sent data is acknowleged. They do not belong to tcp_sock_write_txrx group, because TCP receivers do not touch them. Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260430100021.211139-2-edumazet@google.com Signed-off-by: Jakub Kicinski --- Documentation/networking/net_cachelines/tcp_sock.rst | 4 ++-- include/linux/tcp.h | 4 ++-- net/ipv4/tcp.c | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) (limited to 'include') diff --git a/Documentation/networking/net_cachelines/tcp_sock.rst b/Documentation/networking/net_cachelines/tcp_sock.rst index fecf61166a54..f2eafc933b5f 100644 --- a/Documentation/networking/net_cachelines/tcp_sock.rst +++ b/Documentation/networking/net_cachelines/tcp_sock.rst @@ -99,8 +99,8 @@ u32 snd_cwnd_stamp u32 prior_cwnd u32 prr_delivered u32 prr_out read_mostly read_mostly tcp_rate_skb_sent,tcp_newly_delivered(tx);tcp_ack,tcp_rate_gen,tcp_clean_rtx_queue(rx) -u32 delivered read_mostly read_write tcp_rate_skb_sent, tcp_newly_delivered(tx);tcp_ack, tcp_rate_gen, tcp_clean_rtx_queue (rx) -u32 delivered_ce read_mostly read_write tcp_rate_skb_sent(tx);tcp_rate_gen(rx) +u32 delivered read_write tcp_rate_skb_sent, tcp_newly_delivered(tx);tcp_ack, tcp_rate_gen, tcp_clean_rtx_queue (rx) +u32 delivered_ce read_write tcp_rate_skb_sent(tx);tcp_rate_gen(rx) u32 received_ce read_mostly read_write u32[3] received_ecn_bytes read_mostly read_write u8:4 received_ce_pending read_mostly read_write diff --git a/include/linux/tcp.h b/include/linux/tcp.h index 6982f10e826b..3e20bffd6ae9 100644 --- a/include/linux/tcp.h +++ b/include/linux/tcp.h @@ -259,6 +259,8 @@ struct tcp_sock { u32 data_segs_out; /* RFC4898 tcpEStatsPerfDataSegsOut * total number of data segments sent. */ + u32 delivered; /* Total data packets delivered incl. rexmits */ + u32 delivered_ce; /* Like the above but only ECE marked packets */ u64 bytes_sent; /* RFC4898 tcpEStatsPerfHCDataOctetsOut * total number of data bytes sent. */ @@ -307,8 +309,6 @@ struct tcp_sock { u32 srtt_us; /* smoothed round trip time << 3 in usecs */ u32 packets_out; /* Packets which are "in flight" */ u32 snd_up; /* Urgent pointer */ - u32 delivered; /* Total data packets delivered incl. rexmits */ - u32 delivered_ce; /* Like the above but only ECE marked packets */ u32 received_ce; /* Like the above but for rcvd CE marked pkts */ u32 received_ecn_bytes[3]; /* received byte counters for three ECN * types: INET_ECN_ECT_1, INET_ECN_ECT_0, diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c index 432fa28e47d4..fe1e54321e18 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c @@ -5232,6 +5232,8 @@ static void __init tcp_struct_check(void) /* TX read-write hotpath cache lines */ CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_tx, segs_out); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_tx, data_segs_out); + CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_tx, delivered); + CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_tx, delivered_ce); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_tx, bytes_sent); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_tx, snd_sml); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_tx, chrono_start); @@ -5258,8 +5260,6 @@ static void __init tcp_struct_check(void) CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_txrx, srtt_us); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_txrx, packets_out); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_txrx, snd_up); - CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_txrx, delivered); - CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_txrx, delivered_ce); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_txrx, received_ce); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_txrx, received_ecn_bytes); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_txrx, app_limited); -- cgit v1.2.3 From 2b28dd212a8ad67b100137626a3d008b1e40a740 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 30 Apr 2026 10:00:18 +0000 Subject: tcp: move tp->segs_in and tp->segs_out to tcp_sock_write_txrx group segs_in is changed for each incoming packet, including ACK packets. segs_out is changed for each outgoing packet, including ACK packets. They belong to tcp_sock_write_txrx group. Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260430100021.211139-3-edumazet@google.com Signed-off-by: Jakub Kicinski --- Documentation/networking/net_cachelines/tcp_sock.rst | 4 ++-- include/linux/tcp.h | 18 +++++++++--------- net/ipv4/tcp.c | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) (limited to 'include') diff --git a/Documentation/networking/net_cachelines/tcp_sock.rst b/Documentation/networking/net_cachelines/tcp_sock.rst index f2eafc933b5f..b914382cb8fd 100644 --- a/Documentation/networking/net_cachelines/tcp_sock.rst +++ b/Documentation/networking/net_cachelines/tcp_sock.rst @@ -13,13 +13,13 @@ u16 tcp_header_len read_mostly read_m u16 gso_segs read_mostly tcp_xmit_size_goal __be32 pred_flags read_write read_mostly tcp_select_window(tx);tcp_rcv_established(rx) u64 bytes_received read_write tcp_rcv_nxt_update(rx) -u32 segs_in read_write tcp_v6_rcv(rx) +u32 segs_in read_write read_write tcp_segs_in(),tcp_v6_rcv(rx),tcp_v4_rcv() u32 data_segs_in read_write tcp_v6_rcv(rx) u32 rcv_nxt read_mostly read_write tcp_cleanup_rbuf,tcp_send_ack,tcp_inq_hint,tcp_transmit_skb,tcp_receive_window(tx);tcp_v6_do_rcv,tcp_rcv_established,tcp_data_queue,tcp_receive_window,tcp_rcv_nxt_update(write)(rx) u32 copied_seq read_mostly tcp_cleanup_rbuf,tcp_rcv_space_adjust,tcp_inq_hint u32 rcv_wup read_write __tcp_cleanup_rbuf,tcp_receive_window,tcp_receive_established u32 snd_nxt read_write read_mostly tcp_rate_check_app_limited,__tcp_transmit_skb,tcp_event_new_data_sent(write)(tx);tcp_rcv_established,tcp_ack,tcp_clean_rtx_queue(rx) -u32 segs_out read_write __tcp_transmit_skb +u32 segs_out read_write read_write __tcp_transmit_skb u32 data_segs_out read_write __tcp_transmit_skb,tcp_update_skb_after_send u64 bytes_sent read_write __tcp_transmit_skb u64 bytes_acked read_write tcp_snd_una_update/tcp_ack diff --git a/include/linux/tcp.h b/include/linux/tcp.h index 3e20bffd6ae9..89013be1519a 100644 --- a/include/linux/tcp.h +++ b/include/linux/tcp.h @@ -253,17 +253,14 @@ struct tcp_sock { /* TX read-write hotpath cache lines */ __cacheline_group_begin(tcp_sock_write_tx) ____cacheline_aligned; - u32 segs_out; /* RFC4898 tcpEStatsPerfSegsOut - * The total number of segments sent. - */ - u32 data_segs_out; /* RFC4898 tcpEStatsPerfDataSegsOut - * total number of data segments sent. - */ u32 delivered; /* Total data packets delivered incl. rexmits */ u32 delivered_ce; /* Like the above but only ECE marked packets */ u64 bytes_sent; /* RFC4898 tcpEStatsPerfHCDataOctetsOut * total number of data bytes sent. */ + u32 data_segs_out; /* RFC4898 tcpEStatsPerfDataSegsOut + * total number of data segments sent. + */ u32 snd_sml; /* Last byte of the most recently transmitted small packet */ u8 chrono_type; /* current chronograph type */ u32 chrono_start; /* Start time in jiffies of a TCP chrono */ @@ -324,6 +321,12 @@ struct tcp_sock { * Options received (usually on last packet, some only on SYN packets). */ struct tcp_options_received rx_opt; + u32 segs_in; /* RFC4898 tcpEStatsPerfSegsIn + * total number of segments in. + */ + u32 segs_out; /* RFC4898 tcpEStatsPerfSegsOut + * The total number of segments sent. + */ __cacheline_group_end(tcp_sock_write_txrx); /* RX read-write hotpath cache lines */ @@ -333,9 +336,6 @@ struct tcp_sock { * sum(delta(rcv_nxt)), or how many bytes * were acked. */ - u32 segs_in; /* RFC4898 tcpEStatsPerfSegsIn - * total number of segments in. - */ u32 data_segs_in; /* RFC4898 tcpEStatsPerfDataSegsIn * total number of data segments in. */ diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c index fe1e54321e18..135a5db6c3fc 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c @@ -5230,7 +5230,6 @@ static void __init tcp_struct_check(void) CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_read_rx, snd_ssthresh); /* TX read-write hotpath cache lines */ - CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_tx, segs_out); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_tx, data_segs_out); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_tx, delivered); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_tx, delivered_ce); @@ -5267,10 +5266,11 @@ static void __init tcp_struct_check(void) CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_txrx, rcv_mwnd_seq); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_txrx, rcv_tstamp); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_txrx, rx_opt); + CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_txrx, segs_in); + CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_txrx, segs_out); /* RX read-write hotpath cache lines */ CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_rx, bytes_received); - CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_rx, segs_in); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_rx, data_segs_in); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_rx, rcv_wup); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_rx, max_packets_out); -- cgit v1.2.3 From 07db42c4b3eb60a21645ad88211b69933eab0a03 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 30 Apr 2026 10:00:19 +0000 Subject: tcp: move tp->first_tx_mstamp and tp->delivered_mstamp to tcp_sock_write_tx These fields are touched in when payload is sent. Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260430100021.211139-4-edumazet@google.com Signed-off-by: Jakub Kicinski --- include/linux/tcp.h | 4 ++-- net/ipv4/tcp.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'include') diff --git a/include/linux/tcp.h b/include/linux/tcp.h index 89013be1519a..e9adc88b73b4 100644 --- a/include/linux/tcp.h +++ b/include/linux/tcp.h @@ -258,6 +258,8 @@ struct tcp_sock { u64 bytes_sent; /* RFC4898 tcpEStatsPerfHCDataOctetsOut * total number of data bytes sent. */ + u64 first_tx_mstamp; /* start of window send phase */ + u64 delivered_mstamp; /* time we reached "delivered" */ u32 data_segs_out; /* RFC4898 tcpEStatsPerfDataSegsOut * total number of data segments sent. */ @@ -347,8 +349,6 @@ struct tcp_sock { u32 rcv_rtt_last_tsecr; u32 delivered_ecn_bytes[3]; u16 pkts_acked_ewma;/* Pkts acked EWMA for AccECN cep heuristic */ - u64 first_tx_mstamp; /* start of window send phase */ - u64 delivered_mstamp; /* time we reached "delivered" */ u64 bytes_acked; /* RFC4898 tcpEStatsAppHCThruOctetsAcked * sum(delta(snd_una)), or how many bytes * were acked. diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c index 135a5db6c3fc..d25698a0e681 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c @@ -5234,6 +5234,8 @@ static void __init tcp_struct_check(void) CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_tx, delivered); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_tx, delivered_ce); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_tx, bytes_sent); + CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_tx, first_tx_mstamp); + CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_tx, delivered_mstamp); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_tx, snd_sml); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_tx, chrono_start); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_tx, chrono_stat); @@ -5280,8 +5282,6 @@ static void __init tcp_struct_check(void) CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_rx, rcv_rtt_last_tsecr); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_rx, delivered_ecn_bytes); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_rx, pkts_acked_ewma); - CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_rx, first_tx_mstamp); - CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_rx, delivered_mstamp); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_rx, bytes_acked); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_rx, rcv_rtt_est); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_rx, rcvq_space); -- cgit v1.2.3 From dd033ec406b4d9eade3e0fd998ec9bfa50db42ba Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 30 Apr 2026 10:00:20 +0000 Subject: tcp: move tp->bytes_acked to tcp_sock_write_tx group tp->bytes_acked is touched in TX path only. Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260430100021.211139-5-edumazet@google.com Signed-off-by: Jakub Kicinski --- Documentation/networking/net_cachelines/tcp_sock.rst | 2 +- include/linux/tcp.h | 8 ++++---- net/ipv4/tcp.c | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) (limited to 'include') diff --git a/Documentation/networking/net_cachelines/tcp_sock.rst b/Documentation/networking/net_cachelines/tcp_sock.rst index b914382cb8fd..0f6088c4ab8b 100644 --- a/Documentation/networking/net_cachelines/tcp_sock.rst +++ b/Documentation/networking/net_cachelines/tcp_sock.rst @@ -22,7 +22,7 @@ u32 snd_nxt read_write read_m u32 segs_out read_write read_write __tcp_transmit_skb u32 data_segs_out read_write __tcp_transmit_skb,tcp_update_skb_after_send u64 bytes_sent read_write __tcp_transmit_skb -u64 bytes_acked read_write tcp_snd_una_update/tcp_ack +u64 bytes_acked read_write tcp_snd_una_update/tcp_ack u32 dsack_dups u32 snd_una read_mostly read_write tcp_wnd_end,tcp_urg_mode,tcp_minshall_check,tcp_cwnd_validate(tx);tcp_ack,tcp_may_update_window,tcp_clean_rtx_queue(write),tcp_ack_tstamp(rx) u32 snd_sml read_write tcp_minshall_check,tcp_minshall_update diff --git a/include/linux/tcp.h b/include/linux/tcp.h index e9adc88b73b4..d3650f04d942 100644 --- a/include/linux/tcp.h +++ b/include/linux/tcp.h @@ -255,6 +255,10 @@ struct tcp_sock { __cacheline_group_begin(tcp_sock_write_tx) ____cacheline_aligned; u32 delivered; /* Total data packets delivered incl. rexmits */ u32 delivered_ce; /* Like the above but only ECE marked packets */ + u64 bytes_acked; /* RFC4898 tcpEStatsAppHCThruOctetsAcked + * sum(delta(snd_una)), or how many bytes + * were acked. + */ u64 bytes_sent; /* RFC4898 tcpEStatsPerfHCDataOctetsOut * total number of data bytes sent. */ @@ -349,10 +353,6 @@ struct tcp_sock { u32 rcv_rtt_last_tsecr; u32 delivered_ecn_bytes[3]; u16 pkts_acked_ewma;/* Pkts acked EWMA for AccECN cep heuristic */ - u64 bytes_acked; /* RFC4898 tcpEStatsAppHCThruOctetsAcked - * sum(delta(snd_una)), or how many bytes - * were acked. - */ struct { u32 rtt_us; u32 seq; diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c index d25698a0e681..7211d2c669b8 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c @@ -5233,6 +5233,7 @@ static void __init tcp_struct_check(void) CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_tx, data_segs_out); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_tx, delivered); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_tx, delivered_ce); + CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_tx, bytes_acked); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_tx, bytes_sent); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_tx, first_tx_mstamp); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_tx, delivered_mstamp); @@ -5282,7 +5283,6 @@ static void __init tcp_struct_check(void) CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_rx, rcv_rtt_last_tsecr); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_rx, delivered_ecn_bytes); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_rx, pkts_acked_ewma); - CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_rx, bytes_acked); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_rx, rcv_rtt_est); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_rx, rcvq_space); } -- cgit v1.2.3 From affe6c27651a359cf69c15b33b86e4d3f226010e Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 30 Apr 2026 10:00:21 +0000 Subject: tcp: move max_packets_out, cwnd_usage_seq, rate_delivered and rate_interval_us to tcp_sock_write_tx group These fields are used in TX path. Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260430100021.211139-6-edumazet@google.com Signed-off-by: Jakub Kicinski --- include/linux/tcp.h | 8 ++++---- net/ipv4/tcp.c | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) (limited to 'include') diff --git a/include/linux/tcp.h b/include/linux/tcp.h index d3650f04d942..8a6807082672 100644 --- a/include/linux/tcp.h +++ b/include/linux/tcp.h @@ -276,6 +276,10 @@ struct tcp_sock { u32 lsndtime; /* timestamp of last sent data packet (for restart window) */ u32 mdev_us; /* medium deviation */ u32 rtt_seq; /* sequence number to update rttvar */ + u32 max_packets_out; /* max packets_out in last window */ + u32 cwnd_usage_seq; /* right edge of cwnd usage tracking flight */ + u32 rate_delivered; /* saved rate sample: packets delivered */ + u32 rate_interval_us; /* saved rate sample: time elapsed */ u64 tcp_wstamp_ns; /* departure time for next sent data packet */ u64 accecn_opt_tstamp; /* Last AccECN option sent timestamp */ struct list_head tsorted_sent_queue; /* time-sorted sent but un-SACKed skbs */ @@ -346,10 +350,6 @@ struct tcp_sock { * total number of data segments in. */ u32 rcv_wup; /* rcv_nxt on last window update sent */ - u32 max_packets_out; /* max packets_out in last window */ - u32 cwnd_usage_seq; /* right edge of cwnd usage tracking flight */ - u32 rate_delivered; /* saved rate sample: packets delivered */ - u32 rate_interval_us; /* saved rate sample: time elapsed */ u32 rcv_rtt_last_tsecr; u32 delivered_ecn_bytes[3]; u16 pkts_acked_ewma;/* Pkts acked EWMA for AccECN cep heuristic */ diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c index 7211d2c669b8..21ece4c71612 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c @@ -5247,6 +5247,10 @@ static void __init tcp_struct_check(void) CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_tx, tcp_wstamp_ns); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_tx, accecn_opt_tstamp); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_tx, rtt_seq); + CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_tx, max_packets_out); + CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_tx, cwnd_usage_seq); + CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_tx, rate_delivered); + CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_tx, rate_interval_us); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_tx, tsorted_sent_queue); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_tx, highest_sack); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_tx, ecn_flags); @@ -5276,10 +5280,6 @@ static void __init tcp_struct_check(void) CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_rx, bytes_received); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_rx, data_segs_in); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_rx, rcv_wup); - CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_rx, max_packets_out); - CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_rx, cwnd_usage_seq); - CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_rx, rate_delivered); - CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_rx, rate_interval_us); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_rx, rcv_rtt_last_tsecr); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_rx, delivered_ecn_bytes); CACHELINE_ASSERT_GROUP_MEMBER(struct tcp_sock, tcp_sock_write_rx, pkts_acked_ewma); -- cgit v1.2.3 From 3b16155425af46ac89c6cc676db0ca1ec4c4ab85 Mon Sep 17 00:00:00 2001 From: Nimrod Oren Date: Wed, 29 Apr 2026 23:14:28 +0300 Subject: net/mlx5: add frag buf pools create/destroy paths Introduce mlx5 DMA pool and pool-page data structures, and add the creation and teardown paths. Each NUMA node owns a set of mlx5_dma_pool instances, each one with a different block size. The sizes are defined as all powers of two starting from MLX5_ADAPTER_PAGE_SHIFT and up to PAGE_SHIFT. Since mlx5_frag_bufs are used to back objects whose sizes are encoded relative to MLX5_ADAPTER_PAGE_SHIFT, a smaller block_shift value cannot be used. Requests larger than PAGE_SIZE continue to be handled as page-sized fragments, as in the existing frag-buf allocation model. Signed-off-by: Nimrod Oren Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260429201429.223809-3-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/alloc.c | 116 +++++++++++++++++++++++- include/linux/mlx5/driver.h | 7 +- 2 files changed, 119 insertions(+), 4 deletions(-) (limited to 'include') diff --git a/drivers/net/ethernet/mellanox/mlx5/core/alloc.c b/drivers/net/ethernet/mellanox/mlx5/core/alloc.c index cebb3559d2c9..fcc859c5f810 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/alloc.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/alloc.c @@ -37,10 +37,15 @@ #include #include #include +#include #include #include "mlx5_core.h" +#define MLX5_FRAG_BUF_POOL_MIN_BLOCK_SHIFT MLX5_ADAPTER_PAGE_SHIFT +#define MLX5_FRAG_BUF_POOLS_NUM \ + (PAGE_SHIFT - MLX5_FRAG_BUF_POOL_MIN_BLOCK_SHIFT + 1) + struct mlx5_db_pgdir { struct list_head list; unsigned long *bitmap; @@ -48,6 +53,27 @@ struct mlx5_db_pgdir { dma_addr_t db_dma; }; +struct mlx5_dma_pool { + /* Protects page_list and per-page allocation bitmaps. */ + struct mutex lock; + struct list_head page_list; + struct mlx5_core_dev *dev; + int node; + u8 block_shift; +}; + +struct mlx5_dma_pool_page { + struct mlx5_dma_pool *pool; + struct list_head pool_link; + unsigned long *bitmap; + void *buf; + dma_addr_t dma; +}; + +struct mlx5_frag_buf_node_pools { + struct mlx5_dma_pool *pools[MLX5_FRAG_BUF_POOLS_NUM]; +}; + /* Handling for queue buffers -- we allocate a bunch of memory and * register it in a memory region at HCA virtual address 0. */ @@ -71,14 +97,100 @@ static void *mlx5_dma_zalloc_coherent_node(struct mlx5_core_dev *dev, return cpu_handle; } -/* Implemented later in the series */ +static void mlx5_dma_pool_destroy(struct mlx5_dma_pool *pool) +{ + mutex_destroy(&pool->lock); + kfree(pool); +} + +static struct mlx5_dma_pool *mlx5_dma_pool_create(struct mlx5_core_dev *dev, + int node, u8 block_shift) +{ + struct mlx5_dma_pool *pool; + + pool = kzalloc_obj(*pool); + if (!pool) + return NULL; + + INIT_LIST_HEAD(&pool->page_list); + mutex_init(&pool->lock); + pool->dev = dev; + pool->node = node; + pool->block_shift = block_shift; + return pool; +} + +static void +mlx5_frag_buf_node_pools_destroy(struct mlx5_frag_buf_node_pools *node_pools) +{ + for (int i = 0; i < MLX5_FRAG_BUF_POOLS_NUM; i++) + if (node_pools->pools[i]) + mlx5_dma_pool_destroy(node_pools->pools[i]); + kfree(node_pools); +} + +static struct mlx5_frag_buf_node_pools * +mlx5_frag_buf_node_pools_create(struct mlx5_core_dev *dev, int node) +{ + struct mlx5_frag_buf_node_pools *node_pools; + + node_pools = kzalloc_obj(*node_pools); + if (!node_pools) + return NULL; + + for (int i = 0; i < MLX5_FRAG_BUF_POOLS_NUM; i++) { + u8 block_shift = MLX5_FRAG_BUF_POOL_MIN_BLOCK_SHIFT + i; + + node_pools->pools[i] = mlx5_dma_pool_create(dev, node, + block_shift); + if (!node_pools->pools[i]) { + mlx5_frag_buf_node_pools_destroy(node_pools); + return NULL; + } + } + + return node_pools; +} + void mlx5_frag_buf_pools_cleanup(struct mlx5_core_dev *dev) { + struct mlx5_priv *priv = &dev->priv; + int node; + + for_each_node_state(node, N_POSSIBLE) { + struct mlx5_frag_buf_node_pools *node_pools; + + node_pools = priv->frag_buf_node_pools[node]; + if (!node_pools) + continue; + mlx5_frag_buf_node_pools_destroy(node_pools); + } + + kfree(priv->frag_buf_node_pools); + priv->frag_buf_node_pools = NULL; } -/* Implemented later in the series */ int mlx5_frag_buf_pools_init(struct mlx5_core_dev *dev) { + struct mlx5_priv *priv = &dev->priv; + int node; + + priv->frag_buf_node_pools = kzalloc_objs(*priv->frag_buf_node_pools, + nr_node_ids); + if (!priv->frag_buf_node_pools) + return -ENOMEM; + + for_each_node_state(node, N_POSSIBLE) { + struct mlx5_frag_buf_node_pools *node_pools; + + node_pools = mlx5_frag_buf_node_pools_create(dev, node); + if (!node_pools) { + mlx5_frag_buf_pools_cleanup(dev); + return -ENOMEM; + } + priv->frag_buf_node_pools[node] = node_pools; + } + return 0; } diff --git a/include/linux/mlx5/driver.h b/include/linux/mlx5/driver.h index 04b96c5abb57..71f7615ab553 100644 --- a/include/linux/mlx5/driver.h +++ b/include/linux/mlx5/driver.h @@ -558,6 +558,7 @@ enum mlx5_func_type { MLX5_FUNC_TYPE_NUM, }; +struct mlx5_frag_buf_node_pools; struct mlx5_ft_pool; struct mlx5_priv { /* IRQ table valid only for real pci devices PF or VF */ @@ -581,14 +582,16 @@ struct mlx5_priv { struct mlx5_debugfs_entries dbg; - /* start: alloc staff */ + /* start: alloc stuff */ /* protect buffer allocation according to numa node */ struct mutex alloc_mutex; int numa_node; struct mutex pgdir_mutex; struct list_head pgdir_list; - /* end: alloc staff */ + + struct mlx5_frag_buf_node_pools **frag_buf_node_pools; + /* end: alloc stuff */ struct mlx5_adev **adev; int adev_idx; -- cgit v1.2.3 From fbf6f64a4322cfeb0d98f39baf8ce18246dd12c0 Mon Sep 17 00:00:00 2001 From: Nimrod Oren Date: Wed, 29 Apr 2026 23:14:29 +0300 Subject: net/mlx5: use internal dma pools for frag buf alloc Add mlx5_dma_pool alloc/free paths, and wire mlx5_frag_buf allocation and free paths to use them. mlx5_frag_buf_alloc_node() now selects an mlx5_dma_pool to allocate fragments from, instead of directly allocating full coherent pages. mlx5_frag_buf_free() frees from the respective pool. mlx5_dma_pool_alloc() keeps allocation fast by maintaining pages with available indexes at the head of the list, so the common allocation path can take a free index immediately. New backing pages are allocated only when no free index is available. mlx5_dma_pool_free() returns released indexes to the pool and frees a backing page once all of its indexes become free. This avoids keeping fully free pages for the lifetime of the pool and reduces coherent DMA memory footprint. Signed-off-by: Nimrod Oren Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260429201429.223809-4-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/alloc.c | 170 +++++++++++++++++++----- include/linux/mlx5/driver.h | 2 + 2 files changed, 140 insertions(+), 32 deletions(-) (limited to 'include') diff --git a/drivers/net/ethernet/mellanox/mlx5/core/alloc.c b/drivers/net/ethernet/mellanox/mlx5/core/alloc.c index fcc859c5f810..f19644183828 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/alloc.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/alloc.c @@ -120,6 +120,111 @@ static struct mlx5_dma_pool *mlx5_dma_pool_create(struct mlx5_core_dev *dev, return pool; } +static struct mlx5_dma_pool_page * +mlx5_dma_pool_page_alloc(struct mlx5_dma_pool *pool) +{ + int blocks_per_page = BIT(PAGE_SHIFT - pool->block_shift); + struct mlx5_dma_pool_page *page; + + page = kzalloc_obj(*page); + if (!page) + goto err_out; + + page->pool = pool; + page->bitmap = bitmap_zalloc(blocks_per_page, GFP_KERNEL); + if (!page->bitmap) + goto err_free_page; + + bitmap_fill(page->bitmap, blocks_per_page); + page->buf = mlx5_dma_zalloc_coherent_node(pool->dev, PAGE_SIZE, + &page->dma, pool->node); + if (!page->buf) + goto err_free_bitmap; + + return page; + +err_free_bitmap: + bitmap_free(page->bitmap); +err_free_page: + kfree(page); +err_out: + return NULL; +} + +static void mlx5_dma_pool_page_free(struct mlx5_core_dev *dev, + struct mlx5_dma_pool_page *page) +{ + dma_free_coherent(mlx5_core_dma_dev(dev), PAGE_SIZE, page->buf, + page->dma); + bitmap_free(page->bitmap); + kfree(page); +} + +static int mlx5_dma_pool_alloc_from_page(struct mlx5_dma_pool *pool, + struct mlx5_dma_pool_page *page, + unsigned long *idx_out) +{ + int blocks_per_page = BIT(PAGE_SHIFT - pool->block_shift); + + *idx_out = find_first_bit(page->bitmap, blocks_per_page); + if (*idx_out >= blocks_per_page) + return -ENOMEM; + + __clear_bit(*idx_out, page->bitmap); + + if (bitmap_empty(page->bitmap, blocks_per_page)) + list_move_tail(&page->pool_link, &pool->page_list); + + return 0; +} + +static struct mlx5_dma_pool_page * +mlx5_dma_pool_alloc(struct mlx5_dma_pool *pool, unsigned long *idx_out) +{ + struct mlx5_dma_pool_page *page; + + mutex_lock(&pool->lock); + + page = list_first_entry_or_null(&pool->page_list, + struct mlx5_dma_pool_page, pool_link); + if (page && !mlx5_dma_pool_alloc_from_page(pool, page, idx_out)) + goto unlock; /* successfully allocated from existing page */ + + page = mlx5_dma_pool_page_alloc(pool); + if (!page) + goto unlock; + + list_add(&page->pool_link, &pool->page_list); + mlx5_dma_pool_alloc_from_page(pool, page, idx_out); + +unlock: + mutex_unlock(&pool->lock); + return page; +} + +static void mlx5_dma_pool_free(struct mlx5_dma_pool *pool, + struct mlx5_dma_pool_page *page, + unsigned long idx) +{ + int blocks_per_page = BIT(PAGE_SHIFT - pool->block_shift); + bool was_full; + + mutex_lock(&pool->lock); + was_full = bitmap_empty(page->bitmap, blocks_per_page); + __set_bit(idx, page->bitmap); + + if (bitmap_full(page->bitmap, blocks_per_page)) { + list_del(&page->pool_link); + mlx5_dma_pool_page_free(pool->dev, page); + } else { + memset((u8 *)page->buf + (idx << pool->block_shift), 0, + BIT(pool->block_shift)); + if (was_full) + list_move(&page->pool_link, &pool->page_list); + } + mutex_unlock(&pool->lock); +} + static void mlx5_frag_buf_node_pools_destroy(struct mlx5_frag_buf_node_pools *node_pools) { @@ -197,56 +302,57 @@ int mlx5_frag_buf_pools_init(struct mlx5_core_dev *dev) int mlx5_frag_buf_alloc_node(struct mlx5_core_dev *dev, int size, struct mlx5_frag_buf *buf, int node) { - int i; + struct mlx5_dma_pool *pool; + int pool_idx; + + node = node == NUMA_NO_NODE ? first_online_node : node; buf->size = size; buf->npages = DIV_ROUND_UP(size, PAGE_SIZE); - buf->page_shift = PAGE_SHIFT; - buf->frags = kzalloc_objs(struct mlx5_buf_list, buf->npages); + buf->page_shift = clamp_t(int, order_base_2(size), + MLX5_FRAG_BUF_POOL_MIN_BLOCK_SHIFT, + PAGE_SHIFT); + buf->frags = kcalloc_node(buf->npages, sizeof(*buf->frags), + GFP_KERNEL, node); if (!buf->frags) - goto err_out; + return -ENOMEM; - for (i = 0; i < buf->npages; i++) { + pool_idx = buf->page_shift - MLX5_FRAG_BUF_POOL_MIN_BLOCK_SHIFT; + pool = dev->priv.frag_buf_node_pools[node]->pools[pool_idx]; + for (int i = 0; i < buf->npages; i++) { struct mlx5_buf_list *frag = &buf->frags[i]; - int frag_sz = min_t(int, size, PAGE_SIZE); + struct mlx5_dma_pool_page *page; + unsigned long idx; - frag->buf = mlx5_dma_zalloc_coherent_node(dev, frag_sz, - &frag->map, node); - if (!frag->buf) - goto err_free_buf; - if (frag->map & ((1 << buf->page_shift) - 1)) { - dma_free_coherent(mlx5_core_dma_dev(dev), frag_sz, - buf->frags[i].buf, buf->frags[i].map); - mlx5_core_warn(dev, "unexpected map alignment: %pad, page_shift=%d\n", - &frag->map, buf->page_shift); - goto err_free_buf; + page = mlx5_dma_pool_alloc(pool, &idx); + if (!page) { + mlx5_frag_buf_free(dev, buf); + return -ENOMEM; } - size -= frag_sz; + frag->buf = (u8 *)page->buf + (idx << pool->block_shift); + frag->map = page->dma + (idx << pool->block_shift); + frag->frag_page = page; } return 0; - -err_free_buf: - while (i--) - dma_free_coherent(mlx5_core_dma_dev(dev), PAGE_SIZE, buf->frags[i].buf, - buf->frags[i].map); - kfree(buf->frags); -err_out: - return -ENOMEM; } EXPORT_SYMBOL_GPL(mlx5_frag_buf_alloc_node); void mlx5_frag_buf_free(struct mlx5_core_dev *dev, struct mlx5_frag_buf *buf) { - int size = buf->size; - int i; + for (int i = 0; i < buf->npages; i++) { + struct mlx5_buf_list *frag = &buf->frags[i]; + struct mlx5_dma_pool_page *page; + struct mlx5_dma_pool *pool; + unsigned long idx; - for (i = 0; i < buf->npages; i++) { - int frag_sz = min_t(int, size, PAGE_SIZE); + if (!frag->buf) + continue; - dma_free_coherent(mlx5_core_dma_dev(dev), frag_sz, buf->frags[i].buf, - buf->frags[i].map); - size -= frag_sz; + page = frag->frag_page; + pool = page->pool; + idx = (frag->map - page->dma) >> pool->block_shift; + mlx5_dma_pool_free(pool, page, idx); } kfree(buf->frags); } diff --git a/include/linux/mlx5/driver.h b/include/linux/mlx5/driver.h index 71f7615ab553..531ce66fc8ef 100644 --- a/include/linux/mlx5/driver.h +++ b/include/linux/mlx5/driver.h @@ -343,9 +343,11 @@ struct mlx5_cmd_mailbox { struct mlx5_cmd_mailbox *next; }; +struct mlx5_dma_pool_page; struct mlx5_buf_list { void *buf; dma_addr_t map; + struct mlx5_dma_pool_page *frag_page; }; struct mlx5_frag_buf { -- cgit v1.2.3 From 09e4d1298b367051b60d9b0a13b22e7d78e61803 Mon Sep 17 00:00:00 2001 From: David Yang Date: Thu, 30 Apr 2026 19:45:24 +0800 Subject: net: dsa: pass extack to dsa_switch_ops :: port_policer_add() Drivers might have error messages to propagate to user space. Propagate the netlink extack so that they can inform user space in a verbal way of their limitations. Make the according transformations to the two users (sja1105 and felix). Signed-off-by: David Yang Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260430114529.3536911-2-mmyangfl@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/dsa/ocelot/felix.c | 3 ++- drivers/net/dsa/sja1105/sja1105_main.c | 3 ++- include/net/dsa.h | 3 ++- net/dsa/user.c | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) (limited to 'include') diff --git a/drivers/net/dsa/ocelot/felix.c b/drivers/net/dsa/ocelot/felix.c index 84cf8e7fb17a..4272ea6e9ca8 100644 --- a/drivers/net/dsa/ocelot/felix.c +++ b/drivers/net/dsa/ocelot/felix.c @@ -2001,7 +2001,8 @@ static int felix_cls_flower_stats(struct dsa_switch *ds, int port, } static int felix_port_policer_add(struct dsa_switch *ds, int port, - const struct flow_action_police *policer) + const struct flow_action_police *policer, + struct netlink_ext_ack *extack) { struct ocelot *ocelot = ds->priv; struct ocelot_policer pol = { diff --git a/drivers/net/dsa/sja1105/sja1105_main.c b/drivers/net/dsa/sja1105/sja1105_main.c index c72c2bfdcffb..dbfa45064747 100644 --- a/drivers/net/dsa/sja1105/sja1105_main.c +++ b/drivers/net/dsa/sja1105/sja1105_main.c @@ -2847,7 +2847,8 @@ static void sja1105_mirror_del(struct dsa_switch *ds, int port, } static int sja1105_port_policer_add(struct dsa_switch *ds, int port, - const struct flow_action_police *policer) + const struct flow_action_police *policer, + struct netlink_ext_ack *extack) { struct sja1105_l2_policing_entry *policing; struct sja1105_private *priv = ds->priv; diff --git a/include/net/dsa.h b/include/net/dsa.h index 8b6d34e8a6f0..4cc67469cf2e 100644 --- a/include/net/dsa.h +++ b/include/net/dsa.h @@ -1122,7 +1122,8 @@ struct dsa_switch_ops { void (*port_mirror_del)(struct dsa_switch *ds, int port, struct dsa_mall_mirror_tc_entry *mirror); int (*port_policer_add)(struct dsa_switch *ds, int port, - const struct flow_action_police *policer); + const struct flow_action_police *policer, + struct netlink_ext_ack *extack); void (*port_policer_del)(struct dsa_switch *ds, int port); int (*port_setup_tc)(struct dsa_switch *ds, int port, enum tc_setup_type type, void *type_data); diff --git a/net/dsa/user.c b/net/dsa/user.c index c4bd6fe90b45..8704c1a3a5b7 100644 --- a/net/dsa/user.c +++ b/net/dsa/user.c @@ -1499,7 +1499,7 @@ dsa_user_add_cls_matchall_police(struct net_device *dev, policer = &mall_tc_entry->policer; *policer = act->police; - err = ds->ops->port_policer_add(ds, dp->index, policer); + err = ds->ops->port_policer_add(ds, dp->index, policer, extack); if (err) { kfree(mall_tc_entry); return err; -- cgit v1.2.3 From 54f63d24ce2025db818dc4bcf602593f0cf81d5d Mon Sep 17 00:00:00 2001 From: Jedrzej Jagielski Date: Thu, 30 Apr 2026 23:37:12 -0700 Subject: ixgbe: E610: add discovering EEE capability Add detecting and parsing EEE device capability. Recently EEE functionality support has been introduced to E610 FW. Currently ixgbe driver has no possibility to detect whether NVM loaded on given adapter supports EEE. There's dedicated device capability element reflecting FW support for given EEE link speed. Reviewed-by: Aleksandr Loktionov Signed-off-by: Jedrzej Jagielski Tested-by: Rinitha S Signed-off-by: Jacob Keller Link: https://patch.msgid.link/20260430-jk-iwl-net-next-2026-04-30-v1-1-6f27ae1cd073@intel.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/intel/ixgbe/ixgbe_e610.c | 3 +++ drivers/net/ethernet/intel/ixgbe/ixgbe_type_e610.h | 1 + include/linux/net/intel/libie/adminq.h | 1 + 3 files changed, 5 insertions(+) (limited to 'include') diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.c b/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.c index 52f73142ff75..5383f0fd55d6 100644 --- a/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.c +++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_e610.c @@ -628,6 +628,9 @@ static bool ixgbe_parse_e610_caps(struct ixgbe_hw *hw, (phys_id & IXGBE_EXT_TOPO_DEV_IMG_PROG_EN) != 0; break; } + case LIBIE_AQC_CAPS_EEE: + caps->eee_support = (u8)number; + break; default: /* Not one of the recognized common capabilities */ return false; diff --git a/drivers/net/ethernet/intel/ixgbe/ixgbe_type_e610.h b/drivers/net/ethernet/intel/ixgbe/ixgbe_type_e610.h index ff8d640a50b1..34f62a416eaa 100644 --- a/drivers/net/ethernet/intel/ixgbe/ixgbe_type_e610.h +++ b/drivers/net/ethernet/intel/ixgbe/ixgbe_type_e610.h @@ -891,6 +891,7 @@ struct ixgbe_hw_caps { u8 apm_wol_support; u8 acpi_prog_mthd; u8 proxy_support; + u8 eee_support; bool nvm_update_pending_nvm; bool nvm_update_pending_orom; bool nvm_update_pending_netlist; diff --git a/include/linux/net/intel/libie/adminq.h b/include/linux/net/intel/libie/adminq.h index ab13bd777a28..839114d8975a 100644 --- a/include/linux/net/intel/libie/adminq.h +++ b/include/linux/net/intel/libie/adminq.h @@ -196,6 +196,7 @@ LIBIE_CHECK_STRUCT_LEN(16, libie_aqc_list_caps); #define LIBIE_AQC_BIT_ROCEV2_LAG BIT(0) #define LIBIE_AQC_BIT_SRIOV_LAG BIT(1) #define LIBIE_AQC_BIT_SRIOV_AA_LAG BIT(2) +#define LIBIE_AQC_CAPS_EEE 0x009B #define LIBIE_AQC_CAPS_FLEX10 0x00F1 #define LIBIE_AQC_CAPS_CEM 0x00F2 -- cgit v1.2.3 From 3f3aa77ff1c8b45ec8c9e40212f1a24a93e00df3 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Fri, 1 May 2026 13:59:16 +0000 Subject: net/sched: add qstats_cpu_drop_inc() helper 1) Using this_cpu_inc() is better than going through this_cpu_ptr(): - Single instruction on x86. - Store tearing prevention. 2) Change tcf_action_update_stats() to use this_cpu_add(). 3) Add WRITE_ONCE() to __qdisc_qstats_drop() and qstats_drop_inc() in preparation for lockless "tc qdisc show". $ scripts/bloat-o-meter -t vmlinux.old vmlinux.new add/remove: 0/0 grow/shrink: 3/17 up/down: 72/-216 (-144) Function old new delta dualpi2_enqueue_skb 462 511 +49 tcf_ife_act 1061 1077 +16 taprio_enqueue 613 620 +7 codel_qdisc_enqueue 149 143 -6 tcf_vlan_act 684 676 -8 tcf_skbedit_act 626 618 -8 tcf_police_act 725 717 -8 tcf_mpls_act 1297 1289 -8 tcf_gate_act 310 302 -8 tcf_gact_act 222 214 -8 tcf_csum_act 2438 2430 -8 tcf_bpf_act 709 701 -8 tcf_action_update_stats 124 115 -9 pie_qdisc_enqueue 865 856 -9 pfifo_enqueue 116 107 -9 choke_enqueue 2069 2059 -10 plug_enqueue 139 128 -11 bfifo_enqueue 121 110 -11 tcf_nat_act 1501 1489 -12 gred_enqueue 1743 1668 -75 Total: Before=24388609, After=24388465, chg -0.00% Signed-off-by: Eric Dumazet Reviewed-by: Jamal Hadi Salim Link: https://patch.msgid.link/20260501135916.2566766-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- include/net/act_api.h | 2 +- include/net/sch_generic.h | 9 +++++++-- net/sched/act_api.c | 2 +- net/sched/act_bpf.c | 2 +- net/sched/act_ife.c | 8 ++++---- net/sched/act_mpls.c | 2 +- net/sched/act_police.c | 2 +- net/sched/act_skbedit.c | 2 +- net/sched/sch_cake.c | 2 +- net/sched/sch_fq_codel.c | 2 +- net/sched/sch_gred.c | 2 +- 11 files changed, 20 insertions(+), 15 deletions(-) (limited to 'include') diff --git a/include/net/act_api.h b/include/net/act_api.h index 2ec4ef9a5d0c..167435c5615e 100644 --- a/include/net/act_api.h +++ b/include/net/act_api.h @@ -241,7 +241,7 @@ static inline void tcf_action_update_bstats(struct tc_action *a, static inline void tcf_action_inc_drop_qstats(struct tc_action *a) { if (likely(a->cpu_qstats)) { - qstats_drop_inc(this_cpu_ptr(a->cpu_qstats)); + qstats_cpu_drop_inc(a->cpu_qstats); return; } atomic_inc(&a->tcfa_drops); diff --git a/include/net/sch_generic.h b/include/net/sch_generic.h index cbfe9ed435fd..ccfabfac674e 100644 --- a/include/net/sch_generic.h +++ b/include/net/sch_generic.h @@ -996,12 +996,17 @@ static inline void qdisc_qstats_cpu_requeues_inc(struct Qdisc *sch) static inline void __qdisc_qstats_drop(struct Qdisc *sch, int count) { - sch->qstats.drops += count; + WRITE_ONCE(sch->qstats.drops, sch->qstats.drops + count); } static inline void qstats_drop_inc(struct gnet_stats_queue *qstats) { - qstats->drops++; + WRITE_ONCE(qstats->drops, qstats->drops + 1); +} + +static inline void qstats_cpu_drop_inc(struct gnet_stats_queue __percpu *qstats) +{ + this_cpu_inc(qstats->drops); } static inline void qstats_cpu_overlimit_inc(struct gnet_stats_queue __percpu *qstats) diff --git a/net/sched/act_api.c b/net/sched/act_api.c index 332fd9695e54..551992683d9e 100644 --- a/net/sched/act_api.c +++ b/net/sched/act_api.c @@ -1578,7 +1578,7 @@ void tcf_action_update_stats(struct tc_action *a, u64 bytes, u64 packets, if (a->cpu_bstats) { _bstats_update(this_cpu_ptr(a->cpu_bstats), bytes, packets); - this_cpu_ptr(a->cpu_qstats)->drops += drops; + this_cpu_add(a->cpu_qstats->drops, drops); if (hw) _bstats_update(this_cpu_ptr(a->cpu_bstats_hw), diff --git a/net/sched/act_bpf.c b/net/sched/act_bpf.c index c2b5bc19e091..58a074651176 100644 --- a/net/sched/act_bpf.c +++ b/net/sched/act_bpf.c @@ -76,7 +76,7 @@ TC_INDIRECT_SCOPE int tcf_bpf_act(struct sk_buff *skb, break; case TC_ACT_SHOT: action = filter_res; - qstats_drop_inc(this_cpu_ptr(prog->common.cpu_qstats)); + qstats_cpu_drop_inc(prog->common.cpu_qstats); break; case TC_ACT_UNSPEC: action = prog->tcf_action; diff --git a/net/sched/act_ife.c b/net/sched/act_ife.c index e1b825e14900..065228026c58 100644 --- a/net/sched/act_ife.c +++ b/net/sched/act_ife.c @@ -727,7 +727,7 @@ static int tcf_ife_decode(struct sk_buff *skb, const struct tc_action *a, tlv_data = ife_decode(skb, &metalen); if (unlikely(!tlv_data)) { - qstats_drop_inc(this_cpu_ptr(ife->common.cpu_qstats)); + qstats_cpu_drop_inc(ife->common.cpu_qstats); return TC_ACT_SHOT; } @@ -740,7 +740,7 @@ static int tcf_ife_decode(struct sk_buff *skb, const struct tc_action *a, curr_data = ife_tlv_meta_decode(tlv_data, ifehdr_end, &mtype, &dlen, NULL); if (!curr_data) { - qstats_drop_inc(this_cpu_ptr(ife->common.cpu_qstats)); + qstats_cpu_drop_inc(ife->common.cpu_qstats); return TC_ACT_SHOT; } @@ -755,7 +755,7 @@ static int tcf_ife_decode(struct sk_buff *skb, const struct tc_action *a, } if (WARN_ON(tlv_data != ifehdr_end)) { - qstats_drop_inc(this_cpu_ptr(ife->common.cpu_qstats)); + qstats_cpu_drop_inc(ife->common.cpu_qstats); return TC_ACT_SHOT; } @@ -821,7 +821,7 @@ static int tcf_ife_encode(struct sk_buff *skb, const struct tc_action *a, * so lets be conservative.. */ if ((action == TC_ACT_SHOT) || exceed_mtu) { drop: - qstats_drop_inc(this_cpu_ptr(ife->common.cpu_qstats)); + qstats_cpu_drop_inc(ife->common.cpu_qstats); return TC_ACT_SHOT; } diff --git a/net/sched/act_mpls.c b/net/sched/act_mpls.c index 1abfaf9d99f1..4ea8b2e08c3a 100644 --- a/net/sched/act_mpls.c +++ b/net/sched/act_mpls.c @@ -123,7 +123,7 @@ TC_INDIRECT_SCOPE int tcf_mpls_act(struct sk_buff *skb, return p->action; drop: - qstats_drop_inc(this_cpu_ptr(m->common.cpu_qstats)); + qstats_cpu_drop_inc(m->common.cpu_qstats); return TC_ACT_SHOT; } diff --git a/net/sched/act_police.c b/net/sched/act_police.c index 8060f43e4d11..b16468a98c55 100644 --- a/net/sched/act_police.c +++ b/net/sched/act_police.c @@ -310,7 +310,7 @@ inc_overlimits: qstats_cpu_overlimit_inc(police->common.cpu_qstats); inc_drops: if (ret == TC_ACT_SHOT) - qstats_drop_inc(this_cpu_ptr(police->common.cpu_qstats)); + qstats_cpu_drop_inc(police->common.cpu_qstats); end: return ret; } diff --git a/net/sched/act_skbedit.c b/net/sched/act_skbedit.c index a778cdba9258..bfec6b668410 100644 --- a/net/sched/act_skbedit.c +++ b/net/sched/act_skbedit.c @@ -86,7 +86,7 @@ TC_INDIRECT_SCOPE int tcf_skbedit_act(struct sk_buff *skb, return params->action; err: - qstats_drop_inc(this_cpu_ptr(d->common.cpu_qstats)); + qstats_cpu_drop_inc(d->common.cpu_qstats); return TC_ACT_SHOT; } diff --git a/net/sched/sch_cake.c b/net/sched/sch_cake.c index 13c6d1869a14..c779e72f153c 100644 --- a/net/sched/sch_cake.c +++ b/net/sched/sch_cake.c @@ -1845,7 +1845,7 @@ static s32 cake_enqueue(struct sk_buff *skb, struct Qdisc *sch, if (ack) { WRITE_ONCE(b->ack_drops, b->ack_drops + 1); - sch->qstats.drops++; + qdisc_qstats_drop(sch); ack_pkt_len = qdisc_pkt_len(ack); WRITE_ONCE(b->bytes, b->bytes + ack_pkt_len); q->buffer_used += skb->truesize - ack->truesize; diff --git a/net/sched/sch_fq_codel.c b/net/sched/sch_fq_codel.c index 0664b2f2d6f2..8ba722faf7e3 100644 --- a/net/sched/sch_fq_codel.c +++ b/net/sched/sch_fq_codel.c @@ -176,7 +176,7 @@ static unsigned int fq_codel_drop(struct Qdisc *sch, unsigned int max_packets, flow->cvars.count += i; q->backlogs[idx] -= len; q->memory_usage -= mem; - sch->qstats.drops += i; + __qdisc_qstats_drop(sch, i); sch->qstats.backlog -= len; sch->q.qlen -= i; return idx; diff --git a/net/sched/sch_gred.c b/net/sched/sch_gred.c index 36d0cafac206..8ae65572162c 100644 --- a/net/sched/sch_gred.c +++ b/net/sched/sch_gred.c @@ -389,7 +389,7 @@ static int gred_offload_dump_stats(struct Qdisc *sch) packets += u64_stats_read(&hw_stats->stats.bstats[i].packets); sch->qstats.qlen += hw_stats->stats.qstats[i].qlen; sch->qstats.backlog += hw_stats->stats.qstats[i].backlog; - sch->qstats.drops += hw_stats->stats.qstats[i].drops; + __qdisc_qstats_drop(sch, hw_stats->stats.qstats[i].drops); sch->qstats.requeues += hw_stats->stats.qstats[i].requeues; sch->qstats.overlimits += hw_stats->stats.qstats[i].overlimits; } -- cgit v1.2.3 From 726fa7da2d8c9c021eefad178097448b0356a284 Mon Sep 17 00:00:00 2001 From: Ujjal Roy Date: Sat, 2 May 2026 13:19:02 +0000 Subject: ipv4: igmp: get rid of IGMPV3_{QQIC,MRC} and simplify calculation Get rid of the IGMPV3_MRC macro and use the igmpv3_mrt() API to calculate the Max Resp Time from the Maximum Response Code. Similarly, for IGMPV3_QQIC, use the igmpv3_qqi() API to calculate the Querier's Query Interval from the QQIC field. Reviewed-by: Nikolay Aleksandrov Reviewed-by: Ido Schimmel Signed-off-by: Ujjal Roy Link: https://patch.msgid.link/20260502131907.987-2-royujjal@gmail.com Signed-off-by: Jakub Kicinski --- include/linux/igmp.h | 80 ++++++++++++++++++++++++++++++++++++++++++----- net/bridge/br_multicast.c | 2 +- net/ipv4/igmp.c | 6 ++-- 3 files changed, 76 insertions(+), 12 deletions(-) (limited to 'include') diff --git a/include/linux/igmp.h b/include/linux/igmp.h index 073b30a9b850..0e095c9f234e 100644 --- a/include/linux/igmp.h +++ b/include/linux/igmp.h @@ -92,15 +92,79 @@ struct ip_mc_list { struct rcu_head rcu; }; +/* RFC3376, relevant sections: + * - 4.1.1. Maximum Response Code + * - 4.1.7. QQIC (Querier's Query Interval Code) + * + * For both MRC and QQIC, values >= 128 use the same floating-point + * encoding as follows: + * + * 0 1 2 3 4 5 6 7 + * +-+-+-+-+-+-+-+-+ + * |1| exp | mant | + * +-+-+-+-+-+-+-+-+ + */ +#define IGMPV3_FP_EXP(value) (((value) >> 4) & 0x07) +#define IGMPV3_FP_MAN(value) ((value) & 0x0f) + +/* IGMPv3 floating-point exponential field min threshold */ +#define IGMPV3_EXP_MIN_THRESHOLD 128 + /* V3 exponential field decoding */ -#define IGMPV3_MASK(value, nb) ((nb)>=32 ? (value) : ((1<<(nb))-1) & (value)) -#define IGMPV3_EXP(thresh, nbmant, nbexp, value) \ - ((value) < (thresh) ? (value) : \ - ((IGMPV3_MASK(value, nbmant) | (1<<(nbmant))) << \ - (IGMPV3_MASK((value) >> (nbmant), nbexp) + (nbexp)))) - -#define IGMPV3_QQIC(value) IGMPV3_EXP(0x80, 4, 3, value) -#define IGMPV3_MRC(value) IGMPV3_EXP(0x80, 4, 3, value) + +/* IGMPv3 MRC/QQIC 8-bit exponential field decode + * + * RFC3376, 4.1.1 & 4.1.7. defines the decoding formula: + * 0 1 2 3 4 5 6 7 + * +-+-+-+-+-+-+-+-+ + * |1| exp | mant | + * +-+-+-+-+-+-+-+-+ + * Max Resp Time = (mant | 0x10) << (exp + 3) + * QQI = (mant | 0x10) << (exp + 3) + */ +static inline unsigned long igmpv3_exp_field_decode(const u8 code) +{ + if (code < IGMPV3_EXP_MIN_THRESHOLD) { + return code; + } else { + unsigned long mc_man, mc_exp; + + mc_exp = IGMPV3_FP_EXP(code); + mc_man = IGMPV3_FP_MAN(code); + + return (mc_man | 0x10) << (mc_exp + 3); + } +} + +/* Calculate Max Resp Time from Maximum Response Code + * + * RFC3376, relevant sections: + * - 4.1.1. Maximum Response Code + * - 8.3. Query Response Interval + * + * After decode, MRC represents the Maximum Response Time (MRT) in + * units of 0.1 seconds (100 ms). + */ +static inline unsigned long igmpv3_mrt(const struct igmpv3_query *ih3) +{ + return igmpv3_exp_field_decode(ih3->code); +} + +/* Calculate Querier's Query Interval from Querier's Query Interval Code + * + * RFC3376, relevant sections: + * - 4.1.7. QQIC (Querier's Query Interval Code) + * - 8.2. Query Interval + * - 8.12. Older Version Querier Present Timeout + * (the [Query Interval] in the last Query received) + * + * After decode, QQIC represents the Querier's Query Interval in units + * of seconds. + */ +static inline unsigned long igmpv3_qqi(const struct igmpv3_query *ih3) +{ + return igmpv3_exp_field_decode(ih3->qqic); +} static inline int ip_mc_may_pull(struct sk_buff *skb, unsigned int len) { diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c index 881d866d687a..9fec76e887bc 100644 --- a/net/bridge/br_multicast.c +++ b/net/bridge/br_multicast.c @@ -3518,7 +3518,7 @@ static void br_ip4_multicast_query(struct net_bridge_mcast *brmctx, goto out; max_delay = ih3->code ? - IGMPV3_MRC(ih3->code) * (HZ / IGMP_TIMER_SCALE) : 1; + igmpv3_mrt(ih3) * (HZ / IGMP_TIMER_SCALE) : 1; } else { goto out; } diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c index a674fb44ec25..d7eff36d98c3 100644 --- a/net/ipv4/igmp.c +++ b/net/ipv4/igmp.c @@ -991,7 +991,7 @@ static bool igmp_heard_query(struct in_device *in_dev, struct sk_buff *skb, * different encoding. We use the v3 encoding as more likely * to be intended in a v3 query. */ - max_delay = IGMPV3_MRC(ih3->code)*(HZ/IGMP_TIMER_SCALE); + max_delay = igmpv3_mrt(ih3) * (HZ / IGMP_TIMER_SCALE); if (!max_delay) max_delay = 1; /* can't mod w/ 0 */ } else { /* v3 */ @@ -1006,7 +1006,7 @@ static bool igmp_heard_query(struct in_device *in_dev, struct sk_buff *skb, ih3 = igmpv3_query_hdr(skb); } - max_delay = IGMPV3_MRC(ih3->code)*(HZ/IGMP_TIMER_SCALE); + max_delay = igmpv3_mrt(ih3) * (HZ / IGMP_TIMER_SCALE); if (!max_delay) max_delay = 1; /* can't mod w/ 0 */ WRITE_ONCE(in_dev->mr_maxdelay, max_delay); @@ -1016,7 +1016,7 @@ static bool igmp_heard_query(struct in_device *in_dev, struct sk_buff *skb, * configured value. */ in_dev->mr_qrv = ih3->qrv ?: READ_ONCE(net->ipv4.sysctl_igmp_qrv); - in_dev->mr_qi = IGMPV3_QQIC(ih3->qqic)*HZ ?: IGMP_QUERY_INTERVAL; + in_dev->mr_qi = igmpv3_qqi(ih3) * HZ ? : IGMP_QUERY_INTERVAL; /* RFC3376, 8.3. Query Response Interval: * The number of seconds represented by the [Query Response -- cgit v1.2.3 From 12cfb4ecc471652890cae362b8609d9f71b46ee9 Mon Sep 17 00:00:00 2001 From: Ujjal Roy Date: Sat, 2 May 2026 13:19:03 +0000 Subject: ipv6: mld: rename mldv2_mrc() and add mldv2_qqi() Rename mldv2_mrc() to mldv2_mrd() as it is used to calculate the Maximum Response Delay from the Maximum Response Code. Introduce a new API mldv2_qqi() to define the existing calculation logic of QQI from QQIC. This also organizes the existing mld_update_qi() API. Reviewed-by: Nikolay Aleksandrov Reviewed-by: Ido Schimmel Signed-off-by: Ujjal Roy Link: https://patch.msgid.link/20260502131907.987-3-royujjal@gmail.com Signed-off-by: Jakub Kicinski --- include/net/mld.h | 66 ++++++++++++++++++++++++++++++++++++++++------- net/bridge/br_multicast.c | 2 +- net/ipv6/mcast.c | 19 +++----------- 3 files changed, 61 insertions(+), 26 deletions(-) (limited to 'include') diff --git a/include/net/mld.h b/include/net/mld.h index c07359808493..10e402e0fe08 100644 --- a/include/net/mld.h +++ b/include/net/mld.h @@ -89,29 +89,77 @@ struct mld2_query { #define MLDV2_QQIC_EXP(value) (((value) >> 4) & 0x07) #define MLDV2_QQIC_MAN(value) ((value) & 0x0f) -#define MLD_EXP_MIN_LIMIT 32768UL -#define MLDV1_MRD_MAX_COMPAT (MLD_EXP_MIN_LIMIT - 1) +/* MLDv2 QQIC floating-point exponential field min threshold */ +#define MLD_QQIC_MIN_THRESHOLD 128 +/* MLDv2 MRC floating-point exponential field min threshold */ +#define MLD_MRC_MIN_THRESHOLD 32768UL +#define MLDV1_MRD_MAX_COMPAT (MLD_MRC_MIN_THRESHOLD - 1) #define MLD_MAX_QUEUE 8 #define MLD_MAX_SKBS 32 -static inline unsigned long mldv2_mrc(const struct mld2_query *mlh2) +/* V2 exponential field decoding */ + +/* Calculate Maximum Response Delay from Maximum Response Code + * + * RFC3810, relevant sections: + * - 5.1.3. Maximum Response Code defines the decoding formula: + * 0 1 2 3 4 5 6 7 8 9 A B C D E F + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |1| exp | mant | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * Maximum Response Delay = (mant | 0x1000) << (exp+3) + * - 9.3. Query Response Interval + * + * After decode, MRC represents the Maximum Response Delay (MRD) in + * units of milliseconds. + */ +static inline unsigned long mldv2_mrd(const struct mld2_query *mlh2) { - /* RFC3810, 5.1.3. Maximum Response Code */ - unsigned long ret, mc_mrc = ntohs(mlh2->mld2q_mrc); + unsigned long mc_mrc = ntohs(mlh2->mld2q_mrc); - if (mc_mrc < MLD_EXP_MIN_LIMIT) { - ret = mc_mrc; + if (mc_mrc < MLD_MRC_MIN_THRESHOLD) { + return mc_mrc; } else { unsigned long mc_man, mc_exp; mc_exp = MLDV2_MRC_EXP(mc_mrc); mc_man = MLDV2_MRC_MAN(mc_mrc); - ret = (mc_man | 0x1000) << (mc_exp + 3); + return (mc_man | 0x1000) << (mc_exp + 3); } +} - return ret; +/* Calculate Querier's Query Interval from Querier's Query Interval Code + * + * RFC3810, relevant sections: + * - 5.1.9. QQIC (Querier's Query Interval Code) defines the decoding formula: + * 0 1 2 3 4 5 6 7 + * +-+-+-+-+-+-+-+-+ + * |1| exp | mant | + * +-+-+-+-+-+-+-+-+ + * QQI = (mant | 0x10) << (exp + 3) + * - 9.2. Query Interval + * - 9.12. Older Version Querier Present Timeout + * (the [Query Interval] in the last Query received) + * + * After decode, QQIC represents the Querier's Query Interval in units + * of seconds. + */ +static inline unsigned long mldv2_qqi(const struct mld2_query *mlh2) +{ + unsigned long qqic = mlh2->mld2q_qqic; + + if (qqic < MLD_QQIC_MIN_THRESHOLD) { + return qqic; + } else { + unsigned long mc_man, mc_exp; + + mc_exp = MLDV2_QQIC_EXP(qqic); + mc_man = MLDV2_QQIC_MAN(qqic); + + return (mc_man | 0x10) << (mc_exp + 3); + } } #endif diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c index 9fec76e887bc..1438c023db62 100644 --- a/net/bridge/br_multicast.c +++ b/net/bridge/br_multicast.c @@ -3606,7 +3606,7 @@ static int br_ip6_multicast_query(struct net_bridge_mcast *brmctx, mld2q->mld2q_suppress) goto out; - max_delay = max(msecs_to_jiffies(mldv2_mrc(mld2q)), 1UL); + max_delay = max(msecs_to_jiffies(mldv2_mrd(mld2q)), 1UL); } is_general_query = group && ipv6_addr_any(group); diff --git a/net/ipv6/mcast.c b/net/ipv6/mcast.c index 3330adcf26db..6ddc18ac59b9 100644 --- a/net/ipv6/mcast.c +++ b/net/ipv6/mcast.c @@ -1315,20 +1315,7 @@ static void mld_update_qi(struct inet6_dev *idev, * - 9.12. Older Version Querier Present Timeout * (the [Query Interval] in the last Query received) */ - unsigned long mc_qqi; - - if (mlh2->mld2q_qqic < 128) { - mc_qqi = mlh2->mld2q_qqic; - } else { - unsigned long mc_man, mc_exp; - - mc_exp = MLDV2_QQIC_EXP(mlh2->mld2q_qqic); - mc_man = MLDV2_QQIC_MAN(mlh2->mld2q_qqic); - - mc_qqi = (mc_man | 0x10) << (mc_exp + 3); - } - - idev->mc_qi = mc_qqi * HZ; + idev->mc_qi = mldv2_qqi(mlh2) * HZ; } static void mld_update_qri(struct inet6_dev *idev, @@ -1338,7 +1325,7 @@ static void mld_update_qri(struct inet6_dev *idev, * - 5.1.3. Maximum Response Code * - 9.3. Query Response Interval */ - idev->mc_qri = msecs_to_jiffies(mldv2_mrc(mlh2)); + idev->mc_qri = msecs_to_jiffies(mldv2_mrd(mlh2)); } static int mld_process_v1(struct inet6_dev *idev, struct mld_msg *mld, @@ -1390,7 +1377,7 @@ static int mld_process_v1(struct inet6_dev *idev, struct mld_msg *mld, static void mld_process_v2(struct inet6_dev *idev, struct mld2_query *mld, unsigned long *max_delay) { - *max_delay = max(msecs_to_jiffies(mldv2_mrc(mld)), 1UL); + *max_delay = max(msecs_to_jiffies(mldv2_mrd(mld)), 1UL); mld_update_qrv(idev, mld); mld_update_qi(idev, mld); -- cgit v1.2.3 From 95bfd196f0dc6ccf8ef02356cf7088778b84416b Mon Sep 17 00:00:00 2001 From: Ujjal Roy Date: Sat, 2 May 2026 13:19:04 +0000 Subject: ipv4: igmp: encode multicast exponential fields In IGMP, MRC and QQIC fields are not correctly encoded when generating query packets. Since the receiver of the query interprets these fields using the IGMPv3 floating- point decoding logic, any value that exceeds the linear threshold is incorrectly parsed as an exponential value, leading to an incorrect interval calculation. Encode and assign the corresponding protocol fields during query generation. Introduce the logic to dynamically calculate the exponent and mantissa using bit-scan (fls). This ensures MRC and QQIC fields (8-bit) are properly encoded when transmitting query packets with intervals that exceed their respective linear threshold value of 128 (for MRT/QQI). RFC3376: for both MRC and QQIC, values >= 128 represent the same floating-point encoding as follows: 0 1 2 3 4 5 6 7 +-+-+-+-+-+-+-+-+ |1| exp | mant | +-+-+-+-+-+-+-+-+ Reviewed-by: Nikolay Aleksandrov Reviewed-by: Ido Schimmel Signed-off-by: Ujjal Roy Link: https://patch.msgid.link/20260502131907.987-4-royujjal@gmail.com Signed-off-by: Jakub Kicinski --- include/linux/igmp.h | 87 +++++++++++++++++++++++++++++++++++++++++++++++ net/bridge/br_multicast.c | 14 ++++---- 2 files changed, 93 insertions(+), 8 deletions(-) (limited to 'include') diff --git a/include/linux/igmp.h b/include/linux/igmp.h index 0e095c9f234e..3a2d35a9f307 100644 --- a/include/linux/igmp.h +++ b/include/linux/igmp.h @@ -109,6 +109,93 @@ struct ip_mc_list { /* IGMPv3 floating-point exponential field min threshold */ #define IGMPV3_EXP_MIN_THRESHOLD 128 +/* IGMPv3 FP max threshold (mant = 0xF, exp = 7) -> 31744 */ +#define IGMPV3_EXP_MAX_THRESHOLD 31744 + +/* V3 exponential field encoding */ + +/* IGMPv3 MRC/QQIC 8-bit exponential field encode + * + * RFC3376, 4.1.1 & 4.1.7. defines only the decoding formula: + * MRT/QQI = (mant | 0x10) << (exp + 3) + * + * but does NOT define the encoding procedure. To derive exponent: + * + * For any value of mantissa and exponent, the decoding formula + * indicates that the "hidden bit" (0x10) is shifted 4 bits left + * to sit above the 4-bit mantissa. The RFC again shifts this + * entire block left by (exp + 3) to reconstruct the value. + * So, 'hidden bit' is the MSB which is shifted by (4 + exp + 3). + * + * Total left shift of the 'hidden bit' = 4 + (exp + 3) = exp + 7. + * This is the MSB at the 0-based bit position: (exp + 7). + * Since fls() is 1-based, fls(value) - 1 = exp + 7. + * + * Therefore: + * exp = fls(value) - 8 + * mant = (value >> (exp + 3)) & 0x0F + * + * Final encoding formula: + * 0x80 | (exp << 4) | mant + * + * Example (value = 3200): + * 0 1 + * 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |0 0 0 0 1 1 0 0 1 0 0 0 0 0 0 0| (value = 3200) + * | ^-^-mant^ ^..(exp+3)..^| exp = 4, mant = 9 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * Encoded: + * 0x80 | (4 << 4) | 9 = 0xC9 + */ +static inline u8 igmpv3_exp_field_encode(unsigned long value) +{ + u8 mc_exp, mc_man; + + /* MRC/QQIC < 128 is literal */ + if (value < IGMPV3_EXP_MIN_THRESHOLD) + return value; + + /* Saturate at max representable (mant = 0xF, exp = 7) -> 31744 */ + if (value >= IGMPV3_EXP_MAX_THRESHOLD) + return 0xFF; + + mc_exp = fls(value) - 8; + mc_man = (value >> (mc_exp + 3)) & 0x0F; + + return 0x80 | (mc_exp << 4) | mc_man; +} + +/* Calculate Maximum Response Code from Max Resp Time + * + * RFC3376, relevant sections: + * - 4.1.1. Maximum Response Code + * - 8.3. Query Response Interval + * + * MRC represents the encoded form of Max Resp Time (MRT); once + * decoded, the resulting value is in units of 0.1 seconds (100 ms). + */ +static inline u8 igmpv3_mrc(unsigned long mrt) +{ + return igmpv3_exp_field_encode(mrt); +} + +/* Calculate Querier's Query Interval Code from Querier's Query Interval + * + * RFC3376, relevant sections: + * - 4.1.7. QQIC (Querier's Query Interval Code) + * - 8.2. Query Interval + * - 8.12. Older Version Querier Present Timeout + * (the [Query Interval] in the last Query received) + * + * QQIC represents the encoded form of Querier's Query Interval (QQI); + * once decoded, the resulting value is in units of seconds. + */ +static inline u8 igmpv3_qqic(unsigned long qi) +{ + return igmpv3_exp_field_encode(qi); +} /* V3 exponential field decoding */ diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c index 1438c023db62..27010744d7ae 100644 --- a/net/bridge/br_multicast.c +++ b/net/bridge/br_multicast.c @@ -934,12 +934,12 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge_mcast *brm size_t pkt_size, igmp_hdr_size; unsigned long now = jiffies; struct igmpv3_query *ihv3; + unsigned long lmqt, mrt; void *csum_start = NULL; __sum16 *csum = NULL; struct sk_buff *skb; struct igmphdr *ih; struct ethhdr *eth; - unsigned long lmqt; struct iphdr *iph; u16 lmqt_srcs = 0; @@ -1004,15 +1004,15 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge_mcast *brm skb_put(skb, 24); skb_set_transport_header(skb, skb->len); + mrt = group ? brmctx->multicast_last_member_interval : + brmctx->multicast_query_response_interval; *igmp_type = IGMP_HOST_MEMBERSHIP_QUERY; switch (brmctx->multicast_igmp_version) { case 2: ih = igmp_hdr(skb); ih->type = IGMP_HOST_MEMBERSHIP_QUERY; - ih->code = (group ? brmctx->multicast_last_member_interval : - brmctx->multicast_query_response_interval) / - (HZ / IGMP_TIMER_SCALE); + ih->code = mrt / (HZ / IGMP_TIMER_SCALE); ih->group = group; ih->csum = 0; csum = &ih->csum; @@ -1021,11 +1021,9 @@ static struct sk_buff *br_ip4_multicast_alloc_query(struct net_bridge_mcast *brm case 3: ihv3 = igmpv3_query_hdr(skb); ihv3->type = IGMP_HOST_MEMBERSHIP_QUERY; - ihv3->code = (group ? brmctx->multicast_last_member_interval : - brmctx->multicast_query_response_interval) / - (HZ / IGMP_TIMER_SCALE); + ihv3->code = igmpv3_mrc(mrt / (HZ / IGMP_TIMER_SCALE)); ihv3->group = group; - ihv3->qqic = brmctx->multicast_query_interval / HZ; + ihv3->qqic = igmpv3_qqic(brmctx->multicast_query_interval / HZ); ihv3->nsrcs = htons(lmqt_srcs); ihv3->resv = 0; ihv3->suppress = sflag; -- cgit v1.2.3 From e51560f4220a5c2185dc1abbfc3aa0eb543139c2 Mon Sep 17 00:00:00 2001 From: Ujjal Roy Date: Sat, 2 May 2026 13:19:05 +0000 Subject: ipv6: mld: encode multicast exponential fields In MLD, MRC and QQIC fields are not correctly encoded when generating query packets. Since the receiver of the query interprets these fields using the MLDv2 floating-point decoding logic, any value that exceeds the linear threshold is incorrectly parsed as an exponential value, leading to an incorrect interval calculation. Encode and assign the corresponding protocol fields during query generation. Introduce the logic to dynamically calculate the exponent and mantissa using bit-scan (fls). This ensures MRC (16-bit) and QQIC (8-bit) fields are properly encoded when transmitting query packets with intervals that exceed their respective linear thresholds (32768 for MRD; 128 for QQI). RFC3810: If Maximum Response Code >= 32768, the Maximum Response Code field represents a floating-point value as follows: 0 1 2 3 4 5 6 7 8 9 A B C D E F +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |1| exp | mant | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ RFC3810: If QQIC >= 128, the QQIC field represents a floating-point value as follows: 0 1 2 3 4 5 6 7 +-+-+-+-+-+-+-+-+ |1| exp | mant | +-+-+-+-+-+-+-+-+ Reviewed-by: Nikolay Aleksandrov Reviewed-by: Ido Schimmel Signed-off-by: Ujjal Roy Link: https://patch.msgid.link/20260502131907.987-5-royujjal@gmail.com Signed-off-by: Jakub Kicinski --- include/net/mld.h | 119 ++++++++++++++++++++++++++++++++++++++++++++++ net/bridge/br_multicast.c | 4 +- 2 files changed, 121 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/net/mld.h b/include/net/mld.h index 10e402e0fe08..23255cc47ced 100644 --- a/include/net/mld.h +++ b/include/net/mld.h @@ -91,13 +91,132 @@ struct mld2_query { /* MLDv2 QQIC floating-point exponential field min threshold */ #define MLD_QQIC_MIN_THRESHOLD 128 +/* MLDv2 QQIC FP max threshold (mant = 0xF, exp = 7) -> 31744 */ +#define MLD_QQIC_MAX_THRESHOLD 31744 /* MLDv2 MRC floating-point exponential field min threshold */ #define MLD_MRC_MIN_THRESHOLD 32768UL +/* MLDv2 MRC FP max threshold (mant = 0xFFF, exp = 7) -> 8387584 */ +#define MLD_MRC_MAX_THRESHOLD 8387584 #define MLDV1_MRD_MAX_COMPAT (MLD_MRC_MIN_THRESHOLD - 1) #define MLD_MAX_QUEUE 8 #define MLD_MAX_SKBS 32 +/* V2 exponential field encoding */ + +/* + * Calculate Maximum Response Code from Maximum Response Delay + * + * MRC represents the 16-bit encoded form of Maximum Response Delay (MRD); + * once decoded, the resulting value is in milliseconds. + * + * RFC3810, 5.1.3. defines only the decoding formula: + * Maximum Response Delay = (mant | 0x1000) << (exp + 3) + * + * but does NOT define the encoding procedure. To derive exponent: + * + * For the 16-bit MRC, the "hidden bit" (0x1000) is left shifted by 12 to + * sit above the 12-bit mantissa. The RFC then shifts this entire block + * left by (exp + 3) to reconstruct the value. So, 'hidden bit' is the + * MSB which is shifted by (12 + exp + 3). + * + * Total left shift of the hidden bit = 12 + (exp + 3) = exp + 15. + * This is the MSB at the 0-based bit position: (exp + 15). + * Since fls() is 1-based, fls(value) - 1 = exp + 15. + * + * Therefore: + * exp = fls(value) - 16 + * mant = (value >> (exp + 3)) & 0x0FFF + * + * Final encoding formula: + * 0x8000 | (exp << 12) | mant + * + * Example (value = 1311744): + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0| 1311744 + * | ^-^--------mant---------^ ^...(exp+3)...^| exp=5 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * Encoded: + * 0x8000 | (5 << 12) | 0x404 = 0xD404 + */ +static inline u16 mldv2_mrc(unsigned long mrd) +{ + u16 mc_man, mc_exp; + + /* MRC < 32768 is literal */ + if (mrd < MLD_MRC_MIN_THRESHOLD) + return mrd; + + /* Saturate at max representable (mant = 0xFFF, exp = 7) -> 8387584 */ + if (mrd >= MLD_MRC_MAX_THRESHOLD) + return 0xFFFF; + + mc_exp = fls(mrd) - 16; + mc_man = (mrd >> (mc_exp + 3)) & 0x0FFF; + + return 0x8000 | (mc_exp << 12) | mc_man; +} + +/* + * Calculate Querier's Query Interval Code from Querier's Query Interval + * + * QQIC represents the 8-bit encoded form of Querier's Query Interval (QQI); + * once decoded, the resulting value is in seconds. + * + * RFC3810, 5.1.9. defines only the decoding formula: + * QQI = (mant | 0x10) << (exp + 3) + * + * but does NOT define the encoding procedure. To derive exponent: + * + * For any value of mantissa and exponent, the decoding formula indicates + * that the "hidden bit" (0x10) is shifted 4 bits left to sit above the + * 4-bit mantissa. The RFC again shifts this entire block left by (exp + 3) + * to reconstruct the value. So, 'hidden bit' is the MSB which is shifted + * by (4 + exp + 3). + * + * Total left shift of the 'hidden bit' = 4 + (exp + 3) = exp + 7. + * This is the MSB at the 0-based bit position: (exp + 7). + * Since fls() is 1-based, fls(value) - 1 = exp + 7. + * + * Therefore: + * exp = fls(value) - 8 + * mant = (value >> (exp + 3)) & 0x0F + * + * Final encoding formula: + * 0x80 | (exp << 4) | mant + * + * Example (value = 3200): + * 0 1 + * 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |0 0 0 0 1 1 0 0 1 0 0 0 0 0 0 0| (value = 3200) + * | ^-^-mant^ ^..(exp+3)..^| exp = 4, mant = 9 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * Encoded: + * 0x80 | (4 << 4) | 9 = 0xC9 + */ +static inline u8 mldv2_qqic(unsigned long value) +{ + u8 mc_man, mc_exp; + + /* QQIC < 128 is literal */ + if (value < MLD_QQIC_MIN_THRESHOLD) + return value; + + /* Saturate at max representable (mant = 0xF, exp = 7) -> 31744 */ + if (value >= MLD_QQIC_MAX_THRESHOLD) + return 0xFF; + + mc_exp = fls(value) - 8; + mc_man = (value >> (mc_exp + 3)) & 0x0F; + + return 0x80 | (mc_exp << 4) | mc_man; +} + /* V2 exponential field decoding */ /* Calculate Maximum Response Delay from Maximum Response Code diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c index 27010744d7ae..49ceea3ff974 100644 --- a/net/bridge/br_multicast.c +++ b/net/bridge/br_multicast.c @@ -1181,7 +1181,7 @@ static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge_mcast *brm break; case 2: mld2q = (struct mld2_query *)icmp6_hdr(skb); - mld2q->mld2q_mrc = htons((u16)jiffies_to_msecs(interval)); + mld2q->mld2q_mrc = htons(mldv2_mrc(jiffies_to_msecs(interval))); mld2q->mld2q_type = ICMPV6_MGM_QUERY; mld2q->mld2q_code = 0; mld2q->mld2q_cksum = 0; @@ -1190,7 +1190,7 @@ static struct sk_buff *br_ip6_multicast_alloc_query(struct net_bridge_mcast *brm mld2q->mld2q_suppress = sflag; mld2q->mld2q_qrv = 2; mld2q->mld2q_nsrcs = htons(llqt_srcs); - mld2q->mld2q_qqic = brmctx->multicast_query_interval / HZ; + mld2q->mld2q_qqic = mldv2_qqic(brmctx->multicast_query_interval / HZ); mld2q->mld2q_mca = *group; csum = &mld2q->mld2q_cksum; csum_start = (void *)mld2q; -- cgit v1.2.3 From 0814869dc26e7f82b31683fdfc5d43dcf3331f9f Mon Sep 17 00:00:00 2001 From: Israel Kozitz Date: Mon, 4 May 2026 12:17:40 +0300 Subject: wifi: cfg80211: fix max_channel_switch_time documentation unit The max_channel_switch_time field in struct wiphy_nan_capa was documented as being in milliseconds, but it is actually in microseconds as defined in the Wi-Fi Aware specification and as indicated in the nl80211 NL80211_NAN_CAPA_MAX_CHANNEL_SWITCH_TIME attribute. Fix the documentation to say microseconds. Signed-off-by: Israel Kozitz Reviewed-by: Ilan Peer Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260504121734.d0a0152f7d8c.I47e5c91f7ead4f8006fb13f9194d95a55cf9c398@changeid Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include') diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index a40ab36b8edb..51eded4204cf 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -6027,7 +6027,7 @@ enum wiphy_nan_flags { * nibble indicates the number of TX antennas and upper nibble indicates the * number of RX antennas. Value 0 indicates the information is not * available. - * @max_channel_switch_time: maximum channel switch time in milliseconds. + * @max_channel_switch_time: maximum channel switch time in microseconds. * @dev_capabilities: NAN device capabilities as defined in Wi-Fi Aware (TM) * specification Table 79 (Capabilities field). * @phy: Band-agnostic capabilities for NAN data interfaces. Since NAN -- cgit v1.2.3 From 829b815e910b8cc7bf36c85005abc3e66b59303b Mon Sep 17 00:00:00 2001 From: Kavita Kavita Date: Mon, 4 May 2026 18:06:23 +0530 Subject: wifi: cfg80211: indicate (Re)Association frame encryption to userspace In SME-in-driver mode, the driver handles the entire (re)association exchange. Userspace (e.g., wpa_supplicant) currently has no explicit indication of whether the (re)association exchange was encrypted, making it difficult to distinguish EPP (Enhanced Privacy Protection, IEEE 802.11bi) associations from non-EPP associations. When (Re)Association frame encryption is used, the (Re)Association Response frame must contain a Key Delivery element as specified in IEEE P802.11bi/D4.0, Table 9-65. Userspace must process this element only when the (Re)Association Response frame is actually encrypted. Processing it unconditionally for unencrypted frames leads to incorrect behavior. Without an explicit indication from the driver, userspace cannot determine whether encryption was used and whether the Key Delivery element is valid. Add a new flag attribute NL80211_ATTR_ASSOC_ENCRYPTED and a corresponding field "assoc_encrypted" in cfg80211_connect_resp_params to indicate that both the (Re)Association Request and Response frames are transmitted encrypted over the air. For mac80211-based drivers, extend cfg80211_rx_assoc_resp_data with the assoc_encrypted field as well, which is then propagated to cfg80211_connect_resp_params. Pass the flag to userspace via NL80211_CMD_CONNECT event. Signed-off-by: Kavita Kavita Link: https://patch.msgid.link/20260504123624.529218-2-kavita.kavita@oss.qualcomm.com Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 6 ++++++ include/uapi/linux/nl80211.h | 9 +++++++++ net/wireless/mlme.c | 1 + net/wireless/nl80211.c | 4 +++- net/wireless/sme.c | 1 + 5 files changed, 20 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 51eded4204cf..b0a908b2ba73 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -8305,6 +8305,7 @@ void cfg80211_auth_timeout(struct net_device *dev, const u8 *addr); * as the AC bitmap in the QoS info field * @req_ies: information elements from the (Re)Association Request frame * @req_ies_len: length of req_ies data + * @assoc_encrypted: indicate if the (re)association exchange is encrypted. * @ap_mld_addr: AP MLD address (in case of MLO) * @links: per-link information indexed by link ID, use links[0] for * non-MLO connections @@ -8319,6 +8320,7 @@ struct cfg80211_rx_assoc_resp_data { const u8 *req_ies; size_t req_ies_len; int uapsd_queues; + bool assoc_encrypted; const u8 *ap_mld_addr; struct { u8 addr[ETH_ALEN] __aligned(2); @@ -8838,6 +8840,9 @@ struct cfg80211_fils_resp_params { * @links.status: per-link status code, to report a status code that's not * %WLAN_STATUS_SUCCESS for a given link, it must also be in the * @valid_links bitmap and may have a BSS pointer (which is then released) + * @assoc_encrypted: The driver should set this flag to indicate that the + * (Re)Association Request/Response frames are transmitted encrypted over + * the air. */ struct cfg80211_connect_resp_params { int status; @@ -8847,6 +8852,7 @@ struct cfg80211_connect_resp_params { size_t resp_ie_len; struct cfg80211_fils_resp_params fils; enum nl80211_timeout_reason timeout_reason; + bool assoc_encrypted; const u8 *ap_mld_addr; u16 valid_links; diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 072b383d7d3c..e26d65c1b737 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -3142,6 +3142,13 @@ enum nl80211_commands { * association response etc., since it's abridged in the beacon. Used * for START_AP etc. * + * @NL80211_ATTR_ASSOC_ENCRYPTED: Flag attribute, used only with the + * %NL80211_CMD_CONNECT event in SME-in-driver mode. The driver should + * set this flag to indicate that both the (Re)Association Request frame + * and the corresponding (Re)Association Response frame are transmitted + * encrypted over the air. Enhanced Privacy Protection (EPP), as defined + * in IEEE P802.11bi/D4.0, mandates this encryption. + * * @NUM_NL80211_ATTR: total number of nl80211_attrs available * @NL80211_ATTR_MAX: highest attribute number currently defined * @__NL80211_ATTR_AFTER_LAST: internal use @@ -3735,6 +3742,8 @@ enum nl80211_attrs { NL80211_ATTR_NAN_MAX_CHAN_SWITCH_TIME, NL80211_ATTR_NAN_PEER_MAPS, + NL80211_ATTR_ASSOC_ENCRYPTED, + /* add attributes here, update the policy in nl80211.c */ __NL80211_ATTR_AFTER_LAST, diff --git a/net/wireless/mlme.c b/net/wireless/mlme.c index bd72317c4964..d196b5c086cc 100644 --- a/net/wireless/mlme.c +++ b/net/wireless/mlme.c @@ -38,6 +38,7 @@ void cfg80211_rx_assoc_resp(struct net_device *dev, u.assoc_resp.variable), .status = le16_to_cpu(mgmt->u.assoc_resp.status_code), .ap_mld_addr = data->ap_mld_addr, + .assoc_encrypted = data->assoc_encrypted, }; unsigned int link_id; diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index cf236307cca9..b96f2f7f67d2 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -20660,7 +20660,9 @@ void nl80211_send_connect_result(struct cfg80211_registered_device *rdev, (cr->fils.pmk && nla_put(msg, NL80211_ATTR_PMK, cr->fils.pmk_len, cr->fils.pmk)) || (cr->fils.pmkid && - nla_put(msg, NL80211_ATTR_PMKID, WLAN_PMKID_LEN, cr->fils.pmkid))))) + nla_put(msg, NL80211_ATTR_PMKID, WLAN_PMKID_LEN, cr->fils.pmkid)))) || + (cr->assoc_encrypted && + nla_put_flag(msg, NL80211_ATTR_ASSOC_ENCRYPTED))) goto nla_put_failure; if (cr->valid_links) { diff --git a/net/wireless/sme.c b/net/wireless/sme.c index 86e2ccaa678c..b451df3096dd 100644 --- a/net/wireless/sme.c +++ b/net/wireless/sme.c @@ -1066,6 +1066,7 @@ void cfg80211_connect_done(struct net_device *dev, } ev->cr.status = params->status; ev->cr.timeout_reason = params->timeout_reason; + ev->cr.assoc_encrypted = params->assoc_encrypted; spin_lock_irqsave(&wdev->event_lock, flags); list_add_tail(&ev->list, &wdev->event_list); -- cgit v1.2.3 From 43ef5856bdab300acc90ea2c3c437f5535a5f66f Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Mon, 4 May 2026 10:20:41 +0300 Subject: wifi: mac80211: track the id of the NAN cluster we joined Currently, we store in nan.conf the cluster id that was configured from upper layer to be used when the device opens a cluster. But after we joined a cluster, the configured cluster id is no longer relevant. Particularly, in reconfig we will give the driver the (possibly) wrong cluster id. Add an API to be called by the driver when joined a cluster in which the cluster id will be updated. Use the locally stored cluster id instead of cfg80211's copy. Ignore cluster id updates from cfg80211 if we already have one configured. Adjust the drivers that use the cfg80211 API (cfg80211_nan_cluster_joined) directly, otherwise we break functionality (i.e. accept frame check won't evaluate to true). Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260504101829.5dada1b756a4.I0f1060215267fd8aef31afd99f8f42e6fde7f234@changeid Signed-off-by: Johannes Berg --- include/net/mac80211.h | 14 ++++++++++++++ net/mac80211/cfg.c | 19 +++++++++++++++++++ net/mac80211/rx.c | 4 ++-- net/mac80211/tx.c | 2 +- 4 files changed, 36 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/include/net/mac80211.h b/include/net/mac80211.h index 02318a4be0e1..0d1b1d726b9c 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -7891,6 +7891,20 @@ void ieee80211_nan_func_match(struct ieee80211_vif *vif, */ void ieee80211_nan_sched_update_done(struct ieee80211_vif *vif); +/** + * ieee80211_nan_cluster_joined - notify about NAN cluster join. + * + * This function is used to notify mac80211 about NAN cluster join. + * + * @vif: &struct ieee80211_vif pointer from the add_interface callback. + * @cluster_id: the cluster ID that was joined + * @new_cluster: true if this is a new cluster + * @gfp: allocation flags + */ +void ieee80211_nan_cluster_joined(struct ieee80211_vif *vif, + const u8 *cluster_id, bool new_cluster, + gfp_t gfp); + /** * ieee80211_calc_rx_airtime - calculate estimated transmission airtime for RX. * diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 00261bd6674b..c71af8878562 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -5215,6 +5215,25 @@ void ieee80211_nan_func_match(struct ieee80211_vif *vif, } EXPORT_SYMBOL(ieee80211_nan_func_match); +void ieee80211_nan_cluster_joined(struct ieee80211_vif *vif, + const u8 *cluster_id, bool new_cluster, + gfp_t gfp) +{ + struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif); + + if (WARN_ON(vif->type != NL80211_IFTYPE_NAN)) + return; + + if (WARN_ON(!sdata->u.nan.started)) + return; + + ether_addr_copy(sdata->u.nan.conf.cluster_id, cluster_id); + + cfg80211_nan_cluster_joined(ieee80211_vif_to_wdev(vif), cluster_id, + new_cluster, gfp); +} +EXPORT_SYMBOL(ieee80211_nan_cluster_joined); + static int ieee80211_set_multicast_to_unicast(struct wiphy *wiphy, struct net_device *dev, const bool enabled) diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c index 3e5d1c47a5b0..82ea7404f3da 100644 --- a/net/mac80211/rx.c +++ b/net/mac80211/rx.c @@ -4629,7 +4629,7 @@ static bool ieee80211_accept_frame(struct ieee80211_rx_data *rx) * action frames or authentication frames that are addressed to * the local NAN interface. */ - return memcmp(sdata->wdev.u.nan.cluster_id, + return memcmp(sdata->u.nan.conf.cluster_id, hdr->addr3, ETH_ALEN) == 0 && (ieee80211_is_public_action(hdr, skb->len) || (ieee80211_is_auth(hdr->frame_control) && @@ -4646,7 +4646,7 @@ static bool ieee80211_accept_frame(struct ieee80211_rx_data *rx) if (!nmi) return false; - if (!ether_addr_equal(nmi->wdev.u.nan.cluster_id, + if (!ether_addr_equal(nmi->u.nan.conf.cluster_id, hdr->addr3)) return false; diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index 8e4876d1c544..1702f816419b 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -2854,7 +2854,7 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata, ret = -ENOTCONN; goto free; } - memcpy(hdr.addr3, nmi->wdev.u.nan.cluster_id, ETH_ALEN); + memcpy(hdr.addr3, nmi->u.nan.conf.cluster_id, ETH_ALEN); hdrlen = 24; break; } -- cgit v1.2.3 From 42c9de58d990337a8848671591252f71d0db18c8 Mon Sep 17 00:00:00 2001 From: Miri Korenblit Date: Mon, 4 May 2026 10:20:47 +0300 Subject: wifi: mac80211: add NAN channel evacuation support A NAN channel can be evacuated, i.e. detached from its chanctx, if all chanctxs are used by NAN and a chanctx is needed for something else. For example if the STA interface needs to perform a channel switch. Implement the evacuation: detach the NAN channel from its chanctx, remove all the peer NAN channels that were using this chanctx, and update the driver. Internally, the NAN channel evacuation will be triggered in the scenario described above, and API is provided for the driver to also trigger it. The driver/device is assumed to publish a ULW to notify the peers about the fact that we won't be present on this NAN channel anymore. Also export this as an API for the drivers: if a driver has other resources per channel, it might want to trigger channel evacuation in order to free up such internal resources for other usages. Reviewed-by: Johannes Berg Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260504101829.1e1dc40d2f3e.I003fe84dc6373bb9ad55abd7824b9fc21c51203f@changeid Signed-off-by: Johannes Berg --- include/net/mac80211.h | 15 ++++++ net/mac80211/chan.c | 28 ++++++++--- net/mac80211/nan.c | 126 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 162 insertions(+), 7 deletions(-) (limited to 'include') diff --git a/include/net/mac80211.h b/include/net/mac80211.h index 0d1b1d726b9c..d909bc1b29ff 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -7905,6 +7905,21 @@ void ieee80211_nan_cluster_joined(struct ieee80211_vif *vif, const u8 *cluster_id, bool new_cluster, gfp_t gfp); +/** + * ieee80211_nan_try_evacuate - try to evacuate a NAN channel + * + * This function tries to evacuate a NAN channel that is using the given + * channel context, to free up channel context resources. + * + * @hw: pointer as obtained from ieee80211_alloc_hw() + * @conf: the channel context configuration to try to evacuate. If %NULL, + * the NAN channel that has the fewest slots scheduled will be evacuated. + * + * Return: %true if a channel was evacuated, %false otherwise + */ +bool ieee80211_nan_try_evacuate(struct ieee80211_hw *hw, + struct ieee80211_chanctx_conf *conf); + /** * ieee80211_calc_rx_airtime - calculate estimated transmission airtime for RX. * diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c index 248531051a4e..9683d3e6e1d2 100644 --- a/net/mac80211/chan.c +++ b/net/mac80211/chan.c @@ -1861,16 +1861,21 @@ static int ieee80211_vif_use_reserved_switch(struct ieee80211_local *local) } if (n_assigned != n_reserved) { - if (n_ready == n_reserved) { - wiphy_info(local->hw.wiphy, - "channel context reservation cannot be finalized because some interfaces aren't switching\n"); - err = -EBUSY; - goto err; - } + if (n_ready != n_reserved) + return -EAGAIN; - return -EAGAIN; + if (n_assigned == n_reserved + 1 && + ieee80211_nan_try_evacuate(&local->hw, + &ctx->replace_ctx->conf)) + goto use_reserved; + + wiphy_info(local->hw.wiphy, + "channel context reservation cannot be finalized because some interfaces aren't switching\n"); + err = -EBUSY; + goto err; } +use_reserved: ctx->conf.radar_enabled = false; for_each_chanctx_user_reserved(local, ctx, &iter) { if (ieee80211_link_has_in_place_reservation(iter.link) && @@ -2178,6 +2183,15 @@ int _ieee80211_link_use_channel(struct ieee80211_link_data *link, ctx = ieee80211_find_or_create_chanctx(sdata, chanreq, mode, assign_on_failure, &reused_ctx); + if (IS_ERR(ctx)) { + /* Try to evacuate a NAN channel to free up a chanctx */ + if (ieee80211_nan_try_evacuate(&local->hw, NULL)) + ctx = ieee80211_find_or_create_chanctx(sdata, chanreq, + mode, + assign_on_failure, + &reused_ctx); + } + if (IS_ERR(ctx)) { ret = PTR_ERR(ctx); goto out; diff --git a/net/mac80211/nan.c b/net/mac80211/nan.c index 4e262b624521..cea620aaee6a 100644 --- a/net/mac80211/nan.c +++ b/net/mac80211/nan.c @@ -334,7 +334,10 @@ int ieee80211_nan_set_local_sched(struct ieee80211_sub_if_data *sdata, sched_idx_to_chan[i] = chan; ieee80211_nan_init_channel(chan, &sched->nan_channels[i]); + } + /* Also a pre-existing channel might have been ULWed, so no chanctx */ + if (!chan->chanctx_conf) { ret = ieee80211_nan_use_chanctx(sdata, chan, false); if (ret) { memset(chan, 0, sizeof(*chan)); @@ -708,3 +711,126 @@ out: ieee80211_nan_free_peer_sched(to_free); return ret; } + +static void +ieee80211_nan_evacuate_channel(struct ieee80211_sub_if_data *sdata, + struct ieee80211_nan_channel *nan_channel) +{ + struct ieee80211_chanctx_conf *conf; + struct ieee80211_chanctx *ctx; + + lockdep_assert_wiphy(sdata->local->hw.wiphy); + + if (WARN_ON(!nan_channel || !nan_channel->chanreq.oper.chan)) + return; + + conf = nan_channel->chanctx_conf; + if (WARN_ON(!conf)) + return; + + nan_channel->chanctx_conf = NULL; + + /* Update all peer channels that reference this chanctx */ + ieee80211_nan_update_peer_channels(sdata, conf); + + drv_vif_cfg_changed(sdata->local, sdata, BSS_CHANGED_NAN_LOCAL_SCHED); + + cfg80211_nan_channel_evac(&sdata->wdev, &nan_channel->chanreq.oper, + GFP_KERNEL); + + /* Update NDI carrier states */ + ieee80211_nan_update_all_ndi_carriers(sdata->local); + + /* Clean up the channel context if no longer used */ + ctx = container_of(conf, struct ieee80211_chanctx, conf); + + if (ieee80211_chanctx_num_assigned(sdata->local, ctx) > 0) { + ieee80211_recalc_chanctx_chantype(sdata->local, ctx); + ieee80211_recalc_smps_chanctx(sdata->local, ctx); + ieee80211_recalc_chanctx_min_def(sdata->local, ctx); + } + + if (ieee80211_chanctx_refcount(sdata->local, ctx) == 0) + ieee80211_free_chanctx(sdata->local, ctx, false); +} + +bool ieee80211_nan_try_evacuate(struct ieee80211_hw *hw, + struct ieee80211_chanctx_conf *conf) +{ + struct ieee80211_sub_if_data *sdata = NULL, *tmp; + struct ieee80211_local *local = hw_to_local(hw); + struct ieee80211_nan_channel *evac_chan = NULL; + struct ieee80211_nan_sched_cfg *sched_cfg; + struct ieee80211_chanctx *ctx = NULL; + int min_slot_count = INT_MAX; + int usable_channels = 0; + + lockdep_assert_wiphy(local->hw.wiphy); + + if (conf) + ctx = container_of(conf, struct ieee80211_chanctx, conf); + + /* Find the NAN interface - there can only be one */ + list_for_each_entry(tmp, &local->interfaces, list) { + if (ieee80211_sdata_running(tmp) && + tmp->vif.type == NL80211_IFTYPE_NAN) { + sdata = tmp; + break; + } + } + + if (!sdata) + return false; + + sched_cfg = &sdata->vif.cfg.nan_sched; + + /* Find the channel to evacuate and count usable channels */ + for (int i = 0; i < IEEE80211_NAN_MAX_CHANNELS; i++) { + struct ieee80211_nan_channel *chan = + &sched_cfg->channels[i]; + struct ieee80211_chanctx *chan_ctx; + int slot_count = 0; + + if (!chan->chanreq.oper.chan || !chan->chanctx_conf) + continue; + + usable_channels++; + + chan_ctx = container_of(chan->chanctx_conf, + struct ieee80211_chanctx, conf); + + /* If ctx specified, only consider that specific chanctx */ + if (ctx) { + if (chan_ctx == ctx) + evac_chan = chan; + continue; + } + + /* Can only evacuate channels whose chanctx is NAN-only */ + if (ieee80211_chanctx_refcount(local, chan_ctx) > 1) + continue; + + /* Count how many time slots use this channel */ + for (int s = 0; s < CFG80211_NAN_SCHED_NUM_TIME_SLOTS; s++) + if (sched_cfg->schedule[s] == chan) + slot_count++; + + if (slot_count < min_slot_count) { + min_slot_count = slot_count; + evac_chan = chan; + } + } + + /* No suitable NAN channel found */ + if (!evac_chan) + return false; + + /* NAN needs at least one remaining usable channel after evacuation */ + if (usable_channels < 2) + return false; + + ieee80211_nan_evacuate_channel(sdata, evac_chan); + + return true; +} +EXPORT_SYMBOL(ieee80211_nan_try_evacuate); -- cgit v1.2.3 From 4dbd1829045ee4146ae16b0e1ad122d855a694cb Mon Sep 17 00:00:00 2001 From: Peddolla Harshavardhan Reddy Date: Mon, 20 Apr 2026 14:38:45 +0530 Subject: wifi: cfg80211: Add MAC address filter to remain_on_channel Currently the remain_on_channel operation does not support filtering incoming frames by destination MAC address. This prevents use cases such as PASN authentication in the responder side that need to receive frames addressed to a specific MAC during the off-channel period. Add an rx_addr parameter to the remain_on_channel operation callback and propagate it through the call chain from nl80211 to driver implementations. Introduce the extended feature NL80211_EXT_FEATURE_ROC_ADDR_FILTER as a capability gate so that cfg80211 rejects the request if the driver does not advertise support for address filtering. Extract the address from the NL80211_ATTR_MAC attribute when provided in the netlink message and update the tracing infrastructure to include the address in remain_on_channel trace events. The rx_addr parameter is optional and can be NULL, maintaining backward compatibility with existing drivers. Signed-off-by: Peddolla Harshavardhan Reddy Link: https://patch.msgid.link/20260420090856.2152905-3-peddolla.reddy@oss.qualcomm.com Signed-off-by: Johannes Berg --- drivers/net/wireless/ath/ath6kl/cfg80211.c | 3 ++- drivers/net/wireless/ath/wil6210/cfg80211.c | 3 ++- drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c | 4 +++- drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.h | 3 ++- drivers/net/wireless/marvell/mwifiex/cfg80211.c | 3 ++- drivers/net/wireless/microchip/wilc1000/cfg80211.c | 3 ++- include/net/cfg80211.h | 2 +- include/uapi/linux/nl80211.h | 11 ++++++++++- net/mac80211/ieee80211_i.h | 3 ++- net/mac80211/offchannel.c | 3 ++- net/wireless/nl80211.c | 11 ++++++++++- net/wireless/rdev-ops.h | 7 ++++--- net/wireless/trace.h | 12 ++++++++---- 13 files changed, 50 insertions(+), 18 deletions(-) (limited to 'include') diff --git a/drivers/net/wireless/ath/ath6kl/cfg80211.c b/drivers/net/wireless/ath/ath6kl/cfg80211.c index 739a24a6ad67..cc0f2c45fc3a 100644 --- a/drivers/net/wireless/ath/ath6kl/cfg80211.c +++ b/drivers/net/wireless/ath/ath6kl/cfg80211.c @@ -1,6 +1,7 @@ /* * Copyright (c) 2004-2011 Atheros Communications Inc. * Copyright (c) 2011-2012 Qualcomm Atheros, Inc. + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -3033,7 +3034,7 @@ static int ath6kl_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, struct ieee80211_channel *chan, unsigned int duration, - u64 *cookie) + u64 *cookie, const u8 *rx_addr) { struct ath6kl_vif *vif = ath6kl_vif_from_wdev(wdev); struct ath6kl *ar = ath6kl_priv(vif->ndev); diff --git a/drivers/net/wireless/ath/wil6210/cfg80211.c b/drivers/net/wireless/ath/wil6210/cfg80211.c index 3d6e5aad48b1..d6ef92cfcbaf 100644 --- a/drivers/net/wireless/ath/wil6210/cfg80211.c +++ b/drivers/net/wireless/ath/wil6210/cfg80211.c @@ -2,6 +2,7 @@ /* * Copyright (c) 2012-2017 Qualcomm Atheros, Inc. * Copyright (c) 2018-2019, The Linux Foundation. All rights reserved. + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */ #include @@ -1734,7 +1735,7 @@ static int wil_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, struct ieee80211_channel *chan, unsigned int duration, - u64 *cookie) + u64 *cookie, const u8 *rx_addr) { struct wil6210_priv *wil = wiphy_to_wil(wiphy); int rc; diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c index e1752a513c73..92c16a317328 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c @@ -970,10 +970,12 @@ exit: * @channel: channel to stay on. * @duration: time in ms to remain on channel. * @cookie: cookie. + * @rx_addr: Address to match against the destination of received frames */ int brcmf_p2p_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, struct ieee80211_channel *channel, - unsigned int duration, u64 *cookie) + unsigned int duration, u64 *cookie, + const u8 *rx_addr) { struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy); struct brcmf_p2p_info *p2p = &cfg->p2p; diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.h index d3137ebd7158..9f3f01ade2b7 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.h @@ -157,7 +157,8 @@ int brcmf_p2p_scan_prep(struct wiphy *wiphy, struct brcmf_cfg80211_vif *vif); int brcmf_p2p_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, struct ieee80211_channel *channel, - unsigned int duration, u64 *cookie); + unsigned int duration, u64 *cookie, + const u8 *rx_addr); int brcmf_p2p_notify_listen_complete(struct brcmf_if *ifp, const struct brcmf_event_msg *e, void *data); diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c index c9a651bdf882..c9daf893472f 100644 --- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c +++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c @@ -304,7 +304,8 @@ static int mwifiex_cfg80211_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, struct ieee80211_channel *chan, - unsigned int duration, u64 *cookie) + unsigned int duration, u64 *cookie, + const u8 *rx_addr) { struct mwifiex_private *priv = mwifiex_netdev_get_priv(wdev->netdev); int ret; diff --git a/drivers/net/wireless/microchip/wilc1000/cfg80211.c b/drivers/net/wireless/microchip/wilc1000/cfg80211.c index 3a774cc44b26..6654fce4ded8 100644 --- a/drivers/net/wireless/microchip/wilc1000/cfg80211.c +++ b/drivers/net/wireless/microchip/wilc1000/cfg80211.c @@ -1100,7 +1100,8 @@ static void wilc_wfi_remain_on_channel_expired(struct wilc_vif *vif, u64 cookie) static int remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, struct ieee80211_channel *chan, - unsigned int duration, u64 *cookie) + unsigned int duration, u64 *cookie, + const u8 *rx_addr) { int ret = 0; struct wilc_vif *vif = netdev_priv(wdev->netdev); diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index b0a908b2ba73..897dbe325b7e 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -5270,7 +5270,7 @@ struct cfg80211_ops { struct wireless_dev *wdev, struct ieee80211_channel *chan, unsigned int duration, - u64 *cookie); + u64 *cookie, const u8 *rx_addr); int (*cancel_remain_on_channel)(struct wiphy *wiphy, struct wireless_dev *wdev, u64 cookie); diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index e26d65c1b737..8103f2912abc 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -729,7 +729,9 @@ * to remain on the channel. This command is also used as an event to * notify when the requested duration starts (it may take a while for the * driver to schedule this time due to other concurrent needs for the - * radio). + * radio). An optional attribute %NL80211_ATTR_MAC can be used to filter + * incoming frames during remain-on-channel, such that frames + * addressed to the specified destination MAC are reported. * When called, this operation returns a cookie (%NL80211_ATTR_COOKIE) * that will be included with any events pertaining to this request; * the cookie is also used to cancel the request. @@ -7040,6 +7042,12 @@ enum nl80211_feature_flags { * (NL80211_CMD_AUTHENTICATE) in non-AP STA mode, as specified in * "IEEE P802.11bi/D4.0, 12.16.5". * + * @NL80211_EXT_FEATURE_ROC_ADDR_FILTER: Driver supports MAC address + * filtering during remain-on-channel. When %NL80211_ATTR_MAC is + * provided with %NL80211_CMD_REMAIN_ON_CHANNEL, the driver will + * forward frames with a matching MAC address to userspace during + * the off-channel period. + * * @NUM_NL80211_EXT_FEATURES: number of extended features. * @MAX_NL80211_EXT_FEATURES: highest extended feature index. */ @@ -7119,6 +7127,7 @@ enum nl80211_ext_feature_index { NL80211_EXT_FEATURE_EPPKE, NL80211_EXT_FEATURE_ASSOC_FRAME_ENCRYPTION, NL80211_EXT_FEATURE_IEEE8021X_AUTH, + NL80211_EXT_FEATURE_ROC_ADDR_FILTER, /* add new features before the definition below */ NUM_NL80211_EXT_FEATURES, diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index e23e347f870d..76ed7aa7d8cd 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -2097,7 +2097,8 @@ void ieee80211_roc_purge(struct ieee80211_local *local, struct ieee80211_sub_if_data *sdata); int ieee80211_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, struct ieee80211_channel *chan, - unsigned int duration, u64 *cookie); + unsigned int duration, u64 *cookie, + const u8 *rx_addr); int ieee80211_cancel_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, u64 cookie); int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, diff --git a/net/mac80211/offchannel.c b/net/mac80211/offchannel.c index 10c962d28037..83e4be06039e 100644 --- a/net/mac80211/offchannel.c +++ b/net/mac80211/offchannel.c @@ -706,7 +706,8 @@ static int ieee80211_start_roc_work(struct ieee80211_local *local, int ieee80211_remain_on_channel(struct wiphy *wiphy, struct wireless_dev *wdev, struct ieee80211_channel *chan, - unsigned int duration, u64 *cookie) + unsigned int duration, u64 *cookie, + const u8 *rx_addr) { struct ieee80211_sub_if_data *sdata = IEEE80211_WDEV_TO_SUB_IF(wdev); struct ieee80211_local *local = sdata->local; diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index b96f2f7f67d2..c5879056e694 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -14133,6 +14133,7 @@ static int nl80211_remain_on_channel(struct sk_buff *skb, unsigned int link_id = nl80211_link_id(info->attrs); struct wireless_dev *wdev = info->user_ptr[1]; struct cfg80211_chan_def chandef; + const u8 *rx_addr = NULL; struct sk_buff *msg; void *hdr; u64 cookie; @@ -14145,6 +14146,14 @@ static int nl80211_remain_on_channel(struct sk_buff *skb, duration = nla_get_u32(info->attrs[NL80211_ATTR_DURATION]); + if (info->attrs[NL80211_ATTR_MAC]) + rx_addr = nla_data(info->attrs[NL80211_ATTR_MAC]); + + if (rx_addr && + !wiphy_ext_feature_isset(wdev->wiphy, + NL80211_EXT_FEATURE_ROC_ADDR_FILTER)) + return -EOPNOTSUPP; + if (!rdev->ops->remain_on_channel || !(rdev->wiphy.flags & WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL)) return -EOPNOTSUPP; @@ -14192,7 +14201,7 @@ static int nl80211_remain_on_channel(struct sk_buff *skb, } err = rdev_remain_on_channel(rdev, wdev, chandef.chan, - duration, &cookie); + duration, &cookie, rx_addr); if (err) goto free_msg; diff --git a/net/wireless/rdev-ops.h b/net/wireless/rdev-ops.h index bba239a068f6..d97d5c08d135 100644 --- a/net/wireless/rdev-ops.h +++ b/net/wireless/rdev-ops.h @@ -736,12 +736,13 @@ static inline int rdev_remain_on_channel(struct cfg80211_registered_device *rdev, struct wireless_dev *wdev, struct ieee80211_channel *chan, - unsigned int duration, u64 *cookie) + unsigned int duration, u64 *cookie, const u8 *rx_addr) { int ret; - trace_rdev_remain_on_channel(&rdev->wiphy, wdev, chan, duration); + trace_rdev_remain_on_channel(&rdev->wiphy, wdev, chan, duration, + rx_addr); ret = rdev->ops->remain_on_channel(&rdev->wiphy, wdev, chan, - duration, cookie); + duration, cookie, rx_addr); trace_rdev_return_int_cookie(&rdev->wiphy, ret, *cookie); return ret; } diff --git a/net/wireless/trace.h b/net/wireless/trace.h index eb5bedf9c92a..938fea1fe9d8 100644 --- a/net/wireless/trace.h +++ b/net/wireless/trace.h @@ -2155,22 +2155,26 @@ DEFINE_EVENT(rdev_pmksa, rdev_del_pmksa, TRACE_EVENT(rdev_remain_on_channel, TP_PROTO(struct wiphy *wiphy, struct wireless_dev *wdev, struct ieee80211_channel *chan, - unsigned int duration), - TP_ARGS(wiphy, wdev, chan, duration), + unsigned int duration, const u8 *rx_addr), + TP_ARGS(wiphy, wdev, chan, duration, rx_addr), TP_STRUCT__entry( WIPHY_ENTRY WDEV_ENTRY CHAN_ENTRY __field(unsigned int, duration) + MAC_ENTRY(rx_addr) ), TP_fast_assign( WIPHY_ASSIGN; WDEV_ASSIGN; CHAN_ASSIGN(chan); __entry->duration = duration; + MAC_ASSIGN(rx_addr, rx_addr); ), - TP_printk(WIPHY_PR_FMT ", " WDEV_PR_FMT ", " CHAN_PR_FMT ", duration: %u", - WIPHY_PR_ARG, WDEV_PR_ARG, CHAN_PR_ARG, __entry->duration) + TP_printk(WIPHY_PR_FMT ", " WDEV_PR_FMT ", " CHAN_PR_FMT + ", duration: %u, %pM", + WIPHY_PR_ARG, WDEV_PR_ARG, CHAN_PR_ARG, __entry->duration, + __entry->rx_addr) ); TRACE_EVENT(rdev_return_int_cookie, -- cgit v1.2.3 From 55f23d68f93a4cce394885886cdcd4015c35e951 Mon Sep 17 00:00:00 2001 From: Peddolla Harshavardhan Reddy Date: Mon, 20 Apr 2026 14:38:46 +0530 Subject: wifi: cfg80211/mac80211: Add NL80211_IFTYPE_PD for PD PASN and PMSR operations Add a new wdev-only interface type NL80211_IFTYPE_PD to support Proximity Detection (PD) operations such as PASN and peer measurement operations. This interface type operates without a netdev, similar to P2P_DEVICE and NAN interfaces. Implement support across cfg80211 and mac80211 layers with PD-specific checks gated by the NL80211_EXT_FEATURE_SECURE_RTT feature flag, management frame registration and transmission capabilities, and proper channel context handling where PD interfaces are excluded from bandwidth calculations. Update mac80211 to recognize the new interface type in the relevant paths for this management-only interface. PD discovery can be performed on any available interface, such as NL80211_IFTYPE_STATION. If PD/PMSR uses the MAC address of an existing interface type, such as NL80211_IFTYPE_STATION, then pairing and measurement shall use that same interface. If PD/PMSR uses a different MAC address, such as a random MAC address, then pairing and measurement can be performed on a new NL80211_IFTYPE_PD interface created with that random MAC address. Signed-off-by: Peddolla Harshavardhan Reddy Link: https://patch.msgid.link/20260420090856.2152905-4-peddolla.reddy@oss.qualcomm.com [fix comment style] Signed-off-by: Johannes Berg --- include/uapi/linux/nl80211.h | 2 ++ net/mac80211/cfg.c | 2 ++ net/mac80211/chan.c | 2 ++ net/mac80211/iface.c | 6 +++++- net/mac80211/offchannel.c | 1 + net/mac80211/rx.c | 7 +++++++ net/mac80211/util.c | 1 + net/wireless/chan.c | 2 ++ net/wireless/core.c | 1 + net/wireless/mlme.c | 1 + net/wireless/nl80211.c | 27 +++++++++++++++++++++++++-- net/wireless/reg.c | 3 +++ net/wireless/util.c | 4 +++- 13 files changed, 55 insertions(+), 4 deletions(-) (limited to 'include') diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 8103f2912abc..69e13cd7978a 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -3842,6 +3842,7 @@ enum nl80211_attrs { * @NL80211_IFTYPE_NAN_DATA: NAN data interface type (netdev); NAN data * interfaces can only be brought up (IFF_UP) when a NAN interface * already exists and NAN has been started (using %NL80211_CMD_START_NAN). + * @NL80211_IFTYPE_PD: PD device interface type (not a netdev) * @NL80211_IFTYPE_MAX: highest interface type number currently defined * @NUM_NL80211_IFTYPES: number of defined interface types * @@ -3864,6 +3865,7 @@ enum nl80211_iftype { NL80211_IFTYPE_OCB, NL80211_IFTYPE_NAN, NL80211_IFTYPE_NAN_DATA, + NL80211_IFTYPE_PD, /* keep last */ NUM_NL80211_IFTYPES, diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index c71af8878562..413dd73e8815 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -716,6 +716,7 @@ static int ieee80211_add_key(struct wiphy *wiphy, struct wireless_dev *wdev, case NL80211_IFTYPE_WDS: case NL80211_IFTYPE_MONITOR: case NL80211_IFTYPE_P2P_DEVICE: + case NL80211_IFTYPE_PD: case NL80211_IFTYPE_UNSPECIFIED: case NUM_NL80211_IFTYPES: case NL80211_IFTYPE_P2P_CLIENT: @@ -3459,6 +3460,7 @@ static int ieee80211_scan(struct wiphy *wiphy, } break; case NL80211_IFTYPE_NAN: + case NL80211_IFTYPE_PD: default: return -EOPNOTSUPP; } diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c index 9683d3e6e1d2..16ee79566e50 100644 --- a/net/mac80211/chan.c +++ b/net/mac80211/chan.c @@ -535,6 +535,7 @@ ieee80211_get_width_of_link(struct ieee80211_link_data *link) case NL80211_IFTYPE_P2P_GO: case NL80211_IFTYPE_NAN: case NL80211_IFTYPE_NAN_DATA: + case NL80211_IFTYPE_PD: WARN_ON_ONCE(1); break; } @@ -1532,6 +1533,7 @@ ieee80211_link_chanctx_reservation_complete(struct ieee80211_link_data *link) case NL80211_IFTYPE_P2P_DEVICE: case NL80211_IFTYPE_NAN: case NL80211_IFTYPE_NAN_DATA: + case NL80211_IFTYPE_PD: case NUM_NL80211_IFTYPES: WARN_ON(1); break; diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index 683d8db4da14..84e8e78d1573 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c @@ -1400,6 +1400,7 @@ int ieee80211_do_open(struct wireless_dev *wdev, bool coming_up) case NL80211_IFTYPE_P2P_DEVICE: case NL80211_IFTYPE_OCB: case NL80211_IFTYPE_NAN: + case NL80211_IFTYPE_PD: /* no special treatment */ break; case NL80211_IFTYPE_NAN_DATA: @@ -1532,7 +1533,8 @@ int ieee80211_do_open(struct wireless_dev *wdev, bool coming_up) FIF_PROBE_REQ); if (sdata->vif.type != NL80211_IFTYPE_P2P_DEVICE && - sdata->vif.type != NL80211_IFTYPE_NAN) + sdata->vif.type != NL80211_IFTYPE_NAN && + sdata->vif.type != NL80211_IFTYPE_PD) changed |= ieee80211_reset_erp_info(sdata); ieee80211_link_info_change_notify(sdata, &sdata->deflink, changed); @@ -1548,6 +1550,7 @@ int ieee80211_do_open(struct wireless_dev *wdev, bool coming_up) break; case NL80211_IFTYPE_P2P_DEVICE: case NL80211_IFTYPE_NAN: + case NL80211_IFTYPE_PD: break; default: /* not reached */ @@ -1988,6 +1991,7 @@ static void ieee80211_setup_sdata(struct ieee80211_sub_if_data *sdata, sdata->vif.bss_conf.bssid = sdata->vif.addr; break; case NL80211_IFTYPE_NAN_DATA: + case NL80211_IFTYPE_PD: break; case NL80211_IFTYPE_UNSPECIFIED: case NL80211_IFTYPE_WDS: diff --git a/net/mac80211/offchannel.c b/net/mac80211/offchannel.c index 83e4be06039e..8bec39b099a0 100644 --- a/net/mac80211/offchannel.c +++ b/net/mac80211/offchannel.c @@ -895,6 +895,7 @@ int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, } break; case NL80211_IFTYPE_P2P_DEVICE: + case NL80211_IFTYPE_PD: need_offchan = true; break; case NL80211_IFTYPE_NAN: diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c index e1f376e0620c..4c788328c820 100644 --- a/net/mac80211/rx.c +++ b/net/mac80211/rx.c @@ -4672,6 +4672,13 @@ static bool ieee80211_accept_frame(struct ieee80211_rx_data *rx) /* Unicast secure management frames */ return ether_addr_equal(sdata->vif.addr, hdr->addr1) && ieee80211_is_unicast_robust_mgmt_frame(skb); + case NL80211_IFTYPE_PD: + /* + * Accept only authentication frames (PASN) addressed to + * this interface. + */ + return ieee80211_is_auth(hdr->frame_control) && + ether_addr_equal(sdata->vif.addr, hdr->addr1); default: break; } diff --git a/net/mac80211/util.c b/net/mac80211/util.c index 89e82d34ae48..61ec2116fab2 100644 --- a/net/mac80211/util.c +++ b/net/mac80211/util.c @@ -2210,6 +2210,7 @@ int ieee80211_reconfig(struct ieee80211_local *local) case NL80211_IFTYPE_AP_VLAN: case NL80211_IFTYPE_MONITOR: case NL80211_IFTYPE_P2P_DEVICE: + case NL80211_IFTYPE_PD: /* nothing to do */ break; case NL80211_IFTYPE_UNSPECIFIED: diff --git a/net/wireless/chan.c b/net/wireless/chan.c index 8b94c0de80ad..8954ac1659c1 100644 --- a/net/wireless/chan.c +++ b/net/wireless/chan.c @@ -817,6 +817,7 @@ int cfg80211_chandef_dfs_required(struct wiphy *wiphy, case NL80211_IFTYPE_AP_VLAN: case NL80211_IFTYPE_P2P_DEVICE: case NL80211_IFTYPE_NAN_DATA: + case NL80211_IFTYPE_PD: break; case NL80211_IFTYPE_WDS: case NL80211_IFTYPE_UNSPECIFIED: @@ -941,6 +942,7 @@ bool cfg80211_beaconing_iface_active(struct wireless_dev *wdev) /* Can NAN type be considered as beaconing interface? */ case NL80211_IFTYPE_NAN: case NL80211_IFTYPE_NAN_DATA: + case NL80211_IFTYPE_PD: break; case NL80211_IFTYPE_UNSPECIFIED: case NL80211_IFTYPE_WDS: diff --git a/net/wireless/core.c b/net/wireless/core.c index 345a83fe428f..9a6f8ffe7ba9 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -1519,6 +1519,7 @@ void cfg80211_leave_locked(struct cfg80211_registered_device *rdev, case NL80211_IFTYPE_AP_VLAN: case NL80211_IFTYPE_MONITOR: case NL80211_IFTYPE_NAN_DATA: + case NL80211_IFTYPE_PD: /* nothing to do */ break; case NL80211_IFTYPE_UNSPECIFIED: diff --git a/net/wireless/mlme.c b/net/wireless/mlme.c index d196b5c086cc..50ee462e5eac 100644 --- a/net/wireless/mlme.c +++ b/net/wireless/mlme.c @@ -945,6 +945,7 @@ int cfg80211_mlme_mgmt_tx(struct cfg80211_registered_device *rdev, * fall through, P2P device only supports * public action frames */ + case NL80211_IFTYPE_PD: default: err = -EOPNOTSUPP; break; diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index c5879056e694..85096c8964ff 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -1842,6 +1842,11 @@ static int nl80211_key_allowed(struct wireless_dev *wdev) NL80211_EXT_FEATURE_SECURE_NAN)) return 0; return -EINVAL; + case NL80211_IFTYPE_PD: + if (wiphy_ext_feature_isset(wdev->wiphy, + NL80211_EXT_FEATURE_SECURE_RTT)) + return 0; + return -EINVAL; case NL80211_IFTYPE_UNSPECIFIED: case NL80211_IFTYPE_OCB: case NL80211_IFTYPE_MONITOR: @@ -4938,6 +4943,7 @@ static int _nl80211_new_interface(struct sk_buff *skb, struct genl_info *info) return -EOPNOTSUPP; if ((type == NL80211_IFTYPE_P2P_DEVICE || type == NL80211_IFTYPE_NAN || + type == NL80211_IFTYPE_PD || rdev->wiphy.features & NL80211_FEATURE_MAC_ON_CREATE) && info->attrs[NL80211_ATTR_MAC]) { nla_memcpy(params.macaddr, info->attrs[NL80211_ATTR_MAC], @@ -4994,8 +5000,9 @@ static int _nl80211_new_interface(struct sk_buff *skb, struct genl_info *info) break; case NL80211_IFTYPE_NAN: case NL80211_IFTYPE_P2P_DEVICE: + case NL80211_IFTYPE_PD: /* - * P2P Device and NAN do not have a netdev, so don't go + * P2P Device, NAN and PD do not have a netdev, so don't go * through the netdev notifier and must be added here */ cfg80211_init_wdev(wdev); @@ -10869,7 +10876,8 @@ static int nl80211_trigger_scan(struct sk_buff *skb, struct genl_info *info) wiphy = &rdev->wiphy; - if (wdev->iftype == NL80211_IFTYPE_NAN) + if (wdev->iftype == NL80211_IFTYPE_NAN || + wdev->iftype == NL80211_IFTYPE_PD) return -EOPNOTSUPP; if (!rdev->ops->scan) @@ -14290,6 +14298,11 @@ static int nl80211_register_mgmt(struct sk_buff *skb, struct genl_info *info) WIPHY_NAN_FLAGS_USERSPACE_DE)) return -EOPNOTSUPP; break; + case NL80211_IFTYPE_PD: + if (!wiphy_ext_feature_isset(wdev->wiphy, + NL80211_EXT_FEATURE_SECURE_RTT)) + return -EOPNOTSUPP; + break; default: return -EOPNOTSUPP; } @@ -14354,6 +14367,11 @@ static int nl80211_tx_mgmt(struct sk_buff *skb, struct genl_info *info) WIPHY_NAN_FLAGS_USERSPACE_DE)) return -EOPNOTSUPP; break; + case NL80211_IFTYPE_PD: + if (!wiphy_ext_feature_isset(wdev->wiphy, + NL80211_EXT_FEATURE_SECURE_RTT)) + return -EOPNOTSUPP; + break; default: return -EOPNOTSUPP; } @@ -14479,6 +14497,11 @@ static int nl80211_tx_mgmt_cancel_wait(struct sk_buff *skb, struct genl_info *in NL80211_EXT_FEATURE_SECURE_NAN)) return -EOPNOTSUPP; break; + case NL80211_IFTYPE_PD: + if (!wiphy_ext_feature_isset(wdev->wiphy, + NL80211_EXT_FEATURE_SECURE_RTT)) + return -EOPNOTSUPP; + break; default: return -EOPNOTSUPP; } diff --git a/net/wireless/reg.c b/net/wireless/reg.c index 5db2121c0b57..1e8214d6b6d8 100644 --- a/net/wireless/reg.c +++ b/net/wireless/reg.c @@ -2412,6 +2412,9 @@ static bool reg_wdev_chan_valid(struct wiphy *wiphy, struct wireless_dev *wdev) case NL80211_IFTYPE_NAN_DATA: /* NAN channels are checked in NL80211_IFTYPE_NAN interface */ break; + case NL80211_IFTYPE_PD: + /* we have no info, but PD is also pretty universal */ + continue; default: /* others not implemented for now */ WARN_ON_ONCE(1); diff --git a/net/wireless/util.c b/net/wireless/util.c index 95ae06e94f10..b638e205c71e 100644 --- a/net/wireless/util.c +++ b/net/wireless/util.c @@ -1211,7 +1211,8 @@ int cfg80211_change_iface(struct cfg80211_registered_device *rdev, /* cannot change into P2P device or NAN */ if (ntype == NL80211_IFTYPE_P2P_DEVICE || - ntype == NL80211_IFTYPE_NAN) + ntype == NL80211_IFTYPE_NAN || + ntype == NL80211_IFTYPE_PD) return -EOPNOTSUPP; if (!rdev->ops->change_virtual_intf || @@ -1276,6 +1277,7 @@ int cfg80211_change_iface(struct cfg80211_registered_device *rdev, case NL80211_IFTYPE_P2P_DEVICE: case NL80211_IFTYPE_WDS: case NL80211_IFTYPE_NAN: + case NL80211_IFTYPE_PD: WARN_ON(1); break; } -- cgit v1.2.3 From b47a6e4d662976efbe53518cb22cf7df86d19c75 Mon Sep 17 00:00:00 2001 From: Peddolla Harshavardhan Reddy Date: Mon, 20 Apr 2026 14:38:47 +0530 Subject: wifi: cfg80211: add start/stop proximity detection commands Currently, the proximity detection (PD) interface type has no start/stop commands defined, preventing user space from controlling PD operations through the nl80211 interface. Add NL80211_CMD_START_PD and NL80211_CMD_STOP_PD commands to allow user space to start and stop a PD interface. Add the corresponding start_pd and stop_pd operations to cfg80211_ops and ieee80211_ops, along with nl80211 command handlers, rdev wrappers, and tracing support. Validate that drivers advertising PD interface support implement the required operations. Handle PD interface teardown during device unregistration and when the interface leaves the network. Signed-off-by: Peddolla Harshavardhan Reddy Link: https://patch.msgid.link/20260420090856.2152905-5-peddolla.reddy@oss.qualcomm.com Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 5 ++++ include/uapi/linux/nl80211.h | 9 ++++++++ net/wireless/core.c | 35 +++++++++++++++++++++++++++- net/wireless/core.h | 2 ++ net/wireless/nl80211.c | 54 ++++++++++++++++++++++++++++++++++++++++++++ net/wireless/rdev-ops.h | 19 ++++++++++++++++ net/wireless/trace.h | 10 ++++++++ 7 files changed, 133 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 897dbe325b7e..8f010af419bf 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -5088,6 +5088,9 @@ struct mgmt_frame_regs { * links by calling cfg80211_mlo_reconf_add_done(). When calling * cfg80211_mlo_reconf_add_done() the bss pointer must be given for each * link for which MLO reconfiguration 'add' operation was requested. + * + * @start_pd: Start the PD interface. + * @stop_pd: Stop the PD interface. */ struct cfg80211_ops { int (*suspend)(struct wiphy *wiphy, struct cfg80211_wowlan *wow); @@ -5464,6 +5467,8 @@ struct cfg80211_ops { struct cfg80211_ml_reconf_req *req); int (*set_epcs)(struct wiphy *wiphy, struct net_device *dev, bool val); + int (*start_pd)(struct wiphy *wiphy, struct wireless_dev *wdev); + void (*stop_pd)(struct wiphy *wiphy, struct wireless_dev *wdev); }; /* diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 69e13cd7978a..704607824b8a 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -1419,6 +1419,12 @@ * identifying the evacuated channel. * User space may reconfigure the local schedule in response to this * notification. + * @NL80211_CMD_START_PD: Start PD operation, identified by its + * %NL80211_ATTR_WDEV interface. This interface must have been previously + * created with %NL80211_CMD_NEW_INTERFACE. + * @NL80211_CMD_STOP_PD: Stop the PD operation, identified by + * its %NL80211_ATTR_WDEV interface. + * * @NL80211_CMD_MAX: highest used command number * @__NL80211_CMD_AFTER_LAST: internal use */ @@ -1694,6 +1700,9 @@ enum nl80211_commands { NL80211_CMD_NAN_CHANNEL_EVAC, + NL80211_CMD_START_PD, + NL80211_CMD_STOP_PD, + /* add new commands above here */ /* used to define NL80211_CMD_MAX below */ diff --git a/net/wireless/core.c b/net/wireless/core.c index 9a6f8ffe7ba9..4dd1981a3629 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -322,6 +322,28 @@ int cfg80211_nan_set_local_schedule(struct cfg80211_registered_device *rdev, return 0; } +void cfg80211_stop_pd(struct cfg80211_registered_device *rdev, + struct wireless_dev *wdev) +{ + lockdep_assert_held(&rdev->wiphy.mtx); + + if (WARN_ON(wdev->iftype != NL80211_IFTYPE_PD)) + return; + + if (!rdev->ops->stop_pd) + return; + + if (!wdev_running(wdev)) + return; + + cfg80211_pmsr_wdev_down(wdev); + + rdev_stop_pd(rdev, wdev); + wdev->is_running = false; + + rdev->opencount--; +} + void cfg80211_shutdown_all_interfaces(struct wiphy *wiphy) { struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); @@ -351,6 +373,9 @@ void cfg80211_shutdown_all_interfaces(struct wiphy *wiphy) case NL80211_IFTYPE_NAN: cfg80211_stop_nan(rdev, wdev); break; + case NL80211_IFTYPE_PD: + cfg80211_stop_pd(rdev, wdev); + break; default: break; } @@ -846,6 +871,9 @@ int wiphy_register(struct wiphy *wiphy) (!rdev->ops->tdls_channel_switch || !rdev->ops->tdls_cancel_channel_switch))) return -EINVAL; + if (WARN_ON((wiphy->interface_modes & BIT(NL80211_IFTYPE_PD)) && + (!rdev->ops->start_pd || !rdev->ops->stop_pd))) + return -EINVAL; if (WARN_ON((wiphy->interface_modes & BIT(NL80211_IFTYPE_NAN)) && (!rdev->ops->start_nan || !rdev->ops->stop_nan || @@ -1408,6 +1436,9 @@ static void _cfg80211_unregister_wdev(struct wireless_dev *wdev, case NL80211_IFTYPE_NAN: cfg80211_stop_nan(rdev, wdev); break; + case NL80211_IFTYPE_PD: + cfg80211_stop_pd(rdev, wdev); + break; default: break; } @@ -1516,10 +1547,12 @@ void cfg80211_leave_locked(struct cfg80211_registered_device *rdev, case NL80211_IFTYPE_NAN: cfg80211_stop_nan(rdev, wdev); break; + case NL80211_IFTYPE_PD: + cfg80211_stop_pd(rdev, wdev); + break; case NL80211_IFTYPE_AP_VLAN: case NL80211_IFTYPE_MONITOR: case NL80211_IFTYPE_NAN_DATA: - case NL80211_IFTYPE_PD: /* nothing to do */ break; case NL80211_IFTYPE_UNSPECIFIED: diff --git a/net/wireless/core.h b/net/wireless/core.h index 0e6f9bd4ba1b..df47ed6208a5 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h @@ -557,6 +557,8 @@ void cfg80211_stop_p2p_device(struct cfg80211_registered_device *rdev, void cfg80211_stop_nan(struct cfg80211_registered_device *rdev, struct wireless_dev *wdev); +void cfg80211_stop_pd(struct cfg80211_registered_device *rdev, + struct wireless_dev *wdev); int cfg80211_nan_set_local_schedule(struct cfg80211_registered_device *rdev, struct wireless_dev *wdev, diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 85096c8964ff..8e20fb714eff 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -16638,6 +16638,46 @@ static int nl80211_nan_change_config(struct sk_buff *skb, return rdev_nan_change_conf(rdev, wdev, &conf, changed); } +static int nl80211_start_pd(struct sk_buff *skb, struct genl_info *info) +{ + struct cfg80211_registered_device *rdev = info->user_ptr[0]; + struct wireless_dev *wdev = info->user_ptr[1]; + int err; + + if (wdev->iftype != NL80211_IFTYPE_PD) + return -EOPNOTSUPP; + + if (wdev_running(wdev)) + return -EEXIST; + + if (rfkill_blocked(rdev->wiphy.rfkill)) + return -ERFKILL; + + if (!rdev->ops->start_pd) + return -EOPNOTSUPP; + + err = rdev_start_pd(rdev, wdev); + if (err) + return err; + wdev->is_running = true; + rdev->opencount++; + + return 0; +} + +static int nl80211_stop_pd(struct sk_buff *skb, struct genl_info *info) +{ + struct cfg80211_registered_device *rdev = info->user_ptr[0]; + struct wireless_dev *wdev = info->user_ptr[1]; + + if (wdev->iftype != NL80211_IFTYPE_PD) + return -EOPNOTSUPP; + + cfg80211_stop_pd(rdev, wdev); + + return 0; +} + void cfg80211_nan_match(struct wireless_dev *wdev, struct cfg80211_nan_match_params *match, gfp_t gfp) { @@ -19805,6 +19845,20 @@ static const struct genl_small_ops nl80211_small_ops[] = { .flags = GENL_ADMIN_PERM, .internal_flags = IFLAGS(NL80211_FLAG_NEED_WDEV_UP), }, + { + .cmd = NL80211_CMD_START_PD, + .doit = nl80211_start_pd, + .flags = GENL_ADMIN_PERM, + .internal_flags = IFLAGS(NL80211_FLAG_NEED_WDEV | + NL80211_FLAG_NEED_RTNL), + }, + { + .cmd = NL80211_CMD_STOP_PD, + .doit = nl80211_stop_pd, + .flags = GENL_ADMIN_PERM, + .internal_flags = IFLAGS(NL80211_FLAG_NEED_WDEV_UP | + NL80211_FLAG_NEED_RTNL), + }, { .cmd = NL80211_CMD_SET_MCAST_RATE, .validate = GENL_DONT_VALIDATE_STRICT | GENL_DONT_VALIDATE_DUMP, diff --git a/net/wireless/rdev-ops.h b/net/wireless/rdev-ops.h index d97d5c08d135..63c26e8b1139 100644 --- a/net/wireless/rdev-ops.h +++ b/net/wireless/rdev-ops.h @@ -1093,6 +1093,25 @@ rdev_nan_set_peer_sched(struct cfg80211_registered_device *rdev, return ret; } +static inline int rdev_start_pd(struct cfg80211_registered_device *rdev, + struct wireless_dev *wdev) +{ + int ret; + + trace_rdev_start_pd(&rdev->wiphy, wdev); + ret = rdev->ops->start_pd(&rdev->wiphy, wdev); + trace_rdev_return_int(&rdev->wiphy, ret); + return ret; +} + +static inline void rdev_stop_pd(struct cfg80211_registered_device *rdev, + struct wireless_dev *wdev) +{ + trace_rdev_stop_pd(&rdev->wiphy, wdev); + rdev->ops->stop_pd(&rdev->wiphy, wdev); + trace_rdev_return_void(&rdev->wiphy); +} + static inline int rdev_set_mac_acl(struct cfg80211_registered_device *rdev, struct net_device *dev, struct cfg80211_acl_data *params) diff --git a/net/wireless/trace.h b/net/wireless/trace.h index 938fea1fe9d8..a68d356fe127 100644 --- a/net/wireless/trace.h +++ b/net/wireless/trace.h @@ -2375,6 +2375,16 @@ DEFINE_EVENT(wiphy_wdev_evt, rdev_stop_nan, TP_ARGS(wiphy, wdev) ); +DEFINE_EVENT(wiphy_wdev_evt, rdev_start_pd, + TP_PROTO(struct wiphy *wiphy, struct wireless_dev *wdev), + TP_ARGS(wiphy, wdev) +); + +DEFINE_EVENT(wiphy_wdev_evt, rdev_stop_pd, + TP_PROTO(struct wiphy *wiphy, struct wireless_dev *wdev), + TP_ARGS(wiphy, wdev) +); + TRACE_EVENT(rdev_add_nan_func, TP_PROTO(struct wiphy *wiphy, struct wireless_dev *wdev, const struct cfg80211_nan_func *func), -- cgit v1.2.3 From 18709c618d3b4b2990f202a436784f4c36e2c193 Mon Sep 17 00:00:00 2001 From: Peddolla Harshavardhan Reddy Date: Mon, 20 Apr 2026 14:38:48 +0530 Subject: wifi: cfg80211: add proximity detection capabilities to PMSR Introduce Proximity Detection (PD) capabilities in Peer Measurement Service (PMSR) as defined in the Wi-Fi Alliance specification "Proximity Ranging (PR) Implementation Consideration Draft 1.9 Rev 1 section 3.3". This enables devices to advertise peer to peer ranging support. Restructure FTM capabilities in cfg80211_pmsr_capabilities to replace the single support_rsta flag with nested ista and rsta sub-structs, each carrying per-mode flags for Non-Trigger Based (NTB), Trigger Based (TB), and EDCA based ranging. This allows drivers to advertise detailed role and protocol support for both initiator and responder roles. Add support to pass additional ISTA and RSTA role capabilities to userspace using new nested ISTA_CAPS and RSTA_CAPS attributes. The legacy RSTA_SUPPORT flag is retained for backward compatibility. Add NL80211_PMSR_FTM_CAPA_ATTR_TYPE_CAPS nested attribute using the nl80211_peer_measurement_ftm_type_capa enum with two sub-flags: NL80211_PMSR_FTM_TYPE_CAPA_ATTR_INFRA_SUPPORT for STA-to-AP or AP-to-STA ranging, and NL80211_PMSR_FTM_TYPE_CAPA_ATTR_PD_SUPPORT for peer-to-peer ranging. Add CONCURRENT_ISTA_RSTA_SUPPORT as a FTM capability flag indicating the device can simultaneously act as initiator and responder in a multi-peer measurement request. Extend FTM capabilities with antenna configuration fields (max_no_of_tx_antennas, max_no_of_rx_antennas) for the PR Element during PASN negotiation, and ranging interval limits (min_allowed_ranging_interval_edca, min_allowed_ranging_interval_ntb) to advertise device timing constraints for EDCA and NTB-based ranging. Update the FTM request validation path in pmsr.c to check RSTA requests against the per-mode rsta capabilities (NTB, TB, EDCA), rejecting requests for modes the device does not support. Co-developed-by: Kavita Kavita Signed-off-by: Kavita Kavita Signed-off-by: Peddolla Harshavardhan Reddy Link: https://patch.msgid.link/20260420090856.2152905-6-peddolla.reddy@oss.qualcomm.com Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 60 ++++++++++++++++++++++++++++- include/uapi/linux/nl80211.h | 78 +++++++++++++++++++++++++++++++++++++ net/wireless/nl80211.c | 91 +++++++++++++++++++++++++++++++++++++++++++- net/wireless/pmsr.c | 22 ++++++++++- 4 files changed, 245 insertions(+), 6 deletions(-) (limited to 'include') diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 8f010af419bf..98943a510112 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -5911,7 +5911,45 @@ cfg80211_get_iftype_ext_capa(struct wiphy *wiphy, enum nl80211_iftype type); * (0 means unknown) * @ftm.max_total_ltf_rx: maximum total number of LTFs that can be received * (0 means unknown) - * @ftm.support_rsta: supports operating as RSTA in PMSR FTM request + * @ftm.ista: initiator role capabilities + * @ftm.ista.support_ntb: supports operating as ISTA in PMSR FTM request for + * NTB ranging. + * @ftm.ista.support_tb: supports operating as ISTA in PMSR FTM request for + * TB ranging. + * @ftm.ista.support_edca: supports operating as ISTA in PMSR FTM request for + * EDCA based ranging. + * @ftm.rsta: responder role capabilities + * @ftm.rsta.support_ntb: supports operating as RSTA in PMSR FTM request for + * NTB ranging. + * @ftm.rsta.support_tb: supports operating as RSTA in PMSR FTM request for + * TB ranging. + * @ftm.rsta.support_edca: supports operating as RSTA in PMSR FTM request for + * EDCA based ranging. + * @ftm.max_no_of_tx_antennas: maximum number of transmit antennas supported for + * EDCA based ranging (0 means unknown) + * @ftm.max_no_of_rx_antennas: maximum number of receive antennas supported for + * EDCA based ranging (0 means unknown) + * @ftm.min_allowed_ranging_interval_edca: Minimum EDCA ranging + * interval supported by the device in milli seconds. (0 means unknown). + * Applications can use this value to estimate the burst period to be + * given in the FTM request for the EDCA based ranging case. If + * non-zero, this value will be used to validate the burst period in + * the FTM request. + * @ftm.min_allowed_ranging_interval_ntb: Minimum NTB ranging + * interval supported by the device in milli seconds. (0 means unknown). + * Applications can use this value to estimate the burst period to be + * given in the FTM request for the NTB ranging case. If non-zero, + * this value will be used to validate the nominal time in the FTM + * request. + * @ftm.type: ranging type capabilities + * @ftm.type.infra_support: supports infrastructure ranging (STA-to-AP or + * AP-to-STA) as part of Proximity Detection + * @ftm.type.pd_support: supports peer-to-peer ranging as mentioned in the + * specification "PR Implementation Consideration Draft 1.9 rev 1" where + * PD stands for proximity detection + * @ftm.concurrent_ista_rsta_support: indicates if the device can + * simultaneously act as initiator and responder in a multi-peer + * measurement request. Only valid if @ftm.rsta_support is set. */ struct cfg80211_pmsr_capabilities { unsigned int max_peers; @@ -5937,7 +5975,25 @@ struct cfg80211_pmsr_capabilities { u8 max_rx_sts; u8 max_total_ltf_tx; u8 max_total_ltf_rx; - u8 support_rsta:1; + struct { + u8 support_ntb:1, + support_tb:1, + support_edca:1; + } ista; + struct { + u8 support_ntb:1, + support_tb:1, + support_edca:1; + } rsta; + u8 max_no_of_tx_antennas; + u8 max_no_of_rx_antennas; + u32 min_allowed_ranging_interval_edca; + u32 min_allowed_ranging_interval_ntb; + struct { + u8 infra_support:1, + pd_support:1; + } type; + u8 concurrent_ista_rsta_support:1; } ftm; }; diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 704607824b8a..ea0acce724ca 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -8111,6 +8111,46 @@ enum nl80211_peer_measurement_attrs { * This limits the allowed combinations of LTF repetitions and STS. * @NL80211_PMSR_FTM_CAPA_ATTR_RSTA_SUPPORT: flag attribute indicating the * device supports operating as the RSTA in PMSR FTM request + * @NL80211_PMSR_FTM_CAPA_ATTR_ISTA_CAPS: nested attribute containing ISTA + * (initiator) role capabilities. Uses the same sub-attributes as + * %NL80211_PMSR_FTM_CAPA_ATTR_RSTA_CAPS. + * @NL80211_PMSR_FTM_CAPA_ATTR_RSTA_CAPS: nested attribute containing RSTA + * (responder) role capabilities. + * @NL80211_PMSR_FTM_CAPA_ATTR_SUPPORT_NTB: flag attribute (used inside + * %NL80211_PMSR_FTM_CAPA_ATTR_ISTA_CAPS or + * %NL80211_PMSR_FTM_CAPA_ATTR_RSTA_CAPS) indicating NTB ranging support. + * @NL80211_PMSR_FTM_CAPA_ATTR_SUPPORT_TB: flag attribute (used inside + * %NL80211_PMSR_FTM_CAPA_ATTR_ISTA_CAPS or + * %NL80211_PMSR_FTM_CAPA_ATTR_RSTA_CAPS) indicating TB ranging support. + * @NL80211_PMSR_FTM_CAPA_ATTR_SUPPORT_EDCA: flag attribute (used inside + * %NL80211_PMSR_FTM_CAPA_ATTR_ISTA_CAPS or + * %NL80211_PMSR_FTM_CAPA_ATTR_RSTA_CAPS) indicating EDCA based ranging + * support. + * @NL80211_PMSR_FTM_CAPA_ATTR_TYPE_CAPS: nested attribute containing ranging + * type capabilities. Uses sub-attributes from + * &enum nl80211_peer_measurement_ftm_type_capa. + * @NL80211_PMSR_FTM_CAPA_ATTR_CONCURRENT_ISTA_RSTA_SUPPORT: flag attribute + * indicating that the device can simultaneously act as initiator and + * responder in a multi-peer measurement request. Only valid if + * @NL80211_PMSR_FTM_CAPA_ATTR_RSTA_SUPPORT is set. + * @NL80211_PMSR_FTM_CAPA_ATTR_MAX_NUM_TX_ANTENNAS: u32 attribute indicating + * the maximum number of transmit antennas supported for EDCA based ranging + * (0 means unknown) + * @NL80211_PMSR_FTM_CAPA_ATTR_MAX_NUM_RX_ANTENNAS: u32 attribute indicating + * the maximum number of receive antennas supported for EDCA based ranging + * (0 means unknown) + * @NL80211_PMSR_FTM_CAPA_ATTR_MIN_INTERVAL_EDCA: u32 attribute indicating + * the minimum EDCA ranging interval supported by the device + * in milli seconds. (0 means unknown). Applications can use this value + * to estimate the burst period to be given in the FTM request for the + * EDCA based ranging case. If non-zero, this value will be used to + * validate the burst period in the FTM request. + * @NL80211_PMSR_FTM_CAPA_ATTR_MIN_INTERVAL_NTB: u32 attribute indicating + * the minimum NTB ranging interval supported by the device + * in milli seconds. (0 means unknown). Applications can use this value + * to estimate the burst period to be given in the FTM request for the + * NTB ranging case. If non-zero, this value will be used to validate + * the nominal time in the FTM request. * * @NUM_NL80211_PMSR_FTM_CAPA_ATTR: internal * @NL80211_PMSR_FTM_CAPA_ATTR_MAX: highest attribute number @@ -8136,12 +8176,50 @@ enum nl80211_peer_measurement_ftm_capa { NL80211_PMSR_FTM_CAPA_ATTR_MAX_TOTAL_LTF_TX, NL80211_PMSR_FTM_CAPA_ATTR_MAX_TOTAL_LTF_RX, NL80211_PMSR_FTM_CAPA_ATTR_RSTA_SUPPORT, + NL80211_PMSR_FTM_CAPA_ATTR_ISTA_CAPS, + NL80211_PMSR_FTM_CAPA_ATTR_RSTA_CAPS, + NL80211_PMSR_FTM_CAPA_ATTR_SUPPORT_NTB, + NL80211_PMSR_FTM_CAPA_ATTR_SUPPORT_TB, + NL80211_PMSR_FTM_CAPA_ATTR_SUPPORT_EDCA, + NL80211_PMSR_FTM_CAPA_ATTR_TYPE_CAPS, + NL80211_PMSR_FTM_CAPA_ATTR_CONCURRENT_ISTA_RSTA_SUPPORT, + NL80211_PMSR_FTM_CAPA_ATTR_MAX_NUM_TX_ANTENNAS, + NL80211_PMSR_FTM_CAPA_ATTR_MAX_NUM_RX_ANTENNAS, + NL80211_PMSR_FTM_CAPA_ATTR_MIN_INTERVAL_EDCA, + NL80211_PMSR_FTM_CAPA_ATTR_MIN_INTERVAL_NTB, /* keep last */ NUM_NL80211_PMSR_FTM_CAPA_ATTR, NL80211_PMSR_FTM_CAPA_ATTR_MAX = NUM_NL80211_PMSR_FTM_CAPA_ATTR - 1 }; +/** + * enum nl80211_peer_measurement_ftm_type_capa - FTM ranging type capability + * sub-attributes, used inside %NL80211_PMSR_FTM_CAPA_ATTR_TYPE_CAPS + * @__NL80211_PMSR_FTM_TYPE_CAPA_ATTR_INVALID: invalid + * + * @NL80211_PMSR_FTM_TYPE_CAPA_ATTR_INFRA_SUPPORT: flag attribute indicating + * that the device supports infrastructure ranging (STA-to-AP or + * AP-to-STA) as part of Proximity Detection + * @NL80211_PMSR_FTM_TYPE_CAPA_ATTR_PD_SUPPORT: flag attribute indicating that + * the device supports peer-to-peer ranging as mentioned in the + * specification "PR Implementation Consideration Draft 1.9 rev 1" where + * PD stands for proximity detection + * + * @NUM_NL80211_PMSR_FTM_TYPE_CAPA_ATTR: internal + * @NL80211_PMSR_FTM_TYPE_CAPA_ATTR_MAX: highest attribute number + */ +enum nl80211_peer_measurement_ftm_type_capa { + __NL80211_PMSR_FTM_TYPE_CAPA_ATTR_INVALID, + + NL80211_PMSR_FTM_TYPE_CAPA_ATTR_INFRA_SUPPORT, + NL80211_PMSR_FTM_TYPE_CAPA_ATTR_PD_SUPPORT, + + /* keep last */ + NUM_NL80211_PMSR_FTM_TYPE_CAPA_ATTR, + NL80211_PMSR_FTM_TYPE_CAPA_ATTR_MAX = NUM_NL80211_PMSR_FTM_TYPE_CAPA_ATTR - 1 +}; + /** * enum nl80211_peer_measurement_ftm_req - FTM request attributes * @__NL80211_PMSR_FTM_REQ_ATTR_INVALID: invalid diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 8e20fb714eff..0a87f4f81cd2 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -2503,10 +2503,97 @@ nl80211_send_pmsr_ftm_capa(const struct cfg80211_pmsr_capabilities *cap, nla_put_u32(msg, NL80211_PMSR_FTM_CAPA_ATTR_MAX_TOTAL_LTF_RX, cap->ftm.max_total_ltf_rx)) return -ENOBUFS; - if (cap->ftm.support_rsta && - nla_put_flag(msg, NL80211_PMSR_FTM_CAPA_ATTR_RSTA_SUPPORT)) + + if (cap->ftm.ista.support_ntb || cap->ftm.ista.support_tb || + cap->ftm.ista.support_edca) { + struct nlattr *ista_caps; + + ista_caps = nla_nest_start_noflag(msg, + NL80211_PMSR_FTM_CAPA_ATTR_ISTA_CAPS); + if (!ista_caps) + return -ENOBUFS; + if (cap->ftm.ista.support_ntb && + nla_put_flag(msg, NL80211_PMSR_FTM_CAPA_ATTR_SUPPORT_NTB)) + return -ENOBUFS; + if (cap->ftm.ista.support_tb && + nla_put_flag(msg, NL80211_PMSR_FTM_CAPA_ATTR_SUPPORT_TB)) + return -ENOBUFS; + if (cap->ftm.ista.support_edca && + nla_put_flag(msg, NL80211_PMSR_FTM_CAPA_ATTR_SUPPORT_EDCA)) + return -ENOBUFS; + nla_nest_end(msg, ista_caps); + } + + if (cap->ftm.rsta.support_ntb || cap->ftm.rsta.support_tb || + cap->ftm.rsta.support_edca) { + struct nlattr *rsta_caps; + + /* + * Set the generic RSTA_SUPPORT flag if any of the specific + * ranging modes is supported to maintain the backward + * compatibility. + */ + if (nla_put_flag(msg, NL80211_PMSR_FTM_CAPA_ATTR_RSTA_SUPPORT)) + return -ENOBUFS; + + rsta_caps = nla_nest_start_noflag(msg, + NL80211_PMSR_FTM_CAPA_ATTR_RSTA_CAPS); + if (!rsta_caps) + return -ENOBUFS; + if (cap->ftm.rsta.support_ntb && + nla_put_flag(msg, NL80211_PMSR_FTM_CAPA_ATTR_SUPPORT_NTB)) + return -ENOBUFS; + if (cap->ftm.rsta.support_tb && + nla_put_flag(msg, NL80211_PMSR_FTM_CAPA_ATTR_SUPPORT_TB)) + return -ENOBUFS; + if (cap->ftm.rsta.support_edca && + nla_put_flag(msg, NL80211_PMSR_FTM_CAPA_ATTR_SUPPORT_EDCA)) + return -ENOBUFS; + nla_nest_end(msg, rsta_caps); + } + + if (cap->ftm.max_no_of_tx_antennas && + nla_put_u8(msg, NL80211_PMSR_FTM_CAPA_ATTR_MAX_NUM_TX_ANTENNAS, + cap->ftm.max_no_of_tx_antennas)) + return -ENOBUFS; + + if (cap->ftm.max_no_of_rx_antennas && + nla_put_u8(msg, NL80211_PMSR_FTM_CAPA_ATTR_MAX_NUM_RX_ANTENNAS, + cap->ftm.max_no_of_rx_antennas)) + return -ENOBUFS; + + if (cap->ftm.min_allowed_ranging_interval_edca && + nla_put_u32(msg, NL80211_PMSR_FTM_CAPA_ATTR_MIN_INTERVAL_EDCA, + cap->ftm.min_allowed_ranging_interval_edca)) return -ENOBUFS; + if (cap->ftm.min_allowed_ranging_interval_ntb && + nla_put_u32(msg, NL80211_PMSR_FTM_CAPA_ATTR_MIN_INTERVAL_NTB, + cap->ftm.min_allowed_ranging_interval_ntb)) + return -ENOBUFS; + + if (cap->ftm.type.infra_support || cap->ftm.type.pd_support) { + struct nlattr *pd_caps; + + pd_caps = nla_nest_start_noflag(msg, + NL80211_PMSR_FTM_CAPA_ATTR_TYPE_CAPS); + if (!pd_caps) + return -ENOBUFS; + + if (cap->ftm.type.infra_support && + nla_put_flag(msg, NL80211_PMSR_FTM_TYPE_CAPA_ATTR_INFRA_SUPPORT)) + return -ENOBUFS; + + if (cap->ftm.type.pd_support && + nla_put_flag(msg, NL80211_PMSR_FTM_TYPE_CAPA_ATTR_PD_SUPPORT)) + return -ENOBUFS; + + nla_nest_end(msg, pd_caps); + } + + if (cap->ftm.concurrent_ista_rsta_support && + nla_put_flag(msg, NL80211_PMSR_FTM_CAPA_ATTR_CONCURRENT_ISTA_RSTA_SUPPORT)) + return -ENOBUFS; nla_nest_end(msg, ftm); return 0; } diff --git a/net/wireless/pmsr.c b/net/wireless/pmsr.c index afc0e3f931ec..c21f693fac29 100644 --- a/net/wireless/pmsr.c +++ b/net/wireless/pmsr.c @@ -188,10 +188,28 @@ static int pmsr_parse_ftm(struct cfg80211_registered_device *rdev, } out->ftm.rsta = !!tb[NL80211_PMSR_FTM_REQ_ATTR_RSTA]; - if (out->ftm.rsta && !capa->ftm.support_rsta) { + if (out->ftm.rsta && out->ftm.non_trigger_based && + !capa->ftm.rsta.support_ntb) { NL_SET_ERR_MSG_ATTR(info->extack, tb[NL80211_PMSR_FTM_REQ_ATTR_RSTA], - "FTM: RSTA not supported by device"); + "FTM: NTB RSTA not supported by device"); + return -EOPNOTSUPP; + } + + if (out->ftm.rsta && out->ftm.trigger_based && + !capa->ftm.rsta.support_tb) { + NL_SET_ERR_MSG_ATTR(info->extack, + tb[NL80211_PMSR_FTM_REQ_ATTR_RSTA], + "FTM: TB RSTA not supported by device"); + return -EOPNOTSUPP; + } + + if (out->ftm.rsta && !out->ftm.non_trigger_based && + !out->ftm.trigger_based && + !capa->ftm.rsta.support_edca) { + NL_SET_ERR_MSG_ATTR(info->extack, + tb[NL80211_PMSR_FTM_REQ_ATTR_RSTA], + "FTM: EDCA RSTA not supported by device"); return -EOPNOTSUPP; } -- cgit v1.2.3 From 8823a9b0e7af5395cbfa06bf23806197d59cd124 Mon Sep 17 00:00:00 2001 From: Peddolla Harshavardhan Reddy Date: Mon, 20 Apr 2026 14:38:49 +0530 Subject: wifi: cfg80211: add NTB continuous ranging and FTM request type support Enable NTB continuous ranging with configurable timing and measurement parameters as per the Wi-Fi Alliance specification "Proximity Ranging (PR) Implementation Consideration Draft 1.9 Rev 1, section 5.3". Add new FTM request attributes for min/max time between measurements, nominal time (mandatory for NTB), AW duration, and total measurement count. Add NL80211_PMSR_PEER_ATTR_REQ_TYPE attribute using the new nl80211_peer_measurement_ftm_req_type enum to allow userspace to specify the ranging request type per peer: - NL80211_PMSR_FTM_REQ_TYPE_INFRA: STA-to-AP or AP-to-STA ranging (default if attribute is absent) - NL80211_PMSR_FTM_REQ_TYPE_PD: peer-to-peer ranging Validate the request type against the device TYPE_CAPS capabilities advertised via NL80211_PMSR_FTM_CAPA_ATTR_TYPE_CAPS. Reject PD requests if the device does not advertise PD support. Reject PD requests that set trigger-based ranging, as TB ranging is not compatible with peer-to-peer proximity detection. Add ftms_per_burst limit of 4 for PD NTB ranging requests. Signed-off-by: Peddolla Harshavardhan Reddy Link: https://patch.msgid.link/20260420090856.2152905-7-peddolla.reddy@oss.qualcomm.com Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 35 ++++++++++++++++++++++++- include/uapi/linux/nl80211.h | 62 ++++++++++++++++++++++++++++++++++++++++++-- net/wireless/nl80211.c | 11 ++++++++ net/wireless/pmsr.c | 61 ++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 163 insertions(+), 6 deletions(-) (limited to 'include') diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 98943a510112..28923539456e 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -4527,7 +4527,8 @@ struct cfg80211_pmsr_result { * @burst_duration: burst duration. If @trigger_based or @non_trigger_based is * set, this is the burst duration in milliseconds, and zero means the * device should pick an appropriate value based on @ftms_per_burst. - * @ftms_per_burst: number of FTMs per burst + * @ftms_per_burst: number of FTMs per burst. If set to 0, the firmware or + * driver can automatically select an appropriate value. * @ftmr_retries: number of retries for FTM request * @request_lci: request LCI information * @request_civicloc: request civic location information @@ -4544,6 +4545,31 @@ struct cfg80211_pmsr_result { * @bss_color: the bss color of the responder. Optional. Set to zero to * indicate the driver should set the BSS color. Only valid if * @non_trigger_based or @trigger_based is set. + * @request_type: ranging request type, one of + * &enum nl80211_peer_measurement_ftm_req_type. Defaults to + * %NL80211_PMSR_FTM_REQ_TYPE_INFRA if not specified. + * @min_time_between_measurements: minimum time between two consecutive range + * measurements in units of 100 microseconds, for non-trigger based + * ranging. Should be set as short as possible to minimize turnaround + * time, since two-way ranging with delayed LMR requires two measurements. + * Only valid if @non_trigger_based is set. + * @max_time_between_measurements: maximum time between two consecutive range + * measurements in units of 10 milliseconds, for non-trigger based + * ranging. Acts as a session timeout; if exceeded, the ranging session + * should be terminated. Only valid if @non_trigger_based is set. + * @availability_window: duration of the availability window (AW) in units of + * 1 millisecond (0-255 ms). Only valid if @non_trigger_based is set. + * If set to 0, the firmware or driver can automatically select an + * appropriate value. + * @nominal_time: Nominal duration between adjacent availability windows + * in units of milli seconds. Only valid if @non_trigger_based is set. + * If set to 0, the firmware or driver can automatically select an + * appropriate value. + * @num_measurements: number of Availability Windows (AWs) to schedule + * for non-trigger-based ranging. Each AW may contain multiple FTM + * exchanges as configured by @ftms_per_burst. Only valid if + * @non_trigger_based is set. If set to 0, the firmware or driver + * can automatically select an appropriate value. * * See also nl80211 for the respective attribute documentation. */ @@ -4563,6 +4589,13 @@ struct cfg80211_pmsr_ftm_request_peer { u8 ftms_per_burst; u8 ftmr_retries; u8 bss_color; + + u32 request_type; + u32 min_time_between_measurements; + u32 max_time_between_measurements; + u8 availability_window; + u32 nominal_time; + u32 num_measurements; }; /** diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index ea0acce724ca..6be89e656276 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -7991,6 +7991,26 @@ enum nl80211_peer_measurement_resp { NL80211_PMSR_RESP_ATTR_MAX = NUM_NL80211_PMSR_RESP_ATTRS - 1 }; +/** + * enum nl80211_peer_measurement_ftm_req_type - FTM ranging request type, + * used with %NL80211_PMSR_PEER_ATTR_REQ_TYPE + * + * @NL80211_PMSR_FTM_REQ_TYPE_INFRA: infrastructure ranging, i.e. STA-to-AP + * @NL80211_PMSR_FTM_REQ_TYPE_PD: peer-to-peer ranging as defined in the + * Wi-Fi Alliance specification "Proximity Ranging (PR) Implementation + * Consideration Draft 1.9 Rev 1" + * @NUM_NL80211_PMSR_FTM_REQ_TYPE: internal + * @NL80211_PMSR_FTM_REQ_TYPE_MAX: highest request type value + */ +enum nl80211_peer_measurement_ftm_req_type { + NL80211_PMSR_FTM_REQ_TYPE_INFRA, + NL80211_PMSR_FTM_REQ_TYPE_PD, + + /* keep last */ + NUM_NL80211_PMSR_FTM_REQ_TYPE, + NL80211_PMSR_FTM_REQ_TYPE_MAX = NUM_NL80211_PMSR_FTM_REQ_TYPE - 1 +}; + /** * enum nl80211_peer_measurement_peer_attrs - peer attributes for measurement * @__NL80211_PMSR_PEER_ATTR_INVALID: invalid @@ -8004,6 +8024,9 @@ enum nl80211_peer_measurement_resp { * @NL80211_PMSR_PEER_ATTR_RESP: This is a nested attribute indexed by * measurement type, with attributes from the * &enum nl80211_peer_measurement_resp inside. + * @NL80211_PMSR_PEER_ATTR_REQ_TYPE: u32 attribute specifying the ranging + * request type, using values from &enum nl80211_peer_measurement_ftm_req_type. + * If absent, defaults to %NL80211_PMSR_FTM_REQ_TYPE_INFRA. * * @NUM_NL80211_PMSR_PEER_ATTRS: internal * @NL80211_PMSR_PEER_ATTR_MAX: highest attribute number @@ -8015,6 +8038,7 @@ enum nl80211_peer_measurement_peer_attrs { NL80211_PMSR_PEER_ATTR_CHAN, NL80211_PMSR_PEER_ATTR_REQ, NL80211_PMSR_PEER_ATTR_RESP, + NL80211_PMSR_PEER_ATTR_REQ_TYPE, /* keep last */ NUM_NL80211_PMSR_PEER_ATTRS, @@ -8238,9 +8262,11 @@ enum nl80211_peer_measurement_ftm_type_capa { * default 15 i.e. "no preference"). For non-EDCA ranging, this is the * burst duration in milliseconds (optional with default 0, i.e. let the * device decide). - * @NL80211_PMSR_FTM_REQ_ATTR_FTMS_PER_BURST: number of successful FTM frames - * requested per burst + * @NL80211_PMSR_FTM_REQ_ATTR_FTMS_PER_BURST: (Optional) number of successful + * FTM frames requested per burst * (u8, 0-31, optional with default 0 i.e. "no preference") + * If the attribute is absent ("no preference"), the driver or firmware can + * choose a suitable value. * @NL80211_PMSR_FTM_REQ_ATTR_NUM_FTMR_RETRIES: number of FTMR frame retries * (u8, default 3) * @NL80211_PMSR_FTM_REQ_ATTR_REQUEST_LCI: request LCI data (flag) @@ -8274,6 +8300,33 @@ enum nl80211_peer_measurement_ftm_type_capa { * Only valid if %NL80211_PMSR_FTM_REQ_ATTR_LMR_FEEDBACK is set (so the * RSTA will have the measurement results to report back in the FTM * response). + * @NL80211_PMSR_FTM_REQ_ATTR_MIN_TIME_BETWEEN_MEASUREMENTS: minimum time + * between two consecutive range measurements in units of 100 microseconds, + * for non-trigger based ranging (u32). Should be set as short as possible + * to minimize turnaround time, since two-way ranging with delayed LMR + * requires two measurements. Only valid if + * %NL80211_PMSR_FTM_REQ_ATTR_NON_TRIGGER_BASED is set. + * @NL80211_PMSR_FTM_REQ_ATTR_MAX_TIME_BETWEEN_MEASUREMENTS: maximum time + * between two consecutive range measurements in units of 10 milliseconds, + * for non-trigger based ranging (u32). Acts as a session timeout; if + * exceeded, the ranging session should be terminated. Only valid if + * %NL80211_PMSR_FTM_REQ_ATTR_NON_TRIGGER_BASED is set. + * @NL80211_PMSR_FTM_REQ_ATTR_NOMINAL_TIME: The nominal time field shall be + * set to the nominal duration between adjacent Availability Windows in + * units of milli seconds (u32). Mandatory if + * %NL80211_PMSR_FTM_REQ_ATTR_NON_TRIGGER_BASED is set. + * @NL80211_PMSR_FTM_REQ_ATTR_AW_DURATION: (Optional) The AW duration field + * shall be set to the duration of the AW in units of 1ms (0-255 ms) (u32). + * Only valid if %NL80211_PMSR_FTM_REQ_ATTR_NON_TRIGGER_BASED is set. + * If the attribute is absent ("no preference"), the driver or firmware + * can choose a suitable value. + * @NL80211_PMSR_FTM_REQ_ATTR_NUM_MEASUREMENTS: (Optional) number of + * Availability Windows (AWs) to schedule for non-trigger-based ranging. + * Each AW may contain multiple FTM exchanges as configured by + * %NL80211_PMSR_FTM_REQ_ATTR_FTMS_PER_BURST. Only valid if + * %NL80211_PMSR_FTM_REQ_ATTR_NON_TRIGGER_BASED is set. + * If the attribute is absent ("no preference"), the driver or firmware + * can choose a suitable value. * * @NUM_NL80211_PMSR_FTM_REQ_ATTR: internal * @NL80211_PMSR_FTM_REQ_ATTR_MAX: highest attribute number @@ -8295,6 +8348,11 @@ enum nl80211_peer_measurement_ftm_req { NL80211_PMSR_FTM_REQ_ATTR_LMR_FEEDBACK, NL80211_PMSR_FTM_REQ_ATTR_BSS_COLOR, NL80211_PMSR_FTM_REQ_ATTR_RSTA, + NL80211_PMSR_FTM_REQ_ATTR_MIN_TIME_BETWEEN_MEASUREMENTS, + NL80211_PMSR_FTM_REQ_ATTR_MAX_TIME_BETWEEN_MEASUREMENTS, + NL80211_PMSR_FTM_REQ_ATTR_NOMINAL_TIME, + NL80211_PMSR_FTM_REQ_ATTR_AW_DURATION, + NL80211_PMSR_FTM_REQ_ATTR_NUM_MEASUREMENTS, /* keep last */ NUM_NL80211_PMSR_FTM_REQ_ATTR, diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 0a87f4f81cd2..61ecf8fceb6a 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -476,6 +476,15 @@ nl80211_pmsr_ftm_req_attr_policy[NL80211_PMSR_FTM_REQ_ATTR_MAX + 1] = { [NL80211_PMSR_FTM_REQ_ATTR_LMR_FEEDBACK] = { .type = NLA_FLAG }, [NL80211_PMSR_FTM_REQ_ATTR_BSS_COLOR] = { .type = NLA_U8 }, [NL80211_PMSR_FTM_REQ_ATTR_RSTA] = { .type = NLA_FLAG }, + [NL80211_PMSR_FTM_REQ_ATTR_MIN_TIME_BETWEEN_MEASUREMENTS] = { + .type = NLA_U32 + }, + [NL80211_PMSR_FTM_REQ_ATTR_MAX_TIME_BETWEEN_MEASUREMENTS] = { + .type = NLA_U32 + }, + [NL80211_PMSR_FTM_REQ_ATTR_NOMINAL_TIME] = { .type = NLA_U32 }, + [NL80211_PMSR_FTM_REQ_ATTR_AW_DURATION] = NLA_POLICY_MAX(NLA_U32, 255), + [NL80211_PMSR_FTM_REQ_ATTR_NUM_MEASUREMENTS] = { .type = NLA_U32 }, }; static const struct nla_policy @@ -498,6 +507,8 @@ nl80211_pmsr_peer_attr_policy[NL80211_PMSR_PEER_ATTR_MAX + 1] = { [NL80211_PMSR_PEER_ATTR_REQ] = NLA_POLICY_NESTED(nl80211_pmsr_req_attr_policy), [NL80211_PMSR_PEER_ATTR_RESP] = { .type = NLA_REJECT }, + [NL80211_PMSR_PEER_ATTR_REQ_TYPE] = + NLA_POLICY_MAX(NLA_U32, NL80211_PMSR_FTM_REQ_TYPE_MAX), }; static const struct nla_policy diff --git a/net/wireless/pmsr.c b/net/wireless/pmsr.c index c21f693fac29..951ba0b96da2 100644 --- a/net/wireless/pmsr.c +++ b/net/wireless/pmsr.c @@ -91,11 +91,10 @@ static int pmsr_parse_ftm(struct cfg80211_registered_device *rdev, nla_get_u32(tb[NL80211_PMSR_FTM_REQ_ATTR_FTMS_PER_BURST]); if (capa->ftm.max_ftms_per_burst && - (out->ftm.ftms_per_burst > capa->ftm.max_ftms_per_burst || - out->ftm.ftms_per_burst == 0)) { + out->ftm.ftms_per_burst > capa->ftm.max_ftms_per_burst) { NL_SET_ERR_MSG_ATTR(info->extack, tb[NL80211_PMSR_FTM_REQ_ATTR_FTMS_PER_BURST], - "FTM: FTMs per burst must be set lower than the device limit but non-zero"); + "FTM: FTMs per burst must be set lower than the device limit"); return -EINVAL; } @@ -128,6 +127,14 @@ static int pmsr_parse_ftm(struct cfg80211_registered_device *rdev, return -EINVAL; } + if (out->ftm.request_type == NL80211_PMSR_FTM_REQ_TYPE_PD && + out->ftm.trigger_based) { + NL_SET_ERR_MSG_ATTR(info->extack, + ftmreq, + "FTM: TB ranging is not supported for PD request type"); + return -EINVAL; + } + out->ftm.non_trigger_based = !!tb[NL80211_PMSR_FTM_REQ_ATTR_NON_TRIGGER_BASED]; if (out->ftm.non_trigger_based && !capa->ftm.non_trigger_based) { @@ -143,6 +150,14 @@ static int pmsr_parse_ftm(struct cfg80211_registered_device *rdev, return -EINVAL; } + if (out->ftm.request_type == NL80211_PMSR_FTM_REQ_TYPE_PD && + out->ftm.non_trigger_based && out->ftm.ftms_per_burst > 4) { + NL_SET_ERR_MSG_ATTR(info->extack, + tb[NL80211_PMSR_FTM_REQ_ATTR_FTMS_PER_BURST], + "FTM: FTMs per burst must not exceed 4 for PD NTB ranging"); + return -ERANGE; + } + if (out->ftm.ftms_per_burst > 31 && !out->ftm.non_trigger_based && !out->ftm.trigger_based) { NL_SET_ERR_MSG_ATTR(info->extack, @@ -222,6 +237,33 @@ static int pmsr_parse_ftm(struct cfg80211_registered_device *rdev, return -EINVAL; } + if (out->ftm.non_trigger_based) { + if (out->ftm.request_type == NL80211_PMSR_FTM_REQ_TYPE_PD && + !tb[NL80211_PMSR_FTM_REQ_ATTR_NOMINAL_TIME]) { + NL_SET_ERR_MSG(info->extack, + "FTM: nominal time is required for PD NTB ranging"); + return -EINVAL; + } + out->ftm.nominal_time = + nla_get_u32(tb[NL80211_PMSR_FTM_REQ_ATTR_NOMINAL_TIME]); + + if (tb[NL80211_PMSR_FTM_REQ_ATTR_MIN_TIME_BETWEEN_MEASUREMENTS]) + out->ftm.min_time_between_measurements = + nla_get_u32(tb[NL80211_PMSR_FTM_REQ_ATTR_MIN_TIME_BETWEEN_MEASUREMENTS]); + + if (tb[NL80211_PMSR_FTM_REQ_ATTR_MAX_TIME_BETWEEN_MEASUREMENTS]) + out->ftm.max_time_between_measurements = + nla_get_u32(tb[NL80211_PMSR_FTM_REQ_ATTR_MAX_TIME_BETWEEN_MEASUREMENTS]); + + if (tb[NL80211_PMSR_FTM_REQ_ATTR_AW_DURATION]) + out->ftm.availability_window = + nla_get_u8(tb[NL80211_PMSR_FTM_REQ_ATTR_AW_DURATION]); + + if (tb[NL80211_PMSR_FTM_REQ_ATTR_NUM_MEASUREMENTS]) + out->ftm.num_measurements = + nla_get_u32(tb[NL80211_PMSR_FTM_REQ_ATTR_NUM_MEASUREMENTS]); + } + return 0; } @@ -249,6 +291,19 @@ static int pmsr_parse_peer(struct cfg80211_registered_device *rdev, memcpy(out->addr, nla_data(tb[NL80211_PMSR_PEER_ATTR_ADDR]), ETH_ALEN); + if (tb[NL80211_PMSR_PEER_ATTR_REQ_TYPE]) + out->ftm.request_type = + nla_get_u32(tb[NL80211_PMSR_PEER_ATTR_REQ_TYPE]); + else + out->ftm.request_type = NL80211_PMSR_FTM_REQ_TYPE_INFRA; + + if (out->ftm.request_type == NL80211_PMSR_FTM_REQ_TYPE_PD && + !rdev->wiphy.pmsr_capa->ftm.type.pd_support) { + NL_SET_ERR_MSG_ATTR(info->extack, + tb[NL80211_PMSR_PEER_ATTR_REQ_TYPE], + "FTM: PD request type not supported by device"); + return -EINVAL; + } /* reuse info->attrs */ memset(info->attrs, 0, sizeof(*info->attrs) * (NL80211_ATTR_MAX + 1)); err = nla_parse_nested_deprecated(info->attrs, NL80211_ATTR_MAX, -- cgit v1.2.3 From 4e901e99edd2c289e6917cb79027e39c98d711e2 Mon Sep 17 00:00:00 2001 From: Peddolla Harshavardhan Reddy Date: Mon, 20 Apr 2026 14:38:50 +0530 Subject: wifi: cfg80211: extend PMSR FTM response for proximity ranging Applications need negotiated session parameters to interpret proximity ranging results and perform post-processing. Currently, the FTM response lacks LTF repetition counts, time constraints, spatial stream configuration, and availability window parameters. Extend the FTM response structure to report these negotiated parameters, enabling applications to track session configuration and use them in post-processing to increase ranging precision. Signed-off-by: Peddolla Harshavardhan Reddy Link: https://patch.msgid.link/20260420090856.2152905-8-peddolla.reddy@oss.qualcomm.com Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 56 ++++++++++++++++++++++++++++++++++++++++++-- include/uapi/linux/nl80211.h | 38 ++++++++++++++++++++++++++++++ net/wireless/pmsr.c | 15 ++++++++++++ 3 files changed, 107 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 28923539456e..ad32353e87e0 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -4436,6 +4436,25 @@ struct cfg80211_ftm_responder_stats { * (must have either this or @rtt_avg) * @dist_variance: variance of distances measured (see also @rtt_variance) * @dist_spread: spread of distances measured (see also @rtt_spread) + * @tx_ltf_repetition_count: negotiated value of number of tx ltf repetitions + * in NDP frames + * @rx_ltf_repetition_count: negotiated value of number of rx ltf repetitions + * in NDP frames + * @max_time_between_measurements: the negotiated maximum interval (in units of + * 10 ms) by which the ISTA must complete the next measurement cycle. + * @min_time_between_measurements: the negotiated minimum interval (in units of + * 100 us) between two consecutive range measurements initiated by the + * ISTA. + * @num_tx_spatial_streams: number of Tx space-time streams used in the NDP + * frame during the measurement sounding phase. + * @num_rx_spatial_streams: number of Rx space-time streams used in the NDP + * frame during the measurement sounding phase. + * @nominal_time: negotiated nominal duration between adjacent availability + * windows in units of milliseconds (u32). + * @availability_window: negotiated availability window time used in this + * session in units of milliseconds (u8). + * @chan_width: band width used for measurement. + * @preamble: preamble used for measurement. * @num_ftmr_attempts_valid: @num_ftmr_attempts is valid * @num_ftmr_successes_valid: @num_ftmr_successes is valid * @rssi_avg_valid: @rssi_avg is valid @@ -4448,6 +4467,18 @@ struct cfg80211_ftm_responder_stats { * @dist_avg_valid: @dist_avg is valid * @dist_variance_valid: @dist_variance is valid * @dist_spread_valid: @dist_spread is valid + * @tx_ltf_repetition_count_valid: @tx_ltf_repetition_count is valid + * @rx_ltf_repetition_count_valid: @rx_ltf_repetition_count is valid + * @max_time_between_measurements_valid: @max_time_between_measurements is valid + * @min_time_between_measurements_valid: @min_time_between_measurements is valid + * @num_tx_spatial_streams_valid: @num_tx_spatial_streams is valid + * @num_rx_spatial_streams_valid: @num_rx_spatial_streams is valid + * @nominal_time_valid: @nominal_time is valid + * @availability_window_valid: @availability_window is valid + * @chan_width_valid: @chan_width is valid. + * @preamble_valid: @preamble is valid. + * @is_delayed_lmr: indicates if the reported LMR is of the current burst or the + * previous burst, flag. */ struct cfg80211_pmsr_ftm_result { const u8 *lci; @@ -4471,8 +4502,18 @@ struct cfg80211_pmsr_ftm_result { s64 dist_avg; s64 dist_variance; s64 dist_spread; + u32 tx_ltf_repetition_count; + u32 rx_ltf_repetition_count; + u32 max_time_between_measurements; + u32 min_time_between_measurements; + u8 num_tx_spatial_streams; + u8 num_rx_spatial_streams; + u32 nominal_time; + u8 availability_window; + enum nl80211_chan_width chan_width; + enum nl80211_preamble preamble; - u16 num_ftmr_attempts_valid:1, + u32 num_ftmr_attempts_valid:1, num_ftmr_successes_valid:1, rssi_avg_valid:1, rssi_spread_valid:1, @@ -4483,7 +4524,18 @@ struct cfg80211_pmsr_ftm_result { rtt_spread_valid:1, dist_avg_valid:1, dist_variance_valid:1, - dist_spread_valid:1; + dist_spread_valid:1, + tx_ltf_repetition_count_valid:1, + rx_ltf_repetition_count_valid:1, + max_time_between_measurements_valid:1, + min_time_between_measurements_valid:1, + num_tx_spatial_streams_valid:1, + num_rx_spatial_streams_valid:1, + nominal_time_valid:1, + availability_window_valid:1, + chan_width_valid:1, + preamble_valid:1, + is_delayed_lmr:1; }; /** diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 6be89e656276..08e8e4de650c 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -8439,6 +8439,33 @@ enum nl80211_peer_measurement_ftm_failure_reasons { * @NL80211_PMSR_FTM_RESP_ATTR_PAD: ignore, for u64/s64 padding only * @NL80211_PMSR_FTM_RESP_ATTR_BURST_PERIOD: actual burst period used by * the responder (similar to request, u16) + * @NL80211_PMSR_FTM_RESP_ATTR_TX_LTF_REPETITION_COUNT: negotiated value of + * number of tx ltf repetitions in NDP frames (u32, optional) + * @NL80211_PMSR_FTM_RESP_ATTR_RX_LTF_REPETITION_COUNT: negotiated value of + * number of rx ltf repetitions in NDP frames (u32, optional) + * @NL80211_PMSR_FTM_RESP_ATTR_MAX_TIME_BETWEEN_MEASUREMENTS: negotiated value + * where latest time by which the ISTA needs to complete the next round of + * measurements, in units of 10 ms (u32, optional) + * @NL80211_PMSR_FTM_RESP_ATTR_MIN_TIME_BETWEEN_MEASUREMENTS: negotiated + * minimum time between two consecutive range measurements initiated by an + * ISTA, in units of 100 us (u32, optional) + * @NL80211_PMSR_FTM_RESP_ATTR_NUM_TX_SPATIAL_STREAMS: number of Tx space-time + * streams used in NDP frames during the measurement sounding phase + * (u32, optional). + * @NL80211_PMSR_FTM_RESP_ATTR_NUM_RX_SPATIAL_STREAMS: number of Rx space-time + * streams used in the NDP frames during the measurement sounding phase + * (u32, optional) + * @NL80211_PMSR_FTM_RESP_ATTR_NOMINAL_TIME: negotiated nominal time used in + * this session in milliseconds. (u32, optional) + * @NL80211_PMSR_FTM_RESP_ATTR_AVAILABILITY_WINDOW: negotiated availability + * window time used in this session, in units of milli seconds. + * (u32, optional) + * @NL80211_PMSR_FTM_RESP_ATTR_CHANNEL_WIDTH: u32 attribute indicating channel + * width used for measurement, see &enum nl80211_chan_width (optional). + * @NL80211_PMSR_FTM_RESP_ATTR_PREAMBLE: u32 attribute indicating the preamble + * type used for the measurement, see &enum nl80211_preamble (optional). + * @NL80211_PMSR_FTM_RESP_ATTR_IS_DELAYED_LMR: flag, indicates if the + * current result is delayed LMR data. * * @NUM_NL80211_PMSR_FTM_RESP_ATTR: internal * @NL80211_PMSR_FTM_RESP_ATTR_MAX: highest attribute number @@ -8468,6 +8495,17 @@ enum nl80211_peer_measurement_ftm_resp { NL80211_PMSR_FTM_RESP_ATTR_CIVICLOC, NL80211_PMSR_FTM_RESP_ATTR_PAD, NL80211_PMSR_FTM_RESP_ATTR_BURST_PERIOD, + NL80211_PMSR_FTM_RESP_ATTR_TX_LTF_REPETITION_COUNT, + NL80211_PMSR_FTM_RESP_ATTR_RX_LTF_REPETITION_COUNT, + NL80211_PMSR_FTM_RESP_ATTR_MAX_TIME_BETWEEN_MEASUREMENTS, + NL80211_PMSR_FTM_RESP_ATTR_MIN_TIME_BETWEEN_MEASUREMENTS, + NL80211_PMSR_FTM_RESP_ATTR_NUM_TX_SPATIAL_STREAMS, + NL80211_PMSR_FTM_RESP_ATTR_NUM_RX_SPATIAL_STREAMS, + NL80211_PMSR_FTM_RESP_ATTR_NOMINAL_TIME, + NL80211_PMSR_FTM_RESP_ATTR_AVAILABILITY_WINDOW, + NL80211_PMSR_FTM_RESP_ATTR_CHANNEL_WIDTH, + NL80211_PMSR_FTM_RESP_ATTR_PREAMBLE, + NL80211_PMSR_FTM_RESP_ATTR_IS_DELAYED_LMR, /* keep last */ NUM_NL80211_PMSR_FTM_RESP_ATTR, diff --git a/net/wireless/pmsr.c b/net/wireless/pmsr.c index 951ba0b96da2..caffa2421c20 100644 --- a/net/wireless/pmsr.c +++ b/net/wireless/pmsr.c @@ -562,6 +562,21 @@ static int nl80211_pmsr_send_ftm_res(struct sk_buff *msg, PUTOPT_U64(DIST_AVG, dist_avg); PUTOPT_U64(DIST_VARIANCE, dist_variance); PUTOPT_U64(DIST_SPREAD, dist_spread); + PUTOPT(u32, TX_LTF_REPETITION_COUNT, tx_ltf_repetition_count); + PUTOPT(u32, RX_LTF_REPETITION_COUNT, rx_ltf_repetition_count); + PUTOPT(u32, MAX_TIME_BETWEEN_MEASUREMENTS, + max_time_between_measurements); + PUTOPT(u32, MIN_TIME_BETWEEN_MEASUREMENTS, + min_time_between_measurements); + PUTOPT(u8, NUM_TX_SPATIAL_STREAMS, num_tx_spatial_streams); + PUTOPT(u8, NUM_RX_SPATIAL_STREAMS, num_rx_spatial_streams); + PUTOPT(u32, NOMINAL_TIME, nominal_time); + PUTOPT(u8, AVAILABILITY_WINDOW, availability_window); + PUTOPT(u32, CHANNEL_WIDTH, chan_width); + PUTOPT(u32, PREAMBLE, preamble); + if (res->ftm.is_delayed_lmr && + nla_put_flag(msg, NL80211_PMSR_FTM_RESP_ATTR_IS_DELAYED_LMR)) + goto error; if (res->ftm.lci && res->ftm.lci_len && nla_put(msg, NL80211_PMSR_FTM_RESP_ATTR_LCI, res->ftm.lci_len, res->ftm.lci)) -- cgit v1.2.3 From ea996c2c036df21fbd3b195e6c5c42c11eadb6c4 Mon Sep 17 00:00:00 2001 From: Peddolla Harshavardhan Reddy Date: Mon, 20 Apr 2026 14:38:51 +0530 Subject: wifi: cfg80211: add role-based peer limits to FTM capabilities Peer measurement capabilities currently advertise a single maximum peer count regardless of device role. Some devices support different peer limits when operating as initiator versus responder. Add max_peers fields inside the ftm.ista and ftm.rsta sub-structs of cfg80211_pmsr_capabilities to allow drivers to advertise per-role peer limits. These limits are generic and not restricted to any specific ranging type. Expose these over nl80211 using new NL80211_PMSR_ATTR_MAX_PEER_ISTA_ROLE and NL80211_PMSR_ATTR_MAX_PEER_RSTA_ROLE attributes inside the ISTA_CAPS and RSTA_CAPS nested attributes respectively. When a role limit is advertised, validate the number of peers in the request separately for each role using the existing rsta flag in the FTM request, and reject the request if the limit is exceeded. Signed-off-by: Peddolla Harshavardhan Reddy Link: https://patch.msgid.link/20260420090856.2152905-9-peddolla.reddy@oss.qualcomm.com Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 6 ++++++ include/uapi/linux/nl80211.h | 14 ++++++++++++++ net/wireless/nl80211.c | 8 ++++++++ net/wireless/pmsr.c | 46 ++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 70 insertions(+), 4 deletions(-) (limited to 'include') diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index ad32353e87e0..5669948e0f3d 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -6003,6 +6003,8 @@ cfg80211_get_iftype_ext_capa(struct wiphy *wiphy, enum nl80211_iftype type); * TB ranging. * @ftm.ista.support_edca: supports operating as ISTA in PMSR FTM request for * EDCA based ranging. + * @ftm.ista.max_peers: maximum number of peers supported in the ISTA role. + * If zero, no role-specific peer limit applies. * @ftm.rsta: responder role capabilities * @ftm.rsta.support_ntb: supports operating as RSTA in PMSR FTM request for * NTB ranging. @@ -6010,6 +6012,8 @@ cfg80211_get_iftype_ext_capa(struct wiphy *wiphy, enum nl80211_iftype type); * TB ranging. * @ftm.rsta.support_edca: supports operating as RSTA in PMSR FTM request for * EDCA based ranging. + * @ftm.rsta.max_peers: maximum number of peers supported in the RSTA role. + * If zero, no role-specific peer limit applies. * @ftm.max_no_of_tx_antennas: maximum number of transmit antennas supported for * EDCA based ranging (0 means unknown) * @ftm.max_no_of_rx_antennas: maximum number of receive antennas supported for @@ -6064,11 +6068,13 @@ struct cfg80211_pmsr_capabilities { u8 support_ntb:1, support_tb:1, support_edca:1; + u32 max_peers; } ista; struct { u8 support_ntb:1, support_tb:1, support_edca:1; + u32 max_peers; } rsta; u8 max_no_of_tx_antennas; u8 max_no_of_rx_antennas; diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 08e8e4de650c..e31ff2a745b2 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -8065,6 +8065,18 @@ enum nl80211_peer_measurement_peer_attrs { * meaningless, just a list of peers to measure with, with the * sub-attributes taken from * &enum nl80211_peer_measurement_peer_attrs. + * @NL80211_PMSR_ATTR_MAX_PEER_ISTA_ROLE: u32 attribute indicating the + * maximum number of peers supported when the device operates in the + * ISTA (Initiator STA) role. If absent, no role-specific peer limit + * applies. The sum of %NL80211_PMSR_ATTR_MAX_PEER_ISTA_ROLE and + * %NL80211_PMSR_ATTR_MAX_PEER_RSTA_ROLE is enforced when the device + * supports concurrent ISTA/RSTA operation. + * @NL80211_PMSR_ATTR_MAX_PEER_RSTA_ROLE: u32 attribute indicating the + * maximum number of peers supported when the device operates in the + * RSTA (Responder STA) role. If absent, no role-specific peer limit + * applies. The sum of %NL80211_PMSR_ATTR_MAX_PEER_ISTA_ROLE and + * %NL80211_PMSR_ATTR_MAX_PEER_RSTA_ROLE is enforced when the device + * supports concurrent ISTA/RSTA operation. * * @NUM_NL80211_PMSR_ATTR: internal * @NL80211_PMSR_ATTR_MAX: highest attribute number @@ -8077,6 +8089,8 @@ enum nl80211_peer_measurement_attrs { NL80211_PMSR_ATTR_RANDOMIZE_MAC_ADDR, NL80211_PMSR_ATTR_TYPE_CAPA, NL80211_PMSR_ATTR_PEERS, + NL80211_PMSR_ATTR_MAX_PEER_ISTA_ROLE, + NL80211_PMSR_ATTR_MAX_PEER_RSTA_ROLE, /* keep last */ NUM_NL80211_PMSR_ATTR, diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 61ecf8fceb6a..0ffaa2cd7808 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -2532,6 +2532,10 @@ nl80211_send_pmsr_ftm_capa(const struct cfg80211_pmsr_capabilities *cap, if (cap->ftm.ista.support_edca && nla_put_flag(msg, NL80211_PMSR_FTM_CAPA_ATTR_SUPPORT_EDCA)) return -ENOBUFS; + if (cap->ftm.ista.max_peers && + nla_put_u32(msg, NL80211_PMSR_ATTR_MAX_PEER_ISTA_ROLE, + cap->ftm.ista.max_peers)) + return -ENOBUFS; nla_nest_end(msg, ista_caps); } @@ -2560,6 +2564,10 @@ nl80211_send_pmsr_ftm_capa(const struct cfg80211_pmsr_capabilities *cap, if (cap->ftm.rsta.support_edca && nla_put_flag(msg, NL80211_PMSR_FTM_CAPA_ATTR_SUPPORT_EDCA)) return -ENOBUFS; + if (cap->ftm.rsta.max_peers && + nla_put_u32(msg, NL80211_PMSR_ATTR_MAX_PEER_RSTA_ROLE, + cap->ftm.rsta.max_peers)) + return -ENOBUFS; nla_nest_end(msg, rsta_caps); } diff --git a/net/wireless/pmsr.c b/net/wireless/pmsr.c index caffa2421c20..432d34be7945 100644 --- a/net/wireless/pmsr.c +++ b/net/wireless/pmsr.c @@ -361,12 +361,15 @@ int nl80211_pmsr_start(struct sk_buff *skb, struct genl_info *info) { struct nlattr *reqattr = info->attrs[NL80211_ATTR_PEER_MEASUREMENTS]; struct cfg80211_registered_device *rdev = info->user_ptr[0]; + int count, rem, err, idx, peer_count; struct wireless_dev *wdev = info->user_ptr[1]; + const struct cfg80211_pmsr_capabilities *capa; struct cfg80211_pmsr_request *req; struct nlattr *peers, *peer; - int count, rem, err, idx; - if (!rdev->wiphy.pmsr_capa) + capa = rdev->wiphy.pmsr_capa; + + if (!capa) return -EOPNOTSUPP; if (!reqattr) @@ -381,7 +384,7 @@ int nl80211_pmsr_start(struct sk_buff *skb, struct genl_info *info) nla_for_each_nested(peer, peers, rem) { count++; - if (count > rdev->wiphy.pmsr_capa->max_peers) { + if (count > capa->max_peers) { NL_SET_ERR_MSG_ATTR(info->extack, peer, "Too many peers used"); return -EINVAL; @@ -397,7 +400,7 @@ int nl80211_pmsr_start(struct sk_buff *skb, struct genl_info *info) req->timeout = nla_get_u32(info->attrs[NL80211_ATTR_TIMEOUT]); if (info->attrs[NL80211_ATTR_MAC]) { - if (!rdev->wiphy.pmsr_capa->randomize_mac_addr) { + if (!capa->randomize_mac_addr) { NL_SET_ERR_MSG_ATTR(info->extack, info->attrs[NL80211_ATTR_MAC], "device cannot randomize MAC address"); @@ -422,6 +425,41 @@ int nl80211_pmsr_start(struct sk_buff *skb, struct genl_info *info) goto out_err; idx++; } + + /* Validate per-role peer limits if advertised */ + if (capa->ftm.ista.max_peers) { + peer_count = 0; + + for (idx = 0; idx < req->n_peers; idx++) { + if (!req->peers[idx].ftm.rsta) { + peer_count++; + + if (peer_count > capa->ftm.ista.max_peers) { + NL_SET_ERR_MSG(info->extack, + "Too many ISTA peers for device limit"); + err = -EINVAL; + goto out_err; + } + } + } + } + + if (capa->ftm.rsta.max_peers) { + peer_count = 0; + + for (idx = 0; idx < req->n_peers; idx++) { + if (req->peers[idx].ftm.rsta) { + peer_count++; + + if (peer_count > capa->ftm.rsta.max_peers) { + NL_SET_ERR_MSG(info->extack, + "Too many RSTA peers for device limit"); + err = -EINVAL; + goto out_err; + } + } + } + } req->cookie = cfg80211_assign_cookie(rdev); req->nl_portid = info->snd_portid; -- cgit v1.2.3 From 99529edd28df505576366753e204ba78a406659b Mon Sep 17 00:00:00 2001 From: Peddolla Harshavardhan Reddy Date: Mon, 20 Apr 2026 14:38:52 +0530 Subject: wifi: cfg80211: add ingress/egress distance thresholds for FTM Proximity detection applications need to receive measurement results only when devices cross specific distance boundaries to avoid unnecessary host wakeups and reduce power consumption. Introduce configurable distance-based reporting thresholds that drivers can use to implement selective result reporting. Add ingress and egress distance parameters allowing applications to specify when results should be reported as peers cross these boundaries. Signed-off-by: Peddolla Harshavardhan Reddy Link: https://patch.msgid.link/20260420090856.2152905-10-peddolla.reddy@oss.qualcomm.com [remove mm units from variables] Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 14 ++++++++++++++ include/uapi/linux/nl80211.h | 15 +++++++++++++++ net/wireless/nl80211.c | 2 ++ net/wireless/pmsr.c | 8 ++++++++ 4 files changed, 39 insertions(+) (limited to 'include') diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 5669948e0f3d..87e848750339 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -4622,6 +4622,18 @@ struct cfg80211_pmsr_result { * exchanges as configured by @ftms_per_burst. Only valid if * @non_trigger_based is set. If set to 0, the firmware or driver * can automatically select an appropriate value. + * @ingress_distance: optional ingress threshold in units of mm. When set, + * the measurement result of the peer needs to be indicated if the device + * moves into this range. Measurement results need to be sent on a burst + * index basis in this case. + * @egress_distance: optional egress threshold in units of mm. When set, + * the measurement result of the peer needs to be indicated if the device + * moves out of this range. Measurement results need to be sent on a burst + * index basis in this case. + * If neither or only one of @ingress_distance and @egress_distance + * is set, only the specified threshold is used. If both are set, both + * thresholds are applied. If neither is set, results are reported without + * threshold filtering. * * See also nl80211 for the respective attribute documentation. */ @@ -4648,6 +4660,8 @@ struct cfg80211_pmsr_ftm_request_peer { u8 availability_window; u32 nominal_time; u32 num_measurements; + u64 ingress_distance; + u64 egress_distance; }; /** diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index e31ff2a745b2..02fe42b4f29c 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -8341,6 +8341,18 @@ enum nl80211_peer_measurement_ftm_type_capa { * %NL80211_PMSR_FTM_REQ_ATTR_NON_TRIGGER_BASED is set. * If the attribute is absent ("no preference"), the driver or firmware * can choose a suitable value. + * @NL80211_PMSR_FTM_REQ_ATTR_PAD: ignore, for u64/s64 padding only. + * @NL80211_PMSR_FTM_REQ_ATTR_INGRESS: optional u64 attribute in units of mm. + * When specified, the measurement result of the peer needs to be + * indicated if the device moves into this range. + * @NL80211_PMSR_FTM_REQ_ATTR_EGRESS: optional u64 attribute in units of mm. + * When specified, the measurement result of the peer needs to be + * indicated if the device moves out of this range. + * If neither or only one of @NL80211_PMSR_FTM_REQ_ATTR_INGRESS and + * @NL80211_PMSR_FTM_REQ_ATTR_EGRESS is specified, only the specified + * threshold is used. If both are specified, both thresholds are applied. + * If neither is specified, results are reported without threshold + * filtering. * * @NUM_NL80211_PMSR_FTM_REQ_ATTR: internal * @NL80211_PMSR_FTM_REQ_ATTR_MAX: highest attribute number @@ -8367,6 +8379,9 @@ enum nl80211_peer_measurement_ftm_req { NL80211_PMSR_FTM_REQ_ATTR_NOMINAL_TIME, NL80211_PMSR_FTM_REQ_ATTR_AW_DURATION, NL80211_PMSR_FTM_REQ_ATTR_NUM_MEASUREMENTS, + NL80211_PMSR_FTM_REQ_ATTR_PAD, + NL80211_PMSR_FTM_REQ_ATTR_INGRESS, + NL80211_PMSR_FTM_REQ_ATTR_EGRESS, /* keep last */ NUM_NL80211_PMSR_FTM_REQ_ATTR, diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 0ffaa2cd7808..ae968d53fcea 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -485,6 +485,8 @@ nl80211_pmsr_ftm_req_attr_policy[NL80211_PMSR_FTM_REQ_ATTR_MAX + 1] = { [NL80211_PMSR_FTM_REQ_ATTR_NOMINAL_TIME] = { .type = NLA_U32 }, [NL80211_PMSR_FTM_REQ_ATTR_AW_DURATION] = NLA_POLICY_MAX(NLA_U32, 255), [NL80211_PMSR_FTM_REQ_ATTR_NUM_MEASUREMENTS] = { .type = NLA_U32 }, + [NL80211_PMSR_FTM_REQ_ATTR_INGRESS] = { .type = NLA_U64 }, + [NL80211_PMSR_FTM_REQ_ATTR_EGRESS] = { .type = NLA_U64 }, }; static const struct nla_policy diff --git a/net/wireless/pmsr.c b/net/wireless/pmsr.c index 432d34be7945..2d6ace7f048d 100644 --- a/net/wireless/pmsr.c +++ b/net/wireless/pmsr.c @@ -264,6 +264,14 @@ static int pmsr_parse_ftm(struct cfg80211_registered_device *rdev, nla_get_u32(tb[NL80211_PMSR_FTM_REQ_ATTR_NUM_MEASUREMENTS]); } + if (tb[NL80211_PMSR_FTM_REQ_ATTR_INGRESS]) + out->ftm.ingress_distance = + nla_get_u64(tb[NL80211_PMSR_FTM_REQ_ATTR_INGRESS]); + + if (tb[NL80211_PMSR_FTM_REQ_ATTR_EGRESS]) + out->ftm.egress_distance = + nla_get_u64(tb[NL80211_PMSR_FTM_REQ_ATTR_EGRESS]); + return 0; } -- cgit v1.2.3 From 5733daa670dc1e1464be59dfcacaa76597201b3e Mon Sep 17 00:00:00 2001 From: Peddolla Harshavardhan Reddy Date: Mon, 20 Apr 2026 14:38:53 +0530 Subject: wifi: cfg80211: add PD-specific preamble and bandwidth capabilities Devices may support different preamble and bandwidth configurations for proximity detection (PD) ranging versus standard ranging. Add separate pd_preambles and pd_bandwidths fields to cfg80211_pmsr_capabilities to allow drivers to advertise PD-specific capabilities. Expose these over nl80211 using new attributes NL80211_PMSR_FTM_CAPA_ATTR_PD_PREAMBLES and NL80211_PMSR_FTM_CAPA_ATTR_PD_BANDWIDTHS, advertised only when pd_support is set. For PD requests, validate bandwidth and preamble against pd_bandwidths and pd_preambles. For non-PD requests, validate against the existing bandwidths and preambles fields. Signed-off-by: Peddolla Harshavardhan Reddy Link: https://patch.msgid.link/20260420090856.2152905-11-peddolla.reddy@oss.qualcomm.com Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 6 ++++++ include/uapi/linux/nl80211.h | 10 ++++++++++ net/wireless/nl80211.c | 12 ++++++++++++ net/wireless/pmsr.c | 21 +++++++++++++++++++-- 4 files changed, 47 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 87e848750339..f5a47a0c7532 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -6053,6 +6053,10 @@ cfg80211_get_iftype_ext_capa(struct wiphy *wiphy, enum nl80211_iftype type); * @ftm.concurrent_ista_rsta_support: indicates if the device can * simultaneously act as initiator and responder in a multi-peer * measurement request. Only valid if @ftm.rsta_support is set. + * @ftm.pd_preambles: bitmap of preambles supported (&enum nl80211_preamble) + * for PD ranging requests. Ignored if @ftm.type.pd_support is not set. + * @ftm.pd_bandwidths: bitmap of bandwidths supported (&enum nl80211_chan_width) + * for PD ranging requests. Ignored if @ftm.type.pd_support is not set. */ struct cfg80211_pmsr_capabilities { unsigned int max_peers; @@ -6099,6 +6103,8 @@ struct cfg80211_pmsr_capabilities { pd_support:1; } type; u8 concurrent_ista_rsta_support:1; + u32 pd_preambles; + u32 pd_bandwidths; } ftm; }; diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 02fe42b4f29c..5bef8fd25270 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -8189,6 +8189,14 @@ enum nl80211_peer_measurement_attrs { * to estimate the burst period to be given in the FTM request for the * NTB ranging case. If non-zero, this value will be used to validate * the nominal time in the FTM request. + * @NL80211_PMSR_FTM_CAPA_ATTR_PD_PREAMBLES: u32 bitmap of values from + * &enum nl80211_preamble indicating the supported preambles for PD + * ranging requests. Only valid if %NL80211_PMSR_FTM_TYPE_CAPA_ATTR_PD_SUPPORT + * is set. + * @NL80211_PMSR_FTM_CAPA_ATTR_PD_BANDWIDTHS: u32 bitmap of values from + * &enum nl80211_chan_width indicating the supported channel bandwidths + * for PD ranging requests. Only valid if + * %NL80211_PMSR_FTM_TYPE_CAPA_ATTR_PD_SUPPORT is set. * * @NUM_NL80211_PMSR_FTM_CAPA_ATTR: internal * @NL80211_PMSR_FTM_CAPA_ATTR_MAX: highest attribute number @@ -8225,6 +8233,8 @@ enum nl80211_peer_measurement_ftm_capa { NL80211_PMSR_FTM_CAPA_ATTR_MAX_NUM_RX_ANTENNAS, NL80211_PMSR_FTM_CAPA_ATTR_MIN_INTERVAL_EDCA, NL80211_PMSR_FTM_CAPA_ATTR_MIN_INTERVAL_NTB, + NL80211_PMSR_FTM_CAPA_ATTR_PD_PREAMBLES, + NL80211_PMSR_FTM_CAPA_ATTR_PD_BANDWIDTHS, /* keep last */ NUM_NL80211_PMSR_FTM_CAPA_ATTR, diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index ae968d53fcea..84f17032fdd9 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -2615,6 +2615,18 @@ nl80211_send_pmsr_ftm_capa(const struct cfg80211_pmsr_capabilities *cap, if (cap->ftm.concurrent_ista_rsta_support && nla_put_flag(msg, NL80211_PMSR_FTM_CAPA_ATTR_CONCURRENT_ISTA_RSTA_SUPPORT)) return -ENOBUFS; + + if (cap->ftm.type.pd_support) { + if (cap->ftm.pd_preambles && + nla_put_u32(msg, NL80211_PMSR_FTM_CAPA_ATTR_PD_PREAMBLES, + cap->ftm.pd_preambles)) + return -ENOBUFS; + if (cap->ftm.pd_bandwidths && + nla_put_u32(msg, NL80211_PMSR_FTM_CAPA_ATTR_PD_BANDWIDTHS, + cap->ftm.pd_bandwidths)) + return -ENOBUFS; + } + nla_nest_end(msg, ftm); return 0; } diff --git a/net/wireless/pmsr.c b/net/wireless/pmsr.c index 2d6ace7f048d..8b7843f75db2 100644 --- a/net/wireless/pmsr.c +++ b/net/wireless/pmsr.c @@ -17,11 +17,19 @@ static int pmsr_parse_ftm(struct cfg80211_registered_device *rdev, u32 preamble = NL80211_PREAMBLE_DMG; /* only optional in DMG */ /* validate existing data */ - if (!(rdev->wiphy.pmsr_capa->ftm.bandwidths & BIT(out->chandef.width))) { + if (out->ftm.request_type == NL80211_PMSR_FTM_REQ_TYPE_INFRA && + !(capa->ftm.bandwidths & BIT(out->chandef.width))) { NL_SET_ERR_MSG(info->extack, "FTM: unsupported bandwidth"); return -EINVAL; } + if (out->ftm.request_type == NL80211_PMSR_FTM_REQ_TYPE_PD && + !(capa->ftm.pd_bandwidths & BIT(out->chandef.width))) { + NL_SET_ERR_MSG(info->extack, + "FTM: unsupported bandwidth for PD request"); + return -EINVAL; + } + /* no validation needed - was already done via nested policy */ nla_parse_nested_deprecated(tb, NL80211_PMSR_FTM_REQ_ATTR_MAX, ftmreq, NULL, NULL); @@ -44,13 +52,22 @@ static int pmsr_parse_ftm(struct cfg80211_registered_device *rdev, } } - if (!(capa->ftm.preambles & BIT(preamble))) { + if (out->ftm.request_type == NL80211_PMSR_FTM_REQ_TYPE_INFRA && + !(capa->ftm.preambles & BIT(preamble))) { NL_SET_ERR_MSG_ATTR(info->extack, tb[NL80211_PMSR_FTM_REQ_ATTR_PREAMBLE], "FTM: invalid preamble"); return -EINVAL; } + if (out->ftm.request_type == NL80211_PMSR_FTM_REQ_TYPE_PD && + !(capa->ftm.pd_preambles & BIT(preamble))) { + NL_SET_ERR_MSG_ATTR(info->extack, + tb[NL80211_PMSR_FTM_REQ_ATTR_PREAMBLE], + "FTM: invalid preamble for PD request"); + return -EINVAL; + } + out->ftm.preamble = preamble; out->ftm.burst_period = 0; -- cgit v1.2.3 From 410aa47fd9d308029f3520e97eec71a8eb508622 Mon Sep 17 00:00:00 2001 From: Peddolla Harshavardhan Reddy Date: Mon, 20 Apr 2026 14:38:54 +0530 Subject: wifi: cfg80211: allow suppressing FTM result reporting for PD requests Proximity detection often does not require detailed ranging measurements, yet userspace currently receives full FTM results for every request, causing unnecessary data transfer, host wakeups, and processing overhead. Add an optional control to suppress ranging result reporting for peer-to-peer PD requests. Introduce the NL80211_PMSR_FTM_REQ_ATTR_PD_SUPPRESS_RESULTS flag; when set with a PD request, the device may perform the measurements (e.g. when acting as RSTA) but must not report the measurement results to userspace. Validate that the flag is only accepted when request_type is set to NL80211_PMSR_FTM_REQ_TYPE_PD, reject otherwise. Signed-off-by: Peddolla Harshavardhan Reddy Link: https://patch.msgid.link/20260420090856.2152905-12-peddolla.reddy@oss.qualcomm.com Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 6 ++++++ include/uapi/linux/nl80211.h | 6 ++++++ net/wireless/nl80211.c | 1 + net/wireless/pmsr.c | 11 +++++++++++ 4 files changed, 24 insertions(+) (limited to 'include') diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index f5a47a0c7532..fdc8363b296c 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -4634,6 +4634,11 @@ struct cfg80211_pmsr_result { * is set, only the specified threshold is used. If both are set, both * thresholds are applied. If neither is set, results are reported without * threshold filtering. + * @pd_suppress_range_results: flag to suppress ranging results for PD + * requests. When set, the device performs ranging measurements to + * provide ranging services to a peer (e.g. in RSTA role) but does + * not report the measurement results to userspace. Only valid when + * @request_type is %NL80211_PMSR_FTM_REQ_TYPE_PD. * * See also nl80211 for the respective attribute documentation. */ @@ -4662,6 +4667,7 @@ struct cfg80211_pmsr_ftm_request_peer { u32 num_measurements; u64 ingress_distance; u64 egress_distance; + u8 pd_suppress_range_results:1; }; /** diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 5bef8fd25270..1da4dc3fc816 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -8363,6 +8363,11 @@ enum nl80211_peer_measurement_ftm_type_capa { * threshold is used. If both are specified, both thresholds are applied. * If neither is specified, results are reported without threshold * filtering. + * @NL80211_PMSR_FTM_REQ_ATTR_PD_SUPPRESS_RESULTS: Flag to suppress ranging + * results for PD requests. When set, ranging measurements are performed + * but results are not reported to userspace, regardless of ranging role + * or type. Only valid when %NL80211_PMSR_PEER_ATTR_REQ_TYPE is set to + * %NL80211_PMSR_FTM_REQ_TYPE_PD. * * @NUM_NL80211_PMSR_FTM_REQ_ATTR: internal * @NL80211_PMSR_FTM_REQ_ATTR_MAX: highest attribute number @@ -8392,6 +8397,7 @@ enum nl80211_peer_measurement_ftm_req { NL80211_PMSR_FTM_REQ_ATTR_PAD, NL80211_PMSR_FTM_REQ_ATTR_INGRESS, NL80211_PMSR_FTM_REQ_ATTR_EGRESS, + NL80211_PMSR_FTM_REQ_ATTR_PD_SUPPRESS_RESULTS, /* keep last */ NUM_NL80211_PMSR_FTM_REQ_ATTR, diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 84f17032fdd9..b33f688b983a 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -487,6 +487,7 @@ nl80211_pmsr_ftm_req_attr_policy[NL80211_PMSR_FTM_REQ_ATTR_MAX + 1] = { [NL80211_PMSR_FTM_REQ_ATTR_NUM_MEASUREMENTS] = { .type = NLA_U32 }, [NL80211_PMSR_FTM_REQ_ATTR_INGRESS] = { .type = NLA_U64 }, [NL80211_PMSR_FTM_REQ_ATTR_EGRESS] = { .type = NLA_U64 }, + [NL80211_PMSR_FTM_REQ_ATTR_PD_SUPPRESS_RESULTS] = { .type = NLA_FLAG }, }; static const struct nla_policy diff --git a/net/wireless/pmsr.c b/net/wireless/pmsr.c index 8b7843f75db2..f77658b6fccc 100644 --- a/net/wireless/pmsr.c +++ b/net/wireless/pmsr.c @@ -289,6 +289,17 @@ static int pmsr_parse_ftm(struct cfg80211_registered_device *rdev, out->ftm.egress_distance = nla_get_u64(tb[NL80211_PMSR_FTM_REQ_ATTR_EGRESS]); + out->ftm.pd_suppress_range_results = + nla_get_flag(tb[NL80211_PMSR_FTM_REQ_ATTR_PD_SUPPRESS_RESULTS]); + + if (out->ftm.request_type != NL80211_PMSR_FTM_REQ_TYPE_PD && + out->ftm.pd_suppress_range_results) { + NL_SET_ERR_MSG_ATTR(info->extack, + tb[NL80211_PMSR_FTM_REQ_ATTR_PD_SUPPRESS_RESULTS], + "FTM: suppress range result flag only valid for PD requests"); + return -EINVAL; + } + return 0; } -- cgit v1.2.3 From 4bb6e58bc29ab772c26c5eb471ab255fe2e044d8 Mon Sep 17 00:00:00 2001 From: Peddolla Harshavardhan Reddy Date: Mon, 20 Apr 2026 14:38:55 +0530 Subject: wifi: cfg80211: add LTF keyseed support for secure ranging Currently there is no way to install an LTF key seed that can be used in non-trigger-based (NTB) and trigger-based (TB) FTM ranging to protect NDP frames. Without this, drivers cannot enable PHY-layer security for peer measurement sessions, leaving ranging measurements vulnerable to eavesdropping and manipulation. Introduce NL80211_KEY_LTF_SEED attribute and the dedicated extended feature flag NL80211_EXT_FEATURE_SET_KEY_LTF_SEED to allow drivers to advertise and install LTF key seeds via nl80211. The key seed must be configured beforehand to ensure the peer measurement session is secure. The driver must advertise both NL80211_EXT_FEATURE_SECURE_LTF and NL80211_EXT_FEATURE_SET_KEY_LTF_SEED for the key seed installation to be permitted. The LTF key seed is pairwise key material and must only be used with pairwise key type. Reject attempts to use it with other key types. Signed-off-by: Peddolla Harshavardhan Reddy Link: https://patch.msgid.link/20260420090856.2152905-13-peddolla.reddy@oss.qualcomm.com [fix policy coding style] Signed-off-by: Johannes Berg --- include/linux/ieee80211.h | 1 + include/net/cfg80211.h | 4 ++++ include/uapi/linux/nl80211.h | 18 ++++++++++++++++++ net/wireless/nl80211.c | 9 +++++++++ net/wireless/util.c | 15 +++++++++++++++ 5 files changed, 47 insertions(+) (limited to 'include') diff --git a/include/linux/ieee80211.h b/include/linux/ieee80211.h index 23f9df9be837..11106589acc6 100644 --- a/include/linux/ieee80211.h +++ b/include/linux/ieee80211.h @@ -2236,6 +2236,7 @@ struct ieee80211_multiple_bssid_configuration { #define WLAN_AKM_SUITE_WFA_DPP SUITE(WLAN_OUI_WFA, 2) #define WLAN_MAX_KEY_LEN 32 +#define WLAN_MAX_SECURE_LTF_KEYSEED_LEN 48 #define WLAN_PMK_NAME_LEN 16 #define WLAN_PMKID_LEN 16 diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index fdc8363b296c..13e035fecf7f 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -830,6 +830,8 @@ struct vif_params { * @seq_len: length of @seq. * @vlan_id: vlan_id for VLAN group key (if nonzero) * @mode: key install mode (RX_TX, NO_TX or SET_TX) + * @ltf_keyseed: LTF key seed material + * @ltf_keyseed_len: length of LTF key seed material */ struct key_params { const u8 *key; @@ -839,6 +841,8 @@ struct key_params { u16 vlan_id; u32 cipher; enum nl80211_key_mode mode; + const u8 *ltf_keyseed; + size_t ltf_keyseed_len; }; /** diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 1da4dc3fc816..6c7e6c05b9a8 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -5814,6 +5814,18 @@ enum nl80211_key_default_types { * @NL80211_KEY_MODE: the mode from enum nl80211_key_mode. * Defaults to @NL80211_KEY_RX_TX. * @NL80211_KEY_DEFAULT_BEACON: flag indicating default Beacon frame key + * @NL80211_KEY_LTF_SEED: LTF key seed is used by the driver to generate + * secure LTF keys used in case of peer measurement request with FTM + * request type as either %NL80211_PMSR_FTM_REQ_ATTR_NON_TRIGGER_BASED + * or %NL80211_PMSR_FTM_REQ_ATTR_TRIGGER_BASED. Secure LTF key seeds + * will help enable PHY security in peer measurement session. + * The LTF key seed is installed along with the TK (Temporal Key) using + * %NL80211_CMD_NEW_KEY. The TK is configured using the + * %NL80211_ATTR_KEY_DATA attribute, while the LTF key seed is configured + * using this attribute. Both keys must be configured before initiation + * of peer measurement to ensure peer measurement session is secure. + * Only valid if %NL80211_EXT_FEATURE_SET_KEY_LTF_SEED is set. This + * attribute is restricted to pairwise keys (%NL80211_KEYTYPE_PAIRWISE). * * @__NL80211_KEY_AFTER_LAST: internal * @NL80211_KEY_MAX: highest key attribute @@ -5830,6 +5842,7 @@ enum nl80211_key_attributes { NL80211_KEY_DEFAULT_TYPES, NL80211_KEY_MODE, NL80211_KEY_DEFAULT_BEACON, + NL80211_KEY_LTF_SEED, /* keep last */ __NL80211_KEY_AFTER_LAST, @@ -7059,6 +7072,10 @@ enum nl80211_feature_flags { * forward frames with a matching MAC address to userspace during * the off-channel period. * + * @NL80211_EXT_FEATURE_SET_KEY_LTF_SEED: Driver supports installing the + * LTF key seed via %NL80211_KEY_LTF_SEED. The seed is used to generate + * secure LTF keys for secure LTF measurement sessions. + * * @NUM_NL80211_EXT_FEATURES: number of extended features. * @MAX_NL80211_EXT_FEATURES: highest extended feature index. */ @@ -7139,6 +7156,7 @@ enum nl80211_ext_feature_index { NL80211_EXT_FEATURE_ASSOC_FRAME_ENCRYPTION, NL80211_EXT_FEATURE_IEEE8021X_AUTH, NL80211_EXT_FEATURE_ROC_ADDR_FILTER, + NL80211_EXT_FEATURE_SET_KEY_LTF_SEED, /* add new features before the definition below */ NUM_NL80211_EXT_FEATURES, diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index b33f688b983a..61b1716daf1e 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -1103,6 +1103,10 @@ static const struct nla_policy nl80211_key_policy[NL80211_KEY_MAX + 1] = { [NL80211_KEY_TYPE] = NLA_POLICY_MAX(NLA_U32, NUM_NL80211_KEYTYPES - 1), [NL80211_KEY_DEFAULT_TYPES] = { .type = NLA_NESTED }, [NL80211_KEY_MODE] = NLA_POLICY_RANGE(NLA_U8, 0, NL80211_KEY_SET_TX), + [NL80211_KEY_LTF_SEED] = { + .type = NLA_BINARY, + .len = WLAN_MAX_SECURE_LTF_KEYSEED_LEN, + }, }; /* policy for the key default flags */ @@ -1634,6 +1638,11 @@ static int nl80211_parse_key_new(struct genl_info *info, struct nlattr *key, if (tb[NL80211_KEY_MODE]) k->p.mode = nla_get_u8(tb[NL80211_KEY_MODE]); + if (tb[NL80211_KEY_LTF_SEED]) { + k->p.ltf_keyseed = nla_data(tb[NL80211_KEY_LTF_SEED]); + k->p.ltf_keyseed_len = nla_len(tb[NL80211_KEY_LTF_SEED]); + } + return 0; } diff --git a/net/wireless/util.c b/net/wireless/util.c index b638e205c71e..8dd7545b9097 100644 --- a/net/wireless/util.c +++ b/net/wireless/util.c @@ -434,6 +434,21 @@ int cfg80211_validate_key_settings(struct cfg80211_registered_device *rdev, if (!cfg80211_supported_cipher_suite(&rdev->wiphy, params->cipher)) return -EINVAL; + if (params->ltf_keyseed) { + if (!wiphy_ext_feature_isset(&rdev->wiphy, + NL80211_EXT_FEATURE_SECURE_LTF) || + !wiphy_ext_feature_isset(&rdev->wiphy, + NL80211_EXT_FEATURE_SET_KEY_LTF_SEED)) + return -EOPNOTSUPP; + + /* + * LTF key seed is pairwise key material and must only be + * used with a pairwise key + */ + if (!pairwise) + return -EINVAL; + } + return 0; } -- cgit v1.2.3 From ca283942e5b91894d3a9228eaf789837f66c986f Mon Sep 17 00:00:00 2001 From: "Mike Marciniszyn (Meta)" Date: Thu, 30 Apr 2026 11:08:00 -0400 Subject: net: mdio: Add support for RSFEC Control register for PMA Add the constants associated with RS-FEC configuration and status as well as the indicated separated bits for DEVS1 to convey a separated PMA. Reviewed-by: Andrew Lunn Signed-off-by: Mike Marciniszyn (Meta) Link: https://patch.msgid.link/20260430150802.3521-2-mike.marciniszyn@gmail.com Signed-off-by: Paolo Abeni --- include/uapi/linux/mdio.h | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'include') diff --git a/include/uapi/linux/mdio.h b/include/uapi/linux/mdio.h index 8d769f100de6..b2541c948fc1 100644 --- a/include/uapi/linux/mdio.h +++ b/include/uapi/linux/mdio.h @@ -23,6 +23,10 @@ #define MDIO_MMD_DTEXS 5 /* DTE Extender Sublayer */ #define MDIO_MMD_TC 6 /* Transmission Convergence */ #define MDIO_MMD_AN 7 /* Auto-Negotiation */ +#define MDIO_MMD_SEP_PMA1 8 /* Separated PMA (1) */ +#define MDIO_MMD_SEP_PMA2 9 /* Separated PMA (2) */ +#define MDIO_MMD_SEP_PMA3 10 /* Separated PMA (3) */ +#define MDIO_MMD_SEP_PMA4 11 /* Separated PMA (4) */ #define MDIO_MMD_POWER_UNIT 13 /* PHY Power Unit */ #define MDIO_MMD_C22EXT 29 /* Clause 22 extension */ #define MDIO_MMD_VEND1 30 /* Vendor specific 1 */ @@ -63,6 +67,8 @@ * Lanes B-D are numbered 134-136. */ #define MDIO_PMA_10GBR_FSRT_CSR 147 /* 10GBASE-R fast retrain status and control */ #define MDIO_PMA_10GBR_FECABLE 170 /* 10GBASE-R FEC ability */ +#define MDIO_PMA_RSFEC_CTRL 200 /* RSFEC control */ +#define MDIO_PMA_RSFEC_LANE_MAP 206 /* RSFEC lane mapping */ #define MDIO_PCS_10GBX_STAT1 24 /* 10GBASE-X PCS status 1 */ #define MDIO_PCS_10GBRT_STAT1 32 /* 10GBASE-R/-T PCS status 1 */ #define MDIO_PCS_10GBRT_STAT2 33 /* 10GBASE-R/-T PCS status 2 */ @@ -175,6 +181,10 @@ #define MDIO_DEVS_DTEXS MDIO_DEVS_PRESENT(MDIO_MMD_DTEXS) #define MDIO_DEVS_TC MDIO_DEVS_PRESENT(MDIO_MMD_TC) #define MDIO_DEVS_AN MDIO_DEVS_PRESENT(MDIO_MMD_AN) +#define MDIO_DEVS_SEP_PMA1 MDIO_DEVS_PRESENT(MDIO_MMD_SEP_PMA1) +#define MDIO_DEVS_SEP_PMA2 MDIO_DEVS_PRESENT(MDIO_MMD_SEP_PMA2) +#define MDIO_DEVS_SEP_PMA3 MDIO_DEVS_PRESENT(MDIO_MMD_SEP_PMA3) +#define MDIO_DEVS_SEP_PMA4 MDIO_DEVS_PRESENT(MDIO_MMD_SEP_PMA4) #define MDIO_DEVS_C22EXT MDIO_DEVS_PRESENT(MDIO_MMD_C22EXT) #define MDIO_DEVS_VEND1 MDIO_DEVS_PRESENT(MDIO_MMD_VEND1) #define MDIO_DEVS_VEND2 MDIO_DEVS_PRESENT(MDIO_MMD_VEND2) -- cgit v1.2.3 From f6310f043fb4f45f0027da842e46fcddf9c859fe Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 28 Apr 2026 10:36:57 +0200 Subject: wifi: mac80211: update UHR capabilities field order Since 802.11bn D1.4 the DBE capabilities are after the PHY capabilities, not between MAC and PHY, adjust the code accordingly. Also add a struct for DBE capabilities and use it for checking the correct length instead of hard-coding the lengths. Signed-off-by: Johannes Berg Reviewed-by: Miriam Rachel Korenblit Link: https://patch.msgid.link/20260428103657.b40af50f182d.I75306a092dc2c8a9eb7276160f0b7144b4846d18@changeid Signed-off-by: Johannes Berg --- include/linux/ieee80211-uhr.h | 55 +++++++++++++++++-------------------------- net/mac80211/main.c | 3 +-- net/mac80211/mlme.c | 3 +-- net/mac80211/uhr.c | 5 +--- net/mac80211/util.c | 3 +-- 5 files changed, 25 insertions(+), 44 deletions(-) (limited to 'include') diff --git a/include/linux/ieee80211-uhr.h b/include/linux/ieee80211-uhr.h index d199f3ebdba0..f4f4bd8256df 100644 --- a/include/linux/ieee80211-uhr.h +++ b/include/linux/ieee80211-uhr.h @@ -372,6 +372,12 @@ ieee80211_uhr_npca_dis_subch_bitmap(const struct ieee80211_uhr_operation *oper) #define IEEE80211_UHR_MAC_CAP_DBE_EHT_MCS_MAP_160_PRES 0x08 #define IEEE80211_UHR_MAC_CAP_DBE_EHT_MCS_MAP_320_PRES 0x10 +struct ieee80211_uhr_cap_dbe { + u8 cap; + /* present 0, 1 or 2 times depending on _PRES bits */ + struct ieee80211_eht_mcs_nss_supp_bw eht_mcs_map[]; +} __packed; + /** * enum ieee80211_uhr_dbe_max_supported_bw - DBE Maximum Supported Bandwidth * @@ -394,12 +400,6 @@ struct ieee80211_uhr_cap_mac { u8 mac_cap[5]; } __packed; -struct ieee80211_uhr_cap { - struct ieee80211_uhr_cap_mac mac; - /* DBE, PHY capabilities */ - u8 variable[]; -} __packed; - #define IEEE80211_UHR_PHY_CAP_MAX_NSS_RX_SND_NDP_LE80 0x01 #define IEEE80211_UHR_PHY_CAP_MAX_NSS_RX_DL_MU_LE80 0x02 #define IEEE80211_UHR_PHY_CAP_MAX_NSS_RX_SND_NDP_160 0x04 @@ -413,11 +413,18 @@ struct ieee80211_uhr_cap_phy { u8 cap; } __packed; +struct ieee80211_uhr_cap { + struct ieee80211_uhr_cap_mac mac; + struct ieee80211_uhr_cap_phy phy; + /* optional DBE capabilities */ + u8 variable[]; +} __packed; + static inline bool ieee80211_uhr_capa_size_ok(const u8 *data, u8 len, bool from_ap) { const struct ieee80211_uhr_cap *cap = (const void *)data; - size_t needed = sizeof(*cap) + sizeof(struct ieee80211_uhr_cap_phy); + size_t needed = sizeof(*cap); if (len < needed) return false; @@ -427,44 +434,24 @@ static inline bool ieee80211_uhr_capa_size_ok(const u8 *data, u8 len, * in the UHR MAC Capabilities Information field. */ if (from_ap && cap->mac.mac_cap[1] & IEEE80211_UHR_MAC_CAP1_DBE_SUPP) { - u8 dbe; + const struct ieee80211_uhr_cap_dbe *dbe; - needed += 1; + needed += sizeof(struct ieee80211_uhr_cap_dbe); if (len < needed) return false; - dbe = cap->variable[0]; + dbe = (const void *)cap->variable; - if (dbe & IEEE80211_UHR_MAC_CAP_DBE_EHT_MCS_MAP_160_PRES) - needed += 3; + if (dbe->cap & IEEE80211_UHR_MAC_CAP_DBE_EHT_MCS_MAP_160_PRES) + needed += sizeof(dbe->eht_mcs_map[0]); - if (dbe & IEEE80211_UHR_MAC_CAP_DBE_EHT_MCS_MAP_320_PRES) - needed += 3; + if (dbe->cap & IEEE80211_UHR_MAC_CAP_DBE_EHT_MCS_MAP_320_PRES) + needed += sizeof(dbe->eht_mcs_map[0]); } return len >= needed; } -static inline const struct ieee80211_uhr_cap_phy * -ieee80211_uhr_phy_cap(const struct ieee80211_uhr_cap *cap, bool from_ap) -{ - u8 offs = 0; - - if (from_ap && cap->mac.mac_cap[1] & IEEE80211_UHR_MAC_CAP1_DBE_SUPP) { - u8 dbe = cap->variable[0]; - - offs += 1; - - if (dbe & IEEE80211_UHR_MAC_CAP_DBE_EHT_MCS_MAP_160_PRES) - offs += 3; - - if (dbe & IEEE80211_UHR_MAC_CAP_DBE_EHT_MCS_MAP_320_PRES) - offs += 3; - } - - return (const void *)&cap->variable[offs]; -} - #define IEEE80211_SMD_INFO_CAPA_DL_DATA_FWD 0x01 #define IEEE80211_SMD_INFO_CAPA_MAX_NUM_PREP 0x0E #define IEEE80211_SMD_INFO_CAPA_TYPE 0x10 diff --git a/net/mac80211/main.c b/net/mac80211/main.c index 8400792d67e2..90d295cc364f 100644 --- a/net/mac80211/main.c +++ b/net/mac80211/main.c @@ -1463,8 +1463,7 @@ int ieee80211_register_hw(struct ieee80211_hw *hw) if (supp_uhr) local->scan_ies_len += - 3 + sizeof(struct ieee80211_uhr_cap) + - sizeof(struct ieee80211_uhr_cap_phy); + 3 + sizeof(struct ieee80211_uhr_cap); if (!local->ops->hw_scan) { /* For hw_scan, driver needs to set these up. */ diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index 870711fb8e7e..f404dd0fce43 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -2225,8 +2225,7 @@ ieee80211_link_common_elems_size(struct ieee80211_sub_if_data *sdata, sizeof(struct ieee80211_eht_mcs_nss_supp) + IEEE80211_EHT_PPE_THRES_MAX_LEN; - size += 2 + 1 + sizeof(struct ieee80211_uhr_cap) + - sizeof(struct ieee80211_uhr_cap_phy); + size += 2 + 1 + sizeof(struct ieee80211_uhr_cap); return size; } diff --git a/net/mac80211/uhr.c b/net/mac80211/uhr.c index 2d8f5e5480ef..ca3b471b7981 100644 --- a/net/mac80211/uhr.c +++ b/net/mac80211/uhr.c @@ -15,7 +15,6 @@ ieee80211_uhr_cap_ie_to_sta_uhr_cap(struct ieee80211_sub_if_data *sdata, struct link_sta_info *link_sta) { struct ieee80211_sta_uhr_cap *sta_uhr_cap = &link_sta->pub->uhr_cap; - bool from_ap; memset(sta_uhr_cap, 0, sizeof(*sta_uhr_cap)); @@ -23,8 +22,6 @@ ieee80211_uhr_cap_ie_to_sta_uhr_cap(struct ieee80211_sub_if_data *sdata, return; sta_uhr_cap->has_uhr = true; - sta_uhr_cap->mac = uhr_cap->mac; - from_ap = sdata->vif.type == NL80211_IFTYPE_STATION; - sta_uhr_cap->phy = *ieee80211_uhr_phy_cap(uhr_cap, from_ap); + sta_uhr_cap->phy = uhr_cap->phy; } diff --git a/net/mac80211/util.c b/net/mac80211/util.c index 61ec2116fab2..00e46b5bc020 100644 --- a/net/mac80211/util.c +++ b/net/mac80211/util.c @@ -4641,8 +4641,7 @@ int ieee80211_put_uhr_cap(struct sk_buff *skb, if (!uhr_cap) return 0; - len = 2 + 1 + sizeof(struct ieee80211_uhr_cap) + - sizeof(struct ieee80211_uhr_cap_phy); + len = 2 + 1 + sizeof(struct ieee80211_uhr_cap); if (skb_tailroom(skb) < len) return -ENOBUFS; -- cgit v1.2.3 From 32ae6c6f7b8f85ad403d9606d27b03d8d7e03fec Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 28 Apr 2026 11:06:56 +0200 Subject: wifi: ieee80211: define UHR ML-PM extended MLD capability UHR defines bit 8 to mean multi-link power management, add a definition for it. Also reindent the other definitions to use tabs, not spaces. Link: https://patch.msgid.link/20260428110915.c6b6a06016cf.I7ebd97397507d320124547017e21191b55c5d34d@changeid Signed-off-by: Johannes Berg --- include/linux/ieee80211-eht.h | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'include') diff --git a/include/linux/ieee80211-eht.h b/include/linux/ieee80211-eht.h index 335e78bd4b5d..87d92fb86fab 100644 --- a/include/linux/ieee80211-eht.h +++ b/include/linux/ieee80211-eht.h @@ -9,7 +9,7 @@ * Copyright (c) 2006, Michael Wu * Copyright (c) 2013 - 2014 Intel Mobile Communications GmbH * Copyright (c) 2016 - 2017 Intel Deutschland GmbH - * Copyright (c) 2018 - 2025 Intel Corporation + * Copyright (c) 2018 - 2026 Intel Corporation */ #ifndef LINUX_IEEE80211_EHT_H @@ -750,11 +750,13 @@ static inline u16 ieee80211_mle_get_mld_capa_op(const u8 *data) } /* Defined in Figure 9-1074t in P802.11be_D7.0 */ -#define IEEE80211_EHT_ML_EXT_MLD_CAPA_OP_PARAM_UPDATE 0x0001 -#define IEEE80211_EHT_ML_EXT_MLD_CAPA_OP_RECO_MAX_LINKS_MASK 0x001e -#define IEEE80211_EHT_ML_EXT_MLD_CAPA_NSTR_UPDATE 0x0020 -#define IEEE80211_EHT_ML_EXT_MLD_CAPA_EMLSR_ENA_ON_ONE_LINK 0x0040 -#define IEEE80211_EHT_ML_EXT_MLD_CAPA_BTM_MLD_RECO_MULTI_AP 0x0080 +#define IEEE80211_EHT_ML_EXT_MLD_CAPA_OP_PARAM_UPDATE 0x0001 +#define IEEE80211_EHT_ML_EXT_MLD_CAPA_OP_RECO_MAX_LINKS_MASK 0x001e +#define IEEE80211_EHT_ML_EXT_MLD_CAPA_NSTR_UPDATE 0x0020 +#define IEEE80211_EHT_ML_EXT_MLD_CAPA_EMLSR_ENA_ON_ONE_LINK 0x0040 +#define IEEE80211_EHT_ML_EXT_MLD_CAPA_BTM_MLD_RECO_MULTI_AP 0x0080 +/* defined by UHR Draft P802.11bn_D1.3 Figure 9-1147 */ +#define IEEE80211_UHR_ML_EXT_MLD_CAPA_ML_PM 0x0100 /** * ieee80211_mle_get_ext_mld_capa_op - returns the extended MLD capabilities -- cgit v1.2.3 From 6765bc7429f8d9fa1984430b86c71a17237fd86a Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 28 Apr 2026 11:06:57 +0200 Subject: wifi: mac80211: track AP's extended MLD capa/ops For UHR multi-link power management, the driver/device needs to know if the AP supports it, to be able to use it. Track the AP's extended MLD capabilities and operations so it does. Link: https://patch.msgid.link/20260428110915.e4038a00e4b2.I323686be5d4a73e8b962019a30d51309496b86a6@changeid Signed-off-by: Johannes Berg --- include/net/mac80211.h | 3 +++ net/mac80211/mlme.c | 6 ++++++ 2 files changed, 9 insertions(+) (limited to 'include') diff --git a/include/net/mac80211.h b/include/net/mac80211.h index d909bc1b29ff..ba1723e62481 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -2674,6 +2674,8 @@ struct ieee80211_link_sta { * @epp_peer: indicates that the peer is an EPP peer. * @nmi: For NDI stations, pointer to the NMI station of the peer. * @nan_sched: NAN peer schedule for this station. Valid only for NMI stations. + * @ext_mld_capa_ops: the MLD's extended MLD capabilities and operations + * NOTE: currently only tracked for AP STAs */ struct ieee80211_sta { u8 addr[ETH_ALEN] __aligned(2); @@ -2698,6 +2700,7 @@ struct ieee80211_sta { struct ieee80211_txq *txq[IEEE80211_NUM_TIDS + 1]; u16 valid_links; + u16 ext_mld_capa_ops; bool epp_peer; struct ieee80211_link_sta deflink; struct ieee80211_link_sta __rcu *link[IEEE80211_MLD_MAX_NUM_LINKS]; diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index f404dd0fce43..6e81e29b8163 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -6461,6 +6461,12 @@ static bool ieee80211_assoc_success(struct ieee80211_sub_if_data *sdata, sta->sta.spp_amsdu = assoc_data->spp_amsdu; if (ieee80211_vif_is_mld(&sdata->vif)) { + if (!elems->ml_basic) + goto out_err; + + sta->sta.ext_mld_capa_ops = + ieee80211_mle_get_ext_mld_capa_op((const void *)elems->ml_basic); + for (link_id = 0; link_id < IEEE80211_MLD_MAX_NUM_LINKS; link_id++) { if (!assoc_data->link[link_id].bss) continue; -- cgit v1.2.3 From fb19b4d67d81fb91d3c0dce0ddea7fc393a37b2e Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 28 Apr 2026 11:06:59 +0200 Subject: wifi: cfg80211: allow devices to advertise extended MLD capa/ops For UHR, multi-link power-management capability lives there, and so it's needed that hostapd knows what to advertise, and clients should have it shown to userspace for information. Repurpose the existing NL80211_ATTR_ASSOC_MLD_EXT_CAPA_OPS by renaming it to NL80211_ATTR_EXT_MLD_CAPA_AND_OPS (with a define for compatibility) and advertise the capabilities. We can also later use the value, if needed, to set per-station capabilities on STAs added to AP interfaces. Link: https://patch.msgid.link/20260428110915.e808e70feed6.I378a7c017bfc1ebb072fa8d5d1db2ac9b45596c9@changeid Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 2 ++ include/uapi/linux/nl80211.h | 15 +++++++++------ net/wireless/nl80211.c | 18 ++++++++++++------ 3 files changed, 23 insertions(+), 12 deletions(-) (limited to 'include') diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 13e035fecf7f..5755aa288912 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -5969,6 +5969,7 @@ struct wiphy_vendor_command { * @extended_capabilities_len: length of the extended capabilities * @eml_capabilities: EML capabilities (for MLO) * @mld_capa_and_ops: MLD capabilities and operations (for MLO) + * @ext_mld_capa_and_ops: Extended MLD capabilities and operations (for MLO) */ struct wiphy_iftype_ext_capab { enum nl80211_iftype iftype; @@ -5977,6 +5978,7 @@ struct wiphy_iftype_ext_capab { u8 extended_capabilities_len; u16 eml_capabilities; u16 mld_capa_and_ops; + u16 ext_mld_capa_and_ops; }; /** diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 6c7e6c05b9a8..235d7ae72e60 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -3004,11 +3004,13 @@ enum nl80211_commands { * @NL80211_ATTR_EPCS: Flag attribute indicating that EPCS is enabled for a * station interface. * - * @NL80211_ATTR_ASSOC_MLD_EXT_CAPA_OPS: Extended MLD capabilities and - * operations that userspace implements to use during association/ML - * link reconfig, currently only "BTM MLD Recommendation For Multiple - * APs Support". Drivers may set additional flags that they support - * in the kernel or device. + * @NL80211_ATTR_EXT_MLD_CAPA_AND_OPS: Extended MLD capabilities and operations. + * For association and link reconfiguration, indicates extra capabilities + * that userspace implements, currently only "BTM MLD Recommendation For + * Multiple APs Support". + * For wiphy information, additional flags that drivers will set, but + * this is informational only for userspace (it's not expected to set + * these.) * * @NL80211_ATTR_WIPHY_RADIO_INDEX: (int) Integer attribute denoting the index * of the radio in interest. Internally a value of -1 is used to @@ -3715,7 +3717,7 @@ enum nl80211_attrs { NL80211_ATTR_MLO_RECONF_REM_LINKS, NL80211_ATTR_EPCS, - NL80211_ATTR_ASSOC_MLD_EXT_CAPA_OPS, + NL80211_ATTR_EXT_MLD_CAPA_AND_OPS, NL80211_ATTR_WIPHY_RADIO_INDEX, @@ -3769,6 +3771,7 @@ enum nl80211_attrs { #define NL80211_ATTR_SAE_DATA NL80211_ATTR_AUTH_DATA #define NL80211_ATTR_CSA_C_OFF_BEACON NL80211_ATTR_CNTDWN_OFFS_BEACON #define NL80211_ATTR_CSA_C_OFF_PRESP NL80211_ATTR_CNTDWN_OFFS_PRESP +#define NL80211_ATTR_ASSOC_MLD_EXT_CAPA_OPS NL80211_ATTR_EXT_MLD_CAPA_AND_OPS /* * Allow user space programs to use #ifdef on new attributes by defining them diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 61b1716daf1e..f935b18112e8 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -1061,7 +1061,7 @@ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = { NL80211_MAX_SUPP_SELECTORS), [NL80211_ATTR_MLO_RECONF_REM_LINKS] = { .type = NLA_U16 }, [NL80211_ATTR_EPCS] = { .type = NLA_FLAG }, - [NL80211_ATTR_ASSOC_MLD_EXT_CAPA_OPS] = { .type = NLA_U16 }, + [NL80211_ATTR_EXT_MLD_CAPA_AND_OPS] = { .type = NLA_U16 }, [NL80211_ATTR_WIPHY_RADIO_INDEX] = { .type = NLA_U8 }, [NL80211_ATTR_S1G_LONG_BEACON_PERIOD] = NLA_POLICY_MIN(NLA_U8, 2), [NL80211_ATTR_S1G_SHORT_BEACON] = @@ -3596,6 +3596,12 @@ static int nl80211_send_wiphy(struct cfg80211_registered_device *rdev, NL80211_ATTR_MLD_CAPA_AND_OPS, capab->mld_capa_and_ops))) goto nla_put_failure; + if (rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_MLO && + capab->ext_mld_capa_and_ops && + nla_put_u16(msg, + NL80211_ATTR_EXT_MLD_CAPA_AND_OPS, + capab->ext_mld_capa_and_ops)) + goto nla_put_failure; nla_nest_end(msg, nested_ext_capab); if (state->split) @@ -13059,9 +13065,9 @@ static int nl80211_associate(struct sk_buff *skb, struct genl_info *info) goto free; } - if (info->attrs[NL80211_ATTR_ASSOC_MLD_EXT_CAPA_OPS]) + if (info->attrs[NL80211_ATTR_EXT_MLD_CAPA_AND_OPS]) req.ext_mld_capa_ops = - nla_get_u16(info->attrs[NL80211_ATTR_ASSOC_MLD_EXT_CAPA_OPS]); + nla_get_u16(info->attrs[NL80211_ATTR_EXT_MLD_CAPA_AND_OPS]); } else { if (req.link_id >= 0) return -EINVAL; @@ -13072,7 +13078,7 @@ static int nl80211_associate(struct sk_buff *skb, struct genl_info *info) return PTR_ERR(req.bss); ap_addr = req.bss->bssid; - if (info->attrs[NL80211_ATTR_ASSOC_MLD_EXT_CAPA_OPS]) + if (info->attrs[NL80211_ATTR_EXT_MLD_CAPA_AND_OPS]) return -EINVAL; } @@ -19000,9 +19006,9 @@ static int nl80211_assoc_ml_reconf(struct sk_buff *skb, struct genl_info *info) goto out; } - if (info->attrs[NL80211_ATTR_ASSOC_MLD_EXT_CAPA_OPS]) + if (info->attrs[NL80211_ATTR_EXT_MLD_CAPA_AND_OPS]) req.ext_mld_capa_ops = - nla_get_u16(info->attrs[NL80211_ATTR_ASSOC_MLD_EXT_CAPA_OPS]); + nla_get_u16(info->attrs[NL80211_ATTR_EXT_MLD_CAPA_AND_OPS]); err = cfg80211_assoc_ml_reconf(rdev, dev, &req); -- cgit v1.2.3 From 1cf1d06e956dfbb05a7f69f64ab3c8938117eabe Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 28 Apr 2026 11:25:32 +0200 Subject: wifi: cfg80211: allow representing NPCA in chandef Add the necessary fields to the chandef data structure to represent NPCA (the NPCA primary channel and NPCA punctured/disabled subchannels bitmap), and the code to check these for validity, compatibility, as well as allowing it to be passed for AP mode for capable devices. Compatibility is assumed to only be the case when it's actually identical, enabling later management of this in channel contexts in mac80211 for multiple APs, but requiring userspace to set up the identical chandef on all AP interfaces that share a channel (and BSS color.) Link: https://patch.msgid.link/20260428112708.46f3872aeb35.I85888dab88a6659ba52db4b3318979ca5bcfc0c8@changeid Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 11 +++- include/uapi/linux/nl80211.h | 6 +++ net/wireless/chan.c | 59 ++++++++++++++++++--- net/wireless/nl80211.c | 120 +++++++++++++++++++++++++++++++++++++------ net/wireless/nl80211.h | 5 +- net/wireless/pmsr.c | 2 +- net/wireless/trace.h | 16 ++++-- 7 files changed, 189 insertions(+), 30 deletions(-) (limited to 'include') diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 5755aa288912..16e11c5718b5 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -848,6 +848,7 @@ struct key_params { /** * struct cfg80211_chan_def - channel definition * @chan: the (control) channel + * @npca_chan: the NPCA primary channel * @width: channel width * @center_freq1: center frequency of first segment * @center_freq2: center frequency of second segment @@ -860,18 +861,22 @@ struct key_params { * @punctured: mask of the punctured 20 MHz subchannels, with * bits turned on being disabled (punctured); numbered * from lower to higher frequency (like in the spec) + * @npca_punctured: NPCA puncturing bitmap, like @punctured but for + * NPCA transmissions. If NPCA is used (@npca_chan is not %NULL) + * this will be a superset of the @punctured bimap. * @s1g_primary_2mhz: Indicates if the control channel pointed to * by 'chan' exists as a 1MHz primary subchannel within an * S1G 2MHz primary channel. */ struct cfg80211_chan_def { struct ieee80211_channel *chan; + struct ieee80211_channel *npca_chan; enum nl80211_chan_width width; u32 center_freq1; u32 center_freq2; struct ieee80211_edmg edmg; u16 freq1_offset; - u16 punctured; + u16 punctured, npca_punctured; bool s1g_primary_2mhz; }; @@ -1018,7 +1023,9 @@ cfg80211_chandef_identical(const struct cfg80211_chan_def *chandef1, chandef1->freq1_offset == chandef2->freq1_offset && chandef1->center_freq2 == chandef2->center_freq2 && chandef1->punctured == chandef2->punctured && - chandef1->s1g_primary_2mhz == chandef2->s1g_primary_2mhz); + chandef1->s1g_primary_2mhz == chandef2->s1g_primary_2mhz && + chandef1->npca_chan == chandef2->npca_chan && + chandef1->npca_punctured == chandef2->npca_punctured); } /** diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 235d7ae72e60..9998f6c0a665 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -3162,6 +3162,9 @@ enum nl80211_commands { * encrypted over the air. Enhanced Privacy Protection (EPP), as defined * in IEEE P802.11bi/D4.0, mandates this encryption. * + * @NL80211_ATTR_NPCA_PRIMARY_FREQ: NPCA primary channel (u32) + * @NL80211_ATTR_NPCA_PUNCT_BITMAP: NPCA puncturing bitmap (u32) + * * @NUM_NL80211_ATTR: total number of nl80211_attrs available * @NL80211_ATTR_MAX: highest attribute number currently defined * @__NL80211_ATTR_AFTER_LAST: internal use @@ -3757,6 +3760,9 @@ enum nl80211_attrs { NL80211_ATTR_ASSOC_ENCRYPTED, + NL80211_ATTR_NPCA_PRIMARY_FREQ, + NL80211_ATTR_NPCA_PUNCT_BITMAP, + /* add attributes here, update the policy in nl80211.c */ __NL80211_ATTR_AFTER_LAST, diff --git a/net/wireless/chan.c b/net/wireless/chan.c index 8954ac1659c1..38b83b44b5dc 100644 --- a/net/wireless/chan.c +++ b/net/wireless/chan.c @@ -138,9 +138,10 @@ static const struct cfg80211_per_bw_puncturing_values per_bw_puncturing[] = { CFG80211_PER_BW_VALID_PUNCTURING_VALUES(320) }; -static bool valid_puncturing_bitmap(const struct cfg80211_chan_def *chandef) +static bool valid_puncturing_bitmap(const struct cfg80211_chan_def *chandef, + u32 primary_center, u32 punctured) { - u32 idx, i, start_freq, primary_center = chandef->chan->center_freq; + u32 idx, i, start_freq; switch (chandef->width) { case NL80211_CHAN_WIDTH_80: @@ -156,18 +157,18 @@ static bool valid_puncturing_bitmap(const struct cfg80211_chan_def *chandef) start_freq = chandef->center_freq1 - 160; break; default: - return chandef->punctured == 0; + return punctured == 0; } - if (!chandef->punctured) + if (!punctured) return true; /* check if primary channel is punctured */ - if (chandef->punctured & (u16)BIT((primary_center - start_freq) / 20)) + if (punctured & (u16)BIT((primary_center - start_freq) / 20)) return false; for (i = 0; i < per_bw_puncturing[idx].len; i++) { - if (per_bw_puncturing[idx].valid_values[i] == chandef->punctured) + if (per_bw_puncturing[idx].valid_values[i] == punctured) return true; } @@ -458,6 +459,40 @@ bool cfg80211_chandef_valid(const struct cfg80211_chan_def *chandef) if (!cfg80211_chandef_valid_control_freq(chandef, control_freq)) return false; + if (chandef->npca_chan) { + bool pri_upper, npca_upper; + u32 cf1; + + switch (chandef->width) { + case NL80211_CHAN_WIDTH_80: + case NL80211_CHAN_WIDTH_160: + case NL80211_CHAN_WIDTH_320: + break; + default: + return false; + } + + if (!cfg80211_chandef_valid_control_freq(chandef, + chandef->npca_chan->center_freq)) + return false; + + cf1 = chandef->center_freq1; + pri_upper = chandef->chan->center_freq > cf1; + npca_upper = chandef->npca_chan->center_freq > cf1; + + if (pri_upper == npca_upper) + return false; + + if (!valid_puncturing_bitmap(chandef, + chandef->npca_chan->center_freq, + chandef->npca_punctured) || + (chandef->punctured & chandef->npca_punctured) != + chandef->punctured) + return false; + } else if (chandef->npca_punctured) { + return false; + } + if (!cfg80211_valid_center_freq(chandef->center_freq1, chandef->width)) return false; @@ -477,7 +512,8 @@ bool cfg80211_chandef_valid(const struct cfg80211_chan_def *chandef) if (!cfg80211_chandef_is_s1g(chandef) && chandef->s1g_primary_2mhz) return false; - return valid_puncturing_bitmap(chandef); + return valid_puncturing_bitmap(chandef, control_freq, + chandef->punctured); } EXPORT_SYMBOL(cfg80211_chandef_valid); @@ -564,6 +600,15 @@ _cfg80211_chandef_compatible(const struct cfg80211_chan_def *c1, if (c1->width == c2->width) return NULL; + /* + * We need NPCA to be compatible for some scenarios such as + * multiple APs, but in this case userspace should configure + * identical chandefs including NPCA, even if perhaps one of + * the AP interfaces doesn't even advertise it. + */ + if (c1->npca_chan || c2->npca_chan) + return NULL; + /* * can't be compatible if one of them is 5/10 MHz or S1G * but they don't have the same width. diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index f935b18112e8..cd72f187a606 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -1090,6 +1090,9 @@ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = { [NL80211_ATTR_NAN_MAX_CHAN_SWITCH_TIME] = { .type = NLA_U16 }, [NL80211_ATTR_NAN_PEER_MAPS] = NLA_POLICY_NESTED_ARRAY(nl80211_nan_peer_map_policy), + [NL80211_ATTR_NPCA_PRIMARY_FREQ] = { .type = NLA_U32 }, + [NL80211_ATTR_NPCA_PUNCT_BITMAP] = + NLA_POLICY_FULL_RANGE(NLA_U32, &nl80211_punct_bitmap_range), }; /* policy for the key attributes */ @@ -3913,7 +3916,8 @@ static bool nl80211_can_set_dev_channel(struct wireless_dev *wdev) static int _nl80211_parse_chandef(struct cfg80211_registered_device *rdev, struct netlink_ext_ack *extack, struct nlattr **attrs, bool monitor, - struct cfg80211_chan_def *chandef) + struct cfg80211_chan_def *chandef, + bool permit_npca) { u32 control_freq; @@ -4027,6 +4031,34 @@ static int _nl80211_parse_chandef(struct cfg80211_registered_device *rdev, } } + if (attrs[NL80211_ATTR_NPCA_PRIMARY_FREQ]) { + if (!permit_npca) { + NL_SET_ERR_MSG_ATTR(extack, + attrs[NL80211_ATTR_NPCA_PRIMARY_FREQ], + "NPCA not supported"); + return -EINVAL; + } + + chandef->npca_chan = + ieee80211_get_channel(&rdev->wiphy, + nla_get_u32(attrs[NL80211_ATTR_NPCA_PRIMARY_FREQ])); + if (!chandef->npca_chan) { + NL_SET_ERR_MSG_ATTR(extack, + attrs[NL80211_ATTR_NPCA_PRIMARY_FREQ], + "invalid NPCA primary channel"); + return -EINVAL; + } + + chandef->npca_punctured = + nla_get_u32_default(attrs[NL80211_ATTR_NPCA_PUNCT_BITMAP], + chandef->punctured); + } else if (attrs[NL80211_ATTR_NPCA_PUNCT_BITMAP]) { + NL_SET_ERR_MSG_ATTR(extack, + attrs[NL80211_ATTR_NPCA_PUNCT_BITMAP], + "NPCA puncturing only valid with NPCA"); + return -EINVAL; + } + if (!cfg80211_chandef_valid(chandef)) { NL_SET_ERR_MSG_ATTR(extack, attrs[NL80211_ATTR_WIPHY_FREQ], "invalid channel definition"); @@ -4054,9 +4086,11 @@ static int _nl80211_parse_chandef(struct cfg80211_registered_device *rdev, int nl80211_parse_chandef(struct cfg80211_registered_device *rdev, struct netlink_ext_ack *extack, struct nlattr **attrs, - struct cfg80211_chan_def *chandef) + struct cfg80211_chan_def *chandef, + bool permit_npca) { - return _nl80211_parse_chandef(rdev, extack, attrs, false, chandef); + return _nl80211_parse_chandef(rdev, extack, attrs, false, chandef, + permit_npca); } static int __nl80211_set_channel(struct cfg80211_registered_device *rdev, @@ -4069,6 +4103,7 @@ static int __nl80211_set_channel(struct cfg80211_registered_device *rdev, enum nl80211_iftype iftype = NL80211_IFTYPE_MONITOR; struct wireless_dev *wdev = NULL; int link_id = _link_id; + bool permit_npca; if (dev) wdev = dev->ieee80211_ptr; @@ -4083,9 +4118,13 @@ static int __nl80211_set_channel(struct cfg80211_registered_device *rdev, link_id = 0; } + /* allow parsing it - will check on start_ap or below */ + permit_npca = iftype == NL80211_IFTYPE_AP || + iftype == NL80211_IFTYPE_P2P_GO; + result = _nl80211_parse_chandef(rdev, info->extack, info->attrs, iftype == NL80211_IFTYPE_MONITOR, - &chandef); + &chandef, permit_npca); if (result) return result; @@ -4104,6 +4143,9 @@ static int __nl80211_set_channel(struct cfg80211_registered_device *rdev, return -EBUSY; /* Only allow dynamic channel width changes */ + cur_chan = wdev->links[link_id].ap.chandef.npca_chan; + if (chandef.npca_chan != cur_chan) + return -EBUSY; cur_chan = wdev->links[link_id].ap.chandef.chan; if (chandef.chan != cur_chan) return -EBUSY; @@ -4588,6 +4630,15 @@ int nl80211_send_chandef(struct sk_buff *msg, const struct cfg80211_chan_def *ch nla_put_flag(msg, NL80211_ATTR_S1G_PRIMARY_2MHZ)) return -ENOBUFS; + if (chandef->npca_chan && + nla_put_u32(msg, NL80211_ATTR_NPCA_PRIMARY_FREQ, + chandef->npca_chan->center_freq)) + return -ENOBUFS; + if (chandef->npca_punctured && + nla_put_u32(msg, NL80211_ATTR_NPCA_PUNCT_BITMAP, + chandef->npca_punctured)) + return -ENOBUFS; + return 0; } EXPORT_SYMBOL(nl80211_send_chandef); @@ -7092,6 +7143,28 @@ nl80211_parse_s1g_short_beacon(struct cfg80211_registered_device *rdev, return 0; } +static int nl80211_check_npca(struct cfg80211_registered_device *rdev, + const struct cfg80211_chan_def *chandef, + enum nl80211_iftype iftype, + struct netlink_ext_ack *extack) +{ + const struct ieee80211_supported_band *sband; + const struct ieee80211_sta_uhr_cap *uhr_cap; + + if (!chandef->npca_chan) + return 0; + + sband = rdev->wiphy.bands[chandef->chan->band]; + uhr_cap = ieee80211_get_uhr_iftype_cap(sband, iftype); + + if (uhr_cap && + (uhr_cap->mac.mac_cap[0] & IEEE80211_UHR_MAC_CAP0_NPCA_SUPP)) + return 0; + + NL_SET_ERR_MSG(extack, "NPCA not supported"); + return -EINVAL; +} + static int nl80211_start_ap(struct sk_buff *skb, struct genl_info *info) { struct cfg80211_registered_device *rdev = info->user_ptr[0]; @@ -7231,7 +7304,7 @@ static int nl80211_start_ap(struct sk_buff *skb, struct genl_info *info) if (info->attrs[NL80211_ATTR_WIPHY_FREQ]) { err = nl80211_parse_chandef(rdev, info->extack, info->attrs, - ¶ms->chandef); + ¶ms->chandef, true); if (err) goto out; } else if (wdev->valid_links) { @@ -7250,6 +7323,11 @@ static int nl80211_start_ap(struct sk_buff *skb, struct genl_info *info) if (err) goto out; + err = nl80211_check_npca(rdev, ¶ms->chandef, wdev->iftype, + info->extack); + if (err) + goto out; + beacon_check.iftype = wdev->iftype; beacon_check.relax = true; beacon_check.reg_power = @@ -11801,7 +11879,8 @@ static int nl80211_start_radar_detection(struct sk_buff *skb, if (dfs_region == NL80211_DFS_UNSET) return -EINVAL; - err = nl80211_parse_chandef(rdev, info->extack, info->attrs, &chandef); + err = nl80211_parse_chandef(rdev, info->extack, info->attrs, &chandef, + false); if (err) return err; @@ -11890,7 +11969,8 @@ static int nl80211_notify_radar_detection(struct sk_buff *skb, return -EINVAL; } - err = nl80211_parse_chandef(rdev, info->extack, info->attrs, &chandef); + err = nl80211_parse_chandef(rdev, info->extack, info->attrs, &chandef, + false); if (err) { GENL_SET_ERR_MSG(info, "Unable to extract chandef info"); return err; @@ -11973,6 +12053,7 @@ static int nl80211_channel_switch(struct sk_buff *skb, struct genl_info *info) int err; bool need_new_beacon = false; bool need_handle_dfs_flag = true; + bool permit_npca = false; u32 cs_count; if (!rdev->ops->channel_switch || @@ -11990,6 +12071,8 @@ static int nl80211_channel_switch(struct sk_buff *skb, struct genl_info *info) */ need_handle_dfs_flag = false; + permit_npca = true; + /* useless if AP is not running */ if (!wdev->links[link_id].ap.beacon_interval) return -ENOTCONN; @@ -12027,7 +12110,12 @@ static int nl80211_channel_switch(struct sk_buff *skb, struct genl_info *info) params.count = cs_count; err = nl80211_parse_chandef(rdev, info->extack, info->attrs, - ¶ms.chandef); + ¶ms.chandef, permit_npca); + if (err) + goto free; + + err = nl80211_check_npca(rdev, ¶ms.chandef, wdev->iftype, + info->extack); if (err) goto free; @@ -13301,7 +13389,7 @@ static int nl80211_join_ibss(struct sk_buff *skb, struct genl_info *info) } err = nl80211_parse_chandef(rdev, info->extack, info->attrs, - &ibss.chandef); + &ibss.chandef, false); if (err) return err; @@ -14310,7 +14398,8 @@ static int nl80211_remain_on_channel(struct sk_buff *skb, duration > rdev->wiphy.max_remain_on_channel_duration) return -EINVAL; - err = nl80211_parse_chandef(rdev, info->extack, info->attrs, &chandef); + err = nl80211_parse_chandef(rdev, info->extack, info->attrs, &chandef, + false); if (err) return err; @@ -14539,7 +14628,7 @@ static int nl80211_tx_mgmt(struct sk_buff *skb, struct genl_info *info) chandef.chan = NULL; if (info->attrs[NL80211_ATTR_WIPHY_FREQ]) { err = nl80211_parse_chandef(rdev, info->extack, info->attrs, - &chandef); + &chandef, false); if (err) return err; } @@ -14947,7 +15036,7 @@ static int nl80211_join_ocb(struct sk_buff *skb, struct genl_info *info) int err; err = nl80211_parse_chandef(rdev, info->extack, info->attrs, - &setup.chandef); + &setup.chandef, false); if (err) return err; @@ -15023,7 +15112,7 @@ static int nl80211_join_mesh(struct sk_buff *skb, struct genl_info *info) if (info->attrs[NL80211_ATTR_WIPHY_FREQ]) { err = nl80211_parse_chandef(rdev, info->extack, info->attrs, - &setup.chandef); + &setup.chandef, false); if (err) return err; } else { @@ -17027,7 +17116,7 @@ static int nl80211_parse_nan_channel(struct cfg80211_registered_device *rdev, return ret; ret = nl80211_parse_chandef(rdev, info->extack, channel_parsed, - &chandef); + &chandef, false); if (ret) return ret; @@ -18026,7 +18115,8 @@ static int nl80211_tdls_channel_switch(struct sk_buff *skb, !info->attrs[NL80211_ATTR_OPER_CLASS]) return -EINVAL; - err = nl80211_parse_chandef(rdev, info->extack, info->attrs, &chandef); + err = nl80211_parse_chandef(rdev, info->extack, info->attrs, &chandef, + false); if (err) return err; diff --git a/net/wireless/nl80211.h b/net/wireless/nl80211.h index 048ba92c3e42..bdb065d14054 100644 --- a/net/wireless/nl80211.h +++ b/net/wireless/nl80211.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: GPL-2.0 */ /* * Portions of this file - * Copyright (C) 2018, 2020-2025 Intel Corporation + * Copyright (C) 2018, 2020-2026 Intel Corporation */ #ifndef __NET_WIRELESS_NL80211_H #define __NET_WIRELESS_NL80211_H @@ -25,7 +25,8 @@ static inline u64 wdev_id(struct wireless_dev *wdev) int nl80211_parse_chandef(struct cfg80211_registered_device *rdev, struct netlink_ext_ack *extack, struct nlattr **attrs, - struct cfg80211_chan_def *chandef); + struct cfg80211_chan_def *chandef, + bool npca_permitted); int nl80211_parse_random_mac(struct nlattr **attrs, u8 *mac_addr, u8 *mac_addr_mask); diff --git a/net/wireless/pmsr.c b/net/wireless/pmsr.c index f77658b6fccc..e45fc1fb65c2 100644 --- a/net/wireless/pmsr.c +++ b/net/wireless/pmsr.c @@ -349,7 +349,7 @@ static int pmsr_parse_peer(struct cfg80211_registered_device *rdev, return err; err = nl80211_parse_chandef(rdev, info->extack, info->attrs, - &out->chandef); + &out->chandef, false); if (err) return err; diff --git a/net/wireless/trace.h b/net/wireless/trace.h index a68d356fe127..94944f2a39a4 100644 --- a/net/wireless/trace.h +++ b/net/wireless/trace.h @@ -141,7 +141,9 @@ __field(u32, center_freq1) \ __field(u32, freq1_offset) \ __field(u32, center_freq2) \ - __field(u16, punctured) + __field(u16, punctured) \ + __field(u32, npca_pri_freq) \ + __field(u16, npca_punctured) #define CHAN_DEF_ASSIGN(chandef) \ do { \ if ((chandef) && (chandef)->chan) { \ @@ -155,6 +157,11 @@ __entry->freq1_offset = (chandef)->freq1_offset;\ __entry->center_freq2 = (chandef)->center_freq2;\ __entry->punctured = (chandef)->punctured; \ + __entry->npca_pri_freq = \ + (chandef)->npca_chan ? \ + (chandef)->npca_chan->center_freq : 0; \ + __entry->npca_punctured = \ + (chandef)->npca_punctured; \ } else { \ __entry->band = 0; \ __entry->control_freq = 0; \ @@ -164,14 +171,17 @@ __entry->freq1_offset = 0; \ __entry->center_freq2 = 0; \ __entry->punctured = 0; \ + __entry->npca_pri_freq = 0; \ + __entry->npca_punctured = 0; \ } \ } while (0) #define CHAN_DEF_PR_FMT \ - "band: %d, control freq: %u.%03u, width: %d, cf1: %u.%03u, cf2: %u, punct: 0x%x" + "band: %d, control freq: %u.%03u, width: %d, cf1: %u.%03u, cf2: %u, punct: 0x%x, npca:%u, npca_punct:0x%x" #define CHAN_DEF_PR_ARG __entry->band, __entry->control_freq, \ __entry->freq_offset, __entry->width, \ __entry->center_freq1, __entry->freq1_offset, \ - __entry->center_freq2, __entry->punctured + __entry->center_freq2, __entry->punctured, \ + __entry->npca_pri_freq, __entry->npca_punctured #define FILS_AAD_ASSIGN(fa) \ do { \ -- cgit v1.2.3 From 3ca884399be1bcb04baec264a75133cf7a422b52 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 28 Apr 2026 11:25:33 +0200 Subject: wifi: cfg80211: add helper for parsing NPCA to chandef Add a cfg80211_chandef_add_npca() helper function that takes an existing chandef without NPCA and sets the NPCA information from the format used in UHR operation and UHR Parameters Update. Link: https://patch.msgid.link/20260428112708.5cdc4e69a306.I95d396ac671da438f340b1afb735ebfe33164894@changeid Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 13 +++++++++++++ net/wireless/chan.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) (limited to 'include') diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 16e11c5718b5..e807999fc8e8 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -1154,6 +1154,19 @@ int cfg80211_chandef_primary(const struct cfg80211_chan_def *chandef, enum nl80211_chan_width primary_chan_width, u16 *punctured); +/** + * cfg80211_chandef_add_npca - parse and add NPCA information to chandef + * @wiphy: the wiphy this will be used for, for channel pointer lookup + * @chandef: the chandef to modify, must be a valid chandef without NPCA + * @npca: the NPCA information, can be %NULL + * + * Returns: 0 if the NPCA information was added and the resulting + * chandef is valid, a negative error code on errors + */ +int cfg80211_chandef_add_npca(struct wiphy *wiphy, + struct cfg80211_chan_def *chandef, + const struct ieee80211_uhr_npca_info *npca); + /** * nl80211_send_chandef - sends the channel definition. * @msg: the msg to send channel definition diff --git a/net/wireless/chan.c b/net/wireless/chan.c index 38b83b44b5dc..5289a4ddacd6 100644 --- a/net/wireless/chan.c +++ b/net/wireless/chan.c @@ -556,6 +556,52 @@ int cfg80211_chandef_primary(const struct cfg80211_chan_def *c, } EXPORT_SYMBOL(cfg80211_chandef_primary); +int cfg80211_chandef_add_npca(struct wiphy *wiphy, + struct cfg80211_chan_def *chandef, + const struct ieee80211_uhr_npca_info *npca) +{ + struct cfg80211_chan_def new_chandef = *chandef; + u32 width, npca_freq; + u8 offs; + + if (chandef->npca_chan || chandef->npca_punctured) + return -EINVAL; + + if (WARN_ON(!cfg80211_chandef_valid(chandef))) + return -EINVAL; + + if (!npca) + return 0; + + switch (chandef->width) { + case NL80211_CHAN_WIDTH_80: + case NL80211_CHAN_WIDTH_160: + case NL80211_CHAN_WIDTH_320: + break; + default: + return -EINVAL; + } + + offs = le32_get_bits(npca->params, + IEEE80211_UHR_NPCA_PARAMS_PRIMARY_CHAN_OFFS); + + width = cfg80211_chandef_get_width(chandef); + npca_freq = chandef->center_freq1 - width / 2 + 10 + 20 * offs; + new_chandef.npca_chan = ieee80211_get_channel(wiphy, npca_freq); + if (!new_chandef.npca_chan) + return -EINVAL; + + if (npca->params & cpu_to_le32(IEEE80211_UHR_NPCA_PARAMS_DIS_SUBCH_BMAP_PRES)) + new_chandef.npca_punctured = le16_to_cpu(npca->dis_subch_bmap[0]); + + if (!cfg80211_chandef_valid(&new_chandef)) + return -EINVAL; + + *chandef = new_chandef; + return 0; +} +EXPORT_SYMBOL(cfg80211_chandef_add_npca); + static const struct cfg80211_chan_def * check_chandef_primary_compat(const struct cfg80211_chan_def *c1, const struct cfg80211_chan_def *c2, -- cgit v1.2.3 From a731e2fc26b68c0af61de7138e4b468091668a55 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 28 Apr 2026 11:25:37 +0200 Subject: wifi: mac80211: allow only AP chanctx sharing with NPCA When two interfaces share a channel context, disable NPCA unless both are AP interfaces that require NPCA. This way, two AP interfaces can have identical chandefs set up and share the channel context, but any non-APs cannot share a chanctx with NPCA (they'd almost certainly have different BSS color.) This doesn't mean the chanctx cannot be shared but rather that NPCA will be disabled on the shared channel context. Link: https://patch.msgid.link/20260428112708.3832e15f4e78.I08a7c7f47d796f4d5d8f9a682c1fba37db2e4cf5@changeid Signed-off-by: Johannes Berg --- include/net/mac80211.h | 7 +++++++ net/mac80211/cfg.c | 15 ++++++++++++--- net/mac80211/chan.c | 34 ++++++++++++++++++++++++++++++---- 3 files changed, 49 insertions(+), 7 deletions(-) (limited to 'include') diff --git a/include/net/mac80211.h b/include/net/mac80211.h index ba1723e62481..ae7fd44e17b5 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -218,6 +218,8 @@ struct ieee80211_low_level_stats { * bandwidth) OFDMA settings need to be changed * @IEEE80211_CHANCTX_CHANGE_PUNCTURING: The punctured channel(s) bitmap * was changed. + * @IEEE80211_CHANCTX_CHANGE_NPCA: NPCA configuration changed + * @IEEE80211_CHANCTX_CHANGE_NPCA_PUNCT: NPCA puncturing changed */ enum ieee80211_chanctx_change { IEEE80211_CHANCTX_CHANGE_WIDTH = BIT(0), @@ -227,6 +229,8 @@ enum ieee80211_chanctx_change { IEEE80211_CHANCTX_CHANGE_MIN_DEF = BIT(4), IEEE80211_CHANCTX_CHANGE_AP = BIT(5), IEEE80211_CHANCTX_CHANGE_PUNCTURING = BIT(6), + IEEE80211_CHANCTX_CHANGE_NPCA = BIT(7), + IEEE80211_CHANCTX_CHANGE_NPCA_PUNCT = BIT(8), }; /** @@ -234,10 +238,13 @@ enum ieee80211_chanctx_change { * @oper: channel definition to use for operation * @ap: the channel definition of the AP, if any * (otherwise the chan member is %NULL) + * @require_npca: If NPCA is configured, require it to + * remain, this is used by AP interfaces */ struct ieee80211_chan_req { struct cfg80211_chan_def oper; struct cfg80211_chan_def ap; + bool require_npca; }; /** diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 413dd73e8815..7e9dcb02f9bd 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -1613,7 +1613,10 @@ static int ieee80211_start_ap(struct wiphy *wiphy, struct net_device *dev, unsigned int link_id = params->beacon.link_id; struct ieee80211_link_data *link; struct ieee80211_bss_conf *link_conf; - struct ieee80211_chan_req chanreq = { .oper = params->chandef }; + struct ieee80211_chan_req chanreq = { + .oper = params->chandef, + .require_npca = true, + }; u64 tsf; lockdep_assert_wiphy(local->hw.wiphy); @@ -4627,7 +4630,10 @@ __ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev, struct cfg80211_csa_settings *params) { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); - struct ieee80211_chan_req chanreq = { .oper = params->chandef }; + struct ieee80211_chan_req chanreq = { + .oper = params->chandef, + .require_npca = true, + }; struct ieee80211_local *local = sdata->local; struct ieee80211_channel_switch ch_switch = { .link_id = params->link_id, @@ -5069,7 +5075,10 @@ static int ieee80211_set_ap_chanwidth(struct wiphy *wiphy, { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ieee80211_link_data *link; - struct ieee80211_chan_req chanreq = { .oper = *chandef }; + struct ieee80211_chan_req chanreq = { + .oper = *chandef, + .require_npca = true, + }; int ret; u64 changed = 0; diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c index 16ee79566e50..5e24988ef561 100644 --- a/net/mac80211/chan.c +++ b/net/mac80211/chan.c @@ -285,19 +285,41 @@ ieee80211_chanreq_compatible(const struct ieee80211_chan_req *a, const struct ieee80211_chan_req *b, struct ieee80211_chan_req *tmp) { + struct ieee80211_chan_req _a = *a, _b = *b; const struct cfg80211_chan_def *compat; if (a->ap.chan && b->ap.chan && !cfg80211_chandef_identical(&a->ap, &b->ap)) return NULL; - compat = cfg80211_chandef_compatible(&a->oper, &b->oper); + /* + * Remove NPCA if it's not required, so that interfaces + * sharing a channel context will not use NPCA while the + * channel context is shared. + * If both sides are AP interfaces requiring NPC, there's + * an assumption that userspace will set them up with + * identical configurations and the same BSS color + * (if the config is not identical, sharing will fail due + * to cfg80211_chandef_compatible() failing below.) + */ + if (!_a.require_npca) { + _a.oper.npca_chan = NULL; + _a.oper.npca_punctured = 0; + } + + if (!_b.require_npca) { + _b.oper.npca_chan = NULL; + _b.oper.npca_punctured = 0; + } + + compat = cfg80211_chandef_compatible(&_a.oper, &_b.oper); if (!compat) return NULL; /* Note: later code assumes this always fills & returns tmp if compat */ tmp->oper = *compat; tmp->ap = a->ap.chan ? a->ap : b->ap; + tmp->require_npca = a->require_npca && b->require_npca; return tmp; } @@ -754,7 +776,6 @@ static void _ieee80211_change_chanctx(struct ieee80211_local *local, const struct ieee80211_chan_req *chanreq, struct ieee80211_link_data *rsvd_for) { - const struct cfg80211_chan_def *chandef = &chanreq->oper; struct ieee80211_chan_req ctx_req = { .oper = ctx->conf.def, .ap = ctx->conf.ap, @@ -762,7 +783,7 @@ static void _ieee80211_change_chanctx(struct ieee80211_local *local, u32 changed = 0; /* 5/10 MHz not handled here */ - switch (chandef->width) { + switch (chanreq->oper.width) { case NL80211_CHAN_WIDTH_1: case NL80211_CHAN_WIDTH_2: case NL80211_CHAN_WIDTH_4: @@ -807,10 +828,15 @@ static void _ieee80211_change_chanctx(struct ieee80211_local *local, changed |= IEEE80211_CHANCTX_CHANGE_WIDTH; if (ctx->conf.def.punctured != chanreq->oper.punctured) changed |= IEEE80211_CHANCTX_CHANGE_PUNCTURING; + if (ctx->conf.def.npca_chan != chanreq->oper.npca_chan) + changed |= IEEE80211_CHANCTX_CHANGE_NPCA; + if (chanreq->oper.npca_chan && + ctx->conf.def.npca_punctured != chanreq->oper.npca_punctured) + changed |= IEEE80211_CHANCTX_CHANGE_NPCA_PUNCT; } if (!cfg80211_chandef_identical(&ctx->conf.ap, &chanreq->ap)) changed |= IEEE80211_CHANCTX_CHANGE_AP; - ctx->conf.def = *chandef; + ctx->conf.def = chanreq->oper; ctx->conf.ap = chanreq->ap; /* check if min chanctx also changed */ -- cgit v1.2.3 From 5af8f06349d85824a32aa15949cb66b174e0f713 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 28 Apr 2026 11:25:38 +0200 Subject: wifi: mac80211: mlme: use NPCA chandef if capable If the device is capable, parse the AP chandef with NPCA. Also advertise the other NPCA operational parameters to the underlying driver and track if they change (though not with BSS critical update etc. yet) Since NPCA can only be enabled when the chanctx isn't shared, the channel context code needs to clear/set npca.enabled in the per-link configuration, except during association since we can't enable NPCA before having completed association. In this case, set npca.enabled during the association process. Link: https://patch.msgid.link/20260428112708.eb1e42c0b6d7.I0acd8445d4600363afb8430922531450399d0fab@changeid Signed-off-by: Johannes Berg --- include/net/mac80211.h | 25 ++++++++++++++++ net/mac80211/chan.c | 38 +++++++++++++++++++++++++ net/mac80211/mlme.c | 77 +++++++++++++++++++++++++++++++++++++++++++++++++- net/mac80211/util.c | 3 ++ 4 files changed, 142 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/include/net/mac80211.h b/include/net/mac80211.h index ae7fd44e17b5..4fb579805e0f 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -373,6 +373,7 @@ struct ieee80211_vif_chanctx_switch { * @BSS_CHANGED_MLD_TTLM: negotiated TID to link mapping was changed * @BSS_CHANGED_TPE: transmit power envelope changed * @BSS_CHANGED_NAN_LOCAL_SCHED: NAN local schedule changed (NAN mode only) + * @BSS_CHANGED_NPCA: NPCA parameters changed */ enum ieee80211_bss_change { BSS_CHANGED_ASSOC = 1<<0, @@ -411,6 +412,7 @@ enum ieee80211_bss_change { BSS_CHANGED_MLD_TTLM = BIT_ULL(34), BSS_CHANGED_TPE = BIT_ULL(35), BSS_CHANGED_NAN_LOCAL_SCHED = BIT_ULL(36), + BSS_CHANGED_NPCA = BIT_ULL(37), /* when adding here, make sure to change ieee80211_reconfig */ }; @@ -596,6 +598,26 @@ struct ieee80211_parsed_tpe { struct ieee80211_parsed_tpe_psd psd_local[2], psd_reg_client[2]; }; +/** + * struct ieee80211_bss_npca_params - NPCA parameters + * @min_dur_thresh: NPCA minimum duration threshold (512 + 128*n usec) + * @switch_delay: NPCA switch delay (units of 4 usec) + * @switch_back_delay: NPCA switch back delay (units of 4 usec) + * @init_qsrc: initial QSRC value + * @moplen: indicates MOPLEN NPCA is permitted in the BSS + * @enabled: NPCA is enabled for this link + * + * Note: the individual values (except @enabled) are in spec representation. + */ +struct ieee80211_bss_npca_params { + u32 min_dur_thresh:4, + switch_delay:6, + switch_back_delay:6, + init_qsrc:2, + moplen:1, + enabled:1; +}; + /** * struct ieee80211_bss_conf - holds the BSS's changing parameters * @@ -770,6 +792,7 @@ struct ieee80211_parsed_tpe { * (as opposed to hearing its value from another link's beacon). * @s1g_long_beacon_period: number of beacon intervals between each long * beacon transmission. + * @npca: NPCA parameters */ struct ieee80211_bss_conf { struct ieee80211_vif *vif; @@ -873,6 +896,8 @@ struct ieee80211_bss_conf { u8 bss_param_ch_cnt_link_id; u8 s1g_long_beacon_period; + + struct ieee80211_bss_npca_params npca; }; #define IEEE80211_NAN_MAX_CHANNELS 3 diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c index 5e24988ef561..b9d563f927da 100644 --- a/net/mac80211/chan.c +++ b/net/mac80211/chan.c @@ -770,6 +770,38 @@ void ieee80211_recalc_chanctx_min_def(struct ieee80211_local *local, _ieee80211_recalc_chanctx_min_def(local, ctx, NULL, false); } +static void +ieee80211_chanctx_update_npca_links(struct ieee80211_local *local, + struct ieee80211_chanctx *ctx, + bool enable) +{ + struct ieee80211_chanctx_user_iter iter; + + if (!!ctx->conf.def.npca_chan != enable) + return; + + for_each_chanctx_user_assigned(local, ctx, &iter) { + if (!iter.link) + continue; + if (!iter.sdata->vif.cfg.assoc) + continue; + + if (enable) { + if (!iter.link->conf->chanreq.oper.npca_chan) + continue; + } else { + if (!iter.link->conf->npca.enabled) + continue; + } + + iter.link->conf->npca.enabled = enable; + drv_link_info_changed(local, iter.sdata, + iter.link->conf, + iter.link->link_id, + BSS_CHANGED_NPCA); + } +} + static void _ieee80211_change_chanctx(struct ieee80211_local *local, struct ieee80211_chanctx *ctx, struct ieee80211_chanctx *old_ctx, @@ -845,10 +877,16 @@ static void _ieee80211_change_chanctx(struct ieee80211_local *local, ieee80211_add_wbrf(local, &ctx->conf.def); + /* disable NPCA on the link using it */ + ieee80211_chanctx_update_npca_links(local, ctx, false); + drv_change_chanctx(local, ctx, changed); /* check if BW is wider */ ieee80211_chan_bw_change(local, old_ctx, false, false); + + /* enable NPCA on the link that requested it */ + ieee80211_chanctx_update_npca_links(local, ctx, true); } static void ieee80211_change_chanctx(struct ieee80211_local *local, diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index 5be390de6756..3db8a499a1c8 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -401,6 +401,7 @@ check_uhr: elems->uhr_operation_len, false)) { struct cfg80211_chan_def npca_chandef = *chandef; + const struct ieee80211_sta_uhr_cap *uhr_cap; const struct ieee80211_uhr_npca_info *npca; npca = ieee80211_uhr_npca_info(uhr_oper); @@ -411,6 +412,14 @@ check_uhr: "AP UHR NPCA settings invalid, disabling UHR\n"); return IEEE80211_CONN_MODE_EHT; } + + uhr_cap = ieee80211_get_uhr_iftype_cap_vif(sband, &sdata->vif); + /* can't happen since we must have UHR to parse the elems */ + if (WARN_ON(!uhr_cap)) + return IEEE80211_CONN_MODE_EHT; + + if (uhr_cap->mac.mac_cap[0] & IEEE80211_UHR_MAC_CAP0_NPCA_SUPP) + *chandef = npca_chandef; } return IEEE80211_CONN_MODE_UHR; @@ -1320,6 +1329,7 @@ static int ieee80211_config_bw(struct ieee80211_link_data *link, .conn = &link->u.mgd.conn, }; struct ieee80211_sub_if_data *sdata = link->sdata; + struct ieee80211_chanctx_conf *chanctx_conf; struct ieee80211_chan_req chanreq = {}; enum ieee80211_conn_mode ap_mode; const char *frame; @@ -1403,8 +1413,55 @@ static int ieee80211_config_bw(struct ieee80211_link_data *link, } } - if (ieee80211_chanreq_identical(&chanreq, &link->conf->chanreq)) + /* + * Beacons don't have the full information - we need to track + * critical updates for NPCA parameters etc. For now only handle + * association and link reconfiguration response. + */ + if (stype != IEEE80211_STYPE_BEACON && + chanreq.oper.npca_chan && elems->uhr_operation && + ieee80211_uhr_oper_size_ok((const void *)elems->uhr_operation, + elems->uhr_operation_len, + false)) { + const struct ieee80211_uhr_npca_info *npca; + struct ieee80211_bss_npca_params params = {}; + + npca = ieee80211_uhr_npca_info(elems->uhr_operation); + if (!npca) { + chanreq.oper.npca_chan = NULL; + chanreq.oper.npca_punctured = 0; + } else { + params.min_dur_thresh = + le32_get_bits(npca->params, + IEEE80211_UHR_NPCA_PARAMS_MIN_DUR_THRESH); + params.switch_delay = + le32_get_bits(npca->params, + IEEE80211_UHR_NPCA_PARAMS_SWITCH_DELAY); + params.switch_back_delay = + le32_get_bits(npca->params, + IEEE80211_UHR_NPCA_PARAMS_SWITCH_BACK_DELAY); + params.init_qsrc = + le32_get_bits(npca->params, + IEEE80211_UHR_NPCA_PARAMS_INIT_QSRC); + params.moplen = + le32_get_bits(npca->params, + IEEE80211_UHR_NPCA_PARAMS_MOPLEN); + /* don't change the enabled bit yet */ + params.enabled = link->conf->npca.enabled; + } + + if (memcmp(¶ms, &link->conf->npca, sizeof(params)) || + !update) { + link->conf->npca = params; + *changed |= BSS_CHANGED_NPCA; + } + } + + if (ieee80211_chanreq_identical(&chanreq, &link->conf->chanreq)) { + if (update) + goto update_npca; return 0; + } link_info(link, "AP %pM changed bandwidth in %s, new used config is %d.%03d MHz, width %d (%d.%03d/%d MHz)\n", @@ -1451,6 +1508,24 @@ static int ieee80211_config_bw(struct ieee80211_link_data *link, } cfg80211_schedule_channels_check(&sdata->wdev); + +update_npca: + chanctx_conf = sdata_dereference(link->conf->chanctx_conf, sdata); + /* must be non-NULL when update is true */ + if (WARN_ON(!chanctx_conf)) + return -EINVAL; + + /* + * If we're not associated yet (i.e. in the process associating) + * then the chanctx code won't have enabled NPCA in the link, so + * if the channel context was set up with NPCA for us, enable it. + */ + if (chanreq.oper.npca_chan && chanctx_conf->def.npca_chan && + !link->conf->npca.enabled && !sdata->vif.cfg.assoc) { + link->conf->npca.enabled = true; + *changed |= BSS_CHANGED_NPCA; + } + return 0; } diff --git a/net/mac80211/util.c b/net/mac80211/util.c index 24537794256c..3184007f40c1 100644 --- a/net/mac80211/util.c +++ b/net/mac80211/util.c @@ -2147,6 +2147,9 @@ int ieee80211_reconfig(struct ieee80211_local *local) ieee80211_bss_info_change_notify(sdata, changed); } else if (!WARN_ON(!link)) { + if (link->conf->npca.enabled) + changed |= BSS_CHANGED_NPCA; + ieee80211_link_info_change_notify(sdata, link, changed); changed = BSS_CHANGED_ASSOC | -- cgit v1.2.3 From 07f3e2174606202f7954e3a0454dd59064148610 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Tue, 28 Apr 2026 11:25:40 +0200 Subject: wifi: cfg80211: separate NPCA validity from chandef validity When considering both NPCA and DBE, it can appear that the NPCA configuration is invalid, e.g. for an 80 MHz BSS channel with DBE to 160 MHz: | primary channel | NPCA primary channel | | V V | p | | n | | | | | | | BSS channel | | DBE channel | Now the NPCA primary channel is in the same half as the primary channel, and the NPCA puncturing bitmap could be completely invalid as a puncturing bitmap when considering the overall channel. Split out the validity checks from cfg80211_chandef_valid() to a new cfg80211_chandef_npca_valid() function that just checks the NPCA configuration against the BSS chandef. Link: https://patch.msgid.link/20260428112708.1225df131557.If3a6afadcce05d215b72fd82175f72373a0f6d24@changeid Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 23 ++++++++++++++++++++ net/mac80211/mlme.c | 5 ++++- net/wireless/chan.c | 59 ++++++++++++++++++++++++++++++++------------------ 3 files changed, 65 insertions(+), 22 deletions(-) (limited to 'include') diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index e807999fc8e8..ddcf559430dd 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -849,6 +849,10 @@ struct key_params { * struct cfg80211_chan_def - channel definition * @chan: the (control) channel * @npca_chan: the NPCA primary channel + * Note that if DBE is in use, this channel may appear to be + * inside the primary half of the chandef. Implementations + * can use the position of this channel to understand how + * NPCA is used. * @width: channel width * @center_freq1: center frequency of first segment * @center_freq2: center frequency of second segment @@ -864,6 +868,8 @@ struct key_params { * @npca_punctured: NPCA puncturing bitmap, like @punctured but for * NPCA transmissions. If NPCA is used (@npca_chan is not %NULL) * this will be a superset of the @punctured bimap. + * Note that if DBE is used, this bitmap is also shifted to be in + * accordance with the overall chandef bandwidth. * @s1g_primary_2mhz: Indicates if the control channel pointed to * by 'chan' exists as a 1MHz primary subchannel within an * S1G 2MHz primary channel. @@ -1154,6 +1160,23 @@ int cfg80211_chandef_primary(const struct cfg80211_chan_def *chandef, enum nl80211_chan_width primary_chan_width, u16 *punctured); +/** + * cfg80211_chandef_npca_valid - check that NPCA information is valid + * @wiphy: the wiphy to check for, for channel pointer lookup + * @chandef: the BSS channel chandef to check against + * @npca: NPCA information, can be %NULL in which case this + * always returns %true + * + * Note that DBE must not have been configured into the chandef yet + * before checking NPCA, i.e. @chandef must represent the BSS channel. + * + * Returns: %true if the NPCA channel and puncturing bitmap are valid + * according to the chandef, %false otherwise + */ +bool cfg80211_chandef_npca_valid(struct wiphy *wiphy, + const struct cfg80211_chan_def *chandef, + const struct ieee80211_uhr_npca_info *npca); + /** * cfg80211_chandef_add_npca - parse and add NPCA information to chandef * @wiphy: the wiphy this will be used for, for channel pointer lookup diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index 3db8a499a1c8..a818568d34b9 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -406,7 +406,10 @@ check_uhr: npca = ieee80211_uhr_npca_info(uhr_oper); - if (cfg80211_chandef_add_npca(sdata->local->hw.wiphy, + /* DBE is not considered yet, so this works */ + if (!cfg80211_chandef_npca_valid(sdata->local->hw.wiphy, + &npca_chandef, npca) || + cfg80211_chandef_add_npca(sdata->local->hw.wiphy, &npca_chandef, npca)) { sdata_info(sdata, "AP UHR NPCA settings invalid, disabling UHR\n"); diff --git a/net/wireless/chan.c b/net/wireless/chan.c index 5289a4ddacd6..ed35b55b1b67 100644 --- a/net/wireless/chan.c +++ b/net/wireless/chan.c @@ -460,9 +460,6 @@ bool cfg80211_chandef_valid(const struct cfg80211_chan_def *chandef) return false; if (chandef->npca_chan) { - bool pri_upper, npca_upper; - u32 cf1; - switch (chandef->width) { case NL80211_CHAN_WIDTH_80: case NL80211_CHAN_WIDTH_160: @@ -471,24 +468,6 @@ bool cfg80211_chandef_valid(const struct cfg80211_chan_def *chandef) default: return false; } - - if (!cfg80211_chandef_valid_control_freq(chandef, - chandef->npca_chan->center_freq)) - return false; - - cf1 = chandef->center_freq1; - pri_upper = chandef->chan->center_freq > cf1; - npca_upper = chandef->npca_chan->center_freq > cf1; - - if (pri_upper == npca_upper) - return false; - - if (!valid_puncturing_bitmap(chandef, - chandef->npca_chan->center_freq, - chandef->npca_punctured) || - (chandef->punctured & chandef->npca_punctured) != - chandef->punctured) - return false; } else if (chandef->npca_punctured) { return false; } @@ -556,6 +535,44 @@ int cfg80211_chandef_primary(const struct cfg80211_chan_def *c, } EXPORT_SYMBOL(cfg80211_chandef_primary); +bool cfg80211_chandef_npca_valid(struct wiphy *wiphy, + const struct cfg80211_chan_def *chandef, + const struct ieee80211_uhr_npca_info *npca) +{ + struct cfg80211_chan_def tmp = *chandef; + bool pri_upper, npca_upper; + u32 cf1; + + if (chandef->npca_chan || chandef->npca_punctured) + return false; + + if (!npca) + return true; + + if (cfg80211_chandef_add_npca(wiphy, &tmp, npca)) + return false; + + if (!cfg80211_chandef_valid_control_freq(&tmp, + tmp.npca_chan->center_freq)) + return false; + + cf1 = tmp.center_freq1; + pri_upper = tmp.chan->center_freq > cf1; + npca_upper = tmp.npca_chan->center_freq > cf1; + + if (pri_upper == npca_upper) + return false; + + if (!valid_puncturing_bitmap(&tmp, + tmp.npca_chan->center_freq, + tmp.npca_punctured) || + (tmp.punctured & tmp.npca_punctured) != tmp.punctured) + return false; + + return true; +} +EXPORT_SYMBOL(cfg80211_chandef_npca_valid); + int cfg80211_chandef_add_npca(struct wiphy *wiphy, struct cfg80211_chan_def *chandef, const struct ieee80211_uhr_npca_info *npca) -- cgit v1.2.3 From 944bfc1b1c6fe9417668006aae7124886bcca038 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Sat, 2 May 2026 03:12:54 +0000 Subject: udp_tunnel: Pass struct sock to udp_tunnel_sock_release(). None of the udp_tunnel users need struct socket in their fast paths; it is only used for tunnel setup / teardown. While the UDP tunnel interface accepts struct socket, this encourages users to store the pointer unnecessarily. This leads to extra dereferences when accessing struct sock fields (e.g., sk->sk_user_data instead of sock->sk->sk_user_data). Furthermore, these dereferences necessitate synchronize_rcu() in udp_tunnel_sock_release() to protect the fast paths from sock_orphan() setting sk->sk_socket to NULL. This overhead can be avoided if users store the struct sock pointer directly in their private structures. As a prep, let's change udp_tunnel_sock_release() to take struct sock instead of struct socket. Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260502031401.3557229-2-kuniyu@google.com Signed-off-by: Jakub Kicinski --- drivers/infiniband/sw/rxe/rxe_net.c | 4 ++-- drivers/infiniband/sw/rxe/rxe_ns.c | 4 ++-- drivers/net/amt.c | 2 +- drivers/net/bareudp.c | 2 +- drivers/net/geneve.c | 2 +- drivers/net/gtp.c | 6 +++--- drivers/net/pfcp.c | 2 +- drivers/net/vxlan/vxlan_core.c | 4 ++-- drivers/net/wireguard/socket.c | 4 ++-- include/net/udp_tunnel.h | 2 +- net/ipv4/fou_core.c | 7 ++----- net/ipv4/udp_tunnel_core.c | 6 ++++-- net/sctp/protocol.c | 6 +++--- net/tipc/udp_media.c | 4 ++-- 14 files changed, 27 insertions(+), 28 deletions(-) (limited to 'include') diff --git a/drivers/infiniband/sw/rxe/rxe_net.c b/drivers/infiniband/sw/rxe/rxe_net.c index 50a2cb5405e2..b454e4a17997 100644 --- a/drivers/infiniband/sw/rxe/rxe_net.c +++ b/drivers/infiniband/sw/rxe/rxe_net.c @@ -288,7 +288,7 @@ static struct socket *rxe_setup_udp_tunnel(struct net *net, __be16 port, return sock; } -static void rxe_release_udp_tunnel(struct socket *sk) +static void rxe_release_udp_tunnel(struct sock *sk) { if (sk) udp_tunnel_sock_release(sk); @@ -636,7 +636,7 @@ static void rxe_sock_put(struct sock *sk, if (refcount_read(&sk->sk_refcnt) > SK_REF_FOR_TUNNEL) { __sock_put(sk); } else { - rxe_release_udp_tunnel(sk->sk_socket); + rxe_release_udp_tunnel(sk); sk = NULL; set_sk(net, sk); } diff --git a/drivers/infiniband/sw/rxe/rxe_ns.c b/drivers/infiniband/sw/rxe/rxe_ns.c index 8b9d734229b2..64621c89f8bf 100644 --- a/drivers/infiniband/sw/rxe/rxe_ns.c +++ b/drivers/infiniband/sw/rxe/rxe_ns.c @@ -47,7 +47,7 @@ static void rxe_ns_exit(struct net *net) rcu_read_unlock(); if (sk) { rcu_assign_pointer(ns_sk->rxe_sk4, NULL); - udp_tunnel_sock_release(sk->sk_socket); + udp_tunnel_sock_release(sk); } #if IS_ENABLED(CONFIG_IPV6) @@ -56,7 +56,7 @@ static void rxe_ns_exit(struct net *net) rcu_read_unlock(); if (sk) { rcu_assign_pointer(ns_sk->rxe_sk6, NULL); - udp_tunnel_sock_release(sk->sk_socket); + udp_tunnel_sock_release(sk); } #endif } diff --git a/drivers/net/amt.c b/drivers/net/amt.c index f2f3139e38a5..fc415072864b 100644 --- a/drivers/net/amt.c +++ b/drivers/net/amt.c @@ -3032,7 +3032,7 @@ static int amt_dev_stop(struct net_device *dev) RCU_INIT_POINTER(amt->sock, NULL); synchronize_net(); if (sock) - udp_tunnel_sock_release(sock); + udp_tunnel_sock_release(sock->sk); cancel_work_sync(&amt->event_wq); for (i = 0; i < AMT_MAX_EVENTS; i++) { diff --git a/drivers/net/bareudp.c b/drivers/net/bareudp.c index da5866ba0699..f3025a5c5261 100644 --- a/drivers/net/bareudp.c +++ b/drivers/net/bareudp.c @@ -290,7 +290,7 @@ static void bareudp_sock_release(struct bareudp_dev *bareudp) sock = bareudp->sock; rcu_assign_pointer(bareudp->sock, NULL); synchronize_net(); - udp_tunnel_sock_release(sock); + udp_tunnel_sock_release(sock->sk); } static int bareudp_stop(struct net_device *dev) diff --git a/drivers/net/geneve.c b/drivers/net/geneve.c index c6563367d382..8d55160305ee 100644 --- a/drivers/net/geneve.c +++ b/drivers/net/geneve.c @@ -1018,7 +1018,7 @@ static void __geneve_sock_release(struct geneve_sock *gs) list_del(&gs->list); udp_tunnel_notify_del_rx_port(gs->sock, UDP_TUNNEL_TYPE_GENEVE); - udp_tunnel_sock_release(gs->sock); + udp_tunnel_sock_release(gs->sock->sk); kfree_rcu(gs, rcu); } diff --git a/drivers/net/gtp.c b/drivers/net/gtp.c index 5150f2e4f66b..064ce1029d33 100644 --- a/drivers/net/gtp.c +++ b/drivers/net/gtp.c @@ -885,8 +885,8 @@ static void gtp_encap_disable_sock(struct sock *sk) static void gtp_encap_disable(struct gtp_dev *gtp) { if (gtp->sk_created) { - udp_tunnel_sock_release(gtp->sk0->sk_socket); - udp_tunnel_sock_release(gtp->sk1u->sk_socket); + udp_tunnel_sock_release(gtp->sk0); + udp_tunnel_sock_release(gtp->sk1u); gtp->sk_created = false; gtp->sk0 = NULL; gtp->sk1u = NULL; @@ -1451,7 +1451,7 @@ static int gtp_create_sockets(struct gtp_dev *gtp, const struct nlattr *nla, sk1u = gtp_create_sock(UDP_ENCAP_GTP1U, gtp, nla, family); if (IS_ERR(sk1u)) { - udp_tunnel_sock_release(sk0->sk_socket); + udp_tunnel_sock_release(sk0); return PTR_ERR(sk1u); } diff --git a/drivers/net/pfcp.c b/drivers/net/pfcp.c index 28e6bc4a1f14..ce58038cfccb 100644 --- a/drivers/net/pfcp.c +++ b/drivers/net/pfcp.c @@ -104,7 +104,7 @@ drop: static void pfcp_del_sock(struct pfcp_dev *pfcp) { - udp_tunnel_sock_release(pfcp->sock); + udp_tunnel_sock_release(pfcp->sock->sk); pfcp->sock = NULL; } diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c index e88798497503..1d1aba1c7cfc 100644 --- a/drivers/net/vxlan/vxlan_core.c +++ b/drivers/net/vxlan/vxlan_core.c @@ -1519,13 +1519,13 @@ static void vxlan_sock_release(struct vxlan_dev *vxlan) vxlan_vs_del_dev(vxlan); if (__vxlan_sock_release_prep(sock4)) { - udp_tunnel_sock_release(sock4->sock); + udp_tunnel_sock_release(sock4->sock->sk); kfree(sock4); } #if IS_ENABLED(CONFIG_IPV6) if (__vxlan_sock_release_prep(sock6)) { - udp_tunnel_sock_release(sock6->sock); + udp_tunnel_sock_release(sock6->sock->sk); kfree(sock6); } #endif diff --git a/drivers/net/wireguard/socket.c b/drivers/net/wireguard/socket.c index c362c78d908e..4a4c177af170 100644 --- a/drivers/net/wireguard/socket.c +++ b/drivers/net/wireguard/socket.c @@ -335,7 +335,7 @@ static void sock_free(struct sock *sock) if (unlikely(!sock)) return; sk_clear_memalloc(sock); - udp_tunnel_sock_release(sock->sk_socket); + udp_tunnel_sock_release(sock); } static void set_sock_opts(struct socket *sock) @@ -396,7 +396,7 @@ retry: port6.local_udp_port = inet_sk(new4->sk)->inet_sport; ret = udp_sock_create(net, &port6, &new6); if (ret < 0) { - udp_tunnel_sock_release(new4); + udp_tunnel_sock_release(new4->sk); if (ret == -EADDRINUSE && !port && retries++ < 100) goto retry; pr_err("%s: Could not create IPv6 socket\n", diff --git a/include/net/udp_tunnel.h b/include/net/udp_tunnel.h index 47c23d4a1740..dbbd56280f50 100644 --- a/include/net/udp_tunnel.h +++ b/include/net/udp_tunnel.h @@ -176,7 +176,7 @@ static inline void udp_tunnel_set_inner_protocol(struct sk_buff *skb, skb_set_inner_protocol(skb, inner_proto); } -void udp_tunnel_sock_release(struct socket *sock); +void udp_tunnel_sock_release(struct sock *sk); struct rtable *udp_tunnel_dst_lookup(struct sk_buff *skb, struct net_device *dev, diff --git a/net/ipv4/fou_core.c b/net/ipv4/fou_core.c index 5bae3cf7fe76..422f86291b42 100644 --- a/net/ipv4/fou_core.c +++ b/net/ipv4/fou_core.c @@ -558,11 +558,8 @@ static int fou_add_to_port_list(struct net *net, struct fou *fou, static void fou_release(struct fou *fou) { - struct socket *sock = fou->sock; - list_del(&fou->list); - udp_tunnel_sock_release(sock); - + udp_tunnel_sock_release(fou->sock->sk); kfree_rcu(fou, rcu); } @@ -634,7 +631,7 @@ static int fou_create(struct net *net, struct fou_cfg *cfg, error: kfree(fou); if (sock) - udp_tunnel_sock_release(sock); + udp_tunnel_sock_release(sock->sk); return err; } diff --git a/net/ipv4/udp_tunnel_core.c b/net/ipv4/udp_tunnel_core.c index b1f667c52cb2..1159a6a6fbb2 100644 --- a/net/ipv4/udp_tunnel_core.c +++ b/net/ipv4/udp_tunnel_core.c @@ -195,9 +195,11 @@ void udp_tunnel_xmit_skb(struct rtable *rt, struct sock *sk, struct sk_buff *skb } EXPORT_SYMBOL_GPL(udp_tunnel_xmit_skb); -void udp_tunnel_sock_release(struct socket *sock) +void udp_tunnel_sock_release(struct sock *sk) { - rcu_assign_sk_user_data(sock->sk, NULL); + struct socket *sock = sk->sk_socket; + + rcu_assign_sk_user_data(sk, NULL); synchronize_rcu(); kernel_sock_shutdown(sock, SHUT_RDWR); sock_release(sock); diff --git a/net/sctp/protocol.c b/net/sctp/protocol.c index 5800e7ee7ea0..ffe594ad4414 100644 --- a/net/sctp/protocol.c +++ b/net/sctp/protocol.c @@ -878,7 +878,7 @@ int sctp_udp_sock_start(struct net *net) err = udp_sock_create(net, &udp_conf, &sock); if (err) { pr_err("Failed to create the SCTP UDP tunneling v6 sock\n"); - udp_tunnel_sock_release(net->sctp.udp4_sock->sk_socket); + udp_tunnel_sock_release(net->sctp.udp4_sock); net->sctp.udp4_sock = NULL; return err; } @@ -896,11 +896,11 @@ int sctp_udp_sock_start(struct net *net) void sctp_udp_sock_stop(struct net *net) { if (net->sctp.udp4_sock) { - udp_tunnel_sock_release(net->sctp.udp4_sock->sk_socket); + udp_tunnel_sock_release(net->sctp.udp4_sock); net->sctp.udp4_sock = NULL; } if (net->sctp.udp6_sock) { - udp_tunnel_sock_release(net->sctp.udp6_sock->sk_socket); + udp_tunnel_sock_release(net->sctp.udp6_sock); net->sctp.udp6_sock = NULL; } } diff --git a/net/tipc/udp_media.c b/net/tipc/udp_media.c index 2c66b356025a..d7c050ff5804 100644 --- a/net/tipc/udp_media.c +++ b/net/tipc/udp_media.c @@ -793,7 +793,7 @@ static int tipc_udp_enable(struct net *net, struct tipc_bearer *b, free: dst_cache_destroy(&ub->rcast.dst_cache); - udp_tunnel_sock_release(ub->ubsock); + udp_tunnel_sock_release(ub->ubsock->sk); err: kfree(ub); return err; @@ -815,7 +815,7 @@ static void cleanup_bearer(struct work_struct *work) tn = tipc_net(sock_net(ub->ubsock->sk)); dst_cache_destroy(&ub->rcast.dst_cache); - udp_tunnel_sock_release(ub->ubsock); + udp_tunnel_sock_release(ub->ubsock->sk); /* Note: could use a call_rcu() to avoid another synchronize_net() */ synchronize_net(); -- cgit v1.2.3 From 2cba193628fe523cee6dd61938db2c4563ce15a9 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Sat, 2 May 2026 03:12:55 +0000 Subject: udp_tunnel: Pass struct sock to setup_udp_tunnel_sock(). None of the udp_tunnel users need struct socket in their fast paths; it is only used for tunnel setup / teardown. Even setup_udp_tunnel_sock() does not need struct socket. Let's change setup_udp_tunnel_sock() to take struct sock instead of struct socket. Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260502031401.3557229-3-kuniyu@google.com Signed-off-by: Jakub Kicinski --- drivers/infiniband/sw/rxe/rxe_net.c | 2 +- drivers/net/amt.c | 2 +- drivers/net/bareudp.c | 2 +- drivers/net/geneve.c | 2 +- drivers/net/gtp.c | 4 ++-- drivers/net/ovpn/udp.c | 2 +- drivers/net/pfcp.c | 2 +- drivers/net/vxlan/vxlan_core.c | 2 +- drivers/net/wireguard/socket.c | 4 ++-- include/net/udp_tunnel.h | 2 +- net/ipv4/fou_core.c | 2 +- net/ipv4/udp_tunnel_core.c | 4 +--- net/l2tp/l2tp_core.c | 2 +- net/rxrpc/local_object.c | 2 +- net/sctp/protocol.c | 4 ++-- net/tipc/udp_media.c | 2 +- 16 files changed, 19 insertions(+), 21 deletions(-) (limited to 'include') diff --git a/drivers/infiniband/sw/rxe/rxe_net.c b/drivers/infiniband/sw/rxe/rxe_net.c index b454e4a17997..082ff387d081 100644 --- a/drivers/infiniband/sw/rxe/rxe_net.c +++ b/drivers/infiniband/sw/rxe/rxe_net.c @@ -283,7 +283,7 @@ static struct socket *rxe_setup_udp_tunnel(struct net *net, __be16 port, tnl_cfg.encap_rcv = rxe_udp_encap_recv; /* Setup UDP tunnel */ - setup_udp_tunnel_sock(net, sock, &tnl_cfg); + setup_udp_tunnel_sock(net, sock->sk, &tnl_cfg); return sock; } diff --git a/drivers/net/amt.c b/drivers/net/amt.c index fc415072864b..c03aa7c207e6 100644 --- a/drivers/net/amt.c +++ b/drivers/net/amt.c @@ -2979,7 +2979,7 @@ static int amt_socket_create(struct amt_dev *amt) tunnel_cfg.encap_rcv = amt_rcv; tunnel_cfg.encap_err_lookup = amt_err_lookup; tunnel_cfg.encap_destroy = NULL; - setup_udp_tunnel_sock(amt->net, sock, &tunnel_cfg); + setup_udp_tunnel_sock(amt->net, sock->sk, &tunnel_cfg); rcu_assign_pointer(amt->sock, sock); return 0; diff --git a/drivers/net/bareudp.c b/drivers/net/bareudp.c index f3025a5c5261..169ab90393cc 100644 --- a/drivers/net/bareudp.c +++ b/drivers/net/bareudp.c @@ -268,7 +268,7 @@ static int bareudp_socket_create(struct bareudp_dev *bareudp, __be16 port) tunnel_cfg.encap_rcv = bareudp_udp_encap_recv; tunnel_cfg.encap_err_lookup = bareudp_err_lookup; tunnel_cfg.encap_destroy = NULL; - setup_udp_tunnel_sock(bareudp->net, sock, &tunnel_cfg); + setup_udp_tunnel_sock(bareudp->net, sock->sk, &tunnel_cfg); rcu_assign_pointer(bareudp->sock, sock); return 0; diff --git a/drivers/net/geneve.c b/drivers/net/geneve.c index 8d55160305ee..c3a7736cd6fc 100644 --- a/drivers/net/geneve.c +++ b/drivers/net/geneve.c @@ -1006,7 +1006,7 @@ static struct geneve_sock *geneve_socket_create(struct net *net, __be16 port, tunnel_cfg.encap_rcv = geneve_udp_encap_recv; tunnel_cfg.encap_err_lookup = geneve_udp_encap_err_lookup; tunnel_cfg.encap_destroy = NULL; - setup_udp_tunnel_sock(net, sock, &tunnel_cfg); + setup_udp_tunnel_sock(net, sock->sk, &tunnel_cfg); list_add(&gs->list, &gn->sock_list); return gs; } diff --git a/drivers/net/gtp.c b/drivers/net/gtp.c index 064ce1029d33..a60ef32b35b8 100644 --- a/drivers/net/gtp.c +++ b/drivers/net/gtp.c @@ -1434,7 +1434,7 @@ static struct sock *gtp_create_sock(int type, struct gtp_dev *gtp, tuncfg.encap_rcv = gtp_encap_recv; tuncfg.encap_destroy = NULL; - setup_udp_tunnel_sock(net, sock, &tuncfg); + setup_udp_tunnel_sock(net, sock->sk, &tuncfg); return sock->sk; } @@ -1689,7 +1689,7 @@ static struct sock *gtp_encap_enable_socket(int fd, int type, tuncfg.encap_rcv = gtp_encap_recv; tuncfg.encap_destroy = gtp_encap_destroy; - setup_udp_tunnel_sock(sock_net(sock->sk), sock, &tuncfg); + setup_udp_tunnel_sock(sock_net(sock->sk), sk, &tuncfg); out_rel_sock: release_sock(sock->sk); diff --git a/drivers/net/ovpn/udp.c b/drivers/net/ovpn/udp.c index 059e896b4a2f..493a5a0744af 100644 --- a/drivers/net/ovpn/udp.c +++ b/drivers/net/ovpn/udp.c @@ -399,7 +399,7 @@ int ovpn_udp_socket_attach(struct ovpn_socket *ovpn_sock, struct socket *sock, if (!old_data) { /* socket is currently unused - we can take it */ rcu_read_unlock(); - setup_udp_tunnel_sock(sock_net(ovpn_sock->sk), sock, &cfg); + setup_udp_tunnel_sock(sock_net(ovpn_sock->sk), sock->sk, &cfg); return 0; } diff --git a/drivers/net/pfcp.c b/drivers/net/pfcp.c index ce58038cfccb..870137695e8a 100644 --- a/drivers/net/pfcp.c +++ b/drivers/net/pfcp.c @@ -172,7 +172,7 @@ static struct socket *pfcp_create_sock(struct pfcp_dev *pfcp) tuncfg.encap_rcv = pfcp_encap_recv; tuncfg.encap_type = 1; - setup_udp_tunnel_sock(net, sock, &tuncfg); + setup_udp_tunnel_sock(net, sock->sk, &tuncfg); return sock; } diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c index 1d1aba1c7cfc..394801c068b3 100644 --- a/drivers/net/vxlan/vxlan_core.c +++ b/drivers/net/vxlan/vxlan_core.c @@ -3620,7 +3620,7 @@ static struct vxlan_sock *vxlan_socket_create(struct net *net, bool ipv6, tunnel_cfg.gro_complete = vxlan_gro_complete; } - setup_udp_tunnel_sock(net, sock, &tunnel_cfg); + setup_udp_tunnel_sock(net, sock->sk, &tunnel_cfg); return vs; } diff --git a/drivers/net/wireguard/socket.c b/drivers/net/wireguard/socket.c index 4a4c177af170..0028ef17dc71 100644 --- a/drivers/net/wireguard/socket.c +++ b/drivers/net/wireguard/socket.c @@ -389,7 +389,7 @@ retry: goto out; } set_sock_opts(new4); - setup_udp_tunnel_sock(net, new4, &cfg); + setup_udp_tunnel_sock(net, new4->sk, &cfg); #if IS_ENABLED(CONFIG_IPV6) if (ipv6_mod_enabled()) { @@ -404,7 +404,7 @@ retry: goto out; } set_sock_opts(new6); - setup_udp_tunnel_sock(net, new6, &cfg); + setup_udp_tunnel_sock(net, new6->sk, &cfg); } #endif diff --git a/include/net/udp_tunnel.h b/include/net/udp_tunnel.h index dbbd56280f50..49324e28ec27 100644 --- a/include/net/udp_tunnel.h +++ b/include/net/udp_tunnel.h @@ -94,7 +94,7 @@ struct udp_tunnel_sock_cfg { }; /* Setup the given (UDP) sock to receive UDP encapsulated packets */ -void setup_udp_tunnel_sock(struct net *net, struct socket *sock, +void setup_udp_tunnel_sock(struct net *net, struct sock *sk, struct udp_tunnel_sock_cfg *sock_cfg); /* -- List of parsable UDP tunnel types -- diff --git a/net/ipv4/fou_core.c b/net/ipv4/fou_core.c index 422f86291b42..6bed0e1dbe0e 100644 --- a/net/ipv4/fou_core.c +++ b/net/ipv4/fou_core.c @@ -615,7 +615,7 @@ static int fou_create(struct net *net, struct fou_cfg *cfg, goto error; } - setup_udp_tunnel_sock(net, sock, &tunnel_cfg); + setup_udp_tunnel_sock(net, sk, &tunnel_cfg); sk->sk_allocation = GFP_ATOMIC; diff --git a/net/ipv4/udp_tunnel_core.c b/net/ipv4/udp_tunnel_core.c index 1159a6a6fbb2..3090b4745d47 100644 --- a/net/ipv4/udp_tunnel_core.c +++ b/net/ipv4/udp_tunnel_core.c @@ -68,11 +68,9 @@ static bool sk_saddr_any(struct sock *sk) #endif } -void setup_udp_tunnel_sock(struct net *net, struct socket *sock, +void setup_udp_tunnel_sock(struct net *net, struct sock *sk, struct udp_tunnel_sock_cfg *cfg) { - struct sock *sk = sock->sk; - /* Disable multicast loopback */ inet_clear_bit(MC_LOOP, sk); diff --git a/net/l2tp/l2tp_core.c b/net/l2tp/l2tp_core.c index 157fc23ce4e1..cbc5a3e57b33 100644 --- a/net/l2tp/l2tp_core.c +++ b/net/l2tp/l2tp_core.c @@ -1683,7 +1683,7 @@ int l2tp_tunnel_register(struct l2tp_tunnel *tunnel, struct net *net, .encap_destroy = l2tp_udp_encap_destroy, }; - setup_udp_tunnel_sock(net, sock, &udp_cfg); + setup_udp_tunnel_sock(net, sock->sk, &udp_cfg); } sk->sk_allocation = GFP_ATOMIC; diff --git a/net/rxrpc/local_object.c b/net/rxrpc/local_object.c index 111f574fe667..169f9dfdaa77 100644 --- a/net/rxrpc/local_object.c +++ b/net/rxrpc/local_object.c @@ -194,7 +194,7 @@ static int rxrpc_open_socket(struct rxrpc_local *local, struct net *net) tuncfg.encap_rcv = rxrpc_encap_rcv; tuncfg.encap_err_rcv = rxrpc_encap_err_rcv; tuncfg.sk_user_data = local; - setup_udp_tunnel_sock(net, local->socket, &tuncfg); + setup_udp_tunnel_sock(net, local->socket->sk, &tuncfg); /* set the socket up */ usk = local->socket->sk; diff --git a/net/sctp/protocol.c b/net/sctp/protocol.c index ffe594ad4414..5c6fa8e8d34d 100644 --- a/net/sctp/protocol.c +++ b/net/sctp/protocol.c @@ -864,7 +864,7 @@ int sctp_udp_sock_start(struct net *net) tuncfg.encap_type = 1; tuncfg.encap_rcv = sctp_udp_rcv; tuncfg.encap_err_lookup = sctp_udp_v4_err; - setup_udp_tunnel_sock(net, sock, &tuncfg); + setup_udp_tunnel_sock(net, sock->sk, &tuncfg); net->sctp.udp4_sock = sock->sk; #if IS_ENABLED(CONFIG_IPV6) @@ -886,7 +886,7 @@ int sctp_udp_sock_start(struct net *net) tuncfg.encap_type = 1; tuncfg.encap_rcv = sctp_udp_rcv; tuncfg.encap_err_lookup = sctp_udp_v6_err; - setup_udp_tunnel_sock(net, sock, &tuncfg); + setup_udp_tunnel_sock(net, sock->sk, &tuncfg); net->sctp.udp6_sock = sock->sk; #endif diff --git a/net/tipc/udp_media.c b/net/tipc/udp_media.c index d7c050ff5804..0db172f1a41a 100644 --- a/net/tipc/udp_media.c +++ b/net/tipc/udp_media.c @@ -771,7 +771,7 @@ static int tipc_udp_enable(struct net *net, struct tipc_bearer *b, tuncfg.encap_type = 1; tuncfg.encap_rcv = tipc_udp_recv; tuncfg.encap_destroy = NULL; - setup_udp_tunnel_sock(net, ub->ubsock, &tuncfg); + setup_udp_tunnel_sock(net, ub->ubsock->sk, &tuncfg); err = dst_cache_init(&ub->rcast.dst_cache, GFP_ATOMIC); if (err) -- cgit v1.2.3 From 9333d5ff28bd9aa5a8d961a2414afc704ea34095 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Sat, 2 May 2026 03:12:56 +0000 Subject: udp_tunnel: Pass struct sock to udp_tunnel6_dst_lookup(). None of the udp_tunnel users need struct socket in their fast paths; it is only used for tunnel setup / teardown. Even udp_tunnel6_dst_lookup() does not need struct socket. Let's change udp_tunnel6_dst_lookup() to take struct sock instead of struct socket. Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260502031401.3557229-4-kuniyu@google.com Signed-off-by: Jakub Kicinski --- drivers/net/bareudp.c | 4 ++-- drivers/net/geneve.c | 4 ++-- drivers/net/vxlan/vxlan_core.c | 4 ++-- include/net/udp_tunnel.h | 2 +- net/ipv6/ip6_udp_tunnel.c | 6 +++--- 5 files changed, 10 insertions(+), 10 deletions(-) (limited to 'include') diff --git a/drivers/net/bareudp.c b/drivers/net/bareudp.c index 169ab90393cc..073ac8a15354 100644 --- a/drivers/net/bareudp.c +++ b/drivers/net/bareudp.c @@ -396,7 +396,7 @@ static int bareudp6_xmit_skb(struct sk_buff *skb, struct net_device *dev, sport = udp_flow_src_port(bareudp->net, skb, bareudp->sport_min, USHRT_MAX, true); - dst = udp_tunnel6_dst_lookup(skb, dev, bareudp->net, sock, 0, &saddr, + dst = udp_tunnel6_dst_lookup(skb, dev, bareudp->net, sock->sk, 0, &saddr, key, sport, bareudp->port, key->tos, use_cache ? (struct dst_cache *) &info->dst_cache : NULL); @@ -532,7 +532,7 @@ static int bareudp_fill_metadata_dst(struct net_device *dev, if (!sock) return -ESHUTDOWN; - dst = udp_tunnel6_dst_lookup(skb, dev, bareudp->net, sock, + dst = udp_tunnel6_dst_lookup(skb, dev, bareudp->net, sock->sk, 0, &saddr, &info->key, sport, bareudp->port, info->key.tos, use_cache ? &info->dst_cache : NULL); diff --git a/drivers/net/geneve.c b/drivers/net/geneve.c index c3a7736cd6fc..4b7081b97015 100644 --- a/drivers/net/geneve.c +++ b/drivers/net/geneve.c @@ -1424,7 +1424,7 @@ static int geneve6_xmit_skb(struct sk_buff *skb, struct net_device *dev, geneve->cfg.port_min, geneve->cfg.port_max, true); - dst = udp_tunnel6_dst_lookup(skb, dev, geneve->net, gs6->sock, 0, + dst = udp_tunnel6_dst_lookup(skb, dev, geneve->net, gs6->sock->sk, 0, &saddr, key, sport, geneve->cfg.info.key.tp_dst, prio, use_cache ? @@ -1592,7 +1592,7 @@ static int geneve_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb) geneve->cfg.port_min, geneve->cfg.port_max, true); - dst = udp_tunnel6_dst_lookup(skb, dev, geneve->net, gs6->sock, 0, + dst = udp_tunnel6_dst_lookup(skb, dev, geneve->net, gs6->sock->sk, 0, &saddr, &info->key, sport, geneve->cfg.info.key.tp_dst, prio, use_cache ? &info->dst_cache : NULL); diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c index 394801c068b3..a19f951e05f1 100644 --- a/drivers/net/vxlan/vxlan_core.c +++ b/drivers/net/vxlan/vxlan_core.c @@ -2559,7 +2559,7 @@ void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev, if (!ifindex) ifindex = sock6->sock->sk->sk_bound_dev_if; - ndst = udp_tunnel6_dst_lookup(skb, dev, vxlan->net, sock6->sock, + ndst = udp_tunnel6_dst_lookup(skb, dev, vxlan->net, sock6->sock->sk, ifindex, &saddr, pkey, src_port, dst_port, tos, use_cache ? dst_cache : NULL); @@ -3254,7 +3254,7 @@ static int vxlan_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb) if (!sock6) return -EIO; - ndst = udp_tunnel6_dst_lookup(skb, dev, vxlan->net, sock6->sock, + ndst = udp_tunnel6_dst_lookup(skb, dev, vxlan->net, sock6->sock->sk, 0, &info->key.u.ipv6.src, &info->key, sport, dport, info->key.tos, diff --git a/include/net/udp_tunnel.h b/include/net/udp_tunnel.h index 49324e28ec27..14a9c5155608 100644 --- a/include/net/udp_tunnel.h +++ b/include/net/udp_tunnel.h @@ -188,7 +188,7 @@ struct rtable *udp_tunnel_dst_lookup(struct sk_buff *skb, struct dst_entry *udp_tunnel6_dst_lookup(struct sk_buff *skb, struct net_device *dev, struct net *net, - struct socket *sock, int oif, + struct sock *sk, int oif, struct in6_addr *saddr, const struct ip_tunnel_key *key, __be16 sport, __be16 dport, u8 dsfield, diff --git a/net/ipv6/ip6_udp_tunnel.c b/net/ipv6/ip6_udp_tunnel.c index 405ef1cb8864..9adb5775487f 100644 --- a/net/ipv6/ip6_udp_tunnel.c +++ b/net/ipv6/ip6_udp_tunnel.c @@ -118,7 +118,7 @@ EXPORT_SYMBOL_GPL(udp_tunnel6_xmit_skb); * @skb: Packet for which lookup is done * @dev: Tunnel device * @net: Network namespace of tunnel device - * @sock: Socket which provides route info + * @sk: Socket which provides route info * @oif: Index of the output interface * @saddr: Memory to store the src ip address * @key: Tunnel information @@ -135,7 +135,7 @@ EXPORT_SYMBOL_GPL(udp_tunnel6_xmit_skb); struct dst_entry *udp_tunnel6_dst_lookup(struct sk_buff *skb, struct net_device *dev, struct net *net, - struct socket *sock, + struct sock *sk, int oif, struct in6_addr *saddr, const struct ip_tunnel_key *key, @@ -162,7 +162,7 @@ struct dst_entry *udp_tunnel6_dst_lookup(struct sk_buff *skb, fl6.fl6_dport = dport; fl6.flowlabel = ip6_make_flowinfo(dsfield, key->label); - dst = ip6_dst_lookup_flow(net, sock->sk, &fl6, NULL); + dst = ip6_dst_lookup_flow(net, sk, &fl6, NULL); if (IS_ERR(dst)) { netdev_dbg(dev, "no route to %pI6\n", &fl6.daddr); return ERR_PTR(-ENETUNREACH); -- cgit v1.2.3 From 8d0012ce1b413c010ead5e3ca71dec6bb49280ff Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Sat, 2 May 2026 03:12:57 +0000 Subject: udp_tunnel: Pass struct sock to udp_tunnel_{push,drop}_rx_port(). None of the udp_tunnel users need struct socket in their fast paths; it is only used for tunnel setup / teardown. Even udp_tunnel_{push,drop}_rx_port() do not need struct socket. Let's change udp_tunnel_{push,drop}_rx_port() to take struct sock instead of struct socket. Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260502031401.3557229-5-kuniyu@google.com Signed-off-by: Jakub Kicinski --- drivers/net/geneve.c | 4 ++-- drivers/net/vxlan/vxlan_core.c | 4 ++-- include/net/udp_tunnel.h | 4 ++-- net/ipv4/udp_tunnel_core.c | 6 ++---- 4 files changed, 8 insertions(+), 10 deletions(-) (limited to 'include') diff --git a/drivers/net/geneve.c b/drivers/net/geneve.c index 4b7081b97015..16df8d5c42c9 100644 --- a/drivers/net/geneve.c +++ b/drivers/net/geneve.c @@ -1654,10 +1654,10 @@ static void geneve_offload_rx_ports(struct net_device *dev, bool push) list_for_each_entry(gs, &gn->sock_list, list) { if (push) { - udp_tunnel_push_rx_port(dev, gs->sock, + udp_tunnel_push_rx_port(dev, gs->sock->sk, UDP_TUNNEL_TYPE_GENEVE); } else { - udp_tunnel_drop_rx_port(dev, gs->sock, + udp_tunnel_drop_rx_port(dev, gs->sock->sk, UDP_TUNNEL_TYPE_GENEVE); } } diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c index a19f951e05f1..184df57bc705 100644 --- a/drivers/net/vxlan/vxlan_core.c +++ b/drivers/net/vxlan/vxlan_core.c @@ -3332,9 +3332,9 @@ static void vxlan_offload_rx_ports(struct net_device *dev, bool push) type = UDP_TUNNEL_TYPE_VXLAN; if (push) - udp_tunnel_push_rx_port(dev, vs->sock, type); + udp_tunnel_push_rx_port(dev, vs->sock->sk, type); else - udp_tunnel_drop_rx_port(dev, vs->sock, type); + udp_tunnel_drop_rx_port(dev, vs->sock->sk, type); } } } diff --git a/include/net/udp_tunnel.h b/include/net/udp_tunnel.h index 14a9c5155608..29ead6a38ef6 100644 --- a/include/net/udp_tunnel.h +++ b/include/net/udp_tunnel.h @@ -127,9 +127,9 @@ struct udp_tunnel_info { }; /* Notify network devices of offloadable types */ -void udp_tunnel_push_rx_port(struct net_device *dev, struct socket *sock, +void udp_tunnel_push_rx_port(struct net_device *dev, struct sock *sk, unsigned short type); -void udp_tunnel_drop_rx_port(struct net_device *dev, struct socket *sock, +void udp_tunnel_drop_rx_port(struct net_device *dev, struct sock *sk, unsigned short type); void udp_tunnel_notify_add_rx_port(struct socket *sock, unsigned short type); void udp_tunnel_notify_del_rx_port(struct socket *sock, unsigned short type); diff --git a/net/ipv4/udp_tunnel_core.c b/net/ipv4/udp_tunnel_core.c index 3090b4745d47..857b51d62ce1 100644 --- a/net/ipv4/udp_tunnel_core.c +++ b/net/ipv4/udp_tunnel_core.c @@ -97,10 +97,9 @@ void setup_udp_tunnel_sock(struct net *net, struct sock *sk, } EXPORT_SYMBOL_GPL(setup_udp_tunnel_sock); -void udp_tunnel_push_rx_port(struct net_device *dev, struct socket *sock, +void udp_tunnel_push_rx_port(struct net_device *dev, struct sock *sk, unsigned short type) { - struct sock *sk = sock->sk; struct udp_tunnel_info ti; ti.type = type; @@ -111,10 +110,9 @@ void udp_tunnel_push_rx_port(struct net_device *dev, struct socket *sock, } EXPORT_SYMBOL_GPL(udp_tunnel_push_rx_port); -void udp_tunnel_drop_rx_port(struct net_device *dev, struct socket *sock, +void udp_tunnel_drop_rx_port(struct net_device *dev, struct sock *sk, unsigned short type) { - struct sock *sk = sock->sk; struct udp_tunnel_info ti; ti.type = type; -- cgit v1.2.3 From af3f903fbb70eced6394770cd704951767394015 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Sat, 2 May 2026 03:12:58 +0000 Subject: udp_tunnel: Pass struct sock to udp_tunnel_notify_{add,del}_rx_port(). None of the udp_tunnel users need struct socket in their fast paths; it is only used for tunnel setup / teardown. Even udp_tunnel_notify_{add,del}_rx_port() do not need struct socket. Let's change udp_tunnel_notify_{add,del}_rx_port() to take struct sock instead of struct socket. Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260502031401.3557229-6-kuniyu@google.com Signed-off-by: Jakub Kicinski --- drivers/net/geneve.c | 4 ++-- drivers/net/vxlan/vxlan_core.c | 4 ++-- include/net/udp_tunnel.h | 4 ++-- net/ipv4/udp_tunnel_core.c | 6 ++---- 4 files changed, 8 insertions(+), 10 deletions(-) (limited to 'include') diff --git a/drivers/net/geneve.c b/drivers/net/geneve.c index 16df8d5c42c9..9cf62d3ee471 100644 --- a/drivers/net/geneve.c +++ b/drivers/net/geneve.c @@ -995,7 +995,7 @@ static struct geneve_sock *geneve_socket_create(struct net *net, __be16 port, INIT_HLIST_HEAD(&gs->vni_list[h]); /* Initialize the geneve udp offloads structure */ - udp_tunnel_notify_add_rx_port(gs->sock, UDP_TUNNEL_TYPE_GENEVE); + udp_tunnel_notify_add_rx_port(gs->sock->sk, UDP_TUNNEL_TYPE_GENEVE); /* Mark socket as an encapsulation socket */ memset(&tunnel_cfg, 0, sizeof(tunnel_cfg)); @@ -1017,7 +1017,7 @@ static void __geneve_sock_release(struct geneve_sock *gs) return; list_del(&gs->list); - udp_tunnel_notify_del_rx_port(gs->sock, UDP_TUNNEL_TYPE_GENEVE); + udp_tunnel_notify_del_rx_port(gs->sock->sk, UDP_TUNNEL_TYPE_GENEVE); udp_tunnel_sock_release(gs->sock->sk); kfree_rcu(gs, rcu); } diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c index 184df57bc705..0ea88232b985 100644 --- a/drivers/net/vxlan/vxlan_core.c +++ b/drivers/net/vxlan/vxlan_core.c @@ -1493,7 +1493,7 @@ static bool __vxlan_sock_release_prep(struct vxlan_sock *vs) return false; hlist_del_rcu(&vs->hlist); - udp_tunnel_notify_del_rx_port(vs->sock, + udp_tunnel_notify_del_rx_port(vs->sock->sk, (vs->flags & VXLAN_F_GPE) ? UDP_TUNNEL_TYPE_VXLAN_GPE : UDP_TUNNEL_TYPE_VXLAN); @@ -3600,7 +3600,7 @@ static struct vxlan_sock *vxlan_socket_create(struct net *net, bool ipv6, vs->flags = (flags & VXLAN_F_RCV_FLAGS); hlist_add_head_rcu(&vs->hlist, vs_head(net, port)); - udp_tunnel_notify_add_rx_port(sock, + udp_tunnel_notify_add_rx_port(sock->sk, (vs->flags & VXLAN_F_GPE) ? UDP_TUNNEL_TYPE_VXLAN_GPE : UDP_TUNNEL_TYPE_VXLAN); diff --git a/include/net/udp_tunnel.h b/include/net/udp_tunnel.h index 29ead6a38ef6..498b7b262fa9 100644 --- a/include/net/udp_tunnel.h +++ b/include/net/udp_tunnel.h @@ -131,8 +131,8 @@ void udp_tunnel_push_rx_port(struct net_device *dev, struct sock *sk, unsigned short type); void udp_tunnel_drop_rx_port(struct net_device *dev, struct sock *sk, unsigned short type); -void udp_tunnel_notify_add_rx_port(struct socket *sock, unsigned short type); -void udp_tunnel_notify_del_rx_port(struct socket *sock, unsigned short type); +void udp_tunnel_notify_add_rx_port(struct sock *sk, unsigned short type); +void udp_tunnel_notify_del_rx_port(struct sock *sk, unsigned short type); /* Transmit the skb using UDP encapsulation. */ void udp_tunnel_xmit_skb(struct rtable *rt, struct sock *sk, struct sk_buff *skb, diff --git a/net/ipv4/udp_tunnel_core.c b/net/ipv4/udp_tunnel_core.c index 857b51d62ce1..44788b95c823 100644 --- a/net/ipv4/udp_tunnel_core.c +++ b/net/ipv4/udp_tunnel_core.c @@ -124,9 +124,8 @@ void udp_tunnel_drop_rx_port(struct net_device *dev, struct sock *sk, EXPORT_SYMBOL_GPL(udp_tunnel_drop_rx_port); /* Notify netdevs that UDP port started listening */ -void udp_tunnel_notify_add_rx_port(struct socket *sock, unsigned short type) +void udp_tunnel_notify_add_rx_port(struct sock *sk, unsigned short type) { - struct sock *sk = sock->sk; struct net *net = sock_net(sk); struct udp_tunnel_info ti; struct net_device *dev; @@ -146,9 +145,8 @@ void udp_tunnel_notify_add_rx_port(struct socket *sock, unsigned short type) EXPORT_SYMBOL_GPL(udp_tunnel_notify_add_rx_port); /* Notify netdevs that UDP port is no more listening */ -void udp_tunnel_notify_del_rx_port(struct socket *sock, unsigned short type) +void udp_tunnel_notify_del_rx_port(struct sock *sk, unsigned short type) { - struct sock *sk = sock->sk; struct net *net = sock_net(sk); struct udp_tunnel_info ti; struct net_device *dev; -- cgit v1.2.3 From 027bffa1ad19f5da59ee7923464dd248b0a9930f Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Sat, 2 May 2026 03:13:00 +0000 Subject: vxlan: Store struct sock in struct vxlan_sock. Commit 3cf7203ca620 ("net/tunnel: wait until all sk_user_data reader finish before releasing the sock") added synchronize_rcu() in udp_tunnel_sock_release(). This was intended to protect the fast path of a dying vxlan device from dereferencing vxlan_sock->sock->sk after sock_orphan() has set sock->sk to NULL. However, vxlan does not need to access struct socket itself in the fast path; it only reads struct sock, and struct socket is only used for tunnel setup and teardown. Let's store struct sock directly in struct vxlan_sock. In the next patch, we will free vxlan_sock with kfree_rcu(), then vxlan no longer needs synchronize_rcu() in udp_tunnel_sock_release(). Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260502031401.3557229-8-kuniyu@google.com Signed-off-by: Jakub Kicinski --- drivers/net/vxlan/vxlan_core.c | 48 ++++++++++++++++++------------------- drivers/net/vxlan/vxlan_multicast.c | 8 +++---- include/net/vxlan.h | 4 ++-- 3 files changed, 30 insertions(+), 30 deletions(-) (limited to 'include') diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c index abf3ae04d75b..ce99da44ea7d 100644 --- a/drivers/net/vxlan/vxlan_core.c +++ b/drivers/net/vxlan/vxlan_core.c @@ -88,10 +88,10 @@ static struct vxlan_sock *vxlan_find_sock(struct net *net, sa_family_t family, flags &= VXLAN_F_RCV_FLAGS; hlist_for_each_entry_rcu(vs, vs_head(net, port), hlist) { - if (inet_sk(vs->sock->sk)->inet_sport == port && + if (inet_sk(vs->sk)->inet_sport == port && vxlan_get_sk_family(vs) == family && vs->flags == flags && - vs->sock->sk->sk_bound_dev_if == ifindex) + vs->sk->sk_bound_dev_if == ifindex) return vs; } return NULL; @@ -1497,7 +1497,7 @@ static bool __vxlan_sock_release_prep(struct vxlan_sock *vs) return false; hlist_del_rcu(&vs->hlist); - udp_tunnel_notify_del_rx_port(vs->sock->sk, + udp_tunnel_notify_del_rx_port(vs->sk, (vs->flags & VXLAN_F_GPE) ? UDP_TUNNEL_TYPE_VXLAN_GPE : UDP_TUNNEL_TYPE_VXLAN); @@ -1523,13 +1523,13 @@ static void vxlan_sock_release(struct vxlan_dev *vxlan) vxlan_vs_del_dev(vxlan); if (__vxlan_sock_release_prep(sock4)) { - udp_tunnel_sock_release(sock4->sock->sk); + udp_tunnel_sock_release(sock4->sk); kfree(sock4); } #if IS_ENABLED(CONFIG_IPV6) if (__vxlan_sock_release_prep(sock6)) { - udp_tunnel_sock_release(sock6->sock->sk); + udp_tunnel_sock_release(sock6->sk); kfree(sock6); } #endif @@ -2477,7 +2477,7 @@ void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev, } if (!ifindex) - ifindex = sock4->sock->sk->sk_bound_dev_if; + ifindex = sock4->sk->sk_bound_dev_if; rt = udp_tunnel_dst_lookup(skb, dev, vxlan->net, ifindex, &saddr, pkey, src_port, dst_port, @@ -2544,7 +2544,7 @@ void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev, goto tx_error; } - udp_tunnel_xmit_skb(rt, sock4->sock->sk, skb, saddr, + udp_tunnel_xmit_skb(rt, sock4->sk, skb, saddr, pkey->u.ipv4.dst, tos, ttl, df, src_port, dst_port, xnet, !udp_sum, ipcb_flags); @@ -2561,9 +2561,9 @@ void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev, } if (!ifindex) - ifindex = sock6->sock->sk->sk_bound_dev_if; + ifindex = sock6->sk->sk_bound_dev_if; - ndst = udp_tunnel6_dst_lookup(skb, dev, vxlan->net, sock6->sock->sk, + ndst = udp_tunnel6_dst_lookup(skb, dev, vxlan->net, sock6->sk, ifindex, &saddr, pkey, src_port, dst_port, tos, use_cache ? dst_cache : NULL); @@ -2619,7 +2619,7 @@ void vxlan_xmit_one(struct sk_buff *skb, struct net_device *dev, goto tx_error; } - udp_tunnel6_xmit_skb(ndst, sock6->sock->sk, skb, dev, + udp_tunnel6_xmit_skb(ndst, sock6->sk, skb, dev, &saddr, &pkey->u.ipv6.dst, tos, ttl, pkey->label, src_port, dst_port, !udp_sum, ip6cb_flags); @@ -3258,7 +3258,7 @@ static int vxlan_fill_metadata_dst(struct net_device *dev, struct sk_buff *skb) if (!sock6) return -EIO; - ndst = udp_tunnel6_dst_lookup(skb, dev, vxlan->net, sock6->sock->sk, + ndst = udp_tunnel6_dst_lookup(skb, dev, vxlan->net, sock6->sk, 0, &info->key.u.ipv6.src, &info->key, sport, dport, info->key.tos, @@ -3336,9 +3336,9 @@ static void vxlan_offload_rx_ports(struct net_device *dev, bool push) type = UDP_TUNNEL_TYPE_VXLAN; if (push) - udp_tunnel_push_rx_port(dev, vs->sock->sk, type); + udp_tunnel_push_rx_port(dev, vs->sk, type); else - udp_tunnel_drop_rx_port(dev, vs->sock->sk, type); + udp_tunnel_drop_rx_port(dev, vs->sk, type); } } } @@ -3544,8 +3544,8 @@ static const struct ethtool_ops vxlan_ethtool_ops = { .get_link_ksettings = vxlan_get_link_ksettings, }; -static struct socket *vxlan_create_sock(struct net *net, bool ipv6, - __be16 port, u32 flags, int ifindex) +static struct sock *vxlan_create_sock(struct net *net, bool ipv6, + __be16 port, u32 flags, int ifindex) { struct socket *sock; struct udp_port_cfg udp_conf; @@ -3571,7 +3571,7 @@ static struct socket *vxlan_create_sock(struct net *net, bool ipv6, return ERR_PTR(err); udp_allow_gso(sock->sk); - return sock; + return sock->sk; } /* Create new listen socket if needed */ @@ -3579,10 +3579,10 @@ static struct vxlan_sock *vxlan_socket_create(struct net *net, bool ipv6, __be16 port, u32 flags, int ifindex) { + struct udp_tunnel_sock_cfg tunnel_cfg; struct vxlan_sock *vs; - struct socket *sock; + struct sock *sk; unsigned int h; - struct udp_tunnel_sock_cfg tunnel_cfg; ASSERT_RTNL(); @@ -3593,18 +3593,18 @@ static struct vxlan_sock *vxlan_socket_create(struct net *net, bool ipv6, for (h = 0; h < VNI_HASH_SIZE; ++h) INIT_HLIST_HEAD(&vs->vni_list[h]); - sock = vxlan_create_sock(net, ipv6, port, flags, ifindex); - if (IS_ERR(sock)) { + sk = vxlan_create_sock(net, ipv6, port, flags, ifindex); + if (IS_ERR(sk)) { kfree(vs); - return ERR_CAST(sock); + return ERR_CAST(sk); } - vs->sock = sock; + vs->sk = sk; refcount_set(&vs->refcnt, 1); vs->flags = (flags & VXLAN_F_RCV_FLAGS); hlist_add_head_rcu(&vs->hlist, vs_head(net, port)); - udp_tunnel_notify_add_rx_port(sock->sk, + udp_tunnel_notify_add_rx_port(sk, (vs->flags & VXLAN_F_GPE) ? UDP_TUNNEL_TYPE_VXLAN_GPE : UDP_TUNNEL_TYPE_VXLAN); @@ -3624,7 +3624,7 @@ static struct vxlan_sock *vxlan_socket_create(struct net *net, bool ipv6, tunnel_cfg.gro_complete = vxlan_gro_complete; } - setup_udp_tunnel_sock(net, sock->sk, &tunnel_cfg); + setup_udp_tunnel_sock(net, sk, &tunnel_cfg); return vs; } diff --git a/drivers/net/vxlan/vxlan_multicast.c b/drivers/net/vxlan/vxlan_multicast.c index b0e80bca855c..3b75b48dc726 100644 --- a/drivers/net/vxlan/vxlan_multicast.c +++ b/drivers/net/vxlan/vxlan_multicast.c @@ -29,7 +29,7 @@ int vxlan_igmp_join(struct vxlan_dev *vxlan, union vxlan_addr *rip, .imr_ifindex = ifindex, }; - sk = sock4->sock->sk; + sk = sock4->sk; lock_sock(sk); ret = ip_mc_join_group(sk, &mreq); release_sock(sk); @@ -37,7 +37,7 @@ int vxlan_igmp_join(struct vxlan_dev *vxlan, union vxlan_addr *rip, } else { struct vxlan_sock *sock6 = rtnl_dereference(vxlan->vn6_sock); - sk = sock6->sock->sk; + sk = sock6->sk; lock_sock(sk); ret = ipv6_sock_mc_join(sk, ifindex, &ip->sin6.sin6_addr); release_sock(sk); @@ -62,7 +62,7 @@ int vxlan_igmp_leave(struct vxlan_dev *vxlan, union vxlan_addr *rip, .imr_ifindex = ifindex, }; - sk = sock4->sock->sk; + sk = sock4->sk; lock_sock(sk); ret = ip_mc_leave_group(sk, &mreq); release_sock(sk); @@ -70,7 +70,7 @@ int vxlan_igmp_leave(struct vxlan_dev *vxlan, union vxlan_addr *rip, } else { struct vxlan_sock *sock6 = rtnl_dereference(vxlan->vn6_sock); - sk = sock6->sock->sk; + sk = sock6->sk; lock_sock(sk); ret = ipv6_sock_mc_drop(sk, ifindex, &ip->sin6.sin6_addr); release_sock(sk); diff --git a/include/net/vxlan.h b/include/net/vxlan.h index 0ee50785f4f1..8b52294b2902 100644 --- a/include/net/vxlan.h +++ b/include/net/vxlan.h @@ -185,7 +185,7 @@ struct vxlan_metadata { /* per UDP socket information */ struct vxlan_sock { struct hlist_node hlist; - struct socket *sock; + struct sock *sk; struct hlist_head vni_list[VNI_HASH_SIZE]; refcount_t refcnt; u32 flags; @@ -448,7 +448,7 @@ static inline __be32 vxlan_compute_rco(unsigned int start, unsigned int offset) static inline unsigned short vxlan_get_sk_family(struct vxlan_sock *vs) { - return vs->sock->sk->sk_family; + return vs->sk->sk_family; } #if IS_ENABLED(CONFIG_IPV6) -- cgit v1.2.3 From 4f71dd1b421d1300237adf546aa14521cdf46355 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Sat, 2 May 2026 03:13:01 +0000 Subject: vxlan: Free vxlan_sock with kfree_rcu(). We will remove synchronize_rcu() in udp_tunnel_sock_release(). We must ensure that vxlan_sock is freed after inflight RX fast path. Let's free vxlan_sock with kfree_rcu(). Note that vxlan_sock.vni_list[] is 8K and struct rcu_head must be placed before it. Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260502031401.3557229-9-kuniyu@google.com Signed-off-by: Jakub Kicinski --- drivers/net/vxlan/vxlan_core.c | 4 ++-- include/net/vxlan.h | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/drivers/net/vxlan/vxlan_core.c b/drivers/net/vxlan/vxlan_core.c index ce99da44ea7d..00facbfabced 100644 --- a/drivers/net/vxlan/vxlan_core.c +++ b/drivers/net/vxlan/vxlan_core.c @@ -1524,13 +1524,13 @@ static void vxlan_sock_release(struct vxlan_dev *vxlan) if (__vxlan_sock_release_prep(sock4)) { udp_tunnel_sock_release(sock4->sk); - kfree(sock4); + kfree_rcu(sock4, rcu); } #if IS_ENABLED(CONFIG_IPV6) if (__vxlan_sock_release_prep(sock6)) { udp_tunnel_sock_release(sock6->sk); - kfree(sock6); + kfree_rcu(sock6, rcu); } #endif } diff --git a/include/net/vxlan.h b/include/net/vxlan.h index 8b52294b2902..dfba89695efc 100644 --- a/include/net/vxlan.h +++ b/include/net/vxlan.h @@ -186,6 +186,7 @@ struct vxlan_metadata { struct vxlan_sock { struct hlist_node hlist; struct sock *sk; + struct rcu_head rcu; struct hlist_head vni_list[VNI_HASH_SIZE]; refcount_t refcnt; u32 flags; -- cgit v1.2.3 From 65c6e06d5abad33ff2ad43daad472efb90deafc2 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Sat, 2 May 2026 03:13:05 +0000 Subject: amt: Store struct sock in struct amt_dev. amt does not need to access struct socket itself in the fast path; it only reads struct sock, and struct socket is only used for tunnel setup and teardown. Let's store struct sock directly in struct amt. amt_dev_stop() is called as dev->netdev_ops->ndo_stop(). synchronize_net() in unregister_netdevice_many_notify() ensures that inflight amt RX fast paths finish before amt_dev is freed. amt no longer needs synchronize_rcu() in udp_tunnel_sock_release(). Note that amt_dev_stop() looks buggy; cancel_delayed_work_sync() should be called after udp_tunnel_sock_release(). Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260502031401.3557229-13-kuniyu@google.com Signed-off-by: Jakub Kicinski --- drivers/net/amt.c | 80 +++++++++++++++++++++++++++---------------------------- include/net/amt.h | 2 +- 2 files changed, 41 insertions(+), 41 deletions(-) (limited to 'include') diff --git a/drivers/net/amt.c b/drivers/net/amt.c index c03aa7c207e6..724a8163a514 100644 --- a/drivers/net/amt.c +++ b/drivers/net/amt.c @@ -614,24 +614,24 @@ static void amt_send_discovery(struct amt_dev *amt) { struct amt_header_discovery *amtd; int hlen, tlen, offset; - struct socket *sock; struct udphdr *udph; struct sk_buff *skb; struct iphdr *iph; struct rtable *rt; struct flowi4 fl4; + struct sock *sk; u32 len; int err; rcu_read_lock(); - sock = rcu_dereference(amt->sock); - if (!sock) + sk = rcu_dereference(amt->sk); + if (!sk) goto out; if (!netif_running(amt->stream_dev) || !netif_running(amt->dev)) goto out; - rt = ip_route_output_ports(amt->net, &fl4, sock->sk, + rt = ip_route_output_ports(amt->net, &fl4, sk, amt->discovery_ip, amt->local_ip, amt->gw_port, amt->relay_port, IPPROTO_UDP, 0, @@ -690,7 +690,7 @@ static void amt_send_discovery(struct amt_dev *amt) skb->ip_summed = CHECKSUM_NONE; ip_select_ident(amt->net, skb, NULL); ip_send_check(iph); - err = ip_local_out(amt->net, sock->sk, skb); + err = ip_local_out(amt->net, sk, skb); if (unlikely(net_xmit_eval(err))) amt->dev->stats.tx_errors++; @@ -703,24 +703,24 @@ static void amt_send_request(struct amt_dev *amt, bool v6) { struct amt_header_request *amtrh; int hlen, tlen, offset; - struct socket *sock; struct udphdr *udph; struct sk_buff *skb; struct iphdr *iph; struct rtable *rt; struct flowi4 fl4; + struct sock *sk; u32 len; int err; rcu_read_lock(); - sock = rcu_dereference(amt->sock); - if (!sock) + sk = rcu_dereference(amt->sk); + if (!sk) goto out; if (!netif_running(amt->stream_dev) || !netif_running(amt->dev)) goto out; - rt = ip_route_output_ports(amt->net, &fl4, sock->sk, + rt = ip_route_output_ports(amt->net, &fl4, sk, amt->remote_ip, amt->local_ip, amt->gw_port, amt->relay_port, IPPROTO_UDP, 0, @@ -781,7 +781,7 @@ static void amt_send_request(struct amt_dev *amt, bool v6) skb->ip_summed = CHECKSUM_NONE; ip_select_ident(amt->net, skb, NULL); ip_send_check(iph); - err = ip_local_out(amt->net, sock->sk, skb); + err = ip_local_out(amt->net, sk, skb); if (unlikely(net_xmit_eval(err))) amt->dev->stats.tx_errors++; @@ -1000,14 +1000,14 @@ static bool amt_send_membership_update(struct amt_dev *amt, bool v6) { struct amt_header_membership_update *amtmu; - struct socket *sock; struct iphdr *iph; struct flowi4 fl4; struct rtable *rt; + struct sock *sk; int err; - sock = rcu_dereference_bh(amt->sock); - if (!sock) + sk = rcu_dereference_bh(amt->sk); + if (!sk) return true; err = skb_cow_head(skb, LL_RESERVED_SPACE(amt->dev) + sizeof(*amtmu) + @@ -1039,7 +1039,7 @@ static bool amt_send_membership_update(struct amt_dev *amt, skb_set_inner_protocol(skb, htons(ETH_P_IP)); else skb_set_inner_protocol(skb, htons(ETH_P_IPV6)); - udp_tunnel_xmit_skb(rt, sock->sk, skb, + udp_tunnel_xmit_skb(rt, sk, skb, fl4.saddr, fl4.daddr, AMT_TOS, @@ -1060,14 +1060,14 @@ static void amt_send_multicast_data(struct amt_dev *amt, bool v6) { struct amt_header_mcast_data *amtmd; - struct socket *sock; struct sk_buff *skb; struct iphdr *iph; struct flowi4 fl4; struct rtable *rt; + struct sock *sk; - sock = rcu_dereference_bh(amt->sock); - if (!sock) + sk = rcu_dereference_bh(amt->sk); + if (!sk) return; skb = skb_copy_expand(oskb, sizeof(*amtmd) + sizeof(*iph) + @@ -1097,7 +1097,7 @@ static void amt_send_multicast_data(struct amt_dev *amt, skb_set_inner_protocol(skb, htons(ETH_P_IP)); else skb_set_inner_protocol(skb, htons(ETH_P_IPV6)); - udp_tunnel_xmit_skb(rt, sock->sk, skb, + udp_tunnel_xmit_skb(rt, sk, skb, fl4.saddr, fl4.daddr, AMT_TOS, @@ -1116,13 +1116,13 @@ static bool amt_send_membership_query(struct amt_dev *amt, bool v6) { struct amt_header_membership_query *amtmq; - struct socket *sock; struct rtable *rt; struct flowi4 fl4; + struct sock *sk; int err; - sock = rcu_dereference_bh(amt->sock); - if (!sock) + sk = rcu_dereference_bh(amt->sk); + if (!sk) return true; err = skb_cow_head(skb, LL_RESERVED_SPACE(amt->dev) + sizeof(*amtmq) + @@ -1156,7 +1156,7 @@ static bool amt_send_membership_query(struct amt_dev *amt, skb_set_inner_protocol(skb, htons(ETH_P_IP)); else skb_set_inner_protocol(skb, htons(ETH_P_IPV6)); - udp_tunnel_xmit_skb(rt, sock->sk, skb, + udp_tunnel_xmit_skb(rt, sk, skb, fl4.saddr, fl4.daddr, AMT_TOS, @@ -2554,24 +2554,24 @@ static void amt_send_advertisement(struct amt_dev *amt, __be32 nonce, { struct amt_header_advertisement *amta; int hlen, tlen, offset; - struct socket *sock; struct udphdr *udph; struct sk_buff *skb; struct iphdr *iph; struct rtable *rt; struct flowi4 fl4; + struct sock *sk; u32 len; int err; rcu_read_lock(); - sock = rcu_dereference(amt->sock); - if (!sock) + sk = rcu_dereference(amt->sk); + if (!sk) goto out; if (!netif_running(amt->stream_dev) || !netif_running(amt->dev)) goto out; - rt = ip_route_output_ports(amt->net, &fl4, sock->sk, + rt = ip_route_output_ports(amt->net, &fl4, sk, daddr, amt->local_ip, dport, amt->relay_port, IPPROTO_UDP, 0, @@ -2631,7 +2631,7 @@ static void amt_send_advertisement(struct amt_dev *amt, __be32 nonce, skb->ip_summed = CHECKSUM_NONE; ip_select_ident(amt->net, skb, NULL); ip_send_check(iph); - err = ip_local_out(amt->net, sock->sk, skb); + err = ip_local_out(amt->net, sk, skb); if (unlikely(net_xmit_eval(err))) amt->dev->stats.tx_errors++; @@ -2944,7 +2944,7 @@ drop: return 0; } -static struct socket *amt_create_sock(struct net *net, __be16 port) +static struct sock *amt_create_sock(struct net *net, __be16 port) { struct udp_port_cfg udp_conf; struct socket *sock; @@ -2960,17 +2960,17 @@ static struct socket *amt_create_sock(struct net *net, __be16 port) if (err < 0) return ERR_PTR(err); - return sock; + return sock->sk; } static int amt_socket_create(struct amt_dev *amt) { struct udp_tunnel_sock_cfg tunnel_cfg; - struct socket *sock; + struct sock *sk; - sock = amt_create_sock(amt->net, amt->relay_port); - if (IS_ERR(sock)) - return PTR_ERR(sock); + sk = amt_create_sock(amt->net, amt->relay_port); + if (IS_ERR(sk)) + return PTR_ERR(sk); /* Mark socket as an encapsulation socket */ memset(&tunnel_cfg, 0, sizeof(tunnel_cfg)); @@ -2979,9 +2979,9 @@ static int amt_socket_create(struct amt_dev *amt) tunnel_cfg.encap_rcv = amt_rcv; tunnel_cfg.encap_err_lookup = amt_err_lookup; tunnel_cfg.encap_destroy = NULL; - setup_udp_tunnel_sock(amt->net, sock->sk, &tunnel_cfg); + setup_udp_tunnel_sock(amt->net, sk, &tunnel_cfg); - rcu_assign_pointer(amt->sock, sock); + rcu_assign_pointer(amt->sk, sk); return 0; } @@ -3019,8 +3019,8 @@ static int amt_dev_stop(struct net_device *dev) { struct amt_dev *amt = netdev_priv(dev); struct amt_tunnel_list *tunnel, *tmp; - struct socket *sock; struct sk_buff *skb; + struct sock *sk; int i; cancel_delayed_work_sync(&amt->req_wq); @@ -3028,11 +3028,11 @@ static int amt_dev_stop(struct net_device *dev) cancel_delayed_work_sync(&amt->secret_wq); /* shutdown */ - sock = rtnl_dereference(amt->sock); - RCU_INIT_POINTER(amt->sock, NULL); + sk = rtnl_dereference(amt->sk); + RCU_INIT_POINTER(amt->sk, NULL); synchronize_net(); - if (sock) - udp_tunnel_sock_release(sock->sk); + if (sk) + udp_tunnel_sock_release(sk); cancel_work_sync(&amt->event_wq); for (i = 0; i < AMT_MAX_EVENTS; i++) { diff --git a/include/net/amt.h b/include/net/amt.h index c881bc8b673b..a0255491f5b0 100644 --- a/include/net/amt.h +++ b/include/net/amt.h @@ -331,7 +331,7 @@ struct amt_dev { enum amt_status status; /* Generated key */ siphash_key_t key; - struct socket __rcu *sock; + struct sock __rcu *sk; u32 max_groups; u32 max_sources; u32 hash_buckets; -- cgit v1.2.3 From d07efe5a6e641af59f2dbbef4a748fab6d967747 Mon Sep 17 00:00:00 2001 From: Aditya Garg Date: Sat, 2 May 2026 00:45:33 -0700 Subject: net: mana: Use per-queue allocation for tx_qp to reduce allocation size Convert tx_qp from a single contiguous array allocation to per-queue individual allocations. Each mana_tx_qp struct is approximately 35KB. With many queues (e.g., 32/64), the flat array requires a single contiguous allocation that can fail under memory fragmentation. Change mana_tx_qp *tx_qp to mana_tx_qp **tx_qp (array of pointers), allocating each queue's mana_tx_qp individually via kvzalloc. This reduces each allocation to ~35KB and provides vmalloc fallback, avoiding allocation failure due to fragmentation. Signed-off-by: Aditya Garg Reviewed-by: Haiyang Zhang Link: https://patch.msgid.link/20260502074552.23857-2-gargaditya@linux.microsoft.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/microsoft/mana/mana_bpf.c | 2 +- drivers/net/ethernet/microsoft/mana/mana_en.c | 49 +++++++++++++--------- drivers/net/ethernet/microsoft/mana/mana_ethtool.c | 2 +- include/net/mana/mana.h | 2 +- 4 files changed, 33 insertions(+), 22 deletions(-) (limited to 'include') diff --git a/drivers/net/ethernet/microsoft/mana/mana_bpf.c b/drivers/net/ethernet/microsoft/mana/mana_bpf.c index 7697c9b52ed3..b5e9bb184a1d 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_bpf.c +++ b/drivers/net/ethernet/microsoft/mana/mana_bpf.c @@ -68,7 +68,7 @@ int mana_xdp_xmit(struct net_device *ndev, int n, struct xdp_frame **frames, count++; } - tx_stats = &apc->tx_qp[q_idx].txq.stats; + tx_stats = &apc->tx_qp[q_idx]->txq.stats; u64_stats_update_begin(&tx_stats->syncp); tx_stats->xdp_xmit += count; diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c index 7c83e010a1e6..b4379993d096 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_en.c +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c @@ -355,9 +355,9 @@ netdev_tx_t mana_start_xmit(struct sk_buff *skb, struct net_device *ndev) if (skb_cow_head(skb, MANA_HEADROOM)) goto tx_drop_count; - txq = &apc->tx_qp[txq_idx].txq; + txq = &apc->tx_qp[txq_idx]->txq; gdma_sq = txq->gdma_sq; - cq = &apc->tx_qp[txq_idx].tx_cq; + cq = &apc->tx_qp[txq_idx]->tx_cq; tx_stats = &txq->stats; BUILD_BUG_ON(MAX_TX_WQE_SGL_ENTRIES != MANA_MAX_TX_WQE_SGL_ENTRIES); @@ -614,7 +614,7 @@ static void mana_get_stats64(struct net_device *ndev, } for (q = 0; q < num_queues; q++) { - tx_stats = &apc->tx_qp[q].txq.stats; + tx_stats = &apc->tx_qp[q]->txq.stats; do { start = u64_stats_fetch_begin(&tx_stats->syncp); @@ -2327,21 +2327,26 @@ static void mana_destroy_txq(struct mana_port_context *apc) return; for (i = 0; i < apc->num_queues; i++) { - debugfs_remove_recursive(apc->tx_qp[i].mana_tx_debugfs); - apc->tx_qp[i].mana_tx_debugfs = NULL; + if (!apc->tx_qp[i]) + continue; + + debugfs_remove_recursive(apc->tx_qp[i]->mana_tx_debugfs); + apc->tx_qp[i]->mana_tx_debugfs = NULL; - napi = &apc->tx_qp[i].tx_cq.napi; - if (apc->tx_qp[i].txq.napi_initialized) { + napi = &apc->tx_qp[i]->tx_cq.napi; + if (apc->tx_qp[i]->txq.napi_initialized) { napi_synchronize(napi); napi_disable_locked(napi); netif_napi_del_locked(napi); - apc->tx_qp[i].txq.napi_initialized = false; + apc->tx_qp[i]->txq.napi_initialized = false; } - mana_destroy_wq_obj(apc, GDMA_SQ, apc->tx_qp[i].tx_object); + mana_destroy_wq_obj(apc, GDMA_SQ, apc->tx_qp[i]->tx_object); - mana_deinit_cq(apc, &apc->tx_qp[i].tx_cq); + mana_deinit_cq(apc, &apc->tx_qp[i]->tx_cq); - mana_deinit_txq(apc, &apc->tx_qp[i].txq); + mana_deinit_txq(apc, &apc->tx_qp[i]->txq); + + kvfree(apc->tx_qp[i]); } kfree(apc->tx_qp); @@ -2350,7 +2355,7 @@ static void mana_destroy_txq(struct mana_port_context *apc) static void mana_create_txq_debugfs(struct mana_port_context *apc, int idx) { - struct mana_tx_qp *tx_qp = &apc->tx_qp[idx]; + struct mana_tx_qp *tx_qp = apc->tx_qp[idx]; char qnum[32]; sprintf(qnum, "TX-%d", idx); @@ -2389,7 +2394,7 @@ static int mana_create_txq(struct mana_port_context *apc, int err; int i; - apc->tx_qp = kzalloc_objs(struct mana_tx_qp, apc->num_queues); + apc->tx_qp = kzalloc_objs(struct mana_tx_qp *, apc->num_queues); if (!apc->tx_qp) return -ENOMEM; @@ -2409,10 +2414,16 @@ static int mana_create_txq(struct mana_port_context *apc, gc = gd->gdma_context; for (i = 0; i < apc->num_queues; i++) { - apc->tx_qp[i].tx_object = INVALID_MANA_HANDLE; + apc->tx_qp[i] = kvzalloc_obj(*apc->tx_qp[i]); + if (!apc->tx_qp[i]) { + err = -ENOMEM; + goto out; + } + + apc->tx_qp[i]->tx_object = INVALID_MANA_HANDLE; /* Create SQ */ - txq = &apc->tx_qp[i].txq; + txq = &apc->tx_qp[i]->txq; u64_stats_init(&txq->stats.syncp); txq->ndev = net; @@ -2430,7 +2441,7 @@ static int mana_create_txq(struct mana_port_context *apc, goto out; /* Create SQ's CQ */ - cq = &apc->tx_qp[i].tx_cq; + cq = &apc->tx_qp[i]->tx_cq; cq->type = MANA_CQ_TYPE_TX; cq->txq = txq; @@ -2459,7 +2470,7 @@ static int mana_create_txq(struct mana_port_context *apc, err = mana_create_wq_obj(apc, apc->port_handle, GDMA_SQ, &wq_spec, &cq_spec, - &apc->tx_qp[i].tx_object); + &apc->tx_qp[i]->tx_object); if (err) goto out; @@ -3294,7 +3305,7 @@ static int mana_dealloc_queues(struct net_device *ndev) */ for (i = 0; i < apc->num_queues; i++) { - txq = &apc->tx_qp[i].txq; + txq = &apc->tx_qp[i]->txq; tsleep = 1000; while (atomic_read(&txq->pending_sends) > 0 && time_before(jiffies, timeout)) { @@ -3313,7 +3324,7 @@ static int mana_dealloc_queues(struct net_device *ndev) } for (i = 0; i < apc->num_queues; i++) { - txq = &apc->tx_qp[i].txq; + txq = &apc->tx_qp[i]->txq; while ((skb = skb_dequeue(&txq->pending_skbs))) { mana_unmap_skb(skb, apc); dev_kfree_skb_any(skb); diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c index 6a4b42fe0944..04350973e19e 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c +++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c @@ -260,7 +260,7 @@ static void mana_get_ethtool_stats(struct net_device *ndev, } for (q = 0; q < num_queues; q++) { - tx_stats = &apc->tx_qp[q].txq.stats; + tx_stats = &apc->tx_qp[q]->txq.stats; do { start = u64_stats_fetch_begin(&tx_stats->syncp); diff --git a/include/net/mana/mana.h b/include/net/mana/mana.h index 8f721cd4e4a7..aa90a858c8e3 100644 --- a/include/net/mana/mana.h +++ b/include/net/mana/mana.h @@ -507,7 +507,7 @@ struct mana_port_context { bool tx_shortform_allowed; u16 tx_vp_offset; - struct mana_tx_qp *tx_qp; + struct mana_tx_qp **tx_qp; /* Indirection Table for RX & TX. The values are queue indexes */ u32 *indir_table; -- cgit v1.2.3 From 32a72840ee300fe88e9f31dc55f9ae1dacdc8b8d Mon Sep 17 00:00:00 2001 From: Mark Bloch Date: Sun, 3 May 2026 23:27:24 +0300 Subject: net/mlx5: E-Switch, serialize representor lifecycle Representor callbacks can be registered and unregistered while the E-Switch is already in switchdev mode, and the same E-Switch may also be reconfigured by devlink, VF changes and SF changes. Serialize these paths with the per-E-Switch representor mutex instead of relying on ad-hoc bit state and wait queues. Take the representor lock around the mode transition, VF/SF representor changes and representor ops registration. Keep mode_lock and the representor lock unnested by using the operation flag while the mode lock is dropped. During mode changes, drop the representor lock around the auxiliary bus rescan because driver bind/unbind may register or unregister representor ops. Split representor ops registration into locked public wrappers and blocked internal helpers, clear the ops pointer on unregister, and add nested wrappers for the shared-FDB master IB path that registers peer representor ops while another E-Switch representor lock is already held. On unregister, always call __unload_reps_all_vport() before marking reps unregistered and clearing rep_ops. The per-representor state check makes this a no-op for types that were not loaded, so unregister no longer has to infer load state from esw->mode. Signed-off-by: Mark Bloch Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260503202726.266415-6-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/infiniband/hw/mlx5/ib_rep.c | 6 +- drivers/net/ethernet/mellanox/mlx5/core/eswitch.c | 10 ++ .../ethernet/mellanox/mlx5/core/eswitch_offloads.c | 105 +++++++++++++++++++-- .../net/ethernet/mellanox/mlx5/core/sf/devlink.c | 5 + include/linux/mlx5/eswitch.h | 6 ++ 5 files changed, 120 insertions(+), 12 deletions(-) (limited to 'include') diff --git a/drivers/infiniband/hw/mlx5/ib_rep.c b/drivers/infiniband/hw/mlx5/ib_rep.c index 1709b628702e..65d8767d1830 100644 --- a/drivers/infiniband/hw/mlx5/ib_rep.c +++ b/drivers/infiniband/hw/mlx5/ib_rep.c @@ -262,9 +262,10 @@ mlx5_ib_vport_rep_unload(struct mlx5_eswitch_rep *rep) struct mlx5_core_dev *peer_mdev; struct mlx5_eswitch *esw; + /* Called while the master E-Switch reps_lock is held. */ mlx5_lag_for_each_peer_mdev(mdev, peer_mdev, i) { esw = peer_mdev->priv.eswitch; - mlx5_eswitch_unregister_vport_reps(esw, REP_IB); + mlx5_eswitch_unregister_vport_reps_nested(esw, REP_IB); } mlx5_ib_release_transport(mdev); } @@ -284,9 +285,10 @@ static void mlx5_ib_register_peer_vport_reps(struct mlx5_core_dev *mdev) struct mlx5_eswitch *esw; int i; + /* Called while the master E-Switch reps_lock is held. */ mlx5_lag_for_each_peer_mdev(mdev, peer_mdev, i) { esw = peer_mdev->priv.eswitch; - mlx5_eswitch_register_vport_reps(esw, &rep_ops, REP_IB); + mlx5_eswitch_register_vport_reps_nested(esw, &rep_ops, REP_IB); } } diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c index 66a773a99876..f70737437954 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c @@ -1712,6 +1712,7 @@ int mlx5_eswitch_enable(struct mlx5_eswitch *esw, int num_vfs) mlx5_lag_disable_change(esw->dev); mlx5_eswitch_invalidate_wq(esw); + mlx5_esw_reps_block(esw); if (!mlx5_esw_is_fdb_created(esw)) { ret = mlx5_eswitch_enable_locked(esw, num_vfs); @@ -1735,6 +1736,8 @@ int mlx5_eswitch_enable(struct mlx5_eswitch *esw, int num_vfs) } } + mlx5_esw_reps_unblock(esw); + if (toggle_lag) mlx5_lag_enable_change(esw->dev); @@ -1759,6 +1762,7 @@ void mlx5_eswitch_disable_sriov(struct mlx5_eswitch *esw, bool clear_vf) esw->esw_funcs.num_vfs, esw->esw_funcs.num_ec_vfs, esw->enabled_vports); mlx5_eswitch_invalidate_wq(esw); + mlx5_esw_reps_block(esw); if (!mlx5_core_is_ecpf(esw->dev)) { mlx5_eswitch_unload_vf_vports(esw, esw->esw_funcs.num_vfs); @@ -1770,6 +1774,8 @@ void mlx5_eswitch_disable_sriov(struct mlx5_eswitch *esw, bool clear_vf) mlx5_eswitch_clear_ec_vf_vports_info(esw); } + mlx5_esw_reps_unblock(esw); + if (esw->mode == MLX5_ESWITCH_OFFLOADS) { struct devlink *devlink = priv_to_devlink(esw->dev); @@ -1825,7 +1831,11 @@ void mlx5_eswitch_disable(struct mlx5_eswitch *esw) devl_assert_locked(priv_to_devlink(esw->dev)); mlx5_lag_disable_change(esw->dev); + + mlx5_esw_reps_block(esw); mlx5_eswitch_disable_locked(esw); + mlx5_esw_reps_unblock(esw); + esw->mode = MLX5_ESWITCH_LEGACY; mlx5_lag_enable_change(esw->dev); } diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c b/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c index af7d0d58c048..a393efaa2fd7 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c @@ -36,6 +36,7 @@ #include #include #include +#include #include "mlx5_core.h" #include "eswitch.h" #include "esw/indir_table.h" @@ -2413,11 +2414,21 @@ out_free: return err; } +static void mlx5_esw_assert_reps_locked(struct mlx5_eswitch *esw) +{ + lockdep_assert_held(&esw->offloads.reps_lock); +} + void mlx5_esw_reps_block(struct mlx5_eswitch *esw) { mutex_lock(&esw->offloads.reps_lock); } +static void mlx5_esw_reps_block_nested(struct mlx5_eswitch *esw) +{ + mutex_lock_nested(&esw->offloads.reps_lock, SINGLE_DEPTH_NESTING); +} + void mlx5_esw_reps_unblock(struct mlx5_eswitch *esw) { mutex_unlock(&esw->offloads.reps_lock); @@ -2425,21 +2436,22 @@ void mlx5_esw_reps_unblock(struct mlx5_eswitch *esw) static void esw_mode_change(struct mlx5_eswitch *esw, u16 mode) { + mlx5_esw_reps_unblock(esw); mlx5_devcom_comp_lock(esw->dev->priv.hca_devcom_comp); if (esw->dev->priv.flags & MLX5_PRIV_FLAGS_DISABLE_IB_ADEV || mlx5_core_mp_enabled(esw->dev)) { esw->mode = mode; - mlx5_rescan_drivers_locked(esw->dev); - mlx5_devcom_comp_unlock(esw->dev->priv.hca_devcom_comp); - return; + goto out; } esw->dev->priv.flags |= MLX5_PRIV_FLAGS_DISABLE_IB_ADEV; mlx5_rescan_drivers_locked(esw->dev); esw->mode = mode; esw->dev->priv.flags &= ~MLX5_PRIV_FLAGS_DISABLE_IB_ADEV; +out: mlx5_rescan_drivers_locked(esw->dev); mlx5_devcom_comp_unlock(esw->dev->priv.hca_devcom_comp); + mlx5_esw_reps_block(esw); } static void mlx5_esw_fdb_drop_destroy(struct mlx5_eswitch *esw) @@ -2776,6 +2788,8 @@ void esw_offloads_cleanup(struct mlx5_eswitch *esw) static int __esw_offloads_load_rep(struct mlx5_eswitch *esw, struct mlx5_eswitch_rep *rep, u8 rep_type) { + mlx5_esw_assert_reps_locked(esw); + if (atomic_cmpxchg(&rep->rep_data[rep_type].state, REP_REGISTERED, REP_LOADED) == REP_REGISTERED) return esw->offloads.rep_ops[rep_type]->load(esw->dev, rep); @@ -2786,6 +2800,8 @@ static int __esw_offloads_load_rep(struct mlx5_eswitch *esw, static void __esw_offloads_unload_rep(struct mlx5_eswitch *esw, struct mlx5_eswitch_rep *rep, u8 rep_type) { + mlx5_esw_assert_reps_locked(esw); + if (atomic_cmpxchg(&rep->rep_data[rep_type].state, REP_LOADED, REP_REGISTERED) == REP_LOADED) { if (rep_type == REP_ETH) @@ -3691,6 +3707,7 @@ static void esw_vfs_changed_event_handler(struct mlx5_eswitch *esw) if (new_num_vfs == esw->esw_funcs.num_vfs || host_pf_disabled) goto free; + mlx5_esw_reps_block(esw); /* Number of VFs can only change from "0 to x" or "x to 0". */ if (esw->esw_funcs.num_vfs > 0) { mlx5_eswitch_unload_vf_vports(esw, esw->esw_funcs.num_vfs); @@ -3700,9 +3717,11 @@ static void esw_vfs_changed_event_handler(struct mlx5_eswitch *esw) err = mlx5_eswitch_load_vf_vports(esw, new_num_vfs, MLX5_VPORT_UC_ADDR_CHANGE); if (err) - goto free; + goto unblock; } esw->esw_funcs.num_vfs = new_num_vfs; +unblock: + mlx5_esw_reps_unblock(esw); free: kvfree(out); } @@ -4190,9 +4209,14 @@ int mlx5_devlink_eswitch_mode_set(struct devlink *devlink, u16 mode, goto unlock; } + /* Keep mode_lock and reps_lock unnested. The operation flag excludes + * mode users while mode_lock is dropped before taking reps_lock. + */ esw->eswitch_operation_in_progress = true; up_write(&esw->mode_lock); + mlx5_esw_reps_block(esw); + if (mlx5_mode == MLX5_ESWITCH_OFFLOADS && !mlx5_devlink_netdev_netns_immutable_set(devlink, true)) { NL_SET_ERR_MSG_MOD(extack, @@ -4225,6 +4249,10 @@ int mlx5_devlink_eswitch_mode_set(struct devlink *devlink, u16 mode, skip: if (mlx5_mode == MLX5_ESWITCH_OFFLOADS && err) mlx5_devlink_netdev_netns_immutable_set(devlink, false); + /* Reconfiguration is done; drop reps_lock before taking mode_lock again + * to clear the operation flag. + */ + mlx5_esw_reps_unblock(esw); down_write(&esw->mode_lock); esw->eswitch_operation_in_progress = false; unlock: @@ -4498,9 +4526,10 @@ mlx5_eswitch_vport_has_rep(const struct mlx5_eswitch *esw, u16 vport_num) return true; } -void mlx5_eswitch_register_vport_reps(struct mlx5_eswitch *esw, - const struct mlx5_eswitch_rep_ops *ops, - u8 rep_type) +static void +mlx5_eswitch_register_vport_reps_blocked(struct mlx5_eswitch *esw, + const struct mlx5_eswitch_rep_ops *ops, + u8 rep_type) { struct mlx5_eswitch_rep_data *rep_data; struct mlx5_eswitch_rep *rep; @@ -4515,21 +4544,77 @@ void mlx5_eswitch_register_vport_reps(struct mlx5_eswitch *esw, } } } + +static void +mlx5_eswitch_register_vport_reps_locked(struct mlx5_eswitch *esw, + const struct mlx5_eswitch_rep_ops *ops, + u8 rep_type, bool nested) +{ + if (nested) + mlx5_esw_reps_block_nested(esw); + else + mlx5_esw_reps_block(esw); + mlx5_eswitch_register_vport_reps_blocked(esw, ops, rep_type); + mlx5_esw_reps_unblock(esw); +} + +void mlx5_eswitch_register_vport_reps(struct mlx5_eswitch *esw, + const struct mlx5_eswitch_rep_ops *ops, + u8 rep_type) +{ + mlx5_eswitch_register_vport_reps_locked(esw, ops, rep_type, false); +} EXPORT_SYMBOL(mlx5_eswitch_register_vport_reps); -void mlx5_eswitch_unregister_vport_reps(struct mlx5_eswitch *esw, u8 rep_type) +void +mlx5_eswitch_register_vport_reps_nested(struct mlx5_eswitch *esw, + const struct mlx5_eswitch_rep_ops *ops, + u8 rep_type) +{ + mlx5_eswitch_register_vport_reps_locked(esw, ops, rep_type, true); +} +EXPORT_SYMBOL(mlx5_eswitch_register_vport_reps_nested); + +static void +mlx5_eswitch_unregister_vport_reps_blocked(struct mlx5_eswitch *esw, + u8 rep_type) { struct mlx5_eswitch_rep *rep; unsigned long i; - if (esw->mode == MLX5_ESWITCH_OFFLOADS) - __unload_reps_all_vport(esw, rep_type); + __unload_reps_all_vport(esw, rep_type); mlx5_esw_for_each_rep(esw, i, rep) atomic_set(&rep->rep_data[rep_type].state, REP_UNREGISTERED); + + esw->offloads.rep_ops[rep_type] = NULL; +} + +static void +mlx5_eswitch_unregister_vport_reps_locked(struct mlx5_eswitch *esw, + u8 rep_type, bool nested) +{ + if (nested) + mlx5_esw_reps_block_nested(esw); + else + mlx5_esw_reps_block(esw); + mlx5_eswitch_unregister_vport_reps_blocked(esw, rep_type); + mlx5_esw_reps_unblock(esw); +} + +void mlx5_eswitch_unregister_vport_reps(struct mlx5_eswitch *esw, u8 rep_type) +{ + mlx5_eswitch_unregister_vport_reps_locked(esw, rep_type, false); } EXPORT_SYMBOL(mlx5_eswitch_unregister_vport_reps); +void mlx5_eswitch_unregister_vport_reps_nested(struct mlx5_eswitch *esw, + u8 rep_type) +{ + mlx5_eswitch_unregister_vport_reps_locked(esw, rep_type, true); +} +EXPORT_SYMBOL(mlx5_eswitch_unregister_vport_reps_nested); + void *mlx5_eswitch_get_uplink_priv(struct mlx5_eswitch *esw, u8 rep_type) { struct mlx5_eswitch_rep *rep; diff --git a/drivers/net/ethernet/mellanox/mlx5/core/sf/devlink.c b/drivers/net/ethernet/mellanox/mlx5/core/sf/devlink.c index 8503e532f423..2fc69897e35b 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/sf/devlink.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/sf/devlink.c @@ -245,8 +245,10 @@ static int mlx5_sf_add(struct mlx5_core_dev *dev, struct mlx5_sf_table *table, if (IS_ERR(sf)) return PTR_ERR(sf); + mlx5_esw_reps_block(esw); err = mlx5_eswitch_load_sf_vport(esw, sf->hw_fn_id, MLX5_VPORT_UC_ADDR_CHANGE, &sf->dl_port, new_attr->controller, new_attr->sfnum); + mlx5_esw_reps_unblock(esw); if (err) goto esw_err; *dl_port = &sf->dl_port.dl_port; @@ -367,7 +369,10 @@ int mlx5_devlink_sf_port_del(struct devlink *devlink, struct mlx5_sf_table *table = dev->priv.sf_table; struct mlx5_sf *sf = mlx5_sf_by_dl_port(dl_port); + mlx5_esw_reps_block(dev->priv.eswitch); mlx5_sf_del(table, sf); + mlx5_esw_reps_unblock(dev->priv.eswitch); + return 0; } diff --git a/include/linux/mlx5/eswitch.h b/include/linux/mlx5/eswitch.h index 3b29a3c6794d..a0dd162baa78 100644 --- a/include/linux/mlx5/eswitch.h +++ b/include/linux/mlx5/eswitch.h @@ -63,7 +63,13 @@ struct mlx5_eswitch_rep { void mlx5_eswitch_register_vport_reps(struct mlx5_eswitch *esw, const struct mlx5_eswitch_rep_ops *ops, u8 rep_type); +void +mlx5_eswitch_register_vport_reps_nested(struct mlx5_eswitch *esw, + const struct mlx5_eswitch_rep_ops *ops, + u8 rep_type); void mlx5_eswitch_unregister_vport_reps(struct mlx5_eswitch *esw, u8 rep_type); +void mlx5_eswitch_unregister_vport_reps_nested(struct mlx5_eswitch *esw, + u8 rep_type); void *mlx5_eswitch_get_proto_dev(struct mlx5_eswitch *esw, u16 vport_num, u8 rep_type); -- cgit v1.2.3 From 5e138e0ec32b12d99f639947fc77b4bebda8f6e9 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 5 May 2026 20:04:57 +0200 Subject: w5100: remove MMIO support This driver supports both SPI and MMIO based register access, but only the former has devicetree support. While MMIO mode would have worked with old-style board files, those have never defined such a device upstream. Remove the MMIO mode, leaving SPI as the only way to use this driver, but leave it in two loadable modules. More cleanups can be done by combining the two into one file. Signed-off-by: Arnd Bergmann Link: https://patch.msgid.link/20260505180459.1247690-1-arnd@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/wiznet/Kconfig | 19 +- drivers/net/ethernet/wiznet/Makefile | 3 +- drivers/net/ethernet/wiznet/w5100.c | 350 ----------------------------------- include/linux/platform_data/wiznet.h | 23 --- 4 files changed, 3 insertions(+), 392 deletions(-) delete mode 100644 include/linux/platform_data/wiznet.h (limited to 'include') diff --git a/drivers/net/ethernet/wiznet/Kconfig b/drivers/net/ethernet/wiznet/Kconfig index 4bac2ad2d6a1..67b3376b39c7 100644 --- a/drivers/net/ethernet/wiznet/Kconfig +++ b/drivers/net/ethernet/wiznet/Kconfig @@ -5,7 +5,6 @@ config NET_VENDOR_WIZNET bool "WIZnet devices" - depends on HAS_IOMEM default y help If you have a network (Ethernet) card belonging to this class, say Y. @@ -18,8 +17,8 @@ config NET_VENDOR_WIZNET if NET_VENDOR_WIZNET config WIZNET_W5100 - tristate "WIZnet W5100 Ethernet support" - depends on HAS_IOMEM + tristate "WIZnet W5100/W5200/W5500 Ethernet support for SPI mode" + depends on SPI help Support for WIZnet W5100 chips. @@ -70,18 +69,4 @@ config WIZNET_BUS_ANY Performance may decrease compared to explicitly selected bus mode. endchoice -config WIZNET_W5100_SPI - tristate "WIZnet W5100/W5200/W5500 Ethernet support for SPI mode" - depends on WIZNET_BUS_ANY && WIZNET_W5100 - depends on SPI - help - In SPI mode host system accesses registers using SPI protocol - (mode 0) on the SPI bus. - - Performance decreases compared to other bus interface mode. - In W5100 SPI mode, burst READ/WRITE processing are not provided. - - To compile this driver as a module, choose M here: the module - will be called w5100-spi. - endif # NET_VENDOR_WIZNET diff --git a/drivers/net/ethernet/wiznet/Makefile b/drivers/net/ethernet/wiznet/Makefile index 78104f0bf415..a97fdcdf4632 100644 --- a/drivers/net/ethernet/wiznet/Makefile +++ b/drivers/net/ethernet/wiznet/Makefile @@ -1,4 +1,3 @@ # SPDX-License-Identifier: GPL-2.0-only -obj-$(CONFIG_WIZNET_W5100) += w5100.o -obj-$(CONFIG_WIZNET_W5100_SPI) += w5100-spi.o +obj-$(CONFIG_WIZNET_W5100) += w5100.o w5100-spi.o obj-$(CONFIG_WIZNET_W5300) += w5300.o diff --git a/drivers/net/ethernet/wiznet/w5100.c b/drivers/net/ethernet/wiznet/w5100.c index c5424d882135..cfe6813ce805 100644 --- a/drivers/net/ethernet/wiznet/w5100.c +++ b/drivers/net/ethernet/wiznet/w5100.c @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -172,311 +171,6 @@ struct w5100_priv { struct work_struct restart_work; }; -/************************************************************************ - * - * Lowlevel I/O functions - * - ***********************************************************************/ - -struct w5100_mmio_priv { - void __iomem *base; - /* Serialize access in indirect address mode */ - spinlock_t reg_lock; -}; - -static inline struct w5100_mmio_priv *w5100_mmio_priv(struct net_device *dev) -{ - return w5100_ops_priv(dev); -} - -static inline void __iomem *w5100_mmio(struct net_device *ndev) -{ - struct w5100_mmio_priv *mmio_priv = w5100_mmio_priv(ndev); - - return mmio_priv->base; -} - -/* - * In direct address mode host system can directly access W5100 registers - * after mapping to Memory-Mapped I/O space. - * - * 0x8000 bytes are required for memory space. - */ -static inline int w5100_read_direct(struct net_device *ndev, u32 addr) -{ - return ioread8(w5100_mmio(ndev) + (addr << CONFIG_WIZNET_BUS_SHIFT)); -} - -static inline int __w5100_write_direct(struct net_device *ndev, u32 addr, - u8 data) -{ - iowrite8(data, w5100_mmio(ndev) + (addr << CONFIG_WIZNET_BUS_SHIFT)); - - return 0; -} - -static inline int w5100_write_direct(struct net_device *ndev, u32 addr, u8 data) -{ - __w5100_write_direct(ndev, addr, data); - - return 0; -} - -static int w5100_read16_direct(struct net_device *ndev, u32 addr) -{ - u16 data; - data = w5100_read_direct(ndev, addr) << 8; - data |= w5100_read_direct(ndev, addr + 1); - return data; -} - -static int w5100_write16_direct(struct net_device *ndev, u32 addr, u16 data) -{ - __w5100_write_direct(ndev, addr, data >> 8); - __w5100_write_direct(ndev, addr + 1, data); - - return 0; -} - -static int w5100_readbulk_direct(struct net_device *ndev, u32 addr, u8 *buf, - int len) -{ - int i; - - for (i = 0; i < len; i++, addr++) - *buf++ = w5100_read_direct(ndev, addr); - - return 0; -} - -static int w5100_writebulk_direct(struct net_device *ndev, u32 addr, - const u8 *buf, int len) -{ - int i; - - for (i = 0; i < len; i++, addr++) - __w5100_write_direct(ndev, addr, *buf++); - - return 0; -} - -static int w5100_mmio_init(struct net_device *ndev) -{ - struct platform_device *pdev = to_platform_device(ndev->dev.parent); - struct w5100_mmio_priv *mmio_priv = w5100_mmio_priv(ndev); - - spin_lock_init(&mmio_priv->reg_lock); - - mmio_priv->base = devm_platform_get_and_ioremap_resource(pdev, 0, NULL); - if (IS_ERR(mmio_priv->base)) - return PTR_ERR(mmio_priv->base); - - return 0; -} - -static const struct w5100_ops w5100_mmio_direct_ops = { - .chip_id = W5100, - .read = w5100_read_direct, - .write = w5100_write_direct, - .read16 = w5100_read16_direct, - .write16 = w5100_write16_direct, - .readbulk = w5100_readbulk_direct, - .writebulk = w5100_writebulk_direct, - .init = w5100_mmio_init, -}; - -/* - * In indirect address mode host system indirectly accesses registers by - * using Indirect Mode Address Register (IDM_AR) and Indirect Mode Data - * Register (IDM_DR), which are directly mapped to Memory-Mapped I/O space. - * Mode Register (MR) is directly accessible. - * - * Only 0x04 bytes are required for memory space. - */ -#define W5100_IDM_AR 0x01 /* Indirect Mode Address Register */ -#define W5100_IDM_DR 0x03 /* Indirect Mode Data Register */ - -static int w5100_read_indirect(struct net_device *ndev, u32 addr) -{ - struct w5100_mmio_priv *mmio_priv = w5100_mmio_priv(ndev); - unsigned long flags; - u8 data; - - spin_lock_irqsave(&mmio_priv->reg_lock, flags); - w5100_write16_direct(ndev, W5100_IDM_AR, addr); - data = w5100_read_direct(ndev, W5100_IDM_DR); - spin_unlock_irqrestore(&mmio_priv->reg_lock, flags); - - return data; -} - -static int w5100_write_indirect(struct net_device *ndev, u32 addr, u8 data) -{ - struct w5100_mmio_priv *mmio_priv = w5100_mmio_priv(ndev); - unsigned long flags; - - spin_lock_irqsave(&mmio_priv->reg_lock, flags); - w5100_write16_direct(ndev, W5100_IDM_AR, addr); - w5100_write_direct(ndev, W5100_IDM_DR, data); - spin_unlock_irqrestore(&mmio_priv->reg_lock, flags); - - return 0; -} - -static int w5100_read16_indirect(struct net_device *ndev, u32 addr) -{ - struct w5100_mmio_priv *mmio_priv = w5100_mmio_priv(ndev); - unsigned long flags; - u16 data; - - spin_lock_irqsave(&mmio_priv->reg_lock, flags); - w5100_write16_direct(ndev, W5100_IDM_AR, addr); - data = w5100_read_direct(ndev, W5100_IDM_DR) << 8; - data |= w5100_read_direct(ndev, W5100_IDM_DR); - spin_unlock_irqrestore(&mmio_priv->reg_lock, flags); - - return data; -} - -static int w5100_write16_indirect(struct net_device *ndev, u32 addr, u16 data) -{ - struct w5100_mmio_priv *mmio_priv = w5100_mmio_priv(ndev); - unsigned long flags; - - spin_lock_irqsave(&mmio_priv->reg_lock, flags); - w5100_write16_direct(ndev, W5100_IDM_AR, addr); - __w5100_write_direct(ndev, W5100_IDM_DR, data >> 8); - w5100_write_direct(ndev, W5100_IDM_DR, data); - spin_unlock_irqrestore(&mmio_priv->reg_lock, flags); - - return 0; -} - -static int w5100_readbulk_indirect(struct net_device *ndev, u32 addr, u8 *buf, - int len) -{ - struct w5100_mmio_priv *mmio_priv = w5100_mmio_priv(ndev); - unsigned long flags; - int i; - - spin_lock_irqsave(&mmio_priv->reg_lock, flags); - w5100_write16_direct(ndev, W5100_IDM_AR, addr); - - for (i = 0; i < len; i++) - *buf++ = w5100_read_direct(ndev, W5100_IDM_DR); - - spin_unlock_irqrestore(&mmio_priv->reg_lock, flags); - - return 0; -} - -static int w5100_writebulk_indirect(struct net_device *ndev, u32 addr, - const u8 *buf, int len) -{ - struct w5100_mmio_priv *mmio_priv = w5100_mmio_priv(ndev); - unsigned long flags; - int i; - - spin_lock_irqsave(&mmio_priv->reg_lock, flags); - w5100_write16_direct(ndev, W5100_IDM_AR, addr); - - for (i = 0; i < len; i++) - __w5100_write_direct(ndev, W5100_IDM_DR, *buf++); - - spin_unlock_irqrestore(&mmio_priv->reg_lock, flags); - - return 0; -} - -static int w5100_reset_indirect(struct net_device *ndev) -{ - w5100_write_direct(ndev, W5100_MR, MR_RST); - mdelay(5); - w5100_write_direct(ndev, W5100_MR, MR_PB | MR_AI | MR_IND); - - return 0; -} - -static const struct w5100_ops w5100_mmio_indirect_ops = { - .chip_id = W5100, - .read = w5100_read_indirect, - .write = w5100_write_indirect, - .read16 = w5100_read16_indirect, - .write16 = w5100_write16_indirect, - .readbulk = w5100_readbulk_indirect, - .writebulk = w5100_writebulk_indirect, - .init = w5100_mmio_init, - .reset = w5100_reset_indirect, -}; - -#if defined(CONFIG_WIZNET_BUS_DIRECT) - -static int w5100_read(struct w5100_priv *priv, u32 addr) -{ - return w5100_read_direct(priv->ndev, addr); -} - -static int w5100_write(struct w5100_priv *priv, u32 addr, u8 data) -{ - return w5100_write_direct(priv->ndev, addr, data); -} - -static int w5100_read16(struct w5100_priv *priv, u32 addr) -{ - return w5100_read16_direct(priv->ndev, addr); -} - -static int w5100_write16(struct w5100_priv *priv, u32 addr, u16 data) -{ - return w5100_write16_direct(priv->ndev, addr, data); -} - -static int w5100_readbulk(struct w5100_priv *priv, u32 addr, u8 *buf, int len) -{ - return w5100_readbulk_direct(priv->ndev, addr, buf, len); -} - -static int w5100_writebulk(struct w5100_priv *priv, u32 addr, const u8 *buf, - int len) -{ - return w5100_writebulk_direct(priv->ndev, addr, buf, len); -} - -#elif defined(CONFIG_WIZNET_BUS_INDIRECT) - -static int w5100_read(struct w5100_priv *priv, u32 addr) -{ - return w5100_read_indirect(priv->ndev, addr); -} - -static int w5100_write(struct w5100_priv *priv, u32 addr, u8 data) -{ - return w5100_write_indirect(priv->ndev, addr, data); -} - -static int w5100_read16(struct w5100_priv *priv, u32 addr) -{ - return w5100_read16_indirect(priv->ndev, addr); -} - -static int w5100_write16(struct w5100_priv *priv, u32 addr, u16 data) -{ - return w5100_write16_indirect(priv->ndev, addr, data); -} - -static int w5100_readbulk(struct w5100_priv *priv, u32 addr, u8 *buf, int len) -{ - return w5100_readbulk_indirect(priv->ndev, addr, buf, len); -} - -static int w5100_writebulk(struct w5100_priv *priv, u32 addr, const u8 *buf, - int len) -{ - return w5100_writebulk_indirect(priv->ndev, addr, buf, len); -} - -#else /* CONFIG_WIZNET_BUS_ANY */ - static int w5100_read(struct w5100_priv *priv, u32 addr) { return priv->ops->read(priv->ndev, addr); @@ -508,8 +202,6 @@ static int w5100_writebulk(struct w5100_priv *priv, u32 addr, const u8 *buf, return priv->ops->writebulk(priv->ndev, addr, buf, len); } -#endif - static int w5100_readbuf(struct w5100_priv *priv, u16 offset, u8 *buf, int len) { u32 addr; @@ -1035,38 +727,6 @@ static const struct net_device_ops w5100_netdev_ops = { .ndo_validate_addr = eth_validate_addr, }; -static int w5100_mmio_probe(struct platform_device *pdev) -{ - struct wiznet_platform_data *data = dev_get_platdata(&pdev->dev); - const void *mac_addr = NULL; - struct resource *mem; - const struct w5100_ops *ops; - int irq; - - if (data && is_valid_ether_addr(data->mac_addr)) - mac_addr = data->mac_addr; - - mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!mem) - return -EINVAL; - if (resource_size(mem) < W5100_BUS_DIRECT_SIZE) - ops = &w5100_mmio_indirect_ops; - else - ops = &w5100_mmio_direct_ops; - - irq = platform_get_irq(pdev, 0); - if (irq < 0) - return irq; - - return w5100_probe(&pdev->dev, ops, sizeof(struct w5100_mmio_priv), - mac_addr, irq, data ? data->link_gpio : -EINVAL); -} - -static void w5100_mmio_remove(struct platform_device *pdev) -{ - w5100_remove(&pdev->dev); -} - void *w5100_ops_priv(const struct net_device *ndev) { return netdev_priv(ndev) + @@ -1264,13 +924,3 @@ static int w5100_resume(struct device *dev) SIMPLE_DEV_PM_OPS(w5100_pm_ops, w5100_suspend, w5100_resume); EXPORT_SYMBOL_GPL(w5100_pm_ops); - -static struct platform_driver w5100_mmio_driver = { - .driver = { - .name = DRV_NAME, - .pm = &w5100_pm_ops, - }, - .probe = w5100_mmio_probe, - .remove = w5100_mmio_remove, -}; -module_platform_driver(w5100_mmio_driver); diff --git a/include/linux/platform_data/wiznet.h b/include/linux/platform_data/wiznet.h deleted file mode 100644 index 1154c4db8a13..000000000000 --- a/include/linux/platform_data/wiznet.h +++ /dev/null @@ -1,23 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * Ethernet driver for the WIZnet W5x00 chip. - */ - -#ifndef PLATFORM_DATA_WIZNET_H -#define PLATFORM_DATA_WIZNET_H - -#include - -struct wiznet_platform_data { - int link_gpio; - u8 mac_addr[ETH_ALEN]; -}; - -#ifndef CONFIG_WIZNET_BUS_SHIFT -#define CONFIG_WIZNET_BUS_SHIFT 0 -#endif - -#define W5100_BUS_DIRECT_SIZE (0x8000 << CONFIG_WIZNET_BUS_SHIFT) -#define W5300_BUS_DIRECT_SIZE (0x0400 << CONFIG_WIZNET_BUS_SHIFT) - -#endif /* PLATFORM_DATA_WIZNET_H */ -- cgit v1.2.3 From 5796d9fe0b883327d629f675894c47a1c7108d41 Mon Sep 17 00:00:00 2001 From: Moshe Shemesh Date: Wed, 6 May 2026 16:32:38 +0300 Subject: net/mlx5: Make debugfs page counters by function type dynamic Make the per function type debugfs page counters dynamically added after mlx5_eswitch_init(). When page management operates in vhca_id mode, only the function acting as either eSwitch or vport manager can initialize the eSwitch structure and translate the vhca_id to function type for the functions to which it supplies pages. The next patch will add support for page management in vhca_id mode. Signed-off-by: Moshe Shemesh Reviewed-by: Akiva Goldberger Reviewed-by: Mark Bloch Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260506133239.276237-3-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/debugfs.c | 39 ++++++++++++++++++++--- drivers/net/ethernet/mellanox/mlx5/core/main.c | 7 ++-- include/linux/mlx5/driver.h | 2 ++ 3 files changed, 42 insertions(+), 6 deletions(-) (limited to 'include') diff --git a/drivers/net/ethernet/mellanox/mlx5/core/debugfs.c b/drivers/net/ethernet/mellanox/mlx5/core/debugfs.c index 8fe263190d38..6347957fefcb 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/debugfs.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/debugfs.c @@ -285,10 +285,6 @@ void mlx5_pages_debugfs_init(struct mlx5_core_dev *dev) pages = dev->priv.dbg.pages_debugfs; debugfs_create_u32("fw_pages_total", 0400, pages, &dev->priv.fw_pages); - debugfs_create_u32("fw_pages_vfs", 0400, pages, &dev->priv.page_counters[MLX5_VF]); - debugfs_create_u32("fw_pages_ec_vfs", 0400, pages, &dev->priv.page_counters[MLX5_EC_VF]); - debugfs_create_u32("fw_pages_sfs", 0400, pages, &dev->priv.page_counters[MLX5_SF]); - debugfs_create_u32("fw_pages_host_pf", 0400, pages, &dev->priv.page_counters[MLX5_HOST_PF]); debugfs_create_u32("fw_pages_alloc_failed", 0400, pages, &dev->priv.fw_pages_alloc_failed); debugfs_create_u32("fw_pages_give_dropped", 0400, pages, &dev->priv.give_pages_dropped); debugfs_create_u32("fw_pages_reclaim_discard", 0400, pages, @@ -300,6 +296,41 @@ void mlx5_pages_debugfs_cleanup(struct mlx5_core_dev *dev) debugfs_remove_recursive(dev->priv.dbg.pages_debugfs); } +void mlx5_pages_by_func_type_debugfs_init(struct mlx5_core_dev *dev) +{ + struct dentry *pages = dev->priv.dbg.pages_debugfs; + + if (!pages) + return; + + if (!dev->priv.eswitch && + MLX5_CAP_GEN(dev, icm_mng_function_id_mode) == + MLX5_ID_MODE_FUNCTION_VHCA_ID) + return; + + debugfs_create_u32("fw_pages_vfs", 0400, pages, + &dev->priv.page_counters[MLX5_VF]); + debugfs_create_u32("fw_pages_ec_vfs", 0400, pages, + &dev->priv.page_counters[MLX5_EC_VF]); + debugfs_create_u32("fw_pages_sfs", 0400, pages, + &dev->priv.page_counters[MLX5_SF]); + debugfs_create_u32("fw_pages_host_pf", 0400, pages, + &dev->priv.page_counters[MLX5_HOST_PF]); +} + +void mlx5_pages_by_func_type_debugfs_cleanup(struct mlx5_core_dev *dev) +{ + struct dentry *pages = dev->priv.dbg.pages_debugfs; + + if (!pages) + return; + + debugfs_lookup_and_remove("fw_pages_vfs", pages); + debugfs_lookup_and_remove("fw_pages_ec_vfs", pages); + debugfs_lookup_and_remove("fw_pages_sfs", pages); + debugfs_lookup_and_remove("fw_pages_host_pf", pages); +} + static u64 qp_read_field(struct mlx5_core_dev *dev, struct mlx5_core_qp *qp, int index, int *is_str) { diff --git a/drivers/net/ethernet/mellanox/mlx5/core/main.c b/drivers/net/ethernet/mellanox/mlx5/core/main.c index b1b9ebfd3866..0c1c906b60fa 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/main.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/main.c @@ -987,11 +987,12 @@ static int mlx5_init_once(struct mlx5_core_dev *dev) mlx5_core_err(dev, "Failed to init eswitch %d\n", err); goto err_sriov_cleanup; } + mlx5_pages_by_func_type_debugfs_init(dev); err = mlx5_fpga_init(dev); if (err) { mlx5_core_err(dev, "Failed to init fpga device %d\n", err); - goto err_eswitch_cleanup; + goto err_page_debugfs_cleanup; } err = mlx5_vhca_event_init(dev); @@ -1034,7 +1035,8 @@ err_sf_hw_table_cleanup: mlx5_vhca_event_cleanup(dev); err_fpga_cleanup: mlx5_fpga_cleanup(dev); -err_eswitch_cleanup: +err_page_debugfs_cleanup: + mlx5_pages_by_func_type_debugfs_cleanup(dev); mlx5_eswitch_cleanup(dev->priv.eswitch); err_sriov_cleanup: mlx5_sriov_cleanup(dev); @@ -1072,6 +1074,7 @@ static void mlx5_cleanup_once(struct mlx5_core_dev *dev) mlx5_sf_hw_table_cleanup(dev); mlx5_vhca_event_cleanup(dev); mlx5_fpga_cleanup(dev); + mlx5_pages_by_func_type_debugfs_cleanup(dev); mlx5_eswitch_cleanup(dev->priv.eswitch); mlx5_sriov_cleanup(dev); mlx5_mpfs_cleanup(dev); diff --git a/include/linux/mlx5/driver.h b/include/linux/mlx5/driver.h index 531ce66fc8ef..d1751c5d01c7 100644 --- a/include/linux/mlx5/driver.h +++ b/include/linux/mlx5/driver.h @@ -1039,6 +1039,8 @@ void mlx5_pagealloc_start(struct mlx5_core_dev *dev); void mlx5_pagealloc_stop(struct mlx5_core_dev *dev); void mlx5_pages_debugfs_init(struct mlx5_core_dev *dev); void mlx5_pages_debugfs_cleanup(struct mlx5_core_dev *dev); +void mlx5_pages_by_func_type_debugfs_init(struct mlx5_core_dev *dev); +void mlx5_pages_by_func_type_debugfs_cleanup(struct mlx5_core_dev *dev); int mlx5_satisfy_startup_pages(struct mlx5_core_dev *dev, int boot); int mlx5_reclaim_startup_pages(struct mlx5_core_dev *dev); void mlx5_register_debugfs(void); -- cgit v1.2.3 From 1fba57c91416d8e06aae9f36382f6be2513c2c0f Mon Sep 17 00:00:00 2001 From: Moshe Shemesh Date: Wed, 6 May 2026 16:32:39 +0300 Subject: net/mlx5: Add VHCA_ID page management mode support Add support for VHCA_ID-based page management mode. When the device firmware advertises the icm_mng_function_id_mode capability with MLX5_ID_MODE_FUNCTION_VHCA_ID, page management operations between the driver and firmware may use vhca_id instead of function_id as the effective function identifier, and the ec_function field is ignored. Update page management commands to conditionally set ec_function field only in FUNC_ID mode. Boot page allocation always uses FUNC_ID mode semantics for backward compatibility, as the capability bit is only available after set_hca_cap(). If after set_hca_cap() VHCA_ID mode was set, modify the tracking of the boot pages in page_root_xa to use vhca_id too. Add mlx5_esw_vhca_id_to_func_type() to resolve the function type in VHCA_ID mode, enabling per-type debugfs counters. Use a dedicated vhca_type_map xarray, to provide lockless lookup. Store the resolved type on each fw_page at allocation time so reclaim and release paths read it directly without any lookup. Signed-off-by: Moshe Shemesh Reviewed-by: Akiva Goldberger Reviewed-by: Mark Bloch Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260506133239.276237-4-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/eswitch.c | 45 +++- drivers/net/ethernet/mellanox/mlx5/core/eswitch.h | 8 + drivers/net/ethernet/mellanox/mlx5/core/main.c | 3 + .../net/ethernet/mellanox/mlx5/core/pagealloc.c | 250 ++++++++++++++++----- include/linux/mlx5/driver.h | 7 + 5 files changed, 251 insertions(+), 62 deletions(-) (limited to 'include') diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c index 0fef96d99091..43c40353b2d8 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c @@ -852,6 +852,38 @@ bool mlx5_esw_vport_vhca_id(struct mlx5_eswitch *esw, u16 vportn, u16 *vhca_id) return true; } +static enum mlx5_func_type +esw_vport_to_func_type(struct mlx5_eswitch *esw, struct mlx5_vport *vport) +{ + u16 vport_num = vport->vport; + + if (vport_num == MLX5_VPORT_HOST_PF) + return MLX5_HOST_PF; + if (xa_get_mark(&esw->vports, vport_num, MLX5_ESW_VPT_SF)) + return MLX5_SF; + if (xa_get_mark(&esw->vports, vport_num, MLX5_ESW_VPT_VF)) + return MLX5_VF; + return MLX5_EC_VF; +} + +u16 mlx5_esw_vhca_id_to_func_type(struct mlx5_core_dev *dev, u16 vhca_id) +{ + struct mlx5_eswitch *esw = dev->priv.eswitch; + void *entry; + + if (vhca_id == MLX5_CAP_GEN(dev, vhca_id)) + return MLX5_SELF; + + if (!esw) + return MLX5_FUNC_TYPE_NONE; + + entry = xa_load(&esw->vhca_type_map, vhca_id); + if (entry) + return xa_to_value(entry); + + return MLX5_FUNC_TYPE_NONE; +} + static int esw_vport_setup(struct mlx5_eswitch *esw, struct mlx5_vport *vport) { bool vst_mode_steering = esw_vst_mode_is_steering(esw); @@ -942,6 +974,11 @@ int mlx5_esw_vport_enable(struct mlx5_eswitch *esw, struct mlx5_vport *vport, ret = mlx5_esw_vport_vhca_id_map(esw, vport); if (ret) goto err_vhca_mapping; + ret = xa_insert(&esw->vhca_type_map, vport->vhca_id, + xa_mk_value(esw_vport_to_func_type(esw, vport)), + GFP_KERNEL); + if (ret) + goto err_type_map; } esw_vport_change_handle_locked(vport); @@ -952,6 +989,8 @@ done: mutex_unlock(&esw->state_lock); return ret; +err_type_map: + mlx5_esw_vport_vhca_id_unmap(esw, vport); err_vhca_mapping: esw_vport_cleanup(esw, vport); mutex_unlock(&esw->state_lock); @@ -976,8 +1015,10 @@ void mlx5_esw_vport_disable(struct mlx5_eswitch *esw, struct mlx5_vport *vport) arm_vport_context_events_cmd(esw->dev, vport_num, 0); if (!mlx5_esw_is_manager_vport(esw, vport_num) && - MLX5_CAP_GEN(esw->dev, vport_group_manager)) + MLX5_CAP_GEN(esw->dev, vport_group_manager)) { + xa_erase(&esw->vhca_type_map, vport->vhca_id); mlx5_esw_vport_vhca_id_unmap(esw, vport); + } if (vport->vport != MLX5_VPORT_HOST_PF && (vport->info.ipsec_crypto_enabled || vport->info.ipsec_packet_enabled)) @@ -2094,6 +2135,7 @@ int mlx5_eswitch_init(struct mlx5_core_dev *dev) atomic64_set(&esw->offloads.num_flows, 0); ida_init(&esw->offloads.vport_metadata_ida); xa_init_flags(&esw->offloads.vhca_map, XA_FLAGS_ALLOC); + xa_init(&esw->vhca_type_map); mutex_init(&esw->state_lock); init_rwsem(&esw->mode_lock); refcount_set(&esw->qos.refcnt, 0); @@ -2143,6 +2185,7 @@ void mlx5_eswitch_cleanup(struct mlx5_eswitch *esw) mutex_destroy(&esw->state_lock); WARN_ON(!xa_empty(&esw->offloads.vhca_map)); xa_destroy(&esw->offloads.vhca_map); + xa_destroy(&esw->vhca_type_map); ida_destroy(&esw->offloads.vport_metadata_ida); mlx5e_mod_hdr_tbl_destroy(&esw->offloads.mod_hdr); mutex_destroy(&esw->offloads.encap_tbl_lock); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h index 3858690e09b4..291271afa96c 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h +++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.h @@ -374,6 +374,7 @@ struct mlx5_eswitch { struct dentry *debugfs_root; struct workqueue_struct *work_queue; struct xarray vports; + struct xarray vhca_type_map; u32 flags; int total_vports; int enabled_vports; @@ -864,6 +865,7 @@ void mlx5_esw_vport_vhca_id_unmap(struct mlx5_eswitch *esw, struct mlx5_vport *vport); int mlx5_eswitch_vhca_id_to_vport(struct mlx5_eswitch *esw, u16 vhca_id, u16 *vport_num); bool mlx5_esw_vport_vhca_id(struct mlx5_eswitch *esw, u16 vportn, u16 *vhca_id); +u16 mlx5_esw_vhca_id_to_func_type(struct mlx5_core_dev *dev, u16 vhca_id); void mlx5_esw_offloads_rep_remove(struct mlx5_eswitch *esw, const struct mlx5_vport *vport); @@ -1040,6 +1042,12 @@ mlx5_esw_vport_vhca_id(struct mlx5_eswitch *esw, u16 vportn, u16 *vhca_id) return false; } +static inline u16 +mlx5_esw_vhca_id_to_func_type(struct mlx5_core_dev *dev, u16 vhca_id) +{ + return MLX5_FUNC_TYPE_NONE; +} + static inline void mlx5_eswitch_safe_aux_devs_remove(struct mlx5_core_dev *dev) {} static inline struct mlx5_flow_handle * diff --git a/drivers/net/ethernet/mellanox/mlx5/core/main.c b/drivers/net/ethernet/mellanox/mlx5/core/main.c index 0c1c906b60fa..296c5223cf61 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/main.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/main.c @@ -597,6 +597,9 @@ static int handle_hca_cap(struct mlx5_core_dev *dev, void *set_ctx) if (MLX5_CAP_GEN_MAX(dev, release_all_pages)) MLX5_SET(cmd_hca_cap, set_hca_cap, release_all_pages, 1); + if (MLX5_CAP_GEN_MAX(dev, icm_mng_function_id_mode)) + MLX5_SET(cmd_hca_cap, set_hca_cap, icm_mng_function_id_mode, 1); + if (MLX5_CAP_GEN_MAX(dev, mkey_by_name)) MLX5_SET(cmd_hca_cap, set_hca_cap, mkey_by_name, 1); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/pagealloc.c b/drivers/net/ethernet/mellanox/mlx5/core/pagealloc.c index 77ffa31cc505..ce2f7fa9bd48 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/pagealloc.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/pagealloc.c @@ -38,6 +38,7 @@ #include "mlx5_core.h" #include "lib/eq.h" #include "lib/tout.h" +#include "eswitch.h" enum { MLX5_PAGES_CANT_GIVE = 0, @@ -59,6 +60,7 @@ struct fw_page { u64 addr; struct page *page; u32 function; + u16 func_type; unsigned long bitmask; struct list_head list; unsigned int free_count; @@ -69,9 +71,24 @@ enum { MLX5_NUM_4K_IN_PAGE = PAGE_SIZE / MLX5_ADAPTER_PAGE_SIZE, }; -static u32 get_function(u16 func_id, bool ec_function) +static bool mlx5_page_mgt_mode_is_vhca_id(const struct mlx5_core_dev *dev) { - return (u32)func_id | (ec_function << 16); + return dev->priv.page_mgt_mode == MLX5_PAGE_MGT_MODE_VHCA_ID; +} + +static void mlx5_page_mgt_mode_set(struct mlx5_core_dev *dev, + enum mlx5_page_mgt_mode mode) +{ + dev->priv.page_mgt_mode = mode; +} + +static u32 get_function_key(struct mlx5_core_dev *dev, u16 func_vhca_id, + bool ec_function) +{ + if (mlx5_page_mgt_mode_is_vhca_id(dev)) + return (u32)func_vhca_id; + + return (u32)func_vhca_id | (ec_function << 16); } static u16 func_id_to_type(struct mlx5_core_dev *dev, u16 func_id, bool ec_function) @@ -89,12 +106,21 @@ static u16 func_id_to_type(struct mlx5_core_dev *dev, u16 func_id, bool ec_funct return MLX5_SF; } +static u16 func_vhca_id_to_type(struct mlx5_core_dev *dev, u16 func_vhca_id, + bool ec_function) +{ + if (mlx5_page_mgt_mode_is_vhca_id(dev)) + return mlx5_esw_vhca_id_to_func_type(dev, func_vhca_id); + + return func_id_to_type(dev, func_vhca_id, ec_function); +} + static u32 mlx5_get_ec_function(u32 function) { return function >> 16; } -static u32 mlx5_get_func_id(u32 function) +static u32 mlx5_get_func_vhca_id(u32 function) { return function & 0xffff; } @@ -123,7 +149,8 @@ static struct rb_root *page_root_per_function(struct mlx5_core_dev *dev, u32 fun return root; } -static int insert_page(struct mlx5_core_dev *dev, u64 addr, struct page *page, u32 function) +static int insert_page(struct mlx5_core_dev *dev, u64 addr, struct page *page, + u32 function, u16 func_type) { struct rb_node *parent = NULL; struct rb_root *root; @@ -156,6 +183,7 @@ static int insert_page(struct mlx5_core_dev *dev, u64 addr, struct page *page, u nfp->addr = addr; nfp->page = page; nfp->function = function; + nfp->func_type = func_type; nfp->free_count = MLX5_NUM_4K_IN_PAGE; for (i = 0; i < MLX5_NUM_4K_IN_PAGE; i++) set_bit(i, &nfp->bitmask); @@ -196,7 +224,7 @@ static struct fw_page *find_fw_page(struct mlx5_core_dev *dev, u64 addr, return result; } -static int mlx5_cmd_query_pages(struct mlx5_core_dev *dev, u16 *func_id, +static int mlx5_cmd_query_pages(struct mlx5_core_dev *dev, u16 *func_vhca_id, s32 *npages, int boot) { u32 out[MLX5_ST_SZ_DW(query_pages_out)] = {}; @@ -207,14 +235,20 @@ static int mlx5_cmd_query_pages(struct mlx5_core_dev *dev, u16 *func_id, MLX5_SET(query_pages_in, in, op_mod, boot ? MLX5_QUERY_PAGES_IN_OP_MOD_BOOT_PAGES : MLX5_QUERY_PAGES_IN_OP_MOD_INIT_PAGES); - MLX5_SET(query_pages_in, in, embedded_cpu_function, mlx5_core_is_ecpf(dev)); + + if (mlx5_page_mgt_mode_is_vhca_id(dev)) + MLX5_SET(query_pages_in, in, function_id, + MLX5_CAP_GEN(dev, vhca_id)); + else + MLX5_SET(query_pages_in, in, embedded_cpu_function, + mlx5_core_is_ecpf(dev)); err = mlx5_cmd_exec_inout(dev, query_pages, in, out); if (err) return err; *npages = MLX5_GET(query_pages_out, out, num_pages); - *func_id = MLX5_GET(query_pages_out, out, function_id); + *func_vhca_id = MLX5_GET(query_pages_out, out, function_id); return err; } @@ -245,6 +279,10 @@ static int alloc_4k(struct mlx5_core_dev *dev, u64 *addr, u32 function) if (!fp->free_count) list_del(&fp->list); + if (fp->func_type != MLX5_FUNC_TYPE_NONE) + dev->priv.page_counters[fp->func_type]++; + dev->priv.fw_pages++; + *addr = fp->addr + n * MLX5_ADAPTER_PAGE_SIZE; return 0; @@ -280,6 +318,11 @@ static void free_4k(struct mlx5_core_dev *dev, u64 addr, u32 function) mlx5_core_warn_rl(dev, "page not found\n"); return; } + + if (fwp->func_type != MLX5_FUNC_TYPE_NONE) + dev->priv.page_counters[fwp->func_type]--; + dev->priv.fw_pages--; + n = (addr & ~MLX5_U64_4K_PAGE_MASK) >> MLX5_ADAPTER_PAGE_SHIFT; fwp->free_count++; set_bit(n, &fwp->bitmask); @@ -289,7 +332,8 @@ static void free_4k(struct mlx5_core_dev *dev, u64 addr, u32 function) list_add(&fwp->list, &dev->priv.free_list); } -static int alloc_system_page(struct mlx5_core_dev *dev, u32 function) +static int alloc_system_page(struct mlx5_core_dev *dev, u32 function, + u16 func_type) { struct device *device = mlx5_core_dma_dev(dev); int nid = dev->priv.numa_node; @@ -317,7 +361,7 @@ map: goto map; } - err = insert_page(dev, addr, page, function); + err = insert_page(dev, addr, page, function, func_type); if (err) { mlx5_core_err(dev, "failed to track allocated page\n"); dma_unmap_page(device, addr, PAGE_SIZE, DMA_BIDIRECTIONAL); @@ -334,7 +378,7 @@ err_mapping: return err; } -static void page_notify_fail(struct mlx5_core_dev *dev, u16 func_id, +static void page_notify_fail(struct mlx5_core_dev *dev, u16 func_vhca_id, bool ec_function) { u32 in[MLX5_ST_SZ_DW(manage_pages_in)] = {}; @@ -342,19 +386,23 @@ static void page_notify_fail(struct mlx5_core_dev *dev, u16 func_id, MLX5_SET(manage_pages_in, in, opcode, MLX5_CMD_OP_MANAGE_PAGES); MLX5_SET(manage_pages_in, in, op_mod, MLX5_PAGES_CANT_GIVE); - MLX5_SET(manage_pages_in, in, function_id, func_id); - MLX5_SET(manage_pages_in, in, embedded_cpu_function, ec_function); + MLX5_SET(manage_pages_in, in, function_id, func_vhca_id); + + if (!mlx5_page_mgt_mode_is_vhca_id(dev)) + MLX5_SET(manage_pages_in, in, embedded_cpu_function, + ec_function); err = mlx5_cmd_exec_in(dev, manage_pages, in); if (err) - mlx5_core_warn(dev, "page notify failed func_id(%d) err(%d)\n", - func_id, err); + mlx5_core_warn(dev, + "page notify failed func_vhca_id(%d) err(%d)\n", + func_vhca_id, err); } -static int give_pages(struct mlx5_core_dev *dev, u16 func_id, int npages, +static int give_pages(struct mlx5_core_dev *dev, u16 func_vhca_id, int npages, int event, bool ec_function) { - u32 function = get_function(func_id, ec_function); + u32 function = get_function_key(dev, func_vhca_id, ec_function); u32 out[MLX5_ST_SZ_DW(manage_pages_out)] = {0}; int inlen = MLX5_ST_SZ_BYTES(manage_pages_in); int notify_fail = event; @@ -364,6 +412,8 @@ static int give_pages(struct mlx5_core_dev *dev, u16 func_id, int npages, u32 *in; int i; + func_type = func_vhca_id_to_type(dev, func_vhca_id, ec_function); + inlen += npages * MLX5_FLD_SZ_BYTES(manage_pages_in, pas[0]); in = kvzalloc(inlen, GFP_KERNEL); if (!in) { @@ -377,7 +427,8 @@ retry: err = alloc_4k(dev, &addr, function); if (err) { if (err == -ENOMEM) - err = alloc_system_page(dev, function); + err = alloc_system_page(dev, function, + func_type); if (err) { dev->priv.fw_pages_alloc_failed += (npages - i); goto out_4k; @@ -390,9 +441,12 @@ retry: MLX5_SET(manage_pages_in, in, opcode, MLX5_CMD_OP_MANAGE_PAGES); MLX5_SET(manage_pages_in, in, op_mod, MLX5_PAGES_GIVE); - MLX5_SET(manage_pages_in, in, function_id, func_id); + MLX5_SET(manage_pages_in, in, function_id, func_vhca_id); MLX5_SET(manage_pages_in, in, input_num_entries, npages); - MLX5_SET(manage_pages_in, in, embedded_cpu_function, ec_function); + + if (!mlx5_page_mgt_mode_is_vhca_id(dev)) + MLX5_SET(manage_pages_in, in, embedded_cpu_function, + ec_function); err = mlx5_cmd_do(dev, in, inlen, out, sizeof(out)); if (err == -EREMOTEIO) { @@ -405,17 +459,15 @@ retry: } err = mlx5_cmd_check(dev, err, in, out); if (err) { - mlx5_core_warn(dev, "func_id 0x%x, npages %d, err %d\n", - func_id, npages, err); + mlx5_core_warn(dev, "func_vhca_id 0x%x, npages %d, err %d\n", + func_vhca_id, npages, err); goto out_dropped; } - func_type = func_id_to_type(dev, func_id, ec_function); - dev->priv.page_counters[func_type] += npages; - dev->priv.fw_pages += npages; - - mlx5_core_dbg(dev, "npages %d, ec_function %d, func_id 0x%x, err %d\n", - npages, ec_function, func_id, err); + mlx5_core_dbg(dev, + "npages %d, ec_function %d, func 0x%x, mode %d, err %d\n", + npages, ec_function, func_vhca_id, + mlx5_page_mgt_mode_is_vhca_id(dev), err); kvfree(in); return 0; @@ -428,18 +480,17 @@ out_4k: out_free: kvfree(in); if (notify_fail) - page_notify_fail(dev, func_id, ec_function); + page_notify_fail(dev, func_vhca_id, ec_function); return err; } -static void release_all_pages(struct mlx5_core_dev *dev, u16 func_id, +static void release_all_pages(struct mlx5_core_dev *dev, u16 func_vhca_id, bool ec_function) { - u32 function = get_function(func_id, ec_function); + u32 function = get_function_key(dev, func_vhca_id, ec_function); struct rb_root *root; struct rb_node *p; int npages = 0; - u16 func_type; root = xa_load(&dev->priv.page_root_xa, function); if (WARN_ON_ONCE(!root)) @@ -448,18 +499,20 @@ static void release_all_pages(struct mlx5_core_dev *dev, u16 func_id, p = rb_first(root); while (p) { struct fw_page *fwp = rb_entry(p, struct fw_page, rb_node); + int used = MLX5_NUM_4K_IN_PAGE - fwp->free_count; p = rb_next(p); - npages += (MLX5_NUM_4K_IN_PAGE - fwp->free_count); + npages += used; + if (fwp->func_type != MLX5_FUNC_TYPE_NONE) + dev->priv.page_counters[fwp->func_type] -= used; free_fwp(dev, fwp, fwp->free_count); } - func_type = func_id_to_type(dev, func_id, ec_function); - dev->priv.page_counters[func_type] -= npages; dev->priv.fw_pages -= npages; - mlx5_core_dbg(dev, "npages %d, ec_function %d, func_id 0x%x\n", - npages, ec_function, func_id); + mlx5_core_dbg(dev, "npages %d, ec_function %d, func 0x%x, mode %d\n", + npages, ec_function, func_vhca_id, + mlx5_page_mgt_mode_is_vhca_id(dev)); } static u32 fwp_fill_manage_pages_out(struct fw_page *fwp, u32 *out, u32 index, @@ -487,7 +540,7 @@ static int reclaim_pages_cmd(struct mlx5_core_dev *dev, struct fw_page *fwp; struct rb_node *p; bool ec_function; - u32 func_id; + u32 func_vhca_id; u32 npages; u32 i = 0; int err; @@ -499,10 +552,11 @@ static int reclaim_pages_cmd(struct mlx5_core_dev *dev, /* No hard feelings, we want our pages back! */ npages = MLX5_GET(manage_pages_in, in, input_num_entries); - func_id = MLX5_GET(manage_pages_in, in, function_id); + func_vhca_id = MLX5_GET(manage_pages_in, in, function_id); ec_function = MLX5_GET(manage_pages_in, in, embedded_cpu_function); - root = xa_load(&dev->priv.page_root_xa, get_function(func_id, ec_function)); + root = xa_load(&dev->priv.page_root_xa, + get_function_key(dev, func_vhca_id, ec_function)); if (WARN_ON_ONCE(!root)) return -EEXIST; @@ -518,14 +572,14 @@ static int reclaim_pages_cmd(struct mlx5_core_dev *dev, return 0; } -static int reclaim_pages(struct mlx5_core_dev *dev, u16 func_id, int npages, - int *nclaimed, bool event, bool ec_function) +static int reclaim_pages(struct mlx5_core_dev *dev, u16 func_vhca_id, + int npages, int *nclaimed, bool event, + bool ec_function) { - u32 function = get_function(func_id, ec_function); + u32 function = get_function_key(dev, func_vhca_id, ec_function); int outlen = MLX5_ST_SZ_BYTES(manage_pages_out); u32 in[MLX5_ST_SZ_DW(manage_pages_in)] = {}; int num_claimed; - u16 func_type; u32 *out; int err; int i; @@ -540,12 +594,16 @@ static int reclaim_pages(struct mlx5_core_dev *dev, u16 func_id, int npages, MLX5_SET(manage_pages_in, in, opcode, MLX5_CMD_OP_MANAGE_PAGES); MLX5_SET(manage_pages_in, in, op_mod, MLX5_PAGES_TAKE); - MLX5_SET(manage_pages_in, in, function_id, func_id); + MLX5_SET(manage_pages_in, in, function_id, func_vhca_id); MLX5_SET(manage_pages_in, in, input_num_entries, npages); - MLX5_SET(manage_pages_in, in, embedded_cpu_function, ec_function); - mlx5_core_dbg(dev, "func 0x%x, npages %d, outlen %d\n", - func_id, npages, outlen); + if (!mlx5_page_mgt_mode_is_vhca_id(dev)) + MLX5_SET(manage_pages_in, in, embedded_cpu_function, + ec_function); + + mlx5_core_dbg(dev, "func 0x%x, npages %d, outlen %d mode %d\n", + func_vhca_id, npages, outlen, + mlx5_page_mgt_mode_is_vhca_id(dev)); err = reclaim_pages_cmd(dev, in, sizeof(in), out, outlen); if (err) { npages = MLX5_GET(manage_pages_in, in, input_num_entries); @@ -577,10 +635,6 @@ static int reclaim_pages(struct mlx5_core_dev *dev, u16 func_id, int npages, if (nclaimed) *nclaimed = num_claimed; - func_type = func_id_to_type(dev, func_id, ec_function); - dev->priv.page_counters[func_type] -= num_claimed; - dev->priv.fw_pages -= num_claimed; - out_free: kvfree(out); return err; @@ -658,30 +712,102 @@ static int req_pages_handler(struct notifier_block *nb, * req->npages (and not min ()). */ req->npages = max_t(s32, npages, MAX_RECLAIM_NPAGES); - req->ec_function = ec_function; + if (!mlx5_page_mgt_mode_is_vhca_id(dev)) + req->ec_function = ec_function; req->release_all = release_all; INIT_WORK(&req->work, pages_work_handler); queue_work(dev->priv.pg_wq, &req->work); return NOTIFY_OK; } +/* + * After set_hca_cap(), the second satisfy_startup_pages(dev, 0) may see + * VHCA_ID mode. If page_root_xa already has the PF entry from the first + * (boot) call under FUNC_ID keys 0 or (ec_function << 16), migrate that + * entry to the device vhca_id key so lookups use VHCA_ID semantics. + */ +static int mlx5_pagealloc_migrate_pf_to_vhca_id(struct mlx5_core_dev *dev) +{ + u32 vhca_id_key, old_key; + struct rb_root *root; + struct fw_page *fwp; + struct rb_node *p; + bool ec_function; + int err; + + if (xa_empty(&dev->priv.page_root_xa)) + return 0; + + vhca_id_key = MLX5_CAP_GEN(dev, vhca_id); + ec_function = mlx5_core_is_ecpf(dev); + + old_key = ec_function ? (1U << 16) : 0; + root = xa_load(&dev->priv.page_root_xa, old_key); + if (!root) + return 0; + + if (old_key == vhca_id_key) + return 0; + + err = xa_insert(&dev->priv.page_root_xa, vhca_id_key, root, GFP_KERNEL); + if (err) { + mlx5_core_warn(dev, + "failed to migrate page root key 0x%x to vhca_id 0x%x\n", + old_key, vhca_id_key); + return err; + } + + for (p = rb_first(root); p; p = rb_next(p)) { + fwp = rb_entry(p, struct fw_page, rb_node); + fwp->function = vhca_id_key; + } + + xa_erase(&dev->priv.page_root_xa, old_key); + + return 0; +} + int mlx5_satisfy_startup_pages(struct mlx5_core_dev *dev, int boot) { - u16 func_id; + bool ec_function = false; + u16 func_vhca_id; s32 npages; int err; - err = mlx5_cmd_query_pages(dev, &func_id, &npages, boot); + /* Boot pages are requested before set_hca_cap(), so the capability + * is not negotiated yet; use FUNC_ID mode for backward compatibility. + * Init pages are requested after set_hca_cap(), which unconditionally + * enables CAP_GEN_MAX. Current caps are not re-queried at this point, + * so check CAP_GEN_MAX directly. + */ + if (boot) { + mlx5_page_mgt_mode_set(dev, MLX5_PAGE_MGT_MODE_FUNC_ID); + } else { + if (MLX5_CAP_GEN_MAX(dev, icm_mng_function_id_mode) == + MLX5_ID_MODE_FUNCTION_VHCA_ID) { + err = mlx5_pagealloc_migrate_pf_to_vhca_id(dev); + if (err) + return err; + mlx5_page_mgt_mode_set(dev, MLX5_PAGE_MGT_MODE_VHCA_ID); + } + } + + err = mlx5_cmd_query_pages(dev, &func_vhca_id, &npages, boot); if (err) return err; - mlx5_core_dbg(dev, "requested %d %s pages for func_id 0x%x\n", - npages, boot ? "boot" : "init", func_id); + mlx5_core_dbg(dev, + "requested %d %s pages for func_vhca_id 0x%x\n", + npages, boot ? "boot" : "init", func_vhca_id); if (!npages) return 0; - return give_pages(dev, func_id, npages, 0, mlx5_core_is_ecpf(dev)); + /* In VHCA_ID mode, ec_function remains false (not used). */ + if (!mlx5_page_mgt_mode_is_vhca_id(dev)) + ec_function = mlx5_core_is_ecpf(dev); + + return give_pages(dev, func_vhca_id, npages, 0, ec_function); } enum { @@ -709,15 +835,17 @@ static int mlx5_reclaim_root_pages(struct mlx5_core_dev *dev, while (!RB_EMPTY_ROOT(root)) { u32 ec_function = mlx5_get_ec_function(function); - u32 function_id = mlx5_get_func_id(function); + u32 func_vhca_id = mlx5_get_func_vhca_id(function); int nclaimed; int err; - err = reclaim_pages(dev, function_id, optimal_reclaimed_pages(), + err = reclaim_pages(dev, func_vhca_id, + optimal_reclaimed_pages(), &nclaimed, false, ec_function); if (err) { - mlx5_core_warn(dev, "reclaim_pages err (%d) func_id=0x%x ec_func=0x%x\n", - err, function_id, ec_function); + mlx5_core_warn(dev, + "reclaim_pages err (%d) func_vhca_id=0x%x ec_func=0x%x\n", + err, func_vhca_id, ec_function); return err; } diff --git a/include/linux/mlx5/driver.h b/include/linux/mlx5/driver.h index d1751c5d01c7..8b4d384125d1 100644 --- a/include/linux/mlx5/driver.h +++ b/include/linux/mlx5/driver.h @@ -558,6 +558,12 @@ enum mlx5_func_type { MLX5_HOST_PF, MLX5_EC_VF, MLX5_FUNC_TYPE_NUM, + MLX5_FUNC_TYPE_NONE = MLX5_FUNC_TYPE_NUM, +}; + +enum mlx5_page_mgt_mode { + MLX5_PAGE_MGT_MODE_FUNC_ID, + MLX5_PAGE_MGT_MODE_VHCA_ID, }; struct mlx5_frag_buf_node_pools; @@ -578,6 +584,7 @@ struct mlx5_priv { u32 fw_pages_alloc_failed; u32 give_pages_dropped; u32 reclaim_pages_discard; + enum mlx5_page_mgt_mode page_mgt_mode; struct mlx5_core_health health; struct list_head traps; -- cgit v1.2.3 From cb880b02117de1040b0e23a79d9c05d9a6c475b7 Mon Sep 17 00:00:00 2001 From: David Yang Date: Fri, 8 May 2026 05:40:51 +0800 Subject: net: mention the convention for .ndo_setup_tc() qdisc_offload_dump_helper(), originated from commit 602f3baf2218 ("net_sch: red: Add offload ability to RED qdisc"), is designed to that Whether RED is being offloaded is being determined every time dump action is being called because parent change of this qdisc could change its offload state but doesn't require any RED function to be called. and returning -EOPNOTSUPP (for dump queries) does not mean "I don't have any statistics", but "I don't offload this qdisc anymore". At least two existing drivers did it wrong, so it is worth mentioning. Signed-off-by: David Yang Link: https://patch.msgid.link/20260507214054.2539790-1-mmyangfl@gmail.com Signed-off-by: Jakub Kicinski --- include/linux/netdevice.h | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'include') diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 744ffa243501..e7af71491a47 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -1223,6 +1223,12 @@ struct netdev_net_notifier { * tx queues stopped. This allows the netdevice to perform queue * management safely. * + * NB: Returning -EOPNOTSUPP for whatever commands means "this qdisc + * is not offloaded (anymore, offloading may have silently stopped)", + * and the offloading flag is cleared. Notably, this is also true for + * dump queries (e.g. TC_*_STATS commands). If the underlying device does + * not report any statistics but is still offloading, return 0 instead. + * * Fiber Channel over Ethernet (FCoE) offload functions. * int (*ndo_fcoe_enable)(struct net_device *dev); * Called when the FCoE protocol stack wants to start using LLD for FCoE -- cgit v1.2.3 From 863cd78652018576c60f1b73477a15bdb0ea7551 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Sun, 10 May 2026 09:14:49 +0000 Subject: net/sched: add qdisc_qlen_inc() and qdisc_qlen_dec() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Helpers to increment or decrement sch->q.qlen, with appropriate WRITE_ONCE() to prevent store tearing. Add other WRITE_ONCE() when sch->q.qlen is changed. Signed-off-by: Eric Dumazet Acked-by: Toke Høiland-Jørgensen Link: https://patch.msgid.link/20260510091455.4039245-3-edumazet@google.com Signed-off-by: Jakub Kicinski --- include/net/sch_generic.h | 32 +++++++++++++++++++++----------- net/sched/sch_api.c | 2 +- net/sched/sch_cake.c | 8 ++++---- net/sched/sch_cbs.c | 4 ++-- net/sched/sch_choke.c | 8 ++++---- net/sched/sch_drr.c | 4 ++-- net/sched/sch_dualpi2.c | 6 +++--- net/sched/sch_etf.c | 8 ++++---- net/sched/sch_ets.c | 4 ++-- net/sched/sch_fq.c | 6 +++--- net/sched/sch_fq_codel.c | 7 ++++--- net/sched/sch_fq_pie.c | 4 ++-- net/sched/sch_generic.c | 10 +++++----- net/sched/sch_hfsc.c | 4 ++-- net/sched/sch_hhf.c | 7 ++++--- net/sched/sch_htb.c | 4 ++-- net/sched/sch_mq.c | 5 +++-- net/sched/sch_mqprio.c | 18 ++++++++++-------- net/sched/sch_multiq.c | 4 ++-- net/sched/sch_netem.c | 10 +++++----- net/sched/sch_prio.c | 4 ++-- net/sched/sch_qfq.c | 6 +++--- net/sched/sch_red.c | 4 ++-- net/sched/sch_sfb.c | 4 ++-- net/sched/sch_sfq.c | 9 +++++---- net/sched/sch_skbprio.c | 4 ++-- net/sched/sch_taprio.c | 4 ++-- net/sched/sch_tbf.c | 6 +++--- net/sched/sch_teql.c | 2 +- 29 files changed, 107 insertions(+), 91 deletions(-) (limited to 'include') diff --git a/include/net/sch_generic.h b/include/net/sch_generic.h index ccfabfac674e..dbb3ba6416e4 100644 --- a/include/net/sch_generic.h +++ b/include/net/sch_generic.h @@ -542,6 +542,16 @@ static inline int qdisc_qlen(const struct Qdisc *q) return q->q.qlen; } +static inline void qdisc_qlen_inc(struct Qdisc *q) +{ + WRITE_ONCE(q->q.qlen, q->q.qlen + 1); +} + +static inline void qdisc_qlen_dec(struct Qdisc *q) +{ + WRITE_ONCE(q->q.qlen, q->q.qlen - 1); +} + static inline int qdisc_qlen_sum(const struct Qdisc *q) { __u32 qlen = q->qstats.qlen; @@ -549,9 +559,9 @@ static inline int qdisc_qlen_sum(const struct Qdisc *q) if (qdisc_is_percpu_stats(q)) { for_each_possible_cpu(i) - qlen += per_cpu_ptr(q->cpu_qstats, i)->qlen; + qlen += READ_ONCE(per_cpu_ptr(q->cpu_qstats, i)->qlen); } else { - qlen += q->q.qlen; + qlen += READ_ONCE(q->q.qlen); } return qlen; @@ -1068,7 +1078,7 @@ static inline void __qdisc_enqueue_tail(struct sk_buff *skb, qh->tail = skb; qh->head = skb; } - qh->qlen++; + WRITE_ONCE(qh->qlen, qh->qlen + 1); } static inline int qdisc_enqueue_tail(struct sk_buff *skb, struct Qdisc *sch) @@ -1086,7 +1096,7 @@ static inline void __qdisc_enqueue_head(struct sk_buff *skb, if (!qh->head) qh->tail = skb; qh->head = skb; - qh->qlen++; + WRITE_ONCE(qh->qlen, qh->qlen + 1); } static inline struct sk_buff *__qdisc_dequeue_head(struct qdisc_skb_head *qh) @@ -1095,7 +1105,7 @@ static inline struct sk_buff *__qdisc_dequeue_head(struct qdisc_skb_head *qh) if (likely(skb != NULL)) { qh->head = skb->next; - qh->qlen--; + WRITE_ONCE(qh->qlen, qh->qlen - 1); if (qh->head == NULL) qh->tail = NULL; skb->next = NULL; @@ -1110,7 +1120,7 @@ static inline struct sk_buff *qdisc_dequeue_internal(struct Qdisc *sch, bool dir skb = __skb_dequeue(&sch->gso_skb); if (skb) { - sch->q.qlen--; + qdisc_qlen_dec(sch); qdisc_qstats_backlog_dec(sch, skb); return skb; } @@ -1266,7 +1276,7 @@ static inline struct sk_buff *qdisc_peek_dequeued(struct Qdisc *sch) __skb_queue_head(&sch->gso_skb, skb); /* it's still part of the queue */ qdisc_qstats_backlog_inc(sch, skb); - sch->q.qlen++; + qdisc_qlen_inc(sch); } } @@ -1283,7 +1293,7 @@ static inline void qdisc_update_stats_at_dequeue(struct Qdisc *sch, } else { qdisc_qstats_backlog_dec(sch, skb); qdisc_bstats_update(sch, skb); - sch->q.qlen--; + qdisc_qlen_dec(sch); } } @@ -1295,7 +1305,7 @@ static inline void qdisc_update_stats_at_enqueue(struct Qdisc *sch, this_cpu_add(sch->cpu_qstats->backlog, pkt_len); } else { sch->qstats.backlog += pkt_len; - sch->q.qlen++; + qdisc_qlen_inc(sch); } } @@ -1311,7 +1321,7 @@ static inline struct sk_buff *qdisc_dequeue_peeked(struct Qdisc *sch) qdisc_qstats_cpu_qlen_dec(sch); } else { qdisc_qstats_backlog_dec(sch, skb); - sch->q.qlen--; + qdisc_qlen_dec(sch); } } else { skb = sch->dequeue(sch); @@ -1332,7 +1342,7 @@ static inline void __qdisc_reset_queue(struct qdisc_skb_head *qh) qh->head = NULL; qh->tail = NULL; - qh->qlen = 0; + WRITE_ONCE(qh->qlen, 0); } } diff --git a/net/sched/sch_api.c b/net/sched/sch_api.c index 6f7847c5536f..cefa2d8ac5ec 100644 --- a/net/sched/sch_api.c +++ b/net/sched/sch_api.c @@ -805,7 +805,7 @@ void qdisc_tree_reduce_backlog(struct Qdisc *sch, int n, int len) cl = cops->find(sch, parentid); cops->qlen_notify(sch, cl); } - sch->q.qlen -= n; + WRITE_ONCE(sch->q.qlen, sch->q.qlen - n); sch->qstats.backlog -= len; __qdisc_qstats_drop(sch, drops); } diff --git a/net/sched/sch_cake.c b/net/sched/sch_cake.c index d931e8d51f72..7ab75a52f7d1 100644 --- a/net/sched/sch_cake.c +++ b/net/sched/sch_cake.c @@ -1612,7 +1612,7 @@ static unsigned int cake_drop(struct Qdisc *sch, struct sk_buff **to_free) cake_advance_shaper(q, b, skb, now, true); qdisc_drop_reason(skb, sch, to_free, QDISC_DROP_OVERLIMIT); - sch->q.qlen--; + qdisc_qlen_dec(sch); cake_heapify(q, 0); @@ -1822,7 +1822,7 @@ static s32 cake_enqueue(struct sk_buff *skb, struct Qdisc *sch, segs); flow_queue_add(flow, segs); - sch->q.qlen++; + qdisc_qlen_inc(sch); numsegs++; slen += segs->len; q->buffer_used += segs->truesize; @@ -1861,7 +1861,7 @@ static s32 cake_enqueue(struct sk_buff *skb, struct Qdisc *sch, qdisc_tree_reduce_backlog(sch, 1, ack_pkt_len); consume_skb(ack); } else { - sch->q.qlen++; + qdisc_qlen_inc(sch); q->buffer_used += skb->truesize; } @@ -1987,7 +1987,7 @@ static struct sk_buff *cake_dequeue_one(struct Qdisc *sch) WRITE_ONCE(b->tin_backlog, b->tin_backlog - len); sch->qstats.backlog -= len; q->buffer_used -= skb->truesize; - sch->q.qlen--; + qdisc_qlen_dec(sch); if (q->overflow_timeout) cake_heapify(q, b->overflow_idx[q->cur_flow]); diff --git a/net/sched/sch_cbs.c b/net/sched/sch_cbs.c index 8c9a0400c862..a75e58876797 100644 --- a/net/sched/sch_cbs.c +++ b/net/sched/sch_cbs.c @@ -97,7 +97,7 @@ static int cbs_child_enqueue(struct sk_buff *skb, struct Qdisc *sch, return err; sch->qstats.backlog += len; - sch->q.qlen++; + qdisc_qlen_inc(sch); return NET_XMIT_SUCCESS; } @@ -168,7 +168,7 @@ static struct sk_buff *cbs_child_dequeue(struct Qdisc *sch, struct Qdisc *child) qdisc_qstats_backlog_dec(sch, skb); qdisc_bstats_update(sch, skb); - sch->q.qlen--; + qdisc_qlen_dec(sch); return skb; } diff --git a/net/sched/sch_choke.c b/net/sched/sch_choke.c index 2875bcdb18a4..73d3e673dc7b 100644 --- a/net/sched/sch_choke.c +++ b/net/sched/sch_choke.c @@ -123,7 +123,7 @@ static void choke_drop_by_idx(struct Qdisc *sch, unsigned int idx, if (idx == q->tail) choke_zap_tail_holes(q); - --sch->q.qlen; + qdisc_qlen_dec(sch); qdisc_qstats_backlog_dec(sch, skb); qdisc_tree_reduce_backlog(sch, 1, qdisc_pkt_len(skb)); qdisc_drop(skb, sch, to_free); @@ -271,7 +271,7 @@ static int choke_enqueue(struct sk_buff *skb, struct Qdisc *sch, if (sch->q.qlen < q->limit) { q->tab[q->tail] = skb; q->tail = (q->tail + 1) & q->tab_mask; - ++sch->q.qlen; + qdisc_qlen_inc(sch); qdisc_qstats_backlog_inc(sch, skb); return NET_XMIT_SUCCESS; } @@ -298,7 +298,7 @@ static struct sk_buff *choke_dequeue(struct Qdisc *sch) skb = q->tab[q->head]; q->tab[q->head] = NULL; choke_zap_head_holes(q); - --sch->q.qlen; + qdisc_qlen_dec(sch); qdisc_qstats_backlog_dec(sch, skb); qdisc_bstats_update(sch, skb); @@ -396,7 +396,7 @@ static int choke_change(struct Qdisc *sch, struct nlattr *opt, } dropped += qdisc_pkt_len(skb); qdisc_qstats_backlog_dec(sch, skb); - --sch->q.qlen; + qdisc_qlen_dec(sch); rtnl_qdisc_drop(skb, sch); } qdisc_tree_reduce_backlog(sch, oqlen - sch->q.qlen, dropped); diff --git a/net/sched/sch_drr.c b/net/sched/sch_drr.c index 01335a49e091..925fa0cfd730 100644 --- a/net/sched/sch_drr.c +++ b/net/sched/sch_drr.c @@ -366,7 +366,7 @@ static int drr_enqueue(struct sk_buff *skb, struct Qdisc *sch, } sch->qstats.backlog += len; - sch->q.qlen++; + qdisc_qlen_inc(sch); return err; } @@ -399,7 +399,7 @@ static struct sk_buff *drr_dequeue(struct Qdisc *sch) bstats_update(&cl->bstats, skb); qdisc_bstats_update(sch, skb); qdisc_qstats_backlog_dec(sch, skb); - sch->q.qlen--; + qdisc_qlen_dec(sch); return skb; } diff --git a/net/sched/sch_dualpi2.c b/net/sched/sch_dualpi2.c index 241e6a46bd00..c6416f09dddd 100644 --- a/net/sched/sch_dualpi2.c +++ b/net/sched/sch_dualpi2.c @@ -415,7 +415,7 @@ static int dualpi2_enqueue_skb(struct sk_buff *skb, struct Qdisc *sch, dualpi2_skb_cb(skb)->apply_step = skb_apply_step(skb, q); /* Keep the overall qdisc stats consistent */ - ++sch->q.qlen; + qdisc_qlen_inc(sch); qdisc_qstats_backlog_inc(sch, skb); ++q->packets_in_l; if (!q->l_head_ts) @@ -530,7 +530,7 @@ static struct sk_buff *dequeue_packet(struct Qdisc *sch, qdisc_qstats_backlog_dec(q->l_queue, skb); /* Keep the global queue size consistent */ - --sch->q.qlen; + qdisc_qlen_dec(sch); q->memory_used -= skb->truesize; } else if (c_len) { skb = __qdisc_dequeue_head(&sch->q); @@ -888,7 +888,7 @@ static int dualpi2_change(struct Qdisc *sch, struct nlattr *opt, * l_queue on enqueue; qdisc_dequeue_internal() * handled l_queue, so we further account for sch. */ - --sch->q.qlen; + qdisc_qlen_dec(sch); qdisc_qstats_backlog_dec(sch, skb); q->memory_used -= skb->truesize; rtnl_qdisc_drop(skb, q->l_queue); diff --git a/net/sched/sch_etf.c b/net/sched/sch_etf.c index c74d778c32a1..ada87a81da6a 100644 --- a/net/sched/sch_etf.c +++ b/net/sched/sch_etf.c @@ -189,7 +189,7 @@ static int etf_enqueue_timesortedlist(struct sk_buff *nskb, struct Qdisc *sch, rb_insert_color_cached(&nskb->rbnode, &q->head, leftmost); qdisc_qstats_backlog_inc(sch, nskb); - sch->q.qlen++; + qdisc_qlen_inc(sch); /* Now we may need to re-arm the qdisc watchdog for the next packet. */ reset_watchdog(sch); @@ -222,7 +222,7 @@ static void timesortedlist_drop(struct Qdisc *sch, struct sk_buff *skb, qdisc_qstats_backlog_dec(sch, skb); qdisc_drop(skb, sch, &to_free); qdisc_qstats_overlimit(sch); - sch->q.qlen--; + qdisc_qlen_dec(sch); } kfree_skb_list(to_free); @@ -247,7 +247,7 @@ static void timesortedlist_remove(struct Qdisc *sch, struct sk_buff *skb) q->last = skb->tstamp; - sch->q.qlen--; + qdisc_qlen_dec(sch); } static struct sk_buff *etf_dequeue_timesortedlist(struct Qdisc *sch) @@ -426,7 +426,7 @@ static void timesortedlist_clear(struct Qdisc *sch) rb_erase_cached(&skb->rbnode, &q->head); rtnl_kfree_skbs(skb, skb); - sch->q.qlen--; + qdisc_qlen_dec(sch); } } diff --git a/net/sched/sch_ets.c b/net/sched/sch_ets.c index a4b07b661b77..c817e0a6c146 100644 --- a/net/sched/sch_ets.c +++ b/net/sched/sch_ets.c @@ -449,7 +449,7 @@ static int ets_qdisc_enqueue(struct sk_buff *skb, struct Qdisc *sch, } sch->qstats.backlog += len; - sch->q.qlen++; + qdisc_qlen_inc(sch); return err; } @@ -458,7 +458,7 @@ ets_qdisc_dequeue_skb(struct Qdisc *sch, struct sk_buff *skb) { qdisc_bstats_update(sch, skb); qdisc_qstats_backlog_dec(sch, skb); - sch->q.qlen--; + qdisc_qlen_dec(sch); return skb; } diff --git a/net/sched/sch_fq.c b/net/sched/sch_fq.c index f2edcf872981..1e34ac136b15 100644 --- a/net/sched/sch_fq.c +++ b/net/sched/sch_fq.c @@ -497,7 +497,7 @@ static void fq_dequeue_skb(struct Qdisc *sch, struct fq_flow *flow, fq_erase_head(sch, flow, skb); skb_mark_not_on_list(skb); qdisc_qstats_backlog_dec(sch, skb); - sch->q.qlen--; + qdisc_qlen_dec(sch); qdisc_bstats_update(sch, skb); } @@ -597,7 +597,7 @@ static int fq_enqueue(struct sk_buff *skb, struct Qdisc *sch, flow_queue_add(f, skb); qdisc_qstats_backlog_inc(sch, skb); - sch->q.qlen++; + qdisc_qlen_inc(sch); return NET_XMIT_SUCCESS; } @@ -801,7 +801,7 @@ static void fq_reset(struct Qdisc *sch) struct fq_flow *f; unsigned int idx; - sch->q.qlen = 0; + WRITE_ONCE(sch->q.qlen, 0); sch->qstats.backlog = 0; fq_flow_purge(&q->internal); diff --git a/net/sched/sch_fq_codel.c b/net/sched/sch_fq_codel.c index ed42ce62a17f..cae8483fbb0c 100644 --- a/net/sched/sch_fq_codel.c +++ b/net/sched/sch_fq_codel.c @@ -178,7 +178,7 @@ static unsigned int fq_codel_drop(struct Qdisc *sch, unsigned int max_packets, q->memory_usage -= mem; __qdisc_qstats_drop(sch, i); sch->qstats.backlog -= len; - sch->q.qlen -= i; + WRITE_ONCE(sch->q.qlen, sch->q.qlen - i); return idx; } @@ -215,7 +215,8 @@ static int fq_codel_enqueue(struct sk_buff *skb, struct Qdisc *sch, get_codel_cb(skb)->mem_usage = skb->truesize; q->memory_usage += get_codel_cb(skb)->mem_usage; memory_limited = q->memory_usage > q->memory_limit; - if (++sch->q.qlen <= sch->limit && !memory_limited) + qdisc_qlen_inc(sch); + if (sch->q.qlen <= sch->limit && !memory_limited) return NET_XMIT_SUCCESS; prev_backlog = sch->qstats.backlog; @@ -266,7 +267,7 @@ static struct sk_buff *dequeue_func(struct codel_vars *vars, void *ctx) WRITE_ONCE(q->backlogs[flow - q->flows], q->backlogs[flow - q->flows] - qdisc_pkt_len(skb)); q->memory_usage -= get_codel_cb(skb)->mem_usage; - sch->q.qlen--; + qdisc_qlen_dec(sch); sch->qstats.backlog -= qdisc_pkt_len(skb); } return skb; diff --git a/net/sched/sch_fq_pie.c b/net/sched/sch_fq_pie.c index 7becbf5362b3..0a4eca4ab086 100644 --- a/net/sched/sch_fq_pie.c +++ b/net/sched/sch_fq_pie.c @@ -185,7 +185,7 @@ static int fq_pie_qdisc_enqueue(struct sk_buff *skb, struct Qdisc *sch, q->stats.packets_in++; q->memory_usage += skb->truesize; sch->qstats.backlog += pkt_len; - sch->q.qlen++; + qdisc_qlen_inc(sch); flow_queue_add(sel_flow, skb); if (list_empty(&sel_flow->flowchain)) { list_add_tail(&sel_flow->flowchain, &q->new_flows); @@ -263,7 +263,7 @@ begin: skb = dequeue_head(flow); pkt_len = qdisc_pkt_len(skb); sch->qstats.backlog -= pkt_len; - sch->q.qlen--; + qdisc_qlen_dec(sch); qdisc_bstats_update(sch, skb); } diff --git a/net/sched/sch_generic.c b/net/sched/sch_generic.c index 05c250c483f0..7b4a5874ed1f 100644 --- a/net/sched/sch_generic.c +++ b/net/sched/sch_generic.c @@ -118,7 +118,7 @@ static inline struct sk_buff *__skb_dequeue_bad_txq(struct Qdisc *q) qdisc_qstats_cpu_qlen_dec(q); } else { qdisc_qstats_backlog_dec(q, skb); - q->q.qlen--; + qdisc_qlen_dec(q); } } else { skb = SKB_XOFF_MAGIC; @@ -159,7 +159,7 @@ static inline void qdisc_enqueue_skb_bad_txq(struct Qdisc *q, qdisc_qstats_cpu_qlen_inc(q); } else { qdisc_qstats_backlog_inc(q, skb); - q->q.qlen++; + qdisc_qlen_inc(q); } if (lock) @@ -188,7 +188,7 @@ static inline void dev_requeue_skb(struct sk_buff *skb, struct Qdisc *q) } else { q->qstats.requeues++; qdisc_qstats_backlog_inc(q, skb); - q->q.qlen++; + qdisc_qlen_inc(q); } skb = next; @@ -294,7 +294,7 @@ static struct sk_buff *dequeue_skb(struct Qdisc *q, bool *validate, qdisc_qstats_cpu_qlen_dec(q); } else { qdisc_qstats_backlog_dec(q, skb); - q->q.qlen--; + qdisc_qlen_dec(q); } } else { skb = NULL; @@ -1062,7 +1062,7 @@ void qdisc_reset(struct Qdisc *qdisc) __skb_queue_purge(&qdisc->gso_skb); __skb_queue_purge(&qdisc->skb_bad_txq); - qdisc->q.qlen = 0; + WRITE_ONCE(qdisc->q.qlen, 0); qdisc->qstats.backlog = 0; } EXPORT_SYMBOL(qdisc_reset); diff --git a/net/sched/sch_hfsc.c b/net/sched/sch_hfsc.c index 83b2ca2e37fc..e71a565100ed 100644 --- a/net/sched/sch_hfsc.c +++ b/net/sched/sch_hfsc.c @@ -1561,7 +1561,7 @@ hfsc_enqueue(struct sk_buff *skb, struct Qdisc *sch, struct sk_buff **to_free) } sch->qstats.backlog += len; - sch->q.qlen++; + qdisc_qlen_inc(sch); if (first && !cl_in_el_or_vttree(cl)) { if (cl->cl_flags & HFSC_RSC) @@ -1650,7 +1650,7 @@ hfsc_dequeue(struct Qdisc *sch) qdisc_bstats_update(sch, skb); qdisc_qstats_backlog_dec(sch, skb); - sch->q.qlen--; + qdisc_qlen_dec(sch); return skb; } diff --git a/net/sched/sch_hhf.c b/net/sched/sch_hhf.c index 96021f52d835..1e25b75daae2 100644 --- a/net/sched/sch_hhf.c +++ b/net/sched/sch_hhf.c @@ -360,7 +360,7 @@ static unsigned int hhf_drop(struct Qdisc *sch, struct sk_buff **to_free) if (bucket->head) { struct sk_buff *skb = dequeue_head(bucket); - sch->q.qlen--; + qdisc_qlen_dec(sch); qdisc_qstats_backlog_dec(sch, skb); qdisc_drop(skb, sch, to_free); } @@ -400,7 +400,8 @@ static int hhf_enqueue(struct sk_buff *skb, struct Qdisc *sch, } bucket->deficit = weight * q->quantum; } - if (++sch->q.qlen <= sch->limit) + qdisc_qlen_inc(sch); + if (sch->q.qlen <= sch->limit) return NET_XMIT_SUCCESS; prev_backlog = sch->qstats.backlog; @@ -443,7 +444,7 @@ begin: if (bucket->head) { skb = dequeue_head(bucket); - sch->q.qlen--; + qdisc_qlen_dec(sch); qdisc_qstats_backlog_dec(sch, skb); } diff --git a/net/sched/sch_htb.c b/net/sched/sch_htb.c index eb12381795ce..c22ccd8eae8c 100644 --- a/net/sched/sch_htb.c +++ b/net/sched/sch_htb.c @@ -651,7 +651,7 @@ static int htb_enqueue(struct sk_buff *skb, struct Qdisc *sch, } sch->qstats.backlog += len; - sch->q.qlen++; + qdisc_qlen_inc(sch); return NET_XMIT_SUCCESS; } @@ -951,7 +951,7 @@ static struct sk_buff *htb_dequeue(struct Qdisc *sch) ok: qdisc_bstats_update(sch, skb); qdisc_qstats_backlog_dec(sch, skb); - sch->q.qlen--; + qdisc_qlen_dec(sch); return skb; } diff --git a/net/sched/sch_mq.c b/net/sched/sch_mq.c index a0133a7b9d3b..ec8c91d3fde0 100644 --- a/net/sched/sch_mq.c +++ b/net/sched/sch_mq.c @@ -143,10 +143,10 @@ EXPORT_SYMBOL_NS_GPL(mq_attach, "NET_SCHED_INTERNAL"); void mq_dump_common(struct Qdisc *sch, struct sk_buff *skb) { struct net_device *dev = qdisc_dev(sch); + unsigned int qlen = 0; struct Qdisc *qdisc; unsigned int ntx; - sch->q.qlen = 0; gnet_stats_basic_sync_init(&sch->bstats); memset(&sch->qstats, 0, sizeof(sch->qstats)); @@ -163,10 +163,11 @@ void mq_dump_common(struct Qdisc *sch, struct sk_buff *skb) &qdisc->bstats, false); gnet_stats_add_queue(&sch->qstats, qdisc->cpu_qstats, &qdisc->qstats); - sch->q.qlen += qdisc_qlen(qdisc); + qlen += qdisc_qlen(qdisc); spin_unlock_bh(qdisc_lock(qdisc)); } + WRITE_ONCE(sch->q.qlen, qlen); } EXPORT_SYMBOL_NS_GPL(mq_dump_common, "NET_SCHED_INTERNAL"); diff --git a/net/sched/sch_mqprio.c b/net/sched/sch_mqprio.c index 002add5ce9e0..91a92992cd24 100644 --- a/net/sched/sch_mqprio.c +++ b/net/sched/sch_mqprio.c @@ -555,10 +555,11 @@ static int mqprio_dump(struct Qdisc *sch, struct sk_buff *skb) struct mqprio_sched *priv = qdisc_priv(sch); struct nlattr *nla = (struct nlattr *)skb_tail_pointer(skb); struct tc_mqprio_qopt opt = { 0 }; + unsigned int qlen = 0; struct Qdisc *qdisc; unsigned int ntx; - sch->q.qlen = 0; + qlen = 0; gnet_stats_basic_sync_init(&sch->bstats); memset(&sch->qstats, 0, sizeof(sch->qstats)); @@ -575,10 +576,11 @@ static int mqprio_dump(struct Qdisc *sch, struct sk_buff *skb) &qdisc->bstats, false); gnet_stats_add_queue(&sch->qstats, qdisc->cpu_qstats, &qdisc->qstats); - sch->q.qlen += qdisc_qlen(qdisc); + qlen += qdisc_qlen(qdisc); spin_unlock_bh(qdisc_lock(qdisc)); } + WRITE_ONCE(sch->q.qlen, qlen); mqprio_qopt_reconstruct(dev, &opt); opt.hw = priv->hw_offload; @@ -663,12 +665,12 @@ static int mqprio_dump_class_stats(struct Qdisc *sch, unsigned long cl, __acquires(d->lock) { if (cl >= TC_H_MIN_PRIORITY) { - int i; - __u32 qlen; - struct gnet_stats_queue qstats = {0}; - struct gnet_stats_basic_sync bstats; struct net_device *dev = qdisc_dev(sch); struct netdev_tc_txq tc = dev->tc_to_txq[cl & TC_BITMASK]; + struct gnet_stats_queue qstats = {0}; + struct gnet_stats_basic_sync bstats; + u32 qlen = 0; + int i; gnet_stats_basic_sync_init(&bstats); /* Drop lock here it will be reclaimed before touching @@ -689,11 +691,11 @@ static int mqprio_dump_class_stats(struct Qdisc *sch, unsigned long cl, &qdisc->bstats, false); gnet_stats_add_queue(&qstats, qdisc->cpu_qstats, &qdisc->qstats); - sch->q.qlen += qdisc_qlen(qdisc); + qlen += qdisc_qlen(qdisc); spin_unlock_bh(qdisc_lock(qdisc)); } - qlen = qdisc_qlen(sch) + qstats.qlen; + qlen = qlen + qstats.qlen; /* Reclaim root sleeping lock before completing stats */ if (d->lock) diff --git a/net/sched/sch_multiq.c b/net/sched/sch_multiq.c index 9f822fee113d..4e465d11e3d7 100644 --- a/net/sched/sch_multiq.c +++ b/net/sched/sch_multiq.c @@ -76,7 +76,7 @@ multiq_enqueue(struct sk_buff *skb, struct Qdisc *sch, ret = qdisc_enqueue(skb, qdisc, to_free); if (ret == NET_XMIT_SUCCESS) { - sch->q.qlen++; + qdisc_qlen_inc(sch); return NET_XMIT_SUCCESS; } if (net_xmit_drop_count(ret)) @@ -106,7 +106,7 @@ static struct sk_buff *multiq_dequeue(struct Qdisc *sch) skb = qdisc->dequeue(qdisc); if (skb) { qdisc_bstats_update(sch, skb); - sch->q.qlen--; + qdisc_qlen_dec(sch); return skb; } } diff --git a/net/sched/sch_netem.c b/net/sched/sch_netem.c index bc18e1976b6e..57b12cbca453 100644 --- a/net/sched/sch_netem.c +++ b/net/sched/sch_netem.c @@ -416,7 +416,7 @@ static void tfifo_enqueue(struct sk_buff *nskb, struct Qdisc *sch) rb_insert_color(&nskb->rbnode, &q->t_root); } q->t_len++; - sch->q.qlen++; + qdisc_qlen_inc(sch); } /* netem can't properly corrupt a megapacket (like we get from GSO), so instead @@ -751,19 +751,19 @@ deliver: if (net_xmit_drop_count(err)) qdisc_qstats_drop(sch); sch->qstats.backlog -= pkt_len; - sch->q.qlen--; + qdisc_qlen_dec(sch); qdisc_tree_reduce_backlog(sch, 1, pkt_len); } goto tfifo_dequeue; } - sch->q.qlen--; + qdisc_qlen_dec(sch); goto deliver; } if (q->qdisc) { skb = q->qdisc->ops->dequeue(q->qdisc); if (skb) { - sch->q.qlen--; + qdisc_qlen_dec(sch); goto deliver; } } @@ -776,7 +776,7 @@ deliver: if (q->qdisc) { skb = q->qdisc->ops->dequeue(q->qdisc); if (skb) { - sch->q.qlen--; + qdisc_qlen_dec(sch); goto deliver; } } diff --git a/net/sched/sch_prio.c b/net/sched/sch_prio.c index 9e2b9a490db2..fe42ae3d6b69 100644 --- a/net/sched/sch_prio.c +++ b/net/sched/sch_prio.c @@ -86,7 +86,7 @@ prio_enqueue(struct sk_buff *skb, struct Qdisc *sch, struct sk_buff **to_free) ret = qdisc_enqueue(skb, qdisc, to_free); if (ret == NET_XMIT_SUCCESS) { sch->qstats.backlog += len; - sch->q.qlen++; + qdisc_qlen_inc(sch); return NET_XMIT_SUCCESS; } if (net_xmit_drop_count(ret)) @@ -119,7 +119,7 @@ static struct sk_buff *prio_dequeue(struct Qdisc *sch) if (skb) { qdisc_bstats_update(sch, skb); qdisc_qstats_backlog_dec(sch, skb); - sch->q.qlen--; + qdisc_qlen_dec(sch); return skb; } } diff --git a/net/sched/sch_qfq.c b/net/sched/sch_qfq.c index 699e45873f86..195c434aae5f 100644 --- a/net/sched/sch_qfq.c +++ b/net/sched/sch_qfq.c @@ -1152,12 +1152,12 @@ static struct sk_buff *qfq_dequeue(struct Qdisc *sch) if (!skb) return NULL; - sch->q.qlen--; + qdisc_qlen_dec(sch); skb = agg_dequeue(in_serv_agg, cl, len); if (!skb) { - sch->q.qlen++; + qdisc_qlen_inc(sch); return NULL; } @@ -1265,7 +1265,7 @@ static int qfq_enqueue(struct sk_buff *skb, struct Qdisc *sch, _bstats_update(&cl->bstats, len, gso_segs); sch->qstats.backlog += len; - ++sch->q.qlen; + qdisc_qlen_inc(sch); agg = cl->agg; /* if the class is active, then done here */ diff --git a/net/sched/sch_red.c b/net/sched/sch_red.c index 4d0e44a2e7c6..0719590dfd73 100644 --- a/net/sched/sch_red.c +++ b/net/sched/sch_red.c @@ -139,7 +139,7 @@ static int red_enqueue(struct sk_buff *skb, struct Qdisc *sch, ret = qdisc_enqueue(skb, child, to_free); if (likely(ret == NET_XMIT_SUCCESS)) { sch->qstats.backlog += len; - sch->q.qlen++; + qdisc_qlen_inc(sch); } else if (net_xmit_drop_count(ret)) { WRITE_ONCE(q->stats.pdrop, q->stats.pdrop + 1); @@ -166,7 +166,7 @@ static struct sk_buff *red_dequeue(struct Qdisc *sch) if (skb) { qdisc_bstats_update(sch, skb); qdisc_qstats_backlog_dec(sch, skb); - sch->q.qlen--; + qdisc_qlen_dec(sch); } else { if (!red_is_idling(&q->vars)) red_start_of_idle_period(&q->vars); diff --git a/net/sched/sch_sfb.c b/net/sched/sch_sfb.c index d3ee8e5479b3..efd9251c3add 100644 --- a/net/sched/sch_sfb.c +++ b/net/sched/sch_sfb.c @@ -416,7 +416,7 @@ enqueue: ret = qdisc_enqueue(skb, child, to_free); if (likely(ret == NET_XMIT_SUCCESS)) { sch->qstats.backlog += len; - sch->q.qlen++; + qdisc_qlen_inc(sch); increment_qlen(&cb, q); } else if (net_xmit_drop_count(ret)) { WRITE_ONCE(q->stats.childdrop, @@ -446,7 +446,7 @@ static struct sk_buff *sfb_dequeue(struct Qdisc *sch) if (skb) { qdisc_bstats_update(sch, skb); qdisc_qstats_backlog_dec(sch, skb); - sch->q.qlen--; + qdisc_qlen_dec(sch); decrement_qlen(skb, q); } diff --git a/net/sched/sch_sfq.c b/net/sched/sch_sfq.c index f39822babf88..f9807ee2cf6c 100644 --- a/net/sched/sch_sfq.c +++ b/net/sched/sch_sfq.c @@ -302,7 +302,7 @@ drop: len = qdisc_pkt_len(skb); WRITE_ONCE(slot->backlog, slot->backlog - len); sfq_dec(q, x); - sch->q.qlen--; + qdisc_qlen_dec(sch); qdisc_qstats_backlog_dec(sch, skb); qdisc_drop_reason(skb, sch, to_free, QDISC_DROP_OVERLIMIT); return len; @@ -456,7 +456,8 @@ enqueue: /* We could use a bigger initial quantum for new flows */ WRITE_ONCE(slot->allot, q->quantum); } - if (++sch->q.qlen <= q->limit) + qdisc_qlen_inc(sch); + if (sch->q.qlen <= q->limit) return NET_XMIT_SUCCESS; qlen = slot->qlen; @@ -497,7 +498,7 @@ next_slot: skb = slot_dequeue_head(slot); sfq_dec(q, a); qdisc_bstats_update(sch, skb); - sch->q.qlen--; + qdisc_qlen_dec(sch); qdisc_qstats_backlog_dec(sch, skb); WRITE_ONCE(slot->backlog, slot->backlog - qdisc_pkt_len(skb)); /* Is the slot empty? */ @@ -596,7 +597,7 @@ drop: WRITE_ONCE(slot->allot, q->quantum); } } - sch->q.qlen -= dropped; + WRITE_ONCE(sch->q.qlen, sch->q.qlen - dropped); qdisc_tree_reduce_backlog(sch, dropped, drop_len); } diff --git a/net/sched/sch_skbprio.c b/net/sched/sch_skbprio.c index f485f62ab721..52abfb4015a3 100644 --- a/net/sched/sch_skbprio.c +++ b/net/sched/sch_skbprio.c @@ -93,7 +93,7 @@ static int skbprio_enqueue(struct sk_buff *skb, struct Qdisc *sch, if (prio < q->lowest_prio) q->lowest_prio = prio; - sch->q.qlen++; + qdisc_qlen_inc(sch); return NET_XMIT_SUCCESS; } @@ -145,7 +145,7 @@ static struct sk_buff *skbprio_dequeue(struct Qdisc *sch) if (unlikely(!skb)) return NULL; - sch->q.qlen--; + qdisc_qlen_dec(sch); qdisc_qstats_backlog_dec(sch, skb); qdisc_bstats_update(sch, skb); diff --git a/net/sched/sch_taprio.c b/net/sched/sch_taprio.c index 71b690e1974d..d6b981e5df11 100644 --- a/net/sched/sch_taprio.c +++ b/net/sched/sch_taprio.c @@ -574,7 +574,7 @@ static int taprio_enqueue_one(struct sk_buff *skb, struct Qdisc *sch, } qdisc_qstats_backlog_inc(sch, skb); - sch->q.qlen++; + qdisc_qlen_inc(sch); return qdisc_enqueue(skb, child, to_free); } @@ -755,7 +755,7 @@ skip_peek_checks: qdisc_bstats_update(sch, skb); qdisc_qstats_backlog_dec(sch, skb); - sch->q.qlen--; + qdisc_qlen_dec(sch); return skb; } diff --git a/net/sched/sch_tbf.c b/net/sched/sch_tbf.c index f2340164f579..25edf11a7d67 100644 --- a/net/sched/sch_tbf.c +++ b/net/sched/sch_tbf.c @@ -231,7 +231,7 @@ static int tbf_segment(struct sk_buff *skb, struct Qdisc *sch, len += seg_len; } } - sch->q.qlen += nb; + WRITE_ONCE(sch->q.qlen, sch->q.qlen + nb); sch->qstats.backlog += len; if (nb > 0) { qdisc_tree_reduce_backlog(sch, 1 - nb, prev_len - len); @@ -264,7 +264,7 @@ static int tbf_enqueue(struct sk_buff *skb, struct Qdisc *sch, } sch->qstats.backlog += len; - sch->q.qlen++; + qdisc_qlen_inc(sch); return NET_XMIT_SUCCESS; } @@ -309,7 +309,7 @@ static struct sk_buff *tbf_dequeue(struct Qdisc *sch) q->tokens = toks; q->ptokens = ptoks; qdisc_qstats_backlog_dec(sch, skb); - sch->q.qlen--; + qdisc_qlen_dec(sch); qdisc_bstats_update(sch, skb); return skb; } diff --git a/net/sched/sch_teql.c b/net/sched/sch_teql.c index ec4039a201a2..e7bbc9e5174d 100644 --- a/net/sched/sch_teql.c +++ b/net/sched/sch_teql.c @@ -107,7 +107,7 @@ teql_dequeue(struct Qdisc *sch) } else { qdisc_bstats_update(sch, skb); } - sch->q.qlen = dat->q.qlen + q->q.qlen; + WRITE_ONCE(sch->q.qlen, dat->q.qlen + READ_ONCE(q->q.qlen)); return skb; } -- cgit v1.2.3 From 0e5b05f44d818d1efe5154832d2e3f3c44352c02 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Sun, 10 May 2026 09:14:50 +0000 Subject: net/sched: annotate data-races around sch->qstats.backlog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add qstats_backlog_sub() and qstats_backlog_add() helpers and use them instead of open-coding them. These helpers use WRITE_ONCE() to prevent store-tearing. Also use WRITE_ONCE() in fq_reset() and qdisc_reset() when sch->qstats.backlog is cleared. Signed-off-by: Eric Dumazet Acked-by: Toke Høiland-Jørgensen Link: https://patch.msgid.link/20260510091455.4039245-4-edumazet@google.com Signed-off-by: Jakub Kicinski --- include/net/sch_generic.h | 16 +++++++++++++--- net/sched/sch_api.c | 2 +- net/sched/sch_cake.c | 10 +++++----- net/sched/sch_cbs.c | 2 +- net/sched/sch_codel.c | 2 +- net/sched/sch_drr.c | 2 +- net/sched/sch_ets.c | 2 +- net/sched/sch_fq.c | 2 +- net/sched/sch_fq_codel.c | 4 ++-- net/sched/sch_fq_pie.c | 4 ++-- net/sched/sch_generic.c | 2 +- net/sched/sch_gred.c | 2 +- net/sched/sch_hfsc.c | 2 +- net/sched/sch_htb.c | 2 +- net/sched/sch_netem.c | 2 +- net/sched/sch_prio.c | 2 +- net/sched/sch_qfq.c | 2 +- net/sched/sch_red.c | 2 +- net/sched/sch_sfb.c | 4 ++-- net/sched/sch_sfq.c | 2 +- net/sched/sch_tbf.c | 4 ++-- 21 files changed, 41 insertions(+), 31 deletions(-) (limited to 'include') diff --git a/include/net/sch_generic.h b/include/net/sch_generic.h index dbb3ba6416e4..391ee8530017 100644 --- a/include/net/sch_generic.h +++ b/include/net/sch_generic.h @@ -965,10 +965,15 @@ static inline void qdisc_bstats_update(struct Qdisc *sch, bstats_update(&sch->bstats, skb); } +static inline void qstats_backlog_sub(struct Qdisc *sch, u32 val) +{ + WRITE_ONCE(sch->qstats.backlog, sch->qstats.backlog - val); +} + static inline void qdisc_qstats_backlog_dec(struct Qdisc *sch, const struct sk_buff *skb) { - sch->qstats.backlog -= qdisc_pkt_len(skb); + qstats_backlog_sub(sch, qdisc_pkt_len(skb)); } static inline void qdisc_qstats_cpu_backlog_dec(struct Qdisc *sch, @@ -977,10 +982,15 @@ static inline void qdisc_qstats_cpu_backlog_dec(struct Qdisc *sch, this_cpu_sub(sch->cpu_qstats->backlog, qdisc_pkt_len(skb)); } +static inline void qstats_backlog_add(struct Qdisc *sch, u32 val) +{ + WRITE_ONCE(sch->qstats.backlog, sch->qstats.backlog + val); +} + static inline void qdisc_qstats_backlog_inc(struct Qdisc *sch, const struct sk_buff *skb) { - sch->qstats.backlog += qdisc_pkt_len(skb); + qstats_backlog_add(sch, qdisc_pkt_len(skb)); } static inline void qdisc_qstats_cpu_backlog_inc(struct Qdisc *sch, @@ -1304,7 +1314,7 @@ static inline void qdisc_update_stats_at_enqueue(struct Qdisc *sch, qdisc_qstats_cpu_qlen_inc(sch); this_cpu_add(sch->cpu_qstats->backlog, pkt_len); } else { - sch->qstats.backlog += pkt_len; + qstats_backlog_add(sch, pkt_len); qdisc_qlen_inc(sch); } } diff --git a/net/sched/sch_api.c b/net/sched/sch_api.c index cefa2d8ac5ec..3c779e5098ef 100644 --- a/net/sched/sch_api.c +++ b/net/sched/sch_api.c @@ -806,7 +806,7 @@ void qdisc_tree_reduce_backlog(struct Qdisc *sch, int n, int len) cops->qlen_notify(sch, cl); } WRITE_ONCE(sch->q.qlen, sch->q.qlen - n); - sch->qstats.backlog -= len; + qstats_backlog_sub(sch, len); __qdisc_qstats_drop(sch, drops); } rcu_read_unlock(); diff --git a/net/sched/sch_cake.c b/net/sched/sch_cake.c index 7ab75a52f7d1..a3c185505afc 100644 --- a/net/sched/sch_cake.c +++ b/net/sched/sch_cake.c @@ -1600,10 +1600,10 @@ static unsigned int cake_drop(struct Qdisc *sch, struct sk_buff **to_free) b->unresponsive_flow_count + 1); len = qdisc_pkt_len(skb); - q->buffer_used -= skb->truesize; + qstats_backlog_sub(sch, len); + q->buffer_used -= skb->truesize; WRITE_ONCE(b->tin_backlog, b->tin_backlog - len); WRITE_ONCE(b->backlogs[idx], b->backlogs[idx] - len); - sch->qstats.backlog -= len; WRITE_ONCE(flow->dropped, flow->dropped + 1); WRITE_ONCE(b->tin_dropped, b->tin_dropped + 1); @@ -1830,7 +1830,7 @@ static s32 cake_enqueue(struct sk_buff *skb, struct Qdisc *sch, } /* stats */ - sch->qstats.backlog += slen; + qstats_backlog_add(sch, slen); q->avg_window_bytes += slen; WRITE_ONCE(b->bytes, b->bytes + slen); WRITE_ONCE(b->tin_backlog, b->tin_backlog + slen); @@ -1867,7 +1867,7 @@ static s32 cake_enqueue(struct sk_buff *skb, struct Qdisc *sch, /* stats */ WRITE_ONCE(b->packets, b->packets + 1); - sch->qstats.backlog += len - ack_pkt_len; + qstats_backlog_add(sch, len - ack_pkt_len); q->avg_window_bytes += len - ack_pkt_len; WRITE_ONCE(b->bytes, b->bytes + len - ack_pkt_len); WRITE_ONCE(b->tin_backlog, b->tin_backlog + len - ack_pkt_len); @@ -1985,7 +1985,7 @@ static struct sk_buff *cake_dequeue_one(struct Qdisc *sch) len = qdisc_pkt_len(skb); WRITE_ONCE(b->backlogs[q->cur_flow], b->backlogs[q->cur_flow] - len); WRITE_ONCE(b->tin_backlog, b->tin_backlog - len); - sch->qstats.backlog -= len; + qstats_backlog_sub(sch, len); q->buffer_used -= skb->truesize; qdisc_qlen_dec(sch); diff --git a/net/sched/sch_cbs.c b/net/sched/sch_cbs.c index a75e58876797..2cfa0fd92829 100644 --- a/net/sched/sch_cbs.c +++ b/net/sched/sch_cbs.c @@ -96,7 +96,7 @@ static int cbs_child_enqueue(struct sk_buff *skb, struct Qdisc *sch, if (err != NET_XMIT_SUCCESS) return err; - sch->qstats.backlog += len; + qstats_backlog_add(sch, len); qdisc_qlen_inc(sch); return NET_XMIT_SUCCESS; diff --git a/net/sched/sch_codel.c b/net/sched/sch_codel.c index 317aae0ec7bd..91dd2e629af8 100644 --- a/net/sched/sch_codel.c +++ b/net/sched/sch_codel.c @@ -42,7 +42,7 @@ static struct sk_buff *dequeue_func(struct codel_vars *vars, void *ctx) struct sk_buff *skb = __qdisc_dequeue_head(&sch->q); if (skb) { - sch->qstats.backlog -= qdisc_pkt_len(skb); + qstats_backlog_sub(sch, qdisc_pkt_len(skb)); prefetch(&skb->end); /* we'll need skb_shinfo() */ } return skb; diff --git a/net/sched/sch_drr.c b/net/sched/sch_drr.c index 925fa0cfd730..3f6687fa9666 100644 --- a/net/sched/sch_drr.c +++ b/net/sched/sch_drr.c @@ -365,7 +365,7 @@ static int drr_enqueue(struct sk_buff *skb, struct Qdisc *sch, cl->deficit = cl->quantum; } - sch->qstats.backlog += len; + qstats_backlog_add(sch, len); qdisc_qlen_inc(sch); return err; } diff --git a/net/sched/sch_ets.c b/net/sched/sch_ets.c index c817e0a6c146..1cc559634ed2 100644 --- a/net/sched/sch_ets.c +++ b/net/sched/sch_ets.c @@ -448,7 +448,7 @@ static int ets_qdisc_enqueue(struct sk_buff *skb, struct Qdisc *sch, cl->deficit = cl->quantum; } - sch->qstats.backlog += len; + qstats_backlog_add(sch, len); qdisc_qlen_inc(sch); return err; } diff --git a/net/sched/sch_fq.c b/net/sched/sch_fq.c index 1e34ac136b15..796cb8046a90 100644 --- a/net/sched/sch_fq.c +++ b/net/sched/sch_fq.c @@ -802,7 +802,7 @@ static void fq_reset(struct Qdisc *sch) unsigned int idx; WRITE_ONCE(sch->q.qlen, 0); - sch->qstats.backlog = 0; + WRITE_ONCE(sch->qstats.backlog, 0); fq_flow_purge(&q->internal); diff --git a/net/sched/sch_fq_codel.c b/net/sched/sch_fq_codel.c index cae8483fbb0c..1b1de693d4c6 100644 --- a/net/sched/sch_fq_codel.c +++ b/net/sched/sch_fq_codel.c @@ -177,7 +177,7 @@ static unsigned int fq_codel_drop(struct Qdisc *sch, unsigned int max_packets, WRITE_ONCE(q->backlogs[idx], q->backlogs[idx] - len); q->memory_usage -= mem; __qdisc_qstats_drop(sch, i); - sch->qstats.backlog -= len; + qstats_backlog_sub(sch, len); WRITE_ONCE(sch->q.qlen, sch->q.qlen - i); return idx; } @@ -268,7 +268,7 @@ static struct sk_buff *dequeue_func(struct codel_vars *vars, void *ctx) q->backlogs[flow - q->flows] - qdisc_pkt_len(skb)); q->memory_usage -= get_codel_cb(skb)->mem_usage; qdisc_qlen_dec(sch); - sch->qstats.backlog -= qdisc_pkt_len(skb); + qdisc_qstats_backlog_dec(sch, skb); } return skb; } diff --git a/net/sched/sch_fq_pie.c b/net/sched/sch_fq_pie.c index 0a4eca4ab086..72f48fa4010b 100644 --- a/net/sched/sch_fq_pie.c +++ b/net/sched/sch_fq_pie.c @@ -184,7 +184,7 @@ static int fq_pie_qdisc_enqueue(struct sk_buff *skb, struct Qdisc *sch, pkt_len = qdisc_pkt_len(skb); q->stats.packets_in++; q->memory_usage += skb->truesize; - sch->qstats.backlog += pkt_len; + qstats_backlog_add(sch, pkt_len); qdisc_qlen_inc(sch); flow_queue_add(sel_flow, skb); if (list_empty(&sel_flow->flowchain)) { @@ -262,7 +262,7 @@ begin: if (flow->head) { skb = dequeue_head(flow); pkt_len = qdisc_pkt_len(skb); - sch->qstats.backlog -= pkt_len; + qstats_backlog_sub(sch, pkt_len); qdisc_qlen_dec(sch); qdisc_bstats_update(sch, skb); } diff --git a/net/sched/sch_generic.c b/net/sched/sch_generic.c index 7b4a5874ed1f..237ee1cd0136 100644 --- a/net/sched/sch_generic.c +++ b/net/sched/sch_generic.c @@ -1063,7 +1063,7 @@ void qdisc_reset(struct Qdisc *qdisc) __skb_queue_purge(&qdisc->skb_bad_txq); WRITE_ONCE(qdisc->q.qlen, 0); - qdisc->qstats.backlog = 0; + WRITE_ONCE(qdisc->qstats.backlog, 0); } EXPORT_SYMBOL(qdisc_reset); diff --git a/net/sched/sch_gred.c b/net/sched/sch_gred.c index 8ae65572162c..fcc1a4c03636 100644 --- a/net/sched/sch_gred.c +++ b/net/sched/sch_gred.c @@ -388,7 +388,7 @@ static int gred_offload_dump_stats(struct Qdisc *sch) bytes += u64_stats_read(&hw_stats->stats.bstats[i].bytes); packets += u64_stats_read(&hw_stats->stats.bstats[i].packets); sch->qstats.qlen += hw_stats->stats.qstats[i].qlen; - sch->qstats.backlog += hw_stats->stats.qstats[i].backlog; + qstats_backlog_add(sch, hw_stats->stats.qstats[i].backlog); __qdisc_qstats_drop(sch, hw_stats->stats.qstats[i].drops); sch->qstats.requeues += hw_stats->stats.qstats[i].requeues; sch->qstats.overlimits += hw_stats->stats.qstats[i].overlimits; diff --git a/net/sched/sch_hfsc.c b/net/sched/sch_hfsc.c index e71a565100ed..59409ee2d2ff 100644 --- a/net/sched/sch_hfsc.c +++ b/net/sched/sch_hfsc.c @@ -1560,7 +1560,7 @@ hfsc_enqueue(struct sk_buff *skb, struct Qdisc *sch, struct sk_buff **to_free) return err; } - sch->qstats.backlog += len; + qstats_backlog_add(sch, len); qdisc_qlen_inc(sch); if (first && !cl_in_el_or_vttree(cl)) { diff --git a/net/sched/sch_htb.c b/net/sched/sch_htb.c index c22ccd8eae8c..1e600f65c876 100644 --- a/net/sched/sch_htb.c +++ b/net/sched/sch_htb.c @@ -650,7 +650,7 @@ static int htb_enqueue(struct sk_buff *skb, struct Qdisc *sch, htb_activate(q, cl); } - sch->qstats.backlog += len; + qstats_backlog_add(sch, len); qdisc_qlen_inc(sch); return NET_XMIT_SUCCESS; } diff --git a/net/sched/sch_netem.c b/net/sched/sch_netem.c index 57b12cbca453..ddbfea9dd32a 100644 --- a/net/sched/sch_netem.c +++ b/net/sched/sch_netem.c @@ -750,7 +750,7 @@ deliver: if (err != NET_XMIT_SUCCESS) { if (net_xmit_drop_count(err)) qdisc_qstats_drop(sch); - sch->qstats.backlog -= pkt_len; + qstats_backlog_sub(sch, pkt_len); qdisc_qlen_dec(sch); qdisc_tree_reduce_backlog(sch, 1, pkt_len); } diff --git a/net/sched/sch_prio.c b/net/sched/sch_prio.c index fe42ae3d6b69..e4dd56a89072 100644 --- a/net/sched/sch_prio.c +++ b/net/sched/sch_prio.c @@ -85,7 +85,7 @@ prio_enqueue(struct sk_buff *skb, struct Qdisc *sch, struct sk_buff **to_free) ret = qdisc_enqueue(skb, qdisc, to_free); if (ret == NET_XMIT_SUCCESS) { - sch->qstats.backlog += len; + qstats_backlog_add(sch, len); qdisc_qlen_inc(sch); return NET_XMIT_SUCCESS; } diff --git a/net/sched/sch_qfq.c b/net/sched/sch_qfq.c index 195c434aae5f..cb56787e1d25 100644 --- a/net/sched/sch_qfq.c +++ b/net/sched/sch_qfq.c @@ -1264,7 +1264,7 @@ static int qfq_enqueue(struct sk_buff *skb, struct Qdisc *sch, } _bstats_update(&cl->bstats, len, gso_segs); - sch->qstats.backlog += len; + qstats_backlog_add(sch, len); qdisc_qlen_inc(sch); agg = cl->agg; diff --git a/net/sched/sch_red.c b/net/sched/sch_red.c index 0719590dfd73..d7598214270b 100644 --- a/net/sched/sch_red.c +++ b/net/sched/sch_red.c @@ -138,7 +138,7 @@ static int red_enqueue(struct sk_buff *skb, struct Qdisc *sch, len = qdisc_pkt_len(skb); ret = qdisc_enqueue(skb, child, to_free); if (likely(ret == NET_XMIT_SUCCESS)) { - sch->qstats.backlog += len; + qstats_backlog_add(sch, len); qdisc_qlen_inc(sch); } else if (net_xmit_drop_count(ret)) { WRITE_ONCE(q->stats.pdrop, diff --git a/net/sched/sch_sfb.c b/net/sched/sch_sfb.c index efd9251c3add..b1d465094276 100644 --- a/net/sched/sch_sfb.c +++ b/net/sched/sch_sfb.c @@ -415,7 +415,7 @@ enqueue: memcpy(&cb, sfb_skb_cb(skb), sizeof(cb)); ret = qdisc_enqueue(skb, child, to_free); if (likely(ret == NET_XMIT_SUCCESS)) { - sch->qstats.backlog += len; + qstats_backlog_add(sch, len); qdisc_qlen_inc(sch); increment_qlen(&cb, q); } else if (net_xmit_drop_count(ret)) { @@ -592,7 +592,7 @@ static int sfb_dump(struct Qdisc *sch, struct sk_buff *skb) .penalty_burst = q->penalty_burst, }; - sch->qstats.backlog = q->qdisc->qstats.backlog; + WRITE_ONCE(sch->qstats.backlog, READ_ONCE(q->qdisc->qstats.backlog)); opts = nla_nest_start_noflag(skb, TCA_OPTIONS); if (opts == NULL) goto nla_put_failure; diff --git a/net/sched/sch_sfq.c b/net/sched/sch_sfq.c index f9807ee2cf6c..758b88f21865 100644 --- a/net/sched/sch_sfq.c +++ b/net/sched/sch_sfq.c @@ -427,7 +427,7 @@ congestion_drop: /* We know we have at least one packet in queue */ head = slot_dequeue_head(slot); delta = qdisc_pkt_len(head) - qdisc_pkt_len(skb); - sch->qstats.backlog -= delta; + qstats_backlog_sub(sch, delta); WRITE_ONCE(slot->backlog, slot->backlog - delta); qdisc_drop_reason(head, sch, to_free, QDISC_DROP_FLOW_LIMIT); diff --git a/net/sched/sch_tbf.c b/net/sched/sch_tbf.c index 25edf11a7d67..67c7aaaf8f60 100644 --- a/net/sched/sch_tbf.c +++ b/net/sched/sch_tbf.c @@ -232,7 +232,7 @@ static int tbf_segment(struct sk_buff *skb, struct Qdisc *sch, } } WRITE_ONCE(sch->q.qlen, sch->q.qlen + nb); - sch->qstats.backlog += len; + qstats_backlog_add(sch, len); if (nb > 0) { qdisc_tree_reduce_backlog(sch, 1 - nb, prev_len - len); consume_skb(skb); @@ -263,7 +263,7 @@ static int tbf_enqueue(struct sk_buff *skb, struct Qdisc *sch, return ret; } - sch->qstats.backlog += len; + qstats_backlog_add(sch, len); qdisc_qlen_inc(sch); return NET_XMIT_SUCCESS; } -- cgit v1.2.3 From 554e96a47a84e19abca8d2b1fea1de81cd2c8657 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Sun, 10 May 2026 09:14:51 +0000 Subject: net/sched: add qdisc_qlen_lockless() helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Used in contexts were qdisc spinlock is not held. Signed-off-by: Eric Dumazet Acked-by: Toke Høiland-Jørgensen Link: https://patch.msgid.link/20260510091455.4039245-5-edumazet@google.com Signed-off-by: Jakub Kicinski --- include/net/sch_generic.h | 7 ++++++- net/sched/sch_mq.c | 2 +- net/sched/sch_mqprio.c | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) (limited to 'include') diff --git a/include/net/sch_generic.h b/include/net/sch_generic.h index 391ee8530017..0edf05d56862 100644 --- a/include/net/sch_generic.h +++ b/include/net/sch_generic.h @@ -542,6 +542,11 @@ static inline int qdisc_qlen(const struct Qdisc *q) return q->q.qlen; } +static inline int qdisc_qlen_lockless(const struct Qdisc *q) +{ + return READ_ONCE(q->q.qlen); +} + static inline void qdisc_qlen_inc(struct Qdisc *q) { WRITE_ONCE(q->q.qlen, q->q.qlen + 1); @@ -561,7 +566,7 @@ static inline int qdisc_qlen_sum(const struct Qdisc *q) for_each_possible_cpu(i) qlen += READ_ONCE(per_cpu_ptr(q->cpu_qstats, i)->qlen); } else { - qlen += READ_ONCE(q->q.qlen); + qlen += qdisc_qlen_lockless(q); } return qlen; diff --git a/net/sched/sch_mq.c b/net/sched/sch_mq.c index ec8c91d3fde0..4172ec24a43d 100644 --- a/net/sched/sch_mq.c +++ b/net/sched/sch_mq.c @@ -163,7 +163,7 @@ void mq_dump_common(struct Qdisc *sch, struct sk_buff *skb) &qdisc->bstats, false); gnet_stats_add_queue(&sch->qstats, qdisc->cpu_qstats, &qdisc->qstats); - qlen += qdisc_qlen(qdisc); + qlen += qdisc_qlen_lockless(qdisc); spin_unlock_bh(qdisc_lock(qdisc)); } diff --git a/net/sched/sch_mqprio.c b/net/sched/sch_mqprio.c index 91a92992cd24..3b4881c389c5 100644 --- a/net/sched/sch_mqprio.c +++ b/net/sched/sch_mqprio.c @@ -576,7 +576,7 @@ static int mqprio_dump(struct Qdisc *sch, struct sk_buff *skb) &qdisc->bstats, false); gnet_stats_add_queue(&sch->qstats, qdisc->cpu_qstats, &qdisc->qstats); - qlen += qdisc_qlen(qdisc); + qlen += qdisc_qlen_lockless(qdisc); spin_unlock_bh(qdisc_lock(qdisc)); } @@ -691,7 +691,7 @@ static int mqprio_dump_class_stats(struct Qdisc *sch, unsigned long cl, &qdisc->bstats, false); gnet_stats_add_queue(&qstats, qdisc->cpu_qstats, &qdisc->qstats); - qlen += qdisc_qlen(qdisc); + qlen += qdisc_qlen_lockless(qdisc); spin_unlock_bh(qdisc_lock(qdisc)); } -- cgit v1.2.3 From 5b12bd5c474bab896000407e6b38133121a74c07 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Sun, 10 May 2026 09:14:52 +0000 Subject: net/sched: add const qualifiers to gnet_stats helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In preparation of lockless qdisc dumps, add const qualifiers to: - gnet_stats_add_basic() - gnet_stats_copy_basic() - gnet_stats_copy_basic_hw() - gnet_stats_copy_queue() - gnet_stats_read_basic() - ___gnet_stats_copy_basic() - qdisc_qstats_copy() Signed-off-by: Eric Dumazet Acked-by: Toke Høiland-Jørgensen Link: https://patch.msgid.link/20260510091455.4039245-6-edumazet@google.com Signed-off-by: Jakub Kicinski --- include/net/gen_stats.h | 16 ++++++++-------- include/net/sch_generic.h | 2 +- net/core/gen_stats.c | 31 +++++++++++++++---------------- 3 files changed, 24 insertions(+), 25 deletions(-) (limited to 'include') diff --git a/include/net/gen_stats.h b/include/net/gen_stats.h index 7aa2b8e1fb29..856b5e90fbdb 100644 --- a/include/net/gen_stats.h +++ b/include/net/gen_stats.h @@ -47,19 +47,19 @@ int gnet_stats_start_copy_compat(struct sk_buff *skb, int type, int padattr); int gnet_stats_copy_basic(struct gnet_dump *d, - struct gnet_stats_basic_sync __percpu *cpu, - struct gnet_stats_basic_sync *b, bool running); + const struct gnet_stats_basic_sync __percpu *cpu, + const struct gnet_stats_basic_sync *b, bool running); void gnet_stats_add_basic(struct gnet_stats_basic_sync *bstats, - struct gnet_stats_basic_sync __percpu *cpu, - struct gnet_stats_basic_sync *b, bool running); + const struct gnet_stats_basic_sync __percpu *cpu, + const struct gnet_stats_basic_sync *b, bool running); int gnet_stats_copy_basic_hw(struct gnet_dump *d, - struct gnet_stats_basic_sync __percpu *cpu, - struct gnet_stats_basic_sync *b, bool running); + const struct gnet_stats_basic_sync __percpu *cpu, + const struct gnet_stats_basic_sync *b, bool running); int gnet_stats_copy_rate_est(struct gnet_dump *d, struct net_rate_estimator __rcu **ptr); int gnet_stats_copy_queue(struct gnet_dump *d, - struct gnet_stats_queue __percpu *cpu_q, - struct gnet_stats_queue *q, __u32 qlen); + const struct gnet_stats_queue __percpu *cpu_q, + const struct gnet_stats_queue *q, __u32 qlen); void gnet_stats_add_queue(struct gnet_stats_queue *qstats, const struct gnet_stats_queue __percpu *cpu_q, const struct gnet_stats_queue *q); diff --git a/include/net/sch_generic.h b/include/net/sch_generic.h index 0edf05d56862..2ce84b2c9423 100644 --- a/include/net/sch_generic.h +++ b/include/net/sch_generic.h @@ -1054,7 +1054,7 @@ static inline void qdisc_qstats_overlimit(struct Qdisc *sch) WRITE_ONCE(sch->qstats.overlimits, sch->qstats.overlimits + 1); } -static inline int qdisc_qstats_copy(struct gnet_dump *d, struct Qdisc *sch) +static inline int qdisc_qstats_copy(struct gnet_dump *d, const struct Qdisc *sch) { __u32 qlen = qdisc_qlen_sum(sch); diff --git a/net/core/gen_stats.c b/net/core/gen_stats.c index 1a2380e74272..9468bc5ca3f4 100644 --- a/net/core/gen_stats.c +++ b/net/core/gen_stats.c @@ -124,13 +124,13 @@ void gnet_stats_basic_sync_init(struct gnet_stats_basic_sync *b) EXPORT_SYMBOL(gnet_stats_basic_sync_init); static void gnet_stats_add_basic_cpu(struct gnet_stats_basic_sync *bstats, - struct gnet_stats_basic_sync __percpu *cpu) + const struct gnet_stats_basic_sync __percpu *cpu) { u64 t_bytes = 0, t_packets = 0; int i; for_each_possible_cpu(i) { - struct gnet_stats_basic_sync *bcpu = per_cpu_ptr(cpu, i); + const struct gnet_stats_basic_sync *bcpu = per_cpu_ptr(cpu, i); unsigned int start; u64 bytes, packets; @@ -147,8 +147,8 @@ static void gnet_stats_add_basic_cpu(struct gnet_stats_basic_sync *bstats, } void gnet_stats_add_basic(struct gnet_stats_basic_sync *bstats, - struct gnet_stats_basic_sync __percpu *cpu, - struct gnet_stats_basic_sync *b, bool running) + const struct gnet_stats_basic_sync __percpu *cpu, + const struct gnet_stats_basic_sync *b, bool running) { unsigned int start; u64 bytes = 0; @@ -172,8 +172,8 @@ void gnet_stats_add_basic(struct gnet_stats_basic_sync *bstats, EXPORT_SYMBOL(gnet_stats_add_basic); static void gnet_stats_read_basic(u64 *ret_bytes, u64 *ret_packets, - struct gnet_stats_basic_sync __percpu *cpu, - struct gnet_stats_basic_sync *b, bool running) + const struct gnet_stats_basic_sync __percpu *cpu, + const struct gnet_stats_basic_sync *b, bool running) { unsigned int start; @@ -182,8 +182,7 @@ static void gnet_stats_read_basic(u64 *ret_bytes, u64 *ret_packets, int i; for_each_possible_cpu(i) { - struct gnet_stats_basic_sync *bcpu = per_cpu_ptr(cpu, i); - unsigned int start; + const struct gnet_stats_basic_sync *bcpu = per_cpu_ptr(cpu, i); u64 bytes, packets; do { @@ -209,8 +208,8 @@ static void gnet_stats_read_basic(u64 *ret_bytes, u64 *ret_packets, static int ___gnet_stats_copy_basic(struct gnet_dump *d, - struct gnet_stats_basic_sync __percpu *cpu, - struct gnet_stats_basic_sync *b, + const struct gnet_stats_basic_sync __percpu *cpu, + const struct gnet_stats_basic_sync *b, int type, bool running) { u64 bstats_bytes, bstats_packets; @@ -258,8 +257,8 @@ ___gnet_stats_copy_basic(struct gnet_dump *d, */ int gnet_stats_copy_basic(struct gnet_dump *d, - struct gnet_stats_basic_sync __percpu *cpu, - struct gnet_stats_basic_sync *b, + const struct gnet_stats_basic_sync __percpu *cpu, + const struct gnet_stats_basic_sync *b, bool running) { return ___gnet_stats_copy_basic(d, cpu, b, TCA_STATS_BASIC, running); @@ -285,8 +284,8 @@ EXPORT_SYMBOL(gnet_stats_copy_basic); */ int gnet_stats_copy_basic_hw(struct gnet_dump *d, - struct gnet_stats_basic_sync __percpu *cpu, - struct gnet_stats_basic_sync *b, + const struct gnet_stats_basic_sync __percpu *cpu, + const struct gnet_stats_basic_sync *b, bool running) { return ___gnet_stats_copy_basic(d, cpu, b, TCA_STATS_BASIC_HW, running); @@ -385,8 +384,8 @@ EXPORT_SYMBOL(gnet_stats_add_queue); */ int gnet_stats_copy_queue(struct gnet_dump *d, - struct gnet_stats_queue __percpu *cpu_q, - struct gnet_stats_queue *q, __u32 qlen) + const struct gnet_stats_queue __percpu *cpu_q, + const struct gnet_stats_queue *q, __u32 qlen) { struct gnet_stats_queue qstats = {0}; -- cgit v1.2.3 From 4f07179f4006cbb1ff199c1e6d8a91d0e8a087b6 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Sun, 10 May 2026 09:14:53 +0000 Subject: net/sched: mq: no longer acquire qdisc spinlocks in dump operations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prepare mq_dump_common() for RTNL avoidance. Use RCU instead of RTNL, and no longer acquire each children spinlock. Signed-off-by: Eric Dumazet Acked-by: Toke Høiland-Jørgensen Link: https://patch.msgid.link/20260510091455.4039245-7-edumazet@google.com Signed-off-by: Jakub Kicinski --- include/net/sch_generic.h | 9 +++++++++ net/sched/sch_mq.c | 32 ++++++++++++++++++++++---------- 2 files changed, 31 insertions(+), 10 deletions(-) (limited to 'include') diff --git a/include/net/sch_generic.h b/include/net/sch_generic.h index 2ce84b2c9423..9e6ed92729d2 100644 --- a/include/net/sch_generic.h +++ b/include/net/sch_generic.h @@ -952,6 +952,15 @@ static inline void _bstats_update(struct gnet_stats_basic_sync *bstats, u64_stats_update_end(&bstats->syncp); } +static inline void _bstats_set(struct gnet_stats_basic_sync *bstats, + u64 bytes, u64 packets) +{ + u64_stats_update_begin(&bstats->syncp); + u64_stats_set(&bstats->bytes, bytes); + u64_stats_set(&bstats->packets, packets); + u64_stats_update_end(&bstats->syncp); +} + static inline void bstats_update(struct gnet_stats_basic_sync *bstats, const struct sk_buff *skb) { diff --git a/net/sched/sch_mq.c b/net/sched/sch_mq.c index 4172ec24a43d..5a6b14557d85 100644 --- a/net/sched/sch_mq.c +++ b/net/sched/sch_mq.c @@ -143,30 +143,42 @@ EXPORT_SYMBOL_NS_GPL(mq_attach, "NET_SCHED_INTERNAL"); void mq_dump_common(struct Qdisc *sch, struct sk_buff *skb) { struct net_device *dev = qdisc_dev(sch); + struct gnet_stats_queue qstats = { 0 }; + struct gnet_stats_basic_sync bstats; + const struct Qdisc *qdisc; unsigned int qlen = 0; - struct Qdisc *qdisc; unsigned int ntx; - gnet_stats_basic_sync_init(&sch->bstats); - memset(&sch->qstats, 0, sizeof(sch->qstats)); + gnet_stats_basic_sync_init(&bstats); /* MQ supports lockless qdiscs. However, statistics accounting needs * to account for all, none, or a mix of locked and unlocked child * qdiscs. Percpu stats are added to counters in-band and locking * qdisc totals are added at end. */ + rcu_read_lock(); for (ntx = 0; ntx < dev->num_tx_queues; ntx++) { - qdisc = rtnl_dereference(netdev_get_tx_queue(dev, ntx)->qdisc_sleeping); - spin_lock_bh(qdisc_lock(qdisc)); + qdisc = rcu_dereference(netdev_get_tx_queue(dev, ntx)->qdisc_sleeping); - gnet_stats_add_basic(&sch->bstats, qdisc->cpu_bstats, - &qdisc->bstats, false); - gnet_stats_add_queue(&sch->qstats, qdisc->cpu_qstats, + gnet_stats_add_basic(&bstats, qdisc->cpu_bstats, + &qdisc->bstats, true); + gnet_stats_add_queue(&qstats, qdisc->cpu_qstats, &qdisc->qstats); qlen += qdisc_qlen_lockless(qdisc); - - spin_unlock_bh(qdisc_lock(qdisc)); } + rcu_read_unlock(); + + spin_lock_bh(qdisc_lock(sch)); + _bstats_set(&sch->bstats, u64_stats_read(&bstats.bytes), + u64_stats_read(&bstats.packets)); + spin_unlock_bh(qdisc_lock(sch)); + + WRITE_ONCE(sch->qstats.qlen, qstats.qlen); + WRITE_ONCE(sch->qstats.backlog, qstats.backlog); + WRITE_ONCE(sch->qstats.drops, qstats.drops); + WRITE_ONCE(sch->qstats.requeues, qstats.requeues); + WRITE_ONCE(sch->qstats.overlimits, qstats.overlimits); + WRITE_ONCE(sch->q.qlen, qlen); } EXPORT_SYMBOL_NS_GPL(mq_dump_common, "NET_SCHED_INTERNAL"); -- cgit v1.2.3 From b588019e85f490696bf19f0747e93f14b1563927 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Mon, 11 May 2026 07:02:44 +0000 Subject: rtnetlink: add RTEXT_FILTER_NAME_ONLY support iproute2 can spend considerable amount of time in ll_init_map() or ll_link_get() to dump verbose netdev attributes, contributing to RTNL pressure. Add RTEXT_FILTER_NAME_ONLY new flag so that rtnl_fill_ifinfo() limits its output to: - struct nlmsghdr - IFLA_IFNAME - IFLA_PROP_LIST (alternate names) We can later avoid using RTNL when RTEXT_FILTER_NAME_ONLY is requested, as none of these attributes need RTNL. Signed-off-by: Eric Dumazet Reviewed-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260511070244.971028-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- include/uapi/linux/rtnetlink.h | 1 + net/core/rtnetlink.c | 31 ++++++++++++++++++++++--------- 2 files changed, 23 insertions(+), 9 deletions(-) (limited to 'include') diff --git a/include/uapi/linux/rtnetlink.h b/include/uapi/linux/rtnetlink.h index dab9493c791b..27265fd31e5f 100644 --- a/include/uapi/linux/rtnetlink.h +++ b/include/uapi/linux/rtnetlink.h @@ -840,6 +840,7 @@ enum { #define RTEXT_FILTER_CFM_CONFIG (1 << 5) #define RTEXT_FILTER_CFM_STATUS (1 << 6) #define RTEXT_FILTER_MST (1 << 7) +#define RTEXT_FILTER_NAME_ONLY (1 << 8) /* End of information exported to user level */ diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c index df042da422ef..70fde922b371 100644 --- a/net/core/rtnetlink.c +++ b/net/core/rtnetlink.c @@ -1295,7 +1295,12 @@ static noinline size_t if_nlmsg_size(const struct net_device *dev, size = NLMSG_ALIGN(sizeof(struct ifinfomsg)) + nla_total_size(IFNAMSIZ) /* IFLA_IFNAME */ - + nla_total_size(IFALIASZ) /* IFLA_IFALIAS */ + + rtnl_prop_list_size(dev); + + if (ext_filter_mask & RTEXT_FILTER_NAME_ONLY) + return size; + + size += nla_total_size(IFALIASZ) /* IFLA_IFALIAS */ + nla_total_size(IFNAMSIZ) /* IFLA_QDISC */ + nla_total_size_64bit(sizeof(struct rtnl_link_ifmap)) + nla_total_size(MAX_ADDR_LEN) /* IFLA_ADDRESS */ @@ -1342,7 +1347,6 @@ static noinline size_t if_nlmsg_size(const struct net_device *dev, + nla_total_size(4) /* IFLA_CARRIER_DOWN_COUNT */ + nla_total_size(4) /* IFLA_MIN_MTU */ + nla_total_size(4) /* IFLA_MAX_MTU */ - + rtnl_prop_list_size(dev) + nla_total_size(MAX_ADDR_LEN) /* IFLA_PERM_ADDRESS */ + rtnl_devlink_port_size(dev) + rtnl_dpll_pin_size(dev) @@ -1941,15 +1945,18 @@ static int rtnl_fill_alt_ifnames(struct sk_buff *skb, struct netdev_name_node *name_node; int count = 0; + rcu_read_lock(); list_for_each_entry_rcu(name_node, &dev->name_node->list, list) { - if (nla_put_string(skb, IFLA_ALT_IFNAME, name_node->name)) + if (nla_put_string(skb, IFLA_ALT_IFNAME, name_node->name)) { + rcu_read_unlock(); return -EMSGSIZE; + } count++; } + rcu_read_unlock(); return count; } -/* RCU protected. */ static int rtnl_fill_prop_list(struct sk_buff *skb, const struct net_device *dev) { @@ -2072,13 +2079,20 @@ static int rtnl_fill_ifinfo(struct sk_buff *skb, ifm->ifi_flags = netif_get_flags(dev); ifm->ifi_change = change; - if (tgt_netnsid >= 0 && nla_put_s32(skb, IFLA_TARGET_NETNSID, tgt_netnsid)) - goto nla_put_failure; - netdev_copy_name(dev, devname); if (nla_put_string(skb, IFLA_IFNAME, devname)) goto nla_put_failure; + if (rtnl_fill_prop_list(skb, dev)) + goto nla_put_failure; + + if (ext_filter_mask & RTEXT_FILTER_NAME_ONLY) + goto end; + + if (tgt_netnsid >= 0 && + nla_put_s32(skb, IFLA_TARGET_NETNSID, tgt_netnsid)) + goto nla_put_failure; + if (nla_put_u32(skb, IFLA_TXQLEN, READ_ONCE(dev->tx_queue_len)) || nla_put_u8(skb, IFLA_OPERSTATE, netif_running(dev) ? READ_ONCE(dev->operstate) : @@ -2191,8 +2205,6 @@ static int rtnl_fill_ifinfo(struct sk_buff *skb, goto nla_put_failure_rcu; if (rtnl_fill_link_ifmap(skb, dev)) goto nla_put_failure_rcu; - if (rtnl_fill_prop_list(skb, dev)) - goto nla_put_failure_rcu; rcu_read_unlock(); if (dev->dev.parent && @@ -2211,6 +2223,7 @@ static int rtnl_fill_ifinfo(struct sk_buff *skb, if (rtnl_fill_dpll_pin(skb, dev)) goto nla_put_failure; +end: nlmsg_end(skb, nlh); return 0; -- cgit v1.2.3 From 5c4b709b5fc1929ed3e9d3ef48c089d7df8b8dff Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Mon, 11 May 2026 07:23:08 +0000 Subject: net: constify sk_skb_reason_drop() sock parameter sk_skb_reason_drop() does not change sock parameter, make it const so that we can call it from TCP stack without a cast on a (const) listener socket. Signed-off-by: Eric Dumazet Reviewed-by: David Ahern Reviewed-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260511072310.1094859-2-edumazet@google.com Signed-off-by: Jakub Kicinski --- include/linux/skbuff.h | 3 ++- include/trace/events/skb.h | 4 ++-- net/core/drop_monitor.c | 6 +++--- net/core/skbuff.c | 5 +++-- 4 files changed, 10 insertions(+), 8 deletions(-) (limited to 'include') diff --git a/include/linux/skbuff.h b/include/linux/skbuff.h index 2bcf78a4de7b..746e741a8ef9 100644 --- a/include/linux/skbuff.h +++ b/include/linux/skbuff.h @@ -1313,7 +1313,8 @@ static inline bool skb_data_unref(const struct sk_buff *skb, return true; } -void __fix_address sk_skb_reason_drop(struct sock *sk, struct sk_buff *skb, +void __fix_address sk_skb_reason_drop(const struct sock *sk, + struct sk_buff *skb, enum skb_drop_reason reason); static inline void diff --git a/include/trace/events/skb.h b/include/trace/events/skb.h index b877133cd93a..2945aa7fe9a7 100644 --- a/include/trace/events/skb.h +++ b/include/trace/events/skb.h @@ -24,14 +24,14 @@ DEFINE_DROP_REASON(FN, FN) TRACE_EVENT(kfree_skb, TP_PROTO(struct sk_buff *skb, void *location, - enum skb_drop_reason reason, struct sock *rx_sk), + enum skb_drop_reason reason, const struct sock *rx_sk), TP_ARGS(skb, location, reason, rx_sk), TP_STRUCT__entry( __field(void *, skbaddr) __field(void *, location) - __field(void *, rx_sk) + __field(const void *, rx_sk) __field(unsigned short, protocol) __field(enum skb_drop_reason, reason) ), diff --git a/net/core/drop_monitor.c b/net/core/drop_monitor.c index f23cea9e1aaf..2bf3cab5e557 100644 --- a/net/core/drop_monitor.c +++ b/net/core/drop_monitor.c @@ -110,7 +110,7 @@ struct net_dm_alert_ops { void (*kfree_skb_probe)(void *ignore, struct sk_buff *skb, void *location, enum skb_drop_reason reason, - struct sock *rx_sk); + const struct sock *rx_sk); void (*napi_poll_probe)(void *ignore, struct napi_struct *napi, int work, int budget); void (*work_item_func)(struct work_struct *work); @@ -266,7 +266,7 @@ out: static void trace_kfree_skb_hit(void *ignore, struct sk_buff *skb, void *location, enum skb_drop_reason reason, - struct sock *rx_sk) + const struct sock *rx_sk) { trace_drop_common(skb, location); } @@ -493,7 +493,7 @@ static void net_dm_packet_trace_kfree_skb_hit(void *ignore, struct sk_buff *skb, void *location, enum skb_drop_reason reason, - struct sock *rx_sk) + const struct sock *rx_sk) { ktime_t tstamp = ktime_get_real(); struct per_cpu_dm_data *data; diff --git a/net/core/skbuff.c b/net/core/skbuff.c index 7dad68e3b518..acca1365672c 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -1206,7 +1206,7 @@ void __kfree_skb(struct sk_buff *skb) EXPORT_SYMBOL(__kfree_skb); static __always_inline -bool __sk_skb_reason_drop(struct sock *sk, struct sk_buff *skb, +bool __sk_skb_reason_drop(const struct sock *sk, struct sk_buff *skb, enum skb_drop_reason reason) { if (unlikely(!skb_unref(skb))) @@ -1235,7 +1235,8 @@ bool __sk_skb_reason_drop(struct sock *sk, struct sk_buff *skb, * 'kfree_skb' tracepoint. */ void __fix_address -sk_skb_reason_drop(struct sock *sk, struct sk_buff *skb, enum skb_drop_reason reason) +sk_skb_reason_drop(const struct sock *sk, struct sk_buff *skb, + enum skb_drop_reason reason) { if (__sk_skb_reason_drop(sk, skb, reason)) __kfree_skb(skb); -- cgit v1.2.3 From 9c11fcb2e9a54d0f1467380831e2e4bb68f7498d Mon Sep 17 00:00:00 2001 From: Ivan Vecera Date: Mon, 11 May 2026 17:58:15 +0200 Subject: dpll: add fractional frequency offset to pin-parent-device Add both fractional-frequency-offset (PPM) and fractional-frequency-offset-ppt (PPT) attributes to the pin-parent-device nested attribute set, alongside the existing top-level pin attributes. Both carry the same measurement at different precisions. Introduce enum dpll_ffo_type and struct dpll_ffo_param to distinguish FFO contexts: DPLL_FFO_PORT_RXTX_RATE for the RX vs TX symbol rate offset reported at the top level, and DPLL_FFO_PIN_DEVICE for the pin vs parent DPLL offset reported in the pin-parent-device nest. Add a supported_ffo bitmask to struct dpll_pin_ops so drivers declare which FFO types they support. The core only calls ffo_get for types the driver has opted into, eliminating the need for per-driver NULL pointer guards. Validate at pin registration time that supported_ffo is not set without an ffo_get callback. Update mlx5 (DPLL_FFO_PORT_RXTX_RATE) and zl3073x (DPLL_FFO_PORT_RXTX_RATE) drivers to use the new API. Add documentation for both FFO types to dpll.rst. Changes v3 -> v4: - Replace dpll=NULL overloading with enum dpll_ffo_type and struct dpll_ffo_param (Jakub Kicinski) - Add supported_ffo opt-in bitmask in dpll_pin_ops for fail-close driver validation (Jakub Kicinski) - Add WARN_ON in dpll_pin_register for supported_ffo without ffo_get callback Reviewed-by: Jiri Pirko Signed-off-by: Ivan Vecera Link: https://patch.msgid.link/20260511155816.99936-2-ivecera@redhat.com Signed-off-by: Jakub Kicinski --- Documentation/driver-api/dpll.rst | 20 +++++++++++++++++ Documentation/netlink/specs/dpll.yaml | 28 +++++++++++++++--------- drivers/dpll/dpll_core.c | 3 ++- drivers/dpll/dpll_netlink.c | 30 ++++++++++++++------------ drivers/dpll/dpll_nl.c | 2 ++ drivers/dpll/zl3073x/dpll.c | 6 ++++-- drivers/net/ethernet/mellanox/mlx5/core/dpll.c | 6 ++++-- include/linux/dpll.h | 16 +++++++++++++- 8 files changed, 81 insertions(+), 30 deletions(-) (limited to 'include') diff --git a/Documentation/driver-api/dpll.rst b/Documentation/driver-api/dpll.rst index 37eaef785e30..bae14766d4f7 100644 --- a/Documentation/driver-api/dpll.rst +++ b/Documentation/driver-api/dpll.rst @@ -258,6 +258,26 @@ in the ``DPLL_A_PIN_PHASE_OFFSET`` attribute. ``DPLL_A_PHASE_OFFSET_MONITOR`` attr state of a feature =============================== ======================== +Fractional frequency offset +=========================== + +The fractional frequency offset (FFO) is reported through two attributes +that carry the same measurement at different precisions: + +- ``DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET`` in PPM (parts per million) +- ``DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET_PPT`` in PPT (parts per trillion) + +Both attributes appear at the top level of a pin and inside each +``pin-parent-device`` nest. Two FFO types are defined: + +- ``DPLL_FFO_PORT_RXTX_RATE`` - RX vs TX symbol rate offset (top-level) +- ``DPLL_FFO_PIN_DEVICE`` - pin vs parent DPLL offset (per-parent) + +The driver declares which types it supports via the ``supported_ffo`` +bitmask in ``struct dpll_pin_ops``. The core only calls the ``ffo_get`` +callback for types the driver has opted into. The requested type is +passed to the driver in the ``struct dpll_ffo_param``. + Frequency monitor ================= diff --git a/Documentation/netlink/specs/dpll.yaml b/Documentation/netlink/specs/dpll.yaml index c45de70a47ce..91a172617b3a 100644 --- a/Documentation/netlink/specs/dpll.yaml +++ b/Documentation/netlink/specs/dpll.yaml @@ -448,12 +448,14 @@ attribute-sets: name: fractional-frequency-offset type: sint doc: | - The FFO (Fractional Frequency Offset) between the RX and TX - symbol rate on the media associated with the pin: - (rx_frequency-tx_frequency)/rx_frequency + The FFO (Fractional Frequency Offset) of the pin. + At top level this represents the RX vs TX symbol rate + offset on the media associated with the pin. Inside + the pin-parent-device nest it represents the frequency + offset between the pin and its parent DPLL device. Value is in PPM (parts per million). - This may be implemented for example for pin of type - PIN_TYPE_SYNCE_ETH_PORT. + This is a lower-precision version of + fractional-frequency-offset-ppt. - name: esync-frequency type: u64 @@ -492,12 +494,14 @@ attribute-sets: name: fractional-frequency-offset-ppt type: sint doc: | - The FFO (Fractional Frequency Offset) of the pin with respect to - the nominal frequency. - Value = (frequency_measured - frequency_nominal) / frequency_nominal + The FFO (Fractional Frequency Offset) of the pin. + At top level this represents the RX vs TX symbol rate + offset on the media associated with the pin. Inside + the pin-parent-device nest it represents the frequency + offset between the pin and its parent DPLL device. Value is in PPT (parts per trillion, 10^-12). - Note: This attribute provides higher resolution than the standard - fractional-frequency-offset (which is in PPM). + This is a higher-precision version of + fractional-frequency-offset. - name: measured-frequency type: u64 @@ -534,6 +538,10 @@ attribute-sets: name: operstate - name: phase-offset + - + name: fractional-frequency-offset + - + name: fractional-frequency-offset-ppt - name: pin-parent-pin subset-of: pin diff --git a/drivers/dpll/dpll_core.c b/drivers/dpll/dpll_core.c index cbb635db4321..20a54728549c 100644 --- a/drivers/dpll/dpll_core.c +++ b/drivers/dpll/dpll_core.c @@ -879,7 +879,8 @@ dpll_pin_register(struct dpll_device *dpll, struct dpll_pin *pin, WARN_ON(!ops->direction_get) || WARN_ON(ops->measured_freq_get && (!dpll_device_ops(dpll)->freq_monitor_get || - !dpll_device_ops(dpll)->freq_monitor_set))) + !dpll_device_ops(dpll)->freq_monitor_set)) || + WARN_ON(ops->supported_ffo && !ops->ffo_get)) return -EINVAL; mutex_lock(&dpll_lock); diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c index ea6360263786..53ca24d0f191 100644 --- a/drivers/dpll/dpll_netlink.c +++ b/drivers/dpll/dpll_netlink.c @@ -417,31 +417,28 @@ dpll_msg_add_phase_offset(struct sk_buff *msg, struct dpll_pin *pin, static int dpll_msg_add_ffo(struct sk_buff *msg, struct dpll_pin *pin, struct dpll_pin_ref *ref, + enum dpll_ffo_type type, struct netlink_ext_ack *extack) { const struct dpll_pin_ops *ops = dpll_pin_ops(ref); - struct dpll_device *dpll = ref->dpll; - s64 ffo; + struct dpll_ffo_param ffo = { .type = type }; int ret; - if (!ops->ffo_get) + if (!ops->ffo_get || !(ops->supported_ffo & BIT(type))) return 0; - ret = ops->ffo_get(pin, dpll_pin_on_dpll_priv(dpll, pin), - dpll, dpll_priv(dpll), &ffo, extack); + ret = ops->ffo_get(pin, dpll_pin_on_dpll_priv(ref->dpll, pin), + ref->dpll, dpll_priv(ref->dpll), &ffo, extack); if (ret) { if (ret == -ENODATA) return 0; return ret; } - /* Put the FFO value in PPM to preserve compatibility with older - * programs. - */ - ret = nla_put_sint(msg, DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET, - div_s64(ffo, 1000000)); - if (ret) + if (nla_put_sint(msg, DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET, + div_s64(ffo.ffo, 1000000))) return -EMSGSIZE; - return nla_put_sint(msg, DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET_PPT, - ffo); + return nla_put_sint(msg, + DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET_PPT, + ffo.ffo); } static int dpll_msg_add_measured_freq(struct sk_buff *msg, struct dpll_pin *pin, @@ -686,6 +683,10 @@ dpll_msg_add_pin_dplls(struct sk_buff *msg, struct dpll_pin *pin, if (ret) goto nest_cancel; ret = dpll_msg_add_phase_offset(msg, pin, ref, extack); + if (ret) + goto nest_cancel; + ret = dpll_msg_add_ffo(msg, pin, ref, + DPLL_FFO_PIN_DEVICE, extack); if (ret) goto nest_cancel; nla_nest_end(msg, attr); @@ -748,7 +749,8 @@ dpll_cmd_pin_get_one(struct sk_buff *msg, struct dpll_pin *pin, ret = dpll_msg_add_pin_phase_adjust(msg, pin, ref, extack); if (ret) return ret; - ret = dpll_msg_add_ffo(msg, pin, ref, extack); + ret = dpll_msg_add_ffo(msg, pin, ref, + DPLL_FFO_PORT_RXTX_RATE, extack); if (ret) return ret; ret = dpll_msg_add_measured_freq(msg, pin, ref, extack); diff --git a/drivers/dpll/dpll_nl.c b/drivers/dpll/dpll_nl.c index 58235845fa3d..b1d9182c7802 100644 --- a/drivers/dpll/dpll_nl.c +++ b/drivers/dpll/dpll_nl.c @@ -19,6 +19,8 @@ const struct nla_policy dpll_pin_parent_device_nl_policy[DPLL_A_PIN_OPERSTATE + [DPLL_A_PIN_STATE] = NLA_POLICY_RANGE(NLA_U32, 1, 3), [DPLL_A_PIN_OPERSTATE] = NLA_POLICY_RANGE(NLA_U32, 1, 4), [DPLL_A_PIN_PHASE_OFFSET] = { .type = NLA_S64, }, + [DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET] = { .type = NLA_SINT, }, + [DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET_PPT] = { .type = NLA_SINT, }, }; const struct nla_policy dpll_pin_parent_pin_nl_policy[DPLL_A_PIN_STATE + 1] = { diff --git a/drivers/dpll/zl3073x/dpll.c b/drivers/dpll/zl3073x/dpll.c index 6fd718696de0..05e63661bf07 100644 --- a/drivers/dpll/zl3073x/dpll.c +++ b/drivers/dpll/zl3073x/dpll.c @@ -295,11 +295,12 @@ zl3073x_dpll_input_pin_ref_sync_set(const struct dpll_pin *dpll_pin, static int zl3073x_dpll_input_pin_ffo_get(const struct dpll_pin *dpll_pin, void *pin_priv, const struct dpll_device *dpll, void *dpll_priv, - s64 *ffo, struct netlink_ext_ack *extack) + struct dpll_ffo_param *ffo, + struct netlink_ext_ack *extack) { struct zl3073x_dpll_pin *pin = pin_priv; - *ffo = pin->freq_offset; + ffo->ffo = pin->freq_offset; return 0; } @@ -1274,6 +1275,7 @@ zl3073x_dpll_freq_monitor_set(const struct dpll_device *dpll, } static const struct dpll_pin_ops zl3073x_dpll_input_pin_ops = { + .supported_ffo = BIT(DPLL_FFO_PORT_RXTX_RATE), .direction_get = zl3073x_dpll_pin_direction_get, .esync_get = zl3073x_dpll_input_pin_esync_get, .esync_set = zl3073x_dpll_input_pin_esync_set, diff --git a/drivers/net/ethernet/mellanox/mlx5/core/dpll.c b/drivers/net/ethernet/mellanox/mlx5/core/dpll.c index bce72e8d1bc3..7c69d9029bfa 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/dpll.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/dpll.c @@ -300,7 +300,8 @@ static int mlx5_dpll_state_on_dpll_set(const struct dpll_pin *pin, static int mlx5_dpll_ffo_get(const struct dpll_pin *pin, void *pin_priv, const struct dpll_device *dpll, void *dpll_priv, - s64 *ffo, struct netlink_ext_ack *extack) + struct dpll_ffo_param *ffo, + struct netlink_ext_ack *extack) { struct mlx5_dpll_synce_status synce_status; struct mlx5_dpll *mdpll = pin_priv; @@ -309,10 +310,11 @@ static int mlx5_dpll_ffo_get(const struct dpll_pin *pin, void *pin_priv, err = mlx5_dpll_synce_status_get(mdpll->mdev, &synce_status); if (err) return err; - return mlx5_dpll_pin_ffo_get(&synce_status, ffo); + return mlx5_dpll_pin_ffo_get(&synce_status, &ffo->ffo); } static const struct dpll_pin_ops mlx5_dpll_pins_ops = { + .supported_ffo = BIT(DPLL_FFO_PORT_RXTX_RATE), .direction_get = mlx5_dpll_pin_direction_get, .state_on_dpll_get = mlx5_dpll_state_on_dpll_get, .state_on_dpll_set = mlx5_dpll_state_on_dpll_set, diff --git a/include/linux/dpll.h b/include/linux/dpll.h index 193abfb6ce66..82dfadd9f593 100644 --- a/include/linux/dpll.h +++ b/include/linux/dpll.h @@ -60,7 +60,20 @@ struct dpll_device_ops { struct netlink_ext_ack *extack); }; +enum dpll_ffo_type { + DPLL_FFO_PORT_RXTX_RATE, + DPLL_FFO_PIN_DEVICE, + + __DPLL_FFO_TYPE_MAX, +}; + +struct dpll_ffo_param { + enum dpll_ffo_type type; + s64 ffo; +}; + struct dpll_pin_ops { + unsigned long supported_ffo; int (*frequency_set)(const struct dpll_pin *pin, void *pin_priv, const struct dpll_device *dpll, void *dpll_priv, const u64 frequency, @@ -121,7 +134,8 @@ struct dpll_pin_ops { struct netlink_ext_ack *extack); int (*ffo_get)(const struct dpll_pin *pin, void *pin_priv, const struct dpll_device *dpll, void *dpll_priv, - s64 *ffo, struct netlink_ext_ack *extack); + struct dpll_ffo_param *ffo, + struct netlink_ext_ack *extack); int (*measured_freq_get)(const struct dpll_pin *pin, void *pin_priv, const struct dpll_device *dpll, void *dpll_priv, u64 *measured_freq, -- cgit v1.2.3 From baf808fe4fcd35767ab732b4ab2ea80dabfd97a6 Mon Sep 17 00:00:00 2001 From: Simon Schippers Date: Sun, 10 May 2026 17:15:27 +0200 Subject: vhost-net: wake queue of tun/tap after ptr_ring consume Add tun_wake_queue() to tun.c and export it for use by vhost-net. The function validates that the file belongs to a tun/tap device and that the tfile exists, dereferences the tun_struct under RCU, and delegates to __tun_wake_queue(). vhost_net_buf_produce() now calls tun_wake_queue() after a successful batched consume of the ring to allow the netdev subqueue to be woken up. The point is to allow the queue to be stopped when it gets full, which is required for traffic shaping - implemented by the following "avoid ptr_ring tail-drop when a qdisc is present". Without the corresponding queue stopping, this patch alone causes no throughput regression for a tap+vhost-net setup sending to a qemu VM: 3.857 Mpps to 3.891 Mpps. Details: AMD Ryzen 5 5600X at 4.3 GHz, 3200 MHz RAM, isolated QEMU threads, XDP drop program active in VM, pktgen sender; Avg over 50 runs @ 100,000,000 packets. SRSO and spectre v2 mitigations disabled. Co-developed-by: Tim Gebauer Signed-off-by: Tim Gebauer Signed-off-by: Simon Schippers Acked-by: Michael S. Tsirkin Link: https://patch.msgid.link/20260510151529.43895-3-simon.schippers@tu-dortmund.de Signed-off-by: Jakub Kicinski --- drivers/net/tun.c | 23 +++++++++++++++++++++++ drivers/vhost/net.c | 21 +++++++++++++++------ include/linux/if_tun.h | 3 +++ 3 files changed, 41 insertions(+), 6 deletions(-) (limited to 'include') diff --git a/drivers/net/tun.c b/drivers/net/tun.c index 3dded7c7d12d..cbec66646a4b 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -3783,6 +3783,29 @@ struct ptr_ring *tun_get_tx_ring(struct file *file) } EXPORT_SYMBOL_GPL(tun_get_tx_ring); +/* Callers must hold ring.consumer_lock */ +void tun_wake_queue(struct file *file, int consumed) +{ + struct tun_file *tfile; + struct tun_struct *tun; + + if (file->f_op != &tun_fops) + return; + + tfile = file->private_data; + if (!tfile) + return; + + rcu_read_lock(); + + tun = rcu_dereference(tfile->tun); + if (tun) + __tun_wake_queue(tun, tfile, consumed); + + rcu_read_unlock(); +} +EXPORT_SYMBOL_GPL(tun_wake_queue); + module_init(tun_init); module_exit(tun_cleanup); MODULE_DESCRIPTION(DRV_DESCRIPTION); diff --git a/drivers/vhost/net.c b/drivers/vhost/net.c index c6536cad9c4f..db341c922673 100644 --- a/drivers/vhost/net.c +++ b/drivers/vhost/net.c @@ -176,13 +176,21 @@ static void *vhost_net_buf_consume(struct vhost_net_buf *rxq) return ret; } -static int vhost_net_buf_produce(struct vhost_net_virtqueue *nvq) +static int vhost_net_buf_produce(struct sock *sk, + struct vhost_net_virtqueue *nvq) { + struct file *file = sk->sk_socket->file; struct vhost_net_buf *rxq = &nvq->rxq; rxq->head = 0; - rxq->tail = ptr_ring_consume_batched(nvq->rx_ring, rxq->queue, - VHOST_NET_BATCH); + spin_lock(&nvq->rx_ring->consumer_lock); + rxq->tail = __ptr_ring_consume_batched(nvq->rx_ring, rxq->queue, + VHOST_NET_BATCH); + + if (rxq->tail) + tun_wake_queue(file, rxq->tail); + + spin_unlock(&nvq->rx_ring->consumer_lock); return rxq->tail; } @@ -209,14 +217,15 @@ static int vhost_net_buf_peek_len(void *ptr) return __skb_array_len_with_tag(ptr); } -static int vhost_net_buf_peek(struct vhost_net_virtqueue *nvq) +static int vhost_net_buf_peek(struct sock *sk, + struct vhost_net_virtqueue *nvq) { struct vhost_net_buf *rxq = &nvq->rxq; if (!vhost_net_buf_is_empty(rxq)) goto out; - if (!vhost_net_buf_produce(nvq)) + if (!vhost_net_buf_produce(sk, nvq)) return 0; out: @@ -995,7 +1004,7 @@ static int peek_head_len(struct vhost_net_virtqueue *rvq, struct sock *sk) unsigned long flags; if (rvq->rx_ring) - return vhost_net_buf_peek(rvq); + return vhost_net_buf_peek(sk, rvq); spin_lock_irqsave(&sk->sk_receive_queue.lock, flags); head = skb_peek(&sk->sk_receive_queue); diff --git a/include/linux/if_tun.h b/include/linux/if_tun.h index 80166eb62f41..5f3e206c7a73 100644 --- a/include/linux/if_tun.h +++ b/include/linux/if_tun.h @@ -22,6 +22,7 @@ struct tun_msg_ctl { #if defined(CONFIG_TUN) || defined(CONFIG_TUN_MODULE) struct socket *tun_get_socket(struct file *); struct ptr_ring *tun_get_tx_ring(struct file *file); +void tun_wake_queue(struct file *file, int consumed); static inline bool tun_is_xdp_frame(void *ptr) { @@ -55,6 +56,8 @@ static inline struct ptr_ring *tun_get_tx_ring(struct file *f) return ERR_PTR(-EINVAL); } +static inline void tun_wake_queue(struct file *f, int consumed) {} + static inline bool tun_is_xdp_frame(void *ptr) { return false; -- cgit v1.2.3 From fba362c17d9d9211fc51f272156bb84fc23bdf98 Mon Sep 17 00:00:00 2001 From: Simon Schippers Date: Sun, 10 May 2026 17:15:28 +0200 Subject: ptr_ring: move free-space check into separate helper This patch moves the check for available free space for a new entry into a separate function. Existing callers that only check for a non-zero return value are unaffected; __ptr_ring_produce() now returns -EINVAL for a zero-size ring and -ENOSPC when full, whereas before both cases returned -ENOSPC. The new helper allows callers to determine in advance whether subsequent __ptr_ring_produce() calls will succeed. This information can, for example, be used to temporarily stop producing until __ptr_ring_check_produce() indicates that space is available again. Co-developed-by: Tim Gebauer Signed-off-by: Tim Gebauer Signed-off-by: Simon Schippers Acked-by: Michael S. Tsirkin Link: https://patch.msgid.link/20260510151529.43895-4-simon.schippers@tu-dortmund.de Signed-off-by: Jakub Kicinski --- include/linux/ptr_ring.h | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/linux/ptr_ring.h b/include/linux/ptr_ring.h index d2c3629bbe45..c95e891903f0 100644 --- a/include/linux/ptr_ring.h +++ b/include/linux/ptr_ring.h @@ -96,6 +96,20 @@ static inline bool ptr_ring_full_bh(struct ptr_ring *r) return ret; } +/* Note: callers invoking this in a loop must use a compiler barrier, + * for example cpu_relax(). Callers must hold producer_lock. + */ +static inline int __ptr_ring_check_produce(struct ptr_ring *r) +{ + if (unlikely(!r->size)) + return -EINVAL; + + if (data_race(r->queue[r->producer])) + return -ENOSPC; + + return 0; +} + /* Note: callers invoking this in a loop must use a compiler barrier, * for example cpu_relax(). Callers must hold producer_lock. * Callers are responsible for making sure pointer that is being queued @@ -103,8 +117,10 @@ static inline bool ptr_ring_full_bh(struct ptr_ring *r) */ static inline int __ptr_ring_produce(struct ptr_ring *r, void *ptr) { - if (unlikely(!r->size) || data_race(r->queue[r->producer])) - return -ENOSPC; + int p = __ptr_ring_check_produce(r); + + if (p) + return p; /* Make sure the pointer we are storing points to a valid data. */ /* Pairs with the dependency ordering in __ptr_ring_consume. */ -- cgit v1.2.3 From 06baf87e219fa7f396c7dc149d6f5ffde922dfa2 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Tue, 12 May 2026 03:46:34 -0700 Subject: netpoll: expose UDP packet builder helpers for netconsole Promote each from file-static to EXPORT_SYMBOL_GPL and forward- declare them in include/linux/netpoll.h so netconsole can call them once netpoll_send_udp() moves out. These exports are kept until the end of the series, when al of them move into netconsole. No functional change. Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20260512-netconsole_split-v2-1-1191d14ad66d@debian.org Signed-off-by: Jakub Kicinski --- include/linux/netpoll.h | 6 ++++++ net/core/netpoll.c | 20 +++++++++++++------- 2 files changed, 19 insertions(+), 7 deletions(-) (limited to 'include') diff --git a/include/linux/netpoll.h b/include/linux/netpoll.h index f22eec466040..e68c80b32941 100644 --- a/include/linux/netpoll.h +++ b/include/linux/netpoll.h @@ -74,6 +74,12 @@ void __netpoll_free(struct netpoll *np); void netpoll_cleanup(struct netpoll *np); void do_netpoll_cleanup(struct netpoll *np); netdev_tx_t netpoll_send_skb(struct netpoll *np, struct sk_buff *skb); +struct sk_buff *find_skb(struct netpoll *np, int len, int reserve); +void netpoll_udp_checksum(struct netpoll *np, struct sk_buff *skb, int len); +void push_ipv6(struct netpoll *np, struct sk_buff *skb, int len); +void push_ipv4(struct netpoll *np, struct sk_buff *skb, int len); +void push_udp(struct netpoll *np, struct sk_buff *skb, int len); +void push_eth(struct netpoll *np, struct sk_buff *skb); #ifdef CONFIG_NETPOLL static inline void *netpoll_poll_lock(struct napi_struct *napi) diff --git a/net/core/netpoll.c b/net/core/netpoll.c index 84faace50ac2..41305056c56f 100644 --- a/net/core/netpoll.c +++ b/net/core/netpoll.c @@ -268,7 +268,7 @@ static void zap_completion_queue(void) put_cpu_var(softnet_data); } -static struct sk_buff *find_skb(struct netpoll *np, int len, int reserve) +struct sk_buff *find_skb(struct netpoll *np, int len, int reserve) { int count = 0; struct sk_buff *skb; @@ -294,6 +294,7 @@ repeat: skb_reserve(skb, reserve); return skb; } +EXPORT_SYMBOL_GPL(find_skb); static int netpoll_owner_active(struct net_device *dev) { @@ -369,8 +370,8 @@ out: return ret; } -static void netpoll_udp_checksum(struct netpoll *np, struct sk_buff *skb, - int len) +void netpoll_udp_checksum(struct netpoll *np, struct sk_buff *skb, + int len) { struct udphdr *udph; int udp_len; @@ -393,6 +394,7 @@ static void netpoll_udp_checksum(struct netpoll *np, struct sk_buff *skb, if (udph->check == 0) udph->check = CSUM_MANGLED_0; } +EXPORT_SYMBOL_GPL(netpoll_udp_checksum); netdev_tx_t netpoll_send_skb(struct netpoll *np, struct sk_buff *skb) { @@ -411,7 +413,7 @@ netdev_tx_t netpoll_send_skb(struct netpoll *np, struct sk_buff *skb) } EXPORT_SYMBOL(netpoll_send_skb); -static void push_ipv6(struct netpoll *np, struct sk_buff *skb, int len) +void push_ipv6(struct netpoll *np, struct sk_buff *skb, int len) { struct ipv6hdr *ip6h; @@ -433,8 +435,9 @@ static void push_ipv6(struct netpoll *np, struct sk_buff *skb, int len) skb->protocol = htons(ETH_P_IPV6); } +EXPORT_SYMBOL_GPL(push_ipv6); -static void push_ipv4(struct netpoll *np, struct sk_buff *skb, int len) +void push_ipv4(struct netpoll *np, struct sk_buff *skb, int len) { static atomic_t ip_ident; struct iphdr *iph; @@ -460,8 +463,9 @@ static void push_ipv4(struct netpoll *np, struct sk_buff *skb, int len) iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl); skb->protocol = htons(ETH_P_IP); } +EXPORT_SYMBOL_GPL(push_ipv4); -static void push_udp(struct netpoll *np, struct sk_buff *skb, int len) +void push_udp(struct netpoll *np, struct sk_buff *skb, int len) { struct udphdr *udph; int udp_len; @@ -478,8 +482,9 @@ static void push_udp(struct netpoll *np, struct sk_buff *skb, int len) netpoll_udp_checksum(np, skb, len); } +EXPORT_SYMBOL_GPL(push_udp); -static void push_eth(struct netpoll *np, struct sk_buff *skb) +void push_eth(struct netpoll *np, struct sk_buff *skb) { struct ethhdr *eth; @@ -492,6 +497,7 @@ static void push_eth(struct netpoll *np, struct sk_buff *skb) else eth->h_proto = htons(ETH_P_IP); } +EXPORT_SYMBOL_GPL(push_eth); int netpoll_send_udp(struct netpoll *np, const char *msg, int len) { -- cgit v1.2.3 From d3345a61e4ae50119d20cfa6a54cb8df0cc47dfa Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Tue, 12 May 2026 03:46:35 -0700 Subject: netconsole: move netpoll_send_udp() from netpoll Move netpoll_send_udp() from net/core/netpoll.c into drivers/net/netconsole.c as a static helper, drop EXPORT_SYMBOL(), and remove the prototype from include/linux/netpoll.h. netconsole was the only in-tree caller of this entry point. Every other netpoll consumer (bonding, team, vlan, bridge, macvlan, dsa) already builds its own sk_buff and hands it to netpoll_send_skb(), so the netpoll send-side interface is now skb-only. The helpers it depends on (find_skb(), push_ipv6(), push_ipv4(), push_udp(), push_eth(), netpoll_udp_checksum()) were exposed in the previous patches and stay in net/core/netpoll.c for now. Subsequent patches move each of them into netconsole one at a time and drop the corresponding EXPORT_SYMBOL_GPL. Pull , and into netconsole.c so the moved code can name the header structures. Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20260512-netconsole_split-v2-2-1191d14ad66d@debian.org Signed-off-by: Jakub Kicinski --- drivers/net/netconsole.c | 38 ++++++++++++++++++++++++++++++++++++++ include/linux/netpoll.h | 1 - net/core/netpoll.c | 37 ------------------------------------- 3 files changed, 38 insertions(+), 38 deletions(-) (limited to 'include') diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index 57dd6821a8aa..56f310041ebd 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -32,6 +32,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -1648,6 +1651,41 @@ static struct notifier_block netconsole_netdev_notifier = { .notifier_call = netconsole_netdev_event, }; +static int netpoll_send_udp(struct netpoll *np, const char *msg, int len) +{ + int total_len, ip_len, udp_len; + struct sk_buff *skb; + + if (!IS_ENABLED(CONFIG_PREEMPT_RT)) + WARN_ON_ONCE(!irqs_disabled()); + + udp_len = len + sizeof(struct udphdr); + if (np->ipv6) + ip_len = udp_len + sizeof(struct ipv6hdr); + else + ip_len = udp_len + sizeof(struct iphdr); + + total_len = ip_len + LL_RESERVED_SPACE(np->dev); + + skb = find_skb(np, total_len + np->dev->needed_tailroom, + total_len - len); + if (!skb) + return -ENOMEM; + + skb_copy_to_linear_data(skb, msg, len); + skb_put(skb, len); + + push_udp(np, skb, len); + if (np->ipv6) + push_ipv6(np, skb, len); + else + push_ipv4(np, skb, len); + push_eth(np, skb); + skb->dev = np->dev; + + return (int)netpoll_send_skb(np, skb); +} + /** * send_udp - Wrapper for netpoll_send_udp that counts errors * @nt: target to send message to diff --git a/include/linux/netpoll.h b/include/linux/netpoll.h index e68c80b32941..57be72bb82dc 100644 --- a/include/linux/netpoll.h +++ b/include/linux/netpoll.h @@ -67,7 +67,6 @@ static inline void netpoll_poll_disable(struct net_device *dev) { return; } static inline void netpoll_poll_enable(struct net_device *dev) { return; } #endif -int netpoll_send_udp(struct netpoll *np, const char *msg, int len); int __netpoll_setup(struct netpoll *np, struct net_device *ndev); int netpoll_setup(struct netpoll *np); void __netpoll_free(struct netpoll *np); diff --git a/net/core/netpoll.c b/net/core/netpoll.c index 41305056c56f..39209308efad 100644 --- a/net/core/netpoll.c +++ b/net/core/netpoll.c @@ -499,43 +499,6 @@ void push_eth(struct netpoll *np, struct sk_buff *skb) } EXPORT_SYMBOL_GPL(push_eth); -int netpoll_send_udp(struct netpoll *np, const char *msg, int len) -{ - int total_len, ip_len, udp_len; - struct sk_buff *skb; - - if (!IS_ENABLED(CONFIG_PREEMPT_RT)) - WARN_ON_ONCE(!irqs_disabled()); - - udp_len = len + sizeof(struct udphdr); - if (np->ipv6) - ip_len = udp_len + sizeof(struct ipv6hdr); - else - ip_len = udp_len + sizeof(struct iphdr); - - total_len = ip_len + LL_RESERVED_SPACE(np->dev); - - skb = find_skb(np, total_len + np->dev->needed_tailroom, - total_len - len); - if (!skb) - return -ENOMEM; - - skb_copy_to_linear_data(skb, msg, len); - skb_put(skb, len); - - push_udp(np, skb, len); - if (np->ipv6) - push_ipv6(np, skb, len); - else - push_ipv4(np, skb, len); - push_eth(np, skb); - skb->dev = np->dev; - - return (int)netpoll_send_skb(np, skb); -} -EXPORT_SYMBOL(netpoll_send_udp); - - static void skb_pool_flush(struct netpoll *np) { struct sk_buff_head *skb_pool; -- cgit v1.2.3 From c411baa463e85a779a7e68a00ba6298770b58c4c Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Tue, 12 May 2026 03:46:36 -0700 Subject: netconsole: move push_ipv6() from netpoll push_ipv6() builds the IPv6 header for netconsole's UDP packets. Its only caller, netpoll_send_udp(), now lives in netconsole, so the helper can move there as a file-static function. Drop its EXPORT_SYMBOL_GPL and remove the prototype from include/linux/netpoll.h. Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20260512-netconsole_split-v2-3-1191d14ad66d@debian.org Signed-off-by: Jakub Kicinski --- drivers/net/netconsole.c | 23 +++++++++++++++++++++++ include/linux/netpoll.h | 1 - net/core/netpoll.c | 24 ------------------------ 3 files changed, 23 insertions(+), 25 deletions(-) (limited to 'include') diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index 56f310041ebd..f805ae95ce7b 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -1651,6 +1651,29 @@ static struct notifier_block netconsole_netdev_notifier = { .notifier_call = netconsole_netdev_event, }; +static void push_ipv6(struct netpoll *np, struct sk_buff *skb, int len) +{ + struct ipv6hdr *ip6h; + + skb_push(skb, sizeof(struct ipv6hdr)); + skb_reset_network_header(skb); + ip6h = ipv6_hdr(skb); + + /* ip6h->version = 6; ip6h->priority = 0; */ + *(unsigned char *)ip6h = 0x60; + ip6h->flow_lbl[0] = 0; + ip6h->flow_lbl[1] = 0; + ip6h->flow_lbl[2] = 0; + + ip6h->payload_len = htons(sizeof(struct udphdr) + len); + ip6h->nexthdr = IPPROTO_UDP; + ip6h->hop_limit = 32; + ip6h->saddr = np->local_ip.in6; + ip6h->daddr = np->remote_ip.in6; + + skb->protocol = htons(ETH_P_IPV6); +} + static int netpoll_send_udp(struct netpoll *np, const char *msg, int len) { int total_len, ip_len, udp_len; diff --git a/include/linux/netpoll.h b/include/linux/netpoll.h index 57be72bb82dc..7f57bd0eac66 100644 --- a/include/linux/netpoll.h +++ b/include/linux/netpoll.h @@ -75,7 +75,6 @@ void do_netpoll_cleanup(struct netpoll *np); netdev_tx_t netpoll_send_skb(struct netpoll *np, struct sk_buff *skb); struct sk_buff *find_skb(struct netpoll *np, int len, int reserve); void netpoll_udp_checksum(struct netpoll *np, struct sk_buff *skb, int len); -void push_ipv6(struct netpoll *np, struct sk_buff *skb, int len); void push_ipv4(struct netpoll *np, struct sk_buff *skb, int len); void push_udp(struct netpoll *np, struct sk_buff *skb, int len); void push_eth(struct netpoll *np, struct sk_buff *skb); diff --git a/net/core/netpoll.c b/net/core/netpoll.c index 39209308efad..6a10ca5eaa09 100644 --- a/net/core/netpoll.c +++ b/net/core/netpoll.c @@ -413,30 +413,6 @@ netdev_tx_t netpoll_send_skb(struct netpoll *np, struct sk_buff *skb) } EXPORT_SYMBOL(netpoll_send_skb); -void push_ipv6(struct netpoll *np, struct sk_buff *skb, int len) -{ - struct ipv6hdr *ip6h; - - skb_push(skb, sizeof(struct ipv6hdr)); - skb_reset_network_header(skb); - ip6h = ipv6_hdr(skb); - - /* ip6h->version = 6; ip6h->priority = 0; */ - *(unsigned char *)ip6h = 0x60; - ip6h->flow_lbl[0] = 0; - ip6h->flow_lbl[1] = 0; - ip6h->flow_lbl[2] = 0; - - ip6h->payload_len = htons(sizeof(struct udphdr) + len); - ip6h->nexthdr = IPPROTO_UDP; - ip6h->hop_limit = 32; - ip6h->saddr = np->local_ip.in6; - ip6h->daddr = np->remote_ip.in6; - - skb->protocol = htons(ETH_P_IPV6); -} -EXPORT_SYMBOL_GPL(push_ipv6); - void push_ipv4(struct netpoll *np, struct sk_buff *skb, int len) { static atomic_t ip_ident; -- cgit v1.2.3 From 5664c3908b7083e5905bbaff2dd6566a81c367b1 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Tue, 12 May 2026 03:46:37 -0700 Subject: netconsole: move push_ipv4() from netpoll push_ipv4() builds the IPv4 header for netconsole's UDP packets. Move it into drivers/net/netconsole.c as a file-static helper; drop its EXPORT_SYMBOL_GPL and remove the prototype from include/linux/netpoll.h. put_unaligned() is no longer used in net/core/netpoll.c, so drop the now-stale include from there. Pull it into netconsole.c so the moved code keeps building. Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20260512-netconsole_split-v2-4-1191d14ad66d@debian.org Signed-off-by: Jakub Kicinski --- drivers/net/netconsole.c | 28 ++++++++++++++++++++++++++++ include/linux/netpoll.h | 1 - net/core/netpoll.c | 29 ----------------------------- 3 files changed, 28 insertions(+), 30 deletions(-) (limited to 'include') diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index f805ae95ce7b..84f90b53a10f 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -1651,6 +1652,33 @@ static struct notifier_block netconsole_netdev_notifier = { .notifier_call = netconsole_netdev_event, }; +static void push_ipv4(struct netpoll *np, struct sk_buff *skb, int len) +{ + static atomic_t ip_ident; + struct iphdr *iph; + int ip_len; + + ip_len = len + sizeof(struct udphdr) + sizeof(struct iphdr); + + skb_push(skb, sizeof(struct iphdr)); + skb_reset_network_header(skb); + iph = ip_hdr(skb); + + /* iph->version = 4; iph->ihl = 5; */ + *(unsigned char *)iph = 0x45; + iph->tos = 0; + put_unaligned(htons(ip_len), &iph->tot_len); + iph->id = htons(atomic_inc_return(&ip_ident)); + iph->frag_off = 0; + iph->ttl = 64; + iph->protocol = IPPROTO_UDP; + iph->check = 0; + put_unaligned(np->local_ip.ip, &iph->saddr); + put_unaligned(np->remote_ip.ip, &iph->daddr); + iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl); + skb->protocol = htons(ETH_P_IP); +} + static void push_ipv6(struct netpoll *np, struct sk_buff *skb, int len) { struct ipv6hdr *ip6h; diff --git a/include/linux/netpoll.h b/include/linux/netpoll.h index 7f57bd0eac66..1fcde9c87b8c 100644 --- a/include/linux/netpoll.h +++ b/include/linux/netpoll.h @@ -75,7 +75,6 @@ void do_netpoll_cleanup(struct netpoll *np); netdev_tx_t netpoll_send_skb(struct netpoll *np, struct sk_buff *skb); struct sk_buff *find_skb(struct netpoll *np, int len, int reserve); void netpoll_udp_checksum(struct netpoll *np, struct sk_buff *skb, int len); -void push_ipv4(struct netpoll *np, struct sk_buff *skb, int len); void push_udp(struct netpoll *np, struct sk_buff *skb, int len); void push_eth(struct netpoll *np, struct sk_buff *skb); diff --git a/net/core/netpoll.c b/net/core/netpoll.c index 6a10ca5eaa09..030424e43bfb 100644 --- a/net/core/netpoll.c +++ b/net/core/netpoll.c @@ -34,7 +34,6 @@ #include #include #include -#include #include #include @@ -413,34 +412,6 @@ netdev_tx_t netpoll_send_skb(struct netpoll *np, struct sk_buff *skb) } EXPORT_SYMBOL(netpoll_send_skb); -void push_ipv4(struct netpoll *np, struct sk_buff *skb, int len) -{ - static atomic_t ip_ident; - struct iphdr *iph; - int ip_len; - - ip_len = len + sizeof(struct udphdr) + sizeof(struct iphdr); - - skb_push(skb, sizeof(struct iphdr)); - skb_reset_network_header(skb); - iph = ip_hdr(skb); - - /* iph->version = 4; iph->ihl = 5; */ - *(unsigned char *)iph = 0x45; - iph->tos = 0; - put_unaligned(htons(ip_len), &iph->tot_len); - iph->id = htons(atomic_inc_return(&ip_ident)); - iph->frag_off = 0; - iph->ttl = 64; - iph->protocol = IPPROTO_UDP; - iph->check = 0; - put_unaligned(np->local_ip.ip, &iph->saddr); - put_unaligned(np->remote_ip.ip, &iph->daddr); - iph->check = ip_fast_csum((unsigned char *)iph, iph->ihl); - skb->protocol = htons(ETH_P_IP); -} -EXPORT_SYMBOL_GPL(push_ipv4); - void push_udp(struct netpoll *np, struct sk_buff *skb, int len) { struct udphdr *udph; -- cgit v1.2.3 From 6a44c82a48c74bf98b3d5adf8c4e9ef7a7dfe300 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Tue, 12 May 2026 03:46:38 -0700 Subject: netconsole: move push_eth() from netpoll push_eth() builds the Ethernet header for netconsole's UDP packets. Move it into drivers/net/netconsole.c as a file-static helper; drop its EXPORT_SYMBOL_GPL and remove the prototype from include/linux/netpoll.h. Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20260512-netconsole_split-v2-5-1191d14ad66d@debian.org Signed-off-by: Jakub Kicinski --- drivers/net/netconsole.c | 14 ++++++++++++++ include/linux/netpoll.h | 1 - net/core/netpoll.c | 15 --------------- 3 files changed, 14 insertions(+), 16 deletions(-) (limited to 'include') diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index 84f90b53a10f..bcedc161b13e 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -1652,6 +1652,20 @@ static struct notifier_block netconsole_netdev_notifier = { .notifier_call = netconsole_netdev_event, }; +static void push_eth(struct netpoll *np, struct sk_buff *skb) +{ + struct ethhdr *eth; + + eth = skb_push(skb, ETH_HLEN); + skb_reset_mac_header(skb); + ether_addr_copy(eth->h_source, np->dev->dev_addr); + ether_addr_copy(eth->h_dest, np->remote_mac); + if (np->ipv6) + eth->h_proto = htons(ETH_P_IPV6); + else + eth->h_proto = htons(ETH_P_IP); +} + static void push_ipv4(struct netpoll *np, struct sk_buff *skb, int len) { static atomic_t ip_ident; diff --git a/include/linux/netpoll.h b/include/linux/netpoll.h index 1fcde9c87b8c..11eb95b3248e 100644 --- a/include/linux/netpoll.h +++ b/include/linux/netpoll.h @@ -76,7 +76,6 @@ netdev_tx_t netpoll_send_skb(struct netpoll *np, struct sk_buff *skb); struct sk_buff *find_skb(struct netpoll *np, int len, int reserve); void netpoll_udp_checksum(struct netpoll *np, struct sk_buff *skb, int len); void push_udp(struct netpoll *np, struct sk_buff *skb, int len); -void push_eth(struct netpoll *np, struct sk_buff *skb); #ifdef CONFIG_NETPOLL static inline void *netpoll_poll_lock(struct napi_struct *napi) diff --git a/net/core/netpoll.c b/net/core/netpoll.c index 030424e43bfb..12165667705f 100644 --- a/net/core/netpoll.c +++ b/net/core/netpoll.c @@ -431,21 +431,6 @@ void push_udp(struct netpoll *np, struct sk_buff *skb, int len) } EXPORT_SYMBOL_GPL(push_udp); -void push_eth(struct netpoll *np, struct sk_buff *skb) -{ - struct ethhdr *eth; - - eth = skb_push(skb, ETH_HLEN); - skb_reset_mac_header(skb); - ether_addr_copy(eth->h_source, np->dev->dev_addr); - ether_addr_copy(eth->h_dest, np->remote_mac); - if (np->ipv6) - eth->h_proto = htons(ETH_P_IPV6); - else - eth->h_proto = htons(ETH_P_IP); -} -EXPORT_SYMBOL_GPL(push_eth); - static void skb_pool_flush(struct netpoll *np) { struct sk_buff_head *skb_pool; -- cgit v1.2.3 From df82899714b125172702437ad8bd066c565a98e9 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Tue, 12 May 2026 03:46:39 -0700 Subject: netconsole: move push_udp() from netpoll push_udp() builds the UDP header (and triggers the checksum) for netconsole's UDP packets. Move it into drivers/net/netconsole.c as a file-static helper; drop its EXPORT_SYMBOL_GPL and remove the prototype from include/linux/netpoll.h. Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20260512-netconsole_split-v2-6-1191d14ad66d@debian.org Signed-off-by: Jakub Kicinski --- drivers/net/netconsole.c | 18 ++++++++++++++++++ include/linux/netpoll.h | 1 - net/core/netpoll.c | 19 ------------------- 3 files changed, 18 insertions(+), 20 deletions(-) (limited to 'include') diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index bcedc161b13e..e8d96e482fe0 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -1652,6 +1652,24 @@ static struct notifier_block netconsole_netdev_notifier = { .notifier_call = netconsole_netdev_event, }; +static void push_udp(struct netpoll *np, struct sk_buff *skb, int len) +{ + struct udphdr *udph; + int udp_len; + + udp_len = len + sizeof(struct udphdr); + + skb_push(skb, sizeof(struct udphdr)); + skb_reset_transport_header(skb); + + udph = udp_hdr(skb); + udph->source = htons(np->local_port); + udph->dest = htons(np->remote_port); + udph->len = htons(udp_len); + + netpoll_udp_checksum(np, skb, len); +} + static void push_eth(struct netpoll *np, struct sk_buff *skb) { struct ethhdr *eth; diff --git a/include/linux/netpoll.h b/include/linux/netpoll.h index 11eb95b3248e..03a49953a3d8 100644 --- a/include/linux/netpoll.h +++ b/include/linux/netpoll.h @@ -75,7 +75,6 @@ void do_netpoll_cleanup(struct netpoll *np); netdev_tx_t netpoll_send_skb(struct netpoll *np, struct sk_buff *skb); struct sk_buff *find_skb(struct netpoll *np, int len, int reserve); void netpoll_udp_checksum(struct netpoll *np, struct sk_buff *skb, int len); -void push_udp(struct netpoll *np, struct sk_buff *skb, int len); #ifdef CONFIG_NETPOLL static inline void *netpoll_poll_lock(struct napi_struct *napi) diff --git a/net/core/netpoll.c b/net/core/netpoll.c index 12165667705f..96c65625f4cb 100644 --- a/net/core/netpoll.c +++ b/net/core/netpoll.c @@ -412,25 +412,6 @@ netdev_tx_t netpoll_send_skb(struct netpoll *np, struct sk_buff *skb) } EXPORT_SYMBOL(netpoll_send_skb); -void push_udp(struct netpoll *np, struct sk_buff *skb, int len) -{ - struct udphdr *udph; - int udp_len; - - udp_len = len + sizeof(struct udphdr); - - skb_push(skb, sizeof(struct udphdr)); - skb_reset_transport_header(skb); - - udph = udp_hdr(skb); - udph->source = htons(np->local_port); - udph->dest = htons(np->remote_port); - udph->len = htons(udp_len); - - netpoll_udp_checksum(np, skb, len); -} -EXPORT_SYMBOL_GPL(push_udp); - static void skb_pool_flush(struct netpoll *np) { struct sk_buff_head *skb_pool; -- cgit v1.2.3 From 597bfa943131511e668235f112c70e806ca36e5d Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Tue, 12 May 2026 03:46:40 -0700 Subject: netconsole: move netpoll_udp_checksum() from netpoll netpoll_udp_checksum() computes the UDP checksum for netconsole's packets. Move it into drivers/net/netconsole.c as a file-static helper; drop its EXPORT_SYMBOL_GPL and remove the prototype from include/linux/netpoll.h. This was the last csum_ipv6_magic() consumer in net/core/netpoll.c, so drop the now-stale include there. Pull it into netconsole.c so the moved code keeps building. It was also the last udp_hdr() consumer in net/core/netpoll.c. The file no longer needs anything from (the UDP socket-layer helpers); MAX_SKB_SIZE only needs struct udphdr, which is provided by the lighter . Swap the include accordingly. Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20260512-netconsole_split-v2-7-1191d14ad66d@debian.org Signed-off-by: Jakub Kicinski --- drivers/net/netconsole.c | 26 ++++++++++++++++++++++++++ include/linux/netpoll.h | 1 - net/core/netpoll.c | 29 +---------------------------- 3 files changed, 27 insertions(+), 29 deletions(-) (limited to 'include') diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index e8d96e482fe0..48d2670bbdef 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -1652,6 +1653,31 @@ static struct notifier_block netconsole_netdev_notifier = { .notifier_call = netconsole_netdev_event, }; +static void netpoll_udp_checksum(struct netpoll *np, struct sk_buff *skb, + int len) +{ + struct udphdr *udph; + int udp_len; + + udp_len = len + sizeof(struct udphdr); + udph = udp_hdr(skb); + + /* check needs to be set, since it will be consumed in csum_partial */ + udph->check = 0; + if (np->ipv6) + udph->check = csum_ipv6_magic(&np->local_ip.in6, + &np->remote_ip.in6, + udp_len, IPPROTO_UDP, + csum_partial(udph, udp_len, 0)); + else + udph->check = csum_tcpudp_magic(np->local_ip.ip, + np->remote_ip.ip, + udp_len, IPPROTO_UDP, + csum_partial(udph, udp_len, 0)); + if (udph->check == 0) + udph->check = CSUM_MANGLED_0; +} + static void push_udp(struct netpoll *np, struct sk_buff *skb, int len) { struct udphdr *udph; diff --git a/include/linux/netpoll.h b/include/linux/netpoll.h index 03a49953a3d8..1877d42ccbcc 100644 --- a/include/linux/netpoll.h +++ b/include/linux/netpoll.h @@ -74,7 +74,6 @@ void netpoll_cleanup(struct netpoll *np); void do_netpoll_cleanup(struct netpoll *np); netdev_tx_t netpoll_send_skb(struct netpoll *np, struct sk_buff *skb); struct sk_buff *find_skb(struct netpoll *np, int len, int reserve); -void netpoll_udp_checksum(struct netpoll *np, struct sk_buff *skb, int len); #ifdef CONFIG_NETPOLL static inline void *netpoll_poll_lock(struct napi_struct *napi) diff --git a/net/core/netpoll.c b/net/core/netpoll.c index 96c65625f4cb..c148b3b68cec 100644 --- a/net/core/netpoll.c +++ b/net/core/netpoll.c @@ -29,11 +29,10 @@ #include #include #include +#include #include -#include #include #include -#include #include #include @@ -369,32 +368,6 @@ out: return ret; } -void netpoll_udp_checksum(struct netpoll *np, struct sk_buff *skb, - int len) -{ - struct udphdr *udph; - int udp_len; - - udp_len = len + sizeof(struct udphdr); - udph = udp_hdr(skb); - - /* check needs to be set, since it will be consumed in csum_partial */ - udph->check = 0; - if (np->ipv6) - udph->check = csum_ipv6_magic(&np->local_ip.in6, - &np->remote_ip.in6, - udp_len, IPPROTO_UDP, - csum_partial(udph, udp_len, 0)); - else - udph->check = csum_tcpudp_magic(np->local_ip.ip, - np->remote_ip.ip, - udp_len, IPPROTO_UDP, - csum_partial(udph, udp_len, 0)); - if (udph->check == 0) - udph->check = CSUM_MANGLED_0; -} -EXPORT_SYMBOL_GPL(netpoll_udp_checksum); - netdev_tx_t netpoll_send_skb(struct netpoll *np, struct sk_buff *skb) { unsigned long flags; -- cgit v1.2.3 From 7bb1daff3612276357b28d0dee2a32301be97dfd Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Tue, 12 May 2026 03:46:41 -0700 Subject: netpoll: rename and export netpoll_zap_completion_queue() zap_completion_queue() drains the per-CPU softnet completion queue. Rename it with the netpoll_ prefix shared by the rest of the subsystem's public API, and promote it from file-static to EXPORT_SYMBOL_NS_GPL in the NETDEV_INTERNAL namespace so the upcoming netconsole-side find_skb() can call it once the function moves out. A forward declaration is added to include/linux/netpoll.h, and the old file-static forward declaration is dropped. No functional change. Suggested-by: Jakub Kicinski Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20260512-netconsole_split-v2-8-1191d14ad66d@debian.org Signed-off-by: Jakub Kicinski --- include/linux/netpoll.h | 1 + net/core/netpoll.c | 9 ++++----- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'include') diff --git a/include/linux/netpoll.h b/include/linux/netpoll.h index 1877d42ccbcc..f986f7486cb8 100644 --- a/include/linux/netpoll.h +++ b/include/linux/netpoll.h @@ -74,6 +74,7 @@ void netpoll_cleanup(struct netpoll *np); void do_netpoll_cleanup(struct netpoll *np); netdev_tx_t netpoll_send_skb(struct netpoll *np, struct sk_buff *skb); struct sk_buff *find_skb(struct netpoll *np, int len, int reserve); +void netpoll_zap_completion_queue(void); #ifdef CONFIG_NETPOLL static inline void *netpoll_poll_lock(struct napi_struct *napi) diff --git a/net/core/netpoll.c b/net/core/netpoll.c index c148b3b68cec..e4cda0aa4d48 100644 --- a/net/core/netpoll.c +++ b/net/core/netpoll.c @@ -51,8 +51,6 @@ sizeof(struct udphdr) + \ MAX_UDP_CHUNK) -static void zap_completion_queue(void); - static unsigned int carrier_timeout = 4; module_param(carrier_timeout, uint, 0644); @@ -199,7 +197,7 @@ void netpoll_poll_dev(struct net_device *dev) up(&ni->dev_lock); - zap_completion_queue(); + netpoll_zap_completion_queue(); } EXPORT_SYMBOL(netpoll_poll_dev); @@ -238,7 +236,7 @@ static void refill_skbs(struct netpoll *np) } } -static void zap_completion_queue(void) +void netpoll_zap_completion_queue(void) { unsigned long flags; struct softnet_data *sd = &get_cpu_var(softnet_data); @@ -265,13 +263,14 @@ static void zap_completion_queue(void) put_cpu_var(softnet_data); } +EXPORT_SYMBOL_NS_GPL(netpoll_zap_completion_queue, "NETDEV_INTERNAL"); struct sk_buff *find_skb(struct netpoll *np, int len, int reserve) { int count = 0; struct sk_buff *skb; - zap_completion_queue(); + netpoll_zap_completion_queue(); repeat: skb = alloc_skb(len, GFP_ATOMIC); -- cgit v1.2.3 From f35422ed3664d2a40161c38f53ba69cb99387af7 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Tue, 12 May 2026 03:46:42 -0700 Subject: netconsole: move find_skb() from netpoll find_skb() is the netconsole-specific entry into the netpoll skb pool: every other netpoll consumer (bonding, team, vlan, bridge, macvlan, dsa) builds its own sk_buff and never touches the pool. With netpoll_send_udp() (its only caller) now living in netconsole, find_skb() can join it. Move find_skb() into drivers/net/netconsole.c as a file-static helper, drop EXPORT_SYMBOL_GPL(find_skb) and remove its prototype from include/linux/netpoll.h. find_skb() drains TX completions via netpoll_zap_completion_queue(), which is already exported in the NETDEV_INTERNAL namespace, so netconsole picks up MODULE_IMPORT_NS("NETDEV_INTERNAL") to consume it. The skb pool's lifecycle (np->skb_pool, np->refill_wq, refill_skbs(), refill_skbs_work_handler(), skb_pool_flush()) stays in netpoll: it is initialised in __netpoll_setup() and torn down in __netpoll_cleanup(), both of which remain netpoll's responsibility. The refill work queued via schedule_work(&np->refill_wq) from the moved find_skb() runs refill_skbs_work_handler() in netpoll without any further plumbing. This is pure code motion: the function body is unchanged and its sole caller (netpoll_send_udp(), already moved by an earlier patch) keeps invoking it the same way. Pre-existing concerns about find_skb() running from NMI/printk context (zap_completion_queue() re-entry, skb_pool spinlocks, GFP_ATOMIC allocation, fallback skb sizing vs. MAX_SKB_SIZE, PREEMPT_RT semantics of __kfree_skb()) are inherited as-is and are not addressed here; they predate this series and are out of scope. Fixing them is left for follow-up work. Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20260512-netconsole_split-v2-9-1191d14ad66d@debian.org Signed-off-by: Jakub Kicinski --- drivers/net/netconsole.c | 28 ++++++++++++++++++++++++++++ include/linux/netpoll.h | 1 - net/core/netpoll.c | 28 ---------------------------- 3 files changed, 28 insertions(+), 29 deletions(-) (limited to 'include') diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index 48d2670bbdef..d804d44af87c 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -50,6 +50,7 @@ MODULE_AUTHOR("Matt Mackall "); MODULE_DESCRIPTION("Console driver for network interfaces"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("NETDEV_INTERNAL"); #define MAX_PARAM_LENGTH 256 #define MAX_EXTRADATA_ENTRY_LEN 256 @@ -1653,6 +1654,33 @@ static struct notifier_block netconsole_netdev_notifier = { .notifier_call = netconsole_netdev_event, }; +static struct sk_buff *find_skb(struct netpoll *np, int len, int reserve) +{ + int count = 0; + struct sk_buff *skb; + + netpoll_zap_completion_queue(); +repeat: + + skb = alloc_skb(len, GFP_ATOMIC); + if (!skb) { + skb = skb_dequeue(&np->skb_pool); + schedule_work(&np->refill_wq); + } + + if (!skb) { + if (++count < 10) { + netpoll_poll_dev(np->dev); + goto repeat; + } + return NULL; + } + + refcount_set(&skb->users, 1); + skb_reserve(skb, reserve); + return skb; +} + static void netpoll_udp_checksum(struct netpoll *np, struct sk_buff *skb, int len) { diff --git a/include/linux/netpoll.h b/include/linux/netpoll.h index f986f7486cb8..e4b8f1f91e54 100644 --- a/include/linux/netpoll.h +++ b/include/linux/netpoll.h @@ -73,7 +73,6 @@ void __netpoll_free(struct netpoll *np); void netpoll_cleanup(struct netpoll *np); void do_netpoll_cleanup(struct netpoll *np); netdev_tx_t netpoll_send_skb(struct netpoll *np, struct sk_buff *skb); -struct sk_buff *find_skb(struct netpoll *np, int len, int reserve); void netpoll_zap_completion_queue(void); #ifdef CONFIG_NETPOLL diff --git a/net/core/netpoll.c b/net/core/netpoll.c index e4cda0aa4d48..33ab3d827a42 100644 --- a/net/core/netpoll.c +++ b/net/core/netpoll.c @@ -265,34 +265,6 @@ void netpoll_zap_completion_queue(void) } EXPORT_SYMBOL_NS_GPL(netpoll_zap_completion_queue, "NETDEV_INTERNAL"); -struct sk_buff *find_skb(struct netpoll *np, int len, int reserve) -{ - int count = 0; - struct sk_buff *skb; - - netpoll_zap_completion_queue(); -repeat: - - skb = alloc_skb(len, GFP_ATOMIC); - if (!skb) { - skb = skb_dequeue(&np->skb_pool); - schedule_work(&np->refill_wq); - } - - if (!skb) { - if (++count < 10) { - netpoll_poll_dev(np->dev); - goto repeat; - } - return NULL; - } - - refcount_set(&skb->users, 1); - skb_reserve(skb, reserve); - return skb; -} -EXPORT_SYMBOL_GPL(find_skb); - static int netpoll_owner_active(struct net_device *dev) { struct napi_struct *napi; -- cgit v1.2.3 From f0de88303d5e7e04a1224bc7a00512b5a1c4fe7a Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Tue, 12 May 2026 09:48:58 +0000 Subject: net: make is_skb_wmem() available to modules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Following patch will use is_skb_wmem() from fq_codel. Provide __sock_wfree() only if CONFIG_INET=y Signed-off-by: Eric Dumazet Reviewed-by: Toke Høiland-Jørgensen Link: https://patch.msgid.link/20260512094859.3673997-2-edumazet@google.com Signed-off-by: Jakub Kicinski --- include/net/sock.h | 12 +++++++++++- include/net/tcp.h | 1 - net/core/skbuff.c | 2 +- net/core/sock.c | 3 +++ net/core/sock_destructor.h | 12 ------------ net/ipv4/inet_fragment.c | 2 -- net/ipv4/tcp_output.c | 1 + 7 files changed, 16 insertions(+), 17 deletions(-) delete mode 100644 net/core/sock_destructor.h (limited to 'include') diff --git a/include/net/sock.h b/include/net/sock.h index dccd3738c368..219917959b54 100644 --- a/include/net/sock.h +++ b/include/net/sock.h @@ -1850,8 +1850,18 @@ static inline struct sock *sk_clone_lock(const struct sock *sk, const gfp_t prio struct sk_buff *sock_wmalloc(struct sock *sk, unsigned long size, int force, gfp_t priority); -void __sock_wfree(struct sk_buff *skb); void sock_wfree(struct sk_buff *skb); +#ifdef CONFIG_INET +void __sock_wfree(struct sk_buff *skb); +void tcp_wfree(struct sk_buff *skb); +#endif +static inline bool is_skb_wmem(const struct sk_buff *skb) +{ + return skb->destructor == sock_wfree || + (IS_ENABLED(CONFIG_INET) && skb->destructor == __sock_wfree) || + (IS_ENABLED(CONFIG_INET) && skb->destructor == tcp_wfree); +} + struct sk_buff *sock_omalloc(struct sock *sk, unsigned long size, gfp_t priority); void skb_orphan_partial(struct sk_buff *skb); diff --git a/include/net/tcp.h b/include/net/tcp.h index 3c4e6adb0dbd..5715e60c519a 100644 --- a/include/net/tcp.h +++ b/include/net/tcp.h @@ -390,7 +390,6 @@ static inline bool tcp_release_cb_cond(struct sock *sk) return false; } -void tcp_wfree(struct sk_buff *skb); void tcp_write_timer_handler(struct sock *sk); void tcp_delack_timer_handler(struct sock *sk); int tcp_ioctl(struct sock *sk, int cmd, int *karg); diff --git a/net/core/skbuff.c b/net/core/skbuff.c index acca1365672c..419a4d2833b2 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c @@ -78,6 +78,7 @@ #include #include #include +#include #include #include #include @@ -96,7 +97,6 @@ #include "devmem.h" #include "net-sysfs.h" #include "netmem_priv.h" -#include "sock_destructor.h" #ifdef CONFIG_SKB_EXTENSIONS static struct kmem_cache *skbuff_ext_cache __ro_after_init; diff --git a/net/core/sock.c b/net/core/sock.c index b37b664b6eb9..f362e3ce1efb 100644 --- a/net/core/sock.c +++ b/net/core/sock.c @@ -2708,6 +2708,7 @@ EXPORT_SYMBOL(sock_wfree); /* This variant of sock_wfree() is used by TCP, * since it sets SOCK_USE_WRITE_QUEUE. */ +#ifdef CONFIG_INET void __sock_wfree(struct sk_buff *skb) { struct sock *sk = skb->sk; @@ -2715,6 +2716,8 @@ void __sock_wfree(struct sk_buff *skb) if (refcount_sub_and_test(skb->truesize, &sk->sk_wmem_alloc)) __sk_free(sk); } +EXPORT_SYMBOL_GPL(__sock_wfree); +#endif void skb_set_owner_w(struct sk_buff *skb, struct sock *sk) { diff --git a/net/core/sock_destructor.h b/net/core/sock_destructor.h deleted file mode 100644 index 2f396e6bfba5..000000000000 --- a/net/core/sock_destructor.h +++ /dev/null @@ -1,12 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ -#ifndef _NET_CORE_SOCK_DESTRUCTOR_H -#define _NET_CORE_SOCK_DESTRUCTOR_H -#include - -static inline bool is_skb_wmem(const struct sk_buff *skb) -{ - return skb->destructor == sock_wfree || - skb->destructor == __sock_wfree || - (IS_ENABLED(CONFIG_INET) && skb->destructor == tcp_wfree); -} -#endif diff --git a/net/ipv4/inet_fragment.c b/net/ipv4/inet_fragment.c index 393770920abd..86b100694659 100644 --- a/net/ipv4/inet_fragment.c +++ b/net/ipv4/inet_fragment.c @@ -24,8 +24,6 @@ #include #include -#include "../core/sock_destructor.h" - /* Use skb->cb to track consecutive/adjacent fragments coming at * the end of the queue. Nodes in the rb-tree queue will * contain "runs" of one or more adjacent fragments. diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c index 75eb18a2ad4e..47647a99e324 100644 --- a/net/ipv4/tcp_output.c +++ b/net/ipv4/tcp_output.c @@ -1415,6 +1415,7 @@ void tcp_wfree(struct sk_buff *skb) out: sk_free(sk); } +EXPORT_SYMBOL_GPL(tcp_wfree); /* Note: Called under soft irq. * We can call TCP stack right away, unless socket is owned by user. -- cgit v1.2.3 From a2f6ed7b4873288d9e90e69199012857bed4bfa4 Mon Sep 17 00:00:00 2001 From: Stephen Hemminger Date: Sat, 9 May 2026 10:03:26 -0700 Subject: net/sched: netem: add per-impairment extended statistics Add 64-bit counters for each impairment netem applies (delay, loss, ECN marking, corruption, duplication, reordering) and for skb allocation failures during enqueue. Exposed through TCA_STATS_APP as struct tc_netem_xstats. Counters increment when an impairment is occurs, independent of later events that may mask its on-wire effect. Added allocation_errors (similar to sch_fq) to account for when impairment could not be applied due to memory pressure, etc. Signed-off-by: Stephen Hemminger Link: https://patch.msgid.link/20260509171123.307549-6-stephen@networkplumber.org Signed-off-by: Paolo Abeni --- include/uapi/linux/pkt_sched.h | 10 ++++++++ net/sched/sch_netem.c | 55 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 59 insertions(+), 6 deletions(-) (limited to 'include') diff --git a/include/uapi/linux/pkt_sched.h b/include/uapi/linux/pkt_sched.h index 66e8072f44df..490efd288526 100644 --- a/include/uapi/linux/pkt_sched.h +++ b/include/uapi/linux/pkt_sched.h @@ -569,6 +569,16 @@ struct tc_netem_gemodel { #define NETEM_DIST_SCALE 8192 #define NETEM_DIST_MAX 16384 +struct tc_netem_xstats { + __u64 delayed; /* packets delayed */ + __u64 dropped; /* packets dropped by loss model */ + __u64 corrupted; /* packets with bit errors injected */ + __u64 duplicated; /* duplicate packets generated */ + __u64 reordered; /* packets sent out of order */ + __u64 ecn_marked; /* packets ECN CE-marked (not dropped)*/ + __u64 allocation_errors; +}; + /* DRR */ enum { diff --git a/net/sched/sch_netem.c b/net/sched/sch_netem.c index 1e9de2ba8891..6cd1838e09e7 100644 --- a/net/sched/sch_netem.c +++ b/net/sched/sch_netem.c @@ -152,6 +152,15 @@ struct netem_sched_data { u8 state; } clg; + /* Impairment counters */ + u64 delayed; + u64 dropped; + u64 corrupted; + u64 duplicated; + u64 ecn_marked; + u64 reordered; + u64 allocation_errors; + /* Cold tail: slot reschedule config and the watchdog timer. */ struct tc_netem_slot slot_config; struct qdisc_watchdog watchdog; @@ -462,16 +471,21 @@ static int netem_enqueue(struct sk_buff *skb, struct Qdisc *sch, skb->prev = NULL; /* Random duplication */ - if (q->duplicate && q->duplicate >= get_crandom(&q->dup_cor, &q->prng)) + if (q->duplicate && q->duplicate >= get_crandom(&q->dup_cor, &q->prng)) { ++count; + WRITE_ONCE(q->duplicated, q->duplicated + 1); + } /* Drop packet? */ if (loss_event(q)) { - if (q->ecn && INET_ECN_set_ce(skb)) - qdisc_qstats_drop(sch); /* mark packet */ - else + if (q->ecn && INET_ECN_set_ce(skb)) { + WRITE_ONCE(q->ecn_marked, q->ecn_marked + 1); + } else { + WRITE_ONCE(q->dropped, q->dropped + 1); --count; + } } + if (count == 0) { qdisc_qstats_drop(sch); __qdisc_drop(skb, to_free); @@ -488,8 +502,11 @@ static int netem_enqueue(struct sk_buff *skb, struct Qdisc *sch, * If we need to duplicate packet, then clone it before * original is modified. */ - if (count > 1) + if (count > 1) { skb2 = skb_clone(skb, GFP_ATOMIC); + if (!skb2) + WRITE_ONCE(q->allocation_errors, q->allocation_errors + 1); + } /* * Randomized packet corruption. @@ -500,8 +517,10 @@ static int netem_enqueue(struct sk_buff *skb, struct Qdisc *sch, if (q->corrupt && q->corrupt >= get_crandom(&q->corrupt_cor, &q->prng)) { if (skb_is_gso(skb)) { skb = netem_segment(skb, sch, to_free); - if (!skb) + if (!skb) { + WRITE_ONCE(q->allocation_errors, q->allocation_errors + 1); goto finish_segs; + } segs = skb->next; skb_mark_not_on_list(skb); @@ -510,11 +529,13 @@ static int netem_enqueue(struct sk_buff *skb, struct Qdisc *sch, skb = skb_unshare(skb, GFP_ATOMIC); if (unlikely(!skb)) { + WRITE_ONCE(q->allocation_errors, q->allocation_errors + 1); qdisc_qstats_drop(sch); goto finish_segs; } if (skb_linearize(skb) || (skb->ip_summed == CHECKSUM_PARTIAL && skb_checksum_help(skb))) { + WRITE_ONCE(q->allocation_errors, q->allocation_errors + 1); qdisc_drop(skb, sch, to_free); skb = NULL; goto finish_segs; @@ -523,6 +544,7 @@ static int netem_enqueue(struct sk_buff *skb, struct Qdisc *sch, if (skb->len) { u32 offset = get_random_u32_below(skb->len); skb->data[offset] ^= 1 << get_random_u32_below(8); + WRITE_ONCE(q->corrupted, q->corrupted + 1); } } @@ -604,12 +626,16 @@ static int netem_enqueue(struct sk_buff *skb, struct Qdisc *sch, cb->time_to_send = now + delay; ++q->counter; + if (delay) + WRITE_ONCE(q->delayed, q->delayed + 1); + tfifo_enqueue(skb, sch); } else { /* * Do re-ordering by putting one out of N packets at the front * of the queue. */ + WRITE_ONCE(q->reordered, q->reordered + 1); cb->time_to_send = ktime_get_ns(); q->counter = 0; @@ -1348,6 +1374,22 @@ nla_put_failure: return -1; } +static int netem_dump_stats(struct Qdisc *sch, struct gnet_dump *d) +{ + struct netem_sched_data *q = qdisc_priv(sch); + struct tc_netem_xstats st = { + .delayed = READ_ONCE(q->delayed), + .dropped = READ_ONCE(q->dropped), + .corrupted = READ_ONCE(q->corrupted), + .duplicated = READ_ONCE(q->duplicated), + .reordered = READ_ONCE(q->reordered), + .ecn_marked = READ_ONCE(q->ecn_marked), + .allocation_errors = READ_ONCE(q->allocation_errors), + }; + + return gnet_stats_copy_app(d, &st, sizeof(st)); +} + static int netem_dump_class(struct Qdisc *sch, unsigned long cl, struct sk_buff *skb, struct tcmsg *tcm) { @@ -1410,6 +1452,7 @@ static struct Qdisc_ops netem_qdisc_ops __read_mostly = { .destroy = netem_destroy, .change = netem_change, .dump = netem_dump, + .dump_stats = netem_dump_stats, .owner = THIS_MODULE, }; MODULE_ALIAS_NET_SCH("netem"); -- cgit v1.2.3 From 0c32db0761fef3d98d6e4d6d8ce02c40e914f4d8 Mon Sep 17 00:00:00 2001 From: Danielle Ratson Date: Mon, 11 May 2026 09:59:31 +0300 Subject: bridge: uapi: Add neigh_forward_grat netlink attributes Add netlink attributes for controlling gratuitous ARP and unsolicited NA forwarding when neighbor suppression is enabled. Add IFLA_BRPORT_NEIGH_FORWARD_GRAT for port-level control and BRIDGE_VLANDB_ENTRY_NEIGH_FORWARD_GRAT for per-VLAN control. The new attributes provide independent control of gratuitous ARP and unsolicited NA packets. Operators can enable forwarding for those packets for fast mobility across VTEPs while keeping general neighbor suppression active. Reviewed-by: Ido Schimmel Signed-off-by: Danielle Ratson Link: https://patch.msgid.link/20260511065936.4173106-2-danieller@nvidia.com Signed-off-by: Paolo Abeni --- Documentation/netlink/specs/rt-link.yaml | 3 +++ include/uapi/linux/if_bridge.h | 1 + include/uapi/linux/if_link.h | 17 +++++++++++++++++ net/core/rtnetlink.c | 2 +- 4 files changed, 22 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/Documentation/netlink/specs/rt-link.yaml b/Documentation/netlink/specs/rt-link.yaml index f23aa5f229c5..79c89f204415 100644 --- a/Documentation/netlink/specs/rt-link.yaml +++ b/Documentation/netlink/specs/rt-link.yaml @@ -1700,6 +1700,9 @@ attribute-sets: - name: backup-nhid type: u32 + - + name: neigh-forward-grat + type: u8 - name: linkinfo-gre-attrs name-prefix: ifla-gre- diff --git a/include/uapi/linux/if_bridge.h b/include/uapi/linux/if_bridge.h index e52f8207ab27..21a700c02ef7 100644 --- a/include/uapi/linux/if_bridge.h +++ b/include/uapi/linux/if_bridge.h @@ -526,6 +526,7 @@ enum { BRIDGE_VLANDB_ENTRY_MCAST_N_GROUPS, BRIDGE_VLANDB_ENTRY_MCAST_MAX_GROUPS, BRIDGE_VLANDB_ENTRY_NEIGH_SUPPRESS, + BRIDGE_VLANDB_ENTRY_NEIGH_FORWARD_GRAT, __BRIDGE_VLANDB_ENTRY_MAX, }; #define BRIDGE_VLANDB_ENTRY_MAX (__BRIDGE_VLANDB_ENTRY_MAX - 1) diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h index 79ce4bc24cba..46413392b402 100644 --- a/include/uapi/linux/if_link.h +++ b/include/uapi/linux/if_link.h @@ -1085,6 +1085,22 @@ enum { * Note that this option only takes effect when *IFLA_BRPORT_NEIGH_SUPPRESS* * is enabled for a given port. * + * @IFLA_BRPORT_NEIGH_FORWARD_GRAT + * Controls whether gratuitous ARP packets and unsolicited Neighbor + * Advertisement packets are forwarded on a given port even when neighbor + * suppression is enabled. + * By default this flag is off, meaning gratuitous ARP and unsolicited NA + * packets will be suppressed when neighbor suppression is enabled. + * Setting this flag to on allows these packets to be forwarded even + * when *IFLA_BRPORT_NEIGH_SUPPRESS* or *IFLA_BRPORT_NEIGH_VLAN_SUPPRESS* + * is enabled. + * + * Note that this option only takes effect when *IFLA_BRPORT_NEIGH_SUPPRESS* + * or *IFLA_BRPORT_NEIGH_VLAN_SUPPRESS* is enabled for a given port. + * When *IFLA_BRPORT_NEIGH_VLAN_SUPPRESS* is set, this port-level flag is + * ignored and per-VLAN control is available via + * *BRIDGE_VLANDB_ENTRY_NEIGH_FORWARD_GRAT*. + * * @IFLA_BRPORT_BACKUP_NHID * The FDB nexthop object ID to attach to packets being redirected to a * backup port that has VLAN tunnel mapping enabled (via the @@ -1137,6 +1153,7 @@ enum { IFLA_BRPORT_MCAST_MAX_GROUPS, IFLA_BRPORT_NEIGH_VLAN_SUPPRESS, IFLA_BRPORT_BACKUP_NHID, + IFLA_BRPORT_NEIGH_FORWARD_GRAT, __IFLA_BRPORT_MAX }; #define IFLA_BRPORT_MAX (__IFLA_BRPORT_MAX - 1) diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c index 70fde922b371..6a5e9ace55a0 100644 --- a/net/core/rtnetlink.c +++ b/net/core/rtnetlink.c @@ -63,7 +63,7 @@ #include "dev.h" #define RTNL_MAX_TYPE 50 -#define RTNL_SLAVE_MAX_TYPE 44 +#define RTNL_SLAVE_MAX_TYPE 45 struct rtnl_link { rtnl_doit_func doit; -- cgit v1.2.3 From 3a28ccb9dd47d1b6f64c64a94c958a968299aa1d Mon Sep 17 00:00:00 2001 From: Danielle Ratson Date: Mon, 11 May 2026 09:59:32 +0300 Subject: bridge: Add internal flags for neigh_forward_grat Add internal flags for the neigh_forward_grat feature: - BR_NEIGH_FORWARD_GRAT: Port-level flag - BR_VLFLAG_NEIGH_FORWARD_GRAT_ENABLED: Per-VLAN flag These will be used to control whether gratuitous ARP and unsolicited NA packets are forwarded when neighbor suppression is enabled. Reviewed-by: Petr Machata Reviewed-by: Ido Schimmel Signed-off-by: Danielle Ratson Acked-by: Nikolay Aleksandrov Link: https://patch.msgid.link/20260511065936.4173106-3-danieller@nvidia.com Signed-off-by: Paolo Abeni --- include/linux/if_bridge.h | 1 + net/bridge/br_private.h | 1 + 2 files changed, 2 insertions(+) (limited to 'include') diff --git a/include/linux/if_bridge.h b/include/linux/if_bridge.h index c5fe3b2a53e8..ec9ffea1e46e 100644 --- a/include/linux/if_bridge.h +++ b/include/linux/if_bridge.h @@ -61,6 +61,7 @@ struct br_ip_list { #define BR_PORT_LOCKED BIT(21) #define BR_PORT_MAB BIT(22) #define BR_NEIGH_VLAN_SUPPRESS BIT(23) +#define BR_NEIGH_FORWARD_GRAT BIT(24) #define BR_DEFAULT_AGEING_TIME (300 * HZ) diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h index bed1b1d9b282..3bc15978a8df 100644 --- a/net/bridge/br_private.h +++ b/net/bridge/br_private.h @@ -183,6 +183,7 @@ enum { BR_VLFLAG_GLOBAL_MCAST_ENABLED = BIT(3), BR_VLFLAG_NEIGH_SUPPRESS_ENABLED = BIT(4), BR_VLFLAG_TAGGING_BY_SWITCHDEV = BIT(5), + BR_VLFLAG_NEIGH_FORWARD_GRAT_ENABLED = BIT(6), }; /** -- cgit v1.2.3 From ff205bf8c55451f95300bceed9779b647205a850 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Tue, 12 May 2026 15:52:44 +0000 Subject: netlink: add one debug check in nla_nest_end() Add a DEBUG_NET_WARN_ON_ONCE(diff > U16_MAX) to warn if the kernel sends corrupted nested attribute to user space. Offenders can be converted to nla_nest_end_safe(). Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260512155244.4137851-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- include/net/netlink.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/include/net/netlink.h b/include/net/netlink.h index 546d10586576..b5048dd1d511 100644 --- a/include/net/netlink.h +++ b/include/net/netlink.h @@ -2260,7 +2260,10 @@ static inline struct nlattr *nla_nest_start(struct sk_buff *skb, int attrtype) */ static inline int nla_nest_end(struct sk_buff *skb, struct nlattr *start) { - start->nla_len = skb_tail_pointer(skb) - (unsigned char *)start; + unsigned long diff = skb_tail_pointer(skb) - (unsigned char *)start; + + DEBUG_NET_WARN_ON_ONCE(diff > U16_MAX); + start->nla_len = diff; return skb->len; } -- cgit v1.2.3 From 72a1795999df1a5cb9296343202f8679151a5ea2 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Wed, 13 May 2026 08:08:51 +0000 Subject: net/sched: qdisc_qstats_qlen_backlog() runs locklessly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit qdisc_qstats_qlen_backlog() can be called without qdisc spinlock being held. Use qdisc_qlen_lockless() instead of qdisc_qlen(). Add a const qualifier to its first parameter (struct Qdisc *sch). Fixes: edb09eb17ed8 ("net: sched: do not acquire qdisc spinlock in qdisc/class stats dump") Signed-off-by: Eric Dumazet Reviewed-by: Toke Høiland-Jørgensen Link: https://patch.msgid.link/20260513080853.1383975-2-edumazet@google.com Signed-off-by: Jakub Kicinski --- include/net/sch_generic.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/include/net/sch_generic.h b/include/net/sch_generic.h index 9e6ed92729d2..d0ca932b1871 100644 --- a/include/net/sch_generic.h +++ b/include/net/sch_generic.h @@ -1070,13 +1070,13 @@ static inline int qdisc_qstats_copy(struct gnet_dump *d, const struct Qdisc *sch return gnet_stats_copy_queue(d, sch->cpu_qstats, &sch->qstats, qlen); } -static inline void qdisc_qstats_qlen_backlog(struct Qdisc *sch, __u32 *qlen, - __u32 *backlog) +static inline void qdisc_qstats_qlen_backlog(const struct Qdisc *sch, + u32 *qlen, u32 *backlog) { struct gnet_stats_queue qstats = { 0 }; gnet_stats_add_queue(&qstats, sch->cpu_qstats, &sch->qstats); - *qlen = qstats.qlen + qdisc_qlen(sch); + *qlen = qstats.qlen + qdisc_qlen_lockless(sch); *backlog = qstats.backlog; } -- cgit v1.2.3 From 4987a5763fd5ab72afde7493216d944d976a0b15 Mon Sep 17 00:00:00 2001 From: Jann Horn Date: Tue, 12 May 2026 16:02:03 +0200 Subject: net: block MSG_NO_SHARED_FRAGS in sendmsg() This change should cause no difference in behavior; it just cleans up some hazardous code that could have become a problem in the future. MSG_NO_SHARED_FRAGS is a kernel-internal flag that cancels the effect of MSG_SPLICE_PAGES, another kernel-internal flag that influences the data-sharing semantics of SKBs. Prevent passing this flag in from userspace via sendmsg() by adding it to MSG_INTERNAL_SENDMSG_FLAGS. This is not currently an observable problem because MSG_NO_SHARED_FRAGS only has an effect if kernel code adds MSG_SPLICE_PAGES to it. The only codepath that adds MSG_SPLICE_PAGES to user-supplied flags from which MSG_NO_SHARED_FRAGS hasn't been cleared is the path tcp_bpf_sendmsg -> tcp_bpf_send_verdict -> tcp_bpf_push, and that is not a problem because tcp_bpf_sendmsg always intentionally sets MSG_NO_SHARED_FRAGS anyway. Signed-off-by: Jann Horn Link: https://patch.msgid.link/20260512-msg_no_shared_frags-v1-1-55ea46760331@google.com Signed-off-by: Jakub Kicinski --- include/linux/socket.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include') diff --git a/include/linux/socket.h b/include/linux/socket.h index ec4a0a025793..1a4d0d128a13 100644 --- a/include/linux/socket.h +++ b/include/linux/socket.h @@ -357,7 +357,7 @@ struct ucred { /* Flags to be cleared on entry by sendmsg and sendmmsg syscalls */ #define MSG_INTERNAL_SENDMSG_FLAGS \ - (MSG_SPLICE_PAGES | MSG_SENDPAGE_NOPOLICY | MSG_SENDPAGE_DECRYPTED) + (MSG_SPLICE_PAGES | MSG_SENDPAGE_NOPOLICY | MSG_SENDPAGE_DECRYPTED | MSG_NO_SHARED_FRAGS) /* Setsockoptions(2) level. Thanks to BSD these must match IPPROTO_xxx */ #define SOL_IP 0 -- cgit v1.2.3 From e758acedce4e344e6ce290b4a422bb695dc53117 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 14 May 2026 09:55:06 +0000 Subject: net: always declare __sock_wfree() and tcp_wfree() Even if guarded by IS_ENABLED(CONFIG_INET) compilers need to know what __sock_wfree() and tcp_wfree() are: include/net/sock.h:1861:63: note: each undeclared identifier is reported only once for each function it appears in include/net/sock.h:1862:63: error: 'tcp_wfree' undeclared (first use in this function); did you mean 'sock_wfree'? 1862 | (IS_ENABLED(CONFIG_INET) && skb->destructor == tcp_wfree); Fixes: f0de88303d5e ("net: make is_skb_wmem() available to modules") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202605141607.mDXnYFKY-lkp@intel.com/ Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260514095506.3919094-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- include/net/sock.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'include') diff --git a/include/net/sock.h b/include/net/sock.h index 219917959b54..e0263bae8da9 100644 --- a/include/net/sock.h +++ b/include/net/sock.h @@ -1851,10 +1851,9 @@ static inline struct sock *sk_clone_lock(const struct sock *sk, const gfp_t prio struct sk_buff *sock_wmalloc(struct sock *sk, unsigned long size, int force, gfp_t priority); void sock_wfree(struct sk_buff *skb); -#ifdef CONFIG_INET void __sock_wfree(struct sk_buff *skb); void tcp_wfree(struct sk_buff *skb); -#endif + static inline bool is_skb_wmem(const struct sk_buff *skb) { return skb->destructor == sock_wfree || -- cgit v1.2.3 From 9fe78db3ad1737f393b18b04220a158b45e6dd91 Mon Sep 17 00:00:00 2001 From: Nimrod Oren Date: Thu, 14 May 2026 13:49:25 +0300 Subject: net/mlx5: add debugfs stats for frag buf dma pools Add a debugfs file exposing per-node DMA pool usage for mlx5_frag_buf allocations. # cat /sys/kernel/debug/mlx5//frag_buf_dma_pools node block_size used_blocks allocated_blocks 0 4096 0 0 0 8192 0 0 0 16384 0 0 0 32768 0 0 0 65536 0 0 1 4096 0 0 1 8192 0 0 1 16384 0 0 1 32768 0 0 1 65536 0 0 Signed-off-by: Nimrod Oren Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260514104925.337570-3-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/alloc.c | 86 +++++++++++++++++++++++++ include/linux/mlx5/driver.h | 1 + 2 files changed, 87 insertions(+) (limited to 'include') diff --git a/drivers/net/ethernet/mellanox/mlx5/core/alloc.c b/drivers/net/ethernet/mellanox/mlx5/core/alloc.c index 16d6b126a486..4fe9d7d4f143 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/alloc.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/alloc.c @@ -38,6 +38,8 @@ #include #include #include +#include +#include #include #include "mlx5_core.h" @@ -74,6 +76,13 @@ struct mlx5_frag_buf_node_pools { struct mlx5_dma_pool *pools[MLX5_FRAG_BUF_POOLS_NUM]; }; +struct mlx5_dma_pool_stats { + int node; + size_t block_size; + size_t used_blocks; + size_t allocated_blocks; +}; + /* Handling for queue buffers -- we allocate a bunch of memory and * register it in a memory region at HCA virtual address 0. */ @@ -225,6 +234,43 @@ static void mlx5_dma_pool_free(struct mlx5_dma_pool *pool, mutex_unlock(&pool->lock); } +static void mlx5_dma_pool_debugfs_get_stats(struct mlx5_dma_pool *pool, + struct mlx5_dma_pool_stats *stats) +{ + int blocks_per_page = BIT(PAGE_SHIFT - pool->block_shift); + struct mlx5_dma_pool_page *page; + size_t free_blocks = 0; + size_t pages = 0; + + mutex_lock(&pool->lock); + list_for_each_entry(page, &pool->page_list, pool_link) { + pages++; + free_blocks += bitmap_weight(page->bitmap, blocks_per_page); + } + mutex_unlock(&pool->lock); + + stats->node = pool->node; + stats->block_size = BIT(pool->block_shift); + stats->allocated_blocks = pages * blocks_per_page; + stats->used_blocks = stats->allocated_blocks - free_blocks; +} + +static void mlx5_dma_pool_debugfs_stats_print(struct seq_file *file, + struct mlx5_dma_pool *pool) +{ + struct mlx5_dma_pool_stats stats = {}; + + mlx5_dma_pool_debugfs_get_stats(pool, &stats); + seq_printf(file, "%4d %5zu %7zu %7zu\n", + stats.node, stats.block_size, stats.used_blocks, + stats.allocated_blocks); +} + +static void mlx5_dma_pools_debugfs_print_header(struct seq_file *file) +{ + seq_puts(file, "node block_size used_blocks allocated_blocks\n"); +} + static void mlx5_frag_buf_node_pools_destroy(struct mlx5_frag_buf_node_pools *node_pools) { @@ -257,11 +303,46 @@ mlx5_frag_buf_node_pools_create(struct mlx5_core_dev *dev, int node) return node_pools; } +static int +mlx5_frag_buf_dma_pools_debugfs_show(struct seq_file *file, void *priv) +{ + struct mlx5_core_dev *dev = file->private; + int node; + + mlx5_dma_pools_debugfs_print_header(file); + + if (!dev->priv.frag_buf_node_pools) + return 0; + + for_each_node_state(node, N_POSSIBLE) { + struct mlx5_frag_buf_node_pools *node_pools; + + node_pools = dev->priv.frag_buf_node_pools[node]; + if (!node_pools) + continue; + + for (int i = 0; i < MLX5_FRAG_BUF_POOLS_NUM; i++) { + struct mlx5_dma_pool *pool = node_pools->pools[i]; + + if (!pool) + continue; + + mlx5_dma_pool_debugfs_stats_print(file, pool); + } + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(mlx5_frag_buf_dma_pools_debugfs); + void mlx5_frag_buf_pools_cleanup(struct mlx5_core_dev *dev) { struct mlx5_priv *priv = &dev->priv; int node; + debugfs_remove(priv->dbg.frag_buf_dma_pools_debugfs); + priv->dbg.frag_buf_dma_pools_debugfs = NULL; + for_each_node_state(node, N_POSSIBLE) { struct mlx5_frag_buf_node_pools *node_pools; @@ -296,6 +377,11 @@ int mlx5_frag_buf_pools_init(struct mlx5_core_dev *dev) priv->frag_buf_node_pools[node] = node_pools; } + priv->dbg.frag_buf_dma_pools_debugfs = + debugfs_create_file("frag_buf_dma_pools", 0444, + priv->dbg.dbg_root, dev, + &mlx5_frag_buf_dma_pools_debugfs_fops); + return 0; } diff --git a/include/linux/mlx5/driver.h b/include/linux/mlx5/driver.h index 8b4d384125d1..9a4bb25d8e0a 100644 --- a/include/linux/mlx5/driver.h +++ b/include/linux/mlx5/driver.h @@ -547,6 +547,7 @@ struct mlx5_debugfs_entries { struct dentry *eq_debugfs; struct dentry *cq_debugfs; struct dentry *cmdif_debugfs; + struct dentry *frag_buf_dma_pools_debugfs; struct dentry *pages_debugfs; struct dentry *lag_debugfs; }; -- cgit v1.2.3 From 086abf9da661e3da9a9ef40b36963b230ec58112 Mon Sep 17 00:00:00 2001 From: Costa Shulyupin Date: Fri, 15 May 2026 21:45:25 +0300 Subject: include: Remove unused ks8851_mll.h The last user was removed in commit 72628da6d634 ("net: ks8851: Remove ks8851_mll.c") which consolidated the driver into a common implementation. No file includes this header. Signed-off-by: Costa Shulyupin Link: https://patch.msgid.link/20260515184531.1515418-1-costa.shul@redhat.com Signed-off-by: Jakub Kicinski --- include/linux/ks8851_mll.h | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 include/linux/ks8851_mll.h (limited to 'include') diff --git a/include/linux/ks8851_mll.h b/include/linux/ks8851_mll.h deleted file mode 100644 index 57c0a39ed796..000000000000 --- a/include/linux/ks8851_mll.h +++ /dev/null @@ -1,21 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * ks8861_mll platform data struct definition - * Copyright (c) 2012 BTicino S.p.A. - */ - -#ifndef _LINUX_KS8851_MLL_H -#define _LINUX_KS8851_MLL_H - -#include - -/** - * struct ks8851_mll_platform_data - Platform data of the KS8851_MLL network driver - * @macaddr: The MAC address of the device, set to all 0:s to use the on in - * the chip. - */ -struct ks8851_mll_platform_data { - u8 mac_addr[ETH_ALEN]; -}; - -#endif -- cgit v1.2.3 From 7d3ab852dcd853692197a830d20052127754a087 Mon Sep 17 00:00:00 2001 From: Bobby Eshleman Date: Thu, 14 May 2026 10:22:28 -0700 Subject: net: convert netmem_tx flag to enum Devices that support netmem TX previously set dev->netmem_tx = true. This was checked in validate_xmit_unreadable_skb() to drop unreadable skbs (skbs with dmabuf-backed frags) before they reach drivers that would mishandle them or devices that would not have the iommu mappings for them. A subsequent patch will introduce a third state for virtual devices that forward unreadable skbs without ever performing DMA on them. To prepare for that, convert the boolean dev->netmem_tx into an enum: NETMEM_TX_NONE - no netmem TX support (drop unreadable skbs) NETMEM_TX_DMA - full support, device does DMA Update the existing NIC drivers (bnxt, gve, mlx5, fbnic) and the validators in net/core to use the new enum. No functional change. Acked-by: Harshitha Ramamurthy Acked-by: Stanislav Fomichev Signed-off-by: Bobby Eshleman Link: https://patch.msgid.link/20260514-tcp-dm-netkit-v5-1-408c59b91e66@meta.com Signed-off-by: Jakub Kicinski --- Documentation/networking/netmem.rst | 5 ++++- Documentation/translations/zh_CN/networking/netmem.rst | 4 +++- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 2 +- drivers/net/ethernet/google/gve/gve_main.c | 2 +- drivers/net/ethernet/mellanox/mlx5/core/en_main.c | 2 +- drivers/net/ethernet/meta/fbnic/fbnic_netdev.c | 2 +- include/linux/netdevice.h | 7 ++++++- net/core/dev.c | 2 +- net/core/netdev-genl.c | 2 +- 9 files changed, 19 insertions(+), 9 deletions(-) (limited to 'include') diff --git a/Documentation/networking/netmem.rst b/Documentation/networking/netmem.rst index b63aded46337..5ccadba4f373 100644 --- a/Documentation/networking/netmem.rst +++ b/Documentation/networking/netmem.rst @@ -95,4 +95,7 @@ Driver TX Requirements netdev@, or reach out to the maintainers and/or almasrymina@google.com for help adding the netmem API. -2. Driver should declare support by setting `netdev->netmem_tx = true` +2. Driver should declare support by setting `netdev->netmem_tx` to the + appropriate mode: + + - `NETMEM_TX_DMA`: for physical devices that perform DMA. diff --git a/Documentation/translations/zh_CN/networking/netmem.rst b/Documentation/translations/zh_CN/networking/netmem.rst index fe351a240f02..9c84423b7528 100644 --- a/Documentation/translations/zh_CN/networking/netmem.rst +++ b/Documentation/translations/zh_CN/networking/netmem.rst @@ -89,4 +89,6 @@ dma-mapping API 去处理。 使用某个还不存在的 netmem API,你可以自行添加并提交到 netdev@,也可以联系维护 人员或者发送邮件至 almasrymina@google.com 寻求帮助。 -2. 驱动程序应通过设置 netdev->netmem_tx = true 来表明自身支持 netmem 功能。 +2. 驱动程序应将 `netdev->netmem_tx` 设置为适当的模式: + + - `NETMEM_TX_DMA`:适用于执行 DMA 的物理设备。 diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index 945a86696f2f..d4f93e62f583 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -17123,7 +17123,7 @@ static int bnxt_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) dev->queue_mgmt_ops = &bnxt_queue_mgmt_ops_unsupp; if (BNXT_SUPPORTS_QUEUE_API(bp)) dev->queue_mgmt_ops = &bnxt_queue_mgmt_ops; - dev->netmem_tx = true; + dev->netmem_tx = NETMEM_TX_DMA; rc = register_netdev(dev); if (rc) diff --git a/drivers/net/ethernet/google/gve/gve_main.c b/drivers/net/ethernet/google/gve/gve_main.c index 00750643e614..e4d78ae52daf 100644 --- a/drivers/net/ethernet/google/gve/gve_main.c +++ b/drivers/net/ethernet/google/gve/gve_main.c @@ -2894,7 +2894,7 @@ static int gve_probe(struct pci_dev *pdev, const struct pci_device_id *ent) goto abort_with_wq; if (!gve_is_gqi(priv) && !gve_is_qpl(priv)) - dev->netmem_tx = true; + dev->netmem_tx = NETMEM_TX_DMA; err = register_netdev(dev); if (err) diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c index a904e468c197..c5d26c6829a0 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c @@ -5963,7 +5963,7 @@ static void mlx5e_build_nic_netdev(struct net_device *netdev) netdev->priv_flags |= IFF_UNICAST_FLT; - netdev->netmem_tx = true; + netdev->netmem_tx = NETMEM_TX_DMA; netif_set_tso_max_size(netdev, GSO_MAX_SIZE); mlx5e_set_xdp_feature(priv); diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c b/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c index 4dea2bb58d2f..f99ca551c1ce 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c @@ -752,7 +752,7 @@ struct net_device *fbnic_netdev_alloc(struct fbnic_dev *fbd) netdev->netdev_ops = &fbnic_netdev_ops; netdev->stat_ops = &fbnic_stat_ops; netdev->queue_mgmt_ops = &fbnic_queue_mgmt_ops; - netdev->netmem_tx = true; + netdev->netmem_tx = NETMEM_TX_DMA; fbnic_set_ethtool_ops(netdev); diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index e7af71491a47..b7a4503f7cdb 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -1794,6 +1794,11 @@ enum netdev_stat_type { NETDEV_PCPU_STAT_DSTATS, /* struct pcpu_dstats */ }; +enum netmem_tx_mode { + NETMEM_TX_NONE, /* no netmem TX support */ + NETMEM_TX_DMA, /* DMA-capable netmem TX (real HW) */ +}; + enum netdev_reg_state { NETREG_UNINITIALIZED = 0, NETREG_REGISTERED, /* completed register_netdevice */ @@ -1815,7 +1820,7 @@ enum netdev_reg_state { * @lltx: device supports lockless Tx. Deprecated for real HW * drivers. Mainly used by logical interfaces, such as * bonding and tunnels - * @netmem_tx: device support netmem_tx. + * @netmem_tx: device netmem TX mode * * @name: This is the first field of the "visible" part of this structure * (i.e. as seen by users in the "Space.c" file). It is the name diff --git a/net/core/dev.c b/net/core/dev.c index 267cf3854395..23c1ad3d393d 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -3996,7 +3996,7 @@ static struct sk_buff *validate_xmit_unreadable_skb(struct sk_buff *skb, if (likely(skb_frags_readable(skb))) goto out; - if (!dev->netmem_tx) + if (dev->netmem_tx == NETMEM_TX_NONE) goto out_free; shinfo = skb_shinfo(skb); diff --git a/net/core/netdev-genl.c b/net/core/netdev-genl.c index b8f6076d8007..4d2c49371cdb 100644 --- a/net/core/netdev-genl.c +++ b/net/core/netdev-genl.c @@ -1164,7 +1164,7 @@ int netdev_nl_bind_tx_doit(struct sk_buff *skb, struct genl_info *info) goto err_unlock_netdev; } - if (!netdev->netmem_tx) { + if (netdev->netmem_tx == NETMEM_TX_NONE) { err = -EOPNOTSUPP; NL_SET_ERR_MSG(info->extack, "Driver does not support netmem TX"); -- cgit v1.2.3 From 6ce2bb048055ee599c0f35f1a90ff5db29ba5d65 Mon Sep 17 00:00:00 2001 From: Bobby Eshleman Date: Thu, 14 May 2026 10:22:29 -0700 Subject: net: netkit: declare NETMEM_TX_NO_DMA mode Some virtual devices like netkit (or ifb) never DMA and never touch frag contents, they just forward the skb to another device. They are unable to forward unreadable skbs, however, because they fail to pass TX validation checks on dev->netmem_tx. The existing two-state NETMEM_TX_NONE / NETMEM_TX_DMA doesn't give the TX validator enough information to differentiate devices that will attempt DMA on the unreadable skb from those that will simply route it untouched. Add a third mode to the enum so drivers can indicate 1) if they have netmem TX support, and 2) if they do, whether they are DMA-capable: NETMEM_TX_NO_DMA - pass-through, device never DMAs Widen dev->netmem_tx from a 1-bit field to 2 bits to fit the new value, and declare netkit as NETMEM_TX_NO_DMA. Devmem TX support over these devices comes in a follow-up patch. Acked-by: Stanislav Fomichev Signed-off-by: Bobby Eshleman Link: https://patch.msgid.link/20260514-tcp-dm-netkit-v5-2-408c59b91e66@meta.com Signed-off-by: Jakub Kicinski --- Documentation/networking/net_cachelines/net_device.rst | 2 +- Documentation/networking/netmem.rst | 3 +++ Documentation/translations/zh_CN/networking/netmem.rst | 3 +++ drivers/net/netkit.c | 1 + include/linux/netdevice.h | 3 ++- 5 files changed, 10 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/Documentation/networking/net_cachelines/net_device.rst b/Documentation/networking/net_cachelines/net_device.rst index 1c19bb7705df..7b3392553fd6 100644 --- a/Documentation/networking/net_cachelines/net_device.rst +++ b/Documentation/networking/net_cachelines/net_device.rst @@ -10,7 +10,7 @@ Type Name fastpath_tx_acce =================================== =========================== =================== =================== =================================================================================== unsigned_long:32 priv_flags read_mostly __dev_queue_xmit(tx) unsigned_long:1 lltx read_mostly HARD_TX_LOCK,HARD_TX_TRYLOCK,HARD_TX_UNLOCK(tx) -unsigned long:1 netmem_tx:1; read_mostly +unsigned_long:2 netmem_tx:2; read_mostly char name[16] struct netdev_name_node* name_node struct dev_ifalias* ifalias diff --git a/Documentation/networking/netmem.rst b/Documentation/networking/netmem.rst index 5ccadba4f373..217869d1108d 100644 --- a/Documentation/networking/netmem.rst +++ b/Documentation/networking/netmem.rst @@ -99,3 +99,6 @@ Driver TX Requirements appropriate mode: - `NETMEM_TX_DMA`: for physical devices that perform DMA. + + - `NETMEM_TX_NO_DMA`: for virtual or passthrough devices that do + not DMA, but still support handling of netmem-backed skbs. diff --git a/Documentation/translations/zh_CN/networking/netmem.rst b/Documentation/translations/zh_CN/networking/netmem.rst index 9c84423b7528..320f3eacf51b 100644 --- a/Documentation/translations/zh_CN/networking/netmem.rst +++ b/Documentation/translations/zh_CN/networking/netmem.rst @@ -92,3 +92,6 @@ dma-mapping API 去处理。 2. 驱动程序应将 `netdev->netmem_tx` 设置为适当的模式: - `NETMEM_TX_DMA`:适用于执行 DMA 的物理设备。 + + - `NETMEM_TX_NO_DMA`:适用于不执行 DMA 的虚拟或透传设备,但仍支持 + 处理 netmem 支持的 skb。 diff --git a/drivers/net/netkit.c b/drivers/net/netkit.c index 5e2eecc3165d..0ad6a806d7d5 100644 --- a/drivers/net/netkit.c +++ b/drivers/net/netkit.c @@ -466,6 +466,7 @@ static void netkit_setup(struct net_device *dev) dev->priv_flags |= IFF_NO_QUEUE; dev->priv_flags |= IFF_DISABLE_NETPOLL; dev->lltx = true; + dev->netmem_tx = NETMEM_TX_NO_DMA; dev->netdev_ops = &netkit_netdev_ops; dev->ethtool_ops = &netkit_ethtool_ops; diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index b7a4503f7cdb..bf3dd9b2c1a7 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -1797,6 +1797,7 @@ enum netdev_stat_type { enum netmem_tx_mode { NETMEM_TX_NONE, /* no netmem TX support */ NETMEM_TX_DMA, /* DMA-capable netmem TX (real HW) */ + NETMEM_TX_NO_DMA, /* no DMA, e.g. passthrough for virtual devs */ }; enum netdev_reg_state { @@ -2143,7 +2144,7 @@ struct net_device { struct_group(priv_flags_fast, unsigned long priv_flags:32; unsigned long lltx:1; - unsigned long netmem_tx:1; + unsigned long netmem_tx:2; ); const struct net_device_ops *netdev_ops; const struct header_ops *header_ops; -- cgit v1.2.3 From 9bf93cb2e180a58d5984ba13daee95903ff4fc14 Mon Sep 17 00:00:00 2001 From: Vadim Fedorenko Date: Fri, 15 May 2026 13:50:28 +0000 Subject: pps: bump PPS device count Modern systems may have more than 16 PPS sources and current hard-coded limit breaks registration of some devices. Let's bump the limit to 256 in hope it will be enough in foreseen future. Signed-off-by: Vadim Fedorenko Acked-by: Rodolfo Giometti Link: https://patch.msgid.link/20260515135028.2021318-1-vadim.fedorenko@linux.dev Signed-off-by: Jakub Kicinski --- include/uapi/linux/pps.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include') diff --git a/include/uapi/linux/pps.h b/include/uapi/linux/pps.h index 009ebcd8ced5..1088dea65e12 100644 --- a/include/uapi/linux/pps.h +++ b/include/uapi/linux/pps.h @@ -26,7 +26,7 @@ #include #define PPS_VERSION "5.3.6" -#define PPS_MAX_SOURCES 16 /* should be enough... */ +#define PPS_MAX_SOURCES 256 /* should be enough... */ /* Implementation note: the logical states ``assert'' and ``clear'' * are implemented in terms of the chip register, i.e. ``assert'' -- cgit v1.2.3 From c62675c217f10a7fec945b4671e21274fe92f097 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 15 May 2026 14:12:12 +0300 Subject: wifi: cfg80211: add a function to parse UHR DBE Add a function that takes the DBE information and parses it into an existing chandef that should hold the BSS channel. Signed-off-by: Johannes Berg Signed-off-by: Miri Korenblit Link: https://patch.msgid.link/20260515141209.4eb1490f5cc6.I3ca9421f1fe4c31073846b1b62017f12c75889de@changeid Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 10 +++ net/wireless/chan.c | 130 +++++++++++++++++++++++++++++++ net/wireless/tests/chan.c | 192 ++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 326 insertions(+), 6 deletions(-) (limited to 'include') diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index ddcf559430dd..69dc9a978861 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -1190,6 +1190,16 @@ int cfg80211_chandef_add_npca(struct wiphy *wiphy, struct cfg80211_chan_def *chandef, const struct ieee80211_uhr_npca_info *npca); +/** + * cfg80211_chandef_add_dbe - parse and add DBE information to chandef + * @chandef: the chandef to expand + * @dbe: the DBE information, must be size-checked if not %NULL + * + * Returns: 0 for success, a negative error code otherwise + */ +int cfg80211_chandef_add_dbe(struct cfg80211_chan_def *chandef, + const struct ieee80211_uhr_dbe_info *dbe); + /** * nl80211_send_chandef - sends the channel definition. * @msg: the msg to send channel definition diff --git a/net/wireless/chan.c b/net/wireless/chan.c index ed35b55b1b67..f0811efb5d0f 100644 --- a/net/wireless/chan.c +++ b/net/wireless/chan.c @@ -619,6 +619,136 @@ int cfg80211_chandef_add_npca(struct wiphy *wiphy, } EXPORT_SYMBOL(cfg80211_chandef_add_npca); +int cfg80211_chandef_add_dbe(struct cfg80211_chan_def *chandef, + const struct ieee80211_uhr_dbe_info *dbe) +{ + struct cfg80211_chan_def new_chandef = *chandef; + u16 starting_freq, bw_mhz, start_old, start_new; + u8 bw, punct_shift; + int offset, index; + + if (!dbe) + return 0; + + if (!cfg80211_chandef_valid(chandef)) + return -EINVAL; + + if (chandef->width == NL80211_CHAN_WIDTH_20_NOHT) + return -EINVAL; + + bw = u8_get_bits(dbe->params, IEEE80211_UHR_DBE_OPER_BANDWIDTH); + + switch (chandef->chan->band) { + case NL80211_BAND_5GHZ: + if (bw > IEEE80211_UHR_DBE_OPER_BW_160) + return -EINVAL; + if (chandef->chan->center_freq < 5745) + starting_freq = 5180; /* channel 36 */ + else + starting_freq = 5745; /* channel 149 */ + break; + case NL80211_BAND_6GHZ: + starting_freq = 5955; /* channel 1 center */ + break; + default: + return -EINVAL; + } + + switch (bw) { + case IEEE80211_UHR_DBE_OPER_BW_320_2: + case IEEE80211_UHR_DBE_OPER_BW_320_1: + if (chandef->width == NL80211_CHAN_WIDTH_160) + break; + fallthrough; + case IEEE80211_UHR_DBE_OPER_BW_160: + if (chandef->width == NL80211_CHAN_WIDTH_80) + break; + fallthrough; + case IEEE80211_UHR_DBE_OPER_BW_80: + if (chandef->width == NL80211_CHAN_WIDTH_40) + break; + fallthrough; + case IEEE80211_UHR_DBE_OPER_BW_40: + if (chandef->width == NL80211_CHAN_WIDTH_20) + break; + fallthrough; + default: + return -EINVAL; + } + + switch (bw) { + case IEEE80211_UHR_DBE_OPER_BW_320_2: + /* 320-2 starts shifted by 160 */ + starting_freq += 160; + fallthrough; + case IEEE80211_UHR_DBE_OPER_BW_320_1: + new_chandef.width = NL80211_CHAN_WIDTH_320; + bw_mhz = 320; + break; + case IEEE80211_UHR_DBE_OPER_BW_160: + new_chandef.width = NL80211_CHAN_WIDTH_160; + bw_mhz = 160; + break; + case IEEE80211_UHR_DBE_OPER_BW_80: + new_chandef.width = NL80211_CHAN_WIDTH_80; + bw_mhz = 80; + break; + case IEEE80211_UHR_DBE_OPER_BW_40: + new_chandef.width = NL80211_CHAN_WIDTH_40; + bw_mhz = 40; + break; + } + + /* this should only happen for 320-2 and misconfigured AP */ + if (chandef->chan->center_freq < starting_freq) + return -EINVAL; + + offset = chandef->chan->center_freq - starting_freq; + index = offset / bw_mhz; + start_new = starting_freq - 10 + index * bw_mhz; + new_chandef.center_freq1 = start_new + bw_mhz / 2; + + start_old = chandef->center_freq1 - + cfg80211_chandef_get_width(chandef) / 2; + + /* + * If the DBE channel extends downward below the lower + * edge of the BSS channel, we need to shift puncturing + * bitmaps up to adjust for that. + */ + if (start_new < start_old) + punct_shift = (start_old - start_new) / 20; + else + punct_shift = 0; + + new_chandef.punctured <<= punct_shift; + new_chandef.npca_punctured <<= punct_shift; + + if (dbe->params & IEEE80211_UHR_DBE_OPER_DIS_SUBCHANNEL_BITMAP_PRES) { + u16 punct_mask = ((1 << (bw_mhz / 40)) - 1) << punct_shift; + u16 punctured = le16_to_cpu(dbe->dis_subch_bmap[0]); + + if ((punctured & punct_mask) != (new_chandef.punctured & punct_mask)) + return -EINVAL; + + new_chandef.punctured = punctured; + } + + if (!cfg80211_chandef_valid(&new_chandef)) + return -EINVAL; + + /* + * If e.g. a 40 MHz BSS channel (erroneously) occupies the center of the + * DBE 80 MHz channel, they would be incompatible; check and reject. + */ + if (!cfg80211_chandef_compatible(&new_chandef, chandef)) + return -EINVAL; + + *chandef = new_chandef; + return 0; +} +EXPORT_SYMBOL(cfg80211_chandef_add_dbe); + static const struct cfg80211_chan_def * check_chandef_primary_compat(const struct cfg80211_chan_def *c1, const struct cfg80211_chan_def *c2, diff --git a/net/wireless/tests/chan.c b/net/wireless/tests/chan.c index 7b97b731993c..65eb18c498de 100644 --- a/net/wireless/tests/chan.c +++ b/net/wireless/tests/chan.c @@ -2,13 +2,18 @@ /* * KUnit tests for channel helper functions * - * Copyright (C) 2023-2024 Intel Corporation + * Copyright (C) 2023-2024, 2026 Intel Corporation */ #include #include MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING"); +static struct ieee80211_channel chan_2ghz_1 = { + .band = NL80211_BAND_2GHZ, + .center_freq = 2412, +}; + static struct ieee80211_channel chan_6ghz_1 = { .band = NL80211_BAND_6GHZ, .center_freq = 5955, @@ -215,14 +220,189 @@ static void test_chandef_compat(struct kunit *test) KUNIT_EXPECT_PTR_EQ(test, ret, expect); } -static struct kunit_case chandef_compat_test_cases[] = { +static const struct chandef_dbe_case { + const char *desc; + struct cfg80211_chan_def c; + u8 dbe[3]; + bool fails; + u16 cf1; +} chandef_dbe_cases[] = { + { + .desc = "non-HT failure", + .c = { + .width = NL80211_CHAN_WIDTH_20_NOHT, + .chan = &chan_6ghz_1, + .center_freq1 = 5955, + }, + .dbe[0] = IEEE80211_UHR_DBE_OPER_BW_40, + .fails = true, + }, + { + .desc = "2.4 GHz fails", + .c = { + .width = NL80211_CHAN_WIDTH_20, + .chan = &chan_2ghz_1, + .center_freq1 = 2412, + }, + .dbe[0] = IEEE80211_UHR_DBE_OPER_BW_40, + .fails = true, + }, + { + .desc = "DBE narrower", + .c = { + .width = NL80211_CHAN_WIDTH_320, + .chan = &chan_6ghz_1, + .center_freq1 = 5955 + 10 + 20 + 40 + 80, + }, + .dbe[0] = IEEE80211_UHR_DBE_OPER_BW_160, + .fails = true, + }, + { + .desc = "DBE to 320-1", + .c = { + .width = NL80211_CHAN_WIDTH_160, + .chan = &chan_6ghz_105, + .center_freq1 = 6475 + 30, + }, + .dbe[0] = IEEE80211_UHR_DBE_OPER_BW_320_1, + .cf1 = 6425, + }, + { + .desc = "DBE to 320-2", + .c = { + .width = NL80211_CHAN_WIDTH_160, + .chan = &chan_6ghz_105, + .center_freq1 = 6475 + 30, + }, + .dbe[0] = IEEE80211_UHR_DBE_OPER_BW_320_2, + .cf1 = 6585, + }, + { + .desc = "bad disabled subchannel bitmap - not enough in BSS (1)", + .c = { + .width = NL80211_CHAN_WIDTH_160, + .chan = &chan_6ghz_105, + .center_freq1 = 6475 + 30, + .punctured = 0x0001, + }, + .dbe[0] = IEEE80211_UHR_DBE_OPER_BW_320_1 | + IEEE80211_UHR_DBE_OPER_DIS_SUBCHANNEL_BITMAP_PRES, + /* DBE disabled subchannel bitmap == 0 */ + .fails = true, + }, + { + .desc = "bad disabled subchannel bitmap - too much in BSS (1)", + .c = { + .width = NL80211_CHAN_WIDTH_160, + .chan = &chan_6ghz_105, + .center_freq1 = 6475 + 30, + .punctured = 0x0001, + }, + .dbe[0] = IEEE80211_UHR_DBE_OPER_BW_320_1 | + IEEE80211_UHR_DBE_OPER_DIS_SUBCHANNEL_BITMAP_PRES, + /* DBE disabled subchannel bitmap == 0x0300 */ + .dbe[2] = 0x03, + .fails = true, + }, + { + .desc = "bad disabled subchannel bitmap - not enough in BSS (2)", + .c = { + .width = NL80211_CHAN_WIDTH_160, + .chan = &chan_6ghz_105, + .center_freq1 = 6475 + 30, + .punctured = 0x0001, + }, + .dbe[0] = IEEE80211_UHR_DBE_OPER_BW_320_2 | + IEEE80211_UHR_DBE_OPER_DIS_SUBCHANNEL_BITMAP_PRES, + /* DBE disabled subchannel bitmap == 0 */ + .fails = true, + }, + { + .desc = "bad disabled subchannel bitmap - too much in BSS (2)", + .c = { + .width = NL80211_CHAN_WIDTH_160, + .chan = &chan_6ghz_105, + .center_freq1 = 6475 + 30, + .punctured = 0x0001, + }, + .dbe[0] = IEEE80211_UHR_DBE_OPER_BW_320_2 | + IEEE80211_UHR_DBE_OPER_DIS_SUBCHANNEL_BITMAP_PRES, + /* DBE disabled subchannel bitmap == 0x03 */ + .dbe[1] = 0x03, + .fails = true, + }, + { + .desc = "bad disabled subchannel bitmap - bad bitmap", + .c = { + .width = NL80211_CHAN_WIDTH_160, + .chan = &chan_6ghz_105, + .center_freq1 = 6475 + 30, + .punctured = 0x0001, + }, + .dbe[0] = IEEE80211_UHR_DBE_OPER_BW_320_1 | + IEEE80211_UHR_DBE_OPER_DIS_SUBCHANNEL_BITMAP_PRES, + /* DBE disabled subchannel bitmap == 0x1100 */ + .dbe[2] = 0x11, + .fails = true, + }, + { + .desc = "good disabled subchannel bitmap (1)", + .c = { + .width = NL80211_CHAN_WIDTH_160, + .chan = &chan_6ghz_105, + .center_freq1 = 6475 + 30, + .punctured = 0x0003, + }, + .dbe[0] = IEEE80211_UHR_DBE_OPER_BW_320_1 | + IEEE80211_UHR_DBE_OPER_DIS_SUBCHANNEL_BITMAP_PRES, + /* DBE disabled subchannel bitmap == 0x0300 */ + .dbe[2] = 0x03, + .cf1 = 6425, + }, + { + .desc = "good disabled subchannel bitmap (2)", + .c = { + .width = NL80211_CHAN_WIDTH_160, + .chan = &chan_6ghz_105, + .center_freq1 = 6475 + 30, + .punctured = 0x0003, + }, + .dbe[0] = IEEE80211_UHR_DBE_OPER_BW_320_2 | + IEEE80211_UHR_DBE_OPER_DIS_SUBCHANNEL_BITMAP_PRES, + /* DBE disabled subchannel bitmap == 0x0003 */ + .dbe[1] = 0x03, + .cf1 = 6585, + }, +}; + +KUNIT_ARRAY_PARAM_DESC(chandef_dbe, chandef_dbe_cases, desc) + +static void test_chandef_dbe(struct kunit *test) +{ + const struct chandef_dbe_case *params = test->param_value; + struct cfg80211_chan_def c = params->c; + int ret; + + KUNIT_EXPECT_EQ(test, cfg80211_chandef_valid(¶ms->c), true); + + ret = cfg80211_chandef_add_dbe(&c, (void *)params->dbe); + KUNIT_EXPECT_EQ(test, ret != 0, params->fails); + + if (params->fails) + return; + + KUNIT_EXPECT_EQ(test, c.center_freq1, params->cf1); +} + +static struct kunit_case chandef_test_cases[] = { KUNIT_CASE_PARAM(test_chandef_compat, chandef_compat_gen_params), + KUNIT_CASE_PARAM(test_chandef_dbe, chandef_dbe_gen_params), {} }; -static struct kunit_suite chandef_compat = { - .name = "cfg80211-chandef-compat", - .test_cases = chandef_compat_test_cases, +static struct kunit_suite chandef = { + .name = "cfg80211-chandef", + .test_cases = chandef_test_cases, }; -kunit_test_suite(chandef_compat); +kunit_test_suite(chandef); -- cgit v1.2.3 From 5026c42d7f147fb6bc4e063abfa5b52bfd52ca35 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Mon, 18 May 2026 17:52:06 -0700 Subject: smc: Use flexible array for SMCD connections Store the per-DMB connection pointers in the SMCD device allocation instead of allocating a separate connection array. This keeps the connection table tied to the SMCD device lifetime and simplifies the allocation and cleanup paths. Signed-off-by: Rosen Penev Reviewed-by: Sidraya Jayagond Link: https://patch.msgid.link/20260519005206.628071-1-rosenp@gmail.com Signed-off-by: Jakub Kicinski --- include/net/smc.h | 2 +- net/smc/smc_ism.c | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) (limited to 'include') diff --git a/include/net/smc.h b/include/net/smc.h index bfdc4c41f019..a2bc3ab88075 100644 --- a/include/net/smc.h +++ b/include/net/smc.h @@ -40,7 +40,6 @@ struct smcd_dev { struct dibs_dev *dibs; struct list_head list; spinlock_t lock; - struct smc_connection **conn; struct list_head vlan; struct workqueue_struct *event_wq; u8 pnetid[SMC_MAX_PNETID_LEN]; @@ -50,6 +49,7 @@ struct smcd_dev { atomic_t lgr_cnt; wait_queue_head_t lgrs_deleted; u8 going_away : 1; + struct smc_connection *conn[]; }; #define SMC_HS_CTRL_NAME_MAX 16 diff --git a/net/smc/smc_ism.c b/net/smc/smc_ism.c index e0dba2c7b6e3..bde938c5eb39 100644 --- a/net/smc/smc_ism.c +++ b/net/smc/smc_ism.c @@ -467,17 +467,14 @@ static struct smcd_dev *smcd_alloc_dev(const char *name, int max_dmbs) { struct smcd_dev *smcd; - smcd = kzalloc_obj(*smcd); + smcd = kzalloc_flex(*smcd, conn, max_dmbs); if (!smcd) return NULL; - smcd->conn = kzalloc_objs(struct smc_connection *, max_dmbs); - if (!smcd->conn) - goto free_smcd; smcd->event_wq = alloc_ordered_workqueue("ism_evt_wq-%s)", WQ_MEM_RECLAIM, name); if (!smcd->event_wq) - goto free_conn; + goto free_smcd; spin_lock_init(&smcd->lock); spin_lock_init(&smcd->lgr_lock); @@ -486,8 +483,6 @@ static struct smcd_dev *smcd_alloc_dev(const char *name, int max_dmbs) init_waitqueue_head(&smcd->lgrs_deleted); return smcd; -free_conn: - kfree(smcd->conn); free_smcd: kfree(smcd); return NULL; @@ -557,7 +552,6 @@ static void smcd_unregister_dev(struct dibs_dev *dibs) list_del_init(&smcd->list); mutex_unlock(&smcd_dev_list.mutex); destroy_workqueue(smcd->event_wq); - kfree(smcd->conn); kfree(smcd); } -- cgit v1.2.3 From 4566269803ff284010550263299647f1bf9b01d5 Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Mon, 18 May 2026 16:24:55 +0800 Subject: net: enetc: add basic operations to the FDB table The FDB table is used for MAC learning lookups and MAC forwarding lookups. Each table entry includes information such as a FID and MAC address that may be unicast or multicast and a forwarding destination field containing a port bitmap identifying the associated port(s) with the MAC address. FDB table entries can be static or dynamic. Static entries are added from software whereby dynamic entries are added either by software or by the hardware as MAC addresses are learned in the datapath. The FDB table can only be managed by the command BD ring using table management protocol version 2.0. Table management command operations Add, Delete, Update and Query are supported. And the FDB table supports three access methods: Entry ID, Exact Match Key Element and Search. This patch adds the following basic supports to the FDB table. ntmp_fdbt_update_entry() - update the configuration element data of a specified FDB entry ntmp_fdbt_delete_entry() - delete a specified FDB entry ntmp_fdbt_add_entry() - add an entry into the FDB table ntmp_fdbt_search_port_entry() - Search the FDB entry on the specified port based on RESUME_ENTRY_ID. Signed-off-by: Wei Fang Link: https://patch.msgid.link/20260518082506.1318236-5-wei.fang@nxp.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/freescale/enetc/ntmp.c | 205 ++++++++++++++++++++- .../net/ethernet/freescale/enetc/ntmp_private.h | 61 +++++- include/linux/fsl/ntmp.h | 44 ++++- 3 files changed, 307 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c b/drivers/net/ethernet/freescale/enetc/ntmp.c index c94a928622fd..6074eeafd5a2 100644 --- a/drivers/net/ethernet/freescale/enetc/ntmp.c +++ b/drivers/net/ethernet/freescale/enetc/ntmp.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) /* * NETC NTMP (NETC Table Management Protocol) 2.0 Library - * Copyright 2025 NXP + * Copyright 2025-2026 NXP */ #include @@ -21,11 +21,17 @@ /* Define NTMP Table ID */ #define NTMP_MAFT_ID 1 #define NTMP_RSST_ID 3 +#define NTMP_FDBT_ID 15 /* Generic Update Actions for most tables */ #define NTMP_GEN_UA_CFGEU BIT(0) #define NTMP_GEN_UA_STSEU BIT(1) +/* Query Action: 0: Full query. 1: Query entry ID, the fields after entry + * ID are not returned. + */ +#define NTMP_QA_ENTRY_ID 1 + #define NTMP_ENTRY_ID_SIZE 4 #define RSST_ENTRY_NUM 64 #define RSST_STSE_DATA_SIZE(n) ((n) * 8) @@ -260,6 +266,8 @@ static const char *ntmp_table_name(int tbl_id) return "MAC Address Filter Table"; case NTMP_RSST_ID: return "RSS Table"; + case NTMP_FDBT_ID: + return "FDB Table"; default: return "Unknown Table"; } @@ -496,5 +504,200 @@ unlock_cbdr: } EXPORT_SYMBOL_GPL(ntmp_rsst_query_entry); +/** + * ntmp_fdbt_add_entry - add an entry into the FDB table + * @user: target ntmp_user struct + * @entry_id: returned value, the entry ID of the new added entry + * @keye: key element data + * @cfge: configuration element data + * + * Return: 0 on success, otherwise a negative error code + */ +int ntmp_fdbt_add_entry(struct ntmp_user *user, u32 *entry_id, + const struct fdbt_keye_data *keye, + const struct fdbt_cfge_data *cfge) +{ + struct fdbt_resp_query *resp; + struct fdbt_req_ua *req; + struct netc_swcbd swcbd; + struct netc_cbdr *cbdr; + union netc_cbd cbd; + u32 len; + int err; + + swcbd.size = sizeof(*req); + err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req); + if (err) + return err; + + /* Request data */ + ntmp_fill_crd(&req->crd, user->tbl.fdbt_ver, NTMP_QA_ENTRY_ID, + NTMP_GEN_UA_CFGEU); + req->ak.exact.keye = *keye; + req->cfge = *cfge; + + len = NTMP_LEN(swcbd.size, sizeof(*resp)); + /* The entry ID is allotted by hardware, so we need to perform + * a query action after the add action to get the entry ID from + * hardware. + */ + ntmp_fill_request_hdr(&cbd, swcbd.dma, len, NTMP_FDBT_ID, + NTMP_CMD_AQ, NTMP_AM_EXACT_KEY); + + ntmp_select_and_lock_cbdr(user, &cbdr); + err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd); + if (err) { + dev_err(user->dev, "Failed to add %s entry, err: %pe\n", + ntmp_table_name(NTMP_FDBT_ID), ERR_PTR(err)); + goto unlock_cbdr; + } + + if (entry_id) { + resp = (struct fdbt_resp_query *)req; + *entry_id = le32_to_cpu(resp->entry_id); + } + +unlock_cbdr: + ntmp_unlock_cbdr(cbdr); + + return err; +} +EXPORT_SYMBOL_GPL(ntmp_fdbt_add_entry); + +/** + * ntmp_fdbt_update_entry - update the configuration element data of the + * specified FDB entry + * @user: target ntmp_user struct + * @entry_id: the specified entry ID of the FDB table + * @cfge: configuration element data + * + * Return: 0 on success, otherwise a negative error code + */ +int ntmp_fdbt_update_entry(struct ntmp_user *user, u32 entry_id, + const struct fdbt_cfge_data *cfge) +{ + struct fdbt_req_ua *req; + struct netc_swcbd swcbd; + struct netc_cbdr *cbdr; + union netc_cbd cbd; + u32 len; + int err; + + swcbd.size = sizeof(*req); + err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req); + if (err) + return err; + + /* Request data */ + ntmp_fill_crd(&req->crd, user->tbl.fdbt_ver, 0, NTMP_GEN_UA_CFGEU); + req->ak.eid.entry_id = cpu_to_le32(entry_id); + req->cfge = *cfge; + + /* Request header */ + len = NTMP_LEN(swcbd.size, NTMP_STATUS_RESP_LEN); + ntmp_fill_request_hdr(&cbd, swcbd.dma, len, NTMP_FDBT_ID, + NTMP_CMD_UPDATE, NTMP_AM_ENTRY_ID); + + ntmp_select_and_lock_cbdr(user, &cbdr); + err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd); + if (err) + dev_err(user->dev, "Failed to update %s entry, err: %pe\n", + ntmp_table_name(NTMP_FDBT_ID), ERR_PTR(err)); + + ntmp_unlock_cbdr(cbdr); + + return err; +} +EXPORT_SYMBOL_GPL(ntmp_fdbt_update_entry); + +/** + * ntmp_fdbt_delete_entry - delete the specified FDB entry + * @user: target ntmp_user struct + * @entry_id: the specified ID of the FDB entry + * + * Return: 0 on success, otherwise a negative error code + */ +int ntmp_fdbt_delete_entry(struct ntmp_user *user, u32 entry_id) +{ + u32 req_len = sizeof(struct fdbt_req_qd); + + return ntmp_delete_entry_by_id(user, NTMP_FDBT_ID, + user->tbl.fdbt_ver, + entry_id, req_len, + NTMP_STATUS_RESP_LEN); +} +EXPORT_SYMBOL_GPL(ntmp_fdbt_delete_entry); + +/** + * ntmp_fdbt_search_port_entry - Search the FDB entry on the specified + * port based on RESUME_ENTRY_ID + * @user: target ntmp_user struct + * @port: the specified switch port ID + * @resume_entry_id: it is both an input and an output. As an input, it + * represents the FDB entry ID to be searched. If it is a NULL entry ID, + * it indicates that the first FDB entry for that port is being searched. + * As an output, it represents the next FDB entry ID to be searched. + * @entry: returned value, the response data of the searched FDB entry + * + * Return: 0 on success, otherwise a negative error code + */ +int ntmp_fdbt_search_port_entry(struct ntmp_user *user, int port, + u32 *resume_entry_id, + struct fdbt_entry_data *entry) +{ + struct fdbt_resp_query *resp; + struct fdbt_req_qd *req; + struct netc_swcbd swcbd; + struct netc_cbdr *cbdr; + union netc_cbd cbd; + u32 len; + int err; + + swcbd.size = sizeof(*req); + err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req); + if (err) + return err; + + /* Request data */ + ntmp_fill_crd(&req->crd, user->tbl.fdbt_ver, 0, 0); + req->ak.search.resume_eid = cpu_to_le32(*resume_entry_id); + req->ak.search.cfge.port_bitmap = cpu_to_le32(BIT(port)); + /* Match CFGE_DATA[PORT_BITMAP] field */ + req->ak.search.cfge_mc = FDBT_CFGE_MC_PORT_BITMAP; + + /* Request header */ + len = NTMP_LEN(swcbd.size, sizeof(*resp)); + ntmp_fill_request_hdr(&cbd, swcbd.dma, len, NTMP_FDBT_ID, + NTMP_CMD_QUERY, NTMP_AM_SEARCH); + + ntmp_select_and_lock_cbdr(user, &cbdr); + err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd); + if (err) { + dev_err(user->dev, + "Failed to search %s entry on port %d, err: %pe\n", + ntmp_table_name(NTMP_FDBT_ID), port, ERR_PTR(err)); + goto unlock_cbdr; + } + + if (!cbd.resp_hdr.num_matched) { + entry->entry_id = NTMP_NULL_ENTRY_ID; + *resume_entry_id = NTMP_NULL_ENTRY_ID; + goto unlock_cbdr; + } + + resp = (struct fdbt_resp_query *)req; + *resume_entry_id = le32_to_cpu(resp->status); + entry->entry_id = le32_to_cpu(resp->entry_id); + entry->keye = resp->keye; + entry->cfge = resp->cfge; + entry->acte = resp->acte; + +unlock_cbdr: + ntmp_unlock_cbdr(cbdr); + + return err; +} +EXPORT_SYMBOL_GPL(ntmp_fdbt_search_port_entry); + MODULE_DESCRIPTION("NXP NETC Library"); MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/net/ethernet/freescale/enetc/ntmp_private.h b/drivers/net/ethernet/freescale/enetc/ntmp_private.h index f8dff3ba2c28..b0b5805ac4f6 100644 --- a/drivers/net/ethernet/freescale/enetc/ntmp_private.h +++ b/drivers/net/ethernet/freescale/enetc/ntmp_private.h @@ -1,7 +1,7 @@ /* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */ /* * NTMP table request and response data buffer formats - * Copyright 2025 NXP + * Copyright 2025-2026 NXP */ #ifndef __NTMP_PRIVATE_H @@ -11,6 +11,7 @@ #include #define NTMP_EID_REQ_LEN 8 +#define NTMP_STATUS_RESP_LEN 4 #define NETC_CBDR_BD_NUM 256 #define NETC_CBDRCIR_INDEX GENMASK(9, 0) #define NETC_CBDRCIR_SBE BIT(31) @@ -30,6 +31,7 @@ union netc_cbd { #define NTMP_CMD_QUERY BIT(2) #define NTMP_CMD_ADD BIT(3) #define NTMP_CMD_QU (NTMP_CMD_QUERY | NTMP_CMD_UPDATE) +#define NTMP_CMD_AQ (NTMP_CMD_ADD | NTMP_CMD_QUERY) u8 access_method; #define NTMP_ACCESS_METHOD GENMASK(7, 4) #define NTMP_AM_ENTRY_ID 0 @@ -97,4 +99,61 @@ struct rsst_req_update { u8 groups[]; }; +/* Access Key Format of FDB Table */ +struct fdbt_ak_eid { + __le32 entry_id; + __le32 resv[7]; +}; + +struct fdbt_ak_exact { + struct fdbt_keye_data keye; + __le32 resv[5]; +}; + +struct fdbt_ak_search { + __le32 resume_eid; + struct fdbt_keye_data keye; + struct fdbt_cfge_data cfge; + u8 acte; + u8 keye_mc; +#define FDBT_KEYE_MAC GENMASK(1, 0) + u8 cfge_mc; +#define FDBT_CFGE_MC GENMASK(2, 0) +#define FDBT_CFGE_MC_ANY 0 +#define FDBT_CFGE_MC_DYNAMIC 1 +#define FDBT_CFGE_MC_PORT_BITMAP 2 +#define FDBT_CFGE_MC_DYNAMIC_AND_PORT_BITMAP 3 + u8 acte_mc; +#define FDBT_ACTE_MC BIT(0) +}; + +union fdbt_access_key { + struct fdbt_ak_eid eid; + struct fdbt_ak_exact exact; + struct fdbt_ak_search search; +}; + +/* FDB Table Request Data Buffer Format of Update and Add actions */ +struct fdbt_req_ua { + struct ntmp_cmn_req_data crd; + union fdbt_access_key ak; + struct fdbt_cfge_data cfge; +}; + +/* FDB Table Request Data Buffer Format of Query and Delete actions */ +struct fdbt_req_qd { + struct ntmp_cmn_req_data crd; + union fdbt_access_key ak; +}; + +/* FDB Table Response Data Buffer Format of Query action */ +struct fdbt_resp_query { + __le32 status; + __le32 entry_id; + struct fdbt_keye_data keye; + struct fdbt_cfge_data cfge; + u8 acte; + u8 resv[3]; +}; + #endif diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h index 83a449b4d6ec..4cfff835954e 100644 --- a/include/linux/fsl/ntmp.h +++ b/include/linux/fsl/ntmp.h @@ -1,11 +1,13 @@ /* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */ -/* Copyright 2025 NXP */ +/* Copyright 2025-2026 NXP */ #ifndef __NETC_NTMP_H #define __NETC_NTMP_H #include #include +#define NTMP_NULL_ENTRY_ID 0xffffffffU + struct maft_keye_data { u8 mac_addr[ETH_ALEN]; __le16 resv; @@ -29,6 +31,7 @@ struct netc_cbdr_regs { struct netc_tbl_vers { u8 maft_ver; u8 rsst_ver; + u8 fdbt_ver; }; struct netc_swcbd { @@ -68,6 +71,36 @@ struct maft_entry_data { struct maft_cfge_data cfge; }; +struct fdbt_keye_data { + u8 mac_addr[ETH_ALEN]; /* big-endian */ + __le16 resv0; + __le16 fid; +#define FDBT_FID GENMASK(11, 0) + __le16 resv1; +}; + +struct fdbt_cfge_data { + __le32 port_bitmap; +#define FDBT_PORT_BITMAP GENMASK(23, 0) + __le32 cfg; +#define FDBT_OETEID GENMASK(1, 0) +#define FDBT_EPORT GENMASK(6, 2) +#define FDBT_IMIRE BIT(7) +#define FDBT_CTD GENMASK(10, 9) +#define FDBT_DYNAMIC BIT(11) +#define FDBT_TIMECAPE BIT(12) + __le32 et_eid; +}; + +struct fdbt_entry_data { + u32 entry_id; + struct fdbt_keye_data keye; + struct fdbt_cfge_data cfge; + u8 acte; +#define FDBT_ACT_CNT GENMASK(6, 0) +#define FDBT_ACT_FLAG BIT(7) +}; + #if IS_ENABLED(CONFIG_NXP_NETC_LIB) int ntmp_init_cbdr(struct netc_cbdr *cbdr, struct device *dev, const struct netc_cbdr_regs *regs); @@ -83,6 +116,15 @@ int ntmp_rsst_update_entry(struct ntmp_user *user, const u32 *table, int count); int ntmp_rsst_query_entry(struct ntmp_user *user, u32 *table, int count); +int ntmp_fdbt_add_entry(struct ntmp_user *user, u32 *entry_id, + const struct fdbt_keye_data *keye, + const struct fdbt_cfge_data *cfge); +int ntmp_fdbt_update_entry(struct ntmp_user *user, u32 entry_id, + const struct fdbt_cfge_data *cfge); +int ntmp_fdbt_delete_entry(struct ntmp_user *user, u32 entry_id); +int ntmp_fdbt_search_port_entry(struct ntmp_user *user, int port, + u32 *resume_entry_id, + struct fdbt_entry_data *entry); #else static inline int ntmp_init_cbdr(struct netc_cbdr *cbdr, struct device *dev, const struct netc_cbdr_regs *regs) -- cgit v1.2.3 From d0ac4d4bd299f5948be6a0a65e5f1b360fdfb686 Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Mon, 18 May 2026 16:24:56 +0800 Subject: net: enetc: add support for the "Add" operation to VLAN filter table The VLAN filter table contains configuration and control information for each VLAN configured on the switch. Each VLAN entry includes the VLAN port membership, which FID to use in the FDB lookup, which spanning tree group to use, the egress frame modification actions to apply to a frame exiting form this VLAN, and various configuration and control parameters for this VLAN. The VLAN filter table can only be managed by the command BD ring using table management protocol version 2.0. The table supports Add, Delete, Update and Query operations. And the table supports 3 access methods: Entry ID, Exact Match Key Element and Search. But currently we only add the ntmp_vft_add_entry() helper to support the upcoming switch driver to add an entry to the VLAN filter table. Other interfaces will be added in the future. Signed-off-by: Wei Fang Link: https://patch.msgid.link/20260518082506.1318236-6-wei.fang@nxp.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/freescale/enetc/ntmp.c | 50 ++++++++++++++++++++++ .../net/ethernet/freescale/enetc/ntmp_private.h | 19 ++++++++ include/linux/fsl/ntmp.h | 24 +++++++++++ 3 files changed, 93 insertions(+) (limited to 'include') diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c b/drivers/net/ethernet/freescale/enetc/ntmp.c index 6074eeafd5a2..db74a9107975 100644 --- a/drivers/net/ethernet/freescale/enetc/ntmp.c +++ b/drivers/net/ethernet/freescale/enetc/ntmp.c @@ -22,6 +22,7 @@ #define NTMP_MAFT_ID 1 #define NTMP_RSST_ID 3 #define NTMP_FDBT_ID 15 +#define NTMP_VFT_ID 18 /* Generic Update Actions for most tables */ #define NTMP_GEN_UA_CFGEU BIT(0) @@ -268,6 +269,8 @@ static const char *ntmp_table_name(int tbl_id) return "RSS Table"; case NTMP_FDBT_ID: return "FDB Table"; + case NTMP_VFT_ID: + return "VLAN Filter Table"; default: return "Unknown Table"; } @@ -699,5 +702,52 @@ unlock_cbdr: } EXPORT_SYMBOL_GPL(ntmp_fdbt_search_port_entry); +/** + * ntmp_vft_add_entry - add an entry into the VLAN filter table + * @user: target ntmp_user struct + * @vid: VLAN ID + * @cfge: configuration element data + * + * Return: 0 on success, otherwise a negative error code + */ +int ntmp_vft_add_entry(struct ntmp_user *user, u16 vid, + const struct vft_cfge_data *cfge) +{ + struct netc_swcbd swcbd; + struct vft_req_ua *req; + struct netc_cbdr *cbdr; + union netc_cbd cbd; + u32 len; + int err; + + swcbd.size = sizeof(*req); + err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req); + if (err) + return err; + + /* Request data */ + ntmp_fill_crd(&req->crd, user->tbl.vft_ver, 0, + NTMP_GEN_UA_CFGEU); + req->ak.exact.vid = cpu_to_le16(vid); + req->cfge = *cfge; + + /* Request header */ + len = NTMP_LEN(swcbd.size, NTMP_STATUS_RESP_LEN); + ntmp_fill_request_hdr(&cbd, swcbd.dma, len, NTMP_VFT_ID, + NTMP_CMD_ADD, NTMP_AM_EXACT_KEY); + + ntmp_select_and_lock_cbdr(user, &cbdr); + err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd); + if (err) + dev_err(user->dev, + "Failed to add %s entry, vid: %u, err: %pe\n", + ntmp_table_name(NTMP_VFT_ID), vid, ERR_PTR(err)); + + ntmp_unlock_cbdr(cbdr); + + return err; +} +EXPORT_SYMBOL_GPL(ntmp_vft_add_entry); + MODULE_DESCRIPTION("NXP NETC Library"); MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/net/ethernet/freescale/enetc/ntmp_private.h b/drivers/net/ethernet/freescale/enetc/ntmp_private.h index b0b5805ac4f6..575ee783be47 100644 --- a/drivers/net/ethernet/freescale/enetc/ntmp_private.h +++ b/drivers/net/ethernet/freescale/enetc/ntmp_private.h @@ -156,4 +156,23 @@ struct fdbt_resp_query { u8 resv[3]; }; +/* Access Key Format of VLAN Filter Table */ +struct vft_ak_exact { + __le16 vid; /* bit0~11: VLAN ID, other bits are reserved */ + __le16 resv; +}; + +union vft_access_key { + __le32 entry_id; /* entry_id match */ + struct vft_ak_exact exact; + __le32 resume_entry_id; /* search */ +}; + +/* VLAN Filter Table Request Data Buffer Format of Update and Add actions */ +struct vft_req_ua { + struct ntmp_cmn_req_data crd; + union vft_access_key ak; + struct vft_cfge_data cfge; +}; + #endif diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h index 4cfff835954e..3672e0dc7726 100644 --- a/include/linux/fsl/ntmp.h +++ b/include/linux/fsl/ntmp.h @@ -32,6 +32,7 @@ struct netc_tbl_vers { u8 maft_ver; u8 rsst_ver; u8 fdbt_ver; + u8 vft_ver; }; struct netc_swcbd { @@ -101,6 +102,27 @@ struct fdbt_entry_data { #define FDBT_ACT_FLAG BIT(7) }; +struct vft_cfge_data { + __le32 bitmap_stg; +#define VFT_PORT_MEMBERSHIP GENMASK(23, 0) +#define VFT_STG_ID_MASK GENMASK(27, 24) +#define VFT_STG_ID(g) FIELD_PREP(VFT_STG_ID_MASK, (g)) + __le16 fid; +#define VFT_FID GENMASK(11, 0) + __le16 cfg; +#define VFT_MLO GENMASK(2, 0) +#define VFT_MFO GENMASK(4, 3) +#define VFT_IPMFE BIT(6) +#define VFT_IPMFLE BIT(7) +#define VFT_PGA BIT(8) +#define VFT_SFDA BIT(10) +#define VFT_OSFDA BIT(11) +#define VFT_FDBAFSS BIT(12) + __le32 eta_port_bitmap; +#define VFT_ETA_PORT_BITMAP GENMASK(23, 0) + __le32 et_eid; +}; + #if IS_ENABLED(CONFIG_NXP_NETC_LIB) int ntmp_init_cbdr(struct netc_cbdr *cbdr, struct device *dev, const struct netc_cbdr_regs *regs); @@ -125,6 +147,8 @@ int ntmp_fdbt_delete_entry(struct ntmp_user *user, u32 entry_id); int ntmp_fdbt_search_port_entry(struct ntmp_user *user, int port, u32 *resume_entry_id, struct fdbt_entry_data *entry); +int ntmp_vft_add_entry(struct ntmp_user *user, u16 vid, + const struct vft_cfge_data *cfge); #else static inline int ntmp_init_cbdr(struct netc_cbdr *cbdr, struct device *dev, const struct netc_cbdr_regs *regs) -- cgit v1.2.3 From cb4d95d79d85e9614a701041100b5012bf595b44 Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Mon, 18 May 2026 16:24:57 +0800 Subject: net: enetc: add support for the "Update" operation to buffer pool table The buffer pool table contains buffer pool configuration and operational information. Each entry corresponds to a buffer pool. The Entry ID value represents the buffer pool ID to access. The buffer pool table is a static bounded index table, buffer pools are always present and enabled. It only supports Update and Query operations, This patch only adds ntmp_bpt_update_entry() helper to support updating the specified entry of the buffer pool table. Query action to the table will be added in the future. Signed-off-by: Wei Fang Link: https://patch.msgid.link/20260518082506.1318236-7-wei.fang@nxp.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/freescale/enetc/ntmp.c | 43 ++++++++++++++++++++++ .../net/ethernet/freescale/enetc/ntmp_private.h | 6 +++ include/linux/fsl/ntmp.h | 25 +++++++++++++ 3 files changed, 74 insertions(+) (limited to 'include') diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c b/drivers/net/ethernet/freescale/enetc/ntmp.c index db74a9107975..ad89be85b185 100644 --- a/drivers/net/ethernet/freescale/enetc/ntmp.c +++ b/drivers/net/ethernet/freescale/enetc/ntmp.c @@ -23,11 +23,15 @@ #define NTMP_RSST_ID 3 #define NTMP_FDBT_ID 15 #define NTMP_VFT_ID 18 +#define NTMP_BPT_ID 41 /* Generic Update Actions for most tables */ #define NTMP_GEN_UA_CFGEU BIT(0) #define NTMP_GEN_UA_STSEU BIT(1) +/* Specific Update Actions for some tables */ +#define BPT_UA_BPSEU BIT(1) + /* Query Action: 0: Full query. 1: Query entry ID, the fields after entry * ID are not returned. */ @@ -271,6 +275,8 @@ static const char *ntmp_table_name(int tbl_id) return "FDB Table"; case NTMP_VFT_ID: return "VLAN Filter Table"; + case NTMP_BPT_ID: + return "Buffer Pool Table"; default: return "Unknown Table"; } @@ -749,5 +755,42 @@ int ntmp_vft_add_entry(struct ntmp_user *user, u16 vid, } EXPORT_SYMBOL_GPL(ntmp_vft_add_entry); +int ntmp_bpt_update_entry(struct ntmp_user *user, u32 entry_id, + const struct bpt_cfge_data *cfge) +{ + struct bpt_req_update *req; + struct netc_swcbd swcbd; + struct netc_cbdr *cbdr; + union netc_cbd cbd; + int err; + + swcbd.size = sizeof(*req); + err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req); + if (err) + return err; + + /* Note that BPT_UA_BPSEU is used to update the BPSE_DATA of the entry, + * which is maintained by the hardware. The BPSE_DATA is not present in + * the request data for 'Update' operation. + */ + ntmp_fill_crd_eid(&req->rbe, user->tbl.bpt_ver, 0, + NTMP_GEN_UA_CFGEU | BPT_UA_BPSEU, entry_id); + req->cfge = *cfge; + ntmp_fill_request_hdr(&cbd, swcbd.dma, NTMP_LEN(swcbd.size, 0), + NTMP_BPT_ID, NTMP_CMD_UPDATE, NTMP_AM_ENTRY_ID); + + ntmp_select_and_lock_cbdr(user, &cbdr); + err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd); + if (err) + dev_err(user->dev, + "Failed to update %s entry 0x%x, err: %pe\n", + ntmp_table_name(NTMP_BPT_ID), entry_id, ERR_PTR(err)); + + ntmp_unlock_cbdr(cbdr); + + return err; +} +EXPORT_SYMBOL_GPL(ntmp_bpt_update_entry); + MODULE_DESCRIPTION("NXP NETC Library"); MODULE_LICENSE("Dual BSD/GPL"); diff --git a/drivers/net/ethernet/freescale/enetc/ntmp_private.h b/drivers/net/ethernet/freescale/enetc/ntmp_private.h index 575ee783be47..64df49e9a3ef 100644 --- a/drivers/net/ethernet/freescale/enetc/ntmp_private.h +++ b/drivers/net/ethernet/freescale/enetc/ntmp_private.h @@ -175,4 +175,10 @@ struct vft_req_ua { struct vft_cfge_data cfge; }; +/* Buffer Pool Table Request Data Buffer Format of Update action */ +struct bpt_req_update { + struct ntmp_req_by_eid rbe; + struct bpt_cfge_data cfge; +}; + #endif diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h index 3672e0dc7726..5fded868725f 100644 --- a/include/linux/fsl/ntmp.h +++ b/include/linux/fsl/ntmp.h @@ -33,6 +33,7 @@ struct netc_tbl_vers { u8 rsst_ver; u8 fdbt_ver; u8 vft_ver; + u8 bpt_ver; }; struct netc_swcbd { @@ -123,6 +124,28 @@ struct vft_cfge_data { __le32 et_eid; }; +struct bpt_bpse_data { + __le32 amount_used; + __le32 amount_used_hwm; + u8 bpd_fc_state; +#define BPT_FC_STATE BIT(0) +#define BPT_BPD BIT(1) +} __packed; + +struct bpt_cfge_data { + u8 fccfg_sbpen; +#define BPT_FC_CFG GENMASK(2, 1) +#define BPT_FC_CFG_EN_BPFC 1 + u8 pfc_vector; + __le16 max_thresh; + __le16 fc_on_thresh; + __le16 fc_off_thresh; + __le16 sbp_thresh; + __le16 resv; + __le32 sbp_eid; + __le32 fc_ports; +}; + #if IS_ENABLED(CONFIG_NXP_NETC_LIB) int ntmp_init_cbdr(struct netc_cbdr *cbdr, struct device *dev, const struct netc_cbdr_regs *regs); @@ -149,6 +172,8 @@ int ntmp_fdbt_search_port_entry(struct ntmp_user *user, int port, struct fdbt_entry_data *entry); int ntmp_vft_add_entry(struct ntmp_user *user, u16 vid, const struct vft_cfge_data *cfge); +int ntmp_bpt_update_entry(struct ntmp_user *user, u32 entry_id, + const struct bpt_cfge_data *cfge); #else static inline int ntmp_init_cbdr(struct netc_cbdr *cbdr, struct device *dev, const struct netc_cbdr_regs *regs) -- cgit v1.2.3 From 123db6a2751144f3e86cb029ebac4ef4777a5507 Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Mon, 18 May 2026 16:24:58 +0800 Subject: net: enetc: add support for "Add" and "Delete" operations to IPFT The ingress port filter table (IPFT )contains a set of filters each capable of classifying incoming traffic using a mix of L2, L3, and L4 parsed and arbitrary field data. As a result of a filter match, several actions can be specified such as on whether to deny or allow a frame, overriding internal QoS attributes associated with the frame and setting parameters for the subsequent frame processing functions, such as stream identification, policing, ingress mirroring. Each entry corresponds to a filter. The ingress port filter entries are added using a precedence value. If a frame matches multiple entries, the entry with the higher precedence is used. Currently, this patch only adds "Add" and "Delete" operations to the ingress port filter table. These two interfaces will be used by both ENETC driver and NETC switch driver. Signed-off-by: Wei Fang Link: https://patch.msgid.link/20260518082506.1318236-8-wei.fang@nxp.com Signed-off-by: Paolo Abeni --- drivers/net/ethernet/freescale/enetc/ntmp.c | 80 +++++++++++++++++++ .../net/ethernet/freescale/enetc/ntmp_private.h | 36 +++++++++ include/linux/fsl/ntmp.h | 93 ++++++++++++++++++++++ 3 files changed, 209 insertions(+) (limited to 'include') diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c b/drivers/net/ethernet/freescale/enetc/ntmp.c index ad89be85b185..635032d24dc7 100644 --- a/drivers/net/ethernet/freescale/enetc/ntmp.c +++ b/drivers/net/ethernet/freescale/enetc/ntmp.c @@ -21,6 +21,7 @@ /* Define NTMP Table ID */ #define NTMP_MAFT_ID 1 #define NTMP_RSST_ID 3 +#define NTMP_IPFT_ID 13 #define NTMP_FDBT_ID 15 #define NTMP_VFT_ID 18 #define NTMP_BPT_ID 41 @@ -271,6 +272,8 @@ static const char *ntmp_table_name(int tbl_id) return "MAC Address Filter Table"; case NTMP_RSST_ID: return "RSS Table"; + case NTMP_IPFT_ID: + return "Ingress Port Filter Table"; case NTMP_FDBT_ID: return "FDB Table"; case NTMP_VFT_ID: @@ -513,6 +516,83 @@ unlock_cbdr: } EXPORT_SYMBOL_GPL(ntmp_rsst_query_entry); +/** + * ntmp_ipft_add_entry - add an entry into the ingress port filter table + * @user: target ntmp_user struct + * @entry: the entry data, entry->cfge (configuration element data) and + * entry->keye (key element data) are used as input. Since the entry ID + * is assigned by the hardware, so entry->entry_id is a returned value + * for the driver to use, the driver can update/delete/query the entry + * based on the entry_id. + * + * Return: 0 on success, otherwise a negative error code + */ +int ntmp_ipft_add_entry(struct ntmp_user *user, + struct ipft_entry_data *entry) +{ + struct ipft_resp_query *resp; + struct ipft_req_ua *req; + struct netc_swcbd swcbd; + struct netc_cbdr *cbdr; + union netc_cbd cbd; + u32 len; + int err; + + swcbd.size = sizeof(*resp); + err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req); + if (err) + return err; + + /* Note that NTMP_GEN_UA_STSEU is used to reset the statistics of + * the entry. The STSE_DATA is not present in the request data for + * 'Add' operation. + */ + ntmp_fill_crd(&req->crd, user->tbl.ipft_ver, NTMP_QA_ENTRY_ID, + NTMP_GEN_UA_CFGEU | NTMP_GEN_UA_STSEU); + req->ak.keye = entry->keye; + req->cfge = entry->cfge; + + len = NTMP_LEN(sizeof(*req), swcbd.size); + ntmp_fill_request_hdr(&cbd, swcbd.dma, len, NTMP_IPFT_ID, + NTMP_CMD_AQ, NTMP_AM_TERNARY_KEY); + + ntmp_select_and_lock_cbdr(user, &cbdr); + err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd); + if (err) { + dev_err(user->dev, "Failed to add %s entry, err: %pe\n", + ntmp_table_name(NTMP_IPFT_ID), ERR_PTR(err)); + + goto unlock_cbdr; + } + + resp = (struct ipft_resp_query *)req; + entry->entry_id = le32_to_cpu(resp->entry_id); + +unlock_cbdr: + ntmp_unlock_cbdr(cbdr); + + return err; +} +EXPORT_SYMBOL_GPL(ntmp_ipft_add_entry); + +/** + * ntmp_ipft_delete_entry - delete a specified ingress port filter table entry + * @user: target ntmp_user struct + * @entry_id: the specified ID of the ingress port filter table entry + * + * Return: 0 on success, otherwise a negative error code + */ +int ntmp_ipft_delete_entry(struct ntmp_user *user, u32 entry_id) +{ + u32 req_len = sizeof(struct ipft_req_qd); + + return ntmp_delete_entry_by_id(user, NTMP_IPFT_ID, + user->tbl.ipft_ver, + entry_id, req_len, + NTMP_STATUS_RESP_LEN); +} +EXPORT_SYMBOL_GPL(ntmp_ipft_delete_entry); + /** * ntmp_fdbt_add_entry - add an entry into the FDB table * @user: target ntmp_user struct diff --git a/drivers/net/ethernet/freescale/enetc/ntmp_private.h b/drivers/net/ethernet/freescale/enetc/ntmp_private.h index 64df49e9a3ef..0a9b87286105 100644 --- a/drivers/net/ethernet/freescale/enetc/ntmp_private.h +++ b/drivers/net/ethernet/freescale/enetc/ntmp_private.h @@ -99,6 +99,42 @@ struct rsst_req_update { u8 groups[]; }; +/* Ingress Port Filter Table Response Data Buffer Format of Query action */ +struct ipft_resp_query { + __le32 status; + __le32 entry_id; + struct ipft_keye_data keye; + __le64 match_count; /* STSE_DATA */ + struct ipft_cfge_data cfge; +} __packed; + +struct ipft_ak_eid { + __le32 entry_id; + __le32 resv[52]; +}; + +union ipft_access_key { + struct ipft_ak_eid eid; + struct ipft_keye_data keye; +}; + +/* Ingress Port Filter Table Request Data Buffer Format of Update and + * Add actions + */ +struct ipft_req_ua { + struct ntmp_cmn_req_data crd; + union ipft_access_key ak; + struct ipft_cfge_data cfge; +}; + +/* Ingress Port Filter Table Request Data Buffer Format of Query and + * Delete actions + */ +struct ipft_req_qd { + struct ntmp_req_by_eid rbe; + __le32 resv[52]; +}; + /* Access Key Format of FDB Table */ struct fdbt_ak_eid { __le32 entry_id; diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h index 5fded868725f..88166f9ad3a2 100644 --- a/include/linux/fsl/ntmp.h +++ b/include/linux/fsl/ntmp.h @@ -7,6 +7,7 @@ #include #define NTMP_NULL_ENTRY_ID 0xffffffffU +#define IPFT_MAX_PLD_LEN 24 struct maft_keye_data { u8 mac_addr[ETH_ALEN]; @@ -34,6 +35,7 @@ struct netc_tbl_vers { u8 fdbt_ver; u8 vft_ver; u8 bpt_ver; + u8 ipft_ver; }; struct netc_swcbd { @@ -73,6 +75,94 @@ struct maft_entry_data { struct maft_cfge_data cfge; }; +struct ipft_pld_byte { + u8 data; + u8 mask; +}; + +struct ipft_keye_data { + __le16 precedence; + __le16 resv0[3]; + __le16 frm_attr_flags; +#define IPFT_FAF_OVLAN BIT(2) +#define IPFT_FAF_IVLAN BIT(3) +#define IPFT_FAF_IP_HDR BIT(7) +#define IPFT_FAF_IP_VER6 BIT(8) +#define IPFT_FAF_L4_CODE GENMASK(11, 10) +#define IPFT_FAF_TCP_HDR 1 +#define IPFT_FAF_UDP_HDR 2 +#define IPFT_FAF_SCTP_HDR 3 +#define IPFT_FAF_WOL_MAGIC BIT(12) + __le16 frm_attr_flags_mask; + __le16 dscp; +#define IPFT_DSCP GENMASK(5, 0) +#define IPFT_DSCP_MASK GENMASK(11, 6) +#define IPFT_DSCP_MASK_ALL 0x3f + __le16 src_port; /* This field is reserved for ENETC */ +#define IPFT_SRC_PORT GENMASK(4, 0) +#define IPFT_SRC_PORT_MASK GENMASK(9, 5) +#define IPFT_SRC_PORT_MASK_ALL 0x1f + __be16 outer_vlan_tci; + __be16 outer_vlan_tci_mask; + u8 dmac[ETH_ALEN]; + u8 dmac_mask[ETH_ALEN]; + u8 smac[ETH_ALEN]; + u8 smac_mask[ETH_ALEN]; + __be16 inner_vlan_tci; + __be16 inner_vlan_tci_mask; + __be16 ethertype; + __be16 ethertype_mask; + u8 ip_protocol; + u8 ip_protocol_mask; + __le16 resv1[7]; + __be32 ip_src[4]; + __le32 resv2[2]; + __be32 ip_src_mask[4]; + __be16 l4_src_port; + __be16 l4_src_port_mask; + __le32 resv3; + __be32 ip_dst[4]; + __le32 resv4[2]; + __be32 ip_dst_mask[4]; + __be16 l4_dst_port; + __be16 l4_dst_port_mask; + __le32 resv5; + struct ipft_pld_byte byte[IPFT_MAX_PLD_LEN]; +}; + +struct ipft_cfge_data { + __le32 cfg; +#define IPFT_IPV GENMASK(3, 0) +#define IPFT_OIPV BIT(4) +#define IPFT_DR GENMASK(6, 5) +#define IPFT_ODR BIT(7) +#define IPFT_FLTFA GENMASK(10, 8) +#define IPFT_FLTFA_DISCARD 0 +#define IPFT_FLTFA_PERMIT 1 +/* Redirect is only for switch */ +#define IPFT_FLTFA_REDIRECT 2 +#define IPFT_IMIRE BIT(11) +#define IPFT_WOLTE BIT(12) +#define IPFT_FLTA GENMASK(14, 13) +#define IPFT_FLTA_RP 1 +#define IPFT_FLTA_IS 2 +#define IPFT_FLTA_SI_BITMAP 3 +#define IPFT_RPR GENMASK(16, 15) +#define IPFT_CTD BIT(17) +#define IPFT_HR GENMASK(21, 18) +#define IPFT_TIMECAPE BIT(22) +#define IPFT_RRT BIT(23) +#define IPFT_BL2F BIT(24) +#define IPFT_EVMEID GENMASK(31, 28) + __le32 flta_tgt; +}; + +struct ipft_entry_data { + u32 entry_id; /* hardware assigns entry ID */ + struct ipft_keye_data keye; + struct ipft_cfge_data cfge; +}; + struct fdbt_keye_data { u8 mac_addr[ETH_ALEN]; /* big-endian */ __le16 resv0; @@ -161,6 +251,9 @@ int ntmp_rsst_update_entry(struct ntmp_user *user, const u32 *table, int count); int ntmp_rsst_query_entry(struct ntmp_user *user, u32 *table, int count); +int ntmp_ipft_add_entry(struct ntmp_user *user, + struct ipft_entry_data *entry); +int ntmp_ipft_delete_entry(struct ntmp_user *user, u32 entry_id); int ntmp_fdbt_add_entry(struct ntmp_user *user, u32 *entry_id, const struct fdbt_keye_data *keye, const struct fdbt_cfge_data *cfge); -- cgit v1.2.3 From 0850005c26d2623f3d77489cf27d342191a97b5c Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Mon, 18 May 2026 16:25:00 +0800 Subject: net: dsa: add NETC switch tag support The NXP NETC switch tag is a proprietary header added to frames after the source MAC address. The switch tag has 3 types, and each type has 1 ~ 4 subtypes, the details are as follows. Forward NXP switch tag (Type=0): Represents forwarded frames. - SubType = 0 - Normal frame processing. To_Port NXP switch tag (Type=1): Represents frames that are to be sent to a specific switch port. - SubType = 0. No request to perform timestamping. - SubType = 1. Request to perform one-step timestamping. - SubType = 2. Request to perform two-step timestamping. - SubType = 3. Request to perform both one-step timestamping and two-step timestamping. To_Host NXP switch tag (Type=2): Represents frames redirected or copied to the switch management port. - SubType = 0. Received frames redirected or copied to the switch management port. - SubType = 1. Received frames redirected or copied to the switch management port with captured timestamp at the switch port where the frame was received. - SubType = 2. Transmit timestamp response (two-step timestamping). In addition, the length of different type switch tag is different, the minimum length is 6 bytes, the maximum length is 14 bytes. Currently, Forward tag, SubType 0 of To_Port tag and Subtype 0 of To_Host tag are supported. More tags will be supported in the future. Signed-off-by: Wei Fang Link: https://patch.msgid.link/20260518082506.1318236-10-wei.fang@nxp.com Signed-off-by: Paolo Abeni --- include/linux/dsa/tag_netc.h | 14 +++ include/net/dsa.h | 2 + include/uapi/linux/if_ether.h | 1 + net/dsa/Kconfig | 10 ++ net/dsa/Makefile | 1 + net/dsa/tag_netc.c | 214 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 242 insertions(+) create mode 100644 include/linux/dsa/tag_netc.h create mode 100644 net/dsa/tag_netc.c (limited to 'include') diff --git a/include/linux/dsa/tag_netc.h b/include/linux/dsa/tag_netc.h new file mode 100644 index 000000000000..fe964722e5b0 --- /dev/null +++ b/include/linux/dsa/tag_netc.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright 2025-2026 NXP + */ + +#ifndef __NET_DSA_TAG_NETC_H +#define __NET_DSA_TAG_NETC_H + +#include +#include + +#define NETC_TAG_MAX_LEN 14 + +#endif diff --git a/include/net/dsa.h b/include/net/dsa.h index 4cc67469cf2e..8c16ef23cc10 100644 --- a/include/net/dsa.h +++ b/include/net/dsa.h @@ -58,6 +58,7 @@ struct tc_action; #define DSA_TAG_PROTO_YT921X_VALUE 30 #define DSA_TAG_PROTO_MXL_GSW1XX_VALUE 31 #define DSA_TAG_PROTO_MXL862_VALUE 32 +#define DSA_TAG_PROTO_NETC_VALUE 33 enum dsa_tag_protocol { DSA_TAG_PROTO_NONE = DSA_TAG_PROTO_NONE_VALUE, @@ -93,6 +94,7 @@ enum dsa_tag_protocol { DSA_TAG_PROTO_YT921X = DSA_TAG_PROTO_YT921X_VALUE, DSA_TAG_PROTO_MXL_GSW1XX = DSA_TAG_PROTO_MXL_GSW1XX_VALUE, DSA_TAG_PROTO_MXL862 = DSA_TAG_PROTO_MXL862_VALUE, + DSA_TAG_PROTO_NETC = DSA_TAG_PROTO_NETC_VALUE, }; struct dsa_switch; diff --git a/include/uapi/linux/if_ether.h b/include/uapi/linux/if_ether.h index df9d44a11540..fb5efc8e06cc 100644 --- a/include/uapi/linux/if_ether.h +++ b/include/uapi/linux/if_ether.h @@ -123,6 +123,7 @@ #define ETH_P_DSA_A5PSW 0xE001 /* A5PSW Tag Value [ NOT AN OFFICIALLY REGISTERED ID ] */ #define ETH_P_IFE 0xED3E /* ForCES inter-FE LFB type */ #define ETH_P_AF_IUCV 0xFBFB /* IBM af_iucv [ NOT AN OFFICIALLY REGISTERED ID ] */ +#define ETH_P_NXP_NETC 0xFD3A /* NXP NETC DSA [ NOT AN OFFICIALLY REGISTERED ID ] */ #define ETH_P_802_3_MIN 0x0600 /* If the value in the ethernet type is more than this value * then the frame is Ethernet II. Else it is 802.3 */ diff --git a/net/dsa/Kconfig b/net/dsa/Kconfig index 5ed8c704636d..d5e725b90d78 100644 --- a/net/dsa/Kconfig +++ b/net/dsa/Kconfig @@ -125,6 +125,16 @@ config NET_DSA_TAG_KSZ Say Y if you want to enable support for tagging frames for the Microchip 8795/937x/9477/9893 families of switches. +config NET_DSA_TAG_NETC + tristate "Tag driver for NXP NETC switches" + help + Say Y or M if you want to enable support for the NXP Switch Tag (NST), + as implemented by NXP NETC switches having version 4.3 or later. The + switch tag is a proprietary header added to frames after the source + MAC address, it has 3 types and each type has different subtypes, so + its length depends on the type and subtype of the tag, the maximum + length is 14 bytes. + config NET_DSA_TAG_OCELOT tristate "Tag driver for Ocelot family of switches, using NPI port" select PACKING diff --git a/net/dsa/Makefile b/net/dsa/Makefile index bf7247759a64..b8c2667cd14a 100644 --- a/net/dsa/Makefile +++ b/net/dsa/Makefile @@ -30,6 +30,7 @@ obj-$(CONFIG_NET_DSA_TAG_LAN9303) += tag_lan9303.o obj-$(CONFIG_NET_DSA_TAG_MTK) += tag_mtk.o obj-$(CONFIG_NET_DSA_TAG_MXL_862XX) += tag_mxl862xx.o obj-$(CONFIG_NET_DSA_TAG_MXL_GSW1XX) += tag_mxl-gsw1xx.o +obj-$(CONFIG_NET_DSA_TAG_NETC) += tag_netc.o obj-$(CONFIG_NET_DSA_TAG_NONE) += tag_none.o obj-$(CONFIG_NET_DSA_TAG_OCELOT) += tag_ocelot.o obj-$(CONFIG_NET_DSA_TAG_OCELOT_8021Q) += tag_ocelot_8021q.o diff --git a/net/dsa/tag_netc.c b/net/dsa/tag_netc.c new file mode 100644 index 000000000000..ccedfe3a80b6 --- /dev/null +++ b/net/dsa/tag_netc.c @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2025-2026 NXP + */ + +#include + +#include "tag.h" + +#define NETC_NAME "nxp_netc" + +/* Forward NXP switch tag */ +#define NETC_TAG_FORWARD 0 + +/* To_Port NXP switch tag */ +#define NETC_TAG_TO_PORT 1 +/* SubType0: No request to perform timestamping */ +#define NETC_TAG_TP_SUBTYPE0 0 + +/* To_Host NXP switch tag */ +#define NETC_TAG_TO_HOST 2 +/* SubType0: frames redirected or copied to CPU port */ +#define NETC_TAG_TH_SUBTYPE0 0 +/* SubType1: frames redirected or copied to CPU port with timestamp */ +#define NETC_TAG_TH_SUBTYPE1 1 +/* SubType2: Transmit timestamp response (two-step timestamping) */ +#define NETC_TAG_TH_SUBTYPE2 2 + +/* NETC switch tag lengths */ +#define NETC_TAG_FORWARD_LEN 6 +#define NETC_TAG_TP_SUBTYPE0_LEN 6 +#define NETC_TAG_TH_SUBTYPE0_LEN 6 +#define NETC_TAG_TH_SUBTYPE1_LEN 14 +#define NETC_TAG_TH_SUBTYPE2_LEN 14 +#define NETC_TAG_CMN_LEN 5 + +#define NETC_TAG_SUBTYPE GENMASK(3, 0) +#define NETC_TAG_TYPE GENMASK(7, 4) +#define NETC_TAG_QV BIT(0) +#define NETC_TAG_IPV GENMASK(4, 2) +#define NETC_TAG_SWITCH GENMASK(2, 0) +#define NETC_TAG_PORT GENMASK(7, 3) + +struct netc_tag_cmn { + __be16 tpid; + u8 type; + u8 qos; + u8 switch_port; +} __packed; + +static void netc_fill_common_tag(struct netc_tag_cmn *tag, u8 type, + u8 subtype, u8 sw_id, u8 port, u8 ipv) +{ + tag->tpid = htons(ETH_P_NXP_NETC); + tag->type = FIELD_PREP(NETC_TAG_TYPE, type) | + FIELD_PREP(NETC_TAG_SUBTYPE, subtype); + tag->qos = NETC_TAG_QV | FIELD_PREP(NETC_TAG_IPV, ipv); + tag->switch_port = FIELD_PREP(NETC_TAG_SWITCH, sw_id) | + FIELD_PREP(NETC_TAG_PORT, port); +} + +static void *netc_fill_common_tp_tag(struct sk_buff *skb, + struct net_device *ndev, + u8 subtype, int tag_len) +{ + struct dsa_port *dp = dsa_user_to_port(ndev); + u16 queue = skb_get_queue_mapping(skb); + s8 ipv = netdev_txq_to_tc(ndev, queue); + void *tag; + + if (unlikely(ipv < 0)) + ipv = 0; + + skb_push(skb, tag_len); + dsa_alloc_etype_header(skb, tag_len); + + tag = dsa_etype_header_pos_tx(skb); + memset(tag + NETC_TAG_CMN_LEN, 0, tag_len - NETC_TAG_CMN_LEN); + /* As 'dsa,member' is a required property for NETC switch, the member + * is used to specify the switch ID (thus the hardware switch ID and + * the software switch ID are consistent), its range is 1 ~ 7. The + * NETC switch driver will check this value, and if it is invalid, + * the switch driver will fail the probe. + * In addition, according to the nxp,netc-switch.yaml doc, the port + * index will not be greater than 0xf. + */ + netc_fill_common_tag(tag, NETC_TAG_TO_PORT, subtype, + dp->ds->index, dp->index, ipv); + + return tag; +} + +static void netc_fill_tp_tag_subtype0(struct sk_buff *skb, + struct net_device *ndev) +{ + netc_fill_common_tp_tag(skb, ndev, NETC_TAG_TP_SUBTYPE0, + NETC_TAG_TP_SUBTYPE0_LEN); +} + +/* Currently only support To_Port tag, subtype 0 */ +static struct sk_buff *netc_xmit(struct sk_buff *skb, + struct net_device *ndev) +{ + netc_fill_tp_tag_subtype0(skb, ndev); + + return skb; +} + +static int netc_get_rx_tag_len(int type, int subtype) +{ + /* Only NETC_TAG_TO_HOST and NETC_TAG_FORWARD are expected in RX, + * NETC_TAG_TO_PORT is a TX switch tag that does not exist in RX. + */ + if (type == NETC_TAG_TO_HOST) { + if (subtype == NETC_TAG_TH_SUBTYPE1) + return NETC_TAG_TH_SUBTYPE1_LEN; + else if (subtype == NETC_TAG_TH_SUBTYPE2) + return NETC_TAG_TH_SUBTYPE2_LEN; + else + return NETC_TAG_TH_SUBTYPE0_LEN; + } + + return NETC_TAG_FORWARD_LEN; +} + +static struct sk_buff *netc_rcv(struct sk_buff *skb, + struct net_device *ndev) +{ + struct netc_tag_cmn *tag_cmn; + int tag_len, sw_id, port; + int type, subtype; + + if (unlikely(!pskb_may_pull(skb, NETC_TAG_MAX_LEN))) + return NULL; + + tag_cmn = dsa_etype_header_pos_rx(skb); + if (ntohs(tag_cmn->tpid) != ETH_P_NXP_NETC) { + dev_warn_ratelimited(&ndev->dev, "Unknown TPID 0x%04x\n", + ntohs(tag_cmn->tpid)); + + return NULL; + } + + if (tag_cmn->qos & NETC_TAG_QV) + skb->priority = FIELD_GET(NETC_TAG_IPV, tag_cmn->qos); + + sw_id = FIELD_GET(NETC_TAG_SWITCH, tag_cmn->switch_port); + /* ENETC VEPA switch ID (0) is not supported yet */ + if (!sw_id) { + dev_warn_ratelimited(&ndev->dev, + "VEPA switch ID is not supported yet\n"); + + return NULL; + } + + port = FIELD_GET(NETC_TAG_PORT, tag_cmn->switch_port); + skb->dev = dsa_conduit_find_user(ndev, sw_id, port); + if (!skb->dev) + return NULL; + + type = FIELD_GET(NETC_TAG_TYPE, tag_cmn->type); + subtype = FIELD_GET(NETC_TAG_SUBTYPE, tag_cmn->type); + if (type == NETC_TAG_FORWARD) { + dsa_default_offload_fwd_mark(skb); + } else if (type == NETC_TAG_TO_HOST) { + /* Currently only subtype0 supported */ + if (subtype != NETC_TAG_TH_SUBTYPE0) + return NULL; + } else { + dev_warn_ratelimited(&ndev->dev, + "Unexpected tag type %d\n", type); + return NULL; + } + + /* Remove Switch tag from the frame */ + tag_len = netc_get_rx_tag_len(type, subtype); + skb_pull_rcsum(skb, tag_len); + dsa_strip_etype_header(skb, tag_len); + + return skb; +} + +static void netc_flow_dissect(const struct sk_buff *skb, __be16 *proto, + int *offset) +{ + struct netc_tag_cmn *tag_cmn = (struct netc_tag_cmn *)(skb->data - 2); + int subtype = FIELD_GET(NETC_TAG_SUBTYPE, tag_cmn->type); + int type = FIELD_GET(NETC_TAG_TYPE, tag_cmn->type); + int tag_len = netc_get_rx_tag_len(type, subtype); + + /* The RX minimum frame length of the NETC switch port is 64 bytes, + * and the frame is received by the ENETC driver. From the hardware + * perspective, the receive buffer of RX BD is at least 128 bytes, + * so the switch tag header is guaranteed to be in the linear region + * of the skb. + */ + *offset = tag_len; + *proto = ((__be16 *)skb->data)[(tag_len / 2) - 1]; +} + +static const struct dsa_device_ops netc_netdev_ops = { + .name = NETC_NAME, + .proto = DSA_TAG_PROTO_NETC, + .xmit = netc_xmit, + .rcv = netc_rcv, + .needed_headroom = NETC_TAG_MAX_LEN, + .flow_dissect = netc_flow_dissect, +}; + +MODULE_DESCRIPTION("DSA tag driver for NXP NETC switch family"); +MODULE_LICENSE("GPL"); + +MODULE_ALIAS_DSA_TAG_DRIVER(DSA_TAG_PROTO_NETC, NETC_NAME); +module_dsa_tag_driver(netc_netdev_ops); -- cgit v1.2.3 From 25049d8b6e6b87f7ffcf53ce5ea1b51528b8677f Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Mon, 18 May 2026 16:25:05 +0800 Subject: net: dsa: netc: add support for the standardized counters Each user port of the NETC switch supports 802.3 basic and mandatory managed objects statistic counters and IETF Management Information Database (MIB) package (RFC2665) and Remote Network Monitoring (RMON) counters. And all of these counters are 64-bit registers. In addition, some user ports support preemption, so these ports have two MACs, MAC 0 is the express MAC (eMAC), MAC 1 is the preemptible MAC (pMAC). So for ports that support preemption, the statistics are the sum of the pMAC and eMAC statistics. Note that the current switch driver does not support preemption, all frames are sent and received via the eMAC by default. The statistics read from the pMAC should be zero. Signed-off-by: Wei Fang Link: https://patch.msgid.link/20260518082506.1318236-15-wei.fang@nxp.com Signed-off-by: Paolo Abeni --- drivers/net/dsa/netc/Makefile | 2 +- drivers/net/dsa/netc/netc_ethtool.c | 190 ++++++++++++++++++++++++++++++++++ drivers/net/dsa/netc/netc_main.c | 4 + drivers/net/dsa/netc/netc_switch.h | 12 +++ drivers/net/dsa/netc/netc_switch_hw.h | 120 +++++++++++++++++++++ include/linux/fsl/netc_global.h | 6 ++ 6 files changed, 333 insertions(+), 1 deletion(-) create mode 100644 drivers/net/dsa/netc/netc_ethtool.c (limited to 'include') diff --git a/drivers/net/dsa/netc/Makefile b/drivers/net/dsa/netc/Makefile index 4a5767562574..f40b13c702e0 100644 --- a/drivers/net/dsa/netc/Makefile +++ b/drivers/net/dsa/netc/Makefile @@ -1,3 +1,3 @@ # SPDX-License-Identifier: GPL-2.0-only obj-$(CONFIG_NET_DSA_NETC_SWITCH) += nxp-netc-switch.o -nxp-netc-switch-objs := netc_main.o netc_platform.o +nxp-netc-switch-objs := netc_main.o netc_platform.o netc_ethtool.o diff --git a/drivers/net/dsa/netc/netc_ethtool.c b/drivers/net/dsa/netc/netc_ethtool.c new file mode 100644 index 000000000000..ac8940b5a85c --- /dev/null +++ b/drivers/net/dsa/netc/netc_ethtool.c @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) +/* + * NXP NETC switch driver + * Copyright 2025-2026 NXP + */ + +#include + +#include "netc_switch.h" + +static const struct ethtool_rmon_hist_range netc_rmon_ranges[] = { + { 64, 64 }, + { 65, 127 }, + { 128, 255 }, + { 256, 511 }, + { 512, 1023 }, + { 1024, 1522 }, + { 1523, NETC_MAX_FRAME_LEN }, + { } +}; + +static void netc_port_pause_stats(struct netc_port *np, int mac, + struct ethtool_pause_stats *stats) +{ + if (mac && !np->caps.pmac) + return; + + stats->tx_pause_frames = netc_port_rd64(np, NETC_PM_TXPF(mac)); + stats->rx_pause_frames = netc_port_rd64(np, NETC_PM_RXPF(mac)); +} + +void netc_port_get_pause_stats(struct dsa_switch *ds, int port, + struct ethtool_pause_stats *pause_stats) +{ + struct netc_port *np = NETC_PORT(ds, port); + struct net_device *ndev; + + switch (pause_stats->src) { + case ETHTOOL_MAC_STATS_SRC_EMAC: + netc_port_pause_stats(np, 0, pause_stats); + break; + case ETHTOOL_MAC_STATS_SRC_PMAC: + netc_port_pause_stats(np, 1, pause_stats); + break; + case ETHTOOL_MAC_STATS_SRC_AGGREGATE: + ndev = dsa_to_port(ds, port)->user; + ethtool_aggregate_pause_stats(ndev, pause_stats); + break; + } +} + +static void netc_port_rmon_stats(struct netc_port *np, int mac, + struct ethtool_rmon_stats *stats) +{ + if (mac && !np->caps.pmac) + return; + + stats->undersize_pkts = netc_port_rd64(np, NETC_PM_RUND(mac)); + stats->oversize_pkts = netc_port_rd64(np, NETC_PM_ROVR(mac)); + stats->fragments = netc_port_rd64(np, NETC_PM_RFRG(mac)); + stats->jabbers = netc_port_rd64(np, NETC_PM_RJBR(mac)); + + stats->hist[0] = netc_port_rd64(np, NETC_PM_R64(mac)); + stats->hist[1] = netc_port_rd64(np, NETC_PM_R127(mac)); + stats->hist[2] = netc_port_rd64(np, NETC_PM_R255(mac)); + stats->hist[3] = netc_port_rd64(np, NETC_PM_R511(mac)); + stats->hist[4] = netc_port_rd64(np, NETC_PM_R1023(mac)); + stats->hist[5] = netc_port_rd64(np, NETC_PM_R1522(mac)); + stats->hist[6] = netc_port_rd64(np, NETC_PM_R1523X(mac)); + + stats->hist_tx[0] = netc_port_rd64(np, NETC_PM_T64(mac)); + stats->hist_tx[1] = netc_port_rd64(np, NETC_PM_T127(mac)); + stats->hist_tx[2] = netc_port_rd64(np, NETC_PM_T255(mac)); + stats->hist_tx[3] = netc_port_rd64(np, NETC_PM_T511(mac)); + stats->hist_tx[4] = netc_port_rd64(np, NETC_PM_T1023(mac)); + stats->hist_tx[5] = netc_port_rd64(np, NETC_PM_T1522(mac)); + stats->hist_tx[6] = netc_port_rd64(np, NETC_PM_T1523X(mac)); +} + +void netc_port_get_rmon_stats(struct dsa_switch *ds, int port, + struct ethtool_rmon_stats *rmon_stats, + const struct ethtool_rmon_hist_range **ranges) +{ + struct netc_port *np = NETC_PORT(ds, port); + struct net_device *ndev; + + *ranges = netc_rmon_ranges; + + switch (rmon_stats->src) { + case ETHTOOL_MAC_STATS_SRC_EMAC: + netc_port_rmon_stats(np, 0, rmon_stats); + break; + case ETHTOOL_MAC_STATS_SRC_PMAC: + netc_port_rmon_stats(np, 1, rmon_stats); + break; + case ETHTOOL_MAC_STATS_SRC_AGGREGATE: + ndev = dsa_to_port(ds, port)->user; + ethtool_aggregate_rmon_stats(ndev, rmon_stats); + break; + } +} + +static void netc_port_ctrl_stats(struct netc_port *np, int mac, + struct ethtool_eth_ctrl_stats *stats) +{ + if (mac && !np->caps.pmac) + return; + + stats->MACControlFramesTransmitted = + netc_port_rd64(np, NETC_PM_TCNP(mac)); + stats->MACControlFramesReceived = + netc_port_rd64(np, NETC_PM_RCNP(mac)); +} + +void netc_port_get_eth_ctrl_stats(struct dsa_switch *ds, int port, + struct ethtool_eth_ctrl_stats *ctrl_stats) +{ + struct netc_port *np = NETC_PORT(ds, port); + struct net_device *ndev; + + switch (ctrl_stats->src) { + case ETHTOOL_MAC_STATS_SRC_EMAC: + netc_port_ctrl_stats(np, 0, ctrl_stats); + break; + case ETHTOOL_MAC_STATS_SRC_PMAC: + netc_port_ctrl_stats(np, 1, ctrl_stats); + break; + case ETHTOOL_MAC_STATS_SRC_AGGREGATE: + ndev = dsa_to_port(ds, port)->user; + ethtool_aggregate_ctrl_stats(ndev, ctrl_stats); + break; + } +} + +static void netc_port_mac_stats(struct netc_port *np, int mac, + struct ethtool_eth_mac_stats *stats) +{ + if (mac && !np->caps.pmac) + return; + + stats->FramesTransmittedOK = netc_port_rd64(np, NETC_PM_TFRM(mac)); + stats->SingleCollisionFrames = netc_port_rd64(np, NETC_PM_TSCOL(mac)); + stats->MultipleCollisionFrames = + netc_port_rd64(np, NETC_PM_TMCOL(mac)); + stats->FramesReceivedOK = netc_port_rd64(np, NETC_PM_RFRM(mac)); + stats->FrameCheckSequenceErrors = + netc_port_rd64(np, NETC_PM_RFCS(mac)); + stats->AlignmentErrors = netc_port_rd64(np, NETC_PM_RALN(mac)); + stats->OctetsTransmittedOK = netc_port_rd64(np, NETC_PM_TEOCT(mac)); + stats->FramesWithDeferredXmissions = + netc_port_rd64(np, NETC_PM_TDFR(mac)); + stats->LateCollisions = netc_port_rd64(np, NETC_PM_TLCOL(mac)); + stats->FramesAbortedDueToXSColls = + netc_port_rd64(np, NETC_PM_TECOL(mac)); + stats->FramesLostDueToIntMACXmitError = + netc_port_rd64(np, NETC_PM_TERR(mac)); + stats->OctetsReceivedOK = netc_port_rd64(np, NETC_PM_REOCT(mac)); + stats->FramesLostDueToIntMACRcvError = + netc_port_rd64(np, NETC_PM_RDRNTP(mac)); + stats->MulticastFramesXmittedOK = + netc_port_rd64(np, NETC_PM_TMCA(mac)); + stats->BroadcastFramesXmittedOK = + netc_port_rd64(np, NETC_PM_TBCA(mac)); + stats->MulticastFramesReceivedOK = + netc_port_rd64(np, NETC_PM_RMCA(mac)); + stats->BroadcastFramesReceivedOK = + netc_port_rd64(np, NETC_PM_RBCA(mac)); + stats->FramesWithExcessiveDeferral = + netc_port_rd64(np, NETC_PM_TEDFR(mac)); +} + +void netc_port_get_eth_mac_stats(struct dsa_switch *ds, int port, + struct ethtool_eth_mac_stats *mac_stats) +{ + struct netc_port *np = NETC_PORT(ds, port); + struct net_device *ndev; + + switch (mac_stats->src) { + case ETHTOOL_MAC_STATS_SRC_EMAC: + netc_port_mac_stats(np, 0, mac_stats); + break; + case ETHTOOL_MAC_STATS_SRC_PMAC: + netc_port_mac_stats(np, 1, mac_stats); + break; + case ETHTOOL_MAC_STATS_SRC_AGGREGATE: + ndev = dsa_to_port(ds, port)->user; + ethtool_aggregate_mac_stats(ndev, mac_stats); + break; + } +} diff --git a/drivers/net/dsa/netc/netc_main.c b/drivers/net/dsa/netc/netc_main.c index bc1962b38cda..f3ab0c7646da 100644 --- a/drivers/net/dsa/netc/netc_main.c +++ b/drivers/net/dsa/netc/netc_main.c @@ -1487,6 +1487,10 @@ static const struct dsa_switch_ops netc_switch_ops = { .port_mdb_add = netc_port_mdb_add, .port_mdb_del = netc_port_mdb_del, .port_set_host_flood = netc_port_set_host_flood, + .get_pause_stats = netc_port_get_pause_stats, + .get_rmon_stats = netc_port_get_rmon_stats, + .get_eth_ctrl_stats = netc_port_get_eth_ctrl_stats, + .get_eth_mac_stats = netc_port_get_eth_mac_stats, }; static int netc_switch_probe(struct pci_dev *pdev, diff --git a/drivers/net/dsa/netc/netc_switch.h b/drivers/net/dsa/netc/netc_switch.h index f587c5031c70..ed83201d128b 100644 --- a/drivers/net/dsa/netc/netc_switch.h +++ b/drivers/net/dsa/netc/netc_switch.h @@ -124,6 +124,7 @@ struct netc_switch { /* Write/Read registers of Switch Port (including pseudo MAC port) */ #define netc_port_rd(p, o) netc_read((p)->iobase + (o)) +#define netc_port_rd64(p, o) netc_read64((p)->iobase + (o)) #define netc_port_wr(p, o, v) netc_write((p)->iobase + (o), v) /* Write/Read Switch global registers */ @@ -149,4 +150,15 @@ static inline void netc_del_fdb_entry(struct netc_fdb_entry *entry) int netc_switch_platform_probe(struct netc_switch *priv); +/* ethtool APIs */ +void netc_port_get_pause_stats(struct dsa_switch *ds, int port, + struct ethtool_pause_stats *pause_stats); +void netc_port_get_rmon_stats(struct dsa_switch *ds, int port, + struct ethtool_rmon_stats *rmon_stats, + const struct ethtool_rmon_hist_range **ranges); +void netc_port_get_eth_ctrl_stats(struct dsa_switch *ds, int port, + struct ethtool_eth_ctrl_stats *ctrl_stats); +void netc_port_get_eth_mac_stats(struct dsa_switch *ds, int port, + struct ethtool_eth_mac_stats *mac_stats); + #endif diff --git a/drivers/net/dsa/netc/netc_switch_hw.h b/drivers/net/dsa/netc/netc_switch_hw.h index 78335c399955..f4e8ef983c21 100644 --- a/drivers/net/dsa/netc/netc_switch_hw.h +++ b/drivers/net/dsa/netc/netc_switch_hw.h @@ -184,6 +184,126 @@ enum netc_stg_stage { #define SSP_10M 1 #define SSP_1G 2 +/* Port MAC 0/1 Receive Ethernet Octets Counter */ +#define NETC_PM_REOCT(a) (0x1100 + (a) * 0x400) + +/* Port MAC 0/1 Receive Alignment Error Counter Register */ +#define NETC_PM_RALN(a) (0x1110 + (a) * 0x400) + +/* Port MAC 0/1 Receive Valid Pause Frame Counter */ +#define NETC_PM_RXPF(a) (0x1118 + (a) * 0x400) + +/* Port MAC 0/1 Receive Frame Counter */ +#define NETC_PM_RFRM(a) (0x1120 + (a) * 0x400) + +/* Port MAC 0/1 Receive Frame Check Sequence Error Counter */ +#define NETC_PM_RFCS(a) (0x1128 + (a) * 0x400) + +/* Port MAC 0/1 Receive Multicast Frame Counter */ +#define NETC_PM_RMCA(a) (0x1148 + (a) * 0x400) + +/* Port MAC 0/1 Receive Broadcast Frame Counter */ +#define NETC_PM_RBCA(a) (0x1150 + (a) * 0x400) + +/* Port MAC 0/1 Receive Undersized Packet Counter */ +#define NETC_PM_RUND(a) (0x1168 + (a) * 0x400) + +/* Port MAC 0/1 Receive 64-Octet Packet Counter */ +#define NETC_PM_R64(a) (0x1170 + (a) * 0x400) + +/* Port MAC 0/1 Receive 65 to 127-Octet Packet Counter */ +#define NETC_PM_R127(a) (0x1178 + (a) * 0x400) + +/* Port MAC 0/1 Receive 128 to 255-Octet Packet Counter */ +#define NETC_PM_R255(a) (0x1180 + (a) * 0x400) + +/* Port MAC 0/1 Receive 256 to 511-Octet Packet Counter */ +#define NETC_PM_R511(a) (0x1188 + (a) * 0x400) + +/* Port MAC 0/1 Receive 512 to 1023-Octet Packet Counter */ +#define NETC_PM_R1023(a) (0x1190 + (a) * 0x400) + +/* Port MAC 0/1 Receive 1024 to 1522-Octet Packet Counter */ +#define NETC_PM_R1522(a) (0x1198 + (a) * 0x400) + +/* Port MAC 0/1 Receive 1523 to Max-Octet Packet Counter */ +#define NETC_PM_R1523X(a) (0x11a0 + (a) * 0x400) + +/* Port MAC 0/1 Receive Oversized Packet Counter */ +#define NETC_PM_ROVR(a) (0x11a8 + (a) * 0x400) + +/* Port MAC 0/1 Receive Jabber Packet Counter */ +#define NETC_PM_RJBR(a) (0x11b0 + (a) * 0x400) + +/* Port MAC 0/1 Receive Fragment Packet Counter */ +#define NETC_PM_RFRG(a) (0x11b8 + (a) * 0x400) + +/* Port MAC 0/1 Receive Control Packet Counter */ +#define NETC_PM_RCNP(a) (0x11c0 + (a) * 0x400) + +/* Port MAC 0/1 Receive Dropped Not Truncated Packets Counter */ +#define NETC_PM_RDRNTP(a) (0x11c8 + (a) * 0x400) + +/* Port MAC 0/1 Transmit Ethernet Octets Counter */ +#define NETC_PM_TEOCT(a) (0x1200 + (a) * 0x400) + +/* Port MAC 0/1 Transmit Excessive Deferral Packet Counter */ +#define NETC_PM_TEDFR(a) (0x1210 + (a) * 0x400) + +/* Port MAC 0/1 Transmit Valid Pause Frame Counter */ +#define NETC_PM_TXPF(a) (0x1218 + (a) * 0x400) + +/* Port MAC 0/1 Transmit Frame Counter */ +#define NETC_PM_TFRM(a) (0x1220 + (a) * 0x400) + +/* Port MAC 0/1 Transmit Frame Error Counter */ +#define NETC_PM_TERR(a) (0x1238 + (a) * 0x400) + +/* Port MAC 0/1 Transmit Multicast Frame Counter */ +#define NETC_PM_TMCA(a) (0x1248 + (a) * 0x400) + +/* Port MAC 0/1 Transmit Broadcast Frame Counter */ +#define NETC_PM_TBCA(a) (0x1250 + (a) * 0x400) + +/* Port MAC 0/1 Transmit 64-Octet Packet Counter */ +#define NETC_PM_T64(a) (0x1270 + (a) * 0x400) + +/* Port MAC 0/1 Transmit 65 to 127-Octet Packet Counter */ +#define NETC_PM_T127(a) (0x1278 + (a) * 0x400) + +/* Port MAC 0/1 Transmit 128 to 255-Octet Packet Counter */ +#define NETC_PM_T255(a) (0x1280 + (a) * 0x400) + +/* Port MAC 0/1 Transmit 256 to 511-Octet Packet Counter */ +#define NETC_PM_T511(a) (0x1288 + (a) * 0x400) + +/* Port MAC 0/1 Transmit 512 to 1023-Octet Packet Counter */ +#define NETC_PM_T1023(a) (0x1290 + (a) * 0x400) + +/* Port MAC 0/1 Transmit 1024 to 1522-Octet Packet Counter */ +#define NETC_PM_T1522(a) (0x1298 + (a) * 0x400) + +/* Port MAC 0/1 Transmit 1523 to TX_MTU-Octet Packet Counter */ +#define NETC_PM_T1523X(a) (0x12a0 + (a) * 0x400) + +/* Port MAC 0/1 Transmit Control Packet Counter */ +#define NETC_PM_TCNP(a) (0x12c0 + (a) * 0x400) + +/* Port MAC 0/1 Transmit Deferred Packet Counter */ +#define NETC_PM_TDFR(a) (0x12d0 + (a) * 0x400) + +/* Port MAC 0/1 Transmit Multiple Collisions Counter */ +#define NETC_PM_TMCOL(a) (0x12d8 + (a) * 0x400) + +/* Port MAC 0/1 Transmit Single Collision */ +#define NETC_PM_TSCOL(a) (0x12e0 + (a) * 0x400) + +/* Port MAC 0/1 Transmit Late Collision Counter */ +#define NETC_PM_TLCOL(a) (0x12e8 + (a) * 0x400) + +/* Port MAC 0/1 Transmit Excessive Collisions Counter */ +#define NETC_PM_TECOL(a) (0x12f0 + (a) * 0x400) + #define NETC_PEMDIOCR 0x1c00 #define NETC_EMDIO_BASE NETC_PEMDIOCR diff --git a/include/linux/fsl/netc_global.h b/include/linux/fsl/netc_global.h index fdecca8c90f0..5b8ff528d369 100644 --- a/include/linux/fsl/netc_global.h +++ b/include/linux/fsl/netc_global.h @@ -5,6 +5,7 @@ #define __NETC_GLOBAL_H #include +#include static inline u32 netc_read(void __iomem *reg) { @@ -16,4 +17,9 @@ static inline void netc_write(void __iomem *reg, u32 val) iowrite32(val, reg); } +static inline u64 netc_read64(void __iomem *reg) +{ + return ioread64(reg); +} + #endif -- cgit v1.2.3 From c227f8aaf22cf5acc3a55a4fef1dd2bf110caebe Mon Sep 17 00:00:00 2001 From: Erni Sri Satya Vennela Date: Mon, 18 May 2026 23:46:10 -0700 Subject: net: mana: Expose hardware diagnostic info via debugfs Add debugfs entries to expose hardware configuration and diagnostic information that aids in debugging driver initialization and runtime operations without adding noise to dmesg. The debugfs directory for each PCI device is named using pci_name() (the unique BDF address), and its creation and removal is integrated into mana_gd_setup() and mana_gd_cleanup_device() respectively, so that all callers (probe, remove, suspend, resume, shutdown) share a single code path. Device-level entries (under /sys/kernel/debug/mana//): - num_msix_usable, max_num_queues: Max resources from hardware - gdma_protocol_ver, pf_cap_flags1: VF version negotiation results - num_vports, bm_hostmode: Device configuration Per-vPort entries (under /sys/kernel/debug/mana//vportN/): - port_handle: Hardware vPort handle - max_sq, max_rq: Max queues from vPort config - indir_table_sz: Indirection table size - steer_rx, steer_rss, steer_update_tab, steer_cqe_coalescing: Last applied steering configuration parameters Signed-off-by: Erni Sri Satya Vennela Reviewed-by: Simon Horman Link: https://patch.msgid.link/20260519064621.772154-1-ernis@linux.microsoft.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/microsoft/mana/gdma_main.c | 73 +++++++++++++------------ drivers/net/ethernet/microsoft/mana/mana_en.c | 35 ++++++++++++ include/net/mana/gdma.h | 1 + include/net/mana/mana.h | 8 +++ 4 files changed, 83 insertions(+), 34 deletions(-) (limited to 'include') diff --git a/drivers/net/ethernet/microsoft/mana/gdma_main.c b/drivers/net/ethernet/microsoft/mana/gdma_main.c index 3bc3fff55999..712a0881d720 100644 --- a/drivers/net/ethernet/microsoft/mana/gdma_main.c +++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c @@ -227,6 +227,11 @@ static int mana_gd_query_max_resources(struct pci_dev *pdev) if (gc->max_num_queues == 0) return -ENOSPC; + debugfs_create_u32("num_msix_usable", 0400, gc->mana_pci_debugfs, + &gc->num_msix_usable); + debugfs_create_u32("max_num_queues", 0400, gc->mana_pci_debugfs, + &gc->max_num_queues); + return 0; } @@ -1297,6 +1302,13 @@ int mana_gd_verify_vf_version(struct pci_dev *pdev) return err ? err : -EPROTO; } gc->pf_cap_flags1 = resp.pf_cap_flags1; + gc->gdma_protocol_ver = resp.gdma_protocol_ver; + + debugfs_create_x64("gdma_protocol_ver", 0400, gc->mana_pci_debugfs, + &gc->gdma_protocol_ver); + debugfs_create_x64("pf_cap_flags1", 0400, gc->mana_pci_debugfs, + &gc->pf_cap_flags1); + if (resp.pf_cap_flags1 & GDMA_DRV_CAP_FLAG_1_HWC_TIMEOUT_RECONFIG) { err = mana_gd_query_hwc_timeout(pdev, &hwc->hwc_timeout); if (err) { @@ -1976,15 +1988,20 @@ static int mana_gd_setup(struct pci_dev *pdev) struct gdma_context *gc = pci_get_drvdata(pdev); int err; + gc->mana_pci_debugfs = debugfs_create_dir(pci_name(pdev), + mana_debugfs_root); + err = mana_gd_init_registers(pdev); if (err) - return err; + goto remove_debugfs; mana_smc_init(&gc->shm_channel, gc->dev, gc->shm_base); gc->service_wq = alloc_ordered_workqueue("gdma_service_wq", 0); - if (!gc->service_wq) - return -ENOMEM; + if (!gc->service_wq) { + err = -ENOMEM; + goto remove_debugfs; + } err = mana_gd_setup_hwc_irqs(pdev); if (err) { @@ -2025,11 +2042,14 @@ remove_irq: free_workqueue: destroy_workqueue(gc->service_wq); gc->service_wq = NULL; +remove_debugfs: + debugfs_remove_recursive(gc->mana_pci_debugfs); + gc->mana_pci_debugfs = NULL; dev_err(&pdev->dev, "%s failed (error %d)\n", __func__, err); return err; } -static void mana_gd_cleanup(struct pci_dev *pdev) +static void mana_gd_cleanup_device(struct pci_dev *pdev) { struct gdma_context *gc = pci_get_drvdata(pdev); @@ -2041,6 +2061,10 @@ static void mana_gd_cleanup(struct pci_dev *pdev) destroy_workqueue(gc->service_wq); gc->service_wq = NULL; } + + debugfs_remove_recursive(gc->mana_pci_debugfs); + gc->mana_pci_debugfs = NULL; + dev_dbg(&pdev->dev, "mana gdma cleanup successful\n"); } @@ -2098,9 +2122,6 @@ static int mana_gd_probe(struct pci_dev *pdev, const struct pci_device_id *ent) gc->dev = &pdev->dev; xa_init(&gc->irq_contexts); - gc->mana_pci_debugfs = debugfs_create_dir(pci_name(pdev), - mana_debugfs_root); - err = mana_gd_setup(pdev); if (err) goto unmap_bar; @@ -2129,16 +2150,8 @@ cleanup_mana_rdma: cleanup_mana: mana_remove(&gc->mana, false); cleanup_gd: - mana_gd_cleanup(pdev); + mana_gd_cleanup_device(pdev); unmap_bar: - /* - * at this point we know that the other debugfs child dir/files - * are either not yet created or are already cleaned up. - * The pci debugfs folder clean-up now, will only be cleaning up - * adapter-MTU file and apc->mana_pci_debugfs folder. - */ - debugfs_remove_recursive(gc->mana_pci_debugfs); - gc->mana_pci_debugfs = NULL; xa_destroy(&gc->irq_contexts); pci_iounmap(pdev, bar0_va); free_gc: @@ -2188,11 +2201,7 @@ static void mana_gd_remove(struct pci_dev *pdev) mana_rdma_remove(&gc->mana_ib); mana_remove(&gc->mana, false); - mana_gd_cleanup(pdev); - - debugfs_remove_recursive(gc->mana_pci_debugfs); - - gc->mana_pci_debugfs = NULL; + mana_gd_cleanup_device(pdev); xa_destroy(&gc->irq_contexts); @@ -2214,15 +2223,11 @@ int mana_gd_suspend(struct pci_dev *pdev, pm_message_t state) mana_rdma_remove(&gc->mana_ib); mana_remove(&gc->mana, true); - mana_gd_cleanup(pdev); + mana_gd_cleanup_device(pdev); return 0; } -/* In case the NIC hardware stops working, the suspend and resume callbacks will - * fail -- if this happens, it's safer to just report an error than try to undo - * what has been done. - */ int mana_gd_resume(struct pci_dev *pdev) { struct gdma_context *gc = pci_get_drvdata(pdev); @@ -2234,13 +2239,17 @@ int mana_gd_resume(struct pci_dev *pdev) err = mana_probe(&gc->mana, true); if (err) - return err; + goto cleanup_gd; err = mana_rdma_probe(&gc->mana_ib); if (err) - return err; + mana_rdma_remove(&gc->mana_ib); - return 0; + return err; + +cleanup_gd: + mana_gd_cleanup_device(pdev); + return err; } /* Quiesce the device for kexec. This is also called upon reboot/shutdown. */ @@ -2253,11 +2262,7 @@ static void mana_gd_shutdown(struct pci_dev *pdev) mana_rdma_remove(&gc->mana_ib); mana_remove(&gc->mana, true); - mana_gd_cleanup(pdev); - - debugfs_remove_recursive(gc->mana_pci_debugfs); - - gc->mana_pci_debugfs = NULL; + mana_gd_cleanup_device(pdev); pci_disable_device(pdev); } diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c index b2faa7cf398f..82f1461a48e9 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_en.c +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c @@ -1282,6 +1282,9 @@ static int mana_query_vport_cfg(struct mana_port_context *apc, u32 vport_index, apc->port_handle = resp.vport; ether_addr_copy(apc->mac_addr, resp.mac_addr); + apc->vport_max_sq = *max_sq; + apc->vport_max_rq = *max_rq; + return 0; } @@ -1436,6 +1439,11 @@ static int mana_cfg_vport_steering(struct mana_port_context *apc, netdev_info(ndev, "Configured steering vPort %llu entries %u\n", apc->port_handle, apc->indir_table_sz); + + apc->steer_rx = rx; + apc->steer_rss = apc->rss_state; + apc->steer_update_tab = update_tab; + apc->steer_cqe_coalescing = req->cqe_coalescing_enable; out: kfree(req); return err; @@ -3178,6 +3186,23 @@ static int mana_init_port(struct net_device *ndev) eth_hw_addr_set(ndev, apc->mac_addr); sprintf(vport, "vport%d", port_idx); apc->mana_port_debugfs = debugfs_create_dir(vport, gc->mana_pci_debugfs); + + debugfs_create_u64("port_handle", 0400, apc->mana_port_debugfs, + &apc->port_handle); + debugfs_create_u32("max_sq", 0400, apc->mana_port_debugfs, + &apc->vport_max_sq); + debugfs_create_u32("max_rq", 0400, apc->mana_port_debugfs, + &apc->vport_max_rq); + debugfs_create_u32("indir_table_sz", 0400, apc->mana_port_debugfs, + &apc->indir_table_sz); + debugfs_create_u32("steer_rx", 0400, apc->mana_port_debugfs, + &apc->steer_rx); + debugfs_create_u32("steer_rss", 0400, apc->mana_port_debugfs, + &apc->steer_rss); + debugfs_create_bool("steer_update_tab", 0400, apc->mana_port_debugfs, + &apc->steer_update_tab); + debugfs_create_u32("steer_cqe_coalescing", 0400, apc->mana_port_debugfs, + &apc->steer_cqe_coalescing); debugfs_create_u32("current_speed", 0400, apc->mana_port_debugfs, &apc->speed); return 0; @@ -3695,6 +3720,11 @@ int mana_probe(struct gdma_dev *gd, bool resuming) if (ac->num_ports > MAX_PORTS_IN_MANA_DEV) ac->num_ports = MAX_PORTS_IN_MANA_DEV; + debugfs_create_u16("num_vports", 0400, gc->mana_pci_debugfs, + &ac->num_ports); + debugfs_create_u8("bm_hostmode", 0400, gc->mana_pci_debugfs, + &ac->bm_hostmode); + ac->per_port_queue_reset_wq = create_singlethread_workqueue("mana_per_port_queue_reset_wq"); if (!ac->per_port_queue_reset_wq) { @@ -3817,6 +3847,11 @@ void mana_remove(struct gdma_dev *gd, bool suspending) mana_gd_deregister_device(gd); + if (gc->mana_pci_debugfs) { + debugfs_lookup_and_remove("bm_hostmode", gc->mana_pci_debugfs); + debugfs_lookup_and_remove("num_vports", gc->mana_pci_debugfs); + } + if (suspending) return; diff --git a/include/net/mana/gdma.h b/include/net/mana/gdma.h index 6d836060976a..70d62bc32837 100644 --- a/include/net/mana/gdma.h +++ b/include/net/mana/gdma.h @@ -442,6 +442,7 @@ struct gdma_context { struct gdma_dev mana_ib; u64 pf_cap_flags1; + u64 gdma_protocol_ver; struct workqueue_struct *service_wq; diff --git a/include/net/mana/mana.h b/include/net/mana/mana.h index aa90a858c8e3..d9c27310fd04 100644 --- a/include/net/mana/mana.h +++ b/include/net/mana/mana.h @@ -568,6 +568,14 @@ struct mana_port_context { /* Debugfs */ struct dentry *mana_port_debugfs; + + /* Cached vport/steering config for debugfs */ + u32 vport_max_sq; + u32 vport_max_rq; + u32 steer_rx; + u32 steer_rss; + bool steer_update_tab; + u32 steer_cqe_coalescing; }; netdev_tx_t mana_start_xmit(struct sk_buff *skb, struct net_device *ndev); -- cgit v1.2.3 From b7b1b8464b4712da8cce2015709ead71050c5a8b Mon Sep 17 00:00:00 2001 From: David Yang Date: Thu, 21 May 2026 09:03:06 +0800 Subject: net/sched: tbf: add extack to offload params Drivers might have error messages to propagate to user space. Propagate the netlink extack so that they can inform user space in a verbal way of their limitations. Signed-off-by: David Yang Link: https://patch.msgid.link/20260521010320.208138-3-mmyangfl@gmail.com Signed-off-by: Jakub Kicinski --- include/net/pkt_cls.h | 1 + net/sched/sch_tbf.c | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/net/pkt_cls.h b/include/net/pkt_cls.h index 99ac747b7906..3bd08d7f39c1 100644 --- a/include/net/pkt_cls.h +++ b/include/net/pkt_cls.h @@ -1046,6 +1046,7 @@ struct tc_tbf_qopt_offload_replace_params { }; struct tc_tbf_qopt_offload { + struct netlink_ext_ack *extack; enum tc_tbf_command command; u32 handle; u32 parent; diff --git a/net/sched/sch_tbf.c b/net/sched/sch_tbf.c index 67c7aaaf8f60..58c9687efb0f 100644 --- a/net/sched/sch_tbf.c +++ b/net/sched/sch_tbf.c @@ -139,7 +139,8 @@ static u64 psched_ns_t2l(const struct psched_ratecfg *r, return len; } -static void tbf_offload_change(struct Qdisc *sch) +static void tbf_offload_change(struct Qdisc *sch, + struct netlink_ext_ack *extack) { struct tbf_sched_data *q = qdisc_priv(sch); struct net_device *dev = qdisc_dev(sch); @@ -148,6 +149,7 @@ static void tbf_offload_change(struct Qdisc *sch) if (!tc_can_offload(dev) || !dev->netdev_ops->ndo_setup_tc) return; + qopt.extack = extack; qopt.command = TC_TBF_REPLACE; qopt.handle = sch->handle; qopt.parent = sch->parent; @@ -166,6 +168,7 @@ static void tbf_offload_destroy(struct Qdisc *sch) if (!tc_can_offload(dev) || !dev->netdev_ops->ndo_setup_tc) return; + qopt.extack = NULL; qopt.command = TC_TBF_DESTROY; qopt.handle = sch->handle; qopt.parent = sch->parent; @@ -176,6 +179,7 @@ static int tbf_offload_dump(struct Qdisc *sch) { struct tc_tbf_qopt_offload qopt; + qopt.extack = NULL; qopt.command = TC_TBF_STATS; qopt.handle = sch->handle; qopt.parent = sch->parent; @@ -193,6 +197,7 @@ static void tbf_offload_graft(struct Qdisc *sch, struct Qdisc *new, .parent = sch->parent, .child_handle = new->handle, .command = TC_TBF_GRAFT, + .extack = extack, }; qdisc_offload_graft_helper(qdisc_dev(sch), sch, new, old, @@ -477,7 +482,7 @@ static int tbf_change(struct Qdisc *sch, struct nlattr *opt, qdisc_put(old); err = 0; - tbf_offload_change(sch); + tbf_offload_change(sch, extack); done: return err; } -- cgit v1.2.3 From 7409fad779e271f252d844ae16e1a7429626b13e Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 21 May 2026 17:14:39 +0000 Subject: dpll: change dpll_netdev_pin_handle_size() to assume DPLL_A_PIN_ID will be used We plan to no longer hold RTNL in "ip link show", and use RCU instead. Assume rtnl_fill_dpll_pin() will have to fill DPLL_A_PIN_ID. It is fine to over-estimate skb size (by 8 bytes) in if_nlmsg_size(). Signed-off-by: Eric Dumazet Reviewed-by: Vadim Fedorenko Link: https://patch.msgid.link/20260521171440.114956-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- drivers/dpll/dpll_netlink.c | 11 ----------- include/linux/dpll.h | 9 +++++++-- net/core/rtnetlink.c | 6 +++--- 3 files changed, 10 insertions(+), 16 deletions(-) (limited to 'include') diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c index 53ca24d0f191..095ef8703c0b 100644 --- a/drivers/dpll/dpll_netlink.c +++ b/drivers/dpll/dpll_netlink.c @@ -89,17 +89,6 @@ static struct dpll_pin *dpll_netdev_pin(const struct net_device *dev) return rcu_dereference_rtnl(dev->dpll_pin); } -/** - * dpll_netdev_pin_handle_size - get size of pin handle attribute of a netdev - * @dev: netdev from which to get the pin - * - * Return: byte size of pin handle attribute, or 0 if @dev has no pin. - */ -size_t dpll_netdev_pin_handle_size(const struct net_device *dev) -{ - return dpll_netdev_pin(dev) ? nla_total_size(4) : 0; /* DPLL_A_PIN_ID */ -} - int dpll_netdev_add_pin_handle(struct sk_buff *msg, const struct net_device *dev) { diff --git a/include/linux/dpll.h b/include/linux/dpll.h index 82dfadd9f593..4071fd974dd7 100644 --- a/include/linux/dpll.h +++ b/include/linux/dpll.h @@ -13,6 +13,7 @@ #include #include #include +#include struct dpll_device; struct dpll_pin; @@ -238,7 +239,11 @@ struct dpll_pin_notifier_info { void dpll_netdev_pin_set(struct net_device *dev, struct dpll_pin *dpll_pin); void dpll_netdev_pin_clear(struct net_device *dev); -size_t dpll_netdev_pin_handle_size(const struct net_device *dev); +static inline size_t dpll_netdev_pin_handle_size(void) +{ + return nla_total_size(4); /* DPLL_A_PIN_ID */ +} + int dpll_netdev_add_pin_handle(struct sk_buff *msg, const struct net_device *dev); @@ -249,7 +254,7 @@ static inline void dpll_netdev_pin_set(struct net_device *dev, struct dpll_pin *dpll_pin) { } static inline void dpll_netdev_pin_clear(struct net_device *dev) { } -static inline size_t dpll_netdev_pin_handle_size(const struct net_device *dev) +static inline size_t dpll_netdev_pin_handle_size(void) { return 0; } diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c index 5e461c752df1..0aa429336ffe 100644 --- a/net/core/rtnetlink.c +++ b/net/core/rtnetlink.c @@ -1263,11 +1263,11 @@ static size_t rtnl_devlink_port_size(const struct net_device *dev) return size; } -static size_t rtnl_dpll_pin_size(const struct net_device *dev) +static size_t rtnl_dpll_pin_size(void) { size_t size = nla_total_size(0); /* nest IFLA_DPLL_PIN */ - size += dpll_netdev_pin_handle_size(dev); + size += dpll_netdev_pin_handle_size(); return size; } @@ -1348,7 +1348,7 @@ static noinline size_t if_nlmsg_size(const struct net_device *dev, + nla_total_size(4) /* IFLA_MAX_MTU */ + nla_total_size(MAX_ADDR_LEN) /* IFLA_PERM_ADDRESS */ + rtnl_devlink_port_size(dev) - + rtnl_dpll_pin_size(dev) + + rtnl_dpll_pin_size() + nla_total_size(8) /* IFLA_MAX_PACING_OFFLOAD_HORIZON */ + nla_total_size(2) /* IFLA_HEADROOM */ + nla_total_size(2) /* IFLA_TAILROOM */ -- cgit v1.2.3 From ec1806a730a1c0b3d68a7f9afe81514fb0dd7991 Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Sat, 25 Apr 2026 15:00:50 +0200 Subject: netfilter: x_tables: disable 32bit compat interface in user namespaces This feature is required to use 32bit arp/ip/ip6/ebtables binaries on 64bit kernels. I don't think there are many users left. Support has been a compile-time option since 2021 and defaults to off since 2023. The XTABLES_COMPAT config option is already off in many distributions including Debian and Fedora. Give a few more months before complete removal but disable support in user namespaces already. Assisted-by: Claude:claude-sonnet-4-6 Signed-off-by: Florian Westphal --- include/linux/netfilter/x_tables.h | 17 +++++++++++++++++ net/bridge/netfilter/ebtables.c | 4 ++++ net/ipv4/netfilter/arp_tables.c | 4 ++++ net/ipv4/netfilter/ip_tables.c | 4 ++++ net/ipv6/netfilter/ip6_tables.c | 4 ++++ 5 files changed, 33 insertions(+) (limited to 'include') diff --git a/include/linux/netfilter/x_tables.h b/include/linux/netfilter/x_tables.h index 5a1c5c336fa4..20d70dddbe50 100644 --- a/include/linux/netfilter/x_tables.h +++ b/include/linux/netfilter/x_tables.h @@ -534,4 +534,21 @@ int xt_compat_check_entry_offsets(const void *base, const char *elems, unsigned int next_offset); #endif /* CONFIG_NETFILTER_XTABLES_COMPAT */ + +static inline bool xt_compat_check(void) +{ +#ifdef CONFIG_NETFILTER_XTABLES_COMPAT + if (!in_compat_syscall()) + return true; + + pr_warn_once("%s %s\n", + "xtables 32bit compat interface no longer supported", + "in namespaces and will be removed soon."); + + if (!capable(CAP_NET_ADMIN)) + return false; +#endif + return true; +} + #endif /* _X_TABLES_H */ diff --git a/net/bridge/netfilter/ebtables.c b/net/bridge/netfilter/ebtables.c index b9f4daac09af..8d8f1a7c9ad5 100644 --- a/net/bridge/netfilter/ebtables.c +++ b/net/bridge/netfilter/ebtables.c @@ -2455,6 +2455,8 @@ static int do_ebt_get_ctl(struct sock *sk, int cmd, void __user *user, int *len) if (!ns_capable(net->user_ns, CAP_NET_ADMIN)) return -EPERM; + if (!xt_compat_check()) + return -EPERM; #ifdef CONFIG_NETFILTER_XTABLES_COMPAT /* try real handler in case userland supplied needed padding */ @@ -2520,6 +2522,8 @@ static int do_ebt_set_ctl(struct sock *sk, int cmd, sockptr_t arg, if (!ns_capable(net->user_ns, CAP_NET_ADMIN)) return -EPERM; + if (!xt_compat_check()) + return -EPERM; switch (cmd) { case EBT_SO_SET_ENTRIES: diff --git a/net/ipv4/netfilter/arp_tables.c b/net/ipv4/netfilter/arp_tables.c index ad2259678c78..341ae049e5a2 100644 --- a/net/ipv4/netfilter/arp_tables.c +++ b/net/ipv4/netfilter/arp_tables.c @@ -1430,6 +1430,8 @@ static int do_arpt_set_ctl(struct sock *sk, int cmd, sockptr_t arg, if (!ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN)) return -EPERM; + if (!xt_compat_check()) + return -EPERM; switch (cmd) { case ARPT_SO_SET_REPLACE: @@ -1458,6 +1460,8 @@ static int do_arpt_get_ctl(struct sock *sk, int cmd, void __user *user, int *len if (!ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN)) return -EPERM; + if (!xt_compat_check()) + return -EPERM; switch (cmd) { case ARPT_SO_GET_INFO: diff --git a/net/ipv4/netfilter/ip_tables.c b/net/ipv4/netfilter/ip_tables.c index 5cbdb0815857..f917a9004a01 100644 --- a/net/ipv4/netfilter/ip_tables.c +++ b/net/ipv4/netfilter/ip_tables.c @@ -1624,6 +1624,8 @@ do_ipt_set_ctl(struct sock *sk, int cmd, sockptr_t arg, unsigned int len) if (!ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN)) return -EPERM; + if (!xt_compat_check()) + return -EPERM; switch (cmd) { case IPT_SO_SET_REPLACE: @@ -1653,6 +1655,8 @@ do_ipt_get_ctl(struct sock *sk, int cmd, void __user *user, int *len) if (!ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN)) return -EPERM; + if (!xt_compat_check()) + return -EPERM; switch (cmd) { case IPT_SO_GET_INFO: diff --git a/net/ipv6/netfilter/ip6_tables.c b/net/ipv6/netfilter/ip6_tables.c index 9d9c3763f2f5..ecf79d05a51b 100644 --- a/net/ipv6/netfilter/ip6_tables.c +++ b/net/ipv6/netfilter/ip6_tables.c @@ -1633,6 +1633,8 @@ do_ip6t_set_ctl(struct sock *sk, int cmd, sockptr_t arg, unsigned int len) if (!ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN)) return -EPERM; + if (!xt_compat_check()) + return -EPERM; switch (cmd) { case IP6T_SO_SET_REPLACE: @@ -1662,6 +1664,8 @@ do_ip6t_get_ctl(struct sock *sk, int cmd, void __user *user, int *len) if (!ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN)) return -EPERM; + if (!xt_compat_check()) + return -EPERM; switch (cmd) { case IP6T_SO_GET_INFO: -- cgit v1.2.3 From ea0dada7194e02ff9d2c785cc83949bd51496bf3 Mon Sep 17 00:00:00 2001 From: Moshe Shemesh Date: Thu, 21 May 2026 14:08:43 +0300 Subject: net/mlx5: Add SPF function type for page management Add MLX5_SPF to enum mlx5_func_type so SPFs get their own page counter, and add the corresponding WARN check at page cleanup. Wait for SPF pages to be reclaimed during ECPF teardown, alongside the existing host PF and VF page waits. SPF page requests are always identified by vhca_id, so the legacy func_id_to_type() path is not reached for satellite PFs. Signed-off-by: Moshe Shemesh Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260521110843.367329-13-tariqt@nvidia.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/debugfs.c | 3 +++ drivers/net/ethernet/mellanox/mlx5/core/ecpf.c | 5 +++++ drivers/net/ethernet/mellanox/mlx5/core/eswitch.c | 2 ++ drivers/net/ethernet/mellanox/mlx5/core/pagealloc.c | 3 +++ include/linux/mlx5/driver.h | 1 + 5 files changed, 14 insertions(+) (limited to 'include') diff --git a/drivers/net/ethernet/mellanox/mlx5/core/debugfs.c b/drivers/net/ethernet/mellanox/mlx5/core/debugfs.c index 6347957fefcb..30be2b631e7c 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/debugfs.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/debugfs.c @@ -316,6 +316,8 @@ void mlx5_pages_by_func_type_debugfs_init(struct mlx5_core_dev *dev) &dev->priv.page_counters[MLX5_SF]); debugfs_create_u32("fw_pages_host_pf", 0400, pages, &dev->priv.page_counters[MLX5_HOST_PF]); + debugfs_create_u32("fw_pages_spfs", 0400, pages, + &dev->priv.page_counters[MLX5_SPF]); } void mlx5_pages_by_func_type_debugfs_cleanup(struct mlx5_core_dev *dev) @@ -329,6 +331,7 @@ void mlx5_pages_by_func_type_debugfs_cleanup(struct mlx5_core_dev *dev) debugfs_lookup_and_remove("fw_pages_ec_vfs", pages); debugfs_lookup_and_remove("fw_pages_sfs", pages); debugfs_lookup_and_remove("fw_pages_host_pf", pages); + debugfs_lookup_and_remove("fw_pages_spfs", pages); } static u64 qp_read_field(struct mlx5_core_dev *dev, struct mlx5_core_qp *qp, diff --git a/drivers/net/ethernet/mellanox/mlx5/core/ecpf.c b/drivers/net/ethernet/mellanox/mlx5/core/ecpf.c index 350c47d3643b..9839f1a58640 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/ecpf.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/ecpf.c @@ -102,6 +102,11 @@ void mlx5_ec_cleanup(struct mlx5_core_dev *dev) if (err) mlx5_core_warn(dev, "Timeout reclaiming external host PF pages err(%d)\n", err); + err = mlx5_wait_for_pages(dev, &dev->priv.page_counters[MLX5_SPF]); + if (err) + mlx5_core_warn(dev, "Timeout reclaiming SPF pages err(%d)\n", + err); + err = mlx5_wait_for_pages(dev, &dev->priv.page_counters[MLX5_VF]); if (err) mlx5_core_warn(dev, "Timeout reclaiming external host VFs pages err(%d)\n", err); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c index bc3c116f5327..f8cfbf76dd6a 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch.c @@ -863,6 +863,8 @@ esw_vport_to_func_type(struct mlx5_eswitch *esw, struct mlx5_vport *vport) return MLX5_SF; if (xa_get_mark(&esw->vports, vport_num, MLX5_ESW_VPT_VF)) return MLX5_VF; + if (mlx5_esw_is_spf_vport(esw, vport_num)) + return MLX5_SPF; return MLX5_EC_VF; } diff --git a/drivers/net/ethernet/mellanox/mlx5/core/pagealloc.c b/drivers/net/ethernet/mellanox/mlx5/core/pagealloc.c index ce2f7fa9bd48..7fef3a7fee6e 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/pagealloc.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/pagealloc.c @@ -885,6 +885,9 @@ int mlx5_reclaim_startup_pages(struct mlx5_core_dev *dev) WARN(dev->priv.page_counters[MLX5_HOST_PF], "External host PF FW pages counter is %d after reclaiming all pages\n", dev->priv.page_counters[MLX5_HOST_PF]); + WARN(dev->priv.page_counters[MLX5_SPF], + "SPFs FW pages counter is %d after reclaiming all pages\n", + dev->priv.page_counters[MLX5_SPF]); WARN(dev->priv.page_counters[MLX5_EC_VF], "EC VFs FW pages counter is %d after reclaiming all pages\n", dev->priv.page_counters[MLX5_EC_VF]); diff --git a/include/linux/mlx5/driver.h b/include/linux/mlx5/driver.h index 9a4bb25d8e0a..b1871c0821d0 100644 --- a/include/linux/mlx5/driver.h +++ b/include/linux/mlx5/driver.h @@ -557,6 +557,7 @@ enum mlx5_func_type { MLX5_VF, MLX5_SF, MLX5_HOST_PF, + MLX5_SPF, MLX5_EC_VF, MLX5_FUNC_TYPE_NUM, MLX5_FUNC_TYPE_NONE = MLX5_FUNC_TYPE_NUM, -- cgit v1.2.3 From d603517771d8e08a2d8fc9e1f7682ce393d3973a Mon Sep 17 00:00:00 2001 From: Ratheesh Kannoth Date: Thu, 21 May 2026 15:22:57 +0530 Subject: devlink: pass param values by pointer union devlink_param_value grows substantially once U64 array parameters are added to devlink (from 32 bytes to over 264 bytes). devlink_nl_param_value_fill_one() and devlink_nl_param_value_put() copy the union by value in several places. Passing two instances as value arguments alone consumes over 528 bytes of stack; combined with deeper call chains the parameter stack can approach 800 bytes and trip CONFIG_FRAME_WARN more easily. Switch internal helpers and exported driver APIs to pass pointers to union devlink_param_value rather than passing the union by value. Reviewed-by: Petr Machata # for mlxsw Acked-by: Przemek Kitszel Reviewed-by: Arthur Kiyanovski #for ena Signed-off-by: Ratheesh Kannoth Link: https://patch.msgid.link/20260521095303.2395584-4-rkannoth@marvell.com Signed-off-by: Jakub Kicinski --- drivers/dpll/zl3073x/devlink.c | 6 +- drivers/net/ethernet/amazon/ena/ena_devlink.c | 8 +-- drivers/net/ethernet/amd/pds_core/core.h | 2 +- drivers/net/ethernet/amd/pds_core/devlink.c | 2 +- drivers/net/ethernet/broadcom/bnxt/bnxt_devlink.c | 6 +- drivers/net/ethernet/intel/ice/devlink/devlink.c | 30 ++++----- .../ethernet/marvell/octeontx2/af/rvu_devlink.c | 22 +++---- .../ethernet/marvell/octeontx2/nic/otx2_devlink.c | 4 +- drivers/net/ethernet/mellanox/mlx4/main.c | 14 ++--- drivers/net/ethernet/mellanox/mlx5/core/devlink.c | 72 +++++++++++----------- .../ethernet/mellanox/mlx5/core/eswitch_offloads.c | 2 +- drivers/net/ethernet/mellanox/mlx5/core/fs_core.c | 4 +- .../net/ethernet/mellanox/mlx5/core/lib/nv_param.c | 12 ++-- drivers/net/ethernet/mellanox/mlxsw/core.c | 8 +-- drivers/net/ethernet/netronome/nfp/devlink_param.c | 6 +- drivers/net/netdevsim/dev.c | 4 +- include/net/devlink.h | 4 +- net/devlink/param.c | 32 +++++----- 18 files changed, 119 insertions(+), 119 deletions(-) (limited to 'include') diff --git a/drivers/dpll/zl3073x/devlink.c b/drivers/dpll/zl3073x/devlink.c index ccc22332b346..218b08fd8a30 100644 --- a/drivers/dpll/zl3073x/devlink.c +++ b/drivers/dpll/zl3073x/devlink.c @@ -315,10 +315,10 @@ EXPORT_SYMBOL_NS_GPL(zl3073x_devm_alloc, "ZL3073X"); static int zl3073x_devlink_param_clock_id_validate(struct devlink *devlink, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack) { - if (!val.vu64) { + if (!val->vu64) { NL_SET_ERR_MSG_MOD(extack, "'clock_id' must be non-zero"); return -EINVAL; } @@ -377,7 +377,7 @@ int zl3073x_devlink_register(struct zl3073x_dev *zldev) value.vu64 = zldev->clock_id; devl_param_driverinit_value_set(devlink, DEVLINK_PARAM_GENERIC_ID_CLOCK_ID, - value); + &value); /* Register devlink instance */ devl_register(devlink); diff --git a/drivers/net/ethernet/amazon/ena/ena_devlink.c b/drivers/net/ethernet/amazon/ena/ena_devlink.c index 4772185e669d..5ea9fef149aa 100644 --- a/drivers/net/ethernet/amazon/ena/ena_devlink.c +++ b/drivers/net/ethernet/amazon/ena/ena_devlink.c @@ -8,12 +8,12 @@ #include "ena_phc.h" static int ena_devlink_enable_phc_validate(struct devlink *devlink, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack) { struct ena_adapter *adapter = ENA_DEVLINK_PRIV(devlink); - if (!val.vbool) + if (!val->vbool) return 0; if (!ena_com_phc_supported(adapter->ena_dev)) { @@ -57,7 +57,7 @@ void ena_devlink_disable_phc_param(struct devlink *devlink) value.vbool = false; devl_param_driverinit_value_set(devlink, DEVLINK_PARAM_GENERIC_ID_ENABLE_PHC, - value); + &value); devl_unlock(devlink); } @@ -151,7 +151,7 @@ static int ena_devlink_configure_params(struct devlink *devlink) value.vbool = ena_phc_is_enabled(adapter); devl_param_driverinit_value_set(devlink, DEVLINK_PARAM_GENERIC_ID_ENABLE_PHC, - value); + &value); devl_unlock(devlink); return 0; diff --git a/drivers/net/ethernet/amd/pds_core/core.h b/drivers/net/ethernet/amd/pds_core/core.h index 4a6b35c84dab..b7fe9ad73349 100644 --- a/drivers/net/ethernet/amd/pds_core/core.h +++ b/drivers/net/ethernet/amd/pds_core/core.h @@ -261,7 +261,7 @@ int pdsc_dl_enable_set(struct devlink *dl, u32 id, struct devlink_param_gset_ctx *ctx, struct netlink_ext_ack *extack); int pdsc_dl_enable_validate(struct devlink *dl, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack); void __iomem *pdsc_map_dbpage(struct pdsc *pdsc, int page_num); diff --git a/drivers/net/ethernet/amd/pds_core/devlink.c b/drivers/net/ethernet/amd/pds_core/devlink.c index 3f0e56b951bf..2ea97e1c5939 100644 --- a/drivers/net/ethernet/amd/pds_core/devlink.c +++ b/drivers/net/ethernet/amd/pds_core/devlink.c @@ -68,7 +68,7 @@ int pdsc_dl_enable_set(struct devlink *dl, u32 id, } int pdsc_dl_enable_validate(struct devlink *dl, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack) { struct pdsc *pdsc = devlink_priv(dl); diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt_devlink.c b/drivers/net/ethernet/broadcom/bnxt/bnxt_devlink.c index 835f2b413931..eb17a3454b4c 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt_devlink.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt_devlink.c @@ -1123,7 +1123,7 @@ static int bnxt_dl_nvm_param_set(struct devlink *dl, u32 id, } static int bnxt_dl_roce_validate(struct devlink *dl, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack) { const struct bnxt_dl_nvm_param nvm_roce_cap = {0, NVM_OFF_RDMA_CAPABLE, @@ -1149,7 +1149,7 @@ static int bnxt_dl_roce_validate(struct devlink *dl, u32 id, } static int bnxt_dl_msix_validate(struct devlink *dl, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack) { int max_val = -1; @@ -1160,7 +1160,7 @@ static int bnxt_dl_msix_validate(struct devlink *dl, u32 id, if (id == DEVLINK_PARAM_GENERIC_ID_MSIX_VEC_PER_PF_MIN) max_val = BNXT_MSIX_VEC_MIN_MAX; - if (val.vu32 > max_val) { + if (val->vu32 > max_val) { NL_SET_ERR_MSG_MOD(extack, "MSIX value is exceeding the range"); return -EINVAL; } diff --git a/drivers/net/ethernet/intel/ice/devlink/devlink.c b/drivers/net/ethernet/intel/ice/devlink/devlink.c index 641d6e289d5c..22b7d8e6bd9e 100644 --- a/drivers/net/ethernet/intel/ice/devlink/devlink.c +++ b/drivers/net/ethernet/intel/ice/devlink/devlink.c @@ -671,10 +671,10 @@ static int ice_devlink_tx_sched_layers_set(struct devlink *devlink, u32 id, * error. */ static int ice_devlink_tx_sched_layers_validate(struct devlink *devlink, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack) { - if (val.vu8 != ICE_SCHED_5_LAYERS && val.vu8 != ICE_SCHED_9_LAYERS) { + if (val->vu8 != ICE_SCHED_5_LAYERS && val->vu8 != ICE_SCHED_9_LAYERS) { NL_SET_ERR_MSG_MOD(extack, "Wrong number of tx scheduler layers provided."); return -EINVAL; @@ -1398,7 +1398,7 @@ static int ice_devlink_enable_roce_set(struct devlink *devlink, u32 id, static int ice_devlink_enable_roce_validate(struct devlink *devlink, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack) { struct ice_pf *pf = devlink_priv(devlink); @@ -1465,7 +1465,7 @@ static int ice_devlink_enable_iw_set(struct devlink *devlink, u32 id, static int ice_devlink_enable_iw_validate(struct devlink *devlink, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack) { struct ice_pf *pf = devlink_priv(devlink); @@ -1591,10 +1591,10 @@ static int ice_devlink_local_fwd_set(struct devlink *devlink, u32 id, * error. */ static int ice_devlink_local_fwd_validate(struct devlink *devlink, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack) { - if (ice_devlink_local_fwd_str_to_mode(val.vstr) < 0) { + if (ice_devlink_local_fwd_str_to_mode(val->vstr) < 0) { NL_SET_ERR_MSG_MOD(extack, "Error: Requested value is not supported."); return -EINVAL; } @@ -1604,12 +1604,12 @@ static int ice_devlink_local_fwd_validate(struct devlink *devlink, u32 id, static int ice_devlink_msix_max_pf_validate(struct devlink *devlink, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack) { struct ice_pf *pf = devlink_priv(devlink); - if (val.vu32 > pf->hw.func_caps.common_cap.num_msix_vectors) + if (val->vu32 > pf->hw.func_caps.common_cap.num_msix_vectors) return -EINVAL; return 0; @@ -1617,21 +1617,21 @@ ice_devlink_msix_max_pf_validate(struct devlink *devlink, u32 id, static int ice_devlink_msix_min_pf_validate(struct devlink *devlink, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack) { - if (val.vu32 < ICE_MIN_MSIX) + if (val->vu32 < ICE_MIN_MSIX) return -EINVAL; return 0; } static int ice_devlink_enable_rdma_validate(struct devlink *devlink, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack) { struct ice_pf *pf = devlink_priv(devlink); - bool new_state = val.vbool; + bool new_state = val->vbool; if (new_state && !test_bit(ICE_FLAG_RDMA_ENA, pf->flags)) return -EOPNOTSUPP; @@ -1791,16 +1791,16 @@ int ice_devlink_register_params(struct ice_pf *pf) value.vu32 = pf->msix.max; devl_param_driverinit_value_set(devlink, DEVLINK_PARAM_GENERIC_ID_MSIX_VEC_PER_PF_MAX, - value); + &value); value.vu32 = pf->msix.min; devl_param_driverinit_value_set(devlink, DEVLINK_PARAM_GENERIC_ID_MSIX_VEC_PER_PF_MIN, - value); + &value); value.vbool = test_bit(ICE_FLAG_RDMA_ENA, pf->flags); devl_param_driverinit_value_set(devlink, DEVLINK_PARAM_GENERIC_ID_ENABLE_RDMA, - value); + &value); return 0; diff --git a/drivers/net/ethernet/marvell/octeontx2/af/rvu_devlink.c b/drivers/net/ethernet/marvell/octeontx2/af/rvu_devlink.c index 6494a9ee2f0d..a42404e6db7c 100644 --- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_devlink.c +++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_devlink.c @@ -1180,12 +1180,12 @@ static void rvu_health_reporters_destroy(struct rvu *rvu) /* Devlink Params APIs */ static int rvu_af_dl_dwrr_mtu_validate(struct devlink *devlink, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack) { struct rvu_devlink *rvu_dl = devlink_priv(devlink); struct rvu *rvu = rvu_dl->rvu; - int dwrr_mtu = val.vu32; + int dwrr_mtu = val->vu32; struct nix_txsch *txsch; struct nix_hw *nix_hw; @@ -1295,14 +1295,14 @@ static int rvu_af_npc_defrag(struct devlink *devlink, u32 id, } static int rvu_af_npc_defrag_feature_validate(struct devlink *devlink, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack) { struct rvu_devlink *rvu_dl = devlink_priv(devlink); struct rvu *rvu = rvu_dl->rvu; u64 enable; - if (kstrtoull(val.vstr, 10, &enable)) { + if (kstrtoull(val->vstr, 10, &enable)) { NL_SET_ERR_MSG_MOD(extack, "Only 1 value is supported"); return -EINVAL; @@ -1351,14 +1351,14 @@ static int rvu_af_npc_exact_feature_disable(struct devlink *devlink, u32 id, } static int rvu_af_npc_exact_feature_validate(struct devlink *devlink, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack) { struct rvu_devlink *rvu_dl = devlink_priv(devlink); struct rvu *rvu = rvu_dl->rvu; u64 enable; - if (kstrtoull(val.vstr, 10, &enable)) { + if (kstrtoull(val->vstr, 10, &enable)) { NL_SET_ERR_MSG_MOD(extack, "Only 1 value is supported"); return -EINVAL; @@ -1414,7 +1414,7 @@ static int rvu_af_dl_npc_mcam_high_zone_percent_set(struct devlink *devlink, u32 } static int rvu_af_dl_npc_mcam_high_zone_percent_validate(struct devlink *devlink, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack) { struct rvu_devlink *rvu_dl = devlink_priv(devlink); @@ -1422,7 +1422,7 @@ static int rvu_af_dl_npc_mcam_high_zone_percent_validate(struct devlink *devlink struct npc_mcam *mcam; /* The percent of high prio zone must range from 12% to 100% of unreserved mcam space */ - if (val.vu8 < 12 || val.vu8 > 100) { + if (val->vu8 < 12 || val->vu8 > 100) { NL_SET_ERR_MSG_MOD(extack, "mcam high zone percent must be between 12% to 100%"); return -EINVAL; @@ -1504,7 +1504,7 @@ static int rvu_af_dl_nix_maxlf_set(struct devlink *devlink, u32 id, } static int rvu_af_dl_nix_maxlf_validate(struct devlink *devlink, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack) { struct rvu_devlink *rvu_dl = devlink_priv(devlink); @@ -1528,13 +1528,13 @@ static int rvu_af_dl_nix_maxlf_validate(struct devlink *devlink, u32 id, return -EPERM; } - if (max_nix0_lf && val.vu16 > max_nix0_lf) { + if (max_nix0_lf && val->vu16 > max_nix0_lf) { NL_SET_ERR_MSG_MOD(extack, "requested nixlf is greater than the max supported nix0_lf"); return -EPERM; } - if (max_nix1_lf && val.vu16 > max_nix1_lf) { + if (max_nix1_lf && val->vu16 > max_nix1_lf) { NL_SET_ERR_MSG_MOD(extack, "requested nixlf is greater than the max supported nix1_lf"); return -EINVAL; diff --git a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_devlink.c b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_devlink.c index a72694219df4..4a5ce0e67dda 100644 --- a/drivers/net/ethernet/marvell/octeontx2/nic/otx2_devlink.c +++ b/drivers/net/ethernet/marvell/octeontx2/nic/otx2_devlink.c @@ -8,7 +8,7 @@ /* Devlink Params APIs */ static int otx2_dl_mcam_count_validate(struct devlink *devlink, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack) { struct otx2_devlink *otx2_dl = devlink_priv(devlink); @@ -97,7 +97,7 @@ static int otx2_dl_ucast_flt_cnt_get(struct devlink *devlink, u32 id, } static int otx2_dl_ucast_flt_cnt_validate(struct devlink *devlink, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack) { struct otx2_devlink *otx2_dl = devlink_priv(devlink); diff --git a/drivers/net/ethernet/mellanox/mlx4/main.c b/drivers/net/ethernet/mellanox/mlx4/main.c index 4fe6dbf0942f..c851daa5da9f 100644 --- a/drivers/net/ethernet/mellanox/mlx4/main.c +++ b/drivers/net/ethernet/mellanox/mlx4/main.c @@ -213,10 +213,10 @@ static int mlx4_devlink_crdump_snapshot_set(struct devlink *devlink, u32 id, static int mlx4_devlink_max_macs_validate(struct devlink *devlink, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack) { - u32 value = val.vu32; + u32 value = val->vu32; if (value < 1 || value > 128) return -ERANGE; @@ -266,27 +266,27 @@ static void mlx4_devlink_set_params_init_values(struct devlink *devlink) value.vbool = !!mlx4_internal_err_reset; devl_param_driverinit_value_set(devlink, DEVLINK_PARAM_GENERIC_ID_INT_ERR_RESET, - value); + &value); value.vu32 = 1UL << log_num_mac; devl_param_driverinit_value_set(devlink, DEVLINK_PARAM_GENERIC_ID_MAX_MACS, - value); + &value); value.vbool = enable_64b_cqe_eqe; devl_param_driverinit_value_set(devlink, MLX4_DEVLINK_PARAM_ID_ENABLE_64B_CQE_EQE, - value); + &value); value.vbool = enable_4k_uar; devl_param_driverinit_value_set(devlink, MLX4_DEVLINK_PARAM_ID_ENABLE_4K_UAR, - value); + &value); value.vbool = false; devl_param_driverinit_value_set(devlink, DEVLINK_PARAM_GENERIC_ID_REGION_SNAPSHOT, - value); + &value); } static inline void mlx4_set_num_reserved_uars(struct mlx4_dev *dev, diff --git a/drivers/net/ethernet/mellanox/mlx5/core/devlink.c b/drivers/net/ethernet/mellanox/mlx5/core/devlink.c index 73cf0321bb86..c31e05529fc4 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/devlink.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/devlink.c @@ -459,11 +459,11 @@ void mlx5_devlink_free(struct devlink *devlink) } static int mlx5_devlink_enable_roce_validate(struct devlink *devlink, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack) { struct mlx5_core_dev *dev = devlink_priv(devlink); - bool new_state = val.vbool; + bool new_state = val->vbool; if (new_state && !MLX5_CAP_GEN(dev, roce) && !(MLX5_CAP_GEN(dev, roce_rw_supported) && MLX5_CAP_GEN_MAX(dev, roce))) { @@ -480,10 +480,10 @@ static int mlx5_devlink_enable_roce_validate(struct devlink *devlink, u32 id, #ifdef CONFIG_MLX5_ESWITCH static int mlx5_devlink_large_group_num_validate(struct devlink *devlink, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack) { - int group_num = val.vu32; + int group_num = val->vu32; if (group_num < 1 || group_num > 1024) { NL_SET_ERR_MSG_MOD(extack, @@ -496,27 +496,27 @@ static int mlx5_devlink_large_group_num_validate(struct devlink *devlink, u32 id #endif static int mlx5_devlink_eq_depth_validate(struct devlink *devlink, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack) { - return (val.vu32 >= 64 && val.vu32 <= 4096) ? 0 : -EINVAL; + return (val->vu32 >= 64 && val->vu32 <= 4096) ? 0 : -EINVAL; } static int mlx5_devlink_hairpin_num_queues_validate(struct devlink *devlink, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack) { - return val.vu32 ? 0 : -EINVAL; + return val->vu32 ? 0 : -EINVAL; } static int mlx5_devlink_hairpin_queue_size_validate(struct devlink *devlink, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack) { struct mlx5_core_dev *dev = devlink_priv(devlink); - u32 val32 = val.vu32; + u32 val32 = val->vu32; if (!is_power_of_2(val32)) { NL_SET_ERR_MSG_MOD(extack, "Value is not power of two"); @@ -534,11 +534,11 @@ mlx5_devlink_hairpin_queue_size_validate(struct devlink *devlink, u32 id, } static int mlx5_devlink_num_doorbells_validate(struct devlink *devlink, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack) { struct mlx5_core_dev *mdev = devlink_priv(devlink); - u32 val32 = val.vu32; + u32 val32 = val->vu32; u32 max_num_channels; max_num_channels = mlx5e_get_max_num_channels(mdev); @@ -567,13 +567,13 @@ static void mlx5_devlink_hairpin_params_init_values(struct devlink *devlink) value.vu32 = link_speed64; devl_param_driverinit_value_set( - devlink, MLX5_DEVLINK_PARAM_ID_HAIRPIN_NUM_QUEUES, value); + devlink, MLX5_DEVLINK_PARAM_ID_HAIRPIN_NUM_QUEUES, &value); value.vu32 = BIT(min_t(u32, 16 - MLX5_MPWRQ_MIN_LOG_STRIDE_SZ(dev), MLX5_CAP_GEN(dev, log_max_hairpin_num_packets))); devl_param_driverinit_value_set( - devlink, MLX5_DEVLINK_PARAM_ID_HAIRPIN_QUEUE_SIZE, value); + devlink, MLX5_DEVLINK_PARAM_ID_HAIRPIN_QUEUE_SIZE, &value); } static const struct devlink_param mlx5_devlink_params[] = { @@ -600,24 +600,24 @@ static void mlx5_devlink_set_params_init_values(struct devlink *devlink) value.vbool = MLX5_CAP_GEN(dev, roce) && !mlx5_dev_is_lightweight(dev); devl_param_driverinit_value_set(devlink, DEVLINK_PARAM_GENERIC_ID_ENABLE_ROCE, - value); + &value); #ifdef CONFIG_MLX5_ESWITCH value.vu32 = ESW_OFFLOADS_DEFAULT_NUM_GROUPS; devl_param_driverinit_value_set(devlink, MLX5_DEVLINK_PARAM_ID_ESW_LARGE_GROUP_NUM, - value); + &value); #endif value.vu32 = MLX5_COMP_EQ_SIZE; devl_param_driverinit_value_set(devlink, DEVLINK_PARAM_GENERIC_ID_IO_EQ_SIZE, - value); + &value); value.vu32 = MLX5_NUM_ASYNC_EQE; devl_param_driverinit_value_set(devlink, DEVLINK_PARAM_GENERIC_ID_EVENT_EQ_SIZE, - value); + &value); } static const struct devlink_param mlx5_devlink_eth_params[] = { @@ -653,14 +653,14 @@ static int mlx5_devlink_eth_params_register(struct devlink *devlink) value.vbool = !mlx5_dev_is_lightweight(dev); devl_param_driverinit_value_set(devlink, DEVLINK_PARAM_GENERIC_ID_ENABLE_ETH, - value); + &value); mlx5_devlink_hairpin_params_init_values(devlink); value.vu32 = MLX5_DEFAULT_NUM_DOORBELLS; devl_param_driverinit_value_set(devlink, DEVLINK_PARAM_GENERIC_ID_NUM_DOORBELLS, - value); + &value); return 0; } @@ -681,12 +681,12 @@ static void mlx5_devlink_eth_params_unregister(struct devlink *devlink) static int mlx5_devlink_pcie_cong_thresh_validate(struct devlink *devl, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack) { - if (val.vu16 > MLX5_PCIE_CONG_THRESH_MAX) { + if (val->vu16 > MLX5_PCIE_CONG_THRESH_MAX) { NL_SET_ERR_MSG_FMT_MOD(extack, "Value %u > max supported (%u)", - val.vu16, MLX5_PCIE_CONG_THRESH_MAX); + val->vu16, MLX5_PCIE_CONG_THRESH_MAX); return -EINVAL; } @@ -711,19 +711,19 @@ static void mlx5_devlink_pcie_cong_init_values(struct devlink *devlink) value.vu16 = MLX5_PCIE_CONG_THRESH_DEF_LOW; id = MLX5_DEVLINK_PARAM_ID_PCIE_CONG_IN_LOW; - devl_param_driverinit_value_set(devlink, id, value); + devl_param_driverinit_value_set(devlink, id, &value); value.vu16 = MLX5_PCIE_CONG_THRESH_DEF_HIGH; id = MLX5_DEVLINK_PARAM_ID_PCIE_CONG_IN_HIGH; - devl_param_driverinit_value_set(devlink, id, value); + devl_param_driverinit_value_set(devlink, id, &value); value.vu16 = MLX5_PCIE_CONG_THRESH_DEF_LOW; id = MLX5_DEVLINK_PARAM_ID_PCIE_CONG_OUT_LOW; - devl_param_driverinit_value_set(devlink, id, value); + devl_param_driverinit_value_set(devlink, id, &value); value.vu16 = MLX5_PCIE_CONG_THRESH_DEF_HIGH; id = MLX5_DEVLINK_PARAM_ID_PCIE_CONG_OUT_HIGH; - devl_param_driverinit_value_set(devlink, id, value); + devl_param_driverinit_value_set(devlink, id, &value); } static const struct devlink_param mlx5_devlink_pcie_cong_params[] = { @@ -775,11 +775,11 @@ static void mlx5_devlink_pcie_cong_params_unregister(struct devlink *devlink) } static int mlx5_devlink_enable_rdma_validate(struct devlink *devlink, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack) { struct mlx5_core_dev *dev = devlink_priv(devlink); - bool new_state = val.vbool; + bool new_state = val->vbool; if (new_state && !mlx5_rdma_supported(dev)) return -EOPNOTSUPP; @@ -808,7 +808,7 @@ static int mlx5_devlink_rdma_params_register(struct devlink *devlink) value.vbool = !mlx5_dev_is_lightweight(dev); devl_param_driverinit_value_set(devlink, DEVLINK_PARAM_GENERIC_ID_ENABLE_RDMA, - value); + &value); return 0; } @@ -843,7 +843,7 @@ static int mlx5_devlink_vnet_params_register(struct devlink *devlink) value.vbool = !mlx5_dev_is_lightweight(dev); devl_param_driverinit_value_set(devlink, DEVLINK_PARAM_GENERIC_ID_ENABLE_VNET, - value); + &value); return 0; } @@ -890,22 +890,22 @@ static void mlx5_devlink_auxdev_params_unregister(struct devlink *devlink) } static int mlx5_devlink_max_uc_list_validate(struct devlink *devlink, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack) { struct mlx5_core_dev *dev = devlink_priv(devlink); - if (val.vu32 == 0) { + if (val->vu32 == 0) { NL_SET_ERR_MSG_MOD(extack, "max_macs value must be greater than 0"); return -EINVAL; } - if (!is_power_of_2(val.vu32)) { + if (!is_power_of_2(val->vu32)) { NL_SET_ERR_MSG_MOD(extack, "Only power of 2 values are supported for max_macs"); return -EINVAL; } - if (ilog2(val.vu32) > + if (ilog2(val->vu32) > MLX5_CAP_GEN_MAX(dev, log_max_current_uc_list)) { NL_SET_ERR_MSG_MOD(extack, "max_macs value is out of the supported range"); return -EINVAL; @@ -936,7 +936,7 @@ static int mlx5_devlink_max_uc_list_params_register(struct devlink *devlink) value.vu32 = 1 << MLX5_CAP_GEN(dev, log_max_current_uc_list); devl_param_driverinit_value_set(devlink, DEVLINK_PARAM_GENERIC_ID_MAX_MACS, - value); + &value); return 0; } diff --git a/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c b/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c index 355d27934fb4..189be11c4c39 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/eswitch_offloads.c @@ -2748,7 +2748,7 @@ static int esw_port_metadata_get(struct devlink *devlink, u32 id, } static int esw_port_metadata_validate(struct devlink *devlink, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack) { struct mlx5_core_dev *dev = devlink_priv(devlink); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/fs_core.c b/drivers/net/ethernet/mellanox/mlx5/core/fs_core.c index 61a6ba1e49dd..c8f6adae6f51 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/fs_core.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/fs_core.c @@ -3765,11 +3765,11 @@ cleanup: } static int mlx5_fs_mode_validate(struct devlink *devlink, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack) { struct mlx5_core_dev *dev = devlink_priv(devlink); - char *value = val.vstr; + char *value = val->vstr; u8 eswitch_mode; eswitch_mode = mlx5_eswitch_mode(dev); diff --git a/drivers/net/ethernet/mellanox/mlx5/core/lib/nv_param.c b/drivers/net/ethernet/mellanox/mlx5/core/lib/nv_param.c index 19bb620b7436..4a7275e8b62e 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/lib/nv_param.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/lib/nv_param.c @@ -270,13 +270,13 @@ mlx5_nv_param_devlink_cqe_compress_get(struct devlink *devlink, u32 id, static int mlx5_nv_param_devlink_cqe_compress_validate(struct devlink *devlink, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack) { int i; for (i = 0; i < ARRAY_SIZE(cqe_compress_str); i++) { - if (!strcmp(val.vstr, cqe_compress_str[i])) + if (!strcmp(val->vstr, cqe_compress_str[i])) return 0; } @@ -374,7 +374,7 @@ mlx5_devlink_swp_l4_csum_mode_get(struct devlink *devlink, u32 id, static int mlx5_devlink_swp_l4_csum_mode_validate(struct devlink *devlink, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack) { struct mlx5_core_dev *dev = devlink_priv(devlink); @@ -383,7 +383,7 @@ mlx5_devlink_swp_l4_csum_mode_validate(struct devlink *devlink, u32 id, int err, i; for (i = 0; i < ARRAY_SIZE(swp_l4_csum_mode_str); i++) { - if (!strcmp(val.vstr, swp_l4_csum_mode_str[i])) + if (!strcmp(val->vstr, swp_l4_csum_mode_str[i])) break; } @@ -727,7 +727,7 @@ static int mlx5_devlink_total_vfs_set(struct devlink *devlink, u32 id, } static int mlx5_devlink_total_vfs_validate(struct devlink *devlink, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack) { struct mlx5_core_dev *dev = devlink_priv(devlink); @@ -746,7 +746,7 @@ static int mlx5_devlink_total_vfs_validate(struct devlink *devlink, u32 id, return 0; /* optimistic, but set might fail later */ max = MLX5_GET(nv_global_pci_cap, data, max_vfs_per_pf); - if (val.vu16 > max) { + if (val->vu16 > max) { NL_SET_ERR_MSG_FMT_MOD(extack, "Max allowed by device is %u", max); return -EINVAL; diff --git a/drivers/net/ethernet/mellanox/mlxsw/core.c b/drivers/net/ethernet/mellanox/mlxsw/core.c index d76246301f67..308d8a94865f 100644 --- a/drivers/net/ethernet/mellanox/mlxsw/core.c +++ b/drivers/net/ethernet/mellanox/mlxsw/core.c @@ -1306,11 +1306,11 @@ static int mlxsw_core_fw_flash_update(struct mlxsw_core *mlxsw_core, } static int mlxsw_core_devlink_param_fw_load_policy_validate(struct devlink *devlink, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack) { - if (val.vu8 != DEVLINK_PARAM_FW_LOAD_POLICY_VALUE_DRIVER && - val.vu8 != DEVLINK_PARAM_FW_LOAD_POLICY_VALUE_FLASH) { + if (val->vu8 != DEVLINK_PARAM_FW_LOAD_POLICY_VALUE_DRIVER && + val->vu8 != DEVLINK_PARAM_FW_LOAD_POLICY_VALUE_FLASH) { NL_SET_ERR_MSG_MOD(extack, "'fw_load_policy' must be 'driver' or 'flash'"); return -EINVAL; } @@ -1337,7 +1337,7 @@ static int mlxsw_core_fw_params_register(struct mlxsw_core *mlxsw_core) value.vu8 = DEVLINK_PARAM_FW_LOAD_POLICY_VALUE_DRIVER; devl_param_driverinit_value_set(devlink, DEVLINK_PARAM_GENERIC_ID_FW_LOAD_POLICY, - value); + &value); return 0; } diff --git a/drivers/net/ethernet/netronome/nfp/devlink_param.c b/drivers/net/ethernet/netronome/nfp/devlink_param.c index 85e3b19e6165..826527992e4a 100644 --- a/drivers/net/ethernet/netronome/nfp/devlink_param.c +++ b/drivers/net/ethernet/netronome/nfp/devlink_param.c @@ -170,7 +170,7 @@ exit_close_nsp: static int nfp_devlink_param_u8_validate(struct devlink *devlink, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack) { const struct nfp_devlink_param_u8_arg *arg; @@ -180,12 +180,12 @@ nfp_devlink_param_u8_validate(struct devlink *devlink, u32 id, arg = &nfp_devlink_u8_args[id]; - if (val.vu8 > arg->max_dl_val) { + if (val->vu8 > arg->max_dl_val) { NL_SET_ERR_MSG_MOD(extack, "parameter out of range"); return -EINVAL; } - if (val.vu8 == arg->invalid_dl_val) { + if (val->vu8 == arg->invalid_dl_val) { NL_SET_ERR_MSG_MOD(extack, "unknown/invalid value specified"); return -EINVAL; } diff --git a/drivers/net/netdevsim/dev.c b/drivers/net/netdevsim/dev.c index f00fc2f9ebde..aed9ad5f1b43 100644 --- a/drivers/net/netdevsim/dev.c +++ b/drivers/net/netdevsim/dev.c @@ -597,11 +597,11 @@ static void nsim_devlink_set_params_init_values(struct nsim_dev *nsim_dev, value.vu32 = nsim_dev->max_macs; devl_param_driverinit_value_set(devlink, DEVLINK_PARAM_GENERIC_ID_MAX_MACS, - value); + &value); value.vbool = nsim_dev->test1; devl_param_driverinit_value_set(devlink, NSIM_DEVLINK_PARAM_ID_TEST1, - value); + &value); } static void nsim_devlink_param_load_driverinit_values(struct devlink *devlink) diff --git a/include/net/devlink.h b/include/net/devlink.h index bcd31de1f890..5f4083dc4345 100644 --- a/include/net/devlink.h +++ b/include/net/devlink.h @@ -501,7 +501,7 @@ struct devlink_param { struct devlink_param_gset_ctx *ctx, struct netlink_ext_ack *extack); int (*validate)(struct devlink *devlink, u32 id, - union devlink_param_value val, + union devlink_param_value *val, struct netlink_ext_ack *extack); int (*get_default)(struct devlink *devlink, u32 id, struct devlink_param_gset_ctx *ctx, @@ -1923,7 +1923,7 @@ void devlink_params_unregister(struct devlink *devlink, int devl_param_driverinit_value_get(struct devlink *devlink, u32 param_id, union devlink_param_value *val); void devl_param_driverinit_value_set(struct devlink *devlink, u32 param_id, - union devlink_param_value init_val); + union devlink_param_value *init_val); void devl_param_value_changed(struct devlink *devlink, u32 param_id); struct devlink_region *devl_region_create(struct devlink *devlink, const struct devlink_region_ops *ops, diff --git a/net/devlink/param.c b/net/devlink/param.c index cf95268da5b0..1a196d3a843d 100644 --- a/net/devlink/param.c +++ b/net/devlink/param.c @@ -216,28 +216,28 @@ static int devlink_param_reset_default(struct devlink *devlink, static int devlink_nl_param_value_put(struct sk_buff *msg, enum devlink_param_type type, - int nla_type, union devlink_param_value val, + int nla_type, union devlink_param_value *val, bool flag_as_u8) { switch (type) { case DEVLINK_PARAM_TYPE_U8: - if (nla_put_u8(msg, nla_type, val.vu8)) + if (nla_put_u8(msg, nla_type, val->vu8)) return -EMSGSIZE; break; case DEVLINK_PARAM_TYPE_U16: - if (nla_put_u16(msg, nla_type, val.vu16)) + if (nla_put_u16(msg, nla_type, val->vu16)) return -EMSGSIZE; break; case DEVLINK_PARAM_TYPE_U32: - if (nla_put_u32(msg, nla_type, val.vu32)) + if (nla_put_u32(msg, nla_type, val->vu32)) return -EMSGSIZE; break; case DEVLINK_PARAM_TYPE_U64: - if (devlink_nl_put_u64(msg, nla_type, val.vu64)) + if (devlink_nl_put_u64(msg, nla_type, val->vu64)) return -EMSGSIZE; break; case DEVLINK_PARAM_TYPE_STRING: - if (nla_put_string(msg, nla_type, val.vstr)) + if (nla_put_string(msg, nla_type, val->vstr)) return -EMSGSIZE; break; case DEVLINK_PARAM_TYPE_BOOL: @@ -245,10 +245,10 @@ devlink_nl_param_value_put(struct sk_buff *msg, enum devlink_param_type type, * false can be distinguished from not present */ if (flag_as_u8) { - if (nla_put_u8(msg, nla_type, val.vbool)) + if (nla_put_u8(msg, nla_type, val->vbool)) return -EMSGSIZE; } else { - if (val.vbool && nla_put_flag(msg, nla_type)) + if (val->vbool && nla_put_flag(msg, nla_type)) return -EMSGSIZE; } break; @@ -260,8 +260,8 @@ static int devlink_nl_param_value_fill_one(struct sk_buff *msg, enum devlink_param_type type, enum devlink_param_cmode cmode, - union devlink_param_value val, - union devlink_param_value default_val, + union devlink_param_value *val, + union devlink_param_value *default_val, bool has_default) { struct nlattr *param_value_attr; @@ -383,8 +383,8 @@ static int devlink_nl_param_fill(struct sk_buff *msg, struct devlink *devlink, if (!param_value_set[i]) continue; err = devlink_nl_param_value_fill_one(msg, param->type, - i, param_value[i], - default_value[i], + i, ¶m_value[i], + &default_value[i], default_value_set[i]); if (err) goto values_list_nest_cancel; @@ -621,7 +621,7 @@ static int __devlink_nl_cmd_param_set_doit(struct devlink *devlink, if (err) return err; if (param->validate) { - err = param->validate(devlink, param->id, value, + err = param->validate(devlink, param->id, &value, info->extack); if (err) return err; @@ -888,7 +888,7 @@ EXPORT_SYMBOL_GPL(devl_param_driverinit_value_get); * configuration mode default value. */ void devl_param_driverinit_value_set(struct devlink *devlink, u32 param_id, - union devlink_param_value init_val) + union devlink_param_value *init_val) { struct devlink_param_item *param_item; @@ -902,9 +902,9 @@ void devl_param_driverinit_value_set(struct devlink *devlink, u32 param_id, DEVLINK_PARAM_CMODE_DRIVERINIT))) return; - param_item->driverinit_value = init_val; + param_item->driverinit_value = *init_val; param_item->driverinit_value_valid = true; - param_item->driverinit_default = init_val; + param_item->driverinit_default = *init_val; devlink_param_notify(devlink, 0, param_item, DEVLINK_CMD_PARAM_NEW); } -- cgit v1.2.3 From 0b13c6a618d09b20dbb1a33bc354764cbac6f2bd Mon Sep 17 00:00:00 2001 From: Tim Bird Date: Fri, 22 May 2026 16:55:08 -0600 Subject: llc: Add SPDX id lines to some llc source files Most of the lls source files are missing SPDX-License-Identifier lines. Add appropriate IDs to these files, and remove other license info from the header. In once case, leave the existing id line and just remove the license reference text. Signed-off-by: Tim Bird Link: https://patch.msgid.link/20260522225508.24006-1-tim.bird@sony.com Signed-off-by: Jakub Kicinski --- include/linux/llc.h | 8 +------- include/net/llc.h | 8 +------- include/uapi/linux/llc.h | 7 ------- net/llc/Makefile | 8 +------- net/llc/af_llc.c | 8 +------- net/llc/llc_c_ac.c | 8 +------- net/llc/llc_c_ev.c | 8 +------- net/llc/llc_c_st.c | 8 +------- net/llc/llc_conn.c | 8 +------- net/llc/llc_core.c | 8 +------- net/llc/llc_if.c | 8 +------- net/llc/llc_input.c | 8 +------- net/llc/llc_pdu.c | 8 +------- net/llc/llc_proc.c | 8 +------- net/llc/llc_s_ac.c | 8 +------- net/llc/llc_s_ev.c | 8 +------- net/llc/llc_s_st.c | 8 +------- net/llc/llc_sap.c | 8 +------- net/llc/llc_station.c | 8 +------- 19 files changed, 18 insertions(+), 133 deletions(-) (limited to 'include') diff --git a/include/linux/llc.h b/include/linux/llc.h index b965314d017f..944e9e213112 100644 --- a/include/linux/llc.h +++ b/include/linux/llc.h @@ -1,14 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* * IEEE 802.2 User Interface SAPs for Linux, data structures and indicators. * * Copyright (c) 2001 by Jay Schulist - * - * This program can be redistributed or modified under the terms of the - * GNU General Public License as published by the Free Software Foundation. - * This program is distributed without any warranty or implied warranty - * of merchantability or fitness for a particular purpose. - * - * See the GNU General Public License for more details. */ #ifndef __LINUX_LLC_H #define __LINUX_LLC_H diff --git a/include/net/llc.h b/include/net/llc.h index e250dca03963..029ba8a22319 100644 --- a/include/net/llc.h +++ b/include/net/llc.h @@ -1,15 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ #ifndef LLC_H #define LLC_H /* * Copyright (c) 1997 by Procom Technology, Inc. * 2001-2003 by Arnaldo Carvalho de Melo - * - * This program can be redistributed or modified under the terms of the - * GNU General Public License as published by the Free Software Foundation. - * This program is distributed without any warranty or implied warranty - * of merchantability or fitness for a particular purpose. - * - * See the GNU General Public License for more details. */ #include diff --git a/include/uapi/linux/llc.h b/include/uapi/linux/llc.h index cf8806b14d5f..2ffd81f9cc01 100644 --- a/include/uapi/linux/llc.h +++ b/include/uapi/linux/llc.h @@ -3,13 +3,6 @@ * IEEE 802.2 User Interface SAPs for Linux, data structures and indicators. * * Copyright (c) 2001 by Jay Schulist - * - * This program can be redistributed or modified under the terms of the - * GNU General Public License as published by the Free Software Foundation. - * This program is distributed without any warranty or implied warranty - * of merchantability or fitness for a particular purpose. - * - * See the GNU General Public License for more details. */ #ifndef _UAPI__LINUX_LLC_H #define _UAPI__LINUX_LLC_H diff --git a/net/llc/Makefile b/net/llc/Makefile index 5e0ef436daae..46b1cd905ffd 100644 --- a/net/llc/Makefile +++ b/net/llc/Makefile @@ -1,15 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 ########################################################################### # Makefile for the Linux 802.2 LLC (fully-functional) layer. # # Copyright (c) 1997 by Procom Technology,Inc. # 2001-2003 by Arnaldo Carvalho de Melo -# -# This program can be redistributed or modified under the terms of the -# GNU General Public License as published by the Free Software Foundation. -# This program is distributed without any warranty or implied warranty -# of merchantability or fitness for a particular purpose. -# -# See the GNU General Public License for more details. ########################################################################### obj-$(CONFIG_LLC) += llc.o diff --git a/net/llc/af_llc.c b/net/llc/af_llc.c index 35278c519a30..8ed1be1ecccc 100644 --- a/net/llc/af_llc.c +++ b/net/llc/af_llc.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* * af_llc.c - LLC User Interface SAPs * Description: @@ -12,13 +13,6 @@ * * Copyright (c) 2001 by Jay Schulist * 2002-2003 by Arnaldo Carvalho de Melo - * - * This program can be redistributed or modified under the terms of the - * GNU General Public License as published by the Free Software Foundation. - * This program is distributed without any warranty or implied warranty - * of merchantability or fitness for a particular purpose. - * - * See the GNU General Public License for more details. */ #include #include diff --git a/net/llc/llc_c_ac.c b/net/llc/llc_c_ac.c index ab86c720b3ec..724ecd741d4c 100644 --- a/net/llc/llc_c_ac.c +++ b/net/llc/llc_c_ac.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* * llc_c_ac.c - actions performed during connection state transition. * @@ -9,13 +10,6 @@ * * Copyright (c) 1997 by Procom Technology, Inc. * 2001-2003 by Arnaldo Carvalho de Melo - * - * This program can be redistributed or modified under the terms of the - * GNU General Public License as published by the Free Software Foundation. - * This program is distributed without any warranty or implied warranty - * of merchantability or fitness for a particular purpose. - * - * See the GNU General Public License for more details. */ #include #include diff --git a/net/llc/llc_c_ev.c b/net/llc/llc_c_ev.c index d6627a80cb45..beb2836c7db0 100644 --- a/net/llc/llc_c_ev.c +++ b/net/llc/llc_c_ev.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* * llc_c_ev.c - Connection component state transition event qualifiers * @@ -25,13 +26,6 @@ * * Copyright (c) 1997 by Procom Technology, Inc. * 2001-2003 by Arnaldo Carvalho de Melo - * - * This program can be redistributed or modified under the terms of the - * GNU General Public License as published by the Free Software Foundation. - * This program is distributed without any warranty or implied warranty - * of merchantability or fitness for a particular purpose. - * - * See the GNU General Public License for more details. */ #include #include diff --git a/net/llc/llc_c_st.c b/net/llc/llc_c_st.c index 1c267db304df..5fbd8d19c6c4 100644 --- a/net/llc/llc_c_st.c +++ b/net/llc/llc_c_st.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* * llc_c_st.c - This module contains state transition of connection component. * @@ -6,13 +7,6 @@ * * Copyright (c) 1997 by Procom Technology, Inc. * 2001-2003 by Arnaldo Carvalho de Melo - * - * This program can be redistributed or modified under the terms of the - * GNU General Public License as published by the Free Software Foundation. - * This program is distributed without any warranty or implied warranty - * of merchantability or fitness for a particular purpose. - * - * See the GNU General Public License for more details. */ #include #include diff --git a/net/llc/llc_conn.c b/net/llc/llc_conn.c index 5c0ac243b248..e8f427375c68 100644 --- a/net/llc/llc_conn.c +++ b/net/llc/llc_conn.c @@ -1,15 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 /* * llc_conn.c - Driver routines for connection component. * * Copyright (c) 1997 by Procom Technology, Inc. * 2001-2003 by Arnaldo Carvalho de Melo - * - * This program can be redistributed or modified under the terms of the - * GNU General Public License as published by the Free Software Foundation. - * This program is distributed without any warranty or implied warranty - * of merchantability or fitness for a particular purpose. - * - * See the GNU General Public License for more details. */ #include diff --git a/net/llc/llc_core.c b/net/llc/llc_core.c index d73f5414d8ce..5b0f1986bddc 100644 --- a/net/llc/llc_core.c +++ b/net/llc/llc_core.c @@ -1,15 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 /* * llc_core.c - Minimum needed routines for sap handling and module init/exit * * Copyright (c) 1997 by Procom Technology, Inc. * 2001-2003 by Arnaldo Carvalho de Melo - * - * This program can be redistributed or modified under the terms of the - * GNU General Public License as published by the Free Software Foundation. - * This program is distributed without any warranty or implied warranty - * of merchantability or fitness for a particular purpose. - * - * See the GNU General Public License for more details. */ #include diff --git a/net/llc/llc_if.c b/net/llc/llc_if.c index 58a5f419adc6..1514362e613d 100644 --- a/net/llc/llc_if.c +++ b/net/llc/llc_if.c @@ -1,15 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 /* * llc_if.c - Defines LLC interface to upper layer * * Copyright (c) 1997 by Procom Technology, Inc. * 2001-2003 by Arnaldo Carvalho de Melo - * - * This program can be redistributed or modified under the terms of the - * GNU General Public License as published by the Free Software Foundation. - * This program is distributed without any warranty or implied warranty - * of merchantability or fitness for a particular purpose. - * - * See the GNU General Public License for more details. */ #include #include diff --git a/net/llc/llc_input.c b/net/llc/llc_input.c index 61b0159b2fbe..8eb3d73c39d1 100644 --- a/net/llc/llc_input.c +++ b/net/llc/llc_input.c @@ -1,15 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 /* * llc_input.c - Minimal input path for LLC * * Copyright (c) 1997 by Procom Technology, Inc. * 2001-2003 by Arnaldo Carvalho de Melo - * - * This program can be redistributed or modified under the terms of the - * GNU General Public License as published by the Free Software Foundation. - * This program is distributed without any warranty or implied warranty - * of merchantability or fitness for a particular purpose. - * - * See the GNU General Public License for more details. */ #include #include diff --git a/net/llc/llc_pdu.c b/net/llc/llc_pdu.c index 63749dde542f..c1938fa24a3f 100644 --- a/net/llc/llc_pdu.c +++ b/net/llc/llc_pdu.c @@ -1,15 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 /* * llc_pdu.c - access to PDU internals * * Copyright (c) 1997 by Procom Technology, Inc. * 2001-2003 by Arnaldo Carvalho de Melo - * - * This program can be redistributed or modified under the terms of the - * GNU General Public License as published by the Free Software Foundation. - * This program is distributed without any warranty or implied warranty - * of merchantability or fitness for a particular purpose. - * - * See the GNU General Public License for more details. */ #include diff --git a/net/llc/llc_proc.c b/net/llc/llc_proc.c index aa81c67b24a1..4804a08c2490 100644 --- a/net/llc/llc_proc.c +++ b/net/llc/llc_proc.c @@ -1,15 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 /* * proc_llc.c - proc interface for LLC * * Copyright (c) 2001 by Jay Schulist * 2002-2003 by Arnaldo Carvalho de Melo - * - * This program can be redistributed or modified under the terms of the - * GNU General Public License as published by the Free Software Foundation. - * This program is distributed without any warranty or implied warranty - * of merchantability or fitness for a particular purpose. - * - * See the GNU General Public License for more details. */ #include diff --git a/net/llc/llc_s_ac.c b/net/llc/llc_s_ac.c index 7a0cae9a8111..98deee560373 100644 --- a/net/llc/llc_s_ac.c +++ b/net/llc/llc_s_ac.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* * llc_s_ac.c - actions performed during sap state transition. * @@ -9,13 +10,6 @@ * * Copyright (c) 1997 by Procom Technology, Inc. * 2001-2003 by Arnaldo Carvalho de Melo - * - * This program can be redistributed or modified under the terms of the - * GNU General Public License as published by the Free Software Foundation. - * This program is distributed without any warranty or implied warranty - * of merchantability or fitness for a particular purpose. - * - * See the GNU General Public License for more details. */ #include diff --git a/net/llc/llc_s_ev.c b/net/llc/llc_s_ev.c index a74d2a1d6581..cfbecba589e7 100644 --- a/net/llc/llc_s_ev.c +++ b/net/llc/llc_s_ev.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* * llc_s_ev.c - Defines SAP component events * @@ -6,13 +7,6 @@ * * Copyright (c) 1997 by Procom Technology, Inc. * 2001-2003 by Arnaldo Carvalho de Melo - * - * This program can be redistributed or modified under the terms of the - * GNU General Public License as published by the Free Software Foundation. - * This program is distributed without any warranty or implied warranty - * of merchantability or fitness for a particular purpose. - * - * See the GNU General Public License for more details. */ #include #include diff --git a/net/llc/llc_s_st.c b/net/llc/llc_s_st.c index acccc827c562..e14d4f520327 100644 --- a/net/llc/llc_s_st.c +++ b/net/llc/llc_s_st.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* * llc_s_st.c - Defines SAP component state machine transitions. * @@ -6,13 +7,6 @@ * * Copyright (c) 1997 by Procom Technology, Inc. * 2001-2003 by Arnaldo Carvalho de Melo - * - * This program can be redistributed or modified under the terms of the - * GNU General Public License as published by the Free Software Foundation. - * This program is distributed without any warranty or implied warranty - * of merchantability or fitness for a particular purpose. - * - * See the GNU General Public License for more details. */ #include #include diff --git a/net/llc/llc_sap.c b/net/llc/llc_sap.c index 6cd03c2ae7d5..1bd446a21092 100644 --- a/net/llc/llc_sap.c +++ b/net/llc/llc_sap.c @@ -1,15 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 /* * llc_sap.c - driver routines for SAP component. * * Copyright (c) 1997 by Procom Technology, Inc. * 2001-2003 by Arnaldo Carvalho de Melo - * - * This program can be redistributed or modified under the terms of the - * GNU General Public License as published by the Free Software Foundation. - * This program is distributed without any warranty or implied warranty - * of merchantability or fitness for a particular purpose. - * - * See the GNU General Public License for more details. */ #include diff --git a/net/llc/llc_station.c b/net/llc/llc_station.c index f50654292510..77fd0ac75263 100644 --- a/net/llc/llc_station.c +++ b/net/llc/llc_station.c @@ -1,15 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 /* * llc_station.c - station component of LLC * * Copyright (c) 1997 by Procom Technology, Inc. * 2001-2003 by Arnaldo Carvalho de Melo - * - * This program can be redistributed or modified under the terms of the - * GNU General Public License as published by the Free Software Foundation. - * This program is distributed without any warranty or implied warranty - * of merchantability or fitness for a particular purpose. - * - * See the GNU General Public License for more details. */ #include #include -- cgit v1.2.3 From 29fb4a26416d851333be909fc313db9147b7099c Mon Sep 17 00:00:00 2001 From: Tim Bird Date: Fri, 22 May 2026 18:23:54 -0600 Subject: llc: Add SPDX id lines to llc header files Add appropriate SPDX-License-Identifier lines to llc header (.h) files, and remove other license text from the files. Signed-off-by: Tim Bird Link: https://patch.msgid.link/20260523002354.28831-1-tim.bird@sony.com Signed-off-by: Jakub Kicinski --- include/net/llc_c_ac.h | 8 +------- include/net/llc_c_ev.h | 8 +------- include/net/llc_c_st.h | 8 +------- include/net/llc_conn.h | 8 +------- include/net/llc_if.h | 8 +------- include/net/llc_pdu.h | 8 +------- include/net/llc_s_ac.h | 8 +------- include/net/llc_s_ev.h | 8 +------- include/net/llc_s_st.h | 8 +------- include/net/llc_sap.h | 8 +------- 10 files changed, 10 insertions(+), 70 deletions(-) (limited to 'include') diff --git a/include/net/llc_c_ac.h b/include/net/llc_c_ac.h index 7620a9196922..af31a49bebb0 100644 --- a/include/net/llc_c_ac.h +++ b/include/net/llc_c_ac.h @@ -1,15 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ #ifndef LLC_C_AC_H #define LLC_C_AC_H /* * Copyright (c) 1997 by Procom Technology,Inc. * 2001 by Arnaldo Carvalho de Melo - * - * This program can be redistributed or modified under the terms of the - * GNU General Public License as published by the Free Software Foundation. - * This program is distributed without any warranty or implied warranty - * of merchantability or fitness for a particular purpose. - * - * See the GNU General Public License for more details. */ /* Connection component state transition actions */ /* diff --git a/include/net/llc_c_ev.h b/include/net/llc_c_ev.h index 241889955157..d45140d4289a 100644 --- a/include/net/llc_c_ev.h +++ b/include/net/llc_c_ev.h @@ -1,15 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ #ifndef LLC_C_EV_H #define LLC_C_EV_H /* * Copyright (c) 1997 by Procom Technology,Inc. * 2001 by Arnaldo Carvalho de Melo - * - * This program can be redistributed or modified under the terms of the - * GNU General Public License as published by the Free Software Foundation. - * This program is distributed without any warranty or implied warranty - * of merchantability or fitness for a particular purpose. - * - * See the GNU General Public License for more details. */ #include diff --git a/include/net/llc_c_st.h b/include/net/llc_c_st.h index a4bea0f33188..f52a4cc4880d 100644 --- a/include/net/llc_c_st.h +++ b/include/net/llc_c_st.h @@ -1,15 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ #ifndef LLC_C_ST_H #define LLC_C_ST_H /* * Copyright (c) 1997 by Procom Technology,Inc. * 2001 by Arnaldo Carvalho de Melo - * - * This program can be redistributed or modified under the terms of the - * GNU General Public License as published by the Free Software Foundation. - * This program is distributed without any warranty or implied warranty - * of merchantability or fitness for a particular purpose. - * - * See the GNU General Public License for more details. */ #include diff --git a/include/net/llc_conn.h b/include/net/llc_conn.h index 374411b3066c..e1a302696723 100644 --- a/include/net/llc_conn.h +++ b/include/net/llc_conn.h @@ -1,15 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ #ifndef LLC_CONN_H #define LLC_CONN_H /* * Copyright (c) 1997 by Procom Technology, Inc. * 2001, 2002 by Arnaldo Carvalho de Melo - * - * This program can be redistributed or modified under the terms of the - * GNU General Public License as published by the Free Software Foundation. - * This program is distributed without any warranty or implied warranty - * of merchantability or fitness for a particular purpose. - * - * See the GNU General Public License for more details. */ #include #include diff --git a/include/net/llc_if.h b/include/net/llc_if.h index c72570a21a4f..a333259613ab 100644 --- a/include/net/llc_if.h +++ b/include/net/llc_if.h @@ -1,15 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ #ifndef LLC_IF_H #define LLC_IF_H /* * Copyright (c) 1997 by Procom Technology,Inc. * 2001 by Arnaldo Carvalho de Melo - * - * This program can be redistributed or modified under the terms of the - * GNU General Public License as published by the Free Software Foundation. - * This program is distributed without any warranty or implied warranty - * of merchantability or fitness for a particular purpose. - * - * See the GNU General Public License for more details. */ /* Defines LLC interface to network layer */ /* Available primitives */ diff --git a/include/net/llc_pdu.h b/include/net/llc_pdu.h index 86681f29bda7..f165237a3723 100644 --- a/include/net/llc_pdu.h +++ b/include/net/llc_pdu.h @@ -1,15 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ #ifndef LLC_PDU_H #define LLC_PDU_H /* * Copyright (c) 1997 by Procom Technology,Inc. * 2001-2003 by Arnaldo Carvalho de Melo - * - * This program can be redistributed or modified under the terms of the - * GNU General Public License as published by the Free Software Foundation. - * This program is distributed without any warranty or implied warranty - * of merchantability or fitness for a particular purpose. - * - * See the GNU General Public License for more details. */ #include diff --git a/include/net/llc_s_ac.h b/include/net/llc_s_ac.h index f71790305bc9..076adc5217fd 100644 --- a/include/net/llc_s_ac.h +++ b/include/net/llc_s_ac.h @@ -1,15 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ #ifndef LLC_S_AC_H #define LLC_S_AC_H /* * Copyright (c) 1997 by Procom Technology,Inc. * 2001 by Arnaldo Carvalho de Melo - * - * This program can be redistributed or modified under the terms of the - * GNU General Public License as published by the Free Software Foundation. - * This program is distributed without any warranty or implied warranty - * of merchantability or fitness for a particular purpose. - * - * See the GNU General Public License for more details. */ struct llc_sap; diff --git a/include/net/llc_s_ev.h b/include/net/llc_s_ev.h index fb7df1d70af3..7b9c94093336 100644 --- a/include/net/llc_s_ev.h +++ b/include/net/llc_s_ev.h @@ -1,15 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ #ifndef LLC_S_EV_H #define LLC_S_EV_H /* * Copyright (c) 1997 by Procom Technology,Inc. * 2001 by Arnaldo Carvalho de Melo - * - * This program can be redistributed or modified under the terms of the - * GNU General Public License as published by the Free Software Foundation. - * This program is distributed without any warranty or implied warranty - * of merchantability or fitness for a particular purpose. - * - * See the GNU General Public License for more details. */ #include diff --git a/include/net/llc_s_st.h b/include/net/llc_s_st.h index fca49d483d20..85ce230fd946 100644 --- a/include/net/llc_s_st.h +++ b/include/net/llc_s_st.h @@ -1,15 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ #ifndef LLC_S_ST_H #define LLC_S_ST_H /* * Copyright (c) 1997 by Procom Technology,Inc. * 2001 by Arnaldo Carvalho de Melo - * - * This program can be redistributed or modified under the terms of the - * GNU General Public License as published by the Free Software Foundation. - * This program is distributed without any warranty or implied warranty - * of merchantability or fitness for a particular purpose. - * - * See the GNU General Public License for more details. */ #include diff --git a/include/net/llc_sap.h b/include/net/llc_sap.h index 1e4df9fd9fb2..b259f5993c46 100644 --- a/include/net/llc_sap.h +++ b/include/net/llc_sap.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0 */ #ifndef LLC_SAP_H #define LLC_SAP_H @@ -6,13 +7,6 @@ /* * Copyright (c) 1997 by Procom Technology,Inc. * 2001-2003 by Arnaldo Carvalho de Melo - * - * This program can be redistributed or modified under the terms of the - * GNU General Public License as published by the Free Software Foundation. - * This program is distributed without any warranty or implied warranty - * of merchantability or fitness for a particular purpose. - * - * See the GNU General Public License for more details. */ struct llc_sap; struct net_device; -- cgit v1.2.3 From 507541c2a8eeb76c02bd2511958f73a8cfa3e1bc Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Tue, 26 May 2026 14:55:28 +0000 Subject: ipv6: guard against possible NULL deref in __in6_dev_stats_get() dev_get_by_index_rcu() could return NULL if the original physical device is unregistered. Found by Sashiko. Fixes: e1ae5c2ea478 ("vrf: Increment Icmp6InMsgs on the original netdev") Signed-off-by: Eric Dumazet Cc: Stephen Suryaputra Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260526145529.3587126-2-edumazet@google.com Signed-off-by: Jakub Kicinski --- include/net/addrconf.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/include/net/addrconf.h b/include/net/addrconf.h index 9e96776945e5..539bbbe54b14 100644 --- a/include/net/addrconf.h +++ b/include/net/addrconf.h @@ -369,8 +369,11 @@ static inline struct inet6_dev *__in6_dev_get_rtnl_net(const struct net_device * static inline struct inet6_dev *__in6_dev_stats_get(const struct net_device *dev, const struct sk_buff *skb) { - if (netif_is_l3_master(dev)) + if (netif_is_l3_master(dev)) { dev = dev_get_by_index_rcu(dev_net(dev), inet6_iif(skb)); + if (!dev) + return NULL; + } return __in6_dev_get(dev); } -- cgit v1.2.3 From 31eedfbaa2b8312e89b0fc973c8044dbb7163677 Mon Sep 17 00:00:00 2001 From: Xuan Zhuo Date: Wed, 27 May 2026 20:09:36 +0800 Subject: net: remove SIOCSHWTSTAMP and SIOCGHWTSTAMP from ndo_eth_ioctl comment Since commit 4ee58e1e5680 ("net: promote SIOCSHWTSTAMP and SIOCGHWTSTAMP ioctls to dedicated handlers"), SIOCSHWTSTAMP and SIOCGHWTSTAMP are no longer dispatched through dev_eth_ioctl() / ndo_eth_ioctl(). They are now handled by their own dedicated functions dev_set_hwtstamp() and dev_get_hwtstamp() in the ioctl path. However, the comment describing ndo_eth_ioctl in netdevice.h still lists these two ioctls, which is misleading for driver developers who may incorrectly assume they need to handle hardware timestamping commands in their ndo_eth_ioctl implementation. Remove the stale references from the comment to accurately reflect that ndo_eth_ioctl only handles SIOCGMIIPHY, SIOCGMIIREG and SIOCSMIIREG. Signed-off-by: Xuan Zhuo Reviewed-by: Vadim Fedorenko Link: https://patch.msgid.link/20260527120936.24169-1-xuanzhuo@linux.alibaba.com Signed-off-by: Jakub Kicinski --- include/linux/netdevice.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index bf3dd9b2c1a7..7309467d7873 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -1149,8 +1149,8 @@ struct netdev_net_notifier { * SIOCBONDSLAVEINFOQUERY, and SIOCBONDINFOQUERY * * * int (*ndo_eth_ioctl)(struct net_device *dev, struct ifreq *ifr, int cmd); - * Called for ethernet specific ioctls: SIOCGMIIPHY, SIOCGMIIREG, - * SIOCSMIIREG, SIOCSHWTSTAMP and SIOCGHWTSTAMP. + * Called for ethernet specific ioctls: SIOCGMIIPHY, SIOCGMIIREG and + * SIOCSMIIREG. * * int (*ndo_set_config)(struct net_device *dev, struct ifmap *map); * Used to set network devices bus interface parameters. This interface -- cgit v1.2.3 From f331c7be97cec765c611f95df31ee2a99628a013 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Fri, 22 May 2026 00:30:23 -0500 Subject: crypto: hash - Remove support for cloning hash tfms Hash transformation cloning no longer has a user, and there's a good chance no new one will appear because the library API solves the problem in a much simpler and more efficient way. Remove support for it. Note that no tests need to be removed, as this feature had no tests. Reviewed-by: Ard Biesheuvel Signed-off-by: Eric Biggers Acked-by: Herbert Xu Link: https://patch.msgid.link/20260522053028.91165-2-ebiggers@kernel.org Signed-off-by: Jakub Kicinski --- crypto/ahash.c | 70 --------------------------------------------------- crypto/cmac.c | 16 ------------ crypto/cryptd.c | 16 ------------ crypto/hmac.c | 31 ----------------------- crypto/shash.c | 37 --------------------------- include/crypto/hash.h | 8 ------ 6 files changed, 178 deletions(-) (limited to 'include') diff --git a/crypto/ahash.c b/crypto/ahash.c index 7a730324c50e..85dcd120de3e 100644 --- a/crypto/ahash.c +++ b/crypto/ahash.c @@ -862,76 +862,6 @@ bool crypto_hash_alg_has_setkey(struct hash_alg_common *halg) } EXPORT_SYMBOL_GPL(crypto_hash_alg_has_setkey); -struct crypto_ahash *crypto_clone_ahash(struct crypto_ahash *hash) -{ - struct hash_alg_common *halg = crypto_hash_alg_common(hash); - struct crypto_tfm *tfm = crypto_ahash_tfm(hash); - struct crypto_ahash *fb = NULL; - struct crypto_ahash *nhash; - struct ahash_alg *alg; - int err; - - if (!crypto_hash_alg_has_setkey(halg)) { - tfm = crypto_tfm_get(tfm); - if (IS_ERR(tfm)) - return ERR_CAST(tfm); - - return hash; - } - - nhash = crypto_clone_tfm(&crypto_ahash_type, tfm); - - if (IS_ERR(nhash)) - return nhash; - - nhash->reqsize = hash->reqsize; - nhash->statesize = hash->statesize; - - if (likely(hash->using_shash)) { - struct crypto_shash **nctx = crypto_ahash_ctx(nhash); - struct crypto_shash *shash; - - shash = crypto_clone_shash(ahash_to_shash(hash)); - if (IS_ERR(shash)) { - err = PTR_ERR(shash); - goto out_free_nhash; - } - crypto_ahash_tfm(nhash)->exit = crypto_exit_ahash_using_shash; - nhash->using_shash = true; - *nctx = shash; - return nhash; - } - - if (crypto_ahash_need_fallback(hash)) { - fb = crypto_clone_ahash(crypto_ahash_fb(hash)); - err = PTR_ERR(fb); - if (IS_ERR(fb)) - goto out_free_nhash; - - crypto_ahash_tfm(nhash)->fb = crypto_ahash_tfm(fb); - } - - err = -ENOSYS; - alg = crypto_ahash_alg(hash); - if (!alg->clone_tfm) - goto out_free_fb; - - err = alg->clone_tfm(nhash, hash); - if (err) - goto out_free_fb; - - crypto_ahash_tfm(nhash)->exit = crypto_ahash_exit_tfm; - - return nhash; - -out_free_fb: - crypto_free_ahash(fb); -out_free_nhash: - crypto_free_ahash(nhash); - return ERR_PTR(err); -} -EXPORT_SYMBOL_GPL(crypto_clone_ahash); - static int ahash_default_export_core(struct ahash_request *req, void *out) { return -ENOSYS; diff --git a/crypto/cmac.c b/crypto/cmac.c index 1b03964abe00..83e58937f013 100644 --- a/crypto/cmac.c +++ b/crypto/cmac.c @@ -151,21 +151,6 @@ static int cmac_init_tfm(struct crypto_shash *tfm) return 0; } -static int cmac_clone_tfm(struct crypto_shash *tfm, struct crypto_shash *otfm) -{ - struct cmac_tfm_ctx *octx = crypto_shash_ctx(otfm); - struct cmac_tfm_ctx *ctx = crypto_shash_ctx(tfm); - struct crypto_cipher *cipher; - - cipher = crypto_clone_cipher(octx->child); - if (IS_ERR(cipher)) - return PTR_ERR(cipher); - - ctx->child = cipher; - - return 0; -} - static void cmac_exit_tfm(struct crypto_shash *tfm) { struct cmac_tfm_ctx *ctx = crypto_shash_ctx(tfm); @@ -222,7 +207,6 @@ static int cmac_create(struct crypto_template *tmpl, struct rtattr **tb) inst->alg.finup = crypto_cmac_digest_finup; inst->alg.setkey = crypto_cmac_digest_setkey; inst->alg.init_tfm = cmac_init_tfm; - inst->alg.clone_tfm = cmac_clone_tfm; inst->alg.exit_tfm = cmac_exit_tfm; inst->free = shash_free_singlespawn_instance; diff --git a/crypto/cryptd.c b/crypto/cryptd.c index aba9fe0f23b4..d8dbf07ab426 100644 --- a/crypto/cryptd.c +++ b/crypto/cryptd.c @@ -452,21 +452,6 @@ static int cryptd_hash_init_tfm(struct crypto_ahash *tfm) return 0; } -static int cryptd_hash_clone_tfm(struct crypto_ahash *ntfm, - struct crypto_ahash *tfm) -{ - struct cryptd_hash_ctx *nctx = crypto_ahash_ctx(ntfm); - struct cryptd_hash_ctx *ctx = crypto_ahash_ctx(tfm); - struct crypto_shash *hash; - - hash = crypto_clone_shash(ctx->child); - if (IS_ERR(hash)) - return PTR_ERR(hash); - - nctx->child = hash; - return 0; -} - static void cryptd_hash_exit_tfm(struct crypto_ahash *tfm) { struct cryptd_hash_ctx *ctx = crypto_ahash_ctx(tfm); @@ -700,7 +685,6 @@ static int cryptd_create_hash(struct crypto_template *tmpl, struct rtattr **tb, inst->alg.halg.base.cra_ctxsize = sizeof(struct cryptd_hash_ctx); inst->alg.init_tfm = cryptd_hash_init_tfm; - inst->alg.clone_tfm = cryptd_hash_clone_tfm; inst->alg.exit_tfm = cryptd_hash_exit_tfm; inst->alg.init = cryptd_hash_init_enqueue; diff --git a/crypto/hmac.c b/crypto/hmac.c index 148af460ae97..807e08b252c5 100644 --- a/crypto/hmac.c +++ b/crypto/hmac.c @@ -158,20 +158,6 @@ static int hmac_init_tfm(struct crypto_shash *parent) return 0; } -static int hmac_clone_tfm(struct crypto_shash *dst, struct crypto_shash *src) -{ - struct hmac_ctx *sctx = crypto_shash_ctx(src); - struct hmac_ctx *dctx = crypto_shash_ctx(dst); - struct crypto_shash *hash; - - hash = crypto_clone_shash(sctx->hash); - if (IS_ERR(hash)) - return PTR_ERR(hash); - - dctx->hash = hash; - return 0; -} - static void hmac_exit_tfm(struct crypto_shash *parent) { struct hmac_ctx *tctx = crypto_shash_ctx(parent); @@ -235,7 +221,6 @@ static int __hmac_create_shash(struct crypto_template *tmpl, inst->alg.import_core = hmac_import_core; inst->alg.setkey = hmac_setkey; inst->alg.init_tfm = hmac_init_tfm; - inst->alg.clone_tfm = hmac_clone_tfm; inst->alg.exit_tfm = hmac_exit_tfm; inst->free = shash_free_singlespawn_instance; @@ -423,21 +408,6 @@ static int hmac_init_ahash_tfm(struct crypto_ahash *parent) return 0; } -static int hmac_clone_ahash_tfm(struct crypto_ahash *dst, - struct crypto_ahash *src) -{ - struct ahash_hmac_ctx *sctx = crypto_ahash_ctx(src); - struct ahash_hmac_ctx *dctx = crypto_ahash_ctx(dst); - struct crypto_ahash *hash; - - hash = crypto_clone_ahash(sctx->hash); - if (IS_ERR(hash)) - return PTR_ERR(hash); - - dctx->hash = hash; - return 0; -} - static void hmac_exit_ahash_tfm(struct crypto_ahash *parent) { struct ahash_hmac_ctx *tctx = crypto_ahash_ctx(parent); @@ -503,7 +473,6 @@ static int hmac_create_ahash(struct crypto_template *tmpl, struct rtattr **tb, inst->alg.import_core = hmac_import_core_ahash; inst->alg.setkey = hmac_setkey_ahash; inst->alg.init_tfm = hmac_init_ahash_tfm; - inst->alg.clone_tfm = hmac_clone_ahash_tfm; inst->alg.exit_tfm = hmac_exit_ahash_tfm; inst->free = ahash_free_singlespawn_instance; diff --git a/crypto/shash.c b/crypto/shash.c index 2f07d0bd1f61..351cba3c1107 100644 --- a/crypto/shash.c +++ b/crypto/shash.c @@ -395,43 +395,6 @@ int crypto_has_shash(const char *alg_name, u32 type, u32 mask) } EXPORT_SYMBOL_GPL(crypto_has_shash); -struct crypto_shash *crypto_clone_shash(struct crypto_shash *hash) -{ - struct crypto_tfm *tfm = crypto_shash_tfm(hash); - struct shash_alg *alg = crypto_shash_alg(hash); - struct crypto_shash *nhash; - int err; - - if (!crypto_shash_alg_has_setkey(alg)) { - tfm = crypto_tfm_get(tfm); - if (IS_ERR(tfm)) - return ERR_CAST(tfm); - - return hash; - } - - if (!alg->clone_tfm && (alg->init_tfm || alg->base.cra_init)) - return ERR_PTR(-ENOSYS); - - nhash = crypto_clone_tfm(&crypto_shash_type, tfm); - if (IS_ERR(nhash)) - return nhash; - - if (alg->clone_tfm) { - err = alg->clone_tfm(nhash, hash); - if (err) { - crypto_free_shash(nhash); - return ERR_PTR(err); - } - } - - if (alg->exit_tfm) - crypto_shash_tfm(nhash)->exit = crypto_shash_exit_tfm; - - return nhash; -} -EXPORT_SYMBOL_GPL(crypto_clone_shash); - int hash_prepare_alg(struct hash_alg_common *alg) { struct crypto_alg *base = &alg->base; diff --git a/include/crypto/hash.h b/include/crypto/hash.h index 586700332c73..e474f8461ea1 100644 --- a/include/crypto/hash.h +++ b/include/crypto/hash.h @@ -148,7 +148,6 @@ struct ahash_request { * @exit_tfm: Deinitialize the cryptographic transformation object. * This is a counterpart to @init_tfm, used to remove * various changes set in @init_tfm. - * @clone_tfm: Copy transform into new object, may allocate memory. * @halg: see struct hash_alg_common */ struct ahash_alg { @@ -165,7 +164,6 @@ struct ahash_alg { unsigned int keylen); int (*init_tfm)(struct crypto_ahash *tfm); void (*exit_tfm)(struct crypto_ahash *tfm); - int (*clone_tfm)(struct crypto_ahash *dst, struct crypto_ahash *src); struct hash_alg_common halg; }; @@ -239,7 +237,6 @@ struct shash_desc { * @exit_tfm: Deinitialize the cryptographic transformation object. * This is a counterpart to @init_tfm, used to remove * various changes set in @init_tfm. - * @clone_tfm: Copy transform into new object, may allocate memory. * @descsize: Size of the operational state for the message digest. This state * size is the memory size that needs to be allocated for * shash_desc.__ctx @@ -263,7 +260,6 @@ struct shash_alg { unsigned int keylen); int (*init_tfm)(struct crypto_shash *tfm); void (*exit_tfm)(struct crypto_shash *tfm); - int (*clone_tfm)(struct crypto_shash *dst, struct crypto_shash *src); unsigned int descsize; @@ -322,8 +318,6 @@ static inline struct crypto_ahash *__crypto_ahash_cast(struct crypto_tfm *tfm) struct crypto_ahash *crypto_alloc_ahash(const char *alg_name, u32 type, u32 mask); -struct crypto_ahash *crypto_clone_ahash(struct crypto_ahash *tfm); - static inline struct crypto_tfm *crypto_ahash_tfm(struct crypto_ahash *tfm) { return &tfm->base; @@ -759,8 +753,6 @@ static inline void ahash_request_set_virt(struct ahash_request *req, struct crypto_shash *crypto_alloc_shash(const char *alg_name, u32 type, u32 mask); -struct crypto_shash *crypto_clone_shash(struct crypto_shash *tfm); - int crypto_has_shash(const char *alg_name, u32 type, u32 mask); static inline struct crypto_tfm *crypto_shash_tfm(struct crypto_shash *tfm) -- cgit v1.2.3 From cb2e6e86ceb518f792e9c7f404278cfea63c1154 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Fri, 22 May 2026 00:30:24 -0500 Subject: crypto: cipher - Remove crypto_clone_cipher() Since the only caller of crypto_clone_cipher() was cmac_clone_tfm() which has been removed, remove crypto_clone_cipher() as well. Note that no tests need to be removed, as this function had no tests. Reviewed-by: Ard Biesheuvel Signed-off-by: Eric Biggers Acked-by: Herbert Xu Link: https://patch.msgid.link/20260522053028.91165-3-ebiggers@kernel.org Signed-off-by: Jakub Kicinski --- crypto/cipher.c | 28 ---------------------------- include/crypto/internal/cipher.h | 2 -- 2 files changed, 30 deletions(-) (limited to 'include') diff --git a/crypto/cipher.c b/crypto/cipher.c index 1fe62bf79656..c9dab656a622 100644 --- a/crypto/cipher.c +++ b/crypto/cipher.c @@ -89,31 +89,3 @@ void crypto_cipher_decrypt_one(struct crypto_cipher *tfm, cipher_crypt_one(tfm, dst, src, false); } EXPORT_SYMBOL_NS_GPL(crypto_cipher_decrypt_one, "CRYPTO_INTERNAL"); - -struct crypto_cipher *crypto_clone_cipher(struct crypto_cipher *cipher) -{ - struct crypto_tfm *tfm = crypto_cipher_tfm(cipher); - struct crypto_alg *alg = tfm->__crt_alg; - struct crypto_cipher *ncipher; - struct crypto_tfm *ntfm; - - if (alg->cra_init) - return ERR_PTR(-ENOSYS); - - if (unlikely(!crypto_mod_get(alg))) - return ERR_PTR(-ESTALE); - - ntfm = __crypto_alloc_tfmgfp(alg, CRYPTO_ALG_TYPE_CIPHER, - CRYPTO_ALG_TYPE_MASK, GFP_ATOMIC); - if (IS_ERR(ntfm)) { - crypto_mod_put(alg); - return ERR_CAST(ntfm); - } - - ntfm->crt_flags = tfm->crt_flags; - - ncipher = __crypto_cipher_cast(ntfm); - - return ncipher; -} -EXPORT_SYMBOL_GPL(crypto_clone_cipher); diff --git a/include/crypto/internal/cipher.h b/include/crypto/internal/cipher.h index 5030f6d2df31..a9174ba90250 100644 --- a/include/crypto/internal/cipher.h +++ b/include/crypto/internal/cipher.h @@ -176,8 +176,6 @@ void crypto_cipher_encrypt_one(struct crypto_cipher *tfm, void crypto_cipher_decrypt_one(struct crypto_cipher *tfm, u8 *dst, const u8 *src); -struct crypto_cipher *crypto_clone_cipher(struct crypto_cipher *cipher); - struct crypto_cipher_spawn { struct crypto_spawn base; }; -- cgit v1.2.3 From 3065170bfc7f256f9d1339062dae331f731c6763 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Fri, 22 May 2026 00:30:26 -0500 Subject: crypto: api - Remove per-tfm refcount This reverts commit ae131f4970f0 ("crypto: api - Add crypto_tfm_get"). The refcount in struct crypto_tfm was added solely to support crypto_clone_tfm(). Before then it was a simple non-refcounted object. Since crypto_clone_tfm() has been removed, remove the refcount as well. Note that this eliminates an expensive atomic operation from every tfm freeing operation. So this revert doesn't just remove unused code, but it also fixes a performance regression. Signed-off-by: Eric Biggers Acked-by: Herbert Xu Link: https://patch.msgid.link/20260522053028.91165-5-ebiggers@kernel.org Signed-off-by: Jakub Kicinski --- crypto/api.c | 4 ---- crypto/internal.h | 6 ------ include/linux/crypto.h | 1 - 3 files changed, 11 deletions(-) (limited to 'include') diff --git a/crypto/api.c b/crypto/api.c index d019d1979857..be9ee104ffc2 100644 --- a/crypto/api.c +++ b/crypto/api.c @@ -418,7 +418,6 @@ struct crypto_tfm *__crypto_alloc_tfmgfp(struct crypto_alg *alg, u32 type, goto out_err; tfm->__crt_alg = alg; - refcount_set(&tfm->refcnt, 1); if (!tfm->exit && alg->cra_init && (err = alg->cra_init(tfm))) goto cra_init_failed; @@ -519,7 +518,6 @@ static void *crypto_alloc_tfmmem(struct crypto_alg *alg, tfm = (struct crypto_tfm *)(mem + tfmsize); tfm->__crt_alg = alg; tfm->node = node; - refcount_set(&tfm->refcnt, 1); return mem; } @@ -649,8 +647,6 @@ void crypto_destroy_tfm(void *mem, struct crypto_tfm *tfm) if (IS_ERR_OR_NULL(mem)) return; - if (!refcount_dec_and_test(&tfm->refcnt)) - return; alg = tfm->__crt_alg; if (!tfm->exit && alg->cra_exit) diff --git a/crypto/internal.h b/crypto/internal.h index 96f84abfac91..b6e437f463d4 100644 --- a/crypto/internal.h +++ b/crypto/internal.h @@ -10,7 +10,6 @@ #include #include -#include #include #include #include @@ -209,10 +208,5 @@ static inline int crypto_is_test_larval(struct crypto_larval *larval) return larval->alg.cra_driver_name[0]; } -static inline struct crypto_tfm *crypto_tfm_get(struct crypto_tfm *tfm) -{ - return refcount_inc_not_zero(&tfm->refcnt) ? tfm : ERR_PTR(-EOVERFLOW); -} - #endif /* _CRYPTO_INTERNAL_H */ diff --git a/include/linux/crypto.h b/include/linux/crypto.h index a2137e19be7d..b7c97f1c47c9 100644 --- a/include/linux/crypto.h +++ b/include/linux/crypto.h @@ -409,7 +409,6 @@ int crypto_has_alg(const char *name, u32 type, u32 mask); */ struct crypto_tfm { - refcount_t refcnt; u32 crt_flags; -- cgit v1.2.3 From 81a4d039537a89e7619aa94c5b6db051ae0b180c Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Tue, 26 May 2026 08:57:22 -0700 Subject: net: make page_pool_get_stats() void The kdoc for page_pool_get_stats() is missing a Returns: statement. Looking at this function, I have no idea what is the purpose of the bool it returns. My guess was that maybe the static inline stub returns false if CONFIG_PAGE_POOL_STATS=n but such static inline helper doesn't exist at all. All callers pass a pointer to a struct on the stack. Make this function void. Reviewed-by: Nicolai Buchwitz Reviewed-by: Tariq Toukan Link: https://patch.msgid.link/20260526155722.2790742-5-kuba@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/mellanox/mlx5/core/en_stats.c | 3 +-- include/net/page_pool/helpers.h | 2 +- net/core/page_pool.c | 7 +------ net/core/page_pool_user.c | 3 +-- 4 files changed, 4 insertions(+), 11 deletions(-) (limited to 'include') diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_stats.c b/drivers/net/ethernet/mellanox/mlx5/core/en_stats.c index 1a3ecf073913..7f33261ba655 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_stats.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_stats.c @@ -496,8 +496,7 @@ static void mlx5e_stats_update_stats_rq_page_pool(struct mlx5e_channel *c) struct page_pool *pool = c->rq.page_pool; struct page_pool_stats stats = { 0 }; - if (!page_pool_get_stats(pool, &stats)) - return; + page_pool_get_stats(pool, &stats); rq_stats->pp_alloc_fast = stats.alloc_stats.fast; rq_stats->pp_alloc_slow = stats.alloc_stats.slow; diff --git a/include/net/page_pool/helpers.h b/include/net/page_pool/helpers.h index 3247026e096a..e2730dd273b2 100644 --- a/include/net/page_pool/helpers.h +++ b/include/net/page_pool/helpers.h @@ -64,7 +64,7 @@ int page_pool_ethtool_stats_get_count(void); u8 *page_pool_ethtool_stats_get_strings(u8 *data); u64 *page_pool_ethtool_stats_get(u64 *data, const void *stats); -bool page_pool_get_stats(const struct page_pool *pool, +void page_pool_get_stats(const struct page_pool *pool, struct page_pool_stats *stats); #else static inline int page_pool_ethtool_stats_get_count(void) diff --git a/net/core/page_pool.c b/net/core/page_pool.c index 7798726f5a3e..21dc4a9c8714 100644 --- a/net/core/page_pool.c +++ b/net/core/page_pool.c @@ -85,14 +85,11 @@ static const char pp_stats[][ETH_GSTRING_LEN] = { * is passed to this API which is filled in. The caller can then report * those stats to the user (perhaps via ethtool, debugfs, etc.). */ -bool page_pool_get_stats(const struct page_pool *pool, +void page_pool_get_stats(const struct page_pool *pool, struct page_pool_stats *stats) { int cpu = 0; - if (!stats) - return false; - /* The caller is responsible to initialize stats. */ stats->alloc_stats.fast += pool->alloc_stats.fast; stats->alloc_stats.slow += pool->alloc_stats.slow; @@ -111,8 +108,6 @@ bool page_pool_get_stats(const struct page_pool *pool, stats->recycle_stats.ring_full += pcpu->ring_full; stats->recycle_stats.released_refcnt += pcpu->released_refcnt; } - - return true; } EXPORT_SYMBOL(page_pool_get_stats); diff --git a/net/core/page_pool_user.c b/net/core/page_pool_user.c index 1cdef13e6cea..ef4261c0e8ea 100644 --- a/net/core/page_pool_user.c +++ b/net/core/page_pool_user.c @@ -127,8 +127,7 @@ page_pool_nl_stats_fill(struct sk_buff *rsp, const struct page_pool *pool, struct nlattr *nest; void *hdr; - if (!page_pool_get_stats(pool, &stats)) - return 0; + page_pool_get_stats(pool, &stats); hdr = genlmsg_iput(rsp, info); if (!hdr) -- cgit v1.2.3 From 7745f1978a0f34a416c241e94eb812a4c8fcb890 Mon Sep 17 00:00:00 2001 From: Costa Shulyupin Date: Sun, 31 May 2026 16:48:36 +0300 Subject: net: Remove orphaned ax25_ptr references The AX.25 subsystem was removed in commit dd8d4bc28ad7 ("net: remove ax25 and amateur radio (hamradio) subsystem"), which removed the ax25_ptr field from struct net_device but left behind the kdoc comment and documentation. Signed-off-by: Costa Shulyupin Reviewed-by: Randy Dunlap Link: https://patch.msgid.link/20260531134837.4111349-1-costa.shul@redhat.com Signed-off-by: Jakub Kicinski --- Documentation/networking/net_cachelines/net_device.rst | 1 - include/linux/netdevice.h | 1 - 2 files changed, 2 deletions(-) (limited to 'include') diff --git a/Documentation/networking/net_cachelines/net_device.rst b/Documentation/networking/net_cachelines/net_device.rst index 7b3392553fd6..eb2e6851c6f6 100644 --- a/Documentation/networking/net_cachelines/net_device.rst +++ b/Documentation/networking/net_cachelines/net_device.rst @@ -91,7 +91,6 @@ struct vlan_info* vlan_info struct dsa_port* dsa_ptr struct tipc_bearer* tipc_ptr void* atalk_ptr -void* ax25_ptr struct wireless_dev* ieee80211_ptr struct wpan_dev* ieee802154_ptr struct mpls_dev* mpls_ptr diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 7309467d7873..74507c006490 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -1947,7 +1947,6 @@ enum netdev_reg_state { * @atalk_ptr: AppleTalk link * @ip_ptr: IPv4 specific data * @ip6_ptr: IPv6 specific data - * @ax25_ptr: AX.25 specific data * @ieee80211_ptr: IEEE 802.11 specific data, assign before registering * @ieee802154_ptr: IEEE 802.15.4 low-rate Wireless Personal Area Network * device struct -- cgit v1.2.3 From 1a15bf9708ba3bf80410065e113aa17cd6a18dcf Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 29 May 2026 08:40:27 +0200 Subject: wifi: cfg80211: remove 5/10 MHz channel support Remove WIPHY_FLAG_SUPPORTS_5_10_MHZ and 5/10 MHz channel width support. We contemplated this back in early 2023 and didn't do it yet, but nobody stepped up to maintain it. It's already _mostly_ dead code since it can really only be used for AP and maybe IBSS and monitor, but not on a client since there's no way to scan (and hasn't been in a very long time, if ever), so the only thing that ever could really happen with it was run syzbot and trip over assumptions in the code. Signed-off-by: Johannes Berg Reviewed-by: Lachlan Hodges Link: https://patch.msgid.link/20260529084502.080c5885f0b7.I77cc94485b523c3c006005b9233db13cd4e077b3@changeid Signed-off-by: Johannes Berg --- drivers/net/wireless/ath/ath5k/base.c | 2 -- drivers/net/wireless/ath/ath9k/init.c | 1 - drivers/net/wireless/virtual/mac80211_hwsim_main.c | 1 - include/net/cfg80211.h | 2 -- net/wireless/chan.c | 35 +++++----------------- net/wireless/core.c | 4 +-- net/wireless/nl80211.c | 14 ++------- net/wireless/util.c | 3 -- 8 files changed, 12 insertions(+), 50 deletions(-) (limited to 'include') diff --git a/drivers/net/wireless/ath/ath5k/base.c b/drivers/net/wireless/ath/ath5k/base.c index 6ca31d4ea437..610a3321b497 100644 --- a/drivers/net/wireless/ath/ath5k/base.c +++ b/drivers/net/wireless/ath/ath5k/base.c @@ -2600,8 +2600,6 @@ ath5k_init_ah(struct ath5k_hw *ah, const struct ath_bus_ops *bus_ops) /* SW support for IBSS_RSN is provided by mac80211 */ hw->wiphy->flags |= WIPHY_FLAG_IBSS_RSN; - hw->wiphy->flags |= WIPHY_FLAG_SUPPORTS_5_10_MHZ; - /* both antennas can be configured as RX or TX */ hw->wiphy->available_antennas_tx = 0x3; hw->wiphy->available_antennas_rx = 0x3; diff --git a/drivers/net/wireless/ath/ath9k/init.c b/drivers/net/wireless/ath/ath9k/init.c index e1a67e8ed09f..2f0c4ef86b7e 100644 --- a/drivers/net/wireless/ath/ath9k/init.c +++ b/drivers/net/wireless/ath/ath9k/init.c @@ -957,7 +957,6 @@ static void ath9k_set_hw_capab(struct ath_softc *sc, struct ieee80211_hw *hw) hw->wiphy->flags |= WIPHY_FLAG_IBSS_RSN; hw->wiphy->flags |= WIPHY_FLAG_SUPPORTS_TDLS; hw->wiphy->flags |= WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL; - hw->wiphy->flags |= WIPHY_FLAG_SUPPORTS_5_10_MHZ; hw->wiphy->flags |= WIPHY_FLAG_HAS_CHANNEL_SWITCH; hw->wiphy->flags |= WIPHY_FLAG_AP_UAPSD; diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c index 6ea082157307..4926122d6648 100644 --- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c +++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c @@ -5903,7 +5903,6 @@ static int mac80211_hwsim_new_radio(struct genl_info *info, hw->wiphy->flags |= WIPHY_FLAG_SUPPORTS_TDLS | WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL | WIPHY_FLAG_AP_UAPSD | - WIPHY_FLAG_SUPPORTS_5_10_MHZ | WIPHY_FLAG_HAS_CHANNEL_SWITCH; hw->wiphy->flags |= WIPHY_FLAG_IBSS_RSN; hw->wiphy->features |= NL80211_FEATURE_ACTIVE_MONITOR | diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 69dc9a978861..f91a71c7f4db 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -5676,7 +5676,6 @@ struct cfg80211_ops { * responds to probe-requests in hardware. * @WIPHY_FLAG_OFFCHAN_TX: Device supports direct off-channel TX. * @WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL: Device supports remain-on-channel call. - * @WIPHY_FLAG_SUPPORTS_5_10_MHZ: Device supports 5 MHz and 10 MHz channels. * @WIPHY_FLAG_HAS_CHANNEL_SWITCH: Device supports channel switch in * beaconing mode (AP, IBSS, Mesh, ...). * @WIPHY_FLAG_SUPPORTS_EXT_KEK_KCK: The device supports bigger kek and kck keys @@ -5716,7 +5715,6 @@ enum wiphy_flags { WIPHY_FLAG_AP_PROBE_RESP_OFFLOAD = BIT(19), WIPHY_FLAG_OFFCHAN_TX = BIT(20), WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL = BIT(21), - WIPHY_FLAG_SUPPORTS_5_10_MHZ = BIT(22), WIPHY_FLAG_HAS_CHANNEL_SWITCH = BIT(23), WIPHY_FLAG_NOTIFY_REGDOM_BY_DRIVER = BIT(24), WIPHY_FLAG_CHANNEL_CHANGE_ON_BEACON = BIT(25), diff --git a/net/wireless/chan.c b/net/wireless/chan.c index f0811efb5d0f..7f6af1790736 100644 --- a/net/wireless/chan.c +++ b/net/wireless/chan.c @@ -280,12 +280,6 @@ int nl80211_chan_width_to_mhz(enum nl80211_chan_width chan_width) case NL80211_CHAN_WIDTH_16: mhz = 16; break; - case NL80211_CHAN_WIDTH_5: - mhz = 5; - break; - case NL80211_CHAN_WIDTH_10: - mhz = 10; - break; case NL80211_CHAN_WIDTH_20: case NL80211_CHAN_WIDTH_20_NOHT: mhz = 20; @@ -347,8 +341,6 @@ cfg80211_chandef_valid_control_freq(const struct cfg80211_chan_def *chandef, u32 control_freq) { switch (chandef->width) { - case NL80211_CHAN_WIDTH_5: - case NL80211_CHAN_WIDTH_10: case NL80211_CHAN_WIDTH_20: case NL80211_CHAN_WIDTH_20_NOHT: case NL80211_CHAN_WIDTH_1: @@ -415,8 +407,6 @@ bool cfg80211_chandef_valid(const struct cfg80211_chan_def *chandef) return false; switch (chandef->width) { - case NL80211_CHAN_WIDTH_5: - case NL80211_CHAN_WIDTH_10: case NL80211_CHAN_WIDTH_20: case NL80211_CHAN_WIDTH_20_NOHT: if (ieee80211_chandef_to_khz(chandef) != @@ -803,18 +793,16 @@ _cfg80211_chandef_compatible(const struct cfg80211_chan_def *c1, return NULL; /* - * can't be compatible if one of them is 5/10 MHz or S1G + * can't be compatible if one of them is S1G * but they don't have the same width. */ -#define NARROW_OR_S1G(width) ((width) == NL80211_CHAN_WIDTH_5 || \ - (width) == NL80211_CHAN_WIDTH_10 || \ - (width) == NL80211_CHAN_WIDTH_1 || \ - (width) == NL80211_CHAN_WIDTH_2 || \ - (width) == NL80211_CHAN_WIDTH_4 || \ - (width) == NL80211_CHAN_WIDTH_8 || \ - (width) == NL80211_CHAN_WIDTH_16) - - if (NARROW_OR_S1G(c1->width) || NARROW_OR_S1G(c2->width)) +#define IS_S1G(width) ((width) == NL80211_CHAN_WIDTH_1 || \ + (width) == NL80211_CHAN_WIDTH_2 || \ + (width) == NL80211_CHAN_WIDTH_4 || \ + (width) == NL80211_CHAN_WIDTH_8 || \ + (width) == NL80211_CHAN_WIDTH_16) + + if (IS_S1G(c1->width) || IS_S1G(c2->width)) return NULL; /* @@ -1506,13 +1494,6 @@ bool _cfg80211_chandef_usable(struct wiphy *wiphy, control_freq = chandef->chan->center_freq; switch (chandef->width) { - case NL80211_CHAN_WIDTH_5: - width = 5; - break; - case NL80211_CHAN_WIDTH_10: - prohibited_flags |= IEEE80211_CHAN_NO_10MHZ; - width = 10; - break; case NL80211_CHAN_WIDTH_20: if (!ht_cap->ht_supported && chandef->chan->band != NL80211_BAND_6GHZ) diff --git a/net/wireless/core.c b/net/wireless/core.c index 62ab5e4639be..3dcf63b04c41 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -917,9 +917,7 @@ int wiphy_register(struct wiphy *wiphy) BIT(NL80211_CHAN_WIDTH_80) | BIT(NL80211_CHAN_WIDTH_80P80) | BIT(NL80211_CHAN_WIDTH_160) | - BIT(NL80211_CHAN_WIDTH_320) | - BIT(NL80211_CHAN_WIDTH_5) | - BIT(NL80211_CHAN_WIDTH_10)))) + BIT(NL80211_CHAN_WIDTH_320)))) return -EINVAL; } diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index cdb5e9b77143..19244708f5c3 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -3450,11 +3450,6 @@ static int nl80211_send_wiphy(struct cfg80211_registered_device *rdev, if (nl80211_send_coalesce(msg, rdev)) goto nla_put_failure; - if ((rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_5_10_MHZ) && - (nla_put_flag(msg, NL80211_ATTR_SUPPORT_5_MHZ) || - nla_put_flag(msg, NL80211_ATTR_SUPPORT_10_MHZ))) - goto nla_put_failure; - if (rdev->wiphy.max_ap_assoc_sta && nla_put_u32(msg, NL80211_ATTR_MAX_AP_ASSOC_STA, rdev->wiphy.max_ap_assoc_sta)) @@ -4085,10 +4080,9 @@ static int _nl80211_parse_chandef(struct cfg80211_registered_device *rdev, return -EINVAL; } - if ((chandef->width == NL80211_CHAN_WIDTH_5 || - chandef->width == NL80211_CHAN_WIDTH_10) && - !(rdev->wiphy.flags & WIPHY_FLAG_SUPPORTS_5_10_MHZ)) { - NL_SET_ERR_MSG(extack, "5/10 MHz not supported"); + if (chandef->width == NL80211_CHAN_WIDTH_5 || + chandef->width == NL80211_CHAN_WIDTH_10) { + NL_SET_ERR_MSG(extack, "5/10 MHz not supported any more"); return -EINVAL; } @@ -13410,8 +13404,6 @@ static int nl80211_join_ibss(struct sk_buff *skb, struct genl_info *info) return -EINVAL; switch (ibss.chandef.width) { - case NL80211_CHAN_WIDTH_5: - case NL80211_CHAN_WIDTH_10: case NL80211_CHAN_WIDTH_20_NOHT: break; case NL80211_CHAN_WIDTH_20: diff --git a/net/wireless/util.c b/net/wireless/util.c index 8dd7545b9097..24527bf321b2 100644 --- a/net/wireless/util.c +++ b/net/wireless/util.c @@ -2304,9 +2304,6 @@ bool ieee80211_chandef_to_operating_class(struct cfg80211_chan_def *chandef, case NL80211_CHAN_WIDTH_80P80: vht_opclass = 130; break; - case NL80211_CHAN_WIDTH_10: - case NL80211_CHAN_WIDTH_5: - return false; /* unsupported for now */ default: vht_opclass = 0; break; -- cgit v1.2.3 From 4ac20bd40b7db8568aea5ba1d390241842e41ed8 Mon Sep 17 00:00:00 2001 From: Masashi Honma Date: Sat, 30 May 2026 08:09:43 +0900 Subject: wifi: mac80211: Use struct instead of macro for PREQ frame The existing PREQ_IE_* macros access HWMP PREQ frame fields via hardcoded byte offsets. When the AE (Address Extension) flag is set, an additional 6 bytes appear mid-frame, and the macros handle this with conditional arithmetic (e.g., AE_F_SET(x) ? x + N+6 : x + N). This approach obscures the frame layout and is prone to miscalculation. Introduce typed packed C structs to represent the PREQ frame layout: - ieee80211_mesh_hwmp_preq_top: fixed fields before the optional AE address - ieee80211_mesh_hwmp_preq_bottom: fields after the optional AE address - ieee80211_mesh_hwmp_preq_target: per-target fields Add ieee80211_mesh_hwmp_preq_get_bottom() to locate the bottom struct correctly based on whether the AE flag is set. This preparatory refactoring is needed to fix a 2-byte overread of target_addr in hwmp_preq_frame_process() when AE is enabled, which is addressed in a subsequent patch. Signed-off-by: Masashi Honma Link: https://patch.msgid.link/20260529230952.124754-1-masashi.honma@gmail.com Signed-off-by: Johannes Berg --- include/linux/ieee80211-mesh.h | 42 ++++++++++++++++++++++++++ net/mac80211/mesh_hwmp.c | 67 ++++++++++++++++++++---------------------- 2 files changed, 74 insertions(+), 35 deletions(-) (limited to 'include') diff --git a/include/linux/ieee80211-mesh.h b/include/linux/ieee80211-mesh.h index 4b829bcb38b6..bf4a544aed00 100644 --- a/include/linux/ieee80211-mesh.h +++ b/include/linux/ieee80211-mesh.h @@ -28,12 +28,40 @@ struct ieee80211s_hdr { u8 eaddr2[ETH_ALEN]; } __packed __aligned(2); +struct ieee80211_mesh_hwmp_preq_target { + u8 flags; + u8 addr[ETH_ALEN]; + __le32 sn; +} __packed; + +struct ieee80211_mesh_hwmp_preq_top { + u8 flags; + u8 hopcount; + u8 ttl; + __le32 preq_id; + u8 orig_addr[ETH_ALEN]; + __le32 orig_sn; + + /* optional AE, lifetime, metric, target */ + u8 variable[]; +} __packed; + +struct ieee80211_mesh_hwmp_preq_bottom { + __le32 lifetime; + __le32 metric; + u8 target_count; + struct ieee80211_mesh_hwmp_preq_target targets[]; +} __packed; + /* Mesh flags */ #define MESH_FLAGS_AE_A4 0x1 #define MESH_FLAGS_AE_A5_A6 0x2 #define MESH_FLAGS_AE 0x3 #define MESH_FLAGS_PS_DEEP 0x4 +/* HWMP IE processing macros */ +#define AE_F (1<<6) + /** * enum ieee80211_preq_flags - mesh PREQ element flags * @@ -227,4 +255,18 @@ enum ieee80211_root_mode_identifier { IEEE80211_PROACTIVE_RANN = 4, }; +static inline bool ieee80211_mesh_preq_prep_ae_enabled(const u8 *ie) +{ + return ie[0] & AE_F; +} + +static inline struct ieee80211_mesh_hwmp_preq_bottom * +ieee80211_mesh_hwmp_preq_get_bottom(const u8 *ie) +{ + struct ieee80211_mesh_hwmp_preq_top *top = (void *)ie; + + return (void *)&top->variable[ + ieee80211_mesh_preq_prep_ae_enabled(ie) ? ETH_ALEN : 0]; +} + #endif /* LINUX_IEEE80211_MESH_H */ diff --git a/net/mac80211/mesh_hwmp.c b/net/mac80211/mesh_hwmp.c index 9d89ebcce1c1..1a6a22b185d9 100644 --- a/net/mac80211/mesh_hwmp.c +++ b/net/mac80211/mesh_hwmp.c @@ -35,24 +35,11 @@ static inline u16 u16_field_get(const u8 *preq_elem, int offset, bool ae) } /* HWMP IE processing macros */ -#define AE_F (1<<6) #define AE_F_SET(x) (*x & AE_F) -#define PREQ_IE_FLAGS(x) (*(x)) -#define PREQ_IE_HOPCOUNT(x) (*(x + 1)) -#define PREQ_IE_TTL(x) (*(x + 2)) -#define PREQ_IE_PREQ_ID(x) u32_field_get(x, 3, 0) -#define PREQ_IE_ORIG_ADDR(x) (x + 7) -#define PREQ_IE_ORIG_SN(x) u32_field_get(x, 13, 0) -#define PREQ_IE_LIFETIME(x) u32_field_get(x, 17, AE_F_SET(x)) -#define PREQ_IE_METRIC(x) u32_field_get(x, 21, AE_F_SET(x)) -#define PREQ_IE_TARGET_F(x) (*(AE_F_SET(x) ? x + 32 : x + 26)) -#define PREQ_IE_TARGET_ADDR(x) (AE_F_SET(x) ? x + 33 : x + 27) -#define PREQ_IE_TARGET_SN(x) u32_field_get(x, 33, AE_F_SET(x)) - - -#define PREP_IE_FLAGS(x) PREQ_IE_FLAGS(x) -#define PREP_IE_HOPCOUNT(x) PREQ_IE_HOPCOUNT(x) -#define PREP_IE_TTL(x) PREQ_IE_TTL(x) + +#define PREP_IE_FLAGS(x) (*(x)) +#define PREP_IE_HOPCOUNT(x) (*(x + 1)) +#define PREP_IE_TTL(x) (*(x + 2)) #define PREP_IE_ORIG_ADDR(x) (AE_F_SET(x) ? x + 27 : x + 21) #define PREP_IE_ORIG_SN(x) u32_field_get(x, 27, AE_F_SET(x)) #define PREP_IE_LIFETIME(x) u32_field_get(x, 13, AE_F_SET(x)) @@ -415,11 +402,16 @@ static u32 hwmp_route_info_get(struct ieee80211_sub_if_data *sdata, switch (action) { case MPATH_PREQ: - orig_addr = PREQ_IE_ORIG_ADDR(hwmp_ie); - orig_sn = PREQ_IE_ORIG_SN(hwmp_ie); - orig_lifetime = PREQ_IE_LIFETIME(hwmp_ie); - orig_metric = PREQ_IE_METRIC(hwmp_ie); - hopcount = PREQ_IE_HOPCOUNT(hwmp_ie) + 1; + struct ieee80211_mesh_hwmp_preq_top *preq_elem_top = + (void *)hwmp_ie; + struct ieee80211_mesh_hwmp_preq_bottom *preq_elem_bottom = + ieee80211_mesh_hwmp_preq_get_bottom(hwmp_ie); + + orig_addr = preq_elem_top->orig_addr; + orig_sn = le32_to_cpu(preq_elem_top->orig_sn); + orig_lifetime = le32_to_cpu(preq_elem_bottom->lifetime); + orig_metric = le32_to_cpu(preq_elem_bottom->metric); + hopcount = preq_elem_top->hopcount + 1; break; case MPATH_PREP: /* Originator here refers to the MP that was the target in the @@ -579,6 +571,11 @@ static void hwmp_preq_frame_process(struct ieee80211_sub_if_data *sdata, const u8 *preq_elem, u32 orig_metric) { struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh; + struct ieee80211_mesh_hwmp_preq_top *preq_elem_top = (void *)preq_elem; + struct ieee80211_mesh_hwmp_preq_bottom *preq_elem_bottom = + ieee80211_mesh_hwmp_preq_get_bottom(preq_elem); + struct ieee80211_mesh_hwmp_preq_target *target = + preq_elem_bottom->targets; struct mesh_path *mpath = NULL; const u8 *target_addr, *orig_addr; const u8 *da; @@ -589,13 +586,13 @@ static void hwmp_preq_frame_process(struct ieee80211_sub_if_data *sdata, bool root_is_gate; /* Update target SN, if present */ - target_addr = PREQ_IE_TARGET_ADDR(preq_elem); - orig_addr = PREQ_IE_ORIG_ADDR(preq_elem); - target_sn = PREQ_IE_TARGET_SN(preq_elem); - orig_sn = PREQ_IE_ORIG_SN(preq_elem); - target_flags = PREQ_IE_TARGET_F(preq_elem); + target_addr = target[0].addr; + orig_addr = preq_elem_top->orig_addr; + target_sn = le32_to_cpu(target[0].sn); + orig_sn = le32_to_cpu(preq_elem_top->orig_sn); + target_flags = target[0].flags; /* Proactive PREQ gate announcements */ - flags = PREQ_IE_FLAGS(preq_elem); + flags = preq_elem_top->flags; root_is_gate = !!(flags & RANN_FLAG_IS_GATE); mhwmp_dbg(sdata, "received PREQ from %pM\n", orig_addr); @@ -655,7 +652,7 @@ static void hwmp_preq_frame_process(struct ieee80211_sub_if_data *sdata, } if (reply) { - lifetime = PREQ_IE_LIFETIME(preq_elem); + lifetime = le32_to_cpu(preq_elem_bottom->lifetime); ttl = ifmsh->mshcfg.element_ttl; if (ttl != 0) { mhwmp_dbg(sdata, "replying to the PREQ\n"); @@ -673,22 +670,22 @@ static void hwmp_preq_frame_process(struct ieee80211_sub_if_data *sdata, u32 preq_id; u8 hopcount; - ttl = PREQ_IE_TTL(preq_elem); - lifetime = PREQ_IE_LIFETIME(preq_elem); + ttl = preq_elem_top->ttl; + lifetime = le32_to_cpu(preq_elem_bottom->lifetime); if (ttl <= 1) { ifmsh->mshstats.dropped_frames_ttl++; return; } mhwmp_dbg(sdata, "forwarding the PREQ from %pM\n", orig_addr); --ttl; - preq_id = PREQ_IE_PREQ_ID(preq_elem); - hopcount = PREQ_IE_HOPCOUNT(preq_elem) + 1; + preq_id = le32_to_cpu(preq_elem_top->preq_id); + hopcount = preq_elem_top->hopcount + 1; da = (mpath && mpath->is_root) ? mpath->rann_snd_addr : broadcast_addr; if (flags & IEEE80211_PREQ_PROACTIVE_PREP_FLAG) { - target_addr = PREQ_IE_TARGET_ADDR(preq_elem); - target_sn = PREQ_IE_TARGET_SN(preq_elem); + target_addr = target[0].addr; + target_sn = le32_to_cpu(target[0].sn); } mesh_path_sel_frame_tx(MPATH_PREQ, flags, orig_addr, -- cgit v1.2.3 From a91c65cb99d1e03c8d8f0244258cbdd2d60faa86 Mon Sep 17 00:00:00 2001 From: Masashi Honma Date: Sat, 30 May 2026 08:09:44 +0900 Subject: wifi: mac80211: Use struct instead of macro for PREP frame The existing PREP_IE_* macros access HWMP PREP frame fields via hardcoded byte offsets. When the AE (Address Extension) flag is set, an additional 6 bytes appear mid-frame, making the offset arithmetic error-prone. Introduce typed packed C structs to represent the PREP frame layout: - ieee80211_mesh_hwmp_prep_top: fixed fields before the optional AE address - ieee80211_mesh_hwmp_prep_bottom: fields after the optional AE address Add ieee80211_mesh_hwmp_prep_get_bottom() to locate the bottom struct correctly based on whether the AE flag is set. This preparatory refactoring is needed to fix a 2-byte overread of orig_addr in hwmp_prep_frame_process() when AE is enabled, which is addressed in a subsequent patch. Signed-off-by: Masashi Honma Link: https://patch.msgid.link/20260529230952.124754-2-masashi.honma@gmail.com Signed-off-by: Johannes Berg --- include/linux/ieee80211-mesh.h | 27 +++++++++++++++++++++++++ net/mac80211/mesh_hwmp.c | 46 ++++++++++++++++++++---------------------- 2 files changed, 49 insertions(+), 24 deletions(-) (limited to 'include') diff --git a/include/linux/ieee80211-mesh.h b/include/linux/ieee80211-mesh.h index bf4a544aed00..4ce4e47d6d01 100644 --- a/include/linux/ieee80211-mesh.h +++ b/include/linux/ieee80211-mesh.h @@ -53,6 +53,24 @@ struct ieee80211_mesh_hwmp_preq_bottom { struct ieee80211_mesh_hwmp_preq_target targets[]; } __packed; +struct ieee80211_mesh_hwmp_prep_top { + u8 flags; + u8 hopcount; + u8 ttl; + u8 target_addr[ETH_ALEN]; + __le32 target_sn; + + /* optional Target External Address */ + u8 variable[]; +} __packed; + +struct ieee80211_mesh_hwmp_prep_bottom { + __le32 lifetime; + __le32 metric; + u8 orig_addr[ETH_ALEN]; + __le32 orig_sn; +} __packed; + /* Mesh flags */ #define MESH_FLAGS_AE_A4 0x1 #define MESH_FLAGS_AE_A5_A6 0x2 @@ -269,4 +287,13 @@ ieee80211_mesh_hwmp_preq_get_bottom(const u8 *ie) ieee80211_mesh_preq_prep_ae_enabled(ie) ? ETH_ALEN : 0]; } +static inline struct ieee80211_mesh_hwmp_prep_bottom * +ieee80211_mesh_hwmp_prep_get_bottom(const u8 *ie) +{ + struct ieee80211_mesh_hwmp_prep_top *top = (void *)ie; + + return (void *)&top->variable[ + ieee80211_mesh_preq_prep_ae_enabled(ie) ? ETH_ALEN : 0]; +} + #endif /* LINUX_IEEE80211_MESH_H */ diff --git a/net/mac80211/mesh_hwmp.c b/net/mac80211/mesh_hwmp.c index 1a6a22b185d9..39b782370df0 100644 --- a/net/mac80211/mesh_hwmp.c +++ b/net/mac80211/mesh_hwmp.c @@ -37,16 +37,6 @@ static inline u16 u16_field_get(const u8 *preq_elem, int offset, bool ae) /* HWMP IE processing macros */ #define AE_F_SET(x) (*x & AE_F) -#define PREP_IE_FLAGS(x) (*(x)) -#define PREP_IE_HOPCOUNT(x) (*(x + 1)) -#define PREP_IE_TTL(x) (*(x + 2)) -#define PREP_IE_ORIG_ADDR(x) (AE_F_SET(x) ? x + 27 : x + 21) -#define PREP_IE_ORIG_SN(x) u32_field_get(x, 27, AE_F_SET(x)) -#define PREP_IE_LIFETIME(x) u32_field_get(x, 13, AE_F_SET(x)) -#define PREP_IE_METRIC(x) u32_field_get(x, 17, AE_F_SET(x)) -#define PREP_IE_TARGET_ADDR(x) (x + 3) -#define PREP_IE_TARGET_SN(x) u32_field_get(x, 9, 0) - #define PERR_IE_TTL(x) (*(x)) #define PERR_IE_TARGET_FLAGS(x) (*(x + 2)) #define PERR_IE_TARGET_ADDR(x) (x + 3) @@ -419,11 +409,16 @@ static u32 hwmp_route_info_get(struct ieee80211_sub_if_data *sdata, * so that we can easily use a single function to gather path * information from both PREQ and PREP frames. */ - orig_addr = PREP_IE_TARGET_ADDR(hwmp_ie); - orig_sn = PREP_IE_TARGET_SN(hwmp_ie); - orig_lifetime = PREP_IE_LIFETIME(hwmp_ie); - orig_metric = PREP_IE_METRIC(hwmp_ie); - hopcount = PREP_IE_HOPCOUNT(hwmp_ie) + 1; + struct ieee80211_mesh_hwmp_prep_top *prep_elem_top = + (void *)hwmp_ie; + struct ieee80211_mesh_hwmp_prep_bottom *prep_elem_bottom = + ieee80211_mesh_hwmp_prep_get_bottom(hwmp_ie); + + orig_addr = prep_elem_top->target_addr; + orig_sn = le32_to_cpu(prep_elem_top->target_sn); + orig_lifetime = le32_to_cpu(prep_elem_bottom->lifetime); + orig_metric = le32_to_cpu(prep_elem_bottom->metric); + hopcount = prep_elem_top->hopcount + 1; break; default: rcu_read_unlock(); @@ -714,6 +709,9 @@ static void hwmp_prep_frame_process(struct ieee80211_sub_if_data *sdata, const u8 *prep_elem, u32 metric) { struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh; + struct ieee80211_mesh_hwmp_prep_top *prep_elem_top = (void *)prep_elem; + struct ieee80211_mesh_hwmp_prep_bottom *prep_elem_bottom = + ieee80211_mesh_hwmp_prep_get_bottom(prep_elem); struct mesh_path *mpath; const u8 *target_addr, *orig_addr; u8 ttl, hopcount, flags; @@ -721,9 +719,9 @@ static void hwmp_prep_frame_process(struct ieee80211_sub_if_data *sdata, u32 target_sn, orig_sn, lifetime; mhwmp_dbg(sdata, "received PREP from %pM\n", - PREP_IE_TARGET_ADDR(prep_elem)); + prep_elem_top->target_addr); - orig_addr = PREP_IE_ORIG_ADDR(prep_elem); + orig_addr = prep_elem_bottom->orig_addr; if (ether_addr_equal(orig_addr, sdata->vif.addr)) /* destination, no forwarding required */ return; @@ -731,7 +729,7 @@ static void hwmp_prep_frame_process(struct ieee80211_sub_if_data *sdata, if (!ifmsh->mshcfg.dot11MeshForwarding) return; - ttl = PREP_IE_TTL(prep_elem); + ttl = prep_elem_top->ttl; if (ttl <= 1) { sdata->u.mesh.mshstats.dropped_frames_ttl++; return; @@ -750,12 +748,12 @@ static void hwmp_prep_frame_process(struct ieee80211_sub_if_data *sdata, memcpy(next_hop, next_hop_deref_protected(mpath)->sta.addr, ETH_ALEN); spin_unlock_bh(&mpath->state_lock); --ttl; - flags = PREP_IE_FLAGS(prep_elem); - lifetime = PREP_IE_LIFETIME(prep_elem); - hopcount = PREP_IE_HOPCOUNT(prep_elem) + 1; - target_addr = PREP_IE_TARGET_ADDR(prep_elem); - target_sn = PREP_IE_TARGET_SN(prep_elem); - orig_sn = PREP_IE_ORIG_SN(prep_elem); + flags = prep_elem_top->flags; + lifetime = le32_to_cpu(prep_elem_bottom->lifetime); + hopcount = prep_elem_top->hopcount + 1; + target_addr = prep_elem_top->target_addr; + target_sn = le32_to_cpu(prep_elem_top->target_sn); + orig_sn = le32_to_cpu(prep_elem_bottom->orig_sn); mesh_path_sel_frame_tx(MPATH_PREP, flags, orig_addr, orig_sn, 0, target_addr, target_sn, next_hop, hopcount, -- cgit v1.2.3 From 68511e16320b19387a8593c578a7ebe307c05166 Mon Sep 17 00:00:00 2001 From: Masashi Honma Date: Sat, 30 May 2026 08:09:45 +0900 Subject: wifi: mac80211: Use struct instead of macro for PERR frame The existing PERR_IE_* macros access HWMP PERR frame fields via hardcoded byte offsets. Each PERR destination entry contains an optional 6-byte AE (Address Extension) address followed by a reason code, making offset-based access error-prone. Introduce typed packed C structs to represent the PERR frame layout: - ieee80211_mesh_hwmp_perr: top-level frame containing TTL and destination count - ieee80211_mesh_hwmp_perr_dst: per-destination entry with optional AE address and variable-position reason code Add ieee80211_mesh_hwmp_perr_get_rcode() to locate the reason code in each destination entry depending on whether the AE flag is set. This refactoring makes the PERR processing code consistent with the struct-based approach adopted for PREQ and PREP in preceding patches. Signed-off-by: Masashi Honma Link: https://patch.msgid.link/20260529230952.124754-3-masashi.honma@gmail.com Signed-off-by: Johannes Berg --- include/linux/ieee80211-mesh.h | 62 ++++++++++++++++++++++++++++++++++++++++++ net/mac80211/mesh_hwmp.c | 31 ++++----------------- 2 files changed, 67 insertions(+), 26 deletions(-) (limited to 'include') diff --git a/include/linux/ieee80211-mesh.h b/include/linux/ieee80211-mesh.h index 4ce4e47d6d01..f709263c310b 100644 --- a/include/linux/ieee80211-mesh.h +++ b/include/linux/ieee80211-mesh.h @@ -71,6 +71,21 @@ struct ieee80211_mesh_hwmp_prep_bottom { __le32 orig_sn; } __packed; +struct ieee80211_mesh_hwmp_perr_dst { + u8 flags; + u8 addr[ETH_ALEN]; + __le32 sn; + /* optional Destination External Address */ + u8 variable[]; +} __packed; + +struct ieee80211_mesh_hwmp_perr { + u8 ttl; + u8 number_of_dst; + /* Destinations */ + u8 variable[]; +} __packed; + /* Mesh flags */ #define MESH_FLAGS_AE_A4 0x1 #define MESH_FLAGS_AE_A5_A6 0x2 @@ -296,4 +311,51 @@ ieee80211_mesh_hwmp_prep_get_bottom(const u8 *ie) ieee80211_mesh_preq_prep_ae_enabled(ie) ? ETH_ALEN : 0]; } +static inline struct ieee80211_mesh_hwmp_perr_dst * +ieee80211_mesh_hwmp_perr_get_dst(const u8 *ie, u8 dst_idx) +{ + struct ieee80211_mesh_hwmp_perr *perr_ie = (void *)ie; + struct ieee80211_mesh_hwmp_perr_dst *dst; + u8 *pos = perr_ie->variable; + int i; + + for (i = 0; i < dst_idx + 1; i++) { + dst = (void *)pos; + pos += sizeof(struct ieee80211_mesh_hwmp_perr_dst) + + ((dst->flags & AE_F) ? ETH_ALEN : 0) + /* Destination External Address */ + + 2 /* Reason Code */; + } + + return dst; +} + +static inline u8 * +ieee80211_mesh_hwmp_perr_get_addr(const u8 *ie, u8 dst_idx) +{ + struct ieee80211_mesh_hwmp_perr_dst *dst = + ieee80211_mesh_hwmp_perr_get_dst(ie, dst_idx); + + return dst->addr; +} + +static inline u32 +ieee80211_mesh_hwmp_perr_get_sn(const u8 *ie, u8 dst_idx) +{ + struct ieee80211_mesh_hwmp_perr_dst *dst = + ieee80211_mesh_hwmp_perr_get_dst(ie, dst_idx); + + return le32_to_cpu(dst->sn); +} + +static inline u16 +ieee80211_mesh_hwmp_perr_get_rcode(const u8 *ie, u8 dst_idx) +{ + struct ieee80211_mesh_hwmp_perr_dst *dst = + ieee80211_mesh_hwmp_perr_get_dst(ie, dst_idx); + + return get_unaligned_le16(&dst->variable[ + (dst->flags & AE_F) ? ETH_ALEN : 0]); +} + #endif /* LINUX_IEEE80211_MESH_H */ diff --git a/net/mac80211/mesh_hwmp.c b/net/mac80211/mesh_hwmp.c index 39b782370df0..378338778a23 100644 --- a/net/mac80211/mesh_hwmp.c +++ b/net/mac80211/mesh_hwmp.c @@ -20,29 +20,7 @@ static void mesh_queue_preq(struct mesh_path *, u8); -static inline u32 u32_field_get(const u8 *preq_elem, int offset, bool ae) -{ - if (ae) - offset += 6; - return get_unaligned_le32(preq_elem + offset); -} - -static inline u16 u16_field_get(const u8 *preq_elem, int offset, bool ae) -{ - if (ae) - offset += 6; - return get_unaligned_le16(preq_elem + offset); -} - /* HWMP IE processing macros */ -#define AE_F_SET(x) (*x & AE_F) - -#define PERR_IE_TTL(x) (*(x)) -#define PERR_IE_TARGET_FLAGS(x) (*(x + 2)) -#define PERR_IE_TARGET_ADDR(x) (x + 3) -#define PERR_IE_TARGET_SN(x) u32_field_get(x, 9, 0) -#define PERR_IE_TARGET_RCODE(x) u16_field_get(x, 13, 0) - #define MSEC_TO_TU(x) (x*1000/1024) #define SN_GT(x, y) ((s32)(y - x) < 0) #define SN_LT(x, y) ((s32)(x - y) < 0) @@ -774,6 +752,7 @@ static void hwmp_perr_frame_process(struct ieee80211_sub_if_data *sdata, const u8 *perr_elem) { struct ieee80211_if_mesh *ifmsh = &sdata->u.mesh; + struct ieee80211_mesh_hwmp_perr *perr_elem_s = (void *)perr_elem; struct mesh_path *mpath; u8 ttl; const u8 *ta, *target_addr; @@ -781,15 +760,15 @@ static void hwmp_perr_frame_process(struct ieee80211_sub_if_data *sdata, u16 target_rcode; ta = mgmt->sa; - ttl = PERR_IE_TTL(perr_elem); + ttl = perr_elem_s->ttl; if (ttl <= 1) { ifmsh->mshstats.dropped_frames_ttl++; return; } ttl--; - target_addr = PERR_IE_TARGET_ADDR(perr_elem); - target_sn = PERR_IE_TARGET_SN(perr_elem); - target_rcode = PERR_IE_TARGET_RCODE(perr_elem); + target_addr = ieee80211_mesh_hwmp_perr_get_addr(perr_elem, 0); + target_sn = ieee80211_mesh_hwmp_perr_get_sn(perr_elem, 0); + target_rcode = ieee80211_mesh_hwmp_perr_get_rcode(perr_elem, 0); rcu_read_lock(); mpath = mesh_path_lookup(sdata, target_addr); -- cgit v1.2.3 From 8b40b1d24a6099fe9fac8e207d4cb04ab5e0baae Mon Sep 17 00:00:00 2001 From: Masashi Honma Date: Sat, 30 May 2026 08:09:46 +0900 Subject: wifi: mac80211: Fix overread in PREQ frame processing When the AF flag is enabled, hwmp_preq_frame_process() overreads target_addr by 2 bytes. Since this occurs within the socket buffer, it does not read across memory boundaries and therefore poses no security risk; however, we will fix it as a precaution. In this fix, a new function mesh_path_parse_request_frame() is established to separate the implementation of frame format validation and the check for unsupported features. This is intended to facilitate future work when implementing the currently unsupported parts. Assisted-by: Claude:Sonnet 4.6 Signed-off-by: Masashi Honma Link: https://patch.msgid.link/20260529230952.124754-4-masashi.honma@gmail.com Signed-off-by: Johannes Berg --- include/linux/ieee80211-mesh.h | 29 +++++++++++++++++++++++++++++ net/mac80211/mesh_hwmp.c | 12 ++++++++++-- net/mac80211/parse.c | 9 +++++++-- 3 files changed, 46 insertions(+), 4 deletions(-) (limited to 'include') diff --git a/include/linux/ieee80211-mesh.h b/include/linux/ieee80211-mesh.h index f709263c310b..8fbd31d9538d 100644 --- a/include/linux/ieee80211-mesh.h +++ b/include/linux/ieee80211-mesh.h @@ -358,4 +358,33 @@ ieee80211_mesh_hwmp_perr_get_rcode(const u8 *ie, u8 dst_idx) (dst->flags & AE_F) ? ETH_ALEN : 0]); } +/* IEEE Std 802.11-2016 9.4.2.113 PREQ element */ +static inline bool ieee80211_mesh_preq_size_ok(const u8 *pos, u8 elen) +{ + struct ieee80211_mesh_hwmp_preq_bottom *preq_elem_bottom = + ieee80211_mesh_hwmp_preq_get_bottom(pos); + u8 target_count; + int needed; + + /* Check if the element contains flags */ + needed = sizeof(struct ieee80211_mesh_hwmp_preq_top); + if (elen < needed) + return false; + + /* Check if the element contains target_count */ + needed += (ieee80211_mesh_preq_prep_ae_enabled(pos) ? ETH_ALEN : 0) + /* Originator External Address */ + + sizeof(struct ieee80211_mesh_hwmp_preq_bottom); + if (elen < needed) + return false; + + target_count = preq_elem_bottom->target_count; + /* IEEE Std 802.11-2016 Table 14-10 to 14-16 */ + if (target_count < 1) + return false; + + needed += target_count * sizeof(struct ieee80211_mesh_hwmp_preq_target); + return elen == needed; +} + #endif /* LINUX_IEEE80211_MESH_H */ diff --git a/net/mac80211/mesh_hwmp.c b/net/mac80211/mesh_hwmp.c index 378338778a23..ef6eff52f32a 100644 --- a/net/mac80211/mesh_hwmp.c +++ b/net/mac80211/mesh_hwmp.c @@ -929,9 +929,17 @@ void mesh_rx_path_sel_frame(struct ieee80211_sub_if_data *sdata, return; if (elems->preq) { - if (elems->preq_len != 37) - /* Right now we support just 1 destination and no AE */ + struct ieee80211_mesh_hwmp_preq_bottom *preq_elem_bottom = + ieee80211_mesh_hwmp_preq_get_bottom(elems->preq); + + /* Right now we do not support AE (Address Extension) */ + if (ieee80211_mesh_preq_prep_ae_enabled(elems->preq)) goto free; + + /* Right now we only support 1 target */ + if (preq_elem_bottom->target_count != 1) + goto free; + path_metric = hwmp_route_info_get(sdata, mgmt, elems->preq, MPATH_PREQ); if (path_metric) diff --git a/net/mac80211/parse.c b/net/mac80211/parse.c index 8b30e361b622..3d441ff9593d 100644 --- a/net/mac80211/parse.c +++ b/net/mac80211/parse.c @@ -563,8 +563,13 @@ _ieee802_11_parse_elems_full(struct ieee80211_elems_parse_params *params, elems->awake_window = (void *)pos; break; case WLAN_EID_PREQ: - elems->preq = pos; - elems->preq_len = elen; + if (ieee80211_mesh_preq_size_ok(pos, elen)) { + elems->preq = pos; + elems->preq_len = elen; + } else { + elem_parse_failed = + IEEE80211_PARSE_ERR_BAD_ELEM_SIZE; + } break; case WLAN_EID_PREP: elems->prep = pos; -- cgit v1.2.3 From d158e54476ea9667c33dfa2c8d87c7cc32b40f1b Mon Sep 17 00:00:00 2001 From: Masashi Honma Date: Sat, 30 May 2026 08:09:47 +0900 Subject: wifi: mac80211: Fix overread in PREP frame processing When the AF flag is enabled, hwmp_prep_frame_process() overreads orig_addr by 2 bytes. Since this occurs within the socket buffer, it does not read across memory boundaries and therefore poses no security risk; however, we will fix it as a precaution. In this fix, a new function mesh_path_parse_reply_frame() is established to separate the implementation of frame format validation and the check for unsupported features. This is intended to facilitate future work when implementing the currently unsupported parts. Assisted-by: Claude:Sonnet 4.6 Signed-off-by: Masashi Honma Link: https://patch.msgid.link/20260529230952.124754-5-masashi.honma@gmail.com Signed-off-by: Johannes Berg --- include/linux/ieee80211-mesh.h | 16 ++++++++++++++++ net/mac80211/mesh_hwmp.c | 4 ++-- net/mac80211/parse.c | 9 +++++++-- 3 files changed, 25 insertions(+), 4 deletions(-) (limited to 'include') diff --git a/include/linux/ieee80211-mesh.h b/include/linux/ieee80211-mesh.h index 8fbd31d9538d..482ac0c6d759 100644 --- a/include/linux/ieee80211-mesh.h +++ b/include/linux/ieee80211-mesh.h @@ -387,4 +387,20 @@ static inline bool ieee80211_mesh_preq_size_ok(const u8 *pos, u8 elen) return elen == needed; } +/* IEEE Std 802.11-2016 9.4.2.114 PREP element */ +static inline bool ieee80211_mesh_prep_size_ok(const u8 *pos, u8 elen) +{ + u8 needed; + + /* Check if the element contains flags */ + needed = sizeof(struct ieee80211_mesh_hwmp_prep_top); + if (elen < needed) + return false; + + needed += (ieee80211_mesh_preq_prep_ae_enabled(pos) ? ETH_ALEN : 0) + /* Target External Address */ + + sizeof(struct ieee80211_mesh_hwmp_prep_bottom); + return elen == needed; +} + #endif /* LINUX_IEEE80211_MESH_H */ diff --git a/net/mac80211/mesh_hwmp.c b/net/mac80211/mesh_hwmp.c index ef6eff52f32a..f07e57d5568a 100644 --- a/net/mac80211/mesh_hwmp.c +++ b/net/mac80211/mesh_hwmp.c @@ -947,8 +947,8 @@ void mesh_rx_path_sel_frame(struct ieee80211_sub_if_data *sdata, path_metric); } if (elems->prep) { - if (elems->prep_len != 31) - /* Right now we support no AE */ + /* Right now we do not support AE (Address Extension) */ + if (ieee80211_mesh_preq_prep_ae_enabled(elems->prep)) goto free; path_metric = hwmp_route_info_get(sdata, mgmt, elems->prep, MPATH_PREP); diff --git a/net/mac80211/parse.c b/net/mac80211/parse.c index 3d441ff9593d..97508b141b8c 100644 --- a/net/mac80211/parse.c +++ b/net/mac80211/parse.c @@ -572,8 +572,13 @@ _ieee802_11_parse_elems_full(struct ieee80211_elems_parse_params *params, } break; case WLAN_EID_PREP: - elems->prep = pos; - elems->prep_len = elen; + if (ieee80211_mesh_prep_size_ok(pos, elen)) { + elems->prep = pos; + elems->prep_len = elen; + } else { + elem_parse_failed = + IEEE80211_PARSE_ERR_BAD_ELEM_SIZE; + } break; case WLAN_EID_PERR: elems->perr = pos; -- cgit v1.2.3 From 4ec9ea8a58859c260ae32e097aa643975572e58b Mon Sep 17 00:00:00 2001 From: Masashi Honma Date: Sat, 30 May 2026 08:09:48 +0900 Subject: wifi: mac80211: Fix PERR frame processing There are no issues with the PERR processing itself; however, to maintain consistency with the previous PREQ/PREP code modifications, I will create a new mesh_path_parse_error_frame() function to separately implement the frame format validation and the "not supported" check. Signed-off-by: Masashi Honma Link: https://patch.msgid.link/20260529230952.124754-6-masashi.honma@gmail.com Signed-off-by: Johannes Berg --- include/linux/ieee80211-mesh.h | 36 ++++++++++++++++++++++++++++++++++++ net/mac80211/mesh_hwmp.c | 18 ++++++++++++++++-- net/mac80211/parse.c | 9 +++++++-- 3 files changed, 59 insertions(+), 4 deletions(-) (limited to 'include') diff --git a/include/linux/ieee80211-mesh.h b/include/linux/ieee80211-mesh.h index 482ac0c6d759..7eb15834531c 100644 --- a/include/linux/ieee80211-mesh.h +++ b/include/linux/ieee80211-mesh.h @@ -403,4 +403,40 @@ static inline bool ieee80211_mesh_prep_size_ok(const u8 *pos, u8 elen) return elen == needed; } +/* IEEE Std 802.11-2016 9.4.2.115 PERR element */ +static inline bool ieee80211_mesh_perr_size_ok(const u8 *pos, u8 elen) +{ + struct ieee80211_mesh_hwmp_perr *perr_elem = (void *)pos; + const u8 *start = pos; + u8 number_of_dst; + int needed; + int i; + + needed = sizeof(struct ieee80211_mesh_hwmp_perr); + + /* Check if the element contains number of dst */ + if (elen < needed) + return false; + + pos += sizeof(struct ieee80211_mesh_hwmp_perr); + number_of_dst = perr_elem->number_of_dst; + + for (i = 0; i < number_of_dst; i++) { + struct ieee80211_mesh_hwmp_perr_dst *dst = (void *)pos; + u8 dst_len = sizeof(struct ieee80211_mesh_hwmp_perr_dst); + + /* Check if the element contains flags */ + if (elen < pos - start + dst_len) + return false; + + dst_len += ((dst->flags & AE_F) ? ETH_ALEN : 0) + /* Destination External Address */ + + 2 /* Reason Code */; + needed += dst_len; + pos += dst_len; + } + + return elen == needed; +} + #endif /* LINUX_IEEE80211_MESH_H */ diff --git a/net/mac80211/mesh_hwmp.c b/net/mac80211/mesh_hwmp.c index f07e57d5568a..84903737271d 100644 --- a/net/mac80211/mesh_hwmp.c +++ b/net/mac80211/mesh_hwmp.c @@ -957,9 +957,23 @@ void mesh_rx_path_sel_frame(struct ieee80211_sub_if_data *sdata, path_metric); } if (elems->perr) { - if (elems->perr_len != 15) - /* Right now we support only one destination per PERR */ + struct ieee80211_mesh_hwmp_perr *perr_elem = + (struct ieee80211_mesh_hwmp_perr *)elems->perr; + int i; + + /* Right now we support only one destination per PERR */ + if (perr_elem->number_of_dst != 1) goto free; + + /* Right now we do not support AE (Address Extension) */ + for (i = 0; i < perr_elem->number_of_dst; i++) { + struct ieee80211_mesh_hwmp_perr_dst *dst = + ieee80211_mesh_hwmp_perr_get_dst(elems->perr, i); + + if (dst->flags & AE_F) + goto free; + } + hwmp_perr_frame_process(sdata, mgmt, elems->perr); } if (elems->rann) diff --git a/net/mac80211/parse.c b/net/mac80211/parse.c index 97508b141b8c..c44e81a2f80d 100644 --- a/net/mac80211/parse.c +++ b/net/mac80211/parse.c @@ -581,8 +581,13 @@ _ieee802_11_parse_elems_full(struct ieee80211_elems_parse_params *params, } break; case WLAN_EID_PERR: - elems->perr = pos; - elems->perr_len = elen; + if (ieee80211_mesh_perr_size_ok(pos, elen)) { + elems->perr = pos; + elems->perr_len = elen; + } else { + elem_parse_failed = + IEEE80211_PARSE_ERR_BAD_ELEM_SIZE; + } break; case WLAN_EID_RANN: if (elen >= sizeof(struct ieee80211_rann_ie)) -- cgit v1.2.3 From b3d519330554b0b9e662cefebba854e0a7c1a592 Mon Sep 17 00:00:00 2001 From: Lachlan Hodges Date: Tue, 2 Jun 2026 16:22:24 +1000 Subject: wifi: mac80211: basic S1G rx rate reporting support Introduce basic rate encoding/decoding for S1G stas such that the usermode rx reporting is relevant as it currently uses VHT calculations which are obviously wildy different to S1G. Sample iw output (with the associated iw patches applied): Connected to 0c:bf:74:00:21:c4 (on wlan0) SSID: wifi_halow freq: 923.500 RX: 7325230 bytes (4756 packets) TX: 190044 bytes (2238 packets) signal: -38 dBm rx bitrate: 43.3 MBit/s S1G-MCS 9 8MHz short GI S1G-NSS 1 tx bitrate: 43.3 MBit/s S1G-MCS 9 8MHz short GI S1G-NSS 1 bss flags: dtim period: 1 beacon int: 100 Signed-off-by: Lachlan Hodges Link: https://patch.msgid.link/20260602062224.1792985-1-lachlan.hodges@morsemicro.com Signed-off-by: Johannes Berg --- include/net/mac80211.h | 1 + net/mac80211/rx.c | 8 ++++++++ net/mac80211/sta_info.c | 7 +++++++ net/mac80211/sta_info.h | 11 ++++++++++- 4 files changed, 26 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/include/net/mac80211.h b/include/net/mac80211.h index 4fb579805e0f..7dd558f4025b 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -1704,6 +1704,7 @@ enum mac80211_rx_encoding { RX_ENC_HE, RX_ENC_EHT, RX_ENC_UHR, + RX_ENC_S1G, }; /** diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c index ef6086b183f7..91b4f6cbfce8 100644 --- a/net/mac80211/rx.c +++ b/net/mac80211/rx.c @@ -5621,6 +5621,14 @@ void ieee80211_rx_list(struct ieee80211_hw *hw, struct ieee80211_sta *pubsta, status->rate_idx, status->nss)) goto drop; break; + case RX_ENC_S1G: + if (WARN_ONCE(status->rate_idx > 12 || + !status->nss || + status->nss > 4, + "Rate marked as an S1G rate but data is invalid: MCS: %d, NSS: %d\n", + status->rate_idx, status->nss)) + goto drop; + break; default: WARN_ON_ONCE(1); fallthrough; diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c index 4c86a3793804..857c7d3355c7 100644 --- a/net/mac80211/sta_info.c +++ b/net/mac80211/sta_info.c @@ -2605,6 +2605,13 @@ static void sta_stats_decode_rate(struct ieee80211_local *local, u32 rate, if (STA_STATS_GET(UHR_IM, rate)) rinfo->flags |= RATE_INFO_FLAGS_UHR_IM; break; + case STA_STATS_RATE_TYPE_S1G: + rinfo->flags = RATE_INFO_FLAGS_S1G_MCS; + rinfo->mcs = STA_STATS_GET(S1G_MCS, rate); + rinfo->nss = STA_STATS_GET(S1G_NSS, rate); + if (STA_STATS_GET(SGI, rate)) + rinfo->flags |= RATE_INFO_FLAGS_SHORT_GI; + break; } } diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h index 39608a0abbb5..e1837e986837 100644 --- a/net/mac80211/sta_info.h +++ b/net/mac80211/sta_info.h @@ -1042,7 +1042,7 @@ enum sta_stats_type { #define STA_STATS_FIELD_VHT_MCS 0x0000F000 #define STA_STATS_FIELD_VHT_NSS 0x000F0000 -/* HT & VHT */ +/* HT, VHT & S1G */ #define STA_STATS_FIELD_SGI 0x00100000 /* STA_STATS_RATE_TYPE_HE */ @@ -1066,6 +1066,9 @@ enum sta_stats_type { #define STA_STATS_FIELD_UHR_ELR 0x08000000 #define STA_STATS_FIELD_UHR_IM 0x10000000 +/* STA_STATS_RATE_TYPE_S1G */ +#define STA_STATS_FIELD_S1G_MCS 0x0000F000 +#define STA_STATS_FIELD_S1G_NSS 0x000F0000 #define STA_STATS_FIELD(_n, _v) FIELD_PREP(STA_STATS_FIELD_ ## _n, _v) #define STA_STATS_GET(_n, _v) FIELD_GET(STA_STATS_FIELD_ ## _n, _v) @@ -1081,6 +1084,7 @@ static inline u32 sta_stats_encode_rate(struct ieee80211_rx_status *s) switch (s->encoding) { case RX_ENC_HT: case RX_ENC_VHT: + case RX_ENC_S1G: if (s->enc_flags & RX_ENC_FLAG_SHORT_GI) r |= STA_STATS_FIELD(SGI, 1); break; @@ -1127,6 +1131,11 @@ static inline u32 sta_stats_encode_rate(struct ieee80211_rx_status *s) r |= STA_STATS_FIELD(UHR_ELR, s->uhr.elr); r |= STA_STATS_FIELD(UHR_IM, s->uhr.im); break; + case RX_ENC_S1G: + r |= STA_STATS_FIELD(TYPE, STA_STATS_RATE_TYPE_S1G); + r |= STA_STATS_FIELD(S1G_NSS, s->nss); + r |= STA_STATS_FIELD(S1G_MCS, s->rate_idx); + break; default: WARN_ON(1); return STA_STATS_RATE_INVALID; -- cgit v1.2.3 From 50aa66ac0162aac818285a4a5d7c2c553b6bfb65 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 29 May 2026 10:24:54 +0200 Subject: wifi: ieee80211: define some UHR link reconfiguration frame types Define some values needed for UHR link reconfiguration frames, in particular to prepare for UHR mode change request/handling. Link: https://patch.msgid.link/20260529102644.03029bae6447.If22b0c1e10d9db712dca408a420469b3d385b4ea@changeid Signed-off-by: Johannes Berg --- include/linux/ieee80211-uhr.h | 51 +++++++++++++++++++++++++++++++++++++++++++ include/linux/ieee80211.h | 17 +++++++++++++++ 2 files changed, 68 insertions(+) (limited to 'include') diff --git a/include/linux/ieee80211-uhr.h b/include/linux/ieee80211-uhr.h index f4f4bd8256df..71faf4a6825e 100644 --- a/include/linux/ieee80211-uhr.h +++ b/include/linux/ieee80211-uhr.h @@ -8,6 +8,7 @@ #define LINUX_IEEE80211_UHR_H #include +#include #include #define IEEE80211_UHR_OPER_PARAMS_DPS_ENA 0x0001 @@ -463,4 +464,54 @@ struct ieee80211_smd_info { __le16 timeout; } __packed; +enum ieee80211_protected_uhr_action { + IEEE80211_PROTECTED_UHR_ACTION_LINK_RECONFIG_REQUEST = 0, + IEEE80211_PROTECTED_UHR_ACTION_LINK_RECONFIG_RESPONSE = 1, + IEEE80211_PROTECTED_UHR_ACTION_LINK_RECONFIG_NOTIFY = 2, +}; + +enum ieee80211_uhr_link_reconfig_request_type { + IEEE80211_UHR_LINK_RECONFIG_REQUEST_ST_PREP = 0, + IEEE80211_UHR_LINK_RECONFIG_REQUEST_ST_EXEC = 1, + IEEE80211_UHR_LINK_RECONFIG_REQUEST_OMP_REQUEST = 3, +}; + +enum ieee80211_uhr_link_reconfig_response_type { + IEEE80211_UHR_LINK_RECONFIG_RESPONSE_ST_PREP = 0, + IEEE80211_UHR_LINK_RECONFIG_RESPONSE_ST_EXEC = 1, +}; + +enum ieee80211_uhr_link_reconfig_notify_type { + IEEE80211_UHR_LINK_RECONFIG_NOTIFY_DL_DRAINED = 2, + IEEE80211_UHR_LINK_RECONFIG_NOTIFY_OMP_RESPONSE = 3, +}; + +enum ieee80211_uhr_mode_change_control { + IEEE80211_UHR_MODE_CHANGE_CONTROL_MODE_ID = 0x003f, + IEEE80211_UHR_MODE_CHANGE_CONTROL_MODE_ENABLE = 0x0040, + IEEE80211_UHR_MODE_CHANGE_CONTROL_MODE_UPDATE = 0x0080, + IEEE80211_UHR_MODE_CHANGE_CONTROL_MODE_LENGTH = 0x0f00, + IEEE80211_UHR_MODE_CHANGE_CONTROL_MODE_SPECIFIC = 0xf000, +}; + +enum ieee80211_uhr_mode_change_mode_id { + IEEE80211_UHR_MODE_CHANGE_MODE_ID_DPS = 0, + IEEE80211_UHR_MODE_CHANGE_MODE_ID_NPCA = 1, + IEEE80211_UHR_MODE_CHANGE_MODE_ID_DUO = 2, + IEEE80211_UHR_MODE_CHANGE_MODE_ID_DSO = 3, + IEEE80211_UHR_MODE_CHANGE_MODE_ID_P_EDCA = 4, + IEEE80211_UHR_MODE_CHANGE_MODE_ID_ELR_RX = 5, + IEEE80211_UHR_MODE_CHANGE_MODE_ID_AOM = 6, + IEEE80211_UHR_MODE_CHANGE_MODE_ID_LLI = 7, + IEEE80211_UHR_MODE_CHANGE_MODE_ID_CO_BF = 8, + IEEE80211_UHR_MODE_CHANGE_MODE_ID_CO_SR = 9, + IEEE80211_UHR_MODE_CHANGE_MODE_ID_EMLSR = 10, + IEEE80211_UHR_MODE_CHANGE_MODE_ID_DBE = 11, +}; + +struct ieee80211_uhr_mode_change_tuple { + __le16 control; + u8 variable[]; +} __packed; + #endif /* LINUX_IEEE80211_UHR_H */ diff --git a/include/linux/ieee80211.h b/include/linux/ieee80211.h index 11106589acc6..d40484451e9a 100644 --- a/include/linux/ieee80211.h +++ b/include/linux/ieee80211.h @@ -1174,6 +1174,22 @@ struct ieee80211_mgmt { u8 control; u8 variable[]; } __packed eml_omn; + struct { + u8 dialog_token; + u8 type; + u8 variable[]; + } __packed uhr_link_reconf_req; + struct { + u8 dialog_token; + u8 type; + u8 count; + u8 variable[]; + } __packed uhr_link_reconf_resp; + struct { + u8 dialog_token; + u8 type; + u8 variable[]; + } __packed uhr_link_reconf_notif; }; } __packed action; DECLARE_FLEX_ARRAY(u8, body); /* Generic frame body */ @@ -1837,6 +1853,7 @@ enum ieee80211_category { WLAN_CATEGORY_VHT = 21, WLAN_CATEGORY_S1G = 22, WLAN_CATEGORY_PROTECTED_EHT = 37, + WLAN_CATEGORY_PROTECTED_UHR = 43, WLAN_CATEGORY_VENDOR_SPECIFIC_PROTECTED = 126, WLAN_CATEGORY_VENDOR_SPECIFIC = 127, }; -- cgit v1.2.3 From 7ad8731738d7511c92f9ffc8c69fa8681986dd5d Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 29 May 2026 10:25:04 +0200 Subject: wifi: Update UHR PHY capabilities to D1.4 There are new capabilities in D1.4, and some reserved bits. Update the code accordingly. Link: https://patch.msgid.link/20260529102644.f146932b21e2.I12bad84157bf809fbe285b79420143b3c456d9d2@changeid Signed-off-by: Johannes Berg --- drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c | 10 +++--- drivers/net/wireless/intel/iwlwifi/mld/tlc.c | 4 +-- drivers/net/wireless/virtual/mac80211_hwsim_main.c | 24 ++++++------- include/linux/ieee80211-uhr.h | 40 +++++++++++++++++----- 4 files changed, 49 insertions(+), 29 deletions(-) (limited to 'include') diff --git a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c index 7027bca249a0..d47b4ae2f486 100644 --- a/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c +++ b/drivers/net/wireless/intel/iwlwifi/iwl-nvm-parse.c @@ -693,8 +693,9 @@ static const struct ieee80211_sband_iftype_data iwl_iftype_cap[] = { }, .uhr_cap = { .has_uhr = true, - .phy.cap = IEEE80211_UHR_PHY_CAP_ELR_RX | - IEEE80211_UHR_PHY_CAP_ELR_TX, + /* Note: asymmetry is fixed later */ + .phy.cap = cpu_to_le32(IEEE80211_UHR_PHY_CAP_ELR_RX | + IEEE80211_UHR_PHY_CAP_ELR_TX), .mac.mac_cap = { [0] = IEEE80211_UHR_MAC_CAP0_NPCA_SUPP | IEEE80211_UHR_MAC_CAP0_DPS_SUPP, @@ -801,8 +802,9 @@ static const struct ieee80211_sband_iftype_data iwl_iftype_cap[] = { }, .uhr_cap = { .has_uhr = true, - .phy.cap = IEEE80211_UHR_PHY_CAP_ELR_RX | - IEEE80211_UHR_PHY_CAP_ELR_TX, + /* Note: asymmetry is fixed later */ + .phy.cap = cpu_to_le32(IEEE80211_UHR_PHY_CAP_ELR_RX | + IEEE80211_UHR_PHY_CAP_ELR_TX), }, }, }; diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tlc.c b/drivers/net/wireless/intel/iwlwifi/mld/tlc.c index a03834d3ac65..edf9c735dd67 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/tlc.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/tlc.c @@ -114,8 +114,8 @@ iwl_mld_get_tlc_cmd_flags(struct iwl_mld *mld, } if (uhr_cap && uhr_cap->has_uhr && own_uhr_cap && - uhr_cap->phy.cap & IEEE80211_UHR_PHY_CAP_ELR_RX && - own_uhr_cap->phy.cap & IEEE80211_UHR_PHY_CAP_ELR_TX) + uhr_cap->phy.cap & cpu_to_le32(IEEE80211_UHR_PHY_CAP_ELR_RX) && + own_uhr_cap->phy.cap & cpu_to_le32(IEEE80211_UHR_PHY_CAP_ELR_TX)) flags |= IWL_TLC_MNG_CFG_FLAGS_UHR_ELR_1_5_MBPS_MSK | IWL_TLC_MNG_CFG_FLAGS_UHR_ELR_3_MBPS_MSK; diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c index 4926122d6648..de0bb4ce7456 100644 --- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c +++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c @@ -4593,8 +4593,8 @@ static const struct ieee80211_sband_iftype_data sband_capa_2ghz[] = { .mac.mac_cap = { [0] = IEEE80211_UHR_MAC_CAP0_NPCA_SUPP, }, - .phy.cap = IEEE80211_UHR_PHY_CAP_ELR_RX | - IEEE80211_UHR_PHY_CAP_ELR_TX, + .phy.cap = cpu_to_le32(IEEE80211_UHR_PHY_CAP_ELR_RX | + IEEE80211_UHR_PHY_CAP_ELR_TX), }, }, { @@ -4709,8 +4709,8 @@ static const struct ieee80211_sband_iftype_data sband_capa_2ghz[] = { .mac.mac_cap = { [0] = IEEE80211_UHR_MAC_CAP0_NPCA_SUPP, }, - .phy.cap = IEEE80211_UHR_PHY_CAP_ELR_RX | - IEEE80211_UHR_PHY_CAP_ELR_TX, + .phy.cap = cpu_to_le32(IEEE80211_UHR_PHY_CAP_ELR_RX | + IEEE80211_UHR_PHY_CAP_ELR_TX), }, }, #ifdef CONFIG_MAC80211_MESH @@ -4886,8 +4886,7 @@ static const struct ieee80211_sband_iftype_data sband_capa_5ghz[] = { .mac.mac_cap = { [0] = IEEE80211_UHR_MAC_CAP0_NPCA_SUPP, }, - .phy.cap = IEEE80211_UHR_PHY_CAP_ELR_RX | - IEEE80211_UHR_PHY_CAP_ELR_TX, + .phy.cap = cpu_to_le32(IEEE80211_UHR_PHY_CAP_ELR_TX), }, }, { @@ -5019,8 +5018,7 @@ static const struct ieee80211_sband_iftype_data sband_capa_5ghz[] = { .mac.mac_cap = { [0] = IEEE80211_UHR_MAC_CAP0_NPCA_SUPP, }, - .phy.cap = IEEE80211_UHR_PHY_CAP_ELR_RX | - IEEE80211_UHR_PHY_CAP_ELR_TX, + .phy.cap = cpu_to_le32(IEEE80211_UHR_PHY_CAP_ELR_RX), }, }, #ifdef CONFIG_MAC80211_MESH @@ -5220,8 +5218,7 @@ static const struct ieee80211_sband_iftype_data sband_capa_6ghz[] = { .mac.mac_cap = { [0] = IEEE80211_UHR_MAC_CAP0_NPCA_SUPP, }, - .phy.cap = IEEE80211_UHR_PHY_CAP_ELR_RX | - IEEE80211_UHR_PHY_CAP_ELR_TX, + .phy.cap = cpu_to_le32(IEEE80211_UHR_PHY_CAP_ELR_TX), }, }, { @@ -5374,8 +5371,7 @@ static const struct ieee80211_sband_iftype_data sband_capa_6ghz[] = { .mac.mac_cap = { [0] = IEEE80211_UHR_MAC_CAP0_NPCA_SUPP, }, - .phy.cap = IEEE80211_UHR_PHY_CAP_ELR_RX | - IEEE80211_UHR_PHY_CAP_ELR_TX, + .phy.cap = cpu_to_le32(IEEE80211_UHR_PHY_CAP_ELR_RX), }, }, #ifdef CONFIG_MAC80211_MESH @@ -5473,8 +5469,8 @@ static const struct ieee80211_sband_iftype_data sband_capa_6ghz[] = { .mac.mac_cap = { [0] = IEEE80211_UHR_MAC_CAP0_NPCA_SUPP, }, - .phy.cap = IEEE80211_UHR_PHY_CAP_ELR_RX | - IEEE80211_UHR_PHY_CAP_ELR_TX, + .phy.cap = cpu_to_le32(IEEE80211_UHR_PHY_CAP_ELR_RX | + IEEE80211_UHR_PHY_CAP_ELR_TX), }, }, #endif diff --git a/include/linux/ieee80211-uhr.h b/include/linux/ieee80211-uhr.h index 71faf4a6825e..be51d91f5e03 100644 --- a/include/linux/ieee80211-uhr.h +++ b/include/linux/ieee80211-uhr.h @@ -401,17 +401,39 @@ struct ieee80211_uhr_cap_mac { u8 mac_cap[5]; } __packed; -#define IEEE80211_UHR_PHY_CAP_MAX_NSS_RX_SND_NDP_LE80 0x01 -#define IEEE80211_UHR_PHY_CAP_MAX_NSS_RX_DL_MU_LE80 0x02 -#define IEEE80211_UHR_PHY_CAP_MAX_NSS_RX_SND_NDP_160 0x04 -#define IEEE80211_UHR_PHY_CAP_MAX_NSS_RX_DL_MU_160 0x08 -#define IEEE80211_UHR_PHY_CAP_MAX_NSS_RX_SND_NDP_320 0x10 -#define IEEE80211_UHR_PHY_CAP_MAX_NSS_RX_DL_MU_320 0x20 -#define IEEE80211_UHR_PHY_CAP_ELR_RX 0x40 -#define IEEE80211_UHR_PHY_CAP_ELR_TX 0x80 +#define IEEE80211_UHR_PHY_CAP_MAX_NSS_RX_SND_NDP_LE80 0x00000001 +#define IEEE80211_UHR_PHY_CAP_MAX_NSS_RX_DL_MU_LE80 0x00000002 +#define IEEE80211_UHR_PHY_CAP_MAX_NSS_RX_SND_NDP_160 0x00000004 +#define IEEE80211_UHR_PHY_CAP_MAX_NSS_RX_DL_MU_160 0x00000008 +#define IEEE80211_UHR_PHY_CAP_MAX_NSS_RX_SND_NDP_320 0x00000010 +#define IEEE80211_UHR_PHY_CAP_MAX_NSS_RX_DL_MU_320 0x00000020 +#define IEEE80211_UHR_PHY_CAP_ELR_TX 0x00000040 +#define IEEE80211_UHR_PHY_CAP_ELR_RX 0x00000080 +#define IEEE80211_UHR_PHY_CAP_PART_BW_DL_MUMIMO 0x00000100 +#define IEEE80211_UHR_PHY_CAP_PART_BW_UL_MUMIMO 0x00000200 +#define IEEE80211_UHR_PHY_CAP_MCS15 0x00000400 +#define IEEE80211_UHR_PHY_CAP_2XLDPC_TX 0x00000800 +#define IEEE80211_UHR_PHY_CAP_2XLDPC_RX 0x00001000 +#define IEEE80211_UHR_PHY_CAP_UEQM_TX_MAX_NSS 0x00006000 +#define IEEE80211_UHR_PHY_CAP_UEQM_RX_MAX_NSS 0x00018000 +#define IEEE80211_UHR_PHY_CAP_CO_BF_JOINT_SOUNDING 0x00040000 +#define IEEE80211_UHR_PHY_CAP_IM_TX 0x00080000 +#define IEEE80211_UHR_PHY_CAP_IM_RX 0x00100000 +#define IEEE80211_UHR_PHY_CAP_CO_SR_MODE_1 0x00200000 +#define IEEE80211_UHR_PHY_CAP_CO_SR_MODE_2 0x00400000 +#define IEEE80211_UHR_PHY_CAP_DRU_DBW_20_IN_PBW_20 0x00800000 +#define IEEE80211_UHR_PHY_CAP_DRU_DBW_40_IN_PBW_40 0x01000000 +#define IEEE80211_UHR_PHY_CAP_DRU_DBW_80_IN_PBW_80 0x02000000 +#define IEEE80211_UHR_PHY_CAP_DRU_DBW_80_IN_PBW_160 0x04000000 +#define IEEE80211_UHR_PHY_CAP_DRU_DBW_80_IN_PBW_320 0x08000000 +#define IEEE80211_UHR_PHY_CAP_DRU_DBW_20_IN_PBW_GE80 0x10000000 +#define IEEE80211_UHR_PHY_CAP_DRU_DBW_40_IN_PBW_GE80 0x20000000 +#define IEEE80211_UHR_PHY_CAP_DRU_DBW_60_IN_PBW_GE80 0x40000000 +#define IEEE80211_UHR_PHY_CAP_DRU_RRU_HYBRID_MODE 0x80000000 struct ieee80211_uhr_cap_phy { - u8 cap; + __le32 cap; + u8 reserved; } __packed; struct ieee80211_uhr_cap { -- cgit v1.2.3 From f576db12f1f0adafd8fd1c395e377b7b37a77b7b Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 29 May 2026 10:25:05 +0200 Subject: wifi: Update UHR MAC capabilities to D1.4 There are now 8 more reserved bits in D1.4, update the code accordingly. Link: https://patch.msgid.link/20260529102644.6e27c54cfceb.Id395c07ffde286011494fc75190dc6060117436e@changeid Signed-off-by: Johannes Berg --- include/linux/ieee80211-uhr.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include') diff --git a/include/linux/ieee80211-uhr.h b/include/linux/ieee80211-uhr.h index be51d91f5e03..c8dde0fbd896 100644 --- a/include/linux/ieee80211-uhr.h +++ b/include/linux/ieee80211-uhr.h @@ -398,7 +398,7 @@ enum ieee80211_uhr_dbe_max_supported_bw { }; struct ieee80211_uhr_cap_mac { - u8 mac_cap[5]; + u8 mac_cap[6]; } __packed; #define IEEE80211_UHR_PHY_CAP_MAX_NSS_RX_SND_NDP_LE80 0x00000001 -- cgit v1.2.3 From d3ea22270d7c02fe08606f609545cbe583e53060 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 29 May 2026 10:25:07 +0200 Subject: wifi: mac80211: parse and apply UHR DBE channel When a UHR AP has DBE enabled, parse the channel and apply it to the chandef. Apply for TX only after the OMP response (or timeout) so that the AP doesn't receive frames with DBE width before the station completed transition to DBE. Link: https://patch.msgid.link/20260529102644.cb810f212128.Ife37c2673251346e84e4250b242b31f0895520ab@changeid Signed-off-by: Johannes Berg --- include/linux/ieee80211-eht.h | 18 +- include/linux/ieee80211-uhr.h | 92 +++++++++ net/mac80211/ieee80211_i.h | 15 +- net/mac80211/mlme.c | 449 ++++++++++++++++++++++++++++++++++++++++- net/mac80211/rx.c | 14 ++ net/mac80211/sta_info.c | 16 ++ net/mac80211/sta_info.h | 4 + net/mac80211/status.c | 25 +++ net/mac80211/tests/chan-mode.c | 4 +- 9 files changed, 616 insertions(+), 21 deletions(-) (limited to 'include') diff --git a/include/linux/ieee80211-eht.h b/include/linux/ieee80211-eht.h index 87d92fb86fab..73e97fe30724 100644 --- a/include/linux/ieee80211-eht.h +++ b/include/linux/ieee80211-eht.h @@ -1038,13 +1038,17 @@ ieee80211_mle_basic_sta_prof_bss_param_ch_cnt(const struct ieee80211_mle_per_sta #define IEEE80211_MLE_STA_RECONF_CONTROL_COMPLETE_PROFILE 0x0010 #define IEEE80211_MLE_STA_RECONF_CONTROL_STA_MAC_ADDR_PRESENT 0x0020 #define IEEE80211_MLE_STA_RECONF_CONTROL_AP_REM_TIMER_PRESENT 0x0040 -#define IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE 0x0780 -#define IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE_AP_REM 0 -#define IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE_OP_PARAM_UPDATE 1 -#define IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE_ADD_LINK 2 -#define IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE_DEL_LINK 3 -#define IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE_NSTR_STATUS 4 -#define IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_PARAMS_PRESENT 0x0800 +#define IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE 0x0780 +#define IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE_AP_REM 0 +#define IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE_OP_PARAM_UPDATE 1 +#define IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE_ADD_LINK 2 +#define IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE_DEL_LINK 3 +#define IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE_NSTR_STATUS 4 +#define IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE_UHR_OMP_UPD 5 +#define IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_PARAMS_PRESENT 0x0800 +#define IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_NSTR_BMAP_SIZE 0x1000 +#define IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_NSTR_IND_BMAP_PRES 0x2000 +#define IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_DSO_INFO_PRESENT 0x4000 /** * ieee80211_mle_reconf_sta_prof_size_ok - validate reconfiguration multi-link diff --git a/include/linux/ieee80211-uhr.h b/include/linux/ieee80211-uhr.h index c8dde0fbd896..1dd53c5f2954 100644 --- a/include/linux/ieee80211-uhr.h +++ b/include/linux/ieee80211-uhr.h @@ -10,11 +10,13 @@ #include #include #include +#include "ieee80211-eht.h" #define IEEE80211_UHR_OPER_PARAMS_DPS_ENA 0x0001 #define IEEE80211_UHR_OPER_PARAMS_NPCA_ENA 0x0002 #define IEEE80211_UHR_OPER_PARAMS_PEDCA_ENA 0x0004 #define IEEE80211_UHR_OPER_PARAMS_DBE_ENA 0x0008 +#define IEEE80211_UHR_OPER_PARAMS_DBE_BW 0x0070 struct ieee80211_uhr_operation { __le16 params; @@ -177,6 +179,29 @@ enum ieee80211_uhr_dbe_oper_bw { IEEE80211_UHR_DBE_OPER_BW_320_2 = 5, }; +/** + * ieee80211_uhr_dbe_bw_mhz - get bandwidth in MHz from UHR DBE bandwidth + * @bw: UHR DBE bandwidth + * + * Return: the bandwidth in MHz, or -1 for invalid values + */ +static inline int ieee80211_uhr_dbe_bw_mhz(enum ieee80211_uhr_dbe_oper_bw bw) +{ + switch (bw) { + case IEEE80211_UHR_DBE_OPER_BW_40: + return 40; + case IEEE80211_UHR_DBE_OPER_BW_80: + return 80; + case IEEE80211_UHR_DBE_OPER_BW_160: + return 160; + case IEEE80211_UHR_DBE_OPER_BW_320_1: + case IEEE80211_UHR_DBE_OPER_BW_320_2: + return 320; + default: + return -1; + } +} + /** * struct ieee80211_uhr_dbe_info - DBE operation information * @@ -335,6 +360,35 @@ ieee80211_uhr_npca_dis_subch_bitmap(const struct ieee80211_uhr_operation *oper) return npca->dis_subch_bmap; } +/* + * Note: cannot call this on the element coming from a beacon, + * must ensure ieee80211_uhr_oper_size_ok(..., false) first + */ +static inline const struct ieee80211_uhr_dbe_info * +ieee80211_uhr_oper_dbe_info(const struct ieee80211_uhr_operation *oper) +{ + const u8 *pos = oper->variable; + + if (!(oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_DBE_ENA))) + return NULL; + + if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_DPS_ENA)) + pos += sizeof(struct ieee80211_uhr_dps_info); + + if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_NPCA_ENA)) { + const struct ieee80211_uhr_npca_info *npca = (const void *)pos; + + pos += sizeof(*npca); + if (npca->params & cpu_to_le32(IEEE80211_UHR_NPCA_PARAMS_DIS_SUBCH_BMAP_PRES)) + pos += sizeof(npca->dis_subch_bmap[0]); + } + + if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_PEDCA_ENA)) + pos += sizeof(struct ieee80211_uhr_p_edca_info); + + return (const void *)pos; +} + #define IEEE80211_UHR_MAC_CAP0_DPS_SUPP 0x01 #define IEEE80211_UHR_MAC_CAP0_DPS_ASSIST_SUPP 0x02 #define IEEE80211_UHR_MAC_CAP0_DPS_AP_STATIC_HCM_SUPP 0x04 @@ -475,6 +529,44 @@ static inline bool ieee80211_uhr_capa_size_ok(const u8 *data, u8 len, return len >= needed; } +#define IEEE80211_UHR_OM_PU_TO_128TU 11 + +/** + * ieee80211_uhr_capa_get_om_pu_to_us - get OM parameter update timeout in usec + * @cap: the UHR capability element, size must be validated + * + * Return: the OM parameter update timeout in usec, or -1 if it's not valid + */ +static inline int +ieee80211_uhr_capa_get_om_pu_to_us(const struct ieee80211_uhr_cap *cap) +{ + u8 timeout; + + timeout = u8_get_bits(cap->mac.mac_cap[3], + IEEE80211_UHR_MAC_CAP3_UHR_OM_PU_TO_HIGH); + timeout <<= 2; + timeout |= u8_get_bits(cap->mac.mac_cap[2], + IEEE80211_UHR_MAC_CAP2_UHR_OM_PU_TO_LOW); + + if (timeout > IEEE80211_UHR_OM_PU_TO_128TU) + return -1; + + if (!timeout) + return 0; + + return 128 << (timeout - 1); +} + +/* only valid from AP, must check ieee80211_uhr_capa_size_ok(..., true) */ +static inline const struct ieee80211_uhr_cap_dbe * +ieee80211_uhr_dbe_cap(const struct ieee80211_uhr_cap *cap) +{ + if (!(cap->mac.mac_cap[1] & IEEE80211_UHR_MAC_CAP1_DBE_SUPP)) + return NULL; + + return (const void *)cap->variable; +} + #define IEEE80211_SMD_INFO_CAPA_DL_DATA_FWD 0x01 #define IEEE80211_SMD_INFO_CAPA_MAX_NUM_PREP 0x0E #define IEEE80211_SMD_INFO_CAPA_TYPE 0x10 diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 339faa7a0a0e..29bdfd2a39bd 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -99,6 +99,7 @@ enum ieee80211_status_data { IEEE80211_STATUS_TYPE_INVALID = 0, IEEE80211_STATUS_TYPE_SMPS = 1, IEEE80211_STATUS_TYPE_NEG_TTLM = 2, + IEEE80211_STATUS_TYPE_UHR_OMP = 3, IEEE80211_STATUS_SUBDATA_MASK = 0x1ff0, }; @@ -412,6 +413,7 @@ enum ieee80211_conn_bw_limit { struct ieee80211_conn_settings { enum ieee80211_conn_mode mode; enum ieee80211_conn_bw_limit bw_limit; + bool dbe_enabled; }; extern const struct ieee80211_conn_settings ieee80211_conn_settings_unlimited; @@ -639,6 +641,15 @@ struct ieee80211_if_managed { bool enabled; u8 dialog_token; } epcs; + + struct { + struct wiphy_hrtimer_work status_work; + u32 timeout_us; + u16 links; + u16 pending, pending_init; + u8 dialog_token; + bool acked; + } uhr_omp; }; struct ieee80211_if_ibss { @@ -2790,6 +2801,7 @@ ieee80211_chanreq_downgrade(struct ieee80211_chan_req *chanreq, return; if (conn->mode < IEEE80211_CONN_MODE_EHT) chanreq->ap.chan = NULL; + conn->dbe_enabled = false; } bool ieee80211_chanreq_identical(const struct ieee80211_chan_req *a, @@ -2980,7 +2992,8 @@ void ieee80211_rearrange_tpe_psd(struct ieee80211_parsed_tpe_psd *psd, struct ieee802_11_elems * ieee80211_determine_chan_mode(struct ieee80211_sub_if_data *sdata, struct ieee80211_conn_settings *conn, - struct cfg80211_bss *cbss, int link_id, + struct cfg80211_bss *cbss, + struct link_sta_info *link_sta, int link_id, struct ieee80211_chan_req *chanreq, struct cfg80211_chan_def *ap_chandef, unsigned long *userspace_selectors); diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index 3553e39a7db4..9e92337bb6f9 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -171,14 +171,24 @@ struct ieee80211_determine_ap_chan_data { const struct ieee80211_conn_settings *conn; u32 vht_cap_info; bool ignore_ht_channel_mismatch; + const struct cfg80211_chan_def *cur_chandef; + bool cur_dbe_used; /* target chandef is filled in */ struct cfg80211_chan_def *chandef; }; +struct ieee80211_determine_ap_chan_output { + /* filled to indicate UHR DBE was used */ + bool dbe_used; + /* and need to know non-DBE width */ + enum nl80211_chan_width non_dbe_width; +}; + static enum ieee80211_conn_mode ieee80211_determine_ap_chan(struct ieee80211_sub_if_data *sdata, - struct ieee80211_determine_ap_chan_data *data) + const struct ieee80211_determine_ap_chan_data *data, + struct ieee80211_determine_ap_chan_output *out) { bool ignore_ht_channel_mismatch = data->ignore_ht_channel_mismatch; const struct ieee802_11_elems *elems = data->elems; @@ -196,6 +206,8 @@ ieee80211_determine_ap_chan(struct ieee80211_sub_if_data *sdata, bool no_vht = false; u32 ht_cfreq; + memset(out, 0, sizeof(*out)); + if (ieee80211_hw_check(&sdata->local->hw, STRICT)) ignore_ht_channel_mismatch = false; @@ -396,6 +408,8 @@ check_uhr: struct cfg80211_chan_def npca_chandef = *chandef; const struct ieee80211_sta_uhr_cap *uhr_cap; const struct ieee80211_uhr_npca_info *npca; + const struct ieee80211_uhr_dbe_info *dbe; + struct cfg80211_chan_def dbe_chandef; /* frames other than beacons carry UHR capability too */ if (!elems->uhr_cap) @@ -427,6 +441,96 @@ check_uhr: if (uhr_cap->mac.mac_cap[0] & IEEE80211_UHR_MAC_CAP0_NPCA_SUPP) *chandef = npca_chandef; + + dbe = ieee80211_uhr_oper_dbe_info(uhr_oper); + if (dbe) { + const struct ieee80211_uhr_cap_dbe *dbe_cap; + u8 dbe_bw_oper; + u8 dbe_bw_cap; + + dbe_cap = ieee80211_uhr_dbe_cap(elems->uhr_cap); + + if (!dbe_cap) { + sdata_info(sdata, + "AP without UHR DBE capability uses it, disabling UHR\n"); + return IEEE80211_CONN_MODE_EHT; + } + + dbe_bw_oper = u8_get_bits(dbe->params, + IEEE80211_UHR_DBE_OPER_BANDWIDTH); + + if (le16_get_bits(uhr_oper->params, + IEEE80211_UHR_OPER_PARAMS_DBE_BW) != dbe_bw_oper) { + sdata_info(sdata, + "AP UHR DBE settings mismatch, disabling UHR\n"); + return IEEE80211_CONN_MODE_EHT; + } + + if (ieee80211_uhr_dbe_bw_mhz(dbe_bw_oper) < 0) { + sdata_info(sdata, + "AP UHR DBE bandwidth invalid, disabling UHR\n"); + return IEEE80211_CONN_MODE_EHT; + } + + dbe_bw_cap = u8_get_bits(dbe_cap->cap, + IEEE80211_UHR_MAC_CAP_DBE_MAX_BW); + + switch (dbe_bw_cap) { + case IEEE80211_UHR_DBE_MAX_BW_40: + case IEEE80211_UHR_DBE_MAX_BW_80: + case IEEE80211_UHR_DBE_MAX_BW_160: + case IEEE80211_UHR_DBE_MAX_BW_320: + break; + default: + sdata_info(sdata, + "AP UHR DBE capability invalid, disabling UHR\n"); + return IEEE80211_CONN_MODE_EHT; + } + + /* 1-4 are same in DBE capabilities, map 320-2 to 320 */ + if (dbe_bw_oper == IEEE80211_UHR_DBE_OPER_BW_320_2) + dbe_bw_oper = IEEE80211_UHR_DBE_MAX_BW_320; + if (dbe_bw_oper > dbe_bw_cap) { + sdata_info(sdata, + "AP UHR DBE wider than capability, disabling UHR\n"); + return IEEE80211_CONN_MODE_EHT; + } + } + + dbe_chandef = *chandef; + + if (cfg80211_chandef_add_dbe(&dbe_chandef, dbe)) { + sdata_info(sdata, + "AP UHR DBE settings invalid, disabling UHR\n"); + return IEEE80211_CONN_MODE_EHT; + } + + if (dbe && + /* maybe driver would like to never use DBE */ + uhr_cap->mac.mac_cap[1] & IEEE80211_UHR_MAC_CAP1_DBE_SUPP && + ieee80211_chandef_usable(sdata, &dbe_chandef, + IEEE80211_CHAN_DISABLED)) { + out->non_dbe_width = chandef->width; + *chandef = dbe_chandef; + out->dbe_used = true; + } + } else if (data->cur_chandef && data->cur_dbe_used && + cfg80211_chandef_compatible(chandef, data->cur_chandef)) { + u8 dbe_bw = le16_get_bits(uhr_oper->params, + IEEE80211_UHR_OPER_PARAMS_DBE_BW); + int dbe_bw_mhz; + + dbe_bw_mhz = ieee80211_uhr_dbe_bw_mhz(dbe_bw); + if (dbe_bw_mhz < 0) { + sdata_info(sdata, + "AP UHR DBE bandwidth invalid, drop UHR\n"); + return IEEE80211_CONN_MODE_EHT; + } + + if (cfg80211_chandef_get_width(data->cur_chandef) == dbe_bw_mhz) { + *chandef = *data->cur_chandef; + out->dbe_used = true; + } } return IEEE80211_CONN_MODE_UHR; @@ -1069,7 +1173,8 @@ static void ieee80211_set_chanreq_ap(struct ieee80211_sub_if_data *sdata, VISIBLE_IF_MAC80211_KUNIT struct ieee802_11_elems * ieee80211_determine_chan_mode(struct ieee80211_sub_if_data *sdata, struct ieee80211_conn_settings *conn, - struct cfg80211_bss *cbss, int link_id, + struct cfg80211_bss *cbss, + struct link_sta_info *link_sta, int link_id, struct ieee80211_chan_req *chanreq, struct cfg80211_chan_def *ap_chandef, unsigned long *userspace_selectors) @@ -1099,6 +1204,7 @@ ieee80211_determine_chan_mode(struct ieee80211_sub_if_data *sdata, enum ieee80211_conn_mode ap_mode; unsigned long unknown_rates_selectors[BITS_TO_LONGS(128)] = {}; unsigned long sta_selectors[BITS_TO_LONGS(128)] = {}; + struct ieee80211_determine_ap_chan_output ap_chan_out; struct ieee80211_determine_ap_chan_data ap_chan_data = { .channel = channel, .vht_cap_info = bss->vht_cap_info, @@ -1115,7 +1221,9 @@ again: return ERR_PTR(-ENOMEM); ap_chan_data.elems = elems; - ap_mode = ieee80211_determine_ap_chan(sdata, &ap_chan_data); + ap_mode = ieee80211_determine_ap_chan(sdata, &ap_chan_data, + &ap_chan_out); + conn->dbe_enabled = ap_chan_out.dbe_used; /* this should be impossible since parsing depends on our mode */ if (WARN_ON(ap_mode > conn->mode)) { @@ -1331,6 +1439,10 @@ again: goto free; } + if (conn->dbe_enabled && link_sta) + link_sta->uhr_usable_tx_width = + ieee80211_chan_width_to_rx_bw(ap_chan_out.non_dbe_width); + return elems; free: kfree(elems); @@ -1338,12 +1450,152 @@ free: } EXPORT_SYMBOL_IF_MAC80211_KUNIT(ieee80211_determine_chan_mode); +static void ieee80211_send_uhr_omp_req_dbe(struct ieee80211_sub_if_data *sdata, + u16 link_mask, bool initial) +{ + struct ieee80211_mle_basic_common_info *common; + struct ieee80211_mle_per_sta_profile *per_sta; + struct ieee80211_uhr_mode_change_tuple *tuple; + struct ieee80211_local *local = sdata->local; + struct ieee80211_multi_link_elem *mle; + struct ieee80211_link_data *link; + struct ieee80211_tx_info *info; + struct ieee80211_mgmt *mgmt; + struct sk_buff *skb; + u8 *ml_elem_len; + size_t size; + + if (initial) { + bool enabled = false; + + for_each_link_data(sdata, link) { + if (!(link_mask & BIT(link->link_id))) + continue; + if (link->u.mgd.conn.dbe_enabled) { + enabled = true; + break; + } + } + + if (!enabled) + return; + } + + if (sdata->u.mgd.uhr_omp.links) { + if (initial) + sdata->u.mgd.uhr_omp.pending_init |= link_mask; + else + sdata->u.mgd.uhr_omp.pending |= link_mask; + return; + } + + size = local->hw.extra_tx_headroom + + IEEE80211_MIN_ACTION_SIZE(uhr_link_reconf_req) + + 3 + sizeof(*mle) + sizeof(*common) + + IEEE80211_MLD_MAX_NUM_LINKS * + (2 + sizeof(*per_sta) + + 3 + sizeof(*tuple) /* single tuple for each link */); + + skb = alloc_skb(size, GFP_KERNEL); + if (!skb) + return; + + skb_reserve(skb, local->hw.extra_tx_headroom); + + mgmt = skb_put_zero(skb, IEEE80211_MIN_ACTION_SIZE(uhr_link_reconf_req)); + mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | + IEEE80211_STYPE_ACTION); + memcpy(mgmt->da, sdata->vif.cfg.ap_addr, ETH_ALEN); + memcpy(mgmt->sa, sdata->vif.addr, ETH_ALEN); + memcpy(mgmt->bssid, sdata->vif.cfg.ap_addr, ETH_ALEN); + + mgmt->u.action.category = WLAN_CATEGORY_PROTECTED_UHR; + mgmt->u.action.action_code = + IEEE80211_PROTECTED_UHR_ACTION_LINK_RECONFIG_REQUEST; + + sdata->u.mgd.dialog_token_alloc++; + /* + * NOTE: + * Driver and FW might both send these frames, and iwlwifi + * decided that the driver uses odd numbers, FW uses even + * numbers. For now, hardcode that here, until it matters + * to some other driver. + * + * Note also that there's currently no real synchronisation + * in this case, but it's not valid that both send such a + * frame at the same time, i.e. while waiting for a response + * there can't be another frame sent. This needs addressing + * in the future. + */ + if (sdata->u.mgd.dialog_token_alloc % 2 == 0) + sdata->u.mgd.dialog_token_alloc++; + + sdata->u.mgd.uhr_omp.dialog_token = sdata->u.mgd.dialog_token_alloc; + mgmt->u.action.uhr_link_reconf_req.dialog_token = + sdata->u.mgd.uhr_omp.dialog_token; + mgmt->u.action.uhr_link_reconf_req.type = + IEEE80211_UHR_LINK_RECONFIG_REQUEST_OMP_REQUEST; + + skb_put_u8(skb, WLAN_EID_EXTENSION); + ml_elem_len = skb_put(skb, 1); + skb_put_u8(skb, WLAN_EID_EXT_EHT_MULTI_LINK); + mle = skb_put_zero(skb, sizeof(*mle)); + mle->control = cpu_to_le16(IEEE80211_ML_CONTROL_TYPE_RECONF | + IEEE80211_MLC_RECONF_PRES_MLD_MAC_ADDR); + + common = skb_put(skb, sizeof(*common)); + common->len = sizeof(*common); + memcpy(common->mld_mac_addr, sdata->vif.addr, ETH_ALEN); + + for_each_link_data(sdata, link) { + u8 *subelem_len; + + if (!(link_mask & BIT(link->link_id))) + continue; + + sdata->u.mgd.uhr_omp.links |= BIT(link->link_id); + + skb_put_u8(skb, IEEE80211_MLE_SUBELEM_PER_STA_PROFILE); + subelem_len = skb_put(skb, 1); + per_sta = skb_put_zero(skb, sizeof(*per_sta)); + per_sta->control = + le16_encode_bits(link->link_id, + IEEE80211_MLE_STA_CONTROL_LINK_ID) | + le16_encode_bits(IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE_UHR_OMP_UPD, + IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE); + per_sta->sta_info_len = 1; /* includes itself */ + + skb_put_u8(skb, WLAN_EID_EXTENSION); + skb_put_u8(skb, 1 + sizeof(*tuple)); + skb_put_u8(skb, WLAN_EID_EXT_UHR_MODE_CHG); + tuple = skb_put_zero(skb, sizeof(*tuple)); + tuple->control = + le16_encode_bits(IEEE80211_UHR_MODE_CHANGE_MODE_ID_DBE, + IEEE80211_UHR_MODE_CHANGE_CONTROL_MODE_ID); + + if (link->u.mgd.conn.dbe_enabled) + tuple->control |= + cpu_to_le16(IEEE80211_UHR_MODE_CHANGE_CONTROL_MODE_ENABLE); + + ieee80211_fragment_element(skb, subelem_len, + IEEE80211_MLE_SUBELEM_FRAGMENT); + } + + ieee80211_fragment_element(skb, ml_elem_len, WLAN_EID_FRAGMENT); + + info = IEEE80211_SKB_CB(skb); + info->flags |= IEEE80211_TX_CTL_REQ_TX_STATUS; + info->status_data = IEEE80211_STATUS_TYPE_UHR_OMP; + ieee80211_tx_skb(sdata, skb); +} + static int ieee80211_config_bw(struct ieee80211_link_data *link, struct ieee802_11_elems *elems, bool update, u64 *changed, u16 stype) { struct ieee80211_channel *channel = link->conf->chanreq.oper.chan; struct cfg80211_chan_def ap_chandef; + struct ieee80211_determine_ap_chan_output ap_chan_out; struct ieee80211_determine_ap_chan_data ap_chan_data = { .channel = channel, .vht_cap_info = 0, @@ -1351,6 +1603,8 @@ static int ieee80211_config_bw(struct ieee80211_link_data *link, .chandef = &ap_chandef, .elems = elems, .conn = &link->u.mgd.conn, + .cur_chandef = &link->conf->chanreq.oper, + .cur_dbe_used = link->u.mgd.conn.dbe_enabled, }; struct ieee80211_sub_if_data *sdata = link->sdata; struct ieee80211_chanctx_conf *chanctx_conf; @@ -1387,7 +1641,9 @@ static int ieee80211_config_bw(struct ieee80211_link_data *link, ap_chan_data.vht_cap_info = le32_to_cpu(elems->vht_cap_elem->vht_cap_info); - ap_mode = ieee80211_determine_ap_chan(sdata, &ap_chan_data); + ap_mode = ieee80211_determine_ap_chan(sdata, &ap_chan_data, + &ap_chan_out); + link->u.mgd.conn.dbe_enabled = ap_chan_out.dbe_used; if (ap_mode != link->u.mgd.conn.mode) { link_info(link, @@ -2652,6 +2908,7 @@ static void ieee80211_csa_switch_work(struct wiphy *wiphy, cfg80211_ch_switch_notify(sdata->dev, &link->csa.chanreq.oper, link->link_id); link->conf->csa_active = false; + link->u.mgd.conn.dbe_enabled = false; ap_sta = sta_info_get(sdata, sdata->vif.cfg.ap_addr); if (WARN_ON(!ap_sta)) @@ -2662,6 +2919,12 @@ static void ieee80211_csa_switch_work(struct wiphy *wiphy, if (WARN_ON(!link_sta)) return; + /* + * If the link was somehow deactivated in the middle of enabling + * DBE while waiting for a response, this could be stuck, reset. + */ + link_sta->uhr_usable_tx_width = IEEE80211_STA_RX_BW_MAX; + link_sta->pub->bandwidth = ieee80211_sta_current_bw(link_sta, &link->csa.chanreq.oper, @@ -2754,6 +3017,8 @@ static void ieee80211_chswitch_post_beacon(struct ieee80211_link_data *link) { struct ieee80211_sub_if_data *sdata = link->sdata; struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; + struct link_sta_info *link_sta; + struct sta_info *ap_sta; int ret; lockdep_assert_wiphy(sdata->local->hw.wiphy); @@ -2763,6 +3028,7 @@ static void ieee80211_chswitch_post_beacon(struct ieee80211_link_data *link) ieee80211_vif_unblock_queues_csa(sdata); link->conf->csa_active = false; + link->u.mgd.conn.dbe_enabled = false; link->u.mgd.csa.blocked_tx = false; link->u.mgd.csa.waiting_bcn = false; @@ -2775,6 +3041,20 @@ static void ieee80211_chswitch_post_beacon(struct ieee80211_link_data *link) return; } + ap_sta = sta_info_get(sdata, sdata->vif.cfg.ap_addr); + if (WARN_ON(!ap_sta)) + return; + + link_sta = sdata_dereference(ap_sta->link[link->link_id], sdata); + if (WARN_ON(!link_sta)) + return; + + /* + * If DBE was being activated and CSA happened, this could be + * on a wrong value. Reset it. + */ + link_sta->uhr_usable_tx_width = IEEE80211_STA_RX_BW_MAX; + cfg80211_ch_switch_notify(sdata->dev, &link->conf->chanreq.oper, link->link_id); } @@ -4479,6 +4759,9 @@ static void ieee80211_set_disassoc(struct ieee80211_sub_if_data *sdata, memset(ifmgd->userspace_selectors, 0, sizeof(ifmgd->userspace_selectors)); + + ifmgd->uhr_omp.pending = 0; + ifmgd->uhr_omp.pending_init = 0; } static void ieee80211_reset_ap_probe(struct ieee80211_sub_if_data *sdata) @@ -4799,6 +5082,101 @@ static void __ieee80211_disconnect(struct ieee80211_sub_if_data *sdata) ifmgd->reconnect = false; } +static void ieee80211_uhr_omp_req_status(struct ieee80211_sub_if_data *sdata) +{ + bool acked = sdata->u.mgd.uhr_omp.acked; + u16 links = sdata->u.mgd.uhr_omp.links; + struct ieee80211_link_data *link; + struct sta_info *ap; + + /* timer and queued RX could overlap */ + if (!links) + return; + + sdata->u.mgd.uhr_omp.links = 0; + sdata->u.mgd.uhr_omp.acked = false; + + if (!acked) { + sdata_dbg(sdata, "UHR OMP frame not ACKed - disconnect\n"); + __ieee80211_disconnect(sdata); + return; + } + + ap = sta_info_get(sdata, sdata->vif.cfg.ap_addr); + if (!ap) + return; + + for_each_link_data(sdata, link) { + struct link_sta_info *link_sta; + + if (!(links & BIT(link->link_id))) + continue; + + /* only handle transition to enabled for now */ + if (!link->u.mgd.conn.dbe_enabled) + continue; + + link_sta = sdata_dereference(ap->link[link->link_id], sdata); + if (WARN_ON(!link_sta)) + continue; + + link_sta->uhr_usable_tx_width = IEEE80211_STA_RX_BW_MAX; + ieee80211_link_sta_update_rc_bw(link, link_sta); + } + + /* next round - send pending frames if needed */ + + if (sdata->u.mgd.uhr_omp.pending_init) { + links = sdata->u.mgd.uhr_omp.pending_init; + + sdata->u.mgd.uhr_omp.pending_init = 0; + ieee80211_send_uhr_omp_req_dbe(sdata, links, true); + return; + } + + if (sdata->u.mgd.uhr_omp.pending) { + links = sdata->u.mgd.uhr_omp.pending; + + sdata->u.mgd.uhr_omp.pending = 0; + ieee80211_send_uhr_omp_req_dbe(sdata, links, false); + return; + } +} + +static void ieee80211_uhr_omp_req_status_wk(struct wiphy *wiphy, + struct wiphy_work *work) +{ + struct ieee80211_sub_if_data *sdata = + container_of(work, struct ieee80211_sub_if_data, + u.mgd.uhr_omp.status_work.work); + + ieee80211_uhr_omp_req_status(sdata); +} + +static void ieee80211_process_uhr_omp_resp(struct ieee80211_sub_if_data *sdata, + struct ieee80211_mgmt *mgmt) +{ + if (mgmt->u.action.uhr_link_reconf_notif.dialog_token != + sdata->u.mgd.uhr_omp.dialog_token) + return; + + wiphy_hrtimer_work_cancel(sdata->local->hw.wiphy, + &sdata->u.mgd.uhr_omp.status_work); + ieee80211_uhr_omp_req_status(sdata); +} + +static void +ieee80211_process_uhr_link_reconf_notif(struct ieee80211_sub_if_data *sdata, + struct ieee80211_mgmt *mgmt, + size_t len) +{ + switch (mgmt->u.action.uhr_link_reconf_notif.type) { + case IEEE80211_UHR_LINK_RECONFIG_NOTIFY_OMP_RESPONSE: + ieee80211_process_uhr_omp_resp(sdata, mgmt); + break; + } +} + static void ieee80211_beacon_connection_loss_work(struct wiphy *wiphy, struct wiphy_work *work) { @@ -5777,12 +6155,27 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link, if (elems->uhr_operation && elems->uhr_cap && link->u.mgd.conn.mode >= IEEE80211_CONN_MODE_UHR) { + int omp_to_us; + ieee80211_uhr_cap_ie_to_sta_uhr_cap(sdata, sband, elems->uhr_cap, elems->uhr_cap_len, link_sta); bss_conf->uhr_support = link_sta->pub->uhr_cap.has_uhr; + + /* + * This assumes that the timeout is the same across all links, + * maybe we should actually validate that. + */ + omp_to_us = ieee80211_uhr_capa_get_om_pu_to_us(elems->uhr_cap); + if (omp_to_us < 0) { + ret = false; + link_info(link, "Invalid UHR OMP timeout\n"); + goto out; + } + + sdata->u.mgd.uhr_omp.timeout_us = omp_to_us; } else { bss_conf->uhr_support = false; } @@ -6312,6 +6705,7 @@ ieee80211_determine_our_sta_mode_assoc(struct ieee80211_sub_if_data *sdata, static int ieee80211_prep_channel(struct ieee80211_sub_if_data *sdata, struct ieee80211_link_data *link, + struct link_sta_info *link_sta, int link_id, struct cfg80211_bss *cbss, bool mlo, struct ieee80211_conn_settings *conn, @@ -6327,7 +6721,8 @@ static int ieee80211_prep_channel(struct ieee80211_sub_if_data *sdata, lockdep_assert_wiphy(local->hw.wiphy); rcu_read_lock(); - elems = ieee80211_determine_chan_mode(sdata, conn, cbss, link_id, + elems = ieee80211_determine_chan_mode(sdata, conn, cbss, + link_sta, link_id, &chanreq, &ap_chandef, userspace_selectors); @@ -6647,7 +7042,8 @@ static bool ieee80211_assoc_success(struct ieee80211_sub_if_data *sdata, if (link_id != assoc_data->assoc_link_id) { link->u.mgd.conn = assoc_data->link[link_id].conn; - err = ieee80211_prep_channel(sdata, link, link_id, cbss, + err = ieee80211_prep_channel(sdata, link, link_sta, + link_id, cbss, true, &link->u.mgd.conn, sdata->u.mgd.userspace_selectors); if (err) { @@ -6728,6 +7124,8 @@ static bool ieee80211_assoc_success(struct ieee80211_sub_if_data *sdata, ieee80211_sta_reset_beacon_monitor(sdata); ieee80211_sta_reset_conn_monitor(sdata); + ieee80211_send_uhr_omp_req_dbe(sdata, ~0, true); + return true; out_err: eth_zero_addr(sdata->vif.cfg.ap_addr); @@ -9067,6 +9465,8 @@ void ieee80211_sta_setup_sdata(struct ieee80211_sub_if_data *sdata) ieee80211_neg_ttlm_timeout_work); wiphy_work_init(&ifmgd->teardown_ttlm_work, ieee80211_teardown_ttlm_work); + wiphy_hrtimer_work_init(&ifmgd->uhr_omp.status_work, + ieee80211_uhr_omp_req_status_wk); ifmgd->flags = 0; ifmgd->powersave = sdata->wdev.ps; @@ -9281,6 +9681,9 @@ static int ieee80211_prep_connection(struct ieee80211_sub_if_data *sdata, } if (new_sta || override) { + struct link_sta_info *link_sta; + struct sta_info *ap; + /* * Only set this if we're also going to calculate the AP * settings etc., otherwise this was set before in a @@ -9288,7 +9691,18 @@ static int ieee80211_prep_connection(struct ieee80211_sub_if_data *sdata, * if the settings were changed. */ link->u.mgd.conn = *conn; - err = ieee80211_prep_channel(sdata, link, link->link_id, cbss, + + ap = new_sta ?: have_sta; + link_sta = sdata_dereference(ap->link[link->link_id], sdata); + if (!link_sta) { + err = -EINVAL; + if (new_sta) + sta_info_free(local, new_sta); + goto out_err; + } + + err = ieee80211_prep_channel(sdata, link, link_sta, + link->link_id, cbss, mlo, &link->u.mgd.conn, userspace_selectors); if (err) { @@ -10183,8 +10597,8 @@ int ieee80211_mgd_assoc(struct ieee80211_sub_if_data *sdata, continue; if (i == assoc_data->assoc_link_id) continue; - /* only calculate the mode, hence link == NULL */ - err = ieee80211_prep_channel(sdata, NULL, i, + /* only calculate the mode, hence link/link_sta == NULL */ + err = ieee80211_prep_channel(sdata, NULL, NULL, i, assoc_data->link[i].bss, true, &assoc_data->link[i].conn, sdata->u.mgd.userspace_selectors); @@ -10367,6 +10781,8 @@ void ieee80211_mgd_stop(struct ieee80211_sub_if_data *sdata) &ifmgd->csa_connection_drop_work); wiphy_delayed_work_cancel(sdata->local->hw.wiphy, &ifmgd->tdls_peer_del_work); + wiphy_hrtimer_work_cancel(sdata->local->hw.wiphy, + &ifmgd->uhr_omp.status_work); if (ifmgd->assoc_data) ieee80211_destroy_assoc_data(sdata, ASSOC_TIMEOUT); @@ -10613,7 +11029,7 @@ ieee80211_process_ml_reconf_resp(struct ieee80211_sub_if_data *sdata, link->conf->dtim_period = link->u.mgd.dtim_period ?: 1; link->u.mgd.conn = add_links_data->link[link_id].conn; - if (ieee80211_prep_channel(sdata, link, link_id, cbss, + if (ieee80211_prep_channel(sdata, link, link_sta, link_id, cbss, true, &link->u.mgd.conn, sdata->u.mgd.userspace_selectors)) { link_info(link, "mlo: reconf: prep_channel failed\n"); @@ -10658,6 +11074,8 @@ ieee80211_process_ml_reconf_resp(struct ieee80211_sub_if_data *sdata, ieee80211_recalc_ps(local); ieee80211_recalc_ps_vif(sdata); + ieee80211_send_uhr_omp_req_dbe(sdata, link_mask, true); + done_data.buf = (const u8 *)mgmt; done_data.len = orig_len; done_data.added_links = link_mask; @@ -11028,7 +11446,7 @@ int ieee80211_mgd_assoc_ml_reconf(struct ieee80211_sub_if_data *sdata, continue; /* only used to verify the mode, nothing is allocated */ - err = ieee80211_prep_channel(sdata, NULL, link_id, + err = ieee80211_prep_channel(sdata, NULL, NULL, link_id, data->link[link_id].bss, true, &data->link[link_id].conn, @@ -11490,6 +11908,15 @@ void ieee80211_sta_rx_queued_frame(struct ieee80211_sub_if_data *sdata, break; } break; + case WLAN_CATEGORY_PROTECTED_UHR: + switch (mgmt->u.action.action_code) { + case IEEE80211_PROTECTED_UHR_ACTION_LINK_RECONFIG_NOTIFY: + ieee80211_process_uhr_link_reconf_notif(sdata, + mgmt, + skb->len); + break; + } + break; } break; } diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c index 91b4f6cbfce8..858c3f659119 100644 --- a/net/mac80211/rx.c +++ b/net/mac80211/rx.c @@ -3947,6 +3947,20 @@ ieee80211_rx_h_action(struct ieee80211_rx_data *rx) break; } break; + case WLAN_CATEGORY_PROTECTED_UHR: + if (len < IEEE80211_MIN_ACTION_SIZE(action_code)) + break; + + switch (mgmt->u.action.action_code) { + case IEEE80211_PROTECTED_UHR_ACTION_LINK_RECONFIG_NOTIFY: + if (sdata->vif.type != NL80211_IFTYPE_STATION) + break; + + if (len < IEEE80211_MIN_ACTION_SIZE(uhr_link_reconf_notif)) + goto invalid; + goto queue; + } + break; } return RX_CONTINUE; diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c index 205e8d5c0c72..2787be556034 100644 --- a/net/mac80211/sta_info.c +++ b/net/mac80211/sta_info.c @@ -572,6 +572,20 @@ static int sta_info_alloc_link(struct ieee80211_local *local, link_info->rx_omi_bw_tx = IEEE80211_STA_RX_BW_MAX; link_info->rx_omi_bw_staging = IEEE80211_STA_RX_BW_MAX; + /* + * This will always be taken into account, so set to MAX. + * When mac80211 is the client on a UHR AP, it'll be used + * for the TX side, to limit the bandwidth to TX to the AP + * with, to limit to the BSS width during DBE enablement. + * + * This is needed since the chanreq, which normally has + * maximum bandwidth to use with the AP, will already be + * set to the DBE width during enablement to prepare for + * RX (and not be racy), but the TX can only use higher + * bandwidth after enablement finishes. + */ + link_info->uhr_usable_tx_width = IEEE80211_STA_RX_BW_MAX; + link_info->op_mode_bw = IEEE80211_STA_RX_BW_MAX; /* @@ -3727,6 +3741,8 @@ ieee80211_sta_current_bw_tx_to_sta(struct link_sta_info *link_sta, bw = min(bw, link_sta->op_mode_bw); /* also limit to RX OMI bandwidth we TX to the STA */ bw = min(bw, link_sta->rx_omi_bw_tx); + /* and UHR DBE transition limits */ + bw = min(bw, link_sta->uhr_usable_tx_width); /* Don't consider AP's bandwidth for TDLS peers, section 11.23.1 of * IEEE80211-2016 specification makes higher bandwidth operation diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h index 3abac031d01c..0f56a6e53629 100644 --- a/net/mac80211/sta_info.h +++ b/net/mac80211/sta_info.h @@ -511,6 +511,9 @@ struct ieee80211_fragment_cache { * @rx_omi_bw_tx: RX OMI bandwidth restriction to apply for TX * @rx_omi_bw_staging: RX OMI bandwidth restriction to apply later * during finalize + * @uhr_usable_tx_width: bandwidth restriction for UHR for TX, only when + * the link_sta is an AP, to restrict TX to BSS width during DBE + * enablement * @debugfs_dir: debug filesystem directory dentry * @pub: public (driver visible) link STA data */ @@ -562,6 +565,7 @@ struct link_sta_info { enum ieee80211_sta_rx_bandwidth rx_omi_bw_rx, rx_omi_bw_tx, rx_omi_bw_staging; + enum ieee80211_sta_rx_bandwidth uhr_usable_tx_width; #ifdef CONFIG_MAC80211_DEBUGFS struct dentry *debugfs_dir; diff --git a/net/mac80211/status.c b/net/mac80211/status.c index 8716eda8317d..dd1dbba06838 100644 --- a/net/mac80211/status.c +++ b/net/mac80211/status.c @@ -731,6 +731,28 @@ ieee80211_handle_teardown_ttlm_status(struct ieee80211_sub_if_data *sdata, &sdata->u.mgd.teardown_ttlm_work); } +static void +ieee80211_handle_uhr_omp_status(struct ieee80211_sub_if_data *sdata, bool acked) +{ + if (!sdata || !ieee80211_sdata_running(sdata)) + return; + + if (sdata->vif.type != NL80211_IFTYPE_STATION) + return; + + sdata->u.mgd.uhr_omp.acked = acked; + + if (!acked) { + wiphy_hrtimer_work_queue(sdata->local->hw.wiphy, + &sdata->u.mgd.uhr_omp.status_work, 0); + return; + } + + wiphy_hrtimer_work_queue(sdata->local->hw.wiphy, + &sdata->u.mgd.uhr_omp.status_work, + us_to_ktime(sdata->u.mgd.uhr_omp.timeout_us)); +} + static void ieee80211_report_used_skb(struct ieee80211_local *local, struct sk_buff *skb, bool dropped, ktime_t ack_hwtstamp) @@ -811,6 +833,9 @@ static void ieee80211_report_used_skb(struct ieee80211_local *local, case IEEE80211_STATUS_TYPE_NEG_TTLM: ieee80211_handle_teardown_ttlm_status(sdata, acked); break; + case IEEE80211_STATUS_TYPE_UHR_OMP: + ieee80211_handle_uhr_omp_status(sdata, acked); + break; } rcu_read_unlock(); } diff --git a/net/mac80211/tests/chan-mode.c b/net/mac80211/tests/chan-mode.c index fa370831d617..ab7d38ef6a3a 100644 --- a/net/mac80211/tests/chan-mode.c +++ b/net/mac80211/tests/chan-mode.c @@ -2,7 +2,7 @@ /* * KUnit tests for channel mode functions * - * Copyright (C) 2024-2025 Intel Corporation + * Copyright (C) 2024-2026 Intel Corporation */ #include #include @@ -243,7 +243,7 @@ static void test_determine_chan_mode(struct kunit *test) rcu_read_lock(); elems = ieee80211_determine_chan_mode(t_sdata->sdata, &conn, &cbss, - 0, &chanreq, &ap_chandef, + NULL, 0, &chanreq, &ap_chandef, userspace_selectors); rcu_read_unlock(); -- cgit v1.2.3 From 14b1a85518a17c01a8d52a8d3332596b68b4f201 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 29 May 2026 10:25:08 +0200 Subject: wifi: mac80211: AP: handle DBE for clients In AP mode, track the BSS non-DBE bandwidth and apply that to all non-DBE clients, then track OMP updates from the clients and enable/disable DBE accordingly. For now don't send a response, clients need to have a timer anyway (it's up to the driver to set the right timeout in UHR capabilities.) Link: https://patch.msgid.link/20260529102644.be84f2b055cc.I4d2c067dfe54c47621d5a872ca07a0e754d6c20f@changeid Signed-off-by: Johannes Berg --- include/linux/ieee80211-eht.h | 16 ++++- include/linux/ieee80211-uhr.h | 16 +++++ net/mac80211/ap.c | 146 ++++++++++++++++++++++++++++++++++++++++++ net/mac80211/cfg.c | 35 ++++++++++ net/mac80211/ieee80211_i.h | 2 + net/mac80211/rx.c | 9 +++ net/mac80211/sta_info.c | 10 ++- net/mac80211/sta_info.h | 4 ++ 8 files changed, 232 insertions(+), 6 deletions(-) (limited to 'include') diff --git a/include/linux/ieee80211-eht.h b/include/linux/ieee80211-eht.h index 73e97fe30724..18f9c662cf4c 100644 --- a/include/linux/ieee80211-eht.h +++ b/include/linux/ieee80211-eht.h @@ -393,14 +393,24 @@ ieee80211_eht_oper_size_ok(const u8 *data, u8 len) return len >= needed; } +/* must validate ieee80211_eht_oper_size_ok() first */ +static inline const struct ieee80211_eht_operation_info * +ieee80211_eht_oper_info(const struct ieee80211_eht_operation *eht_oper) +{ + if (!(eht_oper->params & IEEE80211_EHT_OPER_INFO_PRESENT)) + return NULL; + + return (const void *)eht_oper->optional; +} + /* must validate ieee80211_eht_oper_size_ok() first */ static inline u16 ieee80211_eht_oper_dis_subchan_bitmap(const struct ieee80211_eht_operation *eht_oper) { - const struct ieee80211_eht_operation_info *info = - (const void *)eht_oper->optional; + const struct ieee80211_eht_operation_info *info; - if (!(eht_oper->params & IEEE80211_EHT_OPER_INFO_PRESENT)) + info = ieee80211_eht_oper_info(eht_oper); + if (!info) return 0; if (!(eht_oper->params & IEEE80211_EHT_OPER_DISABLED_SUBCHANNEL_BITMAP_PRESENT)) diff --git a/include/linux/ieee80211-uhr.h b/include/linux/ieee80211-uhr.h index 1dd53c5f2954..597c9e559261 100644 --- a/include/linux/ieee80211-uhr.h +++ b/include/linux/ieee80211-uhr.h @@ -628,4 +628,20 @@ struct ieee80211_uhr_mode_change_tuple { u8 variable[]; } __packed; +static inline int +ieee80211_uhr_mode_change_tuple_size(const struct ieee80211_uhr_mode_change_tuple *tuple) +{ + return sizeof(*tuple) + + le16_get_bits(tuple->control, + IEEE80211_UHR_MODE_CHANGE_CONTROL_MODE_LENGTH); +} + +#define for_each_uhr_mode_change_tuple(data, len, tuple) \ + for (tuple = (const void *)(data); \ + (len) - ((const u8 *)tuple - (data)) >= sizeof(*tuple) && \ + (len) - ((const u8 *)tuple - (data)) >= \ + ieee80211_uhr_mode_change_tuple_size(tuple); \ + tuple = (const void *)((const u8 *)tuple + \ + ieee80211_uhr_mode_change_tuple_size(tuple))) + #endif /* LINUX_IEEE80211_UHR_H */ diff --git a/net/mac80211/ap.c b/net/mac80211/ap.c index 6c7d2d51a372..e7ac99c5a22e 100644 --- a/net/mac80211/ap.c +++ b/net/mac80211/ap.c @@ -8,6 +8,7 @@ #include "driver-ops.h" #include "ieee80211_i.h" +#include "rate.h" static void ieee80211_send_eml_op_mode_notif(struct ieee80211_sub_if_data *sdata, @@ -186,6 +187,113 @@ ieee80211_rx_eml_op_mode_notif(struct ieee80211_sub_if_data *sdata, ieee80211_send_eml_op_mode_notif(sdata, mgmt, opt_len); } +static void +ieee80211_rx_uhr_link_reconfig_req(struct ieee80211_sub_if_data *sdata, + struct sk_buff *skb) +{ + struct ieee80211_mgmt *mgmt = (void *)skb->data; + const struct element *sub; + struct sta_info *sta; + + /* + * rx.c only accepts IEEE80211_UHR_LINK_RECONFIG_REQUEST_OMP_REQUEST + * which is valid, so no need to check the frame type/format/etc. + */ + + sta = sta_info_get_bss(sdata, mgmt->sa); + if (!sta) + return; + + struct ieee802_11_elems *elems __free(kfree) = + ieee802_11_parse_elems(mgmt->u.action.uhr_link_reconf_req.variable, + skb->len - IEEE80211_MIN_ACTION_SIZE(uhr_link_reconf_req), + IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_ACTION, + NULL); + /* STA will assume we processed it, not good */ + if (!elems) + return; + + if (!elems->ml_reconf) + return; + + for_each_mle_subelement(sub, (u8 *)elems->ml_reconf, + elems->ml_reconf_len) { + const struct ieee80211_mle_per_sta_profile *prof = + (const void *)sub->data; + struct ieee80211_chanctx_conf *chanctx_conf; + struct ieee80211_chanctx *chanctx; + struct ieee80211_link_data *link; + struct link_sta_info *link_sta; + const struct element *chg; + u16 control; + u8 link_id; + + if (sub->id != IEEE80211_MLE_SUBELEM_PER_STA_PROFILE) + continue; + + if (!ieee80211_mle_reconf_sta_prof_size_ok(sub->data, + sub->datalen)) + return; + + control = le16_to_cpu(prof->control); + link_id = control & IEEE80211_MLE_STA_RECONF_CONTROL_LINK_ID; + + if (link_id >= IEEE80211_MLD_MAX_NUM_LINKS) + return; + + link = sdata_dereference(sdata->link[link_id], sdata); + if (!link) + continue; + + chanctx_conf = sdata_dereference(link->conf->chanctx_conf, + sdata); + if (!chanctx_conf) + continue; + chanctx = container_of(chanctx_conf, struct ieee80211_chanctx, + conf); + + link_sta = sdata_dereference(sta->link[link_id], sdata); + if (!link_sta) + continue; + + /* do we need to handle any other bits? */ + if (control & ~(IEEE80211_MLE_STA_RECONF_CONTROL_LINK_ID | + IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE)) + continue; + + if (u16_get_bits(control, IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE) != + IEEE80211_MLE_STA_RECONF_CONTROL_OPERATION_TYPE_UHR_OMP_UPD) + continue; + + for_each_element_extid(chg, WLAN_EID_EXT_UHR_MODE_CHG, + prof->variable + prof->sta_info_len - 1, + sub->datalen - sizeof(*prof) - + prof->sta_info_len + 1) { + const struct ieee80211_uhr_mode_change_tuple *tuple; + + for_each_uhr_mode_change_tuple(chg->data + 1, + chg->datalen - 1, + tuple) { + u8 id = le16_get_bits(tuple->control, + IEEE80211_UHR_MODE_CHANGE_CONTROL_MODE_ID); + bool enabled = le16_get_bits(tuple->control, + IEEE80211_UHR_MODE_CHANGE_CONTROL_MODE_ENABLE); + + /* only handle DBE (for now?) */ + if (id != IEEE80211_UHR_MODE_CHANGE_MODE_ID_DBE) + continue; + + link_sta->uhr_dbe_enabled = enabled; + /* also recalculates and updates per-STA bw */ + ieee80211_recalc_chanctx_min_def(sdata->local, + chanctx); + } + } + } + + /* TODO: send a response */ +} + void ieee80211_ap_rx_queued_frame(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb) { @@ -203,5 +311,43 @@ void ieee80211_ap_rx_queued_frame(struct ieee80211_sub_if_data *sdata, break; } break; + case WLAN_CATEGORY_PROTECTED_UHR: + switch (mgmt->u.action.action_code) { + case IEEE80211_PROTECTED_UHR_ACTION_LINK_RECONFIG_REQUEST: + ieee80211_rx_uhr_link_reconfig_req(sdata, skb); + break; + } + break; + } +} + +void ieee80211_uhr_disable_dbe_all_stas(struct ieee80211_link_data *link) +{ + struct ieee80211_sub_if_data *sdata = link->sdata; + struct ieee80211_local *local = sdata->local; + struct ieee80211_chanctx_conf *chanctx_conf; + struct ieee80211_chanctx *chanctx; + int link_id = link->link_id; + struct sta_info *sta; + + chanctx_conf = sdata_dereference(link->conf->chanctx_conf, sdata); + if (!chanctx_conf) + return; + chanctx = container_of(chanctx_conf, struct ieee80211_chanctx, conf); + + list_for_each_entry(sta, &local->sta_list, list) { + struct link_sta_info *link_sta; + + if (sta->sdata->bss != sdata->bss) + continue; + + link_sta = sdata_dereference(sta->link[link_id], sdata); + if (!link_sta) + continue; + + link_sta->uhr_dbe_enabled = false; } + + /* also recalculates and updates per-STA bw */ + ieee80211_recalc_chanctx_min_def(local, chanctx); } diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c index 1554e31f0029..3b58af59f7e4 100644 --- a/net/mac80211/cfg.c +++ b/net/mac80211/cfg.c @@ -1391,6 +1391,36 @@ ieee80211_calc_ap_he_and_lower(struct cfg80211_beacon_data *params) return IEEE80211_STA_RX_BW_20; } +static enum ieee80211_sta_rx_bandwidth +ieee80211_calc_ap_eht_bw(struct cfg80211_beacon_data *params, + enum ieee80211_sta_rx_bandwidth he_and_lower) +{ + const struct ieee80211_eht_operation_info *info; + + if (!params->eht_oper) + return he_and_lower; + + info = ieee80211_eht_oper_info(params->eht_oper); + if (!info) + return he_and_lower; + + switch (u8_get_bits(info->control, IEEE80211_EHT_OPER_CHAN_WIDTH)) { + case IEEE80211_EHT_OPER_CHAN_WIDTH_20MHZ: + return IEEE80211_STA_RX_BW_20; + case IEEE80211_EHT_OPER_CHAN_WIDTH_40MHZ: + return IEEE80211_STA_RX_BW_40; + case IEEE80211_EHT_OPER_CHAN_WIDTH_80MHZ: + return IEEE80211_STA_RX_BW_80; + case IEEE80211_EHT_OPER_CHAN_WIDTH_160MHZ: + return IEEE80211_STA_RX_BW_160; + case IEEE80211_EHT_OPER_CHAN_WIDTH_320MHZ: + return IEEE80211_STA_RX_BW_320; + } + + /* invalid setting, assume 20 MHz */ + return IEEE80211_STA_RX_BW_20; +} + static void ieee80211_update_ap_bandwidth(struct ieee80211_link_data *link, struct cfg80211_beacon_data *params) { @@ -1415,6 +1445,8 @@ static void ieee80211_update_ap_bandwidth(struct ieee80211_link_data *link, return; link->bss_bw.he_and_lower = ieee80211_calc_ap_he_and_lower(params); + link->bss_bw.eht = ieee80211_calc_ap_eht_bw(params, + link->bss_bw.he_and_lower); chanctx_conf = sdata_dereference(link->conf->chanctx_conf, link->sdata); chanctx = container_of(chanctx_conf, struct ieee80211_chanctx, conf); @@ -4469,6 +4501,9 @@ static int __ieee80211_csa_finalize(struct ieee80211_link_data *link_data) ieee80211_link_info_change_notify(sdata, link_data, changed); + if (sdata->vif.type == NL80211_IFTYPE_AP) + ieee80211_uhr_disable_dbe_all_stas(link_data); + ieee80211_vif_unblock_queues_csa(sdata); err = drv_post_channel_switch(link_data); diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 29bdfd2a39bd..34a9ea8b6f85 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -1146,6 +1146,7 @@ struct ieee80211_link_data { struct { enum ieee80211_sta_rx_bandwidth he_and_lower; + enum ieee80211_sta_rx_bandwidth eht; /* and UHR non-DBE */ } bss_bw; #ifdef CONFIG_MAC80211_DEBUGFS @@ -2003,6 +2004,7 @@ bool ieee80211_is_our_addr(struct ieee80211_sub_if_data *sdata, /* AP code */ void ieee80211_ap_rx_queued_frame(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb); +void ieee80211_uhr_disable_dbe_all_stas(struct ieee80211_link_data *link); /* STA code */ void ieee80211_sta_setup_sdata(struct ieee80211_sub_if_data *sdata); diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c index 858c3f659119..db421edfd54c 100644 --- a/net/mac80211/rx.c +++ b/net/mac80211/rx.c @@ -3952,6 +3952,15 @@ ieee80211_rx_h_action(struct ieee80211_rx_data *rx) break; switch (mgmt->u.action.action_code) { + case IEEE80211_PROTECTED_UHR_ACTION_LINK_RECONFIG_REQUEST: + if (sdata->vif.type != NL80211_IFTYPE_AP) + break; + if (len < IEEE80211_MIN_ACTION_SIZE(uhr_link_reconf_req)) + goto invalid; + if (mgmt->u.action.uhr_link_reconf_req.type != + IEEE80211_UHR_LINK_RECONFIG_REQUEST_OMP_REQUEST) + break; + goto queue; case IEEE80211_PROTECTED_UHR_ACTION_LINK_RECONFIG_NOTIFY: if (sdata->vif.type != NL80211_IFTYPE_STATION) break; diff --git a/net/mac80211/sta_info.c b/net/mac80211/sta_info.c index 2787be556034..02b587ff8504 100644 --- a/net/mac80211/sta_info.c +++ b/net/mac80211/sta_info.c @@ -3701,10 +3701,14 @@ ieee80211_sta_usable_bw(struct link_sta_info *link_sta, if (WARN_ON(!link)) return IEEE80211_STA_RX_BW_20; - if (link_sta->pub->eht_cap.has_eht) - return bw; + if (!link_sta->pub->eht_cap.has_eht) + return min(bw, link->bss_bw.he_and_lower); + + if (!link_sta->pub->uhr_cap.has_uhr || + !link_sta->uhr_dbe_enabled) + return min(bw, link->bss_bw.eht); - return min(bw, link->bss_bw.he_and_lower); + return bw; } static enum ieee80211_sta_rx_bandwidth diff --git a/net/mac80211/sta_info.h b/net/mac80211/sta_info.h index 0f56a6e53629..26138934b72e 100644 --- a/net/mac80211/sta_info.h +++ b/net/mac80211/sta_info.h @@ -514,6 +514,8 @@ struct ieee80211_fragment_cache { * @uhr_usable_tx_width: bandwidth restriction for UHR for TX, only when * the link_sta is an AP, to restrict TX to BSS width during DBE * enablement + * @uhr_dbe_enabled: for STAs as clients to an AP interface indicates + * DBE is enabled by the STA * @debugfs_dir: debug filesystem directory dentry * @pub: public (driver visible) link STA data */ @@ -567,6 +569,8 @@ struct link_sta_info { rx_omi_bw_staging; enum ieee80211_sta_rx_bandwidth uhr_usable_tx_width; + bool uhr_dbe_enabled; + #ifdef CONFIG_MAC80211_DEBUGFS struct dentry *debugfs_dir; #endif -- cgit v1.2.3 From afabbb56a7262979a75259e2710019aa8ced3e8a Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Tue, 2 Jun 2026 19:03:45 +0000 Subject: geneve: Introduce IFLA_GENEVE_LOCAL and IFLA_GENEVE_LOCAL6. By default, a GENEVE device bind()s its underlying UDP socket(s) to the IPv4 or IPv6 wildcard address because there is no way to specify a specific local IP address to bind() to. This prevents deploying multiple GENEVE devices on a multi-homed host where each device should be isolated and bound to a different local IP address on the same UDP port. Let's introduce new options, IFLA_GENEVE_LOCAL and IFLA_GENEVE_LOCAL6, to allow specifying a local IPv4/IPv6 address for the backend UDP socket. By default, when collect metadata mode (IFLA_GENEVE_COLLECT_METADATA) is enabled, both IPv4 and IPv6 sockets are created. However, if a source address is specified via the new attributes, only a single socket corresponding to that specific address family is created. Accordingly, geneve_find_sock() and geneve_find_dev() are updated to take the source address into account, ensuring that multiple devices and sockets configured with different source addresses can coexist without conflict. In addition, the source address is validated in geneve_xmit_skb() and geneve6_xmit_skb(), so the BPF prog must set it in bpf_tunnel_key. With this change, multiple GENEVE devices can be successfully created and bound to their respective local IP addresses: (*) "local" is the keyword for IFLA_GENEVE_LOCAL / IFLA_GENEVE_LOCAL6 # for i in $(seq 1 2); do ip link add geneve4_${i} type geneve local 192.168.0.${i} external ip addr add 192.168.0.${i}/24 dev geneve4_${i} ip link set geneve4_${i} up ip link add geneve6_${i} type geneve local 2001:9292::${i} external ip addr add 2001:9292::${i}/64 dev geneve6_${i} nodad ip link set geneve6_${i} up done # ip -d l | grep geneve 9: geneve4_1: ... geneve external id 0 local 192.168.0.1 ... 10: geneve6_1: ... geneve external id 0 local 2001:9292::1 ... 11: geneve4_2: ... geneve external id 0 local 192.168.0.2 ... 12: geneve6_2: ... geneve external id 0 local 2001:9292::2 ... # ss -ua | grep geneve UNCONN 0 0 192.168.0.2:geneve 0.0.0.0:* UNCONN 0 0 192.168.0.1:geneve 0.0.0.0:* UNCONN 0 0 [2001:9292::2]:geneve *:* UNCONN 0 0 [2001:9292::1]:geneve *:* Note that even if the local address is explicitly configured with the wildcard address, kernel does not dump it except for devices with IFLA_GENEVE_COLLECT_METADATA. This is consistent with the behaviour of is_tnl_info_zero(), which treats the wildcard remote address as not configured. ## ynl example. # ./tools/net/ynl/pyynl/cli.py \ --spec ./Documentation/netlink/specs/rt-link.yaml \ --do newlink --create \ --json '{"ifname": "geneve0", "linkinfo": {"kind":"geneve", "data": {"local": "0.0.0.0", "collect-metadata": true}}}' # ./tools/net/ynl/pyynl/cli.py \ --spec ./Documentation/netlink/specs/rt-link.yaml \ --do getlink \ --json '{"ifname": "geneve0"}' --output-json | \ jq .linkinfo.data.local "0.0.0.0" Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260602190436.139591-6-kuniyu@google.com Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/rt-link.yaml | 9 ++ drivers/net/geneve.c | 167 +++++++++++++++++++++++++++++-- include/uapi/linux/if_link.h | 2 + 3 files changed, 169 insertions(+), 9 deletions(-) (limited to 'include') diff --git a/Documentation/netlink/specs/rt-link.yaml b/Documentation/netlink/specs/rt-link.yaml index a5c0297818c6..7f8e3ad3a405 100644 --- a/Documentation/netlink/specs/rt-link.yaml +++ b/Documentation/netlink/specs/rt-link.yaml @@ -1941,6 +1941,15 @@ attribute-sets: - name: gro-hint type: flag + - + name: local + type: u32 + byte-order: big-endian + display-hint: ipv4 + - + name: local6 + type: binary + display-hint: ipv6 - name: linkinfo-hsr-attrs name-prefix: ifla-hsr- diff --git a/drivers/net/geneve.c b/drivers/net/geneve.c index 3a62d132a8c4..da17b4f6fda5 100644 --- a/drivers/net/geneve.c +++ b/drivers/net/geneve.c @@ -775,9 +775,10 @@ static struct sock *geneve_create_sock(struct net *net, udp_conf.family = AF_INET6; udp_conf.ipv6_v6only = 1; udp_conf.use_udp6_rx_checksums = geneve->cfg.use_udp6_rx_checksums; + udp_conf.local_ip6 = info->key.u.ipv6.src; } else { udp_conf.family = AF_INET; - udp_conf.local_ip.s_addr = htonl(INADDR_ANY); + udp_conf.local_ip.s_addr = info->key.u.ipv4.src; } udp_conf.local_udp_port = info->key.tp_dst; @@ -1061,6 +1062,16 @@ static struct geneve_sock *geneve_find_sock(struct net *net, if (gs->gro_hint != gro_hint) continue; + if (family == AF_INET && + inet_sk(gs->sk)->inet_saddr != info->key.u.ipv4.src) + continue; + +#if IS_ENABLED(CONFIG_IPV6) + if (family == AF_INET6 && + !ipv6_addr_equal(&gs->sk->sk_v6_rcv_saddr, &info->key.u.ipv6.src)) + continue; +#endif + return gs; } @@ -1327,6 +1338,12 @@ static int geneve_xmit_skb(struct sk_buff *skb, struct net_device *dev, if (IS_ERR(rt)) return PTR_ERR(rt); + if (geneve->cfg.info.key.u.ipv4.src && + saddr != geneve->cfg.info.key.u.ipv4.src) { + dst_release(&rt->dst); + return -EADDRNOTAVAIL; + } + err = skb_tunnel_check_pmtu(skb, &rt->dst, GENEVE_IPV4_HLEN + info->options_len + geneve_build_gro_hint_opt(geneve, skb), @@ -1438,6 +1455,12 @@ static int geneve6_xmit_skb(struct sk_buff *skb, struct net_device *dev, if (IS_ERR(dst)) return PTR_ERR(dst); + if (!ipv6_addr_any(&geneve->cfg.info.key.u.ipv6.src) && + !ipv6_addr_equal(&saddr, &geneve->cfg.info.key.u.ipv6.src)) { + dst_release(dst); + return -EADDRNOTAVAIL; + } + err = skb_tunnel_check_pmtu(skb, dst, GENEVE_IPV6_HLEN + info->options_len + geneve_build_gro_hint_opt(geneve, skb), @@ -1729,6 +1752,8 @@ static const struct nla_policy geneve_policy[IFLA_GENEVE_MAX + 1] = { [IFLA_GENEVE_INNER_PROTO_INHERIT] = { .type = NLA_FLAG }, [IFLA_GENEVE_PORT_RANGE] = NLA_POLICY_EXACT_LEN(sizeof(struct ifla_geneve_port_range)), [IFLA_GENEVE_GRO_HINT] = { .type = NLA_FLAG }, + [IFLA_GENEVE_LOCAL] = { .type = NLA_BE32 }, + [IFLA_GENEVE_LOCAL6] = NLA_POLICY_EXACT_LEN(sizeof(struct in6_addr)), }; static int geneve_validate(struct nlattr *tb[], struct nlattr *data[], @@ -1788,7 +1813,45 @@ static int geneve_validate(struct nlattr *tb[], struct nlattr *data[], return 0; } +static bool geneve_saddr_wildcard(const struct ip_tunnel_info *info) +{ + if (ip_tunnel_info_af(info) == AF_INET) { + if (!info->key.u.ipv4.src) + return true; +#if IS_ENABLED(CONFIG_IPV6) + } else { + if (ipv6_addr_any(&info->key.u.ipv6.src)) + return true; +#endif + } + + return false; +} + +static bool geneve_saddr_conflict(const struct ip_tunnel_info *a, + const struct ip_tunnel_info *b) +{ + if (ip_tunnel_info_af(a) != ip_tunnel_info_af(b)) + return false; + + if (geneve_saddr_wildcard(a) || geneve_saddr_wildcard(b)) + return true; + + if (ip_tunnel_info_af(a) == AF_INET) { + if (a->key.u.ipv4.src == b->key.u.ipv4.src) + return true; +#if IS_ENABLED(CONFIG_IPV6) + } else { + if (ipv6_addr_equal(&a->key.u.ipv6.src, &b->key.u.ipv6.src)) + return true; +#endif + } + + return false; +} + static struct geneve_dev *geneve_find_dev(struct geneve_net *gn, + const struct geneve_config *cfg, const struct ip_tunnel_info *info, bool *tun_on_same_port, bool *tun_collect_md) @@ -1798,8 +1861,10 @@ static struct geneve_dev *geneve_find_dev(struct geneve_net *gn, *tun_on_same_port = false; *tun_collect_md = false; list_for_each_entry(geneve, &gn->geneve_list, next) { - if (info->key.tp_dst == geneve->cfg.info.key.tp_dst) { - *tun_collect_md = geneve->cfg.collect_md; + if (info->key.tp_dst == geneve->cfg.info.key.tp_dst && + (cfg->dualstack || geneve->cfg.dualstack || + geneve_saddr_conflict(info, &geneve->cfg.info))) { + *tun_collect_md |= geneve->cfg.collect_md; *tun_on_same_port = true; } if (info->key.tun_id == geneve->cfg.info.key.tun_id && @@ -1815,7 +1880,12 @@ static bool is_tnl_info_zero(const struct ip_tunnel_info *info) return !(info->key.tun_id || info->key.tos || !ip_tunnel_flags_empty(info->key.tun_flags) || info->key.ttl || info->key.label || info->key.tp_src || - memchr_inv(&info->key.u, 0, sizeof(info->key.u))); +#if IS_ENABLED(CONFIG_IPV6) + (ip_tunnel_info_af(info) == AF_INET6 && + !ipv6_addr_any(&info->key.u.ipv6.dst)) || +#endif + (ip_tunnel_info_af(info) == AF_INET && + info->key.u.ipv4.dst)); } static bool geneve_dst_addr_equal(struct ip_tunnel_info *a, @@ -1846,7 +1916,7 @@ static int geneve_configure(struct net *net, struct net_device *dev, geneve->net = net; geneve->dev = dev; - t = geneve_find_dev(gn, info, &tun_on_same_port, &tun_collect_md); + t = geneve_find_dev(gn, cfg, info, &tun_on_same_port, &tun_collect_md); if (t) return -EBUSY; @@ -1864,13 +1934,13 @@ static int geneve_configure(struct net *net, struct net_device *dev, if (cfg->collect_md) { if (tun_on_same_port) { NL_SET_ERR_MSG(extack, - "There can be only one externally controlled device on a destination port"); + "There can be only one externally controlled device on a destination port and a source address"); return -EPERM; } } else { if (tun_collect_md) { NL_SET_ERR_MSG(extack, - "There already exists an externally controlled device on this destination port"); + "There already exists an externally controlled device on this destination port and the source address"); return -EPERM; } } @@ -1917,9 +1987,10 @@ static int geneve_nl2info(struct nlattr *tb[], struct nlattr *data[], cfg->dualstack = true; } - if (data[IFLA_GENEVE_REMOTE] && data[IFLA_GENEVE_REMOTE6]) { + if ((data[IFLA_GENEVE_LOCAL] || data[IFLA_GENEVE_REMOTE]) && + (data[IFLA_GENEVE_LOCAL6] || data[IFLA_GENEVE_REMOTE6])) { NL_SET_ERR_MSG(extack, - "Cannot specify both IPv4 and IPv6 Remote addresses"); + "Cannot specify both IPv4/IPv6 Remote/Local addresses"); return -EINVAL; } @@ -1972,6 +2043,65 @@ static int geneve_nl2info(struct nlattr *tb[], struct nlattr *data[], #endif } + if (data[IFLA_GENEVE_LOCAL]) { + if (changelink) { + __be32 src = nla_get_in_addr(data[IFLA_GENEVE_LOCAL]); + + if (ip_tunnel_info_af(info) == AF_INET6 || + src != info->key.u.ipv4.src) { + attrtype = IFLA_GENEVE_LOCAL; + goto change_notsup; + } + } else { + info->key.u.ipv4.src = nla_get_in_addr(data[IFLA_GENEVE_LOCAL]); + + if (ipv4_is_multicast(info->key.u.ipv4.src)) { + NL_SET_ERR_MSG_ATTR(extack, data[IFLA_GENEVE_LOCAL], + "Local IPv4 address cannot be Multicast"); + return -EINVAL; + } + + cfg->dualstack = false; + } + } + + if (data[IFLA_GENEVE_LOCAL6]) { +#if IS_ENABLED(CONFIG_IPV6) + if (changelink) { + struct in6_addr src = nla_get_in6_addr(data[IFLA_GENEVE_LOCAL6]); + + if (ip_tunnel_info_af(info) == AF_INET || + !ipv6_addr_equal(&src, &info->key.u.ipv6.src)) { + attrtype = IFLA_GENEVE_LOCAL6; + goto change_notsup; + } + } else { + int addr_type; + + info->mode = IP_TUNNEL_INFO_IPV6; + info->key.u.ipv6.src = nla_get_in6_addr(data[IFLA_GENEVE_LOCAL6]); + + addr_type = ipv6_addr_type(&info->key.u.ipv6.src); + if (addr_type & IPV6_ADDR_LINKLOCAL) { + NL_SET_ERR_MSG_ATTR(extack, data[IFLA_GENEVE_LOCAL6], + "Local IPv6 address cannot be link-local"); + return -EINVAL; + } + if (addr_type & IPV6_ADDR_MULTICAST) { + NL_SET_ERR_MSG_ATTR(extack, data[IFLA_GENEVE_LOCAL6], + "Local IPv6 address cannot be Multicast"); + return -EINVAL; + } + + cfg->dualstack = false; + } +#else + NL_SET_ERR_MSG_ATTR(extack, data[IFLA_GENEVE_LOCAL6], + "IPv6 support not enabled in the kernel"); + return -EPFNOSUPPORT; +#endif + } + if (data[IFLA_GENEVE_ID]) { __u32 vni; __u8 tvni[3]; @@ -2265,6 +2395,7 @@ static size_t geneve_get_size(const struct net_device *dev) { return nla_total_size(sizeof(__u32)) + /* IFLA_GENEVE_ID */ nla_total_size(sizeof(struct in6_addr)) + /* IFLA_GENEVE_REMOTE{6} */ + nla_total_size(sizeof(struct in6_addr)) + /* IFLA_GENEVE_LOCAL{6} */ nla_total_size(sizeof(__u8)) + /* IFLA_GENEVE_TTL */ nla_total_size(sizeof(__u8)) + /* IFLA_GENEVE_TOS */ nla_total_size(sizeof(__u8)) + /* IFLA_GENEVE_DF */ @@ -2320,6 +2451,24 @@ static int geneve_fill_info(struct sk_buff *skb, const struct net_device *dev) #endif } + if (!geneve->cfg.dualstack) { + if (ip_tunnel_info_af(info) == AF_INET) { + if ((info->key.u.ipv4.src || + geneve->cfg.collect_md) && + nla_put_in_addr(skb, IFLA_GENEVE_LOCAL, + info->key.u.ipv4.src)) + goto nla_put_failure; +#if IS_ENABLED(CONFIG_IPV6) + } else { + if ((!ipv6_addr_any(&info->key.u.ipv6.src) || + geneve->cfg.collect_md) && + nla_put_in6_addr(skb, IFLA_GENEVE_LOCAL6, + &info->key.u.ipv6.src)) + goto nla_put_failure; +#endif + } + } + if (nla_put_u8(skb, IFLA_GENEVE_TTL, info->key.ttl) || nla_put_u8(skb, IFLA_GENEVE_TOS, info->key.tos) || nla_put_be32(skb, IFLA_GENEVE_LABEL, info->key.label)) diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h index 46413392b402..363526549a01 100644 --- a/include/uapi/linux/if_link.h +++ b/include/uapi/linux/if_link.h @@ -1506,6 +1506,8 @@ enum { IFLA_GENEVE_INNER_PROTO_INHERIT, IFLA_GENEVE_PORT_RANGE, IFLA_GENEVE_GRO_HINT, + IFLA_GENEVE_LOCAL, + IFLA_GENEVE_LOCAL6, __IFLA_GENEVE_MAX }; #define IFLA_GENEVE_MAX (__IFLA_GENEVE_MAX - 1) -- cgit v1.2.3 From a02a765bd5c2ae7705144829b2911c96c29db6ba Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Tue, 2 Jun 2026 12:51:38 +0000 Subject: mptcp: change mptcp_established_options() to return opt_size Instead of passing opt_size address to mptcp_established_options(), change this function to return it by value. This removes the need for an expensive stack canary in tcp_established_options() when CONFIG_STACKPROTECTOR_STRONG=y. $ scripts/bloat-o-meter -t vmlinux.old vmlinux.new add/remove: 0/0 grow/shrink: 0/3 up/down: 0/-92 (-92) Function old new delta tcp_options_write.isra 1423 1407 -16 mptcp_established_options 2746 2720 -26 tcp_established_options 553 503 -50 Total: Before=22110750, After=22110658, chg -0.00% Signed-off-by: Eric Dumazet Reviewed-by: Matthieu Baerts (NGI0) Link: https://patch.msgid.link/20260602125138.2317015-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- include/net/mptcp.h | 17 ++++++++--------- net/ipv4/tcp_output.c | 7 ++++--- net/mptcp/options.c | 31 ++++++++++++++++--------------- 3 files changed, 28 insertions(+), 27 deletions(-) (limited to 'include') diff --git a/include/net/mptcp.h b/include/net/mptcp.h index f7263fe2a2e4..27225c68a388 100644 --- a/include/net/mptcp.h +++ b/include/net/mptcp.h @@ -149,9 +149,9 @@ bool mptcp_syn_options(struct sock *sk, const struct sk_buff *skb, unsigned int *size, struct mptcp_out_options *opts); bool mptcp_synack_options(const struct request_sock *req, unsigned int *size, struct mptcp_out_options *opts); -bool mptcp_established_options(struct sock *sk, struct sk_buff *skb, - unsigned int *size, unsigned int remaining, - struct mptcp_out_options *opts); +int mptcp_established_options(struct sock *sk, struct sk_buff *skb, + unsigned int remaining, + struct mptcp_out_options *opts); bool mptcp_incoming_options(struct sock *sk, struct sk_buff *skb); void mptcp_write_options(struct tcphdr *th, __be32 *ptr, struct tcp_sock *tp, @@ -266,13 +266,12 @@ static inline bool mptcp_synack_options(const struct request_sock *req, return false; } -static inline bool mptcp_established_options(struct sock *sk, - struct sk_buff *skb, - unsigned int *size, - unsigned int remaining, - struct mptcp_out_options *opts) +static inline int mptcp_established_options(struct sock *sk, + struct sk_buff *skb, + unsigned int remaining, + struct mptcp_out_options *opts) { - return false; + return -1; } static inline bool mptcp_incoming_options(struct sock *sk, diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c index 1b0a066bb6a3..d3b8e61d3c5e 100644 --- a/net/ipv4/tcp_output.c +++ b/net/ipv4/tcp_output.c @@ -1183,10 +1183,11 @@ static unsigned int tcp_established_options(struct sock *sk, struct sk_buff *skb */ if (sk_is_mptcp(sk)) { unsigned int remaining = MAX_TCP_OPTION_SPACE - size; - unsigned int opt_size = 0; + int opt_size; - if (mptcp_established_options(sk, skb, &opt_size, remaining, - &opts->mptcp)) { + opt_size = mptcp_established_options(sk, skb, remaining, + &opts->mptcp); + if (opt_size >= 0) { opts->options |= OPTION_MPTCP; size += opt_size; } diff --git a/net/mptcp/options.c b/net/mptcp/options.c index 8a1c5698983c..53528301394d 100644 --- a/net/mptcp/options.c +++ b/net/mptcp/options.c @@ -836,13 +836,14 @@ static bool mptcp_established_options_mp_fail(struct sock *sk, return true; } -bool mptcp_established_options(struct sock *sk, struct sk_buff *skb, - unsigned int *size, unsigned int remaining, - struct mptcp_out_options *opts) +int mptcp_established_options(struct sock *sk, struct sk_buff *skb, + unsigned int remaining, + struct mptcp_out_options *opts) { struct mptcp_subflow_context *subflow = mptcp_subflow_ctx(sk); struct mptcp_sock *msk = mptcp_sk(subflow->conn); unsigned int opt_size = 0; + int total_size = 0; bool snd_data_fin; bool ret = false; @@ -852,20 +853,20 @@ bool mptcp_established_options(struct sock *sk, struct sk_buff *skb, * option space. */ if (unlikely(__mptcp_check_fallback(msk) && !mptcp_check_infinite_map(skb))) - return true; + return 0; if (unlikely(skb && TCP_SKB_CB(skb)->tcp_flags & TCPHDR_RST)) { if (mptcp_established_options_fastclose(sk, &opt_size, remaining, opts) || mptcp_established_options_mp_fail(sk, &opt_size, remaining, opts)) { - *size += opt_size; + total_size += opt_size; remaining -= opt_size; } /* MP_RST can be used with MP_FASTCLOSE and MP_FAIL if there is room */ if (mptcp_established_options_rst(sk, skb, &opt_size, remaining, opts)) { - *size += opt_size; + total_size += opt_size; remaining -= opt_size; } - return true; + return total_size; } snd_data_fin = mptcp_data_fin_enabled(msk); @@ -877,9 +878,9 @@ bool mptcp_established_options(struct sock *sk, struct sk_buff *skb, ret = true; if (mptcp_established_options_mp_fail(sk, &mp_fail_size, remaining - opt_size, opts)) { - *size += opt_size + mp_fail_size; + total_size += opt_size + mp_fail_size; remaining -= opt_size - mp_fail_size; - return true; + return total_size; } } @@ -887,27 +888,27 @@ bool mptcp_established_options(struct sock *sk, struct sk_buff *skb, * TCP option space would be fatal */ if (WARN_ON_ONCE(opt_size > remaining)) - return false; + return -1; - *size += opt_size; + total_size += opt_size; remaining -= opt_size; if (mptcp_established_options_add_addr(sk, skb, &opt_size, remaining, opts)) { - *size += opt_size; + total_size += opt_size; remaining -= opt_size; ret = true; } else if (mptcp_established_options_rm_addr(sk, &opt_size, remaining, opts)) { - *size += opt_size; + total_size += opt_size; remaining -= opt_size; ret = true; } if (mptcp_established_options_mp_prio(sk, &opt_size, remaining, opts)) { - *size += opt_size; + total_size += opt_size; remaining -= opt_size; ret = true; } - return ret; + return ret ? total_size : -1; } bool mptcp_synack_options(const struct request_sock *req, unsigned int *size, -- cgit v1.2.3 From 231a1744dc433e8f39871a8fd0f5eab78202e136 Mon Sep 17 00:00:00 2001 From: Antony Antony Date: Tue, 26 May 2026 21:06:06 +0200 Subject: xfrm: add extack to xfrm_init_state Add a struct extack parameter to xfrm_init_state() and pass it through to __xfrm_init_state(). This allows validation errors detected during state initialization to propagate meaningful error messages back to userspace. xfrm_state_migrate() now passes extack so that errors from the XFRM_MSG_MIGRATE_STATE path are properly reported. Callers without an extack context (af_key, ipcomp4, ipcomp6) pass NULL, preserving their existing behaviour. Reviewed-by: Sabrina Dubroca Signed-off-by: Antony Antony Signed-off-by: Steffen Klassert --- include/net/xfrm.h | 2 +- net/ipv4/ipcomp.c | 2 +- net/ipv6/ipcomp6.c | 2 +- net/key/af_key.c | 2 +- net/xfrm/xfrm_state.c | 6 +++--- 5 files changed, 7 insertions(+), 7 deletions(-) (limited to 'include') diff --git a/include/net/xfrm.h b/include/net/xfrm.h index 10d3edde6b2f..0c035955d87d 100644 --- a/include/net/xfrm.h +++ b/include/net/xfrm.h @@ -1774,7 +1774,7 @@ u32 xfrm_replay_seqhi(struct xfrm_state *x, __be32 net_seq); int xfrm_init_replay(struct xfrm_state *x, struct netlink_ext_ack *extack); u32 xfrm_state_mtu(struct xfrm_state *x, int mtu); int __xfrm_init_state(struct xfrm_state *x, struct netlink_ext_ack *extack); -int xfrm_init_state(struct xfrm_state *x); +int xfrm_init_state(struct xfrm_state *x, struct netlink_ext_ack *extack); int xfrm_input(struct sk_buff *skb, int nexthdr, __be32 spi, int encap_type); int xfrm_input_resume(struct sk_buff *skb, int nexthdr); int xfrm_trans_queue_net(struct net *net, struct sk_buff *skb, diff --git a/net/ipv4/ipcomp.c b/net/ipv4/ipcomp.c index 9a45aed508d1..b1ea2d37e8c5 100644 --- a/net/ipv4/ipcomp.c +++ b/net/ipv4/ipcomp.c @@ -77,7 +77,7 @@ static struct xfrm_state *ipcomp_tunnel_create(struct xfrm_state *x) memcpy(&t->mark, &x->mark, sizeof(t->mark)); t->if_id = x->if_id; - if (xfrm_init_state(t)) + if (xfrm_init_state(t, NULL)) goto error; atomic_set(&t->tunnel_users, 1); diff --git a/net/ipv6/ipcomp6.c b/net/ipv6/ipcomp6.c index 8607569de34f..b340d67eb1d9 100644 --- a/net/ipv6/ipcomp6.c +++ b/net/ipv6/ipcomp6.c @@ -95,7 +95,7 @@ static struct xfrm_state *ipcomp6_tunnel_create(struct xfrm_state *x) memcpy(&t->mark, &x->mark, sizeof(t->mark)); t->if_id = x->if_id; - if (xfrm_init_state(t)) + if (xfrm_init_state(t, NULL)) goto error; atomic_set(&t->tunnel_users, 1); diff --git a/net/key/af_key.c b/net/key/af_key.c index a166a88d8788..842bf5786e3f 100644 --- a/net/key/af_key.c +++ b/net/key/af_key.c @@ -1299,7 +1299,7 @@ static struct xfrm_state * pfkey_msg2xfrm_state(struct net *net, } } - err = xfrm_init_state(x); + err = xfrm_init_state(x, NULL); if (err) goto out; diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c index 9417a025270c..53d88b87bdbd 100644 --- a/net/xfrm/xfrm_state.c +++ b/net/xfrm/xfrm_state.c @@ -2143,7 +2143,7 @@ struct xfrm_state *xfrm_state_migrate(struct xfrm_state *x, if (!xc) return NULL; - if (xfrm_init_state(xc) < 0) + if (xfrm_init_state(xc, extack) < 0) goto error; /* configure the hardware if offload is requested */ @@ -3238,11 +3238,11 @@ error: EXPORT_SYMBOL(__xfrm_init_state); -int xfrm_init_state(struct xfrm_state *x) +int xfrm_init_state(struct xfrm_state *x, struct netlink_ext_ack *extack) { int err; - err = __xfrm_init_state(x, NULL); + err = __xfrm_init_state(x, extack); if (err) return err; -- cgit v1.2.3 From e2e92714d08117d18f0e560673206717d10edbd4 Mon Sep 17 00:00:00 2001 From: Antony Antony Date: Tue, 26 May 2026 21:07:15 +0200 Subject: xfrm: rename reqid in xfrm_migrate In preparation for a later patch in this series s/reqid/old_reqid/. No functional change. Signed-off-by: Antony Antony Signed-off-by: Steffen Klassert --- include/net/xfrm.h | 2 +- net/key/af_key.c | 10 +++++----- net/xfrm/xfrm_policy.c | 4 ++-- net/xfrm/xfrm_state.c | 6 +++--- net/xfrm/xfrm_user.c | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) (limited to 'include') diff --git a/include/net/xfrm.h b/include/net/xfrm.h index 0c035955d87d..368b1dc22e5c 100644 --- a/include/net/xfrm.h +++ b/include/net/xfrm.h @@ -685,7 +685,7 @@ struct xfrm_migrate { u8 proto; u8 mode; u16 reserved; - u32 reqid; + u32 old_reqid; u16 old_family; u16 new_family; }; diff --git a/net/key/af_key.c b/net/key/af_key.c index 842bf5786e3f..1f0201d97b4f 100644 --- a/net/key/af_key.c +++ b/net/key/af_key.c @@ -2554,7 +2554,7 @@ static int ipsecrequests_to_migrate(struct sadb_x_ipsecrequest *rq1, int len, if ((mode = pfkey_mode_to_xfrm(rq1->sadb_x_ipsecrequest_mode)) < 0) return -EINVAL; m->mode = mode; - m->reqid = rq1->sadb_x_ipsecrequest_reqid; + m->old_reqid = rq1->sadb_x_ipsecrequest_reqid; return ((int)(rq1->sadb_x_ipsecrequest_len + rq2->sadb_x_ipsecrequest_len)); @@ -3655,15 +3655,15 @@ static int pfkey_send_migrate(const struct xfrm_selector *sel, u8 dir, u8 type, if (mode < 0) goto err; if (set_ipsecrequest(skb, mp->proto, mode, - (mp->reqid ? IPSEC_LEVEL_UNIQUE : IPSEC_LEVEL_REQUIRE), - mp->reqid, mp->old_family, + (mp->old_reqid ? IPSEC_LEVEL_UNIQUE : IPSEC_LEVEL_REQUIRE), + mp->old_reqid, mp->old_family, &mp->old_saddr, &mp->old_daddr) < 0) goto err; /* new ipsecrequest */ if (set_ipsecrequest(skb, mp->proto, mode, - (mp->reqid ? IPSEC_LEVEL_UNIQUE : IPSEC_LEVEL_REQUIRE), - mp->reqid, mp->new_family, + (mp->old_reqid ? IPSEC_LEVEL_UNIQUE : IPSEC_LEVEL_REQUIRE), + mp->old_reqid, mp->new_family, &mp->new_saddr, &mp->new_daddr) < 0) goto err; } diff --git a/net/xfrm/xfrm_policy.c b/net/xfrm/xfrm_policy.c index c944327ce66c..fd505adf080e 100644 --- a/net/xfrm/xfrm_policy.c +++ b/net/xfrm/xfrm_policy.c @@ -4538,7 +4538,7 @@ static int migrate_tmpl_match(const struct xfrm_migrate *m, const struct xfrm_tm int match = 0; if (t->mode == m->mode && t->id.proto == m->proto && - (m->reqid == 0 || t->reqid == m->reqid)) { + (m->old_reqid == 0 || t->reqid == m->old_reqid)) { switch (t->mode) { case XFRM_MODE_TUNNEL: case XFRM_MODE_BEET: @@ -4632,7 +4632,7 @@ static int xfrm_migrate_check(const struct xfrm_migrate *m, int num_migrate, sizeof(m[i].old_saddr)) && m[i].proto == m[j].proto && m[i].mode == m[j].mode && - m[i].reqid == m[j].reqid && + m[i].old_reqid == m[j].old_reqid && m[i].old_family == m[j].old_family) { NL_SET_ERR_MSG(extack, "Entries in the MIGRATE attribute's list must be unique"); return -EINVAL; diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c index b9de931d84c1..5424f2becbaf 100644 --- a/net/xfrm/xfrm_state.c +++ b/net/xfrm/xfrm_state.c @@ -2081,14 +2081,14 @@ struct xfrm_state *xfrm_migrate_state_find(struct xfrm_migrate *m, struct net *n spin_lock_bh(&net->xfrm.xfrm_state_lock); - if (m->reqid) { + if (m->old_reqid) { h = xfrm_dst_hash(net, &m->old_daddr, &m->old_saddr, - m->reqid, m->old_family); + m->old_reqid, m->old_family); hlist_for_each_entry(x, xfrm_state_deref_prot(net->xfrm.state_bydst, net) + h, bydst) { if (x->props.mode != m->mode || x->id.proto != m->proto) continue; - if (m->reqid && x->props.reqid != m->reqid) + if (m->old_reqid && x->props.reqid != m->old_reqid) continue; if (if_id != 0 && x->if_id != if_id) continue; diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c index e87f33aaa99c..ce65e872cbac 100644 --- a/net/xfrm/xfrm_user.c +++ b/net/xfrm/xfrm_user.c @@ -3110,7 +3110,7 @@ static int copy_from_user_migrate(struct xfrm_migrate *ma, ma->proto = um->proto; ma->mode = um->mode; - ma->reqid = um->reqid; + ma->old_reqid = um->reqid; ma->old_family = um->old_family; ma->new_family = um->new_family; @@ -3193,7 +3193,7 @@ static int copy_to_user_migrate(const struct xfrm_migrate *m, struct sk_buff *sk memset(&um, 0, sizeof(um)); um.proto = m->proto; um.mode = m->mode; - um.reqid = m->reqid; + um.reqid = m->old_reqid; um.old_family = m->old_family; memcpy(&um.old_daddr, &m->old_daddr, sizeof(um.old_daddr)); memcpy(&um.old_saddr, &m->old_saddr, sizeof(um.old_saddr)); -- cgit v1.2.3 From 8de53883a4bf807a628f0d150a7ca7ddf56a5cc3 Mon Sep 17 00:00:00 2001 From: Antony Antony Date: Tue, 26 May 2026 21:07:30 +0200 Subject: xfrm: split xfrm_state_migrate into create and install functions To prepare for subsequent patches, split xfrm_state_migrate() into two functions: - xfrm_state_migrate_create(): creates the migrated state - xfrm_state_migrate_install(): installs it into the state table splitting will help to avoid SN/IV reuse when migrating AEAD SA. And add const whenever possible. No functional change. Reviewed-by: Sabrina Dubroca Signed-off-by: Antony Antony Signed-off-by: Steffen Klassert --- include/net/xfrm.h | 11 ++++++++ net/xfrm/xfrm_state.c | 73 +++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 64 insertions(+), 20 deletions(-) (limited to 'include') diff --git a/include/net/xfrm.h b/include/net/xfrm.h index 368b1dc22e5c..4137986f15e2 100644 --- a/include/net/xfrm.h +++ b/include/net/xfrm.h @@ -1895,6 +1895,17 @@ int km_migrate(const struct xfrm_selector *sel, u8 dir, u8 type, const struct xfrm_encap_tmpl *encap); struct xfrm_state *xfrm_migrate_state_find(struct xfrm_migrate *m, struct net *net, u32 if_id); +struct xfrm_state *xfrm_state_migrate_create(struct xfrm_state *x, + const struct xfrm_migrate *m, + const struct xfrm_encap_tmpl *encap, + struct net *net, + struct xfrm_user_offload *xuo, + struct netlink_ext_ack *extack); +int xfrm_state_migrate_install(const struct xfrm_state *x, + struct xfrm_state *xc, + const struct xfrm_migrate *m, + struct xfrm_user_offload *xuo, + struct netlink_ext_ack *extack); struct xfrm_state *xfrm_state_migrate(struct xfrm_state *x, struct xfrm_migrate *m, struct xfrm_encap_tmpl *encap, diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c index 5424f2becbaf..85fd80520184 100644 --- a/net/xfrm/xfrm_state.c +++ b/net/xfrm/xfrm_state.c @@ -1966,8 +1966,8 @@ static inline int clone_security(struct xfrm_state *x, struct xfrm_sec_ctx *secu } static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig, - struct xfrm_encap_tmpl *encap, - struct xfrm_migrate *m) + const struct xfrm_encap_tmpl *encap, + const struct xfrm_migrate *m) { struct net *net = xs_net(orig); struct xfrm_state *x = xfrm_state_alloc(net); @@ -2125,12 +2125,12 @@ struct xfrm_state *xfrm_migrate_state_find(struct xfrm_migrate *m, struct net *n } EXPORT_SYMBOL(xfrm_migrate_state_find); -struct xfrm_state *xfrm_state_migrate(struct xfrm_state *x, - struct xfrm_migrate *m, - struct xfrm_encap_tmpl *encap, - struct net *net, - struct xfrm_user_offload *xuo, - struct netlink_ext_ack *extack) +struct xfrm_state *xfrm_state_migrate_create(struct xfrm_state *x, + const struct xfrm_migrate *m, + const struct xfrm_encap_tmpl *encap, + struct net *net, + struct xfrm_user_offload *xuo, + struct netlink_ext_ack *extack) { struct xfrm_state *xc; @@ -2145,24 +2145,57 @@ struct xfrm_state *xfrm_state_migrate(struct xfrm_state *x, if (xuo && xfrm_dev_state_add(net, xc, xuo, extack)) goto error; - /* add state */ + return xc; +error: + xc->km.state = XFRM_STATE_DEAD; + xfrm_state_put(xc); + return NULL; +} +EXPORT_SYMBOL(xfrm_state_migrate_create); + +int xfrm_state_migrate_install(const struct xfrm_state *x, + struct xfrm_state *xc, + const struct xfrm_migrate *m, + struct xfrm_user_offload *xuo, + struct netlink_ext_ack *extack) +{ if (xfrm_addr_equal(&x->id.daddr, &m->new_daddr, m->new_family)) { - /* a care is needed when the destination address of the - state is to be updated as it is a part of triplet */ + /* + * Care is needed when the destination address + * of the state is to be updated as it is a part of triplet. + */ xfrm_state_insert(xc); } else { - if (xfrm_state_add(xc) < 0) - goto error_add; + if (xfrm_state_add(xc) < 0) { + if (xuo) + xfrm_dev_state_delete(xc); + xc->km.state = XFRM_STATE_DEAD; + xfrm_state_put(xc); + return -EEXIST; + } } + return 0; +} +EXPORT_SYMBOL(xfrm_state_migrate_install); + +struct xfrm_state *xfrm_state_migrate(struct xfrm_state *x, + struct xfrm_migrate *m, + struct xfrm_encap_tmpl *encap, + struct net *net, + struct xfrm_user_offload *xuo, + struct netlink_ext_ack *extack) +{ + struct xfrm_state *xc; + + xc = xfrm_state_migrate_create(x, m, encap, net, xuo, extack); + if (!xc) + return NULL; + + if (xfrm_state_migrate_install(x, xc, m, xuo, extack) < 0) + return NULL; + return xc; -error_add: - if (xuo) - xfrm_dev_state_delete(xc); -error: - xc->km.state = XFRM_STATE_DEAD; - xfrm_state_put(xc); - return NULL; } EXPORT_SYMBOL(xfrm_state_migrate); #endif -- cgit v1.2.3 From bac7a60e267831471ecdf54881fc62a2c80c446c Mon Sep 17 00:00:00 2001 From: Antony Antony Date: Tue, 26 May 2026 21:08:17 +0200 Subject: xfrm: add state synchronization after migration Add xfrm_migrate_sync() to copy curlft and replay state from the old SA to the new one before installation. The function allocates no memory, so it can be called under a spinlock. In preparation for a subsequent patch in this series. A subsequent patch calls this under x->lock, atomically capturing the latest lifetime counters and replay state from the original SA and deleting it in the same critical section to prevent SN/IV reuse for XFRM_MSG_MIGRATE_STATE method. No functional change. Signed-off-by: Antony Antony Signed-off-by: Steffen Klassert --- include/net/xfrm.h | 46 +++++++++++++++++++++++++++++++++++++--------- net/xfrm/xfrm_state.c | 11 ++++------- 2 files changed, 41 insertions(+), 16 deletions(-) (limited to 'include') diff --git a/include/net/xfrm.h b/include/net/xfrm.h index 4137986f15e2..be22c26e4661 100644 --- a/include/net/xfrm.h +++ b/include/net/xfrm.h @@ -2024,23 +2024,51 @@ static inline unsigned int xfrm_replay_state_esn_len(struct xfrm_replay_state_es #ifdef CONFIG_XFRM_MIGRATE static inline int xfrm_replay_clone(struct xfrm_state *x, - struct xfrm_state *orig) + const struct xfrm_state *orig) { + /* Counters synced later in xfrm_replay_sync() */ - x->replay_esn = kmemdup(orig->replay_esn, + x->replay = orig->replay; + x->preplay = orig->preplay; + + if (orig->replay_esn) { + x->replay_esn = kmemdup(orig->replay_esn, xfrm_replay_state_esn_len(orig->replay_esn), GFP_KERNEL); - if (!x->replay_esn) - return -ENOMEM; - x->preplay_esn = kmemdup(orig->preplay_esn, - xfrm_replay_state_esn_len(orig->preplay_esn), - GFP_KERNEL); - if (!x->preplay_esn) - return -ENOMEM; + if (!x->replay_esn) + return -ENOMEM; + x->preplay_esn = kmemdup(orig->preplay_esn, + xfrm_replay_state_esn_len(orig->preplay_esn), + GFP_KERNEL); + if (!x->preplay_esn) + return -ENOMEM; + } return 0; } +static inline void xfrm_replay_sync(struct xfrm_state *x, const struct xfrm_state *orig) +{ + x->replay = orig->replay; + x->preplay = orig->preplay; + + if (orig->replay_esn) { + memcpy(x->replay_esn, orig->replay_esn, + xfrm_replay_state_esn_len(orig->replay_esn)); + + memcpy(x->preplay_esn, orig->preplay_esn, + xfrm_replay_state_esn_len(orig->preplay_esn)); + } +} + +static inline void xfrm_migrate_sync(struct xfrm_state *x, + const struct xfrm_state *orig) +{ + /* called under lock so no race conditions or mallocs allowed */ + memcpy(&x->curlft, &orig->curlft, sizeof(x->curlft)); + xfrm_replay_sync(x, orig); +} + static inline struct xfrm_algo_aead *xfrm_algo_aead_clone(struct xfrm_algo_aead *orig) { return kmemdup(orig, aead_len(orig), GFP_KERNEL); diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c index 327a855253e6..fcf6f0c6400d 100644 --- a/net/xfrm/xfrm_state.c +++ b/net/xfrm/xfrm_state.c @@ -2027,10 +2027,8 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig, goto error; } - if (orig->replay_esn) { - if (xfrm_replay_clone(x, orig)) - goto error; - } + if (xfrm_replay_clone(x, orig)) + goto error; memcpy(&x->mark, &orig->mark, sizeof(x->mark)); memcpy(&x->props.smark, &orig->props.smark, sizeof(x->props.smark)); @@ -2043,11 +2041,8 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig, x->tfcpad = orig->tfcpad; x->replay_maxdiff = orig->replay_maxdiff; x->replay_maxage = orig->replay_maxage; - memcpy(&x->curlft, &orig->curlft, sizeof(x->curlft)); x->km.state = orig->km.state; x->km.seq = orig->km.seq; - x->replay = orig->replay; - x->preplay = orig->preplay; x->lastused = orig->lastused; x->new_mapping = 0; x->new_mapping_sport = 0; @@ -2193,6 +2188,8 @@ struct xfrm_state *xfrm_state_migrate(struct xfrm_state *x, if (!xc) return NULL; + xfrm_migrate_sync(xc, x); + if (xfrm_state_migrate_install(x, xc, m, xuo, extack) < 0) return NULL; -- cgit v1.2.3 From 1d97daee303847bd9f0ff97dc257380fc156206d Mon Sep 17 00:00:00 2001 From: Antony Antony Date: Tue, 26 May 2026 21:08:46 +0200 Subject: xfrm: move encap and xuo into struct xfrm_migrate In preparation for an upcoming patch, move the xfrm_encap_tmpl and xfrm_user_offload pointers from separate parameters into struct xfrm_migrate, reducing the parameter count of xfrm_state_migrate_create(), xfrm_state_migrate_install() and xfrm_state_migrate() The fields are placed after the four xfrm_address_t members where the struct is naturally 8-byte aligned, avoiding padding. No functional change. Reviewed-by: Sabrina Dubroca Signed-off-by: Antony Antony Signed-off-by: Steffen Klassert --- include/net/xfrm.h | 7 ++----- net/xfrm/xfrm_policy.c | 4 +++- net/xfrm/xfrm_state.c | 20 +++++++------------- 3 files changed, 12 insertions(+), 19 deletions(-) (limited to 'include') diff --git a/include/net/xfrm.h b/include/net/xfrm.h index be22c26e4661..4b29ab92c2a7 100644 --- a/include/net/xfrm.h +++ b/include/net/xfrm.h @@ -682,6 +682,8 @@ struct xfrm_migrate { xfrm_address_t old_saddr; xfrm_address_t new_daddr; xfrm_address_t new_saddr; + struct xfrm_encap_tmpl *encap; + struct xfrm_user_offload *xuo; u8 proto; u8 mode; u16 reserved; @@ -1897,20 +1899,15 @@ struct xfrm_state *xfrm_migrate_state_find(struct xfrm_migrate *m, struct net *n u32 if_id); struct xfrm_state *xfrm_state_migrate_create(struct xfrm_state *x, const struct xfrm_migrate *m, - const struct xfrm_encap_tmpl *encap, struct net *net, - struct xfrm_user_offload *xuo, struct netlink_ext_ack *extack); int xfrm_state_migrate_install(const struct xfrm_state *x, struct xfrm_state *xc, const struct xfrm_migrate *m, - struct xfrm_user_offload *xuo, struct netlink_ext_ack *extack); struct xfrm_state *xfrm_state_migrate(struct xfrm_state *x, struct xfrm_migrate *m, - struct xfrm_encap_tmpl *encap, struct net *net, - struct xfrm_user_offload *xuo, struct netlink_ext_ack *extack); int xfrm_migrate(const struct xfrm_selector *sel, u8 dir, u8 type, struct xfrm_migrate *m, int num_bundles, diff --git a/net/xfrm/xfrm_policy.c b/net/xfrm/xfrm_policy.c index fd505adf080e..cf05d778e2dd 100644 --- a/net/xfrm/xfrm_policy.c +++ b/net/xfrm/xfrm_policy.c @@ -4680,7 +4680,9 @@ int xfrm_migrate(const struct xfrm_selector *sel, u8 dir, u8 type, if ((x = xfrm_migrate_state_find(mp, net, if_id))) { x_cur[nx_cur] = x; nx_cur++; - xc = xfrm_state_migrate(x, mp, encap, net, xuo, extack); + mp->encap = encap; + mp->xuo = xuo; + xc = xfrm_state_migrate(x, mp, net, extack); if (xc) { x_new[nx_new] = xc; nx_new++; diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c index 1db48ecda80d..043e573c4f32 100644 --- a/net/xfrm/xfrm_state.c +++ b/net/xfrm/xfrm_state.c @@ -1966,7 +1966,6 @@ static inline int clone_security(struct xfrm_state *x, struct xfrm_sec_ctx *secu } static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig, - const struct xfrm_encap_tmpl *encap, const struct xfrm_migrate *m) { struct net *net = xs_net(orig); @@ -2008,8 +2007,8 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig, } x->props.calgo = orig->props.calgo; - if (encap) { - x->encap = kmemdup(encap, sizeof(*x->encap), GFP_KERNEL); + if (m->encap) { + x->encap = kmemdup(m->encap, sizeof(*x->encap), GFP_KERNEL); if (!x->encap) goto error; x->mapping_maxage = orig->mapping_maxage; @@ -2122,14 +2121,12 @@ EXPORT_SYMBOL(xfrm_migrate_state_find); struct xfrm_state *xfrm_state_migrate_create(struct xfrm_state *x, const struct xfrm_migrate *m, - const struct xfrm_encap_tmpl *encap, struct net *net, - struct xfrm_user_offload *xuo, struct netlink_ext_ack *extack) { struct xfrm_state *xc; - xc = xfrm_state_clone_and_setup(x, encap, m); + xc = xfrm_state_clone_and_setup(x, m); if (!xc) { NL_SET_ERR_MSG(extack, "Failed to clone and setup state"); return NULL; @@ -2141,7 +2138,7 @@ struct xfrm_state *xfrm_state_migrate_create(struct xfrm_state *x, } /* configure the hardware if offload is requested */ - if (xuo && xfrm_dev_state_add(net, xc, xuo, extack)) + if (m->xuo && xfrm_dev_state_add(net, xc, m->xuo, extack)) goto error; return xc; @@ -2155,7 +2152,6 @@ EXPORT_SYMBOL(xfrm_state_migrate_create); int xfrm_state_migrate_install(const struct xfrm_state *x, struct xfrm_state *xc, const struct xfrm_migrate *m, - struct xfrm_user_offload *xuo, struct netlink_ext_ack *extack) { if (m->new_family == m->old_family && @@ -2168,7 +2164,7 @@ int xfrm_state_migrate_install(const struct xfrm_state *x, } else { if (xfrm_state_add(xc) < 0) { NL_SET_ERR_MSG(extack, "Failed to add migrated state"); - if (xuo) + if (m->xuo) xfrm_dev_state_delete(xc); xc->km.state = XFRM_STATE_DEAD; xfrm_state_put(xc); @@ -2182,20 +2178,18 @@ EXPORT_SYMBOL(xfrm_state_migrate_install); struct xfrm_state *xfrm_state_migrate(struct xfrm_state *x, struct xfrm_migrate *m, - struct xfrm_encap_tmpl *encap, struct net *net, - struct xfrm_user_offload *xuo, struct netlink_ext_ack *extack) { struct xfrm_state *xc; - xc = xfrm_state_migrate_create(x, m, encap, net, xuo, extack); + xc = xfrm_state_migrate_create(x, m, net, extack); if (!xc) return NULL; xfrm_migrate_sync(xc, x); - if (xfrm_state_migrate_install(x, xc, m, xuo, extack) < 0) + if (xfrm_state_migrate_install(x, xc, m, extack) < 0) return NULL; return xc; -- cgit v1.2.3 From 8eed5ba25734bfeec766f9518515be7cf6d1de2a Mon Sep 17 00:00:00 2001 From: Antony Antony Date: Tue, 26 May 2026 21:09:35 +0200 Subject: xfrm: make xfrm_dev_state_add xuo parameter const The xuo pointer is not modified by xfrm_dev_state_add(); make it const. Signed-off-by: Antony Antony Signed-off-by: Steffen Klassert --- include/net/xfrm.h | 6 ++++-- net/xfrm/xfrm_device.c | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/include/net/xfrm.h b/include/net/xfrm.h index 4b29ab92c2a7..5515c7b10020 100644 --- a/include/net/xfrm.h +++ b/include/net/xfrm.h @@ -2104,7 +2104,7 @@ void xfrm_dev_resume(struct sk_buff *skb); void xfrm_dev_backlog(struct softnet_data *sd); struct sk_buff *validate_xmit_xfrm(struct sk_buff *skb, netdev_features_t features, bool *again); int xfrm_dev_state_add(struct net *net, struct xfrm_state *x, - struct xfrm_user_offload *xuo, + const struct xfrm_user_offload *xuo, struct netlink_ext_ack *extack); int xfrm_dev_policy_add(struct net *net, struct xfrm_policy *xp, struct xfrm_user_offload *xuo, u8 dir, @@ -2175,7 +2175,9 @@ static inline struct sk_buff *validate_xmit_xfrm(struct sk_buff *skb, netdev_fea return skb; } -static inline int xfrm_dev_state_add(struct net *net, struct xfrm_state *x, struct xfrm_user_offload *xuo, struct netlink_ext_ack *extack) +static inline int xfrm_dev_state_add(struct net *net, struct xfrm_state *x, + const struct xfrm_user_offload *xuo, + struct netlink_ext_ack *extack) { return 0; } diff --git a/net/xfrm/xfrm_device.c b/net/xfrm/xfrm_device.c index 550457e4c4f0..630f3dd31cc5 100644 --- a/net/xfrm/xfrm_device.c +++ b/net/xfrm/xfrm_device.c @@ -229,7 +229,7 @@ struct sk_buff *validate_xmit_xfrm(struct sk_buff *skb, netdev_features_t featur EXPORT_SYMBOL_GPL(validate_xmit_xfrm); int xfrm_dev_state_add(struct net *net, struct xfrm_state *x, - struct xfrm_user_offload *xuo, + const struct xfrm_user_offload *xuo, struct netlink_ext_ack *extack) { int err; -- cgit v1.2.3 From a9d155ea9b44d9b979796506bec518222f10b9e6 Mon Sep 17 00:00:00 2001 From: Antony Antony Date: Tue, 26 May 2026 21:09:51 +0200 Subject: xfrm: add XFRM_MSG_MIGRATE_STATE for single SA migration Add a new netlink method to migrate a single xfrm_state. Unlike the existing migration mechanism (SA + policy), this supports migrating only the SA and allows changing the reqid. The SA is looked up via xfrm_usersa_id, which uniquely identifies it, so old_saddr is not needed. old_daddr is carried in xfrm_usersa_id.daddr. The reqid is invariant in the old migration. Signed-off-by: Antony Antony Signed-off-by: Steffen Klassert --- include/net/xfrm.h | 10 +- include/uapi/linux/xfrm.h | 25 ++++ net/xfrm/xfrm_compat.c | 5 +- net/xfrm/xfrm_policy.c | 17 +++ net/xfrm/xfrm_state.c | 29 +++- net/xfrm/xfrm_user.c | 333 +++++++++++++++++++++++++++++++++++++++++++- security/selinux/nlmsgtab.c | 3 +- 7 files changed, 405 insertions(+), 17 deletions(-) (limited to 'include') diff --git a/include/net/xfrm.h b/include/net/xfrm.h index 5515c7b10020..e2cb2d0e5cee 100644 --- a/include/net/xfrm.h +++ b/include/net/xfrm.h @@ -684,12 +684,20 @@ struct xfrm_migrate { xfrm_address_t new_saddr; struct xfrm_encap_tmpl *encap; struct xfrm_user_offload *xuo; + struct xfrm_mark old_mark; + const struct xfrm_mark *new_mark; + struct xfrm_mark smark; u8 proto; u8 mode; - u16 reserved; + u16 msg_type; /* XFRM_MSG_MIGRATE or XFRM_MSG_MIGRATE_STATE */ + u32 flags; u32 old_reqid; + u32 new_reqid; + u32 nat_keepalive_interval; + u32 mapping_maxage; u16 old_family; u16 new_family; + const struct xfrm_selector *new_sel; }; #define XFRM_KM_TIMEOUT 30 diff --git a/include/uapi/linux/xfrm.h b/include/uapi/linux/xfrm.h index a23495c0e0a1..051f8066efd1 100644 --- a/include/uapi/linux/xfrm.h +++ b/include/uapi/linux/xfrm.h @@ -227,6 +227,9 @@ enum { #define XFRM_MSG_SETDEFAULT XFRM_MSG_SETDEFAULT XFRM_MSG_GETDEFAULT, #define XFRM_MSG_GETDEFAULT XFRM_MSG_GETDEFAULT + + XFRM_MSG_MIGRATE_STATE, +#define XFRM_MSG_MIGRATE_STATE XFRM_MSG_MIGRATE_STATE __XFRM_MSG_MAX }; #define XFRM_MSG_MAX (__XFRM_MSG_MAX - 1) @@ -507,6 +510,28 @@ struct xfrm_user_migrate { __u16 new_family; }; +struct xfrm_user_migrate_state { + struct xfrm_usersa_id id; + xfrm_address_t new_daddr; + xfrm_address_t new_saddr; + struct xfrm_mark old_mark; + struct xfrm_selector new_sel; + __u32 new_reqid; + __u32 flags; + __u16 new_family; + __u16 reserved; +}; + +/* Flags for xfrm_user_migrate_state.flags */ +enum xfrm_migrate_state_flags { + XFRM_MIGRATE_STATE_CLEAR_OFFLOAD = 1, /* do not inherit offload from existing SA */ + XFRM_MIGRATE_STATE_UPDATE_H2H_SEL = 2, /* update H2H selector from new daddr/saddr */ +}; + +/* All flags defined as of this header version; unknown bits are rejected. */ +#define XFRM_MIGRATE_STATE_KNOWN_FLAGS \ + (XFRM_MIGRATE_STATE_CLEAR_OFFLOAD | XFRM_MIGRATE_STATE_UPDATE_H2H_SEL) + struct xfrm_user_mapping { struct xfrm_usersa_id id; __u32 reqid; diff --git a/net/xfrm/xfrm_compat.c b/net/xfrm/xfrm_compat.c index b8d2e6930041..670e3512fc09 100644 --- a/net/xfrm/xfrm_compat.c +++ b/net/xfrm/xfrm_compat.c @@ -94,7 +94,8 @@ static const int compat_msg_min[XFRM_NR_MSGTYPES] = { [XFRM_MSG_GETSADINFO - XFRM_MSG_BASE] = sizeof(u32), [XFRM_MSG_NEWSPDINFO - XFRM_MSG_BASE] = sizeof(u32), [XFRM_MSG_GETSPDINFO - XFRM_MSG_BASE] = sizeof(u32), - [XFRM_MSG_MAPPING - XFRM_MSG_BASE] = XMSGSIZE(xfrm_user_mapping) + [XFRM_MSG_MAPPING - XFRM_MSG_BASE] = XMSGSIZE(xfrm_user_mapping), + [XFRM_MSG_MIGRATE_STATE - XFRM_MSG_BASE] = XMSGSIZE(xfrm_user_migrate_state), }; static const struct nla_policy compat_policy[XFRMA_MAX+1] = { @@ -162,6 +163,7 @@ static struct nlmsghdr *xfrm_nlmsg_put_compat(struct sk_buff *skb, case XFRM_MSG_NEWAE: case XFRM_MSG_REPORT: case XFRM_MSG_MIGRATE: + case XFRM_MSG_MIGRATE_STATE: case XFRM_MSG_NEWSADINFO: case XFRM_MSG_NEWSPDINFO: case XFRM_MSG_MAPPING: @@ -498,6 +500,7 @@ static int xfrm_xlate32(struct nlmsghdr *dst, const struct nlmsghdr *src, case XFRM_MSG_GETAE: case XFRM_MSG_REPORT: case XFRM_MSG_MIGRATE: + case XFRM_MSG_MIGRATE_STATE: case XFRM_MSG_NEWSADINFO: case XFRM_MSG_GETSADINFO: case XFRM_MSG_NEWSPDINFO: diff --git a/net/xfrm/xfrm_policy.c b/net/xfrm/xfrm_policy.c index cf05d778e2dd..1c558362d375 100644 --- a/net/xfrm/xfrm_policy.c +++ b/net/xfrm/xfrm_policy.c @@ -4643,6 +4643,21 @@ static int xfrm_migrate_check(const struct xfrm_migrate *m, int num_migrate, return 0; } +/* + * Fill migrate fields that are invariant in XFRM_MSG_MIGRATE: inherited + * from the existing SA unchanged. XFRM_MSG_MIGRATE_STATE can update these. + */ +static void xfrm_migrate_copy_old(const struct xfrm_state *x, + struct xfrm_migrate *mp) +{ + mp->msg_type = XFRM_MSG_MIGRATE; + mp->smark = x->props.smark; + mp->new_reqid = x->props.reqid; + mp->nat_keepalive_interval = x->nat_keepalive_interval; + mp->mapping_maxage = x->mapping_maxage; + mp->new_mark = &x->mark; +} + int xfrm_migrate(const struct xfrm_selector *sel, u8 dir, u8 type, struct xfrm_migrate *m, int num_migrate, struct xfrm_kmaddress *k, struct net *net, @@ -4682,6 +4697,8 @@ int xfrm_migrate(const struct xfrm_selector *sel, u8 dir, u8 type, nx_cur++; mp->encap = encap; mp->xuo = xuo; + xfrm_migrate_copy_old(x, mp); + xc = xfrm_state_migrate(x, mp, net, extack); if (xc) { x_new[nx_new] = xc; diff --git a/net/xfrm/xfrm_state.c b/net/xfrm/xfrm_state.c index 043e573c4f32..2c738e980b3f 100644 --- a/net/xfrm/xfrm_state.c +++ b/net/xfrm/xfrm_state.c @@ -1974,11 +1974,25 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig, goto out; memcpy(&x->id, &orig->id, sizeof(x->id)); - memcpy(&x->sel, &orig->sel, sizeof(x->sel)); + if (m->msg_type == XFRM_MSG_MIGRATE_STATE) { + if (m->flags & XFRM_MIGRATE_STATE_UPDATE_H2H_SEL) { + u8 prefixlen = (m->new_family == AF_INET6) ? 128 : 32; + + x->sel = orig->sel; + x->sel.family = m->new_family; + x->sel.prefixlen_d = prefixlen; + x->sel.prefixlen_s = prefixlen; + x->sel.daddr = m->new_daddr; + x->sel.saddr = m->new_saddr; + } else { + x->sel = *m->new_sel; + } + } else { + x->sel = orig->sel; + } memcpy(&x->lft, &orig->lft, sizeof(x->lft)); x->props.mode = orig->props.mode; x->props.replay_window = orig->props.replay_window; - x->props.reqid = orig->props.reqid; if (orig->aalg) { x->aalg = xfrm_algo_auth_clone(orig->aalg); @@ -2011,8 +2025,8 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig, x->encap = kmemdup(m->encap, sizeof(*x->encap), GFP_KERNEL); if (!x->encap) goto error; - x->mapping_maxage = orig->mapping_maxage; - x->nat_keepalive_interval = orig->nat_keepalive_interval; + x->mapping_maxage = m->mapping_maxage; + x->nat_keepalive_interval = m->nat_keepalive_interval; } if (orig->security) @@ -2029,8 +2043,9 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig, if (xfrm_replay_clone(x, orig)) goto error; - memcpy(&x->mark, &orig->mark, sizeof(x->mark)); - memcpy(&x->props.smark, &orig->props.smark, sizeof(x->props.smark)); + x->mark = m->new_mark ? *m->new_mark : m->old_mark; + + x->props.smark = m->smark; x->props.flags = orig->props.flags; x->props.extra_flags = orig->props.extra_flags; @@ -2053,7 +2068,7 @@ static struct xfrm_state *xfrm_state_clone_and_setup(struct xfrm_state *orig, goto error; } - + x->props.reqid = m->new_reqid; x->props.family = m->new_family; memcpy(&x->id.daddr, &m->new_daddr, sizeof(x->id.daddr)); memcpy(&x->props.saddr, &m->new_saddr, sizeof(x->props.saddr)); diff --git a/net/xfrm/xfrm_user.c b/net/xfrm/xfrm_user.c index de857ef65b2f..b9fbb8d13c1a 100644 --- a/net/xfrm/xfrm_user.c +++ b/net/xfrm/xfrm_user.c @@ -1201,6 +1201,16 @@ static int copy_sec_ctx(struct xfrm_sec_ctx *s, struct sk_buff *skb) return 0; } +static void xso_to_xuo(const struct xfrm_dev_offload *xso, + struct xfrm_user_offload *xuo) +{ + xuo->ifindex = xso->dev->ifindex; + if (xso->dir == XFRM_DEV_OFFLOAD_IN) + xuo->flags = XFRM_OFFLOAD_INBOUND; + if (xso->type == XFRM_DEV_OFFLOAD_PACKET) + xuo->flags |= XFRM_OFFLOAD_PACKET; +} + static int copy_user_offload(struct xfrm_dev_offload *xso, struct sk_buff *skb) { struct xfrm_user_offload *xuo; @@ -1212,11 +1222,7 @@ static int copy_user_offload(struct xfrm_dev_offload *xso, struct sk_buff *skb) xuo = nla_data(attr); memset(xuo, 0, sizeof(*xuo)); - xuo->ifindex = xso->dev->ifindex; - if (xso->dir == XFRM_DEV_OFFLOAD_IN) - xuo->flags = XFRM_OFFLOAD_INBOUND; - if (xso->type == XFRM_DEV_OFFLOAD_PACKET) - xuo->flags |= XFRM_OFFLOAD_PACKET; + xso_to_xuo(xso, xuo); return 0; } @@ -1341,7 +1347,7 @@ static int copy_to_user_encap(struct xfrm_encap_tmpl *ep, struct sk_buff *skb) return 0; } -static int xfrm_smark_put(struct sk_buff *skb, struct xfrm_mark *m) +static int xfrm_smark_put(struct sk_buff *skb, const struct xfrm_mark *m) { int ret = 0; @@ -3090,6 +3096,25 @@ nomem: } #ifdef CONFIG_XFRM_MIGRATE +static void copy_from_user_migrate_state(struct xfrm_migrate *ma, + const struct xfrm_user_migrate_state *um) +{ + memcpy(&ma->old_daddr, &um->id.daddr, sizeof(ma->old_daddr)); + memcpy(&ma->new_daddr, &um->new_daddr, sizeof(ma->new_daddr)); + memcpy(&ma->new_saddr, &um->new_saddr, sizeof(ma->new_saddr)); + + ma->proto = um->id.proto; + ma->new_reqid = um->new_reqid; + + ma->old_family = um->id.family; + ma->new_family = um->new_family; + + ma->old_mark = um->old_mark; + ma->flags = um->flags; + ma->new_sel = &um->new_sel; + ma->msg_type = XFRM_MSG_MIGRATE_STATE; +} + static int copy_from_user_migrate(struct xfrm_migrate *ma, struct xfrm_kmaddress *k, struct nlattr **attrs, int *num, @@ -3129,6 +3154,7 @@ static int copy_from_user_migrate(struct xfrm_migrate *ma, ma->old_family = um->old_family; ma->new_family = um->new_family; + ma->msg_type = XFRM_MSG_MIGRATE; } *num = i; @@ -3139,7 +3165,7 @@ static int xfrm_do_migrate(struct sk_buff *skb, struct nlmsghdr *nlh, struct nlattr **attrs, struct netlink_ext_ack *extack) { struct xfrm_userpolicy_id *pi = nlmsg_data(nlh); - struct xfrm_migrate m[XFRM_MAX_DEPTH]; + struct xfrm_migrate m[XFRM_MAX_DEPTH] = {}; struct xfrm_kmaddress km, *kmp; u8 type; int err; @@ -3192,7 +3218,298 @@ error: kfree(xuo); return err; } + +static int build_migrate_state(struct sk_buff *skb, + const struct xfrm_user_migrate_state *um, + const struct xfrm_migrate *m, + u8 dir, u32 portid, u32 seq) +{ + int err; + struct nlmsghdr *nlh; + struct xfrm_user_migrate_state *hdr; + + nlh = nlmsg_put(skb, portid, seq, XFRM_MSG_MIGRATE_STATE, + sizeof(struct xfrm_user_migrate_state), 0); + if (!nlh) + return -EMSGSIZE; + + hdr = nlmsg_data(nlh); + *hdr = *um; + hdr->new_sel = *m->new_sel; + + if (m->encap) { + err = nla_put(skb, XFRMA_ENCAP, sizeof(*m->encap), m->encap); + if (err) + goto out_cancel; + } + + if (m->xuo) { + err = nla_put(skb, XFRMA_OFFLOAD_DEV, sizeof(*m->xuo), m->xuo); + if (err) + goto out_cancel; + } + + if (m->new_mark) { + err = nla_put(skb, XFRMA_MARK, sizeof(*m->new_mark), + m->new_mark); + if (err) + goto out_cancel; + } + + err = xfrm_smark_put(skb, &m->smark); + if (err) + goto out_cancel; + + if (m->mapping_maxage) { + err = nla_put_u32(skb, XFRMA_MTIMER_THRESH, m->mapping_maxage); + if (err) + goto out_cancel; + } + + if (m->nat_keepalive_interval) { + err = nla_put_u32(skb, XFRMA_NAT_KEEPALIVE_INTERVAL, + m->nat_keepalive_interval); + if (err) + goto out_cancel; + } + + if (dir) { + err = nla_put_u8(skb, XFRMA_SA_DIR, dir); + if (err) + goto out_cancel; + } + + nlmsg_end(skb, nlh); + return 0; + +out_cancel: + nlmsg_cancel(skb, nlh); + return err; +} + +static unsigned int xfrm_migrate_state_msgsize(const struct xfrm_migrate *m, + u8 dir) +{ + return NLMSG_ALIGN(sizeof(struct xfrm_user_migrate_state)) + + (m->encap ? nla_total_size(sizeof(struct xfrm_encap_tmpl)) : 0) + + (m->xuo ? nla_total_size(sizeof(struct xfrm_user_offload)) : 0) + + (m->new_mark ? nla_total_size(sizeof(struct xfrm_mark)) : 0) + + ((m->smark.v | m->smark.m) ? nla_total_size(sizeof(u32)) * 2 : 0) + + (m->mapping_maxage ? nla_total_size(sizeof(u32)) : 0) + + (m->nat_keepalive_interval ? nla_total_size(sizeof(u32)) : 0) + + (dir ? nla_total_size(sizeof(u8)) : 0); /* XFRMA_SA_DIR */ +} + +static int xfrm_send_migrate_state(struct net *net, + const struct xfrm_user_migrate_state *um, + const struct xfrm_migrate *m, + u8 dir, u32 portid, u32 seq) +{ + int err; + struct sk_buff *skb; + + skb = nlmsg_new(xfrm_migrate_state_msgsize(m, dir), GFP_ATOMIC); + if (!skb) + return -ENOMEM; + + err = build_migrate_state(skb, um, m, dir, portid, seq); + if (err < 0) { + kfree_skb(skb); + return err; + } + + return xfrm_nlmsg_multicast(net, skb, 0, XFRMNLGRP_MIGRATE); +} + +static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh, + struct nlattr **attrs, struct netlink_ext_ack *extack) +{ + struct xfrm_user_migrate_state *um = nlmsg_data(nlh); + struct net *net = sock_net(skb->sk); + struct xfrm_user_offload xuo = {}; + struct xfrm_migrate m = {}; + struct xfrm_state *xc; + struct xfrm_state *x; + int err; + + if (!um->id.spi) { + NL_SET_ERR_MSG(extack, "Invalid SPI 0x0"); + return -EINVAL; + } + + if (um->reserved) { + NL_SET_ERR_MSG(extack, "Reserved field must be zero"); + return -EINVAL; + } + + if (um->flags & ~XFRM_MIGRATE_STATE_KNOWN_FLAGS) { + NL_SET_ERR_MSG_FMT(extack, "Unknown flags: 0x%x", + um->flags & ~XFRM_MIGRATE_STATE_KNOWN_FLAGS); + return -EINVAL; + } + + err = verify_xfrm_family(um->new_family, extack); + if (err) + return err; + + if (!(um->flags & XFRM_MIGRATE_STATE_UPDATE_H2H_SEL)) { + err = verify_selector_prefixlen(um->new_sel.family, + &um->new_sel, extack); + if (err) + return err; + } + + copy_from_user_migrate_state(&m, um); + + x = xfrm_state_lookup(net, m.old_mark.v & m.old_mark.m, + &um->id.daddr, um->id.spi, + um->id.proto, um->id.family); + if (!x) { + NL_SET_ERR_MSG(extack, "Can not find state"); + return -ESRCH; + } + + if (um->flags & XFRM_MIGRATE_STATE_UPDATE_H2H_SEL) { + u8 prefixlen = (x->props.family == AF_INET6) ? 128 : 32; + + if (x->sel.prefixlen_s != x->sel.prefixlen_d || + x->sel.prefixlen_d != prefixlen || + !xfrm_addr_equal(&x->sel.daddr, &x->id.daddr, x->props.family) || + !xfrm_addr_equal(&x->sel.saddr, &x->props.saddr, x->props.family)) { + NL_SET_ERR_MSG(extack, + "SA selector is not a single-host match for SA addresses"); + err = -EINVAL; + goto out; + } + } + + if (attrs[XFRMA_ENCAP]) { + m.encap = nla_data(attrs[XFRMA_ENCAP]); + if (m.encap->encap_type == 0) { + m.encap = NULL; /* sentinel: remove encap */ + } else if (m.encap->encap_type != UDP_ENCAP_ESPINUDP) { + NL_SET_ERR_MSG(extack, "Unsupported encapsulation type"); + err = -EINVAL; + goto out; + } + } else { + m.encap = x->encap; /* omit-to-inherit */ + } + + if (attrs[XFRMA_MTIMER_THRESH]) { + err = verify_mtimer_thresh(!!m.encap, x->dir, extack); + if (err) + goto out; + } + + if (nla_get_u32_default(attrs[XFRMA_NAT_KEEPALIVE_INTERVAL], 0) && !m.encap) { + NL_SET_ERR_MSG(extack, + "NAT_KEEPALIVE_INTERVAL requires encapsulation"); + err = -EINVAL; + goto out; + } + + if (attrs[XFRMA_OFFLOAD_DEV]) { + m.xuo = nla_data(attrs[XFRMA_OFFLOAD_DEV]); + } else { + bool inherit_offload = !(um->flags & XFRM_MIGRATE_STATE_CLEAR_OFFLOAD); + + if (inherit_offload && x->xso.dev) { + xso_to_xuo(&x->xso, &xuo); + m.xuo = &xuo; + } + } + + if (attrs[XFRMA_MARK]) + m.new_mark = nla_data(attrs[XFRMA_MARK]); + + if (attrs[XFRMA_SET_MARK]) + xfrm_smark_init(attrs, &m.smark); + else + m.smark = x->props.smark; + + m.mapping_maxage = nla_get_u32_default(attrs[XFRMA_MTIMER_THRESH], + x->mapping_maxage); + m.nat_keepalive_interval = nla_get_u32_default(attrs[XFRMA_NAT_KEEPALIVE_INTERVAL], + x->nat_keepalive_interval); + + if (m.new_family != um->id.family || + !xfrm_addr_equal(&m.new_daddr, &um->id.daddr, um->id.family)) { + u32 new_mark_key = m.new_mark ? m.new_mark->v & m.new_mark->m : + m.old_mark.v & m.old_mark.m; + struct xfrm_state *x_new; + + x_new = xfrm_state_lookup(net, new_mark_key, &m.new_daddr, + um->id.spi, um->id.proto, m.new_family); + if (x_new) { + xfrm_state_put(x_new); + NL_SET_ERR_MSG(extack, "New SA tuple already occupied"); + err = -EEXIST; + goto out; + } + } + + xc = xfrm_state_migrate_create(x, &m, net, extack); + if (!xc) { + NL_SET_ERR_MSG_WEAK(extack, "State migration clone failed"); + err = -EINVAL; + goto out; + } + + spin_lock_bh(&x->lock); + if (x->km.state != XFRM_STATE_VALID) { + spin_unlock_bh(&x->lock); + NL_SET_ERR_MSG(extack, "State already deleted"); + err = -ESRCH; + goto out_xc; + } + xfrm_migrate_sync(xc, x); /* to prevent SN/IV reuse */ + __xfrm_state_delete(x); + spin_unlock_bh(&x->lock); + + err = xfrm_state_migrate_install(x, xc, &m, extack); + if (err < 0) { + /* + * Should not occur: pre-check above ensures the new tuple is + * free under xfrm_cfg_mutex. Both SAs are gone if it does; + * restoring x would risk SN/IV reuse. + */ + goto out; + } + + /* Restore encap cleared by sentinel (type=0) during migration. */ + if (attrs[XFRMA_ENCAP]) + m.encap = nla_data(attrs[XFRMA_ENCAP]); + + m.new_sel = &xc->sel; + m.mapping_maxage = xc->mapping_maxage; + m.nat_keepalive_interval = xc->nat_keepalive_interval; + + err = xfrm_send_migrate_state(net, um, &m, xc->dir, + nlh->nlmsg_pid, nlh->nlmsg_seq); + if (err < 0) { + NL_SET_ERR_MSG(extack, "Failed to send migration notification"); + err = 0; + } + +out: + xfrm_state_put(x); + return err; +out_xc: + xc->km.state = XFRM_STATE_DEAD; + xfrm_state_put(xc); + xfrm_state_put(x); + return err; +} + #else +static int xfrm_do_migrate_state(struct sk_buff *skb, struct nlmsghdr *nlh, + struct nlattr **attrs, struct netlink_ext_ack *extack) +{ + NL_SET_ERR_MSG(extack, "XFRM_MSG_MIGRATE_STATE is not supported"); + return -ENOPROTOOPT; +} + static int xfrm_do_migrate(struct sk_buff *skb, struct nlmsghdr *nlh, struct nlattr **attrs, struct netlink_ext_ack *extack) { @@ -3345,6 +3662,7 @@ const int xfrm_msg_min[XFRM_NR_MSGTYPES] = { [XFRM_MSG_GETSPDINFO - XFRM_MSG_BASE] = sizeof(u32), [XFRM_MSG_SETDEFAULT - XFRM_MSG_BASE] = XMSGSIZE(xfrm_userpolicy_default), [XFRM_MSG_GETDEFAULT - XFRM_MSG_BASE] = XMSGSIZE(xfrm_userpolicy_default), + [XFRM_MSG_MIGRATE_STATE - XFRM_MSG_BASE] = XMSGSIZE(xfrm_user_migrate_state), }; EXPORT_SYMBOL_GPL(xfrm_msg_min); @@ -3438,6 +3756,7 @@ static const struct xfrm_link { [XFRM_MSG_GETSPDINFO - XFRM_MSG_BASE] = { .doit = xfrm_get_spdinfo }, [XFRM_MSG_SETDEFAULT - XFRM_MSG_BASE] = { .doit = xfrm_set_default }, [XFRM_MSG_GETDEFAULT - XFRM_MSG_BASE] = { .doit = xfrm_get_default }, + [XFRM_MSG_MIGRATE_STATE - XFRM_MSG_BASE] = { .doit = xfrm_do_migrate_state }, }; static int xfrm_reject_unused_attr(int type, struct nlattr **attrs, diff --git a/security/selinux/nlmsgtab.c b/security/selinux/nlmsgtab.c index 2c0b07f9fbbd..655d2616c9d2 100644 --- a/security/selinux/nlmsgtab.c +++ b/security/selinux/nlmsgtab.c @@ -128,6 +128,7 @@ static const struct nlmsg_perm nlmsg_xfrm_perms[] = { { XFRM_MSG_MAPPING, NETLINK_XFRM_SOCKET__NLMSG_READ }, { XFRM_MSG_SETDEFAULT, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, { XFRM_MSG_GETDEFAULT, NETLINK_XFRM_SOCKET__NLMSG_READ }, + { XFRM_MSG_MIGRATE_STATE, NETLINK_XFRM_SOCKET__NLMSG_WRITE }, }; static const struct nlmsg_perm nlmsg_audit_perms[] = { @@ -203,7 +204,7 @@ int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32 *perm) * structures at the top of this file with the new mappings * before updating the BUILD_BUG_ON() macro! */ - BUILD_BUG_ON(XFRM_MSG_MAX != XFRM_MSG_GETDEFAULT); + BUILD_BUG_ON(XFRM_MSG_MAX != XFRM_MSG_MIGRATE_STATE); if (selinux_policycap_netlink_xperm()) { *perm = NETLINK_XFRM_SOCKET__NLMSG; -- cgit v1.2.3 From 251bc9db88fb351ea2dfbd976c4e6aaae6507a91 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Tue, 2 Jun 2026 18:28:30 -0700 Subject: net: rename netdev_ops_assert_locked() Jakub suggests renaming the existing assert to match the netdev_lock_ops_compat() semantics. We want netdev_assert_locked_ops() to mean - if the driver is ops locked - check that it's holding the device lock. The existing helper check for either ops lock or rtnl_lock, which is the locking behavior of netdev_lock_ops_compat(). The reason for naming divergence is likely that netdev_ops_assert_locked() predated the _compat() helpers. Suggested-by: Jakub Sitnicki Reviewed-by: Nicolai Buchwitz Reviewed-by: Jakub Sitnicki Acked-by: Stanislav Fomichev Link: https://patch.msgid.link/20260603012840.2254293-2-kuba@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 2 +- include/net/netdev_lock.h | 6 +++--- net/core/dev.c | 26 +++++++++++++------------- net/core/dev_addr_lists.c | 2 +- net/core/link_watch.c | 2 +- net/core/lock_debug.c | 2 +- net/core/netdev_queues.c | 2 +- net/ethtool/netlink.c | 4 ++-- net/ipv6/addrconf.c | 2 +- net/xdp/xsk_buff_pool.c | 2 +- 10 files changed, 25 insertions(+), 25 deletions(-) (limited to 'include') diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index 3587f39202d2..2402cc865e76 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -16762,7 +16762,7 @@ int bnxt_restore_pf_fw_resources(struct bnxt *bp) { int rc; - netdev_ops_assert_locked(bp->dev); + netdev_assert_locked_ops_compat(bp->dev); bnxt_hwrm_func_qcaps(bp); if (netif_running(bp->dev)) diff --git a/include/net/netdev_lock.h b/include/net/netdev_lock.h index 3d3aef80beac..8e84d29b0bfb 100644 --- a/include/net/netdev_lock.h +++ b/include/net/netdev_lock.h @@ -64,7 +64,7 @@ static inline void netdev_unlock_full_to_ops(struct net_device *dev) netdev_unlock(dev); } -static inline void netdev_ops_assert_locked(const struct net_device *dev) +static inline void netdev_assert_locked_ops_compat(const struct net_device *dev) { if (netdev_need_ops_lock(dev)) lockdep_assert_held(&dev->lock); @@ -73,11 +73,11 @@ static inline void netdev_ops_assert_locked(const struct net_device *dev) } static inline void -netdev_ops_assert_locked_or_invisible(const struct net_device *dev) +netdev_assert_locked_ops_compat_or_invisible(const struct net_device *dev) { if (dev->reg_state == NETREG_REGISTERED || dev->reg_state == NETREG_UNREGISTERING) - netdev_ops_assert_locked(dev); + netdev_assert_locked_ops_compat(dev); } static inline void netdev_lock_ops_compat(struct net_device *dev) diff --git a/net/core/dev.c b/net/core/dev.c index 804e8ad25010..1ecd5691992e 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -1593,7 +1593,7 @@ EXPORT_SYMBOL(netdev_features_change); void netif_state_change(struct net_device *dev) { - netdev_ops_assert_locked_or_invisible(dev); + netdev_assert_locked_ops_compat_or_invisible(dev); if (dev->flags & IFF_UP) { struct netdev_notifier_change_info change_info = { @@ -1693,7 +1693,7 @@ static int __dev_open(struct net_device *dev, struct netlink_ext_ack *extack) set_bit(__LINK_STATE_START, &dev->state); - netdev_ops_assert_locked(dev); + netdev_assert_locked_ops_compat(dev); if (ops->ndo_validate_addr) ret = ops->ndo_validate_addr(dev); @@ -1770,7 +1770,7 @@ static void __dev_close_many(struct list_head *head) * event. */ - netdev_ops_assert_locked(dev); + netdev_assert_locked_ops_compat(dev); if (ops->ndo_stop) ops->ndo_stop(dev); @@ -3198,7 +3198,7 @@ int netif_set_real_num_tx_queues(struct net_device *dev, unsigned int txq) if (dev->reg_state == NETREG_REGISTERED || dev->reg_state == NETREG_UNREGISTERING) { - netdev_ops_assert_locked(dev); + netdev_assert_locked_ops_compat(dev); rc = netdev_queue_update_kobjects(dev, dev->real_num_tx_queues, txq); @@ -3247,7 +3247,7 @@ int netif_set_real_num_rx_queues(struct net_device *dev, unsigned int rxq) return -EINVAL; if (dev->reg_state == NETREG_REGISTERED) { - netdev_ops_assert_locked(dev); + netdev_assert_locked_ops_compat(dev); rc = net_rx_queue_update_kobjects(dev, dev->real_num_rx_queues, rxq); @@ -7294,7 +7294,7 @@ void netif_queue_set_napi(struct net_device *dev, unsigned int queue_index, if (WARN_ON_ONCE(napi && !napi->dev)) return; - netdev_ops_assert_locked_or_invisible(dev); + netdev_assert_locked_ops_compat_or_invisible(dev); switch (type) { case NETDEV_QUEUE_TYPE_RX: @@ -9589,7 +9589,7 @@ int __dev_set_promiscuity(struct net_device *dev, int inc, bool notify) kuid_t uid; kgid_t gid; - netdev_ops_assert_locked(dev); + netdev_assert_locked_ops_compat(dev); promiscuity = dev->promiscuity + inc; if (promiscuity == 0) { @@ -9648,7 +9648,7 @@ int netif_set_allmulti(struct net_device *dev, int inc, bool notify) unsigned int old_flags = dev->flags, old_gflags = dev->gflags; unsigned int allmulti, flags; - netdev_ops_assert_locked(dev); + netdev_assert_locked_ops_compat(dev); allmulti = dev->allmulti + inc; if (allmulti == 0) { @@ -9716,7 +9716,7 @@ int __dev_change_flags(struct net_device *dev, unsigned int flags, unsigned int old_flags = dev->flags; int ret; - netdev_ops_assert_locked(dev); + netdev_assert_locked_ops_compat(dev); /* * Set the flags on our device. @@ -9864,7 +9864,7 @@ int netif_set_mtu_ext(struct net_device *dev, int new_mtu, { int err, orig_mtu; - netdev_ops_assert_locked(dev); + netdev_assert_locked_ops_compat(dev); if (new_mtu == dev->mtu) return 0; @@ -10317,7 +10317,7 @@ static int dev_xdp_install(struct net_device *dev, enum bpf_xdp_mode mode, struct netdev_bpf xdp; int err; - netdev_ops_assert_locked(dev); + netdev_assert_locked_ops_compat(dev); if (dev->cfg->hds_config == ETHTOOL_TCP_DATA_SPLIT_ENABLED && prog && !prog->aux->xdp_has_frags) { @@ -10769,7 +10769,7 @@ u32 dev_get_min_mp_channel_count(const struct net_device *dev) { int i; - netdev_ops_assert_locked(dev); + netdev_assert_locked_ops_compat(dev); for (i = dev->real_num_rx_queues - 1; i >= 0; i--) if (dev->_rx[i].mp_params.mp_priv) @@ -10997,7 +10997,7 @@ int __netdev_update_features(struct net_device *dev) int err = -1; ASSERT_RTNL(); - netdev_ops_assert_locked(dev); + netdev_assert_locked_ops_compat(dev); features = netdev_get_wanted_features(dev); diff --git a/net/core/dev_addr_lists.c b/net/core/dev_addr_lists.c index d73fcb0c6785..6b493af8dc8b 100644 --- a/net/core/dev_addr_lists.c +++ b/net/core/dev_addr_lists.c @@ -1260,7 +1260,7 @@ static void netif_rx_mode_run(struct net_device *dev) int err; might_sleep(); - netdev_ops_assert_locked(dev); + netdev_assert_locked_ops_compat(dev); __hw_addr_init(&uc_snap); __hw_addr_init(&mc_snap); diff --git a/net/core/link_watch.c b/net/core/link_watch.c index ff2c1d4538ef..9c35aac8b2e9 100644 --- a/net/core/link_watch.c +++ b/net/core/link_watch.c @@ -277,7 +277,7 @@ static bool linkwatch_clean_dev(struct net_device *dev) void __linkwatch_sync_dev(struct net_device *dev) { - netdev_ops_assert_locked(dev); + netdev_assert_locked_ops_compat(dev); if (linkwatch_clean_dev(dev)) { linkwatch_do_dev(dev); diff --git a/net/core/lock_debug.c b/net/core/lock_debug.c index 9e9fb25314b9..14fd8fcdcd56 100644 --- a/net/core/lock_debug.c +++ b/net/core/lock_debug.c @@ -24,7 +24,7 @@ int netdev_debug_event(struct notifier_block *nb, unsigned long event, case NETDEV_CHANGE: case NETDEV_REGISTER: case NETDEV_UP: - netdev_ops_assert_locked(dev); + netdev_assert_locked_ops_compat(dev); fallthrough; case NETDEV_DOWN: case NETDEV_REBOOT: diff --git a/net/core/netdev_queues.c b/net/core/netdev_queues.c index 73fb28087a93..4d6864bb4f6b 100644 --- a/net/core/netdev_queues.c +++ b/net/core/netdev_queues.c @@ -40,7 +40,7 @@ struct device *netdev_queue_get_dma_dev(struct net_device *dev, struct netdev_rx_queue *hw_rxq; struct device *dma_dev; - netdev_ops_assert_locked(dev); + netdev_assert_locked_ops_compat(dev); /* Only RX side supports queue leasing today. */ if (type != NETDEV_QUEUE_TYPE_RX || !netif_rxq_is_leased(dev, idx)) diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c index 6cbd13b61bd1..25e22c48060a 100644 --- a/net/ethtool/netlink.c +++ b/net/ethtool/netlink.c @@ -98,7 +98,7 @@ int ethnl_ops_begin(struct net_device *dev) if (dev->dev.parent) pm_runtime_get_sync(dev->dev.parent); - netdev_ops_assert_locked(dev); + netdev_assert_locked_ops_compat(dev); if (!netif_device_present(dev) || dev->reg_state >= NETREG_UNREGISTERING) { @@ -1005,7 +1005,7 @@ static void ethnl_default_notify(struct net_device *dev, unsigned int cmd, ops->req_info_size - sizeof(*req_info)); } - netdev_ops_assert_locked(dev); + netdev_assert_locked_ops_compat(dev); ethnl_init_reply_data(reply_data, ops, dev); ret = ops->prepare_data(req_info, reply_data, &info); diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c index f5a93155e7eb..af3a0e94773c 100644 --- a/net/ipv6/addrconf.c +++ b/net/ipv6/addrconf.c @@ -380,7 +380,7 @@ static struct inet6_dev *ipv6_add_dev(struct net_device *dev) int err = -ENOMEM; ASSERT_RTNL(); - netdev_ops_assert_locked(dev); + netdev_assert_locked_ops_compat(dev); if (dev->mtu < IPV6_MIN_MTU && dev != blackhole_netdev) return ERR_PTR(-EINVAL); diff --git a/net/xdp/xsk_buff_pool.c b/net/xdp/xsk_buff_pool.c index d981cfdd8535..1f28a9641571 100644 --- a/net/xdp/xsk_buff_pool.c +++ b/net/xdp/xsk_buff_pool.c @@ -239,7 +239,7 @@ int xp_assign_dev(struct xsk_buff_pool *pool, bpf.xsk.pool = pool; bpf.xsk.queue_id = queue_id; - netdev_ops_assert_locked(netdev); + netdev_assert_locked_ops_compat(netdev); err = netdev->netdev_ops->ndo_bpf(netdev, &bpf); if (err) goto err_unreg_pool; -- cgit v1.2.3 From 1d3964e29e53b02a7c61ed6d6e4aeccba3275ebc Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Tue, 2 Jun 2026 18:28:31 -0700 Subject: net: ethtool: cmis_cdb: hold instance lock for ops locked devices FW module flashing was written so that the flashing happens without holding rtnl_lock. This allows flashing multiple modules at once. Current drivers can handle that well, but we should let drivers depend on the netdev instance lock. Instance lock is per netdev, and so is the module so we won't break parallel updates. Reviewed-by: Jakub Sitnicki Acked-by: Stanislav Fomichev Link: https://patch.msgid.link/20260603012840.2254293-3-kuba@kernel.org Signed-off-by: Jakub Kicinski --- include/net/netdev_lock.h | 6 ++++++ net/ethtool/cmis_cdb.c | 3 +++ net/ethtool/cmis_fw_update.c | 8 ++------ net/ethtool/module.c | 2 ++ 4 files changed, 13 insertions(+), 6 deletions(-) (limited to 'include') diff --git a/include/net/netdev_lock.h b/include/net/netdev_lock.h index 8e84d29b0bfb..d3daec4e93f3 100644 --- a/include/net/netdev_lock.h +++ b/include/net/netdev_lock.h @@ -80,6 +80,12 @@ netdev_assert_locked_ops_compat_or_invisible(const struct net_device *dev) netdev_assert_locked_ops_compat(dev); } +static inline void netdev_assert_locked_ops(const struct net_device *dev) +{ + if (netdev_need_ops_lock(dev)) + netdev_assert_locked(dev); +} + static inline void netdev_lock_ops_compat(struct net_device *dev) { if (netdev_need_ops_lock(dev)) diff --git a/net/ethtool/cmis_cdb.c b/net/ethtool/cmis_cdb.c index f3a53a984460..a4e8f4b3fb75 100644 --- a/net/ethtool/cmis_cdb.c +++ b/net/ethtool/cmis_cdb.c @@ -2,6 +2,7 @@ #include #include +#include #include "common.h" #include "module_fw.h" @@ -179,6 +180,7 @@ cmis_cdb_validate_password(struct ethtool_cmis_cdb *cdb, pe_pl = *((struct cmis_password_entry_pl *)page_data.data); pe_pl.password = params->password; + netdev_assert_locked_ops(dev); err = ops->set_module_eeprom_by_page(dev, &page_data, &extack); if (err < 0) { if (extack._msg) @@ -546,6 +548,7 @@ __ethtool_cmis_cdb_execute_cmd(struct net_device *dev, if (!page_data->data) return -ENOMEM; + netdev_assert_locked_ops(dev); err = ops->set_module_eeprom_by_page(dev, page_data, &extack); if (err < 0) { if (extack._msg) diff --git a/net/ethtool/cmis_fw_update.c b/net/ethtool/cmis_fw_update.c index 291d04d2776a..dff83807e975 100644 --- a/net/ethtool/cmis_fw_update.c +++ b/net/ethtool/cmis_fw_update.c @@ -435,13 +435,9 @@ cmis_fw_update_commit_image(struct ethtool_cmis_cdb *cdb, static int cmis_fw_update_reset(struct net_device *dev) { __u32 reset_data = ETH_RESET_PHY; - int ret; - netdev_lock_ops(dev); - ret = dev->ethtool_ops->reset(dev, &reset_data); - netdev_unlock_ops(dev); - - return ret; + netdev_assert_locked_ops(dev); + return dev->ethtool_ops->reset(dev, &reset_data); } void diff --git a/net/ethtool/module.c b/net/ethtool/module.c index ea4fb2a76650..c3388e6d7ec8 100644 --- a/net/ethtool/module.c +++ b/net/ethtool/module.c @@ -226,7 +226,9 @@ static void module_flash_fw_work(struct work_struct *work) module_fw = container_of(work, struct ethtool_module_fw_flash, work); dev = module_fw->fw_update.dev; + netdev_lock_ops(dev); ethtool_cmis_fw_update(&module_fw->fw_update); + netdev_unlock_ops(dev); module_flash_fw_work_list_del(&module_fw->list); -- cgit v1.2.3 From f32fe1d79a18592b96342a0016cd62cf9601a054 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Tue, 2 Jun 2026 18:28:33 -0700 Subject: net: ethtool: add netif_get_link_ksettings() for correct ops-locked use __ethtool_get_link_ksettings() is exported and called from sysfs and many drivers. It invokes ethtool_ops->get_link_ksettings so by our own docs it should be holding netdev lock for ops locked devices. Looks like commit 2bcf4772e45a ("net: ethtool: try to protect all callback with netdev instance lock") missed adding the ops lock here. There's a number of callers we need to fix up so let's add the netif_get_link_ksettings() helper first, without any actual locking changes (this commit is a nop). Not treating this as a fix because I don't think any driver cares at this point, but if we want to remove the rtnl_lock protection this will become critical. Reviewed-by: Maxime Chevallier Acked-by: Stanislav Fomichev Link: https://patch.msgid.link/20260603012840.2254293-5-kuba@kernel.org Signed-off-by: Jakub Kicinski --- include/linux/ethtool.h | 2 ++ net/ethtool/ioctl.c | 17 ++++++++++++++--- net/ethtool/linkinfo.c | 4 ++-- net/ethtool/linkmodes.c | 4 ++-- 4 files changed, 20 insertions(+), 7 deletions(-) (limited to 'include') diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h index 1cb0740ba331..f51346a6a686 100644 --- a/include/linux/ethtool.h +++ b/include/linux/ethtool.h @@ -325,6 +325,8 @@ struct ethtool_link_ksettings { extern int __ethtool_get_link_ksettings(struct net_device *dev, struct ethtool_link_ksettings *link_ksettings); +int netif_get_link_ksettings(struct net_device *dev, + struct ethtool_link_ksettings *link_ksettings); struct ethtool_keee { __ETHTOOL_DECLARE_LINK_MODE_MASK(supported); diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c index bd97f9b9bf18..49da873b673d 100644 --- a/net/ethtool/ioctl.c +++ b/net/ethtool/ioctl.c @@ -436,10 +436,10 @@ struct ethtool_link_usettings { }; /* Internal kernel helper to query a device ethtool_link_settings. */ -int __ethtool_get_link_ksettings(struct net_device *dev, - struct ethtool_link_ksettings *link_ksettings) +int netif_get_link_ksettings(struct net_device *dev, + struct ethtool_link_ksettings *link_ksettings) { - ASSERT_RTNL(); + /* once callers fixed - assert ops locked */ if (!dev->ethtool_ops->get_link_ksettings) return -EOPNOTSUPP; @@ -450,6 +450,17 @@ int __ethtool_get_link_ksettings(struct net_device *dev, memset(link_ksettings, 0, sizeof(*link_ksettings)); return dev->ethtool_ops->get_link_ksettings(dev, link_ksettings); } +EXPORT_SYMBOL(netif_get_link_ksettings); + +/* Convenience helper for callers that hold only rtnl_lock(). */ +int __ethtool_get_link_ksettings(struct net_device *dev, + struct ethtool_link_ksettings *link_ksettings) +{ + ASSERT_RTNL(); + + /* once callers fixed - take the ops lock around this call */ + return netif_get_link_ksettings(dev, link_ksettings); +} EXPORT_SYMBOL(__ethtool_get_link_ksettings); /* convert ethtool_link_usettings in user space to a kernel internal diff --git a/net/ethtool/linkinfo.c b/net/ethtool/linkinfo.c index 244ff92e2ff9..d5b3dbc53c5f 100644 --- a/net/ethtool/linkinfo.c +++ b/net/ethtool/linkinfo.c @@ -34,7 +34,7 @@ static int linkinfo_prepare_data(const struct ethnl_req_info *req_base, ret = ethnl_ops_begin(dev); if (ret < 0) return ret; - ret = __ethtool_get_link_ksettings(dev, &data->ksettings); + ret = netif_get_link_ksettings(dev, &data->ksettings); if (ret < 0) GENL_SET_ERR_MSG(info, "failed to retrieve link settings"); ethnl_ops_complete(dev); @@ -104,7 +104,7 @@ ethnl_set_linkinfo(struct ethnl_req_info *req_info, struct genl_info *info) bool mod = false; int ret; - ret = __ethtool_get_link_ksettings(dev, &ksettings); + ret = netif_get_link_ksettings(dev, &ksettings); if (ret < 0) { GENL_SET_ERR_MSG(info, "failed to retrieve link settings"); return ret; diff --git a/net/ethtool/linkmodes.c b/net/ethtool/linkmodes.c index 30d703531652..a6d32f0d9fcc 100644 --- a/net/ethtool/linkmodes.c +++ b/net/ethtool/linkmodes.c @@ -39,7 +39,7 @@ static int linkmodes_prepare_data(const struct ethnl_req_info *req_base, if (ret < 0) return ret; - ret = __ethtool_get_link_ksettings(dev, &data->ksettings); + ret = netif_get_link_ksettings(dev, &data->ksettings); if (ret < 0) { GENL_SET_ERR_MSG(info, "failed to retrieve link settings"); goto out; @@ -324,7 +324,7 @@ ethnl_set_linkmodes(struct ethnl_req_info *req_info, struct genl_info *info) bool mod = false; int ret; - ret = __ethtool_get_link_ksettings(dev, &ksettings); + ret = netif_get_link_ksettings(dev, &ksettings); if (ret < 0) { GENL_SET_ERR_MSG(info, "failed to retrieve link settings"); return ret; -- cgit v1.2.3 From f3ff3eb0d6a4080fa7f554b8478a3405b8e4befe Mon Sep 17 00:00:00 2001 From: Sven Eckelmann Date: Thu, 4 Jun 2026 21:58:31 +0200 Subject: batman-adv: uapi: keep kernel-doc in struct member order The order of the members of struct batadv_coded_packet and struct batadv_unicast_tvlv_packet didn't match the kernel doc. This is the case for all other structures and should also be done the same way for these two. Signed-off-by: Sven Eckelmann --- include/uapi/linux/batadv_packet.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'include') diff --git a/include/uapi/linux/batadv_packet.h b/include/uapi/linux/batadv_packet.h index 439132a819ea..1241285b866c 100644 --- a/include/uapi/linux/batadv_packet.h +++ b/include/uapi/linux/batadv_packet.h @@ -518,16 +518,16 @@ struct batadv_mcast_packet { * @packet_type: batman-adv packet type, part of the general header * @version: batman-adv protocol version, part of the general header * @ttl: time to live for this packet, part of the general header + * @first_ttvn: tt-version number of first included packet * @first_source: original source of first included packet * @first_orig_dest: original destination of first included packet * @first_crc: checksum of first included packet - * @first_ttvn: tt-version number of first included packet * @second_ttl: ttl of second packet + * @second_ttvn: tt version number of second included packet * @second_dest: second receiver of this coded packet * @second_source: original source of second included packet * @second_orig_dest: original destination of second included packet * @second_crc: checksum of second included packet - * @second_ttvn: tt version number of second included packet * @coded_len: length of network coded part of the payload */ struct batadv_coded_packet { @@ -554,8 +554,8 @@ struct batadv_coded_packet { * @version: batman-adv protocol version, part of the general header * @ttl: time to live for this packet, part of the general header * @reserved: reserved field (for packet alignment) - * @src: address of the source * @dst: address of the destination + * @src: address of the source * @tvlv_len: length of tvlv data following the unicast tvlv header * @align: 2 bytes to align the header to a 4 byte boundary */ -- cgit v1.2.3 From dfb67ae569bf0726187725b1ef8d89377778861e Mon Sep 17 00:00:00 2001 From: Thiyagarajan Pandiyan Date: Fri, 5 Jun 2026 11:13:07 +0530 Subject: wifi: nl80211: Increase ie_len size to prevent truncated IEs in new peer notifications Currently, ie_len in cfg80211_notify_new_peer_candidate is defined as 1-byte field, capping the maximum IE list size at 255 bytes. When a large beacon is received, the IE list is truncated, passing incomplete data to wpa_supplicant. This causes supplicant to fail parsing the IEs. Increasing the size of ie_len to allow the full length of the IE list to be forwarded properly. Signed-off-by: Thiyagarajan Pandiyan Link: https://patch.msgid.link/20260605054307.427874-1-thiyagarajan@aerlync.com Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 2 +- net/wireless/nl80211.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index f91a71c7f4db..8188ad200de5 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -8685,7 +8685,7 @@ void cfg80211_ibss_joined(struct net_device *dev, const u8 *bssid, * cfg80211 then sends a notification to userspace. */ void cfg80211_notify_new_peer_candidate(struct net_device *dev, - const u8 *macaddr, const u8 *ie, u8 ie_len, + const u8 *macaddr, const u8 *ie, size_t ie_len, int sig_dbm, gfp_t gfp); /** diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 19244708f5c3..85057bd4d565 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -21355,7 +21355,7 @@ void nl80211_send_ibss_bssid(struct cfg80211_registered_device *rdev, } void cfg80211_notify_new_peer_candidate(struct net_device *dev, const u8 *addr, - const u8 *ie, u8 ie_len, + const u8 *ie, size_t ie_len, int sig_dbm, gfp_t gfp) { struct wireless_dev *wdev = dev->ieee80211_ptr; -- cgit v1.2.3 From 4a15044a2b06748c99a8c8c3c6b3ee0a01f8004d Mon Sep 17 00:00:00 2001 From: Julian Anastasov Date: Mon, 25 May 2026 06:54:39 +0300 Subject: ipvs: add conn_max sysctl to limit connections Currently, we are using atomic_t to track the number of connections. On 64-bit setups with large memory there is a risk this counter to overflow. Also, setups with many containers may need to tune the limit for connections. Add sysctl control to limit the number of connections to 1,073,741,824 (64-bit) and 16,777,216 (32-bit). Depending on the admin's privilege, the value is used to change a soft or hard limit allowing unprivileged admins to change the soft limit in range determined by privileged admins. Link: https://sashiko.dev/#/patchset/20260523172715.94795-1-ja%40ssi.bg Link: https://sashiko.dev/#/patchset/20260430074420.26697-7-ja%40ssi.bg Link: https://sashiko.dev/#/patchset/20260522105546.13732-1-ja%40ssi.bg Signed-off-by: Julian Anastasov Signed-off-by: Pablo Neira Ayuso --- Documentation/networking/ipvs-sysctl.rst | 35 +++++++++++++++++++++ include/net/ip_vs.h | 22 +++++++++++++ net/netfilter/ipvs/ip_vs_conn.c | 10 +++++- net/netfilter/ipvs/ip_vs_ctl.c | 53 ++++++++++++++++++++++++++++++++ 4 files changed, 119 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/Documentation/networking/ipvs-sysctl.rst b/Documentation/networking/ipvs-sysctl.rst index a556439f8be7..b6bac2612420 100644 --- a/Documentation/networking/ipvs-sysctl.rst +++ b/Documentation/networking/ipvs-sysctl.rst @@ -56,6 +56,41 @@ conn_lfactor - INTEGER -4: grow if load goes above 6% (buckets = nodes * 16) 2: grow if load goes above 400% (buckets = nodes / 4) +conn_max - INTEGER + Limit for number of connections, per netns. + + Controls the soft and hard limit for number of connections. + Initially, the platform specific limit is assigned for init_net. + The value can be changed and later the soft limit propagated + to other networking namespaces. + + Privileged admin can change both limits up to the value of the + platform limit while the unprivileged admin can change only the + soft limit up to the value of the hard limit. + + For setups using conntrack=1 (CONFIG_IP_VS_NFCT for + Netfilter connection tracking) the connections can be + limited also by nf_conntrack_max. + + soft limit hard limit + ===================================================== + init_net: + create netns platform platform + priv admin 0 .. platform 0 .. platform + ===================================================== + new netns: + create netns init_net:soft init_net:soft + priv admin 0 .. platform 0 .. platform + unpriv admin 0 .. hard N/A + + Limits per platform: + 1,073,741,824 (2^30 for 64-bit) + 16,777,216 (2^24 for 32-bit) + + Possible values: 0 .. platform limit + + Default: platform limit + conn_reuse_mode - INTEGER 1 - default diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h index e517eaaa177b..49297fec448a 100644 --- a/include/net/ip_vs.h +++ b/include/net/ip_vs.h @@ -44,6 +44,14 @@ #define IP_VS_CONN_TAB_MAX_BITS 20 #endif +/* conn_max limits */ +#if BITS_PER_LONG > 32 +/* Limit of atomic_t but restricted by roundup_pow_of_two() in ip_vs_core.c */ +#define IP_VS_CONN_MAX (1 << 30) +#else +#define IP_VS_CONN_MAX (1 << 24) +#endif + /* svc_table limits */ #define IP_VS_SVC_TAB_MIN_BITS 4 #define IP_VS_SVC_TAB_MAX_BITS 20 @@ -1220,6 +1228,10 @@ struct netns_ipvs { /* sysctl variables */ int sysctl_amemthresh; int sysctl_am_droprate; +#ifdef CONFIG_SYSCTL + int sysctl_conn_max;/* soft limit for conns */ + int conn_max_limit; /* hard limit for conn_max */ +#endif int sysctl_drop_entry; int sysctl_drop_packet; int sysctl_secure_tcp; @@ -1317,6 +1329,11 @@ struct netns_ipvs { #ifdef CONFIG_SYSCTL +static inline int sysctl_conn_max(struct netns_ipvs *ipvs) +{ + return READ_ONCE(ipvs->sysctl_conn_max); +} + static inline int sysctl_sync_threshold(struct netns_ipvs *ipvs) { return ipvs->sysctl_sync_threshold[0]; @@ -1436,6 +1453,11 @@ static inline int sysctl_est_nice(struct netns_ipvs *ipvs) #else +static inline int sysctl_conn_max(struct netns_ipvs *ipvs) +{ + return IP_VS_CONN_MAX; +} + static inline int sysctl_sync_threshold(struct netns_ipvs *ipvs) { return DEFAULT_SYNC_THRESHOLD; diff --git a/net/netfilter/ipvs/ip_vs_conn.c b/net/netfilter/ipvs/ip_vs_conn.c index 9ea6b4fa78bf..e76a73d183d5 100644 --- a/net/netfilter/ipvs/ip_vs_conn.c +++ b/net/netfilter/ipvs/ip_vs_conn.c @@ -1358,9 +1358,18 @@ ip_vs_conn_new(const struct ip_vs_conn_param *p, int dest_af, struct netns_ipvs *ipvs = p->ipvs; struct ip_vs_proto_data *pd = ip_vs_proto_data_get(p->ipvs, p->protocol); + /* Increment conn_count up to conn_max */ + int count = atomic_read(&ipvs->conn_count); + int max = sysctl_conn_max(ipvs); + + do { + if (count >= max) + return NULL; + } while (!atomic_try_cmpxchg(&ipvs->conn_count, &count, count + 1)); cp = kmem_cache_alloc(ip_vs_conn_cachep, GFP_ATOMIC); if (cp == NULL) { + atomic_dec(&ipvs->conn_count); IP_VS_ERR_RL("%s(): no memory\n", __func__); return NULL; } @@ -1414,7 +1423,6 @@ ip_vs_conn_new(const struct ip_vs_conn_param *p, int dest_af, cp->in_seq.delta = 0; cp->out_seq.delta = 0; - atomic_inc(&ipvs->conn_count); if (unlikely(flags & IP_VS_CONN_F_NO_CPORT)) { int af_id = ip_vs_af_index(cp->af); diff --git a/net/netfilter/ipvs/ip_vs_ctl.c b/net/netfilter/ipvs/ip_vs_ctl.c index 16daba8cac83..f765d1506839 100644 --- a/net/netfilter/ipvs/ip_vs_ctl.c +++ b/net/netfilter/ipvs/ip_vs_ctl.c @@ -2322,6 +2322,45 @@ static int ip_vs_zero_all(struct netns_ipvs *ipvs) #ifdef CONFIG_SYSCTL +static int +proc_do_conn_max(const struct ctl_table *table, int write, + void *buffer, size_t *lenp, loff_t *ppos) +{ + int *valp = table->data; + /* We can not use *valp to check if new value is provided, use INT_MIN + * for this because different admins change different limits. + */ + int unset = INT_MIN; + int val = write ? unset : READ_ONCE(*valp); + int rc; + + const struct ctl_table tmp = { + .data = &val, + .maxlen = sizeof(int), + }; + + rc = proc_dointvec(&tmp, write, buffer, lenp, ppos); + if (write && !rc && val != unset) { + struct netns_ipvs *ipvs = table->extra2; + bool priv = capable(CAP_NET_ADMIN); + int max; + + mutex_lock(&ipvs->service_mutex); + /* Unprivileged admins can not go above the hard limit */ + max = priv ? IP_VS_CONN_MAX : ipvs->conn_max_limit; + if (val < 0 || val > max) { + rc = -EINVAL; + } else { + /* Privileged admin changes both limits */ + if (priv) + ipvs->conn_max_limit = val; + WRITE_ONCE(*valp, val); + } + mutex_unlock(&ipvs->service_mutex); + } + return rc; +} + static int proc_do_defense_mode(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos) @@ -2626,6 +2665,12 @@ static struct ctl_table vs_vars[] = { .mode = 0644, .proc_handler = proc_dointvec, }, + { + .procname = "conn_max", + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_do_conn_max, + }, { .procname = "drop_entry", .maxlen = sizeof(int), @@ -4980,6 +5025,14 @@ static int __net_init ip_vs_control_net_init_sysctl(struct netns_ipvs *ipvs) tbl[idx++].data = &ipvs->sysctl_amemthresh; ipvs->sysctl_am_droprate = 10; tbl[idx++].data = &ipvs->sysctl_am_droprate; + + /* Inherit both limits from init_net:conn_max */ + ipvs->conn_max_limit = net_eq(net, &init_net) ? IP_VS_CONN_MAX : + READ_ONCE(*(int *)vs_vars[idx].data); + ipvs->sysctl_conn_max = ipvs->conn_max_limit; + tbl[idx].extra2 = ipvs; + tbl[idx++].data = &ipvs->sysctl_conn_max; + tbl[idx++].data = &ipvs->sysctl_drop_entry; tbl[idx++].data = &ipvs->sysctl_drop_packet; #ifdef CONFIG_IP_VS_NFCT -- cgit v1.2.3 From 7d6a9cdb8d3a51d9cfe546a09a518ab3d2671549 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Thu, 4 Jun 2026 08:21:08 +0200 Subject: netfilter: cttimeout: detach dataplane timeout policy and repurpose refcount Add a refcount for struct nf_ct_timeout which is used by ct extension to set the custom ct timeout policy, this tells us that the ct timeout is being used by a conntrack entry. When the last conntrack entry drops the refcount on the ct timeout, the ct timeout is released. Remove the refcount for control plane which controls if the ruleset refers to the timeout policy. After this update, it is possible to remove the ct timeout policy from nfnetlink_cttimeout immediately. This is for simplicity not to handle two refcounts on a single object. Remove nf_queue_nf_hook_drop(): a packet sitting in nfqueue will just hold a reference to the nf_ct_timeout object until packet is reinjected, since this is part of the ct extension, this will be released by the time the conntrack is freed. nf_ct_untimeout() is still called to clean up in a best effort basis: the ct timeout on existing entries gets removed when the ct timeout goes away, but as long as the iptables ruleset still refers to the ct timeout through a template, new conntracks may keep attaching it and extend its lifetime until the rule is removed. nf_ct_untimeout() is not called anymore from module removal path, this is unlikely to find timeouts give module refcount is bumped, and the new refcount already tracks the ct timeout policy use so it is released when unused. Fixes: 50978462300f ("netfilter: add cttimeout infrastructure for fine timeout tuning") Fixes: 7e0b2b57f01d ("netfilter: nft_ct: add ct timeout support") Signed-off-by: Pablo Neira Ayuso --- include/net/netfilter/nf_conntrack_timeout.h | 27 ++++++- net/netfilter/nf_conntrack_core.c | 6 +- net/netfilter/nf_conntrack_timeout.c | 27 ++++++- net/netfilter/nfnetlink_cttimeout.c | 112 ++++++++++++--------------- net/netfilter/nft_ct.c | 7 +- net/netfilter/xt_CT.c | 2 +- 6 files changed, 107 insertions(+), 74 deletions(-) (limited to 'include') diff --git a/include/net/netfilter/nf_conntrack_timeout.h b/include/net/netfilter/nf_conntrack_timeout.h index 3a66d4abb6d6..d60aa86be019 100644 --- a/include/net/netfilter/nf_conntrack_timeout.h +++ b/include/net/netfilter/nf_conntrack_timeout.h @@ -12,6 +12,7 @@ #define CTNL_TIMEOUT_NAME_MAX 32 struct nf_ct_timeout { + refcount_t refcnt; __u16 l3num; const struct nf_conntrack_l4proto *l4proto; struct rcu_head rcu; @@ -22,6 +23,22 @@ struct nf_conn_timeout { struct nf_ct_timeout __rcu *timeout; }; +static inline void nf_ct_timeout_put(const struct nf_conn *ct) +{ +#ifdef CONFIG_NF_CONNTRACK_TIMEOUT + struct nf_conn_timeout *timeout_ext; + struct nf_ct_timeout *timeout; + + timeout_ext = nf_ct_ext_find(ct, NF_CT_EXT_TIMEOUT); + if (!timeout_ext) + return; + + timeout = rcu_dereference(timeout_ext->timeout); + if (timeout && refcount_dec_and_test(&timeout->refcnt)) + kfree_rcu(timeout, rcu); +#endif +} + static inline unsigned int * nf_ct_timeout_data(const struct nf_conn_timeout *t) { @@ -56,8 +73,14 @@ struct nf_conn_timeout *nf_ct_timeout_ext_add(struct nf_conn *ct, #ifdef CONFIG_NF_CONNTRACK_TIMEOUT struct nf_conn_timeout *timeout_ext; + if (!timeout) + return NULL; + timeout_ext = nf_ct_ext_add(ct, NF_CT_EXT_TIMEOUT, gfp); - if (timeout_ext == NULL) + if (!timeout_ext || timeout_ext->timeout) + return NULL; + + if (!refcount_inc_not_zero(&timeout->refcnt)) return NULL; rcu_assign_pointer(timeout_ext->timeout, timeout); @@ -75,7 +98,7 @@ static inline unsigned int *nf_ct_timeout_lookup(const struct nf_conn *ct) struct nf_conn_timeout *timeout_ext; timeout_ext = nf_ct_timeout_find(ct); - if (timeout_ext) + if (timeout_ext && rcu_access_pointer(timeout_ext->timeout)) timeouts = nf_ct_timeout_data(timeout_ext); #endif return timeouts; diff --git a/net/netfilter/nf_conntrack_core.c b/net/netfilter/nf_conntrack_core.c index c072a14a306a..a45b73239369 100644 --- a/net/netfilter/nf_conntrack_core.c +++ b/net/netfilter/nf_conntrack_core.c @@ -1737,16 +1737,18 @@ void nf_conntrack_free(struct nf_conn *ct) */ WARN_ON(refcount_read(&ct->ct_general.use) != 0); + rcu_read_lock(); if (ct->status & IPS_SRC_NAT_DONE) { const struct nf_nat_hook *nat_hook; - rcu_read_lock(); nat_hook = rcu_dereference(nf_nat_hook); if (nat_hook) nat_hook->remove_nat_bysrc(ct); - rcu_read_unlock(); } + nf_ct_timeout_put(ct); + rcu_read_unlock(); + kfree(ct->ext); kmem_cache_free(nf_conntrack_cachep, ct); cnet = nf_ct_pernet(net); diff --git a/net/netfilter/nf_conntrack_timeout.c b/net/netfilter/nf_conntrack_timeout.c index 0cc584d3dbb1..c81becde2afa 100644 --- a/net/netfilter/nf_conntrack_timeout.c +++ b/net/netfilter/nf_conntrack_timeout.c @@ -25,17 +25,32 @@ const struct nf_ct_timeout_hooks __rcu *nf_ct_timeout_hook __read_mostly; EXPORT_SYMBOL_GPL(nf_ct_timeout_hook); +/* nf_ct_iterate_cleanup() holds the conntrack lock. */ static int untimeout(struct nf_conn *ct, void *timeout) { struct nf_conn_timeout *timeout_ext = nf_ct_timeout_find(ct); if (timeout_ext) { - const struct nf_ct_timeout *t; + struct nf_ct_timeout *t; - t = rcu_access_pointer(timeout_ext->timeout); + rcu_read_lock(); + t = rcu_dereference(timeout_ext->timeout); + if (!t) { + rcu_read_unlock(); + return 0; + } - if (!timeout || t == timeout) + if (!timeout || t == timeout) { RCU_INIT_POINTER(timeout_ext->timeout, NULL); + + /* No race with nf_conntrack_free() which is called + * only after the conntrack has been removed from + * the hashes. + */ + if (refcount_dec_and_test(&t->refcnt)) + kfree_rcu(t, rcu); + } + rcu_read_unlock(); } /* We are not intended to delete this conntrack. */ @@ -70,6 +85,8 @@ int nf_ct_set_timeout(struct net *net, struct nf_conn *ct, const char *errmsg = NULL; int ret = 0; + WARN_ON_ONCE(!nf_ct_is_template(ct)); + rcu_read_lock(); h = rcu_dereference(nf_ct_timeout_hook); if (!h) { @@ -127,6 +144,8 @@ void nf_ct_destroy_timeout(struct nf_conn *ct) struct nf_conn_timeout *timeout_ext; const struct nf_ct_timeout_hooks *h; + WARN_ON_ONCE(!nf_ct_is_template(ct)); + rcu_read_lock(); h = rcu_dereference(nf_ct_timeout_hook); @@ -139,6 +158,8 @@ void nf_ct_destroy_timeout(struct nf_conn *ct) if (t) h->timeout_put(t); RCU_INIT_POINTER(timeout_ext->timeout, NULL); + if (t && refcount_dec_and_test(&t->refcnt)) + kfree_rcu(t, rcu); } } rcu_read_unlock(); diff --git a/net/netfilter/nfnetlink_cttimeout.c b/net/netfilter/nfnetlink_cttimeout.c index dca6826af7de..170d3db860c5 100644 --- a/net/netfilter/nfnetlink_cttimeout.c +++ b/net/netfilter/nfnetlink_cttimeout.c @@ -37,11 +37,8 @@ struct ctnl_timeout { struct list_head head; struct list_head free_head; struct rcu_head rcu_head; - refcount_t refcnt; char name[CTNL_TIMEOUT_NAME_MAX]; - - /* must be at the end */ - struct nf_ct_timeout timeout; + struct nf_ct_timeout *timeout; }; struct nfct_timeout_pernet { @@ -132,12 +129,12 @@ static int cttimeout_new_timeout(struct sk_buff *skb, /* You cannot replace one timeout policy by another of * different kind, sorry. */ - if (matching->timeout.l3num != l3num || - matching->timeout.l4proto->l4proto != l4num) + if (matching->timeout->l3num != l3num || + matching->timeout->l4proto->l4proto != l4num) return -EINVAL; - return ctnl_timeout_parse_policy(&matching->timeout.data, - matching->timeout.l4proto, + return ctnl_timeout_parse_policy(&matching->timeout->data, + matching->timeout->l4proto, info->net, cda[CTA_TIMEOUT_DATA]); } @@ -153,26 +150,35 @@ static int cttimeout_new_timeout(struct sk_buff *skb, goto err_proto_put; } - timeout = kzalloc(sizeof(struct ctnl_timeout) + - l4proto->ctnl_timeout.obj_size, GFP_KERNEL); + timeout = kzalloc(sizeof(*timeout), GFP_KERNEL); if (timeout == NULL) { ret = -ENOMEM; goto err_proto_put; } - ret = ctnl_timeout_parse_policy(&timeout->timeout.data, l4proto, + timeout->timeout = kzalloc(sizeof(*timeout->timeout) + + l4proto->ctnl_timeout.obj_size, GFP_KERNEL); + if (!timeout->timeout) { + ret = -ENOMEM; + goto err; + } + + ret = ctnl_timeout_parse_policy(&timeout->timeout->data, l4proto, info->net, cda[CTA_TIMEOUT_DATA]); if (ret < 0) - goto err; + goto err_free_timeout_policy; strcpy(timeout->name, nla_data(cda[CTA_TIMEOUT_NAME])); - timeout->timeout.l3num = l3num; - timeout->timeout.l4proto = l4proto; - refcount_set(&timeout->refcnt, 1); + timeout->timeout->l3num = l3num; + timeout->timeout->l4proto = l4proto; + refcount_set(&timeout->timeout->refcnt, 1); __module_get(THIS_MODULE); list_add_tail_rcu(&timeout->head, &pernet->nfct_timeout_list); return 0; + +err_free_timeout_policy: + kfree(timeout->timeout); err: kfree(timeout); err_proto_put: @@ -185,7 +191,7 @@ ctnl_timeout_fill_info(struct sk_buff *skb, u32 portid, u32 seq, u32 type, { struct nlmsghdr *nlh; unsigned int flags = portid ? NLM_F_MULTI : 0; - const struct nf_conntrack_l4proto *l4proto = timeout->timeout.l4proto; + const struct nf_conntrack_l4proto *l4proto = timeout->timeout->l4proto; struct nlattr *nest_parms; int ret; @@ -197,17 +203,17 @@ ctnl_timeout_fill_info(struct sk_buff *skb, u32 portid, u32 seq, u32 type, if (nla_put_string(skb, CTA_TIMEOUT_NAME, timeout->name) || nla_put_be16(skb, CTA_TIMEOUT_L3PROTO, - htons(timeout->timeout.l3num)) || + htons(timeout->timeout->l3num)) || nla_put_u8(skb, CTA_TIMEOUT_L4PROTO, l4proto->l4proto) || nla_put_be32(skb, CTA_TIMEOUT_USE, - htonl(refcount_read(&timeout->refcnt)))) + htonl(refcount_read(&timeout->timeout->refcnt)))) goto nla_put_failure; nest_parms = nla_nest_start(skb, CTA_TIMEOUT_DATA); if (!nest_parms) goto nla_put_failure; - ret = l4proto->ctnl_timeout.obj_to_nlattr(skb, &timeout->timeout.data); + ret = l4proto->ctnl_timeout.obj_to_nlattr(skb, &timeout->timeout->data); if (ret < 0) goto nla_put_failure; @@ -307,23 +313,17 @@ static int cttimeout_get_timeout(struct sk_buff *skb, return ret; } -/* try to delete object, fail if it is still in use. */ -static int ctnl_timeout_try_del(struct net *net, struct ctnl_timeout *timeout) +static void ctnl_timeout_del(struct net *net, struct ctnl_timeout *timeout) { - int ret = 0; + /* We are protected by nfnl mutex. */ + list_del_rcu(&timeout->head); + nf_ct_untimeout(net, timeout->timeout); - /* We want to avoid races with ctnl_timeout_put. So only when the - * current refcnt is 1, we decrease it to 0. - */ - if (refcount_dec_if_one(&timeout->refcnt)) { - /* We are protected by nfnl mutex. */ - list_del_rcu(&timeout->head); - nf_ct_untimeout(net, &timeout->timeout); - kfree_rcu(timeout, rcu_head); - } else { - ret = -EBUSY; - } - return ret; + if (refcount_dec_and_test(&timeout->timeout->refcnt)) + kfree_rcu(timeout->timeout, rcu); + + kfree_rcu(timeout, rcu_head); + module_put(THIS_MODULE); } static int cttimeout_del_timeout(struct sk_buff *skb, @@ -338,7 +338,7 @@ static int cttimeout_del_timeout(struct sk_buff *skb, if (!cda[CTA_TIMEOUT_NAME]) { list_for_each_entry_safe(cur, tmp, &pernet->nfct_timeout_list, head) - ctnl_timeout_try_del(info->net, cur); + ctnl_timeout_del(info->net, cur); return 0; } @@ -348,10 +348,8 @@ static int cttimeout_del_timeout(struct sk_buff *skb, if (strncmp(cur->name, name, CTNL_TIMEOUT_NAME_MAX) != 0) continue; - ret = ctnl_timeout_try_del(info->net, cur); - if (ret < 0) - return ret; - + ctnl_timeout_del(info->net, cur); + ret = 0; break; } return ret; @@ -511,24 +509,22 @@ static struct nf_ct_timeout *ctnl_timeout_find_get(struct net *net, if (strncmp(timeout->name, name, CTNL_TIMEOUT_NAME_MAX) != 0) continue; - if (!refcount_inc_not_zero(&timeout->refcnt)) + if (!refcount_inc_not_zero(&timeout->timeout->refcnt)) goto err; matching = timeout; + __module_get(THIS_MODULE); break; } err: - return matching ? &matching->timeout : NULL; + return matching ? matching->timeout : NULL; } -static void ctnl_timeout_put(struct nf_ct_timeout *t) +static void ctnl_timeout_put(struct nf_ct_timeout *timeout) { - struct ctnl_timeout *timeout = - container_of(t, struct ctnl_timeout, timeout); + if (refcount_dec_and_test(&timeout->refcnt)) + kfree_rcu(timeout, rcu); - if (refcount_dec_and_test(&timeout->refcnt)) { - kfree_rcu(timeout, rcu_head); - module_put(THIS_MODULE); - } + module_put(THIS_MODULE); } static const struct nfnl_callback cttimeout_cb[IPCTNL_MSG_TIMEOUT_MAX] = { @@ -609,8 +605,11 @@ static void __net_exit cttimeout_net_exit(struct net *net) list_for_each_entry_safe(cur, tmp, &pernet->nfct_timeout_freelist, free_head) { list_del(&cur->free_head); - if (refcount_dec_and_test(&cur->refcnt)) - kfree_rcu(cur, rcu_head); + if (refcount_dec_and_test(&cur->timeout->refcnt)) + kfree_rcu(cur->timeout, rcu); + + kfree_rcu(cur, rcu_head); + module_put(THIS_MODULE); } } @@ -649,24 +648,13 @@ err_out: return ret; } -static int untimeout(struct nf_conn *ct, void *timeout) -{ - struct nf_conn_timeout *timeout_ext = nf_ct_timeout_find(ct); - - if (timeout_ext) - RCU_INIT_POINTER(timeout_ext->timeout, NULL); - - return 0; -} - static void __exit cttimeout_exit(void) { nfnetlink_subsys_unregister(&cttimeout_subsys); unregister_pernet_subsys(&cttimeout_ops); RCU_INIT_POINTER(nf_ct_timeout_hook, NULL); - - nf_ct_iterate_destroy(untimeout, NULL); + synchronize_net(); } module_init(cttimeout_init); diff --git a/net/netfilter/nft_ct.c b/net/netfilter/nft_ct.c index 357513c6dcea..801c01c6af95 100644 --- a/net/netfilter/nft_ct.c +++ b/net/netfilter/nft_ct.c @@ -897,8 +897,6 @@ static void nft_ct_timeout_obj_eval(struct nft_object *obj, } } - rcu_assign_pointer(timeout->timeout, priv->timeout); - /* adjust the timeout as per 'new' state. ct is unconfirmed, * so the current timestamp must not be added. */ @@ -949,6 +947,7 @@ static int nft_ct_timeout_obj_init(const struct nft_ctx *ctx, timeout->l3num = l3num; timeout->l4proto = l4proto; + refcount_set(&timeout->refcnt, 1); ret = nf_ct_netns_get(ctx->net, ctx->family); if (ret < 0) @@ -969,10 +968,10 @@ static void nft_ct_timeout_obj_destroy(const struct nft_ctx *ctx, struct nft_ct_timeout_obj *priv = nft_obj_data(obj); struct nf_ct_timeout *timeout = priv->timeout; - nf_queue_nf_hook_drop(ctx->net); nf_ct_untimeout(ctx->net, timeout); nf_ct_netns_put(ctx->net, ctx->family); - kfree_rcu(priv->timeout, rcu); + if (refcount_dec_and_test(&timeout->refcnt)) + kfree_rcu(priv->timeout, rcu); } static int nft_ct_timeout_obj_dump(struct sk_buff *skb, diff --git a/net/netfilter/xt_CT.c b/net/netfilter/xt_CT.c index d2aeacf94230..b94f004d5f5c 100644 --- a/net/netfilter/xt_CT.c +++ b/net/netfilter/xt_CT.c @@ -284,7 +284,7 @@ static void xt_ct_tg_destroy(const struct xt_tgdtor_param *par, struct nf_conn_help *help; if (ct) { - if (info->helper[0] || info->timeout[0]) + if (info->helper[0]) nf_queue_nf_hook_drop(par->net); help = nfct_help(ct); -- cgit v1.2.3 From 6031487d4e273d7e7c8c7deea5061b7a9aaa9db3 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Thu, 4 Jun 2026 08:21:09 +0200 Subject: netfilter: nf_conntrack_helper: dynamically allocate struct nf_conntrack_helper Adapt all existing helpers to use a modified version of nf_ct_helper_init(), to dynamically allocate struct nf_conntrack_helper. Allocate expect_policy[] built-in into the helper to ensure this area is reachable after helper removal since a follow up patch adds refcount to track use of the nf_conntrack_helper structure from packet path so it remains around until last reference from ct helper extension is dropped. Export __nf_conntrack_helper_register() which allows to register nfnetlink_cthelper dynamically allocated helper. Adapt nfnetlink_cthelper to use the built-in expect_policy[]. This is a preparation patch to add packet path refcounting to helpers. Signed-off-by: Pablo Neira Ayuso --- include/net/netfilter/nf_conntrack_helper.h | 16 +++-- net/ipv4/netfilter/nf_nat_snmp_basic_main.c | 27 ++++--- net/netfilter/nf_conntrack_amanda.c | 39 ++++------ net/netfilter/nf_conntrack_ftp.c | 5 +- net/netfilter/nf_conntrack_h323_main.c | 107 +++++++++++----------------- net/netfilter/nf_conntrack_helper.c | 75 +++++++++++++++---- net/netfilter/nf_conntrack_irc.c | 5 +- net/netfilter/nf_conntrack_netbios_ns.c | 20 +++--- net/netfilter/nf_conntrack_pptp.c | 22 +++--- net/netfilter/nf_conntrack_sane.c | 5 +- net/netfilter/nf_conntrack_sip.c | 5 +- net/netfilter/nf_conntrack_snmp.c | 21 +++--- net/netfilter/nf_conntrack_tftp.c | 5 +- net/netfilter/nfnetlink_cthelper.c | 47 ++++++------ 14 files changed, 210 insertions(+), 189 deletions(-) (limited to 'include') diff --git a/include/net/netfilter/nf_conntrack_helper.h b/include/net/netfilter/nf_conntrack_helper.h index de2f956abf34..1956bc12bf56 100644 --- a/include/net/netfilter/nf_conntrack_helper.h +++ b/include/net/netfilter/nf_conntrack_helper.h @@ -29,13 +29,16 @@ enum nf_ct_helper_flags { #define NF_CT_HELPER_NAME_LEN 16 +/* Must be kept in sync with the classes defined by helpers */ +#define NF_CT_MAX_EXPECT_CLASSES 4 + struct nf_conntrack_helper { struct hlist_node hnode; /* Internal use. */ char name[NF_CT_HELPER_NAME_LEN]; /* name of the module */ refcount_t refcnt; struct module *me; /* pointer to self */ - const struct nf_conntrack_expect_policy *expect_policy; + struct nf_conntrack_expect_policy expect_policy[NF_CT_MAX_EXPECT_CLASSES]; /* Tuple of things we will help (compared against server response) */ struct nf_conntrack_tuple tuple; @@ -63,9 +66,6 @@ struct nf_conntrack_helper { char nat_mod_name[NF_CT_HELPER_NAME_LEN]; }; -/* Must be kept in sync with the classes defined by helpers */ -#define NF_CT_MAX_EXPECT_CLASSES 4 - /* nf_conn feature for connections that have a helper */ struct nf_conn_help { /* Helper. if any */ @@ -103,11 +103,13 @@ void nf_ct_helper_init(struct nf_conntrack_helper *helper, struct nf_conn *ct), struct module *module); -int nf_conntrack_helper_register(struct nf_conntrack_helper *); +int nf_conntrack_helper_register(struct nf_conntrack_helper *, struct nf_conntrack_helper **); +int __nf_conntrack_helper_register(struct nf_conntrack_helper *); void nf_conntrack_helper_unregister(struct nf_conntrack_helper *); -int nf_conntrack_helpers_register(struct nf_conntrack_helper *, unsigned int); -void nf_conntrack_helpers_unregister(struct nf_conntrack_helper *, +int nf_conntrack_helpers_register(struct nf_conntrack_helper *, unsigned int, + struct nf_conntrack_helper **); +void nf_conntrack_helpers_unregister(struct nf_conntrack_helper **, unsigned int); struct nf_conn_help *nf_ct_helper_ext_add(struct nf_conn *ct, gfp_t gfp); diff --git a/net/ipv4/netfilter/nf_nat_snmp_basic_main.c b/net/ipv4/netfilter/nf_nat_snmp_basic_main.c index 717b726504fe..0ede138dfd29 100644 --- a/net/ipv4/netfilter/nf_nat_snmp_basic_main.c +++ b/net/ipv4/netfilter/nf_nat_snmp_basic_main.c @@ -202,29 +202,34 @@ static const struct nf_conntrack_expect_policy snmp_exp_policy = { .timeout = 180, }; -static struct nf_conntrack_helper snmp_trap_helper __read_mostly = { - .me = THIS_MODULE, - .help = help, - .expect_policy = &snmp_exp_policy, - .name = "snmp_trap", - .tuple.src.l3num = AF_INET, - .tuple.src.u.udp.port = cpu_to_be16(SNMP_TRAP_PORT), - .tuple.dst.protonum = IPPROTO_UDP, -}; +static struct nf_conntrack_helper snmp_trap_helper __read_mostly; +static struct nf_conntrack_helper *snmp_trap_helper_ptr __read_mostly; static int __init nf_nat_snmp_basic_init(void) { + int err; + BUG_ON(nf_nat_snmp_hook != NULL); RCU_INIT_POINTER(nf_nat_snmp_hook, help); - return nf_conntrack_helper_register(&snmp_trap_helper); + nf_ct_helper_init(&snmp_trap_helper, AF_INET, IPPROTO_UDP, + "snmp_trap", SNMP_TRAP_PORT, SNMP_TRAP_PORT, SNMP_TRAP_PORT, + &snmp_exp_policy, 0, help, NULL, THIS_MODULE); + + err = nf_conntrack_helper_register(&snmp_trap_helper, &snmp_trap_helper_ptr); + if (err < 0) { + RCU_INIT_POINTER(nf_nat_snmp_hook, NULL); + return err; + } + + return 0; } static void __exit nf_nat_snmp_basic_fini(void) { RCU_INIT_POINTER(nf_nat_snmp_hook, NULL); synchronize_rcu(); - nf_conntrack_helper_unregister(&snmp_trap_helper); + nf_conntrack_helper_unregister(snmp_trap_helper_ptr); } module_init(nf_nat_snmp_basic_init); diff --git a/net/netfilter/nf_conntrack_amanda.c b/net/netfilter/nf_conntrack_amanda.c index d2c09e8dd872..ddafbdfc96dc 100644 --- a/net/netfilter/nf_conntrack_amanda.c +++ b/net/netfilter/nf_conntrack_amanda.c @@ -169,35 +169,15 @@ static const struct nf_conntrack_expect_policy amanda_exp_policy = { .timeout = 180, }; -static struct nf_conntrack_helper amanda_helper[2] __read_mostly = { - { - .name = HELPER_NAME, - .me = THIS_MODULE, - .help = amanda_help, - .tuple.src.l3num = AF_INET, - .tuple.src.u.udp.port = cpu_to_be16(10080), - .tuple.dst.protonum = IPPROTO_UDP, - .expect_policy = &amanda_exp_policy, - .nat_mod_name = NF_NAT_HELPER_NAME(HELPER_NAME), - }, - { - .name = "amanda", - .me = THIS_MODULE, - .help = amanda_help, - .tuple.src.l3num = AF_INET6, - .tuple.src.u.udp.port = cpu_to_be16(10080), - .tuple.dst.protonum = IPPROTO_UDP, - .expect_policy = &amanda_exp_policy, - .nat_mod_name = NF_NAT_HELPER_NAME(HELPER_NAME), - }, -}; +static struct nf_conntrack_helper amanda_helper[2] __read_mostly; +static struct nf_conntrack_helper *amanda_helper_ptr[2] __read_mostly; static void __exit nf_conntrack_amanda_fini(void) { int i; - nf_conntrack_helpers_unregister(amanda_helper, - ARRAY_SIZE(amanda_helper)); + nf_conntrack_helpers_unregister(amanda_helper_ptr, + ARRAY_SIZE(amanda_helper_ptr)); for (i = 0; i < ARRAY_SIZE(search); i++) textsearch_destroy(search[i].ts); } @@ -217,8 +197,17 @@ static int __init nf_conntrack_amanda_init(void) goto err1; } } + + nf_ct_helper_init(&amanda_helper[0], AF_INET, IPPROTO_UDP, + HELPER_NAME, 10080, 10080, 10080, + &amanda_exp_policy, 0, amanda_help, NULL, THIS_MODULE); + nf_ct_helper_init(&amanda_helper[1], AF_INET6, IPPROTO_UDP, + HELPER_NAME, 10080, 10080, 10080, + &amanda_exp_policy, 0, amanda_help, NULL, THIS_MODULE); + ret = nf_conntrack_helpers_register(amanda_helper, - ARRAY_SIZE(amanda_helper)); + ARRAY_SIZE(amanda_helper), + amanda_helper_ptr); if (ret < 0) goto err1; return 0; diff --git a/net/netfilter/nf_conntrack_ftp.c b/net/netfilter/nf_conntrack_ftp.c index dc6f0017ca6b..c7777f37371a 100644 --- a/net/netfilter/nf_conntrack_ftp.c +++ b/net/netfilter/nf_conntrack_ftp.c @@ -555,6 +555,7 @@ static int nf_ct_ftp_from_nlattr(struct nlattr *attr, struct nf_conn *ct) } static struct nf_conntrack_helper ftp[MAX_PORTS * 2] __read_mostly; +static struct nf_conntrack_helper *ftp_ptr[MAX_PORTS * 2] __read_mostly; static const struct nf_conntrack_expect_policy ftp_exp_policy = { .max_expected = 1, @@ -563,7 +564,7 @@ static const struct nf_conntrack_expect_policy ftp_exp_policy = { static void __exit nf_conntrack_ftp_fini(void) { - nf_conntrack_helpers_unregister(ftp, ports_c * 2); + nf_conntrack_helpers_unregister(ftp_ptr, ports_c * 2); } static int __init nf_conntrack_ftp_init(void) @@ -588,7 +589,7 @@ static int __init nf_conntrack_ftp_init(void) nf_ct_ftp_from_nlattr, THIS_MODULE); } - ret = nf_conntrack_helpers_register(ftp, ports_c * 2); + ret = nf_conntrack_helpers_register(ftp, ports_c * 2, ftp_ptr); if (ret < 0) { pr_err("failed to register helpers\n"); return ret; diff --git a/net/netfilter/nf_conntrack_h323_main.c b/net/netfilter/nf_conntrack_h323_main.c index b2fe6554b9cf..ebae9fdab897 100644 --- a/net/netfilter/nf_conntrack_h323_main.c +++ b/net/netfilter/nf_conntrack_h323_main.c @@ -577,14 +577,8 @@ static const struct nf_conntrack_expect_policy h245_exp_policy = { .timeout = 240, }; -static struct nf_conntrack_helper nf_conntrack_helper_h245 __read_mostly = { - .name = "H.245", - .me = THIS_MODULE, - .tuple.src.l3num = AF_UNSPEC, - .tuple.dst.protonum = IPPROTO_UDP, - .help = h245_help, - .expect_policy = &h245_exp_policy, -}; +static struct nf_conntrack_helper nf_conntrack_helper_h245 __read_mostly; +static struct nf_conntrack_helper *nf_conntrack_helper_h245_ptr __read_mostly; int get_h225_addr(struct nf_conn *ct, unsigned char *data, TransportAddress *taddr, @@ -643,7 +637,7 @@ static int expect_h245(struct sk_buff *skb, struct nf_conn *ct, &ct->tuplehash[!dir].tuple.src.u3, &ct->tuplehash[!dir].tuple.dst.u3, IPPROTO_TCP, NULL, &port); - rcu_assign_pointer(exp->assign_helper, &nf_conntrack_helper_h245); + rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_h245_ptr); nathook = rcu_dereference(nfct_h323_nat_hook); if (memcmp(&ct->tuplehash[dir].tuple.src.u3, @@ -732,6 +726,9 @@ static int callforward_do_filter(struct net *net, } +static struct nf_conntrack_helper nf_conntrack_helper_q931[2] __read_mostly; +static struct nf_conntrack_helper *nf_conntrack_helper_q931_ptr[2] __read_mostly; + static int expect_callforwarding(struct sk_buff *skb, struct nf_conn *ct, enum ip_conntrack_info ctinfo, @@ -767,7 +764,7 @@ static int expect_callforwarding(struct sk_buff *skb, nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT, nf_ct_l3num(ct), &ct->tuplehash[!dir].tuple.src.u3, &addr, IPPROTO_TCP, NULL, &port); - rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_q931); + rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_q931_ptr[0]); nathook = rcu_dereference(nfct_h323_nat_hook); if (memcmp(&ct->tuplehash[dir].tuple.src.u3, @@ -1140,27 +1137,6 @@ static const struct nf_conntrack_expect_policy q931_exp_policy = { .timeout = 240, }; -static struct nf_conntrack_helper nf_conntrack_helper_q931[] __read_mostly = { - { - .name = "Q.931", - .me = THIS_MODULE, - .tuple.src.l3num = AF_INET, - .tuple.src.u.tcp.port = cpu_to_be16(Q931_PORT), - .tuple.dst.protonum = IPPROTO_TCP, - .help = q931_help, - .expect_policy = &q931_exp_policy, - }, - { - .name = "Q.931", - .me = THIS_MODULE, - .tuple.src.l3num = AF_INET6, - .tuple.src.u.tcp.port = cpu_to_be16(Q931_PORT), - .tuple.dst.protonum = IPPROTO_TCP, - .help = q931_help, - .expect_policy = &q931_exp_policy, - }, -}; - static unsigned char *get_udp_data(struct sk_buff *skb, unsigned int protoff, int *datalen) { @@ -1234,7 +1210,7 @@ static int expect_q931(struct sk_buff *skb, struct nf_conn *ct, &ct->tuplehash[!dir].tuple.src.u3 : NULL, &ct->tuplehash[!dir].tuple.dst.u3, IPPROTO_TCP, NULL, &port); - rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_q931); + rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_q931_ptr[0]); exp->flags = NF_CT_EXPECT_PERMANENT; /* Accept multiple calls */ nathook = rcu_dereference(nfct_h323_nat_hook); @@ -1275,6 +1251,9 @@ static int process_grq(struct sk_buff *skb, struct nf_conn *ct, return 0; } +static struct nf_conntrack_helper nf_conntrack_helper_ras[2] __read_mostly; +static struct nf_conntrack_helper *nf_conntrack_helper_ras_ptr[2] __read_mostly; + static int process_gcf(struct sk_buff *skb, struct nf_conn *ct, enum ip_conntrack_info ctinfo, unsigned int protoff, @@ -1306,7 +1285,7 @@ static int process_gcf(struct sk_buff *skb, struct nf_conn *ct, nf_ct_expect_init(exp, NF_CT_EXPECT_CLASS_DEFAULT, nf_ct_l3num(ct), &ct->tuplehash[!dir].tuple.src.u3, &addr, IPPROTO_UDP, NULL, &port); - rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_ras); + rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_ras_ptr[0]); if (nf_ct_expect_related(exp, 0) == 0) { pr_debug("nf_ct_ras: expect RAS "); @@ -1523,7 +1502,7 @@ static int process_acf(struct sk_buff *skb, struct nf_conn *ct, &ct->tuplehash[!dir].tuple.src.u3, &addr, IPPROTO_TCP, NULL, &port); exp->flags = NF_CT_EXPECT_PERMANENT; - rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_q931); + rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_q931_ptr[0]); if (nf_ct_expect_related(exp, 0) == 0) { pr_debug("nf_ct_ras: expect Q.931 "); @@ -1577,7 +1556,7 @@ static int process_lcf(struct sk_buff *skb, struct nf_conn *ct, &ct->tuplehash[!dir].tuple.src.u3, &addr, IPPROTO_TCP, NULL, &port); exp->flags = NF_CT_EXPECT_PERMANENT; - rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_q931); + rcu_assign_pointer(exp->assign_helper, nf_conntrack_helper_q931_ptr[0]); if (nf_ct_expect_related(exp, 0) == 0) { pr_debug("nf_ct_ras: expect Q.931 "); @@ -1711,59 +1690,57 @@ static const struct nf_conntrack_expect_policy ras_exp_policy = { .timeout = 240, }; -static struct nf_conntrack_helper nf_conntrack_helper_ras[] __read_mostly = { - { - .name = "RAS", - .me = THIS_MODULE, - .tuple.src.l3num = AF_INET, - .tuple.src.u.udp.port = cpu_to_be16(RAS_PORT), - .tuple.dst.protonum = IPPROTO_UDP, - .help = ras_help, - .expect_policy = &ras_exp_policy, - }, - { - .name = "RAS", - .me = THIS_MODULE, - .tuple.src.l3num = AF_INET6, - .tuple.src.u.udp.port = cpu_to_be16(RAS_PORT), - .tuple.dst.protonum = IPPROTO_UDP, - .help = ras_help, - .expect_policy = &ras_exp_policy, - }, -}; - static int __init h323_helper_init(void) { int ret; - ret = nf_conntrack_helper_register(&nf_conntrack_helper_h245); + nf_ct_helper_init(&nf_conntrack_helper_ras[0], AF_INET, IPPROTO_UDP, + "RAS", RAS_PORT, RAS_PORT, RAS_PORT, + &ras_exp_policy, 0, ras_help, NULL, THIS_MODULE); + nf_ct_helper_init(&nf_conntrack_helper_ras[1], AF_INET6, IPPROTO_UDP, + "RAS", RAS_PORT, RAS_PORT, RAS_PORT, + &ras_exp_policy, 0, ras_help, NULL, THIS_MODULE); + nf_ct_helper_init(&nf_conntrack_helper_h245, AF_UNSPEC, IPPROTO_UDP, + "H.245", 0, 0, 0, + &h245_exp_policy, 0, h245_help, NULL, THIS_MODULE); + nf_ct_helper_init(&nf_conntrack_helper_q931[0], AF_INET, IPPROTO_TCP, + "Q.931", Q931_PORT, Q931_PORT, Q931_PORT, + &q931_exp_policy, 0, q931_help, NULL, THIS_MODULE); + nf_ct_helper_init(&nf_conntrack_helper_q931[1], AF_INET6, IPPROTO_TCP, + "Q.931", Q931_PORT, Q931_PORT, Q931_PORT, + &q931_exp_policy, 0, q931_help, NULL, THIS_MODULE); + + ret = nf_conntrack_helper_register(&nf_conntrack_helper_h245, + &nf_conntrack_helper_h245_ptr); if (ret < 0) return ret; ret = nf_conntrack_helpers_register(nf_conntrack_helper_q931, - ARRAY_SIZE(nf_conntrack_helper_q931)); + ARRAY_SIZE(nf_conntrack_helper_q931), + nf_conntrack_helper_q931_ptr); if (ret < 0) goto err1; ret = nf_conntrack_helpers_register(nf_conntrack_helper_ras, - ARRAY_SIZE(nf_conntrack_helper_ras)); + ARRAY_SIZE(nf_conntrack_helper_ras), + nf_conntrack_helper_ras_ptr); if (ret < 0) goto err2; return 0; err2: - nf_conntrack_helpers_unregister(nf_conntrack_helper_q931, - ARRAY_SIZE(nf_conntrack_helper_q931)); + nf_conntrack_helpers_unregister(nf_conntrack_helper_q931_ptr, + ARRAY_SIZE(nf_conntrack_helper_q931_ptr)); err1: - nf_conntrack_helper_unregister(&nf_conntrack_helper_h245); + nf_conntrack_helper_unregister(nf_conntrack_helper_h245_ptr); return ret; } static void __exit h323_helper_exit(void) { - nf_conntrack_helpers_unregister(nf_conntrack_helper_ras, + nf_conntrack_helpers_unregister(nf_conntrack_helper_ras_ptr, ARRAY_SIZE(nf_conntrack_helper_ras)); - nf_conntrack_helpers_unregister(nf_conntrack_helper_q931, + nf_conntrack_helpers_unregister(nf_conntrack_helper_q931_ptr, ARRAY_SIZE(nf_conntrack_helper_q931)); - nf_conntrack_helper_unregister(&nf_conntrack_helper_h245); + nf_conntrack_helper_unregister(nf_conntrack_helper_h245_ptr); } static void __exit nf_conntrack_h323_fini(void) diff --git a/net/netfilter/nf_conntrack_helper.c b/net/netfilter/nf_conntrack_helper.c index 17e971bd4c74..ce2d59331dfb 100644 --- a/net/netfilter/nf_conntrack_helper.c +++ b/net/netfilter/nf_conntrack_helper.c @@ -347,14 +347,13 @@ void nf_ct_helper_log(struct sk_buff *skb, const struct nf_conn *ct, } EXPORT_SYMBOL_GPL(nf_ct_helper_log); -int nf_conntrack_helper_register(struct nf_conntrack_helper *me) +int __nf_conntrack_helper_register(struct nf_conntrack_helper *me) { struct nf_conntrack_tuple_mask mask = { .src.u.all = htons(0xFFFF) }; unsigned int h = helper_hash(&me->tuple); struct nf_conntrack_helper *cur; int ret = 0, i; - BUG_ON(me->expect_policy == NULL); BUG_ON(me->expect_class_max >= NF_CT_MAX_EXPECT_CLASSES); BUG_ON(strlen(me->name) > NF_CT_HELPER_NAME_LEN - 1); @@ -394,6 +393,33 @@ out: mutex_unlock(&nf_ct_helper_mutex); return ret; } +EXPORT_SYMBOL_GPL(__nf_conntrack_helper_register); + +int nf_conntrack_helper_register(struct nf_conntrack_helper *me, + struct nf_conntrack_helper **helper_ptr) +{ + struct nf_conntrack_helper *new_helper; + int err; + + new_helper = kzalloc_obj(*new_helper, GFP_KERNEL_ACCOUNT); + if (!new_helper) + return -ENOMEM; + + memcpy(new_helper, me, sizeof(*new_helper)); + *helper_ptr = new_helper; + + err = __nf_conntrack_helper_register(new_helper); + if (err < 0) + goto err_helper; + + return 0; + +err_helper: + *helper_ptr = NULL; + kfree(new_helper); + + return err; +} EXPORT_SYMBOL_GPL(nf_conntrack_helper_register); static bool expect_iter_me(struct nf_conntrack_expect *exp, void *data) @@ -430,6 +456,7 @@ void nf_conntrack_helper_unregister(struct nf_conntrack_helper *me) * last step, this ensures rcu readers of exp->helper are done. * No need for another synchronize_rcu() here. */ + kfree(me); } EXPORT_SYMBOL_GPL(nf_conntrack_helper_unregister); @@ -445,11 +472,12 @@ void nf_ct_helper_init(struct nf_conntrack_helper *helper, struct nf_conn *ct), struct module *module) { + memset(helper, 0, sizeof(*helper)); + helper->tuple.src.l3num = l3num; helper->tuple.dst.protonum = protonum; helper->tuple.src.u.all = htons(spec_port); - helper->expect_policy = exp_pol; - helper->expect_class_max = expect_class_max; + helper->help = help; helper->from_nlattr = from_nlattr; helper->me = module; @@ -460,34 +488,57 @@ void nf_ct_helper_init(struct nf_conntrack_helper *helper, snprintf(helper->name, sizeof(helper->name), "%s", name); else snprintf(helper->name, sizeof(helper->name), "%s-%u", name, id); + + if (WARN_ON_ONCE(expect_class_max >= NF_CT_MAX_EXPECT_CLASSES)) + return; + + memcpy(helper->expect_policy, exp_pol, + (expect_class_max + 1) * sizeof(*exp_pol)); + helper->expect_class_max = expect_class_max; } EXPORT_SYMBOL_GPL(nf_ct_helper_init); int nf_conntrack_helpers_register(struct nf_conntrack_helper *helper, - unsigned int n) + unsigned int n, struct nf_conntrack_helper **helper_ptr) { + struct nf_conntrack_helper *new_helper; unsigned int i; int err = 0; for (i = 0; i < n; i++) { - err = nf_conntrack_helper_register(&helper[i]); - if (err < 0) + new_helper = kzalloc_obj(*new_helper, GFP_KERNEL_ACCOUNT); + if (!new_helper) { + err = -ENOMEM; goto err; + } + + memcpy(new_helper, &helper[i], sizeof(*new_helper)); + helper_ptr[i] = new_helper; + + err = __nf_conntrack_helper_register(new_helper); + if (err < 0) { + helper_ptr[i] = NULL; + goto err_helper; + } } return err; +err_helper: + kfree(new_helper); err: if (i > 0) - nf_conntrack_helpers_unregister(helper, i); + nf_conntrack_helpers_unregister(helper_ptr, i); return err; } EXPORT_SYMBOL_GPL(nf_conntrack_helpers_register); -void nf_conntrack_helpers_unregister(struct nf_conntrack_helper *helper, - unsigned int n) +void nf_conntrack_helpers_unregister(struct nf_conntrack_helper **helper, + unsigned int n) { - while (n-- > 0) - nf_conntrack_helper_unregister(&helper[n]); + while (n-- > 0) { + nf_conntrack_helper_unregister(helper[n]); + helper[n] = NULL; + } } EXPORT_SYMBOL_GPL(nf_conntrack_helpers_unregister); diff --git a/net/netfilter/nf_conntrack_irc.c b/net/netfilter/nf_conntrack_irc.c index 4d539657d4cb..0c117b8492e9 100644 --- a/net/netfilter/nf_conntrack_irc.c +++ b/net/netfilter/nf_conntrack_irc.c @@ -255,6 +255,7 @@ static int help(struct sk_buff *skb, unsigned int protoff, } static struct nf_conntrack_helper irc[MAX_PORTS] __read_mostly; +static struct nf_conntrack_helper *irc_ptr[MAX_PORTS] __read_mostly; static struct nf_conntrack_expect_policy irc_exp_policy; static int __init nf_conntrack_irc_init(void) @@ -289,7 +290,7 @@ static int __init nf_conntrack_irc_init(void) 0, help, NULL, THIS_MODULE); } - ret = nf_conntrack_helpers_register(&irc[0], ports_c); + ret = nf_conntrack_helpers_register(&irc[0], ports_c, irc_ptr); if (ret) { pr_err("failed to register helpers\n"); kfree(irc_buffer); @@ -301,7 +302,7 @@ static int __init nf_conntrack_irc_init(void) static void __exit nf_conntrack_irc_fini(void) { - nf_conntrack_helpers_unregister(irc, ports_c); + nf_conntrack_helpers_unregister(irc_ptr, ports_c); kfree(irc_buffer); } diff --git a/net/netfilter/nf_conntrack_netbios_ns.c b/net/netfilter/nf_conntrack_netbios_ns.c index 55415f011943..89d1cf7d6512 100644 --- a/net/netfilter/nf_conntrack_netbios_ns.c +++ b/net/netfilter/nf_conntrack_netbios_ns.c @@ -44,27 +44,25 @@ static int netbios_ns_help(struct sk_buff *skb, unsigned int protoff, return nf_conntrack_broadcast_help(skb, ct, ctinfo, timeout); } -static struct nf_conntrack_helper helper __read_mostly = { - .name = HELPER_NAME, - .tuple.src.l3num = NFPROTO_IPV4, - .tuple.src.u.udp.port = cpu_to_be16(NMBD_PORT), - .tuple.dst.protonum = IPPROTO_UDP, - .me = THIS_MODULE, - .help = netbios_ns_help, - .expect_policy = &exp_policy, -}; +static struct nf_conntrack_helper helper __read_mostly; +static struct nf_conntrack_helper *helper_ptr __read_mostly; static int __init nf_conntrack_netbios_ns_init(void) { NF_CT_HELPER_BUILD_BUG_ON(0); exp_policy.timeout = timeout; - return nf_conntrack_helper_register(&helper); + + nf_ct_helper_init(&helper, AF_INET, IPPROTO_UDP, HELPER_NAME, + NMBD_PORT, NMBD_PORT, NMBD_PORT, + &exp_policy, 0, netbios_ns_help, NULL, THIS_MODULE); + + return nf_conntrack_helper_register(&helper, &helper_ptr); } static void __exit nf_conntrack_netbios_ns_fini(void) { - nf_conntrack_helper_unregister(&helper); + nf_conntrack_helper_unregister(helper_ptr); } module_init(nf_conntrack_netbios_ns_init); diff --git a/net/netfilter/nf_conntrack_pptp.c b/net/netfilter/nf_conntrack_pptp.c index dc23e4181618..edc85a3eef1e 100644 --- a/net/netfilter/nf_conntrack_pptp.c +++ b/net/netfilter/nf_conntrack_pptp.c @@ -582,27 +582,25 @@ static const struct nf_conntrack_expect_policy pptp_exp_policy = { }; /* control protocol helper */ -static struct nf_conntrack_helper pptp __read_mostly = { - .name = "pptp", - .me = THIS_MODULE, - .tuple.src.l3num = AF_INET, - .tuple.src.u.tcp.port = cpu_to_be16(PPTP_CONTROL_PORT), - .tuple.dst.protonum = IPPROTO_TCP, - .help = conntrack_pptp_help, - .destroy = pptp_destroy_siblings, - .expect_policy = &pptp_exp_policy, -}; +static struct nf_conntrack_helper pptp __read_mostly; +static struct nf_conntrack_helper *pptp_ptr __read_mostly; static int __init nf_conntrack_pptp_init(void) { NF_CT_HELPER_BUILD_BUG_ON(sizeof(struct nf_ct_pptp_master)); - return nf_conntrack_helper_register(&pptp); + nf_ct_helper_init(&pptp, AF_INET, IPPROTO_TCP, + "pptp", PPTP_CONTROL_PORT, PPTP_CONTROL_PORT, PPTP_CONTROL_PORT, + &pptp_exp_policy, 0, conntrack_pptp_help, NULL, THIS_MODULE); + + pptp.destroy = pptp_destroy_siblings; + + return nf_conntrack_helper_register(&pptp, &pptp_ptr); } static void __exit nf_conntrack_pptp_fini(void) { - nf_conntrack_helper_unregister(&pptp); + nf_conntrack_helper_unregister(pptp_ptr); } module_init(nf_conntrack_pptp_init); diff --git a/net/netfilter/nf_conntrack_sane.c b/net/netfilter/nf_conntrack_sane.c index 13dc421fc4f5..a7f7b07ba0c2 100644 --- a/net/netfilter/nf_conntrack_sane.c +++ b/net/netfilter/nf_conntrack_sane.c @@ -167,6 +167,7 @@ static int help(struct sk_buff *skb, } static struct nf_conntrack_helper sane[MAX_PORTS * 2] __read_mostly; +static struct nf_conntrack_helper *sane_ptr[MAX_PORTS * 2] __read_mostly; static const struct nf_conntrack_expect_policy sane_exp_policy = { .max_expected = 1, @@ -175,7 +176,7 @@ static const struct nf_conntrack_expect_policy sane_exp_policy = { static void __exit nf_conntrack_sane_fini(void) { - nf_conntrack_helpers_unregister(sane, ports_c * 2); + nf_conntrack_helpers_unregister(sane_ptr, ports_c * 2); } static int __init nf_conntrack_sane_init(void) @@ -200,7 +201,7 @@ static int __init nf_conntrack_sane_init(void) THIS_MODULE); } - ret = nf_conntrack_helpers_register(sane, ports_c * 2); + ret = nf_conntrack_helpers_register(sane, ports_c * 2, sane_ptr); if (ret < 0) { pr_err("failed to register helpers\n"); return ret; diff --git a/net/netfilter/nf_conntrack_sip.c b/net/netfilter/nf_conntrack_sip.c index e69941f1a101..2c78a3e1dab5 100644 --- a/net/netfilter/nf_conntrack_sip.c +++ b/net/netfilter/nf_conntrack_sip.c @@ -1731,6 +1731,7 @@ static int sip_help_udp(struct sk_buff *skb, unsigned int protoff, } static struct nf_conntrack_helper sip[MAX_PORTS * 4] __read_mostly; +static struct nf_conntrack_helper *sip_ptr[MAX_PORTS * 4] __read_mostly; static const struct nf_conntrack_expect_policy sip_exp_policy[SIP_EXPECT_MAX + 1] = { [SIP_EXPECT_SIGNALLING] = { @@ -1757,7 +1758,7 @@ static const struct nf_conntrack_expect_policy sip_exp_policy[SIP_EXPECT_MAX + 1 static void __exit nf_conntrack_sip_fini(void) { - nf_conntrack_helpers_unregister(sip, ports_c * 4); + nf_conntrack_helpers_unregister(sip_ptr, ports_c * 4); } static int __init nf_conntrack_sip_init(void) @@ -1788,7 +1789,7 @@ static int __init nf_conntrack_sip_init(void) NULL, THIS_MODULE); } - ret = nf_conntrack_helpers_register(sip, ports_c * 4); + ret = nf_conntrack_helpers_register(sip, ports_c * 4, sip_ptr); if (ret < 0) { pr_err("failed to register helpers\n"); return ret; diff --git a/net/netfilter/nf_conntrack_snmp.c b/net/netfilter/nf_conntrack_snmp.c index 7b7eed43c54f..b6fce5703fce 100644 --- a/net/netfilter/nf_conntrack_snmp.c +++ b/net/netfilter/nf_conntrack_snmp.c @@ -47,25 +47,24 @@ static struct nf_conntrack_expect_policy exp_policy = { .max_expected = 1, }; -static struct nf_conntrack_helper helper __read_mostly = { - .name = "snmp", - .tuple.src.l3num = NFPROTO_IPV4, - .tuple.src.u.udp.port = cpu_to_be16(SNMP_PORT), - .tuple.dst.protonum = IPPROTO_UDP, - .me = THIS_MODULE, - .help = snmp_conntrack_help, - .expect_policy = &exp_policy, -}; +static struct nf_conntrack_helper helper __read_mostly; +static struct nf_conntrack_helper *helper_ptr __read_mostly; static int __init nf_conntrack_snmp_init(void) { exp_policy.timeout = timeout; - return nf_conntrack_helper_register(&helper); + + nf_ct_helper_init(&helper, AF_INET, IPPROTO_UDP, + "snmp", SNMP_PORT, SNMP_PORT, SNMP_PORT, + &exp_policy, 0, snmp_conntrack_help, NULL, + THIS_MODULE); + + return nf_conntrack_helper_register(&helper, &helper_ptr); } static void __exit nf_conntrack_snmp_fini(void) { - nf_conntrack_helper_unregister(&helper); + nf_conntrack_helper_unregister(helper_ptr); } module_init(nf_conntrack_snmp_init); diff --git a/net/netfilter/nf_conntrack_tftp.c b/net/netfilter/nf_conntrack_tftp.c index a2e6833a0bf7..4393c435aa35 100644 --- a/net/netfilter/nf_conntrack_tftp.c +++ b/net/netfilter/nf_conntrack_tftp.c @@ -96,6 +96,7 @@ static int tftp_help(struct sk_buff *skb, } static struct nf_conntrack_helper tftp[MAX_PORTS * 2] __read_mostly; +static struct nf_conntrack_helper *tftp_ptr[MAX_PORTS * 2] __read_mostly; static const struct nf_conntrack_expect_policy tftp_exp_policy = { .max_expected = 1, @@ -104,7 +105,7 @@ static const struct nf_conntrack_expect_policy tftp_exp_policy = { static void __exit nf_conntrack_tftp_fini(void) { - nf_conntrack_helpers_unregister(tftp, ports_c * 2); + nf_conntrack_helpers_unregister(tftp_ptr, ports_c * 2); } static int __init nf_conntrack_tftp_init(void) @@ -127,7 +128,7 @@ static int __init nf_conntrack_tftp_init(void) THIS_MODULE); } - ret = nf_conntrack_helpers_register(tftp, ports_c * 2); + ret = nf_conntrack_helpers_register(tftp, ports_c * 2, tftp_ptr); if (ret < 0) { pr_err("failed to register helpers\n"); return ret; diff --git a/net/netfilter/nfnetlink_cthelper.c b/net/netfilter/nfnetlink_cthelper.c index 267eac1167f3..338515697c91 100644 --- a/net/netfilter/nfnetlink_cthelper.c +++ b/net/netfilter/nfnetlink_cthelper.c @@ -32,7 +32,7 @@ MODULE_DESCRIPTION("nfnl_cthelper: User-space connection tracking helpers"); struct nfnl_cthelper { struct list_head list; - struct nf_conntrack_helper helper; + struct nf_conntrack_helper *helper; }; static LIST_HEAD(nfnl_cthelper_list); @@ -176,7 +176,6 @@ nfnl_cthelper_parse_expect_policy(struct nf_conntrack_helper *helper, const struct nlattr *attr) { int i, ret; - struct nf_conntrack_expect_policy *expect_policy; struct nlattr *tb[NFCTH_POLICY_SET_MAX+1]; unsigned int class_max; @@ -195,26 +194,19 @@ nfnl_cthelper_parse_expect_policy(struct nf_conntrack_helper *helper, if (class_max > NF_CT_MAX_EXPECT_CLASSES) return -EOVERFLOW; - expect_policy = kzalloc_objs(struct nf_conntrack_expect_policy, - class_max); - if (expect_policy == NULL) - return -ENOMEM; - for (i = 0; i < class_max; i++) { if (!tb[NFCTH_POLICY_SET+i]) goto err; - ret = nfnl_cthelper_expect_policy(&expect_policy[i], + ret = nfnl_cthelper_expect_policy(&helper->expect_policy[i], tb[NFCTH_POLICY_SET+i]); if (ret < 0) goto err; } helper->expect_class_max = class_max - 1; - helper->expect_policy = expect_policy; return 0; err: - kfree(expect_policy); return -EINVAL; } @@ -230,21 +222,28 @@ nfnl_cthelper_create(const struct nlattr * const tb[], if (!tb[NFCTH_TUPLE] || !tb[NFCTH_POLICY] || !tb[NFCTH_PRIV_DATA_LEN]) return -EINVAL; - nfcth = kzalloc_obj(*nfcth); + nfcth = kzalloc_obj(*nfcth, GFP_KERNEL_ACCOUNT); if (nfcth == NULL) return -ENOMEM; - helper = &nfcth->helper; + + helper = kzalloc_obj(*helper, GFP_KERNEL_ACCOUNT); + if (!helper) { + ret = -ENOMEM; + goto err_cth; + } + + nfcth->helper = helper; ret = nfnl_cthelper_parse_expect_policy(helper, tb[NFCTH_POLICY]); if (ret < 0) - goto err1; + goto err_helper; nla_strscpy(helper->name, tb[NFCTH_NAME], NF_CT_HELPER_NAME_LEN); size = ntohl(nla_get_be32(tb[NFCTH_PRIV_DATA_LEN])); if (size > sizeof_field(struct nf_conn_help, data)) { ret = -ENOMEM; - goto err2; + goto err_helper; } helper->data_len = size; @@ -273,15 +272,15 @@ nfnl_cthelper_create(const struct nlattr * const tb[], } } - ret = nf_conntrack_helper_register(helper); + ret = __nf_conntrack_helper_register(helper); if (ret < 0) - goto err2; + goto err_helper; list_add_tail(&nfcth->list, &nfnl_cthelper_list); return 0; -err2: - kfree(helper->expect_policy); -err1: +err_helper: + kfree(helper); +err_cth: kfree(nfcth); return ret; } @@ -439,7 +438,7 @@ static int nfnl_cthelper_new(struct sk_buff *skb, const struct nfnl_info *info, return ret; list_for_each_entry(nlcth, &nfnl_cthelper_list, list) { - cur = &nlcth->helper; + cur = nlcth->helper; if (strncmp(cur->name, helper_name, NF_CT_HELPER_NAME_LEN)) continue; @@ -650,7 +649,7 @@ static int nfnl_cthelper_get(struct sk_buff *skb, const struct nfnl_info *info, } list_for_each_entry(nlcth, &nfnl_cthelper_list, list) { - cur = &nlcth->helper; + cur = nlcth->helper; if (helper_name && strncmp(cur->name, helper_name, NF_CT_HELPER_NAME_LEN)) continue; @@ -708,7 +707,7 @@ static int nfnl_cthelper_del(struct sk_buff *skb, const struct nfnl_info *info, ret = -ENOENT; list_for_each_entry_safe(nlcth, n, &nfnl_cthelper_list, list) { - cur = &nlcth->helper; + cur = nlcth->helper; j++; if (helper_name && @@ -723,7 +722,6 @@ static int nfnl_cthelper_del(struct sk_buff *skb, const struct nfnl_info *info, if (refcount_dec_if_one(&cur->refcnt)) { found = true; nf_conntrack_helper_unregister(cur); - kfree(cur->expect_policy); list_del(&nlcth->list); kfree(nlcth); @@ -796,10 +794,9 @@ static void __exit nfnl_cthelper_exit(void) nfnetlink_subsys_unregister(&nfnl_cthelper_subsys); list_for_each_entry_safe(nlcth, n, &nfnl_cthelper_list, list) { - cur = &nlcth->helper; + cur = nlcth->helper; nf_conntrack_helper_unregister(cur); - kfree(cur->expect_policy); kfree(nlcth); } } -- cgit v1.2.3 From 4cc0cc0b297c17c2b106d6892bd13d9c32fe66ce Mon Sep 17 00:00:00 2001 From: Tamizh Chelvam Raja Date: Thu, 4 Jun 2026 21:54:03 +0530 Subject: wifi: mac80211: Add 802.3 multicast encapsulation offload support mac80211 converts 802.3 multicast packets to 802.11 format before driver TX, even when Ethernet encapsulation offload is enabled. This prevents drivers that support multicast Ethernet encapsulation offload from receiving frames in native 802.3 format. Introduce the IEEE80211_OFFLOAD_ENCAP_MCAST flag to bypass the 802.11 encapsulation step and pass the multicast packet to the driver in 802.3 format. Drivers that support multicast Ethernet encapsulation offload can advertise this flag. Disable multicast encapsulation offload in MLO case for drivers not advertising MLO_MCAST_MULTI_LINK_TX support for AP mode and for 3-address AP_VLAN multicast packets. Signed-off-by: Tamizh Chelvam Raja Link: https://patch.msgid.link/20260604162403.1563729-4-tamizh.raja@oss.qualcomm.com [fix unlikely(), indentation] Signed-off-by: Johannes Berg --- include/net/mac80211.h | 4 ++++ net/mac80211/tx.c | 51 +++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 48 insertions(+), 7 deletions(-) (limited to 'include') diff --git a/include/net/mac80211.h b/include/net/mac80211.h index 7dd558f4025b..4f95da023746 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -2014,12 +2014,16 @@ enum ieee80211_vif_flags { * @IEEE80211_OFFLOAD_DECAP_ENABLED: rx encapsulation offload is enabled * The driver supports passing received 802.11 frames as 802.3 frames to * mac80211. + * @IEEE80211_OFFLOAD_ENCAP_MCAST: tx multicast encapsulation offload is enabled + * The driver supports sending multicast frames passed as 802.3 frames + * by mac80211. */ enum ieee80211_offload_flags { IEEE80211_OFFLOAD_ENCAP_ENABLED = BIT(0), IEEE80211_OFFLOAD_ENCAP_4ADDR = BIT(1), IEEE80211_OFFLOAD_DECAP_ENABLED = BIT(2), + IEEE80211_OFFLOAD_ENCAP_MCAST = BIT(3), }; #define IEEE80211_NAN_AVAIL_BLOB_MAX_LEN 54 diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index a353758f53ff..e96a5733ec51 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -4746,11 +4746,32 @@ out_free: kfree_skb(skb); } +static bool ieee80211_check_mcast_offload(struct ieee80211_sub_if_data *sdata, + struct sk_buff *skb) +{ + struct ethhdr *ehdr = (struct ethhdr *)skb->data; + + if ((ieee80211_vif_is_mld(&sdata->vif) && + (sdata->vif.type == NL80211_IFTYPE_AP && + !ieee80211_hw_check(&sdata->local->hw, MLO_MCAST_MULTI_LINK_TX))) || + (sdata->vif.type == NL80211_IFTYPE_AP_VLAN && + !sdata->wdev.use_4addr)) + return false; + + if (!is_multicast_ether_addr(skb->data) || + !(sdata->vif.offload_flags & IEEE80211_OFFLOAD_ENCAP_MCAST) || + sdata->control_port_protocol == ehdr->h_proto) + return false; + + return true; +} + static void __ieee80211_subif_start_xmit_8023(struct sk_buff *skb, struct net_device *dev) { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ethhdr *ehdr = (struct ethhdr *)skb->data; + struct ieee80211_link_data *link; struct ieee80211_key *key; struct sta_info *sta; @@ -4761,14 +4782,30 @@ static void __ieee80211_subif_start_xmit_8023(struct sk_buff *skb, goto out; } - if (unlikely(IS_ERR_OR_NULL(sta) || !sta->uploaded || - !test_sta_flag(sta, WLAN_STA_AUTHORIZED) || - sdata->control_port_protocol == ehdr->h_proto)) + /* + * If STA is invalid, use the multicast offload path when applicable. + * In AP mode, drop the frame if there are no associated stations; + * otherwise use the default link and multicast key for transmission. + */ + if (unlikely(IS_ERR_OR_NULL(sta) && + ieee80211_check_mcast_offload(sdata, skb))) { + sta = NULL; + if (ieee80211_vif_get_num_mcast_if(sdata) <= 0) { + /* No associated STAs - no need to send multicast frames. */ + kfree_skb(skb); + goto out; + } + link = &sdata->deflink; + key = rcu_dereference(link->default_multicast_key); + } else if (unlikely(IS_ERR_OR_NULL(sta) || !sta->uploaded || + !test_sta_flag(sta, WLAN_STA_AUTHORIZED) || + sdata->control_port_protocol == ehdr->h_proto)) { goto skip_offload; - - key = rcu_dereference(sta->ptk[sta->ptk_idx]); - if (!key) - key = rcu_dereference(sdata->default_unicast_key); + } else { + key = rcu_dereference(sta->ptk[sta->ptk_idx]); + if (!key) + key = rcu_dereference(sdata->default_unicast_key); + } if (key && (!(key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE) || key->conf.cipher == WLAN_CIPHER_SUITE_TKIP)) -- cgit v1.2.3 From fe97fd540a03034a780224f24b0b2f0e21c9c763 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Thu, 4 Jun 2026 08:21:10 +0200 Subject: netfilter: nf_conntrack_pptp: move GRE specific cleanup to GRE tracker Move the GRE specific cleanup to nf_conntrack_proto_gre.c to ensure that the .destroy callback for the pptp helper is still reachable by existing conntrack entries while pptp module is being removed. This is a preparation patch, no functional changes are intended. Signed-off-by: Pablo Neira Ayuso --- include/net/netfilter/ipv4/nf_conntrack_ipv4.h | 4 ++ net/netfilter/nf_conntrack_pptp.c | 63 +------------------------- net/netfilter/nf_conntrack_proto_gre.c | 61 +++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 61 deletions(-) (limited to 'include') diff --git a/include/net/netfilter/ipv4/nf_conntrack_ipv4.h b/include/net/netfilter/ipv4/nf_conntrack_ipv4.h index b39417ad955e..0b07d5e69c15 100644 --- a/include/net/netfilter/ipv4/nf_conntrack_ipv4.h +++ b/include/net/netfilter/ipv4/nf_conntrack_ipv4.h @@ -20,4 +20,8 @@ extern const struct nf_conntrack_l4proto nf_conntrack_l4proto_sctp; extern const struct nf_conntrack_l4proto nf_conntrack_l4proto_gre; #endif +#if IS_ENABLED(CONFIG_NF_CONNTRACK_PPTP) +void gre_pptp_destroy_siblings(struct nf_conn *ct); +#endif + #endif /*_NF_CONNTRACK_IPV4_H*/ diff --git a/net/netfilter/nf_conntrack_pptp.c b/net/netfilter/nf_conntrack_pptp.c index edc85a3eef1e..ed567a1cf7fd 100644 --- a/net/netfilter/nf_conntrack_pptp.c +++ b/net/netfilter/nf_conntrack_pptp.c @@ -124,65 +124,6 @@ static void pptp_expectfn(struct nf_conn *ct, } } -static int destroy_sibling_or_exp(struct net *net, struct nf_conn *ct, - const struct nf_conntrack_tuple *t) -{ - const struct nf_conntrack_tuple_hash *h; - const struct nf_conntrack_zone *zone; - struct nf_conntrack_expect *exp; - struct nf_conn *sibling; - - pr_debug("trying to timeout ct or exp for tuple "); - nf_ct_dump_tuple(t); - - zone = nf_ct_zone(ct); - h = nf_conntrack_find_get(net, zone, t); - if (h) { - sibling = nf_ct_tuplehash_to_ctrack(h); - pr_debug("setting timeout of conntrack %p to 0\n", sibling); - sibling->proto.gre.timeout = 0; - sibling->proto.gre.stream_timeout = 0; - nf_ct_kill(sibling); - nf_ct_put(sibling); - return 1; - } else { - exp = nf_ct_expect_find_get(net, zone, t); - if (exp) { - pr_debug("unexpect_related of expect %p\n", exp); - nf_ct_unexpect_related(exp); - nf_ct_expect_put(exp); - return 1; - } - } - return 0; -} - -/* timeout GRE data connections */ -static void pptp_destroy_siblings(struct nf_conn *ct) -{ - struct net *net = nf_ct_net(ct); - const struct nf_ct_pptp_master *ct_pptp_info = nfct_help_data(ct); - struct nf_conntrack_tuple t; - - nf_ct_gre_keymap_destroy(ct); - - /* try original (pns->pac) tuple */ - memcpy(&t, &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple, sizeof(t)); - t.dst.protonum = IPPROTO_GRE; - t.src.u.gre.key = ct_pptp_info->pns_call_id; - t.dst.u.gre.key = ct_pptp_info->pac_call_id; - if (!destroy_sibling_or_exp(net, ct, &t)) - pr_debug("failed to timeout original pns->pac ct/exp\n"); - - /* try reply (pac->pns) tuple */ - memcpy(&t, &ct->tuplehash[IP_CT_DIR_REPLY].tuple, sizeof(t)); - t.dst.protonum = IPPROTO_GRE; - t.src.u.gre.key = ct_pptp_info->pac_call_id; - t.dst.u.gre.key = ct_pptp_info->pns_call_id; - if (!destroy_sibling_or_exp(net, ct, &t)) - pr_debug("failed to timeout reply pac->pns ct/exp\n"); -} - /* expect GRE connections (PNS->PAC and PAC->PNS direction) */ static int exp_gre(struct nf_conn *ct, __be16 callid, __be16 peer_callid) { @@ -343,7 +284,7 @@ pptp_inbound_pkt(struct sk_buff *skb, unsigned int protoff, info->cstate = PPTP_CALL_NONE; /* untrack this call id, unexpect GRE packets */ - pptp_destroy_siblings(ct); + gre_pptp_destroy_siblings(ct); break; case PPTP_WAN_ERROR_NOTIFY: @@ -593,7 +534,7 @@ static int __init nf_conntrack_pptp_init(void) "pptp", PPTP_CONTROL_PORT, PPTP_CONTROL_PORT, PPTP_CONTROL_PORT, &pptp_exp_policy, 0, conntrack_pptp_help, NULL, THIS_MODULE); - pptp.destroy = pptp_destroy_siblings; + pptp.destroy = gre_pptp_destroy_siblings; return nf_conntrack_helper_register(&pptp, &pptp_ptr); } diff --git a/net/netfilter/nf_conntrack_proto_gre.c b/net/netfilter/nf_conntrack_proto_gre.c index 35e22082d65a..473658259f1a 100644 --- a/net/netfilter/nf_conntrack_proto_gre.c +++ b/net/netfilter/nf_conntrack_proto_gre.c @@ -349,6 +349,67 @@ gre_timeout_nla_policy[CTA_TIMEOUT_GRE_MAX+1] = { }; #endif /* CONFIG_NF_CONNTRACK_TIMEOUT */ +#if IS_ENABLED(CONFIG_NF_CONNTRACK_PPTP) +static int destroy_sibling_or_exp(struct net *net, struct nf_conn *ct, + const struct nf_conntrack_tuple *t) +{ + const struct nf_conntrack_tuple_hash *h; + const struct nf_conntrack_zone *zone; + struct nf_conntrack_expect *exp; + struct nf_conn *sibling; + + pr_debug("trying to timeout ct or exp for tuple "); + nf_ct_dump_tuple(t); + + zone = nf_ct_zone(ct); + h = nf_conntrack_find_get(net, zone, t); + if (h) { + sibling = nf_ct_tuplehash_to_ctrack(h); + pr_debug("setting timeout of conntrack %p to 0\n", sibling); + sibling->proto.gre.timeout = 0; + sibling->proto.gre.stream_timeout = 0; + nf_ct_kill(sibling); + nf_ct_put(sibling); + return 1; + } else { + exp = nf_ct_expect_find_get(net, zone, t); + if (exp) { + pr_debug("unexpect_related of expect %p\n", exp); + nf_ct_unexpect_related(exp); + nf_ct_expect_put(exp); + return 1; + } + } + return 0; +} + +void gre_pptp_destroy_siblings(struct nf_conn *ct) +{ + struct net *net = nf_ct_net(ct); + const struct nf_ct_pptp_master *ct_pptp_info = nfct_help_data(ct); + struct nf_conntrack_tuple t; + + nf_ct_gre_keymap_destroy(ct); + + /* try original (pns->pac) tuple */ + memcpy(&t, &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple, sizeof(t)); + t.dst.protonum = IPPROTO_GRE; + t.src.u.gre.key = ct_pptp_info->pns_call_id; + t.dst.u.gre.key = ct_pptp_info->pac_call_id; + if (!destroy_sibling_or_exp(net, ct, &t)) + pr_debug("failed to timeout original pns->pac ct/exp\n"); + + /* try reply (pac->pns) tuple */ + memcpy(&t, &ct->tuplehash[IP_CT_DIR_REPLY].tuple, sizeof(t)); + t.dst.protonum = IPPROTO_GRE; + t.src.u.gre.key = ct_pptp_info->pac_call_id; + t.dst.u.gre.key = ct_pptp_info->pns_call_id; + if (!destroy_sibling_or_exp(net, ct, &t)) + pr_debug("failed to timeout reply pac->pns ct/exp\n"); +} +EXPORT_SYMBOL_GPL(gre_pptp_destroy_siblings); +#endif + void nf_conntrack_gre_init_net(struct net *net) { struct nf_gre_net *net_gre = gre_pernet(net); -- cgit v1.2.3 From ac46f3f35b6e68fb062ae7cf780d516c0cf4c00a Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Thu, 4 Jun 2026 08:21:11 +0200 Subject: netfilter: nf_conntrack_helper: add refcounting from datapath This patch adds a new ->ct_refcnt field to struct nf_conntrack_helper which is bumped when the helper is used by the ct helper extension. Drop this reference count when the conntrack entry is released. This is a packet path refcount which ensures that struct nf_conntrack_helper remains in place for tricky scenarios where a packet sits in nfqueue, or elsewhere, with a conntrack that refers to this helper. For simplicity, this leaves a single refcount for helper objects in place, remove the existing refcount for control plane that ensures that the helper does not go away if it is used by ruleset. On helper removal, the help callback is set to NULL to disable it from packet path and, after rcu grace period, existing expectations are removed. Update ctnetlink to disable access to .to_nlattr and .from_nlattr if the helper is going away. Remove nf_queue_nf_hook_drop() since it has proven not to be effective because packets with unconfirmed conntracks which are still flying to sit in nfqueue. Signed-off-by: Pablo Neira Ayuso --- include/net/netfilter/nf_conntrack_helper.h | 26 ++++++++++++--- net/netfilter/nf_conntrack_core.c | 3 +- net/netfilter/nf_conntrack_helper.c | 52 +++++++++++++---------------- net/netfilter/nf_conntrack_netlink.c | 28 ++++++++++------ net/netfilter/nf_conntrack_ovs.c | 9 ++++- net/netfilter/nf_conntrack_proto.c | 15 ++++++--- net/netfilter/nfnetlink_cthelper.c | 14 +++----- net/netfilter/nft_ct.c | 3 +- net/netfilter/xt_CT.c | 3 -- 9 files changed, 89 insertions(+), 64 deletions(-) (limited to 'include') diff --git a/include/net/netfilter/nf_conntrack_helper.h b/include/net/netfilter/nf_conntrack_helper.h index 1956bc12bf56..ed93a5a1adc8 100644 --- a/include/net/netfilter/nf_conntrack_helper.h +++ b/include/net/netfilter/nf_conntrack_helper.h @@ -35,20 +35,22 @@ enum nf_ct_helper_flags { struct nf_conntrack_helper { struct hlist_node hnode; /* Internal use. */ + struct rcu_head rcu; + char name[NF_CT_HELPER_NAME_LEN]; /* name of the module */ - refcount_t refcnt; struct module *me; /* pointer to self */ struct nf_conntrack_expect_policy expect_policy[NF_CT_MAX_EXPECT_CLASSES]; + refcount_t ct_refcnt; + /* Tuple of things we will help (compared against server response) */ struct nf_conntrack_tuple tuple; /* Function to call when data passes; return verdict, or -1 to invalidate. */ - int (*help)(struct sk_buff *skb, - unsigned int protoff, - struct nf_conn *ct, - enum ip_conntrack_info conntrackinfo); + int __rcu (*help)(struct sk_buff *skb, unsigned int protoff, + struct nf_conn *ct, + enum ip_conntrack_info conntrackinfo); void (*destroy)(struct nf_conn *ct); @@ -138,6 +140,20 @@ static inline void *nfct_help_data(const struct nf_conn *ct) return (void *)help->data; } +static inline void nf_ct_help_put(const struct nf_conn *ct) +{ + struct nf_conntrack_helper *helper; + struct nf_conn_help *help; + + help = nfct_help(ct); + if (!help) + return; + + helper = rcu_dereference(help->helper); + if (helper && refcount_dec_and_test(&helper->ct_refcnt)) + kfree_rcu(helper, rcu); +} + int nf_conntrack_helper_init(void); void nf_conntrack_helper_fini(void); diff --git a/net/netfilter/nf_conntrack_core.c b/net/netfilter/nf_conntrack_core.c index a45b73239369..7c135fe3dd03 100644 --- a/net/netfilter/nf_conntrack_core.c +++ b/net/netfilter/nf_conntrack_core.c @@ -1746,6 +1746,7 @@ void nf_conntrack_free(struct nf_conn *ct) nat_hook->remove_nat_bysrc(ct); } + nf_ct_help_put(ct); nf_ct_timeout_put(ct); rcu_read_unlock(); @@ -1829,7 +1830,7 @@ init_conntrack(struct net *net, struct nf_conn *tmpl, assign_helper = rcu_dereference(exp->assign_helper); if (assign_helper) { help = nf_ct_helper_ext_add(ct, GFP_ATOMIC); - if (help) + if (help && refcount_inc_not_zero(&assign_helper->ct_refcnt)) rcu_assign_pointer(help->helper, assign_helper); } diff --git a/net/netfilter/nf_conntrack_helper.c b/net/netfilter/nf_conntrack_helper.c index ce2d59331dfb..83dfdb06bfdd 100644 --- a/net/netfilter/nf_conntrack_helper.c +++ b/net/netfilter/nf_conntrack_helper.c @@ -92,7 +92,7 @@ nf_conntrack_helper_try_module_get(const char *name, u16 l3num, u8 protonum) #endif if (h != NULL && !try_module_get(h->me)) h = NULL; - if (h != NULL && !refcount_inc_not_zero(&h->refcnt)) { + if (h != NULL && !refcount_inc_not_zero(&h->ct_refcnt)) { module_put(h->me); h = NULL; } @@ -105,8 +105,9 @@ EXPORT_SYMBOL_GPL(nf_conntrack_helper_try_module_get); void nf_conntrack_helper_put(struct nf_conntrack_helper *helper) { - refcount_dec(&helper->refcnt); module_put(helper->me); + if (refcount_dec_and_test(&helper->ct_refcnt)) + kfree_rcu(helper, rcu); } EXPORT_SYMBOL_GPL(nf_conntrack_helper_put); @@ -210,8 +211,13 @@ int __nf_ct_try_assign_helper(struct nf_conn *ct, struct nf_conn *tmpl, help = nfct_help(ct); if (helper == NULL) { - if (help) + if (help) { + struct nf_conntrack_helper *tmp = rcu_dereference(help->helper); + RCU_INIT_POINTER(help->helper, NULL); + if (tmp && refcount_dec_and_test(&tmp->ct_refcnt)) + kfree_rcu(tmp, rcu); + } return 0; } @@ -225,32 +231,23 @@ int __nf_ct_try_assign_helper(struct nf_conn *ct, struct nf_conn *tmpl, */ struct nf_conntrack_helper *tmp = rcu_dereference(help->helper); - if (tmp && tmp->help != helper->help) { - RCU_INIT_POINTER(help->helper, NULL); + if (tmp) { + if (tmp->help != helper->help) { + RCU_INIT_POINTER(help->helper, NULL); + if (refcount_dec_and_test(&tmp->ct_refcnt)) + kfree_rcu(tmp, rcu); + } return 0; } } - rcu_assign_pointer(help->helper, helper); + if (refcount_inc_not_zero(&helper->ct_refcnt)) + rcu_assign_pointer(help->helper, helper); return 0; } EXPORT_SYMBOL_GPL(__nf_ct_try_assign_helper); -/* appropriate ct lock protecting must be taken by caller */ -static int unhelp(struct nf_conn *ct, void *me) -{ - struct nf_conn_help *help = nfct_help(ct); - - if (help && rcu_dereference_raw(help->helper) == me) { - nf_conntrack_event(IPCT_HELPER, ct); - RCU_INIT_POINTER(help->helper, NULL); - } - - /* We are not intended to delete this conntrack. */ - return 0; -} - void nf_ct_helper_destroy(struct nf_conn *ct) { struct nf_conn_help *help = nfct_help(ct); @@ -386,7 +383,7 @@ int __nf_conntrack_helper_register(struct nf_conntrack_helper *me) } } } - refcount_set(&me->refcnt, 1); + refcount_set(&me->ct_refcnt, 1); hlist_add_head_rcu(&me->hnode, &nf_ct_helper_hash[h]); nf_ct_helper_count++; out: @@ -444,19 +441,18 @@ void nf_conntrack_helper_unregister(struct nf_conntrack_helper *me) nf_ct_helper_count--; mutex_unlock(&nf_ct_helper_mutex); + /* This helper is going away, disable it. */ + rcu_assign_pointer(me->help, NULL); + /* Make sure every nothing is still using the helper unless its a * connection in the hash. */ synchronize_rcu(); nf_ct_expect_iterate_destroy(expect_iter_me, me); - nf_ct_iterate_destroy(unhelp, me); - /* nf_ct_iterate_destroy() does an unconditional synchronize_rcu() as - * last step, this ensures rcu readers of exp->helper are done. - * No need for another synchronize_rcu() here. - */ - kfree(me); + if (refcount_dec_and_test(&me->ct_refcnt)) + kfree_rcu(me, rcu); } EXPORT_SYMBOL_GPL(nf_conntrack_helper_unregister); @@ -478,7 +474,7 @@ void nf_ct_helper_init(struct nf_conntrack_helper *helper, helper->tuple.dst.protonum = protonum; helper->tuple.src.u.all = htons(spec_port); - helper->help = help; + rcu_assign_pointer(helper->help, help); helper->from_nlattr = from_nlattr; helper->me = module; snprintf(helper->nat_mod_name, sizeof(helper->nat_mod_name), diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c index d429f9c9546c..b429e648f06c 100644 --- a/net/netfilter/nf_conntrack_netlink.c +++ b/net/netfilter/nf_conntrack_netlink.c @@ -240,7 +240,8 @@ static int ctnetlink_dump_helpinfo(struct sk_buff *skb, if (nla_put_string(skb, CTA_HELP_NAME, helper->name)) goto nla_put_failure; - if (helper->to_nlattr) + if (rcu_access_pointer(helper->help) && + helper->to_nlattr) helper->to_nlattr(skb, ct); nla_nest_end(skb, nest_helper); @@ -1935,6 +1936,7 @@ static int ctnetlink_change_helper(struct nf_conn *ct, if (err < 0) return err; + rcu_read_lock(); /* don't change helper of sibling connections */ if (ct->master) { /* If we try to change the helper to the same thing twice, @@ -1943,27 +1945,27 @@ static int ctnetlink_change_helper(struct nf_conn *ct, */ err = -EBUSY; if (help) { - rcu_read_lock(); helper = rcu_dereference(help->helper); if (helper && !strcmp(helper->name, helpname)) err = 0; - rcu_read_unlock(); } - + rcu_read_unlock(); return err; } - if (!strcmp(helpname, "")) { - if (help && help->helper) { + if (!strcmp(helpname, "") && help) { + helper = rcu_dereference(help->helper); + if (helper) { /* we had a helper before ... */ nf_ct_remove_expectations(ct); RCU_INIT_POINTER(help->helper, NULL); + if (refcount_dec_and_test(&helper->ct_refcnt)) + kfree_rcu(helper, rcu); } - + rcu_read_unlock(); return 0; } - rcu_read_lock(); helper = __nf_conntrack_helper_find(helpname, nf_ct_l3num(ct), nf_ct_protonum(ct)); if (helper == NULL) { @@ -1974,7 +1976,8 @@ static int ctnetlink_change_helper(struct nf_conn *ct, if (help) { if (rcu_access_pointer(help->helper) == helper) { /* update private helper data if allowed. */ - if (helper->from_nlattr) + if (rcu_access_pointer(helper->help) && + helper->from_nlattr) helper->from_nlattr(helpinfo, ct); err = 0; } else @@ -2289,11 +2292,16 @@ ctnetlink_create_conntrack(struct net *net, goto err2; } /* set private helper data if allowed. */ - if (helper->from_nlattr) + if (rcu_access_pointer(helper->help) && + helper->from_nlattr) helper->from_nlattr(helpinfo, ct); /* disable helper auto-assignment for this entry */ ct->status |= IPS_HELPER; + if (!refcount_inc_not_zero(&helper->ct_refcnt)) { + err = -ENOENT; + goto err2; + } RCU_INIT_POINTER(help->helper, helper); } } diff --git a/net/netfilter/nf_conntrack_ovs.c b/net/netfilter/nf_conntrack_ovs.c index a6988eeb1579..49d1511e9921 100644 --- a/net/netfilter/nf_conntrack_ovs.c +++ b/net/netfilter/nf_conntrack_ovs.c @@ -12,6 +12,9 @@ int nf_ct_helper(struct sk_buff *skb, struct nf_conn *ct, enum ip_conntrack_info ctinfo, u16 proto) { + int (*helper_cb)(struct sk_buff *skb, unsigned int protoff, + struct nf_conn *ct, + enum ip_conntrack_info conntrackinfo); const struct nf_conntrack_helper *helper; const struct nf_conn_help *help; unsigned int protoff; @@ -60,7 +63,11 @@ int nf_ct_helper(struct sk_buff *skb, struct nf_conn *ct, if (helper->tuple.dst.protonum != proto) return NF_ACCEPT; - err = helper->help(skb, protoff, ct, ctinfo); + helper_cb = rcu_dereference(helper->help); + if (!helper_cb) + return NF_ACCEPT; + + err = helper_cb(skb, protoff, ct, ctinfo); if (err != NF_ACCEPT) return err; diff --git a/net/netfilter/nf_conntrack_proto.c b/net/netfilter/nf_conntrack_proto.c index 50ddd3d613e1..ad96896516b6 100644 --- a/net/netfilter/nf_conntrack_proto.c +++ b/net/netfilter/nf_conntrack_proto.c @@ -129,6 +129,9 @@ unsigned int nf_confirm(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { + int (*helper_cb)(struct sk_buff *skb, unsigned int protoff, + struct nf_conn *ct, + enum ip_conntrack_info conntrackinfo); const struct nf_conn_help *help; enum ip_conntrack_info ctinfo; unsigned int protoff; @@ -175,11 +178,13 @@ unsigned int nf_confirm(void *priv, /* rcu_read_lock()ed by nf_hook */ helper = rcu_dereference(help->helper); if (helper) { - ret = helper->help(skb, - protoff, - ct, ctinfo); - if (ret != NF_ACCEPT) - return ret; + helper_cb = rcu_dereference(helper->help); + if (helper_cb) { + ret = helper_cb(skb, protoff, + ct, ctinfo); + if (ret != NF_ACCEPT) + return ret; + } } } diff --git a/net/netfilter/nfnetlink_cthelper.c b/net/netfilter/nfnetlink_cthelper.c index 338515697c91..033ea90c4401 100644 --- a/net/netfilter/nfnetlink_cthelper.c +++ b/net/netfilter/nfnetlink_cthelper.c @@ -719,15 +719,11 @@ static int nfnl_cthelper_del(struct sk_buff *skb, const struct nfnl_info *info, tuple.dst.protonum != cur->tuple.dst.protonum)) continue; - if (refcount_dec_if_one(&cur->refcnt)) { - found = true; - nf_conntrack_helper_unregister(cur); - - list_del(&nlcth->list); - kfree(nlcth); - } else { - ret = -EBUSY; - } + found = true; + nf_conntrack_helper_unregister(cur); + + list_del(&nlcth->list); + kfree(nlcth); } /* Make sure we return success if we flush and there is no helpers */ diff --git a/net/netfilter/nft_ct.c b/net/netfilter/nft_ct.c index 801c01c6af95..9fe179d688da 100644 --- a/net/netfilter/nft_ct.c +++ b/net/netfilter/nft_ct.c @@ -1101,7 +1101,6 @@ static void nft_ct_helper_obj_destroy(const struct nft_ctx *ctx, { struct nft_ct_helper_obj *priv = nft_obj_data(obj); - nf_queue_nf_hook_drop(ctx->net); if (priv->helper4) nf_conntrack_helper_put(priv->helper4); if (priv->helper6) @@ -1144,7 +1143,7 @@ static void nft_ct_helper_obj_eval(struct nft_object *obj, return; help = nf_ct_helper_ext_add(ct, GFP_ATOMIC); - if (help) { + if (help && refcount_inc_not_zero(&to_assign->ct_refcnt)) { rcu_assign_pointer(help->helper, to_assign); set_bit(IPS_HELPER_BIT, &ct->status); diff --git a/net/netfilter/xt_CT.c b/net/netfilter/xt_CT.c index b94f004d5f5c..e78660dfdf4b 100644 --- a/net/netfilter/xt_CT.c +++ b/net/netfilter/xt_CT.c @@ -284,9 +284,6 @@ static void xt_ct_tg_destroy(const struct xt_tgdtor_param *par, struct nf_conn_help *help; if (ct) { - if (info->helper[0]) - nf_queue_nf_hook_drop(par->net); - help = nfct_help(ct); xt_ct_put_helper(help); -- cgit v1.2.3 From 35e21a4dccc5c255ba59ccfbfeb4629ed21da972 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Thu, 4 Jun 2026 08:21:12 +0200 Subject: netfilter: conntrack: revert ct extension genid infrastructure This infrastructure is not used anymore after moving ct timeout and helper to use datapath refcount to track object use. Revert commit c56716c69ce1 ("netfilter: extensions: introduce extension genid count") this patch disables all ct extensions (leading to NULL) for unconfirmed conntracks, when this is only targeted at ct helper and ct timeout. There is also codebase that dereferences the ct extension without checking for NULL which could lead to crash. Fixes: c56716c69ce1 ("netfilter: extensions: introduce extension genid count") Signed-off-by: Pablo Neira Ayuso --- include/net/netfilter/nf_conntrack_extend.h | 12 ------ net/netfilter/nf_conntrack_core.c | 61 +---------------------------- net/netfilter/nf_conntrack_extend.c | 32 +-------------- 3 files changed, 2 insertions(+), 103 deletions(-) (limited to 'include') diff --git a/include/net/netfilter/nf_conntrack_extend.h b/include/net/netfilter/nf_conntrack_extend.h index 0b247248b032..fd5c4dbf72ca 100644 --- a/include/net/netfilter/nf_conntrack_extend.h +++ b/include/net/netfilter/nf_conntrack_extend.h @@ -38,7 +38,6 @@ enum nf_ct_ext_id { struct nf_ct_ext { u8 offset[NF_CT_EXT_NUM]; u8 len; - unsigned int gen_id; char data[] __aligned(8); }; @@ -52,8 +51,6 @@ static inline bool nf_ct_ext_exist(const struct nf_conn *ct, u8 id) return (ct->ext && __nf_ct_ext_exist(ct->ext, id)); } -void *__nf_ct_ext_find(const struct nf_ct_ext *ext, u8 id); - static inline void *nf_ct_ext_find(const struct nf_conn *ct, u8 id) { struct nf_ct_ext *ext = ct->ext; @@ -61,19 +58,10 @@ static inline void *nf_ct_ext_find(const struct nf_conn *ct, u8 id) if (!ext || !__nf_ct_ext_exist(ext, id)) return NULL; - if (unlikely(ext->gen_id)) - return __nf_ct_ext_find(ext, id); - return (void *)ct->ext + ct->ext->offset[id]; } /* Add this type, returns pointer to data or NULL. */ void *nf_ct_ext_add(struct nf_conn *ct, enum nf_ct_ext_id id, gfp_t gfp); -/* ext genid. if ext->id != ext_genid, extensions cannot be used - * anymore unless conntrack has CONFIRMED bit set. - */ -extern atomic_t nf_conntrack_ext_genid; -void nf_ct_ext_bump_genid(void); - #endif /* _NF_CONNTRACK_EXTEND_H */ diff --git a/net/netfilter/nf_conntrack_core.c b/net/netfilter/nf_conntrack_core.c index 7c135fe3dd03..91255fd3b35d 100644 --- a/net/netfilter/nf_conntrack_core.c +++ b/net/netfilter/nf_conntrack_core.c @@ -840,33 +840,6 @@ static void __nf_conntrack_hash_insert(struct nf_conn *ct, &nf_conntrack_hash[reply_hash]); } -static bool nf_ct_ext_valid_pre(const struct nf_ct_ext *ext) -{ - /* if ext->gen_id is not equal to nf_conntrack_ext_genid, some extensions - * may contain stale pointers to e.g. helper that has been removed. - * - * The helper can't clear this because the nf_conn object isn't in - * any hash and synchronize_rcu() isn't enough because associated skb - * might sit in a queue. - */ - return !ext || ext->gen_id == atomic_read(&nf_conntrack_ext_genid); -} - -static bool nf_ct_ext_valid_post(struct nf_ct_ext *ext) -{ - if (!ext) - return true; - - if (ext->gen_id != atomic_read(&nf_conntrack_ext_genid)) - return false; - - /* inserted into conntrack table, nf_ct_iterate_cleanup() - * will find it. Disable nf_ct_ext_find() id check. - */ - WRITE_ONCE(ext->gen_id, 0); - return true; -} - int nf_conntrack_hash_check_insert(struct nf_conn *ct) { @@ -882,9 +855,6 @@ nf_conntrack_hash_check_insert(struct nf_conn *ct) zone = nf_ct_zone(ct); - if (!nf_ct_ext_valid_pre(ct->ext)) - return -EAGAIN; - local_bh_disable(); do { sequence = read_seqcount_begin(&nf_conntrack_generation); @@ -918,18 +888,6 @@ nf_conntrack_hash_check_insert(struct nf_conn *ct) goto chaintoolong; } - /* If genid has changed, we can't insert anymore because ct - * extensions could have stale pointers and nf_ct_iterate_destroy - * might have completed its table scan already. - * - * Increment of the ext genid right after this check is fine: - * nf_ct_iterate_destroy blocks until locks are released. - */ - if (!nf_ct_ext_valid_post(ct->ext)) { - err = -EAGAIN; - goto out; - } - smp_wmb(); /* The caller holds a reference to this object */ refcount_set(&ct->ct_general.use, 2); @@ -1257,11 +1215,6 @@ __nf_conntrack_confirm(struct sk_buff *skb) return NF_DROP; } - if (!nf_ct_ext_valid_pre(ct->ext)) { - NF_CT_STAT_INC(net, insert_failed); - goto dying; - } - /* We have to check the DYING flag after unlink to prevent * a race against nf_ct_get_next_corpse() possibly called from * user context, else we insert an already 'dead' hash, blocking @@ -1324,16 +1277,6 @@ chaintoolong: nf_conntrack_double_unlock(hash, reply_hash); local_bh_enable(); - /* ext area is still valid (rcu read lock is held, - * but will go out of scope soon, we need to remove - * this conntrack again. - */ - if (!nf_ct_ext_valid_post(ct->ext)) { - nf_ct_kill(ct); - NF_CT_STAT_INC_ATOMIC(net, drop); - return NF_DROP; - } - help = nfct_help(ct); if (help && help->helper) nf_conntrack_event_cache(IPCT_HELPER, ct); @@ -2441,13 +2384,11 @@ nf_ct_iterate_destroy(int (*iter)(struct nf_conn *i, void *data), void *data) */ synchronize_net(); - nf_ct_ext_bump_genid(); iter_data.data = data; nf_ct_iterate_cleanup(iter, &iter_data); /* Another cpu might be in a rcu read section with - * rcu protected pointer cleared in iter callback - * or hidden via nf_ct_ext_bump_genid() above. + * rcu protected pointer cleared in iter callback. * * Wait until those are done. */ diff --git a/net/netfilter/nf_conntrack_extend.c b/net/netfilter/nf_conntrack_extend.c index dd62cc12e775..0da105e1ded9 100644 --- a/net/netfilter/nf_conntrack_extend.c +++ b/net/netfilter/nf_conntrack_extend.c @@ -27,8 +27,6 @@ #define NF_CT_EXT_PREALLOC 128u /* conntrack events are on by default */ -atomic_t nf_conntrack_ext_genid __read_mostly = ATOMIC_INIT(1); - static const u8 nf_ct_ext_type_len[NF_CT_EXT_NUM] = { [NF_CT_EXT_HELPER] = sizeof(struct nf_conn_help), #if IS_ENABLED(CONFIG_NF_NAT) @@ -118,10 +116,8 @@ void *nf_ct_ext_add(struct nf_conn *ct, enum nf_ct_ext_id id, gfp_t gfp) if (!new) return NULL; - if (!ct->ext) { + if (!ct->ext) memset(new->offset, 0, sizeof(new->offset)); - new->gen_id = atomic_read(&nf_conntrack_ext_genid); - } new->offset[id] = newoff; new->len = newlen; @@ -131,29 +127,3 @@ void *nf_ct_ext_add(struct nf_conn *ct, enum nf_ct_ext_id id, gfp_t gfp) return (void *)new + newoff; } EXPORT_SYMBOL(nf_ct_ext_add); - -/* Use nf_ct_ext_find wrapper. This is only useful for unconfirmed entries. */ -void *__nf_ct_ext_find(const struct nf_ct_ext *ext, u8 id) -{ - unsigned int gen_id = atomic_read(&nf_conntrack_ext_genid); - unsigned int this_id = READ_ONCE(ext->gen_id); - - if (!__nf_ct_ext_exist(ext, id)) - return NULL; - - if (this_id == 0 || ext->gen_id == gen_id) - return (void *)ext + ext->offset[id]; - - return NULL; -} -EXPORT_SYMBOL(__nf_ct_ext_find); - -void nf_ct_ext_bump_genid(void) -{ - unsigned int value = atomic_inc_return(&nf_conntrack_ext_genid); - - if (value == UINT_MAX) - atomic_set(&nf_conntrack_ext_genid, 1); - - msleep(HZ); -} -- cgit v1.2.3 From 87020def2529ec26fc9077e889fa1c34f53f1f83 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 4 Jun 2026 14:13:34 +0000 Subject: bridge: add bridge_flags_bit enum We want to use atomic operations for lockless p->flags changes and reads. Add definitions for bits in addition of masks so that we can use test_bit(), clear_bit() and set_bit() in subsequent patches. Signed-off-by: Eric Dumazet Reviewed-by: Ido Schimmel Acked-by: Nikolay Aleksandrov Link: https://patch.msgid.link/20260604141343.2124500-3-edumazet@google.com Signed-off-by: Jakub Kicinski --- include/linux/if_bridge.h | 78 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 53 insertions(+), 25 deletions(-) (limited to 'include') diff --git a/include/linux/if_bridge.h b/include/linux/if_bridge.h index ec9ffea1e46e..75673b8bffcb 100644 --- a/include/linux/if_bridge.h +++ b/include/linux/if_bridge.h @@ -36,32 +36,60 @@ struct br_ip_list { struct br_ip addr; }; -#define BR_HAIRPIN_MODE BIT(0) -#define BR_BPDU_GUARD BIT(1) -#define BR_ROOT_BLOCK BIT(2) -#define BR_MULTICAST_FAST_LEAVE BIT(3) -#define BR_ADMIN_COST BIT(4) -#define BR_LEARNING BIT(5) -#define BR_FLOOD BIT(6) +enum bridge_flags_bit { + BR_HAIRPIN_MODE_BIT, + BR_BPDU_GUARD_BIT, + BR_ROOT_BLOCK_BIT, + BR_MULTICAST_FAST_LEAVE_BIT, + BR_ADMIN_COST_BIT, + BR_LEARNING_BIT, + BR_FLOOD_BIT, + BR_PROMISC_BIT, + BR_PROXYARP_BIT, + BR_LEARNING_SYNC_BIT, + BR_PROXYARP_WIFI_BIT, + BR_MCAST_FLOOD_BIT, + BR_MULTICAST_TO_UNICAST_BIT, + BR_VLAN_TUNNEL_BIT, + BR_BCAST_FLOOD_BIT, + BR_NEIGH_SUPPRESS_BIT, + BR_ISOLATED_BIT, + BR_MRP_AWARE_BIT, + BR_MRP_LOST_CONT_BIT, + BR_MRP_LOST_IN_CONT_BIT, + BR_TX_FWD_OFFLOAD_BIT, + BR_PORT_LOCKED_BIT, + BR_PORT_MAB_BIT, + BR_NEIGH_VLAN_SUPPRESS_BIT, + BR_NEIGH_FORWARD_GRAT_BIT, +}; + +#define BR_HAIRPIN_MODE BIT(BR_HAIRPIN_MODE_BIT) +#define BR_BPDU_GUARD BIT(BR_BPDU_GUARD_BIT) +#define BR_ROOT_BLOCK BIT(BR_ROOT_BLOCK_BIT) +#define BR_MULTICAST_FAST_LEAVE BIT(BR_MULTICAST_FAST_LEAVE_BIT) +#define BR_ADMIN_COST BIT(BR_ADMIN_COST_BIT) +#define BR_LEARNING BIT(BR_LEARNING_BIT) +#define BR_FLOOD BIT(BR_FLOOD_BIT) #define BR_AUTO_MASK (BR_FLOOD | BR_LEARNING) -#define BR_PROMISC BIT(7) -#define BR_PROXYARP BIT(8) -#define BR_LEARNING_SYNC BIT(9) -#define BR_PROXYARP_WIFI BIT(10) -#define BR_MCAST_FLOOD BIT(11) -#define BR_MULTICAST_TO_UNICAST BIT(12) -#define BR_VLAN_TUNNEL BIT(13) -#define BR_BCAST_FLOOD BIT(14) -#define BR_NEIGH_SUPPRESS BIT(15) -#define BR_ISOLATED BIT(16) -#define BR_MRP_AWARE BIT(17) -#define BR_MRP_LOST_CONT BIT(18) -#define BR_MRP_LOST_IN_CONT BIT(19) -#define BR_TX_FWD_OFFLOAD BIT(20) -#define BR_PORT_LOCKED BIT(21) -#define BR_PORT_MAB BIT(22) -#define BR_NEIGH_VLAN_SUPPRESS BIT(23) -#define BR_NEIGH_FORWARD_GRAT BIT(24) +#define BR_PROMISC BIT(BR_PROMISC_BIT) +#define BR_PROXYARP BIT(BR_PROXYARP_BIT) +#define BR_LEARNING_SYNC BIT(BR_LEARNING_SYNC_BIT) +#define BR_PROXYARP_WIFI BIT(BR_PROXYARP_WIFI_BIT) +#define BR_MCAST_FLOOD BIT(BR_MCAST_FLOOD_BIT) +#define BR_MULTICAST_TO_UNICAST BIT(BR_MULTICAST_TO_UNICAST_BIT) +#define BR_VLAN_TUNNEL BIT(BR_VLAN_TUNNEL_BIT) +#define BR_BCAST_FLOOD BIT(BR_BCAST_FLOOD_BIT) +#define BR_NEIGH_SUPPRESS BIT(BR_NEIGH_SUPPRESS_BIT) +#define BR_ISOLATED BIT(BR_ISOLATED_BIT) +#define BR_MRP_AWARE BIT(BR_MRP_AWARE_BIT) +#define BR_MRP_LOST_CONT BIT(BR_MRP_LOST_CONT_BIT) +#define BR_MRP_LOST_IN_CONT BIT(BR_MRP_LOST_IN_CONT_BIT) +#define BR_TX_FWD_OFFLOAD BIT(BR_TX_FWD_OFFLOAD_BIT) +#define BR_PORT_LOCKED BIT(BR_PORT_LOCKED_BIT) +#define BR_PORT_MAB BIT(BR_PORT_MAB_BIT) +#define BR_NEIGH_VLAN_SUPPRESS BIT(BR_NEIGH_VLAN_SUPPRESS_BIT) +#define BR_NEIGH_FORWARD_GRAT BIT(BR_NEIGH_FORWARD_GRAT_BIT) #define BR_DEFAULT_AGEING_TIME (300 * HZ) -- cgit v1.2.3 From cc71b7f32f0ba8bd95f57e519303ce79f01fd467 Mon Sep 17 00:00:00 2001 From: Dragos Tatulea Date: Fri, 29 May 2026 08:23:58 +0300 Subject: net/mlx5: Update IFC allowed_list_size field bits The vport context allowed_list_size was increased from 12 to 16 bits. Writing to this field is protected by the log_max_current_uc/mc_list capabilities. On older FW versions these capabilities are limited to < 2K and only the high bits of the field are extended. This means that the change is backward compatible with older FW versions. Signed-off-by: Dragos Tatulea Reviewed-by: Cosmin Ratiu Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260529052359.389413-2-tariqt@nvidia.com Signed-off-by: Leon Romanovsky --- include/linux/mlx5/mlx5_ifc.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/linux/mlx5/mlx5_ifc.h b/include/linux/mlx5/mlx5_ifc.h index 6a675f918c40..d23332cdd9b3 100644 --- a/include/linux/mlx5/mlx5_ifc.h +++ b/include/linux/mlx5/mlx5_ifc.h @@ -4487,8 +4487,8 @@ struct mlx5_ifc_nic_vport_context_bits { u8 promisc_all[0x1]; u8 reserved_at_783[0x2]; u8 allowed_list_type[0x3]; - u8 reserved_at_788[0xc]; - u8 allowed_list_size[0xc]; + u8 reserved_at_788[0x8]; + u8 allowed_list_size[0x10]; struct mlx5_ifc_mac_address_layout_bits permanent_address; -- cgit v1.2.3 From ddbddbf8aee54bee038149187270c93a45478473 Mon Sep 17 00:00:00 2001 From: Shay Drory Date: Fri, 29 May 2026 08:23:59 +0300 Subject: net/mlx5: Add sd_group_size bits for SD management Currently, mlx5 is querying the MPIR register to get the number of PFs that should comprise the SD group. However, this register does not reflect the correct number in complex deployments. Hence, add an sd_group_size field to nic_vport_context to determine the correct number of PFs, and add an sd_group_size capability bit to indicate whether FW supports it. Signed-off-by: Shay Drory Reviewed-by: Moshe Shemesh Signed-off-by: Tariq Toukan Link: https://patch.msgid.link/20260529052359.389413-3-tariqt@nvidia.com Signed-off-by: Leon Romanovsky --- include/linux/mlx5/mlx5_ifc.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/linux/mlx5/mlx5_ifc.h b/include/linux/mlx5/mlx5_ifc.h index d23332cdd9b3..4f59b7e8a3d5 100644 --- a/include/linux/mlx5/mlx5_ifc.h +++ b/include/linux/mlx5/mlx5_ifc.h @@ -1986,7 +1986,8 @@ struct mlx5_ifc_cmd_hca_cap_bits { u8 basic_cyclic_rcv_wqe[0x1]; u8 reserved_at_381[0x2]; u8 log_max_rmp[0x5]; - u8 reserved_at_388[0x3]; + u8 sd_group_size[0x1]; + u8 reserved_at_389[0x2]; u8 log_max_rqt[0x5]; u8 reserved_at_390[0x3]; u8 log_max_rqt_size[0x5]; @@ -4469,7 +4470,9 @@ struct mlx5_ifc_nic_vport_context_bits { u8 reserved_at_100[0x1]; u8 sd_group[0x3]; - u8 reserved_at_104[0x1c]; + u8 reserved_at_104[0x4]; + u8 sd_group_size[0x8]; + u8 reserved_at_110[0x10]; u8 reserved_at_120[0x10]; u8 mtu[0x10]; -- cgit v1.2.3 From b224d18b1e5d1cddfc67f63f41d80023b2ec8889 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Sat, 6 Jun 2026 07:43:45 +0000 Subject: wifi: mac80211: bound S1G TIM PVB walk to the TIM element ieee80211_s1g_check_tim() parses the S1G Partial Virtual Bitmap (PVB) of a received TIM element. The TIM is handed in as the element payload: ieee802_11_parse_elems_full() stores elems->tim = elem->data and elems->tim_len = elem->datalen (net/mac80211/parse.c), so the valid bytes are [tim, tim + tim_len). When walking the encoded blocks the function passes the walker an end sentinel of (const u8 *)tim + tim_len + 2, i.e. two bytes past the end of the element. ieee80211_s1g_find_target_block() loops while (ptr + 1 <= end) and dereferences ptr (and the per-mode ieee80211_s1g_len_*() helpers read *ptr), so it can read up to two bytes beyond the TIM element -- an out-of-bounds read of adjacent skb/heap data when the TIM is the last element in the frame. The +2 appears to account for the element id/len header, but tim already points past that header at the element payload, so the addend is wrong. Pass the correct element end, (const u8 *)tim + tim_len. Fixes: e0c47c6229c2 ("wifi: mac80211: support parsing S1G TIM PVB") Signed-off-by: Bryam Vargas Link: https://patch.msgid.link/20260606074341.49135-1-hexlabsecurity@proton.me Signed-off-by: Johannes Berg --- include/linux/ieee80211-s1g.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include') diff --git a/include/linux/ieee80211-s1g.h b/include/linux/ieee80211-s1g.h index 22dde4cbc1b0..3f9626ad3d97 100644 --- a/include/linux/ieee80211-s1g.h +++ b/include/linux/ieee80211-s1g.h @@ -556,7 +556,7 @@ static inline bool ieee80211_s1g_check_tim(const struct ieee80211_tim_ie *tim, */ err = ieee80211_s1g_find_target_block(&enc_blk, &target_aid, tim->virtual_map, - (const u8 *)tim + tim_len + 2); + (const u8 *)tim + tim_len); if (err) return false; -- cgit v1.2.3 From a543cb49e603f88917e3258bbcb59a60d9c3c2fa Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Thu, 4 Jun 2026 22:46:25 +0000 Subject: net: Remove rtnl_held of struct fib_dump_filter. Commit 22e36ea9f5d7 ("inet: allow ip_valid_fib_dump_req() to be called with RTNL or RCU") introduced the rtnl_held field in struct fib_dump_filter to switch __dev_get_by_index() and dev_get_by_index_rcu() depending on the caller's context. This field served as an interim measure while we were incrementally converting all callers of ip_valid_fib_dump_req() to RCU. Now that all users (IPv4, IPv6, ipmr, ip6mr, and MPLS) have been converted to RCU, the field is no longer necessary. Let's remove it. Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260604224712.3209821-8-kuniyu@google.com Signed-off-by: Jakub Kicinski --- include/net/ip_fib.h | 1 - net/ipv4/fib_frontend.c | 19 ++++++------------- net/ipv4/ipmr.c | 4 +--- net/ipv6/ip6_fib.c | 1 - net/ipv6/ip6mr.c | 4 +--- net/mpls/af_mpls.c | 6 ++---- 6 files changed, 10 insertions(+), 25 deletions(-) (limited to 'include') diff --git a/include/net/ip_fib.h b/include/net/ip_fib.h index 318593743b6e..1142ffad7444 100644 --- a/include/net/ip_fib.h +++ b/include/net/ip_fib.h @@ -269,7 +269,6 @@ struct fib_dump_filter { bool filter_set; bool dump_routes; bool dump_exceptions; - bool rtnl_held; unsigned char protocol; unsigned char rt_type; unsigned int flags; diff --git a/net/ipv4/fib_frontend.c b/net/ipv4/fib_frontend.c index 1dab44e13d3b..ceeb87b13b93 100644 --- a/net/ipv4/fib_frontend.c +++ b/net/ipv4/fib_frontend.c @@ -946,9 +946,6 @@ int ip_valid_fib_dump_req(struct net *net, const struct nlmsghdr *nlh, struct rtmsg *rtm; int err, i; - if (filter->rtnl_held) - ASSERT_RTNL(); - rtm = nlmsg_payload(nlh, sizeof(*rtm)); if (!rtm) { NL_SET_ERR_MSG(extack, "Invalid header for FIB dump request"); @@ -992,10 +989,8 @@ int ip_valid_fib_dump_req(struct net *net, const struct nlmsghdr *nlh, break; case RTA_OIF: ifindex = nla_get_u32(tb[i]); - if (filter->rtnl_held) - filter->dev = __dev_get_by_index(net, ifindex); - else - filter->dev = dev_get_by_index_rcu(net, ifindex); + + filter->dev = dev_get_by_index_rcu(net, ifindex); if (!filter->dev) return -ENODEV; break; @@ -1017,18 +1012,16 @@ EXPORT_SYMBOL_GPL(ip_valid_fib_dump_req); static int inet_dump_fib(struct sk_buff *skb, struct netlink_callback *cb) { + const struct nlmsghdr *nlh = cb->nlh; + struct net *net = sock_net(skb->sk); struct fib_dump_filter filter = { .dump_routes = true, .dump_exceptions = true, - .rtnl_held = false, }; - const struct nlmsghdr *nlh = cb->nlh; - struct net *net = sock_net(skb->sk); - unsigned int h, s_h; - unsigned int e = 0, s_e; - struct fib_table *tb; + unsigned int e = 0, s_e, h, s_h; struct hlist_head *head; int dumped = 0, err = 0; + struct fib_table *tb; rcu_read_lock(); if (cb->strict_check) { diff --git a/net/ipv4/ipmr.c b/net/ipv4/ipmr.c index 2628cd3a93a6..78dbeecf71bb 100644 --- a/net/ipv4/ipmr.c +++ b/net/ipv4/ipmr.c @@ -2777,9 +2777,7 @@ errout_unlock: static int ipmr_rtm_dumproute(struct sk_buff *skb, struct netlink_callback *cb) { - struct fib_dump_filter filter = { - .rtnl_held = false, - }; + struct fib_dump_filter filter = {}; int err; rcu_read_lock(); diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c index b897b3c5023b..fc95738ded76 100644 --- a/net/ipv6/ip6_fib.c +++ b/net/ipv6/ip6_fib.c @@ -633,7 +633,6 @@ static int inet6_dump_fib(struct sk_buff *skb, struct netlink_callback *cb) struct rt6_rtnl_dump_arg arg = { .filter.dump_exceptions = true, .filter.dump_routes = true, - .filter.rtnl_held = false, }; const struct nlmsghdr *nlh = cb->nlh; struct net *net = sock_net(skb->sk); diff --git a/net/ipv6/ip6mr.c b/net/ipv6/ip6mr.c index 380e5eb9416d..8c8ad1753c75 100644 --- a/net/ipv6/ip6mr.c +++ b/net/ipv6/ip6mr.c @@ -2745,9 +2745,7 @@ err: static int ip6mr_rtm_dumproute(struct sk_buff *skb, struct netlink_callback *cb) { const struct nlmsghdr *nlh = cb->nlh; - struct fib_dump_filter filter = { - .rtnl_held = false, - }; + struct fib_dump_filter filter = {}; int err; rcu_read_lock(); diff --git a/net/mpls/af_mpls.c b/net/mpls/af_mpls.c index 26340a7306b5..ca504d9626cf 100644 --- a/net/mpls/af_mpls.c +++ b/net/mpls/af_mpls.c @@ -2221,12 +2221,10 @@ static bool mpls_rt_uses_dev(struct mpls_route *rt, static int mpls_dump_routes(struct sk_buff *skb, struct netlink_callback *cb) { + struct mpls_route __rcu **platform_label; const struct nlmsghdr *nlh = cb->nlh; struct net *net = sock_net(skb->sk); - struct mpls_route __rcu **platform_label; - struct fib_dump_filter filter = { - .rtnl_held = false, - }; + struct fib_dump_filter filter = {}; unsigned int flags = NLM_F_MULTI; size_t platform_labels; unsigned int index; -- cgit v1.2.3 From a4c98421720a79587ad6c528f81fe7976a45976b Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Thu, 4 Jun 2026 22:46:32 +0000 Subject: ip6mr: Replace RTNL with a dedicated mutex for MFC. ip6mr does not have rtnetlink interface for MFC unlike ipmr, which uses dev_get_by_index_rcu() to set struct mfcctl.mfcc_parent. ip6mr_mfc_add() and ip6mr_mfc_delete() are called under RTNL from ip6_mroute_setsockopt() only. There are no RTNL dependant, but ip6_mroute_setsockopt() reuses RTNL just for mrt->mfc_hash and mrt->mfc_cache_list. Let's replace RTNL with a new per-netns mutex. Later, ip6mr_notifier_ops and ipmr_seq will be moved under CONFIG_IPV6_MROUTE. Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260604224712.3209821-15-kuniyu@google.com Signed-off-by: Jakub Kicinski --- include/net/netns/ipv6.h | 1 + net/ipv6/ip6mr.c | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) (limited to 'include') diff --git a/include/net/netns/ipv6.h b/include/net/netns/ipv6.h index 875916d60bfe..668f10498c5c 100644 --- a/include/net/netns/ipv6.h +++ b/include/net/netns/ipv6.h @@ -112,6 +112,7 @@ struct netns_ipv6 { struct list_head mr6_tables; struct fib_rules_ops *mr6_rules_ops; #endif + struct mutex mfc_mutex; #endif atomic_t dev_addr_genid; atomic_t fib6_sernum; diff --git a/net/ipv6/ip6mr.c b/net/ipv6/ip6mr.c index b13ce9c2c463..5a4816225fb1 100644 --- a/net/ipv6/ip6mr.c +++ b/net/ipv6/ip6mr.c @@ -1266,7 +1266,6 @@ static int ip6mr_mfc_delete(struct mr_table *mrt, struct mf6cctl *mfc, { struct mfc6_cache *c; - /* The entries are added/deleted only under RTNL */ rcu_read_lock(); c = ip6mr_cache_find_parent(mrt, &mfc->mf6cc_origin.sin6_addr, &mfc->mf6cc_mcastgrp.sin6_addr, parent); @@ -1358,6 +1357,8 @@ static int __net_init ip6mr_net_init(struct net *net) #endif int err; + mutex_init(&net->ipv6.mfc_mutex); + err = ip6mr_notifier_init(net); if (err) return err; @@ -1486,7 +1487,6 @@ static int ip6mr_mfc_add(struct net *net, struct mr_table *mrt, ttls[i] = 1; } - /* The entries are added/deleted only under RTNL */ rcu_read_lock(); c = ip6mr_cache_find_parent(mrt, &mfc->mf6cc_origin.sin6_addr, &mfc->mf6cc_mcastgrp.sin6_addr, parent); @@ -1581,6 +1581,8 @@ static void mroute_clean_tables(struct mr_table *mrt, int flags, /* Wipe the cache */ if (flags & (MRT6_FLUSH_MFC | MRT6_FLUSH_MFC_STATIC)) { + mutex_lock(&net->ipv6.mfc_mutex); + list_for_each_entry_safe(c, tmp, &mrt->mfc_cache_list, list) { if (((c->mfc_flags & MFC_STATIC) && !(flags & MRT6_FLUSH_MFC_STATIC)) || (!(c->mfc_flags & MFC_STATIC) && !(flags & MRT6_FLUSH_MFC))) @@ -1592,6 +1594,8 @@ static void mroute_clean_tables(struct mr_table *mrt, int flags, mr6_netlink_event(mrt, (struct mfc6_cache *)c, RTM_DELROUTE); mr_cache_put(c); } + + mutex_unlock(&net->ipv6.mfc_mutex); } if (flags & MRT6_FLUSH_MFC) { @@ -1775,15 +1779,18 @@ int ip6_mroute_setsockopt(struct sock *sk, int optname, sockptr_t optval, return -EFAULT; if (parent == 0) parent = mfc.mf6cc_parent; - rtnl_lock(); + + mutex_lock(&net->ipv6.mfc_mutex); + if (optname == MRT6_DEL_MFC || optname == MRT6_DEL_MFC_PROXY) ret = ip6mr_mfc_delete(mrt, &mfc, parent); else ret = ip6mr_mfc_add(net, mrt, &mfc, sk == - rtnl_dereference(mrt->mroute_sk), + rcu_access_pointer(mrt->mroute_sk), parent); - rtnl_unlock(); + + mutex_unlock(&net->ipv6.mfc_mutex); return ret; case MRT6_FLUSH: -- cgit v1.2.3 From 5499868699eebf14ff143495766264964b3a2e9e Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Thu, 4 Jun 2026 22:46:33 +0000 Subject: ip6mr: Define net->ipv6.{ip6mr_notifier_ops,ipmr_seq} under CONFIG_IPV6_MROUTE. net->ipv6.ip6mr_notifier_ops and net->ipv6.ipmr_seq are used only in net/ipv6/ip6mr.c. Let's move these definitions under CONFIG_IPV6_MROUTE. Signed-off-by: Kuniyuki Iwashima Link: https://patch.msgid.link/20260604224712.3209821-16-kuniyu@google.com Signed-off-by: Jakub Kicinski --- include/net/netns/ipv6.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/net/netns/ipv6.h b/include/net/netns/ipv6.h index 668f10498c5c..ffda15318be5 100644 --- a/include/net/netns/ipv6.h +++ b/include/net/netns/ipv6.h @@ -112,14 +112,14 @@ struct netns_ipv6 { struct list_head mr6_tables; struct fib_rules_ops *mr6_rules_ops; #endif + struct fib_notifier_ops *ip6mr_notifier_ops; + atomic_t ipmr_seq; struct mutex mfc_mutex; #endif atomic_t dev_addr_genid; atomic_t fib6_sernum; struct seg6_pernet_data *seg6_data; struct fib_notifier_ops *notifier_ops; - struct fib_notifier_ops *ip6mr_notifier_ops; - atomic_t ipmr_seq; int flowlabel_count; struct { struct hlist_head head; -- cgit v1.2.3 From 69710a37bad62afc3d2f1fbf7b108e5f8b3808cd Mon Sep 17 00:00:00 2001 From: David 'equinox' Lamparter Date: Fri, 5 Jun 2026 18:41:44 +0200 Subject: if_ether.h: add 802.1AC, warn about GRE 0x00FE Because LLC wasn't complicated/annoying enough, there's 2 more "ethertypes" being used for it: - 0x8870 is pretty "normal", it got standardized in 802.1AC-2016/Cor1-2018 for transporting LLC frames > 1500 bytes. It simply replaces the length value (which is no longer encoded, and must now be derived from the packet.) The actual value dates back to 2001; https://datatracker.ietf.org/doc/html/draft-ietf-isis-ext-eth-01 (it was used without "proper" standardization for a long time) - 0x00fe is a doozy - actually "invalid" depending on how you look at it; it's used in GRE (and possibly GENEVE) tunnels to transport the IS-IS routing protocol. https://seclists.org/tcpdump/2002/q4/61 is the best/oldest source I could find. It's inspired by the 0xfe SAP value, a GRE packet with protocol 0x00fe is followed by a payload "as if" it was Ethernet with " 0xfe 0xfe 0x03". (Again the length isn't encoded explicitly anymore.) The 0x00fe value is quite close to other values the kernel is using internally for various things (after all they "won't clash for 1500 types"). Except this one does clash, and if someone unknowingly starts using it for something internal... we end up in a world of pain in getting IS-IS running on GRE tunnels. Hence the "WARNING". Signed-off-by: David 'equinox' Lamparter Cc: Andrew Lunn Link: https://patch.msgid.link/20260605164144.81184-1-equinox@diac24.net Signed-off-by: Jakub Kicinski --- include/uapi/linux/if_ether.h | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'include') diff --git a/include/uapi/linux/if_ether.h b/include/uapi/linux/if_ether.h index fb5efc8e06cc..1ffac52c39df 100644 --- a/include/uapi/linux/if_ether.h +++ b/include/uapi/linux/if_ether.h @@ -82,6 +82,7 @@ #define ETH_P_PPP_DISC 0x8863 /* PPPoE discovery messages */ #define ETH_P_PPP_SES 0x8864 /* PPPoE session messages */ #define ETH_P_LINK_CTL 0x886c /* HPNA, wlan link local tunnel */ +#define ETH_P_8021AC 0x8870 /* 802.1AC LLC > 1500 bytes */ #define ETH_P_ATMFATE 0x8884 /* Frame-based ATM Transport * over Ethernet */ @@ -164,6 +165,10 @@ #define ETH_P_MCTP 0x00FA /* Management component transport * protocol packets */ +#define ETH_P_GRE_OSI 0x00FE /* GRE tunnels: LLC "fe fe 03" analog, + * used primarily for IS-IS over GRE + * WARNING: not internal, used on wire! + */ /* * This is an Ethernet frame header. -- cgit v1.2.3 From 22f8bf8808dc80a44f88a3ab1776ddb1baaa12d8 Mon Sep 17 00:00:00 2001 From: Chuck Lever Date: Thu, 4 Jun 2026 13:48:28 -0400 Subject: tls: Suppress spurious saved_data_ready on all receive paths Each record release via tls_strp_msg_done() triggered tls_strp_check_rcv(), which called tls_rx_msg_ready() and fired saved_data_ready(). During a multi-record receive, the first N-1 wakeups are pure overhead: the caller is already running and will pick up subsequent records on the next loop iteration. The recvmsg and splice_read paths share this waste. Suppress per-record notifications and emit a single one on reader exit. tls_rx_rec_done() releases the current record and parses the next without announcing; tls_strp_check_rcv() gains a bool announce parameter so callers can request the quiet form. tls_rx_reader_release() fires the deferred announce on exit through tls_rx_msg_maybe_announce(), an idempotent helper that calls saved_data_ready() only when a record is parsed and has not yet been announced. To keep the final notification idempotent against records that the BH or the worker has already announced, tls_strparser gains a msg_announced bit. tls_rx_msg_maybe_announce() sets the bit when firing saved_data_ready(); the bit is cleared whenever the parsed record is wiped, by tls_strp_msg_consume() on consumption or by tls_strp_msg_load() when the lower socket loses bytes from under the parse. A second call for the same parsed record -- as when recvmsg() satisfies the request from ctx->rx_list without touching the strparser -- becomes a no-op. With no remaining callers, tls_strp_msg_done() is removed. Signed-off-by: Chuck Lever Reviewed-by: Hannes Reinecke Reviewed-by: Sabrina Dubroca Link: https://patch.msgid.link/20260604-tls-read-sock-v12-5-b114efa6e3e2@oracle.com Signed-off-by: Jakub Kicinski --- include/net/tls.h | 5 +++++ net/tls/tls.h | 5 ++--- net/tls/tls_main.c | 2 +- net/tls/tls_strp.c | 23 ++++++++++++----------- net/tls/tls_sw.c | 27 ++++++++++++++++++++++++--- 5 files changed, 44 insertions(+), 18 deletions(-) (limited to 'include') diff --git a/include/net/tls.h b/include/net/tls.h index ebd2550280ae..3811943288b3 100644 --- a/include/net/tls.h +++ b/include/net/tls.h @@ -111,11 +111,16 @@ struct tls_sw_context_tx { struct tls_strparser { struct sock *sk; + /* Bitfield word and msg_ready are serialized by the lower + * socket lock; BH and worker contexts both acquire it. + */ u32 mark : 8; u32 stopped : 1; u32 copy_mode : 1; u32 mixed_decrypted : 1; + u32 msg_announced : 1; + bool msg_ready; struct strp_msg stm; diff --git a/net/tls/tls.h b/net/tls/tls.h index cb0091e03f41..60a37bdaaa25 100644 --- a/net/tls/tls.h +++ b/net/tls/tls.h @@ -193,12 +193,11 @@ void tls_strp_stop(struct tls_strparser *strp); int tls_strp_init(struct tls_strparser *strp, struct sock *sk); void tls_strp_data_ready(struct tls_strparser *strp); -void tls_strp_check_rcv(struct tls_strparser *strp); +void tls_strp_check_rcv(struct tls_strparser *strp, bool announce); void tls_strp_msg_consume(struct tls_strparser *strp); -void tls_strp_msg_done(struct tls_strparser *strp); int tls_rx_msg_size(struct tls_strparser *strp, struct sk_buff *skb); -void tls_rx_msg_ready(struct tls_strparser *strp); +void tls_rx_msg_maybe_announce(struct tls_strparser *strp); bool tls_strp_msg_load(struct tls_strparser *strp, bool force_refresh); int tls_strp_msg_cow(struct tls_sw_context_rx *ctx); diff --git a/net/tls/tls_main.c b/net/tls/tls_main.c index fd39acf41a61..c10a3fd7fc17 100644 --- a/net/tls/tls_main.c +++ b/net/tls/tls_main.c @@ -769,7 +769,7 @@ static int do_tls_setsockopt_conf(struct sock *sk, sockptr_t optval, } else { struct tls_sw_context_rx *rx_ctx = tls_sw_ctx_rx(ctx); - tls_strp_check_rcv(&rx_ctx->strp); + tls_strp_check_rcv(&rx_ctx->strp, true); } return 0; diff --git a/net/tls/tls_strp.c b/net/tls/tls_strp.c index e7aaee6efe6e..61b10c697ecc 100644 --- a/net/tls/tls_strp.c +++ b/net/tls/tls_strp.c @@ -368,7 +368,6 @@ static int tls_strp_copyin(read_descriptor_t *desc, struct sk_buff *in_skb, desc->count = 0; WRITE_ONCE(strp->msg_ready, 1); - tls_rx_msg_ready(strp); } return ret; @@ -492,6 +491,7 @@ bool tls_strp_msg_load(struct tls_strparser *strp, bool force_refresh) if (!strp->copy_mode && force_refresh) { if (unlikely(tcp_inq(strp->sk) < strp->stm.full_len)) { WRITE_ONCE(strp->msg_ready, 0); + strp->msg_announced = 0; memset(&strp->stm, 0, sizeof(strp->stm)); return false; } @@ -539,18 +539,24 @@ static int tls_strp_read_sock(struct tls_strparser *strp) return tls_strp_read_copy(strp, false); WRITE_ONCE(strp->msg_ready, 1); - tls_rx_msg_ready(strp); return 0; } -void tls_strp_check_rcv(struct tls_strparser *strp) +/* Parse queued data. When @announce is true and parsing produces a + * newly-ready record, fire the consumer notification. Callers that + * need to notify a waiter about a record parsed by another path + * should invoke tls_rx_msg_maybe_announce() directly. + */ +void tls_strp_check_rcv(struct tls_strparser *strp, bool announce) { if (unlikely(strp->stopped) || strp->msg_ready) return; if (tls_strp_read_sock(strp) == -ENOMEM) queue_work(tls_strp_wq, &strp->work); + else if (announce && strp->msg_ready) + tls_rx_msg_maybe_announce(strp); } /* Lower sock lock held */ @@ -568,7 +574,7 @@ void tls_strp_data_ready(struct tls_strparser *strp) return; } - tls_strp_check_rcv(strp); + tls_strp_check_rcv(strp, true); } static void tls_strp_work(struct work_struct *w) @@ -577,7 +583,7 @@ static void tls_strp_work(struct work_struct *w) container_of(w, struct tls_strparser, work); lock_sock(strp->sk); - tls_strp_check_rcv(strp); + tls_strp_check_rcv(strp, true); release_sock(strp->sk); } @@ -596,15 +602,10 @@ void tls_strp_msg_consume(struct tls_strparser *strp) tls_strp_flush_anchor_copy(strp); WRITE_ONCE(strp->msg_ready, 0); + strp->msg_announced = 0; memset(&strp->stm, 0, sizeof(strp->stm)); } -void tls_strp_msg_done(struct tls_strparser *strp) -{ - tls_strp_msg_consume(strp); - tls_strp_check_rcv(strp); -} - void tls_strp_stop(struct tls_strparser *strp) { strp->stopped = 1; diff --git a/net/tls/tls_sw.c b/net/tls/tls_sw.c index 798f2535ddf7..df4cdf11f784 100644 --- a/net/tls/tls_sw.c +++ b/net/tls/tls_sw.c @@ -1401,7 +1401,10 @@ tls_rx_rec_wait(struct sock *sk, struct sk_psock *psock, bool nonblock, return ret; if (!skb_queue_empty(&sk->sk_receive_queue)) { - tls_strp_check_rcv(&ctx->strp); + /* Defer notification to the exit point; this thread + * will consume the record directly. + */ + tls_strp_check_rcv(&ctx->strp, false); if (tls_strp_msg_ready(ctx)) break; } @@ -1887,9 +1890,13 @@ static int tls_record_content_type(struct msghdr *msg, struct tls_msg *tlm, return 1; } +/* The deferred announce is fired once on reader exit by + * tls_rx_reader_release(). + */ static void tls_rx_rec_done(struct tls_sw_context_rx *ctx) { - tls_strp_msg_done(&ctx->strp); + tls_strp_msg_consume(&ctx->strp); + tls_strp_check_rcv(&ctx->strp, false); } /* This function traverses the rx_list in tls receive context to copies the @@ -2044,6 +2051,12 @@ static int tls_rx_reader_lock(struct sock *sk, struct tls_sw_context_rx *ctx, static void tls_rx_reader_release(struct sock *sk, struct tls_sw_context_rx *ctx) { + /* Fire any deferred announce once per reader so that a record + * parsed but not yet announced becomes visible to the next + * reader. The call is idempotent through msg_announced. + */ + tls_rx_msg_maybe_announce(&ctx->strp); + if (unlikely(ctx->reader_contended)) { if (wq_has_sleeper(&ctx->wq)) wake_up(&ctx->wq); @@ -2520,10 +2533,18 @@ read_failure: return ret; } -void tls_rx_msg_ready(struct tls_strparser *strp) +/* Fire saved_data_ready() at most once per parsed record. The + * msg_announced bit is cleared by tls_strp_msg_consume() when the + * record is consumed, arming the next announcement. + */ +void tls_rx_msg_maybe_announce(struct tls_strparser *strp) { struct tls_sw_context_rx *ctx; + if (!READ_ONCE(strp->msg_ready) || strp->msg_announced) + return; + strp->msg_announced = 1; + ctx = container_of(strp, struct tls_sw_context_rx, strp); ctx->saved_data_ready(strp->sk); } -- cgit v1.2.3 From 06528596662bf9a34d3691eb5b00bc39b228c994 Mon Sep 17 00:00:00 2001 From: Victor Nogueira Date: Fri, 5 Jun 2026 18:22:39 -0300 Subject: net/sched: Update function name in TCQ_F_NOPARENT comment Commit 2ccccf5fb43f ("net_sched: update hierarchical backlog too") renamed qdisc_tree_decrease_qlen() to qdisc_tree_reduce_backlog(), but this comment was missed. Update it. Signed-off-by: Victor Nogueira Link: https://patch.msgid.link/20260605212239.2261320-1-victor@mojatatu.com Signed-off-by: Paolo Abeni --- include/net/sch_generic.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include') diff --git a/include/net/sch_generic.h b/include/net/sch_generic.h index d0ca932b1871..45a1e8c78222 100644 --- a/include/net/sch_generic.h +++ b/include/net/sch_generic.h @@ -86,7 +86,7 @@ struct Qdisc { #define TCQ_F_WARN_NONWC (1 << 16) #define TCQ_F_CPUSTATS 0x20 /* run using percpu statistics */ #define TCQ_F_NOPARENT 0x40 /* root of its hierarchy : - * qdisc_tree_decrease_qlen() should stop. + * qdisc_tree_reduce_backlog() should stop. */ #define TCQ_F_INVISIBLE 0x80 /* invisible by default in dump */ #define TCQ_F_NOLOCK 0x100 /* qdisc does not require locking */ -- cgit v1.2.3 From 6c537b845c99e32312a1bd84d4c95cdb26efb577 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Thu, 4 Jun 2026 09:10:11 -0700 Subject: netconsole: do not dequeue pooled skbs that cannot satisfy len find_skb() falls back to np->skb_pool when the GFP_ATOMIC alloc_skb() fails. The pool is refilled by refill_skbs(), which always allocates buffers of MAX_SKB_SIZE (ethhdr + iphdr + udphdr + MAX_UDP_CHUNK == 1502 bytes). netconsole, however, computes the requested length dynamically as total_len + np->dev->needed_tailroom If the egress device declares a non-zero needed_tailroom (e.g. some tunnel or hardware accelerator devices), the required length can exceed MAX_SKB_SIZE. The pooled skb is then handed back to the caller, which immediately performs skb_put(skb, len), trips the tail > end check, and triggers skb_over_panic(). Leave the normal alloc_skb(len, GFP_ATOMIC) path untouched -- the slab allocator can still satisfy oversized requests when memory is available, so senders to devices with non-zero needed_tailroom keep working in the common case. Only the pool fallback is gated: when alloc_skb() failed and len exceeds the pool buffer size, skip the skb_dequeue() instead of burning a pre-allocated skb on a request that would later trip skb_over_panic(). Reserving pool entries for requests they can actually satisfy also keeps the panic path, which depends on the pool being primed, intact. When that drop happens, emit a rate-limited net_warn() so the user notices that netconsole is unable to push messages on the egress device. The warn is skipped under in_nmi() for the same reason schedule_work() is: printk machinery taken by net_warn_ratelimited() is not NMI-safe and would risk recursing into the same nbcon console we are servicing. MAX_SKB_SIZE / MAX_UDP_CHUNK were private to net/core/netpoll.c. Move them to include/linux/netpoll.h so netconsole can reference the same definition that refill_skbs() uses, keeping the two in sync by construction. The header now pulls in and explicitly so MAX_SKB_SIZE remains self-contained for any future user. Signed-off-by: Breno Leitao Link: https://patch.msgid.link/20260604-netcons_fix_before_move-v3-2-ab055b3a6aa5@debian.org Signed-off-by: Paolo Abeni --- drivers/net/netconsole.c | 22 ++++++++++++++++++++-- include/linux/netpoll.h | 16 ++++++++++++++++ net/core/netpoll.c | 7 ------- 3 files changed, 36 insertions(+), 9 deletions(-) (limited to 'include') diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index 918e4a9f4456..58250e648f8b 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -1655,15 +1655,33 @@ static struct notifier_block netconsole_netdev_notifier = { }; /* Pop a pre-allocated skb from the pool and request a refill. + * + * The pool is refilled with MAX_SKB_SIZE buffers, so a pooled skb cannot + * satisfy a larger request. Return NULL in that case rather than handing + * back a too-small skb that would later trip skb_over_panic() in skb_put(); + * the caller still polls and retries, and alloc_skb() itself can satisfy the + * oversized request once memory frees up. * * The refill is requested via schedule_work(), which takes the workqueue * pool locks and is therefore not NMI-safe. Skip the refill when called * from NMI context; the next non-NMI caller will top the pool back up. */ -static struct sk_buff *netcons_skb_pop(struct netpoll *np) +static struct sk_buff *netcons_skb_pop(struct netpoll *np, int len) { struct sk_buff *skb; + if (len > MAX_SKB_SIZE) { + /* net_warn_ratelimited() pulls in printk machinery that is not + * NMI-safe and could recurse into the nbcon console we are + * servicing, so only warn outside NMI. + */ + if (!in_nmi()) + net_warn_ratelimited("netconsole: dropping message, requested skb len %d exceeds pool buffer size %zu on %s\n", + len, (size_t)MAX_SKB_SIZE, + np->dev->name); + return NULL; + } + skb = skb_dequeue(&np->skb_pool); if (!in_nmi()) schedule_work(&np->refill_wq); @@ -1681,7 +1699,7 @@ repeat: skb = alloc_skb(len, GFP_ATOMIC); if (!skb) - skb = netcons_skb_pop(np); + skb = netcons_skb_pop(np, len); if (!skb) { if (++count < 10) { diff --git a/include/linux/netpoll.h b/include/linux/netpoll.h index e4b8f1f91e54..88f7daa8560e 100644 --- a/include/linux/netpoll.h +++ b/include/linux/netpoll.h @@ -13,12 +13,28 @@ #include #include #include +#include +#include union inet_addr { __be32 ip; struct in6_addr in6; }; +/* + * Maximum payload netpoll's preallocated skb pool can carry. Keep this in + * sync with the buffer size used by refill_skbs() in net/core/netpoll.c; + * callers (e.g. netconsole) use it to detect requests the pool can never + * satisfy and avoid dequeuing a pooled skb that would later trip + * skb_over_panic() in skb_put(). + */ +#define MAX_UDP_CHUNK 1460 +#define MAX_SKB_SIZE \ + (sizeof(struct ethhdr) + \ + sizeof(struct iphdr) + \ + sizeof(struct udphdr) + \ + MAX_UDP_CHUNK) + struct netpoll { struct net_device *dev; netdevice_tracker dev_tracker; diff --git a/net/core/netpoll.c b/net/core/netpoll.c index b3fe59445f2d..229dde818ab3 100644 --- a/net/core/netpoll.c +++ b/net/core/netpoll.c @@ -41,16 +41,9 @@ * message gets out even in extreme OOM situations. */ -#define MAX_UDP_CHUNK 1460 #define MAX_SKBS 32 #define USEC_PER_POLL 50 -#define MAX_SKB_SIZE \ - (sizeof(struct ethhdr) + \ - sizeof(struct iphdr) + \ - sizeof(struct udphdr) + \ - MAX_UDP_CHUNK) - static unsigned int carrier_timeout = 4; module_param(carrier_timeout, uint, 0644); -- cgit v1.2.3 From ded86da4bbb78cad74cecc368fee3ae3a296e2ca Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Thu, 4 Jun 2026 17:29:02 -0700 Subject: net: ethtool: relax ethnl_req_get_phydev() locking assertion phydev <> netdev linking and lifecycle depends on rtnl_lock. We want to switch to instance locks for most ethtool ops. Let's add an assert that ops locked devices don't use phydev today. If one does we can either opt the phy ops out of being purely ops locked, or do deeper surgery to make phy locking ops-compatible. I don't think there's any fundamental challenge to make that work. Reviewed-by: Nicolai Buchwitz Reviewed-by: Maxime Chevallier Reviewed-by: Eric Dumazet Acked-by: Stanislav Fomichev Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260605002912.3456868-3-kuba@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/phy/phy_link_topology.c | 8 ++++++++ include/linux/phy_link_topology.h | 5 +++++ net/ethtool/netlink.c | 6 ++++-- net/ethtool/netlink.h | 7 ++++--- net/ethtool/phy.c | 1 - 5 files changed, 21 insertions(+), 6 deletions(-) (limited to 'include') diff --git a/drivers/net/phy/phy_link_topology.c b/drivers/net/phy/phy_link_topology.c index 1f1eb5d59b38..aed3b26c1674 100644 --- a/drivers/net/phy/phy_link_topology.c +++ b/drivers/net/phy/phy_link_topology.c @@ -10,6 +10,7 @@ #include #include #include +#include static int netdev_alloc_phy_link_topology(struct net_device *dev) { @@ -35,6 +36,13 @@ int phy_link_topo_add_phy(struct net_device *dev, struct phy_device_node *pdn; int ret; + /* ethtool ops may run without rtnl_lock, and rtnl_lock is what + * currently protects the PHY topology. No driver currently mixes + * the two, flag if someone tries. See also ethnl_req_get_phydev(). + */ + if (WARN_ON_ONCE(netdev_need_ops_lock(dev))) + return -EOPNOTSUPP; + if (!topo) { ret = netdev_alloc_phy_link_topology(dev); if (ret) diff --git a/include/linux/phy_link_topology.h b/include/linux/phy_link_topology.h index 68a59e25821c..95575f68d5bc 100644 --- a/include/linux/phy_link_topology.h +++ b/include/linux/phy_link_topology.h @@ -36,6 +36,11 @@ struct phy_device_node { struct phy_device *phy; }; +static inline bool phy_link_topo_empty(struct net_device *dev) +{ + return !dev->link_topo; +} + #if IS_ENABLED(CONFIG_PHYLIB) int phy_link_topo_add_phy(struct net_device *dev, struct phy_device *phy, diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c index c4054a9795ff..afafed738584 100644 --- a/net/ethtool/netlink.c +++ b/net/ethtool/netlink.c @@ -226,11 +226,13 @@ struct phy_device *ethnl_req_get_phydev(const struct ethnl_req_info *req_info, { struct phy_device *phydev; - ASSERT_RTNL(); - if (!req_info->dev) return NULL; + /* If there is no PHY in sight there's no need for assert locking */ + if (!phy_link_topo_empty(req_info->dev)) + ASSERT_RTNL(); + if (!req_info->phy_index) return req_info->dev->phydev; diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h index f94aaa66379c..4ca2eca2e94b 100644 --- a/net/ethtool/netlink.h +++ b/net/ethtool/netlink.h @@ -275,14 +275,15 @@ static inline void ethnl_parse_header_dev_put(struct ethnl_req_info *req_info) /** * ethnl_req_get_phydev() - Gets the phy_device targeted by this request, - * if any. Must be called under rntl_lock(). + * if any. * @req_info: The ethnl request to get the phy from. * @tb: The netlink attributes array, for error reporting. * @header: The netlink header index, used for error reporting. * @extack: The netlink extended ACK, for error reporting. * - * The caller must hold RTNL, until it's done interacting with the returned - * phy_device. + * If a phy_device is returned the caller must hold rtnl_lock when calling + * this function, and until it's done interacting with the returned phy_device. + * IOW caller must hold rtnl_lock unless they know netdev has no phy_device. * * Return: A phy_device pointer corresponding either to the passed phy_index * if one is provided. If not, the phy_device attached to the diff --git a/net/ethtool/phy.c b/net/ethtool/phy.c index ddc6eab701ed..018b0412be86 100644 --- a/net/ethtool/phy.c +++ b/net/ethtool/phy.c @@ -78,7 +78,6 @@ static int phy_prepare_data(const struct ethnl_req_info *req_info, struct phy_device *phydev; int ret; - /* RTNL is held by the caller */ phydev = ethnl_req_get_phydev(req_info, tb, ETHTOOL_A_PHY_HEADER, info->extack); if (IS_ERR_OR_NULL(phydev)) -- cgit v1.2.3 From 97f51bf91b3afa8819fa10e9282e3f2328bb78e4 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Thu, 4 Jun 2026 17:29:03 -0700 Subject: net: ethtool: make dev->hwprov ops-protected dev->hwprov tracks the active hwtstamp provider for the device. Make it ops protected (instance lock if the netdev driver opts into holding instance lock around callbacks, otherwise rtnl_lock). hwprov is written and read in: - drivers/net/phy/phy_device.c phydev and ops protection don't currently mix, add a comment - net/ethtool/ as of now holds both rtnl lock and ops lock, this one will soon only hold one lock or the other read in: - net/core/dev_ioctl.c holds both rtnl lock and ops lock - net/core/timestamping.c RCU reader The new netdev_ops_lock_dereference() helper does not have "compat" in the name. The name would be quite long and I think in this case it should be obvious that we need _a_ lock. netdev_lock_dereference() already exists and means dev->lock is always expected. Reviewed-by: Eric Dumazet Acked-by: Stanislav Fomichev Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260605002912.3456868-4-kuba@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/phy/phy_device.c | 3 +++ drivers/net/phy/phy_link_topology.c | 4 +++- include/linux/netdevice.h | 3 +++ include/net/netdev_lock.h | 11 +++++++++++ net/core/dev_ioctl.c | 4 ++-- net/ethtool/tsconfig.c | 10 ++++++---- 6 files changed, 28 insertions(+), 7 deletions(-) (limited to 'include') diff --git a/drivers/net/phy/phy_device.c b/drivers/net/phy/phy_device.c index 3370eb822017..ea53e477465d 100644 --- a/drivers/net/phy/phy_device.c +++ b/drivers/net/phy/phy_device.c @@ -1935,6 +1935,9 @@ void phy_detach(struct phy_device *phydev) if (dev) { struct hwtstamp_provider *hwprov; + /* hwprov may technically be protected by ops lock but + * not for devices with a phydev, see phy_link_topo_add_phy() + */ hwprov = rtnl_dereference(dev->hwprov); /* Disable timestamp if it is the one selected */ if (hwprov && hwprov->phydev == phydev) { diff --git a/drivers/net/phy/phy_link_topology.c b/drivers/net/phy/phy_link_topology.c index aed3b26c1674..4134de7ae313 100644 --- a/drivers/net/phy/phy_link_topology.c +++ b/drivers/net/phy/phy_link_topology.c @@ -38,7 +38,9 @@ int phy_link_topo_add_phy(struct net_device *dev, /* ethtool ops may run without rtnl_lock, and rtnl_lock is what * currently protects the PHY topology. No driver currently mixes - * the two, flag if someone tries. See also ethnl_req_get_phydev(). + * the two, flag if someone tries. See also: + * - ethnl_req_get_phydev() + * - phy_detach() */ if (WARN_ON_ONCE(netdev_need_ops_lock(dev))) return -EOPNOTSUPP; diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 74507c006490..a8709d0cc8d4 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -2583,6 +2583,9 @@ struct net_device { * Double protects: * @up, @moving_ns, @nd_net, @xdp_features * + * Ops protects: + * @hwprov + * * Double ops protects: * @real_num_rx_queues, @real_num_tx_queues * diff --git a/include/net/netdev_lock.h b/include/net/netdev_lock.h index d3daec4e93f3..9fb3e93857c3 100644 --- a/include/net/netdev_lock.h +++ b/include/net/netdev_lock.h @@ -102,6 +102,14 @@ static inline void netdev_unlock_ops_compat(struct net_device *dev) rtnl_unlock(); } +/* Matching "ops protected" category from netdevice.h */ +static inline int netdev_is_locked_ops_compat(const struct net_device *dev) +{ + if (netdev_need_ops_lock(dev)) + return lockdep_is_held(&dev->lock); + return lockdep_rtnl_is_held(); +} + static inline int netdev_lock_cmp_fn(const struct lockdep_map *a, const struct lockdep_map *b) { @@ -138,6 +146,9 @@ static inline int netdev_lock_cmp_fn(const struct lockdep_map *a, #define netdev_lock_dereference(p, dev) \ rcu_dereference_protected(p, lockdep_is_held(&(dev)->lock)) +#define netdev_ops_lock_dereference(p, dev) \ + rcu_dereference_protected(p, netdev_is_locked_ops_compat(dev)) + int netdev_debug_event(struct notifier_block *nb, unsigned long event, void *ptr); diff --git a/net/core/dev_ioctl.c b/net/core/dev_ioctl.c index f3979b276090..a320e264eaaf 100644 --- a/net/core/dev_ioctl.c +++ b/net/core/dev_ioctl.c @@ -260,7 +260,7 @@ int dev_get_hwtstamp_phylib(struct net_device *dev, { struct hwtstamp_provider *hwprov; - hwprov = rtnl_dereference(dev->hwprov); + hwprov = netdev_ops_lock_dereference(dev->hwprov, dev); if (hwprov) { cfg->qualifier = hwprov->desc.qualifier; if (hwprov->source == HWTSTAMP_SOURCE_PHYLIB && @@ -337,7 +337,7 @@ int dev_set_hwtstamp_phylib(struct net_device *dev, bool phy_ts; int err; - hwprov = rtnl_dereference(dev->hwprov); + hwprov = netdev_ops_lock_dereference(dev->hwprov, dev); if (hwprov) { if (hwprov->source == HWTSTAMP_SOURCE_PHYLIB && hwprov->phydev) { diff --git a/net/ethtool/tsconfig.c b/net/ethtool/tsconfig.c index 664c3fe49b5b..24b64862011f 100644 --- a/net/ethtool/tsconfig.c +++ b/net/ethtool/tsconfig.c @@ -2,6 +2,7 @@ #include #include +#include #include "bitset.h" #include "common.h" @@ -57,7 +58,7 @@ static int tsconfig_prepare_data(const struct ethnl_req_info *req_base, data->hwtst_config.flags = cfg.flags; data->hwprov_desc.index = -1; - hwprov = rtnl_dereference(dev->hwprov); + hwprov = netdev_ops_lock_dereference(dev->hwprov, dev); if (hwprov) { data->hwprov_desc.index = hwprov->desc.index; data->hwprov_desc.qualifier = hwprov->desc.qualifier; @@ -213,7 +214,7 @@ static int tsconfig_send_reply(struct net_device *dev, struct genl_info *info) return -ENOMEM; } - ASSERT_RTNL(); + netdev_assert_locked_ops_compat(dev); reply_data->base.dev = dev; ret = tsconfig_prepare_data(&req_info->base, &reply_data->base, info); if (ret < 0) @@ -316,7 +317,7 @@ static int ethnl_set_tsconfig(struct ethnl_req_info *req_base, struct hwtstamp_provider_desc __hwprov_desc = {.index = -1}; struct hwtstamp_provider *__hwprov; - __hwprov = rtnl_dereference(dev->hwprov); + __hwprov = netdev_ops_lock_dereference(dev->hwprov, dev); if (__hwprov) { __hwprov_desc.index = __hwprov->desc.index; __hwprov_desc.qualifier = __hwprov->desc.qualifier; @@ -414,7 +415,8 @@ static int ethnl_set_tsconfig(struct ethnl_req_info *req_base, goto err_free_hwprov; /* Change the selected hwtstamp source */ - __hwprov = rcu_replace_pointer_rtnl(dev->hwprov, hwprov); + __hwprov = rcu_replace_pointer(dev->hwprov, hwprov, + netdev_is_locked_ops_compat(dev)); if (__hwprov) kfree_rcu(__hwprov, rcu_head); } -- cgit v1.2.3 From 45079e00133ee78fd216ccc4285534044ea69173 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Thu, 4 Jun 2026 17:29:04 -0700 Subject: net: ethtool: optionally skip rtnl_lock on Netlink path for GET ops ethnl_default_doit() and ethnl_default_dump_one() are both used exclusively for GET callbacks (former to get info for a single device or get global strings). ops-locked devices don't need rtnl_lock for GET callbacks, stop taking it. Introduce an opt-out mechanism for devices which use phylink (fbnic) since phylink currently depends on rtnl_lock protection. Subsequent patches will add more exceptions, anyway. Practically the new helpers for judging if command needs rtnl_lock could also call netdev_need_ops_lock() but I find that it makes the code in the callers slightly less obvious. Add a helper for IOCTLs already, even tho it's unused so that we can keep them in sync as the series progresses. This is the first user-visible step of moving ethtool ops out from under rtnl. Subsequent patches do the same for SET ops, as well as the ioctl path. Reviewed-by: Eric Dumazet Acked-by: Stanislav Fomichev Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260605002912.3456868-5-kuba@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/google/gve/gve_ethtool.c | 4 +-- drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c | 2 ++ include/linux/ethtool.h | 18 +++++++++- net/ethtool/common.h | 46 +++++++++++++++++++++++++ net/ethtool/mm.c | 5 +-- net/ethtool/netlink.c | 19 +++++++--- 6 files changed, 85 insertions(+), 9 deletions(-) (limited to 'include') diff --git a/drivers/net/ethernet/google/gve/gve_ethtool.c b/drivers/net/ethernet/google/gve/gve_ethtool.c index dc2213b5ce24..7cd43e082b2a 100644 --- a/drivers/net/ethernet/google/gve/gve_ethtool.c +++ b/drivers/net/ethernet/google/gve/gve_ethtool.c @@ -4,7 +4,7 @@ * Copyright (C) 2015-2024 Google LLC */ -#include +#include #include "gve.h" #include "gve_adminq.h" #include "gve_dqo.h" @@ -171,7 +171,7 @@ gve_get_ethtool_stats(struct net_device *netdev, int ring; int i, j; - ASSERT_RTNL(); + netdev_assert_locked(netdev); priv = netdev_priv(netdev); num_tx_queues = gve_num_tx_queues(priv); diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c b/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c index f14de2366854..a2c16d599389 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c @@ -2020,6 +2020,8 @@ static const struct ethtool_ops fbnic_ethtool_ops = { .supported_ring_params = ETHTOOL_RING_USE_TCP_DATA_SPLIT | ETHTOOL_RING_USE_HDS_THRS, .rxfh_max_num_contexts = FBNIC_RPC_RSS_TBL_COUNT, + .op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_LINKSETTINGS | + ETHTOOL_OP_NEEDS_RTNL_GPAUSEPARAM, .get_drvinfo = fbnic_get_drvinfo, .get_regs_len = fbnic_get_regs_len, .get_regs = fbnic_get_regs, diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h index f51346a6a686..1da49161d36f 100644 --- a/include/linux/ethtool.h +++ b/include/linux/ethtool.h @@ -930,6 +930,13 @@ struct kernel_ethtool_ts_info { u32 rx_filters; }; +/* Bits for ethtool_ops::op_needs_rtnl + * LINKSETTINGS cover a number of commands, but in most cases we want to keep + * these bits separate, per GET and SET. GET is much easier to "unlock". + */ +#define ETHTOOL_OP_NEEDS_RTNL_LINKSETTINGS BIT(0) +#define ETHTOOL_OP_NEEDS_RTNL_GPAUSEPARAM BIT(1) + /** * struct ethtool_ops - optional netdev operations * @supported_input_xfrm: supported types of input xfrm from %RXH_XFRM_*. @@ -956,6 +963,14 @@ struct kernel_ethtool_ts_info { * @supported_coalesce_params: supported types of interrupt coalescing. * @supported_ring_params: supported ring params. * @supported_hwtstamp_qualifiers: bitfield of supported hwtstamp qualifier. + * @op_needs_rtnl: mask of %ETHTOOL_OP_NEEDS_RTNL_* bits. + * For use with ops-locked drivers (ignored otherwise). Selects which + * ethtool callbacks driver needs to still be executed under rtnl_lock + * (in addition to the netdev instance lock). + * The following commonly used core APIs currently require rtnl_lock + * (this list may not be exhaustive): + * - phylink helpers (note that phydev is currently unsupported!) + * * @get_drvinfo: Report driver/device information. Modern drivers no * longer have to implement this callback. Most fields are * correctly filled in by the core using system information, or @@ -1155,7 +1170,7 @@ struct kernel_ethtool_ts_info { * * All operations are optional (i.e. the function pointer may be set * to %NULL) and callers must take this into account. Callers must - * hold the RTNL lock. + * hold the RTNL lock or netdev instance lock (see @op_needs_rtnl). * * See the structures used by these operations for further documentation. * Note that for all operations using a structure ending with a zero- @@ -1178,6 +1193,7 @@ struct ethtool_ops { u32 supported_coalesce_params; u32 supported_ring_params; u32 supported_hwtstamp_qualifiers; + u32 op_needs_rtnl; void (*get_drvinfo)(struct net_device *, struct ethtool_drvinfo *); int (*get_regs_len)(struct net_device *); void (*get_regs)(struct net_device *, struct ethtool_regs *, void *); diff --git a/net/ethtool/common.h b/net/ethtool/common.h index 1609cf4e53eb..391c41ca56be 100644 --- a/net/ethtool/common.h +++ b/net/ethtool/common.h @@ -80,6 +80,52 @@ int ethtool_get_module_eeprom_call(struct net_device *dev, bool __ethtool_dev_mm_supported(struct net_device *dev); +/** + * ethtool_nl_msg_needs_rtnl() - does this Netlink cmd need rtnl_lock? + * @dev: target device + * @cmd: ETHTOOL_MSG_* Netlink command value + * + * Return: true if @cmd is a command for which @dev has opted-in to + * keeping rtnl_lock held across the call (via op_needs_rtnl). + */ +static inline bool +ethtool_nl_msg_needs_rtnl(const struct net_device *dev, u8 cmd) +{ + const struct ethtool_ops *ops = dev->ethtool_ops; + + switch (cmd) { + case ETHTOOL_MSG_LINKINFO_GET: + case ETHTOOL_MSG_LINKMODES_GET: + return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_LINKSETTINGS; + case ETHTOOL_MSG_PAUSE_GET: + return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_GPAUSEPARAM; + } + return false; +} + +/** + * ethtool_ioctl_needs_rtnl() - does this legacy ioctl cmd need rtnl_lock? + * @dev: target device + * @ethcmd: ETHTOOL_* ioctl command value + * + * Return: true if @ethcmd is a command for which @dev has opted-in to + * keeping rtnl_lock held across the call (via op_needs_rtnl). + */ +static inline bool +ethtool_ioctl_needs_rtnl(const struct net_device *dev, u32 ethcmd) +{ + const struct ethtool_ops *ops = dev->ethtool_ops; + + switch (ethcmd) { + case ETHTOOL_GLINKSETTINGS: + case ETHTOOL_GSET: + return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_LINKSETTINGS; + case ETHTOOL_GPAUSEPARAM: + return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_GPAUSEPARAM; + } + return false; +} + #if IS_ENABLED(CONFIG_ETHTOOL_NETLINK) void ethtool_rss_notify(struct net_device *dev, u32 type, u32 rss_context); #else diff --git a/net/ethtool/mm.c b/net/ethtool/mm.c index 29bbbc149375..2d10e2a1393f 100644 --- a/net/ethtool/mm.c +++ b/net/ethtool/mm.c @@ -246,8 +246,9 @@ const struct ethnl_request_ops ethnl_mm_request_ops = { }; /* Returns whether a given device supports the MAC merge layer - * (has an eMAC and a pMAC). Must be called under rtnl_lock() and - * ethnl_ops_begin(). + * (has an eMAC and a pMAC). Must be called under whichever lock + * netdev_assert_locked_ops_compat() accepts (rtnl for traditional drivers, + * the netdev instance lock for ops-locked ones) and ethnl_ops_begin(). */ bool __ethtool_dev_mm_supported(struct net_device *dev) { diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c index afafed738584..2c30eb1f4666 100644 --- a/net/ethtool/netlink.c +++ b/net/ethtool/netlink.c @@ -7,6 +7,7 @@ #include #include +#include "common.h" #include "module_fw.h" #include "netlink.h" @@ -509,6 +510,7 @@ static int ethnl_default_doit(struct sk_buff *skb, struct genl_info *info) struct ethnl_req_info *req_info = NULL; const u8 cmd = info->genlhdr->cmd; const struct ethnl_request_ops *ops; + bool need_rtnl = false; int hdr_len, reply_len; struct sk_buff *rskb; void *reply_payload; @@ -535,13 +537,17 @@ static int ethnl_default_doit(struct sk_buff *skb, struct genl_info *info) ethnl_init_reply_data(reply_data, ops, req_info->dev); if (req_info->dev) { - rtnl_lock(); + need_rtnl = !netdev_need_ops_lock(req_info->dev) || + ethtool_nl_msg_needs_rtnl(req_info->dev, cmd); + if (need_rtnl) + rtnl_lock(); netdev_lock_ops(req_info->dev); } ret = ops->prepare_data(req_info, reply_data, info); if (req_info->dev) { netdev_unlock_ops(req_info->dev); - rtnl_unlock(); + if (need_rtnl) + rtnl_unlock(); } if (ret < 0) goto err_dev; @@ -589,6 +595,7 @@ static int ethnl_default_dump_one(struct sk_buff *skb, struct net_device *dev, const struct ethnl_dump_ctx *ctx, const struct genl_info *info) { + bool need_rtnl; void *ehdr; int ret; @@ -599,11 +606,15 @@ static int ethnl_default_dump_one(struct sk_buff *skb, struct net_device *dev, return -EMSGSIZE; ethnl_init_reply_data(ctx->reply_data, ctx->ops, dev); - rtnl_lock(); + need_rtnl = !netdev_need_ops_lock(dev) || + ethtool_nl_msg_needs_rtnl(dev, ctx->ops->request_cmd); + if (need_rtnl) + rtnl_lock(); netdev_lock_ops(dev); ret = ctx->ops->prepare_data(ctx->req_info, ctx->reply_data, info); netdev_unlock_ops(dev); - rtnl_unlock(); + if (need_rtnl) + rtnl_unlock(); if (ret < 0) goto out_cancel; ret = ethnl_fill_reply_header(skb, dev, ctx->ops->hdr_attr); -- cgit v1.2.3 From f9a3e05114b85d63452e7f9c172b53d6a1736fe0 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Thu, 4 Jun 2026 17:29:05 -0700 Subject: net: ethtool: optionally skip rtnl_lock on Netlink path for SET ops Make ethtool not take rtnl_lock for SET commands when operation is performed on an ops-locked driver. cfg/cfg_pending are now ops-locked, since only ethtool modifies them. Some SET driver callbacks will still need rtnl_lock, most notably those which may end up calling netdev_update_features() or the qdisc layer (via netif_set_real_num_tx_queues()). Let drivers selectively opt back into the rtnl_lock with a new bitfield in ops. We need two helpers since Netlink and ioctl cmds have different values. Keep the helpers side by side in common.h to make sure they get updated together, even tho they will only get called from ioctl.c and netlink.c. SET commands which don't use ethnl_default_set_doit() are converted by subsequent commits. Reviewed-by: Eric Dumazet Acked-by: Stanislav Fomichev Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260605002912.3456868-6-kuba@kernel.org Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c | 4 +++ drivers/net/ethernet/google/gve/gve_ethtool.c | 2 ++ .../net/ethernet/mellanox/mlx5/core/en_ethtool.c | 3 +++ drivers/net/ethernet/mellanox/mlx5/core/en_rep.c | 2 ++ .../ethernet/mellanox/mlx5/core/ipoib/ethtool.c | 2 ++ drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c | 5 +++- drivers/net/ethernet/microsoft/mana/mana_ethtool.c | 2 ++ drivers/net/netdevsim/ethtool.c | 1 + include/linux/ethtool.h | 10 +++++++- include/linux/netdevice.h | 2 +- net/ethtool/common.h | 30 ++++++++++++++++++++++ net/ethtool/netlink.c | 9 +++++-- 12 files changed, 67 insertions(+), 5 deletions(-) (limited to 'include') diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c b/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c index edafa79f636c..56d74a3c24b7 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt_ethtool.c @@ -5729,6 +5729,10 @@ const struct ethtool_ops bnxt_ethtool_ops = { .rxfh_max_num_contexts = BNXT_MAX_ETH_RSS_CTX + 1, .rxfh_indir_space = BNXT_MAX_RSS_TABLE_ENTRIES_P5, .rxfh_priv_size = sizeof(struct bnxt_rss_ctx), + .op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_SCHANNELS | + ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM | + ETHTOOL_OP_NEEDS_RTNL_SCOALESCE | + ETHTOOL_OP_NEEDS_RTNL_RSS, .supported_coalesce_params = ETHTOOL_COALESCE_USECS | ETHTOOL_COALESCE_MAX_FRAMES | ETHTOOL_COALESCE_USECS_IRQ | diff --git a/drivers/net/ethernet/google/gve/gve_ethtool.c b/drivers/net/ethernet/google/gve/gve_ethtool.c index 7cd43e082b2a..7cc22916852f 100644 --- a/drivers/net/ethernet/google/gve/gve_ethtool.c +++ b/drivers/net/ethernet/google/gve/gve_ethtool.c @@ -983,6 +983,8 @@ const struct ethtool_ops gve_ethtool_ops = { .supported_coalesce_params = ETHTOOL_COALESCE_USECS, .supported_ring_params = ETHTOOL_RING_USE_TCP_DATA_SPLIT | ETHTOOL_RING_USE_RX_BUF_LEN, + .op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_SCHANNELS | + ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM, .get_drvinfo = gve_get_drvinfo, .get_strings = gve_get_strings, .get_sset_count = gve_get_sset_count, diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c b/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c index 61993485e451..2f5b626ba33f 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_ethtool.c @@ -2719,6 +2719,9 @@ const struct ethtool_ops mlx5e_ethtool_ops = { .rxfh_per_ctx_fields = true, .rxfh_per_ctx_key = true, .rxfh_max_num_contexts = MLX5E_MAX_NUM_RSS, + .op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_SCHANNELS | + ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM | + ETHTOOL_OP_NEEDS_RTNL_SPFLAGS, .supported_coalesce_params = ETHTOOL_COALESCE_USECS | ETHTOOL_COALESCE_MAX_FRAMES | ETHTOOL_COALESCE_USE_ADAPTIVE | diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c b/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c index ba6c0f38cc73..1a8a19f980d3 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_rep.c @@ -418,6 +418,8 @@ static const struct ethtool_ops mlx5e_rep_ethtool_ops = { .supported_coalesce_params = ETHTOOL_COALESCE_USECS | ETHTOOL_COALESCE_MAX_FRAMES | ETHTOOL_COALESCE_USE_ADAPTIVE, + .op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_SCHANNELS | + ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM, .get_drvinfo = mlx5e_rep_get_drvinfo, .get_link = ethtool_op_get_link, .get_strings = mlx5e_rep_get_strings, diff --git a/drivers/net/ethernet/mellanox/mlx5/core/ipoib/ethtool.c b/drivers/net/ethernet/mellanox/mlx5/core/ipoib/ethtool.c index 3b2f54ca30a8..9b3b32408c64 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/ipoib/ethtool.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/ipoib/ethtool.c @@ -285,6 +285,8 @@ const struct ethtool_ops mlx5i_ethtool_ops = { .supported_coalesce_params = ETHTOOL_COALESCE_USECS | ETHTOOL_COALESCE_MAX_FRAMES | ETHTOOL_COALESCE_USE_ADAPTIVE, + .op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_SCHANNELS | + ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM, .get_drvinfo = mlx5i_get_drvinfo, .get_strings = mlx5i_get_strings, .get_sset_count = mlx5i_get_sset_count, diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c b/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c index a2c16d599389..cb34fc166ef9 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_ethtool.c @@ -2021,7 +2021,10 @@ static const struct ethtool_ops fbnic_ethtool_ops = { ETHTOOL_RING_USE_HDS_THRS, .rxfh_max_num_contexts = FBNIC_RPC_RSS_TBL_COUNT, .op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_LINKSETTINGS | - ETHTOOL_OP_NEEDS_RTNL_GPAUSEPARAM, + ETHTOOL_OP_NEEDS_RTNL_GPAUSEPARAM | + ETHTOOL_OP_NEEDS_RTNL_SPAUSEPARAM | + ETHTOOL_OP_NEEDS_RTNL_SCHANNELS | + ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM, .get_drvinfo = fbnic_get_drvinfo, .get_regs_len = fbnic_get_regs_len, .get_regs = fbnic_get_regs, diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c index 04350973e19e..55df44d728a0 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c +++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c @@ -575,6 +575,8 @@ static int mana_get_link_ksettings(struct net_device *ndev, const struct ethtool_ops mana_ethtool_ops = { .supported_coalesce_params = ETHTOOL_COALESCE_RX_CQE_FRAMES, + .op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_SCHANNELS | + ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM, .get_ethtool_stats = mana_get_ethtool_stats, .get_sset_count = mana_get_sset_count, .get_strings = mana_get_strings, diff --git a/drivers/net/netdevsim/ethtool.c b/drivers/net/netdevsim/ethtool.c index 36a201533aae..9350ba48eb81 100644 --- a/drivers/net/netdevsim/ethtool.c +++ b/drivers/net/netdevsim/ethtool.c @@ -209,6 +209,7 @@ static const struct ethtool_ops nsim_ethtool_ops = { .supported_coalesce_params = ETHTOOL_COALESCE_ALL_PARAMS, .supported_ring_params = ETHTOOL_RING_USE_TCP_DATA_SPLIT | ETHTOOL_RING_USE_HDS_THRS, + .op_needs_rtnl = ETHTOOL_OP_NEEDS_RTNL_SCHANNELS, .get_pause_stats = nsim_get_pause_stats, .get_pauseparam = nsim_get_pauseparam, .set_pauseparam = nsim_set_pauseparam, diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h index 1da49161d36f..74c8109b0cf3 100644 --- a/include/linux/ethtool.h +++ b/include/linux/ethtool.h @@ -935,7 +935,13 @@ struct kernel_ethtool_ts_info { * these bits separate, per GET and SET. GET is much easier to "unlock". */ #define ETHTOOL_OP_NEEDS_RTNL_LINKSETTINGS BIT(0) -#define ETHTOOL_OP_NEEDS_RTNL_GPAUSEPARAM BIT(1) +#define ETHTOOL_OP_NEEDS_RTNL_SPFLAGS BIT(1) +#define ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM BIT(2) +#define ETHTOOL_OP_NEEDS_RTNL_SCHANNELS BIT(3) +#define ETHTOOL_OP_NEEDS_RTNL_SCOALESCE BIT(4) +#define ETHTOOL_OP_NEEDS_RTNL_GPAUSEPARAM BIT(5) +#define ETHTOOL_OP_NEEDS_RTNL_SPAUSEPARAM BIT(6) +#define ETHTOOL_OP_NEEDS_RTNL_RSS BIT(7) /** * struct ethtool_ops - optional netdev operations @@ -970,6 +976,8 @@ struct kernel_ethtool_ts_info { * The following commonly used core APIs currently require rtnl_lock * (this list may not be exhaustive): * - phylink helpers (note that phydev is currently unsupported!) + * - netdev_update_features() + * - netif_set_real_num_tx_queues() * * @get_drvinfo: Report driver/device information. Modern drivers no * longer have to implement this callback. Most fields are diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index a8709d0cc8d4..403b6d1c67f8 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -2584,7 +2584,7 @@ struct net_device { * @up, @moving_ns, @nd_net, @xdp_features * * Ops protects: - * @hwprov + * @cfg, @cfg_pending, @hwprov * * Double ops protects: * @real_num_rx_queues, @real_num_tx_queues diff --git a/net/ethtool/common.h b/net/ethtool/common.h index 391c41ca56be..e3052972f953 100644 --- a/net/ethtool/common.h +++ b/net/ethtool/common.h @@ -95,10 +95,24 @@ ethtool_nl_msg_needs_rtnl(const struct net_device *dev, u8 cmd) switch (cmd) { case ETHTOOL_MSG_LINKINFO_GET: + case ETHTOOL_MSG_LINKINFO_SET: case ETHTOOL_MSG_LINKMODES_GET: + case ETHTOOL_MSG_LINKMODES_SET: return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_LINKSETTINGS; + case ETHTOOL_MSG_PRIVFLAGS_SET: + return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_SPFLAGS; + case ETHTOOL_MSG_RINGS_SET: + return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM; + case ETHTOOL_MSG_CHANNELS_SET: + return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_SCHANNELS; + case ETHTOOL_MSG_COALESCE_SET: + return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_SCOALESCE; case ETHTOOL_MSG_PAUSE_GET: return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_GPAUSEPARAM; + case ETHTOOL_MSG_PAUSE_SET: + return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_SPAUSEPARAM; + case ETHTOOL_MSG_RSS_SET: + return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_RSS; } return false; } @@ -119,9 +133,25 @@ ethtool_ioctl_needs_rtnl(const struct net_device *dev, u32 ethcmd) switch (ethcmd) { case ETHTOOL_GLINKSETTINGS: case ETHTOOL_GSET: + case ETHTOOL_SLINKSETTINGS: + case ETHTOOL_SSET: return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_LINKSETTINGS; + case ETHTOOL_SPFLAGS: + return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_SPFLAGS; + case ETHTOOL_SRINGPARAM: + return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_SRINGPARAM; + case ETHTOOL_SCHANNELS: + return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_SCHANNELS; + case ETHTOOL_SCOALESCE: + return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_SCOALESCE; case ETHTOOL_GPAUSEPARAM: return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_GPAUSEPARAM; + case ETHTOOL_SPAUSEPARAM: + return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_SPAUSEPARAM; + case ETHTOOL_SRSSH: + case ETHTOOL_SRXFH: + case ETHTOOL_SRXFHINDIR: + return ops->op_needs_rtnl & ETHTOOL_OP_NEEDS_RTNL_RSS; } return false; } diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c index 2c30eb1f4666..1af395b54330 100644 --- a/net/ethtool/netlink.c +++ b/net/ethtool/netlink.c @@ -903,6 +903,7 @@ static int ethnl_default_set_doit(struct sk_buff *skb, struct genl_info *info) const u8 cmd = info->genlhdr->cmd; struct ethnl_req_info *req_info; struct net_device *dev; + bool need_rtnl; int ret; ops = ethnl_default_requests[cmd]; @@ -927,8 +928,11 @@ static int ethnl_default_set_doit(struct sk_buff *skb, struct genl_info *info) } dev = req_info->dev; + need_rtnl = !netdev_need_ops_lock(dev) || + ethtool_nl_msg_needs_rtnl(dev, cmd); - rtnl_lock(); + if (need_rtnl) + rtnl_lock(); netdev_lock_ops(dev); dev->cfg_pending = kmemdup(dev->cfg, sizeof(*dev->cfg), GFP_KERNEL_ACCOUNT); @@ -958,7 +962,8 @@ out_free_cfg: out_tie_cfg: dev->cfg_pending = dev->cfg; netdev_unlock_ops(dev); - rtnl_unlock(); + if (need_rtnl) + rtnl_unlock(); out_dev: ethnl_parse_header_dev_put(req_info); out_free_req: -- cgit v1.2.3 From 2526717624ab477fb7df2bf1af6e1f257c72ee0f Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Thu, 4 Jun 2026 17:29:08 -0700 Subject: net: ethtool: optionally skip rtnl_lock in ethnl_act_module_fw_flash() Module firmware flashing reads SFF-8024 identifier bytes via .get_module_eeprom_by_page(). Other than that it modifies a bit in the netdev->ethtool struct. Both should be ops-locked at this point. Reviewed-by: Eric Dumazet Acked-by: Stanislav Fomichev Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260605002912.3456868-9-kuba@kernel.org Signed-off-by: Jakub Kicinski --- include/linux/netdevice.h | 2 +- net/ethtool/module.c | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) (limited to 'include') diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 403b6d1c67f8..9b876cd930d7 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -2584,7 +2584,7 @@ struct net_device { * @up, @moving_ns, @nd_net, @xdp_features * * Ops protects: - * @cfg, @cfg_pending, @hwprov + * @cfg, @cfg_pending, @ethtool, @hwprov * * Double ops protects: * @real_num_rx_queues, @real_num_tx_queues diff --git a/net/ethtool/module.c b/net/ethtool/module.c index c3388e6d7ec8..9cf670e089f2 100644 --- a/net/ethtool/module.c +++ b/net/ethtool/module.c @@ -429,8 +429,7 @@ int ethnl_act_module_fw_flash(struct sk_buff *skb, struct genl_info *info) return ret; dev = req_info.dev; - rtnl_lock(); - netdev_lock_ops(dev); + netdev_lock_ops_compat(dev); ret = ethnl_ops_begin(dev); if (ret < 0) goto out_unlock; @@ -445,8 +444,7 @@ out_complete: ethnl_ops_complete(dev); out_unlock: - netdev_unlock_ops(dev); - rtnl_unlock(); + netdev_unlock_ops_compat(dev); ethnl_parse_header_dev_put(&req_info); return ret; } -- cgit v1.2.3 From f994752b11273ded7ec6b49ae5c1d319eef21743 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Thu, 4 Jun 2026 17:29:11 -0700 Subject: net: ethtool: optionally skip rtnl_lock on IOCTL path Convert the IOCTL path similarly to how we converted Netlink. The device lookup gets a little hairy. We could take rtnl_lock unconditionally and drop it before calling the driver (this would avoid the reference + liveness check). But I think being able to make progress even if rtnl is dead-locked is quite useful. First extra concern is handling features. List all the cmds which modify features and always take rtnl_lock. We could fold this list into ethtool_ioctl_needs_rtnl() but seems cleaner to keep ethtool_ioctl_needs_rtnl() driver-related. If a driver changed features and we were not holding rtnl_lock - warn about it. It can only happen on buggy ops locked drivers (buggy because they should have set appropriate "I need rtnl for op X" bit). Second wrinkle is the PHY ID hack which drops the locks while sleeping. Convert its static "busy" variable which used to be protected by rtnl_lock to a field in struct ethtool_netdev_state. This feature is about identifying an adapter or a port within a system, so being able to blink multiple LEDs at the same time is likely not very useful in practice. But it's the simplest fix, we can add a mutex if someone thinks a system should only be ID'ing one port at a time. Reviewed-by: Eric Dumazet Acked-by: Stanislav Fomichev Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260605002912.3456868-12-kuba@kernel.org Signed-off-by: Jakub Kicinski --- include/linux/ethtool.h | 2 + net/ethtool/ioctl.c | 106 ++++++++++++++++++++++++++++++++++++------------ 2 files changed, 82 insertions(+), 26 deletions(-) (limited to 'include') diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h index 74c8109b0cf3..da29017c757f 100644 --- a/include/linux/ethtool.h +++ b/include/linux/ethtool.h @@ -1375,6 +1375,7 @@ int ethtool_virtdev_set_link_ksettings(struct net_device *dev, * within RTNL. * @rss_indir_user_size: Number of user provided entries for the default * (context 0) indirection table. + * @phys_id_busy: Loop blinking the device LED is running. * @wol_enabled: Wake-on-LAN is enabled * @module_fw_flash_in_progress: Module firmware flashing is in progress. */ @@ -1382,6 +1383,7 @@ struct ethtool_netdev_state { struct xarray rss_ctx; struct mutex rss_lock; u32 rss_indir_user_size; + unsigned phys_id_busy:1; unsigned wol_enabled:1; unsigned module_fw_flash_in_progress:1; }; diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c index c32c099845e3..a7bff829b758 100644 --- a/net/ethtool/ioctl.c +++ b/net/ethtool/ioctl.c @@ -544,7 +544,7 @@ static int ethtool_get_link_ksettings(struct net_device *dev, int err = 0; struct ethtool_link_ksettings link_ksettings; - ASSERT_RTNL(); + netdev_assert_locked_ops_compat(dev); if (!dev->ethtool_ops->get_link_ksettings) return -EOPNOTSUPP; @@ -601,7 +601,7 @@ static int ethtool_set_link_ksettings(struct net_device *dev, struct ethtool_link_ksettings link_ksettings = {}; int err; - ASSERT_RTNL(); + netdev_assert_locked_ops_compat(dev); if (!dev->ethtool_ops->set_link_ksettings) return -EOPNOTSUPP; @@ -675,7 +675,7 @@ static int ethtool_get_settings(struct net_device *dev, void __user *useraddr) struct ethtool_cmd cmd; int err; - ASSERT_RTNL(); + netdev_assert_locked_ops_compat(dev); if (!dev->ethtool_ops->get_link_ksettings) return -EOPNOTSUPP; @@ -711,7 +711,7 @@ static int ethtool_set_settings(struct net_device *dev, void __user *useraddr) struct ethtool_cmd cmd; int ret; - ASSERT_RTNL(); + netdev_assert_locked_ops_compat(dev); if (copy_from_user(&cmd, useraddr, sizeof(cmd))) return -EFAULT; @@ -2452,10 +2452,10 @@ void ethtool_puts(u8 **data, const char *str) } EXPORT_SYMBOL(ethtool_puts); -static int ethtool_phys_id(struct net_device *dev, void __user *useraddr) +static int ethtool_phys_id(struct net_device *dev, void __user *useraddr, + bool has_rtnl_lock) { struct ethtool_value id; - static bool busy; const struct ethtool_ops *ops = dev->ethtool_ops; netdevice_tracker dev_tracker; int rc; @@ -2463,7 +2463,7 @@ static int ethtool_phys_id(struct net_device *dev, void __user *useraddr) if (!ops->set_phys_id) return -EOPNOTSUPP; - if (busy) + if (dev->ethtool->phys_id_busy) return -EBUSY; if (copy_from_user(&id, useraddr, sizeof(id))) @@ -2473,13 +2473,14 @@ static int ethtool_phys_id(struct net_device *dev, void __user *useraddr) if (rc < 0) return rc; - /* Drop the RTNL lock while waiting, but prevent reentry or + /* Drop the locks while waiting, but prevent reentry or * removal of the device. */ - busy = true; + dev->ethtool->phys_id_busy = true; netdev_hold(dev, &dev_tracker, GFP_KERNEL); netdev_unlock_ops(dev); - rtnl_unlock(); + if (has_rtnl_lock) + rtnl_unlock(); if (rc == 0) { /* Driver will handle this itself */ @@ -2492,22 +2493,25 @@ static int ethtool_phys_id(struct net_device *dev, void __user *useraddr) u64 i = 0; do { - rtnl_lock(); + if (has_rtnl_lock) + rtnl_lock(); netdev_lock_ops(dev); rc = ops->set_phys_id(dev, (i++ & 1) ? ETHTOOL_ID_OFF : ETHTOOL_ID_ON); netdev_unlock_ops(dev); - rtnl_unlock(); + if (has_rtnl_lock) + rtnl_unlock(); if (rc) break; schedule_timeout_interruptible(interval); } while (!signal_pending(current) && (!id.data || i < count)); } - rtnl_lock(); + if (has_rtnl_lock) + rtnl_lock(); netdev_lock_ops(dev); netdev_put(dev, &dev_tracker); - busy = false; + dev->ethtool->phys_id_busy = false; (void) ops->set_phys_id(dev, ETHTOOL_ID_INACTIVE); return rc; @@ -3260,7 +3264,8 @@ static int ethtool_set_fecparam(struct net_device *dev, void __user *useraddr) static int dev_ethtool_locked(struct net *net, struct net_device *dev, void __user *useraddr, - u32 ethcmd, struct ethtool_devlink_compat *devlink_state) + u32 ethcmd, struct ethtool_devlink_compat *devlink_state, + bool has_rtnl_lock) { u32 sub_cmd; int rc; @@ -3316,6 +3321,8 @@ dev_ethtool_locked(struct net *net, struct net_device *dev, return -EPERM; } + netdev_assert_locked_ops_compat(dev); + if (dev->dev.parent) pm_runtime_get_sync(dev->dev.parent); @@ -3403,7 +3410,7 @@ dev_ethtool_locked(struct net *net, struct net_device *dev, rc = ethtool_get_strings(dev, useraddr); break; case ETHTOOL_PHYS_ID: - rc = ethtool_phys_id(dev, useraddr); + rc = ethtool_phys_id(dev, useraddr, has_rtnl_lock); break; case ETHTOOL_GSTATS: rc = ethtool_get_stats(dev, useraddr); @@ -3550,8 +3557,20 @@ dev_ethtool_locked(struct net *net, struct net_device *dev, if (dev->ethtool_ops->complete) dev->ethtool_ops->complete(dev); - if (old_features != dev->features) - netdev_features_change(dev); + switch (ethcmd) { + case ETHTOOL_PHYS_ID: + /* Don't check features if operation drops the locks. + * Someone else may have changed features in parallel. + */ + break; + default: + if (old_features != dev->features) { + if (has_rtnl_lock) + netdev_features_change(dev); + else + netdev_WARN(dev, "ethtool cmd %u changed features without rtnl_lock", ethcmd); + } + } out: if (dev->dev.parent) pm_runtime_put(dev->dev.parent); @@ -3559,25 +3578,60 @@ out: return rc; } +/* Commands that may toggle dev->features in net/ethtool/ioctl.c and so + * call into __netdev_update_features(), which still requires rtnl_lock. + * Driver-decided SET commands that may chain into rtnl-only helpers are + * covered by ethtool_ioctl_needs_rtnl()/ETHTOOL_OP_NEEDS_RTNL_*. + */ +static bool ethtool_cmd_changes_features(u32 ethcmd) +{ + switch (ethcmd) { + case ETHTOOL_SFEATURES: + case ETHTOOL_SFLAGS: + case ETHTOOL_STXCSUM: + case ETHTOOL_SRXCSUM: + case ETHTOOL_SSG: + case ETHTOOL_STSO: + case ETHTOOL_SGSO: + case ETHTOOL_SGRO: + return true; + } + return false; +} + static int __dev_ethtool(struct net *net, struct ifreq *ifr, void __user *useraddr, u32 ethcmd, struct ethtool_devlink_compat *devlink_state) { + netdevice_tracker dev_tracker; struct net_device *dev; + bool need_rtnl; int rc; - rtnl_lock(); - dev = __dev_get_by_name(net, ifr->ifr_name); - if (!dev) { + dev = netdev_get_by_name(net, ifr->ifr_name, &dev_tracker, GFP_KERNEL); + if (!dev) + return -ENODEV; + + need_rtnl = !netdev_need_ops_lock(dev) || + ethtool_cmd_changes_features(ethcmd) || + ethtool_ioctl_needs_rtnl(dev, ethcmd); + if (need_rtnl) + rtnl_lock(); + netdev_lock_ops(dev); + if (dev->reg_state > NETREG_REGISTERED || + dev->moving_ns || !net_eq(dev_net(dev), net)) { rc = -ENODEV; - goto exit_rtnl_unlock; + goto exit_ops_unlock; } - netdev_lock_ops(dev); - rc = dev_ethtool_locked(net, dev, useraddr, ethcmd, devlink_state); + rc = dev_ethtool_locked(net, dev, useraddr, ethcmd, devlink_state, + need_rtnl); + +exit_ops_unlock: netdev_unlock_ops(dev); -exit_rtnl_unlock: - rtnl_unlock(); + if (need_rtnl) + rtnl_unlock(); + netdev_put(dev, &dev_tracker); return rc; } -- cgit v1.2.3 From b5125db8d0205048a3b9dafec7f8e2b0a702101f Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Thu, 4 Jun 2026 17:29:12 -0700 Subject: docs: net: ethtool: document ops-locked drivers and op_needs_rtnl Catch up various bits of documentation after the locking changes. Reviewed-by: Nicolai Buchwitz Reviewed-by: Eric Dumazet Acked-by: Stanislav Fomichev Reviewed-by: Jacob Keller Link: https://patch.msgid.link/20260605002912.3456868-13-kuba@kernel.org Signed-off-by: Jakub Kicinski --- Documentation/networking/netdev-features.rst | 7 +++++++ Documentation/networking/netdevices.rst | 17 ++++++++++------- include/linux/ethtool.h | 10 ++++++++-- 3 files changed, 25 insertions(+), 9 deletions(-) (limited to 'include') diff --git a/Documentation/networking/netdev-features.rst b/Documentation/networking/netdev-features.rst index 6293d47e5b09..f9e1f3921f53 100644 --- a/Documentation/networking/netdev-features.rst +++ b/Documentation/networking/netdev-features.rst @@ -76,6 +76,13 @@ netdev instance lock, that lock must be held as well. This should not be done from ndo_*_features callbacks. netdev->features should not be modified by driver except by means of ndo_fix_features callback. +For "ops locked" drivers (see Documentation/networking/netdevices.rst), +ethtool callbacks that may end up invoking netdev_update_features() must +opt back into rtnl_lock by setting the matching ETHTOOL_OP_NEEDS_RTNL_* +bit in ``ethtool_ops::op_needs_rtnl``. The ethtool core then keeps +rtnl_lock held across those SET callbacks so the contract above still +holds. + ndo_features_check is called for each skb before that skb is passed to ndo_start_xmit. Driver may perform any non-trivial checks (e.g. exact header geometry / length) and withdraw features like HW_CSUM or TSO, diff --git a/Documentation/networking/netdevices.rst b/Documentation/networking/netdevices.rst index 8fc96975b3bd..fde601acd1d2 100644 --- a/Documentation/networking/netdevices.rst +++ b/Documentation/networking/netdevices.rst @@ -351,10 +351,6 @@ virtual and the physical device. To prevent deadlocks, the virtual device's lock must always be acquired before the physical device's (see ``netdev_nl_queue_create_doit``). -In the future, there will be an option for individual -drivers to opt out of using ``rtnl_lock`` and instead perform their control -operations directly under the netdev instance lock. - Device drivers are encouraged to rely on the instance lock where possible. For the (mostly software) drivers that need to interact with the core stack, @@ -375,9 +371,16 @@ the instance lock. struct ethtool_ops ------------------ -Similarly to ``ndos`` the instance lock is only held for select drivers. -For "ops locked" drivers all ethtool ops without exceptions should -be called under the instance lock. +For non-"ops locked" drivers ethtool_ops are executed under ``rtnl_lock``. + +For "ops locked" drivers, ``ethtool_ops``, unlike ``ndos``, run under +the instance lock **only**. Drivers may request that ``rtnl_lock`` +is held around specific operations (both SET and GET) by setting +appropriate bits in ``ethtool_ops::op_needs_rtnl`` (if the necessary +``ETHTOOL_OP_NEEDS_RTNL_*`` bit doesn't exist, just add it). +Commonly used core helpers which force drivers to selectively opt-in to +``rtnl_lock`` protection include ``netdev_update_features()``, +``netif_set_real_num_tx_queues()``, and phylink helpers. struct netdev_stat_ops ---------------------- diff --git a/include/linux/ethtool.h b/include/linux/ethtool.h index da29017c757f..1b834e2a522e 100644 --- a/include/linux/ethtool.h +++ b/include/linux/ethtool.h @@ -1177,8 +1177,14 @@ struct kernel_ethtool_ts_info { * @get_mm_stats: Query the 802.3 MAC Merge layer statistics. * * All operations are optional (i.e. the function pointer may be set - * to %NULL) and callers must take this into account. Callers must - * hold the RTNL lock or netdev instance lock (see @op_needs_rtnl). + * to %NULL) and callers must take this into account. + * + * For traditional drivers callers hold ``rtnl_lock`` across the call. + * For "ops locked" drivers (see Documentation/networking/netdevices.rst) + * callers instead hold the netdev instance lock (``netdev_lock_ops``); + * ``rtnl_lock`` is additionally held only for callbacks for which + * the driver opts in via the matching ``ETHTOOL_OP_NEEDS_RTNL_*`` bit + * in @op_needs_rtnl. * * See the structures used by these operations for further documentation. * Note that for all operations using a structure ending with a zero- -- cgit v1.2.3 From 5c9a6a124afdb933b3f2c9cd685f729920da0a37 Mon Sep 17 00:00:00 2001 From: Minda Chen Date: Fri, 5 Jun 2026 11:36:07 +0800 Subject: net: ncsi: Set ncsi_stop_dev() to inline while NET_NCSI not enabled While NET_NCSI not enabled, ncsi_stop_dev() is not inline and call with it, casue compile waring: linux/include/net/ncsi.h:63:13: warning: 'ncsi_stop_dev' defined but not used [-Wunused-function] static void ncsi_stop_dev(struct ncsi_dev *nd) Setting ncsi_stop_dev() to inline like other function to remove compile warnings. Signed-off-by: Minda Chen Link: https://patch.msgid.link/20260605033607.37630-1-minda.chen@starfivetech.com Signed-off-by: Jakub Kicinski --- include/net/ncsi.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include') diff --git a/include/net/ncsi.h b/include/net/ncsi.h index 08a50d9acb0a..e886358207aa 100644 --- a/include/net/ncsi.h +++ b/include/net/ncsi.h @@ -60,7 +60,7 @@ static inline int ncsi_start_dev(struct ncsi_dev *nd) return -ENOTTY; } -static void ncsi_stop_dev(struct ncsi_dev *nd) +static inline void ncsi_stop_dev(struct ncsi_dev *nd) { } -- cgit v1.2.3 From fa1a3b7bcd161028e038025c1a4a8963b2f56a95 Mon Sep 17 00:00:00 2001 From: Long Li Date: Thu, 4 Jun 2026 17:57:10 -0700 Subject: net: mana: Create separate EQs for each vPort To prepare for assigning vPorts to dedicated MSI-X vectors, remove EQ sharing among the vPorts and create dedicated EQs for each vPort. Move the EQ definition from struct mana_context to struct mana_port_context and update related support functions. Export mana_create_eq() and mana_destroy_eq() for use by the MANA RDMA driver. RSS QPs now take a vport reference via pd->vport_use_count to ensure EQs outlive all QP consumers. The vport must already be configured by a raw QP before an RSS QP can be created. EQs are only destroyed when the last QP (raw or RSS) on the PD releases its reference. Restrict each vport to a single RSS QP. The hardware only supports one steering configuration (indirection table / hash key) per vport, and mana_disable_vport_rx() on QP destroy disables RX globally for the vport. Previously, creating a second RSS QP would silently overwrite the first QP's steering config and destroy would blackhole all traffic. This is now explicitly rejected with -EBUSY. Existing applications (DPDK being the primary RDMA consumer) always create one RSS QP per vport, so no real-world flows are affected. Reject cross-port PD sharing for both raw and RSS QPs. Since EQs and vport configuration are per-port, a PD is bound to the port used by its first raw QP. Subsequent QPs on the same PD must use the same port or the creation fails with -EINVAL. Previously this was silently broken: with shared EQs it appeared to work, but with per-vPort EQs a cross-port PD would cause wrong-port EQ teardown and corruption. DPDK creates one PD per port so no existing flows are affected. Serialize mana_set_channels() and the async per-port queue reset handler against RDMA vport configuration to prevent RDMA from claiming the vport during the detach/attach window. A channel_changing flag is set under apc->vport_mutex before detach and checked by mana_cfg_vport() when called from the RDMA path, blocking RDMA from grabbing the vport during the entire window. When the port is down and RDMA already holds the vport, the channel change is rejected with -EBUSY. Signed-off-by: Long Li Link: https://patch.msgid.link/20260605005717.2059954-2-longli@microsoft.com Signed-off-by: Jakub Kicinski --- drivers/infiniband/hw/mana/main.c | 40 ++++-- drivers/infiniband/hw/mana/mana_ib.h | 14 +++ drivers/infiniband/hw/mana/qp.c | 68 +++++++++-- drivers/net/ethernet/microsoft/mana/mana_en.c | 135 +++++++++++++-------- drivers/net/ethernet/microsoft/mana/mana_ethtool.c | 23 +++- include/net/mana/mana.h | 15 ++- 6 files changed, 228 insertions(+), 67 deletions(-) (limited to 'include') diff --git a/drivers/infiniband/hw/mana/main.c b/drivers/infiniband/hw/mana/main.c index afc2fc124fee..f42ea20cb75d 100644 --- a/drivers/infiniband/hw/mana/main.c +++ b/drivers/infiniband/hw/mana/main.c @@ -20,8 +20,10 @@ void mana_ib_uncfg_vport(struct mana_ib_dev *dev, struct mana_ib_pd *pd, pd->vport_use_count--; WARN_ON(pd->vport_use_count < 0); - if (!pd->vport_use_count) + if (!pd->vport_use_count) { + mana_destroy_eq(mpc); mana_uncfg_vport(mpc); + } mutex_unlock(&pd->vport_mutex); } @@ -40,13 +42,27 @@ int mana_ib_cfg_vport(struct mana_ib_dev *dev, u32 port, struct mana_ib_pd *pd, pd->vport_use_count++; if (pd->vport_use_count > 1) { + /* Reject cross-port PD sharing. EQs and vport config + * are per-port, so the PD must stay bound to the port + * that was configured on the first raw QP creation. + */ + if (pd->vport_port != port) { + pd->vport_use_count--; + mutex_unlock(&pd->vport_mutex); + ibdev_dbg(&dev->ib_dev, + "PD already bound to port %u\n", + pd->vport_port); + return -EINVAL; + } ibdev_dbg(&dev->ib_dev, "Skip as this PD is already configured vport\n"); mutex_unlock(&pd->vport_mutex); return 0; } - err = mana_cfg_vport(mpc, pd->pdn, doorbell_id); + pd->vport_port = port; + + err = mana_cfg_vport(mpc, pd->pdn, doorbell_id, true); if (err) { pd->vport_use_count--; mutex_unlock(&pd->vport_mutex); @@ -55,15 +71,23 @@ int mana_ib_cfg_vport(struct mana_ib_dev *dev, u32 port, struct mana_ib_pd *pd, return err; } - mutex_unlock(&pd->vport_mutex); - pd->tx_shortform_allowed = mpc->tx_shortform_allowed; - pd->tx_vp_offset = mpc->tx_vp_offset; + err = mana_create_eq(mpc); + if (err) { + mana_uncfg_vport(mpc); + pd->vport_use_count--; + } else { + pd->tx_shortform_allowed = mpc->tx_shortform_allowed; + pd->tx_vp_offset = mpc->tx_vp_offset; + } + + mutex_unlock(&pd->vport_mutex); - ibdev_dbg(&dev->ib_dev, "vport handle %llx pdid %x doorbell_id %x\n", - mpc->port_handle, pd->pdn, doorbell_id); + if (!err) + ibdev_dbg(&dev->ib_dev, "vport handle %llx pdid %x doorbell_id %x\n", + mpc->port_handle, pd->pdn, doorbell_id); - return 0; + return err; } int mana_ib_alloc_pd(struct ib_pd *ibpd, struct ib_udata *udata) diff --git a/drivers/infiniband/hw/mana/mana_ib.h b/drivers/infiniband/hw/mana/mana_ib.h index c9c94e86a72b..da05966aff19 100644 --- a/drivers/infiniband/hw/mana/mana_ib.h +++ b/drivers/infiniband/hw/mana/mana_ib.h @@ -102,6 +102,20 @@ struct mana_ib_pd { struct mutex vport_mutex; int vport_use_count; + /* Port bound to this PD for raw QP usage. Only valid when + * vport_use_count > 0. A PD can only be associated with a + * single physical port because per-port EQs and vport + * configuration are tied to the PD's refcount. + */ + u32 vport_port; + + /* Only one RSS QP is allowed per vport because each RSS QP + * overwrites the vport steering config (indirection table / + * hash key) and mana_disable_vport_rx() on destroy would + * blackhole traffic for any other RSS QP on the same vport. + */ + bool has_rss_qp; + bool tx_shortform_allowed; u32 tx_vp_offset; }; diff --git a/drivers/infiniband/hw/mana/qp.c b/drivers/infiniband/hw/mana/qp.c index 0fbcf449c134..d3ee30b64f53 100644 --- a/drivers/infiniband/hw/mana/qp.c +++ b/drivers/infiniband/hw/mana/qp.c @@ -79,6 +79,7 @@ static int mana_ib_create_qp_rss(struct ib_qp *ibqp, struct ib_pd *pd, struct ib_qp_init_attr *attr, struct ib_udata *udata) { + struct mana_ib_pd *mana_pd = container_of(pd, struct mana_ib_pd, ibpd); struct mana_ib_qp *qp = container_of(ibqp, struct mana_ib_qp, ibqp); struct mana_ib_dev *mdev = container_of(pd->device, struct mana_ib_dev, ib_dev); @@ -155,6 +156,30 @@ static int mana_ib_create_qp_rss(struct ib_qp *ibqp, struct ib_pd *pd, qp->port = port; + /* Take a reference on the vport to ensure EQs outlive this QP. + * The vport must already be configured by a raw QP on the + * same port — cross-port PD sharing is not supported. + * Only one RSS QP per vport is allowed because each one + * overwrites the steering config and destroy disables RX + * globally. + */ + mutex_lock(&mana_pd->vport_mutex); + if (!mana_pd->vport_use_count || mana_pd->vport_port != port) { + mutex_unlock(&mana_pd->vport_mutex); + ret = -EINVAL; + goto fail; + } + if (mana_pd->has_rss_qp) { + mutex_unlock(&mana_pd->vport_mutex); + ibdev_dbg(&mdev->ib_dev, + "Only one RSS QP per vport is supported\n"); + ret = -EBUSY; + goto fail; + } + mana_pd->vport_use_count++; + mana_pd->has_rss_qp = true; + mutex_unlock(&mana_pd->vport_mutex); + for (i = 0; i < ind_tbl_size; i++) { struct mana_obj_spec wq_spec = {}; struct mana_obj_spec cq_spec = {}; @@ -171,13 +196,19 @@ static int mana_ib_create_qp_rss(struct ib_qp *ibqp, struct ib_pd *pd, cq_spec.gdma_region = cq->queue.gdma_region; cq_spec.queue_size = cq->cqe * COMP_ENTRY_SIZE; cq_spec.modr_ctx_id = 0; - eq = &mpc->ac->eqs[cq->comp_vector]; + /* Map comp_vector to a per-vPort EQ. The modulo handles + * the case where the RDMA-advertised num_comp_vectors + * exceeds this port's num_queues (e.g. after ethtool -L + * reduces it), remapping to an available EQ rather than + * failing the QP creation. + */ + eq = &mpc->eqs[cq->comp_vector % mpc->num_queues]; cq_spec.attached_eq = eq->eq->id; ret = mana_create_wq_obj(mpc, mpc->port_handle, GDMA_RQ, &wq_spec, &cq_spec, &wq->rx_object); if (ret) - goto fail; + goto free_vport; /* The GDMA regions are now owned by the WQ object */ wq->queue.gdma_region = GDMA_INVALID_DMA_REGION; @@ -199,7 +230,7 @@ static int mana_ib_create_qp_rss(struct ib_qp *ibqp, struct ib_pd *pd, ret = mana_ib_install_cq_cb(mdev, cq); if (ret) { mana_destroy_wq_obj(mpc, GDMA_RQ, wq->rx_object); - goto fail; + goto free_vport; } } resp.num_entries = i; @@ -210,7 +241,7 @@ static int mana_ib_create_qp_rss(struct ib_qp *ibqp, struct ib_pd *pd, ucmd.rx_hash_key_len, ucmd.rx_hash_key); if (ret) - goto fail; + goto free_vport; ret = ib_copy_to_udata(udata, &resp, sizeof(resp)); if (ret) { @@ -226,7 +257,7 @@ static int mana_ib_create_qp_rss(struct ib_qp *ibqp, struct ib_pd *pd, err_disable_vport_rx: mana_disable_vport_rx(mpc); -fail: +free_vport: while (i-- > 0) { ibwq = ind_tbl->ind_tbl[i]; ibcq = ibwq->cq; @@ -237,6 +268,13 @@ fail: mana_destroy_wq_obj(mpc, GDMA_RQ, wq->rx_object); } + mutex_lock(&mana_pd->vport_mutex); + mana_pd->has_rss_qp = false; + mutex_unlock(&mana_pd->vport_mutex); + + mana_ib_uncfg_vport(mdev, mana_pd, port); + +fail: kfree(mana_ind_table); return ret; @@ -299,7 +337,7 @@ static int mana_ib_create_qp_raw(struct ib_qp *ibqp, struct ib_pd *ibpd, err = mana_ib_cfg_vport(mdev, port, pd, mana_ucontext->doorbell); if (err) - return -ENODEV; + return err; qp->port = port; @@ -321,7 +359,14 @@ static int mana_ib_create_qp_raw(struct ib_qp *ibqp, struct ib_pd *ibpd, cq_spec.queue_size = send_cq->cqe * COMP_ENTRY_SIZE; cq_spec.modr_ctx_id = 0; eq_vec = send_cq->comp_vector; - eq = &mpc->ac->eqs[eq_vec]; + if (!mpc->eqs) { + err = -EINVAL; + goto err_destroy_queue; + } + /* Map comp_vector to a per-vPort EQ. See comment in + * mana_ib_create_qp_rss() for the modulo rationale. + */ + eq = &mpc->eqs[eq_vec % mpc->num_queues]; cq_spec.attached_eq = eq->eq->id; err = mana_create_wq_obj(mpc, mpc->port_handle, GDMA_SQ, &wq_spec, @@ -785,14 +830,17 @@ static int mana_ib_destroy_qp_rss(struct mana_ib_qp *qp, { struct mana_ib_dev *mdev = container_of(qp->ibqp.device, struct mana_ib_dev, ib_dev); + struct ib_pd *ibpd = qp->ibqp.pd; struct mana_port_context *mpc; struct net_device *ndev; + struct mana_ib_pd *pd; struct mana_ib_wq *wq; struct ib_wq *ibwq; int i; ndev = mana_ib_get_netdev(qp->ibqp.device, qp->port); mpc = netdev_priv(ndev); + pd = container_of(ibpd, struct mana_ib_pd, ibpd); /* Disable vPort RX steering before destroying RX WQ objects. * Otherwise firmware still routes traffic to the destroyed queues, @@ -817,6 +865,12 @@ static int mana_ib_destroy_qp_rss(struct mana_ib_qp *qp, mana_destroy_wq_obj(mpc, GDMA_RQ, wq->rx_object); } + mutex_lock(&pd->vport_mutex); + pd->has_rss_qp = false; + mutex_unlock(&pd->vport_mutex); + + mana_ib_uncfg_vport(mdev, pd, qp->port); + return 0; } diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c index db14357d3732..ed60cc15fe78 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_en.c +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c @@ -309,11 +309,18 @@ static void mana_per_port_queue_reset_work_handler(struct work_struct *work) rtnl_lock(); + /* Block RDMA from grabbing the vport during the detach/attach + * window, same as mana_set_channels(). + */ + mutex_lock(&apc->vport_mutex); + apc->channel_changing = true; + mutex_unlock(&apc->vport_mutex); + /* Pre-allocate buffers to prevent failure in mana_attach later */ err = mana_pre_alloc_rxbufs(apc, ndev->mtu, apc->num_queues); if (err) { netdev_err(ndev, "Insufficient memory for reset post tx stall detection\n"); - goto out; + goto clear_flag; } err = mana_detach(ndev, false); @@ -328,7 +335,11 @@ static void mana_per_port_queue_reset_work_handler(struct work_struct *work) dealloc_pre_rxbufs: mana_pre_dealloc_rxbufs(apc); -out: +clear_flag: + mutex_lock(&apc->vport_mutex); + apc->channel_changing = false; + mutex_unlock(&apc->vport_mutex); + rtnl_unlock(); } @@ -1298,7 +1309,7 @@ void mana_uncfg_vport(struct mana_port_context *apc) EXPORT_SYMBOL_NS(mana_uncfg_vport, "NET_MANA"); int mana_cfg_vport(struct mana_port_context *apc, u32 protection_dom_id, - u32 doorbell_pg_id) + u32 doorbell_pg_id, bool check_channel_changing) { struct mana_config_vport_resp resp = {}; struct mana_config_vport_req req = {}; @@ -1323,7 +1334,8 @@ int mana_cfg_vport(struct mana_port_context *apc, u32 protection_dom_id, * Ethernet usage on the same port. */ mutex_lock(&apc->vport_mutex); - if (apc->vport_use_count > 0) { + if (apc->vport_use_count > 0 || + (check_channel_changing && apc->channel_changing)) { mutex_unlock(&apc->vport_mutex); return -EBUSY; } @@ -1623,78 +1635,84 @@ void mana_destroy_wq_obj(struct mana_port_context *apc, u32 wq_type, } EXPORT_SYMBOL_NS(mana_destroy_wq_obj, "NET_MANA"); -static void mana_destroy_eq(struct mana_context *ac) +void mana_destroy_eq(struct mana_port_context *apc) { + struct mana_context *ac = apc->ac; struct gdma_context *gc = ac->gdma_dev->gdma_context; struct gdma_queue *eq; int i; - if (!ac->eqs) + if (!apc->eqs) return; - debugfs_remove_recursive(ac->mana_eqs_debugfs); - ac->mana_eqs_debugfs = NULL; + debugfs_remove_recursive(apc->mana_eqs_debugfs); + apc->mana_eqs_debugfs = NULL; - for (i = 0; i < gc->max_num_queues; i++) { - eq = ac->eqs[i].eq; + for (i = 0; i < apc->num_queues; i++) { + eq = apc->eqs[i].eq; if (!eq) continue; mana_gd_destroy_queue(gc, eq); } - kfree(ac->eqs); - ac->eqs = NULL; + kfree(apc->eqs); + apc->eqs = NULL; } +EXPORT_SYMBOL_NS(mana_destroy_eq, "NET_MANA"); -static void mana_create_eq_debugfs(struct mana_context *ac, int i) +static void mana_create_eq_debugfs(struct mana_port_context *apc, int i) { - struct mana_eq eq = ac->eqs[i]; + struct mana_eq eq = apc->eqs[i]; char eqnum[32]; sprintf(eqnum, "eq%d", i); - eq.mana_eq_debugfs = debugfs_create_dir(eqnum, ac->mana_eqs_debugfs); + eq.mana_eq_debugfs = debugfs_create_dir(eqnum, apc->mana_eqs_debugfs); debugfs_create_u32("head", 0400, eq.mana_eq_debugfs, &eq.eq->head); debugfs_create_u32("tail", 0400, eq.mana_eq_debugfs, &eq.eq->tail); debugfs_create_file("eq_dump", 0400, eq.mana_eq_debugfs, eq.eq, &mana_dbg_q_fops); } -static int mana_create_eq(struct mana_context *ac) +int mana_create_eq(struct mana_port_context *apc) { - struct gdma_dev *gd = ac->gdma_dev; + struct gdma_dev *gd = apc->ac->gdma_dev; struct gdma_context *gc = gd->gdma_context; struct gdma_queue_spec spec = {}; int err; int i; - ac->eqs = kzalloc_objs(struct mana_eq, gc->max_num_queues); - if (!ac->eqs) + if (WARN_ON(apc->eqs)) + return -EEXIST; + apc->eqs = kzalloc_objs(struct mana_eq, apc->num_queues); + if (!apc->eqs) return -ENOMEM; spec.type = GDMA_EQ; spec.monitor_avl_buf = false; spec.queue_size = EQ_SIZE; spec.eq.callback = NULL; - spec.eq.context = ac->eqs; + spec.eq.context = apc->eqs; spec.eq.log2_throttle_limit = LOG2_EQ_THROTTLE; - ac->mana_eqs_debugfs = debugfs_create_dir("EQs", gc->mana_pci_debugfs); + apc->mana_eqs_debugfs = + debugfs_create_dir("EQs", apc->mana_port_debugfs); - for (i = 0; i < gc->max_num_queues; i++) { + for (i = 0; i < apc->num_queues; i++) { spec.eq.msix_index = (i + 1) % gc->num_msix_usable; - err = mana_gd_create_mana_eq(gd, &spec, &ac->eqs[i].eq); + err = mana_gd_create_mana_eq(gd, &spec, &apc->eqs[i].eq); if (err) { dev_err(gc->dev, "Failed to create EQ %d : %d\n", i, err); goto out; } - mana_create_eq_debugfs(ac, i); + mana_create_eq_debugfs(apc, i); } return 0; out: - mana_destroy_eq(ac); + mana_destroy_eq(apc); return err; } +EXPORT_SYMBOL_NS(mana_create_eq, "NET_MANA"); static int mana_fence_rq(struct mana_port_context *apc, struct mana_rxq *rxq) { @@ -2462,7 +2480,7 @@ static int mana_create_txq(struct mana_port_context *apc, spec.monitor_avl_buf = false; spec.queue_size = cq_size; spec.cq.callback = mana_schedule_napi; - spec.cq.parent_eq = ac->eqs[i].eq; + spec.cq.parent_eq = apc->eqs[i].eq; spec.cq.context = cq; err = mana_gd_create_mana_wq_cq(gd, &spec, &cq->gdma_cq); if (err) @@ -2855,13 +2873,12 @@ static void mana_create_rxq_debugfs(struct mana_port_context *apc, int idx) static int mana_add_rx_queues(struct mana_port_context *apc, struct net_device *ndev) { - struct mana_context *ac = apc->ac; struct mana_rxq *rxq; int err = 0; int i; for (i = 0; i < apc->num_queues; i++) { - rxq = mana_create_rxq(apc, i, &ac->eqs[i], ndev); + rxq = mana_create_rxq(apc, i, &apc->eqs[i], ndev); if (!rxq) { err = -ENOMEM; netdev_err(ndev, "Failed to create rxq %d : %d\n", i, err); @@ -2880,9 +2897,8 @@ out: return err; } -static void mana_destroy_vport(struct mana_port_context *apc) +static void mana_destroy_rxqs(struct mana_port_context *apc) { - struct gdma_dev *gd = apc->ac->gdma_dev; struct mana_rxq *rxq; u32 rxq_idx; @@ -2897,8 +2913,12 @@ static void mana_destroy_vport(struct mana_port_context *apc) apc->rxqs[rxq_idx] = NULL; } } +} + +static void mana_destroy_vport(struct mana_port_context *apc) +{ + struct gdma_dev *gd = apc->ac->gdma_dev; - mana_destroy_txq(apc); mana_uncfg_vport(apc); if (gd->gdma_context->is_pf && !apc->ac->bm_hostmode) @@ -2919,11 +2939,14 @@ static int mana_create_vport(struct mana_port_context *apc, return err; } - err = mana_cfg_vport(apc, gd->pdid, gd->doorbell); - if (err) + err = mana_cfg_vport(apc, gd->pdid, gd->doorbell, false); + if (err) { + if (gd->gdma_context->is_pf && !apc->ac->bm_hostmode) + mana_pf_deregister_hw_vport(apc); return err; + } - return mana_create_txq(apc, net); + return 0; } static int mana_rss_table_alloc(struct mana_port_context *apc) @@ -3226,21 +3249,36 @@ int mana_alloc_queues(struct net_device *ndev) err = mana_create_vport(apc, ndev); if (err) { - netdev_err(ndev, "Failed to create vPort %u : %d\n", apc->port_idx, err); + netdev_err(ndev, "Failed to create vPort %u : %d\n", + apc->port_idx, err); return err; } + err = mana_create_eq(apc); + if (err) { + netdev_err(ndev, "Failed to create EQ on vPort %u: %d\n", + apc->port_idx, err); + goto destroy_vport; + } + + err = mana_create_txq(apc, ndev); + if (err) { + netdev_err(ndev, "Failed to create TXQ on vPort %u: %d\n", + apc->port_idx, err); + goto destroy_eq; + } + err = netif_set_real_num_tx_queues(ndev, apc->num_queues); if (err) { netdev_err(ndev, "netif_set_real_num_tx_queues () failed for ndev with num_queues %u : %d\n", apc->num_queues, err); - goto destroy_vport; + goto destroy_txq; } err = mana_add_rx_queues(apc, ndev); if (err) - goto destroy_vport; + goto destroy_rxq; apc->rss_state = apc->num_queues > 1 ? TRI_STATE_TRUE : TRI_STATE_FALSE; @@ -3249,7 +3287,7 @@ int mana_alloc_queues(struct net_device *ndev) netdev_err(ndev, "netif_set_real_num_rx_queues () failed for ndev with num_queues %u : %d\n", apc->num_queues, err); - goto destroy_vport; + goto destroy_rxq; } mana_rss_table_init(apc); @@ -3257,19 +3295,25 @@ int mana_alloc_queues(struct net_device *ndev) err = mana_config_rss(apc, TRI_STATE_TRUE, true, true); if (err) { netdev_err(ndev, "Failed to configure RSS table: %d\n", err); - goto destroy_vport; + goto destroy_rxq; } if (gd->gdma_context->is_pf && !apc->ac->bm_hostmode) { err = mana_pf_register_filter(apc); if (err) - goto destroy_vport; + goto destroy_rxq; } mana_chn_setxdp(apc, mana_xdp_get(apc)); return 0; +destroy_rxq: + mana_destroy_rxqs(apc); +destroy_txq: + mana_destroy_txq(apc); +destroy_eq: + mana_destroy_eq(apc); destroy_vport: mana_destroy_vport(apc); return err; @@ -3380,6 +3424,9 @@ static int mana_dealloc_queues(struct net_device *ndev) mana_fence_rqs(apc); /* Even in err case, still need to cleanup the vPort */ + mana_destroy_rxqs(apc); + mana_destroy_txq(apc); + mana_destroy_eq(apc); mana_destroy_vport(apc); return 0; @@ -3706,12 +3753,6 @@ int mana_probe(struct gdma_dev *gd, bool resuming) INIT_DELAYED_WORK(&ac->gf_stats_work, mana_gf_stats_work_handler); - err = mana_create_eq(ac); - if (err) { - dev_err(dev, "Failed to create EQs: %d\n", err); - goto out; - } - err = mana_query_device_cfg(ac, MANA_MAJOR_VERSION, MANA_MINOR_VERSION, MANA_MICRO_VERSION, &num_ports, &bm_hostmode); if (err) @@ -3856,8 +3897,6 @@ void mana_remove(struct gdma_dev *gd, bool suspending) free_netdev(ndev); } - mana_destroy_eq(ac); - if (ac->per_port_queue_reset_wq) { destroy_workqueue(ac->per_port_queue_reset_wq); ac->per_port_queue_reset_wq = NULL; diff --git a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c index 55df44d728a0..94e658d07a27 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_ethtool.c +++ b/drivers/net/ethernet/microsoft/mana/mana_ethtool.c @@ -454,6 +454,11 @@ static int mana_set_coalesce(struct net_device *ndev, return err; } +/* mana_set_channels - change the number of queues on a port + * + * Returns -EBUSY if RDMA holds the vport with EQs sized to the + * current num_queues. + */ static int mana_set_channels(struct net_device *ndev, struct ethtool_channels *channels) { @@ -462,10 +467,22 @@ static int mana_set_channels(struct net_device *ndev, unsigned int old_count = apc->num_queues; int err; + /* Set channel_changing to block RDMA from grabbing the vport + * during the detach/attach window. mana_cfg_vport() checks + * this flag under vport_mutex and returns -EBUSY if set. + */ + mutex_lock(&apc->vport_mutex); + if (!apc->port_is_up && apc->vport_use_count) { + mutex_unlock(&apc->vport_mutex); + return -EBUSY; + } + apc->channel_changing = true; + mutex_unlock(&apc->vport_mutex); + err = mana_pre_alloc_rxbufs(apc, ndev->mtu, new_count); if (err) { netdev_err(ndev, "Insufficient memory for new allocations"); - return err; + goto clear_flag; } err = mana_detach(ndev, false); @@ -483,6 +500,10 @@ static int mana_set_channels(struct net_device *ndev, out: mana_pre_dealloc_rxbufs(apc); +clear_flag: + mutex_lock(&apc->vport_mutex); + apc->channel_changing = false; + mutex_unlock(&apc->vport_mutex); return err; } diff --git a/include/net/mana/mana.h b/include/net/mana/mana.h index d9c27310fd04..5a9b94e0ef34 100644 --- a/include/net/mana/mana.h +++ b/include/net/mana/mana.h @@ -480,8 +480,6 @@ struct mana_context { u8 bm_hostmode; struct mana_ethtool_hc_stats hc_stats; - struct mana_eq *eqs; - struct dentry *mana_eqs_debugfs; struct workqueue_struct *per_port_queue_reset_wq; /* Workqueue for querying hardware stats */ struct delayed_work gf_stats_work; @@ -501,6 +499,9 @@ struct mana_port_context { u8 mac_addr[ETH_ALEN]; + struct mana_eq *eqs; + struct dentry *mana_eqs_debugfs; + enum TRI_STATE rss_state; mana_handle_t default_rxobj; @@ -547,6 +548,12 @@ struct mana_port_context { struct mutex vport_mutex; int vport_use_count; + /* Set by mana_set_channels() under vport_mutex to block RDMA + * from grabbing the vport during the detach/attach window. + * Checked by mana_cfg_vport() when called from the RDMA path. + */ + bool channel_changing; + /* Net shaper handle*/ struct net_shaper_handle handle; @@ -1040,8 +1047,10 @@ void mana_destroy_wq_obj(struct mana_port_context *apc, u32 wq_type, mana_handle_t wq_obj); int mana_cfg_vport(struct mana_port_context *apc, u32 protection_dom_id, - u32 doorbell_pg_id); + u32 doorbell_pg_id, bool check_channel_changing); void mana_uncfg_vport(struct mana_port_context *apc); +int mana_create_eq(struct mana_port_context *apc); +void mana_destroy_eq(struct mana_port_context *apc); struct net_device *mana_get_primary_netdev(struct mana_context *ac, u32 port_index, -- cgit v1.2.3 From d7c253d614887f5f006fe868f30b65fb72d3c193 Mon Sep 17 00:00:00 2001 From: Long Li Date: Thu, 4 Jun 2026 17:57:11 -0700 Subject: net: mana: Query device capabilities and configure MSI-X sharing for EQs When querying the device, adjust the max number of queues to allow dedicated MSI-X vectors for each vPort. The per-vPort queue count is clamped towards MANA_DEF_NUM_QUEUES but will not exceed the hardware maximum reported by the device. MSI-X sharing among vPorts is enabled when there are not enough MSI-X vectors for dedicated allocation, or when the platform does not support dynamic MSI-X allocation (in which case all vectors are pre-allocated at probe time and sharing is always used). The msi_sharing flag is reset at the top of mana_gd_query_max_resources() so it is recomputed from current hardware state on each probe or resume cycle. Clamp apc->max_queues to gc->max_num_queues_vport in mana_init_port() so that on resume, if max_num_queues_vport has decreased due to fewer MSI-X vectors, num_queues is reduced accordingly before EQ allocation. A device reporting zero ports now results in a fatal probe error since the per-vPort MSI-X math requires at least one port. Rename mana_query_device_cfg() to mana_gd_query_device_cfg() as it is used at GDMA device probe time for querying device capabilities. Signed-off-by: Long Li Link: https://patch.msgid.link/20260605005717.2059954-3-longli@microsoft.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/microsoft/mana/gdma_main.c | 78 ++++++++++++++++++++++++- drivers/net/ethernet/microsoft/mana/mana_en.c | 45 ++++++++------ include/net/mana/gdma.h | 13 ++++- 3 files changed, 114 insertions(+), 22 deletions(-) (limited to 'include') diff --git a/drivers/net/ethernet/microsoft/mana/gdma_main.c b/drivers/net/ethernet/microsoft/mana/gdma_main.c index 712a0881d720..786e1d4cd4fe 100644 --- a/drivers/net/ethernet/microsoft/mana/gdma_main.c +++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c @@ -179,8 +179,21 @@ static int mana_gd_query_max_resources(struct pci_dev *pdev) struct gdma_context *gc = pci_get_drvdata(pdev); struct gdma_query_max_resources_resp resp = {}; struct gdma_general_req req = {}; + unsigned int max_num_queues; + u8 bm_hostmode; + u16 num_ports; int err; + /* Reset msi_sharing so it is recomputed from current hardware + * state. On resume, num_online_cpus() or num_msix_usable may + * have changed, making dedicated MSI-X feasible where it was + * not before. Only reset on platforms that support dynamic + * MSI-X allocation; on non-dyn platforms msi_sharing is + * unconditionally true (set in mana_gd_setup_hwc_irqs). + */ + if (pci_msix_can_alloc_dyn(to_pci_dev(gc->dev))) + gc->msi_sharing = false; + mana_gd_init_req_hdr(&req.hdr, GDMA_QUERY_MAX_RESOURCES, sizeof(req), sizeof(resp)); @@ -232,6 +245,52 @@ static int mana_gd_query_max_resources(struct pci_dev *pdev) debugfs_create_u32("max_num_queues", 0400, gc->mana_pci_debugfs, &gc->max_num_queues); + err = mana_gd_query_device_cfg(gc, MANA_MAJOR_VERSION, + MANA_MINOR_VERSION, + MANA_MICRO_VERSION, + &num_ports, &bm_hostmode); + if (err) + return err; + + if (!num_ports) { + dev_err(gc->dev, "Failed to detect any vPort\n"); + return -EINVAL; + } + + /* Cap to the same limit used by mana_probe() for port instantiation, + * so MSI-X and queue budgeting matches the actual port count. + */ + if (num_ports > MAX_PORTS_IN_MANA_DEV) + num_ports = MAX_PORTS_IN_MANA_DEV; + + /* + * Adjust the per-vPort max queue count to allow dedicated + * MSIx for each vPort. Prefer at least MANA_DEF_NUM_QUEUES, + * but the hardware max (gc->max_num_queues) takes precedence. + */ + max_num_queues = (gc->num_msix_usable - 1) / num_ports; + max_num_queues = rounddown_pow_of_two(max(max_num_queues, 1U)); + if (max_num_queues < MANA_DEF_NUM_QUEUES) + max_num_queues = MANA_DEF_NUM_QUEUES; + + /* + * Use dedicated MSIx for EQs whenever possible, use MSIx sharing for + * Ethernet EQs when (max_num_queues * num_ports > num_msix_usable - 1). + */ + max_num_queues = min(gc->max_num_queues, max_num_queues); + if (max_num_queues * num_ports > gc->num_msix_usable - 1) + gc->msi_sharing = true; + + /* If MSI is shared, use max allowed value */ + if (gc->msi_sharing) + gc->max_num_queues_vport = min(gc->num_msix_usable - 1, + gc->max_num_queues); + else + gc->max_num_queues_vport = max_num_queues; + + dev_info(gc->dev, "MSI sharing mode %u max queues %u\n", + gc->msi_sharing, gc->max_num_queues_vport); + return 0; } @@ -1901,6 +1960,7 @@ static int mana_gd_setup_hwc_irqs(struct pci_dev *pdev) /* Need 1 interrupt for HWC */ max_irqs = min(num_online_cpus(), MANA_MAX_NUM_QUEUES) + 1; min_irqs = 2; + gc->msi_sharing = true; } nvec = pci_alloc_irq_vectors(pdev, min_irqs, max_irqs, PCI_IRQ_MSIX); @@ -1979,6 +2039,8 @@ static void mana_gd_remove_irqs(struct pci_dev *pdev) pci_free_irq_vectors(pdev); + bitmap_free(gc->msi_bitmap); + gc->msi_bitmap = NULL; gc->max_num_msix = 0; gc->num_msix_usable = 0; } @@ -2018,6 +2080,10 @@ static int mana_gd_setup(struct pci_dev *pdev) if (err) goto destroy_hwc; + err = mana_gd_detect_devices(pdev); + if (err) + goto destroy_hwc; + err = mana_gd_query_max_resources(pdev); if (err) goto destroy_hwc; @@ -2028,9 +2094,15 @@ static int mana_gd_setup(struct pci_dev *pdev) goto destroy_hwc; } - err = mana_gd_detect_devices(pdev); - if (err) - goto destroy_hwc; + if (!gc->msi_sharing) { + gc->msi_bitmap = bitmap_zalloc(gc->num_msix_usable, GFP_KERNEL); + if (!gc->msi_bitmap) { + err = -ENOMEM; + goto destroy_hwc; + } + /* Set bit for HWC */ + set_bit(0, gc->msi_bitmap); + } dev_dbg(&pdev->dev, "mana gdma setup successful\n"); return 0; diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c index ed60cc15fe78..3ec8e94e7c17 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_en.c +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c @@ -1018,10 +1018,9 @@ static int mana_init_port_context(struct mana_port_context *apc) return !apc->rxqs ? -ENOMEM : 0; } -static int mana_send_request(struct mana_context *ac, void *in_buf, - u32 in_len, void *out_buf, u32 out_len) +static int gdma_mana_send_request(struct gdma_context *gc, void *in_buf, + u32 in_len, void *out_buf, u32 out_len) { - struct gdma_context *gc = ac->gdma_dev->gdma_context; struct gdma_resp_hdr *resp = out_buf; struct gdma_req_hdr *req = in_buf; struct device *dev = gc->dev; @@ -1055,6 +1054,14 @@ static int mana_send_request(struct mana_context *ac, void *in_buf, return 0; } +static int mana_send_request(struct mana_context *ac, void *in_buf, + u32 in_len, void *out_buf, u32 out_len) +{ + struct gdma_context *gc = ac->gdma_dev->gdma_context; + + return gdma_mana_send_request(gc, in_buf, in_len, out_buf, out_len); +} + static int mana_verify_resp_hdr(const struct gdma_resp_hdr *resp_hdr, const enum mana_command_code expected_code, const u32 min_size) @@ -1188,11 +1195,10 @@ static void mana_pf_deregister_filter(struct mana_port_context *apc) err, resp.hdr.status); } -static int mana_query_device_cfg(struct mana_context *ac, u32 proto_major_ver, - u32 proto_minor_ver, u32 proto_micro_ver, - u16 *max_num_vports, u8 *bm_hostmode) +int mana_gd_query_device_cfg(struct gdma_context *gc, u32 proto_major_ver, + u32 proto_minor_ver, u32 proto_micro_ver, + u16 *max_num_vports, u8 *bm_hostmode) { - struct gdma_context *gc = ac->gdma_dev->gdma_context; struct mana_query_device_cfg_resp resp = {}; struct mana_query_device_cfg_req req = {}; struct device *dev = gc->dev; @@ -1207,7 +1213,8 @@ static int mana_query_device_cfg(struct mana_context *ac, u32 proto_major_ver, req.proto_minor_ver = proto_minor_ver; req.proto_micro_ver = proto_micro_ver; - err = mana_send_request(ac, &req, sizeof(req), &resp, sizeof(resp)); + err = gdma_mana_send_request(gc, &req, sizeof(req), + &resp, sizeof(resp)); if (err) { dev_err(dev, "Failed to query config: %d", err); return err; @@ -1241,8 +1248,6 @@ static int mana_query_device_cfg(struct mana_context *ac, u32 proto_major_ver, else *bm_hostmode = 0; - debugfs_create_u16("adapter-MTU", 0400, gc->mana_pci_debugfs, &gc->adapter_mtu); - return 0; } @@ -3208,6 +3213,8 @@ static int mana_init_port(struct net_device *ndev) max_queues = min_t(u32, max_txq, max_rxq); if (apc->max_queues > max_queues) apc->max_queues = max_queues; + if (apc->max_queues > gc->max_num_queues_vport) + apc->max_queues = gc->max_num_queues_vport; if (apc->num_queues > apc->max_queues) apc->num_queues = apc->max_queues; @@ -3478,7 +3485,7 @@ static int mana_probe_port(struct mana_context *ac, int port_idx, int err; ndev = alloc_etherdev_mq(sizeof(struct mana_port_context), - gc->max_num_queues); + gc->max_num_queues_vport); if (!ndev) return -ENOMEM; @@ -3487,9 +3494,9 @@ static int mana_probe_port(struct mana_context *ac, int port_idx, apc = netdev_priv(ndev); apc->ac = ac; apc->ndev = ndev; - apc->max_queues = gc->max_num_queues; + apc->max_queues = gc->max_num_queues_vport; /* Use MANA_DEF_NUM_QUEUES as default, still honoring the HW limit */ - apc->num_queues = min(gc->max_num_queues, MANA_DEF_NUM_QUEUES); + apc->num_queues = min(gc->max_num_queues_vport, MANA_DEF_NUM_QUEUES); apc->tx_queue_size = DEF_TX_BUFFERS_PER_QUEUE; apc->rx_queue_size = DEF_RX_BUFFERS_PER_QUEUE; apc->port_handle = INVALID_MANA_HANDLE; @@ -3753,13 +3760,18 @@ int mana_probe(struct gdma_dev *gd, bool resuming) INIT_DELAYED_WORK(&ac->gf_stats_work, mana_gf_stats_work_handler); - err = mana_query_device_cfg(ac, MANA_MAJOR_VERSION, MANA_MINOR_VERSION, - MANA_MICRO_VERSION, &num_ports, &bm_hostmode); + err = mana_gd_query_device_cfg(gc, MANA_MAJOR_VERSION, + MANA_MINOR_VERSION, + MANA_MICRO_VERSION, + &num_ports, &bm_hostmode); if (err) goto out; ac->bm_hostmode = bm_hostmode; + debugfs_create_u16("adapter-MTU", 0400, + gc->mana_pci_debugfs, &gc->adapter_mtu); + if (!resuming) { ac->num_ports = num_ports; } else { @@ -3773,9 +3785,6 @@ int mana_probe(struct gdma_dev *gd, bool resuming) enable_work(&ac->link_change_work); } - if (ac->num_ports == 0) - dev_err(dev, "Failed to detect any vPort\n"); - if (ac->num_ports > MAX_PORTS_IN_MANA_DEV) ac->num_ports = MAX_PORTS_IN_MANA_DEV; diff --git a/include/net/mana/gdma.h b/include/net/mana/gdma.h index 70d62bc32837..145cc59dfc19 100644 --- a/include/net/mana/gdma.h +++ b/include/net/mana/gdma.h @@ -399,8 +399,10 @@ struct gdma_context { struct device *dev; struct dentry *mana_pci_debugfs; - /* Per-vPort max number of queues */ + /* Hardware max number of queues */ unsigned int max_num_queues; + /* Per-vPort max number of queues */ + unsigned int max_num_queues_vport; unsigned int max_num_msix; unsigned int num_msix_usable; struct xarray irq_contexts; @@ -447,6 +449,12 @@ struct gdma_context { struct workqueue_struct *service_wq; unsigned long flags; + + /* Indicate if this device is sharing MSI for EQs on MANA */ + bool msi_sharing; + + /* Bitmap tracks where MSI is allocated when it is not shared for EQs */ + unsigned long *msi_bitmap; }; static inline bool mana_gd_is_mana(struct gdma_dev *gd) @@ -1019,4 +1027,7 @@ int mana_gd_resume(struct pci_dev *pdev); bool mana_need_log(struct gdma_context *gc, int err); +int mana_gd_query_device_cfg(struct gdma_context *gc, u32 proto_major_ver, + u32 proto_minor_ver, u32 proto_micro_ver, + u16 *max_num_vports, u8 *bm_hostmode); #endif /* _GDMA_H */ -- cgit v1.2.3 From d478457fc1b7fd80f481c323a8d22567d99d6881 Mon Sep 17 00:00:00 2001 From: Long Li Date: Thu, 4 Jun 2026 17:57:12 -0700 Subject: net: mana: Introduce GIC context with refcounting for interrupt management To allow Ethernet EQs to use dedicated or shared MSI-X vectors and RDMA EQs to share the same MSI-X, introduce a GIC (GDMA IRQ Context) with reference counting. This allows the driver to create an interrupt context on an assigned or unassigned MSI-X vector and share it across multiple EQ consumers. Signed-off-by: Long Li Link: https://patch.msgid.link/20260605005717.2059954-4-longli@microsoft.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/microsoft/mana/gdma_main.c | 169 ++++++++++++++++++++++++ include/net/mana/gdma.h | 12 ++ 2 files changed, 181 insertions(+) (limited to 'include') diff --git a/drivers/net/ethernet/microsoft/mana/gdma_main.c b/drivers/net/ethernet/microsoft/mana/gdma_main.c index 786e1d4cd4fe..fd643300a427 100644 --- a/drivers/net/ethernet/microsoft/mana/gdma_main.c +++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c @@ -871,6 +871,10 @@ static int mana_gd_register_irq(struct gdma_queue *queue, } queue->eq.msix_index = msi_index; + /* The caller acquired a GIC reference via mana_gd_get_gic(). + * That refcount prevents mana_gd_put_gic() from erasing this + * irq_contexts entry concurrently. + */ gic = xa_load(&gc->irq_contexts, msi_index); if (WARN_ON(!gic)) return -EINVAL; @@ -898,6 +902,10 @@ static void mana_gd_deregister_irq(struct gdma_queue *queue) if (WARN_ON(msix_index >= gc->num_msix_usable)) return; + /* The caller releases the GIC reference via mana_gd_put_gic() + * after this function returns. The refcount guarantees this + * irq_contexts entry is still valid. + */ gic = xa_load(&gc->irq_contexts, msix_index); if (WARN_ON(!gic)) return; @@ -1679,6 +1687,166 @@ static irqreturn_t mana_gd_intr(int irq, void *arg) return IRQ_HANDLED; } +void mana_gd_put_gic(struct gdma_context *gc, bool use_msi_bitmap, int msi) +{ + struct pci_dev *dev = to_pci_dev(gc->dev); + struct gdma_irq_context *gic; + struct msi_map irq_map; + int irq; + + mutex_lock(&gc->gic_mutex); + + gic = xa_load(&gc->irq_contexts, msi); + if (WARN_ON(!gic)) { + mutex_unlock(&gc->gic_mutex); + return; + } + + if (use_msi_bitmap) + gic->bitmap_refs--; + + if (use_msi_bitmap && gic->bitmap_refs == 0) + clear_bit(msi, gc->msi_bitmap); + + if (!refcount_dec_and_test(&gic->refcount)) + goto out; + + irq = gic->irq; + + irq_update_affinity_hint(irq, NULL); + free_irq(irq, gic); + + if (gic->dyn_msix) { + irq_map.virq = irq; + irq_map.index = msi; + pci_msix_free_irq(dev, irq_map); + } + + xa_erase(&gc->irq_contexts, msi); + kfree(gic); + +out: + mutex_unlock(&gc->gic_mutex); +} +EXPORT_SYMBOL_NS(mana_gd_put_gic, "NET_MANA"); + +/* + * Get a GIC (GDMA IRQ Context) on a MSI vector + * a MSI can be shared between different EQs, this function supports setting + * up separate MSIs using a bitmap, or directly using the MSI index + * + * @use_msi_bitmap: + * True if MSI is assigned by this function on available slots from bitmap. + * False if MSI is passed from *msi_requested + */ +struct gdma_irq_context *mana_gd_get_gic(struct gdma_context *gc, + bool use_msi_bitmap, + int *msi_requested) +{ + struct pci_dev *dev = to_pci_dev(gc->dev); + struct gdma_irq_context *gic; + struct msi_map irq_map = { }; + int irq; + int msi; + int err; + + mutex_lock(&gc->gic_mutex); + + if (use_msi_bitmap) { + msi = find_first_zero_bit(gc->msi_bitmap, gc->num_msix_usable); + if (msi >= gc->num_msix_usable) { + dev_err(gc->dev, "No free MSI vectors available\n"); + gic = ERR_PTR(-ENOSPC); + goto out; + } + *msi_requested = msi; + } else { + msi = *msi_requested; + } + + gic = xa_load(&gc->irq_contexts, msi); + if (gic) { + refcount_inc(&gic->refcount); + if (use_msi_bitmap) { + gic->bitmap_refs++; + set_bit(msi, gc->msi_bitmap); + } + goto out; + } + + irq = pci_irq_vector(dev, msi); + if (irq == -EINVAL) { + irq_map = pci_msix_alloc_irq_at(dev, msi, NULL); + if (!irq_map.virq) { + err = irq_map.index; + dev_err(gc->dev, + "Failed to alloc irq_map msi %d err %d\n", + msi, err); + gic = ERR_PTR(err); + goto out; + } + irq = irq_map.virq; + msi = irq_map.index; + *msi_requested = msi; + } + + gic = kzalloc(sizeof(*gic), GFP_KERNEL); + if (!gic) { + gic = ERR_PTR(-ENOMEM); + if (irq_map.virq) + pci_msix_free_irq(dev, irq_map); + goto out; + } + + gic->handler = mana_gd_process_eq_events; + gic->msi = msi; + gic->irq = irq; + INIT_LIST_HEAD(&gic->eq_list); + spin_lock_init(&gic->lock); + + if (!gic->msi) + snprintf(gic->name, MANA_IRQ_NAME_SZ, "mana_hwc@pci:%s", + pci_name(dev)); + else + snprintf(gic->name, MANA_IRQ_NAME_SZ, "mana_msi%d@pci:%s", + gic->msi, pci_name(dev)); + + err = request_irq(irq, mana_gd_intr, 0, gic->name, gic); + if (err) { + dev_err(gc->dev, "Failed to request irq %d %s\n", + irq, gic->name); + kfree(gic); + gic = ERR_PTR(err); + if (irq_map.virq) + pci_msix_free_irq(dev, irq_map); + goto out; + } + + gic->dyn_msix = !!irq_map.virq; + refcount_set(&gic->refcount, 1); + gic->bitmap_refs = use_msi_bitmap ? 1 : 0; + + err = xa_err(xa_store(&gc->irq_contexts, msi, gic, GFP_KERNEL)); + if (err) { + dev_err(gc->dev, "Failed to store irq context for msi %d: %d\n", + msi, err); + free_irq(irq, gic); + kfree(gic); + gic = ERR_PTR(err); + if (irq_map.virq) + pci_msix_free_irq(dev, irq_map); + goto out; + } + + if (use_msi_bitmap) + set_bit(msi, gc->msi_bitmap); + +out: + mutex_unlock(&gc->gic_mutex); + return gic; +} +EXPORT_SYMBOL_NS(mana_gd_get_gic, "NET_MANA"); + int mana_gd_alloc_res_map(u32 res_avail, struct gdma_resource *r) { r->map = bitmap_zalloc(res_avail, GFP_KERNEL); @@ -2180,6 +2348,7 @@ static int mana_gd_probe(struct pci_dev *pdev, const struct pci_device_id *ent) goto release_region; mutex_init(&gc->eq_test_event_mutex); + mutex_init(&gc->gic_mutex); pci_set_drvdata(pdev, gc); gc->bar0_pa = pci_resource_start(pdev, 0); gc->bar0_size = pci_resource_len(pdev, 0); diff --git a/include/net/mana/gdma.h b/include/net/mana/gdma.h index 145cc59dfc19..e3ee85c614ec 100644 --- a/include/net/mana/gdma.h +++ b/include/net/mana/gdma.h @@ -388,6 +388,11 @@ struct gdma_irq_context { spinlock_t lock; struct list_head eq_list; char name[MANA_IRQ_NAME_SZ]; + unsigned int msi; + unsigned int irq; + refcount_t refcount; + unsigned int bitmap_refs; + bool dyn_msix; }; enum gdma_context_flags { @@ -450,6 +455,9 @@ struct gdma_context { unsigned long flags; + /* Protect access to GIC context */ + struct mutex gic_mutex; + /* Indicate if this device is sharing MSI for EQs on MANA */ bool msi_sharing; @@ -1027,6 +1035,10 @@ int mana_gd_resume(struct pci_dev *pdev); bool mana_need_log(struct gdma_context *gc, int err); +struct gdma_irq_context *mana_gd_get_gic(struct gdma_context *gc, + bool use_msi_bitmap, + int *msi_requested); +void mana_gd_put_gic(struct gdma_context *gc, bool use_msi_bitmap, int msi); int mana_gd_query_device_cfg(struct gdma_context *gc, u32 proto_major_ver, u32 proto_minor_ver, u32 proto_micro_ver, u16 *max_num_vports, u8 *bm_hostmode); -- cgit v1.2.3 From 487af6f5391e7461437e0972d15d9bcbcab2ac21 Mon Sep 17 00:00:00 2001 From: Long Li Date: Thu, 4 Jun 2026 17:57:14 -0700 Subject: net: mana: Allocate interrupt context for each EQ when creating vPort Use GIC functions to create a dedicated interrupt context or acquire a shared interrupt context for each EQ when setting up a vPort. The caller now owns the GIC reference across the EQ create/destroy lifecycle: mana_create_eq() calls mana_gd_get_gic() before creating each EQ and mana_destroy_eq() calls mana_gd_put_gic() after destroying it. The msix_index invalidation is moved from mana_gd_deregister_irq() to the mana_gd_create_eq() error path so that mana_destroy_eq() can read the index before teardown. Signed-off-by: Long Li Link: https://patch.msgid.link/20260605005717.2059954-6-longli@microsoft.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/microsoft/mana/gdma_main.c | 2 +- drivers/net/ethernet/microsoft/mana/mana_en.c | 18 +++++++++++++++++- include/net/mana/gdma.h | 1 + 3 files changed, 19 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/drivers/net/ethernet/microsoft/mana/gdma_main.c b/drivers/net/ethernet/microsoft/mana/gdma_main.c index 653c091ad296..6feedbbdebc3 100644 --- a/drivers/net/ethernet/microsoft/mana/gdma_main.c +++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c @@ -919,7 +919,6 @@ static void mana_gd_deregister_irq(struct gdma_queue *queue) } spin_unlock_irqrestore(&gic->lock, flags); - queue->eq.msix_index = INVALID_PCI_MSIX_INDEX; synchronize_rcu(); } @@ -1034,6 +1033,7 @@ static int mana_gd_create_eq(struct gdma_dev *gd, out: dev_err(dev, "Failed to create EQ: %d\n", err); mana_gd_destroy_eq(gc, false, queue); + queue->eq.msix_index = INVALID_PCI_MSIX_INDEX; return err; } diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c index 3ec8e94e7c17..beada8660258 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_en.c +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c @@ -1645,6 +1645,7 @@ void mana_destroy_eq(struct mana_port_context *apc) struct mana_context *ac = apc->ac; struct gdma_context *gc = ac->gdma_dev->gdma_context; struct gdma_queue *eq; + unsigned int msi; int i; if (!apc->eqs) @@ -1658,7 +1659,9 @@ void mana_destroy_eq(struct mana_port_context *apc) if (!eq) continue; + msi = eq->eq.msix_index; mana_gd_destroy_queue(gc, eq); + mana_gd_put_gic(gc, !gc->msi_sharing, msi); } kfree(apc->eqs); @@ -1675,6 +1678,7 @@ static void mana_create_eq_debugfs(struct mana_port_context *apc, int i) eq.mana_eq_debugfs = debugfs_create_dir(eqnum, apc->mana_eqs_debugfs); debugfs_create_u32("head", 0400, eq.mana_eq_debugfs, &eq.eq->head); debugfs_create_u32("tail", 0400, eq.mana_eq_debugfs, &eq.eq->tail); + debugfs_create_u32("irq", 0400, eq.mana_eq_debugfs, &eq.eq->eq.irq); debugfs_create_file("eq_dump", 0400, eq.mana_eq_debugfs, eq.eq, &mana_dbg_q_fops); } @@ -1683,7 +1687,9 @@ int mana_create_eq(struct mana_port_context *apc) struct gdma_dev *gd = apc->ac->gdma_dev; struct gdma_context *gc = gd->gdma_context; struct gdma_queue_spec spec = {}; + struct gdma_irq_context *gic; int err; + int msi; int i; if (WARN_ON(apc->eqs)) @@ -1703,12 +1709,22 @@ int mana_create_eq(struct mana_port_context *apc) debugfs_create_dir("EQs", apc->mana_port_debugfs); for (i = 0; i < apc->num_queues; i++) { - spec.eq.msix_index = (i + 1) % gc->num_msix_usable; + msi = (i + 1) % gc->num_msix_usable; + + gic = mana_gd_get_gic(gc, !gc->msi_sharing, &msi); + if (IS_ERR(gic)) { + err = PTR_ERR(gic); + goto out; + } + spec.eq.msix_index = msi; + err = mana_gd_create_mana_eq(gd, &spec, &apc->eqs[i].eq); if (err) { dev_err(gc->dev, "Failed to create EQ %d : %d\n", i, err); + mana_gd_put_gic(gc, !gc->msi_sharing, msi); goto out; } + apc->eqs[i].eq->eq.irq = gic->irq; mana_create_eq_debugfs(apc, i); } diff --git a/include/net/mana/gdma.h b/include/net/mana/gdma.h index e3ee85c614ec..6a65fedae38f 100644 --- a/include/net/mana/gdma.h +++ b/include/net/mana/gdma.h @@ -342,6 +342,7 @@ struct gdma_queue { void *context; unsigned int msix_index; + unsigned int irq; u32 log2_throttle_limit; } eq; -- cgit v1.2.3 From 062b2b051f14a974b1fbd7b506fb833f5a171fa8 Mon Sep 17 00:00:00 2001 From: Long Li Date: Thu, 4 Jun 2026 17:57:15 -0700 Subject: RDMA/mana_ib: Allocate interrupt contexts on EQs Use the GIC functions to allocate interrupt contexts for RDMA EQs. These interrupt contexts may be shared with Ethernet EQs when MSI-X vectors are limited. The driver now supports allocating dedicated MSI-X for each EQ. Indicate this capability through driver capability bits. The RDMA EQs pass use_msi_bitmap=false to share MSI-X vectors with Ethernet, while the capability flag advertises that the driver supports per-vPort EQ separation when hardware has sufficient vectors. Populate eq.irq on all RDMA EQs for consistency with the Ethernet path. Also relocate the GDMA_DRV_CAP_FLAG_1_HW_VPORT_LINK_AWARE define to its numeric BIT(6) position among the other capability flags. Signed-off-by: Long Li Acked-by: Leon Romanovsky Link: https://patch.msgid.link/20260605005717.2059954-7-longli@microsoft.com Signed-off-by: Jakub Kicinski --- drivers/infiniband/hw/mana/main.c | 43 +++++++++++++++++++++++++++++++-------- include/net/mana/gdma.h | 7 +++++-- 2 files changed, 40 insertions(+), 10 deletions(-) (limited to 'include') diff --git a/drivers/infiniband/hw/mana/main.c b/drivers/infiniband/hw/mana/main.c index f42ea20cb75d..142047847f38 100644 --- a/drivers/infiniband/hw/mana/main.c +++ b/drivers/infiniband/hw/mana/main.c @@ -765,7 +765,8 @@ int mana_ib_create_eqs(struct mana_ib_dev *mdev) { struct gdma_context *gc = mdev_to_gc(mdev); struct gdma_queue_spec spec = {}; - int err, i; + struct gdma_irq_context *gic; + int err, i, msi; spec.type = GDMA_EQ; spec.monitor_avl_buf = false; @@ -773,11 +774,19 @@ int mana_ib_create_eqs(struct mana_ib_dev *mdev) spec.eq.callback = mana_ib_event_handler; spec.eq.context = mdev; spec.eq.log2_throttle_limit = LOG2_EQ_THROTTLE; - spec.eq.msix_index = 0; + + msi = 0; + gic = mana_gd_get_gic(gc, false, &msi); + if (IS_ERR(gic)) + return PTR_ERR(gic); + spec.eq.msix_index = msi; err = mana_gd_create_mana_eq(mdev->gdma_dev, &spec, &mdev->fatal_err_eq); - if (err) + if (err) { + mana_gd_put_gic(gc, false, 0); return err; + } + mdev->fatal_err_eq->eq.irq = gic->irq; mdev->eqs = kzalloc_objs(struct gdma_queue *, mdev->ib_dev.num_comp_vectors); @@ -787,32 +796,50 @@ int mana_ib_create_eqs(struct mana_ib_dev *mdev) } spec.eq.callback = NULL; for (i = 0; i < mdev->ib_dev.num_comp_vectors; i++) { - spec.eq.msix_index = (i + 1) % gc->num_msix_usable; + msi = (i + 1) % gc->num_msix_usable; + + gic = mana_gd_get_gic(gc, false, &msi); + if (IS_ERR(gic)) { + err = PTR_ERR(gic); + goto destroy_eqs; + } + spec.eq.msix_index = msi; + err = mana_gd_create_mana_eq(mdev->gdma_dev, &spec, &mdev->eqs[i]); - if (err) + if (err) { + mana_gd_put_gic(gc, false, msi); goto destroy_eqs; + } + mdev->eqs[i]->eq.irq = gic->irq; } return 0; destroy_eqs: - while (i-- > 0) + while (i-- > 0) { mana_gd_destroy_queue(gc, mdev->eqs[i]); + mana_gd_put_gic(gc, false, (i + 1) % gc->num_msix_usable); + } kfree(mdev->eqs); destroy_fatal_eq: mana_gd_destroy_queue(gc, mdev->fatal_err_eq); + mana_gd_put_gic(gc, false, 0); return err; } void mana_ib_destroy_eqs(struct mana_ib_dev *mdev) { struct gdma_context *gc = mdev_to_gc(mdev); - int i; + int i, msi; mana_gd_destroy_queue(gc, mdev->fatal_err_eq); + mana_gd_put_gic(gc, false, 0); - for (i = 0; i < mdev->ib_dev.num_comp_vectors; i++) + for (i = 0; i < mdev->ib_dev.num_comp_vectors; i++) { mana_gd_destroy_queue(gc, mdev->eqs[i]); + msi = (i + 1) % gc->num_msix_usable; + mana_gd_put_gic(gc, false, msi); + } kfree(mdev->eqs); } diff --git a/include/net/mana/gdma.h b/include/net/mana/gdma.h index 6a65fedae38f..78afd696b08b 100644 --- a/include/net/mana/gdma.h +++ b/include/net/mana/gdma.h @@ -616,6 +616,7 @@ enum { #define GDMA_DRV_CAP_FLAG_1_HWC_TIMEOUT_RECONFIG BIT(3) #define GDMA_DRV_CAP_FLAG_1_GDMA_PAGES_4MB_1GB_2GB BIT(4) #define GDMA_DRV_CAP_FLAG_1_VARIABLE_INDIRECTION_TABLE_SUPPORT BIT(5) +#define GDMA_DRV_CAP_FLAG_1_HW_VPORT_LINK_AWARE BIT(6) /* Driver can handle holes (zeros) in the device list */ #define GDMA_DRV_CAP_FLAG_1_DEV_LIST_HOLES_SUP BIT(11) @@ -632,7 +633,8 @@ enum { /* Driver detects stalled send queues and recovers them */ #define GDMA_DRV_CAP_FLAG_1_HANDLE_STALL_SQ_RECOVERY BIT(18) -#define GDMA_DRV_CAP_FLAG_1_HW_VPORT_LINK_AWARE BIT(6) +/* Driver supports separate EQ/MSIs for each vPort */ +#define GDMA_DRV_CAP_FLAG_1_EQ_MSI_UNSHARE_MULTI_VPORT BIT(19) /* Driver supports linearizing the skb when num_sge exceeds hardware limit */ #define GDMA_DRV_CAP_FLAG_1_SKB_LINEARIZE BIT(20) @@ -660,7 +662,8 @@ enum { GDMA_DRV_CAP_FLAG_1_SKB_LINEARIZE | \ GDMA_DRV_CAP_FLAG_1_PROBE_RECOVERY | \ GDMA_DRV_CAP_FLAG_1_HANDLE_STALL_SQ_RECOVERY | \ - GDMA_DRV_CAP_FLAG_1_HWC_TIMEOUT_RECOVERY) + GDMA_DRV_CAP_FLAG_1_HWC_TIMEOUT_RECOVERY | \ + GDMA_DRV_CAP_FLAG_1_EQ_MSI_UNSHARE_MULTI_VPORT) #define GDMA_DRV_CAP_FLAGS2 0 -- cgit v1.2.3 From 53a65db20a4f3fe6c01b1f789f9eae6b1244910f Mon Sep 17 00:00:00 2001 From: Haiyang Zhang Date: Fri, 5 Jun 2026 14:22:56 -0700 Subject: net: mana: Add support for PF device 0x00C1 Update the device id table to include the new device id 0x00C1. This device's BAR layout is similar to VF's, update the function, mana_gd_init_registers(), accordingly. Signed-off-by: Haiyang Zhang Link: https://patch.msgid.link/20260605212302.2135499-1-haiyangz@linux.microsoft.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/microsoft/mana/gdma_main.c | 7 +++++-- include/net/mana/gdma.h | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/drivers/net/ethernet/microsoft/mana/gdma_main.c b/drivers/net/ethernet/microsoft/mana/gdma_main.c index 6feedbbdebc3..c9ec80a1dd6f 100644 --- a/drivers/net/ethernet/microsoft/mana/gdma_main.c +++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c @@ -150,7 +150,7 @@ static int mana_gd_init_registers(struct pci_dev *pdev) { struct gdma_context *gc = pci_get_drvdata(pdev); - if (gc->is_pf) + if (gc->is_pf && !gc->is_pf2) return mana_gd_init_pf_regs(pdev); else return mana_gd_init_vf_regs(pdev); @@ -2244,7 +2244,7 @@ static void mana_gd_cleanup_device(struct pci_dev *pdev) static bool mana_is_pf(unsigned short dev_id) { - return dev_id == MANA_PF_DEVICE_ID; + return dev_id == MANA_PF_DEVICE_ID || dev_id == MANA_PF2_DEVICE_ID; } static int mana_gd_probe(struct pci_dev *pdev, const struct pci_device_id *ent) @@ -2293,6 +2293,8 @@ static int mana_gd_probe(struct pci_dev *pdev, const struct pci_device_id *ent) gc->numa_node = dev_to_node(&pdev->dev); gc->is_pf = mana_is_pf(pdev->device); + gc->is_pf2 = (pdev->device == MANA_PF2_DEVICE_ID); + gc->bar0_va = bar0_va; gc->dev = &pdev->dev; xa_init(&gc->irq_contexts); @@ -2444,6 +2446,7 @@ static void mana_gd_shutdown(struct pci_dev *pdev) static const struct pci_device_id mana_id_table[] = { { PCI_DEVICE(PCI_VENDOR_ID_MICROSOFT, MANA_PF_DEVICE_ID) }, + { PCI_DEVICE(PCI_VENDOR_ID_MICROSOFT, MANA_PF2_DEVICE_ID) }, { PCI_DEVICE(PCI_VENDOR_ID_MICROSOFT, MANA_VF_DEVICE_ID) }, { } }; diff --git a/include/net/mana/gdma.h b/include/net/mana/gdma.h index 78afd696b08b..0c395917b214 100644 --- a/include/net/mana/gdma.h +++ b/include/net/mana/gdma.h @@ -426,6 +426,7 @@ struct gdma_context { u32 test_event_eq_id; bool is_pf; + bool is_pf2; phys_addr_t bar0_pa; void __iomem *bar0_va; @@ -588,6 +589,7 @@ struct gdma_eqe { #define GDMA_SRIOV_REG_CFG_BASE_OFF 0x108 #define MANA_PF_DEVICE_ID 0x00B9 +#define MANA_PF2_DEVICE_ID 0x00C1 #define MANA_VF_DEVICE_ID 0x00BA struct gdma_posted_wqe_info { -- cgit v1.2.3 From ea07514700f7a5125db67c9d1cbfb06e65c30a07 Mon Sep 17 00:00:00 2001 From: David Yang Date: Sat, 6 Jun 2026 20:52:44 +0800 Subject: net: mscc: ocelot: validate netdev belongs to switch in .netdev_to_port() The .netdev_to_port() currently takes only a net_device and returns the port index, without verifying the netdev actually belongs to the switch being operated on. This can cause flower rule parsing to silently resolve to a wrong port on the local hardware. Update both implementations felix_netdev_to_port() and ocelot_netdev_to_port() to validate ownership. Also update the callers in ocelot_flower.c to pass through the ocelot context. Signed-off-by: David Yang Link: https://patch.msgid.link/20260606125247.305167-1-mmyangfl@gmail.com Signed-off-by: Jakub Kicinski --- drivers/net/dsa/ocelot/felix.c | 6 ++++-- drivers/net/dsa/ocelot/felix.h | 2 +- drivers/net/ethernet/mscc/ocelot.h | 2 +- drivers/net/ethernet/mscc/ocelot_flower.c | 4 ++-- drivers/net/ethernet/mscc/ocelot_net.c | 4 +++- include/soc/mscc/ocelot.h | 2 +- 6 files changed, 12 insertions(+), 8 deletions(-) (limited to 'include') diff --git a/drivers/net/dsa/ocelot/felix.c b/drivers/net/dsa/ocelot/felix.c index 4272ea6e9ca8..84a95007825f 100644 --- a/drivers/net/dsa/ocelot/felix.c +++ b/drivers/net/dsa/ocelot/felix.c @@ -2446,12 +2446,14 @@ struct net_device *felix_port_to_netdev(struct ocelot *ocelot, int port) } EXPORT_SYMBOL_GPL(felix_port_to_netdev); -int felix_netdev_to_port(struct net_device *dev) +int felix_netdev_to_port(struct ocelot *ocelot, struct net_device *dev) { + struct felix *felix = ocelot_to_felix(ocelot); + struct dsa_switch *ds = felix->ds; struct dsa_port *dp; dp = dsa_port_from_netdev(dev); - if (IS_ERR(dp)) + if (IS_ERR(dp) || dp->ds != ds) return -EINVAL; return dp->index; diff --git a/drivers/net/dsa/ocelot/felix.h b/drivers/net/dsa/ocelot/felix.h index a657b190c5d7..19addcfd62be 100644 --- a/drivers/net/dsa/ocelot/felix.h +++ b/drivers/net/dsa/ocelot/felix.h @@ -104,6 +104,6 @@ int felix_register_switch(struct device *dev, resource_size_t switch_base, enum dsa_tag_protocol init_tag_proto, const struct felix_info *info); struct net_device *felix_port_to_netdev(struct ocelot *ocelot, int port); -int felix_netdev_to_port(struct net_device *dev); +int felix_netdev_to_port(struct ocelot *ocelot, struct net_device *dev); #endif diff --git a/drivers/net/ethernet/mscc/ocelot.h b/drivers/net/ethernet/mscc/ocelot.h index e50be508c166..42d2c456f712 100644 --- a/drivers/net/ethernet/mscc/ocelot.h +++ b/drivers/net/ethernet/mscc/ocelot.h @@ -92,7 +92,7 @@ int ocelot_mact_learn(struct ocelot *ocelot, int port, int ocelot_mact_forget(struct ocelot *ocelot, const unsigned char mac[ETH_ALEN], unsigned int vid); struct net_device *ocelot_port_to_netdev(struct ocelot *ocelot, int port); -int ocelot_netdev_to_port(struct net_device *dev); +int ocelot_netdev_to_port(struct ocelot *ocelot, struct net_device *dev); int ocelot_probe_port(struct ocelot *ocelot, int port, struct regmap *target, struct device_node *portnp); diff --git a/drivers/net/ethernet/mscc/ocelot_flower.c b/drivers/net/ethernet/mscc/ocelot_flower.c index 844292eb7422..c5983c56a544 100644 --- a/drivers/net/ethernet/mscc/ocelot_flower.c +++ b/drivers/net/ethernet/mscc/ocelot_flower.c @@ -233,8 +233,8 @@ ocelot_flower_parse_egress_port(struct ocelot *ocelot, struct flow_cls_offload * const struct flow_action_entry *a, bool mirror, struct netlink_ext_ack *extack) { + int egress_port = ocelot->ops->netdev_to_port(ocelot, a->dev); const char *act_string = mirror ? "mirror" : "redirect"; - int egress_port = ocelot->ops->netdev_to_port(a->dev); enum flow_action_id offloadable_act_id; offloadable_act_id = mirror ? FLOW_ACTION_MIRRED : FLOW_ACTION_REDIRECT; @@ -580,7 +580,7 @@ static int ocelot_flower_parse_indev(struct ocelot *ocelot, int port, return -ENOENT; } - ingress_port = ocelot->ops->netdev_to_port(indev); + ingress_port = ocelot->ops->netdev_to_port(ocelot, indev); if (ingress_port < 0) { NL_SET_ERR_MSG_MOD(extack, "Can only offload an ocelot ingress port"); diff --git a/drivers/net/ethernet/mscc/ocelot_net.c b/drivers/net/ethernet/mscc/ocelot_net.c index 1b8269320464..4d45e136f08f 100644 --- a/drivers/net/ethernet/mscc/ocelot_net.c +++ b/drivers/net/ethernet/mscc/ocelot_net.c @@ -984,7 +984,7 @@ static bool ocelot_netdevice_dev_check(const struct net_device *dev) return dev->netdev_ops == &ocelot_port_netdev_ops; } -int ocelot_netdev_to_port(struct net_device *dev) +int ocelot_netdev_to_port(struct ocelot *ocelot, struct net_device *dev) { struct ocelot_port_private *priv; @@ -992,6 +992,8 @@ int ocelot_netdev_to_port(struct net_device *dev) return -EINVAL; priv = netdev_priv(dev); + if (priv->port.ocelot != ocelot) + return -EINVAL; return priv->port.index; } diff --git a/include/soc/mscc/ocelot.h b/include/soc/mscc/ocelot.h index 48d6deb3efd7..05a8191b148e 100644 --- a/include/soc/mscc/ocelot.h +++ b/include/soc/mscc/ocelot.h @@ -650,7 +650,7 @@ struct device_node; struct ocelot_ops { struct net_device *(*port_to_netdev)(struct ocelot *ocelot, int port); - int (*netdev_to_port)(struct net_device *dev); + int (*netdev_to_port)(struct ocelot *ocelot, struct net_device *dev); int (*reset)(struct ocelot *ocelot); u16 (*wm_enc)(u16 value); u16 (*wm_dec)(u16 value); -- cgit v1.2.3 From 8e0ffcc926f42103fdded733a28af16438babc0e Mon Sep 17 00:00:00 2001 From: Erni Sri Satya Vennela Date: Sat, 6 Jun 2026 06:32:55 -0700 Subject: net: mana: Cache MANA_QUERY_LINK_CONFIG result to avoid repeated HWC queries mana_query_link_cfg() sends an HWC command to firmware on every call, but the link speed and QoS values it returns only change when the driver explicitly calls mana_set_bw_clamp(). This function is called not only by userspace via ethtool get_link_ksettings, but also periodically by hv_netvsc through netvsc_get_link_ksettings and by the sysfs speed_show attribute via dev_attr_show, resulting in unnecessary HWC traffic every few minutes. Add a link_cfg_error field to mana_port_context to cache the query result. The field uses three states: 1 (not yet queried, initial value set during mana_probe_port), 0 (success, speed/max_speed are valid), or a negative errno for permanent errors like -EOPNOTSUPP when the hardware does not support the command. Transient errors and qos_unconfigured responses are not cached so that subsequent calls will retry. MANA is ops-locked because it implements net_shaper_ops, so the core already takes netdev_lock() around all ethtool_ops and net_shaper_ops entry points. Reuse that lock to serialize mana_query_link_cfg() and mana_set_bw_clamp(). This prevents a concurrent mana_set_bw_clamp() from racing with an in-flight query and publishing stale pre-clamp speed/max_speed. Invalidate the cache inside mana_set_bw_clamp() on success, so all current and future callers that change the link configuration automatically trigger a fresh query on the next mana_query_link_cfg() call. Also reset link_cfg_error during resume in mana_probe() under netdev_lock(), so that any query already in flight cannot later store 0 and silently overwrite the post-resume invalidation. Signed-off-by: Erni Sri Satya Vennela Link: https://patch.msgid.link/20260606133301.2180073-1-ernis@linux.microsoft.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/microsoft/mana/mana_en.c | 23 +++++++++++++++++++---- include/net/mana/mana.h | 4 ++++ 2 files changed, 23 insertions(+), 4 deletions(-) (limited to 'include') diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c b/drivers/net/ethernet/microsoft/mana/mana_en.c index beada8660258..26aef21c6c2c 100644 --- a/drivers/net/ethernet/microsoft/mana/mana_en.c +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c @@ -1473,6 +1473,12 @@ int mana_query_link_cfg(struct mana_port_context *apc) struct mana_query_link_config_req req = {}; int err; + netdev_assert_locked(ndev); + + err = apc->link_cfg_error; + if (err <= 0) + return err; + mana_gd_init_req_hdr(&req.hdr, MANA_QUERY_LINK_CONFIG, sizeof(req), sizeof(resp)); @@ -1485,6 +1491,7 @@ int mana_query_link_cfg(struct mana_port_context *apc) if (err) { if (err == -EOPNOTSUPP) { netdev_info_once(ndev, "MANA_QUERY_LINK_CONFIG not supported\n"); + apc->link_cfg_error = err; return err; } netdev_err(ndev, "Failed to query link config: %d\n", err); @@ -1502,12 +1509,12 @@ int mana_query_link_cfg(struct mana_port_context *apc) return err; } - if (resp.qos_unconfigured) { - err = -EINVAL; - return err; - } + if (resp.qos_unconfigured) + return -EINVAL; + apc->speed = resp.link_speed_mbps; apc->max_speed = resp.qos_speed_mbps; + apc->link_cfg_error = 0; return 0; } @@ -1519,6 +1526,8 @@ int mana_set_bw_clamp(struct mana_port_context *apc, u32 speed, struct net_device *ndev = apc->ndev; int err; + netdev_assert_locked(ndev); + mana_gd_init_req_hdr(&req.hdr, MANA_SET_BW_CLAMP, sizeof(req), sizeof(resp)); req.vport = apc->port_handle; @@ -1552,6 +1561,8 @@ int mana_set_bw_clamp(struct mana_port_context *apc, u32 speed, if (resp.qos_unconfigured) netdev_info(ndev, "QoS is unconfigured\n"); + /* Invalidate the cache; next query will re-fetch from firmware. */ + apc->link_cfg_error = 1; return 0; } @@ -3518,6 +3529,7 @@ static int mana_probe_port(struct mana_context *ac, int port_idx, apc->port_handle = INVALID_MANA_HANDLE; apc->pf_filter_handle = INVALID_MANA_HANDLE; apc->port_idx = port_idx; + apc->link_cfg_error = 1; apc->cqe_coalescing_enable = 0; mutex_init(&apc->vport_mutex); @@ -3834,6 +3846,9 @@ int mana_probe(struct gdma_dev *gd, bool resuming) rtnl_lock(); apc = netdev_priv(ac->ports[i]); enable_work(&apc->queue_reset_work); + netdev_lock(ac->ports[i]); + apc->link_cfg_error = 1; + netdev_unlock(ac->ports[i]); err = mana_attach(ac->ports[i]); rtnl_unlock(); /* Log the port for which the attach failed, stop diff --git a/include/net/mana/mana.h b/include/net/mana/mana.h index 5a9b94e0ef34..13c87baf018e 100644 --- a/include/net/mana/mana.h +++ b/include/net/mana/mana.h @@ -562,6 +562,10 @@ struct mana_port_context { u32 speed; /* Maximum speed supported by the SKU (mbps) */ u32 max_speed; + /* 1 = not queried, 0 = cached success, negative = permanent error. + * Protected by the netdev instance lock. + */ + int link_cfg_error; bool port_is_up; bool port_st_save; /* Saved port state */ -- cgit v1.2.3 From d11706b56a3f738a93bb2af8e94d6fab506e81b6 Mon Sep 17 00:00:00 2001 From: Stanislav Fomichev Date: Mon, 8 Jun 2026 08:40:12 -0700 Subject: net: change ndo_set_rx_mode_async return type to int Change the return type of ndo_set_rx_mode_async from void to int to allow drivers to report failures back to the core stack. This is a prerequisite for adding retry logic in the core when drivers fail to program RX filters (e.g. bnxt VF when PF is unavailable). All existing implementations return 0 for now, maintaining current behavior. Signed-off-by: Stanislav Fomichev Link: https://patch.msgid.link/20260608154014.227538-2-sdf@fomichev.me Signed-off-by: Jakub Kicinski --- drivers/infiniband/ulp/ipoib/ipoib_main.c | 9 +++++---- drivers/net/dummy.c | 7 ++++--- drivers/net/ethernet/broadcom/bnxt/bnxt.c | 10 ++++++---- drivers/net/ethernet/intel/iavf/iavf_main.c | 10 +++++++--- drivers/net/ethernet/mellanox/mlx5/core/en_main.c | 8 +++++--- drivers/net/ethernet/meta/fbnic/fbnic_netdev.c | 8 +++++--- drivers/net/netdevsim/netdev.c | 7 ++++--- drivers/net/netkit.c | 7 ++++--- include/linux/netdevice.h | 11 ++++++----- 9 files changed, 46 insertions(+), 31 deletions(-) (limited to 'include') diff --git a/drivers/infiniband/ulp/ipoib/ipoib_main.c b/drivers/infiniband/ulp/ipoib/ipoib_main.c index 3e1e1e861739..16a015b67206 100644 --- a/drivers/infiniband/ulp/ipoib/ipoib_main.c +++ b/drivers/infiniband/ulp/ipoib/ipoib_main.c @@ -1297,18 +1297,19 @@ static int ipoib_hard_header(struct sk_buff *skb, return IPOIB_HARD_LEN; } -static void ipoib_set_rx_mode_async(struct net_device *dev, - struct netdev_hw_addr_list *uc, - struct netdev_hw_addr_list *mc) +static int ipoib_set_rx_mode_async(struct net_device *dev, + struct netdev_hw_addr_list *uc, + struct netdev_hw_addr_list *mc) { struct ipoib_dev_priv *priv = ipoib_priv(dev); if (!test_bit(IPOIB_FLAG_OPER_UP, &priv->flags)) { ipoib_dbg(priv, "IPOIB_FLAG_OPER_UP not set"); - return; + return 0; } queue_work(priv->wq, &priv->restart_task); + return 0; } static int ipoib_get_iflink(const struct net_device *dev) diff --git a/drivers/net/dummy.c b/drivers/net/dummy.c index f6732eab5923..36a4e85c6668 100644 --- a/drivers/net/dummy.c +++ b/drivers/net/dummy.c @@ -47,10 +47,11 @@ static int numdummies = 1; /* fake multicast ability */ -static void set_multicast_list(struct net_device *dev, - struct netdev_hw_addr_list *uc, - struct netdev_hw_addr_list *mc) +static int set_multicast_list(struct net_device *dev, + struct netdev_hw_addr_list *uc, + struct netdev_hw_addr_list *mc) { + return 0; } static void dummy_get_stats64(struct net_device *dev, diff --git a/drivers/net/ethernet/broadcom/bnxt/bnxt.c b/drivers/net/ethernet/broadcom/bnxt/bnxt.c index dbaf9586ee22..f3c76ffaa1ca 100644 --- a/drivers/net/ethernet/broadcom/bnxt/bnxt.c +++ b/drivers/net/ethernet/broadcom/bnxt/bnxt.c @@ -13679,9 +13679,9 @@ static bool bnxt_uc_list_updated(struct bnxt *bp, return false; } -static void bnxt_set_rx_mode(struct net_device *dev, - struct netdev_hw_addr_list *uc, - struct netdev_hw_addr_list *mc) +static int bnxt_set_rx_mode(struct net_device *dev, + struct netdev_hw_addr_list *uc, + struct netdev_hw_addr_list *mc) { struct bnxt *bp = netdev_priv(dev); struct bnxt_vnic_info *vnic; @@ -13690,7 +13690,7 @@ static void bnxt_set_rx_mode(struct net_device *dev, u32 mask; if (!test_bit(BNXT_STATE_OPEN, &bp->state)) - return; + return 0; vnic = &bp->vnic_info[BNXT_VNIC_DEFAULT]; mask = vnic->rx_mask; @@ -13718,6 +13718,8 @@ static void bnxt_set_rx_mode(struct net_device *dev, bnxt_cfg_rx_mode(bp, uc, uc_update); } + + return 0; } static int bnxt_cfg_rx_mode(struct bnxt *bp, struct netdev_hw_addr_list *uc, diff --git a/drivers/net/ethernet/intel/iavf/iavf_main.c b/drivers/net/ethernet/intel/iavf/iavf_main.c index 8b53ffb75650..29b8403a066b 100644 --- a/drivers/net/ethernet/intel/iavf/iavf_main.c +++ b/drivers/net/ethernet/intel/iavf/iavf_main.c @@ -1134,10 +1134,12 @@ bool iavf_promiscuous_mode_changed(struct iavf_adapter *adapter) * @netdev: network interface device structure * @uc: snapshot of uc address list * @mc: snapshot of mc address list + * + * Return: 0 on success. **/ -static void iavf_set_rx_mode(struct net_device *netdev, - struct netdev_hw_addr_list *uc, - struct netdev_hw_addr_list *mc) +static int iavf_set_rx_mode(struct net_device *netdev, + struct netdev_hw_addr_list *uc, + struct netdev_hw_addr_list *mc) { struct iavf_adapter *adapter = netdev_priv(netdev); @@ -1150,6 +1152,8 @@ static void iavf_set_rx_mode(struct net_device *netdev, if (iavf_promiscuous_mode_changed(adapter)) adapter->aq_required |= IAVF_FLAG_AQ_CONFIGURE_PROMISC_MODE; spin_unlock_bh(&adapter->current_netdev_promisc_flags_lock); + + return 0; } /** diff --git a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c index c5d26c6829a0..775f0c6e55c9 100644 --- a/drivers/net/ethernet/mellanox/mlx5/core/en_main.c +++ b/drivers/net/ethernet/mellanox/mlx5/core/en_main.c @@ -4142,13 +4142,15 @@ static void mlx5e_nic_set_rx_mode(struct mlx5e_priv *priv) queue_work(priv->wq, &priv->set_rx_mode_work); } -static void mlx5e_set_rx_mode(struct net_device *dev, - struct netdev_hw_addr_list *uc, - struct netdev_hw_addr_list *mc) +static int mlx5e_set_rx_mode(struct net_device *dev, + struct netdev_hw_addr_list *uc, + struct netdev_hw_addr_list *mc) { struct mlx5e_priv *priv = netdev_priv(dev); mlx5e_fs_set_rx_mode_work(priv->fs, dev, uc, mc); + + return 0; } static int mlx5e_set_mac(struct net_device *netdev, void *addr) diff --git a/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c b/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c index f99ca551c1ce..dd77ab6052c8 100644 --- a/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c +++ b/drivers/net/ethernet/meta/fbnic/fbnic_netdev.c @@ -240,9 +240,9 @@ void __fbnic_set_rx_mode(struct fbnic_dev *fbd, fbnic_write_tce_tcam(fbd); } -static void fbnic_set_rx_mode(struct net_device *netdev, - struct netdev_hw_addr_list *uc, - struct netdev_hw_addr_list *mc) +static int fbnic_set_rx_mode(struct net_device *netdev, + struct netdev_hw_addr_list *uc, + struct netdev_hw_addr_list *mc) { struct fbnic_net *fbn = netdev_priv(netdev); struct fbnic_dev *fbd = fbn->fbd; @@ -250,6 +250,8 @@ static void fbnic_set_rx_mode(struct net_device *netdev, /* No need to update the hardware if we are not running */ if (netif_running(netdev)) __fbnic_set_rx_mode(fbd, uc, mc); + + return 0; } static int fbnic_set_mac(struct net_device *netdev, void *p) diff --git a/drivers/net/netdevsim/netdev.c b/drivers/net/netdevsim/netdev.c index a750768912b5..27e5f109f933 100644 --- a/drivers/net/netdevsim/netdev.c +++ b/drivers/net/netdevsim/netdev.c @@ -185,10 +185,11 @@ out_drop_cnt: return NETDEV_TX_OK; } -static void nsim_set_rx_mode(struct net_device *dev, - struct netdev_hw_addr_list *uc, - struct netdev_hw_addr_list *mc) +static int nsim_set_rx_mode(struct net_device *dev, + struct netdev_hw_addr_list *uc, + struct netdev_hw_addr_list *mc) { + return 0; } static int nsim_change_mtu(struct net_device *dev, int new_mtu) diff --git a/drivers/net/netkit.c b/drivers/net/netkit.c index 0ad6a806d7d5..a3931cd82132 100644 --- a/drivers/net/netkit.c +++ b/drivers/net/netkit.c @@ -186,11 +186,12 @@ static int netkit_get_iflink(const struct net_device *dev) return iflink; } -static void netkit_set_multicast(struct net_device *dev, - struct netdev_hw_addr_list *uc, - struct netdev_hw_addr_list *mc) +static int netkit_set_multicast(struct net_device *dev, + struct netdev_hw_addr_list *uc, + struct netdev_hw_addr_list *mc) { /* Nothing to do, we receive whatever gets pushed to us! */ + return 0; } static int netkit_set_macaddr(struct net_device *dev, void *sa) diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 9b876cd930d7..51ec2df02094 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -1122,13 +1122,14 @@ struct netdev_net_notifier { * Cannot sleep, called with netif_addr_lock_bh held. * Deprecated in favor of ndo_set_rx_mode_async. * - * void (*ndo_set_rx_mode_async)(struct net_device *dev, - * struct netdev_hw_addr_list *uc, - * struct netdev_hw_addr_list *mc); + * int (*ndo_set_rx_mode_async)(struct net_device *dev, + * struct netdev_hw_addr_list *uc, + * struct netdev_hw_addr_list *mc); * Async version of ndo_set_rx_mode which runs in process context * with rtnl_lock and netdev_lock_ops(dev) held. The uc/mc parameters * are snapshots of the address lists - iterate with - * netdev_hw_addr_list_for_each(ha, uc). + * netdev_hw_addr_list_for_each(ha, uc). Return 0 on success or a + * negative errno to request a retry via the core backoff. * * int (*ndo_set_mac_address)(struct net_device *dev, void *addr); * This function is called when the Media Access Control address @@ -1455,7 +1456,7 @@ struct net_device_ops { void (*ndo_change_rx_flags)(struct net_device *dev, int flags); void (*ndo_set_rx_mode)(struct net_device *dev); - void (*ndo_set_rx_mode_async)( + int (*ndo_set_rx_mode_async)( struct net_device *dev, struct netdev_hw_addr_list *uc, struct netdev_hw_addr_list *mc); -- cgit v1.2.3 From d90b85c23b3d64cc58a2bc59ceda6f6aa4df9ea3 Mon Sep 17 00:00:00 2001 From: Stanislav Fomichev Date: Mon, 8 Jun 2026 08:40:13 -0700 Subject: net: add retry mechanism to ndo_set_rx_mode_async When ndo_set_rx_mode_async returns an error, schedule a retry with exponential backoff (1s, 2s, 4s, 8s -- 15s total). Give up after the 4th retry and log an error via netdev_err(). This moves retry logic from individual drivers into the core stack. Timer callback does not hold a ref on dev. Safe because the timer can only be armed when dev is IFF_UP, and __dev_close_many runs timer_delete_sync before clearing IFF_UP. Unregister always closes IFF_UP devices first, so by the time dev can be freed the timer is dead and cannot be re-armed. Reviewed-by: Jakub Kicinski Signed-off-by: Stanislav Fomichev Link: https://patch.msgid.link/20260608154014.227538-3-sdf@fomichev.me Signed-off-by: Jakub Kicinski --- include/linux/netdevice.h | 5 +++++ net/core/dev.c | 4 ++-- net/core/dev.h | 2 ++ net/core/dev_addr_lists.c | 53 +++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 60 insertions(+), 4 deletions(-) (limited to 'include') diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 51ec2df02094..7f4f0837c09f 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -1933,6 +1933,8 @@ enum netdev_reg_state { * @rx_mode_node: List entry for rx_mode work processing * @rx_mode_tracker: Refcount tracker for rx_mode work * @rx_mode_addr_cache: Recycled snapshot entries for rx_mode work + * @rx_mode_retry_timer: Timer that re-queues rx_mode work after failure + * @rx_mode_retry_count: Number of consecutive retries already scheduled * @uc: unicast mac addresses * @mc: multicast mac addresses * @dev_addrs: list of device hw addresses @@ -2326,6 +2328,8 @@ struct net_device { struct list_head rx_mode_node; netdevice_tracker rx_mode_tracker; struct netdev_hw_addr_list rx_mode_addr_cache; + struct timer_list rx_mode_retry_timer; + unsigned int rx_mode_retry_count; #ifdef CONFIG_LOCKDEP unsigned char nested_level; #endif @@ -5152,6 +5156,7 @@ static inline void __dev_mc_unsync(struct net_device *dev, /* Functions used for secondary unicast and multicast support */ void dev_set_rx_mode(struct net_device *dev); +void netif_rx_mode_schedule_retry(struct net_device *dev); int netif_set_promiscuity(struct net_device *dev, int inc); int dev_set_promiscuity(struct net_device *dev, int inc); int netif_set_allmulti(struct net_device *dev, int inc, bool notify); diff --git a/net/core/dev.c b/net/core/dev.c index 1ecd5691992e..202e35acb15b 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -1775,6 +1775,7 @@ static void __dev_close_many(struct list_head *head) if (ops->ndo_stop) ops->ndo_stop(dev); + netif_rx_mode_cancel_retry(dev); netif_set_up(dev, false); netpoll_poll_enable(dev); } @@ -12094,8 +12095,7 @@ struct net_device *alloc_netdev_mqs(int sizeof_priv, const char *name, #endif mutex_init(&dev->lock); - INIT_LIST_HEAD(&dev->rx_mode_node); - __hw_addr_init(&dev->rx_mode_addr_cache); + netif_rx_mode_init(dev); dev->priv_flags = IFF_XMIT_DST_RELEASE | IFF_XMIT_DST_RELEASE_PERM; setup(dev); diff --git a/net/core/dev.h b/net/core/dev.h index 9e9431440869..4121c50e7c88 100644 --- a/net/core/dev.h +++ b/net/core/dev.h @@ -166,8 +166,10 @@ int dev_change_carrier(struct net_device *dev, bool new_carrier); void __dev_set_rx_mode(struct net_device *dev); int __dev_set_promiscuity(struct net_device *dev, int inc, bool notify); +void netif_rx_mode_init(struct net_device *dev); bool netif_rx_mode_clean(struct net_device *dev); void netif_rx_mode_sync(struct net_device *dev); +void netif_rx_mode_cancel_retry(struct net_device *dev); void __dev_notify_flags(struct net_device *dev, unsigned int old_flags, unsigned int gchanges, u32 portid, diff --git a/net/core/dev_addr_lists.c b/net/core/dev_addr_lists.c index 6b493af8dc8b..e17f64a65e17 100644 --- a/net/core/dev_addr_lists.c +++ b/net/core/dev_addr_lists.c @@ -1252,6 +1252,35 @@ static int netif_uc_promisc_update(struct net_device *dev) return 0; } +/* Total retry budget (4): 1+2+4+8 = 15 seconds */ +#define NETIF_RX_MODE_RETRY_MAX 4 + +void netif_rx_mode_schedule_retry(struct net_device *dev) +{ + unsigned long delay; + + netdev_assert_locked_ops_compat(dev); + + if (dev->rx_mode_retry_count >= NETIF_RX_MODE_RETRY_MAX) { + netdev_err(dev, "rx_mode retry limit reached, giving up\n"); + return; + } + + delay = HZ << dev->rx_mode_retry_count; + if (mod_timer(&dev->rx_mode_retry_timer, jiffies + delay)) + return; + if (!dev->rx_mode_retry_count) + netdev_info(dev, "rx_mode install failed, retrying with backoff\n"); + dev->rx_mode_retry_count++; +} +EXPORT_SYMBOL_GPL(netif_rx_mode_schedule_retry); + +void netif_rx_mode_cancel_retry(struct net_device *dev) +{ + timer_delete_sync(&dev->rx_mode_retry_timer); + dev->rx_mode_retry_count = 0; +} + static void netif_rx_mode_run(struct net_device *dev) { struct netdev_hw_addr_list uc_snap, mc_snap, uc_ref, mc_ref; @@ -1275,8 +1304,8 @@ static void netif_rx_mode_run(struct net_device *dev) err = netif_addr_lists_snapshot(dev, &uc_snap, &mc_snap, &uc_ref, &mc_ref); if (err) { - netdev_WARN(dev, "failed to sync uc/mc addresses\n"); netif_addr_unlock_bh(dev); + netif_rx_mode_schedule_retry(dev); return; } @@ -1292,12 +1321,17 @@ static void netif_rx_mode_run(struct net_device *dev) __dev_set_promiscuity(dev, promisc_inc, false); if (ops->ndo_set_rx_mode_async) { - ops->ndo_set_rx_mode_async(dev, &uc_snap, &mc_snap); + err = ops->ndo_set_rx_mode_async(dev, &uc_snap, &mc_snap); netif_addr_lock_bh(dev); netif_addr_lists_reconcile(dev, &uc_snap, &mc_snap, &uc_ref, &mc_ref); netif_addr_unlock_bh(dev); + + if (err) + netif_rx_mode_schedule_retry(dev); + else + dev->rx_mode_retry_count = 0; } else if (ops->ndo_set_rx_mode) { netif_addr_lock_bh(dev); ops->ndo_set_rx_mode(dev); @@ -1350,6 +1384,21 @@ static void netif_rx_mode_queue(struct net_device *dev) schedule_work(&rx_mode_work); } +static void netif_rx_mode_retry(struct timer_list *t) +{ + struct net_device *dev = + timer_container_of(dev, t, rx_mode_retry_timer); + + netif_rx_mode_queue(dev); +} + +void netif_rx_mode_init(struct net_device *dev) +{ + INIT_LIST_HEAD(&dev->rx_mode_node); + __hw_addr_init(&dev->rx_mode_addr_cache); + timer_setup(&dev->rx_mode_retry_timer, netif_rx_mode_retry, 0); +} + /** * __dev_set_rx_mode() - upload unicast and multicast address lists to device * and configure RX filtering. -- cgit v1.2.3 From 32b0b8953343eaceaa816a9ead3b6bb66355c64e Mon Sep 17 00:00:00 2001 From: Louis Scalbert Date: Wed, 3 Jun 2026 17:03:28 +0200 Subject: bonding: 3ad: add lacp_strict configuration knob When an 802.3ad (LACP) bonding interface has no slaves in the collecting/distributing state, the bonding master still reports carrier as up as long as at least 'min_links' slaves have carrier. In this situation, only one slave is effectively used for TX/RX, while traffic received on other slaves is dropped. Upper-layer daemons therefore consider the interface operational, even though traffic may be blackholed if the lack of LACP negotiation means the partner is not ready to deal with traffic. Introduce a configuration knob to control this behavior. It allows the bonding master to assert carrier only when at least 'min_links' slaves are in Collecting_Distributing state. The default mode preserves the existing behavior. This patch only introduces the knob; its behavior is implemented in the subsequent commit. Fixes: 655f8919d549 ("bonding: add min links parameter to 802.3ad") Signed-off-by: Louis Scalbert Acked-by: Jay Vosburgh Link: https://patch.msgid.link/20260603150331.1919611-4-louis.scalbert@6wind.com Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/rt-link.yaml | 3 +++ Documentation/networking/bonding.rst | 23 +++++++++++++++++++++++ drivers/net/bonding/bond_main.c | 1 + drivers/net/bonding/bond_netlink.c | 16 ++++++++++++++++ drivers/net/bonding/bond_options.c | 26 ++++++++++++++++++++++++++ include/net/bond_options.h | 1 + include/net/bonding.h | 1 + include/uapi/linux/if_link.h | 1 + tools/include/uapi/linux/if_link.h | 1 + 9 files changed, 73 insertions(+) (limited to 'include') diff --git a/Documentation/netlink/specs/rt-link.yaml b/Documentation/netlink/specs/rt-link.yaml index 694fd0a3a0c1..892979da098e 100644 --- a/Documentation/netlink/specs/rt-link.yaml +++ b/Documentation/netlink/specs/rt-link.yaml @@ -1356,6 +1356,9 @@ attribute-sets: - name: broadcast-neigh type: u8 + - + name: lacp-strict + type: u8 - name: bond-ad-info-attrs name-prefix: ifla-bond-ad-info- diff --git a/Documentation/networking/bonding.rst b/Documentation/networking/bonding.rst index e700bf1d095c..33ca5afafdf6 100644 --- a/Documentation/networking/bonding.rst +++ b/Documentation/networking/bonding.rst @@ -619,6 +619,29 @@ min_links aggregator cannot be active without at least one available link, setting this option to 0 or to 1 has the exact same effect. +lacp_strict + + Specifies the fallback behavior of a bonding when LACP negotiation + fails on all slave links, i.e. when no slave is in the + Collecting_Distributing state, while at least `min_links` link still + reports carrier up. + + This option is only applicable to 802.3ad mode (mode 4). + + Valid values are: + + off or 0 + One interface of the bond is selected to be active, in order to + facilitate communication with peer devices that do not implement + LACP. + + on or 1 + Interfaces are only permitted to be made active if they have an + active LACP partner and have successfully reached + Collecting_Distributing state. + + The default value is 0 (off). + mode Specifies one of the bonding policies. The default is diff --git a/drivers/net/bonding/bond_main.c b/drivers/net/bonding/bond_main.c index be50125f0635..e044fc733b8c 100644 --- a/drivers/net/bonding/bond_main.c +++ b/drivers/net/bonding/bond_main.c @@ -6457,6 +6457,7 @@ static int __init bond_check_params(struct bond_params *params) params->ad_user_port_key = ad_user_port_key; params->coupled_control = 1; params->broadcast_neighbor = 0; + params->lacp_strict = 0; if (packets_per_slave > 0) { params->reciprocal_packets_per_slave = reciprocal_value(packets_per_slave); diff --git a/drivers/net/bonding/bond_netlink.c b/drivers/net/bonding/bond_netlink.c index 90365d3f7ebf..4a11572f663d 100644 --- a/drivers/net/bonding/bond_netlink.c +++ b/drivers/net/bonding/bond_netlink.c @@ -143,6 +143,7 @@ static const struct nla_policy bond_policy[IFLA_BOND_MAX + 1] = { [IFLA_BOND_NS_IP6_TARGET] = { .type = NLA_NESTED }, [IFLA_BOND_COUPLED_CONTROL] = { .type = NLA_U8 }, [IFLA_BOND_BROADCAST_NEIGH] = { .type = NLA_U8 }, + [IFLA_BOND_LACP_STRICT] = { .type = NLA_U8 }, }; static const struct nla_policy bond_slave_policy[IFLA_BOND_SLAVE_MAX + 1] = { @@ -599,6 +600,16 @@ static int bond_changelink(struct net_device *bond_dev, struct nlattr *tb[], return err; } + if (data[IFLA_BOND_LACP_STRICT]) { + int fallback_mode = nla_get_u8(data[IFLA_BOND_LACP_STRICT]); + + bond_opt_initval(&newval, fallback_mode); + err = __bond_opt_set(bond, BOND_OPT_LACP_STRICT, &newval, + data[IFLA_BOND_LACP_STRICT], extack); + if (err) + return err; + } + return 0; } @@ -671,6 +682,7 @@ static size_t bond_get_size(const struct net_device *bond_dev) nla_total_size(sizeof(struct in6_addr)) * BOND_MAX_NS_TARGETS + nla_total_size(sizeof(u8)) + /* IFLA_BOND_COUPLED_CONTROL */ nla_total_size(sizeof(u8)) + /* IFLA_BOND_BROADCAST_NEIGH */ + nla_total_size(sizeof(u8)) + /* IFLA_BOND_LACP_STRICT */ 0; } @@ -838,6 +850,10 @@ static int bond_fill_info(struct sk_buff *skb, bond->params.broadcast_neighbor)) goto nla_put_failure; + if (nla_put_u8(skb, IFLA_BOND_LACP_STRICT, + bond->params.lacp_strict)) + goto nla_put_failure; + if (BOND_MODE(bond) == BOND_MODE_8023AD) { struct ad_info info; diff --git a/drivers/net/bonding/bond_options.c b/drivers/net/bonding/bond_options.c index 5095ac3dad2c..f01b3baa57b4 100644 --- a/drivers/net/bonding/bond_options.c +++ b/drivers/net/bonding/bond_options.c @@ -68,6 +68,8 @@ static int bond_option_lacp_active_set(struct bonding *bond, const struct bond_opt_value *newval); static int bond_option_lacp_rate_set(struct bonding *bond, const struct bond_opt_value *newval); +static int bond_option_lacp_strict_set(struct bonding *bond, + const struct bond_opt_value *newval); static int bond_option_ad_select_set(struct bonding *bond, const struct bond_opt_value *newval); static int bond_option_queue_id_set(struct bonding *bond, @@ -162,6 +164,12 @@ static const struct bond_opt_value bond_lacp_rate_tbl[] = { { NULL, -1, 0}, }; +static const struct bond_opt_value bond_lacp_strict_tbl[] = { + { "off", 0, BOND_VALFLAG_DEFAULT}, + { "on", 1, 0}, + { NULL, -1, 0 } +}; + static const struct bond_opt_value bond_ad_select_tbl[] = { { "stable", BOND_AD_STABLE, BOND_VALFLAG_DEFAULT}, { "bandwidth", BOND_AD_BANDWIDTH, 0}, @@ -363,6 +371,14 @@ static const struct bond_option bond_opts[BOND_OPT_LAST] = { .values = bond_lacp_rate_tbl, .set = bond_option_lacp_rate_set }, + [BOND_OPT_LACP_STRICT] = { + .id = BOND_OPT_LACP_STRICT, + .name = "lacp_strict", + .desc = "Define the LACP fallback mode when no slaves have negotiated", + .unsuppmodes = BOND_MODE_ALL_EX(BIT(BOND_MODE_8023AD)), + .values = bond_lacp_strict_tbl, + .set = bond_option_lacp_strict_set + }, [BOND_OPT_MINLINKS] = { .id = BOND_OPT_MINLINKS, .name = "min_links", @@ -1685,6 +1701,16 @@ static int bond_option_lacp_rate_set(struct bonding *bond, return 0; } +static int bond_option_lacp_strict_set(struct bonding *bond, + const struct bond_opt_value *newval) +{ + netdev_dbg(bond->dev, "Setting LACP fallback to %s (%llu)\n", + newval->string, newval->value); + bond->params.lacp_strict = newval->value; + + return 0; +} + static int bond_option_ad_select_set(struct bonding *bond, const struct bond_opt_value *newval) { diff --git a/include/net/bond_options.h b/include/net/bond_options.h index e6eedf23aea1..52b966e92793 100644 --- a/include/net/bond_options.h +++ b/include/net/bond_options.h @@ -79,6 +79,7 @@ enum { BOND_OPT_COUPLED_CONTROL, BOND_OPT_BROADCAST_NEIGH, BOND_OPT_ACTOR_PORT_PRIO, + BOND_OPT_LACP_STRICT, BOND_OPT_LAST }; diff --git a/include/net/bonding.h b/include/net/bonding.h index edd1942dcd73..2c54a36a8477 100644 --- a/include/net/bonding.h +++ b/include/net/bonding.h @@ -129,6 +129,7 @@ struct bond_params { int peer_notif_delay; int lacp_active; int lacp_fast; + int lacp_strict; unsigned int min_links; int ad_select; char primary[IFNAMSIZ]; diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h index 363526549a01..43cecca49f01 100644 --- a/include/uapi/linux/if_link.h +++ b/include/uapi/linux/if_link.h @@ -1603,6 +1603,7 @@ enum { IFLA_BOND_NS_IP6_TARGET, IFLA_BOND_COUPLED_CONTROL, IFLA_BOND_BROADCAST_NEIGH, + IFLA_BOND_LACP_STRICT, __IFLA_BOND_MAX, }; diff --git a/tools/include/uapi/linux/if_link.h b/tools/include/uapi/linux/if_link.h index 97a2d4411534..757ce5e9426e 100644 --- a/tools/include/uapi/linux/if_link.h +++ b/tools/include/uapi/linux/if_link.h @@ -1527,6 +1527,7 @@ enum { IFLA_BOND_NS_IP6_TARGET, IFLA_BOND_COUPLED_CONTROL, IFLA_BOND_BROADCAST_NEIGH, + IFLA_BOND_LACP_STRICT, __IFLA_BOND_MAX, }; -- cgit v1.2.3 From eb87bf6fcae6a2dc25d7f2c977d2cbca2113fb1e Mon Sep 17 00:00:00 2001 From: Luiz Augusto von Dentz Date: Tue, 26 May 2026 12:43:42 -0400 Subject: Bluetooth: hci_sync: Add support for HCI_LE_Set_Host_Feature [v2] This adds support for using HCI_LE_Set_Host_Feature [v2] instead of v1 if LL Extented Features is supported and the controller supports the command. Signed-off-by: Luiz Augusto von Dentz --- include/net/bluetooth/hci.h | 6 ++++++ net/bluetooth/hci_sync.c | 24 +++++++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h index 572b1c620c5d..5462f14ea0c6 100644 --- a/include/net/bluetooth/hci.h +++ b/include/net/bluetooth/hci.h @@ -2486,6 +2486,12 @@ struct hci_rp_le_cs_test { #define HCI_OP_LE_CS_TEST_END 0x2096 +#define HCI_OP_LE_SET_HOST_FEATURE_V2 0x2097 +struct hci_cp_le_set_host_feature_v2 { + __le16 bit_number; + __u8 bit_value; +} __packed; + /* ---- HCI Events ---- */ struct hci_ev_status { __u8 status; diff --git a/net/bluetooth/hci_sync.c b/net/bluetooth/hci_sync.c index df23245d6ccd..3be8c3581c6c 100644 --- a/net/bluetooth/hci_sync.c +++ b/net/bluetooth/hci_sync.c @@ -4588,11 +4588,33 @@ static int hci_set_le_support_sync(struct hci_dev *hdev) sizeof(cp), &cp, HCI_CMD_TIMEOUT); } +/* LE Set Host Feature V2 */ +static int hci_le_set_host_feature_v2_sync(struct hci_dev *hdev, u16 bit, + u8 value) +{ + struct hci_cp_le_set_host_feature_v2 cp; + + memset(&cp, 0, sizeof(cp)); + + /* Connected Isochronous Channels (Host Support) */ + cp.bit_number = cpu_to_le16(bit); + cp.bit_value = value; + + return __hci_cmd_sync_status(hdev, HCI_OP_LE_SET_HOST_FEATURE_V2, + sizeof(cp), &cp, HCI_CMD_TIMEOUT); +} + /* LE Set Host Feature */ -static int hci_le_set_host_feature_sync(struct hci_dev *hdev, u8 bit, u8 value) +static int hci_le_set_host_feature_sync(struct hci_dev *hdev, u16 bit, u8 value) { struct hci_cp_le_set_host_feature cp; + if (ll_ext_feature_capable(hdev) && hdev->commands[47] & BIT(4)) + return hci_le_set_host_feature_v2_sync(hdev, bit, value); + + if (bit > 255) + return 0; + memset(&cp, 0, sizeof(cp)); /* Connected Isochronous Channels (Host Support) */ -- cgit v1.2.3 From bea06c7c1b83bcd0519b91141999369eae6925bd Mon Sep 17 00:00:00 2001 From: Tim Bird Date: Thu, 4 Jun 2026 11:06:33 -0600 Subject: Bluetooth: Add SPDX id lines to some source files Many bluetooth source files are missing SPDX-License-Identifier lines. Add appropriate IDs to these files, and remove other license lines from the headers. Leave the warranty disclaimer in files where the license ID is GPL-2.0 but the wording of the disclaimer is slightly different from that of the GPL v2 disclaimer. It is not different enough to cause licensing conflicts, but is kept to honor the original contributors' legal intent. Signed-off-by: Tim Bird Signed-off-by: Luiz Augusto von Dentz --- drivers/bluetooth/btrsi.c | 12 +----------- include/net/bluetooth/bluetooth.h | 5 +---- include/net/bluetooth/hci.h | 5 +---- include/net/bluetooth/hci_core.h | 5 +---- include/net/bluetooth/hci_mon.h | 5 +---- include/net/bluetooth/hci_sock.h | 5 +---- include/net/bluetooth/l2cap.h | 5 +---- include/net/bluetooth/mgmt.h | 5 +---- include/net/bluetooth/rfcomm.h | 5 +---- include/net/bluetooth/sco.h | 5 +---- net/bluetooth/af_bluetooth.c | 5 +---- net/bluetooth/bnep/core.c | 5 +---- net/bluetooth/bnep/netdev.c | 5 +---- net/bluetooth/bnep/sock.c | 5 +---- net/bluetooth/ecdh_helper.c | 5 +---- net/bluetooth/ecdh_helper.h | 5 +---- net/bluetooth/hci_conn.c | 5 +---- net/bluetooth/hci_core.c | 5 +---- net/bluetooth/hci_debugfs.c | 5 +---- net/bluetooth/hci_debugfs.h | 5 +---- net/bluetooth/hci_event.c | 5 +---- net/bluetooth/hci_sock.c | 5 +---- net/bluetooth/hidp/core.c | 5 +---- net/bluetooth/hidp/hidp.h | 5 +---- net/bluetooth/hidp/sock.c | 5 +---- net/bluetooth/l2cap_core.c | 5 +---- net/bluetooth/l2cap_sock.c | 5 +---- net/bluetooth/lib.c | 5 +---- net/bluetooth/mgmt.c | 5 +---- net/bluetooth/mgmt_util.c | 5 +---- net/bluetooth/mgmt_util.h | 5 +---- net/bluetooth/rfcomm/core.c | 5 +---- net/bluetooth/rfcomm/sock.c | 5 +---- net/bluetooth/rfcomm/tty.c | 5 +---- net/bluetooth/sco.c | 5 +---- net/bluetooth/selftest.c | 5 +---- net/bluetooth/selftest.h | 5 +---- net/bluetooth/smp.c | 5 +---- net/bluetooth/smp.h | 5 +---- 39 files changed, 39 insertions(+), 163 deletions(-) (limited to 'include') diff --git a/drivers/bluetooth/btrsi.c b/drivers/bluetooth/btrsi.c index c68dd2fba01c..59ad0b9b14c3 100644 --- a/drivers/bluetooth/btrsi.c +++ b/drivers/bluetooth/btrsi.c @@ -1,17 +1,7 @@ +// SPDX-License-Identifier: ISC /* * Copyright (c) 2017 Redpine Signals Inc. * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include diff --git a/include/net/bluetooth/bluetooth.h b/include/net/bluetooth/bluetooth.h index 3faea66b1979..b624da5026f5 100644 --- a/include/net/bluetooth/bluetooth.h +++ b/include/net/bluetooth/bluetooth.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* BlueZ - Bluetooth protocol stack for Linux Copyright (C) 2000-2001 Qualcomm Incorporated @@ -5,10 +6,6 @@ Written 2000,2001 by Maxim Krasnyansky - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 as - published by the Free Software Foundation; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h index 5462f14ea0c6..38186a245f14 100644 --- a/include/net/bluetooth/hci.h +++ b/include/net/bluetooth/hci.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* BlueZ - Bluetooth protocol stack for Linux Copyright (C) 2000-2001 Qualcomm Incorporated @@ -5,10 +6,6 @@ Written 2000,2001 by Maxim Krasnyansky - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 as - published by the Free Software Foundation; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index aa600fbf9a53..7e15da47fe3a 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* BlueZ - Bluetooth protocol stack for Linux Copyright (c) 2000-2001, 2010, Code Aurora Forum. All rights reserved. @@ -5,10 +6,6 @@ Written 2000,2001 by Maxim Krasnyansky - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 as - published by the Free Software Foundation; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. diff --git a/include/net/bluetooth/hci_mon.h b/include/net/bluetooth/hci_mon.h index bbd752494ef9..4b2a0af4ed58 100644 --- a/include/net/bluetooth/hci_mon.h +++ b/include/net/bluetooth/hci_mon.h @@ -1,12 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* BlueZ - Bluetooth protocol stack for Linux Copyright (C) 2011-2012 Intel Corporation - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 as - published by the Free Software Foundation; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. diff --git a/include/net/bluetooth/hci_sock.h b/include/net/bluetooth/hci_sock.h index 13e8cd4414a1..16f150b861be 100644 --- a/include/net/bluetooth/hci_sock.h +++ b/include/net/bluetooth/hci_sock.h @@ -1,13 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* BlueZ - Bluetooth protocol stack for Linux Copyright (C) 2000-2001 Qualcomm Incorporated Written 2000,2001 by Maxim Krasnyansky - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 as - published by the Free Software Foundation; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h index e0a1f2293679..790935950a0c 100644 --- a/include/net/bluetooth/l2cap.h +++ b/include/net/bluetooth/l2cap.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* BlueZ - Bluetooth protocol stack for Linux Copyright (C) 2000-2001 Qualcomm Incorporated @@ -6,10 +7,6 @@ Written 2000,2001 by Maxim Krasnyansky - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 as - published by the Free Software Foundation; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. diff --git a/include/net/bluetooth/mgmt.h b/include/net/bluetooth/mgmt.h index 8234915854b6..08daed7a96d5 100644 --- a/include/net/bluetooth/mgmt.h +++ b/include/net/bluetooth/mgmt.h @@ -1,13 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* BlueZ - Bluetooth protocol stack for Linux Copyright (C) 2010 Nokia Corporation Copyright (C) 2011-2012 Intel Corporation - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 as - published by the Free Software Foundation; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. diff --git a/include/net/bluetooth/rfcomm.h b/include/net/bluetooth/rfcomm.h index c05882476900..feb6b3ae5e69 100644 --- a/include/net/bluetooth/rfcomm.h +++ b/include/net/bluetooth/rfcomm.h @@ -1,12 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* RFCOMM implementation for Linux Bluetooth stack (BlueZ) Copyright (C) 2002 Maxim Krasnyansky Copyright (C) 2002 Marcel Holtmann - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 as - published by the Free Software Foundation; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. diff --git a/include/net/bluetooth/sco.h b/include/net/bluetooth/sco.h index f40ddb4264fc..214409527b35 100644 --- a/include/net/bluetooth/sco.h +++ b/include/net/bluetooth/sco.h @@ -1,13 +1,10 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* BlueZ - Bluetooth protocol stack for Linux Copyright (C) 2000-2001 Qualcomm Incorporated Written 2000,2001 by Maxim Krasnyansky - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 as - published by the Free Software Foundation; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. diff --git a/net/bluetooth/af_bluetooth.c b/net/bluetooth/af_bluetooth.c index 1a6aa3f8d4d6..bcbc11c9cb15 100644 --- a/net/bluetooth/af_bluetooth.c +++ b/net/bluetooth/af_bluetooth.c @@ -1,13 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0 /* BlueZ - Bluetooth protocol stack for Linux Copyright (C) 2000-2001 Qualcomm Incorporated Written 2000,2001 by Maxim Krasnyansky - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 as - published by the Free Software Foundation; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. diff --git a/net/bluetooth/bnep/core.c b/net/bluetooth/bnep/core.c index 5c5f53ff30e8..add9a8f7535d 100644 --- a/net/bluetooth/bnep/core.c +++ b/net/bluetooth/bnep/core.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* BNEP implementation for Linux Bluetooth stack (BlueZ). Copyright (C) 2001-2002 Inventel Systemes @@ -7,10 +8,6 @@ Copyright (C) 2002 Maxim Krasnyansky - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 as - published by the Free Software Foundation; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. diff --git a/net/bluetooth/bnep/netdev.c b/net/bluetooth/bnep/netdev.c index cc1cff63194f..ee1e39a3daff 100644 --- a/net/bluetooth/bnep/netdev.c +++ b/net/bluetooth/bnep/netdev.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* BNEP implementation for Linux Bluetooth stack (BlueZ). Copyright (C) 2001-2002 Inventel Systemes @@ -7,10 +8,6 @@ Copyright (C) 2002 Maxim Krasnyansky - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 as - published by the Free Software Foundation; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. diff --git a/net/bluetooth/bnep/sock.c b/net/bluetooth/bnep/sock.c index 00d47bcf4d7d..f3fe818d0e4c 100644 --- a/net/bluetooth/bnep/sock.c +++ b/net/bluetooth/bnep/sock.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* BNEP implementation for Linux Bluetooth stack (BlueZ). Copyright (C) 2001-2002 Inventel Systemes @@ -6,10 +7,6 @@ Copyright (C) 2002 Maxim Krasnyansky - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 as - published by the Free Software Foundation; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. diff --git a/net/bluetooth/ecdh_helper.c b/net/bluetooth/ecdh_helper.c index 0efc93fdae8a..1938e5559d88 100644 --- a/net/bluetooth/ecdh_helper.c +++ b/net/bluetooth/ecdh_helper.c @@ -1,12 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 /* * ECDH helper functions - KPP wrappings * * Copyright (C) 2017 Intel Corporation * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation; - * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. diff --git a/net/bluetooth/ecdh_helper.h b/net/bluetooth/ecdh_helper.h index 830723971cf8..9d787d3c8797 100644 --- a/net/bluetooth/ecdh_helper.h +++ b/net/bluetooth/ecdh_helper.h @@ -1,12 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* * ECDH helper functions - KPP wrappings * * Copyright (C) 2017 Intel Corporation * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation; - * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index 54eabaa46960..c335372e4062 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* BlueZ - Bluetooth protocol stack for Linux Copyright (c) 2000-2001, 2010, Code Aurora Forum. All rights reserved. @@ -5,10 +6,6 @@ Written 2000,2001 by Maxim Krasnyansky - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 as - published by the Free Software Foundation; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c index 1cbc666527c5..5ba9fe8261ec 100644 --- a/net/bluetooth/hci_core.c +++ b/net/bluetooth/hci_core.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* BlueZ - Bluetooth protocol stack for Linux Copyright (C) 2000-2001 Qualcomm Incorporated @@ -5,10 +6,6 @@ Written 2000,2001 by Maxim Krasnyansky - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 as - published by the Free Software Foundation; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. diff --git a/net/bluetooth/hci_debugfs.c b/net/bluetooth/hci_debugfs.c index 99e2e9fc70e8..0635e4641db4 100644 --- a/net/bluetooth/hci_debugfs.c +++ b/net/bluetooth/hci_debugfs.c @@ -1,12 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 /* BlueZ - Bluetooth protocol stack for Linux Copyright (C) 2014 Intel Corporation - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 as - published by the Free Software Foundation; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. diff --git a/net/bluetooth/hci_debugfs.h b/net/bluetooth/hci_debugfs.h index 9a8a7c93bb12..92365cbc922a 100644 --- a/net/bluetooth/hci_debugfs.h +++ b/net/bluetooth/hci_debugfs.h @@ -1,11 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* BlueZ - Bluetooth protocol stack for Linux Copyright (C) 2014 Intel Corporation - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 as - published by the Free Software Foundation; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index 1cd5f97daafe..b6d963ce26d0 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* BlueZ - Bluetooth protocol stack for Linux Copyright (c) 2000-2001, 2010, Code Aurora Forum. All rights reserved. @@ -5,10 +6,6 @@ Written 2000,2001 by Maxim Krasnyansky - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 as - published by the Free Software Foundation; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. diff --git a/net/bluetooth/hci_sock.c b/net/bluetooth/hci_sock.c index 61fec674a207..070ca388f9ac 100644 --- a/net/bluetooth/hci_sock.c +++ b/net/bluetooth/hci_sock.c @@ -1,13 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0 /* BlueZ - Bluetooth protocol stack for Linux Copyright (C) 2000-2001 Qualcomm Incorporated Written 2000,2001 by Maxim Krasnyansky - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 as - published by the Free Software Foundation; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. diff --git a/net/bluetooth/hidp/core.c b/net/bluetooth/hidp/core.c index 70344bd3248a..0e24c5e2955e 100644 --- a/net/bluetooth/hidp/core.c +++ b/net/bluetooth/hidp/core.c @@ -1,12 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 /* HIDP implementation for Linux Bluetooth stack (BlueZ). Copyright (C) 2003-2004 Marcel Holtmann Copyright (C) 2013 David Herrmann - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 as - published by the Free Software Foundation; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. diff --git a/net/bluetooth/hidp/hidp.h b/net/bluetooth/hidp/hidp.h index 6ef88d0a1919..959b745bb770 100644 --- a/net/bluetooth/hidp/hidp.h +++ b/net/bluetooth/hidp/hidp.h @@ -1,11 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* HIDP implementation for Linux Bluetooth stack (BlueZ). Copyright (C) 2003-2004 Marcel Holtmann - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 as - published by the Free Software Foundation; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. diff --git a/net/bluetooth/hidp/sock.c b/net/bluetooth/hidp/sock.c index c93aaeb3a3fa..b75bea3cf422 100644 --- a/net/bluetooth/hidp/sock.c +++ b/net/bluetooth/hidp/sock.c @@ -1,11 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0 /* HIDP implementation for Linux Bluetooth stack (BlueZ). Copyright (C) 2003-2004 Marcel Holtmann - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 as - published by the Free Software Foundation; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index c4ccfbda9d78..863fc4b8a55e 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* BlueZ - Bluetooth protocol stack for Linux Copyright (C) 2000-2001 Qualcomm Incorporated @@ -8,10 +9,6 @@ Written 2000,2001 by Maxim Krasnyansky - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 as - published by the Free Software Foundation; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c index 025329636353..4853f1b33449 100644 --- a/net/bluetooth/l2cap_sock.c +++ b/net/bluetooth/l2cap_sock.c @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: GPL-2.0 /* BlueZ - Bluetooth protocol stack for Linux Copyright (C) 2000-2001 Qualcomm Incorporated @@ -7,10 +8,6 @@ Written 2000,2001 by Maxim Krasnyansky - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 as - published by the Free Software Foundation; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. diff --git a/net/bluetooth/lib.c b/net/bluetooth/lib.c index 305044a84478..cea21a4c49a0 100644 --- a/net/bluetooth/lib.c +++ b/net/bluetooth/lib.c @@ -1,13 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0 /* BlueZ - Bluetooth protocol stack for Linux Copyright (C) 2000-2001 Qualcomm Incorporated Written 2000,2001 by Maxim Krasnyansky - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 as - published by the Free Software Foundation; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c index f4aa814a0397..d23ca1dd0893 100644 --- a/net/bluetooth/mgmt.c +++ b/net/bluetooth/mgmt.c @@ -1,13 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0 /* BlueZ - Bluetooth protocol stack for Linux Copyright (C) 2010 Nokia Corporation Copyright (C) 2011-2012 Intel Corporation - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 as - published by the Free Software Foundation; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. diff --git a/net/bluetooth/mgmt_util.c b/net/bluetooth/mgmt_util.c index 4f19654d41a9..6ea107c0e054 100644 --- a/net/bluetooth/mgmt_util.c +++ b/net/bluetooth/mgmt_util.c @@ -1,12 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 /* BlueZ - Bluetooth protocol stack for Linux Copyright (C) 2015 Intel Corporation - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 as - published by the Free Software Foundation; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. diff --git a/net/bluetooth/mgmt_util.h b/net/bluetooth/mgmt_util.h index bcba8c9d8952..20810cf06e81 100644 --- a/net/bluetooth/mgmt_util.h +++ b/net/bluetooth/mgmt_util.h @@ -1,11 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* BlueZ - Bluetooth protocol stack for Linux Copyright (C) 2015 Intel Coropration - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 as - published by the Free Software Foundation; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. diff --git a/net/bluetooth/rfcomm/core.c b/net/bluetooth/rfcomm/core.c index 364b9381c2dc..ebeae17b71d1 100644 --- a/net/bluetooth/rfcomm/core.c +++ b/net/bluetooth/rfcomm/core.c @@ -1,12 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 /* RFCOMM implementation for Linux Bluetooth stack (BlueZ). Copyright (C) 2002 Maxim Krasnyansky Copyright (C) 2002 Marcel Holtmann - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 as - published by the Free Software Foundation; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. diff --git a/net/bluetooth/rfcomm/sock.c b/net/bluetooth/rfcomm/sock.c index ed31d5f4fc76..feb302a491fa 100644 --- a/net/bluetooth/rfcomm/sock.c +++ b/net/bluetooth/rfcomm/sock.c @@ -1,12 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 /* RFCOMM implementation for Linux Bluetooth stack (BlueZ). Copyright (C) 2002 Maxim Krasnyansky Copyright (C) 2002 Marcel Holtmann - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 as - published by the Free Software Foundation; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. diff --git a/net/bluetooth/rfcomm/tty.c b/net/bluetooth/rfcomm/tty.c index 91bf5274262e..4b9a699ec59b 100644 --- a/net/bluetooth/rfcomm/tty.c +++ b/net/bluetooth/rfcomm/tty.c @@ -1,12 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 /* RFCOMM implementation for Linux Bluetooth stack (BlueZ). Copyright (C) 2002 Maxim Krasnyansky Copyright (C) 2002 Marcel Holtmann - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 as - published by the Free Software Foundation; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. diff --git a/net/bluetooth/sco.c b/net/bluetooth/sco.c index 451ed32bee76..fcc597be5bbd 100644 --- a/net/bluetooth/sco.c +++ b/net/bluetooth/sco.c @@ -1,13 +1,10 @@ +// SPDX-License-Identifier: GPL-2.0 /* BlueZ - Bluetooth protocol stack for Linux Copyright (C) 2000-2001 Qualcomm Incorporated Written 2000,2001 by Maxim Krasnyansky - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 as - published by the Free Software Foundation; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. diff --git a/net/bluetooth/selftest.c b/net/bluetooth/selftest.c index f49604d44b87..ae5b44bb9d3d 100644 --- a/net/bluetooth/selftest.c +++ b/net/bluetooth/selftest.c @@ -1,12 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 /* BlueZ - Bluetooth protocol stack for Linux Copyright (C) 2014 Intel Corporation - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 as - published by the Free Software Foundation; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. diff --git a/net/bluetooth/selftest.h b/net/bluetooth/selftest.h index 2aa0a346a913..34d684ee84fd 100644 --- a/net/bluetooth/selftest.h +++ b/net/bluetooth/selftest.h @@ -1,11 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* BlueZ - Bluetooth protocol stack for Linux Copyright (C) 2014 Intel Corporation - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 as - published by the Free Software Foundation; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. diff --git a/net/bluetooth/smp.c b/net/bluetooth/smp.c index 1739c1989dbd..031d3022cb1e 100644 --- a/net/bluetooth/smp.c +++ b/net/bluetooth/smp.c @@ -1,11 +1,8 @@ +// SPDX-License-Identifier: GPL-2.0 /* BlueZ - Bluetooth protocol stack for Linux Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 as - published by the Free Software Foundation; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. diff --git a/net/bluetooth/smp.h b/net/bluetooth/smp.h index c5da53dfab04..eac27bd541bb 100644 --- a/net/bluetooth/smp.h +++ b/net/bluetooth/smp.h @@ -1,11 +1,8 @@ +/* SPDX-License-Identifier: GPL-2.0 */ /* BlueZ - Bluetooth protocol stack for Linux Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 as - published by the Free Software Foundation; - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. -- cgit v1.2.3 From b66774b48dd98f07254951f74ea6f513efe7ff8b Mon Sep 17 00:00:00 2001 From: Marco Elver Date: Fri, 5 Jun 2026 16:23:35 +0200 Subject: Bluetooth: L2CAP: Fix UAF in channel timeout by holding conn ref l2cap_chan_timeout() runs asynchronously and accesses chan->conn. If the connection is torn down while the timer is running or pending, chan->conn can be freed, leading to a use-after-free when the timer worker attempts to lock conn->lock: | BUG: KASAN: slab-use-after-free in instrument_atomic_read_write include/linux/instrumented.h:112 [inline] | BUG: KASAN: slab-use-after-free in atomic_long_try_cmpxchg_acquire include/linux/atomic/atomic-instrumented.h:4456 [inline] | BUG: KASAN: slab-use-after-free in __mutex_trylock_fast kernel/locking/mutex.c:161 [inline] | BUG: KASAN: slab-use-after-free in mutex_lock+0x4f/0xa0 kernel/locking/mutex.c:318 | Write of size 8 at addr ffff8881298d9550 by task kworker/2:1/83 | | CPU: 2 UID: 0 PID: 83 Comm: kworker/2:1 Not tainted 7.1.0-rc6-next-20260601-dirty #6 PREEMPT(full) | Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.17.0-debian-1.17.0-1 04/01/2014 | Workqueue: events l2cap_chan_timeout | Call Trace: | | instrument_atomic_read_write include/linux/instrumented.h:112 [inline] | atomic_long_try_cmpxchg_acquire include/linux/atomic/atomic-instrumented.h:4456 [inline] | __mutex_trylock_fast kernel/locking/mutex.c:161 [inline] | mutex_lock+0x4f/0xa0 kernel/locking/mutex.c:318 | l2cap_chan_timeout+0x5d/0x1b0 net/bluetooth/l2cap_core.c:422 | process_one_work kernel/workqueue.c:3326 [inline] | process_scheduled_works+0x7c8/0xfb0 kernel/workqueue.c:3409 | worker_thread+0x8a9/0xcf0 kernel/workqueue.c:3490 | kthread+0x346/0x430 kernel/kthread.c:436 | ret_from_fork+0x1a3/0x470 arch/x86/kernel/process.c:158 | ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245 | | | Allocated by task 320: | l2cap_conn_add+0xa7/0x820 net/bluetooth/l2cap_core.c:7075 | l2cap_connect_cfm+0xdb/0xd70 net/bluetooth/l2cap_core.c:7452 | hci_connect_cfm include/net/bluetooth/hci_core.h:2139 [inline] | hci_remote_features_evt+0x52f/0x9f0 net/bluetooth/hci_event.c:3760 | hci_event_func net/bluetooth/hci_event.c:7796 [inline] | hci_event_packet+0x561/0xa70 net/bluetooth/hci_event.c:7847 | hci_rx_work+0x370/0x890 net/bluetooth/hci_core.c:4040 | process_one_work kernel/workqueue.c:3326 [inline] | process_scheduled_works+0x7c8/0xfb0 kernel/workqueue.c:3409 | worker_thread+0x8a9/0xcf0 kernel/workqueue.c:3490 | kthread+0x346/0x430 kernel/kthread.c:436 | ret_from_fork+0x1a3/0x470 arch/x86/kernel/process.c:158 | ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245 | | Freed by task 322: | hci_disconn_cfm include/net/bluetooth/hci_core.h:2154 [inline] | hci_conn_hash_flush+0x101/0x1f0 net/bluetooth/hci_conn.c:2736 | hci_dev_close_sync+0x889/0xde0 net/bluetooth/hci_sync.c:5405 | hci_dev_do_close net/bluetooth/hci_core.c:502 [inline] | hci_unregister_dev+0x1f7/0x370 net/bluetooth/hci_core.c:2679 | vhci_release+0x12a/0x180 drivers/bluetooth/hci_vhci.c:690 | __fput+0x369/0x890 fs/file_table.c:510 | task_work_run+0x160/0x1d0 kernel/task_work.c:233 | get_signal+0xf5b/0x1120 kernel/signal.c:2810 | arch_do_signal_or_restart+0x4d/0x600 arch/x86/kernel/signal.c:337 | __exit_to_user_mode_loop kernel/entry/common.c:64 [inline] | exit_to_user_mode_loop+0x85/0x510 kernel/entry/common.c:98 | do_syscall_64+0x263/0x3d0 arch/x86/entry/syscall_64.c:100 | entry_SYSCALL_64_after_hwframe+0x77/0x7f | | The buggy address belongs to the object at ffff8881298d9400 | which belongs to the cache kmalloc-512 of size 512 | The buggy address is located 336 bytes inside of | freed 512-byte region [ffff8881298d9400, ffff8881298d9600) Fix it by having chan->conn hold a reference to l2cap_conn (via l2cap_conn_get) when the channel is added to the connection, and releasing it in the channel destructor. This ensures the l2cap_conn remains alive as long as the channel exists. A new FLAG_DEL channel flag is introduced to indicate that the channel has been deleted from its connection. l2cap_chan_del() atomically sets this flag using test_and_set_bit() instead of setting chan->conn to NULL. All asynchronous workers (l2cap_chan_timeout, l2cap_ack_timeout, l2cap_monitor_timeout, l2cap_retrans_timeout) and l2cap_chan_send() check FLAG_DEL to determine whether the channel has been torn down, rather than testing chan->conn for NULL. Fixes: 8c8e620467a7 ("Bluetooth: L2CAP: use chan timer to close channels in cleanup_listen()") Cc: Cc: Siwei Zhang Cc: Luiz Augusto von Dentz Assisted-by: Gemini:gemini-3.1-pro-preview Reported-by: https://sashiko.dev/#/patchset/20260521021249.3258069-1-oss%40fourdim.xyz Signed-off-by: Marco Elver Signed-off-by: Luiz Augusto von Dentz --- include/net/bluetooth/l2cap.h | 1 + net/bluetooth/l2cap_core.c | 34 ++++++++++++++++++++-------------- 2 files changed, 21 insertions(+), 14 deletions(-) (limited to 'include') diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h index 790935950a0c..1640cc9bf83a 100644 --- a/include/net/bluetooth/l2cap.h +++ b/include/net/bluetooth/l2cap.h @@ -745,6 +745,7 @@ enum { FLAG_ECRED_CONN_REQ_SENT, FLAG_PENDING_SECURITY, FLAG_HOLD_HCI_CONN, + FLAG_DEL, }; /* Lock nesting levels for L2CAP channels. We need these because lockdep diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 863fc4b8a55e..a97d492473e2 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -408,7 +408,7 @@ static void l2cap_chan_timeout(struct work_struct *work) BT_DBG("chan %p state %s", chan, state_to_string(chan->state)); - if (!conn) { + if (test_bit(FLAG_DEL, &chan->flags)) { l2cap_chan_put(chan); return; } @@ -419,6 +419,9 @@ static void l2cap_chan_timeout(struct work_struct *work) */ l2cap_chan_lock(chan); + if (test_bit(FLAG_DEL, &chan->flags)) + goto unlock; + if (chan->state == BT_CONNECTED || chan->state == BT_CONFIG) reason = ECONNREFUSED; else if (chan->state == BT_CONNECT && @@ -431,10 +434,10 @@ static void l2cap_chan_timeout(struct work_struct *work) chan->ops->close(chan); +unlock: l2cap_chan_unlock(chan); - l2cap_chan_put(chan); - mutex_unlock(&conn->lock); + l2cap_chan_put(chan); } struct l2cap_chan *l2cap_chan_create(void) @@ -487,6 +490,9 @@ static void l2cap_chan_destroy(struct kref *kref) list_del(&chan->global_l); write_unlock(&chan_list_lock); + if (chan->conn) + l2cap_conn_put(chan->conn); + kfree(chan); } @@ -590,7 +596,7 @@ void __l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan) conn->disc_reason = HCI_ERROR_REMOTE_USER_TERM; - chan->conn = conn; + chan->conn = l2cap_conn_get(conn); switch (chan->chan_type) { case L2CAP_CHAN_CONN_ORIENTED: @@ -645,30 +651,26 @@ void l2cap_chan_add(struct l2cap_conn *conn, struct l2cap_chan *chan) void l2cap_chan_del(struct l2cap_chan *chan, int err) { - struct l2cap_conn *conn = chan->conn; - __clear_chan_timer(chan); - BT_DBG("chan %p, conn %p, err %d, state %s", chan, conn, err, + BT_DBG("chan %p, err %d, state %s", chan, err, state_to_string(chan->state)); chan->ops->teardown(chan, err); - if (conn) { + if (!test_and_set_bit(FLAG_DEL, &chan->flags)) { /* Delete from channel list */ list_del(&chan->list); l2cap_chan_put(chan); - chan->conn = NULL; - /* Reference was only held for non-fixed channels or * fixed channels that explicitly requested it using the * FLAG_HOLD_HCI_CONN flag. */ if (chan->chan_type != L2CAP_CHAN_FIXED || test_bit(FLAG_HOLD_HCI_CONN, &chan->flags)) - hci_conn_drop(conn->hcon); + hci_conn_drop(chan->conn->hcon); } if (test_bit(CONF_NOT_COMPLETE, &chan->conf_state)) @@ -1900,7 +1902,7 @@ static void l2cap_monitor_timeout(struct work_struct *work) l2cap_chan_lock(chan); - if (!chan->conn) { + if (test_bit(FLAG_DEL, &chan->flags)) { l2cap_chan_unlock(chan); l2cap_chan_put(chan); return; @@ -1921,7 +1923,7 @@ static void l2cap_retrans_timeout(struct work_struct *work) l2cap_chan_lock(chan); - if (!chan->conn) { + if (test_bit(FLAG_DEL, &chan->flags)) { l2cap_chan_unlock(chan); l2cap_chan_put(chan); return; @@ -2562,7 +2564,7 @@ int l2cap_chan_send(struct l2cap_chan *chan, struct msghdr *msg, size_t len, int err; struct sk_buff_head seg_queue; - if (!chan->conn) + if (test_bit(FLAG_DEL, &chan->flags)) return -ENOTCONN; /* Connectionless channel */ @@ -3157,12 +3159,16 @@ static void l2cap_ack_timeout(struct work_struct *work) l2cap_chan_lock(chan); + if (test_bit(FLAG_DEL, &chan->flags)) + goto unlock; + frames_to_ack = __seq_offset(chan, chan->buffer_seq, chan->last_acked_seq); if (frames_to_ack) l2cap_send_rr_or_rnr(chan, 0); +unlock: l2cap_chan_unlock(chan); l2cap_chan_put(chan); } -- cgit v1.2.3 From 06b693d2eb6651a63ad85bad8673de3b7d4edd6d Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Wed, 10 Jun 2026 06:17:18 +0000 Subject: ipv4: fib: Don't dump dying fib_info in fib_leaf_notify(). syzbot reported use-after-free in nsim_fib4_prepare_event(). [0] The problem is that the following functions call fib_info_hold() / refcount_inc() while dumping fib_info under RCU, which is unsafe. * mlxsw_sp_router_fib4_event() * rocker_router_fib_event() * nsim_fib4_prepare_event() refcount_inc_not_zero() must be used, but it would be too late there. Let's guarantee the lifetime of fib_info in fib_leaf_notify(). Note that IPv6 does not need the corresponding change since fib6_table_dump() holds fib6_table.tb6_lock. [0]: refcount_t: addition on 0; use-after-free. WARNING: lib/refcount.c:25 at refcount_warn_saturate+0x9f/0x110 lib/refcount.c:25, CPU#0: kworker/u8:15/3420 Modules linked in: CPU: 0 UID: 0 PID: 3420 Comm: kworker/u8:15 Not tainted syzkaller #0 PREEMPT_{RT,(full)} Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 04/18/2026 Workqueue: netns cleanup_net RIP: 0010:refcount_warn_saturate+0x9f/0x110 lib/refcount.c:25 Code: eb 66 85 db 74 3e 83 fb 01 75 4c e8 1b f1 22 fd 48 8d 3d 84 cb f1 0a 67 48 0f b9 3a eb 4a e8 08 f1 22 fd 48 8d 3d 81 cb f1 0a <67> 48 0f b9 3a eb 37 e8 f5 f0 22 fd 48 8d 3d 7e cb f1 0a 67 48 0f RSP: 0018:ffffc9000f2c7270 EFLAGS: 00010293 RAX: ffffffff84a18858 RBX: 0000000000000002 RCX: ffff888032ff9ec0 RDX: 0000000000000000 RSI: 0000000000000000 RDI: ffffffff8f9353e0 RBP: 0000000000000000 R08: ffff888032ff9ec0 R09: 0000000000000005 R10: 0000000000000100 R11: 0000000000000004 R12: ffff8880570cc000 R13: dffffc0000000000 R14: ffff88802b40563c R15: ffff8880570cc000 FS: 0000000000000000(0000) GS:ffff888126173000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007fb1f4d5d000 CR3: 000000006072a000 CR4: 00000000003526f0 Call Trace: __refcount_add include/linux/refcount.h:-1 [inline] __refcount_inc include/linux/refcount.h:366 [inline] refcount_inc include/linux/refcount.h:383 [inline] fib_info_hold include/net/ip_fib.h:629 [inline] nsim_fib4_prepare_event drivers/net/netdevsim/fib.c:930 [inline] nsim_fib_event_schedule_work drivers/net/netdevsim/fib.c:1000 [inline] nsim_fib_event_nb+0x1055/0x1240 drivers/net/netdevsim/fib.c:1043 call_fib_notifier+0x45/0x80 net/core/fib_notifier.c:25 call_fib_entry_notifier net/ipv4/fib_trie.c:90 [inline] fib_leaf_notify net/ipv4/fib_trie.c:2176 [inline] fib_table_notify net/ipv4/fib_trie.c:2194 [inline] fib_notify+0x36b/0x5e0 net/ipv4/fib_trie.c:2217 fib_net_dump net/core/fib_notifier.c:70 [inline] register_fib_notifier+0x184/0x360 net/core/fib_notifier.c:108 nsim_fib_create+0x85d/0x9f0 drivers/net/netdevsim/fib.c:1596 nsim_dev_reload_create drivers/net/netdevsim/dev.c:1604 [inline] nsim_dev_reload_up+0x374/0x7c0 drivers/net/netdevsim/dev.c:1058 devlink_reload+0x501/0x8d0 net/devlink/dev.c:475 devlink_pernet_pre_exit+0x1ff/0x420 net/devlink/core.c:558 ops_pre_exit_list net/core/net_namespace.c:161 [inline] ops_undo_list+0x187/0x940 net/core/net_namespace.c:234 cleanup_net+0x56e/0x800 net/core/net_namespace.c:702 process_one_work kernel/workqueue.c:3314 [inline] process_scheduled_works+0xb5d/0x1860 kernel/workqueue.c:3397 worker_thread+0xa53/0xfc0 kernel/workqueue.c:3478 kthread+0x388/0x470 kernel/kthread.c:436 ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245 Fixes: 0ae3eb7b4611 ("netdevsim: fib: Perform the route programming in a non-atomic context") Fixes: c3852ef7f2f8 ("ipv4: fib: Replay events when registering FIB notifier") Reported-by: syzbot+cb2aa2390ac024e25f5c@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/6a290011.39669fcc.33b062.00b1.GAE@google.com/ Signed-off-by: Kuniyuki Iwashima Reviewed-by: Ido Schimmel Reviewed-by: David Ahern Link: https://patch.msgid.link/20260610061744.2030996-2-kuniyu@google.com Signed-off-by: Jakub Kicinski --- include/net/ip_fib.h | 5 +++++ net/ipv4/fib_trie.c | 4 ++++ 2 files changed, 9 insertions(+) (limited to 'include') diff --git a/include/net/ip_fib.h b/include/net/ip_fib.h index 318593743b6e..541da2dde626 100644 --- a/include/net/ip_fib.h +++ b/include/net/ip_fib.h @@ -629,6 +629,11 @@ static inline void fib_info_hold(struct fib_info *fi) refcount_inc(&fi->fib_clntref); } +static inline bool fib_info_hold_safe(struct fib_info *fi) +{ + return refcount_inc_not_zero(&fi->fib_clntref); +} + static inline void fib_info_put(struct fib_info *fi) { if (refcount_dec_and_test(&fi->fib_clntref)) diff --git a/net/ipv4/fib_trie.c b/net/ipv4/fib_trie.c index 1308213791f1..dac543c1d686 100644 --- a/net/ipv4/fib_trie.c +++ b/net/ipv4/fib_trie.c @@ -2172,10 +2172,14 @@ static int fib_leaf_notify(struct key_vector *l, struct fib_table *tb, if (fa->fa_slen == last_slen) continue; + if (!fib_info_hold_safe(fa->fa_info)) + continue; + last_slen = fa->fa_slen; err = call_fib_entry_notifier(nb, FIB_EVENT_ENTRY_REPLACE, l->key, KEYLENGTH - fa->fa_slen, fa, extack); + fib_info_put(fa->fa_info); if (err) return err; } -- cgit v1.2.3 From 2821e85c058f81c9948a2fb1a634f7b47457d51c Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Wed, 10 Jun 2026 06:17:19 +0000 Subject: net: fib_rules: Don't dump dying fib_rule in fib_rules_dump(). rocker_router_fib_event() calls fib_rule_get() during RCU dump. If the fib_rule is dying, refcount_inc() will complain about it. Let's call refcount_inc_not_zero() in fib_rules_dump(). Fixes: 5d7bfd141924 ("ipv4: fib_rules: Dump FIB rules when registering FIB notifier") Signed-off-by: Kuniyuki Iwashima Reviewed-by: Ido Schimmel Reviewed-by: David Ahern Link: https://patch.msgid.link/20260610061744.2030996-3-kuniyu@google.com Signed-off-by: Jakub Kicinski --- include/net/fib_rules.h | 5 +++++ net/core/fib_rules.c | 6 +++++- 2 files changed, 10 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/include/net/fib_rules.h b/include/net/fib_rules.h index 6e68e359ad18..7dee0ae616e3 100644 --- a/include/net/fib_rules.h +++ b/include/net/fib_rules.h @@ -111,6 +111,11 @@ static inline void fib_rule_get(struct fib_rule *rule) refcount_inc(&rule->refcnt); } +static inline bool fib_rule_get_safe(struct fib_rule *rule) +{ + return refcount_inc_not_zero(&rule->refcnt); +} + static inline void fib_rule_put(struct fib_rule *rule) { if (refcount_dec_and_test(&rule->refcnt)) diff --git a/net/core/fib_rules.c b/net/core/fib_rules.c index 8ca634964e36..cf374c208732 100644 --- a/net/core/fib_rules.c +++ b/net/core/fib_rules.c @@ -349,7 +349,7 @@ jumped: if (err != -EAGAIN) { if ((arg->flags & FIB_LOOKUP_NOREF) || - likely(refcount_inc_not_zero(&rule->refcnt))) { + likely(fib_rule_get_safe(rule))) { arg->rule = rule; goto out; } @@ -410,8 +410,12 @@ int fib_rules_dump(struct net *net, struct notifier_block *nb, int family, if (!ops) return -EAFNOSUPPORT; list_for_each_entry_rcu(rule, &ops->rules_list, list) { + if (!fib_rule_get_safe(rule)) + continue; + err = call_fib_rule_notifier(nb, FIB_EVENT_RULE_ADD, rule, family, extack); + fib_rule_put(rule); if (err) break; } -- cgit v1.2.3 From 65440a8ce42d89caaee26f70bd0ee09b92ec2c03 Mon Sep 17 00:00:00 2001 From: Kuniyuki Iwashima Date: Tue, 9 Jun 2026 22:20:04 +0000 Subject: ipmr: Convert mr_table.cache_resolve_queue_len to u32. mr_table.cache_resolve_queue_len is always updated under spin_lock_bh(&mfc_unres_lock). Let's convert it to u32. Signed-off-by: Kuniyuki Iwashima Reviewed-by: Ido Schimmel Link: https://patch.msgid.link/20260609222013.1550355-1-kuniyu@google.com Signed-off-by: Jakub Kicinski --- include/linux/mroute_base.h | 2 +- net/ipv4/ipmr.c | 18 ++++++++++-------- net/ipv6/ip6mr.c | 12 +++++++----- 3 files changed, 18 insertions(+), 14 deletions(-) (limited to 'include') diff --git a/include/linux/mroute_base.h b/include/linux/mroute_base.h index 5d75cc5b057e..4d55827e9705 100644 --- a/include/linux/mroute_base.h +++ b/include/linux/mroute_base.h @@ -256,7 +256,7 @@ struct mr_table { struct rhltable mfc_hash; struct list_head mfc_cache_list; int maxvif; - atomic_t cache_resolve_queue_len; + u32 cache_resolve_queue_len; bool mroute_do_assert; bool mroute_do_pim; bool mroute_do_wrvifwhole; diff --git a/net/ipv4/ipmr.c b/net/ipv4/ipmr.c index 78dbeecf71bb..1d9a4ac14fce 100644 --- a/net/ipv4/ipmr.c +++ b/net/ipv4/ipmr.c @@ -761,7 +761,8 @@ static void ipmr_destroy_unres(struct mr_table *mrt, struct mfc_cache *c) struct sk_buff *skb; struct nlmsgerr *e; - atomic_dec(&mrt->cache_resolve_queue_len); + WRITE_ONCE(mrt->cache_resolve_queue_len, + mrt->cache_resolve_queue_len - 1); while ((skb = skb_dequeue(&c->_c.mfc_un.unres.unresolved))) { if (ip_hdr(skb)->version == 0) { @@ -1180,11 +1181,12 @@ static int ipmr_cache_unresolved(struct mr_table *mrt, vifi_t vifi, if (err < 0) goto err; - atomic_inc(&mrt->cache_resolve_queue_len); + WRITE_ONCE(mrt->cache_resolve_queue_len, + mrt->cache_resolve_queue_len + 1); list_add(&c->_c.list, &mrt->mfc_unres_queue); mroute_netlink_event(mrt, c, RTM_NEWROUTE); - if (atomic_read(&mrt->cache_resolve_queue_len) == 1) + if (mrt->cache_resolve_queue_len == 1) mod_timer(&mrt->ipmr_expire_timer, c->_c.mfc_un.unres.expires); } @@ -1297,7 +1299,8 @@ static int ipmr_mfc_add(struct net *net, struct mr_table *mrt, if (uc->mfc_origin == c->mfc_origin && uc->mfc_mcastgrp == c->mfc_mcastgrp) { list_del(&_uc->list); - atomic_dec(&mrt->cache_resolve_queue_len); + WRITE_ONCE(mrt->cache_resolve_queue_len, + mrt->cache_resolve_queue_len - 1); found = true; break; } @@ -1356,7 +1359,7 @@ static void mroute_clean_tables(struct mr_table *mrt, int flags, } if (flags & MRT_FLUSH_MFC) { - if (atomic_read(&mrt->cache_resolve_queue_len) != 0 || !check_net(net)) { + if (READ_ONCE(mrt->cache_resolve_queue_len) || !check_net(net)) { spin_lock_bh(&mfc_unres_lock); list_for_each_entry_safe(c, tmp, &mrt->mfc_unres_queue, list) { list_del(&c->list); @@ -2962,10 +2965,9 @@ static int ipmr_rtm_route(struct sk_buff *skb, struct nlmsghdr *nlh, static bool ipmr_fill_table(struct mr_table *mrt, struct sk_buff *skb) { - u32 queue_len = atomic_read(&mrt->cache_resolve_queue_len); - if (nla_put_u32(skb, IPMRA_TABLE_ID, mrt->id) || - nla_put_u32(skb, IPMRA_TABLE_CACHE_RES_QUEUE_LEN, queue_len) || + nla_put_u32(skb, IPMRA_TABLE_CACHE_RES_QUEUE_LEN, + READ_ONCE(mrt->cache_resolve_queue_len)) || nla_put_s32(skb, IPMRA_TABLE_MROUTE_REG_VIF_NUM, READ_ONCE(mrt->mroute_reg_vif_num)) || nla_put_u8(skb, IPMRA_TABLE_MROUTE_DO_ASSERT, diff --git a/net/ipv6/ip6mr.c b/net/ipv6/ip6mr.c index 5a4816225fb1..604a58838901 100644 --- a/net/ipv6/ip6mr.c +++ b/net/ipv6/ip6mr.c @@ -817,7 +817,8 @@ static void ip6mr_destroy_unres(struct mr_table *mrt, struct mfc6_cache *c) struct net *net = read_pnet(&mrt->net); struct sk_buff *skb; - atomic_dec(&mrt->cache_resolve_queue_len); + WRITE_ONCE(mrt->cache_resolve_queue_len, + mrt->cache_resolve_queue_len - 1); while ((skb = skb_dequeue(&c->_c.mfc_un.unres.unresolved)) != NULL) { if (ipv6_hdr(skb)->version == 0) { @@ -1225,7 +1226,8 @@ static int ip6mr_cache_unresolved(struct mr_table *mrt, mifi_t mifi, if (err < 0) goto err; - atomic_inc(&mrt->cache_resolve_queue_len); + WRITE_ONCE(mrt->cache_resolve_queue_len, + mrt->cache_resolve_queue_len + 1); list_add(&c->_c.list, &mrt->mfc_unres_queue); mr6_netlink_event(mrt, c, RTM_NEWROUTE); @@ -1538,7 +1540,8 @@ static int ip6mr_mfc_add(struct net *net, struct mr_table *mrt, if (ipv6_addr_equal(&uc->mf6c_origin, &c->mf6c_origin) && ipv6_addr_equal(&uc->mf6c_mcastgrp, &c->mf6c_mcastgrp)) { list_del(&_uc->list); - atomic_dec(&mrt->cache_resolve_queue_len); + WRITE_ONCE(mrt->cache_resolve_queue_len, + mrt->cache_resolve_queue_len - 1); found = true; break; } @@ -1599,8 +1602,7 @@ static void mroute_clean_tables(struct mr_table *mrt, int flags, } if (flags & MRT6_FLUSH_MFC) { - if (atomic_read(&mrt->cache_resolve_queue_len) != 0 || - !check_net(net)) { + if (READ_ONCE(mrt->cache_resolve_queue_len) || !check_net(net)) { spin_lock_bh(&mfc_unres_lock); list_for_each_entry_safe(c, tmp, &mrt->mfc_unres_queue, list) { list_del(&c->list); -- cgit v1.2.3 From 1c3e7e0439773357e25d13d9cd56e5138593e5bd Mon Sep 17 00:00:00 2001 From: "Matthieu Baerts (NGI0)" Date: Fri, 5 Jun 2026 19:21:49 +1000 Subject: tcp: allow mptcp to drop TS for some packets With TCP-timestamps (padded) taking 12 bytes and ADD_ADDR IPv6 + port taking 30 bytes, the 40-byte limit for the TCP options is reached. In this case, it is then not possible to send the address signal. The idea is to let MPTCP dropping the TCP-timestamps option for some specific packets, to be able to send some specific pure ACK carrying >28 bytes of MPTCP options, like with this specific ADD_ADDR. A new parameter is passed from tcp_established_options to the MPTCP side to indicate if the TCP TS option is used, and if it should be dropped. The next commit implements the part on MPTCP side, but split into two patches to help TCP maintainers to identify the modifications on TCP side. This feature will be controlled by a new add_addr_v6_port_drop_ts MPTCP sysctl knob. It is important to keep in mind that dropping the TCP timestamps option for one packet of the connection could eventually disrupt some middleboxes: even if it should be unlikely, they could drop the packet or even block the connection. That's why this new feature will be controlled by a sysctl knob. Note that it would be technically possible to squeeze both options into the header if the ADD_ADDR is first written, and then the TCP timestamps without the NOPs preceding it. But this means more modifications on TCP side, plus some middleboxes could still be disrupted by that. In this implementation, an unused bit is used in mptcp_out_options structure to avoid passing an address to a local variable. Reading and setting it needs CONFIG_MPTCP, so the whole block now has this #if condition: mptcp_established_options() is then no longer used without CONFIG_MPTCP. About alternatives, instead of passing a new boolean (has_ts), another option would be to pass the whole option structure (opts), but 'struct tcp_out_options' is currently defined in tcp_output.c, and it would need to be exported. Plus that means the removal of the TCP TS option would be done on the MPTCP side, and not here on the TCP side. It feels clearer to remove other TCP options from the TCP side, than hiding that from the MPTCP side. Yet an other alternative would be to pass the size already taken by the other TCP options, and have a way to drop them all when needed. But this feels better to target only the timestamps option where dropping it should be safe, even if it is currently the only option that would be set before MPTCP, when MPTCP is used. Reviewed-by: Mat Martineau Signed-off-by: Matthieu Baerts (NGI0) Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260605-net-next-mptcp-add-addr6-port-ts-v2-5-758e7ca73f4d@kernel.org Signed-off-by: Jakub Kicinski --- include/net/mptcp.h | 13 +++---------- net/ipv4/tcp_output.c | 10 +++++++++- net/mptcp/options.c | 2 +- 3 files changed, 13 insertions(+), 12 deletions(-) (limited to 'include') diff --git a/include/net/mptcp.h b/include/net/mptcp.h index 24d1016a4664..71b9fc5a5796 100644 --- a/include/net/mptcp.h +++ b/include/net/mptcp.h @@ -72,7 +72,8 @@ struct mptcp_out_options { u8 reset_reason:4, reset_transient:1, csum_reqd:1, - allow_join_id0:1; + allow_join_id0:1, + drop_ts:1; union { struct { u64 sndr_key; @@ -153,7 +154,7 @@ bool mptcp_syn_options(struct sock *sk, const struct sk_buff *skb, bool mptcp_synack_options(const struct request_sock *req, unsigned int *size, struct mptcp_out_options *opts); int mptcp_established_options(struct sock *sk, struct sk_buff *skb, - unsigned int remaining, + unsigned int remaining, bool has_ts, struct mptcp_out_options *opts); bool mptcp_incoming_options(struct sock *sk, struct sk_buff *skb); @@ -269,14 +270,6 @@ static inline bool mptcp_synack_options(const struct request_sock *req, return false; } -static inline int mptcp_established_options(struct sock *sk, - struct sk_buff *skb, - unsigned int remaining, - struct mptcp_out_options *opts) -{ - return -1; -} - static inline bool mptcp_incoming_options(struct sock *sk, struct sk_buff *skb) { diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c index d3b8e61d3c5e..26dd751ec72a 100644 --- a/net/ipv4/tcp_output.c +++ b/net/ipv4/tcp_output.c @@ -1175,6 +1175,7 @@ static unsigned int tcp_established_options(struct sock *sk, struct sk_buff *skb size += TCPOLEN_TSTAMP_ALIGNED; } +#if IS_ENABLED(CONFIG_MPTCP) /* MPTCP options have precedence over SACK for the limited TCP * option space because a MPTCP connection would be forced to * fall back to regular TCP if a required multipath option is @@ -1183,15 +1184,22 @@ static unsigned int tcp_established_options(struct sock *sk, struct sk_buff *skb */ if (sk_is_mptcp(sk)) { unsigned int remaining = MAX_TCP_OPTION_SPACE - size; + bool has_ts = opts->options & OPTION_TS; int opt_size; - opt_size = mptcp_established_options(sk, skb, remaining, + opts->mptcp.drop_ts = 0; + + opt_size = mptcp_established_options(sk, skb, remaining, has_ts, &opts->mptcp); if (opt_size >= 0) { opts->options |= OPTION_MPTCP; size += opt_size; + + if (opts->mptcp.drop_ts) + opts->options &= ~OPTION_TS; } } +#endif eff_sacks = tp->rx_opt.num_sacks + tp->rx_opt.dsack; if (unlikely(eff_sacks)) { diff --git a/net/mptcp/options.c b/net/mptcp/options.c index 2e4b6aafbad5..95f16f9f0ce2 100644 --- a/net/mptcp/options.c +++ b/net/mptcp/options.c @@ -804,7 +804,7 @@ static bool mptcp_established_options_mp_fail(struct sock *sk, int *size, } int mptcp_established_options(struct sock *sk, struct sk_buff *skb, - unsigned int remaining, + unsigned int remaining, bool has_ts, struct mptcp_out_options *opts) { struct mptcp_subflow_context *subflow = mptcp_subflow_ctx(sk); -- cgit v1.2.3 From 13655144ddcad532c4e9338788041654a54e5c2f Mon Sep 17 00:00:00 2001 From: Fidelio Lawson Date: Tue, 9 Jun 2026 18:19:56 +0200 Subject: net: ethtool: add KSZ87xx low-loss cable PHY tunables Introduce vendor-specific PHY tunable identifiers to control the KSZ87xx low-loss cable erratum handling through the ethtool PHY tunable interface. The following tunables are added: - a boolean "short-cable" tunable, applying a documented and conservative preset intended for short or low-loss Ethernet cables; - an integer LPF bandwidth tunable, allowing advanced adjustment of the receiver low-pass filter bandwidth; - an integer DSP EQ initial value tunable, allowing advanced tuning of the PHY equalizer initialization. The actual behavior is implemented by the corresponding PHY and switch drivers. Reviewed-by: Marek Vasut Reviewed-by: Nicolai Buchwitz Signed-off-by: Fidelio Lawson Link: https://patch.msgid.link/20260609-ksz87xx_errata_low_loss_connections-v10-2-9ba4418cf3db@exotec.com Signed-off-by: Jakub Kicinski --- include/uapi/linux/ethtool.h | 3 +++ net/ethtool/common.c | 3 +++ net/ethtool/ioctl.c | 7 +++++++ 3 files changed, 13 insertions(+) (limited to 'include') diff --git a/include/uapi/linux/ethtool.h b/include/uapi/linux/ethtool.h index 1cdfb8341df2..a2091d4e00f3 100644 --- a/include/uapi/linux/ethtool.h +++ b/include/uapi/linux/ethtool.h @@ -291,6 +291,9 @@ enum phy_tunable_id { ETHTOOL_PHY_DOWNSHIFT, ETHTOOL_PHY_FAST_LINK_DOWN, ETHTOOL_PHY_EDPD, + ETHTOOL_PHY_SHORT_CABLE_PRESET, + ETHTOOL_PHY_LPF_BW, + ETHTOOL_PHY_DSP_EQ_INIT_VALUE, /* * Add your fresh new phy tunable attribute above and remember to update * phy_tunable_strings[] in net/ethtool/common.c diff --git a/net/ethtool/common.c b/net/ethtool/common.c index 84ec88dee05c..a24d3a8a9ec1 100644 --- a/net/ethtool/common.c +++ b/net/ethtool/common.c @@ -101,6 +101,9 @@ phy_tunable_strings[__ETHTOOL_PHY_TUNABLE_COUNT][ETH_GSTRING_LEN] = { [ETHTOOL_PHY_DOWNSHIFT] = "phy-downshift", [ETHTOOL_PHY_FAST_LINK_DOWN] = "phy-fast-link-down", [ETHTOOL_PHY_EDPD] = "phy-energy-detect-power-down", + [ETHTOOL_PHY_SHORT_CABLE_PRESET] = "phy-short-cable-preset", + [ETHTOOL_PHY_LPF_BW] = "phy-lpf-bandwidth", + [ETHTOOL_PHY_DSP_EQ_INIT_VALUE] = "phy-dsp-eq-init-value", }; #define __LINK_MODE_NAME(speed, type, duplex) \ diff --git a/net/ethtool/ioctl.c b/net/ethtool/ioctl.c index a7bff829b758..4b0bc503f930 100644 --- a/net/ethtool/ioctl.c +++ b/net/ethtool/ioctl.c @@ -3133,6 +3133,7 @@ static int ethtool_phy_tunable_valid(const struct ethtool_tunable *tuna) switch (tuna->id) { case ETHTOOL_PHY_DOWNSHIFT: case ETHTOOL_PHY_FAST_LINK_DOWN: + case ETHTOOL_PHY_SHORT_CABLE_PRESET: if (tuna->len != sizeof(u8) || tuna->type_id != ETHTOOL_TUNABLE_U8) return -EINVAL; @@ -3142,6 +3143,12 @@ static int ethtool_phy_tunable_valid(const struct ethtool_tunable *tuna) tuna->type_id != ETHTOOL_TUNABLE_U16) return -EINVAL; break; + case ETHTOOL_PHY_LPF_BW: + case ETHTOOL_PHY_DSP_EQ_INIT_VALUE: + if (tuna->len != sizeof(u32) || + tuna->type_id != ETHTOOL_TUNABLE_U32) + return -EINVAL; + break; default: return -EINVAL; } -- cgit v1.2.3 From 990348e5bb457697c2f1f7f7b65154a3334d9d2b Mon Sep 17 00:00:00 2001 From: Sechang Lim Date: Thu, 11 Jun 2026 09:29:18 +0000 Subject: tcp: clear sock_ops cb flags before force-closing a child socket A child socket inherits the listener's bpf_sock_ops_cb_flags via sk_clone_lock(). If its setup fails in tcp_v4_syn_recv_sock() / tcp_v6_syn_recv_sock(), the child is freed through put_and_exit, where inet_csk_prepare_forced_close() drops the socket lock and tcp_done() runs without it. If BPF_SOCK_OPS_STATE_CB_FLAG was inherited, tcp_done() -> tcp_set_state() calls tcp_call_bpf(), which expects the lock and trips sock_owned_by_me(): WARNING: include/net/sock.h:1799 at tcp_set_state+0x433/0x550 RIP: 0010:tcp_set_state+0x433/0x550 include/net/sock.h:1799 Call Trace: tcp_done+0xba/0x250 net/ipv4/tcp.c:5095 tcp_v4_syn_recv_sock+0x850/0xa50 net/ipv4/tcp_ipv4.c:1787 tcp_check_req+0xf30/0x1360 net/ipv4/tcp_minisocks.c:926 tcp_v4_rcv+0x1047/0x1b50 net/ipv4/tcp_ipv4.c:2164 The child is freed before it is ever established, so it should run no sock_ops callback. Clear its cb flags in inet_csk_prepare_for_destroy_sock(), the common point for the IPv4, IPv6 and chtls forced-close paths and for the MPTCP ->syn_recv_sock() failure path (dispose_child), which reaches tcp_done() on a child that was never established too. Suggested-by: Jiayuan Chen Fixes: d44874910a26 ("bpf: Add BPF_SOCK_OPS_STATE_CB") Signed-off-by: Sechang Lim Reviewed-by: Jiayuan Chen Reviewed-by: Kuniyuki Iwashima Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260611092923.1895982-1-rhkrqnwk98@gmail.com Signed-off-by: Jakub Kicinski --- include/net/tcp.h | 9 +++++++++ net/ipv4/inet_connection_sock.c | 1 + 2 files changed, 10 insertions(+) (limited to 'include') diff --git a/include/net/tcp.h b/include/net/tcp.h index 98848db62894..607298501e12 100644 --- a/include/net/tcp.h +++ b/include/net/tcp.h @@ -2942,6 +2942,11 @@ static inline int tcp_call_bpf_3arg(struct sock *sk, int op, u32 arg1, u32 arg2, return tcp_call_bpf(sk, op, 3, args); } +static inline void tcp_clear_sock_ops_cb_flags(struct sock *sk) +{ + tcp_sk(sk)->bpf_sock_ops_cb_flags = 0; +} + #else static inline int tcp_call_bpf(struct sock *sk, int op, u32 nargs, u32 *args) { @@ -2959,6 +2964,10 @@ static inline int tcp_call_bpf_3arg(struct sock *sk, int op, u32 arg1, u32 arg2, return -EPERM; } +static inline void tcp_clear_sock_ops_cb_flags(struct sock *sk) +{ +} + #endif static inline u32 tcp_timeout_init(struct sock *sk) diff --git a/net/ipv4/inet_connection_sock.c b/net/ipv4/inet_connection_sock.c index 5b934ce8d98a..690f7fb3f029 100644 --- a/net/ipv4/inet_connection_sock.c +++ b/net/ipv4/inet_connection_sock.c @@ -1285,6 +1285,7 @@ EXPORT_SYMBOL(inet_csk_destroy_sock); void inet_csk_prepare_for_destroy_sock(struct sock *sk) { /* The below has to be done to allow calling inet_csk_destroy_sock */ + tcp_clear_sock_ops_cb_flags(sk); sock_set_flag(sk, SOCK_DEAD); tcp_orphan_count_inc(); } -- cgit v1.2.3 From cdae65fc43f28b6508d85fa242264f3bc5c9a5c7 Mon Sep 17 00:00:00 2001 From: Sabrina Dubroca Date: Thu, 11 Jun 2026 12:21:33 +0200 Subject: tls: remove tls_toe and the related driver The tls_toe feature and its single user (chelsio chtls) have been unmaintained for multiple years. It also hooks into the core of the TCP implementation, and bypasses most of the networking stack. Signed-off-by: Sabrina Dubroca Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/1f30e73275c07bf879f547589872d0916025a52e.1781165969.git.sd@queasysnail.net Signed-off-by: Jakub Kicinski --- Documentation/networking/tls-offload.rst | 7 +- arch/s390/configs/debug_defconfig | 1 - arch/s390/configs/defconfig | 1 - drivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c | 3 +- drivers/net/ethernet/chelsio/inline_crypto/Kconfig | 12 - .../net/ethernet/chelsio/inline_crypto/Makefile | 1 - .../ethernet/chelsio/inline_crypto/chtls/Makefile | 6 - .../ethernet/chelsio/inline_crypto/chtls/chtls.h | 584 ----- .../chelsio/inline_crypto/chtls/chtls_cm.c | 2336 -------------------- .../chelsio/inline_crypto/chtls/chtls_cm.h | 218 -- .../chelsio/inline_crypto/chtls/chtls_hw.c | 462 ---- .../chelsio/inline_crypto/chtls/chtls_io.c | 1836 --------------- .../chelsio/inline_crypto/chtls/chtls_main.c | 642 ------ include/linux/netdev_features.h | 3 +- include/net/tls.h | 1 - include/net/tls_toe.h | 77 - include/uapi/linux/tls.h | 2 +- net/ethtool/common.c | 1 - net/tls/Kconfig | 10 - net/tls/Makefile | 1 - net/tls/tls_main.c | 17 - net/tls/tls_toe.c | 141 -- 22 files changed, 4 insertions(+), 6358 deletions(-) delete mode 100644 drivers/net/ethernet/chelsio/inline_crypto/chtls/Makefile delete mode 100644 drivers/net/ethernet/chelsio/inline_crypto/chtls/chtls.h delete mode 100644 drivers/net/ethernet/chelsio/inline_crypto/chtls/chtls_cm.c delete mode 100644 drivers/net/ethernet/chelsio/inline_crypto/chtls/chtls_cm.h delete mode 100644 drivers/net/ethernet/chelsio/inline_crypto/chtls/chtls_hw.c delete mode 100644 drivers/net/ethernet/chelsio/inline_crypto/chtls/chtls_io.c delete mode 100644 drivers/net/ethernet/chelsio/inline_crypto/chtls/chtls_main.c delete mode 100644 include/net/tls_toe.h delete mode 100644 net/tls/tls_toe.c (limited to 'include') diff --git a/Documentation/networking/tls-offload.rst b/Documentation/networking/tls-offload.rst index c173f537bf4d..25ee8d9f12c9 100644 --- a/Documentation/networking/tls-offload.rst +++ b/Documentation/networking/tls-offload.rst @@ -13,7 +13,7 @@ Layer Protocol (ULP) and install the cryptographic connection state. For details regarding the user-facing interface refer to the TLS documentation in :ref:`Documentation/networking/tls.rst `. -``ktls`` can operate in three modes: +``ktls`` can operate in two modes: * Software crypto mode (``TLS_SW``) - CPU handles the cryptography. In most basic cases only crypto operations synchronous with the CPU @@ -26,11 +26,6 @@ documentation in :ref:`Documentation/networking/tls.rst `. This mode integrates best with the kernel stack and is described in detail in the remaining part of this document (``ethtool`` flags ``tls-hw-tx-offload`` and ``tls-hw-rx-offload``). - * Full TCP NIC offload mode (``TLS_HW_RECORD``) - mode of operation where - NIC driver and firmware replace the kernel networking stack - with its own TCP handling, it is not usable in production environments - making use of the Linux networking stack for example any firewalling - abilities or QoS and packet scheduling (``ethtool`` flag ``tls-hw-record``). The operation mode is selected automatically based on device configuration, offload opt-in or opt-out on per-connection basis is not currently supported. diff --git a/arch/s390/configs/debug_defconfig b/arch/s390/configs/debug_defconfig index 471d078e5c53..54637be87fb7 100644 --- a/arch/s390/configs/debug_defconfig +++ b/arch/s390/configs/debug_defconfig @@ -125,7 +125,6 @@ CONFIG_UNIX=y CONFIG_UNIX_DIAG=m CONFIG_TLS=m CONFIG_TLS_DEVICE=y -CONFIG_TLS_TOE=y CONFIG_XFRM_USER=m CONFIG_NET_KEY=m CONFIG_SMC=m diff --git a/arch/s390/configs/defconfig b/arch/s390/configs/defconfig index c0d39d05f50a..5f5114a253cf 100644 --- a/arch/s390/configs/defconfig +++ b/arch/s390/configs/defconfig @@ -116,7 +116,6 @@ CONFIG_UNIX=y CONFIG_UNIX_DIAG=m CONFIG_TLS=m CONFIG_TLS_DEVICE=y -CONFIG_TLS_TOE=y CONFIG_XFRM_USER=m CONFIG_NET_KEY=m CONFIG_SMC=m diff --git a/drivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c b/drivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c index 6df98fca932f..9e2c2fa16d7a 100644 --- a/drivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c +++ b/drivers/net/ethernet/chelsio/cxgb4/cxgb4_main.c @@ -6795,8 +6795,7 @@ static int init_one(struct pci_dev *pdev, const struct pci_device_id *ent) NETIF_F_TSO | NETIF_F_TSO6; netdev->hw_features |= NETIF_F_GSO_UDP_TUNNEL | - NETIF_F_GSO_UDP_TUNNEL_CSUM | - NETIF_F_HW_TLS_RECORD; + NETIF_F_GSO_UDP_TUNNEL_CSUM; if (adapter->rawf_cnt) netdev->udp_tunnel_nic_info = &cxgb_udp_tunnels; diff --git a/drivers/net/ethernet/chelsio/inline_crypto/Kconfig b/drivers/net/ethernet/chelsio/inline_crypto/Kconfig index 521955e1f894..b7d452f4a7f1 100644 --- a/drivers/net/ethernet/chelsio/inline_crypto/Kconfig +++ b/drivers/net/ethernet/chelsio/inline_crypto/Kconfig @@ -13,18 +13,6 @@ config CHELSIO_INLINE_CRYPTO if CHELSIO_INLINE_CRYPTO -config CRYPTO_DEV_CHELSIO_TLS - tristate "Chelsio Crypto Inline TLS Driver" - depends on CHELSIO_T4 - depends on TLS - depends on TLS_TOE - help - Support Chelsio Inline TLS with Chelsio crypto accelerator. - Enable inline TLS support for Tx and Rx. - - To compile this driver as a module, choose M here: the module - will be called chtls. - config CHELSIO_IPSEC_INLINE tristate "Chelsio IPSec XFRM Tx crypto offload" depends on CHELSIO_T4 diff --git a/drivers/net/ethernet/chelsio/inline_crypto/Makefile b/drivers/net/ethernet/chelsio/inline_crypto/Makefile index 27e6d7e2f1eb..ca6548adc6a7 100644 --- a/drivers/net/ethernet/chelsio/inline_crypto/Makefile +++ b/drivers/net/ethernet/chelsio/inline_crypto/Makefile @@ -1,4 +1,3 @@ # SPDX-License-Identifier: GPL-2.0-only -obj-$(CONFIG_CRYPTO_DEV_CHELSIO_TLS) += chtls/ obj-$(CONFIG_CHELSIO_IPSEC_INLINE) += ch_ipsec/ obj-$(CONFIG_CHELSIO_TLS_DEVICE) += ch_ktls/ diff --git a/drivers/net/ethernet/chelsio/inline_crypto/chtls/Makefile b/drivers/net/ethernet/chelsio/inline_crypto/chtls/Makefile deleted file mode 100644 index bc11495acdb3..000000000000 --- a/drivers/net/ethernet/chelsio/inline_crypto/chtls/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -ccflags-y := -I $(srctree)/drivers/net/ethernet/chelsio/cxgb4 \ - -I $(srctree)/drivers/crypto/chelsio - -obj-$(CONFIG_CRYPTO_DEV_CHELSIO_TLS) += chtls.o -chtls-objs := chtls_main.o chtls_cm.o chtls_io.o chtls_hw.o diff --git a/drivers/net/ethernet/chelsio/inline_crypto/chtls/chtls.h b/drivers/net/ethernet/chelsio/inline_crypto/chtls/chtls.h deleted file mode 100644 index 1de5744a49b0..000000000000 --- a/drivers/net/ethernet/chelsio/inline_crypto/chtls/chtls.h +++ /dev/null @@ -1,584 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Copyright (c) 2018 Chelsio Communications, Inc. - */ - -#ifndef __CHTLS_H__ -#define __CHTLS_H__ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "t4fw_api.h" -#include "t4_msg.h" -#include "cxgb4.h" -#include "cxgb4_uld.h" -#include "l2t.h" -#include "chcr_algo.h" -#include "chcr_core.h" -#include "chcr_crypto.h" - -#define CHTLS_DRV_VERSION "1.0.0.0-ko" - -#define TLS_KEYCTX_RXFLIT_CNT_S 24 -#define TLS_KEYCTX_RXFLIT_CNT_V(x) ((x) << TLS_KEYCTX_RXFLIT_CNT_S) - -#define TLS_KEYCTX_RXPROT_VER_S 20 -#define TLS_KEYCTX_RXPROT_VER_M 0xf -#define TLS_KEYCTX_RXPROT_VER_V(x) ((x) << TLS_KEYCTX_RXPROT_VER_S) - -#define TLS_KEYCTX_RXCIPH_MODE_S 16 -#define TLS_KEYCTX_RXCIPH_MODE_M 0xf -#define TLS_KEYCTX_RXCIPH_MODE_V(x) ((x) << TLS_KEYCTX_RXCIPH_MODE_S) - -#define TLS_KEYCTX_RXAUTH_MODE_S 12 -#define TLS_KEYCTX_RXAUTH_MODE_M 0xf -#define TLS_KEYCTX_RXAUTH_MODE_V(x) ((x) << TLS_KEYCTX_RXAUTH_MODE_S) - -#define TLS_KEYCTX_RXCIAU_CTRL_S 11 -#define TLS_KEYCTX_RXCIAU_CTRL_V(x) ((x) << TLS_KEYCTX_RXCIAU_CTRL_S) - -#define TLS_KEYCTX_RX_SEQCTR_S 9 -#define TLS_KEYCTX_RX_SEQCTR_M 0x3 -#define TLS_KEYCTX_RX_SEQCTR_V(x) ((x) << TLS_KEYCTX_RX_SEQCTR_S) - -#define TLS_KEYCTX_RX_VALID_S 8 -#define TLS_KEYCTX_RX_VALID_V(x) ((x) << TLS_KEYCTX_RX_VALID_S) - -#define TLS_KEYCTX_RXCK_SIZE_S 3 -#define TLS_KEYCTX_RXCK_SIZE_M 0x7 -#define TLS_KEYCTX_RXCK_SIZE_V(x) ((x) << TLS_KEYCTX_RXCK_SIZE_S) - -#define TLS_KEYCTX_RXMK_SIZE_S 0 -#define TLS_KEYCTX_RXMK_SIZE_M 0x7 -#define TLS_KEYCTX_RXMK_SIZE_V(x) ((x) << TLS_KEYCTX_RXMK_SIZE_S) - -#define KEYCTX_TX_WR_IV_S 55 -#define KEYCTX_TX_WR_IV_M 0x1ffULL -#define KEYCTX_TX_WR_IV_V(x) ((x) << KEYCTX_TX_WR_IV_S) -#define KEYCTX_TX_WR_IV_G(x) \ - (((x) >> KEYCTX_TX_WR_IV_S) & KEYCTX_TX_WR_IV_M) - -#define KEYCTX_TX_WR_AAD_S 47 -#define KEYCTX_TX_WR_AAD_M 0xffULL -#define KEYCTX_TX_WR_AAD_V(x) ((x) << KEYCTX_TX_WR_AAD_S) -#define KEYCTX_TX_WR_AAD_G(x) (((x) >> KEYCTX_TX_WR_AAD_S) & \ - KEYCTX_TX_WR_AAD_M) - -#define KEYCTX_TX_WR_AADST_S 39 -#define KEYCTX_TX_WR_AADST_M 0xffULL -#define KEYCTX_TX_WR_AADST_V(x) ((x) << KEYCTX_TX_WR_AADST_S) -#define KEYCTX_TX_WR_AADST_G(x) \ - (((x) >> KEYCTX_TX_WR_AADST_S) & KEYCTX_TX_WR_AADST_M) - -#define KEYCTX_TX_WR_CIPHER_S 30 -#define KEYCTX_TX_WR_CIPHER_M 0x1ffULL -#define KEYCTX_TX_WR_CIPHER_V(x) ((x) << KEYCTX_TX_WR_CIPHER_S) -#define KEYCTX_TX_WR_CIPHER_G(x) \ - (((x) >> KEYCTX_TX_WR_CIPHER_S) & KEYCTX_TX_WR_CIPHER_M) - -#define KEYCTX_TX_WR_CIPHERST_S 23 -#define KEYCTX_TX_WR_CIPHERST_M 0x7f -#define KEYCTX_TX_WR_CIPHERST_V(x) ((x) << KEYCTX_TX_WR_CIPHERST_S) -#define KEYCTX_TX_WR_CIPHERST_G(x) \ - (((x) >> KEYCTX_TX_WR_CIPHERST_S) & KEYCTX_TX_WR_CIPHERST_M) - -#define KEYCTX_TX_WR_AUTH_S 14 -#define KEYCTX_TX_WR_AUTH_M 0x1ff -#define KEYCTX_TX_WR_AUTH_V(x) ((x) << KEYCTX_TX_WR_AUTH_S) -#define KEYCTX_TX_WR_AUTH_G(x) \ - (((x) >> KEYCTX_TX_WR_AUTH_S) & KEYCTX_TX_WR_AUTH_M) - -#define KEYCTX_TX_WR_AUTHST_S 7 -#define KEYCTX_TX_WR_AUTHST_M 0x7f -#define KEYCTX_TX_WR_AUTHST_V(x) ((x) << KEYCTX_TX_WR_AUTHST_S) -#define KEYCTX_TX_WR_AUTHST_G(x) \ - (((x) >> KEYCTX_TX_WR_AUTHST_S) & KEYCTX_TX_WR_AUTHST_M) - -#define KEYCTX_TX_WR_AUTHIN_S 0 -#define KEYCTX_TX_WR_AUTHIN_M 0x7f -#define KEYCTX_TX_WR_AUTHIN_V(x) ((x) << KEYCTX_TX_WR_AUTHIN_S) -#define KEYCTX_TX_WR_AUTHIN_G(x) \ - (((x) >> KEYCTX_TX_WR_AUTHIN_S) & KEYCTX_TX_WR_AUTHIN_M) - -struct sge_opaque_hdr { - void *dev; - dma_addr_t addr[MAX_SKB_FRAGS + 1]; -}; - -#define MAX_IVS_PAGE 256 -#define TLS_KEY_CONTEXT_SZ 64 -#define CIPHER_BLOCK_SIZE 16 -#define GCM_TAG_SIZE 16 -#define KEY_ON_MEM_SZ 16 -#define AEAD_EXPLICIT_DATA_SIZE 8 -#define TLS_HEADER_LENGTH 5 -#define SCMD_CIPH_MODE_AES_GCM 2 -/* Any MFS size should work and come from openssl */ -#define TLS_MFS 16384 - -#define RSS_HDR sizeof(struct rss_header) -#define TLS_WR_CPL_LEN \ - (sizeof(struct fw_tlstx_data_wr) + sizeof(struct cpl_tx_tls_sfo)) - -enum { - CHTLS_KEY_CONTEXT_DSGL, - CHTLS_KEY_CONTEXT_IMM, - CHTLS_KEY_CONTEXT_DDR, -}; - -enum { - CHTLS_LISTEN_START, - CHTLS_LISTEN_STOP, -}; - -/* Flags for return value of CPL message handlers */ -enum { - CPL_RET_BUF_DONE = 1, /* buffer processing done */ - CPL_RET_BAD_MSG = 2, /* bad CPL message */ - CPL_RET_UNKNOWN_TID = 4 /* unexpected unknown TID */ -}; - -#define LISTEN_INFO_HASH_SIZE 32 -#define RSPQ_HASH_BITS 5 -struct listen_info { - struct listen_info *next; /* Link to next entry */ - struct sock *sk; /* The listening socket */ - unsigned int stid; /* The server TID */ -}; - -enum { - T4_LISTEN_START_PENDING, - T4_LISTEN_STARTED -}; - -enum csk_flags { - CSK_CALLBACKS_CHKD, /* socket callbacks have been sanitized */ - CSK_ABORT_REQ_RCVD, /* received one ABORT_REQ_RSS message */ - CSK_TX_MORE_DATA, /* sending ULP data; don't set SHOVE bit */ - CSK_TX_WAIT_IDLE, /* suspend Tx until in-flight data is ACKed */ - CSK_ABORT_SHUTDOWN, /* shouldn't send more abort requests */ - CSK_ABORT_RPL_PENDING, /* expecting an abort reply */ - CSK_CLOSE_CON_REQUESTED,/* we've sent a close_conn_req */ - CSK_TX_DATA_SENT, /* sent a TX_DATA WR on this connection */ - CSK_TX_FAILOVER, /* Tx traffic failing over */ - CSK_UPDATE_RCV_WND, /* Need to update rcv window */ - CSK_RST_ABORTED, /* outgoing RST was aborted */ - CSK_TLS_HANDSHK, /* TLS Handshake */ - CSK_CONN_INLINE, /* Connection on HW */ -}; - -enum chtls_cdev_state { - CHTLS_CDEV_STATE_UP = 1 -}; - -struct listen_ctx { - struct sock *lsk; - struct chtls_dev *cdev; - struct sk_buff_head synq; - u32 state; -}; - -struct key_map { - unsigned long *addr; - unsigned int start; - unsigned int available; - unsigned int size; - spinlock_t lock; /* lock for key id request from map */ -} __packed; - -struct tls_scmd { - u32 seqno_numivs; - u32 ivgen_hdrlen; -}; - -struct chtls_dev { - struct tls_toe_device tlsdev; - struct list_head list; - struct cxgb4_lld_info *lldi; - struct pci_dev *pdev; - struct listen_info *listen_hash_tab[LISTEN_INFO_HASH_SIZE]; - spinlock_t listen_lock; /* lock for listen list */ - struct net_device **ports; - struct tid_info *tids; - unsigned int pfvf; - const unsigned short *mtus; - - struct idr hwtid_idr; - struct idr stid_idr; - - spinlock_t idr_lock ____cacheline_aligned_in_smp; - - struct net_device *egr_dev[NCHAN * 2]; - struct sk_buff *rspq_skb_cache[1 << RSPQ_HASH_BITS]; - struct sk_buff *askb; - - struct sk_buff_head deferq; - struct work_struct deferq_task; - - struct list_head list_node; - struct list_head rcu_node; - struct list_head na_node; - unsigned int send_page_order; - int max_host_sndbuf; - u32 round_robin_cnt; - struct key_map kmap; - unsigned int cdev_state; -}; - -struct chtls_listen { - struct chtls_dev *cdev; - struct sock *sk; -}; - -struct chtls_hws { - struct sk_buff_head sk_recv_queue; - u8 txqid; - u8 ofld; - u16 type; - u16 rstate; - u16 keyrpl; - u16 pldlen; - u16 rcvpld; - u16 compute; - u16 expansion; - u16 keylen; - u16 pdus; - u16 adjustlen; - u16 ivsize; - u16 txleft; - u32 mfs; - s32 txkey; - s32 rxkey; - u32 fcplenmax; - u32 copied_seq; - u64 tx_seq_no; - struct tls_scmd scmd; - union { - struct tls12_crypto_info_aes_gcm_128 aes_gcm_128; - struct tls12_crypto_info_aes_gcm_256 aes_gcm_256; - } crypto_info; -}; - -struct chtls_sock { - struct sock *sk; - struct chtls_dev *cdev; - struct l2t_entry *l2t_entry; /* pointer to the L2T entry */ - struct net_device *egress_dev; /* TX_CHAN for act open retry */ - - struct sk_buff_head txq; - struct sk_buff *wr_skb_head; - struct sk_buff *wr_skb_tail; - struct sk_buff *ctrl_skb_cache; - struct sk_buff *txdata_skb_cache; /* abort path messages */ - struct kref kref; - unsigned long flags; - u32 opt2; - u32 wr_credits; - u32 wr_unacked; - u32 wr_max_credits; - u32 wr_nondata; - u32 hwtid; /* TCP Control Block ID */ - u32 txq_idx; - u32 rss_qid; - u32 tid; - u32 idr; - u32 mss; - u32 ulp_mode; - u32 tx_chan; - u32 rx_chan; - u32 sndbuf; - u32 txplen_max; - u32 mtu_idx; /* MTU table index */ - u32 smac_idx; - u8 port_id; - u8 tos; - u16 resv2; - u32 delack_mode; - u32 delack_seq; - u32 snd_win; - u32 rcv_win; - - void *passive_reap_next; /* placeholder for passive */ - struct chtls_hws tlshws; - struct synq { - struct sk_buff *next; - struct sk_buff *prev; - } synq; - struct listen_ctx *listen_ctx; -}; - -struct tls_hdr { - u8 type; - u16 version; - u16 length; -} __packed; - -struct tlsrx_cmp_hdr { - u8 type; - u16 version; - u16 length; - - u64 tls_seq; - u16 reserved1; - u8 res_to_mac_error; -} __packed; - -/* res_to_mac_error fields */ -#define TLSRX_HDR_PKT_INT_ERROR_S 4 -#define TLSRX_HDR_PKT_INT_ERROR_M 0x1 -#define TLSRX_HDR_PKT_INT_ERROR_V(x) \ - ((x) << TLSRX_HDR_PKT_INT_ERROR_S) -#define TLSRX_HDR_PKT_INT_ERROR_G(x) \ - (((x) >> TLSRX_HDR_PKT_INT_ERROR_S) & TLSRX_HDR_PKT_INT_ERROR_M) -#define TLSRX_HDR_PKT_INT_ERROR_F TLSRX_HDR_PKT_INT_ERROR_V(1U) - -#define TLSRX_HDR_PKT_SPP_ERROR_S 3 -#define TLSRX_HDR_PKT_SPP_ERROR_M 0x1 -#define TLSRX_HDR_PKT_SPP_ERROR_V(x) ((x) << TLSRX_HDR_PKT_SPP_ERROR) -#define TLSRX_HDR_PKT_SPP_ERROR_G(x) \ - (((x) >> TLSRX_HDR_PKT_SPP_ERROR_S) & TLSRX_HDR_PKT_SPP_ERROR_M) -#define TLSRX_HDR_PKT_SPP_ERROR_F TLSRX_HDR_PKT_SPP_ERROR_V(1U) - -#define TLSRX_HDR_PKT_CCDX_ERROR_S 2 -#define TLSRX_HDR_PKT_CCDX_ERROR_M 0x1 -#define TLSRX_HDR_PKT_CCDX_ERROR_V(x) ((x) << TLSRX_HDR_PKT_CCDX_ERROR_S) -#define TLSRX_HDR_PKT_CCDX_ERROR_G(x) \ - (((x) >> TLSRX_HDR_PKT_CCDX_ERROR_S) & TLSRX_HDR_PKT_CCDX_ERROR_M) -#define TLSRX_HDR_PKT_CCDX_ERROR_F TLSRX_HDR_PKT_CCDX_ERROR_V(1U) - -#define TLSRX_HDR_PKT_PAD_ERROR_S 1 -#define TLSRX_HDR_PKT_PAD_ERROR_M 0x1 -#define TLSRX_HDR_PKT_PAD_ERROR_V(x) ((x) << TLSRX_HDR_PKT_PAD_ERROR_S) -#define TLSRX_HDR_PKT_PAD_ERROR_G(x) \ - (((x) >> TLSRX_HDR_PKT_PAD_ERROR_S) & TLSRX_HDR_PKT_PAD_ERROR_M) -#define TLSRX_HDR_PKT_PAD_ERROR_F TLSRX_HDR_PKT_PAD_ERROR_V(1U) - -#define TLSRX_HDR_PKT_MAC_ERROR_S 0 -#define TLSRX_HDR_PKT_MAC_ERROR_M 0x1 -#define TLSRX_HDR_PKT_MAC_ERROR_V(x) ((x) << TLSRX_HDR_PKT_MAC_ERROR) -#define TLSRX_HDR_PKT_MAC_ERROR_G(x) \ - (((x) >> S_TLSRX_HDR_PKT_MAC_ERROR_S) & TLSRX_HDR_PKT_MAC_ERROR_M) -#define TLSRX_HDR_PKT_MAC_ERROR_F TLSRX_HDR_PKT_MAC_ERROR_V(1U) - -#define TLSRX_HDR_PKT_ERROR_M 0x1F -#define CONTENT_TYPE_ERROR 0x7F - -struct ulp_mem_rw { - __be32 cmd; - __be32 len16; /* command length */ - __be32 dlen; /* data length in 32-byte units */ - __be32 lock_addr; -}; - -struct tls_key_wr { - __be32 op_to_compl; - __be32 flowid_len16; - __be32 ftid; - u8 reneg_to_write_rx; - u8 protocol; - __be16 mfs; -}; - -struct tls_key_req { - struct tls_key_wr wr; - struct ulp_mem_rw req; - struct ulptx_idata sc_imm; -}; - -/* - * This lives in skb->cb and is used to chain WRs in a linked list. - */ -struct wr_skb_cb { - struct l2t_skb_cb l2t; /* reserve space for l2t CB */ - struct sk_buff *next_wr; /* next write request */ -}; - -/* Per-skb backlog handler. Run when a socket's backlog is processed. */ -struct blog_skb_cb { - void (*backlog_rcv)(struct sock *sk, struct sk_buff *skb); - struct chtls_dev *cdev; -}; - -/* - * Similar to tcp_skb_cb but with ULP elements added to support TLS, - * etc. - */ -struct ulp_skb_cb { - struct wr_skb_cb wr; /* reserve space for write request */ - u16 flags; /* TCP-like flags */ - u8 psh; - u8 ulp_mode; /* ULP mode/submode of sk_buff */ - u32 seq; /* TCP sequence number */ - union { /* ULP-specific fields */ - struct { - u8 type; - u8 ofld; - u8 iv; - } tls; - } ulp; -}; - -#define ULP_SKB_CB(skb) ((struct ulp_skb_cb *)&((skb)->cb[0])) -#define BLOG_SKB_CB(skb) ((struct blog_skb_cb *)(skb)->cb) - -/* - * Flags for ulp_skb_cb.flags. - */ -enum { - ULPCB_FLAG_NEED_HDR = 1 << 0, /* packet needs a TX_DATA_WR header */ - ULPCB_FLAG_NO_APPEND = 1 << 1, /* don't grow this skb */ - ULPCB_FLAG_BARRIER = 1 << 2, /* set TX_WAIT_IDLE after sending */ - ULPCB_FLAG_HOLD = 1 << 3, /* skb not ready for Tx yet */ - ULPCB_FLAG_COMPL = 1 << 4, /* request WR completion */ - ULPCB_FLAG_URG = 1 << 5, /* urgent data */ - ULPCB_FLAG_TLS_HDR = 1 << 6, /* payload with tls hdr */ - ULPCB_FLAG_NO_HDR = 1 << 7, /* not a ofld wr */ -}; - -/* The ULP mode/submode of an skbuff */ -#define skb_ulp_mode(skb) (ULP_SKB_CB(skb)->ulp_mode) -#define TCP_PAGE(sk) (sk->sk_frag.page) -#define TCP_OFF(sk) (sk->sk_frag.offset) - -static inline struct chtls_dev *to_chtls_dev(struct tls_toe_device *tlsdev) -{ - return container_of(tlsdev, struct chtls_dev, tlsdev); -} - -static inline void csk_set_flag(struct chtls_sock *csk, - enum csk_flags flag) -{ - __set_bit(flag, &csk->flags); -} - -static inline void csk_reset_flag(struct chtls_sock *csk, - enum csk_flags flag) -{ - __clear_bit(flag, &csk->flags); -} - -static inline bool csk_conn_inline(const struct chtls_sock *csk) -{ - return test_bit(CSK_CONN_INLINE, &csk->flags); -} - -static inline int csk_flag(const struct sock *sk, enum csk_flags flag) -{ - struct chtls_sock *csk = rcu_dereference_sk_user_data(sk); - - if (!csk_conn_inline(csk)) - return 0; - return test_bit(flag, &csk->flags); -} - -static inline int csk_flag_nochk(const struct chtls_sock *csk, - enum csk_flags flag) -{ - return test_bit(flag, &csk->flags); -} - -static inline void *cplhdr(struct sk_buff *skb) -{ - return skb->data; -} - -static inline int is_neg_adv(unsigned int status) -{ - return status == CPL_ERR_RTX_NEG_ADVICE || - status == CPL_ERR_KEEPALV_NEG_ADVICE || - status == CPL_ERR_PERSIST_NEG_ADVICE; -} - -static inline void process_cpl_msg(void (*fn)(struct sock *, struct sk_buff *), - struct sock *sk, - struct sk_buff *skb) -{ - skb_reset_mac_header(skb); - skb_reset_network_header(skb); - skb_reset_transport_header(skb); - - bh_lock_sock(sk); - if (unlikely(sock_owned_by_user(sk))) { - BLOG_SKB_CB(skb)->backlog_rcv = fn; - __sk_add_backlog(sk, skb); - } else { - fn(sk, skb); - } - bh_unlock_sock(sk); -} - -static inline void chtls_sock_free(struct kref *ref) -{ - struct chtls_sock *csk = container_of(ref, struct chtls_sock, - kref); - kfree(csk); -} - -static inline void __chtls_sock_put(const char *fn, struct chtls_sock *csk) -{ - kref_put(&csk->kref, chtls_sock_free); -} - -static inline void __chtls_sock_get(const char *fn, - struct chtls_sock *csk) -{ - kref_get(&csk->kref); -} - -static inline void send_or_defer(struct sock *sk, struct tcp_sock *tp, - struct sk_buff *skb, int through_l2t) -{ - struct chtls_sock *csk = rcu_dereference_sk_user_data(sk); - - if (through_l2t) { - /* send through L2T */ - cxgb4_l2t_send(csk->egress_dev, skb, csk->l2t_entry); - } else { - /* send directly */ - cxgb4_ofld_send(csk->egress_dev, skb); - } -} - -typedef int (*chtls_handler_func)(struct chtls_dev *, struct sk_buff *); -extern chtls_handler_func chtls_handlers[NUM_CPL_CMDS]; -void chtls_install_cpl_ops(struct sock *sk); -int chtls_init_kmap(struct chtls_dev *cdev, struct cxgb4_lld_info *lldi); -void chtls_listen_stop(struct chtls_dev *cdev, struct sock *sk); -int chtls_listen_start(struct chtls_dev *cdev, struct sock *sk); -void chtls_close(struct sock *sk, long timeout); -int chtls_disconnect(struct sock *sk, int flags); -void chtls_shutdown(struct sock *sk, int how); -void chtls_destroy_sock(struct sock *sk); -int chtls_sendmsg(struct sock *sk, struct msghdr *msg, size_t size); -int chtls_recvmsg(struct sock *sk, struct msghdr *msg, - size_t len, int flags); -void chtls_splice_eof(struct socket *sock); -int send_tx_flowc_wr(struct sock *sk, int compl, - u32 snd_nxt, u32 rcv_nxt); -void chtls_tcp_push(struct sock *sk, int flags); -int chtls_push_frames(struct chtls_sock *csk, int comp); -void chtls_set_tcb_field_rpl_skb(struct sock *sk, u16 word, - u64 mask, u64 val, u8 cookie, - int through_l2t); -int chtls_setkey(struct chtls_sock *csk, u32 keylen, u32 mode, int cipher_type); -void chtls_set_quiesce_ctrl(struct sock *sk, int val); -void skb_entail(struct sock *sk, struct sk_buff *skb, int flags); -unsigned int keyid_to_addr(int start_addr, int keyid); -void free_tls_keyid(struct sock *sk); -#endif diff --git a/drivers/net/ethernet/chelsio/inline_crypto/chtls/chtls_cm.c b/drivers/net/ethernet/chelsio/inline_crypto/chtls/chtls_cm.c deleted file mode 100644 index 0e3e5cf52c2c..000000000000 --- a/drivers/net/ethernet/chelsio/inline_crypto/chtls/chtls_cm.c +++ /dev/null @@ -1,2336 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (c) 2018 Chelsio Communications, Inc. - * - * Written by: Atul Gupta (atul.gupta@chelsio.com) - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "chtls.h" -#include "chtls_cm.h" -#include "clip_tbl.h" -#include "t4_tcb.h" - -/* - * State transitions and actions for close. Note that if we are in SYN_SENT - * we remain in that state as we cannot control a connection while it's in - * SYN_SENT; such connections are allowed to establish and are then aborted. - */ -static unsigned char new_state[16] = { - /* current state: new state: action: */ - /* (Invalid) */ TCP_CLOSE, - /* TCP_ESTABLISHED */ TCP_FIN_WAIT1 | TCP_ACTION_FIN, - /* TCP_SYN_SENT */ TCP_SYN_SENT, - /* TCP_SYN_RECV */ TCP_FIN_WAIT1 | TCP_ACTION_FIN, - /* TCP_FIN_WAIT1 */ TCP_FIN_WAIT1, - /* TCP_FIN_WAIT2 */ TCP_FIN_WAIT2, - /* TCP_TIME_WAIT */ TCP_CLOSE, - /* TCP_CLOSE */ TCP_CLOSE, - /* TCP_CLOSE_WAIT */ TCP_LAST_ACK | TCP_ACTION_FIN, - /* TCP_LAST_ACK */ TCP_LAST_ACK, - /* TCP_LISTEN */ TCP_CLOSE, - /* TCP_CLOSING */ TCP_CLOSING, -}; - -static struct chtls_sock *chtls_sock_create(struct chtls_dev *cdev) -{ - struct chtls_sock *csk = kzalloc_obj(*csk, GFP_ATOMIC); - - if (!csk) - return NULL; - - csk->txdata_skb_cache = alloc_skb(TXDATA_SKB_LEN, GFP_ATOMIC); - if (!csk->txdata_skb_cache) { - kfree(csk); - return NULL; - } - - kref_init(&csk->kref); - csk->cdev = cdev; - skb_queue_head_init(&csk->txq); - csk->wr_skb_head = NULL; - csk->wr_skb_tail = NULL; - csk->mss = MAX_MSS; - csk->tlshws.ofld = 1; - csk->tlshws.txkey = -1; - csk->tlshws.rxkey = -1; - csk->tlshws.mfs = TLS_MFS; - skb_queue_head_init(&csk->tlshws.sk_recv_queue); - return csk; -} - -static void chtls_sock_release(struct kref *ref) -{ - struct chtls_sock *csk = - container_of(ref, struct chtls_sock, kref); - - kfree(csk); -} - -static struct net_device *chtls_find_netdev(struct chtls_dev *cdev, - struct sock *sk) -{ - struct adapter *adap = pci_get_drvdata(cdev->pdev); - struct net_device *ndev = cdev->ports[0]; -#if IS_ENABLED(CONFIG_IPV6) - struct net_device *temp; - int addr_type; -#endif - int i; - - switch (sk->sk_family) { - case PF_INET: - if (likely(!inet_sk(sk)->inet_rcv_saddr)) - return ndev; - ndev = __ip_dev_find(&init_net, inet_sk(sk)->inet_rcv_saddr, false); - break; -#if IS_ENABLED(CONFIG_IPV6) - case PF_INET6: - addr_type = ipv6_addr_type(&sk->sk_v6_rcv_saddr); - if (likely(addr_type == IPV6_ADDR_ANY)) - return ndev; - - for_each_netdev_rcu(&init_net, temp) { - if (ipv6_chk_addr(&init_net, (struct in6_addr *) - &sk->sk_v6_rcv_saddr, temp, 1)) { - ndev = temp; - break; - } - } - break; -#endif - default: - return NULL; - } - - if (!ndev) - return NULL; - - if (is_vlan_dev(ndev)) - ndev = vlan_dev_real_dev(ndev); - - for_each_port(adap, i) - if (cdev->ports[i] == ndev) - return ndev; - return NULL; -} - -static void assign_rxopt(struct sock *sk, unsigned int opt) -{ - const struct chtls_dev *cdev; - struct chtls_sock *csk; - struct tcp_sock *tp; - - csk = rcu_dereference_sk_user_data(sk); - tp = tcp_sk(sk); - - cdev = csk->cdev; - tp->tcp_header_len = sizeof(struct tcphdr); - tp->rx_opt.mss_clamp = cdev->mtus[TCPOPT_MSS_G(opt)] - 40; - tp->mss_cache = tp->rx_opt.mss_clamp; - tp->rx_opt.tstamp_ok = TCPOPT_TSTAMP_G(opt); - tp->rx_opt.snd_wscale = TCPOPT_SACK_G(opt); - tp->rx_opt.wscale_ok = TCPOPT_WSCALE_OK_G(opt); - SND_WSCALE(tp) = TCPOPT_SND_WSCALE_G(opt); - if (!tp->rx_opt.wscale_ok) - tp->rx_opt.rcv_wscale = 0; - if (tp->rx_opt.tstamp_ok) { - tp->tcp_header_len += TCPOLEN_TSTAMP_ALIGNED; - tp->rx_opt.mss_clamp -= TCPOLEN_TSTAMP_ALIGNED; - } else if (csk->opt2 & TSTAMPS_EN_F) { - csk->opt2 &= ~TSTAMPS_EN_F; - csk->mtu_idx = TCPOPT_MSS_G(opt); - } -} - -static void chtls_purge_receive_queue(struct sock *sk) -{ - struct sk_buff *skb; - - while ((skb = __skb_dequeue(&sk->sk_receive_queue)) != NULL) { - skb_dstref_steal(skb); - kfree_skb(skb); - } -} - -static void chtls_purge_write_queue(struct sock *sk) -{ - struct chtls_sock *csk = rcu_dereference_sk_user_data(sk); - struct sk_buff *skb; - - while ((skb = __skb_dequeue(&csk->txq))) { - sk->sk_wmem_queued -= skb->truesize; - __kfree_skb(skb); - } -} - -static void chtls_purge_recv_queue(struct sock *sk) -{ - struct chtls_sock *csk = rcu_dereference_sk_user_data(sk); - struct chtls_hws *tlsk = &csk->tlshws; - struct sk_buff *skb; - - while ((skb = __skb_dequeue(&tlsk->sk_recv_queue)) != NULL) { - skb_dstref_steal(skb); - kfree_skb(skb); - } -} - -static void abort_arp_failure(void *handle, struct sk_buff *skb) -{ - struct cpl_abort_req *req = cplhdr(skb); - struct chtls_dev *cdev; - - cdev = (struct chtls_dev *)handle; - req->cmd = CPL_ABORT_NO_RST; - cxgb4_ofld_send(cdev->lldi->ports[0], skb); -} - -static struct sk_buff *alloc_ctrl_skb(struct sk_buff *skb, int len) -{ - if (likely(skb && !skb_shared(skb) && !skb_cloned(skb))) { - __skb_trim(skb, 0); - refcount_inc(&skb->users); - } else { - skb = alloc_skb(len, GFP_KERNEL | __GFP_NOFAIL); - } - return skb; -} - -static void chtls_send_abort(struct sock *sk, int mode, struct sk_buff *skb) -{ - struct cpl_abort_req *req; - struct chtls_sock *csk; - struct tcp_sock *tp; - - csk = rcu_dereference_sk_user_data(sk); - tp = tcp_sk(sk); - - if (!skb) - skb = alloc_ctrl_skb(csk->txdata_skb_cache, sizeof(*req)); - - req = (struct cpl_abort_req *)skb_put(skb, sizeof(*req)); - INIT_TP_WR_CPL(req, CPL_ABORT_REQ, csk->tid); - skb_set_queue_mapping(skb, (csk->txq_idx << 1) | CPL_PRIORITY_DATA); - req->rsvd0 = htonl(tp->snd_nxt); - req->rsvd1 = !csk_flag_nochk(csk, CSK_TX_DATA_SENT); - req->cmd = mode; - t4_set_arp_err_handler(skb, csk->cdev, abort_arp_failure); - send_or_defer(sk, tp, skb, mode == CPL_ABORT_SEND_RST); -} - -static void chtls_send_reset(struct sock *sk, int mode, struct sk_buff *skb) -{ - struct chtls_sock *csk = rcu_dereference_sk_user_data(sk); - - if (unlikely(csk_flag_nochk(csk, CSK_ABORT_SHUTDOWN) || - !csk->cdev)) { - if (sk->sk_state == TCP_SYN_RECV) - csk_set_flag(csk, CSK_RST_ABORTED); - goto out; - } - - if (!csk_flag_nochk(csk, CSK_TX_DATA_SENT)) { - struct tcp_sock *tp = tcp_sk(sk); - - if (send_tx_flowc_wr(sk, 0, tp->snd_nxt, tp->rcv_nxt) < 0) - WARN_ONCE(1, "send tx flowc error"); - csk_set_flag(csk, CSK_TX_DATA_SENT); - } - - csk_set_flag(csk, CSK_ABORT_RPL_PENDING); - chtls_purge_write_queue(sk); - - csk_set_flag(csk, CSK_ABORT_SHUTDOWN); - if (sk->sk_state != TCP_SYN_RECV) - chtls_send_abort(sk, mode, skb); - else - chtls_set_tcb_field_rpl_skb(sk, TCB_T_FLAGS_W, - TCB_T_FLAGS_V(TCB_T_FLAGS_M), 0, - TCB_FIELD_COOKIE_TFLAG, 1); - - return; -out: - kfree_skb(skb); -} - -static void release_tcp_port(struct sock *sk) -{ - if (inet_csk(sk)->icsk_bind_hash) - inet_put_port(sk); -} - -static void tcp_uncork(struct sock *sk) -{ - struct tcp_sock *tp = tcp_sk(sk); - - if (tp->nonagle & TCP_NAGLE_CORK) { - tp->nonagle &= ~TCP_NAGLE_CORK; - chtls_tcp_push(sk, 0); - } -} - -static void chtls_close_conn(struct sock *sk) -{ - struct cpl_close_con_req *req; - struct chtls_sock *csk; - struct sk_buff *skb; - unsigned int tid; - unsigned int len; - - len = roundup(sizeof(struct cpl_close_con_req), 16); - csk = rcu_dereference_sk_user_data(sk); - tid = csk->tid; - - skb = alloc_skb(len, GFP_KERNEL | __GFP_NOFAIL); - req = (struct cpl_close_con_req *)__skb_put(skb, len); - memset(req, 0, len); - req->wr.wr_hi = htonl(FW_WR_OP_V(FW_TP_WR) | - FW_WR_IMMDLEN_V(sizeof(*req) - - sizeof(req->wr))); - req->wr.wr_mid = htonl(FW_WR_LEN16_V(DIV_ROUND_UP(sizeof(*req), 16)) | - FW_WR_FLOWID_V(tid)); - - OPCODE_TID(req) = htonl(MK_OPCODE_TID(CPL_CLOSE_CON_REQ, tid)); - - tcp_uncork(sk); - skb_entail(sk, skb, ULPCB_FLAG_NO_HDR | ULPCB_FLAG_NO_APPEND); - if (sk->sk_state != TCP_SYN_SENT) - chtls_push_frames(csk, 1); -} - -/* - * Perform a state transition during close and return the actions indicated - * for the transition. Do not make this function inline, the main reason - * it exists at all is to avoid multiple inlining of tcp_set_state. - */ -static int make_close_transition(struct sock *sk) -{ - int next = (int)new_state[sk->sk_state]; - - tcp_set_state(sk, next & TCP_STATE_MASK); - return next & TCP_ACTION_FIN; -} - -void chtls_close(struct sock *sk, long timeout) -{ - int data_lost, prev_state; - struct chtls_sock *csk; - - csk = rcu_dereference_sk_user_data(sk); - - lock_sock(sk); - sk->sk_shutdown |= SHUTDOWN_MASK; - - data_lost = skb_queue_len(&sk->sk_receive_queue); - data_lost |= skb_queue_len(&csk->tlshws.sk_recv_queue); - chtls_purge_recv_queue(sk); - chtls_purge_receive_queue(sk); - - if (sk->sk_state == TCP_CLOSE) { - goto wait; - } else if (data_lost || sk->sk_state == TCP_SYN_SENT) { - chtls_send_reset(sk, CPL_ABORT_SEND_RST, NULL); - release_tcp_port(sk); - goto unlock; - } else if (sock_flag(sk, SOCK_LINGER) && !sk->sk_lingertime) { - sk->sk_prot->disconnect(sk, 0); - } else if (make_close_transition(sk)) { - chtls_close_conn(sk); - } -wait: - if (timeout) - sk_stream_wait_close(sk, timeout); - -unlock: - prev_state = sk->sk_state; - sock_hold(sk); - sock_orphan(sk); - - release_sock(sk); - - local_bh_disable(); - bh_lock_sock(sk); - - if (prev_state != TCP_CLOSE && sk->sk_state == TCP_CLOSE) - goto out; - - if (sk->sk_state == TCP_FIN_WAIT2 && tcp_sk(sk)->linger2 < 0 && - !csk_flag(sk, CSK_ABORT_SHUTDOWN)) { - struct sk_buff *skb; - - skb = alloc_skb(sizeof(struct cpl_abort_req), GFP_ATOMIC); - if (skb) - chtls_send_reset(sk, CPL_ABORT_SEND_RST, skb); - } - - if (sk->sk_state == TCP_CLOSE) - inet_csk_destroy_sock(sk); - -out: - bh_unlock_sock(sk); - local_bh_enable(); - sock_put(sk); -} - -/* - * Wait until a socket enters on of the given states. - */ -static int wait_for_states(struct sock *sk, unsigned int states) -{ - DECLARE_WAITQUEUE(wait, current); - struct socket_wq _sk_wq; - long current_timeo; - int err = 0; - - current_timeo = 200; - - /* - * We want this to work even when there's no associated struct socket. - * In that case we provide a temporary wait_queue_head_t. - */ - if (!sk->sk_wq) { - init_waitqueue_head(&_sk_wq.wait); - _sk_wq.fasync_list = NULL; - init_rcu_head_on_stack(&_sk_wq.rcu); - RCU_INIT_POINTER(sk->sk_wq, &_sk_wq); - } - - add_wait_queue(sk_sleep(sk), &wait); - while (!sk_in_state(sk, states)) { - if (!current_timeo) { - err = -EBUSY; - break; - } - if (signal_pending(current)) { - err = sock_intr_errno(current_timeo); - break; - } - set_current_state(TASK_UNINTERRUPTIBLE); - release_sock(sk); - if (!sk_in_state(sk, states)) - current_timeo = schedule_timeout(current_timeo); - __set_current_state(TASK_RUNNING); - lock_sock(sk); - } - remove_wait_queue(sk_sleep(sk), &wait); - - if (rcu_dereference(sk->sk_wq) == &_sk_wq) - sk->sk_wq = NULL; - return err; -} - -int chtls_disconnect(struct sock *sk, int flags) -{ - struct tcp_sock *tp; - int err; - - tp = tcp_sk(sk); - chtls_purge_recv_queue(sk); - chtls_purge_receive_queue(sk); - chtls_purge_write_queue(sk); - - if (sk->sk_state != TCP_CLOSE) { - sk->sk_err = ECONNRESET; - chtls_send_reset(sk, CPL_ABORT_SEND_RST, NULL); - err = wait_for_states(sk, TCPF_CLOSE); - if (err) - return err; - } - chtls_purge_recv_queue(sk); - chtls_purge_receive_queue(sk); - tp->max_window = 0xFFFF << (tp->rx_opt.snd_wscale); - return tcp_disconnect(sk, flags); -} - -#define SHUTDOWN_ELIGIBLE_STATE (TCPF_ESTABLISHED | \ - TCPF_SYN_RECV | TCPF_CLOSE_WAIT) -void chtls_shutdown(struct sock *sk, int how) -{ - if ((how & SEND_SHUTDOWN) && - sk_in_state(sk, SHUTDOWN_ELIGIBLE_STATE) && - make_close_transition(sk)) - chtls_close_conn(sk); -} - -void chtls_destroy_sock(struct sock *sk) -{ - struct chtls_sock *csk; - - csk = rcu_dereference_sk_user_data(sk); - chtls_purge_recv_queue(sk); - csk->ulp_mode = ULP_MODE_NONE; - chtls_purge_write_queue(sk); - free_tls_keyid(sk); - kref_put(&csk->kref, chtls_sock_release); - if (sk->sk_family == AF_INET) - sk->sk_prot = &tcp_prot; -#if IS_ENABLED(CONFIG_IPV6) - else - sk->sk_prot = &tcpv6_prot; -#endif - sk->sk_prot->destroy(sk); -} - -static void reset_listen_child(struct sock *child) -{ - struct chtls_sock *csk = rcu_dereference_sk_user_data(child); - struct sk_buff *skb; - - skb = alloc_ctrl_skb(csk->txdata_skb_cache, - sizeof(struct cpl_abort_req)); - - chtls_send_reset(child, CPL_ABORT_SEND_RST, skb); - sock_orphan(child); - tcp_orphan_count_inc(); - if (child->sk_state == TCP_CLOSE) - inet_csk_destroy_sock(child); -} - -static void chtls_disconnect_acceptq(struct sock *listen_sk) -{ - struct request_sock **pprev; - - pprev = ACCEPT_QUEUE(listen_sk); - while (*pprev) { - struct request_sock *req = *pprev; - - if (req->rsk_ops == &chtls_rsk_ops || - req->rsk_ops == &chtls_rsk_opsv6) { - struct sock *child = req->sk; - - *pprev = req->dl_next; - sk_acceptq_removed(listen_sk); - reqsk_put(req); - sock_hold(child); - local_bh_disable(); - bh_lock_sock(child); - release_tcp_port(child); - reset_listen_child(child); - bh_unlock_sock(child); - local_bh_enable(); - sock_put(child); - } else { - pprev = &req->dl_next; - } - } -} - -static int listen_hashfn(const struct sock *sk) -{ - return ((unsigned long)sk >> 10) & (LISTEN_INFO_HASH_SIZE - 1); -} - -static struct listen_info *listen_hash_add(struct chtls_dev *cdev, - struct sock *sk, - unsigned int stid) -{ - struct listen_info *p = kmalloc_obj(*p); - - if (p) { - int key = listen_hashfn(sk); - - p->sk = sk; - p->stid = stid; - spin_lock(&cdev->listen_lock); - p->next = cdev->listen_hash_tab[key]; - cdev->listen_hash_tab[key] = p; - spin_unlock(&cdev->listen_lock); - } - return p; -} - -static int listen_hash_find(struct chtls_dev *cdev, - struct sock *sk) -{ - struct listen_info *p; - int stid = -1; - int key; - - key = listen_hashfn(sk); - - spin_lock(&cdev->listen_lock); - for (p = cdev->listen_hash_tab[key]; p; p = p->next) - if (p->sk == sk) { - stid = p->stid; - break; - } - spin_unlock(&cdev->listen_lock); - return stid; -} - -static int listen_hash_del(struct chtls_dev *cdev, - struct sock *sk) -{ - struct listen_info *p, **prev; - int stid = -1; - int key; - - key = listen_hashfn(sk); - prev = &cdev->listen_hash_tab[key]; - - spin_lock(&cdev->listen_lock); - for (p = *prev; p; prev = &p->next, p = p->next) - if (p->sk == sk) { - stid = p->stid; - *prev = p->next; - kfree(p); - break; - } - spin_unlock(&cdev->listen_lock); - return stid; -} - -static void cleanup_syn_rcv_conn(struct sock *child, struct sock *parent) -{ - struct request_sock *req; - struct chtls_sock *csk; - - csk = rcu_dereference_sk_user_data(child); - req = csk->passive_reap_next; - - reqsk_queue_removed(&inet_csk(parent)->icsk_accept_queue, req); - __skb_unlink((struct sk_buff *)&csk->synq, &csk->listen_ctx->synq); - chtls_reqsk_free(req); - csk->passive_reap_next = NULL; -} - -static void chtls_reset_synq(struct listen_ctx *listen_ctx) -{ - struct sock *listen_sk = listen_ctx->lsk; - - while (!skb_queue_empty(&listen_ctx->synq)) { - struct chtls_sock *csk = - container_of((struct synq *)skb_peek - (&listen_ctx->synq), struct chtls_sock, synq); - struct sock *child = csk->sk; - - cleanup_syn_rcv_conn(child, listen_sk); - sock_hold(child); - local_bh_disable(); - bh_lock_sock(child); - release_tcp_port(child); - reset_listen_child(child); - bh_unlock_sock(child); - local_bh_enable(); - sock_put(child); - } -} - -int chtls_listen_start(struct chtls_dev *cdev, struct sock *sk) -{ - struct net_device *ndev; -#if IS_ENABLED(CONFIG_IPV6) - bool clip_valid = false; -#endif - struct listen_ctx *ctx; - struct adapter *adap; - struct port_info *pi; - int ret = 0; - int stid; - - rcu_read_lock(); - ndev = chtls_find_netdev(cdev, sk); - rcu_read_unlock(); - if (!ndev) - return -EBADF; - - pi = netdev_priv(ndev); - adap = pi->adapter; - if (!(adap->flags & CXGB4_FULL_INIT_DONE)) - return -EBADF; - - if (listen_hash_find(cdev, sk) >= 0) /* already have it */ - return -EADDRINUSE; - - ctx = kmalloc_obj(*ctx); - if (!ctx) - return -ENOMEM; - - __module_get(THIS_MODULE); - ctx->lsk = sk; - ctx->cdev = cdev; - ctx->state = T4_LISTEN_START_PENDING; - skb_queue_head_init(&ctx->synq); - - stid = cxgb4_alloc_stid(cdev->tids, sk->sk_family, ctx); - if (stid < 0) - goto free_ctx; - - sock_hold(sk); - if (!listen_hash_add(cdev, sk, stid)) - goto free_stid; - - if (sk->sk_family == PF_INET) { - ret = cxgb4_create_server(ndev, stid, - inet_sk(sk)->inet_rcv_saddr, - inet_sk(sk)->inet_sport, 0, - cdev->lldi->rxq_ids[0]); -#if IS_ENABLED(CONFIG_IPV6) - } else { - int addr_type; - - addr_type = ipv6_addr_type(&sk->sk_v6_rcv_saddr); - if (addr_type != IPV6_ADDR_ANY) { - ret = cxgb4_clip_get(ndev, (const u32 *) - &sk->sk_v6_rcv_saddr, 1); - if (ret) - goto del_hash; - clip_valid = true; - } - ret = cxgb4_create_server6(ndev, stid, - &sk->sk_v6_rcv_saddr, - inet_sk(sk)->inet_sport, - cdev->lldi->rxq_ids[0]); -#endif - } - if (ret > 0) - ret = net_xmit_errno(ret); - if (ret) - goto del_hash; - return 0; -del_hash: -#if IS_ENABLED(CONFIG_IPV6) - if (clip_valid) - cxgb4_clip_release(ndev, (const u32 *)&sk->sk_v6_rcv_saddr, 1); -#endif - listen_hash_del(cdev, sk); -free_stid: - cxgb4_free_stid(cdev->tids, stid, sk->sk_family); - sock_put(sk); -free_ctx: - kfree(ctx); - module_put(THIS_MODULE); - return -EBADF; -} - -void chtls_listen_stop(struct chtls_dev *cdev, struct sock *sk) -{ - struct listen_ctx *listen_ctx; - int stid; - - stid = listen_hash_del(cdev, sk); - if (stid < 0) - return; - - listen_ctx = (struct listen_ctx *)lookup_stid(cdev->tids, stid); - chtls_reset_synq(listen_ctx); - - cxgb4_remove_server(cdev->lldi->ports[0], stid, - cdev->lldi->rxq_ids[0], sk->sk_family == PF_INET6); - -#if IS_ENABLED(CONFIG_IPV6) - if (sk->sk_family == PF_INET6) { - struct net_device *ndev = chtls_find_netdev(cdev, sk); - int addr_type = 0; - - addr_type = ipv6_addr_type((const struct in6_addr *) - &sk->sk_v6_rcv_saddr); - if (addr_type != IPV6_ADDR_ANY) - cxgb4_clip_release(ndev, (const u32 *) - &sk->sk_v6_rcv_saddr, 1); - } -#endif - chtls_disconnect_acceptq(sk); -} - -static int chtls_pass_open_rpl(struct chtls_dev *cdev, struct sk_buff *skb) -{ - struct cpl_pass_open_rpl *rpl = cplhdr(skb) + RSS_HDR; - unsigned int stid = GET_TID(rpl); - struct listen_ctx *listen_ctx; - - listen_ctx = (struct listen_ctx *)lookup_stid(cdev->tids, stid); - if (!listen_ctx) - return CPL_RET_BUF_DONE; - - if (listen_ctx->state == T4_LISTEN_START_PENDING) { - listen_ctx->state = T4_LISTEN_STARTED; - return CPL_RET_BUF_DONE; - } - - if (rpl->status != CPL_ERR_NONE) { - pr_info("Unexpected PASS_OPEN_RPL status %u for STID %u\n", - rpl->status, stid); - } else { - cxgb4_free_stid(cdev->tids, stid, listen_ctx->lsk->sk_family); - sock_put(listen_ctx->lsk); - kfree(listen_ctx); - module_put(THIS_MODULE); - } - return CPL_RET_BUF_DONE; -} - -static int chtls_close_listsrv_rpl(struct chtls_dev *cdev, struct sk_buff *skb) -{ - struct cpl_close_listsvr_rpl *rpl = cplhdr(skb) + RSS_HDR; - struct listen_ctx *listen_ctx; - unsigned int stid; - void *data; - - stid = GET_TID(rpl); - data = lookup_stid(cdev->tids, stid); - listen_ctx = (struct listen_ctx *)data; - - if (rpl->status != CPL_ERR_NONE) { - pr_info("Unexpected CLOSE_LISTSRV_RPL status %u for STID %u\n", - rpl->status, stid); - } else { - cxgb4_free_stid(cdev->tids, stid, listen_ctx->lsk->sk_family); - sock_put(listen_ctx->lsk); - kfree(listen_ctx); - module_put(THIS_MODULE); - } - return CPL_RET_BUF_DONE; -} - -static void chtls_purge_wr_queue(struct sock *sk) -{ - struct sk_buff *skb; - - while ((skb = dequeue_wr(sk)) != NULL) - kfree_skb(skb); -} - -static void chtls_release_resources(struct sock *sk) -{ - struct chtls_sock *csk = rcu_dereference_sk_user_data(sk); - struct chtls_dev *cdev = csk->cdev; - unsigned int tid = csk->tid; - struct tid_info *tids; - - if (!cdev) - return; - - tids = cdev->tids; - kfree_skb(csk->txdata_skb_cache); - csk->txdata_skb_cache = NULL; - - if (csk->wr_credits != csk->wr_max_credits) { - chtls_purge_wr_queue(sk); - chtls_reset_wr_list(csk); - } - - if (csk->l2t_entry) { - cxgb4_l2t_release(csk->l2t_entry); - csk->l2t_entry = NULL; - } - - if (sk->sk_state != TCP_SYN_SENT) { - cxgb4_remove_tid(tids, csk->port_id, tid, sk->sk_family); - sock_put(sk); - } -} - -static void chtls_conn_done(struct sock *sk) -{ - if (sock_flag(sk, SOCK_DEAD)) - chtls_purge_receive_queue(sk); - sk_wakeup_sleepers(sk, 0); - tcp_done(sk); -} - -static void do_abort_syn_rcv(struct sock *child, struct sock *parent) -{ - /* - * If the server is still open we clean up the child connection, - * otherwise the server already did the clean up as it was purging - * its SYN queue and the skb was just sitting in its backlog. - */ - if (likely(parent->sk_state == TCP_LISTEN)) { - cleanup_syn_rcv_conn(child, parent); - /* Without the below call to sock_orphan, - * we leak the socket resource with syn_flood test - * as inet_csk_destroy_sock will not be called - * in tcp_done since SOCK_DEAD flag is not set. - * Kernel handles this differently where new socket is - * created only after 3 way handshake is done. - */ - sock_orphan(child); - tcp_orphan_count_inc(); - chtls_release_resources(child); - chtls_conn_done(child); - } else { - if (csk_flag(child, CSK_RST_ABORTED)) { - chtls_release_resources(child); - chtls_conn_done(child); - } - } -} - -static void pass_open_abort(struct sock *child, struct sock *parent, - struct sk_buff *skb) -{ - do_abort_syn_rcv(child, parent); - kfree_skb(skb); -} - -static void bl_pass_open_abort(struct sock *lsk, struct sk_buff *skb) -{ - pass_open_abort(skb->sk, lsk, skb); -} - -static void chtls_pass_open_arp_failure(struct sock *sk, - struct sk_buff *skb) -{ - const struct request_sock *oreq; - struct chtls_sock *csk; - struct chtls_dev *cdev; - struct sock *parent; - void *data; - - csk = rcu_dereference_sk_user_data(sk); - cdev = csk->cdev; - - /* - * If the connection is being aborted due to the parent listening - * socket going away there's nothing to do, the ABORT_REQ will close - * the connection. - */ - if (csk_flag(sk, CSK_ABORT_RPL_PENDING)) { - kfree_skb(skb); - return; - } - - oreq = csk->passive_reap_next; - data = lookup_stid(cdev->tids, oreq->ts_recent); - parent = ((struct listen_ctx *)data)->lsk; - - bh_lock_sock(parent); - if (!sock_owned_by_user(parent)) { - pass_open_abort(sk, parent, skb); - } else { - BLOG_SKB_CB(skb)->backlog_rcv = bl_pass_open_abort; - __sk_add_backlog(parent, skb); - } - bh_unlock_sock(parent); -} - -static void chtls_accept_rpl_arp_failure(void *handle, - struct sk_buff *skb) -{ - struct sock *sk = (struct sock *)handle; - - sock_hold(sk); - process_cpl_msg(chtls_pass_open_arp_failure, sk, skb); - sock_put(sk); -} - -static unsigned int chtls_select_mss(const struct chtls_sock *csk, - unsigned int pmtu, - struct cpl_pass_accept_req *req) -{ - struct chtls_dev *cdev; - struct dst_entry *dst; - unsigned int tcpoptsz; - unsigned int iphdrsz; - unsigned int mtu_idx; - struct tcp_sock *tp; - unsigned int mss; - struct sock *sk; - u16 user_mss; - - mss = ntohs(req->tcpopt.mss); - sk = csk->sk; - dst = __sk_dst_get(sk); - cdev = csk->cdev; - tp = tcp_sk(sk); - tcpoptsz = 0; - -#if IS_ENABLED(CONFIG_IPV6) - if (sk->sk_family == AF_INET6) - iphdrsz = sizeof(struct ipv6hdr) + sizeof(struct tcphdr); - else -#endif - iphdrsz = sizeof(struct iphdr) + sizeof(struct tcphdr); - if (req->tcpopt.tstamp) - tcpoptsz += round_up(TCPOLEN_TIMESTAMP, 4); - - tp->advmss = dst_metric_advmss(dst); - user_mss = USER_MSS(tp); - if (user_mss && tp->advmss > user_mss) - tp->advmss = user_mss; - if (tp->advmss > pmtu - iphdrsz) - tp->advmss = pmtu - iphdrsz; - if (mss && tp->advmss > mss) - tp->advmss = mss; - - tp->advmss = cxgb4_best_aligned_mtu(cdev->lldi->mtus, - iphdrsz + tcpoptsz, - tp->advmss - tcpoptsz, - 8, &mtu_idx); - tp->advmss -= iphdrsz; - - inet_csk(sk)->icsk_pmtu_cookie = pmtu; - return mtu_idx; -} - -static unsigned int select_rcv_wscale(int space, int wscale_ok, int win_clamp) -{ - int wscale = 0; - - if (space > MAX_RCV_WND) - space = MAX_RCV_WND; - if (win_clamp && win_clamp < space) - space = win_clamp; - - if (wscale_ok) { - while (wscale < 14 && (65535 << wscale) < space) - wscale++; - } - return wscale; -} - -static void chtls_pass_accept_rpl(struct sk_buff *skb, - struct cpl_pass_accept_req *req, - unsigned int tid) - -{ - struct cpl_t5_pass_accept_rpl *rpl5; - struct cxgb4_lld_info *lldi; - const struct tcphdr *tcph; - const struct tcp_sock *tp; - struct chtls_sock *csk; - unsigned int len; - struct sock *sk; - u32 opt2, hlen; - u64 opt0; - - sk = skb->sk; - tp = tcp_sk(sk); - csk = sk->sk_user_data; - csk->tid = tid; - lldi = csk->cdev->lldi; - len = roundup(sizeof(*rpl5), 16); - - rpl5 = __skb_put_zero(skb, len); - INIT_TP_WR(rpl5, tid); - - OPCODE_TID(rpl5) = cpu_to_be32(MK_OPCODE_TID(CPL_PASS_ACCEPT_RPL, - csk->tid)); - csk->mtu_idx = chtls_select_mss(csk, dst_mtu(__sk_dst_get(sk)), - req); - opt0 = TCAM_BYPASS_F | - WND_SCALE_V(RCV_WSCALE(tp)) | - MSS_IDX_V(csk->mtu_idx) | - L2T_IDX_V(csk->l2t_entry->idx) | - NAGLE_V(!(tp->nonagle & TCP_NAGLE_OFF)) | - TX_CHAN_V(csk->tx_chan) | - SMAC_SEL_V(csk->smac_idx) | - DSCP_V(csk->tos >> 2) | - ULP_MODE_V(ULP_MODE_TLS) | - RCV_BUFSIZ_V(min(tp->rcv_wnd >> 10, RCV_BUFSIZ_M)); - - opt2 = RX_CHANNEL_V(0) | - RSS_QUEUE_VALID_F | RSS_QUEUE_V(csk->rss_qid); - - if (!is_t5(lldi->adapter_type)) - opt2 |= RX_FC_DISABLE_F; - if (req->tcpopt.tstamp) - opt2 |= TSTAMPS_EN_F; - if (req->tcpopt.sack) - opt2 |= SACK_EN_F; - hlen = ntohl(req->hdr_len); - - tcph = (struct tcphdr *)((u8 *)(req + 1) + - T6_ETH_HDR_LEN_G(hlen) + T6_IP_HDR_LEN_G(hlen)); - if (tcph->ece && tcph->cwr) - opt2 |= CCTRL_ECN_V(1); - opt2 |= CONG_CNTRL_V(CONG_ALG_NEWRENO); - opt2 |= T5_ISS_F; - opt2 |= T5_OPT_2_VALID_F; - opt2 |= WND_SCALE_EN_V(WSCALE_OK(tp)); - rpl5->opt0 = cpu_to_be64(opt0); - rpl5->opt2 = cpu_to_be32(opt2); - rpl5->iss = cpu_to_be32((get_random_u32() & ~7UL) - 1); - set_wr_txq(skb, CPL_PRIORITY_SETUP, csk->port_id); - t4_set_arp_err_handler(skb, sk, chtls_accept_rpl_arp_failure); - cxgb4_l2t_send(csk->egress_dev, skb, csk->l2t_entry); -} - -static void inet_inherit_port(struct sock *lsk, struct sock *newsk) -{ - local_bh_disable(); - __inet_inherit_port(lsk, newsk); - local_bh_enable(); -} - -static int chtls_backlog_rcv(struct sock *sk, struct sk_buff *skb) -{ - if (skb->protocol) { - kfree_skb(skb); - return 0; - } - BLOG_SKB_CB(skb)->backlog_rcv(sk, skb); - return 0; -} - -static void chtls_set_tcp_window(struct chtls_sock *csk) -{ - struct net_device *ndev = csk->egress_dev; - struct port_info *pi = netdev_priv(ndev); - unsigned int linkspeed; - u8 scale; - - linkspeed = pi->link_cfg.speed; - scale = linkspeed / SPEED_10000; -#define CHTLS_10G_RCVWIN (256 * 1024) - csk->rcv_win = CHTLS_10G_RCVWIN; - if (scale) - csk->rcv_win *= scale; -#define CHTLS_10G_SNDWIN (256 * 1024) - csk->snd_win = CHTLS_10G_SNDWIN; - if (scale) - csk->snd_win *= scale; -} - -static struct sock *chtls_recv_sock(struct sock *lsk, - struct request_sock *oreq, - void *network_hdr, - const struct cpl_pass_accept_req *req, - struct chtls_dev *cdev) -{ - struct adapter *adap = pci_get_drvdata(cdev->pdev); - struct neighbour *n = NULL; - struct inet_sock *newinet; - const struct iphdr *iph; - struct tls_context *ctx; - struct net_device *ndev; - struct chtls_sock *csk; - struct dst_entry *dst; - struct tcp_sock *tp; - struct sock *newsk; - bool found = false; - u16 port_id; - int rxq_idx; - int step, i; - - iph = (const struct iphdr *)network_hdr; - newsk = tcp_create_openreq_child(lsk, oreq, cdev->askb); - if (!newsk) - goto free_oreq; - - if (lsk->sk_family == AF_INET) { - dst = inet_csk_route_child_sock(lsk, newsk, oreq); - if (!dst) - goto free_sk; - - n = dst_neigh_lookup(dst, &iph->saddr); -#if IS_ENABLED(CONFIG_IPV6) - } else { - const struct ipv6hdr *ip6h; - struct flowi6 fl6; - - ip6h = (const struct ipv6hdr *)network_hdr; - memset(&fl6, 0, sizeof(fl6)); - fl6.flowi6_proto = IPPROTO_TCP; - fl6.saddr = ip6h->daddr; - fl6.daddr = ip6h->saddr; - fl6.fl6_dport = inet_rsk(oreq)->ir_rmt_port; - fl6.fl6_sport = htons(inet_rsk(oreq)->ir_num); - security_req_classify_flow(oreq, flowi6_to_flowi_common(&fl6)); - dst = ip6_dst_lookup_flow(sock_net(lsk), lsk, &fl6, NULL); - if (IS_ERR(dst)) - goto free_sk; - n = dst_neigh_lookup(dst, &ip6h->saddr); -#endif - } - if (!n || !n->dev) - goto free_dst; - - ndev = n->dev; - if (is_vlan_dev(ndev)) - ndev = vlan_dev_real_dev(ndev); - - for_each_port(adap, i) - if (cdev->ports[i] == ndev) - found = true; - - if (!found) - goto free_dst; - - port_id = cxgb4_port_idx(ndev); - - csk = chtls_sock_create(cdev); - if (!csk) - goto free_dst; - - csk->l2t_entry = cxgb4_l2t_get(cdev->lldi->l2t, n, ndev, 0); - if (!csk->l2t_entry) - goto free_csk; - - newsk->sk_user_data = csk; - newsk->sk_backlog_rcv = chtls_backlog_rcv; - - tp = tcp_sk(newsk); - newinet = inet_sk(newsk); - - if (iph->version == 0x4) { - newinet->inet_daddr = iph->saddr; - newinet->inet_rcv_saddr = iph->daddr; - newinet->inet_saddr = iph->daddr; -#if IS_ENABLED(CONFIG_IPV6) - } else { - struct tcp6_sock *newtcp6sk = (struct tcp6_sock *)newsk; - struct inet_request_sock *treq = inet_rsk(oreq); - struct ipv6_pinfo *newnp = inet6_sk(newsk); - struct ipv6_pinfo *np = inet6_sk(lsk); - - newinet->pinet6 = &newtcp6sk->inet6; - newinet->ipv6_fl_list = NULL; - memcpy(newnp, np, sizeof(struct ipv6_pinfo)); - newsk->sk_v6_daddr = treq->ir_v6_rmt_addr; - newsk->sk_v6_rcv_saddr = treq->ir_v6_loc_addr; - inet6_sk(newsk)->saddr = treq->ir_v6_loc_addr; - newnp->pktoptions = NULL; - newsk->sk_bound_dev_if = treq->ir_iif; - newinet->inet_opt = NULL; - newinet->inet_daddr = LOOPBACK4_IPV6; - newinet->inet_saddr = LOOPBACK4_IPV6; -#endif - } - - oreq->ts_recent = PASS_OPEN_TID_G(ntohl(req->tos_stid)); - sk_setup_caps(newsk, dst); - ctx = tls_get_ctx(lsk); - newsk->sk_destruct = ctx->sk_destruct; - newsk->sk_prot_creator = lsk->sk_prot_creator; - csk->sk = newsk; - csk->passive_reap_next = oreq; - csk->tx_chan = cxgb4_port_chan(ndev); - csk->port_id = port_id; - csk->egress_dev = ndev; - csk->tos = PASS_OPEN_TOS_G(ntohl(req->tos_stid)); - chtls_set_tcp_window(csk); - tp->rcv_wnd = csk->rcv_win; - csk->sndbuf = csk->snd_win; - csk->ulp_mode = ULP_MODE_TLS; - step = cdev->lldi->nrxq / cdev->lldi->nchan; - rxq_idx = port_id * step; - rxq_idx += cdev->round_robin_cnt++ % step; - csk->rss_qid = cdev->lldi->rxq_ids[rxq_idx]; - csk->txq_idx = (rxq_idx < cdev->lldi->ntxq) ? rxq_idx : - port_id * step; - csk->sndbuf = newsk->sk_sndbuf; - csk->smac_idx = ((struct port_info *)netdev_priv(ndev))->smt_idx; - RCV_WSCALE(tp) = select_rcv_wscale(tcp_full_space(newsk), - READ_ONCE(sock_net(newsk)-> - ipv4.sysctl_tcp_window_scaling), - tp->window_clamp); - neigh_release(n); - inet_inherit_port(lsk, newsk); - csk_set_flag(csk, CSK_CONN_INLINE); - bh_unlock_sock(newsk); /* tcp_create_openreq_child ->sk_clone_lock */ - - return newsk; -free_csk: - chtls_sock_release(&csk->kref); -free_dst: - if (n) - neigh_release(n); - dst_release(dst); -free_sk: - inet_csk_prepare_forced_close(newsk); - tcp_done(newsk); -free_oreq: - chtls_reqsk_free(oreq); - return NULL; -} - -/* - * Populate a TID_RELEASE WR. The skb must be already propely sized. - */ -static void mk_tid_release(struct sk_buff *skb, - unsigned int chan, unsigned int tid) -{ - struct cpl_tid_release *req; - unsigned int len; - - len = roundup(sizeof(struct cpl_tid_release), 16); - req = (struct cpl_tid_release *)__skb_put(skb, len); - memset(req, 0, len); - set_wr_txq(skb, CPL_PRIORITY_SETUP, chan); - INIT_TP_WR_CPL(req, CPL_TID_RELEASE, tid); -} - -static int chtls_get_module(struct sock *sk) -{ - struct inet_connection_sock *icsk = inet_csk(sk); - - if (!try_module_get(icsk->icsk_ulp_ops->owner)) - return -1; - - return 0; -} - -static void chtls_pass_accept_request(struct sock *sk, - struct sk_buff *skb) -{ - struct cpl_t5_pass_accept_rpl *rpl; - struct cpl_pass_accept_req *req; - struct listen_ctx *listen_ctx; - struct vlan_ethhdr *vlan_eh; - struct request_sock *oreq; - struct sk_buff *reply_skb; - struct chtls_sock *csk; - struct chtls_dev *cdev; - struct ipv6hdr *ip6h; - struct tcphdr *tcph; - struct sock *newsk; - struct ethhdr *eh; - struct iphdr *iph; - void *network_hdr; - unsigned int stid; - unsigned int len; - unsigned int tid; - bool th_ecn, ect; - __u8 ip_dsfield; /* IPv4 tos or IPv6 dsfield */ - u16 eth_hdr_len; - bool ecn_ok; - - req = cplhdr(skb) + RSS_HDR; - tid = GET_TID(req); - cdev = BLOG_SKB_CB(skb)->cdev; - newsk = lookup_tid(cdev->tids, tid); - stid = PASS_OPEN_TID_G(ntohl(req->tos_stid)); - if (newsk) { - pr_info("tid (%d) already in use\n", tid); - return; - } - - len = roundup(sizeof(*rpl), 16); - reply_skb = alloc_skb(len, GFP_ATOMIC); - if (!reply_skb) { - cxgb4_remove_tid(cdev->tids, 0, tid, sk->sk_family); - kfree_skb(skb); - return; - } - - if (sk->sk_state != TCP_LISTEN) - goto reject; - - if (inet_csk_reqsk_queue_is_full(sk)) - goto reject; - - if (sk_acceptq_is_full(sk)) - goto reject; - - - eth_hdr_len = T6_ETH_HDR_LEN_G(ntohl(req->hdr_len)); - if (eth_hdr_len == ETH_HLEN) { - eh = (struct ethhdr *)(req + 1); - iph = (struct iphdr *)(eh + 1); - ip6h = (struct ipv6hdr *)(eh + 1); - network_hdr = (void *)(eh + 1); - } else { - vlan_eh = (struct vlan_ethhdr *)(req + 1); - iph = (struct iphdr *)(vlan_eh + 1); - ip6h = (struct ipv6hdr *)(vlan_eh + 1); - network_hdr = (void *)(vlan_eh + 1); - } - - if (iph->version == 0x4) { - tcph = (struct tcphdr *)(iph + 1); - skb_set_network_header(skb, (void *)iph - (void *)req); - oreq = inet_reqsk_alloc(&chtls_rsk_ops, sk, true); - } else { - tcph = (struct tcphdr *)(ip6h + 1); - skb_set_network_header(skb, (void *)ip6h - (void *)req); - oreq = inet_reqsk_alloc(&chtls_rsk_opsv6, sk, false); - } - - if (!oreq) - goto reject; - - oreq->rsk_rcv_wnd = 0; - oreq->rsk_window_clamp = 0; - oreq->syncookie = 0; - oreq->mss = 0; - oreq->ts_recent = 0; - - tcp_rsk(oreq)->tfo_listener = false; - tcp_rsk(oreq)->rcv_isn = ntohl(tcph->seq); - chtls_set_req_port(oreq, tcph->source, tcph->dest); - if (iph->version == 0x4) { - chtls_set_req_addr(oreq, iph->daddr, iph->saddr); - ip_dsfield = ipv4_get_dsfield(iph); -#if IS_ENABLED(CONFIG_IPV6) - } else { - inet_rsk(oreq)->ir_v6_rmt_addr = ipv6_hdr(skb)->saddr; - inet_rsk(oreq)->ir_v6_loc_addr = ipv6_hdr(skb)->daddr; - ip_dsfield = ipv6_get_dsfield(ipv6_hdr(skb)); -#endif - } - if (req->tcpopt.wsf <= 14 && - READ_ONCE(sock_net(sk)->ipv4.sysctl_tcp_window_scaling)) { - inet_rsk(oreq)->wscale_ok = 1; - inet_rsk(oreq)->snd_wscale = req->tcpopt.wsf; - } - inet_rsk(oreq)->ir_iif = sk->sk_bound_dev_if; - th_ecn = tcph->ece && tcph->cwr; - if (th_ecn) { - ect = !INET_ECN_is_not_ect(ip_dsfield); - ecn_ok = READ_ONCE(sock_net(sk)->ipv4.sysctl_tcp_ecn); - if ((!ect && ecn_ok) || tcp_ca_needs_ecn(sk)) - inet_rsk(oreq)->ecn_ok = 1; - } - - newsk = chtls_recv_sock(sk, oreq, network_hdr, req, cdev); - if (!newsk) - goto reject; - - if (chtls_get_module(newsk)) - goto reject; - inet_csk_reqsk_queue_added(sk); - reply_skb->sk = newsk; - chtls_install_cpl_ops(newsk); - cxgb4_insert_tid(cdev->tids, newsk, tid, newsk->sk_family); - csk = rcu_dereference_sk_user_data(newsk); - listen_ctx = (struct listen_ctx *)lookup_stid(cdev->tids, stid); - csk->listen_ctx = listen_ctx; - __skb_queue_tail(&listen_ctx->synq, (struct sk_buff *)&csk->synq); - chtls_pass_accept_rpl(reply_skb, req, tid); - kfree_skb(skb); - return; - -reject: - mk_tid_release(reply_skb, 0, tid); - cxgb4_ofld_send(cdev->lldi->ports[0], reply_skb); - kfree_skb(skb); -} - -/* - * Handle a CPL_PASS_ACCEPT_REQ message. - */ -static int chtls_pass_accept_req(struct chtls_dev *cdev, struct sk_buff *skb) -{ - struct cpl_pass_accept_req *req = cplhdr(skb) + RSS_HDR; - struct listen_ctx *ctx; - unsigned int stid; - unsigned int tid; - struct sock *lsk; - void *data; - - stid = PASS_OPEN_TID_G(ntohl(req->tos_stid)); - tid = GET_TID(req); - - data = lookup_stid(cdev->tids, stid); - if (!data) - return 1; - - ctx = (struct listen_ctx *)data; - lsk = ctx->lsk; - - if (unlikely(tid_out_of_range(cdev->tids, tid))) { - pr_info("passive open TID %u too large\n", tid); - return 1; - } - - BLOG_SKB_CB(skb)->cdev = cdev; - process_cpl_msg(chtls_pass_accept_request, lsk, skb); - return 0; -} - -/* - * Completes some final bits of initialization for just established connections - * and changes their state to TCP_ESTABLISHED. - * - * snd_isn here is the ISN after the SYN, i.e., the true ISN + 1. - */ -static void make_established(struct sock *sk, u32 snd_isn, unsigned int opt) -{ - struct tcp_sock *tp = tcp_sk(sk); - - tp->pushed_seq = snd_isn; - tp->write_seq = snd_isn; - tp->snd_nxt = snd_isn; - tp->snd_una = snd_isn; - atomic_set(&inet_sk(sk)->inet_id, get_random_u16()); - assign_rxopt(sk, opt); - - if (tp->rcv_wnd > (RCV_BUFSIZ_M << 10)) - tp->rcv_wup -= tp->rcv_wnd - (RCV_BUFSIZ_M << 10); - - smp_mb(); - tcp_set_state(sk, TCP_ESTABLISHED); -} - -static void chtls_abort_conn(struct sock *sk, struct sk_buff *skb) -{ - struct sk_buff *abort_skb; - - abort_skb = alloc_skb(sizeof(struct cpl_abort_req), GFP_ATOMIC); - if (abort_skb) - chtls_send_reset(sk, CPL_ABORT_SEND_RST, abort_skb); -} - -static struct sock *reap_list; -static DEFINE_SPINLOCK(reap_list_lock); - -/* - * Process the reap list. - */ -DECLARE_TASK_FUNC(process_reap_list, task_param) -{ - spin_lock_bh(&reap_list_lock); - while (reap_list) { - struct sock *sk = reap_list; - struct chtls_sock *csk = rcu_dereference_sk_user_data(sk); - - reap_list = csk->passive_reap_next; - csk->passive_reap_next = NULL; - spin_unlock(&reap_list_lock); - sock_hold(sk); - - bh_lock_sock(sk); - chtls_abort_conn(sk, NULL); - sock_orphan(sk); - if (sk->sk_state == TCP_CLOSE) - inet_csk_destroy_sock(sk); - bh_unlock_sock(sk); - sock_put(sk); - spin_lock(&reap_list_lock); - } - spin_unlock_bh(&reap_list_lock); -} - -static DECLARE_WORK(reap_task, process_reap_list); - -static void add_to_reap_list(struct sock *sk) -{ - struct chtls_sock *csk = sk->sk_user_data; - - local_bh_disable(); - release_tcp_port(sk); /* release the port immediately */ - - spin_lock(&reap_list_lock); - csk->passive_reap_next = reap_list; - reap_list = sk; - if (!csk->passive_reap_next) - schedule_work(&reap_task); - spin_unlock(&reap_list_lock); - local_bh_enable(); -} - -static void add_pass_open_to_parent(struct sock *child, struct sock *lsk, - struct chtls_dev *cdev) -{ - struct request_sock *oreq; - struct chtls_sock *csk; - - if (lsk->sk_state != TCP_LISTEN) - return; - - csk = child->sk_user_data; - oreq = csk->passive_reap_next; - csk->passive_reap_next = NULL; - - reqsk_queue_removed(&inet_csk(lsk)->icsk_accept_queue, oreq); - __skb_unlink((struct sk_buff *)&csk->synq, &csk->listen_ctx->synq); - - if (sk_acceptq_is_full(lsk)) { - chtls_reqsk_free(oreq); - add_to_reap_list(child); - } else { - refcount_set(&oreq->rsk_refcnt, 1); - inet_csk_reqsk_queue_add(lsk, oreq, child); - lsk->sk_data_ready(lsk); - } -} - -static void bl_add_pass_open_to_parent(struct sock *lsk, struct sk_buff *skb) -{ - struct sock *child = skb->sk; - - skb->sk = NULL; - add_pass_open_to_parent(child, lsk, BLOG_SKB_CB(skb)->cdev); - kfree_skb(skb); -} - -static int chtls_pass_establish(struct chtls_dev *cdev, struct sk_buff *skb) -{ - struct cpl_pass_establish *req = cplhdr(skb) + RSS_HDR; - struct chtls_sock *csk; - struct sock *lsk, *sk; - unsigned int hwtid; - - hwtid = GET_TID(req); - sk = lookup_tid(cdev->tids, hwtid); - if (!sk) - return (CPL_RET_UNKNOWN_TID | CPL_RET_BUF_DONE); - - bh_lock_sock(sk); - if (unlikely(sock_owned_by_user(sk))) { - kfree_skb(skb); - } else { - unsigned int stid; - void *data; - - csk = sk->sk_user_data; - csk->wr_max_credits = 64; - csk->wr_credits = 64; - csk->wr_unacked = 0; - make_established(sk, ntohl(req->snd_isn), ntohs(req->tcp_opt)); - stid = PASS_OPEN_TID_G(ntohl(req->tos_stid)); - sk->sk_state_change(sk); - if (unlikely(sk->sk_socket)) - sk_wake_async(sk, 0, POLL_OUT); - - data = lookup_stid(cdev->tids, stid); - if (!data) { - /* listening server close */ - kfree_skb(skb); - goto unlock; - } - lsk = ((struct listen_ctx *)data)->lsk; - - bh_lock_sock(lsk); - if (unlikely(skb_queue_empty(&csk->listen_ctx->synq))) { - /* removed from synq */ - bh_unlock_sock(lsk); - kfree_skb(skb); - goto unlock; - } - - if (likely(!sock_owned_by_user(lsk))) { - kfree_skb(skb); - add_pass_open_to_parent(sk, lsk, cdev); - } else { - skb->sk = sk; - BLOG_SKB_CB(skb)->cdev = cdev; - BLOG_SKB_CB(skb)->backlog_rcv = - bl_add_pass_open_to_parent; - __sk_add_backlog(lsk, skb); - } - bh_unlock_sock(lsk); - } -unlock: - bh_unlock_sock(sk); - return 0; -} - -/* - * Handle receipt of an urgent pointer. - */ -static void handle_urg_ptr(struct sock *sk, u32 urg_seq) -{ - struct tcp_sock *tp = tcp_sk(sk); - - urg_seq--; - if (tp->urg_data && !after(urg_seq, tp->urg_seq)) - return; /* duplicate pointer */ - - sk_send_sigurg(sk); - if (tp->urg_seq == tp->copied_seq && tp->urg_data && - !sock_flag(sk, SOCK_URGINLINE) && - tp->copied_seq != tp->rcv_nxt) { - struct sk_buff *skb = skb_peek(&sk->sk_receive_queue); - - tp->copied_seq++; - if (skb && tp->copied_seq - ULP_SKB_CB(skb)->seq >= skb->len) - chtls_free_skb(sk, skb); - } - - tp->urg_data = TCP_URG_NOTYET; - tp->urg_seq = urg_seq; -} - -static void check_sk_callbacks(struct chtls_sock *csk) -{ - struct sock *sk = csk->sk; - - if (unlikely(sk->sk_user_data && - !csk_flag_nochk(csk, CSK_CALLBACKS_CHKD))) - csk_set_flag(csk, CSK_CALLBACKS_CHKD); -} - -/* - * Handles Rx data that arrives in a state where the socket isn't accepting - * new data. - */ -static void handle_excess_rx(struct sock *sk, struct sk_buff *skb) -{ - if (!csk_flag(sk, CSK_ABORT_SHUTDOWN)) - chtls_abort_conn(sk, skb); - - kfree_skb(skb); -} - -static void chtls_recv_data(struct sock *sk, struct sk_buff *skb) -{ - struct cpl_rx_data *hdr = cplhdr(skb) + RSS_HDR; - struct chtls_sock *csk; - struct tcp_sock *tp; - - csk = rcu_dereference_sk_user_data(sk); - tp = tcp_sk(sk); - - if (unlikely(sk->sk_shutdown & RCV_SHUTDOWN)) { - handle_excess_rx(sk, skb); - return; - } - - ULP_SKB_CB(skb)->seq = ntohl(hdr->seq); - ULP_SKB_CB(skb)->psh = hdr->psh; - skb_ulp_mode(skb) = ULP_MODE_NONE; - - skb_reset_transport_header(skb); - __skb_pull(skb, sizeof(*hdr) + RSS_HDR); - if (!skb->data_len) - __skb_trim(skb, ntohs(hdr->len)); - - if (unlikely(hdr->urg)) - handle_urg_ptr(sk, tp->rcv_nxt + ntohs(hdr->urg)); - if (unlikely(tp->urg_data == TCP_URG_NOTYET && - tp->urg_seq - tp->rcv_nxt < skb->len)) - tp->urg_data = TCP_URG_VALID | - skb->data[tp->urg_seq - tp->rcv_nxt]; - - if (unlikely(hdr->dack_mode != csk->delack_mode)) { - csk->delack_mode = hdr->dack_mode; - csk->delack_seq = tp->rcv_nxt; - } - - tcp_hdr(skb)->fin = 0; - tp->rcv_nxt += skb->len; - - __skb_queue_tail(&sk->sk_receive_queue, skb); - - if (!sock_flag(sk, SOCK_DEAD)) { - check_sk_callbacks(csk); - sk->sk_data_ready(sk); - } -} - -static int chtls_rx_data(struct chtls_dev *cdev, struct sk_buff *skb) -{ - struct cpl_rx_data *req = cplhdr(skb) + RSS_HDR; - unsigned int hwtid = GET_TID(req); - struct sock *sk; - - sk = lookup_tid(cdev->tids, hwtid); - if (unlikely(!sk)) { - pr_err("can't find conn. for hwtid %u.\n", hwtid); - return -EINVAL; - } - skb_dstref_steal(skb); - process_cpl_msg(chtls_recv_data, sk, skb); - return 0; -} - -static void chtls_recv_pdu(struct sock *sk, struct sk_buff *skb) -{ - struct cpl_tls_data *hdr = cplhdr(skb); - struct chtls_sock *csk; - struct chtls_hws *tlsk; - struct tcp_sock *tp; - - csk = rcu_dereference_sk_user_data(sk); - tlsk = &csk->tlshws; - tp = tcp_sk(sk); - - if (unlikely(sk->sk_shutdown & RCV_SHUTDOWN)) { - handle_excess_rx(sk, skb); - return; - } - - ULP_SKB_CB(skb)->seq = ntohl(hdr->seq); - ULP_SKB_CB(skb)->flags = 0; - skb_ulp_mode(skb) = ULP_MODE_TLS; - - skb_reset_transport_header(skb); - __skb_pull(skb, sizeof(*hdr)); - if (!skb->data_len) - __skb_trim(skb, - CPL_TLS_DATA_LENGTH_G(ntohl(hdr->length_pkd))); - - if (unlikely(tp->urg_data == TCP_URG_NOTYET && tp->urg_seq - - tp->rcv_nxt < skb->len)) - tp->urg_data = TCP_URG_VALID | - skb->data[tp->urg_seq - tp->rcv_nxt]; - - tcp_hdr(skb)->fin = 0; - tlsk->pldlen = CPL_TLS_DATA_LENGTH_G(ntohl(hdr->length_pkd)); - __skb_queue_tail(&tlsk->sk_recv_queue, skb); -} - -static int chtls_rx_pdu(struct chtls_dev *cdev, struct sk_buff *skb) -{ - struct cpl_tls_data *req = cplhdr(skb); - unsigned int hwtid = GET_TID(req); - struct sock *sk; - - sk = lookup_tid(cdev->tids, hwtid); - if (unlikely(!sk)) { - pr_err("can't find conn. for hwtid %u.\n", hwtid); - return -EINVAL; - } - skb_dstref_steal(skb); - process_cpl_msg(chtls_recv_pdu, sk, skb); - return 0; -} - -static void chtls_set_hdrlen(struct sk_buff *skb, unsigned int nlen) -{ - struct tlsrx_cmp_hdr *tls_cmp_hdr = cplhdr(skb); - - skb->hdr_len = ntohs((__force __be16)tls_cmp_hdr->length); - tls_cmp_hdr->length = ntohs((__force __be16)nlen); -} - -static void chtls_rx_hdr(struct sock *sk, struct sk_buff *skb) -{ - struct tlsrx_cmp_hdr *tls_hdr_pkt; - struct cpl_rx_tls_cmp *cmp_cpl; - struct sk_buff *skb_rec; - struct chtls_sock *csk; - struct chtls_hws *tlsk; - struct tcp_sock *tp; - - cmp_cpl = cplhdr(skb); - csk = rcu_dereference_sk_user_data(sk); - tlsk = &csk->tlshws; - tp = tcp_sk(sk); - - ULP_SKB_CB(skb)->seq = ntohl(cmp_cpl->seq); - ULP_SKB_CB(skb)->flags = 0; - - skb_reset_transport_header(skb); - __skb_pull(skb, sizeof(*cmp_cpl)); - tls_hdr_pkt = (struct tlsrx_cmp_hdr *)skb->data; - if (tls_hdr_pkt->res_to_mac_error & TLSRX_HDR_PKT_ERROR_M) - tls_hdr_pkt->type = CONTENT_TYPE_ERROR; - if (!skb->data_len) - __skb_trim(skb, TLS_HEADER_LENGTH); - - tp->rcv_nxt += - CPL_RX_TLS_CMP_PDULENGTH_G(ntohl(cmp_cpl->pdulength_length)); - - ULP_SKB_CB(skb)->flags |= ULPCB_FLAG_TLS_HDR; - skb_rec = __skb_dequeue(&tlsk->sk_recv_queue); - if (!skb_rec) { - __skb_queue_tail(&sk->sk_receive_queue, skb); - } else { - chtls_set_hdrlen(skb, tlsk->pldlen); - tlsk->pldlen = 0; - __skb_queue_tail(&sk->sk_receive_queue, skb); - __skb_queue_tail(&sk->sk_receive_queue, skb_rec); - } - - if (!sock_flag(sk, SOCK_DEAD)) { - check_sk_callbacks(csk); - sk->sk_data_ready(sk); - } -} - -static int chtls_rx_cmp(struct chtls_dev *cdev, struct sk_buff *skb) -{ - struct cpl_rx_tls_cmp *req = cplhdr(skb); - unsigned int hwtid = GET_TID(req); - struct sock *sk; - - sk = lookup_tid(cdev->tids, hwtid); - if (unlikely(!sk)) { - pr_err("can't find conn. for hwtid %u.\n", hwtid); - return -EINVAL; - } - skb_dstref_steal(skb); - process_cpl_msg(chtls_rx_hdr, sk, skb); - - return 0; -} - -static void chtls_timewait(struct sock *sk) -{ - struct tcp_sock *tp = tcp_sk(sk); - - tp->rcv_nxt++; - tp->rx_opt.ts_recent_stamp = ktime_get_seconds(); - tp->srtt_us = 0; - tcp_time_wait(sk, TCP_TIME_WAIT, 0); -} - -static void chtls_peer_close(struct sock *sk, struct sk_buff *skb) -{ - struct chtls_sock *csk = rcu_dereference_sk_user_data(sk); - - if (csk_flag_nochk(csk, CSK_ABORT_RPL_PENDING)) - goto out; - - sk->sk_shutdown |= RCV_SHUTDOWN; - sock_set_flag(sk, SOCK_DONE); - - switch (sk->sk_state) { - case TCP_SYN_RECV: - case TCP_ESTABLISHED: - tcp_set_state(sk, TCP_CLOSE_WAIT); - break; - case TCP_FIN_WAIT1: - tcp_set_state(sk, TCP_CLOSING); - break; - case TCP_FIN_WAIT2: - chtls_release_resources(sk); - if (csk_flag_nochk(csk, CSK_ABORT_RPL_PENDING)) - chtls_conn_done(sk); - else - chtls_timewait(sk); - break; - default: - pr_info("cpl_peer_close in bad state %d\n", sk->sk_state); - } - - if (!sock_flag(sk, SOCK_DEAD)) { - sk->sk_state_change(sk); - /* Do not send POLL_HUP for half duplex close. */ - - if ((sk->sk_shutdown & SEND_SHUTDOWN) || - sk->sk_state == TCP_CLOSE) - sk_wake_async(sk, SOCK_WAKE_WAITD, POLL_HUP); - else - sk_wake_async(sk, SOCK_WAKE_WAITD, POLL_IN); - } -out: - kfree_skb(skb); -} - -static void chtls_close_con_rpl(struct sock *sk, struct sk_buff *skb) -{ - struct cpl_close_con_rpl *rpl = cplhdr(skb) + RSS_HDR; - struct chtls_sock *csk; - struct tcp_sock *tp; - - csk = rcu_dereference_sk_user_data(sk); - - if (csk_flag_nochk(csk, CSK_ABORT_RPL_PENDING)) - goto out; - - tp = tcp_sk(sk); - - tp->snd_una = ntohl(rpl->snd_nxt) - 1; /* exclude FIN */ - - switch (sk->sk_state) { - case TCP_CLOSING: - chtls_release_resources(sk); - if (csk_flag_nochk(csk, CSK_ABORT_RPL_PENDING)) - chtls_conn_done(sk); - else - chtls_timewait(sk); - break; - case TCP_LAST_ACK: - chtls_release_resources(sk); - chtls_conn_done(sk); - break; - case TCP_FIN_WAIT1: - tcp_set_state(sk, TCP_FIN_WAIT2); - sk->sk_shutdown |= SEND_SHUTDOWN; - - if (!sock_flag(sk, SOCK_DEAD)) - sk->sk_state_change(sk); - else if (tcp_sk(sk)->linger2 < 0 && - !csk_flag_nochk(csk, CSK_ABORT_SHUTDOWN)) - chtls_abort_conn(sk, skb); - else if (csk_flag_nochk(csk, CSK_TX_DATA_SENT)) - chtls_set_quiesce_ctrl(sk, 0); - break; - default: - pr_info("close_con_rpl in bad state %d\n", sk->sk_state); - } -out: - kfree_skb(skb); -} - -static struct sk_buff *get_cpl_skb(struct sk_buff *skb, - size_t len, gfp_t gfp) -{ - if (likely(!skb_is_nonlinear(skb) && !skb_cloned(skb))) { - WARN_ONCE(skb->len < len, "skb alloc error"); - __skb_trim(skb, len); - skb_get(skb); - } else { - skb = alloc_skb(len, gfp); - if (skb) - __skb_put(skb, len); - } - return skb; -} - -static void set_abort_rpl_wr(struct sk_buff *skb, unsigned int tid, - int cmd) -{ - struct cpl_abort_rpl *rpl = cplhdr(skb); - - INIT_TP_WR_CPL(rpl, CPL_ABORT_RPL, tid); - rpl->cmd = cmd; -} - -static void send_defer_abort_rpl(struct chtls_dev *cdev, struct sk_buff *skb) -{ - struct cpl_abort_req_rss *req = cplhdr(skb); - struct sk_buff *reply_skb; - - reply_skb = alloc_skb(sizeof(struct cpl_abort_rpl), - GFP_KERNEL | __GFP_NOFAIL); - __skb_put(reply_skb, sizeof(struct cpl_abort_rpl)); - set_abort_rpl_wr(reply_skb, GET_TID(req), - (req->status & CPL_ABORT_NO_RST)); - set_wr_txq(reply_skb, CPL_PRIORITY_DATA, req->status >> 1); - cxgb4_ofld_send(cdev->lldi->ports[0], reply_skb); - kfree_skb(skb); -} - -/* - * Add an skb to the deferred skb queue for processing from process context. - */ -static void t4_defer_reply(struct sk_buff *skb, struct chtls_dev *cdev, - defer_handler_t handler) -{ - DEFERRED_SKB_CB(skb)->handler = handler; - spin_lock_bh(&cdev->deferq.lock); - __skb_queue_tail(&cdev->deferq, skb); - if (skb_queue_len(&cdev->deferq) == 1) - schedule_work(&cdev->deferq_task); - spin_unlock_bh(&cdev->deferq.lock); -} - -static void chtls_send_abort_rpl(struct sock *sk, struct sk_buff *skb, - struct chtls_dev *cdev, - int status, int queue) -{ - struct cpl_abort_req_rss *req = cplhdr(skb) + RSS_HDR; - struct sk_buff *reply_skb; - struct chtls_sock *csk; - unsigned int tid; - - csk = rcu_dereference_sk_user_data(sk); - tid = GET_TID(req); - - reply_skb = get_cpl_skb(skb, sizeof(struct cpl_abort_rpl), gfp_any()); - if (!reply_skb) { - req->status = (queue << 1) | status; - t4_defer_reply(skb, cdev, send_defer_abort_rpl); - return; - } - - set_abort_rpl_wr(reply_skb, tid, status); - kfree_skb(skb); - set_wr_txq(reply_skb, CPL_PRIORITY_DATA, queue); - if (csk_conn_inline(csk)) { - struct l2t_entry *e = csk->l2t_entry; - - if (e && sk->sk_state != TCP_SYN_RECV) { - cxgb4_l2t_send(csk->egress_dev, reply_skb, e); - return; - } - } - cxgb4_ofld_send(cdev->lldi->ports[0], reply_skb); -} - -/* - * This is run from a listener's backlog to abort a child connection in - * SYN_RCV state (i.e., one on the listener's SYN queue). - */ -static void bl_abort_syn_rcv(struct sock *lsk, struct sk_buff *skb) -{ - struct chtls_sock *csk; - struct sock *child; - int queue; - - child = skb->sk; - csk = rcu_dereference_sk_user_data(child); - queue = csk->txq_idx; - - skb->sk = NULL; - chtls_send_abort_rpl(child, skb, BLOG_SKB_CB(skb)->cdev, - CPL_ABORT_NO_RST, queue); - do_abort_syn_rcv(child, lsk); -} - -static int abort_syn_rcv(struct sock *sk, struct sk_buff *skb) -{ - const struct request_sock *oreq; - struct listen_ctx *listen_ctx; - struct chtls_sock *csk; - struct chtls_dev *cdev; - struct sock *psk; - void *ctx; - - csk = sk->sk_user_data; - oreq = csk->passive_reap_next; - cdev = csk->cdev; - - if (!oreq) - return -1; - - ctx = lookup_stid(cdev->tids, oreq->ts_recent); - if (!ctx) - return -1; - - listen_ctx = (struct listen_ctx *)ctx; - psk = listen_ctx->lsk; - - bh_lock_sock(psk); - if (!sock_owned_by_user(psk)) { - int queue = csk->txq_idx; - - chtls_send_abort_rpl(sk, skb, cdev, CPL_ABORT_NO_RST, queue); - do_abort_syn_rcv(sk, psk); - } else { - skb->sk = sk; - BLOG_SKB_CB(skb)->backlog_rcv = bl_abort_syn_rcv; - __sk_add_backlog(psk, skb); - } - bh_unlock_sock(psk); - return 0; -} - -static void chtls_abort_req_rss(struct sock *sk, struct sk_buff *skb) -{ - const struct cpl_abort_req_rss *req = cplhdr(skb) + RSS_HDR; - struct chtls_sock *csk = sk->sk_user_data; - int rst_status = CPL_ABORT_NO_RST; - int queue = csk->txq_idx; - - if (is_neg_adv(req->status)) { - kfree_skb(skb); - return; - } - - csk_reset_flag(csk, CSK_ABORT_REQ_RCVD); - - if (!csk_flag_nochk(csk, CSK_ABORT_SHUTDOWN) && - !csk_flag_nochk(csk, CSK_TX_DATA_SENT)) { - struct tcp_sock *tp = tcp_sk(sk); - - if (send_tx_flowc_wr(sk, 0, tp->snd_nxt, tp->rcv_nxt) < 0) - WARN_ONCE(1, "send_tx_flowc error"); - csk_set_flag(csk, CSK_TX_DATA_SENT); - } - - csk_set_flag(csk, CSK_ABORT_SHUTDOWN); - - if (!csk_flag_nochk(csk, CSK_ABORT_RPL_PENDING)) { - sk->sk_err = ETIMEDOUT; - - if (!sock_flag(sk, SOCK_DEAD)) - sk_error_report(sk); - - if (sk->sk_state == TCP_SYN_RECV && !abort_syn_rcv(sk, skb)) - return; - - } - - chtls_send_abort_rpl(sk, skb, BLOG_SKB_CB(skb)->cdev, - rst_status, queue); - chtls_release_resources(sk); - chtls_conn_done(sk); -} - -static void chtls_abort_rpl_rss(struct sock *sk, struct sk_buff *skb) -{ - struct cpl_abort_rpl_rss *rpl = cplhdr(skb) + RSS_HDR; - struct chtls_sock *csk; - struct chtls_dev *cdev; - - csk = rcu_dereference_sk_user_data(sk); - cdev = csk->cdev; - - if (csk_flag_nochk(csk, CSK_ABORT_RPL_PENDING)) { - csk_reset_flag(csk, CSK_ABORT_RPL_PENDING); - if (!csk_flag_nochk(csk, CSK_ABORT_REQ_RCVD)) { - if (sk->sk_state == TCP_SYN_SENT) { - cxgb4_remove_tid(cdev->tids, - csk->port_id, - GET_TID(rpl), - sk->sk_family); - sock_put(sk); - } - chtls_release_resources(sk); - chtls_conn_done(sk); - } - } - kfree_skb(skb); -} - -static int chtls_conn_cpl(struct chtls_dev *cdev, struct sk_buff *skb) -{ - struct cpl_peer_close *req = cplhdr(skb) + RSS_HDR; - void (*fn)(struct sock *sk, struct sk_buff *skb); - unsigned int hwtid = GET_TID(req); - struct chtls_sock *csk; - struct sock *sk; - u8 opcode; - - opcode = ((const struct rss_header *)cplhdr(skb))->opcode; - - sk = lookup_tid(cdev->tids, hwtid); - if (!sk) - goto rel_skb; - - csk = sk->sk_user_data; - - switch (opcode) { - case CPL_PEER_CLOSE: - fn = chtls_peer_close; - break; - case CPL_CLOSE_CON_RPL: - fn = chtls_close_con_rpl; - break; - case CPL_ABORT_REQ_RSS: - /* - * Save the offload device in the skb, we may process this - * message after the socket has closed. - */ - BLOG_SKB_CB(skb)->cdev = csk->cdev; - fn = chtls_abort_req_rss; - break; - case CPL_ABORT_RPL_RSS: - fn = chtls_abort_rpl_rss; - break; - default: - goto rel_skb; - } - - process_cpl_msg(fn, sk, skb); - return 0; - -rel_skb: - kfree_skb(skb); - return 0; -} - -static void chtls_rx_ack(struct sock *sk, struct sk_buff *skb) -{ - struct cpl_fw4_ack *hdr = cplhdr(skb) + RSS_HDR; - struct chtls_sock *csk = sk->sk_user_data; - struct tcp_sock *tp = tcp_sk(sk); - u32 credits = hdr->credits; - u32 snd_una; - - snd_una = ntohl(hdr->snd_una); - csk->wr_credits += credits; - - if (csk->wr_unacked > csk->wr_max_credits - csk->wr_credits) - csk->wr_unacked = csk->wr_max_credits - csk->wr_credits; - - while (credits) { - struct sk_buff *pskb = csk->wr_skb_head; - u32 csum; - - if (unlikely(!pskb)) { - if (csk->wr_nondata) - csk->wr_nondata -= credits; - break; - } - csum = (__force u32)pskb->csum; - if (unlikely(credits < csum)) { - pskb->csum = (__force __wsum)(csum - credits); - break; - } - dequeue_wr(sk); - credits -= csum; - kfree_skb(pskb); - } - if (hdr->seq_vld & CPL_FW4_ACK_FLAGS_SEQVAL) { - if (unlikely(before(snd_una, tp->snd_una))) { - kfree_skb(skb); - return; - } - - if (tp->snd_una != snd_una) { - tp->snd_una = snd_una; - tp->rcv_tstamp = tcp_jiffies32; - if (tp->snd_una == tp->snd_nxt && - !csk_flag_nochk(csk, CSK_TX_FAILOVER)) - csk_reset_flag(csk, CSK_TX_WAIT_IDLE); - } - } - - if (hdr->seq_vld & CPL_FW4_ACK_FLAGS_CH) { - unsigned int fclen16 = roundup(failover_flowc_wr_len, 16); - - csk->wr_credits -= fclen16; - csk_reset_flag(csk, CSK_TX_WAIT_IDLE); - csk_reset_flag(csk, CSK_TX_FAILOVER); - } - if (skb_queue_len(&csk->txq) && chtls_push_frames(csk, 0)) - sk->sk_write_space(sk); - - kfree_skb(skb); -} - -static int chtls_wr_ack(struct chtls_dev *cdev, struct sk_buff *skb) -{ - struct cpl_fw4_ack *rpl = cplhdr(skb) + RSS_HDR; - unsigned int hwtid = GET_TID(rpl); - struct sock *sk; - - sk = lookup_tid(cdev->tids, hwtid); - if (unlikely(!sk)) { - pr_err("can't find conn. for hwtid %u.\n", hwtid); - return -EINVAL; - } - process_cpl_msg(chtls_rx_ack, sk, skb); - - return 0; -} - -static int chtls_set_tcb_rpl(struct chtls_dev *cdev, struct sk_buff *skb) -{ - struct cpl_set_tcb_rpl *rpl = cplhdr(skb) + RSS_HDR; - unsigned int hwtid = GET_TID(rpl); - struct sock *sk; - - sk = lookup_tid(cdev->tids, hwtid); - - /* return EINVAL if socket doesn't exist */ - if (!sk) - return -EINVAL; - - /* Reusing the skb as size of cpl_set_tcb_field structure - * is greater than cpl_abort_req - */ - if (TCB_COOKIE_G(rpl->cookie) == TCB_FIELD_COOKIE_TFLAG) - chtls_send_abort(sk, CPL_ABORT_SEND_RST, NULL); - - kfree_skb(skb); - return 0; -} - -chtls_handler_func chtls_handlers[NUM_CPL_CMDS] = { - [CPL_PASS_OPEN_RPL] = chtls_pass_open_rpl, - [CPL_CLOSE_LISTSRV_RPL] = chtls_close_listsrv_rpl, - [CPL_PASS_ACCEPT_REQ] = chtls_pass_accept_req, - [CPL_PASS_ESTABLISH] = chtls_pass_establish, - [CPL_RX_DATA] = chtls_rx_data, - [CPL_TLS_DATA] = chtls_rx_pdu, - [CPL_RX_TLS_CMP] = chtls_rx_cmp, - [CPL_PEER_CLOSE] = chtls_conn_cpl, - [CPL_CLOSE_CON_RPL] = chtls_conn_cpl, - [CPL_ABORT_REQ_RSS] = chtls_conn_cpl, - [CPL_ABORT_RPL_RSS] = chtls_conn_cpl, - [CPL_FW4_ACK] = chtls_wr_ack, - [CPL_SET_TCB_RPL] = chtls_set_tcb_rpl, -}; diff --git a/drivers/net/ethernet/chelsio/inline_crypto/chtls/chtls_cm.h b/drivers/net/ethernet/chelsio/inline_crypto/chtls/chtls_cm.h deleted file mode 100644 index 29ceff5a5fcb..000000000000 --- a/drivers/net/ethernet/chelsio/inline_crypto/chtls/chtls_cm.h +++ /dev/null @@ -1,218 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * Copyright (c) 2018 Chelsio Communications, Inc. - */ - -#ifndef __CHTLS_CM_H__ -#define __CHTLS_CM_H__ - -/* - * TCB settings - */ -/* 3:0 */ -#define TCB_ULP_TYPE_W 0 -#define TCB_ULP_TYPE_S 0 -#define TCB_ULP_TYPE_M 0xfULL -#define TCB_ULP_TYPE_V(x) ((x) << TCB_ULP_TYPE_S) - -/* 11:4 */ -#define TCB_ULP_RAW_W 0 -#define TCB_ULP_RAW_S 4 -#define TCB_ULP_RAW_M 0xffULL -#define TCB_ULP_RAW_V(x) ((x) << TCB_ULP_RAW_S) - -#define TF_TLS_KEY_SIZE_S 7 -#define TF_TLS_KEY_SIZE_V(x) ((x) << TF_TLS_KEY_SIZE_S) - -#define TF_TLS_CONTROL_S 2 -#define TF_TLS_CONTROL_V(x) ((x) << TF_TLS_CONTROL_S) - -#define TF_TLS_ACTIVE_S 1 -#define TF_TLS_ACTIVE_V(x) ((x) << TF_TLS_ACTIVE_S) - -#define TF_TLS_ENABLE_S 0 -#define TF_TLS_ENABLE_V(x) ((x) << TF_TLS_ENABLE_S) - -#define TF_RX_QUIESCE_S 15 -#define TF_RX_QUIESCE_V(x) ((x) << TF_RX_QUIESCE_S) - -/* - * Max receive window supported by HW in bytes. Only a small part of it can - * be set through option0, the rest needs to be set through RX_DATA_ACK. - */ -#define MAX_RCV_WND ((1U << 27) - 1) -#define MAX_MSS 65536 - -/* - * Min receive window. We want it to be large enough to accommodate receive - * coalescing, handle jumbo frames, and not trigger sender SWS avoidance. - */ -#define MIN_RCV_WND (24 * 1024U) -#define LOOPBACK(x) (((x) & htonl(0xff000000)) == htonl(0x7f000000)) - -/* for TX: a skb must have a headroom of at least TX_HEADER_LEN bytes */ -#define TX_HEADER_LEN \ - (sizeof(struct fw_ofld_tx_data_wr) + sizeof(struct sge_opaque_hdr)) -#define TX_TLSHDR_LEN \ - (sizeof(struct fw_tlstx_data_wr) + sizeof(struct cpl_tx_tls_sfo) + \ - sizeof(struct sge_opaque_hdr)) -#define TXDATA_SKB_LEN 128 - -enum { - CPL_TX_TLS_SFO_TYPE_CCS, - CPL_TX_TLS_SFO_TYPE_ALERT, - CPL_TX_TLS_SFO_TYPE_HANDSHAKE, - CPL_TX_TLS_SFO_TYPE_DATA, - CPL_TX_TLS_SFO_TYPE_HEARTBEAT, -}; - -enum { - TLS_HDR_TYPE_CCS = 20, - TLS_HDR_TYPE_ALERT, - TLS_HDR_TYPE_HANDSHAKE, - TLS_HDR_TYPE_RECORD, - TLS_HDR_TYPE_HEARTBEAT, -}; - -typedef void (*defer_handler_t)(struct chtls_dev *dev, struct sk_buff *skb); -extern struct request_sock_ops chtls_rsk_ops; -extern struct request_sock_ops chtls_rsk_opsv6; - -struct deferred_skb_cb { - defer_handler_t handler; - struct chtls_dev *dev; -}; - -#define DEFERRED_SKB_CB(skb) ((struct deferred_skb_cb *)(skb)->cb) -#define failover_flowc_wr_len offsetof(struct fw_flowc_wr, mnemval[3]) -#define WR_SKB_CB(skb) ((struct wr_skb_cb *)(skb)->cb) -#define ACCEPT_QUEUE(sk) (&inet_csk(sk)->icsk_accept_queue.rskq_accept_head) - -#define SND_WSCALE(tp) ((tp)->rx_opt.snd_wscale) -#define RCV_WSCALE(tp) ((tp)->rx_opt.rcv_wscale) -#define USER_MSS(tp) (READ_ONCE((tp)->rx_opt.user_mss)) -#define TS_RECENT_STAMP(tp) ((tp)->rx_opt.ts_recent_stamp) -#define WSCALE_OK(tp) ((tp)->rx_opt.wscale_ok) -#define TSTAMP_OK(tp) ((tp)->rx_opt.tstamp_ok) -#define SACK_OK(tp) ((tp)->rx_opt.sack_ok) - -/* TLS SKB */ -#define skb_ulp_tls_inline(skb) (ULP_SKB_CB(skb)->ulp.tls.ofld) -#define skb_ulp_tls_iv_imm(skb) (ULP_SKB_CB(skb)->ulp.tls.iv) - -void chtls_defer_reply(struct sk_buff *skb, struct chtls_dev *dev, - defer_handler_t handler); - -/* - * Returns true if the socket is in one of the supplied states. - */ -static inline unsigned int sk_in_state(const struct sock *sk, - unsigned int states) -{ - return states & (1 << sk->sk_state); -} - -static void chtls_rsk_destructor(struct request_sock *req) -{ - /* do nothing */ -} - -static inline void chtls_init_rsk_ops(struct proto *chtls_tcp_prot, - struct request_sock_ops *chtls_tcp_ops, - struct proto *tcp_prot, int family) -{ - memset(chtls_tcp_ops, 0, sizeof(*chtls_tcp_ops)); - chtls_tcp_ops->family = family; - chtls_tcp_ops->obj_size = sizeof(struct tcp_request_sock); - chtls_tcp_ops->destructor = chtls_rsk_destructor; - chtls_tcp_ops->slab = tcp_prot->rsk_prot->slab; - chtls_tcp_prot->rsk_prot = chtls_tcp_ops; -} - -static inline void chtls_reqsk_free(struct request_sock *req) -{ - if (req->rsk_listener) - sock_put(req->rsk_listener); - kmem_cache_free(req->rsk_ops->slab, req); -} - -#define DECLARE_TASK_FUNC(task, task_param) \ - static void task(struct work_struct *task_param) - -static inline void sk_wakeup_sleepers(struct sock *sk, bool interruptable) -{ - struct socket_wq *wq; - - rcu_read_lock(); - wq = rcu_dereference(sk->sk_wq); - if (skwq_has_sleeper(wq)) { - if (interruptable) - wake_up_interruptible(sk_sleep(sk)); - else - wake_up_all(sk_sleep(sk)); - } - rcu_read_unlock(); -} - -static inline void chtls_set_req_port(struct request_sock *oreq, - __be16 source, __be16 dest) -{ - inet_rsk(oreq)->ir_rmt_port = source; - inet_rsk(oreq)->ir_num = ntohs(dest); -} - -static inline void chtls_set_req_addr(struct request_sock *oreq, - __be32 local_ip, __be32 peer_ip) -{ - inet_rsk(oreq)->ir_loc_addr = local_ip; - inet_rsk(oreq)->ir_rmt_addr = peer_ip; -} - -static inline void chtls_free_skb(struct sock *sk, struct sk_buff *skb) -{ - skb_dstref_steal(skb); - __skb_unlink(skb, &sk->sk_receive_queue); - __kfree_skb(skb); -} - -static inline void chtls_kfree_skb(struct sock *sk, struct sk_buff *skb) -{ - skb_dstref_steal(skb); - __skb_unlink(skb, &sk->sk_receive_queue); - kfree_skb(skb); -} - -static inline void chtls_reset_wr_list(struct chtls_sock *csk) -{ - csk->wr_skb_head = NULL; - csk->wr_skb_tail = NULL; -} - -static inline void enqueue_wr(struct chtls_sock *csk, struct sk_buff *skb) -{ - WR_SKB_CB(skb)->next_wr = NULL; - - skb_get(skb); - - if (!csk->wr_skb_head) - csk->wr_skb_head = skb; - else - WR_SKB_CB(csk->wr_skb_tail)->next_wr = skb; - csk->wr_skb_tail = skb; -} - -static inline struct sk_buff *dequeue_wr(struct sock *sk) -{ - struct chtls_sock *csk = rcu_dereference_sk_user_data(sk); - struct sk_buff *skb = NULL; - - skb = csk->wr_skb_head; - - if (likely(skb)) { - /* Don't bother clearing the tail */ - csk->wr_skb_head = WR_SKB_CB(skb)->next_wr; - WR_SKB_CB(skb)->next_wr = NULL; - } - return skb; -} -#endif diff --git a/drivers/net/ethernet/chelsio/inline_crypto/chtls/chtls_hw.c b/drivers/net/ethernet/chelsio/inline_crypto/chtls/chtls_hw.c deleted file mode 100644 index d84473ca844d..000000000000 --- a/drivers/net/ethernet/chelsio/inline_crypto/chtls/chtls_hw.c +++ /dev/null @@ -1,462 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (c) 2018 Chelsio Communications, Inc. - * - * Written by: Atul Gupta (atul.gupta@chelsio.com) - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "chtls.h" -#include "chtls_cm.h" - -static void __set_tcb_field_direct(struct chtls_sock *csk, - struct cpl_set_tcb_field *req, u16 word, - u64 mask, u64 val, u8 cookie, int no_reply) -{ - struct ulptx_idata *sc; - - INIT_TP_WR_CPL(req, CPL_SET_TCB_FIELD, csk->tid); - req->wr.wr_mid |= htonl(FW_WR_FLOWID_V(csk->tid)); - req->reply_ctrl = htons(NO_REPLY_V(no_reply) | - QUEUENO_V(csk->rss_qid)); - req->word_cookie = htons(TCB_WORD_V(word) | TCB_COOKIE_V(cookie)); - req->mask = cpu_to_be64(mask); - req->val = cpu_to_be64(val); - sc = (struct ulptx_idata *)(req + 1); - sc->cmd_more = htonl(ULPTX_CMD_V(ULP_TX_SC_NOOP)); - sc->len = htonl(0); -} - -static void __set_tcb_field(struct sock *sk, struct sk_buff *skb, u16 word, - u64 mask, u64 val, u8 cookie, int no_reply) -{ - struct cpl_set_tcb_field *req; - struct chtls_sock *csk; - struct ulptx_idata *sc; - unsigned int wrlen; - - wrlen = roundup(sizeof(*req) + sizeof(*sc), 16); - csk = rcu_dereference_sk_user_data(sk); - - req = (struct cpl_set_tcb_field *)__skb_put(skb, wrlen); - __set_tcb_field_direct(csk, req, word, mask, val, cookie, no_reply); - set_wr_txq(skb, CPL_PRIORITY_CONTROL, csk->port_id); -} - -/* - * Send control message to HW, message go as immediate data and packet - * is freed immediately. - */ -static int chtls_set_tcb_field(struct sock *sk, u16 word, u64 mask, u64 val) -{ - struct cpl_set_tcb_field *req; - unsigned int credits_needed; - struct chtls_sock *csk; - struct ulptx_idata *sc; - struct sk_buff *skb; - unsigned int wrlen; - int ret; - - wrlen = roundup(sizeof(*req) + sizeof(*sc), 16); - - skb = alloc_skb(wrlen, GFP_ATOMIC); - if (!skb) - return -ENOMEM; - - credits_needed = DIV_ROUND_UP(wrlen, 16); - csk = rcu_dereference_sk_user_data(sk); - - __set_tcb_field(sk, skb, word, mask, val, 0, 1); - skb_set_queue_mapping(skb, (csk->txq_idx << 1) | CPL_PRIORITY_DATA); - csk->wr_credits -= credits_needed; - csk->wr_unacked += credits_needed; - enqueue_wr(csk, skb); - ret = cxgb4_ofld_send(csk->egress_dev, skb); - if (ret < 0) - kfree_skb(skb); - return ret < 0 ? ret : 0; -} - -void chtls_set_tcb_field_rpl_skb(struct sock *sk, u16 word, - u64 mask, u64 val, u8 cookie, - int through_l2t) -{ - struct sk_buff *skb; - unsigned int wrlen; - - wrlen = sizeof(struct cpl_set_tcb_field) + sizeof(struct ulptx_idata); - wrlen = roundup(wrlen, 16); - - skb = alloc_skb(wrlen, GFP_KERNEL | __GFP_NOFAIL); - if (!skb) - return; - - __set_tcb_field(sk, skb, word, mask, val, cookie, 0); - send_or_defer(sk, tcp_sk(sk), skb, through_l2t); -} - -static int chtls_set_tcb_keyid(struct sock *sk, int keyid) -{ - return chtls_set_tcb_field(sk, 31, 0xFFFFFFFFULL, keyid); -} - -static int chtls_set_tcb_seqno(struct sock *sk) -{ - return chtls_set_tcb_field(sk, 28, ~0ULL, 0); -} - -static int chtls_set_tcb_quiesce(struct sock *sk, int val) -{ - return chtls_set_tcb_field(sk, 1, (1ULL << TF_RX_QUIESCE_S), - TF_RX_QUIESCE_V(val)); -} - -void chtls_set_quiesce_ctrl(struct sock *sk, int val) -{ - struct chtls_sock *csk; - struct sk_buff *skb; - unsigned int wrlen; - int ret; - - wrlen = sizeof(struct cpl_set_tcb_field) + sizeof(struct ulptx_idata); - wrlen = roundup(wrlen, 16); - - skb = alloc_skb(wrlen, GFP_ATOMIC); - if (!skb) - return; - - csk = rcu_dereference_sk_user_data(sk); - - __set_tcb_field(sk, skb, 1, TF_RX_QUIESCE_V(1), 0, 0, 1); - set_wr_txq(skb, CPL_PRIORITY_CONTROL, csk->port_id); - ret = cxgb4_ofld_send(csk->egress_dev, skb); - if (ret < 0) - kfree_skb(skb); -} - -/* TLS Key bitmap processing */ -int chtls_init_kmap(struct chtls_dev *cdev, struct cxgb4_lld_info *lldi) -{ - unsigned int num_key_ctx, bsize; - int ksize; - - num_key_ctx = (lldi->vr->key.size / TLS_KEY_CONTEXT_SZ); - bsize = BITS_TO_LONGS(num_key_ctx); - - cdev->kmap.size = num_key_ctx; - cdev->kmap.available = bsize; - ksize = sizeof(*cdev->kmap.addr) * bsize; - cdev->kmap.addr = kvzalloc(ksize, GFP_KERNEL); - if (!cdev->kmap.addr) - return -ENOMEM; - - cdev->kmap.start = lldi->vr->key.start; - spin_lock_init(&cdev->kmap.lock); - return 0; -} - -static int get_new_keyid(struct chtls_sock *csk, u32 optname) -{ - struct net_device *dev = csk->egress_dev; - struct chtls_dev *cdev = csk->cdev; - struct chtls_hws *hws; - struct adapter *adap; - int keyid; - - adap = netdev2adap(dev); - hws = &csk->tlshws; - - spin_lock_bh(&cdev->kmap.lock); - keyid = find_first_zero_bit(cdev->kmap.addr, cdev->kmap.size); - if (keyid < cdev->kmap.size) { - __set_bit(keyid, cdev->kmap.addr); - if (optname == TLS_RX) - hws->rxkey = keyid; - else - hws->txkey = keyid; - atomic_inc(&adap->chcr_stats.tls_key); - } else { - keyid = -1; - } - spin_unlock_bh(&cdev->kmap.lock); - return keyid; -} - -void free_tls_keyid(struct sock *sk) -{ - struct chtls_sock *csk = rcu_dereference_sk_user_data(sk); - struct net_device *dev = csk->egress_dev; - struct chtls_dev *cdev = csk->cdev; - struct chtls_hws *hws; - struct adapter *adap; - - if (!cdev->kmap.addr) - return; - - adap = netdev2adap(dev); - hws = &csk->tlshws; - - spin_lock_bh(&cdev->kmap.lock); - if (hws->rxkey >= 0) { - __clear_bit(hws->rxkey, cdev->kmap.addr); - atomic_dec(&adap->chcr_stats.tls_key); - hws->rxkey = -1; - } - if (hws->txkey >= 0) { - __clear_bit(hws->txkey, cdev->kmap.addr); - atomic_dec(&adap->chcr_stats.tls_key); - hws->txkey = -1; - } - spin_unlock_bh(&cdev->kmap.lock); -} - -unsigned int keyid_to_addr(int start_addr, int keyid) -{ - return (start_addr + (keyid * TLS_KEY_CONTEXT_SZ)) >> 5; -} - -static void chtls_rxkey_ivauth(struct _key_ctx *kctx) -{ - kctx->iv_to_auth = cpu_to_be64(KEYCTX_TX_WR_IV_V(6ULL) | - KEYCTX_TX_WR_AAD_V(1ULL) | - KEYCTX_TX_WR_AADST_V(5ULL) | - KEYCTX_TX_WR_CIPHER_V(14ULL) | - KEYCTX_TX_WR_CIPHERST_V(0ULL) | - KEYCTX_TX_WR_AUTH_V(14ULL) | - KEYCTX_TX_WR_AUTHST_V(16ULL) | - KEYCTX_TX_WR_AUTHIN_V(16ULL)); -} - -static int chtls_key_info(struct chtls_sock *csk, - struct _key_ctx *kctx, - u32 keylen, u32 optname, - int cipher_type) -{ - unsigned char key[AES_MAX_KEY_SIZE]; - unsigned char *key_p, *salt; - unsigned char ghash_h[AEAD_H_SIZE]; - int ck_size, key_ctx_size, kctx_mackey_size, salt_size; - struct aes_enckey aes; - int ret; - - key_ctx_size = sizeof(struct _key_ctx) + - roundup(keylen, 16) + AEAD_H_SIZE; - - /* GCM mode of AES supports 128 and 256 bit encryption, so - * prepare key context base on GCM cipher type - */ - switch (cipher_type) { - case TLS_CIPHER_AES_GCM_128: { - struct tls12_crypto_info_aes_gcm_128 *gcm_ctx_128 = - (struct tls12_crypto_info_aes_gcm_128 *) - &csk->tlshws.crypto_info; - memcpy(key, gcm_ctx_128->key, keylen); - - key_p = gcm_ctx_128->key; - salt = gcm_ctx_128->salt; - ck_size = CHCR_KEYCTX_CIPHER_KEY_SIZE_128; - salt_size = TLS_CIPHER_AES_GCM_128_SALT_SIZE; - kctx_mackey_size = CHCR_KEYCTX_MAC_KEY_SIZE_128; - break; - } - case TLS_CIPHER_AES_GCM_256: { - struct tls12_crypto_info_aes_gcm_256 *gcm_ctx_256 = - (struct tls12_crypto_info_aes_gcm_256 *) - &csk->tlshws.crypto_info; - memcpy(key, gcm_ctx_256->key, keylen); - - key_p = gcm_ctx_256->key; - salt = gcm_ctx_256->salt; - ck_size = CHCR_KEYCTX_CIPHER_KEY_SIZE_256; - salt_size = TLS_CIPHER_AES_GCM_256_SALT_SIZE; - kctx_mackey_size = CHCR_KEYCTX_MAC_KEY_SIZE_256; - break; - } - default: - pr_err("GCM: Invalid key length %d\n", keylen); - return -EINVAL; - } - - /* Calculate the H = CIPH(K, 0 repeated 16 times). - * It will go in key context - */ - ret = aes_prepareenckey(&aes, key, keylen); - if (ret) - return ret; - - memset(ghash_h, 0, AEAD_H_SIZE); - aes_encrypt(&aes, ghash_h, ghash_h); - memzero_explicit(&aes, sizeof(aes)); - csk->tlshws.keylen = key_ctx_size; - - /* Copy the Key context */ - if (optname == TLS_RX) { - int key_ctx; - - key_ctx = ((key_ctx_size >> 4) << 3); - kctx->ctx_hdr = FILL_KEY_CRX_HDR(ck_size, - kctx_mackey_size, - 0, 0, key_ctx); - chtls_rxkey_ivauth(kctx); - } else { - kctx->ctx_hdr = FILL_KEY_CTX_HDR(ck_size, - kctx_mackey_size, - 0, 0, key_ctx_size >> 4); - } - - memcpy(kctx->salt, salt, salt_size); - memcpy(kctx->key, key_p, keylen); - memcpy(kctx->key + keylen, ghash_h, AEAD_H_SIZE); - /* erase key info from driver */ - memset(key_p, 0, keylen); - - return 0; -} - -static void chtls_set_scmd(struct chtls_sock *csk) -{ - struct chtls_hws *hws = &csk->tlshws; - - hws->scmd.seqno_numivs = - SCMD_SEQ_NO_CTRL_V(3) | - SCMD_PROTO_VERSION_V(0) | - SCMD_ENC_DEC_CTRL_V(0) | - SCMD_CIPH_AUTH_SEQ_CTRL_V(1) | - SCMD_CIPH_MODE_V(2) | - SCMD_AUTH_MODE_V(4) | - SCMD_HMAC_CTRL_V(0) | - SCMD_IV_SIZE_V(4) | - SCMD_NUM_IVS_V(1); - - hws->scmd.ivgen_hdrlen = - SCMD_IV_GEN_CTRL_V(1) | - SCMD_KEY_CTX_INLINE_V(0) | - SCMD_TLS_FRAG_ENABLE_V(1); -} - -int chtls_setkey(struct chtls_sock *csk, u32 keylen, - u32 optname, int cipher_type) -{ - struct tls_key_req *kwr; - struct chtls_dev *cdev; - struct _key_ctx *kctx; - int wrlen, klen, len; - struct sk_buff *skb; - struct sock *sk; - int keyid; - int kaddr; - int ret; - - cdev = csk->cdev; - sk = csk->sk; - - klen = roundup((keylen + AEAD_H_SIZE) + sizeof(*kctx), 32); - wrlen = roundup(sizeof(*kwr), 16); - len = klen + wrlen; - - /* Flush out-standing data before new key takes effect */ - if (optname == TLS_TX) { - lock_sock(sk); - if (skb_queue_len(&csk->txq)) - chtls_push_frames(csk, 0); - release_sock(sk); - } - - skb = alloc_skb(len, GFP_KERNEL); - if (!skb) - return -ENOMEM; - - keyid = get_new_keyid(csk, optname); - if (keyid < 0) { - ret = -ENOSPC; - goto out_nokey; - } - - kaddr = keyid_to_addr(cdev->kmap.start, keyid); - kwr = (struct tls_key_req *)__skb_put_zero(skb, len); - kwr->wr.op_to_compl = - cpu_to_be32(FW_WR_OP_V(FW_ULPTX_WR) | FW_WR_COMPL_F | - FW_WR_ATOMIC_V(1U)); - kwr->wr.flowid_len16 = - cpu_to_be32(FW_WR_LEN16_V(DIV_ROUND_UP(len, 16) | - FW_WR_FLOWID_V(csk->tid))); - kwr->wr.protocol = 0; - kwr->wr.mfs = htons(TLS_MFS); - kwr->wr.reneg_to_write_rx = optname; - - /* ulptx command */ - kwr->req.cmd = cpu_to_be32(ULPTX_CMD_V(ULP_TX_MEM_WRITE) | - T5_ULP_MEMIO_ORDER_V(1) | - T5_ULP_MEMIO_IMM_V(1)); - kwr->req.len16 = cpu_to_be32((csk->tid << 8) | - DIV_ROUND_UP(len - sizeof(kwr->wr), 16)); - kwr->req.dlen = cpu_to_be32(ULP_MEMIO_DATA_LEN_V(klen >> 5)); - kwr->req.lock_addr = cpu_to_be32(ULP_MEMIO_ADDR_V(kaddr)); - - /* sub command */ - kwr->sc_imm.cmd_more = cpu_to_be32(ULPTX_CMD_V(ULP_TX_SC_IMM)); - kwr->sc_imm.len = cpu_to_be32(klen); - - lock_sock(sk); - /* key info */ - kctx = (struct _key_ctx *)(kwr + 1); - ret = chtls_key_info(csk, kctx, keylen, optname, cipher_type); - if (ret) - goto out_notcb; - - if (unlikely(csk_flag(sk, CSK_ABORT_SHUTDOWN))) - goto out_notcb; - - set_wr_txq(skb, CPL_PRIORITY_DATA, csk->tlshws.txqid); - csk->wr_credits -= DIV_ROUND_UP(len, 16); - csk->wr_unacked += DIV_ROUND_UP(len, 16); - enqueue_wr(csk, skb); - cxgb4_ofld_send(csk->egress_dev, skb); - skb = NULL; - - chtls_set_scmd(csk); - /* Clear quiesce for Rx key */ - if (optname == TLS_RX) { - ret = chtls_set_tcb_keyid(sk, keyid); - if (ret) - goto out_notcb; - ret = chtls_set_tcb_field(sk, 0, - TCB_ULP_RAW_V(TCB_ULP_RAW_M), - TCB_ULP_RAW_V((TF_TLS_KEY_SIZE_V(1) | - TF_TLS_CONTROL_V(1) | - TF_TLS_ACTIVE_V(1) | - TF_TLS_ENABLE_V(1)))); - if (ret) - goto out_notcb; - ret = chtls_set_tcb_seqno(sk); - if (ret) - goto out_notcb; - ret = chtls_set_tcb_quiesce(sk, 0); - if (ret) - goto out_notcb; - csk->tlshws.rxkey = keyid; - } else { - csk->tlshws.tx_seq_no = 0; - csk->tlshws.txkey = keyid; - } - - release_sock(sk); - return ret; -out_notcb: - release_sock(sk); - free_tls_keyid(sk); -out_nokey: - kfree_skb(skb); - return ret; -} diff --git a/drivers/net/ethernet/chelsio/inline_crypto/chtls/chtls_io.c b/drivers/net/ethernet/chelsio/inline_crypto/chtls/chtls_io.c deleted file mode 100644 index c8e99409a52a..000000000000 --- a/drivers/net/ethernet/chelsio/inline_crypto/chtls/chtls_io.c +++ /dev/null @@ -1,1836 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (c) 2018 Chelsio Communications, Inc. - * - * Written by: Atul Gupta (atul.gupta@chelsio.com) - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "chtls.h" -#include "chtls_cm.h" - -static bool is_tls_tx(struct chtls_sock *csk) -{ - return csk->tlshws.txkey >= 0; -} - -static bool is_tls_rx(struct chtls_sock *csk) -{ - return csk->tlshws.rxkey >= 0; -} - -static int data_sgl_len(const struct sk_buff *skb) -{ - unsigned int cnt; - - cnt = skb_shinfo(skb)->nr_frags; - return sgl_len(cnt) * 8; -} - -static int nos_ivs(struct sock *sk, unsigned int size) -{ - struct chtls_sock *csk = rcu_dereference_sk_user_data(sk); - - return DIV_ROUND_UP(size, csk->tlshws.mfs); -} - -static int set_ivs_imm(struct sock *sk, const struct sk_buff *skb) -{ - int ivs_size = nos_ivs(sk, skb->len) * CIPHER_BLOCK_SIZE; - int hlen = TLS_WR_CPL_LEN + data_sgl_len(skb); - - if ((hlen + KEY_ON_MEM_SZ + ivs_size) < - MAX_IMM_OFLD_TX_DATA_WR_LEN) { - ULP_SKB_CB(skb)->ulp.tls.iv = 1; - return 1; - } - ULP_SKB_CB(skb)->ulp.tls.iv = 0; - return 0; -} - -static int max_ivs_size(struct sock *sk, int size) -{ - return nos_ivs(sk, size) * CIPHER_BLOCK_SIZE; -} - -static int ivs_size(struct sock *sk, const struct sk_buff *skb) -{ - return set_ivs_imm(sk, skb) ? (nos_ivs(sk, skb->len) * - CIPHER_BLOCK_SIZE) : 0; -} - -static int flowc_wr_credits(int nparams, int *flowclenp) -{ - int flowclen16, flowclen; - - flowclen = offsetof(struct fw_flowc_wr, mnemval[nparams]); - flowclen16 = DIV_ROUND_UP(flowclen, 16); - flowclen = flowclen16 * 16; - - if (flowclenp) - *flowclenp = flowclen; - - return flowclen16; -} - -static struct sk_buff *create_flowc_wr_skb(struct sock *sk, - struct fw_flowc_wr *flowc, - int flowclen) -{ - struct chtls_sock *csk = rcu_dereference_sk_user_data(sk); - struct sk_buff *skb; - - skb = alloc_skb(flowclen, GFP_ATOMIC); - if (!skb) - return NULL; - - __skb_put_data(skb, flowc, flowclen); - skb_set_queue_mapping(skb, (csk->txq_idx << 1) | CPL_PRIORITY_DATA); - - return skb; -} - -static int send_flowc_wr(struct sock *sk, struct fw_flowc_wr *flowc, - int flowclen) -{ - struct chtls_sock *csk = rcu_dereference_sk_user_data(sk); - struct tcp_sock *tp = tcp_sk(sk); - struct sk_buff *skb; - int flowclen16; - int ret; - - flowclen16 = flowclen / 16; - - if (csk_flag(sk, CSK_TX_DATA_SENT)) { - skb = create_flowc_wr_skb(sk, flowc, flowclen); - if (!skb) - return -ENOMEM; - - skb_entail(sk, skb, - ULPCB_FLAG_NO_HDR | ULPCB_FLAG_NO_APPEND); - return 0; - } - - ret = cxgb4_immdata_send(csk->egress_dev, - csk->txq_idx, - flowc, flowclen); - if (!ret) - return flowclen16; - skb = create_flowc_wr_skb(sk, flowc, flowclen); - if (!skb) - return -ENOMEM; - send_or_defer(sk, tp, skb, 0); - return flowclen16; -} - -static u8 tcp_state_to_flowc_state(u8 state) -{ - switch (state) { - case TCP_ESTABLISHED: - return FW_FLOWC_MNEM_TCPSTATE_ESTABLISHED; - case TCP_CLOSE_WAIT: - return FW_FLOWC_MNEM_TCPSTATE_CLOSEWAIT; - case TCP_FIN_WAIT1: - return FW_FLOWC_MNEM_TCPSTATE_FINWAIT1; - case TCP_CLOSING: - return FW_FLOWC_MNEM_TCPSTATE_CLOSING; - case TCP_LAST_ACK: - return FW_FLOWC_MNEM_TCPSTATE_LASTACK; - case TCP_FIN_WAIT2: - return FW_FLOWC_MNEM_TCPSTATE_FINWAIT2; - } - - return FW_FLOWC_MNEM_TCPSTATE_ESTABLISHED; -} - -int send_tx_flowc_wr(struct sock *sk, int compl, - u32 snd_nxt, u32 rcv_nxt) -{ - DEFINE_RAW_FLEX(struct fw_flowc_wr, flowc, mnemval, FW_FLOWC_MNEM_MAX); - int nparams, paramidx, flowclen16, flowclen; - struct chtls_sock *csk; - struct tcp_sock *tp; - - csk = rcu_dereference_sk_user_data(sk); - tp = tcp_sk(sk); - -#define FLOWC_PARAM(__m, __v) \ - do { \ - flowc->mnemval[paramidx].mnemonic = FW_FLOWC_MNEM_##__m; \ - flowc->mnemval[paramidx].val = cpu_to_be32(__v); \ - paramidx++; \ - } while (0) - - paramidx = 0; - - FLOWC_PARAM(PFNVFN, FW_PFVF_CMD_PFN_V(csk->cdev->lldi->pf)); - FLOWC_PARAM(CH, csk->tx_chan); - FLOWC_PARAM(PORT, csk->tx_chan); - FLOWC_PARAM(IQID, csk->rss_qid); - FLOWC_PARAM(SNDNXT, tp->snd_nxt); - FLOWC_PARAM(RCVNXT, tp->rcv_nxt); - FLOWC_PARAM(SNDBUF, csk->sndbuf); - FLOWC_PARAM(MSS, tp->mss_cache); - FLOWC_PARAM(TCPSTATE, tcp_state_to_flowc_state(sk->sk_state)); - - if (SND_WSCALE(tp)) - FLOWC_PARAM(RCV_SCALE, SND_WSCALE(tp)); - - if (csk->ulp_mode == ULP_MODE_TLS) - FLOWC_PARAM(ULD_MODE, ULP_MODE_TLS); - - if (csk->tlshws.fcplenmax) - FLOWC_PARAM(TXDATAPLEN_MAX, csk->tlshws.fcplenmax); - - nparams = paramidx; -#undef FLOWC_PARAM - - flowclen16 = flowc_wr_credits(nparams, &flowclen); - flowc->op_to_nparams = - cpu_to_be32(FW_WR_OP_V(FW_FLOWC_WR) | - FW_WR_COMPL_V(compl) | - FW_FLOWC_WR_NPARAMS_V(nparams)); - flowc->flowid_len16 = cpu_to_be32(FW_WR_LEN16_V(flowclen16) | - FW_WR_FLOWID_V(csk->tid)); - - return send_flowc_wr(sk, flowc, flowclen); -} - -/* Copy IVs to WR */ -static int tls_copy_ivs(struct sock *sk, struct sk_buff *skb) - -{ - struct chtls_sock *csk; - unsigned char *iv_loc; - struct chtls_hws *hws; - unsigned char *ivs; - u16 number_of_ivs; - struct page *page; - int err = 0; - - csk = rcu_dereference_sk_user_data(sk); - hws = &csk->tlshws; - number_of_ivs = nos_ivs(sk, skb->len); - - if (number_of_ivs > MAX_IVS_PAGE) { - pr_warn("MAX IVs in PAGE exceeded %d\n", number_of_ivs); - return -ENOMEM; - } - - /* generate the IVs */ - ivs = kmalloc_array(CIPHER_BLOCK_SIZE, number_of_ivs, GFP_ATOMIC); - if (!ivs) - return -ENOMEM; - get_random_bytes(ivs, number_of_ivs * CIPHER_BLOCK_SIZE); - - if (skb_ulp_tls_iv_imm(skb)) { - /* send the IVs as immediate data in the WR */ - iv_loc = (unsigned char *)__skb_push(skb, number_of_ivs * - CIPHER_BLOCK_SIZE); - if (iv_loc) - memcpy(iv_loc, ivs, number_of_ivs * CIPHER_BLOCK_SIZE); - - hws->ivsize = number_of_ivs * CIPHER_BLOCK_SIZE; - } else { - /* Send the IVs as sgls */ - /* Already accounted IV DSGL for credits */ - skb_shinfo(skb)->nr_frags--; - page = alloc_pages(sk->sk_allocation | __GFP_COMP, 0); - if (!page) { - pr_info("%s : Page allocation for IVs failed\n", - __func__); - err = -ENOMEM; - goto out; - } - memcpy(page_address(page), ivs, number_of_ivs * - CIPHER_BLOCK_SIZE); - skb_fill_page_desc(skb, skb_shinfo(skb)->nr_frags, page, 0, - number_of_ivs * CIPHER_BLOCK_SIZE); - hws->ivsize = 0; - } -out: - kfree(ivs); - return err; -} - -/* Copy Key to WR */ -static void tls_copy_tx_key(struct sock *sk, struct sk_buff *skb) -{ - struct ulptx_sc_memrd *sc_memrd; - struct chtls_sock *csk; - struct chtls_dev *cdev; - struct ulptx_idata *sc; - struct chtls_hws *hws; - u32 immdlen; - int kaddr; - - csk = rcu_dereference_sk_user_data(sk); - hws = &csk->tlshws; - cdev = csk->cdev; - - immdlen = sizeof(*sc) + sizeof(*sc_memrd); - kaddr = keyid_to_addr(cdev->kmap.start, hws->txkey); - sc = (struct ulptx_idata *)__skb_push(skb, immdlen); - if (sc) { - sc->cmd_more = htonl(ULPTX_CMD_V(ULP_TX_SC_NOOP)); - sc->len = htonl(0); - sc_memrd = (struct ulptx_sc_memrd *)(sc + 1); - sc_memrd->cmd_to_len = - htonl(ULPTX_CMD_V(ULP_TX_SC_MEMRD) | - ULP_TX_SC_MORE_V(1) | - ULPTX_LEN16_V(hws->keylen >> 4)); - sc_memrd->addr = htonl(kaddr); - } -} - -static u64 tlstx_incr_seqnum(struct chtls_hws *hws) -{ - return hws->tx_seq_no++; -} - -static bool is_sg_request(const struct sk_buff *skb) -{ - return skb->peeked || - (skb->len > MAX_IMM_ULPTX_WR_LEN); -} - -/* - * Returns true if an sk_buff carries urgent data. - */ -static bool skb_urgent(struct sk_buff *skb) -{ - return ULP_SKB_CB(skb)->flags & ULPCB_FLAG_URG; -} - -/* TLS content type for CPL SFO */ -static unsigned char tls_content_type(unsigned char content_type) -{ - switch (content_type) { - case TLS_HDR_TYPE_CCS: - return CPL_TX_TLS_SFO_TYPE_CCS; - case TLS_HDR_TYPE_ALERT: - return CPL_TX_TLS_SFO_TYPE_ALERT; - case TLS_HDR_TYPE_HANDSHAKE: - return CPL_TX_TLS_SFO_TYPE_HANDSHAKE; - case TLS_HDR_TYPE_HEARTBEAT: - return CPL_TX_TLS_SFO_TYPE_HEARTBEAT; - } - return CPL_TX_TLS_SFO_TYPE_DATA; -} - -static void tls_tx_data_wr(struct sock *sk, struct sk_buff *skb, - int dlen, int tls_immd, u32 credits, - int expn, int pdus) -{ - struct fw_tlstx_data_wr *req_wr; - struct cpl_tx_tls_sfo *req_cpl; - unsigned int wr_ulp_mode_force; - struct tls_scmd *updated_scmd; - unsigned char data_type; - struct chtls_sock *csk; - struct net_device *dev; - struct chtls_hws *hws; - struct tls_scmd *scmd; - struct adapter *adap; - unsigned char *req; - int immd_len; - int iv_imm; - int len; - - csk = rcu_dereference_sk_user_data(sk); - iv_imm = skb_ulp_tls_iv_imm(skb); - dev = csk->egress_dev; - adap = netdev2adap(dev); - hws = &csk->tlshws; - scmd = &hws->scmd; - len = dlen + expn; - - dlen = (dlen < hws->mfs) ? dlen : hws->mfs; - atomic_inc(&adap->chcr_stats.tls_pdu_tx); - - updated_scmd = scmd; - updated_scmd->seqno_numivs &= 0xffffff80; - updated_scmd->seqno_numivs |= SCMD_NUM_IVS_V(pdus); - hws->scmd = *updated_scmd; - - req = (unsigned char *)__skb_push(skb, sizeof(struct cpl_tx_tls_sfo)); - req_cpl = (struct cpl_tx_tls_sfo *)req; - req = (unsigned char *)__skb_push(skb, (sizeof(struct - fw_tlstx_data_wr))); - - req_wr = (struct fw_tlstx_data_wr *)req; - immd_len = (tls_immd ? dlen : 0); - req_wr->op_to_immdlen = - htonl(FW_WR_OP_V(FW_TLSTX_DATA_WR) | - FW_TLSTX_DATA_WR_COMPL_V(1) | - FW_TLSTX_DATA_WR_IMMDLEN_V(immd_len)); - req_wr->flowid_len16 = htonl(FW_TLSTX_DATA_WR_FLOWID_V(csk->tid) | - FW_TLSTX_DATA_WR_LEN16_V(credits)); - wr_ulp_mode_force = TX_ULP_MODE_V(ULP_MODE_TLS); - - if (is_sg_request(skb)) - wr_ulp_mode_force |= FW_OFLD_TX_DATA_WR_ALIGNPLD_F | - ((tcp_sk(sk)->nonagle & TCP_NAGLE_OFF) ? 0 : - FW_OFLD_TX_DATA_WR_SHOVE_F); - - req_wr->lsodisable_to_flags = - htonl(TX_ULP_MODE_V(ULP_MODE_TLS) | - TX_URG_V(skb_urgent(skb)) | - T6_TX_FORCE_F | wr_ulp_mode_force | - TX_SHOVE_V((!csk_flag(sk, CSK_TX_MORE_DATA)) && - skb_queue_empty(&csk->txq))); - - req_wr->ctxloc_to_exp = - htonl(FW_TLSTX_DATA_WR_NUMIVS_V(pdus) | - FW_TLSTX_DATA_WR_EXP_V(expn) | - FW_TLSTX_DATA_WR_CTXLOC_V(CHTLS_KEY_CONTEXT_DDR) | - FW_TLSTX_DATA_WR_IVDSGL_V(!iv_imm) | - FW_TLSTX_DATA_WR_KEYSIZE_V(hws->keylen >> 4)); - - /* Fill in the length */ - req_wr->plen = htonl(len); - req_wr->mfs = htons(hws->mfs); - req_wr->adjustedplen_pkd = - htons(FW_TLSTX_DATA_WR_ADJUSTEDPLEN_V(hws->adjustlen)); - req_wr->expinplenmax_pkd = - htons(FW_TLSTX_DATA_WR_EXPINPLENMAX_V(hws->expansion)); - req_wr->pdusinplenmax_pkd = - FW_TLSTX_DATA_WR_PDUSINPLENMAX_V(hws->pdus); - req_wr->r10 = 0; - - data_type = tls_content_type(ULP_SKB_CB(skb)->ulp.tls.type); - req_cpl->op_to_seg_len = htonl(CPL_TX_TLS_SFO_OPCODE_V(CPL_TX_TLS_SFO) | - CPL_TX_TLS_SFO_DATA_TYPE_V(data_type) | - CPL_TX_TLS_SFO_CPL_LEN_V(2) | - CPL_TX_TLS_SFO_SEG_LEN_V(dlen)); - req_cpl->pld_len = htonl(len - expn); - - req_cpl->type_protover = htonl(CPL_TX_TLS_SFO_TYPE_V - ((data_type == CPL_TX_TLS_SFO_TYPE_HEARTBEAT) ? - TLS_HDR_TYPE_HEARTBEAT : 0) | - CPL_TX_TLS_SFO_PROTOVER_V(0)); - - /* create the s-command */ - req_cpl->r1_lo = 0; - req_cpl->seqno_numivs = cpu_to_be32(hws->scmd.seqno_numivs); - req_cpl->ivgen_hdrlen = cpu_to_be32(hws->scmd.ivgen_hdrlen); - req_cpl->scmd1 = cpu_to_be64(tlstx_incr_seqnum(hws)); -} - -/* - * Calculate the TLS data expansion size - */ -static int chtls_expansion_size(struct sock *sk, int data_len, - int fullpdu, - unsigned short *pducnt) -{ - struct chtls_sock *csk = rcu_dereference_sk_user_data(sk); - struct chtls_hws *hws = &csk->tlshws; - struct tls_scmd *scmd = &hws->scmd; - int fragsize = hws->mfs; - int expnsize = 0; - int fragleft; - int fragcnt; - int expppdu; - - if (SCMD_CIPH_MODE_G(scmd->seqno_numivs) == - SCMD_CIPH_MODE_AES_GCM) { - expppdu = GCM_TAG_SIZE + AEAD_EXPLICIT_DATA_SIZE + - TLS_HEADER_LENGTH; - - if (fullpdu) { - *pducnt = data_len / (expppdu + fragsize); - if (*pducnt > 32) - *pducnt = 32; - else if (!*pducnt) - *pducnt = 1; - expnsize = (*pducnt) * expppdu; - return expnsize; - } - fragcnt = (data_len / fragsize); - expnsize = fragcnt * expppdu; - fragleft = data_len % fragsize; - if (fragleft > 0) - expnsize += expppdu; - } - return expnsize; -} - -/* WR with IV, KEY and CPL SFO added */ -static void make_tlstx_data_wr(struct sock *sk, struct sk_buff *skb, - int tls_tx_imm, int tls_len, u32 credits) -{ - unsigned short pdus_per_ulp = 0; - struct chtls_sock *csk; - struct chtls_hws *hws; - int expn_sz; - int pdus; - - csk = rcu_dereference_sk_user_data(sk); - hws = &csk->tlshws; - pdus = DIV_ROUND_UP(tls_len, hws->mfs); - expn_sz = chtls_expansion_size(sk, tls_len, 0, NULL); - if (!hws->compute) { - hws->expansion = chtls_expansion_size(sk, - hws->fcplenmax, - 1, &pdus_per_ulp); - hws->pdus = pdus_per_ulp; - hws->adjustlen = hws->pdus * - ((hws->expansion / hws->pdus) + hws->mfs); - hws->compute = 1; - } - if (tls_copy_ivs(sk, skb)) - return; - tls_copy_tx_key(sk, skb); - tls_tx_data_wr(sk, skb, tls_len, tls_tx_imm, credits, expn_sz, pdus); - hws->tx_seq_no += (pdus - 1); -} - -static void make_tx_data_wr(struct sock *sk, struct sk_buff *skb, - unsigned int immdlen, int len, - u32 credits, u32 compl) -{ - struct fw_ofld_tx_data_wr *req; - unsigned int wr_ulp_mode_force; - struct chtls_sock *csk; - unsigned int opcode; - - csk = rcu_dereference_sk_user_data(sk); - opcode = FW_OFLD_TX_DATA_WR; - - req = (struct fw_ofld_tx_data_wr *)__skb_push(skb, sizeof(*req)); - req->op_to_immdlen = htonl(WR_OP_V(opcode) | - FW_WR_COMPL_V(compl) | - FW_WR_IMMDLEN_V(immdlen)); - req->flowid_len16 = htonl(FW_WR_FLOWID_V(csk->tid) | - FW_WR_LEN16_V(credits)); - - wr_ulp_mode_force = TX_ULP_MODE_V(csk->ulp_mode); - if (is_sg_request(skb)) - wr_ulp_mode_force |= FW_OFLD_TX_DATA_WR_ALIGNPLD_F | - ((tcp_sk(sk)->nonagle & TCP_NAGLE_OFF) ? 0 : - FW_OFLD_TX_DATA_WR_SHOVE_F); - - req->tunnel_to_proxy = htonl(wr_ulp_mode_force | - TX_URG_V(skb_urgent(skb)) | - TX_SHOVE_V((!csk_flag(sk, CSK_TX_MORE_DATA)) && - skb_queue_empty(&csk->txq))); - req->plen = htonl(len); -} - -static int chtls_wr_size(struct chtls_sock *csk, const struct sk_buff *skb, - bool size) -{ - int wr_size; - - wr_size = TLS_WR_CPL_LEN; - wr_size += KEY_ON_MEM_SZ; - wr_size += ivs_size(csk->sk, skb); - - if (size) - return wr_size; - - /* frags counted for IV dsgl */ - if (!skb_ulp_tls_iv_imm(skb)) - skb_shinfo(skb)->nr_frags++; - - return wr_size; -} - -static bool is_ofld_imm(struct chtls_sock *csk, const struct sk_buff *skb) -{ - int length = skb->len; - - if (skb->peeked || skb->len > MAX_IMM_ULPTX_WR_LEN) - return false; - - if (likely(ULP_SKB_CB(skb)->flags & ULPCB_FLAG_NEED_HDR)) { - /* Check TLS header len for Immediate */ - if (csk->ulp_mode == ULP_MODE_TLS && - skb_ulp_tls_inline(skb)) - length += chtls_wr_size(csk, skb, true); - else - length += sizeof(struct fw_ofld_tx_data_wr); - - return length <= MAX_IMM_OFLD_TX_DATA_WR_LEN; - } - return true; -} - -static unsigned int calc_tx_flits(const struct sk_buff *skb, - unsigned int immdlen) -{ - unsigned int flits, cnt; - - flits = immdlen / 8; /* headers */ - cnt = skb_shinfo(skb)->nr_frags; - if (skb_tail_pointer(skb) != skb_transport_header(skb)) - cnt++; - return flits + sgl_len(cnt); -} - -static void arp_failure_discard(void *handle, struct sk_buff *skb) -{ - kfree_skb(skb); -} - -int chtls_push_frames(struct chtls_sock *csk, int comp) -{ - struct chtls_hws *hws = &csk->tlshws; - struct tcp_sock *tp; - struct sk_buff *skb; - int total_size = 0; - struct sock *sk; - int wr_size; - - wr_size = sizeof(struct fw_ofld_tx_data_wr); - sk = csk->sk; - tp = tcp_sk(sk); - - if (unlikely(sk_in_state(sk, TCPF_SYN_SENT | TCPF_CLOSE))) - return 0; - - if (unlikely(csk_flag(sk, CSK_ABORT_SHUTDOWN))) - return 0; - - while (csk->wr_credits && (skb = skb_peek(&csk->txq)) && - (!(ULP_SKB_CB(skb)->flags & ULPCB_FLAG_HOLD) || - skb_queue_len(&csk->txq) > 1)) { - unsigned int credit_len = skb->len; - unsigned int credits_needed; - unsigned int completion = 0; - int tls_len = skb->len;/* TLS data len before IV/key */ - unsigned int immdlen; - int len = skb->len; /* length [ulp bytes] inserted by hw */ - int flowclen16 = 0; - int tls_tx_imm = 0; - - immdlen = skb->len; - if (!is_ofld_imm(csk, skb)) { - immdlen = skb_transport_offset(skb); - if (skb_ulp_tls_inline(skb)) - wr_size = chtls_wr_size(csk, skb, false); - credit_len = 8 * calc_tx_flits(skb, immdlen); - } else { - if (skb_ulp_tls_inline(skb)) { - wr_size = chtls_wr_size(csk, skb, false); - tls_tx_imm = 1; - } - } - if (likely(ULP_SKB_CB(skb)->flags & ULPCB_FLAG_NEED_HDR)) - credit_len += wr_size; - credits_needed = DIV_ROUND_UP(credit_len, 16); - if (!csk_flag_nochk(csk, CSK_TX_DATA_SENT)) { - flowclen16 = send_tx_flowc_wr(sk, 1, tp->snd_nxt, - tp->rcv_nxt); - if (flowclen16 <= 0) - break; - csk->wr_credits -= flowclen16; - csk->wr_unacked += flowclen16; - csk->wr_nondata += flowclen16; - csk_set_flag(csk, CSK_TX_DATA_SENT); - } - - if (csk->wr_credits < credits_needed) { - if (skb_ulp_tls_inline(skb) && - !skb_ulp_tls_iv_imm(skb)) - skb_shinfo(skb)->nr_frags--; - break; - } - - __skb_unlink(skb, &csk->txq); - skb_set_queue_mapping(skb, (csk->txq_idx << 1) | - CPL_PRIORITY_DATA); - if (hws->ofld) - hws->txqid = (skb->queue_mapping >> 1); - skb->csum = (__force __wsum)(credits_needed + csk->wr_nondata); - csk->wr_credits -= credits_needed; - csk->wr_unacked += credits_needed; - csk->wr_nondata = 0; - enqueue_wr(csk, skb); - - if (likely(ULP_SKB_CB(skb)->flags & ULPCB_FLAG_NEED_HDR)) { - if ((comp && csk->wr_unacked == credits_needed) || - (ULP_SKB_CB(skb)->flags & ULPCB_FLAG_COMPL) || - csk->wr_unacked >= csk->wr_max_credits / 2) { - completion = 1; - csk->wr_unacked = 0; - } - if (skb_ulp_tls_inline(skb)) - make_tlstx_data_wr(sk, skb, tls_tx_imm, - tls_len, credits_needed); - else - make_tx_data_wr(sk, skb, immdlen, len, - credits_needed, completion); - tp->snd_nxt += len; - tp->lsndtime = tcp_jiffies32; - if (completion) - ULP_SKB_CB(skb)->flags &= ~ULPCB_FLAG_NEED_HDR; - } else { - struct cpl_close_con_req *req = cplhdr(skb); - unsigned int cmd = CPL_OPCODE_G(ntohl - (OPCODE_TID(req))); - - if (cmd == CPL_CLOSE_CON_REQ) - csk_set_flag(csk, - CSK_CLOSE_CON_REQUESTED); - - if ((ULP_SKB_CB(skb)->flags & ULPCB_FLAG_COMPL) && - (csk->wr_unacked >= csk->wr_max_credits / 2)) { - req->wr.wr_hi |= htonl(FW_WR_COMPL_F); - csk->wr_unacked = 0; - } - } - total_size += skb->truesize; - if (ULP_SKB_CB(skb)->flags & ULPCB_FLAG_BARRIER) - csk_set_flag(csk, CSK_TX_WAIT_IDLE); - t4_set_arp_err_handler(skb, NULL, arp_failure_discard); - cxgb4_l2t_send(csk->egress_dev, skb, csk->l2t_entry); - } - sk->sk_wmem_queued -= total_size; - return total_size; -} - -static void mark_urg(struct tcp_sock *tp, int flags, - struct sk_buff *skb) -{ - if (unlikely(flags & MSG_OOB)) { - tp->snd_up = tp->write_seq; - ULP_SKB_CB(skb)->flags = ULPCB_FLAG_URG | - ULPCB_FLAG_BARRIER | - ULPCB_FLAG_NO_APPEND | - ULPCB_FLAG_NEED_HDR; - } -} - -/* - * Returns true if a connection should send more data to TCP engine - */ -static bool should_push(struct sock *sk) -{ - struct chtls_sock *csk = rcu_dereference_sk_user_data(sk); - struct chtls_dev *cdev = csk->cdev; - struct tcp_sock *tp = tcp_sk(sk); - - /* - * If we've released our offload resources there's nothing to do ... - */ - if (!cdev) - return false; - - /* - * If there aren't any work requests in flight, or there isn't enough - * data in flight, or Nagle is off then send the current TX_DATA - * otherwise hold it and wait to accumulate more data. - */ - return csk->wr_credits == csk->wr_max_credits || - (tp->nonagle & TCP_NAGLE_OFF); -} - -/* - * Returns true if a TCP socket is corked. - */ -static bool corked(const struct tcp_sock *tp, int flags) -{ - return (flags & MSG_MORE) || (tp->nonagle & TCP_NAGLE_CORK); -} - -/* - * Returns true if a send should try to push new data. - */ -static bool send_should_push(struct sock *sk, int flags) -{ - return should_push(sk) && !corked(tcp_sk(sk), flags); -} - -void chtls_tcp_push(struct sock *sk, int flags) -{ - struct chtls_sock *csk = rcu_dereference_sk_user_data(sk); - int qlen = skb_queue_len(&csk->txq); - - if (likely(qlen)) { - struct sk_buff *skb = skb_peek_tail(&csk->txq); - struct tcp_sock *tp = tcp_sk(sk); - - mark_urg(tp, flags, skb); - - if (!(ULP_SKB_CB(skb)->flags & ULPCB_FLAG_NO_APPEND) && - corked(tp, flags)) { - ULP_SKB_CB(skb)->flags |= ULPCB_FLAG_HOLD; - return; - } - - ULP_SKB_CB(skb)->flags &= ~ULPCB_FLAG_HOLD; - if (qlen == 1 && - ((ULP_SKB_CB(skb)->flags & ULPCB_FLAG_NO_APPEND) || - should_push(sk))) - chtls_push_frames(csk, 1); - } -} - -/* - * Calculate the size for a new send sk_buff. It's maximum size so we can - * pack lots of data into it, unless we plan to send it immediately, in which - * case we size it more tightly. - * - * Note: we don't bother compensating for MSS < PAGE_SIZE because it doesn't - * arise in normal cases and when it does we are just wasting memory. - */ -static int select_size(struct sock *sk, int io_len, int flags, int len) -{ - const int pgbreak = SKB_MAX_HEAD(len); - - /* - * If the data wouldn't fit in the main body anyway, put only the - * header in the main body so it can use immediate data and place all - * the payload in page fragments. - */ - if (io_len > pgbreak) - return 0; - - /* - * If we will be accumulating payload get a large main body. - */ - if (!send_should_push(sk, flags)) - return pgbreak; - - return io_len; -} - -void skb_entail(struct sock *sk, struct sk_buff *skb, int flags) -{ - struct chtls_sock *csk = rcu_dereference_sk_user_data(sk); - struct tcp_sock *tp = tcp_sk(sk); - - ULP_SKB_CB(skb)->seq = tp->write_seq; - ULP_SKB_CB(skb)->flags = flags; - __skb_queue_tail(&csk->txq, skb); - sk->sk_wmem_queued += skb->truesize; - - if (TCP_PAGE(sk) && TCP_OFF(sk)) { - put_page(TCP_PAGE(sk)); - TCP_PAGE(sk) = NULL; - TCP_OFF(sk) = 0; - } -} - -static struct sk_buff *get_tx_skb(struct sock *sk, int size) -{ - struct sk_buff *skb; - - skb = alloc_skb(size + TX_HEADER_LEN, sk->sk_allocation); - if (likely(skb)) { - skb_reserve(skb, TX_HEADER_LEN); - skb_entail(sk, skb, ULPCB_FLAG_NEED_HDR); - skb_reset_transport_header(skb); - } - return skb; -} - -static struct sk_buff *get_record_skb(struct sock *sk, int size, bool zcopy) -{ - struct chtls_sock *csk = rcu_dereference_sk_user_data(sk); - struct sk_buff *skb; - - skb = alloc_skb(((zcopy ? 0 : size) + TX_TLSHDR_LEN + - KEY_ON_MEM_SZ + max_ivs_size(sk, size)), - sk->sk_allocation); - if (likely(skb)) { - skb_reserve(skb, (TX_TLSHDR_LEN + - KEY_ON_MEM_SZ + max_ivs_size(sk, size))); - skb_entail(sk, skb, ULPCB_FLAG_NEED_HDR); - skb_reset_transport_header(skb); - ULP_SKB_CB(skb)->ulp.tls.ofld = 1; - ULP_SKB_CB(skb)->ulp.tls.type = csk->tlshws.type; - } - return skb; -} - -static void tx_skb_finalize(struct sk_buff *skb) -{ - struct ulp_skb_cb *cb = ULP_SKB_CB(skb); - - if (!(cb->flags & ULPCB_FLAG_NO_HDR)) - cb->flags = ULPCB_FLAG_NEED_HDR; - cb->flags |= ULPCB_FLAG_NO_APPEND; -} - -static void push_frames_if_head(struct sock *sk) -{ - struct chtls_sock *csk = rcu_dereference_sk_user_data(sk); - - if (skb_queue_len(&csk->txq) == 1) - chtls_push_frames(csk, 1); -} - -static int chtls_skb_copy_to_page_nocache(struct sock *sk, - struct iov_iter *from, - struct sk_buff *skb, - struct page *page, - int off, int copy) -{ - int err; - - err = skb_do_copy_data_nocache(sk, skb, from, page_address(page) + - off, copy, skb->len); - if (err) - return err; - - skb->len += copy; - skb->data_len += copy; - skb->truesize += copy; - sk->sk_wmem_queued += copy; - return 0; -} - -static bool csk_mem_free(struct chtls_dev *cdev, struct sock *sk) -{ - return (cdev->max_host_sndbuf - sk->sk_wmem_queued > 0); -} - -static int csk_wait_memory(struct chtls_dev *cdev, - struct sock *sk, long *timeo_p) -{ - DEFINE_WAIT_FUNC(wait, woken_wake_function); - int ret, err = 0; - long current_timeo; - long vm_wait = 0; - bool noblock; - - current_timeo = *timeo_p; - noblock = (*timeo_p ? false : true); - if (csk_mem_free(cdev, sk)) { - current_timeo = get_random_u32_below(HZ / 5) + 2; - vm_wait = get_random_u32_below(HZ / 5) + 2; - } - - add_wait_queue(sk_sleep(sk), &wait); - while (1) { - sk_set_bit(SOCKWQ_ASYNC_NOSPACE, sk); - - if (sk->sk_err || (sk->sk_shutdown & SEND_SHUTDOWN)) - goto do_error; - if (!*timeo_p) { - if (noblock) - set_bit(SOCK_NOSPACE, &sk->sk_socket->flags); - goto do_nonblock; - } - if (signal_pending(current)) - goto do_interrupted; - sk_clear_bit(SOCKWQ_ASYNC_NOSPACE, sk); - if (csk_mem_free(cdev, sk) && !vm_wait) - break; - - set_bit(SOCK_NOSPACE, &sk->sk_socket->flags); - sk->sk_write_pending++; - ret = sk_wait_event(sk, ¤t_timeo, sk->sk_err || - (sk->sk_shutdown & SEND_SHUTDOWN) || - (csk_mem_free(cdev, sk) && !vm_wait), - &wait); - sk->sk_write_pending--; - if (ret < 0) - goto do_error; - - if (vm_wait) { - vm_wait -= current_timeo; - current_timeo = *timeo_p; - if (current_timeo != MAX_SCHEDULE_TIMEOUT) { - current_timeo -= vm_wait; - if (current_timeo < 0) - current_timeo = 0; - } - vm_wait = 0; - } - *timeo_p = current_timeo; - } -do_rm_wq: - remove_wait_queue(sk_sleep(sk), &wait); - return err; -do_error: - err = -EPIPE; - goto do_rm_wq; -do_nonblock: - err = -EAGAIN; - goto do_rm_wq; -do_interrupted: - err = sock_intr_errno(*timeo_p); - goto do_rm_wq; -} - -static int chtls_proccess_cmsg(struct sock *sk, struct msghdr *msg, - unsigned char *record_type) -{ - struct cmsghdr *cmsg; - int rc = -EINVAL; - - for_each_cmsghdr(cmsg, msg) { - if (!CMSG_OK(msg, cmsg)) - return -EINVAL; - if (cmsg->cmsg_level != SOL_TLS) - continue; - - switch (cmsg->cmsg_type) { - case TLS_SET_RECORD_TYPE: - if (cmsg->cmsg_len < CMSG_LEN(sizeof(*record_type))) - return -EINVAL; - - if (msg->msg_flags & MSG_MORE) - return -EINVAL; - - *record_type = *(unsigned char *)CMSG_DATA(cmsg); - rc = 0; - break; - default: - return -EINVAL; - } - } - - return rc; -} - -int chtls_sendmsg(struct sock *sk, struct msghdr *msg, size_t size) -{ - struct chtls_sock *csk = rcu_dereference_sk_user_data(sk); - struct chtls_dev *cdev = csk->cdev; - struct tcp_sock *tp = tcp_sk(sk); - struct sk_buff *skb; - int mss, flags, err; - int recordsz = 0; - int copied = 0; - long timeo; - - lock_sock(sk); - flags = msg->msg_flags; - timeo = sock_sndtimeo(sk, flags & MSG_DONTWAIT); - - if (!sk_in_state(sk, TCPF_ESTABLISHED | TCPF_CLOSE_WAIT)) { - err = sk_stream_wait_connect(sk, &timeo); - if (err) - goto out_err; - } - - sk_clear_bit(SOCKWQ_ASYNC_NOSPACE, sk); - err = -EPIPE; - if (sk->sk_err || (sk->sk_shutdown & SEND_SHUTDOWN)) - goto out_err; - - mss = csk->mss; - csk_set_flag(csk, CSK_TX_MORE_DATA); - - while (msg_data_left(msg)) { - int copy = 0; - - skb = skb_peek_tail(&csk->txq); - if (skb) { - copy = mss - skb->len; - skb->ip_summed = CHECKSUM_UNNECESSARY; - } - if (!csk_mem_free(cdev, sk)) - goto wait_for_sndbuf; - - if (is_tls_tx(csk) && !csk->tlshws.txleft) { - unsigned char record_type = TLS_RECORD_TYPE_DATA; - - if (unlikely(msg->msg_controllen)) { - err = chtls_proccess_cmsg(sk, msg, - &record_type); - if (err) - goto out_err; - - /* Avoid appending tls handshake, alert to tls data */ - if (skb) - tx_skb_finalize(skb); - } - - recordsz = size; - csk->tlshws.txleft = recordsz; - csk->tlshws.type = record_type; - } - - if (!skb || (ULP_SKB_CB(skb)->flags & ULPCB_FLAG_NO_APPEND) || - copy <= 0) { -new_buf: - if (skb) { - tx_skb_finalize(skb); - push_frames_if_head(sk); - } - - if (is_tls_tx(csk)) { - skb = get_record_skb(sk, - select_size(sk, - recordsz, - flags, - TX_TLSHDR_LEN), - false); - } else { - skb = get_tx_skb(sk, - select_size(sk, size, flags, - TX_HEADER_LEN)); - } - if (unlikely(!skb)) - goto wait_for_memory; - - skb->ip_summed = CHECKSUM_UNNECESSARY; - copy = mss; - } - if (copy > size) - copy = size; - - if (msg->msg_flags & MSG_SPLICE_PAGES) { - err = skb_splice_from_iter(skb, &msg->msg_iter, copy); - if (err < 0) { - if (err == -EMSGSIZE) - goto new_buf; - goto do_fault; - } - copy = err; - sk_wmem_queued_add(sk, copy); - } else if (skb_tailroom(skb) > 0) { - copy = min(copy, skb_tailroom(skb)); - if (is_tls_tx(csk)) - copy = min_t(int, copy, csk->tlshws.txleft); - err = skb_add_data_nocache(sk, skb, - &msg->msg_iter, copy); - if (err) - goto do_fault; - } else { - int i = skb_shinfo(skb)->nr_frags; - struct page *page = TCP_PAGE(sk); - int pg_size = PAGE_SIZE; - int off = TCP_OFF(sk); - bool merge; - - if (page) - pg_size = page_size(page); - if (off < pg_size && - skb_can_coalesce(skb, i, page, off)) { - merge = true; - goto copy; - } - merge = false; - if (i == (is_tls_tx(csk) ? (MAX_SKB_FRAGS - 1) : - MAX_SKB_FRAGS)) - goto new_buf; - - if (page && off == pg_size) { - put_page(page); - TCP_PAGE(sk) = page = NULL; - pg_size = PAGE_SIZE; - } - - if (!page) { - gfp_t gfp = sk->sk_allocation; - int order = cdev->send_page_order; - - if (order) { - page = alloc_pages(gfp | __GFP_COMP | - __GFP_NOWARN | - __GFP_NORETRY, - order); - if (page) - pg_size <<= order; - } - if (!page) { - page = alloc_page(gfp); - pg_size = PAGE_SIZE; - } - if (!page) - goto wait_for_memory; - off = 0; - } -copy: - if (copy > pg_size - off) - copy = pg_size - off; - if (is_tls_tx(csk)) - copy = min_t(int, copy, csk->tlshws.txleft); - - err = chtls_skb_copy_to_page_nocache(sk, &msg->msg_iter, - skb, page, - off, copy); - if (unlikely(err)) { - if (!TCP_PAGE(sk)) { - TCP_PAGE(sk) = page; - TCP_OFF(sk) = 0; - } - goto do_fault; - } - /* Update the skb. */ - if (merge) { - skb_frag_size_add( - &skb_shinfo(skb)->frags[i - 1], - copy); - } else { - skb_fill_page_desc(skb, i, page, off, copy); - if (off + copy < pg_size) { - /* space left keep page */ - get_page(page); - TCP_PAGE(sk) = page; - } else { - TCP_PAGE(sk) = NULL; - } - } - TCP_OFF(sk) = off + copy; - } - if (unlikely(skb->len == mss)) - tx_skb_finalize(skb); - tp->write_seq += copy; - copied += copy; - size -= copy; - - if (is_tls_tx(csk)) - csk->tlshws.txleft -= copy; - - if (corked(tp, flags) && - (sk_stream_wspace(sk) < sk_stream_min_wspace(sk))) - ULP_SKB_CB(skb)->flags |= ULPCB_FLAG_NO_APPEND; - - if (size == 0) - goto out; - - if (ULP_SKB_CB(skb)->flags & ULPCB_FLAG_NO_APPEND) - push_frames_if_head(sk); - continue; -wait_for_sndbuf: - set_bit(SOCK_NOSPACE, &sk->sk_socket->flags); -wait_for_memory: - err = csk_wait_memory(cdev, sk, &timeo); - if (err) - goto do_error; - } -out: - csk_reset_flag(csk, CSK_TX_MORE_DATA); - if (copied) - chtls_tcp_push(sk, flags); -done: - release_sock(sk); - return copied; -do_fault: - if (!skb->len) { - __skb_unlink(skb, &csk->txq); - sk->sk_wmem_queued -= skb->truesize; - __kfree_skb(skb); - } -do_error: - if (copied) - goto out; -out_err: - if (csk_conn_inline(csk)) - csk_reset_flag(csk, CSK_TX_MORE_DATA); - copied = sk_stream_error(sk, flags, err); - goto done; -} - -void chtls_splice_eof(struct socket *sock) -{ - struct sock *sk = sock->sk; - - lock_sock(sk); - chtls_tcp_push(sk, 0); - release_sock(sk); -} - -static void chtls_select_window(struct sock *sk) -{ - struct chtls_sock *csk = rcu_dereference_sk_user_data(sk); - struct tcp_sock *tp = tcp_sk(sk); - unsigned int wnd = tp->rcv_wnd; - - wnd = max_t(unsigned int, wnd, tcp_full_space(sk)); - wnd = max_t(unsigned int, MIN_RCV_WND, wnd); - - if (wnd > MAX_RCV_WND) - wnd = MAX_RCV_WND; - -/* - * Check if we need to grow the receive window in response to an increase in - * the socket's receive buffer size. Some applications increase the buffer - * size dynamically and rely on the window to grow accordingly. - */ - - if (wnd > tp->rcv_wnd) { - tp->rcv_wup -= wnd - tp->rcv_wnd; - tp->rcv_wnd = wnd; - /* Mark the receive window as updated */ - csk_reset_flag(csk, CSK_UPDATE_RCV_WND); - } -} - -/* - * Send RX credits through an RX_DATA_ACK CPL message. We are permitted - * to return without sending the message in case we cannot allocate - * an sk_buff. Returns the number of credits sent. - */ -static u32 send_rx_credits(struct chtls_sock *csk, u32 credits) -{ - struct cpl_rx_data_ack *req; - struct sk_buff *skb; - - skb = alloc_skb(sizeof(*req), GFP_ATOMIC); - if (!skb) - return 0; - __skb_put(skb, sizeof(*req)); - req = (struct cpl_rx_data_ack *)skb->head; - - set_wr_txq(skb, CPL_PRIORITY_ACK, csk->port_id); - INIT_TP_WR(req, csk->tid); - OPCODE_TID(req) = cpu_to_be32(MK_OPCODE_TID(CPL_RX_DATA_ACK, - csk->tid)); - req->credit_dack = cpu_to_be32(RX_CREDITS_V(credits) | - RX_FORCE_ACK_F); - cxgb4_ofld_send(csk->cdev->ports[csk->port_id], skb); - return credits; -} - -#define CREDIT_RETURN_STATE (TCPF_ESTABLISHED | \ - TCPF_FIN_WAIT1 | \ - TCPF_FIN_WAIT2) - -/* - * Called after some received data has been read. It returns RX credits - * to the HW for the amount of data processed. - */ -static void chtls_cleanup_rbuf(struct sock *sk, int copied) -{ - struct chtls_sock *csk = rcu_dereference_sk_user_data(sk); - struct tcp_sock *tp; - int must_send; - u32 credits; - u32 thres; - - thres = 15 * 1024; - - if (!sk_in_state(sk, CREDIT_RETURN_STATE)) - return; - - chtls_select_window(sk); - tp = tcp_sk(sk); - credits = tp->copied_seq - tp->rcv_wup; - if (unlikely(!credits)) - return; - -/* - * For coalescing to work effectively ensure the receive window has - * at least 16KB left. - */ - must_send = credits + 16384 >= tp->rcv_wnd; - - if (must_send || credits >= thres) - tp->rcv_wup += send_rx_credits(csk, credits); -} - -static int chtls_pt_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, - int flags) -{ - struct chtls_sock *csk = rcu_dereference_sk_user_data(sk); - struct chtls_hws *hws = &csk->tlshws; - struct net_device *dev = csk->egress_dev; - struct adapter *adap = netdev2adap(dev); - struct tcp_sock *tp = tcp_sk(sk); - unsigned long avail; - int buffers_freed; - int copied = 0; - int target; - long timeo; - int ret; - - buffers_freed = 0; - - timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT); - target = sock_rcvlowat(sk, flags & MSG_WAITALL, len); - - if (unlikely(csk_flag(sk, CSK_UPDATE_RCV_WND))) - chtls_cleanup_rbuf(sk, copied); - - do { - struct sk_buff *skb; - u32 offset = 0; - - if (unlikely(tp->urg_data && - tp->urg_seq == tp->copied_seq)) { - if (copied) - break; - if (signal_pending(current)) { - copied = timeo ? sock_intr_errno(timeo) : - -EAGAIN; - break; - } - } - skb = skb_peek(&sk->sk_receive_queue); - if (skb) - goto found_ok_skb; - if (csk->wr_credits && - skb_queue_len(&csk->txq) && - chtls_push_frames(csk, csk->wr_credits == - csk->wr_max_credits)) - sk->sk_write_space(sk); - - if (copied >= target && !READ_ONCE(sk->sk_backlog.tail)) - break; - - if (copied) { - if (sk->sk_err || sk->sk_state == TCP_CLOSE || - (sk->sk_shutdown & RCV_SHUTDOWN) || - signal_pending(current)) - break; - - if (!timeo) - break; - } else { - if (sock_flag(sk, SOCK_DONE)) - break; - if (sk->sk_err) { - copied = sock_error(sk); - break; - } - if (sk->sk_shutdown & RCV_SHUTDOWN) - break; - if (sk->sk_state == TCP_CLOSE) { - copied = -ENOTCONN; - break; - } - if (!timeo) { - copied = -EAGAIN; - break; - } - if (signal_pending(current)) { - copied = sock_intr_errno(timeo); - break; - } - } - if (READ_ONCE(sk->sk_backlog.tail)) { - release_sock(sk); - lock_sock(sk); - chtls_cleanup_rbuf(sk, copied); - continue; - } - - if (copied >= target) - break; - chtls_cleanup_rbuf(sk, copied); - ret = sk_wait_data(sk, &timeo, NULL); - if (ret < 0) { - copied = copied ? : ret; - goto unlock; - } - continue; -found_ok_skb: - if (!skb->len) { - skb_dstref_steal(skb); - __skb_unlink(skb, &sk->sk_receive_queue); - kfree_skb(skb); - - if (!copied && !timeo) { - copied = -EAGAIN; - break; - } - - if (copied < target) { - release_sock(sk); - lock_sock(sk); - continue; - } - break; - } - offset = hws->copied_seq; - avail = skb->len - offset; - if (len < avail) - avail = len; - - if (unlikely(tp->urg_data)) { - u32 urg_offset = tp->urg_seq - tp->copied_seq; - - if (urg_offset < avail) { - if (urg_offset) { - avail = urg_offset; - } else if (!sock_flag(sk, SOCK_URGINLINE)) { - /* First byte is urgent, skip */ - tp->copied_seq++; - offset++; - avail--; - if (!avail) - goto skip_copy; - } - } - } - /* Set record type if not already done. For a non-data record, - * do not proceed if record type could not be copied. - */ - if (ULP_SKB_CB(skb)->flags & ULPCB_FLAG_TLS_HDR) { - struct tls_hdr *thdr = (struct tls_hdr *)skb->data; - int cerr = 0; - - cerr = put_cmsg(msg, SOL_TLS, TLS_GET_RECORD_TYPE, - sizeof(thdr->type), &thdr->type); - - if (cerr && thdr->type != TLS_RECORD_TYPE_DATA) { - copied = -EIO; - break; - } - /* don't send tls header, skip copy */ - goto skip_copy; - } - - if (skb_copy_datagram_msg(skb, offset, msg, avail)) { - if (!copied) { - copied = -EFAULT; - break; - } - } - - copied += avail; - len -= avail; - hws->copied_seq += avail; -skip_copy: - if (tp->urg_data && after(tp->copied_seq, tp->urg_seq)) - tp->urg_data = 0; - - if ((avail + offset) >= skb->len) { - struct sk_buff *next_skb; - if (ULP_SKB_CB(skb)->flags & ULPCB_FLAG_TLS_HDR) { - tp->copied_seq += skb->len; - hws->rcvpld = skb->hdr_len; - } else { - atomic_inc(&adap->chcr_stats.tls_pdu_rx); - tp->copied_seq += hws->rcvpld; - } - chtls_free_skb(sk, skb); - buffers_freed++; - hws->copied_seq = 0; - next_skb = skb_peek(&sk->sk_receive_queue); - if (copied >= target && !next_skb) - break; - if (ULP_SKB_CB(next_skb)->flags & ULPCB_FLAG_TLS_HDR) - break; - } - } while (len > 0); - - if (buffers_freed) - chtls_cleanup_rbuf(sk, copied); - -unlock: - release_sock(sk); - return copied; -} - -/* - * Peek at data in a socket's receive buffer. - */ -static int peekmsg(struct sock *sk, struct msghdr *msg, - size_t len, int flags) -{ - struct tcp_sock *tp = tcp_sk(sk); - u32 peek_seq, offset; - struct sk_buff *skb; - int copied = 0; - size_t avail; /* amount of available data in current skb */ - long timeo; - int ret; - - lock_sock(sk); - timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT); - peek_seq = tp->copied_seq; - - do { - if (unlikely(tp->urg_data && tp->urg_seq == peek_seq)) { - if (copied) - break; - if (signal_pending(current)) { - copied = timeo ? sock_intr_errno(timeo) : - -EAGAIN; - break; - } - } - - skb_queue_walk(&sk->sk_receive_queue, skb) { - offset = peek_seq - ULP_SKB_CB(skb)->seq; - if (offset < skb->len) - goto found_ok_skb; - } - - /* empty receive queue */ - if (copied) - break; - if (sock_flag(sk, SOCK_DONE)) - break; - if (sk->sk_err) { - copied = sock_error(sk); - break; - } - if (sk->sk_shutdown & RCV_SHUTDOWN) - break; - if (sk->sk_state == TCP_CLOSE) { - copied = -ENOTCONN; - break; - } - if (!timeo) { - copied = -EAGAIN; - break; - } - if (signal_pending(current)) { - copied = sock_intr_errno(timeo); - break; - } - - if (READ_ONCE(sk->sk_backlog.tail)) { - /* Do not sleep, just process backlog. */ - release_sock(sk); - lock_sock(sk); - } else { - ret = sk_wait_data(sk, &timeo, NULL); - if (ret < 0) { - /* here 'copied' is 0 due to previous checks */ - copied = ret; - break; - } - } - - if (unlikely(peek_seq != tp->copied_seq)) { - if (net_ratelimit()) - pr_info("TCP(%s:%d), race in MSG_PEEK.\n", - current->comm, current->pid); - peek_seq = tp->copied_seq; - } - continue; - -found_ok_skb: - avail = skb->len - offset; - if (len < avail) - avail = len; - /* - * Do we have urgent data here? We need to skip over the - * urgent byte. - */ - if (unlikely(tp->urg_data)) { - u32 urg_offset = tp->urg_seq - peek_seq; - - if (urg_offset < avail) { - /* - * The amount of data we are preparing to copy - * contains urgent data. - */ - if (!urg_offset) { /* First byte is urgent */ - if (!sock_flag(sk, SOCK_URGINLINE)) { - peek_seq++; - offset++; - avail--; - } - if (!avail) - continue; - } else { - /* stop short of the urgent data */ - avail = urg_offset; - } - } - } - - /* - * If MSG_TRUNC is specified the data is discarded. - */ - if (likely(!(flags & MSG_TRUNC))) - if (skb_copy_datagram_msg(skb, offset, msg, len)) { - if (!copied) { - copied = -EFAULT; - break; - } - } - peek_seq += avail; - copied += avail; - len -= avail; - } while (len > 0); - - release_sock(sk); - return copied; -} - -int chtls_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, - int flags) -{ - struct tcp_sock *tp = tcp_sk(sk); - struct chtls_sock *csk; - unsigned long avail; /* amount of available data in current skb */ - int buffers_freed; - int copied = 0; - long timeo; - int target; /* Read at least this many bytes */ - int ret; - - buffers_freed = 0; - - if (unlikely(flags & MSG_OOB)) - return tcp_prot.recvmsg(sk, msg, len, flags); - - if (unlikely(flags & MSG_PEEK)) - return peekmsg(sk, msg, len, flags); - - if (sk_can_busy_loop(sk) && - skb_queue_empty_lockless(&sk->sk_receive_queue) && - sk->sk_state == TCP_ESTABLISHED) - sk_busy_loop(sk, flags & MSG_DONTWAIT); - - lock_sock(sk); - csk = rcu_dereference_sk_user_data(sk); - - if (is_tls_rx(csk)) - return chtls_pt_recvmsg(sk, msg, len, flags); - - timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT); - target = sock_rcvlowat(sk, flags & MSG_WAITALL, len); - - if (unlikely(csk_flag(sk, CSK_UPDATE_RCV_WND))) - chtls_cleanup_rbuf(sk, copied); - - do { - struct sk_buff *skb; - u32 offset; - - if (unlikely(tp->urg_data && tp->urg_seq == tp->copied_seq)) { - if (copied) - break; - if (signal_pending(current)) { - copied = timeo ? sock_intr_errno(timeo) : - -EAGAIN; - break; - } - } - - skb = skb_peek(&sk->sk_receive_queue); - if (skb) - goto found_ok_skb; - - if (csk->wr_credits && - skb_queue_len(&csk->txq) && - chtls_push_frames(csk, csk->wr_credits == - csk->wr_max_credits)) - sk->sk_write_space(sk); - - if (copied >= target && !READ_ONCE(sk->sk_backlog.tail)) - break; - - if (copied) { - if (sk->sk_err || sk->sk_state == TCP_CLOSE || - (sk->sk_shutdown & RCV_SHUTDOWN) || - signal_pending(current)) - break; - } else { - if (sock_flag(sk, SOCK_DONE)) - break; - if (sk->sk_err) { - copied = sock_error(sk); - break; - } - if (sk->sk_shutdown & RCV_SHUTDOWN) - break; - if (sk->sk_state == TCP_CLOSE) { - copied = -ENOTCONN; - break; - } - if (!timeo) { - copied = -EAGAIN; - break; - } - if (signal_pending(current)) { - copied = sock_intr_errno(timeo); - break; - } - } - - if (READ_ONCE(sk->sk_backlog.tail)) { - release_sock(sk); - lock_sock(sk); - chtls_cleanup_rbuf(sk, copied); - continue; - } - - if (copied >= target) - break; - chtls_cleanup_rbuf(sk, copied); - ret = sk_wait_data(sk, &timeo, NULL); - if (ret < 0) { - copied = copied ? : ret; - goto unlock; - } - continue; - -found_ok_skb: - if (!skb->len) { - chtls_kfree_skb(sk, skb); - if (!copied && !timeo) { - copied = -EAGAIN; - break; - } - - if (copied < target) - continue; - - break; - } - - offset = tp->copied_seq - ULP_SKB_CB(skb)->seq; - avail = skb->len - offset; - if (len < avail) - avail = len; - - if (unlikely(tp->urg_data)) { - u32 urg_offset = tp->urg_seq - tp->copied_seq; - - if (urg_offset < avail) { - if (urg_offset) { - avail = urg_offset; - } else if (!sock_flag(sk, SOCK_URGINLINE)) { - tp->copied_seq++; - offset++; - avail--; - if (!avail) - goto skip_copy; - } - } - } - - if (likely(!(flags & MSG_TRUNC))) { - if (skb_copy_datagram_msg(skb, offset, - msg, avail)) { - if (!copied) { - copied = -EFAULT; - break; - } - } - } - - tp->copied_seq += avail; - copied += avail; - len -= avail; - -skip_copy: - if (tp->urg_data && after(tp->copied_seq, tp->urg_seq)) - tp->urg_data = 0; - - if (avail + offset >= skb->len) { - chtls_free_skb(sk, skb); - buffers_freed++; - - if (copied >= target && - !skb_peek(&sk->sk_receive_queue)) - break; - } - } while (len > 0); - - if (buffers_freed) - chtls_cleanup_rbuf(sk, copied); - -unlock: - release_sock(sk); - return copied; -} diff --git a/drivers/net/ethernet/chelsio/inline_crypto/chtls/chtls_main.c b/drivers/net/ethernet/chelsio/inline_crypto/chtls/chtls_main.c deleted file mode 100644 index 2570575434f9..000000000000 --- a/drivers/net/ethernet/chelsio/inline_crypto/chtls/chtls_main.c +++ /dev/null @@ -1,642 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Copyright (c) 2018 Chelsio Communications, Inc. - * - * Written by: Atul Gupta (atul.gupta@chelsio.com) - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "chtls.h" -#include "chtls_cm.h" - -#define DRV_NAME "chtls" - -/* - * chtls device management - * maintains a list of the chtls devices - */ -static LIST_HEAD(cdev_list); -static DEFINE_MUTEX(cdev_mutex); - -static DEFINE_MUTEX(notify_mutex); -static RAW_NOTIFIER_HEAD(listen_notify_list); -static struct proto chtls_cpl_prot, chtls_cpl_protv6; -struct request_sock_ops chtls_rsk_ops, chtls_rsk_opsv6; -static uint send_page_order = (14 - PAGE_SHIFT < 0) ? 0 : 14 - PAGE_SHIFT; - -static void register_listen_notifier(struct notifier_block *nb) -{ - mutex_lock(¬ify_mutex); - raw_notifier_chain_register(&listen_notify_list, nb); - mutex_unlock(¬ify_mutex); -} - -static void unregister_listen_notifier(struct notifier_block *nb) -{ - mutex_lock(¬ify_mutex); - raw_notifier_chain_unregister(&listen_notify_list, nb); - mutex_unlock(¬ify_mutex); -} - -static int listen_notify_handler(struct notifier_block *this, - unsigned long event, void *data) -{ - struct chtls_listen *clisten; - int ret = NOTIFY_DONE; - - clisten = (struct chtls_listen *)data; - - switch (event) { - case CHTLS_LISTEN_START: - ret = chtls_listen_start(clisten->cdev, clisten->sk); - kfree(clisten); - break; - case CHTLS_LISTEN_STOP: - chtls_listen_stop(clisten->cdev, clisten->sk); - kfree(clisten); - break; - } - return ret; -} - -static struct notifier_block listen_notifier = { - .notifier_call = listen_notify_handler -}; - -static int listen_backlog_rcv(struct sock *sk, struct sk_buff *skb) -{ - if (likely(skb_transport_header(skb) != skb_network_header(skb))) - return tcp_v4_do_rcv(sk, skb); - BLOG_SKB_CB(skb)->backlog_rcv(sk, skb); - return 0; -} - -static int chtls_start_listen(struct chtls_dev *cdev, struct sock *sk) -{ - struct chtls_listen *clisten; - - if (sk->sk_protocol != IPPROTO_TCP) - return -EPROTONOSUPPORT; - - if (sk->sk_family == PF_INET && - LOOPBACK(inet_sk(sk)->inet_rcv_saddr)) - return -EADDRNOTAVAIL; - - sk->sk_backlog_rcv = listen_backlog_rcv; - clisten = kmalloc_obj(*clisten); - if (!clisten) - return -ENOMEM; - clisten->cdev = cdev; - clisten->sk = sk; - mutex_lock(¬ify_mutex); - raw_notifier_call_chain(&listen_notify_list, - CHTLS_LISTEN_START, clisten); - mutex_unlock(¬ify_mutex); - return 0; -} - -static void chtls_stop_listen(struct chtls_dev *cdev, struct sock *sk) -{ - struct chtls_listen *clisten; - - if (sk->sk_protocol != IPPROTO_TCP) - return; - - clisten = kmalloc_obj(*clisten); - if (!clisten) - return; - clisten->cdev = cdev; - clisten->sk = sk; - mutex_lock(¬ify_mutex); - raw_notifier_call_chain(&listen_notify_list, - CHTLS_LISTEN_STOP, clisten); - mutex_unlock(¬ify_mutex); -} - -static int chtls_inline_feature(struct tls_toe_device *dev) -{ - struct net_device *netdev; - struct chtls_dev *cdev; - int i; - - cdev = to_chtls_dev(dev); - - for (i = 0; i < cdev->lldi->nports; i++) { - netdev = cdev->ports[i]; - if (netdev->features & NETIF_F_HW_TLS_RECORD) - return 1; - } - return 0; -} - -static int chtls_create_hash(struct tls_toe_device *dev, struct sock *sk) -{ - struct chtls_dev *cdev = to_chtls_dev(dev); - - if (sk->sk_state == TCP_LISTEN) - return chtls_start_listen(cdev, sk); - return 0; -} - -static void chtls_destroy_hash(struct tls_toe_device *dev, struct sock *sk) -{ - struct chtls_dev *cdev = to_chtls_dev(dev); - - if (sk->sk_state == TCP_LISTEN) - chtls_stop_listen(cdev, sk); -} - -static void chtls_free_uld(struct chtls_dev *cdev) -{ - int i; - - tls_toe_unregister_device(&cdev->tlsdev); - kvfree(cdev->kmap.addr); - idr_destroy(&cdev->hwtid_idr); - for (i = 0; i < (1 << RSPQ_HASH_BITS); i++) - kfree_skb(cdev->rspq_skb_cache[i]); - kfree(cdev->lldi); - kfree_skb(cdev->askb); - kfree(cdev); -} - -static inline void chtls_dev_release(struct kref *kref) -{ - struct tls_toe_device *dev; - struct chtls_dev *cdev; - struct adapter *adap; - - dev = container_of(kref, struct tls_toe_device, kref); - cdev = to_chtls_dev(dev); - - /* Reset tls rx/tx stats */ - adap = pci_get_drvdata(cdev->pdev); - atomic_set(&adap->chcr_stats.tls_pdu_tx, 0); - atomic_set(&adap->chcr_stats.tls_pdu_rx, 0); - - chtls_free_uld(cdev); -} - -static void chtls_register_dev(struct chtls_dev *cdev) -{ - struct tls_toe_device *tlsdev = &cdev->tlsdev; - - strscpy(tlsdev->name, "chtls", TLS_TOE_DEVICE_NAME_MAX); - strlcat(tlsdev->name, cdev->lldi->ports[0]->name, - TLS_TOE_DEVICE_NAME_MAX); - tlsdev->feature = chtls_inline_feature; - tlsdev->hash = chtls_create_hash; - tlsdev->unhash = chtls_destroy_hash; - tlsdev->release = chtls_dev_release; - kref_init(&tlsdev->kref); - tls_toe_register_device(tlsdev); - cdev->cdev_state = CHTLS_CDEV_STATE_UP; -} - -static void process_deferq(struct work_struct *task_param) -{ - struct chtls_dev *cdev = container_of(task_param, - struct chtls_dev, deferq_task); - struct sk_buff *skb; - - spin_lock_bh(&cdev->deferq.lock); - while ((skb = __skb_dequeue(&cdev->deferq)) != NULL) { - spin_unlock_bh(&cdev->deferq.lock); - DEFERRED_SKB_CB(skb)->handler(cdev, skb); - spin_lock_bh(&cdev->deferq.lock); - } - spin_unlock_bh(&cdev->deferq.lock); -} - -static int chtls_get_skb(struct chtls_dev *cdev) -{ - cdev->askb = alloc_skb(sizeof(struct tcphdr), GFP_KERNEL); - if (!cdev->askb) - return -ENOMEM; - - skb_put(cdev->askb, sizeof(struct tcphdr)); - skb_reset_transport_header(cdev->askb); - memset(cdev->askb->data, 0, cdev->askb->len); - return 0; -} - -static void *chtls_uld_add(const struct cxgb4_lld_info *info) -{ - struct cxgb4_lld_info *lldi; - struct chtls_dev *cdev; - int i, j; - - cdev = kzalloc_obj(*cdev); - if (!cdev) - goto out; - - lldi = kzalloc_obj(*lldi); - if (!lldi) - goto out_lldi; - - if (chtls_get_skb(cdev)) - goto out_skb; - - *lldi = *info; - cdev->lldi = lldi; - cdev->pdev = lldi->pdev; - cdev->tids = lldi->tids; - cdev->ports = lldi->ports; - cdev->mtus = lldi->mtus; - cdev->tids = lldi->tids; - cdev->pfvf = FW_VIID_PFN_G(cxgb4_port_viid(lldi->ports[0])) - << FW_VIID_PFN_S; - - for (i = 0; i < (1 << RSPQ_HASH_BITS); i++) { - unsigned int size = 64 - sizeof(struct rsp_ctrl) - 8; - - cdev->rspq_skb_cache[i] = __alloc_skb(size, - gfp_any(), 0, - lldi->nodeid); - if (unlikely(!cdev->rspq_skb_cache[i])) - goto out_rspq_skb; - } - - idr_init(&cdev->hwtid_idr); - INIT_WORK(&cdev->deferq_task, process_deferq); - spin_lock_init(&cdev->listen_lock); - spin_lock_init(&cdev->idr_lock); - cdev->send_page_order = min_t(uint, get_order(32768), - send_page_order); - cdev->max_host_sndbuf = 48 * 1024; - - if (lldi->vr->key.size) - if (chtls_init_kmap(cdev, lldi)) - goto out_rspq_skb; - - mutex_lock(&cdev_mutex); - list_add_tail(&cdev->list, &cdev_list); - mutex_unlock(&cdev_mutex); - - return cdev; -out_rspq_skb: - for (j = 0; j < i; j++) - kfree_skb(cdev->rspq_skb_cache[j]); - kfree_skb(cdev->askb); -out_skb: - kfree(lldi); -out_lldi: - kfree(cdev); -out: - return NULL; -} - -static void chtls_free_all_uld(void) -{ - struct chtls_dev *cdev, *tmp; - - mutex_lock(&cdev_mutex); - list_for_each_entry_safe(cdev, tmp, &cdev_list, list) { - if (cdev->cdev_state == CHTLS_CDEV_STATE_UP) { - list_del(&cdev->list); - kref_put(&cdev->tlsdev.kref, cdev->tlsdev.release); - } - } - mutex_unlock(&cdev_mutex); -} - -static int chtls_uld_state_change(void *handle, enum cxgb4_state new_state) -{ - struct chtls_dev *cdev = handle; - - switch (new_state) { - case CXGB4_STATE_UP: - chtls_register_dev(cdev); - break; - case CXGB4_STATE_DOWN: - break; - case CXGB4_STATE_START_RECOVERY: - break; - case CXGB4_STATE_DETACH: - mutex_lock(&cdev_mutex); - list_del(&cdev->list); - mutex_unlock(&cdev_mutex); - kref_put(&cdev->tlsdev.kref, cdev->tlsdev.release); - break; - default: - break; - } - return 0; -} - -static struct sk_buff *copy_gl_to_skb_pkt(const struct pkt_gl *gl, - const __be64 *rsp, - u32 pktshift) -{ - struct sk_buff *skb; - - /* Allocate space for cpl_pass_accept_req which will be synthesized by - * driver. Once driver synthesizes cpl_pass_accept_req the skb will go - * through the regular cpl_pass_accept_req processing in TOM. - */ - skb = alloc_skb(size_add(gl->tot_len, - sizeof(struct cpl_pass_accept_req)) - - pktshift, GFP_ATOMIC); - if (unlikely(!skb)) - return NULL; - __skb_put(skb, gl->tot_len + sizeof(struct cpl_pass_accept_req) - - pktshift); - /* For now we will copy cpl_rx_pkt in the skb */ - skb_copy_to_linear_data(skb, rsp, sizeof(struct cpl_rx_pkt)); - skb_copy_to_linear_data_offset(skb, sizeof(struct cpl_pass_accept_req) - , gl->va + pktshift, - gl->tot_len - pktshift); - - return skb; -} - -static int chtls_recv_packet(struct chtls_dev *cdev, - const struct pkt_gl *gl, const __be64 *rsp) -{ - unsigned int opcode = *(u8 *)rsp; - struct sk_buff *skb; - int ret; - - skb = copy_gl_to_skb_pkt(gl, rsp, cdev->lldi->sge_pktshift); - if (!skb) - return -ENOMEM; - - ret = chtls_handlers[opcode](cdev, skb); - if (ret & CPL_RET_BUF_DONE) - kfree_skb(skb); - - return 0; -} - -static int chtls_recv_rsp(struct chtls_dev *cdev, const __be64 *rsp) -{ - unsigned long rspq_bin; - unsigned int opcode; - struct sk_buff *skb; - unsigned int len; - int ret; - - len = 64 - sizeof(struct rsp_ctrl) - 8; - opcode = *(u8 *)rsp; - - rspq_bin = hash_ptr((void *)rsp, RSPQ_HASH_BITS); - skb = cdev->rspq_skb_cache[rspq_bin]; - if (skb && !skb_is_nonlinear(skb) && - !skb_shared(skb) && !skb_cloned(skb)) { - refcount_inc(&skb->users); - if (refcount_read(&skb->users) == 2) { - __skb_trim(skb, 0); - if (skb_tailroom(skb) >= len) - goto copy_out; - } - refcount_dec(&skb->users); - } - skb = alloc_skb(len, GFP_ATOMIC); - if (unlikely(!skb)) - return -ENOMEM; - -copy_out: - __skb_put(skb, len); - skb_copy_to_linear_data(skb, rsp, len); - skb_reset_network_header(skb); - skb_reset_transport_header(skb); - ret = chtls_handlers[opcode](cdev, skb); - - if (ret & CPL_RET_BUF_DONE) - kfree_skb(skb); - return 0; -} - -static void chtls_recv(struct chtls_dev *cdev, - struct sk_buff **skbs, const __be64 *rsp) -{ - struct sk_buff *skb = *skbs; - unsigned int opcode; - int ret; - - opcode = *(u8 *)rsp; - - __skb_push(skb, sizeof(struct rss_header)); - skb_copy_to_linear_data(skb, rsp, sizeof(struct rss_header)); - - ret = chtls_handlers[opcode](cdev, skb); - if (ret & CPL_RET_BUF_DONE) - kfree_skb(skb); -} - -static int chtls_uld_rx_handler(void *handle, const __be64 *rsp, - const struct pkt_gl *gl) -{ - struct chtls_dev *cdev = handle; - unsigned int opcode; - struct sk_buff *skb; - - opcode = *(u8 *)rsp; - - if (unlikely(opcode == CPL_RX_PKT)) { - if (chtls_recv_packet(cdev, gl, rsp) < 0) - goto nomem; - return 0; - } - - if (!gl) - return chtls_recv_rsp(cdev, rsp); - -#define RX_PULL_LEN 128 - skb = cxgb4_pktgl_to_skb(gl, RX_PULL_LEN, RX_PULL_LEN); - if (unlikely(!skb)) - goto nomem; - chtls_recv(cdev, &skb, rsp); - return 0; - -nomem: - return -ENOMEM; -} - -static int do_chtls_getsockopt(struct sock *sk, char __user *optval, - int __user *optlen) -{ - struct tls_crypto_info crypto_info = { 0 }; - - crypto_info.version = TLS_1_2_VERSION; - if (copy_to_user(optval, &crypto_info, sizeof(struct tls_crypto_info))) - return -EFAULT; - return 0; -} - -static int chtls_getsockopt(struct sock *sk, int level, int optname, - char __user *optval, int __user *optlen) -{ - struct tls_context *ctx = tls_get_ctx(sk); - - if (level != SOL_TLS) - return ctx->sk_proto->getsockopt(sk, level, - optname, optval, optlen); - - return do_chtls_getsockopt(sk, optval, optlen); -} - -static int do_chtls_setsockopt(struct sock *sk, int optname, - sockptr_t optval, unsigned int optlen) -{ - struct tls_crypto_info *crypto_info, tmp_crypto_info; - struct chtls_sock *csk; - int keylen; - int cipher_type; - int rc = 0; - - csk = rcu_dereference_sk_user_data(sk); - - if (sockptr_is_null(optval) || optlen < sizeof(*crypto_info)) { - rc = -EINVAL; - goto out; - } - - rc = copy_from_sockptr(&tmp_crypto_info, optval, sizeof(*crypto_info)); - if (rc) { - rc = -EFAULT; - goto out; - } - - /* check version */ - if (tmp_crypto_info.version != TLS_1_2_VERSION) { - rc = -ENOTSUPP; - goto out; - } - - crypto_info = (struct tls_crypto_info *)&csk->tlshws.crypto_info; - - /* GCM mode of AES supports 128 and 256 bit encryption, so - * copy keys from user based on GCM cipher type. - */ - switch (tmp_crypto_info.cipher_type) { - case TLS_CIPHER_AES_GCM_128: { - /* Obtain version and type from previous copy */ - crypto_info[0] = tmp_crypto_info; - /* Now copy the following data */ - rc = copy_from_sockptr_offset((char *)crypto_info + - sizeof(*crypto_info), - optval, sizeof(*crypto_info), - sizeof(struct tls12_crypto_info_aes_gcm_128) - - sizeof(*crypto_info)); - - if (rc) { - rc = -EFAULT; - goto out; - } - - keylen = TLS_CIPHER_AES_GCM_128_KEY_SIZE; - cipher_type = TLS_CIPHER_AES_GCM_128; - break; - } - case TLS_CIPHER_AES_GCM_256: { - crypto_info[0] = tmp_crypto_info; - rc = copy_from_sockptr_offset((char *)crypto_info + - sizeof(*crypto_info), - optval, sizeof(*crypto_info), - sizeof(struct tls12_crypto_info_aes_gcm_256) - - sizeof(*crypto_info)); - - if (rc) { - rc = -EFAULT; - goto out; - } - - keylen = TLS_CIPHER_AES_GCM_256_KEY_SIZE; - cipher_type = TLS_CIPHER_AES_GCM_256; - break; - } - default: - rc = -EINVAL; - goto out; - } - rc = chtls_setkey(csk, keylen, optname, cipher_type); -out: - return rc; -} - -static int chtls_setsockopt(struct sock *sk, int level, int optname, - sockptr_t optval, unsigned int optlen) -{ - struct tls_context *ctx = tls_get_ctx(sk); - - if (level != SOL_TLS) - return ctx->sk_proto->setsockopt(sk, level, - optname, optval, optlen); - - return do_chtls_setsockopt(sk, optname, optval, optlen); -} - -static struct cxgb4_uld_info chtls_uld_info = { - .name = DRV_NAME, - .nrxq = MAX_ULD_QSETS, - .ntxq = MAX_ULD_QSETS, - .rxq_size = 1024, - .add = chtls_uld_add, - .state_change = chtls_uld_state_change, - .rx_handler = chtls_uld_rx_handler, -}; - -void chtls_install_cpl_ops(struct sock *sk) -{ - if (sk->sk_family == AF_INET) - sk->sk_prot = &chtls_cpl_prot; - else - sk->sk_prot = &chtls_cpl_protv6; -} - -static void __init chtls_init_ulp_ops(void) -{ - chtls_cpl_prot = tcp_prot; - chtls_init_rsk_ops(&chtls_cpl_prot, &chtls_rsk_ops, - &tcp_prot, PF_INET); - chtls_cpl_prot.close = chtls_close; - chtls_cpl_prot.disconnect = chtls_disconnect; - chtls_cpl_prot.destroy = chtls_destroy_sock; - chtls_cpl_prot.shutdown = chtls_shutdown; - chtls_cpl_prot.sendmsg = chtls_sendmsg; - chtls_cpl_prot.splice_eof = chtls_splice_eof; - chtls_cpl_prot.recvmsg = chtls_recvmsg; - chtls_cpl_prot.setsockopt = chtls_setsockopt; - chtls_cpl_prot.getsockopt = chtls_getsockopt; -#if IS_ENABLED(CONFIG_IPV6) - chtls_cpl_protv6 = chtls_cpl_prot; - chtls_init_rsk_ops(&chtls_cpl_protv6, &chtls_rsk_opsv6, - &tcpv6_prot, PF_INET6); -#endif -} - -static int __init chtls_register(void) -{ - chtls_init_ulp_ops(); - register_listen_notifier(&listen_notifier); - cxgb4_register_uld(CXGB4_ULD_TLS, &chtls_uld_info); - return 0; -} - -static void __exit chtls_unregister(void) -{ - unregister_listen_notifier(&listen_notifier); - chtls_free_all_uld(); - cxgb4_unregister_uld(CXGB4_ULD_TLS); -} - -module_init(chtls_register); -module_exit(chtls_unregister); - -MODULE_DESCRIPTION("Chelsio TLS Inline driver"); -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Chelsio Communications"); -MODULE_VERSION(CHTLS_DRV_VERSION); diff --git a/include/linux/netdev_features.h b/include/linux/netdev_features.h index 93e4da7046a1..8eb6b8033606 100644 --- a/include/linux/netdev_features.h +++ b/include/linux/netdev_features.h @@ -79,7 +79,7 @@ enum { NETIF_F_HW_TLS_RX_BIT, /* Hardware TLS RX offload */ NETIF_F_GRO_HW_BIT, /* Hardware Generic receive offload */ - NETIF_F_HW_TLS_RECORD_BIT, /* Offload TLS record */ + __UNUSED_NETIF_F_56, NETIF_F_GRO_FRAGLIST_BIT, /* Fraglist GRO */ NETIF_F_HW_MACSEC_BIT, /* Offload MACsec operations */ @@ -153,7 +153,6 @@ enum { #define NETIF_F_HW_ESP __NETIF_F(HW_ESP) #define NETIF_F_HW_ESP_TX_CSUM __NETIF_F(HW_ESP_TX_CSUM) #define NETIF_F_RX_UDP_TUNNEL_PORT __NETIF_F(RX_UDP_TUNNEL_PORT) -#define NETIF_F_HW_TLS_RECORD __NETIF_F(HW_TLS_RECORD) #define NETIF_F_GSO_UDP_L4 __NETIF_F(GSO_UDP_L4) #define NETIF_F_HW_TLS_TX __NETIF_F(HW_TLS_TX) #define NETIF_F_HW_TLS_RX __NETIF_F(HW_TLS_RX) diff --git a/include/net/tls.h b/include/net/tls.h index 3811943288b3..e57bef58851e 100644 --- a/include/net/tls.h +++ b/include/net/tls.h @@ -85,7 +85,6 @@ enum { TLS_BASE, TLS_SW, TLS_HW, - TLS_HW_RECORD, TLS_NUM_CONFIG, }; diff --git a/include/net/tls_toe.h b/include/net/tls_toe.h deleted file mode 100644 index b3aa7593ce2c..000000000000 --- a/include/net/tls_toe.h +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2016-2017, Mellanox Technologies. All rights reserved. - * Copyright (c) 2016-2017, Dave Watson . All rights reserved. - * - * This software is available to you under a choice of one of two - * licenses. You may choose to be licensed under the terms of the GNU - * General Public License (GPL) Version 2, available from the file - * COPYING in the main directory of this source tree, or the - * OpenIB.org BSD license below: - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above - * copyright notice, this list of conditions and the following - * disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include -#include - -struct sock; - -#define TLS_TOE_DEVICE_NAME_MAX 32 - -/* - * This structure defines the routines for Inline TLS driver. - * The following routines are optional and filled with a - * null pointer if not defined. - * - * @name: Its the name of registered Inline tls device - * @dev_list: Inline tls device list - * int (*feature)(struct tls_toe_device *device); - * Called to return Inline TLS driver capability - * - * int (*hash)(struct tls_toe_device *device, struct sock *sk); - * This function sets Inline driver for listen and program - * device specific functioanlity as required - * - * void (*unhash)(struct tls_toe_device *device, struct sock *sk); - * This function cleans listen state set by Inline TLS driver - * - * void (*release)(struct kref *kref); - * Release the registered device and allocated resources - * @kref: Number of reference to tls_toe_device - */ -struct tls_toe_device { - char name[TLS_TOE_DEVICE_NAME_MAX]; - struct list_head dev_list; - int (*feature)(struct tls_toe_device *device); - int (*hash)(struct tls_toe_device *device, struct sock *sk); - void (*unhash)(struct tls_toe_device *device, struct sock *sk); - void (*release)(struct kref *kref); - struct kref kref; -}; - -int tls_toe_bypass(struct sock *sk); -int tls_toe_hash(struct sock *sk); -void tls_toe_unhash(struct sock *sk); - -void tls_toe_register_device(struct tls_toe_device *device); -void tls_toe_unregister_device(struct tls_toe_device *device); diff --git a/include/uapi/linux/tls.h b/include/uapi/linux/tls.h index b8b9c42f848c..1245ab38afc1 100644 --- a/include/uapi/linux/tls.h +++ b/include/uapi/linux/tls.h @@ -203,6 +203,6 @@ enum { #define TLS_CONF_BASE 1 #define TLS_CONF_SW 2 #define TLS_CONF_HW 3 -#define TLS_CONF_HW_RECORD 4 +#define TLS_CONF_HW_RECORD 4 /* unused */ #endif /* _UAPI_LINUX_TLS_H */ diff --git a/net/ethtool/common.c b/net/ethtool/common.c index a24d3a8a9ec1..23db40618fed 100644 --- a/net/ethtool/common.c +++ b/net/ethtool/common.c @@ -67,7 +67,6 @@ const char netdev_features_strings[NETDEV_FEATURE_COUNT][ETH_GSTRING_LEN] = { [NETIF_F_HW_ESP_BIT] = "esp-hw-offload", [NETIF_F_HW_ESP_TX_CSUM_BIT] = "esp-tx-csum-hw-offload", [NETIF_F_RX_UDP_TUNNEL_PORT_BIT] = "rx-udp_tunnel-port-offload", - [NETIF_F_HW_TLS_RECORD_BIT] = "tls-hw-record", [NETIF_F_HW_TLS_TX_BIT] = "tls-hw-tx-offload", [NETIF_F_HW_TLS_RX_BIT] = "tls-hw-rx-offload", [NETIF_F_GRO_FRAGLIST_BIT] = "rx-gro-list", diff --git a/net/tls/Kconfig b/net/tls/Kconfig index a25bf57f2673..4f4d5973a28f 100644 --- a/net/tls/Kconfig +++ b/net/tls/Kconfig @@ -27,13 +27,3 @@ config TLS_DEVICE Enable kernel support for HW offload of the TLS protocol. If unsure, say N. - -config TLS_TOE - bool "Transport Layer Security TCP stack bypass" - depends on TLS - default n - help - Enable kernel support for legacy HW offload of the TLS protocol, - which is incompatible with the Linux networking stack semantics. - - If unsure, say N. diff --git a/net/tls/Makefile b/net/tls/Makefile index e41c800489ac..4c7d296081ee 100644 --- a/net/tls/Makefile +++ b/net/tls/Makefile @@ -9,5 +9,4 @@ obj-$(CONFIG_TLS) += tls.o tls-y := tls_main.o tls_sw.o tls_proc.o trace.o tls_strp.o -tls-$(CONFIG_TLS_TOE) += tls_toe.o tls-$(CONFIG_TLS_DEVICE) += tls_device.o tls_device_fallback.o diff --git a/net/tls/tls_main.c b/net/tls/tls_main.c index c10a3fd7fc17..13c88a7b8787 100644 --- a/net/tls/tls_main.c +++ b/net/tls/tls_main.c @@ -43,8 +43,6 @@ #include #include -#include - #include "tls.h" MODULE_AUTHOR("Mellanox Technologies"); @@ -963,9 +961,6 @@ static void build_proto_ops(struct proto_ops ops[TLS_NUM_CONFIG][TLS_NUM_CONFIG] ops[TLS_HW ][TLS_HW ] = ops[TLS_HW ][TLS_SW ]; #endif -#ifdef CONFIG_TLS_TOE - ops[TLS_HW_RECORD][TLS_HW_RECORD] = *base; -#endif } static void tls_build_proto(struct sock *sk) @@ -1037,11 +1032,6 @@ static void build_protos(struct proto prot[TLS_NUM_CONFIG][TLS_NUM_CONFIG], prot[TLS_HW][TLS_HW] = prot[TLS_HW][TLS_SW]; #endif -#ifdef CONFIG_TLS_TOE - prot[TLS_HW_RECORD][TLS_HW_RECORD] = *base; - prot[TLS_HW_RECORD][TLS_HW_RECORD].hash = tls_toe_hash; - prot[TLS_HW_RECORD][TLS_HW_RECORD].unhash = tls_toe_unhash; -#endif } static int tls_init(struct sock *sk) @@ -1051,11 +1041,6 @@ static int tls_init(struct sock *sk) tls_build_proto(sk); -#ifdef CONFIG_TLS_TOE - if (tls_toe_bypass(sk)) - return 0; -#endif - /* The TLS ulp is currently supported only for TCP sockets * in ESTABLISHED state. * Supporting sockets in LISTEN state will require us @@ -1111,8 +1096,6 @@ static u16 tls_user_config(struct tls_context *ctx, bool tx) return TLS_CONF_SW; case TLS_HW: return TLS_CONF_HW; - case TLS_HW_RECORD: - return TLS_CONF_HW_RECORD; } return 0; } diff --git a/net/tls/tls_toe.c b/net/tls/tls_toe.c deleted file mode 100644 index 825669e1ab47..000000000000 --- a/net/tls/tls_toe.c +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (c) 2016-2017, Mellanox Technologies. All rights reserved. - * Copyright (c) 2016-2017, Dave Watson . All rights reserved. - * - * This software is available to you under a choice of one of two - * licenses. You may choose to be licensed under the terms of the GNU - * General Public License (GPL) Version 2, available from the file - * COPYING in the main directory of this source tree, or the - * OpenIB.org BSD license below: - * - * Redistribution and use in source and binary forms, with or - * without modification, are permitted provided that the following - * conditions are met: - * - * - Redistributions of source code must retain the above - * copyright notice, this list of conditions and the following - * disclaimer. - * - * - Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include -#include -#include -#include -#include -#include - -#include "tls.h" - -static LIST_HEAD(device_list); -static DEFINE_SPINLOCK(device_spinlock); - -static void tls_toe_sk_destruct(struct sock *sk) -{ - struct inet_connection_sock *icsk = inet_csk(sk); - struct tls_context *ctx = tls_get_ctx(sk); - - ctx->sk_destruct(sk); - /* Free ctx */ - rcu_assign_pointer(icsk->icsk_ulp_data, NULL); - tls_ctx_free(sk, ctx); -} - -int tls_toe_bypass(struct sock *sk) -{ - struct tls_toe_device *dev; - struct tls_context *ctx; - int rc = 0; - - spin_lock_bh(&device_spinlock); - list_for_each_entry(dev, &device_list, dev_list) { - if (dev->feature && dev->feature(dev)) { - ctx = tls_ctx_create(sk); - if (!ctx) - goto out; - - ctx->sk_destruct = sk->sk_destruct; - sk->sk_destruct = tls_toe_sk_destruct; - ctx->rx_conf = TLS_HW_RECORD; - ctx->tx_conf = TLS_HW_RECORD; - update_sk_prot(sk, ctx); - rc = 1; - break; - } - } -out: - spin_unlock_bh(&device_spinlock); - return rc; -} - -void tls_toe_unhash(struct sock *sk) -{ - struct tls_context *ctx = tls_get_ctx(sk); - struct tls_toe_device *dev; - - spin_lock_bh(&device_spinlock); - list_for_each_entry(dev, &device_list, dev_list) { - if (dev->unhash) { - kref_get(&dev->kref); - spin_unlock_bh(&device_spinlock); - dev->unhash(dev, sk); - kref_put(&dev->kref, dev->release); - spin_lock_bh(&device_spinlock); - } - } - spin_unlock_bh(&device_spinlock); - ctx->sk_proto->unhash(sk); -} - -int tls_toe_hash(struct sock *sk) -{ - struct tls_context *ctx = tls_get_ctx(sk); - struct tls_toe_device *dev; - int err; - - err = ctx->sk_proto->hash(sk); - spin_lock_bh(&device_spinlock); - list_for_each_entry(dev, &device_list, dev_list) { - if (dev->hash) { - kref_get(&dev->kref); - spin_unlock_bh(&device_spinlock); - err |= dev->hash(dev, sk); - kref_put(&dev->kref, dev->release); - spin_lock_bh(&device_spinlock); - } - } - spin_unlock_bh(&device_spinlock); - - if (err) - tls_toe_unhash(sk); - return err; -} - -void tls_toe_register_device(struct tls_toe_device *device) -{ - spin_lock_bh(&device_spinlock); - list_add_tail(&device->dev_list, &device_list); - spin_unlock_bh(&device_spinlock); -} -EXPORT_SYMBOL(tls_toe_register_device); - -void tls_toe_unregister_device(struct tls_toe_device *device) -{ - spin_lock_bh(&device_spinlock); - list_del(&device->dev_list); - spin_unlock_bh(&device_spinlock); -} -EXPORT_SYMBOL(tls_toe_unregister_device); -- cgit v1.2.3 From 8eed5519e496b7a07f441a0f579cb228a33189f7 Mon Sep 17 00:00:00 2001 From: Eric Dumazet Date: Thu, 11 Jun 2026 15:27:37 +0000 Subject: net: watchdog: fix refcount tracking races Blamed commit converted the untracked dev_hold()/dev_put() calls in the watchdog code to use the tracked dev_hold_track()/dev_put_track() (which were later renamed/interfaced to netdev_hold() and netdev_put()). By introducing dev->watchdog_dev_tracker to store the reference tracking information without adding synchronization between netdev_watchdog_up() and dev_watchdog(), it enabled the race condition where this pointer could be overwritten or freed concurrently, leading to the list corruption crash syzbot reported: list_del corruption, ffff888114a18c00->next is NULL kernel BUG at lib/list_debug.c:52 ! Oops: invalid opcode: 0000 [#1] SMP KASAN PTI CPU: 1 UID: 0 PID: 91 Comm: kworker/u8:5 Not tainted syzkaller #0 PREEMPT(lazy) Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 05/09/2026 Workqueue: events_unbound linkwatch_event RIP: 0010:__list_del_entry_valid_or_report.cold+0x22/0x2a lib/list_debug.c:52 Call Trace: __list_del_entry_valid include/linux/list.h:132 [inline] __list_del_entry include/linux/list.h:246 [inline] list_move_tail include/linux/list.h:341 [inline] ref_tracker_free+0x1a7/0x6c0 lib/ref_tracker.c:329 netdev_tracker_free include/linux/netdevice.h:4491 [inline] netdev_put include/linux/netdevice.h:4508 [inline] netdev_put include/linux/netdevice.h:4504 [inline] netdev_watchdog_down net/sched/sch_generic.c:600 [inline] dev_deactivate_many+0x28c/0xfe0 net/sched/sch_generic.c:1363 dev_deactivate+0x109/0x1d0 net/sched/sch_generic.c:1397 linkwatch_do_dev net/core/link_watch.c:184 [inline] linkwatch_do_dev+0xd3/0x120 net/core/link_watch.c:166 __linkwatch_run_queue+0x3a5/0x810 net/core/link_watch.c:240 linkwatch_event+0x8f/0xc0 net/core/link_watch.c:314 process_one_work+0xa0e/0x1980 kernel/workqueue.c:3314 process_scheduled_works kernel/workqueue.c:3397 [inline] worker_thread+0x5ef/0xe50 kernel/workqueue.c:3478 kthread+0x370/0x450 kernel/kthread.c:436 ret_from_fork+0x69a/0xc80 arch/x86/kernel/process.c:158 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245 This patch has three coordinated parts: 1) Add dev->watchdog_lock and dev->watchdog_ref_held to serialize watchdog operations. 2) Remove netdev_watchdog_up() call from netif_carrier_on(): This ensures netdev_watchdog_up() is only called from process/BH context (via linkwatch workqueue dev_activate()), allowing us to use spin_lock_bh() for synchronization. 3) Synchronize watchdog up and watchdog timer: Protect netdev_watchdog_up() with tx_global_lock and watchdog_lock. Only allocate a new tracker in netdev_watchdog_up() if one is not already present. In dev_watchdog(), ensure we don't release the tracker if the timer was rescheduled either by dev_watchdog() itself or concurrently by netdev_watchdog_up(). Fixes: f12bf6f3f942 ("net: watchdog: add net device refcount tracker") Reported-by: syzbot+381d82bbf0253710b35d@syzkaller.appspotmail.com Closes: https://lore.kernel.org/netdev/6a26b751.c25708ab.1b19ef.0013.GAE@google.com/T/#u Tested-by: syzbot+3479efbc2821cb2a79f2@syzkaller.appspotmail.com Signed-off-by: Eric Dumazet Link: https://patch.msgid.link/20260611152737.2580480-1-edumazet@google.com Signed-off-by: Jakub Kicinski --- include/linux/netdevice.h | 4 ++++ net/core/dev.c | 3 ++- net/sched/sch_generic.c | 44 +++++++++++++++++++++++++++++++++----------- 3 files changed, 39 insertions(+), 12 deletions(-) (limited to 'include') diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 0e1e581efc5a..4a0e83709f29 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -1980,6 +1980,8 @@ enum netdev_reg_state { * @qdisc_hash: qdisc hash table * @watchdog_timeo: Represents the timeout that is used by * the watchdog (see dev_watchdog()) + * @watchdog_lock: protect watchdog_ref_held + * @watchdog_ref_held: True if the watchdog device ref is taken. * @watchdog_timer: List of timers * * @proto_down_reason: reason a netdev interface is held down @@ -2392,6 +2394,8 @@ struct net_device { /* These may be needed for future network-power-down code. */ struct timer_list watchdog_timer; int watchdog_timeo; + spinlock_t watchdog_lock; + bool watchdog_ref_held; u32 proto_down_reason; diff --git a/net/core/dev.c b/net/core/dev.c index 0c6c270d9f7d..731e661d7be6 100644 --- a/net/core/dev.c +++ b/net/core/dev.c @@ -11217,7 +11217,8 @@ static int netif_alloc_netdev_queues(struct net_device *dev) netdev_for_each_tx_queue(dev, netdev_init_one_queue, NULL); spin_lock_init(&dev->tx_global_lock); - + spin_lock_init(&dev->watchdog_lock); + dev->watchdog_ref_held = false; return 0; } diff --git a/net/sched/sch_generic.c b/net/sched/sch_generic.c index a93321db8fd7..6cdf2ccfb093 100644 --- a/net/sched/sch_generic.c +++ b/net/sched/sch_generic.c @@ -568,16 +568,24 @@ static void dev_watchdog(struct timer_list *t) dev->netdev_ops->ndo_tx_timeout(dev, i); netif_unfreeze_queues(dev); } - if (!mod_timer(&dev->watchdog_timer, - round_jiffies(oldest_start + - dev->watchdog_timeo))) - release = false; + spin_lock(&dev->watchdog_lock); + mod_timer(&dev->watchdog_timer, + round_jiffies(oldest_start + + dev->watchdog_timeo)); + release = false; + spin_unlock(&dev->watchdog_lock); } } spin_unlock(&dev->tx_global_lock); - if (release) + spin_lock(&dev->watchdog_lock); + if (timer_pending(&dev->watchdog_timer)) + release = false; + if (release && dev->watchdog_ref_held) { netdev_put(dev, &dev->watchdog_dev_tracker); + dev->watchdog_ref_held = false; + } + spin_unlock(&dev->watchdog_lock); } void netdev_watchdog_up(struct net_device *dev) @@ -586,18 +594,34 @@ void netdev_watchdog_up(struct net_device *dev) return; if (dev->watchdog_timeo <= 0) dev->watchdog_timeo = 5*HZ; + spin_lock_bh(&dev->tx_global_lock); + + spin_lock(&dev->watchdog_lock); if (!mod_timer(&dev->watchdog_timer, - round_jiffies(jiffies + dev->watchdog_timeo))) - netdev_hold(dev, &dev->watchdog_dev_tracker, - GFP_ATOMIC); + round_jiffies(jiffies + dev->watchdog_timeo))) { + if (!dev->watchdog_ref_held) { + netdev_hold(dev, &dev->watchdog_dev_tracker, + GFP_ATOMIC); + dev->watchdog_ref_held = true; + } + } + spin_unlock(&dev->watchdog_lock); + + spin_unlock_bh(&dev->tx_global_lock); } EXPORT_SYMBOL_GPL(netdev_watchdog_up); static void netdev_watchdog_down(struct net_device *dev) { netif_tx_lock_bh(dev); - if (timer_delete(&dev->watchdog_timer)) + + spin_lock(&dev->watchdog_lock); + if (timer_delete(&dev->watchdog_timer)) { netdev_put(dev, &dev->watchdog_dev_tracker); + dev->watchdog_ref_held = false; + } + spin_unlock(&dev->watchdog_lock); + netif_tx_unlock_bh(dev); } @@ -614,8 +638,6 @@ void netif_carrier_on(struct net_device *dev) return; atomic_inc(&dev->carrier_up_count); linkwatch_fire_event(dev); - if (netif_running(dev)) - netdev_watchdog_up(dev); } } EXPORT_SYMBOL(netif_carrier_on); -- cgit v1.2.3 From 06c2dce2d0f69727144443664182052f56d1da35 Mon Sep 17 00:00:00 2001 From: Wei Wang Date: Mon, 8 Jun 2026 16:31:10 -0700 Subject: psp: add new netlink cmd for dev-assoc and dev-disassoc The main purpose of this cmd is to be able to associate a non-psp-capable device (e.g. veth or netkit) with a psp device. One use case is if we create a pair of veth/netkit, and assign 1 end inside a netns, while leaving the other end within the default netns, with a real PSP device, e.g. netdevsim or a physical PSP-capable NIC. With this command, we could associate the veth/netkit inside the netns with PSP device, so the virtual device could act as PSP-capable device to initiate PSP connections, and performs PSP encryption/decryption on the real PSP device. Signed-off-by: Wei Wang Reviewed-by: Daniel Zahka Link: https://patch.msgid.link/20260608233118.2694144-3-weibunny.kernel@gmail.com Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/psp.yaml | 69 +++++++- include/net/psp/types.h | 23 +++ include/uapi/linux/psp.h | 13 ++ net/psp/psp-nl-gen.c | 32 ++++ net/psp/psp-nl-gen.h | 2 + net/psp/psp.h | 1 + net/psp/psp_main.c | 15 ++ net/psp/psp_nl.c | 325 +++++++++++++++++++++++++++++++++-- 8 files changed, 469 insertions(+), 11 deletions(-) (limited to 'include') diff --git a/Documentation/netlink/specs/psp.yaml b/Documentation/netlink/specs/psp.yaml index e3b0fe296e8f..aa79332cb9b1 100644 --- a/Documentation/netlink/specs/psp.yaml +++ b/Documentation/netlink/specs/psp.yaml @@ -13,6 +13,17 @@ definitions: hdr0-aes-gmac-128, hdr0-aes-gmac-256] attribute-sets: + - + name: assoc-dev-info + attributes: + - + name: ifindex + doc: ifindex of an associated network device. + type: u32 + - + name: nsid + doc: Network namespace ID of the associated device. + type: s32 - name: dev attributes: @@ -24,7 +35,9 @@ attribute-sets: min: 1 - name: ifindex - doc: ifindex of the main netdevice linked to the PSP device. + doc: | + ifindex of the main netdevice linked to the PSP device, + or the ifindex to associate with the PSP device. type: u32 - name: psp-versions-cap @@ -38,6 +51,28 @@ attribute-sets: type: u32 enum: version enum-as-flags: true + - + name: assoc-list + doc: List of associated virtual devices. + type: nest + nested-attributes: assoc-dev-info + multi-attr: true + - + name: nsid + doc: | + Network namespace ID for the device to associate/disassociate. + Optional for dev-assoc and dev-disassoc; if not present, the + device is looked up in the caller's network namespace. + type: s32 + - + name: by-association + doc: | + Flag indicating the PSP device is an associated device from a + different network namespace. + Present when in associated namespace, absent when in primary/host + namespace. + type: flag + - name: assoc attributes: @@ -170,6 +205,8 @@ operations: - ifindex - psp-versions-cap - psp-versions-ena + - assoc-list + - by-association pre: psp-device-get-locked post: psp-device-unlock dump: @@ -281,6 +318,36 @@ operations: post: psp-device-unlock dump: reply: *stats-all + - + name: dev-assoc + doc: Associate a network device with a PSP device. + attribute-set: dev + flags: [admin-perm] + do: + request: + attributes: + - id + - ifindex + - nsid + reply: + attributes: [] + pre: psp-device-get-locked + post: psp-device-unlock + - + name: dev-disassoc + doc: Disassociate a network device from a PSP device. + attribute-set: dev + flags: [admin-perm] + do: + request: + attributes: + - id + - ifindex + - nsid + reply: + attributes: [] + pre: psp-device-get-locked + post: psp-device-unlock mcast-groups: list: diff --git a/include/net/psp/types.h b/include/net/psp/types.h index 25a9096d4e7d..87991a1ea02d 100644 --- a/include/net/psp/types.h +++ b/include/net/psp/types.h @@ -5,6 +5,7 @@ #include #include +#include struct netlink_ext_ack; @@ -43,9 +44,29 @@ struct psp_dev_config { u32 versions; }; +/* Max number of devices that can be associated with a single PSP device. + * Each entry consumes ~24 bytes in the netlink dev-get response, and the + * response must fit in GENLMSG_DEFAULT_SIZE (~3.7KB). + */ +#define PSP_ASSOC_DEV_MAX 128 + +/** + * struct psp_assoc_dev - wrapper for associated net_device + * @dev_list: list node for psp_dev::assoc_dev_list + * @assoc_dev: the associated net_device + * @dev_tracker: tracker for the net_device reference + */ +struct psp_assoc_dev { + struct list_head dev_list; + struct net_device *assoc_dev; + netdevice_tracker dev_tracker; +}; + /** * struct psp_dev - PSP device struct * @main_netdev: original netdevice of this PSP device + * @assoc_dev_list: list of psp_assoc_dev entries associated with this PSP device + * @assoc_dev_cnt: number of entries in @assoc_dev_list * @ops: driver callbacks * @caps: device capabilities * @drv_priv: driver priv pointer @@ -67,6 +88,8 @@ struct psp_dev_config { */ struct psp_dev { struct net_device *main_netdev; + struct list_head assoc_dev_list; + int assoc_dev_cnt; struct psp_dev_ops *ops; struct psp_dev_caps *caps; diff --git a/include/uapi/linux/psp.h b/include/uapi/linux/psp.h index a3a336488dc3..1c8899cd4da5 100644 --- a/include/uapi/linux/psp.h +++ b/include/uapi/linux/psp.h @@ -17,11 +17,22 @@ enum psp_version { PSP_VERSION_HDR0_AES_GMAC_256, }; +enum { + PSP_A_ASSOC_DEV_INFO_IFINDEX = 1, + PSP_A_ASSOC_DEV_INFO_NSID, + + __PSP_A_ASSOC_DEV_INFO_MAX, + PSP_A_ASSOC_DEV_INFO_MAX = (__PSP_A_ASSOC_DEV_INFO_MAX - 1) +}; + enum { PSP_A_DEV_ID = 1, PSP_A_DEV_IFINDEX, PSP_A_DEV_PSP_VERSIONS_CAP, PSP_A_DEV_PSP_VERSIONS_ENA, + PSP_A_DEV_ASSOC_LIST, + PSP_A_DEV_NSID, + PSP_A_DEV_BY_ASSOCIATION, __PSP_A_DEV_MAX, PSP_A_DEV_MAX = (__PSP_A_DEV_MAX - 1) @@ -74,6 +85,8 @@ enum { PSP_CMD_RX_ASSOC, PSP_CMD_TX_ASSOC, PSP_CMD_GET_STATS, + PSP_CMD_DEV_ASSOC, + PSP_CMD_DEV_DISASSOC, __PSP_CMD_MAX, PSP_CMD_MAX = (__PSP_CMD_MAX - 1) diff --git a/net/psp/psp-nl-gen.c b/net/psp/psp-nl-gen.c index a71dd629aeab..c3cc189f0a7b 100644 --- a/net/psp/psp-nl-gen.c +++ b/net/psp/psp-nl-gen.c @@ -53,6 +53,20 @@ static const struct nla_policy psp_get_stats_nl_policy[PSP_A_STATS_DEV_ID + 1] = [PSP_A_STATS_DEV_ID] = NLA_POLICY_MIN(NLA_U32, 1), }; +/* PSP_CMD_DEV_ASSOC - do */ +static const struct nla_policy psp_dev_assoc_nl_policy[PSP_A_DEV_NSID + 1] = { + [PSP_A_DEV_ID] = NLA_POLICY_MIN(NLA_U32, 1), + [PSP_A_DEV_IFINDEX] = { .type = NLA_U32, }, + [PSP_A_DEV_NSID] = { .type = NLA_S32, }, +}; + +/* PSP_CMD_DEV_DISASSOC - do */ +static const struct nla_policy psp_dev_disassoc_nl_policy[PSP_A_DEV_NSID + 1] = { + [PSP_A_DEV_ID] = NLA_POLICY_MIN(NLA_U32, 1), + [PSP_A_DEV_IFINDEX] = { .type = NLA_U32, }, + [PSP_A_DEV_NSID] = { .type = NLA_S32, }, +}; + /* Ops table for psp */ static const struct genl_split_ops psp_nl_ops[] = { { @@ -119,6 +133,24 @@ static const struct genl_split_ops psp_nl_ops[] = { .dumpit = psp_nl_get_stats_dumpit, .flags = GENL_CMD_CAP_DUMP, }, + { + .cmd = PSP_CMD_DEV_ASSOC, + .pre_doit = psp_device_get_locked, + .doit = psp_nl_dev_assoc_doit, + .post_doit = psp_device_unlock, + .policy = psp_dev_assoc_nl_policy, + .maxattr = PSP_A_DEV_NSID, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = PSP_CMD_DEV_DISASSOC, + .pre_doit = psp_device_get_locked, + .doit = psp_nl_dev_disassoc_doit, + .post_doit = psp_device_unlock, + .policy = psp_dev_disassoc_nl_policy, + .maxattr = PSP_A_DEV_NSID, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, }; static const struct genl_multicast_group psp_nl_mcgrps[] = { diff --git a/net/psp/psp-nl-gen.h b/net/psp/psp-nl-gen.h index 977355455395..4dd0f0f23053 100644 --- a/net/psp/psp-nl-gen.h +++ b/net/psp/psp-nl-gen.h @@ -33,6 +33,8 @@ int psp_nl_rx_assoc_doit(struct sk_buff *skb, struct genl_info *info); int psp_nl_tx_assoc_doit(struct sk_buff *skb, struct genl_info *info); int psp_nl_get_stats_doit(struct sk_buff *skb, struct genl_info *info); int psp_nl_get_stats_dumpit(struct sk_buff *skb, struct netlink_callback *cb); +int psp_nl_dev_assoc_doit(struct sk_buff *skb, struct genl_info *info); +int psp_nl_dev_disassoc_doit(struct sk_buff *skb, struct genl_info *info); enum { PSP_NLGRP_MGMT, diff --git a/net/psp/psp.h b/net/psp/psp.h index 0f9c4e4e52cb..cf381e786cb6 100644 --- a/net/psp/psp.h +++ b/net/psp/psp.h @@ -15,6 +15,7 @@ extern struct mutex psp_devs_lock; void psp_dev_free(struct psp_dev *psd); int psp_dev_check_access(struct psp_dev *psd, struct net *net, bool admin); +bool psp_has_assoc_dev_in_ns(struct psp_dev *psd, struct net *net); void psp_nl_notify_dev(struct psp_dev *psd, u32 cmd); diff --git a/net/psp/psp_main.c b/net/psp/psp_main.c index aaa44e6cb9ff..470f6c7ce9ee 100644 --- a/net/psp/psp_main.c +++ b/net/psp/psp_main.c @@ -39,6 +39,10 @@ int psp_dev_check_access(struct psp_dev *psd, struct net *net, bool admin) { if (dev_net(psd->main_netdev) == net) return 0; + + if (!admin && psp_has_assoc_dev_in_ns(psd, net)) + return 0; + return -ENOENT; } @@ -74,6 +78,7 @@ psp_dev_create(struct net_device *netdev, return ERR_PTR(-ENOMEM); psd->main_netdev = netdev; + INIT_LIST_HEAD(&psd->assoc_dev_list); psd->ops = psd_ops; psd->caps = psd_caps; psd->drv_priv = priv_ptr; @@ -125,6 +130,7 @@ void psp_dev_free(struct psp_dev *psd) */ void psp_dev_unregister(struct psp_dev *psd) { + struct psp_assoc_dev *entry, *entry_tmp; struct psp_assoc *pas, *next; mutex_lock(&psp_devs_lock); @@ -144,6 +150,15 @@ void psp_dev_unregister(struct psp_dev *psd) list_for_each_entry_safe(pas, next, &psd->stale_assocs, assocs_list) psp_dev_tx_key_del(psd, pas); + list_for_each_entry_safe(entry, entry_tmp, &psd->assoc_dev_list, + dev_list) { + list_del(&entry->dev_list); + rcu_assign_pointer(entry->assoc_dev->psp_dev, NULL); + netdev_put(entry->assoc_dev, &entry->dev_tracker); + kfree(entry); + } + psd->assoc_dev_cnt = 0; + rcu_assign_pointer(psd->main_netdev->psp_dev, NULL); psd->ops = NULL; diff --git a/net/psp/psp_nl.c b/net/psp/psp_nl.c index b4f1b7f9b0c2..a2058aaf0f36 100644 --- a/net/psp/psp_nl.c +++ b/net/psp/psp_nl.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-only #include +#include #include #include #include @@ -38,6 +39,74 @@ static int psp_nl_reply_send(struct sk_buff *rsp, struct genl_info *info) return genlmsg_reply(rsp, info); } +/** + * psp_nl_multicast_per_ns() - multicast a notification to each unique netns + * @psd: PSP device (must be locked) + * @group: multicast group + * @build_ntf: callback to build an skb for a given netns, or NULL on failure + * @ctx: opaque context passed to @build_ntf + * + * Iterates all unique network namespaces from the associated device list + * plus the main device's netns. For each unique netns, calls @build_ntf + * to construct a notification skb and multicasts it. + */ +static void +psp_nl_multicast_per_ns(struct psp_dev *psd, unsigned int group, + struct sk_buff *(*build_ntf)(struct psp_dev *, + struct net *, + void *), + void *ctx) +{ + struct psp_assoc_dev *entry; + struct xarray sent_nets; + struct net *main_net; + struct sk_buff *ntf; + + main_net = dev_net(psd->main_netdev); + xa_init(&sent_nets); + + list_for_each_entry(entry, &psd->assoc_dev_list, dev_list) { + struct net *assoc_net = dev_net(entry->assoc_dev); + int ret; + + if (net_eq(assoc_net, main_net)) + continue; + + ret = xa_insert(&sent_nets, (unsigned long)assoc_net, assoc_net, + GFP_KERNEL); + if (ret == -EBUSY) + continue; + + ntf = build_ntf(psd, assoc_net, ctx); + if (!ntf) + continue; + + genlmsg_multicast_netns(&psp_nl_family, assoc_net, ntf, 0, + group, GFP_KERNEL); + } + xa_destroy(&sent_nets); + + /* Send to main device netns */ + ntf = build_ntf(psd, main_net, ctx); + if (!ntf) + return; + genlmsg_multicast_netns(&psp_nl_family, main_net, ntf, 0, group, + GFP_KERNEL); +} + +static struct sk_buff *psp_nl_clone_ntf(struct psp_dev *psd, struct net *net, + void *ctx) +{ + return skb_clone(ctx, GFP_KERNEL); +} + +static void psp_nl_multicast_all_ns(struct psp_dev *psd, struct sk_buff *ntf, + unsigned int group) +{ + psp_nl_multicast_per_ns(psd, group, psp_nl_clone_ntf, ntf); + nlmsg_consume(ntf); +} + /* Device stuff */ static struct psp_dev * @@ -91,6 +160,38 @@ int psp_device_get_locked(const struct genl_split_ops *ops, return __psp_device_get_locked(ops, skb, info, false); } +static struct net *psp_nl_resolve_assoc_dev_ns(struct psp_dev *psd, + struct genl_info *info) +{ + struct net *net; + int nsid; + + if (GENL_REQ_ATTR_CHECK(info, PSP_A_DEV_IFINDEX)) + return ERR_PTR(-EINVAL); + + if (info->attrs[PSP_A_DEV_NSID]) { + /* Only callers in the main netns may specify nsid */ + if (dev_net(psd->main_netdev) != genl_info_net(info)) { + NL_SET_BAD_ATTR(info->extack, + info->attrs[PSP_A_DEV_NSID]); + return ERR_PTR(-EPERM); + } + + nsid = nla_get_s32(info->attrs[PSP_A_DEV_NSID]); + + net = get_net_ns_by_id(genl_info_net(info), nsid); + if (!net) { + NL_SET_BAD_ATTR(info->extack, + info->attrs[PSP_A_DEV_NSID]); + return ERR_PTR(-EINVAL); + } + } else { + net = get_net(genl_info_net(info)); + } + + return net; +} + void psp_device_unlock(const struct genl_split_ops *ops, struct sk_buff *skb, struct genl_info *info) @@ -103,11 +204,67 @@ psp_device_unlock(const struct genl_split_ops *ops, struct sk_buff *skb, sockfd_put(socket); } +bool psp_has_assoc_dev_in_ns(struct psp_dev *psd, struct net *net) +{ + struct psp_assoc_dev *entry; + + list_for_each_entry(entry, &psd->assoc_dev_list, dev_list) { + if (dev_net(entry->assoc_dev) == net) + return true; + } + + return false; +} + +static int psp_nl_fill_assoc_dev_list(struct psp_dev *psd, struct sk_buff *rsp, + struct net *cur_net, + struct net *filter_net) +{ + struct psp_assoc_dev *entry; + struct net *dev_net_ns; + struct nlattr *nest; + int nsid; + + list_for_each_entry(entry, &psd->assoc_dev_list, dev_list) { + dev_net_ns = dev_net(entry->assoc_dev); + + if (filter_net && dev_net_ns != filter_net) + continue; + + /* When filtering by namespace, all devices are in the caller's + * namespace so nsid is always NETNSA_NSID_NOT_ASSIGNED (-1). + * Otherwise, calculate the nsid relative to cur_net. + */ + nsid = filter_net ? NETNSA_NSID_NOT_ASSIGNED : + peernet2id_alloc(cur_net, dev_net_ns, + GFP_KERNEL); + + nest = nla_nest_start(rsp, PSP_A_DEV_ASSOC_LIST); + if (!nest) + return -EMSGSIZE; + + if (nla_put_u32(rsp, PSP_A_ASSOC_DEV_INFO_IFINDEX, + entry->assoc_dev->ifindex) || + nla_put_s32(rsp, PSP_A_ASSOC_DEV_INFO_NSID, nsid)) { + nla_nest_cancel(rsp, nest); + return -EMSGSIZE; + } + + nla_nest_end(rsp, nest); + } + + return 0; +} + static int psp_nl_dev_fill(struct psp_dev *psd, struct sk_buff *rsp, const struct genl_info *info) { + struct net *cur_net; void *hdr; + int err; + + cur_net = genl_info_net(info); hdr = genlmsg_iput(rsp, info); if (!hdr) @@ -119,6 +276,22 @@ psp_nl_dev_fill(struct psp_dev *psd, struct sk_buff *rsp, nla_put_u32(rsp, PSP_A_DEV_PSP_VERSIONS_ENA, psd->config.versions)) goto err_cancel_msg; + if (cur_net == dev_net(psd->main_netdev)) { + /* Primary device - dump assoc list */ + err = psp_nl_fill_assoc_dev_list(psd, rsp, cur_net, NULL); + if (err) + goto err_cancel_msg; + } else { + /* In netns: set by-association flag and dump filtered + * assoc list containing only devices in cur_net + */ + if (nla_put_flag(rsp, PSP_A_DEV_BY_ASSOCIATION)) + goto err_cancel_msg; + err = psp_nl_fill_assoc_dev_list(psd, rsp, cur_net, cur_net); + if (err) + goto err_cancel_msg; + } + genlmsg_end(rsp, hdr); return 0; @@ -127,27 +300,34 @@ err_cancel_msg: return -EMSGSIZE; } -void psp_nl_notify_dev(struct psp_dev *psd, u32 cmd) +static struct sk_buff *psp_nl_build_dev_ntf(struct psp_dev *psd, + struct net *net, void *ctx) { + u32 cmd = *(u32 *)ctx; struct genl_info info; struct sk_buff *ntf; - if (!genl_has_listeners(&psp_nl_family, dev_net(psd->main_netdev), - PSP_NLGRP_MGMT)) - return; + if (!genl_has_listeners(&psp_nl_family, net, PSP_NLGRP_MGMT)) + return NULL; ntf = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL); if (!ntf) - return; + return NULL; genl_info_init_ntf(&info, &psp_nl_family, cmd); + genl_info_net_set(&info, net); if (psp_nl_dev_fill(psd, ntf, &info)) { nlmsg_free(ntf); - return; + return NULL; } - genlmsg_multicast_netns(&psp_nl_family, dev_net(psd->main_netdev), ntf, - 0, PSP_NLGRP_MGMT, GFP_KERNEL); + return ntf; +} + +void psp_nl_notify_dev(struct psp_dev *psd, u32 cmd) +{ + psp_nl_multicast_per_ns(psd, PSP_NLGRP_MGMT, + psp_nl_build_dev_ntf, &cmd); } int psp_nl_dev_get_doit(struct sk_buff *req, struct genl_info *info) @@ -281,8 +461,9 @@ int psp_nl_key_rotate_doit(struct sk_buff *skb, struct genl_info *info) psd->stats.rotations++; nlmsg_end(ntf, (struct nlmsghdr *)ntf->data); - genlmsg_multicast_netns(&psp_nl_family, dev_net(psd->main_netdev), ntf, - 0, PSP_NLGRP_USE, GFP_KERNEL); + + psp_nl_multicast_all_ns(psd, ntf, PSP_NLGRP_USE); + return psp_nl_reply_send(rsp, info); err_free_ntf: @@ -292,6 +473,130 @@ err_free_rsp: return err; } +int psp_nl_dev_assoc_doit(struct sk_buff *skb, struct genl_info *info) +{ + struct psp_dev *psd = info->user_ptr[0]; + struct psp_assoc_dev *psp_assoc_dev; + struct net_device *assoc_dev; + struct sk_buff *rsp; + u32 assoc_ifindex; + struct net *net; + int err; + + if (psd->assoc_dev_cnt >= PSP_ASSOC_DEV_MAX) { + NL_SET_ERR_MSG(info->extack, + "Maximum number of associated devices reached"); + return -ENOSPC; + } + + net = psp_nl_resolve_assoc_dev_ns(psd, info); + if (IS_ERR(net)) + return PTR_ERR(net); + + psp_assoc_dev = kzalloc_obj(*psp_assoc_dev); + if (!psp_assoc_dev) { + err = -ENOMEM; + goto err_put_net; + } + + assoc_ifindex = nla_get_u32(info->attrs[PSP_A_DEV_IFINDEX]); + assoc_dev = netdev_get_by_index(net, assoc_ifindex, + &psp_assoc_dev->dev_tracker, + GFP_KERNEL); + if (!assoc_dev) { + NL_SET_BAD_ATTR(info->extack, info->attrs[PSP_A_DEV_IFINDEX]); + err = -ENODEV; + goto err_free_assoc; + } + + /* Check if device is already associated with a PSP device */ + if (cmpxchg(&assoc_dev->psp_dev, NULL, RCU_INITIALIZER(psd))) { + NL_SET_ERR_MSG(info->extack, + "Device already associated with a PSP device"); + err = -EBUSY; + goto err_put_dev; + } + + psp_assoc_dev->assoc_dev = assoc_dev; + rsp = psp_nl_reply_new(info); + if (!rsp) { + err = -ENOMEM; + goto err_clean_ptr; + } + + list_add_tail(&psp_assoc_dev->dev_list, &psd->assoc_dev_list); + psd->assoc_dev_cnt++; + + put_net(net); + + psp_nl_notify_dev(psd, PSP_CMD_DEV_CHANGE_NTF); + + return psp_nl_reply_send(rsp, info); + +err_clean_ptr: + rcu_assign_pointer(assoc_dev->psp_dev, NULL); +err_put_dev: + netdev_put(assoc_dev, &psp_assoc_dev->dev_tracker); +err_free_assoc: + kfree(psp_assoc_dev); +err_put_net: + put_net(net); + + return err; +} + +int psp_nl_dev_disassoc_doit(struct sk_buff *skb, struct genl_info *info) +{ + struct psp_assoc_dev *entry, *found = NULL; + struct psp_dev *psd = info->user_ptr[0]; + struct sk_buff *rsp; + u32 assoc_ifindex; + struct net *net; + + net = psp_nl_resolve_assoc_dev_ns(psd, info); + if (IS_ERR(net)) + return PTR_ERR(net); + + assoc_ifindex = nla_get_u32(info->attrs[PSP_A_DEV_IFINDEX]); + + /* Search the association list by ifindex and netns */ + list_for_each_entry(entry, &psd->assoc_dev_list, dev_list) { + if (entry->assoc_dev->ifindex == assoc_ifindex && + dev_net(entry->assoc_dev) == net) { + found = entry; + break; + } + } + + if (!found) { + put_net(net); + NL_SET_BAD_ATTR(info->extack, info->attrs[PSP_A_DEV_IFINDEX]); + return -ENODEV; + } + + rsp = psp_nl_reply_new(info); + if (!rsp) { + put_net(net); + return -ENOMEM; + } + + put_net(net); + + /* Notify before removal so listeners in the disassociated namespace + * still receive the notification. + */ + psp_nl_notify_dev(psd, PSP_CMD_DEV_CHANGE_NTF); + + /* Remove from the association list */ + list_del(&found->dev_list); + psd->assoc_dev_cnt--; + rcu_assign_pointer(found->assoc_dev->psp_dev, NULL); + netdev_put(found->assoc_dev, &found->dev_tracker); + kfree(found); + + return psp_nl_reply_send(rsp, info); +} + /* Key etc. */ int psp_assoc_device_get_locked(const struct genl_split_ops *ops, -- cgit v1.2.3 From 77eee189397df7ef8a19cf279db6845ab839662e Mon Sep 17 00:00:00 2001 From: Raf Dickson Date: Fri, 12 Jun 2026 04:52:13 +0000 Subject: vsock: introduce vsock_pending_to_accept() helper Add vsock_pending_to_accept() to move a socket directly from the pending list to the accept queue in a single operation, avoiding the sock_put/sock_hold dance and the sk_acceptq_removed()/ sk_acceptq_added() pair that would otherwise be needed when calling vsock_remove_pending() followed by vsock_enqueue_accept(). Use it in vmci_transport_recv_connecting_server() where a completed handshake transitions the socket from pending to accept queue. Suggested-by: Stefano Garzarella Signed-off-by: Raf Dickson Reviewed-by: Stefano Garzarella Reviewed-by: Luigi Leonardi Reviewed-by: Bobby Eshleman Link: https://patch.msgid.link/20260612045216.105796-2-rafdog35@gmail.com Signed-off-by: Jakub Kicinski --- include/net/af_vsock.h | 1 + net/vmw_vsock/af_vsock.c | 10 ++++++++++ net/vmw_vsock/vmci_transport.c | 3 +-- 3 files changed, 12 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/include/net/af_vsock.h b/include/net/af_vsock.h index 4e40063adab4..30046a3c20f7 100644 --- a/include/net/af_vsock.h +++ b/include/net/af_vsock.h @@ -220,6 +220,7 @@ static inline bool __vsock_in_connected_table(struct vsock_sock *vsk) void vsock_add_pending(struct sock *listener, struct sock *pending); void vsock_remove_pending(struct sock *listener, struct sock *pending); void vsock_enqueue_accept(struct sock *listener, struct sock *connected); +void vsock_pending_to_accept(struct sock *listener, struct sock *pending); void vsock_insert_connected(struct vsock_sock *vsk); void vsock_remove_bound(struct vsock_sock *vsk); void vsock_remove_connected(struct vsock_sock *vsk); diff --git a/net/vmw_vsock/af_vsock.c b/net/vmw_vsock/af_vsock.c index 1b20ec498518..2a267283aef8 100644 --- a/net/vmw_vsock/af_vsock.c +++ b/net/vmw_vsock/af_vsock.c @@ -497,6 +497,16 @@ void vsock_remove_pending(struct sock *listener, struct sock *pending) } EXPORT_SYMBOL_GPL(vsock_remove_pending); +void vsock_pending_to_accept(struct sock *listener, struct sock *pending) +{ + struct vsock_sock *vpending = vsock_sk(pending); + struct vsock_sock *vlistener = vsock_sk(listener); + + list_del_init(&vpending->pending_links); + list_add_tail(&vpending->accept_queue, &vlistener->accept_queue); +} +EXPORT_SYMBOL_GPL(vsock_pending_to_accept); + void vsock_enqueue_accept(struct sock *listener, struct sock *connected) { struct vsock_sock *vlistener; diff --git a/net/vmw_vsock/vmci_transport.c b/net/vmw_vsock/vmci_transport.c index 56503bee3165..af64fd57c0c8 100644 --- a/net/vmw_vsock/vmci_transport.c +++ b/net/vmw_vsock/vmci_transport.c @@ -1258,8 +1258,7 @@ vmci_transport_recv_connecting_server(struct sock *listener, * listener's pending list to the accept queue so callers of accept() * can find it. */ - vsock_remove_pending(listener, pending); - vsock_enqueue_accept(listener, pending); + vsock_pending_to_accept(listener, pending); /* Callers of accept() will be waiting on the listening socket, not * the pending socket. -- cgit v1.2.3 From 9375487c0c78817b3651e2621d648c6198757c41 Mon Sep 17 00:00:00 2001 From: Grzegorz Nitka Date: Sun, 7 Jun 2026 20:30:33 +0200 Subject: dpll: add generic DPLL type Add DPLL_TYPE_GENERIC to represent DPLL devices which do not fit the existing PPS or EEC classes. The UAPI type is intentionally generic. During netdev discussion, maintainers pointed out that introducing identifiers tied to a specific placement or single design does not scale across ASICs and vendors. The role of a DPLL is already inferable from the spawning driver, bus device, and pin topology, without encoding additional purpose-specific taxonomy in the type name. Using a generic type keeps the UAPI extensible and avoids premature naming that may become incorrect as new hardware topologies are exposed through the DPLL subsystem. Expose the new type through UAPI and netlink specification as "generic". Reviewed-by: Aleksandr Loktionov Reviewed-by: Jiri Pirko Signed-off-by: Grzegorz Nitka Link: https://patch.msgid.link/20260607183045.1213735-2-grzegorz.nitka@intel.com Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/dpll.yaml | 3 +++ drivers/dpll/dpll_nl.c | 2 +- include/uapi/linux/dpll.h | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/Documentation/netlink/specs/dpll.yaml b/Documentation/netlink/specs/dpll.yaml index 91a172617b3a..2bf83f6732ab 100644 --- a/Documentation/netlink/specs/dpll.yaml +++ b/Documentation/netlink/specs/dpll.yaml @@ -138,6 +138,9 @@ definitions: - name: eec doc: dpll drives the Ethernet Equipment Clock + - + name: generic + doc: generic dpll type for devices outside PPS/EEC classes render-max: true - type: enum diff --git a/drivers/dpll/dpll_nl.c b/drivers/dpll/dpll_nl.c index b1d9182c7802..ed3bbe9841ea 100644 --- a/drivers/dpll/dpll_nl.c +++ b/drivers/dpll/dpll_nl.c @@ -37,7 +37,7 @@ const struct nla_policy dpll_reference_sync_nl_policy[DPLL_A_PIN_STATE + 1] = { static const struct nla_policy dpll_device_id_get_nl_policy[DPLL_A_TYPE + 1] = { [DPLL_A_MODULE_NAME] = { .type = NLA_NUL_STRING, }, [DPLL_A_CLOCK_ID] = { .type = NLA_U64, }, - [DPLL_A_TYPE] = NLA_POLICY_RANGE(NLA_U32, 1, 2), + [DPLL_A_TYPE] = NLA_POLICY_RANGE(NLA_U32, 1, 3), }; /* DPLL_CMD_DEVICE_GET - do */ diff --git a/include/uapi/linux/dpll.h b/include/uapi/linux/dpll.h index cb363cccf2e2..55eaa82f5f98 100644 --- a/include/uapi/linux/dpll.h +++ b/include/uapi/linux/dpll.h @@ -109,10 +109,12 @@ enum dpll_clock_quality_level { * enum dpll_type - type of dpll, valid values for DPLL_A_TYPE attribute * @DPLL_TYPE_PPS: dpll produces Pulse-Per-Second signal * @DPLL_TYPE_EEC: dpll drives the Ethernet Equipment Clock + * @DPLL_TYPE_GENERIC: generic dpll type for devices outside PPS/EEC classes */ enum dpll_type { DPLL_TYPE_PPS = 1, DPLL_TYPE_EEC, + DPLL_TYPE_GENERIC, /* private: */ __DPLL_TYPE_MAX, -- cgit v1.2.3 From 0bf47f722fa9e4ecdab7497afc1af64330540bed Mon Sep 17 00:00:00 2001 From: Grzegorz Nitka Date: Sun, 7 Jun 2026 20:30:40 +0200 Subject: dpll: extend pin notifier with notification source ID Extend the DPLL pin notification API to include a source identifier indicating where the notification originates. This allows notifier consumers to distinguish between notifications coming from an associated DPLL instance, a parent pin, or the pin itself. A new field, src_clock_id, is added to struct dpll_pin_notifier_info and is passed through all pin-related notification paths. Callers of dpll_pin_notify() are updated to provide a meaningful source identifier based on their context: - pin registration/unregistration uses the DPLL's clock_id, - pin-on-pin operations use the parent pin's clock_id, - pin changes use the pin's own clock_id. As introduced in the commit ("dpll: allow registering FW-identified pin with a different DPLL"), it is possible to share the same physical pin via firmware description (fwnode) with DPLL objects from different kernel modules. This means that a given pin can be registered multiple times. Driver such as ICE (E825 devices) rely on this mechanism when listening for the event where a shared-fwnode pin appears, while avoiding reacting to events triggered by their own registration logic. This change only extends the notification metadata and does not alter existing semantics for drivers that do not use the new field. Reviewed-by: Arkadiusz Kubalewski Reviewed-by: Aleksandr Loktionov Reviewed-by: Jiri Pirko Signed-off-by: Grzegorz Nitka Link: https://patch.msgid.link/20260607183045.1213735-9-grzegorz.nitka@intel.com Signed-off-by: Jakub Kicinski --- drivers/dpll/dpll_core.c | 14 ++++++++------ drivers/dpll/dpll_core.h | 3 ++- drivers/dpll/dpll_netlink.c | 10 +++++----- drivers/dpll/dpll_netlink.h | 4 ++-- include/linux/dpll.h | 1 + 5 files changed, 18 insertions(+), 14 deletions(-) (limited to 'include') diff --git a/drivers/dpll/dpll_core.c b/drivers/dpll/dpll_core.c index 1aaf62775408..2e8690cb3c16 100644 --- a/drivers/dpll/dpll_core.c +++ b/drivers/dpll/dpll_core.c @@ -72,7 +72,8 @@ void dpll_device_notify(struct dpll_device *dpll, unsigned long action) call_dpll_notifiers(action, &info); } -void dpll_pin_notify(struct dpll_pin *pin, unsigned long action) +void dpll_pin_notify(struct dpll_pin *pin, u64 src_clock_id, + unsigned long action) { struct dpll_pin_notifier_info info = { .pin = pin, @@ -81,6 +82,7 @@ void dpll_pin_notify(struct dpll_pin *pin, unsigned long action) .clock_id = pin->clock_id, .fwnode = pin->fwnode, .prop = &pin->prop, + .src_clock_id = src_clock_id, }; call_dpll_notifiers(action, &info); @@ -849,7 +851,7 @@ __dpll_pin_register(struct dpll_device *dpll, struct dpll_pin *pin, if (ret) goto ref_pin_del; xa_set_mark(&dpll_pin_xa, pin->id, DPLL_REGISTERED); - dpll_pin_create_ntf(pin); + dpll_pin_create_ntf(pin, dpll->clock_id); return ret; @@ -926,7 +928,7 @@ __dpll_pin_unregister(struct dpll_device *dpll, struct dpll_pin *pin, const struct dpll_pin_ops *ops, void *priv, void *cookie) { ASSERT_DPLL_PIN_REGISTERED(pin); - dpll_pin_delete_ntf(pin); + dpll_pin_delete_ntf(pin, dpll->clock_id); dpll_xa_ref_pin_del(&dpll->pin_refs, pin, ops, priv, cookie); dpll_xa_ref_dpll_del(&pin->dpll_refs, dpll, ops, priv, cookie); if (xa_empty(&pin->dpll_refs)) { @@ -999,7 +1001,7 @@ int dpll_pin_on_pin_register(struct dpll_pin *parent, struct dpll_pin *pin, stop = i; goto dpll_unregister; } - dpll_pin_create_ntf(pin); + dpll_pin_create_ntf(pin, parent->clock_id); } mutex_unlock(&dpll_lock); @@ -1008,7 +1010,7 @@ int dpll_pin_on_pin_register(struct dpll_pin *parent, struct dpll_pin *pin, dpll_unregister: xa_for_each(&parent->dpll_refs, i, ref) if (i < stop) { - dpll_pin_delete_ntf(pin); + dpll_pin_delete_ntf(pin, parent->clock_id); __dpll_pin_unregister(ref->dpll, pin, ops, priv, parent); } @@ -1041,7 +1043,7 @@ void dpll_pin_on_pin_unregister(struct dpll_pin *parent, struct dpll_pin *pin, reg = dpll_pin_registration_find(ref, ops, priv, parent); if (!reg) continue; - dpll_pin_delete_ntf(pin); + dpll_pin_delete_ntf(pin, parent->clock_id); __dpll_pin_unregister(ref->dpll, pin, ops, priv, parent); } dpll_xa_ref_pin_del(&pin->parent_refs, parent, ops, priv, pin); diff --git a/drivers/dpll/dpll_core.h b/drivers/dpll/dpll_core.h index 26d1537ada82..e24577113431 100644 --- a/drivers/dpll/dpll_core.h +++ b/drivers/dpll/dpll_core.h @@ -100,6 +100,7 @@ extern struct xarray dpll_pin_xa; extern struct mutex dpll_lock; void dpll_device_notify(struct dpll_device *dpll, unsigned long action); -void dpll_pin_notify(struct dpll_pin *pin, unsigned long action); +void dpll_pin_notify(struct dpll_pin *pin, u64 src_clock_id, + unsigned long action); #endif diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c index 6a23298244cc..8232d4543c7d 100644 --- a/drivers/dpll/dpll_netlink.c +++ b/drivers/dpll/dpll_netlink.c @@ -915,15 +915,15 @@ err_free_msg: return ret; } -int dpll_pin_create_ntf(struct dpll_pin *pin) +int dpll_pin_create_ntf(struct dpll_pin *pin, u64 src_clock_id) { - dpll_pin_notify(pin, DPLL_PIN_CREATED); + dpll_pin_notify(pin, src_clock_id, DPLL_PIN_CREATED); return dpll_pin_event_send(DPLL_CMD_PIN_CREATE_NTF, pin); } -int dpll_pin_delete_ntf(struct dpll_pin *pin) +int dpll_pin_delete_ntf(struct dpll_pin *pin, u64 src_clock_id) { - dpll_pin_notify(pin, DPLL_PIN_DELETED); + dpll_pin_notify(pin, src_clock_id, DPLL_PIN_DELETED); return dpll_pin_event_send(DPLL_CMD_PIN_DELETE_NTF, pin); } @@ -938,7 +938,7 @@ int dpll_pin_delete_ntf(struct dpll_pin *pin) int __dpll_pin_change_ntf(struct dpll_pin *pin) { lockdep_assert_held(&dpll_lock); - dpll_pin_notify(pin, DPLL_PIN_CHANGED); + dpll_pin_notify(pin, pin->clock_id, DPLL_PIN_CHANGED); return dpll_pin_event_send(DPLL_CMD_PIN_CHANGE_NTF, pin); } EXPORT_SYMBOL_GPL(__dpll_pin_change_ntf); diff --git a/drivers/dpll/dpll_netlink.h b/drivers/dpll/dpll_netlink.h index a9cfd55f57fc..4f63aa58789a 100644 --- a/drivers/dpll/dpll_netlink.h +++ b/drivers/dpll/dpll_netlink.h @@ -8,6 +8,6 @@ int dpll_device_create_ntf(struct dpll_device *dpll); int dpll_device_delete_ntf(struct dpll_device *dpll); -int dpll_pin_create_ntf(struct dpll_pin *pin); +int dpll_pin_create_ntf(struct dpll_pin *pin, u64 src_clock_id); -int dpll_pin_delete_ntf(struct dpll_pin *pin); +int dpll_pin_delete_ntf(struct dpll_pin *pin, u64 src_clock_id); diff --git a/include/linux/dpll.h b/include/linux/dpll.h index 03a538167691..7a6f8796cda2 100644 --- a/include/linux/dpll.h +++ b/include/linux/dpll.h @@ -233,6 +233,7 @@ struct dpll_pin_notifier_info { u64 clock_id; const struct fwnode_handle *fwnode; const struct dpll_pin_properties *prop; + u64 src_clock_id; }; #if IS_ENABLED(CONFIG_DPLL) -- cgit v1.2.3 From eb7b4d458e0d6833ffbb717edf4282f5ca6a7b57 Mon Sep 17 00:00:00 2001 From: Saeed Mahameed Date: Tue, 9 Jun 2026 09:34:48 +0530 Subject: devlink: Implement devlink param multi attribute nested data values Devlink param value attribute is not defined since devlink is handling the value validating and parsing internally, this allows us to implement multi attribute values without breaking any policies. Devlink param multi-attribute values are considered to be dynamically sized arrays of u64 values, by introducing a new devlink param type DEVLINK_PARAM_TYPE_U64_ARRAY, driver and user space can set a variable count of u64 values into the DEVLINK_ATTR_PARAM_VALUE_DATA attribute. Implement get/set parsing and add to the internal value structure passed to drivers. This is useful for devices that need to configure a list of values for a specific configuration. example: $ devlink dev param show pci/... name multi-value-param name multi-value-param type driver-specific values: cmode permanent value: 0,1,2,3,4,5,6,7 $ devlink dev param set pci/... name multi-value-param \ value 4,5,6,7,0,1,2,3 cmode permanent Signed-off-by: Saeed Mahameed Signed-off-by: Ratheesh Kannoth Link: https://patch.msgid.link/20260609040453.711932-5-rkannoth@marvell.com Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/devlink.yaml | 4 ++++ include/net/devlink.h | 8 ++++++++ include/uapi/linux/devlink.h | 1 + net/devlink/netlink_gen.c | 2 ++ net/devlink/param.c | 33 +++++++++++++++++++++++++++++++- 5 files changed, 47 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/Documentation/netlink/specs/devlink.yaml b/Documentation/netlink/specs/devlink.yaml index 247b147d689f..52ad1e7805d1 100644 --- a/Documentation/netlink/specs/devlink.yaml +++ b/Documentation/netlink/specs/devlink.yaml @@ -234,6 +234,10 @@ definitions: value: 10 - name: binary + - + name: u64-array + value: 129 + - name: rate-tc-index-max type: const diff --git a/include/net/devlink.h b/include/net/devlink.h index 5f4083dc4345..dd546dbd57cf 100644 --- a/include/net/devlink.h +++ b/include/net/devlink.h @@ -433,6 +433,13 @@ enum devlink_param_type { DEVLINK_PARAM_TYPE_U64 = DEVLINK_VAR_ATTR_TYPE_U64, DEVLINK_PARAM_TYPE_STRING = DEVLINK_VAR_ATTR_TYPE_STRING, DEVLINK_PARAM_TYPE_BOOL = DEVLINK_VAR_ATTR_TYPE_FLAG, + DEVLINK_PARAM_TYPE_U64_ARRAY = DEVLINK_VAR_ATTR_TYPE_U64_ARRAY, +}; + +#define __DEVLINK_PARAM_MAX_ARRAY_SIZE 32 +struct devlink_param_u64_array { + u64 size; + u64 val[__DEVLINK_PARAM_MAX_ARRAY_SIZE]; }; union devlink_param_value { @@ -442,6 +449,7 @@ union devlink_param_value { u64 vu64; char vstr[__DEVLINK_PARAM_MAX_STRING_VALUE]; bool vbool; + struct devlink_param_u64_array u64arr; }; struct devlink_param_gset_ctx { diff --git a/include/uapi/linux/devlink.h b/include/uapi/linux/devlink.h index 0b165eac7619..ca713bcc47b9 100644 --- a/include/uapi/linux/devlink.h +++ b/include/uapi/linux/devlink.h @@ -406,6 +406,7 @@ enum devlink_var_attr_type { DEVLINK_VAR_ATTR_TYPE_BINARY, __DEVLINK_VAR_ATTR_TYPE_CUSTOM_BASE = 0x80, /* Any possible custom types, unrelated to NLA_* values go below */ + DEVLINK_VAR_ATTR_TYPE_U64_ARRAY, }; enum devlink_attr { diff --git a/net/devlink/netlink_gen.c b/net/devlink/netlink_gen.c index 81899786fd98..f52b0c2b19ed 100644 --- a/net/devlink/netlink_gen.c +++ b/net/devlink/netlink_gen.c @@ -37,6 +37,8 @@ devlink_attr_param_type_validate(const struct nlattr *attr, case DEVLINK_VAR_ATTR_TYPE_NUL_STRING: fallthrough; case DEVLINK_VAR_ATTR_TYPE_BINARY: + fallthrough; + case DEVLINK_VAR_ATTR_TYPE_U64_ARRAY: return 0; } NL_SET_ERR_MSG_ATTR(extack, attr, "invalid enum value"); diff --git a/net/devlink/param.c b/net/devlink/param.c index bd3881349c60..3e9d2e5750c2 100644 --- a/net/devlink/param.c +++ b/net/devlink/param.c @@ -252,6 +252,15 @@ devlink_nl_param_value_put(struct sk_buff *msg, enum devlink_param_type type, return -EMSGSIZE; } break; + case DEVLINK_PARAM_TYPE_U64_ARRAY: + if (val->u64arr.size > __DEVLINK_PARAM_MAX_ARRAY_SIZE) + return -EMSGSIZE; + + for (int i = 0; i < val->u64arr.size; i++) { + if (nla_put_uint(msg, nla_type, val->u64arr.val[i])) + return -EMSGSIZE; + } + break; } return 0; } @@ -537,7 +546,7 @@ devlink_param_value_get_from_info(const struct devlink_param *param, union devlink_param_value *value) { struct nlattr *param_data; - int len; + int len, cnt, rem; param_data = info->attrs[DEVLINK_ATTR_PARAM_VALUE_DATA]; @@ -577,6 +586,28 @@ devlink_param_value_get_from_info(const struct devlink_param *param, return -EINVAL; value->vbool = nla_get_flag(param_data); break; + + case DEVLINK_PARAM_TYPE_U64_ARRAY: + cnt = 0; + nla_for_each_attr_type(param_data, + DEVLINK_ATTR_PARAM_VALUE_DATA, + genlmsg_data(info->genlhdr), + genlmsg_len(info->genlhdr), rem) { + if (cnt >= __DEVLINK_PARAM_MAX_ARRAY_SIZE) + return -EMSGSIZE; + + if ((nla_len(param_data) != sizeof(u64)) && + (nla_len(param_data) != sizeof(u32))) { + NL_SET_BAD_ATTR(info->extack, param_data); + return -EINVAL; + } + + value->u64arr.val[cnt] = nla_get_uint(param_data); + cnt++; + } + + value->u64arr.size = cnt; + break; } return 0; } -- cgit v1.2.3 From e3cd138e560764299965fba5ec5240281a7faca2 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Mon, 8 Jun 2026 18:14:36 +0200 Subject: netfilter: conntrack: check NULL when retrieving ct extension nf_ct_ext_find() might return NULL if ct extension is not found. Add also the null checks to: - nfct_help() - nfct_help_data() - nfct_seqadj() - nfct_nat() This is defensive, for safety reasons. nf_ct_ext_find() used to return NULL if the extension is stale for unconfirmed conntracks if the genid validation fails. Skip NULL check in nf_nat_inet_fn() given this is valid to be NULL for non-initialized ct nat extensions. While at it, fetch ct helper area in nf_ct_expect_related_report() only once and pass it on to other ancilliary functions. Replace WARN_ON() by WARN_ON_ONCE() in nf_ct_unlink_expect_report(). Signed-off-by: Pablo Neira Ayuso --- include/net/netfilter/nf_conntrack_helper.h | 2 ++ net/ipv4/netfilter/nf_nat_h323.c | 12 +++++++++ net/ipv4/netfilter/nf_nat_pptp.c | 14 +++++++--- net/netfilter/nf_conntrack_broadcast.c | 3 +++ net/netfilter/nf_conntrack_expect.c | 33 ++++++++++++----------- net/netfilter/nf_conntrack_ftp.c | 6 +++++ net/netfilter/nf_conntrack_h323_main.c | 18 +++++++++++++ net/netfilter/nf_conntrack_pptp.c | 9 +++++++ net/netfilter/nf_conntrack_proto_gre.c | 9 +++++++ net/netfilter/nf_conntrack_sane.c | 3 +++ net/netfilter/nf_conntrack_seqadj.c | 17 ++++++++---- net/netfilter/nf_conntrack_sip.c | 41 +++++++++++++++++++++++++++-- net/netfilter/nf_nat_sip.c | 12 +++++++++ net/netfilter/nfnetlink_cthelper.c | 6 +++++ 14 files changed, 158 insertions(+), 27 deletions(-) (limited to 'include') diff --git a/include/net/netfilter/nf_conntrack_helper.h b/include/net/netfilter/nf_conntrack_helper.h index ed93a5a1adc8..93207de4f2c8 100644 --- a/include/net/netfilter/nf_conntrack_helper.h +++ b/include/net/netfilter/nf_conntrack_helper.h @@ -136,6 +136,8 @@ static inline void *nfct_help_data(const struct nf_conn *ct) struct nf_conn_help *help; help = nf_ct_ext_find(ct, NF_CT_EXT_HELPER); + if (!help) + return NULL; return (void *)help->data; } diff --git a/net/ipv4/netfilter/nf_nat_h323.c b/net/ipv4/netfilter/nf_nat_h323.c index faee20af4856..19dad54ada09 100644 --- a/net/ipv4/netfilter/nf_nat_h323.c +++ b/net/ipv4/netfilter/nf_nat_h323.c @@ -100,6 +100,9 @@ static int set_sig_addr(struct sk_buff *skb, struct nf_conn *ct, __be16 port; union nf_inet_addr addr; + if (!info) + return -1; + for (i = 0; i < count; i++) { if (get_h225_addr(ct, *data, &taddr[i], &addr, &port)) { if (addr.ip == ct->tuplehash[dir].tuple.src.u3.ip && @@ -184,6 +187,9 @@ static int nat_rtp_rtcp(struct sk_buff *skb, struct nf_conn *ct, int i; u_int16_t nated_port; + if (!info) + return -1; + /* Set expectations for NAT */ rtp_exp->saved_proto.udp.port = rtp_exp->tuple.dst.u.udp.port; rtp_exp->expectfn = nf_nat_follow_master; @@ -325,6 +331,9 @@ static int nat_h245(struct sk_buff *skb, struct nf_conn *ct, int dir = CTINFO2DIR(ctinfo); u_int16_t nated_port = ntohs(port); + if (!info) + return -1; + /* Set expectations for NAT */ exp->saved_proto.tcp.port = exp->tuple.dst.u.tcp.port; exp->expectfn = nf_nat_follow_master; @@ -404,6 +413,9 @@ static int nat_q931(struct sk_buff *skb, struct nf_conn *ct, u_int16_t nated_port = ntohs(port); union nf_inet_addr addr; + if (!info) + return -1; + /* Set expectations for NAT */ exp->saved_proto.tcp.port = exp->tuple.dst.u.tcp.port; exp->expectfn = ip_nat_q931_expect; diff --git a/net/ipv4/netfilter/nf_nat_pptp.c b/net/ipv4/netfilter/nf_nat_pptp.c index fab357cc8559..fed5249001a4 100644 --- a/net/ipv4/netfilter/nf_nat_pptp.c +++ b/net/ipv4/netfilter/nf_nat_pptp.c @@ -53,11 +53,13 @@ static void pptp_nat_expected(struct nf_conn *ct, struct nf_conn_nat *nat; nat = nf_ct_nat_ext_add(ct); - if (WARN_ON_ONCE(!nat)) + if (!nat) return; nat_pptp_info = &nat->help.nat_pptp_info; ct_pptp_info = nfct_help_data(master); + if (!ct_pptp_info) + return; /* And here goes the grand finale of corrosion... */ if (exp->dir == IP_CT_DIR_ORIGINAL) { @@ -132,11 +134,13 @@ pptp_outbound_pkt(struct sk_buff *skb, __be16 new_callid; unsigned int cid_off; - if (WARN_ON_ONCE(!nat)) + if (!nat) return NF_DROP; nat_pptp_info = &nat->help.nat_pptp_info; ct_pptp_info = nfct_help_data(ct); + if (!ct_pptp_info) + return NF_DROP; new_callid = ct_pptp_info->pns_call_id; @@ -204,11 +208,13 @@ pptp_exp_gre(struct nf_conntrack_expect *expect_orig, struct nf_ct_pptp_master *ct_pptp_info; struct nf_nat_pptp *nat_pptp_info; - if (WARN_ON_ONCE(!nat)) + if (!nat) return; nat_pptp_info = &nat->help.nat_pptp_info; ct_pptp_info = nfct_help_data(ct); + if (!ct_pptp_info) + return; /* save original PAC call ID in nat_info */ nat_pptp_info->pac_call_id = ct_pptp_info->pac_call_id; @@ -241,7 +247,7 @@ pptp_inbound_pkt(struct sk_buff *skb, __be16 new_pcid; unsigned int pcid_off; - if (WARN_ON_ONCE(!nat)) + if (!nat) return NF_DROP; nat_pptp_info = &nat->help.nat_pptp_info; diff --git a/net/netfilter/nf_conntrack_broadcast.c b/net/netfilter/nf_conntrack_broadcast.c index 75e53fde6b29..400119b6320e 100644 --- a/net/netfilter/nf_conntrack_broadcast.c +++ b/net/netfilter/nf_conntrack_broadcast.c @@ -29,6 +29,9 @@ int nf_conntrack_broadcast_help(struct sk_buff *skb, struct nf_conn_help *help = nfct_help(ct); __be32 mask = 0; + if (!help) + goto out; + /* we're only interested in locally generated packets */ if (skb->sk == NULL || !net_eq(nf_ct_net(ct), sock_net(skb->sk))) goto out; diff --git a/net/netfilter/nf_conntrack_expect.c b/net/netfilter/nf_conntrack_expect.c index 8e943efbdf0a..5c9b17835c28 100644 --- a/net/netfilter/nf_conntrack_expect.c +++ b/net/netfilter/nf_conntrack_expect.c @@ -52,8 +52,7 @@ void nf_ct_unlink_expect_report(struct nf_conntrack_expect *exp, struct nf_conntrack_net *cnet; lockdep_nfct_expect_lock_held(); - WARN_ON(!master_help); - WARN_ON(timer_pending(&exp->timeout)); + WARN_ON_ONCE(timer_pending(&exp->timeout)); hlist_del_rcu(&exp->hnode); @@ -61,7 +60,8 @@ void nf_ct_unlink_expect_report(struct nf_conntrack_expect *exp, cnet->expect_count--; hlist_del_rcu(&exp->lnode); - master_help->expecting[exp->class]--; + if (master_help) + master_help->expecting[exp->class]--; nf_ct_expect_event_report(IPEXP_DESTROY, exp, portid, report); nf_ct_expect_put(exp); @@ -405,10 +405,10 @@ void nf_ct_expect_put(struct nf_conntrack_expect *exp) } EXPORT_SYMBOL_GPL(nf_ct_expect_put); -static void nf_ct_expect_insert(struct nf_conntrack_expect *exp) +static void nf_ct_expect_insert(struct nf_conntrack_expect *exp, + struct nf_conn_help *master_help) { struct nf_conntrack_net *cnet; - struct nf_conn_help *master_help = nfct_help(exp->master); struct nf_conntrack_helper *helper; struct net *net = nf_ct_exp_net(exp); unsigned int h = nf_ct_expect_dst_hash(net, &exp->tuple); @@ -436,10 +436,9 @@ static void nf_ct_expect_insert(struct nf_conntrack_expect *exp) } /* Race with expectations being used means we could have none to find; OK. */ -static void evict_oldest_expect(struct nf_conn *master, +static void evict_oldest_expect(struct nf_conn_help *master_help, struct nf_conntrack_expect *new) { - struct nf_conn_help *master_help = nfct_help(master); struct nf_conntrack_expect *exp, *last = NULL; hlist_for_each_entry(exp, &master_help->expectations, lnode) { @@ -452,13 +451,12 @@ static void evict_oldest_expect(struct nf_conn *master, } static inline int __nf_ct_expect_check(struct nf_conntrack_expect *expect, + struct nf_conn_help *master_help, unsigned int flags) { const struct nf_conntrack_expect_policy *p; struct nf_conntrack_expect *i; struct nf_conntrack_net *cnet; - struct nf_conn *master = expect->master; - struct nf_conn_help *master_help = nfct_help(master); struct nf_conntrack_helper *helper; struct net *net = nf_ct_exp_net(expect); struct hlist_node *next; @@ -467,10 +465,6 @@ static inline int __nf_ct_expect_check(struct nf_conntrack_expect *expect, lockdep_nfct_expect_lock_held(); - if (!master_help) { - ret = -ESHUTDOWN; - goto out; - } h = nf_ct_expect_dst_hash(net, &expect->tuple); hlist_for_each_entry_safe(i, next, &nf_ct_expect_hash[h], hnode) { if (master_matches(i, expect, flags) && @@ -493,7 +487,7 @@ static inline int __nf_ct_expect_check(struct nf_conntrack_expect *expect, p = &helper->expect_policy[expect->class]; if (p->max_expected && master_help->expecting[expect->class] >= p->max_expected) { - evict_oldest_expect(master, expect); + evict_oldest_expect(master_help, expect); if (master_help->expecting[expect->class] >= p->max_expected) { ret = -EMFILE; @@ -514,14 +508,21 @@ out: int nf_ct_expect_related_report(struct nf_conntrack_expect *expect, u32 portid, int report, unsigned int flags) { + struct nf_conn_help *master_help; int ret; spin_lock_bh(&nf_conntrack_expect_lock); - ret = __nf_ct_expect_check(expect, flags); + master_help = nfct_help(expect->master); + if (!master_help) { + ret = -ESHUTDOWN; + goto out; + } + + ret = __nf_ct_expect_check(expect, master_help, flags); if (ret < 0) goto out; - nf_ct_expect_insert(expect); + nf_ct_expect_insert(expect, master_help); nf_ct_expect_event_report(IPEXP_NEW, expect, portid, report); spin_unlock_bh(&nf_conntrack_expect_lock); diff --git a/net/netfilter/nf_conntrack_ftp.c b/net/netfilter/nf_conntrack_ftp.c index c7777f37371a..0847f845613d 100644 --- a/net/netfilter/nf_conntrack_ftp.c +++ b/net/netfilter/nf_conntrack_ftp.c @@ -384,6 +384,9 @@ static int help(struct sk_buff *skb, int found = 0, ends_in_nl; nf_nat_ftp_hook_fn *nf_nat_ftp; + if (!ct_ftp_info) + return NF_DROP; + /* Until there's been traffic both ways, don't look in packets. */ if (ctinfo != IP_CT_ESTABLISHED && ctinfo != IP_CT_ESTABLISHED_REPLY) { @@ -545,6 +548,9 @@ static int nf_ct_ftp_from_nlattr(struct nlattr *attr, struct nf_conn *ct) { struct nf_ct_ftp_master *ftp = nfct_help_data(ct); + if (!ftp) + return -ENOENT; + /* This conntrack has been injected from user-space, always pick up * sequence tracking. Otherwise, the first FTP command after the * failover breaks. diff --git a/net/netfilter/nf_conntrack_h323_main.c b/net/netfilter/nf_conntrack_h323_main.c index ebae9fdab897..7f189dceb3c4 100644 --- a/net/netfilter/nf_conntrack_h323_main.c +++ b/net/netfilter/nf_conntrack_h323_main.c @@ -76,6 +76,9 @@ static int get_tpkt_data(struct sk_buff *skb, unsigned int protoff, int tpktlen; int tpktoff; + if (!info) + return 0; + /* Get TCP header */ th = skb_header_pointer(skb, protoff, sizeof(_tcph), &_tcph); if (th == NULL) @@ -1191,6 +1194,9 @@ static int expect_q931(struct sk_buff *skb, struct nf_conn *ct, union nf_inet_addr addr; struct nf_conntrack_expect *exp; + if (!info) + return -1; + /* Look for the first related address */ for (i = 0; i < count; i++) { if (get_h225_addr(ct, *data, &taddr[i], &addr, &port) && @@ -1307,6 +1313,9 @@ static int process_rrq(struct sk_buff *skb, struct nf_conn *ct, const struct nfct_h323_nat_hooks *nathook; int ret; + if (!info) + return -1; + pr_debug("nf_ct_ras: RRQ\n"); ret = expect_q931(skb, ct, ctinfo, protoff, data, @@ -1345,6 +1354,9 @@ static int process_rcf(struct sk_buff *skb, struct nf_conn *ct, int ret; struct nf_conntrack_expect *exp; + if (!info) + return -1; + pr_debug("nf_ct_ras: RCF\n"); nathook = rcu_dereference(nfct_h323_nat_hook); @@ -1395,6 +1407,9 @@ static int process_urq(struct sk_buff *skb, struct nf_conn *ct, int dir = CTINFO2DIR(ctinfo); int ret; + if (!info) + return -1; + pr_debug("nf_ct_ras: URQ\n"); nathook = rcu_dereference(nfct_h323_nat_hook); @@ -1429,6 +1444,9 @@ static int process_arq(struct sk_buff *skb, struct nf_conn *ct, __be16 port; union nf_inet_addr addr; + if (!info) + return 0; + pr_debug("nf_ct_ras: ARQ\n"); nathook = rcu_dereference(nfct_h323_nat_hook); diff --git a/net/netfilter/nf_conntrack_pptp.c b/net/netfilter/nf_conntrack_pptp.c index ed567a1cf7fd..776505a78e64 100644 --- a/net/netfilter/nf_conntrack_pptp.c +++ b/net/netfilter/nf_conntrack_pptp.c @@ -198,6 +198,9 @@ pptp_inbound_pkt(struct sk_buff *skb, unsigned int protoff, u_int16_t msg; __be16 cid = 0, pcid = 0; + if (!info) + return NF_DROP; + msg = ntohs(ctlh->messageType); pr_debug("inbound control message %s\n", pptp_msg_name(msg)); @@ -325,6 +328,9 @@ pptp_outbound_pkt(struct sk_buff *skb, unsigned int protoff, u_int16_t msg; __be16 cid = 0, pcid = 0; + if (!info) + return NF_DROP; + msg = ntohs(ctlh->messageType); pr_debug("outbound control message %s\n", pptp_msg_name(msg)); @@ -443,6 +449,9 @@ conntrack_pptp_help(struct sk_buff *skb, unsigned int protoff, int ret; u_int16_t msg; + if (!info) + return NF_DROP; + #if IS_ENABLED(CONFIG_NF_NAT) if (!nf_ct_is_confirmed(ct) && (ct->status & IPS_NAT_MASK)) { struct nf_conn_nat *nat = nf_ct_ext_find(ct, NF_CT_EXT_NAT); diff --git a/net/netfilter/nf_conntrack_proto_gre.c b/net/netfilter/nf_conntrack_proto_gre.c index 473658259f1a..616ab1e2fc5e 100644 --- a/net/netfilter/nf_conntrack_proto_gre.c +++ b/net/netfilter/nf_conntrack_proto_gre.c @@ -131,6 +131,9 @@ bool nf_ct_gre_keymap_add(struct nf_conn *ct, struct nf_ct_gre_keymap *km_orig, *km_repl; bool ret = false; + if (!ct_pptp_info) + return false; + km_orig = kmalloc_obj(*km_orig, GFP_ATOMIC); if (!km_orig) return false; @@ -187,6 +190,9 @@ void nf_ct_gre_keymap_destroy(struct nf_conn *ct) struct nf_ct_pptp_master *ct_pptp_info = nfct_help_data(ct); enum ip_conntrack_dir dir; + if (!ct_pptp_info) + return; + pr_debug("entering for ct %p\n", ct); spin_lock_bh(&keymap_lock); @@ -389,6 +395,9 @@ void gre_pptp_destroy_siblings(struct nf_conn *ct) const struct nf_ct_pptp_master *ct_pptp_info = nfct_help_data(ct); struct nf_conntrack_tuple t; + if (!ct_pptp_info) + return; + nf_ct_gre_keymap_destroy(ct); /* try original (pns->pac) tuple */ diff --git a/net/netfilter/nf_conntrack_sane.c b/net/netfilter/nf_conntrack_sane.c index a7f7b07ba0c2..39085acf7a71 100644 --- a/net/netfilter/nf_conntrack_sane.c +++ b/net/netfilter/nf_conntrack_sane.c @@ -74,6 +74,9 @@ static int help(struct sk_buff *skb, struct sane_reply_net_start repl; } buf; + if (!ct_sane_info) + return NF_DROP; + /* Until there's been traffic both ways, don't look in packets. */ if (ctinfo != IP_CT_ESTABLISHED && ctinfo != IP_CT_ESTABLISHED_REPLY) diff --git a/net/netfilter/nf_conntrack_seqadj.c b/net/netfilter/nf_conntrack_seqadj.c index b7e99f34dfce..220216a4edc5 100644 --- a/net/netfilter/nf_conntrack_seqadj.c +++ b/net/netfilter/nf_conntrack_seqadj.c @@ -18,9 +18,12 @@ int nf_ct_seqadj_init(struct nf_conn *ct, enum ip_conntrack_info ctinfo, return 0; spin_lock_bh(&ct->lock); - set_bit(IPS_SEQ_ADJUST_BIT, &ct->status); - seqadj = nfct_seqadj(ct); + if (!seqadj) { + spin_unlock_bh(&ct->lock); + return 0; + } + set_bit(IPS_SEQ_ADJUST_BIT, &ct->status); this_way = &seqadj->seq[dir]; this_way->offset_before = off; this_way->offset_after = off; @@ -39,10 +42,8 @@ int nf_ct_seqadj_set(struct nf_conn *ct, enum ip_conntrack_info ctinfo, if (off == 0) return 0; - if (unlikely(!seqadj)) { - WARN_ONCE(1, "Missing nfct_seqadj_ext_add() setup call\n"); + if (unlikely(!seqadj)) return 0; - } set_bit(IPS_SEQ_ADJUST_BIT, &ct->status); @@ -125,6 +126,9 @@ static unsigned int nf_ct_sack_adjust(struct sk_buff *skb, struct nf_conn_seqadj *seqadj = nfct_seqadj(ct); unsigned int dir, optoff, optend; + if (!seqadj) + return 0; + optoff = protoff + sizeof(struct tcphdr); optend = protoff + tcph->doff * 4; @@ -175,6 +179,9 @@ int nf_ct_seq_adjust(struct sk_buff *skb, struct nf_ct_seqadj *this_way, *other_way; int res = 1; + if (!seqadj) + return 0; + this_way = &seqadj->seq[dir]; other_way = &seqadj->seq[!dir]; diff --git a/net/netfilter/nf_conntrack_sip.c b/net/netfilter/nf_conntrack_sip.c index 2c78a3e1dab5..c606d1f60b58 100644 --- a/net/netfilter/nf_conntrack_sip.c +++ b/net/netfilter/nf_conntrack_sip.c @@ -887,6 +887,9 @@ static int refresh_signalling_expectation(struct nf_conn *ct, struct hlist_node *next; int found = 0; + if (!help) + return 0; + spin_lock_bh(&nf_conntrack_expect_lock); hlist_for_each_entry_safe(exp, next, &help->expectations, lnode) { if (exp->class != SIP_EXPECT_SIGNALLING || @@ -910,6 +913,9 @@ static void flush_expectations(struct nf_conn *ct, bool media) struct nf_conntrack_expect *exp; struct hlist_node *next; + if (!help) + return; + spin_lock_bh(&nf_conntrack_expect_lock); hlist_for_each_entry_safe(exp, next, &help->expectations, lnode) { if ((exp->class != SIP_EXPECT_SIGNALLING) ^ media) @@ -940,6 +946,11 @@ static int set_expected_rtp_rtcp(struct sk_buff *skb, unsigned int protoff, u_int16_t base_port; __be16 rtp_port, rtcp_port; const struct nf_nat_sip_hooks *hooks; + struct nf_conn_help *help; + + help = nfct_help(ct); + if (!help) + return NF_DROP; saddr = NULL; if (sip_direct_media) { @@ -1002,7 +1013,7 @@ static int set_expected_rtp_rtcp(struct sk_buff *skb, unsigned int protoff, exp = __nf_ct_expect_find(net, nf_ct_zone(ct), &tuple); if (!exp || exp->master == ct || - exp->helper != nfct_help(ct)->helper || + exp->helper != help->helper || exp->class != class) break; #if IS_ENABLED(CONFIG_NF_NAT) @@ -1227,6 +1238,9 @@ static int process_invite_response(struct sk_buff *skb, unsigned int protoff, struct nf_conn *ct = nf_ct_get(skb, &ctinfo); struct nf_ct_sip_master *ct_sip_info = nfct_help_data(ct); + if (!ct_sip_info) + return NF_DROP; + if ((code >= 100 && code <= 199) || (code >= 200 && code <= 299)) return process_sdp(skb, protoff, dataoff, dptr, datalen, cseq); @@ -1244,6 +1258,9 @@ static int process_update_response(struct sk_buff *skb, unsigned int protoff, struct nf_conn *ct = nf_ct_get(skb, &ctinfo); struct nf_ct_sip_master *ct_sip_info = nfct_help_data(ct); + if (!ct_sip_info) + return NF_DROP; + if ((code >= 100 && code <= 199) || (code >= 200 && code <= 299)) return process_sdp(skb, protoff, dataoff, dptr, datalen, cseq); @@ -1261,6 +1278,9 @@ static int process_prack_response(struct sk_buff *skb, unsigned int protoff, struct nf_conn *ct = nf_ct_get(skb, &ctinfo); struct nf_ct_sip_master *ct_sip_info = nfct_help_data(ct); + if (!ct_sip_info) + return NF_DROP; + if ((code >= 100 && code <= 199) || (code >= 200 && code <= 299)) return process_sdp(skb, protoff, dataoff, dptr, datalen, cseq); @@ -1279,6 +1299,9 @@ static int process_invite_request(struct sk_buff *skb, unsigned int protoff, struct nf_ct_sip_master *ct_sip_info = nfct_help_data(ct); unsigned int ret; + if (!ct_sip_info) + return NF_DROP; + flush_expectations(ct, true); ret = process_sdp(skb, protoff, dataoff, dptr, datalen, cseq); if (ret == NF_ACCEPT) @@ -1316,11 +1339,15 @@ static int process_register_request(struct sk_buff *skb, unsigned int protoff, union nf_inet_addr *saddr, daddr; const struct nf_nat_sip_hooks *hooks; struct nf_conntrack_helper *helper; + struct nf_conn_help *help; __be16 port; u8 proto; unsigned int expires = 0; int ret; + if (!ct_sip_info) + return NF_DROP; + /* Expected connections can not register again. */ if (ct->status & IPS_EXPECTED) return NF_ACCEPT; @@ -1366,7 +1393,11 @@ static int process_register_request(struct sk_buff *skb, unsigned int protoff, goto store_cseq; } - helper = rcu_dereference(nfct_help(ct)->helper); + help = nfct_help(ct); + if (!help) + return NF_DROP; + + helper = rcu_dereference(help->helper); if (!helper) return NF_DROP; @@ -1421,6 +1452,9 @@ static int process_register_response(struct sk_buff *skb, unsigned int protoff, unsigned int expires = 0; int in_contact = 0, ret; + if (!ct_sip_info) + return NF_DROP; + /* According to RFC 3261, "UAs MUST NOT send a new registration until * they have received a final response from the registrar for the * previous one or the previous REGISTER request has timed out". @@ -1550,6 +1584,9 @@ static int process_sip_request(struct sk_buff *skb, unsigned int protoff, union nf_inet_addr addr; __be16 port; + if (!ct_sip_info) + return NF_DROP; + /* Many Cisco IP phones use a high source port for SIP requests, but * listen for the response on port 5060. If we are the local * router for one of these phones, save the port number from the diff --git a/net/netfilter/nf_nat_sip.c b/net/netfilter/nf_nat_sip.c index 9fbfc6bff0c2..7f29a6785327 100644 --- a/net/netfilter/nf_nat_sip.c +++ b/net/netfilter/nf_nat_sip.c @@ -106,6 +106,9 @@ static int map_addr(struct sk_buff *skb, unsigned int protoff, union nf_inet_addr newaddr; __be16 newport; + if (!ct_sip_info) + return 0; + if (nf_inet_addr_cmp(&ct->tuplehash[dir].tuple.src.u3, addr) && ct->tuplehash[dir].tuple.src.u.udp.port == port) { newaddr = ct->tuplehash[!dir].tuple.dst.u3; @@ -158,6 +161,9 @@ static unsigned int nf_nat_sip(struct sk_buff *skb, unsigned int protoff, __be16 port; int request, in_header; + if (!ct_sip_info) + return NF_DROP; + /* Basic rules: requests and responses. */ if (strncasecmp(*dptr, "SIP/2.0", strlen("SIP/2.0")) != 0) { if (ct_sip_parse_request(ct, *dptr, *datalen, @@ -326,6 +332,9 @@ static void nf_nat_sip_expected(struct nf_conn *ct, int range_set_for_snat = 0; struct nf_nat_range2 range; + if (!help) + return; + /* This must be a fresh one. */ BUG_ON(ct->status & IPS_NAT_DONE_MASK); @@ -390,6 +399,9 @@ static unsigned int nf_nat_sip_expect(struct sk_buff *skb, unsigned int protoff, char buffer[INET6_ADDRSTRLEN + sizeof("[]:nnnnn")]; unsigned int buflen; + if (!ct_sip_info) + return NF_DROP; + /* Connection will come from reply */ if (nf_inet_addr_cmp(&ct->tuplehash[dir].tuple.src.u3, &ct->tuplehash[!dir].tuple.dst.u3)) diff --git a/net/netfilter/nfnetlink_cthelper.c b/net/netfilter/nfnetlink_cthelper.c index 033ea90c4401..f1460b683d7a 100644 --- a/net/netfilter/nfnetlink_cthelper.c +++ b/net/netfilter/nfnetlink_cthelper.c @@ -101,6 +101,9 @@ nfnl_cthelper_from_nlattr(struct nlattr *attr, struct nf_conn *ct) struct nf_conn_help *help = nfct_help(ct); const struct nf_conntrack_helper *helper; + if (!help) + return -EINVAL; + if (attr == NULL) return -EINVAL; @@ -118,6 +121,9 @@ nfnl_cthelper_to_nlattr(struct sk_buff *skb, const struct nf_conn *ct) const struct nf_conn_help *help = nfct_help(ct); const struct nf_conntrack_helper *helper; + if (!help) + return 0; + helper = rcu_dereference(help->helper); if (helper && helper->data_len && nla_put(skb, CTA_HELP_INFO, helper->data_len, &help->data)) -- cgit v1.2.3 From 2354e975932dabb06fad239f07a3b68fd1809737 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Wed, 10 Jun 2026 00:03:19 +0200 Subject: netfilter: nf_dup_netdev: add nf_dev_xmit_recursion*() helpers and use them Update nft_dup and nft_fwd to use the nf_dev_xmit_recursion() helpers. This patch also disables BH when transmitting the skb to address a possible migration to different CPU leading to imbalanced decrementation of the recursion counters. This is modeled after Florian Westphal's dev_xmit_recursion*() API available since commit 97cdcf37b57e ("net: place xmit recursion in softnet data") according to its current state in the tree. Fixes: 1d47b55b36d2 ("netfilter: nft_fwd_netdev: use recursion counter in neigh egress path") Fixes: f37ad9127039 ("netfilter: nf_dup_netdev: Move the recursion counter struct netdev_xmit") Signed-off-by: Pablo Neira Ayuso --- include/net/netfilter/nf_dup_netdev.h | 34 +++++++++++++++++++++++++++++----- net/netfilter/nf_dup_netdev.c | 15 ++++++++------- net/netfilter/nft_fwd_netdev.c | 17 ++++++++++------- 3 files changed, 47 insertions(+), 19 deletions(-) (limited to 'include') diff --git a/include/net/netfilter/nf_dup_netdev.h b/include/net/netfilter/nf_dup_netdev.h index 609bcf422a9b..f6b05bd80c3f 100644 --- a/include/net/netfilter/nf_dup_netdev.h +++ b/include/net/netfilter/nf_dup_netdev.h @@ -11,15 +11,39 @@ void nf_fwd_netdev_egress(const struct nft_pktinfo *pkt, int oif); #define NF_RECURSION_LIMIT 2 -static inline u8 *nf_get_nf_dup_skb_recursion(void) -{ #ifndef CONFIG_PREEMPT_RT - return this_cpu_ptr(&softnet_data.xmit.nf_dup_skb_recursion); +static inline bool nf_dev_xmit_recursion(void) +{ + return unlikely(__this_cpu_read(softnet_data.xmit.nf_dup_skb_recursion) > + NF_RECURSION_LIMIT); +} + +static inline void nf_dev_xmit_recursion_inc(void) +{ + __this_cpu_inc(softnet_data.xmit.nf_dup_skb_recursion); +} + +static inline void nf_dev_xmit_recursion_dec(void) +{ + __this_cpu_dec(softnet_data.xmit.nf_dup_skb_recursion); +} #else - return ¤t->net_xmit.nf_dup_skb_recursion; -#endif +static inline bool nf_dev_xmit_recursion(void) +{ + return unlikely(current->net_xmit.nf_dup_skb_recursion > NF_RECURSION_LIMIT); +} + +static inline void nf_dev_xmit_recursion_inc(void) +{ + current->net_xmit.nf_dup_skb_recursion++; } +static inline void nf_dev_xmit_recursion_dec(void) +{ + current->net_xmit.nf_dup_skb_recursion--; +} +#endif + struct nft_offload_ctx; struct nft_flow_rule; diff --git a/net/netfilter/nf_dup_netdev.c b/net/netfilter/nf_dup_netdev.c index 3b0a70e154cd..c189716e986a 100644 --- a/net/netfilter/nf_dup_netdev.c +++ b/net/netfilter/nf_dup_netdev.c @@ -16,11 +16,6 @@ static void nf_do_netdev_egress(struct sk_buff *skb, struct net_device *dev, enum nf_dev_hooks hook) { - u8 *nf_dup_skb_recursion = nf_get_nf_dup_skb_recursion(); - - if (*nf_dup_skb_recursion > NF_RECURSION_LIMIT) - goto err; - if (hook == NF_NETDEV_INGRESS && skb_mac_header_was_set(skb)) { if (skb_cow_head(skb, skb->mac_len)) goto err; @@ -30,9 +25,15 @@ static void nf_do_netdev_egress(struct sk_buff *skb, struct net_device *dev, skb->dev = dev; skb_clear_tstamp(skb); - (*nf_dup_skb_recursion)++; + local_bh_disable(); + if (nf_dev_xmit_recursion()) { + local_bh_enable(); + goto err; + } + nf_dev_xmit_recursion_inc(); dev_queue_xmit(skb); - (*nf_dup_skb_recursion)--; + nf_dev_xmit_recursion_dec(); + local_bh_enable(); return; err: kfree_skb(skb); diff --git a/net/netfilter/nft_fwd_netdev.c b/net/netfilter/nft_fwd_netdev.c index b9e88d7cf308..a48c2f765bba 100644 --- a/net/netfilter/nft_fwd_netdev.c +++ b/net/netfilter/nft_fwd_netdev.c @@ -95,7 +95,6 @@ static void nft_fwd_neigh_eval(const struct nft_expr *expr, struct nft_regs *regs, const struct nft_pktinfo *pkt) { - u8 *nf_dup_skb_recursion = nf_get_nf_dup_skb_recursion(); struct nft_fwd_neigh *priv = nft_expr_priv(expr); void *addr = ®s->data[priv->sreg_addr]; int oif = regs->data[priv->sreg_dev]; @@ -154,13 +153,15 @@ static void nft_fwd_neigh_eval(const struct nft_expr *expr, goto out; } - if (*nf_dup_skb_recursion > NF_RECURSION_LIMIT) { + dev = dev_get_by_index_rcu(nft_net(pkt), oif); + if (!dev) { verdict = NF_DROP; goto out; } - dev = dev_get_by_index_rcu(nft_net(pkt), oif); - if (dev == NULL) { + local_bh_disable(); + if (nf_dev_xmit_recursion()) { + local_bh_enable(); verdict = NF_DROP; goto out; } @@ -169,16 +170,18 @@ static void nft_fwd_neigh_eval(const struct nft_expr *expr, if (unlikely(skb_headroom(skb) < hh_len && dev->header_ops)) { skb = skb_expand_head(skb, hh_len); if (!skb) { - verdict = NF_STOLEN; + local_bh_enable(); goto out; } } skb->dev = dev; skb_clear_tstamp(skb); - (*nf_dup_skb_recursion)++; + + nf_dev_xmit_recursion_inc(); neigh_xmit(neigh_table, dev, addr, skb); - (*nf_dup_skb_recursion)--; + nf_dev_xmit_recursion_dec(); + local_bh_enable(); out: regs->verdict.code = verdict; } -- cgit v1.2.3 From 5c4adb7fb46fac348197c5a15c676a066dd1f88e Mon Sep 17 00:00:00 2001 From: Dragos Tatulea Date: Sat, 13 Jun 2026 00:17:03 +0300 Subject: netdev: expose io_uring rx_page_order order via netlink This adds observability for the io_uring zcrx rx-buf-len configuration. Signed-off-by: Dragos Tatulea Reviewed-by: Yael Chemla Reviewed-by: Tariq Toukan Acked-by: Pavel Begunkov Link: https://patch.msgid.link/20260612211709.1456966-3-dtatulea@nvidia.com Signed-off-by: Jakub Kicinski --- Documentation/netlink/specs/netdev.yaml | 9 ++++++++- include/uapi/linux/netdev.h | 2 ++ io_uring/zcrx.c | 8 ++++++++ tools/include/uapi/linux/netdev.h | 2 ++ 4 files changed, 20 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/Documentation/netlink/specs/netdev.yaml b/Documentation/netlink/specs/netdev.yaml index 49862b666d7d..5f143da7458c 100644 --- a/Documentation/netlink/specs/netdev.yaml +++ b/Documentation/netlink/specs/netdev.yaml @@ -127,7 +127,14 @@ attribute-sets: enum: xsk-flags - name: io-uring-provider-info - attributes: [] + attributes: + - + name: rx-buf-len + type: uint + doc: | + RX buffer length in bytes for this io_uring memory provider. + Reflects the rx_buf_len passed at io_uring zerocopy rx + registration time. - name: page-pool attributes: diff --git a/include/uapi/linux/netdev.h b/include/uapi/linux/netdev.h index 7df1056a35fd..2f3ab75e8cc0 100644 --- a/include/uapi/linux/netdev.h +++ b/include/uapi/linux/netdev.h @@ -97,6 +97,8 @@ enum { }; enum { + NETDEV_A_IO_URING_PROVIDER_INFO_RX_BUF_LEN = 1, + __NETDEV_A_IO_URING_PROVIDER_INFO_MAX, NETDEV_A_IO_URING_PROVIDER_INFO_MAX = (__NETDEV_A_IO_URING_PROVIDER_INFO_MAX - 1) }; diff --git a/io_uring/zcrx.c b/io_uring/zcrx.c index 19837e0b5e91..c7b167c2d4e4 100644 --- a/io_uring/zcrx.c +++ b/io_uring/zcrx.c @@ -1156,6 +1156,7 @@ static void io_pp_zc_destroy(struct page_pool *pp) static int io_pp_nl_fill(void *mp_priv, struct sk_buff *rsp, struct netdev_rx_queue *rxq) { + struct io_zcrx_ifq *ifq = mp_priv; struct nlattr *nest; int type; @@ -1163,6 +1164,13 @@ static int io_pp_nl_fill(void *mp_priv, struct sk_buff *rsp, nest = nla_nest_start(rsp, type); if (!nest) return -EMSGSIZE; + + if (nla_put_uint(rsp, NETDEV_A_IO_URING_PROVIDER_INFO_RX_BUF_LEN, + 1ULL << ifq->niov_shift)) { + nla_nest_cancel(rsp, nest); + return -EMSGSIZE; + } + nla_nest_end(rsp, nest); return 0; diff --git a/tools/include/uapi/linux/netdev.h b/tools/include/uapi/linux/netdev.h index 7df1056a35fd..2f3ab75e8cc0 100644 --- a/tools/include/uapi/linux/netdev.h +++ b/tools/include/uapi/linux/netdev.h @@ -97,6 +97,8 @@ enum { }; enum { + NETDEV_A_IO_URING_PROVIDER_INFO_RX_BUF_LEN = 1, + __NETDEV_A_IO_URING_PROVIDER_INFO_MAX, NETDEV_A_IO_URING_PROVIDER_INFO_MAX = (__NETDEV_A_IO_URING_PROVIDER_INFO_MAX - 1) }; -- cgit v1.2.3 From ca394837dfddba669e0021b8be7f1e68affb206b Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Thu, 11 Jun 2026 10:14:50 +0800 Subject: net: enetc: add interfaces to manage dynamic FDB entries Add three interfaces to manage dynamic entries in the FDB table: ntmp_fdbt_update_activity_element(): Update the activity element of all dynamic FDB entries. For each entry, if its activity flag is not set, which means no packet has matched this entry since the last update, the activity counter is incremented. Otherwise, both the activity flag and activity counter are reset. The activity counter is used to track how long an FDB entry has been inactive, which is useful for implementing an ageing mechanism. ntmp_fdbt_delete_ageing_entries(): Delete all dynamic FDB entries whose activity flag is not set and whose activity counter is greater than or equal to the specified threshold. This is used to remove stale entries that have been inactive for too long. ntmp_fdbt_delete_port_dynamic_entries(): Delete all dynamic FDB entries associated with the specified switch port. This is typically called when a port goes down or is removed from a bridge. Signed-off-by: Wei Fang Link: https://patch.msgid.link/20260611021458.2629145-2-wei.fang@oss.nxp.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/freescale/enetc/ntmp.c | 162 +++++++++++++++++++++ .../net/ethernet/freescale/enetc/ntmp_private.h | 4 +- include/linux/fsl/ntmp.h | 3 + 3 files changed, 167 insertions(+), 2 deletions(-) (limited to 'include') diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c b/drivers/net/ethernet/freescale/enetc/ntmp.c index f71cad943424..bda26fe93b8d 100644 --- a/drivers/net/ethernet/freescale/enetc/ntmp.c +++ b/drivers/net/ethernet/freescale/enetc/ntmp.c @@ -31,6 +31,7 @@ #define NTMP_GEN_UA_STSEU BIT(1) /* Specific Update Actions for some tables */ +#define FDBT_UA_ACTEU BIT(1) #define BPT_UA_BPSEU BIT(1) /* Query Action: 0: Full query. 1: Query entry ID, the fields after entry @@ -793,6 +794,167 @@ unlock_cbdr: } EXPORT_SYMBOL_GPL(ntmp_fdbt_search_port_entry); +/** + * ntmp_fdbt_update_activity_element - update the activity element of all + * the dynamic entries in the FDB table. + * @user: target ntmp_user struct + * + * A single activity update management could be used to process all the + * dynamic entries in the FDB table. When hardware process an activity + * update management command for an entry in the FDB table and the entry + * does not have its activity flag set, the activity counter is incremented. + * However, if the activity flag is set, then both the activity flag and + * activity counter are reset. Software can issue the activity update + * management commands at predefined times and the value of the activity + * counter can then be used to estimate the period of how long an FDB + * entry has been inactive. + * + * Return: 0 on success, otherwise a negative error code + */ +int ntmp_fdbt_update_activity_element(struct ntmp_user *user) +{ + struct fdbt_req_ua *req; + struct netc_swcbd swcbd; + struct netc_cbdr *cbdr; + union netc_cbd cbd; + u32 len; + int err; + + swcbd.size = sizeof(*req); + err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req); + if (err) + return err; + + /* Request data */ + ntmp_fill_crd(&req->crd, user->tbl.fdbt_ver, 0, FDBT_UA_ACTEU); + req->ak.search.resume_eid = cpu_to_le32(NTMP_NULL_ENTRY_ID); + req->ak.search.cfge.cfg = cpu_to_le32(FDBT_DYNAMIC); + req->ak.search.cfge_mc = FDBT_CFGE_MC_DYNAMIC; + + /* Request header */ + len = NTMP_LEN(swcbd.size, NTMP_STATUS_RESP_LEN); + /* For activity update, the access method must be search */ + ntmp_fill_request_hdr(&cbd, swcbd.dma, len, NTMP_FDBT_ID, + NTMP_CMD_UPDATE, NTMP_AM_SEARCH); + + ntmp_select_and_lock_cbdr(user, &cbdr); + err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd); + if (err) + dev_err(user->dev, + "Failed to update activity of %s, err: %pe\n", + ntmp_table_name(NTMP_FDBT_ID), ERR_PTR(err)); + + ntmp_unlock_cbdr(cbdr); + + return err; +} +EXPORT_SYMBOL_GPL(ntmp_fdbt_update_activity_element); + +/** + * ntmp_fdbt_delete_ageing_entries - delete all the ageing dynamic entries + * in the FDB table + * @user: target ntmp_user struct + * @act_cnt: the target value of the activity counter + * + * The matching rule is that the activity flag is not set and the activity + * counter is greater than or equal to act_cnt + * + * Return: 0 on success, otherwise a negative error code + */ +int ntmp_fdbt_delete_ageing_entries(struct ntmp_user *user, u8 act_cnt) +{ + struct fdbt_req_qd *req; + struct netc_swcbd swcbd; + struct netc_cbdr *cbdr; + union netc_cbd cbd; + u32 len; + int err; + + if (act_cnt > FDBT_ACT_CNT) + return -EINVAL; + + swcbd.size = sizeof(*req); + err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req); + if (err) + return err; + + /* Request data */ + ntmp_fill_crd(&req->crd, user->tbl.fdbt_ver, 0, 0); + req->ak.search.resume_eid = cpu_to_le32(NTMP_NULL_ENTRY_ID); + req->ak.search.cfge.cfg = cpu_to_le32(FDBT_DYNAMIC); + req->ak.search.acte = act_cnt; + /* Exact match with ACTE_DATA[ACT_FLAG] AND + * match >= ACTE_DATA[ACT_CNT] + */ + req->ak.search.acte_mc = FDBT_ACTE_MC; + req->ak.search.cfge_mc = FDBT_CFGE_MC_DYNAMIC; + + /* Request header */ + len = NTMP_LEN(swcbd.size, NTMP_STATUS_RESP_LEN); + ntmp_fill_request_hdr(&cbd, swcbd.dma, len, NTMP_FDBT_ID, + NTMP_CMD_DELETE, NTMP_AM_SEARCH); + + ntmp_select_and_lock_cbdr(user, &cbdr); + err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd); + if (err) + dev_err(user->dev, + "Failed to delete ageing entries of %s, err: %pe\n", + ntmp_table_name(NTMP_FDBT_ID), ERR_PTR(err)); + + ntmp_unlock_cbdr(cbdr); + + return err; +} +EXPORT_SYMBOL_GPL(ntmp_fdbt_delete_ageing_entries); + +/** + * ntmp_fdbt_delete_port_dynamic_entries - delete all dynamic FDB entries + * associated with the specified switch port + * @user: target ntmp_user struct + * @port: the specified switch port ID + * + * Return: 0 on success, otherwise a negative error code + */ +int ntmp_fdbt_delete_port_dynamic_entries(struct ntmp_user *user, int port) +{ + struct fdbt_req_qd *req; + struct netc_swcbd swcbd; + struct netc_cbdr *cbdr; + union netc_cbd cbd; + u32 len; + int err; + + swcbd.size = sizeof(*req); + err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req); + if (err) + return err; + + /* Request data */ + ntmp_fill_crd(&req->crd, user->tbl.fdbt_ver, 0, 0); + req->ak.search.resume_eid = cpu_to_le32(NTMP_NULL_ENTRY_ID); + req->ak.search.cfge.port_bitmap = cpu_to_le32(BIT(port)); + req->ak.search.cfge.cfg = cpu_to_le32(FDBT_DYNAMIC); + /* Match CFGE_DATA[DYNAMIC & PORT_BITMAP] field */ + req->ak.search.cfge_mc = FDBT_CFGE_MC_DYNAMIC_AND_PORT_BITMAP; + + /* Request header */ + len = NTMP_LEN(swcbd.size, NTMP_STATUS_RESP_LEN); + ntmp_fill_request_hdr(&cbd, swcbd.dma, len, NTMP_FDBT_ID, + NTMP_CMD_DELETE, NTMP_AM_SEARCH); + + ntmp_select_and_lock_cbdr(user, &cbdr); + err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd); + if (err) + dev_err(user->dev, + "Failed to delete dynamic %s entries on port %d, err: %pe\n", + ntmp_table_name(NTMP_FDBT_ID), port, ERR_PTR(err)); + + ntmp_unlock_cbdr(cbdr); + + return err; +} +EXPORT_SYMBOL_GPL(ntmp_fdbt_delete_port_dynamic_entries); + /** * ntmp_vft_add_entry - add an entry into the VLAN filter table * @user: target ntmp_user struct diff --git a/drivers/net/ethernet/freescale/enetc/ntmp_private.h b/drivers/net/ethernet/freescale/enetc/ntmp_private.h index 0a9b87286105..ad532b059ba8 100644 --- a/drivers/net/ethernet/freescale/enetc/ntmp_private.h +++ b/drivers/net/ethernet/freescale/enetc/ntmp_private.h @@ -155,8 +155,8 @@ struct fdbt_ak_search { #define FDBT_KEYE_MAC GENMASK(1, 0) u8 cfge_mc; #define FDBT_CFGE_MC GENMASK(2, 0) -#define FDBT_CFGE_MC_ANY 0 -#define FDBT_CFGE_MC_DYNAMIC 1 +#define FDBT_CFGE_MC_ANY 0 +#define FDBT_CFGE_MC_DYNAMIC 1 #define FDBT_CFGE_MC_PORT_BITMAP 2 #define FDBT_CFGE_MC_DYNAMIC_AND_PORT_BITMAP 3 u8 acte_mc; diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h index 88166f9ad3a2..5db078e1caa0 100644 --- a/include/linux/fsl/ntmp.h +++ b/include/linux/fsl/ntmp.h @@ -263,6 +263,9 @@ int ntmp_fdbt_delete_entry(struct ntmp_user *user, u32 entry_id); int ntmp_fdbt_search_port_entry(struct ntmp_user *user, int port, u32 *resume_entry_id, struct fdbt_entry_data *entry); +int ntmp_fdbt_update_activity_element(struct ntmp_user *user); +int ntmp_fdbt_delete_ageing_entries(struct ntmp_user *user, u8 act_cnt); +int ntmp_fdbt_delete_port_dynamic_entries(struct ntmp_user *user, int port); int ntmp_vft_add_entry(struct ntmp_user *user, u16 vid, const struct vft_cfge_data *cfge); int ntmp_bpt_update_entry(struct ntmp_user *user, u32 entry_id, -- cgit v1.2.3 From c52b6702a9481dd7f05fb5f484cb6639f5ce57ee Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Thu, 11 Jun 2026 10:14:51 +0800 Subject: net: enetc: add "Update" and "Delete" operations to VLAN filter table Add two interfaces to manage entries in the VLAN filter table: ntmp_vft_update_entry(): Update the configuration element data of the specified VLAN filter entry based on the given VLAN ID. It uses the exact key access method to locate the entry. ntmp_vft_delete_entry(): Delete the VLAN filter entry corresponding to the specified VLAN ID. It also uses the exact key access method to identify the target entry. In addition, introduce struct vft_req_qd to describe the request data buffer format for Query and Delete actions of the VLAN filter table, which contains a common request data header and a VLAN access key. Signed-off-by: Wei Fang Link: https://patch.msgid.link/20260611021458.2629145-3-wei.fang@oss.nxp.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/freescale/enetc/ntmp.c | 103 +++++++++++++++++++-- .../net/ethernet/freescale/enetc/ntmp_private.h | 6 ++ include/linux/fsl/ntmp.h | 3 + 3 files changed, 105 insertions(+), 7 deletions(-) (limited to 'include') diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c b/drivers/net/ethernet/freescale/enetc/ntmp.c index bda26fe93b8d..4e60bbc38cfa 100644 --- a/drivers/net/ethernet/freescale/enetc/ntmp.c +++ b/drivers/net/ethernet/freescale/enetc/ntmp.c @@ -956,15 +956,17 @@ int ntmp_fdbt_delete_port_dynamic_entries(struct ntmp_user *user, int port) EXPORT_SYMBOL_GPL(ntmp_fdbt_delete_port_dynamic_entries); /** - * ntmp_vft_add_entry - add an entry into the VLAN filter table + * ntmp_vft_set_entry - add an entry into the VLAN filter table or update + * the configuration element data of the specified VLAN filter entry * @user: target ntmp_user struct * @vid: VLAN ID + * @cmd: command type, NTMP_CMD_ADD or NTMP_CMD_UPDATE * @cfge: configuration element data * * Return: 0 on success, otherwise a negative error code */ -int ntmp_vft_add_entry(struct ntmp_user *user, u16 vid, - const struct vft_cfge_data *cfge) +static int ntmp_vft_set_entry(struct ntmp_user *user, u16 vid, int cmd, + const struct vft_cfge_data *cfge) { struct netc_swcbd swcbd; struct vft_req_ua *req; @@ -973,34 +975,121 @@ int ntmp_vft_add_entry(struct ntmp_user *user, u16 vid, u32 len; int err; + if (cmd != NTMP_CMD_ADD && cmd != NTMP_CMD_UPDATE) + return -EINVAL; + swcbd.size = sizeof(*req); err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req); if (err) return err; /* Request data */ - ntmp_fill_crd(&req->crd, user->tbl.vft_ver, 0, - NTMP_GEN_UA_CFGEU); + ntmp_fill_crd(&req->crd, user->tbl.vft_ver, 0, NTMP_GEN_UA_CFGEU); req->ak.exact.vid = cpu_to_le16(vid); req->cfge = *cfge; /* Request header */ len = NTMP_LEN(swcbd.size, NTMP_STATUS_RESP_LEN); ntmp_fill_request_hdr(&cbd, swcbd.dma, len, NTMP_VFT_ID, - NTMP_CMD_ADD, NTMP_AM_EXACT_KEY); + cmd, NTMP_AM_EXACT_KEY); ntmp_select_and_lock_cbdr(user, &cbdr); err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd); + ntmp_unlock_cbdr(cbdr); + + return err; +} + +/** + * ntmp_vft_add_entry - add an entry into the VLAN filter table + * @user: target ntmp_user struct + * @vid: VLAN ID + * @cfge: configuration element data + * + * Return: 0 on success, otherwise a negative error code + */ +int ntmp_vft_add_entry(struct ntmp_user *user, u16 vid, + const struct vft_cfge_data *cfge) +{ + int err; + + err = ntmp_vft_set_entry(user, vid, NTMP_CMD_ADD, cfge); if (err) dev_err(user->dev, "Failed to add %s entry, vid: %u, err: %pe\n", ntmp_table_name(NTMP_VFT_ID), vid, ERR_PTR(err)); + return err; +} +EXPORT_SYMBOL_GPL(ntmp_vft_add_entry); + +/** + * ntmp_vft_update_entry - update the configuration element data of the + * specified VLAN filter entry + * @user: target ntmp_user struct + * @vid: VLAN ID + * @cfge: configuration element data + * + * Return: 0 on success, otherwise a negative error code + */ +int ntmp_vft_update_entry(struct ntmp_user *user, u16 vid, + const struct vft_cfge_data *cfge) +{ + int err; + + err = ntmp_vft_set_entry(user, vid, NTMP_CMD_UPDATE, cfge); + if (err) + dev_err(user->dev, + "Failed to update %s entry, vid: %u, err: %pe\n", + ntmp_table_name(NTMP_VFT_ID), vid, ERR_PTR(err)); + + return err; +} +EXPORT_SYMBOL_GPL(ntmp_vft_update_entry); + +/** + * ntmp_vft_delete_entry - delete the VLAN filter entry based on the + * specified VLAN ID + * @user: target ntmp_user struct + * @vid: VLAN ID + * + * Return: 0 on success, otherwise a negative error code + */ +int ntmp_vft_delete_entry(struct ntmp_user *user, u16 vid) +{ + struct netc_swcbd swcbd; + struct vft_req_qd *req; + struct netc_cbdr *cbdr; + union netc_cbd cbd; + u32 len; + int err; + + swcbd.size = sizeof(*req); + err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req); + if (err) + return err; + + /* Request data */ + ntmp_fill_crd(&req->crd, user->tbl.vft_ver, 0, 0); + req->ak.exact.vid = cpu_to_le16(vid); + + /* Request header */ + len = NTMP_LEN(swcbd.size, NTMP_STATUS_RESP_LEN); + ntmp_fill_request_hdr(&cbd, swcbd.dma, len, NTMP_VFT_ID, + NTMP_CMD_DELETE, NTMP_AM_EXACT_KEY); + + ntmp_select_and_lock_cbdr(user, &cbdr); + err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd); + if (err) + dev_err(user->dev, + "Failed to delete %s entry, vid: %u, err: %pe\n", + ntmp_table_name(NTMP_VFT_ID), vid, ERR_PTR(err)); + ntmp_unlock_cbdr(cbdr); return err; } -EXPORT_SYMBOL_GPL(ntmp_vft_add_entry); +EXPORT_SYMBOL_GPL(ntmp_vft_delete_entry); int ntmp_bpt_update_entry(struct ntmp_user *user, u32 entry_id, const struct bpt_cfge_data *cfge) diff --git a/drivers/net/ethernet/freescale/enetc/ntmp_private.h b/drivers/net/ethernet/freescale/enetc/ntmp_private.h index ad532b059ba8..9d30f128849a 100644 --- a/drivers/net/ethernet/freescale/enetc/ntmp_private.h +++ b/drivers/net/ethernet/freescale/enetc/ntmp_private.h @@ -211,6 +211,12 @@ struct vft_req_ua { struct vft_cfge_data cfge; }; +/* VLAN Filter Table Request Data Buffer Format of Query and Delete actions */ +struct vft_req_qd { + struct ntmp_cmn_req_data crd; + union vft_access_key ak; +}; + /* Buffer Pool Table Request Data Buffer Format of Update action */ struct bpt_req_update { struct ntmp_req_by_eid rbe; diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h index 5db078e1caa0..36a9089526ad 100644 --- a/include/linux/fsl/ntmp.h +++ b/include/linux/fsl/ntmp.h @@ -268,6 +268,9 @@ int ntmp_fdbt_delete_ageing_entries(struct ntmp_user *user, u8 act_cnt); int ntmp_fdbt_delete_port_dynamic_entries(struct ntmp_user *user, int port); int ntmp_vft_add_entry(struct ntmp_user *user, u16 vid, const struct vft_cfge_data *cfge); +int ntmp_vft_update_entry(struct ntmp_user *user, u16 vid, + const struct vft_cfge_data *cfge); +int ntmp_vft_delete_entry(struct ntmp_user *user, u16 vid); int ntmp_bpt_update_entry(struct ntmp_user *user, u32 entry_id, const struct bpt_cfge_data *cfge); #else -- cgit v1.2.3 From 3cc291a35939a3d59e50a1b2cc4845abd6bb1208 Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Thu, 11 Jun 2026 10:14:52 +0800 Subject: net: enetc: add interfaces to manage egress treatment table Each entry in the egress treatment table contains the egress packet processing actions to be applied to a grouping or scope of packets exiting on a particular egress port of the switch. A scope of packets, for example, could be the packets exiting a particular VLAN, matching a particular 802.1Q bridge forwarding entry or belonging to a stream identified at ingress. The egress treatment table is implemented as a linear array of entries accessed using an index (0,1, 2, ..., n) that uniquely identifies an entry within the array. The egress treatment table only supports access vid entry ID, which is assigned by the software. It supports Add, Update, Delete and Query operations. Note that only Query operation is not supported yet. Signed-off-by: Wei Fang Link: https://patch.msgid.link/20260611021458.2629145-4-wei.fang@oss.nxp.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/freescale/enetc/ntmp.c | 106 +++++++++++++++++++++ .../net/ethernet/freescale/enetc/ntmp_private.h | 8 ++ include/linux/fsl/ntmp.h | 23 +++++ 3 files changed, 137 insertions(+) (limited to 'include') diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c b/drivers/net/ethernet/freescale/enetc/ntmp.c index 4e60bbc38cfa..9249f78219ed 100644 --- a/drivers/net/ethernet/freescale/enetc/ntmp.c +++ b/drivers/net/ethernet/freescale/enetc/ntmp.c @@ -24,6 +24,7 @@ #define NTMP_IPFT_ID 13 #define NTMP_FDBT_ID 15 #define NTMP_VFT_ID 18 +#define NTMP_ETT_ID 33 #define NTMP_BPT_ID 41 /* Generic Update Actions for most tables */ @@ -284,6 +285,8 @@ static const char *ntmp_table_name(int tbl_id) return "FDB Table"; case NTMP_VFT_ID: return "VLAN Filter Table"; + case NTMP_ETT_ID: + return "Egress Treatment Table"; case NTMP_BPT_ID: return "Buffer Pool Table"; default: @@ -1091,6 +1094,109 @@ int ntmp_vft_delete_entry(struct ntmp_user *user, u16 vid) } EXPORT_SYMBOL_GPL(ntmp_vft_delete_entry); +/** + * ntmp_ett_set_entry - add a new entry to the egress treatment table or + * update the configuration element data of the specified entry + * @user: target ntmp_user struct + * @entry_id: entry ID + * @cmd: command type, NTMP_CMD_ADD or NTMP_CMD_UPDATE + * @cfge: configuration element data + * + * Return: 0 on success, otherwise a negative error code + */ +static int ntmp_ett_set_entry(struct ntmp_user *user, u32 entry_id, + int cmd, const struct ett_cfge_data *cfge) +{ + struct netc_swcbd swcbd; + struct ett_req_ua *req; + struct netc_cbdr *cbdr; + union netc_cbd cbd; + int err; + + if (cmd != NTMP_CMD_ADD && cmd != NTMP_CMD_UPDATE) + return -EINVAL; + + swcbd.size = sizeof(*req); + err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req); + if (err) + return err; + + /* Request data */ + ntmp_fill_crd_eid(&req->rbe, user->tbl.ett_ver, 0, + NTMP_GEN_UA_CFGEU, entry_id); + req->cfge = *cfge; + + /* Request header */ + ntmp_fill_request_hdr(&cbd, swcbd.dma, NTMP_LEN(swcbd.size, 0), + NTMP_ETT_ID, cmd, NTMP_AM_ENTRY_ID); + + ntmp_select_and_lock_cbdr(user, &cbdr); + err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd); + ntmp_unlock_cbdr(cbdr); + + return err; +} + +/** + * ntmp_ett_add_entry - add a new entry to the egress treatment table + * @user: target ntmp_user struct + * @entry_id: entry ID + * @cfge: configuration element data + * + * Return: 0 on success, otherwise a negative error code + */ +int ntmp_ett_add_entry(struct ntmp_user *user, u32 entry_id, + const struct ett_cfge_data *cfge) +{ + int err; + + err = ntmp_ett_set_entry(user, entry_id, NTMP_CMD_ADD, cfge); + if (err) + dev_err(user->dev, "Failed to add %s entry 0x%x, err: %pe\n", + ntmp_table_name(NTMP_ETT_ID), entry_id, ERR_PTR(err)); + + return err; +} +EXPORT_SYMBOL_GPL(ntmp_ett_add_entry); + +/** + * ntmp_ett_update_entry - update the configuration element data of the + * specified entry + * @user: target ntmp_user struct + * @entry_id: entry ID + * @cfge: configuration element data + * + * Return: 0 on success, otherwise a negative error code + */ +int ntmp_ett_update_entry(struct ntmp_user *user, u32 entry_id, + const struct ett_cfge_data *cfge) +{ + int err; + + err = ntmp_ett_set_entry(user, entry_id, NTMP_CMD_UPDATE, cfge); + if (err) + dev_err(user->dev, + "Failed to update %s entry 0x%x, err: %pe\n", + ntmp_table_name(NTMP_ETT_ID), entry_id, ERR_PTR(err)); + + return err; +} +EXPORT_SYMBOL_GPL(ntmp_ett_update_entry); + +/** + * ntmp_ett_delete_entry - delete the specified egress treatment table entry + * @user: target ntmp_user struct + * @entry_id: entry ID + * + * Return: 0 on success, otherwise a negative error code + */ +int ntmp_ett_delete_entry(struct ntmp_user *user, u32 entry_id) +{ + return ntmp_delete_entry_by_id(user, NTMP_ETT_ID, user->tbl.ett_ver, + entry_id, NTMP_EID_REQ_LEN, 0); +} +EXPORT_SYMBOL_GPL(ntmp_ett_delete_entry); + int ntmp_bpt_update_entry(struct ntmp_user *user, u32 entry_id, const struct bpt_cfge_data *cfge) { diff --git a/drivers/net/ethernet/freescale/enetc/ntmp_private.h b/drivers/net/ethernet/freescale/enetc/ntmp_private.h index 9d30f128849a..531ea7ddd145 100644 --- a/drivers/net/ethernet/freescale/enetc/ntmp_private.h +++ b/drivers/net/ethernet/freescale/enetc/ntmp_private.h @@ -217,6 +217,14 @@ struct vft_req_qd { union vft_access_key ak; }; +/* Egress Treatment Table Request Data Buffer Format of Update and Add + * actions + */ +struct ett_req_ua { + struct ntmp_req_by_eid rbe; + struct ett_cfge_data cfge; +}; + /* Buffer Pool Table Request Data Buffer Format of Update action */ struct bpt_req_update { struct ntmp_req_by_eid rbe; diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h index 36a9089526ad..0c951e1c763d 100644 --- a/include/linux/fsl/ntmp.h +++ b/include/linux/fsl/ntmp.h @@ -36,6 +36,7 @@ struct netc_tbl_vers { u8 vft_ver; u8 bpt_ver; u8 ipft_ver; + u8 ett_ver; }; struct netc_swcbd { @@ -214,6 +215,23 @@ struct vft_cfge_data { __le32 et_eid; }; +struct ett_cfge_data { + __le16 efm_cfg; +#define ETT_EFM_MODE GENMASK(1, 0) +#define ETT_ESQA GENMASK(5, 4) +#define ETT_ECA GENMASK(8, 6) +#define ETT_ECA_INC 1 +#define ETT_EFM_LEN_CHANGE GENMASK(15, 9) +#define ETT_FRM_LEN_DEL_VLAN 0x7c +#define ETT_FRM_LEN_DEL_RTAG 0x7a +#define ETT_FRM_LEN_DEL_VLAN_RTAG 0x76 + __le16 efm_data_len; +#define ETT_EFM_DATA_LEN GENMASK(10, 0) + __le32 efm_eid; + __le32 ec_eid; + __le32 esqa_tgt_eid; +}; + struct bpt_bpse_data { __le32 amount_used; __le32 amount_used_hwm; @@ -271,6 +289,11 @@ int ntmp_vft_add_entry(struct ntmp_user *user, u16 vid, int ntmp_vft_update_entry(struct ntmp_user *user, u16 vid, const struct vft_cfge_data *cfge); int ntmp_vft_delete_entry(struct ntmp_user *user, u16 vid); +int ntmp_ett_add_entry(struct ntmp_user *user, u32 entry_id, + const struct ett_cfge_data *cfge); +int ntmp_ett_update_entry(struct ntmp_user *user, u32 entry_id, + const struct ett_cfge_data *cfge); +int ntmp_ett_delete_entry(struct ntmp_user *user, u32 entry_id); int ntmp_bpt_update_entry(struct ntmp_user *user, u32 entry_id, const struct bpt_cfge_data *cfge); #else -- cgit v1.2.3 From d51f238a154aeb6cb76c70a0b0cb72ea99319870 Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Thu, 11 Jun 2026 10:14:53 +0800 Subject: net: enetc: add "Update" operation to the egress count table The egress count table is a static bounded index table, egress related statistics are maintained in this table. The table is implemented as a linear array of entries accessed using an index (0, 1, 2, ..., n) that uniquely identifies an entry within the array. Egress Counter Entry ID (EC_EID) is used as an index to an entry in this table. The EC_EID is specified in the egress treatment table. Egress count table entries are always present and enabled. The table only supports access via entry ID, which is assigned by the software. And it supports Update, Query and Query followed by Update operations. Currently, only Update operation is supported. Signed-off-by: Wei Fang Link: https://patch.msgid.link/20260611021458.2629145-5-wei.fang@oss.nxp.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/freescale/enetc/ntmp.c | 45 +++++++++++++++++++++++++++++ include/linux/fsl/ntmp.h | 2 ++ 2 files changed, 47 insertions(+) (limited to 'include') diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c b/drivers/net/ethernet/freescale/enetc/ntmp.c index 9249f78219ed..601435966ed1 100644 --- a/drivers/net/ethernet/freescale/enetc/ntmp.c +++ b/drivers/net/ethernet/freescale/enetc/ntmp.c @@ -25,6 +25,7 @@ #define NTMP_FDBT_ID 15 #define NTMP_VFT_ID 18 #define NTMP_ETT_ID 33 +#define NTMP_ECT_ID 39 #define NTMP_BPT_ID 41 /* Generic Update Actions for most tables */ @@ -33,6 +34,7 @@ /* Specific Update Actions for some tables */ #define FDBT_UA_ACTEU BIT(1) +#define ECT_UA_STSEU BIT(0) #define BPT_UA_BPSEU BIT(1) /* Query Action: 0: Full query. 1: Query entry ID, the fields after entry @@ -287,6 +289,8 @@ static const char *ntmp_table_name(int tbl_id) return "VLAN Filter Table"; case NTMP_ETT_ID: return "Egress Treatment Table"; + case NTMP_ECT_ID: + return "Egress Count Table"; case NTMP_BPT_ID: return "Buffer Pool Table"; default: @@ -1197,6 +1201,47 @@ int ntmp_ett_delete_entry(struct ntmp_user *user, u32 entry_id) } EXPORT_SYMBOL_GPL(ntmp_ett_delete_entry); +/** + * ntmp_ect_update_entry - reset the statistics element data of the + * specified egress counter table entry + * @user: target ntmp_user struct + * @entry_id: entry ID + * + * Return: 0 on success, otherwise a negative error code + */ +int ntmp_ect_update_entry(struct ntmp_user *user, u32 entry_id) +{ + struct ntmp_req_by_eid *req; + struct netc_swcbd swcbd; + struct netc_cbdr *cbdr; + union netc_cbd cbd; + int err; + + swcbd.size = sizeof(*req); + err = ntmp_alloc_data_mem(user->dev, &swcbd, (void **)&req); + if (err) + return err; + + /* Request data */ + ntmp_fill_crd_eid(req, user->tbl.ect_ver, 0, ECT_UA_STSEU, entry_id); + + /* Request header */ + ntmp_fill_request_hdr(&cbd, swcbd.dma, NTMP_LEN(swcbd.size, 0), + NTMP_ECT_ID, NTMP_CMD_UPDATE, NTMP_AM_ENTRY_ID); + + ntmp_select_and_lock_cbdr(user, &cbdr); + err = netc_xmit_ntmp_cmd(cbdr, &cbd, &swcbd); + if (err) + dev_err(user->dev, + "Failed to update %s entry 0x%x, err: %pe\n", + ntmp_table_name(NTMP_ECT_ID), entry_id, ERR_PTR(err)); + + ntmp_unlock_cbdr(cbdr); + + return err; +} +EXPORT_SYMBOL_GPL(ntmp_ect_update_entry); + int ntmp_bpt_update_entry(struct ntmp_user *user, u32 entry_id, const struct bpt_cfge_data *cfge) { diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h index 0c951e1c763d..1222901f48a7 100644 --- a/include/linux/fsl/ntmp.h +++ b/include/linux/fsl/ntmp.h @@ -37,6 +37,7 @@ struct netc_tbl_vers { u8 bpt_ver; u8 ipft_ver; u8 ett_ver; + u8 ect_ver; }; struct netc_swcbd { @@ -294,6 +295,7 @@ int ntmp_ett_add_entry(struct ntmp_user *user, u32 entry_id, int ntmp_ett_update_entry(struct ntmp_user *user, u32 entry_id, const struct ett_cfge_data *cfge); int ntmp_ett_delete_entry(struct ntmp_user *user, u32 entry_id); +int ntmp_ect_update_entry(struct ntmp_user *user, u32 entry_id); int ntmp_bpt_update_entry(struct ntmp_user *user, u32 entry_id, const struct bpt_cfge_data *cfge); #else -- cgit v1.2.3 From 1a58ae73dd743bba2895431ee99471d784175244 Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Thu, 11 Jun 2026 10:14:54 +0800 Subject: net: dsa: netc: initialize the group bitmap of ETT and ECT The Egress Treatment Table (ETT) and Egress Count Table (ECT) are both index tables whose entry IDs are allocated by software. Every num_ports entries form a group, where each entry in the group corresponds to one port. To facilitate group allocation and management, initialize the group index bitmaps for both tables based on hardware capabilities reported by ETTCAPR and ECTCAPR registers. The bitmap size per table is calculated as the total number of hardware entries divided by the number of available ports, which gives the number of groups available for software allocation. A set bit in the bitmap represents a group index that has been allocated. These bitmaps will be used by subsequent patches that add VLAN support. Signed-off-by: Wei Fang Link: https://patch.msgid.link/20260611021458.2629145-6-wei.fang@oss.nxp.com Signed-off-by: Jakub Kicinski --- drivers/net/dsa/netc/netc_main.c | 90 ++++++++++++++++++++++++++++++++++- drivers/net/dsa/netc/netc_switch_hw.h | 6 +++ include/linux/fsl/ntmp.h | 7 +++ 3 files changed, 102 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/drivers/net/dsa/netc/netc_main.c b/drivers/net/dsa/netc/netc_main.c index fa7dd307ce13..d4475ad7ed6c 100644 --- a/drivers/net/dsa/netc/netc_main.c +++ b/drivers/net/dsa/netc/netc_main.c @@ -323,16 +323,104 @@ static void netc_remove_all_cbdrs(struct netc_switch *priv) ntmp_free_cbdr(&ntmp->ring[i]); } +static u32 netc_num_available_ports(struct netc_switch *priv) +{ + struct dsa_port *dp; + u32 num_ports = 0; + + dsa_switch_for_each_available_port(dp, priv->ds) + num_ports++; + + return num_ports; +} + +static int netc_init_ntmp_bitmap_sizes(struct netc_switch *priv) +{ + u32 num_ports = netc_num_available_ports(priv); + struct netc_switch_regs *regs = &priv->regs; + struct ntmp_user *ntmp = &priv->ntmp; + u32 val; + + if (!num_ports) + return -EINVAL; + + val = netc_base_rd(regs, NETC_ETTCAPR); + ntmp->ett_bitmap_size = NETC_GET_NUM_ENTRIES(val) / num_ports; + if (!ntmp->ett_bitmap_size) + return -EINVAL; + + val = netc_base_rd(regs, NETC_ECTCAPR); + ntmp->ect_bitmap_size = NETC_GET_NUM_ENTRIES(val) / num_ports; + if (!ntmp->ect_bitmap_size) + return -EINVAL; + + return 0; +} + +static int netc_init_ntmp_bitmaps(struct netc_switch *priv) +{ + struct ntmp_user *ntmp = &priv->ntmp; + + ntmp->ett_gid_bitmap = bitmap_zalloc(ntmp->ett_bitmap_size, + GFP_KERNEL); + if (!ntmp->ett_gid_bitmap) + return -ENOMEM; + + ntmp->ect_gid_bitmap = bitmap_zalloc(ntmp->ect_bitmap_size, + GFP_KERNEL); + if (!ntmp->ect_gid_bitmap) + goto free_ett_gid_bitmap; + + return 0; + +free_ett_gid_bitmap: + bitmap_free(ntmp->ett_gid_bitmap); + ntmp->ett_gid_bitmap = NULL; + + return -ENOMEM; +} + +static void netc_free_ntmp_bitmaps(struct netc_switch *priv) +{ + struct ntmp_user *ntmp = &priv->ntmp; + + bitmap_free(ntmp->ect_gid_bitmap); + ntmp->ect_gid_bitmap = NULL; + + bitmap_free(ntmp->ett_gid_bitmap); + ntmp->ett_gid_bitmap = NULL; +} + static int netc_init_ntmp_user(struct netc_switch *priv) { + int err; + netc_init_ntmp_tbl_versions(priv); - return netc_init_all_cbdrs(priv); + err = netc_init_ntmp_bitmap_sizes(priv); + if (err) + return err; + + err = netc_init_ntmp_bitmaps(priv); + if (err) + return err; + + err = netc_init_all_cbdrs(priv); + if (err) + goto free_ntmp_bitmaps; + + return 0; + +free_ntmp_bitmaps: + netc_free_ntmp_bitmaps(priv); + + return err; } static void netc_free_ntmp_user(struct netc_switch *priv) { netc_remove_all_cbdrs(priv); + netc_free_ntmp_bitmaps(priv); } static void netc_switch_dos_default_config(struct netc_switch *priv) diff --git a/drivers/net/dsa/netc/netc_switch_hw.h b/drivers/net/dsa/netc/netc_switch_hw.h index 1d976882a6cc..1404ae41c7bc 100644 --- a/drivers/net/dsa/netc/netc_switch_hw.h +++ b/drivers/net/dsa/netc/netc_switch_hw.h @@ -36,6 +36,12 @@ #define DOSL3CR_SAMEADDR BIT(0) #define DOSL3CR_IPSAMCC BIT(1) +#define NETC_ETTCAPR 0x18c4 +#define NETC_ECTCAPR 0x18ec +/* Index table NUM_ENTRIES mask */ +#define NETC_NUM_ENTRIES GENMASK(15, 0) +#define NETC_GET_NUM_ENTRIES(v) FIELD_GET(NETC_NUM_ENTRIES, (v)) + /* Hash table memory capability register, the memory is shared by * the following tables: * diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h index 1222901f48a7..e8b1bd802f19 100644 --- a/include/linux/fsl/ntmp.h +++ b/include/linux/fsl/ntmp.h @@ -3,6 +3,7 @@ #ifndef __NETC_NTMP_H #define __NETC_NTMP_H +#include #include #include @@ -70,6 +71,12 @@ struct ntmp_user { struct device *dev; struct netc_cbdr *ring; struct netc_tbl_vers tbl; + + /* NTMP table bitmaps for resource management */ + u32 ett_bitmap_size; + u32 ect_bitmap_size; + unsigned long *ett_gid_bitmap; /* only valid for switch */ + unsigned long *ect_gid_bitmap; /* only valid for switch */ }; struct maft_entry_data { -- cgit v1.2.3 From 8469b17310d1814b8e7d6fe2b4cc77a05a9da4f1 Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Thu, 11 Jun 2026 10:14:55 +0800 Subject: net: enetc: add helpers to set/clear table bitmap NTMP index tables require software to allocate and manage entry IDs. Add two bitmap helper functions to facilitate this management: ntmp_lookup_free_eid(): finds the first zero bit in the given bitmap, sets it to mark the entry as in-use, and returns the corresponding entry ID. Returns NTMP_NULL_ENTRY_ID if no free entry is available. ntmp_clear_eid_bitmap(): clears the bit associated with the given entry ID in the bitmap to mark the entry as free. It is a no-op if the entry ID is NTMP_NULL_ENTRY_ID. Both functions are exported for use by other modules, such as the NETC switch driver which needs to manage group index bitmaps for the Egress Treatment Table (ETT) and Egress Count Table (ECT). Signed-off-by: Wei Fang Link: https://patch.msgid.link/20260611021458.2629145-7-wei.fang@oss.nxp.com Signed-off-by: Jakub Kicinski --- drivers/net/ethernet/freescale/enetc/ntmp.c | 24 ++++++++++++++++++++++++ include/linux/fsl/ntmp.h | 2 ++ 2 files changed, 26 insertions(+) (limited to 'include') diff --git a/drivers/net/ethernet/freescale/enetc/ntmp.c b/drivers/net/ethernet/freescale/enetc/ntmp.c index 601435966ed1..0d83f0c9dc6e 100644 --- a/drivers/net/ethernet/freescale/enetc/ntmp.c +++ b/drivers/net/ethernet/freescale/enetc/ntmp.c @@ -47,6 +47,30 @@ #define RSST_STSE_DATA_SIZE(n) ((n) * 8) #define RSST_CFGE_DATA_SIZE(n) (n) +u32 ntmp_lookup_free_eid(unsigned long *bitmap, u32 size) +{ + u32 entry_id; + + entry_id = find_first_zero_bit(bitmap, size); + if (entry_id == size) + return NTMP_NULL_ENTRY_ID; + + /* Set the bit once we found it */ + __set_bit(entry_id, bitmap); + + return entry_id; +} +EXPORT_SYMBOL_GPL(ntmp_lookup_free_eid); + +void ntmp_clear_eid_bitmap(unsigned long *bitmap, u32 entry_id) +{ + if (entry_id == NTMP_NULL_ENTRY_ID) + return; + + __clear_bit(entry_id, bitmap); +} +EXPORT_SYMBOL_GPL(ntmp_clear_eid_bitmap); + int ntmp_init_cbdr(struct netc_cbdr *cbdr, struct device *dev, const struct netc_cbdr_regs *regs) { diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h index e8b1bd802f19..4d329488763d 100644 --- a/include/linux/fsl/ntmp.h +++ b/include/linux/fsl/ntmp.h @@ -266,6 +266,8 @@ struct bpt_cfge_data { int ntmp_init_cbdr(struct netc_cbdr *cbdr, struct device *dev, const struct netc_cbdr_regs *regs); void ntmp_free_cbdr(struct netc_cbdr *cbdr); +u32 ntmp_lookup_free_eid(unsigned long *bitmap, u32 size); +void ntmp_clear_eid_bitmap(unsigned long *bitmap, u32 entry_id); /* NTMP APIs */ int ntmp_maft_add_entry(struct ntmp_user *user, u32 entry_id, -- cgit v1.2.3 From 84b4a3b30abd226ec528985d7c3cb63a3376d098 Mon Sep 17 00:00:00 2001 From: Wei Fang Date: Thu, 11 Jun 2026 10:14:56 +0800 Subject: net: dsa: netc: add VLAN filter table and egress treatment management Implement the DSA .port_vlan_add and .port_vlan_del operations to enable VLAN-aware bridge offloading on the NETC switch. VLAN membership is maintained in the VLAN Filter Table (VFT). Adding the first port to a VLAN creates a new VFT entry with hardware MAC learning and flood-on-miss forwarding; subsequent ports update the existing entry's membership bitmap. Removing the last port deletes the entry. Egress tagging is handled through the Egress Treatment Table (ETT). Each VLAN is allocated a group of ETT entries, one per available port. Ports are assigned a sequential ett_offset during initialisation, used to address each port's entry within the group. Untagged ports configure the ETT to strip the outer VLAN tag; tagged ports pass frames through unmodified. Each ETT group is optionally paired with an Egress Counter Table (ECT) group for per-port frame counting, allocated on a best-effort basis. When the egress rule of an ETT entry changes, the counter of the corresponding ECT entry will be recounted to track the number of frames that match the new egress rule. A software shadow list serialised by vft_lock tracks active VLAN state across both port membership and egress tagging. VID 0 is used for single port mode and is ignored by both callbacks. Signed-off-by: Wei Fang Link: https://patch.msgid.link/20260611021458.2629145-8-wei.fang@oss.nxp.com Signed-off-by: Jakub Kicinski --- drivers/net/dsa/netc/netc_main.c | 439 +++++++++++++++++++++++++++++++++++++ drivers/net/dsa/netc/netc_switch.h | 24 ++ include/linux/fsl/ntmp.h | 15 ++ 3 files changed, 478 insertions(+) (limited to 'include') diff --git a/drivers/net/dsa/netc/netc_main.c b/drivers/net/dsa/netc/netc_main.c index d4475ad7ed6c..9382e7b68e65 100644 --- a/drivers/net/dsa/netc/netc_main.c +++ b/drivers/net/dsa/netc/netc_main.c @@ -37,6 +37,27 @@ static void netc_destroy_fdb_list(struct netc_switch *priv) netc_del_fdb_entry(entry); } +static struct netc_vlan_entry * +netc_lookup_vlan_entry(struct netc_switch *priv, u16 vid) +{ + struct netc_vlan_entry *entry; + + hlist_for_each_entry(entry, &priv->vlan_list, node) + if (entry->vid == vid) + return entry; + + return NULL; +} + +static void netc_destroy_vlan_list(struct netc_switch *priv) +{ + struct netc_vlan_entry *entry; + struct hlist_node *tmp; + + hlist_for_each_entry_safe(entry, tmp, &priv->vlan_list, node) + netc_del_vlan_entry(entry); +} + static enum dsa_tag_protocol netc_get_tag_protocol(struct dsa_switch *ds, int port, enum dsa_tag_protocol mprot) @@ -222,6 +243,7 @@ static int netc_init_all_ports(struct netc_switch *priv) struct device *dev = priv->dev; struct netc_port *np; struct dsa_port *dp; + int ett_offset = 0; int err; priv->ports = devm_kcalloc(dev, priv->info->num_ports, @@ -251,6 +273,8 @@ static int netc_init_all_ports(struct netc_switch *priv) dsa_switch_for_each_available_port(dp, priv->ds) { np = priv->ports[dp->index]; np->dp = dp; + np->ett_offset = ett_offset++; + priv->port_bitmap |= BIT(dp->index); err = netc_port_get_info_from_dt(np, dp->dn, dev); if (err) @@ -831,6 +855,8 @@ static int netc_setup(struct dsa_switch *ds) INIT_HLIST_HEAD(&priv->fdb_list); mutex_init(&priv->fdbt_lock); + INIT_HLIST_HEAD(&priv->vlan_list); + mutex_init(&priv->vft_lock); netc_switch_fixed_config(priv); @@ -858,6 +884,7 @@ free_lock_and_ntmp_user: * hardware state. */ mutex_destroy(&priv->fdbt_lock); + mutex_destroy(&priv->vft_lock); netc_free_ntmp_user(priv); return err; @@ -867,6 +894,8 @@ static void netc_destroy_all_lists(struct netc_switch *priv) { netc_destroy_fdb_list(priv); mutex_destroy(&priv->fdbt_lock); + netc_destroy_vlan_list(priv); + mutex_destroy(&priv->vft_lock); } static void netc_free_host_flood_rules(struct netc_switch *priv) @@ -1025,6 +1054,385 @@ static void netc_switch_get_ip_revision(struct netc_switch *priv) priv->revision = FIELD_GET(IPBRR0_IP_REV, val); } +static void netc_init_ett_cfge(struct ett_cfge_data *cfge, + bool untagged, u32 ect_eid) +{ + u32 vuda_sqta = FMTEID_VUDA_SQTA; + u16 efm_cfg = 0; + + if (ect_eid != NTMP_NULL_ENTRY_ID) { + /* Increase egress frame counter */ + efm_cfg |= FIELD_PREP(ETT_ECA, ETT_ECA_INC); + cfge->ec_eid = cpu_to_le32(ect_eid); + } + + /* If egress rule is VLAN untagged */ + if (untagged) { + /* delete outer VLAN tag */ + vuda_sqta |= FIELD_PREP(FMTEID_VUDA, FMTEID_VUDA_DEL_OTAG); + /* length change: twos-complement notation */ + efm_cfg |= FIELD_PREP(ETT_EFM_LEN_CHANGE, + ETT_FRM_LEN_DEL_VLAN); + } + + cfge->efm_eid = cpu_to_le32(vuda_sqta); + cfge->efm_cfg = cpu_to_le16(efm_cfg); +} + +static int netc_add_ett_entry(struct netc_switch *priv, bool untagged, + u32 ett_eid, u32 ect_eid) +{ + struct ntmp_user *ntmp = &priv->ntmp; + struct ett_cfge_data cfge = {}; + + netc_init_ett_cfge(&cfge, untagged, ect_eid); + + return ntmp_ett_add_entry(ntmp, ett_eid, &cfge); +} + +static int netc_update_ett_entry(struct netc_switch *priv, bool untagged, + u32 ett_eid, u32 ect_eid) +{ + struct ntmp_user *ntmp = &priv->ntmp; + struct ett_cfge_data cfge = {}; + + netc_init_ett_cfge(&cfge, untagged, ect_eid); + + return ntmp_ett_update_entry(ntmp, ett_eid, &cfge); +} + +static int netc_add_ett_group_entries(struct netc_switch *priv, + u32 untagged_port_bitmap, + u32 ett_base_eid, + u32 ect_base_eid) +{ + struct netc_port **ports = priv->ports; + u32 ett_eid, ect_eid; + bool untagged; + int i, err; + + for (i = 0; i < priv->info->num_ports; i++) { + if (!ports[i]->dp) + continue; + + untagged = !!(untagged_port_bitmap & BIT(i)); + ett_eid = ett_base_eid + ports[i]->ett_offset; + ect_eid = NTMP_NULL_ENTRY_ID; + if (ect_base_eid != NTMP_NULL_ENTRY_ID) + ect_eid = ect_base_eid + ports[i]->ett_offset; + + err = netc_add_ett_entry(priv, untagged, ett_eid, ect_eid); + if (err) + goto clear_ett_entries; + } + + return 0; + +clear_ett_entries: + while (--i >= 0) { + if (!ports[i]->dp) + continue; + + ett_eid = ett_base_eid + ports[i]->ett_offset; + ntmp_ett_delete_entry(&priv->ntmp, ett_eid); + } + + return err; +} + +static int netc_add_vlan_egress_rule(struct netc_switch *priv, + struct netc_vlan_entry *entry) +{ + u32 num_ports = netc_num_available_ports(priv); + struct ntmp_user *ntmp = &priv->ntmp; + u32 ect_eid = NTMP_NULL_ENTRY_ID; + u32 ett_eid, ett_gid, ect_gid; + int err; + + /* Step 1: Find available egress counter table entries and update + * these entries. + */ + ect_gid = ntmp_lookup_free_eid(ntmp->ect_gid_bitmap, + ntmp->ect_bitmap_size); + if (ect_gid == NTMP_NULL_ENTRY_ID) { + dev_info(priv->dev, + "No egress counter table entries available\n"); + } else { + ect_eid = ect_gid * num_ports; + for (int i = 0; i < num_ports; i++) + /* There is no need to check the return value, the only + * issue is that the entry's counter might be inaccurate, + * but it will not affect the functionality, it is only + * for future debugging. + */ + ntmp_ect_update_entry(ntmp, ect_eid + i); + } + + /* Step 2: Find available egress treatment table entries and add + * these entries. + */ + ett_gid = ntmp_lookup_free_eid(ntmp->ett_gid_bitmap, + ntmp->ett_bitmap_size); + if (ett_gid == NTMP_NULL_ENTRY_ID) { + dev_err(priv->dev, + "No egress treatment table entries available\n"); + err = -ENOSPC; + goto clear_ect_gid; + } + + ett_eid = ett_gid * num_ports; + err = netc_add_ett_group_entries(priv, entry->untagged_port_bitmap, + ett_eid, ect_eid); + if (err) + goto clear_ett_gid; + + entry->cfge.et_eid = cpu_to_le32(ett_eid); + entry->ect_gid = ect_gid; + + return 0; + +clear_ett_gid: + ntmp_clear_eid_bitmap(ntmp->ett_gid_bitmap, ett_gid); + +clear_ect_gid: + if (ect_gid != NTMP_NULL_ENTRY_ID) + ntmp_clear_eid_bitmap(ntmp->ect_gid_bitmap, ect_gid); + + return err; +} + +static void netc_delete_vlan_egress_rule(struct netc_switch *priv, + struct netc_vlan_entry *entry) +{ + u32 num_ports = netc_num_available_ports(priv); + struct ntmp_user *ntmp = &priv->ntmp; + u32 ett_eid, ett_gid; + + ett_eid = le32_to_cpu(entry->cfge.et_eid); + if (ett_eid == NTMP_NULL_ENTRY_ID) + return; + + ett_gid = ett_eid / num_ports; + ntmp_clear_eid_bitmap(ntmp->ett_gid_bitmap, ett_gid); + for (int i = 0; i < num_ports; i++) + ntmp_ett_delete_entry(ntmp, ett_eid + i); + + if (entry->ect_gid == NTMP_NULL_ENTRY_ID) + return; + + ntmp_clear_eid_bitmap(ntmp->ect_gid_bitmap, entry->ect_gid); +} + +static int netc_port_update_vlan_egress_rule(struct netc_port *np, + struct netc_vlan_entry *entry) +{ + bool untagged = !!(entry->untagged_port_bitmap & BIT(np->dp->index)); + u32 num_ports = netc_num_available_ports(np->switch_priv); + u32 ett_eid = le32_to_cpu(entry->cfge.et_eid); + struct netc_switch *priv = np->switch_priv; + u32 ect_eid = NTMP_NULL_ENTRY_ID; + int err; + + if (ett_eid == NTMP_NULL_ENTRY_ID) + return 0; + + if (entry->ect_gid != NTMP_NULL_ENTRY_ID) + /* Each ETT entry maps to an ECT entry if ect_gid is not NULL + * entry ID. The offset of the ECT entry corresponding to the + * port in the group is equal to ett_offset. + */ + ect_eid = entry->ect_gid * num_ports + np->ett_offset; + + ett_eid += np->ett_offset; + err = netc_update_ett_entry(priv, untagged, ett_eid, ect_eid); + if (err) { + dev_err(priv->dev, + "Failed to update VLAN %u egress rule on port %d\n", + entry->vid, np->dp->index); + return err; + } + + if (ect_eid != NTMP_NULL_ENTRY_ID) + ntmp_ect_update_entry(&priv->ntmp, ect_eid); + + return 0; +} + +static int netc_port_add_vlan_entry(struct netc_port *np, u16 vid, + bool untagged) +{ + struct netc_switch *priv = np->switch_priv; + struct netc_vlan_entry *entry; + struct vft_cfge_data *cfge; + u32 index = np->dp->index; + u32 bitmap_stg; + int err; + u16 cfg; + + entry = kzalloc_obj(*entry); + if (!entry) + return -ENOMEM; + + entry->vid = vid; + entry->ect_gid = NTMP_NULL_ENTRY_ID; + + bitmap_stg = BIT(index) | VFT_STG_ID(0); + cfg = FIELD_PREP(VFT_MLO, MLO_HW) | + FIELD_PREP(VFT_MFO, MFO_NO_MATCH_FLOOD); + + cfge = &entry->cfge; + cfge->et_eid = cpu_to_le32(NTMP_NULL_ENTRY_ID); + cfge->bitmap_stg = cpu_to_le32(bitmap_stg); + cfge->fid = cpu_to_le16(vid); + cfge->cfg = cpu_to_le16(cfg); + cfge->eta_port_bitmap = cpu_to_le32(priv->port_bitmap); + + if (untagged) + entry->untagged_port_bitmap = BIT(index); + + err = netc_add_vlan_egress_rule(priv, entry); + if (err) + goto free_vlan_entry; + + err = ntmp_vft_add_entry(&priv->ntmp, vid, cfge); + if (err) { + dev_err(priv->dev, + "Failed to add VLAN %u entry on port %d\n", + vid, index); + goto delete_vlan_egress_rule; + } + + netc_add_vlan_entry(priv, entry); + + return 0; + +delete_vlan_egress_rule: + netc_delete_vlan_egress_rule(priv, entry); +free_vlan_entry: + kfree(entry); + + return err; +} + +static bool netc_port_vlan_egress_rule_changed(struct netc_vlan_entry *entry, + int port, bool untagged) +{ + bool old_untagged = !!(entry->untagged_port_bitmap & BIT(port)); + + return old_untagged != untagged; +} + +static int netc_port_set_vlan_entry(struct netc_port *np, u16 vid, + bool untagged) +{ + struct netc_switch *priv = np->switch_priv; + struct netc_vlan_entry *entry; + struct vft_cfge_data *cfge; + int port = np->dp->index; + bool changed; + int err = 0; + + mutex_lock(&priv->vft_lock); + + entry = netc_lookup_vlan_entry(priv, vid); + if (!entry) { + err = netc_port_add_vlan_entry(np, vid, untagged); + goto unlock_vft; + } + + /* Check whether the egress VLAN rule is changed */ + changed = netc_port_vlan_egress_rule_changed(entry, port, untagged); + if (changed) { + entry->untagged_port_bitmap ^= BIT(port); + err = netc_port_update_vlan_egress_rule(np, entry); + if (err) { + entry->untagged_port_bitmap ^= BIT(port); + goto unlock_vft; + } + } + + cfge = &entry->cfge; + if (cfge->bitmap_stg & cpu_to_le32(BIT(port))) + goto unlock_vft; + + cfge->bitmap_stg |= cpu_to_le32(BIT(port)); + err = ntmp_vft_update_entry(&priv->ntmp, vid, cfge); + if (err) { + dev_err(priv->dev, + "Failed to update VLAN %u entry on port %d\n", + vid, port); + + goto restore_bitmap_stg; + } + + mutex_unlock(&priv->vft_lock); + + return 0; + +restore_bitmap_stg: + cfge->bitmap_stg &= cpu_to_le32(~BIT(port)); + if (changed) { + entry->untagged_port_bitmap ^= BIT(port); + /* Recover the corresponding ETT entry. It doesn't matter + * if it fails because the bit corresponding to the port + * in the port bitmap of the VFT entry is not set. so the + * frame will not match that ETT entry. + */ + if (netc_port_update_vlan_egress_rule(np, entry)) + entry->untagged_port_bitmap ^= BIT(port); + } +unlock_vft: + mutex_unlock(&priv->vft_lock); + + return err; +} + +static int netc_port_del_vlan_entry(struct netc_port *np, u16 vid) +{ + struct netc_switch *priv = np->switch_priv; + struct netc_vlan_entry *entry; + struct vft_cfge_data *cfge; + int port = np->dp->index; + u32 vlan_port_bitmap; + int err = 0; + + mutex_lock(&priv->vft_lock); + + entry = netc_lookup_vlan_entry(priv, vid); + if (!entry) + goto unlock_vft; + + cfge = &entry->cfge; + vlan_port_bitmap = FIELD_GET(VFT_PORT_MEMBERSHIP, + le32_to_cpu(cfge->bitmap_stg)); + /* If the VLAN only belongs to the current port */ + if (vlan_port_bitmap == BIT(port)) { + err = ntmp_vft_delete_entry(&priv->ntmp, vid); + if (err) + goto unlock_vft; + + netc_delete_vlan_egress_rule(priv, entry); + netc_del_vlan_entry(entry); + + goto unlock_vft; + } + + if (!(vlan_port_bitmap & BIT(port))) + goto unlock_vft; + + cfge->bitmap_stg &= cpu_to_le32(~BIT(port)); + err = ntmp_vft_update_entry(&priv->ntmp, vid, cfge); + if (err) { + cfge->bitmap_stg |= cpu_to_le32(BIT(port)); + goto unlock_vft; + } + +unlock_vft: + mutex_unlock(&priv->vft_lock); + + return err; +} + static int netc_port_enable(struct dsa_switch *ds, int port, struct phy_device *phy) { @@ -1297,6 +1705,35 @@ static void netc_port_set_host_flood(struct dsa_switch *ds, int port, netc_port_remove_host_flood(np, old_host_flood); } +static int netc_port_vlan_add(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan, + struct netlink_ext_ack *extack) +{ + struct netc_port *np = NETC_PORT(ds, port); + bool untagged; + + /* The 8021q layer may attempt to change NETC_STANDALONE_PVID + * (VID 0), so we need to ignore it. + */ + if (vlan->vid == NETC_STANDALONE_PVID) + return 0; + + untagged = !!(vlan->flags & BRIDGE_VLAN_INFO_UNTAGGED); + + return netc_port_set_vlan_entry(np, vlan->vid, untagged); +} + +static int netc_port_vlan_del(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan) +{ + struct netc_port *np = NETC_PORT(ds, port); + + if (vlan->vid == NETC_STANDALONE_PVID) + return 0; + + return netc_port_del_vlan_entry(np, vlan->vid); +} + static void netc_phylink_get_caps(struct dsa_switch *ds, int port, struct phylink_config *config) { @@ -1575,6 +2012,8 @@ static const struct dsa_switch_ops netc_switch_ops = { .port_mdb_add = netc_port_mdb_add, .port_mdb_del = netc_port_mdb_del, .port_set_host_flood = netc_port_set_host_flood, + .port_vlan_add = netc_port_vlan_add, + .port_vlan_del = netc_port_vlan_del, .get_pause_stats = netc_port_get_pause_stats, .get_rmon_stats = netc_port_get_rmon_stats, .get_eth_ctrl_stats = netc_port_get_eth_ctrl_stats, diff --git a/drivers/net/dsa/netc/netc_switch.h b/drivers/net/dsa/netc/netc_switch.h index 4fbd12825b67..9ff334301fbc 100644 --- a/drivers/net/dsa/netc/netc_switch.h +++ b/drivers/net/dsa/netc/netc_switch.h @@ -74,6 +74,7 @@ struct netc_port { struct dsa_port *dp; struct clk *ref_clk; /* RGMII/RMII reference clock */ struct mii_bus *emdio; + int ett_offset; u16 enable:1; u16 uc:1; @@ -94,6 +95,14 @@ struct netc_fdb_entry { struct hlist_node node; }; +struct netc_vlan_entry { + u16 vid; + u32 ect_gid; + u32 untagged_port_bitmap; + struct vft_cfge_data cfge; + struct hlist_node node; +}; + struct netc_port_stat { int reg; char name[ETH_GSTRING_LEN] __nonstring; @@ -108,10 +117,13 @@ struct netc_switch { const struct netc_switch_info *info; struct netc_switch_regs regs; struct netc_port **ports; + u32 port_bitmap; /* bitmap of available ports */ struct ntmp_user ntmp; struct hlist_head fdb_list; struct mutex fdbt_lock; /* FDB table lock */ + struct hlist_head vlan_list; + struct mutex vft_lock; /* VLAN filter table lock */ /* Switch hardware capabilities */ u32 htmcapr_num_words; @@ -153,6 +165,18 @@ static inline void netc_del_fdb_entry(struct netc_fdb_entry *entry) kfree(entry); } +static inline void netc_add_vlan_entry(struct netc_switch *priv, + struct netc_vlan_entry *entry) +{ + hlist_add_head(&entry->node, &priv->vlan_list); +} + +static inline void netc_del_vlan_entry(struct netc_vlan_entry *entry) +{ + hlist_del(&entry->node); + kfree(entry); +} + int netc_switch_platform_probe(struct netc_switch *priv); /* ethtool APIs */ diff --git a/include/linux/fsl/ntmp.h b/include/linux/fsl/ntmp.h index 4d329488763d..d3b6c476b91a 100644 --- a/include/linux/fsl/ntmp.h +++ b/include/linux/fsl/ntmp.h @@ -262,6 +262,21 @@ struct bpt_cfge_data { __le32 fc_ports; }; +union ntmp_fmt_eid { + __le32 index; +#define FMTEID_INDEX GENMASK(12, 0) + __le32 vuda_sqta; +#define FMTEID_VUDA GENMASK(1, 0) +#define FMTEID_VUDA_DEL_OTAG 2 +#define FMTEID_SQTA GENMASK(4, 2) +#define FMTEID_SQTA_DEL 2 +#define FMTEID_VUDA_SQTA BIT(13) + __le32 vara_vid; +#define FMTEID_VID GENMASK(11, 0) +#define FMTEID_VARA GENMASK(13, 12) +#define FMTEID_VARA_VID BIT(14) +}; + #if IS_ENABLED(CONFIG_NXP_NETC_LIB) int ntmp_init_cbdr(struct netc_cbdr *cbdr, struct device *dev, const struct netc_cbdr_regs *regs); -- cgit v1.2.3 From eb82367bca5656c99d98bba65f101776e2c2dc36 Mon Sep 17 00:00:00 2001 From: Ethan Nelson-Moore Date: Sat, 13 Jun 2026 16:37:25 -0700 Subject: sctp: correct CONFIG_SCTP_DBG_OBJCNT macro name in comment A comment in incorrectly refers to CONFIG_SCTP_DBG_OBJCOUNT instead of CONFIG_SCTP_DBG_OBJCNT. Correct it. Discovered while searching for CONFIG_* symbols referenced in code but not defined in any Kconfig file. Signed-off-by: Ethan Nelson-Moore Link: https://patch.msgid.link/20260613233725.162470-1-enelsonmoore@gmail.com Signed-off-by: Jakub Kicinski --- include/net/sctp/sctp.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include') diff --git a/include/net/sctp/sctp.h b/include/net/sctp/sctp.h index 58242b37b47a..60b073fd3ed8 100644 --- a/include/net/sctp/sctp.h +++ b/include/net/sctp/sctp.h @@ -303,7 +303,7 @@ void sctp_dbg_objcnt_init(struct net *); static inline void sctp_dbg_objcnt_init(struct net *net) { return; } -#endif /* CONFIG_SCTP_DBG_OBJCOUNT */ +#endif /* CONFIG_SCTP_DBG_OBJCNT */ #if defined CONFIG_SYSCTL void sctp_sysctl_register(void); -- cgit v1.2.3 From 658eb696544cc0e39ef1d60795546e64f542604a Mon Sep 17 00:00:00 2001 From: Neil Spring Date: Sun, 14 Jun 2026 21:21:57 -0700 Subject: tcp: rehash onto different local ECMP path on retransmit timeout Currently sk_rethink_txhash() re-rolls the socket's txhash on RTO, PLB, and spurious-retransmission events, but the cached route is reused and the new hash is not propagated into the ECMP path selection logic. Two changes are needed to make rehash select a different local ECMP path: 1. Add __sk_dst_reset() alongside sk_rethink_txhash() in tcp_write_timeout(), tcp_rcv_spurious_retrans(), and tcp_plb_check_rehash() so the cached dst is invalidated and the next transmit triggers a fresh route lookup. 2. Set fl6->mp_hash from sk_txhash (or tcp_rsk(req)->txhash for SYN/ACK retransmits and syncookies) in tcp_v6_connect(), inet6_sk_rebuild_header(), inet6_csk_route_req(), inet6_csk_route_socket(), tcp_v6_send_response(), and cookie_v6_check() so fib6_select_path() picks a path based on the new hash. The mp_hash override only applies to fib_multipath_hash_policy 0 (the default L3 policy). Its hash includes the flow label, but that is 0 by default -- np->flow_label is unset, and auto_flowlabels only computes the on-wire label later, per packet -- so flows to the same peer share one local path. Keying the hash on sk_txhash makes the local path per-connection and lets a rehash re-select it. Policies 1-3 are left unchanged. The mp_hash assignment is factored into a small helper, ip6_ecmp_set_mp_hash(), shared by inet6_csk_route_req(), inet6_csk_route_socket(), tcp_v6_connect(), inet6_sk_rebuild_header(), tcp_v6_send_response(), and cookie_v6_check(). It applies (txhash >> 1) ?: 1 for policy 0 (the >> 1 keeps mp_hash in the 31-bit range; ?: 1 keeps it non-zero, since 0 would fall back to rt6_multipath_hash()). inet6_csk_route_socket() calls it only for sk_protocol == IPPROTO_TCP so that non-TCP callers (e.g., L2TP via inet6_csk_xmit) fall through to rt6_multipath_hash() and retain their existing flow-key-based ECMP behavior. tcp_v6_send_response() also sets mp_hash from the response txhash so that a control packet (a RST from the full socket, or an ACK from a time-wait socket) selects the same local ECMP nexthop as the connection's txhash rather than falling back to the flow hash. The time-wait socket's tw_txhash is copied from sk_txhash when the connection enters TIME_WAIT, so it reflects any rehash that occurred. Setting mp_hash explicitly is necessary because the default ECMP hash derives from fl6->flowlabel via np->flow_label, which is not updated from sk_txhash (REPFLOW is off by default). ip6_make_flowlabel() cannot help either, as it runs after the route lookup. As a consequence, for policy 0 the local ECMP path of an IPv6 TCP flow follows sk_txhash even when fl6->flowlabel is non-zero, e.g. a reflected (REPFLOW) or explicitly set (IPV6_FLOWLABEL_MGR) flow label. This is intentional: only local path selection changes, so rehash can recover from a failed path; the on-wire flow label is unchanged. sk_set_txhash() is moved before ip6_dst_lookup_flow() in tcp_v6_connect() so the initial ECMP path is selected by the same txhash that subsequent route rebuilds will use. This avoids unintended path changes when the cached dst is naturally invalidated (e.g., by PMTU discovery or route changes). The rehash sites (tcp_write_timeout(), tcp_plb_check_rehash(), and tcp_rcv_spurious_retrans()) call __sk_rethink_txhash_reset_dst(), which re-rolls the txhash and, when it changed, drops the cached dst so the next transmit re-runs route selection. The dst reset is guarded by sk->sk_family == AF_INET6 since IPv4 ECMP does not currently use sk_txhash for path selection. For IPv4-mapped IPv6 sockets this produces a redundant dst reset on a cold path (RTO/PLB); the subsequent IPv4 route lookup returns the same result. The helper is deliberately separate from sk_rethink_txhash() itself: dst_negative_advice() calls sk_rethink_txhash() before its own dst op, so resetting the dst inside sk_rethink_txhash() would skip that op (e.g. rt6_remove_exception_rt()). For syncookies, cookie_init_sequence() computes the cookie value before route_req() and sets txhash so the SYN-ACK selects the same ECMP path that cookie_v6_check() will use when the full socket is created. cookie_tcp_reqsk_init() derives txhash from the cookie so the full socket's ECMP path matches the SYN-ACK. Both the SYN-ACK assignment in tcp_conn_request() and the full-socket assignment in cookie_tcp_reqsk_init() set txhash from the cookie for IPv4 and IPv6 alike. On IPv6 this drives ECMP path selection; on IPv4, which does not use sk_txhash for ECMP, it only affects TX-queue selection. That selection scales the hash by its high bits (reciprocal_scale()), which are uniform in the keyed secure_tcp_syn_cookie() output -- the MSS index only perturbs the low bits -- so the queue distribution matches net_tx_rndhash(). cookie_init_sequence() is split from the former version that also called tcp_synq_overflow() and incremented SYNCOOKIESSENT; those side effects are now in cookie_record_sent(), called after route_req() succeeds so they are not bumped when route_req() fails. cookie_record_sent() is guarded by CONFIG_SYN_COOKIES to match the guard on tcp_synq_overflow(). route_req() receives 0 as tw_isn for the syncookie path so that tcp_v6_init_req() still saves ireq->pktopts for REPFLOW flowlabel reflection and IPv6 cmsg options. The ecn_ok clear for syncookies without timestamps stays after tcp_ecn_create_request() so it takes precedence. Signed-off-by: Neil Spring Reviewed-by: Eric Dumazet Link: https://patch.msgid.link/20260615042158.1600746-2-ntspring@meta.com Signed-off-by: Jakub Kicinski --- Documentation/networking/ip-sysctl.rst | 6 +++++- include/net/ipv6.h | 12 ++++++++++++ include/net/sock.h | 14 ++++++++++++++ include/net/tcp.h | 20 ++++++++++++++------ net/ipv4/syncookies.c | 6 +++++- net/ipv4/tcp_input.c | 17 +++++++++++++---- net/ipv4/tcp_plb.c | 2 +- net/ipv4/tcp_timer.c | 2 +- net/ipv6/af_inet6.c | 2 ++ net/ipv6/inet6_connection_sock.c | 5 +++++ net/ipv6/syncookies.c | 2 ++ net/ipv6/tcp_ipv6.c | 19 +++++++++++++++++-- 12 files changed, 91 insertions(+), 16 deletions(-) (limited to 'include') diff --git a/Documentation/networking/ip-sysctl.rst b/Documentation/networking/ip-sysctl.rst index 4c6a35d07a08..208f46967ee5 100644 --- a/Documentation/networking/ip-sysctl.rst +++ b/Documentation/networking/ip-sysctl.rst @@ -2444,7 +2444,11 @@ fib_multipath_hash_policy - INTEGER Possible values: - - 0 - Layer 3 (source and destination addresses plus flow label) + - 0 - Layer 3 (source and destination addresses plus flow label). + For IPv6 TCP, the local ECMP path is selected from the socket + txhash rather than the flow label, and may change after a TCP + rehash event (such as a retransmission timeout) to recover from + path failure. The on-wire flow label is unaffected. - 1 - Layer 4 (standard 5-tuple) - 2 - Layer 3 or inner Layer 3 if present - 3 - Custom multipath hash. Fields used for multipath hash calculation diff --git a/include/net/ipv6.h b/include/net/ipv6.h index 1dec81faff28..3de07e738538 100644 --- a/include/net/ipv6.h +++ b/include/net/ipv6.h @@ -955,6 +955,18 @@ static inline u32 ip6_multipath_hash_fields(const struct net *net) } #endif +/* Derive the IPv6 ECMP hash from txhash so a rehash may pick a different path; + * policy 0 only, and only when txhash is set. >> 1 clears the top bit + * (fib6_select_path() uses mp_hash as a signed 31-bit value); ?: 1 keeps the + * result non-zero, since mp_hash 0 falls back to rt6_multipath_hash(). + */ +static inline void ip6_ecmp_set_mp_hash(const struct net *net, + struct flowi6 *fl6, u32 txhash) +{ + if (ip6_multipath_hash_policy(net) == 0 && txhash) + fl6->mp_hash = (txhash >> 1) ?: 1; +} + /* * Header manipulation */ diff --git a/include/net/sock.h b/include/net/sock.h index 91ddc1a6fd60..51185222aac2 100644 --- a/include/net/sock.h +++ b/include/net/sock.h @@ -2262,6 +2262,20 @@ sk_dst_reset(struct sock *sk) sk_dst_set(sk, NULL); } +/* Re-roll the socket txhash. On a rehash, IPv6 also drops the cached route + * so the next transmit re-selects an ECMP path; IPv4 keeps its route, since + * IPv4 ECMP path selection does not use sk_txhash. + */ +static inline bool __sk_rethink_txhash_reset_dst(struct sock *sk) +{ + if (sk_rethink_txhash(sk)) { + if (sk->sk_family == AF_INET6) + __sk_dst_reset(sk); + return true; + } + return false; +} + struct dst_entry *__sk_dst_check(struct sock *sk, u32 cookie); struct dst_entry *sk_dst_check(struct sock *sk, u32 cookie); diff --git a/include/net/tcp.h b/include/net/tcp.h index f063eccbbba3..0632cf39d20a 100644 --- a/include/net/tcp.h +++ b/include/net/tcp.h @@ -2540,22 +2540,30 @@ extern const struct tcp_request_sock_ops tcp_request_sock_ipv6_ops; #ifdef CONFIG_SYN_COOKIES static inline __u32 cookie_init_sequence(const struct tcp_request_sock_ops *ops, - const struct sock *sk, struct sk_buff *skb, - __u16 *mss) + struct sk_buff *skb, __u16 *mss) { - tcp_synq_overflow(sk); - __NET_INC_STATS(sock_net(sk), LINUX_MIB_SYNCOOKIESSENT); return ops->cookie_init_seq(skb, mss); } #else static inline __u32 cookie_init_sequence(const struct tcp_request_sock_ops *ops, - const struct sock *sk, struct sk_buff *skb, - __u16 *mss) + struct sk_buff *skb, __u16 *mss) { return 0; } #endif +#ifdef CONFIG_SYN_COOKIES +static inline void cookie_record_sent(const struct sock *sk) +{ + tcp_synq_overflow(sk); + __NET_INC_STATS(sock_net(sk), LINUX_MIB_SYNCOOKIESSENT); +} +#else +static inline void cookie_record_sent(const struct sock *sk) +{ +} +#endif + struct tcp_key { union { struct { diff --git a/net/ipv4/syncookies.c b/net/ipv4/syncookies.c index df479277fb80..73e129768184 100644 --- a/net/ipv4/syncookies.c +++ b/net/ipv4/syncookies.c @@ -280,9 +280,13 @@ static int cookie_tcp_reqsk_init(struct sock *sk, struct sk_buff *skb, treq->snt_synack = 0; treq->snt_tsval_first = 0; treq->tfo_listener = false; - treq->txhash = net_tx_rndhash(); treq->rcv_isn = ntohl(th->seq) - 1; treq->snt_isn = ntohl(th->ack_seq) - 1; + /* The request socket was freed after the SYN-ACK; use the cookie + * (snt_isn) as txhash so the full socket and the SYN-ACK make the + * same egress choice (IPv6 ECMP path; IPv4 TX queue). + */ + treq->txhash = treq->snt_isn; treq->syn_tos = TCP_SKB_CB(skb)->ip_dsfield; #if IS_ENABLED(CONFIG_MPTCP) diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c index 8560a9c6d382..61045a8886e4 100644 --- a/net/ipv4/tcp_input.c +++ b/net/ipv4/tcp_input.c @@ -5022,8 +5022,9 @@ static void tcp_rcv_spurious_retrans(struct sock *sk, skb->protocol == htons(ETH_P_IPV6) && (tcp_sk(sk)->inet_conn.icsk_ack.lrcv_flowlabel != ntohl(ip6_flowlabel(ipv6_hdr(skb)))) && - sk_rethink_txhash(sk)) + __sk_rethink_txhash_reset_dst(sk)) { NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPDUPLICATEDATAREHASH); + } /* Save last flowlabel after a spurious retrans. */ tcp_save_lrcv_flowlabel(sk, skb); @@ -7635,6 +7636,7 @@ int tcp_conn_request(struct request_sock_ops *rsk_ops, tcp_rsk(req)->af_specific = af_ops; tcp_rsk(req)->ts_off = 0; tcp_rsk(req)->req_usec_ts = false; + tcp_rsk(req)->txhash = net_tx_rndhash(); #if IS_ENABLED(CONFIG_MPTCP) tcp_rsk(req)->is_mptcp = 0; #endif @@ -7658,7 +7660,15 @@ int tcp_conn_request(struct request_sock_ops *rsk_ops, /* Note: tcp_v6_init_req() might override ir_iif for link locals */ inet_rsk(req)->ir_iif = inet_request_bound_dev_if(sk, skb); - dst = af_ops->route_req(sk, skb, &fl, req, isn); + if (want_cookie) { + isn = cookie_init_sequence(af_ops, skb, &req->mss); + /* Use the cookie as txhash so the SYN-ACK and the later full + * socket make the same egress choice (IPv6 ECMP path; IPv4 TX queue). + */ + tcp_rsk(req)->txhash = isn; + } + + dst = af_ops->route_req(sk, skb, &fl, req, want_cookie ? 0 : isn); if (!dst) goto drop_and_free; @@ -7698,7 +7708,7 @@ int tcp_conn_request(struct request_sock_ops *rsk_ops, tcp_ecn_create_request(req, skb, sk, dst); if (want_cookie) { - isn = cookie_init_sequence(af_ops, sk, skb, &req->mss); + cookie_record_sent(sk); if (!tmp_opt.tstamp_ok) inet_rsk(req)->ecn_ok = 0; } @@ -7716,7 +7726,6 @@ int tcp_conn_request(struct request_sock_ops *rsk_ops, } #endif tcp_rsk(req)->snt_isn = isn; - tcp_rsk(req)->txhash = net_tx_rndhash(); tcp_rsk(req)->syn_tos = TCP_SKB_CB(skb)->ip_dsfield; tcp_openreq_init_rwin(req, sk, dst); sk_rx_queue_set(req_to_sk(req), skb); diff --git a/net/ipv4/tcp_plb.c b/net/ipv4/tcp_plb.c index c11a0cd3f8fe..bcc2f0add6af 100644 --- a/net/ipv4/tcp_plb.c +++ b/net/ipv4/tcp_plb.c @@ -78,7 +78,7 @@ void tcp_plb_check_rehash(struct sock *sk, struct tcp_plb_state *plb) if (plb->pause_until) return; - sk_rethink_txhash(sk); + __sk_rethink_txhash_reset_dst(sk); plb->consec_cong_rounds = 0; WRITE_ONCE(tcp_sk(sk)->plb_rehash, tcp_sk(sk)->plb_rehash + 1); NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPPLBREHASH); diff --git a/net/ipv4/tcp_timer.c b/net/ipv4/tcp_timer.c index 322db13333c7..bf171b5e1eb3 100644 --- a/net/ipv4/tcp_timer.c +++ b/net/ipv4/tcp_timer.c @@ -297,7 +297,7 @@ static int tcp_write_timeout(struct sock *sk) return 1; } - if (sk_rethink_txhash(sk)) { + if (__sk_rethink_txhash_reset_dst(sk)) { WRITE_ONCE(tp->timeout_rehash, tp->timeout_rehash + 1); __NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPTIMEOUTREHASH); } diff --git a/net/ipv6/af_inet6.c b/net/ipv6/af_inet6.c index 48dd7711d659..282912a11999 100644 --- a/net/ipv6/af_inet6.c +++ b/net/ipv6/af_inet6.c @@ -822,6 +822,8 @@ int inet6_sk_rebuild_header(struct sock *sk) fl6->flowi6_uid = sk_uid(sk); security_sk_classify_flow(sk, flowi6_to_flowi_common(fl6)); + ip6_ecmp_set_mp_hash(sock_net(sk), fl6, sk->sk_txhash); + rcu_read_lock(); final_p = fl6_update_dst(fl6, rcu_dereference(np->opt), &np->final); rcu_read_unlock(); diff --git a/net/ipv6/inet6_connection_sock.c b/net/ipv6/inet6_connection_sock.c index 4665d84a7380..3e4ce8cb478e 100644 --- a/net/ipv6/inet6_connection_sock.c +++ b/net/ipv6/inet6_connection_sock.c @@ -48,6 +48,8 @@ struct dst_entry *inet6_csk_route_req(const struct sock *sk, fl6->flowi6_uid = sk_uid(sk); security_req_classify_flow(req, flowi6_to_flowi_common(fl6)); + ip6_ecmp_set_mp_hash(sock_net(sk), fl6, tcp_rsk(req)->txhash); + if (!dst) { dst = ip6_dst_lookup_flow(sock_net(sk), sk, fl6, final_p); if (IS_ERR(dst)) @@ -70,6 +72,9 @@ struct dst_entry *inet6_csk_route_socket(struct sock *sk, fl6->saddr = np->saddr; fl6->flowlabel = np->flow_label; IP6_ECN_flow_xmit(sk, fl6->flowlabel); + + if (sk->sk_protocol == IPPROTO_TCP) + ip6_ecmp_set_mp_hash(sock_net(sk), fl6, sk->sk_txhash); fl6->flowi6_oif = sk->sk_bound_dev_if; fl6->flowi6_mark = sk->sk_mark; fl6->fl6_sport = inet->inet_sport; diff --git a/net/ipv6/syncookies.c b/net/ipv6/syncookies.c index 4f6f0d751d6c..b581cb1ee2e8 100644 --- a/net/ipv6/syncookies.c +++ b/net/ipv6/syncookies.c @@ -245,6 +245,8 @@ struct sock *cookie_v6_check(struct sock *sk, struct sk_buff *skb) fl6.flowi6_uid = sk_uid(sk); security_req_classify_flow(req, flowi6_to_flowi_common(&fl6)); + ip6_ecmp_set_mp_hash(net, &fl6, tcp_rsk(req)->txhash); + dst = ip6_dst_lookup_flow(net, sk, &fl6, final_p); if (IS_ERR(dst)) { SKB_DR_SET(reason, IP_OUTNOROUTES); diff --git a/net/ipv6/tcp_ipv6.c b/net/ipv6/tcp_ipv6.c index c219f3334835..ebe161d72fbd 100644 --- a/net/ipv6/tcp_ipv6.c +++ b/net/ipv6/tcp_ipv6.c @@ -258,6 +258,8 @@ static int tcp_v6_connect(struct sock *sk, struct sockaddr_unsized *uaddr, if (!ipv6_addr_any(&sk->sk_v6_rcv_saddr)) saddr = &sk->sk_v6_rcv_saddr; + sk_set_txhash(sk); + fl6->flowi6_proto = IPPROTO_TCP; fl6->daddr = sk->sk_v6_daddr; fl6->saddr = saddr ? *saddr : np->saddr; @@ -275,6 +277,14 @@ static int tcp_v6_connect(struct sock *sk, struct sockaddr_unsized *uaddr, security_sk_classify_flow(sk, flowi6_to_flowi_common(fl6)); + /* Non-zero mp_hash bypasses rt6_multipath_hash() in + * fib6_select_path(), letting txhash control ECMP path + * selection so that sk_rethink_txhash() rehashes onto a + * different path. Policies 1-3 derive a deterministic + * hash from the flow keys and must not be overridden. + */ + ip6_ecmp_set_mp_hash(net, fl6, sk->sk_txhash); + dst = ip6_dst_lookup_flow(net, sk, fl6, final_p); if (IS_ERR(dst)) { err = PTR_ERR(dst); @@ -315,8 +325,6 @@ static int tcp_v6_connect(struct sock *sk, struct sockaddr_unsized *uaddr, if (err) goto late_failure; - sk_set_txhash(sk); - if (likely(!tp->repair)) { union tcp_seq_and_ts_off st; @@ -957,6 +965,13 @@ static void tcp_v6_send_response(const struct sock *sk, struct sk_buff *skb, u32 if (txhash) { /* autoflowlabel/skb_get_hash_flowi6 rely on buff->hash */ skb_set_hash(buff, txhash, PKT_HASH_TYPE_L4); + + /* Select the local ECMP path from the connection's txhash, + * so a control packet (RST, or ACK from a time-wait socket) + * uses the same nexthop as the data. Only policy 0 uses + * mp_hash; policies 1-3 derive a deterministic hash. + */ + ip6_ecmp_set_mp_hash(net, &fl6, txhash); } fl6.flowi6_mark = IP6_REPLY_MARK(net, skb->mark) ?: mark; fl6.fl6_dport = t1->dest; -- cgit v1.2.3 From b20aa9eded107bc78b7e54e64ccf79fbaeda9ecd Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Mon, 15 Jun 2026 12:44:09 -0700 Subject: atm: remove the unused send_oam / push_oam callbacks The atmdev_ops::send_oam device operation and the atm_vcc::push_oam callback were the kernel's interface for raw F4/F5 OAM cell exchange. Nothing assigns them a non-NULL value and nothing ever invokes them: the core only ever initialises push_oam to NULL (in vcc_create() and the AAL init helpers) and the Solos driver only lists send_oam = NULL for documentation. The drivers that actually drove OAM through these hooks were removed along with the legacy ATM adapters. Drop both callbacks and the NULL initialisers. Link: https://patch.msgid.link/20260615194416.752559-3-kuba@kernel.org Signed-off-by: Jakub Kicinski --- drivers/atm/solos-pci.c | 1 - include/linux/atmdev.h | 8 -------- net/atm/common.c | 1 - net/atm/raw.c | 2 -- 4 files changed, 12 deletions(-) (limited to 'include') diff --git a/drivers/atm/solos-pci.c b/drivers/atm/solos-pci.c index bcb1353877e4..4ad170a858ee 100644 --- a/drivers/atm/solos-pci.c +++ b/drivers/atm/solos-pci.c @@ -1180,7 +1180,6 @@ static const struct atmdev_ops fpga_ops = { .close = pclose, .ioctl = NULL, .send = psend, - .send_oam = NULL, .phy_put = NULL, .phy_get = NULL, .change_qos = NULL, diff --git a/include/linux/atmdev.h b/include/linux/atmdev.h index 82a32526df64..71c5bf6950e3 100644 --- a/include/linux/atmdev.h +++ b/include/linux/atmdev.h @@ -104,7 +104,6 @@ struct atm_vcc { void (*release_cb)(struct atm_vcc *vcc); /* release_sock callback */ void (*push)(struct atm_vcc *vcc,struct sk_buff *skb); void (*pop)(struct atm_vcc *vcc,struct sk_buff *skb); /* optional */ - int (*push_oam)(struct atm_vcc *vcc,void *cell); int (*send)(struct atm_vcc *vcc,struct sk_buff *skb); void *dev_data; /* per-device data */ void *proto_data; /* per-protocol data */ @@ -170,12 +169,6 @@ struct atm_dev { struct list_head dev_list; /* linkage */ }; - -/* OF: send_Oam Flags */ - -#define ATM_OF_IMMED 1 /* Attempt immediate delivery */ -#define ATM_OF_INRATE 2 /* Attempt in-rate delivery */ - struct atmdev_ops { /* only send is required */ void (*dev_close)(struct atm_dev *dev); int (*open)(struct atm_vcc *vcc); @@ -188,7 +181,6 @@ struct atmdev_ops { /* only send is required */ int (*pre_send)(struct atm_vcc *vcc, struct sk_buff *skb); int (*send)(struct atm_vcc *vcc,struct sk_buff *skb); int (*send_bh)(struct atm_vcc *vcc, struct sk_buff *skb); - int (*send_oam)(struct atm_vcc *vcc,void *cell,int flags); void (*phy_put)(struct atm_dev *dev,unsigned char value, unsigned long addr); unsigned char (*phy_get)(struct atm_dev *dev,unsigned long addr); diff --git a/net/atm/common.c b/net/atm/common.c index 913f7e32ce41..c6e87fc9bbfc 100644 --- a/net/atm/common.c +++ b/net/atm/common.c @@ -163,7 +163,6 @@ int vcc_create(struct net *net, struct socket *sock, int protocol, int family, i vcc->push = NULL; vcc->pop = NULL; vcc->owner = NULL; - vcc->push_oam = NULL; vcc->release_cb = NULL; vcc->vpi = vcc->vci = 0; /* no VCI/VPI yet */ vcc->atm_options = vcc->aal_options = 0; diff --git a/net/atm/raw.c b/net/atm/raw.c index 0d36aeb3671b..1d6ac7b0c4e5 100644 --- a/net/atm/raw.c +++ b/net/atm/raw.c @@ -63,7 +63,6 @@ int atm_init_aal0(struct atm_vcc *vcc) { vcc->push = atm_push_raw; vcc->pop = atm_pop_raw; - vcc->push_oam = NULL; vcc->send = atm_send_aal0; return 0; } @@ -72,7 +71,6 @@ int atm_init_aal5(struct atm_vcc *vcc) { vcc->push = atm_push_raw; vcc->pop = atm_pop_raw; - vcc->push_oam = NULL; if (vcc->dev->ops->send_bh) vcc->send = vcc->dev->ops->send_bh; else -- cgit v1.2.3 From a5a12d76d2cb23b3f2150ed3a5d674b0eba6a8b7 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Mon, 15 Jun 2026 12:44:11 -0700 Subject: atm: remove the local ATM (NSAP) address registry net/atm/addr.c maintained the per-device lists of local NSAP addresses (dev->local) and ILMI-learned LECS addresses (dev->lecs). These exist solely to serve SVC signaling: the lists are populated through the ATM_{ADD,DEL,RST}ADDR / ATM_{ADD,DEL,GET}LECSADDR ioctls used by the atmsigd / ILMI daemons, and consumed when registering addresses with the signaling daemon. The LECS list belonged to LAN Emulation, which has been removed. With no SVC users in a DSL-only configuration these lists are always empty, so drop the registry entirely: - remove the ADDR/LECSADDR/RSTADDR ioctls - drop the now-always-empty "atmaddress" sysfs attribute - remove the dev->local / dev->lecs lists, structs and enums - delete net/atm/addr.c and net/atm/addr.h The device ESI ("MAC" address) and its ATM_{G,S}ETESI ioctls and "address" sysfs attribute are retained - the USB DSL modems populate the ESI. Link: https://patch.msgid.link/20260615194416.752559-5-kuba@kernel.org Signed-off-by: Jakub Kicinski --- include/linux/atmdev.h | 9 --- net/atm/Makefile | 2 +- net/atm/addr.c | 162 ------------------------------------------------- net/atm/addr.h | 21 ------- net/atm/atm_sysfs.c | 25 -------- net/atm/common.c | 1 - net/atm/ioctl.c | 12 ---- net/atm/resources.c | 53 +--------------- net/atm/svc.c | 1 - 9 files changed, 2 insertions(+), 284 deletions(-) delete mode 100644 net/atm/addr.c delete mode 100644 net/atm/addr.h (limited to 'include') diff --git a/include/linux/atmdev.h b/include/linux/atmdev.h index 71c5bf6950e3..7abbd23fada6 100644 --- a/include/linux/atmdev.h +++ b/include/linux/atmdev.h @@ -136,13 +136,6 @@ static inline struct sock *sk_atm(struct atm_vcc *vcc) return (struct sock *)vcc; } -struct atm_dev_addr { - struct sockaddr_atmsvc addr; /* ATM address */ - struct list_head entry; /* next address */ -}; - -enum atm_addr_type_t { ATM_ADDR_LOCAL, ATM_ADDR_LECS }; - struct atm_dev { const struct atmdev_ops *ops; /* device operations; NULL if unused */ const struct atmphy_ops *phy; /* PHY operations, may be undefined */ @@ -152,8 +145,6 @@ struct atm_dev { void *dev_data; /* per-device data */ void *phy_data; /* private PHY data */ unsigned long flags; /* device flags (ATM_DF_*) */ - struct list_head local; /* local ATM addresses */ - struct list_head lecs; /* LECS ATM addresses learned via ILMI */ unsigned char esi[ESI_LEN]; /* ESI ("MAC" addr) */ struct atm_cirange ci_range; /* VPI/VCI range */ struct k_atm_dev_stats stats; /* statistics */ diff --git a/net/atm/Makefile b/net/atm/Makefile index 484a1b1552cc..5ed48d50df35 100644 --- a/net/atm/Makefile +++ b/net/atm/Makefile @@ -3,7 +3,7 @@ # Makefile for the ATM Protocol Families. # -atm-y := addr.o pvc.o signaling.o svc.o ioctl.o common.o atm_misc.o raw.o resources.o atm_sysfs.o +atm-y := pvc.o signaling.o svc.o ioctl.o common.o atm_misc.o raw.o resources.o atm_sysfs.o obj-$(CONFIG_ATM) += atm.o obj-$(CONFIG_ATM_BR2684) += br2684.o diff --git a/net/atm/addr.c b/net/atm/addr.c deleted file mode 100644 index 938f360ae230..000000000000 --- a/net/atm/addr.c +++ /dev/null @@ -1,162 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* net/atm/addr.c - Local ATM address registry */ - -/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */ - -#include -#include -#include -#include - -#include "signaling.h" -#include "addr.h" - -static int check_addr(const struct sockaddr_atmsvc *addr) -{ - int i; - - if (addr->sas_family != AF_ATMSVC) - return -EAFNOSUPPORT; - if (!*addr->sas_addr.pub) - return *addr->sas_addr.prv ? 0 : -EINVAL; - for (i = 1; i < ATM_E164_LEN + 1; i++) /* make sure it's \0-terminated */ - if (!addr->sas_addr.pub[i]) - return 0; - return -EINVAL; -} - -static int identical(const struct sockaddr_atmsvc *a, const struct sockaddr_atmsvc *b) -{ - if (*a->sas_addr.prv) - if (memcmp(a->sas_addr.prv, b->sas_addr.prv, ATM_ESA_LEN)) - return 0; - if (!*a->sas_addr.pub) - return !*b->sas_addr.pub; - if (!*b->sas_addr.pub) - return 0; - return !strcmp(a->sas_addr.pub, b->sas_addr.pub); -} - -static void notify_sigd(const struct atm_dev *dev) -{ - struct sockaddr_atmpvc pvc; - - pvc.sap_addr.itf = dev->number; - sigd_enq(NULL, as_itf_notify, NULL, &pvc, NULL); -} - -void atm_reset_addr(struct atm_dev *dev, enum atm_addr_type_t atype) -{ - unsigned long flags; - struct atm_dev_addr *this, *p; - struct list_head *head; - - spin_lock_irqsave(&dev->lock, flags); - if (atype == ATM_ADDR_LECS) - head = &dev->lecs; - else - head = &dev->local; - list_for_each_entry_safe(this, p, head, entry) { - list_del(&this->entry); - kfree(this); - } - spin_unlock_irqrestore(&dev->lock, flags); - if (head == &dev->local) - notify_sigd(dev); -} - -int atm_add_addr(struct atm_dev *dev, const struct sockaddr_atmsvc *addr, - enum atm_addr_type_t atype) -{ - unsigned long flags; - struct atm_dev_addr *this; - struct list_head *head; - int error; - - error = check_addr(addr); - if (error) - return error; - spin_lock_irqsave(&dev->lock, flags); - if (atype == ATM_ADDR_LECS) - head = &dev->lecs; - else - head = &dev->local; - list_for_each_entry(this, head, entry) { - if (identical(&this->addr, addr)) { - spin_unlock_irqrestore(&dev->lock, flags); - return -EEXIST; - } - } - this = kmalloc_obj(struct atm_dev_addr, GFP_ATOMIC); - if (!this) { - spin_unlock_irqrestore(&dev->lock, flags); - return -ENOMEM; - } - this->addr = *addr; - list_add(&this->entry, head); - spin_unlock_irqrestore(&dev->lock, flags); - if (head == &dev->local) - notify_sigd(dev); - return 0; -} - -int atm_del_addr(struct atm_dev *dev, const struct sockaddr_atmsvc *addr, - enum atm_addr_type_t atype) -{ - unsigned long flags; - struct atm_dev_addr *this; - struct list_head *head; - int error; - - error = check_addr(addr); - if (error) - return error; - spin_lock_irqsave(&dev->lock, flags); - if (atype == ATM_ADDR_LECS) - head = &dev->lecs; - else - head = &dev->local; - list_for_each_entry(this, head, entry) { - if (identical(&this->addr, addr)) { - list_del(&this->entry); - spin_unlock_irqrestore(&dev->lock, flags); - kfree(this); - if (head == &dev->local) - notify_sigd(dev); - return 0; - } - } - spin_unlock_irqrestore(&dev->lock, flags); - return -ENOENT; -} - -int atm_get_addr(struct atm_dev *dev, struct sockaddr_atmsvc __user * buf, - size_t size, enum atm_addr_type_t atype) -{ - unsigned long flags; - struct atm_dev_addr *this; - struct list_head *head; - int total = 0, error; - struct sockaddr_atmsvc *tmp_buf, *tmp_bufp; - - spin_lock_irqsave(&dev->lock, flags); - if (atype == ATM_ADDR_LECS) - head = &dev->lecs; - else - head = &dev->local; - list_for_each_entry(this, head, entry) - total += sizeof(struct sockaddr_atmsvc); - tmp_buf = tmp_bufp = kmalloc(total, GFP_ATOMIC); - if (!tmp_buf) { - spin_unlock_irqrestore(&dev->lock, flags); - return -ENOMEM; - } - list_for_each_entry(this, head, entry) - memcpy(tmp_bufp++, &this->addr, sizeof(struct sockaddr_atmsvc)); - spin_unlock_irqrestore(&dev->lock, flags); - error = total > size ? -E2BIG : total; - if (copy_to_user(buf, tmp_buf, total < size ? total : size)) - error = -EFAULT; - kfree(tmp_buf); - return error; -} diff --git a/net/atm/addr.h b/net/atm/addr.h deleted file mode 100644 index da3f848411a0..000000000000 --- a/net/atm/addr.h +++ /dev/null @@ -1,21 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* net/atm/addr.h - Local ATM address registry */ - -/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */ - - -#ifndef NET_ATM_ADDR_H -#define NET_ATM_ADDR_H - -#include -#include - -void atm_reset_addr(struct atm_dev *dev, enum atm_addr_type_t type); -int atm_add_addr(struct atm_dev *dev, const struct sockaddr_atmsvc *addr, - enum atm_addr_type_t type); -int atm_del_addr(struct atm_dev *dev, const struct sockaddr_atmsvc *addr, - enum atm_addr_type_t type); -int atm_get_addr(struct atm_dev *dev, struct sockaddr_atmsvc __user *buf, - size_t size, enum atm_addr_type_t type); - -#endif diff --git a/net/atm/atm_sysfs.c b/net/atm/atm_sysfs.c index 54e7fb1a4ee5..0676a9c333ff 100644 --- a/net/atm/atm_sysfs.c +++ b/net/atm/atm_sysfs.c @@ -27,29 +27,6 @@ static ssize_t address_show(struct device *cdev, return scnprintf(buf, PAGE_SIZE, "%pM\n", adev->esi); } -static ssize_t atmaddress_show(struct device *cdev, - struct device_attribute *attr, char *buf) -{ - unsigned long flags; - struct atm_dev *adev = to_atm_dev(cdev); - struct atm_dev_addr *aaddr; - int count = 0; - - spin_lock_irqsave(&adev->lock, flags); - list_for_each_entry(aaddr, &adev->local, entry) { - count += scnprintf(buf + count, PAGE_SIZE - count, - "%1phN.%2phN.%10phN.%6phN.%1phN\n", - &aaddr->addr.sas_addr.prv[0], - &aaddr->addr.sas_addr.prv[1], - &aaddr->addr.sas_addr.prv[3], - &aaddr->addr.sas_addr.prv[13], - &aaddr->addr.sas_addr.prv[19]); - } - spin_unlock_irqrestore(&adev->lock, flags); - - return count; -} - static ssize_t atmindex_show(struct device *cdev, struct device_attribute *attr, char *buf) { @@ -91,14 +68,12 @@ static ssize_t link_rate_show(struct device *cdev, } static DEVICE_ATTR_RO(address); -static DEVICE_ATTR_RO(atmaddress); static DEVICE_ATTR_RO(atmindex); static DEVICE_ATTR_RO(carrier); static DEVICE_ATTR_RO(type); static DEVICE_ATTR_RO(link_rate); static struct device_attribute *atm_attrs[] = { - &dev_attr_atmaddress, &dev_attr_address, &dev_attr_atmindex, &dev_attr_carrier, diff --git a/net/atm/common.c b/net/atm/common.c index c6e87fc9bbfc..7d5b7c39b80b 100644 --- a/net/atm/common.c +++ b/net/atm/common.c @@ -30,7 +30,6 @@ #include "resources.h" /* atm_find_dev */ #include "common.h" /* prototypes */ #include "protocols.h" /* atm_init_ */ -#include "addr.h" /* address registry */ #include "signaling.h" /* for WAITING and sigd_attach */ struct hlist_head vcc_hash[VCC_HTABLE_SIZE]; diff --git a/net/atm/ioctl.c b/net/atm/ioctl.c index 4f2d185bf2f0..97f20cd051ed 100644 --- a/net/atm/ioctl.c +++ b/net/atm/ioctl.c @@ -220,10 +220,6 @@ int vcc_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) #define ATM_GETNAMES32 _IOW('a', ATMIOC_ITF+3, struct compat_atm_iobuf) #define ATM_GETTYPE32 _IOW('a', ATMIOC_ITF+4, struct compat_atmif_sioc) #define ATM_GETESI32 _IOW('a', ATMIOC_ITF+5, struct compat_atmif_sioc) -#define ATM_GETADDR32 _IOW('a', ATMIOC_ITF+6, struct compat_atmif_sioc) -#define ATM_RSTADDR32 _IOW('a', ATMIOC_ITF+7, struct compat_atmif_sioc) -#define ATM_ADDADDR32 _IOW('a', ATMIOC_ITF+8, struct compat_atmif_sioc) -#define ATM_DELADDR32 _IOW('a', ATMIOC_ITF+9, struct compat_atmif_sioc) #define ATM_GETCIRANGE32 _IOW('a', ATMIOC_ITF+10, struct compat_atmif_sioc) #define ATM_SETCIRANGE32 _IOW('a', ATMIOC_ITF+11, struct compat_atmif_sioc) #define ATM_SETESI32 _IOW('a', ATMIOC_ITF+12, struct compat_atmif_sioc) @@ -242,10 +238,6 @@ static struct { { ATM_GETNAMES32, ATM_GETNAMES }, { ATM_GETTYPE32, ATM_GETTYPE }, { ATM_GETESI32, ATM_GETESI }, - { ATM_GETADDR32, ATM_GETADDR }, - { ATM_RSTADDR32, ATM_RSTADDR }, - { ATM_ADDADDR32, ATM_ADDADDR }, - { ATM_DELADDR32, ATM_DELADDR }, { ATM_GETCIRANGE32, ATM_GETCIRANGE }, { ATM_SETCIRANGE32, ATM_SETCIRANGE }, { ATM_SETESI32, ATM_SETESI }, @@ -305,10 +297,6 @@ static int do_atm_ioctl(struct socket *sock, unsigned int cmd32, case ATM_GETLINKRATE: case ATM_GETTYPE: case ATM_GETESI: - case ATM_GETADDR: - case ATM_RSTADDR: - case ATM_ADDADDR: - case ATM_DELADDR: case ATM_GETCIRANGE: case ATM_SETCIRANGE: case ATM_SETESI: diff --git a/net/atm/resources.c b/net/atm/resources.c index 7aac25e917b4..12aef5542263 100644 --- a/net/atm/resources.c +++ b/net/atm/resources.c @@ -25,7 +25,6 @@ #include "common.h" #include "resources.h" -#include "addr.h" LIST_HEAD(atm_devs); @@ -42,8 +41,6 @@ static struct atm_dev *__alloc_atm_dev(const char *type) dev->signal = ATM_PHY_SIG_UNKNOWN; dev->link_rate = ATM_OC3_PCR; spin_lock_init(&dev->lock); - INIT_LIST_HEAD(&dev->local); - INIT_LIST_HEAD(&dev->lecs); return dev; } @@ -227,11 +224,8 @@ int atm_getnames(void __user *buf, int __user *iobuf_len) int atm_dev_ioctl(unsigned int cmd, void __user *buf, int __user *sioc_len, int number, int compat) { - int error, len, size = 0; struct atm_dev *dev; - - if (get_user(len, sioc_len)) - return -EFAULT; + int error, size = 0; dev = try_then_request_module(atm_dev_lookup(number), "atm-device-%d", number); @@ -306,51 +300,6 @@ int atm_dev_ioctl(unsigned int cmd, void __user *buf, int __user *sioc_len, goto done; } break; - case ATM_RSTADDR: - if (!capable(CAP_NET_ADMIN)) { - error = -EPERM; - goto done; - } - atm_reset_addr(dev, ATM_ADDR_LOCAL); - break; - case ATM_ADDADDR: - case ATM_DELADDR: - case ATM_ADDLECSADDR: - case ATM_DELLECSADDR: - { - struct sockaddr_atmsvc addr; - - if (!capable(CAP_NET_ADMIN)) { - error = -EPERM; - goto done; - } - - if (copy_from_user(&addr, buf, sizeof(addr))) { - error = -EFAULT; - goto done; - } - if (cmd == ATM_ADDADDR || cmd == ATM_ADDLECSADDR) - error = atm_add_addr(dev, &addr, - (cmd == ATM_ADDADDR ? - ATM_ADDR_LOCAL : ATM_ADDR_LECS)); - else - error = atm_del_addr(dev, &addr, - (cmd == ATM_DELADDR ? - ATM_ADDR_LOCAL : ATM_ADDR_LECS)); - goto done; - } - case ATM_GETADDR: - case ATM_GETLECSADDR: - error = atm_get_addr(dev, buf, len, - (cmd == ATM_GETADDR ? - ATM_ADDR_LOCAL : ATM_ADDR_LECS)); - if (error < 0) - goto done; - size = error; - /* may return 0, but later on size == 0 means "don't - write the length" */ - error = put_user(size, sioc_len) ? -EFAULT : 0; - goto done; case ATM_SETLOOP: if (__ATM_LM_XTRMT((int) (unsigned long) buf) && __ATM_LM_XTLOC((int) (unsigned long) buf) > diff --git a/net/atm/svc.c b/net/atm/svc.c index 7c5559f50a99..270e95154a2b 100644 --- a/net/atm/svc.c +++ b/net/atm/svc.c @@ -27,7 +27,6 @@ #include "resources.h" #include "common.h" /* common for PVCs and SVCs */ #include "signaling.h" -#include "addr.h" #ifdef CONFIG_COMPAT /* It actually takes struct sockaddr_atmsvc, not struct atm_iobuf */ -- cgit v1.2.3 From aa582dc25ace8951ad595c71fb93d21ed2ec4624 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Mon, 15 Jun 2026 12:44:12 -0700 Subject: atm: remove SVC socket support and the signaling daemon interface ATM switched virtual circuits (SVCs) are set up and torn down by a user-space signaling daemon (atmsigd) which the kernel talks to over a dedicated "sigd" socket: the kernel marshals Q.2931-style requests (as_connect, as_listen, as_accept, as_close, ...) to the daemon and applies the results to PF_ATMSVC sockets. This is the machinery behind classical SVC use and was the foundation for LANE / MPOA, all of which have been removed. DSL deployments do not use any of this. PPPoATM and BR2684 run over permanent virtual circuits (PF_ATMPVC) with a statically configured VPI/VCI; no atmsigd, no Q.2931. Neither remaining ATM driver (solos-pci, the USB DSL modems) is reachable through the SVC path. Remove the SVC socket family and the signaling interface: - delete net/atm/svc.c, net/atm/signaling.c and signaling.h - drop atmsvc_init()/atmsvc_exit() and the PF_ATMSVC registration and module alias - drop the ATMSIGD_CTRL ioctl (sigd_attach) and the /proc/net/atm/svc file - fold the SVC branch out of atm_change_qos(); all sockets are PVCs now The obsolete ATM_SETSC ioctl stub is left in place (it already just warns and returns 0), as is the struct atm_vcc SVC bookkeeping shared with the queueing layer. Link: https://patch.msgid.link/20260615194416.752559-6-kuba@kernel.org Signed-off-by: Jakub Kicinski --- include/linux/atmdev.h | 38 +-- net/atm/Makefile | 2 +- net/atm/br2684.c | 3 +- net/atm/common.c | 35 +-- net/atm/common.h | 5 - net/atm/ioctl.c | 32 --- net/atm/pppoatm.c | 3 +- net/atm/proc.c | 56 ---- net/atm/resources.c | 1 - net/atm/signaling.c | 297 --------------------- net/atm/signaling.h | 31 --- net/atm/svc.c | 694 ------------------------------------------------- 12 files changed, 12 insertions(+), 1185 deletions(-) delete mode 100644 net/atm/signaling.c delete mode 100644 net/atm/signaling.h delete mode 100644 net/atm/svc.c (limited to 'include') diff --git a/include/linux/atmdev.h b/include/linux/atmdev.h index 7abbd23fada6..59477676063c 100644 --- a/include/linux/atmdev.h +++ b/include/linux/atmdev.h @@ -51,33 +51,11 @@ enum { driver, cleared by anybody. */ ATM_VF_PARTIAL, /* resources are bound to PVC (partial PVC setup), controlled by socket layer */ - ATM_VF_REGIS, /* registered with demon, controlled by SVC - socket layer */ - ATM_VF_BOUND, /* local SAP is set, controlled by SVC socket - layer */ - ATM_VF_RELEASED, /* demon has indicated/requested release, - controlled by SVC socket layer */ ATM_VF_HASQOS, /* QOS parameters have been set */ - ATM_VF_LISTEN, /* socket is used for listening */ - ATM_VF_META, /* SVC socket isn't used for normal data - traffic and doesn't depend on signaling - to be available */ - ATM_VF_SESSION, /* VCC is p2mp session control descriptor */ - ATM_VF_HASSAP, /* SAP has been set */ - ATM_VF_CLOSE, /* asynchronous close - treat like VF_RELEASED*/ - ATM_VF_WAITING, /* waiting for reply from sigd */ - ATM_VF_IS_CLIP, /* in use by CLIP protocol */ + ATM_VF_CLOSE, /* asynchronous close - VC is being torn down */ }; -#define ATM_VF2VS(flags) \ - (test_bit(ATM_VF_READY,&(flags)) ? ATM_VS_CONNECTED : \ - test_bit(ATM_VF_RELEASED,&(flags)) ? ATM_VS_CLOSING : \ - test_bit(ATM_VF_LISTEN,&(flags)) ? ATM_VS_LISTEN : \ - test_bit(ATM_VF_REGIS,&(flags)) ? ATM_VS_INUSE : \ - test_bit(ATM_VF_BOUND,&(flags)) ? ATM_VS_BOUND : ATM_VS_IDLE) - - enum { ATM_DF_REMOVED, /* device was removed from atm_devs list */ }; @@ -100,7 +78,6 @@ struct atm_vcc { unsigned long atm_options; /* ATM layer options */ struct atm_dev *dev; /* device back pointer */ struct atm_qos qos; /* QOS */ - struct atm_sap sap; /* SAP */ void (*release_cb)(struct atm_vcc *vcc); /* release_sock callback */ void (*push)(struct atm_vcc *vcc,struct sk_buff *skb); void (*pop)(struct atm_vcc *vcc,struct sk_buff *skb); /* optional */ @@ -109,16 +86,8 @@ struct atm_vcc { void *proto_data; /* per-protocol data */ struct k_atm_aal_stats *stats; /* pointer to AAL stats group */ struct module *owner; /* owner of ->push function */ - /* SVC part --- may move later ------------------------------------- */ - short itf; /* interface number */ - struct sockaddr_atmsvc local; - struct sockaddr_atmsvc remote; - /* Multipoint part ------------------------------------------------- */ - struct atm_vcc *session; /* session VCC descriptor */ - /* Other stuff ----------------------------------------------------- */ - void *user_back; /* user backlink - not touched by */ - /* native ATM stack. Currently used */ - /* by CLIP and sch_atm. */ + void *user_back; /* user backlink - not touched by the */ + /* native ATM stack, used by sch_atm */ }; static inline struct atm_vcc *atm_sk(struct sock *sk) @@ -151,7 +120,6 @@ struct atm_dev { char signal; /* signal status (ATM_PHY_SIG_*) */ int link_rate; /* link rate (default: OC3) */ refcount_t refcnt; /* reference count */ - spinlock_t lock; /* protect internal members */ #ifdef CONFIG_PROC_FS struct proc_dir_entry *proc_entry; /* proc entry */ char *proc_name; /* proc entry name */ diff --git a/net/atm/Makefile b/net/atm/Makefile index 5ed48d50df35..0a14ea7d4683 100644 --- a/net/atm/Makefile +++ b/net/atm/Makefile @@ -3,7 +3,7 @@ # Makefile for the ATM Protocol Families. # -atm-y := pvc.o signaling.o svc.o ioctl.o common.o atm_misc.o raw.o resources.o atm_sysfs.o +atm-y := pvc.o ioctl.o common.o atm_misc.o raw.o resources.o atm_sysfs.o obj-$(CONFIG_ATM) += atm.o obj-$(CONFIG_ATM_BR2684) += br2684.o diff --git a/net/atm/br2684.c b/net/atm/br2684.c index 6580d67c3456..05712c28386a 100644 --- a/net/atm/br2684.c +++ b/net/atm/br2684.c @@ -311,8 +311,7 @@ static netdev_tx_t br2684_start_xmit(struct sk_buff *skb, bh_lock_sock(sk_atm(atmvcc)); - if (test_bit(ATM_VF_RELEASED, &atmvcc->flags) || - test_bit(ATM_VF_CLOSE, &atmvcc->flags) || + if (test_bit(ATM_VF_CLOSE, &atmvcc->flags) || !test_bit(ATM_VF_READY, &atmvcc->flags)) { dev->stats.tx_dropped++; dev_kfree_skb(skb); diff --git a/net/atm/common.c b/net/atm/common.c index 7d5b7c39b80b..650814d0a56c 100644 --- a/net/atm/common.c +++ b/net/atm/common.c @@ -30,7 +30,6 @@ #include "resources.h" /* atm_find_dev */ #include "common.h" /* prototypes */ #include "protocols.h" /* atm_init_ */ -#include "signaling.h" /* for WAITING and sigd_attach */ struct hlist_head vcc_hash[VCC_HTABLE_SIZE]; EXPORT_SYMBOL(vcc_hash); @@ -154,8 +153,6 @@ int vcc_create(struct net *net, struct socket *sock, int protocol, int family, i vcc = atm_sk(sk); vcc->dev = NULL; - memset(&vcc->local, 0, sizeof(struct sockaddr_atmsvc)); - memset(&vcc->remote, 0, sizeof(struct sockaddr_atmsvc)); vcc->qos.txtp.max_sdu = 1 << 16; /* for meta VCs */ refcount_set(&sk->sk_wmem_alloc, SK_WMEM_ALLOC_BIAS); atomic_set(&sk->sk_rmem_alloc, 0); @@ -216,7 +213,6 @@ void vcc_release_async(struct atm_vcc *vcc, int reply) set_bit(ATM_VF_CLOSE, &vcc->flags); sk->sk_shutdown |= RCV_SHUTDOWN; sk->sk_err = -reply; - clear_bit(ATM_VF_WAITING, &vcc->flags); sk->sk_state_change(sk); } EXPORT_SYMBOL(vcc_release_async); @@ -527,8 +523,7 @@ int vcc_recvmsg(struct socket *sock, struct msghdr *msg, size_t size, return -EOPNOTSUPP; vcc = ATM_SD(sock); - if (test_bit(ATM_VF_RELEASED, &vcc->flags) || - test_bit(ATM_VF_CLOSE, &vcc->flags) || + if (test_bit(ATM_VF_CLOSE, &vcc->flags) || !test_bit(ATM_VF_READY, &vcc->flags)) return 0; @@ -575,8 +570,7 @@ int vcc_sendmsg(struct socket *sock, struct msghdr *m, size_t size) goto out; } vcc = ATM_SD(sock); - if (test_bit(ATM_VF_RELEASED, &vcc->flags) || - test_bit(ATM_VF_CLOSE, &vcc->flags) || + if (test_bit(ATM_VF_CLOSE, &vcc->flags) || !test_bit(ATM_VF_READY, &vcc->flags)) { error = -EPIPE; send_sig(SIGPIPE, current, 0); @@ -604,8 +598,7 @@ int vcc_sendmsg(struct socket *sock, struct msghdr *m, size_t size) error = -ERESTARTSYS; break; } - if (test_bit(ATM_VF_RELEASED, &vcc->flags) || - test_bit(ATM_VF_CLOSE, &vcc->flags) || + if (test_bit(ATM_VF_CLOSE, &vcc->flags) || !test_bit(ATM_VF_READY, &vcc->flags)) { error = -EPIPE; send_sig(SIGPIPE, current, 0); @@ -665,8 +658,7 @@ __poll_t vcc_poll(struct file *file, struct socket *sock, poll_table *wait) if (sk->sk_err) mask = EPOLLERR; - if (test_bit(ATM_VF_RELEASED, &vcc->flags) || - test_bit(ATM_VF_CLOSE, &vcc->flags)) + if (test_bit(ATM_VF_CLOSE, &vcc->flags)) mask |= EPOLLHUP; /* readable? */ @@ -674,10 +666,6 @@ __poll_t vcc_poll(struct file *file, struct socket *sock, poll_table *wait) mask |= EPOLLIN | EPOLLRDNORM; /* writable? */ - if (sock->state == SS_CONNECTING && - test_bit(ATM_VF_WAITING, &vcc->flags)) - return mask; - if (vcc->qos.txtp.traffic_class != ATM_NONE && vcc_writable(sk)) mask |= EPOLLOUT | EPOLLWRNORM | EPOLLWRBAND; @@ -704,9 +692,7 @@ static int atm_change_qos(struct atm_vcc *vcc, struct atm_qos *qos) return error; if (!vcc->dev->ops->change_qos) return -EOPNOTSUPP; - if (sk_atm(vcc)->sk_family == AF_ATMPVC) - return vcc->dev->ops->change_qos(vcc, qos, ATM_MF_SET); - return svc_change_qos(vcc, qos); + return vcc->dev->ops->change_qos(vcc, qos, ATM_MF_SET); } static int check_tp(const struct atm_trafprm *tp) @@ -854,15 +840,10 @@ static int __init atm_init(void) pr_err("atmpvc_init() failed with %d\n", error); goto out_unregister_vcc_proto; } - error = atmsvc_init(); - if (error < 0) { - pr_err("atmsvc_init() failed with %d\n", error); - goto out_atmpvc_exit; - } error = atm_proc_init(); if (error < 0) { pr_err("atm_proc_init() failed with %d\n", error); - goto out_atmsvc_exit; + goto out_atmpvc_exit; } error = atm_sysfs_init(); if (error < 0) { @@ -873,8 +854,6 @@ out: return error; out_atmproc_exit: atm_proc_exit(); -out_atmsvc_exit: - atmsvc_exit(); out_atmpvc_exit: atmpvc_exit(); out_unregister_vcc_proto: @@ -886,7 +865,6 @@ static void __exit atm_exit(void) { atm_proc_exit(); atm_sysfs_exit(); - atmsvc_exit(); atmpvc_exit(); proto_unregister(&vcc_proto); } @@ -898,4 +876,3 @@ module_exit(atm_exit); MODULE_DESCRIPTION("Asynchronous Transfer Mode (ATM) networking core"); MODULE_LICENSE("GPL"); MODULE_ALIAS_NETPROTO(PF_ATMPVC); -MODULE_ALIAS_NETPROTO(PF_ATMSVC); diff --git a/net/atm/common.h b/net/atm/common.h index ae4502abf028..11cb51dd7dbb 100644 --- a/net/atm/common.h +++ b/net/atm/common.h @@ -28,8 +28,6 @@ void vcc_process_recv_queue(struct atm_vcc *vcc); int atmpvc_init(void); void atmpvc_exit(void); -int atmsvc_init(void); -void atmsvc_exit(void); int atm_sysfs_init(void); void atm_sysfs_exit(void); @@ -48,9 +46,6 @@ static inline void atm_proc_exit(void) } #endif /* CONFIG_PROC_FS */ -/* SVC */ -int svc_change_qos(struct atm_vcc *vcc,struct atm_qos *qos); - void atm_dev_release_vccs(struct atm_dev *dev); #endif diff --git a/net/atm/ioctl.c b/net/atm/ioctl.c index 97f20cd051ed..11f6d236f5d6 100644 --- a/net/atm/ioctl.c +++ b/net/atm/ioctl.c @@ -13,13 +13,11 @@ #include #include /* manifest constants */ #include -#include #include #include #include #include "resources.h" -#include "signaling.h" /* for WAITING and sigd_attach */ #include "common.h" @@ -86,36 +84,6 @@ static int do_vcc_ioctl(struct socket *sock, unsigned int cmd, current->comm, task_pid_nr(current)); error = 0; goto done; - case ATMSIGD_CTRL: - if (!capable(CAP_NET_ADMIN)) { - error = -EPERM; - goto done; - } - /* - * The user/kernel protocol for exchanging signalling - * info uses kernel pointers as opaque references, - * so the holder of the file descriptor can scribble - * on the kernel... so we should make sure that we - * have the same privileges that /proc/kcore needs - */ - if (!capable(CAP_SYS_RAWIO)) { - error = -EPERM; - goto done; - } -#ifdef CONFIG_COMPAT - /* WTF? I don't even want to _think_ about making this - work for 32-bit userspace. TBH I don't really want - to think about it at all. dwmw2. */ - if (compat) { - net_warn_ratelimited("32-bit task cannot be atmsigd\n"); - error = -EINVAL; - goto done; - } -#endif - error = sigd_attach(vcc); - if (!error) - sock->state = SS_CONNECTED; - goto done; case ATM_SETBACKEND: case ATM_NEWBACKENDIF: { diff --git a/net/atm/pppoatm.c b/net/atm/pppoatm.c index e3c422dc533a..6da52d12df68 100644 --- a/net/atm/pppoatm.c +++ b/net/atm/pppoatm.c @@ -308,8 +308,7 @@ static int pppoatm_send(struct ppp_channel *chan, struct sk_buff *skb) test_and_set_bit(BLOCKED, &pvcc->blocked); goto nospace; } - if (test_bit(ATM_VF_RELEASED, &vcc->flags) || - test_bit(ATM_VF_CLOSE, &vcc->flags) || + if (test_bit(ATM_VF_CLOSE, &vcc->flags) || !test_bit(ATM_VF_READY, &vcc->flags)) { bh_unlock_sock(sk_atm(vcc)); kfree_skb(skb); diff --git a/net/atm/proc.c b/net/atm/proc.c index b650da764a23..8f20b49b9c02 100644 --- a/net/atm/proc.c +++ b/net/atm/proc.c @@ -29,7 +29,6 @@ #include #include "resources.h" #include "common.h" /* atm_proc_init prototype */ -#include "signaling.h" /* to get sigd - ugly too */ static ssize_t proc_dev_atm_read(struct file *file, char __user *buf, size_t count, loff_t *pos); @@ -156,13 +155,6 @@ static void pvc_info(struct seq_file *seq, struct atm_vcc *vcc) seq_putc(seq, '\n'); } -static const char *vcc_state(struct atm_vcc *vcc) -{ - static const char *const map[] = { ATM_VS2TXT_MAP }; - - return map[ATM_VF2VS(vcc->flags)]; -} - static void vcc_info(struct seq_file *seq, struct atm_vcc *vcc) { struct sock *sk = sk_atm(vcc); @@ -177,9 +169,6 @@ static void vcc_info(struct seq_file *seq, struct atm_vcc *vcc) case AF_ATMPVC: seq_printf(seq, "PVC"); break; - case AF_ATMSVC: - seq_printf(seq, "SVC"); - break; default: seq_printf(seq, "%3d", sk->sk_family); } @@ -190,26 +179,6 @@ static void vcc_info(struct seq_file *seq, struct atm_vcc *vcc) refcount_read(&sk->sk_refcnt)); } -static void svc_info(struct seq_file *seq, struct atm_vcc *vcc) -{ - if (!vcc->dev) - seq_printf(seq, sizeof(void *) == 4 ? - "N/A@%pK%10s" : "N/A@%pK%2s", vcc, ""); - else - seq_printf(seq, "%3d %3d %5d ", - vcc->dev->number, vcc->vpi, vcc->vci); - seq_printf(seq, "%-10s ", vcc_state(vcc)); - seq_printf(seq, "%s%s", vcc->remote.sas_addr.pub, - *vcc->remote.sas_addr.pub && *vcc->remote.sas_addr.prv ? "+" : ""); - if (*vcc->remote.sas_addr.prv) { - int i; - - for (i = 0; i < ATM_ESA_LEN; i++) - seq_printf(seq, "%02x", vcc->remote.sas_addr.prv[i]); - } - seq_putc(seq, '\n'); -} - static int atm_dev_seq_show(struct seq_file *seq, void *v) { static char atm_dev_banner[] = @@ -278,29 +247,6 @@ static const struct seq_operations vcc_seq_ops = { .show = vcc_seq_show, }; -static int svc_seq_show(struct seq_file *seq, void *v) -{ - static const char atm_svc_banner[] = - "Itf VPI VCI State Remote\n"; - - if (v == SEQ_START_TOKEN) - seq_puts(seq, atm_svc_banner); - else { - struct vcc_state *state = seq->private; - struct atm_vcc *vcc = atm_sk(state->sk); - - svc_info(seq, vcc); - } - return 0; -} - -static const struct seq_operations svc_seq_ops = { - .start = vcc_seq_start, - .next = vcc_seq_next, - .stop = vcc_seq_stop, - .show = svc_seq_show, -}; - static ssize_t proc_dev_atm_read(struct file *file, char __user *buf, size_t count, loff_t *pos) { @@ -376,8 +322,6 @@ int __init atm_proc_init(void) proc_create_seq("devices", 0444, atm_proc_root, &atm_dev_seq_ops); proc_create_seq_private("pvc", 0444, atm_proc_root, &pvc_seq_ops, sizeof(struct vcc_state), (void *)(uintptr_t)PF_ATMPVC); - proc_create_seq_private("svc", 0444, atm_proc_root, &svc_seq_ops, - sizeof(struct vcc_state), (void *)(uintptr_t)PF_ATMSVC); proc_create_seq_private("vc", 0444, atm_proc_root, &vcc_seq_ops, sizeof(struct vcc_state), NULL); return 0; diff --git a/net/atm/resources.c b/net/atm/resources.c index 12aef5542263..d94dc8221ea1 100644 --- a/net/atm/resources.c +++ b/net/atm/resources.c @@ -40,7 +40,6 @@ static struct atm_dev *__alloc_atm_dev(const char *type) dev->type = type; dev->signal = ATM_PHY_SIG_UNKNOWN; dev->link_rate = ATM_OC3_PCR; - spin_lock_init(&dev->lock); return dev; } diff --git a/net/atm/signaling.c b/net/atm/signaling.c deleted file mode 100644 index b991d937205a..000000000000 --- a/net/atm/signaling.c +++ /dev/null @@ -1,297 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* net/atm/signaling.c - ATM signaling */ - -/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */ - -#define pr_fmt(fmt) KBUILD_MODNAME ":%s: " fmt, __func__ - -#include /* error codes */ -#include /* printk */ -#include -#include -#include /* jiffies and HZ */ -#include /* ATM stuff */ -#include -#include -#include -#include -#include - -#include "resources.h" -#include "signaling.h" - -struct atm_vcc *sigd = NULL; - -/* - * find_get_vcc - validate and get a reference to a vcc pointer - * @vcc: the vcc pointer to validate - * - * This function validates that @vcc points to a registered VCC in vcc_hash. - * If found, it increments the socket reference count and returns the vcc. - * The caller must call sock_put(sk_atm(vcc)) when done. - * - * Returns the vcc pointer if valid, NULL otherwise. - */ -static struct atm_vcc *find_get_vcc(struct atm_vcc *vcc) -{ - int i; - - read_lock(&vcc_sklist_lock); - for (i = 0; i < VCC_HTABLE_SIZE; i++) { - struct sock *s; - - sk_for_each(s, &vcc_hash[i]) { - if (atm_sk(s) == vcc) { - sock_hold(s); - read_unlock(&vcc_sklist_lock); - return vcc; - } - } - } - read_unlock(&vcc_sklist_lock); - return NULL; -} - -static void sigd_put_skb(struct sk_buff *skb) -{ - if (!sigd) { - pr_debug("atmsvc: no signaling daemon\n"); - kfree_skb(skb); - return; - } - atm_force_charge(sigd, skb->truesize); - skb_queue_tail(&sk_atm(sigd)->sk_receive_queue, skb); - sk_atm(sigd)->sk_data_ready(sk_atm(sigd)); -} - -static void modify_qos(struct atm_vcc *vcc, struct atmsvc_msg *msg) -{ - struct sk_buff *skb; - - if (test_bit(ATM_VF_RELEASED, &vcc->flags) || - !test_bit(ATM_VF_READY, &vcc->flags)) - return; - msg->type = as_error; - if (!vcc->dev->ops->change_qos) - msg->reply = -EOPNOTSUPP; - else { - /* should lock VCC */ - msg->reply = vcc->dev->ops->change_qos(vcc, &msg->qos, - msg->reply); - if (!msg->reply) - msg->type = as_okay; - } - /* - * Should probably just turn around the old skb. But then, the buffer - * space accounting needs to follow the change too. Maybe later. - */ - while (!(skb = alloc_skb(sizeof(struct atmsvc_msg), GFP_KERNEL))) - schedule(); - *(struct atmsvc_msg *)skb_put(skb, sizeof(struct atmsvc_msg)) = *msg; - sigd_put_skb(skb); -} - -static int sigd_send(struct atm_vcc *vcc, struct sk_buff *skb) -{ - struct atmsvc_msg *msg; - struct atm_vcc *session_vcc; - struct sock *sk; - - msg = (struct atmsvc_msg *) skb->data; - WARN_ON(refcount_sub_and_test(skb->truesize, &sk_atm(vcc)->sk_wmem_alloc)); - - vcc = find_get_vcc(*(struct atm_vcc **)&msg->vcc); - if (!vcc) { - pr_debug("invalid vcc pointer in msg\n"); - dev_kfree_skb(skb); - return -EINVAL; - } - - pr_debug("%d (0x%lx)\n", (int)msg->type, (unsigned long)vcc); - sk = sk_atm(vcc); - - switch (msg->type) { - case as_okay: - sk->sk_err = -msg->reply; - clear_bit(ATM_VF_WAITING, &vcc->flags); - if (!*vcc->local.sas_addr.prv && !*vcc->local.sas_addr.pub) { - vcc->local.sas_family = AF_ATMSVC; - memcpy(vcc->local.sas_addr.prv, - msg->local.sas_addr.prv, ATM_ESA_LEN); - memcpy(vcc->local.sas_addr.pub, - msg->local.sas_addr.pub, ATM_E164_LEN + 1); - } - session_vcc = vcc->session ? vcc->session : vcc; - if (session_vcc->vpi || session_vcc->vci) - break; - session_vcc->itf = msg->pvc.sap_addr.itf; - session_vcc->vpi = msg->pvc.sap_addr.vpi; - session_vcc->vci = msg->pvc.sap_addr.vci; - if (session_vcc->vpi || session_vcc->vci) - session_vcc->qos = msg->qos; - break; - case as_error: - clear_bit(ATM_VF_REGIS, &vcc->flags); - clear_bit(ATM_VF_READY, &vcc->flags); - sk->sk_err = -msg->reply; - clear_bit(ATM_VF_WAITING, &vcc->flags); - break; - case as_indicate: - /* Release the reference from msg->vcc, we'll use msg->listen_vcc instead */ - sock_put(sk); - - vcc = find_get_vcc(*(struct atm_vcc **)&msg->listen_vcc); - if (!vcc) { - pr_debug("invalid listen_vcc pointer in msg\n"); - dev_kfree_skb(skb); - return -EINVAL; - } - - sk = sk_atm(vcc); - pr_debug("as_indicate!!!\n"); - lock_sock(sk); - if (sk_acceptq_is_full(sk)) { - sigd_enq(NULL, as_reject, vcc, NULL, NULL); - dev_kfree_skb(skb); - goto as_indicate_complete; - } - sk_acceptq_added(sk); - skb_queue_tail(&sk->sk_receive_queue, skb); - pr_debug("waking sk_sleep(sk) 0x%p\n", sk_sleep(sk)); - sk->sk_state_change(sk); -as_indicate_complete: - release_sock(sk); - /* Paired with find_get_vcc(msg->listen_vcc) above */ - sock_put(sk); - return 0; - case as_close: - set_bit(ATM_VF_RELEASED, &vcc->flags); - vcc_release_async(vcc, msg->reply); - goto out; - case as_modify: - modify_qos(vcc, msg); - break; - case as_addparty: - case as_dropparty: - WRITE_ONCE(sk->sk_err_soft, -msg->reply); - /* < 0 failure, otherwise ep_ref */ - clear_bit(ATM_VF_WAITING, &vcc->flags); - break; - default: - pr_alert("bad message type %d\n", (int)msg->type); - dev_kfree_skb(skb); - /* Paired with find_get_vcc(msg->vcc) above */ - sock_put(sk); - return -EINVAL; - } - sk->sk_state_change(sk); -out: - dev_kfree_skb(skb); - /* Paired with find_get_vcc(msg->vcc) above */ - sock_put(sk); - return 0; -} - -void sigd_enq2(struct atm_vcc *vcc, enum atmsvc_msg_type type, - struct atm_vcc *listen_vcc, const struct sockaddr_atmpvc *pvc, - const struct sockaddr_atmsvc *svc, const struct atm_qos *qos, - int reply) -{ - struct sk_buff *skb; - struct atmsvc_msg *msg; - static unsigned int session = 0; - - pr_debug("%d (0x%p)\n", (int)type, vcc); - while (!(skb = alloc_skb(sizeof(struct atmsvc_msg), GFP_KERNEL))) - schedule(); - msg = skb_put_zero(skb, sizeof(struct atmsvc_msg)); - msg->type = type; - *(struct atm_vcc **) &msg->vcc = vcc; - *(struct atm_vcc **) &msg->listen_vcc = listen_vcc; - msg->reply = reply; - if (qos) - msg->qos = *qos; - if (vcc) - msg->sap = vcc->sap; - if (svc) - msg->svc = *svc; - if (vcc) - msg->local = vcc->local; - if (pvc) - msg->pvc = *pvc; - if (vcc) { - if (type == as_connect && test_bit(ATM_VF_SESSION, &vcc->flags)) - msg->session = ++session; - /* every new pmp connect gets the next session number */ - } - sigd_put_skb(skb); - if (vcc) - set_bit(ATM_VF_REGIS, &vcc->flags); -} - -void sigd_enq(struct atm_vcc *vcc, enum atmsvc_msg_type type, - struct atm_vcc *listen_vcc, const struct sockaddr_atmpvc *pvc, - const struct sockaddr_atmsvc *svc) -{ - sigd_enq2(vcc, type, listen_vcc, pvc, svc, vcc ? &vcc->qos : NULL, 0); - /* other ISP applications may use "reply" */ -} - -static void purge_vcc(struct atm_vcc *vcc) -{ - if (sk_atm(vcc)->sk_family == PF_ATMSVC && - !test_bit(ATM_VF_META, &vcc->flags)) { - set_bit(ATM_VF_RELEASED, &vcc->flags); - clear_bit(ATM_VF_REGIS, &vcc->flags); - vcc_release_async(vcc, -EUNATCH); - } -} - -static void sigd_close(struct atm_vcc *vcc) -{ - struct sock *s; - int i; - - pr_debug("\n"); - sigd = NULL; - if (skb_peek(&sk_atm(vcc)->sk_receive_queue)) - pr_err("closing with requests pending\n"); - skb_queue_purge(&sk_atm(vcc)->sk_receive_queue); - - read_lock(&vcc_sklist_lock); - for (i = 0; i < VCC_HTABLE_SIZE; ++i) { - struct hlist_head *head = &vcc_hash[i]; - - sk_for_each(s, head) { - vcc = atm_sk(s); - - purge_vcc(vcc); - } - } - read_unlock(&vcc_sklist_lock); -} - -static const struct atmdev_ops sigd_dev_ops = { - .close = sigd_close, - .send = sigd_send -}; - -static struct atm_dev sigd_dev = { - .ops = &sigd_dev_ops, - .type = "sig", - .number = 999, - .lock = __SPIN_LOCK_UNLOCKED(sigd_dev.lock) -}; - -int sigd_attach(struct atm_vcc *vcc) -{ - if (sigd) - return -EADDRINUSE; - pr_debug("\n"); - sigd = vcc; - vcc->dev = &sigd_dev; - vcc_insert_socket(sk_atm(vcc)); - set_bit(ATM_VF_META, &vcc->flags); - set_bit(ATM_VF_READY, &vcc->flags); - return 0; -} diff --git a/net/atm/signaling.h b/net/atm/signaling.h deleted file mode 100644 index 2df8220f7ab5..000000000000 --- a/net/atm/signaling.h +++ /dev/null @@ -1,31 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* net/atm/signaling.h - ATM signaling */ - -/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */ - - -#ifndef NET_ATM_SIGNALING_H -#define NET_ATM_SIGNALING_H - -#include -#include -#include - - -extern struct atm_vcc *sigd; /* needed in svc_release */ - - -/* - * sigd_enq is a wrapper for sigd_enq2, covering the more common cases, and - * avoiding huge lists of null values. - */ - -void sigd_enq2(struct atm_vcc *vcc,enum atmsvc_msg_type type, - struct atm_vcc *listen_vcc,const struct sockaddr_atmpvc *pvc, - const struct sockaddr_atmsvc *svc,const struct atm_qos *qos,int reply); -void sigd_enq(struct atm_vcc *vcc,enum atmsvc_msg_type type, - struct atm_vcc *listen_vcc,const struct sockaddr_atmpvc *pvc, - const struct sockaddr_atmsvc *svc); -int sigd_attach(struct atm_vcc *vcc); - -#endif diff --git a/net/atm/svc.c b/net/atm/svc.c deleted file mode 100644 index 270e95154a2b..000000000000 --- a/net/atm/svc.c +++ /dev/null @@ -1,694 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* net/atm/svc.c - ATM SVC sockets */ - -/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */ - -#define pr_fmt(fmt) KBUILD_MODNAME ":%s: " fmt, __func__ - -#include -#include /* struct socket, struct proto_ops */ -#include /* error codes */ -#include /* printk */ -#include -#include -#include -#include /* O_NONBLOCK */ -#include -#include /* ATM stuff */ -#include -#include -#include -#include -#include /* for sock_no_* */ -#include -#include -#include - -#include "resources.h" -#include "common.h" /* common for PVCs and SVCs */ -#include "signaling.h" - -#ifdef CONFIG_COMPAT -/* It actually takes struct sockaddr_atmsvc, not struct atm_iobuf */ -#define COMPAT_ATM_ADDPARTY _IOW('a', ATMIOC_SPECIAL + 4, struct compat_atm_iobuf) -#endif - -static int svc_create(struct net *net, struct socket *sock, int protocol, - int kern); - -/* - * Note: since all this is still nicely synchronized with the signaling demon, - * there's no need to protect sleep loops with clis. If signaling is - * moved into the kernel, that would change. - */ - - -static int svc_shutdown(struct socket *sock, int how) -{ - return 0; -} - -static void svc_disconnect(struct atm_vcc *vcc) -{ - DEFINE_WAIT(wait); - struct sk_buff *skb; - struct sock *sk = sk_atm(vcc); - - pr_debug("%p\n", vcc); - if (test_bit(ATM_VF_REGIS, &vcc->flags)) { - sigd_enq(vcc, as_close, NULL, NULL, NULL); - for (;;) { - prepare_to_wait(sk_sleep(sk), &wait, TASK_UNINTERRUPTIBLE); - if (test_bit(ATM_VF_RELEASED, &vcc->flags) || !sigd) - break; - schedule(); - } - finish_wait(sk_sleep(sk), &wait); - } - /* beware - socket is still in use by atmsigd until the last - as_indicate has been answered */ - while ((skb = skb_dequeue(&sk->sk_receive_queue)) != NULL) { - atm_return(vcc, skb->truesize); - pr_debug("LISTEN REL\n"); - sigd_enq2(NULL, as_reject, vcc, NULL, NULL, &vcc->qos, 0); - dev_kfree_skb(skb); - } - clear_bit(ATM_VF_REGIS, &vcc->flags); - /* ... may retry later */ -} - -static int svc_release(struct socket *sock) -{ - struct sock *sk = sock->sk; - struct atm_vcc *vcc; - - if (sk) { - vcc = ATM_SD(sock); - pr_debug("%p\n", vcc); - clear_bit(ATM_VF_READY, &vcc->flags); - /* - * VCC pointer is used as a reference, - * so we must not free it (thereby subjecting it to re-use) - * before all pending connections are closed - */ - svc_disconnect(vcc); - vcc_release(sock); - } - return 0; -} - -static int svc_bind(struct socket *sock, struct sockaddr_unsized *sockaddr, - int sockaddr_len) -{ - DEFINE_WAIT(wait); - struct sock *sk = sock->sk; - struct sockaddr_atmsvc *addr; - struct atm_vcc *vcc; - int error; - - if (sockaddr_len != sizeof(struct sockaddr_atmsvc)) - return -EINVAL; - lock_sock(sk); - if (sock->state == SS_CONNECTED) { - error = -EISCONN; - goto out; - } - if (sock->state != SS_UNCONNECTED) { - error = -EINVAL; - goto out; - } - vcc = ATM_SD(sock); - addr = (struct sockaddr_atmsvc *) sockaddr; - if (addr->sas_family != AF_ATMSVC) { - error = -EAFNOSUPPORT; - goto out; - } - clear_bit(ATM_VF_BOUND, &vcc->flags); - /* failing rebind will kill old binding */ - /* @@@ check memory (de)allocation on rebind */ - if (!test_bit(ATM_VF_HASQOS, &vcc->flags)) { - error = -EBADFD; - goto out; - } - vcc->local = *addr; - set_bit(ATM_VF_WAITING, &vcc->flags); - sigd_enq(vcc, as_bind, NULL, NULL, &vcc->local); - for (;;) { - prepare_to_wait(sk_sleep(sk), &wait, TASK_UNINTERRUPTIBLE); - if (!test_bit(ATM_VF_WAITING, &vcc->flags) || !sigd) - break; - schedule(); - } - finish_wait(sk_sleep(sk), &wait); - clear_bit(ATM_VF_REGIS, &vcc->flags); /* doesn't count */ - if (!sigd) { - error = -EUNATCH; - goto out; - } - if (!sk->sk_err) - set_bit(ATM_VF_BOUND, &vcc->flags); - error = -sk->sk_err; -out: - release_sock(sk); - return error; -} - -static int svc_connect(struct socket *sock, struct sockaddr_unsized *sockaddr, - int sockaddr_len, int flags) -{ - DEFINE_WAIT(wait); - struct sock *sk = sock->sk; - struct sockaddr_atmsvc *addr; - struct atm_vcc *vcc = ATM_SD(sock); - int error; - - pr_debug("%p\n", vcc); - lock_sock(sk); - if (sockaddr_len != sizeof(struct sockaddr_atmsvc)) { - error = -EINVAL; - goto out; - } - - switch (sock->state) { - default: - error = -EINVAL; - goto out; - case SS_CONNECTED: - error = -EISCONN; - goto out; - case SS_CONNECTING: - if (test_bit(ATM_VF_WAITING, &vcc->flags)) { - error = -EALREADY; - goto out; - } - sock->state = SS_UNCONNECTED; - if (sk->sk_err) { - error = -sk->sk_err; - goto out; - } - break; - case SS_UNCONNECTED: - addr = (struct sockaddr_atmsvc *) sockaddr; - if (addr->sas_family != AF_ATMSVC) { - error = -EAFNOSUPPORT; - goto out; - } - if (!test_bit(ATM_VF_HASQOS, &vcc->flags)) { - error = -EBADFD; - goto out; - } - if (vcc->qos.txtp.traffic_class == ATM_ANYCLASS || - vcc->qos.rxtp.traffic_class == ATM_ANYCLASS) { - error = -EINVAL; - goto out; - } - if (!vcc->qos.txtp.traffic_class && - !vcc->qos.rxtp.traffic_class) { - error = -EINVAL; - goto out; - } - vcc->remote = *addr; - set_bit(ATM_VF_WAITING, &vcc->flags); - sigd_enq(vcc, as_connect, NULL, NULL, &vcc->remote); - if (flags & O_NONBLOCK) { - sock->state = SS_CONNECTING; - error = -EINPROGRESS; - goto out; - } - error = 0; - prepare_to_wait(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE); - while (test_bit(ATM_VF_WAITING, &vcc->flags) && sigd) { - schedule(); - if (!signal_pending(current)) { - prepare_to_wait(sk_sleep(sk), &wait, - TASK_INTERRUPTIBLE); - continue; - } - pr_debug("*ABORT*\n"); - /* - * This is tricky: - * Kernel ---close--> Demon - * Kernel <--close--- Demon - * or - * Kernel ---close--> Demon - * Kernel <--error--- Demon - * or - * Kernel ---close--> Demon - * Kernel <--okay---- Demon - * Kernel <--close--- Demon - */ - sigd_enq(vcc, as_close, NULL, NULL, NULL); - while (test_bit(ATM_VF_WAITING, &vcc->flags) && sigd) { - prepare_to_wait(sk_sleep(sk), &wait, - TASK_INTERRUPTIBLE); - schedule(); - } - if (!sk->sk_err) - while (!test_bit(ATM_VF_RELEASED, &vcc->flags) && - sigd) { - prepare_to_wait(sk_sleep(sk), &wait, - TASK_INTERRUPTIBLE); - schedule(); - } - clear_bit(ATM_VF_REGIS, &vcc->flags); - clear_bit(ATM_VF_RELEASED, &vcc->flags); - clear_bit(ATM_VF_CLOSE, &vcc->flags); - /* we're gone now but may connect later */ - error = -EINTR; - break; - } - finish_wait(sk_sleep(sk), &wait); - if (error) - goto out; - if (!sigd) { - error = -EUNATCH; - goto out; - } - if (sk->sk_err) { - error = -sk->sk_err; - goto out; - } - } - - vcc->qos.txtp.max_pcr = SELECT_TOP_PCR(vcc->qos.txtp); - vcc->qos.txtp.pcr = 0; - vcc->qos.txtp.min_pcr = 0; - - error = vcc_connect(sock, vcc->itf, vcc->vpi, vcc->vci); - if (!error) - sock->state = SS_CONNECTED; - else - (void)svc_disconnect(vcc); -out: - release_sock(sk); - return error; -} - -static int svc_listen(struct socket *sock, int backlog) -{ - DEFINE_WAIT(wait); - struct sock *sk = sock->sk; - struct atm_vcc *vcc = ATM_SD(sock); - int error; - - pr_debug("%p\n", vcc); - lock_sock(sk); - /* let server handle listen on unbound sockets */ - if (test_bit(ATM_VF_SESSION, &vcc->flags)) { - error = -EINVAL; - goto out; - } - if (test_bit(ATM_VF_LISTEN, &vcc->flags)) { - error = -EADDRINUSE; - goto out; - } - set_bit(ATM_VF_WAITING, &vcc->flags); - sigd_enq(vcc, as_listen, NULL, NULL, &vcc->local); - for (;;) { - prepare_to_wait(sk_sleep(sk), &wait, TASK_UNINTERRUPTIBLE); - if (!test_bit(ATM_VF_WAITING, &vcc->flags) || !sigd) - break; - schedule(); - } - finish_wait(sk_sleep(sk), &wait); - if (!sigd) { - error = -EUNATCH; - goto out; - } - set_bit(ATM_VF_LISTEN, &vcc->flags); - vcc_insert_socket(sk); - sk->sk_max_ack_backlog = backlog > 0 ? backlog : ATM_BACKLOG_DEFAULT; - error = -sk->sk_err; -out: - release_sock(sk); - return error; -} - -static int svc_accept(struct socket *sock, struct socket *newsock, - struct proto_accept_arg *arg) -{ - struct sock *sk = sock->sk; - struct sk_buff *skb; - struct atmsvc_msg *msg; - struct atm_vcc *old_vcc = ATM_SD(sock); - struct atm_vcc *new_vcc; - int error; - - lock_sock(sk); - - error = svc_create(sock_net(sk), newsock, 0, arg->kern); - if (error) - goto out; - - new_vcc = ATM_SD(newsock); - - pr_debug("%p -> %p\n", old_vcc, new_vcc); - while (1) { - DEFINE_WAIT(wait); - - prepare_to_wait(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE); - while (!(skb = skb_dequeue(&sk->sk_receive_queue)) && - sigd) { - if (test_bit(ATM_VF_RELEASED, &old_vcc->flags)) - break; - if (test_bit(ATM_VF_CLOSE, &old_vcc->flags)) { - error = -sk->sk_err; - break; - } - if (arg->flags & O_NONBLOCK) { - error = -EAGAIN; - break; - } - release_sock(sk); - schedule(); - lock_sock(sk); - if (signal_pending(current)) { - error = -ERESTARTSYS; - break; - } - prepare_to_wait(sk_sleep(sk), &wait, - TASK_INTERRUPTIBLE); - } - finish_wait(sk_sleep(sk), &wait); - if (error) - goto out; - if (!skb) { - error = -EUNATCH; - goto out; - } - msg = (struct atmsvc_msg *)skb->data; - new_vcc->qos = msg->qos; - set_bit(ATM_VF_HASQOS, &new_vcc->flags); - new_vcc->remote = msg->svc; - new_vcc->local = msg->local; - new_vcc->sap = msg->sap; - error = vcc_connect(newsock, msg->pvc.sap_addr.itf, - msg->pvc.sap_addr.vpi, - msg->pvc.sap_addr.vci); - dev_kfree_skb(skb); - sk_acceptq_removed(sk); - if (error) { - sigd_enq2(NULL, as_reject, old_vcc, NULL, NULL, - &old_vcc->qos, error); - error = error == -EAGAIN ? -EBUSY : error; - goto out; - } - /* wait should be short, so we ignore the non-blocking flag */ - set_bit(ATM_VF_WAITING, &new_vcc->flags); - sigd_enq(new_vcc, as_accept, old_vcc, NULL, NULL); - for (;;) { - prepare_to_wait(sk_sleep(sk_atm(new_vcc)), &wait, - TASK_UNINTERRUPTIBLE); - if (!test_bit(ATM_VF_WAITING, &new_vcc->flags) || !sigd) - break; - release_sock(sk); - schedule(); - lock_sock(sk); - } - finish_wait(sk_sleep(sk_atm(new_vcc)), &wait); - if (!sigd) { - error = -EUNATCH; - goto out; - } - if (!sk_atm(new_vcc)->sk_err) - break; - if (sk_atm(new_vcc)->sk_err != ERESTARTSYS) { - error = -sk_atm(new_vcc)->sk_err; - goto out; - } - } - newsock->state = SS_CONNECTED; -out: - release_sock(sk); - return error; -} - -static int svc_getname(struct socket *sock, struct sockaddr *sockaddr, - int peer) -{ - struct sockaddr_atmsvc *addr; - - addr = (struct sockaddr_atmsvc *) sockaddr; - memcpy(addr, peer ? &ATM_SD(sock)->remote : &ATM_SD(sock)->local, - sizeof(struct sockaddr_atmsvc)); - return sizeof(struct sockaddr_atmsvc); -} - -int svc_change_qos(struct atm_vcc *vcc, struct atm_qos *qos) -{ - struct sock *sk = sk_atm(vcc); - DEFINE_WAIT(wait); - - set_bit(ATM_VF_WAITING, &vcc->flags); - sigd_enq2(vcc, as_modify, NULL, NULL, &vcc->local, qos, 0); - for (;;) { - prepare_to_wait(sk_sleep(sk), &wait, TASK_UNINTERRUPTIBLE); - if (!test_bit(ATM_VF_WAITING, &vcc->flags) || - test_bit(ATM_VF_RELEASED, &vcc->flags) || !sigd) { - break; - } - schedule(); - } - finish_wait(sk_sleep(sk), &wait); - if (!sigd) - return -EUNATCH; - return -sk->sk_err; -} - -static int svc_setsockopt(struct socket *sock, int level, int optname, - sockptr_t optval, unsigned int optlen) -{ - struct sock *sk = sock->sk; - struct atm_vcc *vcc = ATM_SD(sock); - int value, error = 0; - - lock_sock(sk); - switch (optname) { - case SO_ATMSAP: - if (level != SOL_ATM || optlen != sizeof(struct atm_sap)) { - error = -EINVAL; - goto out; - } - if (copy_from_sockptr(&vcc->sap, optval, optlen)) { - error = -EFAULT; - goto out; - } - set_bit(ATM_VF_HASSAP, &vcc->flags); - break; - case SO_MULTIPOINT: - if (level != SOL_ATM || optlen != sizeof(int)) { - error = -EINVAL; - goto out; - } - if (copy_from_sockptr(&value, optval, sizeof(int))) { - error = -EFAULT; - goto out; - } - if (value == 1) - set_bit(ATM_VF_SESSION, &vcc->flags); - else if (value == 0) - clear_bit(ATM_VF_SESSION, &vcc->flags); - else - error = -EINVAL; - break; - default: - error = vcc_setsockopt(sock, level, optname, optval, optlen); - } - -out: - release_sock(sk); - return error; -} - -static int svc_getsockopt(struct socket *sock, int level, int optname, - sockopt_t *opt) -{ - struct sock *sk = sock->sk; - int error = 0, len; - - lock_sock(sk); - if (!__SO_LEVEL_MATCH(optname, level) || optname != SO_ATMSAP) { - error = vcc_getsockopt(sock, level, optname, opt); - goto out; - } - len = opt->optlen; - if (len != sizeof(struct atm_sap)) { - error = -EINVAL; - goto out; - } - if (copy_to_iter(&ATM_SD(sock)->sap, sizeof(struct atm_sap), - &opt->iter_out) != sizeof(struct atm_sap)) { - error = -EFAULT; - goto out; - } -out: - release_sock(sk); - return error; -} - -static int svc_addparty(struct socket *sock, struct sockaddr *sockaddr, - int sockaddr_len, int flags) -{ - DEFINE_WAIT(wait); - struct sock *sk = sock->sk; - struct atm_vcc *vcc = ATM_SD(sock); - int error; - - lock_sock(sk); - set_bit(ATM_VF_WAITING, &vcc->flags); - sigd_enq(vcc, as_addparty, NULL, NULL, - (struct sockaddr_atmsvc *) sockaddr); - if (flags & O_NONBLOCK) { - error = -EINPROGRESS; - goto out; - } - pr_debug("added wait queue\n"); - for (;;) { - prepare_to_wait(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE); - if (!test_bit(ATM_VF_WAITING, &vcc->flags) || !sigd) - break; - schedule(); - } - finish_wait(sk_sleep(sk), &wait); - error = -xchg(&sk->sk_err_soft, 0); -out: - release_sock(sk); - return error; -} - -static int svc_dropparty(struct socket *sock, int ep_ref) -{ - DEFINE_WAIT(wait); - struct sock *sk = sock->sk; - struct atm_vcc *vcc = ATM_SD(sock); - int error; - - lock_sock(sk); - set_bit(ATM_VF_WAITING, &vcc->flags); - sigd_enq2(vcc, as_dropparty, NULL, NULL, NULL, NULL, ep_ref); - for (;;) { - prepare_to_wait(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE); - if (!test_bit(ATM_VF_WAITING, &vcc->flags) || !sigd) - break; - schedule(); - } - finish_wait(sk_sleep(sk), &wait); - if (!sigd) { - error = -EUNATCH; - goto out; - } - error = -xchg(&sk->sk_err_soft, 0); -out: - release_sock(sk); - return error; -} - -static int svc_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) -{ - int error, ep_ref; - struct sockaddr_atmsvc sa; - struct atm_vcc *vcc = ATM_SD(sock); - - switch (cmd) { - case ATM_ADDPARTY: - if (!test_bit(ATM_VF_SESSION, &vcc->flags)) - return -EINVAL; - if (copy_from_user(&sa, (void __user *) arg, sizeof(sa))) - return -EFAULT; - error = svc_addparty(sock, (struct sockaddr *)&sa, sizeof(sa), - 0); - break; - case ATM_DROPPARTY: - if (!test_bit(ATM_VF_SESSION, &vcc->flags)) - return -EINVAL; - if (copy_from_user(&ep_ref, (void __user *) arg, sizeof(int))) - return -EFAULT; - error = svc_dropparty(sock, ep_ref); - break; - default: - error = vcc_ioctl(sock, cmd, arg); - } - - return error; -} - -#ifdef CONFIG_COMPAT -static int svc_compat_ioctl(struct socket *sock, unsigned int cmd, - unsigned long arg) -{ - /* The definition of ATM_ADDPARTY uses the size of struct atm_iobuf. - But actually it takes a struct sockaddr_atmsvc, which doesn't need - compat handling. So all we have to do is fix up cmd... */ - if (cmd == COMPAT_ATM_ADDPARTY) - cmd = ATM_ADDPARTY; - - if (cmd == ATM_ADDPARTY || cmd == ATM_DROPPARTY) - return svc_ioctl(sock, cmd, arg); - else - return vcc_compat_ioctl(sock, cmd, arg); -} -#endif /* CONFIG_COMPAT */ - -static const struct proto_ops svc_proto_ops = { - .family = PF_ATMSVC, - .owner = THIS_MODULE, - - .release = svc_release, - .bind = svc_bind, - .connect = svc_connect, - .socketpair = sock_no_socketpair, - .accept = svc_accept, - .getname = svc_getname, - .poll = vcc_poll, - .ioctl = svc_ioctl, -#ifdef CONFIG_COMPAT - .compat_ioctl = svc_compat_ioctl, -#endif - .gettstamp = sock_gettstamp, - .listen = svc_listen, - .shutdown = svc_shutdown, - .setsockopt = svc_setsockopt, - .getsockopt_iter = svc_getsockopt, - .sendmsg = vcc_sendmsg, - .recvmsg = vcc_recvmsg, - .mmap = sock_no_mmap, -}; - - -static int svc_create(struct net *net, struct socket *sock, int protocol, - int kern) -{ - int error; - - if (!net_eq(net, &init_net)) - return -EAFNOSUPPORT; - - sock->ops = &svc_proto_ops; - error = vcc_create(net, sock, protocol, AF_ATMSVC, kern); - if (error) - return error; - ATM_SD(sock)->local.sas_family = AF_ATMSVC; - ATM_SD(sock)->remote.sas_family = AF_ATMSVC; - return 0; -} - -static const struct net_proto_family svc_family_ops = { - .family = PF_ATMSVC, - .create = svc_create, - .owner = THIS_MODULE, -}; - - -/* - * Initialize the ATM SVC protocol family - */ - -int __init atmsvc_init(void) -{ - return sock_register(&svc_family_ops); -} - -void atmsvc_exit(void) -{ - sock_unregister(PF_ATMSVC); -} -- cgit v1.2.3 From 6719d57ee047cba211e8684eef46d5486a3295bb Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Mon, 15 Jun 2026 12:44:13 -0700 Subject: atm: remove the unused change_qos device operation atmdev_ops::change_qos() was the hook for renegotiating the traffic parameters of an already-connected VCC, driven from SO_ATMQOS on a connected socket (and previously from the SVC as_modify path, now gone). None of the ATM drivers left in tree implement it - solos-pci only listed change_qos = NULL - so atm_change_qos() always returned -EOPNOTSUPP. Drop the operation and return -EOPNOTSUPP directly. Link: https://patch.msgid.link/20260615194416.752559-7-kuba@kernel.org Signed-off-by: Jakub Kicinski --- drivers/atm/solos-pci.c | 1 - include/linux/atmdev.h | 1 - net/atm/common.c | 25 ++----------------------- 3 files changed, 2 insertions(+), 25 deletions(-) (limited to 'include') diff --git a/drivers/atm/solos-pci.c b/drivers/atm/solos-pci.c index 4ad170a858ee..9c246b956c30 100644 --- a/drivers/atm/solos-pci.c +++ b/drivers/atm/solos-pci.c @@ -1182,7 +1182,6 @@ static const struct atmdev_ops fpga_ops = { .send = psend, .phy_put = NULL, .phy_get = NULL, - .change_qos = NULL, .proc_read = NULL, .owner = THIS_MODULE }; diff --git a/include/linux/atmdev.h b/include/linux/atmdev.h index 59477676063c..218c05f2ec54 100644 --- a/include/linux/atmdev.h +++ b/include/linux/atmdev.h @@ -143,7 +143,6 @@ struct atmdev_ops { /* only send is required */ void (*phy_put)(struct atm_dev *dev,unsigned char value, unsigned long addr); unsigned char (*phy_get)(struct atm_dev *dev,unsigned long addr); - int (*change_qos)(struct atm_vcc *vcc,struct atm_qos *qos,int flags); int (*proc_read)(struct atm_dev *dev,loff_t *pos,char *page); struct module *owner; }; diff --git a/net/atm/common.c b/net/atm/common.c index 650814d0a56c..44a0179d4586 100644 --- a/net/atm/common.c +++ b/net/atm/common.c @@ -673,28 +673,6 @@ __poll_t vcc_poll(struct file *file, struct socket *sock, poll_table *wait) return mask; } -static int atm_change_qos(struct atm_vcc *vcc, struct atm_qos *qos) -{ - int error; - - /* - * Don't let the QoS change the already connected AAL type nor the - * traffic class. - */ - if (qos->aal != vcc->qos.aal || - qos->rxtp.traffic_class != vcc->qos.rxtp.traffic_class || - qos->txtp.traffic_class != vcc->qos.txtp.traffic_class) - return -EINVAL; - error = adjust_tp(&qos->txtp, qos->aal); - if (!error) - error = adjust_tp(&qos->rxtp, qos->aal); - if (error) - return error; - if (!vcc->dev->ops->change_qos) - return -EOPNOTSUPP; - return vcc->dev->ops->change_qos(vcc, qos, ATM_MF_SET); -} - static int check_tp(const struct atm_trafprm *tp) { /* @@@ Should be merged with adjust_tp */ @@ -753,8 +731,9 @@ int vcc_setsockopt(struct socket *sock, int level, int optname, error = check_qos(&qos); if (error) return error; + /* QoS cannot be renegotiated on an already connected VCC. */ if (sock->state == SS_CONNECTED) - return atm_change_qos(vcc, &qos); + return -EOPNOTSUPP; if (sock->state != SS_UNCONNECTED) return -EBADFD; vcc->qos = qos; -- cgit v1.2.3 From ae6e653514d156f0cc2327fe9e7db731ecb35a76 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Mon, 15 Jun 2026 12:44:14 -0700 Subject: atm: remove the unused pre_send and send_bh device operations atmdev_ops::pre_send (a TX pre-processing hook) and ::send_bh (a bottom-half capable send variant) have no implementation behind them: no remaining ATM driver sets either, so vcc_sendmsg() always skipped pre_send and the raw AAL0/AAL5 paths always fell back to ->send(). The drivers that used these hooks were removed with the legacy ATM adapters. Drop both operations and the dead branches that tested for them. Link: https://patch.msgid.link/20260615194416.752559-8-kuba@kernel.org Signed-off-by: Jakub Kicinski --- include/linux/atmdev.h | 2 -- net/atm/common.c | 6 ------ net/atm/raw.c | 7 +------ 3 files changed, 1 insertion(+), 14 deletions(-) (limited to 'include') diff --git a/include/linux/atmdev.h b/include/linux/atmdev.h index 218c05f2ec54..96ce36e02247 100644 --- a/include/linux/atmdev.h +++ b/include/linux/atmdev.h @@ -137,9 +137,7 @@ struct atmdev_ops { /* only send is required */ int (*compat_ioctl)(struct atm_dev *dev,unsigned int cmd, void __user *arg); #endif - int (*pre_send)(struct atm_vcc *vcc, struct sk_buff *skb); int (*send)(struct atm_vcc *vcc,struct sk_buff *skb); - int (*send_bh)(struct atm_vcc *vcc, struct sk_buff *skb); void (*phy_put)(struct atm_dev *dev,unsigned char value, unsigned long addr); unsigned char (*phy_get)(struct atm_dev *dev,unsigned long addr); diff --git a/net/atm/common.c b/net/atm/common.c index 44a0179d4586..654cbe3c855e 100644 --- a/net/atm/common.c +++ b/net/atm/common.c @@ -626,12 +626,6 @@ int vcc_sendmsg(struct socket *sock, struct msghdr *m, size_t size) if (eff != size) memset(skb->data + size, 0, eff-size); - if (vcc->dev->ops->pre_send) { - error = vcc->dev->ops->pre_send(vcc, skb); - if (error) - goto free_skb; - } - error = vcc->dev->ops->send(vcc, skb); error = error ? error : size; out: diff --git a/net/atm/raw.c b/net/atm/raw.c index 1d6ac7b0c4e5..87d136c7554b 100644 --- a/net/atm/raw.c +++ b/net/atm/raw.c @@ -54,8 +54,6 @@ static int atm_send_aal0(struct atm_vcc *vcc, struct sk_buff *skb) kfree_skb(skb); return -EADDRNOTAVAIL; } - if (vcc->dev->ops->send_bh) - return vcc->dev->ops->send_bh(vcc, skb); return vcc->dev->ops->send(vcc, skb); } @@ -71,10 +69,7 @@ int atm_init_aal5(struct atm_vcc *vcc) { vcc->push = atm_push_raw; vcc->pop = atm_pop_raw; - if (vcc->dev->ops->send_bh) - vcc->send = vcc->dev->ops->send_bh; - else - vcc->send = vcc->dev->ops->send; + vcc->send = vcc->dev->ops->send; return 0; } EXPORT_SYMBOL(atm_init_aal5); -- cgit v1.2.3 From e44e224e2f44c9ad3ab422b513d5ee2f0f45e416 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Mon, 15 Jun 2026 12:44:15 -0700 Subject: atm: remove unused ATM PHY operations The PHY operations are vestiges of the SAR/framer split used by the removed PCI/SBUS ATM adapters: - atmdev_ops::phy_put / ::phy_get (register accessors) are never called by the core and solos-pci only listed them as NULL - struct atmphy_ops and atm_dev::phy have no users at all - nothing assigns or dereferences them Remove all of them. atm_dev::phy_data is kept: solos-pci repurposes it to stash its per-port channel index. Link: https://patch.msgid.link/20260615194416.752559-9-kuba@kernel.org Signed-off-by: Jakub Kicinski --- drivers/atm/solos-pci.c | 2 -- include/linux/atmdev.h | 12 ------------ 2 files changed, 14 deletions(-) (limited to 'include') diff --git a/drivers/atm/solos-pci.c b/drivers/atm/solos-pci.c index 9c246b956c30..fc0e91a1029e 100644 --- a/drivers/atm/solos-pci.c +++ b/drivers/atm/solos-pci.c @@ -1180,8 +1180,6 @@ static const struct atmdev_ops fpga_ops = { .close = pclose, .ioctl = NULL, .send = psend, - .phy_put = NULL, - .phy_get = NULL, .proc_read = NULL, .owner = THIS_MODULE }; diff --git a/include/linux/atmdev.h b/include/linux/atmdev.h index 96ce36e02247..fe21d2f547b5 100644 --- a/include/linux/atmdev.h +++ b/include/linux/atmdev.h @@ -107,8 +107,6 @@ static inline struct sock *sk_atm(struct atm_vcc *vcc) struct atm_dev { const struct atmdev_ops *ops; /* device operations; NULL if unused */ - const struct atmphy_ops *phy; /* PHY operations, may be undefined */ - /* (NULL) */ const char *type; /* device type name */ int number; /* device index */ void *dev_data; /* per-device data */ @@ -138,20 +136,10 @@ struct atmdev_ops { /* only send is required */ void __user *arg); #endif int (*send)(struct atm_vcc *vcc,struct sk_buff *skb); - void (*phy_put)(struct atm_dev *dev,unsigned char value, - unsigned long addr); - unsigned char (*phy_get)(struct atm_dev *dev,unsigned long addr); int (*proc_read)(struct atm_dev *dev,loff_t *pos,char *page); struct module *owner; }; -struct atmphy_ops { - int (*start)(struct atm_dev *dev); - int (*ioctl)(struct atm_dev *dev,unsigned int cmd,void __user *arg); - void (*interrupt)(struct atm_dev *dev); - int (*stop)(struct atm_dev *dev); -}; - struct atm_skb_data { struct atm_vcc *vcc; /* ATM VCC */ unsigned long atm_options; /* ATM layer options */ -- cgit v1.2.3 From 8f9616500c59bb85a06a9d1c52e59d1bf4a194c2 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Mon, 15 Jun 2026 12:44:16 -0700 Subject: atm: remove orphaned uAPI for deleted drivers, protocols and SVCs ATM removals have left a number of uAPI headers and ioctl definitions with no in-kernel implementation behind them: - device headers for adapters deleted with the legacy PCI/SBUS drivers: atm_eni.h, atm_he.h, atm_idt77105.h, atm_nicstar.h, atm_zatm.h and the atmtcp pair atm_tcp.h / - protocol headers for the removed CLIP, LANE and MPOA stacks: atmarp.h, atmclip.h, atmlec.h, atmmpc.h - atmsvc.h and the SVC / p2mp / local-address ioctls in atmdev.h (ATM_{GET,RST,ADD,DEL}ADDR, ATM_{ADD,DEL,GET}LECSADDR, ATM_{ADD,DROP}PARTY) left behind by the SVC and address-registry removals None of these are referenced by any remaining in-tree code. Let's try to delete all this. Chances are nobody cares about these headers any more. I'm keeping this separate from the kernel side code changes for ease of revert, in case I am proven wrong... Link: https://patch.msgid.link/20260615194416.752559-10-kuba@kernel.org Signed-off-by: Jakub Kicinski --- include/linux/atm_tcp.h | 24 ------- include/uapi/linux/atm_eni.h | 24 ------- include/uapi/linux/atm_he.h | 21 ------- include/uapi/linux/atm_idt77105.h | 29 --------- include/uapi/linux/atm_nicstar.h | 54 ---------------- include/uapi/linux/atm_tcp.h | 62 ------------------- include/uapi/linux/atm_zatm.h | 47 -------------- include/uapi/linux/atmarp.h | 42 ------------- include/uapi/linux/atmclip.h | 22 ------- include/uapi/linux/atmdev.h | 18 ------ include/uapi/linux/atmlec.h | 92 --------------------------- include/uapi/linux/atmmpc.h | 127 -------------------------------------- include/uapi/linux/atmsvc.h | 56 ----------------- net/atm/ioctl.c | 1 - 14 files changed, 619 deletions(-) delete mode 100644 include/linux/atm_tcp.h delete mode 100644 include/uapi/linux/atm_eni.h delete mode 100644 include/uapi/linux/atm_he.h delete mode 100644 include/uapi/linux/atm_idt77105.h delete mode 100644 include/uapi/linux/atm_nicstar.h delete mode 100644 include/uapi/linux/atm_tcp.h delete mode 100644 include/uapi/linux/atm_zatm.h delete mode 100644 include/uapi/linux/atmarp.h delete mode 100644 include/uapi/linux/atmclip.h delete mode 100644 include/uapi/linux/atmlec.h delete mode 100644 include/uapi/linux/atmmpc.h delete mode 100644 include/uapi/linux/atmsvc.h (limited to 'include') diff --git a/include/linux/atm_tcp.h b/include/linux/atm_tcp.h deleted file mode 100644 index 2558439d849b..000000000000 --- a/include/linux/atm_tcp.h +++ /dev/null @@ -1,24 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* atm_tcp.h - Driver-specific declarations of the ATMTCP driver (for use by - driver-specific utilities) */ - -/* Written 1997-2000 by Werner Almesberger, EPFL LRC/ICA */ - -#ifndef LINUX_ATM_TCP_H -#define LINUX_ATM_TCP_H - -#include - -struct atm_vcc; -struct module; - -struct atm_tcp_ops { - int (*attach)(struct atm_vcc *vcc,int itf); - int (*create_persistent)(int itf); - int (*remove_persistent)(int itf); - struct module *owner; -}; - -extern struct atm_tcp_ops atm_tcp_ops; - -#endif diff --git a/include/uapi/linux/atm_eni.h b/include/uapi/linux/atm_eni.h deleted file mode 100644 index cf5bfd1a2691..000000000000 --- a/include/uapi/linux/atm_eni.h +++ /dev/null @@ -1,24 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ -/* atm_eni.h - Driver-specific declarations of the ENI driver (for use by - driver-specific utilities) */ - -/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */ - - -#ifndef LINUX_ATM_ENI_H -#define LINUX_ATM_ENI_H - -#include - - -struct eni_multipliers { - int tx,rx; /* values are in percent and must be > 100 */ -}; - - -#define ENI_MEMDUMP _IOW('a',ATMIOC_SARPRV,struct atmif_sioc) - /* printk memory map */ -#define ENI_SETMULT _IOW('a',ATMIOC_SARPRV+7,struct atmif_sioc) - /* set buffer multipliers */ - -#endif diff --git a/include/uapi/linux/atm_he.h b/include/uapi/linux/atm_he.h deleted file mode 100644 index 9f4b43293988..000000000000 --- a/include/uapi/linux/atm_he.h +++ /dev/null @@ -1,21 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ -/* atm_he.h */ - -#ifndef LINUX_ATM_HE_H -#define LINUX_ATM_HE_H - -#include - -#define HE_GET_REG _IOW('a', ATMIOC_SARPRV, struct atmif_sioc) - -#define HE_REGTYPE_PCI 1 -#define HE_REGTYPE_RCM 2 -#define HE_REGTYPE_TCM 3 -#define HE_REGTYPE_MBOX 4 - -struct he_ioctl_reg { - unsigned addr, val; - char type; -}; - -#endif /* LINUX_ATM_HE_H */ diff --git a/include/uapi/linux/atm_idt77105.h b/include/uapi/linux/atm_idt77105.h deleted file mode 100644 index f0fd6912a14b..000000000000 --- a/include/uapi/linux/atm_idt77105.h +++ /dev/null @@ -1,29 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ -/* atm_idt77105.h - Driver-specific declarations of the IDT77105 driver (for - * use by driver-specific utilities) */ - -/* Written 1999 by Greg Banks . Copied from atm_suni.h. */ - - -#ifndef LINUX_ATM_IDT77105_H -#define LINUX_ATM_IDT77105_H - -#include -#include -#include - -/* - * Structure for IDT77105_GETSTAT and IDT77105_GETSTATZ ioctls. - * Pointed to by `arg' in atmif_sioc. - */ -struct idt77105_stats { - __u32 symbol_errors; /* wire symbol errors */ - __u32 tx_cells; /* cells transmitted */ - __u32 rx_cells; /* cells received */ - __u32 rx_hec_errors; /* Header Error Check errors on receive */ -}; - -#define IDT77105_GETSTAT _IOW('a',ATMIOC_PHYPRV+2,struct atmif_sioc) /* get stats */ -#define IDT77105_GETSTATZ _IOW('a',ATMIOC_PHYPRV+3,struct atmif_sioc) /* get stats and zero */ - -#endif diff --git a/include/uapi/linux/atm_nicstar.h b/include/uapi/linux/atm_nicstar.h deleted file mode 100644 index 880d368b5b9a..000000000000 --- a/include/uapi/linux/atm_nicstar.h +++ /dev/null @@ -1,54 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ -/****************************************************************************** - * - * atm_nicstar.h - * - * Driver-specific declarations for use by NICSTAR driver specific utils. - * - * Author: Rui Prior - * - * (C) INESC 1998 - * - ******************************************************************************/ - - -#ifndef LINUX_ATM_NICSTAR_H -#define LINUX_ATM_NICSTAR_H - -/* Note: non-kernel programs including this file must also include - * sys/types.h for struct timeval - */ - -#include -#include - -#define NS_GETPSTAT _IOWR('a',ATMIOC_SARPRV+1,struct atmif_sioc) - /* get pool statistics */ -#define NS_SETBUFLEV _IOW('a',ATMIOC_SARPRV+2,struct atmif_sioc) - /* set buffer level markers */ -#define NS_ADJBUFLEV _IO('a',ATMIOC_SARPRV+3) - /* adjust buffer level */ - -typedef struct buf_nr -{ - unsigned min; - unsigned init; - unsigned max; -}buf_nr; - - -typedef struct pool_levels -{ - int buftype; - int count; /* (At least for now) only used in NS_GETPSTAT */ - buf_nr level; -} pool_levels; - -/* type must be one of the following: */ -#define NS_BUFTYPE_SMALL 1 -#define NS_BUFTYPE_LARGE 2 -#define NS_BUFTYPE_HUGE 3 -#define NS_BUFTYPE_IOVEC 4 - - -#endif /* LINUX_ATM_NICSTAR_H */ diff --git a/include/uapi/linux/atm_tcp.h b/include/uapi/linux/atm_tcp.h deleted file mode 100644 index 7309e1bc8867..000000000000 --- a/include/uapi/linux/atm_tcp.h +++ /dev/null @@ -1,62 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ -/* atm_tcp.h - Driver-specific declarations of the ATMTCP driver (for use by - driver-specific utilities) */ - -/* Written 1997-2000 by Werner Almesberger, EPFL LRC/ICA */ - - -#ifndef _UAPILINUX_ATM_TCP_H -#define _UAPILINUX_ATM_TCP_H - -#include -#include -#include -#include - - -/* - * All values in struct atmtcp_hdr are in network byte order - */ - -struct atmtcp_hdr { - __u16 vpi; - __u16 vci; - __u32 length; /* ... of data part */ -}; - -/* - * All values in struct atmtcp_command are in host byte order - */ - -#define ATMTCP_HDR_MAGIC (~0) /* this length indicates a command */ -#define ATMTCP_CTRL_OPEN 1 /* request/reply */ -#define ATMTCP_CTRL_CLOSE 2 /* request/reply */ - -struct atmtcp_control { - struct atmtcp_hdr hdr; /* must be first */ - int type; /* message type; both directions */ - atm_kptr_t vcc; /* both directions */ - struct sockaddr_atmpvc addr; /* suggested value from kernel */ - struct atm_qos qos; /* both directions */ - int result; /* to kernel only */ -} __ATM_API_ALIGN; - -/* - * Field usage: - * Messge type dir. hdr.v?i type addr qos vcc result - * ----------- ---- ------- ---- ---- --- --- ------ - * OPEN K->D Y Y Y Y Y 0 - * OPEN D->K - Y Y Y Y Y - * CLOSE K->D - - Y - Y 0 - * CLOSE D->K - - - - Y Y - */ - -#define SIOCSIFATMTCP _IO('a',ATMIOC_ITF) /* set ATMTCP mode */ -#define ATMTCP_CREATE _IO('a',ATMIOC_ITF+14) /* create persistent ATMTCP - interface */ -#define ATMTCP_REMOVE _IO('a',ATMIOC_ITF+15) /* destroy persistent ATMTCP - interface */ - - - -#endif /* _UAPILINUX_ATM_TCP_H */ diff --git a/include/uapi/linux/atm_zatm.h b/include/uapi/linux/atm_zatm.h deleted file mode 100644 index 5135027b93c1..000000000000 --- a/include/uapi/linux/atm_zatm.h +++ /dev/null @@ -1,47 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ -/* atm_zatm.h - Driver-specific declarations of the ZATM driver (for use by - driver-specific utilities) */ - -/* Written 1995-1999 by Werner Almesberger, EPFL LRC/ICA */ - - -#ifndef LINUX_ATM_ZATM_H -#define LINUX_ATM_ZATM_H - -/* - * Note: non-kernel programs including this file must also include - * sys/types.h for struct timeval - */ - -#include -#include - -#define ZATM_GETPOOL _IOW('a',ATMIOC_SARPRV+1,struct atmif_sioc) - /* get pool statistics */ -#define ZATM_GETPOOLZ _IOW('a',ATMIOC_SARPRV+2,struct atmif_sioc) - /* get statistics and zero */ -#define ZATM_SETPOOL _IOW('a',ATMIOC_SARPRV+3,struct atmif_sioc) - /* set pool parameters */ - -struct zatm_pool_info { - int ref_count; /* free buffer pool usage counters */ - int low_water,high_water; /* refill parameters */ - int rqa_count,rqu_count; /* queue condition counters */ - int offset,next_off; /* alignment optimizations: offset */ - int next_cnt,next_thres; /* repetition counter and threshold */ -}; - -struct zatm_pool_req { - int pool_num; /* pool number */ - struct zatm_pool_info info; /* actual information */ -}; - -#define ZATM_OAM_POOL 0 /* free buffer pool for OAM cells */ -#define ZATM_AAL0_POOL 1 /* free buffer pool for AAL0 cells */ -#define ZATM_AAL5_POOL_BASE 2 /* first AAL5 free buffer pool */ -#define ZATM_LAST_POOL ZATM_AAL5_POOL_BASE+10 /* max. 64 kB */ - -#define ZATM_TIMER_HISTORY_SIZE 16 /* number of timer adjustments to - record; must be 2^n */ - -#endif diff --git a/include/uapi/linux/atmarp.h b/include/uapi/linux/atmarp.h deleted file mode 100644 index 8e44d121fde1..000000000000 --- a/include/uapi/linux/atmarp.h +++ /dev/null @@ -1,42 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ -/* atmarp.h - ATM ARP protocol and kernel-demon interface definitions */ - -/* Written 1995-1999 by Werner Almesberger, EPFL LRC/ICA */ - - -#ifndef _LINUX_ATMARP_H -#define _LINUX_ATMARP_H - -#include -#include -#include - - -#define ATMARP_RETRY_DELAY 30 /* request next resolution or forget - NAK after 30 sec - should go into - atmclip.h */ -#define ATMARP_MAX_UNRES_PACKETS 5 /* queue that many packets while - waiting for the resolver */ - - -#define ATMARPD_CTRL _IO('a',ATMIOC_CLIP+1) /* become atmarpd ctrl sock */ -#define ATMARP_MKIP _IO('a',ATMIOC_CLIP+2) /* attach socket to IP */ -#define ATMARP_SETENTRY _IO('a',ATMIOC_CLIP+3) /* fill or hide ARP entry */ -#define ATMARP_ENCAP _IO('a',ATMIOC_CLIP+5) /* change encapsulation */ - - -enum atmarp_ctrl_type { - act_invalid, /* catch uninitialized structures */ - act_need, /* need address resolution */ - act_up, /* interface is coming up */ - act_down, /* interface is going down */ - act_change /* interface configuration has changed */ -}; - -struct atmarp_ctrl { - enum atmarp_ctrl_type type; /* message type */ - int itf_num;/* interface number (if present) */ - __be32 ip; /* IP address (act_need only) */ -}; - -#endif diff --git a/include/uapi/linux/atmclip.h b/include/uapi/linux/atmclip.h deleted file mode 100644 index c818bb82b4e6..000000000000 --- a/include/uapi/linux/atmclip.h +++ /dev/null @@ -1,22 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ -/* atmclip.h - Classical IP over ATM */ - -/* Written 1995-1998 by Werner Almesberger, EPFL LRC/ICA */ - - -#ifndef LINUX_ATMCLIP_H -#define LINUX_ATMCLIP_H - -#include -#include - - -#define RFC1483LLC_LEN 8 /* LLC+OUI+PID = 8 */ -#define RFC1626_MTU 9180 /* RFC1626 default MTU */ - -#define CLIP_DEFAULT_IDLETIMER 1200 /* 20 minutes, see RFC1755 */ -#define CLIP_CHECK_INTERVAL 10 /* check every ten seconds */ - -#define SIOCMKCLIP _IO('a',ATMIOC_CLIP) /* create IP interface */ - -#endif diff --git a/include/uapi/linux/atmdev.h b/include/uapi/linux/atmdev.h index 20b0215084fc..a94e7b8360ee 100644 --- a/include/uapi/linux/atmdev.h +++ b/include/uapi/linux/atmdev.h @@ -60,14 +60,6 @@ struct atm_dev_stats { /* get interface type name */ #define ATM_GETESI _IOW('a',ATMIOC_ITF+5,struct atmif_sioc) /* get interface ESI */ -#define ATM_GETADDR _IOW('a',ATMIOC_ITF+6,struct atmif_sioc) - /* get itf's local ATM addr. list */ -#define ATM_RSTADDR _IOW('a',ATMIOC_ITF+7,struct atmif_sioc) - /* reset itf's ATM address list */ -#define ATM_ADDADDR _IOW('a',ATMIOC_ITF+8,struct atmif_sioc) - /* add a local ATM address */ -#define ATM_DELADDR _IOW('a',ATMIOC_ITF+9,struct atmif_sioc) - /* remove a local ATM address */ #define ATM_GETCIRANGE _IOW('a',ATMIOC_ITF+10,struct atmif_sioc) /* get connection identifier range */ #define ATM_SETCIRANGE _IOW('a',ATMIOC_ITF+11,struct atmif_sioc) @@ -76,12 +68,6 @@ struct atm_dev_stats { /* set interface ESI */ #define ATM_SETESIF _IOW('a',ATMIOC_ITF+13,struct atmif_sioc) /* force interface ESI */ -#define ATM_ADDLECSADDR _IOW('a', ATMIOC_ITF+14, struct atmif_sioc) - /* register a LECS address */ -#define ATM_DELLECSADDR _IOW('a', ATMIOC_ITF+15, struct atmif_sioc) - /* unregister a LECS address */ -#define ATM_GETLECSADDR _IOW('a', ATMIOC_ITF+16, struct atmif_sioc) - /* retrieve LECS address(es) */ #define ATM_GETSTAT _IOW('a',ATMIOC_SARCOM+0,struct atmif_sioc) /* get AAL layer statistics */ @@ -99,10 +85,6 @@ struct atm_dev_stats { /* set backend handler */ #define ATM_NEWBACKENDIF _IOW('a',ATMIOC_SPECIAL+3,atm_backend_t) /* use backend to make new if */ -#define ATM_ADDPARTY _IOW('a', ATMIOC_SPECIAL+4,struct atm_iobuf) - /* add party to p2mp call */ -#define ATM_DROPPARTY _IOW('a', ATMIOC_SPECIAL+5,int) - /* drop party from p2mp call */ /* * These are backend handkers that can be set via the ATM_SETBACKEND call diff --git a/include/uapi/linux/atmlec.h b/include/uapi/linux/atmlec.h deleted file mode 100644 index c68346bb40e6..000000000000 --- a/include/uapi/linux/atmlec.h +++ /dev/null @@ -1,92 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ -/* - * ATM Lan Emulation Daemon driver interface - * - * Marko Kiiskila - */ - -#ifndef _ATMLEC_H_ -#define _ATMLEC_H_ - -#include -#include -#include -#include -#include - -/* ATM lec daemon control socket */ -#define ATMLEC_CTRL _IO('a', ATMIOC_LANE) -#define ATMLEC_DATA _IO('a', ATMIOC_LANE+1) -#define ATMLEC_MCAST _IO('a', ATMIOC_LANE+2) - -/* Maximum number of LEC interfaces (tweakable) */ -#define MAX_LEC_ITF 48 - -typedef enum { - l_set_mac_addr, - l_del_mac_addr, - l_svc_setup, - l_addr_delete, - l_topology_change, - l_flush_complete, - l_arp_update, - l_narp_req, /* LANE2 mandates the use of this */ - l_config, - l_flush_tran_id, - l_set_lecid, - l_arp_xmt, - l_rdesc_arp_xmt, - l_associate_req, - l_should_bridge /* should we bridge this MAC? */ -} atmlec_msg_type; - -#define ATMLEC_MSG_TYPE_MAX l_should_bridge - -struct atmlec_config_msg { - unsigned int maximum_unknown_frame_count; - unsigned int max_unknown_frame_time; - unsigned short max_retry_count; - unsigned int aging_time; - unsigned int forward_delay_time; - unsigned int arp_response_time; - unsigned int flush_timeout; - unsigned int path_switching_delay; - unsigned int lane_version; /* LANE2: 1 for LANEv1, 2 for LANEv2 */ - int mtu; - int is_proxy; -}; - -struct atmlec_msg { - atmlec_msg_type type; - int sizeoftlvs; /* LANE2: if != 0, tlvs follow */ - union { - struct { - unsigned char mac_addr[ETH_ALEN]; - unsigned char atm_addr[ATM_ESA_LEN]; - unsigned int flag; /* - * Topology_change flag, - * remoteflag, permanent flag, - * lecid, transaction id - */ - unsigned int targetless_le_arp; /* LANE2 */ - unsigned int no_source_le_narp; /* LANE2 */ - } normal; - struct atmlec_config_msg config; - struct { - __u16 lec_id; /* requestor lec_id */ - __u32 tran_id; /* transaction id */ - unsigned char mac_addr[ETH_ALEN]; /* dst mac addr */ - unsigned char atm_addr[ATM_ESA_LEN]; /* reqestor ATM addr */ - } proxy; /* - * For mapping LE_ARP requests to responses. Filled by - * zeppelin, returned by kernel. Used only when proxying - */ - } content; -} __ATM_API_ALIGN; - -struct atmlec_ioc { - int dev_num; - unsigned char atm_addr[ATM_ESA_LEN]; - unsigned char receive; /* 1= receive vcc, 0 = send vcc */ -}; -#endif /* _ATMLEC_H_ */ diff --git a/include/uapi/linux/atmmpc.h b/include/uapi/linux/atmmpc.h deleted file mode 100644 index cc17f4304839..000000000000 --- a/include/uapi/linux/atmmpc.h +++ /dev/null @@ -1,127 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ -#ifndef _ATMMPC_H_ -#define _ATMMPC_H_ - -#include -#include -#include -#include - -#define ATMMPC_CTRL _IO('a', ATMIOC_MPOA) -#define ATMMPC_DATA _IO('a', ATMIOC_MPOA+1) - -#define MPC_SOCKET_INGRESS 1 -#define MPC_SOCKET_EGRESS 2 - -struct atmmpc_ioc { - int dev_num; - __be32 ipaddr; /* the IP address of the shortcut */ - int type; /* ingress or egress */ -}; - -typedef struct in_ctrl_info { - __u8 Last_NHRP_CIE_code; - __u8 Last_Q2931_cause_value; - __u8 eg_MPC_ATM_addr[ATM_ESA_LEN]; - __be32 tag; - __be32 in_dst_ip; /* IP address this ingress MPC sends packets to */ - __u16 holding_time; - __u32 request_id; -} in_ctrl_info; - -typedef struct eg_ctrl_info { - __u8 DLL_header[256]; - __u8 DH_length; - __be32 cache_id; - __be32 tag; - __be32 mps_ip; - __be32 eg_dst_ip; /* IP address to which ingress MPC sends packets */ - __u8 in_MPC_data_ATM_addr[ATM_ESA_LEN]; - __u16 holding_time; -} eg_ctrl_info; - -struct mpc_parameters { - __u16 mpc_p1; /* Shortcut-Setup Frame Count */ - __u16 mpc_p2; /* Shortcut-Setup Frame Time */ - __u8 mpc_p3[8]; /* Flow-detection Protocols */ - __u16 mpc_p4; /* MPC Initial Retry Time */ - __u16 mpc_p5; /* MPC Retry Time Maximum */ - __u16 mpc_p6; /* Hold Down Time */ -} ; - -struct k_message { - __u16 type; - __be32 ip_mask; - __u8 MPS_ctrl[ATM_ESA_LEN]; - union { - in_ctrl_info in_info; - eg_ctrl_info eg_info; - struct mpc_parameters params; - } content; - struct atm_qos qos; -} __ATM_API_ALIGN; - -struct llc_snap_hdr { - /* RFC 1483 LLC/SNAP encapsulation for routed IP PDUs */ - __u8 dsap; /* Destination Service Access Point (0xAA) */ - __u8 ssap; /* Source Service Access Point (0xAA) */ - __u8 ui; /* Unnumbered Information (0x03) */ - __u8 org[3]; /* Organizational identification (0x000000) */ - __u8 type[2]; /* Ether type (for IP) (0x0800) */ -}; - -/* TLVs this MPC recognizes */ -#define TLV_MPOA_DEVICE_TYPE 0x00a03e2a - -/* MPOA device types in MPOA Device Type TLV */ -#define NON_MPOA 0 -#define MPS 1 -#define MPC 2 -#define MPS_AND_MPC 3 - - -/* MPC parameter defaults */ - -#define MPC_P1 10 /* Shortcut-Setup Frame Count */ -#define MPC_P2 1 /* Shortcut-Setup Frame Time */ -#define MPC_P3 0 /* Flow-detection Protocols */ -#define MPC_P4 5 /* MPC Initial Retry Time */ -#define MPC_P5 40 /* MPC Retry Time Maximum */ -#define MPC_P6 160 /* Hold Down Time */ -#define HOLDING_TIME_DEFAULT 1200 /* same as MPS-p7 */ - -/* MPC constants */ - -#define MPC_C1 2 /* Retry Time Multiplier */ -#define MPC_C2 60 /* Initial Keep-Alive Lifetime */ - -/* Message types - to MPOA daemon */ - -#define SND_MPOA_RES_RQST 201 -#define SET_MPS_CTRL_ADDR 202 -#define SND_MPOA_RES_RTRY 203 /* Different type in a retry due to req id */ -#define STOP_KEEP_ALIVE_SM 204 -#define EGRESS_ENTRY_REMOVED 205 -#define SND_EGRESS_PURGE 206 -#define DIE 207 /* tell the daemon to exit() */ -#define DATA_PLANE_PURGE 208 /* Data plane purge because of egress cache hit miss or dead MPS */ -#define OPEN_INGRESS_SVC 209 - -/* Message types - from MPOA daemon */ - -#define MPOA_TRIGGER_RCVD 101 -#define MPOA_RES_REPLY_RCVD 102 -#define INGRESS_PURGE_RCVD 103 -#define EGRESS_PURGE_RCVD 104 -#define MPS_DEATH 105 -#define CACHE_IMPOS_RCVD 106 -#define SET_MPC_CTRL_ADDR 107 /* Our MPC's control ATM address */ -#define SET_MPS_MAC_ADDR 108 -#define CLEAN_UP_AND_EXIT 109 -#define SET_MPC_PARAMS 110 /* MPC configuration parameters */ - -/* Message types - bidirectional */ - -#define RELOAD 301 /* kill -HUP the daemon for reload */ - -#endif /* _ATMMPC_H_ */ diff --git a/include/uapi/linux/atmsvc.h b/include/uapi/linux/atmsvc.h deleted file mode 100644 index 137b5f853449..000000000000 --- a/include/uapi/linux/atmsvc.h +++ /dev/null @@ -1,56 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ -/* atmsvc.h - ATM signaling kernel-demon interface definitions */ - -/* Written 1995-2000 by Werner Almesberger, EPFL LRC/ICA */ - - -#ifndef _LINUX_ATMSVC_H -#define _LINUX_ATMSVC_H - -#include -#include -#include - - -#define ATMSIGD_CTRL _IO('a',ATMIOC_SPECIAL) - /* become ATM signaling demon control socket */ - -enum atmsvc_msg_type { as_catch_null, as_bind, as_connect, as_accept, as_reject, - as_listen, as_okay, as_error, as_indicate, as_close, - as_itf_notify, as_modify, as_identify, as_terminate, - as_addparty, as_dropparty }; - -struct atmsvc_msg { - enum atmsvc_msg_type type; - atm_kptr_t vcc; - atm_kptr_t listen_vcc; /* indicate */ - int reply; /* for okay and close: */ - /* < 0: error before active */ - /* (sigd has discarded ctx) */ - /* ==0: success */ - /* > 0: error when active (still */ - /* need to close) */ - struct sockaddr_atmpvc pvc; /* indicate, okay (connect) */ - struct sockaddr_atmsvc local; /* local SVC address */ - struct atm_qos qos; /* QOS parameters */ - struct atm_sap sap; /* SAP */ - unsigned int session; /* for p2pm */ - struct sockaddr_atmsvc svc; /* SVC address */ -} __ATM_API_ALIGN; - -/* - * Message contents: see ftp://icaftp.epfl.ch/pub/linux/atm/docs/isp-*.tar.gz - */ - -/* - * Some policy stuff for atmsigd and for net/atm/svc.c. Both have to agree on - * what PCR is used to request bandwidth from the device driver. net/atm/svc.c - * tries to do better than that, but only if there's no routing decision (i.e. - * if signaling only uses one ATM interface). - */ - -#define SELECT_TOP_PCR(tp) ((tp).pcr ? (tp).pcr : \ - (tp).max_pcr && (tp).max_pcr != ATM_MAX_PCR ? (tp).max_pcr : \ - (tp).min_pcr ? (tp).min_pcr : ATM_MAX_PCR) - -#endif diff --git a/net/atm/ioctl.c b/net/atm/ioctl.c index 11f6d236f5d6..194587fc15e8 100644 --- a/net/atm/ioctl.c +++ b/net/atm/ioctl.c @@ -11,7 +11,6 @@ #include /* struct socket, struct proto_ops */ #include /* ATM stuff */ #include -#include /* manifest constants */ #include #include #include -- cgit v1.2.3 From 79511603a65b990bed675eb4bcfd85305d3ff42a Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Sat, 13 Jun 2026 18:40:57 -0700 Subject: tls: remove dead sockmap (psock) handling from the SW path TLS and sockmap are now mutually exclusive. Try to delete the code from sendmsg and recvmsg path which is now obviously dead. The main goal is to delete enough code for AI security scanners to no longer bother us with sockmap related bugs. At the same time retain the code in case someone has the cycles to fix all of this and make the integration work, again. If the integration does not get restored we can wipe the rest of the skmsg code from TLS in two or three releases. The changes on the Tx side are deeper since that's where most of the bugs are, Rx side simply takes the data from sockmap and gives it to the user. On Tx split record handling and rolling back the iterator were the two problem areas. Reviewed-by: Jakub Sitnicki Reviewed-by: Sabrina Dubroca Link: https://patch.msgid.link/20260614014102.461064-3-kuba@kernel.org Signed-off-by: Jakub Kicinski --- include/linux/skmsg.h | 9 -- net/core/skmsg.c | 52 +------ net/tls/tls_main.c | 8 +- net/tls/tls_sw.c | 399 ++++---------------------------------------------- 4 files changed, 31 insertions(+), 437 deletions(-) (limited to 'include') diff --git a/include/linux/skmsg.h b/include/linux/skmsg.h index 19f4f253b4f9..ca0ec9c8608e 100644 --- a/include/linux/skmsg.h +++ b/include/linux/skmsg.h @@ -544,15 +544,6 @@ static inline void psock_progs_drop(struct sk_psock_progs *progs) psock_set_prog(&progs->skb_verdict, NULL); } -int sk_psock_tls_strp_read(struct sk_psock *psock, struct sk_buff *skb); - -static inline bool sk_psock_strp_enabled(struct sk_psock *psock) -{ - if (!psock) - return false; - return !!psock->saved_data_ready; -} - /* for tcp only, sk is locked */ static inline ssize_t sk_psock_msg_inq(struct sock *sk) { diff --git a/net/core/skmsg.c b/net/core/skmsg.c index e1850caf1a71..73ae12f25940 100644 --- a/net/core/skmsg.c +++ b/net/core/skmsg.c @@ -7,7 +7,6 @@ #include #include -#include #include static bool sk_msg_try_coalesce_ok(struct sk_msg *msg, int elem_first_coalesce) @@ -992,41 +991,6 @@ static int sk_psock_skb_redirect(struct sk_psock *from, struct sk_buff *skb) return 0; } -static void sk_psock_tls_verdict_apply(struct sk_buff *skb, - struct sk_psock *from, int verdict) -{ - switch (verdict) { - case __SK_REDIRECT: - sk_psock_skb_redirect(from, skb); - break; - case __SK_PASS: - case __SK_DROP: - default: - break; - } -} - -int sk_psock_tls_strp_read(struct sk_psock *psock, struct sk_buff *skb) -{ - struct bpf_prog *prog; - int ret = __SK_PASS; - - rcu_read_lock(); - prog = READ_ONCE(psock->progs.stream_verdict); - if (likely(prog)) { - skb->sk = psock->sk; - skb_dst_drop(skb); - skb_bpf_redirect_clear(skb); - ret = bpf_prog_run_pin_on_cpu(prog, skb); - ret = sk_psock_map_verd(ret, skb_bpf_redirect_fetch(skb)); - skb->sk = NULL; - } - sk_psock_tls_verdict_apply(skb, psock, ret); - rcu_read_unlock(); - return ret; -} -EXPORT_SYMBOL_GPL(sk_psock_tls_strp_read); - static int sk_psock_verdict_apply(struct sk_psock *psock, struct sk_buff *skb, int verdict) { @@ -1167,13 +1131,9 @@ static void sk_psock_strp_data_ready(struct sock *sk) rcu_read_lock(); psock = sk_psock(sk); if (likely(psock)) { - if (tls_sw_has_ctx_rx(sk)) { - psock->saved_data_ready(sk); - } else { - read_lock_bh(&sk->sk_callback_lock); - strp_data_ready(&psock->strp); - read_unlock_bh(&sk->sk_callback_lock); - } + read_lock_bh(&sk->sk_callback_lock); + strp_data_ready(&psock->strp); + read_unlock_bh(&sk->sk_callback_lock); } rcu_read_unlock(); } @@ -1275,12 +1235,6 @@ static void sk_psock_verdict_data_ready(struct sock *sk) trace_sk_data_ready(sk); rcu_read_lock(); - psock = sk_psock(sk); - if (psock && tls_sw_has_ctx_rx(sk)) { - psock->saved_data_ready(sk); - rcu_read_unlock(); - return; - } sock = READ_ONCE(sk->sk_socket); if (likely(sock)) ops = READ_ONCE(sock->ops); diff --git a/net/tls/tls_main.c b/net/tls/tls_main.c index 8e7ba018988d..8c588cdab733 100644 --- a/net/tls/tls_main.c +++ b/net/tls/tls_main.c @@ -402,7 +402,6 @@ static __poll_t tls_sk_poll(struct file *file, struct socket *sock, struct tls_sw_context_rx *ctx; struct tls_context *tls_ctx; struct sock *sk = sock->sk; - struct sk_psock *psock; __poll_t mask = 0; u8 shutdown; int state; @@ -416,17 +415,12 @@ static __poll_t tls_sk_poll(struct file *file, struct socket *sock, tls_ctx = tls_get_ctx(sk); ctx = tls_sw_ctx_rx(tls_ctx); - psock = sk_psock_get(sk); if ((skb_queue_empty_lockless(&ctx->rx_list) && - !tls_strp_msg_ready(ctx) && - sk_psock_queue_empty(psock)) || + !tls_strp_msg_ready(ctx)) || READ_ONCE(ctx->key_update_pending)) mask &= ~(EPOLLIN | EPOLLRDNORM); - if (psock) - sk_psock_put(sk, psock); - return mask; } diff --git a/net/tls/tls_sw.c b/net/tls/tls_sw.c index 5a4300c943a1..9324e4ed20a3 100644 --- a/net/tls/tls_sw.c +++ b/net/tls/tls_sw.c @@ -614,136 +614,17 @@ static int tls_do_encryption(struct sock *sk, return rc; } -static int tls_split_open_record(struct sock *sk, struct tls_rec *from, - struct tls_rec **to, struct sk_msg *msg_opl, - struct sk_msg *msg_oen, u32 split_point, - u32 tx_overhead_size, u32 *orig_end) -{ - u32 i, j, bytes = 0, apply = msg_opl->apply_bytes; - struct scatterlist *sge, *osge, *nsge; - u32 orig_size = msg_opl->sg.size; - struct scatterlist tmp = { }; - struct sk_msg *msg_npl; - struct tls_rec *new; - int ret; - - new = tls_get_rec(sk); - if (!new) - return -ENOMEM; - ret = sk_msg_alloc(sk, &new->msg_encrypted, msg_opl->sg.size + - tx_overhead_size, 0); - if (ret < 0) { - tls_free_rec(sk, new); - return ret; - } - - *orig_end = msg_opl->sg.end; - i = msg_opl->sg.start; - sge = sk_msg_elem(msg_opl, i); - while (apply && sge->length) { - if (sge->length > apply) { - u32 len = sge->length - apply; - - get_page(sg_page(sge)); - sg_set_page(&tmp, sg_page(sge), len, - sge->offset + apply); - sge->length = apply; - bytes += apply; - apply = 0; - } else { - apply -= sge->length; - bytes += sge->length; - } - - sk_msg_iter_var_next(i); - if (i == msg_opl->sg.end) - break; - sge = sk_msg_elem(msg_opl, i); - } - - msg_opl->sg.end = i; - msg_opl->sg.curr = i; - msg_opl->sg.copybreak = 0; - msg_opl->apply_bytes = 0; - msg_opl->sg.size = bytes; - - msg_npl = &new->msg_plaintext; - msg_npl->apply_bytes = apply; - msg_npl->sg.size = orig_size - bytes; - - j = msg_npl->sg.start; - nsge = sk_msg_elem(msg_npl, j); - if (tmp.length) { - memcpy(nsge, &tmp, sizeof(*nsge)); - sk_msg_iter_var_next(j); - nsge = sk_msg_elem(msg_npl, j); - } - - osge = sk_msg_elem(msg_opl, i); - while (osge->length) { - memcpy(nsge, osge, sizeof(*nsge)); - sg_unmark_end(nsge); - sk_msg_iter_var_next(i); - sk_msg_iter_var_next(j); - if (i == *orig_end) - break; - osge = sk_msg_elem(msg_opl, i); - nsge = sk_msg_elem(msg_npl, j); - } - - msg_npl->sg.end = j; - msg_npl->sg.curr = j; - msg_npl->sg.copybreak = 0; - - *to = new; - return 0; -} - -static void tls_merge_open_record(struct sock *sk, struct tls_rec *to, - struct tls_rec *from, u32 orig_end) -{ - struct sk_msg *msg_npl = &from->msg_plaintext; - struct sk_msg *msg_opl = &to->msg_plaintext; - struct scatterlist *osge, *nsge; - u32 i, j; - - i = msg_opl->sg.end; - sk_msg_iter_var_prev(i); - j = msg_npl->sg.start; - - osge = sk_msg_elem(msg_opl, i); - nsge = sk_msg_elem(msg_npl, j); - - if (sg_page(osge) == sg_page(nsge) && - osge->offset + osge->length == nsge->offset) { - osge->length += nsge->length; - put_page(sg_page(nsge)); - } - - msg_opl->sg.end = orig_end; - msg_opl->sg.curr = orig_end; - msg_opl->sg.copybreak = 0; - msg_opl->apply_bytes = msg_opl->sg.size + msg_npl->sg.size; - msg_opl->sg.size += msg_npl->sg.size; - - sk_msg_free(sk, &to->msg_encrypted); - sk_msg_xfer_full(&to->msg_encrypted, &from->msg_encrypted); - - kfree(from); -} - static int tls_push_record(struct sock *sk, int flags, unsigned char record_type) { struct tls_context *tls_ctx = tls_get_ctx(sk); struct tls_prot_info *prot = &tls_ctx->prot_info; struct tls_sw_context_tx *ctx = tls_sw_ctx_tx(tls_ctx); - struct tls_rec *rec = ctx->open_rec, *tmp = NULL; - u32 i, split_point, orig_end; + struct tls_rec *rec = ctx->open_rec; struct sk_msg *msg_pl, *msg_en; struct aead_request *req; - bool split; int rc; + u32 i; if (!rec) return 0; @@ -751,38 +632,6 @@ static int tls_push_record(struct sock *sk, int flags, msg_pl = &rec->msg_plaintext; msg_en = &rec->msg_encrypted; - split_point = msg_pl->apply_bytes; - split = split_point && split_point < msg_pl->sg.size; - if (unlikely((!split && - msg_pl->sg.size + - prot->overhead_size > msg_en->sg.size) || - (split && - split_point + - prot->overhead_size > msg_en->sg.size))) { - split = true; - split_point = msg_en->sg.size; - } - if (split) { - rc = tls_split_open_record(sk, rec, &tmp, msg_pl, msg_en, - split_point, prot->overhead_size, - &orig_end); - if (rc < 0) - return rc; - /* This can happen if above tls_split_open_record allocates - * a single large encryption buffer instead of two smaller - * ones. In this case adjust pointers and continue without - * split. - */ - if (!msg_pl->sg.size) { - tls_merge_open_record(sk, rec, tmp, orig_end); - msg_pl = &rec->msg_plaintext; - msg_en = &rec->msg_encrypted; - split = false; - } - sk_msg_trim(sk, msg_en, msg_pl->sg.size + - prot->overhead_size); - } - rec->tx_flags = flags; req = &rec->aead_req; @@ -840,155 +689,26 @@ static int tls_push_record(struct sock *sk, int flags, rc = tls_do_encryption(sk, tls_ctx, ctx, req, msg_pl->sg.size + prot->tail_size, i); if (rc < 0) { - if (rc != -EINPROGRESS) { + if (rc != -EINPROGRESS) tls_err_abort(sk, -EBADMSG); - if (split) { - tls_ctx->pending_open_record_frags = true; - tls_merge_open_record(sk, rec, tmp, orig_end); - } - } ctx->async_capable = 1; return rc; - } else if (split) { - msg_pl = &tmp->msg_plaintext; - msg_en = &tmp->msg_encrypted; - sk_msg_trim(sk, msg_en, msg_pl->sg.size + prot->overhead_size); - tls_ctx->pending_open_record_frags = true; - ctx->open_rec = tmp; } return tls_tx_records(sk, flags); } static int bpf_exec_tx_verdict(struct sk_msg *msg, struct sock *sk, - bool full_record, u8 record_type, - ssize_t *copied, int flags) + u8 record_type, ssize_t *copied, int flags) { - struct tls_context *tls_ctx = tls_get_ctx(sk); - struct tls_sw_context_tx *ctx = tls_sw_ctx_tx(tls_ctx); - struct sk_msg msg_redir = { }; - struct sk_psock *psock; - struct sock *sk_redir; - struct tls_rec *rec; - bool enospc, policy, redir_ingress; - int err = 0, send; - u32 delta = 0; - - policy = !(flags & MSG_SENDPAGE_NOPOLICY); - psock = sk_psock_get(sk); - if (!psock || !policy) { - err = tls_push_record(sk, flags, record_type); - if (err && err != -EINPROGRESS && sk->sk_err == EBADMSG) { - *copied -= sk_msg_free(sk, msg); - tls_free_open_rec(sk); - err = -sk->sk_err; - } - if (psock) - sk_psock_put(sk, psock); - return err; - } -more_data: - enospc = sk_msg_full(msg); - if (psock->eval == __SK_NONE) { - delta = msg->sg.size; - psock->eval = sk_psock_msg_verdict(sk, psock, msg); - delta -= msg->sg.size; - - if ((s32)delta > 0) { - /* It indicates that we executed bpf_msg_pop_data(), - * causing the plaintext data size to decrease. - * Therefore the encrypted data size also needs to - * correspondingly decrease. We only need to subtract - * delta to calculate the new ciphertext length since - * ktls does not support block encryption. - */ - struct sk_msg *enc = &ctx->open_rec->msg_encrypted; + int err; - sk_msg_trim(sk, enc, enc->sg.size - delta); - } - } - if (msg->cork_bytes && msg->cork_bytes > msg->sg.size && - !enospc && !full_record) { - err = -ENOSPC; - goto out_err; - } - msg->cork_bytes = 0; - send = msg->sg.size; - if (msg->apply_bytes && msg->apply_bytes < send) - send = msg->apply_bytes; - - switch (psock->eval) { - case __SK_PASS: - err = tls_push_record(sk, flags, record_type); - if (err && err != -EINPROGRESS && sk->sk_err == EBADMSG) { - *copied -= sk_msg_free(sk, msg); - tls_free_open_rec(sk); - err = -sk->sk_err; - goto out_err; - } - break; - case __SK_REDIRECT: - redir_ingress = psock->redir_ingress; - sk_redir = psock->sk_redir; - memcpy(&msg_redir, msg, sizeof(*msg)); - if (msg->apply_bytes < send) - msg->apply_bytes = 0; - else - msg->apply_bytes -= send; - sk_msg_return_zero(sk, msg, send); - msg->sg.size -= send; - release_sock(sk); - err = tcp_bpf_sendmsg_redir(sk_redir, redir_ingress, - &msg_redir, send, flags); - lock_sock(sk); - if (err < 0) { - /* Regardless of whether the data represented by - * msg_redir is sent successfully, we have already - * uncharged it via sk_msg_return_zero(). The - * msg->sg.size represents the remaining unprocessed - * data, which needs to be uncharged here. - */ - sk_mem_uncharge(sk, msg->sg.size); - *copied -= sk_msg_free_nocharge(sk, &msg_redir); - msg->sg.size = 0; - } - if (msg->sg.size == 0) - tls_free_open_rec(sk); - break; - case __SK_DROP: - default: - sk_msg_free_partial(sk, msg, send); - if (msg->apply_bytes < send) - msg->apply_bytes = 0; - else - msg->apply_bytes -= send; - if (msg->sg.size == 0) - tls_free_open_rec(sk); - *copied -= (send + delta); - err = -EACCES; + err = tls_push_record(sk, flags, record_type); + if (err && err != -EINPROGRESS && sk->sk_err == EBADMSG) { + *copied -= sk_msg_free(sk, msg); + tls_free_open_rec(sk); + err = -sk->sk_err; } - - if (likely(!err)) { - bool reset_eval = !ctx->open_rec; - - rec = ctx->open_rec; - if (rec) { - msg = &rec->msg_plaintext; - if (!msg->apply_bytes) - reset_eval = true; - } - if (reset_eval) { - psock->eval = __SK_NONE; - if (psock->sk_redir) { - sock_put(psock->sk_redir); - psock->sk_redir = NULL; - } - } - if (rec) - goto more_data; - } - out_err: - sk_psock_put(sk, psock); return err; } @@ -1008,7 +728,7 @@ static int tls_sw_push_pending_record(struct sock *sk, int flags) if (!copied) return 0; - return bpf_exec_tx_verdict(msg_pl, sk, true, TLS_RECORD_TYPE_DATA, + return bpf_exec_tx_verdict(msg_pl, sk, TLS_RECORD_TYPE_DATA, &copied, flags); } @@ -1156,7 +876,7 @@ alloc_encrypted: copied += try_to_copy; sk_msg_sg_copy_set(msg_pl, first); - ret = bpf_exec_tx_verdict(msg_pl, sk, full_record, + ret = bpf_exec_tx_verdict(msg_pl, sk, record_type, &copied, msg->msg_flags); if (ret) { @@ -1164,13 +884,7 @@ alloc_encrypted: num_async++; else if (ret == -ENOMEM) goto wait_for_memory; - else if (ctx->open_rec && ret == -ENOSPC) { - if (msg_pl->cork_bytes) { - ret = 0; - goto send_end; - } - goto rollback_iter; - } else if (ret != -EAGAIN) + else if (ret != -EAGAIN) goto send_end; } @@ -1181,11 +895,6 @@ alloc_encrypted: } continue; -rollback_iter: - copied -= try_to_copy; - sk_msg_sg_copy_clear(msg_pl, first); - iov_iter_revert(&msg->msg_iter, - msg_pl->sg.size - orig_size); fallback_to_reg_send: sk_msg_trim(sk, msg_pl, orig_size); } @@ -1221,7 +930,7 @@ fallback_to_reg_send: copied += try_to_copy; copied: if (full_record || eor) { - ret = bpf_exec_tx_verdict(msg_pl, sk, full_record, + ret = bpf_exec_tx_verdict(msg_pl, sk, record_type, &copied, msg->msg_flags); if (ret) { @@ -1229,11 +938,8 @@ copied: num_async++; else if (ret == -ENOMEM) goto wait_for_memory; - else if (ret != -EAGAIN) { - if (ret == -ENOSPC) - ret = 0; + else if (ret != -EAGAIN) goto send_end; - } } /* Transmit if any encryptions have completed */ @@ -1335,8 +1041,8 @@ retry: if (msg_pl->sg.size == 0) goto unlock; - /* Check the BPF advisor and perform transmission. */ - ret = bpf_exec_tx_verdict(msg_pl, sk, false, TLS_RECORD_TYPE_DATA, + /* Perform transmission. */ + ret = bpf_exec_tx_verdict(msg_pl, sk, TLS_RECORD_TYPE_DATA, &copied, 0); switch (ret) { case 0: @@ -1372,8 +1078,7 @@ unlock: * consumed via sock_error(). */ static int -tls_rx_rec_wait(struct sock *sk, struct sk_psock *psock, bool nonblock, - bool released, bool has_copied) +tls_rx_rec_wait(struct sock *sk, bool nonblock, bool released, bool has_copied) { struct tls_context *tls_ctx = tls_get_ctx(sk); struct tls_sw_context_rx *ctx = tls_sw_ctx_rx(tls_ctx); @@ -1388,9 +1093,6 @@ tls_rx_rec_wait(struct sock *sk, struct sk_psock *psock, bool nonblock, timeo = sock_rcvtimeo(sk, nonblock); while (!tls_strp_msg_ready(ctx)) { - if (!sk_psock_queue_empty(psock)) - return 0; - if (sk->sk_err) { if (has_copied) return -READ_ONCE(sk->sk_err); @@ -1434,9 +1136,7 @@ tls_rx_rec_wait(struct sock *sk, struct sk_psock *psock, bool nonblock, add_wait_queue(sk_sleep(sk), &wait); sk_set_bit(SOCKWQ_ASYNC_WAITDATA, sk); ret = sk_wait_event(sk, &timeo, - tls_strp_msg_ready(ctx) || - !sk_psock_queue_empty(psock), - &wait); + tls_strp_msg_ready(ctx), &wait); sk_clear_bit(SOCKWQ_ASYNC_WAITDATA, sk); remove_wait_queue(sk_sleep(sk), &wait); @@ -1446,7 +1146,7 @@ tls_rx_rec_wait(struct sock *sk, struct sk_psock *psock, bool nonblock, } if (unlikely(!tls_strp_msg_load(&ctx->strp, released))) - return tls_rx_rec_wait(sk, psock, nonblock, false, has_copied); + return tls_rx_rec_wait(sk, nonblock, false, has_copied); return 1; } @@ -2096,7 +1796,6 @@ int tls_sw_recvmsg(struct sock *sk, struct tls_sw_context_rx *ctx = tls_sw_ctx_rx(tls_ctx); struct tls_prot_info *prot = &tls_ctx->prot_info; ssize_t decrypted = 0, async_copy_bytes = 0; - struct sk_psock *psock; unsigned char control = 0; size_t flushed_at = 0; struct strp_msg *rxm; @@ -2109,7 +1808,6 @@ int tls_sw_recvmsg(struct sock *sk, bool is_peek = flags & MSG_PEEK; bool rx_more = false; bool released = true; - bool bpf_strp_enabled; bool zc_capable; if (unlikely(flags & MSG_ERRQUEUE)) @@ -2118,8 +1816,6 @@ int tls_sw_recvmsg(struct sock *sk, err = tls_rx_reader_lock(sk, ctx, flags & MSG_DONTWAIT); if (err < 0) return err; - psock = sk_psock_get(sk); - bpf_strp_enabled = sk_psock_strp_enabled(psock); /* If crypto failed the connection is broken */ err = ctx->async_wait.err; @@ -2140,27 +1836,16 @@ int tls_sw_recvmsg(struct sock *sk, target = sock_rcvlowat(sk, flags & MSG_WAITALL, len); len = len - copied; - zc_capable = !bpf_strp_enabled && !is_kvec && !is_peek && - ctx->zc_capable; + zc_capable = !is_kvec && !is_peek && ctx->zc_capable; decrypted = 0; while (len && (decrypted + copied < target || tls_strp_msg_ready(ctx))) { struct tls_decrypt_arg darg; int to_decrypt, chunk; - err = tls_rx_rec_wait(sk, psock, flags & MSG_DONTWAIT, + err = tls_rx_rec_wait(sk, flags & MSG_DONTWAIT, released, !!(decrypted + copied)); - if (err <= 0) { - if (psock) { - chunk = sk_msg_recvmsg(sk, psock, msg, len, - flags); - if (chunk > 0) { - decrypted += chunk; - len -= chunk; - continue; - } - } + if (err <= 0) goto recv_end; - } memset(&darg.inargs, 0, sizeof(darg.inargs)); @@ -2174,7 +1859,7 @@ int tls_sw_recvmsg(struct sock *sk, darg.zc = true; /* Do not use async mode if record is non-data */ - if (tlm->control == TLS_RECORD_TYPE_DATA && !bpf_strp_enabled) + if (tlm->control == TLS_RECORD_TYPE_DATA) darg.async = ctx->async_capable; else darg.async = false; @@ -2230,18 +1915,6 @@ put_on_rx_list: continue; } - if (bpf_strp_enabled) { - released = true; - err = sk_psock_tls_strp_read(psock, skb); - if (err != __SK_PASS) { - rxm->offset = rxm->offset + rxm->full_len; - rxm->full_len = 0; - if (err == __SK_DROP) - consume_skb(skb); - continue; - } - } - if (partially_consumed) chunk = len; @@ -2304,8 +1977,6 @@ recv_end: end: tls_rx_reader_unlock(sk, ctx); - if (psock) - sk_psock_put(sk, psock); return copied ? : err; } @@ -2332,7 +2003,7 @@ ssize_t tls_sw_splice_read(struct socket *sock, loff_t *ppos, } else { struct tls_decrypt_arg darg; - err = tls_rx_rec_wait(sk, NULL, flags & SPLICE_F_NONBLOCK, + err = tls_rx_rec_wait(sk, flags & SPLICE_F_NONBLOCK, true, false); if (err <= 0) goto splice_read_end; @@ -2417,7 +2088,7 @@ int tls_sw_read_sock(struct sock *sk, read_descriptor_t *desc, } else { struct tls_decrypt_arg darg; - err = tls_rx_rec_wait(sk, NULL, true, released, !!copied); + err = tls_rx_rec_wait(sk, true, released, !!copied); if (err <= 0) goto read_sock_end; @@ -2473,16 +2144,8 @@ bool tls_sw_sock_is_readable(struct sock *sk) { struct tls_context *tls_ctx = tls_get_ctx(sk); struct tls_sw_context_rx *ctx = tls_sw_ctx_rx(tls_ctx); - bool ingress_empty = true; - struct sk_psock *psock; - rcu_read_lock(); - psock = sk_psock(sk); - if (psock) - ingress_empty = list_empty(&psock->ingress_msg); - rcu_read_unlock(); - - return !ingress_empty || tls_strp_msg_ready(ctx) || + return tls_strp_msg_ready(ctx) || !skb_queue_empty(&ctx->rx_list); } @@ -2565,7 +2228,6 @@ static void tls_data_ready(struct sock *sk) { struct tls_context *tls_ctx = tls_get_ctx(sk); struct tls_sw_context_rx *ctx = tls_sw_ctx_rx(tls_ctx); - struct sk_psock *psock; gfp_t alloc_save; trace_sk_data_ready(sk); @@ -2574,13 +2236,6 @@ static void tls_data_ready(struct sock *sk) sk->sk_allocation = GFP_ATOMIC; tls_strp_data_ready(&ctx->strp); sk->sk_allocation = alloc_save; - - psock = sk_psock_get(sk); - if (psock) { - if (!list_empty(&psock->ingress_msg)) - ctx->saved_data_ready(sk); - sk_psock_put(sk, psock); - } } void tls_sw_cancel_work_tx(struct tls_context *tls_ctx) -- cgit v1.2.3 From 023f9b0f2f4ffbf78ce9dfb8fbacb767c8e97162 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Mon, 15 Jun 2026 15:29:34 -0700 Subject: appletalk: stop storing per-interface state in struct net_device AppleTalk keeps its per-interface control block (struct atalk_iface) directly in struct netdevice (dev->atalk_ptr). This is the only thing tying the protocol into the core net_device layout and is the sole blocker to moving AppleTalk out of tree. Replace dev->atalk_ptr with a small ifindex-keyed hashtable internal to ddp.c. The existing atalk_interfaces list stays the owner of the iface objects; the hashtable is purely a fast dev->iface index and reuses the same atalk_interfaces_lock. AFAICT this patch does not make this code any more racy than it already is, I'm sure Sashiko will point out some basically existing bugs. AFAICT atalk_interfaces_lock is the innermost lock already. Acked-by: Stephen Hemminger Link: https://patch.msgid.link/20260615222935.947233-2-kuba@kernel.org Signed-off-by: Jakub Kicinski --- .../networking/net_cachelines/net_device.rst | 1 - include/linux/atalk.h | 8 +--- include/linux/netdevice.h | 4 -- net/appletalk/ddp.c | 48 +++++++++++++++++++--- 4 files changed, 45 insertions(+), 16 deletions(-) (limited to 'include') diff --git a/Documentation/networking/net_cachelines/net_device.rst b/Documentation/networking/net_cachelines/net_device.rst index eb2e6851c6f6..512f6d6fa3d8 100644 --- a/Documentation/networking/net_cachelines/net_device.rst +++ b/Documentation/networking/net_cachelines/net_device.rst @@ -90,7 +90,6 @@ struct inet6_dev* ip6_ptr read_mostly struct vlan_info* vlan_info struct dsa_port* dsa_ptr struct tipc_bearer* tipc_ptr -void* atalk_ptr struct wireless_dev* ieee80211_ptr struct wpan_dev* ieee802154_ptr struct mpls_dev* mpls_ptr diff --git a/include/linux/atalk.h b/include/linux/atalk.h index a55bfc6567d0..ce7e6bfa9e2a 100644 --- a/include/linux/atalk.h +++ b/include/linux/atalk.h @@ -30,6 +30,7 @@ struct atalk_iface { #define ATIF_PROBE_FAIL 2 /* Probe collided */ struct atalk_netrange nets; struct atalk_iface *next; + struct hlist_node hash_node; /* keyed on dev->ifindex */ }; struct atalk_sock { @@ -113,12 +114,7 @@ extern int aarp_proto_init(void); /* Inter module exports */ /* Give a device find its atif control structure */ -#if IS_ENABLED(CONFIG_ATALK) -static inline struct atalk_iface *atalk_find_dev(struct net_device *dev) -{ - return dev->atalk_ptr; -} -#endif +extern struct atalk_iface *atalk_find_dev(struct net_device *dev); extern struct atalk_addr *atalk_find_dev_addr(struct net_device *dev); extern struct net_device *atrtr_get_dev(struct atalk_addr *sa); diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h index 7f4f0837c09f..655564621f28 100644 --- a/include/linux/netdevice.h +++ b/include/linux/netdevice.h @@ -1947,7 +1947,6 @@ enum netdev_reg_state { * @vlan_info: VLAN info * @dsa_ptr: dsa specific data * @tipc_ptr: TIPC specific data - * @atalk_ptr: AppleTalk link * @ip_ptr: IPv4 specific data * @ip6_ptr: IPv6 specific data * @ieee80211_ptr: IEEE 802.11 specific data, assign before registering @@ -2349,9 +2348,6 @@ struct net_device { #if IS_ENABLED(CONFIG_TIPC) struct tipc_bearer __rcu *tipc_ptr; #endif -#if IS_ENABLED(CONFIG_ATALK) - void *atalk_ptr; -#endif #if IS_ENABLED(CONFIG_CFG80211) struct wireless_dev *ieee80211_ptr; #endif diff --git a/net/appletalk/ddp.c b/net/appletalk/ddp.c index 30a6dc06291c..afb86ce6e644 100644 --- a/net/appletalk/ddp.c +++ b/net/appletalk/ddp.c @@ -52,6 +52,7 @@ #include /* For TIOCOUTQ/INQ */ #include #include +#include #include #include #include @@ -204,6 +205,33 @@ DEFINE_RWLOCK(atalk_routes_lock); struct atalk_iface *atalk_interfaces; DEFINE_RWLOCK(atalk_interfaces_lock); +/* Fast dev->iface lookup, keyed on ifindex. Shares atalk_interfaces_lock with + * the atalk_interfaces list, which remains the owner of the iface objects. + */ +#define ATALK_IFACE_HASH_BITS 8 +static DEFINE_HASHTABLE(atalk_iface_hash, ATALK_IFACE_HASH_BITS); + +/* Find the iface for @dev. Caller must hold atalk_interfaces_lock. */ +static struct atalk_iface *__atalk_find_dev(struct net_device *dev) +{ + struct atalk_iface *iface; + + hash_for_each_possible(atalk_iface_hash, iface, hash_node, dev->ifindex) + if (iface->dev == dev) + return iface; + return NULL; +} + +struct atalk_iface *atalk_find_dev(struct net_device *dev) +{ + struct atalk_iface *iface; + + read_lock_bh(&atalk_interfaces_lock); + iface = __atalk_find_dev(dev); + read_unlock_bh(&atalk_interfaces_lock); + return iface; +} + /* For probing devices or in a routerless network */ struct atalk_route atrtr_default; @@ -221,9 +249,9 @@ static void atif_drop_device(struct net_device *dev) while ((tmp = *iface) != NULL) { if (tmp->dev == dev) { *iface = tmp->next; + hash_del(&tmp->hash_node); dev_put(dev); kfree(tmp); - dev->atalk_ptr = NULL; } else iface = &tmp->next; } @@ -240,13 +268,13 @@ static struct atalk_iface *atif_add_device(struct net_device *dev, dev_hold(dev); iface->dev = dev; - dev->atalk_ptr = iface; iface->address = *sa; iface->status = 0; write_lock_bh(&atalk_interfaces_lock); iface->next = atalk_interfaces; atalk_interfaces = iface; + hash_add(atalk_iface_hash, &iface->hash_node, dev->ifindex); write_unlock_bh(&atalk_interfaces_lock); out: return iface; @@ -347,8 +375,15 @@ static int atif_proxy_probe_device(struct atalk_iface *atif, struct atalk_addr *atalk_find_dev_addr(struct net_device *dev) { - struct atalk_iface *iface = dev->atalk_ptr; - return iface ? &iface->address : NULL; + struct atalk_addr *addr = NULL; + struct atalk_iface *iface; + + read_lock_bh(&atalk_interfaces_lock); + iface = __atalk_find_dev(dev); + if (iface) + addr = &iface->address; + read_unlock_bh(&atalk_interfaces_lock); + return addr; } static struct atalk_addr *atalk_find_primary(void) @@ -388,8 +423,10 @@ out: */ static struct atalk_iface *atalk_find_anynet(int node, struct net_device *dev) { - struct atalk_iface *iface = dev->atalk_ptr; + struct atalk_iface *iface; + read_lock_bh(&atalk_interfaces_lock); + iface = __atalk_find_dev(dev); if (!iface || iface->status & ATIF_PROBE) goto out_err; @@ -398,6 +435,7 @@ static struct atalk_iface *atalk_find_anynet(int node, struct net_device *dev) node != ATADDR_ANYNODE) goto out_err; out: + read_unlock_bh(&atalk_interfaces_lock); return iface; out_err: iface = NULL; -- cgit v1.2.3 From 8a398a0c189ead8bbce98f5be70b8ea0e30b21f8 Mon Sep 17 00:00:00 2001 From: Jakub Kicinski Date: Mon, 15 Jun 2026 15:29:35 -0700 Subject: appletalk: move the protocol out of tree AppleTalk has been removed in MacOS X 10.6 (Snow Leopard), in 2009, according to Wikipedia. We recently got a burst of AI generated fixes to this protocol which nobody is reviewing. Let AppleTalk follow AX.25 and hamradio out of the Linux tree. We we will maintain the code at: github.com/linux-netdev/mod-orphan for anyone interested in playing with it. Retain the uAPI for now. No strong reason, simply because I suspect keeping it will be less controversial. Acked-by: Stephen Hemminger Link: https://patch.msgid.link/20260615222935.947233-3-kuba@kernel.org Signed-off-by: Jakub Kicinski --- Documentation/admin-guide/sysctl/net.rst | 46 +- MAINTAINERS | 5 +- arch/arm/configs/ixp4xx_defconfig | 1 - arch/m68k/configs/amiga_defconfig | 1 - arch/m68k/configs/apollo_defconfig | 1 - arch/m68k/configs/atari_defconfig | 1 - arch/m68k/configs/bvme6000_defconfig | 1 - arch/m68k/configs/hp300_defconfig | 1 - arch/m68k/configs/mac_defconfig | 1 - arch/m68k/configs/multi_defconfig | 1 - arch/m68k/configs/mvme147_defconfig | 1 - arch/m68k/configs/mvme16x_defconfig | 1 - arch/m68k/configs/q40_defconfig | 1 - arch/m68k/configs/sun3_defconfig | 1 - arch/m68k/configs/sun3x_defconfig | 1 - arch/mips/configs/gpr_defconfig | 1 - arch/mips/configs/malta_defconfig | 1 - arch/mips/configs/malta_kvm_defconfig | 1 - arch/mips/configs/malta_qemu_32r6_defconfig | 1 - arch/mips/configs/maltaaprp_defconfig | 1 - arch/mips/configs/maltasmvp_defconfig | 1 - arch/mips/configs/maltasmvp_eva_defconfig | 1 - arch/mips/configs/maltaup_defconfig | 1 - arch/mips/configs/maltaup_xpa_defconfig | 1 - arch/mips/configs/mtx1_defconfig | 1 - arch/powerpc/configs/ppc6xx_defconfig | 1 - arch/sh/configs/landisk_defconfig | 1 - include/linux/atalk.h | 182 --- net/802/Makefile | 1 - net/Kconfig | 1 - net/Makefile | 1 - net/appletalk/Kconfig | 30 - net/appletalk/Makefile | 10 - net/appletalk/aarp.c | 1041 -------------- net/appletalk/atalk_proc.c | 242 ---- net/appletalk/ddp.c | 2055 --------------------------- net/appletalk/sysctl_net_atalk.c | 58 - 37 files changed, 3 insertions(+), 3694 deletions(-) delete mode 100644 include/linux/atalk.h delete mode 100644 net/appletalk/Kconfig delete mode 100644 net/appletalk/Makefile delete mode 100644 net/appletalk/aarp.c delete mode 100644 net/appletalk/atalk_proc.c delete mode 100644 net/appletalk/ddp.c delete mode 100644 net/appletalk/sysctl_net_atalk.c (limited to 'include') diff --git a/Documentation/admin-guide/sysctl/net.rst b/Documentation/admin-guide/sysctl/net.rst index 0724a793798f..37c5a0624c5d 100644 --- a/Documentation/admin-guide/sysctl/net.rst +++ b/Documentation/admin-guide/sysctl/net.rst @@ -475,51 +475,7 @@ Please see: Documentation/networking/ip-sysctl.rst and Documentation/admin-guide/sysctl/net.rst for descriptions of these entries. -4. Appletalk ------------- - -The /proc/sys/net/appletalk directory holds the Appletalk configuration data -when Appletalk is loaded. The configurable parameters are: - -aarp-expiry-time ----------------- - -The amount of time we keep an ARP entry before expiring it. Used to age out -old hosts. - -aarp-resolve-time ------------------ - -The amount of time we will spend trying to resolve an Appletalk address. - -aarp-retransmit-limit ---------------------- - -The number of times we will retransmit a query before giving up. - -aarp-tick-time --------------- - -Controls the rate at which expires are checked. - -The directory /proc/net/appletalk holds the list of active Appletalk sockets -on a machine. - -The fields indicate the DDP type, the local address (in network:node format) -the remote address, the size of the transmit pending queue, the size of the -received queue (bytes waiting for applications to read) the state and the uid -owning the socket. - -/proc/net/atalk_iface lists all the interfaces configured for appletalk.It -shows the name of the interface, its Appletalk address, the network range on -that address (or network number for phase 1 networks), and the status of the -interface. - -/proc/net/atalk_route lists each known network route. It lists the target -(network) that the route leads to, the router (may be directly connected), the -route flags, and the device the route is using. - -5. TIPC +4. TIPC ------- tipc_rmem diff --git a/MAINTAINERS b/MAINTAINERS index cc1dde0c9067..06df1171f4cf 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1968,10 +1968,9 @@ F: drivers/hwmon/applesmc.c APPLETALK NETWORK LAYER L: netdev@vger.kernel.org -S: Odd fixes -F: include/linux/atalk.h +S: Obsolete F: include/uapi/linux/atalk.h -F: net/appletalk/ +F: include/uapi/linux/if_ltalk.h APPLIED MICRO (APM) X-GENE DEVICE TREE SUPPORT M: Khuong Dinh diff --git a/arch/arm/configs/ixp4xx_defconfig b/arch/arm/configs/ixp4xx_defconfig index 01d72580bcc5..418ef909572b 100644 --- a/arch/arm/configs/ixp4xx_defconfig +++ b/arch/arm/configs/ixp4xx_defconfig @@ -55,7 +55,6 @@ CONFIG_ATM=y CONFIG_ATM_BR2684=m CONFIG_BRIDGE=m CONFIG_VLAN_8021Q=m -CONFIG_ATALK=m CONFIG_DEV_APPLETALK=m CONFIG_IPDDP=m CONFIG_IPDDP_ENCAP=y diff --git a/arch/m68k/configs/amiga_defconfig b/arch/m68k/configs/amiga_defconfig index aadff466830f..93b1cb632b19 100644 --- a/arch/m68k/configs/amiga_defconfig +++ b/arch/m68k/configs/amiga_defconfig @@ -236,7 +236,6 @@ CONFIG_RDS=m CONFIG_RDS_TCP=m CONFIG_L2TP=m CONFIG_BRIDGE=m -CONFIG_ATALK=m CONFIG_DNS_RESOLVER=y CONFIG_BATMAN_ADV=m # CONFIG_BATMAN_ADV_BATMAN_V is not set diff --git a/arch/m68k/configs/apollo_defconfig b/arch/m68k/configs/apollo_defconfig index ea9487a39884..6edfa55c8b9f 100644 --- a/arch/m68k/configs/apollo_defconfig +++ b/arch/m68k/configs/apollo_defconfig @@ -232,7 +232,6 @@ CONFIG_RDS=m CONFIG_RDS_TCP=m CONFIG_L2TP=m CONFIG_BRIDGE=m -CONFIG_ATALK=m CONFIG_DNS_RESOLVER=y CONFIG_BATMAN_ADV=m # CONFIG_BATMAN_ADV_BATMAN_V is not set diff --git a/arch/m68k/configs/atari_defconfig b/arch/m68k/configs/atari_defconfig index a70127ac7a2d..f8808aac9e63 100644 --- a/arch/m68k/configs/atari_defconfig +++ b/arch/m68k/configs/atari_defconfig @@ -239,7 +239,6 @@ CONFIG_RDS=m CONFIG_RDS_TCP=m CONFIG_L2TP=m CONFIG_BRIDGE=m -CONFIG_ATALK=m CONFIG_DNS_RESOLVER=y CONFIG_BATMAN_ADV=m # CONFIG_BATMAN_ADV_BATMAN_V is not set diff --git a/arch/m68k/configs/bvme6000_defconfig b/arch/m68k/configs/bvme6000_defconfig index 83da79382538..067ecf850201 100644 --- a/arch/m68k/configs/bvme6000_defconfig +++ b/arch/m68k/configs/bvme6000_defconfig @@ -229,7 +229,6 @@ CONFIG_RDS=m CONFIG_RDS_TCP=m CONFIG_L2TP=m CONFIG_BRIDGE=m -CONFIG_ATALK=m CONFIG_DNS_RESOLVER=y CONFIG_BATMAN_ADV=m # CONFIG_BATMAN_ADV_BATMAN_V is not set diff --git a/arch/m68k/configs/hp300_defconfig b/arch/m68k/configs/hp300_defconfig index cea5ab74b3b1..aa93c67caa43 100644 --- a/arch/m68k/configs/hp300_defconfig +++ b/arch/m68k/configs/hp300_defconfig @@ -231,7 +231,6 @@ CONFIG_RDS=m CONFIG_RDS_TCP=m CONFIG_L2TP=m CONFIG_BRIDGE=m -CONFIG_ATALK=m CONFIG_DNS_RESOLVER=y CONFIG_BATMAN_ADV=m # CONFIG_BATMAN_ADV_BATMAN_V is not set diff --git a/arch/m68k/configs/mac_defconfig b/arch/m68k/configs/mac_defconfig index 26406777376d..79d2e0383484 100644 --- a/arch/m68k/configs/mac_defconfig +++ b/arch/m68k/configs/mac_defconfig @@ -230,7 +230,6 @@ CONFIG_RDS=m CONFIG_RDS_TCP=m CONFIG_L2TP=m CONFIG_BRIDGE=m -CONFIG_ATALK=m CONFIG_DNS_RESOLVER=y CONFIG_BATMAN_ADV=m # CONFIG_BATMAN_ADV_BATMAN_V is not set diff --git a/arch/m68k/configs/multi_defconfig b/arch/m68k/configs/multi_defconfig index 8357491645ad..b13889268ff5 100644 --- a/arch/m68k/configs/multi_defconfig +++ b/arch/m68k/configs/multi_defconfig @@ -250,7 +250,6 @@ CONFIG_RDS=m CONFIG_RDS_TCP=m CONFIG_L2TP=m CONFIG_BRIDGE=m -CONFIG_ATALK=m CONFIG_DNS_RESOLVER=y CONFIG_BATMAN_ADV=m # CONFIG_BATMAN_ADV_BATMAN_V is not set diff --git a/arch/m68k/configs/mvme147_defconfig b/arch/m68k/configs/mvme147_defconfig index fe94f95862e7..cd773d2509bb 100644 --- a/arch/m68k/configs/mvme147_defconfig +++ b/arch/m68k/configs/mvme147_defconfig @@ -228,7 +228,6 @@ CONFIG_RDS=m CONFIG_RDS_TCP=m CONFIG_L2TP=m CONFIG_BRIDGE=m -CONFIG_ATALK=m CONFIG_DNS_RESOLVER=y CONFIG_BATMAN_ADV=m # CONFIG_BATMAN_ADV_BATMAN_V is not set diff --git a/arch/m68k/configs/mvme16x_defconfig b/arch/m68k/configs/mvme16x_defconfig index ba67cacc079e..af921f24a5c8 100644 --- a/arch/m68k/configs/mvme16x_defconfig +++ b/arch/m68k/configs/mvme16x_defconfig @@ -229,7 +229,6 @@ CONFIG_RDS=m CONFIG_RDS_TCP=m CONFIG_L2TP=m CONFIG_BRIDGE=m -CONFIG_ATALK=m CONFIG_DNS_RESOLVER=y CONFIG_BATMAN_ADV=m # CONFIG_BATMAN_ADV_BATMAN_V is not set diff --git a/arch/m68k/configs/q40_defconfig b/arch/m68k/configs/q40_defconfig index 552399979e4b..f2eb67618e97 100644 --- a/arch/m68k/configs/q40_defconfig +++ b/arch/m68k/configs/q40_defconfig @@ -230,7 +230,6 @@ CONFIG_RDS=m CONFIG_RDS_TCP=m CONFIG_L2TP=m CONFIG_BRIDGE=m -CONFIG_ATALK=m CONFIG_DNS_RESOLVER=y CONFIG_BATMAN_ADV=m # CONFIG_BATMAN_ADV_BATMAN_V is not set diff --git a/arch/m68k/configs/sun3_defconfig b/arch/m68k/configs/sun3_defconfig index b4f3935d3d18..108dac7ed55e 100644 --- a/arch/m68k/configs/sun3_defconfig +++ b/arch/m68k/configs/sun3_defconfig @@ -225,7 +225,6 @@ CONFIG_RDS=m CONFIG_RDS_TCP=m CONFIG_L2TP=m CONFIG_BRIDGE=m -CONFIG_ATALK=m CONFIG_DNS_RESOLVER=y CONFIG_BATMAN_ADV=m # CONFIG_BATMAN_ADV_BATMAN_V is not set diff --git a/arch/m68k/configs/sun3x_defconfig b/arch/m68k/configs/sun3x_defconfig index bb519520ae6e..1e1d946cbe23 100644 --- a/arch/m68k/configs/sun3x_defconfig +++ b/arch/m68k/configs/sun3x_defconfig @@ -226,7 +226,6 @@ CONFIG_RDS=m CONFIG_RDS_TCP=m CONFIG_L2TP=m CONFIG_BRIDGE=m -CONFIG_ATALK=m CONFIG_DNS_RESOLVER=y CONFIG_BATMAN_ADV=m # CONFIG_BATMAN_ADV_BATMAN_V is not set diff --git a/arch/mips/configs/gpr_defconfig b/arch/mips/configs/gpr_defconfig index 47016655a089..7834569a8314 100644 --- a/arch/mips/configs/gpr_defconfig +++ b/arch/mips/configs/gpr_defconfig @@ -91,7 +91,6 @@ CONFIG_ATM_BR2684=m CONFIG_BRIDGE=m CONFIG_VLAN_8021Q=m CONFIG_LLC2=m -CONFIG_ATALK=m CONFIG_DEV_APPLETALK=m CONFIG_IPDDP=m CONFIG_IPDDP_ENCAP=y diff --git a/arch/mips/configs/malta_defconfig b/arch/mips/configs/malta_defconfig index 61c9d5cd1a75..89d7c47ca7f0 100644 --- a/arch/mips/configs/malta_defconfig +++ b/arch/mips/configs/malta_defconfig @@ -170,7 +170,6 @@ CONFIG_IP_SCTP=m CONFIG_BRIDGE=m CONFIG_VLAN_8021Q=m CONFIG_VLAN_8021Q_GVRP=y -CONFIG_ATALK=m CONFIG_DEV_APPLETALK=m CONFIG_IPDDP=m CONFIG_IPDDP_ENCAP=y diff --git a/arch/mips/configs/malta_kvm_defconfig b/arch/mips/configs/malta_kvm_defconfig index f862fbc7fbb7..39c69af12adc 100644 --- a/arch/mips/configs/malta_kvm_defconfig +++ b/arch/mips/configs/malta_kvm_defconfig @@ -174,7 +174,6 @@ CONFIG_IP_SCTP=m CONFIG_BRIDGE=m CONFIG_VLAN_8021Q=m CONFIG_VLAN_8021Q_GVRP=y -CONFIG_ATALK=m CONFIG_DEV_APPLETALK=m CONFIG_IPDDP=m CONFIG_IPDDP_ENCAP=y diff --git a/arch/mips/configs/malta_qemu_32r6_defconfig b/arch/mips/configs/malta_qemu_32r6_defconfig index 14cdd23f1acb..5feb1dc43bae 100644 --- a/arch/mips/configs/malta_qemu_32r6_defconfig +++ b/arch/mips/configs/malta_qemu_32r6_defconfig @@ -46,7 +46,6 @@ CONFIG_INET6_IPCOMP=m CONFIG_IPV6_TUNNEL=m CONFIG_BRIDGE=m CONFIG_VLAN_8021Q=m -CONFIG_ATALK=m CONFIG_DEV_APPLETALK=m CONFIG_IPDDP=m CONFIG_IPDDP_ENCAP=y diff --git a/arch/mips/configs/maltaaprp_defconfig b/arch/mips/configs/maltaaprp_defconfig index 2943593264b9..a69fb992b82a 100644 --- a/arch/mips/configs/maltaaprp_defconfig +++ b/arch/mips/configs/maltaaprp_defconfig @@ -48,7 +48,6 @@ CONFIG_INET6_IPCOMP=m CONFIG_IPV6_TUNNEL=m CONFIG_BRIDGE=m CONFIG_VLAN_8021Q=m -CONFIG_ATALK=m CONFIG_DEV_APPLETALK=m CONFIG_IPDDP=m CONFIG_IPDDP_ENCAP=y diff --git a/arch/mips/configs/maltasmvp_defconfig b/arch/mips/configs/maltasmvp_defconfig index 47226fca0548..689edb56c055 100644 --- a/arch/mips/configs/maltasmvp_defconfig +++ b/arch/mips/configs/maltasmvp_defconfig @@ -49,7 +49,6 @@ CONFIG_INET6_IPCOMP=m CONFIG_IPV6_TUNNEL=m CONFIG_BRIDGE=m CONFIG_VLAN_8021Q=m -CONFIG_ATALK=m CONFIG_DEV_APPLETALK=m CONFIG_IPDDP=m CONFIG_IPDDP_ENCAP=y diff --git a/arch/mips/configs/maltasmvp_eva_defconfig b/arch/mips/configs/maltasmvp_eva_defconfig index 09187a78409f..a56db606b2d8 100644 --- a/arch/mips/configs/maltasmvp_eva_defconfig +++ b/arch/mips/configs/maltasmvp_eva_defconfig @@ -50,7 +50,6 @@ CONFIG_INET6_IPCOMP=m CONFIG_IPV6_TUNNEL=m CONFIG_BRIDGE=m CONFIG_VLAN_8021Q=m -CONFIG_ATALK=m CONFIG_DEV_APPLETALK=m CONFIG_IPDDP=m CONFIG_IPDDP_ENCAP=y diff --git a/arch/mips/configs/maltaup_defconfig b/arch/mips/configs/maltaup_defconfig index a80783097c1e..ee10762ab593 100644 --- a/arch/mips/configs/maltaup_defconfig +++ b/arch/mips/configs/maltaup_defconfig @@ -47,7 +47,6 @@ CONFIG_INET6_IPCOMP=m CONFIG_IPV6_TUNNEL=m CONFIG_BRIDGE=m CONFIG_VLAN_8021Q=m -CONFIG_ATALK=m CONFIG_DEV_APPLETALK=m CONFIG_IPDDP=m CONFIG_IPDDP_ENCAP=y diff --git a/arch/mips/configs/maltaup_xpa_defconfig b/arch/mips/configs/maltaup_xpa_defconfig index e660c503654e..0e207bb0f794 100644 --- a/arch/mips/configs/maltaup_xpa_defconfig +++ b/arch/mips/configs/maltaup_xpa_defconfig @@ -171,7 +171,6 @@ CONFIG_IP_SCTP=m CONFIG_BRIDGE=m CONFIG_VLAN_8021Q=m CONFIG_VLAN_8021Q_GVRP=y -CONFIG_ATALK=m CONFIG_DEV_APPLETALK=m CONFIG_IPDDP=m CONFIG_IPDDP_ENCAP=y diff --git a/arch/mips/configs/mtx1_defconfig b/arch/mips/configs/mtx1_defconfig index a1c374bd4785..46b40784a828 100644 --- a/arch/mips/configs/mtx1_defconfig +++ b/arch/mips/configs/mtx1_defconfig @@ -136,7 +136,6 @@ CONFIG_ATM_BR2684=m CONFIG_BRIDGE=m CONFIG_VLAN_8021Q=m CONFIG_LLC2=m -CONFIG_ATALK=m CONFIG_DEV_APPLETALK=m CONFIG_IPDDP=m CONFIG_IPDDP_ENCAP=y diff --git a/arch/powerpc/configs/ppc6xx_defconfig b/arch/powerpc/configs/ppc6xx_defconfig index c9bdaed06d03..f528cab1c6cf 100644 --- a/arch/powerpc/configs/ppc6xx_defconfig +++ b/arch/powerpc/configs/ppc6xx_defconfig @@ -228,7 +228,6 @@ CONFIG_ATM=m CONFIG_ATM_BR2684=m CONFIG_BRIDGE=m CONFIG_VLAN_8021Q=m -CONFIG_ATALK=m CONFIG_DEV_APPLETALK=m CONFIG_IPDDP=m CONFIG_IPDDP_ENCAP=y diff --git a/arch/sh/configs/landisk_defconfig b/arch/sh/configs/landisk_defconfig index 4c39a23e6e0d..2b7e10c9acee 100644 --- a/arch/sh/configs/landisk_defconfig +++ b/arch/sh/configs/landisk_defconfig @@ -24,7 +24,6 @@ CONFIG_IP_ADVANCED_ROUTER=y CONFIG_IP_PNP=y # CONFIG_IPV6 is not set CONFIG_NETFILTER=y -CONFIG_ATALK=m CONFIG_BLK_DEV_LOOP=y CONFIG_BLK_DEV_RAM=y CONFIG_ATA=y diff --git a/include/linux/atalk.h b/include/linux/atalk.h deleted file mode 100644 index ce7e6bfa9e2a..000000000000 --- a/include/linux/atalk.h +++ /dev/null @@ -1,182 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -#ifndef __LINUX_ATALK_H__ -#define __LINUX_ATALK_H__ - - -#include -#include - -struct atalk_route { - struct net_device *dev; - struct atalk_addr target; - struct atalk_addr gateway; - int flags; - struct atalk_route *next; -}; - -/** - * struct atalk_iface - AppleTalk Interface - * @dev - Network device associated with this interface - * @address - Our address - * @status - What are we doing? - * @nets - Associated direct netrange - * @next - next element in the list of interfaces - */ -struct atalk_iface { - struct net_device *dev; - struct atalk_addr address; - int status; -#define ATIF_PROBE 1 /* Probing for an address */ -#define ATIF_PROBE_FAIL 2 /* Probe collided */ - struct atalk_netrange nets; - struct atalk_iface *next; - struct hlist_node hash_node; /* keyed on dev->ifindex */ -}; - -struct atalk_sock { - /* struct sock has to be the first member of atalk_sock */ - struct sock sk; - __be16 dest_net; - __be16 src_net; - unsigned char dest_node; - unsigned char src_node; - unsigned char dest_port; - unsigned char src_port; -}; - -static inline struct atalk_sock *at_sk(struct sock *sk) -{ - return (struct atalk_sock *)sk; -} - -struct ddpehdr { - __be16 deh_len_hops; /* lower 10 bits are length, next 4 - hops */ - __be16 deh_sum; - __be16 deh_dnet; - __be16 deh_snet; - __u8 deh_dnode; - __u8 deh_snode; - __u8 deh_dport; - __u8 deh_sport; - /* And netatalk apps expect to stick the type in themselves */ -}; - -static __inline__ struct ddpehdr *ddp_hdr(struct sk_buff *skb) -{ - return (struct ddpehdr *)skb_transport_header(skb); -} - -/* AppleTalk AARP headers */ -struct elapaarp { - __be16 hw_type; -#define AARP_HW_TYPE_ETHERNET 1 -#define AARP_HW_TYPE_TOKENRING 2 - __be16 pa_type; - __u8 hw_len; - __u8 pa_len; -#define AARP_PA_ALEN 4 - __be16 function; -#define AARP_REQUEST 1 -#define AARP_REPLY 2 -#define AARP_PROBE 3 - __u8 hw_src[ETH_ALEN]; - __u8 pa_src_zero; - __be16 pa_src_net; - __u8 pa_src_node; - __u8 hw_dst[ETH_ALEN]; - __u8 pa_dst_zero; - __be16 pa_dst_net; - __u8 pa_dst_node; -} __attribute__ ((packed)); - -static __inline__ struct elapaarp *aarp_hdr(struct sk_buff *skb) -{ - return (struct elapaarp *)skb_transport_header(skb); -} - -/* Not specified - how long till we drop a resolved entry */ -#define AARP_EXPIRY_TIME (5 * 60 * HZ) -/* Size of hash table */ -#define AARP_HASH_SIZE 16 -/* Fast retransmission timer when resolving */ -#define AARP_TICK_TIME (HZ / 5) -/* Send 10 requests then give up (2 seconds) */ -#define AARP_RETRANSMIT_LIMIT 10 -/* - * Some value bigger than total retransmit time + a bit for last reply to - * appear and to stop continual requests - */ -#define AARP_RESOLVE_TIME (10 * HZ) - -extern struct datalink_proto *ddp_dl, *aarp_dl; -extern int aarp_proto_init(void); - -/* Inter module exports */ - -/* Give a device find its atif control structure */ -extern struct atalk_iface *atalk_find_dev(struct net_device *dev); - -extern struct atalk_addr *atalk_find_dev_addr(struct net_device *dev); -extern struct net_device *atrtr_get_dev(struct atalk_addr *sa); -extern int aarp_send_ddp(struct net_device *dev, - struct sk_buff *skb, - struct atalk_addr *sa, void *hwaddr); -extern void aarp_device_down(struct net_device *dev); -extern void aarp_probe_network(struct atalk_iface *atif); -extern int aarp_proxy_probe_network(struct atalk_iface *atif, - struct atalk_addr *sa); -extern void aarp_proxy_remove(struct net_device *dev, - struct atalk_addr *sa); - -extern void aarp_cleanup_module(void); - -extern struct hlist_head atalk_sockets; -extern rwlock_t atalk_sockets_lock; - -extern struct atalk_route *atalk_routes; -extern rwlock_t atalk_routes_lock; - -extern struct atalk_iface *atalk_interfaces; -extern rwlock_t atalk_interfaces_lock; - -extern struct atalk_route atrtr_default; - -struct aarp_iter_state { - int bucket; - struct aarp_entry **table; -}; - -extern const struct seq_operations aarp_seq_ops; - -extern int sysctl_aarp_expiry_time; -extern int sysctl_aarp_tick_time; -extern int sysctl_aarp_retransmit_limit; -extern int sysctl_aarp_resolve_time; - -#ifdef CONFIG_SYSCTL -extern int atalk_register_sysctl(void); -extern void atalk_unregister_sysctl(void); -#else -static inline int atalk_register_sysctl(void) -{ - return 0; -} -static inline void atalk_unregister_sysctl(void) -{ -} -#endif - -#ifdef CONFIG_PROC_FS -extern int atalk_proc_init(void); -extern void atalk_proc_exit(void); -#else -static inline int atalk_proc_init(void) -{ - return 0; -} -static inline void atalk_proc_exit(void) -{ -} -#endif /* CONFIG_PROC_FS */ - -#endif /* __LINUX_ATALK_H__ */ diff --git a/net/802/Makefile b/net/802/Makefile index 9503ef6b2e06..54784f3cae5a 100644 --- a/net/802/Makefile +++ b/net/802/Makefile @@ -6,7 +6,6 @@ obj-$(CONFIG_LLC) += psnap.o obj-$(CONFIG_NET_FC) += fc.o obj-$(CONFIG_FDDI) += fddi.o -obj-$(CONFIG_ATALK) += psnap.o obj-$(CONFIG_STP) += stp.o obj-$(CONFIG_GARP) += garp.o obj-$(CONFIG_MRP) += mrp.o diff --git a/net/Kconfig b/net/Kconfig index bdea8aef7983..e38477393551 100644 --- a/net/Kconfig +++ b/net/Kconfig @@ -261,7 +261,6 @@ source "net/bridge/Kconfig" source "net/dsa/Kconfig" source "net/8021q/Kconfig" source "net/llc/Kconfig" -source "net/appletalk/Kconfig" source "net/x25/Kconfig" source "net/lapb/Kconfig" source "net/phonet/Kconfig" diff --git a/net/Makefile b/net/Makefile index d2175fce0406..5b2dd7f07a85 100644 --- a/net/Makefile +++ b/net/Makefile @@ -25,7 +25,6 @@ obj-$(CONFIG_NET_KEY) += key/ obj-$(CONFIG_BRIDGE) += bridge/ obj-$(CONFIG_NET_DEVLINK) += devlink/ obj-y += dsa/ -obj-$(CONFIG_ATALK) += appletalk/ obj-$(CONFIG_X25) += x25/ obj-$(CONFIG_LAPB) += lapb/ obj-$(CONFIG_CAN) += can/ diff --git a/net/appletalk/Kconfig b/net/appletalk/Kconfig deleted file mode 100644 index 041141abf925..000000000000 --- a/net/appletalk/Kconfig +++ /dev/null @@ -1,30 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -# -# Appletalk configuration -# -config ATALK - tristate "Appletalk protocol support" - select LLC - help - AppleTalk is the protocol that Apple computers can use to communicate - on a network. If your Linux box is connected to such a network and you - wish to connect to it, say Y. You will need to use the netatalk package - so that your Linux box can act as a print and file server for Macs as - well as access AppleTalk printers. Check out - on the WWW for details. - EtherTalk is the name used for AppleTalk over Ethernet and the - cheaper and slower LocalTalk is AppleTalk over a proprietary Apple - network using serial links. EtherTalk and LocalTalk are fully - supported by Linux. - - General information about how to connect Linux, Windows machines and - Macs is on the WWW at . The - NET3-4-HOWTO, available from - , contains valuable - information as well. - - To compile this driver as a module, choose M here: the module will be - called appletalk. You almost certainly want to compile it as a - module so you can restart your AppleTalk stack without rebooting - your machine. I hear that the GNU boycott of Apple is over, so - even politically correct people are allowed to say Y here. diff --git a/net/appletalk/Makefile b/net/appletalk/Makefile deleted file mode 100644 index 152312a15180..000000000000 --- a/net/appletalk/Makefile +++ /dev/null @@ -1,10 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -# -# Makefile for the Linux AppleTalk layer. -# - -obj-$(CONFIG_ATALK) += appletalk.o - -appletalk-y := aarp.o ddp.o -appletalk-$(CONFIG_PROC_FS) += atalk_proc.o -appletalk-$(CONFIG_SYSCTL) += sysctl_net_atalk.o diff --git a/net/appletalk/aarp.c b/net/appletalk/aarp.c deleted file mode 100644 index 078fb7a6efa5..000000000000 --- a/net/appletalk/aarp.c +++ /dev/null @@ -1,1041 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * AARP: An implementation of the AppleTalk AARP protocol for - * Ethernet 'ELAP'. - * - * Alan Cox - * - * This doesn't fit cleanly with the IP arp. Potentially we can use - * the generic neighbour discovery code to clean this up. - * - * FIXME: - * We ought to handle the retransmits with a single list and a - * separate fast timer for when it is needed. - * Use neighbour discovery code. - * Token Ring Support. - * - * References: - * Inside AppleTalk (2nd Ed). - * Fixes: - * Jaume Grau - flush caches on AARP_PROBE - * Rob Newberry - Added proxy AARP and AARP proc fs, - * moved probing from DDP module. - * Arnaldo C. Melo - don't mangle rx packets - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -int sysctl_aarp_expiry_time = AARP_EXPIRY_TIME; -int sysctl_aarp_tick_time = AARP_TICK_TIME; -int sysctl_aarp_retransmit_limit = AARP_RETRANSMIT_LIMIT; -int sysctl_aarp_resolve_time = AARP_RESOLVE_TIME; - -/* Lists of aarp entries */ -/** - * struct aarp_entry - AARP entry - * @refcnt: Reference count - * @last_sent: Last time we xmitted the aarp request - * @packet_queue: Queue of frames wait for resolution - * @status: Used for proxy AARP - * @expires_at: Entry expiry time - * @target_addr: DDP Address - * @dev: Device to use - * @hwaddr: Physical i/f address of target/router - * @xmit_count: When this hits 10 we give up - * @next: Next entry in chain - */ -struct aarp_entry { - refcount_t refcnt; - /* These first two are only used for unresolved entries */ - unsigned long last_sent; - struct sk_buff_head packet_queue; - int status; - unsigned long expires_at; - struct atalk_addr target_addr; - struct net_device *dev; - char hwaddr[ETH_ALEN]; - unsigned short xmit_count; - struct aarp_entry *next; -}; - -/* Hashed list of resolved, unresolved and proxy entries */ -static struct aarp_entry *resolved[AARP_HASH_SIZE]; -static struct aarp_entry *unresolved[AARP_HASH_SIZE]; -static struct aarp_entry *proxies[AARP_HASH_SIZE]; -static int unresolved_count; - -/* One lock protects it all. */ -static DEFINE_RWLOCK(aarp_lock); - -/* Used to walk the list and purge/kick entries. */ -static struct timer_list aarp_timer; - -static inline void aarp_entry_get(struct aarp_entry *a) -{ - refcount_inc(&a->refcnt); -} - -static inline void aarp_entry_put(struct aarp_entry *a) -{ - if (refcount_dec_and_test(&a->refcnt)) - kfree(a); -} - -/* - * Delete an aarp queue - * - * Must run under aarp_lock. - */ -static void __aarp_expire(struct aarp_entry *a) -{ - skb_queue_purge(&a->packet_queue); - aarp_entry_put(a); -} - -/* - * Send an aarp queue entry request - * - * Must run under aarp_lock. - */ -static void __aarp_send_query(struct aarp_entry *a) -{ - static unsigned char aarp_eth_multicast[ETH_ALEN] = - { 0x09, 0x00, 0x07, 0xFF, 0xFF, 0xFF }; - struct net_device *dev = a->dev; - struct elapaarp *eah; - int len = dev->hard_header_len + sizeof(*eah) + aarp_dl->header_length; - struct sk_buff *skb = alloc_skb(len, GFP_ATOMIC); - struct atalk_addr *sat = atalk_find_dev_addr(dev); - - if (!skb) - return; - - if (!sat) { - kfree_skb(skb); - return; - } - - /* Set up the buffer */ - skb_reserve(skb, dev->hard_header_len + aarp_dl->header_length); - skb_reset_network_header(skb); - skb_reset_transport_header(skb); - skb_put(skb, sizeof(*eah)); - skb->protocol = htons(ETH_P_ATALK); - skb->dev = dev; - eah = aarp_hdr(skb); - - /* Set up the ARP */ - eah->hw_type = htons(AARP_HW_TYPE_ETHERNET); - eah->pa_type = htons(ETH_P_ATALK); - eah->hw_len = ETH_ALEN; - eah->pa_len = AARP_PA_ALEN; - eah->function = htons(AARP_REQUEST); - - ether_addr_copy(eah->hw_src, dev->dev_addr); - - eah->pa_src_zero = 0; - eah->pa_src_net = sat->s_net; - eah->pa_src_node = sat->s_node; - - eth_zero_addr(eah->hw_dst); - - eah->pa_dst_zero = 0; - eah->pa_dst_net = a->target_addr.s_net; - eah->pa_dst_node = a->target_addr.s_node; - - /* Send it */ - aarp_dl->request(aarp_dl, skb, aarp_eth_multicast); - /* Update the sending count */ - a->xmit_count++; - a->last_sent = jiffies; -} - -/* This runs under aarp_lock and in softint context, so only atomic memory - * allocations can be used. */ -static void aarp_send_reply(struct net_device *dev, struct atalk_addr *us, - struct atalk_addr *them, unsigned char *sha) -{ - struct elapaarp *eah; - int len = dev->hard_header_len + sizeof(*eah) + aarp_dl->header_length; - struct sk_buff *skb = alloc_skb(len, GFP_ATOMIC); - - if (!skb) - return; - - /* Set up the buffer */ - skb_reserve(skb, dev->hard_header_len + aarp_dl->header_length); - skb_reset_network_header(skb); - skb_reset_transport_header(skb); - skb_put(skb, sizeof(*eah)); - skb->protocol = htons(ETH_P_ATALK); - skb->dev = dev; - eah = aarp_hdr(skb); - - /* Set up the ARP */ - eah->hw_type = htons(AARP_HW_TYPE_ETHERNET); - eah->pa_type = htons(ETH_P_ATALK); - eah->hw_len = ETH_ALEN; - eah->pa_len = AARP_PA_ALEN; - eah->function = htons(AARP_REPLY); - - ether_addr_copy(eah->hw_src, dev->dev_addr); - - eah->pa_src_zero = 0; - eah->pa_src_net = us->s_net; - eah->pa_src_node = us->s_node; - - if (!sha) - eth_zero_addr(eah->hw_dst); - else - ether_addr_copy(eah->hw_dst, sha); - - eah->pa_dst_zero = 0; - eah->pa_dst_net = them->s_net; - eah->pa_dst_node = them->s_node; - - /* Send it */ - aarp_dl->request(aarp_dl, skb, sha); -} - -/* - * Send probe frames. Called from aarp_probe_network and - * aarp_proxy_probe_network. - */ - -static void aarp_send_probe(struct net_device *dev, struct atalk_addr *us) -{ - struct elapaarp *eah; - int len = dev->hard_header_len + sizeof(*eah) + aarp_dl->header_length; - struct sk_buff *skb = alloc_skb(len, GFP_ATOMIC); - static unsigned char aarp_eth_multicast[ETH_ALEN] = - { 0x09, 0x00, 0x07, 0xFF, 0xFF, 0xFF }; - - if (!skb) - return; - - /* Set up the buffer */ - skb_reserve(skb, dev->hard_header_len + aarp_dl->header_length); - skb_reset_network_header(skb); - skb_reset_transport_header(skb); - skb_put(skb, sizeof(*eah)); - skb->protocol = htons(ETH_P_ATALK); - skb->dev = dev; - eah = aarp_hdr(skb); - - /* Set up the ARP */ - eah->hw_type = htons(AARP_HW_TYPE_ETHERNET); - eah->pa_type = htons(ETH_P_ATALK); - eah->hw_len = ETH_ALEN; - eah->pa_len = AARP_PA_ALEN; - eah->function = htons(AARP_PROBE); - - ether_addr_copy(eah->hw_src, dev->dev_addr); - - eah->pa_src_zero = 0; - eah->pa_src_net = us->s_net; - eah->pa_src_node = us->s_node; - - eth_zero_addr(eah->hw_dst); - - eah->pa_dst_zero = 0; - eah->pa_dst_net = us->s_net; - eah->pa_dst_node = us->s_node; - - /* Send it */ - aarp_dl->request(aarp_dl, skb, aarp_eth_multicast); -} - -/* - * Handle an aarp timer expire - * - * Must run under the aarp_lock. - */ - -static void __aarp_expire_timer(struct aarp_entry **n) -{ - struct aarp_entry *t; - - while (*n) - /* Expired ? */ - if (time_after(jiffies, (*n)->expires_at)) { - t = *n; - *n = (*n)->next; - __aarp_expire(t); - } else - n = &((*n)->next); -} - -/* - * Kick all pending requests 5 times a second. - * - * Must run under the aarp_lock. - */ -static void __aarp_kick(struct aarp_entry **n) -{ - struct aarp_entry *t; - - while (*n) - /* Expired: if this will be the 11th tx, we delete instead. */ - if ((*n)->xmit_count >= sysctl_aarp_retransmit_limit) { - t = *n; - *n = (*n)->next; - __aarp_expire(t); - } else { - __aarp_send_query(*n); - n = &((*n)->next); - } -} - -/* - * A device has gone down. Take all entries referring to the device - * and remove them. - * - * Must run under the aarp_lock. - */ -static void __aarp_expire_device(struct aarp_entry **n, struct net_device *dev) -{ - struct aarp_entry *t; - - while (*n) - if ((*n)->dev == dev) { - t = *n; - *n = (*n)->next; - __aarp_expire(t); - } else - n = &((*n)->next); -} - -/* Handle the timer event */ -static void aarp_expire_timeout(struct timer_list *unused) -{ - int ct; - - write_lock_bh(&aarp_lock); - - for (ct = 0; ct < AARP_HASH_SIZE; ct++) { - __aarp_expire_timer(&resolved[ct]); - __aarp_kick(&unresolved[ct]); - __aarp_expire_timer(&unresolved[ct]); - __aarp_expire_timer(&proxies[ct]); - } - - write_unlock_bh(&aarp_lock); - mod_timer(&aarp_timer, jiffies + - (unresolved_count ? sysctl_aarp_tick_time : - sysctl_aarp_expiry_time)); -} - -/* Network device notifier chain handler. */ -static int aarp_device_event(struct notifier_block *this, unsigned long event, - void *ptr) -{ - struct net_device *dev = netdev_notifier_info_to_dev(ptr); - int ct; - - if (!net_eq(dev_net(dev), &init_net)) - return NOTIFY_DONE; - - if (event == NETDEV_DOWN) { - write_lock_bh(&aarp_lock); - - for (ct = 0; ct < AARP_HASH_SIZE; ct++) { - __aarp_expire_device(&resolved[ct], dev); - __aarp_expire_device(&unresolved[ct], dev); - __aarp_expire_device(&proxies[ct], dev); - } - - write_unlock_bh(&aarp_lock); - } - return NOTIFY_DONE; -} - -/* Expire all entries in a hash chain */ -static void __aarp_expire_all(struct aarp_entry **n) -{ - struct aarp_entry *t; - - while (*n) { - t = *n; - *n = (*n)->next; - __aarp_expire(t); - } -} - -/* Cleanup all hash chains -- module unloading */ -static void aarp_purge(void) -{ - int ct; - - write_lock_bh(&aarp_lock); - for (ct = 0; ct < AARP_HASH_SIZE; ct++) { - __aarp_expire_all(&resolved[ct]); - __aarp_expire_all(&unresolved[ct]); - __aarp_expire_all(&proxies[ct]); - } - write_unlock_bh(&aarp_lock); -} - -/* - * Create a new aarp entry. This must use GFP_ATOMIC because it - * runs while holding spinlocks. - */ -static struct aarp_entry *aarp_alloc(void) -{ - struct aarp_entry *a = kzalloc_obj(*a, GFP_ATOMIC); - if (!a) - return NULL; - - refcount_set(&a->refcnt, 1); - skb_queue_head_init(&a->packet_queue); - return a; -} - -/* - * Find an entry. We might return an expired but not yet purged entry. We - * don't care as it will do no harm. - * - * This must run under the aarp_lock. - */ -static struct aarp_entry *__aarp_find_entry(struct aarp_entry *list, - struct net_device *dev, - struct atalk_addr *sat) -{ - while (list) { - if (list->target_addr.s_net == sat->s_net && - list->target_addr.s_node == sat->s_node && - list->dev == dev) - break; - list = list->next; - } - - return list; -} - -/* Called from the DDP code, and thus must be exported. */ -void aarp_proxy_remove(struct net_device *dev, struct atalk_addr *sa) -{ - int hash = sa->s_node % (AARP_HASH_SIZE - 1); - struct aarp_entry *a; - - write_lock_bh(&aarp_lock); - - a = __aarp_find_entry(proxies[hash], dev, sa); - if (a) - a->expires_at = jiffies - 1; - - write_unlock_bh(&aarp_lock); -} - -/* This must run under aarp_lock. */ -static struct atalk_addr *__aarp_proxy_find(struct net_device *dev, - struct atalk_addr *sa) -{ - int hash = sa->s_node % (AARP_HASH_SIZE - 1); - struct aarp_entry *a = __aarp_find_entry(proxies[hash], dev, sa); - - return a ? sa : NULL; -} - -void aarp_probe_network(struct atalk_iface *atif) -{ - unsigned int count; - - for (count = 0; count < AARP_RETRANSMIT_LIMIT; count++) { - aarp_send_probe(atif->dev, &atif->address); - - /* Defer 1/10th */ - msleep(100); - - if (atif->status & ATIF_PROBE_FAIL) - break; - } -} - -int aarp_proxy_probe_network(struct atalk_iface *atif, struct atalk_addr *sa) -{ - int hash, retval = -EPROTONOSUPPORT; - struct aarp_entry *entry; - unsigned int count; - - /* - * we don't currently support LocalTalk or PPP for proxy AARP; - * if someone wants to try and add it, have fun - */ - if (atif->dev->type == ARPHRD_LOCALTLK || - atif->dev->type == ARPHRD_PPP) - goto out; - - /* - * create a new AARP entry with the flags set to be published -- - * we need this one to hang around even if it's in use - */ - entry = aarp_alloc(); - retval = -ENOMEM; - if (!entry) - goto out; - - entry->expires_at = -1; - entry->status = ATIF_PROBE; - entry->target_addr.s_node = sa->s_node; - entry->target_addr.s_net = sa->s_net; - entry->dev = atif->dev; - - write_lock_bh(&aarp_lock); - aarp_entry_get(entry); - - hash = sa->s_node % (AARP_HASH_SIZE - 1); - entry->next = proxies[hash]; - proxies[hash] = entry; - - for (count = 0; count < AARP_RETRANSMIT_LIMIT; count++) { - aarp_send_probe(atif->dev, sa); - - /* Defer 1/10th */ - write_unlock_bh(&aarp_lock); - msleep(100); - write_lock_bh(&aarp_lock); - - if (entry->status & ATIF_PROBE_FAIL) - break; - } - - if (entry->status & ATIF_PROBE_FAIL) { - entry->expires_at = jiffies - 1; /* free the entry */ - retval = -EADDRINUSE; /* return network full */ - } else { /* clear the probing flag */ - entry->status &= ~ATIF_PROBE; - retval = 1; - } - - aarp_entry_put(entry); - write_unlock_bh(&aarp_lock); -out: - return retval; -} - -/* Send a DDP frame */ -int aarp_send_ddp(struct net_device *dev, struct sk_buff *skb, - struct atalk_addr *sa, void *hwaddr) -{ - static char ddp_eth_multicast[ETH_ALEN] = - { 0x09, 0x00, 0x07, 0xFF, 0xFF, 0xFF }; - int hash; - struct aarp_entry *a; - - skb_reset_network_header(skb); - - /* Check for LocalTalk first */ - if (dev->type == ARPHRD_LOCALTLK) { - struct atalk_addr *at = atalk_find_dev_addr(dev); - struct ddpehdr *ddp = (struct ddpehdr *)skb->data; - int ft = 2; - - if (!at) { - kfree_skb(skb); - return NET_XMIT_DROP; - } - - /* - * Compressible ? - * - * IFF: src_net == dest_net == device_net - * (zero matches anything) - */ - - if ((!ddp->deh_snet || at->s_net == ddp->deh_snet) && - (!ddp->deh_dnet || at->s_net == ddp->deh_dnet)) { - skb_pull(skb, sizeof(*ddp) - 4); - - /* - * The upper two remaining bytes are the port - * numbers we just happen to need. Now put the - * length in the lower two. - */ - *((__be16 *)skb->data) = htons(skb->len); - ft = 1; - } - /* - * Nice and easy. No AARP type protocols occur here so we can - * just shovel it out with a 3 byte LLAP header - */ - - skb_push(skb, 3); - skb->data[0] = sa->s_node; - skb->data[1] = at->s_node; - skb->data[2] = ft; - skb->dev = dev; - goto sendit; - } - - /* On a PPP link we neither compress nor aarp. */ - if (dev->type == ARPHRD_PPP) { - skb->protocol = htons(ETH_P_PPPTALK); - skb->dev = dev; - goto sendit; - } - - /* Non ELAP we cannot do. */ - if (dev->type != ARPHRD_ETHER) - goto free_it; - - skb->dev = dev; - skb->protocol = htons(ETH_P_ATALK); - hash = sa->s_node % (AARP_HASH_SIZE - 1); - - /* Do we have a resolved entry? */ - if (sa->s_node == ATADDR_BCAST) { - /* Send it */ - ddp_dl->request(ddp_dl, skb, ddp_eth_multicast); - goto sent; - } - - write_lock_bh(&aarp_lock); - a = __aarp_find_entry(resolved[hash], dev, sa); - - if (a) { /* Return 1 and fill in the address */ - a->expires_at = jiffies + (sysctl_aarp_expiry_time * 10); - ddp_dl->request(ddp_dl, skb, a->hwaddr); - write_unlock_bh(&aarp_lock); - goto sent; - } - - /* Do we have an unresolved entry: This is the less common path */ - a = __aarp_find_entry(unresolved[hash], dev, sa); - if (a) { /* Queue onto the unresolved queue */ - skb_queue_tail(&a->packet_queue, skb); - goto out_unlock; - } - - /* Allocate a new entry */ - a = aarp_alloc(); - if (!a) { - /* Whoops slipped... good job it's an unreliable protocol 8) */ - write_unlock_bh(&aarp_lock); - goto free_it; - } - - /* Set up the queue */ - skb_queue_tail(&a->packet_queue, skb); - a->expires_at = jiffies + sysctl_aarp_resolve_time; - a->dev = dev; - a->next = unresolved[hash]; - a->target_addr = *sa; - a->xmit_count = 0; - unresolved[hash] = a; - unresolved_count++; - - /* Send an initial request for the address */ - __aarp_send_query(a); - - /* - * Switch to fast timer if needed (That is if this is the first - * unresolved entry to get added) - */ - - if (unresolved_count == 1) - mod_timer(&aarp_timer, jiffies + sysctl_aarp_tick_time); - - /* Now finally, it is safe to drop the lock. */ -out_unlock: - write_unlock_bh(&aarp_lock); - - /* Tell the ddp layer we have taken over for this frame. */ - goto sent; - -sendit: - if (skb->sk) - skb->priority = READ_ONCE(skb->sk->sk_priority); - if (dev_queue_xmit(skb)) - goto drop; -sent: - return NET_XMIT_SUCCESS; -free_it: - kfree_skb(skb); -drop: - return NET_XMIT_DROP; -} -EXPORT_SYMBOL(aarp_send_ddp); - -/* - * An entry in the aarp unresolved queue has become resolved. Send - * all the frames queued under it. - * - * Must run under aarp_lock. - */ -static void __aarp_resolved(struct aarp_entry **list, struct aarp_entry *a, - int hash) -{ - struct sk_buff *skb; - - while (*list) - if (*list == a) { - unresolved_count--; - *list = a->next; - - /* Move into the resolved list */ - a->next = resolved[hash]; - resolved[hash] = a; - - /* Kick frames off */ - while ((skb = skb_dequeue(&a->packet_queue)) != NULL) { - a->expires_at = jiffies + - sysctl_aarp_expiry_time * 10; - ddp_dl->request(ddp_dl, skb, a->hwaddr); - } - } else - list = &((*list)->next); -} - -/* - * This is called by the SNAP driver whenever we see an AARP SNAP - * frame. We currently only support Ethernet. - */ -static int aarp_rcv(struct sk_buff *skb, struct net_device *dev, - struct packet_type *pt, struct net_device *orig_dev) -{ - struct elapaarp *ea = aarp_hdr(skb); - int hash, ret = 0; - __u16 function; - struct aarp_entry *a; - struct atalk_addr sa, *ma, da; - struct atalk_iface *ifa; - - if (!net_eq(dev_net(dev), &init_net)) - goto out0; - - /* We only do Ethernet SNAP AARP. */ - if (dev->type != ARPHRD_ETHER) - goto out0; - - /* Frame size ok? */ - if (!skb_pull(skb, sizeof(*ea))) - goto out0; - - function = ntohs(ea->function); - - /* Sanity check fields. */ - if (function < AARP_REQUEST || function > AARP_PROBE || - ea->hw_len != ETH_ALEN || ea->pa_len != AARP_PA_ALEN || - ea->pa_src_zero || ea->pa_dst_zero) - goto out0; - - /* Looks good. */ - hash = ea->pa_src_node % (AARP_HASH_SIZE - 1); - - /* Build an address. */ - sa.s_node = ea->pa_src_node; - sa.s_net = ea->pa_src_net; - - /* Process the packet. Check for replies of me. */ - ifa = atalk_find_dev(dev); - if (!ifa) - goto out1; - - if (ifa->status & ATIF_PROBE && - ifa->address.s_node == ea->pa_dst_node && - ifa->address.s_net == ea->pa_dst_net) { - ifa->status |= ATIF_PROBE_FAIL; /* Fail the probe (in use) */ - goto out1; - } - - /* Check for replies of proxy AARP entries */ - da.s_node = ea->pa_dst_node; - da.s_net = ea->pa_dst_net; - - write_lock_bh(&aarp_lock); - a = __aarp_find_entry(proxies[hash], dev, &da); - - if (a && a->status & ATIF_PROBE) { - a->status |= ATIF_PROBE_FAIL; - /* - * we do not respond to probe or request packets of - * this address while we are probing this address - */ - goto unlock; - } - - switch (function) { - case AARP_REPLY: - if (!unresolved_count) /* Speed up */ - break; - - /* Find the entry. */ - a = __aarp_find_entry(unresolved[hash], dev, &sa); - if (!a || dev != a->dev) - break; - - /* We can fill one in - this is good. */ - ether_addr_copy(a->hwaddr, ea->hw_src); - __aarp_resolved(&unresolved[hash], a, hash); - if (!unresolved_count) - mod_timer(&aarp_timer, - jiffies + sysctl_aarp_expiry_time); - break; - - case AARP_REQUEST: - case AARP_PROBE: - - /* - * If it is my address set ma to my address and reply. - * We can treat probe and request the same. Probe - * simply means we shouldn't cache the querying host, - * as in a probe they are proposing an address not - * using one. - * - * Support for proxy-AARP added. We check if the - * address is one of our proxies before we toss the - * packet out. - */ - - sa.s_node = ea->pa_dst_node; - sa.s_net = ea->pa_dst_net; - - /* See if we have a matching proxy. */ - ma = __aarp_proxy_find(dev, &sa); - if (!ma) - ma = &ifa->address; - else { /* We need to make a copy of the entry. */ - da.s_node = sa.s_node; - da.s_net = sa.s_net; - ma = &da; - } - - if (function == AARP_PROBE) { - /* - * A probe implies someone trying to get an - * address. So as a precaution flush any - * entries we have for this address. - */ - a = __aarp_find_entry(resolved[sa.s_node % - (AARP_HASH_SIZE - 1)], - skb->dev, &sa); - - /* - * Make it expire next tick - that avoids us - * getting into a probe/flush/learn/probe/ - * flush/learn cycle during probing of a slow - * to respond host addr. - */ - if (a) { - a->expires_at = jiffies - 1; - mod_timer(&aarp_timer, jiffies + - sysctl_aarp_tick_time); - } - } - - if (sa.s_node != ma->s_node) - break; - - if (sa.s_net && ma->s_net && sa.s_net != ma->s_net) - break; - - sa.s_node = ea->pa_src_node; - sa.s_net = ea->pa_src_net; - - /* aarp_my_address has found the address to use for us. - */ - aarp_send_reply(dev, ma, &sa, ea->hw_src); - break; - } - -unlock: - write_unlock_bh(&aarp_lock); -out1: - ret = 1; -out0: - kfree_skb(skb); - return ret; -} - -static struct notifier_block aarp_notifier = { - .notifier_call = aarp_device_event, -}; - -static unsigned char aarp_snap_id[] = { 0x00, 0x00, 0x00, 0x80, 0xF3 }; - -int __init aarp_proto_init(void) -{ - int rc; - - aarp_dl = register_snap_client(aarp_snap_id, aarp_rcv); - if (!aarp_dl) { - printk(KERN_CRIT "Unable to register AARP with SNAP.\n"); - return -ENOMEM; - } - timer_setup(&aarp_timer, aarp_expire_timeout, 0); - aarp_timer.expires = jiffies + sysctl_aarp_expiry_time; - add_timer(&aarp_timer); - rc = register_netdevice_notifier(&aarp_notifier); - if (rc) { - timer_delete_sync(&aarp_timer); - unregister_snap_client(aarp_dl); - } - return rc; -} - -/* Remove the AARP entries associated with a device. */ -void aarp_device_down(struct net_device *dev) -{ - int ct; - - write_lock_bh(&aarp_lock); - - for (ct = 0; ct < AARP_HASH_SIZE; ct++) { - __aarp_expire_device(&resolved[ct], dev); - __aarp_expire_device(&unresolved[ct], dev); - __aarp_expire_device(&proxies[ct], dev); - } - - write_unlock_bh(&aarp_lock); -} - -#ifdef CONFIG_PROC_FS -/* - * Get the aarp entry that is in the chain described - * by the iterator. - * If pos is set then skip till that index. - * pos = 1 is the first entry - */ -static struct aarp_entry *iter_next(struct aarp_iter_state *iter, loff_t *pos) -{ - int ct = iter->bucket; - struct aarp_entry **table = iter->table; - loff_t off = 0; - struct aarp_entry *entry; - - rescan: - while (ct < AARP_HASH_SIZE) { - for (entry = table[ct]; entry; entry = entry->next) { - if (!pos || ++off == *pos) { - iter->table = table; - iter->bucket = ct; - return entry; - } - } - ++ct; - } - - if (table == resolved) { - ct = 0; - table = unresolved; - goto rescan; - } - if (table == unresolved) { - ct = 0; - table = proxies; - goto rescan; - } - return NULL; -} - -static void *aarp_seq_start(struct seq_file *seq, loff_t *pos) - __acquires(aarp_lock) -{ - struct aarp_iter_state *iter = seq->private; - - read_lock_bh(&aarp_lock); - iter->table = resolved; - iter->bucket = 0; - - return *pos ? iter_next(iter, pos) : SEQ_START_TOKEN; -} - -static void *aarp_seq_next(struct seq_file *seq, void *v, loff_t *pos) -{ - struct aarp_entry *entry = v; - struct aarp_iter_state *iter = seq->private; - - ++*pos; - - /* first line after header */ - if (v == SEQ_START_TOKEN) - entry = iter_next(iter, NULL); - - /* next entry in current bucket */ - else if (entry->next) - entry = entry->next; - - /* next bucket or table */ - else { - ++iter->bucket; - entry = iter_next(iter, NULL); - } - return entry; -} - -static void aarp_seq_stop(struct seq_file *seq, void *v) - __releases(aarp_lock) -{ - read_unlock_bh(&aarp_lock); -} - -static const char *dt2str(unsigned long ticks) -{ - static char buf[32]; - - sprintf(buf, "%ld.%02ld", ticks / HZ, ((ticks % HZ) * 100) / HZ); - - return buf; -} - -static int aarp_seq_show(struct seq_file *seq, void *v) -{ - struct aarp_iter_state *iter = seq->private; - struct aarp_entry *entry = v; - unsigned long now = jiffies; - - if (v == SEQ_START_TOKEN) - seq_puts(seq, - "Address Interface Hardware Address" - " Expires LastSend Retry Status\n"); - else { - seq_printf(seq, "%04X:%02X %-12s", - ntohs(entry->target_addr.s_net), - (unsigned int) entry->target_addr.s_node, - entry->dev ? entry->dev->name : "????"); - seq_printf(seq, "%pM", entry->hwaddr); - seq_printf(seq, " %8s", - dt2str((long)entry->expires_at - (long)now)); - if (iter->table == unresolved) - seq_printf(seq, " %8s %6hu", - dt2str(now - entry->last_sent), - entry->xmit_count); - else - seq_puts(seq, " "); - seq_printf(seq, " %s\n", - (iter->table == resolved) ? "resolved" - : (iter->table == unresolved) ? "unresolved" - : (iter->table == proxies) ? "proxies" - : "unknown"); - } - return 0; -} - -const struct seq_operations aarp_seq_ops = { - .start = aarp_seq_start, - .next = aarp_seq_next, - .stop = aarp_seq_stop, - .show = aarp_seq_show, -}; -#endif - -/* General module cleanup. Called from cleanup_module() in ddp.c. */ -void aarp_cleanup_module(void) -{ - timer_delete_sync(&aarp_timer); - unregister_netdevice_notifier(&aarp_notifier); - unregister_snap_client(aarp_dl); - aarp_purge(); -} diff --git a/net/appletalk/atalk_proc.c b/net/appletalk/atalk_proc.c deleted file mode 100644 index 01787fb6a7bc..000000000000 --- a/net/appletalk/atalk_proc.c +++ /dev/null @@ -1,242 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * atalk_proc.c - proc support for Appletalk - * - * Copyright(c) Arnaldo Carvalho de Melo - */ - -#include -#include -#include -#include -#include -#include -#include - - -static __inline__ struct atalk_iface *atalk_get_interface_idx(loff_t pos) -{ - struct atalk_iface *i; - - for (i = atalk_interfaces; pos && i; i = i->next) - --pos; - - return i; -} - -static void *atalk_seq_interface_start(struct seq_file *seq, loff_t *pos) - __acquires(atalk_interfaces_lock) -{ - loff_t l = *pos; - - read_lock_bh(&atalk_interfaces_lock); - return l ? atalk_get_interface_idx(--l) : SEQ_START_TOKEN; -} - -static void *atalk_seq_interface_next(struct seq_file *seq, void *v, loff_t *pos) -{ - struct atalk_iface *i; - - ++*pos; - if (v == SEQ_START_TOKEN) { - i = NULL; - if (atalk_interfaces) - i = atalk_interfaces; - goto out; - } - i = v; - i = i->next; -out: - return i; -} - -static void atalk_seq_interface_stop(struct seq_file *seq, void *v) - __releases(atalk_interfaces_lock) -{ - read_unlock_bh(&atalk_interfaces_lock); -} - -static int atalk_seq_interface_show(struct seq_file *seq, void *v) -{ - struct atalk_iface *iface; - - if (v == SEQ_START_TOKEN) { - seq_puts(seq, "Interface Address Networks " - "Status\n"); - goto out; - } - - iface = v; - seq_printf(seq, "%-16s %04X:%02X %04X-%04X %d\n", - iface->dev->name, ntohs(iface->address.s_net), - iface->address.s_node, ntohs(iface->nets.nr_firstnet), - ntohs(iface->nets.nr_lastnet), iface->status); -out: - return 0; -} - -static __inline__ struct atalk_route *atalk_get_route_idx(loff_t pos) -{ - struct atalk_route *r; - - for (r = atalk_routes; pos && r; r = r->next) - --pos; - - return r; -} - -static void *atalk_seq_route_start(struct seq_file *seq, loff_t *pos) - __acquires(atalk_routes_lock) -{ - loff_t l = *pos; - - read_lock_bh(&atalk_routes_lock); - return l ? atalk_get_route_idx(--l) : SEQ_START_TOKEN; -} - -static void *atalk_seq_route_next(struct seq_file *seq, void *v, loff_t *pos) -{ - struct atalk_route *r; - - ++*pos; - if (v == SEQ_START_TOKEN) { - r = NULL; - if (atalk_routes) - r = atalk_routes; - goto out; - } - r = v; - r = r->next; -out: - return r; -} - -static void atalk_seq_route_stop(struct seq_file *seq, void *v) - __releases(atalk_routes_lock) -{ - read_unlock_bh(&atalk_routes_lock); -} - -static int atalk_seq_route_show(struct seq_file *seq, void *v) -{ - struct atalk_route *rt; - - if (v == SEQ_START_TOKEN) { - seq_puts(seq, "Target Router Flags Dev\n"); - goto out; - } - - if (atrtr_default.dev) { - rt = &atrtr_default; - seq_printf(seq, "Default %04X:%02X %-4d %s\n", - ntohs(rt->gateway.s_net), rt->gateway.s_node, - rt->flags, rt->dev->name); - } - - rt = v; - seq_printf(seq, "%04X:%02X %04X:%02X %-4d %s\n", - ntohs(rt->target.s_net), rt->target.s_node, - ntohs(rt->gateway.s_net), rt->gateway.s_node, - rt->flags, rt->dev->name); -out: - return 0; -} - -static void *atalk_seq_socket_start(struct seq_file *seq, loff_t *pos) - __acquires(atalk_sockets_lock) -{ - read_lock_bh(&atalk_sockets_lock); - return seq_hlist_start_head(&atalk_sockets, *pos); -} - -static void *atalk_seq_socket_next(struct seq_file *seq, void *v, loff_t *pos) -{ - return seq_hlist_next(v, &atalk_sockets, pos); -} - -static void atalk_seq_socket_stop(struct seq_file *seq, void *v) - __releases(atalk_sockets_lock) -{ - read_unlock_bh(&atalk_sockets_lock); -} - -static int atalk_seq_socket_show(struct seq_file *seq, void *v) -{ - struct sock *s; - struct atalk_sock *at; - - if (v == SEQ_START_TOKEN) { - seq_printf(seq, "Type Local_addr Remote_addr Tx_queue " - "Rx_queue St UID\n"); - goto out; - } - - s = sk_entry(v); - at = at_sk(s); - - seq_printf(seq, "%02X %04X:%02X:%02X %04X:%02X:%02X %08X:%08X " - "%02X %u\n", - s->sk_type, ntohs(at->src_net), at->src_node, at->src_port, - ntohs(at->dest_net), at->dest_node, at->dest_port, - sk_wmem_alloc_get(s), - sk_rmem_alloc_get(s), - s->sk_state, - from_kuid_munged(seq_user_ns(seq), sk_uid(s))); -out: - return 0; -} - -static const struct seq_operations atalk_seq_interface_ops = { - .start = atalk_seq_interface_start, - .next = atalk_seq_interface_next, - .stop = atalk_seq_interface_stop, - .show = atalk_seq_interface_show, -}; - -static const struct seq_operations atalk_seq_route_ops = { - .start = atalk_seq_route_start, - .next = atalk_seq_route_next, - .stop = atalk_seq_route_stop, - .show = atalk_seq_route_show, -}; - -static const struct seq_operations atalk_seq_socket_ops = { - .start = atalk_seq_socket_start, - .next = atalk_seq_socket_next, - .stop = atalk_seq_socket_stop, - .show = atalk_seq_socket_show, -}; - -int __init atalk_proc_init(void) -{ - if (!proc_mkdir("atalk", init_net.proc_net)) - return -ENOMEM; - - if (!proc_create_seq("atalk/interface", 0444, init_net.proc_net, - &atalk_seq_interface_ops)) - goto out; - - if (!proc_create_seq("atalk/route", 0444, init_net.proc_net, - &atalk_seq_route_ops)) - goto out; - - if (!proc_create_seq("atalk/socket", 0444, init_net.proc_net, - &atalk_seq_socket_ops)) - goto out; - - if (!proc_create_seq_private("atalk/arp", 0444, init_net.proc_net, - &aarp_seq_ops, - sizeof(struct aarp_iter_state), NULL)) - goto out; - - return 0; - -out: - remove_proc_subtree("atalk", init_net.proc_net); - return -ENOMEM; -} - -void atalk_proc_exit(void) -{ - remove_proc_subtree("atalk", init_net.proc_net); -} diff --git a/net/appletalk/ddp.c b/net/appletalk/ddp.c deleted file mode 100644 index afb86ce6e644..000000000000 --- a/net/appletalk/ddp.c +++ /dev/null @@ -1,2055 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * DDP: An implementation of the AppleTalk DDP protocol for - * Ethernet 'ELAP'. - * - * Alan Cox - * - * With more than a little assistance from - * - * Wesley Craig - * - * Fixes: - * Neil Horman : Added missing device ioctls - * Michael Callahan : Made routing work - * Wesley Craig : Fix probing to listen to a - * passed node id. - * Alan Cox : Added send/recvmsg support - * Alan Cox : Moved at. to protinfo in - * socket. - * Alan Cox : Added firewall hooks. - * Alan Cox : Supports new ARPHRD_LOOPBACK - * Christer Weinigel : Routing and /proc fixes. - * Bradford Johnson : LocalTalk. - * Tom Dyas : Module support. - * Alan Cox : Hooks for PPP (based on the - * LocalTalk hook). - * Alan Cox : Posix bits - * Alan Cox/Mike Freeman : Possible fix to NBP problems - * Bradford Johnson : IP-over-DDP (experimental) - * Jay Schulist : Moved IP-over-DDP to its own - * driver file. (ipddp.c & ipddp.h) - * Jay Schulist : Made work as module with - * AppleTalk drivers, cleaned it. - * Rob Newberry : Added proxy AARP and AARP - * procfs, moved probing to AARP - * module. - * Adrian Sun/ - * Michael Zuelsdorff : fix for net.0 packets. don't - * allow illegal ether/tokentalk - * port assignment. we lose a - * valid localtalk port as a - * result. - * Arnaldo C. de Melo : Cleanup, in preparation for - * shared skb support 8) - * Arnaldo C. de Melo : Move proc stuff to atalk_proc.c, - * use seq_file - */ - -#include -#include -#include -#include /* For TIOCOUTQ/INQ */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct datalink_proto *ddp_dl, *aarp_dl; -static const struct proto_ops atalk_dgram_ops; - -/**************************************************************************\ -* * -* Handlers for the socket list. * -* * -\**************************************************************************/ - -HLIST_HEAD(atalk_sockets); -DEFINE_RWLOCK(atalk_sockets_lock); - -static inline void __atalk_insert_socket(struct sock *sk) -{ - sk_add_node(sk, &atalk_sockets); -} - -static inline void atalk_remove_socket(struct sock *sk) -{ - write_lock_bh(&atalk_sockets_lock); - sk_del_node_init(sk); - write_unlock_bh(&atalk_sockets_lock); -} - -static struct sock *atalk_search_socket(struct sockaddr_at *to, - struct atalk_iface *atif) -{ - struct sock *def_socket = NULL; - struct sock *s; - - read_lock_bh(&atalk_sockets_lock); - sk_for_each(s, &atalk_sockets) { - struct atalk_sock *at = at_sk(s); - - if (to->sat_port != at->src_port) - continue; - - if (to->sat_addr.s_net == ATADDR_ANYNET && - to->sat_addr.s_node == ATADDR_BCAST) { - if (atif->address.s_node == at->src_node && - atif->address.s_net == at->src_net) { - /* This socket's address matches the address of the interface - * that received the packet -- use it - */ - goto found; - } - - /* Continue searching for a socket matching the interface address, - * but use this socket by default if no other one is found - */ - def_socket = s; - } - - if (to->sat_addr.s_net == at->src_net && - (to->sat_addr.s_node == at->src_node || - to->sat_addr.s_node == ATADDR_BCAST || - to->sat_addr.s_node == ATADDR_ANYNODE)) - goto found; - - /* XXXX.0 -- we got a request for this router. make sure - * that the node is appropriately set. */ - if (to->sat_addr.s_node == ATADDR_ANYNODE && - to->sat_addr.s_net != ATADDR_ANYNET && - atif->address.s_node == at->src_node) { - to->sat_addr.s_node = atif->address.s_node; - goto found; - } - } - s = def_socket; -found: - read_unlock_bh(&atalk_sockets_lock); - return s; -} - -/** - * atalk_find_or_insert_socket - Try to find a socket matching ADDR - * @sk: socket to insert in the list if it is not there already - * @sat: address to search for - * - * Try to find a socket matching ADDR in the socket list, if found then return - * it. If not, insert SK into the socket list. - * - * This entire operation must execute atomically. - */ -static struct sock *atalk_find_or_insert_socket(struct sock *sk, - struct sockaddr_at *sat) -{ - struct sock *s; - struct atalk_sock *at; - - write_lock_bh(&atalk_sockets_lock); - sk_for_each(s, &atalk_sockets) { - at = at_sk(s); - - if (at->src_net == sat->sat_addr.s_net && - at->src_node == sat->sat_addr.s_node && - at->src_port == sat->sat_port) - goto found; - } - s = NULL; - __atalk_insert_socket(sk); /* Wheee, it's free, assign and insert. */ -found: - write_unlock_bh(&atalk_sockets_lock); - return s; -} - -static void atalk_destroy_timer(struct timer_list *t) -{ - struct sock *sk = timer_container_of(sk, t, sk_timer); - - if (sk_has_allocations(sk)) { - sk->sk_timer.expires = jiffies + SOCK_DESTROY_TIME; - add_timer(&sk->sk_timer); - } else - sock_put(sk); -} - -static inline void atalk_destroy_socket(struct sock *sk) -{ - atalk_remove_socket(sk); - skb_queue_purge(&sk->sk_receive_queue); - - if (sk_has_allocations(sk)) { - timer_setup(&sk->sk_timer, atalk_destroy_timer, 0); - sk->sk_timer.expires = jiffies + SOCK_DESTROY_TIME; - add_timer(&sk->sk_timer); - } else - sock_put(sk); -} - -/**************************************************************************\ -* * -* Routing tables for the AppleTalk socket layer. * -* * -\**************************************************************************/ - -/* Anti-deadlock ordering is atalk_routes_lock --> iface_lock -DaveM */ -struct atalk_route *atalk_routes; -DEFINE_RWLOCK(atalk_routes_lock); - -struct atalk_iface *atalk_interfaces; -DEFINE_RWLOCK(atalk_interfaces_lock); - -/* Fast dev->iface lookup, keyed on ifindex. Shares atalk_interfaces_lock with - * the atalk_interfaces list, which remains the owner of the iface objects. - */ -#define ATALK_IFACE_HASH_BITS 8 -static DEFINE_HASHTABLE(atalk_iface_hash, ATALK_IFACE_HASH_BITS); - -/* Find the iface for @dev. Caller must hold atalk_interfaces_lock. */ -static struct atalk_iface *__atalk_find_dev(struct net_device *dev) -{ - struct atalk_iface *iface; - - hash_for_each_possible(atalk_iface_hash, iface, hash_node, dev->ifindex) - if (iface->dev == dev) - return iface; - return NULL; -} - -struct atalk_iface *atalk_find_dev(struct net_device *dev) -{ - struct atalk_iface *iface; - - read_lock_bh(&atalk_interfaces_lock); - iface = __atalk_find_dev(dev); - read_unlock_bh(&atalk_interfaces_lock); - return iface; -} - -/* For probing devices or in a routerless network */ -struct atalk_route atrtr_default; - -/* AppleTalk interface control */ -/* - * Drop a device. Doesn't drop any of its routes - that is the caller's - * problem. Called when we down the interface or delete the address. - */ -static void atif_drop_device(struct net_device *dev) -{ - struct atalk_iface **iface = &atalk_interfaces; - struct atalk_iface *tmp; - - write_lock_bh(&atalk_interfaces_lock); - while ((tmp = *iface) != NULL) { - if (tmp->dev == dev) { - *iface = tmp->next; - hash_del(&tmp->hash_node); - dev_put(dev); - kfree(tmp); - } else - iface = &tmp->next; - } - write_unlock_bh(&atalk_interfaces_lock); -} - -static struct atalk_iface *atif_add_device(struct net_device *dev, - struct atalk_addr *sa) -{ - struct atalk_iface *iface = kzalloc_obj(*iface); - - if (!iface) - goto out; - - dev_hold(dev); - iface->dev = dev; - iface->address = *sa; - iface->status = 0; - - write_lock_bh(&atalk_interfaces_lock); - iface->next = atalk_interfaces; - atalk_interfaces = iface; - hash_add(atalk_iface_hash, &iface->hash_node, dev->ifindex); - write_unlock_bh(&atalk_interfaces_lock); -out: - return iface; -} - -/* Perform phase 2 AARP probing on our tentative address */ -static int atif_probe_device(struct atalk_iface *atif) -{ - int netrange = ntohs(atif->nets.nr_lastnet) - - ntohs(atif->nets.nr_firstnet) + 1; - int probe_net = ntohs(atif->address.s_net); - int probe_node = atif->address.s_node; - int netct, nodect; - - /* Offset the network we start probing with */ - if (probe_net == ATADDR_ANYNET) { - probe_net = ntohs(atif->nets.nr_firstnet); - if (netrange) - probe_net += jiffies % netrange; - } - if (probe_node == ATADDR_ANYNODE) - probe_node = jiffies & 0xFF; - - /* Scan the networks */ - atif->status |= ATIF_PROBE; - for (netct = 0; netct <= netrange; netct++) { - /* Sweep the available nodes from a given start */ - atif->address.s_net = htons(probe_net); - for (nodect = 0; nodect < 256; nodect++) { - atif->address.s_node = (nodect + probe_node) & 0xFF; - if (atif->address.s_node > 0 && - atif->address.s_node < 254) { - /* Probe a proposed address */ - aarp_probe_network(atif); - - if (!(atif->status & ATIF_PROBE_FAIL)) { - atif->status &= ~ATIF_PROBE; - return 0; - } - } - atif->status &= ~ATIF_PROBE_FAIL; - } - probe_net++; - if (probe_net > ntohs(atif->nets.nr_lastnet)) - probe_net = ntohs(atif->nets.nr_firstnet); - } - atif->status &= ~ATIF_PROBE; - - return -EADDRINUSE; /* Network is full... */ -} - - -/* Perform AARP probing for a proxy address */ -static int atif_proxy_probe_device(struct atalk_iface *atif, - struct atalk_addr *proxy_addr) -{ - int netrange = ntohs(atif->nets.nr_lastnet) - - ntohs(atif->nets.nr_firstnet) + 1; - /* we probe the interface's network */ - int probe_net = ntohs(atif->address.s_net); - int probe_node = ATADDR_ANYNODE; /* we'll take anything */ - int netct, nodect; - - /* Offset the network we start probing with */ - if (probe_net == ATADDR_ANYNET) { - probe_net = ntohs(atif->nets.nr_firstnet); - if (netrange) - probe_net += jiffies % netrange; - } - - if (probe_node == ATADDR_ANYNODE) - probe_node = jiffies & 0xFF; - - /* Scan the networks */ - for (netct = 0; netct <= netrange; netct++) { - /* Sweep the available nodes from a given start */ - proxy_addr->s_net = htons(probe_net); - for (nodect = 0; nodect < 256; nodect++) { - proxy_addr->s_node = (nodect + probe_node) & 0xFF; - if (proxy_addr->s_node > 0 && - proxy_addr->s_node < 254) { - /* Tell AARP to probe a proposed address */ - int ret = aarp_proxy_probe_network(atif, - proxy_addr); - - if (ret != -EADDRINUSE) - return ret; - } - } - probe_net++; - if (probe_net > ntohs(atif->nets.nr_lastnet)) - probe_net = ntohs(atif->nets.nr_firstnet); - } - - return -EADDRINUSE; /* Network is full... */ -} - - -struct atalk_addr *atalk_find_dev_addr(struct net_device *dev) -{ - struct atalk_addr *addr = NULL; - struct atalk_iface *iface; - - read_lock_bh(&atalk_interfaces_lock); - iface = __atalk_find_dev(dev); - if (iface) - addr = &iface->address; - read_unlock_bh(&atalk_interfaces_lock); - return addr; -} - -static struct atalk_addr *atalk_find_primary(void) -{ - struct atalk_iface *fiface = NULL; - struct atalk_addr *retval; - struct atalk_iface *iface; - - /* - * Return a point-to-point interface only if - * there is no non-ptp interface available. - */ - read_lock_bh(&atalk_interfaces_lock); - for (iface = atalk_interfaces; iface; iface = iface->next) { - if (!fiface && !(iface->dev->flags & IFF_LOOPBACK)) - fiface = iface; - if (!(iface->dev->flags & (IFF_LOOPBACK | IFF_POINTOPOINT))) { - retval = &iface->address; - goto out; - } - } - - if (fiface) - retval = &fiface->address; - else if (atalk_interfaces) - retval = &atalk_interfaces->address; - else - retval = NULL; -out: - read_unlock_bh(&atalk_interfaces_lock); - return retval; -} - -/* - * Find a match for 'any network' - ie any of our interfaces with that - * node number will do just nicely. - */ -static struct atalk_iface *atalk_find_anynet(int node, struct net_device *dev) -{ - struct atalk_iface *iface; - - read_lock_bh(&atalk_interfaces_lock); - iface = __atalk_find_dev(dev); - if (!iface || iface->status & ATIF_PROBE) - goto out_err; - - if (node != ATADDR_BCAST && - iface->address.s_node != node && - node != ATADDR_ANYNODE) - goto out_err; -out: - read_unlock_bh(&atalk_interfaces_lock); - return iface; -out_err: - iface = NULL; - goto out; -} - -/* Find a match for a specific network:node pair */ -static struct atalk_iface *atalk_find_interface(__be16 net, int node) -{ - struct atalk_iface *iface; - - read_lock_bh(&atalk_interfaces_lock); - for (iface = atalk_interfaces; iface; iface = iface->next) { - if ((node == ATADDR_BCAST || - node == ATADDR_ANYNODE || - iface->address.s_node == node) && - iface->address.s_net == net && - !(iface->status & ATIF_PROBE)) - break; - - /* XXXX.0 -- net.0 returns the iface associated with net */ - if (node == ATADDR_ANYNODE && net != ATADDR_ANYNET && - ntohs(iface->nets.nr_firstnet) <= ntohs(net) && - ntohs(net) <= ntohs(iface->nets.nr_lastnet)) - break; - } - read_unlock_bh(&atalk_interfaces_lock); - return iface; -} - - -/* - * Find a route for an AppleTalk packet. This ought to get cached in - * the socket (later on...). We know about host routes and the fact - * that a route must be direct to broadcast. - */ -static struct atalk_route *atrtr_find(struct atalk_addr *target) -{ - /* - * we must search through all routes unless we find a - * host route, because some host routes might overlap - * network routes - */ - struct atalk_route *net_route = NULL; - struct atalk_route *r; - - read_lock_bh(&atalk_routes_lock); - for (r = atalk_routes; r; r = r->next) { - if (!(r->flags & RTF_UP)) - continue; - - if (r->target.s_net == target->s_net) { - if (r->flags & RTF_HOST) { - /* - * if this host route is for the target, - * the we're done - */ - if (r->target.s_node == target->s_node) - goto out; - } else - /* - * this route will work if there isn't a - * direct host route, so cache it - */ - net_route = r; - } - } - - /* - * if we found a network route but not a direct host - * route, then return it - */ - if (net_route) - r = net_route; - else if (atrtr_default.dev) - r = &atrtr_default; - else /* No route can be found */ - r = NULL; -out: - read_unlock_bh(&atalk_routes_lock); - return r; -} - - -/* - * Given an AppleTalk network, find the device to use. This can be - * a simple lookup. - */ -struct net_device *atrtr_get_dev(struct atalk_addr *sa) -{ - struct atalk_route *atr = atrtr_find(sa); - return atr ? atr->dev : NULL; -} - -/* Set up a default router */ -static void atrtr_set_default(struct net_device *dev) -{ - atrtr_default.dev = dev; - atrtr_default.flags = RTF_UP; - atrtr_default.gateway.s_net = htons(0); - atrtr_default.gateway.s_node = 0; -} - -/* - * Add a router. Basically make sure it looks valid and stuff the - * entry in the list. While it uses netranges we always set them to one - * entry to work like netatalk. - */ -static int atrtr_create(struct rtentry *r, struct net_device *devhint) -{ - struct sockaddr_at *ta = (struct sockaddr_at *)&r->rt_dst; - struct sockaddr_at *ga = (struct sockaddr_at *)&r->rt_gateway; - struct atalk_route *rt; - struct atalk_iface *iface, *riface; - int retval = -EINVAL; - - /* - * Fixme: Raise/Lower a routing change semaphore for these - * operations. - */ - - /* Validate the request */ - if (ta->sat_family != AF_APPLETALK || - (!devhint && ga->sat_family != AF_APPLETALK)) - goto out; - - /* Now walk the routing table and make our decisions */ - write_lock_bh(&atalk_routes_lock); - for (rt = atalk_routes; rt; rt = rt->next) { - if (r->rt_flags != rt->flags) - continue; - - if (ta->sat_addr.s_net == rt->target.s_net) { - if (!(rt->flags & RTF_HOST)) - break; - if (ta->sat_addr.s_node == rt->target.s_node) - break; - } - } - - if (!devhint) { - riface = NULL; - - read_lock_bh(&atalk_interfaces_lock); - for (iface = atalk_interfaces; iface; iface = iface->next) { - if (!riface && - ntohs(ga->sat_addr.s_net) >= - ntohs(iface->nets.nr_firstnet) && - ntohs(ga->sat_addr.s_net) <= - ntohs(iface->nets.nr_lastnet)) - riface = iface; - - if (ga->sat_addr.s_net == iface->address.s_net && - ga->sat_addr.s_node == iface->address.s_node) - riface = iface; - } - read_unlock_bh(&atalk_interfaces_lock); - - retval = -ENETUNREACH; - if (!riface) - goto out_unlock; - - devhint = riface->dev; - } - - if (!rt) { - rt = kzalloc_obj(*rt, GFP_ATOMIC); - - retval = -ENOBUFS; - if (!rt) - goto out_unlock; - - rt->next = atalk_routes; - atalk_routes = rt; - } - - /* Fill in the routing entry */ - rt->target = ta->sat_addr; - dev_put(rt->dev); /* Release old device */ - dev_hold(devhint); - rt->dev = devhint; - rt->flags = r->rt_flags; - rt->gateway = ga->sat_addr; - - retval = 0; -out_unlock: - write_unlock_bh(&atalk_routes_lock); -out: - return retval; -} - -/* Delete a route. Find it and discard it */ -static int atrtr_delete(struct atalk_addr *addr) -{ - struct atalk_route **r = &atalk_routes; - int retval = 0; - struct atalk_route *tmp; - - write_lock_bh(&atalk_routes_lock); - while ((tmp = *r) != NULL) { - if (tmp->target.s_net == addr->s_net && - (!(tmp->flags&RTF_GATEWAY) || - tmp->target.s_node == addr->s_node)) { - *r = tmp->next; - dev_put(tmp->dev); - kfree(tmp); - goto out; - } - r = &tmp->next; - } - retval = -ENOENT; -out: - write_unlock_bh(&atalk_routes_lock); - return retval; -} - -/* - * Called when a device is downed. Just throw away any routes - * via it. - */ -static void atrtr_device_down(struct net_device *dev) -{ - struct atalk_route **r = &atalk_routes; - struct atalk_route *tmp; - - write_lock_bh(&atalk_routes_lock); - while ((tmp = *r) != NULL) { - if (tmp->dev == dev) { - *r = tmp->next; - dev_put(dev); - kfree(tmp); - } else - r = &tmp->next; - } - write_unlock_bh(&atalk_routes_lock); - - if (atrtr_default.dev == dev) - atrtr_set_default(NULL); -} - -/* Actually down the interface */ -static inline void atalk_dev_down(struct net_device *dev) -{ - atrtr_device_down(dev); /* Remove all routes for the device */ - aarp_device_down(dev); /* Remove AARP entries for the device */ - atif_drop_device(dev); /* Remove the device */ -} - -/* - * A device event has occurred. Watch for devices going down and - * delete our use of them (iface and route). - */ -static int ddp_device_event(struct notifier_block *this, unsigned long event, - void *ptr) -{ - struct net_device *dev = netdev_notifier_info_to_dev(ptr); - - if (!net_eq(dev_net(dev), &init_net)) - return NOTIFY_DONE; - - if (event == NETDEV_DOWN) - /* Discard any use of this */ - atalk_dev_down(dev); - - return NOTIFY_DONE; -} - -/* ioctl calls. Shouldn't even need touching */ -/* Device configuration ioctl calls */ -static int atif_ioctl(int cmd, void __user *arg) -{ - static char aarp_mcast[6] = { 0x09, 0x00, 0x00, 0xFF, 0xFF, 0xFF }; - struct ifreq atreq; - struct atalk_netrange *nr; - struct sockaddr_at *sa; - struct net_device *dev; - struct atalk_iface *atif; - int ct; - int limit; - struct rtentry rtdef; - int add_route; - - if (get_user_ifreq(&atreq, NULL, arg)) - return -EFAULT; - - dev = __dev_get_by_name(&init_net, atreq.ifr_name); - if (!dev) - return -ENODEV; - - sa = (struct sockaddr_at *)&atreq.ifr_addr; - atif = atalk_find_dev(dev); - - switch (cmd) { - case SIOCSIFADDR: - if (!capable(CAP_NET_ADMIN)) - return -EPERM; - if (sa->sat_family != AF_APPLETALK) - return -EINVAL; - if (dev->type != ARPHRD_ETHER && - dev->type != ARPHRD_LOOPBACK && - dev->type != ARPHRD_LOCALTLK && - dev->type != ARPHRD_PPP) - return -EPROTONOSUPPORT; - - nr = (struct atalk_netrange *)&sa->sat_zero[0]; - add_route = 1; - - /* - * if this is a point-to-point iface, and we already - * have an iface for this AppleTalk address, then we - * should not add a route - */ - if ((dev->flags & IFF_POINTOPOINT) && - atalk_find_interface(sa->sat_addr.s_net, - sa->sat_addr.s_node)) { - printk(KERN_DEBUG "AppleTalk: point-to-point " - "interface added with " - "existing address\n"); - add_route = 0; - } - - /* - * Phase 1 is fine on LocalTalk but we don't do - * EtherTalk phase 1. Anyone wanting to add it, go ahead. - */ - if (dev->type == ARPHRD_ETHER && nr->nr_phase != 2) - return -EPROTONOSUPPORT; - if (sa->sat_addr.s_node == ATADDR_BCAST || - sa->sat_addr.s_node == 254) - return -EINVAL; - if (atif) { - /* Already setting address */ - if (atif->status & ATIF_PROBE) - return -EBUSY; - - atif->address.s_net = sa->sat_addr.s_net; - atif->address.s_node = sa->sat_addr.s_node; - atrtr_device_down(dev); /* Flush old routes */ - } else { - atif = atif_add_device(dev, &sa->sat_addr); - if (!atif) - return -ENOMEM; - } - atif->nets = *nr; - - /* - * Check if the chosen address is used. If so we - * error and atalkd will try another. - */ - - if (!(dev->flags & IFF_LOOPBACK) && - !(dev->flags & IFF_POINTOPOINT) && - atif_probe_device(atif) < 0) { - atif_drop_device(dev); - return -EADDRINUSE; - } - - /* Hey it worked - add the direct routes */ - sa = (struct sockaddr_at *)&rtdef.rt_gateway; - sa->sat_family = AF_APPLETALK; - sa->sat_addr.s_net = atif->address.s_net; - sa->sat_addr.s_node = atif->address.s_node; - sa = (struct sockaddr_at *)&rtdef.rt_dst; - rtdef.rt_flags = RTF_UP; - sa->sat_family = AF_APPLETALK; - sa->sat_addr.s_node = ATADDR_ANYNODE; - if (dev->flags & IFF_LOOPBACK || - dev->flags & IFF_POINTOPOINT) - rtdef.rt_flags |= RTF_HOST; - - /* Routerless initial state */ - if (nr->nr_firstnet == htons(0) && - nr->nr_lastnet == htons(0xFFFE)) { - sa->sat_addr.s_net = atif->address.s_net; - atrtr_create(&rtdef, dev); - atrtr_set_default(dev); - } else { - limit = ntohs(nr->nr_lastnet); - if (limit - ntohs(nr->nr_firstnet) > 4096) { - printk(KERN_WARNING "Too many routes/" - "iface.\n"); - return -EINVAL; - } - if (add_route) - for (ct = ntohs(nr->nr_firstnet); - ct <= limit; ct++) { - sa->sat_addr.s_net = htons(ct); - atrtr_create(&rtdef, dev); - } - } - dev_mc_add_global(dev, aarp_mcast); - return 0; - - case SIOCGIFADDR: - if (!atif) - return -EADDRNOTAVAIL; - - sa->sat_family = AF_APPLETALK; - sa->sat_addr = atif->address; - break; - - case SIOCGIFBRDADDR: - if (!atif) - return -EADDRNOTAVAIL; - - sa->sat_family = AF_APPLETALK; - sa->sat_addr.s_net = atif->address.s_net; - sa->sat_addr.s_node = ATADDR_BCAST; - break; - - case SIOCATALKDIFADDR: - case SIOCDIFADDR: - if (!capable(CAP_NET_ADMIN)) - return -EPERM; - if (sa->sat_family != AF_APPLETALK) - return -EINVAL; - atalk_dev_down(dev); - break; - - case SIOCSARP: - if (!capable(CAP_NET_ADMIN)) - return -EPERM; - if (sa->sat_family != AF_APPLETALK) - return -EINVAL; - /* - * for now, we only support proxy AARP on ELAP; - * we should be able to do it for LocalTalk, too. - */ - if (dev->type != ARPHRD_ETHER) - return -EPROTONOSUPPORT; - - /* - * atif points to the current interface on this network; - * we aren't concerned about its current status (at - * least for now), but it has all the settings about - * the network we're going to probe. Consequently, it - * must exist. - */ - if (!atif) - return -EADDRNOTAVAIL; - - nr = (struct atalk_netrange *)&(atif->nets); - /* - * Phase 1 is fine on Localtalk but we don't do - * Ethertalk phase 1. Anyone wanting to add it, go ahead. - */ - if (dev->type == ARPHRD_ETHER && nr->nr_phase != 2) - return -EPROTONOSUPPORT; - - if (sa->sat_addr.s_node == ATADDR_BCAST || - sa->sat_addr.s_node == 254) - return -EINVAL; - - /* - * Check if the chosen address is used. If so we - * error and ATCP will try another. - */ - if (atif_proxy_probe_device(atif, &(sa->sat_addr)) < 0) - return -EADDRINUSE; - - /* - * We now have an address on the local network, and - * the AARP code will defend it for us until we take it - * down. We don't set up any routes right now, because - * ATCP will install them manually via SIOCADDRT. - */ - break; - - case SIOCDARP: - if (!capable(CAP_NET_ADMIN)) - return -EPERM; - if (sa->sat_family != AF_APPLETALK) - return -EINVAL; - if (!atif) - return -EADDRNOTAVAIL; - - /* give to aarp module to remove proxy entry */ - aarp_proxy_remove(atif->dev, &(sa->sat_addr)); - return 0; - } - - return put_user_ifreq(&atreq, arg); -} - -static int atrtr_ioctl_addrt(struct rtentry *rt) -{ - struct net_device *dev = NULL; - - if (rt->rt_dev) { - char name[IFNAMSIZ]; - - if (copy_from_user(name, rt->rt_dev, IFNAMSIZ-1)) - return -EFAULT; - name[IFNAMSIZ-1] = '\0'; - - dev = __dev_get_by_name(&init_net, name); - if (!dev) - return -ENODEV; - } - return atrtr_create(rt, dev); -} - -/* Routing ioctl() calls */ -static int atrtr_ioctl(unsigned int cmd, void __user *arg) -{ - struct rtentry rt; - - if (copy_from_user(&rt, arg, sizeof(rt))) - return -EFAULT; - - switch (cmd) { - case SIOCDELRT: - if (rt.rt_dst.sa_family != AF_APPLETALK) - return -EINVAL; - return atrtr_delete(&((struct sockaddr_at *) - &rt.rt_dst)->sat_addr); - - case SIOCADDRT: - return atrtr_ioctl_addrt(&rt); - } - return -EINVAL; -} - -/**************************************************************************\ -* * -* Handling for system calls applied via the various interfaces to an * -* AppleTalk socket object. * -* * -\**************************************************************************/ - -/* - * Checksum: This is 'optional'. It's quite likely also a good - * candidate for assembler hackery 8) - */ -static unsigned long atalk_sum_partial(const unsigned char *data, - int len, unsigned long sum) -{ - /* This ought to be unwrapped neatly. I'll trust gcc for now */ - while (len--) { - sum += *data++; - sum = rol16(sum, 1); - } - return sum; -} - -/* Checksum skb data -- similar to skb_checksum */ -static unsigned long atalk_sum_skb(const struct sk_buff *skb, int offset, - int len, unsigned long sum) -{ - int start = skb_headlen(skb); - struct sk_buff *frag_iter; - int i, copy; - - /* checksum stuff in header space */ - if ((copy = start - offset) > 0) { - if (copy > len) - copy = len; - sum = atalk_sum_partial(skb->data + offset, copy, sum); - if ((len -= copy) == 0) - return sum; - - offset += copy; - } - - /* checksum stuff in frags */ - for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { - int end; - const skb_frag_t *frag = &skb_shinfo(skb)->frags[i]; - WARN_ON(start > offset + len); - - end = start + skb_frag_size(frag); - if ((copy = end - offset) > 0) { - u8 *vaddr; - - if (copy > len) - copy = len; - vaddr = kmap_atomic(skb_frag_page(frag)); - sum = atalk_sum_partial(vaddr + skb_frag_off(frag) + - offset - start, copy, sum); - kunmap_atomic(vaddr); - - if (!(len -= copy)) - return sum; - offset += copy; - } - start = end; - } - - skb_walk_frags(skb, frag_iter) { - int end; - - WARN_ON(start > offset + len); - - end = start + frag_iter->len; - if ((copy = end - offset) > 0) { - if (copy > len) - copy = len; - sum = atalk_sum_skb(frag_iter, offset - start, - copy, sum); - if ((len -= copy) == 0) - return sum; - offset += copy; - } - start = end; - } - - BUG_ON(len > 0); - - return sum; -} - -static __be16 atalk_checksum(const struct sk_buff *skb, int len) -{ - unsigned long sum; - - /* skip header 4 bytes */ - sum = atalk_sum_skb(skb, 4, len-4, 0); - - /* Use 0xFFFF for 0. 0 itself means none */ - return sum ? htons((unsigned short)sum) : htons(0xFFFF); -} - -static struct proto ddp_proto = { - .name = "DDP", - .owner = THIS_MODULE, - .obj_size = sizeof(struct atalk_sock), -}; - -/* - * Create a socket. Initialise the socket, blank the addresses - * set the state. - */ -static int atalk_create(struct net *net, struct socket *sock, int protocol, - int kern) -{ - struct sock *sk; - int rc = -ESOCKTNOSUPPORT; - - if (!net_eq(net, &init_net)) - return -EAFNOSUPPORT; - - /* - * We permit SOCK_DGRAM and RAW is an extension. It is trivial to do - * and gives you the full ELAP frame. Should be handy for CAP 8) - */ - if (sock->type != SOCK_RAW && sock->type != SOCK_DGRAM) - goto out; - - rc = -EPERM; - if (sock->type == SOCK_RAW && !kern && !capable(CAP_NET_RAW)) - goto out; - - rc = -ENOMEM; - sk = sk_alloc(net, PF_APPLETALK, GFP_KERNEL, &ddp_proto, kern); - if (!sk) - goto out; - rc = 0; - sock->ops = &atalk_dgram_ops; - sock_init_data(sock, sk); - - /* Checksums on by default */ - sock_set_flag(sk, SOCK_ZAPPED); -out: - return rc; -} - -/* Free a socket. No work needed */ -static int atalk_release(struct socket *sock) -{ - struct sock *sk = sock->sk; - - if (sk) { - sock_hold(sk); - lock_sock(sk); - - sock_orphan(sk); - sock->sk = NULL; - atalk_destroy_socket(sk); - - release_sock(sk); - sock_put(sk); - } - return 0; -} - -/** - * atalk_pick_and_bind_port - Pick a source port when one is not given - * @sk: socket to insert into the tables - * @sat: address to search for - * - * Pick a source port when one is not given. If we can find a suitable free - * one, we insert the socket into the tables using it. - * - * This whole operation must be atomic. - */ -static int atalk_pick_and_bind_port(struct sock *sk, struct sockaddr_at *sat) -{ - int retval; - - write_lock_bh(&atalk_sockets_lock); - - for (sat->sat_port = ATPORT_RESERVED; - sat->sat_port < ATPORT_LAST; - sat->sat_port++) { - struct sock *s; - - sk_for_each(s, &atalk_sockets) { - struct atalk_sock *at = at_sk(s); - - if (at->src_net == sat->sat_addr.s_net && - at->src_node == sat->sat_addr.s_node && - at->src_port == sat->sat_port) - goto try_next_port; - } - - /* Wheee, it's free, assign and insert. */ - __atalk_insert_socket(sk); - at_sk(sk)->src_port = sat->sat_port; - retval = 0; - goto out; - -try_next_port:; - } - - retval = -EBUSY; -out: - write_unlock_bh(&atalk_sockets_lock); - return retval; -} - -static int atalk_autobind(struct sock *sk) -{ - struct atalk_sock *at = at_sk(sk); - struct sockaddr_at sat; - struct atalk_addr *ap = atalk_find_primary(); - int n = -EADDRNOTAVAIL; - - if (!ap || ap->s_net == htons(ATADDR_ANYNET)) - goto out; - - at->src_net = sat.sat_addr.s_net = ap->s_net; - at->src_node = sat.sat_addr.s_node = ap->s_node; - - n = atalk_pick_and_bind_port(sk, &sat); - if (!n) - sock_reset_flag(sk, SOCK_ZAPPED); -out: - return n; -} - -/* Set the address 'our end' of the connection */ -static int atalk_bind(struct socket *sock, struct sockaddr_unsized *uaddr, int addr_len) -{ - struct sockaddr_at *addr = (struct sockaddr_at *)uaddr; - struct sock *sk = sock->sk; - struct atalk_sock *at = at_sk(sk); - int err; - - if (!sock_flag(sk, SOCK_ZAPPED) || - addr_len != sizeof(struct sockaddr_at)) - return -EINVAL; - - if (addr->sat_family != AF_APPLETALK) - return -EAFNOSUPPORT; - - lock_sock(sk); - if (addr->sat_addr.s_net == htons(ATADDR_ANYNET)) { - struct atalk_addr *ap = atalk_find_primary(); - - err = -EADDRNOTAVAIL; - if (!ap) - goto out; - - at->src_net = addr->sat_addr.s_net = ap->s_net; - at->src_node = addr->sat_addr.s_node = ap->s_node; - } else { - err = -EADDRNOTAVAIL; - if (!atalk_find_interface(addr->sat_addr.s_net, - addr->sat_addr.s_node)) - goto out; - - at->src_net = addr->sat_addr.s_net; - at->src_node = addr->sat_addr.s_node; - } - - if (addr->sat_port == ATADDR_ANYPORT) { - err = atalk_pick_and_bind_port(sk, addr); - - if (err < 0) - goto out; - } else { - at->src_port = addr->sat_port; - - err = -EADDRINUSE; - if (atalk_find_or_insert_socket(sk, addr)) - goto out; - } - - sock_reset_flag(sk, SOCK_ZAPPED); - err = 0; -out: - release_sock(sk); - return err; -} - -/* Set the address we talk to */ -static int atalk_connect(struct socket *sock, struct sockaddr_unsized *uaddr, - int addr_len, int flags) -{ - struct sock *sk = sock->sk; - struct atalk_sock *at = at_sk(sk); - struct sockaddr_at *addr; - int err; - - sk->sk_state = TCP_CLOSE; - sock->state = SS_UNCONNECTED; - - if (addr_len != sizeof(*addr)) - return -EINVAL; - - addr = (struct sockaddr_at *)uaddr; - - if (addr->sat_family != AF_APPLETALK) - return -EAFNOSUPPORT; - - if (addr->sat_addr.s_node == ATADDR_BCAST && - !sock_flag(sk, SOCK_BROADCAST)) { -#if 1 - pr_warn("atalk_connect: %s is broken and did not set SO_BROADCAST.\n", - current->comm); -#else - return -EACCES; -#endif - } - - lock_sock(sk); - err = -EBUSY; - if (sock_flag(sk, SOCK_ZAPPED)) - if (atalk_autobind(sk) < 0) - goto out; - - err = -ENETUNREACH; - if (!atrtr_get_dev(&addr->sat_addr)) - goto out; - - at->dest_port = addr->sat_port; - at->dest_net = addr->sat_addr.s_net; - at->dest_node = addr->sat_addr.s_node; - - sock->state = SS_CONNECTED; - sk->sk_state = TCP_ESTABLISHED; - err = 0; -out: - release_sock(sk); - return err; -} - -/* - * Find the name of an AppleTalk socket. Just copy the right - * fields into the sockaddr. - */ -static int atalk_getname(struct socket *sock, struct sockaddr *uaddr, - int peer) -{ - struct sockaddr_at sat; - struct sock *sk = sock->sk; - struct atalk_sock *at = at_sk(sk); - int err; - - lock_sock(sk); - err = -ENOBUFS; - if (sock_flag(sk, SOCK_ZAPPED)) - if (atalk_autobind(sk) < 0) - goto out; - - memset(&sat, 0, sizeof(sat)); - - if (peer) { - err = -ENOTCONN; - if (sk->sk_state != TCP_ESTABLISHED) - goto out; - - sat.sat_addr.s_net = at->dest_net; - sat.sat_addr.s_node = at->dest_node; - sat.sat_port = at->dest_port; - } else { - sat.sat_addr.s_net = at->src_net; - sat.sat_addr.s_node = at->src_node; - sat.sat_port = at->src_port; - } - - sat.sat_family = AF_APPLETALK; - memcpy(uaddr, &sat, sizeof(sat)); - err = sizeof(struct sockaddr_at); - -out: - release_sock(sk); - return err; -} - -static int atalk_route_packet(struct sk_buff *skb, struct net_device *dev, - struct ddpehdr *ddp, __u16 len_hops, int origlen) -{ - struct atalk_route *rt; - struct atalk_addr ta; - - /* - * Don't route multicast, etc., packets, or packets sent to "this - * network" - */ - if (skb->pkt_type != PACKET_HOST || !ddp->deh_dnet) { - /* - * FIXME: - * - * Can it ever happen that a packet is from a PPP iface and - * needs to be broadcast onto the default network? - */ - if (dev->type == ARPHRD_PPP) - printk(KERN_DEBUG "AppleTalk: didn't forward broadcast " - "packet received from PPP iface\n"); - goto free_it; - } - - ta.s_net = ddp->deh_dnet; - ta.s_node = ddp->deh_dnode; - - /* Route the packet */ - rt = atrtr_find(&ta); - /* increment hops count */ - len_hops += 1 << 10; - if (!rt || !(len_hops & (15 << 10))) - goto free_it; - - /* FIXME: use skb->cb to be able to use shared skbs */ - - /* - * Route goes through another gateway, so set the target to the - * gateway instead. - */ - - if (rt->flags & RTF_GATEWAY) { - ta.s_net = rt->gateway.s_net; - ta.s_node = rt->gateway.s_node; - } - - /* Fix up skb->len field */ - skb_trim(skb, min_t(unsigned int, origlen, - (rt->dev->hard_header_len + - ddp_dl->header_length + (len_hops & 1023)))); - - /* FIXME: use skb->cb to be able to use shared skbs */ - ddp->deh_len_hops = htons(len_hops); - - /* - * Send the buffer onwards - * - * Now we must always be careful. If it's come from LocalTalk to - * EtherTalk it might not fit - * - * Order matters here: If a packet has to be copied to make a new - * headroom (rare hopefully) then it won't need unsharing. - * - * Note. ddp-> becomes invalid at the realloc. - */ - if (skb_headroom(skb) < 22) { - /* 22 bytes - 12 ether, 2 len, 3 802.2 5 snap */ - struct sk_buff *nskb = skb_realloc_headroom(skb, 32); - kfree_skb(skb); - skb = nskb; - } else - skb = skb_unshare(skb, GFP_ATOMIC); - - /* - * If the buffer didn't vanish into the lack of space bitbucket we can - * send it. - */ - if (skb == NULL) - goto drop; - - if (aarp_send_ddp(rt->dev, skb, &ta, NULL) == NET_XMIT_DROP) - return NET_RX_DROP; - return NET_RX_SUCCESS; -free_it: - kfree_skb(skb); -drop: - return NET_RX_DROP; -} - -/** - * atalk_rcv - Receive a packet (in skb) from device dev - * @skb: packet received - * @dev: network device where the packet comes from - * @pt: packet type - * @orig_dev: the original receive net device - * - * Receive a packet (in skb) from device dev. This has come from the SNAP - * decoder, and on entry skb->transport_header is the DDP header, skb->len - * is the DDP header, skb->len is the DDP length. The physical headers - * have been extracted. PPP should probably pass frames marked as for this - * layer. [ie ARPHRD_ETHERTALK] - */ -static int atalk_rcv(struct sk_buff *skb, struct net_device *dev, - struct packet_type *pt, struct net_device *orig_dev) -{ - struct ddpehdr *ddp; - struct sock *sock; - struct atalk_iface *atif; - struct sockaddr_at tosat; - int origlen; - __u16 len_hops; - - if (!net_eq(dev_net(dev), &init_net)) - goto drop; - - /* Don't mangle buffer if shared */ - if (!(skb = skb_share_check(skb, GFP_ATOMIC))) - goto out; - - /* Size check and make sure header is contiguous */ - if (!pskb_may_pull(skb, sizeof(*ddp))) - goto drop; - - ddp = ddp_hdr(skb); - - len_hops = ntohs(ddp->deh_len_hops); - - /* Trim buffer in case of stray trailing data */ - origlen = skb->len; - skb_trim(skb, min_t(unsigned int, skb->len, len_hops & 1023)); - - /* - * Size check to see if ddp->deh_len was crap - * (Otherwise we'll detonate most spectacularly - * in the middle of atalk_checksum() or recvmsg()). - */ - if (skb->len < sizeof(*ddp) || skb->len < (len_hops & 1023)) { - pr_debug("AppleTalk: dropping corrupted frame (deh_len=%u, " - "skb->len=%u)\n", len_hops & 1023, skb->len); - goto drop; - } - - /* - * Any checksums. Note we don't do htons() on this == is assumed to be - * valid for net byte orders all over the networking code... - */ - if (ddp->deh_sum && - atalk_checksum(skb, len_hops & 1023) != ddp->deh_sum) - /* Not a valid AppleTalk frame - dustbin time */ - goto drop; - - /* Check the packet is aimed at us */ - if (!ddp->deh_dnet) /* Net 0 is 'this network' */ - atif = atalk_find_anynet(ddp->deh_dnode, dev); - else - atif = atalk_find_interface(ddp->deh_dnet, ddp->deh_dnode); - - if (!atif) { - /* Not ours, so we route the packet via the correct - * AppleTalk iface - */ - return atalk_route_packet(skb, dev, ddp, len_hops, origlen); - } - - /* - * Which socket - atalk_search_socket() looks for a *full match* - * of the tuple. - */ - tosat.sat_addr.s_net = ddp->deh_dnet; - tosat.sat_addr.s_node = ddp->deh_dnode; - tosat.sat_port = ddp->deh_dport; - - sock = atalk_search_socket(&tosat, atif); - if (!sock) /* But not one of our sockets */ - goto drop; - - /* Queue packet (standard) */ - if (sock_queue_rcv_skb(sock, skb) < 0) - goto drop; - - return NET_RX_SUCCESS; - -drop: - kfree_skb(skb); -out: - return NET_RX_DROP; - -} - -/* - * Receive a LocalTalk frame. We make some demands on the caller here. - * Caller must provide enough headroom on the packet to pull the short - * header and append a long one. - */ -static int ltalk_rcv(struct sk_buff *skb, struct net_device *dev, - struct packet_type *pt, struct net_device *orig_dev) -{ - if (!net_eq(dev_net(dev), &init_net)) - goto freeit; - - /* Expand any short form frames */ - if (skb_mac_header(skb)[2] == 1) { - struct ddpehdr *ddp; - /* Find our address */ - struct atalk_addr *ap = atalk_find_dev_addr(dev); - - if (!ap || skb->len < sizeof(__be16) || skb->len > 1023) - goto freeit; - - /* Don't mangle buffer if shared */ - if (!(skb = skb_share_check(skb, GFP_ATOMIC))) - return 0; - - /* - * The push leaves us with a ddephdr not an shdr, and - * handily the port bytes in the right place preset. - */ - ddp = skb_push(skb, sizeof(*ddp) - 4); - - /* Now fill in the long header */ - - /* - * These two first. The mac overlays the new source/dest - * network information so we MUST copy these before - * we write the network numbers ! - */ - - ddp->deh_dnode = skb_mac_header(skb)[0]; /* From physical header */ - ddp->deh_snode = skb_mac_header(skb)[1]; /* From physical header */ - - ddp->deh_dnet = ap->s_net; /* Network number */ - ddp->deh_snet = ap->s_net; - ddp->deh_sum = 0; /* No checksum */ - /* - * Not sure about this bit... - */ - /* Non routable, so force a drop if we slip up later */ - ddp->deh_len_hops = htons(skb->len + (DDP_MAXHOPS << 10)); - } - skb_reset_transport_header(skb); - - return atalk_rcv(skb, dev, pt, orig_dev); -freeit: - kfree_skb(skb); - return 0; -} - -static int atalk_sendmsg(struct socket *sock, struct msghdr *msg, size_t len) -{ - struct sock *sk = sock->sk; - struct atalk_sock *at = at_sk(sk); - DECLARE_SOCKADDR(struct sockaddr_at *, usat, msg->msg_name); - int flags = msg->msg_flags; - int loopback = 0; - struct sockaddr_at local_satalk, gsat; - struct sk_buff *skb; - struct net_device *dev; - struct ddpehdr *ddp; - int size, hard_header_len; - struct atalk_route *rt, *rt_lo = NULL; - int err; - - if (flags & ~(MSG_DONTWAIT|MSG_CMSG_COMPAT)) - return -EINVAL; - - if (len > DDP_MAXSZ) - return -EMSGSIZE; - - lock_sock(sk); - if (usat) { - err = -EBUSY; - if (sock_flag(sk, SOCK_ZAPPED)) - if (atalk_autobind(sk) < 0) - goto out; - - err = -EINVAL; - if (msg->msg_namelen < sizeof(*usat) || - usat->sat_family != AF_APPLETALK) - goto out; - - err = -EPERM; - /* netatalk didn't implement this check */ - if (usat->sat_addr.s_node == ATADDR_BCAST && - !sock_flag(sk, SOCK_BROADCAST)) { - goto out; - } - } else { - err = -ENOTCONN; - if (sk->sk_state != TCP_ESTABLISHED) - goto out; - usat = &local_satalk; - usat->sat_family = AF_APPLETALK; - usat->sat_port = at->dest_port; - usat->sat_addr.s_node = at->dest_node; - usat->sat_addr.s_net = at->dest_net; - } - - /* Build a packet */ - net_dbg_ratelimited("SK %p: Got address.\n", sk); - - /* For headers */ - size = sizeof(struct ddpehdr) + len + ddp_dl->header_length; - - if (usat->sat_addr.s_net || usat->sat_addr.s_node == ATADDR_ANYNODE) { - rt = atrtr_find(&usat->sat_addr); - } else { - struct atalk_addr at_hint; - - at_hint.s_node = 0; - at_hint.s_net = at->src_net; - - rt = atrtr_find(&at_hint); - } - err = -ENETUNREACH; - if (!rt) - goto out; - - dev = rt->dev; - - net_dbg_ratelimited("SK %p: Size needed %d, device %s\n", - sk, size, dev->name); - - hard_header_len = dev->hard_header_len; - /* Leave room for loopback hardware header if necessary */ - if (usat->sat_addr.s_node == ATADDR_BCAST && - (dev->flags & IFF_LOOPBACK || !(rt->flags & RTF_GATEWAY))) { - struct atalk_addr at_lo; - - at_lo.s_node = 0; - at_lo.s_net = 0; - - rt_lo = atrtr_find(&at_lo); - - if (rt_lo && rt_lo->dev->hard_header_len > hard_header_len) - hard_header_len = rt_lo->dev->hard_header_len; - } - - size += hard_header_len; - release_sock(sk); - skb = sock_alloc_send_skb(sk, size, (flags & MSG_DONTWAIT), &err); - lock_sock(sk); - if (!skb) - goto out; - - skb_reserve(skb, ddp_dl->header_length); - skb_reserve(skb, hard_header_len); - skb->dev = dev; - - net_dbg_ratelimited("SK %p: Begin build.\n", sk); - - ddp = skb_put(skb, sizeof(struct ddpehdr)); - ddp->deh_len_hops = htons(len + sizeof(*ddp)); - ddp->deh_dnet = usat->sat_addr.s_net; - ddp->deh_snet = at->src_net; - ddp->deh_dnode = usat->sat_addr.s_node; - ddp->deh_snode = at->src_node; - ddp->deh_dport = usat->sat_port; - ddp->deh_sport = at->src_port; - - net_dbg_ratelimited("SK %p: Copy user data (%zd bytes).\n", sk, len); - - err = memcpy_from_msg(skb_put(skb, len), msg, len); - if (err) { - kfree_skb(skb); - err = -EFAULT; - goto out; - } - - if (sk->sk_no_check_tx) - ddp->deh_sum = 0; - else - ddp->deh_sum = atalk_checksum(skb, len + sizeof(*ddp)); - - /* - * Loopback broadcast packets to non gateway targets (ie routes - * to group we are in) - */ - if (ddp->deh_dnode == ATADDR_BCAST && - !(rt->flags & RTF_GATEWAY) && !(dev->flags & IFF_LOOPBACK)) { - struct sk_buff *skb2 = skb_copy(skb, GFP_KERNEL); - - if (skb2) { - loopback = 1; - net_dbg_ratelimited("SK %p: send out(copy).\n", sk); - /* - * If it fails it is queued/sent above in the aarp queue - */ - aarp_send_ddp(dev, skb2, &usat->sat_addr, NULL); - } - } - - if (dev->flags & IFF_LOOPBACK || loopback) { - net_dbg_ratelimited("SK %p: Loop back.\n", sk); - /* loop back */ - skb_orphan(skb); - if (ddp->deh_dnode == ATADDR_BCAST) { - if (!rt_lo) { - kfree_skb(skb); - err = -ENETUNREACH; - goto out; - } - dev = rt_lo->dev; - skb->dev = dev; - } - ddp_dl->request(ddp_dl, skb, dev->dev_addr); - } else { - net_dbg_ratelimited("SK %p: send out.\n", sk); - if (rt->flags & RTF_GATEWAY) { - gsat.sat_addr = rt->gateway; - usat = &gsat; - } - - /* - * If it fails it is queued/sent above in the aarp queue - */ - aarp_send_ddp(dev, skb, &usat->sat_addr, NULL); - } - net_dbg_ratelimited("SK %p: Done write (%zd).\n", sk, len); - -out: - release_sock(sk); - return err ? : len; -} - -static int atalk_recvmsg(struct socket *sock, struct msghdr *msg, size_t size, - int flags) -{ - struct sock *sk = sock->sk; - struct ddpehdr *ddp; - int copied = 0; - int offset = 0; - int err = 0; - struct sk_buff *skb; - - skb = skb_recv_datagram(sk, flags, &err); - lock_sock(sk); - - if (!skb) - goto out; - - /* FIXME: use skb->cb to be able to use shared skbs */ - ddp = ddp_hdr(skb); - copied = ntohs(ddp->deh_len_hops) & 1023; - - if (sk->sk_type != SOCK_RAW) { - offset = sizeof(*ddp); - copied -= offset; - } - - if (copied > size) { - copied = size; - msg->msg_flags |= MSG_TRUNC; - } - err = skb_copy_datagram_msg(skb, offset, msg, copied); - - if (!err && msg->msg_name) { - DECLARE_SOCKADDR(struct sockaddr_at *, sat, msg->msg_name); - sat->sat_family = AF_APPLETALK; - sat->sat_port = ddp->deh_sport; - sat->sat_addr.s_node = ddp->deh_snode; - sat->sat_addr.s_net = ddp->deh_snet; - msg->msg_namelen = sizeof(*sat); - } - - skb_free_datagram(sk, skb); /* Free the datagram. */ - -out: - release_sock(sk); - return err ? : copied; -} - - -/* - * AppleTalk ioctl calls. - */ -static int atalk_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) -{ - int rc = -ENOIOCTLCMD; - struct sock *sk = sock->sk; - void __user *argp = (void __user *)arg; - - switch (cmd) { - /* Protocol layer */ - case TIOCOUTQ: { - long amount = sk->sk_sndbuf - sk_wmem_alloc_get(sk); - - if (amount < 0) - amount = 0; - rc = put_user(amount, (int __user *)argp); - break; - } - case TIOCINQ: { - struct sk_buff *skb; - long amount = 0; - - spin_lock_irq(&sk->sk_receive_queue.lock); - skb = skb_peek(&sk->sk_receive_queue); - if (skb) - amount = skb->len - sizeof(struct ddpehdr); - spin_unlock_irq(&sk->sk_receive_queue.lock); - rc = put_user(amount, (int __user *)argp); - break; - } - /* Routing */ - case SIOCADDRT: - case SIOCDELRT: - rc = -EPERM; - if (capable(CAP_NET_ADMIN)) - rc = atrtr_ioctl(cmd, argp); - break; - /* Interface */ - case SIOCGIFADDR: - case SIOCSIFADDR: - case SIOCGIFBRDADDR: - case SIOCATALKDIFADDR: - case SIOCDIFADDR: - case SIOCSARP: /* proxy AARP */ - case SIOCDARP: /* proxy AARP */ - rtnl_lock(); - rc = atif_ioctl(cmd, argp); - rtnl_unlock(); - break; - } - - return rc; -} - - -#ifdef CONFIG_COMPAT -static int atalk_compat_routing_ioctl(struct sock *sk, unsigned int cmd, - struct compat_rtentry __user *ur) -{ - compat_uptr_t rtdev; - struct rtentry rt; - - if (copy_from_user(&rt.rt_dst, &ur->rt_dst, - 3 * sizeof(struct sockaddr)) || - get_user(rt.rt_flags, &ur->rt_flags) || - get_user(rt.rt_metric, &ur->rt_metric) || - get_user(rt.rt_mtu, &ur->rt_mtu) || - get_user(rt.rt_window, &ur->rt_window) || - get_user(rt.rt_irtt, &ur->rt_irtt) || - get_user(rtdev, &ur->rt_dev)) - return -EFAULT; - - switch (cmd) { - case SIOCDELRT: - if (rt.rt_dst.sa_family != AF_APPLETALK) - return -EINVAL; - return atrtr_delete(&((struct sockaddr_at *) - &rt.rt_dst)->sat_addr); - - case SIOCADDRT: - rt.rt_dev = compat_ptr(rtdev); - return atrtr_ioctl_addrt(&rt); - default: - return -EINVAL; - } -} -static int atalk_compat_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) -{ - void __user *argp = compat_ptr(arg); - struct sock *sk = sock->sk; - - switch (cmd) { - case SIOCADDRT: - case SIOCDELRT: - return atalk_compat_routing_ioctl(sk, cmd, argp); - /* - * SIOCATALKDIFADDR is a SIOCPROTOPRIVATE ioctl number, so we - * cannot handle it in common code. The data we access if ifreq - * here is compatible, so we can simply call the native - * handler. - */ - case SIOCATALKDIFADDR: - return atalk_ioctl(sock, cmd, (unsigned long)argp); - default: - return -ENOIOCTLCMD; - } -} -#endif /* CONFIG_COMPAT */ - - -static const struct net_proto_family atalk_family_ops = { - .family = PF_APPLETALK, - .create = atalk_create, - .owner = THIS_MODULE, -}; - -static const struct proto_ops atalk_dgram_ops = { - .family = PF_APPLETALK, - .owner = THIS_MODULE, - .release = atalk_release, - .bind = atalk_bind, - .connect = atalk_connect, - .socketpair = sock_no_socketpair, - .accept = sock_no_accept, - .getname = atalk_getname, - .poll = datagram_poll, - .ioctl = atalk_ioctl, - .gettstamp = sock_gettstamp, -#ifdef CONFIG_COMPAT - .compat_ioctl = atalk_compat_ioctl, -#endif - .listen = sock_no_listen, - .shutdown = sock_no_shutdown, - .sendmsg = atalk_sendmsg, - .recvmsg = atalk_recvmsg, - .mmap = sock_no_mmap, -}; - -static struct notifier_block ddp_notifier = { - .notifier_call = ddp_device_event, -}; - -static struct packet_type ltalk_packet_type __read_mostly = { - .type = cpu_to_be16(ETH_P_LOCALTALK), - .func = ltalk_rcv, -}; - -static struct packet_type ppptalk_packet_type __read_mostly = { - .type = cpu_to_be16(ETH_P_PPPTALK), - .func = atalk_rcv, -}; - -static unsigned char ddp_snap_id[] = { 0x08, 0x00, 0x07, 0x80, 0x9B }; - -/* Export symbols for use by drivers when AppleTalk is a module */ -EXPORT_SYMBOL(atrtr_get_dev); -EXPORT_SYMBOL(atalk_find_dev_addr); - -/* Called by proto.c on kernel start up */ -static int __init atalk_init(void) -{ - int rc; - - rc = proto_register(&ddp_proto, 0); - if (rc) - goto out; - - rc = sock_register(&atalk_family_ops); - if (rc) - goto out_proto; - - ddp_dl = register_snap_client(ddp_snap_id, atalk_rcv); - if (!ddp_dl) { - pr_crit("Unable to register DDP with SNAP.\n"); - rc = -ENOMEM; - goto out_sock; - } - - dev_add_pack(<alk_packet_type); - dev_add_pack(&ppptalk_packet_type); - - rc = register_netdevice_notifier(&ddp_notifier); - if (rc) - goto out_snap; - - rc = aarp_proto_init(); - if (rc) - goto out_dev; - - rc = atalk_proc_init(); - if (rc) - goto out_aarp; - - rc = atalk_register_sysctl(); - if (rc) - goto out_proc; -out: - return rc; -out_proc: - atalk_proc_exit(); -out_aarp: - aarp_cleanup_module(); -out_dev: - unregister_netdevice_notifier(&ddp_notifier); -out_snap: - dev_remove_pack(&ppptalk_packet_type); - dev_remove_pack(<alk_packet_type); - unregister_snap_client(ddp_dl); -out_sock: - sock_unregister(PF_APPLETALK); -out_proto: - proto_unregister(&ddp_proto); - goto out; -} -module_init(atalk_init); - -/* - * No explicit module reference count manipulation is needed in the - * protocol. Socket layer sets module reference count for us - * and interfaces reference counting is done - * by the network device layer. - * - * Ergo, before the AppleTalk module can be removed, all AppleTalk - * sockets should be closed from user space. - */ -static void __exit atalk_exit(void) -{ -#ifdef CONFIG_SYSCTL - atalk_unregister_sysctl(); -#endif /* CONFIG_SYSCTL */ - atalk_proc_exit(); - aarp_cleanup_module(); /* General aarp clean-up. */ - unregister_netdevice_notifier(&ddp_notifier); - dev_remove_pack(<alk_packet_type); - dev_remove_pack(&ppptalk_packet_type); - unregister_snap_client(ddp_dl); - sock_unregister(PF_APPLETALK); - proto_unregister(&ddp_proto); -} -module_exit(atalk_exit); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Alan Cox "); -MODULE_DESCRIPTION("AppleTalk 0.20\n"); -MODULE_ALIAS_NETPROTO(PF_APPLETALK); diff --git a/net/appletalk/sysctl_net_atalk.c b/net/appletalk/sysctl_net_atalk.c deleted file mode 100644 index 7aebfe903242..000000000000 --- a/net/appletalk/sysctl_net_atalk.c +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * sysctl_net_atalk.c: sysctl interface to net AppleTalk subsystem. - * - * Begun April 1, 1996, Mike Shaver. - * Added /proc/sys/net/atalk directory entry (empty =) ). [MS] - * Dynamic registration, added aarp entries. (5/30/97 Chris Horn) - */ - -#include -#include -#include - -static struct ctl_table atalk_table[] = { - { - .procname = "aarp-expiry-time", - .data = &sysctl_aarp_expiry_time, - .maxlen = sizeof(int), - .mode = 0644, - .proc_handler = proc_dointvec_jiffies, - }, - { - .procname = "aarp-tick-time", - .data = &sysctl_aarp_tick_time, - .maxlen = sizeof(int), - .mode = 0644, - .proc_handler = proc_dointvec_jiffies, - }, - { - .procname = "aarp-retransmit-limit", - .data = &sysctl_aarp_retransmit_limit, - .maxlen = sizeof(int), - .mode = 0644, - .proc_handler = proc_dointvec, - }, - { - .procname = "aarp-resolve-time", - .data = &sysctl_aarp_resolve_time, - .maxlen = sizeof(int), - .mode = 0644, - .proc_handler = proc_dointvec_jiffies, - }, -}; - -static struct ctl_table_header *atalk_table_header; - -int __init atalk_register_sysctl(void) -{ - atalk_table_header = register_net_sysctl(&init_net, "net/appletalk", atalk_table); - if (!atalk_table_header) - return -ENOMEM; - return 0; -} - -void atalk_unregister_sysctl(void) -{ - unregister_net_sysctl_table(atalk_table_header); -} -- cgit v1.2.3 From 406e8a651a7b854c41fecd5117bb282b3a6c2c6b Mon Sep 17 00:00:00 2001 From: Yiming Qian Date: Wed, 10 Jun 2026 06:21:36 +0000 Subject: net: skmsg: preserve sg.copy across SG transforms The sk_msg sg.copy bitmap is part of the scatterlist entry ownership state. A set bit tells sk_msg_compute_data_pointers() not to expose the entry through writable BPF ctx->data. This protects entries backed by pages that are not private to the sk_msg, such as splice-backed file page-cache pages. Several sk_msg transform paths move, copy, split, or compact msg->sg.data[] entries without moving the matching sg.copy bit. This can make an externally backed entry arrive at a new slot with a clear copy bit. A later SK_MSG verdict can then expose sg_virt(sge) as writable ctx->data and BPF stores can modify the original page cache. Keep sg.copy synchronized with sg.data[] whenever entries are transferred, shifted, split, or copied into a new sk_msg. Clear the bit when an entry is replaced by a newly allocated private page or freed. This covers the BPF pull/push/pop helpers, sk_msg_shift_left/right(), sk_msg_xfer(), and tls_split_open_record(), including the partial tail entry created during TLS open-record splitting. Fixes: d3b18ad31f93 ("tls: add bpf support to sk_msg handling") Cc: stable@vger.kernel.org Reported-by: Yiming Qian Reported-by: Keenan Dong Signed-off-by: Yiming Qian Link: https://patch.msgid.link/20260610062137.49075-1-yimingqian591@gmail.com Signed-off-by: Jakub Kicinski --- include/linux/skmsg.h | 15 +++++++++++---- net/core/filter.c | 27 +++++++++++++++++++++++++++ net/core/skmsg.c | 2 ++ net/tls/tls_sw.c | 4 ++++ 4 files changed, 44 insertions(+), 4 deletions(-) (limited to 'include') diff --git a/include/linux/skmsg.h b/include/linux/skmsg.h index 19f4f253b4f9..937823856de5 100644 --- a/include/linux/skmsg.h +++ b/include/linux/skmsg.h @@ -4,6 +4,7 @@ #ifndef _LINUX_SKMSG_H #define _LINUX_SKMSG_H +#include #include #include #include @@ -199,11 +200,14 @@ static inline void sk_msg_xfer(struct sk_msg *dst, struct sk_msg *src, int which, u32 size) { dst->sg.data[which] = src->sg.data[which]; + __assign_bit(which, dst->sg.copy, test_bit(which, src->sg.copy)); dst->sg.data[which].length = size; dst->sg.size += size; src->sg.size -= size; src->sg.data[which].length -= size; src->sg.data[which].offset += size; + if (!src->sg.data[which].length) + __clear_bit(which, src->sg.copy); } static inline void sk_msg_xfer_full(struct sk_msg *dst, struct sk_msg *src) @@ -273,16 +277,19 @@ static inline void sk_msg_page_add(struct sk_msg *msg, struct page *page, static inline void sk_msg_sg_copy(struct sk_msg *msg, u32 i, bool copy_state) { do { - if (copy_state) - __set_bit(i, msg->sg.copy); - else - __clear_bit(i, msg->sg.copy); + __assign_bit(i, msg->sg.copy, copy_state); sk_msg_iter_var_next(i); if (i == msg->sg.end) break; } while (1); } +static inline void sk_msg_sg_copy_assign(struct sk_msg *dst, u32 dst_i, + const struct sk_msg *src, u32 src_i) +{ + __assign_bit(dst_i, dst->sg.copy, test_bit(src_i, src->sg.copy)); +} + static inline void sk_msg_sg_copy_set(struct sk_msg *msg, u32 start) { sk_msg_sg_copy(msg, start, true); diff --git a/net/core/filter.c b/net/core/filter.c index 80439767e0ee..40037413dd4e 100644 --- a/net/core/filter.c +++ b/net/core/filter.c @@ -2733,11 +2733,13 @@ BPF_CALL_4(bpf_msg_pull_data, struct sk_msg *, msg, u32, start, poffset += len; sge->length = 0; put_page(sg_page(sge)); + __clear_bit(i, msg->sg.copy); sk_msg_iter_var_next(i); } while (i != last_sge); sg_set_page(&msg->sg.data[first_sge], page, copy, 0); + __clear_bit(first_sge, msg->sg.copy); /* To repair sg ring we need to shift entries. If we only * had a single entry though we can just replace it and @@ -2763,9 +2765,11 @@ BPF_CALL_4(bpf_msg_pull_data, struct sk_msg *, msg, u32, start, break; msg->sg.data[i] = msg->sg.data[move_from]; + sk_msg_sg_copy_assign(msg, i, msg, move_from); msg->sg.data[move_from].length = 0; msg->sg.data[move_from].page_link = 0; msg->sg.data[move_from].offset = 0; + __clear_bit(move_from, msg->sg.copy); sk_msg_iter_var_next(i); } while (1); @@ -2794,6 +2798,7 @@ BPF_CALL_4(bpf_msg_push_data, struct sk_msg *, msg, u32, start, { struct scatterlist sge, nsge, nnsge, rsge = {0}, *psge; u32 new, i = 0, l = 0, space, copy = 0, offset = 0; + bool sge_copy, nsge_copy, nnsge_copy, rsge_copy = false; u8 *raw, *to, *from; struct page *page; @@ -2866,6 +2871,7 @@ BPF_CALL_4(bpf_msg_push_data, struct sk_msg *, msg, u32, start, sk_msg_iter_var_prev(i); psge = sk_msg_elem(msg, i); rsge = sk_msg_elem_cpy(msg, i); + rsge_copy = test_bit(i, msg->sg.copy); psge->length = start - offset; rsge.length -= psge->length; @@ -2890,24 +2896,32 @@ BPF_CALL_4(bpf_msg_push_data, struct sk_msg *, msg, u32, start, /* Shift one or two slots as needed */ sge = sk_msg_elem_cpy(msg, new); + sge_copy = test_bit(new, msg->sg.copy); sg_unmark_end(&sge); nsge = sk_msg_elem_cpy(msg, i); + nsge_copy = test_bit(i, msg->sg.copy); if (rsge.length) { sk_msg_iter_var_next(i); nnsge = sk_msg_elem_cpy(msg, i); + nnsge_copy = test_bit(i, msg->sg.copy); sk_msg_iter_next(msg, end); } while (i != msg->sg.end) { msg->sg.data[i] = sge; + __assign_bit(i, msg->sg.copy, sge_copy); sge = nsge; + sge_copy = nsge_copy; sk_msg_iter_var_next(i); if (rsge.length) { nsge = nnsge; + nsge_copy = nnsge_copy; nnsge = sk_msg_elem_cpy(msg, i); + nnsge_copy = test_bit(i, msg->sg.copy); } else { nsge = sk_msg_elem_cpy(msg, i); + nsge_copy = test_bit(i, msg->sg.copy); } } @@ -2921,6 +2935,7 @@ place_new: get_page(sg_page(&rsge)); sk_msg_iter_var_next(new); msg->sg.data[new] = rsge; + __assign_bit(new, msg->sg.copy, rsge_copy); } sk_msg_reset_curr(msg); @@ -2948,25 +2963,33 @@ static void sk_msg_shift_left(struct sk_msg *msg, int i) prev = i; sk_msg_iter_var_next(i); msg->sg.data[prev] = msg->sg.data[i]; + sk_msg_sg_copy_assign(msg, prev, msg, i); } while (i != msg->sg.end); sk_msg_iter_prev(msg, end); + __clear_bit(msg->sg.end, msg->sg.copy); } static void sk_msg_shift_right(struct sk_msg *msg, int i) { struct scatterlist tmp, sge; + bool tmp_copy, sge_copy; sk_msg_iter_next(msg, end); sge = sk_msg_elem_cpy(msg, i); + sge_copy = test_bit(i, msg->sg.copy); sk_msg_iter_var_next(i); tmp = sk_msg_elem_cpy(msg, i); + tmp_copy = test_bit(i, msg->sg.copy); while (i != msg->sg.end) { msg->sg.data[i] = sge; + __assign_bit(i, msg->sg.copy, sge_copy); sk_msg_iter_var_next(i); sge = tmp; + sge_copy = tmp_copy; tmp = sk_msg_elem_cpy(msg, i); + tmp_copy = test_bit(i, msg->sg.copy); } } @@ -3026,6 +3049,8 @@ BPF_CALL_4(bpf_msg_pop_data, struct sk_msg *, msg, u32, start, struct scatterlist *nsge, *sge = sk_msg_elem(msg, i); int a = start - offset; int b = sge->length - pop - a; + u32 sge_i = i; + bool sge_copy = test_bit(i, msg->sg.copy); sk_msg_iter_var_next(i); @@ -3038,6 +3063,7 @@ BPF_CALL_4(bpf_msg_pop_data, struct sk_msg *, msg, u32, start, sg_set_page(nsge, sg_page(sge), b, sge->offset + pop + a); + __assign_bit(i, msg->sg.copy, sge_copy); } else { struct page *page, *orig; u8 *to, *from; @@ -3054,6 +3080,7 @@ BPF_CALL_4(bpf_msg_pop_data, struct sk_msg *, msg, u32, start, memcpy(to, from, a); memcpy(to + a, from + a + pop, b); sg_set_page(sge, page, a + b, 0); + __clear_bit(sge_i, msg->sg.copy); put_page(orig); } pop = 0; diff --git a/net/core/skmsg.c b/net/core/skmsg.c index e1850caf1a71..30c3b9a2681c 100644 --- a/net/core/skmsg.c +++ b/net/core/skmsg.c @@ -66,6 +66,7 @@ int sk_msg_alloc(struct sock *sk, struct sk_msg *msg, int len, sge = &msg->sg.data[msg->sg.end]; sg_unmark_end(sge); sg_set_page(sge, pfrag->page, use, orig_offset); + __clear_bit(msg->sg.end, msg->sg.copy); get_page(pfrag->page); sk_msg_iter_next(msg, end); } @@ -186,6 +187,7 @@ static int sk_msg_free_elem(struct sock *sk, struct sk_msg *msg, u32 i, sk_mem_uncharge(sk, len); put_page(sg_page(sge)); } + __clear_bit(i, msg->sg.copy); memset(sge, 0, sizeof(*sge)); return len; } diff --git a/net/tls/tls_sw.c b/net/tls/tls_sw.c index 964ebc268ee4..a47f6a1e2c77 100644 --- a/net/tls/tls_sw.c +++ b/net/tls/tls_sw.c @@ -623,6 +623,7 @@ static int tls_split_open_record(struct sock *sk, struct tls_rec *from, struct scatterlist *sge, *osge, *nsge; u32 orig_size = msg_opl->sg.size; struct scatterlist tmp = { }; + u32 tmp_i = 0; struct sk_msg *msg_npl; struct tls_rec *new; int ret; @@ -644,6 +645,7 @@ static int tls_split_open_record(struct sock *sk, struct tls_rec *from, if (sge->length > apply) { u32 len = sge->length - apply; + tmp_i = i; get_page(sg_page(sge)); sg_set_page(&tmp, sg_page(sge), len, sge->offset + apply); @@ -675,6 +677,7 @@ static int tls_split_open_record(struct sock *sk, struct tls_rec *from, nsge = sk_msg_elem(msg_npl, j); if (tmp.length) { memcpy(nsge, &tmp, sizeof(*nsge)); + sk_msg_sg_copy_assign(msg_npl, j, msg_opl, tmp_i); sk_msg_iter_var_next(j); nsge = sk_msg_elem(msg_npl, j); } @@ -682,6 +685,7 @@ static int tls_split_open_record(struct sock *sk, struct tls_rec *from, osge = sk_msg_elem(msg_opl, i); while (osge->length) { memcpy(nsge, osge, sizeof(*nsge)); + sk_msg_sg_copy_assign(msg_npl, j, msg_opl, i); sg_unmark_end(nsge); sk_msg_iter_var_next(i); sk_msg_iter_var_next(j); -- cgit v1.2.3