From 9629f31f505d74e76ac0d7a9492fd06c0316fc5d Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Sun, 26 Apr 2026 23:33:36 +0900 Subject: [PATCH 01/71] wifi: mt76: mt7925: clean up DMA on probe failure mt7925_pci_probe() initializes DMA before registering the device. If mt7925_register_device() fails, probe returns through err_free_irq without tearing down DMA state. That leaves the TX NAPI instance enabled and skips the DMA queue cleanup that the normal remove path performs through mt7925e_unregister_device(). Add a dedicated unwind label for failures after mt7925_dma_init() succeeds. Fixes: c948b5da6bbe ("wifi: mt76: mt7925: add Mediatek Wi-Fi7 driver for mt7925 chips") Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Link: https://patch.msgid.link/20260426143728.41534-1-pakmyeonghun@bagmyeonghun-ui-MacBookPro.local Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/pci.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c index c4161754c01d..48837723024d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c @@ -415,10 +415,12 @@ static int mt7925_pci_probe(struct pci_dev *pdev, ret = mt7925_register_device(dev); if (ret) - goto err_free_irq; + goto err_free_dma; return 0; +err_free_dma: + mt792x_dma_cleanup(dev); err_free_irq: devm_free_irq(&pdev->dev, pdev->irq, dev); err_free_dev: From 31ee1582717e220cc5a3fa8f3940d5693c5c9169 Mon Sep 17 00:00:00 2001 From: Rosen Penev Date: Sun, 26 Apr 2026 22:17:46 -0700 Subject: [PATCH 02/71] wifi: mt76: fix of_get_mac_address error handling Check return value instead of is_valid_ether_addr. The latter is handled by the former. Signed-off-by: Rosen Penev Acked-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260427051746.954704-1-rosenp@gmail.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/eeprom.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/eeprom.c b/drivers/net/wireless/mediatek/mt76/eeprom.c index afdb73661866..b99d7452800f 100644 --- a/drivers/net/wireless/mediatek/mt76/eeprom.c +++ b/drivers/net/wireless/mediatek/mt76/eeprom.c @@ -181,7 +181,7 @@ mt76_eeprom_override(struct mt76_phy *phy) if (err == -EPROBE_DEFER) return err; - if (!is_valid_ether_addr(phy->macaddr)) { + if (err) { eth_random_addr(phy->macaddr); dev_info(dev->dev, "Invalid MAC address, using random address %pM\n", From 346dac35b1384af9338b34b6835e82e634ea4d2c Mon Sep 17 00:00:00 2001 From: Hongling Zeng Date: Tue, 12 May 2026 14:52:45 +0800 Subject: [PATCH 03/71] wifi: mt76: mt7921: fix resource leak in probe error path When pcim_iomap_region() or devm_kmemdup() fail, the code returns directly without cleaning up previously allocated resources: - mt76_device allocated by mt76_alloc_device() - pci irq vectors allocated by pci_alloc_irq_vectors() Fix this by jumping to the existing error cleanup path instead of returning directly. Fixes: ee5bb35d2b83 ("wifi: mt76: mt7921: Replace deprecated PCI function") Fixes: 222606f43b58 ("wifi: mt76: mt7921: handle MT7902 irq_map quirk with mutable copy") Signed-off-by: Hongling Zeng Link: https://patch.msgid.link/20260512065245.46496-1-zenghongling@kylinos.cn Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7921/pci.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c index 7a790ddf43bb..49a37185f056 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c @@ -343,11 +343,14 @@ static int mt7921_pci_probe(struct pci_dev *pdev, pci_set_drvdata(pdev, mdev); - regs = pcim_iomap_region(pdev, 0, pci_name(pdev)); - if (IS_ERR(regs)) - return PTR_ERR(regs); - dev = container_of(mdev, struct mt792x_dev, mt76); + + regs = pcim_iomap_region(pdev, 0, pci_name(pdev)); + if (IS_ERR(regs)) { + ret = PTR_ERR(regs); + goto err_free_dev; + } + dev->fw_features = features; dev->hif_ops = &mt7921_pcie_ops; dev->irq_map = &irq_map; @@ -359,8 +362,10 @@ static int mt7921_pci_probe(struct pci_dev *pdev, /* MT7902 needs a mutable copy because wm2_complete_mask differs */ map = devm_kmemdup(&pdev->dev, &irq_map, sizeof(irq_map), GFP_KERNEL); - if (!map) - return -ENOMEM; + if (!map) { + ret = -ENOMEM; + goto err_free_dev; + } map->rx.wm2_complete_mask = 0; dev->irq_map = map; From 351dd7d2c80d23e56dcce6faa4e62bea5b0877c7 Mon Sep 17 00:00:00 2001 From: Arjan van de Ven Date: Mon, 4 May 2026 07:51:06 -0700 Subject: [PATCH 04/71] wifi: mt76: mt7921/mt7925: fix NULL dereference in CSA beacon This patch is based on a BUG as reported by Bongani Hlope at https://lore.kernel.org/all/20260502125824.425d7159@bongani-mini.home.org.za/ When a channel-switch announcement (CSA) beacon is received, cfg80211 queues a wiphy work item that eventually calls mt7921_channel_switch_rx_beacon(). If the station disconnects (or the channel context is otherwise torn down) between the time the work is queued and the time it runs, the driver's dev->new_ctx pointer can already have been cleared to NULL. mt7921_channel_switch_rx_beacon() then dereferences new_ctx unconditionally, triggering a NULL pointer dereference at address 0x0: BUG: kernel NULL pointer dereference, address: 0000000000000000 RIP: 0010:mt7921_channel_switch_rx_beacon+0x1f/0x100 [mt7921_common] The same missing guard exists in mt7925_channel_switch_rx_beacon(), which shares the same code pattern introduced by the same commit. Add an early-return NULL check for dev->new_ctx in both mt7921_channel_switch_rx_beacon() and mt7925_channel_switch_rx_beacon(). When new_ctx is NULL there is no pending channel switch to process, so returning immediately is the correct and safe action. Fixes: 8aa2f59260eb ("wifi: mt76: mt7921: introduce CSA support") Reported-by: Bongani Hlope Oops-Analysis: http://oops.fenrus.org/reports/lkml/20260502125824.425d7159@bongani-mini.home.org.za/report.html Link: https://lore.kernel.org/all/20260502125824.425d7159@bongani-mini.home.org.za/ Signed-off-by: Arjan van de Ven Cc: linux-wireless@vger.kernel.org Cc: linux-mediatek@lists.infradead.org Cc: Felix Fietkau Cc: Lorenzo Bianconi Cc: Ryder Lee Link: https://patch.msgid.link/20260504145107.1329197-1-arjan@linux.intel.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7921/main.c | 3 +++ drivers/net/wireless/mediatek/mt76/mt7925/main.c | 3 +++ 2 files changed, 6 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/main.c b/drivers/net/wireless/mediatek/mt76/mt7921/main.c index 3d74fabe7408..a326f4c95c7c 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/main.c @@ -1508,6 +1508,9 @@ static void mt7921_channel_switch_rx_beacon(struct ieee80211_hw *hw, struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv; u16 beacon_interval = vif->bss_conf.beacon_int; + if (!dev->new_ctx) + return; + if (cfg80211_chandef_identical(&chsw->chandef, &dev->new_ctx->def) && chsw->count) { diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index 73d3722739d0..ec6eb2897aec 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -2400,6 +2400,9 @@ static void mt7925_channel_switch_rx_beacon(struct ieee80211_hw *hw, if (ieee80211_vif_is_mld(vif)) return; + if (!dev->new_ctx) + return; + beacon_interval = vif->bss_conf.beacon_int; if (cfg80211_chandef_identical(&chsw->chandef, From 5b7154f934c4c1b86e0fbfd95ad570a25bd08662 Mon Sep 17 00:00:00 2001 From: ElXreno Date: Wed, 6 May 2026 04:39:15 +0300 Subject: [PATCH 05/71] wifi: mt76: route TDLS-peer frames as 3-addr non-DS in HW encap With HW TX encap offload enabled, the mt76 firmware builds the 802.11 header for the 802.3 frame using the per-WCID context. For a STATION vif the HDR_TRANS TLV currently sets ToDS=1, which makes the firmware default to the BSSID as A1 and emit STA->AP-formatted frames regardless of which peer the WCID points to. For TDLS-paired peers this is wrong. Data frames go on air addressed to the AP, the AP MAC-ACKs and silently drops them per IEEE 802.11z (an AP must not forward to a TDLS-paired peer). Management and control frames bypass the HW encap path and still reach the peer; only user data fails. Add MT_WCID_FLAG_TDLS_PEER, set it in mt7915, mt7921, mt7925 and mt7996 sta-add paths when sta->tdls is true, and override the HDR_TRANS TLV in mt76_connac_mcu_wtbl_hdr_trans_tlv() (Connac2 - mt7915 / mt7921 / mt7922), mt7925_mcu_sta_hdr_trans_tlv() (mt7925) and mt7996_mcu_sta_hdr_trans_tlv() (mt7996) to set ToDS=0, FromDS=0 when the flag is set. The 3-addr non-DS form matches what 802.11z uses for direct links; the firmware then constructs the frame with A1=peer rather than A1=BSSID. HW encap offload remains enabled for AP and any non-TDLS traffic. Verified on mt7925e + Samsung S938B over a 5 GHz HE 80 MHz channel with iperf3 -t 30 to the TDLS peer: before fix: over the TDLS direct link, 7 TDLS Setup action frames and 3 RTS frames reach the peer; 0 QoS Data frames make it through (mgmt/control paths bypass HW encap, the data path does not). iperf3 stalls. after fix: 2.90 GBytes transferred at 830 Mbit/s sustained, 0 TCP retransmits. mt7915, mt7921, mt7922 and mt7996 are not regression-tested in this change for lack of hardware. Their HDR_TRANS handling mirrors the verified mt7925 change; the firmware behavior is shared across these chips. Signed-off-by: ElXreno Assisted-by: Claude:claude-opus-4-7 bpftrace tcpdump Link: https://patch.msgid.link/20260506-mt7925-tdls-fixes-v2-1-46aa826ba8bb@gmail.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt76.h | 1 + drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c | 5 +++++ drivers/net/wireless/mediatek/mt76/mt7915/main.c | 3 +++ drivers/net/wireless/mediatek/mt76/mt7921/main.c | 3 +++ drivers/net/wireless/mediatek/mt76/mt7925/main.c | 3 +++ drivers/net/wireless/mediatek/mt76/mt7925/mcu.c | 5 +++++ drivers/net/wireless/mediatek/mt76/mt7996/main.c | 3 +++ drivers/net/wireless/mediatek/mt76/mt7996/mcu.c | 5 +++++ 8 files changed, 28 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index 527bef97e122..07955555f84d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -361,6 +361,7 @@ enum mt76_wcid_flags { MT_WCID_FLAG_PS, MT_WCID_FLAG_4ADDR, MT_WCID_FLAG_HDR_TRANS, + MT_WCID_FLAG_TDLS_PEER, }; #define MT76_N_WCIDS 1088 diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c index 89bd52ea8bf7..f61d0625ef51 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c @@ -491,6 +491,11 @@ void mt76_connac_mcu_wtbl_hdr_trans_tlv(struct sk_buff *skb, htr->to_ds = true; htr->from_ds = true; } + + if (test_bit(MT_WCID_FLAG_TDLS_PEER, &wcid->flags)) { + htr->to_ds = false; + htr->from_ds = false; + } } EXPORT_SYMBOL_GPL(mt76_connac_mcu_wtbl_hdr_trans_tlv); diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/main.c b/drivers/net/wireless/mediatek/mt76/mt7915/main.c index e1d83052aa6d..51643a48ed15 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/main.c @@ -760,6 +760,9 @@ int mt7915_mac_sta_add(struct mt76_dev *mdev, struct ieee80211_vif *vif, msta->wcid.phy_idx = ext_phy; msta->jiffies = jiffies; + if (sta->tdls) + set_bit(MT_WCID_FLAG_TDLS_PEER, &msta->wcid.flags); + ewma_avg_signal_init(&msta->avg_ack_signal); mt7915_mac_wtbl_update(dev, idx, diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/main.c b/drivers/net/wireless/mediatek/mt76/mt7921/main.c index a326f4c95c7c..adaf9a53f296 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/main.c @@ -828,6 +828,9 @@ int mt7921_mac_sta_add(struct mt76_dev *mdev, struct ieee80211_vif *vif, msta->deflink.last_txs = jiffies; msta->deflink.sta = msta; + if (sta->tdls) + set_bit(MT_WCID_FLAG_TDLS_PEER, &msta->deflink.wcid.flags); + ret = mt76_connac_pm_wake(&dev->mphy, &dev->pm); if (ret) return ret; diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index ec6eb2897aec..30832c4bd303 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -887,6 +887,9 @@ static int mt7925_mac_link_sta_add(struct mt76_dev *mdev, mlink->wcid.link_valid = !!link_sta->sta->valid_links; mlink->sta = msta; + if (link_sta->sta->tdls) + set_bit(MT_WCID_FLAG_TDLS_PEER, &mlink->wcid.flags); + wcid = &mlink->wcid; ewma_signal_init(&wcid->rssi); rcu_assign_pointer(dev->mt76.wcid[wcid->idx], wcid); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c index 22bad3cba8df..333cacfaca9c 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c @@ -1091,6 +1091,11 @@ mt7925_mcu_sta_hdr_trans_tlv(struct sk_buff *skb, hdr_trans->to_ds = true; hdr_trans->from_ds = true; } + + if (test_bit(MT_WCID_FLAG_TDLS_PEER, &wcid->flags)) { + hdr_trans->to_ds = false; + hdr_trans->from_ds = false; + } } int mt7925_mcu_wtbl_update_hdr_trans(struct mt792x_dev *dev, diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index a8a6552d49f6..9b8db6efb5ac 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1164,6 +1164,9 @@ mt7996_mac_sta_init_link(struct mt7996_dev *dev, msta_link->wcid.link_valid = !!sta->valid_links; msta_link->wcid.def_wcid = &msta->deflink.wcid; + if (link_sta->sta->tdls) + set_bit(MT_WCID_FLAG_TDLS_PEER, &msta_link->wcid.flags); + ewma_avg_signal_init(&msta_link->avg_ack_signal); ewma_signal_init(&msta_link->wcid.rssi); diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index 16420375112d..6aaf3ed94221 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -2139,6 +2139,11 @@ mt7996_mcu_sta_hdr_trans_tlv(struct mt7996_dev *dev, struct sk_buff *skb, hdr_trans->from_ds = true; hdr_trans->mesh = true; } + + if (test_bit(MT_WCID_FLAG_TDLS_PEER, &wcid->flags)) { + hdr_trans->to_ds = false; + hdr_trans->from_ds = false; + } } static enum mcu_mmps_mode From 37d65384aa6f9cbe45f4052b13b378af1aab3e95 Mon Sep 17 00:00:00 2001 From: ElXreno Date: Wed, 6 May 2026 04:39:16 +0300 Subject: [PATCH 06/71] wifi: mt76: mt7925: don't disable AP BSS when removing TDLS peer On a STATION vif, removing a TDLS peer takes the mt7925_mac_sta_remove -> mt7925_mac_sta_remove_links path. The first loop in that function calls mt7925_mcu_add_bss_info(..., enable=false) for every link of the station being removed. For a non-MLO STATION vif there is exactly one link, link 0, whose bss_conf is the AP's. TDLS peers do not have their own bss_conf - they share the AP's BSS. The result is that every TDLS peer teardown sends a BSS_INFO_UPDATE with enable=0 for the AP's BSS to the firmware, which wipes the AP-side rate-control context. The connection stays associated and TX from the host still works at the negotiated rate, but the AP's downlink to us collapses to the lowest mandatory OFDM rate (HE-MCS 0 / 6 Mbit/s OFDM) and only slowly recovers as rate adaptation re-learns under sustained traffic. With brief or bursty traffic the link can stay at 6-72 Mbit/s indefinitely, requiring a manual reconnect. mt7925_mac_link_sta_remove() already guards its own mt7925_mcu_add_bss_info(..., false) call with "vif->type == NL80211_IFTYPE_STATION && !link_sta->sta->tdls". Add the equivalent guard at the top of the cleanup loop in mt7925_mac_sta_remove_links(), above the link_sta / link_conf / mlink / mconf lookups, so TDLS peer teardown skips the loop body entirely without doing the per-link work that would just be thrown away. Verified on mt7925e by triggering Samsung-S938B auto-TDLS via iperf3 and watching iw rx bitrate after teardown: Before: rx bitrate collapses to 6.0-72.0 Mbit/s, oscillates 17/72/ 137/288/432 Mbit/s for 30+ seconds, no full recovery without a manual reassoc. After: rx bitrate stays at 1200.9 Mbit/s HE-MCS 11 NSS 2 80 MHz across the entire TDLS lifecycle. bpftrace confirms a single mt7925_mcu_add_bss_info(enable=0) call per teardown before the fix; zero such calls after. Fixes: 3878b4333602 ("wifi: mt76: mt7925: update mt7925_mac_link_sta_[add, assoc, remove] for MLO") Cc: stable@vger.kernel.org Signed-off-by: ElXreno Assisted-by: Claude:claude-opus-4-7 bpftrace Link: https://patch.msgid.link/20260506-mt7925-tdls-fixes-v2-2-46aa826ba8bb@gmail.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/main.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index 30832c4bd303..dcd2f07318fc 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -1268,6 +1268,9 @@ mt7925_mac_sta_remove_links(struct mt792x_dev *dev, struct ieee80211_vif *vif, if (vif->type == NL80211_IFTYPE_AP) break; + if (vif->type == NL80211_IFTYPE_STATION && sta->tdls) + continue; + link_sta = mt792x_sta_to_link_sta(vif, sta, link_id); if (!link_sta) continue; From 7fae097aa9a56c30febf539d72ef3773165d3aa3 Mon Sep 17 00:00:00 2001 From: Rajat Gupta Date: Wed, 6 May 2026 21:35:31 -0700 Subject: [PATCH 07/71] wifi: mt76: use kfree_rcu for offchannel link in mt76_put_vif_phy_link mt76_put_vif_phy_link() frees the offchannel mlink with plain kfree() after rcu_assign_pointer(NULL). However, rcu_assign_pointer only prevents future RCU readers from obtaining the pointer -- it does not wait for existing readers that already hold it via rcu_dereference. The TX datapath (e.g. mt7996_mac_write_txwi) dereferences mlink->wcid and mlink->idx under rcu_read_lock. If a TX softirq obtained the pointer via rcu_dereference just before the NULL assignment, it will dereference freed memory after the kfree. struct mt76_vif_link already contains an rcu_head field that is unused at this free site -- a developer oversight, since the adjacent kfree_rcu_mightsleep call for rx_sc in the same function shows the pattern was understood. Replace kfree(mlink) with kfree_rcu(mlink, rcu_head). Fixes: a8f424c1287c ("wifi: mt76: add multi-radio remain_on_channel functions") Signed-off-by: Rajat Gupta Link: https://patch.msgid.link/20260507043531.492-1-rajat.gupta@oss.qualcomm.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/channel.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/channel.c b/drivers/net/wireless/mediatek/mt76/channel.c index 05eee64706ea..6edcb3b8f279 100644 --- a/drivers/net/wireless/mediatek/mt76/channel.c +++ b/drivers/net/wireless/mediatek/mt76/channel.c @@ -307,7 +307,7 @@ void mt76_put_vif_phy_link(struct mt76_phy *phy, struct ieee80211_vif *vif, rcu_assign_pointer(mvif->offchannel_link, NULL); dev->drv->vif_link_remove(phy, vif, &vif->bss_conf, mlink); - kfree(mlink); + kfree_rcu(mlink, rcu_head); } void mt76_roc_complete(struct mt76_phy *phy) From b350ad117b4266f03e22c68b8057e98035aa059a Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 30 Apr 2026 10:33:31 +0200 Subject: [PATCH 08/71] wifi: mt76: drop redundant device reference Driver core holds a reference to the USB interface and its parent USB device while the interface is bound to a driver and there is no need to take additional references unless the structures are needed after disconnect. Drop the redundant device reference to reduce cargo culting, make it easier to spot drivers where an extra reference is needed, and reduce the risk of memory leaks when drivers fail to release it. Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260430083335.215239-2-johan@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7615/usb.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7615/usb.c b/drivers/net/wireless/mediatek/mt76/mt7615/usb.c index d91feffadda9..bab7b91f14be 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7615/usb.c +++ b/drivers/net/wireless/mediatek/mt76/mt7615/usb.c @@ -151,7 +151,6 @@ static int mt7663u_probe(struct usb_interface *usb_intf, return -ENOMEM; dev = container_of(mdev, struct mt7615_dev, mt76); - udev = usb_get_dev(udev); usb_reset_device(udev); usb_set_intfdata(usb_intf, dev); @@ -193,7 +192,6 @@ static int mt7663u_probe(struct usb_interface *usb_intf, error: mt76u_queues_deinit(&dev->mt76); usb_set_intfdata(usb_intf, NULL); - usb_put_dev(interface_to_usbdev(usb_intf)); mt76_free_device(&dev->mt76); @@ -211,7 +209,6 @@ static void mt7663u_disconnect(struct usb_interface *usb_intf) mt7663u_cleanup(dev); usb_set_intfdata(usb_intf, NULL); - usb_put_dev(interface_to_usbdev(usb_intf)); mt76_free_device(&dev->mt76); } From 7f81b0214c4ee4fb39fbc92ca5aa96dfe890d2ea Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 30 Apr 2026 10:33:32 +0200 Subject: [PATCH 09/71] wifi: mt76x0u: drop redundant device reference Driver core holds a reference to the USB interface and its parent USB device while the interface is bound to a driver and there is no need to take additional references unless the structures are needed after disconnect. Drop the redundant device reference to reduce cargo culting, make it easier to spot drivers where an extra reference is needed, and reduce the risk of memory leaks when drivers fail to release it. Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260430083335.215239-3-johan@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt76x0/usb.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76x0/usb.c b/drivers/net/wireless/mediatek/mt76/mt76x0/usb.c index 90e5666c0857..2acce121fb14 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76x0/usb.c +++ b/drivers/net/wireless/mediatek/mt76/mt76x0/usb.c @@ -245,7 +245,6 @@ static int mt76x0u_probe(struct usb_interface *usb_intf, if (id->driver_info) dev->no_2ghz = true; - usb_dev = usb_get_dev(usb_dev); usb_reset_device(usb_dev); usb_set_intfdata(usb_intf, dev); @@ -284,7 +283,6 @@ static int mt76x0u_probe(struct usb_interface *usb_intf, err: usb_set_intfdata(usb_intf, NULL); - usb_put_dev(interface_to_usbdev(usb_intf)); mt76u_queues_deinit(&dev->mt76); mt76_free_device(&dev->mt76); @@ -303,7 +301,6 @@ static void mt76x0_disconnect(struct usb_interface *usb_intf) mt76x0u_cleanup(dev); usb_set_intfdata(usb_intf, NULL); - usb_put_dev(interface_to_usbdev(usb_intf)); mt76_free_device(&dev->mt76); } From 70a05728dbb68ab5b2917fee5bbb1f0b6c498225 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 30 Apr 2026 10:33:33 +0200 Subject: [PATCH 10/71] wifi: mt76x2u: drop redundant device reference Driver core holds a reference to the USB interface and its parent USB device while the interface is bound to a driver and there is no need to take additional references unless the structures are needed after disconnect. Drop the redundant device reference to reduce cargo culting, make it easier to spot drivers where an extra reference is needed, and reduce the risk of memory leaks when drivers fail to release it. Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260430083335.215239-4-johan@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt76x2/usb.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76x2/usb.c b/drivers/net/wireless/mediatek/mt76/mt76x2/usb.c index 01cb3b2830f3..8af360bca643 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76x2/usb.c +++ b/drivers/net/wireless/mediatek/mt76/mt76x2/usb.c @@ -57,7 +57,6 @@ static int mt76x2u_probe(struct usb_interface *intf, dev = container_of(mdev, struct mt76x02_dev, mt76); - udev = usb_get_dev(udev); usb_reset_device(udev); usb_set_intfdata(intf, dev); @@ -84,14 +83,12 @@ static int mt76x2u_probe(struct usb_interface *intf, mt76u_queues_deinit(&dev->mt76); mt76_free_device(&dev->mt76); usb_set_intfdata(intf, NULL); - usb_put_dev(udev); return err; } static void mt76x2u_disconnect(struct usb_interface *intf) { - struct usb_device *udev = interface_to_usbdev(intf); struct mt76x02_dev *dev = usb_get_intfdata(intf); struct ieee80211_hw *hw = mt76_hw(dev); @@ -100,7 +97,6 @@ static void mt76x2u_disconnect(struct usb_interface *intf) mt76x2u_cleanup(dev); mt76_free_device(&dev->mt76); usb_set_intfdata(intf, NULL); - usb_put_dev(udev); } static int __maybe_unused mt76x2u_suspend(struct usb_interface *intf, From d246ff8e5958ffd407361a33fdf0021f000083ac Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 30 Apr 2026 10:33:34 +0200 Subject: [PATCH 11/71] wifi: mt76: mt792xu: drop redundant device reference Driver core holds a reference to the USB interface and its parent USB device while the interface is bound to a driver and there is no need to take additional references unless the structures are needed after disconnect. Drop the redundant device reference to reduce cargo culting, make it easier to spot drivers where an extra reference is needed, and reduce the risk of memory leaks when drivers fail to release it. Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260430083335.215239-5-johan@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7921/usb.c | 2 -- drivers/net/wireless/mediatek/mt76/mt7925/usb.c | 2 -- drivers/net/wireless/mediatek/mt76/mt792x_usb.c | 1 - 3 files changed, 5 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/usb.c b/drivers/net/wireless/mediatek/mt76/mt7921/usb.c index 17057e68bf21..9bfc234f306f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/usb.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/usb.c @@ -197,7 +197,6 @@ static int mt7921u_probe(struct usb_interface *usb_intf, dev->fw_features = features; dev->hif_ops = &hif_ops; - udev = usb_get_dev(udev); usb_reset_device(udev); usb_set_intfdata(usb_intf, dev); @@ -246,7 +245,6 @@ static int mt7921u_probe(struct usb_interface *usb_intf, mt76u_queues_deinit(&dev->mt76); usb_set_intfdata(usb_intf, NULL); - usb_put_dev(interface_to_usbdev(usb_intf)); mt76_free_device(&dev->mt76); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/usb.c b/drivers/net/wireless/mediatek/mt76/mt7925/usb.c index d9968f03856d..84bcebbf009a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/usb.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/usb.c @@ -185,7 +185,6 @@ static int mt7925u_probe(struct usb_interface *usb_intf, dev->fw_features = features; dev->hif_ops = &hif_ops; - udev = usb_get_dev(udev); usb_reset_device(udev); usb_set_intfdata(usb_intf, dev); @@ -234,7 +233,6 @@ static int mt7925u_probe(struct usb_interface *usb_intf, mt76u_queues_deinit(&dev->mt76); usb_set_intfdata(usb_intf, NULL); - usb_put_dev(interface_to_usbdev(usb_intf)); mt76_free_device(&dev->mt76); diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_usb.c b/drivers/net/wireless/mediatek/mt76/mt792x_usb.c index 47827d1c5ccb..0d9dbd0aaf9e 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_usb.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_usb.c @@ -341,7 +341,6 @@ void mt792xu_disconnect(struct usb_interface *usb_intf) mt792xu_cleanup(dev); usb_set_intfdata(usb_intf, NULL); - usb_put_dev(interface_to_usbdev(usb_intf)); mt76_free_device(&dev->mt76); } From 2172c96a1eb495966923ebbbe5965e3158875534 Mon Sep 17 00:00:00 2001 From: Johan Hovold Date: Thu, 30 Apr 2026 10:33:35 +0200 Subject: [PATCH 12/71] wifi: mt7601u: drop redundant device reference Driver core holds a reference to the USB interface and its parent USB device while the interface is bound to a driver and there is no need to take additional references unless the structures are needed after disconnect. Drop the redundant device reference to reduce cargo culting, make it easier to spot drivers where an extra reference is needed, and reduce the risk of memory leaks when drivers fail to release it. Signed-off-by: Johan Hovold Link: https://patch.msgid.link/20260430083335.215239-6-johan@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt7601u/usb.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt7601u/usb.c b/drivers/net/wireless/mediatek/mt7601u/usb.c index c41ae251cb95..9306870cbc91 100644 --- a/drivers/net/wireless/mediatek/mt7601u/usb.c +++ b/drivers/net/wireless/mediatek/mt7601u/usb.c @@ -274,7 +274,6 @@ static int mt7601u_probe(struct usb_interface *usb_intf, if (!dev) return -ENOMEM; - usb_dev = usb_get_dev(usb_dev); usb_reset_device(usb_dev); usb_set_intfdata(usb_intf, dev); @@ -319,7 +318,6 @@ static int mt7601u_probe(struct usb_interface *usb_intf, mt7601u_cleanup(dev); err: usb_set_intfdata(usb_intf, NULL); - usb_put_dev(interface_to_usbdev(usb_intf)); destroy_workqueue(dev->stat_wq); ieee80211_free_hw(dev->hw); @@ -334,7 +332,6 @@ static void mt7601u_disconnect(struct usb_interface *usb_intf) mt7601u_cleanup(dev); usb_set_intfdata(usb_intf, NULL); - usb_put_dev(interface_to_usbdev(usb_intf)); destroy_workqueue(dev->stat_wq); ieee80211_free_hw(dev->hw); From 7ec087fef32a88410488b764b0f5eef68e51175f Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Tue, 24 Mar 2026 15:49:03 +0000 Subject: [PATCH 13/71] wifi: mt76: mt7996: fix out-of-bounds array access during hardware restart During hardware restart, link_id can be IEEE80211_LINK_UNSPECIFIED, causing an out-of-bounds array access on msta->link[]. Add mt7996_sta_link() and mt7996_sta_link_protected() helper functions for accessing sta links with proper RCU handling and bounds checking. Use them for any sta link RCU access. Reported-by: Chad Monroe Link: https://patch.msgid.link/20260324154904.2555603-1-nbd@nbd.name Signed-off-by: Felix Fietkau --- .../wireless/mediatek/mt76/mt7996/debugfs.c | 4 ++-- .../net/wireless/mediatek/mt76/mt7996/mac.c | 6 ++--- .../net/wireless/mediatek/mt76/mt7996/main.c | 17 +++++++------- .../net/wireless/mediatek/mt76/mt7996/mcu.c | 22 +++++++++---------- .../wireless/mediatek/mt76/mt7996/mt7996.h | 19 ++++++++++++++++ 5 files changed, 43 insertions(+), 25 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/debugfs.c b/drivers/net/wireless/mediatek/mt76/mt7996/debugfs.c index 34af800964d1..ef9a9204adf5 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/debugfs.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/debugfs.c @@ -664,7 +664,7 @@ mt7996_sta_hw_queue_read(void *data, struct ieee80211_sta *sta) if (!mlink) continue; - msta_link = rcu_dereference(msta->link[link_id]); + msta_link = mt7996_sta_link(msta, link_id); if (!msta_link) continue; @@ -1042,7 +1042,7 @@ static ssize_t mt7996_link_sta_fixed_rate_set(struct file *file, mutex_lock(&dev->mt76.mutex); - msta_link = mt76_dereference(msta->link[link_sta->link_id], &dev->mt76); + msta_link = mt7996_sta_link_protected(dev, msta, link_sta->link_id); if (!msta_link) { ret = -EINVAL; goto out; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index e2a83da3a09c..c98446057282 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -48,7 +48,7 @@ static struct mt76_wcid *mt7996_rx_get_wcid(struct mt7996_dev *dev, if (mlink->band_idx != band_idx) continue; - msta_link = rcu_dereference(msta->link[i]); + msta_link = mt7996_sta_link(msta, i); break; } @@ -1038,7 +1038,7 @@ int mt7996_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr, if (link_id != wcid->link_id && link_id != IEEE80211_LINK_UNSPECIFIED) { if (msta) { struct mt7996_sta_link *msta_link = - rcu_dereference(msta->link[link_id]); + mt7996_sta_link(msta, link_id); if (msta_link) wcid = &msta_link->wcid; @@ -1346,7 +1346,7 @@ mt7996_mac_tx_free(struct mt7996_dev *dev, void *data, int len) IEEE80211_MLD_MAX_NUM_LINKS) { struct mt7996_sta_link *msta_link; - msta_link = rcu_dereference(msta->link[id]); + msta_link = mt7996_sta_link(msta, id); if (!msta_link) continue; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 9b8db6efb5ac..b3cbf68bb53d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -207,8 +207,7 @@ mt7996_set_hw_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, struct mt7996_sta *msta; msta = (struct mt7996_sta *)sta->drv_priv; - msta_link = mt76_dereference(msta->link[link_id], - &dev->mt76); + msta_link = mt7996_sta_link_protected(dev, msta, link_id); if (!msta_link) return 0; @@ -1384,7 +1383,7 @@ mt7996_mac_sta_event(struct mt7996_dev *dev, struct ieee80211_vif *vif, if (!link) continue; - msta_link = mt76_dereference(msta->link[link_id], &dev->mt76); + msta_link = mt7996_sta_link_protected(dev, msta, link_id); if (!msta_link) continue; @@ -1576,7 +1575,7 @@ static void mt7996_tx(struct ieee80211_hw *hw, if (msta) { struct mt7996_sta_link *msta_link; - msta_link = rcu_dereference(msta->link[link_id]); + msta_link = mt7996_sta_link(msta, link_id); if (msta_link) wcid = &msta_link->wcid; } @@ -1947,7 +1946,7 @@ static void mt7996_link_sta_rc_update(struct ieee80211_hw *hw, rcu_read_lock(); - msta_link = rcu_dereference(msta->link[link_sta->link_id]); + msta_link = mt7996_sta_link(msta, link_sta->link_id); if (msta_link) { struct mt7996_dev *dev = mt7996_hw_dev(hw); @@ -1964,7 +1963,7 @@ static void mt7996_sta_rate_ctrl_update(void *data, struct ieee80211_sta *sta) struct mt7996_sta_link *msta_link; u32 *changed = data; - msta_link = rcu_dereference(msta->link[msta->deflink_id]); + msta_link = mt7996_sta_link(msta, msta->deflink_id); if (msta_link) mt7996_link_rate_ctrl_update(&changed, msta_link); } @@ -2014,7 +2013,7 @@ static void mt7996_sta_set_4addr(struct ieee80211_hw *hw, if (!link) continue; - msta_link = mt76_dereference(msta->link[link_id], &dev->mt76); + msta_link = mt7996_sta_link_protected(dev, msta, link_id); if (!msta_link) continue; @@ -2052,7 +2051,7 @@ static void mt7996_sta_set_decap_offload(struct ieee80211_hw *hw, if (!link) continue; - msta_link = mt76_dereference(msta->link[link_id], &dev->mt76); + msta_link = mt7996_sta_link_protected(dev, msta, link_id); if (!msta_link) continue; @@ -2392,7 +2391,7 @@ mt7996_net_fill_forward_path(struct ieee80211_hw *hw, if (!link) return -EIO; - msta_link = rcu_dereference(msta->link[msta->deflink_id]); + msta_link = mt7996_sta_link(msta, msta->deflink_id); if (!msta_link) return -EIO; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index 6aaf3ed94221..8be40d60ad29 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -1136,7 +1136,7 @@ mt7996_mcu_bss_basic_tlv(struct sk_buff *skb, struct mt7996_sta_link *msta_link; int link_id = link_conf->link_id; - msta_link = rcu_dereference(msta->link[link_id]); + msta_link = mt7996_sta_link(msta, link_id); if (msta_link) sta_wlan_idx = msta_link->wcid.idx; } @@ -1429,7 +1429,7 @@ int mt7996_mcu_add_tx_ba(struct mt7996_dev *dev, struct mt7996_sta_link *msta_link; struct mt7996_vif_link *link; - msta_link = mt76_dereference(msta->link[link_id], &dev->mt76); + msta_link = mt7996_sta_link_protected(dev, msta, link_id); if (!msta_link) continue; @@ -1463,7 +1463,7 @@ int mt7996_mcu_add_rx_ba(struct mt7996_dev *dev, struct mt7996_sta_link *msta_link; struct mt7996_vif_link *link; - msta_link = mt76_dereference(msta->link[link_id], &dev->mt76); + msta_link = mt7996_sta_link_protected(dev, msta, link_id); if (!msta_link) continue; @@ -2205,7 +2205,7 @@ int mt7996_mcu_set_fixed_field(struct mt7996_dev *dev, struct mt7996_sta *msta, if (!mlink) goto error_unlock; - msta_link = rcu_dereference(msta->link[link_id]); + msta_link = mt7996_sta_link(msta, link_id); if (!msta_link) goto error_unlock; @@ -2295,7 +2295,7 @@ mt7996_mcu_add_rate_ctrl_fixed(struct mt7996_dev *dev, struct mt7996_sta *msta, if (!link) goto error_unlock; - msta_link = rcu_dereference(msta->link[link_id]); + msta_link = mt7996_sta_link(msta, link_id); if (!msta_link) goto error_unlock; @@ -2513,7 +2513,7 @@ int mt7996_mcu_add_rate_ctrl(struct mt7996_dev *dev, struct mt7996_sta *msta, if (!link) goto error_unlock; - msta_link = rcu_dereference(msta->link[link_id]); + msta_link = mt7996_sta_link(msta, link_id); if (!msta_link) goto error_unlock; @@ -2668,7 +2668,7 @@ mt7996_mcu_sta_mld_setup_tlv(struct mt7996_dev *dev, struct sk_buff *skb, unsigned int link_id; struct tlv *tlv; - msta_link = mt76_dereference(msta->link[msta->deflink_id], &dev->mt76); + msta_link = mt7996_sta_link_protected(dev, msta, msta->deflink_id); if (!msta_link) return; @@ -2682,8 +2682,8 @@ mt7996_mcu_sta_mld_setup_tlv(struct mt7996_dev *dev, struct sk_buff *skb, mld_setup->primary_id = cpu_to_le16(msta_link->wcid.idx); if (nlinks > 1) { - msta_link = mt76_dereference(msta->link[msta->seclink_id], - &dev->mt76); + msta_link = mt7996_sta_link_protected(dev, msta, + msta->seclink_id); if (!msta_link) return; } @@ -2694,7 +2694,7 @@ mt7996_mcu_sta_mld_setup_tlv(struct mt7996_dev *dev, struct sk_buff *skb, for_each_sta_active_link(vif, sta, link_sta, link_id) { struct mt7996_vif_link *link; - msta_link = mt76_dereference(msta->link[link_id], &dev->mt76); + msta_link = mt7996_sta_link_protected(dev, msta, link_id); if (!msta_link) continue; @@ -2842,7 +2842,7 @@ void mt7996_mcu_update_sta_rec_bw(void *data, struct ieee80211_sta *sta) if (!link_sta) return; - msta_link = mt76_dereference(msta->link[link_id], &dev->mt76); + msta_link = mt7996_sta_link_protected(dev, msta, link_id); if (!msta_link) return; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index bdcf72457954..0dc4198fcf8b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -642,6 +642,25 @@ mt7996_vif_conf_link(struct mt7996_dev *dev, struct ieee80211_vif *vif, link_conf); } +static inline struct mt7996_sta_link * +mt7996_sta_link(struct mt7996_sta *msta, u8 link_id) +{ + if (link_id >= IEEE80211_MLD_MAX_NUM_LINKS) + return NULL; + + return rcu_dereference(msta->link[link_id]); +} + +static inline struct mt7996_sta_link * +mt7996_sta_link_protected(struct mt7996_dev *dev, struct mt7996_sta *msta, + u8 link_id) +{ + if (link_id >= IEEE80211_MLD_MAX_NUM_LINKS) + return NULL; + + return mt76_dereference(msta->link[link_id], &dev->mt76); +} + #define mt7996_for_each_phy(dev, phy) \ for (int __i = 0; __i < ARRAY_SIZE((dev)->radio_phy); __i++) \ if (((phy) = (dev)->radio_phy[__i]) != NULL) From ac41612e0044fa29cf9bc45b6808dda6d87ac2da Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Tue, 24 Mar 2026 15:49:04 +0000 Subject: [PATCH 14/71] wifi: mt76: mt7996: add missing max_remain_on_channel_duration Having this unset breaks remain-on-channel and mgmt TX. Move setting it to mt76 core to keep it in one place. Fixes: 69d54ce7491d0 ("wifi: mt76: mt7996: switch to single multi-radio wiphy") Link: https://patch.msgid.link/20260324154904.2555603-2-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mac80211.c | 2 ++ drivers/net/wireless/mediatek/mt76/mt7615/init.c | 1 - drivers/net/wireless/mediatek/mt76/mt792x_core.c | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c index 4ae5e4715a9c..dd68776ada28 100644 --- a/drivers/net/wireless/mediatek/mt76/mac80211.c +++ b/drivers/net/wireless/mediatek/mt76/mac80211.c @@ -449,6 +449,8 @@ mt76_phy_init(struct mt76_phy *phy, struct ieee80211_hw *hw) wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_AIRTIME_FAIRNESS); wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_AQL); + if (!wiphy->max_remain_on_channel_duration) + wiphy->max_remain_on_channel_duration = 5000; if (!wiphy->available_antennas_tx) wiphy->available_antennas_tx = phy->antenna_mask; if (!wiphy->available_antennas_rx) diff --git a/drivers/net/wireless/mediatek/mt76/mt7615/init.c b/drivers/net/wireless/mediatek/mt76/mt7615/init.c index 42e11ba1206e..e437e088b2e9 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7615/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7615/init.c @@ -195,7 +195,6 @@ mt7615_check_offload_capability(struct mt7615_dev *dev) ieee80211_hw_set(hw, SUPPORTS_DYNAMIC_PS); wiphy->flags &= ~WIPHY_FLAG_4ADDR_STATION; - wiphy->max_remain_on_channel_duration = 5000; wiphy->features |= NL80211_FEATURE_SCHED_SCAN_RANDOM_MAC_ADDR | NL80211_FEATURE_SCAN_RANDOM_MAC_ADDR | WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL | diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_core.c b/drivers/net/wireless/mediatek/mt76/mt792x_core.c index 152cfcca2f90..5a5d7534830b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_core.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_core.c @@ -657,7 +657,6 @@ int mt792x_init_wiphy(struct ieee80211_hw *hw) BIT(NL80211_IFTYPE_P2P_CLIENT) | BIT(NL80211_IFTYPE_P2P_GO) | BIT(NL80211_IFTYPE_P2P_DEVICE); - wiphy->max_remain_on_channel_duration = 5000; wiphy->max_scan_ie_len = MT76_CONNAC_SCAN_IE_LEN; wiphy->max_scan_ssids = 4; wiphy->max_sched_scan_plan_interval = From 291b067a02b9c1fd272057ebf47c365260955fd2 Mon Sep 17 00:00:00 2001 From: Devin Wittmayer Date: Wed, 27 May 2026 07:47:35 -0700 Subject: [PATCH 15/71] wifi: mt76: mt7925: add Netgear A8500 USB device ID Add USB device ID for the Netgear A8500 (0846:9050) which uses the mt7925 chipset. Signed-off-by: Devin Wittmayer Link: https://patch.msgid.link/20260527144735.10254-1-lucid_duck@justthetip.ca Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/usb.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/usb.c b/drivers/net/wireless/mediatek/mt76/mt7925/usb.c index 84bcebbf009a..7071b6c93d14 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/usb.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/usb.c @@ -12,6 +12,9 @@ static const struct usb_device_id mt7925u_device_table[] = { { USB_DEVICE_AND_INTERFACE_INFO(0x0e8d, 0x7925, 0xff, 0xff, 0xff), .driver_info = (kernel_ulong_t)MT7925_FIRMWARE_WM }, + /* Netgear, Inc. A8500 */ + { USB_DEVICE_AND_INTERFACE_INFO(0x0846, 0x9050, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)MT7925_FIRMWARE_WM }, /* Netgear, Inc. A9000 */ { USB_DEVICE_AND_INTERFACE_INFO(0x0846, 0x9072, 0xff, 0xff, 0xff), .driver_info = (kernel_ulong_t)MT7925_FIRMWARE_WM }, From 6794edb55b5f5ef834e03b0b241b1a8b725f82c0 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Sat, 18 Apr 2026 20:04:16 +0200 Subject: [PATCH 16/71] wifi: mt76: mt7996: Fix NULL pointer dereference in mt7996_init_tx_queues() When MT76_NPU and CONFIG_NET_MEDIATEK_SOC_WED are enabled and mt76 detects properly the Airoha NPU SoC, mt7996_init_tx_queues() will dereference a NULL WED pointer. Fix the issue by always passing the WED pointer from mt7996_dma_init(). Fixes: cd7951f242a7 ("wifi: mt76: mt7996: Integrate MT7990 dma configuration for NPU") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260418-mt7996-dma-init-npu-fix-v1-1-6b8dcffbcb57@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/dma.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/dma.c b/drivers/net/wireless/mediatek/mt76/mt7996/dma.c index 8f5d297dafce..3d9353811a02 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/dma.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/dma.c @@ -683,7 +683,7 @@ int mt7996_dma_init(struct mt7996_dev *dev) ret = mt7996_init_tx_queues(&dev->phy, MT_TXQ_ID(0), MT7996_NPU_TX_RING_SIZE, MT_TXQ_RING_BASE(0) + hif1_ofs, - NULL); + wed); else ret = mt7996_init_tx_queues(&dev->phy, MT_TXQ_ID(dev->mphy.band_idx), From 9f1accf3069a0cd42c14ca6c7d44d5b17cc48a80 Mon Sep 17 00:00:00 2001 From: Javier Tia Date: Sat, 25 Apr 2026 14:49:51 -0500 Subject: [PATCH 17/71] wifi: mt76: mt7925: fix stale pointer comparisons in change_vif_links MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the error path of mt7925_change_vif_links(), the free: label iterates over link_ids to clean up, but compares against `mconf` and `mlink` which hold stale values from the last loop iteration rather than the current link_id being freed. Use array-indexed access (mconfs[link_id] / mlinks[link_id]) to compare against the correct per-link pointers. Fixes: 69acd6d910b0 ("wifi: mt76: mt7925: add mt7925_change_vif_links") Tested-by: Marcin FM Tested-by: Cristian-Florin Radoi Tested-by: George Salukvadze Tested-by: Evgeny Kapusta <3193631@gmail.com> Tested-by: Samu Toljamo Tested-by: Ariel Rosenfeld Tested-by: Chapuis Dario Tested-by: Thibaut François Tested-by: 张旭涵 Reviewed-by: Sean Wang Signed-off-by: Javier Tia Link: https://patch.msgid.link/20260425195011.790265-2-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/main.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index dcd2f07318fc..819fd104aa60 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -2185,9 +2185,9 @@ mt7925_change_vif_links(struct ieee80211_hw *hw, struct ieee80211_vif *vif, rcu_assign_pointer(mvif->link_conf[link_id], NULL); rcu_assign_pointer(mvif->sta.link[link_id], NULL); - if (mconf != &mvif->bss_conf) + if (mconfs[link_id] != &mvif->bss_conf) devm_kfree(dev->mt76.dev, mconfs[link_id]); - if (mlink != &mvif->sta.deflink) + if (mlinks[link_id] != &mvif->sta.deflink) devm_kfree(dev->mt76.dev, mlinks[link_id]); } From efc9a10d1de1763baecd2031c1c6dfe6aac9ac38 Mon Sep 17 00:00:00 2001 From: Javier Tia Date: Sat, 25 Apr 2026 14:49:52 -0500 Subject: [PATCH 18/71] wifi: mt76: mt7925: add 320MHz bandwidth to bss_rlm_tlv MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit bss_rlm_tlv() in mt7925_mcu_bss_rlm_tlv() has no case for NL80211_CHAN_WIDTH_320. When associated to a 320MHz BSS, the switch falls through to default and sends bw=0 (CMD_CBW_20MHZ) to firmware via BSS_RLM TLV. Firmware then configures the RX radio for 20MHz and cannot decode the AP's 320MHz frames, resulting in complete data path failure at 320MHz. Add the missing NL80211_CHAN_WIDTH_320 case with CMD_CBW_320MHZ and center_chan2. Tested on ASUS RT-BE92U: 320MHz throughput goes from 0 Mbps to 841 Mbps (iperf3 -t30 -P8), PHY 4803 Mbps EHT-MCS11. Reported-by: 张旭涵 Closes: https://github.com/openwrt/mt76/issues/927 Tested-by: 张旭涵 Tested-by: Marcin FM Tested-by: Cristian-Florin Radoi Tested-by: George Salukvadze Tested-by: Evgeny Kapusta <3193631@gmail.com> Tested-by: Samu Toljamo Tested-by: Ariel Rosenfeld Tested-by: Chapuis Dario Tested-by: Thibaut François Reviewed-by: Sean Wang Signed-off-by: Javier Tia Link: https://patch.msgid.link/20260425195011.790265-3-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/mcu.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c index 333cacfaca9c..d8a956967c0c 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c @@ -2339,6 +2339,10 @@ void mt7925_mcu_bss_rlm_tlv(struct sk_buff *skb, struct mt76_phy *phy, case NL80211_CHAN_WIDTH_160: req->bw = CMD_CBW_160MHZ; break; + case NL80211_CHAN_WIDTH_320: + req->bw = CMD_CBW_320MHZ; + req->center_chan2 = ieee80211_frequency_to_channel(freq2); + break; case NL80211_CHAN_WIDTH_5: req->bw = CMD_CBW_5MHZ; break; From 2237b7b332990f135c1f1550b45ac77b29bcbf7b Mon Sep 17 00:00:00 2001 From: Javier Tia Date: Sat, 25 Apr 2026 14:49:53 -0500 Subject: [PATCH 19/71] wifi: mt76: mt7925: handle 320MHz bandwidth in RXV and TXS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The RX vector (RXV) and TX status (TXS) parsing in mac.c lack handling for 320MHz channel width. When the hardware reports 320MHz in the bandwidth field, mt7925_mac_fill_rx_rate() returns -EINVAL and mt7925_mac_add_txs_skb() records no bandwidth stats. Add IEEE80211_STA_RX_BW_320 cases to both functions. The RXV parser also handles BW_320+1 since the hardware can report 320MHz in two adjacent encoding positions. Tested-by: Marcin FM Tested-by: Cristian-Florin Radoi Tested-by: George Salukvadze Tested-by: Evgeny Kapusta <3193631@gmail.com> Tested-by: Samu Toljamo Tested-by: Ariel Rosenfeld Tested-by: Chapuis Dario Tested-by: Thibaut François Tested-by: 张旭涵 Reviewed-by: Sean Wang Signed-off-by: Javier Tia Link: https://patch.msgid.link/20260425195011.790265-4-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/mac.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c index c47bd812b66b..d681005cc6ff 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c @@ -339,6 +339,11 @@ mt7925_mac_fill_rx_rate(struct mt792x_dev *dev, case IEEE80211_STA_RX_BW_160: status->bw = RATE_INFO_BW_160; break; + /* RXV can report 320 in two positions */ + case IEEE80211_STA_RX_BW_320: + case IEEE80211_STA_RX_BW_320 + 1: + status->bw = RATE_INFO_BW_320; + break; default: return -EINVAL; } @@ -997,6 +1002,10 @@ mt7925_mac_add_txs_skb(struct mt792x_dev *dev, struct mt76_wcid *wcid, stats->tx_mode[mode]++; switch (FIELD_GET(MT_TXS0_BW, txs)) { + case IEEE80211_STA_RX_BW_320: + rate.bw = RATE_INFO_BW_320; + stats->tx_bw[4]++; + break; case IEEE80211_STA_RX_BW_160: rate.bw = RATE_INFO_BW_160; stats->tx_bw[3]++; From e7bbd3d8b4f01473676da3aa8be17099e1604459 Mon Sep 17 00:00:00 2001 From: Javier Tia Date: Sat, 25 Apr 2026 14:49:54 -0500 Subject: [PATCH 20/71] wifi: mt76: mt7925: populate EHT 320MHz MCS map in sta_rec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sta_rec_eht structure has a mcs_map_bw320 field, and the channel width mapping includes NL80211_CHAN_WIDTH_320, but the 320MHz MCS/NSS map was never copied from the station's EHT capabilities to the MCU TLV. This prevents negotiation of 320MHz channel width even when both the hardware and firmware advertise support for it. Add the missing memcpy for the 320MHz MCS map, matching the existing pattern for BW20, BW80, and BW160. Tested-by: Marcin FM Tested-by: Cristian-Florin Radoi Tested-by: George Salukvadze Tested-by: Evgeny Kapusta <3193631@gmail.com> Tested-by: Samu Toljamo Tested-by: Ariel Rosenfeld Tested-by: Chapuis Dario Tested-by: Thibaut François Tested-by: 张旭涵 Reviewed-by: Sean Wang Signed-off-by: Javier Tia Link: https://patch.msgid.link/20260425195011.790265-5-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/mcu.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c index d8a956967c0c..db6df9da5b5d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c @@ -1672,6 +1672,7 @@ mt7925_mcu_sta_eht_tlv(struct sk_buff *skb, struct ieee80211_link_sta *link_sta) memcpy(eht->mcs_map_bw20, &mcs_map->only_20mhz, sizeof(eht->mcs_map_bw20)); memcpy(eht->mcs_map_bw80, &mcs_map->bw._80, sizeof(eht->mcs_map_bw80)); memcpy(eht->mcs_map_bw160, &mcs_map->bw._160, sizeof(eht->mcs_map_bw160)); + memcpy(eht->mcs_map_bw320, &mcs_map->bw._320, sizeof(eht->mcs_map_bw320)); } static void From 77833c57a33450c0409e4b90d7721d255ea23a9e Mon Sep 17 00:00:00 2001 From: Javier Tia Date: Sat, 25 Apr 2026 14:49:55 -0500 Subject: [PATCH 21/71] wifi: mt76: mt7925: advertise EHT 320MHz capabilities for 6GHz band MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mt7925_init_eht_caps() only populates EHT MCS/NSS maps for BW <= 80 and BW = 160, but never sets BW = 320. This means iw phy shows no 320MHz MCS map entries even though the hardware supports 320MHz operation in the 6GHz band. Add the missing 320MHz capability bits for 6GHz: - PHY_CAP0: IEEE80211_EHT_PHY_CAP0_320MHZ_IN_6GHZ - PHY_CAP1: beamformee SS for 320MHz - PHY_CAP2: sounding dimensions for 320MHz - PHY_CAP6: MCS15 support for 320MHz width - MCS/NSS: populate bw._320 maps for 6GHz band Introduce is_320mhz_supported() to gate 320MHz on MT7927 only, since MT7925 does not support 320MHz operation. Tested-by: Marcin FM Tested-by: Cristian-Florin Radoi Tested-by: George Salukvadze Tested-by: Evgeny Kapusta <3193631@gmail.com> Tested-by: Samu Toljamo Tested-by: Ariel Rosenfeld Tested-by: Chapuis Dario Tested-by: Thibaut François Tested-by: 张旭涵 Reviewed-by: Sean Wang Signed-off-by: Javier Tia Link: https://patch.msgid.link/20260425195011.790265-6-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt76_connac.h | 5 +++++ .../net/wireless/mediatek/mt76/mt7925/main.c | 22 ++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac.h b/drivers/net/wireless/mediatek/mt76/mt76_connac.h index 51423c7740bd..802cb26ed6e3 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac.h +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac.h @@ -177,6 +177,11 @@ static inline bool is_mt7925(struct mt76_dev *dev) return mt76_chip(dev) == 0x7925; } +static inline bool is_320mhz_supported(struct mt76_dev *dev) +{ + return mt76_chip(dev) == 0x7927; +} + static inline bool is_mt7920(struct mt76_dev *dev) { return mt76_chip(dev) == 0x7920; diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index 819fd104aa60..5c4bf6126a03 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -183,6 +183,10 @@ mt7925_init_eht_caps(struct mt792x_phy *phy, enum nl80211_band band, IEEE80211_EHT_PHY_CAP0_SU_BEAMFORMER | IEEE80211_EHT_PHY_CAP0_SU_BEAMFORMEE; + if (band == NL80211_BAND_6GHZ && is_320mhz_supported(&phy->dev->mt76)) + eht_cap_elem->phy_cap_info[0] |= + IEEE80211_EHT_PHY_CAP0_320MHZ_IN_6GHZ; + eht_cap_elem->phy_cap_info[0] |= u8_encode_bits(u8_get_bits(sts - 1, BIT(0)), IEEE80211_EHT_PHY_CAP0_BEAMFORMEE_SS_80MHZ_MASK); @@ -193,10 +197,20 @@ mt7925_init_eht_caps(struct mt792x_phy *phy, enum nl80211_band band, u8_encode_bits(sts - 1, IEEE80211_EHT_PHY_CAP1_BEAMFORMEE_SS_160MHZ_MASK); + if (band == NL80211_BAND_6GHZ && is_320mhz_supported(&phy->dev->mt76)) + eht_cap_elem->phy_cap_info[1] |= + u8_encode_bits(sts - 1, + IEEE80211_EHT_PHY_CAP1_BEAMFORMEE_SS_320MHZ_MASK); + eht_cap_elem->phy_cap_info[2] = u8_encode_bits(sts - 1, IEEE80211_EHT_PHY_CAP2_SOUNDING_DIM_80MHZ_MASK) | u8_encode_bits(sts - 1, IEEE80211_EHT_PHY_CAP2_SOUNDING_DIM_160MHZ_MASK); + if (band == NL80211_BAND_6GHZ && is_320mhz_supported(&phy->dev->mt76)) + eht_cap_elem->phy_cap_info[2] |= + u8_encode_bits(sts - 1, + IEEE80211_EHT_PHY_CAP2_SOUNDING_DIM_320MHZ_MASK); + eht_cap_elem->phy_cap_info[3] = IEEE80211_EHT_PHY_CAP3_NG_16_SU_FEEDBACK | IEEE80211_EHT_PHY_CAP3_NG_16_MU_FEEDBACK | @@ -217,7 +231,8 @@ mt7925_init_eht_caps(struct mt792x_phy *phy, enum nl80211_band band, u8_encode_bits(u8_get_bits(0x11, GENMASK(1, 0)), IEEE80211_EHT_PHY_CAP5_MAX_NUM_SUPP_EHT_LTF_MASK); - val = width == NL80211_CHAN_WIDTH_160 ? 0x7 : + val = width == NL80211_CHAN_WIDTH_320 ? 0xf : + width == NL80211_CHAN_WIDTH_160 ? 0x7 : width == NL80211_CHAN_WIDTH_80 ? 0x3 : 0x1; eht_cap_elem->phy_cap_info[6] = u8_encode_bits(u8_get_bits(0x11, GENMASK(4, 2)), @@ -239,6 +254,11 @@ mt7925_init_eht_caps(struct mt792x_phy *phy, enum nl80211_band band, eht_nss->bw._160.rx_tx_mcs9_max_nss = val; eht_nss->bw._160.rx_tx_mcs11_max_nss = val; eht_nss->bw._160.rx_tx_mcs13_max_nss = val; + if (band == NL80211_BAND_6GHZ && is_320mhz_supported(&phy->dev->mt76)) { + eht_nss->bw._320.rx_tx_mcs9_max_nss = val; + eht_nss->bw._320.rx_tx_mcs11_max_nss = val; + eht_nss->bw._320.rx_tx_mcs13_max_nss = val; + } } int mt7925_init_mlo_caps(struct mt792x_phy *phy) From 752003c6d9b8531a5f9005d42821ccc24b553fbc Mon Sep 17 00:00:00 2001 From: Javier Tia Date: Sat, 25 Apr 2026 14:49:56 -0500 Subject: [PATCH 22/71] wifi: mt76: mt7925: add MT7927 chip ID helpers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The MediaTek MT7927 (Filogic 380) combo chip uses MT7927 WiFi silicon that is architecturally compatible with MT7925. Extend is_mt7925() to match chip ID 0x7927, and add is_mt7927() for code paths that need MT7927-specific handling. Also add 0x7927 to is_mt76_fw_txp() to match MT7925's TXP format. Tested-by: Marcin FM Tested-by: Cristian-Florin Radoi Tested-by: George Salukvadze Tested-by: Evgeny Kapusta <3193631@gmail.com> Tested-by: Samu Toljamo Tested-by: Ariel Rosenfeld Tested-by: Chapuis Dario Tested-by: Thibaut François Tested-by: 张旭涵 Reviewed-by: Sean Wang Signed-off-by: Javier Tia Link: https://patch.msgid.link/20260425195011.790265-7-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt76_connac.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac.h b/drivers/net/wireless/mediatek/mt76/mt76_connac.h index 802cb26ed6e3..45479cc29134 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac.h +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac.h @@ -174,7 +174,12 @@ extern const struct wiphy_wowlan_support mt76_connac_wowlan_support; static inline bool is_mt7925(struct mt76_dev *dev) { - return mt76_chip(dev) == 0x7925; + return mt76_chip(dev) == 0x7925 || mt76_chip(dev) == 0x7927; +} + +static inline bool is_mt7927(struct mt76_dev *dev) +{ + return mt76_chip(dev) == 0x7927; } static inline bool is_320mhz_supported(struct mt76_dev *dev) @@ -284,6 +289,7 @@ static inline bool is_mt76_fw_txp(struct mt76_dev *dev) case 0x7922: case 0x7902: case 0x7925: + case 0x7927: case 0x7663: case 0x7622: return false; From 3be5a0d417da36ce7445a3738c9c161aaf769123 Mon Sep 17 00:00:00 2001 From: Javier Tia Date: Sat, 25 Apr 2026 14:49:57 -0500 Subject: [PATCH 23/71] wifi: mt76: mt7925: add MT7927 firmware paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add firmware path definitions for MT7927 WiFi firmware (WIFI_RAM_CODE and PATCH_MCU) and the corresponding MODULE_FIRMWARE declarations. Add MT7927 cases to mt792x_ram_name() and mt792x_patch_name() so the driver loads the correct firmware for the 0x7927 chip ID. PCI device table entries are deferred to a later patch to allow infrastructure setup before device enablement. Tested-by: Marcin FM Tested-by: Cristian-Florin Radoi Tested-by: George Salukvadze Tested-by: Evgeny Kapusta <3193631@gmail.com> Tested-by: Samu Toljamo Tested-by: Ariel Rosenfeld Tested-by: Chapuis Dario Tested-by: Thibaut François Tested-by: 张旭涵 Reviewed-by: Sean Wang Signed-off-by: Javier Tia Link: https://patch.msgid.link/20260425195011.790265-8-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/pci.c | 2 ++ drivers/net/wireless/mediatek/mt76/mt792x.h | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c index 48837723024d..76139a548785 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c @@ -635,6 +635,8 @@ module_pci_driver(mt7925_pci_driver); MODULE_DEVICE_TABLE(pci, mt7925_pci_device_table); MODULE_FIRMWARE(MT7925_FIRMWARE_WM); MODULE_FIRMWARE(MT7925_ROM_PATCH); +MODULE_FIRMWARE(MT7927_FIRMWARE_WM); +MODULE_FIRMWARE(MT7927_ROM_PATCH); MODULE_AUTHOR("Deren Wu "); MODULE_AUTHOR("Lorenzo Bianconi "); MODULE_DESCRIPTION("MediaTek MT7925E (PCIe) wireless driver"); diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h index 4ff93f2cd624..09840483fe2a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x.h +++ b/drivers/net/wireless/mediatek/mt76/mt792x.h @@ -46,12 +46,14 @@ #define MT7921_FIRMWARE_WM "mediatek/WIFI_RAM_CODE_MT7961_1.bin" #define MT7922_FIRMWARE_WM "mediatek/WIFI_RAM_CODE_MT7922_1.bin" #define MT7925_FIRMWARE_WM "mediatek/mt7925/WIFI_RAM_CODE_MT7925_1_1.bin" +#define MT7927_FIRMWARE_WM "mediatek/mt7927/WIFI_RAM_CODE_MT6639_2_1.bin" #define MT7902_ROM_PATCH "mediatek/WIFI_MT7902_patch_mcu_1_1_hdr.bin" #define MT7920_ROM_PATCH "mediatek/WIFI_MT7961_patch_mcu_1a_2_hdr.bin" #define MT7921_ROM_PATCH "mediatek/WIFI_MT7961_patch_mcu_1_2_hdr.bin" #define MT7922_ROM_PATCH "mediatek/WIFI_MT7922_patch_mcu_1_1_hdr.bin" #define MT7925_ROM_PATCH "mediatek/mt7925/WIFI_MT7925_PATCH_MCU_1_1_hdr.bin" +#define MT7927_ROM_PATCH "mediatek/mt7927/WIFI_MT6639_PATCH_MCU_2_1_hdr.bin" #define MT792x_SDIO_HDR_TX_BYTES GENMASK(15, 0) #define MT792x_SDIO_HDR_PKT_TYPE GENMASK(17, 16) @@ -459,6 +461,8 @@ static inline char *mt792x_ram_name(struct mt792x_dev *dev) return MT7922_FIRMWARE_WM; case 0x7925: return MT7925_FIRMWARE_WM; + case 0x7927: + return MT7927_FIRMWARE_WM; default: return MT7921_FIRMWARE_WM; } @@ -475,6 +479,8 @@ static inline char *mt792x_patch_name(struct mt792x_dev *dev) return MT7922_ROM_PATCH; case 0x7925: return MT7925_ROM_PATCH; + case 0x7927: + return MT7927_ROM_PATCH; default: return MT7921_ROM_PATCH; } From 4a91a8c30bf409c06d56017e09f4fc58c5d044ce Mon Sep 17 00:00:00 2001 From: Javier Tia Date: Sat, 25 Apr 2026 14:49:58 -0500 Subject: [PATCH 24/71] wifi: mt76: mt7925: use irq_map for chip-specific interrupt handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The mac_reset and resume paths use the hardcoded MT_INT_RX_DONE_ALL constant (bits 0-2) to re-enable RX interrupts. This is correct for MT7925 (RX rings 0, 1, 2) but wrong for chips using different ring indices. Define a per-chip irq_map with the correct RX interrupt enable bits and replace hardcoded MT_INT_RX_DONE_ALL with irq_map field reads in the resume and mac_reset paths. Add the MT7927 irq_map with interrupt bits matching its RX ring layout (rings 4, 6, 7), selected at probe time based on PCI device ID. This ensures the correct interrupt bits are enabled regardless of the chip variant. Tested-by: Marcin FM Tested-by: Cristian-Florin Radoi Tested-by: George Salukvadze Tested-by: Evgeny Kapusta <3193631@gmail.com> Tested-by: Samu Toljamo Tested-by: Ariel Rosenfeld Tested-by: Chapuis Dario Tested-by: Thibaut François Tested-by: 张旭涵 Reviewed-by: Sean Wang Signed-off-by: Javier Tia Link: https://patch.msgid.link/20260425195011.790265-9-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7925/pci.c | 21 +++++++++++++++++-- .../wireless/mediatek/mt76/mt7925/pci_mac.c | 5 ++++- .../net/wireless/mediatek/mt76/mt792x_regs.h | 3 +++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c index 76139a548785..7e0d0e247fa3 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c @@ -266,6 +266,18 @@ static int mt7925_dma_init(struct mt792x_dev *dev) return mt792x_dma_enable(dev); } +static const struct mt792x_irq_map mt7927_irq_map = { + .host_irq_enable = MT_WFDMA0_HOST_INT_ENA, + .tx = { + .all_complete_mask = MT_INT_TX_DONE_ALL, + .mcu_complete_mask = MT_INT_TX_DONE_MCU, + }, + .rx = { + .data_complete_mask = MT7927_RX_DONE_INT_ENA4, + .wm_complete_mask = MT7927_RX_DONE_INT_ENA6, + .wm2_complete_mask = MT7927_RX_DONE_INT_ENA7, + }, +}; static int mt7925_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) { @@ -310,6 +322,7 @@ static int mt7925_pci_probe(struct pci_dev *pdev, struct mt76_bus_ops *bus_ops; struct mt792x_dev *dev; struct mt76_dev *mdev; + bool is_mt7927_hw; u8 features; int ret; u16 cmd; @@ -358,7 +371,8 @@ static int mt7925_pci_probe(struct pci_dev *pdev, dev = container_of(mdev, struct mt792x_dev, mt76); dev->fw_features = features; dev->hif_ops = &mt7925_pcie_ops; - dev->irq_map = &irq_map; + is_mt7927_hw = (pdev->device == 0x6639 || pdev->device == 0x7927); + dev->irq_map = is_mt7927_hw ? &mt7927_irq_map : &irq_map; mt76_mmio_init(&dev->mt76, pcim_iomap_table(pdev)[0]); tasklet_init(&mdev->irq_tasklet, mt792x_irq_tasklet, (unsigned long)dev); @@ -551,7 +565,10 @@ static int _mt7925_pci_resume(struct device *device, bool restore) mt76_wr(dev, MT_PCIE_MAC_INT_ENABLE, 0xff); mt76_connac_irq_enable(&dev->mt76, dev->irq_map->tx.all_complete_mask | - MT_INT_RX_DONE_ALL | MT_INT_MCU_CMD); + dev->irq_map->rx.data_complete_mask | + dev->irq_map->rx.wm_complete_mask | + dev->irq_map->rx.wm2_complete_mask | + MT_INT_MCU_CMD); mt76_set(dev, MT_MCU2HOST_SW_INT_ENA, MT_MCU_CMD_WAKE_RX_PCIE); /* put dma enabled */ diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci_mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci_mac.c index 3072850c2752..1626a3684082 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/pci_mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci_mac.c @@ -118,7 +118,10 @@ int mt7925e_mac_reset(struct mt792x_dev *dev) mt76_wr(dev, dev->irq_map->host_irq_enable, dev->irq_map->tx.all_complete_mask | - MT_INT_RX_DONE_ALL | MT_INT_MCU_CMD); + dev->irq_map->rx.data_complete_mask | + dev->irq_map->rx.wm_complete_mask | + dev->irq_map->rx.wm2_complete_mask | + MT_INT_MCU_CMD); mt76_wr(dev, MT_PCIE_MAC_INT_ENABLE, 0xff); err = mt792xe_mcu_fw_pmctrl(dev); diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_regs.h b/drivers/net/wireless/mediatek/mt76/mt792x_regs.h index d2a8b2b0df32..20d27ae89aca 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_regs.h +++ b/drivers/net/wireless/mediatek/mt76/mt792x_regs.h @@ -310,6 +310,9 @@ #define HOST_RX_DONE_INT_ENA1 BIT(1) #define HOST_RX_DONE_INT_ENA2 BIT(2) #define HOST_RX_DONE_INT_ENA3 BIT(3) +#define MT7927_RX_DONE_INT_ENA4 BIT(12) +#define MT7927_RX_DONE_INT_ENA6 BIT(14) +#define MT7927_RX_DONE_INT_ENA7 BIT(15) #define HOST_TX_DONE_INT_ENA0 BIT(4) #define HOST_TX_DONE_INT_ENA1 BIT(5) #define HOST_TX_DONE_INT_ENA2 BIT(6) From b0dddf55af8147699182d002af1f7ff017dc5cfd Mon Sep 17 00:00:00 2001 From: Javier Tia Date: Sat, 25 Apr 2026 14:49:59 -0500 Subject: [PATCH 25/71] wifi: mt76: mt7925: disable ASPM and runtime PM for MT7927 Disable PCIe ASPM unconditionally for MT7927. The CONNINFRA power domain and WFDMA register access are unreliable with PCIe L1 active, causing throughput to drop from 1+ Gbps to ~200 Mbps. Disable runtime PM and deep sleep for MT7927. The combo chip shares a CONNINFRA power domain between WiFi (PCIe) and BT (USB). SET_OWN/CLR_OWN transitions on the LPCTL register crash the BT firmware, requiring a full power cycle to recover. PM enablement will be addressed in a follow-up once safe power state transitions are determined. Reviewed-by: Sean Wang Signed-off-by: Javier Tia Link: https://patch.msgid.link/20260425195011.790265-10-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/init.c | 3 ++- drivers/net/wireless/mediatek/mt76/mt7925/pci.c | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/init.c b/drivers/net/wireless/mediatek/mt76/mt7925/init.c index c0c5cb9aff75..a8c2ca7c0efc 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/init.c @@ -232,7 +232,8 @@ int mt7925_register_device(struct mt792x_dev *dev) dev->pm.idle_timeout = MT792x_PM_TIMEOUT; dev->pm.stats.last_wake_event = jiffies; dev->pm.stats.last_doze_event = jiffies; - if (!mt76_is_usb(&dev->mt76)) { + /* MT7927: runtime PM crashes BT firmware on the shared CONNINFRA domain */ + if (!mt76_is_usb(&dev->mt76) && !is_mt7927(&dev->mt76)) { dev->pm.enable_user = true; dev->pm.enable = true; dev->pm.ds_enable_user = true; diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c index 7e0d0e247fa3..3e2afa0dac74 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c @@ -350,7 +350,10 @@ static int mt7925_pci_probe(struct pci_dev *pdev, if (ret) goto err_free_pci_vec; - if (mt7925_disable_aspm) + is_mt7927_hw = (pdev->device == 0x6639 || pdev->device == 0x7927); + + /* MT7927: ASPM L1 causes unreliable WFDMA register access */ + if (mt7925_disable_aspm || is_mt7927_hw) mt76_pci_disable_aspm(pdev); ops = mt792x_get_mac80211_ops(&pdev->dev, &mt7925_ops, @@ -371,7 +374,6 @@ static int mt7925_pci_probe(struct pci_dev *pdev, dev = container_of(mdev, struct mt792x_dev, mt76); dev->fw_features = features; dev->hif_ops = &mt7925_pcie_ops; - is_mt7927_hw = (pdev->device == 0x6639 || pdev->device == 0x7927); dev->irq_map = is_mt7927_hw ? &mt7927_irq_map : &irq_map; mt76_mmio_init(&dev->mt76, pcim_iomap_table(pdev)[0]); tasklet_init(&mdev->irq_tasklet, mt792x_irq_tasklet, (unsigned long)dev); From 60dac6072dbcb35e077906c43e58ed71436af992 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Sat, 25 Apr 2026 14:50:00 -0500 Subject: [PATCH 26/71] wifi: mt76: connac: replace is_mt7925() with is_connac3() Use is_connac3() instead of is_mt7925() to avoid confusion when more chips join. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260425195011.790265-11-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt76_connac.h | 7 ++++++- drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c | 4 ++-- drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c | 4 ++-- drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h | 2 +- drivers/net/wireless/mediatek/mt76/mt792x_usb.c | 2 +- 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac.h b/drivers/net/wireless/mediatek/mt76/mt76_connac.h index 45479cc29134..1994863dc59d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac.h +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac.h @@ -172,11 +172,16 @@ struct mt76_connac_tx_free { extern const struct wiphy_wowlan_support mt76_connac_wowlan_support; -static inline bool is_mt7925(struct mt76_dev *dev) +static inline bool is_connac3(struct mt76_dev *dev) { return mt76_chip(dev) == 0x7925 || mt76_chip(dev) == 0x7927; } +static inline bool is_mt7925(struct mt76_dev *dev) +{ + return mt76_chip(dev) == 0x7925; +} + static inline bool is_mt7927(struct mt76_dev *dev) { return mt76_chip(dev) == 0x7927; diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c index 0339e2e7ab60..c341595e9138 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c @@ -173,7 +173,7 @@ void mt76_connac_write_hw_txp(struct mt76_dev *dev, txp->msdu_id[0] = cpu_to_le16(id | MT_MSDU_ID_VALID); - if (is_mt7663(dev) || is_connac2(dev) || is_mt7925(dev)) + if (is_mt7663(dev) || is_connac2(dev) || is_connac3(dev)) last_mask = MT_TXD_LEN_LAST; else last_mask = MT_TXD_LEN_AMSDU_LAST | @@ -217,7 +217,7 @@ mt76_connac_txp_skb_unmap_hw(struct mt76_dev *dev, u32 last_mask; int i; - if (is_mt7663(dev) || is_connac2(dev) || is_mt7925(dev)) + if (is_mt7663(dev) || is_connac2(dev) || is_connac3(dev)) last_mask = MT_TXD_LEN_LAST; else last_mask = MT_TXD_LEN_MSDU_LAST; diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c index f61d0625ef51..ff884697d62f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c @@ -67,7 +67,7 @@ int mt76_connac_mcu_init_download(struct mt76_dev *dev, u32 addr, u32 len, if ((!is_connac_v1(dev) && addr == MCU_PATCH_ADDRESS) || (is_connac2(dev) && addr == 0x900000) || - (is_mt7925(dev) && (addr == 0x900000 || addr == 0xe0002800)) || + (is_connac3(dev) && (addr == 0x900000 || addr == 0xe0002800)) || (is_mt799x(dev) && addr == 0x900000)) cmd = MCU_CMD(PATCH_START_REQ); else @@ -3089,7 +3089,7 @@ static u32 mt76_connac2_get_data_mode(struct mt76_dev *dev, u32 info) { u32 mode = DL_MODE_NEED_RSP; - if ((!is_connac2(dev) && !is_mt7925(dev)) || info == PATCH_SEC_NOT_SUPPORT) + if ((!is_connac2(dev) && !is_connac3(dev)) || info == PATCH_SEC_NOT_SUPPORT) return mode; switch (FIELD_GET(PATCH_SEC_ENC_TYPE_MASK, info)) { diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h index ac5126ab68ff..552cb94edaa0 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h @@ -1878,7 +1878,7 @@ mt76_connac_mcu_gen_dl_mode(struct mt76_dev *dev, u8 feature_set, bool is_wa) ret |= feature_set & FW_FEATURE_SET_ENCRYPT ? DL_MODE_ENCRYPT | DL_MODE_RESET_SEC_IV : 0; - if (is_connac2(dev) || is_mt7925(dev)) + if (is_connac2(dev) || is_connac3(dev)) ret |= feature_set & FW_FEATURE_ENCRY_MODE ? DL_CONFIG_ENCRY_MODE_SEL : 0; ret |= FIELD_PREP(DL_MODE_KEY_IDX, diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_usb.c b/drivers/net/wireless/mediatek/mt76/mt792x_usb.c index 0d9dbd0aaf9e..be043e964c3c 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_usb.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_usb.c @@ -263,7 +263,7 @@ EXPORT_SYMBOL_GPL(mt792xu_dma_init); int mt792xu_wfsys_reset(struct mt792x_dev *dev) { - const struct mt792xu_wfsys_desc *desc = is_mt7925(&dev->mt76) ? + const struct mt792xu_wfsys_desc *desc = is_connac3(&dev->mt76) ? &mt7925_wfsys_desc : &mt7921_wfsys_desc; u32 val; From b2f5def2f133fca12897b0fc707a8d3df1c3cb05 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Sat, 25 Apr 2026 14:50:01 -0500 Subject: [PATCH 27/71] wifi: mt76: mt7925: use link-specific removal for non-MLD STA Use the default link removal path for non-MLD STA instead of the multi-link flow. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260425195011.790265-12-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/main.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index 5c4bf6126a03..74d65497cf9b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -1344,14 +1344,14 @@ void mt7925_mac_sta_remove(struct mt76_dev *mdev, struct ieee80211_vif *vif, struct mt792x_dev *dev = container_of(mdev, struct mt792x_dev, mt76); struct mt792x_sta *msta = (struct mt792x_sta *)sta->drv_priv; struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv; - unsigned long rem; - rem = ieee80211_vif_is_mld(vif) ? msta->valid_links : BIT(0); - - mt7925_mac_sta_remove_links(dev, vif, sta, rem); - - if (ieee80211_vif_is_mld(vif)) + if (ieee80211_vif_is_mld(vif)) { + mt7925_mac_sta_remove_links(dev, vif, sta, msta->valid_links); mt7925_mcu_del_dev(mdev, vif); + } else { + mt7925_mac_link_sta_remove(mdev, vif, &sta->deflink, + &msta->deflink); + } if (vif->type == NL80211_IFTYPE_STATION) { mvif->wep_sta = NULL; From 04222f980301561edd77917edac570b1393ec45d Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Sat, 25 Apr 2026 14:50:02 -0500 Subject: [PATCH 28/71] wifi: mt76: connac: tolerate inactive BSS deactivation Firmware may return wlan_failure when deactivating a BSS that is already inactive. This is a valid teardown case and should not fail the remove path. Keep activation failures unchanged since they still indicate that firmware failed to create or activate the BSS state. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260425195011.790265-13-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- .../wireless/mediatek/mt76/mt76_connac_mcu.c | 9 +++++++- .../wireless/mediatek/mt76/mt76_connac_mcu.h | 23 +++++++++++++++++++ .../net/wireless/mediatek/mt76/mt7925/mcu.c | 6 +++-- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c index ff884697d62f..be0ec1eaa746 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c @@ -1232,6 +1232,9 @@ int mt76_connac_mcu_uni_add_dev(struct mt76_phy *phy, len = enable ? sizeof(dev_req) : sizeof(basic_req); err = mt76_mcu_send_msg(dev, cmd, data, len, true); + if (err && cmd == MCU_UNI_CMD(BSS_INFO_UPDATE)) + err = mt76_connac_mcu_bss_deact_err(dev, err, enable); + if (err < 0) return err; @@ -1239,7 +1242,11 @@ int mt76_connac_mcu_uni_add_dev(struct mt76_phy *phy, data = enable ? (void *)&basic_req : (void *)&dev_req; len = enable ? sizeof(basic_req) : sizeof(dev_req); - return mt76_mcu_send_msg(dev, cmd, data, len, true); + err = mt76_mcu_send_msg(dev, cmd, data, len, true); + if (err && cmd == MCU_UNI_CMD(BSS_INFO_UPDATE)) + err = mt76_connac_mcu_bss_deact_err(dev, err, enable); + + return err; } EXPORT_SYMBOL_GPL(mt76_connac_mcu_uni_add_dev); diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h index 552cb94edaa0..4691b9b5e2be 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h @@ -1906,6 +1906,29 @@ mt76_connac_mcu_get_wlan_idx(struct mt76_dev *dev, struct mt76_wcid *wcid, } } +#define MT76_CONNAC_MCU_STATUS_WLAN_FAILURE 0xc0000001 + +static inline int +mt76_connac_mcu_bss_deact_err(struct mt76_dev *mdev, int err, bool enable) +{ + if (err != (int)MT76_CONNAC_MCU_STATUS_WLAN_FAILURE) + return err; + + /* Ignore wlan_failure state false alarm when deactivating an + * inactive network. It does not harm the firmware state. + */ + if (!enable) { + dev_dbg(mdev->dev, + "ignore wlan_failure when bss is deactivated\n"); + return 0; + } + + dev_warn(mdev->dev, + "wlan_failure when bss is activated\n"); + + return err; +} + struct sk_buff * __mt76_connac_mcu_alloc_sta_req(struct mt76_dev *dev, struct mt76_vif_link *mvif, struct mt76_wcid *wcid, int len); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c index db6df9da5b5d..9a1891c653c0 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c @@ -2837,6 +2837,7 @@ int mt7925_mcu_add_bss_info_sta(struct mt792x_phy *phy, struct mt792x_bss_conf *mconf = mt792x_link_conf_to_mconf(link_conf); struct mt792x_dev *dev = phy->dev; struct sk_buff *skb; + int err; skb = __mt7925_mcu_alloc_bss_req(&dev->mt76, &mconf->mt76, MT7925_BSS_UPDATE_MAX_SIZE); @@ -2862,8 +2863,9 @@ int mt7925_mcu_add_bss_info_sta(struct mt792x_phy *phy, mt7925_mcu_bss_mbssid_tlv(skb, link_conf, enable); } - return mt76_mcu_skb_send_msg(&dev->mt76, skb, - MCU_UNI_CMD(BSS_INFO_UPDATE), true); + err = mt76_mcu_skb_send_msg(&dev->mt76, skb, + MCU_UNI_CMD(BSS_INFO_UPDATE), true); + return mt76_connac_mcu_bss_deact_err(&dev->mt76, err, enable); } int mt7925_mcu_add_bss_info(struct mt792x_phy *phy, From c08ffdaf32f7b68d81aebf42d5bb222587a35c76 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Sat, 25 Apr 2026 14:50:03 -0500 Subject: [PATCH 29/71] wifi: mt76: mt792x: add MT7927 WFSYS reset support Add a dedicated MT7927 WFSYS reset path in mt792x_wfsys_reset(). Unlike the existing connac2/connac3 reset flow that toggles the WFSYS software reset bit and waits for init-done, MT7927 reset is driven through CBInfra and requires polling ROMCODE_INDEX until the MCU returns to the idle value after reset. Keep this dormant for now: no MT7927 PCI IDs are added by this patch, so it only prepares the reset logic without making the driver bind to MT7927 hardware yet. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260425195011.790265-14-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt792x_dma.c | 57 ++++++++++++++++++- .../net/wireless/mediatek/mt76/mt792x_regs.h | 11 ++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_dma.c b/drivers/net/wireless/mediatek/mt76/mt792x_dma.c index 002aece857b2..2835bf273154 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_dma.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_dma.c @@ -370,7 +370,54 @@ int mt792x_poll_rx(struct napi_struct *napi, int budget) } EXPORT_SYMBOL_GPL(mt792x_poll_rx); -int mt792x_wfsys_reset(struct mt792x_dev *dev) +static void mt7927_sema_status_read(struct mt792x_dev *dev, u32 addr) +{ + u32 remap; + + remap = mt76_rr(dev, MT7927_PCIE2AP_REMAP_WF_0_54); + mt76_wr(dev, MT7927_PCIE2AP_REMAP_WF_0_54, + (remap & ~MT7927_PCIE2AP_REMAP_WF_0_54_MASK) | + MT7927_PCIE2AP_REMAP_WF_0_54_VAL); + usleep_range(10, 20); + + mt76_rr(dev, addr); + + mt76_wr(dev, MT7927_PCIE2AP_REMAP_WF_0_54, remap); + usleep_range(10, 20); +} + +static int mt7927_wfsys_reset(struct mt792x_dev *dev) +{ + struct mt76_dev *mdev = &dev->mt76; + u32 val; + + mt7927_sema_status_read(dev, MT7927_SEMA_OWN_STA); + + mt76_set(dev, MT7927_CBINFRA_RGU_WF_RST, + MT7927_CBINFRA_RGU_WF_RST_WF_SUBSYS); + usleep_range(1000, 2000); + + mt76_clear(dev, MT7927_CBINFRA_RGU_WF_RST, + MT7927_CBINFRA_RGU_WF_RST_WF_SUBSYS); + usleep_range(5000, 10000); + + mt76_wr(dev, MT7927_CBINFRA_MCU_OWN_SET, BIT(0)); + + if (!__mt76_poll_msec(mdev, MT7927_ROMCODE_INDEX, 0xffff, + MT7927_MCU_IDLE_VALUE, 200)) { + val = mt76_rr(dev, MT7927_ROMCODE_INDEX); + dev_err(mdev->dev, + "MT7927 WFSYS reset timeout (ROMCODE_INDEX=0x%04x)\n", + val & 0xffff); + return -ETIMEDOUT; + } + + mt7927_sema_status_read(dev, MT7927_SEMA_OWN_STA_REP); + + return 0; +} + +static int mt792x_wfsys_reset_default(struct mt792x_dev *dev) { u32 addr = is_connac2(&dev->mt76) ? 0x18000140 : 0x7c000140; @@ -384,5 +431,13 @@ int mt792x_wfsys_reset(struct mt792x_dev *dev) return 0; } + +int mt792x_wfsys_reset(struct mt792x_dev *dev) +{ + if (is_mt7927(&dev->mt76)) + return mt7927_wfsys_reset(dev); + + return mt792x_wfsys_reset_default(dev); +} EXPORT_SYMBOL_GPL(mt792x_wfsys_reset); diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_regs.h b/drivers/net/wireless/mediatek/mt76/mt792x_regs.h index 20d27ae89aca..17504ef8e80d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_regs.h +++ b/drivers/net/wireless/mediatek/mt76/mt792x_regs.h @@ -493,4 +493,15 @@ #define WFSYS_SW_RST_B BIT(0) #define WFSYS_SW_INIT_DONE BIT(4) +/* CBInfra registers - MT7927 combo chip */ +#define MT7927_CBINFRA_RGU_WF_RST 0x1f8600 +#define MT7927_CBINFRA_RGU_WF_RST_WF_SUBSYS BIT(4) +#define MT7927_CBINFRA_MCU_OWN_SET 0x1f5034 +#define MT7927_ROMCODE_INDEX 0xc1604 +#define MT7927_MCU_IDLE_VALUE 0x1d1e +#define MT7927_PCIE2AP_REMAP_WF_0_54 0x21008 +#define MT7927_PCIE2AP_REMAP_WF_0_54_MASK GENMASK(15, 0) +#define MT7927_PCIE2AP_REMAP_WF_0_54_VAL 0x00001807 +#define MT7927_SEMA_OWN_STA 0x40000 +#define MT7927_SEMA_OWN_STA_REP 0x40400 #endif /* __MT792X_REGS_H */ From 1e570aa871f0ce535770a9390b833dc865c28d59 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Sat, 25 Apr 2026 14:50:04 -0500 Subject: [PATCH 30/71] wifi: mt76: mt792x: factor out common DMA queue allocation The mt792x PCIe DMA setup uses the same standard queue allocation sequence for data, MCU, firmware download and RX rings. Factor this part out into a small common helper so later chip support can reuse the existing flow without duplicating the queue setup logic. This keeps the common DMA skeleton in one place and makes follow-up chip-specific DMA changes smaller and easier to review. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260425195011.790265-15-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt792x.h | 19 +++++++ .../net/wireless/mediatek/mt76/mt792x_dma.c | 56 +++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h index 09840483fe2a..59562567cc13 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x.h +++ b/drivers/net/wireless/mediatek/mt76/mt792x.h @@ -206,6 +206,23 @@ struct mt792x_irq_map { } rx; }; +struct mt792x_dma_ring { + u8 qid; + u16 n_desc; + u32 ring_base; +}; + +struct mt792x_dma_layout { + struct mt792x_dma_ring tx_data0; + struct mt792x_dma_ring tx_mcu; + struct mt792x_dma_ring tx_fwdl; + struct mt792x_dma_ring rx_data; + struct mt792x_dma_ring rx_mcu; +}; + +#define mt792x_dma_ring(_qid, _n_desc, _ring_base) \ + { .qid = (_qid), .n_desc = (_n_desc), .ring_base = (_ring_base) } + #define mt792x_init_reset(dev) ((dev)->hif_ops->init_reset(dev)) #define mt792x_dev_reset(dev) ((dev)->hif_ops->reset(dev)) #define mt792x_mcu_init(dev) ((dev)->hif_ops->mcu_init(dev)) @@ -421,6 +438,8 @@ void mt792x_sta_statistics(struct ieee80211_hw *hw, void mt792x_set_coverage_class(struct ieee80211_hw *hw, int radio_idx, s16 coverage_class); void mt792x_dma_cleanup(struct mt792x_dev *dev); +int mt792x_dma_alloc_queues(struct mt792x_dev *dev, + const struct mt792x_dma_layout *layout); int mt792x_dma_enable(struct mt792x_dev *dev); int mt792x_wpdma_reset(struct mt792x_dev *dev, bool force); int mt792x_wpdma_reinit_cond(struct mt792x_dev *dev); diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_dma.c b/drivers/net/wireless/mediatek/mt76/mt792x_dma.c index 2835bf273154..c52d0c85913c 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_dma.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_dma.c @@ -87,6 +87,62 @@ void mt792x_rx_poll_complete(struct mt76_dev *mdev, enum mt76_rxq_id q) } EXPORT_SYMBOL_GPL(mt792x_rx_poll_complete); +int mt792x_dma_alloc_queues(struct mt792x_dev *dev, + const struct mt792x_dma_layout *layout) +{ + int ret; + + mt76_dma_attach(&dev->mt76); + + ret = mt792x_dma_disable(dev, true); + if (ret) + return ret; + + /* init tx queue */ + ret = mt76_connac_init_tx_queues(dev->phy.mt76, layout->tx_data0.qid, + layout->tx_data0.n_desc, + layout->tx_data0.ring_base, + NULL, 0); + if (ret) + return ret; + + mt76_wr(dev, MT_WFDMA0_TX_RING0_EXT_CTRL, 0x4); + + /* command to WM */ + ret = mt76_init_mcu_queue(&dev->mt76, MT_MCUQ_WM, + layout->tx_mcu.qid, + layout->tx_mcu.n_desc, + layout->tx_mcu.ring_base); + if (ret) + return ret; + + /* firmware download */ + ret = mt76_init_mcu_queue(&dev->mt76, MT_MCUQ_FWDL, + layout->tx_fwdl.qid, + layout->tx_fwdl.n_desc, + layout->tx_fwdl.ring_base); + if (ret) + return ret; + + /* rx event */ + ret = mt76_queue_alloc(dev, &dev->mt76.q_rx[MT_RXQ_MCU], + layout->rx_mcu.qid, + layout->rx_mcu.n_desc, + MT_RX_BUF_SIZE, + layout->rx_mcu.ring_base); + if (ret) + return ret; + + /* rx data */ + ret = mt76_queue_alloc(dev, &dev->mt76.q_rx[MT_RXQ_MAIN], + layout->rx_data.qid, + layout->rx_data.n_desc, + MT_RX_BUF_SIZE, + layout->rx_data.ring_base); + return ret; +} +EXPORT_SYMBOL_GPL(mt792x_dma_alloc_queues); + #define PREFETCH(base, depth) ((base) << 16 | (depth)) static void mt792x_dma_prefetch(struct mt792x_dev *dev) { From e1fca65c826b2e4810c8f9fddddd8f284eda7f5c Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Sat, 25 Apr 2026 14:50:05 -0500 Subject: [PATCH 31/71] wifi: mt76: mt7925: switch DMA init to common mt792x queue helpers Convert mt7925 DMA init to use the common mt792x queue allocation helper and isolate the mt7925-specific prefetch/WFDMA setup. This removes duplicated DMA setup code and prepares the path for follow-up chip-specific DMA support with smaller deltas. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260425195011.790265-16-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7925/pci.c | 57 +++++++------------ .../net/wireless/mediatek/mt76/mt792x_dma.c | 46 +++++++++------ 2 files changed, 48 insertions(+), 55 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c index 3e2afa0dac74..3888fa0fa522 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c @@ -210,48 +210,29 @@ static u32 mt7925_rmw(struct mt76_dev *mdev, u32 offset, u32 mask, u32 val) return dev->bus_ops->rmw(mdev, addr, mask, val); } +static const struct mt792x_dma_layout mt7925_dma_layout = { + .tx_data0 = mt792x_dma_ring(MT7925_TXQ_BAND0, + MT7925_TX_RING_SIZE, + MT_TX_RING_BASE), + .tx_mcu = mt792x_dma_ring(MT7925_TXQ_MCU_WM, + MT7925_TX_MCU_RING_SIZE, + MT_TX_RING_BASE), + .tx_fwdl = mt792x_dma_ring(MT7925_TXQ_FWDL, + MT7925_TX_FWDL_RING_SIZE, + MT_TX_RING_BASE), + .rx_mcu = mt792x_dma_ring(MT7925_RXQ_MCU_WM, + MT7925_RX_MCU_RING_SIZE, + MT_RX_EVENT_RING_BASE), + .rx_data = mt792x_dma_ring(MT7925_RXQ_BAND0, + MT7925_RX_RING_SIZE, + MT_RX_DATA_RING_BASE), +}; + static int mt7925_dma_init(struct mt792x_dev *dev) { int ret; - mt76_dma_attach(&dev->mt76); - - ret = mt792x_dma_disable(dev, true); - if (ret) - return ret; - - /* init tx queue */ - ret = mt76_connac_init_tx_queues(dev->phy.mt76, MT7925_TXQ_BAND0, - MT7925_TX_RING_SIZE, - MT_TX_RING_BASE, NULL, 0); - if (ret) - return ret; - - mt76_wr(dev, MT_WFDMA0_TX_RING0_EXT_CTRL, 0x4); - - /* command to WM */ - ret = mt76_init_mcu_queue(&dev->mt76, MT_MCUQ_WM, MT7925_TXQ_MCU_WM, - MT7925_TX_MCU_RING_SIZE, MT_TX_RING_BASE); - if (ret) - return ret; - - /* firmware download */ - ret = mt76_init_mcu_queue(&dev->mt76, MT_MCUQ_FWDL, MT7925_TXQ_FWDL, - MT7925_TX_FWDL_RING_SIZE, MT_TX_RING_BASE); - if (ret) - return ret; - - /* rx event */ - ret = mt76_queue_alloc(dev, &dev->mt76.q_rx[MT_RXQ_MCU], - MT7925_RXQ_MCU_WM, MT7925_RX_MCU_RING_SIZE, - MT_RX_BUF_SIZE, MT_RX_EVENT_RING_BASE); - if (ret) - return ret; - - /* rx data */ - ret = mt76_queue_alloc(dev, &dev->mt76.q_rx[MT_RXQ_MAIN], - MT7925_RXQ_BAND0, MT7925_RX_RING_SIZE, - MT_RX_BUF_SIZE, MT_RX_DATA_RING_BASE); + ret = mt792x_dma_alloc_queues(dev, &mt7925_dma_layout); if (ret) return ret; diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_dma.c b/drivers/net/wireless/mediatek/mt76/mt792x_dma.c index c52d0c85913c..7b36f0761dd1 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_dma.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_dma.c @@ -144,21 +144,35 @@ int mt792x_dma_alloc_queues(struct mt792x_dev *dev, EXPORT_SYMBOL_GPL(mt792x_dma_alloc_queues); #define PREFETCH(base, depth) ((base) << 16 | (depth)) + +static void mt7925_dma_prefetch_setup(struct mt792x_dev *dev) +{ + /* rx ring */ + mt76_wr(dev, MT_WFDMA0_RX_RING0_EXT_CTRL, PREFETCH(0x0000, 0x4)); + mt76_wr(dev, MT_WFDMA0_RX_RING1_EXT_CTRL, PREFETCH(0x0040, 0x4)); + mt76_wr(dev, MT_WFDMA0_RX_RING2_EXT_CTRL, PREFETCH(0x0080, 0x4)); + mt76_wr(dev, MT_WFDMA0_RX_RING3_EXT_CTRL, PREFETCH(0x00c0, 0x4)); + + /* tx ring */ + mt76_wr(dev, MT_WFDMA0_TX_RING0_EXT_CTRL, PREFETCH(0x0100, 0x10)); + mt76_wr(dev, MT_WFDMA0_TX_RING1_EXT_CTRL, PREFETCH(0x0200, 0x10)); + mt76_wr(dev, MT_WFDMA0_TX_RING2_EXT_CTRL, PREFETCH(0x0300, 0x10)); + mt76_wr(dev, MT_WFDMA0_TX_RING3_EXT_CTRL, PREFETCH(0x0400, 0x10)); + mt76_wr(dev, MT_WFDMA0_TX_RING15_EXT_CTRL, PREFETCH(0x0500, 0x4)); + mt76_wr(dev, MT_WFDMA0_TX_RING16_EXT_CTRL, PREFETCH(0x0540, 0x4)); +} + +static void mt7925_wfdma_setup(struct mt792x_dev *dev) +{ + mt76_rmw(dev, MT_UWFDMA0_GLO_CFG_EXT1, BIT(28), BIT(28)); + mt76_set(dev, MT_WFDMA0_INT_RX_PRI, 0x0F00); + mt76_set(dev, MT_WFDMA0_INT_TX_PRI, 0x7F00); +} + static void mt792x_dma_prefetch(struct mt792x_dev *dev) { if (is_mt7925(&dev->mt76)) { - /* rx ring */ - mt76_wr(dev, MT_WFDMA0_RX_RING0_EXT_CTRL, PREFETCH(0x0000, 0x4)); - mt76_wr(dev, MT_WFDMA0_RX_RING1_EXT_CTRL, PREFETCH(0x0040, 0x4)); - mt76_wr(dev, MT_WFDMA0_RX_RING2_EXT_CTRL, PREFETCH(0x0080, 0x4)); - mt76_wr(dev, MT_WFDMA0_RX_RING3_EXT_CTRL, PREFETCH(0x00c0, 0x4)); - /* tx ring */ - mt76_wr(dev, MT_WFDMA0_TX_RING0_EXT_CTRL, PREFETCH(0x0100, 0x10)); - mt76_wr(dev, MT_WFDMA0_TX_RING1_EXT_CTRL, PREFETCH(0x0200, 0x10)); - mt76_wr(dev, MT_WFDMA0_TX_RING2_EXT_CTRL, PREFETCH(0x0300, 0x10)); - mt76_wr(dev, MT_WFDMA0_TX_RING3_EXT_CTRL, PREFETCH(0x0400, 0x10)); - mt76_wr(dev, MT_WFDMA0_TX_RING15_EXT_CTRL, PREFETCH(0x0500, 0x4)); - mt76_wr(dev, MT_WFDMA0_TX_RING16_EXT_CTRL, PREFETCH(0x0540, 0x4)); + mt7925_dma_prefetch_setup(dev); } else if (is_mt7902(&dev->mt76)) { /* rx ring */ mt76_wr(dev, MT_WFDMA0_RX_RING0_EXT_CTRL, PREFETCH(0x0000, 0x4)); @@ -222,11 +236,9 @@ int mt792x_dma_enable(struct mt792x_dev *dev) mt76_set(dev, MT_WFDMA0_GLO_CFG, MT_WFDMA0_GLO_CFG_TX_DMA_EN | MT_WFDMA0_GLO_CFG_RX_DMA_EN); - if (is_mt7925(&dev->mt76)) { - mt76_rmw(dev, MT_UWFDMA0_GLO_CFG_EXT1, BIT(28), BIT(28)); - mt76_set(dev, MT_WFDMA0_INT_RX_PRI, 0x0F00); - mt76_set(dev, MT_WFDMA0_INT_TX_PRI, 0x7F00); - } + if (is_mt7925(&dev->mt76)) + mt7925_wfdma_setup(dev); + mt76_set(dev, MT_WFDMA_DUMMY_CR, MT_WFDMA_NEED_REINIT); /* enable interrupts for TX/RX rings */ From 0683825c48d9754832dd1e6a1e88946a5e4f2fd3 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Sat, 25 Apr 2026 14:50:06 -0500 Subject: [PATCH 32/71] wifi: mt76: mt792x: add MT7927-specific PCIe DMA support Add the MT7927-specific PCIe DMA queue layout and local DMA setup, including the extra RX ring and WFDMA/prefetch programming. MT7927 does not follow the mt7925 DMA configuration, so provide a separate DMA init path while reusing the existing common queue setup. This keeps the chip-specific changes local and prepares the driver for later MT7927 enablement without affecting existing devices. Co-developed-by: Javier Tia Signed-off-by: Javier Tia Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260425195011.790265-17-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- .../wireless/mediatek/mt76/mt7925/mt7925.h | 13 +++++ .../net/wireless/mediatek/mt76/mt7925/pci.c | 53 ++++++++++++++++++- .../net/wireless/mediatek/mt76/mt792x_dma.c | 41 +++++++++++++- .../net/wireless/mediatek/mt76/mt792x_regs.h | 9 ++++ 4 files changed, 113 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h index 46b480f7d813..984be40ca76d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h @@ -126,6 +126,19 @@ enum mt7925_rxq_id { MT7925_RXQ_MCU_WM2, /* for tx done */ }; +enum mt7927_txq_id { + MT7927_TXQ_BAND0 = MT7925_TXQ_BAND0, + MT7927_TXQ_BAND1 = MT7925_TXQ_BAND1, + MT7927_TXQ_MCU_WM = MT7925_TXQ_MCU_WM, + MT7927_TXQ_FWDL = MT7925_TXQ_FWDL, +}; + +enum mt7927_rxq_id { + MT7927_RXQ_BAND0 = 4, + MT7927_RXQ_MCU_WM = 6, + MT7927_RXQ_DATA2 = 7, +}; + enum { MODE_OPEN = 0, MODE_SHARED = 1, diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c index 3888fa0fa522..fd6d0b029ef4 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c @@ -228,6 +228,51 @@ static const struct mt792x_dma_layout mt7925_dma_layout = { MT_RX_DATA_RING_BASE), }; +static const struct mt792x_dma_layout mt7927_dma_layout = { + .tx_data0 = mt792x_dma_ring(MT7927_TXQ_BAND0, + MT7925_TX_RING_SIZE, + MT_TX_RING_BASE), + .tx_mcu = mt792x_dma_ring(MT7927_TXQ_MCU_WM, + MT7925_TX_MCU_RING_SIZE, + MT_TX_RING_BASE), + .tx_fwdl = mt792x_dma_ring(MT7927_TXQ_FWDL, + MT7925_TX_FWDL_RING_SIZE, + MT_TX_RING_BASE), + .rx_mcu = mt792x_dma_ring(MT7927_RXQ_MCU_WM, + MT7925_RX_MCU_RING_SIZE, + MT_RX_EVENT_RING_BASE), + .rx_data = mt792x_dma_ring(MT7927_RXQ_BAND0, + MT7925_RX_RING_SIZE, + MT_RX_DATA_RING_BASE), +}; + +static int mt7927_dma_init(struct mt792x_dev *dev) +{ + int ret; + + ret = mt792x_dma_alloc_queues(dev, &mt7927_dma_layout); + if (ret) + return ret; + + ret = mt76_queue_alloc(dev, &dev->mt76.q_rx[MT_RXQ_MCU_WA], + MT7927_RXQ_DATA2, + MT7925_RX_MCU_RING_SIZE, + MT_RX_BUF_SIZE, + MT_RX_DATA_RING_BASE); + if (ret) + return ret; + + ret = mt76_init_queues(dev, mt792x_poll_rx); + if (ret < 0) + return ret; + + netif_napi_add_tx(dev->mt76.tx_napi_dev, &dev->mt76.tx_napi, + mt792x_poll_tx); + napi_enable(&dev->mt76.tx_napi); + + return mt792x_dma_enable(dev); +} + static int mt7925_dma_init(struct mt792x_dev *dev) { int ret; @@ -406,7 +451,13 @@ static int mt7925_pci_probe(struct pci_dev *pdev, if (ret) goto err_free_dev; - ret = mt7925_dma_init(dev); + if (is_mt7927(&dev->mt76)) + ret = mt7927_dma_init(dev); + else if (is_mt7925(&dev->mt76)) + ret = mt7925_dma_init(dev); + else + ret = -EINVAL; + if (ret) goto err_free_irq; diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_dma.c b/drivers/net/wireless/mediatek/mt76/mt792x_dma.c index 7b36f0761dd1..fc326447c792 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_dma.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_dma.c @@ -169,9 +169,39 @@ static void mt7925_wfdma_setup(struct mt792x_dev *dev) mt76_set(dev, MT_WFDMA0_INT_TX_PRI, 0x7F00); } +static void mt7927_dma_prefetch_setup(struct mt792x_dev *dev) +{ + mt76_wr(dev, MT_WFDMA_PREFETCH_CTRL, + mt76_rr(dev, MT_WFDMA_PREFETCH_CTRL)); + mt76_wr(dev, MT_WFDMA_PREFETCH_CFG0, 0x660077); + mt76_wr(dev, MT_WFDMA_PREFETCH_CFG1, 0x1100); + mt76_wr(dev, MT_WFDMA_PREFETCH_CFG2, 0x30004f); + mt76_wr(dev, MT_WFDMA_PREFETCH_CFG3, 0x542200); + mt76_wr(dev, MT_WFDMA0_RX_RING4_EXT_CTRL, PREFETCH(0x0000, 0x8)); + mt76_wr(dev, MT_WFDMA0_RX_RING6_EXT_CTRL, PREFETCH(0x0080, 0x8)); + mt76_wr(dev, MT_WFDMA0_RX_RING7_EXT_CTRL, PREFETCH(0x0100, 0x4)); + mt76_wr(dev, MT_WFDMA0_TX_RING16_EXT_CTRL, PREFETCH(0x0140, 0x4)); + mt76_wr(dev, MT_WFDMA0_TX_RING15_EXT_CTRL, PREFETCH(0x0180, 0x10)); + mt76_wr(dev, MT_WFDMA0_TX_RING0_EXT_CTRL, PREFETCH(0x0280, 0x4)); +} + +static void mt7927_wfdma_setup(struct mt792x_dev *dev) +{ + mt76_set(dev, MT_WFDMA0_GLO_CFG, + MT_WFDMA0_GLO_CFG_ADDR_EXT_EN | + MT_WFDMA0_GLO_CFG_FW_DWLD_BYPASS_DMASHDL); + mt76_clear(dev, MT_WFDMA0_GLO_CFG, + MT_WFDMA0_GLO_CFG_CSR_LBK_RX_Q_SEL_EN); + mt76_rmw(dev, MT_WFDMA0_GLO_CFG_EXT1, BIT(28), BIT(28)); + mt76_set(dev, MT_WFDMA0_INT_RX_PRI, 0x0F00); + mt76_set(dev, MT_WFDMA0_INT_TX_PRI, 0x7F00); +} + static void mt792x_dma_prefetch(struct mt792x_dev *dev) { - if (is_mt7925(&dev->mt76)) { + if (is_mt7927(&dev->mt76)) { + mt7927_dma_prefetch_setup(dev); + } else if (is_mt7925(&dev->mt76)) { mt7925_dma_prefetch_setup(dev); } else if (is_mt7902(&dev->mt76)) { /* rx ring */ @@ -236,7 +266,9 @@ int mt792x_dma_enable(struct mt792x_dev *dev) mt76_set(dev, MT_WFDMA0_GLO_CFG, MT_WFDMA0_GLO_CFG_TX_DMA_EN | MT_WFDMA0_GLO_CFG_RX_DMA_EN); - if (is_mt7925(&dev->mt76)) + if (is_mt7927(&dev->mt76)) + mt7927_wfdma_setup(dev); + else if (is_mt7925(&dev->mt76)) mt7925_wfdma_setup(dev); mt76_set(dev, MT_WFDMA_DUMMY_CR, MT_WFDMA_NEED_REINIT); @@ -349,6 +381,11 @@ int mt792x_dma_disable(struct mt792x_dev *dev, bool force) MT_WFDMA0_GLO_CFG_RX_DMA_BUSY, 0, 100, 1)) return -ETIMEDOUT; + if (is_mt7927(&dev->mt76)) { + mt76_wr(dev, MT_WFDMA0_RST_DTX_PTR, ~0); + mt76_wr(dev, MT_WFDMA0_RST_DRX_PTR, ~0); + } + /* disable dmashdl */ mt76_clear(dev, MT_WFDMA0_GLO_CFG_EXT0, MT_WFDMA0_CSR_TX_DMASHDL_ENABLE); diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_regs.h b/drivers/net/wireless/mediatek/mt76/mt792x_regs.h index 17504ef8e80d..4cd5b33b640e 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_regs.h +++ b/drivers/net/wireless/mediatek/mt76/mt792x_regs.h @@ -301,7 +301,9 @@ #define MT_WFDMA0_GLO_CFG_FIFO_LITTLE_ENDIAN BIT(12) #define MT_WFDMA0_GLO_CFG_RX_WB_DDONE BIT(13) #define MT_WFDMA0_GLO_CFG_CSR_DISP_BASE_PTR_CHAIN_EN BIT(15) +#define MT_WFDMA0_GLO_CFG_CSR_LBK_RX_Q_SEL_EN BIT(20) #define MT_WFDMA0_GLO_CFG_OMIT_RX_INFO_PFET2 BIT(21) +#define MT_WFDMA0_GLO_CFG_ADDR_EXT_EN BIT(26) #define MT_WFDMA0_GLO_CFG_OMIT_RX_INFO BIT(27) #define MT_WFDMA0_GLO_CFG_OMIT_TX_INFO BIT(28) #define MT_WFDMA0_GLO_CFG_CLK_GAT_DIS BIT(30) @@ -368,6 +370,13 @@ #define MT_WFDMA_EXT_CSR_HIF_MISC MT_WFDMA_EXT_CSR(0x44) #define MT_WFDMA_EXT_CSR_HIF_MISC_BUSY BIT(0) +#define MT_WFDMA_PREFETCH_CTRL MT_WFDMA_EXT_CSR(0x30) +#define MT_WFDMA_PREFETCH_CFG0 MT_WFDMA_EXT_CSR(0xf0) +#define MT_WFDMA_PREFETCH_CFG1 MT_WFDMA_EXT_CSR(0xf4) +#define MT_WFDMA_PREFETCH_CFG2 MT_WFDMA_EXT_CSR(0xf8) +#define MT_WFDMA_PREFETCH_CFG3 MT_WFDMA_EXT_CSR(0xfc) +#define MT_WFDMA0_GLO_CFG_EXT1 MT_WFDMA0(0x2b4) + #define MT_SWDEF_BASE 0x41f200 #define MT_SWDEF(ofs) (MT_SWDEF_BASE + (ofs)) #define MT_SWDEF_MODE MT_SWDEF(0x3c) From 05fd148853036ea3b3f688f35872fbd01cdb5892 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Sat, 25 Apr 2026 14:50:07 -0500 Subject: [PATCH 33/71] wifi: mt76: mt7925: sync MT7927 BSS band assignment MT7927 needs DBDC enabled and uses a fixed firmware band assignment for 2.4GHz and 5/6GHz BSS contexts. Reprogram the STA dev context when the channel context is assigned so the firmware sees the updated band_idx before the BSS is used. This avoids stale band programming after band changes. Co-developed-by: Javier Tia Signed-off-by: Javier Tia Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260425195011.790265-18-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7925/init.c | 9 ++++ .../net/wireless/mediatek/mt76/mt7925/main.c | 42 +++++++++++++++++++ .../net/wireless/mediatek/mt76/mt7925/mcu.c | 1 + .../wireless/mediatek/mt76/mt7925/mt7925.h | 12 ++++++ .../wireless/mediatek/mt76/mt7925/pci_mac.c | 7 ++++ 5 files changed, 71 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/init.c b/drivers/net/wireless/mediatek/mt76/mt7925/init.c index a8c2ca7c0efc..e85b0d104fbe 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/init.c @@ -117,6 +117,15 @@ static int __mt7925_init_hardware(struct mt792x_dev *dev) if (ret) goto out; + if (is_mt7927(&dev->mt76)) { + ret = mt7925_mcu_set_dbdc(&dev->mphy, true); + if (ret) { + dev_warn(dev->mt76.dev, + "MT7927 DBDC enable failed: %d\n", ret); + ret = 0; + } + } + out: return ret; } diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index 74d65497cf9b..2599b738496f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -396,6 +396,18 @@ static int mt7925_mac_link_bss_add(struct mt792x_dev *dev, mconf->mt76.omac_idx = ieee80211_vif_is_mld(vif) ? 0 : mconf->mt76.idx; mconf->mt76.band_idx = 0xff; + + if (is_mt7927(&dev->mt76)) { + struct ieee80211_channel *chan; + + if (link_conf->chanreq.oper.chan) + chan = link_conf->chanreq.oper.chan; + else + chan = mvif->phy->mt76->chandef.chan; + + mconf->mt76.band_idx = mt7927_band_idx(chan->band); + } + mconf->mt76.wmm_idx = ieee80211_vif_is_mld(vif) ? 0 : mconf->mt76.idx % MT76_CONNAC_MAX_WMM_SETS; mconf->mt76.link_idx = hweight16(mvif->valid_links); @@ -2244,6 +2256,29 @@ mt7925_change_sta_links(struct ieee80211_hw *hw, struct ieee80211_vif *vif, return err; } +static int +mt7927_reconfig_band(struct mt792x_dev *dev, struct ieee80211_vif *vif, + struct ieee80211_bss_conf *link_conf, + struct mt792x_bss_conf *mconf, + u8 band_idx) +{ + struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv; + struct mt792x_link_sta *mlink = &mvif->sta.deflink; + int ret; + + ret = mt76_connac_mcu_uni_add_dev(&dev->mphy, link_conf, + &mconf->mt76, &mlink->wcid, + false); + if (ret) + return ret; + + mconf->mt76.band_idx = band_idx; + + return mt76_connac_mcu_uni_add_dev(&dev->mphy, link_conf, + &mconf->mt76, &mlink->wcid, + true); +} + static int mt7925_assign_vif_chanctx(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_bss_conf *link_conf, @@ -2254,6 +2289,7 @@ static int mt7925_assign_vif_chanctx(struct ieee80211_hw *hw, struct mt792x_dev *dev = mt792x_hw_dev(hw); struct ieee80211_bss_conf *pri_link_conf; struct mt792x_bss_conf *mconf; + u8 band_idx; mutex_lock(&dev->mt76.mutex); @@ -2267,6 +2303,12 @@ static int mt7925_assign_vif_chanctx(struct ieee80211_hw *hw, NULL, true); } else { mconf = &mvif->bss_conf; + + if (is_mt7927(&dev->mt76)) { + band_idx = mt7927_band_idx(ctx->def.chan->band); + + mt7927_reconfig_band(dev, vif, link_conf, mconf, band_idx); + } } mconf->mt76.ctx = ctx; diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c index 9a1891c653c0..1cd234737adb 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c @@ -2923,6 +2923,7 @@ int mt7925_mcu_set_dbdc(struct mt76_phy *phy, bool enable) return err; } +EXPORT_SYMBOL_GPL(mt7925_mcu_set_dbdc); static void mt7925_mcu_build_scan_ie_tlv(struct mt76_dev *mdev, diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h index 984be40ca76d..242f83f90dd4 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h @@ -252,6 +252,18 @@ struct mt7925_txpwr { s8 eht996x3_484[16][2]; }; +static inline u8 mt7927_band_idx(enum nl80211_band band) +{ + switch (band) { + case NL80211_BAND_2GHZ: + return 0; + case NL80211_BAND_5GHZ: + case NL80211_BAND_6GHZ: + default: + return 1; + } +} + extern const struct ieee80211_ops mt7925_ops; int __mt7925_start(struct mt792x_phy *phy); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci_mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci_mac.c index 1626a3684082..97683949a305 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/pci_mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci_mac.c @@ -3,6 +3,7 @@ #include "mt7925.h" #include "../dma.h" +#include "mcu.h" #include "mac.h" int mt7925e_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr, @@ -144,6 +145,12 @@ int mt7925e_mac_reset(struct mt792x_dev *dev) if (err) goto out; + if (is_mt7927(&dev->mt76)) { + err = mt7925_mcu_set_dbdc(&dev->mphy, true); + if (err) + goto out; + } + err = __mt7925_start(&dev->phy); out: clear_bit(MT76_RESET, &dev->mphy.state); From bcc6e3358a1f946d5f2f8a2547c2a997375100cd Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Sat, 25 Apr 2026 14:50:08 -0500 Subject: [PATCH 34/71] wifi: mt76: mt7925: add MBMC event handling Handle MBMC unsolicited events from firmware to report the current MBMC state. This helps validate MT7927 band setup. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260425195011.790265-19-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- .../wireless/mediatek/mt76/mt76_connac_mcu.h | 1 + .../net/wireless/mediatek/mt76/mt7925/mcu.c | 31 +++++++++++++++++++ .../net/wireless/mediatek/mt76/mt7925/mcu.h | 3 +- 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h index 4691b9b5e2be..6d0429f40b0f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h @@ -1069,6 +1069,7 @@ enum { MCU_UNI_EVENT_SCAN_DONE = 0x0e, MCU_UNI_EVENT_RDD_REPORT = 0x11, MCU_UNI_EVENT_ROC = 0x27, + MCU_UNI_EVENT_MBMC = 0x28, MCU_UNI_EVENT_TX_DONE = 0x2d, MCU_UNI_EVENT_THERMAL = 0x35, MCU_UNI_EVENT_RSSI_MONITOR = 0x41, diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c index 1cd234737adb..f667eba99a51 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c @@ -566,6 +566,34 @@ mt7925_mcu_uni_debug_msg_event(struct mt792x_dev *dev, struct sk_buff *skb) } } +static void +mt7925_mcu_handle_mbmc_event(struct mt792x_dev *dev, struct sk_buff *skb) +{ + struct mbmc_conf_tlv *tlv; + u32 tlv_len; + + skb_pull(skb, sizeof(struct mt7925_mcu_rxd) + 4); + tlv_len = skb->len; + tlv = (struct mbmc_conf_tlv *)skb->data; + + while (tlv_len >= sizeof(*tlv) && + le16_to_cpu(tlv->len) >= sizeof(*tlv) && + le16_to_cpu(tlv->len) <= tlv_len) { + u16 tag = le16_to_cpu(tlv->tag); + + if (tag == UNI_MBMC_SETTING || tag == UNI_MBMC_NO_RESP_SETTING) { + dev_dbg(dev->mt76.dev, + "MBMC event: tag=%u mbmc_en=%u\n", + tag, tlv->mbmc_en); + break; + } + + tlv_len -= le16_to_cpu(tlv->len); + tlv = (struct mbmc_conf_tlv *) + ((u8 *)tlv + le16_to_cpu(tlv->len)); + } +} + static void mt7925_mcu_uni_rx_unsolicited_event(struct mt792x_dev *dev, struct sk_buff *skb) @@ -584,6 +612,9 @@ mt7925_mcu_uni_rx_unsolicited_event(struct mt792x_dev *dev, case MCU_UNI_EVENT_ROC: mt7925_mcu_uni_roc_event(dev, skb); break; + case MCU_UNI_EVENT_MBMC: + mt7925_mcu_handle_mbmc_event(dev, skb); + break; case MCU_UNI_EVENT_SCAN_DONE: mt7925_mcu_scan_event(dev, skb); return; diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h index 56e2772f3ffe..293f173b23dd 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h @@ -143,7 +143,8 @@ enum { }; enum { - UNI_MBMC_SETTING, + UNI_MBMC_SETTING = 0, + UNI_MBMC_NO_RESP_SETTING = 1, }; enum { From 298efd8f9f24727269a95c2f9dd94d604c98ff90 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Sat, 25 Apr 2026 14:50:09 -0500 Subject: [PATCH 35/71] wifi: mt76: mt792x: enable CNM ops for MT7927 Enable CNM support for MT7927 so mac80211 keeps remain-on-channel callbacks available. MT7927 needs them at runtime even when the capability is not advertised through the normal firmware feature path. Co-developed-by: Javier Tia Signed-off-by: Javier Tia Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260425195011.790265-20-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt792x_core.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_core.c b/drivers/net/wireless/mediatek/mt76/mt792x_core.c index 5a5d7534830b..1d807abc7125 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_core.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_core.c @@ -758,6 +758,13 @@ mt792x_get_offload_capability(struct device *dev, const char *fw_wm) return offload_caps; } +static bool mt792x_needs_cnm_runtime(const void *drv_data) +{ + const char *fw_wm = drv_data; + + return fw_wm && !strcmp(fw_wm, MT7927_FIRMWARE_WM); +} + struct ieee80211_ops * mt792x_get_mac80211_ops(struct device *dev, const struct ieee80211_ops *mac80211_ops, @@ -771,6 +778,10 @@ mt792x_get_mac80211_ops(struct device *dev, return NULL; *fw_features = mt792x_get_offload_capability(dev, drv_data); + + if (mt792x_needs_cnm_runtime(drv_data)) + *fw_features |= MT792x_FW_CAP_CNM; + if (!(*fw_features & MT792x_FW_CAP_CNM)) { ops->remain_on_channel = NULL; ops->cancel_remain_on_channel = NULL; From 35a5dcc71735eb42606be34e313f9eb8148b6a42 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Sat, 25 Apr 2026 14:50:10 -0500 Subject: [PATCH 36/71] wifi: mt76: mt7925: add MT7927 PCIe support Add the missing MT7927 device support in the mt7925 PCI path. This ensures MT7927 is identified correctly and uses the proper initialization flow. Co-developed-by: Javier Tia Signed-off-by: Javier Tia Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260425195011.790265-21-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/pci.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c index fd6d0b029ef4..ea64303283ed 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c @@ -16,6 +16,12 @@ static const struct pci_device_id mt7925_pci_device_table[] = { .driver_data = (kernel_ulong_t)MT7925_FIRMWARE_WM }, { PCI_DEVICE(PCI_VENDOR_ID_MEDIATEK, 0x0717), .driver_data = (kernel_ulong_t)MT7925_FIRMWARE_WM }, + { PCI_DEVICE(PCI_VENDOR_ID_MEDIATEK, 0x7927), + .driver_data = (kernel_ulong_t)MT7927_FIRMWARE_WM }, + { PCI_DEVICE(PCI_VENDOR_ID_MEDIATEK, 0x6639), + .driver_data = (kernel_ulong_t)MT7927_FIRMWARE_WM }, + { PCI_DEVICE(PCI_VENDOR_ID_MEDIATEK, 0x0738), + .driver_data = (kernel_ulong_t)MT7927_FIRMWARE_WM }, { }, }; @@ -376,7 +382,8 @@ static int mt7925_pci_probe(struct pci_dev *pdev, if (ret) goto err_free_pci_vec; - is_mt7927_hw = (pdev->device == 0x6639 || pdev->device == 0x7927); + is_mt7927_hw = (pdev->device == 0x6639 || pdev->device == 0x7927 || + pdev->device == 0x0738); /* MT7927: ASPM L1 causes unreliable WFDMA register access */ if (mt7925_disable_aspm || is_mt7927_hw) @@ -436,6 +443,13 @@ static int mt7925_pci_probe(struct pci_dev *pdev, dev_info(mdev->dev, "ASIC revision: %04x\n", mdev->rev); + if (is_mt7927_hw && mt76_chip(mdev) != 0x7927) { + dev_info(mdev->dev, + "MT7927 raw CHIPID=0x%04x, forcing chip=0x7927\n", + mt76_chip(mdev)); + mdev->rev = (0x7927 << 16) | (mdev->rev & 0xff); + } + mt76_rmw_field(dev, MT_HW_EMI_CTL, MT_HW_EMI_CTL_SLPPROT_EN, 1); ret = mt792x_wfsys_reset(dev); From bef8367b48e94cc16f033d0a37711e38d401f739 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Sat, 25 Apr 2026 14:50:11 -0500 Subject: [PATCH 37/71] wifi: mt76: mt7925: add MT7927 USB support Add the MT7927 USB device ID and normalize the raw 0x6639 CHIPID to MT7927 so the shared mt7925u code can use the proper chip-specific paths. Restore the required band setup after USB MAC reset before restarting the device. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260425195011.790265-22-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/usb.c | 15 +++++++++++++++ drivers/net/wireless/mediatek/mt76/usb.c | 1 + 2 files changed, 16 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/usb.c b/drivers/net/wireless/mediatek/mt76/mt7925/usb.c index 7071b6c93d14..caacc54be71d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/usb.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/usb.c @@ -10,6 +10,8 @@ #include "mac.h" static const struct usb_device_id mt7925u_device_table[] = { + { USB_DEVICE_AND_INTERFACE_INFO(0x0e8d, 0x6639, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)MT7925_FIRMWARE_WM }, { USB_DEVICE_AND_INTERFACE_INFO(0x0e8d, 0x7925, 0xff, 0xff, 0xff), .driver_info = (kernel_ulong_t)MT7925_FIRMWARE_WM }, /* Netgear, Inc. A8500 */ @@ -123,6 +125,12 @@ static int mt7925u_mac_reset(struct mt792x_dev *dev) if (err) goto out; + if (is_mt7927(&dev->mt76)) { + err = mt7925_mcu_set_dbdc(&dev->mphy, true); + if (err) + goto out; + } + err = __mt7925_start(&dev->phy); out: clear_bit(MT76_RESET, &dev->mphy.state); @@ -200,6 +208,13 @@ static int mt7925u_probe(struct usb_interface *usb_intf, (mt76_rr(dev, MT_HW_REV) & 0xff); dev_dbg(mdev->dev, "ASIC revision: %04x\n", mdev->rev); + if (mt76_chip(mdev) == 0x6639) { + dev_dbg(mdev->dev, + "MT7927 raw CHIPID=0x%04x, forcing chip=0x7927\n", + mt76_chip(mdev)); + mdev->rev = (0x7927 << 16) | (mdev->rev & 0xff); + } + if (mt76_get_field(dev, MT_CONN_ON_MISC, MT_TOP_MISC2_FW_N9_RDY)) { ret = mt792xu_wfsys_reset(dev); if (ret) diff --git a/drivers/net/wireless/mediatek/mt76/usb.c b/drivers/net/wireless/mediatek/mt76/usb.c index 632ae755c7a6..9bd61c9e94ed 100644 --- a/drivers/net/wireless/mediatek/mt76/usb.c +++ b/drivers/net/wireless/mediatek/mt76/usb.c @@ -925,6 +925,7 @@ mt76u_ac_to_hwq(struct mt76_dev *dev, struct mt76_queue *q, u8 qid) q->ep = q->hw_idx + 1; break; } + case 0x6639: case 0x7961: case 0x7925: q->hw_idx = mt76_ac_to_hwq(ac); From d3c854068bad22a25db6515f12784f64c663fed2 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Sat, 25 Apr 2026 10:47:19 -0500 Subject: [PATCH 38/71] wifi: mt76: mt7925: keep TX BA state in the primary WCID For MLO, the same TID can run over different links. Keeping TX BA state in a link WCID makes the state depend on which link starts aggregation first. Store it in the primary WCID instead, so the BA state stays stable across links. Fixes: 44eb173bdd4f ("wifi: mt76: mt7925: add link handling in mt7925_txwi_free") Tested-by: Yao Ting Hsieh Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260425154721.738101-1-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/mac.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c index d681005cc6ff..5dd895c3f205 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c @@ -846,7 +846,6 @@ static void mt7925_tx_check_aggr(struct ieee80211_sta *sta, struct sk_buff *skb, { struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); struct ieee80211_link_sta *link_sta; - struct mt792x_link_sta *mlink; struct mt792x_sta *msta; bool is_8023; u16 fc, tid; @@ -885,14 +884,14 @@ static void mt7925_tx_check_aggr(struct ieee80211_sta *sta, struct sk_buff *skb, msta = (struct mt792x_sta *)sta->drv_priv; - if (sta->mlo && msta->deflink_id != IEEE80211_LINK_UNSPECIFIED) - mlink = rcu_dereference(msta->link[msta->deflink_id]); - else - mlink = &msta->deflink; - - if (!test_and_set_bit(tid, &mlink->wcid.ampdu_state)) { + /* Packets belonging to the same TID can be transmitted over multiple + * links. Keep the TX BA session state in the primary link so all links + * share the same AMPDU bookkeeping. + */ + if (!test_and_set_bit(tid, &msta->deflink.wcid.ampdu_state)) { if (ieee80211_start_tx_ba_session(sta, tid, 0)) - clear_bit(tid, &mlink->wcid.ampdu_state); + clear_bit(tid, &msta->deflink.wcid.ampdu_state); + } } From a13d3411390b10f5fcb81a654cbe0b19000144c5 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Sat, 25 Apr 2026 10:47:20 -0500 Subject: [PATCH 39/71] wifi: mt76: mt7925: pass WCID explicitly to mt7925_mcu_sta_ba() mt7925_mcu_sta_ba() currently hides the BA target behind an implicit WCID lookup. Pass the WCID explicitly so the caller controls which BA context is being programmed. No functional change intended. Tested-by: Yao Ting Hsieh Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260425154721.738101-2-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/mcu.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c index f667eba99a51..8f0970d2ba31 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c @@ -655,9 +655,8 @@ void mt7925_mcu_rx_event(struct mt792x_dev *dev, struct sk_buff *skb) static int mt7925_mcu_sta_ba(struct mt76_dev *dev, struct mt76_vif_link *mvif, struct ieee80211_ampdu_params *params, - bool enable, bool tx) + struct mt76_wcid *wcid, bool enable, bool tx) { - struct mt76_wcid *wcid = (struct mt76_wcid *)params->sta->drv_priv; struct sta_rec_ba_uni *ba; struct sk_buff *skb; struct tlv *tlv; @@ -695,6 +694,7 @@ int mt7925_mcu_uni_tx_ba(struct mt792x_dev *dev, msta->deflink.wcid.amsdu = false; return mt7925_mcu_sta_ba(&dev->mt76, &mvif->bss_conf.mt76, params, + &msta->deflink.wcid, enable, true); } @@ -706,6 +706,7 @@ int mt7925_mcu_uni_rx_ba(struct mt792x_dev *dev, struct mt792x_vif *mvif = msta->vif; return mt7925_mcu_sta_ba(&dev->mt76, &mvif->bss_conf.mt76, params, + &msta->deflink.wcid, enable, false); } From 733d8aa8d09be3068bcf2f09278446aca7921805 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Sat, 25 Apr 2026 10:47:21 -0500 Subject: [PATCH 40/71] wifi: mt76: mt7925: program BA state on active links With MLO, traffic for one TID can be sent on any active link. Programming BA state only on the default link leaves the other active links out of sync. Program BA state on all active links instead. Fixes: 766ea2cf5a39 ("Revert "wifi: mt76: mt7925: Update mt7925_mcu_uni_[tx,rx]_ba for MLO"") Tested-by: Yao Ting Hsieh Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260425154721.738101-3-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7925/main.c | 10 +-- .../net/wireless/mediatek/mt76/mt7925/mcu.c | 67 ++++++++++++++----- .../wireless/mediatek/mt76/mt7925/mt7925.h | 4 +- 3 files changed, 59 insertions(+), 22 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index 2599b738496f..c20e5316fae0 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -1410,22 +1410,22 @@ mt7925_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif, case IEEE80211_AMPDU_RX_START: mt76_rx_aggr_start(&dev->mt76, &msta->deflink.wcid, tid, ssn, params->buf_size); - mt7925_mcu_uni_rx_ba(dev, params, true); + mt7925_mcu_uni_rx_ba(dev, params, vif, true); break; case IEEE80211_AMPDU_RX_STOP: mt76_rx_aggr_stop(&dev->mt76, &msta->deflink.wcid, tid); - mt7925_mcu_uni_rx_ba(dev, params, false); + mt7925_mcu_uni_rx_ba(dev, params, vif, false); break; case IEEE80211_AMPDU_TX_OPERATIONAL: mtxq->aggr = true; mtxq->send_bar = false; - mt7925_mcu_uni_tx_ba(dev, params, true); + mt7925_mcu_uni_tx_ba(dev, params, vif, true); break; case IEEE80211_AMPDU_TX_STOP_FLUSH: case IEEE80211_AMPDU_TX_STOP_FLUSH_CONT: mtxq->aggr = false; clear_bit(tid, &msta->deflink.wcid.ampdu_state); - mt7925_mcu_uni_tx_ba(dev, params, false); + mt7925_mcu_uni_tx_ba(dev, params, vif, false); break; case IEEE80211_AMPDU_TX_START: set_bit(tid, &msta->deflink.wcid.ampdu_state); @@ -1434,7 +1434,7 @@ mt7925_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif, case IEEE80211_AMPDU_TX_STOP_CONT: mtxq->aggr = false; clear_bit(tid, &msta->deflink.wcid.ampdu_state); - mt7925_mcu_uni_tx_ba(dev, params, false); + mt7925_mcu_uni_tx_ba(dev, params, vif, false); ieee80211_stop_tx_ba_cb_irqsafe(vif, sta->addr, tid); break; } diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c index 8f0970d2ba31..e94fa544ff20 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c @@ -682,32 +682,69 @@ mt7925_mcu_sta_ba(struct mt76_dev *dev, struct mt76_vif_link *mvif, MCU_UNI_CMD(STA_REC_UPDATE), true); } -/** starec & wtbl **/ int mt7925_mcu_uni_tx_ba(struct mt792x_dev *dev, struct ieee80211_ampdu_params *params, - bool enable) + struct ieee80211_vif *vif, bool enable) { - struct mt792x_sta *msta = (struct mt792x_sta *)params->sta->drv_priv; - struct mt792x_vif *mvif = msta->vif; + struct ieee80211_sta *sta = params->sta; + struct mt792x_sta *msta = (struct mt792x_sta *)sta->drv_priv; + struct ieee80211_link_sta *link_sta; + unsigned int link_id; - if (enable && !params->amsdu) - msta->deflink.wcid.amsdu = false; + for_each_sta_active_link(vif, sta, link_sta, link_id) { + struct mt792x_link_sta *mlink; + struct mt792x_bss_conf *mconf; + int ret; - return mt7925_mcu_sta_ba(&dev->mt76, &mvif->bss_conf.mt76, params, - &msta->deflink.wcid, - enable, true); + mlink = mt792x_sta_to_link(msta, link_id); + if (!mlink) + return -EINVAL; + + mconf = mt792x_vif_to_link(msta->vif, link_id); + if (!mconf) + return -EINVAL; + + if (enable && !params->amsdu) + mlink->wcid.amsdu = false; + + ret = mt7925_mcu_sta_ba(&dev->mt76, &mconf->mt76, params, + &mlink->wcid, enable, true); + if (ret) + return ret; + } + + return 0; } int mt7925_mcu_uni_rx_ba(struct mt792x_dev *dev, struct ieee80211_ampdu_params *params, - bool enable) + struct ieee80211_vif *vif, bool enable) { - struct mt792x_sta *msta = (struct mt792x_sta *)params->sta->drv_priv; - struct mt792x_vif *mvif = msta->vif; + struct ieee80211_sta *sta = params->sta; + struct mt792x_sta *msta = (struct mt792x_sta *)sta->drv_priv; + struct ieee80211_link_sta *link_sta; + unsigned int link_id; - return mt7925_mcu_sta_ba(&dev->mt76, &mvif->bss_conf.mt76, params, - &msta->deflink.wcid, - enable, false); + for_each_sta_active_link(vif, sta, link_sta, link_id) { + struct mt792x_link_sta *mlink; + struct mt792x_bss_conf *mconf; + int ret; + + mlink = mt792x_sta_to_link(msta, link_id); + if (!mlink) + return -EINVAL; + + mconf = mt792x_vif_to_link(msta->vif, link_id); + if (!mconf) + return -EINVAL; + + ret = mt7925_mcu_sta_ba(&dev->mt76, &mconf->mt76, params, + &mlink->wcid, enable, false); + if (ret) + return ret; + } + + return 0; } static int mt7925_mcu_read_eeprom(struct mt792x_dev *dev, u32 offset, u8 *val) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h index 242f83f90dd4..4cc259418afc 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h @@ -318,10 +318,10 @@ int mt7925_mcu_set_beacon_filter(struct mt792x_dev *dev, bool enable); int mt7925_mcu_uni_tx_ba(struct mt792x_dev *dev, struct ieee80211_ampdu_params *params, - bool enable); + struct ieee80211_vif *vif, bool enable); int mt7925_mcu_uni_rx_ba(struct mt792x_dev *dev, struct ieee80211_ampdu_params *params, - bool enable); + struct ieee80211_vif *vif, bool enable); void mt7925_mlo_pm_work(struct work_struct *work); void mt7925_scan_work(struct work_struct *work); void mt7925_roc_work(struct work_struct *work); From a1152244702bb31b64650e5ca8308142286c0e4a Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Sat, 25 Apr 2026 09:46:48 -0500 Subject: [PATCH 41/71] wifi: mt76: mt792x: skip MLD header rewrite for 802.3 encap TX mt792x_tx() rewrites addr1/addr2/addr3 by treating skb->data as an 802.11 header for MLD traffic. That is only valid for native 802.11 frames. Direct 802.3 TX can also reach this path with IEEE80211_TX_CTL_HW_80211_ENCAP set, where skb->data is not an 802.11 header. Skip the MLD header rewrite for HW-encap packets to avoid corrupting 802.3 frame contents. Fixes: ebb1406813c6 ("wifi: mt76: mt7925: add link handling to txwi") Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260425144648.734030-1-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt792x_core.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_core.c b/drivers/net/wireless/mediatek/mt76/mt792x_core.c index 1d807abc7125..e5ef724dd2d3 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_core.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_core.c @@ -105,7 +105,8 @@ void mt792x_tx(struct ieee80211_hw *hw, struct ieee80211_tx_control *control, wcid = &mvif->sta.deflink.wcid; } - if (vif && control->sta && ieee80211_vif_is_mld(vif)) { + if (vif && control->sta && ieee80211_vif_is_mld(vif) && + !(info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP)) { struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; struct ieee80211_link_sta *link_sta; struct ieee80211_bss_conf *conf; From c7369a00860a0704461d440e7c3bf9b49bfdbaee Mon Sep 17 00:00:00 2001 From: Aviel Zohar Date: Mon, 13 Apr 2026 06:31:34 +0300 Subject: [PATCH 42/71] wifi: mt76: mt7925: validate skb length in testmode query In mt7925_tm_query(), the response skb from mt76_mcu_send_and_get_msg() is used in a memcpy without validating its length: memcpy(evt_resp, skb->data + 8, MT7925_EVT_RSP_LEN); where MT7925_EVT_RSP_LEN is 512. If the firmware returns a response shorter than 520 bytes (8 + 512), this reads beyond the skb data buffer. The over-read data is then returned to userspace via nla_put() in mt7925_testmode_dump(). Add a length check before the memcpy to ensure the skb contains sufficient data. Fixes: c948b5da6bbe ("wifi: mt76: mt7925: add Mediatek Wi-Fi7 driver for mt7925 chips") Signed-off-by: Aviel Zohar Link: https://patch.msgid.link/20260413033136.5417-2-avielzohar123@gmail.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/testmode.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/testmode.c b/drivers/net/wireless/mediatek/mt76/mt7925/testmode.c index 3d40aacfc011..22a8f1ddc321 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/testmode.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/testmode.c @@ -105,6 +105,11 @@ mt7925_tm_query(struct mt792x_dev *dev, struct mt7925_tm_cmd *req, if (ret) goto out; + if (skb->len < MT7925_EVT_RSP_LEN + 8) { + ret = -EINVAL; + goto out; + } + memcpy((char *)evt_resp, (char *)skb->data + 8, MT7925_EVT_RSP_LEN); out: From 4497665cf4f680f8befbe3c980c93917a195d26b Mon Sep 17 00:00:00 2001 From: Aviel Zohar Date: Mon, 13 Apr 2026 06:31:35 +0300 Subject: [PATCH 43/71] wifi: mt76: mt7915: validate skb length in txpower SKU query In mt7915_mcu_get_txpower_sku(), the response skb from mt76_mcu_send_and_get_msg() is used in memcpy without validating its length: For TX_POWER_INFO_RATE: memcpy(res, skb->data + 4, sizeof(res)); where sizeof(res) is MT7915_SKU_RATE_NUM * 2 = 322 bytes. For TX_POWER_INFO_PATH: memcpy(txpower, skb->data + 4, len); In both cases, if the firmware returns a response shorter than the expected size, the memcpy reads beyond the skb data buffer. The data surfaces to userspace via debugfs (txpower_sku and txpower_path). Add length checks for both code paths before the memcpy. Signed-off-by: Aviel Zohar Link: https://patch.msgid.link/20260413033136.5417-3-avielzohar123@gmail.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7915/mcu.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c index 318c38149463..4a381d351e61 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c @@ -3534,10 +3534,18 @@ int mt7915_mcu_get_txpower_sku(struct mt7915_phy *phy, s8 *txpower, int len, if (category == TX_POWER_INFO_RATE) { s8 res[MT7915_SKU_RATE_NUM][2]; + if (skb->len < sizeof(res) + 4) { + dev_kfree_skb(skb); + return -EINVAL; + } memcpy(res, skb->data + 4, sizeof(res)); for (i = 0; i < len; i++) txpower[i] = res[i][req.band_idx]; } else if (category == TX_POWER_INFO_PATH) { + if (skb->len < len + 4) { + dev_kfree_skb(skb); + return -EINVAL; + } memcpy(txpower, skb->data + 4, len); } From 831074096d0450308357271fc0ffd3f600a2487e Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Sun, 31 May 2026 11:10:59 +0200 Subject: [PATCH 44/71] wifi: mt76: mt7996: Fix possible token leak in mt7996_tx_prepare_skb() If link_conf or link_sta lookup fails in mt7996_tx_prepare_skb routine, mt7996 driver leaks an already allocated tx token. Fix the issue releasing the token in case of error. Fixes: 7ef0c7ad735b0 ("wifi: mt76: mt7996: Implement MLD address translation for EAPOL") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260531-mt7996_tx_prepare_skb-token-leack-v1-1-2b9c9f59ceb1@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 8 ++++++-- drivers/net/wireless/mediatek/mt76/tx.c | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index c98446057282..ade8706623b0 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -1067,11 +1067,11 @@ int mt7996_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr, link_conf = rcu_dereference(vif->link_conf[wcid->link_id]); if (!link_conf) - return -EINVAL; + goto error_release_token; link_sta = rcu_dereference(sta->link[wcid->link_id]); if (!link_sta) - return -EINVAL; + goto error_release_token; dma_sync_single_for_cpu(mdev->dma_dev, tx_info->buf[1].addr, tx_info->buf[1].len, DMA_TO_DEVICE); @@ -1176,6 +1176,10 @@ int mt7996_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr, tx_info->nbuf = MT_CT_DMA_BUF_NUM; return 0; + +error_release_token: + mt76_token_release(mdev, id, NULL); + return -EINVAL; } u32 mt7996_wed_init_buf(void *ptr, dma_addr_t phys, int token_id) diff --git a/drivers/net/wireless/mediatek/mt76/tx.c b/drivers/net/wireless/mediatek/mt76/tx.c index 22f9690634c9..f96d9c471853 100644 --- a/drivers/net/wireless/mediatek/mt76/tx.c +++ b/drivers/net/wireless/mediatek/mt76/tx.c @@ -933,7 +933,7 @@ mt76_token_release(struct mt76_dev *dev, int token, bool *wake) #endif } - if (dev->token_count < dev->token_size - MT76_TOKEN_FREE_THR && + if (wake && dev->token_count < dev->token_size - MT76_TOKEN_FREE_THR && dev->phy.q_tx[0]->blocked) *wake = true; From 61370e6674b5253de5686813ceeceebc35a7d3e5 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Sun, 31 May 2026 11:38:57 +0200 Subject: [PATCH 45/71] wifi: mt76: mt7996: Fix possible NULL pointer dereference in mt7996_mac_write_txwi_80211() For injected frames (e.g. via radiotap), mac80211 can pass info->control.vif = NULL, as explicitly noted in struct ieee80211_tx_info. Check vif pointer before executing ieee80211_vif_is_mld() in mt7996_mac_write_txwi_80211 routine in order to avoid a possible NULL pointer dereference. Fixes: f0b0b239b8f36 ("wifi: mt76: mt7996: rework mt7996_mac_write_txwi() for MLO support") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260531-mt7996_mac_write_txwi_80211-null-ptr-deref-v1-1-6dd38e1d3422@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index ade8706623b0..2224fbe04391 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -757,6 +757,7 @@ mt7996_mac_write_txwi_80211(struct mt7996_dev *dev, __le32 *txwi, bool multicast = is_multicast_ether_addr(hdr->addr1); u8 tid = skb->priority & IEEE80211_QOS_CTL_TID_MASK; __le16 fc = hdr->frame_control, sc = hdr->seq_ctrl; + struct ieee80211_vif *vif = info->control.vif; u16 seqno = le16_to_cpu(sc); bool hw_bigtk = false; u8 fc_type, fc_stype; @@ -819,7 +820,7 @@ mt7996_mac_write_txwi_80211(struct mt7996_dev *dev, __le32 *txwi, txwi[3] |= cpu_to_le32(MT_TXD3_REM_TX_COUNT); } - if (multicast && ieee80211_vif_is_mld(info->control.vif)) { + if (multicast && vif && ieee80211_vif_is_mld(vif)) { val = MT_TXD3_SN_VALID | FIELD_PREP(MT_TXD3_SEQ, IEEE80211_SEQ_TO_SN(seqno)); txwi[3] |= cpu_to_le32(val); @@ -839,12 +840,12 @@ mt7996_mac_write_txwi_80211(struct mt7996_dev *dev, __le32 *txwi, txwi[3] &= ~cpu_to_le32(MT_TXD3_HW_AMSDU); } - if (ieee80211_vif_is_mld(info->control.vif) && + if (vif && ieee80211_vif_is_mld(vif) && (multicast || unlikely(skb->protocol == cpu_to_be16(ETH_P_PAE)))) txwi[5] |= cpu_to_le32(MT_TXD5_FL); if (ieee80211_is_nullfunc(fc) && ieee80211_has_a4(fc) && - ieee80211_vif_is_mld(info->control.vif)) { + vif && ieee80211_vif_is_mld(vif)) { txwi[5] |= cpu_to_le32(MT_TXD5_FL); txwi[6] |= cpu_to_le32(MT_TXD6_DIS_MAT); } From 729c83a3330c0a56662cd0d8e40db96d41c00a54 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Sun, 31 May 2026 10:55:04 +0200 Subject: [PATCH 46/71] wifi: mt76: mt7996: fix reading zeroed info->control.flags after mt76_tx_status_skb_add() mt76_tx_status_skb_add() zeroes the mt76_tx_cb struct stored at info->status.status_driver_data via memset(). Since info->control and info->status are members of the same union in ieee80211_tx_info, this overwrites info->control.flags. In mt7996_tx_prepare_skb(), mt76_tx_status_skb_add() is called before mt7996_mac_write_txwi(), which re-reads info->control.flags to extract IEEE80211_TX_CTRL_MLO_LINK. Because the field has been zeroed, the link_id always resolves to 0 for frames using global_wcid, leading to incorrect TXWI configuration. Fix this by passing link_id as an explicit parameter to mt7996_mac_write_txwi(). In mt7996_tx_prepare_skb(), the link_id is already extracted from info->control.flags before the destructive mt76_tx_status_skb_add() call. For the beacon and inband discovery callers in mcu.c, use link_conf->link_id directly. Fixes: f0b0b239b8f36 ("wifi: mt76: mt7996: rework mt7996_mac_write_txwi() for MLO support") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260531-mt76_tx_status_skb_add-overwrite-fix-v2-1-b73c4b4a9798@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 14 ++++---------- drivers/net/wireless/mediatek/mt76/mt7996/mcu.c | 5 +++-- drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h | 3 ++- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index 2224fbe04391..44713a7a86f8 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -857,7 +857,8 @@ mt7996_mac_write_txwi_80211(struct mt7996_dev *dev, __le32 *txwi, void mt7996_mac_write_txwi(struct mt7996_dev *dev, __le32 *txwi, struct sk_buff *skb, struct mt76_wcid *wcid, struct ieee80211_key_conf *key, int pid, - enum mt76_txq_id qid, u32 changed) + enum mt76_txq_id qid, u32 changed, + unsigned int link_id) { struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data; struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); @@ -867,7 +868,6 @@ void mt7996_mac_write_txwi(struct mt7996_dev *dev, __le32 *txwi, bool is_8023 = info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP; struct mt76_vif_link *mlink = NULL; struct mt7996_vif *mvif; - unsigned int link_id; u16 tx_count = 15; u32 val; bool inband_disc = !!(changed & (BSS_CHANGED_UNSOL_BCAST_PROBE_RESP | @@ -875,17 +875,11 @@ void mt7996_mac_write_txwi(struct mt7996_dev *dev, __le32 *txwi, bool beacon = !!(changed & (BSS_CHANGED_BEACON | BSS_CHANGED_BEACON_ENABLED)) && (!inband_disc); - if (wcid != &dev->mt76.global_wcid) - link_id = wcid->link_id; - else - link_id = u32_get_bits(info->control.flags, - IEEE80211_TX_CTRL_MLO_LINK); - mvif = vif ? (struct mt7996_vif *)vif->drv_priv : NULL; if (mvif) { if (wcid->offchannel) mlink = rcu_dereference(mvif->mt76.offchannel_link); - if (!mlink) + if (!mlink && link_id != IEEE80211_LINK_UNSPECIFIED) mlink = rcu_dereference(mvif->mt76.link[link_id]); } @@ -1097,7 +1091,7 @@ int mt7996_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr, /* Transmit non qos data by 802.11 header and need to fill txd by host*/ if (!is_8023 || pid >= MT_PACKET_ID_FIRST) mt7996_mac_write_txwi(dev, txwi_ptr, tx_info->skb, wcid, key, - pid, qid, 0); + pid, qid, 0, link_id); /* MT7996 and MT7992 require driver to provide the MAC TXP for AddBA * req diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index 8be40d60ad29..a14c63438923 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -3103,7 +3103,7 @@ mt7996_mcu_beacon_cont(struct mt7996_dev *dev, buf = (u8 *)bcn + sizeof(*bcn); mt7996_mac_write_txwi(dev, (__le32 *)buf, skb, wcid, NULL, 0, 0, - BSS_CHANGED_BEACON); + BSS_CHANGED_BEACON, link_conf->link_id); memcpy(buf + MT_TXD_SIZE, skb->data, skb->len); } @@ -3249,7 +3249,8 @@ int mt7996_mcu_beacon_inband_discov(struct mt7996_dev *dev, buf = (u8 *)tlv + sizeof(*discov); - mt7996_mac_write_txwi(dev, (__le32 *)buf, skb, wcid, NULL, 0, 0, changed); + mt7996_mac_write_txwi(dev, (__le32 *)buf, skb, wcid, NULL, 0, 0, + changed, link_conf->link_id); memcpy(buf + MT_TXD_SIZE, skb->data, skb->len); diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index 0dc4198fcf8b..0d6488522ba7 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -874,7 +874,8 @@ void mt7996_mac_enable_nf(struct mt7996_dev *dev, u8 band); void mt7996_mac_write_txwi(struct mt7996_dev *dev, __le32 *txwi, struct sk_buff *skb, struct mt76_wcid *wcid, struct ieee80211_key_conf *key, int pid, - enum mt76_txq_id qid, u32 changed); + enum mt76_txq_id qid, u32 changed, + unsigned int link_id); void mt7996_mac_update_beacons(struct mt7996_phy *phy); void mt7996_mac_set_coverage_class(struct mt7996_phy *phy); void mt7996_mac_work(struct work_struct *work); From 6b294950eaac246e6b0f42d74aa643ff36384c6e Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Fri, 22 May 2026 09:24:52 +0200 Subject: [PATCH 47/71] wifi: mt76: mt7996: remove redundant pdev->bus check in probe Drop the unnecessary pdev->bus NULL check in mt7996_pci_probe() since the pointer is already dereferenced earlier in mt76_pci_disable_aspm(), making the check dead code. Silences the related Smatch warning. Fixes: 377aa17d2aed ("wifi: mt76: mt7996: Add NPU offload support to MT7996 driver") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260522-mt7996-pdev-bus-fix-v1-1-c91716484365@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/pci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/pci.c b/drivers/net/wireless/mediatek/mt76/mt7996/pci.c index 12523ddba630..b7d9193e042f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/pci.c @@ -141,7 +141,7 @@ static int mt7996_pci_probe(struct pci_dev *pdev, dev->hif2 = hif2; mt76_npu_init(mdev, pci_resource_start(pdev, 0), - pdev->bus && pci_domain_nr(pdev->bus) ? 3 : 2); + pci_domain_nr(pdev->bus) ? 3 : 2); ret = mt7996_mmio_wed_init(dev, pdev, false, &irq); if (ret < 0) From 20b126920a259df4d7dcae19fcfe2c57a74d6b2e Mon Sep 17 00:00:00 2001 From: Jiajia Liu Date: Thu, 28 May 2026 11:38:14 +0800 Subject: [PATCH 48/71] wifi: mt76: add wcid publish check in mt76_sta_add Since mt7925_mac_sta_add publishes wcid, add publish check in mt76_sta_add to avoid reinitializing the wcid->poll_list. Found dev->sta_poll_list corruption when using mt7925 and 7.1-rc4. According to the corruption information, prev->next was changed to itself. wlan0: disconnect from AP 90:fb:5d:94:8b:e3 for new auth to 90:fb:5d:94:8b:e2 wlan0: authenticate with 90:fb:5d:94:8b:e2 (local address=84:9e:56:9c:7e:6b) wlan0: send auth to 90:fb:5d:94:8b:e2 (try 1/3) slab kmalloc-8k start ffff8c80958a6000 pointer offset 4160 size 8192 list_add corruption. prev->next should be next (ffff8c808a7488f8), but was ffff8c80958a7040. (prev=ffff8c80958a7040). mt76_wcid_add_poll+0x95/0xd0 [mt76] mt7925_mac_add_txs.part.0+0xa5/0xe0 [mt7925_common] mt7925_rx_check+0xa7/0xc0 [mt7925_common] mt76_dma_rx_poll+0x50d/0x790 [mt76] mt792x_poll_rx+0x52/0xe0 [mt792x_lib] Signed-off-by: Jiajia Liu Link: https://patch.msgid.link/20260528033814.46418-1-liujiajia@kylinos.cn Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mac80211.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c index dd68776ada28..dd09ef1bd0c2 100644 --- a/drivers/net/wireless/mediatek/mt76/mac80211.c +++ b/drivers/net/wireless/mediatek/mt76/mac80211.c @@ -1578,6 +1578,7 @@ mt76_sta_add(struct mt76_phy *phy, struct ieee80211_vif *vif, { struct mt76_wcid *wcid = (struct mt76_wcid *)sta->drv_priv; struct mt76_dev *dev = phy->dev; + struct mt76_wcid *published; int ret; int i; @@ -1597,11 +1598,19 @@ mt76_sta_add(struct mt76_phy *phy, struct ieee80211_vif *vif, mtxq->wcid = wcid->idx; } - ewma_signal_init(&wcid->rssi); - rcu_assign_pointer(dev->wcid[wcid->idx], wcid); + published = rcu_dereference_protected(dev->wcid[wcid->idx], + lockdep_is_held(&dev->mutex)); + if (published != wcid) { + WARN_ON_ONCE(published); + ewma_signal_init(&wcid->rssi); + rcu_assign_pointer(dev->wcid[wcid->idx], wcid); + mt76_wcid_init(wcid, phy->band_idx); + } else { + wcid->phy_idx = phy->band_idx; + } + phy->num_sta++; - mt76_wcid_init(wcid, phy->band_idx); out: mutex_unlock(&dev->mutex); From 2dd78856223484895306351df1f903a4b75d213f Mon Sep 17 00:00:00 2001 From: Jiajia Liu Date: Tue, 2 Jun 2026 13:43:49 +0800 Subject: [PATCH 49/71] wifi: mt76: transform aspm_conf for pci_disable_link_state commit b478e162f227 ("PCI/ASPM: Consolidate link state defines") changed PCIE_LINK_STATE_L0S (1) to (BIT(0) | BIT(1)). PCI_EXP_LNKCTL_ASPM_L0S (1) and PCI_EXP_LNKCTL_ASPM_L1 (2) are no longer matched with PCIE_LINK_STATE_L0S (3) and PCIE_LINK_STATE_L1 (4). On the platform enabling ASPM L0s and L1, mt76_pci_disable_aspm is not able to disable L1. Fix this by transforming aspm_conf to pcie link state. Signed-off-by: Jiajia Liu Link: https://patch.msgid.link/20260602054349.42429-1-liujia6264@gmail.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/pci.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/pci.c b/drivers/net/wireless/mediatek/mt76/pci.c index 833923ab2483..11fe62aa8113 100644 --- a/drivers/net/wireless/mediatek/mt76/pci.c +++ b/drivers/net/wireless/mediatek/mt76/pci.c @@ -30,8 +30,14 @@ void mt76_pci_disable_aspm(struct pci_dev *pdev) if (IS_ENABLED(CONFIG_PCIEASPM)) { int err; + int state = 0; - err = pci_disable_link_state(pdev, aspm_conf); + if (aspm_conf & PCI_EXP_LNKCTL_ASPM_L0S) + state |= PCIE_LINK_STATE_L0S; + if (aspm_conf & PCI_EXP_LNKCTL_ASPM_L1) + state |= PCIE_LINK_STATE_L1; + + err = pci_disable_link_state(pdev, state); if (!err) return; } From 945c8028299a4712f12759f1ea14e63bba841e9f Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Sat, 25 Apr 2026 11:09:30 -0500 Subject: [PATCH 50/71] wifi: mt76: mt7921u: add MT7902 USB support Add the 0e8d:7902 USB ID and select the MT7902 WM firmware. Use the same USB queue mapping as mt7921/mt7925 so MT7902U can bind and probe through mt7921u driver. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260425160930.739237-1-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7921/usb.c | 2 ++ drivers/net/wireless/mediatek/mt76/usb.c | 1 + 2 files changed, 3 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/usb.c b/drivers/net/wireless/mediatek/mt76/mt7921/usb.c index 9bfc234f306f..4105c1baec1e 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/usb.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/usb.c @@ -13,6 +13,8 @@ #include "../mt76_connac2_mac.h" static const struct usb_device_id mt7921u_device_table[] = { + { USB_DEVICE_AND_INTERFACE_INFO(0x0e8d, 0x7902, 0xff, 0xff, 0xff), + .driver_info = (kernel_ulong_t)MT7902_FIRMWARE_WM }, { USB_DEVICE_AND_INTERFACE_INFO(0x0e8d, 0x7961, 0xff, 0xff, 0xff), .driver_info = (kernel_ulong_t)MT7921_FIRMWARE_WM }, /* Comfast CF-952AX */ diff --git a/drivers/net/wireless/mediatek/mt76/usb.c b/drivers/net/wireless/mediatek/mt76/usb.c index 9bd61c9e94ed..d9638a9b749b 100644 --- a/drivers/net/wireless/mediatek/mt76/usb.c +++ b/drivers/net/wireless/mediatek/mt76/usb.c @@ -926,6 +926,7 @@ mt76u_ac_to_hwq(struct mt76_dev *dev, struct mt76_queue *q, u8 qid) break; } case 0x6639: + case 0x7902: case 0x7961: case 0x7925: q->hw_idx = mt76_ac_to_hwq(ac); From 0cb78a6dbe7deef60ae6427554ebca6e4b536855 Mon Sep 17 00:00:00 2001 From: Dylan Eskew Date: Wed, 15 Apr 2026 08:53:55 -0700 Subject: [PATCH 51/71] wifi: mt76: mt7996: reduce phy work in set_coverage In mt7996_set_coverage_class(), each phy is iterated over in calling mt7996_mac_set_coverage_class(). Thus, the phy2 and phy3 configuration logic in mt7996_mac_set_coverage_class() can be dropped. Signed-off-by: Dylan Eskew Link: https://patch.msgid.link/20260415155354.1135548-2-dylan.eskew@candelatech.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index 44713a7a86f8..83e8e1e7feb4 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -2058,8 +2058,6 @@ void mt7996_mac_set_coverage_class(struct mt7996_phy *phy) { s16 coverage_class = phy->coverage_class; struct mt7996_dev *dev = phy->dev; - struct mt7996_phy *phy2 = mt7996_phy2(dev); - struct mt7996_phy *phy3 = mt7996_phy3(dev); u32 reg_offset; u32 cck = FIELD_PREP(MT_TIMEOUT_VAL_PLCP, 231) | FIELD_PREP(MT_TIMEOUT_VAL_CCA, 48); @@ -2071,14 +2069,6 @@ void mt7996_mac_set_coverage_class(struct mt7996_phy *phy) if (!test_bit(MT76_STATE_RUNNING, &phy->mt76->state)) return; - if (phy2) - coverage_class = max_t(s16, dev->phy.coverage_class, - phy2->coverage_class); - - if (phy3) - coverage_class = max_t(s16, coverage_class, - phy3->coverage_class); - offset = 3 * coverage_class; reg_offset = FIELD_PREP(MT_TIMEOUT_VAL_PLCP, offset) | FIELD_PREP(MT_TIMEOUT_VAL_CCA, offset); From 5fd3385505600934f5faa9635e5b30fa38e548b9 Mon Sep 17 00:00:00 2001 From: Dylan Eskew Date: Wed, 8 Apr 2026 07:50:58 -0700 Subject: [PATCH 52/71] wifi: mt76: mt7996: limit work in set_bitrate_mask Calls to mt7996_set_bitrate_mask() would propagate work for all stations on the ieee80211_hw regardless of the vif specified in the call. To prevent unnecessary work in FW, limit setting the sta_rate to only the specified vif in mt7996_sta_rate_ctrl_update(). Fixes: afff4325548f0 ("wifi: mt76: mt7996: Use proper link_id in link_sta_rc_update callback") Signed-off-by: Dylan Eskew Acked-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260408145057.2356878-2-dylan.eskew@candelatech.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/main.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index b3cbf68bb53d..afbcc8c7b18b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1961,7 +1961,11 @@ static void mt7996_sta_rate_ctrl_update(void *data, struct ieee80211_sta *sta) { struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; struct mt7996_sta_link *msta_link; - u32 *changed = data; + struct mt7996_vif *mvif = data; + u32 changed = IEEE80211_RC_SUPP_RATES_CHANGED; + + if (msta->vif != mvif) + return; msta_link = mt7996_sta_link(msta, msta->deflink_id); if (msta_link) @@ -1974,7 +1978,6 @@ mt7996_set_bitrate_mask(struct ieee80211_hw *hw, struct ieee80211_vif *vif, { struct mt7996_dev *dev = mt7996_hw_dev(hw); struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv; - u32 changed = IEEE80211_RC_SUPP_RATES_CHANGED; mvif->deflink.bitrate_mask = *mask; @@ -1987,7 +1990,7 @@ mt7996_set_bitrate_mask(struct ieee80211_hw *hw, struct ieee80211_vif *vif, * then multiple MCS setting (MCS 4,5,6) is not supported. */ ieee80211_iterate_stations_atomic(hw, mt7996_sta_rate_ctrl_update, - &changed); + mvif); ieee80211_queue_work(hw, &dev->rc_work); return 0; From 8286bbf62dcc2fc328c3960e32c4d3da66720463 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Wed, 1 Apr 2026 13:23:20 -0500 Subject: [PATCH 53/71] wifi: mt76: connac: use a helper to cache txpower_cur The cached txpower value is derived from the bounded channel power after applying the chainmask path delta. Use a helper for that conversion so callers do not open-code it. -- v2: - Rebased onto the latest mt76 tree - Added Reported-by, Tested-by, Co-developed-by and Signed-off-by tags Reported-by: Devin Wittmayer Closes: https://lore.kernel.org/linux-wireless/20260130215839.53270-1-lucid_duck@justthetip.ca/ Tested-by: Devin Wittmayer Tested-by: Satadru Pramanik Co-developed-by: Devin Wittmayer Signed-off-by: Devin Wittmayer Signed-off-by: Sean Wang Reported-by: Lucid Duck Tested-by: Lucid Duck Link: https://patch.msgid.link/20260401182322.64355-1-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt76_connac.h | 2 +- drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c | 9 +++++++++ drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c | 11 ++++++++--- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac.h b/drivers/net/wireless/mediatek/mt76/mt76_connac.h index 1994863dc59d..4afcc6477a99 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac.h +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac.h @@ -436,7 +436,7 @@ mt76_connac_mutex_release(struct mt76_dev *dev, struct mt76_connac_pm *pm) void mt76_connac_gen_ppe_thresh(u8 *he_ppet, int nss, enum nl80211_band band); int mt76_connac_init_tx_queues(struct mt76_phy *phy, int idx, int n_desc, int ring_base, void *wed, u32 flags); - +void mt76_connac_set_txpower_cur(struct mt76_phy *phy, s8 max_power); void mt76_connac_write_hw_txp(struct mt76_dev *dev, struct mt76_tx_info *tx_info, void *txp_ptr, u32 id); diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c index c341595e9138..fc9f782032ef 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c @@ -275,6 +275,15 @@ int mt76_connac_init_tx_queues(struct mt76_phy *phy, int idx, int n_desc, } EXPORT_SYMBOL_GPL(mt76_connac_init_tx_queues); +void mt76_connac_set_txpower_cur(struct mt76_phy *phy, s8 max_power) +{ + int delta; + + delta = mt76_tx_power_path_delta(hweight16(phy->chainmask)); + phy->txpower_cur = max_power - delta; +} +EXPORT_SYMBOL_GPL(mt76_connac_set_txpower_cur); + #define __bitrate_mask_check(_mcs, _mode) \ ({ \ u8 i = 0; \ diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c index be0ec1eaa746..03348463f2db 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c @@ -2235,14 +2235,19 @@ mt76_connac_mcu_rate_txpower_band(struct mt76_phy *phy, .hw_value = ch_list[idx], .band = band, }; - s8 reg_power, sar_power; + s8 reg_power, sar_power, max_power; reg_power = mt76_connac_get_ch_power(phy, &chan, tx_power); sar_power = mt76_get_sar_power(phy, &chan, reg_power); - mt76_get_rate_power_limits(phy, &chan, limits, - sar_power); + max_power = mt76_get_rate_power_limits(phy, &chan, limits, + sar_power); + + if (phy->chandef.chan && + phy->chandef.chan->hw_value == ch_list[idx] && + phy->chandef.chan->band == band) + mt76_connac_set_txpower_cur(phy, max_power); tx_power_tlv.last_msg = ch_list[idx] == last_ch; sku_tlbv.channel = ch_list[idx]; From 317bc1a0590e15c5a47c07e61f89edffcde65098 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Wed, 1 Apr 2026 13:23:21 -0500 Subject: [PATCH 54/71] wifi: mt76: connac: factor out rate power limit calculation Factor out the per-channel rate power limit calculation into a shared helper. This avoids duplicating the same regulatory, SAR and rate-limit logic in multiple paths. Reported-by: Devin Wittmayer Closes: https://lore.kernel.org/linux-wireless/20260130215839.53270-1-lucid_duck@justthetip.ca/ Tested-by: Devin Wittmayer Tested-by: Satadru Pramanik Co-developed-by: Devin Wittmayer Signed-off-by: Devin Wittmayer Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260401182322.64355-2-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt76_connac.h | 3 ++ .../wireless/mediatek/mt76/mt76_connac_mcu.c | 28 +++++++++++++------ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac.h b/drivers/net/wireless/mediatek/mt76/mt76_connac.h index 4afcc6477a99..2aa6078993e9 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac.h +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac.h @@ -437,6 +437,9 @@ void mt76_connac_gen_ppe_thresh(u8 *he_ppet, int nss, enum nl80211_band band); int mt76_connac_init_tx_queues(struct mt76_phy *phy, int idx, int n_desc, int ring_base, void *wed, u32 flags); void mt76_connac_set_txpower_cur(struct mt76_phy *phy, s8 max_power); +s8 mt76_connac_get_rate_power_limit(struct mt76_phy *phy, + struct ieee80211_channel *chan, + struct mt76_power_limits *limits); void mt76_connac_write_hw_txp(struct mt76_dev *dev, struct mt76_tx_info *tx_info, void *txp_ptr, u32 id); diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c index 03348463f2db..6596c9e198f4 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c @@ -2235,15 +2235,10 @@ mt76_connac_mcu_rate_txpower_band(struct mt76_phy *phy, .hw_value = ch_list[idx], .band = band, }; - s8 reg_power, sar_power, max_power; - - reg_power = mt76_connac_get_ch_power(phy, &chan, - tx_power); - sar_power = mt76_get_sar_power(phy, &chan, reg_power); - - max_power = mt76_get_rate_power_limits(phy, &chan, limits, - sar_power); + s8 max_power; + max_power = mt76_connac_get_rate_power_limit(phy, &chan, + limits); if (phy->chandef.chan && phy->chandef.chan->hw_value == ch_list[idx] && phy->chandef.chan->band == band) @@ -2979,6 +2974,23 @@ int mt76_connac_mcu_rdd_cmd(struct mt76_dev *dev, int cmd, u8 index, } EXPORT_SYMBOL_GPL(mt76_connac_mcu_rdd_cmd); +s8 mt76_connac_get_rate_power_limit(struct mt76_phy *phy, + struct ieee80211_channel *chan, + struct mt76_power_limits *limits) +{ + s8 reg_power, sar_power; + int tx_power; + + tx_power = 2 * phy->hw->conf.power_level; + if (!tx_power) + tx_power = 127; + + reg_power = mt76_connac_get_ch_power(phy, chan, tx_power); + sar_power = mt76_get_sar_power(phy, chan, reg_power); + return mt76_get_rate_power_limits(phy, chan, limits, sar_power); +} +EXPORT_SYMBOL_GPL(mt76_connac_get_rate_power_limit); + static int mt76_connac_mcu_send_ram_firmware(struct mt76_dev *dev, const struct mt76_connac2_fw_trailer *hdr, From 879d754e48f6fda26ad06c9d183cd9f7f4f0affd Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Wed, 1 Apr 2026 13:23:22 -0500 Subject: [PATCH 55/71] wifi: mt76: mt792x: report txpower for the requested vif link mt792x currently reports txpower from the generic PHY cached state, which may not match the requested vif/link context. Resolve the requested link channel and derive txpower from that channel instead, with fallback to the current PHY chandef if no valid chanctx is available. Reported-by: Devin Wittmayer Closes: https://lore.kernel.org/linux-wireless/20260130215839.53270-1-lucid_duck@justthetip.ca/ Tested-by: Devin Wittmayer Tested-by: Satadru Pramanik Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260401182322.64355-3-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7921/main.c | 2 +- .../net/wireless/mediatek/mt76/mt7925/main.c | 2 +- drivers/net/wireless/mediatek/mt76/mt792x.h | 2 + .../net/wireless/mediatek/mt76/mt792x_core.c | 41 +++++++++++++++++++ 4 files changed, 45 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/main.c b/drivers/net/wireless/mediatek/mt76/mt7921/main.c index adaf9a53f296..6ffccc023728 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/main.c @@ -1558,7 +1558,7 @@ const struct ieee80211_ops mt7921_ops = { .wake_tx_queue = mt76_wake_tx_queue, .release_buffered_frames = mt76_release_buffered_frames, .channel_switch_beacon = mt7921_channel_switch_beacon, - .get_txpower = mt76_get_txpower, + .get_txpower = mt792x_get_txpower, .get_stats = mt792x_get_stats, .get_et_sset_count = mt792x_get_et_sset_count, .get_et_strings = mt792x_get_et_strings, diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index c20e5316fae0..a9059866b701 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -2504,7 +2504,7 @@ const struct ieee80211_ops mt7925_ops = { .wake_tx_queue = mt76_wake_tx_queue, .release_buffered_frames = mt76_release_buffered_frames, .channel_switch_beacon = mt7925_channel_switch_beacon, - .get_txpower = mt76_get_txpower, + .get_txpower = mt792x_get_txpower, .get_stats = mt792x_get_stats, .get_et_sset_count = mt792x_get_et_sset_count, .get_et_strings = mt792x_get_et_strings, diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h index 59562567cc13..4f823fb705d5 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x.h +++ b/drivers/net/wireless/mediatek/mt76/mt792x.h @@ -416,6 +416,8 @@ void mt792x_roc_timer(struct timer_list *timer); void mt792x_csa_timer(struct timer_list *timer); void mt792x_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif, u32 queues, bool drop); +int mt792x_get_txpower(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + unsigned int link_id, int *dbm); int mt792x_assign_vif_chanctx(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_bss_conf *link_conf, diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_core.c b/drivers/net/wireless/mediatek/mt76/mt792x_core.c index e5ef724dd2d3..b50825eccdaf 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_core.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_core.c @@ -330,6 +330,47 @@ void mt792x_flush(struct ieee80211_hw *hw, struct ieee80211_vif *vif, } EXPORT_SYMBOL_GPL(mt792x_flush); +int mt792x_get_txpower(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + unsigned int link_id, int *dbm) +{ + struct mt76_power_limits limits = {}; + struct ieee80211_bss_conf *link_conf; + struct ieee80211_channel *chan; + struct mt792x_bss_conf *mconf; + struct mt792x_vif *mvif; + struct mt76_phy *phy; + s8 max_power; + + if (!vif) + return mt76_get_txpower(hw, vif, link_id, dbm); + + mvif = (struct mt792x_vif *)vif->drv_priv; + phy = mvif->phy->mt76; + + mt792x_mutex_acquire(mvif->phy->dev); + + link_conf = mt792x_vif_to_bss_conf(vif, link_id); + mconf = link_conf ? mt792x_link_conf_to_mconf(link_conf) : NULL; + if (mconf && mconf->mt76.ctx && mconf->mt76.ctx->def.chan) + chan = mconf->mt76.ctx->def.chan; + else + /* Fall back to the current PHY chandef if the requested link + * does not have a valid channel context. + */ + chan = phy->chandef.chan; + + mt792x_mutex_release(mvif->phy->dev); + + if (!chan) + return -EINVAL; + + max_power = mt76_connac_get_rate_power_limit(phy, chan, &limits); + *dbm = DIV_ROUND_UP(max_power, 2); + + return 0; +} +EXPORT_SYMBOL_GPL(mt792x_get_txpower); + int mt792x_assign_vif_chanctx(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_bss_conf *link_conf, From d23353688dd06a3c019ad322884b94713f2a9466 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Wed, 1 Apr 2026 14:06:31 -0500 Subject: [PATCH 56/71] wifi: mt76: mt792x: add common USB transport reset helpers Add per-device USB reset work and a control-path access check helper for mt7921u and mt7925u. This prepares common infrastructure for transport-level recovery while keeping the reset state per-device for correct lifetime handling. No functional change intended. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260401190632.147042-1-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7921/usb.c | 2 + .../net/wireless/mediatek/mt76/mt7925/usb.c | 2 + drivers/net/wireless/mediatek/mt76/mt792x.h | 5 ++ .../net/wireless/mediatek/mt76/mt792x_usb.c | 50 +++++++++++++++++++ 4 files changed, 59 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/usb.c b/drivers/net/wireless/mediatek/mt76/mt7921/usb.c index 4105c1baec1e..01fa1b03e210 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/usb.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/usb.c @@ -198,6 +198,7 @@ static int mt7921u_probe(struct usb_interface *usb_intf, dev = container_of(mdev, struct mt792x_dev, mt76); dev->fw_features = features; dev->hif_ops = &hif_ops; + mt792xu_reset_work_init(dev); usb_reset_device(udev); @@ -245,6 +246,7 @@ static int mt7921u_probe(struct usb_interface *usb_intf, error: mt76u_queues_deinit(&dev->mt76); + mt792xu_reset_work_cleanup(dev); usb_set_intfdata(usb_intf, NULL); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/usb.c b/drivers/net/wireless/mediatek/mt76/mt7925/usb.c index caacc54be71d..e9f58492bf7d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/usb.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/usb.c @@ -195,6 +195,7 @@ static int mt7925u_probe(struct usb_interface *usb_intf, dev = container_of(mdev, struct mt792x_dev, mt76); dev->fw_features = features; dev->hif_ops = &hif_ops; + mt792xu_reset_work_init(dev); usb_reset_device(udev); @@ -249,6 +250,7 @@ static int mt7925u_probe(struct usb_interface *usb_intf, error: mt76u_queues_deinit(&dev->mt76); + mt792xu_reset_work_cleanup(dev); usb_set_intfdata(usb_intf, NULL); diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h index 4f823fb705d5..95e7fb64599d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x.h +++ b/drivers/net/wireless/mediatek/mt76/mt792x.h @@ -262,6 +262,8 @@ struct mt792x_dev { wait_queue_head_t wait; struct work_struct init_work; + struct work_struct usb_reset_work; + atomic_t usb_reset_pending; u8 fw_debug; u8 fw_features; @@ -516,6 +518,9 @@ int mt792xu_dma_init(struct mt792x_dev *dev, bool resume); int mt792xu_mcu_power_on(struct mt792x_dev *dev); int mt792xu_wfsys_reset(struct mt792x_dev *dev); int mt792xu_init_reset(struct mt792x_dev *dev); +void mt792xu_reset_work_init(struct mt792x_dev *dev); +void mt792xu_reset_work_cleanup(struct mt792x_dev *dev); +int mt792xu_check_bus(struct mt792x_dev *dev); u32 mt792xu_rr(struct mt76_dev *dev, u32 addr); void mt792xu_wr(struct mt76_dev *dev, u32 addr, u32 val); u32 mt792xu_rmw(struct mt76_dev *dev, u32 addr, u32 mask, u32 val); diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_usb.c b/drivers/net/wireless/mediatek/mt76/mt792x_usb.c index be043e964c3c..58d442b39536 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_usb.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_usb.c @@ -11,6 +11,55 @@ #include "mt792x.h" #include "mt76_connac2_mac.h" +static int mt792xu_read32(struct mt76_dev *dev, u32 addr, void *buf) +{ + return __mt76u_vendor_request(dev, MT_VEND_READ_EXT, + USB_DIR_IN | MT_USB_TYPE_VENDOR, + (u16)(addr >> 16), (u16)addr, + buf, sizeof(__le32)); +} + +static void mt792xu_reset_work(struct work_struct *work) +{ + struct mt792x_dev *dev = + container_of(work, struct mt792x_dev, usb_reset_work); + struct usb_interface *usb_intf = to_usb_interface(dev->mt76.dev); + + if (usb_intf && usb_get_intfdata(usb_intf) == dev) + usb_queue_reset_device(usb_intf); + + atomic_set(&dev->usb_reset_pending, 0); +} + +void mt792xu_reset_work_init(struct mt792x_dev *dev) +{ + INIT_WORK(&dev->usb_reset_work, mt792xu_reset_work); + atomic_set(&dev->usb_reset_pending, 0); +} +EXPORT_SYMBOL_GPL(mt792xu_reset_work_init); + +void mt792xu_reset_work_cleanup(struct mt792x_dev *dev) +{ + cancel_work_sync(&dev->usb_reset_work); + atomic_set(&dev->usb_reset_pending, 0); +} +EXPORT_SYMBOL_GPL(mt792xu_reset_work_cleanup); + +int mt792xu_check_bus(struct mt792x_dev *dev) +{ + int ret; + + mutex_lock(&dev->mt76.usb.usb_ctrl_mtx); + ret = mt792xu_read32(&dev->mt76, MT_HW_CHIPID, dev->mt76.usb.data); + mutex_unlock(&dev->mt76.usb.usb_ctrl_mtx); + + if (ret == sizeof(__le32)) + return 0; + + return ret < 0 ? ret : -EIO; +} +EXPORT_SYMBOL_GPL(mt792xu_check_bus); + u32 mt792xu_rr(struct mt76_dev *dev, u32 addr) { u32 ret; @@ -333,6 +382,7 @@ void mt792xu_disconnect(struct usb_interface *usb_intf) { struct mt792x_dev *dev = usb_get_intfdata(usb_intf); + mt792xu_reset_work_cleanup(dev); cancel_work_sync(&dev->init_work); if (!test_bit(MT76_STATE_INITIALIZED, &dev->mphy.state)) return; From 161596f033e8483863ed3e21380f9ea9d925bbc3 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Wed, 1 Apr 2026 14:06:32 -0500 Subject: [PATCH 57/71] wifi: mt76: mt7921u: escalate broken USB transport to device reset Check the USB control path before running the normal WFSYS reset flow. If USB access is no longer reliable, stop the WFSYS-only reset path, mark the device as bus_hung, and queue a USB device reset instead. Reuse the existing bus_hung state to represent transport-level failure, keeping the semantics consistent with the SDIO path. Also initialize bus_hung explicitly during probe for consistency. Reported-by: Bryam Vargas Closes: https://lore.kernel.org/r/CANAPQziOh3sB7B8G+U3AZsFfeFN1uAg4munhwA_feZi56D7W+Q@mail.gmail.com Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260401190632.147042-2-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7921/mac.c | 4 ++- .../net/wireless/mediatek/mt76/mt7921/usb.c | 5 ++++ drivers/net/wireless/mediatek/mt76/mt792x.h | 1 + .../net/wireless/mediatek/mt76/mt792x_usb.c | 26 +++++++++++++++++++ 4 files changed, 35 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mac.c b/drivers/net/wireless/mediatek/mt76/mt7921/mac.c index 03b4960db73f..d27dbee8df1b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/mac.c @@ -675,7 +675,9 @@ void mt7921_mac_reset_work(struct work_struct *work) if (!ret) break; } - if (mt76_is_sdio(&dev->mt76) && atomic_read(&dev->mt76.bus_hung)) + + if ((mt76_is_sdio(&dev->mt76) || mt76_is_usb(&dev->mt76)) && + atomic_read(&dev->mt76.bus_hung)) return; if (i == 10) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/usb.c b/drivers/net/wireless/mediatek/mt76/mt7921/usb.c index 01fa1b03e210..2fdecf239125 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/usb.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/usb.c @@ -90,6 +90,10 @@ static int mt7921u_mac_reset(struct mt792x_dev *dev) { int err; + mt792xu_reset_on_bus_error(dev); + if (atomic_read(&dev->mt76.bus_hung)) + return 0; + mt76_txq_schedule_all(&dev->mphy); mt76_worker_disable(&dev->mt76.tx_worker); @@ -198,6 +202,7 @@ static int mt7921u_probe(struct usb_interface *usb_intf, dev = container_of(mdev, struct mt792x_dev, mt76); dev->fw_features = features; dev->hif_ops = &hif_ops; + atomic_set(&dev->mt76.bus_hung, false); mt792xu_reset_work_init(dev); usb_reset_device(udev); diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h index 95e7fb64599d..70073b43af54 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x.h +++ b/drivers/net/wireless/mediatek/mt76/mt792x.h @@ -521,6 +521,7 @@ int mt792xu_init_reset(struct mt792x_dev *dev); void mt792xu_reset_work_init(struct mt792x_dev *dev); void mt792xu_reset_work_cleanup(struct mt792x_dev *dev); int mt792xu_check_bus(struct mt792x_dev *dev); +int mt792xu_reset_on_bus_error(struct mt792x_dev *dev); u32 mt792xu_rr(struct mt76_dev *dev, u32 addr); void mt792xu_wr(struct mt76_dev *dev, u32 addr, u32 val); u32 mt792xu_rmw(struct mt76_dev *dev, u32 addr, u32 mask, u32 val); diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_usb.c b/drivers/net/wireless/mediatek/mt76/mt792x_usb.c index 58d442b39536..910132e94956 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_usb.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_usb.c @@ -60,6 +60,32 @@ int mt792xu_check_bus(struct mt792x_dev *dev) } EXPORT_SYMBOL_GPL(mt792xu_check_bus); +int mt792xu_reset_on_bus_error(struct mt792x_dev *dev) +{ + int err = 0; + + if (!atomic_read(&dev->mt76.bus_hung)) + err = mt792xu_check_bus(dev); + + if (err) { + atomic_set(&dev->mt76.bus_hung, true); + + if (!atomic_xchg(&dev->usb_reset_pending, 1)) { + dev_warn(dev->mt76.dev, + "USB transport access failed (%d), queueing device reset\n", + err); + + schedule_work(&dev->usb_reset_work); + } + + return err; + } + + atomic_set(&dev->mt76.bus_hung, false); + return 0; +} +EXPORT_SYMBOL_GPL(mt792xu_reset_on_bus_error); + u32 mt792xu_rr(struct mt76_dev *dev, u32 addr) { u32 ret; From 5832743279da8c6ae72f715bad2f7141eca6f4b8 Mon Sep 17 00:00:00 2001 From: "Bjoern A. Zeeb" Date: Tue, 31 Mar 2026 22:05:47 +0000 Subject: [PATCH 58/71] wifi: mt76: fix argument to ieee80211_is_first_frag() ieee80211_is_first_frag() operates on the seq_ctrl not the frame_control header field. Pass the correct one in; otherwise the results may vary. Sponsored by: The FreeBSD Foundation Fixes: 30ce7f4456ae4 ("mt76: validate rx CCMP PN") Link: https://cgit.freebsd.org/src/commit/sys/contrib/dev/mediatek/mt76/mac80211.c?id=c67fd35e58c6ee1e19877a7fe5998885683abedc Signed-off-by: Bjoern A. Zeeb Link: https://patch.msgid.link/83s4psnr-popo-8789-757o-npr2n9n7rs2o@SerrOFQ.bet Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mac80211.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c index dd09ef1bd0c2..13c4e8abe281 100644 --- a/drivers/net/wireless/mediatek/mt76/mac80211.c +++ b/drivers/net/wireless/mediatek/mt76/mac80211.c @@ -1323,7 +1323,7 @@ mt76_check_ccmp_pn(struct sk_buff *skb) * All further fragments will be validated by mac80211 only. */ if (ieee80211_is_frag(hdr) && - !ieee80211_is_first_frag(hdr->frame_control)) + !ieee80211_is_first_frag(hdr->seq_ctrl)) return; } From f4ce0664e9f0387873b181777891741c33e19465 Mon Sep 17 00:00:00 2001 From: Zenm Chen Date: Tue, 7 Apr 2026 23:44:30 +0800 Subject: [PATCH 59/71] wifi: mt76: mt76x2u: Add support for ELECOM WDC-867SU3S Add the ID 056e:400a to the table to support an additional MT7612U adapter: ELECOM WDC-867SU3S. Compile tested only. Cc: stable@vger.kernel.org # 5.10.x Signed-off-by: Zenm Chen Acked-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260407154430.9184-1-zenmchen@gmail.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt76x2/usb.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt76x2/usb.c b/drivers/net/wireless/mediatek/mt76/mt76x2/usb.c index 8af360bca643..ade15bddefc5 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76x2/usb.c +++ b/drivers/net/wireless/mediatek/mt76/mt76x2/usb.c @@ -16,6 +16,7 @@ static const struct usb_device_id mt76x2u_device_table[] = { { USB_DEVICE(0x0e8d, 0x7612) }, /* Aukey USBAC1200 - Alfa AWUS036ACM */ { USB_DEVICE(0x057c, 0x8503) }, /* Avm FRITZ!WLAN AC860 */ { USB_DEVICE(0x7392, 0xb711) }, /* Edimax EW 7722 UAC */ + { USB_DEVICE(0x056e, 0x400a) }, /* ELECOM WDC-867SU3S */ { USB_DEVICE(0x0e8d, 0x7632) }, /* HC-M7662BU1 */ { USB_DEVICE(0x0471, 0x2126) }, /* LiteOn WN4516R module, nonstandard USB connector */ { USB_DEVICE(0x0471, 0x7600) }, /* LiteOn WN4519R module, nonstandard USB connector */ From b7744e889f62a0a5fbf744a2a78a0560d7e5542f Mon Sep 17 00:00:00 2001 From: Ryder Lee Date: Wed, 25 Mar 2026 10:17:23 -0700 Subject: [PATCH 60/71] wifi: mt76: mt7996: disable UNI_BSS_INFO_PROTECT_INFO for mt7996 The current MT7996 firmware causes TX failure and need further investigation, so it is temporarily disabled. MT7992 and MT7990 are working normally. Signed-off-by: Ryder Lee Link: https://patch.msgid.link/6427326eb4e8f375c63379f7a0df7e2ae9d120a4.1774458901.git.ryder.lee@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mcu.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index a14c63438923..f119f023bcd5 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -1281,6 +1281,10 @@ int mt7996_mcu_set_protection(struct mt7996_phy *phy, struct mt7996_vif_link *li PROT_NONGF_STA = BIT(7), }; + /* The current firmware causes TX failure. Need further investigation */ + if (is_mt7996(&dev->mt76)) + return 0; + skb = __mt7996_mcu_alloc_bss_req(&dev->mt76, &link->mt76, MT7996_BSS_UPDATE_MAX_SIZE); if (IS_ERR(skb)) From c5c32d63068c4d849a7f66929331a06eec3df041 Mon Sep 17 00:00:00 2001 From: JB Tsai Date: Tue, 3 Mar 2026 13:36:33 +0800 Subject: [PATCH 61/71] wifi: mt76: mt7921: refactor regulatory domain handling to regd.[ch] Move regd logic to regd.c and regd.h files Co-developed-by: Sean Wang Signed-off-by: Sean Wang Signed-off-by: JB Tsai Link: https://patch.msgid.link/20260303053637.465465-1-jb.tsai@mediatek.com Signed-off-by: Felix Fietkau --- .../wireless/mediatek/mt76/mt7921/Makefile | 2 +- .../net/wireless/mediatek/mt76/mt7921/init.c | 98 +---------------- .../net/wireless/mediatek/mt76/mt7921/main.c | 1 + .../wireless/mediatek/mt76/mt7921/mt7921.h | 1 - .../net/wireless/mediatek/mt76/mt7921/pci.c | 1 + .../net/wireless/mediatek/mt76/mt7921/regd.c | 103 ++++++++++++++++++ .../net/wireless/mediatek/mt76/mt7921/regd.h | 15 +++ 7 files changed, 122 insertions(+), 99 deletions(-) create mode 100644 drivers/net/wireless/mediatek/mt76/mt7921/regd.c create mode 100644 drivers/net/wireless/mediatek/mt76/mt7921/regd.h diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/Makefile b/drivers/net/wireless/mediatek/mt76/mt7921/Makefile index 2ad3c1cc3779..3ef7c9c45386 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/Makefile +++ b/drivers/net/wireless/mediatek/mt76/mt7921/Makefile @@ -5,7 +5,7 @@ obj-$(CONFIG_MT7921E) += mt7921e.o obj-$(CONFIG_MT7921S) += mt7921s.o obj-$(CONFIG_MT7921U) += mt7921u.o -mt7921-common-y := mac.o mcu.o main.o init.o debugfs.o +mt7921-common-y := mac.o mcu.o regd.o main.o init.o debugfs.o mt7921-common-$(CONFIG_NL80211_TESTMODE) += testmode.o mt7921e-y := pci.o pci_mac.o pci_mcu.o mt7921s-y := sdio.o sdio_mac.o sdio_mcu.o diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/init.c b/drivers/net/wireless/mediatek/mt76/mt7921/init.c index 8e7790702191..49be2fa4e3fa 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/init.c @@ -9,6 +9,7 @@ #include "mt7921.h" #include "../mt76_connac2_mac.h" #include "mcu.h" +#include "regd.h" static ssize_t mt7921_thermal_temp_show(struct device *dev, struct device_attribute *attr, @@ -60,103 +61,6 @@ static int mt7921_thermal_init(struct mt792x_phy *phy) return PTR_ERR_OR_ZERO(hwmon); } -static void -mt7921_regd_channel_update(struct wiphy *wiphy, struct mt792x_dev *dev) -{ -#define IS_UNII_INVALID(idx, sfreq, efreq) \ - (!(dev->phy.clc_chan_conf & BIT(idx)) && (cfreq) >= (sfreq) && (cfreq) <= (efreq)) - struct ieee80211_supported_band *sband; - struct mt76_dev *mdev = &dev->mt76; - struct device_node *np, *band_np; - struct ieee80211_channel *ch; - int i, cfreq; - - np = mt76_find_power_limits_node(mdev); - - sband = wiphy->bands[NL80211_BAND_5GHZ]; - band_np = np ? of_get_child_by_name(np, "txpower-5g") : NULL; - for (i = 0; i < sband->n_channels; i++) { - ch = &sband->channels[i]; - cfreq = ch->center_freq; - - if (np && (!band_np || !mt76_find_channel_node(band_np, ch))) { - ch->flags |= IEEE80211_CHAN_DISABLED; - continue; - } - - /* UNII-4 */ - if (IS_UNII_INVALID(0, 5845, 5925)) - ch->flags |= IEEE80211_CHAN_DISABLED; - } - - sband = wiphy->bands[NL80211_BAND_6GHZ]; - if (!sband) - return; - - band_np = np ? of_get_child_by_name(np, "txpower-6g") : NULL; - for (i = 0; i < sband->n_channels; i++) { - ch = &sband->channels[i]; - cfreq = ch->center_freq; - - if (np && (!band_np || !mt76_find_channel_node(band_np, ch))) { - ch->flags |= IEEE80211_CHAN_DISABLED; - continue; - } - - /* UNII-5/6/7/8 */ - if (IS_UNII_INVALID(1, 5925, 6425) || - IS_UNII_INVALID(2, 6425, 6525) || - IS_UNII_INVALID(3, 6525, 6875) || - IS_UNII_INVALID(4, 6875, 7125)) - ch->flags |= IEEE80211_CHAN_DISABLED; - } -} - -void mt7921_regd_update(struct mt792x_dev *dev) -{ - struct mt76_dev *mdev = &dev->mt76; - struct ieee80211_hw *hw = mdev->hw; - struct wiphy *wiphy = hw->wiphy; - - mt7921_mcu_set_clc(dev, mdev->alpha2, dev->country_ie_env); - mt7921_regd_channel_update(wiphy, dev); - mt76_connac_mcu_set_channel_domain(hw->priv); - mt7921_set_tx_sar_pwr(hw, NULL); -} -EXPORT_SYMBOL_GPL(mt7921_regd_update); - -static void -mt7921_regd_notifier(struct wiphy *wiphy, - struct regulatory_request *request) -{ - struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy); - struct mt792x_dev *dev = mt792x_hw_dev(hw); - struct mt76_connac_pm *pm = &dev->pm; - - memcpy(dev->mt76.alpha2, request->alpha2, sizeof(dev->mt76.alpha2)); - dev->mt76.region = request->dfs_region; - dev->country_ie_env = request->country_ie_env; - - if (request->initiator == NL80211_REGDOM_SET_BY_USER) { - if (dev->mt76.alpha2[0] == '0' && dev->mt76.alpha2[1] == '0') - wiphy->regulatory_flags &= ~REGULATORY_COUNTRY_IE_IGNORE; - else - wiphy->regulatory_flags |= REGULATORY_COUNTRY_IE_IGNORE; - } - - if (pm->suspended) - return; - - dev->regd_in_progress = true; - - mt792x_mutex_acquire(dev); - mt7921_regd_update(dev); - mt792x_mutex_release(dev); - - dev->regd_in_progress = false; - wake_up(&dev->wait); -} - int mt7921_mac_init(struct mt792x_dev *dev) { int i; diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/main.c b/drivers/net/wireless/mediatek/mt76/mt7921/main.c index 6ffccc023728..4ac26391a721 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/main.c @@ -7,6 +7,7 @@ #include #include #include "mt7921.h" +#include "regd.h" #include "mcu.h" static int diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h b/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h index 64f60c4fc60c..fb8654b7c5e7 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h +++ b/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h @@ -260,7 +260,6 @@ mt7921_l1_rmw(struct mt792x_dev *dev, u32 addr, u32 mask, u32 val) #define mt7921_l1_set(dev, addr, val) mt7921_l1_rmw(dev, addr, 0, val) #define mt7921_l1_clear(dev, addr, val) mt7921_l1_rmw(dev, addr, val, 0) -void mt7921_regd_update(struct mt792x_dev *dev); int mt7921_mac_init(struct mt792x_dev *dev); bool mt7921_mac_wtbl_update(struct mt792x_dev *dev, int idx, u32 mask); int mt7921_mac_sta_add(struct mt76_dev *mdev, struct ieee80211_vif *vif, diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c index 49a37185f056..7ac0ca07278d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c @@ -12,6 +12,7 @@ #include "../mt76_connac2_mac.h" #include "../dma.h" #include "mcu.h" +#include "regd.h" static const struct pci_device_id mt7921_pci_device_table[] = { { PCI_DEVICE(PCI_VENDOR_ID_MEDIATEK, 0x7961), diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/regd.c b/drivers/net/wireless/mediatek/mt76/mt7921/regd.c new file mode 100644 index 000000000000..51dbbadbc63a --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/mt7921/regd.c @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: BSD-3-Clause-Clear +/* Copyright (C) 2025 MediaTek Inc. */ + +#include +#include "mt7921.h" +#include "regd.h" +#include "mcu.h" + +static void +mt7921_regd_channel_update(struct wiphy *wiphy, struct mt792x_dev *dev) +{ +#define IS_UNII_INVALID(idx, sfreq, efreq) \ + (!(dev->phy.clc_chan_conf & BIT(idx)) && (cfreq) >= (sfreq) && (cfreq) <= (efreq)) + struct ieee80211_supported_band *sband; + struct mt76_dev *mdev = &dev->mt76; + struct device_node *np, *band_np; + struct ieee80211_channel *ch; + int i, cfreq; + + np = mt76_find_power_limits_node(mdev); + + sband = wiphy->bands[NL80211_BAND_5GHZ]; + band_np = np ? of_get_child_by_name(np, "txpower-5g") : NULL; + for (i = 0; i < sband->n_channels; i++) { + ch = &sband->channels[i]; + cfreq = ch->center_freq; + + if (np && (!band_np || !mt76_find_channel_node(band_np, ch))) { + ch->flags |= IEEE80211_CHAN_DISABLED; + continue; + } + + /* UNII-4 */ + if (IS_UNII_INVALID(0, 5845, 5925)) + ch->flags |= IEEE80211_CHAN_DISABLED; + } + + sband = wiphy->bands[NL80211_BAND_6GHZ]; + if (!sband) + return; + + band_np = np ? of_get_child_by_name(np, "txpower-6g") : NULL; + for (i = 0; i < sband->n_channels; i++) { + ch = &sband->channels[i]; + cfreq = ch->center_freq; + + if (np && (!band_np || !mt76_find_channel_node(band_np, ch))) { + ch->flags |= IEEE80211_CHAN_DISABLED; + continue; + } + + /* UNII-5/6/7/8 */ + if (IS_UNII_INVALID(1, 5925, 6425) || + IS_UNII_INVALID(2, 6425, 6525) || + IS_UNII_INVALID(3, 6525, 6875) || + IS_UNII_INVALID(4, 6875, 7125)) + ch->flags |= IEEE80211_CHAN_DISABLED; + } +} + +void mt7921_regd_update(struct mt792x_dev *dev) +{ + struct mt76_dev *mdev = &dev->mt76; + struct ieee80211_hw *hw = mdev->hw; + struct wiphy *wiphy = hw->wiphy; + + mt7921_mcu_set_clc(dev, mdev->alpha2, dev->country_ie_env); + mt7921_regd_channel_update(wiphy, dev); + mt76_connac_mcu_set_channel_domain(hw->priv); + mt7921_set_tx_sar_pwr(hw, NULL); +} +EXPORT_SYMBOL_GPL(mt7921_regd_update); + +void mt7921_regd_notifier(struct wiphy *wiphy, + struct regulatory_request *request) +{ + struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy); + struct mt792x_dev *dev = mt792x_hw_dev(hw); + struct mt76_connac_pm *pm = &dev->pm; + + memcpy(dev->mt76.alpha2, request->alpha2, sizeof(dev->mt76.alpha2)); + dev->mt76.region = request->dfs_region; + dev->country_ie_env = request->country_ie_env; + + if (request->initiator == NL80211_REGDOM_SET_BY_USER) { + if (dev->mt76.alpha2[0] == '0' && dev->mt76.alpha2[1] == '0') + wiphy->regulatory_flags &= ~REGULATORY_COUNTRY_IE_IGNORE; + else + wiphy->regulatory_flags |= REGULATORY_COUNTRY_IE_IGNORE; + } + + if (pm->suspended) + return; + + dev->regd_in_progress = true; + + mt792x_mutex_acquire(dev); + mt7921_regd_update(dev); + mt792x_mutex_release(dev); + + dev->regd_in_progress = false; + wake_up(&dev->wait); +} diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/regd.h b/drivers/net/wireless/mediatek/mt76/mt7921/regd.h new file mode 100644 index 000000000000..2907b78d375b --- /dev/null +++ b/drivers/net/wireless/mediatek/mt76/mt7921/regd.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: BSD-3-Clause-Clear */ +/* Copyright (C) 2025 MediaTek Inc. */ + +#ifndef __MT7921_REGD_H +#define __MT7921_REGD_H + +struct mt792x_dev; +struct wiphy; +struct regulatory_request; + +void mt7921_regd_update(struct mt792x_dev *dev); +void mt7921_regd_notifier(struct wiphy *wiphy, + struct regulatory_request *request); + +#endif From fbb493cd953a664e4f007638837c7a02e4ec7774 Mon Sep 17 00:00:00 2001 From: JB Tsai Date: Tue, 3 Mar 2026 13:36:34 +0800 Subject: [PATCH 62/71] wifi: mt76: mt7921: refactor CLC support check flow Move the disable_clc module parameter to regd.c and introduce mt7921_regd_clc_supported() to centralize CLC support checks. Co-developed-by: Sean Wang Signed-off-by: Sean Wang Signed-off-by: JB Tsai Link: https://patch.msgid.link/20260303053637.465465-2-jb.tsai@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7921/mcu.c | 8 ++------ drivers/net/wireless/mediatek/mt76/mt7921/regd.c | 13 +++++++++++++ drivers/net/wireless/mediatek/mt76/mt7921/regd.h | 1 + 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c index 8442dbd2ee23..9777c899e503 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c @@ -4,6 +4,7 @@ #include #include #include "mt7921.h" +#include "regd.h" #include "mcu.h" #include "../mt76_connac2_mac.h" #include "../mt792x_trace.h" @@ -11,10 +12,6 @@ #define MT_STA_BFER BIT(0) #define MT_STA_BFEE BIT(1) -static bool mt7921_disable_clc; -module_param_named(disable_clc, mt7921_disable_clc, bool, 0644); -MODULE_PARM_DESC(disable_clc, "disable CLC support"); - int mt7921_mcu_parse_response(struct mt76_dev *mdev, int cmd, struct sk_buff *skb, int seq) { @@ -422,8 +419,7 @@ static int mt7921_load_clc(struct mt792x_dev *dev, const char *fw_name) u8 *clc_base = NULL, hw_encap = 0; dev->phy.clc_chan_conf = 0xff; - if (mt7921_disable_clc || - mt76_is_usb(&dev->mt76)) + if (!mt7921_regd_clc_supported(dev)) return 0; if (mt76_is_mmio(&dev->mt76)) { diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/regd.c b/drivers/net/wireless/mediatek/mt76/mt7921/regd.c index 51dbbadbc63a..943342984965 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/regd.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/regd.c @@ -6,6 +6,19 @@ #include "regd.h" #include "mcu.h" +static bool mt7921_disable_clc; +module_param_named(disable_clc, mt7921_disable_clc, bool, 0644); +MODULE_PARM_DESC(disable_clc, "disable CLC support"); + +bool mt7921_regd_clc_supported(struct mt792x_dev *dev) +{ + if (mt7921_disable_clc || + mt76_is_usb(&dev->mt76)) + return false; + + return true; +} + static void mt7921_regd_channel_update(struct wiphy *wiphy, struct mt792x_dev *dev) { diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/regd.h b/drivers/net/wireless/mediatek/mt76/mt7921/regd.h index 2907b78d375b..ece85feb7a23 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/regd.h +++ b/drivers/net/wireless/mediatek/mt76/mt7921/regd.h @@ -11,5 +11,6 @@ struct regulatory_request; void mt7921_regd_update(struct mt792x_dev *dev); void mt7921_regd_notifier(struct wiphy *wiphy, struct regulatory_request *request); +bool mt7921_regd_clc_supported(struct mt792x_dev *dev); #endif From e88098133ed45f5fbe1cd29fde19ee3ef827de1b Mon Sep 17 00:00:00 2001 From: JB Tsai Date: Tue, 3 Mar 2026 13:36:35 +0800 Subject: [PATCH 63/71] wifi: mt76: mt7921: refactor regulatory notifier flow Rename mt7921_regd_update() to mt7921_mcu_regd_update() to centralize regd updates with error handling. Co-developed-by: Sean Wang Signed-off-by: Sean Wang Signed-off-by: JB Tsai Link: https://patch.msgid.link/20260303053637.465465-3-jb.tsai@mediatek.com Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7921/main.c | 2 +- .../net/wireless/mediatek/mt76/mt7921/pci.c | 2 +- .../net/wireless/mediatek/mt76/mt7921/regd.c | 45 +++++++++++++------ .../net/wireless/mediatek/mt76/mt7921/regd.h | 3 +- 4 files changed, 36 insertions(+), 16 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/main.c b/drivers/net/wireless/mediatek/mt76/mt7921/main.c index 4ac26391a721..55ffd8eb8c6a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/main.c @@ -802,7 +802,7 @@ mt7921_regd_set_6ghz_power_type(struct ieee80211_vif *vif, bool is_add) out: if (vif->bss_conf.chanreq.oper.chan->band == NL80211_BAND_6GHZ) - mt7921_regd_update(dev); + mt7921_mcu_regd_update(dev, dev->mt76.alpha2, dev->country_ie_env); } int mt7921_mac_sta_add(struct mt76_dev *mdev, struct ieee80211_vif *vif, diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c index 7ac0ca07278d..7728c5ae6791 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c @@ -589,7 +589,7 @@ static int mt7921_pci_resume(struct device *device) if (err < 0) goto failed; - mt7921_regd_update(dev); + mt7921_mcu_regd_update(dev, mdev->alpha2, dev->country_ie_env); err = mt7921_mcu_radio_led_ctrl(dev, EXT_CMD_RADIO_ON_LED); failed: pm->suspended = false; diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/regd.c b/drivers/net/wireless/mediatek/mt76/mt7921/regd.c index 943342984965..fa753e8e041d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/regd.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/regd.c @@ -71,18 +71,43 @@ mt7921_regd_channel_update(struct wiphy *wiphy, struct mt792x_dev *dev) } } -void mt7921_regd_update(struct mt792x_dev *dev) +int mt7921_mcu_regd_update(struct mt792x_dev *dev, u8 *alpha2, + enum environment_cap country_ie_env) { struct mt76_dev *mdev = &dev->mt76; struct ieee80211_hw *hw = mdev->hw; struct wiphy *wiphy = hw->wiphy; + int ret = 0; + + dev->regd_in_progress = true; + + mt792x_mutex_acquire(dev); + if (!dev->regd_change) + goto err; + + ret = mt7921_mcu_set_clc(dev, alpha2, country_ie_env); + if (ret < 0) + goto err; - mt7921_mcu_set_clc(dev, mdev->alpha2, dev->country_ie_env); mt7921_regd_channel_update(wiphy, dev); - mt76_connac_mcu_set_channel_domain(hw->priv); - mt7921_set_tx_sar_pwr(hw, NULL); + + ret = mt76_connac_mcu_set_channel_domain(hw->priv); + if (ret < 0) + goto err; + + ret = mt7921_set_tx_sar_pwr(hw, NULL); + if (ret < 0) + goto err; + +err: + mt792x_mutex_release(dev); + dev->regd_change = false; + dev->regd_in_progress = false; + wake_up(&dev->wait); + + return ret; } -EXPORT_SYMBOL_GPL(mt7921_regd_update); +EXPORT_SYMBOL_GPL(mt7921_mcu_regd_update); void mt7921_regd_notifier(struct wiphy *wiphy, struct regulatory_request *request) @@ -105,12 +130,6 @@ void mt7921_regd_notifier(struct wiphy *wiphy, if (pm->suspended) return; - dev->regd_in_progress = true; - - mt792x_mutex_acquire(dev); - mt7921_regd_update(dev); - mt792x_mutex_release(dev); - - dev->regd_in_progress = false; - wake_up(&dev->wait); + mt7921_mcu_regd_update(dev, request->alpha2, + request->country_ie_env); } diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/regd.h b/drivers/net/wireless/mediatek/mt76/mt7921/regd.h index ece85feb7a23..c7dcf747843c 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/regd.h +++ b/drivers/net/wireless/mediatek/mt76/mt7921/regd.h @@ -8,7 +8,8 @@ struct mt792x_dev; struct wiphy; struct regulatory_request; -void mt7921_regd_update(struct mt792x_dev *dev); +int mt7921_mcu_regd_update(struct mt792x_dev *dev, u8 *alpha2, + enum environment_cap country_ie_env); void mt7921_regd_notifier(struct wiphy *wiphy, struct regulatory_request *request); bool mt7921_regd_clc_supported(struct mt792x_dev *dev); From ae05055d30f6843c6362449f201b005697d4be41 Mon Sep 17 00:00:00 2001 From: JB Tsai Date: Tue, 3 Mar 2026 13:36:36 +0800 Subject: [PATCH 64/71] wifi: mt76: mt7921: add auto regdomain switch support Implement 802.11d-based automatic regulatory domain switching to dynamically determine the regulatory domain at runtime. The scan-done event structure by reusing reserved padding and appending new fields; the layout and values remains backward-compatible with existing users. Co-developed-by: Sean Wang Signed-off-by: Sean Wang Signed-off-by: JB Tsai Link: https://patch.msgid.link/20260303053637.465465-4-jb.tsai@mediatek.com Signed-off-by: Felix Fietkau --- .../wireless/mediatek/mt76/mt76_connac_mcu.h | 3 +- .../net/wireless/mediatek/mt76/mt7921/mac.c | 3 + .../net/wireless/mediatek/mt76/mt7921/main.c | 12 ++- .../net/wireless/mediatek/mt76/mt7921/mcu.c | 3 +- .../net/wireless/mediatek/mt76/mt7921/regd.c | 80 +++++++++++++++++-- .../net/wireless/mediatek/mt76/mt7921/regd.h | 2 + 6 files changed, 92 insertions(+), 11 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h index 6d0429f40b0f..78f633ad81a0 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h @@ -1599,7 +1599,7 @@ struct mt76_connac_hw_scan_done { u8 pno_enabled; u8 pad2[3]; u8 sparse_channel_valid_num; - u8 pad3[3]; + u8 alpha2[3]; u8 channel_num[MT76_CONNAC_SCAN_DONE_EVENT_MAX_CHANNEL_NUM]; /* idle format for channel_idle_time * 0: first bytes: idle time(ms) 2nd byte: dwell time(ms) @@ -1612,6 +1612,7 @@ struct mt76_connac_hw_scan_done { u8 mdrdy_count[MT76_CONNAC_SCAN_DONE_EVENT_MAX_CHANNEL_NUM]; __le32 beacon_2g_num; __le32 beacon_5g_num; + __le16 channel_scan_time[MT76_CONNAC_SCAN_DONE_EVENT_MAX_CHANNEL_NUM]; } __packed; struct mt76_connac_sched_scan_req { diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mac.c b/drivers/net/wireless/mediatek/mt76/mt7921/mac.c index d27dbee8df1b..85434996f8e7 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/mac.c @@ -7,6 +7,7 @@ #include "mt7921.h" #include "../dma.h" #include "../mt76_connac2_mac.h" +#include "regd.h" #include "mcu.h" #define MT_WTBL_TXRX_CAP_RATE_OFFSET 7 @@ -699,6 +700,8 @@ void mt7921_mac_reset_work(struct work_struct *work) IEEE80211_IFACE_ITER_RESUME_ALL, mt7921_vif_connect_iter, NULL); mt76_connac_power_save_sched(&dev->mt76.phy, pm); + + mt7921_regd_change(&dev->phy, "00"); } void mt7921_coredump_work(struct work_struct *work) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/main.c b/drivers/net/wireless/mediatek/mt76/mt7921/main.c index 55ffd8eb8c6a..0e19ba2b094d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/main.c @@ -1031,8 +1031,16 @@ void mt7921_scan_work(struct work_struct *work) rxd = (struct mt76_connac2_mcu_rxd *)skb->data; if (rxd->eid == MCU_EVENT_SCHED_SCAN_DONE) { ieee80211_sched_scan_results(phy->mt76->hw); - } else if (test_and_clear_bit(MT76_HW_SCANNING, - &phy->mt76->state)) { + } else if (rxd->eid == MCU_EVENT_SCAN_DONE) { + struct mt76_connac_hw_scan_done *event = NULL; + + skb_pull(skb, sizeof(*rxd)); + event = (struct mt76_connac_hw_scan_done *)skb->data; + mt7921_regd_change(phy, event->alpha2); + } + + if (test_and_clear_bit(MT76_HW_SCANNING, + &phy->mt76->state)) { struct cfg80211_scan_info info = { .aborted = false, }; diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c index 9777c899e503..3e605a9ab919 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c @@ -484,7 +484,8 @@ static int mt7921_load_clc(struct mt792x_dev *dev, const char *fw_name) goto out; } } - ret = mt7921_mcu_set_clc(dev, "00", ENVIRON_INDOOR); + + ret = mt7921_regd_init(phy); out: release_firmware(fw); diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/regd.c b/drivers/net/wireless/mediatek/mt76/mt7921/regd.c index fa753e8e041d..d14ed100345b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/regd.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/regd.c @@ -110,26 +110,92 @@ int mt7921_mcu_regd_update(struct mt792x_dev *dev, u8 *alpha2, EXPORT_SYMBOL_GPL(mt7921_mcu_regd_update); void mt7921_regd_notifier(struct wiphy *wiphy, - struct regulatory_request *request) + struct regulatory_request *req) { struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy); struct mt792x_dev *dev = mt792x_hw_dev(hw); struct mt76_connac_pm *pm = &dev->pm; + struct mt76_dev *mdev = &dev->mt76; - memcpy(dev->mt76.alpha2, request->alpha2, sizeof(dev->mt76.alpha2)); - dev->mt76.region = request->dfs_region; - dev->country_ie_env = request->country_ie_env; + /* do not need to update the same country twice */ + if (!memcmp(req->alpha2, mdev->alpha2, 2) && + dev->country_ie_env == req->country_ie_env) + return; - if (request->initiator == NL80211_REGDOM_SET_BY_USER) { + memcpy(mdev->alpha2, req->alpha2, 2); + mdev->region = req->dfs_region; + dev->country_ie_env = req->country_ie_env; + + if (req->initiator == NL80211_REGDOM_SET_BY_USER) { if (dev->mt76.alpha2[0] == '0' && dev->mt76.alpha2[1] == '0') wiphy->regulatory_flags &= ~REGULATORY_COUNTRY_IE_IGNORE; else wiphy->regulatory_flags |= REGULATORY_COUNTRY_IE_IGNORE; } + dev->regd_change = true; + if (pm->suspended) return; - mt7921_mcu_regd_update(dev, request->alpha2, - request->country_ie_env); + mt7921_mcu_regd_update(dev, req->alpha2, + req->country_ie_env); +} + +static bool +mt7921_regd_is_valid_alpha2(const char *alpha2) +{ + if (!alpha2) + return false; + + if (alpha2[0] == '0' && alpha2[1] == '0') + return true; + + if (isalpha(alpha2[0]) && isalpha(alpha2[1])) + return true; + + return false; +} + +int mt7921_regd_change(struct mt792x_phy *phy, char *alpha2) +{ + struct wiphy *wiphy = phy->mt76->hw->wiphy; + struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy); + struct mt792x_dev *dev = mt792x_hw_dev(hw); + struct mt76_dev *mdev = &dev->mt76; + + if (dev->hw_full_reset) + return 0; + + if (!mt7921_regd_is_valid_alpha2(alpha2) || + !mt7921_regd_clc_supported(dev)) + return -EINVAL; + + if (mdev->alpha2[0] != '0' && mdev->alpha2[1] != '0') + return 0; + + /* do not need to update the same country twice */ + if (!memcmp(alpha2, mdev->alpha2, 2)) + return 0; + + if (phy->chip_cap & MT792x_CHIP_CAP_11D_EN) + return regulatory_hint(wiphy, alpha2); + else + return mt7921_mcu_set_clc(dev, alpha2, ENVIRON_INDOOR); +} + +int mt7921_regd_init(struct mt792x_phy *phy) +{ + struct wiphy *wiphy = phy->mt76->hw->wiphy; + struct ieee80211_hw *hw = wiphy_to_ieee80211_hw(wiphy); + struct mt792x_dev *dev = mt792x_hw_dev(hw); + struct mt76_dev *mdev = &dev->mt76; + + if (phy->chip_cap & MT792x_CHIP_CAP_11D_EN) + wiphy->regulatory_flags |= REGULATORY_COUNTRY_IE_IGNORE | + REGULATORY_DISABLE_BEACON_HINTS; + else + memzero_explicit(&mdev->alpha2, sizeof(mdev->alpha2)); + + return 0; } diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/regd.h b/drivers/net/wireless/mediatek/mt76/mt7921/regd.h index c7dcf747843c..571f31629e9e 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/regd.h +++ b/drivers/net/wireless/mediatek/mt76/mt7921/regd.h @@ -13,5 +13,7 @@ int mt7921_mcu_regd_update(struct mt792x_dev *dev, u8 *alpha2, void mt7921_regd_notifier(struct wiphy *wiphy, struct regulatory_request *request); bool mt7921_regd_clc_supported(struct mt792x_dev *dev); +int mt7921_regd_change(struct mt792x_phy *phy, char *alpha2); +int mt7921_regd_init(struct mt792x_phy *phy); #endif From 9c082046c36286013ddb3d353db727868f95fab8 Mon Sep 17 00:00:00 2001 From: JB Tsai Date: Tue, 3 Mar 2026 13:36:37 +0800 Subject: [PATCH 65/71] wifi: mt76: mt7921: disable auto regd changes after user set Add regd_user flag to block automatic regulatory domain updates if set by user. Co-developed-by: Sean Wang Signed-off-by: Sean Wang Signed-off-by: JB Tsai Link: https://patch.msgid.link/20260303053637.465465-5-jb.tsai@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7921/mcu.c | 1 + drivers/net/wireless/mediatek/mt76/mt7921/regd.c | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c index 3e605a9ab919..25b9437250f7 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c @@ -419,6 +419,7 @@ static int mt7921_load_clc(struct mt792x_dev *dev, const char *fw_name) u8 *clc_base = NULL, hw_encap = 0; dev->phy.clc_chan_conf = 0xff; + dev->regd_user = false; if (!mt7921_regd_clc_supported(dev)) return 0; diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/regd.c b/drivers/net/wireless/mediatek/mt76/mt7921/regd.c index d14ed100345b..c0e2b48a50bf 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/regd.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/regd.c @@ -117,6 +117,10 @@ void mt7921_regd_notifier(struct wiphy *wiphy, struct mt76_connac_pm *pm = &dev->pm; struct mt76_dev *mdev = &dev->mt76; + if (req->initiator == NL80211_REGDOM_SET_BY_USER && + !dev->regd_user) + dev->regd_user = true; + /* do not need to update the same country twice */ if (!memcmp(req->alpha2, mdev->alpha2, 2) && dev->country_ie_env == req->country_ie_env) @@ -168,7 +172,8 @@ int mt7921_regd_change(struct mt792x_phy *phy, char *alpha2) return 0; if (!mt7921_regd_is_valid_alpha2(alpha2) || - !mt7921_regd_clc_supported(dev)) + !mt7921_regd_clc_supported(dev) || + dev->regd_user) return -EINVAL; if (mdev->alpha2[0] != '0' && mdev->alpha2[1] != '0') From 05e72b6167970043348bfbe8f72a3b67a38a9f1c Mon Sep 17 00:00:00 2001 From: Ryder Lee Date: Fri, 5 Jun 2026 04:33:03 -0700 Subject: [PATCH 66/71] wifi: mt76: mt7915: fix potential tx_retries underflow When FIELD_GET returns 0 for the retry count, subtracting 1 causes an unsigned integer underflow, resulting in tx_retries becoming a very large value (0xFFFFFFFF for u32). Fix by checking if count is non-zero before subtracting 1. Fixes: 943e4fb96e6f ("wifi: mt76: mt7915: report tx retries/failed counts for non-WED path") Signed-off-by: Ryder Lee Link: https://patch.msgid.link/20260605113306.3485554-1-ryder.lee@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7915/mac.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c index cec2c4208255..334c19ab2b22 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c @@ -912,16 +912,16 @@ mt7915_mac_tx_free(struct mt7915_dev *dev, void *data, int len) } if (!mtk_wed_device_active(&mdev->mmio.wed) && wcid) { - u32 tx_retries = 0, tx_failed = 0; + u32 tx_retries = 0, tx_failed = 0, count; if (v3 && (info & MT_TX_FREE_MPDU_HEADER_V3)) { - tx_retries = - FIELD_GET(MT_TX_FREE_COUNT_V3, info) - 1; + count = FIELD_GET(MT_TX_FREE_COUNT_V3, info); + tx_retries = count ? count - 1 : 0; tx_failed = tx_retries + !!FIELD_GET(MT_TX_FREE_STAT_V3, info); } else if (!v3 && (info & MT_TX_FREE_MPDU_HEADER)) { - tx_retries = - FIELD_GET(MT_TX_FREE_COUNT, info) - 1; + count = FIELD_GET(MT_TX_FREE_COUNT, info); + tx_retries = count ? count - 1 : 0; tx_failed = tx_retries + !!FIELD_GET(MT_TX_FREE_STAT, info); } From 3c5671ed81b1fff97fa868dae771690599db94f7 Mon Sep 17 00:00:00 2001 From: Ryder Lee Date: Fri, 5 Jun 2026 04:33:04 -0700 Subject: [PATCH 67/71] wifi: mt76: mt7921: fix potential tx_retries underflow When FIELD_GET returns 0 for the retry count, subtracting 1 causes an unsigned integer underflow, resulting in tx_retries becoming a very large value (0xFFFFFFFF for u32). Fix by checking if count is non-zero before subtracting 1. Fixes: 9aecfa754c7f ("wifi: mt76: mt7921e: report tx retries/failed counts in tx free event") Signed-off-by: Ryder Lee Link: https://patch.msgid.link/20260605113306.3485554-2-ryder.lee@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7921/mac.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mac.c b/drivers/net/wireless/mediatek/mt76/mt7921/mac.c index 85434996f8e7..1c2377d0a53d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/mac.c @@ -531,8 +531,9 @@ static void mt7921_mac_tx_free(struct mt792x_dev *dev, void *data, int len) stat = FIELD_GET(MT_TX_FREE_STATUS, info); if (wcid) { - wcid->stats.tx_retries += - FIELD_GET(MT_TX_FREE_COUNT, info) - 1; + u32 count = FIELD_GET(MT_TX_FREE_COUNT, info); + + wcid->stats.tx_retries += count ? count - 1 : 0; wcid->stats.tx_failed += !!stat; } From 1e1fd84571e62a2961cea44c053340ec5c99b2cb Mon Sep 17 00:00:00 2001 From: Ryder Lee Date: Fri, 5 Jun 2026 04:33:05 -0700 Subject: [PATCH 68/71] wifi: mt76: mt7925: fix potential tx_retries underflow When FIELD_GET returns 0 for the retry count, subtracting 1 causes an unsigned integer underflow, resulting in tx_retries becoming a very large value (0xFFFFFFFF for u32). Fix by checking if count is non-zero before subtracting 1. Fixes: c948b5da6bbe ("wifi: mt76: mt7925: add Mediatek Wi-Fi7 driver for mt7925 chips") Signed-off-by: Ryder Lee Link: https://patch.msgid.link/20260605113306.3485554-3-ryder.lee@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/mac.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c index 5dd895c3f205..0641a7131d7c 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c @@ -1149,8 +1149,9 @@ mt7925_mac_tx_free(struct mt792x_dev *dev, void *data, int len) if (info & MT_TXFREE_INFO_HEADER) { if (wcid) { - wcid->stats.tx_retries += - FIELD_GET(MT_TXFREE_INFO_COUNT, info) - 1; + u32 count = FIELD_GET(MT_TXFREE_INFO_COUNT, info); + + wcid->stats.tx_retries += count ? count - 1 : 0; wcid->stats.tx_failed += !!FIELD_GET(MT_TXFREE_INFO_STAT, info); } From 4d8bba99d645bcb46a442b18eb42402610cba03a Mon Sep 17 00:00:00 2001 From: Ryder Lee Date: Fri, 5 Jun 2026 04:33:06 -0700 Subject: [PATCH 69/71] wifi: mt76: mt7996: fix potential tx_retries underflow When FIELD_GET returns 0 for the retry count, subtracting 1 causes an unsigned integer underflow, resulting in tx_retries becoming a very large value (0xFFFFFFFF for u32). Fix by checking if count is non-zero before subtracting 1. Fixes: 2461599f835e ("wifi: mt76: mt7996: get tx_retries and tx_failed from txfree") Signed-off-by: Ryder Lee Link: https://patch.msgid.link/20260605113306.3485554-4-ryder.lee@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index 83e8e1e7feb4..0eebc8182ca9 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -1359,13 +1359,13 @@ mt7996_mac_tx_free(struct mt7996_dev *dev, void *data, int len) cur_info++; continue; } else if (info & MT_TXFREE_INFO_HEADER) { - u32 tx_retries = 0, tx_failed = 0; + u32 tx_retries = 0, tx_failed = 0, count; if (!wcid) continue; - tx_retries = - FIELD_GET(MT_TXFREE_INFO_COUNT, info) - 1; + count = FIELD_GET(MT_TXFREE_INFO_COUNT, info); + tx_retries = count ? count - 1 : 0; tx_failed = tx_retries + !!FIELD_GET(MT_TXFREE_INFO_STAT, info); From a7d35545c2ce11849de4b4397abaf664d79acd28 Mon Sep 17 00:00:00 2001 From: Devin Wittmayer Date: Fri, 15 May 2026 11:39:21 -0700 Subject: [PATCH 70/71] wifi: mt76: mt7921: assert sniffer on chanctx change mt7921_change_chanctx() configures the channel for monitor vifs but does not re-assert sniffer mode. mt7925_change_chanctx() does. Match mt7925 by adding the missing mt7921_mcu_set_sniffer(true) call, completing the architectural pattern from commit 914189af23b8 ("wifi: mt76: mt7921: fix channel switch fail in monitor mode"). The user-visible regression this asymmetry produced on v6.17 and v6.18 was addressed by commit cdb2941a516c ("Revert "wifi: mt76: mt792x: improve monitor interface handling"") in v6.19 and backported to the 6.17.y and 6.18.y stable trees. This patch is defense in depth in case the NO_VIRTUAL_MONITOR change is reintroduced in a future series. Tested-by: Nick Morrow Tested-on: RasPi4B, RasPiOS 64 bit, Alfa AWUS036AXML mt7921u Tested-on: RasPi4B, RasPiOS 64 bit, Netgear A9000 mt7925u Signed-off-by: Devin Wittmayer Link: https://patch.msgid.link/20260515183921.23484-1-lucid_duck@justthetip.ca Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7921/main.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/main.c b/drivers/net/wireless/mediatek/mt76/mt7921/main.c index 0e19ba2b094d..3480205d5fb9 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/main.c @@ -1417,10 +1417,12 @@ mt7921_change_chanctx(struct ieee80211_hw *hw, vif = container_of((void *)mvif, struct ieee80211_vif, drv_priv); mt792x_mutex_acquire(phy->dev); - if (vif->type == NL80211_IFTYPE_MONITOR) + if (vif->type == NL80211_IFTYPE_MONITOR) { + mt7921_mcu_set_sniffer(mvif->phy->dev, vif, true); mt7921_mcu_config_sniffer(mvif, ctx); - else + } else { mt76_connac_mcu_uni_set_chctx(mvif->phy->mt76, &mvif->bss_conf.mt76, ctx); + } mt792x_mutex_release(phy->dev); } From 50a7f9f9d48eb50c0e95bef53358acb5af5cb3c6 Mon Sep 17 00:00:00 2001 From: Ingyu Jang Date: Tue, 19 May 2026 17:52:14 +0900 Subject: [PATCH 71/71] wifi: mt76: Drop unneeded mt76_register_debugfs_fops() return checks mt76_register_debugfs_fops() returns the dentry from debugfs_create_dir(), which yields an error pointer on failure (notably ERR_PTR(-ENODEV) when CONFIG_DEBUG_FS=n), never NULL. Per commit ff9fb72bc077 ("debugfs: return error values, not NULL"), callers do not need to check the return value. Drop the dead !dir checks in mt7615/mt7915/mt7921/mt7925/mt7996 _init_debugfs(). Converting them to IS_ERR() instead would have exposed a probe abort on CONFIG_DEBUG_FS=n, since each *_init_debugfs() caller propagates the helper's return value. This patch supersedes an earlier proposal that converted the checks to IS_ERR(). Link: https://lore.kernel.org/all/20260514193243.2518979-1-ingyujang25@korea.ac.kr Signed-off-by: Ingyu Jang Link: https://patch.msgid.link/20260519085214.164846-1-ingyujang25@korea.ac.kr Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7615/debugfs.c | 2 -- drivers/net/wireless/mediatek/mt76/mt7915/debugfs.c | 3 +-- drivers/net/wireless/mediatek/mt76/mt7921/debugfs.c | 2 -- drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c | 2 -- drivers/net/wireless/mediatek/mt76/mt7996/debugfs.c | 2 -- 5 files changed, 1 insertion(+), 10 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7615/debugfs.c b/drivers/net/wireless/mediatek/mt76/mt7615/debugfs.c index 0f7b20152279..6a1475e3c899 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7615/debugfs.c +++ b/drivers/net/wireless/mediatek/mt76/mt7615/debugfs.c @@ -550,8 +550,6 @@ int mt7615_init_debugfs(struct mt7615_dev *dev) struct dentry *dir; dir = mt76_register_debugfs_fops(&dev->mphy, &fops_regval); - if (!dir) - return -ENOMEM; if (is_mt7615(&dev->mt76)) debugfs_create_devm_seqfile(dev->mt76.dev, "xmit-queues", dir, diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/debugfs.c b/drivers/net/wireless/mediatek/mt76/mt7915/debugfs.c index 26ed3745af43..4d0854fe785b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/debugfs.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/debugfs.c @@ -1296,8 +1296,7 @@ int mt7915_init_debugfs(struct mt7915_phy *phy) struct dentry *dir; dir = mt76_register_debugfs_fops(phy->mt76, NULL); - if (!dir) - return -ENOMEM; + debugfs_create_file("muru_debug", 0600, dir, dev, &fops_muru_debug); debugfs_create_file("muru_stats", 0400, dir, phy, &mt7915_muru_stats_fops); diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/debugfs.c b/drivers/net/wireless/mediatek/mt76/mt7921/debugfs.c index 4333005b3ad9..a5a70d8e8544 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/debugfs.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/debugfs.c @@ -266,8 +266,6 @@ int mt7921_init_debugfs(struct mt792x_dev *dev) struct dentry *dir; dir = mt76_register_debugfs_fops(&dev->mphy, &fops_regval); - if (!dir) - return -ENOMEM; if (mt76_is_mmio(&dev->mt76)) debugfs_create_devm_seqfile(dev->mt76.dev, "xmit-queues", diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c b/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c index e2498659c884..d01ff49de47a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c @@ -291,8 +291,6 @@ int mt7925_init_debugfs(struct mt792x_dev *dev) struct dentry *dir; dir = mt76_register_debugfs_fops(&dev->mphy, &fops_regval); - if (!dir) - return -ENOMEM; if (mt76_is_mmio(&dev->mt76)) debugfs_create_devm_seqfile(dev->mt76.dev, "xmit-queues", diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/debugfs.c b/drivers/net/wireless/mediatek/mt76/mt7996/debugfs.c index ef9a9204adf5..b85c3e549f8b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/debugfs.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/debugfs.c @@ -875,8 +875,6 @@ int mt7996_init_debugfs(struct mt7996_dev *dev) struct dentry *dir; dir = mt76_register_debugfs_fops(&dev->mphy, NULL); - if (!dir) - return -ENOMEM; debugfs_create_file("hw-queues", 0400, dir, dev, &mt7996_hw_queues_fops);