From f5e0b20f6c6d548d985acdd620c5110776c915c2 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Tue, 18 Aug 2026 12:58:21 +0000 Subject: [PATCH 01/25] wifi: mt76: mt7996: program a link again if the driver holds it The firmware needs the driver to remove active links from the BSSINFO table when it destroys the interface. A link therefore stays registered from its first chanctx assignment until the interface goes away. If the driver holds the link already, a second add does nothing. mac80211 gives a non-AP MLD a new address each time it sets a link up. A link that comes back after a failed attempt thus carries an address that the hardware does not know, on a BSS that the driver never programs again. The station then authenticates from an address that nothing answers. The failure repeats until the driver tears the interface down for good. Program the address and the BSS again. This is the pair that the initial add sets up. For a station it does what mt7996_mcu_mld_link_oper() does for an AP. Fixes: 08813703ac41 ("wifi: mt76: mt7996: Destroy vif active links in mt7996_remove_interface()") Link: https://patch.msgid.link/20260818125825.395538-1-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/main.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index e218856b0c45..d17d1dbb2038 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -310,7 +310,14 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, if (vif->type == NL80211_IFTYPE_AP) return mt7996_mcu_mld_link_oper(dev, link_conf, link, true); - return 0; + + /* update the link address */ + ret = mt7996_mcu_add_dev_info(phy, vif, link_conf, mlink, true); + if (ret) + return ret; + + return mt7996_mcu_add_bss_info(phy, vif, link_conf, mlink, + msta_link, true); } mlink->idx = __ffs64(~dev->mt76.vif_mask); From 0ec14f8c8c5da1c9c314bce4f0bf53c009fbc998 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Tue, 18 Aug 2026 12:58:23 +0000 Subject: [PATCH 02/25] wifi: mt76: mt7996: take over connection monitoring mt76 runs its own beacon monitor. mt76_beacon_mon_check() counts a miss for each affiliated link of an MLD, and it stops the count while the phy is off channel. mt7996 supplies it through mt76_rx_beacon(), but it does not set CONNECTION_MONITOR. Therefore mac80211 also starts its legacy monitor. The legacy monitor knows one interface, not a set of links. It also counts while the radio serves a different link. After seven beacon intervals it removes an association that still carries traffic on the other links. Set the CONNECTION_MONITOR flag to disable the legacy monitor. Fixes: 4df75606ece5 ("wifi: mt76: add per-link beacon monitoring for MLO") Link: https://patch.msgid.link/20260818125825.395538-3-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/init.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index fb635a092584..2db63c3be9d1 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -540,6 +540,7 @@ mt7996_init_wiphy(struct ieee80211_hw *hw, struct mtk_wed_device *wed) ieee80211_hw_set(hw, NO_VIRTUAL_MONITOR); ieee80211_hw_set(hw, SUPPORTS_MULTI_BSSID); ieee80211_hw_set(hw, CHANCTX_STA_CSA); + ieee80211_hw_set(hw, CONNECTION_MONITOR); hw->max_tx_fragments = 4; wiphy->txq_memory_limit = 32 << 20; /* 32 MiB */ From 752770d7a58c821c17909c3ca67f9f14ab146a40 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Tue, 18 Aug 2026 12:58:25 +0000 Subject: [PATCH 03/25] wifi: mt76: account non-AQL frames per peer rather than per link The driver increments wcid->non_aql_packets when a frame goes to the hardware queue. The completion path decrements the counter again. For an MLO peer, the hardware reports the completion on the link that it used for the transmission. This link can differ from the link that the driver queued the frame on. In that case, the increment and the decrement go to different link wcids, and the count drifts upwards until it blocks the station. Keep the count on the wcid of the primary link, like the other peer-wide state. Fixes: 86c051f2c418 ("wifi: mt76: mt7925: enabling MLO when the firmware supports it") Link: https://patch.msgid.link/20260818125825.395538-5-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt76.h | 7 +++++++ drivers/net/wireless/mediatek/mt76/tx.c | 11 ++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index 62b41c8bb7c0..2ade848fe3bb 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -1426,6 +1426,13 @@ mtxq_to_txq(struct mt76_txq *mtxq) return container_of(ptr, struct ieee80211_txq, drv_priv); } +/* peer-wide state uses the wcid of the primary link */ +static inline struct mt76_wcid * +mt76_wcid_primary(struct mt76_wcid *wcid) +{ + return wcid->def_wcid ? wcid->def_wcid : wcid; +} + static inline struct ieee80211_sta * wcid_to_sta(struct mt76_wcid *wcid) { diff --git a/drivers/net/wireless/mediatek/mt76/tx.c b/drivers/net/wireless/mediatek/mt76/tx.c index 3707ee19e4ae..b1e894b19cbd 100644 --- a/drivers/net/wireless/mediatek/mt76/tx.c +++ b/drivers/net/wireless/mediatek/mt76/tx.c @@ -234,6 +234,8 @@ mt76_tx_check_non_aql(struct mt76_dev *dev, struct mt76_wcid *wcid, if (!wcid || info->tx_time_est) return; + wcid = mt76_wcid_primary(wcid); + pending = atomic_dec_return(&wcid->non_aql_packets); if (pending < 0) atomic_cmpxchg(&wcid->non_aql_packets, pending, 0); @@ -344,7 +346,8 @@ __mt76_tx_queue_skb(struct mt76_phy *phy, int qid, struct sk_buff *skb, if (!non_aql) return idx; - pending = atomic_inc_return(&wcid->non_aql_packets); + /* the hardware can report the completion on a different link */ + pending = atomic_inc_return(&mt76_wcid_primary(wcid)->non_aql_packets); if (stop && pending >= MT_MAX_NON_AQL_PKT) *stop = true; @@ -506,7 +509,8 @@ mt76_txq_send_burst(struct mt76_phy *phy, struct mt76_queue *q, return 0; } - if (atomic_read(&wcid->non_aql_packets) >= MT_MAX_NON_AQL_PKT) + if (atomic_read(&mt76_wcid_primary(wcid)->non_aql_packets) >= + MT_MAX_NON_AQL_PKT) return 0; skb = mt76_txq_dequeue(phy, mtxq); @@ -603,7 +607,8 @@ mt76_txq_schedule_list(struct mt76_phy *phy, enum mt76_txq_id qid) continue; } - if (atomic_read(&wcid->non_aql_packets) >= MT_MAX_NON_AQL_PKT) + if (atomic_read(&mt76_wcid_primary(wcid)->non_aql_packets) >= + MT_MAX_NON_AQL_PKT) continue; if (dev->queue_ops->tx_cleanup && q->queued + 2 * MT_TXQ_FREE_THR >= q->ndesc) { From 7c2e6b43c3082c2242ecfa5875b2b4b2cd06140f Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Tue, 1 Sep 2026 11:22:32 +0200 Subject: [PATCH 04/25] wifi: mt76: use ALTX queue for packets to disassociated stations Running these packets through regular buffering queues could cause them to hang, especially when the packets were for stations that in disassoc state. Fixes: dedf2ec30fe4 ("wifi: mt76: fix queue assignment for deauth packets") Acked-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260901092232.461379-1-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7915/main.c | 2 ++ drivers/net/wireless/mediatek/mt76/mt7996/main.c | 1 + drivers/net/wireless/mediatek/mt76/tx.c | 3 ++- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/main.c b/drivers/net/wireless/mediatek/mt76/mt7915/main.c index a8286f8becf9..a03d89aabe80 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/main.c @@ -805,6 +805,7 @@ __mt7915_drop_sta(void *ptr, u8 *mac, struct ieee80211_vif *vif) msta = (struct mt7915_sta *)sta->drv_priv; mt7915_mcu_add_sta(data->dev, vif, sta, CONN_STATE_DISCONNECT, false); + msta->wcid.tx_info &= ~MT_WCID_TX_INFO_SET; msta->wcid.sta_disabled = 1; msta->wcid.sta = 0; } @@ -871,6 +872,7 @@ int mt7915_mac_sta_event(struct mt76_dev *mdev, struct ieee80211_vif *vif, mutex_unlock(&dev->mt76.mutex); mt7915_mcu_add_sta(dev, vif, sta, CONN_STATE_DISCONNECT, false); + msta->wcid.tx_info &= ~MT_WCID_TX_INFO_SET; msta->wcid.sta_disabled = 1; msta->wcid.sta = 0; return 0; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index d17d1dbb2038..dd7a01c02a7c 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1433,6 +1433,7 @@ mt7996_mac_sta_event(struct mt7996_dev *dev, struct ieee80211_vif *vif, else if (sta->mlo && links == BIT(link_id)) /* last link */ mt7996_mcu_teardown_mld_sta(dev, link, msta_link); + msta_link->wcid.tx_info &= ~MT_WCID_TX_INFO_SET; msta_link->wcid.sta_disabled = 1; msta_link->wcid.sta = 0; links = links & ~BIT(link_id); diff --git a/drivers/net/wireless/mediatek/mt76/tx.c b/drivers/net/wireless/mediatek/mt76/tx.c index b1e894b19cbd..3c623df2e741 100644 --- a/drivers/net/wireless/mediatek/mt76/tx.c +++ b/drivers/net/wireless/mediatek/mt76/tx.c @@ -680,7 +680,8 @@ mt76_txq_schedule_pending_wcid(struct mt76_phy *phy, struct mt76_wcid *wcid, if ((dev->drv->drv_flags & MT_DRV_HW_MGMT_TXQ) && !(info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP) && !ieee80211_is_data_present(hdr->frame_control) && - (!ieee80211_is_bufferable_mmpdu(skb) || + (!(wcid->tx_info & MT_WCID_TX_INFO_SET) || + !ieee80211_is_bufferable_mmpdu(skb) || ieee80211_is_deauth(hdr->frame_control) || ieee80211_is_disassoc(hdr->frame_control) || head == &wcid->tx_offchannel)) From 8f9e5807671ee97cacf1957f4a09e43f4f1017a9 Mon Sep 17 00:00:00 2001 From: Aaron Ma Date: Wed, 26 Aug 2026 12:36:58 +0800 Subject: [PATCH 05/25] wifi: mt76: mt7925: restore the legacy BSS after MLO teardown Removing the last link of an MLD interface leaves the firmware with the MLD BSS and DEV entry: the removal loop skips the deflink, and the hweight16(mvif->valid_links) fallback reads the stale pre-update valid_links, which is still non-zero. The entry keeps the per-link MAC address, so every subsequent legacy association fails authentication until the interface is removed or the module is reloaded. Remove the deflink BSS and add it back as a legacy BSS on the transition to zero links. Fixes: 69acd6d910b0 ("wifi: mt76: mt7925: add mt7925_change_vif_links") Signed-off-by: Aaron Ma Link: https://patch.msgid.link/20260826043658.2255553-1-aaron.ma@canonical.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/main.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index 5993b31e1aae..549f9c06b363 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -2294,6 +2294,18 @@ mt7925_change_vif_links(struct ieee80211_hw *hw, struct ieee80211_vif *vif, mvif->valid_links = new_links; + /* Restore the legacy BSS after disabling MLO */ + if (old_links && !new_links) { + mt792x_mac_link_bss_remove(dev, &mvif->bss_conf, + &mvif->sta.deflink); + err = mt7925_mac_link_bss_add(dev, &vif->bss_conf, + &mvif->sta.deflink); + if (err < 0) { + mt792x_mutex_release(dev); + return err; + } + } + mt792x_mutex_release(dev); return 0; From 9c9e4e4c8b45873e53774ab8207825b37b7565ec Mon Sep 17 00:00:00 2001 From: Tao Gong Date: Sun, 23 Aug 2026 05:37:39 +0000 Subject: [PATCH 06/25] wifi: mt76: mt7996: fix struct mt7996_mcu_wed_rro_ba_delete_event layout Correct the definition of struct mt7996_mcu_wed_rro_ba_delete_event to match the 12-byte event record layout observed from firmware. The event TLV sent by firmware is 12 bytes in length rather than 8 bytes. It contains an additional 4 bytes that appear to be wlan_id and tid. For example, a captured batch deletion of sessions 8, 9, and 10 (TIDs 6, 0, and 1) shows: 00000000: 02 00 0c 00 08 00 01 00 06 00 00 00 02 00 0c 00 00000010: 09 00 01 00 00 00 00 00 02 00 0c 00 0a 00 01 00 00000020: 01 00 00 00 The previous 8-byte structure corrupts the parsing of subsequent TLVs, leading to unhandled delete events and erroneous session deletions. Consequently, clients that create and tear down BA sessions in batches (such as Apple Wi-Fi clients) fail to properly clean up stale sessions, causing HW_RRO to be lost upon subsequent reassociations. Fixes: a5d028d66836 ("wifi: mt76: mt7996: add wed rro delete session garbage collector") Signed-off-by: Tao Gong Link: https://patch.msgid.link/20260823053739.186748-1-gongtao0607@gmail.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mcu.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h index 74b70fb6da3d..1b272e904100 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h @@ -284,6 +284,9 @@ struct mt7996_mcu_wed_rro_ba_delete_event { __le16 len; __le16 session_id; + __le16 wlan_id; + u8 tid; + u8 __rsv1; u8 __rsv2[2]; } __packed; From 916c484d29d82f45c281da2786fcd6a1284430ce Mon Sep 17 00:00:00 2001 From: Nicolas Cavallari Date: Mon, 10 Aug 2026 17:45:10 +0200 Subject: [PATCH 07/25] wifi: mt76: mt7915: disable rx napi when removing device Unloading the mt7915e module with a MT7916 triggers multiples WARN in __netif_napi_del_locked() and in page_pool_disable_direct_recycling() because the driver does not disable the napi before destroying it. This is troublesome since on MT7916 it is required to unload the module and reinsert it with a different enable_6ghz parameter to change the frequency. The system generally becomes unstable after reinserting the module. Fix it by disabling rx napi inside mt7915e itself, even through napi rx is enabled in the mt76 generic code. Disabling it in the mt76 generic code was previously attempted and reverted because napi was disabled too late and rx handlers could access freed structures. Note that the tx side is enabled and disabled by the mt76 generic code. It is disabled in mt7915_stop_hardware() since, unlike mt7915_unregister_device(), is it also called in the probe path if an error happens after rx napi is enabled. Fixes: e57b7901469f ("mt76: add mac80211 driver for MT7915 PCIe-based chipsets") Signed-off-by: Nicolas Cavallari Link: https://patch.msgid.link/20260810154923.32546-1-nicolas.cavallari@green-communications.fr Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7915/init.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/init.c b/drivers/net/wireless/mediatek/mt76/mt7915/init.c index ca46a203aa48..c8e389db63a7 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/init.c @@ -1238,6 +1238,12 @@ static void mt7915_unregister_ext_phy(struct mt7915_dev *dev) static void mt7915_stop_hardware(struct mt7915_dev *dev) { + int i; + + mt76_for_each_q_rx(&dev->mt76, i) { + if (!mt76_queue_is_wed_rro(&dev->mt76.q_rx[i])) + napi_disable(&dev->mt76.napi[i]); + } mt7915_mcu_exit(dev); mt76_connac2_tx_token_put(&dev->mt76); mt7915_dma_cleanup(dev); From 5796b2bf9ff718342773ac760d86eb126c112a4f Mon Sep 17 00:00:00 2001 From: Devin Wittmayer Date: Thu, 10 Sep 2026 19:44:28 -0700 Subject: [PATCH 08/25] wifi: mt76: mt7925: don't put the band auto marker in TGID band_idx starts out as 0xff, which tells the firmware to pick the band on its own. A remain-on-channel grant normally writes a real index over it before anything else looks. Two cases never see that: an MLD station throws the grant away, and an AP has no association to trigger one. The TX descriptor reads the same field into a two-bit slot, so the marker comes out as 3. Every data frame from those two goes out tagged band 3. Beacons are fine, they reach the firmware separately and never build a descriptor. Same numbers on an MT7925 and on a Netgear A9000: stock patched station, one link 0x00000001 0x00000001 station, two links 0x00003001 0x00000001 AP 0x00003013 0x00000013 real band index, STA 0x00001001 0x00001001 real band index, AP 0x00001013 0x00001013 Only the marker cases move. Fixes: 0cb349d742d1 ("wifi: mt76: mt7925: update mt7925_mac_link_bss_add for MLO") Signed-off-by: Devin Wittmayer Link: https://patch.msgid.link/20260911024428.65244-1-lucid_duck@justthetip.ca Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/mac.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c index 101f571b027f..3b82c9de26c5 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c @@ -798,7 +798,8 @@ mt7925_mac_write_txwi(struct mt76_dev *dev, __le32 *txwi, val = FIELD_PREP(MT_TXD1_WLAN_IDX, wcid->idx) | FIELD_PREP(MT_TXD1_OWN_MAC, omac_idx); - if (band_idx) + /* 0xff means BAND_AUTO to the firmware, and TGID cannot encode it */ + if (band_idx && band_idx != 0xff) val |= FIELD_PREP(MT_TXD1_TGID, band_idx); txwi[1] = cpu_to_le32(val); From 456de496b53ae73e6e3020fec815dbdc88aa7c07 Mon Sep 17 00:00:00 2001 From: Cristian Papa Date: Thu, 3 Sep 2026 22:07:02 -0300 Subject: [PATCH 09/25] wifi: mt76: mt7603: initialize global station WCID mt7603 uses a driver-private global station WCID for frames that have neither a station nor a vif, including frames injected through a monitor interface. Unlike the core global WCID initialized by mt76_register_device(), this WCID is published without ever being passed to mt76_wcid_init(). Since commit 0335c034e726 ("wifi: mt76: fix race condition related to checking tx queue fill status"), mt76_tx() queues every such frame on the WCID's tx_pending or tx_offchannel queue and links its tx_list. The first injected frame therefore reaches __skb_queue_tail() with an uninitialized sk_buff_head and dereferences a NULL prev pointer. Initialize the WCID before publishing it. The fix was tested on an MT7603 PCIe device. Three wildcard probe requests were injected through a monitor interface. The hardware queue head and tail advanced from 0 to 3 and a second radio captured the third request over the air. No oops or reset occurred. After restoring AP mode, a 150 MB transfer also completed without a reset. Fixes: 0335c034e726 ("wifi: mt76: fix race condition related to checking tx queue fill status") Cc: stable@vger.kernel.org Signed-off-by: Cristian Papa Link: https://patch.msgid.link/20260904010702.16306-1-pcristian292@gmail.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7603/init.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7603/init.c b/drivers/net/wireless/mediatek/mt76/mt7603/init.c index 10f2ec70c792..218ec406d2a8 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7603/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7603/init.c @@ -230,6 +230,7 @@ mt7603_mac_init(struct mt7603_dev *dev) eth_broadcast_addr(bc_addr); mt7603_wtbl_init(dev, MT7603_WTBL_RESERVED, -1, bc_addr); + mt76_wcid_init(&dev->global_sta.wcid, 0); dev->global_sta.wcid.idx = MT7603_WTBL_RESERVED; rcu_assign_pointer(dev->mt76.wcid[MT7603_WTBL_RESERVED], &dev->global_sta.wcid); From 45f53d98cbaa078907b6a300ff3974dfb5ae8776 Mon Sep 17 00:00:00 2001 From: Junjie Cao Date: Thu, 10 Sep 2026 09:23:00 +0800 Subject: [PATCH 10/25] wifi: mt76: mt792x: treat 0xff ACPI SAR entries as no limit Some ASUS firmware carries 0xff in MTGS power slots. The driver narrows the byte to s8, so the slot reads as -1 in its 0.5 dBm units, wins the min() against the regulatory limit and caps every rate the driver programs. On an ASUS Zenbook 14 UM3406HA (MT7922) all six MTGS tables hold 0xff in every power byte, and txpower_sku shows tmac 3 on every supported OFDM/HT/VHT/HE rate against EEPROM entries of 25-39, under both the FR and the world regulatory domain. Huy Nguyen Dinh Quang saw the same clamp on a Vivobook S 14 M5406WA, with 5 GHz TX at ~40 Mbps, and proposed skipping the geo entry. Skip a geo entry whose power byte is 0xff. The MTDS entry is narrowed the same way on its way into the geo lookup, so give it the same treatment, and clamp the dynamic value plus offset at 127 so a no-limit entry cannot wrap. Fixes: f965333e491e ("mt76: mt7921: introduce ACPI SAR support") Link: https://bugzilla.redhat.com/show_bug.cgi?id=2515420 Reported-by: Huy Nguyen Dinh Quang Closes: https://lore.kernel.org/linux-wireless/20260827134820.2607530-1-huynguyendinhquang@gmail.com/ Signed-off-by: Junjie Cao Tested-by: Cristian Fleischer Link: https://patch.msgid.link/acb947435fc78d5c06039c7bd5609c735468b0ee.1789002236.git.junjie.cao@intel.com Signed-off-by: Felix Fietkau --- .../wireless/mediatek/mt76/mt792x_acpi_sar.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c index b468051fbe68..0ffb277f9909 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c @@ -279,10 +279,18 @@ mt792x_asar_get_geo_pwr(struct mt792x_phy *phy, if (idx >= max) return dyn_power; - geo_power = (band_pwr + idx)->pwr; - dyn_power += (band_pwr + idx)->offset; + band_pwr += idx; - return min(geo_power, dyn_power); + /* Some OEM tables carry 0xff here; narrowed to s8 that is -1 and + * wins the min() below. Take it as no limit. + */ + if (band_pwr->pwr == 0xff) + return dyn_power; + + geo_power = band_pwr->pwr; + + return min_t(int, geo_power, + min_t(int, dyn_power + band_pwr->offset, 127)); } static s8 @@ -315,7 +323,9 @@ mt792x_asar_range_pwr(struct mt792x_phy *phy, else band = NL80211_BAND_2GHZ; - return mt792x_asar_get_geo_pwr(phy, band, limit[idx]); + /* 0xff: no dynamic limit, see mt792x_asar_get_geo_pwr() */ + return mt792x_asar_get_geo_pwr(phy, band, + limit[idx] == 0xff ? 127 : limit[idx]); } int mt792x_init_acpi_sar_power(struct mt792x_phy *phy, bool set_default) From af97bf5a7e72da9026de184676df8e2fa57e1058 Mon Sep 17 00:00:00 2001 From: Devin Wittmayer Date: Fri, 4 Sep 2026 18:35:51 -0700 Subject: [PATCH 11/25] wifi: mt76: mt792x: pick the SAR table layout from the table itself The SAR power tables are parsed with a version byte from a different ACPI table, never their own. Some firmware declares version 1 and ships version 2 tables. The entries then start a byte early, and the 6 GHz limits the table carries are dropped, because the older layout has no room for them. ASUS GA503RM, MTCL declares v1: MTGS 28 bytes v1: (28-6) % 5 = 2, no fit v2: 3 tables MTDS 19 bytes v1: (19-6) % 6 = 1, no fit v2: 1 table The layouts accept disjoint lengths, so take whichever one fits and keep it per table. Reported-by: Sagun Kayastha Closes: https://github.com/morrownr/mt76/issues/62 Fixes: f965333e491e ("mt76: mt7921: introduce ACPI SAR support") Signed-off-by: Devin Wittmayer Link: https://patch.msgid.link/20260905013551.24635-1-lucid_duck@justthetip.ca Signed-off-by: Felix Fietkau --- .../wireless/mediatek/mt76/mt792x_acpi_sar.c | 116 +++++++++++------- .../wireless/mediatek/mt76/mt792x_acpi_sar.h | 2 + 2 files changed, 76 insertions(+), 42 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c index 0ffb277f9909..e87dfb0f1b3a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.c @@ -108,69 +108,99 @@ mt792x_asar_acpi_read_mtcl(struct mt792x_dev *dev, u8 **table, u8 *version) return ret; } +/* A table's layout is decided by the version byte in MTCL, but some firmware + * labels a version 2 table as version 1. Accept whichever layout the table + * actually has: for every accepted entry count the two do not overlap, since + * one is 6 + 5n bytes and the other 7 + 7n. + */ +static bool +mt792x_asar_fits(u8 *table, int len, int prelen, int sarlen, int nr_off, + int min, int max) +{ + int tblcnt; + + if (len < prelen || (len - prelen) % sarlen) + return false; + + tblcnt = (len - prelen) / sarlen; + if (tblcnt < min || tblcnt > max) + return false; + + return tblcnt == table[nr_off]; +} + /* MTDS : Dynamic SAR Power Table */ static int -mt792x_asar_acpi_read_mtds(struct mt792x_dev *dev, u8 **table, u8 version) +mt792x_asar_acpi_read_mtds(struct mt792x_dev *dev, u8 **table, u8 version, + u8 *used) { - int len, ret, sarlen, prelen, tblcnt; - bool enable; + int len, ret; + u8 *t; ret = mt792x_acpi_read(dev, MT792x_ACPI_MTDS, table, &len); if (ret) return ret; - /* Table content validation */ - switch (version) { - case 1: - enable = ((struct mt792x_asar_dyn *)*table)->enable; - sarlen = sizeof(struct mt792x_asar_dyn_limit); - prelen = sizeof(struct mt792x_asar_dyn); - break; - case 2: - enable = ((struct mt792x_asar_dyn_v2 *)*table)->enable; - sarlen = sizeof(struct mt792x_asar_dyn_limit_v2); - prelen = sizeof(struct mt792x_asar_dyn_v2); - break; - default: - return -EINVAL; - } + t = *table; - tblcnt = (len - prelen) / sarlen; - if (!enable || - tblcnt > MT792x_ASAR_MAX_DYN || tblcnt < MT792x_ASAR_MIN_DYN) + /* Table content validation */ + if (mt792x_asar_fits(t, len, sizeof(struct mt792x_asar_dyn), + sizeof(struct mt792x_asar_dyn_limit), + offsetof(struct mt792x_asar_dyn, nr_tbl), + MT792x_ASAR_MIN_DYN, MT792x_ASAR_MAX_DYN)) + *used = 1; + else if (mt792x_asar_fits(t, len, sizeof(struct mt792x_asar_dyn_v2), + sizeof(struct mt792x_asar_dyn_limit_v2), + offsetof(struct mt792x_asar_dyn_v2, nr_tbl), + MT792x_ASAR_MIN_DYN, MT792x_ASAR_MAX_DYN)) + *used = 2; + else return -EINVAL; + if (!t[offsetof(struct mt792x_asar_dyn, enable)]) + return -EINVAL; + + if (version && *used != version) + dev_info(dev->mt76.dev, + "MTDS is v%u, MTCL says v%u; using v%u\n", + *used, version, *used); + return 0; } /* MTGS : Geo SAR Power Table */ static int -mt792x_asar_acpi_read_mtgs(struct mt792x_dev *dev, u8 **table, u8 version) +mt792x_asar_acpi_read_mtgs(struct mt792x_dev *dev, u8 **table, u8 version, + u8 *used) { - int len, ret, sarlen, prelen, tblcnt; + int len, ret; + u8 *t; ret = mt792x_acpi_read(dev, MT792x_ACPI_MTGS, table, &len); if (ret) return ret; - /* Table content validation */ - switch (version) { - case 1: - sarlen = sizeof(struct mt792x_asar_geo_limit); - prelen = sizeof(struct mt792x_asar_geo); - break; - case 2: - sarlen = sizeof(struct mt792x_asar_geo_limit_v2); - prelen = sizeof(struct mt792x_asar_geo_v2); - break; - default: - return -EINVAL; - } + t = *table; - tblcnt = (len - prelen) / sarlen; - if (tblcnt > MT792x_ASAR_MAX_GEO || tblcnt < MT792x_ASAR_MIN_GEO) + /* Table content validation */ + if (mt792x_asar_fits(t, len, sizeof(struct mt792x_asar_geo), + sizeof(struct mt792x_asar_geo_limit), + offsetof(struct mt792x_asar_geo, nr_tbl), + MT792x_ASAR_MIN_GEO, MT792x_ASAR_MAX_GEO)) + *used = 1; + else if (mt792x_asar_fits(t, len, sizeof(struct mt792x_asar_geo_v2), + sizeof(struct mt792x_asar_geo_limit_v2), + offsetof(struct mt792x_asar_geo_v2, nr_tbl), + MT792x_ASAR_MIN_GEO, MT792x_ASAR_MAX_GEO)) + *used = 2; + else return -EINVAL; + if (version && *used != version) + dev_info(dev->mt76.dev, + "MTGS is v%u, MTCL says v%u; using v%u\n", + *used, version, *used); + return 0; } @@ -205,14 +235,16 @@ int mt792x_init_acpi_sar(struct mt792x_dev *dev) asar->countrylist = NULL; } - ret = mt792x_asar_acpi_read_mtds(dev, (u8 **)&asar->dyn, asar->ver); + ret = mt792x_asar_acpi_read_mtds(dev, (u8 **)&asar->dyn, asar->ver, + &asar->dyn_ver); if (ret) { devm_kfree(dev->mt76.dev, asar->dyn); asar->dyn = NULL; } /* MTGS is optional */ - ret = mt792x_asar_acpi_read_mtgs(dev, (u8 **)&asar->geo, asar->ver); + ret = mt792x_asar_acpi_read_mtgs(dev, (u8 **)&asar->geo, asar->ver, + &asar->geo_ver); if (ret) { devm_kfree(dev->mt76.dev, asar->geo); asar->geo = NULL; @@ -254,7 +286,7 @@ mt792x_asar_get_geo_pwr(struct mt792x_phy *phy, break; } - if (asar->ver == 1) { + if (asar->geo_ver == 1) { band_pwr = &asar->geo->tbl[idx].band[0]; max = ARRAY_SIZE(asar->geo->tbl[idx].band); } else { @@ -305,7 +337,7 @@ mt792x_asar_range_pwr(struct mt792x_phy *phy, if (!capa) return 127; - if (asar->ver == 1) { + if (asar->dyn_ver == 1) { limit = &asar->dyn->tbl[0].frp[0]; max = ARRAY_SIZE(asar->dyn->tbl[0].frp); } else { diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.h b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.h index 474033073831..1d42f24b05f6 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.h +++ b/drivers/net/wireless/mediatek/mt76/mt792x_acpi_sar.h @@ -105,6 +105,8 @@ struct mt792x_asar_fg { struct mt792x_acpi_sar { u8 ver; + u8 dyn_ver; + u8 geo_ver; union { struct mt792x_asar_dyn *dyn; struct mt792x_asar_dyn_v2 *dyn_v2; From 52f6a065e0810c739a5dabd33a58047f08100f50 Mon Sep 17 00:00:00 2001 From: Lu Huang Date: Wed, 30 Sep 2026 16:54:35 +0800 Subject: [PATCH 12/25] wifi: mt76: mt7996: fix uninitialized buf read in mt7996_variant_fem_init() When the eFuse block covering MT7976C_EFUSE_OFFSET is invalid, mt7996_mcu_get_eeprom() returns -EINVAL without writing buf, but mt7996_variant_fem_init() tolerates -EINVAL and keeps running, so buf[idx] == 0xc reads uninitialized stack memory. A stale 0x0c byte misclassifies a 0x7976 aDie as 7976C, selects MT7996_FEM_INT and the wrong default EEPROM, overriding valid calibration data. Only consult the eFuse byte when the read succeeded; with an invalid eFuse, fall back to the three explicit aDie version checks. Fixes: e8cb33ad546a ("wifi: mt76: mt7996: add support for more variants") Cc: stable@vger.kernel.org Signed-off-by: Lu Huang Link: https://patch.msgid.link/20260930085435.1424493-1-huanglu@kylinos.cn Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/init.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index 2db63c3be9d1..c1d860029801 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -1248,7 +1248,7 @@ static int mt7996_variant_fem_init(struct mt7996_dev *dev) adie_ver = u32_get_bits(regval, MT_ADIE_VERSION_MASK); idx = MT7976C_EFUSE_OFFSET % MT7996_EEPROM_BLOCK_SIZE; is_7976c = adie_ver == 0x8a10 || adie_ver == 0x8b00 || - adie_ver == 0x8c10 || buf[idx] == 0xc; + adie_ver == 0x8c10 || (!ret && buf[idx] == 0xc); adie_id = u32_get_bits(regval, MT_ADIE_CHIP_ID_MASK); if (adie_id == 0x7975 || adie_id == 0x7979 || From ff9b465ddb95d7842282998348df88ff55f62c4a Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Fri, 7 Aug 2026 21:25:53 +0800 Subject: [PATCH 13/25] wifi: nxpwifi: protect sta_list against concurrent add and delete nxpwifi_del_sta_entry() unlinks an entry from priv->sta_list without holding priv->sta_list_spinlock. The comment above it gives the reason: the function is only reached from cfg80211_ops.del_station, which runs under the wiphy mutex. That mutex serialises this callback against other users of the same lock, including wiphy_work items. It covers neither of the two other writers of the same list: nxpwifi_add_sta_entry() from nxpwifi_uap_event_sta_assoc() nxpwifi_del_all_sta_list() from nxpwifi_uap_event_bss_idle() Both run from nxpwifi_process_event() on the driver's main work queue, a plain workqueue that never takes the wiphy lock, and both take sta_list_spinlock around their update. A station associating, or the BSS going idle, can therefore modify priv->sta_list while a hostapd del_station is walking it and unlinking an entry. Take the lock here too. As the traversal then runs under the write lock, list_for_each_entry() replaces the _rcu variant, matching nxpwifi_del_all_sta_list(). The same call path reaches nxpwifi_get_sta_entry() without rcu_read_lock(). Entries are freed with kfree_rcu() and every other caller holds it, so take it around the lookup as well. Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x") Signed-off-by: Linmao Li Tested-by: Jeff Chen Reviewed-by: Jeff Chen Signed-off-by: Jeff Chen --- drivers/net/wireless/nxp/nxpwifi/cfg80211.c | 2 ++ drivers/net/wireless/nxp/nxpwifi/util.c | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c index 86304ea3331c..161697e038c0 100644 --- a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c +++ b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c @@ -1539,9 +1539,11 @@ nxpwifi_cfg80211_del_station(struct wiphy *wiphy, struct wireless_dev *wdev, eth_zero_addr(deauth_mac); + rcu_read_lock(); sta_node = nxpwifi_get_sta_entry(priv, params->mac); if (sta_node) ether_addr_copy(deauth_mac, params->mac); + rcu_read_unlock(); if (is_valid_ether_addr(deauth_mac)) { ret = nxpwifi_uap_sta_deauth(priv, deauth_mac); diff --git a/drivers/net/wireless/nxp/nxpwifi/util.c b/drivers/net/wireless/nxp/nxpwifi/util.c index bbfefb81d8d3..ae8444a14105 100644 --- a/drivers/net/wireless/nxp/nxpwifi/util.c +++ b/drivers/net/wireless/nxp/nxpwifi/util.c @@ -1041,19 +1041,23 @@ nxpwifi_set_sta_ht_cap(struct nxpwifi_private *priv, const u8 *ies, } } -/* Delete a station from list; called under cfg80211 mutex. */ +/* Delete a station from list. */ void nxpwifi_del_sta_entry(struct nxpwifi_private *priv, const u8 *mac) { struct nxpwifi_sta_node *node; - list_for_each_entry_rcu(node, &priv->sta_list, list) { + spin_lock_bh(&priv->sta_list_spinlock); + + list_for_each_entry(node, &priv->sta_list, list) { if (!memcmp(node->mac_addr, mac, ETH_ALEN)) { list_del_rcu(&node->list); kfree_rcu(node, rcu); break; } } + + spin_unlock_bh(&priv->sta_list_spinlock); } /* Delete all stations from list. */ From 128411832c62ca517e4abd938229f0ee40823fdb Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Fri, 7 Aug 2026 21:25:54 +0800 Subject: [PATCH 14/25] wifi: nxpwifi: delete the station entry on the uAP deauth event When a station leaves, the firmware reports EVENT_UAP_STA_DEAUTH. nxpwifi_uap_event_sta_deauth() tears down the reorder, BA and WMM state for that peer but leaves its nxpwifi_sta_node on priv->sta_list. Nothing else removes it. cfg80211_del_sta() only reports the departure upwards, it does not call back into cfg80211_ops.del_station, and that callback is the only caller of nxpwifi_del_sta_entry(). The entry therefore survives until the BSS goes idle and nxpwifi_del_all_sta_list() drops everything. Until then the departed station still looks associated to the driver. nxpwifi_handle_uap_rx_forward() finds the stale node for that address, treats the frame as intra-BSS traffic and requeues it for transmission instead of passing it up the stack, so traffic for that address is misrouted. The list also keeps one entry per station that has ever associated, and a station that comes back reuses the stale node along with its old statistics. mwifiex, which nxpwifi was derived from, deletes the entry in the same event handler. Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x") Signed-off-by: Linmao Li Tested-by: Jeff Chen Reviewed-by: Jeff Chen Signed-off-by: Jeff Chen --- drivers/net/wireless/nxp/nxpwifi/uap_event.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/nxp/nxpwifi/uap_event.c b/drivers/net/wireless/nxp/nxpwifi/uap_event.c index ab5c15686f54..20b7b53db236 100644 --- a/drivers/net/wireless/nxp/nxpwifi/uap_event.c +++ b/drivers/net/wireless/nxp/nxpwifi/uap_event.c @@ -75,6 +75,7 @@ nxpwifi_uap_event_sta_deauth(struct nxpwifi_private *priv) nxpwifi_del_tx_ba_stream_tbl_by_ra(priv, deauth_mac); } nxpwifi_wmm_del_peer_ra_list(priv, deauth_mac); + nxpwifi_del_sta_entry(priv, deauth_mac); return 0; } From 5c9260d7c6990875fc788ebb86ea2bf08b492704 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Thu, 13 Aug 2026 16:23:13 +0800 Subject: [PATCH 15/25] wifi: nxpwifi: wait for the wakeup timer before the adapter is freed nxpwifi_adapter_cleanup() stops adapter->wakeup_timer with timer_delete(), which does not wait for a running callback. The callback goes on using the adapter: wakeup_timer_fn() sets adapter->hw_status, walks the command queues through nxpwifi_cancel_all_pending_cmd() and calls adapter->if_ops.card_reset(). Both paths that reach nxpwifi_adapter_cleanup() free the adapter right afterwards, through nxpwifi_free_adapter() in nxpwifi_remove_card() and in the nxpwifi_add_card() error unwind. Use timer_delete_sync() so the callback has finished before the adapter is released. mwifiex fixed the same issue in commit ae5e95d41574 ("wifi: mwifiex: fix use-after-free in mwifiex_adapter_cleanup()"). Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x") Signed-off-by: Linmao Li Reviewed-by: Jeff Chen Signed-off-by: Jeff Chen --- drivers/net/wireless/nxp/nxpwifi/init.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/nxp/nxpwifi/init.c b/drivers/net/wireless/nxp/nxpwifi/init.c index 7c75dca5d0a8..3c4df37333d8 100644 --- a/drivers/net/wireless/nxp/nxpwifi/init.c +++ b/drivers/net/wireless/nxp/nxpwifi/init.c @@ -328,7 +328,7 @@ static void nxpwifi_invalidate_lists(struct nxpwifi_adapter *adapter) static void nxpwifi_adapter_cleanup(struct nxpwifi_adapter *adapter) { - timer_delete(&adapter->wakeup_timer); + timer_delete_sync(&adapter->wakeup_timer); nxpwifi_cancel_all_pending_cmd(adapter); wake_up_interruptible(&adapter->cmd_wait_q.wait); wake_up_interruptible(&adapter->hs_activate_wait_q); From d68d6d8d8bbb5596909479dc6cac58132d8f0dbf Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Thu, 13 Aug 2026 16:23:14 +0800 Subject: [PATCH 16/25] wifi: nxpwifi: free the aggregation buffer when the RA list disappears nxpwifi_11n_aggregate_pkt() drops ra_list_spinlock while it copies each subframe, so it rechecks the RA list after taking the lock again. The check inside the aggregation loop returns without releasing the skb_aggr it has been filling, leaking one tx_buf_size buffer along with the subframes already aggregated into it. Release skb_aggr there, the way the same check on the -EBUSY path already does. mwifiex fixed the same issue in commit 990a73dec3fd ("wifi: mwifiex: Fix memory leak in mwifiex_11n_aggregate_pkt()"). Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x") Signed-off-by: Linmao Li Reviewed-by: Jeff Chen Signed-off-by: Jeff Chen --- drivers/net/wireless/nxp/nxpwifi/11n_aggr.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/nxp/nxpwifi/11n_aggr.c b/drivers/net/wireless/nxp/nxpwifi/11n_aggr.c index be7080f2a6ce..54933c42c960 100644 --- a/drivers/net/wireless/nxp/nxpwifi/11n_aggr.c +++ b/drivers/net/wireless/nxp/nxpwifi/11n_aggr.c @@ -168,6 +168,7 @@ nxpwifi_11n_aggregate_pkt(struct nxpwifi_private *priv, if (!nxpwifi_is_ralist_valid(priv, pra_list, ptrindex)) { spin_unlock_bh(&priv->wmm.ra_list_spinlock); + nxpwifi_write_data_complete(adapter, skb_aggr, 1, -1); return -ENOENT; } From 050f03bb0effd773c877cfba3b651c6d380b1c15 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Thu, 13 Aug 2026 16:23:15 +0800 Subject: [PATCH 17/25] wifi: nxpwifi: zero the channel statistics array adapter->chan_stats comes from vmalloc(), which does not clear the memory, and nxpwifi_cfg80211_dump_survey() reads it as soon as user space asks for survey data. For the entries no scan has filled in, a non-zero cca_scan_dur passes the validity check and stale bytes are reported to user space as noise, time and time_busy. Use kcalloc() instead. The array holds two entries per supported channel, small enough not to need vmalloc(), and the two callers change to kfree() accordingly. mwifiex fixed the same issue in commit 0e20450829ca ("wifi: mwifiex: Initialize the chan_stats array to zero"). Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x") Signed-off-by: Linmao Li Reviewed-by: Jeff Chen Signed-off-by: Jeff Chen --- drivers/net/wireless/nxp/nxpwifi/cfg80211.c | 5 +++-- drivers/net/wireless/nxp/nxpwifi/main.c | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c index 161697e038c0..788b83356981 100644 --- a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c +++ b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c @@ -3731,8 +3731,9 @@ int nxpwifi_init_channel_scan_gap(struct nxpwifi_adapter *adapter) * additional active scan request for hidden SSIDs on passive channels. */ adapter->num_in_chan_stats = 2 * (n_channels_bg + n_channels_a); - adapter->chan_stats = vmalloc(array_size(sizeof(*adapter->chan_stats), - adapter->num_in_chan_stats)); + adapter->chan_stats = kcalloc(adapter->num_in_chan_stats, + sizeof(*adapter->chan_stats), + GFP_KERNEL); if (!adapter->chan_stats) return -ENOMEM; diff --git a/drivers/net/wireless/nxp/nxpwifi/main.c b/drivers/net/wireless/nxp/nxpwifi/main.c index 55b962430f37..9e13348105c3 100644 --- a/drivers/net/wireless/nxp/nxpwifi/main.c +++ b/drivers/net/wireless/nxp/nxpwifi/main.c @@ -644,7 +644,7 @@ static int _nxpwifi_fw_dpc(const struct firmware *firmware, void *context) goto done; err_add_intf: - vfree(adapter->chan_stats); + kfree(adapter->chan_stats); err_init_chan_scan: wiphy_unregister(adapter->wiphy); wiphy_free(adapter->wiphy); @@ -1384,7 +1384,7 @@ static void nxpwifi_uninit_sw(struct nxpwifi_adapter *adapter) wiphy_free(adapter->wiphy); adapter->wiphy = NULL; - vfree(adapter->chan_stats); + kfree(adapter->chan_stats); nxpwifi_free_cmd_buffers(adapter); } From 7769bb2457fcfa8216aeae84f32cb7084cab5637 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Thu, 20 Aug 2026 16:16:33 +0800 Subject: [PATCH 18/25] wifi: nxpwifi: fix the authentication frame length handling nxpwifi_cfg80211_authenticate() builds a 3-address management frame in a temporary buffer and hands it to nxpwifi_form_mgmt_frame(), which copies it into the skb and splices in address4. The frame length is tracked in a single u16 that conflates the two lengths, and it gets both wrong. Truncation. req->ie_len and req->auth_data_len are both size_t. The IE policy caps req->ie_len at IEEE80211_MAX_DATA_LEN, but NL80211_ATTR_AUTH_DATA only has a four-byte minimum length policy. Since nla_len is a u16, a single authentication data attribute can carry up to 65531 bytes, enough for the combined frame length to exceed U16_MAX before it is assigned to pkt_len. With auth_data_len == 65510 and no IEs the length wraps to 6, kzalloc(6) succeeds, and the memcpy() below then writes 65506 user-provided bytes past a 6-byte heap object. Geometry. NXPWIFI_MGMT_HEADER_LEN is 30, which is the 24-byte 3-address header plus the address4 that the firmware expects. The temporary buffer only holds a struct ieee80211_hdr_3addr, so six of the bytes accounted for are never used, and passing that length to nxpwifi_form_mgmt_frame() makes it append ETH_ALEN on top of a length that already included it. The skb is sized without those six bytes, so the last skb_put_data() overruns the tailroom by exactly ETH_ALEN. It usually goes unnoticed because SKB_DATA_ALIGN() rounding leaves slack. The driver's other caller of the helper, nxpwifi_cfg80211_mgmt_tx(), adds ETH_ALEN to the skb size before allocating and passes the 3-address length, which is what this path should do too. Keep the two lengths apart. frame_len is the 3-address frame the driver builds: it sizes the temporary buffer and is what the helper is told. pkt_len is what the firmware sees, frame_len plus address4: it sizes the skb and goes into tx_info. Computing frame_len in size_t and rejecting anything that cannot be represented once ETH_ALEN is added removes the wrap. The value handed to the firmware is unchanged, so this is not a behavioural change for frames that were already valid. A tighter 802.11 bound may make sense, but that depends on the firmware's frame format and is a separate decision. Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x") Signed-off-by: Linmao Li Signed-off-by: Jeff Chen --- drivers/net/wireless/nxp/nxpwifi/cfg80211.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c index 788b83356981..3221ae167c58 100644 --- a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c +++ b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c @@ -3325,6 +3325,7 @@ nxpwifi_cfg80211_authenticate(struct wiphy *wiphy, struct nxpwifi_adapter *adapter = priv->adapter; struct sk_buff *skb; u16 pkt_len, auth_alg; + size_t frame_len; int ret; struct ieee80211_mgmt *mgmt; struct nxpwifi_txinfo *tx_info; @@ -3398,13 +3399,23 @@ nxpwifi_cfg80211_authenticate(struct wiphy *wiphy, nxpwifi_cancel_scan(adapter); - pkt_len = (u16)req->ie_len + req->auth_data_len + - NXPWIFI_MGMT_HEADER_LEN + NXPWIFI_AUTH_BODY_LEN; + frame_len = req->ie_len + req->auth_data_len + + sizeof(struct ieee80211_hdr_3addr) + NXPWIFI_AUTH_BODY_LEN; if (req->auth_data_len >= 4) - pkt_len -= 4; + frame_len -= 4; - mgmt = kzalloc(pkt_len, GFP_KERNEL); + /* nxpwifi_form_mgmt_frame() inserts address4, so the frame handed to + * the firmware is ETH_ALEN longer than the one built here. + */ + if (frame_len > U16_MAX - ETH_ALEN) { + nxpwifi_dbg(adapter, ERROR, + "auth frame too long: %zu bytes\n", frame_len); + return -EINVAL; + } + pkt_len = frame_len + ETH_ALEN; + + mgmt = kzalloc(frame_len, GFP_KERNEL); skb = dev_alloc_skb(NXPWIFI_MIN_DATA_HEADER_LEN + NXPWIFI_MGMT_FRAME_HEADER_SIZE + @@ -3450,7 +3461,7 @@ nxpwifi_cfg80211_authenticate(struct wiphy *wiphy, memcpy((u8 *)varptr, req->ie, req->ie_len); } - nxpwifi_form_mgmt_frame(skb, (const u8 *)mgmt, pkt_len); + nxpwifi_form_mgmt_frame(skb, (const u8 *)mgmt, frame_len); kfree(mgmt); priv->auth_flag = HOST_MLME_AUTH_PENDING; priv->auth_alg = auth_alg; From 7fa5fb9976c4532007a666624dc241021f74c8a9 Mon Sep 17 00:00:00 2001 From: Linmao Li Date: Thu, 20 Aug 2026 16:16:34 +0800 Subject: [PATCH 19/25] wifi: nxpwifi: handle authentication frame allocation failures nxpwifi_cfg80211_authenticate() allocates the buffer it builds the management frame in and never checks the result: mgmt = kzalloc(frame_len, GFP_KERNEL); skb = dev_alloc_skb(...); if (!skb) { ... return -ENOMEM; } ... memcpy(mgmt->da, req->bss->bssid, ETH_ALEN); On allocation failure the memcpy() a dozen lines later dereferences NULL. The same sequence leaks mgmt when dev_alloc_skb() fails: that path returns without freeing it, while the success path drops it after nxpwifi_form_mgmt_frame(). Bail out when the allocation fails, and free it before returning on the skb error path. Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x") Signed-off-by: Linmao Li Signed-off-by: Jeff Chen --- drivers/net/wireless/nxp/nxpwifi/cfg80211.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c index 3221ae167c58..473e81304ca8 100644 --- a/drivers/net/wireless/nxp/nxpwifi/cfg80211.c +++ b/drivers/net/wireless/nxp/nxpwifi/cfg80211.c @@ -3416,6 +3416,8 @@ nxpwifi_cfg80211_authenticate(struct wiphy *wiphy, pkt_len = frame_len + ETH_ALEN; mgmt = kzalloc(frame_len, GFP_KERNEL); + if (!mgmt) + return -ENOMEM; skb = dev_alloc_skb(NXPWIFI_MIN_DATA_HEADER_LEN + NXPWIFI_MGMT_FRAME_HEADER_SIZE + @@ -3423,6 +3425,7 @@ nxpwifi_cfg80211_authenticate(struct wiphy *wiphy, if (!skb) { nxpwifi_dbg(adapter, ERROR, "allocate skb failed for management frame\n"); + kfree(mgmt); return -ENOMEM; } From 32d1a000e99612e010335760363936d85591fd69 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Wed, 26 Aug 2026 00:17:09 +0100 Subject: [PATCH 20/25] wifi: nxpwifi: fix inverted check in Tx BA stream entry deletion nxpwifi_is_tx_ba_stream_ptr_valid() returns true when the entry is still linked, and every caller passes an entry that is on the list, so the early return always fires and nothing is ever unlinked or freed. Entries leak on every teardown and, since nxpwifi_space_avail_for_new_ba_stream() counts them, Tx aggregation stops being negotiated once the stale count reaches the maximum. Changing the original dead && test to || to silence a NULL dereference report inverted the validity test along with it. Fixes: 00c786a7581e ("wifi: nxpwifi: fix multiple static analysis errors and warnings") Assisted-by: Claude:claude-opus-5 Signed-off-by: David Carlier Signed-off-by: Jeff Chen --- drivers/net/wireless/nxp/nxpwifi/11n.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/nxp/nxpwifi/11n.c b/drivers/net/wireless/nxp/nxpwifi/11n.c index c2a54d781b42..25b2e430f3f9 100644 --- a/drivers/net/wireless/nxp/nxpwifi/11n.c +++ b/drivers/net/wireless/nxp/nxpwifi/11n.c @@ -451,7 +451,7 @@ void nxpwifi_11n_delete_tx_ba_stream_tbl_entry(struct nxpwifi_private *priv, struct nxpwifi_tx_ba_stream_tbl *tbl) { - if (!tbl || nxpwifi_is_tx_ba_stream_ptr_valid(priv, tbl)) + if (!tbl || !nxpwifi_is_tx_ba_stream_ptr_valid(priv, tbl)) return; nxpwifi_dbg(priv->adapter, INFO, From 5e99bdb94720331e2fd38c10c5b1afa1ce640835 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Wed, 26 Aug 2026 00:17:10 +0100 Subject: [PATCH 21/25] wifi: nxpwifi: do not delete Rx reorder entries under RCU The RCU guard is declared in the body of the per-TID loop, so it is still held across the teardown pass. nxpwifi_del_rx_reorder_entry() flushes the Rx workqueue and deletes the reorder timer synchronously, both of which sleep, so a station deauthenticating from the AP splats under CONFIG_DEBUG_ATOMIC_SLEEP. Scope the guard to the collection walk. The teardown does not need RCU: it serialises on priv->rx_reorder_tbl_lock[] and frees with kfree_rcu(). Fixes: 73b01e57ed3e ("wifi: nxp: add nxpwifi driver for IW61x") Assisted-by: Claude:claude-opus-5 Signed-off-by: David Carlier Signed-off-by: Jeff Chen --- drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c b/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c index 65b628411543..87cf2ec85991 100644 --- a/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c +++ b/drivers/net/wireless/nxp/nxpwifi/11n_rxreorder.c @@ -205,11 +205,12 @@ void nxpwifi_11n_del_rx_reorder_tbl_by_ta(struct nxpwifi_private *priv, u8 *ta) return; for (i = 0; i < MAX_NUM_TID; i++) { - guard(rcu)(); - list_for_each_entry_rcu(tbl, &priv->rx_reorder_tbl_ptr[i], list) { - if (!memcmp(tbl->ta, ta, ETH_ALEN)) { - INIT_LIST_HEAD(&tbl->tmp_list); - list_add_tail(&tbl->tmp_list, &to_delete); + scoped_guard(rcu) { + list_for_each_entry_rcu(tbl, &priv->rx_reorder_tbl_ptr[i], list) { + if (!memcmp(tbl->ta, ta, ETH_ALEN)) { + INIT_LIST_HEAD(&tbl->tmp_list); + list_add_tail(&tbl->tmp_list, &to_delete); + } } } From 05165f7b3b9ab6817fe216c24f90755b7c5e15f3 Mon Sep 17 00:00:00 2001 From: Yingying Tang Date: Thu, 11 Jun 2026 20:23:32 -0700 Subject: [PATCH 22/25] Revert "wifi: ath12k: add panic handler" ath12k_core_panic_handler() is invoked via atomic_notifier_call_chain(), which runs inside an RCU read-side critical section. The current code calls ath12k_pci_sw_reset() synchronously from this context, which eventually reaches mhi_device_get_sync() and schedule_timeout(), triggering a voluntary context switch within RCU. Call trace: rcu_note_context_switch+0x4c4/0x508 (P) __schedule+0xbc/0x1204 schedule+0x34/0x110 schedule_timeout+0x84/0x11c __mhi_device_get_sync+0x164/0x228 [mhi] mhi_device_get_sync+0x1c/0x3c [mhi] ath12k_wifi7_pci_bus_wake_up+0x20/0x2c [ath12k_wifi7] ath12k_pci_read32+0x58/0x350 [ath12k] ath12k_pci_clear_dbg_registers+0x28/0xb8 [ath12k] ath12k_pci_panic_handler+0x20/0x44 [ath12k] ath12k_core_panic_handler+0x28/0x3c [ath12k] notifier_call_chain+0x78/0x1c0 atomic_notifier_call_chain+0x3c/0x5c Revert change "wifi: ath12k: add panic handler" to avoid this issue. Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.1.c7-00108-QCAHMTSWPL_V1.0_V2.0_SILICONZ_UPSTREAM-3 Signed-off-by: Yingying Tang Tested-by: Joonhoe Kim <26rote@gmail.com> Reviewed-by: Rameshkumar Sundaram Fixes: 809055628bce ("wifi: ath12k: add panic handler") Link: https://patch.msgid.link/20260612032332.2278338-1-yingying.tang@oss.qualcomm.com Signed-off-by: Jeff Johnson --- drivers/net/wireless/ath/ath12k/core.c | 33 +------------------------- drivers/net/wireless/ath/ath12k/core.h | 3 --- drivers/net/wireless/ath/ath12k/hif.h | 9 ------- drivers/net/wireless/ath/ath12k/pci.c | 8 ------- 4 files changed, 1 insertion(+), 52 deletions(-) diff --git a/drivers/net/wireless/ath/ath12k/core.c b/drivers/net/wireless/ath/ath12k/core.c index 262a2045309b..b0107cde99ff 100644 --- a/drivers/net/wireless/ath/ath12k/core.c +++ b/drivers/net/wireless/ath/ath12k/core.c @@ -1776,29 +1776,6 @@ int ath12k_core_pre_init(struct ath12k_base *ab) return 0; } -static int ath12k_core_panic_handler(struct notifier_block *nb, - unsigned long action, void *data) -{ - struct ath12k_base *ab = container_of(nb, struct ath12k_base, - panic_nb); - - return ath12k_hif_panic_handler(ab); -} - -static int ath12k_core_panic_notifier_register(struct ath12k_base *ab) -{ - ab->panic_nb.notifier_call = ath12k_core_panic_handler; - - return atomic_notifier_chain_register(&panic_notifier_list, - &ab->panic_nb); -} - -static void ath12k_core_panic_notifier_unregister(struct ath12k_base *ab) -{ - atomic_notifier_chain_unregister(&panic_notifier_list, - &ab->panic_nb); -} - static inline bool ath12k_core_hw_group_create_ready(struct ath12k_hw_group *ag) { @@ -2214,18 +2191,13 @@ int ath12k_core_init(struct ath12k_base *ab) struct ath12k_hw_group *ag; int ret; - ret = ath12k_core_panic_notifier_register(ab); - if (ret) - ath12k_warn(ab, "failed to register panic handler: %d\n", ret); - mutex_lock(&ath12k_hw_group_mutex); ag = ath12k_core_hw_group_assign(ab); if (!ag) { mutex_unlock(&ath12k_hw_group_mutex); ath12k_warn(ab, "unable to get hw group\n"); - ret = -ENODEV; - goto err_unregister_notifier; + return -ENODEV; } mutex_unlock(&ath12k_hw_group_mutex); @@ -2250,8 +2222,6 @@ int ath12k_core_init(struct ath12k_base *ab) err_unassign_hw_group: ath12k_core_hw_group_unassign(ab); -err_unregister_notifier: - ath12k_core_panic_notifier_unregister(ab); return ret; } @@ -2260,7 +2230,6 @@ void ath12k_core_deinit(struct ath12k_base *ab) { ath12k_core_hw_group_destroy(ab->ag); ath12k_core_hw_group_unassign(ab); - ath12k_core_panic_notifier_unregister(ab); } void ath12k_core_free(struct ath12k_base *ab) diff --git a/drivers/net/wireless/ath/ath12k/core.h b/drivers/net/wireless/ath/ath12k/core.h index a98fc6e0699d..93fd227b4d19 100644 --- a/drivers/net/wireless/ath/ath12k/core.h +++ b/drivers/net/wireless/ath/ath12k/core.h @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include @@ -1139,8 +1138,6 @@ struct ath12k_base { #endif /* CONFIG_ACPI */ - struct notifier_block panic_nb; - struct ath12k_hw_group *ag; struct ath12k_wsi_info wsi_info; enum ath12k_qmi_firmware_mode fw_mode; diff --git a/drivers/net/wireless/ath/ath12k/hif.h b/drivers/net/wireless/ath/ath12k/hif.h index 4de8667690e9..aecdb0c5f285 100644 --- a/drivers/net/wireless/ath/ath12k/hif.h +++ b/drivers/net/wireless/ath/ath12k/hif.h @@ -30,7 +30,6 @@ struct ath12k_hif_ops { void (*ce_irq_enable)(struct ath12k_base *ab); void (*ce_irq_disable)(struct ath12k_base *ab); void (*get_ce_msi_idx)(struct ath12k_base *ab, u32 ce_id, u32 *msi_idx); - int (*panic_handler)(struct ath12k_base *ab); void (*coredump_download)(struct ath12k_base *ab); }; @@ -149,14 +148,6 @@ static inline void ath12k_hif_power_down(struct ath12k_base *ab, bool is_suspend ab->hif.ops->power_down(ab, is_suspend); } -static inline int ath12k_hif_panic_handler(struct ath12k_base *ab) -{ - if (!ab->hif.ops->panic_handler) - return NOTIFY_DONE; - - return ab->hif.ops->panic_handler(ab); -} - static inline void ath12k_hif_coredump_download(struct ath12k_base *ab) { if (ab->hif.ops->coredump_download) diff --git a/drivers/net/wireless/ath/ath12k/pci.c b/drivers/net/wireless/ath/ath12k/pci.c index 6441927b5382..8978edb44cec 100644 --- a/drivers/net/wireless/ath/ath12k/pci.c +++ b/drivers/net/wireless/ath/ath12k/pci.c @@ -1484,13 +1484,6 @@ void ath12k_pci_power_down(struct ath12k_base *ab, bool is_suspend) ath12k_pci_sw_reset(ab_pci->ab, false); } -static int ath12k_pci_panic_handler(struct ath12k_base *ab) -{ - ath12k_pci_sw_reset(ab, false); - - return NOTIFY_OK; -} - static const struct ath12k_hif_ops ath12k_pci_hif_ops = { .start = ath12k_pci_start, .stop = ath12k_pci_stop, @@ -1508,7 +1501,6 @@ static const struct ath12k_hif_ops ath12k_pci_hif_ops = { .ce_irq_enable = ath12k_pci_hif_ce_irq_enable, .ce_irq_disable = ath12k_pci_hif_ce_irq_disable, .get_ce_msi_idx = ath12k_pci_get_ce_msi_idx, - .panic_handler = ath12k_pci_panic_handler, #ifdef CONFIG_ATH12K_COREDUMP .coredump_download = ath12k_pci_coredump_download, #endif From f394cbd0980c95c9173f8551a5eb4c4f28d598eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Roux?= Date: Tue, 6 Oct 2026 10:37:51 +0200 Subject: [PATCH 23/25] wifi: mac80211: don't estimate airtime for unsupported rate widths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ieee80211_fill_rate_info() copies the driver's rate_info bandwidth into the rx_status as is, but ieee80211_get_rate_duration() only handles 20/40/80/160/320 MHz. When a driver reports another width, e.g. an HE/EHT RU allocation, it hits WARN_ON_ONCE() and returns 0 without setting *overhead. ieee80211_rate_expected_tx_airtime() then goes on using the uninitialized overhead, as Dan's static checker report points out. Seen on a Microsoft Surface Pro 11 (WCN7850, ath12k) while NetworkManager dumps station info: WARNING: net/mac80211/airtime.c:532 at ieee80211_get_rate_duration.isra.0+0xc0/0x180 [mac80211] ieee80211_get_rate_duration.isra.0+0xc0/0x180 [mac80211] sta_estimate_expected_throughput.isra.0+0x38/0x64 [mac80211] sta_set_sinfo+0x7fc/0x8e4 [mac80211] ieee80211_dump_station+0x4c/0x84 [mac80211] nl80211_dump_station+0x594/0x7e8 [cfg80211] Only accept widths that ieee80211_get_rate_duration() handles in ieee80211_fill_rate_info(). Other widths then give no estimate instead of a warning. Also return 0 from ieee80211_rate_expected_tx_airtime() when no duration could be computed, as ieee80211_calc_rx_airtime() already does. Tested on the Surface Pro 11 with next-20260929: Wi-Fi works and "expected throughput" is still reported (about 1049 Mbit/s for a 1297 Mbit/s HE 160 MHz tx rate). The warning itself is rare (twice in several days of use), so this does not prove it is gone, only that the change does not break the normal path. Fixes: 094dc1619cb0 ("wifi: mac80211: factor out part of ieee80211_calc_expected_tx_airtime") Reported-by: Dan Carpenter Closes: https://lore.kernel.org/all/aneGlCMVnqRhlf6p@stanley.mountain/ Assisted-by: LLM Signed-off-by: François Roux Link: https://patch.msgid.link/20261006083751.4015-1-franzelfranzel@gmail.com Signed-off-by: Johannes Berg --- net/mac80211/airtime.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/net/mac80211/airtime.c b/net/mac80211/airtime.c index 0c54cdbd753c..a4e1f33c0377 100644 --- a/net/mac80211/airtime.c +++ b/net/mac80211/airtime.c @@ -632,6 +632,22 @@ static bool ieee80211_fill_rate_info(struct ieee80211_hw *hw, if (!ri || !sband) return false; + /* + * ieee80211_get_rate_duration() only handles these widths. Drivers + * may also report e.g. HE/EHT RU allocations, which cannot be used + * to estimate airtime here. + */ + switch (ri->bw) { + case RATE_INFO_BW_20: + case RATE_INFO_BW_40: + case RATE_INFO_BW_80: + case RATE_INFO_BW_160: + case RATE_INFO_BW_320: + break; + default: + return false; + } + stat->bw = ri->bw; stat->nss = ri->nss; stat->rate_idx = ri->mcs; @@ -770,6 +786,8 @@ u32 ieee80211_rate_expected_tx_airtime(struct ieee80211_hw *hw, return ieee80211_calc_rx_airtime(hw, &stat, len) * 1024; duration = ieee80211_get_rate_duration(hw, &stat, &overhead); + if (!duration) + return 0; /* * Assume that HT/VHT transmission on any AC except VO will From 08a3f02fea636e97a7422d1512f76dacdb5b0887 Mon Sep 17 00:00:00 2001 From: Michele Dionisio Date: Thu, 24 Sep 2026 14:30:27 +0200 Subject: [PATCH 24/25] wifi: brcmfmac: fix P2P device removal race in brcmf_detach() When the driver is removed while the user space process that created the P2P device (e.g. wpa_supplicant) is still exiting, the P2P device interface is removed twice and the kernel crashes. The two paths are: - wpa_supplicant, on exit, sends NL80211_CMD_DEL_INTERFACE for the P2P device. nl80211_del_interface() holds RTNL and the wiphy mutex and calls brcmf_p2p_del_vif(), which disables discovery and waits up to 1.5 s for BRCMF_E_IF_DEL. Then it calls brcmf_remove_interface(), which unregisters the wdev and frees the vif and the ifp. - brcmf_detach() sets the bus down, so BRCMF_E_IF_DEL never arrives, and calls brcmf_remove_interface(ifp, false) for the same ifp. brcmf_p2p_ifp_removed() reads ifp->vif and then blocks on rtnl_lock(). When brcmf_p2p_del_vif() releases RTNL, it calls cfg80211_unregister_wdev() on the wdev that was already unregistered and freed. The second list_del_rcu() of wdev->list faults on LIST_POISON2: brcmfmac: brcmf_p2p_del_vif delete P2P vif brcmfmac: brcmf_p2p_deinit_discovery enter brcmfmac: brcmf_p2p_del_vif P2P: GO_NEG_PHASE status cleared brcmfmac: brcmf_detach Enter brcmfmac: brcmf_bus_change_state 1 -> 0 brcmfmac: brcmf_remove_interface Enter, bsscfgidx=1, ifidx=0 brcmfmac: brcmf_del_if Enter, bsscfgidx=1, ifidx=0 brcmfmac: brcmf_p2p_ifp_removed P2P: device interface removed [1.5 s later, brcmf_p2p_del_vif() timed out] brcmfmac: brcmf_remove_interface Enter, bsscfgidx=1, ifidx=0 brcmfmac: brcmf_del_if Enter, bsscfgidx=1, ifidx=0 brcmfmac: brcmf_p2p_ifp_removed P2P: device interface removed Unable to handle kernel paging request at virtual address dead000000000122 Internal error: Oops: 96000044 [#1] PREEMPT_RT SMP pc : _cfg80211_unregister_wdev+0x6c/0x264 [cfg80211] Call trace: _cfg80211_unregister_wdev+0x6c/0x264 [cfg80211] cfg80211_unregister_wdev+0x10/0x1c [cfg80211] brcmf_p2p_ifp_removed+0x84/0xb0 [brcmfmac] brcmf_remove_interface+0x1f4/0x254 [brcmfmac] brcmf_detach+0x88/0x180 [brcmfmac] brcmf_sdio_remove+0x90/0x7b0 [brcmfmac] brcmf_sdiod_remove+0x20/0xa0 [brcmfmac] brcmf_ops_sdio_remove+0xbc/0x12c [brcmfmac] sdio_bus_remove+0x38/0x144 device_release_driver_internal+0x1e8/0x2c0 device_release_driver+0x14/0x20 brcmf_sdio_bus_remove+0x2c/0x40 [brcmfmac] brcmf_fwvid_unregister_vendor+0xd8/0x170 [brcmfmac] __exit_compat+0x18/0x418 [brcmfmac_cyw] __arm64_sys_delete_module+0x1ac/0x244 The log was taken with debug=0x406 on an i.MX 8M Plus board with a CYW55513 (Sona IF513) on SDIO. The driver is the Ezurio backport of brcmfmac from v6.18.22 on a 5.4-rt kernel. The code involved is the same in mainline. To reproduce: 1. Start wpa_supplicant on wlan0 and let it associate. It creates the P2P device (iw dev shows "type P2P-device"). 2. Stop wpa_supplicant and remove the driver without waiting for wpa_supplicant to exit: kill $(pidof wpa_supplicant) rmmod brcmfmac_cyw brcmfmac The crash is reproducible on that board. For the interface without a netdev (the P2P device), take RTNL and the wiphy mutex in brcmf_detach() before reading iflist, and remove it with locked=true. If brcmf_p2p_del_vif() runs first, brcmf_detach() finds the slot empty. If brcmf_detach() runs first, nl80211 does not find the wdev any more. The interfaces with a netdev are removed as before, because brcmf_del_if() takes RTNL on its own for the primary interface. Fixes: 9831bcb987df ("brcmfmac: Deleting of p2p device is leaking memory.") Cc: stable@vger.kernel.org Assisted-by: claude-opus-5-5 Signed-off-by: Michele Dionisio Acked-by: Arend van Spriel Link: https://patch.msgid.link/20260924123027.4122909-2-michele.dionisio@gmail.com Signed-off-by: Johannes Berg --- .../broadcom/brcm80211/brcmfmac/core.c | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c index d2ae67985606..92ec269f43b2 100644 --- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c +++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c @@ -1489,8 +1489,31 @@ void brcmf_detach(struct device *dev) brcmf_bus_change_state(bus_if, BRCMF_BUS_DOWN); /* make sure primary interface removed last */ for (i = BRCMF_MAX_IFS - 1; i > -1; i--) { - if (drvr->iflist[i]) - brcmf_remove_interface(drvr->iflist[i], false); + struct brcmf_if *ifp = drvr->iflist[i]; + + if (!ifp) + continue; + + if (ifp->ndev) { + brcmf_remove_interface(ifp, false); + continue; + } + + /* The P2P device interface has no netdev. Its removal can + * race with NL80211_CMD_DEL_INTERFACE issued by a user space + * process that is exiting (e.g. wpa_supplicant), which ends + * in brcmf_p2p_del_vif() under RTNL and the wiphy mutex. If + * both paths remove the interface, the wdev is unregistered + * twice and the vif is used after being freed. Take the same + * locks and re-read iflist, so only one path removes it. + */ + rtnl_lock(); + wiphy_lock(drvr->wiphy); + ifp = drvr->iflist[i]; + if (ifp) + brcmf_remove_interface(ifp, true); + wiphy_unlock(drvr->wiphy); + rtnl_unlock(); } brcmf_bus_stop(drvr->bus_if); From 9727d1c4e1c971fc42b049b645fdda6852758835 Mon Sep 17 00:00:00 2001 From: Johannes Berg Date: Wed, 7 Oct 2026 22:42:08 +0200 Subject: [PATCH 25/25] Revert "wifi: libertas: reject short monitor TX frames" This reverts commit 13ff543e0b2c713aedeaadadde686686e949dc78. The 'goto free' just made things worse, causing a spinlock to be handled incorrectly. Revert this for now. Reported-by: Takashi Iwai Cc: stable@vger.kernel.org Fixes: 13ff543e0b2c ("wifi: libertas: reject short monitor TX frames") Signed-off-by: Johannes Berg --- drivers/net/wireless/marvell/libertas/tx.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/drivers/net/wireless/marvell/libertas/tx.c b/drivers/net/wireless/marvell/libertas/tx.c index 13d08022e414..27304a98787d 100644 --- a/drivers/net/wireless/marvell/libertas/tx.c +++ b/drivers/net/wireless/marvell/libertas/tx.c @@ -117,13 +117,6 @@ netdev_tx_t lbs_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) if (priv->wdev->iftype == NL80211_IFTYPE_MONITOR) { struct tx_radiotap_hdr *rtap_hdr = (void *)skb->data; - if (skb->len < sizeof(*rtap_hdr) + 4 + ETH_ALEN) { - lbs_deb_tx("tx err: short monitor frame %u\n", skb->len); - dev->stats.tx_dropped++; - dev->stats.tx_errors++; - goto free; - } - /* set txpd fields from the radiotap header */ txpd->tx_control = cpu_to_le32(convert_radiotap_rate_to_mv(rtap_hdr->rate));