[rdkb][common][bsp][Refactor and sync wifi from openwrt]

[Description]
1dacd97b [MAC80211][WiFi6][Misc][Fix patch fail]
7c02334a [MAC80211][WiFi7][Misc][Fix build fail because of mt76 version upgradation]
7a073097 [MAC80211][WiFi7][misc][ensure the first MLD bss is bss[0]]
27e2304c [MAC80211][WiFi6][mt76][Refactor due to atenl change]
1e1eb98e [MAC80211][WiFi6/7][app][Add single wiphy support for atenl & iwpriv wrapper]
d4101c33 [MAC80211][WiFi7][mt76][enable lftp for wifi7 r1 cert]
55f5732f [MAC80211][WiFi7][hostapd][set ctrl_interface for all bss]

[Release-log]

Change-Id: I9cad01561c310576a9e5bdc9f1b8eec3025e51d9
diff --git a/recipes-wifi/linux-mt76/files/patches-3.x/0075-mtk-wifi-mt76-mt7996-get-airtime-and-RSSI-via-MCU-co.patch b/recipes-wifi/linux-mt76/files/patches-3.x/0075-mtk-wifi-mt76-mt7996-get-airtime-and-RSSI-via-MCU-co.patch
new file mode 100644
index 0000000..a553070
--- /dev/null
+++ b/recipes-wifi/linux-mt76/files/patches-3.x/0075-mtk-wifi-mt76-mt7996-get-airtime-and-RSSI-via-MCU-co.patch
@@ -0,0 +1,784 @@
+From a08f12c4e90f1c849c3e8dc0db60fe294b63f3fe Mon Sep 17 00:00:00 2001
+From: Benjamin Lin <benjamin-jw.lin@mediatek.com>
+Date: Fri, 17 Nov 2023 18:08:06 +0800
+Subject: [PATCH 075/116] mtk: wifi: mt76: mt7996: get airtime and RSSI via MCU
+ commands
+
+Direct access to WTBL for airtime and RSSI may cause synchronization issue with FW.
+Moreover, frequent access to WTBL, whenever TX-Free-Done event is received, leads to heavy CPU overheads.
+Therefore, indirect access to WTBL, through FW, with lower frequence is performed.
+
+Signed-off-by: Yi-Chia Hsieh <yi-chia.hsieh@mediatek.com>
+Signed-off-by: Benjamin Lin <benjamin-jw.lin@mediatek.com>
+---
+ mt76.h               |  20 +++++
+ mt76_connac_mcu.h    |  14 +++-
+ mt7996/debugfs.c     |  17 ++---
+ mt7996/mac.c         | 145 ++++++-----------------------------
+ mt7996/mcu.c         | 177 +++++++++++++++++++++++++++++++++++++++++--
+ mt7996/mcu.h         |  32 +++++++-
+ mt7996/mt7996.h      |  26 ++++++-
+ mt7996/mtk_debugfs.c |  71 +++++++++++++++++
+ mt7996/regs.h        |   2 +
+ 9 files changed, 361 insertions(+), 143 deletions(-)
+
+diff --git a/mt76.h b/mt76.h
+index 677f68f..28defd4 100644
+--- a/mt76.h
++++ b/mt76.h
+@@ -332,11 +332,15 @@ struct mt76_sta_stats {
+ 	u32 tx_packets;		/* unit: MSDU */
+ 	u32 tx_retries;
+ 	u32 tx_failed;
++	u32 tx_total_mpdu_cnt;
++	u32 tx_failed_mpdu_cnt;
++	u64 tx_airtime;
+ 	/* WED RX */
+ 	u64 rx_bytes;
+ 	u32 rx_packets;
+ 	u32 rx_errors;
+ 	u32 rx_drops;
++	u64 rx_airtime;
+ };
+ 
+ enum mt76_wcid_flags {
+@@ -1335,6 +1339,22 @@ static inline int mt76_decr(int val, int size)
+ 
+ u8 mt76_ac_to_hwq(u8 ac);
+ 
++static inline u8
++mt76_ac_to_tid(u8 ac)
++{
++	static const u8 ac_to_tid[] = {
++		[IEEE80211_AC_BE] = 0,
++		[IEEE80211_AC_BK] = 1,
++		[IEEE80211_AC_VI] = 4,
++		[IEEE80211_AC_VO] = 6
++	};
++
++	if (WARN_ON(ac >= IEEE80211_NUM_ACS))
++		return 0;
++
++	return ac_to_tid[ac];
++}
++
+ static inline struct ieee80211_txq *
+ mtxq_to_txq(struct mt76_txq *mtxq)
+ {
+diff --git a/mt76_connac_mcu.h b/mt76_connac_mcu.h
+index 61a14a8..f389470 100644
+--- a/mt76_connac_mcu.h
++++ b/mt76_connac_mcu.h
+@@ -1372,11 +1372,23 @@ enum {
+ 	UNI_OFFLOAD_OFFLOAD_BMC_RPY_DETECT,
+ };
+ 
++enum UNI_PER_STA_INFO_TAG {
++	UNI_PER_STA_RSSI,
++	UNI_PER_STA_CONTENTION_RX_RATE,
++	UNI_PER_STA_PER,
++	UNI_PER_STA_SNR,
++	UNI_PER_STA_TX_RATE,
++	UNI_PER_STA_TX_CNT,
++	UNI_PER_STA_TID_SN_GET,
++	UNI_PER_STA_TID_SN_SET,
++	UNI_PER_STA_MAX_NUM
++};
++
+ enum UNI_ALL_STA_INFO_TAG {
+ 	UNI_ALL_STA_TXRX_RATE,
+ 	UNI_ALL_STA_TX_STAT,
+ 	UNI_ALL_STA_TXRX_ADM_STAT,
+-	UNI_ALL_STA_TXRX_AIR_TIME,
++	UNI_ALL_STA_TXRX_AIRTIME,
+ 	UNI_ALL_STA_DATA_TX_RETRY_COUNT,
+ 	UNI_ALL_STA_GI_MODE,
+ 	UNI_ALL_STA_TXRX_MSDU_COUNT,
+diff --git a/mt7996/debugfs.c b/mt7996/debugfs.c
+index f8ba573..e26de48 100644
+--- a/mt7996/debugfs.c
++++ b/mt7996/debugfs.c
+@@ -992,12 +992,11 @@ mt7996_airtime_read(struct seq_file *s, void *data)
+ {
+ 	struct mt7996_dev *dev = dev_get_drvdata(s->private);
+ 	struct mt76_dev *mdev = &dev->mt76;
+-	struct mt7996_vow_sta_ctrl *vow;
++	struct mt76_sta_stats *stats;
+ 	struct ieee80211_sta *sta;
+ 	struct mt7996_sta *msta;
+ 	struct mt76_wcid *wcid;
+ 	struct mt76_vif *vif;
+-	u64 airtime;
+ 	u16 i;
+ 
+ 	seq_printf(s, "VoW Airtime Information:\n");
+@@ -1009,16 +1008,16 @@ mt7996_airtime_read(struct seq_file *s, void *data)
+ 
+ 		msta = container_of(wcid, struct mt7996_sta, wcid);
+ 		sta = container_of((void *)msta, struct ieee80211_sta, drv_priv);
+-		vow = &msta->vow;
+ 		vif = &msta->vif->mt76;
++		stats = &wcid->stats;
+ 
+-		spin_lock_bh(&vow->lock);
+-		airtime = vow->tx_airtime;
+-		vow->tx_airtime = 0;
+-		spin_unlock_bh(&vow->lock);
++		seq_printf(s, "%pM WCID: %hu BandIdx: %hhu OmacIdx: 0x%hhx\t"
++		              "TxAirtime: %llu\tRxAirtime: %llu\n",
++		              sta->addr, i, vif->band_idx, vif->omac_idx,
++		              stats->tx_airtime, stats->rx_airtime);
+ 
+-		seq_printf(s, "%pM WCID: %hu BandIdx: %hhu OmacIdx: 0x%hhx\tTxAirtime: %llu\n",
+-		           sta->addr, i, vif->band_idx, vif->omac_idx, airtime);
++		stats->tx_airtime = 0;
++		stats->rx_airtime = 0;
+ 	}
+ 	rcu_read_unlock();
+ 
+diff --git a/mt7996/mac.c b/mt7996/mac.c
+index 0805251..782594c 100644
+--- a/mt7996/mac.c
++++ b/mt7996/mac.c
+@@ -12,8 +12,6 @@
+ #include "mcu.h"
+ #include "vendor.h"
+ 
+-#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 = {
+@@ -93,110 +91,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 ieee80211_sta *sta;
+-	struct mt7996_sta *msta;
+-	struct mt7996_vow_sta_ctrl *vow;
+-	u32 tx_time[IEEE80211_NUM_ACS], rx_time[IEEE80211_NUM_ACS];
+-	LIST_HEAD(sta_poll_list);
+-	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 = list_first_entry(&sta_poll_list,
+-					struct mt7996_sta, wcid.poll_list);
+-		list_del_init(&msta->wcid.poll_list);
+-		spin_unlock_bh(&dev->mt76.sta_poll_lock);
+-
+-		idx = msta->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->airtime_ac[i];
+-			u32 rx_last = msta->airtime_ac[i + 4];
+-
+-			msta->airtime_ac[i] = mt76_rr(dev, addr);
+-			msta->airtime_ac[i + 4] = mt76_rr(dev, addr + 4);
+-
+-			tx_time[i] = msta->airtime_ac[i] - tx_last;
+-			rx_time[i] = msta->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->airtime_ac, 0, sizeof(msta->airtime_ac));
+-		}
+-
+-		if (!msta->wcid.sta)
+-			continue;
+-
+-		sta = container_of((void *)msta, struct ieee80211_sta,
+-				   drv_priv);
+-		vow = &msta->vow;
+-		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);
+-
+-			spin_lock_bh(&vow->lock);
+-			vow->tx_airtime += tx_cur;
+-			spin_unlock_bh(&vow->lock);
+-		}
+-
+-		/* 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);
+-
+-		msta->ack_signal =
+-			mt76_rx_signal(msta->vif->phy->mt76->antenna_mask, rssi);
+-
+-		ewma_avg_signal_add(&msta->avg_ack_signal, -msta->ack_signal);
+-	}
+-
+-	rcu_read_unlock();
+-}
+-
+ void mt7996_mac_enable_rtscts(struct mt7996_dev *dev,
+ 			      struct ieee80211_vif *vif, bool enable)
+ {
+@@ -1206,8 +1100,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);
+ 
+@@ -2379,31 +2271,42 @@ void mt7996_mac_sta_rc_work(struct work_struct *work)
+ 
+ void mt7996_mac_work(struct work_struct *work)
+ {
+-	struct mt7996_phy *phy;
+-	struct mt76_phy *mphy;
+-
+-	mphy = (struct mt76_phy *)container_of(work, struct mt76_phy,
+-					       mac_work.work);
+-	phy = mphy->priv;
++	struct mt76_phy *mphy = (struct mt76_phy *)container_of(work, struct mt76_phy,
++	                                                        mac_work.work);
++	struct mt7996_phy *phy = mphy->priv;
++	struct mt76_dev *mdev = mphy->dev;
+ 
+-	mutex_lock(&mphy->dev->mutex);
++	mutex_lock(&mdev->mutex);
+ 
+ 	mt76_update_survey(mphy);
+ 	if (++mphy->mac_work_count == 5) {
++		int i;
++
+ 		mphy->mac_work_count = 0;
+ 
+ 		mt7996_mac_update_stats(phy);
+ 
+-		mt7996_mcu_get_all_sta_info(phy, UNI_ALL_STA_TXRX_RATE);
+-		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);
++		/* Update DEV-wise information only in
++		 * the MAC work of the first band running.
++		 */
++		for (i = MT_BAND0; i <= mphy->band_idx; ++i) {
++			if (i == mphy->band_idx) {
++				mt7996_mcu_get_all_sta_info(mdev, UNI_ALL_STA_TXRX_RATE);
++				mt7996_mcu_get_all_sta_info(mdev, UNI_ALL_STA_TXRX_AIRTIME);
++				mt7996_mcu_get_rssi(mdev);
++				if (mtk_wed_device_active(&mdev->mmio.wed)) {
++					mt7996_mcu_get_all_sta_info(mdev, UNI_ALL_STA_TXRX_ADM_STAT);
++					mt7996_mcu_get_all_sta_info(mdev, UNI_ALL_STA_TXRX_MSDU_COUNT);
++				}
++			} else if (mt7996_band_valid(phy->dev, i) &&
++			           test_bit(MT76_STATE_RUNNING, &mdev->phys[i]->state))
++				break;
+ 		}
+ 	}
+ 
+-	mutex_unlock(&mphy->dev->mutex);
++	mutex_unlock(&mdev->mutex);
+ 
+-	mt76_tx_status_check(mphy->dev, false);
++	mt76_tx_status_check(mdev, false);
+ 
+ 	ieee80211_queue_delayed_work(mphy->hw, &mphy->mac_work,
+ 				     MT7996_WATCHDOG_TIME);
+diff --git a/mt7996/mcu.c b/mt7996/mcu.c
+index 6366cf0..b02303b 100644
+--- a/mt7996/mcu.c
++++ b/mt7996/mcu.c
+@@ -563,7 +563,8 @@ mt7996_mcu_rx_all_sta_info_event(struct mt7996_dev *dev, struct sk_buff *skb)
+ 		u16 wlan_idx;
+ 		struct mt76_wcid *wcid;
+ 		struct mt76_phy *mphy;
+-		u32 tx_bytes, rx_bytes, tx_packets, rx_packets;
++		struct ieee80211_sta *sta;
++		u32 tx_bytes, rx_bytes, tx_airtime, rx_airtime, tx_packets, rx_packets;
+ 
+ 		switch (le16_to_cpu(res->tag)) {
+ 		case UNI_ALL_STA_TXRX_RATE:
+@@ -584,7 +585,7 @@ mt7996_mcu_rx_all_sta_info_event(struct mt7996_dev *dev, struct sk_buff *skb)
+ 				break;
+ 
+ 			mphy = mt76_dev_phy(&dev->mt76, wcid->phy_idx);
+-			for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
++			for (ac = IEEE80211_AC_VO; ac < IEEE80211_NUM_ACS; ac++) {
+ 				tx_bytes = le32_to_cpu(res->adm_stat[i].tx_bytes[ac]);
+ 				rx_bytes = le32_to_cpu(res->adm_stat[i].rx_bytes[ac]);
+ 
+@@ -616,6 +617,24 @@ mt7996_mcu_rx_all_sta_info_event(struct mt7996_dev *dev, struct sk_buff *skb)
+ 			__mt7996_stat_to_netdev(mphy, wcid, 0, 0,
+ 						tx_packets, rx_packets);
+ 			break;
++		case UNI_ALL_STA_TXRX_AIRTIME:
++			wlan_idx = le16_to_cpu(res->airtime[i].wlan_idx);
++			wcid = rcu_dereference(dev->mt76.wcid[wlan_idx]);
++			sta = wcid_to_sta(wcid);
++			if (!sta)
++				continue;
++
++			for (ac = IEEE80211_AC_VO; ac < IEEE80211_NUM_ACS; ++ac) {
++				u8 lmac_ac = mt76_connac_lmac_mapping(ac);
++				tx_airtime = le32_to_cpu(res->airtime[i].tx[lmac_ac]);
++				rx_airtime = le32_to_cpu(res->airtime[i].rx[lmac_ac]);
++
++				wcid->stats.tx_airtime += tx_airtime;
++				wcid->stats.rx_airtime += rx_airtime;
++				ieee80211_sta_register_airtime(sta, mt76_ac_to_tid(ac),
++				                               tx_airtime, rx_airtime);
++			}
++			break;
+ 		default:
+ 			break;
+ 		}
+@@ -2244,8 +2263,6 @@ mt7996_mcu_sta_init_vow(struct mt7996_phy *phy, struct mt7996_sta *msta)
+ 	vow->drr_quantum[IEEE80211_AC_VI] = VOW_DRR_QUANTUM_IDX1;
+ 	vow->drr_quantum[IEEE80211_AC_BE] = VOW_DRR_QUANTUM_IDX2;
+ 	vow->drr_quantum[IEEE80211_AC_BK] = VOW_DRR_QUANTUM_IDX2;
+-	vow->tx_airtime = 0;
+-	spin_lock_init(&vow->lock);
+ 
+ 	ret = mt7996_mcu_set_vow_drr_ctrl(phy, msta, VOW_DRR_CTRL_STA_BSS_GROUP);
+ 	if (ret)
+@@ -4839,9 +4856,155 @@ int mt7996_mcu_set_rro(struct mt7996_dev *dev, u16 tag, u16 val)
+ 				 sizeof(req), true);
+ }
+ 
+-int mt7996_mcu_get_all_sta_info(struct mt7996_phy *phy, u16 tag)
++int mt7996_mcu_get_per_sta_info(struct mt76_dev *dev, u16 tag,
++	                        u16 sta_num, u16 *sta_list)
++{
++#define PER_STA_INFO_MAX_NUM	90
++	struct mt7996_mcu_per_sta_info_event *res;
++	struct mt76_wcid *wcid;
++	struct sk_buff *skb;
++	u16 wlan_idx;
++	int i, ret;
++	struct {
++		u8 __rsv1;
++		u8 unsolicit;
++		u8 __rsv2[2];
++
++		__le16 tag;
++		__le16 len;
++		__le16 sta_num;
++		u8 __rsv3[2];
++		__le16 sta_list[PER_STA_INFO_MAX_NUM];
++	} __packed req = {
++		.unsolicit = 0,
++		.tag = cpu_to_le16(tag),
++		.len = cpu_to_le16(sizeof(req) - 4),
++		.sta_num = cpu_to_le16(sta_num)
++	};
++
++	if (sta_num > PER_STA_INFO_MAX_NUM)
++		return -EINVAL;
++
++	for (i = 0; i < sta_num; ++i)
++		req.sta_list[i] = cpu_to_le16(sta_list[i]);
++
++	ret = mt76_mcu_send_and_get_msg(dev, 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;
++	if (le16_to_cpu(res->tag) != tag) {
++		ret = -EINVAL;
++		goto out;
++	}
++
++	rcu_read_lock();
++	switch (tag) {
++	case UNI_PER_STA_RSSI:
++		for (i = 0; i < sta_num; ++i) {
++			struct mt7996_sta *msta;
++			struct mt76_phy *phy;
++			s8 rssi[4];
++			u8 *rcpi;
++
++			wlan_idx = le16_to_cpu(res->rssi[i].wlan_idx);
++			wcid = rcu_dereference(dev->wcid[wlan_idx]);
++			if (wcid) {
++				rcpi = res->rssi[i].rcpi;
++				rssi[0] = to_rssi(MT_PRXV_RCPI0, rcpi[0]);
++				rssi[1] = to_rssi(MT_PRXV_RCPI0, rcpi[1]);
++				rssi[2] = to_rssi(MT_PRXV_RCPI0, rcpi[2]);
++				rssi[3] = to_rssi(MT_PRXV_RCPI0, rcpi[3]);
++
++				msta = container_of(wcid, struct mt7996_sta, wcid);
++				phy = msta->vif->phy->mt76;
++				msta->ack_signal = mt76_rx_signal(phy->antenna_mask, rssi);
++				ewma_avg_signal_add(&msta->avg_ack_signal, -msta->ack_signal);
++			} else {
++				ret = -EINVAL;
++				dev_err(dev->dev, "Failed to update RSSI for "
++				                  "invalid WCID: %hu\n", wlan_idx);
++			}
++		}
++		break;
++	case UNI_PER_STA_TX_CNT:
++		for (i = 0; i < sta_num; ++i) {
++			wlan_idx = le16_to_cpu(res->tx_cnt[i].wlan_idx);
++			wcid = rcu_dereference(dev->wcid[wlan_idx]);
++			if (wcid) {
++				wcid->stats.tx_total_mpdu_cnt +=
++				            le32_to_cpu(res->tx_cnt[i].total);
++				wcid->stats.tx_failed_mpdu_cnt +=
++				            le32_to_cpu(res->tx_cnt[i].failed);
++			} else {
++				ret = -EINVAL;
++				dev_err(dev->dev, "Failed to update TX MPDU counts "
++				                  "for invalid WCID: %hu\n", wlan_idx);
++			}
++		}
++		break;
++	default:
++		ret = -EINVAL;
++		dev_err(dev->dev, "Unknown UNI_PER_STA_INFO_TAG: %d\n", tag);
++	}
++	rcu_read_unlock();
++out:
++	dev_kfree_skb(skb);
++	return ret;
++}
++
++int mt7996_mcu_get_rssi(struct mt76_dev *dev)
++{
++	u16 sta_list[PER_STA_INFO_MAX_NUM];
++	LIST_HEAD(sta_poll_list);
++	struct mt7996_sta *msta;
++	int i, ret;
++	bool empty = false;
++
++	spin_lock_bh(&dev->sta_poll_lock);
++	list_splice_init(&dev->sta_poll_list, &sta_poll_list);
++	spin_unlock_bh(&dev->sta_poll_lock);
++
++	while (!empty) {
++		for (i = 0; i < PER_STA_INFO_MAX_NUM; ++i) {
++			spin_lock_bh(&dev->sta_poll_lock);
++			if (list_empty(&sta_poll_list)) {
++				spin_unlock_bh(&dev->sta_poll_lock);
++
++				if (i == 0)
++					return 0;
++
++				empty = true;
++				break;
++			}
++			msta = list_first_entry(&sta_poll_list,
++			                        struct mt7996_sta,
++			                        wcid.poll_list);
++			list_del_init(&msta->wcid.poll_list);
++			spin_unlock_bh(&dev->sta_poll_lock);
++
++			sta_list[i] = msta->wcid.idx;
++		}
++
++		ret = mt7996_mcu_get_per_sta_info(dev, UNI_PER_STA_RSSI,
++		                                  i, sta_list);
++		if (ret) {
++			/* Add STAs, whose RSSI has not been updated,
++			 * back to polling list.
++			 */
++			spin_lock_bh(&dev->sta_poll_lock);
++			list_splice(&sta_poll_list, &dev->sta_poll_list);
++			spin_unlock_bh(&dev->sta_poll_lock);
++			break;
++		}
++	}
++
++	return ret;
++}
++
++int mt7996_mcu_get_all_sta_info(struct mt76_dev *dev, u16 tag)
+ {
+-	struct mt7996_dev *dev = phy->dev;
+ 	struct {
+ 		u8 _rsv[4];
+ 
+@@ -4852,7 +5015,7 @@ int mt7996_mcu_get_all_sta_info(struct mt7996_phy *phy, u16 tag)
+ 		.len = cpu_to_le16(sizeof(req) - 4),
+ 	};
+ 
+-	return mt76_mcu_send_msg(&dev->mt76, MCU_WM_UNI_CMD(ALL_STA_INFO),
++	return mt76_mcu_send_msg(dev, MCU_WM_UNI_CMD(ALL_STA_INFO),
+ 				 &req, sizeof(req), false);
+ }
+ 
+diff --git a/mt7996/mcu.h b/mt7996/mcu.h
+index a58f52d..e64812c 100644
+--- a/mt7996/mcu.h
++++ b/mt7996/mcu.h
+@@ -199,6 +199,31 @@ struct mt7996_mcu_mib {
+ 	__le64 data;
+ } __packed;
+ 
++struct per_sta_rssi {
++	__le16 wlan_idx;
++	u8 __rsv[2];
++	u8 rcpi[4];
++} __packed;
++
++struct per_sta_tx_cnt {
++	__le16 wlan_idx;
++	u8 __rsv[2];
++	__le32 total;
++	__le32 failed;
++} __packed;
++
++struct mt7996_mcu_per_sta_info_event {
++	u8 __rsv[4];
++
++	__le16 tag;
++	__le16 len;
++
++	union {
++		struct per_sta_rssi rssi[0];
++		struct per_sta_tx_cnt tx_cnt[0];
++	};
++} __packed;
++
+ struct all_sta_trx_rate {
+ 	__le16 wlan_idx;
+ 	u8 __rsv1[2];
+@@ -237,13 +262,18 @@ struct mt7996_mcu_all_sta_info_event {
+ 			__le32 tx_bytes[IEEE80211_NUM_ACS];
+ 			__le32 rx_bytes[IEEE80211_NUM_ACS];
+ 		} adm_stat[0] __packed;
+-
+ 		struct {
+ 			__le16 wlan_idx;
+ 			u8 rsv[2];
+ 			__le32 tx_msdu_cnt;
+ 			__le32 rx_msdu_cnt;
+ 		} msdu_cnt[0] __packed;
++		struct {
++			__le16 wlan_idx;
++			u8 __rsv[2];
++			__le32 tx[IEEE80211_NUM_ACS];
++			__le32 rx[IEEE80211_NUM_ACS];
++		} airtime[0] __packed;
+ 	} __packed;
+ } __packed;
+ 
+diff --git a/mt7996/mt7996.h b/mt7996/mt7996.h
+index 929a077..a0cc8f3 100644
+--- a/mt7996/mt7996.h
++++ b/mt7996/mt7996.h
+@@ -125,6 +125,8 @@
+ #define MT7996_RRO_MSDU_PG_CR_CNT 8
+ #define MT7996_RRO_MSDU_PG_SIZE_PER_CR 0x10000
+ 
++#define to_rssi(field, rcpi)	((FIELD_GET(field, rcpi) - 220) / 2)
++
+ struct mt7996_vif;
+ struct mt7996_sta;
+ struct mt7996_dfs_pulse;
+@@ -297,8 +299,6 @@ struct mt7996_vow_sta_ctrl {
+ 	bool paused;
+ 	u8 bss_grp_idx;
+ 	u8 drr_quantum[IEEE80211_NUM_ACS];
+-	u64 tx_airtime;
+-	spinlock_t lock;
+ };
+ 
+ struct mt7996_sta {
+@@ -307,7 +307,6 @@ struct mt7996_sta {
+ 	struct mt7996_vif *vif;
+ 
+ 	struct list_head rc_list;
+-	u32 airtime_ac[8];
+ 
+ 	int ack_signal;
+ 	struct ewma_avg_signal avg_ack_signal;
+@@ -404,6 +403,21 @@ struct mt7996_air_monitor_ctrl {
+ };
+ #endif
+ 
++struct mt7996_rro_ba_session {
++	u32 ack_sn         :12;
++	u32 win_sz         :3;
++	u32 bn             :1;
++	u32 last_in_sn     :12;
++	u32 bc             :1;
++	u32 bd             :1;
++	u32 sat            :1;
++	u32 cn             :1;
++	u32 within_cnt     :12;
++	u32 to_sel         :3;
++	u32 rsv            :1;
++	u32 last_in_rxtime :12;
++};
++
+ struct mt7996_phy {
+ 	struct mt76_phy *mt76;
+ 	struct mt7996_dev *dev;
+@@ -599,6 +613,7 @@ struct mt7996_dev {
+ 		u32 fw_dbg_module;
+ 		u8 fw_dbg_lv;
+ 		u32 bcn_total_cnt[__MT_MAX_BAND];
++		u32 sid;
+ 	} dbg;
+ 	const struct mt7996_dbg_reg_desc *dbg_reg;
+ #endif
+@@ -824,7 +839,10 @@ int mt7996_mcu_fw_dbg_ctrl(struct mt7996_dev *dev, u32 module, u8 level);
+ 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 mt76_dev *dev, u16 tag,
++	                        u16 sta_num, u16 *sta_list);
++int mt7996_mcu_get_rssi(struct mt76_dev *dev);
++int mt7996_mcu_get_all_sta_info(struct mt76_dev *dev, u16 tag);
+ int mt7996_mcu_wed_rro_reset_sessions(struct mt7996_dev *dev, u16 id);
+ int mt7996_mcu_set_tx_power_ctrl(struct mt7996_phy *phy, u8 power_ctrl_id, u8 data);
+ int mt7996_mcu_get_tx_power_info(struct mt7996_phy *phy, u8 category, void *event);
+diff --git a/mt7996/mtk_debugfs.c b/mt7996/mtk_debugfs.c
+index 70ff761..dab5b23 100644
+--- a/mt7996/mtk_debugfs.c
++++ b/mt7996/mtk_debugfs.c
+@@ -3106,6 +3106,69 @@ mt7996_vow_drr_dbg(void *data, u64 val)
+ DEFINE_DEBUGFS_ATTRIBUTE(fops_vow_drr_dbg, NULL,
+ 			 mt7996_vow_drr_dbg, "%lld\n");
+ 
++static int
++mt7996_rro_session_read(struct seq_file *s, void *data)
++{
++	struct mt7996_dev *dev = dev_get_drvdata(s->private);
++	struct mt7996_rro_ba_session *tbl;
++	u32 value[2];
++
++	mt76_wr(dev, MT_RRO_DBG_RD_CTRL, MT_RRO_DBG_RD_EXEC +
++		(dev->dbg.sid >> 1) + 0x200);
++
++	if (dev->dbg.sid & 0x1) {
++		value[0] = mt76_rr(dev, MT_RRO_DBG_RDAT_DW(2));
++		value[1] = mt76_rr(dev, MT_RRO_DBG_RDAT_DW(3));
++	} else {
++		value[0] = mt76_rr(dev, MT_RRO_DBG_RDAT_DW(0));
++		value[1] = mt76_rr(dev, MT_RRO_DBG_RDAT_DW(1));
++	}
++
++	tbl = (struct mt7996_rro_ba_session *)&value[0];
++
++	seq_printf(s, " seid %d:\nba session table DW0:%08x DW2:%08x\n",
++		   dev->dbg.sid, value[0], value[1]);
++
++	seq_printf(s, "ack_sn = 0x%x, last_in_sn = 0x%x, sat/bn/bc/bd/cn = %d/%d/%d/%d/%d\n",
++		   tbl->ack_sn, tbl->last_in_sn, tbl->sat, tbl->bn, tbl->bc, tbl->bd, tbl->cn);
++
++	seq_printf(s, "within_cnt = %d, to_sel = %d, last_in_rxtime = %d\n",
++		   tbl->within_cnt, tbl->to_sel, tbl->last_in_rxtime);
++
++	return 0;
++}
++
++static int
++mt7996_show_rro_mib(struct seq_file *s, void *data)
++{
++	struct mt7996_dev *dev = dev_get_drvdata(s->private);
++	u32 reg[12];
++
++	seq_printf(s, "RRO mib Info:\n");
++
++	reg[0] = mt76_rr(dev, WF_RRO_TOP_STATISTIC(0));
++	reg[1] = mt76_rr(dev, WF_RRO_TOP_STATISTIC(1));
++	reg[2] = mt76_rr(dev, WF_RRO_TOP_STATISTIC(2));
++	reg[3] = mt76_rr(dev, WF_RRO_TOP_STATISTIC(3));
++	reg[4] = mt76_rr(dev, WF_RRO_TOP_STATISTIC(4));
++	reg[5] = mt76_rr(dev, WF_RRO_TOP_STATISTIC(5));
++	reg[6] = mt76_rr(dev, WF_RRO_TOP_STATISTIC(6));
++	reg[7] = mt76_rr(dev, WF_RRO_TOP_STATISTIC(7));
++	reg[8] = mt76_rr(dev, WF_RRO_TOP_STATISTIC(8));
++	reg[9] = mt76_rr(dev, WF_RRO_TOP_STATISTIC(9));
++	reg[10] = mt76_rr(dev, WF_RRO_TOP_STATISTIC(10));
++	reg[11] = mt76_rr(dev, WF_RRO_TOP_STATISTIC(11));
++
++	seq_printf(s, "STEP_ONE/WITHIN/SURPASS = %x/%x/%x\n", reg[0], reg[3], reg[4]);
++	seq_printf(s, "REPEAT/OLDPKT/BAR = %x/%x/%x\n", reg[1], reg[2], reg[5]);
++	seq_printf(s, "SURPASS with big gap = %x\n", reg[6]);
++	seq_printf(s, "DISCONNECT/INVALID = %x/%x\n", reg[7], reg[8]);
++	seq_printf(s, "TO(Step one)/TO(flush all) = %x/%x\n", reg[9], reg[10]);
++	seq_printf(s, "buf ran out = %x\n", reg[11]);
++
++	return 0;
++}
++
+ int mt7996_mtk_init_debugfs(struct mt7996_phy *phy, struct dentry *dir)
+ {
+ 	struct mt7996_dev *dev = phy->dev;
+@@ -3205,6 +3268,14 @@ int mt7996_mtk_init_debugfs(struct mt7996_phy *phy, struct dentry *dir)
+ 
+ 	debugfs_create_file("muru_prot_thr", 0200, dir, phy, &fops_muru_prot_thr);
+ 
++	if (dev->has_rro) {
++		debugfs_create_u32("rro_sid", 0600, dir, &dev->dbg.sid);
++		debugfs_create_devm_seqfile(dev->mt76.dev, "rro_sid_info", dir,
++					    mt7996_rro_session_read);
++		debugfs_create_devm_seqfile(dev->mt76.dev, "rro_mib", dir,
++					    mt7996_show_rro_mib);
++	}
++
+ 	return 0;
+ }
+ 
+diff --git a/mt7996/regs.h b/mt7996/regs.h
+index cbd7170..a001d9f 100644
+--- a/mt7996/regs.h
++++ b/mt7996/regs.h
+@@ -122,6 +122,8 @@ enum offs_rev {
+ #define MT_MCU_INT_EVENT_DMA_INIT		BIT(1)
+ #define MT_MCU_INT_EVENT_RESET_DONE		BIT(3)
+ 
++#define WF_RRO_TOP_STATISTIC(_n)		MT_RRO_TOP(0x180 + _n * 0x4)
++
+ /* PLE */
+ #define MT_PLE_BASE				0x820c0000
+ #define MT_PLE(ofs)				(MT_PLE_BASE + (ofs))
+-- 
+2.18.0
+