Direct MMIO access to WTBL entries for airtime and RSSI statistics in mt7996_mac_sta_poll() races with firmware, causing warnings at mt7996_mac_wtbl_lmac_addr, MCU message timeouts, and firmware communication breakdown. The function was called from mt7996_mac_tx_free() on every TX-Free-Done event, compounding the issue with heavy CPU overhead. Replace the direct WTBL polling with firmware MCU queries: - Airtime: UNI_ALL_STA_TXRX_AIR_TIME via the existing all_sta_info MCU command, with a new handler in mt7996_mcu_rx_all_sta_info_event() - RSSI: UNI_PER_STA_RSSI via new mt7996_mcu_get_per_sta_info() using MCU_WM_UNI_CMD(PER_STA_INFO) Both queries run from mt7996_mac_work() every 5th tick under dev mutex, matching the pattern already used for TX rate, admission stats, and MSDU count reporting. Remove mt7996_mac_sta_poll() and its airtime_ac tracking array entirely. The per-sta-info response handler validates the firmware-returned entry count against what was requested and against the skb payload length before walking the response array, and uses bounds-checked mt76_wcid_ptr() for WLAN ID lookup. RSSI polling is batched in groups of PER_STA_INFO_MAX_NUM to cover the full WTBL capacity. Vendor driver analysis (mt_wifi.ko from Xiaomi AX3000T MT7981 firmware) confirms the RCPI-to-RSSI conversion formula (rcpi - 220) / 2 and that the vendor never performs direct WTBL reads for statistics. Fixes: 98686cd21624 ("wifi: mt76: mt7996: add driver for MediaTek Wi-Fi 7 (802.11be) devices") Link: https://github.com/openwrt/openwrt/issues/21177 Signed-off-by: Joshua Klinesmith --- .../wireless/mediatek/mt76/mt76_connac_mcu.h | 7 + .../net/wireless/mediatek/mt76/mt7996/mac.c | 117 +-------------- .../net/wireless/mediatek/mt76/mt7996/mcu.c | 135 ++++++++++++++++++ .../net/wireless/mediatek/mt76/mt7996/mcu.h | 25 ++++ .../wireless/mediatek/mt76/mt7996/mt7996.h | 2 +- 5 files changed, 170 insertions(+), 116 deletions(-) diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h index 8d59cf43f0e2..14d3ee7defa1 100644 --- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h @@ -1392,6 +1392,13 @@ enum { UNI_OFFLOAD_OFFLOAD_BMC_RPY_DETECT, }; +#define PER_STA_INFO_MAX_NUM 90 + +enum UNI_PER_STA_INFO_TAG { + UNI_PER_STA_RSSI, + UNI_PER_STA_MAX_NUM +}; + enum UNI_ALL_STA_INFO_TAG { UNI_ALL_STA_TXRX_RATE, UNI_ALL_STA_TX_STAT, diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c index d4f3ee943b47..3d9648fb6773 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c @@ -111,119 +111,6 @@ u32 mt7996_mac_wtbl_lmac_addr(struct mt7996_dev *dev, u16 wcid, u8 dw) return MT_WTBL_LMAC_OFFS(wcid, dw); } -static void mt7996_mac_sta_poll(struct mt7996_dev *dev) -{ - static const u8 ac_to_tid[] = { - [IEEE80211_AC_BE] = 0, - [IEEE80211_AC_BK] = 1, - [IEEE80211_AC_VI] = 4, - [IEEE80211_AC_VO] = 6 - }; - struct mt7996_sta_link *msta_link; - struct mt76_vif_link *mlink; - struct ieee80211_sta *sta; - struct mt7996_sta *msta; - u32 tx_time[IEEE80211_NUM_ACS], rx_time[IEEE80211_NUM_ACS]; - LIST_HEAD(sta_poll_list); - struct mt76_wcid *wcid; - int i; - - spin_lock_bh(&dev->mt76.sta_poll_lock); - list_splice_init(&dev->mt76.sta_poll_list, &sta_poll_list); - spin_unlock_bh(&dev->mt76.sta_poll_lock); - - rcu_read_lock(); - - while (true) { - bool clear = false; - u32 addr, val; - u16 idx; - s8 rssi[4]; - - spin_lock_bh(&dev->mt76.sta_poll_lock); - if (list_empty(&sta_poll_list)) { - spin_unlock_bh(&dev->mt76.sta_poll_lock); - break; - } - msta_link = list_first_entry(&sta_poll_list, - struct mt7996_sta_link, - wcid.poll_list); - msta = msta_link->sta; - wcid = &msta_link->wcid; - list_del_init(&wcid->poll_list); - spin_unlock_bh(&dev->mt76.sta_poll_lock); - - idx = wcid->idx; - - /* refresh peer's airtime reporting */ - addr = mt7996_mac_wtbl_lmac_addr(dev, idx, 20); - - for (i = 0; i < IEEE80211_NUM_ACS; i++) { - u32 tx_last = msta_link->airtime_ac[i]; - u32 rx_last = msta_link->airtime_ac[i + 4]; - - msta_link->airtime_ac[i] = mt76_rr(dev, addr); - msta_link->airtime_ac[i + 4] = mt76_rr(dev, addr + 4); - - tx_time[i] = msta_link->airtime_ac[i] - tx_last; - rx_time[i] = msta_link->airtime_ac[i + 4] - rx_last; - - if ((tx_last | rx_last) & BIT(30)) - clear = true; - - addr += 8; - } - - if (clear) { - mt7996_mac_wtbl_update(dev, idx, - MT_WTBL_UPDATE_ADM_COUNT_CLEAR); - memset(msta_link->airtime_ac, 0, - sizeof(msta_link->airtime_ac)); - } - - if (!wcid->sta) - continue; - - sta = container_of((void *)msta, struct ieee80211_sta, - drv_priv); - for (i = 0; i < IEEE80211_NUM_ACS; i++) { - u8 q = mt76_connac_lmac_mapping(i); - u32 tx_cur = tx_time[q]; - u32 rx_cur = rx_time[q]; - u8 tid = ac_to_tid[i]; - - if (!tx_cur && !rx_cur) - continue; - - ieee80211_sta_register_airtime(sta, tid, tx_cur, rx_cur); - } - - /* get signal strength of resp frames (CTS/BA/ACK) */ - addr = mt7996_mac_wtbl_lmac_addr(dev, idx, 34); - val = mt76_rr(dev, addr); - - rssi[0] = to_rssi(GENMASK(7, 0), val); - rssi[1] = to_rssi(GENMASK(15, 8), val); - rssi[2] = to_rssi(GENMASK(23, 16), val); - rssi[3] = to_rssi(GENMASK(31, 14), val); - - mlink = rcu_dereference(msta->vif->mt76.link[wcid->link_id]); - if (mlink) { - struct mt76_phy *mphy = mt76_vif_link_phy(mlink); - - if (mphy) - msta_link->ack_signal = - mt76_rx_signal(mphy->antenna_mask, - rssi); - } - - ewma_avg_signal_add(&msta_link->avg_ack_signal, - -msta_link->ack_signal); - } - - rcu_read_unlock(); -} - /* The HW does not translate the mac header to 802.3 for mesh point */ static int mt7996_reverse_frag0_hdr_trans(struct sk_buff *skb, u16 hdr_gap) { @@ -1424,8 +1311,6 @@ mt7996_mac_tx_free(struct mt7996_dev *dev, void *data, int len) } } - mt7996_mac_sta_poll(dev); - if (wake) mt76_set_tx_blocked(&dev->mt76, false); @@ -2947,6 +2832,8 @@ void mt7996_mac_work(struct work_struct *work) mt7996_mac_update_stats(phy); mt7996_mcu_get_all_sta_info(phy, UNI_ALL_STA_TXRX_RATE); + mt7996_mcu_get_all_sta_info(phy, UNI_ALL_STA_TXRX_AIR_TIME); + mt7996_mcu_get_per_sta_info(phy, UNI_PER_STA_RSSI); if (mtk_wed_device_active(&phy->dev->mt76.mmio.wed)) { mt7996_mcu_get_all_sta_info(phy, UNI_ALL_STA_TXRX_ADM_STAT); mt7996_mcu_get_all_sta_info(phy, UNI_ALL_STA_TXRX_MSDU_COUNT); diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c index c0c042de477b..bf95e1d9299d 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c @@ -616,6 +616,35 @@ mt7996_mcu_rx_all_sta_info_event(struct mt7996_dev *dev, struct sk_buff *skb) wcid->stats.rx_packets += le32_to_cpu(res->msdu_cnt[i].rx_msdu_cnt); break; + case UNI_ALL_STA_TXRX_AIR_TIME: { + static const u8 ac_to_tid[] = { + [IEEE80211_AC_BE] = 0, + [IEEE80211_AC_BK] = 1, + [IEEE80211_AC_VI] = 4, + [IEEE80211_AC_VO] = 6 + }; + struct ieee80211_sta *sta; + + wlan_idx = le16_to_cpu(res->airtime[i].wlan_idx); + wcid = mt76_wcid_ptr(dev, wlan_idx); + sta = wcid_to_sta(wcid); + if (!sta) + break; + + for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) { + u8 lmac_ac = mt76_connac_lmac_mapping(ac); + u32 tx_cur = le32_to_cpu(res->airtime[i].tx[lmac_ac]); + u32 rx_cur = le32_to_cpu(res->airtime[i].rx[lmac_ac]); + + if (!tx_cur && !rx_cur) + continue; + + ieee80211_sta_register_airtime(sta, + ac_to_tid[ac], + tx_cur, rx_cur); + } + break; + } default: break; } @@ -4755,6 +4784,112 @@ int mt7996_mcu_get_all_sta_info(struct mt7996_phy *phy, u16 tag) &req, sizeof(req), false); } +int mt7996_mcu_get_per_sta_info(struct mt7996_phy *phy, u16 tag) +{ + struct mt7996_dev *dev = phy->dev; + struct mt7996_mcu_per_sta_info_event *res; + struct mt76_wcid *wcid; + struct sk_buff *skb; + int i, ret, sta_num, resp_sta_num; + int wcid_idx = 0; + struct { + u8 _rsv1; + u8 unsolicit; + u8 _rsv2[2]; + + __le16 tag; + __le16 len; + __le16 sta_num; + u8 _rsv3[2]; + __le16 wlan_idx[PER_STA_INFO_MAX_NUM]; + } __packed req = { + .tag = cpu_to_le16(tag), + .len = cpu_to_le16(sizeof(req) - 4), + }; + + while (wcid_idx < mt7996_wtbl_size(dev)) { + sta_num = 0; + + rcu_read_lock(); + for (i = wcid_idx; + i < mt7996_wtbl_size(dev) && sta_num < PER_STA_INFO_MAX_NUM; + i++) { + wcid = rcu_dereference(dev->mt76.wcid[i]); + if (!wcid || !wcid->sta) + continue; + req.wlan_idx[sta_num++] = cpu_to_le16(i); + } + rcu_read_unlock(); + wcid_idx = i; + + if (!sta_num) + continue; + + req.sta_num = cpu_to_le16(sta_num); + + ret = mt76_mcu_send_and_get_msg(&dev->mt76, + MCU_WM_UNI_CMD(PER_STA_INFO), + &req, sizeof(req), true, &skb); + if (ret) + return ret; + + res = (struct mt7996_mcu_per_sta_info_event *)skb->data; + + resp_sta_num = le16_to_cpu(res->sta_num); + if (resp_sta_num > sta_num || + skb->len < struct_size(res, rssi, resp_sta_num)) { + dev_kfree_skb(skb); + return -EINVAL; + } + + rcu_read_lock(); + for (i = 0; i < resp_sta_num; i++) { + struct mt7996_sta_link *msta_link; + struct mt76_vif_link *mvif; + struct mt76_vif_link *mlink; + struct mt76_phy *mphy; + u16 wlan_idx; + s8 rssi[4]; + + switch (tag) { + case UNI_PER_STA_RSSI: + wlan_idx = le16_to_cpu(res->rssi[i].wlan_idx); + wcid = mt76_wcid_ptr(dev, wlan_idx); + if (!wcid || !wcid->sta) + break; + + msta_link = container_of(wcid, + struct mt7996_sta_link, + wcid); + + rssi[0] = (res->rssi[i].rcpi[0] - 220) / 2; + rssi[1] = (res->rssi[i].rcpi[1] - 220) / 2; + rssi[2] = (res->rssi[i].rcpi[2] - 220) / 2; + rssi[3] = (res->rssi[i].rcpi[3] - 220) / 2; + + mvif = &msta_link->sta->vif->mt76; + mlink = rcu_dereference(mvif->link[wcid->link_id]); + if (mlink) { + mphy = mt76_vif_link_phy(mlink); + if (mphy) + msta_link->ack_signal = + mt76_rx_signal(mphy->antenna_mask, + rssi); + } + + ewma_avg_signal_add(&msta_link->avg_ack_signal, + -msta_link->ack_signal); + break; + } + } + rcu_read_unlock(); + + dev_kfree_skb(skb); + } + + return 0; +} + int mt7996_mcu_wed_rro_reset_sessions(struct mt7996_dev *dev, u16 id) { struct { diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h index e0b83ac9f5e2..b5bad9a76c49 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.h @@ -220,6 +220,31 @@ struct mt7996_mcu_all_sta_info_event { __le32 tx_msdu_cnt; __le32 rx_msdu_cnt; } __packed, msdu_cnt); + + DECLARE_FLEX_ARRAY(struct { + __le16 wlan_idx; + u8 rsv[2]; + __le32 tx[IEEE80211_NUM_ACS]; + __le32 rx[IEEE80211_NUM_ACS]; + } __packed, airtime); + } __packed; +} __packed; + +struct mt7996_mcu_per_sta_info_event { + u8 rsv[4]; + __le16 tag; + __le16 len; + u8 more; + u8 rsv2; + __le16 sta_num; + u8 rsv3[4]; + + union { + DECLARE_FLEX_ARRAY(struct { + __le16 wlan_idx; + u8 rsv[2]; + u8 rcpi[4]; + } __packed, rssi); } __packed; } __packed; diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h index 7a884311800e..b523e971f78c 100644 --- a/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h +++ b/drivers/net/wireless/mediatek/mt76/mt7996/mt7996.h @@ -222,7 +222,6 @@ struct mt7996_sta_link { struct mt7996_sta *sta; struct list_head rc_list; - u32 airtime_ac[8]; int ack_signal; struct ewma_avg_signal avg_ack_signal; @@ -741,6 +740,7 @@ int mt7996_mcu_trigger_assert(struct mt7996_dev *dev); void mt7996_mcu_rx_event(struct mt7996_dev *dev, struct sk_buff *skb); void mt7996_mcu_exit(struct mt7996_dev *dev); int mt7996_mcu_get_all_sta_info(struct mt7996_phy *phy, u16 tag); +int mt7996_mcu_get_per_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); -- 2.43.0