From bd3e260f496adb0123076a237e5e72598dee0404 Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Tue, 28 Jul 2026 18:15:24 -0700 Subject: [PATCH 001/101] wifi: ath12k: remove skb->data check in ath12k_wmi_process_tpc_stats() Currently, ath12k_wmi_process_tpc_stats() has a !skb->data check. This check is pointless since skb->data is accessed by ath12k_wmi_op_rx() in order to dispatch the message to this function, therefore it is always non-NULL. Remove the pointless check and its associated warning message. Compile tested only. Assisted-by: Claude:claude-sonnet-4-6 Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260728-ath12k_wmi_process_tpc_stats-skb-check-v1-1-01b7d09be20d@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/wmi.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index d5160af60e00..7a8a0698b9d3 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -9992,11 +9992,6 @@ static void ath12k_wmi_process_tpc_stats(struct ath12k_base *ab, u32 event_count; int ret; - if (!skb->data) { - ath12k_warn(ab, "No data present in tpc stats event\n"); - return; - } - if (skb->len < (sizeof(*fixed_param) + TLV_HDR_SIZE)) { ath12k_warn(ab, "TPC stats event size invalid\n"); return; From f2576dc0f894ef33045ba7ed057ec417988bba8a Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Wed, 29 Jul 2026 15:18:11 +0800 Subject: [PATCH 002/101] wifi: ath12k: signal regd update completion when reg event is dropped During driver bring-up, ath12k_mac_hw_register() reinitializes ar->regd_update_completed and sends a set current country command to firmware. It then relies on the regulatory event from firmware to signal that completion via ath12k_reg_chan_list_event(). However, when the country being set is identical to the one already applied at boot time, ath12k_reg_validate_reg_info() returns ATH12K_REG_STATUS_DROP through the "Avoid multiple overwrites to default regd" path. In that case ath12k_reg_chan_list_event() jumps to mem_free before assigning pdev_idx, so pdev_idx stays at its initial value of 255, 'ar' remains NULL and complete_all() is never called. As a result the reinitialized completion is left unsignalled and the next ath12k_regd_update() waits the full timeout, printing: Timeout while waiting for regulatory update Fix this by assigning pdev_idx from the event's phy_id right after it is extracted, before validation, so that the drop and fallback paths still reach complete_all(). Change the guard at the end to check the index against ab->num_radios so that an out-of-range phy_id does not lead to an invalid pdevs[] access. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c7-00108-QCAHMTSWPL_V1.0_V2.0_SILICONZ_UPSTREAM-3 Fixes: 906619a00967 ("wifi: ath12k: handle regulatory hints during mac registration") Signed-off-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260729-ath12k-regd-wait-timeout-v1-1-504aa6e7e93c@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/wmi.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index 7a8a0698b9d3..a63bbda0219c 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -6975,6 +6975,14 @@ static int ath12k_reg_chan_list_event(struct ath12k_base *ab, struct sk_buff *sk goto mem_free; } + /* + * Set the valid pdev_idx before validating so that, even when the + * event is dropped or falls back, the completion the caller in + * ath12k_mac_hw_register() may be waiting on is still signalled at + * the end and it does not time out. + */ + pdev_idx = reg_info->phy_id; + ret = ath12k_reg_validate_reg_info(ab, reg_info); if (ret == ATH12K_REG_STATUS_FALLBACK) { ath12k_warn(ab, "failed to validate reg info %d\n", ret); @@ -6991,7 +6999,6 @@ static int ath12k_reg_chan_list_event(struct ath12k_base *ab, struct sk_buff *sk } /* free old reg_info if it exist */ - pdev_idx = reg_info->phy_id; if (ab->reg_info[pdev_idx]) { ath12k_reg_reset_reg_info(ab->reg_info[pdev_idx]); kfree(ab->reg_info[pdev_idx]); @@ -7030,7 +7037,7 @@ static int ath12k_reg_chan_list_event(struct ath12k_base *ab, struct sk_buff *sk out: /* In some error cases, even a valid pdev_idx might not be available */ - if (pdev_idx != 255) + if (pdev_idx < ab->num_radios) ar = ab->pdevs[pdev_idx].ar; /* During the boot-time update, 'ar' might not be allocated, From af8d103b2b23f8cf9a1a72b15b3873bad53470df Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Wed, 29 Jul 2026 15:18:12 +0800 Subject: [PATCH 003/101] wifi: ath12k: use per-radio ab in ath12k_mac_hw_register() In ath12k_mac_hw_register() the local 'ab' is fetched once from the first radio, i.e. ath12k_ah_to_ar(ah, 0)->ab. When an ath12k_hw spans more than one ath12k_base, the radios walked by for_each_ar() may belong to different ath12k_base instances. Using the function-scope 'ab' inside that loop then refers to the first radio's device, which is stale with respect to the ar being processed. This is not a functional problem currently: the loop only dereferences the stale 'ab' under hw_params->current_cc_support, which is set only for WCN7850 and QCC2072. Both devices expose a single radio per ath12k_hw, so 'ab' and ar->ab always point to the same ath12k_base. Still, relying on that is fragile. Cache ar->ab into a per-iteration 'this_ab' and use it for the country code programming and the log messages so that each radio is handled against its own ath12k_base regardless of how many radios an ath12k_hw spans. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c7-00108-QCAHMTSWPL_V1.0_V2.0_SILICONZ_UPSTREAM-3 Signed-off-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260729-ath12k-regd-wait-timeout-v1-2-504aa6e7e93c@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/mac.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 99bf5cf79d10..9c83811b1312 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -15043,24 +15043,26 @@ static int ath12k_mac_hw_register(struct ath12k_hw *ah) wiphy->interface_modes &= ~BIT(NL80211_IFTYPE_MONITOR); for_each_ar(ah, ar, i) { + struct ath12k_base *this_ab = ar->ab; + /* Apply the regd received during initialization */ ret = ath12k_regd_update(ar, true); if (ret) { - ath12k_err(ar->ab, "ath12k regd update failed: %d\n", ret); + ath12k_err(this_ab, "ath12k regd update failed: %d\n", ret); goto err_unregister_hw; } - if (ar->ab->hw_params->current_cc_support && ab->new_alpha2[0]) { + if (this_ab->hw_params->current_cc_support && this_ab->new_alpha2[0]) { struct wmi_set_current_country_arg current_cc = {}; - memcpy(¤t_cc.alpha2, ab->new_alpha2, 2); - memcpy(&ar->alpha2, ab->new_alpha2, 2); + memcpy(¤t_cc.alpha2, this_ab->new_alpha2, 2); + memcpy(&ar->alpha2, this_ab->new_alpha2, 2); reinit_completion(&ar->regd_update_completed); ret = ath12k_wmi_send_set_current_country_cmd(ar, ¤t_cc); if (ret) - ath12k_warn(ar->ab, + ath12k_warn(this_ab, "failed set cc code for mac register: %d\n", ret); } From 04497f2a77216f27f60463c9b3d84c3c8f2cf62c Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Wed, 29 Jul 2026 15:18:13 +0800 Subject: [PATCH 004/101] wifi: ath12k: protect new_alpha2 access with base_lock ab->new_alpha2 is written under ab->base_lock by the 11d new country code event handler ath12k_reg_11d_new_cc_event() and by the SMBIOS BDF parser, and read under the same lock by ath12k_update_11d(). ath12k_mac_hw_register() however read ab->new_alpha2 without holding base_lock, racing with those writers. Take base_lock and copy the value into a local wmi_set_current_country_arg, mirroring what ath12k_update_11d() already does, and use the local copy afterwards. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c7-00108-QCAHMTSWPL_V1.0_V2.0_SILICONZ_UPSTREAM-3 Signed-off-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260729-ath12k-regd-wait-timeout-v1-3-504aa6e7e93c@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/mac.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 9c83811b1312..b12e72ca4c9b 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -15052,11 +15052,17 @@ static int ath12k_mac_hw_register(struct ath12k_hw *ah) goto err_unregister_hw; } - if (this_ab->hw_params->current_cc_support && this_ab->new_alpha2[0]) { + if (this_ab->hw_params->current_cc_support) { struct wmi_set_current_country_arg current_cc = {}; + spin_lock_bh(&this_ab->base_lock); memcpy(¤t_cc.alpha2, this_ab->new_alpha2, 2); - memcpy(&ar->alpha2, this_ab->new_alpha2, 2); + spin_unlock_bh(&this_ab->base_lock); + + if (!current_cc.alpha2[0]) + goto fw_stats_init; + + memcpy(&ar->alpha2, current_cc.alpha2, 2); reinit_completion(&ar->regd_update_completed); @@ -15067,6 +15073,7 @@ static int ath12k_mac_hw_register(struct ath12k_hw *ah) ret); } +fw_stats_init: ath12k_fw_stats_init(ar); ath12k_debugfs_register(ar); } From 8ab8a5ee16b2fcb06837039dc42405ed53866fec Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Wed, 29 Jul 2026 15:18:14 +0800 Subject: [PATCH 005/101] wifi: ath12k: skip setting country code during registration when unchanged Currently ath12k_mac_hw_register() unconditionally sets ab->new_alpha2 to firmware. But when ab->new_alpha2 is the same as the country already applied at boot time (stored in ab->default_regd), setting it again is meaningless. So skip it in that case. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c7-00108-QCAHMTSWPL_V1.0_V2.0_SILICONZ_UPSTREAM-3 Signed-off-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260729-ath12k-regd-wait-timeout-v1-4-504aa6e7e93c@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/mac.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index b12e72ca4c9b..879f5ad80921 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -15054,6 +15054,8 @@ static int ath12k_mac_hw_register(struct ath12k_hw *ah) if (this_ab->hw_params->current_cc_support) { struct wmi_set_current_country_arg current_cc = {}; + struct ieee80211_regdomain *default_regd; + bool same_cc = false; spin_lock_bh(&this_ab->base_lock); memcpy(¤t_cc.alpha2, this_ab->new_alpha2, 2); @@ -15064,6 +15066,16 @@ static int ath12k_mac_hw_register(struct ath12k_hw *ah) memcpy(&ar->alpha2, current_cc.alpha2, 2); + spin_lock_bh(&this_ab->base_lock); + default_regd = this_ab->default_regd[ar->pdev_idx]; + if (default_regd) + same_cc = !memcmp(default_regd->alpha2, + current_cc.alpha2, 2); + spin_unlock_bh(&this_ab->base_lock); + + if (same_cc) + goto fw_stats_init; + reinit_completion(&ar->regd_update_completed); ret = ath12k_wmi_send_set_current_country_cmd(ar, ¤t_cc); From 6132d70d0890c03546f6970893fd71938efb2a2f Mon Sep 17 00:00:00 2001 From: Abdun Nihaal Date: Sat, 1 Aug 2026 17:19:09 +0530 Subject: [PATCH 006/101] wifi: ath11k: Fix possible memory leak in ath11k_dp_srng_setup() In ath11k_dp_srng_setup(), the memory allocated for ring->vaddr_unaligned is not freed in the error paths that follow. Fix that by calling ath11k_dp_srng_cleanup() in those error paths. Compile tested only. Issue found using a prototype static analysis tool built on the LLVM compiler infrastructure. Fixes: d5c65159f289 ("ath11k: driver for Qualcomm IEEE 802.11ax devices") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-sonnet-4-6 Reviewed-by: Baochen Qiang Signed-off-by: Abdun Nihaal Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260801114911.91483-1-nihaal@cse.iitm.ac.in Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/dp.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/wireless/ath/ath11k/dp.c b/drivers/net/wireless/ath/ath11k/dp.c index f389b97acbdd..be65d73b904b 100644 --- a/drivers/net/wireless/ath/ath11k/dp.c +++ b/drivers/net/wireless/ath/ath11k/dp.c @@ -313,6 +313,7 @@ int ath11k_dp_srng_setup(struct ath11k_base *ab, struct dp_srng *ring, case HAL_RXDMA_DIR_BUF: break; default: + ath11k_dp_srng_cleanup(ab, ring); ath11k_warn(ab, "Not a valid ring type in dp :%d\n", type); return -EINVAL; } @@ -324,6 +325,7 @@ int ath11k_dp_srng_setup(struct ath11k_base *ab, struct dp_srng *ring, ret = ath11k_hal_srng_setup(ab, type, ring_num, mac_id, ¶ms); if (ret < 0) { + ath11k_dp_srng_cleanup(ab, ring); ath11k_warn(ab, "failed to setup srng: %d ring_id %d\n", ret, ring_num); return ret; From a7ab5c835e4253b6577e4137367c05bfddd79fa2 Mon Sep 17 00:00:00 2001 From: Julius Bairaktaris Date: Mon, 27 Jul 2026 00:19:07 +0200 Subject: [PATCH 007/101] wifi: ath11k: implement CE interrupt enable/disable for AHB ath11k_core_reset() calls ath11k_hif_ce_irq_disable() right before it powers the target down, but ath11k_ahb_hif_ops_ipq8074 never set the ce_irq_enable/ce_irq_disable pair, and ath11k_hif_ce_irq_disable() does nothing when the op is NULL. On AHB the copy engine interrupts and their tasklets therefore stay live across rproc_shutdown(), where the register space they touch is no longer accessible. Wire the ops up. The sequence is the one ath11k_ahb_stop() already runs, factored into a helper and reused, so behaviour on the stop path is unchanged. wcn6750 is not affected: it uses the pcic ops, which implement the pair already. Assisted-by: Claude:claude-opus-5 Signed-off-by: Julius Bairaktaris Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260726221908.104873-2-julius@bairaktaris.de Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/ahb.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath11k/ahb.c b/drivers/net/wireless/ath/ath11k/ahb.c index ec5bf5c9fd79..47df9db41fa5 100644 --- a/drivers/net/wireless/ath/ath11k/ahb.c +++ b/drivers/net/wireless/ath/ath11k/ahb.c @@ -391,12 +391,17 @@ static void ath11k_ahb_ext_irq_disable(struct ath11k_base *ab) ath11k_ahb_sync_ext_irqs(ab); } -static void ath11k_ahb_stop(struct ath11k_base *ab) +static void ath11k_ahb_ce_irq_disable_sync(struct ath11k_base *ab) { if (!test_bit(ATH11K_FLAG_CRASH_FLUSH, &ab->dev_flags)) ath11k_ahb_ce_irqs_disable(ab); ath11k_ahb_sync_ce_irqs(ab); ath11k_ahb_kill_tasklets(ab); +} + +static void ath11k_ahb_stop(struct ath11k_base *ab) +{ + ath11k_ahb_ce_irq_disable_sync(ab); timer_delete_sync(&ab->rx_replenish_retry); ath11k_ce_cleanup_pipes(ab); } @@ -804,6 +809,8 @@ static const struct ath11k_hif_ops ath11k_ahb_hif_ops_ipq8074 = { .map_service_to_pipe = ath11k_ahb_map_service_to_pipe, .power_down = ath11k_ahb_power_down, .power_up = ath11k_ahb_power_up, + .ce_irq_enable = ath11k_ahb_ce_irqs_enable, + .ce_irq_disable = ath11k_ahb_ce_irq_disable_sync, }; static const struct ath11k_hif_ops ath11k_ahb_hif_ops_wcn6750 = { From f7a74e131d3f0de04335dbe278bfb82bdafcdb3e Mon Sep 17 00:00:00 2001 From: Julius Bairaktaris Date: Mon, 27 Jul 2026 00:19:08 +0200 Subject: [PATCH 008/101] wifi: ath11k: disable interrupts during firmware crash recovery On IPQ8074 a firmware assert reboots the SoC: Unable to handle kernel read from unreadable memory at virtual address 0 pc : ath11k_hal_srng_access_begin+0xc/0x60 [ath11k] lr : ath11k_dp_rx_process_mon_status+0x15c/0xd84 [ath11k] Call trace: ath11k_hal_srng_access_begin+0xc/0x60 [ath11k] ath11k_dp_rx_process_mon_rings+0xa0/0x5d4 [ath11k] ath11k_dp_service_srng+0x1f4/0x348 [ath11k] ath11k_ahb_ext_grp_napi_poll+0x34/0xd4 [ath11k_ahb] __napi_poll+0x38/0x188 net_rx_action+0x120/0x2c0 ath11k_core_reconfigure_on_crash() tears the data path down with ath11k_dp_pdev_free(), ath11k_dp_free() and ath11k_hal_srng_clear(), which memsets the ring list. The DP NAPI is still running while that happens, so it services a ring whose address pointer has just been cleared. That function used to disable the interrupts first, until commit d455e805de70 ("wifi: ath11k: rearrange IRQ enable/disable in reset path") moved the disable into ath11k_core_reset(). reset_work is only queued from mhi.c and from the debugfs hw-restart handler, so AHB parts never run it on a real firmware crash. Their recovery goes QMI server exit -> restart_work -> ath11k_core_reconfigure_on_crash() -> ath11k_core_qmi_firmware_ready(), and nothing disables the interrupts anywhere along it. Disable them again on the crash path. The reset path has already done so by the time it gets here, hence the ab->is_reset check. This is also why the debugfs hw-restart trigger never showed the problem: it goes through ath11k_core_reset(), the one path that still had the disable. Tested-on: IPQ8074 hw2.0 AHB WLAN.HK.2.12-01460-QCAHKSWPL_SILICONZ-1 Fixes: d455e805de70 ("wifi: ath11k: rearrange IRQ enable/disable in reset path") Assisted-by: Claude:claude-opus-5 Signed-off-by: Julius Bairaktaris Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260726221908.104873-3-julius@bairaktaris.de Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/core.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/net/wireless/ath/ath11k/core.c b/drivers/net/wireless/ath/ath11k/core.c index 8039124e7832..d2ed6a0ea7e3 100644 --- a/drivers/net/wireless/ath/ath11k/core.c +++ b/drivers/net/wireless/ath/ath11k/core.c @@ -2334,6 +2334,16 @@ static int ath11k_core_reconfigure_on_crash(struct ath11k_base *ab) mutex_lock(&ab->core_lock); ath11k_thermal_unregister(ab); + + /* + * ath11k_core_reset() already disabled the interrupts on the reset + * path; only the firmware crash path reaches here with them live. + */ + if (!ab->is_reset) { + ath11k_hif_irq_disable(ab); + ath11k_hif_ce_irq_disable(ab); + } + ath11k_dp_pdev_free(ab); ath11k_cfr_deinit(ab); ath11k_spectral_deinit(ab); From 7dba43536f743eb93aec1bb4fce2c0f1495a6680 Mon Sep 17 00:00:00 2001 From: Rameshkumar Sundaram Date: Mon, 10 Aug 2026 11:14:52 +0530 Subject: [PATCH 009/101] mailmap: add entries for Rameshkumar Sundaram Map past iterations of my e-mail addresses to the current one. Signed-off-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260810054452.3245263-1-rameshkumar.sundaram@oss.qualcomm.com Signed-off-by: Jeff Johnson --- .mailmap | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.mailmap b/.mailmap index 8fb9f3898d7a..3f0d5a419032 100644 --- a/.mailmap +++ b/.mailmap @@ -749,6 +749,8 @@ Rakesh Pillai Ralf Baechle Ralf Wildenhues Ram Chandra Jangir +Rameshkumar Sundaram +Rameshkumar Sundaram Randy Dunlap Randy Dunlap Randy Dunlap From ede121c2c2c0894e161d2134fb6c5598ab72085e Mon Sep 17 00:00:00 2001 From: Sriram R Date: Mon, 10 Aug 2026 12:31:09 +0530 Subject: [PATCH 010/101] mailmap: add entries for Sriram R Map past iterations of my e-mail addresses to the current one. Signed-off-by: Sriram R Link: https://patch.msgid.link/20260810070109.700301-1-sriram.ramachandran@oss.qualcomm.com Signed-off-by: Jeff Johnson --- .mailmap | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index 3f0d5a419032..482e6637a797 100644 --- a/.mailmap +++ b/.mailmap @@ -849,7 +849,8 @@ Sricharan Ramabadhran Srinivas Kandagatla Srinivas Kandagatla Srinivas Ramana -Sriram R +Sriram R +Sriram R Sriram Yagnaraman Stanislav Fomichev Stanislav Fomichev From 865b96bf16625aedfec8972d0528d370c245ff2a Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Mon, 10 Aug 2026 17:46:00 +0800 Subject: [PATCH 011/101] mailmap: add entries for Baochen Qiang Map past iterations of my e-mail addresses to the current one. Signed-off-by: Baochen Qiang Link: https://patch.msgid.link/20260810-mailmap-v1-1-95129af871ba@oss.qualcomm.com Signed-off-by: Jeff Johnson --- .mailmap | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index 482e6637a797..4703de1d95d7 100644 --- a/.mailmap +++ b/.mailmap @@ -129,7 +129,8 @@ Axel Dyks Axel Lin Balakrishna Godavarthi Banajit Goswami -Baochen Qiang +Baochen Qiang +Baochen Qiang Baolin Wang Baolin Wang Baolin Wang From 4b8e9bccbe08ce5961540e6c63fac326c31c35bf Mon Sep 17 00:00:00 2001 From: Manish Dharanenthiran Date: Mon, 10 Aug 2026 22:36:09 +0530 Subject: [PATCH 012/101] mailmap: add entries for Manish Dharanenthiran Map past iterations of my e-mail addresses to the current one. Signed-off-by: Manish Dharanenthiran Link: https://patch.msgid.link/20260810-mailmap-v1-1-76b680fd0dd4@oss.qualcomm.com Signed-off-by: Jeff Johnson --- .mailmap | 1 + 1 file changed, 1 insertion(+) diff --git a/.mailmap b/.mailmap index 4703de1d95d7..be1de3422043 100644 --- a/.mailmap +++ b/.mailmap @@ -565,6 +565,7 @@ Maheshwar Ajja MaĆ­ra Canal Malathi Gottam Manikanta Pubbisetty +Manish Dharanenthiran Manivannan Sadhasivam Manivannan Sadhasivam Manoj Basapathi From 3cf8fade4b0471db6f28b5ae4c76af1f47883d56 Mon Sep 17 00:00:00 2001 From: Vasanthakumar Thiagarajan Date: Mon, 10 Aug 2026 23:17:46 +0530 Subject: [PATCH 013/101] mailmap: update email address for Vasanthakumar Thiagarajan Add my current oss email address and map old codeaurora and quicinc addresses to it. Signed-off-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20260810174746.3272636-1-vasanthakumar.thiagarajan@oss.qualcomm.com Signed-off-by: Jeff Johnson --- .mailmap | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index be1de3422043..7cd3b9ec4018 100644 --- a/.mailmap +++ b/.mailmap @@ -935,7 +935,8 @@ Vadim Fedorenko Valdis Kletnieks Vara Reddy Varadarajan Narayanan -Vasanthakumar Thiagarajan +Vasanthakumar Thiagarajan +Vasanthakumar Thiagarajan Vasily Averin Vasily Averin Vasily Averin From 52eec2e1fac8fa49ff853ccd30292f85d9751749 Mon Sep 17 00:00:00 2001 From: Aaradhana Sahu Date: Tue, 11 Aug 2026 11:29:02 +0530 Subject: [PATCH 014/101] mailmap: add entries for Aaradhana Sahu Map past iterations of my e-mail addresses to the current one. Signed-off-by: Aaradhana Sahu Link: https://patch.msgid.link/20260811055902.522384-1-aaradhana.sahu@oss.qualcomm.com Signed-off-by: Jeff Johnson --- .mailmap | 1 + 1 file changed, 1 insertion(+) diff --git a/.mailmap b/.mailmap index 7cd3b9ec4018..b95cb85c1c89 100644 --- a/.mailmap +++ b/.mailmap @@ -10,6 +10,7 @@ # # Please keep this list dictionary sorted. # +Aaradhana Sahu Aaron Durbin Abel Vesa Abel Vesa From 06cca100ad083609ca9cc2d1f692546369144189 Mon Sep 17 00:00:00 2001 From: Andrew LaMarche Date: Tue, 30 Jun 2026 13:30:00 +0000 Subject: [PATCH 015/101] wifi: ath12k: support calibration-variant from device tree ath10k and ath11k support reading calibration variants from the device tree to locate the correct Board Description File (BDF). The ath12k-wsi binding already describes using qcom,calibration-variant but it is not implemented in the driver. Many ath12k designs expose all the radios under a single phy, each of which typically require a separate BDF. Without this, the radios may not come up or will not be calibrated correctly. Fix this by parsing the device tree for the generation-agnostic qcom,calibration-variant. This allows the driver to properly select, read and apply the correct BDF. Signed-off-by: Andrew LaMarche Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260630133001.1426824-1-andrewjlamarche@gmail.com [fixed checkpatch issues] Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/core.c | 23 +++++++++++++++++++++++ drivers/net/wireless/ath/ath12k/qmi.c | 4 ++++ 2 files changed, 27 insertions(+) diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c index 262a2045309b..0c72948f3f56 100644 --- a/drivers/net/wireless/ath/ath12k/core.c +++ b/drivers/net/wireless/ath/ath12k/core.c @@ -792,6 +792,29 @@ int ath12k_core_check_smbios(struct ath12k_base *ab) return 0; } +int ath12k_core_check_dt(struct ath12k_base *ab) +{ + size_t max_len = sizeof(ab->qmi.target.bdf_ext); + const char *variant = NULL; + struct device_node *node; + + node = ab->dev->of_node; + if (!node) + return -ENOENT; + + of_property_read_string(node, "qcom,calibration-variant", + &variant); + if (!variant) + return -ENODATA; + + if (strscpy(ab->qmi.target.bdf_ext, variant, max_len) < 0) + ath12k_dbg(ab, ATH12K_DBG_BOOT, + "bdf variant string is longer than the buffer can accommodate (variant: %s)\n", + variant); + + return 0; +} + static int ath12k_core_soc_create(struct ath12k_base *ab) { int ret; diff --git a/drivers/net/wireless/ath/ath12k/qmi.c b/drivers/net/wireless/ath/ath12k/qmi.c index 280e50a1f31d..50082505c882 100644 --- a/drivers/net/wireless/ath/ath12k/qmi.c +++ b/drivers/net/wireless/ath/ath12k/qmi.c @@ -2972,6 +2972,10 @@ int ath12k_qmi_request_target_cap(struct ath12k_base *ab) if (r) ath12k_dbg(ab, ATH12K_DBG_QMI, "SMBIOS bdf variant name not set.\n"); + r = ath12k_core_check_dt(ab); + if (r) + ath12k_dbg(ab, ATH12K_DBG_QMI, "DT bdf variant name not set.\n"); + r = ath12k_acpi_start(ab); if (r) /* ACPI is optional so continue in case of an error */ From e6c2d73f27b3948cabec1017c9443fba24f1dcc1 Mon Sep 17 00:00:00 2001 From: Andrew LaMarche Date: Tue, 30 Jun 2026 13:30:01 +0000 Subject: [PATCH 016/101] dt-bindings: wireless: ath12k: drop qcom,ath12k-calibration-variant The ath12k-wsi binding documentation describes using the generation-specific qcom,ath12k-calibration-variant binding as well as the generation-agnostic qcom,calibration-variant binding to load board-specific calibration data from the device tree. However, the driver never implemented either of these. Given that no devices currently supported use qcom,ath12k-calibration-variant and the previous patch implements qcom,calibration-variant, drop the generation-specific version from the binding to prevent future confusion. Signed-off-by: Andrew LaMarche Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260630133001.1426824-2-andrewjlamarche@gmail.com Signed-off-by: Jeff Johnson --- .../devicetree/bindings/net/wireless/qcom,ath12k-wsi.yaml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Documentation/devicetree/bindings/net/wireless/qcom,ath12k-wsi.yaml b/Documentation/devicetree/bindings/net/wireless/qcom,ath12k-wsi.yaml index 589960144fe1..fa64c8aa56f5 100644 --- a/Documentation/devicetree/bindings/net/wireless/qcom,ath12k-wsi.yaml +++ b/Documentation/devicetree/bindings/net/wireless/qcom,ath12k-wsi.yaml @@ -58,13 +58,6 @@ properties: String to uniquely identify variant of the calibration data for designs with colliding bus and device ids - qcom,ath12k-calibration-variant: - $ref: /schemas/types.yaml#/definitions/string - deprecated: true - description: - String to uniquely identify variant of the calibration data for designs - with colliding bus and device ids - qcom,wsi-controller: $ref: /schemas/types.yaml#/definitions/flag description: From 32ec8488d75d122768b1977501bcb5ec799230c4 Mon Sep 17 00:00:00 2001 From: Miaoqing Pan Date: Thu, 6 Aug 2026 18:13:29 +0800 Subject: [PATCH 017/101] wifi: ath11k: fix sporadic WLAN initialization failures On WCN6750 platforms, reboot stress testing occasionally results in WLAN initialization failures after boot. The WPSS firmware reaches the running state successfully, but no WLAN interface is created. Analysis shows that ath11k_ahb depends on the QRTR SMD transport for QMI communication with WPSS firmware. However, this dependency is not currently expressed in Kconfig, allowing qrtr_smd and ath11k_ahb to load in either order when built as modules. If ath11k_ahb is loaded before qrtr_smd becomes available, WLAN initialization may not complete successfully. Make the QRTR and QRTR_SMD dependencies explicit and add a soft dependency to ensure qrtr_smd is loaded before ath11k_ahb. Tested-on: WCN6750 hw1.0 AHB WLAN.MSL.2.0.c2-00204-QCAMSLSWPLZ-1 Fixes: 00402f49d26f ("ath11k: Add support for WCN6750 device") Signed-off-by: Miaoqing Pan Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260806101331.484063-2-miaoqing.pan@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/Kconfig | 3 +++ drivers/net/wireless/ath/ath11k/ahb.c | 1 + 2 files changed, 4 insertions(+) diff --git a/drivers/net/wireless/ath/ath11k/Kconfig b/drivers/net/wireless/ath/ath11k/Kconfig index 122726f84492..44b520d2e66e 100644 --- a/drivers/net/wireless/ath/ath11k/Kconfig +++ b/drivers/net/wireless/ath/ath11k/Kconfig @@ -14,6 +14,9 @@ config ATH11K_AHB tristate "Atheros ath11k AHB support" depends on ATH11K depends on REMOTEPROC + select RPMSG + select QRTR + select QRTR_SMD help This module adds support for AHB bus diff --git a/drivers/net/wireless/ath/ath11k/ahb.c b/drivers/net/wireless/ath/ath11k/ahb.c index 47df9db41fa5..9ab255b50f87 100644 --- a/drivers/net/wireless/ath/ath11k/ahb.c +++ b/drivers/net/wireless/ath/ath11k/ahb.c @@ -1350,5 +1350,6 @@ static struct platform_driver ath11k_ahb_driver = { module_platform_driver(ath11k_ahb_driver); +MODULE_SOFTDEP("pre: qrtr_smd"); MODULE_DESCRIPTION("Driver support for Qualcomm Technologies 802.11ax WLAN AHB devices"); MODULE_LICENSE("Dual BSD/GPL"); From 1a7bcf5324c8e2de6512eca5b028b6b283181d3f Mon Sep 17 00:00:00 2001 From: Miaoqing Pan Date: Thu, 6 Aug 2026 18:13:30 +0800 Subject: [PATCH 018/101] wifi: ath11k: fix NULL dereference in ahb remove when QMI init incomplete On WCN6750, if QMI messages never arrive (for example when qrtr_smd is not ready), WLAN initialization stops before the device is fully registered. In this case ATH11K_FLAG_QMI_FAIL is not set because no QMI event handler is executed. When the driver is removed, ath11k_ahb_remove() still calls ath11k_core_deinit(), which eventually triggers ath11k_ce_cleanup_pipes() on uninitialized CE pipes and results in a NULL pointer dereference in ath11k_hal_srng_access_begin(): ath11k_hal_srng_access_begin+0x14/0x68 [ath11k] ath11k_ce_cleanup_pipes+0x184/0x190 [ath11k] ath11k_pcic_stop+0x24/0x38 [ath11k] ath11k_core_deinit+0xfc/0x1c0 [ath11k] ath11k_ahb_remove+0x38/0xa0 [ath11k_ahb] Fix this by invoking ath11k_ahb_remove_prepare() before the state check and skipping ath11k_core_deinit() if ATH11K_FLAG_REGISTERED is not set, core initialization did not complete and CE pipes may remain uninitialized, making ath11k_core_deinit() unsafe. Tested-on: WCN6750 hw1.0 AHB WLAN.MSL.2.0.c2-00204-QCAMSLSWPLZ-1 Fixes: 00402f49d26f ("ath11k: Add support for WCN6750 device") Signed-off-by: Miaoqing Pan Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260806101331.484063-3-miaoqing.pan@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/ahb.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath11k/ahb.c b/drivers/net/wireless/ath/ath11k/ahb.c index 9ab255b50f87..5c41bfe2d52e 100644 --- a/drivers/net/wireless/ath/ath11k/ahb.c +++ b/drivers/net/wireless/ath/ath11k/ahb.c @@ -1303,14 +1303,15 @@ static void ath11k_ahb_remove(struct platform_device *pdev) { struct ath11k_base *ab = platform_get_drvdata(pdev); - if (test_bit(ATH11K_FLAG_QMI_FAIL, &ab->dev_flags)) { + ath11k_ahb_remove_prepare(ab); + + if (!test_bit(ATH11K_FLAG_REGISTERED, &ab->dev_flags)) { ath11k_ahb_power_down(ab, false); ath11k_debugfs_soc_destroy(ab); ath11k_qmi_deinit_service(ab); goto qmi_fail; } - ath11k_ahb_remove_prepare(ab); ath11k_core_deinit(ab); qmi_fail: From 1d8e73163ef933624341075f576e2f36ef9133f7 Mon Sep 17 00:00:00 2001 From: Miaoqing Pan Date: Thu, 6 Aug 2026 18:13:31 +0800 Subject: [PATCH 019/101] wifi: ath11k: unregister PM notifier on QMI init failure path ath11k_core_init() registers a PM notifier before the QMI server becomes available. If the QMI server never arrives, the device remove() path can take the early-exit path introduced for QMI initialization failures, skipping ath11k_core_deinit(). As a result, the PM notifier remains registered after the ath11k base object has been freed. A subsequent suspend or resume event may invoke the stale notifier and trigger a use-after-free. Fix this by explicitly unregistering the PM notifier in the QMI failure cleanup path before releasing ath11k resources. Tested-on: WCN6750 hw1.0 AHB WLAN.MSL.2.0.c2-00204-QCAMSLSWPLZ-1 Fixes: 32d93b51bc7e ("wifi: ath11k: choose default PM policy for hibernation") Signed-off-by: Miaoqing Pan Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260806101331.484063-4-miaoqing.pan@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/ahb.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/ath/ath11k/ahb.c b/drivers/net/wireless/ath/ath11k/ahb.c index 5c41bfe2d52e..27d01411b6e9 100644 --- a/drivers/net/wireless/ath/ath11k/ahb.c +++ b/drivers/net/wireless/ath/ath11k/ahb.c @@ -1309,6 +1309,7 @@ static void ath11k_ahb_remove(struct platform_device *pdev) ath11k_ahb_power_down(ab, false); ath11k_debugfs_soc_destroy(ab); ath11k_qmi_deinit_service(ab); + ath11k_core_pm_notifier_unregister(ab); goto qmi_fail; } From 00f623647c331d31df8629af958f7582a7fc874c Mon Sep 17 00:00:00 2001 From: Lachlan Hodges Date: Thu, 27 Aug 2026 15:44:56 +1000 Subject: [PATCH 020/101] wifi: mac80211: don't seed an S1G sta's last_rate When initialising the rate control system, ieee80211_s1g_sta_rate_init() is called for S1G stations during association where it initialises a last rate of type STA_STATS_RATE_TYPE_S1G but does not initialise nss, mcs bw etc. Since the last_rate no longer has a value equivalent to STA_STATS_RATE_INVALID, the invalid rate will be decoded and the NL80211_STA_INFO_RX_BITRATE flag will be set. As a result, there is a period during association before the first data frame is rx'ed where the seeded value is overwritten by ieee80211_rx_h_sta_process() while this invalid last_rate lives. If the sta is desroyed during this process, nl80211_put_sta_rate() will call into cfg80211_calculate_bitrate() and attempt to calculate the bitrate using the invalid last_rate leading to a WARN: 4,336,675067400,-;------------[ cut here ]------------ 4,337,675067524,-;WARNING: net/wireless/util.c:1960 at cfg80211_calculate_bitrate+0x4d8/0x8b0 [cfg80211], CPU#3: kworker/u16:3/874 4,338,675068176,-;invalid rate bw=0, mcs=0, nss=0 [snip loaded modules] 4,341,675068954,-;Hardware name: BCM2711 4,342,675068961,-;Workqueue: events_unbound cfg80211_wiphy_work [cfg80211] 4,343,675069386,-;Call trace: 4,344,675069392,-; unwind_backtrace from show_stack+0x10/0x14 4,345,675069440,-; show_stack from dump_stack_lvl+0xa8/0xb8 4,346,675069475,-; dump_stack_lvl from __warn+0x90/0x224 4,347,675069517,-; __warn from warn_slowpath_fmt+0x130/0x1a8 4,348,675069559,-; warn_slowpath_fmt from cfg80211_calculate_bitrate+0x4d8/0x8b0 [cfg80211] 4,349,675070005,-; cfg80211_calculate_bitrate [cfg80211] from nl80211_put_sta_rate+0x48/0x590 [cfg80211] 4,350,675070819,-; nl80211_put_sta_rate [cfg80211] from nl80211_put_sta_info_common+0x4f4/0xd50 [cfg80211] 4,351,675071616,-; nl80211_put_sta_info_common [cfg80211] from nl80211_send_station.constprop.0+0x114/0x424 [cfg80211] 4,352,675072427,-; nl80211_send_station.constprop.0 [cfg80211] from cfg80211_del_sta_sinfo+0xc4/0x368 [cfg80211] 4,353,675073222,-; cfg80211_del_sta_sinfo [cfg80211] from __sta_info_destroy_part2+0x108/0x2c0 [mac80211] 4,354,675074178,-; __sta_info_destroy_part2 [mac80211] from sta_info_destroy_addr+0x4c/0x88 [mac80211] 4,355,675075243,-; sta_info_destroy_addr [mac80211] from ieee80211_destroy_assoc_data+0x194/0x2d8 [mac80211] 4,356,675075920,-; ieee80211_destroy_assoc_data [mac80211] from ieee80211_rx_mgmt_assoc_resp+0x220/0x1d4c [mac80211] 4,357,675076558,-; ieee80211_rx_mgmt_assoc_resp [mac80211] from ieee80211_iface_work+0x1c0/0x5d0 [mac80211] 4,358,675077187,-; ieee80211_iface_work [mac80211] from cfg80211_wiphy_work+0x110/0x368 [cfg80211] 4,359,675077763,-; cfg80211_wiphy_work [cfg80211] from process_one_work+0x260/0x868 4,360,675078022,-; process_one_work from worker_thread+0x1bc/0x3ec 4,361,675078042,-; worker_thread from kthread+0x120/0x154 4,362,675078060,-; kthread from ret_from_fork+0x14/0x20 4,363,675078074,-;Exception stack(0xf0ac1fb0 to 0xf0ac1ff8) 4,364,675078081,-;1fa0: 00000000 00000000 00000000 00000000 4,365,675078088,-;1fc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 4,366,675078093,-;1fe0: 00000000 00000000 00000000 00000000 00000013 00000000 4,367,675080226,-;irq event stamp: 15780967 4,368,675080287,-;hardirqs last enabled at (15780975): [] __up_console_sem+0x50/0x60 4,369,675080395,-;hardirqs last disabled at (15780982): [] __up_console_sem+0x3c/0x60 4,370,675080497,-;softirqs last enabled at (15780966): [] handle_softirqs+0x398/0x650 4,371,675080608,-;softirqs last disabled at (15780939): [] __irq_exit_rcu+0x150/0x1f0 4,372,675080715,-;---[ end trace 0000000000000000 ]--- To fix, we can simply not seed this psuedo rate. It was originally implemented to avoid errornous paths that S1G shouldn't take, but those have since been fixed / cleaned up. While we are here, we also don't need to initialise the tx side's last rate with IEEE80211_TX_RC_S1G_MCS (which is equivalent to IEEE80211_TX_RC_VHT_MCS). The entire tx reporting side does not support S1G widths and is assumed to be handled by the driver so we can just remove the entire function. Fixes: 12bf8fad4ca3 ("mac80211: initialize last_rate for S1G STAs") Signed-off-by: Lachlan Hodges Link: https://patch.msgid.link/20260827054456.254573-1-lachlan.hodges@morsemicro.com Signed-off-by: Johannes Berg --- net/mac80211/ieee80211_i.h | 1 - net/mac80211/mlme.c | 14 +++++--------- net/mac80211/rate.c | 1 - net/mac80211/s1g.c | 8 -------- 4 files changed, 5 insertions(+), 19 deletions(-) diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 5761e9621491..d06cb8e7dfb3 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -2388,7 +2388,6 @@ ieee80211_he_op_ie_to_bss_conf(struct ieee80211_vif *vif, const struct ieee80211_he_operation *he_op_ie_elem); /* S1G */ -void ieee80211_s1g_sta_rate_init(struct sta_info *sta); bool ieee80211_s1g_is_twt_setup(struct sk_buff *skb); void ieee80211_s1g_rx_twt_action(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb); diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index f51167f0fc46..47ec703f1094 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -6347,7 +6347,6 @@ static bool ieee80211_assoc_config_link(struct ieee80211_link_data *link, } static int ieee80211_mgd_setup_link_sta(struct ieee80211_link_data *link, - struct sta_info *sta, struct link_sta_info *link_sta, struct cfg80211_bss *cbss) { @@ -6362,11 +6361,9 @@ static int ieee80211_mgd_setup_link_sta(struct ieee80211_link_data *link, memcpy(link_sta->addr, cbss->bssid, ETH_ALEN); memcpy(link_sta->pub->addr, cbss->bssid, ETH_ALEN); - /* TODO: S1G Basic Rate Set is expressed elsewhere */ - if (cbss->channel->band == NL80211_BAND_S1GHZ) { - ieee80211_s1g_sta_rate_init(sta); + /* S1G does not use basic rates */ + if (cbss->channel->band == NL80211_BAND_S1GHZ) return 0; - } sband = local->hw.wiphy->bands[cbss->channel->band]; @@ -7107,7 +7104,7 @@ static bool ieee80211_assoc_success(struct ieee80211_sub_if_data *sdata, } } - err = ieee80211_mgd_setup_link_sta(link, sta, link_sta, + err = ieee80211_mgd_setup_link_sta(link, link_sta, assoc_data->link[link_id].bss); if (err) goto out_err; @@ -9699,8 +9696,7 @@ static int ieee80211_prep_connection(struct ieee80211_sub_if_data *sdata, goto out_err; } - err = ieee80211_mgd_setup_link_sta(link, new_sta, - link_sta, cbss); + err = ieee80211_mgd_setup_link_sta(link, link_sta, cbss); if (err) { rcu_read_unlock(); sta_info_free(local, new_sta); @@ -11090,7 +11086,7 @@ ieee80211_process_ml_reconf_resp(struct ieee80211_sub_if_data *sdata, goto disconnect; } - if (ieee80211_mgd_setup_link_sta(link, sta, link_sta, + if (ieee80211_mgd_setup_link_sta(link, link_sta, add_links_data->link[link_id].bss)) goto disconnect; diff --git a/net/mac80211/rate.c b/net/mac80211/rate.c index 64768abb0a5f..bcc6c4167395 100644 --- a/net/mac80211/rate.c +++ b/net/mac80211/rate.c @@ -57,7 +57,6 @@ void rate_control_rate_init(struct link_sta_info *link_sta) /* TODO: check for minstrel_s1g ? */ if (sband->band == NL80211_BAND_S1GHZ) { - ieee80211_s1g_sta_rate_init(sta); rcu_read_unlock(); return; } diff --git a/net/mac80211/s1g.c b/net/mac80211/s1g.c index bd7baf481836..4b70afa6e6d7 100644 --- a/net/mac80211/s1g.c +++ b/net/mac80211/s1g.c @@ -9,14 +9,6 @@ #include "ieee80211_i.h" #include "driver-ops.h" -void ieee80211_s1g_sta_rate_init(struct sta_info *sta) -{ - /* avoid indicating legacy bitrates for S1G STAs */ - sta->deflink.tx_stats.last_rate.flags |= IEEE80211_TX_RC_S1G_MCS; - sta->deflink.rx_stats.last_rate = - STA_STATS_FIELD(TYPE, STA_STATS_RATE_TYPE_S1G); -} - bool ieee80211_s1g_is_twt_setup(struct sk_buff *skb) { struct ieee80211_mgmt *mgmt = (struct ieee80211_mgmt *)skb->data; From edad0797f1a721f736b5c59fcb2bb6a13ac636f2 Mon Sep 17 00:00:00 2001 From: Lachlan Hodges Date: Thu, 27 Aug 2026 15:46:08 +1000 Subject: [PATCH 021/101] wifi: cfg80211: remove duplicate s1g_cap kerneldoc ieee80211_supported_band::s1g_cap is already annotated. Remove the duplicate. Signed-off-by: Lachlan Hodges Link: https://patch.msgid.link/20260827054608.254883-1-lachlan.hodges@morsemicro.com Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 97c16d4ff127..2abf9e20cc22 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -576,7 +576,6 @@ struct ieee80211_sta_s1g_cap { * @vht_cap: VHT capabilities in this band * @s1g_cap: S1G capabilities in this band * @edmg_cap: EDMG capabilities in this band - * @s1g_cap: S1G capabilities in this band (S1G band only, of course) * @n_iftype_data: number of iftype data entries * @iftype_data: interface type data entries. Note that the bits in * @types_mask inside this structure cannot overlap (i.e. only From b120ec039fae389405549b47ac5cf3a4f5c7b5f3 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sun, 26 Jul 2026 17:30:35 -0700 Subject: [PATCH 022/101] wifi: mwifiex: Use flexible array for RX reorder table Embed the RX reorder pointer array in struct mwifiex_rx_reorder_tbl instead of allocating it separately. This ties the array to the reorder table lifetime and removes a separate allocation and cleanup path. Change the void pointer type to the proper one for clarity. Assisted-by: Codex:GPT-5.5 Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260727003035.118047-1-rosenp@gmail.com Signed-off-by: Johannes Berg --- .../wireless/marvell/mwifiex/11n_rxreorder.c | 20 +++---------------- drivers/net/wireless/marvell/mwifiex/main.h | 2 +- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/drivers/net/wireless/marvell/mwifiex/11n_rxreorder.c b/drivers/net/wireless/marvell/mwifiex/11n_rxreorder.c index 9deb47f22a61..06b005525078 100644 --- a/drivers/net/wireless/marvell/mwifiex/11n_rxreorder.c +++ b/drivers/net/wireless/marvell/mwifiex/11n_rxreorder.c @@ -213,7 +213,6 @@ mwifiex_del_rx_reorder_entry(struct mwifiex_private *priv, list_del(&tbl->list); spin_unlock_bh(&priv->rx_reorder_tbl_lock); - kfree(tbl->rx_reorder_ptr); kfree(tbl); spin_lock_bh(&priv->adapter->rx_proc_lock); @@ -329,7 +328,6 @@ static void mwifiex_11n_create_rx_reorder_tbl(struct mwifiex_private *priv, u8 *ta, int tid, int win_size, int seq_num) { - int i; struct mwifiex_rx_reorder_tbl *tbl, *new_node; u16 last_seq = 0; struct mwifiex_sta_node *node; @@ -344,10 +342,12 @@ mwifiex_11n_create_rx_reorder_tbl(struct mwifiex_private *priv, u8 *ta, return; } /* if !tbl then create one */ - new_node = kzalloc_obj(struct mwifiex_rx_reorder_tbl); + new_node = kzalloc_flex(*new_node, rx_reorder_ptr, win_size); if (!new_node) return; + new_node->win_size = win_size; + INIT_LIST_HEAD(&new_node->list); new_node->tid = tid; memcpy(new_node->ta, ta, ETH_ALEN); @@ -381,26 +381,12 @@ mwifiex_11n_create_rx_reorder_tbl(struct mwifiex_private *priv, u8 *ta, new_node->flags |= RXREOR_INIT_WINDOW_SHIFT; } - new_node->win_size = win_size; - - new_node->rx_reorder_ptr = kcalloc(win_size, sizeof(void *), - GFP_KERNEL); - if (!new_node->rx_reorder_ptr) { - kfree(new_node); - mwifiex_dbg(priv->adapter, ERROR, - "%s: failed to alloc reorder_ptr\n", __func__); - return; - } - new_node->timer_context.ptr = new_node; new_node->timer_context.priv = priv; new_node->timer_context.timer_is_set = false; timer_setup(&new_node->timer_context.timer, mwifiex_flush_data, 0); - for (i = 0; i < win_size; ++i) - new_node->rx_reorder_ptr[i] = NULL; - spin_lock_bh(&priv->rx_reorder_tbl_lock); list_add_tail(&new_node->list, &priv->rx_reorder_tbl_ptr); spin_unlock_bh(&priv->rx_reorder_tbl_lock); diff --git a/drivers/net/wireless/marvell/mwifiex/main.h b/drivers/net/wireless/marvell/mwifiex/main.h index 27559e2ddc31..8e6238c01aa6 100644 --- a/drivers/net/wireless/marvell/mwifiex/main.h +++ b/drivers/net/wireless/marvell/mwifiex/main.h @@ -708,10 +708,10 @@ struct mwifiex_rx_reorder_tbl { int init_win; int start_win; int win_size; - void **rx_reorder_ptr; struct reorder_tmr_cnxt timer_context; u8 amsdu; u8 flags; + struct sk_buff *rx_reorder_ptr[] __counted_by(win_size); }; struct mwifiex_bss_prio_node { From 87beac0a1d3d350c0c0c018967d991a2c2d1aecb Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sun, 10 May 2026 21:19:34 -0700 Subject: [PATCH 023/101] wifi: b43legacy: Use flexible array for DMA metadata Store the per-descriptor metadata in the DMA ring allocation instead of allocating it separately. This ties the metadata lifetime directly to the ring, removes a separate allocation failure path, and keeps the descriptor count available for __counted_by() bounds checking. Assisted-by: Codex:GPT-5.5 Signed-off-by: Rosen Penev Link: https://patch.msgid.link/20260511041934.378209-1-rosenp@gmail.com Signed-off-by: Johannes Berg --- drivers/net/wireless/broadcom/b43legacy/dma.c | 24 ++++++++----------- drivers/net/wireless/broadcom/b43legacy/dma.h | 4 ++-- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/drivers/net/wireless/broadcom/b43legacy/dma.c b/drivers/net/wireless/broadcom/b43legacy/dma.c index a9557356c9ab..6c3d847242bb 100644 --- a/drivers/net/wireless/broadcom/b43legacy/dma.c +++ b/drivers/net/wireless/broadcom/b43legacy/dma.c @@ -610,25 +610,25 @@ struct b43legacy_dmaring *b43legacy_setup_dmaring(struct b43legacy_wldev *dev, int nr_slots; dma_addr_t dma_test; - ring = kzalloc_obj(*ring); + if (for_tx) + nr_slots = B43legacy_TXRING_SLOTS; + else + nr_slots = B43legacy_RXRING_SLOTS; + + ring = kzalloc_flex(*ring, meta, nr_slots); if (!ring) goto out; + + ring->nr_slots = nr_slots; ring->type = type; ring->dev = dev; - nr_slots = B43legacy_RXRING_SLOTS; - if (for_tx) - nr_slots = B43legacy_TXRING_SLOTS; - - ring->meta = kzalloc_objs(struct b43legacy_dmadesc_meta, nr_slots); - if (!ring->meta) - goto err_kfree_ring; if (for_tx) { ring->txhdr_cache = kcalloc(nr_slots, sizeof(struct b43legacy_txhdr_fw3), GFP_KERNEL); if (!ring->txhdr_cache) - goto err_kfree_meta; + goto err_kfree_ring; /* test for ability to dma to txhdr_cache */ dma_test = dma_map_single(dev->dev->dma_dev, ring->txhdr_cache, @@ -643,7 +643,7 @@ struct b43legacy_dmaring *b43legacy_setup_dmaring(struct b43legacy_wldev *dev, sizeof(struct b43legacy_txhdr_fw3), GFP_KERNEL | GFP_DMA); if (!ring->txhdr_cache) - goto err_kfree_meta; + goto err_kfree_txhdr_cache; dma_test = dma_map_single(dev->dev->dma_dev, ring->txhdr_cache, @@ -660,7 +660,6 @@ struct b43legacy_dmaring *b43legacy_setup_dmaring(struct b43legacy_wldev *dev, DMA_TO_DEVICE); } - ring->nr_slots = nr_slots; ring->mmio_base = b43legacy_dmacontroller_base(type, controller_index); ring->index = controller_index; if (for_tx) { @@ -694,8 +693,6 @@ struct b43legacy_dmaring *b43legacy_setup_dmaring(struct b43legacy_wldev *dev, free_ringmemory(ring); err_kfree_txhdr_cache: kfree(ring->txhdr_cache); -err_kfree_meta: - kfree(ring->meta); err_kfree_ring: kfree(ring); ring = NULL; @@ -720,7 +717,6 @@ static void b43legacy_destroy_dmaring(struct b43legacy_dmaring *ring) free_ringmemory(ring); kfree(ring->txhdr_cache); - kfree(ring->meta); kfree(ring); } diff --git a/drivers/net/wireless/broadcom/b43legacy/dma.h b/drivers/net/wireless/broadcom/b43legacy/dma.h index b5c1a51db2a4..edd06225b64f 100644 --- a/drivers/net/wireless/broadcom/b43legacy/dma.h +++ b/drivers/net/wireless/broadcom/b43legacy/dma.h @@ -122,8 +122,6 @@ enum b43legacy_dmatype { struct b43legacy_dmaring { /* Kernel virtual base address of the ring memory. */ void *descbase; - /* Meta data about all descriptors. */ - struct b43legacy_dmadesc_meta *meta; /* Cache of TX headers for each slot. * This is to avoid an allocation on each TX. * This is NULL for an RX ring. @@ -161,6 +159,8 @@ struct b43legacy_dmaring { /* Last time we injected a ring overflow. */ unsigned long last_injected_overflow; #endif /* CONFIG_B43LEGACY_DEBUG*/ + /* Meta data about all descriptors. */ + struct b43legacy_dmadesc_meta meta[] __counted_by(nr_slots); }; From fa01779ff9916b7f3895954185af7c2e519c0435 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 31 Jul 2026 14:02:32 +0200 Subject: [PATCH 024/101] wifi: skip DUO params when looking for NPCA If DUO parameters are present, they need to be skipped. This was missed during the D1.3 -> D1.4 transition and causes frames to be mis-parsed for NPCA when DUO is also enabled. Link: https://patch.msgid.link/20260731140351.cb57484a68b1.I229dcd80b8e30c3fdfe8eedb81725dc35d9d9fe1@changeid Signed-off-by: Johannes Berg --- include/linux/ieee80211-uhr.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/include/linux/ieee80211-uhr.h b/include/linux/ieee80211-uhr.h index 665d4b3a5b41..398ce01fcdee 100644 --- a/include/linux/ieee80211-uhr.h +++ b/include/linux/ieee80211-uhr.h @@ -341,6 +341,9 @@ ieee80211_uhr_npca_info(const struct ieee80211_uhr_operation *oper) if (!(oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_NPCA_ENA))) return NULL; + if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_DUO_PRES)) + pos += 1; + if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_DPS_PRES)) pos += sizeof(struct ieee80211_uhr_dps_info); @@ -372,6 +375,9 @@ ieee80211_uhr_oper_dbe_info(const struct ieee80211_uhr_operation *oper) if (!(oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_DBE_ENA))) return NULL; + if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_DUO_PRES)) + pos += 1; + if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_DPS_PRES)) pos += sizeof(struct ieee80211_uhr_dps_info); From d25ba7909b2781c827f8b2928f201307c3ad6e44 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 31 Jul 2026 14:02:33 +0200 Subject: [PATCH 025/101] wifi: update UHR to Draft_P802.11bn D1.5 Update various definitions and in particular NPCA parsing to D1.5. Link: https://patch.msgid.link/20260731140351.0798bb37908d.I13592341f7f53f7566f9eeb74f0a0b85e73c1fef@changeid Signed-off-by: Johannes Berg --- include/linux/ieee80211-eht.h | 18 ++++++- include/linux/ieee80211-uhr.h | 95 +++++++++++++++++++---------------- net/wireless/chan.c | 11 ++-- 3 files changed, 75 insertions(+), 49 deletions(-) diff --git a/include/linux/ieee80211-eht.h b/include/linux/ieee80211-eht.h index b62297a978e7..a699d108eee6 100644 --- a/include/linux/ieee80211-eht.h +++ b/include/linux/ieee80211-eht.h @@ -482,6 +482,7 @@ struct ieee80211_multi_link_elem { #define IEEE80211_MLC_BASIC_PRES_MLD_ID 0x0200 #define IEEE80211_MLC_BASIC_PRES_EXT_MLD_CAPA_OP 0x0400 #define IEEE80211_MLC_BASIC_PRES_ENH_CRIT_UPD 0x0800 +#define IEEE80211_MLC_BASIC_PRES_AGE_OF_BSS_LOAD 0x1000 #define IEEE80211_MED_SYNC_DELAY_DURATION 0x00ff #define IEEE80211_MED_SYNC_DELAY_SYNC_OFDM_ED_THRESH 0x0f00 @@ -550,6 +551,8 @@ struct ieee80211_mle_basic_common_info { } __packed; #define IEEE80211_MLC_PREQ_PRES_MLD_ID 0x0010 +#define IEEE80211_MLC_PREQ_PRES_MLD_MAC_ADDR 0x0020 +#define IEEE80211_MLC_PREQ_PRES_NEIGH_AP_MLD_MAC_ADDR 0x0040 struct ieee80211_mle_preq_common_info { u8 len; @@ -560,6 +563,7 @@ struct ieee80211_mle_preq_common_info { #define IEEE80211_MLC_RECONF_PRES_EML_CAPA 0x0020 #define IEEE80211_MLC_RECONF_PRES_MLD_CAPA_OP 0x0040 #define IEEE80211_MLC_RECONF_PRES_EXT_MLD_CAPA_OP 0x0080 +#define IEEE80211_MLC_RECONF_PRES_TARGET_AP_MLD_ADDR 0x0100 /* no fixed fields in RECONF */ @@ -766,7 +770,7 @@ static inline u16 ieee80211_mle_get_mld_capa_op(const u8 *data) #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 */ +/* defined by UHR Draft P802.11bn_D1.5 Figure 9-1168 */ #define IEEE80211_UHR_ML_EXT_MLD_CAPA_ML_PM 0x0100 /** @@ -928,11 +932,17 @@ static inline bool ieee80211_mle_size_ok(const u8 *data, size_t len) common += 2; if (control & IEEE80211_MLC_BASIC_PRES_ENH_CRIT_UPD) common += 1; + if (control & IEEE80211_MLC_BASIC_PRES_AGE_OF_BSS_LOAD) + common += 1; break; case IEEE80211_ML_CONTROL_TYPE_PREQ: common += sizeof(struct ieee80211_mle_preq_common_info); if (control & IEEE80211_MLC_PREQ_PRES_MLD_ID) common += 1; + if (control & IEEE80211_MLC_PREQ_PRES_MLD_MAC_ADDR) + common += ETH_ALEN; + if (control & IEEE80211_MLC_PREQ_PRES_NEIGH_AP_MLD_MAC_ADDR) + common += ETH_ALEN; break; case IEEE80211_ML_CONTROL_TYPE_RECONF: common += 1; @@ -944,6 +954,8 @@ static inline bool ieee80211_mle_size_ok(const u8 *data, size_t len) common += 2; if (control & IEEE80211_MLC_RECONF_PRES_EXT_MLD_CAPA_OP) common += 2; + if (control & IEEE80211_MLC_RECONF_PRES_TARGET_AP_MLD_ADDR) + common += ETH_ALEN; break; case IEEE80211_ML_CONTROL_TYPE_TDLS: common += sizeof(struct ieee80211_mle_tdls_common_info); @@ -1003,6 +1015,7 @@ enum ieee80211_mle_subelems { #define IEEE80211_MLE_STA_CONTROL_BSS_PARAM_CHANGE_CNT_PRESENT 0x0800 #define IEEE80211_MLE_STA_CONTROL_ENH_CRIT_UPD_PRESENT 0x1000 #define IEEE80211_MLE_STA_CONTROL_AP_CONDUCTED_TX_PWR_PRESENT 0x2000 +#define IEEE80211_MLE_STA_CONTROL_AGE_OF_BSS_LOAD_PRESENT 0x4000 struct ieee80211_mle_per_sta_profile { __le16 control; @@ -1054,6 +1067,9 @@ static inline bool ieee80211_mle_basic_sta_prof_size_ok(const u8 *data, if (control & IEEE80211_MLE_STA_CONTROL_AP_CONDUCTED_TX_PWR_PRESENT) info_len += 1; + if (control & IEEE80211_MLE_STA_CONTROL_AGE_OF_BSS_LOAD_PRESENT) + info_len += 1; + return prof->sta_info_len >= info_len && fixed + prof->sta_info_len - 1 <= len; } diff --git a/include/linux/ieee80211-uhr.h b/include/linux/ieee80211-uhr.h index 398ce01fcdee..19e9260d0db8 100644 --- a/include/linux/ieee80211-uhr.h +++ b/include/linux/ieee80211-uhr.h @@ -17,32 +17,32 @@ #define IEEE80211_UHR_OPER_PARAMS_PEDCA_ENA 0x0004 #define IEEE80211_UHR_OPER_PARAMS_DBE_ENA 0x0008 #define IEEE80211_UHR_OPER_PARAMS_DBE_BW 0x0070 -#define IEEE80211_UHR_OPER_PARAMS_DUO_PRES 0x0080 -#define IEEE80211_UHR_OPER_PARAMS_DPS_PRES 0x0100 -#define IEEE80211_UHR_OPER_PARAMS_NPCA_PRES 0x0200 -#define IEEE80211_UHR_OPER_PARAMS_PEDCA_PRES 0x0400 -#define IEEE80211_UHR_OPER_PARAMS_DBE_PRES 0x0800 +#define IEEE80211_UHR_OPER_PARAMS_ELR_RX 0x0080 +#define IEEE80211_UHR_OPER_PARAMS_DUO_PRES 0x0100 +#define IEEE80211_UHR_OPER_PARAMS_DPS_PRES 0x0200 +#define IEEE80211_UHR_OPER_PARAMS_NPCA_PRES 0x0400 +#define IEEE80211_UHR_OPER_PARAMS_PEDCA_PRES 0x0800 +#define IEEE80211_UHR_OPER_PARAMS_DBE_PRES 0x1000 struct ieee80211_uhr_operation { __le16 params; - u8 basic_mcs_nss_set[4]; u8 variable[]; } __packed; -#define IEEE80211_UHR_NPCA_PARAMS_PRIMARY_CHAN_OFFS 0x0000000F -#define IEEE80211_UHR_NPCA_PARAMS_MIN_DUR_THRESH 0x000000F0 -#define IEEE80211_UHR_NPCA_PARAMS_SWITCH_DELAY 0x00003F00 -#define IEEE80211_UHR_NPCA_PARAMS_SWITCH_BACK_DELAY 0x000FC000 -#define IEEE80211_UHR_NPCA_PARAMS_INIT_QSRC 0x00300000 -#define IEEE80211_UHR_NPCA_PARAMS_MOPLEN 0x00400000 -#define IEEE80211_UHR_NPCA_PARAMS_DIS_SUBCH_BMAP_PRES 0x00800000 +#define IEEE80211_UHR_NPCA_PARAMS_PRIMARY_CHAN 0x000000FF +#define IEEE80211_UHR_NPCA_PARAMS_MIN_DUR_THRESH 0x00000F00 +#define IEEE80211_UHR_NPCA_PARAMS_SWITCH_DELAY 0x0003F000 +#define IEEE80211_UHR_NPCA_PARAMS_SWITCH_BACK_DELAY 0x00FC0000 +#define IEEE80211_UHR_NPCA_PARAMS_INIT_QSRC 0x03000000 +#define IEEE80211_UHR_NPCA_PARAMS_MOPLEN 0x04000000 +#define IEEE80211_UHR_NPCA_PARAMS_DIS_SUBCH_BMAP_PRES 0x08000000 /** * struct ieee80211_uhr_npca_info - npca operation information * * This structure is the "NPCA Operation Parameters field format" of "UHR - * Operation Element" fields as described in P802.11bn_D1.3 - * subclause 9.4.2.353. See Figure 9-aa4. + * Operation Element" fields as described in P802.11bn_D1.5 + * subclause 9.4.2.355.2, see Figure 9-aa6. * * Refer to IEEE80211_UHR_NPCA* * @params: @@ -97,7 +97,7 @@ struct ieee80211_uhr_npca_info { * struct ieee80211_uhr_dps_info - DPS operation information * * This structure is the "DPS Operation Parameter field" of "UHR - * Operation Element" fields as described in P802.11bn_D1.3 + * Operation Element" fields as described in P802.11bn_D1.5 * subclause 9.4.1.87. See Figure 9-207u. * * Refer to IEEE80211_UHR_DPS* @@ -211,8 +211,8 @@ static inline int ieee80211_uhr_dbe_bw_mhz(enum ieee80211_uhr_dbe_oper_bw bw) * struct ieee80211_uhr_dbe_info - DBE operation information * * This structure is the "DBE Operation Parameters field" of - * "UHR Operation Element" fields as described in P802.11bn_D1.3 - * subclause 9.4.2.353. See Figure 9-aa6. + * "UHR Operation Element" fields as described in P802.11bn_D1.5 + * subclause 9.4.2.355.4, see Figure 9-aa9. * * Refer to IEEE80211_UHR_DBE_OPER* * @params: @@ -234,25 +234,23 @@ struct ieee80211_uhr_dbe_info { __le16 dis_subch_bmap[]; } __packed; -#define IEEE80211_UHR_P_EDCA_ECWMIN 0x0F -#define IEEE80211_UHR_P_EDCA_ECWMAX 0xF0 -#define IEEE80211_UHR_P_EDCA_AIFSN 0x000F -#define IEEE80211_UHR_P_EDCA_CW_DS 0x0030 -#define IEEE80211_UHR_P_EDCA_PSRC_THRESHOLD 0x01C0 -#define IEEE80211_UHR_P_EDCA_QSRC_THRESHOLD 0x0600 +#define IEEE80211_UHR_P_EDCA_ECWMIN 0x0007 +#define IEEE80211_UHR_P_EDCA_ECWMAX 0x0038 +#define IEEE80211_UHR_P_EDCA_AIFSN 0x01C0 +#define IEEE80211_UHR_P_EDCA_CW_DS 0x0600 +#define IEEE80211_UHR_P_EDCA_PSRC_THRESHOLD 0x3800 +#define IEEE80211_UHR_P_EDCA_QSRC_THRESHOLD 0xC000 /** * struct ieee80211_uhr_p_edca_info - P-EDCA operation information * * This structure is the "P-EDCA Operation Parameters field" of - * "UHR Operation Element" fields as described in P802.11bn_D1.3 - * subclause 9.4.2.353. See Figure 9-aa5. + * "UHR Operation Element" fields as described in P802.11bn_D1.5 + * subclause 9.4.2.355.3, see Figure 9-aa8. * * Refer to IEEE80211_UHR_P_EDCA* - * @p_edca_ec: P-EDCA ECWmin and ECWmax. - * These fields indicate the CWmin and CWmax values used by a - * P-EDCA STA during P-EDCA contention. - * @params: AIFSN, CW DS, PSRC threshold, and QSRC threshold. + * @params: ECWmin, ECWmax, AIFSN, CW DS, PSRC threshold, and QSRC threshold. + * - CWmin/CWmax values used by a P-EDCA STA during P-EDCA contention. * - The AIFSN field indicates the AIFSN value used by a P-EDCA STA * during P-EDCA contention. * - The CW DS field indicates the value used for randomization of the @@ -266,7 +264,6 @@ struct ieee80211_uhr_dbe_info { * value 0 is reserved. */ struct ieee80211_uhr_p_edca_info { - u8 p_edca_ec; __le16 params; } __packed; @@ -302,7 +299,7 @@ static inline bool ieee80211_uhr_oper_size_ok(const u8 *data, u8 len) } } - /* P-EDCA Operation Parameters (fixed 3 bytes) */ + /* P-EDCA Operation Parameters */ if (oper->params & cpu_to_le16(IEEE80211_UHR_OPER_PARAMS_PEDCA_PRES)) { needed += sizeof(struct ieee80211_uhr_p_edca_info); if (len < needed) @@ -421,28 +418,42 @@ ieee80211_uhr_oper_dbe_info(const struct ieee80211_uhr_operation *oper) #define IEEE80211_UHR_MAC_CAP2_UHR_OM_PU_TO_LOW 0xC0 #define IEEE80211_UHR_MAC_CAP3_UHR_OM_PU_TO_HIGH 0x03 -#define IEEE80211_UHR_MAC_CAP3_PARAM_UPD_ADV_NOTIF_INTV 0x1C -#define IEEE80211_UHR_MAC_CAP3_UPD_IND_TIM_INTV_LOW 0xE0 +#define IEEE80211_UHR_MAC_CAP3_PARAM_UPD_ADV_NOTIF_INTV 0x7C +#define IEEE80211_UHR_MAC_CAP3_UPD_IND_TIM_INTV_LOW 0x80 -#define IEEE80211_UHR_MAC_CAP4_UPD_IND_TIM_INTV_HIGH 0x03 -#define IEEE80211_UHR_MAC_CAP4_BOUNDED_ESS 0x04 -#define IEEE80211_UHR_MAC_CAP4_BTM_ASSURANCE 0x08 -#define IEEE80211_UHR_MAC_CAP4_CO_BF_SUPP 0x10 +#define IEEE80211_UHR_MAC_CAP4_UPD_IND_TIM_INTV_HIGH 0x0F +#define IEEE80211_UHR_MAC_CAP4_BOUNDED_ESS 0x10 +#define IEEE80211_UHR_MAC_CAP4_BTM_ASSURANCE 0x20 +#define IEEE80211_UHR_MAC_CAP4_CO_BF_SUPP 0x40 +#define IEEE80211_UHR_MAC_CAP4_CO_SR_SUPP 0x80 + +#define IEEE80211_UHR_MAC_CAP5_MAPC_ENH_MSRMT_SUPP 0x01 #define IEEE80211_UHR_MAC_CAP_DBE_MAX_BW 0x07 #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::bwcap[].cap */ +#define IEEE80211_UHR_MAC_CAP_DBE_CAP_NUM_SND_DIMS 0x07 +#define IEEE80211_UHR_MAC_CAP_DBE_CAP_NON_OFDMA_UL_MUMIMO 0x08 +#define IEEE80211_UHR_MAC_CAP_DBE_CAP_MU_BEAMFORMER 0x10 +#define IEEE80211_UHR_MAC_CAP_DBE_CAP_BEAMFORMEE_SS 0xe0 + struct ieee80211_uhr_cap_dbe { u8 cap; + u8 max_switch_time_period; + u8 mode_change_intvl; /* present 0, 1 or 2 times depending on _PRES bits */ - struct ieee80211_eht_mcs_nss_supp_bw eht_mcs_map[]; + struct { + struct ieee80211_eht_mcs_nss_supp_bw eht_mcs_map; + u8 cap; + } __packed bwcap[]; } __packed; /** * enum ieee80211_uhr_dbe_max_supported_bw - DBE Maximum Supported Bandwidth * - * As per spec P802.11bn_D1.3 "Table 9-bb5—Encoding of the DBE Maximum + * As per spec P802.11bn_D1.5 Table 9-bb8 "Encoding of the DBE Maximum * Supported Bandwidth field". * * @IEEE80211_UHR_DBE_MAX_BW_40: Indicates 40 MHz DBE max supported bw @@ -526,10 +537,10 @@ static inline bool ieee80211_uhr_capa_size_ok(const u8 *data, u8 len, dbe = (const void *)cap->variable; if (dbe->cap & IEEE80211_UHR_MAC_CAP_DBE_EHT_MCS_MAP_160_PRES) - needed += sizeof(dbe->eht_mcs_map[0]); + needed += sizeof(dbe->bwcap[0]); if (dbe->cap & IEEE80211_UHR_MAC_CAP_DBE_EHT_MCS_MAP_320_PRES) - needed += sizeof(dbe->eht_mcs_map[0]); + needed += sizeof(dbe->bwcap[0]); } return len >= needed; diff --git a/net/wireless/chan.c b/net/wireless/chan.c index 7f6af1790736..1071e823108b 100644 --- a/net/wireless/chan.c +++ b/net/wireless/chan.c @@ -568,8 +568,8 @@ int cfg80211_chandef_add_npca(struct wiphy *wiphy, const struct ieee80211_uhr_npca_info *npca) { struct cfg80211_chan_def new_chandef = *chandef; - u32 width, npca_freq; - u8 offs; + u32 npca_freq; + u8 chan; if (chandef->npca_chan || chandef->npca_punctured) return -EINVAL; @@ -589,11 +589,10 @@ int cfg80211_chandef_add_npca(struct wiphy *wiphy, return -EINVAL; } - offs = le32_get_bits(npca->params, - IEEE80211_UHR_NPCA_PARAMS_PRIMARY_CHAN_OFFS); + chan = le32_get_bits(npca->params, + IEEE80211_UHR_NPCA_PARAMS_PRIMARY_CHAN); - width = cfg80211_chandef_get_width(chandef); - npca_freq = chandef->center_freq1 - width / 2 + 10 + 20 * offs; + npca_freq = ieee80211_channel_to_frequency(chan, chandef->chan->band); new_chandef.npca_chan = ieee80211_get_channel(wiphy, npca_freq); if (!new_chandef.npca_chan) return -EINVAL; From d59b9bf7df49367e01b06e82dd0f6e7ad3be493c Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Sun, 2 Aug 2026 10:57:18 +0200 Subject: [PATCH 026/101] wifi: mac80211: fix unauthorised port check for encap offload The check in ieee80211_tx_dequeue() reads frame_control, addr1 and addr2 out of the skb, but for encapsulation offload that's just a random part of the ethernet addresses, so dropping depends on bits in the destination address. This is obviously wrong. Refactor the check and short-circuit for ethernet format frames, it only needs the port-control flag check since those are locally generated and not forwarded frames. Fixes: 50ff477a8639 ("mac80211: add 802.11 encapsulation offloading support") Link: https://patch.msgid.link/20260802105818.dd5da579813d.I51ed0121154bb652bebc97bb0597b1fae9ac978f@changeid Signed-off-by: Johannes Berg --- net/mac80211/tx.c | 48 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index 3a1e2c9e1565..ee792b77a8c6 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -3877,6 +3877,38 @@ static bool ieee80211_xmit_fast(struct ieee80211_sub_if_data *sdata, return true; } +static bool ieee80211_drop_unauth_port(struct ieee80211_tx_data *tx) +{ + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(tx->skb); + struct ieee80211_hdr *hdr; + + if (likely(test_sta_flag(tx->sta, WLAN_STA_AUTHORIZED))) + return false; + + if (info->flags & IEEE80211_TX_CTL_INJECTED) + return false; + + if (ieee80211_vif_is_mesh(&tx->sdata->vif) || + tx->sdata->vif.type == NL80211_IFTYPE_OCB) + return false; + + /* no need to check forwarding here */ + if (info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP) + return !(info->control.flags & IEEE80211_TX_CTRL_PORT_CTRL_PROTO); + + hdr = (struct ieee80211_hdr *)tx->skb->data; + + if (!ieee80211_is_data_present(hdr->frame_control) || + is_multicast_ether_addr(hdr->addr1)) + return false; + + if ((info->control.flags & IEEE80211_TX_CTRL_PORT_CTRL_PROTO) && + ieee80211_is_our_addr(tx->sdata, hdr->addr2, NULL)) + return false; + + return true; +} + struct sk_buff *ieee80211_tx_dequeue(struct ieee80211_hw *hw, struct ieee80211_txq *txq) { @@ -3943,20 +3975,8 @@ struct sk_buff *ieee80211_tx_dequeue(struct ieee80211_hw *hw, if (txq->sta) { tx.sta = container_of(txq->sta, struct sta_info, sta); - /* - * Drop unicast frames to unauthorised stations unless they are - * injected frames or EAPOL frames from the local station. - */ - if (unlikely(!(info->flags & IEEE80211_TX_CTL_INJECTED) && - ieee80211_is_data_present(hdr->frame_control) && - !ieee80211_vif_is_mesh(&tx.sdata->vif) && - tx.sdata->vif.type != NL80211_IFTYPE_OCB && - !is_multicast_ether_addr(hdr->addr1) && - !test_sta_flag(tx.sta, WLAN_STA_AUTHORIZED) && - (!(info->control.flags & - IEEE80211_TX_CTRL_PORT_CTRL_PROTO) || - !ieee80211_is_our_addr(tx.sdata, hdr->addr2, - NULL)))) { + + if (unlikely(ieee80211_drop_unauth_port(&tx))) { I802_DEBUG_INC(local->tx_handlers_drop_unauth_port); ieee80211_free_txskb(&local->hw, skb); goto begin; From 0e80db08668b560b3522849cb705623148bcd4b5 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Sun, 2 Aug 2026 10:57:19 +0200 Subject: [PATCH 027/101] wifi: mac80211: fix key selection for encap offload frames ieee80211_tx_h_select_key() assumes the frame is in 802.11 format for picking the key, at least when there's no pairwise TK for the STA to transmit with. For unicast this is likely not relevant because if there's no pairwise TK for the STA there's probably not going to be any other key either. But multicast encapsulation offload frames have no STA, so it looks at the ethernet header as if it was 802.11. This may not matter very much since multicast encapsulation offload is likely to ignore the key selection, but it's still wrong. Create a separate selection function that doesn't (need to) look at the frame header - it's data anyway, so either unicast to a station (with sta pointer) or multicast. For MLO, the driver has to duplicate the frame and select the key anyway, so just skip it in that case entirely. Fixes: 50ff477a8639 ("mac80211: add 802.11 encapsulation offloading support") Link: https://patch.msgid.link/20260802105818.0feb7ad61047.Ia506ca211176a3c466bc60e4cc3e506df34a4a67@changeid Signed-off-by: Johannes Berg --- net/mac80211/tx.c | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index ee792b77a8c6..a94a5859fbdc 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -589,6 +589,34 @@ ieee80211_select_link_key(struct ieee80211_tx_data *tx) return NULL; } +/* + * An 802.3 frame is always a data frame, so there are no 802.11 addresses to + * look at: with a station the pairwise key is all that can apply, without one + * the frame is group addressed. On an MLD the driver has to pick the group key + * itself, as the same frame goes out on several links that each have their own. + */ +static ieee80211_tx_result +ieee80211_select_key_8023(struct ieee80211_tx_data *tx) +{ + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(tx->skb); + + if (unlikely(info->flags & IEEE80211_TX_INTFL_DONT_ENCRYPT)) + return TX_CONTINUE; + + if (tx->sta) { + tx->key = rcu_dereference(tx->sta->ptk[tx->sta->ptk_idx]); + if (!tx->key) + tx->key = rcu_dereference(tx->sdata->default_unicast_key); + } else if (!ieee80211_vif_is_mld(&tx->sdata->vif)) { + tx->key = rcu_dereference(tx->sdata->deflink.default_multicast_key); + } + + if (tx->key && tx->key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE) + info->control.hw_key = &tx->key->conf; + + return TX_CONTINUE; +} + static ieee80211_tx_result debug_noinline ieee80211_tx_h_select_key(struct ieee80211_tx_data *tx) { @@ -596,6 +624,9 @@ ieee80211_tx_h_select_key(struct ieee80211_tx_data *tx) struct ieee80211_tx_info *info = IEEE80211_SKB_CB(tx->skb); struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)tx->skb->data; + if (info->control.flags & IEEE80211_TX_CTL_HW_80211_ENCAP) + return ieee80211_select_key_8023(tx); + if (unlikely(info->flags & IEEE80211_TX_INTFL_DONT_ENCRYPT)) { tx->key = NULL; return TX_CONTINUE; @@ -612,12 +643,6 @@ ieee80211_tx_h_select_key(struct ieee80211_tx_data *tx) else tx->key = NULL; - if (info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP) { - if (tx->key && tx->key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE) - info->control.hw_key = &tx->key->conf; - return TX_CONTINUE; - } - if (tx->key) { bool skip_hw = false; From a48daf145fed445d41ef422573411500785415c8 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Sun, 2 Aug 2026 10:57:20 +0200 Subject: [PATCH 028/101] wifi: mac80211: don't send encap offload frames unencrypted For 802.11 frames, ieee80211_tx_h_select_key() drops a data frame that ended up without a key if the station requires encryption, so that traffic can never leak out unprotected. Encapsulation offload erroneously didn't get this code path. Fix that. Fixes: 50ff477a8639 ("mac80211: add 802.11 encapsulation offloading support") Link: https://patch.msgid.link/20260802085818.1336616-7-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- net/mac80211/tx.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index a94a5859fbdc..c6a4a30d95cc 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -607,6 +607,9 @@ ieee80211_select_key_8023(struct ieee80211_tx_data *tx) tx->key = rcu_dereference(tx->sta->ptk[tx->sta->ptk_idx]); if (!tx->key) tx->key = rcu_dereference(tx->sdata->default_unicast_key); + + if (!tx->key && test_sta_flag(tx->sta, WLAN_STA_USES_ENCRYPTION)) + return TX_DROP; } else if (!ieee80211_vif_is_mld(&tx->sdata->vif)) { tx->key = rcu_dereference(tx->sdata->deflink.default_multicast_key); } From c8ddbe1e8c43f6d008591c50f8be8de39cdce267 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Sun, 2 Aug 2026 10:57:21 +0200 Subject: [PATCH 029/101] wifi: mac80211: drop encap offload frames with a tainted key When a key is tainted, which happens in some WoWLAN and extended key ID scenarios, frames that would use it should be dropped. Fix that for encapsulation offload frames. Fixes: 50ff477a8639 ("mac80211: add 802.11 encapsulation offloading support") Link: https://patch.msgid.link/20260802105818.870b6ef31374.Ib87175b55b9fd94c16ba6d9cd816b7a09248df87@changeid Signed-off-by: Johannes Berg --- net/mac80211/tx.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index c6a4a30d95cc..8bfc99958927 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -614,7 +614,13 @@ ieee80211_select_key_8023(struct ieee80211_tx_data *tx) tx->key = rcu_dereference(tx->sdata->deflink.default_multicast_key); } - if (tx->key && tx->key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE) + if (!tx->key) + return TX_CONTINUE; + + if (unlikely(tx->key->flags & KEY_FLAG_TAINTED)) + return TX_DROP; + + if (tx->key->flags & KEY_FLAG_UPLOADED_TO_HARDWARE) info->control.hw_key = &tx->key->conf; return TX_CONTINUE; From 7056e1b5ff5abf5a89523ca8357ec669784c2fa1 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Sun, 2 Aug 2026 23:08:01 +0200 Subject: [PATCH 030/101] wifi: nl80211: don't export nl80211_mlo_reconf_add_done() This function is only used and only usable by cfg80211 itself, so don't export it. Link: https://patch.msgid.link/20260802210800.1733352-2-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- net/wireless/nl80211.c | 1 - 1 file changed, 1 deletion(-) diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 44f2bad08670..8f022bb1413c 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -21523,7 +21523,6 @@ void nl80211_mlo_reconf_add_done(struct net_device *dev, nl80211_send_mlme_event(rdev, dev, &event, GFP_KERNEL); } -EXPORT_SYMBOL(nl80211_mlo_reconf_add_done); void nl80211_send_ibss_bssid(struct cfg80211_registered_device *rdev, struct net_device *netdev, const u8 *bssid, From 0b25b927ce6b3cddfc0f2eb3bdf7ea7ae4e5bff3 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Sun, 2 Aug 2026 23:18:20 +0200 Subject: [PATCH 031/101] wifi: mac80211: remove ieee80211_sta_ps_transition() return value Nothing cares, so no point calculating it. Also simplify the "no need to do anything" check and fix some docs that should refer to this function instead. Link: https://patch.msgid.link/20260802211819.1738465-2-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- include/net/mac80211.h | 18 +++++------------- net/mac80211/rx.c | 8 +++----- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/include/net/mac80211.h b/include/net/mac80211.h index 9d1fac6e8082..638b8aa5214d 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -2919,8 +2919,8 @@ struct ieee80211_txq { * autonomously manages the PS status of connected stations. When * this flag is set mac80211 will not trigger PS mode for connected * stations based on the PM bit of incoming frames. - * Use ieee80211_start_ps()/ieee8021_end_ps() to manually configure - * the PS mode of connected stations. + * Use ieee80211_sta_ps_transition() to manually toggle the PS mode + * of connected stations. * * @IEEE80211_HW_TX_AMPDU_SETUP_IN_HW: The device handles TX A-MPDU session * setup strictly in HW. mac80211 should not attempt to do this in @@ -5499,10 +5499,8 @@ static inline void ieee80211_rx_ni(struct ieee80211_hw *hw, * * @sta: currently connected sta * @start: start or stop PS - * - * Return: 0 on success. -EINVAL when the requested PS mode is already set. */ -int ieee80211_sta_ps_transition(struct ieee80211_sta *sta, bool start); +void ieee80211_sta_ps_transition(struct ieee80211_sta *sta, bool start); /** * ieee80211_sta_ps_transition_ni - PS transition for connected sta @@ -5514,19 +5512,13 @@ int ieee80211_sta_ps_transition(struct ieee80211_sta *sta, bool start); * * @sta: currently connected sta * @start: start or stop PS - * - * Return: Like ieee80211_sta_ps_transition(). */ -static inline int ieee80211_sta_ps_transition_ni(struct ieee80211_sta *sta, +static inline void ieee80211_sta_ps_transition_ni(struct ieee80211_sta *sta, bool start) { - int ret; - local_bh_disable(); - ret = ieee80211_sta_ps_transition(sta, start); + ieee80211_sta_ps_transition(sta, start); local_bh_enable(); - - return ret; } /** diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c index 5e26be8e27d8..40ed03a775ba 100644 --- a/net/mac80211/rx.c +++ b/net/mac80211/rx.c @@ -1744,7 +1744,7 @@ static void sta_ps_end(struct sta_info *sta) ieee80211_sta_ps_deliver_wakeup(sta); } -int ieee80211_sta_ps_transition(struct ieee80211_sta *pubsta, bool start) +void ieee80211_sta_ps_transition(struct ieee80211_sta *pubsta, bool start) { struct sta_info *sta = container_of(pubsta, struct sta_info, sta); bool in_ps; @@ -1753,15 +1753,13 @@ int ieee80211_sta_ps_transition(struct ieee80211_sta *pubsta, bool start) /* Don't let the same PS state be set twice */ in_ps = test_sta_flag(sta, WLAN_STA_PS_STA); - if ((start && in_ps) || (!start && !in_ps)) - return -EINVAL; + if (start == in_ps) + return; if (start) sta_ps_start(sta); else sta_ps_end(sta); - - return 0; } EXPORT_SYMBOL(ieee80211_sta_ps_transition); From 8d1c320f615dac61240aee9c8a1fc6436ca0ca34 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Mon, 3 Aug 2026 13:52:48 +0200 Subject: [PATCH 032/101] wifi: mac80211: make ieee80211_is_tx_data() internal This function isn't used by any driver, and we may want to change it in the future. Don't expose it. Drivers may still do the same determination based on the flags, but if we'd ever change the encapsulation internally etc. (which I'm considering) then the use in mac80211 may need to change, or the function itself. Link: https://patch.msgid.link/20260803135248.cd23ccac65d7.I1c238c6abe39f5a1ff1b11d8556599d01dd03ff0@changeid Signed-off-by: Johannes Berg --- include/net/mac80211.h | 19 ------------------- net/mac80211/ieee80211_i.h | 9 +++++++++ 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/include/net/mac80211.h b/include/net/mac80211.h index 638b8aa5214d..f147bc019e7b 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -8050,25 +8050,6 @@ void ieee80211_obss_color_collision_notify(struct ieee80211_vif *vif, u64 color_bitmap, u8 link_id); -/** - * ieee80211_is_tx_data - check if frame is a data frame - * - * The function is used to check if a frame is a data frame. Frames with - * hardware encapsulation enabled are data frames. - * - * @skb: the frame to be transmitted. - * - * Return: %true if @skb is a data frame, %false otherwise - */ -static inline bool ieee80211_is_tx_data(struct sk_buff *skb) -{ - struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); - struct ieee80211_hdr *hdr = (void *) skb->data; - - return info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP || - ieee80211_is_data(hdr->frame_control); -} - /** * ieee80211_set_active_links - set active links in client mode * @vif: interface to set active links on diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index d06cb8e7dfb3..04954a963742 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -191,6 +191,15 @@ struct ieee80211_tx_data { unsigned int flags; }; +static inline bool ieee80211_is_tx_data(struct sk_buff *skb) +{ + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct ieee80211_hdr *hdr = (void *)skb->data; + + return info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP || + ieee80211_is_data(hdr->frame_control); +} + /** * enum ieee80211_packet_rx_flags - packet RX flags * @IEEE80211_RX_AMSDU: a-MSDU packet From e035900ffe9e8978f7c0d7b61811b6b578751697 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Mon, 3 Aug 2026 16:08:37 +0200 Subject: [PATCH 033/101] wifi: mac80211: fix IEEE80211_TX_CTL_REQ_TX_STATUS mixup In ieee80211_build_hdr(), IEEE80211_TX_CTL_REQ_TX_STATUS is checked in ctrl_flags, which is wrong - IEEE80211_TX_CTL_REQ_TX_STATUS is in info->flags, so the check is for IEEE80211_TX_CTRL_PORT_CTRL_PROTO instead. This works out OK because the only place setting _that_ at a point before this gets called is in fact ieee80211_tx_control_port() which was intended here. This is actually needed for correctness, if jt were checking info->flags instead, TDLS would fill/leak the status frames created here. However, we can just fix it by using the cookie instead, that'll be set for PREAUTH frames via ieee80211_tx_control_port() as well, and no other frames. Additionally, the code also erroneously set it, but that just caused preauth frames to get it set, since storing the ACK SKB will also set the correct flag. Drop this assignment entirely. Fixes: a7528198add8 ("mac80211: support control port TX status reporting") Link: https://patch.msgid.link/20260803141309.292711-10-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- net/mac80211/tx.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index 8bfc99958927..5891e1a8d71c 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -2979,8 +2979,7 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata, } if (unlikely(!multicast && - (sk_requests_wifi_status(skb->sk) || - ctrl_flags & IEEE80211_TX_CTL_REQ_TX_STATUS))) + (sk_requests_wifi_status(skb->sk) || cookie))) info_id = ieee80211_store_ack_skb(local, skb, &info_flags, cookie); @@ -6608,9 +6607,6 @@ int ieee80211_tx_control_port(struct wiphy *wiphy, struct net_device *dev, if (unencrypted) flags |= IEEE80211_TX_INTFL_DONT_ENCRYPT; - if (cookie) - ctrl_flags |= IEEE80211_TX_CTL_REQ_TX_STATUS; - flags |= IEEE80211_TX_INTFL_NL80211_FRAME_TX; skb = dev_alloc_skb(local->hw.extra_tx_headroom + From d5a014204a3bfaa7538205bdd8a78cfb7628e198 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Mon, 3 Aug 2026 16:08:38 +0200 Subject: [PATCH 034/101] wifi: mac80211: tx: simplify control port frame transmission ieee80211_tx_control_port() already does most of the necessary work, much of which is repeated by __ieee80211_subif_start_xmit(). Most work in the latter function is completely unnecessary since these are special frames we built directly here (long enough, etc.) Call ieee80211_build_hdr() and ieee80211_xmit() directly instead. It also means some errors can now be reported to userspace rather than frames silently getting dropped, but that's a good thing. Link: https://patch.msgid.link/20260803141309.292711-11-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- net/mac80211/tx.c | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index 5891e1a8d71c..c3337015c7b1 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -6650,9 +6650,6 @@ int ieee80211_tx_control_port(struct wiphy *wiphy, struct net_device *dev, skb_reset_network_header(skb); skb_reset_mac_header(skb); - if (local->hw.queues < IEEE80211_NUM_ACS) - goto start_xmit; - /* update QoS header to prioritize control port frames if possible, * prioritization also happens for control port frames send over * AF_PACKET @@ -6665,24 +6662,31 @@ int ieee80211_tx_control_port(struct wiphy *wiphy, struct net_device *dev, return err; } - if (!IS_ERR(sta)) { - u16 queue = ieee80211_select_queue(sdata, sta, skb); + if (IS_ERR(sta)) + sta = NULL; - skb_set_queue_mapping(skb, queue); + skb_set_queue_mapping(skb, ieee80211_select_queue(sdata, sta, skb)); - /* - * for MLO STA, the SA should be the AP MLD address, but - * the link ID has been selected already - */ - if (sta && sta->sta.mlo) - memcpy(ehdr->h_source, sdata->vif.addr, ETH_ALEN); + /* + * for MLO STA, the SA should be the AP MLD address, but + * the link ID has been selected already + */ + if (sta && sta->sta.mlo) + memcpy(ehdr->h_source, sdata->vif.addr, ETH_ALEN); + + skb = ieee80211_build_hdr(sdata, skb, flags, sta, ctrl_flags, cookie); + if (IS_ERR(skb)) { + rcu_read_unlock(); + return PTR_ERR(skb); } - rcu_read_unlock(); -start_xmit: + dev_sw_netstats_tx_add(dev, 1, skb->len); + ieee80211_tpt_led_trig_tx(local, skb->len); + local_bh_disable(); - __ieee80211_subif_start_xmit(skb, skb->dev, flags, ctrl_flags, cookie); + ieee80211_xmit(sdata, sta, skb); local_bh_enable(); + rcu_read_unlock(); return 0; } From ae29625022137547a7378bd2b4aba8d158a1410c Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Mon, 3 Aug 2026 16:08:39 +0200 Subject: [PATCH 035/101] wifi: mac80211: remove cookie from __ieee80211_subif_start_xmit() This argument is now always zero, so no longer used. Remove it. Link: https://patch.msgid.link/20260803141309.292711-12-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- net/mac80211/ieee80211_i.h | 3 +-- net/mac80211/tdls.c | 2 +- net/mac80211/tx.c | 21 ++++++++------------- 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 04954a963742..259d3735c792 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -2242,8 +2242,7 @@ netdev_tx_t ieee80211_subif_start_xmit_8023(struct sk_buff *skb, void __ieee80211_subif_start_xmit(struct sk_buff *skb, struct net_device *dev, u32 info_flags, - u32 ctrl_flags, - u64 cookie); + u32 ctrl_flags); struct sk_buff * ieee80211_build_data_template(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb, u32 info_flags); diff --git a/net/mac80211/tdls.c b/net/mac80211/tdls.c index dc2f662fe4c4..4f4cae9fd274 100644 --- a/net/mac80211/tdls.c +++ b/net/mac80211/tdls.c @@ -1121,7 +1121,7 @@ ieee80211_tdls_prep_mgmt_packet(struct wiphy *wiphy, struct net_device *dev, /* disable bottom halves when entering the Tx path */ local_bh_disable(); __ieee80211_subif_start_xmit(skb, dev, flags, - IEEE80211_TX_CTRL_MLO_LINK_UNSPEC, 0); + IEEE80211_TX_CTRL_MLO_LINK_UNSPEC); local_bh_enable(); return ret; diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index c3337015c7b1..8834e823344d 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -4421,8 +4421,7 @@ EXPORT_SYMBOL(ieee80211_txq_schedule_start); void __ieee80211_subif_start_xmit(struct sk_buff *skb, struct net_device *dev, u32 info_flags, - u32 ctrl_flags, - u64 cookie) + u32 ctrl_flags) { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ieee80211_local *local = sdata->local; @@ -4480,7 +4479,7 @@ void __ieee80211_subif_start_xmit(struct sk_buff *skb, ctrl_flags |= IEEE80211_TX_CTRL_SKIP_MPATH_LOOKUP; skb = ieee80211_build_hdr(sdata, skb, info_flags, - sta, ctrl_flags, cookie); + sta, ctrl_flags, 0); if (IS_ERR(skb)) { kfree_skb_list(next); goto out; @@ -4617,7 +4616,7 @@ static void ieee80211_mlo_multicast_tx_one(struct ieee80211_sub_if_data *sdata, return; ctrl_flags |= u32_encode_bits(link_id, IEEE80211_TX_CTRL_MLO_LINK); - __ieee80211_subif_start_xmit(out, sdata->dev, 0, ctrl_flags, 0); + __ieee80211_subif_start_xmit(out, sdata->dev, 0, ctrl_flags); } static void ieee80211_mlo_multicast_tx(struct net_device *dev, @@ -4632,7 +4631,7 @@ static void ieee80211_mlo_multicast_tx(struct net_device *dev, ctrl_flags |= u32_encode_bits(__ffs(links), IEEE80211_TX_CTRL_MLO_LINK); - __ieee80211_subif_start_xmit(skb, sdata->dev, 0, ctrl_flags, 0); + __ieee80211_subif_start_xmit(skb, sdata->dev, 0, ctrl_flags); return; } @@ -4673,8 +4672,7 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb, ieee80211_convert_to_unicast(skb, dev, &queue); while ((skb = __skb_dequeue(&queue))) __ieee80211_subif_start_xmit(skb, dev, 0, - IEEE80211_TX_CTRL_MLO_LINK_UNSPEC, - 0); + IEEE80211_TX_CTRL_MLO_LINK_UNSPEC); } else if (ieee80211_vif_is_mld(&sdata->vif) && ((sdata->vif.type == NL80211_IFTYPE_AP && !ieee80211_hw_check(&sdata->local->hw, MLO_MCAST_MULTI_LINK_TX)) || @@ -4684,8 +4682,7 @@ netdev_tx_t ieee80211_subif_start_xmit(struct sk_buff *skb, } else { normal: __ieee80211_subif_start_xmit(skb, dev, 0, - IEEE80211_TX_CTRL_MLO_LINK_UNSPEC, - 0); + IEEE80211_TX_CTRL_MLO_LINK_UNSPEC); } return NETDEV_TX_OK; @@ -4783,8 +4780,7 @@ static void ieee80211_8023_xmit(struct ieee80211_sub_if_data *sdata, if (!test_bit(HT_AGG_STATE_OPERATIONAL, &tid_tx->state)) { /* fall back to non-offload slow path */ __ieee80211_subif_start_xmit(skb, dev, 0, - IEEE80211_TX_CTRL_MLO_LINK_UNSPEC, - 0); + IEEE80211_TX_CTRL_MLO_LINK_UNSPEC); return; } @@ -6714,8 +6710,7 @@ int ieee80211_probe_mesh_link(struct wiphy *wiphy, struct net_device *dev, local_bh_disable(); __ieee80211_subif_start_xmit(skb, skb->dev, 0, - IEEE80211_TX_CTRL_SKIP_MPATH_LOOKUP, - 0); + IEEE80211_TX_CTRL_SKIP_MPATH_LOOKUP); local_bh_enable(); return 0; From 1ebbdad2b7986fc1f0af85858a9a78bdc94ee5da Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Mon, 3 Aug 2026 16:08:40 +0200 Subject: [PATCH 036/101] wifi: mac80211: unshare the skb before building the header ieee80211_build_hdr() unshares the skb halfway through, after it has already looked at it but before it writes anything. That's not a problem, but complicates the callers and requires passing the flags to the function, etc. Move the unsharing to the only caller that matters, the new one from the previous patch has a fresh SKB and doesn't need it and the TDLS callers via ieee80211_tdls_build_mgmt_packet_data() are also fresh SKBs. Link: https://patch.msgid.link/20260803141309.292711-13-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- net/mac80211/tx.c | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index 8834e823344d..8bbefe6c8036 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -2671,9 +2671,8 @@ static void ieee80211_remove_ack_skb(struct ieee80211_local *local, u16 info_id) * the appropriate IEEE 802.11 header based on which interface the packet is * being transmitted on. * - * Note that this function also takes care of the TX status request and - * potential unsharing of the SKB - this needs to be interleaved with the - * header building. + * Note that this function also takes care of the TX status request. The skb + * must not be shared. * * The function requires the read-side RCU lock held * @@ -2983,15 +2982,6 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata, info_id = ieee80211_store_ack_skb(local, skb, &info_flags, cookie); - /* - * If the skb is shared we need to obtain our own copy. - */ - skb = skb_share_check(skb, GFP_ATOMIC); - if (unlikely(!skb)) { - ret = -ENOMEM; - goto free; - } - hdr.frame_control = fc; hdr.duration_id = 0; hdr.seq_ctrl = 0; @@ -4478,6 +4468,12 @@ void __ieee80211_subif_start_xmit(struct sk_buff *skb, if (skb->protocol == sdata->control_port_protocol) ctrl_flags |= IEEE80211_TX_CTRL_SKIP_MPATH_LOOKUP; + skb = skb_share_check(skb, GFP_ATOMIC); + if (unlikely(!skb)) { + kfree_skb_list(next); + goto out; + } + skb = ieee80211_build_hdr(sdata, skb, info_flags, sta, ctrl_flags, 0); if (IS_ERR(skb)) { From 78b843974fb83ba5a64f5cd0dad434458c52b717 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Mon, 3 Aug 2026 16:08:41 +0200 Subject: [PATCH 037/101] wifi: mac80211: report multicast transmission from lookup_ra_sta() ieee80211_lookup_ra_sta() returns ERR_PTR(-ENOENT) both when the 802.11 frame will be is group addressed and when it's going to an unknown station. Keep reporting an ERR_PTR() for group addressed but set to NULL for individually addressed frames so the callers can use it to distinguish later. This is already explicitly the case for all interface types except mesh, and callers already tolerate (but don't distinguish) both cases. Also change the ieee80211_lookup_ra_sta() to be static. Link: https://patch.msgid.link/20260803141309.292711-14-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- net/mac80211/ieee80211_i.h | 5 ----- net/mac80211/tx.c | 19 +++++++++++++++---- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 259d3735c792..53b0b08d3459 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -2479,11 +2479,6 @@ static inline bool ieee80211_require_encrypted_assoc(__le16 fc, return sta && sta->sta.epp_peer && ieee80211_is_assoc(fc); } -/* sta_out needs to be checked for ERR_PTR() before using */ -int ieee80211_lookup_ra_sta(struct ieee80211_sub_if_data *sdata, - struct sk_buff *skb, - struct sta_info **sta_out); - static inline void ieee80211_tx_skb_tid_band(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb, int tid, diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index 8bbefe6c8036..33391a7824b7 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -2538,9 +2538,16 @@ static inline bool ieee80211_is_tdls_setup(struct sk_buff *skb) skb->data[14] == WLAN_TDLS_SNAP_RFTYPE; } -int ieee80211_lookup_ra_sta(struct ieee80211_sub_if_data *sdata, - struct sk_buff *skb, - struct sta_info **sta_out) +/* + * Returns an error if the frame should be dropped, otherwise + * the *sta_out pointer is filled: + * - valid sta pointer: frame goes to that station + * - NULL: frame will be unicast to a yet unknown station + * - ERR_PTR(-ENOENT): frame will be group addressed + */ +static int ieee80211_lookup_ra_sta(struct ieee80211_sub_if_data *sdata, + struct sk_buff *skb, + struct sta_info **sta_out) { struct sta_info *sta; @@ -2565,6 +2572,10 @@ int ieee80211_lookup_ra_sta(struct ieee80211_sub_if_data *sdata, break; #ifdef CONFIG_MAC80211_MESH case NL80211_IFTYPE_MESH_POINT: + if (is_multicast_ether_addr(skb->data)) { + *sta_out = ERR_PTR(-ENOENT); + return 0; + } /* determined much later */ *sta_out = NULL; return 0; @@ -2607,7 +2618,7 @@ int ieee80211_lookup_ra_sta(struct ieee80211_sub_if_data *sdata, return -EINVAL; } - *sta_out = sta ?: ERR_PTR(-ENOENT); + *sta_out = sta; return 0; } From 8516aa4f05b2680c7d66bc766207460ac0f5de27 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Mon, 3 Aug 2026 16:08:42 +0200 Subject: [PATCH 038/101] wifi: mac80211: set flags/ctrl_flags before ieee80211_build_hdr() ieee80211_build_hdr() takes the info and control flags to use as arguments, but that leads to some messy code and missed cleanup. Fill them in the SKB before calling ieee80211_build_hdr(). Link: https://patch.msgid.link/20260803141309.292711-15-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- net/mac80211/tx.c | 60 +++++++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index 33391a7824b7..8526c6fa49b8 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -2673,9 +2673,7 @@ static void ieee80211_remove_ack_skb(struct ieee80211_local *local, u16 info_id) * ieee80211_build_hdr - build 802.11 header in the given frame * @sdata: virtual interface to build the header for * @skb: the skb to build the header in - * @info_flags: skb flags to set * @sta: the station pointer - * @ctrl_flags: info control flags to set * @cookie: cookie pointer to fill (if not %NULL) * * This function takes the skb with 802.3 header and reformats the header to @@ -2683,19 +2681,20 @@ static void ieee80211_remove_ack_skb(struct ieee80211_local *local, u16 info_id) * being transmitted on. * * Note that this function also takes care of the TX status request. The skb - * must not be shared. + * must not be shared, and its TX info must already be initialised - this + * function only fills in what it determines itself. * * The function requires the read-side RCU lock held * * Returns: the (possibly reallocated) skb or an ERR_PTR() code */ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata, - struct sk_buff *skb, u32 info_flags, - struct sta_info *sta, u32 ctrl_flags, + struct sk_buff *skb, + struct sta_info *sta, u64 cookie) { + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); struct ieee80211_local *local = sdata->local; - struct ieee80211_tx_info *info; int head_need; u16 ethertype, hdrlen, meshhdrlen = 0; __le16 fc; @@ -2711,14 +2710,15 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata, struct ieee80211_chanctx_conf *chanctx_conf = NULL; enum nl80211_band band; int ret; - u8 link_id = u32_get_bits(ctrl_flags, IEEE80211_TX_CTRL_MLO_LINK); + u8 link_id = u32_get_bits(info->control.flags, + IEEE80211_TX_CTRL_MLO_LINK); if (IS_ERR(sta)) sta = NULL; #ifdef CONFIG_MAC80211_DEBUGFS if (local->force_tx_status) - info_flags |= IEEE80211_TX_CTL_REQ_TX_STATUS; + info->flags |= IEEE80211_TX_CTL_REQ_TX_STATUS; #endif /* convert Ethernet header to proper 802.11 header (based on @@ -2859,7 +2859,7 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata, /* For injected frames, fill RA right away as nexthop lookup * will be skipped. */ - if ((ctrl_flags & IEEE80211_TX_CTRL_SKIP_MPATH_LOOKUP) && + if ((info->control.flags & IEEE80211_TX_CTRL_SKIP_MPATH_LOOKUP) && is_zero_ether_addr(hdr.addr1)) memcpy(hdr.addr1, skb->data, ETH_ALEN); break; @@ -2990,7 +2990,7 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata, if (unlikely(!multicast && (sk_requests_wifi_status(skb->sk) || cookie))) - info_id = ieee80211_store_ack_skb(local, skb, &info_flags, + info_id = ieee80211_store_ack_skb(local, skb, &info->flags, cookie); hdr.frame_control = fc; @@ -3061,10 +3061,6 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata, skb_reset_mac_header(skb); - info = IEEE80211_SKB_CB(skb); - memset(info, 0, sizeof(*info)); - - info->flags = info_flags; if (info_id) { info->status_data = info_id; info->status_data_idr = 1; @@ -3072,18 +3068,18 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata, info->band = band; if (likely(!cookie)) { - ctrl_flags |= u32_encode_bits(link_id, - IEEE80211_TX_CTRL_MLO_LINK); + info->control.flags |= + u32_encode_bits(link_id, IEEE80211_TX_CTRL_MLO_LINK); } else { unsigned int pre_conf_link_id; /* - * ctrl_flags already have been set by + * The link ID already has been set by * ieee80211_tx_control_port(), here * we just sanity check that */ - pre_conf_link_id = u32_get_bits(ctrl_flags, + pre_conf_link_id = u32_get_bits(info->control.flags, IEEE80211_TX_CTRL_MLO_LINK); if (pre_conf_link_id != link_id && @@ -3098,8 +3094,6 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata, } } - info->control.flags = ctrl_flags; - return skb; free: if (info_id) @@ -4426,6 +4420,7 @@ void __ieee80211_subif_start_xmit(struct sk_buff *skb, { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ieee80211_local *local = sdata->local; + struct ieee80211_tx_info *info; struct sta_info *sta; struct sk_buff *next; int len = skb->len; @@ -4485,8 +4480,12 @@ void __ieee80211_subif_start_xmit(struct sk_buff *skb, goto out; } - skb = ieee80211_build_hdr(sdata, skb, info_flags, - sta, ctrl_flags, 0); + info = IEEE80211_SKB_CB(skb); + memset(info, 0, sizeof(*info)); + info->flags = info_flags; + info->control.flags = ctrl_flags; + + skb = ieee80211_build_hdr(sdata, skb, sta, 0); if (IS_ERR(skb)) { kfree_skb_list(next); goto out; @@ -4955,6 +4954,7 @@ ieee80211_build_data_template(struct ieee80211_sub_if_data *sdata, .local = sdata->local, .sdata = sdata, }; + struct ieee80211_tx_info *info; struct sta_info *sta; rcu_read_lock(); @@ -4965,8 +4965,12 @@ ieee80211_build_data_template(struct ieee80211_sub_if_data *sdata, goto out; } - skb = ieee80211_build_hdr(sdata, skb, info_flags, sta, - IEEE80211_TX_CTRL_MLO_LINK_UNSPEC, 0); + info = IEEE80211_SKB_CB(skb); + memset(info, 0, sizeof(*info)); + info->flags = info_flags; + info->control.flags = IEEE80211_TX_CTRL_MLO_LINK_UNSPEC; + + skb = ieee80211_build_hdr(sdata, skb, sta, 0); if (IS_ERR(skb)) goto out; @@ -6586,6 +6590,7 @@ int ieee80211_tx_control_port(struct wiphy *wiphy, struct net_device *dev, { struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ieee80211_local *local = sdata->local; + struct ieee80211_tx_info *info; struct sta_info *sta; struct sk_buff *skb; struct ethhdr *ehdr; @@ -6677,7 +6682,12 @@ int ieee80211_tx_control_port(struct wiphy *wiphy, struct net_device *dev, if (sta && sta->sta.mlo) memcpy(ehdr->h_source, sdata->vif.addr, ETH_ALEN); - skb = ieee80211_build_hdr(sdata, skb, flags, sta, ctrl_flags, cookie); + info = IEEE80211_SKB_CB(skb); + memset(info, 0, sizeof(*info)); + info->flags = flags; + info->control.flags = ctrl_flags; + + skb = ieee80211_build_hdr(sdata, skb, sta, cookie); if (IS_ERR(skb)) { rcu_read_unlock(); return PTR_ERR(skb); From a64a20fb270f244a25c16c184c015b104278a06f Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Mon, 3 Aug 2026 16:08:43 +0200 Subject: [PATCH 039/101] wifi: mac80211: store the ack skb before building the header The ack skb has to be stored while the frame is still in 802.3 format, since that is what is reported back to userspace. It's currently done in ieee80211_build_hdr(), but that complicates it a bit. Thanks the the previous ieee80211_lookup_ra_sta() change, it's now known whether or not the frame will be group addressed on the air, which is needed for this condition. Also simplify callers of ieee80211_store_ack_skb() by filling the TX info directly and fix the free in ieee80211_build_hdr() to use ieee80211_free_txskb(), reporting the drop to userspace rather than being quiet about it. This also lets use validate the link ID for control port TX in that code itself, rather than interleaving it into build_hdr(). Link: https://patch.msgid.link/20260803141309.292711-16-johannes@sipsolutions.net Signed-off-by: Johannes Berg --- net/mac80211/tx.c | 116 ++++++++++++++-------------------------------- 1 file changed, 36 insertions(+), 80 deletions(-) diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index 8526c6fa49b8..7d34b160fddd 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -2622,13 +2622,11 @@ static int ieee80211_lookup_ra_sta(struct ieee80211_sub_if_data *sdata, return 0; } -static u16 ieee80211_store_ack_skb(struct ieee80211_local *local, - struct sk_buff *skb, - u32 *info_flags, - u64 cookie) +static void ieee80211_store_ack_skb(struct ieee80211_local *local, + struct sk_buff *skb, u64 cookie) { + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); struct sk_buff *ack_skb; - u16 info_id = 0; if (skb->sk) ack_skb = skb_clone_sk(skb); @@ -2645,28 +2643,15 @@ static u16 ieee80211_store_ack_skb(struct ieee80211_local *local, spin_unlock_irqrestore(&local->ack_status_lock, flags); if (id >= 0) { - info_id = id; - *info_flags |= IEEE80211_TX_CTL_REQ_TX_STATUS; + info->status_data = id; + info->status_data_idr = 1; + info->flags |= IEEE80211_TX_CTL_REQ_TX_STATUS; if (cookie) IEEE80211_SKB_CB(ack_skb)->ack.cookie = cookie; } else { kfree_skb(ack_skb); } } - - return info_id; -} - -static void ieee80211_remove_ack_skb(struct ieee80211_local *local, u16 info_id) -{ - struct sk_buff *ack_skb; - unsigned long flags; - - spin_lock_irqsave(&local->ack_status_lock, flags); - ack_skb = idr_remove(&local->ack_status_frames, info_id); - spin_unlock_irqrestore(&local->ack_status_lock, flags); - - kfree_skb(ack_skb); } /** @@ -2674,7 +2659,6 @@ static void ieee80211_remove_ack_skb(struct ieee80211_local *local, u16 info_id) * @sdata: virtual interface to build the header for * @skb: the skb to build the header in * @sta: the station pointer - * @cookie: cookie pointer to fill (if not %NULL) * * This function takes the skb with 802.3 header and reformats the header to * the appropriate IEEE 802.11 header based on which interface the packet is @@ -2690,8 +2674,7 @@ static void ieee80211_remove_ack_skb(struct ieee80211_local *local, u16 info_id) */ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb, - struct sta_info *sta, - u64 cookie) + struct sta_info *sta) { struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); struct ieee80211_local *local = sdata->local; @@ -2706,7 +2689,6 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata, bool wme_sta = false, authorized = false; bool tdls_peer; bool multicast; - u16 info_id = 0; struct ieee80211_chanctx_conf *chanctx_conf = NULL; enum nl80211_band band; int ret; @@ -2988,11 +2970,6 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata, goto free; } - if (unlikely(!multicast && - (sk_requests_wifi_status(skb->sk) || cookie))) - info_id = ieee80211_store_ack_skb(local, skb, &info->flags, - cookie); - hdr.frame_control = fc; hdr.duration_id = 0; hdr.seq_ctrl = 0; @@ -3031,8 +3008,6 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata, head_need += local->tx_headroom; head_need = max_t(int, 0, head_need); if (ieee80211_skb_resize(sdata, skb, head_need, ENCRYPT_DATA)) { - ieee80211_free_txskb(&local->hw, skb); - skb = NULL; ret = -ENOMEM; goto free; } @@ -3061,44 +3036,14 @@ static struct sk_buff *ieee80211_build_hdr(struct ieee80211_sub_if_data *sdata, skb_reset_mac_header(skb); - if (info_id) { - info->status_data = info_id; - info->status_data_idr = 1; - } info->band = band; - if (likely(!cookie)) { - info->control.flags |= - u32_encode_bits(link_id, IEEE80211_TX_CTRL_MLO_LINK); - } else { - unsigned int pre_conf_link_id; - - /* - * The link ID already has been set by - * ieee80211_tx_control_port(), here - * we just sanity check that - */ - - pre_conf_link_id = u32_get_bits(info->control.flags, - IEEE80211_TX_CTRL_MLO_LINK); - - if (pre_conf_link_id != link_id && - link_id != IEEE80211_LINK_UNSPECIFIED) { -#ifdef CONFIG_MAC80211_VERBOSE_DEBUG - net_info_ratelimited("%s: dropped frame to %pM with bad link ID request (%d vs. %d)\n", - sdata->name, hdr.addr1, - pre_conf_link_id, link_id); -#endif - ret = -EINVAL; - goto free; - } - } - + info->control.flags = + u32_replace_bits(info->control.flags, link_id, + IEEE80211_TX_CTRL_MLO_LINK); return skb; free: - if (info_id) - ieee80211_remove_ack_skb(local, info_id); - kfree_skb(skb); + ieee80211_free_txskb(&local->hw, skb); return ERR_PTR(ret); } @@ -4423,6 +4368,7 @@ void __ieee80211_subif_start_xmit(struct sk_buff *skb, struct ieee80211_tx_info *info; struct sta_info *sta; struct sk_buff *next; + bool group_addressed; int len = skb->len; if (unlikely(!ieee80211_sdata_running(sdata) || skb->len < ETH_HLEN)) { @@ -4442,6 +4388,7 @@ void __ieee80211_subif_start_xmit(struct sk_buff *skb, if (ieee80211_lookup_ra_sta(sdata, skb, &sta)) goto out_free; + group_addressed = IS_ERR(sta); if (IS_ERR(sta)) sta = NULL; @@ -4485,7 +4432,11 @@ void __ieee80211_subif_start_xmit(struct sk_buff *skb, info->flags = info_flags; info->control.flags = ctrl_flags; - skb = ieee80211_build_hdr(sdata, skb, sta, 0); + if (unlikely(!group_addressed && + sk_requests_wifi_status(skb->sk))) + ieee80211_store_ack_skb(local, skb, 0); + + skb = ieee80211_build_hdr(sdata, skb, sta); if (IS_ERR(skb)) { kfree_skb_list(next); goto out; @@ -4820,12 +4771,8 @@ static void ieee80211_8023_xmit(struct ieee80211_sub_if_data *sdata, memcpy(IEEE80211_SKB_CB(seg), info, sizeof(*info)); } - if (unlikely(sk_requests_wifi_status(skb->sk))) { - info->status_data = ieee80211_store_ack_skb(local, skb, - &info->flags, 0); - if (info->status_data) - info->status_data_idr = 1; - } + if (unlikely(sk_requests_wifi_status(skb->sk))) + ieee80211_store_ack_skb(local, skb, 0); dev_sw_netstats_tx_add(dev, skbs, len); @@ -4970,7 +4917,7 @@ ieee80211_build_data_template(struct ieee80211_sub_if_data *sdata, info->flags = info_flags; info->control.flags = IEEE80211_TX_CTRL_MLO_LINK_UNSPEC; - skb = ieee80211_build_hdr(sdata, skb, sta, 0); + skb = ieee80211_build_hdr(sdata, skb, sta); if (IS_ERR(skb)) goto out; @@ -6591,6 +6538,7 @@ int ieee80211_tx_control_port(struct wiphy *wiphy, struct net_device *dev, struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev); struct ieee80211_local *local = sdata->local; struct ieee80211_tx_info *info; + bool group_addressed; struct sta_info *sta; struct sk_buff *skb; struct ethhdr *ehdr; @@ -6631,15 +6579,11 @@ int ieee80211_tx_control_port(struct wiphy *wiphy, struct net_device *dev, /* we may override the SA for MLO STA later */ if (link_id < 0) { - ctrl_flags |= u32_encode_bits(IEEE80211_LINK_UNSPECIFIED, - IEEE80211_TX_CTRL_MLO_LINK); + link_id = IEEE80211_LINK_UNSPECIFIED; memcpy(ehdr->h_source, sdata->vif.addr, ETH_ALEN); } else { struct ieee80211_bss_conf *link_conf; - ctrl_flags |= u32_encode_bits(link_id, - IEEE80211_TX_CTRL_MLO_LINK); - rcu_read_lock(); link_conf = rcu_dereference(sdata->vif.link_conf[link_id]); if (!link_conf) { @@ -6670,6 +6614,7 @@ int ieee80211_tx_control_port(struct wiphy *wiphy, struct net_device *dev, return err; } + group_addressed = IS_ERR(sta); if (IS_ERR(sta)) sta = NULL; @@ -6685,14 +6630,25 @@ int ieee80211_tx_control_port(struct wiphy *wiphy, struct net_device *dev, info = IEEE80211_SKB_CB(skb); memset(info, 0, sizeof(*info)); info->flags = flags; + ctrl_flags |= u32_encode_bits(link_id, IEEE80211_TX_CTRL_MLO_LINK); info->control.flags = ctrl_flags; - skb = ieee80211_build_hdr(sdata, skb, sta, cookie); + if (!group_addressed) + ieee80211_store_ack_skb(local, skb, cookie); + + skb = ieee80211_build_hdr(sdata, skb, sta); if (IS_ERR(skb)) { rcu_read_unlock(); return PTR_ERR(skb); } + if (link_id != u32_get_bits(info->control.flags, + IEEE80211_TX_CTRL_MLO_LINK)) { + ieee80211_free_txskb(&local->hw, skb); + rcu_read_unlock(); + return -EINVAL; + } + dev_sw_netstats_tx_add(dev, 1, skb->len); ieee80211_tpt_led_trig_tx(local, skb->len); From 102f54dc47be198e9310b18d41305a78d70a0796 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Pouiller?= Date: Tue, 1 Sep 2026 10:33:19 +0200 Subject: [PATCH 040/101] wifi: wfx: fix possible device hang during init MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The WFxxx device provides an SDIO interface. In addition to the usual in-band SDIO IRQ, the device also supports out-of-band IRQ line. OOB IRQ requires a specific initialization described in commit f00dc1d789e1c ("staging: wfx: poll IRQ during init"): When the chip starts in SDIO mode, the external IRQ (aka Out-Of-Band IRQ) cannot be used before to configure it. Therefore, the first exchanges with the chip have to be done without the OOB IRQ. This patch allow to poll the data until the OOB IRQ is correctly setup. In order to keep the code simpler, this patch also poll data even if OOB IRQ is not used. Commit 57aa557f110d9 ("staging: wfx: introduce a way to poll IRQ") announce some limitation about the data polling mechanism: This function [wfx_bh_poll_irq()] must used with care: if an IRQ fires while the host reads control register, the IRQ can be lost. However, with last analysis, it seems it does not only impact the IRQ. When this condition happen the hardware is lost and won't reply the next commands. The only way to recover the error is to pull the reset pin and bind the device again. Currently wfx_bh_poll_irq() is only used during the two first exchanges with the hardware. Therefore, it has been reported to only happen after a few hours of bind/rebind in a loop. This explain why it has not been reported before. Fortunately, wfx_bh_poll_irq() is only required for OOB IRQ, which is probably very marginal. The In-Band IRQ case also uses this function, but only to simplify the code. This patch limits the use of wfx_bh_poll_irq() to the OOB IRQ. The bus driver now tells wfx_probe() whether polling is necessary. When it is not (in-band SDIO IRQ and SPI), the IRQ is subscribed before the first exchange with the device and wfx_bh_poll_irq() is never called. When it is (SDIO with OOB IRQ), the behavior is unchanged: the control register is polled until the PDS file has configured the IRQ line. To conclude, In-Band users are now safe. Users of OOB IRQ will be still impacted by the bug, but there is nothing we can do (and nobody complained during the last 6 years). Fixes: f00dc1d789e1c ("staging: wfx: poll IRQ during init") Reported-by: Gerard Salvatella Reported-by: Lukas Stockmann Signed-off-by: JĆ©rĆ“me Pouiller Reviewed-by: Alexander Sverdlin Tested-by: Alexander Sverdlin Link: https://patch.msgid.link/20260901083548.113131-2-jerome.pouiller@silabs.com Signed-off-by: Johannes Berg --- drivers/net/wireless/silabs/wfx/bh.c | 6 ++-- drivers/net/wireless/silabs/wfx/bus_sdio.c | 1 + drivers/net/wireless/silabs/wfx/main.c | 34 ++++++++++++++-------- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/drivers/net/wireless/silabs/wfx/bh.c b/drivers/net/wireless/silabs/wfx/bh.c index 21dfdcf9cc27..0071659bc9c9 100644 --- a/drivers/net/wireless/silabs/wfx/bh.c +++ b/drivers/net/wireless/silabs/wfx/bh.c @@ -284,10 +284,10 @@ void wfx_bh_request_tx(struct wfx_dev *wdev) } /* If IRQ is not available, this function allow to manually poll the control register and simulate - * an IRQ ahen an event happened. + * an IRQ when an event happened. * - * Note that the device has a bug: If an IRQ raise while host read control register, the IRQ is - * lost. So, use this function carefully (only duing device initialisation). + * Note that the device has a bug: If an IRQ raise while host read control register, the device is + * lost. Unfortunately, this is the only way to initialize the SDIO with out-of-band IRQ. */ void wfx_bh_poll_irq(struct wfx_dev *wdev) { diff --git a/drivers/net/wireless/silabs/wfx/bus_sdio.c b/drivers/net/wireless/silabs/wfx/bus_sdio.c index ab0793b9908f..1ca2cc262feb 100644 --- a/drivers/net/wireless/silabs/wfx/bus_sdio.c +++ b/drivers/net/wireless/silabs/wfx/bus_sdio.c @@ -279,6 +279,7 @@ static int wfx_sdio_probe(struct sdio_func *func, const struct sdio_device_id *i goto sdio_release; } + bus->core->poll_irq = !!bus->of_irq; ret = wfx_probe(bus->core); if (ret) goto sdio_release; diff --git a/drivers/net/wireless/silabs/wfx/main.c b/drivers/net/wireless/silabs/wfx/main.c index dda36e41eed1..4e99fe7e5bb7 100644 --- a/drivers/net/wireless/silabs/wfx/main.c +++ b/drivers/net/wireless/silabs/wfx/main.c @@ -362,7 +362,6 @@ int wfx_probe(struct wfx_dev *wdev) */ gpio_saved = wdev->pdata.gpio_wakeup; wdev->pdata.gpio_wakeup = NULL; - wdev->poll_irq = true; wdev->bh_wq = alloc_workqueue("wfx_bh_wq", WQ_HIGHPRI | WQ_PERCPU, 0); if (!wdev->bh_wq) @@ -370,16 +369,24 @@ int wfx_probe(struct wfx_dev *wdev) wfx_bh_register(wdev); + if (!wdev->poll_irq) { + err = wdev->hwbus_ops->irq_subscribe(wdev->hwbus_priv); + if (err) + goto bh_unregister; + } + err = wfx_init_device(wdev); if (err) - goto bh_unregister; + goto irq_unsubscribe; + + if (wdev->poll_irq) + wfx_bh_poll_irq(wdev); - wfx_bh_poll_irq(wdev); err = wait_for_completion_timeout(&wdev->firmware_ready, 1 * HZ); if (err == 0) { dev_err(wdev->dev, "timeout while waiting for startup indication\n"); err = -ETIMEDOUT; - goto bh_unregister; + goto irq_unsubscribe; } /* FIXME: fill wiphy::hw_version */ @@ -399,12 +406,12 @@ int wfx_probe(struct wfx_dev *wdev) dev_err(wdev->dev, "unsupported firmware API version (expect 1 while firmware returns %d)\n", wdev->hw_caps.api_version_major); err = -EOPNOTSUPP; - goto bh_unregister; + goto irq_unsubscribe; } if (wdev->hw_caps.link_mode == SEC_LINK_ENFORCED) { dev_err(wdev->dev, "chip require secure_link, but can't negotiate it\n"); - goto bh_unregister; + goto irq_unsubscribe; } if (wdev->hw_caps.region_sel_mode) { @@ -420,12 +427,14 @@ int wfx_probe(struct wfx_dev *wdev) dev_dbg(wdev->dev, "sending configuration file %s\n", wdev->pdata.file_pds); err = wfx_send_pdata_pds(wdev); if (err < 0 && err != -ENOENT) - goto bh_unregister; + goto irq_unsubscribe; - wdev->poll_irq = false; - err = wdev->hwbus_ops->irq_subscribe(wdev->hwbus_priv); - if (err) - goto bh_unregister; + if (wdev->poll_irq) { + err = wdev->hwbus_ops->irq_subscribe(wdev->hwbus_priv); + if (err) + goto bh_unregister; + wdev->poll_irq = false; + } err = wfx_hif_use_multi_tx_conf(wdev, true); if (err) @@ -474,7 +483,8 @@ int wfx_probe(struct wfx_dev *wdev) ieee80211_unregister: ieee80211_unregister_hw(wdev->hw); irq_unsubscribe: - wdev->hwbus_ops->irq_unsubscribe(wdev->hwbus_priv); + if (!wdev->poll_irq) + wdev->hwbus_ops->irq_unsubscribe(wdev->hwbus_priv); bh_unregister: wfx_bh_unregister(wdev); destroy_workqueue(wdev->bh_wq); From d8c297ec6bb4aef2dd57e851a9b18607e72ad9a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Pouiller?= Date: Tue, 1 Sep 2026 10:33:20 +0200 Subject: [PATCH 041/101] dt-bindings: net: wireless: wfx: discourage OOB IRQ with SDIO MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The WFxxx device provides an SDIO interface. In addition to the usual in-band SDIO IRQ, the device also supports an out-of-band IRQ line. However, using the OOB IRQ with SDIO requires to poll the control register during the device initialization. This polling is unreliable: if an IRQ is raised while the host reads the control register, the device is lost and only a reset allows to recover it. There is no way to fix this in software. So, warn the user that this setup should be reserved to debug. Signed-off-by: JĆ©rĆ“me Pouiller Acked-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260901083548.113131-3-jerome.pouiller@silabs.com Signed-off-by: Johannes Berg --- Documentation/devicetree/bindings/net/wireless/silabs,wfx.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/net/wireless/silabs,wfx.yaml b/Documentation/devicetree/bindings/net/wireless/silabs,wfx.yaml index 6c0888ae4c4e..60dfe257eadc 100644 --- a/Documentation/devicetree/bindings/net/wireless/silabs,wfx.yaml +++ b/Documentation/devicetree/bindings/net/wireless/silabs,wfx.yaml @@ -50,6 +50,9 @@ properties: used, this property is required. When SDIO is used, the "in-band" interrupt provided by the SDIO bus is used unless an interrupt is defined in the Device Tree. + + The hardware suffers from a limitation when this property is used with + SDIO. OOB IRQ with SDIO should only be used for debug. maxItems: 1 reset-gpios: From 5cf9d60d0e2fdf4310fc4687a6791bb1cdf7fa82 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Thu, 27 Aug 2026 20:58:48 +0300 Subject: [PATCH 042/101] dt-bindings: net: wireless: add flag marvell,invalid-reg-hint-in-rom WRT3200ACM and WRT32x routers always report a fixed US regulatory domain from the mwifiex ROM, which conflicts with the platform regulatory configuration on units sold outside the US market. For example: - phy0 mwlwifi 5 GHz, hard-coded region 98 EU mapped to FR - phy1 mwlwifi 2.4 GHz, hard-coded region 98 EU mapped to FR - phy2 mwifiex 2.4 and 5 GHz, hard-coded region US When the system boots, it detects a conflict between the user selected region and the radios, e.g. BG FR US, and applies extreme restrictions, preventing phy0 from starting on any DFS channel. phy2 works. Add a device-tree flag, which allows affected devices, where the radio conflicts with the platform to ignore the incorrect regulatory hint in the ROM of the radio, so the platform regulatory domain can be used. This does not allow the user to change the regulatory configuration of any radio. It only allows phy0 to start and operate according to its certified configuration. [1] https://github.com/kaloz/mwlwifi/issues/173#issuecomment-307879699 [2] https://github.com/openwrt/openwrt/issues/9956 Signed-off-by: Georgi Valkov Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260827175850.89395-1-gvalkov@gmail.com Signed-off-by: Johannes Berg --- .../devicetree/bindings/net/wireless/marvell,sd8787.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Documentation/devicetree/bindings/net/wireless/marvell,sd8787.yaml b/Documentation/devicetree/bindings/net/wireless/marvell,sd8787.yaml index 930b700b73d0..9f905ef5c2c0 100644 --- a/Documentation/devicetree/bindings/net/wireless/marvell,sd8787.yaml +++ b/Documentation/devicetree/bindings/net/wireless/marvell,sd8787.yaml @@ -59,6 +59,12 @@ properties: description: Calibration data for sub-band 3 in the 5GHz band. maxItems: 502 + marvell,invalid-reg-hint-in-rom: + $ref: /schemas/types.yaml#/definitions/flag + description: + The regulatory hint in ROM conflicts with platform configuration. + It should be ignored, so the platform regulatory domain can be used. + marvell,wakeup-pin: $ref: /schemas/types.yaml#/definitions/uint32 description: From b4572cc9483054dbe0371768bf4075ab4b6c2168 Mon Sep 17 00:00:00 2001 From: Georgi Valkov Date: Thu, 27 Aug 2026 20:58:49 +0300 Subject: [PATCH 043/101] wifi: mwifiex: add dt flag to avoid conflict with platform reg domain WRT3200ACM and WRT32x routers always report a fixed US regulatory domain from the mwifiex ROM, which conflicts with the platform regulatory configuration on units sold outside the US market. For example: - phy0 mwlwifi 5 GHz, hard-coded region 98 EU mapped to FR - phy1 mwlwifi 2.4 GHz, hard-coded region 98 EU mapped to FR - phy2 mwifiex 2.4 and 5 GHz, hard-coded region US When the system boots, it detects a conflict between the user selected region and the radios, e.g. BG FR US, and applies extreme restrictions, preventing phy0 from starting on any DFS channel. phy2 works. Add a device-tree flag, which allows affected devices, where the radio conflicts with the platform to ignore the incorrect regulatory hint in the ROM of the radio, so the platform regulatory domain can be used. This does not allow the user to change the regulatory configuration of any radio. It only allows phy0 to start and operate according to its certified configuration. The change affects only platforms which explicitly define this flag, and helps overcome the incorrect behaviour of proprietary radio firmware. The change has been tested on OpenWrt and fixes the issue where phy0 does not start on WRT3200ACM-EU when a DFS channel is used [1] https://github.com/kaloz/mwlwifi/issues/173#issuecomment-307879699 [2] https://github.com/openwrt/openwrt/issues/9956 Signed-off-by: Georgi Valkov Link: https://patch.msgid.link/20260827175850.89395-2-gvalkov@gmail.com Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/mwifiex/cfg80211.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/net/wireless/marvell/mwifiex/cfg80211.c b/drivers/net/wireless/marvell/mwifiex/cfg80211.c index 7a1ba32f1fb3..293876397b67 100644 --- a/drivers/net/wireless/marvell/mwifiex/cfg80211.c +++ b/drivers/net/wireless/marvell/mwifiex/cfg80211.c @@ -4897,7 +4897,14 @@ int mwifiex_register_cfg80211(struct mwifiex_adapter *adapter) country_code = mwifiex_11d_code_2_region( adapter->region_code); + + /* If the reg hint in ROM conflicts with platform + * configuration, it should be ignored, so the + * platform regulatory domain can be used. + */ if (country_code && + !device_property_read_bool(adapter->dev, + "marvell,invalid-reg-hint-in-rom") && regulatory_hint(wiphy, country_code)) mwifiex_dbg(priv->adapter, ERROR, "regulatory_hint() failed\n"); From f2e8260ad4093f9921b07686eb5cc027c38b1c7d Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 30 Aug 2026 20:48:10 +0800 Subject: [PATCH 044/101] wifi: libertas_tf: validate firmware block extents check_fwfile_format() reads each block header and trusts its data length before proving that both fit in the firmware image. A truncated header or oversized block can therefore pass malformed extents to the download path. Require the remaining image to contain the complete header and payload before accepting each block. Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260830124810.97223-1-pengpeng@iscas.ac.cn Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/libertas_tf/if_usb.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/marvell/libertas_tf/if_usb.c b/drivers/net/wireless/marvell/libertas_tf/if_usb.c index b85c6d783bf7..b9d1a33bddfc 100644 --- a/drivers/net/wireless/marvell/libertas_tf/if_usb.c +++ b/drivers/net/wireless/marvell/libertas_tf/if_usb.c @@ -769,10 +769,18 @@ static int check_fwfile_format(const u8 *data, u32 totlen) exit = len = 0; do { - struct fwheader *fwh = (void *) data; + struct fwheader *fwh; + + if (totlen - len < sizeof(*fwh)) + break; + + fwh = (void *)data; bincmd = le32_to_cpu(fwh->dnldcmd); blksize = le32_to_cpu(fwh->datalength); + if (blksize > totlen - len - sizeof(*fwh)) + break; + switch (bincmd) { case FW_HAS_DATA_TO_RECV: offset = sizeof(struct fwheader) + blksize; From c7fee4aaaf9294ef9f86eb69464e89fe096ba5db Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 30 Aug 2026 22:04:20 +0800 Subject: [PATCH 045/101] wifi: libertas: validate firmware block extents check_fwfile_format() reads each block header and trusts its data length before proving that both fit in the firmware image. A truncated header or oversized block can therefore pass malformed extents to the download path. Require the remaining image to contain the complete header and payload before accepting each block. Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260830140420.26399-1-pengpeng@iscas.ac.cn Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/libertas/if_usb.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/marvell/libertas/if_usb.c b/drivers/net/wireless/marvell/libertas/if_usb.c index 5cc0c5cac257..74870c9bd091 100644 --- a/drivers/net/wireless/marvell/libertas/if_usb.c +++ b/drivers/net/wireless/marvell/libertas/if_usb.c @@ -798,10 +798,18 @@ static int check_fwfile_format(const uint8_t *data, uint32_t totlen) exit = len = 0; do { - struct fwheader *fwh = (void *)data; + struct fwheader *fwh; + + if (totlen - len < sizeof(*fwh)) + break; + + fwh = (void *)data; bincmd = le32_to_cpu(fwh->dnldcmd); blksize = le32_to_cpu(fwh->datalength); + if (blksize > totlen - len - sizeof(*fwh)) + break; + switch (bincmd) { case FW_HAS_DATA_TO_RECV: offset = sizeof(struct fwheader) + blksize; From 25f3f2ff88ae604a3b473b501fc55d58fcb0f5eb Mon Sep 17 00:00:00 2001 From: Julius Bairaktaris Date: Fri, 4 Sep 2026 11:06:26 +0200 Subject: [PATCH 046/101] wifi: mac80211: fix swapped fq_overlimit/fq_overmemory in aqm debugfs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The two scnprintf arguments for fq_overlimit and fq_overmemory are in the wrong order, which results in the aqm debugfs file showing the overmemory count under the fq_overlimit label and vice versa. Fixes: 2a4e675d887b ("mac80211: Export fq memory limit information in debugfs") Reviewed-by: Toke HĆøiland-JĆørgensen Assisted-by: Claude:claude-opus-5 Signed-off-by: Julius Bairaktaris Link: https://patch.msgid.link/20260904090626.92727-1-julius@bairaktaris.de Signed-off-by: Johannes Berg --- net/mac80211/debugfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/mac80211/debugfs.c b/net/mac80211/debugfs.c index 105653a16b68..8946b027782c 100644 --- a/net/mac80211/debugfs.c +++ b/net/mac80211/debugfs.c @@ -96,8 +96,8 @@ static ssize_t aqm_read(struct file *file, "RW fq_quantum %u\n", fq->flows_cnt, fq->backlog, - fq->overmemory, fq->overlimit, + fq->overmemory, fq->collisions, fq->memory_usage, fq->memory_limit, From 01c15a8b936d319f3daf7ffaddfe44ca5bd40504 Mon Sep 17 00:00:00 2001 From: Manish Dharanenthiran Date: Wed, 5 Aug 2026 14:56:02 +0530 Subject: [PATCH 047/101] wifi: nl80211: add support to configure 6 GHz non-HT duplicate transmission As per IEEE Std 802.11-2024, subclause 26.17.2.2, a 6 GHz AP can transmit a Beacon frame using a non-HT duplicate PPDU, so that stations scanning only PSC channels can discover it. Currently, there is no mechanism to request the driver to transmit non-HT duplicate Beacon frames. In [1], changes were made to parse the Beacon from user-space to set the non-HT duplicate flag in the kernel. However, it was suggested that non-HT transmission be part of the Beacon TX rate settings. Hence, add NL80211_TXRATE_6GHZ_NON_HT_DUP as a flag attribute under NL80211_ATTR_TX_RATES to let user-space request the driver to transmit non-HT duplicate Beacons. Also add validation to reject the Beacon if non-HT duplicate Beacon transmission is enabled when legacy rates are not configured. [1] https://lore.kernel.org/all/1644914581-21682-1-git-send-email-quic_ramess@quicinc.com/ Signed-off-by: Manish Dharanenthiran Link: https://patch.msgid.link/20260805-dup-beacon-v1-1-68574869c896@oss.qualcomm.com Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 1 + include/uapi/linux/nl80211.h | 4 ++++ net/wireless/nl80211.c | 13 +++++++++++++ 3 files changed, 18 insertions(+) diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 2abf9e20cc22..6461a7bd0169 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -900,6 +900,7 @@ struct cfg80211_bitrate_mask { enum nl80211_eht_gi eht_gi; enum nl80211_he_ltf he_ltf; enum nl80211_eht_ltf eht_ltf; + bool nonht_dup_6ghz; } control[NUM_NL80211_BANDS]; }; diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 020387d76412..d8fefefc11a9 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -5900,6 +5900,9 @@ enum nl80211_key_attributes { * see &struct nl80211_txrate_eht * @NL80211_TXRATE_EHT_GI: configure EHT GI, (u8, see &enum nl80211_eht_gi) * @NL80211_TXRATE_EHT_LTF: configure EHT LTF, (u8, see &enum nl80211_eht_ltf) + * @NL80211_TXRATE_6GHZ_NON_HT_DUP: configure 6 GHz non-HT duplicate Beacon + * transmission. This flag is applicable only in Beacon TX rate setting + * and must be accompanied by a non-HT (legacy) Beacon rate. * @__NL80211_TXRATE_AFTER_LAST: internal * @NL80211_TXRATE_MAX: highest TX rate attribute */ @@ -5915,6 +5918,7 @@ enum nl80211_tx_rate_attributes { NL80211_TXRATE_EHT, NL80211_TXRATE_EHT_GI, NL80211_TXRATE_EHT_LTF, + NL80211_TXRATE_6GHZ_NON_HT_DUP, /* keep last */ __NL80211_TXRATE_AFTER_LAST, diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 8f022bb1413c..e585da1d79c9 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -569,6 +569,7 @@ static const struct nla_policy nl80211_txattr_policy[NL80211_TXRATE_MAX + 1] = { [NL80211_TXRATE_EHT_LTF] = NLA_POLICY_RANGE(NLA_U8, NL80211_RATE_INFO_EHT_1XLTF, NL80211_RATE_INFO_EHT_8XLTF), + [NL80211_TXRATE_6GHZ_NON_HT_DUP] = { .type = NLA_FLAG }, }; @@ -6303,6 +6304,14 @@ static int nl80211_parse_tx_bitrate_mask(struct genl_info *info, mask->control[band].eht_ltf = nla_get_u8(tb[NL80211_TXRATE_EHT_LTF]); + if (tb[NL80211_TXRATE_6GHZ_NON_HT_DUP]) { + if (band != NL80211_BAND_6GHZ || + (wdev->iftype != NL80211_IFTYPE_AP && + wdev->iftype != NL80211_IFTYPE_P2P_GO)) + return -EINVAL; + mask->control[band].nonht_dup_6ghz = true; + } + if (mask->control[band].legacy == 0) { /* don't allow empty legacy rates if HT, VHT, HE or EHT * are not even supported. @@ -6344,6 +6353,7 @@ static int validate_beacon_tx_rate(struct cfg80211_registered_device *rdev, { u32 count_ht, count_vht, count_he, count_eht, i; u32 rate = beacon_rate->control[band].legacy; + bool nonht_dup = beacon_rate->control[band].nonht_dup_6ghz; /* Allow only one rate */ if (hweight32(rate) > 1) @@ -6405,6 +6415,9 @@ static int validate_beacon_tx_rate(struct cfg80211_registered_device *rdev, (!rate && !count_ht && !count_vht && !count_he && !count_eht)) return -EINVAL; + if (nonht_dup && !rate) + return -EINVAL; + if (rate && !wiphy_ext_feature_isset(&rdev->wiphy, NL80211_EXT_FEATURE_BEACON_RATE_LEGACY)) From 9d1365e96a920bf79974161e370b5640a0054461 Mon Sep 17 00:00:00 2001 From: Mariano Baragiola Date: Mon, 17 Aug 2026 20:35:21 -0300 Subject: [PATCH 048/101] wifi: mac80211: avoid trimming injected FCS twice When packet sockets use PACKET_QDISC_BYPASS, mac80211 can parse the radiotap header while selecting the queue and again before transmit. If the header includes an FCS, both parses trim the skb. Validate the FCS on both parses, but trim it only before transmit. This keeps queue selection read-only and avoids trimming shared skb data twice. Fixes: cb17ed29a7a5 ("mac80211: parse radiotap header when selecting Tx queue") Signed-off-by: Mariano Baragiola Link: https://patch.msgid.link/20260817233521.2747027-1-mbaragiola@linux.com Signed-off-by: Johannes Berg --- include/net/mac80211.h | 3 ++- net/mac80211/iface.c | 2 +- net/mac80211/tx.c | 7 ++++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/include/net/mac80211.h b/include/net/mac80211.h index f147bc019e7b..7bb4618065b6 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -7630,11 +7630,12 @@ bool ieee80211_tx_prepare_skb(struct ieee80211_hw *hw, * * @skb: packet injected by userspace * @dev: the &struct device of this 802.11 device + * @trim_fcs: trim the FCS trailer when present; validate it otherwise * * Return: %true if the radiotap header was parsed, %false otherwise */ bool ieee80211_parse_tx_radiotap(struct sk_buff *skb, - struct net_device *dev); + struct net_device *dev, bool trim_fcs); /** * struct ieee80211_noa_data - holds temporary data for tracking P2P NoA state diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index 43460a705a6b..fdac729ca580 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c @@ -964,7 +964,7 @@ static u16 ieee80211_monitor_select_queue(struct net_device *dev, /* reset flags and info before parsing radiotap header */ memset(info, 0, sizeof(*info)); - if (!ieee80211_parse_tx_radiotap(skb, dev)) + if (!ieee80211_parse_tx_radiotap(skb, dev, false)) return 0; /* doesn't matter, frame will be dropped */ len_rthdr = ieee80211_get_radiotap_len(skb->data); diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index 7d34b160fddd..c33092960df2 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -2138,7 +2138,7 @@ static bool ieee80211_validate_radiotap_len(struct sk_buff *skb) } bool ieee80211_parse_tx_radiotap(struct sk_buff *skb, - struct net_device *dev) + struct net_device *dev, bool trim_fcs) { struct ieee80211_local *local = wdev_priv(dev->ieee80211_ptr); struct ieee80211_radiotap_iterator iterator; @@ -2195,7 +2195,8 @@ bool ieee80211_parse_tx_radiotap(struct sk_buff *skb, if (skb->len < (iterator._max_length + FCS_LEN)) return false; - skb_trim(skb, skb->len - FCS_LEN); + if (trim_fcs) + skb_trim(skb, skb->len - FCS_LEN); } if (*iterator.this_arg & IEEE80211_RADIOTAP_F_WEP) info->flags &= ~IEEE80211_TX_INTFL_DONT_ENCRYPT; @@ -2511,7 +2512,7 @@ netdev_tx_t ieee80211_monitor_start_xmit(struct sk_buff *skb, * selected chandef above to accurately set injection rates and * retransmissions. */ - if (!ieee80211_parse_tx_radiotap(skb, dev)) + if (!ieee80211_parse_tx_radiotap(skb, dev, true)) goto fail_rcu; /* remove the injection radiotap header */ From 1b60ed34f712e9f606d80951f1586f4274ebadf1 Mon Sep 17 00:00:00 2001 From: Zhiling Zou Date: Mon, 3 Aug 2026 12:07:40 +0800 Subject: [PATCH 049/101] wifi: mac80211: serialize debugfs netdev rename with recreate ieee80211_debugfs_rename_netdev() renames sdata->vif.debugfs_dir without sharing synchronization with ieee80211_debugfs_recreate_netdev(). When MLO link updates switch an interface to or from MLO, the recreate path can remove and rebuild that dentry while a concurrent netdev rename still uses the stale pointer. Serialize the rename helper with the same wiphy mutex that protects the recreate path so the dentry cannot be removed while debugfs_change_name() is operating on it. Reported-by: Vega Signed-off-by: Zhiling Zou Link: https://patch.msgid.link/76eecaf74ca2d4e4aedf46c3ee7505917433468b.1785729184.git.zhilinz@nebusec.ai Signed-off-by: Johannes Berg --- net/mac80211/debugfs_netdev.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/net/mac80211/debugfs_netdev.c b/net/mac80211/debugfs_netdev.c index f3c6a41e4911..10f8d77852a1 100644 --- a/net/mac80211/debugfs_netdev.c +++ b/net/mac80211/debugfs_netdev.c @@ -1024,7 +1024,13 @@ void ieee80211_debugfs_remove_netdev(struct ieee80211_sub_if_data *sdata) void ieee80211_debugfs_rename_netdev(struct ieee80211_sub_if_data *sdata) { - debugfs_change_name(sdata->vif.debugfs_dir, "netdev:%s", sdata->name); + struct dentry *dir; + + wiphy_lock(sdata->local->hw.wiphy); + dir = sdata->vif.debugfs_dir; + if (dir) + debugfs_change_name(dir, "netdev:%s", sdata->name); + wiphy_unlock(sdata->local->hw.wiphy); } void ieee80211_debugfs_recreate_netdev(struct ieee80211_sub_if_data *sdata, From 5f2530a4df8e0573e54cc0f4175c06ecb810a4dd Mon Sep 17 00:00:00 2001 From: Bert Karwatzki Date: Tue, 8 Sep 2026 11:43:40 +0200 Subject: [PATCH 050/101] mac80211: tx: Use info->flags in ieee80211_tx_h_select_key() Without this ieee80211_select_key_8023() is never called and wifi never connects. Fixes: 0e80db08668b ("wifi: mac80211: fix key selection for encap offload frames") Signed-off-by: Bert Karwatzki Link: https://patch.msgid.link/20260908094341.12196-1-spasswolf@web.de Signed-off-by: Johannes Berg --- net/mac80211/tx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index c33092960df2..fd7981466137 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -633,7 +633,7 @@ ieee80211_tx_h_select_key(struct ieee80211_tx_data *tx) struct ieee80211_tx_info *info = IEEE80211_SKB_CB(tx->skb); struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)tx->skb->data; - if (info->control.flags & IEEE80211_TX_CTL_HW_80211_ENCAP) + if (info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP) return ieee80211_select_key_8023(tx); if (unlikely(info->flags & IEEE80211_TX_INTFL_DONT_ENCRYPT)) { From 13e51269b6656768dc595ed6025e4974f2026543 Mon Sep 17 00:00:00 2001 From: Stanislaw Gruszka Date: Tue, 8 Sep 2026 14:46:51 +0200 Subject: [PATCH 051/101] wifi: mac80211: queue frames while off-channel Drivers without FAST_XMIT and hardware scan support currently drop TX frames during software scanning while being off-channel. This happens in ieee80211_tx_h_check_assoc() before frames reach internal TX queue. Move the off-channel check to invoke_tx_handlers_late(), after ieee80211_queue_skb(). Frames can then remain queued and be transmitted when the interface returns on-channel. This how TX works currently when code goes via ieee80211_xmit_fast() path. Below is rt2x00 driver ping output example while scanning, before and after the fix. After the fix, packets are no longer lost. Some have increased latency, what is expected behavior. BEFORE: 64 bytes from 192.168.0.1: icmp_seq=29 ttl=64 time=3.61 ms 64 bytes from 192.168.0.1: icmp_seq=31 ttl=64 time=3.33 ms # Missed 30 64 bytes from 192.168.0.1: icmp_seq=32 ttl=64 time=4.03 ms 64 bytes from 192.168.0.1: icmp_seq=34 ttl=64 time=3.10 ms # Missed 33 64 bytes from 192.168.0.1: icmp_seq=36 ttl=64 time=3.04 ms # Missed 35 64 bytes from 192.168.0.1: icmp_seq=38 ttl=64 time=3.05 ms # Missed 37 64 bytes from 192.168.0.1: icmp_seq=40 ttl=64 time=4.14 ms # Missed 39 64 bytes from 192.168.0.1: icmp_seq=41 ttl=64 time=4.50 ms AFTER: 64 bytes from 192.168.0.1: icmp_seq=29 ttl=64 time=3.40 ms 64 bytes from 192.168.0.1: icmp_seq=30 ttl=64 time=6.10 ms 64 bytes from 192.168.0.1: icmp_seq=31 ttl=64 time=4.69 ms 64 bytes from 192.168.0.1: icmp_seq=32 ttl=64 time=4.49 ms 64 bytes from 192.168.0.1: icmp_seq=33 ttl=64 time=148 ms 64 bytes from 192.168.0.1: icmp_seq=34 ttl=64 time=6.83 ms 64 bytes from 192.168.0.1: icmp_seq=35 ttl=64 time=8.66 ms 64 bytes from 192.168.0.1: icmp_seq=36 ttl=64 time=13.0 ms 64 bytes from 192.168.0.1: icmp_seq=37 ttl=64 time=4.78 ms 64 bytes from 192.168.0.1: icmp_seq=38 ttl=64 time=3.85 ms Cc: Signed-off-by: Stanislaw Gruszka Link: https://patch.msgid.link/20260908124651.2995-1-stf_xl@wp.pl Signed-off-by: Johannes Berg --- net/mac80211/tx.c | 52 +++++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index fd7981466137..d85c84a738f7 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -269,7 +269,6 @@ ieee80211_tx_h_dynamic_ps(struct ieee80211_tx_data *tx) static ieee80211_tx_result debug_noinline ieee80211_tx_h_check_assoc(struct ieee80211_tx_data *tx) { - struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)tx->skb->data; struct ieee80211_tx_info *info = IEEE80211_SKB_CB(tx->skb); bool assoc = false; @@ -277,20 +276,6 @@ ieee80211_tx_h_check_assoc(struct ieee80211_tx_data *tx) if (unlikely(info->flags & IEEE80211_TX_CTL_INJECTED)) return TX_CONTINUE; - if (unlikely(test_bit(SCAN_SW_SCANNING, &tx->local->scanning)) && - test_bit(SDATA_STATE_OFFCHANNEL, &tx->sdata->state) && - !ieee80211_is_probe_req(hdr->frame_control) && - !ieee80211_is_any_nullfunc(hdr->frame_control)) - /* - * When software scanning only nullfunc frames (to notify - * the sleep state to the AP) and probe requests (for the - * active scan) are allowed, all other frames should not be - * sent and we should not get here, but if we do - * nonetheless, drop them to avoid sending them - * off-channel. See __ieee80211_start_scan() for more. - */ - return TX_DROP; - if (tx->sdata->vif.type == NL80211_IFTYPE_OCB) return TX_CONTINUE; @@ -839,6 +824,33 @@ ieee80211_tx_h_rate_ctrl(struct ieee80211_tx_data *tx) return TX_CONTINUE; } +static ieee80211_tx_result debug_noinline +ieee80211_tx_h_check_offchannel(struct ieee80211_tx_data *tx) +{ + struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)tx->skb->data; + struct ieee80211_tx_info *info = IEEE80211_SKB_CB(tx->skb); + + if (unlikely(info->flags & IEEE80211_TX_CTL_INJECTED)) + return TX_CONTINUE; + + if (unlikely(test_bit(SCAN_SW_SCANNING, &tx->local->scanning)) && + test_bit(SDATA_STATE_OFFCHANNEL, &tx->sdata->state) && + !ieee80211_is_probe_req(hdr->frame_control) && + !ieee80211_is_any_nullfunc(hdr->frame_control)) { + /* + * When software scanning only nullfunc frames (to notify + * the sleep state to the AP) and probe requests (for the + * active scan) are allowed, all other frames should not be + * sent and we should not get here, but if we do + * nonetheless, drop them to avoid sending them + * off-channel. See __ieee80211_start_scan() for more. + */ + return TX_DROP; + } + + return TX_CONTINUE; +} + static __le16 ieee80211_tx_next_seq(struct sta_info *sta, int tid) { u16 *seq = &sta->tid_seq[tid]; @@ -1905,6 +1917,7 @@ static int invoke_tx_handlers_late(struct ieee80211_tx_data *tx) goto txh_done; } + CALL_TXH(ieee80211_tx_h_check_offchannel); CALL_TXH(ieee80211_tx_h_michael_mic_add); CALL_TXH(ieee80211_tx_h_sequence); CALL_TXH(ieee80211_tx_h_fragment); @@ -4720,10 +4733,6 @@ static void ieee80211_8023_xmit(struct ieee80211_sub_if_data *sdata, queue = ieee80211_select_queue(sdata, sta, skb); skb_set_queue_mapping(skb, queue); - if (unlikely(test_bit(SCAN_SW_SCANNING, &local->scanning)) && - test_bit(SDATA_STATE_OFFCHANNEL, &sdata->state)) - goto out_free; - skb = skb_share_check(skb, GFP_ATOMIC); if (unlikely(!skb)) return; @@ -4785,11 +4794,6 @@ static void ieee80211_8023_xmit(struct ieee80211_sub_if_data *sdata, ieee80211_tpt_led_trig_tx(local, len); ieee80211_tx_8023(sdata, skb, sta, false); - - return; - -out_free: - kfree_skb(skb); } static bool ieee80211_check_mcast_offload(struct ieee80211_sub_if_data *sdata, From 52953d149ab4da0432d54b09f3c8add42b4d4a82 Mon Sep 17 00:00:00 2001 From: Mikhail Lobanov Date: Mon, 9 Sep 2024 11:08:20 -0400 Subject: [PATCH 052/101] wifi: ath11k: modify null check logic in ath11k_ce_rx_post_pipe() The previous logic in ath11k_ce_rx_post_pipe() incorrectly required both dest_ring and status_ring to be NULL in order to exit the function. This caused the function to continue even if only one of the pointers was NULL, potentially leading to null pointer dereferences in ath11k_ce_rx_buf_enqueue_pipe(). Fix the condition by modifying the logic so that the function returns early if either dest_ring or status_ring is NULL. Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: d5c65159f289 ("ath11k: driver for Qualcomm IEEE 802.11ax devices") Signed-off-by: Mikhail Lobanov Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Acked-by: Jeff Johnson Link: https://patch.msgid.link/20240909150824.28195-1-m.lobanov@rosalinux.ru Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/ce.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath11k/ce.c b/drivers/net/wireless/ath/ath11k/ce.c index c56dd8a86760..1e08b0970b14 100644 --- a/drivers/net/wireless/ath/ath11k/ce.c +++ b/drivers/net/wireless/ath/ath11k/ce.c @@ -325,7 +325,7 @@ static int ath11k_ce_rx_post_pipe(struct ath11k_ce_pipe *pipe) dma_addr_t paddr; int ret = 0; - if (!(pipe->dest_ring || pipe->status_ring)) + if (!pipe->dest_ring || !pipe->status_ring) return 0; spin_lock_bh(&ab->ce.ce_lock); From c7555d47b1d051e0406474349deccf74b491ae01 Mon Sep 17 00:00:00 2001 From: Nicolas Escande Date: Thu, 13 Aug 2026 14:15:28 +0200 Subject: [PATCH 053/101] wifi: ath11k: fix locking problem in ath11k_dp_rx_tid_del_func() In this function, we iterate over dp->reo_cmd_cache_flush_list using list_for_each_entry_safe(), under dp->reo_cmd_lock for concurrent access, and for each expired entries we : - drop the lock - call ath11k_dp_reo_cache_flush() - kfree() the entry - retake the lock to keep on iterating list_for_each_entry_safe() protects us from deleting the entry during iteration but doesn't protect for concurrent access. So another thread can take the lock and modify the list in between and crash like below. To fix the issue, move all entries that needs to be freed to a local list while under the lock and then iterate over the list to free the entries without holding the lock. BUG: Unable to handle kernel paging request at virtual address 00000010ddbeef8c Call trace: ath11k_dp_rx_tid_del_func+0x164/0x3c8 ath11k_dp_process_reo_status+0x1d4/0x2fc ath11k_dp_service_srng+0x334/0x338 ath11k_pcic_ext_grp_napi_poll+0x30/0xc0 __napi_poll+0x34/0x184 napi_threaded_poll+0xb4/0x1d8 kthread+0xdc/0xe0 ret_from_fork+0x10/0x20 Tested-on: QCN9074 hw1.0 PCI WLAN.HK.2.9.0.1-01977-QCAHKSWPL_SILICONZ-1 Fixes: d5c65159f289 ("ath11k: driver for Qualcomm IEEE 802.11ax devices") Suggested-by: Baochen Qiang Signed-off-by: Nicolas Escande Reviewed-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260813-ath11k-locking-v2-1-f4113a0bd0f4@gmail.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath11k/dp_rx.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/ath/ath11k/dp_rx.c b/drivers/net/wireless/ath/ath11k/dp_rx.c index 33425707c084..5b717b731197 100644 --- a/drivers/net/wireless/ath/ath11k/dp_rx.c +++ b/drivers/net/wireless/ath/ath11k/dp_rx.c @@ -762,6 +762,9 @@ static void ath11k_dp_rx_tid_del_func(struct ath11k_dp *dp, void *ctx, struct ath11k_base *ab = dp->ab; struct dp_rx_tid *rx_tid = ctx; struct dp_reo_cache_flush_elem *elem, *tmp; + struct list_head flush_list; + + INIT_LIST_HEAD(&flush_list); if (status == HAL_REO_CMD_DRAIN) { goto free_desc; @@ -783,23 +786,25 @@ static void ath11k_dp_rx_tid_del_func(struct ath11k_dp *dp, void *ctx, list_add_tail(&elem->list, &dp->reo_cmd_cache_flush_list); dp->reo_cmd_cache_flush_count++; - /* Flush and invalidate aged REO desc from HW cache */ + /* identify aged REO desc that needs removal */ list_for_each_entry_safe(elem, tmp, &dp->reo_cmd_cache_flush_list, list) { if (dp->reo_cmd_cache_flush_count > DP_REO_DESC_FREE_THRESHOLD || time_after(jiffies, elem->ts + msecs_to_jiffies(DP_REO_DESC_FREE_TIMEOUT_MS))) { - list_del(&elem->list); + list_move_tail(&elem->list, &flush_list); dp->reo_cmd_cache_flush_count--; - spin_unlock_bh(&dp->reo_cmd_lock); - - ath11k_dp_reo_cache_flush(ab, &elem->data); - kfree(elem); - spin_lock_bh(&dp->reo_cmd_lock); } } spin_unlock_bh(&dp->reo_cmd_lock); + /* remove aged REO desc from HW */ + list_for_each_entry_safe(elem, tmp, &flush_list, list) { + ath11k_dp_reo_cache_flush(ab, &elem->data); + list_del(&elem->list); + kfree(elem); + } + return; free_desc: dma_free_noncoherent(ab->dev, rx_tid->unaligned_size, From 6a0832c986f14518645714be87724f13e9d59a95 Mon Sep 17 00:00:00 2001 From: Kamil Bienkiewicz Date: Fri, 28 Aug 2026 02:51:26 +0200 Subject: [PATCH 054/101] wifi: ath12k: advertise AP_VLAN interface mode for IPQ5332 Only the two QCN9274 hw_params advertise NL80211_IFTYPE_AP_VLAN; the IPQ5332 entry does not. ath12k sets SW_CRYPTO_CONTROL, so mac80211 does not add the mode on the driver's behalf either -- ieee80211_alloc_hw_nm() only does that for drivers that leave SW_CRYPTO_CONTROL clear, leaving the declaration to drivers that can transmit software-encrypted frames. AP/VLAN is therefore absent from the wiphy, and creating an AP_VLAN interface without NL80211_ATTR_4ADDR is rejected by cfg80211_iftype_allowed(): nl80211: Failed to create interface : -95 (Operation not supported) That is the path hostapd uses for dynamic per-station VLANs, in hostapd_vlan_if_add(). The 4-address (WDS) case is not affected, as cfg80211_iftype_allowed() short-circuits it on WIPHY_FLAG_4ADDR_AP, which mac80211 sets unconditionally at alloc time. The QCN9274 entries already declare AP_VLAN, and the transmit path is shared Wi-Fi 7 code with no per-chip or per-bus gating, so IPQ5332 can deliver it as well. AP_VLAN is a software interface type, so no interface combination changes are needed. On a mixed-bus single-wiphy group the effect is wider still, since ath12k_mac_get_ifmodes() intersects interface_modes across all radios: one IPQ5332 masks AP_VLAN for the QCN9274 radios too. Advertise AP_VLAN on IPQ5332 as QCN9274 does. With the mode advertised, a non-4addr AP_VLAN interface can be created on an AP interface of an IPQ5332 + 2x QCN9274 single-wiphy group: # iw dev phy0.0-ap0 interface add tvlan0 type __ap_vlan # iw dev tvlan0 info Interface tvlan0 ifindex 19 wdev 0x9 addr 00:03:7f:12:0b:0b type AP/VLAN wiphy 0 Radios: 0 1 2 RADIUS-assigned dynamic VLANs were not exercised end to end. Tested-on: IPQ5332 hw1.0 AHB WLAN.WBE.1.6-01270-QCAHKSWPL_SILICONZ-1 Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1 Signed-off-by: Kamil Bienkiewicz Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260828005126.1685832-1-perceivalpercy@gmail.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/wifi7/hw.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hw.c b/drivers/net/wireless/ath/ath12k/wifi7/hw.c index 7b86f324b2e6..a20c21807344 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hw.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hw.c @@ -664,7 +664,8 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = { .interface_modes = BIT(NL80211_IFTYPE_STATION) | BIT(NL80211_IFTYPE_AP) | - BIT(NL80211_IFTYPE_MESH_POINT), + BIT(NL80211_IFTYPE_MESH_POINT) | + BIT(NL80211_IFTYPE_AP_VLAN), .supports_monitor = true, .supports_cong_ctrl_max_msdus = true, From 958287de1c1e373e634a5751d74e94bb0ad94ad1 Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Wed, 19 Aug 2026 09:43:16 +0800 Subject: [PATCH 055/101] wifi: ath12k: use kernel types instead of userspace stdint types Replace the userspace stdint types uint64_t/uint8_t with the kernel types u64/u8 to conform to the surrounding ath12k code, which uses the kernel types everywhere else. No functional changes, compile tested only. Signed-off-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260819-ath12k-cleanup-userspace-types-v1-1-161a1e8910f5@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/ce.c | 2 +- drivers/net/wireless/ath/ath12k/dp.c | 2 +- drivers/net/wireless/ath/ath12k/mac.c | 2 +- drivers/net/wireless/ath/ath12k/wifi7/hal_tx.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/ce.c b/drivers/net/wireless/ath/ath12k/ce.c index 67bbc6d8d0c9..c99e915e3885 100644 --- a/drivers/net/wireless/ath/ath12k/ce.c +++ b/drivers/net/wireless/ath/ath12k/ce.c @@ -269,7 +269,7 @@ static void ath12k_ce_srng_msi_ring_params_setup(struct ath12k_base *ab, u32 ce_ ath12k_hif_get_ce_msi_idx(ab, ce_id, &msi_data_idx); ring_params->msi_addr = addr_lo; - ring_params->msi_addr |= (dma_addr_t)(((uint64_t)addr_hi) << 32); + ring_params->msi_addr |= (dma_addr_t)(((u64)addr_hi) << 32); ring_params->msi_data = (msi_data_idx % msi_data_count) + msi_data_start; ring_params->flags |= HAL_SRNG_FLAGS_MSI_INTR; } diff --git a/drivers/net/wireless/ath/ath12k/dp.c b/drivers/net/wireless/ath/ath12k/dp.c index f9b37d75956d..ef9601152f81 100644 --- a/drivers/net/wireless/ath/ath12k/dp.c +++ b/drivers/net/wireless/ath/ath12k/dp.c @@ -224,7 +224,7 @@ static void ath12k_dp_srng_msi_setup(struct ath12k_base *ab, ath12k_hif_get_msi_address(ab, &addr_lo, &addr_hi); ring_params->msi_addr = addr_lo; - ring_params->msi_addr |= (dma_addr_t)(((uint64_t)addr_hi) << 32); + ring_params->msi_addr |= (dma_addr_t)(((u64)addr_hi) << 32); ring_params->msi_data = (msi_group_number % msi_data_count) + msi_data_start; ring_params->flags |= HAL_SRNG_FLAGS_MSI_INTR; diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 879f5ad80921..384dea69c178 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -3517,7 +3517,7 @@ static void ath12k_peer_assoc_h_eht(struct ath12k *ar, IEEE80211_EHT_MCS_NSS_RX)); } - max_nss = min(max_nss, (uint8_t)eht_nss); + max_nss = min(max_nss, (u8)eht_nss); arg->peer_nss = min(link_sta->rx_nss, max_nss); diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hal_tx.c b/drivers/net/wireless/ath/ath12k/wifi7/hal_tx.c index eeabe9db776e..def540529872 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hal_tx.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hal_tx.c @@ -36,7 +36,7 @@ void ath12k_wifi7_hal_tx_cmd_desc_setup(struct ath12k_base *ab, tcl_cmd->buf_addr_info.info0 = le32_encode_bits(ti->paddr, BUFFER_ADDR_INFO0_ADDR); tcl_cmd->buf_addr_info.info1 = - le32_encode_bits(((uint64_t)ti->paddr >> HAL_ADDR_MSB_REG_SHIFT), + le32_encode_bits(((u64)ti->paddr >> HAL_ADDR_MSB_REG_SHIFT), BUFFER_ADDR_INFO1_ADDR); tcl_cmd->buf_addr_info.info1 |= le32_encode_bits((ti->rbm_id), BUFFER_ADDR_INFO1_RET_BUF_MGR) | From 507478f6f93864978a399173d4f54c8a987c8235 Mon Sep 17 00:00:00 2001 From: Linkai Gong Date: Tue, 11 Aug 2026 10:05:23 +0800 Subject: [PATCH 056/101] wifi: ath12k: clear dangling channel pointers on error paths On failure, ath12k_mac_setup_channels_rates() frees band channel arrays but leaves the pointers set, or clears the wrong band after a copy-paste typo (6 GHz free paired with a 2 GHz NULL). Clear the matching sbands[].channels pointer after each kfree(). Compile tested only. Signed-off-by: Linkai Gong Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260811020523.387433-1-gonglinkai@kylinos.cn Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/mac.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 384dea69c178..347bdea9b9ec 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -14294,6 +14294,7 @@ static int ath12k_mac_setup_channels_rates(struct ath12k *ar, sizeof(ath12k_6ghz_channels), GFP_KERNEL); if (!channels) { kfree(ar->mac.sbands[NL80211_BAND_2GHZ].channels); + ar->mac.sbands[NL80211_BAND_2GHZ].channels = NULL; return -ENOMEM; } @@ -14344,7 +14345,9 @@ static int ath12k_mac_setup_channels_rates(struct ath12k *ar, GFP_KERNEL); if (!channels) { kfree(ar->mac.sbands[NL80211_BAND_2GHZ].channels); + ar->mac.sbands[NL80211_BAND_2GHZ].channels = NULL; kfree(ar->mac.sbands[NL80211_BAND_6GHZ].channels); + ar->mac.sbands[NL80211_BAND_6GHZ].channels = NULL; return -ENOMEM; } @@ -14384,7 +14387,7 @@ static int ath12k_mac_setup_channels_rates(struct ath12k *ar, kfree(ar->mac.sbands[NL80211_BAND_2GHZ].channels); ar->mac.sbands[NL80211_BAND_2GHZ].channels = NULL; kfree(ar->mac.sbands[NL80211_BAND_6GHZ].channels); - ar->mac.sbands[NL80211_BAND_2GHZ].channels = NULL; + ar->mac.sbands[NL80211_BAND_6GHZ].channels = NULL; kfree(channels); band->channels = NULL; return ret; From 4de5a8a0edddc453f1a4c93b6d9f09013a23b72e Mon Sep 17 00:00:00 2001 From: Rameshkumar Sundaram Date: Thu, 13 Aug 2026 23:09:20 +0530 Subject: [PATCH 057/101] wifi: ath12k: fix DMA unwind for ext MSDU descriptor retry ath12k_wifi7_dp_tx() maps the original MSDU into ti.paddr and stores the address in skb_cb->paddr. When an extended MSDU descriptor is used, the function later maps the ext descriptor and overwrites ti.paddr and ti.data_len with the ext-desc DMA address and length. If TCL ring allocation then fails, the error path first unmaps the ext-desc DMA address from skb_cb->paddr_ext_desc. It then falls through to fail_unmap_dma, which uses ti.paddr/ti.data_len for the original MSDU unmap. At that point ti.paddr still refers to the ext-desc mapping, so the ext descriptor is unmapped twice and the original MSDU mapping is left mapped. Fix the original MSDU unwind to use skb_cb->paddr and skb->len instead of ti.paddr/ti.data_len. The ti fields cannot be used after the ext descriptor is mapped because they are intentionally reused for the buffer submitted to TCL. Also clear skb_cb->paddr_ext_desc after the ext-desc unmap so a TCL ring retry cannot observe stale ext-desc DMA state from the previous attempt. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1 Fixes: 37a068fc9dc4 ("wifi: ath12k: Handle error cases during extended skb allocation") Signed-off-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260813-ext-msdu-fix-v1-1-c5e80ccd061a@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c b/drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c index 587d58eeccfa..0dcb566fd182 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c @@ -449,15 +449,17 @@ int ath12k_wifi7_dp_tx(struct ath12k_pdev_dp *dp_pdev, struct ath12k_link_vif *a return 0; fail_unmap_dma_ext: - if (skb_cb->paddr_ext_desc) + if (skb_cb->paddr_ext_desc) { dma_unmap_single(dp->dev, skb_cb->paddr_ext_desc, skb_ext_desc->len, DMA_TO_DEVICE); + skb_cb->paddr_ext_desc = 0; + } fail_free_ext_skb: kfree_skb(skb_ext_desc); fail_unmap_dma: - dma_unmap_single(dp->dev, ti.paddr, ti.data_len, DMA_TO_DEVICE); + dma_unmap_single(dp->dev, skb_cb->paddr, skb->len, DMA_TO_DEVICE); fail_remove_tx_buf: ath12k_dp_tx_release_txbuf(dp, tx_desc, pool_id); From 6c40719489c8d799798989b15bc07b0700c132f8 Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Tue, 18 Aug 2026 09:44:48 +0800 Subject: [PATCH 058/101] wifi: ath12k: fix stale skb pointers after aligned TX payload shift ath12k_wifi7_dp_tx() caches hdr, eth, and skb_cb from the skb before calling ath12k_dp_tx_align_payload(). That function may shift skb->data in place (when headroom or tailroom is sufficient) or reallocate the buffer entirely via skb_realloc_headroom(), freeing the original skb. In either case hdr, eth, and skb_cb are left pointing into stale memory. After alignment, only hdr is refreshed, leaving eth and skb_cb stale. skb_cb is written immediately after (storing DMA addresses), and eth is re-read on every TCL ring retry via the tcl_ring_sel goto, so both accesses are use-after-free or stale-pointer bugs depending on which alignment path was taken. Refresh eth (conditionally, to preserve the encap-mode distinction) and skb_cb alongside hdr after ath12k_dp_tx_align_payload() returns, so all three point into the live skb for all subsequent accesses. Issue found during code review, compile tested only. Fixes: 38055789d151 ("wifi: ath12k: use 128 bytes aligned iova in transmit path for WCN7850") Signed-off-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260818-ath12k-uaf-for-aligned-tx-v1-1-d6ae195b15e7@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c b/drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c index 0dcb566fd182..7ea49bcb0f47 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c @@ -313,10 +313,15 @@ int ath12k_wifi7_dp_tx(struct ath12k_pdev_dp *dp_pdev, struct ath12k_link_vif *a goto map; } - /* hdr is pointing to a wrong place after alignment, - * so refresh it for later use. + /* + * The payload may have been shifted or even the entire buffer may have + * been reallocated for alignment. In that case, hdr, eth and skb_cb + * are stale pointers. Refresh them now for later dereference. */ hdr = (void *)skb->data; + if (eth) + eth = (struct ethhdr *)skb->data; + skb_cb = ATH12K_SKB_CB(skb); } map: ti.paddr = dma_map_single(dp->dev, skb->data, skb->len, DMA_TO_DEVICE); From 979ecb88ed3528e08dd7f87b90b029374226ca84 Mon Sep 17 00:00:00 2001 From: Baochen Qiang Date: Tue, 18 Aug 2026 10:03:41 +0800 Subject: [PATCH 059/101] wifi: ath12k: fix truncated TX buffer DMA address in MSDU ext descriptor ath12k_wifi7_hal_tx_cmd_ext_desc_setup() programs the low 32 bits of the TX buffer physical address into HAL_TX_MSDU_EXT_INFO0_BUF_PTR_LO but hardcodes the high bits field HAL_TX_MSDU_EXT_INFO1_BUF_PTR_HI to 0x0. dma_addr_t is 64-bit on platforms with CONFIG_ARCH_DMA_ADDR_T_64BIT, so whenever a TX buffer is mapped above the 4 GB boundary the upper bits of paddr are dropped and the firmware is handed a truncated address, leading to potential TX failures. Write the upper bits of paddr into BUF_PTR_HI to fix this issue. Issue found during code review, compile tested only. Fixes: d889913205cf ("wifi: ath12k: driver for Qualcomm Wi-Fi 7 devices") Signed-off-by: Baochen Qiang Reviewed-by: Rameshkumar Sundaram Link: https://patch.msgid.link/20260818-ath12k-truncated-tx-dma-addr-v1-1-ead978969371@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c b/drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c index 7ea49bcb0f47..7c12dba4cc51 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/dp_tx.c @@ -63,7 +63,7 @@ ath12k_wifi7_hal_tx_cmd_ext_desc_setup(struct ath12k_base *ab, { tcl_ext_cmd->info0 = le32_encode_bits(ti->paddr, HAL_TX_MSDU_EXT_INFO0_BUF_PTR_LO); - tcl_ext_cmd->info1 = le32_encode_bits(0x0, + tcl_ext_cmd->info1 = le32_encode_bits((u64)ti->paddr >> HAL_ADDR_MSB_REG_SHIFT, HAL_TX_MSDU_EXT_INFO1_BUF_PTR_HI) | le32_encode_bits(ti->data_len, HAL_TX_MSDU_EXT_INFO1_BUF_LEN); From 12ca935af2e68447e9218a615ebbb5ff7faf1554 Mon Sep 17 00:00:00 2001 From: Aaradhana Sahu Date: Wed, 19 Aug 2026 16:32:14 +0530 Subject: [PATCH 060/101] wifi: ath12k: Free allocated CE IRQs on request_irq() failure When CE IRQ configuration fails, the driver does not release all IRQs that were successfully requested before the failure. This can leak IRQ resources during probe failure. Free the previously requested CE IRQs before returning from the error path to ensure that partially initialized IRQ resources are properly cleaned up during probe failure. Factor out the CE IRQ cleanup into a helper to reuse the cleanup logic during both error handling and driver teardown. Also free CE IRQs when external IRQ configuration fails, before returning from the error path. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1 Fixes: d889913205cf ("wifi: ath12k: driver for Qualcomm Wi-Fi 7 devices") Signed-off-by: Aaradhana Sahu Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260819110215.2485514-2-aaradhana.sahu@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/pci.c | 29 ++++++++++++++++++--------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/pci.c b/drivers/net/wireless/ath/ath12k/pci.c index 6441927b5382..1525e6866cf4 100644 --- a/drivers/net/wireless/ath/ath12k/pci.c +++ b/drivers/net/wireless/ath/ath12k/pci.c @@ -313,6 +313,19 @@ static void ath12k_pci_sw_reset(struct ath12k_base *ab, bool power_on) ath12k_mhi_set_mhictrl_reset(ab); } +static void ath12k_pci_free_ce_irq(struct ath12k_base *ab, int num_ce) +{ + int i, irq_idx; + + for (i = 0; i < num_ce; i++) { + if (ath12k_ce_get_attr_flags(ab, i) & CE_ATTR_DIS_INTR) + continue; + + irq_idx = ATH12K_PCI_IRQ_CE0_OFFSET + i; + free_irq(ab->irq_num[irq_idx], &ab->ce.ce_pipe[i]); + } +} + static void ath12k_pci_free_ext_irq(struct ath12k_base *ab) { int i, j; @@ -330,15 +343,7 @@ static void ath12k_pci_free_ext_irq(struct ath12k_base *ab) static void ath12k_pci_free_irq(struct ath12k_base *ab) { - int i, irq_idx; - - for (i = 0; i < ab->hw_params->ce_count; i++) { - if (ath12k_ce_get_attr_flags(ab, i) & CE_ATTR_DIS_INTR) - continue; - irq_idx = ATH12K_PCI_IRQ_CE0_OFFSET + i; - free_irq(ab->irq_num[irq_idx], &ab->ce.ce_pipe[i]); - } - + ath12k_pci_free_ce_irq(ab, ab->hw_params->ce_count); ath12k_pci_free_ext_irq(ab); } @@ -671,6 +676,8 @@ static int ath12k_pci_config_irq(struct ath12k_base *ab) if (ret) { ath12k_err(ab, "failed to request irq %d: %d\n", irq_idx, ret); + + ath12k_pci_free_ce_irq(ab, i); return ret; } @@ -681,8 +688,10 @@ static int ath12k_pci_config_irq(struct ath12k_base *ab) } ret = ath12k_pci_ext_irq_config(ab); - if (ret) + if (ret) { + ath12k_pci_free_ce_irq(ab, ab->hw_params->ce_count); return ret; + } return 0; } From 9a78703ec4a895bd2e99bf43bd18d76683126c19 Mon Sep 17 00:00:00 2001 From: Aaradhana Sahu Date: Wed, 19 Aug 2026 16:32:15 +0530 Subject: [PATCH 061/101] wifi: ath12k: Free allocated external IRQs on request_irq() failure When external IRQ configuration fails, the driver does not release all IRQs that were successfully requested before the failure. This can leak IRQ resources during probe failure. Free previously requested external IRQs when external IRQ configuration fails. Also remove the NAPI instance with netif_napi_del() before freeing the associated netdev to properly clean up the NAPI resources. Store the IRQ number only after request_irq() succeeds to avoid recording an IRQ that was not successfully requested. Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1 Fixes: d889913205cf ("wifi: ath12k: driver for Qualcomm Wi-Fi 7 devices") Signed-off-by: Aaradhana Sahu Reviewed-by: Rameshkumar Sundaram Reviewed-by: Baochen Qiang Link: https://patch.msgid.link/20260819110215.2485514-3-aaradhana.sahu@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/pci.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/pci.c b/drivers/net/wireless/ath/ath12k/pci.c index 1525e6866cf4..af0e882fd0b5 100644 --- a/drivers/net/wireless/ath/ath12k/pci.c +++ b/drivers/net/wireless/ath/ath12k/pci.c @@ -326,11 +326,11 @@ static void ath12k_pci_free_ce_irq(struct ath12k_base *ab, int num_ce) } } -static void ath12k_pci_free_ext_irq(struct ath12k_base *ab) +static void ath12k_pci_free_ext_irq(struct ath12k_base *ab, int num_ext_irq_grp) { int i, j; - for (i = 0; i < ATH12K_EXT_IRQ_GRP_NUM_MAX; i++) { + for (i = 0; i < num_ext_irq_grp; i++) { struct ath12k_ext_irq_grp *irq_grp = &ab->ext_irq_grp[i]; for (j = 0; j < irq_grp->num_irq; j++) @@ -344,7 +344,7 @@ static void ath12k_pci_free_ext_irq(struct ath12k_base *ab) static void ath12k_pci_free_irq(struct ath12k_base *ab) { ath12k_pci_free_ce_irq(ab, ab->hw_params->ce_count); - ath12k_pci_free_ext_irq(ab); + ath12k_pci_free_ext_irq(ab, ATH12K_EXT_IRQ_GRP_NUM_MAX); } static void ath12k_pci_ce_irq_enable(struct ath12k_base *ab, u16 ce_id) @@ -600,8 +600,6 @@ static int ath12k_pci_ext_irq_config(struct ath12k_base *ab) irq = ath12k_pci_get_msi_irq(ab->dev, vector); - ab->irq_num[irq_idx] = irq; - ath12k_dbg(ab, ATH12K_DBG_PCI, "irq:%d group:%d\n", irq, i); @@ -612,22 +610,24 @@ static int ath12k_pci_ext_irq_config(struct ath12k_base *ab) if (ret) { ath12k_err(ab, "failed request irq %d: %d\n", vector, ret); - goto fail_request; + + for (n = 0; n < j; n++) + free_irq(ab->irq_num[irq_grp->irqs[n]], irq_grp); + + netif_napi_del(&ab->ext_irq_grp[i].napi); + free_netdev(ab->ext_irq_grp[i].napi_ndev); + goto fail_allocate; } + + ab->irq_num[irq_idx] = irq; } ath12k_pci_ext_grp_disable(irq_grp); } return 0; -fail_request: - /* i ->napi_ndev was properly allocated. Free it also */ - i += 1; fail_allocate: - for (n = 0; n < i; n++) { - irq_grp = &ab->ext_irq_grp[n]; - free_netdev(irq_grp->napi_ndev); - } + ath12k_pci_free_ext_irq(ab, i); return ret; } From 69b2d06dafa4a61b718704d655dd370ba660052a Mon Sep 17 00:00:00 2001 From: Kang Yang Date: Fri, 28 Aug 2026 14:55:34 +0800 Subject: [PATCH 062/101] wifi: ath12k: preserve PPDU state across monitor status buffers A monitor PPDU may span multiple status buffers. However, __ath12k_wifi7_dp_mon_process_ring() clears mon_ppdu_info before processing every buffer. As a result, continuation buffers lose PPDU state collected from earlier buffers, including ppdu_id and PHY metadata. This can cause monitor RX processing to fail when ath12k_wifi7_dp_rx_mon_dest_process() observes a PPDU ID mismatch between status and destination rings, preventing MSDUs from being delivered. It can also produce incomplete EHT metadata that triggers warnings from mac80211: Rate marked as an EHT rate but data is invalid: MCS:0, NSS:0 ath12k_wifi7_dp_mon_srng_process() already tracks status TLV continuations via ppdu_continuation. Apply the same logic here and only reinitialize mon_ppdu_info when starting a new PPDU. mon_ppdu_info persists across ar->monitor_started transitions. If monitor mode stops while a PPDU is mid-continuation, ppdu_continuation stays set and could leak into the next monitor session. Reset it when monitor mode starts. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c7-00108-QCAHMTSWPL_V1.0_V2.0_SILICONZ_UPSTREAM-3 Fixes: 78d3d907d0f1 ("wifi: ath12k: add support to reap and process monitor status ring") Signed-off-by: Kang Yang Reviewed-by: Baochen Qiang Reviewed-by: Tamizh Chelvam Raja Link: https://patch.msgid.link/20260828065534.1537-1-kang.yang@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/mac.c | 7 ++++++ .../net/wireless/ath/ath12k/wifi7/dp_mon.c | 23 +++++++++++-------- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/mac.c b/drivers/net/wireless/ath/ath12k/mac.c index 347bdea9b9ec..d4116ba0da08 100644 --- a/drivers/net/wireless/ath/ath12k/mac.c +++ b/drivers/net/wireless/ath/ath12k/mac.c @@ -1506,6 +1506,13 @@ static int ath12k_mac_monitor_start(struct ath12k *ar) return ret; } + /* + * A previous monitor session may have stopped mid-PPDU, leaving + * ppdu_continuation set. Clear it so the first status buffer of + * this session is not merged with stale state from before. + */ + ar->dp.mon_data.mon_ppdu_info.ppdu_continuation = false; + ar->monitor_started = true; ar->num_started_vdevs++; diff --git a/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c b/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c index 016b0c38e51e..ded7d56cd79b 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/dp_mon.c @@ -3211,18 +3211,23 @@ __ath12k_wifi7_dp_mon_process_ring(struct ath12k *ar, int mac_id, goto exit; while ((skb = __skb_dequeue(&skb_list))) { - memset(ppdu_info, 0, sizeof(*ppdu_info)); - ppdu_info->peer_id = HAL_INVALID_PEERID; + if (!ppdu_info->ppdu_continuation) + ath12k_wifi7_dp_mon_rx_memset_ppdu_info(ppdu_info); hal_status = ath12k_wifi7_dp_mon_parse_rx_dest(&ar->dp, pmon, skb); - if (ar->monitor_started && - pmon->mon_ppdu_status == DP_PPDU_STATUS_START && - hal_status == HAL_TLV_STATUS_PPDU_DONE) { - rx_mon_stats->status_ppdu_done++; - pmon->mon_ppdu_status = DP_PPDU_STATUS_DONE; - ath12k_wifi7_dp_rx_mon_dest_process(ar, mac_id, *budget, napi); - pmon->mon_ppdu_status = DP_PPDU_STATUS_START; + if (hal_status == HAL_TLV_STATUS_PPDU_DONE) { + ppdu_info->ppdu_continuation = false; + if (ar->monitor_started && + pmon->mon_ppdu_status == DP_PPDU_STATUS_START) { + rx_mon_stats->status_ppdu_done++; + pmon->mon_ppdu_status = DP_PPDU_STATUS_DONE; + ath12k_wifi7_dp_rx_mon_dest_process(ar, mac_id, + *budget, napi); + pmon->mon_ppdu_status = DP_PPDU_STATUS_START; + } + } else { + ppdu_info->ppdu_continuation = true; } dev_kfree_skb_any(skb); From 93de43318ecdf0ff5f0e6125fe33a8b30fd10c66 Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Thu, 13 Aug 2026 10:41:02 -0700 Subject: [PATCH 063/101] wifi: ath9k: use rcu_dereference_bh() for sta->rates in ath_merge_ratetbl() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Commit c4f518736472 ("wifi: ath9k: Remove redundant rcu_read_lock/unlock() in spin_lock") removed the explicit rcu_read_lock()/rcu_read_unlock() pair from ath_txq_schedule(), relying on spin_lock_bh() to provide an implicit RCU read-side critical section. That is correct on kernels without CONFIG_DEBUG_LOCK_ALLOC, where rcu_read_lock_any_held() falls back to !preemptible() as a proxy, and spin_lock_bh() disables preemption. However, rcu_dereference() in ath_merge_ratetbl() checks rcu_read_lock_held(), which under CONFIG_DEBUG_LOCK_ALLOC only returns true when lock_is_held(&rcu_lock_map) — set exclusively by an explicit rcu_read_lock(). A spin_lock_bh() does not set rcu_lock_map, so on a CONFIG_DEBUG_LOCK_ALLOC + CONFIG_PROVE_RCU kernel, any call path that reaches ath_merge_ratetbl() with a non-NULL sta will produce a lockdep splat. The correct fix is to use rcu_dereference_bh(), whose validity check calls rcu_read_lock_bh_held(), which returns true whenever BH is disabled (in_softirq() || irqs_disabled()). This matches the actual protection at all callers of ath_set_rates() that pass a non-NULL sta: they all hold a spin_lock_bh() (either sc->chan_lock or txq->axq_lock). Fixes: c4f518736472 ("wifi: ath9k: Remove redundant rcu_read_lock/unlock() in spin_lock") Assisted-by: LLM Acked-by: Toke HĆøiland-JĆørgensen Link: https://patch.msgid.link/20260813-ath9k-rcu-fix-v1-1-424665a1ce60@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath9k/xmit.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/ath/ath9k/xmit.c b/drivers/net/wireless/ath/ath9k/xmit.c index 89d8b3178784..3413bb4906d4 100644 --- a/drivers/net/wireless/ath/ath9k/xmit.c +++ b/drivers/net/wireless/ath/ath9k/xmit.c @@ -168,7 +168,7 @@ static bool ath_merge_ratetbl(struct ieee80211_sta *sta, struct ath_buf *bf, if (!sta) return false; - ratetbl = rcu_dereference(sta->rates); + ratetbl = rcu_dereference_bh(sta->rates); if (!ratetbl) return false; From c3bace8584ca707e34ba837f65c2e9d566af4c2c Mon Sep 17 00:00:00 2001 From: Aaradhana Sahu Date: Tue, 11 Aug 2026 11:13:16 +0530 Subject: [PATCH 064/101] wifi: ath12k: add support to load shared firmware on multiPD IPQ5332 platform uses a MultiPD architecture where multiple firmware instances share common read-only code segments. The driver currently loads only the per-UserPD firmware, which leaves the shared read-only firmware unloaded. Add support for loading the shared read-only firmware once before the UserPDs are powered up. The shared segment contains common code that is identical across all UserPDs and reduces the total firmware memory requirement. Store the reserved memory mapping in struct ath12k_ahb_rproc_info so it can be shared by all UserPDs and mapped only once. Keep the mapping valid across UserPD power cycles, and unmap it when the last UserPD is removed. Shut down the shared firmware from the rproc deconfiguration path when the last UserPD is removed. Also, pass NULL for the reloc_base parameter because the driver uses fixed memory regions configured through device tree and does not need the adjusted physical address after relocation. Tested-on: IPQ5332 hw1.0 AHB WLAN.WBE.1.6-01275-QCAHKSWPL_SILICONZ-1 Signed-off-by: Aaradhana Sahu Reviewed-by: Baochen Qiang Reviewed-by: Vasanthakumar Thiagarajan Link: https://patch.msgid.link/20260811054316.518494-1-aaradhana.sahu@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/ahb.c | 166 +++++++++++++++++--- drivers/net/wireless/ath/ath12k/ahb.h | 9 ++ drivers/net/wireless/ath/ath12k/wifi7/ahb.c | 3 + 3 files changed, 157 insertions(+), 21 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/ahb.c b/drivers/net/wireless/ath/ath12k/ahb.c index 0fc55c9169e1..3985f4e1c067 100644 --- a/drivers/net/wireless/ath/ath12k/ahb.c +++ b/drivers/net/wireless/ath/ath12k/ahb.c @@ -347,35 +347,60 @@ static void ath12k_ahb_stop(struct ath12k_base *ab) ath12k_ce_cleanup_pipes(ab); } +static int ath12k_ahb_get_fw_load_region(struct ath12k_base *ab) +{ + struct ath12k_ahb *ab_ahb = ath12k_ab_to_ahb(ab); + struct ath12k_ahb_rproc_info *rproc_info = ab_ahb->rproc_info; + struct device *dev = ab->dev; + struct resource res; + int ret; + + if (rproc_info->mem_region) + return 0; + + ret = of_reserved_mem_region_to_resource_byname(dev->of_node, "q6-region", &res); + if (ret) + return ret; + + rproc_info->mem_phys = res.start; + rproc_info->mem_size = resource_size(&res); + rproc_info->mem_region = memremap(rproc_info->mem_phys, rproc_info->mem_size, + MEMREMAP_WC); + if (!rproc_info->mem_region) { + ath12k_err(ab, "unable to map memory region: %pa+%zx\n", + &res.start, rproc_info->mem_size); + rproc_info->mem_phys = 0; + rproc_info->mem_size = 0; + return -ENOMEM; + } + + return 0; +} + +static void ath12k_ahb_put_fw_load_region(struct ath12k_ahb_rproc_info *rproc_info) +{ + memunmap(rproc_info->mem_region); + rproc_info->mem_region = NULL; + rproc_info->mem_phys = 0; + rproc_info->mem_size = 0; +} + static int ath12k_ahb_power_up(struct ath12k_base *ab) { struct ath12k_ahb *ab_ahb = ath12k_ab_to_ahb(ab); + struct ath12k_ahb_rproc_info *rproc_info = ab_ahb->rproc_info; char fw_name[ATH12K_USERPD_FW_NAME_LEN]; char fw2_name[ATH12K_USERPD_FW_NAME_LEN]; struct device *dev = ab->dev; const struct firmware *fw, *fw2; unsigned long time_left; - phys_addr_t mem_phys; - struct resource res; - void *mem_region; - size_t mem_size; u32 pasid; int ret; - ret = of_reserved_mem_region_to_resource_byname(dev->of_node, "q6-region", - &res); + ret = ath12k_ahb_get_fw_load_region(ab); if (ret) return ret; - mem_phys = res.start; - mem_size = resource_size(&res); - mem_region = devm_memremap(dev, mem_phys, mem_size, MEMREMAP_WC); - if (IS_ERR(mem_region)) { - ath12k_err(ab, "unable to map memory region: %pa+%zx\n", - &res.start, mem_size); - return PTR_ERR(mem_region); - } - snprintf(fw_name, sizeof(fw_name), "%s/%s/%s%d%s", ATH12K_FW_DIR, ab->hw_params->fw.dir, ATH12K_AHB_FW_PREFIX, ab_ahb->userpd_id, ATH12K_AHB_FW_SUFFIX); @@ -400,11 +425,13 @@ static int ath12k_ahb_power_up(struct ath12k_base *ab) /* Load FW image to a reserved memory location */ if (ab_ahb->scm_auth_enabled) - ret = qcom_mdt_load(dev, fw, fw_name, pasid, mem_region, - mem_phys, mem_size, &mem_phys); + ret = qcom_mdt_load(dev, fw, fw_name, pasid, rproc_info->mem_region, + rproc_info->mem_phys, rproc_info->mem_size, + NULL); else - ret = qcom_mdt_load_no_init(dev, fw, fw_name, mem_region, - mem_phys, mem_size, &mem_phys); + ret = qcom_mdt_load_no_init(dev, fw, fw_name, rproc_info->mem_region, + rproc_info->mem_phys, rproc_info->mem_size, + NULL); if (ret) { ath12k_err(ab, "Failed to load MDT segments: %d\n", ret); goto err_fw; @@ -428,8 +455,9 @@ static int ath12k_ahb_power_up(struct ath12k_base *ab) goto err_fw2; } - ret = qcom_mdt_load_no_init(dev, fw2, fw2_name, mem_region, mem_phys, - mem_size, &mem_phys); + ret = qcom_mdt_load_no_init(dev, fw2, fw2_name, rproc_info->mem_region, + rproc_info->mem_phys, rproc_info->mem_size, + NULL); if (ret) { ath12k_err(ab, "Failed to load MDT segments: %d\n", ret); goto err_fw2; @@ -877,6 +905,7 @@ static struct ath12k_ahb_rproc_info *ath12k_ahb_rproc_info_alloc(struct ath12k_b rproc_info->rootpd_booted_by_driver = false; rproc_info->userpd[ab_ahb->userpd_id - 1] = ab_ahb; rproc_info->num_userpd = 1; + rproc_info->shared_fw_loaded = false; init_completion(&rproc_info->rootpd_ready); ab_ahb->rproc_info = rproc_info; @@ -961,6 +990,76 @@ static int ath12k_ahb_boot_root_pd(struct ath12k_base *ab) return 0; } +static int ath12k_ahb_load_auth_shared_fw(struct ath12k_base *ab, + struct ath12k_ahb_rproc_info *rproc_info, + const char *fw_name, u32 pasid) +{ + int ret; + + const struct firmware *fw __free(firmware) = NULL; + ret = request_firmware(&fw, fw_name, ab->dev); + if (ret) { + ath12k_err(ab, "failed to request shared firmware %s: %d\n", + fw_name, ret); + return ret; + } + + if (!fw->size) { + ath12k_err(ab, "Invalid firmware size\n"); + return -EINVAL; + } + + ath12k_dbg(ab, ATH12K_DBG_AHB, "loading firmware %s, size %zd\n", fw_name, + fw->size); + + ret = qcom_mdt_load(ab->dev, fw, fw_name, pasid, rproc_info->mem_region, + rproc_info->mem_phys, rproc_info->mem_size, NULL); + if (ret) { + ath12k_err(ab, "failed to load RO firmware %s: %d\n", fw_name, ret); + return ret; + } + + ret = qcom_pas_auth_and_reset(pasid); + if (ret) + ath12k_err(ab, "failed to authenticate and boot shared firmware: %d\n", + ret); + return ret; +} + +static int ath12k_ahb_load_shared_firmware(struct ath12k_base *ab) +{ + struct ath12k_ahb *ab_ahb = ath12k_ab_to_ahb(ab); + struct ath12k_ahb_rproc_info *rproc_info = ab_ahb->rproc_info; + char fw_name[ATH12K_USERPD_FW_NAME_LEN]; + u32 pasid; + int ret; + + lockdep_assert_held(&ath12k_rproc_info_lock); + + if (!ab_ahb->supports_multipd) + return 0; + + ret = ath12k_ahb_get_fw_load_region(ab); + if (ret) + return ret; + + snprintf(fw_name, sizeof(fw_name), "%s/%s/%s%d%s", ATH12K_FW_DIR, + ab->hw_params->fw.dir, ATH12K_AHB_FW_PREFIX, ATH12K_AHB_RO_ID, + ATH12K_AHB_FW_SUFFIX); + + pasid = u32_encode_bits(ATH12K_AHB_RO_ID, ATH12K_USERPD_ID_MASK) | + ATH12K_AHB_UPD_SWID; + + ret = ath12k_ahb_load_auth_shared_fw(ab, rproc_info, fw_name, pasid); + if (ret) { + ath12k_ahb_put_fw_load_region(rproc_info); + return ret; + } + + rproc_info->shared_fw_loaded = true; + return 0; +} + static int ath12k_ahb_configure_rproc(struct ath12k_base *ab) { int ret; @@ -989,6 +1088,15 @@ static int ath12k_ahb_configure_rproc(struct ath12k_base *ab) g_rproc_info->rootpd_booted_by_driver = true; } + if (!g_rproc_info->shared_fw_loaded) { + ret = ath12k_ahb_load_shared_firmware(ab); + if (ret) { + if (g_rproc_info->rootpd_booted_by_driver) + rproc_shutdown(g_rproc_info->tgt_rproc); + goto err_unreg_notifier; + } + } + mutex_unlock(&ath12k_rproc_info_lock); return 0; @@ -1012,6 +1120,8 @@ static void ath12k_ahb_deconfigure_rproc(struct ath12k_base *ab) { struct ath12k_ahb *ab_ahb = ath12k_ab_to_ahb(ab); struct ath12k_ahb_rproc_info *rproc_info = ab_ahb->rproc_info; + u32 pasid; + int ret; lockdep_assert_held(&ath12k_rproc_info_lock); @@ -1023,6 +1133,20 @@ static void ath12k_ahb_deconfigure_rproc(struct ath12k_base *ab) if (!g_rproc_info->num_userpd) { ath12k_ahb_unregister_rproc_notifier(); + if (g_rproc_info->shared_fw_loaded) { + pasid = u32_encode_bits(ATH12K_AHB_RO_ID, ATH12K_USERPD_ID_MASK) | + ATH12K_AHB_UPD_SWID; + ret = qcom_pas_shutdown(pasid); + if (ret) + ath12k_err(ab, "pas shutdown failed for shared firmware: %d\n", + ret); + + g_rproc_info->shared_fw_loaded = false; + } + + if (g_rproc_info->mem_region) + ath12k_ahb_put_fw_load_region(g_rproc_info); + if (g_rproc_info->rootpd_booted_by_driver && g_rproc_info->tgt_rproc->state == RPROC_RUNNING) rproc_shutdown(g_rproc_info->tgt_rproc); diff --git a/drivers/net/wireless/ath/ath12k/ahb.h b/drivers/net/wireless/ath/ath12k/ahb.h index cdb58b07338f..46a4420d41bd 100644 --- a/drivers/net/wireless/ath/ath12k/ahb.h +++ b/drivers/net/wireless/ath/ath12k/ahb.h @@ -30,6 +30,9 @@ #define ATH12K_USERPD_ID_MASK GENMASK(10, 8) #define ATH12K_USERPD_FW_NAME_LEN 35 +/* Shared read-only firmware ID, mandated by MultiPD firmware architecture */ +#define ATH12K_AHB_RO_ID 4 + enum ath12k_ahb_userpd_id { ATH12K_AHB_USERPD_ID_0 = 1, ATH12K_AHB_USERPD_ID_1, @@ -46,6 +49,7 @@ struct ath12k_ahb_desc { enum ath12k_hw_rev hw_rev; bool auth_enabled; const struct ath12k_hif_ops *ops; + bool supports_multipd; }; enum ath12k_ahb_smp2p_msg_id { @@ -77,6 +81,10 @@ struct ath12k_ahb_rproc_info { u8 num_userpd; bool rootpd_booted_by_driver; struct ath12k_ahb *userpd[ATH12K_MAX_DEVICES]; + bool shared_fw_loaded; + phys_addr_t mem_phys; + size_t mem_size; + void *mem_region; }; struct ath12k_ahb { @@ -95,6 +103,7 @@ struct ath12k_ahb { const struct ath12k_ahb_device_family_ops *device_family_ops; bool scm_auth_enabled; struct ath12k_ahb_rproc_info *rproc_info; + bool supports_multipd; }; struct ath12k_ahb_driver { diff --git a/drivers/net/wireless/ath/ath12k/wifi7/ahb.c b/drivers/net/wireless/ath/ath12k/wifi7/ahb.c index 98a6606ffd76..a8f5cf8cc2a6 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/ahb.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/ahb.c @@ -35,11 +35,13 @@ static const struct ath12k_ahb_desc ath12k_wifi7_ahb_desc[] = { .hw_rev = ATH12K_HW_IPQ5332_HW10, .auth_enabled = true, .ops = &ath12k_ahb_hif_ops, + .supports_multipd = true, }, [ATH12K_HW_IPQ5424_HW10] = { .hw_rev = ATH12K_HW_IPQ5424_HW10, .auth_enabled = false, .ops = &ath12k_ahb_hif_ops, + .supports_multipd = false, }, }; @@ -106,6 +108,7 @@ static int ath12k_wifi7_ahb_probe(struct platform_device *pdev) ab->hw_rev = desc->hw_rev; ab->hif.ops = desc->ops; ab_ahb->scm_auth_enabled = desc->auth_enabled; + ab_ahb->supports_multipd = desc->supports_multipd; ab_ahb->userpd_id = ath12k_wifi7_ahb_get_userpd_id(ab); if (!ab_ahb->userpd_id) return -EOPNOTSUPP; From feb3a036d1cd7b708969dfc58feed16c51a496dc Mon Sep 17 00:00:00 2001 From: Devin Wittmayer Date: Fri, 4 Sep 2026 13:28:17 -0700 Subject: [PATCH 065/101] wifi: mac80211: fix monitor filter refcount leak When bringing a monitor interface up and down again, the refcount is leaked for active monitor interfaces, so the hardware filter is never reset. Move the decrement out so it mirrors the increment. Signed-off-by: Devin Wittmayer Link: https://patch.msgid.link/20260904202817.12564-1-lucid_duck@justthetip.ca [rewrite commit message better] Signed-off-by: Johannes Berg --- net/mac80211/iface.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index fdac729ca580..18e62c5ff0fd 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c @@ -630,8 +630,9 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata, bool going_do hw_reconf_flags |= IEEE80211_CONF_CHANGE_MONITOR; } - ieee80211_adjust_monitor_flags(sdata, -1); } + + ieee80211_adjust_monitor_flags(sdata, -1); break; case NL80211_IFTYPE_NAN: /* Check if any open NAN_DATA interfaces */ From eb0a9f21deb3632d9285bbc80a3adb6e87c4c0ab Mon Sep 17 00:00:00 2001 From: Maoyi Xie Date: Fri, 4 Sep 2026 22:03:00 +0800 Subject: [PATCH 066/101] wifi: mac80211_hwsim: send config events to the radio's net namespace hwsim_mcast_config_msg() falls back to genlmsg_multicast() when it has no genl_info, and that only reaches init_net. A radio in another namespace therefore has its DEL_RADIO announced in init_net, while its own namespace hears nothing. Multicast to wiphy_net() instead, the net that GET and DUMP already filter on. Tested with a config-group listener in each namespace, on a base and a patched kernel. Assisted-by: Claude:claude-opus-5 codeql Signed-off-by: Maoyi Xie Link: https://patch.msgid.link/20260904140300.3402053-1-maoyixie.tju@gmail.com Signed-off-by: Johannes Berg --- .../wireless/virtual/mac80211_hwsim_main.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c index 02b6d81cccd1..d77f54daefc5 100644 --- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c +++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c @@ -4396,15 +4396,15 @@ struct hwsim_new_radio_params { bool background_radar; }; -static void hwsim_mcast_config_msg(struct sk_buff *mcast_skb, +static void hwsim_mcast_config_msg(struct sk_buff *mcast_skb, struct net *net, struct genl_info *info) { if (info) genl_notify(&hwsim_genl_family, mcast_skb, info, HWSIM_MCGRP_CONFIG, GFP_KERNEL); else - genlmsg_multicast(&hwsim_genl_family, mcast_skb, 0, - HWSIM_MCGRP_CONFIG, GFP_KERNEL); + genlmsg_multicast_netns(&hwsim_genl_family, net, mcast_skb, 0, + HWSIM_MCGRP_CONFIG, GFP_KERNEL); } static int append_radio_msg(struct sk_buff *skb, int id, @@ -4488,7 +4488,8 @@ static int append_radio_msg(struct sk_buff *skb, int id, return 0; } -static void hwsim_mcast_new_radio(int id, struct genl_info *info, +static void hwsim_mcast_new_radio(int id, struct net *net, + struct genl_info *info, struct hwsim_new_radio_params *param) { struct sk_buff *mcast_skb; @@ -4508,7 +4509,7 @@ static void hwsim_mcast_new_radio(int id, struct genl_info *info, genlmsg_end(mcast_skb, data); - hwsim_mcast_config_msg(mcast_skb, info); + hwsim_mcast_config_msg(mcast_skb, net, info); return; out_err: @@ -6190,7 +6191,7 @@ static int mac80211_hwsim_new_radio(struct genl_info *info, hwsim_radios_generation++; spin_unlock_bh(&hwsim_radio_lock); - hwsim_mcast_new_radio(idx, info, param); + hwsim_mcast_new_radio(idx, wiphy_net(data->hw->wiphy), info, param); return idx; @@ -6208,7 +6209,7 @@ static int mac80211_hwsim_new_radio(struct genl_info *info, } static void hwsim_mcast_del_radio(int id, const char *hwname, - struct genl_info *info) + struct net *net, struct genl_info *info) { struct sk_buff *skb; void *data; @@ -6234,7 +6235,7 @@ static void hwsim_mcast_del_radio(int id, const char *hwname, genlmsg_end(skb, data); - hwsim_mcast_config_msg(skb, info); + hwsim_mcast_config_msg(skb, net, info); return; @@ -6246,7 +6247,7 @@ static void mac80211_hwsim_del_radio(struct mac80211_hwsim_data *data, const char *hwname, struct genl_info *info) { - hwsim_mcast_del_radio(data->idx, hwname, info); + hwsim_mcast_del_radio(data->idx, hwname, wiphy_net(data->hw->wiphy), info); debugfs_remove_recursive(data->debugfs); ieee80211_unregister_hw(data->hw); device_release_driver(data->dev); From 00f2f21c1474ff5b7fa5085204694231cd388519 Mon Sep 17 00:00:00 2001 From: Lachlan Hodges Date: Wed, 9 Sep 2026 17:30:13 +1000 Subject: [PATCH 067/101] wifi: radiotap: add S1G TLVs and channel frequency Add the necessary definitions to create radiotap S1G TLVs and channel flags for S1G sniffers. Signed-off-by: Lachlan Hodges Link: https://patch.msgid.link/20260909073014.53344-2-lachlan.hodges@morsemicro.com Signed-off-by: Johannes Berg --- include/net/ieee80211_radiotap.h | 66 ++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/include/net/ieee80211_radiotap.h b/include/net/ieee80211_radiotap.h index 8bbaf77da7cf..27ee67b38ea8 100644 --- a/include/net/ieee80211_radiotap.h +++ b/include/net/ieee80211_radiotap.h @@ -93,6 +93,7 @@ enum ieee80211_radiotap_presence { IEEE80211_RADIOTAP_RADIOTAP_NAMESPACE = 29, IEEE80211_RADIOTAP_VENDOR_NAMESPACE = 30, IEEE80211_RADIOTAP_EXT = 31, + IEEE80211_RADIOTAP_S1G = 32, IEEE80211_RADIOTAP_EHT_USIG = 33, IEEE80211_RADIOTAP_EHT = 34, IEEE80211_RADIOTAP_UHR_ELR = 37, @@ -112,6 +113,9 @@ enum ieee80211_radiotap_flags { /* for IEEE80211_RADIOTAP_CHANNEL */ enum ieee80211_radiotap_channel_flags { + IEEE80211_CHAN_700MHZ = 0x0001, + IEEE80211_CHAN_800MHZ = 0x0002, + IEEE80211_CHAN_900MHZ = 0x0004, IEEE80211_CHAN_CCK = 0x0020, IEEE80211_CHAN_OFDM = 0x0040, IEEE80211_CHAN_2GHZ = 0x0080, @@ -792,6 +796,68 @@ enum ieee80211_radiotap_uhr_user_info { IEEE80211_RADIOTAP_UHR_USER_INFO_2X_LDPC = 0x02000000, }; +/* + * ieee80211_radiotap_s1g - content of S1G TLV (type 32) + * see www.radiotap.org/fields/S1G.html for details + */ +struct ieee80211_radiotap_s1g { + __le16 known; + __le16 data1; + __le16 data2; +} __packed; + +enum ieee80211_radiotap_s1g_known { + IEEE80211_RADIOTAP_S1G_KNOWN_PPDU_FORMAT = 0x0001, + IEEE80211_RADIOTAP_S1G_KNOWN_RESPONSE_IND = 0x0002, + IEEE80211_RADIOTAP_S1G_KNOWN_GI = 0x0004, + IEEE80211_RADIOTAP_S1G_KNOWN_NSS = 0x0008, + IEEE80211_RADIOTAP_S1G_KNOWN_BW = 0x0010, + IEEE80211_RADIOTAP_S1G_KNOWN_MCS = 0x0020, + IEEE80211_RADIOTAP_S1G_KNOWN_COLOR = 0x0040, + IEEE80211_RADIOTAP_S1G_KNOWN_UPLINK_IND = 0x0080, +}; + +enum ieee80211_radiotap_s1g_data1 { + IEEE80211_RADIOTAP_S1G_DATA1_PPDU_FORMAT = 0x0003, + IEEE80211_RADIOTAP_S1G_DATA1_RESPONSE_IND = 0x000c, + IEEE80211_RADIOTAP_S1G_DATA1_GI = 0x0020, + IEEE80211_RADIOTAP_S1G_DATA1_NSS = 0x00c0, + IEEE80211_RADIOTAP_S1G_DATA1_BW = 0x0f00, + IEEE80211_RADIOTAP_S1G_DATA1_MCS = 0xf000, +}; + +enum ieee80211_radiotap_s1g_data2 { + IEEE80211_RADIOTAP_S1G_DATA2_COLOR = 0x0007, + IEEE80211_RADIOTAP_S1G_DATA2_UPLINK_IND = 0x0008, + IEEE80211_RADIOTAP_S1G_DATA2_RSSI = 0xff00, +}; + +enum ieee80211_radiotap_s1g_ppdu_format { + IEEE80211_RADIOTAP_S1G_PPDU_FORMAT_1M = 0, + IEEE80211_RADIOTAP_S1G_PPDU_FORMAT_SHORT = 1, + IEEE80211_RADIOTAP_S1G_PPDU_FORMAT_LONG = 2, +}; + +enum ieee80211_radiotap_s1g_response_ind { + IEEE80211_RADIOTAP_S1G_RESPONSE_IND_NONE = 0, + IEEE80211_RADIOTAP_S1G_RESPONSE_IND_NDP = 1, + IEEE80211_RADIOTAP_S1G_RESPONSE_IND_NORMAL = 2, + IEEE80211_RADIOTAP_S1G_RESPONSE_IND_LONG = 3, +}; + +enum ieee80211_radiotap_s1g_gi { + IEEE80211_RADIOTAP_S1G_GI_LONG = 0, + IEEE80211_RADIOTAP_S1G_GI_SHORT = 1, +}; + +enum ieee80211_radiotap_s1g_bw { + IEEE80211_RADIOTAP_S1G_BW_1MHZ = 0, + IEEE80211_RADIOTAP_S1G_BW_2MHZ = 1, + IEEE80211_RADIOTAP_S1G_BW_4MHZ = 2, + IEEE80211_RADIOTAP_S1G_BW_8MHZ = 3, + IEEE80211_RADIOTAP_S1G_BW_16MHZ = 4, +}; + /** * ieee80211_get_radiotap_len - get radiotap header length * @data: pointer to the header From bca17bff4e9d530ecd6f395025231854993d2b9e Mon Sep 17 00:00:00 2001 From: Lachlan Hodges Date: Wed, 9 Sep 2026 17:30:14 +1000 Subject: [PATCH 068/101] wifi: mac80211: report 900MHz channel for S1G band radiotap header Currently S1G frames are reported as 2GHz spectrum, use the 900MHz channel flag instead. Signed-off-by: Lachlan Hodges Link: https://patch.msgid.link/20260909073014.53344-3-lachlan.hodges@morsemicro.com Signed-off-by: Johannes Berg --- net/mac80211/rx.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c index 40ed03a775ba..31dc6f83781b 100644 --- a/net/mac80211/rx.c +++ b/net/mac80211/rx.c @@ -452,6 +452,8 @@ ieee80211_add_rx_radiotap_header(struct ieee80211_local *local, if (status->band == NL80211_BAND_5GHZ || status->band == NL80211_BAND_6GHZ) channel_flags |= IEEE80211_CHAN_OFDM | IEEE80211_CHAN_5GHZ; + else if (status->band == NL80211_BAND_S1GHZ) + channel_flags |= IEEE80211_CHAN_OFDM | IEEE80211_CHAN_900MHZ; else if (status->encoding != RX_ENC_LEGACY) channel_flags |= IEEE80211_CHAN_DYN | IEEE80211_CHAN_2GHZ; else if (rate && rate->flags & IEEE80211_RATE_ERP_G) From aaf06d7cb648665bdff53a450809234326cbcfe7 Mon Sep 17 00:00:00 2001 From: Deepanshu Kartikey Date: Thu, 10 Sep 2026 09:40:47 +0530 Subject: [PATCH 069/101] wifi: mac80211: fix stats/preemption in ieee80211_tx_control_port() ieee80211_tx_control_port() can be called from nl80211_tx_control_port() in normal process context via netlink sendmsg(), where BH/preemption is not disabled. dev_sw_netstats_tx_add() uses this_cpu_ptr() internally, which requires preemption to be disabled, triggering a "BUG: using smp_processor_id() in preemptible code" warning. Fix this by moving the local_bh_disable()/local_bh_enable() section to also cover dev_sw_netstats_tx_add() and ieee80211_tpt_led_trig_tx(). Fixes: d5a014204a3b ("wifi: mac80211: tx: simplify control port frame transmission") Reported-by: syzbot+d979bd35c8a76fd1b6f5@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=d979bd35c8a76fd1b6f5 Signed-off-by: Deepanshu Kartikey Link: https://patch.msgid.link/20260910041047.24437-1-kartikey406@gmail.com Signed-off-by: Johannes Berg --- net/mac80211/tx.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index d85c84a738f7..dc122d8cf9ad 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -6654,10 +6654,9 @@ int ieee80211_tx_control_port(struct wiphy *wiphy, struct net_device *dev, return -EINVAL; } + local_bh_disable(); dev_sw_netstats_tx_add(dev, 1, skb->len); ieee80211_tpt_led_trig_tx(local, skb->len); - - local_bh_disable(); ieee80211_xmit(sdata, sta, skb); local_bh_enable(); rcu_read_unlock(); From 5c7b043cb1ef4bca9aa2e793dab862c0989d59c3 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 12 Sep 2026 09:28:40 +0200 Subject: [PATCH 070/101] wifi: mac80211: Fix the return value in mesh_path_add() kernel-doc mesh_path_add() has returned the new (or the already existing) struct mesh_path, or an ERR_PTR(), since commit ae76eef027f7 ("mac80211: return new mpath from mesh_path_add()"), but its kernel-doc still says "Returns: 0 on success". Describe the pointer. Fixes: ae76eef027f7 ("mac80211: return new mpath from mesh_path_add()") Assisted-by: LLM Signed-off-by: Karl Mehltretter Link: https://patch.msgid.link/20260912072840.50710-1-kmehltretter@gmail.com Signed-off-by: Johannes Berg --- net/mac80211/mesh_pathtbl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/mac80211/mesh_pathtbl.c b/net/mac80211/mesh_pathtbl.c index 03171cf00855..a9c5a3250edb 100644 --- a/net/mac80211/mesh_pathtbl.c +++ b/net/mac80211/mesh_pathtbl.c @@ -673,7 +673,7 @@ void mesh_fast_tx_flush_addr(struct ieee80211_sub_if_data *sdata, * @sdata: local subif * @dst: destination address of the path (ETH_ALEN length) * - * Returns: 0 on success + * Returns: the new or the existing path on success, an ERR_PTR() on failure * * State: the initial state of the new path is set to 0 */ From 8ea81827d3537db7cfcc82c008f052bb1bbe7567 Mon Sep 17 00:00:00 2001 From: Julius Bairaktaris Date: Tue, 8 Sep 2026 16:41:55 +0200 Subject: [PATCH 071/101] wifi: mac80211: don't reset the TXQ scheduling round number MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A round is ended when ieee80211_next_txq() meets a txq whose remembered round number is the current one, meaning it has gone full circle. When the AC exceeds the airtime limit, ieee80211_txq_schedule_start() sets the round number to 0 to close the round. The next open round is 1. A txq from an earlier round 1 still remembers the "1", so the next round stops on it and serves nothing. Fix: use a separate open/closed flag and let the round number keep counting. Testing: ~4200 skipped selections per 20s without the fix and 20 skipped selections per 20s with the fix on ath11k (with my AQL series applied) at BE 500/1000. Fixes: 8e4bac067105 ("wifi: mac80211: add a per-PHY AQL limit to improve fairness") Assisted-by: Claude:claude-opus-5 Signed-off-by: Julius Bairaktaris Reviewed-by: Toke HĆøiland-JĆørgensen Link: https://patch.msgid.link/20260908144155.756569-1-julius@bairaktaris.de Signed-off-by: Johannes Berg --- net/mac80211/ieee80211_i.h | 1 + net/mac80211/tx.c | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 53b0b08d3459..65311340db40 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -1468,6 +1468,7 @@ struct ieee80211_local { spinlock_t active_txq_lock[IEEE80211_NUM_ACS]; struct list_head active_txqs[IEEE80211_NUM_ACS]; u16 schedule_round[IEEE80211_NUM_ACS]; + bool schedule_open[IEEE80211_NUM_ACS]; /* serializes ieee80211_handle_wake_tx_queue */ spinlock_t handle_wake_tx_queue_lock; diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index dc122d8cf9ad..06c50183f064 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -4135,7 +4135,7 @@ struct ieee80211_txq *ieee80211_next_txq(struct ieee80211_hw *hw, u8 ac) spin_lock_bh(&local->active_txq_lock[ac]); - if (!local->schedule_round[ac]) + if (!local->schedule_open[ac]) goto out; begin: @@ -4360,12 +4360,12 @@ void ieee80211_txq_schedule_start(struct ieee80211_hw *hw, u8 ac) spin_lock_bh(&local->active_txq_lock[ac]); - if (ieee80211_txq_schedule_airtime_check(local, ac)) { + local->schedule_open[ac] = + ieee80211_txq_schedule_airtime_check(local, ac); + if (local->schedule_open[ac]) { local->schedule_round[ac]++; if (!local->schedule_round[ac]) local->schedule_round[ac]++; - } else { - local->schedule_round[ac] = 0; } spin_unlock_bh(&local->active_txq_lock[ac]); From abc142dab56c649ec11ec3615f2b3b6f50478de5 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 11 Sep 2026 15:46:29 +0200 Subject: [PATCH 072/101] wifi: mac80211_hwsim: fix potential channel crash If wmediumd is used together with a hwsim radio that has channel contexts enabled, the channel pointer can be NULL. Track the frequency so we don't need it at that point, avoiding the crash but still reporting the right freq. Link: https://patch.msgid.link/20260911154632.44cf5d8492c2.I9448bd3cea76eb90554ae73fc77e0a3092ff1271@changeid Signed-off-by: Johannes Berg --- .../wireless/virtual/mac80211_hwsim_main.c | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c index d77f54daefc5..5c9713659ecf 100644 --- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c +++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c @@ -1337,7 +1337,7 @@ mac80211_hwsim_get_tx_rate(struct ieee80211_hw *hw, static void mac80211_hwsim_monitor_rx(struct ieee80211_hw *hw, struct sk_buff *tx_skb, - struct ieee80211_channel *chan) + u32 freq) { struct mac80211_hwsim_data *data = hw->priv; struct sk_buff *skb; @@ -1369,7 +1369,7 @@ static void mac80211_hwsim_monitor_rx(struct ieee80211_hw *hw, hdr->rt_tsft = __mac80211_hwsim_get_tsf(data); hdr->rt_flags = 0; hdr->rt_rate = bitrate / 5; - hdr->rt_channel = cpu_to_le16(chan->center_freq); + hdr->rt_channel = cpu_to_le16(freq); flags = IEEE80211_CHAN_2GHZ; if (txrate && txrate->flags & IEEE80211_RATE_ERP_G) flags |= IEEE80211_CHAN_OFDM; @@ -1387,8 +1387,7 @@ static void mac80211_hwsim_monitor_rx(struct ieee80211_hw *hw, } -static void mac80211_hwsim_monitor_ack(struct ieee80211_channel *chan, - const u8 *addr) +static void mac80211_hwsim_monitor_ack(u32 freq, const u8 *addr) { struct sk_buff *skb; struct hwsim_radiotap_ack_hdr *hdr; @@ -1410,7 +1409,7 @@ static void mac80211_hwsim_monitor_ack(struct ieee80211_channel *chan, (1 << IEEE80211_RADIOTAP_CHANNEL)); hdr->rt_flags = 0; hdr->pad = 0; - hdr->rt_channel = cpu_to_le16(chan->center_freq); + hdr->rt_channel = cpu_to_le16(freq); flags = IEEE80211_CHAN_2GHZ; hdr->rt_chbitmask = cpu_to_le16(flags); @@ -1735,6 +1734,9 @@ static void mac80211_hwsim_tx_frame_nl(struct ieee80211_hw *hw, if (nla_put_u64_64bit(skb, HWSIM_ATTR_COOKIE, cookie, HWSIM_ATTR_PAD)) goto nla_put_failure; + /* track the frequency */ + info->rate_driver_data[1] = (void *)(uintptr_t)channel->center_freq; + genlmsg_end(skb, msg_head); if (hwsim_virtio_enabled) { @@ -1912,7 +1914,7 @@ static bool mac80211_hwsim_tx_frame_no_nl(struct ieee80211_hw *hw, mac80211_hwsim_write_tsf(data, skb, sim_tsf); - mac80211_hwsim_monitor_rx(hw, skb, chan); + mac80211_hwsim_monitor_rx(hw, skb, chan->center_freq); memset(&rx_status, 0, sizeof(rx_status)); rx_status.flag |= RX_FLAG_MACTIME_START; @@ -2276,7 +2278,7 @@ static void mac80211_hwsim_tx(struct ieee80211_hw *hw, ack = mac80211_hwsim_tx_frame_no_nl(hw, skb, channel); if (ack && skb->len >= 16) - mac80211_hwsim_monitor_ack(channel, hdr->addr2); + mac80211_hwsim_monitor_ack(channel->center_freq, hdr->addr2); ieee80211_tx_info_clear_status(txi); @@ -6392,6 +6394,7 @@ static int hwsim_tx_info_frame_received_nl(struct sk_buff *skb_2, int i; unsigned long flags; bool found = false; + u32 freq; if (!info->attrs[HWSIM_ATTR_ADDR_TRANSMITTER] || !info->attrs[HWSIM_ATTR_FLAGS] || @@ -6437,7 +6440,9 @@ static int hwsim_tx_info_frame_received_nl(struct sk_buff *skb_2, if (!found) goto out; - mac80211_hwsim_monitor_rx(data2->hw, skb, data2->channel); + txi = IEEE80211_SKB_CB(skb); + freq = (uintptr_t)txi->rate_driver_data[1]; + mac80211_hwsim_monitor_rx(data2->hw, skb, freq); /* Tx info received because the frame was broadcasted on user space, so we get all the necessary info: tx attempts and skb control buff */ @@ -6446,8 +6451,6 @@ static int hwsim_tx_info_frame_received_nl(struct sk_buff *skb_2, info->attrs[HWSIM_ATTR_TX_INFO]); /* now send back TX status */ - txi = IEEE80211_SKB_CB(skb); - ieee80211_tx_info_clear_status(txi); for (i = 0; i < IEEE80211_TX_MAX_RATES; i++) { @@ -6467,8 +6470,7 @@ static int hwsim_tx_info_frame_received_nl(struct sk_buff *skb_2, if (!(hwsim_flags & HWSIM_TX_CTL_NO_ACK) && (hwsim_flags & HWSIM_TX_STAT_ACK)) { if (skb->len >= 16) - mac80211_hwsim_monitor_ack(data2->channel, - hdr->addr2); + mac80211_hwsim_monitor_ack(freq, hdr->addr2); txi->flags |= IEEE80211_TX_STAT_ACK; } From 41416127e37e48ad27e671829fe97316e8cd630a Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Fri, 11 Sep 2026 15:46:30 +0200 Subject: [PATCH 073/101] wifi: mac80211_hwsim: overwrite report SKB In a new test framework for hwsim I'm working on, tests can modify the frame as it passes through the wmediumd APIs. If then the original frame is reported on the monitor interface, things get a bit confusing. Overwrite the frame that will be reported to the monitor with the frame reported for RX if both transmitter and cookie attributes are present (wmediumd doesn't set them). Also add a HWSIM_ATTR_NO_MONITOR flag to allow a frame to be dropped entirely from monitor report, e.g. in case it was never delivered to any peer. Link: https://patch.msgid.link/20260911154632.c06da6d18055.Iaba24acec4c5765fbad77376418b17d1b863791b@changeid Signed-off-by: Johannes Berg --- drivers/net/wireless/virtual/mac80211_hwsim.h | 5 +- .../wireless/virtual/mac80211_hwsim_main.c | 52 ++++++++++++++++++- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/virtual/mac80211_hwsim.h b/drivers/net/wireless/virtual/mac80211_hwsim.h index a022cd5c0f1c..1d2fd06c585a 100644 --- a/drivers/net/wireless/virtual/mac80211_hwsim.h +++ b/drivers/net/wireless/virtual/mac80211_hwsim.h @@ -3,7 +3,7 @@ * mac80211_hwsim - software simulator of 802.11 radio(s) for mac80211 * Copyright (c) 2008, Jouni Malinen * Copyright (c) 2011, Javier Lopez - * Copyright (C) 2020, 2022-2025 Intel Corporation + * Copyright (C) 2020, 2022-2026 Intel Corporation */ #ifndef __MAC80211_HWSIM_H @@ -162,6 +162,8 @@ enum hwsim_commands { * each radio instead of for the wiphy. * @HWSIM_ATTR_SUPPORT_NAN_DEVICE: support NAN Device virtual interface (flag) * @HWSIM_ATTR_SUPPORT_BACKGROUND_RADAR: background radar/CAC support (flag) + * @HWSIM_ATTR_NO_MONITOR: skip monitor report for this TX status, e.g. in case + * the frame was never reported to any receiver (flag) * @__HWSIM_ATTR_MAX: enum limit */ enum hwsim_attrs { @@ -197,6 +199,7 @@ enum hwsim_attrs { HWSIM_ATTR_MULTI_RADIO, HWSIM_ATTR_SUPPORT_NAN_DEVICE, HWSIM_ATTR_SUPPORT_BACKGROUND_RADAR, + HWSIM_ATTR_NO_MONITOR, __HWSIM_ATTR_MAX, }; #define HWSIM_ATTR_MAX (__HWSIM_ATTR_MAX - 1) diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c index 5c9713659ecf..9f895ad718e9 100644 --- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c +++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c @@ -895,6 +895,7 @@ static const struct nla_policy hwsim_genl_policy[HWSIM_ATTR_MAX + 1] = { [HWSIM_ATTR_MULTI_RADIO] = { .type = NLA_FLAG }, [HWSIM_ATTR_SUPPORT_NAN_DEVICE] = { .type = NLA_FLAG }, [HWSIM_ATTR_SUPPORT_BACKGROUND_RADAR] = { .type = NLA_FLAG }, + [HWSIM_ATTR_NO_MONITOR] = { .type = NLA_FLAG }, }; #if IS_REACHABLE(CONFIG_VIRTIO) @@ -6394,6 +6395,7 @@ static int hwsim_tx_info_frame_received_nl(struct sk_buff *skb_2, int i; unsigned long flags; bool found = false; + bool no_monitor; u32 freq; if (!info->attrs[HWSIM_ATTR_ADDR_TRANSMITTER] || @@ -6406,6 +6408,7 @@ static int hwsim_tx_info_frame_received_nl(struct sk_buff *skb_2, src = (void *)nla_data(info->attrs[HWSIM_ATTR_ADDR_TRANSMITTER]); hwsim_flags = nla_get_u32(info->attrs[HWSIM_ATTR_FLAGS]); ret_skb_cookie = nla_get_u64(info->attrs[HWSIM_ATTR_COOKIE]); + no_monitor = nla_get_flag(info->attrs[HWSIM_ATTR_NO_MONITOR]); data2 = get_hwsim_data_ref_from_addr(src); if (!data2) @@ -6442,7 +6445,9 @@ static int hwsim_tx_info_frame_received_nl(struct sk_buff *skb_2, txi = IEEE80211_SKB_CB(skb); freq = (uintptr_t)txi->rate_driver_data[1]; - mac80211_hwsim_monitor_rx(data2->hw, skb, freq); + + if (!no_monitor) + mac80211_hwsim_monitor_rx(data2->hw, skb, freq); /* Tx info received because the frame was broadcasted on user space, so we get all the necessary info: tx attempts and skb control buff */ @@ -6538,6 +6543,51 @@ static int hwsim_cloned_frame_received_nl(struct sk_buff *skb_2, goto out; } + /* look for the skb matching the cookie passed back from user */ + if (info->attrs[HWSIM_ATTR_COOKIE] && + info->attrs[HWSIM_ATTR_ADDR_TRANSMITTER]) { + u64 cookie = nla_get_u64(info->attrs[HWSIM_ATTR_COOKIE]); + struct sk_buff *orig_skb, *found = NULL; + struct mac80211_hwsim_data *txdata; + struct ieee80211_tx_info *txi; + const u8 *transmitter; + unsigned long flags; + + transmitter = nla_data(info->attrs[HWSIM_ATTR_ADDR_TRANSMITTER]); + txdata = get_hwsim_data_ref_from_addr(transmitter); + if (!txdata || txdata->netgroup != data2->netgroup) + goto out; + + spin_lock_irqsave(&txdata->pending.lock, flags); + skb_queue_walk(&txdata->pending, orig_skb) { + uintptr_t skb_cookie; + + txi = IEEE80211_SKB_CB(orig_skb); + skb_cookie = (uintptr_t)txi->rate_driver_data[0]; + + if (skb_cookie == cookie) { + found = orig_skb; + break; + } + } + + /* that's weird */ + if (!found) { + spin_unlock_irqrestore(&txdata->pending.lock, flags); + goto out; + } + + if (frame_data_len > found->len && + pskb_expand_head(found, 0, frame_data_len - found->len, + GFP_ATOMIC)) { + spin_unlock_irqrestore(&txdata->pending.lock, flags); + goto out; + } + skb_trim(found, 0); + skb_put_data(found, frame_data, frame_data_len); + spin_unlock_irqrestore(&txdata->pending.lock, flags); + } + /* check if radio is configured properly */ if ((data2->idle && !data2->tmp_chan) || !data2->started) From 17402a869778f6c17b88fd360d7a96378779f97c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Pouiller?= Date: Sun, 6 Sep 2026 22:47:28 +0200 Subject: [PATCH 074/101] wifi: wfx: fix use-after-free of the cooling work on device removal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the device reports that it is too hot, wfx_suspend_hot_dev() blocks the Tx queues and arms cooling_timeout_work with a 10s delay. If the device recovers a normal temperature before the delay expires, the work is canceled. Else, the work declares the chip frozen and unblocks the Tx queues. However, this work is never canceled when the device is removed. cooling_timeout_work is queued on the system workqueue, while struct wfx_dev is released by wfx_free_common() (through ieee80211_free_hw()). So, if the device is unbound during this 10s window, the work fires after struct wfx_dev has been freed and dereferences it. Cancel the work during the teardown. It has to be done after wfx_bh_unregister(): the "device too hot" indication is processed by the bh, so canceling the work earlier would allow the bh to rearm it. On the other hand, the work calls wfx_tx_unlock(), which may in turn call wfx_bh_request_tx(). So it has to be canceled before bh_wq is destroyed. Note that the Tx queues are intentionally left blocked: the device is going away, so there is nothing to unblock. The issue was reported by the Sashiko review bot and the fix has been written by Copilot (including the commit log). The use-case is difficult to reproduce, so this code has not been tested. However, I don't believe this patch could cause any regression. Fixes: 1d52d29983e5d ("staging: wfx: add support for 'device too hot' indication") Assisted-by: Sashiko:gemini-3.1-pro-preview Assisted-by: Copilot:claude-opus-5 Signed-off-by: JĆ©rĆ“me Pouiller Tested-by: Alexander Sverdlin Reviewed-by: Alexander Sverdlin Link: https://patch.msgid.link/20260906204831.949692-2-jerome.pouiller@silabs.com Signed-off-by: Johannes Berg --- drivers/net/wireless/silabs/wfx/main.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/wireless/silabs/wfx/main.c b/drivers/net/wireless/silabs/wfx/main.c index 4e99fe7e5bb7..b6fd9d4c1f5f 100644 --- a/drivers/net/wireless/silabs/wfx/main.c +++ b/drivers/net/wireless/silabs/wfx/main.c @@ -487,6 +487,7 @@ int wfx_probe(struct wfx_dev *wdev) wdev->hwbus_ops->irq_unsubscribe(wdev->hwbus_priv); bh_unregister: wfx_bh_unregister(wdev); + cancel_delayed_work_sync(&wdev->cooling_timeout_work); destroy_workqueue(wdev->bh_wq); return err; } @@ -497,6 +498,7 @@ void wfx_release(struct wfx_dev *wdev) wfx_hif_shutdown(wdev); wdev->hwbus_ops->irq_unsubscribe(wdev->hwbus_priv); wfx_bh_unregister(wdev); + cancel_delayed_work_sync(&wdev->cooling_timeout_work); destroy_workqueue(wdev->bh_wq); } From a137e59948ee2b5e54691738df44c02cfbef3823 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Pouiller?= Date: Sun, 6 Sep 2026 22:47:29 +0200 Subject: [PATCH 075/101] wifi: wfx: fix error code on unsupported firmware MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 'secure link' feature is not supported by upstream (the reference code relies on mbedTLS to implement it, which is not an option for the Linux kernel). If the firmware enforce the 'secure link' feature, wfx_probe() exits early. However, err still holds the value returned by the previous call to wait_for_completion_timeout(), which is the number of jiffies left before the timeout, hence strictly positive (the zero case is handled just above). wfx_probe() therefore returns a positive value. The bus probe functions forward it as-is and the driver core negates it (see really_probe()). The device does not get bound, which is the expected outcome, but the reported error code is a meaningless negative jiffies count instead of an errno. Return -EOPNOTSUPP, as already done a few lines above when the firmware API version is not supported. Copilot reviewed the code and wrote some parts of the log. Reported-by: Alexander Sverdlin Closes: https://lore.kernel.org/all/cc4de4e40e3ca50e7288be2b9067802576567c25.camel@siemens.com/ Assisted-by: Copilot:claude-opus-5 Signed-off-by: JĆ©rĆ“me Pouiller Reviewed-by: Alexander Sverdlin Link: https://patch.msgid.link/20260906204831.949692-3-jerome.pouiller@silabs.com Signed-off-by: Johannes Berg --- drivers/net/wireless/silabs/wfx/main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/silabs/wfx/main.c b/drivers/net/wireless/silabs/wfx/main.c index b6fd9d4c1f5f..16d5b51f6f03 100644 --- a/drivers/net/wireless/silabs/wfx/main.c +++ b/drivers/net/wireless/silabs/wfx/main.c @@ -411,6 +411,7 @@ int wfx_probe(struct wfx_dev *wdev) if (wdev->hw_caps.link_mode == SEC_LINK_ENFORCED) { dev_err(wdev->dev, "chip require secure_link, but can't negotiate it\n"); + err = -EOPNOTSUPP; goto irq_unsubscribe; } From 27b084bdd009c5c82346d39d7773bab3191dd299 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Fri, 4 Sep 2026 14:16:28 -0700 Subject: [PATCH 076/101] wifi: rt2x00: Use device-managed register buffers The rt2x00 PCI and USB probe paths allocate EEPROM and RF storage with plain kzalloc() and then free it from bus-specific teardown helpers. The USB path also manages the CSR cache the same way. These buffers are tied to the device lifetime, so the explicit free paths add probe and disconnect cleanup without providing separate ownership. Allocate the buffers with devm_kzalloc() before the mac80211 hardware is allocated, then attach the resulting storage to struct rt2x00_dev after the driver-private state exists. This lets driver detach and probe failure rely on device-managed cleanup and removes the duplicated bus-specific buffer freeing. Assisted-by: Codex:GPT-5.5 Signed-off-by: Rosen Penev Acked-by: Stanislaw Gruszka Link: https://patch.msgid.link/20260904211628.83180-1-rosenp@gmail.com Signed-off-by: Johannes Berg --- .../net/wireless/ralink/rt2x00/rt2x00pci.c | 104 ++++-------------- .../net/wireless/ralink/rt2x00/rt2x00usb.c | 67 ++++------- 2 files changed, 43 insertions(+), 128 deletions(-) diff --git a/drivers/net/wireless/ralink/rt2x00/rt2x00pci.c b/drivers/net/wireless/ralink/rt2x00/rt2x00pci.c index cabeef0dde45..d4635b531481 100644 --- a/drivers/net/wireless/ralink/rt2x00/rt2x00pci.c +++ b/drivers/net/wireless/ralink/rt2x00/rt2x00pci.c @@ -19,84 +19,50 @@ #include "rt2x00.h" #include "rt2x00pci.h" -/* - * PCI driver handlers. - */ -static void rt2x00pci_free_reg(struct rt2x00_dev *rt2x00dev) -{ - kfree(rt2x00dev->rf); - rt2x00dev->rf = NULL; - - kfree(rt2x00dev->eeprom); - rt2x00dev->eeprom = NULL; - - if (rt2x00dev->csr.base) { - iounmap(rt2x00dev->csr.base); - rt2x00dev->csr.base = NULL; - } -} - -static int rt2x00pci_alloc_reg(struct rt2x00_dev *rt2x00dev) -{ - struct pci_dev *pci_dev = to_pci_dev(rt2x00dev->dev); - - rt2x00dev->csr.base = pci_ioremap_bar(pci_dev, 0); - if (!rt2x00dev->csr.base) - goto exit; - - rt2x00dev->eeprom = kzalloc(rt2x00dev->ops->eeprom_size, GFP_KERNEL); - if (!rt2x00dev->eeprom) - goto exit; - - rt2x00dev->rf = kzalloc(rt2x00dev->ops->rf_size, GFP_KERNEL); - if (!rt2x00dev->rf) - goto exit; - - return 0; - -exit: - rt2x00_probe_err("Failed to allocate registers\n"); - - rt2x00pci_free_reg(rt2x00dev); - - return -ENOMEM; -} - int rt2x00pci_probe(struct pci_dev *pci_dev, const struct rt2x00_ops *ops) { - struct ieee80211_hw *hw; struct rt2x00_dev *rt2x00dev; + struct ieee80211_hw *hw; + void __iomem *base; + __le16 *eeprom; int retval; u16 chip; + u32 *rf; - retval = pci_enable_device(pci_dev); + retval = pcim_enable_device(pci_dev); if (retval) { rt2x00_probe_err("Enable device failed\n"); return retval; } - retval = pci_request_regions(pci_dev, pci_name(pci_dev)); - if (retval) { - rt2x00_probe_err("PCI request regions failed\n"); - goto exit_disable_device; + base = pcim_iomap_region(pci_dev, 0, pci_name(pci_dev)); + if (IS_ERR(base)) { + rt2x00_probe_err("PCI iomap region failed\n"); + return PTR_ERR(base); } pci_set_master(pci_dev); - if (pci_set_mwi(pci_dev)) + if (pcim_set_mwi(pci_dev)) rt2x00_probe_err("MWI not available\n"); if (dma_set_mask(&pci_dev->dev, DMA_BIT_MASK(32))) { rt2x00_probe_err("PCI DMA not supported\n"); - retval = -EIO; - goto exit_release_regions; + return -EIO; } + eeprom = devm_kzalloc(&pci_dev->dev, ops->eeprom_size, GFP_KERNEL); + if (!eeprom) + return -ENOMEM; + + rf = devm_kzalloc(&pci_dev->dev, ops->rf_size, GFP_KERNEL); + if (!rf) + return -ENOMEM; + hw = ieee80211_alloc_hw(sizeof(struct rt2x00_dev), ops->hw); if (!hw) { rt2x00_probe_err("Failed to allocate hardware\n"); - retval = -ENOMEM; - goto exit_release_regions; + return -ENOMEM; } pci_set_drvdata(pci_dev, hw); @@ -105,18 +71,17 @@ int rt2x00pci_probe(struct pci_dev *pci_dev, const struct rt2x00_ops *ops) rt2x00dev->dev = &pci_dev->dev; rt2x00dev->ops = ops; rt2x00dev->hw = hw; + rt2x00dev->csr.base = base; rt2x00dev->irq = pci_dev->irq; rt2x00dev->name = ops->name; + rt2x00dev->eeprom = eeprom; + rt2x00dev->rf = rf; if (pci_is_pcie(pci_dev)) rt2x00_set_chip_intf(rt2x00dev, RT2X00_CHIP_INTF_PCIE); else rt2x00_set_chip_intf(rt2x00dev, RT2X00_CHIP_INTF_PCI); - retval = rt2x00pci_alloc_reg(rt2x00dev); - if (retval) - goto exit_free_device; - /* * Because rt3290 chip use different efuse offset to read efuse data. * So before read efuse it need to indicate it is the @@ -127,23 +92,13 @@ int rt2x00pci_probe(struct pci_dev *pci_dev, const struct rt2x00_ops *ops) retval = rt2x00lib_probe_dev(rt2x00dev); if (retval) - goto exit_free_reg; + goto exit_free_device; return 0; -exit_free_reg: - rt2x00pci_free_reg(rt2x00dev); - exit_free_device: ieee80211_free_hw(hw); -exit_release_regions: - pci_clear_mwi(pci_dev); - pci_release_regions(pci_dev); - -exit_disable_device: - pci_disable_device(pci_dev); - return retval; } EXPORT_SYMBOL_GPL(rt2x00pci_probe); @@ -153,19 +108,8 @@ void rt2x00pci_remove(struct pci_dev *pci_dev) struct ieee80211_hw *hw = pci_get_drvdata(pci_dev); struct rt2x00_dev *rt2x00dev = hw->priv; - /* - * Free all allocated data. - */ rt2x00lib_remove_dev(rt2x00dev); - rt2x00pci_free_reg(rt2x00dev); ieee80211_free_hw(hw); - - /* - * Free the PCI device data. - */ - pci_clear_mwi(pci_dev); - pci_disable_device(pci_dev); - pci_release_regions(pci_dev); } EXPORT_SYMBOL_GPL(rt2x00pci_remove); diff --git a/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c b/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c index 47e427ea8622..c9fbc3710fd8 100644 --- a/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c +++ b/drivers/net/wireless/ralink/rt2x00/rt2x00usb.c @@ -755,55 +755,31 @@ void rt2x00usb_uninitialize(struct rt2x00_dev *rt2x00dev) } EXPORT_SYMBOL_GPL(rt2x00usb_uninitialize); -/* - * USB driver handlers. - */ -static void rt2x00usb_free_reg(struct rt2x00_dev *rt2x00dev) -{ - kfree(rt2x00dev->rf); - rt2x00dev->rf = NULL; - - kfree(rt2x00dev->eeprom); - rt2x00dev->eeprom = NULL; - - kfree(rt2x00dev->csr.cache); - rt2x00dev->csr.cache = NULL; -} - -static int rt2x00usb_alloc_reg(struct rt2x00_dev *rt2x00dev) -{ - rt2x00dev->csr.cache = kzalloc(CSR_CACHE_SIZE, GFP_KERNEL); - if (!rt2x00dev->csr.cache) - goto exit; - - rt2x00dev->eeprom = kzalloc(rt2x00dev->ops->eeprom_size, GFP_KERNEL); - if (!rt2x00dev->eeprom) - goto exit; - - rt2x00dev->rf = kzalloc(rt2x00dev->ops->rf_size, GFP_KERNEL); - if (!rt2x00dev->rf) - goto exit; - - return 0; - -exit: - rt2x00_probe_err("Failed to allocate registers\n"); - - rt2x00usb_free_reg(rt2x00dev); - - return -ENOMEM; -} - int rt2x00usb_probe(struct usb_interface *usb_intf, const struct rt2x00_ops *ops) { struct usb_device *usb_dev = interface_to_usbdev(usb_intf); - struct ieee80211_hw *hw; struct rt2x00_dev *rt2x00dev; + struct ieee80211_hw *hw; + __le16 *eeprom; + void *cache; int retval; + u32 *rf; usb_reset_device(usb_dev); + cache = devm_kzalloc(&usb_intf->dev, CSR_CACHE_SIZE, GFP_KERNEL); + if (!cache) + return -ENOMEM; + + eeprom = devm_kzalloc(&usb_intf->dev, ops->eeprom_size, GFP_KERNEL); + if (!eeprom) + return -ENOMEM; + + rf = devm_kzalloc(&usb_intf->dev, ops->rf_size, GFP_KERNEL); + if (!rf) + return -ENOMEM; + hw = ieee80211_alloc_hw(struct_size(rt2x00dev, anchor, 1), ops->hw); if (!hw) { rt2x00_probe_err("Failed to allocate hardware\n"); @@ -816,16 +792,15 @@ int rt2x00usb_probe(struct usb_interface *usb_intf, rt2x00dev->dev = &usb_intf->dev; rt2x00dev->ops = ops; rt2x00dev->hw = hw; + rt2x00dev->csr.cache = cache; + rt2x00dev->eeprom = eeprom; + rt2x00dev->rf = rf; rt2x00_set_chip_intf(rt2x00dev, RT2X00_CHIP_INTF_USB); INIT_WORK(&rt2x00dev->rxdone_work, rt2x00usb_work_rxdone); INIT_WORK(&rt2x00dev->txdone_work, rt2x00usb_work_txdone); - retval = rt2x00usb_alloc_reg(rt2x00dev); - if (retval) - goto exit_free_device; - init_usb_anchor(rt2x00dev->anchor); retval = rt2x00lib_probe_dev(rt2x00dev); @@ -836,9 +811,6 @@ int rt2x00usb_probe(struct usb_interface *usb_intf, exit_free_anchor: usb_kill_anchored_urbs(rt2x00dev->anchor); - rt2x00usb_free_reg(rt2x00dev); - -exit_free_device: ieee80211_free_hw(hw); usb_set_intfdata(usb_intf, NULL); @@ -855,7 +827,6 @@ void rt2x00usb_disconnect(struct usb_interface *usb_intf) * Free all allocated data. */ rt2x00lib_remove_dev(rt2x00dev); - rt2x00usb_free_reg(rt2x00dev); ieee80211_free_hw(hw); usb_set_intfdata(usb_intf, NULL); From c924efeda80aa098599815319c57d6c937ad24f5 Mon Sep 17 00:00:00 2001 From: Carella Chen Date: Fri, 11 Sep 2026 14:56:52 +0800 Subject: [PATCH 077/101] wifi: nl80211: add fast roam offload extended feature Drivers can offload FT or OKC roaming without necessarily advertising full 802.1X 4-way handshake offload. Add an extended feature bit for that capability and allow PMK configuration when it is present. This gives userspace a way to provide PMK or PMK-R0 material needed by fast roaming firmware implementations while keeping the existing 4-way handshake offload capability unchanged. Assisted-by: GitHub-Copilot-CLI:gpt-5.5 Signed-off-by: Carella Chen Signed-off-by: Jason Huang Acked-by: Arend van Spriel Link: https://patch.msgid.link/20260911065656.1269623-2-Jason.Huang2@infineon.com Signed-off-by: Johannes Berg --- include/uapi/linux/nl80211.h | 5 +++++ net/wireless/nl80211.c | 10 ++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index d8fefefc11a9..32553cacf988 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -7114,6 +7114,10 @@ enum nl80211_feature_flags { * @NL80211_EXT_FEATURE_PROBE_AP: Driver supports probing the associated AP * in STA mode using @NL80211_CMD_PROBE_PEER. * + * @NL80211_EXT_FEATURE_FAST_ROAM_OFFLOAD: Driver supports fast roaming + * offload in station mode, including Fast Transition or Opportunistic + * Key Caching. + * * @NUM_NL80211_EXT_FEATURES: number of extended features. * @MAX_NL80211_EXT_FEATURES: highest extended feature index. */ @@ -7196,6 +7200,7 @@ enum nl80211_ext_feature_index { NL80211_EXT_FEATURE_ROC_ADDR_FILTER, NL80211_EXT_FEATURE_SET_KEY_LTF_SEED, NL80211_EXT_FEATURE_PROBE_AP, + NL80211_EXT_FEATURE_FAST_ROAM_OFFLOAD, /* add new features before the definition below */ NUM_NL80211_EXT_FEATURES, diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index e585da1d79c9..b081eeaf7016 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -18447,6 +18447,7 @@ static int nl80211_set_multicast_to_unicast(struct sk_buff *skb, static int nl80211_set_pmk(struct sk_buff *skb, struct genl_info *info) { struct cfg80211_registered_device *rdev = info->user_ptr[0]; + struct wiphy *wiphy = &rdev->wiphy; struct net_device *dev = info->user_ptr[1]; struct wireless_dev *wdev = dev->ieee80211_ptr; struct cfg80211_pmk_conf pmk_conf = {}; @@ -18455,7 +18456,9 @@ static int nl80211_set_pmk(struct sk_buff *skb, struct genl_info *info) wdev->iftype != NL80211_IFTYPE_P2P_CLIENT) return -EOPNOTSUPP; - if (!wiphy_ext_feature_isset(&rdev->wiphy, + if (!wiphy_ext_feature_isset(wiphy, + NL80211_EXT_FEATURE_FAST_ROAM_OFFLOAD) && + !wiphy_ext_feature_isset(wiphy, NL80211_EXT_FEATURE_4WAY_HANDSHAKE_STA_1X)) return -EOPNOTSUPP; @@ -18485,6 +18488,7 @@ static int nl80211_set_pmk(struct sk_buff *skb, struct genl_info *info) static int nl80211_del_pmk(struct sk_buff *skb, struct genl_info *info) { struct cfg80211_registered_device *rdev = info->user_ptr[0]; + struct wiphy *wiphy = &rdev->wiphy; struct net_device *dev = info->user_ptr[1]; struct wireless_dev *wdev = dev->ieee80211_ptr; const u8 *aa; @@ -18493,7 +18497,9 @@ static int nl80211_del_pmk(struct sk_buff *skb, struct genl_info *info) wdev->iftype != NL80211_IFTYPE_P2P_CLIENT) return -EOPNOTSUPP; - if (!wiphy_ext_feature_isset(&rdev->wiphy, + if (!wiphy_ext_feature_isset(wiphy, + NL80211_EXT_FEATURE_FAST_ROAM_OFFLOAD) && + !wiphy_ext_feature_isset(wiphy, NL80211_EXT_FEATURE_4WAY_HANDSHAKE_STA_1X)) return -EOPNOTSUPP; From b1590c5eef0fadb5bc33689f9d98ce58b91154e7 Mon Sep 17 00:00:00 2001 From: Carella Chen Date: Fri, 11 Sep 2026 14:56:53 +0800 Subject: [PATCH 078/101] wifi: brcmfmac: detect firmware FBT and OKC support Some firmware advertises Fast BSS Transition and Opportunistic Key Caching support through the firmware capability string. Track those capabilities so later roaming offload handling can be enabled only when firmware reports support. Assisted-by: GitHub-Copilot-CLI:gpt-5.5 Signed-off-by: Carella Chen Signed-off-by: Jason Huang Acked-by: Arend van Spriel Link: https://patch.msgid.link/20260911065656.1269623-3-Jason.Huang2@infineon.com Signed-off-by: Johannes Berg --- drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c | 2 ++ drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c index 488364ef8ff2..cc19e5e7ebf5 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c @@ -45,6 +45,8 @@ static const struct brcmf_feat_fwcap brcmf_fwcap_map[] = { { BRCMF_FEAT_SAE, "sae " }, { BRCMF_FEAT_FWAUTH, "idauth" }, { BRCMF_FEAT_SAE_EXT, "sae_ext" }, + { BRCMF_FEAT_FBT, "fbt " }, + { BRCMF_FEAT_OKC, "okc" }, }; #ifdef DEBUG diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h index 31f8695ca417..8165a286b630 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h @@ -59,7 +59,9 @@ BRCMF_FEAT_DEF(SCAN_V2) \ BRCMF_FEAT_DEF(PMKID_V2) \ BRCMF_FEAT_DEF(PMKID_V3) \ - BRCMF_FEAT_DEF(SAE_EXT) + BRCMF_FEAT_DEF(SAE_EXT) \ + BRCMF_FEAT_DEF(FBT) \ + BRCMF_FEAT_DEF(OKC) /* * Quirks: From f84e486e12cfe9de68c250d028acb1a893900167 Mon Sep 17 00:00:00 2001 From: Darren Li Date: Fri, 11 Sep 2026 14:56:54 +0800 Subject: [PATCH 079/101] wifi: brcmfmac: add PMK programming for firmware roaming offload FT and OKC roaming offload need PMK material in firmware even when the initial 802.1X exchange is handled by userspace. Add a roaming firmware-supplicant profile state for those connections and allow PMK configuration while firmware FT or OKC state is active. Program the OKC PMK through the okc_info_pmk iovar before setting the regular PMK when firmware reports OKC state. Only select the roaming firmware-supplicant state when firmware advertises FBT or OKC support; devices without those capabilities keep the profile in the userspace supplicant state and do not issue roaming-offload specific iovars on every connect. For FT-PSK with firmware supplicant support, keep the profile in the PSK firmware-supplicant state. Assisted-by: GitHub-Copilot-CLI:gpt-5.5 Signed-off-by: Darren Li Signed-off-by: Chung-Hsien Hsu Signed-off-by: Chi-hsien Lin Co-developed-by: Carella Chen Signed-off-by: Carella Chen Co-developed-by: Shelley Yang Signed-off-by: Shelley Yang Signed-off-by: Jason Huang Acked-by: Arend van Spriel Link: https://patch.msgid.link/20260911065656.1269623-4-Jason.Huang2@infineon.com Signed-off-by: Johannes Berg --- .../broadcom/brcm80211/brcmfmac/cfg80211.c | 56 +++++++++++++++++-- .../broadcom/brcm80211/brcmfmac/cfg80211.h | 5 +- 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c index 872c48806d09..f2916cc9e5fc 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -2112,9 +2112,11 @@ brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme) struct brcmf_pub *drvr = ifp->drvr; s32 val; s32 err; + s32 okc_enable; const struct brcmf_tlv *rsn_ie; const u8 *ie; u32 ie_len; + bool fwsup_roam; u32 offset; u16 rsn_cap; u32 mfp; @@ -2122,6 +2124,9 @@ brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme) profile->use_fwsup = BRCMF_PROFILE_FWSUP_NONE; profile->is_ft = false; + profile->is_okc = false; + fwsup_roam = brcmf_feat_is_enabled(ifp, BRCMF_FEAT_FBT) || + brcmf_feat_is_enabled(ifp, BRCMF_FEAT_OKC); if (!sme->crypto.n_akm_suites) return 0; @@ -2138,6 +2143,8 @@ brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme) val = WPA_AUTH_UNSPECIFIED; if (sme->want_1x) profile->use_fwsup = BRCMF_PROFILE_FWSUP_1X; + else if (fwsup_roam) + profile->use_fwsup = BRCMF_PROFILE_FWSUP_ROAM; break; case WLAN_AKM_SUITE_PSK: val = WPA_AUTH_PSK; @@ -2153,11 +2160,15 @@ brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme) val = WPA2_AUTH_UNSPECIFIED; if (sme->want_1x) profile->use_fwsup = BRCMF_PROFILE_FWSUP_1X; + else if (fwsup_roam) + profile->use_fwsup = BRCMF_PROFILE_FWSUP_ROAM; break; case WLAN_AKM_SUITE_8021X_SHA256: val = WPA2_AUTH_1X_SHA256; if (sme->want_1x) profile->use_fwsup = BRCMF_PROFILE_FWSUP_1X; + else if (fwsup_roam) + profile->use_fwsup = BRCMF_PROFILE_FWSUP_ROAM; break; case WLAN_AKM_SUITE_PSK_SHA256: val = WPA2_AUTH_PSK_SHA256; @@ -2170,10 +2181,16 @@ brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme) profile->is_ft = true; if (sme->want_1x) profile->use_fwsup = BRCMF_PROFILE_FWSUP_1X; + else if (fwsup_roam) + profile->use_fwsup = BRCMF_PROFILE_FWSUP_ROAM; break; case WLAN_AKM_SUITE_FT_PSK: val = WPA2_AUTH_PSK | WPA2_AUTH_FT; profile->is_ft = true; + if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_FWSUP)) + profile->use_fwsup = BRCMF_PROFILE_FWSUP_PSK; + else if (fwsup_roam) + profile->use_fwsup = BRCMF_PROFILE_FWSUP_ROAM; break; case WLAN_AKM_SUITE_WFA_DPP: val = WFA_AUTH_DPP; @@ -2204,8 +2221,22 @@ brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme) if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_1X) brcmf_dbg(INFO, "using 1X offload\n"); + + if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_1X || + profile->use_fwsup == BRCMF_PROFILE_FWSUP_ROAM) { + err = brcmf_fil_bsscfg_int_get(ifp, "okc_enable", + &okc_enable); + if (err) { + bphy_err(drvr, "get okc_enable failed (%d)\n", err); + } else { + brcmf_dbg(INFO, "okc_enable (%d)\n", okc_enable); + profile->is_okc = okc_enable; + } + } if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_SAE) brcmf_dbg(INFO, "using SAE offload\n"); + if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_ROAM) + brcmf_dbg(INFO, "using roaming offload\n"); if (!brcmf_feat_is_enabled(ifp, BRCMF_FEAT_MFP)) goto skip_mfp_config; @@ -2494,13 +2525,14 @@ brcmf_cfg80211_connect(struct wiphy *wiphy, struct net_device *ndev, if (sme->crypto.psk && !is_sae_akm && profile->use_fwsup != BRCMF_PROFILE_FWSUP_SAE) { - if (WARN_ON(profile->use_fwsup != - BRCMF_PROFILE_FWSUP_NONE)) { + if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_NONE) { + brcmf_dbg(INFO, "using PSK offload\n"); + profile->use_fwsup = BRCMF_PROFILE_FWSUP_PSK; + } else if (WARN_ON(profile->use_fwsup != + BRCMF_PROFILE_FWSUP_PSK)) { err = -EINVAL; goto done; } - brcmf_dbg(INFO, "using PSK offload\n"); - profile->use_fwsup = BRCMF_PROFILE_FWSUP_PSK; } if (profile->use_fwsup != BRCMF_PROFILE_FWSUP_NONE) { /* enable firmware supplicant for this interface */ @@ -5953,17 +5985,29 @@ static int brcmf_cfg80211_set_pmk(struct wiphy *wiphy, struct net_device *dev, const struct cfg80211_pmk_conf *conf) { struct brcmf_if *ifp; + struct brcmf_pub *drvr; + int ret; brcmf_dbg(TRACE, "enter\n"); - /* expect using firmware supplicant for 1X */ ifp = netdev_priv(dev); - if (WARN_ON(ifp->vif->profile.use_fwsup != BRCMF_PROFILE_FWSUP_1X)) + drvr = ifp->drvr; + if (WARN_ON(ifp->vif->profile.use_fwsup != BRCMF_PROFILE_FWSUP_1X && + !ifp->vif->profile.is_ft && + !ifp->vif->profile.is_okc)) return -EINVAL; if (conf->pmk_len > BRCMF_WSEC_MAX_PSK_LEN) return -ERANGE; + if (ifp->vif->profile.is_okc) { + ret = brcmf_fil_iovar_data_set(ifp, "okc_info_pmk", + conf->pmk, conf->pmk_len); + if (ret < 0) + bphy_err(drvr, "okc_info_pmk iovar failed: ret=%d\n", + ret); + } + return brcmf_set_pmk(ifp, conf->pmk, conf->pmk_len); } diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h index 63e534523f51..1aa99390a951 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h @@ -127,7 +127,8 @@ enum brcmf_profile_fwsup { BRCMF_PROFILE_FWSUP_NONE, BRCMF_PROFILE_FWSUP_PSK, BRCMF_PROFILE_FWSUP_1X, - BRCMF_PROFILE_FWSUP_SAE + BRCMF_PROFILE_FWSUP_SAE, + BRCMF_PROFILE_FWSUP_ROAM }; /** @@ -164,6 +165,7 @@ enum brcmf_mgmt_tx_status { * @bssid: bssid of joined/joining ibss. * @sec: security information. * @key: key information + * @is_okc: OKC is used for current connection. */ struct brcmf_cfg80211_profile { u8 bssid[ETH_ALEN]; @@ -172,6 +174,7 @@ struct brcmf_cfg80211_profile { enum brcmf_profile_fwsup use_fwsup; u16 use_fwauth; bool is_ft; + bool is_okc; }; /** From a2c7d6fb835477cd188f8c324c63990928b6c744 Mon Sep 17 00:00:00 2001 From: Darren Li Date: Fri, 11 Sep 2026 14:56:55 +0800 Subject: [PATCH 080/101] wifi: brcmfmac: report port authorization after offloaded roaming Firmware can complete FT or OKC roaming before the driver reports the connect or roam event to cfg80211. Detect those successful offloaded cases when the profile uses firmware 1X or roaming offload and the association request carries PMK cache state, FT is in use, or OKC is enabled. After reporting the connect or roam event, call cfg80211_port_authorized() so nl80211 emits the dedicated NL80211_CMD_PORT_AUTHORIZED event instead of reusing the reserved NL80211_ATTR_PORT_AUTHORIZED flag in CONNECT/ROAM notifications. Assisted-by: GitHub-Copilot-CLI:gpt-5.5 Signed-off-by: Darren Li Signed-off-by: Chung-Hsien Hsu Signed-off-by: Chi-hsien Lin Signed-off-by: Carella Chen Signed-off-by: Jason Huang Acked-by: Arend van Spriel Link: https://patch.msgid.link/20260911065656.1269623-5-Jason.Huang2@infineon.com Signed-off-by: Johannes Berg --- .../broadcom/brcm80211/brcmfmac/cfg80211.c | 67 +++++++++++++++++-- 1 file changed, 61 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c index f2916cc9e5fc..c11b3eac9d47 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -6466,6 +6466,47 @@ static s32 brcmf_get_assoc_ies(struct brcmf_cfg80211_info *cfg, return err; } +static bool brcmf_has_pmkid(const u8 *parse, u32 len) +{ + const struct brcmf_tlv *rsn_ie; + const u8 *ie; + u32 ie_len; + u32 offset; + u16 count; + + if (!parse) + return false; + + rsn_ie = brcmf_parse_tlvs(parse, len, WLAN_EID_RSN); + if (!rsn_ie) + return false; + + ie = (const u8 *)rsn_ie; + ie_len = rsn_ie->len + TLV_HDR_LEN; + + offset = TLV_HDR_LEN + WPA_IE_VERSION_LEN + WPA_IE_MIN_OUI_LEN; + if (offset + WPA_IE_SUITE_COUNT_LEN >= ie_len) + return false; + + count = ie[offset] + (ie[offset + 1] << 8); + offset += WPA_IE_SUITE_COUNT_LEN + count * WPA_IE_MIN_OUI_LEN; + if (offset + WPA_IE_SUITE_COUNT_LEN >= ie_len) + return false; + + count = ie[offset] + (ie[offset + 1] << 8); + offset += WPA_IE_SUITE_COUNT_LEN + count * WPA_IE_MIN_OUI_LEN; + if (offset + RSN_CAP_LEN >= ie_len) + return false; + + offset += RSN_CAP_LEN; + if (offset + RSN_PMKID_COUNT_LEN > ie_len) + return false; + + count = ie[offset] + (ie[offset + 1] << 8); + + return count > 0; +} + static s32 brcmf_bss_roaming_done(struct brcmf_cfg80211_info *cfg, struct net_device *ndev, @@ -6480,6 +6521,7 @@ brcmf_bss_roaming_done(struct brcmf_cfg80211_info *cfg, struct brcmf_bss_info_le *bi; struct brcmu_chan ch; struct cfg80211_roam_info roam_info = {}; + bool authorized = false; u32 freq; s32 err = 0; u8 *buf; @@ -6526,13 +6568,17 @@ brcmf_bss_roaming_done(struct brcmf_cfg80211_info *cfg, roam_info.resp_ie = conn_info->resp_ie; roam_info.resp_ie_len = conn_info->resp_ie_len; - cfg80211_roamed(ndev, &roam_info, GFP_KERNEL); - brcmf_dbg(CONN, "Report roaming result\n"); + if ((profile->use_fwsup == BRCMF_PROFILE_FWSUP_1X || + profile->use_fwsup == BRCMF_PROFILE_FWSUP_ROAM) && + (brcmf_has_pmkid(roam_info.req_ie, roam_info.req_ie_len) || + profile->is_ft || profile->is_okc)) + authorized = true; - if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_1X && profile->is_ft) { - cfg80211_port_authorized(ndev, profile->bssid, NULL, 0, GFP_KERNEL); - brcmf_dbg(CONN, "Report port authorized\n"); - } + cfg80211_roamed(ndev, &roam_info, GFP_KERNEL); + if (authorized) + cfg80211_port_authorized(ndev, profile->bssid, NULL, 0, + GFP_KERNEL); + brcmf_dbg(CONN, "Report roaming result\n"); set_bit(BRCMF_VIF_STATUS_CONNECTED, &ifp->vif->sme_state); brcmf_dbg(TRACE, "Exit\n"); @@ -6548,6 +6594,7 @@ brcmf_bss_connect_done(struct brcmf_cfg80211_info *cfg, struct brcmf_cfg80211_profile *profile = &ifp->vif->profile; struct brcmf_cfg80211_connect_info *conn_info = cfg_to_conn(cfg); struct cfg80211_connect_resp_params conn_params; + bool authorized; brcmf_dbg(TRACE, "Enter\n"); @@ -6572,7 +6619,15 @@ brcmf_bss_connect_done(struct brcmf_cfg80211_info *cfg, conn_params.req_ie_len = conn_info->req_ie_len; conn_params.resp_ie = conn_info->resp_ie; conn_params.resp_ie_len = conn_info->resp_ie_len; + authorized = completed && + (profile->use_fwsup == BRCMF_PROFILE_FWSUP_1X || + profile->use_fwsup == BRCMF_PROFILE_FWSUP_ROAM) && + brcmf_has_pmkid(conn_params.req_ie, + conn_params.req_ie_len); cfg80211_connect_done(ndev, &conn_params, GFP_KERNEL); + if (authorized) + cfg80211_port_authorized(ndev, profile->bssid, NULL, 0, + GFP_KERNEL); brcmf_dbg(CONN, "Report connect result - connection %s\n", completed ? "succeeded" : "failed"); } From f2e344a8d8008970cbb45dee3de8f220fe6755bb Mon Sep 17 00:00:00 2001 From: Carella Chen Date: Fri, 11 Sep 2026 14:56:56 +0800 Subject: [PATCH 081/101] wifi: brcmfmac: advertise firmware fast roam offload support Advertise NL80211_EXT_FEATURE_FAST_ROAM_OFFLOAD only when firmware reports FBT or OKC support. That lets userspace provide PMK material for firmware fast roaming without enabling the path on devices that cannot complete the offloaded roam. Assisted-by: GitHub-Copilot-CLI:gpt-5.5 Signed-off-by: Carella Chen Signed-off-by: Jason Huang Acked-by: Arend van Spriel Link: https://patch.msgid.link/20260911065656.1269623-6-Jason.Huang2@infineon.com Signed-off-by: Johannes Berg --- drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c index c11b3eac9d47..519de96b042e 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -7830,6 +7830,10 @@ static int brcmf_setup_wiphy(struct wiphy *wiphy, struct brcmf_if *ifp) } if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SAE_EXT)) wiphy->features |= NL80211_FEATURE_SAE; + if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_FBT) || + brcmf_feat_is_enabled(ifp, BRCMF_FEAT_OKC)) + wiphy_ext_feature_set(wiphy, + NL80211_EXT_FEATURE_FAST_ROAM_OFFLOAD); wiphy->mgmt_stypes = brcmf_txrx_stypes; wiphy->max_remain_on_channel_duration = 5000; if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_PNO)) { From 42aa76b3fd9278a8504dbfaf0a449993104d2a48 Mon Sep 17 00:00:00 2001 From: Ryohei Hashimoto Date: Mon, 14 Sep 2026 01:55:26 +0900 Subject: [PATCH 082/101] wifi: brcmfmac: log the firmware status when a connect fails brcmf_bss_connect_done() receives the firmware event in @e but discards it on the failure path. Every failed connect is reported to cfg80211 as WLAN_STATUS_AUTH_TIMEOUT (16), whatever the firmware actually said, so userspace only ever sees: wlan0: CTRL-EVENT-ASSOC-REJECT bssid=00:00:00:00:00:00 status_code=16 The all-zero BSSID comes from the same place: conn_params is memset to zero and profile->bssid has not been filled in when the station never associated. status_code=16 therefore carries no information about the cause. It is not an AP response and it does not mean "authentication timed out" - it is the only failure value this driver can produce. This is a recurring source of confusion: [1] has been open since 2023 with more than twenty follow-ups and no explanation of the code, and covers BCM4345/6, BCM43430 and CYW43455 across several kernel versions. It supersedes [2], filed against the firmware repository a day earlier and closed in favour of it. The firmware's own status (BRCMF_E_STATUS_*) is more specific - FAIL, TIMEOUT, NO_NETWORKS, ABORT and so on - and it is already in hand. Log it so the cause can be narrowed down without rebuilding the kernel. bphy_err() is used rather than brcmf_dbg() or brcmf_info(): it is the form the rest of this file uses, and of the three it is the only one that is both visible in a distribution kernel and bounded. brcmf_dbg(CONN) expands to no_printk() unless CONFIG_BRCMDBG is set, which is what makes the reports in [1] and [2] impossible to act on - the people hitting this run stock kernels. brcmf_info() expands to a plain pr_info() in a non-debug build and is not rate limited, and wpa_supplicant retries the association every few seconds, so it would flood the log. bphy_err() is guarded by net_ratelimit() there, so a station retrying against an unreachable AP prints at most a few lines per second. All three fields are printed because only some of them are meaningful on each path into the failure branch. brcmf_is_nonetwork() keys off @status, so that is the useful field for a join that never associated - the case in [1]. brcmf_is_linkdown() keys off @event_code and @flags and does not look at @status at all, so @status can read 0 there; on those events it is @reason which carries the 802.11 reason code, as the mapping in brcmf_map_fw_linkdown_reason() shows. Printing the three together lets the reader tell which path was taken instead of guessing from one number. The status reported to cfg80211 is left alone; changing it would alter what userspace sees. [1] https://github.com/RPi-Distro/firmware-nonfree/issues/38 [2] https://github.com/raspberrypi/firmware/issues/1829 Assisted-by: Claude:claude-opus-5 Signed-off-by: Ryohei Hashimoto Acked-by: Arend van Spriel Link: https://patch.msgid.link/20260913165526.4426-1-laurel.medalist12@gmail.com Signed-off-by: Johannes Berg --- drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c index 519de96b042e..9d8ce7bb046e 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c @@ -6613,6 +6613,8 @@ brcmf_bss_connect_done(struct brcmf_cfg80211_info *cfg, clear_bit(BRCMF_VIF_STATUS_ASSOC_SUCCESS, &ifp->vif->sme_state); conn_params.status = WLAN_STATUS_AUTH_TIMEOUT; + bphy_err(ifp->drvr, "connect failed: event %u status %u reason %u\n", + e->event_code, e->status, e->reason); } conn_params.links[0].bssid = profile->bssid; conn_params.req_ie = conn_info->req_ie; From 24c21f0e97a4cae0f42a420e0e286e2eea051004 Mon Sep 17 00:00:00 2001 From: Jeff Johnson Date: Tue, 15 Sep 2026 09:36:48 -0700 Subject: [PATCH 083/101] MAINTAINERS: Replace wireless.wiki.kernel.org links wireless.wiki.kernel.org has not existed for a while. Update all links to point to the current rendered pages at wireless.docs.kernel.org. Signed-off-by: Jeff Johnson Link: https://patch.msgid.link/20260915-wireless-wiki-v1-1-21dc76684b44@oss.qualcomm.com Signed-off-by: Johannes Berg --- MAINTAINERS | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index b23fb6f2f4ef..f6ead73ab92d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -113,7 +113,7 @@ F: net/6lowpan/ M: Johannes Berg L: linux-wireless@vger.kernel.org S: Maintained -W: https://wireless.wiki.kernel.org/ +W: https://wireless.docs.kernel.org/en/latest/ Q: https://patchwork.kernel.org/project/linux-wireless/list/ T: git git://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless.git T: git git://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless-next.git @@ -4260,13 +4260,13 @@ M: Nick Kossifidis M: Luis Chamberlain L: linux-wireless@vger.kernel.org S: Maintained -W: https://wireless.wiki.kernel.org/en/users/Drivers/ath5k +W: https://wireless.docs.kernel.org/en/latest/en/users/drivers/ath5k.html F: drivers/net/wireless/ath/ath5k/ ATHEROS ATH6KL WIRELESS DRIVER L: linux-wireless@vger.kernel.org S: Orphan -W: https://wireless.wiki.kernel.org/en/users/Drivers/ath6kl +W: https://wireless.docs.kernel.org/en/latest/en/users/drivers/ath6kl.html F: drivers/net/wireless/ath/ath6kl/ ATI_REMOTE2 DRIVER @@ -4535,14 +4535,14 @@ B43 WIRELESS DRIVER L: linux-wireless@vger.kernel.org L: b43-dev@lists.infradead.org S: Orphan -W: https://wireless.wiki.kernel.org/en/users/Drivers/b43 +W: https://wireless.docs.kernel.org/en/latest/en/users/drivers/b43.html F: drivers/net/wireless/broadcom/b43/ B43LEGACY WIRELESS DRIVER L: linux-wireless@vger.kernel.org L: b43-dev@lists.infradead.org S: Orphan -W: https://wireless.wiki.kernel.org/en/users/Drivers/b43 +W: https://wireless.docs.kernel.org/en/latest/en/users/drivers/b43.html F: drivers/net/wireless/broadcom/b43legacy/ BACKLIGHT CLASS/SUBSYSTEM @@ -5919,7 +5919,7 @@ CARL9170 LINUX COMMUNITY WIRELESS DRIVER M: Christian Lamparter L: linux-wireless@vger.kernel.org S: Maintained -W: https://wireless.wiki.kernel.org/en/users/Drivers/carl9170 +W: https://wireless.docs.kernel.org/en/latest/en/users/drivers/carl9170.html F: drivers/net/wireless/ath/carl9170/ CAVIUM I2C DRIVER @@ -13560,7 +13560,7 @@ INTEL WIRELESS WIFI LINK (iwlwifi) M: Miri Korenblit L: linux-wireless@vger.kernel.org S: Supported -W: https://wireless.wiki.kernel.org/en/users/drivers/iwlwifi +W: https://wireless.docs.kernel.org/en/latest/en/users/drivers/iwlwifi.html T: git https://git.kernel.org/pub/scm/linux/kernel/git/iwlwifi/iwlwifi-next.git/ F: drivers/net/wireless/intel/iwlwifi/ @@ -15661,7 +15661,7 @@ MAC80211 M: Johannes Berg L: linux-wireless@vger.kernel.org S: Maintained -W: https://wireless.wiki.kernel.org/ +W: https://wireless.docs.kernel.org/en/latest/ Q: https://patchwork.kernel.org/project/linux-wireless/list/ T: git git://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless.git T: git git://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless-next.git @@ -18920,7 +18920,7 @@ NETWORKING DRIVERS (WIRELESS) M: Johannes Berg L: linux-wireless@vger.kernel.org S: Maintained -W: https://wireless.wiki.kernel.org/ +W: https://wireless.docs.kernel.org/en/latest/ Q: https://patchwork.kernel.org/project/linux-wireless/list/ T: git git://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless.git T: git git://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless-next.git @@ -20592,7 +20592,7 @@ P54 WIRELESS DRIVER M: Christian Lamparter L: linux-wireless@vger.kernel.org S: Maintained -W: https://wireless.wiki.kernel.org/en/users/Drivers/p54 +W: https://wireless.docs.kernel.org/en/latest/en/users/drivers/p54.html F: Documentation/devicetree/bindings/net/wireless/st,stlc4560.yaml F: drivers/net/wireless/intersil/ @@ -22282,7 +22282,7 @@ M: Jeff Johnson L: linux-wireless@vger.kernel.org L: ath12k@lists.infradead.org S: Supported -W: https://wireless.wiki.kernel.org/en/users/Drivers/ath12k +W: https://wireless.docs.kernel.org/en/latest/en/users/drivers/ath12k.html T: git git://git.kernel.org/pub/scm/linux/kernel/git/ath/ath.git F: drivers/net/wireless/ath/ath12k/ N: ath12k @@ -22292,7 +22292,7 @@ M: Jeff Johnson L: linux-wireless@vger.kernel.org L: ath10k@lists.infradead.org S: Supported -W: https://wireless.wiki.kernel.org/en/users/Drivers/ath10k +W: https://wireless.docs.kernel.org/en/latest/en/users/drivers/ath10k.html T: git git://git.kernel.org/pub/scm/linux/kernel/git/ath/ath.git F: drivers/net/wireless/ath/ath10k/ N: ath10k @@ -22302,8 +22302,8 @@ M: Jeff Johnson L: linux-wireless@vger.kernel.org L: ath11k@lists.infradead.org S: Supported -W: https://wireless.wiki.kernel.org/en/users/Drivers/ath11k -B: https://wireless.wiki.kernel.org/en/users/Drivers/ath11k/bugreport +W: https://wireless.docs.kernel.org/en/latest/en/users/drivers/ath11k.html +B: https://wireless.docs.kernel.org/en/latest/en/users/drivers/ath11k/bugreport.html T: git git://git.kernel.org/pub/scm/linux/kernel/git/ath/ath.git F: drivers/net/wireless/ath/ath11k/ N: ath11k @@ -22312,7 +22312,7 @@ QUALCOMM ATHEROS ATH9K WIRELESS DRIVER M: Toke HĆøiland-JĆørgensen L: linux-wireless@vger.kernel.org S: Maintained -W: https://wireless.wiki.kernel.org/en/users/Drivers/ath9k +W: https://wireless.docs.kernel.org/en/latest/en/users/drivers/ath9k.html T: git git://git.kernel.org/pub/scm/linux/kernel/git/ath/ath.git F: Documentation/devicetree/bindings/net/wireless/qca,ath9k.yaml F: drivers/net/wireless/ath/ath9k/ @@ -22654,7 +22654,7 @@ M: Loic Poulain L: wcn36xx@lists.infradead.org L: linux-wireless@vger.kernel.org S: Supported -W: https://wireless.wiki.kernel.org/en/users/Drivers/wcn36xx +W: https://wireless.docs.kernel.org/en/latest/en/users/drivers/wcn36xx.html F: drivers/net/wireless/ath/wcn36xx/ QUANTENNA QTNFMAC WIRELESS DRIVER @@ -23413,7 +23413,7 @@ RFKILL M: Johannes Berg L: linux-wireless@vger.kernel.org S: Maintained -W: https://wireless.wiki.kernel.org/ +W: https://wireless.docs.kernel.org/en/latest/ Q: https://patchwork.kernel.org/project/linux-wireless/list/ T: git git://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless.git T: git git://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless-next.git @@ -27508,8 +27508,8 @@ F: drivers/media/platform/ti/vpe/ TI WILINK WIRELESS DRIVERS L: linux-wireless@vger.kernel.org S: Orphan -W: https://wireless.wiki.kernel.org/en/users/Drivers/wl12xx -W: https://wireless.wiki.kernel.org/en/users/Drivers/wl1251 +W: https://wireless.docs.kernel.org/en/latest/en/users/drivers/wl12xx.html +W: https://wireless.docs.kernel.org/en/latest/en/users/drivers/wl1251.html F: drivers/net/wireless/ti/ TIMEKEEPING, CLOCKSOURCE CORE, NTP, ALARMTIMER @@ -29386,7 +29386,7 @@ F: drivers/hid/hid-wiimote* WILOCITY WIL6210 WIRELESS DRIVER L: linux-wireless@vger.kernel.org S: Orphan -W: https://wireless.wiki.kernel.org/en/users/Drivers/wil6210 +W: https://wireless.docs.kernel.org/en/latest/en/users/drivers/wil6210.html F: drivers/net/wireless/ath/wil6210/ WINBOND CIR DRIVER From 4e1b57b6895945e7f8282dafcc4db93e57a47134 Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Tue, 15 Sep 2026 15:19:27 +0200 Subject: [PATCH 084/101] wifi: iwlwifi: use link_sta internally to the driver Using the link_sta is a natural way to pass both the STA and the link ID information at the same time. Use that internally to the driver in preparation to mac80211 changing its API and adopting the same method. Signed-off-by: Benjamin Berg Link: https://patch.msgid.link/20260915151925.0b1269375e9e.I41df561f8129fac1327a07729cf4734b8752bcd6@changeid Signed-off-by: Johannes Berg --- drivers/net/wireless/intel/iwlwifi/mld/agg.c | 17 +++---- drivers/net/wireless/intel/iwlwifi/mld/agg.h | 4 +- drivers/net/wireless/intel/iwlwifi/mld/rx.c | 45 ++++++++++--------- drivers/net/wireless/intel/iwlwifi/mld/rx.h | 2 +- .../wireless/intel/iwlwifi/mld/tests/agg.c | 7 +-- 5 files changed, 39 insertions(+), 36 deletions(-) diff --git a/drivers/net/wireless/intel/iwlwifi/mld/agg.c b/drivers/net/wireless/intel/iwlwifi/mld/agg.c index 96102d2af2cf..c45c47337509 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/agg.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/agg.c @@ -7,7 +7,8 @@ #include "hcmd.h" static void -iwl_mld_reorder_release_frames(struct iwl_mld *mld, struct ieee80211_sta *sta, +iwl_mld_reorder_release_frames(struct iwl_mld *mld, + struct ieee80211_link_sta *link_sta, struct napi_struct *napi, struct iwl_mld_baid_data *baid_data, struct iwl_mld_reorder_buffer *reorder_buf, @@ -32,7 +33,7 @@ iwl_mld_reorder_release_frames(struct iwl_mld *mld, struct ieee80211_sta *sta, while ((skb = __skb_dequeue(skb_list))) { iwl_mld_pass_packet_to_mac80211(mld, napi, skb, reorder_buf->queue, - sta); + link_sta); reorder_buf->num_stored--; } } @@ -74,7 +75,7 @@ static void iwl_mld_release_frames_from_notif(struct iwl_mld *mld, reorder_buf = &ba_data->reorder_buf[queue]; - iwl_mld_reorder_release_frames(mld, link_sta->sta, napi, ba_data, + iwl_mld_reorder_release_frames(mld, link_sta, napi, ba_data, reorder_buf, nssn); out_unlock: rcu_read_unlock(); @@ -180,7 +181,7 @@ void iwl_mld_del_ba(struct iwl_mld *mld, int queue, reorder_buf = &ba_data->reorder_buf[queue]; /* release all frames that are in the reorder buffer to the stack */ - iwl_mld_reorder_release_frames(mld, link_sta->sta, NULL, + iwl_mld_reorder_release_frames(mld, link_sta, NULL, ba_data, reorder_buf, ieee80211_sn_add(reorder_buf->head_sn, ba_data->buf_size)); @@ -193,7 +194,7 @@ void iwl_mld_del_ba(struct iwl_mld *mld, int queue, */ enum iwl_mld_reorder_result iwl_mld_reorder(struct iwl_mld *mld, struct napi_struct *napi, - int queue, struct ieee80211_sta *sta, + int queue, struct ieee80211_link_sta *link_sta, struct sk_buff *skb, struct iwl_rx_mpdu_desc *desc) { struct ieee80211_hdr *hdr = (void *)skb_mac_header(skb); @@ -228,12 +229,12 @@ iwl_mld_reorder(struct iwl_mld *mld, struct napi_struct *napi, return IWL_MLD_PASS_SKB; /* no sta yet */ - if (IWL_FW_CHECK(mld, !sta, + if (IWL_FW_CHECK(mld, !link_sta, "Got valid BAID without a valid station assigned - %d\n", baid)) return IWL_MLD_PASS_SKB; - mld_sta = iwl_mld_sta_from_mac80211(sta); + mld_sta = iwl_mld_sta_from_mac80211(link_sta->sta); /* not a data packet */ if (!ieee80211_is_data_qos(hdr->frame_control) || @@ -324,7 +325,7 @@ iwl_mld_reorder(struct iwl_mld *mld, struct napi_struct *napi, * will be released when the frame release notification arrives. */ if (!amsdu || last_subframe) - iwl_mld_reorder_release_frames(mld, sta, napi, baid_data, + iwl_mld_reorder_release_frames(mld, link_sta, napi, baid_data, buffer, nssn); else if (buffer->num_stored == 1) buffer->head_sn = nssn; diff --git a/drivers/net/wireless/intel/iwlwifi/mld/agg.h b/drivers/net/wireless/intel/iwlwifi/mld/agg.h index 651c80d1c7cd..c6cd5fa219be 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/agg.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/agg.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause */ /* - * Copyright (C) 2024 Intel Corporation + * Copyright (C) 2024, 2026 Intel Corporation */ #ifndef __iwl_agg_h__ #define __iwl_agg_h__ @@ -106,7 +106,7 @@ int iwl_mld_ampdu_rx_stop(struct iwl_mld *mld, struct ieee80211_sta *sta, enum iwl_mld_reorder_result iwl_mld_reorder(struct iwl_mld *mld, struct napi_struct *napi, - int queue, struct ieee80211_sta *sta, + int queue, struct ieee80211_link_sta *link_sta, struct sk_buff *skb, struct iwl_rx_mpdu_desc *desc); void iwl_mld_handle_frame_release_notif(struct iwl_mld *mld, diff --git a/drivers/net/wireless/intel/iwlwifi/mld/rx.c b/drivers/net/wireless/intel/iwlwifi/mld/rx.c index 269439d789f4..5696a247922b 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/rx.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/rx.c @@ -47,7 +47,8 @@ iwl_mld_fill_phy_data_from_mpdu(struct iwl_mld *mld, } static inline int iwl_mld_check_pn(struct iwl_mld *mld, struct sk_buff *skb, - int queue, struct ieee80211_sta *sta) + int queue, + struct ieee80211_link_sta *link_sta) { struct ieee80211_hdr *hdr = (void *)skb_mac_header(skb); struct ieee80211_rx_status *stats = IEEE80211_SKB_RXCB(skb); @@ -71,13 +72,13 @@ static inline int iwl_mld_check_pn(struct iwl_mld *mld, struct sk_buff *skb, return 0; /* if we are here - this for sure is either CCMP or GCMP */ - if (!sta) { + if (!link_sta) { IWL_DEBUG_DROP(mld, "expected hw-decrypted unicast frame for station\n"); return -1; } - mld_sta = iwl_mld_sta_from_mac80211(sta); + mld_sta = iwl_mld_sta_from_mac80211(link_sta->sta); extiv = (u8 *)hdr + ieee80211_hdrlen(hdr->frame_control); keyidx = extiv[3] >> 6; @@ -119,17 +120,17 @@ static inline int iwl_mld_check_pn(struct iwl_mld *mld, struct sk_buff *skb, void iwl_mld_pass_packet_to_mac80211(struct iwl_mld *mld, struct napi_struct *napi, struct sk_buff *skb, int queue, - struct ieee80211_sta *sta) + struct ieee80211_link_sta *link_sta) { KUNIT_STATIC_STUB_REDIRECT(iwl_mld_pass_packet_to_mac80211, - mld, napi, skb, queue, sta); + mld, napi, skb, queue, link_sta); - if (unlikely(iwl_mld_check_pn(mld, skb, queue, sta))) { + if (unlikely(iwl_mld_check_pn(mld, skb, queue, link_sta))) { kfree_skb(skb); return; } - ieee80211_rx_napi(mld->hw, sta, skb, napi); + ieee80211_rx_napi(mld->hw, link_sta->sta, skb, napi); } EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mld_pass_packet_to_mac80211); @@ -1728,7 +1729,7 @@ static void iwl_mld_update_last_rx_timestamp(struct iwl_mld *mld, u8 baid) * Sets *drop to true if the packet should be dropped. * Returns the station if found, or NULL otherwise. */ -static struct ieee80211_sta * +static struct ieee80211_link_sta * iwl_mld_rx_with_sta(struct iwl_mld *mld, struct ieee80211_hdr *hdr, struct sk_buff *skb, const struct iwl_rx_mpdu_desc *mpdu_desc, @@ -1803,10 +1804,10 @@ iwl_mld_rx_with_sta(struct iwl_mld *mld, struct ieee80211_hdr *hdr, queue); } - return sta; + return link_sta; } -static int iwl_mld_rx_mgmt_prot(struct ieee80211_sta *sta, +static int iwl_mld_rx_mgmt_prot(struct ieee80211_link_sta *link_sta, struct ieee80211_hdr *hdr, struct ieee80211_rx_status *rx_status, u32 mpdu_status, @@ -1820,7 +1821,6 @@ static int iwl_mld_rx_mgmt_prot(struct ieee80211_sta *sta, struct ieee80211_key_conf *key; const u8 *frame = (void *)hdr; const u8 *mmie; - u8 link_id; if ((mpdu_status & IWL_RX_MPDU_STATUS_SEC_MASK) == IWL_RX_MPDU_STATUS_SEC_NONE) @@ -1836,10 +1836,10 @@ static int iwl_mld_rx_mgmt_prot(struct ieee80211_sta *sta, if (!ieee80211_is_beacon(hdr->frame_control)) return 0; - if (!sta) + if (!link_sta) return -1; - mld_sta = iwl_mld_sta_from_mac80211(sta); + mld_sta = iwl_mld_sta_from_mac80211(link_sta->sta); mld_vif = iwl_mld_vif_from_mac80211(mld_sta->vif); /* key mismatch - will also report !MIC_OK but we shouldn't count it */ @@ -1853,8 +1853,7 @@ static int iwl_mld_rx_mgmt_prot(struct ieee80211_sta *sta, return 0; } - link_id = rx_status->link_valid ? rx_status->link_id : 0; - link = rcu_dereference(mld_vif->link[link_id]); + link = rcu_dereference(mld_vif->link[link_sta->link_id]); if (WARN_ON_ONCE(!link)) return -1; @@ -1905,7 +1904,7 @@ static int iwl_mld_rx_mgmt_prot(struct ieee80211_sta *sta, } static int iwl_mld_rx_crypto(struct iwl_mld *mld, - struct ieee80211_sta *sta, + struct ieee80211_link_sta *link_sta, struct ieee80211_hdr *hdr, struct ieee80211_rx_status *rx_status, struct iwl_rx_mpdu_desc *desc, int queue, @@ -1915,7 +1914,7 @@ static int iwl_mld_rx_crypto(struct iwl_mld *mld, if (unlikely(ieee80211_is_mgmt(hdr->frame_control) && !ieee80211_has_protected(hdr->frame_control))) - return iwl_mld_rx_mgmt_prot(sta, hdr, rx_status, status, + return iwl_mld_rx_mgmt_prot(link_sta, hdr, rx_status, status, le16_to_cpu(desc->mpdu_len)); if (!ieee80211_has_protected(hdr->frame_control) || @@ -2023,7 +2022,7 @@ void iwl_mld_rx_mpdu(struct iwl_mld *mld, struct napi_struct *napi, struct iwl_rx_packet *pkt = rxb_addr(rxb); struct iwl_mld_rx_phy_data phy_data = {}; struct iwl_rx_mpdu_desc *mpdu_desc = (void *)pkt->data; - struct ieee80211_sta *sta; + struct ieee80211_link_sta *link_sta; struct ieee80211_hdr *hdr; struct sk_buff *skb; size_t mpdu_desc_size = sizeof(*mpdu_desc); @@ -2086,7 +2085,8 @@ void iwl_mld_rx_mpdu(struct iwl_mld *mld, struct napi_struct *napi, rcu_read_lock(); - sta = iwl_mld_rx_with_sta(mld, hdr, skb, mpdu_desc, pkt, queue, &drop); + link_sta = iwl_mld_rx_with_sta(mld, hdr, skb, mpdu_desc, pkt, queue, + &drop); if (drop) goto drop; @@ -2127,7 +2127,7 @@ void iwl_mld_rx_mpdu(struct iwl_mld *mld, struct napi_struct *napi, iwl_mld_rx_fill_status(mld, link_id, hdr, skb, &phy_data); - if (iwl_mld_rx_crypto(mld, sta, hdr, rx_status, mpdu_desc, queue, + if (iwl_mld_rx_crypto(mld, link_sta, hdr, rx_status, mpdu_desc, queue, le32_to_cpu(pkt->len_n_flags), &crypto_len)) goto drop; @@ -2140,7 +2140,8 @@ void iwl_mld_rx_mpdu(struct iwl_mld *mld, struct napi_struct *napi, if (iwl_mld_time_sync_frame(mld, skb, hdr->addr2)) goto out; - reorder_res = iwl_mld_reorder(mld, napi, queue, sta, skb, mpdu_desc); + reorder_res = iwl_mld_reorder(mld, napi, queue, link_sta, skb, + mpdu_desc); switch (reorder_res) { case IWL_MLD_PASS_SKB: break; @@ -2153,7 +2154,7 @@ void iwl_mld_rx_mpdu(struct iwl_mld *mld, struct napi_struct *napi, goto drop; } - iwl_mld_pass_packet_to_mac80211(mld, napi, skb, queue, sta); + iwl_mld_pass_packet_to_mac80211(mld, napi, skb, queue, link_sta); goto out; diff --git a/drivers/net/wireless/intel/iwlwifi/mld/rx.h b/drivers/net/wireless/intel/iwlwifi/mld/rx.h index 573b89c3c9c6..b08c5ade3db8 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/rx.h +++ b/drivers/net/wireless/intel/iwlwifi/mld/rx.h @@ -69,7 +69,7 @@ void iwl_mld_handle_rsc_notif(struct iwl_mld *mld, void iwl_mld_pass_packet_to_mac80211(struct iwl_mld *mld, struct napi_struct *napi, struct sk_buff *skb, int queue, - struct ieee80211_sta *sta); + struct ieee80211_link_sta *link_sta); void iwl_mld_handle_phy_air_sniffer_notif(struct iwl_mld *mld, struct napi_struct *napi, diff --git a/drivers/net/wireless/intel/iwlwifi/mld/tests/agg.c b/drivers/net/wireless/intel/iwlwifi/mld/tests/agg.c index 29b0248cec3d..e9efe2996f07 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/tests/agg.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/tests/agg.c @@ -2,7 +2,7 @@ /* * KUnit tests for channel helper functions * - * Copyright (C) 2024-2025 Intel Corporation + * Copyright (C) 2024-2026 Intel Corporation */ #include #include @@ -441,7 +441,7 @@ static void fake_iwl_mld_pass_packet_to_mac80211(struct iwl_mld *mld, struct napi_struct *napi, struct sk_buff *skb, int queue, - struct ieee80211_sta *sta) + struct ieee80211_link_sta *link_sta) { __skb_queue_tail(&g_released_skbs, skb); g_num_released_skbs++; @@ -630,7 +630,8 @@ static void test_reorder_buffer(struct kunit *test) mpdu_desc = setup_mpdu_desc(); rcu_read_lock(); - reorder_res = iwl_mld_reorder(mld, NULL, QUEUE, sta, skb, mpdu_desc); + reorder_res = iwl_mld_reorder(mld, NULL, QUEUE, &sta->deflink, skb, + mpdu_desc); rcu_read_unlock(); KUNIT_ASSERT_EQ(test, reorder_res, param->expected.reorder_res); From a8188bb7644f6416c7c16f4964b45ea1b40afcfc Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Tue, 15 Sep 2026 15:19:28 +0200 Subject: [PATCH 085/101] wifi: mac80211: change public RX API to use link stations If a station is passed then the link ID also needs to be known. As such, it is a more natural API to simply pass the link station directly rather than pushing the link information into the RX status. Furthermore, having the link ID in the RX status is not actually correct because the link IDs are VIF specific and there may be multiple VIFs. In the case of a station this relationship is clear, but then one may as well use the link station. This patch only changes the API and emulates the old (incorrect) behaviour for now. The mac80211 RX code will be updated in later patches. Signed-off-by: Benjamin Berg Link: https://patch.msgid.link/20260915151925.06f41565116a.I4a2d45609e94b52654b10ec572e59a45d09c41f4@changeid Signed-off-by: Johannes Berg --- drivers/net/wireless/ath/ath11k/dp_rx.c | 2 +- drivers/net/wireless/ath/ath12k/dp_mon.c | 2 -- drivers/net/wireless/ath/ath12k/dp_rx.c | 20 ++++++++++++--- drivers/net/wireless/ath/ath12k/hw.h | 20 ++++++++------- drivers/net/wireless/ath/ath12k/wifi7/dp_rx.c | 20 +++++++-------- drivers/net/wireless/ath/ath12k/wifi7/dp_rx.h | 12 ++++----- drivers/net/wireless/ath/ath12k/wifi7/hw.c | 6 ++--- drivers/net/wireless/ath/ath12k/wmi.c | 6 ----- drivers/net/wireless/intel/iwlwifi/mld/rx.c | 7 +----- drivers/net/wireless/intel/iwlwifi/mvm/rx.c | 2 +- drivers/net/wireless/intel/iwlwifi/mvm/rxmq.c | 6 ++--- drivers/net/wireless/mediatek/mt76/mac80211.c | 25 +++++++++++-------- drivers/net/wireless/realtek/rtw89/core.c | 7 ------ .../wireless/virtual/mac80211_hwsim_main.c | 3 --- include/net/mac80211.h | 16 +++++++++--- net/mac80211/rx.c | 20 +++++++++++---- 16 files changed, 92 insertions(+), 82 deletions(-) diff --git a/drivers/net/wireless/ath/ath11k/dp_rx.c b/drivers/net/wireless/ath/ath11k/dp_rx.c index 33425707c084..fbb3d18f55dd 100644 --- a/drivers/net/wireless/ath/ath11k/dp_rx.c +++ b/drivers/net/wireless/ath/ath11k/dp_rx.c @@ -2500,7 +2500,7 @@ static void ath11k_dp_rx_deliver_msdu(struct ath11k *ar, struct napi_struct *nap !(is_mcbc && rx_status->flag & RX_FLAG_DECRYPTED)) rx_status->flag |= RX_FLAG_8023; - ieee80211_rx_napi(ar->hw, pubsta, msdu, napi); + ieee80211_rx_napi(ar->hw, pubsta ? &pubsta->deflink : NULL, msdu, napi); } static bool ath11k_dp_rx_check_nwifi_hdr_len_valid(struct ath11k_base *ab, diff --git a/drivers/net/wireless/ath/ath12k/dp_mon.c b/drivers/net/wireless/ath/ath12k/dp_mon.c index 7d5be77b081f..1faf1896d0ab 100644 --- a/drivers/net/wireless/ath/ath12k/dp_mon.c +++ b/drivers/net/wireless/ath/ath12k/dp_mon.c @@ -503,8 +503,6 @@ void ath12k_dp_mon_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, struct ieee80211_rx_status *rx_status; struct ieee80211_radiotap_he *he = NULL; - status->link_valid = 0; - if ((status->encoding == RX_ENC_HE) && !(status->flag & RX_FLAG_RADIOTAP_HE) && !(status->flag & RX_FLAG_SKIP_MONITOR)) { he = skb_push(msdu, sizeof(known)); diff --git a/drivers/net/wireless/ath/ath12k/dp_rx.c b/drivers/net/wireless/ath/ath12k/dp_rx.c index 8fa0e90b4531..41ac0a4072ae 100644 --- a/drivers/net/wireless/ath/ath12k/dp_rx.c +++ b/drivers/net/wireless/ath/ath12k/dp_rx.c @@ -1375,6 +1375,7 @@ void ath12k_dp_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, struct napi_struc struct ath12k_dp *dp = dp_pdev->dp; struct ieee80211_rx_status *rx_status; struct ieee80211_sta *pubsta; + struct ieee80211_link_sta *link_pubsta = NULL; struct ath12k_dp_peer *peer; struct ath12k_skb_rxcb *rxcb = ATH12K_SKB_RXCB(msdu); struct ieee80211_rx_status *status = rx_info->rx_status; @@ -1384,9 +1385,19 @@ void ath12k_dp_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, struct napi_struc pubsta = peer ? peer->sta : NULL; - status->link_valid = 0; - if (pubsta && pubsta->valid_links) - ath12k_hw_set_rx_link_id(dp->hw_params, peer, rxcb, status); + if (pubsta) { + if (pubsta->valid_links) { + int link_id = + ath12k_hw_get_rx_link_id(dp->hw_params, peer, + rxcb, status); + + if (link_id >= 0) + link_pubsta = + rcu_dereference(pubsta->link[link_id]); + } else { + link_pubsta = &pubsta->deflink; + } + } ath12k_dbg(dp->ab, ATH12K_DBG_DATA, "rx skb %p len %u peer %pM %d %s sn %u %s%s%s%s%s%s%s%s%s%s rate_idx %u vht_nss %u freq %u band %u flag 0x%x fcs-err %i mic-err %i amsdu-more %i\n", @@ -1422,7 +1433,8 @@ void ath12k_dp_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev, struct napi_struc /* TODO: trace rx packet */ - ieee80211_rx_napi(ath12k_pdev_dp_to_hw(dp_pdev), pubsta, msdu, napi); + ieee80211_rx_napi(ath12k_pdev_dp_to_hw(dp_pdev), link_pubsta, msdu, + napi); } EXPORT_SYMBOL(ath12k_dp_rx_deliver_msdu); diff --git a/drivers/net/wireless/ath/ath12k/hw.h b/drivers/net/wireless/ath/ath12k/hw.h index 011bb22e21c7..e790de5737db 100644 --- a/drivers/net/wireless/ath/ath12k/hw.h +++ b/drivers/net/wireless/ath/ath12k/hw.h @@ -249,9 +249,9 @@ struct ath12k_hw_ops { bool (*dp_srng_is_tx_comp_ring)(int ring_num); bool (*is_frame_link_agnostic)(struct ath12k_link_vif *arvif, struct ieee80211_mgmt *mgmt); - void (*set_rx_link_id)(struct ath12k_dp_peer *dp_peer, - struct ath12k_skb_rxcb *rxcb, - struct ieee80211_rx_status *status); + int (*get_rx_link_id)(struct ath12k_dp_peer *dp_peer, + struct ath12k_skb_rxcb *rxcb, + struct ieee80211_rx_status *status); }; static inline @@ -282,13 +282,15 @@ static inline int ath12k_hw_mac_id_to_srng_id(const struct ath12k_hw_params *hw, return 0; } -static inline void ath12k_hw_set_rx_link_id(const struct ath12k_hw_params *hw, - struct ath12k_dp_peer *dp_peer, - struct ath12k_skb_rxcb *rxcb, - struct ieee80211_rx_status *status) +static inline int ath12k_hw_get_rx_link_id(const struct ath12k_hw_params *hw, + struct ath12k_dp_peer *dp_peer, + struct ath12k_skb_rxcb *rxcb, + struct ieee80211_rx_status *status) { - if (hw->hw_ops->set_rx_link_id) - hw->hw_ops->set_rx_link_id(dp_peer, rxcb, status); + if (hw->hw_ops->get_rx_link_id) + return hw->hw_ops->get_rx_link_id(dp_peer, rxcb, status); + + return -1; } struct ath12k_fw_ie { diff --git a/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.c b/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.c index 95d87dd67872..8b722ee0ad8f 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.c @@ -2317,17 +2317,16 @@ ath12k_wifi7_dp_rxdesc_mpdu_valid(struct ath12k_base *ab, return tlv_tag == HAL_RX_MPDU_START; } -void -ath12k_wifi7_dp_rx_set_link_id_qcn9274(struct ath12k_dp_peer *dp_peer, +int +ath12k_wifi7_dp_rx_get_link_id_qcn9274(struct ath12k_dp_peer *dp_peer, struct ath12k_skb_rxcb *rxcb, struct ieee80211_rx_status *status) { - status->link_valid = 1; - status->link_id = dp_peer->hw_links[rxcb->hw_link_id]; + return dp_peer->hw_links[rxcb->hw_link_id]; } -void -ath12k_wifi7_dp_rx_set_link_id_wcn7850(struct ath12k_dp_peer *dp_peer, +int +ath12k_wifi7_dp_rx_get_link_id_wcn7850(struct ath12k_dp_peer *dp_peer, struct ath12k_skb_rxcb *rxcb, struct ieee80211_rx_status *status) { @@ -2341,10 +2340,9 @@ ath12k_wifi7_dp_rx_set_link_id_wcn7850(struct ath12k_dp_peer *dp_peer, links_map = READ_ONCE(dp_peer->link_peers_map); for_each_set_bit(i, &links_map, ATH12K_NUM_MAX_LINKS) { link_peer = rcu_dereference(dp_peer->link_peers[i]); - if (link_peer && link_peer->peer_id == rxcb->peer_id) { - status->link_valid = 1; - status->link_id = link_peer->link_id; - return; - } + if (link_peer && link_peer->peer_id == rxcb->peer_id) + return link_peer->link_id; } + + return -1; } diff --git a/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.h b/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.h index 1d3a4788a2dd..d68489ff7b03 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.h +++ b/drivers/net/wireless/ath/ath12k/wifi7/dp_rx.h @@ -57,10 +57,10 @@ ath12k_wifi7_dp_rxdesc_mpdu_valid(struct ath12k_base *ab, struct hal_rx_desc *rx_desc); int ath12k_wifi7_dp_rx_tid_delete_handler(struct ath12k_base *ab, struct ath12k_dp_rx_tid_rxq *rx_tid); -void ath12k_wifi7_dp_rx_set_link_id_qcn9274(struct ath12k_dp_peer *dp_peer, - struct ath12k_skb_rxcb *rxcb, - struct ieee80211_rx_status *status); -void ath12k_wifi7_dp_rx_set_link_id_wcn7850(struct ath12k_dp_peer *dp_peer, - struct ath12k_skb_rxcb *rxcb, - struct ieee80211_rx_status *status); +int ath12k_wifi7_dp_rx_get_link_id_qcn9274(struct ath12k_dp_peer *dp_peer, + struct ath12k_skb_rxcb *rxcb, + struct ieee80211_rx_status *status); +int ath12k_wifi7_dp_rx_get_link_id_wcn7850(struct ath12k_dp_peer *dp_peer, + struct ath12k_skb_rxcb *rxcb, + struct ieee80211_rx_status *status); #endif diff --git a/drivers/net/wireless/ath/ath12k/wifi7/hw.c b/drivers/net/wireless/ath/ath12k/wifi7/hw.c index 7b86f324b2e6..4ded807763b6 100644 --- a/drivers/net/wireless/ath/ath12k/wifi7/hw.c +++ b/drivers/net/wireless/ath/ath12k/wifi7/hw.c @@ -158,7 +158,7 @@ static const struct ath12k_hw_ops qcn9274_ops = { .get_ring_selector = ath12k_wifi7_hw_get_ring_selector_qcn9274, .dp_srng_is_tx_comp_ring = ath12k_wifi7_dp_srng_is_comp_ring_qcn9274, .is_frame_link_agnostic = ath12k_wifi7_is_frame_link_agnostic_qcn9274, - .set_rx_link_id = ath12k_wifi7_dp_rx_set_link_id_qcn9274, + .get_rx_link_id = ath12k_wifi7_dp_rx_get_link_id_qcn9274, }; static const struct ath12k_hw_ops wcn7850_ops = { @@ -169,7 +169,7 @@ static const struct ath12k_hw_ops wcn7850_ops = { .get_ring_selector = ath12k_wifi7_hw_get_ring_selector_wcn7850, .dp_srng_is_tx_comp_ring = ath12k_wifi7_dp_srng_is_comp_ring_wcn7850, .is_frame_link_agnostic = ath12k_wifi7_is_frame_link_agnostic_wcn7850, - .set_rx_link_id = ath12k_wifi7_dp_rx_set_link_id_wcn7850, + .get_rx_link_id = ath12k_wifi7_dp_rx_get_link_id_wcn7850, }; static const struct ath12k_hw_ops qcc2072_ops = { @@ -180,7 +180,7 @@ static const struct ath12k_hw_ops qcc2072_ops = { .get_ring_selector = ath12k_wifi7_hw_get_ring_selector_wcn7850, .dp_srng_is_tx_comp_ring = ath12k_wifi7_dp_srng_is_comp_ring_wcn7850, .is_frame_link_agnostic = ath12k_wifi7_is_frame_link_agnostic_wcn7850, - .set_rx_link_id = ath12k_wifi7_dp_rx_set_link_id_wcn7850, + .get_rx_link_id = ath12k_wifi7_dp_rx_get_link_id_wcn7850, }; #define ATH12K_TX_RING_MASK_0 0x1 diff --git a/drivers/net/wireless/ath/ath12k/wmi.c b/drivers/net/wireless/ath/ath12k/wmi.c index d5160af60e00..7e3655966f02 100644 --- a/drivers/net/wireless/ath/ath12k/wmi.c +++ b/drivers/net/wireless/ath/ath12k/wmi.c @@ -7271,7 +7271,6 @@ static void ath12k_mgmt_rx_event(struct ath12k_base *ab, struct sk_buff *skb) struct ath12k_wmi_mgmt_rx_arg rx_ev = {}; struct ath12k *ar; struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb); - struct ieee80211_sta *pubsta = NULL; struct ath12k_dp_link_peer *peer; struct ieee80211_hdr *hdr; bool is_4addr_null_pkt; @@ -7373,11 +7372,6 @@ static void ath12k_mgmt_rx_event(struct ath12k_base *ab, struct sk_buff *skb) dev_kfree_skb(skb); goto exit; } - pubsta = peer->sta; - if (pubsta && pubsta->valid_links) { - status->link_valid = 1; - status->link_id = peer->link_id; - } spin_unlock_bh(&dp->dp_lock); goto send_rx; } diff --git a/drivers/net/wireless/intel/iwlwifi/mld/rx.c b/drivers/net/wireless/intel/iwlwifi/mld/rx.c index 5696a247922b..02bdf6ccbcdc 100644 --- a/drivers/net/wireless/intel/iwlwifi/mld/rx.c +++ b/drivers/net/wireless/intel/iwlwifi/mld/rx.c @@ -130,7 +130,7 @@ void iwl_mld_pass_packet_to_mac80211(struct iwl_mld *mld, return; } - ieee80211_rx_napi(mld->hw, link_sta->sta, skb, napi); + ieee80211_rx_napi(mld->hw, link_sta, skb, napi); } EXPORT_SYMBOL_IF_IWLWIFI_KUNIT(iwl_mld_pass_packet_to_mac80211); @@ -1765,11 +1765,6 @@ iwl_mld_rx_with_sta(struct iwl_mld *mld, struct ieee80211_hdr *hdr, rx_status = IEEE80211_SKB_RXCB(skb); - if (link_sta && sta->valid_links) { - rx_status->link_valid = true; - rx_status->link_id = link_sta->link_id; - } - /* fill checksum */ if (ieee80211_is_data(hdr->frame_control) && pkt->len_n_flags & cpu_to_le32(FH_RSCSR_RPA_EN)) { diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/rx.c b/drivers/net/wireless/intel/iwlwifi/mvm/rx.c index ab1eb2eb0c3c..8c197a090c2b 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/rx.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/rx.c @@ -90,7 +90,7 @@ static void iwl_mvm_pass_packet_to_mac80211(struct iwl_mvm *mvm, fraglen, rxb->truesize); } - ieee80211_rx_napi(mvm->hw, sta, skb, napi); + ieee80211_rx_napi(mvm->hw, sta ? &sta->deflink : NULL, skb, napi); } /* diff --git a/drivers/net/wireless/intel/iwlwifi/mvm/rxmq.c b/drivers/net/wireless/intel/iwlwifi/mvm/rxmq.c index 7f0b4f5daa21..8c4009d3a00c 100644 --- a/drivers/net/wireless/intel/iwlwifi/mvm/rxmq.c +++ b/drivers/net/wireless/intel/iwlwifi/mvm/rxmq.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * Copyright (C) 2012-2014, 2018-2025 Intel Corporation + * Copyright (C) 2012-2014, 2018-2026 Intel Corporation * Copyright (C) 2013-2015 Intel Mobile Communications GmbH * Copyright (C) 2015-2017 Intel Deutschland GmbH */ @@ -243,7 +243,7 @@ static void iwl_mvm_pass_packet_to_mac80211(struct iwl_mvm *mvm, return; } - ieee80211_rx_napi(mvm->hw, sta, skb, napi); + ieee80211_rx_napi(mvm->hw, sta ? &sta->deflink : NULL, skb, napi); } static bool iwl_mvm_used_average_energy(struct iwl_mvm *mvm, @@ -2528,7 +2528,7 @@ void iwl_mvm_rx_monitor_no_data(struct iwl_mvm *mvm, struct napi_struct *napi, } rcu_read_lock(); - ieee80211_rx_napi(mvm->hw, sta, skb, napi); + ieee80211_rx_napi(mvm->hw, sta ? &sta->deflink : NULL, skb, napi); rcu_read_unlock(); } diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c index abbe65cbcd89..cda5c5d51aab 100644 --- a/drivers/net/wireless/mediatek/mt76/mac80211.c +++ b/drivers/net/wireless/mediatek/mt76/mac80211.c @@ -1255,11 +1255,12 @@ EXPORT_SYMBOL(mt76_rx_signal); static void mt76_rx_convert(struct mt76_dev *dev, struct sk_buff *skb, struct ieee80211_hw **hw, - struct ieee80211_sta **sta) + struct ieee80211_link_sta **link_sta) { struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb); struct ieee80211_hdr *hdr = mt76_skb_get_hdr(skb); struct mt76_rx_status mstat; + struct ieee80211_sta *sta; mstat = *((struct mt76_rx_status *)skb->cb); memset(status, 0, sizeof(*status)); @@ -1301,12 +1302,14 @@ mt76_rx_convert(struct mt76_dev *dev, struct sk_buff *skb, memcpy(status->chain_signal, mstat.chain_signal, sizeof(mstat.chain_signal)); - if (mstat.wcid) { - status->link_valid = mstat.wcid->link_valid; - status->link_id = mstat.wcid->link_id; - } + sta = wcid_to_sta(mstat.wcid); + if (!sta) + *link_sta = NULL; + else if (mstat.wcid->link_valid) + *link_sta = rcu_dereference(sta->link[mstat.wcid->link_id]); + else + *link_sta = &sta->deflink; - *sta = wcid_to_sta(mstat.wcid); *hw = mt76_phy_hw(dev, mstat.phy_idx); } @@ -1530,7 +1533,7 @@ mt76_check_sta(struct mt76_dev *dev, struct sk_buff *skb) void mt76_rx_complete(struct mt76_dev *dev, struct sk_buff_head *frames, struct napi_struct *napi) { - struct ieee80211_sta *sta; + struct ieee80211_link_sta *link_sta; struct ieee80211_hw *hw; struct sk_buff *skb, *tmp; LIST_HEAD(list); @@ -1541,8 +1544,8 @@ void mt76_rx_complete(struct mt76_dev *dev, struct sk_buff_head *frames, mt76_check_ccmp_pn(skb); skb_shinfo(skb)->frag_list = NULL; - mt76_rx_convert(dev, skb, &hw, &sta); - ieee80211_rx_list(hw, sta, skb, &list); + mt76_rx_convert(dev, skb, &hw, &link_sta); + ieee80211_rx_list(hw, link_sta, skb, &list); /* subsequent amsdu frames */ while (nskb) { @@ -1550,8 +1553,8 @@ void mt76_rx_complete(struct mt76_dev *dev, struct sk_buff_head *frames, nskb = nskb->next; skb->next = NULL; - mt76_rx_convert(dev, skb, &hw, &sta); - ieee80211_rx_list(hw, sta, skb, &list); + mt76_rx_convert(dev, skb, &hw, &link_sta); + ieee80211_rx_list(hw, link_sta, skb, &list); } } spin_unlock(&dev->rx_lock); diff --git a/drivers/net/wireless/realtek/rtw89/core.c b/drivers/net/wireless/realtek/rtw89/core.c index 397ebbfcac09..454d876aeb88 100644 --- a/drivers/net/wireless/realtek/rtw89/core.c +++ b/drivers/net/wireless/realtek/rtw89/core.c @@ -3073,10 +3073,8 @@ static void rtw89_vif_rx_stats_iter(void *data, u8 *mac, struct rtw89_vif *rtwvif = vif_to_rtwvif(vif); struct rtw89_rx_desc_info *desc_info = iter_data->desc_info; struct sk_buff *skb = iter_data->skb; - struct ieee80211_rx_status *rx_status = IEEE80211_SKB_RXCB(skb); struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; struct rtw89_rx_phy_ppdu *phy_ppdu = iter_data->phy_ppdu; - bool is_mld = ieee80211_vif_is_mld(vif); struct ieee80211_bss_conf *bss_conf; struct rtw89_vif_link *rtwvif_link; const u8 *bssid = iter_data->bssid; @@ -3110,11 +3108,6 @@ static void rtw89_vif_rx_stats_iter(void *data, u8 *mac, if (!ether_addr_equal(target_bssid, bssid)) goto out; - if (is_mld) { - rx_status->link_valid = true; - rx_status->link_id = rtwvif_link->link_id; - } - bb = rtw89_get_bb_ctx(rtwdev, rtwvif_link->phy_idx); pkt_stat = &bb->cur_pkt_stat; diff --git a/drivers/net/wireless/virtual/mac80211_hwsim_main.c b/drivers/net/wireless/virtual/mac80211_hwsim_main.c index 9f895ad718e9..1a12261f3e8e 100644 --- a/drivers/net/wireless/virtual/mac80211_hwsim_main.c +++ b/drivers/net/wireless/virtual/mac80211_hwsim_main.c @@ -1883,9 +1883,6 @@ static void mac80211_hwsim_rx(struct mac80211_hwsim_data *data, sp->active_links_rx &= ~BIT(link_id); else sp->active_links_rx |= BIT(link_id); - - rx_status->link_valid = true; - rx_status->link_id = link_id; } rcu_read_unlock(); } diff --git a/include/net/mac80211.h b/include/net/mac80211.h index 7bb4618065b6..cba7cdf61ce8 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -5389,14 +5389,18 @@ void ieee80211_restart_hw(struct ieee80211_hw *hw); * mixed for a single hardware. Must not run concurrently with * ieee80211_tx_status_skb() or ieee80211_tx_status_ni(). * + * For data frames, when hardware has done address translation, a link station + * has to be provided and the frequency information may be skipped. + * * This function must be called with BHs disabled and RCU read lock * * @hw: the hardware this frame came in on - * @sta: the station the frame was received from, or %NULL + * @link_sta: the link station the data frame was received from, or %NULL * @skb: the buffer to receive, owned by mac80211 after this call * @list: the destination list */ -void ieee80211_rx_list(struct ieee80211_hw *hw, struct ieee80211_sta *sta, +void ieee80211_rx_list(struct ieee80211_hw *hw, + struct ieee80211_link_sta *link_sta, struct sk_buff *skb, struct list_head *list); /** @@ -5414,14 +5418,18 @@ void ieee80211_rx_list(struct ieee80211_hw *hw, struct ieee80211_sta *sta, * mixed for a single hardware. Must not run concurrently with * ieee80211_tx_status_skb() or ieee80211_tx_status_ni(). * + * For data frames, when hardware has done address translation, a link station + * has to be provided and the frequency information may be skipped. + * * This function must be called with BHs disabled. * * @hw: the hardware this frame came in on - * @sta: the station the frame was received from, or %NULL + * @link_sta: the link station the data frame was received from, or %NULL * @skb: the buffer to receive, owned by mac80211 after this call * @napi: the NAPI context */ -void ieee80211_rx_napi(struct ieee80211_hw *hw, struct ieee80211_sta *sta, +void ieee80211_rx_napi(struct ieee80211_hw *hw, + struct ieee80211_link_sta *link_sta, struct sk_buff *skb, struct napi_struct *napi); /** diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c index 31dc6f83781b..0ddd0b8ea12a 100644 --- a/net/mac80211/rx.c +++ b/net/mac80211/rx.c @@ -5556,7 +5556,8 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw, * This is the receive path handler. It is called by a low level driver when an * 802.11 MPDU is received from the hardware. */ -void ieee80211_rx_list(struct ieee80211_hw *hw, struct ieee80211_sta *pubsta, +void ieee80211_rx_list(struct ieee80211_hw *hw, + struct ieee80211_link_sta *link_pubsta, struct sk_buff *skb, struct list_head *list) { struct ieee80211_local *local = hw_to_local(hw); @@ -5564,6 +5565,7 @@ void ieee80211_rx_list(struct ieee80211_hw *hw, struct ieee80211_sta *pubsta, struct ieee80211_supported_band *sband; struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb); struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; + struct ieee80211_sta *pubsta; WARN_ON_ONCE(softirq_count() == 0); @@ -5694,8 +5696,15 @@ void ieee80211_rx_list(struct ieee80211_hw *hw, struct ieee80211_sta *pubsta, } } - if (WARN_ON_ONCE(status->link_id >= IEEE80211_LINK_UNSPECIFIED)) - goto drop; + /* FIXME: Emulate the old driver behaviour for now */ + if (link_pubsta) { + status->link_valid = 1; + status->link_id = link_pubsta->link_id; + pubsta = link_pubsta->sta; + } else { + status->link_valid = 0; + pubsta = NULL; + } status->rx_flags = 0; @@ -5727,7 +5736,8 @@ void ieee80211_rx_list(struct ieee80211_hw *hw, struct ieee80211_sta *pubsta, } EXPORT_SYMBOL(ieee80211_rx_list); -void ieee80211_rx_napi(struct ieee80211_hw *hw, struct ieee80211_sta *pubsta, +void ieee80211_rx_napi(struct ieee80211_hw *hw, + struct ieee80211_link_sta *link_pubsta, struct sk_buff *skb, struct napi_struct *napi) { struct sk_buff *tmp; @@ -5740,7 +5750,7 @@ void ieee80211_rx_napi(struct ieee80211_hw *hw, struct ieee80211_sta *pubsta, * receive processing */ rcu_read_lock(); - ieee80211_rx_list(hw, pubsta, skb, &list); + ieee80211_rx_list(hw, link_pubsta, skb, &list); rcu_read_unlock(); if (!napi) { From 4db13371e217c360cda76578f9eb5e1bcd10a17a Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Tue, 15 Sep 2026 15:19:29 +0200 Subject: [PATCH 086/101] wifi: mac80211: refactor RX link_id and station handling The link ID for one frequency may be different between VIFs. As such, the sensible thing is to set the link ID later in the flow once the SKB is duplicated for each VIF. As the link ID is not passed in from outside mac80211 it is always valid and the corresponding bit can be dropped. Also switch a few more places to pass the link STA as that is a natural way to pass the link information around. This fixes the per-link statistics when frames are reordered. Note that this patch deliberately leaves some places unchanged and even incorrect as this will be addressed by further refactorings. Signed-off-by: Benjamin Berg Link: https://patch.msgid.link/20260915151925.467593bc1105.I8abe3601d0335dca22d48bcee864817cfccf0de0@changeid Signed-off-by: Johannes Berg --- include/net/mac80211.h | 9 +- net/mac80211/ap.c | 3 - net/mac80211/iface.c | 7 +- net/mac80211/mlme.c | 10 +- net/mac80211/rx.c | 211 ++++++++++++++++++----------------------- net/mac80211/scan.c | 10 +- 6 files changed, 106 insertions(+), 144 deletions(-) diff --git a/include/net/mac80211.h b/include/net/mac80211.h index cba7cdf61ce8..1172d7b83ed1 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -1770,10 +1770,9 @@ enum mac80211_rx_encoding { * @ampdu_reference: A-MPDU reference number, must be a different value for * each A-MPDU but the same for each subframe within one A-MPDU * @zero_length_psdu_type: radiotap type of the 0-length PSDU - * @link_valid: if the link which is identified by @link_id is valid. This flag - * is set only when connection is MLO. - * @link_id: id of the link used to receive the packet. This is used along with - * @link_valid. + * @link_id: id of the link used to receive the packet. Set and used by + * mac80211 internally, it uses @freq set by the driver to identify the + * correct link per vif. */ struct ieee80211_rx_status { u64 mactime; @@ -1813,7 +1812,7 @@ struct ieee80211_rx_status { u8 chains; s8 chain_signal[IEEE80211_MAX_CHAINS]; u8 zero_length_psdu_type; - u8 link_valid:1, link_id:4; + u8 link_id:4; }; static_assert(sizeof(struct ieee80211_rx_status) <= sizeof_field(struct sk_buff, cb)); diff --git a/net/mac80211/ap.c b/net/mac80211/ap.c index e7ac99c5a22e..fe255891c35f 100644 --- a/net/mac80211/ap.c +++ b/net/mac80211/ap.c @@ -82,9 +82,6 @@ ieee80211_rx_eml_op_mode_notif(struct ieee80211_sub_if_data *sdata, if (!ift_ext_capa) return; - if (!status->link_valid) - return; - sta = sta_info_get_bss(sdata, mgmt->sa); if (!sta) return; diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c index 18e62c5ff0fd..29d532183814 100644 --- a/net/mac80211/iface.c +++ b/net/mac80211/iface.c @@ -1674,11 +1674,8 @@ static void ieee80211_iface_process_skb(struct ieee80211_local *local, break; status = IEEE80211_SKB_RXCB(skb); - if (!status->link_valid) - link_sta = &sta->deflink; - else - link_sta = rcu_dereference_protected(sta->link[status->link_id], - lockdep_is_held(&local->hw.wiphy->mtx)); + link_sta = wiphy_dereference(local->hw.wiphy, + sta->link[status->link_id]); if (link_sta) ieee80211_ht_handle_chanwidth_notif(local, sdata, sta, link_sta, chanwidth, diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index 47ec703f1094..189dd3467f76 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -11827,12 +11827,10 @@ void ieee80211_sta_rx_queued_frame(struct ieee80211_sub_if_data *sdata, rx_status = (struct ieee80211_rx_status *) skb->cb; fc = le16_to_cpu(mgmt->frame_control); - if (rx_status->link_valid) { - link = sdata_dereference(sdata->link[rx_status->link_id], - sdata); - if (!link) - return; - } + link = sdata_dereference(sdata->link[rx_status->link_id], + sdata); + if (!link) + return; switch (fc & IEEE80211_FCTL_STYPE) { case IEEE80211_STYPE_BEACON: diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c index 0ddd0b8ea12a..753d639d1955 100644 --- a/net/mac80211/rx.c +++ b/net/mac80211/rx.c @@ -222,43 +222,21 @@ ieee80211_rx_radiotap_hdrlen(struct ieee80211_local *local, } static void __ieee80211_queue_skb_to_iface(struct ieee80211_sub_if_data *sdata, - int link_id, - struct sta_info *sta, + struct link_sta_info *link_sta, struct sk_buff *skb) { - struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb); - - if (link_id >= 0) { - status->link_valid = 1; - status->link_id = link_id; - } else { - status->link_valid = 0; - } - skb_queue_tail(&sdata->skb_queue, skb); wiphy_work_queue(sdata->local->hw.wiphy, &sdata->work); - if (sta) { - struct link_sta_info *link_sta_info; - - if (link_id >= 0) { - link_sta_info = rcu_dereference(sta->link[link_id]); - if (!link_sta_info) - return; - } else { - link_sta_info = &sta->deflink; - } - - link_sta_info->rx_stats.packets++; - } + if (link_sta) + link_sta->rx_stats.packets++; } static void ieee80211_queue_skb_to_iface(struct ieee80211_sub_if_data *sdata, - int link_id, - struct sta_info *sta, + struct link_sta_info *link_sta, struct sk_buff *skb) { skb->protocol = 0; - __ieee80211_queue_skb_to_iface(sdata, link_id, sta, skb); + __ieee80211_queue_skb_to_iface(sdata, link_sta, skb); } static void ieee80211_handle_mu_mimo_mon(struct ieee80211_sub_if_data *sdata, @@ -301,7 +279,7 @@ static void ieee80211_handle_mu_mimo_mon(struct ieee80211_sub_if_data *sdata, if (!skb) return; - ieee80211_queue_skb_to_iface(sdata, -1, NULL, skb); + ieee80211_queue_skb_to_iface(sdata, NULL, skb); } /* @@ -1500,7 +1478,7 @@ static void ieee80211_rx_reorder_ampdu(struct ieee80211_rx_data *rx, /* if this mpdu is fragmented - terminate rx aggregation session */ sc = le16_to_cpu(hdr->seq_ctrl); if (sc & IEEE80211_SCTL_FRAG) { - ieee80211_queue_skb_to_iface(rx->sdata, rx->link_id, NULL, skb); + ieee80211_queue_skb_to_iface(rx->sdata, NULL, skb); return; } @@ -2524,7 +2502,7 @@ ieee80211_rx_h_defragment(struct ieee80211_rx_data *rx) out: ieee80211_led_rx(rx->local); - if (rx->sta) + if (rx->link_sta) rx->link_sta->rx_stats.packets++; return RX_CONTINUE; } @@ -3335,8 +3313,8 @@ ieee80211_rx_h_data(struct ieee80211_rx_data *rx) (tf->action_code == WLAN_TDLS_CHANNEL_SWITCH_REQUEST || tf->action_code == WLAN_TDLS_CHANNEL_SWITCH_RESPONSE)) { rx->skb->protocol = cpu_to_be16(ETH_P_TDLS); - __ieee80211_queue_skb_to_iface(sdata, rx->link_id, - rx->sta, rx->skb); + __ieee80211_queue_skb_to_iface(sdata, rx->link_sta, + rx->skb); return RX_QUEUED; } } @@ -3991,7 +3969,7 @@ ieee80211_rx_h_action(struct ieee80211_rx_data *rx) return RX_QUEUED; queue: - ieee80211_queue_skb_to_iface(sdata, rx->link_id, rx->sta, rx->skb); + ieee80211_queue_skb_to_iface(sdata, rx->link_sta, rx->skb); return RX_QUEUED; } @@ -4147,7 +4125,7 @@ ieee80211_rx_h_ext(struct ieee80211_rx_data *rx) return RX_DROP_U_UNEXPECTED_EXT_FRAME; /* for now only beacons are ext, so queue them */ - ieee80211_queue_skb_to_iface(sdata, rx->link_id, rx->sta, rx->skb); + ieee80211_queue_skb_to_iface(sdata, rx->link_sta, rx->skb); return RX_QUEUED; } @@ -4204,7 +4182,7 @@ ieee80211_rx_h_mgmt(struct ieee80211_rx_data *rx) return RX_DROP_U_UNHANDLED_MGMT_STYPE; } - ieee80211_queue_skb_to_iface(sdata, rx->link_id, rx->sta, rx->skb); + ieee80211_queue_skb_to_iface(sdata, rx->link_sta, rx->skb); return RX_QUEUED; } @@ -4248,12 +4226,28 @@ static void ieee80211_rx_handlers(struct ieee80211_rx_data *rx, spin_lock_bh(&rx->local->rx_path_lock); while ((skb = __skb_dequeue(frames))) { + struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb); + /* - * all the other fields are valid across frames - * that belong to an aMPDU since they are on the - * same TID from the same station + * Most fields are valid across frames. However, + * in the case of reordering frames may have arrived + * on different links, so adjust the link_id, link + * and link_sta accordingly. */ rx->skb = skb; + if (ieee80211_vif_is_mld(&rx->sdata->vif) && + unlikely(rx->link_id != status->link_id)) { + if (rx->sta) { + rx->link_sta = + rcu_dereference(rx->sta->link[rx->link_id]); + + /* Link got disabled, just use deflink */ + if (!rx->link_sta) + rx->link_sta = &rx->sta->deflink; + } + rx->link_id = status->link_id; + rx->link = rcu_dereference(rx->sdata->link[status->link_id]); + } if (WARN_ON_ONCE(!rx->link)) { res = RX_DROP_U_NO_LINK; @@ -4323,7 +4317,10 @@ static void ieee80211_invoke_rx_handlers(struct ieee80211_rx_data *rx) static bool ieee80211_rx_is_valid_sta_link_id(struct ieee80211_sta *sta, u8 link_id) { - return !!(sta->valid_links & BIT(link_id)); + if (sta->mlo) + return !!(sta->valid_links & BIT(link_id)); + else + return sta->deflink.link_id == link_id; } static bool ieee80211_rx_data_set_link(struct ieee80211_rx_data *rx, @@ -4360,11 +4357,13 @@ static bool ieee80211_rx_data_set_sta(struct ieee80211_rx_data *rx, if (link_id < 0) { if (ieee80211_vif_is_mld(&rx->sdata->vif) && - sta && !sta->sta.valid_links) + sta && !sta->sta.valid_links) { rx->link = rcu_dereference(rx->sdata->link[sta->deflink.link_id]); - else + rx->link_sta = &sta->deflink; + } else { rx->link = &rx->sdata->deflink; + } } else if (!ieee80211_rx_data_set_link(rx, link_id)) { return false; } @@ -4387,7 +4386,7 @@ void ieee80211_release_reorder_timeout(struct sta_info *sta, int tid) struct tid_ampdu_rx *tid_agg_rx; int link_id = -1; - /* FIXME: statistics won't be right with this */ + /* NOTE: the correct link STA for the frame is set later */ if (sta->sta.valid_links) link_id = ffs(sta->sta.valid_links) - 1; @@ -4914,20 +4913,14 @@ static void ieee80211_rx_8023(struct ieee80211_rx_data *rx, { struct ieee80211_sta_rx_stats *stats; struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(rx->skb); - struct sta_info *sta = rx->sta; - struct link_sta_info *link_sta; + struct link_sta_info *link_sta = rx->link_sta; struct sk_buff *skb = rx->skb; void *sa = skb->data + ETH_ALEN; void *da = skb->data; - if (rx->link_id >= 0) { - link_sta = rcu_dereference(sta->link[rx->link_id]); - if (WARN_ON_ONCE(!link_sta)) { - dev_kfree_skb(rx->skb); - return; - } - } else { - link_sta = &sta->deflink; + if (WARN_ON_ONCE(!link_sta)) { + dev_kfree_skb(rx->skb); + return; } stats = &link_sta->rx_stats; @@ -5154,6 +5147,9 @@ static bool ieee80211_invoke_fast_rx(struct ieee80211_rx_data *rx, goto drop; } + status = IEEE80211_SKB_RXCB(skb); + status->link_id = rx->link_id < 0 ? 0 : rx->link_id; + ieee80211_rx_8023(rx, fast_rx, orig_len); return true; @@ -5178,6 +5174,7 @@ static bool ieee80211_prepare_and_rx_handle(struct ieee80211_rx_data *rx, struct ieee80211_hdr *hdr = (void *)skb->data; struct link_sta_info *link_sta = rx->link_sta; struct ieee80211_link_data *link = rx->link; + struct ieee80211_rx_status *status; rx->skb = skb; @@ -5221,6 +5218,9 @@ static bool ieee80211_prepare_and_rx_handle(struct ieee80211_rx_data *rx, hdr = (struct ieee80211_hdr *)rx->skb->data; } + status = IEEE80211_SKB_RXCB(rx->skb); + status->link_id = rx->link_id < 0 ? 0 : rx->link_id; + if (ieee80211_is_s1g_beacon(hdr->frame_control)) { ieee80211_invoke_rx_handlers(rx); return true; @@ -5256,22 +5256,19 @@ static bool ieee80211_prepare_and_rx_handle(struct ieee80211_rx_data *rx, } static void __ieee80211_rx_handle_8023(struct ieee80211_hw *hw, - struct ieee80211_sta *pubsta, + struct ieee80211_link_sta *link_pubsta, struct sk_buff *skb, struct list_head *list) { struct ieee80211_local *local = hw_to_local(hw); - struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb); struct ieee80211_fast_rx *fast_rx; struct ieee80211_rx_data rx; struct sta_info *sta; - int link_id = -1; memset(&rx, 0, sizeof(rx)); rx.skb = skb; rx.local = local; rx.list = list; - rx.link_id = -1; I802_DEBUG_INC(local->dot11ReceivedFragmentCount); @@ -5279,23 +5276,15 @@ static void __ieee80211_rx_handle_8023(struct ieee80211_hw *hw, if (skb->len < sizeof(struct ethhdr)) goto drop; - if (!pubsta) + if (!link_pubsta) goto drop; - if (status->link_valid) - link_id = status->link_id; - - /* - * TODO: Should the frame be dropped if the right link_id is not - * available? Or may be it is fine in the current form to proceed with - * the frame processing because with frame being in 802.3 format, - * link_id is used only for stats purpose and updating the stats on - * the deflink is fine? - */ - sta = container_of(pubsta, struct sta_info, sta); - if (!ieee80211_rx_data_set_sta(&rx, sta, link_id)) + sta = container_of(link_pubsta->sta, struct sta_info, sta); + if (!ieee80211_rx_data_set_sta(&rx, sta, link_pubsta->link_id)) goto drop; + rx.link_id = link_pubsta->link_id; + fast_rx = rcu_dereference(rx.sta->fast_rx); if (!fast_rx) goto drop; @@ -5323,6 +5312,9 @@ static bool ieee80211_rx_for_interface(struct ieee80211_rx_data *rx, } /* + * FIXME: Here we can assume that link addresses have not + * been translated. + * * Look up link station first, in case there's a * chance that they might have a link address that * is identical to the MLD address, that way we'll @@ -5336,10 +5328,8 @@ static bool ieee80211_rx_for_interface(struct ieee80211_rx_data *rx, struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb); sta = sta_info_get_bss(rx->sdata, hdr->addr2); - if (status->link_valid) { - link_id = status->link_id; - } else if (ieee80211_vif_is_mld(&rx->sdata->vif) && - status->freq) { + if (ieee80211_vif_is_mld(&rx->sdata->vif) && + status->freq) { struct ieee80211_link_data *link; struct ieee80211_chanctx_conf *conf; @@ -5367,12 +5357,11 @@ static bool ieee80211_rx_for_interface(struct ieee80211_rx_data *rx, * be called with rcu_read_lock protection. */ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw, - struct ieee80211_sta *pubsta, + struct ieee80211_link_sta *link_pubsta, struct sk_buff *skb, struct list_head *list) { struct ieee80211_local *local = hw_to_local(hw); - struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb); struct ieee80211_sub_if_data *sdata; struct ieee80211_hdr *hdr; __le16 fc; @@ -5386,7 +5375,6 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw, rx.skb = skb; rx.local = local; rx.list = list; - rx.link_id = -1; if (ieee80211_is_data(fc) || ieee80211_is_mgmt(fc)) I802_DEBUG_INC(local->dot11ReceivedFragmentCount); @@ -5428,37 +5416,14 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw, if (ieee80211_is_data(fc)) { struct sta_info *sta, *prev_sta; - int link_id = -1; - if (status->link_valid) - link_id = status->link_id; - - if (pubsta) { - sta = container_of(pubsta, struct sta_info, sta); - if (!ieee80211_rx_data_set_sta(&rx, sta, link_id)) + if (link_pubsta) { + sta = container_of(link_pubsta->sta, + struct sta_info, sta); + if (!ieee80211_rx_data_set_sta(&rx, sta, + link_pubsta->link_id)) goto out; - /* - * In MLO connection, fetch the link_id using addr2 - * when the driver does not pass link_id in status. - * When the address translation is already performed by - * driver/hw, the valid link_id must be passed in - * status. - */ - - if (!status->link_valid && pubsta->mlo) { - struct link_sta_info *link_sta; - - link_sta = link_sta_info_get_bss(rx.sdata, - hdr->addr2); - if (!link_sta) - goto out; - - if (!ieee80211_rx_data_set_link(&rx, - link_sta->link_id)) - goto out; - } - if (ieee80211_prepare_and_rx_handle(&rx, skb, true)) return; goto out; @@ -5467,13 +5432,20 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw, prev_sta = NULL; for_each_sta_info(local, hdr->addr2, sta, tmp) { + int link_id; + if (!prev_sta) { prev_sta = sta; continue; } rx.sdata = prev_sta->sdata; - if (!status->link_valid && prev_sta->sta.mlo) { + + /* + * FIXME: This is not correct as the addr2 cannot be a + * link address if the loop itself is iterated. + */ + if (prev_sta->sta.mlo) { struct link_sta_info *link_sta; link_sta = link_sta_info_get_bss(rx.sdata, @@ -5482,6 +5454,8 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw, continue; link_id = link_sta->link_id; + } else { + link_id = sta->deflink.link_id; } if (!ieee80211_rx_data_set_sta(&rx, prev_sta, link_id)) @@ -5493,8 +5467,15 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw, } if (prev_sta) { + int link_id; + rx.sdata = prev_sta->sdata; - if (!status->link_valid && prev_sta->sta.mlo) { + + /* + * FIXME: This is not correct as the addr2 cannot be a + * link address if the loop itself is iterated. + */ + if (prev_sta->sta.mlo) { struct link_sta_info *link_sta; link_sta = link_sta_info_get_bss(rx.sdata, @@ -5503,6 +5484,8 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw, goto out; link_id = link_sta->link_id; + } else { + link_id = sta->deflink.link_id; } if (!ieee80211_rx_data_set_sta(&rx, prev_sta, link_id)) @@ -5565,7 +5548,6 @@ void ieee80211_rx_list(struct ieee80211_hw *hw, struct ieee80211_supported_band *sband; struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb); struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; - struct ieee80211_sta *pubsta; WARN_ON_ONCE(softirq_count() == 0); @@ -5696,16 +5678,6 @@ void ieee80211_rx_list(struct ieee80211_hw *hw, } } - /* FIXME: Emulate the old driver behaviour for now */ - if (link_pubsta) { - status->link_valid = 1; - status->link_id = link_pubsta->link_id; - pubsta = link_pubsta->sta; - } else { - status->link_valid = 0; - pubsta = NULL; - } - status->rx_flags = 0; kcov_remote_start_common(skb_get_kcov_handle(skb)); @@ -5724,9 +5696,10 @@ void ieee80211_rx_list(struct ieee80211_hw *hw, ieee80211_tpt_led_trig_rx(local, skb->len); if (status->flag & RX_FLAG_8023) - __ieee80211_rx_handle_8023(hw, pubsta, skb, list); + __ieee80211_rx_handle_8023(hw, link_pubsta, skb, list); else - __ieee80211_rx_handle_packet(hw, pubsta, skb, list); + __ieee80211_rx_handle_packet(hw, link_pubsta, skb, + list); } kcov_remote_stop(); diff --git a/net/mac80211/scan.c b/net/mac80211/scan.c index eeff230bd909..324c2b6d7ce1 100644 --- a/net/mac80211/scan.c +++ b/net/mac80211/scan.c @@ -9,7 +9,7 @@ * Copyright 2007, Michael Wu * Copyright 2013-2015 Intel Mobile Communications GmbH * Copyright 2016-2017 Intel Deutschland GmbH - * Copyright (C) 2018-2025 Intel Corporation + * Copyright (C) 2018-2026 Intel Corporation */ #include @@ -204,12 +204,10 @@ ieee80211_bss_info_update(struct ieee80211_local *local, * an indication on which of the links the frame was received */ if (ieee80211_vif_is_mld(&scan_sdata->vif)) { - if (rx_status->link_valid) { - s8 link_id = rx_status->link_id; + s8 link_id = rx_status->link_id; - link_conf = - rcu_dereference(scan_sdata->vif.link_conf[link_id]); - } + link_conf = + rcu_dereference(scan_sdata->vif.link_conf[link_id]); } else { link_conf = &scan_sdata->vif.bss_conf; } From 6d531b9af16ea84aab72db573384fa94bfa0f2f7 Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Tue, 15 Sep 2026 15:19:30 +0200 Subject: [PATCH 087/101] wifi: mac80211: rework RX packet handling The code has grown over time and it was not correctly handling all cases. Examples of issues are that for management frames we would match the received address against the MLD address even though we should not resolve the station in that case. Another issue was that for data frames we would not correctly fall back to use link addresses when the driver does not provide the pubsta already. The new code still uses the same approach as before. For data frames, assume that a valid station exists and interfaces do not need to be iterated. On the other hand, for non-data frames (or if a data frame without a station is received) all interfaces must be iterated. Note that this rework makes mac80211 slightly more strict. For example, previously mac80211 would have incorrectly accepted a data frame that was transmitted on the wrong link. Signed-off-by: Benjamin Berg Link: https://patch.msgid.link/20260915151925.de2fc9a47273.Ie12ec077142c6a7fdbb58cdfc5660497cc75150e@changeid Signed-off-by: Johannes Berg --- net/mac80211/rx.c | 301 +++++++++++++++++++++++++++------------------- 1 file changed, 176 insertions(+), 125 deletions(-) diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c index 753d639d1955..1273c9067aed 100644 --- a/net/mac80211/rx.c +++ b/net/mac80211/rx.c @@ -4318,9 +4318,33 @@ static bool ieee80211_rx_is_valid_sta_link_id(struct ieee80211_sta *sta, u8 link_id) { if (sta->mlo) - return !!(sta->valid_links & BIT(link_id)); - else - return sta->deflink.link_id == link_id; + return sta->valid_links & BIT(link_id); + + return sta->deflink.link_id == link_id; +} + +static bool ieee80211_rx_data_set_link_sta(struct ieee80211_rx_data *rx, + struct link_sta_info *link_sta) +{ + struct sta_info *sta; + + if (WARN_ON_ONCE(!link_sta) || + !ieee80211_rx_is_valid_sta_link_id(&link_sta->sta->sta, + link_sta->link_id)) + return false; + + sta = link_sta->sta; + + rx->sta = sta; + rx->local = sta->sdata->local; + rx->link = rcu_dereference(sta->sdata->link[link_sta->link_id]); + if (!rx->link) + return false; + + rx->link_id = rx->link->link_id; + rx->link_sta = link_sta; + + return true; } static bool ieee80211_rx_data_set_link(struct ieee80211_rx_data *rx, @@ -5296,60 +5320,19 @@ static void __ieee80211_rx_handle_8023(struct ieee80211_hw *hw, dev_kfree_skb(skb); } -static bool ieee80211_rx_for_interface(struct ieee80211_rx_data *rx, - struct sk_buff *skb, bool consume) +static bool ieee80211_rx_valid_freq(int freq, struct ieee80211_link_data *link) { - struct link_sta_info *link_sta; - struct ieee80211_hdr *hdr = (void *)skb->data; - struct sta_info *sta; - int link_id = -1; + struct ieee80211_chanctx_conf *conf; - if (ieee80211_is_s1g_beacon(hdr->frame_control)) { - if (!ieee80211_rx_data_set_sta(rx, NULL, -1)) - return false; + if (!freq || link->sdata->vif.type == NL80211_IFTYPE_NAN || + link->sdata->vif.type == NL80211_IFTYPE_NAN_DATA) + return true; - return ieee80211_prepare_and_rx_handle(rx, skb, consume); - } - - /* - * FIXME: Here we can assume that link addresses have not - * been translated. - * - * Look up link station first, in case there's a - * chance that they might have a link address that - * is identical to the MLD address, that way we'll - * have the link information if needed. - */ - link_sta = link_sta_info_get_bss(rx->sdata, hdr->addr2); - if (link_sta) { - sta = link_sta->sta; - link_id = link_sta->link_id; - } else { - struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb); - - sta = sta_info_get_bss(rx->sdata, hdr->addr2); - if (ieee80211_vif_is_mld(&rx->sdata->vif) && - status->freq) { - struct ieee80211_link_data *link; - struct ieee80211_chanctx_conf *conf; - - for_each_link_data_rcu(rx->sdata, link) { - conf = rcu_dereference(link->conf->chanctx_conf); - if (!conf || !conf->def.chan) - continue; - - if (status->freq == conf->def.chan->center_freq) { - link_id = link->link_id; - break; - } - } - } - } - - if (!ieee80211_rx_data_set_sta(rx, sta, link_id)) + conf = rcu_dereference(link->conf->chanctx_conf); + if (!conf || !conf->def.chan) return false; - return ieee80211_prepare_and_rx_handle(rx, skb, consume); + return freq == conf->def.chan->center_freq; } /* @@ -5363,11 +5346,14 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw, { struct ieee80211_local *local = hw_to_local(hw); struct ieee80211_sub_if_data *sdata; + struct ieee80211_rx_status *status = IEEE80211_SKB_RXCB(skb); struct ieee80211_hdr *hdr; + struct link_sta_info *link_sta; + struct sta_info *sta; __le16 fc; struct ieee80211_rx_data rx; - struct ieee80211_sub_if_data *prev; struct rhlist_head *tmp; + bool rx_data_pending; int err = 0; fc = ((struct ieee80211_hdr *)skb->data)->frame_control; @@ -5414,92 +5400,102 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw, ieee80211_is_s1g_beacon(hdr->frame_control))) ieee80211_scan_rx(local, skb); + /* + * RX of a data frame should only happen to existing stations. + * It is therefore more efficient to use the one provided by the driver + * or search based on the station's address. + * + * Note we will fall through and handle a spurious data frame in the + * same way as a management frame. + */ if (ieee80211_is_data(fc)) { - struct sta_info *sta, *prev_sta; - if (link_pubsta) { - sta = container_of(link_pubsta->sta, - struct sta_info, sta); - if (!ieee80211_rx_data_set_sta(&rx, sta, - link_pubsta->link_id)) - goto out; + sta = container_of(link_pubsta->sta, struct sta_info, + sta); + link_sta = rcu_dereference(sta->link[link_pubsta->link_id]); - if (ieee80211_prepare_and_rx_handle(&rx, skb, true)) + rx.sdata = sta->sdata; + if (ieee80211_rx_data_set_link_sta(&rx, link_sta) && + ieee80211_prepare_and_rx_handle(&rx, skb, true)) return; + goto out; } - prev_sta = NULL; + rx_data_pending = false; + /* Search for stations on non-MLD interfaces */ for_each_sta_info(local, hdr->addr2, sta, tmp) { - int link_id; + struct ieee80211_link_data *link; - if (!prev_sta) { - prev_sta = sta; + if (ieee80211_vif_is_mld(&sta->sdata->vif)) continue; + + link = &sta->sdata->deflink; + if (!ieee80211_rx_valid_freq(status->freq, link)) + continue; + + if (rx_data_pending) { + ieee80211_prepare_and_rx_handle(&rx, skb, + false); + rx_data_pending = false; } - rx.sdata = prev_sta->sdata; + rx.sdata = sta->sdata; + if (!ieee80211_rx_data_set_link_sta(&rx, &sta->deflink)) + continue; - /* - * FIXME: This is not correct as the addr2 cannot be a - * link address if the loop itself is iterated. - */ - if (prev_sta->sta.mlo) { - struct link_sta_info *link_sta; - - link_sta = link_sta_info_get_bss(rx.sdata, - hdr->addr2); - if (!link_sta) - continue; - - link_id = link_sta->link_id; - } else { - link_id = sta->deflink.link_id; - } - - if (!ieee80211_rx_data_set_sta(&rx, prev_sta, link_id)) - goto out; - - ieee80211_prepare_and_rx_handle(&rx, skb, false); - - prev_sta = sta; + rx_data_pending = true; } - if (prev_sta) { - int link_id; + /* And search stations on MLD interfaces */ + for_each_link_sta_info(local, hdr->addr2, link_sta, tmp) { + struct ieee80211_link_data *link; - rx.sdata = prev_sta->sdata; + sta = link_sta->sta; + sdata = sta->sdata; + link = rcu_dereference(sdata->link[link_sta->link_id]); - /* - * FIXME: This is not correct as the addr2 cannot be a - * link address if the loop itself is iterated. - */ - if (prev_sta->sta.mlo) { - struct link_sta_info *link_sta; + if (!ieee80211_rx_valid_freq(status->freq, link)) + continue; - link_sta = link_sta_info_get_bss(rx.sdata, - hdr->addr2); - if (!link_sta) - goto out; - - link_id = link_sta->link_id; - } else { - link_id = sta->deflink.link_id; + if (rx_data_pending) { + ieee80211_prepare_and_rx_handle(&rx, skb, + false); + rx_data_pending = false; } - if (!ieee80211_rx_data_set_sta(&rx, prev_sta, link_id)) - goto out; + rx.sdata = sta->sdata; + if (!ieee80211_rx_data_set_link_sta(&rx, link_sta)) + continue; + rx_data_pending = true; + } + + if (rx_data_pending) { if (ieee80211_prepare_and_rx_handle(&rx, skb, true)) return; + goto out; } + + /* fall through, e.g. for spurious frame notification */ } - prev = NULL; + /* + * This is a management frame (or a data frame without a station) and + * it will be delivered independent of whether a station exists, + * so iterate the interfaces. + */ + rx_data_pending = false; + + /* We expect the driver to provide frequency information */ + if (WARN_ON_ONCE(!local->emulate_chanctx && !status->freq)) + goto out; list_for_each_entry_rcu(sdata, &local->interfaces, list) { + struct ieee80211_link_data *link = NULL; + if (!ieee80211_sdata_running(sdata)) continue; @@ -5507,29 +5503,84 @@ static void __ieee80211_rx_handle_packet(struct ieee80211_hw *hw, sdata->vif.type == NL80211_IFTYPE_AP_VLAN) continue; - /* - * frame is destined for this interface, but if it's - * not also for the previous one we handle that after - * the loop to avoid copying the SKB once too much - */ + link_sta = NULL; + link = NULL; + + /* Try to resolve the station */ + if (!ieee80211_vif_is_mld(&sdata->vif)) { + sta = sta_info_get_bss(sdata, hdr->addr2); + + if (sta) { + link_sta = &sta->deflink; + link = &sdata->deflink; + } + } else { + link_sta = link_sta_info_get_bss(sdata, hdr->addr2); + + if (link_sta) + link = rcu_dereference(sdata->link[link_sta->link_id]); + } + + /* + * If we found a STA and it has a valid link information, then + * RX using the station. + */ + if (link_sta && link && + ieee80211_rx_valid_freq(status->freq, link)) { + if (rx_data_pending) { + ieee80211_prepare_and_rx_handle(&rx, skb, false); + rx_data_pending = false; + } + + /* No valid_links check as we need to RX beacons */ + + rx.sdata = sdata; + if (ieee80211_rx_data_set_link_sta(&rx, link_sta)) + rx_data_pending = true; - if (!prev) { - prev = sdata; continue; } - rx.sdata = prev; - ieee80211_rx_for_interface(&rx, skb, false); + /* No station, try to resolve the link and RX */ + if (ieee80211_vif_is_mld(&sdata->vif)) { + struct ieee80211_chanctx_conf *conf; + bool found = false; - prev = sdata; + for_each_link_data_rcu(sdata, link) { + conf = rcu_dereference(link->conf->chanctx_conf); + if (!conf || !conf->def.chan) + continue; + + if (status->freq == conf->def.chan->center_freq) { + found = true; + break; + } + } + + if (!found) + link = &sdata->deflink; + } else { + link = &sdata->deflink; + } + + if (rx_data_pending) { + ieee80211_prepare_and_rx_handle(&rx, skb, false); + rx_data_pending = false; + } + + rx.sdata = sdata; + rx.local = sdata->local; + rx.link = link; + rx.link_id = link->link_id; + rx.sta = NULL; + rx.link_sta = NULL; + + rx_data_pending = true; } - if (prev) { - rx.sdata = prev; - - if (ieee80211_rx_for_interface(&rx, skb, true)) - return; - } + if (rx_data_pending && + ieee80211_prepare_and_rx_handle(&rx, skb, true)) + return; out: dev_kfree_skb(skb); From de5f5ca3d3bfcdcc38c10eb9246eca3687cd2261 Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Tue, 15 Sep 2026 15:19:31 +0200 Subject: [PATCH 088/101] wifi: cfg80211: add attribute for TX/RX denoting there is no station For MLD stations, userspace may need to explicitly transmit a frame to a specific link address. In that case, it needs to ensure that no address translation happens. In the reverse case of an RX, userspace may need to know the link address for a frame. By passing the information whether a STA is known for the frame, userspace knows whether link translation happened and can do the reverse lookup when needed. This is important for flows where a STA is still registered but the connection has been lost and it is returning again. Signed-off-by: Benjamin Berg Link: https://patch.msgid.link/20260915151925.f12af80b1972.I4cfd3db83ba3dc0267575f919ba613ea5d10abd5@changeid Signed-off-by: Johannes Berg --- include/net/cfg80211.h | 4 ++++ include/uapi/linux/nl80211.h | 7 +++++++ net/wireless/nl80211.c | 8 +++++++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 6461a7bd0169..9aa7d2d4a184 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -4009,6 +4009,7 @@ struct cfg80211_update_ft_ies_params { * @link_id: for MLO, the link ID to transmit on, -1 if not given; note * that the link ID isn't validated (much), it's in range but the * link might not exist (or be used by the receiver STA) + * @no_sta: set if the frame should not be transmitted using an existing STA */ struct cfg80211_mgmt_tx_params { struct ieee80211_channel *chan; @@ -4021,6 +4022,7 @@ struct cfg80211_mgmt_tx_params { int n_csa_offsets; const u16 *csa_offsets; int link_id; + bool no_sta; }; /** @@ -9501,6 +9503,7 @@ void cfg80211_conn_failed(struct net_device *dev, const u8 *mac_addr, * @flags: flags, as defined in &enum nl80211_rxmgmt_flags * @rx_tstamp: Hardware timestamp of frame RX in nanoseconds * @ack_tstamp: Hardware timestamp of ack TX in nanoseconds + * @no_sta: set if no station is known for the frame (relevant for MLD) */ struct cfg80211_rx_info { int freq; @@ -9512,6 +9515,7 @@ struct cfg80211_rx_info { u32 flags; u64 rx_tstamp; u64 ack_tstamp; + bool no_sta; }; /** diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 32553cacf988..510102febc26 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -3185,6 +3185,11 @@ enum nl80211_commands { * The aggregated message always precedes the per-link messages for the * same station within a dump sequence. * + * @NL80211_ATTR_FRAME_NO_STA: Valid for @NL80211_CMD_FRAME to denote that + * the kernel had no station for a received frame or should not use a + * known station to transmit a frame. This is relevant to know whether + * MLD address translation happened or to disable it when sending a frame. + * * @NUM_NL80211_ATTR: total number of nl80211_attrs available * @NL80211_ATTR_MAX: highest attribute number currently defined * @__NL80211_ATTR_AFTER_LAST: internal use @@ -3785,6 +3790,8 @@ enum nl80211_attrs { NL80211_ATTR_STA_DUMP_LINK_STATS, + NL80211_ATTR_FRAME_NO_STA, + /* add attributes here, update the policy in nl80211.c */ __NL80211_ATTR_AFTER_LAST, diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index b081eeaf7016..e77a4c81d031 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -1097,6 +1097,7 @@ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = { [NL80211_ATTR_NPCA_PUNCT_BITMAP] = NLA_POLICY_FULL_RANGE(NLA_U32, &nl80211_punct_bitmap_range), [NL80211_ATTR_STA_DUMP_LINK_STATS] = { .type = NLA_FLAG }, + [NL80211_ATTR_FRAME_NO_STA] = { .type = NLA_FLAG }, }; /* policy for the key attributes */ @@ -14873,6 +14874,9 @@ static int nl80211_tx_mgmt(struct sk_buff *skb, struct genl_info *info) !(wdev->valid_links & BIT(params.link_id))) return -EINVAL; + params.no_sta = + nla_get_flag(info->attrs[NL80211_ATTR_FRAME_NO_STA]); + params.buf = nla_data(info->attrs[NL80211_ATTR_FRAME]); params.len = nla_len(info->attrs[NL80211_ATTR_FRAME]); @@ -22042,7 +22046,9 @@ int nl80211_send_mgmt(struct cfg80211_registered_device *rdev, (info->ack_tstamp && nla_put_u64_64bit(msg, NL80211_ATTR_TX_HW_TIMESTAMP, info->ack_tstamp, - NL80211_ATTR_PAD))) + NL80211_ATTR_PAD)) || + (info->no_sta && + nla_put_flag(msg, NL80211_ATTR_FRAME_NO_STA))) goto nla_put_failure; genlmsg_end(msg, hdr); From fc60152ad26d085e12d3dfd33e3d5640e8cbbb72 Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Tue, 15 Sep 2026 15:19:32 +0200 Subject: [PATCH 089/101] wifi: mac80211: report to cfg80211 when no STA is known for a frame This is relevant for hostapd to know whether address translation was done on a received management frame. Signed-off-by: Benjamin Berg Link: https://patch.msgid.link/20260915151925.273a1c7a1ab2.I28d8146b9189c5961411ecb26b44588895de1b56@changeid Signed-off-by: Johannes Berg --- net/mac80211/rx.c | 1 + 1 file changed, 1 insertion(+) diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c index 1273c9067aed..6c823678d099 100644 --- a/net/mac80211/rx.c +++ b/net/mac80211/rx.c @@ -3983,6 +3983,7 @@ ieee80211_rx_h_userspace_mgmt(struct ieee80211_rx_data *rx) .len = rx->skb->len, .link_id = rx->link_id, .have_link_id = rx->link_id >= 0, + .no_sta = !rx->sta, }; /* skip known-bad action frames and return them in the next handler */ From ec27695b0e5ca5360c7ed6d677fbc8e3cb13057b Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Tue, 15 Sep 2026 15:19:33 +0200 Subject: [PATCH 090/101] wifi: mac80211: pass station to ieee80211_tx_skb_tid The station may be relevant for queuing and will also generally be resolved in some cases. However, we want to be able to prevent looking up the station based on the address. Add a station parameter, which can be set to the correct station, to an error value to prevent station lookup or to NULL to get the old behaviour where the address is used to find the appropriate station. Also disable the station lookup for ieee80211_tx_skb_tid_band already as it does not make any sense to find a station when doing an off-channel transmit. Signed-off-by: Benjamin Berg Link: https://patch.msgid.link/20260915151925.759b6144ddf9.Ib7be90db0d3712d14e7a292023ff3d922baef860@changeid Signed-off-by: Johannes Berg --- net/mac80211/agg-tx.c | 4 ++-- net/mac80211/ht.c | 2 +- net/mac80211/ieee80211_i.h | 11 +++++++---- net/mac80211/offchannel.c | 2 +- net/mac80211/rx.c | 2 +- net/mac80211/tdls.c | 2 +- net/mac80211/tx.c | 10 ++++++---- 7 files changed, 19 insertions(+), 14 deletions(-) diff --git a/net/mac80211/agg-tx.c b/net/mac80211/agg-tx.c index 0832213430f4..ce9fb19d8c18 100644 --- a/net/mac80211/agg-tx.c +++ b/net/mac80211/agg-tx.c @@ -98,7 +98,7 @@ static void ieee80211_send_addba_request(struct sta_info *sta, u16 tid, if (sta->sta.deflink.he_cap.has_he) ieee80211_add_addbaext(skb, 0, agg_size); - ieee80211_tx_skb_tid(sdata, skb, tid, -1); + ieee80211_tx_skb_tid(sdata, skb, NULL, tid, -1); } void ieee80211_send_bar(struct ieee80211_vif *vif, u8 *ra, u16 tid, u16 ssn) @@ -127,7 +127,7 @@ void ieee80211_send_bar(struct ieee80211_vif *vif, u8 *ra, u16 tid, u16 ssn) IEEE80211_SKB_CB(skb)->flags |= IEEE80211_TX_INTFL_DONT_ENCRYPT | IEEE80211_TX_CTL_REQ_TX_STATUS; - ieee80211_tx_skb_tid(sdata, skb, tid, -1); + ieee80211_tx_skb_tid(sdata, skb, NULL, tid, -1); } EXPORT_SYMBOL(ieee80211_send_bar); diff --git a/net/mac80211/ht.c b/net/mac80211/ht.c index e1e1b7f82f3f..c5c2113fa4f7 100644 --- a/net/mac80211/ht.c +++ b/net/mac80211/ht.c @@ -545,7 +545,7 @@ int ieee80211_send_smps_action(struct ieee80211_sub_if_data *sdata, info->status_data = IEEE80211_STATUS_TYPE_SMPS | u16_encode_bits(status_link_id << 2 | smps, IEEE80211_STATUS_SUBDATA_MASK); - ieee80211_tx_skb_tid(sdata, skb, 7, link_id); + ieee80211_tx_skb_tid(sdata, skb, NULL, 7, link_id); return 0; } diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 65311340db40..9514f01778be 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h @@ -2471,7 +2471,8 @@ void ieee80211_xmit(struct ieee80211_sub_if_data *sdata, struct sta_info *sta, struct sk_buff *skb); void __ieee80211_tx_skb_tid_band(struct ieee80211_sub_if_data *sdata, - struct sk_buff *skb, int tid, int link_id, + struct sk_buff *skb, struct sta_info *sta, + int tid, int link_id, enum nl80211_band band); static inline bool ieee80211_require_encrypted_assoc(__le16 fc, @@ -2486,18 +2487,20 @@ ieee80211_tx_skb_tid_band(struct ieee80211_sub_if_data *sdata, enum nl80211_band band) { rcu_read_lock(); - __ieee80211_tx_skb_tid_band(sdata, skb, tid, -1, band); + __ieee80211_tx_skb_tid_band(sdata, skb, ERR_PTR(-ENOENT), + tid, -1, band); rcu_read_unlock(); } void ieee80211_tx_skb_tid(struct ieee80211_sub_if_data *sdata, - struct sk_buff *skb, int tid, int link_id); + struct sk_buff *skb, struct sta_info *sta, + int tid, int link_id); static inline void ieee80211_tx_skb(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb) { /* Send all internal mgmt frames on VO. Accordingly set TID to 7. */ - ieee80211_tx_skb_tid(sdata, skb, 7, -1); + ieee80211_tx_skb_tid(sdata, skb, NULL, 7, -1); } /** diff --git a/net/mac80211/offchannel.c b/net/mac80211/offchannel.c index 7acef80d5f1f..5cec8747b193 100644 --- a/net/mac80211/offchannel.c +++ b/net/mac80211/offchannel.c @@ -1022,7 +1022,7 @@ int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, } if (!need_offchan) { - ieee80211_tx_skb_tid(sdata, skb, 7, link_id); + ieee80211_tx_skb_tid(sdata, skb, NULL, 7, link_id); ret = 0; goto out_unlock; } diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c index 6c823678d099..d6755a4c7d3c 100644 --- a/net/mac80211/rx.c +++ b/net/mac80211/rx.c @@ -4106,7 +4106,7 @@ ieee80211_rx_h_action_return(struct ieee80211_rx_data *rx) local->hw.offchannel_tx_hw_queue; } - __ieee80211_tx_skb_tid_band(rx->sdata, nskb, 7, -1, + __ieee80211_tx_skb_tid_band(rx->sdata, nskb, rx->sta, 7, -1, status->band); } diff --git a/net/mac80211/tdls.c b/net/mac80211/tdls.c index 4f4cae9fd274..7ec33885a3e1 100644 --- a/net/mac80211/tdls.c +++ b/net/mac80211/tdls.c @@ -1066,7 +1066,7 @@ ieee80211_tdls_prep_mgmt_packet(struct wiphy *wiphy, struct net_device *dev, } if (action_code == WLAN_PUB_ACTION_TDLS_DISCOVER_RES) { - ieee80211_tx_skb_tid(sdata, skb, 7, link_id); + ieee80211_tx_skb_tid(sdata, skb, sta, 7, link_id); return 0; } diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c index 06c50183f064..73affc540f0c 100644 --- a/net/mac80211/tx.c +++ b/net/mac80211/tx.c @@ -6448,7 +6448,8 @@ void ieee80211_unreserve_tid(struct ieee80211_sta *pubsta, u8 tid) EXPORT_SYMBOL(ieee80211_unreserve_tid); void __ieee80211_tx_skb_tid_band(struct ieee80211_sub_if_data *sdata, - struct sk_buff *skb, int tid, int link_id, + struct sk_buff *skb, struct sta_info *sta, + int tid, int link_id, enum nl80211_band band) { const struct ieee80211_hdr *hdr = (void *)skb->data; @@ -6500,12 +6501,13 @@ void __ieee80211_tx_skb_tid_band(struct ieee80211_sub_if_data *sdata, */ local_bh_disable(); IEEE80211_SKB_CB(skb)->band = band; - ieee80211_xmit(sdata, NULL, skb); + ieee80211_xmit(sdata, sta, skb); local_bh_enable(); } void ieee80211_tx_skb_tid(struct ieee80211_sub_if_data *sdata, - struct sk_buff *skb, int tid, int link_id) + struct sk_buff *skb, struct sta_info *sta, + int tid, int link_id) { struct ieee80211_chanctx_conf *chanctx_conf; enum nl80211_band band; @@ -6531,7 +6533,7 @@ void ieee80211_tx_skb_tid(struct ieee80211_sub_if_data *sdata, band = 0; } - __ieee80211_tx_skb_tid_band(sdata, skb, tid, link_id, band); + __ieee80211_tx_skb_tid_band(sdata, skb, sta, tid, link_id, band); rcu_read_unlock(); } From 3b35f726c20d138fafa154182ee926a419306269 Mon Sep 17 00:00:00 2001 From: Benjamin Berg Date: Tue, 15 Sep 2026 15:19:34 +0200 Subject: [PATCH 091/101] wifi: mac80211: pass error station if non-STA transmit was requested When cfg80211 requested a transmit without a station, pass an error station to ieee80211_tx_skb_tid instead of the correct one. Signed-off-by: Benjamin Berg Link: https://patch.msgid.link/20260915151925.92499fb21e4a.I5ffe7bc0d4ccefca5c1e506d5d3d482e13989cda@changeid Signed-off-by: Johannes Berg --- net/mac80211/offchannel.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/net/mac80211/offchannel.c b/net/mac80211/offchannel.c index 5cec8747b193..ddd7a96a2f81 100644 --- a/net/mac80211/offchannel.c +++ b/net/mac80211/offchannel.c @@ -854,8 +854,10 @@ int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, need_offchan = true; rcu_read_lock(); - sta = sta_info_get_bss(sdata, mgmt->da); - mlo_sta = sta && sta->sta.mlo; + if (!params->no_sta) { + sta = sta_info_get_bss(sdata, mgmt->da); + mlo_sta = sta && sta->sta.mlo; + } if (!ieee80211_is_action(mgmt->frame_control) || mgmt->u.action.category == WLAN_CATEGORY_PUBLIC || @@ -884,7 +886,8 @@ int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, local->ops->remain_on_channel && memcmp(sdata->vif.cfg.ap_addr, mgmt->bssid, ETH_ALEN))) { need_offchan = true; - } else if (sdata->u.mgd.associated && + } else if (!params->no_sta && + sdata->u.mgd.associated && ether_addr_equal(sdata->vif.cfg.ap_addr, mgmt->da)) { sta = sta_info_get_bss(sdata, mgmt->da); mlo_sta = sta && sta->sta.mlo; @@ -1022,7 +1025,9 @@ int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, } if (!need_offchan) { - ieee80211_tx_skb_tid(sdata, skb, NULL, 7, link_id); + ieee80211_tx_skb_tid(sdata, skb, + params->no_sta ? ERR_PTR(-ENOENT) : sta, + 7, link_id); ret = 0; goto out_unlock; } From 8a673c89441d71776579d2ca307c7a6f0bcd86f1 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Sun, 20 Sep 2026 10:28:18 +0800 Subject: [PATCH 092/101] wifi: mac80211: use assign_bit() where applicable Convert open-coded if/else with set_bit/clear_bit the assign_bit API. Done with Coccinelle semantic patch: // set_bit -> clear_bit => assign_bit @@ expression cond, bit, addr; @@ -if (cond) - set_bit(bit, addr); -else - clear_bit(bit, addr); +assign_bit(bit, addr, cond); @@ expression cond, bit, addr; @@ -if (cond) - clear_bit(bit, addr); -else - set_bit(bit, addr); +assign_bit(bit, addr, !cond); Signed-off-by: Peng Fan Link: https://patch.msgid.link/20260920022822.3145944-1-peng.fan@oss.nxp.com Signed-off-by: Johannes Berg --- net/mac80211/agg-tx.c | 5 +---- net/mac80211/rx.c | 5 +---- net/mac80211/tests/chan-mode.c | 6 ++---- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/net/mac80211/agg-tx.c b/net/mac80211/agg-tx.c index ce9fb19d8c18..f3ecdd8601a0 100644 --- a/net/mac80211/agg-tx.c +++ b/net/mac80211/agg-tx.c @@ -212,10 +212,7 @@ ieee80211_agg_start_txq(struct sta_info *sta, int tid, bool enable) txqi = to_txq_info(txq); - if (enable) - set_bit(IEEE80211_TXQ_AMPDU, &txqi->flags); - else - clear_bit(IEEE80211_TXQ_AMPDU, &txqi->flags); + assign_bit(IEEE80211_TXQ_AMPDU, &txqi->flags, enable); clear_bit(IEEE80211_TXQ_STOP, &txqi->flags); local_bh_disable(); diff --git a/net/mac80211/rx.c b/net/mac80211/rx.c index d6755a4c7d3c..b3990b7a7299 100644 --- a/net/mac80211/rx.c +++ b/net/mac80211/rx.c @@ -1692,10 +1692,7 @@ static void sta_ps_start(struct sta_info *sta) list_del_init(&txqi->schedule_order); spin_unlock(&local->active_txq_lock[txq->ac]); - if (txq_has_queue(txq)) - set_bit(tid, &sta->txq_buffered_tids); - else - clear_bit(tid, &sta->txq_buffered_tids); + assign_bit(tid, &sta->txq_buffered_tids, txq_has_queue(txq)); } sta_info_recalc_tim(sta); diff --git a/net/mac80211/tests/chan-mode.c b/net/mac80211/tests/chan-mode.c index ab7d38ef6a3a..85dc87215d23 100644 --- a/net/mac80211/tests/chan-mode.c +++ b/net/mac80211/tests/chan-mode.c @@ -219,10 +219,8 @@ static void test_determine_chan_mode(struct kunit *test) /* To force EHT downgrade to HE on punctured 80 MHz downgraded to 40 MHz */ set_bit(IEEE80211_HW_DISALLOW_PUNCTURING, t_sdata->local.hw.flags); - if (params->strict) - set_bit(IEEE80211_HW_STRICT, t_sdata->local.hw.flags); - else - clear_bit(IEEE80211_HW_STRICT, t_sdata->local.hw.flags); + assign_bit(IEEE80211_HW_STRICT, t_sdata->local.hw.flags, + params->strict); t_sdata->sdata->u.mgd.ht_capa_mask = params->ht_capa_mask; t_sdata->sdata->u.mgd.vht_capa = params->vht_capa; From 5c299357ec1d5c21fa1141310a5f10e0ed47cf03 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 16 Sep 2026 11:57:59 +0200 Subject: [PATCH 093/101] wifi: mac80211: tests: fix memory leak The code allocates the bitrates twice, but really only should do that once. Remove the unconditional allocation even though it's the same in both branches of the switch, since the cleanup only handles those two branches explicitly. Link: https://patch.msgid.link/20260916115759.2cd8a91ed99d.I89c5068a3472e6296baf5a2d6832b733282d2b24@changeid Signed-off-by: Johannes Berg --- net/mac80211/tests/util.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/net/mac80211/tests/util.c b/net/mac80211/tests/util.c index 0b01264af577..89ff4f389de3 100644 --- a/net/mac80211/tests/util.c +++ b/net/mac80211/tests/util.c @@ -2,7 +2,7 @@ /* * Utilities for mac80211 unit testing * - * Copyright (C) 2024 Intel Corporation + * Copyright (C) 2024, 2026 Intel Corporation */ #include #include @@ -226,10 +226,6 @@ int t_sdata_init(struct kunit_resource *resource, void *ctx) sband = t_sdata->wiphy->bands[band]; sband->band = band; - sband->bitrates = - kmemdup(bitrates, sizeof(bitrates), GFP_KERNEL); - sband->n_bitrates = ARRAY_SIZE(bitrates); - /* Initialize channels, feel free to add more channels/bands */ switch (band) { case NL80211_BAND_2GHZ: From c5c853aa6dcb40a2a352982cdd8937c4aa6f2e26 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 16 Sep 2026 12:01:26 +0200 Subject: [PATCH 094/101] wifi: mac80211: WARN on 40 MHz HT/HE mismatch This happened to me and was tricky to debug, of course it shouldn't happen with correct drivers, WARN in this case. Link: https://patch.msgid.link/20260916120127.efa58d51d11f.Ie29dc83578893945231db3badfcb7b4a2e2775ba@changeid Signed-off-by: Johannes Berg --- net/mac80211/main.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/net/mac80211/main.c b/net/mac80211/main.c index a59837b9f480..ecd16374e4a1 100644 --- a/net/mac80211/main.c +++ b/net/mac80211/main.c @@ -1322,12 +1322,13 @@ int ieee80211_register_hw(struct ieee80211_hw *hw) IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G; /* currently no support for HE client where HT has 40 MHz but not HT */ - if (iftd->he_cap.has_he && - iftd->types_mask & (BIT(NL80211_IFTYPE_STATION) | - BIT(NL80211_IFTYPE_P2P_CLIENT)) && - sband->ht_cap.ht_supported && - sband->ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 && - !(iftd->he_cap.he_cap_elem.phy_cap_info[0] & he_40_mhz_cap)) + if (WARN_ON(iftd->he_cap.has_he && + iftd->types_mask & (BIT(NL80211_IFTYPE_STATION) | + BIT(NL80211_IFTYPE_P2P_CLIENT)) && + sband->ht_cap.ht_supported && + sband->ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40 && + !(iftd->he_cap.he_cap_elem.phy_cap_info[0] & + he_40_mhz_cap))) return -EINVAL; /* no support for per-band vendor elems with MLO */ From 8f076d9407f313ae641ef8e9ad241dade9006a91 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 16 Sep 2026 12:01:27 +0200 Subject: [PATCH 095/101] wifi: mac80211: allow advertising 20 MHz-only non-AP STA 802.11 Clause 27 (HE) already defined a 20 MHz-only non-AP HE STA in the 5 GHz band, where VHT is required, although VHT in Clause 21 requires 20, 40 and 80 MHz support. The VHT requirement is implemented in mac80211, but the later allowance for 20 MHz-only since HE wasn't. Allow a device to be a 20 MHz-only non-AP STA and require that it support VHT, but not that it has 80 MHz support, if it's HE as well. Link: https://patch.msgid.link/20260916120127.6e74579d46db.I628aed79ec7f9bf2f8e770b4866ed98cf1ddbe60@changeid Signed-off-by: Johannes Berg --- net/mac80211/mlme.c | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index 189dd3467f76..cf3873d8855b 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c @@ -6513,7 +6513,7 @@ ieee80211_determine_our_sta_mode(struct ieee80211_sub_if_data *sdata, struct ieee80211_sta_ht_cap sta_ht_cap = sband->ht_cap; bool is_5ghz = sband->band == NL80211_BAND_5GHZ; bool is_6ghz = sband->band == NL80211_BAND_6GHZ; - const struct ieee80211_sta_he_cap *he_cap; + const struct ieee80211_sta_he_cap *he_cap = NULL; const struct ieee80211_sta_eht_cap *eht_cap; const struct ieee80211_sta_uhr_cap *uhr_cap; struct ieee80211_sta_vht_cap vht_cap; @@ -6577,7 +6577,15 @@ ieee80211_determine_our_sta_mode(struct ieee80211_sub_if_data *sdata, goto out; } - if (vht_cap.vht_supported && is_5ghz) { + if (req && req->flags & ASSOC_REQ_DISABLE_HE && !is_6ghz) + mlme_link_id_dbg(sdata, link_id, + "HE disabled by flag, limiting to HT/VHT\n"); + else + he_cap = ieee80211_get_he_iftype_cap_vif(sband, &sdata->vif); + + if (vht_cap.vht_supported && is_5ghz && he_cap) { + /* nothing - since HE we can be 20 MHz-only non-AP STA */ + } else if (vht_cap.vht_supported && is_5ghz) { bool have_80mhz = false; unsigned int i; @@ -6623,17 +6631,11 @@ ieee80211_determine_our_sta_mode(struct ieee80211_sub_if_data *sdata, "no VHT 160 MHz capability on 5 GHz, limiting to 80 MHz"); } - if (req && req->flags & ASSOC_REQ_DISABLE_HE) { - mlme_link_id_dbg(sdata, link_id, - "HE disabled by flag, limiting to HT/VHT\n"); - goto out; - } - - he_cap = ieee80211_get_he_iftype_cap_vif(sband, &sdata->vif); if (!he_cap) { WARN_ON(is_6ghz); - mlme_link_id_dbg(sdata, link_id, - "no HE support, limiting to HT/VHT\n"); + if (!req || !(req->flags & ASSOC_REQ_DISABLE_HE)) + mlme_link_id_dbg(sdata, link_id, + "no HE support, limiting to HT/VHT\n"); goto out; } From 94fe252cd763c3e09399dea3fd28e555090599f5 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 16 Sep 2026 12:02:32 +0200 Subject: [PATCH 096/101] wifi: ieee80211: fix FTM bufferable check The FTM action frames don't have a protected dual, in fact their numbers are assigned otherwise. Remove the wrong check. Link: https://patch.msgid.link/20260916120232.d3c05305173e.I3855653e49311b0752b9fcdf9d5c6373408a14da@changeid Signed-off-by: Johannes Berg --- include/linux/ieee80211.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/linux/ieee80211.h b/include/linux/ieee80211.h index 26e674038865..7fbc51f873ff 100644 --- a/include/linux/ieee80211.h +++ b/include/linux/ieee80211.h @@ -2421,8 +2421,7 @@ static inline bool ieee80211_is_bufferable_mmpdu(struct sk_buff *skb) /* action frame - additionally check for non-bufferable FTM */ - if (mgmt->u.action.category != WLAN_CATEGORY_PUBLIC && - mgmt->u.action.category != WLAN_CATEGORY_PROTECTED_DUAL_OF_ACTION) + if (mgmt->u.action.category != WLAN_CATEGORY_PUBLIC) return true; if (mgmt->u.action.action_code == WLAN_PUB_ACTION_FTM_REQUEST || From ea35828287b5988dac9c9e1e33a4f692e794758c Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 16 Sep 2026 12:04:06 +0200 Subject: [PATCH 097/101] wifi: radiotap: correct EHT TB U-SIG 1 B20:25 The mask here needs to cover six bits, not just five, fix that. Link: https://patch.msgid.link/20260916120406.e4c89f440edb.I8d80bba29ff7e7d642ec603098fb7d30f2db5968@changeid Signed-off-by: Johannes Berg --- include/net/ieee80211_radiotap.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/net/ieee80211_radiotap.h b/include/net/ieee80211_radiotap.h index 27ee67b38ea8..ffdfbade25b8 100644 --- a/include/net/ieee80211_radiotap.h +++ b/include/net/ieee80211_radiotap.h @@ -596,7 +596,7 @@ enum ieee80211_radiotap_eht_usig_mu { enum ieee80211_radiotap_eht_usig_tb { /* TB-USIG-1 */ - IEEE80211_RADIOTAP_EHT_USIG1_TB_B20_B25_DISREGARD = 0x0000001f, + IEEE80211_RADIOTAP_EHT_USIG1_TB_B20_B25_DISREGARD = 0x0000003f, /* TB-USIG-2 */ IEEE80211_RADIOTAP_EHT_USIG2_TB_B0_B1_PPDU_TYPE = 0x000000c0, From 73cdc6018f4947c1c29fc274d78edff6223336d1 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 16 Sep 2026 12:06:20 +0200 Subject: [PATCH 098/101] wifi: nl80211: rename no-ACK bitmap to TID bitmap Rename the NL80211_ATTR_NOACK_MAP attribute to be called NL80211_ATTR_TID_BITMAP, allowing it to be reused in more contexts. The interpretation stays for the only command using it now (NL80211_CMD_SET_NOACK_MAP.) Signed-off-by: Johannes Berg Reviewed-by: Jeff Johnson Link: https://patch.msgid.link/20260916120620.823bfa05f5f5.Ibb334016129512921b0aa5236ee4c670d83d0f95@changeid Signed-off-by: Johannes Berg --- include/uapi/linux/nl80211.h | 11 ++++++----- net/wireless/nl80211.c | 6 +++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h index 510102febc26..9a2ccb8d66b8 100644 --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -937,8 +937,8 @@ * OLBC handling in hostapd. Beacons are reported in %NL80211_CMD_FRAME * messages. Note that per PHY only one application may register. * - * @NL80211_CMD_SET_NOACK_MAP: sets a bitmap for the individual TIDs whether - * No Acknowledgement Policy should be applied. + * @NL80211_CMD_SET_NOACK_MAP: sets a bitmap (%NL80211_ATTR_TID_BITMAP) + * indicating for which TIDs No Acknowledgment Policy should be applied. * * @NL80211_CMD_CH_SWITCH_NOTIFY: An AP or GO may decide to switch channels * independently of the userspace SME, send this event indicating @@ -2288,8 +2288,8 @@ enum nl80211_commands { * abides to when initiating radiation on DFS channels. A country maps * to one DFS region. * - * @NL80211_ATTR_NOACK_MAP: This u16 bitmap contains the No Ack Policy of - * up to 16 TIDs. + * @NL80211_ATTR_TID_BITMAP: A TID bitmap (u16) whose meaning depends + * on the command. * * @NL80211_ATTR_INACTIVITY_TIMEOUT: timeout value in seconds, this can be * used by the drivers which has MLME in firmware and does not have support @@ -3434,7 +3434,7 @@ enum nl80211_attrs { NL80211_ATTR_DISABLE_HT, NL80211_ATTR_HT_CAPABILITY_MASK, - NL80211_ATTR_NOACK_MAP, + NL80211_ATTR_TID_BITMAP, NL80211_ATTR_INACTIVITY_TIMEOUT, @@ -3807,6 +3807,7 @@ enum nl80211_attrs { #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 +#define NL80211_ATTR_NOACK_MAP NL80211_ATTR_TID_BITMAP /* * 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 e77a4c81d031..ab5a14f5fe28 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -877,7 +877,7 @@ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = { [NL80211_ATTR_HT_CAPABILITY_MASK] = { .len = NL80211_HT_CAPABILITY_LEN }, - [NL80211_ATTR_NOACK_MAP] = { .type = NLA_U16 }, + [NL80211_ATTR_TID_BITMAP] = { .type = NLA_U16 }, [NL80211_ATTR_INACTIVITY_TIMEOUT] = { .type = NLA_U16 }, [NL80211_ATTR_BG_SCAN_PERIOD] = { .type = NLA_U16 }, [NL80211_ATTR_WDEV] = { .type = NLA_U64 }, @@ -5282,13 +5282,13 @@ static int nl80211_set_noack_map(struct sk_buff *skb, struct genl_info *info) struct net_device *dev = info->user_ptr[1]; u16 noack_map; - if (!info->attrs[NL80211_ATTR_NOACK_MAP]) + if (!info->attrs[NL80211_ATTR_TID_BITMAP]) return -EINVAL; if (!rdev->ops->set_noack_map) return -EOPNOTSUPP; - noack_map = nla_get_u16(info->attrs[NL80211_ATTR_NOACK_MAP]); + noack_map = nla_get_u16(info->attrs[NL80211_ATTR_TID_BITMAP]); return rdev_set_noack_map(rdev, dev, noack_map); } From 0bfef14586c1d2fa4aff5a4e75a669e0f2bccfcb Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 16 Sep 2026 12:07:08 +0200 Subject: [PATCH 099/101] wifi: mac80211: fix multi-link UHR association The maximum connection mode is still set to EHT, and in particular in multi-link this matters, the second link and further links are otherwise limited to EHT. Fix that. Link: https://patch.msgid.link/20260916120708.8ddea0cea883.I72e7fc58d65aca620a05a07b759755e5896f068f@changeid Signed-off-by: Johannes Berg --- net/mac80211/util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/mac80211/util.c b/net/mac80211/util.c index a96078a6bfa2..e2b5b2b21c34 100644 --- a/net/mac80211/util.c +++ b/net/mac80211/util.c @@ -47,7 +47,7 @@ struct ieee80211_hw *wiphy_to_ieee80211_hw(struct wiphy *wiphy) EXPORT_SYMBOL(wiphy_to_ieee80211_hw); const struct ieee80211_conn_settings ieee80211_conn_settings_unlimited = { - .mode = IEEE80211_CONN_MODE_EHT, + .mode = IEEE80211_CONN_MODE_HIGHEST, .bw_limit = IEEE80211_CONN_BW_LIMIT_320, }; From e83a9223c97fecc75e6d019abf191eda96bbc0bc Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Wed, 16 Sep 2026 21:31:07 +0200 Subject: [PATCH 100/101] libertas: mesh: remove unused and undocumented sysfs attribute groups 7e79b38fe9a4 ("libertas: register sysfs groups properly") changed the code to use struct net_device member sysfs_groups array indices 1 and 2. However only one device-specific group is supported by net core, see netdev_register_kobject(). As a result groups boot_opts_group and mesh_ie_group are overwritten with net core internal groups. Referenced change was done 5 yrs ago, and apparently nobody has missed the sysfs attributes in the meantime. So I dare to remove them, especially as they aren't documented under Documentation/ABI. Signed-off-by: Heiner Kallweit Link: https://patch.msgid.link/6810ccfb-0453-4bdc-bb03-d51c3f3c6e18@kernel.org Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/libertas/mesh.c | 506 ------------------- 1 file changed, 506 deletions(-) diff --git a/drivers/net/wireless/marvell/libertas/mesh.c b/drivers/net/wireless/marvell/libertas/mesh.c index fced7485d015..686b2e18a0e9 100644 --- a/drivers/net/wireless/marvell/libertas/mesh.c +++ b/drivers/net/wireless/marvell/libertas/mesh.c @@ -65,19 +65,6 @@ static int __lbs_mesh_config_send(struct lbs_private *priv, return ret; } -static int lbs_mesh_config_send(struct lbs_private *priv, - struct cmd_ds_mesh_config *cmd, - uint16_t action, uint16_t type) -{ - int ret; - - if (!(priv->fwcapinfo & FW_CAPINFO_PERSISTENT_CONFIG)) - return -EOPNOTSUPP; - - ret = __lbs_mesh_config_send(priv, cmd, action, type); - return ret; -} - /* This function is the CMD_MESH_CONFIG legacy function. It only handles the * START and STOP actions. The extended actions supported by CMD_MESH_CONFIG * are all handled by preparing a struct cmd_ds_mesh_config and passing it to @@ -333,497 +320,6 @@ static const struct attribute_group lbs_mesh_attr_group = { .attrs = lbs_mesh_sysfs_entries, }; - -/*************************************************************************** - * Persistent configuration support - */ - -static int mesh_get_default_parameters(struct device *dev, - struct mrvl_mesh_defaults *defs) -{ - struct lbs_private *priv = to_net_dev(dev)->ml_priv; - struct cmd_ds_mesh_config cmd; - int ret; - - memset(&cmd, 0, sizeof(struct cmd_ds_mesh_config)); - ret = lbs_mesh_config_send(priv, &cmd, CMD_ACT_MESH_CONFIG_GET, - CMD_TYPE_MESH_GET_DEFAULTS); - - if (ret) - return -EOPNOTSUPP; - - memcpy(defs, &cmd.data[0], sizeof(struct mrvl_mesh_defaults)); - - return 0; -} - -/** - * bootflag_show - Get function for sysfs attribute bootflag - * @dev: the &struct device - * @attr: device attributes - * @buf: buffer where data will be returned - */ -static ssize_t bootflag_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct mrvl_mesh_defaults defs; - int ret; - - ret = mesh_get_default_parameters(dev, &defs); - - if (ret) - return ret; - - return sysfs_emit(buf, "%d\n", le32_to_cpu(defs.bootflag)); -} - -/** - * bootflag_store - Set function for sysfs attribute bootflag - * @dev: the &struct device - * @attr: device attributes - * @buf: buffer that contains new attribute value - * @count: size of buffer - */ -static ssize_t bootflag_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - struct lbs_private *priv = to_net_dev(dev)->ml_priv; - struct cmd_ds_mesh_config cmd; - uint32_t datum; - int ret; - - ret = kstrtouint(buf, 10, &datum); - if (ret) - return ret; - if (datum > 1) - return -EINVAL; - - memset(&cmd, 0, sizeof(cmd)); - *((__le32 *)&cmd.data[0]) = cpu_to_le32(!!datum); - cmd.length = cpu_to_le16(sizeof(uint32_t)); - ret = lbs_mesh_config_send(priv, &cmd, CMD_ACT_MESH_CONFIG_SET, - CMD_TYPE_MESH_SET_BOOTFLAG); - if (ret) - return ret; - - return strlen(buf); -} - -/** - * boottime_show - Get function for sysfs attribute boottime - * @dev: the &struct device - * @attr: device attributes - * @buf: buffer where data will be returned - */ -static ssize_t boottime_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct mrvl_mesh_defaults defs; - int ret; - - ret = mesh_get_default_parameters(dev, &defs); - - if (ret) - return ret; - - return sysfs_emit(buf, "%d\n", defs.boottime); -} - -/** - * boottime_store - Set function for sysfs attribute boottime - * @dev: the &struct device - * @attr: device attributes - * @buf: buffer that contains new attribute value - * @count: size of buffer - */ -static ssize_t boottime_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct lbs_private *priv = to_net_dev(dev)->ml_priv; - struct cmd_ds_mesh_config cmd; - uint32_t datum; - int ret; - - ret = kstrtouint(buf, 10, &datum); - if (ret) - return ret; - if (datum > 255) - return -EINVAL; - - memset(&cmd, 0, sizeof(cmd)); - - /* A too small boot time will result in the device booting into - * standalone (no-host) mode before the host can take control of it, - * so the change will be hard to revert. This may be a desired - * feature (e.g to configure a very fast boot time for devices that - * will not be attached to a host), but dangerous. So I'm enforcing a - * lower limit of 20 seconds: remove and recompile the driver if this - * does not work for you. - */ - datum = (datum < 20) ? 20 : datum; - cmd.data[0] = datum; - cmd.length = cpu_to_le16(sizeof(uint8_t)); - ret = lbs_mesh_config_send(priv, &cmd, CMD_ACT_MESH_CONFIG_SET, - CMD_TYPE_MESH_SET_BOOTTIME); - if (ret) - return ret; - - return strlen(buf); -} - -/** - * channel_show - Get function for sysfs attribute channel - * @dev: the &struct device - * @attr: device attributes - * @buf: buffer where data will be returned - */ -static ssize_t channel_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct mrvl_mesh_defaults defs; - int ret; - - ret = mesh_get_default_parameters(dev, &defs); - - if (ret) - return ret; - - return sysfs_emit(buf, "%d\n", le16_to_cpu(defs.channel)); -} - -/** - * channel_store - Set function for sysfs attribute channel - * @dev: the &struct device - * @attr: device attributes - * @buf: buffer that contains new attribute value - * @count: size of buffer - */ -static ssize_t channel_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - struct lbs_private *priv = to_net_dev(dev)->ml_priv; - struct cmd_ds_mesh_config cmd; - uint32_t datum; - int ret; - - ret = kstrtouint(buf, 10, &datum); - if (ret) - return ret; - if (datum < 1 || datum > 11) - return -EINVAL; - - memset(&cmd, 0, sizeof(cmd)); - *((__le16 *)&cmd.data[0]) = cpu_to_le16(datum); - cmd.length = cpu_to_le16(sizeof(uint16_t)); - ret = lbs_mesh_config_send(priv, &cmd, CMD_ACT_MESH_CONFIG_SET, - CMD_TYPE_MESH_SET_DEF_CHANNEL); - if (ret) - return ret; - - return strlen(buf); -} - -/** - * mesh_id_show - Get function for sysfs attribute mesh_id - * @dev: the &struct device - * @attr: device attributes - * @buf: buffer where data will be returned - */ -static ssize_t mesh_id_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct mrvl_mesh_defaults defs; - int ret; - - ret = mesh_get_default_parameters(dev, &defs); - - if (ret) - return ret; - - if (defs.meshie.val.mesh_id_len > IEEE80211_MAX_SSID_LEN) { - dev_err(dev, "inconsistent mesh ID length\n"); - defs.meshie.val.mesh_id_len = IEEE80211_MAX_SSID_LEN; - } - - memcpy(buf, defs.meshie.val.mesh_id, defs.meshie.val.mesh_id_len); - buf[defs.meshie.val.mesh_id_len] = '\n'; - buf[defs.meshie.val.mesh_id_len + 1] = '\0'; - - return defs.meshie.val.mesh_id_len + 1; -} - -/** - * mesh_id_store - Set function for sysfs attribute mesh_id - * @dev: the &struct device - * @attr: device attributes - * @buf: buffer that contains new attribute value - * @count: size of buffer - */ -static ssize_t mesh_id_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - struct cmd_ds_mesh_config cmd; - struct mrvl_mesh_defaults defs; - struct mrvl_meshie *ie; - struct lbs_private *priv = to_net_dev(dev)->ml_priv; - int len; - int ret; - - if (count < 2 || count > IEEE80211_MAX_SSID_LEN + 1) - return -EINVAL; - - memset(&cmd, 0, sizeof(struct cmd_ds_mesh_config)); - ie = (struct mrvl_meshie *) &cmd.data[0]; - - /* fetch all other Information Element parameters */ - ret = mesh_get_default_parameters(dev, &defs); - - cmd.length = cpu_to_le16(sizeof(struct mrvl_meshie)); - - /* transfer IE elements */ - memcpy(ie, &defs.meshie, sizeof(struct mrvl_meshie)); - - len = count - 1; - memcpy(ie->val.mesh_id, buf, len); - /* SSID len */ - ie->val.mesh_id_len = len; - /* IE len */ - ie->len = sizeof(struct mrvl_meshie_val) - IEEE80211_MAX_SSID_LEN + len; - - ret = lbs_mesh_config_send(priv, &cmd, CMD_ACT_MESH_CONFIG_SET, - CMD_TYPE_MESH_SET_MESH_IE); - if (ret) - return ret; - - return strlen(buf); -} - -/** - * protocol_id_show - Get function for sysfs attribute protocol_id - * @dev: the &struct device - * @attr: device attributes - * @buf: buffer where data will be returned - */ -static ssize_t protocol_id_show(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct mrvl_mesh_defaults defs; - int ret; - - ret = mesh_get_default_parameters(dev, &defs); - - if (ret) - return ret; - - return sysfs_emit(buf, "%d\n", defs.meshie.val.active_protocol_id); -} - -/** - * protocol_id_store - Set function for sysfs attribute protocol_id - * @dev: the &struct device - * @attr: device attributes - * @buf: buffer that contains new attribute value - * @count: size of buffer - */ -static ssize_t protocol_id_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct cmd_ds_mesh_config cmd; - struct mrvl_mesh_defaults defs; - struct mrvl_meshie *ie; - struct lbs_private *priv = to_net_dev(dev)->ml_priv; - uint32_t datum; - int ret; - - ret = kstrtouint(buf, 10, &datum); - if (ret) - return ret; - if (datum > 255) - return -EINVAL; - - memset(&cmd, 0, sizeof(cmd)); - - /* fetch all other Information Element parameters */ - ret = mesh_get_default_parameters(dev, &defs); - - cmd.length = cpu_to_le16(sizeof(struct mrvl_meshie)); - - /* transfer IE elements */ - ie = (struct mrvl_meshie *) &cmd.data[0]; - memcpy(ie, &defs.meshie, sizeof(struct mrvl_meshie)); - /* update protocol id */ - ie->val.active_protocol_id = datum; - - ret = lbs_mesh_config_send(priv, &cmd, CMD_ACT_MESH_CONFIG_SET, - CMD_TYPE_MESH_SET_MESH_IE); - if (ret) - return ret; - - return strlen(buf); -} - -/** - * metric_id_show - Get function for sysfs attribute metric_id - * @dev: the &struct device - * @attr: device attributes - * @buf: buffer where data will be returned - */ -static ssize_t metric_id_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct mrvl_mesh_defaults defs; - int ret; - - ret = mesh_get_default_parameters(dev, &defs); - - if (ret) - return ret; - - return sysfs_emit(buf, "%d\n", defs.meshie.val.active_metric_id); -} - -/** - * metric_id_store - Set function for sysfs attribute metric_id - * @dev: the &struct device - * @attr: device attributes - * @buf: buffer that contains new attribute value - * @count: size of buffer - */ -static ssize_t metric_id_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct cmd_ds_mesh_config cmd; - struct mrvl_mesh_defaults defs; - struct mrvl_meshie *ie; - struct lbs_private *priv = to_net_dev(dev)->ml_priv; - uint32_t datum; - int ret; - - memset(&cmd, 0, sizeof(cmd)); - ret = sscanf(buf, "%d", &datum); - if ((ret != 1) || (datum > 255)) - return -EINVAL; - - /* fetch all other Information Element parameters */ - ret = mesh_get_default_parameters(dev, &defs); - - cmd.length = cpu_to_le16(sizeof(struct mrvl_meshie)); - - /* transfer IE elements */ - ie = (struct mrvl_meshie *) &cmd.data[0]; - memcpy(ie, &defs.meshie, sizeof(struct mrvl_meshie)); - /* update metric id */ - ie->val.active_metric_id = datum; - - ret = lbs_mesh_config_send(priv, &cmd, CMD_ACT_MESH_CONFIG_SET, - CMD_TYPE_MESH_SET_MESH_IE); - if (ret) - return ret; - - return strlen(buf); -} - -/** - * capability_show - Get function for sysfs attribute capability - * @dev: the &struct device - * @attr: device attributes - * @buf: buffer where data will be returned - */ -static ssize_t capability_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct mrvl_mesh_defaults defs; - int ret; - - ret = mesh_get_default_parameters(dev, &defs); - - if (ret) - return ret; - - return sysfs_emit(buf, "%d\n", defs.meshie.val.mesh_capability); -} - -/** - * capability_store - Set function for sysfs attribute capability - * @dev: the &struct device - * @attr: device attributes - * @buf: buffer that contains new attribute value - * @count: size of buffer - */ -static ssize_t capability_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t count) -{ - struct cmd_ds_mesh_config cmd; - struct mrvl_mesh_defaults defs; - struct mrvl_meshie *ie; - struct lbs_private *priv = to_net_dev(dev)->ml_priv; - uint32_t datum; - int ret; - - memset(&cmd, 0, sizeof(cmd)); - ret = sscanf(buf, "%d", &datum); - if ((ret != 1) || (datum > 255)) - return -EINVAL; - - /* fetch all other Information Element parameters */ - ret = mesh_get_default_parameters(dev, &defs); - - cmd.length = cpu_to_le16(sizeof(struct mrvl_meshie)); - - /* transfer IE elements */ - ie = (struct mrvl_meshie *) &cmd.data[0]; - memcpy(ie, &defs.meshie, sizeof(struct mrvl_meshie)); - /* update value */ - ie->val.mesh_capability = datum; - - ret = lbs_mesh_config_send(priv, &cmd, CMD_ACT_MESH_CONFIG_SET, - CMD_TYPE_MESH_SET_MESH_IE); - if (ret) - return ret; - - return strlen(buf); -} - - -static DEVICE_ATTR_RW(bootflag); -static DEVICE_ATTR_RW(boottime); -static DEVICE_ATTR_RW(channel); -static DEVICE_ATTR_RW(mesh_id); -static DEVICE_ATTR_RW(protocol_id); -static DEVICE_ATTR_RW(metric_id); -static DEVICE_ATTR_RW(capability); - -static struct attribute *boot_opts_attrs[] = { - &dev_attr_bootflag.attr, - &dev_attr_boottime.attr, - &dev_attr_channel.attr, - NULL -}; - -static const struct attribute_group boot_opts_group = { - .name = "boot_options", - .attrs = boot_opts_attrs, -}; - -static struct attribute *mesh_ie_attrs[] = { - &dev_attr_mesh_id.attr, - &dev_attr_protocol_id.attr, - &dev_attr_metric_id.attr, - &dev_attr_capability.attr, - NULL -}; - -static const struct attribute_group mesh_ie_group = { - .name = "mesh_ie", - .attrs = mesh_ie_attrs, -}; - - /*************************************************************************** * Initializing and starting, stopping mesh */ @@ -1019,8 +515,6 @@ static int lbs_add_mesh(struct lbs_private *priv) mesh_dev->flags |= IFF_BROADCAST | IFF_MULTICAST; mesh_dev->sysfs_groups[0] = &lbs_mesh_attr_group; - mesh_dev->sysfs_groups[1] = &boot_opts_group; - mesh_dev->sysfs_groups[2] = &mesh_ie_group; /* Register virtual mesh interface */ ret = register_netdev(mesh_dev); From 6c0b7357e3c7f365ec51dd8d2b626130ee983ce1 Mon Sep 17 00:00:00 2001 From: Eduard Bostina Date: Thu, 17 Sep 2026 20:43:54 +0000 Subject: [PATCH 101/101] dt-bindings: net: wireless: Convert WL1251 to DT schema Convert the Texas Instruments WL1251 Wireless LAN Controller bindings to DT schema. During the conversion, the following updates were made: - Added the 'clocks' property. It was missing from the txt binding but is used in device trees (omap3-n900.dts). - Added 'spi-cpol' and 'spi-cpha'. These were present in the original txt binding's example but missing from its property list. - 'vio-supply' and 'spi-max-frequency' are no longer required. The txt binding listed them as required, but they are only applicable when the chip is connected via SPI. Signed-off-by: Eduard Bostina Reviewed-by: Krzysztof Kozlowski Link: https://patch.msgid.link/20260917204354.3017427-1-egbostina@gmail.com Signed-off-by: Johannes Berg --- .../bindings/net/wireless/ti,wl1251.txt | 64 -------------- .../bindings/net/wireless/ti,wl1251.yaml | 85 +++++++++++++++++++ 2 files changed, 85 insertions(+), 64 deletions(-) delete mode 100644 Documentation/devicetree/bindings/net/wireless/ti,wl1251.txt create mode 100644 Documentation/devicetree/bindings/net/wireless/ti,wl1251.yaml diff --git a/Documentation/devicetree/bindings/net/wireless/ti,wl1251.txt b/Documentation/devicetree/bindings/net/wireless/ti,wl1251.txt deleted file mode 100644 index 88fd28d15eac..000000000000 --- a/Documentation/devicetree/bindings/net/wireless/ti,wl1251.txt +++ /dev/null @@ -1,64 +0,0 @@ -* Texas Instruments wl1251 wireless lan controller - -The wl1251 chip can be connected via SPI or via SDIO. This -document describes the binding for the SPI connected chip. - -Required properties: -- compatible : Should be "ti,wl1251" -- reg : Chip select address of device -- spi-max-frequency : Maximum SPI clocking speed of device in Hz -- interrupts : Should contain interrupt line -- vio-supply : phandle to regulator providing VIO - -Optional properties: -- ti,wl1251-has-eeprom : boolean, the wl1251 has an eeprom connected, which - provides configuration data (calibration, MAC, ...) -- ti,power-gpio : GPIO connected to chip's PMEN pin if operated in - SPI mode -- Please consult Documentation/devicetree/bindings/spi/spi-bus.txt - for optional SPI connection related properties, - -Examples: - -&spi1 { - wl1251@0 { - compatible = "ti,wl1251"; - - reg = <0>; - spi-max-frequency = <48000000>; - spi-cpol; - spi-cpha; - - interrupt-parent = <&gpio2>; - interrupts = <10 IRQ_TYPE_NONE>; /* gpio line 42 */ - - vio-supply = <&vio>; - ti,power-gpio = <&gpio3 23 GPIO_ACTIVE_HIGH>; /* 87 */ - }; -}; - -&mmc3 { - vmmc-supply = <&wlan_en>; - - bus-width = <4>; - non-removable; - ti,non-removable; - cap-power-off-card; - - pinctrl-names = "default"; - pinctrl-0 = <&mmc3_pins>; - - #address-cells = <1>; - #size-cells = <0>; - - wlan: wifi@1 { - compatible = "ti,wl1251"; - - reg = <1>; - - interrupt-parent = <&gpio1>; - interrupts = <21 IRQ_TYPE_LEVEL_HIGH>; /* GPIO_21 */ - - ti,wl1251-has-eeprom; - }; -}; diff --git a/Documentation/devicetree/bindings/net/wireless/ti,wl1251.yaml b/Documentation/devicetree/bindings/net/wireless/ti,wl1251.yaml new file mode 100644 index 000000000000..6ce82c4a2c0b --- /dev/null +++ b/Documentation/devicetree/bindings/net/wireless/ti,wl1251.yaml @@ -0,0 +1,85 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/net/wireless/ti,wl1251.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments WL1251 Wireless LAN Controller + +maintainers: + - Eduard Bostina + +properties: + compatible: + const: ti,wl1251 + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + vio-supply: + description: + Phandle to the regulator providing VIO. Required when connected via SPI. + + ti,power-gpio: + maxItems: 1 + description: + GPIO connected to chip's PMEN pin if operated in SPI mode. + + ti,wl1251-has-eeprom: + type: boolean + description: + The wl1251 has an EEPROM connected, which provides configuration + data (calibration, MAC, ...). + + clocks: + maxItems: 1 + + spi-max-frequency: true + spi-cpol: true + spi-cpha: true + +required: + - compatible + - reg + - interrupts + +allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml# + +unevaluatedProperties: false + +examples: + - | + #include + #include + spi { + #address-cells = <1>; + #size-cells = <0>; + wifi@0 { + compatible = "ti,wl1251"; + reg = <0>; + spi-max-frequency = <48000000>; + spi-cpol; + spi-cpha; + interrupt-parent = <&gpio2>; + interrupts = <10 IRQ_TYPE_NONE>; + vio-supply = <&vio>; + ti,power-gpio = <&gpio3 23 GPIO_ACTIVE_HIGH>; + }; + }; + - | + #include + mmc { + #address-cells = <1>; + #size-cells = <0>; + wifi@1 { + compatible = "ti,wl1251"; + reg = <1>; + interrupt-parent = <&gpio1>; + interrupts = <21 IRQ_TYPE_LEVEL_HIGH>; + ti,wl1251-has-eeprom; + }; + };