From 20743b0b64d91927d1a9346341f3ecdc527c8776 Mon Sep 17 00:00:00 2001 From: Shayne Chen Date: Mon, 15 Dec 2025 14:37:22 +0800 Subject: [PATCH 001/134] wifi: mt76: mt7996: extend CSA and CCA support for MLO Use correct link_id to report CSA and CCA countdown events, and also modify mt7996_channel_switch_beacon() to set beacon with the correct link_id. Co-developed-by: Peter Chiu Signed-off-by: Peter Chiu Co-developed-by: StanleyYP Wang Signed-off-by: StanleyYP Wang Signed-off-by: Shayne Chen Link: https://patch.msgid.link/20251215063728.3013365-1-shayne.chen@mediatek.com Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/main.c | 23 ++- .../net/wireless/mediatek/mt76/mt7996/mcu.c | 159 ++++++++++++------ .../net/wireless/mediatek/mt76/mt7996/mcu.h | 8 +- 3 files changed, 131 insertions(+), 59 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index f16135f0b7f9..4b73fefcee8e 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -927,9 +927,30 @@ mt7996_channel_switch_beacon(struct ieee80211_hw *hw, struct cfg80211_chan_def *chandef) { struct mt7996_dev *dev = mt7996_hw_dev(hw); + struct mt7996_phy *phy = mt7996_band_phy(dev, chandef->chan->band); + struct ieee80211_bss_conf *link_conf; + unsigned int link_id; mutex_lock(&dev->mt76.mutex); - mt7996_mcu_add_beacon(hw, vif, &vif->bss_conf, vif->bss_conf.enable_beacon); + + for_each_vif_active_link(vif, link_conf, link_id) { + struct mt7996_vif_link *link = + mt7996_vif_link(dev, vif, link_id); + + if (!link || link->phy != phy) + continue; + + /* Reset beacon when channel switch triggered during CAC to let + * FW correctly perform CSA countdown + */ + if (!cfg80211_reg_can_beacon(hw->wiphy, &phy->mt76->chandef, + vif->type)) + mt7996_mcu_add_beacon(hw, vif, link_conf, false); + + mt7996_mcu_add_beacon(hw, vif, link_conf, true); + break; + } + mutex_unlock(&dev->mt76.mutex); } diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index b4422a4754cd..c059ddbf1e42 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -390,13 +390,117 @@ int mt7996_mcu_wa_cmd(struct mt7996_dev *dev, int cmd, u32 a1, u32 a2, u32 a3) sizeof(req), false); } +struct mt7996_mcu_countdown_data { + struct mt76_phy *mphy; + u8 omac_idx; +}; + static void mt7996_mcu_csa_finish(void *priv, u8 *mac, struct ieee80211_vif *vif) { - if (!vif->bss_conf.csa_active || vif->type == NL80211_IFTYPE_STATION) + struct mt7996_mcu_countdown_data *cdata = (void *)priv; + struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv; + struct ieee80211_bss_conf *link_conf = NULL; + unsigned long valid_links = vif->valid_links ?: BIT(0); + unsigned int link_id; + + if (vif->type == NL80211_IFTYPE_STATION) return; - ieee80211_csa_finish(vif, 0); + for_each_set_bit(link_id, &valid_links, IEEE80211_MLD_MAX_NUM_LINKS) { + struct mt76_vif_link *mlink = + rcu_dereference(mvif->mt76.link[link_id]); + + if (mlink && mlink->band_idx == cdata->mphy->band_idx && + mlink->omac_idx == cdata->omac_idx) { + link_conf = rcu_dereference(vif->link_conf[link_id]); + break; + } + } + + if (!link_conf || !link_conf->csa_active) + return; + + ieee80211_csa_finish(vif, link_conf->link_id); +} + +static void +mt7996_mcu_cca_finish(void *priv, u8 *mac, struct ieee80211_vif *vif) +{ + struct mt7996_mcu_countdown_data *cdata = (void *)priv; + struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv; + struct ieee80211_bss_conf *link_conf = NULL; + unsigned long valid_links = vif->valid_links ?: BIT(0); + unsigned int link_id; + + if (vif->type == NL80211_IFTYPE_STATION) + return; + + for_each_set_bit(link_id, &valid_links, IEEE80211_MLD_MAX_NUM_LINKS) { + struct mt76_vif_link *mlink = + rcu_dereference(mvif->mt76.link[link_id]); + + if (mlink && mlink->band_idx == cdata->mphy->band_idx && + mlink->omac_idx == cdata->omac_idx) { + link_conf = rcu_dereference(vif->link_conf[link_id]); + break; + } + } + + if (!link_conf || !link_conf->color_change_active) + return; + + ieee80211_color_change_finish(vif, link_conf->link_id); +} + +static void +mt7996_mcu_ie_countdown(struct mt7996_dev *dev, struct sk_buff *skb) +{ +#define UNI_EVENT_IE_COUNTDOWN_CSA 0 +#define UNI_EVENT_IE_COUNTDOWN_BCC 1 + struct header { + u8 band; + u8 rsv[3]; + }; + struct mt7996_mcu_rxd *rxd = (struct mt7996_mcu_rxd *)skb->data; + const char *data = (char *)&rxd[1], *tail; + struct header *hdr = (struct header *)data; + struct tlv *tlv = (struct tlv *)(data + 4); + struct mt7996_mcu_countdown_notify *event; + struct mt7996_mcu_countdown_data cdata; + + if (hdr->band >= ARRAY_SIZE(dev->mt76.phys)) + return; + + cdata.mphy = dev->mt76.phys[hdr->band]; + if (!cdata.mphy) + return; + + tail = skb->data + skb->len; + data += sizeof(*hdr); + while (data + sizeof(*tlv) < tail && le16_to_cpu(tlv->len)) { + event = (struct mt7996_mcu_countdown_notify *)tlv->data; + + cdata.omac_idx = event->omac_idx; + + switch (le16_to_cpu(tlv->tag)) { + case UNI_EVENT_IE_COUNTDOWN_CSA: + ieee80211_iterate_active_interfaces_atomic(mt76_hw(dev), + IEEE80211_IFACE_ITER_RESUME_ALL, + mt7996_mcu_csa_finish, &cdata); + break; + case UNI_EVENT_IE_COUNTDOWN_BCC: + ieee80211_iterate_active_interfaces_atomic(mt76_hw(dev), + IEEE80211_IFACE_ITER_RESUME_ALL, + mt7996_mcu_cca_finish, &cdata); + break; + default: + break; + } + + data += le16_to_cpu(tlv->len); + tlv = (struct tlv *)data; + } } static void @@ -476,57 +580,6 @@ mt7996_mcu_rx_log_message(struct mt7996_dev *dev, struct sk_buff *skb) wiphy_info(mt76_hw(dev)->wiphy, "%s: %.*s", type, len, data); } -static void -mt7996_mcu_cca_finish(void *priv, u8 *mac, struct ieee80211_vif *vif) -{ - if (!vif->bss_conf.color_change_active || vif->type == NL80211_IFTYPE_STATION) - return; - - ieee80211_color_change_finish(vif, 0); -} - -static void -mt7996_mcu_ie_countdown(struct mt7996_dev *dev, struct sk_buff *skb) -{ -#define UNI_EVENT_IE_COUNTDOWN_CSA 0 -#define UNI_EVENT_IE_COUNTDOWN_BCC 1 - struct header { - u8 band; - u8 rsv[3]; - }; - struct mt76_phy *mphy = &dev->mt76.phy; - struct mt7996_mcu_rxd *rxd = (struct mt7996_mcu_rxd *)skb->data; - const char *data = (char *)&rxd[1], *tail; - struct header *hdr = (struct header *)data; - struct tlv *tlv = (struct tlv *)(data + 4); - - if (hdr->band >= ARRAY_SIZE(dev->mt76.phys)) - return; - - if (hdr->band && dev->mt76.phys[hdr->band]) - mphy = dev->mt76.phys[hdr->band]; - - tail = skb->data + skb->len; - data += sizeof(struct header); - while (data + sizeof(struct tlv) < tail && le16_to_cpu(tlv->len)) { - switch (le16_to_cpu(tlv->tag)) { - case UNI_EVENT_IE_COUNTDOWN_CSA: - ieee80211_iterate_active_interfaces_atomic(mphy->hw, - IEEE80211_IFACE_ITER_RESUME_ALL, - mt7996_mcu_csa_finish, mphy->hw); - break; - case UNI_EVENT_IE_COUNTDOWN_BCC: - ieee80211_iterate_active_interfaces_atomic(mphy->hw, - IEEE80211_IFACE_ITER_RESUME_ALL, - mt7996_mcu_cca_finish, mphy->hw); - break; - } - - data += le16_to_cpu(tlv->len); - tlv = (struct tlv *)data; - } -} - static int mt7996_mcu_update_tx_gi(struct rate_info *rate, struct all_sta_trx_rate *mcu_rate) { diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h index e0b83ac9f5e2..fc8b09e76f01 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h @@ -52,12 +52,10 @@ struct mt7996_mcu_thermal_enable { u8 rsv[2]; } __packed; -struct mt7996_mcu_csa_notify { - struct mt7996_mcu_rxd rxd; - +struct mt7996_mcu_countdown_notify { u8 omac_idx; - u8 csa_count; - u8 band_idx; + u8 count; + u8 csa_failure_reason; /* 0: success, 1: beacon disabled */ u8 rsv; } __packed; From 45a09251d610f3b8a1fb02039146e42f1f4efe90 Mon Sep 17 00:00:00 2001 From: StanleyYP Wang Date: Mon, 15 Dec 2025 14:37:23 +0800 Subject: [PATCH 002/134] wifi: mt76: mt7996: fix the behavior of radar detection RDD_DET_MODE is a firmware command intended for testing and does not pause TX after radar detection, so remove it from the normal flow; instead, use the MAC_ENABLE_CTRL firmware command to resume TX after the radar-triggered channel switch completes. Fixes: 1529e335f93d ("wifi: mt76: mt7996: rework radar HWRDD idx") Co-developed-by: Shayne Chen Signed-off-by: Shayne Chen Signed-off-by: StanleyYP Wang Link: https://patch.msgid.link/20251215063728.3013365-2-shayne.chen@mediatek.com Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/mac.c | 8 +-- .../net/wireless/mediatek/mt76/mt7996/main.c | 20 ++++++++ .../net/wireless/mediatek/mt76/mt7996/mcu.c | 49 ++++++++++++++++--- .../net/wireless/mediatek/mt76/mt7996/mcu.h | 1 + .../wireless/mediatek/mt76/mt7996/mt7996.h | 2 + 5 files changed, 68 insertions(+), 12 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index 84cbf36b493c..2765ac7285b4 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -2974,7 +2974,7 @@ static void mt7996_dfs_stop_radar_detector(struct mt7996_phy *phy) static int mt7996_dfs_start_rdd(struct mt7996_dev *dev, int rdd_idx) { - int err, region; + int region; switch (dev->mt76.region) { case NL80211_DFS_ETSI: @@ -2989,11 +2989,7 @@ static int mt7996_dfs_start_rdd(struct mt7996_dev *dev, int rdd_idx) break; } - err = mt7996_mcu_rdd_cmd(dev, RDD_START, rdd_idx, region); - if (err < 0) - return err; - - return mt7996_mcu_rdd_cmd(dev, RDD_DET_MODE, rdd_idx, 1); + return mt7996_mcu_rdd_cmd(dev, RDD_START, rdd_idx, region); } static int mt7996_dfs_start_radar_detector(struct mt7996_phy *phy) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 4b73fefcee8e..fac50dbceae7 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -79,6 +79,7 @@ static void mt7996_stop_phy(struct mt7996_phy *phy) mutex_lock(&dev->mt76.mutex); + mt7996_mcu_rdd_resume_tx(phy); mt7996_mcu_set_radio_en(phy, false); clear_bit(MT76_STATE_RUNNING, &phy->mt76->state); @@ -954,6 +955,24 @@ mt7996_channel_switch_beacon(struct ieee80211_hw *hw, mutex_unlock(&dev->mt76.mutex); } +static int +mt7996_post_channel_switch(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_bss_conf *link_conf) +{ + struct cfg80211_chan_def *chandef = &link_conf->chanreq.oper; + struct mt7996_dev *dev = mt7996_hw_dev(hw); + struct mt7996_phy *phy = mt7996_band_phy(dev, chandef->chan->band); + int ret; + + mutex_lock(&dev->mt76.mutex); + + ret = mt7996_mcu_rdd_resume_tx(phy); + + mutex_unlock(&dev->mt76.mutex); + + return ret; +} + static int mt7996_mac_sta_init_link(struct mt7996_dev *dev, struct ieee80211_bss_conf *link_conf, @@ -2327,6 +2346,7 @@ const struct ieee80211_ops mt7996_ops = { .release_buffered_frames = mt76_release_buffered_frames, .get_txpower = mt7996_get_txpower, .channel_switch_beacon = mt7996_channel_switch_beacon, + .post_channel_switch = mt7996_post_channel_switch, .get_stats = mt7996_get_stats, .get_et_sset_count = mt7996_get_et_sset_count, .get_et_stats = mt7996_get_et_stats, diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index c059ddbf1e42..6294704f7881 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -520,24 +520,32 @@ mt7996_mcu_rx_radar_detected(struct mt7996_dev *dev, struct sk_buff *skb) break; case MT_RDD_IDX_BACKGROUND: if (!dev->rdd2_phy) - return; + goto err; mphy = dev->rdd2_phy->mt76; break; default: - dev_err(dev->mt76.dev, "Unknown RDD idx %d\n", r->rdd_idx); - return; + goto err; } if (!mphy) - return; + goto err; - if (r->rdd_idx == MT_RDD_IDX_BACKGROUND) + if (r->rdd_idx == MT_RDD_IDX_BACKGROUND) { cfg80211_background_radar_event(mphy->hw->wiphy, &dev->rdd2_chandef, GFP_ATOMIC); - else + } else { + struct mt7996_phy *phy = mphy->priv; + + phy->rdd_tx_paused = true; ieee80211_radar_detected(mphy->hw, NULL); + } dev->hw_pattern++; + + return; + +err: + dev_err(dev->mt76.dev, "Invalid RDD idx %d\n", r->rdd_idx); } static void @@ -4612,6 +4620,35 @@ int mt7996_mcu_set_radio_en(struct mt7996_phy *phy, bool enable) &req, sizeof(req), true); } +int mt7996_mcu_rdd_resume_tx(struct mt7996_phy *phy) +{ + struct { + u8 band_idx; + u8 _rsv[3]; + + __le16 tag; + __le16 len; + u8 mac_enable; + u8 _rsv2[3]; + } __packed req = { + .band_idx = phy->mt76->band_idx, + .tag = cpu_to_le16(UNI_BAND_CONFIG_MAC_ENABLE_CTRL), + .len = cpu_to_le16(sizeof(req) - 4), + .mac_enable = 2, + }; + int ret; + + if (!phy->rdd_tx_paused) + return 0; + + ret = mt76_mcu_send_msg(&phy->dev->mt76, MCU_WM_UNI_CMD(BAND_CONFIG), + &req, sizeof(req), true); + if (!ret) + phy->rdd_tx_paused = false; + + return ret; +} + int mt7996_mcu_rdd_cmd(struct mt7996_dev *dev, int cmd, u8 rdd_idx, u8 val) { struct { diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h index fc8b09e76f01..5b3597ca79be 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h @@ -835,6 +835,7 @@ enum { enum { UNI_BAND_CONFIG_RADIO_ENABLE, UNI_BAND_CONFIG_RTS_THRESHOLD = 0x08, + UNI_BAND_CONFIG_MAC_ENABLE_CTRL = 0x0c, }; enum { diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index 7a884311800e..d31864f973cc 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -377,6 +377,7 @@ struct mt7996_phy { bool has_aux_rx; bool counter_reset; + bool rdd_tx_paused; }; struct mt7996_dev { @@ -726,6 +727,7 @@ int mt7996_mcu_get_temperature(struct mt7996_phy *phy); int mt7996_mcu_set_thermal_throttling(struct mt7996_phy *phy, u8 state); int mt7996_mcu_set_thermal_protect(struct mt7996_phy *phy, bool enable); int mt7996_mcu_set_txpower_sku(struct mt7996_phy *phy); +int mt7996_mcu_rdd_resume_tx(struct mt7996_phy *phy); int mt7996_mcu_rdd_cmd(struct mt7996_dev *dev, int cmd, u8 rdd_idx, u8 val); int mt7996_mcu_rdd_background_enable(struct mt7996_phy *phy, struct cfg80211_chan_def *chandef); From 7247037a016ed4bc8a50507d74d0bae98409ae3f Mon Sep 17 00:00:00 2001 From: StanleyYP Wang Date: Mon, 15 Dec 2025 14:37:24 +0800 Subject: [PATCH 003/134] wifi: mt76: mt7996: set specific BSSINFO and STAREC commands after channel switch After channel switch, some tags of BSSINFO (rfch) and STAREC (bfer, rate_ctrl) commands should also be updated. Otherwise, a BSS might not be able to transmit with its peer using correct bandwidth. Co-developed-by: Shayne Chen Signed-off-by: Shayne Chen Signed-off-by: StanleyYP Wang Link: https://patch.msgid.link/20251215063728.3013365-3-shayne.chen@mediatek.com Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/main.c | 14 ++++- .../net/wireless/mediatek/mt76/mt7996/mcu.c | 59 +++++++++++++++++++ .../wireless/mediatek/mt76/mt7996/mt7996.h | 3 + 3 files changed, 75 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index fac50dbceae7..393823368bed 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -962,12 +962,24 @@ mt7996_post_channel_switch(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct cfg80211_chan_def *chandef = &link_conf->chanreq.oper; struct mt7996_dev *dev = mt7996_hw_dev(hw); struct mt7996_phy *phy = mt7996_band_phy(dev, chandef->chan->band); - int ret; + struct mt7996_vif_link *link; + int ret = -EINVAL; mutex_lock(&dev->mt76.mutex); + link = mt7996_vif_conf_link(dev, vif, link_conf); + if (!link) + goto out; + + ret = mt7996_mcu_update_bss_rfch(phy, link); + if (ret) + goto out; + + ieee80211_iterate_stations_mtx(hw, mt7996_mcu_update_sta_rec_bw, link); + ret = mt7996_mcu_rdd_resume_tx(phy); +out: mutex_unlock(&dev->mt76.mutex); return ret; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index 6294704f7881..5a634b71ed2a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -1231,6 +1231,22 @@ int mt7996_mcu_add_bss_info(struct mt7996_phy *phy, struct ieee80211_vif *vif, MCU_WMWA_UNI_CMD(BSS_INFO_UPDATE), true); } +int mt7996_mcu_update_bss_rfch(struct mt7996_phy *phy, struct mt7996_vif_link *link) +{ + struct mt7996_dev *dev = phy->dev; + struct sk_buff *skb; + + skb = __mt7996_mcu_alloc_bss_req(&dev->mt76, &link->mt76, + MT7996_BSS_UPDATE_MAX_SIZE); + if (IS_ERR(skb)) + return PTR_ERR(skb); + + mt7996_mcu_bss_rfch_tlv(skb, phy); + + return mt76_mcu_skb_send_msg(&dev->mt76, skb, + MCU_WMWA_UNI_CMD(BSS_INFO_UPDATE), true); +} + int mt7996_mcu_set_timing(struct mt7996_phy *phy, struct ieee80211_vif *vif, struct ieee80211_bss_conf *link_conf) { @@ -2590,6 +2606,49 @@ int mt7996_mcu_teardown_mld_sta(struct mt7996_dev *dev, MCU_WMWA_UNI_CMD(STA_REC_UPDATE), true); } +void mt7996_mcu_update_sta_rec_bw(void *data, struct ieee80211_sta *sta) +{ + struct mt7996_vif_link *link = (struct mt7996_vif_link *)data; + struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; + struct mt7996_sta_link *msta_link; + struct mt7996_dev *dev; + struct ieee80211_bss_conf *link_conf; + struct ieee80211_link_sta *link_sta; + struct ieee80211_vif *vif; + struct sk_buff *skb; + int link_id; + + if (link->mt76.mvif != &msta->vif->mt76) + return; + + dev = link->phy->dev; + link_id = link->msta_link.wcid.link_id; + link_sta = link_sta_dereference_protected(sta, link_id); + if (!link_sta) + return; + + msta_link = mt76_dereference(msta->link[link_id], &dev->mt76); + if (!msta_link) + return; + + vif = container_of((void *)msta->vif, struct ieee80211_vif, drv_priv); + link_conf = link_conf_dereference_protected(vif, link_id); + if (!link_conf) + return; + + skb = __mt76_connac_mcu_alloc_sta_req(&dev->mt76, &link->mt76, + &msta_link->wcid, + MT7996_STA_UPDATE_MAX_SIZE); + if (IS_ERR(skb)) + return; + + mt7996_mcu_sta_bfer_tlv(dev, skb, link_conf, link_sta, link); + mt7996_mcu_sta_rate_ctrl_tlv(skb, dev, vif, link_conf, link_sta, link); + + mt76_mcu_skb_send_msg(&dev->mt76, skb, + MCU_WMWA_UNI_CMD(STA_REC_UPDATE), true); +} + static int mt7996_mcu_sta_key_tlv(struct mt76_dev *dev, struct mt76_wcid *wcid, struct sk_buff *skb, diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index d31864f973cc..8f1043159f58 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -670,6 +670,8 @@ int mt7996_mcu_add_bss_info(struct mt7996_phy *phy, struct ieee80211_vif *vif, struct ieee80211_bss_conf *link_conf, struct mt76_vif_link *mlink, struct mt7996_sta_link *msta_link, int enable); +int mt7996_mcu_update_bss_rfch(struct mt7996_phy *phy, + struct mt7996_vif_link *link); int mt7996_mcu_add_sta(struct mt7996_dev *dev, struct ieee80211_bss_conf *link_conf, struct ieee80211_link_sta *link_sta, @@ -679,6 +681,7 @@ int mt7996_mcu_add_sta(struct mt7996_dev *dev, int mt7996_mcu_teardown_mld_sta(struct mt7996_dev *dev, struct mt7996_vif_link *link, struct mt7996_sta_link *msta_link); +void mt7996_mcu_update_sta_rec_bw(void *data, struct ieee80211_sta *sta); int mt7996_mcu_add_tx_ba(struct mt7996_dev *dev, struct ieee80211_ampdu_params *params, struct ieee80211_vif *vif, bool enable); From fdce55c038702ac8f330de0697e907713a1e976b Mon Sep 17 00:00:00 2001 From: StanleyYP Wang Date: Mon, 15 Dec 2025 14:37:25 +0800 Subject: [PATCH 004/134] wifi: mt76: mt7996: abort CCA when CSA is starting When CSA countdown is going to start, carry UNI_BSS_INFO_BCN_BCC tag to abort any CCA countdown. Signed-off-by: StanleyYP Wang Signed-off-by: Shayne Chen Link: https://patch.msgid.link/20251215063728.3013365-4-shayne.chen@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mcu.c | 10 ++++++++++ drivers/net/wireless/mediatek/mt76/mt7996/mcu.h | 11 ++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index 5a634b71ed2a..165f87cc7275 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -2796,6 +2796,16 @@ mt7996_mcu_beacon_cntdwn(struct sk_buff *rskb, struct sk_buff *skb, info = (struct bss_bcn_cntdwn_tlv *)tlv; info->cnt = skb->data[offs->cntdwn_counter_offs[0]]; + + /* abort the CCA countdown when starting CSA countdown */ + if (csa) { + struct bss_bcn_cntdwn_tlv *cca_info; + + tlv = mt7996_mcu_add_uni_tlv(rskb, UNI_BSS_INFO_BCN_BCC, + sizeof(*cca_info)); + cca_info = (struct bss_bcn_cntdwn_tlv *)tlv; + cca_info->cca.abort = true; + } } static void diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h index 5b3597ca79be..1283beb0dc19 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h @@ -412,7 +412,16 @@ struct bss_bcn_cntdwn_tlv { __le16 tag; __le16 len; u8 cnt; - u8 rsv[3]; + union { + struct { + bool static_pp; + bool abort; + } csa; + struct { + bool abort; + } cca; + }; + u8 rsv; } __packed; struct bss_bcn_mbss_tlv { From 956d2e65da93f59fb50bc149f2009565bec26f56 Mon Sep 17 00:00:00 2001 From: StanleyYP Wang Date: Mon, 15 Dec 2025 14:37:26 +0800 Subject: [PATCH 005/134] wifi: mt76: mt7996: offload radar threshold initialization Since some radar specifications maintained by the driver are incorrect and are now also maintained by the firmware, offload the initialization procedure to the firmware. This fixes issues for radar detection rate testings. Signed-off-by: StanleyYP Wang Signed-off-by: Shayne Chen Link: https://patch.msgid.link/20251215063728.3013365-5-shayne.chen@mediatek.com Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/mac.c | 77 ------------------- .../net/wireless/mediatek/mt76/mt7996/mac.h | 5 -- 2 files changed, 82 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index 2765ac7285b4..77040980dea3 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -13,45 +13,6 @@ #define to_rssi(field, rcpi) ((FIELD_GET(field, rcpi) - 220) / 2) -static const struct mt7996_dfs_radar_spec etsi_radar_specs = { - .pulse_th = { 110, -10, -80, 40, 5200, 128, 5200 }, - .radar_pattern = { - [5] = { 1, 0, 6, 32, 28, 0, 990, 5010, 17, 1, 1 }, - [6] = { 1, 0, 9, 32, 28, 0, 615, 5010, 27, 1, 1 }, - [7] = { 1, 0, 15, 32, 28, 0, 240, 445, 27, 1, 1 }, - [8] = { 1, 0, 12, 32, 28, 0, 240, 510, 42, 1, 1 }, - [9] = { 1, 1, 0, 0, 0, 0, 2490, 3343, 14, 0, 0, 12, 32, 28, { }, 126 }, - [10] = { 1, 1, 0, 0, 0, 0, 2490, 3343, 14, 0, 0, 15, 32, 24, { }, 126 }, - [11] = { 1, 1, 0, 0, 0, 0, 823, 2510, 14, 0, 0, 18, 32, 28, { }, 54 }, - [12] = { 1, 1, 0, 0, 0, 0, 823, 2510, 14, 0, 0, 27, 32, 24, { }, 54 }, - }, -}; - -static const struct mt7996_dfs_radar_spec fcc_radar_specs = { - .pulse_th = { 110, -10, -80, 40, 5200, 128, 5200 }, - .radar_pattern = { - [0] = { 1, 0, 8, 32, 28, 0, 508, 3076, 13, 1, 1 }, - [1] = { 1, 0, 12, 32, 28, 0, 140, 240, 17, 1, 1 }, - [2] = { 1, 0, 8, 32, 28, 0, 190, 510, 22, 1, 1 }, - [3] = { 1, 0, 6, 32, 28, 0, 190, 510, 32, 1, 1 }, - [4] = { 1, 0, 9, 255, 28, 0, 323, 343, 13, 1, 32 }, - }, -}; - -static const struct mt7996_dfs_radar_spec jp_radar_specs = { - .pulse_th = { 110, -10, -80, 40, 5200, 128, 5200 }, - .radar_pattern = { - [0] = { 1, 0, 8, 32, 28, 0, 508, 3076, 13, 1, 1 }, - [1] = { 1, 0, 12, 32, 28, 0, 140, 240, 17, 1, 1 }, - [2] = { 1, 0, 8, 32, 28, 0, 190, 510, 22, 1, 1 }, - [3] = { 1, 0, 6, 32, 28, 0, 190, 510, 32, 1, 1 }, - [4] = { 1, 0, 9, 255, 28, 0, 323, 343, 13, 1, 32 }, - [13] = { 1, 0, 7, 32, 28, 0, 3836, 3856, 14, 1, 1 }, - [14] = { 1, 0, 6, 32, 28, 0, 615, 5010, 110, 1, 1 }, - [15] = { 1, 1, 0, 0, 0, 0, 15, 5010, 110, 0, 0, 12, 32, 28 }, - }, -}; - static struct mt76_wcid *mt7996_rx_get_wcid(struct mt7996_dev *dev, u16 idx, u8 band_idx) { @@ -3011,40 +2972,6 @@ static int mt7996_dfs_start_radar_detector(struct mt7996_phy *phy) return err; } -static int -mt7996_dfs_init_radar_specs(struct mt7996_phy *phy) -{ - const struct mt7996_dfs_radar_spec *radar_specs; - struct mt7996_dev *dev = phy->dev; - int err, i; - - switch (dev->mt76.region) { - case NL80211_DFS_FCC: - radar_specs = &fcc_radar_specs; - err = mt7996_mcu_set_fcc5_lpn(dev, 8); - if (err < 0) - return err; - break; - case NL80211_DFS_ETSI: - radar_specs = &etsi_radar_specs; - break; - case NL80211_DFS_JP: - radar_specs = &jp_radar_specs; - break; - default: - return -EINVAL; - } - - for (i = 0; i < ARRAY_SIZE(radar_specs->radar_pattern); i++) { - err = mt7996_mcu_set_radar_th(dev, i, - &radar_specs->radar_pattern[i]); - if (err < 0) - return err; - } - - return mt7996_mcu_set_pulse_th(dev, &radar_specs->pulse_th); -} - int mt7996_dfs_init_radar_detector(struct mt7996_phy *phy) { struct mt7996_dev *dev = phy->dev; @@ -3064,10 +2991,6 @@ int mt7996_dfs_init_radar_detector(struct mt7996_phy *phy) goto stop; if (prev_state <= MT_DFS_STATE_DISABLED) { - err = mt7996_dfs_init_radar_specs(phy); - if (err < 0) - return err; - err = mt7996_dfs_start_radar_detector(phy); if (err < 0) return err; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.h b/drivers/net/wireless/mediatek/mt76/mt7996/mac.h index 4eca37b013fc..70ee30f32f88 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.h @@ -37,9 +37,4 @@ struct mt7996_dfs_pattern { u32 min_stgpr_diff; } __packed; -struct mt7996_dfs_radar_spec { - struct mt7996_dfs_pulse pulse_th; - struct mt7996_dfs_pattern radar_pattern[16]; -}; - #endif From 7f3ec778593f24584dbcf25995f2b651133e956d Mon Sep 17 00:00:00 2001 From: Shayne Chen Date: Mon, 15 Dec 2025 14:37:27 +0800 Subject: [PATCH 006/134] wifi: mt76: mt7996: add duplicated WTBL command This is a firmware mechanism to improve packet loss issues for mt7996 and mt7992 chipsets. Signed-off-by: Shayne Chen Link: https://patch.msgid.link/20251215063728.3013365-6-shayne.chen@mediatek.com Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/init.c | 3 +++ .../net/wireless/mediatek/mt76/mt7996/mcu.c | 25 +++++++++++++++++-- .../net/wireless/mediatek/mt76/mt7996/mcu.h | 5 ++++ .../wireless/mediatek/mt76/mt7996/mt7996.h | 1 + 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index 00a8286bd136..7e8bd3b142e7 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -756,6 +756,9 @@ static void mt7996_init_work(struct work_struct *work) mt7996_mcu_set_eeprom(dev); mt7996_mac_init(dev); mt7996_txbf_init(dev); + + if (!is_mt7990(&dev->mt76)) + mt7996_mcu_set_dup_wtbl(dev); } void mt7996_wfsys_reset(struct mt7996_dev *dev) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index 165f87cc7275..aab83ad9c1b5 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -4033,7 +4033,6 @@ int mt7996_mcu_get_eeprom_free_block(struct mt7996_dev *dev, u8 *block_num) int mt7996_mcu_get_chip_config(struct mt7996_dev *dev, u32 *cap) { -#define NIC_CAP 3 #define UNI_EVENT_CHIP_CONFIG_EFUSE_VERSION 0x21 struct { u8 _rsv[4]; @@ -4041,7 +4040,7 @@ int mt7996_mcu_get_chip_config(struct mt7996_dev *dev, u32 *cap) __le16 tag; __le16 len; } __packed req = { - .tag = cpu_to_le16(NIC_CAP), + .tag = cpu_to_le16(UNI_CHIP_CONFIG_NIC_CAPA), .len = cpu_to_le16(sizeof(req) - 4), }; struct sk_buff *skb; @@ -5048,3 +5047,25 @@ int mt7996_mcu_cp_support(struct mt7996_dev *dev, u8 mode) return mt76_mcu_send_msg(&dev->mt76, MCU_WA_EXT_CMD(CP_SUPPORT), &cp_mode, sizeof(cp_mode), true); } + +int mt7996_mcu_set_dup_wtbl(struct mt7996_dev *dev) +{ +#define DUP_WTBL_NUM 80 + struct { + u8 _rsv[4]; + + __le16 tag; + __le16 len; + __le16 base; + __le16 num; + u8 _rsv2[4]; + } __packed req = { + .tag = cpu_to_le16(UNI_CHIP_CONFIG_DUP_WTBL), + .len = cpu_to_le16(sizeof(req) - 4), + .base = cpu_to_le16(MT7996_WTBL_STA - DUP_WTBL_NUM + 1), + .num = cpu_to_le16(DUP_WTBL_NUM), + }; + + return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(CHIP_CONFIG), &req, + sizeof(req), true); +} diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h index 1283beb0dc19..de14394bec22 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h @@ -798,6 +798,11 @@ enum { UNI_CHANNEL_RX_PATH, }; +enum { + UNI_CHIP_CONFIG_NIC_CAPA = 3, + UNI_CHIP_CONFIG_DUP_WTBL = 4, +}; + #define MT7996_BSS_UPDATE_MAX_SIZE (sizeof(struct bss_req_hdr) + \ sizeof(struct mt76_connac_bss_basic_tlv) + \ sizeof(struct bss_rlm_tlv) + \ diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index 8f1043159f58..f850be874b1b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -748,6 +748,7 @@ void mt7996_mcu_exit(struct mt7996_dev *dev); int mt7996_mcu_get_all_sta_info(struct mt7996_phy *phy, u16 tag); int mt7996_mcu_wed_rro_reset_sessions(struct mt7996_dev *dev, u16 id); int mt7996_mcu_set_sniffer_mode(struct mt7996_phy *phy, bool enabled); +int mt7996_mcu_set_dup_wtbl(struct mt7996_dev *dev); static inline bool mt7996_has_hwrro(struct mt7996_dev *dev) { From 5ef0e8e2653b1ba325eb883ffb94073f19cb669a Mon Sep 17 00:00:00 2001 From: Shayne Chen Date: Mon, 15 Dec 2025 14:37:28 +0800 Subject: [PATCH 007/134] wifi: mt76: mt7996: fix iface combination for different chipsets MT7992 and MT7990 support up to 19 interfaces per band and 32 in total. Fixes: 8df63a4bbe3d ("wifi: mt76: mt7996: adjust interface num and wtbl size for mt7992") Signed-off-by: Shayne Chen Link: https://patch.msgid.link/20251215063728.3013365-7-shayne.chen@mediatek.com Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/init.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index 7e8bd3b142e7..2e439f0815d4 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -34,6 +34,20 @@ static const struct ieee80211_iface_combination if_comb_global = { BIT(NL80211_CHAN_WIDTH_40) | BIT(NL80211_CHAN_WIDTH_80) | BIT(NL80211_CHAN_WIDTH_160), + .beacon_int_min_gcd = 100, +}; + +static const struct ieee80211_iface_combination if_comb_global_7992 = { + .limits = &if_limits_global, + .n_limits = 1, + .max_interfaces = 32, + .num_different_channels = MT7996_MAX_RADIOS - 1, + .radar_detect_widths = BIT(NL80211_CHAN_WIDTH_20_NOHT) | + BIT(NL80211_CHAN_WIDTH_20) | + BIT(NL80211_CHAN_WIDTH_40) | + BIT(NL80211_CHAN_WIDTH_80) | + BIT(NL80211_CHAN_WIDTH_160), + .beacon_int_min_gcd = 100, }; static const struct ieee80211_iface_limit if_limits[] = { @@ -485,7 +499,8 @@ mt7996_init_wiphy(struct ieee80211_hw *hw, struct mtk_wed_device *wed) hw->vif_data_size = sizeof(struct mt7996_vif); hw->chanctx_data_size = sizeof(struct mt76_chanctx); - wiphy->iface_combinations = &if_comb_global; + wiphy->iface_combinations = is_mt7996(&dev->mt76) ? &if_comb_global : + &if_comb_global_7992; wiphy->n_iface_combinations = 1; wiphy->radio = dev->radios; From bb8e38fcdbf7290d7f0cd572d2d8fdb2b641b492 Mon Sep 17 00:00:00 2001 From: Quan Zhou Date: Thu, 27 Nov 2025 15:49:11 +0800 Subject: [PATCH 008/134] wifi: mt76: mt7925: fix AMPDU state handling in mt7925_tx_check_aggr Previously, the AMPDU state bit for a given TID was set before attempting to start a BA session, which could result in the AMPDU state being marked active even if ieee80211_start_tx_ba_session() failed. This patch changes the logic to only set the AMPDU state bit after successfully starting a BA session, ensuring proper synchronization between AMPDU state and BA session status. This fixes potential issues with aggregation state tracking and improves compatibility with mac80211 BA session management. Fixes: 44eb173bdd4f ("wifi: mt76: mt7925: add link handling in mt7925_txwi_free") Cc: stable@vger.kernel.org Signed-off-by: Quan Zhou Reviewed-by: Sean Wang Link: https://patch.msgid.link/d5960fbced0beaf33c30203f7f8fb91d0899c87b.1764228973.git.quan.zhou@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/mac.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c index caaf71c31480..63e58c177d65 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c @@ -882,8 +882,10 @@ static void mt7925_tx_check_aggr(struct ieee80211_sta *sta, struct sk_buff *skb, else mlink = &msta->deflink; - if (!test_and_set_bit(tid, &mlink->wcid.ampdu_state)) - ieee80211_start_tx_ba_session(sta, tid, 0); + if (!test_and_set_bit(tid, &mlink->wcid.ampdu_state)) { + if (ieee80211_start_tx_ba_session(sta, tid, 0)) + clear_bit(tid, &mlink->wcid.ampdu_state); + } } static bool From 524ef4b42b40bf1cf634663e746ace0af3fce45c Mon Sep 17 00:00:00 2001 From: David Bauer Date: Sat, 29 Nov 2025 03:39:02 +0100 Subject: [PATCH 009/134] wifi: mt76: mt76x02: wake queues after reconfig The shared reset procedure of MT7610 and MT7612 stop all queues before starting the reset sequence. They however never restart these like other supported mt76 chips do in the reconfig_complete call. This leads to TX not continuing after the reset. Restart queues in the reconfig_complete callback to restore functionality after the reset. Signed-off-by: David Bauer Link: https://patch.msgid.link/20251129023904.288484-1-mail@david-bauer.net Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt76x02_mmio.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt76x02_mmio.c b/drivers/net/wireless/mediatek/mt76/mt76x02_mmio.c index dd71c1c95cc9..dc7c03d23123 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76x02_mmio.c +++ b/drivers/net/wireless/mediatek/mt76/mt76x02_mmio.c @@ -534,6 +534,7 @@ void mt76x02_reconfig_complete(struct ieee80211_hw *hw, return; clear_bit(MT76_RESTART, &dev->mphy.state); + ieee80211_wake_queues(hw); } EXPORT_SYMBOL_GPL(mt76x02_reconfig_complete); From 7900da40e315cd1971405ef95e561b0176e0dac2 Mon Sep 17 00:00:00 2001 From: Leon Yen Date: Fri, 26 Sep 2025 13:34:47 +0800 Subject: [PATCH 010/134] wifi: mt76: mt7925: introduce CSA support in non-MLO mode Add CSA (Channel Switch Announcement) related implementation in collaboration with mac80211 to deal with dynamic channel switching. Signed-off-by: Leon Yen Signed-off-by: Ming Yen Hsieh Link: https://patch.msgid.link/20250926053447.4036650-1-mingyen.hsieh@mediatek.com Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7925/main.c | 139 ++++++++++++++++++ .../wireless/mediatek/mt76/mt7925/mt7925.h | 1 + .../net/wireless/mediatek/mt76/mt792x_core.c | 5 +- 3 files changed, 142 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index 2d358a96640c..9861c1fde1ae 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -245,6 +245,7 @@ int mt7925_init_mlo_caps(struct mt792x_phy *phy) { struct wiphy *wiphy = phy->mt76->hw->wiphy; static const u8 ext_capa_sta[] = { + [0] = WLAN_EXT_CAPA1_EXT_CHANNEL_SWITCHING, [2] = WLAN_EXT_CAPA3_MULTI_BSSID_SUPPORT, [7] = WLAN_EXT_CAPA8_OPMODE_NOTIF, }; @@ -438,6 +439,9 @@ mt7925_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif) if (phy->chip_cap & MT792x_CHIP_CAP_RSSI_NOTIFY_EVT_EN) vif->driver_flags |= IEEE80211_VIF_SUPPORTS_CQM_RSSI; + INIT_WORK(&mvif->csa_work, mt7925_csa_work); + timer_setup(&mvif->csa_timer, mt792x_csa_timer, 0); + out: mt792x_mutex_release(dev); @@ -1749,6 +1753,10 @@ static int mt7925_add_chanctx(struct ieee80211_hw *hw, struct ieee80211_chanctx_conf *ctx) { + struct mt792x_dev *dev = mt792x_hw_dev(hw); + + dev->new_ctx = ctx; + return 0; } @@ -1756,6 +1764,11 @@ static void mt7925_remove_chanctx(struct ieee80211_hw *hw, struct ieee80211_chanctx_conf *ctx) { + struct mt792x_dev *dev = mt792x_hw_dev(hw); + + if (dev->new_ctx == ctx) + dev->new_ctx = NULL; + } static void @@ -2144,6 +2157,11 @@ static void mt7925_unassign_vif_chanctx(struct ieee80211_hw *hw, mctx->bss_conf = NULL; mconf->mt76.ctx = NULL; mutex_unlock(&dev->mt76.mutex); + + if (link_conf->csa_active) { + timer_delete_sync(&mvif->csa_timer); + cancel_work_sync(&mvif->csa_work); + } } static void mt7925_rfkill_poll(struct ieee80211_hw *hw) @@ -2158,6 +2176,121 @@ static void mt7925_rfkill_poll(struct ieee80211_hw *hw) wiphy_rfkill_set_hw_state(hw->wiphy, ret == 0); } +static int mt7925_switch_vif_chanctx(struct ieee80211_hw *hw, + struct ieee80211_vif_chanctx_switch *vifs, + int n_vifs, + enum ieee80211_chanctx_switch_mode mode) +{ + return mt7925_assign_vif_chanctx(hw, vifs->vif, vifs->link_conf, + vifs->new_ctx); +} + +void mt7925_csa_work(struct work_struct *work) +{ + struct mt792x_vif *mvif; + struct mt792x_dev *dev; + struct ieee80211_vif *vif; + struct ieee80211_bss_conf *link_conf; + struct mt792x_bss_conf *mconf; + u8 link_id, roc_rtype; + int ret = 0; + + mvif = (struct mt792x_vif *)container_of(work, struct mt792x_vif, + csa_work); + dev = mvif->phy->dev; + vif = container_of((void *)mvif, struct ieee80211_vif, drv_priv); + + if (ieee80211_vif_is_mld(vif)) + return; + + if (!dev->new_ctx) + return; + + link_id = 0; + mconf = &mvif->bss_conf; + link_conf = &vif->bss_conf; + roc_rtype = MT7925_ROC_REQ_JOIN; + + mt792x_mutex_acquire(dev); + ret = mt7925_set_roc(mvif->phy, mconf, dev->new_ctx->def.chan, + 4000, roc_rtype); + mt792x_mutex_release(dev); + if (!ret) { + mt792x_mutex_acquire(dev); + ret = mt7925_mcu_set_chctx(mvif->phy->mt76, &mconf->mt76, link_conf, + dev->new_ctx); + mt792x_mutex_release(dev); + + mt7925_abort_roc(mvif->phy, mconf); + } + + ieee80211_chswitch_done(vif, !ret, link_id); +} + +static int mt7925_pre_channel_switch(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_channel_switch *chsw) +{ + if (ieee80211_vif_is_mld(vif)) + return -EOPNOTSUPP; + + if (vif->type != NL80211_IFTYPE_STATION || !vif->cfg.assoc) + return -EOPNOTSUPP; + + if (!cfg80211_chandef_usable(hw->wiphy, &chsw->chandef, + IEEE80211_CHAN_DISABLED)) + return -EOPNOTSUPP; + + return 0; +} + +static void mt7925_channel_switch(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_channel_switch *chsw) +{ + struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv; + u16 beacon_interval; + + if (ieee80211_vif_is_mld(vif)) + return; + + beacon_interval = vif->bss_conf.beacon_int; + + mvif->csa_timer.expires = TU_TO_EXP_TIME(beacon_interval * chsw->count); + add_timer(&mvif->csa_timer); +} + +static void mt7925_abort_channel_switch(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *link_conf) +{ + struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv; + + timer_delete_sync(&mvif->csa_timer); + cancel_work_sync(&mvif->csa_work); +} + +static void mt7925_channel_switch_rx_beacon(struct ieee80211_hw *hw, + struct ieee80211_vif *vif, + struct ieee80211_channel_switch *chsw) +{ + struct mt792x_dev *dev = mt792x_hw_dev(hw); + struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv; + u16 beacon_interval; + + if (ieee80211_vif_is_mld(vif)) + return; + + beacon_interval = vif->bss_conf.beacon_int; + + if (cfg80211_chandef_identical(&chsw->chandef, + &dev->new_ctx->def) && + chsw->count) { + mod_timer(&mvif->csa_timer, + TU_TO_EXP_TIME(beacon_interval * chsw->count)); + } +} + const struct ieee80211_ops mt7925_ops = { .tx = mt792x_tx, .start = mt7925_start, @@ -2221,6 +2354,12 @@ const struct ieee80211_ops mt7925_ops = { .change_vif_links = mt7925_change_vif_links, .change_sta_links = mt7925_change_sta_links, .rfkill_poll = mt7925_rfkill_poll, + + .switch_vif_chanctx = mt7925_switch_vif_chanctx, + .pre_channel_switch = mt7925_pre_channel_switch, + .channel_switch = mt7925_channel_switch, + .abort_channel_switch = mt7925_abort_channel_switch, + .channel_switch_rx_beacon = mt7925_channel_switch_rx_beacon, }; EXPORT_SYMBOL_GPL(mt7925_ops); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h index 6b9bf1b89032..5030d7714bcf 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h @@ -298,6 +298,7 @@ int mt7925_mcu_uni_rx_ba(struct mt792x_dev *dev, 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); +void mt7925_csa_work(struct work_struct *work); int mt7925_mcu_uni_bss_ps(struct mt792x_dev *dev, struct ieee80211_bss_conf *link_conf); void mt7925_coredump_work(struct work_struct *work); diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_core.c b/drivers/net/wireless/mediatek/mt76/mt792x_core.c index f2ed16feb6c1..f318a53e4deb 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_core.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_core.c @@ -691,9 +691,8 @@ int mt792x_init_wiphy(struct ieee80211_hw *hw) ieee80211_hw_set(hw, SUPPORTS_MULTI_BSSID); ieee80211_hw_set(hw, SUPPORTS_ONLY_HE_MULTI_BSSID); - if (is_mt7921(&dev->mt76)) { - ieee80211_hw_set(hw, CHANCTX_STA_CSA); - } + ieee80211_hw_set(hw, CHANCTX_STA_CSA); + if (dev->pm.enable) ieee80211_hw_set(hw, CONNECTION_MONITOR); From 0cd776fdccec526ee1f45c81d00da8a316b6e892 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Fri, 28 Nov 2025 17:44:30 +0000 Subject: [PATCH 011/134] wifi: mt76: mt7996: Fix spelling mistake "retriving" -> "retrieving" There are a handful of spelling mistakes in various warning messages. Fix them. Signed-off-by: Colin Ian King Link: https://patch.msgid.link/20251128174430.318838-1-colin.i.king@gmail.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/npu.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/npu.c b/drivers/net/wireless/mediatek/mt76/mt7996/npu.c index 29bb735da4cb..1422533e59c7 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/npu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/npu.c @@ -120,7 +120,7 @@ static int mt7996_npu_rxd_init(struct mt7996_dev *dev, struct airoha_npu *npu) &val, GFP_KERNEL); if (err) { dev_warn(dev->mt76.dev, - "failed retriving NPU wlan rx ring0 addr\n"); + "failed retrieving NPU wlan rx ring0 addr\n"); return err; } writel(val, &dev->mt76.q_rx[MT_RXQ_RRO_BAND0].regs->desc_base); @@ -129,7 +129,7 @@ static int mt7996_npu_rxd_init(struct mt7996_dev *dev, struct airoha_npu *npu) &val, GFP_KERNEL); if (err) { dev_warn(dev->mt76.dev, - "failed retriving NPU wlan rx ring1 addr\n"); + "failed retrieving NPU wlan rx ring1 addr\n"); return err; } writel(val, &dev->mt76.q_rx[MT_RXQ_RRO_BAND1].regs->desc_base); @@ -138,7 +138,7 @@ static int mt7996_npu_rxd_init(struct mt7996_dev *dev, struct airoha_npu *npu) &val, GFP_KERNEL); if (err) { dev_warn(dev->mt76.dev, - "failed retriving NPU wlan rxdmad_c ring addr\n"); + "failed retrieving NPU wlan rxdmad_c ring addr\n"); return err; } writel(val, &dev->mt76.q_rx[MT_RXQ_RRO_RXDMAD_C].regs->desc_base); @@ -159,7 +159,7 @@ static int mt7996_npu_txd_init(struct mt7996_dev *dev, struct airoha_npu *npu) &val, GFP_KERNEL); if (err) { dev_warn(dev->mt76.dev, - "failed retriving NPU wlan tx ring addr\n"); + "failed retrieving NPU wlan tx ring addr\n"); return err; } writel(val, &dev->mt76.phys[i]->q_tx[0]->regs->desc_base); From 654abcbe4528f74428b69292fad5c4224414fa1b Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Fri, 5 Dec 2025 11:24:36 +0100 Subject: [PATCH 012/134] wifi: mt76: mt7996: Set mtxq->wcid just for primary link Set WCID index in mt76_txq struct just for the primary link in mt7996_vif_link_add routine. Fixes: 69d54ce7491d0 ("wifi: mt76: mt7996: switch to single multi-radio wiphy") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20251205-mt76-txq-wicd-fix-v2-1-f19ba48af7c1@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/main.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 393823368bed..d646d1ef8f82 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -301,7 +301,6 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, .cmd = SET_KEY, .link_id = link_conf->link_id, }; - struct mt76_txq *mtxq; int mld_idx, idx, ret; mlink->idx = __ffs64(~dev->mt76.vif_mask); @@ -344,11 +343,6 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, mt7996_mac_wtbl_update(dev, idx, MT_WTBL_UPDATE_ADM_COUNT_CLEAR); - if (vif->txq) { - mtxq = (struct mt76_txq *)vif->txq->drv_priv; - mtxq->wcid = idx; - } - if (vif->type != NL80211_IFTYPE_AP && (!mlink->omac_idx || mlink->omac_idx > 3)) vif->offload_flags = 0; @@ -371,9 +365,13 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, ieee80211_iter_keys(mphy->hw, vif, mt7996_key_iter, &it); - if (!mlink->wcid->offchannel && - mvif->mt76.deflink_id == IEEE80211_LINK_UNSPECIFIED) + if (vif->txq && !mlink->wcid->offchannel && + mvif->mt76.deflink_id == IEEE80211_LINK_UNSPECIFIED) { + struct mt76_txq *mtxq = (struct mt76_txq *)vif->txq->drv_priv; + mvif->mt76.deflink_id = link_conf->link_id; + mtxq->wcid = idx; + } return 0; } From 751a2679b15e3a0fa8fc9175862f0ec40643db68 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Fri, 5 Dec 2025 11:24:37 +0100 Subject: [PATCH 013/134] wifi: mt76: mt7996: Reset mtxq->idx if primary link is removed in mt7996_vif_link_remove() Reset WCID index in mt76_txq struct if primary link is removed in mt7996_vif_link_remove routine. Fixes: a3316d2fc669f ("wifi: mt76: mt7996: set vif default link_id adding/removing vif links") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20251205-mt76-txq-wicd-fix-v2-2-f19ba48af7c1@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/main.c | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index d646d1ef8f82..8a610e0e9bae 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -402,17 +402,28 @@ void mt7996_vif_link_remove(struct mt76_phy *mphy, struct ieee80211_vif *vif, rcu_assign_pointer(dev->mt76.wcid[idx], NULL); - if (!mlink->wcid->offchannel && + if (vif->txq && !mlink->wcid->offchannel && mvif->mt76.deflink_id == link_conf->link_id) { struct ieee80211_bss_conf *iter; + struct mt76_txq *mtxq; unsigned int link_id; mvif->mt76.deflink_id = IEEE80211_LINK_UNSPECIFIED; + mtxq = (struct mt76_txq *)vif->txq->drv_priv; + /* Primary link will be removed, look for a new one */ for_each_vif_active_link(vif, iter, link_id) { - if (link_id != IEEE80211_LINK_UNSPECIFIED) { - mvif->mt76.deflink_id = link_id; - break; - } + struct mt7996_vif_link *link; + + if (link_id == link_conf->link_id) + continue; + + link = mt7996_vif_link(dev, vif, link_id); + if (!link) + continue; + + mtxq->wcid = link->msta_link.wcid.idx; + mvif->mt76.deflink_id = link_id; + break; } } From 5ef44c200618430b004233cbfc1b0929a13d5ac8 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Fri, 5 Dec 2025 11:24:38 +0100 Subject: [PATCH 014/134] wifi: mt76: mt7996: Switch to the secondary link if the default one is removed Switch to the secondary link if available in mt7996_mac_sta_remove_links routine if the primary one is removed. Moreover reset secondary link index for single link scenario. Fixes: 85cd5534a3f2e ("wifi: mt76: mt7996: use correct link_id when filling TXD and TXP") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20251205-mt76-txq-wicd-fix-v2-3-f19ba48af7c1@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/mac.c | 12 ++--- .../net/wireless/mediatek/mt76/mt7996/main.c | 51 +++++++++++++------ 2 files changed, 41 insertions(+), 22 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index 77040980dea3..cf7b0f290328 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -2365,14 +2365,12 @@ mt7996_mac_reset_sta_iter(void *data, struct ieee80211_sta *sta) continue; mt7996_mac_sta_deinit_link(dev, msta_link); - - if (msta->deflink_id == i) { - msta->deflink_id = IEEE80211_LINK_UNSPECIFIED; - continue; - } - - kfree_rcu(msta_link, rcu_head); + if (msta_link != &msta->deflink) + kfree_rcu(msta_link, rcu_head); } + + msta->deflink_id = IEEE80211_LINK_UNSPECIFIED; + msta->seclink_id = msta->deflink_id; } static void diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 8a610e0e9bae..3585a9674adc 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -994,6 +994,22 @@ mt7996_post_channel_switch(struct ieee80211_hw *hw, struct ieee80211_vif *vif, return ret; } +static void +mt7996_sta_init_txq_wcid(struct ieee80211_sta *sta, int idx) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(sta->txq); i++) { + struct mt76_txq *mtxq; + + if (!sta->txq[i]) + continue; + + mtxq = (struct mt76_txq *)sta->txq[i]->drv_priv; + mtxq->wcid = idx; + } +} + static int mt7996_mac_sta_init_link(struct mt7996_dev *dev, struct ieee80211_bss_conf *link_conf, @@ -1011,21 +1027,10 @@ mt7996_mac_sta_init_link(struct mt7996_dev *dev, return -ENOSPC; if (msta->deflink_id == IEEE80211_LINK_UNSPECIFIED) { - int i; - msta_link = &msta->deflink; msta->deflink_id = link_id; msta->seclink_id = msta->deflink_id; - - for (i = 0; i < ARRAY_SIZE(sta->txq); i++) { - struct mt76_txq *mtxq; - - if (!sta->txq[i]) - continue; - - mtxq = (struct mt76_txq *)sta->txq[i]->drv_priv; - mtxq->wcid = idx; - } + mt7996_sta_init_txq_wcid(sta, idx); } else { msta_link = kzalloc_obj(*msta_link); if (!msta_link) @@ -1108,12 +1113,28 @@ mt7996_mac_sta_remove_links(struct mt7996_dev *dev, struct ieee80211_vif *vif, mphy->num_sta--; if (msta->deflink_id == link_id) { msta->deflink_id = IEEE80211_LINK_UNSPECIFIED; - continue; + if (msta->seclink_id == link_id) { + /* no secondary link available */ + msta->seclink_id = msta->deflink_id; + } else { + struct mt7996_sta_link *msta_seclink; + + /* switch to the secondary link */ + msta_seclink = mt76_dereference( + msta->link[msta->seclink_id], + mdev); + if (msta_seclink) { + msta->deflink_id = msta->seclink_id; + mt7996_sta_init_txq_wcid(sta, + msta_seclink->wcid.idx); + } + } } else if (msta->seclink_id == link_id) { - msta->seclink_id = IEEE80211_LINK_UNSPECIFIED; + msta->seclink_id = msta->deflink_id; } - kfree_rcu(msta_link, rcu_head); + if (msta_link != &msta->deflink) + kfree_rcu(msta_link, rcu_head); } } From 88973240dc7c976dd320b36a9e6d925c9be083ae Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Fri, 5 Dec 2025 11:24:39 +0100 Subject: [PATCH 015/134] wifi: mt76: mt7996: Clear wcid pointer in mt7996_mac_sta_deinit_link() Clear WCID pointer removing the sta link in mt7996_mac_sta_deinit_link routine. Fixes: dd82a9e02c054 ("wifi: mt76: mt7996: Rely on mt7996_sta_link in sta_add/sta_remove callbacks") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20251205-mt76-txq-wicd-fix-v2-4-f19ba48af7c1@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/main.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 3585a9674adc..583724c31099 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1076,6 +1076,7 @@ void mt7996_mac_sta_deinit_link(struct mt7996_dev *dev, list_del_init(&msta_link->rc_list); spin_unlock_bh(&dev->mt76.sta_poll_lock); + rcu_assign_pointer(dev->mt76.wcid[msta_link->wcid.idx], NULL); mt76_wcid_cleanup(&dev->mt76, &msta_link->wcid); mt76_wcid_mask_clear(dev->mt76.wcid_mask, msta_link->wcid.idx); } From c0747db7c10c2dfbdcff0e8e97021e3df1f1e362 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Sun, 14 Dec 2025 10:55:30 +0100 Subject: [PATCH 016/134] wifi: mt76: mt7996: Reset ampdu_state state in case of failure in mt7996_tx_check_aggr() Reset the ampdu_state configured state if ieee80211_start_tx_ba_session routine fails in mt7996_tx_check_aggr() Fixes: 98686cd21624c ("wifi: mt76: mt7996: add driver for MediaTek Wi-Fi 7 (802.11be) devices") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20251214-mt7996-aggr-check-fix-v1-1-33a8b62ec0fc@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index cf7b0f290328..0ca908b87a46 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -1231,8 +1231,9 @@ mt7996_tx_check_aggr(struct ieee80211_link_sta *link_sta, if (unlikely(fc != (IEEE80211_FTYPE_DATA | IEEE80211_STYPE_QOS_DATA))) return; - if (!test_and_set_bit(tid, &wcid->ampdu_state)) - ieee80211_start_tx_ba_session(link_sta->sta, tid, 0); + if (!test_and_set_bit(tid, &wcid->ampdu_state) && + ieee80211_start_tx_ba_session(link_sta->sta, tid, 0)) + clear_bit(tid, &wcid->ampdu_state); } static void From 53ffffeb9624ffab6d9a3b1da8635a23f1172b5e Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Mon, 15 Dec 2025 18:59:30 -0600 Subject: [PATCH 017/134] wifi: mt76: mt7921: Reset ampdu_state state in case of failure in mt76_connac2_tx_check_aggr() Reset ampdu_state if ieee80211_start_tx_ba_session() fails in mt76_connac2_tx_check_aggr(), otherwise the driver may incorrectly assume aggregation is active and skip future BA setup attempts. Fixes: 163f4d22c118 ("mt76: mt7921: add MAC support") Signed-off-by: Sean Wang Link: https://patch.msgid.link/20251216005930.9412-1-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c index f946ddc20a47..c46691248513 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c @@ -1153,8 +1153,10 @@ void mt76_connac2_tx_check_aggr(struct ieee80211_sta *sta, __le32 *txwi) return; wcid = (struct mt76_wcid *)sta->drv_priv; - if (!test_and_set_bit(tid, &wcid->ampdu_state)) - ieee80211_start_tx_ba_session(sta, tid, 0); + if (!test_and_set_bit(tid, &wcid->ampdu_state)) { + if (ieee80211_start_tx_ba_session(sta, tid, 0)) + clear_bit(tid, &wcid->ampdu_state); + } } EXPORT_SYMBOL_GPL(mt76_connac2_tx_check_aggr); From 1695f662329faa07c860c73453c097823852df28 Mon Sep 17 00:00:00 2001 From: Leon Yen Date: Thu, 11 Dec 2025 20:38:36 +0800 Subject: [PATCH 018/134] wifi: mt76: mt7925: Fix incorrect MLO mode in firmware control The selection of MLO mode should depend on the capabilities of the STA rather than those of the peer AP to avoid compatibility issues with certain APs, such as Xiaomi BE5000 WiFi7 router. Fixes: 69acd6d910b0c ("wifi: mt76: mt7925: add mt7925_change_vif_links") Signed-off-by: Leon Yen Link: https://patch.msgid.link/20251211123836.4169436-1-leon.yen@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/main.c | 2 +- drivers/net/wireless/mediatek/mt76/mt7925/mcu.c | 9 ++++++--- drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h | 4 ++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index 9861c1fde1ae..1fea2e807f77 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -545,7 +545,7 @@ static int mt7925_set_mlo_roc(struct mt792x_phy *phy, phy->roc_grant = false; - err = mt7925_mcu_set_mlo_roc(mconf, sel_links, 5, ++phy->roc_token_id); + err = mt7925_mcu_set_mlo_roc(phy, mconf, sel_links, 5, ++phy->roc_token_id); if (err < 0) { clear_bit(MT76_STATE_ROC, &phy->mt76->state); goto out; diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c index cf0fdea45cf7..8d9d2c1b83ac 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c @@ -1294,8 +1294,8 @@ int mt7925_mcu_add_key(struct mt76_dev *dev, struct ieee80211_vif *vif, return mt76_mcu_skb_send_msg(dev, skb, mcu_cmd, true); } -int mt7925_mcu_set_mlo_roc(struct mt792x_bss_conf *mconf, u16 sel_links, - int duration, u8 token_id) +int mt7925_mcu_set_mlo_roc(struct mt792x_phy *phy, struct mt792x_bss_conf *mconf, + u16 sel_links, int duration, u8 token_id) { struct mt792x_vif *mvif = mconf->vif; struct ieee80211_vif *vif = container_of((void *)mvif, @@ -1330,6 +1330,8 @@ int mt7925_mcu_set_mlo_roc(struct mt792x_bss_conf *mconf, u16 sel_links, .roc[1].len = cpu_to_le16(sizeof(struct roc_acquire_tlv)) }; + struct wiphy *wiphy = phy->mt76->hw->wiphy; + if (!mconf || hweight16(vif->valid_links) < 2 || hweight16(sel_links) != 2) return -EPERM; @@ -1352,7 +1354,8 @@ int mt7925_mcu_set_mlo_roc(struct mt792x_bss_conf *mconf, u16 sel_links, is_AG_band |= links[i].chan->band == NL80211_BAND_2GHZ; } - if (vif->cfg.eml_cap & IEEE80211_EML_CAP_EMLSR_SUPP) + if (!(wiphy->iftype_ext_capab[0].mld_capa_and_ops & + IEEE80211_MLD_CAP_OP_MAX_SIMUL_LINKS)) type = is_AG_band ? MT7925_ROC_REQ_MLSR_AG : MT7925_ROC_REQ_MLSR_AA; else diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h index 5030d7714bcf..0f0eff748bb7 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h @@ -350,8 +350,8 @@ int mt7925_set_tx_sar_pwr(struct ieee80211_hw *hw, int mt7925_mcu_regval(struct mt792x_dev *dev, u32 regidx, u32 *val, bool set); int mt7925_mcu_set_clc(struct mt792x_dev *dev, u8 *alpha2, enum environment_cap env_cap); -int mt7925_mcu_set_mlo_roc(struct mt792x_bss_conf *mconf, u16 sel_links, - int duration, u8 token_id); +int mt7925_mcu_set_mlo_roc(struct mt792x_phy *phy, struct mt792x_bss_conf *mconf, + u16 sel_links, int duration, u8 token_id); int mt7925_mcu_set_roc(struct mt792x_phy *phy, struct mt792x_bss_conf *mconf, struct ieee80211_channel *chan, int duration, enum mt7925_roc_req type, u8 token_id); From bb2f07819d063a58756186cac6465341956ac0a4 Mon Sep 17 00:00:00 2001 From: Leon Yen Date: Mon, 15 Dec 2025 20:22:31 +0800 Subject: [PATCH 019/134] wifi: mt76: mt792x: Fix a potential deadlock in high-load situations A deadlock may occur between two works, ps_work and mac_work, if their work functions run simultaneously as they attempt to cancel each other by calling cancel_delayed_work_sync(). mt792x_mac_work() -> ... -> cancel_delayed_work_sync(&pm->ps_work); mt792x_pm_power_save_work() -> cancel_delayed_work_sync(&mphy->mac_work); In high-load situations, they are queued but may not have chance to be executed until the CPUs are released. Once the CPUs are available, there is a high possibility that the ps_work function and mac_work function will be executed simultaneously, resulting in a possible deadlock. This patch replaces cancel_delayed_work_sync() with cancel_delayed_work() in ps_work to eliminate the deadlock and make the code easier to maintain. Signed-off-by: Leon Yen Tested-by: Chia-Lin Kao (AceLan) Link: https://patch.msgid.link/20251215122231.3180648-1-leon.yen@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt792x_mac.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_mac.c b/drivers/net/wireless/mediatek/mt76/mt792x_mac.c index 71dec93094eb..888e5a505673 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_mac.c @@ -375,7 +375,7 @@ void mt792x_pm_power_save_work(struct work_struct *work) } if (!mt792x_mcu_fw_pmctrl(dev)) { - cancel_delayed_work_sync(&mphy->mac_work); + cancel_delayed_work(&mphy->mac_work); return; } out: From dcfbd5d3b82d3b5e94df3761c4d25086cab08c38 Mon Sep 17 00:00:00 2001 From: Christian Hewitt Date: Sat, 27 Dec 2025 11:22:19 +0000 Subject: [PATCH 020/134] wifi: mt7601u: check multiple firmware paths The linux-firmware repo moved mt7601u.bin from its root folder to the mediatek sub-folder some time ago, but the driver still tries to load firmware from the old location. Users might have firmware in either location so update the driver to check both. Signed-off-by: Christian Hewitt Link: https://patch.msgid.link/20251227112219.2768439-1-christianshewitt@gmail.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt7601u/mcu.c | 15 ++++++++++++++- drivers/net/wireless/mediatek/mt7601u/usb.h | 1 + 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt7601u/mcu.c b/drivers/net/wireless/mediatek/mt7601u/mcu.c index 1b5cc271a9e1..bad6ca821400 100644 --- a/drivers/net/wireless/mediatek/mt7601u/mcu.c +++ b/drivers/net/wireless/mediatek/mt7601u/mcu.c @@ -403,12 +403,18 @@ mt7601u_upload_firmware(struct mt7601u_dev *dev, const struct mt76_fw *fw) return ret; } +static const char * const mt7601u_fw_paths[] = { + "mediatek/" MT7601U_FIRMWARE, + MT7601U_FIRMWARE, +}; + static int mt7601u_load_firmware(struct mt7601u_dev *dev) { const struct firmware *fw; const struct mt76_fw_header *hdr; int len, ret; u32 val; + int i; mt7601u_wr(dev, MT_USB_DMA_CFG, (MT_USB_DMA_CFG_RX_BULK_EN | MT_USB_DMA_CFG_TX_BULK_EN)); @@ -416,7 +422,14 @@ static int mt7601u_load_firmware(struct mt7601u_dev *dev) if (firmware_running(dev)) return firmware_request_cache(dev->dev, MT7601U_FIRMWARE); - ret = request_firmware(&fw, MT7601U_FIRMWARE, dev->dev); + /* Try loading firmware from multiple locations */ + fw = NULL; + for (i = 0; i < MT7601U_FIRMWARE_PATHS; i++) { + ret = request_firmware(&fw, mt7601u_fw_paths[i], dev->dev); + if (ret == 0) + break; + } + if (ret) return ret; diff --git a/drivers/net/wireless/mediatek/mt7601u/usb.h b/drivers/net/wireless/mediatek/mt7601u/usb.h index 9fdf35970339..723025f84483 100644 --- a/drivers/net/wireless/mediatek/mt7601u/usb.h +++ b/drivers/net/wireless/mediatek/mt7601u/usb.h @@ -9,6 +9,7 @@ #include "mt7601u.h" #define MT7601U_FIRMWARE "mt7601u.bin" +#define MT7601U_FIRMWARE_PATHS ARRAY_SIZE(mt7601u_fw_paths) #define MT_VEND_REQ_MAX_RETRY 10 #define MT_VEND_REQ_TOUT_MS 300 From 1974a67d9b65c29a0a9426e32e8cd8c056de48b7 Mon Sep 17 00:00:00 2001 From: Ryder Lee Date: Wed, 21 Jan 2026 09:41:56 -0800 Subject: [PATCH 021/134] wifi: mt76: mt7615: fix use_cts_prot support Driver should not directly write WTBL to prevent overwritten issues. With this fix, when driver needs to adjust its behavior for compatibility, especially concerning older 11g/n devices, by enabling or disabling CTS protection frames, often for hidden SSIDs or to manage legacy clients. Fixes: e34235ccc5e3 ("wifi: mt76: mt7615: enable use_cts_prot support") Signed-off-by: Ryder Lee Link: https://patch.msgid.link/edb87088b0111b32fafc6c4179f54a5286dd37d8.1768879119.git.ryder.lee@mediatek.com Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7615/mac.c | 15 ------ .../net/wireless/mediatek/mt76/mt7615/main.c | 7 +-- .../net/wireless/mediatek/mt76/mt7615/mcu.c | 47 +++++++++++++++++++ .../wireless/mediatek/mt76/mt7615/mt7615.h | 5 +- .../net/wireless/mediatek/mt76/mt7615/regs.h | 2 - 5 files changed, 53 insertions(+), 23 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7615/mac.c b/drivers/net/wireless/mediatek/mt76/mt7615/mac.c index 45992fdcec60..ce0051468501 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7615/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7615/mac.c @@ -1167,21 +1167,6 @@ void mt7615_mac_set_rates(struct mt7615_phy *phy, struct mt7615_sta *sta, } EXPORT_SYMBOL_GPL(mt7615_mac_set_rates); -void mt7615_mac_enable_rtscts(struct mt7615_dev *dev, - struct ieee80211_vif *vif, bool enable) -{ - struct mt7615_vif *mvif = (struct mt7615_vif *)vif->drv_priv; - u32 addr; - - addr = mt7615_mac_wtbl_addr(dev, mvif->sta.wcid.idx) + 3 * 4; - - if (enable) - mt76_set(dev, addr, MT_WTBL_W3_RTS); - else - mt76_clear(dev, addr, MT_WTBL_W3_RTS); -} -EXPORT_SYMBOL_GPL(mt7615_mac_enable_rtscts); - static int mt7615_mac_wtbl_update_key(struct mt7615_dev *dev, struct mt76_wcid *wcid, struct ieee80211_key_conf *key, diff --git a/drivers/net/wireless/mediatek/mt76/mt7615/main.c b/drivers/net/wireless/mediatek/mt76/mt7615/main.c index 727266892c3d..fc619acbb40d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7615/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7615/main.c @@ -583,9 +583,6 @@ static void mt7615_bss_info_changed(struct ieee80211_hw *hw, } } - if (changed & BSS_CHANGED_ERP_CTS_PROT) - mt7615_mac_enable_rtscts(dev, vif, info->use_cts_prot); - if (changed & BSS_CHANGED_BEACON_ENABLED && info->enable_beacon) { mt7615_mcu_add_bss_info(phy, vif, NULL, true); mt7615_mcu_sta_add(phy, vif, NULL, true); @@ -598,6 +595,10 @@ static void mt7615_bss_info_changed(struct ieee80211_hw *hw, BSS_CHANGED_BEACON_ENABLED)) mt7615_mcu_add_beacon(dev, hw, vif, info->enable_beacon); + if (changed & BSS_CHANGED_HT || changed & BSS_CHANGED_ERP_CTS_PROT) + mt7615_mcu_set_protection(phy, vif, info->ht_operation_mode, + info->use_cts_prot); + if (changed & BSS_CHANGED_PS) mt76_connac_mcu_set_vif_ps(&dev->mt76, vif); diff --git a/drivers/net/wireless/mediatek/mt76/mt7615/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7615/mcu.c index fc0054f8bd60..ff57ede87f71 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7615/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7615/mcu.c @@ -2564,3 +2564,50 @@ int mt7615_mcu_set_roc(struct mt7615_phy *phy, struct ieee80211_vif *vif, return mt76_mcu_send_msg(&dev->mt76, MCU_CE_CMD(SET_ROC), &req, sizeof(req), false); } + +int mt7615_mcu_set_protection(struct mt7615_phy *phy, struct ieee80211_vif *vif, + u8 ht_mode, bool use_cts_prot) +{ + struct mt7615_dev *dev = phy->dev; + struct { + u8 prot_idx; + u8 band; + u8 rsv[2]; + + bool long_nav; + bool prot_mm; + bool prot_gf; + bool prot_bw40; + bool prot_rifs; + bool prot_bw80; + bool prot_bw160; + u8 prot_erp_mask; + } __packed req = { + .prot_idx = 0x2, + .band = phy != &dev->phy, + }; + + switch (ht_mode & IEEE80211_HT_OP_MODE_PROTECTION) { + case IEEE80211_HT_OP_MODE_PROTECTION_NONMEMBER: + case IEEE80211_HT_OP_MODE_PROTECTION_NONHT_MIXED: + req.prot_mm = true; + req.prot_gf = true; + fallthrough; + case IEEE80211_HT_OP_MODE_PROTECTION_20MHZ: + req.prot_bw40 = true; + break; + } + + if (ht_mode & IEEE80211_HT_OP_MODE_NON_GF_STA_PRSNT) + req.prot_gf = true; + + if (use_cts_prot) { + struct mt7615_vif *mvif = (struct mt7615_vif *)vif->drv_priv; + u8 i = mvif->mt76.omac_idx > HW_BSSID_MAX ? HW_BSSID_0 : mvif->mt76.omac_idx; + + req.prot_erp_mask = BIT(i); + } + + return mt76_mcu_send_msg(&dev->mt76, MCU_EXT_CMD(PROTECT_CTRL), &req, + sizeof(req), true); +} diff --git a/drivers/net/wireless/mediatek/mt76/mt7615/mt7615.h b/drivers/net/wireless/mediatek/mt76/mt7615/mt7615.h index c93fd245c90f..391928405f32 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7615/mt7615.h +++ b/drivers/net/wireless/mediatek/mt76/mt7615/mt7615.h @@ -467,8 +467,6 @@ void mt7615_mac_reset_counters(struct mt7615_phy *phy); void mt7615_mac_cca_stats_reset(struct mt7615_phy *phy); void mt7615_mac_set_scs(struct mt7615_phy *phy, bool enable); void mt7615_mac_enable_nf(struct mt7615_dev *dev, bool ext_phy); -void mt7615_mac_enable_rtscts(struct mt7615_dev *dev, - struct ieee80211_vif *vif, bool enable); void mt7615_mac_sta_poll(struct mt7615_dev *dev); int mt7615_mac_write_txwi(struct mt7615_dev *dev, __le32 *txwi, struct sk_buff *skb, struct mt76_wcid *wcid, @@ -523,7 +521,8 @@ int mt7615_mcu_set_sku_en(struct mt7615_phy *phy, bool enable); int mt7615_mcu_apply_rx_dcoc(struct mt7615_phy *phy); int mt7615_mcu_apply_tx_dpd(struct mt7615_phy *phy); int mt7615_dfs_init_radar_detector(struct mt7615_phy *phy); - +int mt7615_mcu_set_protection(struct mt7615_phy *phy, struct ieee80211_vif *vif, + u8 ht_mode, bool use_cts_prot); int mt7615_mcu_set_roc(struct mt7615_phy *phy, struct ieee80211_vif *vif, struct ieee80211_channel *chan, int duration); diff --git a/drivers/net/wireless/mediatek/mt76/mt7615/regs.h b/drivers/net/wireless/mediatek/mt76/mt7615/regs.h index eb3c24d51987..e4133e9181d0 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7615/regs.h +++ b/drivers/net/wireless/mediatek/mt76/mt7615/regs.h @@ -455,8 +455,6 @@ enum mt7615_reg_base { #define MT_WTBL_RIUCR3_RATE6 GENMASK(19, 8) #define MT_WTBL_RIUCR3_RATE7 GENMASK(31, 20) -#define MT_WTBL_W3_RTS BIT(22) - #define MT_WTBL_W5_CHANGE_BW_RATE GENMASK(7, 5) #define MT_WTBL_W5_SHORT_GI_20 BIT(8) #define MT_WTBL_W5_SHORT_GI_40 BIT(9) From 8b2c26562b95c6397e132d21f2bd3d73aaee0c0a Mon Sep 17 00:00:00 2001 From: Ryder Lee Date: Wed, 21 Jan 2026 09:41:57 -0800 Subject: [PATCH 022/134] wifi: mt76: mt7915: fix use_cts_prot support With this fix, when driver needs to adjust its behavior for compatibility, especially concerning older 11g/n devices, by enabling or disabling CTS protection frames, often for hidden SSIDs or to manage legacy clients. Fixes: 150b91419d3d ("wifi: mt76: mt7915: enable use_cts_prot support") Signed-off-by: Ryder Lee Link: https://patch.msgid.link/eb8db4d0bf1c89b7486e89facb788ae3e510dd8b.1768879119.git.ryder.lee@mediatek.com Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7915/mac.c | 13 ---- .../net/wireless/mediatek/mt76/mt7915/main.c | 7 ++- .../net/wireless/mediatek/mt76/mt7915/mcu.c | 62 +++++++++++++++++++ .../net/wireless/mediatek/mt76/mt7915/mcu.h | 11 ++++ .../wireless/mediatek/mt76/mt7915/mt7915.h | 4 ++ 5 files changed, 81 insertions(+), 16 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c index cefe56c05731..cec2c4208255 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c @@ -232,19 +232,6 @@ static void mt7915_mac_sta_poll(struct mt7915_dev *dev) rcu_read_unlock(); } -void mt7915_mac_enable_rtscts(struct mt7915_dev *dev, - struct ieee80211_vif *vif, bool enable) -{ - struct mt7915_vif *mvif = (struct mt7915_vif *)vif->drv_priv; - u32 addr; - - addr = mt7915_mac_wtbl_lmac_addr(dev, mvif->sta.wcid.idx, 5); - if (enable) - mt76_set(dev, addr, BIT(5)); - else - mt76_clear(dev, addr, BIT(5)); -} - static void mt7915_wed_check_ppe(struct mt7915_dev *dev, struct mt76_queue *q, struct mt7915_sta *msta, struct sk_buff *skb, diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/main.c b/drivers/net/wireless/mediatek/mt76/mt7915/main.c index 90d5e79fbf74..0892291616ea 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/main.c @@ -68,7 +68,7 @@ int mt7915_run(struct ieee80211_hw *hw) if (ret) goto out; - ret = mt76_connac_mcu_set_rts_thresh(&dev->mt76, 0x92b, + ret = mt76_connac_mcu_set_rts_thresh(&dev->mt76, MT7915_RTS_LEN_THRES, phy->mt76->band_idx); if (ret) goto out; @@ -633,8 +633,9 @@ static void mt7915_bss_info_changed(struct ieee80211_hw *hw, if (set_sta == 1) mt7915_mcu_add_sta(dev, vif, NULL, CONN_STATE_PORT_SECURE, false); - if (changed & BSS_CHANGED_ERP_CTS_PROT) - mt7915_mac_enable_rtscts(dev, vif, info->use_cts_prot); + if (changed & BSS_CHANGED_HT || changed & BSS_CHANGED_ERP_CTS_PROT) + mt7915_mcu_set_protection(phy, vif, info->ht_operation_mode, + info->use_cts_prot); if (changed & BSS_CHANGED_ERP_SLOT) { int slottime = 9; diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c index 2d2f34aa465d..d6f54b1edfb1 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c @@ -3954,6 +3954,68 @@ int mt7915_mcu_get_rx_rate(struct mt7915_phy *phy, struct ieee80211_vif *vif, return ret; } +int mt7915_mcu_set_protection(struct mt7915_phy *phy, struct ieee80211_vif *vif, + u8 ht_mode, bool use_cts_prot) +{ + struct mt7915_dev *dev = phy->dev; + int len = sizeof(struct sta_req_hdr) + sizeof(struct bss_info_prot); + struct mt7915_vif *mvif = (struct mt7915_vif *)vif->drv_priv; + struct bss_info_prot *prot; + struct sk_buff *skb; + struct tlv *tlv; + enum { + PROT_NONMEMBER = BIT(1), + PROT_20MHZ = BIT(2), + PROT_NONHT_MIXED = BIT(3), + PROT_LEGACY_ERP = BIT(5), + PROT_NONGF_STA = BIT(7), + }; + u32 rts_threshold; + + skb = __mt76_connac_mcu_alloc_sta_req(&dev->mt76, &mvif->mt76, + NULL, len); + if (IS_ERR(skb)) + return PTR_ERR(skb); + + tlv = mt76_connac_mcu_add_tlv(skb, BSS_INFO_PROTECT_INFO, + sizeof(*prot)); + prot = (struct bss_info_prot *)tlv; + + switch (ht_mode & IEEE80211_HT_OP_MODE_PROTECTION) { + case IEEE80211_HT_OP_MODE_PROTECTION_NONMEMBER: + prot->prot_mode = cpu_to_le32(PROT_NONMEMBER); + break; + case IEEE80211_HT_OP_MODE_PROTECTION_20MHZ: + prot->prot_mode = cpu_to_le32(PROT_20MHZ); + break; + case IEEE80211_HT_OP_MODE_PROTECTION_NONHT_MIXED: + prot->prot_mode = cpu_to_le32(PROT_NONHT_MIXED); + break; + } + + if (ht_mode & IEEE80211_HT_OP_MODE_NON_GF_STA_PRSNT) + prot->prot_mode |= cpu_to_le32(PROT_NONGF_STA); + + if (use_cts_prot) + prot->prot_mode |= cpu_to_le32(PROT_LEGACY_ERP); + + /* reuse current RTS setting */ + rts_threshold = phy->mt76->hw->wiphy->rts_threshold; + if (rts_threshold == (u32)-1) + prot->rts_len_thres = cpu_to_le32(MT7915_RTS_LEN_THRES); + else + prot->rts_len_thres = cpu_to_le32(rts_threshold); + + prot->rts_pkt_thres = 0x2; + + prot->he_rts_thres = cpu_to_le16(vif->bss_conf.frame_time_rts_th); + if (!prot->he_rts_thres) + prot->he_rts_thres = cpu_to_le16(DEFAULT_HE_DURATION_RTS_THRES); + + return mt76_mcu_skb_send_msg(&dev->mt76, skb, + MCU_EXT_CMD(BSS_INFO_UPDATE), true); +} + int mt7915_mcu_update_bss_color(struct mt7915_dev *dev, struct ieee80211_vif *vif, struct cfg80211_he_bss_color *he_bss_color) { diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.h index 3af11a075a2f..22f73a5ed425 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.h @@ -399,6 +399,17 @@ struct bss_info_inband_discovery { __le16 prob_rsp_len; } __packed __aligned(4); +struct bss_info_prot { + __le16 tag; + __le16 len; + __le32 prot_type; + __le32 prot_mode; + __le32 rts_len_thres; + __le16 he_rts_thres; + u8 rts_pkt_thres; + u8 rsv[5]; +} __packed; + enum { BSS_INFO_BCN_CSA, BSS_INFO_BCN_BCC, diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mt7915.h b/drivers/net/wireless/mediatek/mt76/mt7915/mt7915.h index b5c06201b707..bf1d915a3ca2 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/mt7915.h +++ b/drivers/net/wireless/mediatek/mt76/mt7915/mt7915.h @@ -84,6 +84,8 @@ #define MT7915_CRIT_TEMP 110 #define MT7915_MAX_TEMP 120 +#define MT7915_RTS_LEN_THRES 0x92b + struct mt7915_vif; struct mt7915_sta; struct mt7915_dfs_pulse; @@ -473,6 +475,8 @@ int mt7915_mcu_add_inband_discov(struct mt7915_dev *dev, struct ieee80211_vif *v u32 changed); int mt7915_mcu_add_beacon(struct ieee80211_hw *hw, struct ieee80211_vif *vif, int enable, u32 changed); +int mt7915_mcu_set_protection(struct mt7915_phy *phy, struct ieee80211_vif *vif, + u8 ht_mode, bool use_cts_prot); int mt7915_mcu_add_obss_spr(struct mt7915_phy *phy, struct ieee80211_vif *vif, struct ieee80211_he_obss_pd *he_obss_pd); int mt7915_mcu_add_rate_ctrl(struct mt7915_dev *dev, struct ieee80211_vif *vif, From 079db35fae4dd7d6daedfb144d50b517d0da10e2 Mon Sep 17 00:00:00 2001 From: Ryder Lee Date: Wed, 21 Jan 2026 09:41:58 -0800 Subject: [PATCH 023/134] wifi: mt76: mt7996: add support for ERP CTS & HT protection This patch adds support for handling BSS_CHANGED_ERP_CTS_PROT and BSS_CHANGED_HT. With this change, when the Wi-Fi driver needs to adjust its behavior for compatibility or performance, especially concerning older 11g/n devices, by enabling or disabling CTS protection frames, often for hidden SSIDs or to manage legacy clients. It also introduces debugfs options to manually control protection mode, allowing users to select betweenno protection, RTS/CTS, and CTS-to-self. Reviewed-by: Money Wang Signed-off-by: Ryder Lee Link: https://patch.msgid.link/942ddb5777d5c201930d6609e9ba877a6ba6714a.1768879119.git.ryder.lee@mediatek.com Signed-off-by: Felix Fietkau --- .../wireless/mediatek/mt76/mt76_connac_mcu.h | 1 + .../net/wireless/mediatek/mt76/mt7996/main.c | 4 ++ .../net/wireless/mediatek/mt76/mt7996/mcu.c | 46 +++++++++++++++++++ .../net/wireless/mediatek/mt76/mt7996/mcu.h | 6 +++ .../wireless/mediatek/mt76/mt7996/mt7996.h | 2 + 5 files changed, 59 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h index 8d59cf43f0e2..f44977f9093d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h @@ -1363,6 +1363,7 @@ enum { UNI_BSS_INFO_BASIC = 0, UNI_BSS_INFO_RA = 1, UNI_BSS_INFO_RLM = 2, + UNI_BSS_INFO_PROTECT_INFO = 3, UNI_BSS_INFO_BSS_COLOR = 4, UNI_BSS_INFO_HE_BASIC = 5, UNI_BSS_INFO_11V_MBSSID = 6, diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 583724c31099..493c47c59d57 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -874,6 +874,10 @@ mt7996_link_info_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif, !!(changed & BSS_CHANGED_BSSID)); } + if (changed & BSS_CHANGED_HT || changed & BSS_CHANGED_ERP_CTS_PROT) + mt7996_mcu_set_protection(phy, link, info->ht_operation_mode, + info->use_cts_prot); + if (changed & BSS_CHANGED_ERP_SLOT) { int slottime = info->use_short_slot ? 9 : 20; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index aab83ad9c1b5..82eea809c47b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -1247,6 +1247,52 @@ int mt7996_mcu_update_bss_rfch(struct mt7996_phy *phy, struct mt7996_vif_link *l MCU_WMWA_UNI_CMD(BSS_INFO_UPDATE), true); } +int mt7996_mcu_set_protection(struct mt7996_phy *phy, struct mt7996_vif_link *link, + u8 ht_mode, bool use_cts_prot) +{ + struct mt7996_dev *dev = phy->dev; + struct bss_prot_tlv *prot; + struct sk_buff *skb; + struct tlv *tlv; + enum { + PROT_NONMEMBER = BIT(1), + PROT_20MHZ = BIT(2), + PROT_NONHT_MIXED = BIT(3), + PROT_LEGACY_ERP = BIT(5), + PROT_NONGF_STA = BIT(7), + }; + + skb = __mt7996_mcu_alloc_bss_req(&dev->mt76, &link->mt76, + MT7996_BSS_UPDATE_MAX_SIZE); + if (IS_ERR(skb)) + return PTR_ERR(skb); + + tlv = mt7996_mcu_add_uni_tlv(skb, UNI_BSS_INFO_PROTECT_INFO, + sizeof(*prot)); + prot = (struct bss_prot_tlv *)tlv; + + switch (ht_mode & IEEE80211_HT_OP_MODE_PROTECTION) { + case IEEE80211_HT_OP_MODE_PROTECTION_NONMEMBER: + prot->prot_mode = cpu_to_le32(PROT_NONMEMBER); + break; + case IEEE80211_HT_OP_MODE_PROTECTION_20MHZ: + prot->prot_mode = cpu_to_le32(PROT_20MHZ); + break; + case IEEE80211_HT_OP_MODE_PROTECTION_NONHT_MIXED: + prot->prot_mode = cpu_to_le32(PROT_NONHT_MIXED); + break; + } + + if (ht_mode & IEEE80211_HT_OP_MODE_NON_GF_STA_PRSNT) + prot->prot_mode |= cpu_to_le32(PROT_NONGF_STA); + + if (use_cts_prot) + prot->prot_mode |= cpu_to_le32(PROT_LEGACY_ERP); + + return mt76_mcu_skb_send_msg(&dev->mt76, skb, + MCU_WM_UNI_CMD(BSS_INFO_UPDATE), true); +} + int mt7996_mcu_set_timing(struct mt7996_phy *phy, struct ieee80211_vif *vif, struct ieee80211_bss_conf *link_conf) { diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h index de14394bec22..d9fb49f7b01b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h @@ -481,6 +481,12 @@ struct bss_mld_tlv { u8 __rsv[2]; } __packed; +struct bss_prot_tlv { + __le16 tag; + __le16 len; + __le32 prot_mode; +} __packed; + struct sta_rec_ht_uni { __le16 tag; __le16 len; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index f850be874b1b..f8b79b05169b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -723,6 +723,8 @@ int mt7996_mcu_set_radar_th(struct mt7996_dev *dev, int index, const struct mt7996_dfs_pattern *pattern); int mt7996_mcu_set_radio_en(struct mt7996_phy *phy, bool enable); int mt7996_mcu_set_rts_thresh(struct mt7996_phy *phy, u32 val); +int mt7996_mcu_set_protection(struct mt7996_phy *phy, struct mt7996_vif_link *link, + u8 ht_mode, bool use_cts_prot); int mt7996_mcu_set_timing(struct mt7996_phy *phy, struct ieee80211_vif *vif, struct ieee80211_bss_conf *link_conf); int mt7996_mcu_get_chan_mib_info(struct mt7996_phy *phy, bool chan_switch); From ccb186326bb6b7f20d77982f855568e7087ad0d7 Mon Sep 17 00:00:00 2001 From: Ming Yen Hsieh Date: Mon, 8 Sep 2025 15:25:26 +0800 Subject: [PATCH 024/134] wifi: mt76: mt7925: fix incorrect length field in txpower command Set `tx_power_tlv->len` to `msg_len` instead of `sizeof(*tx_power_tlv)` to ensure the correct message length is sent to firmware. Cc: stable@vger.kernel.org Fixes: c948b5da6bbe ("wifi: mt76: mt7925: add Mediatek Wi-Fi7 driver for mt7925 chips") Signed-off-by: Ming Yen Hsieh Link: https://patch.msgid.link/20250908072526.1833938-1-mingyen.hsieh@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/mcu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c index 8d9d2c1b83ac..2daf5a29220f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c @@ -3730,7 +3730,7 @@ mt7925_mcu_rate_txpower_band(struct mt76_phy *phy, memcpy(tx_power_tlv->alpha2, dev->alpha2, sizeof(dev->alpha2)); tx_power_tlv->n_chan = num_ch; tx_power_tlv->tag = cpu_to_le16(0x1); - tx_power_tlv->len = cpu_to_le16(sizeof(*tx_power_tlv)); + tx_power_tlv->len = cpu_to_le16(msg_len); switch (band) { case NL80211_BAND_2GHZ: From 34163942195410372fb138bea806c9b34e2f5257 Mon Sep 17 00:00:00 2001 From: Zac Bowling Date: Tue, 20 Jan 2026 12:10:32 -0800 Subject: [PATCH 025/134] wifi: mt76: fix list corruption in mt76_wcid_cleanup mt76_wcid_cleanup() was not removing wcid entries from sta_poll_list before mt76_reset_device() reinitializes the master list. This leaves stale pointers in wcid->poll_list, causing list corruption when mt76_wcid_add_poll() later checks list_empty() and tries to add the entry back. The fix adds proper cleanup of poll_list in mt76_wcid_cleanup(), matching how tx_list is already handled. This is similar to what mt7996_mac_sta_deinit_link() already does correctly. Fixes list corruption warnings like: list_add corruption. prev->next should be next (ffffffff...) Signed-off-by: Zac Bowling Link: https://patch.msgid.link/20260120201043.38225-3-zac@zacbowling.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mac80211.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c index 75772979f438..d0c522909e98 100644 --- a/drivers/net/wireless/mediatek/mt76/mac80211.c +++ b/drivers/net/wireless/mediatek/mt76/mac80211.c @@ -1716,6 +1716,16 @@ void mt76_wcid_cleanup(struct mt76_dev *dev, struct mt76_wcid *wcid) idr_destroy(&wcid->pktid); + /* Remove from sta_poll_list to prevent list corruption after reset. + * Without this, mt76_reset_device() reinitializes sta_poll_list but + * leaves wcid->poll_list with stale pointers, causing list corruption + * when mt76_wcid_add_poll() checks list_empty(). + */ + spin_lock_bh(&dev->sta_poll_lock); + if (!list_empty(&wcid->poll_list)) + list_del_init(&wcid->poll_list); + spin_unlock_bh(&dev->sta_poll_lock); + spin_lock_bh(&phy->tx_lock); if (!list_empty(&wcid->tx_list)) From 83ae3a18ba957257b4c406273d2da2caeea2b439 Mon Sep 17 00:00:00 2001 From: Ming Yen Hsieh Date: Thu, 4 Sep 2025 11:06:48 +0800 Subject: [PATCH 026/134] wifi: mt76: mt7925: prevent NULL pointer dereference in mt7925_tx_check_aggr() Move the NULL check for 'sta' before dereferencing it to prevent a possible crash. Fixes: 44eb173bdd4f ("wifi: mt76: mt7925: add link handling in mt7925_txwi_free") Signed-off-by: Ming Yen Hsieh Link: https://patch.msgid.link/20250904030649.655436-4-mingyen.hsieh@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/mac.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c index 63e58c177d65..33cd5e85a31d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c @@ -846,11 +846,14 @@ static void mt7925_tx_check_aggr(struct ieee80211_sta *sta, struct sk_buff *skb, bool is_8023; u16 fc, tid; + if (!sta) + return; + link_sta = rcu_dereference(sta->link[wcid->link_id]); if (!link_sta) return; - if (!sta || !(link_sta->ht_cap.ht_supported || link_sta->he_cap.has_he)) + if (!(link_sta->ht_cap.ht_supported || link_sta->he_cap.has_he)) return; tid = skb->priority & IEEE80211_QOS_CTL_TID_MASK; From 962eb04e67552be406c906c83099c1d736aae3b6 Mon Sep 17 00:00:00 2001 From: Ming Yen Hsieh Date: Thu, 4 Sep 2025 11:06:47 +0800 Subject: [PATCH 027/134] wifi: mt76: mt7925: prevent NULL vif dereference in mt7925_mac_write_txwi Check for a NULL `vif` before accessing `ieee80211_vif_is_mld(vif)` to avoid a potential kernel panic in scenarios where `vif` might not be initialized. Fixes: ebb1406813c6 ("wifi: mt76: mt7925: add link handling to txwi") Signed-off-by: Ming Yen Hsieh Link: https://patch.msgid.link/20250904030649.655436-3-mingyen.hsieh@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/mac.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c index 33cd5e85a31d..0bafa8e770a6 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c @@ -804,8 +804,8 @@ mt7925_mac_write_txwi(struct mt76_dev *dev, __le32 *txwi, txwi[5] = cpu_to_le32(val); val = MT_TXD6_DAS | FIELD_PREP(MT_TXD6_MSDU_CNT, 1); - if (!ieee80211_vif_is_mld(vif) || - (q_idx >= MT_LMAC_ALTX0 && q_idx <= MT_LMAC_BCN0)) + if (vif && (!ieee80211_vif_is_mld(vif) || + (q_idx >= MT_LMAC_ALTX0 && q_idx <= MT_LMAC_BCN0))) val |= MT_TXD6_DIS_MAT; txwi[6] = cpu_to_le32(val); txwi[7] = 0; From d8db56142e531f060c938fa0b5175ed6c8cabb11 Mon Sep 17 00:00:00 2001 From: Alok Tiwari Date: Mon, 13 Oct 2025 02:08:24 -0700 Subject: [PATCH 028/134] wifi: mt76: mt7996: fix FCS error flag check in RX descriptor The mt7996 driver currently checks the MT_RXD3_NORMAL_FCS_ERR bit in rxd1 whereas other Connac3-based drivers(mt7925) correctly check this bit in rxd3. Since the MT_RXD3_NORMAL_FCS_ERR bit is defined in the fourth RX descriptor word (rxd3), update mt7996 to use the proper descriptor field. This change aligns mt7996 with mt7925 and the rest of the Connac3 family. Fixes: 98686cd21624 ("wifi: mt76: mt7996: add driver for MediaTek Wi-Fi 7 (802.11be) devices") Signed-off-by: Alok Tiwari Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20251013090826.753992-1-alok.a.tiwari@oracle.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index 0ca908b87a46..9c1715f4a3b8 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -488,7 +488,7 @@ mt7996_mac_fill_rx(struct mt7996_dev *dev, enum mt76_rxq_id q, !(csum_status & (BIT(0) | BIT(2) | BIT(3)))) skb->ip_summed = CHECKSUM_UNNECESSARY; - if (rxd1 & MT_RXD3_NORMAL_FCS_ERR) + if (rxd3 & MT_RXD3_NORMAL_FCS_ERR) status->flag |= RX_FLAG_FAILED_FCS_CRC; if (rxd1 & MT_RXD1_NORMAL_TKIP_MIC_ERR) From 4d0bf21e3e20619d51d06c0c36207aabab8b712c Mon Sep 17 00:00:00 2001 From: Rory Little Date: Wed, 3 Sep 2025 17:07:11 -0700 Subject: [PATCH 029/134] wifi: mt76: mt7921: Place upper limit on station AID Any station configured with an AID over 20 causes a firmware crash. This situation occurred in our testing using an AP interface on 7922 hardware, with a modified hostapd, sourced from Mediatek's OpenWRT feeds. In stock hostapd, station AIDs begin counting at 1, and this configuration is prevented with an upper limit on associated stations. However, the modified hostapd began allocation at 65, which caused the firmware to crash. This fix does not allow these AIDs to work, but will prevent the firmware crash. This crash was only seen on IFTYPE_AP interfaces, and the fix does not appear to have an effect on IFTYPE_STATION behavior. Fixes: 5c14a5f944b9 ("mt76: mt7921: introduce mt7921e support") Signed-off-by: Rory Little Link: https://patch.msgid.link/20250904000711.3033860-1-rory@candelatech.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7921/main.c | 6 ++++++ drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h | 2 ++ 2 files changed, 8 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/main.c b/drivers/net/wireless/mediatek/mt76/mt7921/main.c index 5fae9a6e273c..debecd3dff75 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/main.c @@ -807,6 +807,9 @@ int mt7921_mac_sta_add(struct mt76_dev *mdev, struct ieee80211_vif *vif, struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv; int ret, idx; + if (sta->aid > MT7921_MAX_AID) + return -ENOENT; + idx = mt76_wcid_alloc(dev->mt76.wcid_mask, MT792x_WTBL_STA - 1); if (idx < 0) return -ENOSPC; @@ -850,6 +853,9 @@ int mt7921_mac_sta_event(struct mt76_dev *mdev, struct ieee80211_vif *vif, struct mt792x_sta *msta = (struct mt792x_sta *)sta->drv_priv; struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv; + if (sta->aid > MT7921_MAX_AID) + return -ENOENT; + if (ev != MT76_STA_EVENT_ASSOC) return 0; diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h b/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h index 83fc7f49ff84..ad92af98e314 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h +++ b/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h @@ -7,6 +7,8 @@ #include "../mt792x.h" #include "regs.h" +#define MT7921_MAX_AID 20 + #define MT7921_TX_RING_SIZE 2048 #define MT7921_TX_MCU_RING_SIZE 256 #define MT7921_TX_FWDL_RING_SIZE 128 From 5373f8b19e568b5c217832b9bbef165bd2b2df14 Mon Sep 17 00:00:00 2001 From: Leon Yen Date: Thu, 9 Oct 2025 10:01:58 +0800 Subject: [PATCH 030/134] wifi: mt76: mt7921: fix a potential clc buffer length underflow The buf_len is used to limit the iterations for retrieving the country power setting and may underflow under certain conditions due to changes in the power table in CLC. This underflow leads to an almost infinite loop or an invalid power setting resulting in driver initialization failure. Cc: stable@vger.kernel.org Fixes: fa6ad88e023d ("wifi: mt76: mt7921: fix country count limitation for CLC") Signed-off-by: Leon Yen Signed-off-by: Ming Yen Hsieh Link: https://patch.msgid.link/20251009020158.1923429-1-mingyen.hsieh@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7921/mcu.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c index 833d0ab64230..8442dbd2ee23 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/mcu.c @@ -1353,6 +1353,9 @@ int __mt7921_mcu_set_clc(struct mt792x_dev *dev, u8 *alpha2, u16 len = le16_to_cpu(rule->len); u16 offset = len + sizeof(*rule); + if (buf_len < offset) + break; + pos += offset; buf_len -= offset; if (rule->alpha2[0] != alpha2[0] || From 6b470f36616e3448d44b0ef4b1de2a3e3a31b5be Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Mon, 8 Dec 2025 19:54:08 +0100 Subject: [PATCH 031/134] wifi: mt76: Fix memory leak destroying device All MT76 rx queues have an associated page_pool even if the queue is not associated to a NAPI (e.g. WED RRO queues with WED enabled). Destroy the page_pool running mt76_dma_cleanup routine during module unload. Moreover returns pages to the page pool if WED is not enabled for WED RRO queues. Fixes: 950d0abb5cd94 ("wifi: mt76: mt7996: add wed rx support") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20251208-mt76-fix-memory-leak-v1-1-cba813fc62b8@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/dma.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/dma.c b/drivers/net/wireless/mediatek/mt76/dma.c index f240016ed9f0..893ac14285ca 100644 --- a/drivers/net/wireless/mediatek/mt76/dma.c +++ b/drivers/net/wireless/mediatek/mt76/dma.c @@ -874,7 +874,12 @@ mt76_dma_rx_cleanup(struct mt76_dev *dev, struct mt76_queue *q) if (!buf) break; - if (!mt76_queue_is_wed_rro(q)) + if (mtk_wed_device_active(&dev->mmio.wed) && + mt76_queue_is_wed_rro(q)) + continue; + + if (!mt76_queue_is_wed_rro_rxdmad_c(q) && + !mt76_queue_is_wed_rro_ind(q)) mt76_put_page_pool_buf(buf, false); } while (1); @@ -1168,10 +1173,6 @@ void mt76_dma_cleanup(struct mt76_dev *dev) mt76_for_each_q_rx(dev, i) { struct mt76_queue *q = &dev->q_rx[i]; - if (mtk_wed_device_active(&dev->mmio.wed) && - mt76_queue_is_wed_rro(q)) - continue; - netif_napi_del(&dev->napi[i]); mt76_dma_rx_cleanup(dev, q); From 7aed20bd9fe427b192cce80a164429584b298bbe Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Thu, 22 Jan 2026 11:39:45 +0100 Subject: [PATCH 032/134] wifi: mt76: mt7996: Fix NPU stop procedure Move mt7996_npu_hw_stop routine before disabling rx NAPIs in order to fix NPU stop procedure used during device L1 SER recovery. Add missing usleep_range in mt7996_npu_hw_stop(). Fixes: 377aa17d2aedc ("wifi: mt76: mt7996: Add NPU offload support to MT7996 driver") Tested-by: Kang Yang Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260122-mt76-npu-eagle-offload-v2-1-2374614c0de6@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/mac.c | 3 +-- .../net/wireless/mediatek/mt76/mt7996/npu.c | 23 +++++++++++-------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index 9c1715f4a3b8..00c6045622e3 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -2504,6 +2504,7 @@ void mt7996_mac_reset_work(struct work_struct *work) if (mtk_wed_device_active(&dev->mt76.mmio.wed)) mtk_wed_device_stop(&dev->mt76.mmio.wed); + mt7996_npu_hw_stop(dev); ieee80211_stop_queues(mt76_hw(dev)); set_bit(MT76_RESET, &dev->mphy.state); @@ -2530,8 +2531,6 @@ void mt7996_mac_reset_work(struct work_struct *work) mutex_lock(&dev->mt76.mutex); - mt7996_npu_hw_stop(dev); - mt76_wr(dev, MT_MCU_INT_EVENT, MT_MCU_INT_EVENT_DMA_STOPPED); if (mt7996_wait_reset_state(dev, MT_MCU_CMD_RESET_DONE)) { diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/npu.c b/drivers/net/wireless/mediatek/mt76/mt7996/npu.c index 1422533e59c7..9c3b241aae38 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/npu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/npu.c @@ -320,33 +320,38 @@ int mt7996_npu_hw_init(struct mt7996_dev *dev) int mt7996_npu_hw_stop(struct mt7996_dev *dev) { struct airoha_npu *npu; - int i, err; + int i, err = 0; u32 info; + mutex_lock(&dev->mt76.mutex); + npu = rcu_dereference_protected(dev->mt76.mmio.npu, &dev->mt76.mutex); if (!npu) - return 0; + goto unlock; err = mt76_npu_send_msg(npu, 4, WLAN_FUNC_SET_WAIT_INODE_TXRX_REG_ADDR, 0, GFP_KERNEL); if (err) - return err; + goto unlock; for (i = 0; i < 10; i++) { err = mt76_npu_get_msg(npu, 3, WLAN_FUNC_GET_WAIT_NPU_INFO, &info, GFP_KERNEL); - if (err) - continue; + if (!err && !info) + break; - if (info) { - err = -ETIMEDOUT; - continue; - } + err = -ETIMEDOUT; + usleep_range(10000, 15000); } if (!err) err = mt76_npu_send_msg(npu, 6, WLAN_FUNC_SET_WAIT_INODE_TXRX_REG_ADDR, 0, GFP_KERNEL); + else + dev_err(dev->mt76.dev, "npu stop failed\n"); +unlock: + mutex_unlock(&dev->mt76.mutex); + return err; } From 25e3203a2192f2b0d697b2410126bad87e62d4f0 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Thu, 22 Jan 2026 11:39:46 +0100 Subject: [PATCH 033/134] wifi: mt76: npu: Add missing rx_token_size initialization Add missing rx_token_size initialization for NPU offloading. Fixes: 7fb554b1b623 ("wifi: mt76: Introduce the NPU generic layer") Tested-by: Kang Yang Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260122-mt76-npu-eagle-offload-v2-2-2374614c0de6@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/npu.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/mediatek/mt76/npu.c b/drivers/net/wireless/mediatek/mt76/npu.c index ec36975f6dc9..9679237f7398 100644 --- a/drivers/net/wireless/mediatek/mt76/npu.c +++ b/drivers/net/wireless/mediatek/mt76/npu.c @@ -457,6 +457,7 @@ int mt76_npu_init(struct mt76_dev *dev, phys_addr_t phy_addr, int type) dev->mmio.npu_type = type; /* NPU offloading requires HW-RRO for RX packet reordering. */ dev->hwrro_mode = MT76_HWRRO_V3_1; + dev->rx_token_size = 32768; rcu_assign_pointer(dev->mmio.npu, npu); rcu_assign_pointer(dev->mmio.ppe_dev, ppe_dev); From f801fec3f0850ac00073bc322c0e4ea446d938ae Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Thu, 22 Jan 2026 11:39:47 +0100 Subject: [PATCH 034/134] wifi: mt76: always enable RRO queues for non-MT7992 chipset MT7990 NPU binary requires to initialize NPU desc_base after configuring ring_size. This is a preliminary patch to enable NPU offload for MT7996 (Eagle) chipset. Tested-by: Kang Yang Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260122-mt76-npu-eagle-offload-v2-3-2374614c0de6@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/dma.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/dma.c b/drivers/net/wireless/mediatek/mt76/dma.c index 893ac14285ca..f5c6bb94ccbb 100644 --- a/drivers/net/wireless/mediatek/mt76/dma.c +++ b/drivers/net/wireless/mediatek/mt76/dma.c @@ -6,6 +6,7 @@ #include #include "mt76.h" #include "dma.h" +#include "mt76_connac.h" static struct mt76_txwi_cache * mt76_alloc_txwi(struct mt76_dev *dev) @@ -188,16 +189,18 @@ mt76_dma_queue_magic_cnt_init(struct mt76_dev *dev, struct mt76_queue *q) static void mt76_dma_sync_idx(struct mt76_dev *dev, struct mt76_queue *q) { - Q_WRITE(q, desc_base, q->desc_dma); - if ((q->flags & MT_QFLAG_WED_RRO_EN) && !mt76_npu_device_active(dev)) + if ((q->flags & MT_QFLAG_WED_RRO_EN) && + (!is_mt7992(dev) || !mt76_npu_device_active(dev))) Q_WRITE(q, ring_size, MT_DMA_RRO_EN | q->ndesc); else Q_WRITE(q, ring_size, q->ndesc); if (mt76_queue_is_npu_tx(q)) { - writel(q->desc_dma, &q->regs->desc_base); writel(q->ndesc, &q->regs->ring_size); + writel(q->desc_dma, &q->regs->desc_base); } + + Q_WRITE(q, desc_base, q->desc_dma); q->head = Q_READ(q, dma_idx); q->tail = q->head; } From b849930f2ce77185b833d93ab4317a33ffc584c5 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Thu, 22 Jan 2026 11:39:48 +0100 Subject: [PATCH 035/134] wifi: mt76: mt7996: Fix BAND2 tx queues initialization when NPU is enabled Fix BAND2 tx queues initialization for MT7990 chipset when NPU is enabled. This is a preliminary patch to enable NPU offload for MT7996 (Eagle) chipset. Tested-by: Kang Yang Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260122-mt76-npu-eagle-offload-v2-4-2374614c0de6@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/init.c | 18 ++++++++++++------ .../net/wireless/mediatek/mt76/mt7996/mt7996.h | 1 + 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index 2e439f0815d4..e678f06b4556 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -683,8 +683,9 @@ static int mt7996_register_phy(struct mt7996_dev *dev, enum mt76_band_id band) return 0; if (dev->hif2 && - ((is_mt7996(&dev->mt76) && band == MT_BAND2) || - (is_mt7992(&dev->mt76) && band == MT_BAND1))) { + ((is_mt7992(&dev->mt76) && band == MT_BAND1) || + (is_mt7996(&dev->mt76) && band == MT_BAND2 && + !mt76_npu_device_active(&dev->mt76)))) { hif1_ofs = MT_WFDMA0_PCIE1(0) - MT_WFDMA0(0); wed = &dev->mt76.mmio.wed_hif2; } @@ -724,14 +725,19 @@ static int mt7996_register_phy(struct mt7996_dev *dev, enum mt76_band_id band) /* init wiphy according to mphy and phy */ mt7996_init_wiphy_band(mphy->hw, phy); - if (is_mt7996(&dev->mt76) && !dev->hif2 && band == MT_BAND1) { + if (is_mt7996(&dev->mt76) && + ((band == MT_BAND1 && !dev->hif2) || + (band == MT_BAND2 && mt76_npu_device_active(&dev->mt76)))) { int i; for (i = 0; i <= MT_TXQ_PSD; i++) - mphy->q_tx[i] = dev->mt76.phys[MT_BAND0]->q_tx[0]; + mphy->q_tx[i] = dev->mt76.phys[band - 1]->q_tx[0]; } else { - ret = mt7996_init_tx_queues(mphy->priv, MT_TXQ_ID(band), - MT7996_TX_RING_SIZE, + int size = is_mt7996(&dev->mt76) && + mt76_npu_device_active(&dev->mt76) + ? MT7996_NPU_TX_RING_SIZE / 2 : MT7996_TX_RING_SIZE; + + ret = mt7996_init_tx_queues(mphy->priv, MT_TXQ_ID(band), size, MT_TXQ_RING_BASE(band) + hif1_ofs, wed); if (ret) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index f8b79b05169b..09808e79ec86 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -29,6 +29,7 @@ #define MT7996_RX_RING_SIZE 1536 #define MT7996_RX_MCU_RING_SIZE 512 #define MT7996_RX_MCU_RING_SIZE_WA 1024 +#define MT7996_NPU_TX_RING_SIZE 1024 /* scatter-gather of mcu event is not supported in connac3 */ #define MT7996_RX_MCU_BUF_SIZE (2048 + \ SKB_DATA_ALIGN(sizeof(struct skb_shared_info))) From 00fa11ec4ab236a0e959093dc804285533846213 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Thu, 22 Jan 2026 11:39:49 +0100 Subject: [PATCH 036/134] wifi: mt76: mt7996: Fix wdma_idx for MT7996 device if NPU is enabled This is a preliminary patch to enable NPU offload for MT7996 (Eagle) chipset. Tested-by: Kang Yang Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260122-mt76-npu-eagle-offload-v2-5-2374614c0de6@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/main.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 493c47c59d57..c0fae7aec1ae 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -2291,6 +2291,10 @@ mt7996_net_fill_forward_path(struct ieee80211_hw *hw, path->mtk_wdma.wdma_idx = wed->wdma_idx; else #endif + if (is_mt7996(&dev->mt76) && mt76_npu_device_active(&dev->mt76) && + msta_link->wcid.phy_idx == MT_BAND2) + path->mtk_wdma.wdma_idx = 1; + else path->mtk_wdma.wdma_idx = link->mt76.band_idx; path->mtk_wdma.bss = link->mt76.idx; path->mtk_wdma.queue = 0; From a9ac8f837f12ce180da90e70277c36ecd04b01e2 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Thu, 22 Jan 2026 11:39:50 +0100 Subject: [PATCH 037/134] wifi: mt76: mt7996: Add mt7992_npu_txrx_offload_init routine Introduce mt7992_npu_txrx_offload_init utility routine. This is a preliminary patch to enable NPU offload for MT7996 (Eagle) chipset. Tested-by: Kang Yang Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260122-mt76-npu-eagle-offload-v2-6-2374614c0de6@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/npu.c | 63 +++++++++++-------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/npu.c b/drivers/net/wireless/mediatek/mt76/mt7996/npu.c index 9c3b241aae38..0f1aabd63748 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/npu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/npu.c @@ -8,34 +8,14 @@ #include "mt7996.h" -static int mt7996_npu_offload_init(struct mt7996_dev *dev, - struct airoha_npu *npu) +static int mt7992_npu_txrx_offload_init(struct mt7996_dev *dev, + struct airoha_npu *npu) { + u32 hif1_ofs = dev->hif2 ? MT_WFDMA0_PCIE1(0) - MT_WFDMA0(0) : 0; phys_addr_t phy_addr = dev->mt76.mmio.phy_addr; - u32 val, hif1_ofs = 0, dma_addr; + u32 dma_addr; int i, err; - err = mt76_npu_get_msg(npu, 0, WLAN_FUNC_GET_WAIT_NPU_VERSION, - &val, GFP_KERNEL); - if (err) { - dev_warn(dev->mt76.dev, "failed getting NPU fw version\n"); - return err; - } - - dev_info(dev->mt76.dev, "NPU version: %0d.%d\n", - (val >> 16) & 0xffff, val & 0xffff); - - err = mt76_npu_send_msg(npu, 0, WLAN_FUNC_SET_WAIT_PCIE_PORT_TYPE, - dev->mt76.mmio.npu_type, GFP_KERNEL); - if (err) { - dev_warn(dev->mt76.dev, - "failed setting NPU wlan PCIe port type\n"); - return err; - } - - if (dev->hif2) - hif1_ofs = MT_WFDMA0_PCIE1(0) - MT_WFDMA0(0); - for (i = MT_BAND0; i < MT_BAND2; i++) { dma_addr = phy_addr; if (i) @@ -56,7 +36,7 @@ static int mt7996_npu_offload_init(struct mt7996_dev *dev, MT7996_RX_RING_SIZE, GFP_KERNEL); if (err) { dev_warn(dev->mt76.dev, - "failed setting NPU wlan PCIe desc size\n"); + "failed setting NPU wlan rx desc size\n"); return err; } @@ -97,10 +77,41 @@ static int mt7996_npu_offload_init(struct mt7996_dev *dev, phy_addr + MT_RRO_ACK_SN_CTRL, GFP_KERNEL); if (err) { dev_warn(dev->mt76.dev, - "failed setting NPU wlan rro_ack_sn desc addr\n"); + "failed setting NPU wlan tx desc addr\n"); return err; } + return 0; +} + +static int mt7996_npu_offload_init(struct mt7996_dev *dev, + struct airoha_npu *npu) +{ + u32 val; + int err; + + err = mt76_npu_get_msg(npu, 0, WLAN_FUNC_GET_WAIT_NPU_VERSION, + &val, GFP_KERNEL); + if (err) { + dev_warn(dev->mt76.dev, "failed getting NPU fw version\n"); + return err; + } + + dev_info(dev->mt76.dev, "NPU version: %0d.%d\n", + (val >> 16) & 0xffff, val & 0xffff); + + err = mt76_npu_send_msg(npu, 0, WLAN_FUNC_SET_WAIT_PCIE_PORT_TYPE, + dev->mt76.mmio.npu_type, GFP_KERNEL); + if (err) { + dev_warn(dev->mt76.dev, + "failed setting NPU wlan PCIe port type\n"); + return err; + } + + err = mt7992_npu_txrx_offload_init(dev, npu); + if (err) + return err; + err = mt76_npu_send_msg(npu, 0, WLAN_FUNC_SET_WAIT_TOKEN_ID_SIZE, MT7996_HW_TOKEN_SIZE, GFP_KERNEL); if (err) From c93e2fbdc79b91e5c221446f970ab847db38309e Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Thu, 22 Jan 2026 11:39:51 +0100 Subject: [PATCH 038/134] wifi: mt76: mt7996: Rename mt7996_npu_rxd_init() in mt7992_npu_rxd_init() This is a preliminary patch to enable NPU offload for MT7996 (Eagle) chipset. Tested-by: Kang Yang Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260122-mt76-npu-eagle-offload-v2-7-2374614c0de6@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/npu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/npu.c b/drivers/net/wireless/mediatek/mt76/mt7996/npu.c index 0f1aabd63748..b2e7b5aba272 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/npu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/npu.c @@ -122,7 +122,7 @@ static int mt7996_npu_offload_init(struct mt7996_dev *dev, return 0; } -static int mt7996_npu_rxd_init(struct mt7996_dev *dev, struct airoha_npu *npu) +static int mt7992_npu_rxd_init(struct mt7996_dev *dev, struct airoha_npu *npu) { u32 val; int err; @@ -304,7 +304,7 @@ int mt7996_npu_hw_init(struct mt7996_dev *dev) if (err) goto unlock; - err = mt7996_npu_rxd_init(dev, npu); + err = mt7992_npu_rxd_init(dev, npu); if (err) goto unlock; From 880f4e3e5a4c465fba0390ed3c2afa1d7eece550 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Thu, 22 Jan 2026 11:39:52 +0100 Subject: [PATCH 039/134] wifi: mt76: mt7996: Add NPU support for MT7990 chipset Introduce support for MT7990 chipset in MT7996 npu configuration codebase. Tested-by: Kang Yang Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260122-mt76-npu-eagle-offload-v2-8-2374614c0de6@kernel.org Signed-off-by: Felix Fietkau --- .../wireless/mediatek/mt76/mt7996/mt7996.h | 1 + .../net/wireless/mediatek/mt76/mt7996/npu.c | 306 ++++++++++++++++-- 2 files changed, 276 insertions(+), 31 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index 09808e79ec86..c9c506e1d6ed 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -30,6 +30,7 @@ #define MT7996_RX_MCU_RING_SIZE 512 #define MT7996_RX_MCU_RING_SIZE_WA 1024 #define MT7996_NPU_TX_RING_SIZE 1024 +#define MT7996_NPU_RX_RING_SIZE 1024 /* scatter-gather of mcu event is not supported in connac3 */ #define MT7996_RX_MCU_BUF_SIZE (2048 + \ SKB_DATA_ALIGN(sizeof(struct skb_shared_info))) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/npu.c b/drivers/net/wireless/mediatek/mt76/mt7996/npu.c index b2e7b5aba272..c3307bbbb547 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/npu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/npu.c @@ -17,21 +17,6 @@ static int mt7992_npu_txrx_offload_init(struct mt7996_dev *dev, int i, err; for (i = MT_BAND0; i < MT_BAND2; i++) { - dma_addr = phy_addr; - if (i) - dma_addr += MT_RXQ_RING_BASE(MT_RXQ_RRO_BAND1) + 0x90 + - hif1_ofs; - else - dma_addr += MT_RXQ_RING_BASE(MT_RXQ_RRO_BAND0) + 0x80; - - err = mt76_npu_send_msg(npu, i, WLAN_FUNC_SET_WAIT_PCIE_ADDR, - dma_addr, GFP_KERNEL); - if (err) { - dev_warn(dev->mt76.dev, - "failed setting NPU wlan PCIe desc addr\n"); - return err; - } - err = mt76_npu_send_msg(npu, i, WLAN_FUNC_SET_WAIT_DESC, MT7996_RX_RING_SIZE, GFP_KERNEL); if (err) { @@ -84,6 +69,134 @@ static int mt7992_npu_txrx_offload_init(struct mt7996_dev *dev, return 0; } +static int mt7996_npu_txrx_offload_init(struct mt7996_dev *dev, + struct airoha_npu *npu) +{ + u32 hif1_ofs = dev->hif2 ? MT_WFDMA0_PCIE1(0) - MT_WFDMA0(0) : 0; + phys_addr_t phy_addr = dev->mt76.mmio.phy_addr; + u32 dma_addr; + int err; + + /* npu rx rro ring0 */ + err = mt76_npu_send_msg(npu, 0, WLAN_FUNC_SET_WAIT_DESC, + MT7996_RX_RING_SIZE, GFP_KERNEL); + if (err) { + dev_warn(dev->mt76.dev, + "failed setting NPU wlan rx desc size\n"); + return err; + } + + /* npu rx rro ring1 */ + err = mt76_npu_send_msg(npu, 2, WLAN_FUNC_SET_WAIT_DESC, + MT7996_NPU_RX_RING_SIZE, GFP_KERNEL); + if (err) { + dev_warn(dev->mt76.dev, + "failed setting NPU wlan rx desc size\n"); + return err; + } + + /* msdu pg 2GHz */ + dma_addr = phy_addr + MT_RXQ_RING_BASE(MT_RXQ_MSDU_PAGE_BAND0) + 0xa0; + err = mt76_npu_send_msg(npu, 5, WLAN_FUNC_SET_WAIT_PCIE_ADDR, + dma_addr, GFP_KERNEL); + if (err) { + dev_warn(dev->mt76.dev, + "failed setting NPU wlan PCIe desc addr\n"); + return err; + } + + err = mt76_npu_send_msg(npu, 5, WLAN_FUNC_SET_WAIT_DESC, + MT7996_NPU_RX_RING_SIZE / 4, GFP_KERNEL); + if (err) { + dev_warn(dev->mt76.dev, + "failed setting NPU wlan rx desc size\n"); + return err; + } + + /* msdu pg 5GHz */ + dma_addr = phy_addr + MT_RXQ_RING_BASE(MT_RXQ_MSDU_PAGE_BAND1) + 0xb0; + err = mt76_npu_send_msg(npu, 6, WLAN_FUNC_SET_WAIT_PCIE_ADDR, + dma_addr, GFP_KERNEL); + if (err) { + dev_warn(dev->mt76.dev, + "failed setting NPU wlan PCIe desc addr\n"); + return err; + } + + err = mt76_npu_send_msg(npu, 6, WLAN_FUNC_SET_WAIT_DESC, + MT7996_NPU_RX_RING_SIZE / 2, GFP_KERNEL); + if (err) { + dev_warn(dev->mt76.dev, + "failed setting NPU wlan rx desc size\n"); + return err; + } + + /* msdu pg 6GHz */ + dma_addr = phy_addr + MT_RXQ_RING_BASE(MT_RXQ_MSDU_PAGE_BAND2) + 0xc0; + err = mt76_npu_send_msg(npu, 7, WLAN_FUNC_SET_WAIT_PCIE_ADDR, + dma_addr, GFP_KERNEL); + if (err) { + dev_warn(dev->mt76.dev, + "failed setting NPU wlan PCIe desc addr\n"); + return err; + } + + err = mt76_npu_send_msg(npu, 7, WLAN_FUNC_SET_WAIT_DESC, + MT7996_NPU_RX_RING_SIZE, GFP_KERNEL); + if (err) { + dev_warn(dev->mt76.dev, + "failed setting NPU wlan rx desc size\n"); + return err; + } + + /* ind cmd ring */ + err = mt76_npu_send_msg(npu, 8, WLAN_FUNC_SET_WAIT_PCIE_ADDR, + phy_addr + MT_RXQ_RRO_IND_RING_BASE, + GFP_KERNEL); + if (err) { + dev_warn(dev->mt76.dev, + "failed setting NPU wlan PCIe desc addr\n"); + return err; + } + + err = mt76_npu_send_msg(npu, 8, WLAN_FUNC_SET_WAIT_DESC, + MT7996_RX_RING_SIZE, GFP_KERNEL); + if (err) { + dev_warn(dev->mt76.dev, + "failed setting NPU wlan rx desc size\n"); + return err; + } + + err = mt76_npu_send_msg(npu, 3, WLAN_FUNC_SET_WAIT_TX_RING_PCIE_ADDR, + phy_addr + MT_RRO_ACK_SN_CTRL, GFP_KERNEL); + if (err) { + dev_warn(dev->mt76.dev, + "failed setting NPU wlan tx desc addr\n"); + return err; + } + + /* npu tx */ + dma_addr = phy_addr + MT_TXQ_RING_BASE(1) + 0x120; + err = mt76_npu_send_msg(npu, 0, WLAN_FUNC_SET_WAIT_TX_RING_PCIE_ADDR, + dma_addr, GFP_KERNEL); + if (err) { + dev_warn(dev->mt76.dev, + "failed setting NPU wlan tx desc addr\n"); + return err; + } + + dma_addr = phy_addr + MT_TXQ_RING_BASE(0) + 0x150 + hif1_ofs; + err = mt76_npu_send_msg(npu, 2, WLAN_FUNC_SET_WAIT_TX_RING_PCIE_ADDR, + dma_addr, GFP_KERNEL); + if (err) { + dev_warn(dev->mt76.dev, + "failed setting NPU wlan tx desc addr\n"); + return err; + } + + return 0; +} + static int mt7996_npu_offload_init(struct mt7996_dev *dev, struct airoha_npu *npu) { @@ -108,7 +221,11 @@ static int mt7996_npu_offload_init(struct mt7996_dev *dev, return err; } - err = mt7992_npu_txrx_offload_init(dev, npu); + if (is_mt7996(&dev->mt76)) + err = mt7996_npu_txrx_offload_init(dev, npu); + else + err = mt7992_npu_txrx_offload_init(dev, npu); + if (err) return err; @@ -157,15 +274,84 @@ static int mt7992_npu_rxd_init(struct mt7996_dev *dev, struct airoha_npu *npu) return 0; } +static int mt7996_npu_rxd_init(struct mt7996_dev *dev, struct airoha_npu *npu) +{ + u32 val; + int err; + + err = mt76_npu_get_msg(npu, 0, WLAN_FUNC_GET_WAIT_RXDESC_BASE, + &val, GFP_KERNEL); + if (err) { + dev_warn(dev->mt76.dev, + "failed retriving NPU wlan rx ring0 addr\n"); + return err; + } + writel(val, &dev->mt76.q_rx[MT_RXQ_RRO_BAND0].regs->desc_base); + + err = mt76_npu_get_msg(npu, 2, WLAN_FUNC_GET_WAIT_RXDESC_BASE, + &val, GFP_KERNEL); + if (err) { + dev_warn(dev->mt76.dev, + "failed retriving NPU wlan rx ring2 addr\n"); + return err; + } + writel(val, &dev->mt76.q_rx[MT_RXQ_RRO_BAND2].regs->desc_base); + + /* msdu pg ring */ + err = mt76_npu_get_msg(npu, 10, WLAN_FUNC_GET_WAIT_RXDESC_BASE, + &val, GFP_KERNEL); + if (err) { + dev_warn(dev->mt76.dev, + "failed retriving NPU wlan msdu pg ring addr\n"); + return err; + } + writel(val, &dev->mt76.q_rx[MT_RXQ_MSDU_PAGE_BAND0].regs->desc_base); + + err = mt76_npu_get_msg(npu, 11, WLAN_FUNC_GET_WAIT_RXDESC_BASE, + &val, GFP_KERNEL); + if (err) { + dev_warn(dev->mt76.dev, + "failed retriving NPU wlan msdu pg ring addr\n"); + return err; + } + writel(val, &dev->mt76.q_rx[MT_RXQ_MSDU_PAGE_BAND1].regs->desc_base); + + err = mt76_npu_get_msg(npu, 12, WLAN_FUNC_GET_WAIT_RXDESC_BASE, + &val, GFP_KERNEL); + if (err) { + dev_warn(dev->mt76.dev, + "failed retriving NPU wlan msdu pg ring addr\n"); + return err; + } + writel(val, &dev->mt76.q_rx[MT_RXQ_MSDU_PAGE_BAND2].regs->desc_base); + + /* ind_cmd ring */ + err = mt76_npu_get_msg(npu, 8, WLAN_FUNC_GET_WAIT_RXDESC_BASE, + &val, GFP_KERNEL); + if (err) { + dev_warn(dev->mt76.dev, + "failed retriving NPU wlan ind_cmd ring addr\n"); + return err; + } + writel(val, &dev->mt76.q_rx[MT_RXQ_RRO_IND].regs->desc_base); + + return 0; +} + static int mt7996_npu_txd_init(struct mt7996_dev *dev, struct airoha_npu *npu) { - int i, err; + const enum mt76_band_id band_list[] = { + MT_BAND0, + is_mt7996(&dev->mt76) ? MT_BAND2 : MT_BAND1, + }; + int i; - for (i = MT_BAND0; i < MT_BAND2; i++) { + for (i = 0; i < ARRAY_SIZE(band_list); i++) { + int err, band = band_list[i], phy_id; dma_addr_t dma_addr; - u32 val; + u32 val, size; - err = mt76_npu_get_msg(npu, i + 5, + err = mt76_npu_get_msg(npu, band + 5, WLAN_FUNC_GET_WAIT_RXDESC_BASE, &val, GFP_KERNEL); if (err) { @@ -173,14 +359,20 @@ static int mt7996_npu_txd_init(struct mt7996_dev *dev, struct airoha_npu *npu) "failed retrieving NPU wlan tx ring addr\n"); return err; } - writel(val, &dev->mt76.phys[i]->q_tx[0]->regs->desc_base); - if (!dmam_alloc_coherent(dev->mt76.dma_dev, - 256 * MT7996_TX_RING_SIZE, + phy_id = is_mt7996(&dev->mt76) ? band == MT_BAND0 ? 1 : 0 + : band; + writel(val, &dev->mt76.phys[phy_id]->q_tx[0]->regs->desc_base); + + size = is_mt7996(&dev->mt76) ? band == MT_BAND2 + ? MT7996_NPU_TX_RING_SIZE + : MT7996_NPU_RX_RING_SIZE / 2 + : MT7996_TX_RING_SIZE; + if (!dmam_alloc_coherent(dev->mt76.dma_dev, 256 * size, &dma_addr, GFP_KERNEL)) return -ENOMEM; - err = mt76_npu_send_msg(npu, i, + err = mt76_npu_send_msg(npu, band, WLAN_FUNC_SET_WAIT_TX_BUF_SPACE_HW_BASE, dma_addr, GFP_KERNEL); if (err) { @@ -189,12 +381,11 @@ static int mt7996_npu_txd_init(struct mt7996_dev *dev, struct airoha_npu *npu) return err; } - if (!dmam_alloc_coherent(dev->mt76.dma_dev, - 256 * MT7996_TX_RING_SIZE, + if (!dmam_alloc_coherent(dev->mt76.dma_dev, 256 * size, &dma_addr, GFP_KERNEL)) return -ENOMEM; - err = mt76_npu_send_msg(npu, i + 5, + err = mt76_npu_send_msg(npu, band + 5, WLAN_FUNC_SET_WAIT_TX_BUF_SPACE_HW_BASE, dma_addr, GFP_KERNEL); if (err) { @@ -207,7 +398,7 @@ static int mt7996_npu_txd_init(struct mt7996_dev *dev, struct airoha_npu *npu) &dma_addr, GFP_KERNEL)) return -ENOMEM; - err = mt76_npu_send_msg(npu, i + 10, + err = mt76_npu_send_msg(npu, band + 10, WLAN_FUNC_SET_WAIT_TX_BUF_SPACE_HW_BASE, dma_addr, GFP_KERNEL); if (err) { @@ -223,8 +414,9 @@ static int mt7996_npu_txd_init(struct mt7996_dev *dev, struct airoha_npu *npu) static int mt7996_npu_rx_event_init(struct mt7996_dev *dev, struct airoha_npu *npu) { - struct mt76_queue *q = &dev->mt76.q_rx[MT_RXQ_MAIN_WA]; + int qid = is_mt7996(&dev->mt76) ? MT_RXQ_TXFREE_BAND0 : MT_RXQ_MAIN_WA; phys_addr_t phy_addr = dev->mt76.mmio.phy_addr; + struct mt76_queue *q = &dev->mt76.q_rx[qid]; int err; err = mt76_npu_send_msg(npu, 0, @@ -244,7 +436,8 @@ static int mt7996_npu_rx_event_init(struct mt7996_dev *dev, return err; } - phy_addr += MT_RXQ_RING_BASE(MT_RXQ_MAIN_WA) + 0x20; + phy_addr += MT_RXQ_RING_BASE(qid); + phy_addr += is_mt7996(&dev->mt76) ? 0x90 : 0x20; err = mt76_npu_send_msg(npu, 10, WLAN_FUNC_SET_WAIT_PCIE_ADDR, phy_addr, GFP_KERNEL); if (err) @@ -253,11 +446,54 @@ static int mt7996_npu_rx_event_init(struct mt7996_dev *dev, return err; } +static int mt7996_npu_set_pcie_addr(struct mt7996_dev *dev, + struct airoha_npu *npu) +{ + u32 hif1_ofs = dev->hif2 ? MT_WFDMA0_PCIE1(0) - MT_WFDMA0(0) : 0; + dma_addr_t dma_addr = dev->mt76.mmio.phy_addr; + int err; + + dma_addr += MT_RXQ_RING_BASE(MT_RXQ_RRO_BAND0) + 0x80; + err = mt76_npu_send_msg(npu, 0, WLAN_FUNC_SET_WAIT_PCIE_ADDR, + dma_addr, GFP_KERNEL); + if (err) { + dev_warn(dev->mt76.dev, + "failed setting NPU wlan PCIe desc addr\n"); + return err; + } + + dma_addr = dev->mt76.mmio.phy_addr + hif1_ofs; + if (is_mt7996(&dev->mt76)) { + dma_addr += MT_RXQ_RING_BASE(MT_RXQ_RRO_BAND2) + 0x60; + err = mt76_npu_send_msg(npu, 2, WLAN_FUNC_SET_WAIT_PCIE_ADDR, + dma_addr, GFP_KERNEL); + } else { + dma_addr += MT_RXQ_RING_BASE(MT_RXQ_RRO_BAND1) + 0x90; + err = mt76_npu_send_msg(npu, 1, WLAN_FUNC_SET_WAIT_PCIE_ADDR, + dma_addr, GFP_KERNEL); + } + + if (err) + dev_warn(dev->mt76.dev, + "failed setting NPU wlan PCIe desc addr\n"); + + return err; +} + static int mt7996_npu_tx_done_init(struct mt7996_dev *dev, struct airoha_npu *npu) { int err; + /* rro ring cpu idx */ + err = mt76_npu_send_msg(npu, 15, WLAN_FUNC_SET_WAIT_PCIE_ADDR, + 0, GFP_KERNEL); + if (err) { + dev_warn(dev->mt76.dev, + "failed setting NPU wlan PCIe desc addr\n"); + return err; + } + err = mt76_npu_send_msg(npu, 2, WLAN_FUNC_SET_WAIT_INODE_TXRX_REG_ADDR, 0, GFP_KERNEL); if (err) { @@ -304,7 +540,11 @@ int mt7996_npu_hw_init(struct mt7996_dev *dev) if (err) goto unlock; - err = mt7992_npu_rxd_init(dev, npu); + if (is_mt7996(&dev->mt76)) + err = mt7996_npu_rxd_init(dev, npu); + else + err = mt7992_npu_rxd_init(dev, npu); + if (err) goto unlock; @@ -316,6 +556,10 @@ int mt7996_npu_hw_init(struct mt7996_dev *dev) if (err) goto unlock; + err = mt7996_npu_set_pcie_addr(dev, npu); + if (err) + goto unlock; + err = mt7996_npu_tx_done_init(dev, npu); if (err) goto unlock; From aa6a0ded87d7dababcb2e9b23e8137131557b8fa Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Thu, 22 Jan 2026 11:39:53 +0100 Subject: [PATCH 040/134] wifi: mt76: mt7996: Integrate NPU in RRO session management Add NPU integration in RRO 3.0 session management. This is a preliminary patch to enable NPU offload for MT7996 (Eagle) chipset. Tested-by: Kang Yang Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260122-mt76-npu-eagle-offload-v2-9-2374614c0de6@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt76.h | 10 +++++++ .../net/wireless/mediatek/mt76/mt7996/init.c | 16 +++++++++- drivers/net/wireless/mediatek/mt76/npu.c | 30 +++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index d05e83ea1cac..eefc3f555f8a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -1649,6 +1649,9 @@ void mt76_npu_txdesc_cleanup(struct mt76_queue *q, int index); int mt76_npu_net_setup_tc(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct net_device *dev, enum tc_setup_type type, void *type_data); +int mt76_npu_send_txrx_addr(struct mt76_dev *dev, int ifindex, + u32 direction, u32 i_count_addr, + u32 o_status_addr, u32 o_count_addr); #else static inline void mt76_npu_check_ppe(struct mt76_dev *dev, struct sk_buff *skb, u32 info) @@ -1707,6 +1710,13 @@ static inline int mt76_npu_net_setup_tc(struct ieee80211_hw *hw, { return -EOPNOTSUPP; } + +static inline int mt76_npu_send_txrx_addr(struct mt76_dev *dev, int ifindex, + u32 direction, u32 i_count_addr, + u32 o_status_addr, u32 o_count_addr) +{ + return -EOPNOTSUPP; +} #endif /* CONFIG_MT76_NPU */ static inline bool mt76_npu_device_active(struct mt76_dev *dev) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index e678f06b4556..b0f0d3adbb04 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -959,6 +959,12 @@ static int mt7996_wed_rro_init(struct mt7996_dev *dev) addr++; } + if (is_mt7996(&dev->mt76) && + mt76_npu_device_active(&dev->mt76)) + mt76_npu_send_txrx_addr(&dev->mt76, 0, i, + dev->wed_rro.addr_elem[i].phy_addr, + 0, 0); + #ifdef CONFIG_NET_MEDIATEK_SOC_WED if (mtk_wed_device_active(&dev->mt76.mmio.wed) && mtk_wed_get_rx_capa(&dev->mt76.mmio.wed)) { @@ -1019,6 +1025,10 @@ static int mt7996_wed_rro_init(struct mt7996_dev *dev) addr++; } + if (is_mt7996(&dev->mt76) && mt76_npu_device_active(&dev->mt76)) + mt76_npu_send_txrx_addr(&dev->mt76, 1, 0, + dev->wed_rro.session.phy_addr, 0, 0); + mt7996_rro_hw_init(dev); return mt7996_dma_rro_init(dev); @@ -1105,8 +1115,12 @@ static void mt7996_wed_rro_work(struct work_struct *work) list); list_del_init(&e->list); - if (mt76_npu_device_active(&dev->mt76)) + if (mt76_npu_device_active(&dev->mt76)) { + if (is_mt7996(&dev->mt76)) + mt76_npu_send_txrx_addr(&dev->mt76, 3, e->id, + 0, 0, 0); goto reset_session; + } for (i = 0; i < MT7996_RRO_WINDOW_MAX_LEN; i++) { void *ptr = dev->wed_rro.session.ptr; diff --git a/drivers/net/wireless/mediatek/mt76/npu.c b/drivers/net/wireless/mediatek/mt76/npu.c index 9679237f7398..bc8f2012be9d 100644 --- a/drivers/net/wireless/mediatek/mt76/npu.c +++ b/drivers/net/wireless/mediatek/mt76/npu.c @@ -390,6 +390,36 @@ int mt76_npu_net_setup_tc(struct ieee80211_hw *hw, struct ieee80211_vif *vif, } EXPORT_SYMBOL_GPL(mt76_npu_net_setup_tc); +int mt76_npu_send_txrx_addr(struct mt76_dev *dev, int ifindex, + u32 direction, u32 i_count_addr, + u32 o_status_addr, u32 o_count_addr) +{ + struct { + __le32 dir; + __le32 in_count_addr; + __le32 out_status_addr; + __le32 out_count_addr; + } info = { + .dir = cpu_to_le32(direction), + .in_count_addr = cpu_to_le32(i_count_addr), + .out_status_addr = cpu_to_le32(o_status_addr), + .out_count_addr = cpu_to_le32(o_count_addr), + }; + struct airoha_npu *npu; + int err = -ENODEV; + + rcu_read_lock(); + npu = rcu_dereference(dev->mmio.npu); + if (npu) + err = airoha_npu_wlan_send_msg(npu, ifindex, + WLAN_FUNC_SET_WAIT_INODE_TXRX_REG_ADDR, + &info, sizeof(info), GFP_ATOMIC); + rcu_read_unlock(); + + return err; +} +EXPORT_SYMBOL_GPL(mt76_npu_send_txrx_addr); + void mt76_npu_disable_irqs(struct mt76_dev *dev) { struct airoha_npu *npu; From 26c28522fa460435bd9a0dc4e05ae599f21ada6b Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Thu, 22 Jan 2026 11:39:54 +0100 Subject: [PATCH 041/134] wifi: mt76: mt7996: Integrate MT7990 init configuration for NPU Add NPU integration in MT7996 init codebase for MT7990 chipset. This is a preliminary patch to enable NPU offload for MT7996 (Eagle) chipset. Tested-by: Kang Yang Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260122-mt76-npu-eagle-offload-v2-10-2374614c0de6@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/init.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index b0f0d3adbb04..b76bd324a927 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -607,7 +607,7 @@ static void mt7996_mac_init_basic_rates(struct mt7996_dev *dev) void mt7996_mac_init(struct mt7996_dev *dev) { #define HIF_TXD_V2_1 0x21 - int i; + int i, rx_path_type; mt76_clear(dev, MT_MDP_DCR2, MT_MDP_DCR2_RX_TRANS_SHORT); @@ -621,11 +621,16 @@ void mt7996_mac_init(struct mt7996_dev *dev) } /* rro module init */ - if (dev->hif2) + if (dev->hif2) { + if (mt76_npu_device_active(&dev->mt76)) + rx_path_type = is_mt7996(&dev->mt76) ? 6 : 8; + else + rx_path_type = is_mt7996(&dev->mt76) ? 2 : 7; mt7996_mcu_set_rro(dev, UNI_RRO_SET_PLATFORM_TYPE, - is_mt7996(&dev->mt76) ? 2 : 7); - else + rx_path_type); + } else { mt7996_mcu_set_rro(dev, UNI_RRO_SET_PLATFORM_TYPE, 0); + } if (mt7996_has_hwrro(dev)) { u16 timeout; From cd7951f242a7e4114de8f41804d708f5b5079d53 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Thu, 22 Jan 2026 11:39:55 +0100 Subject: [PATCH 042/134] wifi: mt76: mt7996: Integrate MT7990 dma configuration for NPU Add NPU integration in MT7996 dma codebase for MT7990 chipset. This is a preliminary patch to enable NPU offload for MT7996 (Eagle) chipset. Tested-by: Kang Yang Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260122-mt76-npu-eagle-offload-v2-11-2374614c0de6@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/dma.c | 132 ++++++++++++------ 1 file changed, 86 insertions(+), 46 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/dma.c b/drivers/net/wireless/mediatek/mt76/mt7996/dma.c index 274b273df1ee..07212d93bc62 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/dma.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/dma.c @@ -128,15 +128,27 @@ static void mt7996_dma_config(struct mt7996_dev *dev) /* data tx queue */ if (is_mt7996(&dev->mt76)) { - TXQ_CONFIG(0, WFDMA0, MT_INT_TX_DONE_BAND0, MT7996_TXQ_BAND0); if (dev->hif2) { - /* default bn1:ring19 bn2:ring21 */ - TXQ_CONFIG(1, WFDMA0, MT_INT_TX_DONE_BAND1, - MT7996_TXQ_BAND1); - TXQ_CONFIG(2, WFDMA0, MT_INT_TX_DONE_BAND2, - MT7996_TXQ_BAND2); + if (mt76_npu_device_active(&dev->mt76)) { + TXQ_CONFIG(0, WFDMA0, MT_INT_TX_DONE_BAND2, + MT7996_TXQ_BAND2); + TXQ_CONFIG(1, WFDMA0, MT_INT_TX_DONE_BAND0, + MT7996_TXQ_BAND0); + TXQ_CONFIG(2, WFDMA0, MT_INT_TX_DONE_BAND1, + MT7996_TXQ_BAND1); + } else { + /* default bn1:ring19 bn2:ring21 */ + TXQ_CONFIG(0, WFDMA0, MT_INT_TX_DONE_BAND0, + MT7996_TXQ_BAND0); + TXQ_CONFIG(1, WFDMA0, MT_INT_TX_DONE_BAND1, + MT7996_TXQ_BAND1); + TXQ_CONFIG(2, WFDMA0, MT_INT_TX_DONE_BAND2, + MT7996_TXQ_BAND2); + } } else { /* single pcie bn0/1:ring18 bn2:ring19 */ + TXQ_CONFIG(0, WFDMA0, MT_INT_TX_DONE_BAND0, + MT7996_TXQ_BAND0); TXQ_CONFIG(2, WFDMA0, MT_INT_TX_DONE_BAND1, MT7996_TXQ_BAND1); } @@ -350,6 +362,9 @@ void mt7996_dma_start(struct mt7996_dev *dev, bool reset, bool wed_reset) if (!mt7996_has_wa(dev) || mt76_npu_device_active(&dev->mt76)) irq_mask &= ~(MT_INT_RX(MT_RXQ_MAIN_WA) | MT_INT_RX(MT_RXQ_BAND1_WA)); + if (is_mt7996(&dev->mt76) && mt76_npu_device_active(&dev->mt76)) + irq_mask &= ~(MT_INT_RX(MT_RXQ_TXFREE_BAND0) | + MT_INT_RX(MT_RXQ_MSDU_PAGE_BAND2)); irq_mask = reset ? MT_INT_MCU_CMD : irq_mask; mt7996_irq_enable(dev, irq_mask); @@ -430,39 +445,48 @@ static void mt7996_dma_enable(struct mt7996_dev *dev, bool reset) MT_WFDMA_HOST_CONFIG_BAND1_PCIE1 | MT_WFDMA_HOST_CONFIG_BAND2_PCIE1); - if (is_mt7996(&dev->mt76)) - mt76_set(dev, MT_WFDMA_HOST_CONFIG, - MT_WFDMA_HOST_CONFIG_BAND2_PCIE1); - else + if (is_mt7996(&dev->mt76)) { + if (mt76_npu_device_active(&dev->mt76)) + mt76_set(dev, MT_WFDMA_HOST_CONFIG, + MT_WFDMA_HOST_CONFIG_BAND0_PCIE1); + else + mt76_set(dev, MT_WFDMA_HOST_CONFIG, + MT_WFDMA_HOST_CONFIG_BAND2_PCIE1); + } else { mt76_set(dev, MT_WFDMA_HOST_CONFIG, MT_WFDMA_HOST_CONFIG_BAND1_PCIE1); + } /* AXI read outstanding number */ mt76_rmw(dev, MT_WFDMA_AXI_R2A_CTRL, MT_WFDMA_AXI_R2A_CTRL_OUTSTAND_MASK, 0x14); - if (dev->hif2->speed < PCIE_SPEED_5_0GT || - (dev->hif2->speed == PCIE_SPEED_5_0GT && - dev->hif2->width < PCIE_LNK_X2)) { - mt76_rmw(dev, WF_WFDMA0_GLO_CFG_EXT0 + hif1_ofs, - WF_WFDMA0_GLO_CFG_EXT0_OUTSTAND_MASK, - FIELD_PREP(WF_WFDMA0_GLO_CFG_EXT0_OUTSTAND_MASK, - 0x1)); - mt76_rmw(dev, MT_WFDMA_AXI_R2A_CTRL2, - MT_WFDMA_AXI_R2A_CTRL2_OUTSTAND_MASK, - FIELD_PREP(MT_WFDMA_AXI_R2A_CTRL2_OUTSTAND_MASK, - 0x1)); - } else if (dev->hif2->speed < PCIE_SPEED_8_0GT || - (dev->hif2->speed == PCIE_SPEED_8_0GT && - dev->hif2->width < PCIE_LNK_X2)) { - mt76_rmw(dev, WF_WFDMA0_GLO_CFG_EXT0 + hif1_ofs, - WF_WFDMA0_GLO_CFG_EXT0_OUTSTAND_MASK, - FIELD_PREP(WF_WFDMA0_GLO_CFG_EXT0_OUTSTAND_MASK, - 0x2)); - mt76_rmw(dev, MT_WFDMA_AXI_R2A_CTRL2, - MT_WFDMA_AXI_R2A_CTRL2_OUTSTAND_MASK, - FIELD_PREP(MT_WFDMA_AXI_R2A_CTRL2_OUTSTAND_MASK, - 0x2)); + if (!is_mt7996(&dev->mt76) || + !mt76_npu_device_active(&dev->mt76)) { + if (dev->hif2->speed < PCIE_SPEED_5_0GT || + (dev->hif2->speed == PCIE_SPEED_5_0GT && + dev->hif2->width < PCIE_LNK_X2)) { + mt76_rmw(dev, + WF_WFDMA0_GLO_CFG_EXT0 + hif1_ofs, + WF_WFDMA0_GLO_CFG_EXT0_OUTSTAND_MASK, + FIELD_PREP(WF_WFDMA0_GLO_CFG_EXT0_OUTSTAND_MASK, + 0x1)); + mt76_rmw(dev, MT_WFDMA_AXI_R2A_CTRL2, + MT_WFDMA_AXI_R2A_CTRL2_OUTSTAND_MASK, + FIELD_PREP(MT_WFDMA_AXI_R2A_CTRL2_OUTSTAND_MASK, + 0x1)); + } else if (dev->hif2->speed < PCIE_SPEED_8_0GT || + (dev->hif2->speed == PCIE_SPEED_8_0GT && + dev->hif2->width < PCIE_LNK_X2)) { + mt76_rmw(dev, WF_WFDMA0_GLO_CFG_EXT0 + hif1_ofs, + WF_WFDMA0_GLO_CFG_EXT0_OUTSTAND_MASK, + FIELD_PREP(WF_WFDMA0_GLO_CFG_EXT0_OUTSTAND_MASK, + 0x2)); + mt76_rmw(dev, MT_WFDMA_AXI_R2A_CTRL2, + MT_WFDMA_AXI_R2A_CTRL2_OUTSTAND_MASK, + FIELD_PREP(MT_WFDMA_AXI_R2A_CTRL2_OUTSTAND_MASK, + 0x2)); + } } /* WFDMA rx threshold */ @@ -497,7 +521,7 @@ static void mt7996_dma_enable(struct mt7996_dev *dev, bool reset) int mt7996_dma_rro_init(struct mt7996_dev *dev) { struct mt76_dev *mdev = &dev->mt76; - u32 irq_mask; + u32 irq_mask, size; int ret; if (dev->mt76.hwrro_mode == MT76_HWRRO_V3_1) { @@ -545,10 +569,12 @@ int mt7996_dma_rro_init(struct mt7996_dev *dev) if (mtk_wed_device_active(&mdev->mmio.wed) && mtk_wed_get_rx_capa(&mdev->mmio.wed)) mdev->q_rx[MT_RXQ_MSDU_PAGE_BAND0].wed = &mdev->mmio.wed; + + size = is_mt7996(mdev) && mt76_npu_device_active(mdev) + ? MT7996_NPU_RX_RING_SIZE / 4 : MT7996_RX_RING_SIZE; ret = mt76_queue_alloc(dev, &mdev->q_rx[MT_RXQ_MSDU_PAGE_BAND0], MT_RXQ_ID(MT_RXQ_MSDU_PAGE_BAND0), - MT7996_RX_RING_SIZE, - MT7996_RX_MSDU_PAGE_SIZE, + size, MT7996_RX_MSDU_PAGE_SIZE, MT_RXQ_RING_BASE(MT_RXQ_MSDU_PAGE_BAND0)); if (ret) return ret; @@ -560,10 +586,12 @@ int mt7996_dma_rro_init(struct mt7996_dev *dev) if (mtk_wed_device_active(&mdev->mmio.wed) && mtk_wed_get_rx_capa(&mdev->mmio.wed)) mdev->q_rx[MT_RXQ_MSDU_PAGE_BAND1].wed = &mdev->mmio.wed; + + size = is_mt7996(mdev) && mt76_npu_device_active(mdev) + ? MT7996_NPU_RX_RING_SIZE / 2 : MT7996_RX_RING_SIZE; ret = mt76_queue_alloc(dev, &mdev->q_rx[MT_RXQ_MSDU_PAGE_BAND1], MT_RXQ_ID(MT_RXQ_MSDU_PAGE_BAND1), - MT7996_RX_RING_SIZE, - MT7996_RX_MSDU_PAGE_SIZE, + size, MT7996_RX_MSDU_PAGE_SIZE, MT_RXQ_RING_BASE(MT_RXQ_MSDU_PAGE_BAND1)); if (ret) return ret; @@ -576,10 +604,12 @@ int mt7996_dma_rro_init(struct mt7996_dev *dev) if (mtk_wed_device_active(&mdev->mmio.wed) && mtk_wed_get_rx_capa(&mdev->mmio.wed)) mdev->q_rx[MT_RXQ_MSDU_PAGE_BAND2].wed = &mdev->mmio.wed; + + size = is_mt7996(mdev) && mt76_npu_device_active(mdev) + ? MT7996_NPU_RX_RING_SIZE : MT7996_RX_RING_SIZE; ret = mt76_queue_alloc(dev, &mdev->q_rx[MT_RXQ_MSDU_PAGE_BAND2], MT_RXQ_ID(MT_RXQ_MSDU_PAGE_BAND2), - MT7996_RX_RING_SIZE, - MT7996_RX_MSDU_PAGE_SIZE, + size, MT7996_RX_MSDU_PAGE_SIZE, MT_RXQ_RING_BASE(MT_RXQ_MSDU_PAGE_BAND2)); if (ret) return ret; @@ -642,11 +672,16 @@ int mt7996_dma_init(struct mt7996_dev *dev) mt7996_dma_disable(dev, true); /* init tx queue */ - ret = mt7996_init_tx_queues(&dev->phy, - MT_TXQ_ID(dev->mphy.band_idx), - MT7996_TX_RING_SIZE, - MT_TXQ_RING_BASE(0), - wed); + if (is_mt7996(&dev->mt76) && mt76_npu_device_active(&dev->mt76)) + ret = mt7996_init_tx_queues(&dev->phy, MT_TXQ_ID(0), + MT7996_NPU_TX_RING_SIZE, + MT_TXQ_RING_BASE(0) + hif1_ofs, + NULL); + else + ret = mt7996_init_tx_queues(&dev->phy, + MT_TXQ_ID(dev->mphy.band_idx), + MT7996_TX_RING_SIZE, + MT_TXQ_RING_BASE(0), wed); if (ret) return ret; @@ -859,16 +894,21 @@ int mt7996_dma_init(struct mt7996_dev *dev) } if (mt7996_band_valid(dev, MT_BAND2)) { + u32 size; + /* rx rro data queue for band2 */ dev->mt76.q_rx[MT_RXQ_RRO_BAND2].flags = MT_WED_RRO_Q_DATA(1) | MT_QFLAG_WED_RRO_EN; if (mtk_wed_device_active(wed) && mtk_wed_get_rx_capa(wed)) dev->mt76.q_rx[MT_RXQ_RRO_BAND2].wed = wed; + + size = is_mt7996(&dev->mt76) && + mt76_npu_device_active(&dev->mt76) + ? MT7996_NPU_RX_RING_SIZE : MT7996_RX_RING_SIZE; ret = mt76_queue_alloc(dev, &dev->mt76.q_rx[MT_RXQ_RRO_BAND2], MT_RXQ_ID(MT_RXQ_RRO_BAND2), - MT7996_RX_RING_SIZE, - MT7996_RX_BUF_SIZE, + size, MT7996_RX_BUF_SIZE, MT_RXQ_RING_BASE(MT_RXQ_RRO_BAND2) + hif1_ofs); if (ret) return ret; From 93e2491470d34d2d45b240123da0267d6de68c71 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Thu, 22 Jan 2026 11:39:56 +0100 Subject: [PATCH 043/134] wifi: mt76: mt7996: Add __mt7996_npu_hw_init routine Introduce __mt7996_npu_hw_init utility routine in order to run it holding mt76 mutex and move NPU hw re-initialization before restarting the NAPIs during device reset. Tested-by: Kang Yang Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260122-mt76-npu-eagle-offload-v2-12-2374614c0de6@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/mac.c | 4 +-- .../wireless/mediatek/mt76/mt7996/mt7996.h | 6 ++++ .../net/wireless/mediatek/mt76/mt7996/npu.c | 31 ++++++++++++------- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index 00c6045622e3..7bee97ff71df 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -2569,6 +2569,8 @@ void mt7996_mac_reset_work(struct work_struct *work) MT_INT_TX_RX_DONE_EXT); } + __mt7996_npu_hw_init(dev); + clear_bit(MT76_MCU_RESET, &dev->mphy.state); mt7996_for_each_phy(dev, phy) clear_bit(MT76_RESET, &phy->mt76->state); @@ -2598,8 +2600,6 @@ void mt7996_mac_reset_work(struct work_struct *work) mutex_unlock(&dev->mt76.mutex); - mt7996_npu_hw_init(dev); - mt7996_for_each_phy(dev, phy) ieee80211_queue_delayed_work(hw, &phy->mt76->mac_work, MT7996_WATCHDOG_TIME); diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index c9c506e1d6ed..dfa338e8508b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -889,10 +889,16 @@ int mt7996_mtk_init_debugfs(struct mt7996_phy *phy, struct dentry *dir); int mt7996_dma_rro_init(struct mt7996_dev *dev); #ifdef CONFIG_MT7996_NPU +int __mt7996_npu_hw_init(struct mt7996_dev *dev); int mt7996_npu_hw_init(struct mt7996_dev *dev); int mt7996_npu_hw_stop(struct mt7996_dev *dev); int mt7996_npu_rx_queues_init(struct mt7996_dev *dev); #else +static inline int __mt7996_npu_hw_init(struct mt7996_dev *dev) +{ + return 0; +} + static inline int mt7996_npu_hw_init(struct mt7996_dev *dev) { return 0; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/npu.c b/drivers/net/wireless/mediatek/mt76/mt7996/npu.c index c3307bbbb547..0085eddc88bc 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/npu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/npu.c @@ -525,20 +525,18 @@ int mt7996_npu_rx_queues_init(struct mt7996_dev *dev) &dev->mt76.q_rx[MT_RXQ_NPU1]); } -int mt7996_npu_hw_init(struct mt7996_dev *dev) +int __mt7996_npu_hw_init(struct mt7996_dev *dev) { struct airoha_npu *npu; - int i, err = 0; - - mutex_lock(&dev->mt76.mutex); + int i, err; npu = rcu_dereference_protected(dev->mt76.mmio.npu, &dev->mt76.mutex); if (!npu) - goto unlock; + return 0; err = mt7996_npu_offload_init(dev, npu); if (err) - goto unlock; + return err; if (is_mt7996(&dev->mt76)) err = mt7996_npu_rxd_init(dev, npu); @@ -546,27 +544,36 @@ int mt7996_npu_hw_init(struct mt7996_dev *dev) err = mt7992_npu_rxd_init(dev, npu); if (err) - goto unlock; + return err; err = mt7996_npu_txd_init(dev, npu); if (err) - goto unlock; + return err; err = mt7996_npu_rx_event_init(dev, npu); if (err) - goto unlock; + return err; err = mt7996_npu_set_pcie_addr(dev, npu); if (err) - goto unlock; + return err; err = mt7996_npu_tx_done_init(dev, npu); if (err) - goto unlock; + return err; for (i = MT_RXQ_NPU0; i <= MT_RXQ_NPU1; i++) airoha_npu_wlan_enable_irq(npu, i - MT_RXQ_NPU0); -unlock: + + return 0; +} + +int mt7996_npu_hw_init(struct mt7996_dev *dev) +{ + int err; + + mutex_lock(&dev->mt76.mutex); + err = __mt7996_npu_hw_init(dev); mutex_unlock(&dev->mt76.mutex); return err; From ae8ee98014bab9b6b1b782bb19cf47317ea0499a Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Thu, 22 Jan 2026 11:39:57 +0100 Subject: [PATCH 044/134] wifi: mt76: mt7996: Move RRO dma start in a dedicated routine This is a preliminary patch to properly enable NPU offloading for MT7996 chipset since NPU initialization must be completed before kicking rx queues. Tested-by: Kang Yang Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260122-mt76-npu-eagle-offload-v2-13-2374614c0de6@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/dma.c | 73 ++++++++++--------- .../net/wireless/mediatek/mt76/mt7996/init.c | 2 + .../wireless/mediatek/mt76/mt7996/mt7996.h | 1 + 3 files changed, 43 insertions(+), 33 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/dma.c b/drivers/net/wireless/mediatek/mt76/mt7996/dma.c index 07212d93bc62..1a4f5f5b2a84 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/dma.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/dma.c @@ -521,7 +521,7 @@ static void mt7996_dma_enable(struct mt7996_dev *dev, bool reset) int mt7996_dma_rro_init(struct mt7996_dev *dev) { struct mt76_dev *mdev = &dev->mt76; - u32 irq_mask, size; + u32 size; int ret; if (dev->mt76.hwrro_mode == MT76_HWRRO_V3_1) { @@ -548,7 +548,8 @@ int mt7996_dma_rro_init(struct mt7996_dev *dev) mt76_queue_reset(dev, &mdev->q_rx[MT_RXQ_RRO_RXDMAD_C], true); } - goto start_hw_rro; + + return 0; } /* ind cmd */ @@ -615,43 +616,49 @@ int mt7996_dma_rro_init(struct mt7996_dev *dev) return ret; } -start_hw_rro: - if (mtk_wed_device_active(&mdev->mmio.wed)) { - irq_mask = mdev->mmio.irqmask | + return 0; +} + +void mt7996_dma_rro_start(struct mt7996_dev *dev) +{ + u32 irq_mask; + + if (mtk_wed_device_active(&dev->mt76.mmio.wed)) { + irq_mask = dev->mt76.mmio.irqmask | MT_INT_TX_DONE_BAND2; mt76_wr(dev, MT_INT_MASK_CSR, irq_mask); - mtk_wed_device_start_hw_rro(&mdev->mmio.wed, irq_mask, false); + mtk_wed_device_start_hw_rro(&dev->mt76.mmio.wed, irq_mask, + false); mt7996_irq_enable(dev, irq_mask); - } else { - if (is_mt7996(&dev->mt76)) { - mt76_queue_rx_init(dev, MT_RXQ_MSDU_PAGE_BAND1, - mt76_dma_rx_poll); - mt76_queue_rx_init(dev, MT_RXQ_MSDU_PAGE_BAND2, - mt76_dma_rx_poll); - mt76_queue_rx_init(dev, MT_RXQ_RRO_BAND2, - mt76_dma_rx_poll); - } else { - mt76_queue_rx_init(dev, MT_RXQ_RRO_BAND1, - mt76_dma_rx_poll); - } - - mt76_queue_rx_init(dev, MT_RXQ_RRO_BAND0, mt76_dma_rx_poll); - if (dev->mt76.hwrro_mode == MT76_HWRRO_V3_1) { - mt76_queue_rx_init(dev, MT_RXQ_RRO_RXDMAD_C, - mt76_dma_rx_poll); - } else { - mt76_queue_rx_init(dev, MT_RXQ_RRO_IND, - mt76_dma_rx_poll); - mt76_queue_rx_init(dev, MT_RXQ_MSDU_PAGE_BAND0, - mt76_dma_rx_poll); - } - - if (!mt76_npu_device_active(&dev->mt76)) - mt7996_irq_enable(dev, MT_INT_RRO_RX_DONE); + return; } - return 0; + if (is_mt7996(&dev->mt76)) { + mt76_queue_rx_init(dev, MT_RXQ_MSDU_PAGE_BAND1, + mt76_dma_rx_poll); + mt76_queue_rx_init(dev, MT_RXQ_MSDU_PAGE_BAND2, + mt76_dma_rx_poll); + mt76_queue_rx_init(dev, MT_RXQ_RRO_BAND2, + mt76_dma_rx_poll); + } else { + mt76_queue_rx_init(dev, MT_RXQ_RRO_BAND1, + mt76_dma_rx_poll); + } + + mt76_queue_rx_init(dev, MT_RXQ_RRO_BAND0, mt76_dma_rx_poll); + if (dev->mt76.hwrro_mode == MT76_HWRRO_V3_1) { + mt76_queue_rx_init(dev, MT_RXQ_RRO_RXDMAD_C, + mt76_dma_rx_poll); + } else { + mt76_queue_rx_init(dev, MT_RXQ_RRO_IND, + mt76_dma_rx_poll); + mt76_queue_rx_init(dev, MT_RXQ_MSDU_PAGE_BAND0, + mt76_dma_rx_poll); + } + + if (!mt76_npu_device_active(&dev->mt76)) + mt7996_irq_enable(dev, MT_INT_RRO_RX_DONE); } int mt7996_dma_init(struct mt7996_dev *dev) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index b76bd324a927..8aa9807b5087 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -1739,6 +1739,8 @@ int mt7996_register_device(struct mt7996_dev *dev) if (ret) return ret; + mt7996_dma_rro_start(dev); + ret = mt76_register_device(&dev->mt76, true, mt76_rates, ARRAY_SIZE(mt76_rates)); if (ret) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index dfa338e8508b..5b2aa7b2fa4f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -887,6 +887,7 @@ int mt7996_mtk_init_debugfs(struct mt7996_phy *phy, struct dentry *dir); #endif int mt7996_dma_rro_init(struct mt7996_dev *dev); +void mt7996_dma_rro_start(struct mt7996_dev *dev); #ifdef CONFIG_MT7996_NPU int __mt7996_npu_hw_init(struct mt7996_dev *dev); From 966c44ba73097a81fb47e9c6cac71e816e9f5084 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Thu, 22 Jan 2026 11:39:58 +0100 Subject: [PATCH 045/134] wifi: mt76: Do not reset idx for NPU tx queues during reset Do not run reset_q callaback with reset_idx set to true for NPU Tx queues. This is a preliminary patch to properly manage reset procedure when NPU offloading is enabled. Tested-by: Kang Yang Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260122-mt76-npu-eagle-offload-v2-14-2374614c0de6@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/dma.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/dma.h b/drivers/net/wireless/mediatek/mt76/dma.h index 4a63de6c5bf5..2a0226c83f3c 100644 --- a/drivers/net/wireless/mediatek/mt76/dma.h +++ b/drivers/net/wireless/mediatek/mt76/dma.h @@ -174,7 +174,9 @@ void mt76_dma_queue_reset(struct mt76_dev *dev, struct mt76_queue *q, static inline void mt76_dma_reset_tx_queue(struct mt76_dev *dev, struct mt76_queue *q) { - dev->queue_ops->reset_q(dev, q, true); + bool reset_idx = q && !mt76_queue_is_npu_tx(q); + + dev->queue_ops->reset_q(dev, q, reset_idx); if (mtk_wed_device_active(&dev->mmio.wed)) mt76_wed_dma_setup(dev, q, true); } From 850856c4777c80348507da1543e58006ff0063d2 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Thu, 22 Jan 2026 11:39:59 +0100 Subject: [PATCH 046/134] wifi: mt76: mt7996: Do not schedule RRO and TxFree queues during reset for NPU This is a preliminary patch to properly manage reset procedure when NPU offloading is enabled. Tested-by: Kang Yang Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260122-mt76-npu-eagle-offload-v2-15-2374614c0de6@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/dma.c | 11 +++++++++++ drivers/net/wireless/mediatek/mt76/mt76.h | 10 ++++++++++ drivers/net/wireless/mediatek/mt76/mt7996/dma.c | 5 +++++ drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 14 ++++++++++++++ 4 files changed, 40 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/dma.c b/drivers/net/wireless/mediatek/mt76/dma.c index f5c6bb94ccbb..2d133ace7c33 100644 --- a/drivers/net/wireless/mediatek/mt76/dma.c +++ b/drivers/net/wireless/mediatek/mt76/dma.c @@ -881,6 +881,10 @@ mt76_dma_rx_cleanup(struct mt76_dev *dev, struct mt76_queue *q) mt76_queue_is_wed_rro(q)) continue; + if (mt76_npu_device_active(dev) && + mt76_queue_is_wed_rro(q)) + continue; + if (!mt76_queue_is_wed_rro_rxdmad_c(q) && !mt76_queue_is_wed_rro_ind(q)) mt76_put_page_pool_buf(buf, false); @@ -923,6 +927,13 @@ mt76_dma_rx_reset(struct mt76_dev *dev, enum mt76_rxq_id qid) mt76_queue_is_wed_rro(q)) return; + if (mt76_npu_device_active(dev) && + mt76_queue_is_wed_rro(q)) + return; + + if (mt76_queue_is_npu_txfree(q)) + return; + mt76_dma_sync_idx(dev, q); if (mt76_queue_is_npu(q)) mt76_npu_fill_rx_queue(dev, q); diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index eefc3f555f8a..5e68efc367fc 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -55,6 +55,8 @@ FIELD_PREP(MT_QFLAG_WED_RING, _n)) #define MT_NPU_Q_TX(_n) __MT_NPU_Q(MT76_WED_Q_TX, _n) #define MT_NPU_Q_RX(_n) __MT_NPU_Q(MT76_WED_Q_RX, _n) +#define MT_NPU_Q_TXFREE(_n) (FIELD_PREP(MT_QFLAG_WED_TYPE, MT76_WED_Q_TXFREE) | \ + FIELD_PREP(MT_QFLAG_WED_RING, _n)) struct mt76_dev; struct mt76_phy; @@ -2003,6 +2005,14 @@ static inline bool mt76_queue_is_npu_rx(struct mt76_queue *q) FIELD_GET(MT_QFLAG_WED_TYPE, q->flags) == MT76_WED_Q_RX; } +static inline bool mt76_queue_is_npu_txfree(struct mt76_queue *q) +{ + if (q->flags & MT_QFLAG_WED) + return false; + + return FIELD_GET(MT_QFLAG_WED_TYPE, q->flags) == MT76_WED_Q_TXFREE; +} + struct mt76_txwi_cache * mt76_token_release(struct mt76_dev *dev, int token, bool *wake); int mt76_token_consume(struct mt76_dev *dev, struct mt76_txwi_cache **ptxwi); diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/dma.c b/drivers/net/wireless/mediatek/mt76/mt7996/dma.c index 1a4f5f5b2a84..8f5d297dafce 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/dma.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/dma.c @@ -756,6 +756,9 @@ int mt7996_dma_init(struct mt7996_dev *dev) (is_mt7992(&dev->mt76)))) { dev->mt76.q_rx[MT_RXQ_MAIN_WA].flags = MT_WED_Q_TXFREE; dev->mt76.q_rx[MT_RXQ_MAIN_WA].wed = wed; + } else if (is_mt7992(&dev->mt76) && + mt76_npu_device_active(&dev->mt76)) { + dev->mt76.q_rx[MT_RXQ_MAIN_WA].flags = MT_NPU_Q_TXFREE(0); } if (mt7996_has_wa(dev)) { @@ -888,6 +891,8 @@ int mt7996_dma_init(struct mt7996_dev *dev) /* tx free notify event from WA for band0 */ dev->mt76.q_rx[MT_RXQ_TXFREE_BAND0].flags = MT_WED_Q_TXFREE; dev->mt76.q_rx[MT_RXQ_TXFREE_BAND0].wed = wed; + } else if (mt76_npu_device_active(&dev->mt76)) { + dev->mt76.q_rx[MT_RXQ_TXFREE_BAND0].flags = MT_NPU_Q_TXFREE(0); } ret = mt76_queue_alloc(dev, diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index 7bee97ff71df..c8cfc343d37b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -2525,6 +2525,13 @@ void mt7996_mac_reset_work(struct work_struct *work) mt76_queue_is_wed_rro(&dev->mt76.q_rx[i])) continue; + if (mt76_npu_device_active(&dev->mt76) && + mt76_queue_is_wed_rro(&dev->mt76.q_rx[i])) + continue; + + if (mt76_queue_is_npu_txfree(&dev->mt76.q_rx[i])) + continue; + napi_disable(&dev->mt76.napi[i]); } napi_disable(&dev->mt76.tx_napi); @@ -2580,6 +2587,13 @@ void mt7996_mac_reset_work(struct work_struct *work) mt76_queue_is_wed_rro(&dev->mt76.q_rx[i])) continue; + if (mt76_npu_device_active(&dev->mt76) && + mt76_queue_is_wed_rro(&dev->mt76.q_rx[i])) + continue; + + if (mt76_queue_is_npu_txfree(&dev->mt76.q_rx[i])) + continue; + napi_enable(&dev->mt76.napi[i]); local_bh_disable(); napi_schedule(&dev->mt76.napi[i]); From c2efd5fe154686e52fff7321c97b2d21c569c36e Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Thu, 22 Jan 2026 11:40:00 +0100 Subject: [PATCH 047/134] wifi: mt76: mt7996: Store DMA mapped buffer addresses in mt7996_npu_hw_init() In order to not always reallocate them during NPU reset, store the DMA mapped buffer addresses allocated by mt7996_npu_hw_init routine in mt7996 structure. Tested-by: Kang Yang Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260122-mt76-npu-eagle-offload-v2-16-2374614c0de6@kernel.org Signed-off-by: Felix Fietkau --- .../wireless/mediatek/mt76/mt7996/mt7996.h | 4 ++ .../net/wireless/mediatek/mt76/mt7996/npu.c | 58 +++++++++++-------- 2 files changed, 39 insertions(+), 23 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index 5b2aa7b2fa4f..3ff730e36fa6 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -31,6 +31,8 @@ #define MT7996_RX_MCU_RING_SIZE_WA 1024 #define MT7996_NPU_TX_RING_SIZE 1024 #define MT7996_NPU_RX_RING_SIZE 1024 +#define MT7996_NPU_TXD_SIZE 3 + /* scatter-gather of mcu event is not supported in connac3 */ #define MT7996_RX_MCU_BUF_SIZE (2048 + \ SKB_DATA_ALIGN(sizeof(struct skb_shared_info))) @@ -476,6 +478,8 @@ struct mt7996_dev { struct list_head page_map[MT7996_RRO_MSDU_PG_HASH_SIZE]; } wed_rro; + dma_addr_t npu_txd_addr[2 * MT7996_NPU_TXD_SIZE]; + bool ibf; u8 fw_debug_wm; u8 fw_debug_wa; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/npu.c b/drivers/net/wireless/mediatek/mt76/mt7996/npu.c index 0085eddc88bc..b8006b8729a1 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/npu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/npu.c @@ -344,12 +344,14 @@ static int mt7996_npu_txd_init(struct mt7996_dev *dev, struct airoha_npu *npu) MT_BAND0, is_mt7996(&dev->mt76) ? MT_BAND2 : MT_BAND1, }; - int i; + int i, index = 0; + + BUILD_BUG_ON(ARRAY_SIZE(band_list) * 3 != + ARRAY_SIZE(dev->npu_txd_addr)); for (i = 0; i < ARRAY_SIZE(band_list); i++) { int err, band = band_list[i], phy_id; - dma_addr_t dma_addr; - u32 val, size; + u32 val; err = mt76_npu_get_msg(npu, band + 5, WLAN_FUNC_GET_WAIT_RXDESC_BASE, @@ -364,43 +366,29 @@ static int mt7996_npu_txd_init(struct mt7996_dev *dev, struct airoha_npu *npu) : band; writel(val, &dev->mt76.phys[phy_id]->q_tx[0]->regs->desc_base); - size = is_mt7996(&dev->mt76) ? band == MT_BAND2 - ? MT7996_NPU_TX_RING_SIZE - : MT7996_NPU_RX_RING_SIZE / 2 - : MT7996_TX_RING_SIZE; - if (!dmam_alloc_coherent(dev->mt76.dma_dev, 256 * size, - &dma_addr, GFP_KERNEL)) - return -ENOMEM; - err = mt76_npu_send_msg(npu, band, WLAN_FUNC_SET_WAIT_TX_BUF_SPACE_HW_BASE, - dma_addr, GFP_KERNEL); + dev->npu_txd_addr[index++], GFP_KERNEL); if (err) { dev_warn(dev->mt76.dev, "failed setting NPU wlan queue buf addr\n"); return err; } - if (!dmam_alloc_coherent(dev->mt76.dma_dev, 256 * size, - &dma_addr, GFP_KERNEL)) - return -ENOMEM; - err = mt76_npu_send_msg(npu, band + 5, WLAN_FUNC_SET_WAIT_TX_BUF_SPACE_HW_BASE, - dma_addr, GFP_KERNEL); + dev->npu_txd_addr[index++], + GFP_KERNEL); if (err) { dev_warn(dev->mt76.dev, "failed setting NPU wlan tx buf addr\n"); return err; } - if (!dmam_alloc_coherent(dev->mt76.dma_dev, 256 * 1024, - &dma_addr, GFP_KERNEL)) - return -ENOMEM; - err = mt76_npu_send_msg(npu, band + 10, WLAN_FUNC_SET_WAIT_TX_BUF_SPACE_HW_BASE, - dma_addr, GFP_KERNEL); + dev->npu_txd_addr[index++], + GFP_KERNEL); if (err) { dev_warn(dev->mt76.dev, "failed setting NPU wlan tx buf base\n"); @@ -570,7 +558,31 @@ int __mt7996_npu_hw_init(struct mt7996_dev *dev) int mt7996_npu_hw_init(struct mt7996_dev *dev) { - int err; + int i, err; + + BUILD_BUG_ON(ARRAY_SIZE(dev->npu_txd_addr) % 3); + + for (i = 0; i < ARRAY_SIZE(dev->npu_txd_addr); i += 3) { + int band = i && is_mt7996(&dev->mt76) ? MT_BAND2 : MT_BAND0; + u32 size = is_mt7996(&dev->mt76) ? band == MT_BAND2 + ? MT7996_NPU_TX_RING_SIZE + : MT7996_NPU_RX_RING_SIZE / 2 + : MT7996_TX_RING_SIZE; + + if (!dmam_alloc_coherent(dev->mt76.dma_dev, 256 * size, + &dev->npu_txd_addr[i], GFP_KERNEL)) + return -ENOMEM; + + if (!dmam_alloc_coherent(dev->mt76.dma_dev, 256 * size, + &dev->npu_txd_addr[i + 1], + GFP_KERNEL)) + return -ENOMEM; + + if (!dmam_alloc_coherent(dev->mt76.dma_dev, 256 * 1024, + &dev->npu_txd_addr[i + 2], + GFP_KERNEL)) + return -ENOMEM; + } mutex_lock(&dev->mt76.mutex); err = __mt7996_npu_hw_init(dev); From 53afca4329af885fe08703b93e71cb5589835f27 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Thu, 22 Jan 2026 11:40:01 +0100 Subject: [PATCH 048/134] wifi: mt76: Enable NPU support for MT7996 devices Enable NPU offloading for MT7990 chipset. Tested-by: Kang Yang Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260122-mt76-npu-eagle-offload-v2-17-2374614c0de6@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/npu.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/npu.c b/drivers/net/wireless/mediatek/mt76/npu.c index bc8f2012be9d..c4c7c0af6321 100644 --- a/drivers/net/wireless/mediatek/mt76/npu.c +++ b/drivers/net/wireless/mediatek/mt76/npu.c @@ -450,10 +450,6 @@ int mt76_npu_init(struct mt76_dev *dev, phys_addr_t phy_addr, int type) struct airoha_npu *npu; int err = 0; - /* NPU offloading is only supported by MT7992 */ - if (!is_mt7992(dev)) - return 0; - mutex_lock(&dev->mutex); npu = airoha_npu_get(dev->dev); @@ -486,7 +482,7 @@ int mt76_npu_init(struct mt76_dev *dev, phys_addr_t phy_addr, int type) dev->mmio.phy_addr = phy_addr; dev->mmio.npu_type = type; /* NPU offloading requires HW-RRO for RX packet reordering. */ - dev->hwrro_mode = MT76_HWRRO_V3_1; + dev->hwrro_mode = is_mt7996(dev) ? MT76_HWRRO_V3 : MT76_HWRRO_V3_1; dev->rx_token_size = 32768; rcu_assign_pointer(dev->mmio.npu, npu); From 59a1864509d084a4b34117e693951c06b846b00a Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Mon, 15 Dec 2025 20:20:17 -0600 Subject: [PATCH 049/134] wifi: mt76: mt7925: drop puncturing handling from BSS change path IEEE80211_CHANCTX_CHANGE_PUNCTURING is a channel context change flag and should not be checked in the BSS change handler, where the changed mask represents enum ieee80211_bss_change. Remove the puncturing handling from the BSS path and rely on mt7925_change_chanctx() to update puncturing configuration. Fixes: cadebdad959b ("wifi: mt76: mt7925: add EHT preamble puncturing") Signed-off-by: Sean Wang Link: https://patch.msgid.link/20251216022017.23870-1-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/main.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index 1fea2e807f77..a0a4307652d2 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -1907,10 +1907,8 @@ static void mt7925_link_info_changed(struct ieee80211_hw *hw, struct mt792x_phy *phy = mt792x_hw_phy(hw); struct mt792x_dev *dev = mt792x_hw_dev(hw); struct mt792x_bss_conf *mconf; - struct ieee80211_bss_conf *link_conf; mconf = mt792x_vif_to_link(mvif, info->link_id); - link_conf = mt792x_vif_to_bss_conf(vif, mconf->link_id); mt792x_mutex_acquire(dev); @@ -1952,10 +1950,6 @@ static void mt7925_link_info_changed(struct ieee80211_hw *hw, mvif->mlo_pm_state = MT792x_MLO_CHANGED_PS; } - if (changed & IEEE80211_CHANCTX_CHANGE_PUNCTURING) - mt7925_mcu_set_eht_pp(mvif->phy->mt76, &mconf->mt76, - link_conf, NULL); - if (changed & BSS_CHANGED_CQM) mt7925_mcu_set_rssimonitor(dev, vif); From 8c7e19612b01567f641d3ffe21e47fa21c331171 Mon Sep 17 00:00:00 2001 From: Michael Lo Date: Mon, 12 Jan 2026 19:40:07 +0800 Subject: [PATCH 050/134] wifi: mt76: mt7925: Skip scan process during suspend. We are experiencing command timeouts because an upper layer triggers an unexpected scan while the system/device is in suspend. The upper layer should not initiate scans until the NIC has fully resumed. We want to prevent scans during suspend and avoid timeouts without harming power management or user experience. Signed-off-by: Michael Lo Link: https://patch.msgid.link/20260112114007.2115873-1-leon.yen@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/main.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index a0a4307652d2..4220db3f7216 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -1327,10 +1327,18 @@ void mt7925_mlo_pm_work(struct work_struct *work) void mt7925_scan_work(struct work_struct *work) { struct mt792x_phy *phy; + struct mt792x_dev *dev; + struct mt76_connac_pm *pm; phy = (struct mt792x_phy *)container_of(work, struct mt792x_phy, scan_work.work); + dev = phy->dev; + pm = &dev->pm; + + if (pm->suspended) + return; + while (true) { struct sk_buff *skb; struct tlv *tlv; From dd08ca3f092f4185ece69ce2a835c23198b1628a Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Mon, 15 Dec 2025 19:38:49 -0600 Subject: [PATCH 051/134] wifi: mt76: mt7925: fix potential deadlock in mt7925_roc_abort_sync roc_abort_sync() can deadlock with roc_work(). roc_work() holds dev->mt76.mutex, while cancel_work_sync() waits for roc_work() to finish. If the caller already owns the same mutex, both sides block and no progress is possible. This deadlock can occur during station removal when mt76_sta_state() -> mt76_sta_remove() -> mt7925_mac_sta_remove_link() -> mt7925_mac_link_sta_remove() -> mt7925_roc_abort_sync() invokes cancel_work_sync() while roc_work() is still running and holding dev->mt76.mutex. This avoids the mutex deadlock and preserves exactly-once work ownership. Fixes: 45064d19fd3a ("wifi: mt76: mt7925: fix a potential association failure upon resuming") Co-developed-by: Quan Zhou Signed-off-by: Quan Zhou Signed-off-by: Sean Wang Link: https://patch.msgid.link/20251216013849.17976-1-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/main.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index 4220db3f7216..afcc0fa4aa35 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -461,12 +461,16 @@ void mt7925_roc_abort_sync(struct mt792x_dev *dev) { struct mt792x_phy *phy = &dev->phy; + if (!test_and_clear_bit(MT76_STATE_ROC, &phy->mt76->state)) + return; + timer_delete_sync(&phy->roc_timer); - cancel_work_sync(&phy->roc_work); - if (test_and_clear_bit(MT76_STATE_ROC, &phy->mt76->state)) - ieee80211_iterate_interfaces(mt76_hw(dev), - IEEE80211_IFACE_ITER_RESUME_ALL, - mt7925_roc_iter, (void *)phy); + + cancel_work(&phy->roc_work); + + ieee80211_iterate_interfaces(mt76_hw(dev), + IEEE80211_IFACE_ITER_RESUME_ALL, + mt7925_roc_iter, (void *)phy); } EXPORT_SYMBOL_GPL(mt7925_roc_abort_sync); From c41075ce8cf05ed8c0e7b7efef000dce548ffc42 Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Fri, 16 Jan 2026 14:49:19 +0000 Subject: [PATCH 052/134] wifi: mt76: Fix memory leak after mt76_connac_mcu_alloc_sta_req() mt76_connac_mcu_alloc_sta_req() allocates an skb which is expected to be freed eventually by mt76_mcu_skb_send_msg(). However, currently if an intermediate function fails before sending, the allocated skb is leaked. Specifically, mt76_connac_mcu_sta_wed_update() and mt76_connac_mcu_sta_key_tlv() may fail, leading to an immediate memory leak in the error path. Fix this by explicitly freeing the skb in these error paths. Commit 7c0f63fe37a5 ("wifi: mt76: mt7996: fix memory leak on mt7996_mcu_sta_key_tlv error") made a similar change. Compile tested only. Issue found using a prototype static analysis tool and code review. Fixes: d1369e515efe ("wifi: mt76: connac: introduce mt76_connac_mcu_sta_wed_update utility routine") Fixes: 6683d988089c ("mt76: connac: move mt76_connac_mcu_add_key in connac module") Fixes: 4f831d18d12d ("wifi: mt76: mt7915: enable WED RX support") Fixes: c948b5da6bbe ("wifi: mt76: mt7925: add Mediatek Wi-Fi7 driver for mt7925 chips") Signed-off-by: Zilin Guan Link: https://patch.msgid.link/20260116144919.1482558-1-zilin@seu.edu.cn Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt76_connac_mcu.c | 16 ++++++++++++---- drivers/net/wireless/mediatek/mt76/mt7915/mcu.c | 4 +++- drivers/net/wireless/mediatek/mt76/mt7925/mcu.c | 4 +++- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c index 0457712286d5..3f583e2a1dc1 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c @@ -1295,8 +1295,10 @@ int mt76_connac_mcu_sta_ba(struct mt76_dev *dev, struct mt76_vif_link *mvif, wtbl_hdr); ret = mt76_connac_mcu_sta_wed_update(dev, skb); - if (ret) + if (ret) { + dev_kfree_skb(skb); return ret; + } ret = mt76_mcu_skb_send_msg(dev, skb, cmd, true); if (ret) @@ -1309,8 +1311,10 @@ int mt76_connac_mcu_sta_ba(struct mt76_dev *dev, struct mt76_vif_link *mvif, mt76_connac_mcu_sta_ba_tlv(skb, params, enable, tx); ret = mt76_connac_mcu_sta_wed_update(dev, skb); - if (ret) + if (ret) { + dev_kfree_skb(skb); return ret; + } return mt76_mcu_skb_send_msg(dev, skb, cmd, true); } @@ -2764,12 +2768,16 @@ int mt76_connac_mcu_add_key(struct mt76_dev *dev, struct ieee80211_vif *vif, return PTR_ERR(skb); ret = mt76_connac_mcu_sta_key_tlv(sta_key_conf, skb, key, cmd); - if (ret) + if (ret) { + dev_kfree_skb(skb); return ret; + } ret = mt76_connac_mcu_sta_wed_update(dev, skb); - if (ret) + if (ret) { + dev_kfree_skb(skb); return ret; + } return mt76_mcu_skb_send_msg(dev, skb, mcu_cmd, true); } diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c index d6f54b1edfb1..318c38149463 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/mcu.c @@ -1765,8 +1765,10 @@ int mt7915_mcu_add_sta(struct mt7915_dev *dev, struct ieee80211_vif *vif, } out: ret = mt76_connac_mcu_sta_wed_update(&dev->mt76, skb); - if (ret) + if (ret) { + dev_kfree_skb(skb); return ret; + } return mt76_mcu_skb_send_msg(&dev->mt76, skb, MCU_EXT_CMD(STA_REC_UPDATE), true); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c index 2daf5a29220f..1379bf6a26b5 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c @@ -1288,8 +1288,10 @@ int mt7925_mcu_add_key(struct mt76_dev *dev, struct ieee80211_vif *vif, return PTR_ERR(skb); ret = mt7925_mcu_sta_key_tlv(wcid, sta_key_conf, skb, key, cmd, msta); - if (ret) + if (ret) { + dev_kfree_skb(skb); return ret; + } return mt76_mcu_skb_send_msg(dev, skb, mcu_cmd, true); } From aae89dc4a1608da9060bada757f650ac94b7f184 Mon Sep 17 00:00:00 2001 From: Leon Yen Date: Wed, 21 Jan 2026 00:31:52 +0800 Subject: [PATCH 053/134] wifi: mt76: mt7925: fix tx power setting failure after chip reset After the chip reset, the procedure to set the tx power will not be successful because the previous region setting is still remains. Clear the region setting during MAC initialization and allow it to be reset to finalize the TX power setting. Fixes: 3bc62aa4484d ("wifi: mt76: mt7925: add auto regdomain switch support") Signed-off-by: Leon Yen Link: https://patch.msgid.link/20260120163152.3694116-1-leon.yen@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/init.c | 2 ++ drivers/net/wireless/mediatek/mt76/mt7925/regd.c | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/init.c b/drivers/net/wireless/mediatek/mt76/mt7925/init.c index 3ce5d6fcc69d..c0c5cb9aff75 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/init.c @@ -91,6 +91,8 @@ int mt7925_mac_init(struct mt792x_dev *dev) mt7925_mac_init_basic_rates(dev); + memzero_explicit(&dev->mt76.alpha2, sizeof(dev->mt76.alpha2)); + return 0; } EXPORT_SYMBOL_GPL(mt7925_mac_init); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/regd.c b/drivers/net/wireless/mediatek/mt76/mt7925/regd.c index 292087e882d1..16f56ee879d4 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/regd.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/regd.c @@ -232,7 +232,8 @@ int mt7925_regd_change(struct mt792x_phy *phy, char *alpha2) dev->regd_user) return -EINVAL; - if (mdev->alpha2[0] != '0' && mdev->alpha2[1] != '0') + if ((mdev->alpha2[0] && mdev->alpha2[0] != '0') && + (mdev->alpha2[1] && mdev->alpha2[1] != '0')) return 0; /* do not need to update the same country twice */ From fdfa39f9f4fbae532b162da913a67b2410caf38f Mon Sep 17 00:00:00 2001 From: Quan Zhou Date: Fri, 23 Jan 2026 10:16:25 +0800 Subject: [PATCH 054/134] wifi: mt76: mt7921: fix ROC abort flow interruption in mt7921_roc_work The mt7921_set_roc API may be executed concurrently with mt7921_roc_work, specifically between the following code paths: - The check and clear of MT76_STATE_ROC in mt7921_roc_work: if (!test_and_clear_bit(MT76_STATE_ROC, &phy->mt76->state)) return; - The execution of ieee80211_iterate_active_interfaces. This race condition can interrupt the ROC abort flow, resulting in the ROC process failing to abort as expected. To address this defect, the modification of MT76_STATE_ROC is now protected by mt792x_mutex_acquire(phy->dev). This ensures that changes to the ROC state are properly synchronized, preventing race conditions and ensuring the ROC abort flow is not interrupted. Fixes: 034ae28b56f1 ("wifi: mt76: mt7921: introduce remain_on_channel support") Cc: stable@vger.kernel.org Signed-off-by: Quan Zhou Reviewed-by: Sean Wang Link: https://patch.msgid.link/2568ece8b557e5dda79391414c834ef3233049b6.1769133724.git.quan.zhou@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7921/main.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/main.c b/drivers/net/wireless/mediatek/mt76/mt7921/main.c index debecd3dff75..f42e40f9663d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/main.c @@ -387,10 +387,11 @@ void mt7921_roc_work(struct work_struct *work) phy = (struct mt792x_phy *)container_of(work, struct mt792x_phy, roc_work); - if (!test_and_clear_bit(MT76_STATE_ROC, &phy->mt76->state)) - return; - mt792x_mutex_acquire(phy->dev); + if (!test_and_clear_bit(MT76_STATE_ROC, &phy->mt76->state)) { + mt792x_mutex_release(phy->dev); + return; + } ieee80211_iterate_active_interfaces(phy->mt76->hw, IEEE80211_IFACE_ITER_RESUME_ALL, mt7921_roc_iter, phy); From d5059e52fd8bc624ec4255c9fa01a266513d126b Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Mon, 26 Jan 2026 12:00:13 -0600 Subject: [PATCH 055/134] wifi: mt76: mt7921: fix potential deadlock in mt7921_roc_abort_sync roc_abort_sync() can deadlock with roc_work(). roc_work() holds dev->mt76.mutex, while cancel_work_sync() waits for roc_work() to finish. If the caller already owns the same mutex, both sides block and no progress is possible. This deadlock can occur during station removal when mt76_sta_state() -> mt76_sta_remove() -> mt7921_mac_sta_remove() -> mt7921_roc_abort_sync() invokes cancel_work_sync() while roc_work() is still running and holding dev->mt76.mutex. This avoids the mutex deadlock and preserves exactly-once work ownership. Fixes: 352d966126e6 ("wifi: mt76: mt7921: fix a potential association failure upon resuming") Co-developed-by: Quan Zhou Signed-off-by: Quan Zhou Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260126180013.8167-1-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7921/main.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/main.c b/drivers/net/wireless/mediatek/mt76/mt7921/main.c index f42e40f9663d..42b9514e04e7 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/main.c @@ -371,12 +371,15 @@ void mt7921_roc_abort_sync(struct mt792x_dev *dev) { struct mt792x_phy *phy = &dev->phy; + if (!test_and_clear_bit(MT76_STATE_ROC, &phy->mt76->state)) + return; + timer_delete_sync(&phy->roc_timer); - cancel_work_sync(&phy->roc_work); - if (test_and_clear_bit(MT76_STATE_ROC, &phy->mt76->state)) - ieee80211_iterate_interfaces(mt76_hw(dev), - IEEE80211_IFACE_ITER_RESUME_ALL, - mt7921_roc_iter, (void *)phy); + cancel_work(&phy->roc_work); + + ieee80211_iterate_interfaces(mt76_hw(dev), + IEEE80211_IFACE_ITER_RESUME_ALL, + mt7921_roc_iter, (void *)phy); } EXPORT_SYMBOL_GPL(mt7921_roc_abort_sync); From 6939b97ddad3cf3dfbb3b5a0a12ef79cb886747e Mon Sep 17 00:00:00 2001 From: Chad Monroe Date: Mon, 8 Dec 2025 14:31:32 +0000 Subject: [PATCH 056/134] wifi: mt76: fix deadlock in remain-on-channel mt76_remain_on_channel() and mt76_roc_complete() call mt76_set_channel() while already holding dev->mutex. Since mt76_set_channel() also acquires dev->mutex, this results in a deadlock. Use __mt76_set_channel() instead of mt76_set_channel(). Add cancel_delayed_work_sync() for mac_work before acquiring the mutex in mt76_remain_on_channel() to prevent a secondary deadlock with the mac_work workqueue. Fixes: a8f424c1287c ("wifi: mt76: add multi-radio remain_on_channel functions") Signed-off-by: Chad Monroe Link: https://patch.msgid.link/ace737e7b621af7c2adb33b0188011a5c1de2166.1765204256.git.chad@monroe.io Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/channel.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/channel.c b/drivers/net/wireless/mediatek/mt76/channel.c index 2b705bdb7993..d9f8529db7ed 100644 --- a/drivers/net/wireless/mediatek/mt76/channel.c +++ b/drivers/net/wireless/mediatek/mt76/channel.c @@ -326,7 +326,7 @@ void mt76_roc_complete(struct mt76_phy *phy) mlink->mvif->roc_phy = NULL; if (phy->main_chandef.chan && !test_bit(MT76_MCU_RESET, &dev->phy.state)) - mt76_set_channel(phy, &phy->main_chandef, false); + __mt76_set_channel(phy, &phy->main_chandef, false); mt76_put_vif_phy_link(phy, phy->roc_vif, phy->roc_link); phy->roc_vif = NULL; phy->roc_link = NULL; @@ -370,6 +370,8 @@ int mt76_remain_on_channel(struct ieee80211_hw *hw, struct ieee80211_vif *vif, if (!phy) return -EINVAL; + cancel_delayed_work_sync(&phy->mac_work); + mutex_lock(&dev->mutex); if (phy->roc_vif || dev->scan.phy == phy || @@ -388,7 +390,14 @@ int mt76_remain_on_channel(struct ieee80211_hw *hw, struct ieee80211_vif *vif, phy->roc_vif = vif; phy->roc_link = mlink; cfg80211_chandef_create(&chandef, chan, NL80211_CHAN_HT20); - mt76_set_channel(phy, &chandef, true); + ret = __mt76_set_channel(phy, &chandef, true); + if (ret) { + mlink->mvif->roc_phy = NULL; + phy->roc_vif = NULL; + phy->roc_link = NULL; + mt76_put_vif_phy_link(phy, vif, mlink); + goto out; + } ieee80211_ready_on_channel(hw); ieee80211_queue_delayed_work(phy->hw, &phy->roc_work, msecs_to_jiffies(duration)); From d2b860454ea2df8f336e9b859da7ffb27f43444d Mon Sep 17 00:00:00 2001 From: Chad Monroe Date: Mon, 8 Dec 2025 14:24:00 +0000 Subject: [PATCH 057/134] wifi: mt76: mt7996: reset device after MCU message timeout Trigger a full reset after MCU message timeout. Signed-off-by: Chad Monroe Link: https://patch.msgid.link/6e05ed063f3763ad3457633c56b60a728a49a6f0.1765203753.git.chad@monroe.io Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 5 +++++ drivers/net/wireless/mediatek/mt76/mt7996/mcu.c | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index c8cfc343d37b..c6028fabd7d1 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -2710,6 +2710,11 @@ void mt7996_reset(struct mt7996_dev *dev) return; } + if (READ_ONCE(dev->recovery.state) & MT_MCU_CMD_STOP_DMA) { + set_bit(MT76_MCU_RESET, &dev->mphy.state); + wake_up(&dev->mt76.mcu.wait); + } + queue_work(dev->mt76.wq, &dev->reset_work); wake_up(&dev->reset_wait); } diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index 82eea809c47b..7741ba0aa0bd 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -209,6 +209,7 @@ static int mt7996_mcu_parse_response(struct mt76_dev *mdev, int cmd, struct sk_buff *skb, int seq) { + struct mt7996_dev *dev = container_of(mdev, struct mt7996_dev, mt76); struct mt7996_mcu_rxd *rxd; struct mt7996_mcu_uni_event *event; int mcu_cmd = FIELD_GET(__MCU_CMD_FIELD_ID, cmd); @@ -217,6 +218,14 @@ mt7996_mcu_parse_response(struct mt76_dev *mdev, int cmd, if (!skb) { dev_err(mdev->dev, "Message %08x (seq %d) timeout\n", cmd, seq); + + if (!test_and_set_bit(MT76_MCU_RESET, &dev->mphy.state)) { + dev->recovery.restart = true; + wake_up(&dev->mt76.mcu.wait); + queue_work(dev->mt76.wq, &dev->reset_work); + wake_up(&dev->reset_wait); + } + return -ETIMEDOUT; } From 0176417d10ce964cd195e64eff9e079cc0a52b69 Mon Sep 17 00:00:00 2001 From: Chad Monroe Date: Mon, 8 Dec 2025 14:14:50 +0000 Subject: [PATCH 058/134] wifi: mt76: mt7996: increase txq memory limit to 32 MiB Prior to this change, both 2G and 6G radios would fall back to the mac80211 default of 4MB which is not enough for high data rates. Signed-off-by: Chad Monroe Link: https://patch.msgid.link/acfe2e25768b414518be2db22b1d3ba6f5db6fa1.1765203249.git.chad@monroe.io Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/init.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index 8aa9807b5087..2937e89ad0c9 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -538,6 +538,7 @@ mt7996_init_wiphy(struct ieee80211_hw *hw, struct mtk_wed_device *wed) ieee80211_hw_set(hw, SUPPORTS_MULTI_BSSID); hw->max_tx_fragments = 4; + wiphy->txq_memory_limit = 32 << 20; /* 32 MiB */ /* init led callbacks */ if (IS_ENABLED(CONFIG_MT76_LEDS)) { From ee5bb35d2b83fadc6920aa2478326fb50ea653a9 Mon Sep 17 00:00:00 2001 From: Madhur Kumar Date: Mon, 8 Dec 2025 22:53:31 +0530 Subject: [PATCH 059/134] wifi: mt76: mt7921: Replace deprecated PCI function pcim_iomap_table() and pcim_iomap_regions() have been deprecated. Replace them with pcim_iomap_region(). Signed-off-by: Madhur Kumar Link: https://patch.msgid.link/20251208172331.89705-1-madhurkumar004@gmail.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7921/pci.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c index ec9686183251..65c7fe671137 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c @@ -276,6 +276,7 @@ static int mt7921_pci_probe(struct pci_dev *pdev, struct mt76_bus_ops *bus_ops; struct mt792x_dev *dev; struct mt76_dev *mdev; + void __iomem *regs; u16 cmd, chipid; u8 features; int ret; @@ -284,10 +285,6 @@ static int mt7921_pci_probe(struct pci_dev *pdev, if (ret) return ret; - ret = pcim_iomap_regions(pdev, BIT(0), pci_name(pdev)); - if (ret) - return ret; - pci_read_config_word(pdev, PCI_COMMAND, &cmd); if (!(cmd & PCI_COMMAND_MEMORY)) { cmd |= PCI_COMMAND_MEMORY; @@ -321,11 +318,15 @@ 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); dev->fw_features = features; dev->hif_ops = &mt7921_pcie_ops; dev->irq_map = &irq_map; - mt76_mmio_init(&dev->mt76, pcim_iomap_table(pdev)[0]); + mt76_mmio_init(&dev->mt76, regs); tasklet_init(&mdev->irq_tasklet, mt792x_irq_tasklet, (unsigned long)dev); dev->phy.dev = dev; From 37d5b68ab57c5b4fb1c40e62c6b32376c6a2ca2c Mon Sep 17 00:00:00 2001 From: Allen Ye Date: Wed, 18 Feb 2026 16:30:27 -0800 Subject: [PATCH 060/134] wifi: mt76: fix backoff fields and max_power calculation The maximum power value may exist in either the data or backoff field. Previously, backoff power limits were not considered in txpower reporting. This patch ensures mt76 also considers backoff values in the SKU table. Also, each RU entry (RU26, RU52, RU106, BW20, ...) in the DTS corresponds to 10 stream combinations (1T1ss, 2T1ss, 3T1ss, 4T1ss, 2T2ss, 3T2ss, 4T2ss, 3T3ss, 4T3ss, 4T4ss). For beamforming tables: - In connac2, beamforming entries for BW20~BW160, and OFDM do not include 1T1ss. - In connac3, beamforming entries for BW20~BW160, and RU include 1T1ss, but OFDM beamforming does not include 1T1ss. Non-beamforming and RU entries for both connac2 and connac3 include 1T1ss. Fixes: b05ab4be9fd7 ("wifi: mt76: mt7915: add bf backoff limit table support") Signed-off-by: Allen Ye Co-developed-by: Ryder Lee Signed-off-by: Ryder Lee Link: https://patch.msgid.link/8fa8ec500b3d4de7b1966c6887f1dfbe5c46a54c.1771205424.git.ryder.lee@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/eeprom.c | 154 ++++++++++++++------ drivers/net/wireless/mediatek/mt76/mt76.h | 1 - 2 files changed, 109 insertions(+), 46 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/eeprom.c b/drivers/net/wireless/mediatek/mt76/eeprom.c index 573400d57ce7..afdb73661866 100644 --- a/drivers/net/wireless/mediatek/mt76/eeprom.c +++ b/drivers/net/wireless/mediatek/mt76/eeprom.c @@ -9,6 +9,13 @@ #include #include #include "mt76.h" +#include "mt76_connac.h" + +enum mt76_sku_type { + MT76_SKU_RATE, + MT76_SKU_BACKOFF, + MT76_SKU_BACKOFF_BF_OFFSET, +}; static int mt76_get_of_eeprom_data(struct mt76_dev *dev, void *eep, int len) { @@ -292,7 +299,6 @@ mt76_find_channel_node(struct device_node *np, struct ieee80211_channel *chan) } EXPORT_SYMBOL_GPL(mt76_find_channel_node); - static s8 mt76_get_txs_delta(struct device_node *np, u8 nss) { @@ -306,9 +312,24 @@ mt76_get_txs_delta(struct device_node *np, u8 nss) return be32_to_cpu(val[nss - 1]); } +static inline u8 mt76_backoff_n_chains(struct mt76_dev *dev, u8 idx) +{ + /* 0:1T1ss, 1:2T1ss, ..., 14:5T5ss */ + static const u8 connac3_table[] = { + 1, 2, 3, 4, 5, 2, 3, 4, 5, 3, 4, 5, 4, 5, 5}; + static const u8 connac2_table[] = { + 1, 2, 3, 4, 2, 3, 4, 3, 4, 4, 0, 0, 0, 0, 0}; + + if (idx >= ARRAY_SIZE(connac3_table)) + return 0; + + return is_mt799x(dev) ? connac3_table[idx] : connac2_table[idx]; +} + static void -mt76_apply_array_limit(s8 *pwr, size_t pwr_len, const s8 *data, - s8 target_power, s8 nss_delta, s8 *max_power) +mt76_apply_array_limit(struct mt76_dev *dev, s8 *pwr, size_t pwr_len, + const s8 *data, s8 target_power, s8 nss_delta, + s8 *max_power, int n_chains, enum mt76_sku_type type) { int i; @@ -316,18 +337,51 @@ mt76_apply_array_limit(s8 *pwr, size_t pwr_len, const s8 *data, return; for (i = 0; i < pwr_len; i++) { - pwr[i] = min_t(s8, target_power, data[i] + nss_delta); + u8 backoff_chain_idx = i; + int backoff_n_chains; + s8 backoff_delta; + s8 delta; + + switch (type) { + case MT76_SKU_RATE: + delta = 0; + backoff_delta = 0; + backoff_n_chains = 0; + break; + case MT76_SKU_BACKOFF_BF_OFFSET: + backoff_chain_idx += 1; + fallthrough; + case MT76_SKU_BACKOFF: + delta = mt76_tx_power_path_delta(n_chains); + backoff_n_chains = mt76_backoff_n_chains(dev, backoff_chain_idx); + backoff_delta = mt76_tx_power_path_delta(backoff_n_chains); + break; + default: + return; + } + + pwr[i] = min_t(s8, target_power + delta - backoff_delta, data[i] + nss_delta); + + /* used for padding, doesn't need to be considered */ + if (data[i] >= S8_MAX - 1) + continue; + + /* only consider backoff value for the configured chain number */ + if (type != MT76_SKU_RATE && n_chains != backoff_n_chains) + continue; + *max_power = max(*max_power, pwr[i]); } } static void -mt76_apply_multi_array_limit(s8 *pwr, size_t pwr_len, s8 pwr_num, - const s8 *data, size_t len, s8 target_power, - s8 nss_delta) +mt76_apply_multi_array_limit(struct mt76_dev *dev, s8 *pwr, size_t pwr_len, + s8 pwr_num, const s8 *data, size_t len, + s8 target_power, s8 nss_delta, s8 *max_power, + int n_chains, enum mt76_sku_type type) { + static const int connac2_backoff_ru_idx = 2; int i, cur; - s8 max_power = -128; if (!data) return; @@ -337,8 +391,26 @@ mt76_apply_multi_array_limit(s8 *pwr, size_t pwr_len, s8 pwr_num, if (len < pwr_len + 1) break; - mt76_apply_array_limit(pwr + pwr_len * i, pwr_len, data + 1, - target_power, nss_delta, &max_power); + /* Each RU entry (RU26, RU52, RU106, BW20, ...) in the DTS + * corresponds to 10 stream combinations (1T1ss, 2T1ss, 3T1ss, + * 4T1ss, 2T2ss, 3T2ss, 4T2ss, 3T3ss, 4T3ss, 4T4ss). + * + * For beamforming tables: + * - In connac2, beamforming entries for BW20~BW160 and OFDM + * do not include 1T1ss. + * - In connac3, beamforming entries for BW20~BW160 and RU + * include 1T1ss, but OFDM beamforming does not include 1T1ss. + * + * Non-beamforming and RU entries for both connac2 and connac3 + * include 1T1ss. + */ + if (!is_mt799x(dev) && type == MT76_SKU_BACKOFF && + i > connac2_backoff_ru_idx) + type = MT76_SKU_BACKOFF_BF_OFFSET; + + mt76_apply_array_limit(dev, pwr + pwr_len * i, pwr_len, data + 1, + target_power, nss_delta, max_power, + n_chains, type); if (--cur > 0) continue; @@ -360,18 +432,11 @@ s8 mt76_get_rate_power_limits(struct mt76_phy *phy, struct device_node *np; const s8 *val; char name[16]; - u32 mcs_rates = dev->drv->mcs_rates; - u32 ru_rates = ARRAY_SIZE(dest->ru[0]); char band; size_t len; - s8 max_power = 0; - s8 max_power_backoff = -127; + s8 max_power = -127; s8 txs_delta; int n_chains = hweight16(phy->chainmask); - s8 target_power_combine = target_power + mt76_tx_power_path_delta(n_chains); - - if (!mcs_rates) - mcs_rates = 10; memset(dest, target_power, sizeof(*dest) - sizeof(dest->path)); memset(&dest->path, 0, sizeof(dest->path)); @@ -409,46 +474,45 @@ s8 mt76_get_rate_power_limits(struct mt76_phy *phy, txs_delta = mt76_get_txs_delta(np, hweight16(phy->chainmask)); val = mt76_get_of_array_s8(np, "rates-cck", &len, ARRAY_SIZE(dest->cck)); - mt76_apply_array_limit(dest->cck, ARRAY_SIZE(dest->cck), val, - target_power, txs_delta, &max_power); + mt76_apply_array_limit(dev, dest->cck, ARRAY_SIZE(dest->cck), val, + target_power, txs_delta, &max_power, n_chains, MT76_SKU_RATE); - val = mt76_get_of_array_s8(np, "rates-ofdm", - &len, ARRAY_SIZE(dest->ofdm)); - mt76_apply_array_limit(dest->ofdm, ARRAY_SIZE(dest->ofdm), val, - target_power, txs_delta, &max_power); + val = mt76_get_of_array_s8(np, "rates-ofdm", &len, ARRAY_SIZE(dest->ofdm)); + mt76_apply_array_limit(dev, dest->ofdm, ARRAY_SIZE(dest->ofdm), val, + target_power, txs_delta, &max_power, n_chains, MT76_SKU_RATE); - val = mt76_get_of_array_s8(np, "rates-mcs", &len, mcs_rates + 1); - mt76_apply_multi_array_limit(dest->mcs[0], ARRAY_SIZE(dest->mcs[0]), - ARRAY_SIZE(dest->mcs), val, len, - target_power, txs_delta); + val = mt76_get_of_array_s8(np, "rates-mcs", &len, ARRAY_SIZE(dest->mcs[0]) + 1); + mt76_apply_multi_array_limit(dev, dest->mcs[0], ARRAY_SIZE(dest->mcs[0]), + ARRAY_SIZE(dest->mcs), val, len, target_power, + txs_delta, &max_power, n_chains, MT76_SKU_RATE); - val = mt76_get_of_array_s8(np, "rates-ru", &len, ru_rates + 1); - mt76_apply_multi_array_limit(dest->ru[0], ARRAY_SIZE(dest->ru[0]), - ARRAY_SIZE(dest->ru), val, len, - target_power, txs_delta); + val = mt76_get_of_array_s8(np, "rates-ru", &len, ARRAY_SIZE(dest->ru[0]) + 1); + mt76_apply_multi_array_limit(dev, dest->ru[0], ARRAY_SIZE(dest->ru[0]), + ARRAY_SIZE(dest->ru), val, len, target_power, + txs_delta, &max_power, n_chains, MT76_SKU_RATE); - max_power_backoff = max_power; val = mt76_get_of_array_s8(np, "paths-cck", &len, ARRAY_SIZE(dest->path.cck)); - mt76_apply_array_limit(dest->path.cck, ARRAY_SIZE(dest->path.cck), val, - target_power_combine, txs_delta, &max_power_backoff); + mt76_apply_array_limit(dev, dest->path.cck, ARRAY_SIZE(dest->path.cck), val, + target_power, txs_delta, &max_power, n_chains, MT76_SKU_BACKOFF); val = mt76_get_of_array_s8(np, "paths-ofdm", &len, ARRAY_SIZE(dest->path.ofdm)); - mt76_apply_array_limit(dest->path.ofdm, ARRAY_SIZE(dest->path.ofdm), val, - target_power_combine, txs_delta, &max_power_backoff); + mt76_apply_array_limit(dev, dest->path.ofdm, ARRAY_SIZE(dest->path.ofdm), val, + target_power, txs_delta, &max_power, n_chains, MT76_SKU_BACKOFF); val = mt76_get_of_array_s8(np, "paths-ofdm-bf", &len, ARRAY_SIZE(dest->path.ofdm_bf)); - mt76_apply_array_limit(dest->path.ofdm_bf, ARRAY_SIZE(dest->path.ofdm_bf), val, - target_power_combine, txs_delta, &max_power_backoff); + mt76_apply_array_limit(dev, dest->path.ofdm_bf, ARRAY_SIZE(dest->path.ofdm_bf), val, + target_power, txs_delta, &max_power, n_chains, + MT76_SKU_BACKOFF_BF_OFFSET); val = mt76_get_of_array_s8(np, "paths-ru", &len, ARRAY_SIZE(dest->path.ru[0]) + 1); - mt76_apply_multi_array_limit(dest->path.ru[0], ARRAY_SIZE(dest->path.ru[0]), - ARRAY_SIZE(dest->path.ru), val, len, - target_power_combine, txs_delta); + mt76_apply_multi_array_limit(dev, dest->path.ru[0], ARRAY_SIZE(dest->path.ru[0]), + ARRAY_SIZE(dest->path.ru), val, len, target_power, + txs_delta, &max_power, n_chains, MT76_SKU_BACKOFF); val = mt76_get_of_array_s8(np, "paths-ru-bf", &len, ARRAY_SIZE(dest->path.ru_bf[0]) + 1); - mt76_apply_multi_array_limit(dest->path.ru_bf[0], ARRAY_SIZE(dest->path.ru_bf[0]), - ARRAY_SIZE(dest->path.ru_bf), val, len, - target_power_combine, txs_delta); + mt76_apply_multi_array_limit(dev, dest->path.ru_bf[0], ARRAY_SIZE(dest->path.ru_bf[0]), + ARRAY_SIZE(dest->path.ru_bf), val, len, target_power, + txs_delta, &max_power, n_chains, MT76_SKU_BACKOFF); return max_power; } diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index 5e68efc367fc..23a1832812a2 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -542,7 +542,6 @@ struct mt76_driver_ops { u32 survey_flags; u16 txwi_size; u16 token_size; - u8 mcs_rates; unsigned int link_data_size; From f456e1f56c4cdda1f908f1b97b57f6d45f578f2c Mon Sep 17 00:00:00 2001 From: StanleyYP Wang Date: Thu, 12 Feb 2026 17:03:08 +0800 Subject: [PATCH 061/134] wifi: mt76: add external EEPROM support for mt799x chipsets For the MT7992 and MT7990 chipsets, efuse mode is not supported because there is insufficient space in the efuse to store the calibration data. Therefore, an additional on-chip EEPROM is added to address this limitation. Co-developed-by: Elwin Huang Signed-off-by: Elwin Huang Co-developed-by: Shayne Chen Signed-off-by: Shayne Chen Signed-off-by: StanleyYP Wang Link: https://patch.msgid.link/20260212090310.3335392-1-shayne.chen@mediatek.com Signed-off-by: Felix Fietkau --- .../wireless/mediatek/mt76/mt76_connac_mcu.h | 1 + .../wireless/mediatek/mt76/mt7996/eeprom.c | 59 +++++++------ .../net/wireless/mediatek/mt76/mt7996/init.c | 3 +- .../net/wireless/mediatek/mt76/mt7996/mcu.c | 83 ++++++++++++------- .../net/wireless/mediatek/mt76/mt7996/mcu.h | 43 +++++++++- .../wireless/mediatek/mt76/mt7996/mt7996.h | 20 ++++- 6 files changed, 148 insertions(+), 61 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h index f44977f9093d..e91966cd5efe 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h @@ -1308,6 +1308,7 @@ enum { MCU_UNI_CMD_PER_STA_INFO = 0x6d, MCU_UNI_CMD_ALL_STA_INFO = 0x6e, MCU_UNI_CMD_ASSERT_DUMP = 0x6f, + MCU_UNI_CMD_EXT_EEPROM_CTRL = 0x74, MCU_UNI_CMD_RADIO_STATUS = 0x80, MCU_UNI_CMD_SDO = 0x88, }; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/eeprom.c b/drivers/net/wireless/mediatek/mt76/mt7996/eeprom.c index 8f60772913b4..00c72be8498f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/eeprom.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/eeprom.c @@ -153,7 +153,7 @@ mt7996_eeprom_check_or_use_default(struct mt7996_dev *dev, bool use_default) dev_warn(dev->mt76.dev, "eeprom load fail, use default bin\n"); memcpy(eeprom, fw->data, MT7996_EEPROM_SIZE); - dev->flash_mode = true; + dev->eeprom_mode = EEPROM_MODE_DEFAULT_BIN; out: release_firmware(fw); @@ -163,26 +163,31 @@ mt7996_eeprom_check_or_use_default(struct mt7996_dev *dev, bool use_default) static int mt7996_eeprom_load(struct mt7996_dev *dev) { + u32 eeprom_blk_size, block_num; bool use_default = false; - int ret; + int ret, i; ret = mt76_eeprom_init(&dev->mt76, MT7996_EEPROM_SIZE); if (ret < 0) return ret; if (ret && !mt7996_check_eeprom(dev)) { - dev->flash_mode = true; + dev->eeprom_mode = EEPROM_MODE_FLASH; goto out; } - if (!dev->flash_mode) { - u32 eeprom_blk_size = MT7996_EEPROM_BLOCK_SIZE; - u32 block_num = DIV_ROUND_UP(MT7996_EEPROM_SIZE, eeprom_blk_size); + memset(dev->mt76.eeprom.data, 0, MT7996_EEPROM_SIZE); + if (mt7996_has_ext_eeprom(dev)) { + /* external eeprom mode */ + dev->eeprom_mode = EEPROM_MODE_EXT; + eeprom_blk_size = MT7996_EXT_EEPROM_BLOCK_SIZE; + } else { u8 free_block_num; - int i; - memset(dev->mt76.eeprom.data, 0, MT7996_EEPROM_SIZE); - ret = mt7996_mcu_get_eeprom_free_block(dev, &free_block_num); + /* efuse mode */ + dev->eeprom_mode = EEPROM_MODE_EFUSE; + eeprom_blk_size = MT7996_EEPROM_BLOCK_SIZE; + ret = mt7996_mcu_get_efuse_free_block(dev, &free_block_num); if (ret < 0) return ret; @@ -191,27 +196,29 @@ static int mt7996_eeprom_load(struct mt7996_dev *dev) use_default = true; goto out; } + } - /* check if eeprom data from fw is valid */ - if (mt7996_mcu_get_eeprom(dev, 0, NULL, 0) || - mt7996_check_eeprom(dev)) { + /* check if eeprom data from fw is valid */ + if (mt7996_mcu_get_eeprom(dev, 0, NULL, eeprom_blk_size, + dev->eeprom_mode) || + mt7996_check_eeprom(dev)) { + use_default = true; + goto out; + } + + /* read eeprom data from fw */ + block_num = DIV_ROUND_UP(MT7996_EEPROM_SIZE, eeprom_blk_size); + for (i = 1; i < block_num; i++) { + u32 len = eeprom_blk_size; + + if (i == block_num - 1) + len = MT7996_EEPROM_SIZE % eeprom_blk_size; + ret = mt7996_mcu_get_eeprom(dev, i * eeprom_blk_size, + NULL, len, dev->eeprom_mode); + if (ret && ret != -EINVAL) { use_default = true; goto out; } - - /* read eeprom data from fw */ - for (i = 1; i < block_num; i++) { - u32 len = eeprom_blk_size; - - if (i == block_num - 1) - len = MT7996_EEPROM_SIZE % eeprom_blk_size; - ret = mt7996_mcu_get_eeprom(dev, i * eeprom_blk_size, - NULL, len); - if (ret && ret != -EINVAL) { - use_default = true; - goto out; - } - } } out: diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index 2937e89ad0c9..1fab04909831 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -1207,7 +1207,8 @@ static int mt7996_variant_fem_init(struct mt7996_dev *dev) if (ret) return ret; - ret = mt7996_mcu_get_eeprom(dev, MT7976C_EFUSE_OFFSET, buf, sizeof(buf)); + ret = mt7996_mcu_get_eeprom(dev, MT7976C_EFUSE_OFFSET, buf, sizeof(buf), + EEPROM_MODE_EFUSE); if (ret && ret != -EINVAL) return ret; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index 7741ba0aa0bd..2a149f64c667 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -3954,7 +3954,7 @@ static int mt7996_mcu_set_eeprom_flash(struct mt7996_dev *dev) #define MAX_PAGE_IDX_MASK GENMASK(7, 5) #define PAGE_IDX_MASK GENMASK(4, 2) #define PER_PAGE_SIZE 0x400 - struct mt7996_mcu_eeprom req = { + struct mt7996_mcu_eeprom_update req = { .tag = cpu_to_le16(UNI_EFUSE_BUFFER_MODE), .buffer_mode = EE_MODE_BUFFER }; @@ -3996,57 +3996,80 @@ static int mt7996_mcu_set_eeprom_flash(struct mt7996_dev *dev) int mt7996_mcu_set_eeprom(struct mt7996_dev *dev) { - struct mt7996_mcu_eeprom req = { + struct mt7996_mcu_eeprom_update req = { .tag = cpu_to_le16(UNI_EFUSE_BUFFER_MODE), .len = cpu_to_le16(sizeof(req) - 4), .buffer_mode = EE_MODE_EFUSE, .format = EE_FORMAT_WHOLE }; - if (dev->flash_mode) + if (dev->eeprom_mode != EEPROM_MODE_EFUSE) return mt7996_mcu_set_eeprom_flash(dev); return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(EFUSE_CTRL), &req, sizeof(req), true); } -int mt7996_mcu_get_eeprom(struct mt7996_dev *dev, u32 offset, u8 *buf, u32 buf_len) +int mt7996_mcu_get_eeprom(struct mt7996_dev *dev, u32 offset, u8 *buf, u32 buf_len, + enum mt7996_eeprom_mode mode) { - struct { - u8 _rsv[4]; - - __le16 tag; - __le16 len; - __le32 addr; - __le32 valid; - u8 data[16]; - } __packed req = { - .tag = cpu_to_le16(UNI_EFUSE_ACCESS), - .len = cpu_to_le16(sizeof(req) - 4), - .addr = cpu_to_le32(round_down(offset, - MT7996_EEPROM_BLOCK_SIZE)), + struct mt7996_mcu_eeprom_access req = { + .info.len = cpu_to_le16(sizeof(req) - 4), }; + struct mt7996_mcu_eeprom_access_event *event; struct sk_buff *skb; - bool valid; - int ret; + int ret, cmd; + u32 addr; - ret = mt76_mcu_send_and_get_msg(&dev->mt76, - MCU_WM_UNI_CMD_QUERY(EFUSE_CTRL), - &req, sizeof(req), true, &skb); + switch (mode) { + case EEPROM_MODE_EFUSE: + addr = round_down(offset, MT7996_EEPROM_BLOCK_SIZE); + cmd = MCU_WM_UNI_CMD_QUERY(EFUSE_CTRL); + req.info.tag = cpu_to_le16(UNI_EFUSE_ACCESS); + break; + case EEPROM_MODE_EXT: + addr = round_down(offset, MT7996_EXT_EEPROM_BLOCK_SIZE); + cmd = MCU_WM_UNI_CMD_QUERY(EXT_EEPROM_CTRL); + req.info.tag = cpu_to_le16(UNI_EXT_EEPROM_ACCESS); + req.eeprom.ext_eeprom.data_len = cpu_to_le32(buf_len); + break; + default: + return -EINVAL; + } + + req.info.addr = cpu_to_le32(addr); + ret = mt76_mcu_send_and_get_msg(&dev->mt76, cmd, &req, sizeof(req), + true, &skb); if (ret) return ret; - valid = le32_to_cpu(*(__le32 *)(skb->data + 16)); - if (valid) { - u32 addr = le32_to_cpu(*(__le32 *)(skb->data + 12)); + event = (struct mt7996_mcu_eeprom_access_event *)skb->data; + if (event->valid) { + u32 ret_len = le32_to_cpu(event->eeprom.ext_eeprom.data_len); + + addr = le32_to_cpu(event->addr); if (!buf) buf = (u8 *)dev->mt76.eeprom.data + addr; - if (!buf_len || buf_len > MT7996_EEPROM_BLOCK_SIZE) - buf_len = MT7996_EEPROM_BLOCK_SIZE; - skb_pull(skb, 48); - memcpy(buf, skb->data, buf_len); + switch (mode) { + case EEPROM_MODE_EFUSE: + if (!buf_len || buf_len > MT7996_EEPROM_BLOCK_SIZE) + buf_len = MT7996_EEPROM_BLOCK_SIZE; + + memcpy(buf, event->eeprom.efuse, buf_len); + break; + case EEPROM_MODE_EXT: + if (!buf_len || buf_len > MT7996_EXT_EEPROM_BLOCK_SIZE) + buf_len = MT7996_EXT_EEPROM_BLOCK_SIZE; + + memcpy(buf, event->eeprom.ext_eeprom.data, + ret_len < buf_len ? ret_len : buf_len); + break; + default: + ret = -EINVAL; + break; + } } else { ret = -EINVAL; } @@ -4056,7 +4079,7 @@ int mt7996_mcu_get_eeprom(struct mt7996_dev *dev, u32 offset, u8 *buf, u32 buf_l return ret; } -int mt7996_mcu_get_eeprom_free_block(struct mt7996_dev *dev, u8 *block_num) +int mt7996_mcu_get_efuse_free_block(struct mt7996_dev *dev, u8 *block_num) { struct { u8 _rsv[4]; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h index d9fb49f7b01b..905dafccc316 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h @@ -145,7 +145,7 @@ struct mt7996_mcu_background_chain_ctrl { u8 rsv[2]; } __packed; -struct mt7996_mcu_eeprom { +struct mt7996_mcu_eeprom_update { u8 _rsv[4]; __le16 tag; @@ -155,6 +155,43 @@ struct mt7996_mcu_eeprom { __le16 buf_len; } __packed; +union eeprom_data { + struct { + __le32 data_len; + DECLARE_FLEX_ARRAY(u8, data); + } ext_eeprom; + DECLARE_FLEX_ARRAY(u8, efuse); +} __packed; + +struct mt7996_mcu_eeprom_info { + u8 _rsv[4]; + + __le16 tag; + __le16 len; + __le32 addr; + __le32 valid; +} __packed; + +struct mt7996_mcu_eeprom_access { + struct mt7996_mcu_eeprom_info info; + union eeprom_data eeprom; +} __packed; + +struct mt7996_mcu_eeprom_access_event { + u8 _rsv[4]; + + __le16 tag; + __le16 len; + __le32 version; + __le32 addr; + __le32 valid; + __le32 size; + __le32 magic_no; + __le32 type; + __le32 rsv[4]; + union eeprom_data eeprom; +} __packed; + struct mt7996_mcu_phy_rx_info { u8 category; u8 rate; @@ -875,6 +912,10 @@ enum { UNI_EFUSE_BUFFER_RD, }; +enum { + UNI_EXT_EEPROM_ACCESS = 1, +}; + enum { UNI_VOW_DRR_CTRL, UNI_VOW_RX_AT_AIRTIME_EN = 0x0b, diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index 3ff730e36fa6..ea1f656a9334 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -85,6 +85,7 @@ #define MT7996_EEPROM_SIZE 7680 #define MT7996_EEPROM_BLOCK_SIZE 16 +#define MT7996_EXT_EEPROM_BLOCK_SIZE 1024 #define MT7996_TOKEN_SIZE 16384 #define MT7996_HW_TOKEN_SIZE 8192 @@ -169,6 +170,13 @@ enum mt7996_fem_type { MT7996_FEM_MIX, }; +enum mt7996_eeprom_mode { + EEPROM_MODE_DEFAULT_BIN, + EEPROM_MODE_EFUSE, + EEPROM_MODE_FLASH, + EEPROM_MODE_EXT, +}; + enum mt7996_txq_id { MT7996_TXQ_FWDL = 16, MT7996_TXQ_MCU_WM, @@ -441,7 +449,7 @@ struct mt7996_dev { u32 hw_pattern; - bool flash_mode:1; + u8 eeprom_mode; bool has_eht:1; struct { @@ -717,8 +725,9 @@ int mt7996_mcu_set_fixed_rate_ctrl(struct mt7996_dev *dev, int mt7996_mcu_set_fixed_field(struct mt7996_dev *dev, struct mt7996_sta *msta, void *data, u8 link_id, u32 field); int mt7996_mcu_set_eeprom(struct mt7996_dev *dev); -int mt7996_mcu_get_eeprom(struct mt7996_dev *dev, u32 offset, u8 *buf, u32 buf_len); -int mt7996_mcu_get_eeprom_free_block(struct mt7996_dev *dev, u8 *block_num); +int mt7996_mcu_get_eeprom(struct mt7996_dev *dev, u32 offset, u8 *buf, u32 buf_len, + enum mt7996_eeprom_mode mode); +int mt7996_mcu_get_efuse_free_block(struct mt7996_dev *dev, u8 *block_num); int mt7996_mcu_get_chip_config(struct mt7996_dev *dev, u32 *cap); int mt7996_mcu_set_ser(struct mt7996_dev *dev, u8 action, u8 set, u8 band); int mt7996_mcu_set_txbf(struct mt7996_dev *dev, u8 action); @@ -816,6 +825,11 @@ static inline bool mt7996_has_wa(struct mt7996_dev *dev) return !is_mt7990(&dev->mt76); } +static inline bool mt7996_has_ext_eeprom(struct mt7996_dev *dev) +{ + return !is_mt7996(&dev->mt76); +} + void mt7996_mac_init(struct mt7996_dev *dev); u32 mt7996_mac_wtbl_lmac_addr(struct mt7996_dev *dev, u16 wcid, u8 dw); bool mt7996_mac_wtbl_update(struct mt7996_dev *dev, int idx, u32 mask); From 676d5d009bc68596a88104137a0bf785b8c2562a Mon Sep 17 00:00:00 2001 From: Shayne Chen Date: Thu, 12 Feb 2026 17:03:09 +0800 Subject: [PATCH 062/134] wifi: mt76: mt7996: add variant for MT7992 chipsets Introduce VAR_TYPE_24 for the MT7992 chipsets, a dual-band variant supporting 3T3R/2SS on the 2 GHz band and 5T5R/4SS on the 5GHz band. Signed-off-by: Shayne Chen Link: https://patch.msgid.link/20260212090310.3335392-2-shayne.chen@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/eeprom.c | 5 ++++- drivers/net/wireless/mediatek/mt76/mt7996/init.c | 2 +- drivers/net/wireless/mediatek/mt76/mt7996/mcu.c | 3 +++ drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h | 7 +++++++ 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/eeprom.c b/drivers/net/wireless/mediatek/mt76/mt7996/eeprom.c index 00c72be8498f..ac05f7d75d63 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/eeprom.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/eeprom.c @@ -33,6 +33,8 @@ static char *mt7996_eeprom_name(struct mt7996_dev *dev) if (dev->var.fem == MT7996_FEM_INT) return MT7992_EEPROM_DEFAULT_23_INT; return MT7992_EEPROM_DEFAULT_23; + case MT7992_VAR_TYPE_24: + return MT7992_EEPROM_DEFAULT_24; case MT7992_VAR_TYPE_44: default: if (dev->var.fem == MT7996_FEM_INT) @@ -392,7 +394,8 @@ bool mt7996_eeprom_has_background_radar(struct mt7996_dev *dev) return false; break; case MT7992_DEVICE_ID: - if (dev->var.type == MT7992_VAR_TYPE_23) + if (dev->var.type == MT7992_VAR_TYPE_23 || + dev->var.type == MT7992_VAR_TYPE_24) return false; break; case MT7990_DEVICE_ID: { diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index 1fab04909831..3b4f808b968c 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -1173,7 +1173,7 @@ static int mt7996_variant_type_init(struct mt7996_dev *dev) else if (u32_get_bits(val, MT_PAD_GPIO_ADIE_COMB_7992)) var_type = MT7992_VAR_TYPE_44; else - return -EINVAL; + var_type = MT7992_VAR_TYPE_24; break; case MT7990_DEVICE_ID: var_type = MT7990_VAR_TYPE_23; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index 2a149f64c667..81c4e0d4654b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -18,6 +18,9 @@ case MT7992_VAR_TYPE_23: \ _fw = MT7992_##name##_23; \ break; \ + case MT7992_VAR_TYPE_24: \ + _fw = MT7992_##name##_24; \ + break; \ default: \ _fw = MT7992_##name; \ } \ diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index ea1f656a9334..d36fb5396141 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -64,6 +64,11 @@ #define MT7992_FIRMWARE_DSP_23 "mediatek/mt7996/mt7992_dsp_23.bin" #define MT7992_ROM_PATCH_23 "mediatek/mt7996/mt7992_rom_patch_23.bin" +#define MT7992_FIRMWARE_WA_24 "mediatek/mt7996/mt7992_wa_24.bin" +#define MT7992_FIRMWARE_WM_24 "mediatek/mt7996/mt7992_wm_24.bin" +#define MT7992_FIRMWARE_DSP_24 "mediatek/mt7996/mt7992_dsp_24.bin" +#define MT7992_ROM_PATCH_24 "mediatek/mt7996/mt7992_rom_patch_24.bin" + #define MT7990_FIRMWARE_WA "" #define MT7990_FIRMWARE_WM "mediatek/mt7996/mt7990_wm.bin" #define MT7990_FIRMWARE_DSP "" @@ -79,6 +84,7 @@ #define MT7992_EEPROM_DEFAULT_MIX "mediatek/mt7996/mt7992_eeprom_2i5e.bin" #define MT7992_EEPROM_DEFAULT_23 "mediatek/mt7996/mt7992_eeprom_23.bin" #define MT7992_EEPROM_DEFAULT_23_INT "mediatek/mt7996/mt7992_eeprom_23_2i5i.bin" +#define MT7992_EEPROM_DEFAULT_24 "mediatek/mt7996/mt7992_eeprom_24_2i5i.bin" #define MT7990_EEPROM_DEFAULT "mediatek/mt7996/mt7990_eeprom.bin" #define MT7990_EEPROM_DEFAULT_INT "mediatek/mt7996/mt7990_eeprom_2i5i.bin" @@ -158,6 +164,7 @@ enum mt7996_var_type { enum mt7992_var_type { MT7992_VAR_TYPE_44, MT7992_VAR_TYPE_23, + MT7992_VAR_TYPE_24, }; enum mt7990_var_type { From fa1063fc649c08b37f9a21d8bc38344ce8a128f5 Mon Sep 17 00:00:00 2001 From: StanleyYP Wang Date: Thu, 12 Feb 2026 17:03:10 +0800 Subject: [PATCH 063/134] wifi: mt76: mt7996: apply calibration-free data from OTP Before sending the current EEPROM data to the firmware, read the calibration-free data (FT data) from the efuse and merge it with the existing EEPROM data. Co-developed-by: Shayne Chen Signed-off-by: Shayne Chen Signed-off-by: StanleyYP Wang Link: https://patch.msgid.link/20260212090310.3335392-3-shayne.chen@mediatek.com Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/mcu.c | 172 ++++++++++++++++-- .../wireless/mediatek/mt76/mt7996/mt7996.h | 5 + 2 files changed, 158 insertions(+), 19 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index 81c4e0d4654b..27713399c318 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -3952,7 +3952,153 @@ int mt7996_mcu_set_chan_info(struct mt7996_phy *phy, u16 tag) &req, sizeof(req), true); } -static int mt7996_mcu_set_eeprom_flash(struct mt7996_dev *dev) +static int +mt7996_mcu_get_cal_free_data(struct mt7996_dev *dev) +{ +#define MT_EE_7977BN_OFFSET (0x1200 - 0x500) + struct cal_free_data { + u16 adie_offs; + u16 eep_offs; + }; + static const struct cal_free_data cal_7975[] = { + { 0x5cd, 0x451 }, { 0x5cf, 0x453 }, { 0x5d1, 0x455 }, + { 0x5d3, 0x457 }, { 0x6c0, 0x44c }, { 0x6c1, 0x44d }, + { 0x6c2, 0x44e }, { 0x6c3, 0x44f }, { 0x7a1, 0xba1 }, + { 0x7a6, 0xba6 }, { 0x7a8, 0xba8 }, { 0x7aa, 0xbaa }, + }; + static const struct cal_free_data cal_7976[] = { + { 0x4c, 0x44c }, { 0x4d, 0x44d }, { 0x4e, 0x44e }, + { 0x4f, 0x44f }, { 0x50, 0x450 }, { 0x51, 0x451 }, + { 0x53, 0x453 }, { 0x55, 0x455 }, { 0x57, 0x457 }, + { 0x59, 0x459 }, { 0x70, 0x470 }, { 0x71, 0x471 }, + { 0x790, 0xb90 }, { 0x791, 0xb91 }, { 0x794, 0xb94 }, + { 0x795, 0xb95 }, { 0x7a6, 0xba6 }, { 0x7a8, 0xba8 }, + { 0x7aa, 0xbaa }, + }; + static const struct cal_free_data cal_7977[] = { + { 0x4c, 0x124c }, { 0x4d, 0x124d }, { 0x4e, 0x124e }, + { 0x4f, 0x124f }, { 0x50, 0x1250 }, { 0x51, 0x1251 }, + { 0x53, 0x1253 }, { 0x55, 0x1255 }, { 0x57, 0x1257 }, + { 0x59, 0x1259 }, { 0x69, 0x1269 }, { 0x6a, 0x126a }, + { 0x7a, 0x127a }, { 0x7b, 0x127b }, { 0x7c, 0x127c }, + { 0x7d, 0x127d }, { 0x7e, 0x127e }, + }; + static const struct cal_free_data cal_7978[] = { + { 0x91, 0xb91 }, { 0x95, 0xb95 }, { 0x100, 0x480 }, + { 0x102, 0x482 }, { 0x104, 0x484 }, { 0x106, 0x486 }, + { 0x107, 0x487 }, { 0x108, 0x488 }, { 0x109, 0x489 }, + { 0x10a, 0x48a }, { 0x10b, 0x48b }, { 0x10c, 0x48c }, + { 0x10e, 0x48e }, { 0x110, 0x490 }, + }; + static const struct cal_free_data cal_7979[] = { + { 0x4c, 0x124c }, { 0x4d, 0x124d }, { 0x4e, 0x124e }, + { 0x4f, 0x124f }, { 0x50, 0x1250 }, { 0x51, 0x1251 }, + { 0x53, 0x1253 }, { 0x55, 0x1255 }, { 0x57, 0x1257 }, + { 0x59, 0x1259 }, { 0x69, 0x1269 }, { 0x6a, 0x126a }, + { 0x7a, 0x127a }, { 0x7b, 0x127b }, { 0x7c, 0x127c }, + { 0x7e, 0x127e }, { 0x80, 0x1280 }, + }; + const struct cal_free_data *cal_arr[__MT_MAX_BAND]; + u16 cal_arr_len[__MT_MAX_BAND] = {}; + u8 *eeprom = (u8 *)dev->mt76.eeprom.data; + int band, i, ret; + +#define CAL_ARR(_band, _adie) do { \ + cal_arr[_band] = cal_##_adie; \ + cal_arr_len[_band] = ARRAY_SIZE(cal_##_adie); \ + } while (0) + + switch (mt76_chip(&dev->mt76)) { + case MT7996_DEVICE_ID: + /* adie 0 */ + if (dev->var.fem == MT7996_FEM_INT && + dev->var.type != MT7996_VAR_TYPE_233) + CAL_ARR(0, 7975); + else + CAL_ARR(0, 7976); + + /* adie 1 */ + if (dev->var.type == MT7996_VAR_TYPE_444) + CAL_ARR(1, 7977); + + /* adie 2 */ + CAL_ARR(2, 7977); + break; + case MT7992_DEVICE_ID: + /* adie 0 */ + if (dev->var.type == MT7992_VAR_TYPE_44 && + dev->var.fem != MT7996_FEM_EXT) + CAL_ARR(0, 7975); + else if (dev->var.type == MT7992_VAR_TYPE_24) + CAL_ARR(0, 7978); + else + CAL_ARR(0, 7976); + + /* adie 1 */ + if (dev->var.type == MT7992_VAR_TYPE_44 && + dev->var.fem != MT7996_FEM_INT) + CAL_ARR(1, 7977); + else if (dev->var.type != MT7992_VAR_TYPE_23) + CAL_ARR(1, 7979); + break; + case MT7990_DEVICE_ID: + /* adie 0 */ + CAL_ARR(0, 7976); + break; + default: + return -EINVAL; + } + + for (band = 0; band < __MT_MAX_BAND; band++) { + u8 buf[MT7996_EEPROM_BLOCK_SIZE]; + const struct cal_free_data *cal; + u16 prev_block_idx = -1; + u16 adie_base; + + if (!cal_arr_len[band]) + continue; + + if (band == MT_BAND0) + adie_base = MT7996_EFUSE_BASE_OFFS_ADIE0; + else if (band == MT_BAND1 && is_mt7992(&dev->mt76)) + adie_base = MT7992_EFUSE_BASE_OFFS_ADIE1; + else if (band == MT_BAND1) + adie_base = MT7996_EFUSE_BASE_OFFS_ADIE1; + else + adie_base = MT7996_EFUSE_BASE_OFFS_ADIE2; + + cal = cal_arr[band]; + for (i = 0; i < cal_arr_len[band]; i++) { + u16 adie_offset = cal[i].adie_offs + adie_base; + u16 eep_offset = cal[i].eep_offs; + u16 block_idx = adie_offset / MT7996_EEPROM_BLOCK_SIZE; + u16 offset = adie_offset % MT7996_EEPROM_BLOCK_SIZE; + + if (is_mt7996(&dev->mt76) && band == MT_BAND1 && + dev->var.type == MT7996_VAR_TYPE_444) + eep_offset -= MT_EE_7977BN_OFFSET; + + if (prev_block_idx != block_idx) { + memset(buf, 0, sizeof(buf)); + ret = mt7996_mcu_get_eeprom(dev, adie_offset, buf, + MT7996_EEPROM_BLOCK_SIZE, + EEPROM_MODE_EFUSE); + if (ret) { + if (ret != -EINVAL) + return ret; + prev_block_idx = -1; + continue; + } + } + eeprom[eep_offset] = buf[offset]; + prev_block_idx = block_idx; + } + } + + return 0; +} + +int mt7996_mcu_set_eeprom(struct mt7996_dev *dev) { #define MAX_PAGE_IDX_MASK GENMASK(7, 5) #define PAGE_IDX_MASK GENMASK(4, 2) @@ -3964,11 +4110,15 @@ static int mt7996_mcu_set_eeprom_flash(struct mt7996_dev *dev) u16 eeprom_size = MT7996_EEPROM_SIZE; u8 total = DIV_ROUND_UP(eeprom_size, PER_PAGE_SIZE); u8 *eep = (u8 *)dev->mt76.eeprom.data; - int eep_len, i; + int ret, eep_len, i; + + ret = mt7996_mcu_get_cal_free_data(dev); + if (ret) + return ret; for (i = 0; i < total; i++, eep += eep_len) { struct sk_buff *skb; - int ret, msg_len; + int msg_len; if (i == total - 1 && !!(eeprom_size % PER_PAGE_SIZE)) eep_len = eeprom_size % PER_PAGE_SIZE; @@ -3997,22 +4147,6 @@ static int mt7996_mcu_set_eeprom_flash(struct mt7996_dev *dev) return 0; } -int mt7996_mcu_set_eeprom(struct mt7996_dev *dev) -{ - struct mt7996_mcu_eeprom_update req = { - .tag = cpu_to_le16(UNI_EFUSE_BUFFER_MODE), - .len = cpu_to_le16(sizeof(req) - 4), - .buffer_mode = EE_MODE_EFUSE, - .format = EE_FORMAT_WHOLE - }; - - if (dev->eeprom_mode != EEPROM_MODE_EFUSE) - return mt7996_mcu_set_eeprom_flash(dev); - - return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(EFUSE_CTRL), - &req, sizeof(req), true); -} - int mt7996_mcu_get_eeprom(struct mt7996_dev *dev, u32 offset, u8 *buf, u32 buf_len, enum mt7996_eeprom_mode mode) { diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index d36fb5396141..5f574ebe81cc 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -184,6 +184,11 @@ enum mt7996_eeprom_mode { EEPROM_MODE_EXT, }; +#define MT7996_EFUSE_BASE_OFFS_ADIE0 0x400 +#define MT7996_EFUSE_BASE_OFFS_ADIE1 0x1e00 +#define MT7996_EFUSE_BASE_OFFS_ADIE2 0x1200 +#define MT7992_EFUSE_BASE_OFFS_ADIE1 0x1200 + enum mt7996_txq_id { MT7996_TXQ_FWDL = 16, MT7996_TXQ_MCU_WM, From 5c81a4f182d9b48f32e2548f4a39dd76eafb5404 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Wed, 18 Feb 2026 18:39:57 -0600 Subject: [PATCH 064/134] wifi: mt76: connac: use is_connac2() to replace is_mt7921() checks Unify all per-chip conditionals under the new is_connac2() helper. This avoids confusion caused by the previous is_mt7921() check, which implicitly covered multiple connac2 chipsets and no longer reflected its actual scope. This is a clean-up only change with no functional impact. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260219004007.19733-1-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt76_connac.h | 2 +- .../wireless/mediatek/mt76/mt76_connac_mac.c | 16 ++++++------ .../wireless/mediatek/mt76/mt76_connac_mcu.c | 26 +++++++++---------- .../wireless/mediatek/mt76/mt76_connac_mcu.h | 2 +- .../net/wireless/mediatek/mt76/mt792x_core.c | 2 +- .../net/wireless/mediatek/mt76/mt792x_dma.c | 2 +- 6 files changed, 25 insertions(+), 25 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac.h b/drivers/net/wireless/mediatek/mt76/mt76_connac.h index 813d61bffc2c..02bea67d37c3 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac.h +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac.h @@ -187,7 +187,7 @@ static inline bool is_mt7922(struct mt76_dev *dev) return mt76_chip(dev) == 0x7922; } -static inline bool is_mt7921(struct mt76_dev *dev) +static inline bool is_connac2(struct mt76_dev *dev) { return mt76_chip(dev) == 0x7961 || is_mt7922(dev) || is_mt7920(dev); } diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c index c46691248513..c2cf6893848b 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_mt7921(dev) || is_mt7925(dev)) + if (is_mt7663(dev) || is_connac2(dev) || is_mt7925(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_mt7921(dev) || is_mt7925(dev)) + if (is_mt7663(dev) || is_connac2(dev) || is_mt7925(dev)) last_mask = MT_TXD_LEN_LAST; else last_mask = MT_TXD_LEN_MSDU_LAST; @@ -309,7 +309,7 @@ u16 mt76_connac2_mac_tx_rate_val(struct mt76_phy *mphy, chandef = mvif->ctx ? &mvif->ctx->def : &mphy->chandef; band = chandef->chan->band; - if (is_mt7921(mphy->dev)) { + if (is_connac2(mphy->dev)) { rateidx = ffs(conf->basic_rates) - 1; goto legacy; } @@ -548,7 +548,7 @@ void mt76_connac2_mac_write_txwi(struct mt76_dev *dev, __le32 *txwi, val = MT_TXD1_LONG_FORMAT | FIELD_PREP(MT_TXD1_WLAN_IDX, wcid->idx) | FIELD_PREP(MT_TXD1_OWN_MAC, omac_idx); - if (!is_mt7921(dev)) + if (!is_connac2(dev)) val |= MT_TXD1_VTA; if (phy_idx || band_idx) val |= MT_TXD1_TGID; @@ -557,7 +557,7 @@ void mt76_connac2_mac_write_txwi(struct mt76_dev *dev, __le32 *txwi, txwi[2] = 0; val = FIELD_PREP(MT_TXD3_REM_TX_COUNT, 15); - if (!is_mt7921(dev)) + if (!is_connac2(dev)) val |= MT_TXD3_SW_POWER_MGMT; if (key) val |= MT_TXD3_PROTECT_FRAME; @@ -599,7 +599,7 @@ void mt76_connac2_mac_write_txwi(struct mt76_dev *dev, __le32 *txwi, txwi[6] |= cpu_to_le32(val); txwi[3] |= cpu_to_le32(MT_TXD3_BA_DISABLE); - if (!is_mt7921(dev)) { + if (!is_connac2(dev)) { u8 spe_idx = mt76_connac_spe_idx(mphy->antenna_mask); if (!spe_idx) @@ -831,7 +831,7 @@ mt76_connac2_mac_decode_he_mu_radiotap(struct mt76_dev *dev, struct sk_buff *skb }; struct ieee80211_radiotap_he_mu *he_mu; - if (is_mt7921(dev)) { + if (is_connac2(dev)) { mu_known.flags1 |= HE_BITS(MU_FLAGS1_SIG_B_COMP_KNOWN); mu_known.flags2 |= HE_BITS(MU_FLAGS2_PUNC_FROM_SIG_A_BW_KNOWN); } @@ -1047,7 +1047,7 @@ int mt76_connac2_mac_fill_rx_rate(struct mt76_dev *dev, stbc = FIELD_GET(MT_PRXV_HT_STBC, v0); gi = FIELD_GET(MT_PRXV_HT_SGI, v0); *mode = FIELD_GET(MT_PRXV_TX_MODE, v0); - if (is_mt7921(dev)) + if (is_connac2(dev)) dcm = !!(idx & MT_PRXV_TX_DCM); else dcm = FIELD_GET(MT_PRXV_DCM, v0); diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c index 3f583e2a1dc1..d7fbf3454bb8 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c @@ -65,7 +65,7 @@ int mt76_connac_mcu_init_download(struct mt76_dev *dev, u32 addr, u32 len, int cmd; if ((!is_connac_v1(dev) && addr == MCU_PATCH_ADDRESS) || - (is_mt7921(dev) && addr == 0x900000) || + (is_connac2(dev) && addr == 0x900000) || (is_mt7925(dev) && (addr == 0x900000 || addr == 0xe0002800)) || (is_mt799x(dev) && addr == 0x900000)) cmd = MCU_CMD(PATCH_START_REQ); @@ -402,7 +402,7 @@ void mt76_connac_mcu_sta_basic_tlv(struct mt76_dev *dev, struct sk_buff *skb, switch (vif->type) { case NL80211_IFTYPE_MESH_POINT: case NL80211_IFTYPE_AP: - if (vif->p2p && !is_mt7921(dev)) + if (vif->p2p && !is_connac2(dev)) conn_type = CONNECTION_P2P_GC; else conn_type = CONNECTION_INFRA_STA; @@ -410,7 +410,7 @@ void mt76_connac_mcu_sta_basic_tlv(struct mt76_dev *dev, struct sk_buff *skb, basic->aid = cpu_to_le16(link_sta->sta->aid); break; case NL80211_IFTYPE_STATION: - if (vif->p2p && !is_mt7921(dev)) + if (vif->p2p && !is_connac2(dev)) conn_type = CONNECTION_P2P_GO; else conn_type = CONNECTION_INFRA_AP; @@ -874,7 +874,7 @@ void mt76_connac_mcu_sta_tlv(struct mt76_phy *mphy, struct sk_buff *skb, struct sta_rec_vht *vht; int len; - len = is_mt7921(dev) ? sizeof(*vht) : sizeof(*vht) - 4; + len = is_connac2(dev) ? sizeof(*vht) : sizeof(*vht) - 4; tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_VHT, len); vht = (struct sta_rec_vht *)tlv; vht->vht_cap = cpu_to_le32(sta->deflink.vht_cap.cap); @@ -885,7 +885,7 @@ void mt76_connac_mcu_sta_tlv(struct mt76_phy *mphy, struct sk_buff *skb, /* starec uapsd */ mt76_connac_mcu_sta_uapsd(skb, vif, sta); - if (!is_mt7921(dev)) + if (!is_connac2(dev)) return; if (sta->deflink.ht_cap.ht_supported || sta->deflink.he_cap.has_he) @@ -1778,7 +1778,7 @@ int mt76_connac_mcu_hw_scan(struct mt76_phy *phy, struct ieee80211_vif *vif, req->ssid_type_ext = n_ssids ? BIT(0) : 0; req->ssids_num = n_ssids; - duration = is_mt7921(phy->dev) ? 0 : MT76_CONNAC_SCAN_CHANNEL_TIME; + duration = is_connac2(phy->dev) ? 0 : MT76_CONNAC_SCAN_CHANNEL_TIME; /* increase channel time for passive scan */ if (!sreq->n_ssids) duration *= 2; @@ -1821,7 +1821,7 @@ int mt76_connac_mcu_hw_scan(struct mt76_phy *phy, struct ieee80211_vif *vif, req->ies_len = cpu_to_le16(sreq->ie_len); } - if (is_mt7921(phy->dev)) + if (is_connac2(phy->dev)) req->scan_func |= SCAN_FUNC_SPLIT_SCAN; memcpy(req->bssid, sreq->bssid, ETH_ALEN); @@ -1897,7 +1897,7 @@ int mt76_connac_mcu_sched_scan_req(struct mt76_phy *phy, get_random_mask_addr(addr, sreq->mac_addr, sreq->mac_addr_mask); } - if (is_mt7921(phy->dev)) { + if (is_connac2(phy->dev)) { req->mt7921.bss_idx = mvif->idx; req->mt7921.delay = cpu_to_le32(sreq->delay); } @@ -2037,7 +2037,7 @@ mt76_connac_mcu_build_sku(struct mt76_dev *dev, s8 *sku, struct mt76_power_limits *limits, enum nl80211_band band) { - int max_power = is_mt7921(dev) ? 127 : 63; + int max_power = is_connac2(dev) ? 127 : 63; int i, offset = sizeof(limits->cck); memset(sku, max_power, MT_SKU_POWER_LIMIT); @@ -2065,7 +2065,7 @@ mt76_connac_mcu_build_sku(struct mt76_dev *dev, s8 *sku, offset += 12; } - if (!is_mt7921(dev)) + if (!is_connac2(dev)) return; /* he */ @@ -2121,7 +2121,7 @@ mt76_connac_mcu_rate_txpower_band(struct mt76_phy *phy, enum nl80211_band band) { struct mt76_dev *dev = phy->dev; - int sku_len, batch_len = is_mt7921(dev) ? 8 : 16; + int sku_len, batch_len = is_connac2(dev) ? 8 : 16; static const u8 chan_list_2ghz[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 @@ -2162,7 +2162,7 @@ mt76_connac_mcu_rate_txpower_band(struct mt76_phy *phy, if (!limits) return -ENOMEM; - sku_len = is_mt7921(dev) ? sizeof(sku_tlbv) : sizeof(sku_tlbv) - 92; + sku_len = is_connac2(dev) ? sizeof(sku_tlbv) : sizeof(sku_tlbv) - 92; tx_power = 2 * phy->hw->conf.power_level; if (!tx_power) tx_power = 127; @@ -3080,7 +3080,7 @@ static u32 mt76_connac2_get_data_mode(struct mt76_dev *dev, u32 info) { u32 mode = DL_MODE_NEED_RSP; - if ((!is_mt7921(dev) && !is_mt7925(dev)) || info == PATCH_SEC_NOT_SUPPORT) + if ((!is_connac2(dev) && !is_mt7925(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 e91966cd5efe..0809318c1ec2 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h @@ -1867,7 +1867,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_mt7921(dev) || is_mt7925(dev)) + if (is_connac2(dev) || is_mt7925(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_core.c b/drivers/net/wireless/mediatek/mt76/mt792x_core.c index f318a53e4deb..2142fcc4ae27 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_core.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_core.c @@ -151,7 +151,7 @@ void mt792x_stop(struct ieee80211_hw *hw, bool suspend) cancel_work_sync(&dev->reset_work); mt76_connac_free_pending_tx_skbs(&dev->pm, NULL); - if (is_mt7921(&dev->mt76)) { + if (is_connac2(&dev->mt76)) { mt792x_mutex_acquire(dev); mt76_connac_mcu_set_mac_enable(&dev->mt76, 0, false, false); mt792x_mutex_release(dev); diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_dma.c b/drivers/net/wireless/mediatek/mt76/mt792x_dma.c index 1ddec7788b66..34f07bd3097d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_dma.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_dma.c @@ -356,7 +356,7 @@ EXPORT_SYMBOL_GPL(mt792x_poll_rx); int mt792x_wfsys_reset(struct mt792x_dev *dev) { - u32 addr = is_mt7921(&dev->mt76) ? 0x18000140 : 0x7c000140; + u32 addr = is_connac2(&dev->mt76) ? 0x18000140 : 0x7c000140; mt76_clear(dev, addr, WFSYS_SW_RST_B); msleep(50); From 918af1f87f7de988a7e84a7a85390a7d626ee189 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Wed, 18 Feb 2026 18:39:58 -0600 Subject: [PATCH 065/134] wifi: mt76: mt7921: use mt76_for_each_q_rx() in reset path Replace explicit napi_disable() calls for RX queues with mt76_for_each_q_rx() in mt7921e_mac_reset(). This removes hardcoded queue indices and disables all configured RX queues during reset. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260219004007.19733-2-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7921/pci_mac.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/pci_mac.c b/drivers/net/wireless/mediatek/mt76/mt7921/pci_mac.c index 5ec084432ae3..0db7acb3a637 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/pci_mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/pci_mac.c @@ -71,9 +71,9 @@ int mt7921e_mac_reset(struct mt792x_dev *dev) mt76_txq_schedule_all(&dev->mphy); mt76_worker_disable(&dev->mt76.tx_worker); - napi_disable(&dev->mt76.napi[MT_RXQ_MAIN]); - napi_disable(&dev->mt76.napi[MT_RXQ_MCU]); - napi_disable(&dev->mt76.napi[MT_RXQ_MCU_WA]); + mt76_for_each_q_rx(&dev->mt76, i) { + napi_disable(&dev->mt76.napi[i]); + } napi_disable(&dev->mt76.tx_napi); mt76_connac2_tx_token_put(&dev->mt76); From 222606f43b587c9fb4ae063d04db146100c8951c Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Wed, 18 Feb 2026 18:39:59 -0600 Subject: [PATCH 066/134] wifi: mt76: mt7921: handle MT7902 irq_map quirk with mutable copy MT7902 PCIe requires a different wm2_complete_mask value, so introduce a mutable per-device copy of the default irq_map and override the field only for this chip. Other devices continue using the shared const template. This is a prerequisite patch before enabling MT7902 PCIe support. Co-developed-by: Xiong Huang Signed-off-by: Xiong Huang Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260219004007.19733-3-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7921/pci.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c index 65c7fe671137..5f857a21f362 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c @@ -327,6 +327,20 @@ static int mt7921_pci_probe(struct pci_dev *pdev, dev->hif_ops = &mt7921_pcie_ops; dev->irq_map = &irq_map; mt76_mmio_init(&dev->mt76, regs); + + if (id->device == 0x7902) { + struct mt792x_irq_map *map; + + /* 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; + + map->rx.wm2_complete_mask = 0; + dev->irq_map = map; + } + tasklet_init(&mdev->irq_tasklet, mt792x_irq_tasklet, (unsigned long)dev); dev->phy.dev = dev; From d3bb1ca22896a28860009cb83dd8af09748bccac Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Wed, 18 Feb 2026 18:40:00 -0600 Subject: [PATCH 067/134] wifi: mt76: mt7921: add MT7902e DMA layout support Add MT7902 PCIe specific DMA layout overrides for MCU TXQ index, RX ring size, and MCU_WA usage. Common layout remains the default for other chips. This is a prerequisite patch before enabling MT7902 PCIe support. Co-developed-by: Xiong Huang Signed-off-by: Xiong Huang Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260219004007.19733-4-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- .../wireless/mediatek/mt76/mt7921/mt7921.h | 14 +++++++ .../net/wireless/mediatek/mt76/mt7921/pci.c | 41 +++++++++++++++---- 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h b/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h index ad92af98e314..64f60c4fc60c 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h +++ b/drivers/net/wireless/mediatek/mt76/mt7921/mt7921.h @@ -17,6 +17,9 @@ #define MT7921_RX_MCU_RING_SIZE 8 #define MT7921_RX_MCU_WA_RING_SIZE 512 +/* MT7902 Rx Ring0 is for both Rx Event and Tx Done Event */ +#define MT7902_RX_MCU_RING_SIZE 512 + #define MT7921_EEPROM_SIZE 3584 #define MT7921_TOKEN_SIZE 8192 @@ -119,6 +122,17 @@ enum mt7921_rxq_id { MT7921_RXQ_MCU_WM = 0, }; +/* MT7902 assigns its MCU-WM TXQ at index 15 */ +enum mt7902_txq_id { + MT7902_TXQ_MCU_WM = 15, +}; + +struct mt7921_dma_layout { + u8 mcu_wm_txq; + u16 mcu_rxdone_ring_size; + bool has_mcu_wa; +}; + enum { MT7921_CLC_POWER, MT7921_CLC_CHAN, diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c index 5f857a21f362..6bb3c6a1cf6a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c @@ -167,8 +167,29 @@ static u32 mt7921_rmw(struct mt76_dev *mdev, u32 offset, u32 mask, u32 val) static int mt7921_dma_init(struct mt792x_dev *dev) { + struct mt7921_dma_layout layout = { + /* General case: MT7921 / MT7922 /MT7920 */ + .mcu_wm_txq = MT7921_TXQ_MCU_WM, + .mcu_rxdone_ring_size = MT7921_RX_MCU_RING_SIZE, + .has_mcu_wa = true, + }; + bool is_mt7902; int ret; + is_mt7902 = mt7921_l1_rr(dev, MT_HW_CHIPID) == 0x7902; + + /* + * MT7902 special case: + * - MCU-WM TXQ uses index 15 + * - RX Ring0 is larger and shared for event/TX-done + * - MT7902 does not use the MCU_WA ring + */ + if (is_mt7902) { + layout.mcu_wm_txq = MT7902_TXQ_MCU_WM; + layout.mcu_rxdone_ring_size = MT7902_RX_MCU_RING_SIZE; + layout.has_mcu_wa = false; + } + mt76_dma_attach(&dev->mt76); ret = mt792x_dma_disable(dev, true); @@ -185,7 +206,7 @@ static int mt7921_dma_init(struct mt792x_dev *dev) mt76_wr(dev, MT_WFDMA0_TX_RING0_EXT_CTRL, 0x4); /* command to WM */ - ret = mt76_init_mcu_queue(&dev->mt76, MT_MCUQ_WM, MT7921_TXQ_MCU_WM, + ret = mt76_init_mcu_queue(&dev->mt76, MT_MCUQ_WM, layout.mcu_wm_txq, MT7921_TX_MCU_RING_SIZE, MT_TX_RING_BASE); if (ret) return ret; @@ -199,18 +220,20 @@ static int mt7921_dma_init(struct mt792x_dev *dev) /* event from WM before firmware download */ ret = mt76_queue_alloc(dev, &dev->mt76.q_rx[MT_RXQ_MCU], MT7921_RXQ_MCU_WM, - MT7921_RX_MCU_RING_SIZE, + layout.mcu_rxdone_ring_size, MT_RX_BUF_SIZE, MT_RX_EVENT_RING_BASE); if (ret) return ret; - /* Change mcu queue after firmware download */ - ret = mt76_queue_alloc(dev, &dev->mt76.q_rx[MT_RXQ_MCU_WA], - MT7921_RXQ_MCU_WM, - MT7921_RX_MCU_WA_RING_SIZE, - MT_RX_BUF_SIZE, MT_WFDMA0(0x540)); - if (ret) - return ret; + if (layout.has_mcu_wa) { + /* Change mcu queue after firmware download */ + ret = mt76_queue_alloc(dev, &dev->mt76.q_rx[MT_RXQ_MCU_WA], + MT7921_RXQ_MCU_WM, + MT7921_RX_MCU_WA_RING_SIZE, + MT_RX_BUF_SIZE, MT_WFDMA0(0x540)); + if (ret) + return ret; + } /* rx data */ ret = mt76_queue_alloc(dev, &dev->mt76.q_rx[MT_RXQ_MAIN], From 0a7d2fca06afb036ff2d61540fc68e6e48eb9fbe Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Wed, 18 Feb 2026 18:40:01 -0600 Subject: [PATCH 068/134] wifi: mt76: connac: mark MT7902 as hw txp devices Add MT7902 to is_mt76_fw_txp() so it follows the legacy TX descriptor path like the other connac2 chips that return false. This is a prerequisite patch before enabling MT7902 pcie support. Co-developed-by: Xiong Huang Signed-off-by: Xiong Huang Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260219004007.19733-5-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt76_connac.h | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac.h b/drivers/net/wireless/mediatek/mt76/mt76_connac.h index 02bea67d37c3..d868bb7c7ab8 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac.h +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac.h @@ -271,6 +271,7 @@ static inline bool is_mt76_fw_txp(struct mt76_dev *dev) case 0x7961: case 0x7920: case 0x7922: + case 0x7902: case 0x7925: case 0x7663: case 0x7622: From 14a7ba034fcd9d1766503ef44cc491c6fda8db2c Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Wed, 18 Feb 2026 18:40:02 -0600 Subject: [PATCH 069/134] wifi: mt76: mt792x: add PSE handling barrier for the large MCU cmd Add a dummy register read in mt76_connac_mcu_rate_txpower_band() to act as a PSE barrier. This would release PSE pages and prevents buffer underflow issues when handling MCU commands with larger payloads without the response in mt76_connac_mcu_set_rate_txpower(). This is a prerequisite patch before enabling MT7902 PCIe and SDIO support. Co-developed-by: Xiong Huang Signed-off-by: Xiong Huang Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260219004007.19733-6-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c | 4 ++++ drivers/net/wireless/mediatek/mt76/mt792x_regs.h | 2 ++ 2 files changed, 6 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c index d7fbf3454bb8..89bd52ea8bf7 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c @@ -4,6 +4,7 @@ #include #include "mt76_connac2_mac.h" #include "mt76_connac_mcu.h" +#include "mt792x_regs.h" int mt76_connac_mcu_start_firmware(struct mt76_dev *dev, u32 addr, u32 option) { @@ -2246,6 +2247,9 @@ mt76_connac_mcu_rate_txpower_band(struct mt76_phy *phy, false); if (err < 0) goto out; + + /* read a CR to avoid PSE buffer underflow */ + mt76_connac_mcu_reg_rr(dev, MT_PSE_BASE); } out: diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_regs.h b/drivers/net/wireless/mediatek/mt76/mt792x_regs.h index acf627aed609..7ddde9286861 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_regs.h +++ b/drivers/net/wireless/mediatek/mt76/mt792x_regs.h @@ -25,6 +25,8 @@ #define MT_PLE_AC_QEMPTY(_n) MT_PLE(0x500 + 0x40 * (_n)) #define MT_PLE_AMSDU_PACK_MSDU_CNT(n) MT_PLE(0x10e0 + ((n) << 2)) +#define MT_PSE_BASE 0x820c8000 + /* TMAC: band 0(0x21000), band 1(0xa1000) */ #define MT_WF_TMAC_BASE(_band) ((_band) ? 0x820f4000 : 0x820e4000) #define MT_WF_TMAC(_band, ofs) (MT_WF_TMAC_BASE(_band) + (ofs)) From 9eef868b86db32d40e4a45f407af8aa1e4e4e830 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Wed, 18 Feb 2026 18:40:03 -0600 Subject: [PATCH 070/134] wifi: mt76: mt792x: ensure MCU ready before ROM patch download Restart the MCU and poll FW state to ensure correct MCU status before downloading the ROM patch. This is a prerequisite for enabling MT7902 PCIe and has been validated on MT7921 and MT7925 since they share the common code path. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260219004007.19733-7-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt792x_core.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_core.c b/drivers/net/wireless/mediatek/mt76/mt792x_core.c index 2142fcc4ae27..152cfcca2f90 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_core.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_core.c @@ -926,6 +926,13 @@ int mt792x_load_firmware(struct mt792x_dev *dev) { int ret; + mt76_connac_mcu_restart(&dev->mt76); + + if (!mt76_poll_msec(dev, MT_CONN_ON_MISC, MT_TOP_MISC_FW_STATE, + MT_TOP_MISC2_FW_PWR_ON, 1000)) + dev_warn(dev->mt76.dev, + "MCU is not ready for firmware download\n"); + ret = mt76_connac2_load_patch(&dev->mt76, mt792x_patch_name(dev)); if (ret) return ret; From 6d32fb25768946cf2bc8eef9ba77acf9406867ef Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Wed, 18 Feb 2026 18:40:04 -0600 Subject: [PATCH 071/134] wifi: mt76: mt7921: add MT7902 MCU support Add MCU support for the MT7902 chipset. runtime pm is not yet supported by the driver, but normal mac80211 operation is unaffected. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260219004007.19733-8-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt76_connac.h | 8 +++++++- drivers/net/wireless/mediatek/mt76/mt7921/init.c | 4 +++- drivers/net/wireless/mediatek/mt76/mt792x.h | 6 ++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac.h b/drivers/net/wireless/mediatek/mt76/mt76_connac.h index d868bb7c7ab8..51423c7740bd 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac.h +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac.h @@ -182,6 +182,11 @@ static inline bool is_mt7920(struct mt76_dev *dev) return mt76_chip(dev) == 0x7920; } +static inline bool is_mt7902(struct mt76_dev *dev) +{ + return mt76_chip(dev) == 0x7902; +} + static inline bool is_mt7922(struct mt76_dev *dev) { return mt76_chip(dev) == 0x7922; @@ -189,7 +194,8 @@ static inline bool is_mt7922(struct mt76_dev *dev) static inline bool is_connac2(struct mt76_dev *dev) { - return mt76_chip(dev) == 0x7961 || is_mt7922(dev) || is_mt7920(dev); + return mt76_chip(dev) == 0x7961 || is_mt7922(dev) || is_mt7920(dev) || + is_mt7902(dev); } static inline bool is_mt7663(struct mt76_dev *dev) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/init.c b/drivers/net/wireless/mediatek/mt76/mt7921/init.c index 29732315af1c..8e7790702191 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/init.c @@ -302,7 +302,9 @@ int mt7921_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)) { + + if (!mt76_is_usb(&dev->mt76) && + !is_mt7902(&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/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h index 8388638ed550..1f381ab356bc 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x.h +++ b/drivers/net/wireless/mediatek/mt76/mt792x.h @@ -41,11 +41,13 @@ #define MT792x_MCU_INIT_RETRY_COUNT 10 #define MT792x_WFSYS_INIT_RETRY_COUNT 2 +#define MT7902_FIRMWARE_WM "mediatek/WIFI_RAM_CODE_MT7902_1.bin" #define MT7920_FIRMWARE_WM "mediatek/WIFI_RAM_CODE_MT7961_1a.bin" #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 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" @@ -448,6 +450,8 @@ void mt792x_config_mac_addr_list(struct mt792x_dev *dev); static inline char *mt792x_ram_name(struct mt792x_dev *dev) { switch (mt76_chip(&dev->mt76)) { + case 0x7902: + return MT7902_FIRMWARE_WM; case 0x7920: return MT7920_FIRMWARE_WM; case 0x7922: @@ -462,6 +466,8 @@ static inline char *mt792x_ram_name(struct mt792x_dev *dev) static inline char *mt792x_patch_name(struct mt792x_dev *dev) { switch (mt76_chip(&dev->mt76)) { + case 0x7902: + return MT7902_ROM_PATCH; case 0x7920: return MT7920_ROM_PATCH; case 0x7922: From 199443b4015238f46c8a578b84c3834a48246355 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Wed, 18 Feb 2026 18:40:05 -0600 Subject: [PATCH 072/134] wifi: mt76: mt792x: add MT7902 WFDMA prefetch configuration Configure the RX/TX ring prefetch setting for MT7902 PCIe device. This is a prerequisite patch before enabling MT7902 PCIe support. Co-developed-by: Xiong Huang Signed-off-by: Xiong Huang Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260219004007.19733-9-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt792x_dma.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_dma.c b/drivers/net/wireless/mediatek/mt76/mt792x_dma.c index 34f07bd3097d..002aece857b2 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_dma.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_dma.c @@ -103,6 +103,22 @@ static void mt792x_dma_prefetch(struct mt792x_dev *dev) 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)); + } else if (is_mt7902(&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, 0x4)); + mt76_wr(dev, MT_WFDMA0_TX_RING1_EXT_CTRL, PREFETCH(0x0140, 0x4)); + mt76_wr(dev, MT_WFDMA0_TX_RING2_EXT_CTRL, PREFETCH(0x0180, 0x4)); + mt76_wr(dev, MT_WFDMA0_TX_RING3_EXT_CTRL, PREFETCH(0x01c0, 0x4)); + mt76_wr(dev, MT_WFDMA0_TX_RING4_EXT_CTRL, PREFETCH(0x0200, 0x4)); + mt76_wr(dev, MT_WFDMA0_TX_RING5_EXT_CTRL, PREFETCH(0x0240, 0x4)); + mt76_wr(dev, MT_WFDMA0_TX_RING6_EXT_CTRL, PREFETCH(0x0280, 0x4)); + mt76_wr(dev, MT_WFDMA0_TX_RING15_EXT_CTRL, PREFETCH(0x02c0, 0x4)); + mt76_wr(dev, MT_WFDMA0_TX_RING16_EXT_CTRL, PREFETCH(0x0300, 0x4)); } else { /* rx ring */ mt76_wr(dev, MT_WFDMA0_RX_RING0_EXT_CTRL, PREFETCH(0x0, 0x4)); From c26319afb5fb403a0bb2a604cee95a5bab8bbf18 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Wed, 18 Feb 2026 18:40:06 -0600 Subject: [PATCH 073/134] wifi: mt76: mt7921: add MT7902 PCIe device support Register the MT7902 PCI device ID in the mt7921 driver and add its corresponding firmware and ROM patch names. Co-developed-by: Xiong Huang Signed-off-by: Xiong Huang Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260219004007.19733-10-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7921/pci.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c index 6bb3c6a1cf6a..7a790ddf43bb 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c @@ -26,6 +26,8 @@ static const struct pci_device_id mt7921_pci_device_table[] = { .driver_data = (kernel_ulong_t)MT7922_FIRMWARE_WM }, { PCI_DEVICE(PCI_VENDOR_ID_MEDIATEK, 0x7920), .driver_data = (kernel_ulong_t)MT7920_FIRMWARE_WM }, + { PCI_DEVICE(PCI_VENDOR_ID_MEDIATEK, 0x7902), + .driver_data = (kernel_ulong_t)MT7902_FIRMWARE_WM }, { }, }; @@ -617,6 +619,8 @@ MODULE_FIRMWARE(MT7921_FIRMWARE_WM); MODULE_FIRMWARE(MT7921_ROM_PATCH); MODULE_FIRMWARE(MT7922_FIRMWARE_WM); MODULE_FIRMWARE(MT7922_ROM_PATCH); +MODULE_FIRMWARE(MT7902_FIRMWARE_WM); +MODULE_FIRMWARE(MT7902_ROM_PATCH); MODULE_AUTHOR("Sean Wang "); MODULE_AUTHOR("Lorenzo Bianconi "); MODULE_DESCRIPTION("MediaTek MT7921E (PCIe) wireless driver"); From 02b7a65719a00b5edea5b60b00ff85440a3f5d38 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Wed, 18 Feb 2026 18:40:07 -0600 Subject: [PATCH 074/134] wifi: mt76: mt7921: add MT7902 SDIO device support Register the MT7902 SDIO device ID in the mt7921 driver and add its corresponding firmware and ROM patch names. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260219004007.19733-11-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7921/sdio.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/sdio.c b/drivers/net/wireless/mediatek/mt76/mt7921/sdio.c index 3421e53dc948..9150f185716c 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/sdio.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/sdio.c @@ -19,6 +19,8 @@ static const struct sdio_device_id mt7921s_table[] = { { SDIO_DEVICE(SDIO_VENDOR_ID_MEDIATEK, 0x7901), .driver_data = (kernel_ulong_t)MT7921_FIRMWARE_WM }, + { SDIO_DEVICE(SDIO_VENDOR_ID_MEDIATEK, 0x7902), + .driver_data = (kernel_ulong_t)MT7902_FIRMWARE_WM }, { } /* Terminating entry */ }; @@ -317,6 +319,8 @@ static int mt7921s_resume(struct device *__dev) MODULE_DEVICE_TABLE(sdio, mt7921s_table); MODULE_FIRMWARE(MT7921_FIRMWARE_WM); MODULE_FIRMWARE(MT7921_ROM_PATCH); +MODULE_FIRMWARE(MT7902_FIRMWARE_WM); +MODULE_FIRMWARE(MT7902_ROM_PATCH); static DEFINE_SIMPLE_DEV_PM_OPS(mt7921s_pm_ops, mt7921s_suspend, mt7921s_resume); From 97b9f9831bf297f3ffa62018721601ed2736f2c3 Mon Sep 17 00:00:00 2001 From: Shayne Chen Date: Tue, 3 Feb 2026 23:55:29 +0800 Subject: [PATCH 075/134] wifi: mt76: mt7996: fix wrong DMAD length when using MAC TXP The struct mt76_connac_fw_txp is used for HIF TXP. Change to use the struct mt76_connac_hw_txp to fix the wrong DMAD length for MAC TXP. Fixes: cb6ebbdffef2 ("wifi: mt76: mt7996: support writing MAC TXD for AddBA Request") Signed-off-by: Shayne Chen Link: https://patch.msgid.link/20260203155532.1098290-1-shayne.chen@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index c6028fabd7d1..208ed20d0bf9 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -1100,10 +1100,10 @@ int mt7996_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr, * req */ if (le32_to_cpu(ptr[7]) & MT_TXD7_MAC_TXD) { - u32 val; + u32 val, mac_txp_size = sizeof(struct mt76_connac_hw_txp); ptr = (__le32 *)(txwi + MT_TXD_SIZE); - memset((void *)ptr, 0, sizeof(struct mt76_connac_fw_txp)); + memset((void *)ptr, 0, mac_txp_size); val = FIELD_PREP(MT_TXP0_TOKEN_ID0, id) | MT_TXP0_TOKEN_ID0_VALID_MASK; @@ -1122,6 +1122,8 @@ int mt7996_tx_prepare_skb(struct mt76_dev *mdev, void *txwi_ptr, tx_info->buf[1].addr >> 32); #endif ptr[3] = cpu_to_le32(val); + + tx_info->buf[0].len = MT_TXD_SIZE + mac_txp_size; } else { struct mt76_connac_txp_common *txp; From efbd5bf395f4e6b45a87f3835d4c2e28170c77c5 Mon Sep 17 00:00:00 2001 From: StanleyYP Wang Date: Tue, 3 Feb 2026 23:55:30 +0800 Subject: [PATCH 076/134] wifi: mt76: mt7996: fix struct mt7996_mcu_uni_event The cid field is defined as a two-byte value in the firmware. Fixes: 98686cd21624 ("wifi: mt76: mt7996: add driver for MediaTek Wi-Fi 7 (802.11be) devices") Signed-off-by: StanleyYP Wang Signed-off-by: Shayne Chen Link: https://patch.msgid.link/20260203155532.1098290-2-shayne.chen@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mcu.c | 2 +- drivers/net/wireless/mediatek/mt76/mt7996/mcu.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index 27713399c318..13182a69eec9 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -245,7 +245,7 @@ mt7996_mcu_parse_response(struct mt76_dev *mdev, int cmd, event = (struct mt7996_mcu_uni_event *)skb->data; ret = le32_to_cpu(event->status); /* skip invalid event */ - if (mcu_cmd != event->cid) + if (mcu_cmd != le16_to_cpu(event->cid)) ret = -EAGAIN; } else { skb_pull(skb, sizeof(struct mt7996_mcu_rxd)); diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h index 905dafccc316..39df13679779 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h @@ -25,8 +25,8 @@ struct mt7996_mcu_rxd { }; struct mt7996_mcu_uni_event { - u8 cid; - u8 __rsv[3]; + __le16 cid; + u8 __rsv[2]; __le32 status; /* 0: success, others: fail */ } __packed; From 169c83d3df95b57e787174454332e01eb1b823ed Mon Sep 17 00:00:00 2001 From: StanleyYP Wang Date: Tue, 3 Feb 2026 23:55:31 +0800 Subject: [PATCH 077/134] wifi: mt76: avoid to set ACK for MCU command if wait_resp is not set When wait_resp is not set but the ACK option is enabled in the MCU TXD, the ACK event is enqueued to the MCU event queue without being dequeued by the original MCU command request. Any orphaned ACK events will only be removed from the queue when another MCU command requests a response. Due to sequence index mismatches, these events are discarded one by one until a matching sequence index is found. However, if several MCU commands that do not require a response continue to fill up the event queue, there is a risk that when an MCU command with wait_resp enabled is issued, it may dequeue the wrong event skb, especially if the queue contains events with all possible sequence indices. Signed-off-by: StanleyYP Wang Signed-off-by: Shayne Chen Link: https://patch.msgid.link/20260203155532.1098290-3-shayne.chen@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mcu.c | 2 +- drivers/net/wireless/mediatek/mt76/mt7996/mcu.c | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mcu.c b/drivers/net/wireless/mediatek/mt76/mcu.c index 535c3d8a9cc0..cbfb3bbec503 100644 --- a/drivers/net/wireless/mediatek/mt76/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mcu.c @@ -98,7 +98,7 @@ int mt76_mcu_skb_send_and_get_msg(struct mt76_dev *dev, struct sk_buff *skb, /* orig skb might be needed for retry, mcu_skb_send_msg consumes it */ if (orig_skb) skb_get(orig_skb); - ret = dev->mcu_ops->mcu_skb_send_msg(dev, skb, cmd, &seq); + ret = dev->mcu_ops->mcu_skb_send_msg(dev, skb, cmd, wait_resp ? &seq : NULL); if (ret < 0) goto out; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index 13182a69eec9..ee0fb3c45ca2 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -325,13 +325,12 @@ mt7996_mcu_send_message(struct mt76_dev *mdev, struct sk_buff *skb, uni_txd->pkt_type = MCU_PKT_ID; uni_txd->seq = seq; - if (cmd & __MCU_CMD_FIELD_QUERY) - uni_txd->option = MCU_CMD_UNI_QUERY_ACK; - else - uni_txd->option = MCU_CMD_UNI_EXT_ACK; + uni_txd->option = MCU_CMD_UNI; + if (!(cmd & __MCU_CMD_FIELD_QUERY)) + uni_txd->option |= MCU_CMD_SET; - if (mcu_cmd == MCU_UNI_CMD_SDO) - uni_txd->option &= ~MCU_CMD_ACK; + if (wait_seq) + uni_txd->option |= MCU_CMD_ACK; if ((cmd & __MCU_CMD_FIELD_WA) && (cmd & __MCU_CMD_FIELD_WM)) uni_txd->s2d_index = MCU_S2D_H2CN; From 1f9017d19db38ad2cb9bedb5b078f6f4f60afa94 Mon Sep 17 00:00:00 2001 From: StanleyYP Wang Date: Tue, 3 Feb 2026 23:55:32 +0800 Subject: [PATCH 078/134] wifi: mt76: mt7996: fix queue pause after scan due to wrong channel switch reason Previously, we used the IEEE80211_CONF_IDLE flag to avoid setting the parking channel with the CH_SWITCH_NORMAL reason, which could trigger TX emission before bootup CAC. However, we found that this flag can be set after triggering scanning on a connected station interface, and the reason CH_SWITCH_SCAN_BYPASS_DPD will be used when switching back to the operating channel, which makes the firmware failed to resume paused AC queues. Seems that we should avoid relying on this flag after switching to single multi-radio architecture. Instead, use the existence of chanctx as the condition. Signed-off-by: StanleyYP Wang Signed-off-by: Shayne Chen Link: https://patch.msgid.link/20260203155532.1098290-4-shayne.chen@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mcu.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index ee0fb3c45ca2..c632abe54707 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -3929,8 +3929,7 @@ int mt7996_mcu_set_chan_info(struct mt7996_phy *phy, u16 tag) if (phy->mt76->hw->conf.flags & IEEE80211_CONF_MONITOR) req.switch_reason = CH_SWITCH_NORMAL; - else if (phy->mt76->offchannel || - phy->mt76->hw->conf.flags & IEEE80211_CONF_IDLE) + else if (phy->mt76->offchannel || !phy->mt76->chanctx) req.switch_reason = CH_SWITCH_SCAN_BYPASS_DPD; else if (!cfg80211_reg_can_beacon(phy->mt76->hw->wiphy, chandef, NL80211_IFTYPE_AP)) From 964f870e090e9c88a41e2890333421204cc0bdf4 Mon Sep 17 00:00:00 2001 From: David Bauer Date: Fri, 30 Jan 2026 00:23:20 +0100 Subject: [PATCH 079/134] wifi: mt76: don't return TXQ when exceeding max non-AQL packets mt76_txq_send_burst does check if the number of non-AQL frames exceeds the maximum. In this case the queue is returned to ieee80211_return_txq when iterating over the scheduled TXQs in mt76_txq_schedule_list. This has the effect of inserting said TXQ at the head of the list. This means the loop will get the same TXQ again, which will terminate the scheduling round. TXQs following in the list thus never get scheduled for transmission. This can manifest in high latency low throughput or broken connections for said STAs. Check if the non-AQL packet count exceeds the limit and not return the TXQ in this case. Schedule all TXQs for the STA in case the non-AQL limit can be satisfied again. Signed-off-by: David Bauer Link: https://patch.msgid.link/20260129232321.276575-1-mail@david-bauer.net Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/tx.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/tx.c b/drivers/net/wireless/mediatek/mt76/tx.c index 9ec6d0b53a84..0753acf2eccb 100644 --- a/drivers/net/wireless/mediatek/mt76/tx.c +++ b/drivers/net/wireless/mediatek/mt76/tx.c @@ -227,7 +227,9 @@ mt76_tx_check_non_aql(struct mt76_dev *dev, struct mt76_wcid *wcid, struct sk_buff *skb) { struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb); + struct ieee80211_sta *sta; int pending; + int i; if (!wcid || info->tx_time_est) return; @@ -235,6 +237,17 @@ mt76_tx_check_non_aql(struct mt76_dev *dev, struct mt76_wcid *wcid, pending = atomic_dec_return(&wcid->non_aql_packets); if (pending < 0) atomic_cmpxchg(&wcid->non_aql_packets, pending, 0); + + sta = wcid_to_sta(wcid); + if (!sta || pending != MT_MAX_NON_AQL_PKT - 1) + return; + + for (i = 0; i < ARRAY_SIZE(sta->txq); i++) { + if (!sta->txq[i]) + continue; + + ieee80211_schedule_txq(dev->hw, sta->txq[i]); + } } void __mt76_tx_complete_skb(struct mt76_dev *dev, u16 wcid_idx, struct sk_buff *skb, @@ -542,6 +555,9 @@ mt76_txq_schedule_list(struct mt76_phy *phy, enum mt76_txq_id qid) if (!wcid || test_bit(MT_WCID_FLAG_PS, &wcid->flags)) continue; + if (atomic_read(&wcid->non_aql_packets) >= MT_MAX_NON_AQL_PKT) + continue; + phy = mt76_dev_phy(dev, wcid->phy_idx); if (test_bit(MT76_RESET, &phy->state) || phy->offchannel) continue; From 1146d0946b5358fad24812bd39d68f31cd40cc34 Mon Sep 17 00:00:00 2001 From: Duoming Zhou Date: Fri, 30 Jan 2026 22:57:59 +0800 Subject: [PATCH 080/134] wifi: mt76: mt7915: fix use-after-free bugs in mt7915_mac_dump_work() When the mt7915 pci chip is detaching, the mt7915_crash_data is released in mt7915_coredump_unregister(). However, the work item dump_work may still be running or pending, leading to UAF bugs when the already freed crash_data is dereferenced again in mt7915_mac_dump_work(). The race condition can occur as follows: CPU 0 (removal path) | CPU 1 (workqueue) mt7915_pci_remove() | mt7915_sys_recovery_set() mt7915_unregister_device() | mt7915_reset() mt7915_coredump_unregister() | queue_work() vfree(dev->coredump.crash_data) | mt7915_mac_dump_work() | crash_data-> // UAF Fix this by ensuring dump_work is properly canceled before the crash_data is deallocated. Add cancel_work_sync() in mt7915_unregister_device() to synchronize with any pending or executing dump work. Fixes: 4dbcb9125cc3 ("wifi: mt76: mt7915: enable coredump support") Signed-off-by: Duoming Zhou Link: https://patch.msgid.link/20260130145759.84272-1-duoming@zju.edu.cn Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7915/init.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/init.c b/drivers/net/wireless/mediatek/mt76/mt7915/init.c index 22443cbc74ad..250c2d2479b0 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/init.c @@ -1294,6 +1294,7 @@ int mt7915_register_device(struct mt7915_dev *dev) void mt7915_unregister_device(struct mt7915_dev *dev) { + cancel_work_sync(&dev->dump_work); mt7915_unregister_ext_phy(dev); mt7915_coredump_unregister(dev); mt7915_unregister_thermal(&dev->phy); From c8f62f73bbced3a79894655bdb0b625462d956fc Mon Sep 17 00:00:00 2001 From: Duoming Zhou Date: Sat, 31 Jan 2026 10:47:31 +0800 Subject: [PATCH 081/134] wifi: mt76: mt7996: fix use-after-free bugs in mt7996_mac_dump_work() When the mt7996 pci chip is detaching, the mt7996_crash_data is released in mt7996_coredump_unregister(). However, the work item dump_work may still be running or pending, leading to UAF bugs when the already freed crash_data is dereferenced again in mt7996_mac_dump_work(). The race condition can occur as follows: CPU 0 (removal path) | CPU 1 (workqueue) mt7996_pci_remove() | mt7996_sys_recovery_set() mt7996_unregister_device() | mt7996_reset() mt7996_coredump_unregister() | queue_work() vfree(dev->coredump.crash_data) | mt7996_mac_dump_work() | crash_data-> // UAF Fix this by ensuring dump_work is properly canceled before the crash_data is deallocated. Add cancel_work_sync() in mt7996_unregister_device() to synchronize with any pending or executing dump work. Fixes: 878161d5d4a4 ("wifi: mt76: mt7996: enable coredump support") Signed-off-by: Duoming Zhou Link: https://patch.msgid.link/20260131024731.18741-1-duoming@zju.edu.cn Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/init.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index 3b4f808b968c..29bce7cffbb5 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -1773,6 +1773,7 @@ int mt7996_register_device(struct mt7996_dev *dev) void mt7996_unregister_device(struct mt7996_dev *dev) { + cancel_work_sync(&dev->dump_work); cancel_work_sync(&dev->wed_rro.work); mt7996_unregister_phy(mt7996_phy3(dev)); mt7996_unregister_phy(mt7996_phy2(dev)); From c9ce833d7891804f618c3c8349d9c96e4fe62774 Mon Sep 17 00:00:00 2001 From: MeiChia Chiu Date: Tue, 3 Feb 2026 09:32:02 +0100 Subject: [PATCH 082/134] wifi: mt76: mt7996: Add eMLSR support Implement set_eml_op_mode mac80211 callback in order to introduce eMLSR support. Tested-by: Christian Marangi Signed-off-by: MeiChia Chiu Co-developed-by: Lorenzo Bianconi Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260203-mt7996-emlsr-v1-1-38ffb3d5110c@kernel.org Signed-off-by: Felix Fietkau --- .../wireless/mediatek/mt76/mt76_connac_mcu.h | 9 +++ .../net/wireless/mediatek/mt76/mt7996/main.c | 16 ++++++ .../net/wireless/mediatek/mt76/mt7996/mcu.c | 55 +++++++++++++++++++ .../wireless/mediatek/mt76/mt7996/mt7996.h | 4 ++ 4 files changed, 84 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h index 0809318c1ec2..fd9cf9c0c32f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h @@ -628,6 +628,13 @@ struct sta_rec_tx_proc { __le32 flag; } __packed; +struct sta_rec_eml_op { + __le16 tag; + __le16 len; + u8 link_bitmap; + u8 link_ant_num[3]; +} __packed; + /* wtbl_rec */ struct wtbl_req_hdr { @@ -796,6 +803,7 @@ struct wtbl_raw { sizeof(struct sta_rec_he_6g_capa) + \ sizeof(struct sta_rec_pn_info) + \ sizeof(struct sta_rec_tx_proc) + \ + sizeof(struct sta_rec_eml_op) + \ sizeof(struct tlv) + \ MT76_CONNAC_WTBL_UPDATE_MAX_SIZE) @@ -832,6 +840,7 @@ enum { STA_REC_PN_INFO = 0x26, STA_REC_KEY_V3 = 0x27, STA_REC_HDRT = 0x28, + STA_REC_EML_OP = 0x29, STA_REC_HDR_TRANS = 0x2B, STA_REC_MAX_NUM }; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index c0fae7aec1ae..c6d14f09fd10 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -2366,6 +2366,21 @@ mt7996_reconfig_complete(struct ieee80211_hw *hw, MT7996_WATCHDOG_TIME); } +static int +mt7996_set_eml_op_mode(struct ieee80211_hw *hw, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_eml_params *eml_params) +{ + struct mt7996_dev *dev = mt7996_hw_dev(hw); + int ret; + + mutex_lock(&dev->mt76.mutex); + ret = mt7996_mcu_set_emlsr_mode(dev, vif, sta, eml_params); + mutex_unlock(&dev->mt76.mutex); + + return ret; +} + const struct ieee80211_ops mt7996_ops = { .add_chanctx = mt76_add_chanctx, .remove_chanctx = mt76_remove_chanctx, @@ -2429,4 +2444,5 @@ const struct ieee80211_ops mt7996_ops = { .change_vif_links = mt7996_change_vif_links, .change_sta_links = mt7996_mac_sta_change_links, .reconfig_complete = mt7996_reconfig_complete, + .set_eml_op_mode = mt7996_set_eml_op_mode, }; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index c632abe54707..8e06f7fe479c 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -1304,6 +1304,61 @@ int mt7996_mcu_set_protection(struct mt7996_phy *phy, struct mt7996_vif_link *li MCU_WM_UNI_CMD(BSS_INFO_UPDATE), true); } +int mt7996_mcu_set_emlsr_mode(struct mt7996_dev *dev, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_eml_params *eml_params) +{ + struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; + struct mt7996_sta_link *msta_link; + struct sta_rec_eml_op *eml_op; + struct mt7996_vif_link *link; + struct sk_buff *skb; + struct tlv *tlv; + + msta_link = mt76_dereference(msta->link[eml_params->link_id], + &dev->mt76); + if (!msta_link) + return -EINVAL; + + link = mt7996_vif_link(dev, vif, eml_params->link_id); + if (!link) + return -EINVAL; + + skb = __mt76_connac_mcu_alloc_sta_req(&dev->mt76, &link->mt76, + &msta_link->wcid, + MT7996_STA_UPDATE_MAX_SIZE); + if (IS_ERR(skb)) + return PTR_ERR(skb); + + tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_EML_OP, sizeof(*eml_op)); + eml_op = (struct sta_rec_eml_op *)tlv; + eml_op->link_bitmap = 0; + + if (eml_params->control & IEEE80211_EML_CTRL_EMLSR_MODE) { + unsigned long link_bitmap = eml_params->link_bitmap; + unsigned int link_id; + + for_each_set_bit(link_id, &link_bitmap, + IEEE80211_MLD_MAX_NUM_LINKS) { + struct mt76_phy *mphy; + + link = mt7996_vif_link(dev, vif, link_id); + if (!link) + continue; + + mphy = mt76_vif_link_phy(&link->mt76); + if (!mphy) + continue; + + eml_op->link_bitmap |= BIT(mphy->band_idx); + } + } + + return mt76_mcu_skb_send_msg(&dev->mt76, skb, + MCU_WMWA_UNI_CMD(STA_REC_UPDATE), true); +} + int mt7996_mcu_set_timing(struct mt7996_phy *phy, struct ieee80211_vif *vif, struct ieee80211_bss_conf *link_conf) { diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index 5f574ebe81cc..ee3564c0115a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -901,6 +901,10 @@ int mt7996_mcu_wtbl_update_hdr_trans(struct mt7996_dev *dev, struct mt7996_vif_link *link, struct mt7996_sta_link *msta_link); int mt7996_mcu_cp_support(struct mt7996_dev *dev, u8 mode); +int mt7996_mcu_set_emlsr_mode(struct mt7996_dev *dev, + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct ieee80211_eml_params *eml_params); #ifdef CONFIG_MAC80211_DEBUGFS void mt7996_sta_add_debugfs(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_sta *sta, struct dentry *dir); From 947d63d8cd3b03c7be16875ca90273edbdbe7ce5 Mon Sep 17 00:00:00 2001 From: Ryder Lee Date: Fri, 13 Feb 2026 00:00:29 -0800 Subject: [PATCH 083/134] wifi: mt76: mt7996: Disable Rx hdr_trans in monitor mode Ensure raw frames are captured without header modification. Signed-off-by: Ryder Lee Link: https://patch.msgid.link/04008426d6cd5de3995beefb98f9d13f35526c25.1770969275.git.ryder.lee@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/main.c | 2 ++ drivers/net/wireless/mediatek/mt76/mt7996/regs.h | 3 +++ 2 files changed, 5 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index c6d14f09fd10..06f4c653ed67 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -482,6 +482,8 @@ static void mt7996_set_monitor(struct mt7996_phy *phy, bool enabled) mt76_rmw_field(dev, MT_DMA_DCR0(phy->mt76->band_idx), MT_DMA_DCR0_RXD_G5_EN, enabled); + mt76_rmw_field(dev, MT_MDP_DCR0, + MT_MDP_DCR0_RX_HDR_TRANS_EN, !enabled); mt7996_phy_set_rxfilter(phy); mt7996_mcu_set_sniffer_mode(phy, enabled); } diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/regs.h b/drivers/net/wireless/mediatek/mt76/mt7996/regs.h index e48e0e575b64..393faae2d52b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/regs.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/regs.h @@ -159,6 +159,9 @@ enum offs_rev { #define MT_MDP_BASE 0x820cc000 #define MT_MDP(ofs) (MT_MDP_BASE + (ofs)) +#define MT_MDP_DCR0 MT_MDP(0x800) +#define MT_MDP_DCR0_RX_HDR_TRANS_EN BIT(19) + #define MT_MDP_DCR2 MT_MDP(0x8e8) #define MT_MDP_DCR2_RX_TRANS_SHORT BIT(2) From 3dc0c40d7806c72cfe88cf4e1e2650c1673f9db4 Mon Sep 17 00:00:00 2001 From: Michael Lo Date: Wed, 11 Feb 2026 17:50:25 +0800 Subject: [PATCH 084/134] wifi: mt76: mt7921: fix 6GHz regulatory update on connection Call mt7921_regd_update() instead of mt7921_mcu_set_clc() when setting the 6GHz power type after connection, so that regulatory limits and SAR power are also applied. Fixes: 51ba0e3a15eb ("wifi: mt76: mt7921: add 6GHz power type support for clc") Signed-off-by: Michael Lo Link: https://patch.msgid.link/20260211095025.2415624-1-leon.yen@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7921/main.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/main.c b/drivers/net/wireless/mediatek/mt76/mt7921/main.c index 42b9514e04e7..3d74fabe7408 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7921/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7921/main.c @@ -800,7 +800,8 @@ mt7921_regd_set_6ghz_power_type(struct ieee80211_vif *vif, bool is_add) } out: - mt7921_mcu_set_clc(dev, dev->mt76.alpha2, dev->country_ie_env); + if (vif->bss_conf.chanreq.oper.chan->band == NL80211_BAND_6GHZ) + mt7921_regd_update(dev); } int mt7921_mac_sta_add(struct mt76_dev *mdev, struct ieee80211_vif *vif, From f0168f2f9a1eca55d3ae09d8250b94e82b67cac3 Mon Sep 17 00:00:00 2001 From: Ziyi Guo Date: Sat, 31 Jan 2026 03:52:10 +0000 Subject: [PATCH 085/134] wifi: mt76: add missing lock protection in mt76_sta_state for sta_event callback mt76_sta_state() calls the sta_event callback without holding dev->mutex. However, mt7915_mac_sta_event() (MT7915 implementation of this callback) calls mt7915_mac_twt_teardown_flow() which has lockdep_assert_held(&dev->mt76.mutex) indicating that callers must hold this lock. The locking pattern in mt76_sta_state() is inconsistent: - mt76_sta_add() acquires dev->mutex before calling dev->drv->sta_add - mt76_sta_remove() acquires dev->mutex before calling __mt76_sta_remove - But sta_event callback is called without acquiring the lock Add mutex_lock()/mutex_unlock() around the mt7915_mac_twt_teardown_flow invocation to fix the missing lock protection and maintain consistency with the existing locking pattern. Signed-off-by: Ziyi Guo Link: https://patch.msgid.link/20260131035210.2198259-1-n7l8m4@u.northwestern.edu Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7915/main.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/main.c b/drivers/net/wireless/mediatek/mt76/mt7915/main.c index 0892291616ea..e1d83052aa6d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7915/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7915/main.c @@ -852,8 +852,10 @@ int mt7915_mac_sta_event(struct mt76_dev *mdev, struct ieee80211_vif *vif, return mt7915_mcu_add_sta(dev, vif, sta, CONN_STATE_PORT_SECURE, false); case MT76_STA_EVENT_DISASSOC: + mutex_lock(&dev->mt76.mutex); for (i = 0; i < ARRAY_SIZE(msta->twt.flow); i++) mt7915_mac_twt_teardown_flow(dev, msta, i); + mutex_unlock(&dev->mt76.mutex); mt7915_mcu_add_sta(dev, vif, sta, CONN_STATE_DISCONNECT, false); msta->wcid.sta_disabled = 1; From 62e037aa8cf5a69b7ea63336705a35c897b9db2b Mon Sep 17 00:00:00 2001 From: Quan Zhou Date: Wed, 25 Feb 2026 17:47:22 +0800 Subject: [PATCH 086/134] wifi: mt76: mt7925: fix incorrect TLV length in CLC command The previous implementation of __mt7925_mcu_set_clc() set the TLV length field (.len) incorrectly during CLC command construction. The length was initialized as sizeof(req) - 4, regardless of the actual segment length. This could cause the WiFi firmware to misinterpret the command payload, resulting in command execution errors. This patch moves the TLV length assignment to after the segment is selected, and sets .len to sizeof(req) + seg->len - 4, matching the actual command content. This ensures the firmware receives the correct TLV length and parses the command properly. Fixes: c948b5da6bbe ("wifi: mt76: mt7925: add Mediatek Wi-Fi7 driver for mt7925 chips") Cc: stable@vger.kernel.org Signed-off-by: Quan Zhou Acked-by: Sean Wang Link: https://patch.msgid.link/f56ae0e705774dfa8aab3b99e5bbdc92cd93523e.1772011204.git.quan.zhou@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/mcu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c index 1379bf6a26b5..abcdd0e0b3b5 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c @@ -3380,7 +3380,6 @@ __mt7925_mcu_set_clc(struct mt792x_dev *dev, u8 *alpha2, u8 rsvd[64]; } __packed req = { .tag = cpu_to_le16(0x3), - .len = cpu_to_le16(sizeof(req) - 4), .idx = idx, .env = env_cap, @@ -3409,6 +3408,7 @@ __mt7925_mcu_set_clc(struct mt792x_dev *dev, u8 *alpha2, memcpy(req.type, rule->type, 2); req.size = cpu_to_le16(seg->len); + req.len = cpu_to_le16(sizeof(req) + seg->len - 4); dev->phy.clc_chan_conf = clc->ver == 1 ? 0xff : rule->flag; skb = __mt76_mcu_msg_alloc(&dev->mt76, &req, le16_to_cpu(req.size) + sizeof(req), From c0a47ffc4caaf5161955add553322112c3a211b0 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Sun, 28 Sep 2025 18:27:01 +0200 Subject: [PATCH 087/134] wifi: mt76: mt7996: Add missing CHANCTX_STA_CSA property Enable missing CHANCTX_STA_CSA property required for MLO. Fixes: f5160304d57c ("wifi: mt76: mt7996: Enable MLO support for client interfaces") Signed-off-by: Lorenzo Bianconi Reviewed-by: AngeloGioacchino Del Regno Link: https://patch.msgid.link/20250928-mt7996_chanctx_sta_csa-v1-1-82e455185990@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/init.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index 29bce7cffbb5..5aaa93767109 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -536,6 +536,7 @@ mt7996_init_wiphy(struct ieee80211_hw *hw, struct mtk_wed_device *wed) ieee80211_hw_set(hw, SUPPORTS_RX_DECAP_OFFLOAD); ieee80211_hw_set(hw, NO_VIRTUAL_MONITOR); ieee80211_hw_set(hw, SUPPORTS_MULTI_BSSID); + ieee80211_hw_set(hw, CHANCTX_STA_CSA); hw->max_tx_fragments = 4; wiphy->txq_memory_limit = 32 << 20; /* 32 MiB */ From 569ce4340268915911fc356ec9ad27e92fb82289 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Fri, 6 Mar 2026 11:27:52 +0100 Subject: [PATCH 088/134] wifi: mt76: mt7996: Remove link pointer dependency in mt7996_mac_sta_remove_links() Remove link pointer dependency in mt7996_mac_sta_remove_links routine to get the mt7996_phy pointer since the link can be already offchannel running mt7996_mac_sta_remove_links(). Rely on __mt7996_phy routine instead. Fixes: 344dd6a4c919 ("wifi: mt76: mt7996: Move num_sta accounting in mt7996_mac_sta_{add,remove}_links") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260306-mt7996-deflink-lookup-link-remove-v1-1-7162b332873c@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/main.c | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 06f4c653ed67..f063888b9980 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1097,8 +1097,7 @@ mt7996_mac_sta_remove_links(struct mt7996_dev *dev, struct ieee80211_vif *vif, for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) { struct mt7996_sta_link *msta_link = NULL; - struct mt7996_vif_link *link; - struct mt76_phy *mphy; + struct mt7996_phy *phy; msta_link = rcu_replace_pointer(msta->link[link_id], msta_link, lockdep_is_held(&mdev->mutex)); @@ -1107,17 +1106,12 @@ mt7996_mac_sta_remove_links(struct mt7996_dev *dev, struct ieee80211_vif *vif, mt7996_mac_wtbl_update(dev, msta_link->wcid.idx, MT_WTBL_UPDATE_ADM_COUNT_CLEAR); - mt7996_mac_sta_deinit_link(dev, msta_link); - link = mt7996_vif_link(dev, vif, link_id); - if (!link) - continue; - mphy = mt76_vif_link_phy(&link->mt76); - if (!mphy) - continue; + phy = __mt7996_phy(dev, msta_link->wcid.phy_idx); + if (phy) + phy->mt76->num_sta--; - mphy->num_sta--; if (msta->deflink_id == link_id) { msta->deflink_id = IEEE80211_LINK_UNSPECIFIED; if (msta->seclink_id == link_id) { From 5806c91a3f0d21d233f8c386c892e1ffaf64d7b2 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Fri, 6 Mar 2026 11:27:55 +0100 Subject: [PATCH 089/134] wifi: mt76: mt7996: Remove unnecessary phy filed in mt7996_vif_link struct Remove unnecessary phy pointer in mt7996_vif_link struct and rely on mt7996_vif_link_phy() utility routine. Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260306-mt7996-deflink-lookup-link-remove-v1-4-7162b332873c@kernel.org Signed-off-by: Felix Fietkau --- .../wireless/mediatek/mt76/mt7996/debugfs.c | 14 +++- .../net/wireless/mediatek/mt76/mt7996/mac.c | 7 +- .../net/wireless/mediatek/mt76/mt7996/main.c | 43 +++++++---- .../net/wireless/mediatek/mt76/mt7996/mcu.c | 74 +++++++++++++------ .../wireless/mediatek/mt76/mt7996/mt7996.h | 2 - 5 files changed, 101 insertions(+), 39 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/debugfs.c b/drivers/net/wireless/mediatek/mt76/mt7996/debugfs.c index 76d623b2cafb..6cc63f87b222 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/debugfs.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/debugfs.c @@ -626,13 +626,18 @@ mt7996_sta_hw_queue_read(void *data, struct ieee80211_sta *sta) { struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; struct mt7996_vif *mvif = msta->vif; - struct mt7996_dev *dev = mvif->deflink.phy->dev; + struct mt7996_phy *phy = mt7996_vif_link_phy(&mvif->deflink); struct ieee80211_link_sta *link_sta; struct seq_file *s = data; struct ieee80211_vif *vif; + struct mt7996_dev *dev; unsigned int link_id; + if (!phy) + return; + vif = container_of((void *)mvif, struct ieee80211_vif, drv_priv); + dev = phy->dev; rcu_read_lock(); @@ -979,13 +984,17 @@ static ssize_t mt7996_link_sta_fixed_rate_set(struct file *file, #define LONG_PREAMBLE 1 struct ieee80211_link_sta *link_sta = file->private_data; struct mt7996_sta *msta = (struct mt7996_sta *)link_sta->sta->drv_priv; - struct mt7996_dev *dev = msta->vif->deflink.phy->dev; + struct mt7996_phy *link_phy = mt7996_vif_link_phy(&msta->vif->deflink); struct mt7996_sta_link *msta_link; struct ra_rate phy = {}; + struct mt7996_dev *dev; char buf[100]; int ret; u16 gi, ltf; + if (!link_phy) + return -EINVAL; + if (count >= sizeof(buf)) return -EINVAL; @@ -1008,6 +1017,7 @@ static ssize_t mt7996_link_sta_fixed_rate_set(struct file *file, * spe - off: 0, on: 1 * ltf - 1xltf: 0, 2xltf: 1, 4xltf: 2 */ + dev = link_phy->dev; if (sscanf(buf, "%hhu %hhu %hhu %hhu %hu %hhu %hhu %hhu %hhu %hu", &phy.mode, &phy.bw, &phy.mcs, &phy.nss, &gi, &phy.preamble, &phy.stbc, &phy.ldpc, &phy.spe, <f) != 10) { diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index 208ed20d0bf9..7587e7b4e8fe 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -2167,9 +2167,14 @@ mt7996_update_vif_beacon(void *priv, u8 *mac, struct ieee80211_vif *vif) for_each_vif_active_link(vif, link_conf, link_id) { struct mt7996_vif_link *link; + struct mt7996_phy *link_phy; link = mt7996_vif_link(dev, vif, link_id); - if (!link || link->phy != phy) + if (!link) + continue; + + link_phy = mt7996_vif_link_phy(link); + if (link_phy != phy) continue; mt7996_mcu_add_beacon(dev->mt76.hw, vif, link_conf, diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index f063888b9980..8ecbc0511848 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -239,10 +239,13 @@ mt7996_set_hw_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, link_conf = &vif->bss_conf; if (cmd == SET_KEY && !sta && !link->mt76.cipher) { + struct mt7996_phy *phy = mt7996_vif_link_phy(link); + link->mt76.cipher = mt76_connac_mcu_get_cipher(key->cipher); - mt7996_mcu_add_bss_info(link->phy, vif, link_conf, - &link->mt76, msta_link, true); + if (phy) + mt7996_mcu_add_bss_info(phy, vif, link_conf, + &link->mt76, msta_link, true); } if (cmd == SET_KEY) @@ -316,7 +319,6 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, return -ENOSPC; link->mld_idx = mld_idx; - link->phy = phy; mlink->omac_idx = idx; mlink->band_idx = band_idx; mlink->wmm_idx = vif->type == NL80211_IFTYPE_AP ? 0 : 3; @@ -823,15 +825,17 @@ mt7996_vif_cfg_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif, for_each_vif_active_link(vif, link_conf, link_id) { struct mt7996_vif_link *link; + struct mt7996_phy *phy; link = mt7996_vif_link(dev, vif, link_id); if (!link) continue; - if (!link->phy) + phy = mt7996_vif_link_phy(link); + if (!phy) continue; - mt7996_mcu_add_bss_info(link->phy, vif, link_conf, + mt7996_mcu_add_bss_info(phy, vif, link_conf, &link->mt76, &link->msta_link, true); mt7996_mcu_add_sta(dev, link_conf, NULL, link, NULL, @@ -950,10 +954,15 @@ mt7996_channel_switch_beacon(struct ieee80211_hw *hw, mutex_lock(&dev->mt76.mutex); for_each_vif_active_link(vif, link_conf, link_id) { - struct mt7996_vif_link *link = - mt7996_vif_link(dev, vif, link_id); + struct mt7996_vif_link *link; + struct mt7996_phy *link_phy; - if (!link || link->phy != phy) + link = mt7996_vif_link(dev, vif, link_id); + if (!link) + continue; + + link_phy = mt7996_vif_link_phy(link); + if (link_phy != phy) continue; /* Reset beacon when channel switch triggered during CAC to let @@ -1024,10 +1033,13 @@ mt7996_mac_sta_init_link(struct mt7996_dev *dev, { struct ieee80211_sta *sta = link_sta->sta; struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; - struct mt7996_phy *phy = link->phy; + struct mt7996_phy *phy = mt7996_vif_link_phy(link); struct mt7996_sta_link *msta_link; int idx; + if (!phy) + return -EINVAL; + idx = mt76_wcid_alloc(dev->mt76.wcid_mask, MT7996_WTBL_STA); if (idx < 0) return -ENOSPC; @@ -1572,8 +1584,8 @@ mt7996_get_stats(struct ieee80211_hw *hw, u64 __mt7996_get_tsf(struct ieee80211_hw *hw, struct mt7996_vif_link *link) { + struct mt7996_phy *phy = mt7996_vif_link_phy(link); struct mt7996_dev *dev = mt7996_hw_dev(hw); - struct mt7996_phy *phy = link->phy; union { u64 t64; u32 t32[2]; @@ -1632,7 +1644,7 @@ mt7996_set_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif, n = link->mt76.omac_idx > HW_BSSID_MAX ? HW_BSSID_0 : link->mt76.omac_idx; - phy = link->phy; + phy = mt7996_vif_link_phy(link); if (!phy) goto unlock; @@ -1666,7 +1678,7 @@ mt7996_offset_tsf(struct ieee80211_hw *hw, struct ieee80211_vif *vif, if (!link) goto unlock; - phy = link->phy; + phy = mt7996_vif_link_phy(link); if (!phy) goto unlock; @@ -1796,9 +1808,14 @@ static void mt7996_link_rate_ctrl_update(void *data, struct mt7996_sta_link *msta_link) { struct mt7996_sta *msta = msta_link->sta; - struct mt7996_dev *dev = msta->vif->deflink.phy->dev; + struct mt7996_phy *phy = mt7996_vif_link_phy(&msta->vif->deflink); + struct mt7996_dev *dev; u32 *changed = data; + if (!phy) + return; + + dev = phy->dev; spin_lock_bh(&dev->mt76.sta_poll_lock); msta_link->changed |= *changed; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index 8e06f7fe479c..2a50d0758d9c 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -128,9 +128,16 @@ mt7996_mcu_set_sta_he_mcs(struct ieee80211_link_sta *link_sta, struct mt7996_vif_link *link, __le16 *he_mcs, u16 mcs_map) { + struct mt76_phy *mphy = mt76_vif_link_phy(&link->mt76); int nss, max_nss = link_sta->rx_nss > 3 ? 4 : link_sta->rx_nss; - enum nl80211_band band = link->phy->mt76->chandef.chan->band; - const u16 *mask = link->bitrate_mask.control[band].he_mcs; + enum nl80211_band band; + const u16 *mask; + + if (!mphy) + return; + + band = mphy->chandef.chan->band; + mask = link->bitrate_mask.control[band].he_mcs; for (nss = 0; nss < max_nss; nss++) { int mcs; @@ -1968,9 +1975,8 @@ mt7996_mcu_sta_bfer_tlv(struct mt7996_dev *dev, struct sk_buff *skb, #define EBF_MODE BIT(0) #define IBF_MODE BIT(1) #define BF_MAT_ORDER 4 + struct mt7996_phy *phy = mt7996_vif_link_phy(link); struct ieee80211_vif *vif = link_conf->vif; - struct mt7996_phy *phy = link->phy; - int tx_ant = hweight16(phy->mt76->chainmask) - 1; struct sta_rec_bf *bf; struct tlv *tlv; static const u8 matrix[BF_MAT_ORDER][BF_MAT_ORDER] = { @@ -1979,8 +1985,12 @@ mt7996_mcu_sta_bfer_tlv(struct mt7996_dev *dev, struct sk_buff *skb, {2, 4, 4, 0}, /* 3x1, 3x2, 3x3, 3x4 */ {3, 5, 6, 0} /* 4x1, 4x2, 4x3, 4x4 */ }; + int tx_ant; bool ebf; + if (!phy) + return; + if (!(link_sta->ht_cap.ht_supported || link_sta->he_cap.has_he)) return; @@ -1996,17 +2006,18 @@ mt7996_mcu_sta_bfer_tlv(struct mt7996_dev *dev, struct sk_buff *skb, * ht: iBF only, since mac80211 lacks of eBF support */ if (link_sta->eht_cap.has_eht) - mt7996_mcu_sta_bfer_eht(link_sta, vif, link->phy, bf, ebf); + mt7996_mcu_sta_bfer_eht(link_sta, vif, phy, bf, ebf); else if (link_sta->he_cap.has_he) - mt7996_mcu_sta_bfer_he(link_sta, vif, link->phy, bf, ebf); + mt7996_mcu_sta_bfer_he(link_sta, vif, phy, bf, ebf); else if (link_sta->vht_cap.vht_supported) - mt7996_mcu_sta_bfer_vht(link_sta, link->phy, bf, ebf); + mt7996_mcu_sta_bfer_vht(link_sta, phy, bf, ebf); else if (link_sta->ht_cap.ht_supported) - mt7996_mcu_sta_bfer_ht(link_sta, link->phy, bf, ebf); + mt7996_mcu_sta_bfer_ht(link_sta, phy, bf, ebf); else return; bf->bf_cap = ebf ? EBF_MODE : (dev->ibf ? IBF_MODE : 0); + tx_ant = hweight16(phy->mt76->chainmask) - 1; if (is_mt7992(&dev->mt76) && tx_ant == 4) bf->bf_cap |= IBF_MODE; @@ -2038,11 +2049,14 @@ mt7996_mcu_sta_bfee_tlv(struct mt7996_dev *dev, struct sk_buff *skb, struct ieee80211_link_sta *link_sta, struct mt7996_vif_link *link) { - struct mt7996_phy *phy = link->phy; - int tx_ant = hweight8(phy->mt76->antenna_mask) - 1; + struct mt7996_phy *phy = mt7996_vif_link_phy(link); struct sta_rec_bfee *bfee; struct tlv *tlv; u8 nrow = 0; + int tx_ant; + + if (!phy) + return; if (!(link_sta->vht_cap.vht_supported || link_sta->he_cap.has_he)) return; @@ -2066,6 +2080,7 @@ mt7996_mcu_sta_bfee_tlv(struct mt7996_dev *dev, struct sk_buff *skb, } /* reply with identity matrix to avoid 2x2 BF negative gain */ + tx_ant = hweight8(phy->mt76->antenna_mask) - 1; bfee->fb_identity_matrix = (nrow == 1 && tx_ant == 2); } @@ -2249,6 +2264,7 @@ mt7996_mcu_add_rate_ctrl_fixed(struct mt7996_dev *dev, struct mt7996_sta *msta, struct ieee80211_sta *sta; int ret, nrates = 0, idx; enum nl80211_band band; + struct mt76_phy *mphy; bool has_he; #define __sta_phy_bitrate_mask_check(_mcs, _gi, _ht, _he) \ @@ -2282,7 +2298,11 @@ mt7996_mcu_add_rate_ctrl_fixed(struct mt7996_dev *dev, struct mt7996_sta *msta, if (!link_sta) goto error_unlock; - band = link->phy->mt76->chandef.chan->band; + mphy = mt76_vif_link_phy(&link->mt76); + if (!mphy) + goto error_unlock; + + band = mphy->chandef.chan->band; has_he = link_sta->he_cap.has_he; mask = link->bitrate_mask; idx = msta_link->wcid.idx; @@ -2362,18 +2382,25 @@ mt7996_mcu_sta_rate_ctrl_tlv(struct sk_buff *skb, struct mt7996_dev *dev, struct mt7996_vif_link *link) { #define INIT_RCPI 180 - struct mt76_phy *mphy = link->phy->mt76; - struct cfg80211_chan_def *chandef = &mphy->chandef; + struct mt76_phy *mphy = mt76_vif_link_phy(&link->mt76); struct cfg80211_bitrate_mask *mask = &link->bitrate_mask; u32 cap = link_sta->sta->wme ? STA_CAP_WMM : 0; - enum nl80211_band band = chandef->chan->band; + struct cfg80211_chan_def *chandef; struct sta_rec_ra_uni *ra; + enum nl80211_band band; struct tlv *tlv; - u32 supp_rate = link_sta->supp_rates[band]; + u32 supp_rate; + + if (!mphy) + return; tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_RA, sizeof(*ra)); ra = (struct sta_rec_ra_uni *)tlv; + chandef = &mphy->chandef; + band = chandef->chan->band; + supp_rate = link_sta->supp_rates[band]; + ra->valid = true; ra->auto_rate = true; ra->phy_mode = mt76_connac_get_phy_mode(mphy, vif, band, link_sta); @@ -2722,6 +2749,7 @@ void mt7996_mcu_update_sta_rec_bw(void *data, struct ieee80211_sta *sta) { struct mt7996_vif_link *link = (struct mt7996_vif_link *)data; struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; + struct mt7996_phy *phy = mt7996_vif_link_phy(link); struct mt7996_sta_link *msta_link; struct mt7996_dev *dev; struct ieee80211_bss_conf *link_conf; @@ -2730,10 +2758,13 @@ void mt7996_mcu_update_sta_rec_bw(void *data, struct ieee80211_sta *sta) struct sk_buff *skb; int link_id; + if (!phy) + return; + if (link->mt76.mvif != &msta->vif->mt76) return; - dev = link->phy->dev; + dev = phy->dev; link_id = link->msta_link.wcid.link_id; link_sta = link_sta_dereference_protected(sta, link_id); if (!link_sta) @@ -3010,6 +3041,7 @@ int mt7996_mcu_add_beacon(struct ieee80211_hw *hw, struct ieee80211_vif *vif, { struct mt7996_dev *dev = mt7996_hw_dev(hw); struct mt7996_vif_link *link = mt7996_vif_conf_link(dev, vif, link_conf); + struct mt76_phy *mphy = link ? mt76_vif_link_phy(&link->mt76) : NULL; struct mt76_vif_link *mlink = link ? &link->mt76 : NULL; struct ieee80211_mutable_offsets offs; struct ieee80211_tx_info *info; @@ -3024,7 +3056,7 @@ int mt7996_mcu_add_beacon(struct ieee80211_hw *hw, struct ieee80211_vif *vif, if (!mlink) return -EINVAL; - if (link->phy && link->phy->mt76->offchannel) + if (mphy && mphy->offchannel) enabled = false; rskb = __mt7996_mcu_alloc_bss_req(&dev->mt76, mlink, @@ -3075,9 +3107,9 @@ int mt7996_mcu_beacon_inband_discov(struct mt7996_dev *dev, { #define OFFLOAD_TX_MODE_SU BIT(0) #define OFFLOAD_TX_MODE_MU BIT(1) + struct mt76_phy *mphy = mt76_vif_link_phy(&link->mt76); struct ieee80211_vif *vif = link_conf->vif; struct ieee80211_hw *hw = mt76_hw(dev); - struct mt7996_phy *phy = link->phy; struct mt76_wcid *wcid = &dev->mt76.global_wcid; struct bss_inband_discovery_tlv *discov; struct ieee80211_tx_info *info; @@ -3088,10 +3120,10 @@ int mt7996_mcu_beacon_inband_discov(struct mt7996_dev *dev, u8 *buf, interval; int len; - if (!phy) + if (!mphy) return -EINVAL; - chandef = &phy->mt76->chandef; + chandef = &mphy->chandef; band = chandef->chan->band; if (link_conf->nontransmitted) @@ -3129,7 +3161,7 @@ int mt7996_mcu_beacon_inband_discov(struct mt7996_dev *dev, info = IEEE80211_SKB_CB(skb); info->control.vif = vif; info->band = band; - info->hw_queue |= FIELD_PREP(MT_TX_HW_QUEUE_PHY, phy->mt76->band_idx); + info->hw_queue |= FIELD_PREP(MT_TX_HW_QUEUE_PHY, mphy->band_idx); len = ALIGN(sizeof(*discov) + MT_TXD_SIZE + skb->len, 4); tlv = mt7996_mcu_add_uni_tlv(rskb, UNI_BSS_INFO_OFFLOAD, len); diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index ee3564c0115a..d18f8794351e 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -276,8 +276,6 @@ struct mt7996_vif_link { struct mt76_vif_link mt76; /* must be first */ struct mt7996_sta_link msta_link; - struct mt7996_phy *phy; - struct cfg80211_bitrate_mask bitrate_mask; u8 mld_idx; From e648051d52afbdb360bd586218961f5fffff63e8 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Sun, 8 Mar 2026 14:25:20 +0100 Subject: [PATCH 090/134] wifi: mt76: mt7996: Decrement sta counter removing the link in mt7996_mac_reset_sta_iter() Fixes tracking per-phy stations for offchannel switching. Fixes: ace5d3b6b49e8 ("wifi: mt76: mt7996: improve hardware restart reliability") Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260308-mt7996_mac_reset_vif_iter-fix-v1-1-57f640aa2dcf@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index 7587e7b4e8fe..be70c0f4fc30 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -2366,6 +2366,7 @@ mt7996_mac_reset_sta_iter(void *data, struct ieee80211_sta *sta) for (i = 0; i < ARRAY_SIZE(msta->link); i++) { struct mt7996_sta_link *msta_link = NULL; + struct mt7996_phy *phy; msta_link = rcu_replace_pointer(msta->link[i], msta_link, lockdep_is_held(&dev->mt76.mutex)); @@ -2373,6 +2374,10 @@ mt7996_mac_reset_sta_iter(void *data, struct ieee80211_sta *sta) continue; mt7996_mac_sta_deinit_link(dev, msta_link); + phy = __mt7996_phy(dev, msta_link->wcid.phy_idx); + if (phy) + phy->mt76->num_sta--; + if (msta_link != &msta->deflink) kfree_rcu(msta_link, rcu_head); } From 0420180df092419a96351fb2afec1e2a74d385c3 Mon Sep 17 00:00:00 2001 From: Chad Monroe Date: Mon, 9 Mar 2026 06:07:20 +0000 Subject: [PATCH 091/134] wifi: mt76: fix multi-radio on-channel scanning avoid unnecessary channel switch when performing an on-channel scan using a multi-radio device. Fixes: c56d6edebc1f ("wifi: mt76: mt7996: use emulated hardware scan support") Signed-off-by: Chad Monroe Link: https://patch.msgid.link/20251118102723.47997-1-nbd@nbd.name Link: https://patch.msgid.link/20260309060730.87840-1-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/scan.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/scan.c b/drivers/net/wireless/mediatek/mt76/scan.c index ff9176cdee3d..89f16c23e352 100644 --- a/drivers/net/wireless/mediatek/mt76/scan.c +++ b/drivers/net/wireless/mediatek/mt76/scan.c @@ -16,7 +16,7 @@ static void mt76_scan_complete(struct mt76_dev *dev, bool abort) clear_bit(MT76_SCANNING, &phy->state); - if (dev->scan.chan && phy->main_chandef.chan && + if (dev->scan.chan && phy->main_chandef.chan && phy->offchannel && !test_bit(MT76_MCU_RESET, &dev->phy.state)) mt76_set_channel(phy, &phy->main_chandef, false); mt76_put_vif_phy_link(phy, dev->scan.vif, dev->scan.mlink); @@ -87,6 +87,7 @@ void mt76_scan_work(struct work_struct *work) struct cfg80211_chan_def chandef = {}; struct mt76_phy *phy = dev->scan.phy; int duration = HZ / 9; /* ~110 ms */ + bool offchannel = true; int i; if (dev->scan.chan_idx >= req->n_channels) { @@ -94,7 +95,7 @@ void mt76_scan_work(struct work_struct *work) return; } - if (dev->scan.chan && phy->num_sta) { + if (dev->scan.chan && phy->num_sta && phy->offchannel) { dev->scan.chan = NULL; mt76_set_channel(phy, &phy->main_chandef, false); goto out; @@ -102,20 +103,26 @@ void mt76_scan_work(struct work_struct *work) dev->scan.chan = req->channels[dev->scan.chan_idx++]; cfg80211_chandef_create(&chandef, dev->scan.chan, NL80211_CHAN_HT20); - mt76_set_channel(phy, &chandef, true); + if (phy->main_chandef.chan == dev->scan.chan) { + chandef = phy->main_chandef; + offchannel = false; + } + + mt76_set_channel(phy, &chandef, offchannel); if (!req->n_ssids || chandef.chan->flags & (IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_RADAR)) goto out; - duration = HZ / 16; /* ~60 ms */ + if (phy->offchannel) + duration = HZ / 16; /* ~60 ms */ local_bh_disable(); for (i = 0; i < req->n_ssids; i++) mt76_scan_send_probe(dev, &req->ssids[i]); local_bh_enable(); out: - if (dev->scan.chan) + if (dev->scan.chan && phy->offchannel) duration = max_t(int, duration, msecs_to_jiffies(req->duration + (req->duration >> 5))); From 360552c8592dab3c69e0bbff786b55137f1a81bb Mon Sep 17 00:00:00 2001 From: Chad Monroe Date: Mon, 9 Mar 2026 06:07:21 +0000 Subject: [PATCH 092/134] wifi: mt76: support upgrading passive scans to active On channels with NO_IR or RADAR flags, wait for beacon before sending probe requests. Allows active scanning and WPS on restricted channels if another AP is already present. Fixes: c56d6edebc1f ("wifi: mt76: mt7996: use emulated hardware scan support") Tested-by: Piotr Kubik Signed-off-by: Chad Monroe Link: https://patch.msgid.link/20251118102723.47997-2-nbd@nbd.name Link: https://patch.msgid.link/20260309060730.87840-2-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mac80211.c | 1 + drivers/net/wireless/mediatek/mt76/mt76.h | 4 ++ .../net/wireless/mediatek/mt76/mt7996/mac.c | 3 ++ drivers/net/wireless/mediatek/mt76/scan.c | 51 +++++++++++++++++-- 4 files changed, 56 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c index d0c522909e98..3c539c263238 100644 --- a/drivers/net/wireless/mediatek/mt76/mac80211.c +++ b/drivers/net/wireless/mediatek/mt76/mac80211.c @@ -726,6 +726,7 @@ mt76_alloc_device(struct device *pdev, unsigned int size, INIT_LIST_HEAD(&dev->rxwi_cache); dev->token_size = dev->drv->token_size; INIT_DELAYED_WORK(&dev->scan_work, mt76_scan_work); + spin_lock_init(&dev->scan_lock); for (i = 0; i < ARRAY_SIZE(dev->q_rx); i++) skb_queue_head_init(&dev->rx_skb[i]); diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index 23a1832812a2..ffeb4b4c425b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -1003,6 +1003,7 @@ struct mt76_dev { u32 rxfilter; struct delayed_work scan_work; + spinlock_t scan_lock; struct { struct cfg80211_scan_request *req; struct ieee80211_channel *chan; @@ -1010,6 +1011,8 @@ struct mt76_dev { struct mt76_vif_link *mlink; struct mt76_phy *phy; int chan_idx; + bool beacon_wait; + bool beacon_received; } scan; #ifdef CONFIG_NL80211_TESTMODE @@ -1597,6 +1600,7 @@ int mt76_get_rate(struct mt76_dev *dev, int mt76_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_scan_request *hw_req); void mt76_cancel_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif); +void mt76_scan_rx_beacon(struct mt76_dev *dev, struct ieee80211_channel *chan); void mt76_sw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif, const u8 *mac); void mt76_sw_scan_complete(struct ieee80211_hw *hw, diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index be70c0f4fc30..f5537eba9c6b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -515,6 +515,9 @@ mt7996_mac_fill_rx(struct mt7996_dev *dev, enum mt76_rxq_id q, qos_ctl = FIELD_GET(MT_RXD10_QOS_CTL, v2); seq_ctrl = FIELD_GET(MT_RXD10_SEQ_CTRL, v2); + if (ieee80211_is_beacon(fc)) + mt76_scan_rx_beacon(&dev->mt76, mphy->chandef.chan); + rxd += 4; if ((u8 *)rxd - skb->data >= skb->len) return -EINVAL; diff --git a/drivers/net/wireless/mediatek/mt76/scan.c b/drivers/net/wireless/mediatek/mt76/scan.c index 89f16c23e352..2b5f30cef795 100644 --- a/drivers/net/wireless/mediatek/mt76/scan.c +++ b/drivers/net/wireless/mediatek/mt76/scan.c @@ -27,6 +27,10 @@ static void mt76_scan_complete(struct mt76_dev *dev, bool abort) void mt76_abort_scan(struct mt76_dev *dev) { + spin_lock_bh(&dev->scan_lock); + dev->scan.beacon_wait = false; + spin_unlock_bh(&dev->scan_lock); + cancel_delayed_work_sync(&dev->scan_work); mt76_scan_complete(dev, true); } @@ -79,6 +83,28 @@ mt76_scan_send_probe(struct mt76_dev *dev, struct cfg80211_ssid *ssid) rcu_read_unlock(); } +void mt76_scan_rx_beacon(struct mt76_dev *dev, struct ieee80211_channel *chan) +{ + struct mt76_phy *phy; + + spin_lock(&dev->scan_lock); + + if (!dev->scan.beacon_wait || dev->scan.beacon_received || + dev->scan.chan != chan) + goto out; + + phy = dev->scan.phy; + if (!phy) + goto out; + + dev->scan.beacon_received = true; + ieee80211_queue_delayed_work(phy->hw, &dev->scan_work, 0); + +out: + spin_unlock(&dev->scan_lock); +} +EXPORT_SYMBOL_GPL(mt76_scan_rx_beacon); + void mt76_scan_work(struct work_struct *work) { struct mt76_dev *dev = container_of(work, struct mt76_dev, @@ -87,9 +113,20 @@ void mt76_scan_work(struct work_struct *work) struct cfg80211_chan_def chandef = {}; struct mt76_phy *phy = dev->scan.phy; int duration = HZ / 9; /* ~110 ms */ - bool offchannel = true; + bool beacon_rx, offchannel = true; int i; + if (!phy || !req) + return; + + spin_lock_bh(&dev->scan_lock); + beacon_rx = dev->scan.beacon_wait && dev->scan.beacon_received; + dev->scan.beacon_wait = false; + spin_unlock_bh(&dev->scan_lock); + + if (beacon_rx) + goto probe; + if (dev->scan.chan_idx >= req->n_channels) { mt76_scan_complete(dev, false); return; @@ -110,10 +147,18 @@ void mt76_scan_work(struct work_struct *work) mt76_set_channel(phy, &chandef, offchannel); - if (!req->n_ssids || - chandef.chan->flags & (IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_RADAR)) + if (!req->n_ssids) goto out; + if (chandef.chan->flags & (IEEE80211_CHAN_NO_IR | IEEE80211_CHAN_RADAR)) { + spin_lock_bh(&dev->scan_lock); + dev->scan.beacon_received = false; + dev->scan.beacon_wait = true; + spin_unlock_bh(&dev->scan_lock); + goto out; + } + +probe: if (phy->offchannel) duration = HZ / 16; /* ~60 ms */ local_bh_disable(); From ec0a9b01ef88b5f5bdf74140f8c987f7a96693af Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Mon, 9 Mar 2026 06:07:22 +0000 Subject: [PATCH 093/134] wifi: mt76: add offchannel check to mt76_roc_complete mt76_roc_complete() unconditionally calls __mt76_set_channel() to restore the operating channel. The scan equivalent mt76_scan_complete() checks phy->offchannel first, skipping the restore if the phy is already back on-channel. Without this check, ROC completion performs a redundant full hardware channel switch when something has already moved the phy back. Link: https://patch.msgid.link/20260309060730.87840-3-nbd@nbd.name 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 d9f8529db7ed..ae7b4ed27a5c 100644 --- a/drivers/net/wireless/mediatek/mt76/channel.c +++ b/drivers/net/wireless/mediatek/mt76/channel.c @@ -324,7 +324,7 @@ void mt76_roc_complete(struct mt76_phy *phy) if (mlink) mlink->mvif->roc_phy = NULL; - if (phy->main_chandef.chan && + if (phy->main_chandef.chan && phy->offchannel && !test_bit(MT76_MCU_RESET, &dev->phy.state)) __mt76_set_channel(phy, &phy->main_chandef, false); mt76_put_vif_phy_link(phy, phy->roc_vif, phy->roc_link); From f0fb9afb74ec5bec49585772502db62613321fc6 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Mon, 9 Mar 2026 06:07:23 +0000 Subject: [PATCH 094/134] wifi: mt76: check chanctx before restoring channel after ROC mt76_remove_chanctx() sets phy->chanctx to NULL but does not clear phy->main_chandef. If ROC is later performed on that phy, completion tries to restore the stale main_chandef channel, programming the hardware to sit on a channel with no active context. Add a chanctx check to avoid restoring a channel when no context is active. Link: https://patch.msgid.link/20260309060730.87840-4-nbd@nbd.name 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 ae7b4ed27a5c..cfff50892a6e 100644 --- a/drivers/net/wireless/mediatek/mt76/channel.c +++ b/drivers/net/wireless/mediatek/mt76/channel.c @@ -324,7 +324,7 @@ void mt76_roc_complete(struct mt76_phy *phy) if (mlink) mlink->mvif->roc_phy = NULL; - if (phy->main_chandef.chan && phy->offchannel && + if (phy->chanctx && phy->main_chandef.chan && phy->offchannel && !test_bit(MT76_MCU_RESET, &dev->phy.state)) __mt76_set_channel(phy, &phy->main_chandef, false); mt76_put_vif_phy_link(phy, phy->roc_vif, phy->roc_link); From de62b24224ac1533c17b3d5bae77164a82ae2e49 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Mon, 9 Mar 2026 06:07:24 +0000 Subject: [PATCH 095/134] wifi: mt76: abort ROC on chanctx changes mt76_change_chanctx() calls mt76_phy_update_channel() which switches the hardware channel. If ROC is active on the same phy, this switches away from the ROC channel and clears offchannel, but leaves ROC state intact. Mac80211 still thinks the phy is on the ROC channel. Abort any active ROC before proceeding, matching the pattern already used in add, remove, assign, unassign, and switch chanctx functions. Link: https://patch.msgid.link/20260309060730.87840-5-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/channel.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/channel.c b/drivers/net/wireless/mediatek/mt76/channel.c index cfff50892a6e..8d2e72c68c6b 100644 --- a/drivers/net/wireless/mediatek/mt76/channel.c +++ b/drivers/net/wireless/mediatek/mt76/channel.c @@ -88,6 +88,9 @@ void mt76_change_chanctx(struct ieee80211_hw *hw, IEEE80211_CHANCTX_CHANGE_RADAR))) return; + if (phy->roc_vif) + mt76_abort_roc(phy); + cancel_delayed_work_sync(&phy->mac_work); mutex_lock(&dev->mutex); From f72dd74dd0b69e3a87b4702f3c860e9a7318d5dd Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Mon, 9 Mar 2026 06:07:25 +0000 Subject: [PATCH 096/134] wifi: mt76: optimize ROC for same-channel case mt76_remain_on_channel() always creates an HT20 chandef and goes offchannel, even when the ROC channel matches the operating channel. This unnecessarily narrows bandwidth and triggers beacon stop/restart. When the ROC channel matches the current operating channel, preserve the full chandef and skip the offchannel transition, matching the optimization already present in the scan code. Extract the shared same-channel detection into mt76_offchannel_chandef() and use it in both ROC and scan paths. Link: https://patch.msgid.link/20260309060730.87840-6-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/channel.c | 4 ++-- drivers/net/wireless/mediatek/mt76/mt76.h | 12 ++++++++++++ drivers/net/wireless/mediatek/mt76/scan.c | 6 +----- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/channel.c b/drivers/net/wireless/mediatek/mt76/channel.c index 8d2e72c68c6b..f42f25101544 100644 --- a/drivers/net/wireless/mediatek/mt76/channel.c +++ b/drivers/net/wireless/mediatek/mt76/channel.c @@ -392,8 +392,8 @@ int mt76_remain_on_channel(struct ieee80211_hw *hw, struct ieee80211_vif *vif, mlink->mvif->roc_phy = phy; phy->roc_vif = vif; phy->roc_link = mlink; - cfg80211_chandef_create(&chandef, chan, NL80211_CHAN_HT20); - ret = __mt76_set_channel(phy, &chandef, true); + ret = __mt76_set_channel(phy, &chandef, + mt76_offchannel_chandef(phy, chan, &chandef)); if (ret) { mlink->mvif->roc_phy = NULL; phy->roc_vif = NULL; diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index ffeb4b4c425b..c7c9ffd0dc3b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -1790,6 +1790,18 @@ void mt76_queue_tx_complete(struct mt76_dev *dev, struct mt76_queue *q, struct mt76_queue_entry *e); int __mt76_set_channel(struct mt76_phy *phy, struct cfg80211_chan_def *chandef, bool offchannel); + +static inline bool +mt76_offchannel_chandef(struct mt76_phy *phy, struct ieee80211_channel *chan, + struct cfg80211_chan_def *chandef) +{ + cfg80211_chandef_create(chandef, chan, NL80211_CHAN_HT20); + if (phy->main_chandef.chan != chan) + return true; + + *chandef = phy->main_chandef; + return false; +} int mt76_set_channel(struct mt76_phy *phy, struct cfg80211_chan_def *chandef, bool offchannel); void mt76_scan_work(struct work_struct *work); diff --git a/drivers/net/wireless/mediatek/mt76/scan.c b/drivers/net/wireless/mediatek/mt76/scan.c index 2b5f30cef795..5a67e9b8183a 100644 --- a/drivers/net/wireless/mediatek/mt76/scan.c +++ b/drivers/net/wireless/mediatek/mt76/scan.c @@ -139,11 +139,7 @@ void mt76_scan_work(struct work_struct *work) } dev->scan.chan = req->channels[dev->scan.chan_idx++]; - cfg80211_chandef_create(&chandef, dev->scan.chan, NL80211_CHAN_HT20); - if (phy->main_chandef.chan == dev->scan.chan) { - chandef = phy->main_chandef; - offchannel = false; - } + offchannel = mt76_offchannel_chandef(phy, dev->scan.chan, &chandef); mt76_set_channel(phy, &chandef, offchannel); From 381733b3a14aaef36b421571c1e99856304311f1 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Mon, 9 Mar 2026 06:07:26 +0000 Subject: [PATCH 097/134] wifi: mt76: send nullfunc PS frames on offchannel transitions Since mt76 uses chanctx, mac80211 does not send nullfunc power save notifications when the driver goes offchannel for scan or ROC. Add mt76_offchannel_notify() to send nullfunc PM=1 before going offchannel and PM=0 after returning, so that the AP can buffer frames during the absence. For MLO, iterate all vif links on the phy and set IEEE80211_TX_CTRL_MLO_LINK so that the driver's tx_prepare_skb resolves the correct per-link wcid. Link: https://patch.msgid.link/20260309060730.87840-7-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/channel.c | 12 ++- drivers/net/wireless/mediatek/mt76/mac80211.c | 99 +++++++++++++++++++ drivers/net/wireless/mediatek/mt76/mt76.h | 1 + drivers/net/wireless/mediatek/mt76/scan.c | 7 +- 4 files changed, 115 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/channel.c b/drivers/net/wireless/mediatek/mt76/channel.c index f42f25101544..3072e11e2688 100644 --- a/drivers/net/wireless/mediatek/mt76/channel.c +++ b/drivers/net/wireless/mediatek/mt76/channel.c @@ -328,8 +328,10 @@ void mt76_roc_complete(struct mt76_phy *phy) if (mlink) mlink->mvif->roc_phy = NULL; if (phy->chanctx && phy->main_chandef.chan && phy->offchannel && - !test_bit(MT76_MCU_RESET, &dev->phy.state)) + !test_bit(MT76_MCU_RESET, &dev->phy.state)) { __mt76_set_channel(phy, &phy->main_chandef, false); + mt76_offchannel_notify(phy, false); + } mt76_put_vif_phy_link(phy, phy->roc_vif, phy->roc_link); phy->roc_vif = NULL; phy->roc_link = NULL; @@ -367,6 +369,7 @@ int mt76_remain_on_channel(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct mt76_phy *phy = hw->priv; struct mt76_dev *dev = phy->dev; struct mt76_vif_link *mlink; + bool offchannel; int ret = 0; phy = dev->band_phys[chan->band]; @@ -392,8 +395,11 @@ int mt76_remain_on_channel(struct ieee80211_hw *hw, struct ieee80211_vif *vif, mlink->mvif->roc_phy = phy; phy->roc_vif = vif; phy->roc_link = mlink; - ret = __mt76_set_channel(phy, &chandef, - mt76_offchannel_chandef(phy, chan, &chandef)); + + offchannel = mt76_offchannel_chandef(phy, chan, &chandef); + if (offchannel) + mt76_offchannel_notify(phy, true); + ret = __mt76_set_channel(phy, &chandef, offchannel); if (ret) { mlink->mvif->roc_phy = NULL; phy->roc_vif = NULL; diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c index 3c539c263238..63a42fe16f73 100644 --- a/drivers/net/wireless/mediatek/mt76/mac80211.c +++ b/drivers/net/wireless/mediatek/mt76/mac80211.c @@ -2132,3 +2132,102 @@ u16 mt76_select_links(struct ieee80211_vif *vif, int max_active_links) return sel_links; } EXPORT_SYMBOL_GPL(mt76_select_links); + +struct mt76_offchannel_cb_data { + struct mt76_phy *phy; + bool offchannel; +}; + +static void +mt76_offchannel_send_nullfunc(struct mt76_offchannel_cb_data *data, + struct ieee80211_vif *vif, int link_id) +{ + struct mt76_phy *phy = data->phy; + struct ieee80211_tx_info *info; + struct ieee80211_sta *sta = NULL; + struct ieee80211_hdr *hdr; + struct mt76_wcid *wcid; + struct sk_buff *skb; + + skb = ieee80211_nullfunc_get(phy->hw, vif, link_id, true); + if (!skb) + return; + + hdr = (struct ieee80211_hdr *)skb->data; + if (data->offchannel) + hdr->frame_control |= cpu_to_le16(IEEE80211_FCTL_PM); + + skb->priority = 7; + skb_set_queue_mapping(skb, IEEE80211_AC_VO); + + if (!ieee80211_tx_prepare_skb(phy->hw, vif, skb, + phy->main_chandef.chan->band, + &sta)) + return; + + if (sta) + wcid = (struct mt76_wcid *)sta->drv_priv; + else + wcid = ((struct mt76_vif_link *)vif->drv_priv)->wcid; + + if (link_id >= 0) { + info = IEEE80211_SKB_CB(skb); + info->control.flags &= ~IEEE80211_TX_CTRL_MLO_LINK; + info->control.flags |= + u32_encode_bits(link_id, IEEE80211_TX_CTRL_MLO_LINK); + } + + mt76_tx(phy, sta, wcid, skb); +} + +static void +mt76_offchannel_notify_iter(void *_data, u8 *mac, struct ieee80211_vif *vif) +{ + struct mt76_offchannel_cb_data *data = _data; + struct mt76_vif_link *mlink; + struct mt76_vif_data *mvif; + int link_id; + + if (vif->type != NL80211_IFTYPE_STATION || !vif->cfg.assoc) + return; + + mlink = (struct mt76_vif_link *)vif->drv_priv; + mvif = mlink->mvif; + + if (!ieee80211_vif_is_mld(vif)) { + if (mt76_vif_link_phy(mlink) == data->phy) + mt76_offchannel_send_nullfunc(data, vif, -1); + return; + } + + for (link_id = 0; link_id < IEEE80211_MLD_MAX_NUM_LINKS; link_id++) { + if (link_id == mvif->deflink_id) + mlink = (struct mt76_vif_link *)vif->drv_priv; + else + mlink = rcu_dereference(mvif->link[link_id]); + if (!mlink) + continue; + if (mt76_vif_link_phy(mlink) != data->phy) + continue; + + mt76_offchannel_send_nullfunc(data, vif, link_id); + } +} + +void mt76_offchannel_notify(struct mt76_phy *phy, bool offchannel) +{ + struct mt76_offchannel_cb_data data = { + .phy = phy, + .offchannel = offchannel, + }; + + if (!phy->num_sta) + return; + + local_bh_disable(); + ieee80211_iterate_active_interfaces_atomic(phy->hw, + IEEE80211_IFACE_ITER_NORMAL, + mt76_offchannel_notify_iter, &data); + local_bh_enable(); +} +EXPORT_SYMBOL_GPL(mt76_offchannel_notify); diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index c7c9ffd0dc3b..c9084c0ae522 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -1813,6 +1813,7 @@ struct mt76_vif_link *mt76_get_vif_phy_link(struct mt76_phy *phy, struct ieee80211_vif *vif); void mt76_put_vif_phy_link(struct mt76_phy *phy, struct ieee80211_vif *vif, struct mt76_vif_link *mlink); +void mt76_offchannel_notify(struct mt76_phy *phy, bool offchannel); /* usb */ static inline bool mt76u_urb_error(struct urb *urb) diff --git a/drivers/net/wireless/mediatek/mt76/scan.c b/drivers/net/wireless/mediatek/mt76/scan.c index 5a67e9b8183a..04cf8a01f20d 100644 --- a/drivers/net/wireless/mediatek/mt76/scan.c +++ b/drivers/net/wireless/mediatek/mt76/scan.c @@ -17,8 +17,10 @@ static void mt76_scan_complete(struct mt76_dev *dev, bool abort) clear_bit(MT76_SCANNING, &phy->state); if (dev->scan.chan && phy->main_chandef.chan && phy->offchannel && - !test_bit(MT76_MCU_RESET, &dev->phy.state)) + !test_bit(MT76_MCU_RESET, &dev->phy.state)) { mt76_set_channel(phy, &phy->main_chandef, false); + mt76_offchannel_notify(phy, false); + } mt76_put_vif_phy_link(phy, dev->scan.vif, dev->scan.mlink); memset(&dev->scan, 0, sizeof(dev->scan)); if (!test_bit(MT76_MCU_RESET, &dev->phy.state)) @@ -135,12 +137,15 @@ void mt76_scan_work(struct work_struct *work) if (dev->scan.chan && phy->num_sta && phy->offchannel) { dev->scan.chan = NULL; mt76_set_channel(phy, &phy->main_chandef, false); + mt76_offchannel_notify(phy, false); goto out; } dev->scan.chan = req->channels[dev->scan.chan_idx++]; offchannel = mt76_offchannel_chandef(phy, dev->scan.chan, &chandef); + if (offchannel) + mt76_offchannel_notify(phy, true); mt76_set_channel(phy, &chandef, offchannel); if (!req->n_ssids) From 0dcef1cbae27d806cd29c296cc03ad6e8ece771d Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Mon, 9 Mar 2026 06:07:27 +0000 Subject: [PATCH 098/134] wifi: mt76: flush pending TX before channel switch mt76_tx() queues frames on wcid->tx_pending for async processing by tx_worker. In __mt76_set_channel(), the worker gets disabled before it may have run, and the subsequent wait only checks DMA ring queues, not the software pending list. This means frames like nullfunc PS frames from mt76_offchannel_notify() may never be transmitted on the correct channel. Fix this by running mt76_txq_schedule_pending() synchronously after disabling the tx_worker but before setting MT76_RESET, which would otherwise cause mt76_txq_schedule_pending_wcid() to bail out. Link: https://patch.msgid.link/20260309060730.87840-8-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mac80211.c | 5 +++-- drivers/net/wireless/mediatek/mt76/mt76.h | 1 + drivers/net/wireless/mediatek/mt76/tx.c | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c index 63a42fe16f73..2ddbfdcae939 100644 --- a/drivers/net/wireless/mediatek/mt76/mac80211.c +++ b/drivers/net/wireless/mediatek/mt76/mac80211.c @@ -1031,9 +1031,10 @@ int __mt76_set_channel(struct mt76_phy *phy, struct cfg80211_chan_def *chandef, int timeout = HZ / 5; int ret; - set_bit(MT76_RESET, &phy->state); - mt76_worker_disable(&dev->tx_worker); + mt76_txq_schedule_pending(phy); + + set_bit(MT76_RESET, &phy->state); wait_event_timeout(dev->tx_wait, !mt76_has_tx_pending(phy), timeout); mt76_update_survey(phy); diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index c9084c0ae522..210aafc1e21e 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -1522,6 +1522,7 @@ void mt76_stop_tx_queues(struct mt76_phy *phy, struct ieee80211_sta *sta, void mt76_tx_check_agg_ssn(struct ieee80211_sta *sta, struct sk_buff *skb); void mt76_txq_schedule(struct mt76_phy *phy, enum mt76_txq_id qid); void mt76_txq_schedule_all(struct mt76_phy *phy); +void mt76_txq_schedule_pending(struct mt76_phy *phy); void mt76_tx_worker_run(struct mt76_dev *dev); void mt76_tx_worker(struct mt76_worker *w); void mt76_release_buffered_frames(struct ieee80211_hw *hw, diff --git a/drivers/net/wireless/mediatek/mt76/tx.c b/drivers/net/wireless/mediatek/mt76/tx.c index 0753acf2eccb..ab62591b7a26 100644 --- a/drivers/net/wireless/mediatek/mt76/tx.c +++ b/drivers/net/wireless/mediatek/mt76/tx.c @@ -660,7 +660,7 @@ mt76_txq_schedule_pending_wcid(struct mt76_phy *phy, struct mt76_wcid *wcid, return ret; } -static void mt76_txq_schedule_pending(struct mt76_phy *phy) +void mt76_txq_schedule_pending(struct mt76_phy *phy) { LIST_HEAD(tx_list); int ret = 0; From 331e766e75d2a64b5c1f38aadfcfcf31d264f43e Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Mon, 9 Mar 2026 06:07:28 +0000 Subject: [PATCH 099/134] wifi: mt76: route nullfunc frames to PSD/ALTX queue ieee80211_is_data() returns true for nullfunc/QoS-nullfunc frames, so they bypass the PSD queue routing and go through the regular VO data queue. This means firmware processes them through the normal TID queue instead of the ALTX queue, which doesn't guarantee immediate transmission. Use ieee80211_is_data_present() instead, which returns false for both management frames and nullfunc/QoS-nullfunc (no payload), routing them to MT_TXQ_PSD. Firmware maps PSD to the ALTX queue, which transmits immediately without PS buffering. This only affects frames from the mt76_tx() pending path. Regular mac80211 TXQ scheduling is unchanged. Link: https://patch.msgid.link/20260309060730.87840-9-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/tx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/tx.c b/drivers/net/wireless/mediatek/mt76/tx.c index ab62591b7a26..7b0fae694f12 100644 --- a/drivers/net/wireless/mediatek/mt76/tx.c +++ b/drivers/net/wireless/mediatek/mt76/tx.c @@ -632,7 +632,7 @@ mt76_txq_schedule_pending_wcid(struct mt76_phy *phy, struct mt76_wcid *wcid, if ((dev->drv->drv_flags & MT_DRV_HW_MGMT_TXQ) && !(info->flags & IEEE80211_TX_CTL_HW_80211_ENCAP) && - !ieee80211_is_data(hdr->frame_control) && + !ieee80211_is_data_present(hdr->frame_control) && (!ieee80211_is_bufferable_mmpdu(skb) || ieee80211_is_deauth(hdr->frame_control) || head == &wcid->tx_offchannel)) From e765bd6708cdeeab01121247165cae04a254868e Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Mon, 9 Mar 2026 06:07:29 +0000 Subject: [PATCH 100/134] wifi: mt76: wait for firmware TX completion of mgmt frames before channel switch After flushing software-pending frames to DMA, mt76_has_tx_pending() only checks DMA ring q->queued. For token-based drivers, q->queued is decremented at DMA consumption, but firmware may not have transmitted the frame yet. Waiting for all tokens is not feasible because data frames may be stuck in firmware powersave/aggregation queues. Track PSD queue tokens (firmware ALTX) per phy using an atomic counter. These frames are sent by firmware immediately without PS buffering, so the counter reliably reaches zero after transmission. Increment the counter in mt76_token_consume() and decrement it in mt76_token_release(), only for PSD queue tokens. Include the counter in mt76_has_tx_pending() so channel switch waits for firmware TX completion of management and nullfunc frames. mt7615 (uses mt76_token_get/put) and non-token drivers are unaffected as they never call mt76_token_consume/release. Link: https://patch.msgid.link/20260309060730.87840-10-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/dma.c | 2 ++ drivers/net/wireless/mediatek/mt76/mac80211.c | 3 +++ drivers/net/wireless/mediatek/mt76/mt76.h | 3 +++ .../net/wireless/mediatek/mt76/mt76_connac_mac.c | 6 ++++++ drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 6 ++++++ drivers/net/wireless/mediatek/mt76/tx.c | 14 +++++++++++++- 6 files changed, 33 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/dma.c b/drivers/net/wireless/mediatek/mt76/dma.c index 2d133ace7c33..f8c2fe5f2f58 100644 --- a/drivers/net/wireless/mediatek/mt76/dma.c +++ b/drivers/net/wireless/mediatek/mt76/dma.c @@ -666,6 +666,8 @@ mt76_dma_tx_queue_skb(struct mt76_phy *phy, struct mt76_queue *q, if (!t) goto free_skb; + t->phy_idx = phy->band_idx; + t->qid = qid; txwi = mt76_get_txwi_ptr(dev, t); skb->prev = skb->next = NULL; diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c index 2ddbfdcae939..54ed1cec0228 100644 --- a/drivers/net/wireless/mediatek/mt76/mac80211.c +++ b/drivers/net/wireless/mediatek/mt76/mac80211.c @@ -971,6 +971,9 @@ bool mt76_has_tx_pending(struct mt76_phy *phy) return true; } + if (atomic_read(&phy->mgmt_tx_pending)) + return true; + return false; } EXPORT_SYMBOL_GPL(mt76_has_tx_pending); diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index 210aafc1e21e..bc1153142b71 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -450,6 +450,7 @@ struct mt76_txwi_cache { }; u8 qid; + u8 phy_idx; }; struct mt76_rx_tid { @@ -860,6 +861,8 @@ struct mt76_phy { struct list_head tx_list; struct mt76_queue *q_tx[__MT_TXQ_MAX]; + atomic_t mgmt_tx_pending; + struct cfg80211_chan_def chandef; struct cfg80211_chan_def main_chandef; bool offchannel; diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c index c2cf6893848b..0339e2e7ab60 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c @@ -1209,5 +1209,11 @@ void mt76_connac2_tx_token_put(struct mt76_dev *dev) } spin_unlock_bh(&dev->token_lock); idr_destroy(&dev->token); + + for (id = 0; id < __MT_MAX_BAND; id++) { + struct mt76_phy *phy = dev->phys[id]; + if (phy) + atomic_set(&phy->mgmt_tx_pending, 0); + } } EXPORT_SYMBOL_GPL(mt76_connac2_tx_token_put); diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index f5537eba9c6b..7deedd3fcb19 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -2220,6 +2220,12 @@ void mt7996_tx_token_put(struct mt7996_dev *dev) } spin_unlock_bh(&dev->mt76.token_lock); idr_destroy(&dev->mt76.token); + + for (id = 0; id < __MT_MAX_BAND; id++) { + struct mt76_phy *phy = dev->mt76.phys[id]; + if (phy) + atomic_set(&phy->mgmt_tx_pending, 0); + } } static int diff --git a/drivers/net/wireless/mediatek/mt76/tx.c b/drivers/net/wireless/mediatek/mt76/tx.c index 7b0fae694f12..22f9690634c9 100644 --- a/drivers/net/wireless/mediatek/mt76/tx.c +++ b/drivers/net/wireless/mediatek/mt76/tx.c @@ -866,9 +866,15 @@ int mt76_token_consume(struct mt76_dev *dev, struct mt76_txwi_cache **ptxwi) token = idr_alloc(&dev->token, *ptxwi, dev->token_start, dev->token_start + dev->token_size, GFP_ATOMIC); - if (token >= dev->token_start) + if (token >= dev->token_start) { dev->token_count++; + if ((*ptxwi)->qid == MT_TXQ_PSD) { + struct mt76_phy *mphy = mt76_dev_phy(dev, (*ptxwi)->phy_idx); + atomic_inc(&mphy->mgmt_tx_pending); + } + } + #ifdef CONFIG_NET_MEDIATEK_SOC_WED if (mtk_wed_device_active(&dev->mmio.wed) && token >= dev->mmio.wed.wlan.token_start) @@ -913,6 +919,12 @@ mt76_token_release(struct mt76_dev *dev, int token, bool *wake) if (txwi) { dev->token_count--; + if (txwi->qid == MT_TXQ_PSD) { + struct mt76_phy *mphy = mt76_dev_phy(dev, txwi->phy_idx); + if (atomic_dec_and_test(&mphy->mgmt_tx_pending)) + wake_up(&dev->tx_wait); + } + #ifdef CONFIG_NET_MEDIATEK_SOC_WED if (mtk_wed_device_active(&dev->mmio.wed) && token >= dev->mmio.wed.wlan.token_start && From 4df75606ece504a0cef3552ae88217a4af1b7dba Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Mon, 9 Mar 2026 06:07:30 +0000 Subject: [PATCH 101/134] wifi: mt76: add per-link beacon monitoring for MLO With chanctx drivers using hardware scan or remain-on-channel, mac80211 does not know when the radio goes off-channel, which breaks its software beacon loss detection. Implement per-link beacon monitoring in the driver. Track the last beacon timestamp per link and check for beacon loss periodically from the mac_work handler. Beacon monitoring is initialized on association and on late link activation, and cleared on disassociation. The beacon_mon_last timestamp is reset when returning from offchannel and after channel switches to prevent false beacon loss detection. Link: https://patch.msgid.link/20260309060730.87840-11-nbd@nbd.name Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/channel.c | 2 + drivers/net/wireless/mediatek/mt76/mac80211.c | 114 +++++++++++++++++- drivers/net/wireless/mediatek/mt76/mt76.h | 5 + .../net/wireless/mediatek/mt76/mt7996/mac.c | 6 +- .../net/wireless/mediatek/mt76/mt7996/main.c | 32 +++++ drivers/net/wireless/mediatek/mt76/scan.c | 1 - 6 files changed, 155 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/channel.c b/drivers/net/wireless/mediatek/mt76/channel.c index 3072e11e2688..cf3fc09e5d5a 100644 --- a/drivers/net/wireless/mediatek/mt76/channel.c +++ b/drivers/net/wireless/mediatek/mt76/channel.c @@ -257,6 +257,8 @@ int mt76_switch_vif_chanctx(struct ieee80211_hw *hw, continue; mlink->ctx = vifs->new_ctx; + if (mlink->beacon_mon_interval) + WRITE_ONCE(mlink->beacon_mon_last, jiffies); } out: diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c index 54ed1cec0228..4ae5e4715a9c 100644 --- a/drivers/net/wireless/mediatek/mt76/mac80211.c +++ b/drivers/net/wireless/mediatek/mt76/mac80211.c @@ -2199,8 +2199,11 @@ mt76_offchannel_notify_iter(void *_data, u8 *mac, struct ieee80211_vif *vif) mvif = mlink->mvif; if (!ieee80211_vif_is_mld(vif)) { - if (mt76_vif_link_phy(mlink) == data->phy) + if (mt76_vif_link_phy(mlink) == data->phy) { + if (!data->offchannel && mlink->beacon_mon_interval) + WRITE_ONCE(mlink->beacon_mon_last, jiffies); mt76_offchannel_send_nullfunc(data, vif, -1); + } return; } @@ -2214,6 +2217,9 @@ mt76_offchannel_notify_iter(void *_data, u8 *mac, struct ieee80211_vif *vif) if (mt76_vif_link_phy(mlink) != data->phy) continue; + if (!data->offchannel && mlink->beacon_mon_interval) + WRITE_ONCE(mlink->beacon_mon_last, jiffies); + mt76_offchannel_send_nullfunc(data, vif, link_id); } } @@ -2235,3 +2241,109 @@ void mt76_offchannel_notify(struct mt76_phy *phy, bool offchannel) local_bh_enable(); } EXPORT_SYMBOL_GPL(mt76_offchannel_notify); + +struct mt76_rx_beacon_data { + struct mt76_phy *phy; + const u8 *bssid; +}; + +static void mt76_rx_beacon_iter(void *_data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct mt76_rx_beacon_data *data = _data; + struct mt76_vif_link *mlink = (struct mt76_vif_link *)vif->drv_priv; + struct mt76_vif_data *mvif = mlink->mvif; + int link_id; + + if (vif->type != NL80211_IFTYPE_STATION || !vif->cfg.assoc) + return; + + for (link_id = 0; link_id < IEEE80211_MLD_MAX_NUM_LINKS; link_id++) { + struct ieee80211_bss_conf *link_conf; + + if (link_id == mvif->deflink_id) + mlink = (struct mt76_vif_link *)vif->drv_priv; + else + mlink = rcu_dereference(mvif->link[link_id]); + if (!mlink || !mlink->beacon_mon_interval) + continue; + + if (mt76_vif_link_phy(mlink) != data->phy) + continue; + + link_conf = rcu_dereference(vif->link_conf[link_id]); + if (!link_conf) + continue; + + if (!ether_addr_equal(link_conf->bssid, data->bssid) && + (!link_conf->nontransmitted || + !ether_addr_equal(link_conf->transmitter_bssid, + data->bssid))) + continue; + + WRITE_ONCE(mlink->beacon_mon_last, jiffies); + } +} + +void mt76_rx_beacon(struct mt76_phy *phy, struct sk_buff *skb) +{ + struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb; + struct ieee80211_hdr *hdr = mt76_skb_get_hdr(skb); + struct mt76_rx_beacon_data data = { + .phy = phy, + .bssid = hdr->addr3, + }; + + mt76_scan_rx_beacon(phy->dev, phy->chandef.chan); + + if (!phy->num_sta) + return; + + if (status->flag & (RX_FLAG_FAILED_FCS_CRC | RX_FLAG_ONLY_MONITOR)) + return; + + ieee80211_iterate_active_interfaces_atomic(phy->hw, + IEEE80211_IFACE_ITER_RESUME_ALL, + mt76_rx_beacon_iter, &data); +} +EXPORT_SYMBOL_GPL(mt76_rx_beacon); + +static void mt76_beacon_mon_iter(void *data, u8 *mac, + struct ieee80211_vif *vif) +{ + struct mt76_phy *phy = data; + struct mt76_vif_link *mlink = (struct mt76_vif_link *)vif->drv_priv; + struct mt76_vif_data *mvif = mlink->mvif; + int link_id; + + if (vif->type != NL80211_IFTYPE_STATION || !vif->cfg.assoc) + return; + + for (link_id = 0; link_id < IEEE80211_MLD_MAX_NUM_LINKS; link_id++) { + if (link_id == mvif->deflink_id) + mlink = (struct mt76_vif_link *)vif->drv_priv; + else + mlink = rcu_dereference(mvif->link[link_id]); + if (!mlink || !mlink->beacon_mon_interval) + continue; + + if (mt76_vif_link_phy(mlink) != phy) + continue; + + if (time_after(jiffies, + READ_ONCE(mlink->beacon_mon_last) + + MT76_BEACON_MON_MAX_MISS * mlink->beacon_mon_interval)) + ieee80211_beacon_loss(vif); + } +} + +void mt76_beacon_mon_check(struct mt76_phy *phy) +{ + if (phy->offchannel) + return; + + ieee80211_iterate_active_interfaces_atomic(phy->hw, + IEEE80211_IFACE_ITER_RESUME_ALL, + mt76_beacon_mon_iter, phy); +} +EXPORT_SYMBOL_GPL(mt76_beacon_mon_check); diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h index bc1153142b71..527bef97e122 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76.h +++ b/drivers/net/wireless/mediatek/mt76/mt76.h @@ -364,6 +364,7 @@ enum mt76_wcid_flags { }; #define MT76_N_WCIDS 1088 +#define MT76_BEACON_MON_MAX_MISS 7 /* stored in ieee80211_tx_info::hw_queue */ #define MT_TX_HW_QUEUE_PHY GENMASK(3, 2) @@ -833,6 +834,8 @@ struct mt76_vif_link { u8 mcast_rates_idx; u8 beacon_rates_idx; bool offchannel; + unsigned long beacon_mon_last; + u16 beacon_mon_interval; struct ieee80211_chanctx_conf *ctx; struct mt76_wcid *wcid; struct mt76_vif_data *mvif; @@ -1605,6 +1608,8 @@ int mt76_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif, struct ieee80211_scan_request *hw_req); void mt76_cancel_hw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif); void mt76_scan_rx_beacon(struct mt76_dev *dev, struct ieee80211_channel *chan); +void mt76_rx_beacon(struct mt76_phy *phy, struct sk_buff *skb); +void mt76_beacon_mon_check(struct mt76_phy *phy); void mt76_sw_scan(struct ieee80211_hw *hw, struct ieee80211_vif *vif, const u8 *mac); void mt76_sw_scan_complete(struct ieee80211_hw *hw, diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index 7deedd3fcb19..e8bf58dc50b1 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -515,9 +515,6 @@ mt7996_mac_fill_rx(struct mt7996_dev *dev, enum mt76_rxq_id q, qos_ctl = FIELD_GET(MT_RXD10_QOS_CTL, v2); seq_ctrl = FIELD_GET(MT_RXD10_SEQ_CTRL, v2); - if (ieee80211_is_beacon(fc)) - mt76_scan_rx_beacon(&dev->mt76, mphy->chandef.chan); - rxd += 4; if ((u8 *)rxd - skb->data >= skb->len) return -EINVAL; @@ -664,6 +661,8 @@ mt7996_mac_fill_rx(struct mt7996_dev *dev, enum mt76_rxq_id q, hdr = mt76_skb_get_hdr(skb); fc = hdr->frame_control; + if (ieee80211_is_beacon(fc)) + mt76_rx_beacon(mphy, skb); if (ieee80211_is_data_qos(fc)) { u8 *qos = ieee80211_get_qos_ctl(hdr); @@ -2954,6 +2953,7 @@ void mt7996_mac_work(struct work_struct *work) mutex_unlock(&mphy->dev->mutex); + mt76_beacon_mon_check(mphy); mt76_tx_status_check(mphy->dev, false); ieee80211_queue_delayed_work(mphy->hw, &mphy->mac_work, diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 8ecbc0511848..39e1999143da 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -375,6 +375,17 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, mtxq->wcid = idx; } + if (vif->type == NL80211_IFTYPE_STATION) { + vif->driver_flags |= IEEE80211_VIF_BEACON_FILTER; + + if (vif->cfg.assoc && link_conf->beacon_int) { + mlink->beacon_mon_interval = + msecs_to_jiffies(ieee80211_tu_to_usec( + link_conf->beacon_int) / 1000); + WRITE_ONCE(mlink->beacon_mon_last, jiffies); + } + } + return 0; } @@ -831,6 +842,13 @@ mt7996_vif_cfg_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif, if (!link) continue; + if (vif->type == NL80211_IFTYPE_STATION) { + link->mt76.beacon_mon_interval = + msecs_to_jiffies(ieee80211_tu_to_usec( + link_conf->beacon_int) / 1000); + WRITE_ONCE(link->mt76.beacon_mon_last, jiffies); + } + phy = mt7996_vif_link_phy(link); if (!phy) continue; @@ -844,6 +862,20 @@ mt7996_vif_cfg_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif, } } + if ((changed & BSS_CHANGED_ASSOC) && !vif->cfg.assoc && + vif->type == NL80211_IFTYPE_STATION) { + struct ieee80211_bss_conf *link_conf; + unsigned long link_id; + + for_each_vif_active_link(vif, link_conf, link_id) { + struct mt7996_vif_link *link; + + link = mt7996_vif_link(dev, vif, link_id); + if (link) + link->mt76.beacon_mon_interval = 0; + } + } + mutex_unlock(&dev->mt76.mutex); } diff --git a/drivers/net/wireless/mediatek/mt76/scan.c b/drivers/net/wireless/mediatek/mt76/scan.c index 04cf8a01f20d..fbc10c9657cf 100644 --- a/drivers/net/wireless/mediatek/mt76/scan.c +++ b/drivers/net/wireless/mediatek/mt76/scan.c @@ -105,7 +105,6 @@ void mt76_scan_rx_beacon(struct mt76_dev *dev, struct ieee80211_channel *chan) out: spin_unlock(&dev->scan_lock); } -EXPORT_SYMBOL_GPL(mt76_scan_rx_beacon); void mt76_scan_work(struct work_struct *work) { From e6f48512c1ceebcd1ce6bb83df3b3d56a261507d Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Tue, 10 Mar 2026 19:28:24 -0500 Subject: [PATCH 102/134] wifi: mt76: mt792x: describe USB WFSYS reset with a descriptor Prepare mt792xu_wfsys_reset() for chips that share the same USB WFSYS reset flow but use different register definitions. This is a pure refactor of the current mt7921u path and keeps the reset sequence unchanged. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260311002825.15502-1-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt792x_usb.c | 40 +++++++++++++++---- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_usb.c b/drivers/net/wireless/mediatek/mt76/mt792x_usb.c index 552808458138..a92e872226cf 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_usb.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_usb.c @@ -206,6 +206,24 @@ static void mt792xu_epctl_rst_opt(struct mt792x_dev *dev, bool reset) mt792xu_uhw_wr(&dev->mt76, MT_SSUSB_EPCTL_CSR_EP_RST_OPT, val); } +struct mt792xu_wfsys_desc { + u32 rst_reg; + u32 done_reg; + u32 done_mask; + u32 done_val; + u32 delay_ms; + bool need_status_sel; +}; + +static const struct mt792xu_wfsys_desc mt7921_wfsys_desc = { + .rst_reg = MT_CBTOP_RGU_WF_SUBSYS_RST, + .done_reg = MT_UDMA_CONN_INFRA_STATUS, + .done_mask = MT_UDMA_CONN_WFSYS_INIT_DONE, + .done_val = MT_UDMA_CONN_WFSYS_INIT_DONE, + .delay_ms = 0, + .need_status_sel = true, +}; + int mt792xu_dma_init(struct mt792x_dev *dev, bool resume) { int err; @@ -236,25 +254,31 @@ EXPORT_SYMBOL_GPL(mt792xu_dma_init); int mt792xu_wfsys_reset(struct mt792x_dev *dev) { + const struct mt792xu_wfsys_desc *desc = &mt7921_wfsys_desc; u32 val; int i; mt792xu_epctl_rst_opt(dev, false); - val = mt792xu_uhw_rr(&dev->mt76, MT_CBTOP_RGU_WF_SUBSYS_RST); + val = mt792xu_uhw_rr(&dev->mt76, desc->rst_reg); val |= MT_CBTOP_RGU_WF_SUBSYS_RST_WF_WHOLE_PATH; - mt792xu_uhw_wr(&dev->mt76, MT_CBTOP_RGU_WF_SUBSYS_RST, val); + mt792xu_uhw_wr(&dev->mt76, desc->rst_reg, val); - usleep_range(10, 20); + if (desc->delay_ms) + msleep(desc->delay_ms); + else + usleep_range(10, 20); - val = mt792xu_uhw_rr(&dev->mt76, MT_CBTOP_RGU_WF_SUBSYS_RST); + val = mt792xu_uhw_rr(&dev->mt76, desc->rst_reg); val &= ~MT_CBTOP_RGU_WF_SUBSYS_RST_WF_WHOLE_PATH; - mt792xu_uhw_wr(&dev->mt76, MT_CBTOP_RGU_WF_SUBSYS_RST, val); + mt792xu_uhw_wr(&dev->mt76, desc->rst_reg, val); + + if (desc->need_status_sel) + mt792xu_uhw_wr(&dev->mt76, MT_UDMA_CONN_INFRA_STATUS_SEL, 0); - mt792xu_uhw_wr(&dev->mt76, MT_UDMA_CONN_INFRA_STATUS_SEL, 0); for (i = 0; i < MT792x_WFSYS_INIT_RETRY_COUNT; i++) { - val = mt792xu_uhw_rr(&dev->mt76, MT_UDMA_CONN_INFRA_STATUS); - if (val & MT_UDMA_CONN_WFSYS_INIT_DONE) + val = mt792xu_uhw_rr(&dev->mt76, desc->done_reg); + if ((val & desc->done_mask) == desc->done_val) break; msleep(100); From 56154fef47d104effa9f29ed3db4f805cbc0d640 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Tue, 10 Mar 2026 19:28:25 -0500 Subject: [PATCH 103/134] wifi: mt76: mt792x: fix mt7925u USB WFSYS reset handling mt7925u uses different reset/status registers from mt7921u. Reusing the mt7921u register set causes the WFSYS reset to fail. Add a chip-specific descriptor in mt792xu_wfsys_reset() to select the correct registers and fix mt7925u failing to initialize after a warm reboot. Fixes: d28e1a48952e ("wifi: mt76: mt792x: introduce mt792x-usb module") Cc: stable@vger.kernel.org Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260311002825.15502-2-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt792x_regs.h | 4 ++++ drivers/net/wireless/mediatek/mt76/mt792x_usb.c | 13 ++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_regs.h b/drivers/net/wireless/mediatek/mt76/mt792x_regs.h index 7ddde9286861..d2a8b2b0df32 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_regs.h +++ b/drivers/net/wireless/mediatek/mt76/mt792x_regs.h @@ -392,6 +392,10 @@ #define MT_CBTOP_RGU_WF_SUBSYS_RST MT_CBTOP_RGU(0x600) #define MT_CBTOP_RGU_WF_SUBSYS_RST_WF_WHOLE_PATH BIT(0) +#define MT7925_CBTOP_RGU_WF_SUBSYS_RST 0x70028600 +#define MT7925_WFSYS_INIT_DONE_ADDR 0x184c1604 +#define MT7925_WFSYS_INIT_DONE 0x00001d1e + #define MT_HW_BOUND 0x70010020 #define MT_HW_CHIPID 0x70010200 #define MT_HW_REV 0x70010204 diff --git a/drivers/net/wireless/mediatek/mt76/mt792x_usb.c b/drivers/net/wireless/mediatek/mt76/mt792x_usb.c index a92e872226cf..47827d1c5ccb 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x_usb.c +++ b/drivers/net/wireless/mediatek/mt76/mt792x_usb.c @@ -224,6 +224,15 @@ static const struct mt792xu_wfsys_desc mt7921_wfsys_desc = { .need_status_sel = true, }; +static const struct mt792xu_wfsys_desc mt7925_wfsys_desc = { + .rst_reg = MT7925_CBTOP_RGU_WF_SUBSYS_RST, + .done_reg = MT7925_WFSYS_INIT_DONE_ADDR, + .done_mask = U32_MAX, + .done_val = MT7925_WFSYS_INIT_DONE, + .delay_ms = 20, + .need_status_sel = false, +}; + int mt792xu_dma_init(struct mt792x_dev *dev, bool resume) { int err; @@ -254,7 +263,9 @@ EXPORT_SYMBOL_GPL(mt792xu_dma_init); int mt792xu_wfsys_reset(struct mt792x_dev *dev) { - const struct mt792xu_wfsys_desc *desc = &mt7921_wfsys_desc; + const struct mt792xu_wfsys_desc *desc = is_mt7925(&dev->mt76) ? + &mt7925_wfsys_desc : + &mt7921_wfsys_desc; u32 val; int i; From 73b46379e5231138025b271ce8e158d2a8aa0768 Mon Sep 17 00:00:00 2001 From: Peter Chiu Date: Thu, 12 Mar 2026 17:57:19 +0800 Subject: [PATCH 104/134] wifi: mt76: mt7996: fix RRO EMU configuration Use the correct helper to update specific bitfields instead of overwriting the entire register. Fixes: eedb427eb260 ("wifi: mt76: mt7996: Enable HW RRO for MT7992 chipset") Signed-off-by: Peter Chiu Signed-off-by: Shayne Chen Acked-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260312095724.2117448-1-shayne.chen@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/init.c | 3 +-- drivers/net/wireless/mediatek/mt76/mt7996/mac.c | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index 5aaa93767109..f3239f530aea 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -873,8 +873,7 @@ void mt7996_rro_hw_init(struct mt7996_dev *dev) } } else { /* set emul 3.0 function */ - mt76_wr(dev, MT_RRO_3_0_EMU_CONF, - MT_RRO_3_0_EMU_CONF_EN_MASK); + mt76_set(dev, MT_RRO_3_0_EMU_CONF, MT_RRO_3_0_EMU_CONF_EN_MASK); mt76_wr(dev, MT_RRO_ADDR_ARRAY_BASE0, dev->wed_rro.addr_elem[0].phy_addr); diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index e8bf58dc50b1..c895e8a5de4d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -2577,7 +2577,7 @@ void mt7996_mac_reset_work(struct work_struct *work) mt7996_dma_start(dev, false, false); if (!is_mt7996(&dev->mt76) && dev->mt76.hwrro_mode == MT76_HWRRO_V3) - mt76_wr(dev, MT_RRO_3_0_EMU_CONF, MT_RRO_3_0_EMU_CONF_EN_MASK); + mt76_set(dev, MT_RRO_3_0_EMU_CONF, MT_RRO_3_0_EMU_CONF_EN_MASK); if (mtk_wed_device_active(&dev->mt76.mmio.wed)) { u32 wed_irq_mask = MT_INT_TX_DONE_BAND2 | From cf909557c1ba1215328db41f883ca5af5849f2fd Mon Sep 17 00:00:00 2001 From: Howard Hsu Date: Thu, 12 Mar 2026 17:57:20 +0800 Subject: [PATCH 105/134] wifi: mt76: mt7996: support critical packet mode for MT7990 chipsets For MT7990 chipsets, critical packet mode must be enabled. Without this, some higher priority packets may be placed in the wrong AC queue. Signed-off-by: Howard Hsu Signed-off-by: Shayne Chen Link: https://patch.msgid.link/20260312095724.2117448-2-shayne.chen@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 39e1999143da..834edd31458d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -56,7 +56,7 @@ static int mt7996_start(struct ieee80211_hw *hw) mutex_lock(&dev->mt76.mutex); ret = mt7996_mcu_set_hdr_trans(dev, true); - if (!ret && is_mt7992(&dev->mt76)) { + if (!ret && !is_mt7996(&dev->mt76)) { u8 queue = mt76_connac_lmac_mapping(IEEE80211_AC_VI); ret = mt7996_mcu_cp_support(dev, queue); From 22f9abaf3656cf08d36196bab950668e7fc64381 Mon Sep 17 00:00:00 2001 From: Peter Chiu Date: Thu, 12 Mar 2026 17:57:21 +0800 Subject: [PATCH 106/134] wifi: mt76: mt7996: update WFSYS reset flow for MT7990 chipsets Skip WFSYS reset during bootup for MT7990 chipsets; only reset if L0.5 recovery is triggered. Without this fix, the following kernel error may occur: Internal error: synchronous external abort. Signed-off-by: Peter Chiu Signed-off-by: Shayne Chen Link: https://patch.msgid.link/20260312095724.2117448-3-shayne.chen@mediatek.com Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/init.c | 29 +++++++++++++++++-- .../net/wireless/mediatek/mt76/mt7996/regs.h | 8 +++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index f3239f530aea..8dfb81eabc9a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -791,11 +791,34 @@ static void mt7996_init_work(struct work_struct *work) void mt7996_wfsys_reset(struct mt7996_dev *dev) { - mt76_set(dev, MT_WF_SUBSYS_RST, 0x1); + if (!is_mt7990(&dev->mt76)) { + mt76_set(dev, MT_WF_SUBSYS_RST, 0x1); + msleep(20); + + mt76_clear(dev, MT_WF_SUBSYS_RST, 0x1); + msleep(20); + + return; + } + + if (!dev->recovery.hw_full_reset) + return; + + mt76_set(dev, MT_WF_SUBSYS_RST, + MT_WF_SUBSYS_RST_WHOLE_PATH_RST_REVERT | + MT_WF_SUBSYS_RST_BYPASS_WFDMA_SLP_PROT | + MT_WF_SUBSYS_RST_BYPASS_WFDMA2_SLP_PROT); + mt76_rmw(dev, MT_WF_SUBSYS_RST, + MT_WF_SUBSYS_RST_WHOLE_PATH_RST_REVERT_CYCLE, + u32_encode_bits(0x20, MT_WF_SUBSYS_RST_WHOLE_PATH_RST_REVERT_CYCLE)); + mt76_clear(dev, MT_WF_L05_RST, MT_WF_L05_RST_WF_RST_MASK); + mt76_set(dev, MT_WF_SUBSYS_RST, MT_WF_SUBSYS_RST_WHOLE_PATH_RST); msleep(20); - mt76_clear(dev, MT_WF_SUBSYS_RST, 0x1); - msleep(20); + if (mt76_poll(dev, MT_WF_L05_RST, MT_WF_L05_RST_WF_RST_MASK, 0x1a, 1000)) + return; + + dev_err(dev->mt76.dev, "wfsys reset fail\n"); } static void mt7996_rro_hw_init_v3(struct mt7996_dev *dev) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/regs.h b/drivers/net/wireless/mediatek/mt76/mt7996/regs.h index 393faae2d52b..c6379933b6c3 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/regs.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/regs.h @@ -736,7 +736,15 @@ enum offs_rev { #define MT_HW_REV 0x70010204 #define MT_HW_REV1 0x8a00 +#define MT_WF_L05_RST 0x70028550 +#define MT_WF_L05_RST_WF_RST_MASK GENMASK(4, 0) + #define MT_WF_SUBSYS_RST 0x70028600 +#define MT_WF_SUBSYS_RST_WHOLE_PATH_RST BIT(0) +#define MT_WF_SUBSYS_RST_WHOLE_PATH_RST_REVERT BIT(5) +#define MT_WF_SUBSYS_RST_BYPASS_WFDMA_SLP_PROT BIT(6) +#define MT_WF_SUBSYS_RST_BYPASS_WFDMA2_SLP_PROT BIT(16) +#define MT_WF_SUBSYS_RST_WHOLE_PATH_RST_REVERT_CYCLE GENMASK(15, 8) /* PCIE MAC */ #define MT_PCIE_MAC_BASE 0x74030000 From 9eeea2984c309f4c21c697e163abbef00c4d2b5e Mon Sep 17 00:00:00 2001 From: Rex Lu Date: Thu, 12 Mar 2026 17:57:22 +0800 Subject: [PATCH 107/134] wifi: mt76: mt7996: adjust timeout value for boot-up calibration commands Align the vendor driver by adjusting the timeout values for the MCU_UNI_CMD_EFUSE_CTRL and MCU_UNI_CMD_EXT_EEPROM_CTRL commands. Without this adjustment, false positive command timeout errors may occur, especially on some iPA variants. Signed-off-by: Rex Lu Signed-off-by: Shayne Chen Link: https://patch.msgid.link/20260312095724.2117448-4-shayne.chen@mediatek.com Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/mcu.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index 2a50d0758d9c..4bf22318396f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -278,7 +278,8 @@ mt7996_mcu_set_timeout(struct mt76_dev *mdev, int cmd) mdev->mcu.timeout = 2 * HZ; return; case MCU_UNI_CMD_EFUSE_CTRL: - mdev->mcu.timeout = 20 * HZ; + case MCU_UNI_CMD_EXT_EEPROM_CTRL: + mdev->mcu.timeout = 30 * HZ; return; default: break; From fc4533b5db80792fccc2bf4a14322e7af1e55980 Mon Sep 17 00:00:00 2001 From: StanleyYP Wang Date: Thu, 12 Mar 2026 17:57:24 +0800 Subject: [PATCH 108/134] wifi: mt76: mt7996: fix issues with manually triggered radar detection Disallow triggering radar detection on non-DFS channels to prevent paused TX queues from failing to resume, as a channel switch is not performed in this case. Signed-off-by: StanleyYP Wang Signed-off-by: Shayne Chen Link: https://patch.msgid.link/20260312095724.2117448-6-shayne.chen@mediatek.com Signed-off-by: Felix Fietkau --- .../wireless/mediatek/mt76/mt7996/debugfs.c | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/debugfs.c b/drivers/net/wireless/mediatek/mt76/mt7996/debugfs.c index 6cc63f87b222..34af800964d1 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/debugfs.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/debugfs.c @@ -226,14 +226,23 @@ mt7996_radar_trigger(void *data, u64 val) #define RADAR_BACKGROUND 2 struct mt7996_dev *dev = data; struct mt7996_phy *phy = mt7996_band_phy(dev, NL80211_BAND_5GHZ); - int rdd_idx; + struct cfg80211_chan_def *chandef; + int rdd_idx, ret; if (!phy || !val || val > RADAR_BACKGROUND) return -EINVAL; - if (val == RADAR_BACKGROUND && !dev->rdd2_phy) { - dev_err(dev->mt76.dev, "Background radar is not enabled\n"); - return -EINVAL; + if (test_bit(MT76_SCANNING, &phy->mt76->state)) + return -EBUSY; + + if (val == RADAR_BACKGROUND) { + if (!dev->rdd2_phy || !cfg80211_chandef_valid(&dev->rdd2_chandef)) { + dev_err(dev->mt76.dev, "Background radar is not enabled\n"); + return -EINVAL; + } + chandef = &dev->rdd2_chandef; + } else { + chandef = &phy->mt76->chandef; } rdd_idx = mt7996_get_rdd_idx(phy, val == RADAR_BACKGROUND); @@ -242,6 +251,11 @@ mt7996_radar_trigger(void *data, u64 val) return -EINVAL; } + ret = cfg80211_chandef_dfs_required(dev->mt76.hw->wiphy, chandef, + NL80211_IFTYPE_AP); + if (ret <= 0) + return ret; + return mt7996_mcu_rdd_cmd(dev, RDD_RADAR_EMULATE, rdd_idx, 0); } From a1353d994c167c818ef4165653a5ccec091ba534 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Fri, 6 Mar 2026 17:22:20 -0600 Subject: [PATCH 109/134] wifi: mt76: mt7925: pass mlink to sta_amsdu_tlv() Drop the mt792x_sta_to_link() lookup in mt7925_mcu_sta_amsdu_tlv() and pass mlink from the caller instead. The link context is already known so the lookup is redundant. This makes link ownership explicit and keeps the helper lookup-free. No functional change intended. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260306232238.2039675-2-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/mcu.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c index abcdd0e0b3b5..fa5f79004a6e 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c @@ -1726,10 +1726,9 @@ mt7925_mcu_sta_vht_tlv(struct sk_buff *skb, struct ieee80211_link_sta *link_sta) static void mt7925_mcu_sta_amsdu_tlv(struct sk_buff *skb, struct ieee80211_vif *vif, - struct ieee80211_link_sta *link_sta) + struct ieee80211_link_sta *link_sta, + struct mt792x_link_sta *mlink) { - struct mt792x_sta *msta = (struct mt792x_sta *)link_sta->sta->drv_priv; - struct mt792x_link_sta *mlink; struct sta_rec_amsdu *amsdu; struct tlv *tlv; @@ -1745,7 +1744,6 @@ mt7925_mcu_sta_amsdu_tlv(struct sk_buff *skb, amsdu->max_amsdu_num = 8; amsdu->amsdu_en = true; - mlink = mt792x_sta_to_link(msta, link_sta->link_id); mlink->wcid.amsdu = true; switch (link_sta->agg.max_amsdu_len) { @@ -1966,6 +1964,7 @@ mt7925_mcu_sta_cmd(struct mt76_phy *phy, struct mt792x_vif *mvif = (struct mt792x_vif *)info->vif->drv_priv; struct mt76_dev *dev = phy->dev; struct mt792x_bss_conf *mconf; + struct mt792x_link_sta *mlink; struct sk_buff *skb; int conn_state; @@ -1980,6 +1979,8 @@ mt7925_mcu_sta_cmd(struct mt76_phy *phy, CONN_STATE_DISCONNECT; if (info->enable && info->link_sta) { + mlink = container_of(info->wcid, struct mt792x_link_sta, wcid); + mt76_connac_mcu_sta_basic_tlv(dev, skb, info->link_conf, info->link_sta, conn_state, info->newly); @@ -1987,7 +1988,7 @@ mt7925_mcu_sta_cmd(struct mt76_phy *phy, mt7925_mcu_sta_ht_tlv(skb, info->link_sta); mt7925_mcu_sta_vht_tlv(skb, info->link_sta); mt76_connac_mcu_sta_uapsd(skb, info->vif, info->link_sta->sta); - mt7925_mcu_sta_amsdu_tlv(skb, info->vif, info->link_sta); + mt7925_mcu_sta_amsdu_tlv(skb, info->vif, info->link_sta, mlink); mt7925_mcu_sta_he_tlv(skb, info->link_sta); mt7925_mcu_sta_he_6g_tlv(skb, info->link_sta); mt7925_mcu_sta_eht_tlv(skb, info->link_sta); From ea757740dd87c0b00c4844dd3282dff4d83fa3c7 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Fri, 6 Mar 2026 17:22:21 -0600 Subject: [PATCH 110/134] wifi: mt76: mt7925: pass WCID indices to bss_basic_tlv() Drop the mt792x_sta_to_link() lookup in mt7925_mcu_bss_basic_tlv() and pass the resolved WCID indices from the caller instead. The link context is already known, so the lookup is redundant. This makes link ownership explicit and keeps the helper lookup-free. No functional change intended. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260306232238.2039675-3-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7925/main.c | 34 ++++++---- .../net/wireless/mediatek/mt76/mt7925/mcu.c | 65 ++++++++++++------- .../net/wireless/mediatek/mt76/mt7925/mcu.h | 7 ++ 3 files changed, 70 insertions(+), 36 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index afcc0fa4aa35..353461f0e169 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -850,20 +850,22 @@ mt7925_get_rates_table(struct ieee80211_hw *hw, struct ieee80211_vif *vif, static int mt7925_mac_link_sta_add(struct mt76_dev *mdev, struct ieee80211_vif *vif, - struct ieee80211_link_sta *link_sta) + struct ieee80211_link_sta *link_sta, + struct mt792x_link_sta *mlink) { struct mt792x_dev *dev = container_of(mdev, struct mt792x_dev, mt76); struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv; struct ieee80211_bss_conf *link_conf; struct mt792x_bss_conf *mconf; u8 link_id = link_sta->link_id; - struct mt792x_link_sta *mlink; struct mt792x_sta *msta; struct mt76_wcid *wcid; int ret, idx; msta = (struct mt792x_sta *)link_sta->sta->drv_priv; - mlink = mt792x_sta_to_link(msta, link_id); + + if (WARN_ON_ONCE(!mlink)) + return -EINVAL; idx = mt76_wcid_alloc(dev->mt76.wcid_mask, MT792x_WTBL_STA - 1); if (idx < 0) @@ -898,12 +900,21 @@ static int mt7925_mac_link_sta_add(struct mt76_dev *mdev, /* should update bss info before STA add */ if (vif->type == NL80211_IFTYPE_STATION && !link_sta->sta->tdls) { - if (ieee80211_vif_is_mld(vif)) - mt7925_mcu_add_bss_info(&dev->phy, mconf->mt76.ctx, - link_conf, link_sta, link_sta != mlink->pri_link); - else - mt7925_mcu_add_bss_info(&dev->phy, mconf->mt76.ctx, - link_conf, link_sta, false); + struct mt792x_link_sta *mlink_bc; + + mlink_bc = mt792x_sta_to_link(&mvif->sta, mconf->link_id); + + if (ieee80211_vif_is_mld(vif)) { + mt7925_mcu_add_bss_info_sta(&dev->phy, mconf->mt76.ctx, + link_conf, link_sta, + mlink_bc->wcid.idx, mlink->wcid.idx, + link_sta != mlink->pri_link); + } else { + mt7925_mcu_add_bss_info_sta(&dev->phy, mconf->mt76.ctx, + link_conf, link_sta, + mlink_bc->wcid.idx, mlink->wcid.idx, + false); + } } if (ieee80211_vif_is_mld(vif) && @@ -965,7 +976,7 @@ mt7925_mac_sta_add_links(struct mt792x_dev *dev, struct ieee80211_vif *vif, mlink->wcid.def_wcid = &msta->deflink.wcid; link_sta = mt792x_sta_to_link_sta(vif, sta, link_id); - mt7925_mac_link_sta_add(&dev->mt76, vif, link_sta); + mt7925_mac_link_sta_add(&dev->mt76, vif, link_sta, mlink); } return err; @@ -989,7 +1000,8 @@ int mt7925_mac_sta_add(struct mt76_dev *mdev, struct ieee80211_vif *vif, err = mt7925_mac_sta_add_links(dev, vif, sta, sta->valid_links); } else { - err = mt7925_mac_link_sta_add(mdev, vif, &sta->deflink); + err = mt7925_mac_link_sta_add(mdev, vif, &sta->deflink, + &msta->deflink); } return err; diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c index fa5f79004a6e..76bcfaf8ebfa 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c @@ -2476,7 +2476,9 @@ mt7925_mcu_bss_basic_tlv(struct sk_buff *skb, struct ieee80211_bss_conf *link_conf, struct ieee80211_link_sta *link_sta, struct ieee80211_chanctx_conf *ctx, - struct mt76_phy *phy, u16 wlan_idx, + struct mt76_phy *phy, + u16 bmc_tx_wlan_idx, + u16 sta_wlan_idx, bool enable) { struct ieee80211_vif *vif = link_conf->vif; @@ -2485,7 +2487,6 @@ mt7925_mcu_bss_basic_tlv(struct sk_buff *skb, &link_conf->chanreq.oper; enum nl80211_band band = chandef->chan->band; struct mt76_connac_bss_basic_tlv *basic_req; - struct mt792x_link_sta *mlink; struct tlv *tlv; int conn_type; u8 idx; @@ -2509,20 +2510,9 @@ mt7925_mcu_bss_basic_tlv(struct sk_buff *skb, basic_req->phymode = mt76_connac_get_phy_mode(phy, vif, band, link_sta); basic_req->bcn_interval = cpu_to_le16(link_conf->beacon_int); basic_req->dtim_period = link_conf->dtim_period; - basic_req->bmc_tx_wlan_idx = cpu_to_le16(wlan_idx); + basic_req->bmc_tx_wlan_idx = cpu_to_le16(bmc_tx_wlan_idx); basic_req->link_idx = mconf->mt76.idx; - - if (link_sta) { - struct mt792x_sta *msta; - - msta = (struct mt792x_sta *)link_sta->sta->drv_priv; - mlink = mt792x_sta_to_link(msta, link_sta->link_id); - - } else { - mlink = &mconf->vif->sta.deflink; - } - - basic_req->sta_idx = cpu_to_le16(mlink->wcid.idx); + basic_req->sta_idx = cpu_to_le16(sta_wlan_idx); basic_req->omac_idx = mconf->mt76.omac_idx; basic_req->band_idx = mconf->mt76.band_idx; basic_req->wmm_idx = mconf->mt76.wmm_idx; @@ -2829,16 +2819,16 @@ void mt7925_mcu_del_dev(struct mt76_dev *mdev, &dev_req, sizeof(dev_req), true); } -int mt7925_mcu_add_bss_info(struct mt792x_phy *phy, - struct ieee80211_chanctx_conf *ctx, - struct ieee80211_bss_conf *link_conf, - struct ieee80211_link_sta *link_sta, - int enable) +int mt7925_mcu_add_bss_info_sta(struct mt792x_phy *phy, + struct ieee80211_chanctx_conf *ctx, + struct ieee80211_bss_conf *link_conf, + struct ieee80211_link_sta *link_sta, + u16 bmc_tx_wlan_idx, + u16 sta_wlan_idx, + int enable) { - struct mt792x_vif *mvif = (struct mt792x_vif *)link_conf->vif->drv_priv; struct mt792x_bss_conf *mconf = mt792x_link_conf_to_mconf(link_conf); struct mt792x_dev *dev = phy->dev; - struct mt792x_link_sta *mlink_bc; struct sk_buff *skb; skb = __mt7925_mcu_alloc_bss_req(&dev->mt76, &mconf->mt76, @@ -2846,11 +2836,9 @@ int mt7925_mcu_add_bss_info(struct mt792x_phy *phy, if (IS_ERR(skb)) return PTR_ERR(skb); - mlink_bc = mt792x_sta_to_link(&mvif->sta, mconf->link_id); - /* bss_basic must be first */ mt7925_mcu_bss_basic_tlv(skb, link_conf, link_sta, ctx, phy->mt76, - mlink_bc->wcid.idx, enable); + bmc_tx_wlan_idx, sta_wlan_idx, enable); mt7925_mcu_bss_sec_tlv(skb, link_conf); mt7925_mcu_bss_bmc_tlv(skb, phy, ctx, link_conf); mt7925_mcu_bss_qos_tlv(skb, link_conf); @@ -2871,6 +2859,33 @@ int mt7925_mcu_add_bss_info(struct mt792x_phy *phy, MCU_UNI_CMD(BSS_INFO_UPDATE), true); } +int mt7925_mcu_add_bss_info(struct mt792x_phy *phy, + struct ieee80211_chanctx_conf *ctx, + struct ieee80211_bss_conf *link_conf, + struct ieee80211_link_sta *link_sta, + int enable) +{ + struct mt792x_vif *mvif = (struct mt792x_vif *)link_conf->vif->drv_priv; + struct mt792x_bss_conf *mconf = mt792x_link_conf_to_mconf(link_conf); + struct mt792x_link_sta *mlink_bc; + struct mt792x_link_sta *mlink; + + mlink_bc = mt792x_sta_to_link(&mvif->sta, mconf->link_id); + + if (link_sta) { + struct mt792x_sta *msta = (void *)link_sta->sta->drv_priv; + + mlink = mt792x_sta_to_link(msta, link_sta->link_id); + if (WARN_ON(!mlink)) + return -EINVAL; + } else { + mlink = &mconf->vif->sta.deflink; + } + + return mt7925_mcu_add_bss_info_sta(phy, ctx, link_conf, link_sta, + mlink_bc->wcid.idx, mlink->wcid.idx, enable); +} + int mt7925_mcu_set_dbdc(struct mt76_phy *phy, bool enable) { struct mt76_dev *mdev = phy->dev; diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h index e09e0600534a..56e2772f3ffe 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h @@ -693,6 +693,13 @@ int mt7925_mcu_add_bss_info(struct mt792x_phy *phy, struct ieee80211_bss_conf *link_conf, struct ieee80211_link_sta *link_sta, int enable); +int mt7925_mcu_add_bss_info_sta(struct mt792x_phy *phy, + struct ieee80211_chanctx_conf *ctx, + struct ieee80211_bss_conf *link_conf, + struct ieee80211_link_sta *link_sta, + u16 bmc_tx_wlan_idx, + u16 sta_wlan_idx, + int enable); int mt7925_mcu_set_timing(struct mt792x_phy *phy, struct ieee80211_bss_conf *link_conf); int mt7925_mcu_set_deep_sleep(struct mt792x_dev *dev, bool enable); From ff643b81bc38eaff6c0ab783a62e4ba9e10d2476 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Fri, 6 Mar 2026 17:22:22 -0600 Subject: [PATCH 111/134] wifi: mt76: mt7925: pass mlink and mconf to sta_mld_tlv() Drop the mt792x_sta_to_link() lookup in mt7925_mcu_sta_mld_tlv() and pass mlink and mconf from the caller instead. The link context is already known at the call site, making the lookup redundant. This keeps the helper lookup-free and makes MLD link selection explicit. No functional change intended. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260306232238.2039675-4-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7925/mcu.c | 53 +++++++++++++------ 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c index 76bcfaf8ebfa..1e46adf7ddd9 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c @@ -1914,36 +1914,53 @@ mt7925_mcu_sta_eht_mld_tlv(struct sk_buff *skb, static void mt7925_mcu_sta_mld_tlv(struct sk_buff *skb, - struct ieee80211_vif *vif, struct ieee80211_sta *sta) + struct ieee80211_vif *vif, + struct ieee80211_sta *sta, + struct mt792x_bss_conf *mconf, + struct mt792x_link_sta *mlink) { struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv; struct mt792x_sta *msta = (struct mt792x_sta *)sta->drv_priv; - unsigned long valid = mvif->valid_links; - struct mt792x_bss_conf *mconf; - struct mt792x_link_sta *mlink; + struct mt792x_dev *dev = mvif->phy->dev; + struct mt792x_bss_conf *mconf_pri; struct sta_rec_mld *mld; struct tlv *tlv; - int i, cnt = 0; + u8 cnt = 0; + + /* Primary link always uses driver's deflink WCID. */ + mconf_pri = (msta->deflink_id != IEEE80211_LINK_UNSPECIFIED) ? + mt792x_vif_to_link(mvif, msta->deflink_id) : NULL; + + /* If caller is operating on deflink, reuse its mconf as primary. */ + if (!mconf_pri && mlink == &msta->deflink) + mconf_pri = mconf; + + if (!mconf_pri) { + dev_warn_ratelimited(dev->mt76.dev, + "mt7925: MLD_TLV_LINK skip (no primary mconf) sta=%pM\n", + sta->addr); + return; + } tlv = mt76_connac_mcu_add_tlv(skb, STA_REC_MLD, sizeof(*mld)); mld = (struct sta_rec_mld *)tlv; memcpy(mld->mac_addr, sta->addr, ETH_ALEN); + mld->primary_id = cpu_to_le16(msta->deflink.wcid.idx); mld->wlan_id = cpu_to_le16(msta->deflink.wcid.idx); - mld->link_num = min_t(u8, hweight16(mvif->valid_links), 2); - for_each_set_bit(i, &valid, IEEE80211_MLD_MAX_NUM_LINKS) { - if (cnt == mld->link_num) - break; + /* Always encode primary link first. */ + mld->link[cnt].wlan_id = cpu_to_le16(msta->deflink.wcid.idx); + mld->link[cnt++].bss_idx = mconf_pri->mt76.idx; - mconf = mt792x_vif_to_link(mvif, i); - mlink = mt792x_sta_to_link(msta, i); + /* Optionally encode the currently-updated secondary link. */ + if (mlink && mlink != &msta->deflink && mconf) { + mld->secondary_id = cpu_to_le16(mlink->wcid.idx); mld->link[cnt].wlan_id = cpu_to_le16(mlink->wcid.idx); mld->link[cnt++].bss_idx = mconf->mt76.idx; - - if (mlink != &msta->deflink) - mld->secondary_id = cpu_to_le16(mlink->wcid.idx); } + + mld->link_num = cnt; } static void @@ -1969,6 +1986,7 @@ mt7925_mcu_sta_cmd(struct mt76_phy *phy, int conn_state; mconf = mt792x_vif_to_link(mvif, info->wcid->link_id); + mlink = container_of(info->wcid, struct mt792x_link_sta, wcid); skb = __mt76_connac_mcu_alloc_sta_req(dev, &mconf->mt76, info->wcid, MT7925_STA_UPDATE_MAX_SIZE); @@ -1979,8 +1997,6 @@ mt7925_mcu_sta_cmd(struct mt76_phy *phy, CONN_STATE_DISCONNECT; if (info->enable && info->link_sta) { - mlink = container_of(info->wcid, struct mt792x_link_sta, wcid); - mt76_connac_mcu_sta_basic_tlv(dev, skb, info->link_conf, info->link_sta, conn_state, info->newly); @@ -1999,7 +2015,10 @@ mt7925_mcu_sta_cmd(struct mt76_phy *phy, info->state); if (info->state != MT76_STA_INFO_STATE_NONE) { - mt7925_mcu_sta_mld_tlv(skb, info->vif, info->link_sta->sta); + mt7925_mcu_sta_mld_tlv(skb, info->vif, + info->link_sta->sta, + mconf, mlink); + mt7925_mcu_sta_eht_mld_tlv(skb, info->vif, info->link_sta->sta); } } From dc019e3294c7e3c6e997bb11732d46ce0d9211e9 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Fri, 6 Mar 2026 17:22:23 -0600 Subject: [PATCH 112/134] wifi: mt76: mt7925: pass mlink to mcu_sta_update() Drop the mt792x_sta_to_link() lookup in mt7925_mcu_sta_update() and pass the resolved mlink from the caller instead. The link context is already known at the call site, making the lookup redundant. This keeps the helper lookup-free and makes WCID selection explicit. No functional change intended. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260306232238.2039675-5-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7925/mac.c | 3 ++- .../net/wireless/mediatek/mt76/mt7925/main.c | 27 +++++++++++++------ .../net/wireless/mediatek/mt76/mt7925/mcu.c | 12 +++------ .../wireless/mediatek/mt76/mt7925/mt7925.h | 4 ++- 4 files changed, 28 insertions(+), 18 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c index 0bafa8e770a6..c47bd812b66b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c @@ -1285,7 +1285,8 @@ mt7925_vif_connect_iter(void *priv, u8 *mac, if (vif->type == NL80211_IFTYPE_AP) { mt76_connac_mcu_uni_add_bss(dev->phy.mt76, vif, &mvif->sta.deflink.wcid, true, NULL); - mt7925_mcu_sta_update(dev, NULL, vif, true, + mt7925_mcu_sta_update(dev, NULL, vif, + &mvif->sta.deflink, true, MT76_STA_INFO_STATE_NONE); mt7925_mcu_uni_add_beacon_offload(dev, hw, vif, true); } diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index 353461f0e169..c65e32a14c01 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -919,23 +919,31 @@ static int mt7925_mac_link_sta_add(struct mt76_dev *mdev, if (ieee80211_vif_is_mld(vif) && link_sta == mlink->pri_link) { - ret = mt7925_mcu_sta_update(dev, link_sta, vif, true, + ret = mt7925_mcu_sta_update(dev, link_sta, vif, + mlink, true, MT76_STA_INFO_STATE_NONE); if (ret) return ret; } else if (ieee80211_vif_is_mld(vif) && link_sta != mlink->pri_link) { + struct mt792x_link_sta *pri_mlink; + + pri_mlink = mt792x_sta_to_link(msta, mlink->pri_link->link_id); + ret = mt7925_mcu_sta_update(dev, mlink->pri_link, vif, - true, MT76_STA_INFO_STATE_ASSOC); + pri_mlink, true, + MT76_STA_INFO_STATE_ASSOC); if (ret) return ret; - ret = mt7925_mcu_sta_update(dev, link_sta, vif, true, + ret = mt7925_mcu_sta_update(dev, link_sta, vif, + mlink, true, MT76_STA_INFO_STATE_ASSOC); if (ret) return ret; } else { - ret = mt7925_mcu_sta_update(dev, link_sta, vif, true, + ret = mt7925_mcu_sta_update(dev, link_sta, vif, + mlink, true, MT76_STA_INFO_STATE_NONE); if (ret) return ret; @@ -1075,7 +1083,8 @@ static void mt7925_mac_link_sta_assoc(struct mt76_dev *mdev, MT_WTBL_UPDATE_ADM_COUNT_CLEAR); memset(mlink->airtime_ac, 0, sizeof(mlink->airtime_ac)); - mt7925_mcu_sta_update(dev, link_sta, vif, true, MT76_STA_INFO_STATE_ASSOC); + mt7925_mcu_sta_update(dev, link_sta, vif, mlink, true, + MT76_STA_INFO_STATE_ASSOC); mt792x_mutex_release(dev); } @@ -1119,7 +1128,7 @@ static void mt7925_mac_link_sta_remove(struct mt76_dev *mdev, mt76_connac_free_pending_tx_skbs(&dev->pm, &mlink->wcid); mt76_connac_pm_wake(&dev->mphy, &dev->pm); - mt7925_mcu_sta_update(dev, link_sta, vif, false, + mt7925_mcu_sta_update(dev, link_sta, vif, mlink, false, MT76_STA_INFO_STATE_NONE); mt7925_mac_wtbl_update(dev, mlink->wcid.idx, MT_WTBL_UPDATE_ADM_COUNT_CLEAR); @@ -1744,7 +1753,8 @@ mt7925_start_ap(struct ieee80211_hw *hw, struct ieee80211_vif *vif, if (err) goto out; - err = mt7925_mcu_sta_update(dev, NULL, vif, true, + err = mt7925_mcu_sta_update(dev, NULL, vif, + &mvif->sta.deflink, true, MT76_STA_INFO_STATE_NONE); out: mt792x_mutex_release(dev); @@ -1887,7 +1897,8 @@ static void mt7925_vif_cfg_changed(struct ieee80211_hw *hw, mt792x_mutex_acquire(dev); if (changed & BSS_CHANGED_ASSOC) { - mt7925_mcu_sta_update(dev, NULL, vif, true, + mt7925_mcu_sta_update(dev, NULL, vif, + &mvif->sta.deflink, true, MT76_STA_INFO_STATE_ASSOC); mt7925_mcu_set_beacon_filter(dev, vif, vif->cfg.assoc); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c index 1e46adf7ddd9..c97f5917c854 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c @@ -2036,7 +2036,9 @@ mt7925_mcu_sta_cmd(struct mt76_phy *phy, int mt7925_mcu_sta_update(struct mt792x_dev *dev, struct ieee80211_link_sta *link_sta, - struct ieee80211_vif *vif, bool enable, + struct ieee80211_vif *vif, + struct mt792x_link_sta *mlink, + bool enable, enum mt76_sta_info_state state) { struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv; @@ -2051,14 +2053,8 @@ int mt7925_mcu_sta_update(struct mt792x_dev *dev, .offload_fw = true, .rcpi = to_rcpi(rssi), }; - struct mt792x_sta *msta; - struct mt792x_link_sta *mlink; - if (link_sta) { - msta = (struct mt792x_sta *)link_sta->sta->drv_priv; - mlink = mt792x_sta_to_link(msta, link_sta->link_id); - } - info.wcid = link_sta ? &mlink->wcid : &mvif->sta.deflink.wcid; + info.wcid = &mlink->wcid; info.newly = state != MT76_STA_INFO_STATE_ASSOC; return mt7925_mcu_sta_cmd(&dev->mphy, &info); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h index 0f0eff748bb7..95f29dae4d9d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h @@ -250,7 +250,9 @@ int mt7925_mcu_set_bss_pm(struct mt792x_dev *dev, bool enable); int mt7925_mcu_sta_update(struct mt792x_dev *dev, struct ieee80211_link_sta *link_sta, - struct ieee80211_vif *vif, bool enable, + struct ieee80211_vif *vif, + struct mt792x_link_sta *mlink, + bool enable, enum mt76_sta_info_state state); int mt7925_mcu_set_chan_info(struct mt792x_phy *phy, u16 tag); int mt7925_mcu_set_tx(struct mt792x_dev *dev, struct ieee80211_bss_conf *bss_conf); From ecc57c9899e60456015fb355bfcf7650af7d13c1 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Fri, 6 Mar 2026 17:22:24 -0600 Subject: [PATCH 113/134] wifi: mt76: mt7925: resolve primary mlink via def_wcid Use mlink->wcid.def_wcid to obtain the primary mlink in mt7925_mac_link_sta_add() instead of calling mt792x_sta_to_link(). The primary link context is already carried by the WCID, so the extra lookup is redundant. This makes the add path follow the existing WCID association directly. No functional change intended. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260306232238.2039675-6-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/main.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index c65e32a14c01..d7a09a0a79fb 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -927,8 +927,17 @@ static int mt7925_mac_link_sta_add(struct mt76_dev *mdev, } else if (ieee80211_vif_is_mld(vif) && link_sta != mlink->pri_link) { struct mt792x_link_sta *pri_mlink; + struct mt76_wcid *pri_wcid; - pri_mlink = mt792x_sta_to_link(msta, mlink->pri_link->link_id); + /* alternative lookup via def_wcid */ + pri_wcid = mlink->wcid.def_wcid; + + pri_mlink = pri_wcid ? + container_of(pri_wcid, struct mt792x_link_sta, wcid) : + NULL; + + if (WARN_ON_ONCE(!pri_mlink)) + return -EINVAL; ret = mt7925_mcu_sta_update(dev, mlink->pri_link, vif, pri_mlink, true, From 9e4d518a4707175e1154876b760d4f2b39967e9d Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Fri, 6 Mar 2026 17:22:25 -0600 Subject: [PATCH 114/134] wifi: mt76: mt7925: pass mlink to mac_link_sta_remove() Drop the mt792x_sta_to_link() lookup in mt7925_mac_link_sta_remove() and pass mlink from mt7925_mac_sta_remove_links() instead. The link is already resolved there, making the extra lookup redundant. This keeps the remove helper lookup-free and avoids hidden dependence on msta->link[link_id] during teardown. No functional change intended. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260306232238.2039675-7-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/main.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index d7a09a0a79fb..ddff6c5ab876 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -1121,16 +1121,12 @@ EXPORT_SYMBOL_GPL(mt7925_mac_sta_event); static void mt7925_mac_link_sta_remove(struct mt76_dev *mdev, struct ieee80211_vif *vif, - struct ieee80211_link_sta *link_sta) + struct ieee80211_link_sta *link_sta, + struct mt792x_link_sta *mlink) { struct mt792x_dev *dev = container_of(mdev, struct mt792x_dev, mt76); struct ieee80211_bss_conf *link_conf; u8 link_id = link_sta->link_id; - struct mt792x_link_sta *mlink; - struct mt792x_sta *msta; - - msta = (struct mt792x_sta *)link_sta->sta->drv_priv; - mlink = mt792x_sta_to_link(msta, link_id); mt7925_roc_abort_sync(dev); @@ -1213,7 +1209,7 @@ mt7925_mac_sta_remove_links(struct mt792x_dev *dev, struct ieee80211_vif *vif, if (!mlink) continue; - mt7925_mac_link_sta_remove(&dev->mt76, vif, link_sta); + mt7925_mac_link_sta_remove(&dev->mt76, vif, link_sta, mlink); wcid = &mlink->wcid; rcu_assign_pointer(msta->link[link_id], NULL); From 8951131c18979b9d40d0f8a164be0432e8d1083b Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Fri, 6 Mar 2026 17:22:26 -0600 Subject: [PATCH 115/134] wifi: mt76: mt7925: pass mlink to sta_hdr_trans_tlv() Drop the mt792x_sta_to_link() lookup in mt7925_mcu_sta_hdr_trans_tlv() and pass the resolved mlink from the caller instead. The link is already known at the call site, making the lookup redundant. This keeps the helper lookup-free and makes WCID selection explicit. No functional change intended. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260306232238.2039675-8-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7925/mcu.c | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c index c97f5917c854..476aec8337a9 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c @@ -1066,9 +1066,9 @@ EXPORT_SYMBOL_GPL(mt7925_run_firmware); static void mt7925_mcu_sta_hdr_trans_tlv(struct sk_buff *skb, struct ieee80211_vif *vif, - struct ieee80211_link_sta *link_sta) + struct ieee80211_link_sta *link_sta, + struct mt792x_link_sta *mlink) { - struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv; struct sta_rec_hdr_trans *hdr_trans; struct mt76_wcid *wcid; struct tlv *tlv; @@ -1082,15 +1082,7 @@ mt7925_mcu_sta_hdr_trans_tlv(struct sk_buff *skb, else hdr_trans->from_ds = true; - if (link_sta) { - struct mt792x_sta *msta = (struct mt792x_sta *)link_sta->sta->drv_priv; - struct mt792x_link_sta *mlink; - - mlink = mt792x_sta_to_link(msta, link_sta->link_id); - wcid = &mlink->wcid; - } else { - wcid = &mvif->sta.deflink.wcid; - } + wcid = &mlink->wcid; if (!wcid) return; @@ -1127,7 +1119,10 @@ int mt7925_mcu_wtbl_update_hdr_trans(struct mt792x_dev *dev, return PTR_ERR(skb); /* starec hdr trans */ - mt7925_mcu_sta_hdr_trans_tlv(skb, vif, link_sta); + if (!link_sta) + mlink = &mvif->sta.deflink; + + mt7925_mcu_sta_hdr_trans_tlv(skb, vif, link_sta, mlink); return mt76_mcu_skb_send_msg(&dev->mt76, skb, MCU_WMWA_UNI_CMD(STA_REC_UPDATE), true); } @@ -2028,7 +2023,10 @@ mt7925_mcu_sta_cmd(struct mt76_phy *phy, mt76_connac_mcu_add_tlv(skb, STA_REC_MLD_OFF, sizeof(struct tlv)); } else { - mt7925_mcu_sta_hdr_trans_tlv(skb, info->vif, info->link_sta); + if (!info->link_sta) + mlink = &mvif->sta.deflink; + + mt7925_mcu_sta_hdr_trans_tlv(skb, info->vif, info->link_sta, mlink); } return mt76_mcu_skb_send_msg(dev, skb, info->cmd, true); From 292651cafac012ebad3d9d68b38f69117da4685d Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Fri, 6 Mar 2026 17:22:27 -0600 Subject: [PATCH 116/134] wifi: mt76: mt7925: validate mlink in sta_hdr_trans_tlv() Replace the dead wcid NULL check in mt7925_mcu_sta_hdr_trans_tlv() with a WARN_ON_ONCE() guard on mlink before dereferencing mlink->wcid. wcid is always derived from mlink, so mlink is the only meaningful object to validate here. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260306232238.2039675-9-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/mcu.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c index 476aec8337a9..ba471341e8d0 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c @@ -1082,11 +1082,11 @@ mt7925_mcu_sta_hdr_trans_tlv(struct sk_buff *skb, else hdr_trans->from_ds = true; - wcid = &mlink->wcid; - - if (!wcid) + if (WARN_ON_ONCE(!mlink)) return; + wcid = &mlink->wcid; + hdr_trans->dis_rx_hdr_tran = !test_bit(MT_WCID_FLAG_HDR_TRANS, &wcid->flags); if (test_bit(MT_WCID_FLAG_4ADDR, &wcid->flags)) { hdr_trans->to_ds = true; From 46a2264681500f3fff9ebf7ad64f5704d2b568bb Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Fri, 6 Mar 2026 17:22:28 -0600 Subject: [PATCH 117/134] wifi: mt76: mt7925: pass mlink to wtbl_update_hdr_trans() Drop the mt792x_sta_to_link() lookup in mt7925_mcu_wtbl_update_hdr_trans() and pass the resolved mlink from the caller instead. The link context is already known at the call site, making the lookup redundant. This keeps the helper lookup-free and makes link ownership explicit. No functional change intended. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260306232238.2039675-10-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/main.c | 2 +- drivers/net/wireless/mediatek/mt76/mt7925/mcu.c | 8 ++------ drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h | 1 + 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index ddff6c5ab876..f5dd91dacca5 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -1598,7 +1598,7 @@ static void mt7925_sta_set_decap_offload(struct ieee80211_hw *hw, if (!mlink->wcid.sta) continue; - mt7925_mcu_wtbl_update_hdr_trans(dev, vif, sta, i); + mt7925_mcu_wtbl_update_hdr_trans(dev, vif, sta, mlink, i); } mt792x_mutex_release(dev); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c index ba471341e8d0..04650e201071 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c @@ -1097,18 +1097,14 @@ mt7925_mcu_sta_hdr_trans_tlv(struct sk_buff *skb, int mt7925_mcu_wtbl_update_hdr_trans(struct mt792x_dev *dev, struct ieee80211_vif *vif, struct ieee80211_sta *sta, + struct mt792x_link_sta *mlink, int link_id) { - struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv; struct ieee80211_link_sta *link_sta = sta ? &sta->deflink : NULL; - struct mt792x_link_sta *mlink; + struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv; struct mt792x_bss_conf *mconf; - struct mt792x_sta *msta; struct sk_buff *skb; - msta = sta ? (struct mt792x_sta *)sta->drv_priv : &mvif->sta; - - mlink = mt792x_sta_to_link(msta, link_id); link_sta = mt792x_sta_to_link_sta(vif, sta, link_id); mconf = mt792x_vif_to_link(mvif, link_id); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h index 95f29dae4d9d..e28972f0615b 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h @@ -371,6 +371,7 @@ int mt7925_mcu_set_rts_thresh(struct mt792x_phy *phy, u32 val); int mt7925_mcu_wtbl_update_hdr_trans(struct mt792x_dev *dev, struct ieee80211_vif *vif, struct ieee80211_sta *sta, + struct mt792x_link_sta *mlink, int link_id); int mt7925_mcu_wf_rf_pin_ctrl(struct mt792x_phy *phy); From cf9db836b1e069a3d6a80c72b9bdc12df78b0dd1 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Fri, 6 Mar 2026 17:22:29 -0600 Subject: [PATCH 118/134] wifi: mt76: mt7925: pass mlink to set_link_key() Drop the mt792x_sta_to_link() lookup in mt7925_set_link_key() and pass the resolved mlink from the caller instead. The link context is already known at the call site, making the lookup redundant. This keeps the helper lookup-free and makes link ownership explicit. No functional change intended. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260306232238.2039675-11-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/main.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index f5dd91dacca5..68f168a093f2 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -594,7 +594,8 @@ static int mt7925_cancel_remain_on_channel(struct ieee80211_hw *hw, static int mt7925_set_link_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, struct ieee80211_vif *vif, struct ieee80211_sta *sta, - struct ieee80211_key_conf *key, int link_id) + struct ieee80211_key_conf *key, int link_id, + struct mt792x_link_sta *mlink) { struct mt792x_dev *dev = mt792x_hw_dev(hw); struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv; @@ -603,7 +604,6 @@ static int mt7925_set_link_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, struct ieee80211_bss_conf *link_conf; struct ieee80211_link_sta *link_sta; int idx = key->keyidx, err = 0; - struct mt792x_link_sta *mlink; struct mt792x_bss_conf *mconf; struct mt76_wcid *wcid; u8 *wcid_keyidx; @@ -611,7 +611,6 @@ static int mt7925_set_link_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, link_conf = mt792x_vif_to_bss_conf(vif, link_id); link_sta = sta ? mt792x_sta_to_link_sta(vif, sta, link_id) : NULL; mconf = mt792x_vif_to_link(mvif, link_id); - mlink = mt792x_sta_to_link(msta, link_id); wcid = &mlink->wcid; wcid_keyidx = &wcid->hw_key_idx; @@ -679,6 +678,7 @@ static int mt7925_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv; struct mt792x_sta *msta = sta ? (struct mt792x_sta *)sta->drv_priv : &mvif->sta; + struct mt792x_link_sta *mlink; int err; /* The hardware does not support per-STA RX GTK, fallback @@ -700,12 +700,16 @@ static int mt7925_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd, add = key->link_id != -1 ? BIT(key->link_id) : msta->valid_links; for_each_set_bit(link_id, &add, IEEE80211_MLD_MAX_NUM_LINKS) { - err = mt7925_set_link_key(hw, cmd, vif, sta, key, link_id); + mlink = mt792x_sta_to_link(msta, link_id); + err = mt7925_set_link_key(hw, cmd, vif, sta, key, link_id, + mlink); if (err < 0) break; } } else { - err = mt7925_set_link_key(hw, cmd, vif, sta, key, vif->bss_conf.link_id); + mlink = mt792x_sta_to_link(msta, vif->bss_conf.link_id); + err = mt7925_set_link_key(hw, cmd, vif, sta, key, + vif->bss_conf.link_id, mlink); } mt792x_mutex_release(dev); From beec58f36983f826fe90287a90edff46b32e8a89 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Fri, 6 Mar 2026 17:22:30 -0600 Subject: [PATCH 119/134] wifi: mt76: mt7925: resolve link after acquiring mt76 mutex mt792x_sta_to_link() uses rcu_dereference_protected() and therefore expects mt76.mutex to be held. Move the lookup after mt792x_mutex_acquire() to make the locking explicit and correct. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260306232238.2039675-12-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 68f168a093f2..135a803b4382 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -1071,11 +1071,11 @@ static void mt7925_mac_link_sta_assoc(struct mt76_dev *mdev, struct mt792x_link_sta *mlink; struct mt792x_sta *msta; + mt792x_mutex_acquire(dev); + msta = (struct mt792x_sta *)link_sta->sta->drv_priv; mlink = mt792x_sta_to_link(msta, link_sta->link_id); - mt792x_mutex_acquire(dev); - if (ieee80211_vif_is_mld(vif)) { link_conf = mt792x_vif_to_bss_conf(vif, msta->deflink_id); } else { From 9763ead5b9f8fd69033fbe77046a2c5bdd749cf5 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Fri, 6 Mar 2026 17:22:31 -0600 Subject: [PATCH 120/134] wifi: mt76: mt7925: pass mconf and mlink to wtbl_update_hdr_trans() Drop the mt792x_vif_to_link() lookup in mt7925_mcu_wtbl_update_hdr_trans() and pass the resolved mconf and mlink from the caller instead. The link context is already known at the call site, making the lookup redundant. This keeps the helper lookup-free and makes link ownership explicit. No functional change intended. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260306232238.2039675-13-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7925/main.c | 4 +++- .../net/wireless/mediatek/mt76/mt7925/mcu.c | 20 ++++--------------- .../wireless/mediatek/mt76/mt7925/mt7925.h | 5 ++--- 3 files changed, 9 insertions(+), 20 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index 135a803b4382..151dc79f7c12 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -1590,8 +1590,10 @@ static void mt7925_sta_set_decap_offload(struct ieee80211_hw *hw, valid = ieee80211_vif_is_mld(vif) ? mvif->valid_links : BIT(0); for_each_set_bit(i, &valid, IEEE80211_MLD_MAX_NUM_LINKS) { + struct mt792x_bss_conf *mconf; struct mt792x_link_sta *mlink; + mconf = mt792x_vif_to_link(mvif, i); mlink = mt792x_sta_to_link(msta, i); if (enabled) @@ -1602,7 +1604,7 @@ static void mt7925_sta_set_decap_offload(struct ieee80211_hw *hw, if (!mlink->wcid.sta) continue; - mt7925_mcu_wtbl_update_hdr_trans(dev, vif, sta, mlink, i); + mt7925_mcu_wtbl_update_hdr_trans(dev, vif, mconf, mlink); } mt792x_mutex_release(dev); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c index 04650e201071..37cdf3e8a067 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c @@ -1066,7 +1066,6 @@ EXPORT_SYMBOL_GPL(mt7925_run_firmware); static void mt7925_mcu_sta_hdr_trans_tlv(struct sk_buff *skb, struct ieee80211_vif *vif, - struct ieee80211_link_sta *link_sta, struct mt792x_link_sta *mlink) { struct sta_rec_hdr_trans *hdr_trans; @@ -1096,29 +1095,18 @@ mt7925_mcu_sta_hdr_trans_tlv(struct sk_buff *skb, int mt7925_mcu_wtbl_update_hdr_trans(struct mt792x_dev *dev, struct ieee80211_vif *vif, - struct ieee80211_sta *sta, - struct mt792x_link_sta *mlink, - int link_id) + struct mt792x_bss_conf *mconf, + struct mt792x_link_sta *mlink) { - struct ieee80211_link_sta *link_sta = sta ? &sta->deflink : NULL; - struct mt792x_vif *mvif = (struct mt792x_vif *)vif->drv_priv; - struct mt792x_bss_conf *mconf; struct sk_buff *skb; - link_sta = mt792x_sta_to_link_sta(vif, sta, link_id); - mconf = mt792x_vif_to_link(mvif, link_id); - skb = __mt76_connac_mcu_alloc_sta_req(&dev->mt76, &mconf->mt76, &mlink->wcid, MT7925_STA_UPDATE_MAX_SIZE); if (IS_ERR(skb)) return PTR_ERR(skb); - /* starec hdr trans */ - if (!link_sta) - mlink = &mvif->sta.deflink; - - mt7925_mcu_sta_hdr_trans_tlv(skb, vif, link_sta, mlink); + mt7925_mcu_sta_hdr_trans_tlv(skb, vif, mlink); return mt76_mcu_skb_send_msg(&dev->mt76, skb, MCU_WMWA_UNI_CMD(STA_REC_UPDATE), true); } @@ -2022,7 +2010,7 @@ mt7925_mcu_sta_cmd(struct mt76_phy *phy, if (!info->link_sta) mlink = &mvif->sta.deflink; - mt7925_mcu_sta_hdr_trans_tlv(skb, info->vif, info->link_sta, mlink); + mt7925_mcu_sta_hdr_trans_tlv(skb, info->vif, mlink); } return mt76_mcu_skb_send_msg(dev, skb, info->cmd, true); diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h index e28972f0615b..46b480f7d813 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h +++ b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h @@ -370,9 +370,8 @@ int mt7925_mcu_add_key(struct mt76_dev *dev, struct ieee80211_vif *vif, int mt7925_mcu_set_rts_thresh(struct mt792x_phy *phy, u32 val); int mt7925_mcu_wtbl_update_hdr_trans(struct mt792x_dev *dev, struct ieee80211_vif *vif, - struct ieee80211_sta *sta, - struct mt792x_link_sta *mlink, - int link_id); + struct mt792x_bss_conf *mconf, + struct mt792x_link_sta *mlink); int mt7925_mcu_wf_rf_pin_ctrl(struct mt792x_phy *phy); int mt7925_testmode_cmd(struct ieee80211_hw *hw, struct ieee80211_vif *vif, From da14a9349746bae246621f4fbab9e2720b800f6c Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Fri, 6 Mar 2026 17:22:32 -0600 Subject: [PATCH 121/134] wifi: mt76: mt7925: make WCID cleanup unconditional in sta_remove_links() Drop the dead pri_link check in mt7925_mac_sta_remove_links() and perform WCID cleanup unconditionally. mlink->pri_link is already cleared before the test, making the branch ineffective. This matches the actual teardown behaviour and simplifies the remove path. No functional change intended. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260306232238.2039675-14-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/main.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index 151dc79f7c12..584d989fb4e8 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -1221,10 +1221,8 @@ mt7925_mac_sta_remove_links(struct mt792x_dev *dev, struct ieee80211_vif *vif, mlink->sta = NULL; mlink->pri_link = NULL; - if (link_sta != mlink->pri_link) { - mt76_wcid_cleanup(mdev, wcid); - mt76_wcid_mask_clear(mdev->wcid_mask, wcid->idx); - } + mt76_wcid_cleanup(mdev, wcid); + mt76_wcid_mask_clear(mdev->wcid_mask, wcid->idx); if (msta->deflink_id == link_id) msta->deflink_id = IEEE80211_LINK_UNSPECIFIED; From 75e2d6bfd9ac69c79adfe6fa2854558158b260de Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Fri, 6 Mar 2026 17:22:33 -0600 Subject: [PATCH 122/134] wifi: mt76: mt7925: unwind WCID setup on link STA add failure Undo the published WCID state when mt7925_mac_link_sta_add() fails after WCID setup. The add path can fail after dev->mt76.wcid[] is published, so the error path must clear the partial host-side WCID state to avoid leaving stale entries behind. No functional change intended. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260306232238.2039675-15-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7925/main.c | 53 +++++++++++++------ 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index 584d989fb4e8..dbde91727cd0 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -862,8 +862,10 @@ static int mt7925_mac_link_sta_add(struct mt76_dev *mdev, struct ieee80211_bss_conf *link_conf; struct mt792x_bss_conf *mconf; u8 link_id = link_sta->link_id; + bool wcid_published = false; struct mt792x_sta *msta; struct mt76_wcid *wcid; + bool pm_woken = false; int ret, idx; msta = (struct mt792x_sta *)link_sta->sta->drv_priv; @@ -888,6 +890,7 @@ static int mt7925_mac_link_sta_add(struct mt76_dev *mdev, wcid = &mlink->wcid; ewma_signal_init(&wcid->rssi); rcu_assign_pointer(dev->mt76.wcid[wcid->idx], wcid); + wcid_published = true; mt76_wcid_init(wcid, 0); ewma_avg_signal_init(&mlink->avg_ack_signal); memset(mlink->airtime_ac, 0, @@ -895,7 +898,8 @@ static int mt7925_mac_link_sta_add(struct mt76_dev *mdev, ret = mt76_connac_pm_wake(&dev->mphy, &dev->pm); if (ret) - return ret; + goto out_wcid; + pm_woken = true; mt7925_mac_wtbl_update(dev, idx, MT_WTBL_UPDATE_ADM_COUNT_CLEAR); @@ -909,15 +913,19 @@ static int mt7925_mac_link_sta_add(struct mt76_dev *mdev, mlink_bc = mt792x_sta_to_link(&mvif->sta, mconf->link_id); if (ieee80211_vif_is_mld(vif)) { - mt7925_mcu_add_bss_info_sta(&dev->phy, mconf->mt76.ctx, - link_conf, link_sta, - mlink_bc->wcid.idx, mlink->wcid.idx, - link_sta != mlink->pri_link); + ret = mt7925_mcu_add_bss_info_sta(&dev->phy, mconf->mt76.ctx, + link_conf, link_sta, + mlink_bc->wcid.idx, mlink->wcid.idx, + link_sta != mlink->pri_link); + if (ret) + goto out_pm; } else { - mt7925_mcu_add_bss_info_sta(&dev->phy, mconf->mt76.ctx, - link_conf, link_sta, - mlink_bc->wcid.idx, mlink->wcid.idx, - false); + ret = mt7925_mcu_add_bss_info_sta(&dev->phy, mconf->mt76.ctx, + link_conf, link_sta, + mlink_bc->wcid.idx, mlink->wcid.idx, + false); + if (ret) + goto out_pm; } } @@ -927,7 +935,7 @@ static int mt7925_mac_link_sta_add(struct mt76_dev *mdev, mlink, true, MT76_STA_INFO_STATE_NONE); if (ret) - return ret; + goto out_pm; } else if (ieee80211_vif_is_mld(vif) && link_sta != mlink->pri_link) { struct mt792x_link_sta *pri_mlink; @@ -940,31 +948,46 @@ static int mt7925_mac_link_sta_add(struct mt76_dev *mdev, container_of(pri_wcid, struct mt792x_link_sta, wcid) : NULL; - if (WARN_ON_ONCE(!pri_mlink)) - return -EINVAL; + if (WARN_ON_ONCE(!pri_mlink)) { + ret = -EINVAL; + goto out_pm; + } ret = mt7925_mcu_sta_update(dev, mlink->pri_link, vif, pri_mlink, true, MT76_STA_INFO_STATE_ASSOC); if (ret) - return ret; + goto out_pm; ret = mt7925_mcu_sta_update(dev, link_sta, vif, mlink, true, MT76_STA_INFO_STATE_ASSOC); if (ret) - return ret; + goto out_pm; } else { ret = mt7925_mcu_sta_update(dev, link_sta, vif, mlink, true, MT76_STA_INFO_STATE_NONE); if (ret) - return ret; + goto out_pm; } mt76_connac_power_save_sched(&dev->mphy, &dev->pm); return 0; + +out_pm: + if (pm_woken) + mt76_connac_power_save_sched(&dev->mphy, &dev->pm); +out_wcid: + if (wcid_published) { + u16 idx = wcid->idx; + + rcu_assign_pointer(dev->mt76.wcid[idx], NULL); + mt76_wcid_cleanup(mdev, wcid); + mt76_wcid_mask_clear(mdev->wcid_mask, wcid->idx); + } + return ret; } static int From 0fff5b5e2786a4fed7c67087bbaa44ff4a2e200d Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Fri, 6 Mar 2026 17:22:34 -0600 Subject: [PATCH 123/134] wifi: mt76: mt7925: drop WCID reinit after publish Remove the redundant mt76_wcid_init() call after publishing the WCID in mt7925_mac_link_sta_add(). WCID is already initialized before publication, so reinitializing it afterward is unnecessary and makes the setup ordering less clear. No functional change intended. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260306232238.2039675-16-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/main.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index dbde91727cd0..d99dbd707fcd 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -891,7 +891,6 @@ static int mt7925_mac_link_sta_add(struct mt76_dev *mdev, ewma_signal_init(&wcid->rssi); rcu_assign_pointer(dev->mt76.wcid[wcid->idx], wcid); wcid_published = true; - mt76_wcid_init(wcid, 0); ewma_avg_signal_init(&mlink->avg_ack_signal); memset(mlink->airtime_ac, 0, sizeof(msta->deflink.airtime_ac)); From 52f088a2e6bfae6d30eea274738e82898c246bd6 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Fri, 6 Mar 2026 17:22:35 -0600 Subject: [PATCH 124/134] wifi: mt76: mt7925: move WCID teardown into link_sta_remove() Move WCID teardown into mt7925_mac_link_sta_remove() to mirror the dev->mt76.wcid[] publish done during link add. This clears the published WCID before the rest of teardown, so WCID lookups no longer expose a link that is being removed. No functional change intended. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260306232238.2039675-17-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/main.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index d99dbd707fcd..9e3f3874d0b3 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -1151,12 +1151,14 @@ static void mt7925_mac_link_sta_remove(struct mt76_dev *mdev, struct mt792x_link_sta *mlink) { struct mt792x_dev *dev = container_of(mdev, struct mt792x_dev, mt76); + struct mt76_wcid *wcid = &mlink->wcid; struct ieee80211_bss_conf *link_conf; u8 link_id = link_sta->link_id; + u16 idx = wcid->idx; mt7925_roc_abort_sync(dev); - mt76_connac_free_pending_tx_skbs(&dev->pm, &mlink->wcid); + mt76_connac_free_pending_tx_skbs(&dev->pm, wcid); mt76_connac_pm_wake(&dev->mphy, &dev->pm); mt7925_mcu_sta_update(dev, link_sta, vif, mlink, false, @@ -1183,6 +1185,10 @@ static void mt7925_mac_link_sta_remove(struct mt76_dev *mdev, list_del_init(&mlink->wcid.poll_list); spin_unlock_bh(&mdev->sta_poll_lock); + rcu_assign_pointer(dev->mt76.wcid[idx], NULL); + mt76_wcid_cleanup(mdev, wcid); + mt76_wcid_mask_clear(mdev->wcid_mask, idx); + mt76_connac_power_save_sched(&dev->mphy, &dev->pm); } @@ -1191,8 +1197,6 @@ mt7925_mac_sta_remove_links(struct mt792x_dev *dev, struct ieee80211_vif *vif, struct ieee80211_sta *sta, unsigned long old_links) { struct mt792x_sta *msta = (struct mt792x_sta *)sta->drv_priv; - struct mt76_dev *mdev = &dev->mt76; - struct mt76_wcid *wcid; unsigned int link_id; /* clean up bss before starec */ @@ -1235,16 +1239,12 @@ mt7925_mac_sta_remove_links(struct mt792x_dev *dev, struct ieee80211_vif *vif, if (!mlink) continue; - mt7925_mac_link_sta_remove(&dev->mt76, vif, link_sta, mlink); - - wcid = &mlink->wcid; rcu_assign_pointer(msta->link[link_id], NULL); msta->valid_links &= ~BIT(link_id); mlink->sta = NULL; mlink->pri_link = NULL; - mt76_wcid_cleanup(mdev, wcid); - mt76_wcid_mask_clear(mdev->wcid_mask, wcid->idx); + mt7925_mac_link_sta_remove(&dev->mt76, vif, link_sta, mlink); if (msta->deflink_id == link_id) msta->deflink_id = IEEE80211_LINK_UNSPECIFIED; From 6ace866cf6d2c5db3bcc8a0d55dcb2d705f2e7f8 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Fri, 6 Mar 2026 17:22:36 -0600 Subject: [PATCH 125/134] wifi: mt76: mt7925: switch link STA allocation to RCU lifetime Allocate mt792x_link_sta with kzalloc() and free it with kfree_rcu() instead of devm-managed memory. msta->link[] is published via RCU, so the link STA must remain valid until readers have quiesced after teardown. Manage the object lifetime with kfree_rcu() to match its RCU-visible publication. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260306232238.2039675-18-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7925/main.c | 10 +++++++--- drivers/net/wireless/mediatek/mt76/mt792x.h | 1 + 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index 9e3f3874d0b3..eb16c4683100 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -1005,7 +1005,7 @@ mt7925_mac_sta_add_links(struct mt792x_dev *dev, struct ieee80211_vif *vif, mlink = &msta->deflink; msta->deflink_id = link_id; } else { - mlink = devm_kzalloc(dev->mt76.dev, sizeof(*mlink), GFP_KERNEL); + mlink = kzalloc(sizeof(*mlink), GFP_KERNEL); if (!mlink) { err = -ENOMEM; break; @@ -1197,6 +1197,7 @@ mt7925_mac_sta_remove_links(struct mt792x_dev *dev, struct ieee80211_vif *vif, struct ieee80211_sta *sta, unsigned long old_links) { struct mt792x_sta *msta = (struct mt792x_sta *)sta->drv_priv; + struct mt76_dev *mdev = &dev->mt76; unsigned int link_id; /* clean up bss before starec */ @@ -1235,17 +1236,20 @@ mt7925_mac_sta_remove_links(struct mt792x_dev *dev, struct ieee80211_vif *vif, if (!link_sta) continue; - mlink = mt792x_sta_to_link(msta, link_id); + mlink = rcu_replace_pointer(msta->link[link_id], NULL, + lockdep_is_held(&mdev->mutex)); if (!mlink) continue; - rcu_assign_pointer(msta->link[link_id], NULL); msta->valid_links &= ~BIT(link_id); mlink->sta = NULL; mlink->pri_link = NULL; mt7925_mac_link_sta_remove(&dev->mt76, vif, link_sta, mlink); + if (mlink != &msta->deflink) + kfree_rcu(mlink, rcu_head); + if (msta->deflink_id == link_id) msta->deflink_id = IEEE80211_LINK_UNSPECIFIED; } diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h index 1f381ab356bc..4ff93f2cd624 100644 --- a/drivers/net/wireless/mediatek/mt76/mt792x.h +++ b/drivers/net/wireless/mediatek/mt76/mt792x.h @@ -97,6 +97,7 @@ DECLARE_EWMA(avg_signal, 10, 8) struct mt792x_link_sta { struct mt76_wcid wcid; /* must be first */ + struct rcu_head rcu_head; u32 airtime_ac[8]; From db134691924fb19535ad6f27e09354c8ad001964 Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Fri, 6 Mar 2026 17:22:37 -0600 Subject: [PATCH 126/134] wifi: mt76: mt7925: publish msta->link after successful link add Move the msta->link[link_id] publication until after mt7925_mac_link_sta_add() succeeds. msta->link[] is RCU-visible, so publishing it before setup completes can expose a link whose add path later fails. Publish it only after success to avoid partially initialized link state becoming visible. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260306232238.2039675-19-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7925/main.c | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index eb16c4683100..95c631b57894 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -1000,10 +1000,11 @@ mt7925_mac_sta_add_links(struct mt792x_dev *dev, struct ieee80211_vif *vif, for_each_set_bit(link_id, &new_links, IEEE80211_MLD_MAX_NUM_LINKS) { struct ieee80211_link_sta *link_sta; struct mt792x_link_sta *mlink; + bool is_deflink = false; if (msta->deflink_id == IEEE80211_LINK_UNSPECIFIED) { mlink = &msta->deflink; - msta->deflink_id = link_id; + is_deflink = true; } else { mlink = kzalloc(sizeof(*mlink), GFP_KERNEL); if (!mlink) { @@ -1012,14 +1013,23 @@ mt7925_mac_sta_add_links(struct mt792x_dev *dev, struct ieee80211_vif *vif, } } - msta->valid_links |= BIT(link_id); - rcu_assign_pointer(msta->link[link_id], mlink); mlink->sta = msta; mlink->pri_link = &sta->deflink; mlink->wcid.def_wcid = &msta->deflink.wcid; link_sta = mt792x_sta_to_link_sta(vif, sta, link_id); - mt7925_mac_link_sta_add(&dev->mt76, vif, link_sta, mlink); + err = mt7925_mac_link_sta_add(&dev->mt76, vif, link_sta, mlink); + if (err) { + if (!is_deflink) + kfree_rcu(mlink, rcu_head); + break; + } + + if (is_deflink) + msta->deflink_id = link_id; + + rcu_assign_pointer(msta->link[link_id], mlink); + msta->valid_links |= BIT(link_id); } return err; From 9fc83205a62eeeb775739ba6d8efcfdda9b7780a Mon Sep 17 00:00:00 2001 From: Sean Wang Date: Fri, 6 Mar 2026 17:22:38 -0600 Subject: [PATCH 127/134] wifi: mt76: mt7925: host-only unwind published links on add failure Release host link resources when mt7925_mac_sta_add_links() fails after partial success. msta->link[] and dev->mt76.wcid[] may already be published, so unwind the host state to avoid leaving stale links behind. Signed-off-by: Sean Wang Link: https://patch.msgid.link/20260306232238.2039675-20-sean.wang@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7925/main.c | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c index 95c631b57894..73d3722739d0 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c @@ -989,11 +989,51 @@ static int mt7925_mac_link_sta_add(struct mt76_dev *mdev, return ret; } +/* + * Host-only unwind for sta_add_links() failures. + * + * If add_links fail due to MCU/firmware timeouts; calling the full remove + * path would send more firmware commands and may hang again. So only rollback + * host-published state here (msta->link/valid_links, dev->mt76.wcid[idx]) and + * free mlink objects (RCU-safe). Firmware state is left for reset/recovery. + */ +static void +mt7925_mac_sta_unwind_links_host(struct mt792x_dev *dev, + struct ieee80211_sta *sta, + unsigned long links) +{ + struct mt792x_sta *msta = (struct mt792x_sta *)sta->drv_priv; + unsigned int link_id; + + for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) { + struct mt792x_link_sta *mlink; + u16 idx; + + mlink = rcu_replace_pointer(msta->link[link_id], NULL, + lockdep_is_held(&dev->mt76.mutex)); + if (!mlink) + continue; + + msta->valid_links &= ~BIT(link_id); + if (msta->deflink_id == link_id) + msta->deflink_id = IEEE80211_LINK_UNSPECIFIED; + + idx = mlink->wcid.idx; + rcu_assign_pointer(dev->mt76.wcid[idx], NULL); + mt76_wcid_cleanup(&dev->mt76, &mlink->wcid); + mt76_wcid_mask_clear(dev->mt76.wcid_mask, idx); + + if (mlink != &msta->deflink) + kfree_rcu(mlink, rcu_head); + } +} + static int mt7925_mac_sta_add_links(struct mt792x_dev *dev, struct ieee80211_vif *vif, struct ieee80211_sta *sta, unsigned long new_links) { struct mt792x_sta *msta = (struct mt792x_sta *)sta->drv_priv; + unsigned long added_links = 0; unsigned int link_id; int err = 0; @@ -1030,8 +1070,13 @@ mt7925_mac_sta_add_links(struct mt792x_dev *dev, struct ieee80211_vif *vif, rcu_assign_pointer(msta->link[link_id], mlink); msta->valid_links |= BIT(link_id); + + added_links |= BIT(link_id); } + if (err && added_links) + mt7925_mac_sta_unwind_links_host(dev, sta, added_links); + return err; } From 59a295335021f6973a34566554b2b9371f1c6f7d Mon Sep 17 00:00:00 2001 From: Peter Chiu Date: Mon, 16 Mar 2026 12:44:27 +0100 Subject: [PATCH 128/134] wifi: mt76: mt7996: fix frequency separation for station STR mode Fix frequency separation field for STR in MLD capabilities to get the correct chip capability. Signed-off-by: Peter Chiu Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260316-mt7996-sta-str-v1-1-666814e6ab2d@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/init.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/init.c b/drivers/net/wireless/mediatek/mt76/mt7996/init.c index 8dfb81eabc9a..d6f9aa1ab52d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/init.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/init.c @@ -99,6 +99,7 @@ static const struct wiphy_iftype_ext_capab iftypes_ext_capa[] = { .extended_capabilities_mask = if_types_ext_capa_ap, .extended_capabilities_len = sizeof(if_types_ext_capa_ap), .mld_capa_and_ops = + FIELD_PREP_CONST(IEEE80211_MLD_CAP_OP_FREQ_SEP_TYPE_IND, 1) | FIELD_PREP_CONST(IEEE80211_MLD_CAP_OP_MAX_SIMUL_LINKS, MT7996_MAX_RADIOS - 1), }, From 76ceccd60bdd1e496e0e70700f3e045d7bc339bf Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Sun, 15 Mar 2026 11:26:24 +0100 Subject: [PATCH 129/134] wifi: mt76: mt7996: Rely on msta_link link_id in mt7996_vif_link_remove() Rely on msta_link link_id value in mt7996_vif_link_remove routine instead of using link_conf pointer. This assumption is correct since msta_link link_id is set to link_conf link_id value in mt7996_vif_link_add routine. Moreover, fallback to default ieee80211_bss_conf struct if the link_conf pointer in mt7996_vif_link_remove() is NULL. MT7996 hw requires to remove AP MLD links from MCU configuration during AP tear-down process (e.g. running mt7996_remove_interface()). Doing so, we can't assume link_conf pointer is always non-NULL running mt7996_vif_link_remove routine. Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260315-mt7996-mlo-link-reconf-v1-1-a8a634fbc927@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/mt7996/main.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 834edd31458d..21a240f0c8c2 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -396,17 +396,21 @@ void mt7996_vif_link_remove(struct mt76_phy *mphy, struct ieee80211_vif *vif, struct mt7996_vif_link *link = container_of(mlink, struct mt7996_vif_link, mt76); struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv; struct mt7996_sta_link *msta_link = &link->msta_link; + unsigned int link_id = msta_link->wcid.link_id; struct mt7996_phy *phy = mphy->priv; struct mt7996_dev *dev = phy->dev; struct mt7996_key_iter_data it = { .cmd = SET_KEY, - .link_id = link_conf->link_id, + .link_id = link_id, }; int idx = msta_link->wcid.idx; if (!mlink->wcid->offchannel) ieee80211_iter_keys(mphy->hw, vif, mt7996_key_iter, &it); + if (!link_conf) + link_conf = &vif->bss_conf; + mt7996_mcu_add_sta(dev, link_conf, NULL, link, NULL, CONN_STATE_DISCONNECT, false); mt7996_mcu_add_bss_info(phy, vif, link_conf, mlink, msta_link, false); @@ -416,10 +420,9 @@ void mt7996_vif_link_remove(struct mt76_phy *mphy, struct ieee80211_vif *vif, rcu_assign_pointer(dev->mt76.wcid[idx], NULL); if (vif->txq && !mlink->wcid->offchannel && - mvif->mt76.deflink_id == link_conf->link_id) { + mvif->mt76.deflink_id == link_id) { struct ieee80211_bss_conf *iter; struct mt76_txq *mtxq; - unsigned int link_id; mvif->mt76.deflink_id = IEEE80211_LINK_UNSPECIFIED; mtxq = (struct mt76_txq *)vif->txq->drv_priv; @@ -427,7 +430,7 @@ void mt7996_vif_link_remove(struct mt76_phy *mphy, struct ieee80211_vif *vif, for_each_vif_active_link(vif, iter, link_id) { struct mt7996_vif_link *link; - if (link_id == link_conf->link_id) + if (link_id == msta_link->wcid.link_id) continue; link = mt7996_vif_link(dev, vif, link_id); From 108cb0c43fdc93bad105dcd00c30d9729520c71f Mon Sep 17 00:00:00 2001 From: Shayne Chen Date: Sun, 15 Mar 2026 11:26:25 +0100 Subject: [PATCH 130/134] wifi: mt76: mt7996: Account active links in valid_links fields Track active vif links in mt7996_vif_link_add and mt7996_vif_link_remove routines. This is a preliminary patch in order to remove AP MLD links from MCU configuration during AP tear-down process and to support MLO link reconfiguration in MT7996 driver. Signed-off-by: Shayne Chen Co-developed-by: Lorenzo Bianconi Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260315-mt7996-mlo-link-reconf-v1-2-a8a634fbc927@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/main.c | 50 +++++++++++-------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 21a240f0c8c2..07a266f7670c 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -367,12 +367,16 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, ieee80211_iter_keys(mphy->hw, vif, mt7996_key_iter, &it); - if (vif->txq && !mlink->wcid->offchannel && - mvif->mt76.deflink_id == IEEE80211_LINK_UNSPECIFIED) { - struct mt76_txq *mtxq = (struct mt76_txq *)vif->txq->drv_priv; + if (!mlink->wcid->offchannel) { + if (vif->txq && + mvif->mt76.deflink_id == IEEE80211_LINK_UNSPECIFIED) { + struct mt76_txq *mtxq; - mvif->mt76.deflink_id = link_conf->link_id; - mtxq->wcid = idx; + mtxq = (struct mt76_txq *)vif->txq->drv_priv; + mvif->mt76.deflink_id = link_conf->link_id; + mtxq->wcid = idx; + } + mvif->mt76.valid_links |= BIT(link_conf->link_id); } if (vif->type == NL80211_IFTYPE_STATION) { @@ -419,28 +423,30 @@ void mt7996_vif_link_remove(struct mt76_phy *mphy, struct ieee80211_vif *vif, rcu_assign_pointer(dev->mt76.wcid[idx], NULL); - if (vif->txq && !mlink->wcid->offchannel && - mvif->mt76.deflink_id == link_id) { - struct ieee80211_bss_conf *iter; - struct mt76_txq *mtxq; + if (!mlink->wcid->offchannel) { + if (vif->txq && mvif->mt76.deflink_id == link_id) { + struct ieee80211_bss_conf *iter; + struct mt76_txq *mtxq; - mvif->mt76.deflink_id = IEEE80211_LINK_UNSPECIFIED; - mtxq = (struct mt76_txq *)vif->txq->drv_priv; - /* Primary link will be removed, look for a new one */ - for_each_vif_active_link(vif, iter, link_id) { - struct mt7996_vif_link *link; + mvif->mt76.deflink_id = IEEE80211_LINK_UNSPECIFIED; + mtxq = (struct mt76_txq *)vif->txq->drv_priv; + /* Primary link will be removed, look for a new one */ + for_each_vif_active_link(vif, iter, link_id) { + struct mt7996_vif_link *link; - if (link_id == msta_link->wcid.link_id) - continue; + if (link_id == msta_link->wcid.link_id) + continue; - link = mt7996_vif_link(dev, vif, link_id); - if (!link) - continue; + link = mt7996_vif_link(dev, vif, link_id); + if (!link) + continue; - mtxq->wcid = link->msta_link.wcid.idx; - mvif->mt76.deflink_id = link_id; - break; + mtxq->wcid = link->msta_link.wcid.idx; + mvif->mt76.deflink_id = link_id; + break; + } } + mvif->mt76.valid_links &= ~BIT(link_id); } dev->mt76.vif_mask &= ~BIT_ULL(mlink->idx); From c8d22f28ea583bda31b7db8243e4e1eb042ac38a Mon Sep 17 00:00:00 2001 From: Shayne Chen Date: Sun, 15 Mar 2026 11:26:26 +0100 Subject: [PATCH 131/134] wifi: mt76: mt7996: Move mlink deallocation in mt7996_vif_link_remove() Destroy mt76_vif_link struct in mt7996_vif_link_remove routine and not in mt76_unassign_vif_chanctx(). This is necessary since, in order to properly support MLO link reconfiguration, we will destroy mt76_vif_link struct during AP tear-down process and not running unassign_vif_chanctx mac80211 callback. This patch does not introduce any regression since mt76_assign_vif_chanctx/mt76_unassign_vif_chanctx APIs are currently used just by MT7996 driver. Signed-off-by: Shayne Chen Co-developed-by: Lorenzo Bianconi Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260315-mt7996-mlo-link-reconf-v1-3-a8a634fbc927@kernel.org Signed-off-by: Felix Fietkau --- drivers/net/wireless/mediatek/mt76/channel.c | 9 --------- drivers/net/wireless/mediatek/mt76/mt7996/main.c | 6 ++++++ 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/channel.c b/drivers/net/wireless/mediatek/mt76/channel.c index cf3fc09e5d5a..05eee64706ea 100644 --- a/drivers/net/wireless/mediatek/mt76/channel.c +++ b/drivers/net/wireless/mediatek/mt76/channel.c @@ -158,8 +158,6 @@ void mt76_unassign_vif_chanctx(struct ieee80211_hw *hw, { struct mt76_chanctx *ctx = (struct mt76_chanctx *)conf->drv_priv; struct mt76_vif_link *mlink = (struct mt76_vif_link *)vif->drv_priv; - struct mt76_vif_data *mvif = mlink->mvif; - int link_id = link_conf->link_id; struct mt76_phy *phy = ctx->phy; struct mt76_dev *dev = phy->dev; @@ -176,15 +174,8 @@ void mt76_unassign_vif_chanctx(struct ieee80211_hw *hw, if (!mlink) goto out; - if (mlink != (struct mt76_vif_link *)vif->drv_priv) - rcu_assign_pointer(mvif->link[link_id], NULL); - dev->drv->vif_link_remove(phy, vif, link_conf, mlink); mlink->ctx = NULL; - - if (mlink != (struct mt76_vif_link *)vif->drv_priv) - kfree_rcu(mlink, rcu_head); - out: mutex_unlock(&dev->mutex); } diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index 07a266f7670c..feee93340a6c 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -459,6 +459,12 @@ void mt7996_vif_link_remove(struct mt76_phy *mphy, struct ieee80211_vif *vif, spin_unlock_bh(&dev->mt76.sta_poll_lock); mt76_wcid_cleanup(&dev->mt76, &msta_link->wcid); + + if (mlink != (struct mt76_vif_link *)vif->drv_priv && + !mlink->wcid->offchannel) { + rcu_assign_pointer(mlink->mvif->link[link_id], NULL); + kfree_rcu(mlink, rcu_head); + } } static void mt7996_phy_set_rxfilter(struct mt7996_phy *phy) From 08813703ac412360e060cc9abd4a60e3c6668781 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Sun, 15 Mar 2026 11:26:27 +0100 Subject: [PATCH 132/134] wifi: mt76: mt7996: Destroy vif active links in mt7996_remove_interface() MT7996 hw requires to remove active links from the mcu BSSINFO table destroying the interface. For this reason introduce mt7996_vif_link_destroy routine and remove active (non-offchannel) vif links running mt7996_remove_interface routine. This is a preliminary patch in order to support MLO link reconfiguration in MT7996 driver. Co-developed-by: Shayne Chen Signed-off-by: Shayne Chen Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260315-mt7996-mlo-link-reconf-v1-4-a8a634fbc927@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/main.c | 108 ++++++++++++------ 1 file changed, 72 insertions(+), 36 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index feee93340a6c..d8ef41c39a7f 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -306,6 +306,10 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, }; int mld_idx, idx, ret; + if ((mvif->mt76.valid_links & BIT(link_conf->link_id)) && + !mlink->offchannel) + return 0; + mlink->idx = __ffs64(~dev->mt76.vif_mask); if (mlink->idx >= mt7996_max_interface_num(dev)) return -ENOSPC; @@ -393,65 +397,40 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, return 0; } -void mt7996_vif_link_remove(struct mt76_phy *mphy, struct ieee80211_vif *vif, - struct ieee80211_bss_conf *link_conf, - struct mt76_vif_link *mlink) +static void mt7996_vif_link_destroy(struct mt7996_phy *phy, + struct mt7996_vif_link *link, + struct ieee80211_vif *vif, + struct ieee80211_bss_conf *link_conf) { - struct mt7996_vif_link *link = container_of(mlink, struct mt7996_vif_link, mt76); struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv; struct mt7996_sta_link *msta_link = &link->msta_link; unsigned int link_id = msta_link->wcid.link_id; - struct mt7996_phy *phy = mphy->priv; - struct mt7996_dev *dev = phy->dev; + struct mt76_vif_link *mlink = &link->mt76; struct mt7996_key_iter_data it = { .cmd = SET_KEY, .link_id = link_id, }; + struct mt7996_dev *dev = phy->dev; int idx = msta_link->wcid.idx; - if (!mlink->wcid->offchannel) - ieee80211_iter_keys(mphy->hw, vif, mt7996_key_iter, &it); - if (!link_conf) link_conf = &vif->bss_conf; + if (!mlink->wcid->offchannel) + ieee80211_iter_keys(phy->mt76->hw, vif, mt7996_key_iter, &it); + mt7996_mcu_add_sta(dev, link_conf, NULL, link, NULL, CONN_STATE_DISCONNECT, false); mt7996_mcu_add_bss_info(phy, vif, link_conf, mlink, msta_link, false); - mt7996_mcu_add_dev_info(phy, vif, link_conf, mlink, false); rcu_assign_pointer(dev->mt76.wcid[idx], NULL); - if (!mlink->wcid->offchannel) { - if (vif->txq && mvif->mt76.deflink_id == link_id) { - struct ieee80211_bss_conf *iter; - struct mt76_txq *mtxq; - - mvif->mt76.deflink_id = IEEE80211_LINK_UNSPECIFIED; - mtxq = (struct mt76_txq *)vif->txq->drv_priv; - /* Primary link will be removed, look for a new one */ - for_each_vif_active_link(vif, iter, link_id) { - struct mt7996_vif_link *link; - - if (link_id == msta_link->wcid.link_id) - continue; - - link = mt7996_vif_link(dev, vif, link_id); - if (!link) - continue; - - mtxq->wcid = link->msta_link.wcid.idx; - mvif->mt76.deflink_id = link_id; - break; - } - } - mvif->mt76.valid_links &= ~BIT(link_id); - } - dev->mt76.vif_mask &= ~BIT_ULL(mlink->idx); dev->mld_idx_mask &= ~BIT_ULL(link->mld_idx); phy->omac_mask &= ~BIT_ULL(mlink->omac_idx); + if (!mlink->wcid->offchannel) + mvif->mt76.valid_links &= ~BIT(link_id); spin_lock_bh(&dev->mt76.sta_poll_lock); if (!list_empty(&msta_link->wcid.poll_list)) @@ -467,6 +446,44 @@ void mt7996_vif_link_remove(struct mt76_phy *mphy, struct ieee80211_vif *vif, } } +void mt7996_vif_link_remove(struct mt76_phy *mphy, struct ieee80211_vif *vif, + struct ieee80211_bss_conf *link_conf, + struct mt76_vif_link *mlink) +{ + struct mt7996_vif_link *link = container_of(mlink, struct mt7996_vif_link, mt76); + struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv; + struct mt7996_sta_link *msta_link = &link->msta_link; + struct mt7996_phy *phy = mphy->priv; + + /* Hw requires to destroy active links tearing down the interface, so + * postpone it removing the interface. + */ + if (mlink->wcid->offchannel) { + mt7996_vif_link_destroy(phy, link, vif, link_conf); + } else if (vif->txq && + mvif->mt76.deflink_id == msta_link->wcid.link_id) { + struct ieee80211_bss_conf *iter; + struct mt76_txq *mtxq; + unsigned int link_id; + + mvif->mt76.deflink_id = IEEE80211_LINK_UNSPECIFIED; + mtxq = (struct mt76_txq *)vif->txq->drv_priv; + /* Primary link will be removed, look for a new one */ + for_each_vif_active_link(vif, iter, link_id) { + if (link_id == msta_link->wcid.link_id) + continue; + + link = mt7996_vif_link(phy->dev, vif, link_id); + if (!link) + continue; + + mtxq->wcid = link->msta_link.wcid.idx; + mvif->mt76.deflink_id = link_id; + break; + } + } +} + static void mt7996_phy_set_rxfilter(struct mt7996_phy *phy) { struct mt7996_dev *dev = phy->dev; @@ -570,10 +587,29 @@ static void mt7996_remove_iter(void *data, u8 *mac, struct ieee80211_vif *vif) static void mt7996_remove_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif) { + struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv; + unsigned long rem_links = mvif->mt76.valid_links; struct mt7996_dev *dev = mt7996_hw_dev(hw); struct mt7996_radio_data rdata = {}; + unsigned int link_id; int i; + /* Remove all active links */ + for_each_set_bit(link_id, &rem_links, IEEE80211_MLD_MAX_NUM_LINKS) { + struct mt7996_vif_link *link; + struct mt7996_phy *phy; + + link = mt7996_vif_link(dev, vif, link_id); + if (!link) + continue; + + phy = __mt7996_phy(dev, link->msta_link.wcid.phy_idx); + if (!phy) + continue; + + mt7996_vif_link_destroy(phy, link, vif, NULL); + } + ieee80211_iterate_active_interfaces_mtx(hw, 0, mt7996_remove_iter, &rdata); mt76_vif_cleanup(&dev->mt76, vif); From e7ec71d9f8fafe9b431c6b4673465390273d744d Mon Sep 17 00:00:00 2001 From: Shayne Chen Date: Sun, 15 Mar 2026 11:26:28 +0100 Subject: [PATCH 133/134] wifi: mt76: mt7996: Add mcu APIs to enable/disable vif links. Introduce mt7996_mcu_mld_reconf_stop_link and mt7996_mcu_mld_link_oper utility routines in order to communicate to the mcu fw to disable/enable a specific vif link. Please note these APIs are currently supported by the MT7996 firmware only in AP mode. Signed-off-by: Shayne Chen Co-developed-by: Lorenzo Bianconi Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260315-mt7996-mlo-link-reconf-v1-5-a8a634fbc927@kernel.org Signed-off-by: Felix Fietkau --- .../wireless/mediatek/mt76/mt76_connac_mcu.h | 2 + .../net/wireless/mediatek/mt76/mt7996/main.c | 48 +++++++++----- .../net/wireless/mediatek/mt76/mt7996/mcu.c | 66 +++++++++++++++++++ .../net/wireless/mediatek/mt76/mt7996/mcu.h | 34 ++++++++++ .../wireless/mediatek/mt76/mt7996/mt7996.h | 6 ++ 5 files changed, 138 insertions(+), 18 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h index fd9cf9c0c32f..ac5126ab68ff 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h @@ -1319,6 +1319,7 @@ enum { MCU_UNI_CMD_ASSERT_DUMP = 0x6f, MCU_UNI_CMD_EXT_EEPROM_CTRL = 0x74, MCU_UNI_CMD_RADIO_STATUS = 0x80, + MCU_UNI_CMD_MLD = 0x82, MCU_UNI_CMD_SDO = 0x88, }; @@ -1394,6 +1395,7 @@ enum { UNI_BSS_INFO_MLD = 26, UNI_BSS_INFO_PM_DISABLE = 27, UNI_BSS_INFO_EHT = 30, + UNI_BSS_INFO_MLD_LINK_OP = 36, }; enum { diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index d8ef41c39a7f..ac82ea3f066a 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -307,8 +307,12 @@ int mt7996_vif_link_add(struct mt76_phy *mphy, struct ieee80211_vif *vif, int mld_idx, idx, ret; if ((mvif->mt76.valid_links & BIT(link_conf->link_id)) && - !mlink->offchannel) + !mlink->offchannel) { + if (vif->type == NL80211_IFTYPE_AP) + return mt7996_mcu_mld_link_oper(dev, link_conf, link, + true); return 0; + } mlink->idx = __ffs64(~dev->mt76.vif_mask); if (mlink->idx >= mt7996_max_interface_num(dev)) @@ -453,6 +457,7 @@ void mt7996_vif_link_remove(struct mt76_phy *mphy, struct ieee80211_vif *vif, struct mt7996_vif_link *link = container_of(mlink, struct mt7996_vif_link, mt76); struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv; struct mt7996_sta_link *msta_link = &link->msta_link; + unsigned int link_id = msta_link->wcid.link_id; struct mt7996_phy *phy = mphy->priv; /* Hw requires to destroy active links tearing down the interface, so @@ -460,26 +465,33 @@ void mt7996_vif_link_remove(struct mt76_phy *mphy, struct ieee80211_vif *vif, */ if (mlink->wcid->offchannel) { mt7996_vif_link_destroy(phy, link, vif, link_conf); - } else if (vif->txq && - mvif->mt76.deflink_id == msta_link->wcid.link_id) { - struct ieee80211_bss_conf *iter; - struct mt76_txq *mtxq; - unsigned int link_id; + } else { + if (vif->type == NL80211_IFTYPE_AP) { + mt7996_mcu_mld_reconf_stop_link(phy->dev, vif, + BIT(link_id)); + mt7996_mcu_mld_link_oper(phy->dev, link_conf, link, + false); + } - mvif->mt76.deflink_id = IEEE80211_LINK_UNSPECIFIED; - mtxq = (struct mt76_txq *)vif->txq->drv_priv; - /* Primary link will be removed, look for a new one */ - for_each_vif_active_link(vif, iter, link_id) { - if (link_id == msta_link->wcid.link_id) - continue; + if (vif->txq && mvif->mt76.deflink_id == link_id) { + struct ieee80211_bss_conf *iter; + struct mt76_txq *mtxq; - link = mt7996_vif_link(phy->dev, vif, link_id); - if (!link) - continue; + mvif->mt76.deflink_id = IEEE80211_LINK_UNSPECIFIED; + mtxq = (struct mt76_txq *)vif->txq->drv_priv; + /* Primary link will be removed, look for a new one */ + for_each_vif_active_link(vif, iter, link_id) { + if (link_id == msta_link->wcid.link_id) + continue; - mtxq->wcid = link->msta_link.wcid.idx; - mvif->mt76.deflink_id = link_id; - break; + link = mt7996_vif_link(phy->dev, vif, link_id); + if (!link) + continue; + + mtxq->wcid = link->msta_link.wcid.idx; + mvif->mt76.deflink_id = link_id; + break; + } } } } diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index 4bf22318396f..16420375112d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -2583,6 +2583,72 @@ mt7996_mcu_add_group(struct mt7996_dev *dev, struct mt7996_vif_link *link, sizeof(req), true); } +int mt7996_mcu_mld_reconf_stop_link(struct mt7996_dev *dev, + struct ieee80211_vif *vif, + u16 removed_links) +{ + unsigned long rem_links = removed_links; + struct mld_reconf_stop_link *sl; + struct mld_req_hdr hdr = {}; + unsigned int link_id; + struct sk_buff *skb; + struct tlv *tlv; + + skb = mt76_mcu_msg_alloc(&dev->mt76, NULL, sizeof(hdr) + sizeof(*sl)); + if (!skb) + return -ENOMEM; + + memcpy(hdr.mld_addr, vif->addr, ETH_ALEN); + skb_put_data(skb, &hdr, sizeof(hdr)); + + tlv = mt7996_mcu_add_uni_tlv(skb, UNI_CMD_MLD_RECONF_STOP_LINK, + sizeof(*sl)); + sl = (struct mld_reconf_stop_link *)tlv; + sl->link_bitmap = cpu_to_le16(removed_links); + + for_each_set_bit(link_id, &rem_links, IEEE80211_MLD_MAX_NUM_LINKS) { + struct mt7996_vif_link *link; + + link = mt7996_vif_link(dev, vif, link_id); + if (!link) + continue; + + sl->bss_idx[link_id] = link->mt76.idx; + } + + return mt76_mcu_skb_send_msg(&dev->mt76, skb, MCU_WM_UNI_CMD(MLD), + true); +} + +int mt7996_mcu_mld_link_oper(struct mt7996_dev *dev, + struct ieee80211_bss_conf *link_conf, + struct mt7996_vif_link *link, bool add) +{ + struct ieee80211_vif *vif = link_conf->vif; + struct mt7996_vif *mvif = (struct mt7996_vif *)vif->drv_priv; + struct bss_mld_link_op_tlv *mld_op; + struct sk_buff *skb; + struct tlv *tlv; + + skb = __mt7996_mcu_alloc_bss_req(&dev->mt76, &link->mt76, + MT7996_BSS_UPDATE_MAX_SIZE); + if (IS_ERR(skb)) + return PTR_ERR(skb); + + tlv = mt7996_mcu_add_uni_tlv(skb, UNI_BSS_INFO_MLD_LINK_OP, + sizeof(*mld_op)); + mld_op = (struct bss_mld_link_op_tlv *)tlv; + mld_op->link_operation = add; + mld_op->own_mld_id = link->mld_idx; + mld_op->link_id = link_conf->link_id; + mld_op->group_mld_id = add ? mvif->mld_group_idx : 0xff; + mld_op->remap_idx = add ? mvif->mld_remap_idx : 0xff; + memcpy(mld_op->mac_addr, vif->addr, ETH_ALEN); + + return mt76_mcu_skb_send_msg(&dev->mt76, skb, + MCU_WMWA_UNI_CMD(BSS_INFO_UPDATE), true); +} + static void mt7996_mcu_sta_mld_setup_tlv(struct mt7996_dev *dev, struct sk_buff *skb, struct ieee80211_vif *vif, diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h index 39df13679779..8902e16508b7 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h @@ -524,6 +524,18 @@ struct bss_prot_tlv { __le32 prot_mode; } __packed; +struct bss_mld_link_op_tlv { + __le16 tag; + __le16 len; + u8 group_mld_id; + u8 own_mld_id; + u8 mac_addr[ETH_ALEN]; + u8 remap_idx; + u8 link_operation; + u8 link_id; + u8 rsv[2]; +} __packed; + struct sta_rec_ht_uni { __le16 tag; __le16 len; @@ -697,6 +709,28 @@ struct mld_setup_link { u8 __rsv; } __packed; +struct mld_req_hdr { + u8 ver; + u8 mld_addr[ETH_ALEN]; + u8 mld_idx; + u8 flag; + u8 rsv[3]; + u8 buf[]; +} __packed; + +struct mld_reconf_stop_link { + __le16 tag; + __le16 len; + __le16 link_bitmap; + u8 rsv[2]; + u8 bss_idx[16]; +} __packed; + +enum { + UNI_CMD_MLD_RECONF_AP_REM_TIMER = 0x03, + UNI_CMD_MLD_RECONF_STOP_LINK = 0x04, +}; + struct hdr_trans_en { __le16 tag; __le16 len; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index d18f8794351e..e0a5c4eeb516 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -776,6 +776,12 @@ int mt7996_mcu_get_all_sta_info(struct mt7996_phy *phy, u16 tag); int mt7996_mcu_wed_rro_reset_sessions(struct mt7996_dev *dev, u16 id); int mt7996_mcu_set_sniffer_mode(struct mt7996_phy *phy, bool enabled); int mt7996_mcu_set_dup_wtbl(struct mt7996_dev *dev); +int mt7996_mcu_mld_reconf_stop_link(struct mt7996_dev *dev, + struct ieee80211_vif *vif, + u16 removed_links); +int mt7996_mcu_mld_link_oper(struct mt7996_dev *dev, + struct ieee80211_bss_conf *link_conf, + struct mt7996_vif_link *link, bool add); static inline bool mt7996_has_hwrro(struct mt7996_dev *dev) { From e8c819df02436f2c2379766946735e1f06a7c923 Mon Sep 17 00:00:00 2001 From: Lorenzo Bianconi Date: Sun, 15 Mar 2026 11:26:29 +0100 Subject: [PATCH 134/134] wifi: mt76: mt7996: Destroy active sta links in mt7996_mac_sta_remove() Similar to vif link management, postpone sta link destuction in mt7996_mac_sta_remove() introducing mt7996_mac_sta_remove_link utility routine and just disable sta link running mt7996_mac_sta_remove_links routine. This is a preliminary patch in order to support MLO link reconfiguration in MT7996 driver. Signed-off-by: Lorenzo Bianconi Link: https://patch.msgid.link/20260315-mt7996-mlo-link-reconf-v1-6-a8a634fbc927@kernel.org Signed-off-by: Felix Fietkau --- .../net/wireless/mediatek/mt76/mt7996/mac.c | 22 +----- .../net/wireless/mediatek/mt76/mt7996/main.c | 67 +++++++++++-------- .../wireless/mediatek/mt76/mt7996/mt7996.h | 5 +- 3 files changed, 45 insertions(+), 49 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index c895e8a5de4d..e2a83da3a09c 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -2372,26 +2372,8 @@ mt7996_mac_reset_sta_iter(void *data, struct ieee80211_sta *sta) struct mt7996_dev *dev = data; int i; - for (i = 0; i < ARRAY_SIZE(msta->link); i++) { - struct mt7996_sta_link *msta_link = NULL; - struct mt7996_phy *phy; - - msta_link = rcu_replace_pointer(msta->link[i], msta_link, - lockdep_is_held(&dev->mt76.mutex)); - if (!msta_link) - continue; - - mt7996_mac_sta_deinit_link(dev, msta_link); - phy = __mt7996_phy(dev, msta_link->wcid.phy_idx); - if (phy) - phy->mt76->num_sta--; - - if (msta_link != &msta->deflink) - kfree_rcu(msta_link, rcu_head); - } - - msta->deflink_id = IEEE80211_LINK_UNSPECIFIED; - msta->seclink_id = msta->deflink_id; + for (i = 0; i < ARRAY_SIZE(msta->link); i++) + mt7996_mac_sta_remove_link(dev, sta, i, true); } static void diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c index ac82ea3f066a..a8a6552d49f6 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c @@ -1179,9 +1179,17 @@ mt7996_mac_sta_init_link(struct mt7996_dev *dev, return 0; } -void mt7996_mac_sta_deinit_link(struct mt7996_dev *dev, - struct mt7996_sta_link *msta_link) +void mt7996_mac_sta_remove_link(struct mt7996_dev *dev, + struct ieee80211_sta *sta, + unsigned int link_id, bool flush) { + struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; + struct mt7996_sta_link *msta_link; + + msta_link = mt76_dereference(msta->link[link_id], &dev->mt76); + if (!msta_link) + return; + spin_lock_bh(&dev->mt76.sta_poll_lock); if (!list_empty(&msta_link->wcid.poll_list)) list_del_init(&msta_link->wcid.poll_list); @@ -1189,31 +1197,13 @@ void mt7996_mac_sta_deinit_link(struct mt7996_dev *dev, list_del_init(&msta_link->rc_list); spin_unlock_bh(&dev->mt76.sta_poll_lock); - rcu_assign_pointer(dev->mt76.wcid[msta_link->wcid.idx], NULL); mt76_wcid_cleanup(&dev->mt76, &msta_link->wcid); - mt76_wcid_mask_clear(dev->mt76.wcid_mask, msta_link->wcid.idx); -} -static void -mt7996_mac_sta_remove_links(struct mt7996_dev *dev, struct ieee80211_vif *vif, - struct ieee80211_sta *sta, unsigned long links) -{ - struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; - struct mt76_dev *mdev = &dev->mt76; - unsigned int link_id; - - for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) { - struct mt7996_sta_link *msta_link = NULL; + if (msta_link->wcid.link_valid) { struct mt7996_phy *phy; - msta_link = rcu_replace_pointer(msta->link[link_id], msta_link, - lockdep_is_held(&mdev->mutex)); - if (!msta_link) - continue; - mt7996_mac_wtbl_update(dev, msta_link->wcid.idx, MT_WTBL_UPDATE_ADM_COUNT_CLEAR); - mt7996_mac_sta_deinit_link(dev, msta_link); phy = __mt7996_phy(dev, msta_link->wcid.phy_idx); if (phy) @@ -1230,7 +1220,7 @@ mt7996_mac_sta_remove_links(struct mt7996_dev *dev, struct ieee80211_vif *vif, /* switch to the secondary link */ msta_seclink = mt76_dereference( msta->link[msta->seclink_id], - mdev); + &dev->mt76); if (msta_seclink) { msta->deflink_id = msta->seclink_id; mt7996_sta_init_txq_wcid(sta, @@ -1240,12 +1230,29 @@ mt7996_mac_sta_remove_links(struct mt7996_dev *dev, struct ieee80211_vif *vif, } else if (msta->seclink_id == link_id) { msta->seclink_id = msta->deflink_id; } + msta_link->wcid.link_valid = false; + } + if (flush) { + rcu_assign_pointer(msta->link[link_id], NULL); + rcu_assign_pointer(dev->mt76.wcid[msta_link->wcid.idx], NULL); + mt76_wcid_mask_clear(dev->mt76.wcid_mask, msta_link->wcid.idx); if (msta_link != &msta->deflink) kfree_rcu(msta_link, rcu_head); } } +static void +mt7996_mac_sta_remove_links(struct mt7996_dev *dev, struct ieee80211_vif *vif, + struct ieee80211_sta *sta, unsigned long links, + bool flush) +{ + unsigned int link_id; + + for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) + mt7996_mac_sta_remove_link(dev, sta, link_id, flush); +} + static int mt7996_mac_sta_add_links(struct mt7996_dev *dev, struct ieee80211_vif *vif, struct ieee80211_sta *sta, unsigned long new_links) @@ -1257,11 +1264,15 @@ mt7996_mac_sta_add_links(struct mt7996_dev *dev, struct ieee80211_vif *vif, for_each_set_bit(link_id, &new_links, IEEE80211_MLD_MAX_NUM_LINKS) { struct ieee80211_bss_conf *link_conf; struct ieee80211_link_sta *link_sta; + struct mt7996_sta_link *msta_link; struct mt7996_vif_link *link; struct mt76_phy *mphy; - if (rcu_access_pointer(msta->link[link_id])) + msta_link = mt76_dereference(msta->link[link_id], &dev->mt76); + if (msta_link) { + msta_link->wcid.link_valid = true; continue; + } link_conf = link_conf_dereference_protected(vif, link_id); if (!link_conf) { @@ -1298,7 +1309,7 @@ mt7996_mac_sta_add_links(struct mt7996_dev *dev, struct ieee80211_vif *vif, return 0; error_unlink: - mt7996_mac_sta_remove_links(dev, vif, sta, new_links); + mt7996_mac_sta_remove_links(dev, vif, sta, new_links, true); return err; } @@ -1315,7 +1326,7 @@ mt7996_mac_sta_change_links(struct ieee80211_hw *hw, struct ieee80211_vif *vif, mutex_lock(&dev->mt76.mutex); - mt7996_mac_sta_remove_links(dev, vif, sta, rem); + mt7996_mac_sta_remove_links(dev, vif, sta, rem, false); ret = mt7996_mac_sta_add_links(dev, vif, sta, add); mutex_unlock(&dev->mt76.mutex); @@ -1424,10 +1435,12 @@ static void mt7996_mac_sta_remove(struct mt7996_dev *dev, struct ieee80211_vif *vif, struct ieee80211_sta *sta) { - unsigned long links = sta->valid_links ? sta->valid_links : BIT(0); + struct mt7996_sta *msta = (struct mt7996_sta *)sta->drv_priv; + int i; mutex_lock(&dev->mt76.mutex); - mt7996_mac_sta_remove_links(dev, vif, sta, links); + for (i = 0; i < ARRAY_SIZE(msta->link); i++) + mt7996_mac_sta_remove_link(dev, sta, i, true); mutex_unlock(&dev->mt76.mutex); } diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index e0a5c4eeb516..bdcf72457954 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -867,8 +867,9 @@ void mt7996_mac_twt_teardown_flow(struct mt7996_dev *dev, struct mt7996_vif_link *link, struct mt7996_sta_link *msta_link, u8 flowid); -void mt7996_mac_sta_deinit_link(struct mt7996_dev *dev, - struct mt7996_sta_link *msta_link); +void mt7996_mac_sta_remove_link(struct mt7996_dev *dev, + struct ieee80211_sta *sta, + unsigned int link_id, bool flush); void mt7996_mac_add_twt_setup(struct ieee80211_hw *hw, struct ieee80211_sta *sta, struct ieee80211_twt_setup *twt);