* [PATCH v1] wifi: mt76: mt7928: Add debugfs for stats to provide debug purpose
@ 2026-08-24 3:07 Eason Lai
2026-08-25 4:00 ` kernel test robot
` (2 more replies)
0 siblings, 3 replies; 12+ messages in thread
From: Eason Lai @ 2026-08-24 3:07 UTC (permalink / raw)
To: nbd, lorenzo
Cc: linux-wireless, linux-mediatek, kun.wu, deren.wu, sean.wang,
quan.zhou, ryder.lee, leon.yen, litien.chang, jb.tsai,
michael.shih, eason.lai
From: Michael Shih <michael.shih@mediatek.com>
Usage:
(1) Connect STA to AP (You can use dhclient CMD to ask for DHCP IP, to use ping to AP)
DHCP IP CMD: dhclient ${WLAN_IF} -v
Example: dhclient wlan0 -v
## (To simulate traffic) Ping 10 packets for every second to AP
ping 192.168.0.XXX -i0.1
(2) (Get stats) Use command, phyX is your WiFi interface:
cat /sys/kernel/debug/ieee80211/phyX/mt76/stats
(3) You will see below stats info:
(STA) connected AP MAC Address = a8:42:a1:46:c4:6b
BssIdx = [0]
StaRecIdx = [1],
RSSI = -25
Link_Speed = 2402000 (kbit/s)
temperature = 40
Tx success = 716
Tx fail to Rcv ACK after retry = 0
Rx Mpdu = 23
Rx Fcs Error = 1 (MPDU)
Rx FIFO full = 0 (MPDU)
TRX_AGG_RANGE[0] = 0 (PPDU)
TRX_AGG_RANGE[1] = 0 (PPDU)
TRX_AGG_RANGE[2] = 0 (PPDU)
TRX_AGG_RANGE[3] = 0 (PPDU)
TRX_AGG_RANGE[4] = 0 (PPDU)
TRX_AGG_RANGE[5] = 0 (PPDU)
TRX_AGG_RANGE[6] = 0 (PPDU)
TRX_AGG_RANGE[7] = 0 (PPDU)
MPDUs in AMPDUs transmitted = 0 (MPDU)
MPDUs in AMPDUs transmitted with ACK reply = 0 (MPDU)
rate1_tx_cnt = 714
rate1_fail_cnt = 223
train_up = 2310
train_down = 3080
is_force_tx_stream = 0
is_force_se_off = 0
max_ampdu_factor = 9
tx_rate_up_penalty = 0
low_traffic_mode = 2
low_traffic_count = 0
low_traffic_dashboard = 3
dynamic_sgi_state = 0
dynamic_sgi_score = 2
dynamic_bw_state = 0
dynamic_gband_256qam_state = 3
vht_non_sp_rate_state = 0
Last TX Rate = 24M, BW20, OFDM, BCC, SPE24
Chip Out TX Power = +16.5 dBm
Cur Rate Index = 0
Rate index[0] --> NSS2_MCS11, BW160/BW8080, SGI, HE_SU, LDPC
Rate index[1] NSS2_MCS9, BW160/BW8080, SGI, HE_SU, LDPC
Rate index[2] NSS2_MCS7, BW160/BW8080, SGI, HE_SU, LDPC
Rate index[3] NSS2_MCS4, BW160/BW8080, SGI, HE_SU, LDPC
Rate index[4] NSS2_MCS3, BW160/BW8080, SGI, HE_SU, LDPC
Rate index[5] NSS1_MCS2, BW160/BW8080, SGI, HE_SU, LDPC
Rate index[6] NSS1_MCS1, BW80, SGI, HE_SU, LDPC
Rate index[7] NSS1_MCS0, BW80, SGI, HE_SU, LDPC
mib state:
=== Rx Related Counters ===
Rx with CRC = 24020 (MPDU)
Rx drop due to out of resource = 0 (MPDU)
Rx Mpdu = 93223 (MPDU)
Rx AMpdu = 668 (PPDU)
Rx PF Drop = 0 (MPDU)
Rx Len Mismatch = 5853 (PPDU)
=== Phy/Timing Related Counters ===
ChannelIdleCnt = 1040434
CCA_NAV_Tx_Time = 18510036
Rx_MDRDY_CNT = 166580 (PPDU)
CCK_MDRDY = 7782
OFDM_MDRDY = 11912731
OFDM_GREEN_MDRDY = 0
Prim CCA Time = 17917650
Sec CCA Time = 14922034
Prim ED Time = 5677528
=== Tx Related Counters(Generic) ===
BeaconTxCnt = 0
Tx 40MHz Cnt = 279 (MPDU)
Tx 80MHz Cnt = 44 (MPDU)
Tx 160MHz Cnt = 712 (MPDU)
=== BSSID[0] Related Counters ===
BA Miss Cnt = 210 (PPDU)
RTS Tx Cnt = 1003 (MPDU)
Frame Retry Cnt = 239 (MPDU)
Frame Retry 2 Cnt = 71 (MPDU)
RTS Retry Cnt = 152 (MPDU)
Ack Failed Cnt = 319 (PPDU)
=== AMPDU Related Counters ===
Tx AMPDU_Pkt_Cnt = 705 (PPDU)
Tx AMPDU_MPDU_Pkt_Cnt = 713 (MPDU)
AMPDU Tx success = 503 (MPDU)
AMPDU Tx fail count = 210 (MPDU), PER=29.4%
Signed-off-by: Michael Shih <michael.shih@mediatek.com>
---
.../wireless/mediatek/mt76/mt76_connac_mcu.h | 177 +++
.../wireless/mediatek/mt76/mt7925/Makefile | 2 +-
.../wireless/mediatek/mt76/mt7925/debugfs.c | 1054 +++++++++++++++++
.../net/wireless/mediatek/mt76/mt7925/stats.c | 585 +++++++++
.../net/wireless/mediatek/mt76/mt7925/stats.h | 305 +++++
5 files changed, 2122 insertions(+), 1 deletion(-)
create mode 100644 drivers/net/wireless/mediatek/mt76/mt7925/stats.c
create mode 100644 drivers/net/wireless/mediatek/mt76/mt7925/stats.h
diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
index 0ededa569e9a..72cc69750f47 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
@@ -1483,6 +1483,183 @@ enum {
MT_NIC_CAP_EML_CAP = 0x22,
};
+enum {
+ UNI_CMD_ID_STATISTICS_BASIC = 0,
+ UNI_CMD_ID_STATISTICS_LINK_QUALITY = 1,
+ UNI_CMD_ID_STATISTICS_STA = 2,
+ UNI_CMD_ID_STATISTICS_BUG_REPORT = 3,
+ UNI_CMD_ID_STATISTICS_TX_STATS = 4,
+ UNI_CMD_ID_STATISTICS_ALL_STATS = 5,
+ UNI_CMD_ID_STATISTICS_EML_STATS = 6,
+ UNI_CMD_ID_STATISTICS_REGULAR_STATS = 7,
+ UNI_CMD_ID_STATISTICS_BSS_LINK_QUALITY = 8,
+ UNI_CMD_ID_STATISTICS_ML_CHNL_COND = 9,
+ UNI_CMD_ID_STATISTICS_WTBL = 10,
+ UNI_CMD_ID_STATISTICS_LINK_LAYER_STATS = 128,
+ UNI_CMD_ID_STATISTICS_PPDU_LATENCY = 129,
+ UNI_CMD_ID_STATISTICS_CURRENT_TX_RATE = 130,
+ UNI_CMD_ID_STATISTICS_SET_TX_BITRATE_MONITOR = 131,
+ UNI_CMD_ID_STATISTICS_GET_PREDICT_TX_BITRATE = 132,
+ UNI_CMD_ID_STATISTICS_SET_ATPS_TYPE_RATE_MODE = 133,
+ UNI_CMD_ID_STATISTICS_SET_BEACON_REPORT = 134,
+ UNI_CMD_ID_STATISTICS_BSS_CURRENT_TX_RATE = 135,
+ UNI_CMD_ID_STATISTICS_GET_BSS_PREDICT_TX_BITRATE = 136,
+ UNI_CMD_ID_STATISTICS_MAX_NUM
+};
+
+enum {
+ UNI_CMD_MIB_DATA,
+};
+
+enum mt7925_mib_counter {
+ UNI_CMD_MIB_CNT_RX_FCS_ERR = 0,
+ UNI_CMD_MIB_CNT_RX_FIFO_OVERFLOW = 1,
+ UNI_CMD_MIB_CNT_RX_MPDU = 2,
+ UNI_CMD_MIB_CNT_AMPDU_RX_COUNT = 3,
+ UNI_CMD_MIB_CNT_RX_TOTAL_BYTE = 4,
+ UNI_CMD_MIB_CNT_RX_VALID_AMPDU_SF = 5,
+ UNI_CMD_MIB_CNT_RX_VALID_BYTE = 6,
+ UNI_CMD_MIB_CNT_CHANNEL_IDLE = 7,
+ UNI_CMD_MIB_CNT_VEC_DROP = 8,
+ UNI_CMD_MIB_CNT_DELIMITER_FAIL = 9,
+ UNI_CMD_MIB_CNT_VEC_MISMATCH = 10,
+ UNI_CMD_MIB_CNT_MDRDY = 11,
+ UNI_CMD_MIB_CNT_RX_CCK_MDRDY_TIME = 12,
+ UNI_CMD_MIB_CNT_RX_OFDM_LG_MIXED_MDRDY_TIME = 13,
+ UNI_CMD_MIB_CNT_RX_OFDM_GREEN_MDRDY_TIME = 14,
+ UNI_CMD_MIB_CNT_PF_DROP = 15,
+ UNI_CMD_MIB_CNT_LEN_MISMATCH = 16,
+ UNI_CMD_MIB_CNT_P_CCA_TIME = 17,
+ UNI_CMD_MIB_CNT_S_CCA_TIME = 18,
+ UNI_CMD_MIB_CNT_CCA_NAV_TX_TIME = 19,
+ UNI_CMD_MIB_CNT_P_ED_TIME = 20,
+ UNI_CMD_MIB_CNT_BCN_TX = 21,
+ UNI_CMD_MIB_CNT_TX_BW_20MHZ = 22,
+ UNI_CMD_MIB_CNT_TX_BW_40MHZ = 23,
+ UNI_CMD_MIB_CNT_TX_BW_80MHZ = 24,
+ UNI_CMD_MIB_CNT_TX_BW_160MHZ = 25,
+ UNI_CMD_RMAC_CNT_OBSS_AIRTIME = 26,
+ UNI_CMD_RMAC_CNT_NONWIFI_AIRTIME = 27,
+ UNI_CMD_MIB_CNT_TX_DUR_CNT = 28,
+ UNI_CMD_MIB_CNT_RX_DUR_CNT = 29,
+ UNI_CMD_MIB_CNT_BA_CNT = 30,
+ UNI_CMD_MIB_CNT_MAC2PHY_TX_TIME = 31,
+ UNI_CMD_MIB_CNT_RX_OUT_OF_RANGE_COUNT = 32,
+ UNI_CMD_MIB_CNT_IBF_TX = 33,
+ UNI_CMD_MIB_CNT_EBF_TX = 34,
+ UNI_CMD_MIB_CNT_MUBF_TX = 35,
+ UNI_CMD_MIB_CNT_RX_BF_VHTFBK = 36,
+ UNI_CMD_MIB_CNT_RX_BF_HTFBK = 37,
+ UNI_CMD_MIB_CNT_RX_BF_HEFBK = 38,
+ UNI_CMD_MIB_CNT_BFEE_TXFBK_BFPOLL_TRI = 39,
+ UNI_CMD_MIB_CNT_BFEE_TXFBK_TRI = 40,
+ UNI_CMD_MIB_CNT_BFEE_RX_FBKCQI = 41,
+ UNI_CMD_MIB_CNT_BFEE_RX_NDP = 42,
+ UNI_CMD_MIB_CNT_RX_BF_IBF_UPT = 43,
+ UNI_CMD_MIB_CNT_RX_BF_EBF_UPT = 44,
+ UNI_CMD_MIB_CNT_BFEE_SP_ABORT = 45,
+ UNI_CMD_MIB_CNT_BFEE_TB_LEN_ERR = 46,
+ UNI_CMD_MIB_CNT_BFEE_TXFBK_MUTE = 47,
+ UNI_CMD_MIB_CNT_BFEE_TMAC_ABORT = 48,
+ UNI_CMD_MIB_CNT_BFEE_TXFBK_CPL = 49,
+ UNI_CMD_MIB_CNT_BFEE_COANT_BLKTX = 50,
+ UNI_CMD_MIB_CNT_BFEE_FBK_SEG = 51,
+ UNI_CMD_MIB_CNT_NAV_TIME = 52,
+ UNI_CMD_MIB_CNT_TX_ABORT_CNT1 = 53,
+ UNI_CMD_MIB_CNT_TX_ABORT_CNT0 = 54,
+ UNI_CMD_MIB_CNT_TX_ABORT_CNT3 = 55,
+ UNI_CMD_MIB_CNT_TX_ABORT_CNT2 = 56,
+ UNI_CMD_MIB_CNT_BSS0_RTS_TX_CNT = 57,
+ UNI_CMD_MIB_CNT_BSS1_RTS_TX_CNT = 58,
+ UNI_CMD_MIB_CNT_BSS2_RTS_TX_CNT = 59,
+ UNI_CMD_MIB_CNT_BSS3_RTS_TX_CNT = 60,
+ UNI_CMD_MIB_CNT_BSS0_RTS_RETRY = 61,
+ UNI_CMD_MIB_CNT_BSS1_RTS_RETRY = 62,
+ UNI_CMD_MIB_CNT_BSS2_RTS_RETRY = 63,
+ UNI_CMD_MIB_CNT_BSS3_RTS_RETRY = 64,
+ UNI_CMD_MIB_CNT_BSS0_BA_MISS = 65,
+ UNI_CMD_MIB_CNT_BSS1_BA_MISS = 66,
+ UNI_CMD_MIB_CNT_BSS2_BA_MISS = 67,
+ UNI_CMD_MIB_CNT_BSS3_BA_MISS = 68,
+ UNI_CMD_MIB_CNT_BSS0_ACK_FAIL = 69,
+ UNI_CMD_MIB_CNT_BSS1_ACK_FAIL = 70,
+ UNI_CMD_MIB_CNT_BSS2_ACK_FAIL = 71,
+ UNI_CMD_MIB_CNT_BSS3_ACK_FAIL = 72,
+ UNI_CMD_MIB_CNT_BSS0_FRAME_RETRY = 73,
+ UNI_CMD_MIB_CNT_BSS1_FRAME_RETRY = 74,
+ UNI_CMD_MIB_CNT_BSS2_FRAME_RETRY = 75,
+ UNI_CMD_MIB_CNT_BSS3_FRAME_RETRY = 76,
+ UNI_CMD_MIB_CNT_BSS0_FRAME_RETRY_2 = 77,
+ UNI_CMD_MIB_CNT_BSS1_FRAME_RETRY_2 = 78,
+ UNI_CMD_MIB_CNT_BSS2_FRAME_RETRY_2 = 79,
+ UNI_CMD_MIB_CNT_BSS3_FRAME_RETRY_2 = 80,
+ UNI_CMD_MIB_CNT_RX_A1_SEARCH = 81,
+ UNI_CMD_MIB_CNT_RX_DROP_MPDU = 82,
+ UNI_CMD_MIB_CNT_RX_UNWANTED = 83,
+ UNI_CMD_MIB_CNT_RX_FCS_OK = 84,
+ UNI_CMD_MIB_CNT_SU_TX_OK = 85,
+ UNI_CMD_MIB_CNT_TX_FULL_BW = 86,
+ UNI_CMD_MIB_CNT_TX_AUTO_BW = 87,
+ UNI_CMD_MIB_CNT_CCA_SAMPLE_IDLE = 88,
+ UNI_CMD_MIB_CNT_CCA_SAMPLE_ACTIVE = 89,
+ UNI_CMD_MIB_CNT_CCA_TIME = 90,
+ UNI_CMD_MIB_CNT_S_20BW_CCA_TIME = 91,
+ UNI_CMD_MIB_CNT_S_40BW_CCA_TIME = 92,
+ UNI_CMD_MIB_CNT_S_80BW_CCA_TIME = 93,
+ UNI_CMD_MIB_CNT_S_160BW_CCA_TIME = 94,
+ UNI_CMD_MIB_CNT_S_P20BW_0_ED_TIME = 95,
+ UNI_CMD_MIB_CNT_S_P20BW_1_ED_TIME = 96,
+ UNI_CMD_MIB_CNT_S_P20BW_2_ED_TIME = 97,
+ UNI_CMD_MIB_CNT_S_P20BW_3_ED_TIME = 98,
+ UNI_CMD_MIB_CNT_S_P20BW_4_ED_TIME = 99,
+ UNI_CMD_MIB_CNT_S_P20BW_5_ED_TIME = 100,
+ UNI_CMD_MIB_CNT_S_P20BW_6_ED_TIME = 101,
+ UNI_CMD_MIB_CNT_S_P20BW_7_ED_TIME = 102,
+ UNI_CMD_MIB_CNT_S_P20BW_8_ED_TIME = 103,
+ UNI_CMD_MIB_CNT_S_P20BW_9_ED_TIME = 104,
+ UNI_CMD_MIB_CNT_S_P20BW_10_ED_TIME = 105,
+ UNI_CMD_MIB_CNT_S_P20BW_11_ED_TIME = 106,
+ UNI_CMD_MIB_CNT_S_P20BW_12_ED_TIME = 107,
+ UNI_CMD_MIB_CNT_S_P20BW_13_ED_TIME = 108,
+ UNI_CMD_MIB_CNT_S_P20BW_14_ED_TIME = 109,
+ UNI_CMD_MIB_CNT_S_P20BW_15_ED_TIME = 110,
+ UNI_CMD_MIB_CNT_TX_BW_320MHZ = 111,
+ UNI_CMD_MIB_CNT_TX_DDLMT_RNG0 = 112, /* Rng0~Rng4 112~116 */
+ UNI_CMD_MIB_CNT_BSS0_FRAME_RETRY_3 = 117, /* 117~120 */
+ UNI_CMD_MIB_CNT_BSS0_TX = 121, /* 121~124 */
+ UNI_CMD_MIB_CNT_BSS0_TX_DATA = 125, /* 125~128 */
+ UNI_CMD_MIB_CNT_BSS0_TX_BYTE = 129, /* 129~132 */
+ UNI_CMD_MIB_CNT_RX_OK_BSS0 = 133, /* 133~136 */
+ UNI_CMD_MIB_CNT_RX_BYTE_BSS0 = 137, /* 137~140 */
+ UNI_CMD_MIB_CNT_RX_DATA_BSS0 = 141, /* 141~144 */
+ UNI_CMD_MIB_CNT_MBSS0_TX_OK = 145, /* 145~160 */
+ UNI_CMD_MIB_CNT_MBSS0_TX_BYTE = 161, /* 161~176 */
+ UNI_CMD_MIB_CNT_RX_OK_MBSS0 = 177, /* 177~192 */
+ UNI_CMD_MIB_CNT_RX_BYTE_MBSS0 = 193, /* 193~208 */
+ UNI_CMD_MIB_CNT_AMPDU = 209,
+ UNI_CMD_MIB_CNT_AMPDU_MPDU = 210,
+ UNI_CMD_MIB_CNT_AMPDU_ACKED = 211,
+ UNI_CMD_MIB_CNT_MAX_NUM
+};
+
+enum ENUM_UNI_CMD_GET_STATISTICS_TAG {
+ UNI_CMD_GET_STATISTICS_TAG_BASIC = 0,
+ UNI_CMD_GET_STATISTICS_TAG_LINK_QUALITY = 1,
+ UNI_CMD_GET_STATISTICS_TAG_STA = 2,
+ UNI_CMD_GET_STATISTICS_TAG_BUG_REPORT = 3,
+ UNI_CMD_GET_STATISTICS_TAG_EML_STATS = 6,
+ UNI_CMD_GET_STATISTICS_TAG_REGULAR_STATS = 7,
+ UNI_CMD_GET_STATISTICS_TAG_BSS_LINK_QUALITY = 8,
+ UNI_CMD_GET_STATISTICS_TAG_GET_ML_CHNL_COND = 9,
+ UNI_CMD_GET_STATISTICS_TAG_STAT_WTBL = 10,
+ UNI_CMD_GET_STATISTICS_TAG_LINK_LAYER_STATS = 0x80,
+ UNI_CMD_GET_STATISTICS_TAG_PPDU_LATENCY,
+ UNI_CMD_GET_STATISTICS_TAG_CURRENT_TX_RATE,
+ UNI_CMD_GET_STATISTICS_TAG_BEACON_REPORT = 0x86,
+ UNI_CMD_GET_STATISTICS_TAG_BSS_CURRENT_TX_RATE = 0x87,
+ UNI_CMD_GET_STATISTICS_TAG_BSS_PRED_TX_BITRATE = 0x88,
+};
+
#define UNI_WOW_DETECT_TYPE_MAGIC BIT(0)
#define UNI_WOW_DETECT_TYPE_ANY BIT(1)
#define UNI_WOW_DETECT_TYPE_DISCONNECT BIT(2)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/Makefile b/drivers/net/wireless/mediatek/mt76/mt7925/Makefile
index f9dcc0bba393..3863a89b8974 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/Makefile
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/Makefile
@@ -4,7 +4,7 @@ obj-$(CONFIG_MT7925_COMMON) += mt7925-common.o
obj-$(CONFIG_MT7925E) += mt7925e.o
obj-$(CONFIG_MT7925U) += mt7925u.o
-mt7925-common-y := mac.o mcu.o regd.o main.o init.o debugfs.o nan.o
+mt7925-common-y := mac.o mcu.o regd.o main.o init.o debugfs.o nan.o stats.o
mt7925-common-$(CONFIG_NL80211_TESTMODE) += testmode.o
mt7925e-y := pci.o pci_mac.o pci_mcu.o
mt7925u-y := usb.o
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c b/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c
index d01ff49de47a..e3d2d9ef20fe 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c
@@ -3,6 +3,7 @@
#include "mt7925.h"
#include "mcu.h"
+#include "stats.h"
static int
mt7925_reg_set(void *data, u64 val)
@@ -286,6 +287,1057 @@ static int mt7925_chip_reset(void *data, u64 val)
DEFINE_DEBUGFS_ATTRIBUTE(fops_reset, NULL, mt7925_chip_reset, "%lld\n");
+/* fixedrate input format (12 hex digits):
+ * 0x[WCID(2)][Mode][BW][MCS][Nss][SGI][Preamble][STBC][LDPC][SPE_EN][HeLtf]
+ * The RA tag is fixed to SET_FIXED_RATE internally.
+ * WCID is 2 nibbles, all others are 1 nibble.
+ *
+ * [WCID] Wireless Client ID
+ * [Mode] CCK=0, OFDM=1, HT=2, GF=3, VHT=4, PLR=5, HE_SU=8, HE_ER_SU=9,
+ * HE_TRIG=10, HE_MU=11, EHT_ER=13, EHT_TB=14, EHT_SU and EHT_MU=15
+ * [BW] BW20=0, BW40=1, BW80=2, BW160=3, BW320=4
+ * [MCS] CCK=0~3, OFDM=0~7, HT=0~32, VHT=0~9, HE=0~11, EHT=0~13, EHT_ER=14~15
+ * [Nss] 1~8
+ * [GI] HT/VHT: 0=Long, 1=Short; HE/EHT: 0=0.8us, 1=1.6us, 2=3.2us
+ * [Preamble] Long=0, Short=other
+ * [STBC] Enable=1, Disable=0
+ * [LDPC] BCC=0, LDPC=1 (driver sends the FW's "on" value 7)
+ * [SPE_EN] spatial extension index
+ * [HeLtf] 1X=0, 2X=1, 4X=2 (NOTE: EHT modes require HeLtf=1)
+ */
+static int
+mt7925_fixedrate_set(void *data, u64 val)
+{
+ struct mt792x_dev *dev = data;
+ struct mt7925_ra_fixed_rate_v1 rate = {};
+
+ dev->fixed_rate = val;
+
+ rate.wlan_idx = cpu_to_le16((val >> 40) & 0xff);
+ rate.phy_mode = (val >> 36) & 0xf;
+ rate.bw = (val >> 32) & 0xf;
+ rate.mcs = (val >> 28) & 0xf;
+ rate.nss = (val >> 24) & 0xf;
+ rate.short_gi = cpu_to_le16((val >> 20) & 0xf);
+ rate.short_preamble = (val >> 16) & 0xf;
+ rate.stbc = (val >> 12) & 0xf;
+ rate.ecc = ((val >> 8) & 0xf) ? 7 : 0;
+ rate.spe = (val >> 4) & 0xf;
+ rate.he_ltf = cpu_to_le16((val >> 0) & 0xf);
+
+ mt792x_mutex_acquire(dev);
+ mt7925_mcu_set_fixed_rate(dev, &rate);
+ mt792x_mutex_release(dev);
+
+ return 0;
+}
+
+static int
+mt7925_fixedrate_get(void *data, u64 *val)
+{
+ struct mt792x_dev *dev = data;
+
+ *val = dev->fixed_rate;
+
+ return 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(fops_fixedrate, mt7925_fixedrate_get,
+ mt7925_fixedrate_set, "0x%llx\n");
+
+/* autorate input format (6 hex digits):
+ * 0x[Tag(2)][WCID(2)][En][Mode]
+ * The RA tag is fixed to SET_AUTO_RATE internally.
+ * WCID is 2 nibbles, En and Mode are 1 nibble each.
+ *
+ * [WCID] Wireless Client ID
+ * [En] Auto rate: Enable=1, Disable=0
+ * [Mode] don't care (RA picks the rate)
+ */
+static int
+mt7925_autorate_set(void *data, u64 val)
+{
+ struct mt792x_dev *dev = data;
+ u16 wlan_idx = (val >> 8) & 0xff;
+ bool enable = (val >> 4) & 0xf;
+ u8 mode = val & 0xf;
+
+ dev->auto_rate = val;
+
+ mt792x_mutex_acquire(dev);
+ mt7925_mcu_set_auto_rate(dev, wlan_idx, enable, mode);
+ mt792x_mutex_release(dev);
+
+ return 0;
+}
+
+static int
+mt7925_autorate_get(void *data, u64 *val)
+{
+ struct mt792x_dev *dev = data;
+
+ *val = dev->auto_rate;
+
+ return 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(fops_autorate, mt7925_autorate_get,
+ mt7925_autorate_set, "0x%llx\n");
+
+/* WTBL dump
+ *
+ * Usage:
+ * echo <idx> > /sys/kernel/debug/ieee80211/phy1/mt76/wtbl_idx
+ * cat /sys/kernel/debug/ieee80211/phy1/mt76/get_wtbl
+ *
+ * LMAC WTBL has 36 dwords per entry (connac3x).
+ */
+#define MT792x_WTBL_LMAC_DWORDS 36
+
+static int
+mt7925_wtbl_idx_set(void *data, u64 val)
+{
+ struct mt792x_dev *dev = data;
+
+ dev->wtbl_dump_idx = (u32)val;
+
+ return 0;
+}
+
+static int
+mt7925_wtbl_idx_get(void *data, u64 *val)
+{
+ struct mt792x_dev *dev = data;
+
+ *val = dev->wtbl_dump_idx;
+
+ return 0;
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(fops_wtbl_idx, mt7925_wtbl_idx_get,
+ mt7925_wtbl_idx_set, "%llu\n");
+
+static int
+mt7925_wtbl_read(struct seq_file *s, void *data)
+{
+ struct mt792x_dev *dev = dev_get_drvdata(s->private);
+ u32 idx = dev->wtbl_dump_idx;
+ u32 wdu_cr, wducr_val, base;
+ u32 dw0, dw1;
+ int i;
+
+ if (!mt76_is_mmio(&dev->mt76)) {
+ seq_puts(s, "WTBL direct dump not supported on this interface\n");
+ return 0;
+ }
+
+ if (idx >= MT792x_WTBL_SIZE) {
+ seq_printf(s, "Invalid WTBL idx %u (max %u)\n",
+ idx, MT792x_WTBL_SIZE - 1);
+ return 0;
+ }
+
+ mt792x_mutex_acquire(dev);
+
+ /* mt7925_mac_wtbl_lmac_addr() writes WDUCR group selection and
+ * returns the mapped register address for DW 0 of this WCID.
+ */
+ base = mt7925_mac_wtbl_lmac_addr(dev, idx, 0);
+ wdu_cr = is_mt7928(&dev->mt76) ? MT7928_WTBLON_TOP_WDUCR :
+ MT7925_WTBLON_TOP_WDUCR;
+ wducr_val = mt76_rr(dev, wdu_cr);
+
+ /* DW0[15:0] + DW1[31:0] encode the link address */
+ dw0 = mt76_rr(dev, base);
+ dw1 = mt76_rr(dev, base + 4);
+
+ seq_printf(s, "Dump WTBL info of WLAN_IDX: %u\n", idx);
+ seq_printf(s, "LMAC WTBL Addr: group:0x%08x=0x%08x addr: 0x%08x\n",
+ wdu_cr, wducr_val, base);
+ seq_printf(s, "LinkAddr: %02x:%02x:%02x:%02x:%02x:%02x\n",
+ dw1 & 0xff, (dw1 >> 8) & 0xff,
+ (dw1 >> 16) & 0xff, (dw1 >> 24) & 0xff,
+ dw0 & 0xff, (dw0 >> 8) & 0xff);
+
+ seq_puts(s, "\nLMAC WTBL raw data:\n");
+ for (i = 0; i < MT792x_WTBL_LMAC_DWORDS; i++)
+ seq_printf(s, "DW%02d: 0x%08x\n", i,
+ mt76_rr(dev, base + i * 4));
+
+ mt792x_mutex_release(dev);
+
+ return 0;
+}
+
+static int
+mt7925_vefuse_read(struct seq_file *s, void *data)
+{
+ struct mt792x_dev *dev = dev_get_drvdata(s->private);
+ u16 size = dev->vefuse_cap.size;
+ int off = 0;
+ u8 *buf;
+ int ret = 0;
+
+ if (!dev->vefuse_cap.present || !size) {
+ seq_puts(s, "vefuse: no region in FW image\n");
+ return 0;
+ }
+
+ buf = kzalloc(size, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ mt792x_mutex_acquire(dev);
+ while (off < (int)size) {
+ u16 chunk = min_t(u16, 512, size - off);
+
+ ret = mt7925_mcu_read_vefuse(dev, off, chunk, buf + off);
+ if (ret)
+ break;
+ off += chunk;
+ }
+ mt792x_mutex_release(dev);
+
+ if (ret) {
+ seq_printf(s, "vefuse: read failed at 0x%04x (%d)\n", off, ret);
+ kfree(buf);
+ return 0;
+ }
+
+ seq_printf(s, "vefuse content: %u bytes\n", size);
+ for (off = 0; off < (int)size; off += 16)
+ seq_printf(s, "%04x: %*ph\n", off,
+ (int)min_t(u16, 16, size - off), buf + off);
+
+ kfree(buf);
+
+ return 0;
+}
+
+/* send one perf_ind carrying the written value in every tx/rx byte field */
+static int mt7925_perf_ind_set(void *data, u64 val)
+{
+ struct mt792x_dev *dev = data;
+
+ return mt792x_perf_ind_trigger(dev, val);
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(fops_perf_ind, NULL, mt7925_perf_ind_set, "%lld\n");
+
+static int mt7925_perf_ind_enable_get(void *data, u64 *val)
+{
+ struct mt792x_dev *dev = data;
+
+ *val = dev->perf.enabled;
+
+ return 0;
+}
+
+static int mt7925_perf_ind_enable_set(void *data, u64 val)
+{
+ struct mt792x_dev *dev = data;
+
+ dev->perf.enabled = !!val;
+
+ return 0;
+}
+
+DEFINE_DEBUGFS_ATTRIBUTE(fops_perf_ind_enable, mt7925_perf_ind_enable_get,
+ mt7925_perf_ind_enable_set, "%lld\n");
+
+static int
+mt7925_coex_info(struct seq_file *s, void *data)
+{
+#define MT7925_CHIP_CONFIG_RESP_SIZE 320
+ struct mt792x_dev *dev = dev_get_drvdata(s->private);
+ u8 resp[MT7925_CHIP_CONFIG_RESP_SIZE], resp_type;
+ int i, ret;
+
+ mt792x_mutex_acquire(dev);
+ ret = mt7925_mcu_chip_config_query(dev, "coexBwcGetModeInfo 0",
+ &resp_type, resp, sizeof(resp));
+ mt792x_mutex_release(dev);
+
+ if (ret < 0)
+ return ret;
+
+ if (!ret)
+ seq_puts(s, "no reply\n");
+ else if (resp_type == CHIP_CONFIG_TYPE_ASCII)
+ seq_printf(s, "%.*s\n", ret, resp);
+ else
+ for (i = 0; i < ret; i += 16)
+ seq_printf(s, "%04x: %*ph\n", i,
+ min_t(int, 16, ret - i), resp + i);
+
+ return 0;
+}
+
+/* mt7925_dmashdl_read: standard DMASHDL (MT7925/MT7927 only).
+ * MT7928 uses DMASHDL Lite; see mt7928_dmashdl_lite_read below.
+ */
+static int
+mt7925_dmashdl_read(struct seq_file *s, void *data)
+{
+ struct mt792x_dev *dev = dev_get_drvdata(s->private);
+ u32 total_src = 0, total_rsv = 0;
+ u32 ffa_cnt, free_pg_cnt;
+ u32 ple_rsv, ple_src;
+ u32 val;
+ int i;
+
+ if (!mt76_is_mmio(&dev->mt76)) {
+ seq_puts(s, "DMASHDL dump not supported on this interface\n");
+ return 0;
+ }
+
+ mt792x_mutex_acquire(dev);
+
+ seq_puts(s, "DMASHDL Info:\n");
+
+ val = mt76_rr(dev, MT_DMASHDL_REFILL);
+ seq_printf(s, "\tRefill control(0x%08x): 0x%08x\n",
+ MT_DMASHDL_REFILL, val);
+
+ val = mt76_rr(dev, MT_DMASHDL_PKT_MAX_SIZE);
+ seq_printf(s, "\tPkt max size(0x%08x): 0x%08x\n",
+ MT_DMASHDL_PKT_MAX_SIZE, val);
+ seq_printf(s, "\t\tPLE pkt max=0x%03x, PSE pkt max=0x%03x\n",
+ FIELD_GET(MT_DMASHDL_PKT_MAX_SIZE_PLE, val),
+ FIELD_GET(MT_DMASHDL_PKT_MAX_SIZE_PSE, val));
+
+ val = mt76_rr(dev, MT_DMASHDL_ERROR_FLAG_CTRL);
+ seq_printf(s, "\tError flag ctrl(0x%08x): 0x%08x\n",
+ MT_DMASHDL_ERROR_FLAG_CTRL, val);
+
+ for (i = 0; i < 15; i++) {
+ u32 quota = mt76_rr(dev, MT_DMASHDL_GROUP_QUOTA(i));
+ u32 status = mt76_rr(dev, MT_DMASHDL_STATUS_RD_GP(i));
+ u32 rsv = FIELD_GET(MT_DMASHDL_STATUS_RD_GP_RSV, status);
+ u32 src = FIELD_GET(MT_DMASHDL_STATUS_RD_GP_SRC, status);
+
+ seq_printf(s, "\tGroup %2d: quota(min/max)=0x%03x/0x%03x, used/rsv=0x%03x/0x%03x\n",
+ i,
+ FIELD_GET(MT_DMASHDL_GROUP_QUOTA_MIN, quota),
+ FIELD_GET(MT_DMASHDL_GROUP_QUOTA_MAX, quota),
+ src, rsv);
+ total_src += src;
+ total_rsv += rsv;
+ }
+
+ val = mt76_rr(dev, MT_DMASHDL_STATUS_RD);
+ free_pg_cnt = FIELD_GET(MT_DMASHDL_STATUS_RD_FREE_PG, val);
+ ffa_cnt = FIELD_GET(MT_DMASHDL_STATUS_RD_FFA, val);
+ seq_printf(s, "\tStatus_RD(0x%08x): 0x%08x\n",
+ MT_DMASHDL_STATUS_RD, val);
+ seq_printf(s, "\t\tfree page cnt=0x%03x, ffa cnt=0x%03x\n",
+ free_pg_cnt, ffa_cnt);
+
+ /* Counter mismatch check against PLE HIF page counters */
+ seq_puts(s, "\nDMASHDL Counter Check:\n");
+ val = mt76_rr(dev, MT_PLE_HIF_PG_INFO);
+ ple_rsv = FIELD_GET(MT_WF_PG_INFO_RSV_CNT, val);
+ ple_src = FIELD_GET(MT_WF_PG_INFO_SRC_CNT, val);
+ seq_printf(s, "\tPLE: used/rsv pages of HIF group=0x%03x/0x%03x\n",
+ ple_src, ple_rsv);
+ seq_printf(s, "\tDMASHDL: total used pages of group0~14=0x%03x\n",
+ total_src);
+ if (ple_src != total_src)
+ seq_puts(s, "\t[MISMATCH] PLE used pages != DMASHDL total used\n");
+ seq_printf(s, "\tDMASHDL: total rsv pages of group0~14=0x%03x\n",
+ total_rsv);
+ seq_printf(s, "\tDMASHDL: total ffa pages=0x%03x, total free pages=0x%03x\n",
+ ffa_cnt, free_pg_cnt);
+ if (free_pg_cnt != total_rsv + ffa_cnt)
+ seq_puts(s, "\t[MISMATCH] free pages != rsv + ffa\n");
+ if (free_pg_cnt != ple_rsv)
+ seq_puts(s, "\t[MISMATCH] free pages != PLE rsv pages\n");
+ if (ple_src == total_src && free_pg_cnt == total_rsv + ffa_cnt &&
+ free_pg_cnt == ple_rsv)
+ seq_puts(s, "\tDMASHDL: no counter mismatch\n");
+
+ mt792x_mutex_release(dev);
+ return 0;
+}
+
+/* mt7928_dmashdl_lite_read: DMASHDL Lite (MT7928 only, base 0x20026200).
+ * Unlike full DMASHDL, Lite splits runtime state into separate registers:
+ * GROUP_CONTROL (+0x100): min/max quota (config)
+ * GROUP_SRC_CNT (+0x200): pages sourced (in-use) per group (runtime)
+ * GROUP_ACK_CNT (+0x300): pages acknowledged (returned) per group (runtime)
+ * GROUP_VALID (+0x400): bitmap of groups with active page allocations
+ */
+static int
+mt7928_dmashdl_lite_read(struct seq_file *s, void *data)
+{
+ struct mt792x_dev *dev = dev_get_drvdata(s->private);
+ u32 disable0, disable1, valid0, valid1;
+ u32 total_src = 0, total_ack = 0;
+ u32 ple_rsv, ple_src;
+ u32 val;
+ int i;
+
+ if (!mt76_is_mmio(&dev->mt76)) {
+ seq_puts(s, "DMASHDL Lite dump not supported on this interface\n");
+ return 0;
+ }
+
+ mt792x_mutex_acquire(dev);
+
+ seq_puts(s, "DMASHDL Lite Info (MT7928):\n");
+
+ val = mt76_rr(dev, MT_DMASHDL_LITE_MAIN_CONTROL);
+ seq_printf(s, "\tMain control(0x%08x): 0x%08x\n",
+ MT_DMASHDL_LITE_MAIN_CONTROL, val);
+
+ val = mt76_rr(dev, MT_DMASHDL_LITE_PAGE_SIZE);
+ seq_printf(s, "\tPage size(0x%08x): 0x%08x\n",
+ MT_DMASHDL_LITE_PAGE_SIZE, val);
+ seq_printf(s, "\t\tPSE page size=0x%04x, PLE page size=0x%04x\n",
+ FIELD_GET(MT_DMASHDL_LITE_PSE_PAGE_SIZE_MASK, val),
+ FIELD_GET(MT_DMASHDL_LITE_PLE_PAGE_SIZE_MASK, val));
+
+ val = mt76_rr(dev, MT_DMASHDL_LITE_PKT_MAX_SIZE);
+ seq_printf(s, "\tPkt max size(0x%08x): 0x%08x\n",
+ MT_DMASHDL_LITE_PKT_MAX_SIZE, val);
+ seq_printf(s, "\t\tPSE pkt max=0x%04x, PLE pkt max=0x%04x\n",
+ FIELD_GET(MT_DMASHDL_LITE_PSE_PKT_MAX_SIZE_MASK, val),
+ FIELD_GET(MT_DMASHDL_LITE_PLE_PKT_MAX_SIZE_MASK, val));
+
+ val = mt76_rr(dev, MT_DMASHDL_LITE_GROUP_SN_CHK0);
+ seq_printf(s, "\tGroup SN check[31:0](0x%08x): 0x%08x\n",
+ MT_DMASHDL_LITE_GROUP_SN_CHK0, val);
+ val = mt76_rr(dev, MT_DMASHDL_LITE_GROUP_SN_CHK1);
+ seq_printf(s, "\tGroup SN check[63:32](0x%08x): 0x%08x\n",
+ MT_DMASHDL_LITE_GROUP_SN_CHK1, val);
+ val = mt76_rr(dev, MT_DMASHDL_LITE_GROUP_UDF_CHK0);
+ seq_printf(s, "\tGroup UDF check[31:0](0x%08x): 0x%08x\n",
+ MT_DMASHDL_LITE_GROUP_UDF_CHK0, val);
+ val = mt76_rr(dev, MT_DMASHDL_LITE_GROUP_UDF_CHK1);
+ seq_printf(s, "\tGroup UDF check[63:32](0x%08x): 0x%08x\n",
+ MT_DMASHDL_LITE_GROUP_UDF_CHK1, val);
+
+ disable0 = mt76_rr(dev, MT_DMASHDL_LITE_GROUP_DISABLE0);
+ seq_printf(s, "\tGroup disable[31:0](0x%08x): 0x%08x\n",
+ MT_DMASHDL_LITE_GROUP_DISABLE0, disable0);
+ disable1 = mt76_rr(dev, MT_DMASHDL_LITE_GROUP_DISABLE1);
+ seq_printf(s, "\tGroup disable[63:32](0x%08x): 0x%08x\n",
+ MT_DMASHDL_LITE_GROUP_DISABLE1, disable1);
+
+ valid0 = mt76_rr(dev, MT_DMASHDL_LITE_GROUP_VALID_0_31);
+ seq_printf(s, "\tGroup valid[31:0](0x%08x): 0x%08x\n",
+ MT_DMASHDL_LITE_GROUP_VALID_0_31, valid0);
+ valid1 = mt76_rr(dev, MT_DMASHDL_LITE_GROUP_VALID_32_63);
+ seq_printf(s, "\tGroup valid[63:32](0x%08x): 0x%08x\n",
+ MT_DMASHDL_LITE_GROUP_VALID_32_63, valid1);
+
+ seq_puts(s, "\tQ mapping (q2group):\n");
+ for (i = 0; i < 8; i++) {
+ val = mt76_rr(dev, MT_DMASHDL_LITE_Q_MAP(i));
+ seq_printf(s, "\t\tQ_MAP[%d](0x%08x): 0x%08x\n",
+ i, MT_DMASHDL_LITE_Q_MAP(i), val);
+ }
+
+ seq_puts(s, "\tGroup status (skipping disabled+zero quota):\n");
+ for (i = 0; i < DMASHDL_LITE_GROUP_NUM; i++) {
+ bool enabled = !((i < 32 ? disable0 : disable1) >> (i % 32) & 1);
+ u32 quota, src, ack_reg;
+ u32 pktin, add_ret, ret;
+
+ quota = mt76_rr(dev, MT_DMASHDL_LITE_GROUP_QUOTA(i));
+ if (!enabled && !quota)
+ continue;
+
+ src = FIELD_GET(MT_DMASHDL_LITE_GROUP_SRC_CNT_MASK,
+ mt76_rr(dev, MT_DMASHDL_LITE_GROUP_SRC_CNT(i)));
+ ack_reg = mt76_rr(dev, MT_DMASHDL_LITE_GROUP_ACK_CNT(i));
+ pktin = FIELD_GET(MT_DMASHDL_LITE_ACK_PKTIN_CNT, ack_reg);
+ add_ret = FIELD_GET(MT_DMASHDL_LITE_ACK_ADD_RET_CNT, ack_reg);
+ ret = FIELD_GET(MT_DMASHDL_LITE_ACK_RET_CNT, ack_reg);
+
+ seq_printf(s, "\tGroup %2d: quota(min/max)=0x%03x/0x%03x, src=0x%02x, ack(add_ret/pktin/ret)=0x%02x/0x%02x/0x%02x%s\n",
+ i,
+ FIELD_GET(MT_DMASHDL_LITE_GROUP_MIN_QUOTA_MASK, quota),
+ FIELD_GET(MT_DMASHDL_LITE_GROUP_MAX_QUOTA_MASK, quota),
+ src, add_ret, pktin, ret,
+ enabled ? "" : " [disabled]");
+ if (enabled) {
+ total_src += src;
+ total_ack += ret;
+ }
+ }
+ seq_printf(s, "\tTotal (enabled groups): src=0x%03x, ack_ret=0x%03x\n",
+ total_src, total_ack);
+
+ /* Counter mismatch check against PLE HIF page counters */
+ seq_puts(s, "\nDMASHDL Lite Counter Check:\n");
+ val = mt76_rr(dev, MT_PLE_HIF_PG_INFO);
+ ple_rsv = FIELD_GET(MT_WF_PG_INFO_RSV_CNT, val);
+ ple_src = FIELD_GET(MT_WF_PG_INFO_SRC_CNT, val);
+ seq_printf(s, "\tPLE: used/rsv pages of HIF group=0x%03x/0x%03x\n",
+ ple_src, ple_rsv);
+ seq_printf(s, "\tDMASHDL Lite: total src=0x%03x, total ack_ret=0x%03x\n",
+ total_src, total_ack);
+ if (ple_src != total_src)
+ seq_puts(s, "\t[MISMATCH] PLE used pages != DMASHDL Lite total src\n");
+ else
+ seq_puts(s, "\tDMASHDL Lite: no counter mismatch\n");
+
+ seq_puts(s, "\nPLE Page Counters:\n");
+
+ val = mt76_rr(dev, MT_PLE_FREEPG_CNT);
+ seq_printf(s, "\tPLE free pages(0x%08x): total=0x%03x, ffa=0x%03x\n",
+ MT_PLE_FREEPG_CNT,
+ FIELD_GET(MT_WF_FREEPG_FREE_CNT, val),
+ FIELD_GET(MT_WF_FREEPG_FFA_CNT, val));
+
+ val = mt76_rr(dev, MT_PLE_HIF_WMTXD_PG_INFO);
+ seq_printf(s, "\tPLE WMTXD group(0x%08x): used/rsv=0x%03x/0x%03x\n",
+ MT_PLE_HIF_WMTXD_PG_INFO,
+ FIELD_GET(MT_WF_PG_INFO_SRC_CNT, val),
+ FIELD_GET(MT_WF_PG_INFO_RSV_CNT, val));
+
+ val = mt76_rr(dev, MT_PLE_HIF_TXCMD_PG_INFO);
+ seq_printf(s, "\tPLE HIF_TXCMD group(0x%08x): used/rsv=0x%03x/0x%03x\n",
+ MT_PLE_HIF_TXCMD_PG_INFO,
+ FIELD_GET(MT_WF_PG_INFO_SRC_CNT, val),
+ FIELD_GET(MT_WF_PG_INFO_RSV_CNT, val));
+
+ val = mt76_rr(dev, MT_PLE_CPU_PG_INFO);
+ seq_printf(s, "\tPLE CPU group(0x%08x): used/rsv=0x%03x/0x%03x\n",
+ MT_PLE_CPU_PG_INFO,
+ FIELD_GET(MT_WF_PG_INFO_SRC_CNT, val),
+ FIELD_GET(MT_WF_PG_INFO_RSV_CNT, val));
+
+ mt792x_mutex_release(dev);
+ return 0;
+}
+
+static int
+mt7925_ple_read(struct seq_file *s, void *data)
+{
+ struct mt792x_dev *dev = dev_get_drvdata(s->private);
+ u32 pbuf_ctrl, val;
+ u32 fpg_cnt, ffa_cnt, fpg_head, fpg_tail;
+ u32 max_q, min_q, rsv_pg, used_pg;
+
+ if (!mt76_is_mmio(&dev->mt76)) {
+ seq_puts(s, "PLE dump not supported on this interface\n");
+ return 0;
+ }
+
+ mt792x_mutex_acquire(dev);
+
+ pbuf_ctrl = mt76_rr(dev, MT_PLE_PBUF_CTRL);
+ seq_puts(s, "PLE Configuration Info:\n");
+ seq_printf(s, "\tPacket Buffer Control(0x%08x): 0x%08x\n",
+ MT_PLE_PBUF_CTRL, pbuf_ctrl);
+ seq_printf(s, "\t\tPage Size=%d(%d bytes/page), Offset=%d, Total=%d pages\n",
+ (int)FIELD_GET(MT_WF_PBUF_PAGE_SIZE, pbuf_ctrl),
+ FIELD_GET(MT_WF_PBUF_PAGE_SIZE, pbuf_ctrl) ? 128 : 64,
+ (int)FIELD_GET(MT_WF_PBUF_PBUF_OFFSET, pbuf_ctrl),
+ (int)FIELD_GET(MT_WF_PBUF_TOTAL_PAGES, pbuf_ctrl));
+
+ val = mt76_rr(dev, MT_PLE_INT_N9_ERR_STS);
+ seq_printf(s, "\tINT_N9_ERR_STS(0x%08x): 0x%08x\n",
+ MT_PLE_INT_N9_ERR_STS, val);
+ val = mt76_rr(dev, MT_PLE_INT_N9_ERR_STS_1);
+ seq_printf(s, "\tINT_N9_ERR_STS_1(0x%08x): 0x%08x\n",
+ MT_PLE_INT_N9_ERR_STS_1, val);
+
+ seq_puts(s, "PLE Page Flow Control:\n");
+
+ val = mt76_rr(dev, MT_PLE_FREEPG_CNT);
+ fpg_cnt = FIELD_GET(MT_WF_FREEPG_FREE_CNT, val);
+ ffa_cnt = FIELD_GET(MT_WF_FREEPG_FFA_CNT, val);
+ seq_printf(s, "\tFree page counter(0x%08x): 0x%08x\n",
+ MT_PLE_FREEPG_CNT, val);
+ seq_printf(s, "\t\ttotal free=0x%03x, ffa=0x%03x\n", fpg_cnt, ffa_cnt);
+
+ val = mt76_rr(dev, MT_PLE_FREEPG_HEAD_TAIL);
+ fpg_head = FIELD_GET(MT_WF_FREEPG_HEAD, val);
+ fpg_tail = FIELD_GET(MT_WF_FREEPG_TAIL, val);
+ seq_printf(s, "\tFree page head/tail(0x%08x): 0x%08x\n",
+ MT_PLE_FREEPG_HEAD_TAIL, val);
+ seq_printf(s, "\t\ttail/head=0x%03x/0x%03x\n", fpg_tail, fpg_head);
+
+ /* HIF group */
+ val = mt76_rr(dev, MT_PLE_PG_HIF_GROUP);
+ max_q = FIELD_GET(MT_WF_GROUP_QUOTA_MAX, val);
+ min_q = FIELD_GET(MT_WF_GROUP_QUOTA_MIN, val);
+ seq_printf(s, "\tHIF group quota(0x%08x): 0x%08x (max/min=0x%03x/0x%03x)\n",
+ MT_PLE_PG_HIF_GROUP, val, max_q, min_q);
+ val = mt76_rr(dev, MT_PLE_HIF_PG_INFO);
+ used_pg = FIELD_GET(MT_WF_PG_INFO_SRC_CNT, val);
+ rsv_pg = FIELD_GET(MT_WF_PG_INFO_RSV_CNT, val);
+ seq_printf(s, "\tHIF group status(0x%08x): 0x%08x (used/rsv=0x%03x/0x%03x)\n",
+ MT_PLE_HIF_PG_INFO, val, used_pg, rsv_pg);
+
+ /* WMTXD group */
+ val = mt76_rr(dev, MT_PLE_PG_HIF_WMTXD_GROUP);
+ max_q = FIELD_GET(MT_WF_GROUP_QUOTA_MAX, val);
+ min_q = FIELD_GET(MT_WF_GROUP_QUOTA_MIN, val);
+ seq_printf(s, "\tWMTXD group quota(0x%08x): 0x%08x (max/min=0x%03x/0x%03x)\n",
+ MT_PLE_PG_HIF_WMTXD_GROUP, val, max_q, min_q);
+ val = mt76_rr(dev, MT_PLE_HIF_WMTXD_PG_INFO);
+ used_pg = FIELD_GET(MT_WF_PG_INFO_SRC_CNT, val);
+ rsv_pg = FIELD_GET(MT_WF_PG_INFO_RSV_CNT, val);
+ seq_printf(s, "\tWMTXD group status(0x%08x): 0x%08x (used/rsv=0x%03x/0x%03x)\n",
+ MT_PLE_HIF_WMTXD_PG_INFO, val, used_pg, rsv_pg);
+
+ /* HIF_TXCMD group */
+ val = mt76_rr(dev, MT_PLE_PG_HIF_TXCMD_GROUP);
+ max_q = FIELD_GET(MT_WF_GROUP_QUOTA_MAX, val);
+ min_q = FIELD_GET(MT_WF_GROUP_QUOTA_MIN, val);
+ seq_printf(s, "\tHIF_TXCMD group quota(0x%08x): 0x%08x (max/min=0x%03x/0x%03x)\n",
+ MT_PLE_PG_HIF_TXCMD_GROUP, val, max_q, min_q);
+ val = mt76_rr(dev, MT_PLE_HIF_TXCMD_PG_INFO);
+ used_pg = FIELD_GET(MT_WF_PG_INFO_SRC_CNT, val);
+ rsv_pg = FIELD_GET(MT_WF_PG_INFO_RSV_CNT, val);
+ seq_printf(s, "\tHIF_TXCMD group status(0x%08x): 0x%08x (used/rsv=0x%03x/0x%03x)\n",
+ MT_PLE_HIF_TXCMD_PG_INFO, val, used_pg, rsv_pg);
+
+ /* CPU group */
+ val = mt76_rr(dev, MT_PLE_PG_CPU_GROUP);
+ max_q = FIELD_GET(MT_WF_GROUP_QUOTA_MAX, val);
+ min_q = FIELD_GET(MT_WF_GROUP_QUOTA_MIN, val);
+ seq_printf(s, "\tCPU group quota(0x%08x): 0x%08x (max/min=0x%03x/0x%03x)\n",
+ MT_PLE_PG_CPU_GROUP, val, max_q, min_q);
+ val = mt76_rr(dev, MT_PLE_CPU_PG_INFO);
+ used_pg = FIELD_GET(MT_WF_PG_INFO_SRC_CNT, val);
+ rsv_pg = FIELD_GET(MT_WF_PG_INFO_RSV_CNT, val);
+ seq_printf(s, "\tCPU group status(0x%08x): 0x%08x (used/rsv=0x%03x/0x%03x)\n",
+ MT_PLE_CPU_PG_INFO, val, used_pg, rsv_pg);
+
+ val = mt76_rr(dev, MT_PLE_QUEUE_EMPTY);
+ seq_printf(s, "\tQueue empty(0x%08x): 0x%08x%s\n",
+ MT_PLE_QUEUE_EMPTY, val,
+ (val & MT_PLE_QUEUE_EMPTY_ALL_AC) ? " [ALL AC EMPTY]" : "");
+
+ mt792x_mutex_release(dev);
+ return 0;
+}
+
+static int
+mt7925_pse_read(struct seq_file *s, void *data)
+{
+ struct mt792x_dev *dev = dev_get_drvdata(s->private);
+ u32 pbuf_ctrl, val;
+ u32 fpg_cnt, ffa_cnt, fpg_head, fpg_tail;
+ u32 max_q, min_q, rsv_pg, used_pg;
+
+ if (!mt76_is_mmio(&dev->mt76)) {
+ seq_puts(s, "PSE dump not supported on this interface\n");
+ return 0;
+ }
+
+ mt792x_mutex_acquire(dev);
+
+ pbuf_ctrl = mt76_rr(dev, MT_PSE_PBUF_CTRL);
+ seq_puts(s, "PSE Configuration Info:\n");
+ seq_printf(s, "\tPacket Buffer Control(0x%08x): 0x%08x\n",
+ MT_PSE_PBUF_CTRL, pbuf_ctrl);
+ seq_printf(s, "\t\tPage Size=%d(%d bytes/page), Offset=%d, Total=%d pages\n",
+ (int)FIELD_GET(MT_WF_PBUF_PAGE_SIZE, pbuf_ctrl),
+ FIELD_GET(MT_WF_PBUF_PAGE_SIZE, pbuf_ctrl) ? 256 : 128,
+ (int)FIELD_GET(MT_WF_PBUF_PBUF_OFFSET, pbuf_ctrl),
+ (int)FIELD_GET(MT_WF_PBUF_TOTAL_PAGES, pbuf_ctrl));
+
+ val = mt76_rr(dev, MT_PSE_INT_N9_ERR_STS);
+ seq_printf(s, "\tINT_N9_ERR_STS(0x%08x): 0x%08x\n",
+ MT_PSE_INT_N9_ERR_STS, val);
+ val = mt76_rr(dev, MT_PSE_INT_N9_ERR1_STS);
+ seq_printf(s, "\tINT_N9_ERR1_STS(0x%08x): 0x%08x\n",
+ MT_PSE_INT_N9_ERR1_STS, val);
+
+ val = mt76_rr(dev, MT_PSE_QUEUE_EMPTY);
+ seq_printf(s, "\tQueue empty(0x%08x): 0x%08x\n",
+ MT_PSE_QUEUE_EMPTY, val);
+
+ seq_puts(s, "PSE Page Flow Control:\n");
+
+ val = mt76_rr(dev, MT_PSE_FREEPG_CNT);
+ fpg_cnt = FIELD_GET(MT_WF_FREEPG_FREE_CNT, val);
+ ffa_cnt = FIELD_GET(MT_WF_FREEPG_FFA_CNT, val);
+ seq_printf(s, "\tFree page counter(0x%08x): 0x%08x\n",
+ MT_PSE_FREEPG_CNT, val);
+ seq_printf(s, "\t\ttotal free=0x%03x, ffa=0x%03x\n", fpg_cnt, ffa_cnt);
+
+ val = mt76_rr(dev, MT_PSE_FREEPG_HEAD_TAIL);
+ fpg_head = FIELD_GET(MT_WF_FREEPG_HEAD, val);
+ fpg_tail = FIELD_GET(MT_WF_FREEPG_TAIL, val);
+ seq_printf(s, "\tFree page head/tail(0x%08x): 0x%08x\n",
+ MT_PSE_FREEPG_HEAD_TAIL, val);
+ seq_printf(s, "\t\ttail/head=0x%03x/0x%03x\n", fpg_tail, fpg_head);
+
+#define MT7925_PSE_GROUP(_name, _qreg, _ireg) \
+do { \
+ val = mt76_rr(dev, _qreg); \
+ max_q = FIELD_GET(MT_WF_GROUP_QUOTA_MAX, val); \
+ min_q = FIELD_GET(MT_WF_GROUP_QUOTA_MIN, val); \
+ seq_printf(s, "\t" _name " group quota(0x%08x): 0x%08x" \
+ " (max/min=0x%03x/0x%03x)\n", _qreg, val, max_q, min_q); \
+ val = mt76_rr(dev, _ireg); \
+ used_pg = FIELD_GET(MT_WF_PG_INFO_SRC_CNT, val); \
+ rsv_pg = FIELD_GET(MT_WF_PG_INFO_RSV_CNT, val); \
+ seq_printf(s, "\t" _name " group status(0x%08x): 0x%08x" \
+ " (used/rsv=0x%03x/0x%03x)\n", _ireg, val, used_pg, rsv_pg);\
+} while (0)
+
+ MT7925_PSE_GROUP("HIF0", MT_PSE_PG_HIF0_GROUP, MT_PSE_HIF0_PG_INFO);
+ MT7925_PSE_GROUP("HIF1", MT_PSE_PG_HIF1_GROUP, MT_PSE_HIF1_PG_INFO);
+ MT7925_PSE_GROUP("HIF2", MT_PSE_PG_HIF2_GROUP, MT_PSE_HIF2_PG_INFO);
+ MT7925_PSE_GROUP("CPU", MT_PSE_PG_CPU_GROUP, MT_PSE_CPU_PG_INFO);
+ MT7925_PSE_GROUP("PLE", MT_PSE_PG_PLE_GROUP, MT_PSE_PLE_PG_INFO);
+ MT7925_PSE_GROUP("LMAC0", MT_PSE_PG_LMAC0_GROUP, MT_PSE_LMAC0_PG_INFO);
+ MT7925_PSE_GROUP("LMAC1", MT_PSE_PG_LMAC1_GROUP, MT_PSE_LMAC1_PG_INFO);
+ MT7925_PSE_GROUP("LMAC2", MT_PSE_PG_LMAC2_GROUP, MT_PSE_LMAC2_PG_INFO);
+ MT7925_PSE_GROUP("LMAC3", MT_PSE_PG_LMAC3_GROUP, MT_PSE_LMAC3_PG_INFO);
+ MT7925_PSE_GROUP("MDP", MT_PSE_PG_MDP_GROUP, MT_PSE_MDP_PG_INFO);
+
+#undef MT7925_PSE_GROUP
+
+ mt792x_mutex_release(dev);
+ return 0;
+}
+
+static const u32 stats_query_mib_counters[] = {
+ UNI_CMD_MIB_CNT_RX_FCS_ERR,
+ UNI_CMD_MIB_CNT_RX_FIFO_OVERFLOW,
+ UNI_CMD_MIB_CNT_RX_MPDU,
+ UNI_CMD_MIB_CNT_AMPDU_RX_COUNT,
+ UNI_CMD_MIB_CNT_PF_DROP,
+ UNI_CMD_MIB_CNT_LEN_MISMATCH,
+ UNI_CMD_MIB_CNT_CHANNEL_IDLE,
+ UNI_CMD_MIB_CNT_CCA_NAV_TX_TIME,
+ UNI_CMD_MIB_CNT_MDRDY,
+ UNI_CMD_MIB_CNT_RX_CCK_MDRDY_TIME,
+ UNI_CMD_MIB_CNT_RX_OFDM_LG_MIXED_MDRDY_TIME,
+ UNI_CMD_MIB_CNT_RX_OFDM_GREEN_MDRDY_TIME,
+ UNI_CMD_MIB_CNT_P_CCA_TIME,
+ UNI_CMD_MIB_CNT_S_CCA_TIME,
+ UNI_CMD_MIB_CNT_P_ED_TIME,
+ UNI_CMD_MIB_CNT_BCN_TX,
+ UNI_CMD_MIB_CNT_TX_BW_40MHZ,
+ UNI_CMD_MIB_CNT_TX_BW_80MHZ,
+ UNI_CMD_MIB_CNT_TX_BW_160MHZ,
+ UNI_CMD_MIB_CNT_BSS0_BA_MISS,
+ UNI_CMD_MIB_CNT_BSS0_RTS_TX_CNT,
+ UNI_CMD_MIB_CNT_BSS0_FRAME_RETRY,
+ UNI_CMD_MIB_CNT_BSS0_FRAME_RETRY_2,
+ UNI_CMD_MIB_CNT_BSS0_RTS_RETRY,
+ UNI_CMD_MIB_CNT_BSS0_ACK_FAIL,
+ UNI_CMD_MIB_CNT_AMPDU,
+ UNI_CMD_MIB_CNT_AMPDU_MPDU,
+ UNI_CMD_MIB_CNT_AMPDU_ACKED,
+};
+
+/* Dump the whole firmware auto-rate table and mark the entry currently in use
+ * with "-->"
+ */
+static void
+mt7925_dump_auto_rate_table(struct seq_file *s,
+ const struct mt7925_wtbl_rate *wr)
+{
+ u8 fcap = wr->fcap;
+ u8 rate_idx = wr->rate_idx;
+ u8 cbrn = wr->cbrn;
+ int i;
+
+ seq_printf(s, "%-22s Cur Rate Index = %u\n", " ", rate_idx);
+
+ for (i = 0; i < MT7925_AUTO_RATE_NUM; i++) {
+ u16 rate_code = le16_to_cpu(wr->rate_code[i]);
+ u8 txmode = MT7925_HW_RATE_TO_MODE(rate_code);
+ u8 rate = MT7925_HW_RATE_TO_MCS(rate_code);
+ u8 nsts = MT7925_HW_RATE_TO_NSS(rate_code) + 1;
+ u8 stbc = MT7925_HW_RATE_TO_STBC(rate_code);
+ u8 dcm = MT7925_HW_RATE_TO_DCM(rate_code);
+ u8 ersu106t = MT7925_HW_RATE_TO_106T(rate_code);
+ u8 sgi = mt7925_wtbl_get_sgi(wr, txmode, fcap);
+ u8 ldpc = mt7925_wtbl_get_ldpc(wr, txmode);
+ const char *mode_str = "N/A";
+
+ if (dcm)
+ rate = MT7925_HW_RATE_UNMASK_DCM(rate);
+ if (ersu106t)
+ rate = MT7925_HW_RATE_UNMASK_106T(rate);
+
+ if (txmode < ARRAY_SIZE(mt7925_tx_mode_str))
+ mode_str = mt7925_tx_mode_str[txmode];
+
+ seq_printf(s, "Rate index[%d] %s", i,
+ rate_idx == i ? "--> " : " ");
+
+ /* rate/MCS field */
+ if (txmode == MT7925_TX_RATE_MODE_CCK)
+ seq_printf(s, "%s, ",
+ mt7925_tx_rate_cck_str[rate & 0x3]);
+ else if (txmode == MT7925_TX_RATE_MODE_OFDM)
+ seq_printf(s, "%s, ", mt7925_hw_rate_ofdm_str(rate));
+ else if (txmode == MT7925_TX_RATE_MODE_HTMIX ||
+ txmode == MT7925_TX_RATE_MODE_HTGF)
+ seq_printf(s, "MCS%d, ", rate);
+ else
+ seq_printf(s, "%s%d_MCS%d, ",
+ stbc ? "NSTS" : "NSS", nsts, rate);
+
+ /* bandwidth: mirror the CCK/OFDM -> BW20 and cbrn handling */
+ if (txmode == MT7925_TX_RATE_MODE_CCK ||
+ txmode == MT7925_TX_RATE_MODE_OFDM)
+ seq_printf(s, "%s, ", mt7925_tx_rate_bw_str[0]);
+ else if (i > cbrn)
+ seq_printf(s, "%s, ",
+ fcap < 5 ?
+ (fcap > MT7925_BW_20 ?
+ mt7925_tx_rate_bw_str[fcap - 1] :
+ mt7925_tx_rate_bw_str[fcap]) :
+ mt7925_tx_rate_bw_str[5]);
+ else
+ seq_printf(s, "%s, ",
+ fcap < 5 ? mt7925_tx_rate_bw_str[fcap] :
+ mt7925_tx_rate_bw_str[5]);
+
+ /* GI / preamble */
+ if (txmode == MT7925_TX_RATE_MODE_CCK)
+ seq_printf(s, "%s, ", rate < 4 ? "LP" : "SP");
+ else if (txmode == MT7925_TX_RATE_MODE_OFDM)
+ ; /* OFDM has no GI/preamble to print */
+ else if (txmode == MT7925_TX_RATE_MODE_HTMIX ||
+ txmode == MT7925_TX_RATE_MODE_HTGF ||
+ txmode == MT7925_TX_RATE_MODE_VHT ||
+ txmode == MT7925_TX_RATE_MODE_PLR)
+ seq_printf(s, "%s, ", sgi == 0 ? "LGI" : "SGI");
+ else
+ seq_printf(s, "%s, ",
+ sgi == 0 ? "SGI" :
+ (sgi == 1 ? "MGI" : "LGI"));
+
+ /* mode, DCM, 106t, STBC, coding */
+ seq_printf(s, "%s%s%s%s%s\n",
+ mode_str,
+ dcm ? ", DCM" : "", ersu106t ? ", 106t" : "",
+ stbc ? ", STBC, " : ", ",
+ (ldpc == 0 ||
+ txmode == MT7925_TX_RATE_MODE_CCK ||
+ txmode == MT7925_TX_RATE_MODE_OFDM) ?
+ "BCC" : "LDPC");
+ }
+}
+
+static int mt792x_stats(struct seq_file *s, void *data)
+{
+ s8 rssi = 0;
+ u8 band_idx = 0;
+ bool wtbl_rate_valid = false;
+ int ret = 0;
+ int i = 0;
+ u32 stats_value = 0;
+ u32 tx_link_speed_kbps = 0;
+ u64 tx_total = 0;
+ u64 tx_fail = 0;
+ u64 tx_per = 0;
+ /* UNI_CMD_MIB_DATA */
+ u64 mib_values[ARRAY_SIZE(stats_query_mib_counters)];
+ struct mt792x_dev *dev = dev_get_drvdata(s->private);
+ struct mt7925_wtbl_rate wtbl_rate;
+ struct mt7925_sta_stats *stats = NULL;
+
+ band_idx = dev->mphy.band_idx;
+
+ stats = kzalloc(sizeof(*stats), GFP_KERNEL);
+ if (!stats)
+ return -ENOMEM;
+
+ /* Get STA_STATS */
+ mt792x_mutex_acquire(dev);
+ ret = mt792x_mcu_get_stat(dev, stats);
+ mt792x_mutex_release(dev);
+ if (ret)
+ goto out;
+
+ /* Get MIB_STATS */
+ mt792x_mutex_acquire(dev);
+ ret = mt7925_mcu_get_mib_info(dev, band_idx, stats_query_mib_counters,
+ ARRAY_SIZE(stats_query_mib_counters), mib_values);
+ mt792x_mutex_release(dev);
+ if (ret)
+ goto out;
+
+ /* Get Link_Speed and RSSI */
+ mt792x_mutex_acquire(dev);
+ ret = mt7925_mcu_get_link_quality(dev, stats->bss_idx, &rssi, &tx_link_speed_kbps);
+ mt792x_mutex_release(dev);
+ if (ret)
+ goto out;
+
+ /* Get the firmware auto-rate table (best effort; do not abort the
+ * whole stats dump if the WTBL query is not available).
+ */
+ mt792x_mutex_acquire(dev);
+ if (!mt792x_mcu_get_wtbl_rate(dev, stats->wtbl_idx, &wtbl_rate))
+ wtbl_rate_valid = true;
+ mt792x_mutex_release(dev);
+
+ /* Print STA_STATS info */
+ seq_printf(s, "(STA) connected AP MAC Address = %pM\n", stats->mac_addr);
+ seq_printf(s, "%-22s BssIdx = [%u]\n", " ", stats->bss_idx);
+ seq_printf(s, "%-22s StaRecIdx = [%u]\n",
+ " ", stats->sta_idx);
+ seq_printf(s, "%-22s RSSI = %d\n", " ", rssi);
+ seq_printf(s, "%-22s Link_Speed = %u (kbit/s)\n", " ", tx_link_speed_kbps);
+
+ seq_printf(s, "%-22s temperature = %u\n",
+ " ", stats->temperature);
+
+ stats_value = le32_to_cpu(stats->transmit_count) - le32_to_cpu(stats->transmit_fail_count);
+ seq_printf(s, "%-22s Tx success = %u\n",
+ " ", stats_value);
+
+ stats_value = le32_to_cpu(stats->tx_fail_count) - le32_to_cpu(stats->tx_life_timeout_count);
+ seq_printf(s, "%-22s Tx fail to Rcv ACK after retry = %u\n",
+ " ", stats_value);
+
+ seq_printf(s, "%-22s Rx Mpdu = %u\n",
+ " ", le32_to_cpu(stats->mib[band_idx].rx_mpdu_cnt));
+
+ seq_printf(s, "%-22s Rx Fcs Error = %u (MPDU)\n",
+ " ", le32_to_cpu(stats->mib[band_idx].fcs_error));
+ seq_printf(s, "%-22s Rx FIFO full = %u (MPDU)\n",
+ " ", le32_to_cpu(stats->mib[band_idx].rx_fifo_full));
+
+ for (i = 0; i < STAT_MIB_CNT_TRX_AGG_RANGE_MAX_NUM; i++)
+ seq_printf(s, "%-22s TRX_AGG_RANGE[%d] = %u (PPDU)\n",
+ " ", i, le32_to_cpu(stats->mib[band_idx].tx_range_ampdu_cnt[i]));
+
+ seq_printf(s, "%-22s MPDUs in AMPDUs transmitted = %u (MPDU)\n",
+ " ", le32_to_cpu(stats->mib[band_idx].ampdu_tx_sf_cnt));
+ seq_printf(s, "%-22s MPDUs in AMPDUs transmitted with ACK reply = %u (MPDU)\n",
+ " ", le32_to_cpu(stats->mib[band_idx].ampdu_tx_ack_sf_cnt));
+ seq_printf(s, "%-22s rate1_tx_cnt = %u\n",
+ " ", le32_to_cpu(stats->rate1_tx_cnt));
+ seq_printf(s, "%-22s rate1_fail_cnt = %u\n",
+ " ", le32_to_cpu(stats->rate1_fail_cnt));
+ seq_printf(s, "%-22s train_up = %u\n",
+ " ", le16_to_cpu(stats->train_up));
+ seq_printf(s, "%-22s train_down = %u\n",
+ " ", le16_to_cpu(stats->train_down));
+ seq_printf(s, "%-22s is_force_tx_stream = %u\n",
+ " ", stats->is_force_tx_stream);
+ seq_printf(s, "%-22s is_force_se_off = %u\n",
+ " ", stats->is_force_se_off);
+ seq_printf(s, "%-22s max_ampdu_factor = %u\n",
+ " ", stats->max_ampdu_factor);
+ seq_printf(s, "%-22s tx_rate_up_penalty = %u\n",
+ " ", stats->tx_rate_up_penalty);
+ seq_printf(s, "%-22s low_traffic_mode = %u\n",
+ " ", stats->low_traffic_mode);
+ seq_printf(s, "%-22s low_traffic_count = %u\n",
+ " ", stats->low_traffic_count);
+ seq_printf(s, "%-22s low_traffic_dashboard = %u\n",
+ " ", stats->low_traffic_dashboard);
+ seq_printf(s, "%-22s dynamic_sgi_state = %u\n",
+ " ", stats->dynamic_sgi_state);
+ seq_printf(s, "%-22s dynamic_sgi_score = %u\n",
+ " ", stats->dynamic_sgi_score);
+ seq_printf(s, "%-22s dynamic_bw_state = %u\n",
+ " ", stats->dynamic_bw_state);
+ seq_printf(s, "%-22s dynamic_gband_256qam_state = %u\n",
+ " ", stats->dynamic_gband_256qam_state);
+ seq_printf(s, "%-22s vht_non_sp_rate_state = %u\n",
+ " ", stats->vht_non_sp_rate_state);
+
+ /* Decode the TX Vector BBP latch to show the current TX MCS rate */
+ if (band_idx < MT7925_STA_STATS_BAND_NUM)
+ mt7925_print_last_tx_rate(s, stats->tx_vector[band_idx].txv);
+
+ /* Dump the whole firmware auto-rate table and mark the entry currently */
+ if (wtbl_rate_valid)
+ mt7925_dump_auto_rate_table(s, &wtbl_rate);
+
+ seq_puts(s, "\nmib state:\n");
+ /* ===Rx Related Counters=== */
+ seq_puts(s, "=== Rx Related Counters ===\n");
+ seq_printf(s, "%-22s Rx with CRC = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_RX_FCS_ERR]);
+ seq_printf(s, "%-22s Rx drop due to out of resource = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_RX_FIFO_OVERFLOW]);
+ seq_printf(s, "%-22s Rx Mpdu = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_RX_MPDU]);
+ seq_printf(s, "%-22s Rx AMpdu = %llu (PPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_AMPDU_RX_COUNT]);
+ seq_printf(s, "%-22s Rx PF Drop = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_PF_DROP]);
+ seq_printf(s, "%-22s Rx Len Mismatch = %llu (PPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_LEN_MISMATCH]);
+
+ /* ===Phy/Timing Related Counters=== */
+ seq_puts(s, "\n=== Phy/Timing Related Counters ===\n");
+ seq_printf(s, "%-22s ChannelIdleCnt = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_CHANNEL_IDLE]);
+ seq_printf(s, "%-22s CCA_NAV_Tx_Time = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_CCA_NAV_TX_TIME]);
+ seq_printf(s, "%-22s Rx_MDRDY_CNT = %llu (PPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_MDRDY]);
+ seq_printf(s, "%-22s CCK_MDRDY = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_RX_CCK_MDRDY_TIME]);
+ seq_printf(s, "%-22s OFDM_MDRDY = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_RX_OFDM_LG_MIXED_MDRDY_TIME]);
+ seq_printf(s, "%-22s OFDM_GREEN_MDRDY = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_RX_OFDM_GREEN_MDRDY_TIME]);
+ seq_printf(s, "%-22s Prim CCA Time = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_P_CCA_TIME]);
+ seq_printf(s, "%-22s Sec CCA Time = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_S_CCA_TIME]);
+ seq_printf(s, "%-22s Prim ED Time = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_P_ED_TIME]);
+
+ /* ===Tx Related Counters(Generic)=== */
+ seq_puts(s, "\n=== Tx Related Counters(Generic) ===\n");
+ seq_printf(s, "%-22s BeaconTxCnt = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_BCN_TX]);
+ seq_printf(s, "%-22s Tx 40MHz Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_TX_BW_40MHZ]);
+ seq_printf(s, "%-22s Tx 80MHz Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_TX_BW_80MHZ]);
+ seq_printf(s, "%-22s Tx 160MHz Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_TX_BW_160MHZ]);
+
+ /* ===BSSID[0] Related Counters=== */
+ seq_puts(s, "\n=== BSSID[0] Related Counters ===\n");
+ seq_printf(s, "%-22s BA Miss Cnt = %llu (PPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_BSS0_BA_MISS]);
+ seq_printf(s, "%-22s RTS Tx Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_BSS0_RTS_TX_CNT]);
+ seq_printf(s, "%-22s Frame Retry Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_BSS0_FRAME_RETRY]);
+ seq_printf(s, "%-22s Frame Retry 2 Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_BSS0_FRAME_RETRY_2]);
+ seq_printf(s, "%-22s RTS Retry Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_BSS0_RTS_RETRY]);
+ seq_printf(s, "%-22s Ack Failed Cnt = %llu (PPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_BSS0_ACK_FAIL]);
+
+ /* ===AMPDU Related Counters=== */
+ seq_puts(s, "\n=== AMPDU Related Counters ===\n");
+ seq_printf(s, "%-22s Tx AMPDU_Pkt_Cnt = %llu (PPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_AMPDU]);
+ seq_printf(s, "%-22s Tx AMPDU_MPDU_Pkt_Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_AMPDU_MPDU]);
+ seq_printf(s, "%-22s AMPDU Tx success = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_AMPDU_ACKED]);
+
+ tx_total = mib_values[QUERY_MIB_CNT_AMPDU_MPDU];
+ tx_fail = mib_values[QUERY_MIB_CNT_AMPDU_MPDU] - mib_values[QUERY_MIB_CNT_AMPDU_ACKED];
+ tx_per = tx_total == 0 ? 0 : (1000 * (tx_fail)) / tx_total;
+ seq_printf(s, "%-22s AMPDU Tx fail count = %llu (MPDU), PER=%llu.%1llu%%\n",
+ " ",
+ tx_fail,
+ tx_per / 10, tx_per % 10);
+
+out:
+ kfree(stats);
+ return ret;
+}
+
int mt7925_init_debugfs(struct mt792x_dev *dev)
{
struct dentry *dir;
@@ -313,5 +1365,7 @@ int mt7925_init_debugfs(struct mt792x_dev *dev)
mt792x_pm_stats);
debugfs_create_file("deep-sleep", 0600, dir, dev, &fops_ds);
+ debugfs_create_devm_seqfile(dev->mt76.dev, "stats", dir,
+ mt792x_stats);
return 0;
}
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/stats.c b/drivers/net/wireless/mediatek/mt76/mt7925/stats.c
new file mode 100644
index 000000000000..9a47953062bc
--- /dev/null
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/stats.c
@@ -0,0 +1,585 @@
+// SPDX-License-Identifier: BSD-3-Clause-Clear
+/* Copyright (C) 2026 MediaTek Inc. */
+
+#include <linux/firmware.h>
+#include <linux/fs.h>
+#include "mt7925.h"
+#include "mcu.h"
+#include "mac.h"
+#include "stats.h"
+
+#define MT7925_STA_STATS_VERSION 1
+#define MT7925_STA_STATS_VALID 1U
+
+#define MT7925_MIB_MAX_CNT 32
+#define MT7925_MIB_CNT_MAX_NUM 512 /* firmware UNI_CMD_MIB_CNT_MAX_NUM upper bound */
+
+#define MT7925_LQ_BAND_NUM 4 /* Link quality band */
+
+const char * const mt7925_tx_mode_str[] = {
+ "CCK", "OFDM", "MM", "GF", "VHT", "PLR",
+ "PLRP", "ALR", "HE_SU", "HE_ER", "HE_TRIG", "HE_MU",
+ "EHT_ER", "TOF", "EHT_TRIG", "EHT_MU"
+};
+
+const char * const mt7925_tx_rate_cck_str[] = {
+ "1M", "2M", "5.5M", "11M", "N/A"
+};
+
+const char * const mt7925_tx_rate_bw_str[] = {
+ "BW20", "BW40", "BW80", "BW160/BW8080", "BW320", "N/A"
+};
+
+struct mt7925_sta_stats_req {
+ u8 rsv[4];
+ __le16 tag;
+ __le16 len;
+ u8 sta_rec_idx;
+ u8 rsv1;
+ u8 lls_read_clear;
+ u8 reset_counter;
+} __packed;
+
+struct mt7925_sta_stats_event {
+ u8 rsv[4];
+ __le16 tag;
+ __le16 len;
+ struct mt7925_sta_stats stats;
+} __packed;
+
+/* WTBL auto-rate request TLV, mirror Gen4m struct UNI_CMD_STAT_WTBL.
+ * The RA tag reuses UNI_CMD_ID_STATISTICS_WTBL (10).
+ */
+struct mt7925_wtbl_rate_req {
+ u8 rsv[4];
+ __le16 tag;
+ __le16 len;
+ __le16 wlan_idx;
+ __le16 rsv1;
+} __packed;
+
+/* WTBL auto-rate event TLV. Firmware answers cmd tag 10 with event tag 12. */
+struct mt7925_wtbl_rate_event {
+ u8 rsv[4];
+ struct mt7925_wtbl_rate rate;
+} __packed;
+
+/* fixed field, mirror UNI_(CMD|EVENT)_MIB_INFO */
+struct mt7925_mib_hdr {
+ u8 band_idx;
+ u8 rsv[3];
+} __packed;
+
+/* One requested/returned MIB counter, mirror UNI_(CMD|EVENT)_MIB_DATA_T. */
+struct mt7925_mib_data {
+ __le16 tag; /* UNI_(CMD|EVENT)_MIB_DATA = 0 */
+ __le16 len;
+ __le32 counter; /* MIB counter ID (enum mt7925_mib_counter) */
+ __le64 data; /* only valid on event; ignored on cmd */
+} __packed;
+
+/* per-band link quality entry, mirror UNI_LINK_QUALITY */
+struct mt7925_link_quality {
+ s8 rssi; /* moving-average beacon RSSI of connected AP */
+ s8 link_quality; /* always 0 */
+ __le16 link_speed; /* TX rate1, unit 500 Kb/s */
+ u8 medium_busy_pct; /* always 0 */
+ u8 is_ready; /* 0: fields invalid, 1: valid */
+ u8 rsv[2];
+} __packed;
+
+/* request TLV, mirror UNI_CMD_LINK_QUALITY (header + tag/len only) */
+struct mt7925_lq_req {
+ u8 rsv[4]; /* UNI_CMD_GET_STATISTICS fixed field */
+ __le16 tag; /* UNI_CMD_GET_STATISTICS_TAG_LINK_QUALITY = 1 */
+ __le16 len;
+} __packed;
+
+/* event TLV, mirror UNI_EVENT_LINK_QUALITY */
+struct mt7925_lq_event {
+ u8 rsv[4]; /* UNI_EVENT_STATISTICS fixed field */
+ __le16 tag; /* UNI_EVENT_ID_STATISTICS_LINK_QUALITY = 1 */
+ __le16 len;
+ struct mt7925_link_quality lq[MT7925_LQ_BAND_NUM];
+} __packed;
+
+static void
+mt7925_get_ap_sta_rec_idx_iter(void *data, u8 *mac,
+ struct ieee80211_vif *vif)
+{
+ struct mt7925_sta_rec_lookup *lookup = data;
+ struct mt792x_vif *mvif;
+ struct mt792x_sta *msta;
+
+ if (lookup->sta_rec_idx >= 0)
+ return;
+
+ if (vif->type != NL80211_IFTYPE_STATION || !vif->cfg.assoc)
+ return;
+
+ mvif = (struct mt792x_vif *)vif->drv_priv;
+ msta = mvif->wep_sta;
+ if (!msta)
+ return;
+
+ if (!msta->deflink.wcid.sta)
+ return;
+
+ lookup->sta_rec_idx = msta->deflink.wcid.idx;
+}
+
+int
+mt7925_get_ap_sta_rec_idx(struct mt792x_dev *dev, u8 *sta_rec_idx)
+{
+ struct mt7925_sta_rec_lookup lookup = {
+ .sta_rec_idx = -1,
+ };
+
+ lockdep_assert_held(&dev->mt76.mutex);
+
+ ieee80211_iterate_active_interfaces(dev->mphy.hw,
+ IEEE80211_IFACE_ITER_RESUME_ALL,
+ mt7925_get_ap_sta_rec_idx_iter,
+ &lookup);
+
+ if (lookup.sta_rec_idx < 0)
+ return -ENOTCONN;
+
+ if (lookup.sta_rec_idx > U8_MAX)
+ return -ERANGE;
+
+ *sta_rec_idx = lookup.sta_rec_idx;
+
+ return 0;
+}
+
+/* Decode the TX Vector BBP latch and print the last TX rate */
+void
+mt7925_print_last_tx_rate(struct seq_file *s, const __le32 le_txv[3])
+{
+ const char *mode_str = "N/A";
+ u8 rate, txmode, frmode, sgi, ldpc, nsts, stbc, spe, dcm, ersu106t;
+ s8 txpwr, pos_txpwr;
+ u32 txv[3];
+
+ txv[0] = le32_to_cpu(le_txv[0]);
+ txv[1] = le32_to_cpu(le_txv[1]);
+ txv[2] = le32_to_cpu(le_txv[2]);
+
+ if (txv[0] == 0xffffffff) {
+ seq_printf(s, "%-22s %s = %s\n", " ", "Last TX Rate", "N/A");
+ seq_printf(s, "%-22s %s = %s\n",
+ " ", "Chip Out TX Power", "N/A");
+ return;
+ }
+
+ rate = MT7925_TXV_GET_TX_RATE(txv);
+ txmode = MT7925_TXV_GET_TX_MODE(txv);
+ frmode = MT7925_TXV_GET_TX_FRMODE(txv);
+ nsts = MT7925_TXV_GET_TX_NSTS(txv) + 1;
+ sgi = MT7925_TXV_GET_TX_SGI(txv);
+ ldpc = MT7925_TXV_GET_TX_LDPC(txv);
+ stbc = MT7925_TXV_GET_TX_STBC(txv);
+ txpwr = MT7925_TXV_GET_TX_PWR(txv);
+ spe = MT7925_TXV_GET_TX_SPE_IDX(txv);
+ dcm = MT7925_TXV_GET_TX_DCM(txv);
+ ersu106t = MT7925_TXV_GET_TX_106T(txv);
+
+ if (dcm)
+ rate = MT7925_TXV_RATE_UNMASK_DCM(rate);
+ if (ersu106t)
+ rate = MT7925_TXV_RATE_UNMASK_106T(rate);
+
+ if (txmode < ARRAY_SIZE(mt7925_tx_mode_str))
+ mode_str = mt7925_tx_mode_str[txmode];
+
+ seq_printf(s, "%-22s %s = ", " ", "Last TX Rate");
+
+ /* rate/MCS field */
+ if (txmode == MT7925_TX_RATE_MODE_CCK)
+ seq_printf(s, "%s, ",
+ rate < 4 ? mt7925_tx_rate_cck_str[rate] :
+ (((rate >= 5) && (rate <= 7)) ?
+ mt7925_tx_rate_cck_str[rate - 4] :
+ mt7925_tx_rate_cck_str[4]));
+ else if (txmode == MT7925_TX_RATE_MODE_OFDM)
+ seq_printf(s, "%s, ", mt7925_hw_rate_ofdm_str(rate));
+ else if (txmode == MT7925_TX_RATE_MODE_HTMIX ||
+ txmode == MT7925_TX_RATE_MODE_HTGF)
+ seq_printf(s, "MCS%d, ", rate);
+ else
+ seq_printf(s, "%s%d_MCS%d, ",
+ stbc ? "NSTS" : "NSS", nsts, rate);
+
+ /* bandwidth */
+ seq_printf(s, "%s, ",
+ frmode < 5 ? mt7925_tx_rate_bw_str[frmode] :
+ mt7925_tx_rate_bw_str[5]);
+
+ /* GI / preamble */
+ if (txmode == MT7925_TX_RATE_MODE_CCK)
+ seq_printf(s, "%s, ", rate < 4 ? "LP" : "SP");
+ else if (txmode == MT7925_TX_RATE_MODE_OFDM)
+ ; /* OFDM has no GI/preamble to print */
+ else if (txmode == MT7925_TX_RATE_MODE_HTMIX ||
+ txmode == MT7925_TX_RATE_MODE_HTGF ||
+ txmode == MT7925_TX_RATE_MODE_VHT ||
+ txmode == MT7925_TX_RATE_MODE_PLR)
+ seq_printf(s, "%s, ", sgi == 0 ? "LGI" : "SGI");
+ else
+ seq_printf(s, "%s, ",
+ sgi == 0 ? "SGI" : (sgi == 1 ? "MGI" : "LGI"));
+
+ /* mode, DCM, 106t, STBC, coding, SPE */
+ seq_printf(s, "%s%s%s%s%s%s%d\n",
+ mode_str,
+ dcm ? ", DCM" : "", ersu106t ? ", 106t" : "",
+ stbc ? ", STBC, " : ", ", ldpc == 0 ? "BCC" : "LDPC",
+ ", SPE", spe);
+
+ /* chip out TX power (unit: 0.5 dBm) */
+ pos_txpwr = (txpwr < 0) ? (~txpwr + 1) : txpwr;
+ seq_printf(s, "%-22s %s = %c%d.%1d dBm\n",
+ " ", "Chip Out TX Power",
+ (txpwr < 0) ? '-' : '+',
+ (pos_txpwr / 2), 5 * (pos_txpwr % 2));
+}
+
+/* Guard interval used for the auto-rate entries, mirror Gen4m
+ * connac3x_get_sgi_info_from_fw(): pick the per-mode/per-BW flag.
+ */
+u8
+mt7925_wtbl_get_sgi(const struct mt7925_wtbl_rate *wr, u8 txmode, u8 fcap)
+{
+ bool he = (txmode == MT7925_TX_RATE_MODE_HE_SU ||
+ txmode == MT7925_TX_RATE_MODE_HE_ER ||
+ txmode == MT7925_TX_RATE_MODE_HE_TRIG ||
+ txmode == MT7925_TX_RATE_MODE_HE_MU);
+ bool eht = (txmode == MT7925_TX_RATE_MODE_EHT_ER ||
+ txmode == MT7925_TX_RATE_MODE_EHT_TRIG ||
+ txmode == MT7925_TX_RATE_MODE_EHT_MU);
+
+ if (eht)
+ return wr->gi_eht;
+
+ switch (fcap) {
+ case MT7925_BW_20:
+ return he ? wr->g2_he : wr->g2;
+ case MT7925_BW_40:
+ return he ? wr->g4_he : wr->g4;
+ case MT7925_BW_80:
+ return he ? wr->g8_he : wr->g8;
+ case MT7925_BW_160:
+ return he ? wr->g16_he : wr->g16;
+ default:
+ return 0;
+ }
+}
+
+/* Coding used for the auto-rate entries, mirror Gen4m
+ * connac3x_wtbl_get_ldpc_info_from_fw(): 0 = BCC, 1 = LDPC.
+ */
+u8
+mt7925_wtbl_get_ldpc(const struct mt7925_wtbl_rate *wr, u8 txmode)
+{
+ switch (txmode) {
+ case MT7925_TX_RATE_MODE_HTMIX:
+ case MT7925_TX_RATE_MODE_HTGF:
+ return wr->ht_ldpc;
+ case MT7925_TX_RATE_MODE_VHT:
+ return wr->vht_ldpc;
+ case MT7925_TX_RATE_MODE_HE_SU:
+ case MT7925_TX_RATE_MODE_HE_ER:
+ case MT7925_TX_RATE_MODE_HE_TRIG:
+ case MT7925_TX_RATE_MODE_HE_MU:
+ return wr->he_ldpc;
+ case MT7925_TX_RATE_MODE_EHT_ER:
+ case MT7925_TX_RATE_MODE_EHT_TRIG:
+ case MT7925_TX_RATE_MODE_EHT_MU:
+ return wr->eht_ldpc;
+ default:
+ return 0;
+ }
+}
+
+int mt792x_mcu_get_stat(struct mt792x_dev *dev,
+ struct mt7925_sta_stats *stats)
+{
+ u16 len = 0;
+ int ret = 0;
+ struct mt7925_sta_stats_event *event = NULL;
+ struct sk_buff *skb = NULL;
+ struct mt7925_sta_stats_req req = {
+ .tag = cpu_to_le16(UNI_CMD_ID_STATISTICS_STA),
+ .len = cpu_to_le16(sizeof(req) - sizeof(req.rsv)),
+ .lls_read_clear = false,
+ .reset_counter = false,
+ };
+
+ ret = mt7925_get_ap_sta_rec_idx(dev, &req.sta_rec_idx);
+ if (ret)
+ return ret;
+
+ if (!stats)
+ return -EINVAL;
+
+ ret = mt76_mcu_send_and_get_msg(&dev->mt76,
+ MCU_UNI_QUERY(GET_STAT_INFO),
+ &req, sizeof(req), true, &skb);
+
+ if (ret)
+ return ret;
+
+ event = (struct mt7925_sta_stats_event *)skb->data;
+
+ if (le16_to_cpu(event->tag) != UNI_CMD_ID_STATISTICS_STA) {
+ ret = -EPROTO;
+ goto out;
+ }
+
+ len = le16_to_cpu(event->len);
+ if (len < sizeof(*event) - sizeof(event->rsv) ||
+ len > skb->len - sizeof(event->rsv) || !IS_ALIGNED(len, 4)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+
+ if (event->stats.version != MT7925_STA_STATS_VERSION) {
+ ret = -EPROTO;
+ goto out;
+ }
+
+ if (!(le32_to_cpu(event->stats.flags) & MT7925_STA_STATS_VALID)) {
+ ret = -ENOENT;
+ goto out;
+ }
+
+ memcpy(stats, &event->stats, sizeof(*stats));
+
+out:
+ dev_kfree_skb(skb);
+ return ret;
+}
+
+int mt7925_mcu_get_mib_info(struct mt792x_dev *dev, u8 band_idx,
+ const u32 *counters, u8 counter_num,
+ u64 *values)
+{
+ u8 *req;
+ u8 *tlv, *tlv_end;
+ u16 req_len;
+ int i, ret;
+ struct mt7925_mib_hdr *req_hdr;
+ struct mt7925_mib_data *req_tlv;
+ struct sk_buff *skb;
+
+ if (!counters || !values || !counter_num ||
+ counter_num > MT7925_MIB_MAX_CNT)
+ return -EINVAL;
+
+ /* clear output; counters not returned by firmware stay 0 */
+ memset(values, 0, counter_num * sizeof(*values));
+
+ req_len = sizeof(struct mt7925_mib_hdr) +
+ counter_num * sizeof(struct mt7925_mib_data);
+
+ req = kzalloc(req_len, GFP_KERNEL);
+ if (!req)
+ return -ENOMEM;
+
+ req_hdr = (struct mt7925_mib_hdr *)req;
+ req_hdr->band_idx = band_idx;
+
+ req_tlv = (struct mt7925_mib_data *)(req + sizeof(*req_hdr));
+ for (i = 0; i < counter_num; i++) {
+ req_tlv[i].tag = cpu_to_le16(UNI_CMD_MIB_DATA);
+ req_tlv[i].len = cpu_to_le16(sizeof(struct mt7925_mib_data));
+ req_tlv[i].counter = cpu_to_le32(counters[i]);
+ }
+
+ ret = mt76_mcu_send_and_get_msg(&dev->mt76,
+ MCU_UNI_QUERY(GET_MIB_INFO), req,
+ req_len, true, &skb);
+ kfree(req);
+ if (ret)
+ return ret;
+
+ if (skb->len < sizeof(struct mt7925_mib_hdr)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+
+ /* walk the returned TLVs, match each counter ID to the request */
+ tlv = skb->data + sizeof(struct mt7925_mib_hdr);
+ tlv_end = skb->data + skb->len;
+
+ while (tlv + sizeof(struct mt7925_mib_data) <= tlv_end) {
+ struct mt7925_mib_data *evt_tlv =
+ (struct mt7925_mib_data *)tlv;
+ u16 tag = le16_to_cpu(evt_tlv->tag);
+ u16 len = le16_to_cpu(evt_tlv->len);
+ u32 counter;
+
+ if (len < sizeof(struct mt7925_mib_data) ||
+ tlv + len > tlv_end)
+ break;
+
+ if (tag == UNI_CMD_MIB_DATA) {
+ counter = le32_to_cpu(evt_tlv->counter);
+
+ if (counter < MT7925_MIB_CNT_MAX_NUM) {
+ for (i = 0; i < counter_num; i++) {
+ if (counters[i] == counter) {
+ values[i] =
+ le64_to_cpu(evt_tlv->data);
+ break;
+ }
+ }
+ }
+ }
+
+ tlv += len;
+ }
+
+ ret = 0;
+
+out:
+ dev_kfree_skb(skb);
+
+ return ret;
+}
+
+char *mt7925_hw_rate_ofdm_str(u8 ofdm_idx)
+{
+ char * const ofdm_str[] = {
+ "6M", "9M", "12M", "18M", "24M", "36M", "48M", "54M", "N/A"
+ };
+
+ switch (ofdm_idx) {
+ case 11: return ofdm_str[0]; /* 6M */
+ case 15: return ofdm_str[1]; /* 9M */
+ case 10: return ofdm_str[2]; /* 12M */
+ case 14: return ofdm_str[3]; /* 18M */
+ case 9: return ofdm_str[4]; /* 24M */
+ case 13: return ofdm_str[5]; /* 36M */
+ case 8: return ofdm_str[6]; /* 48M */
+ case 12: return ofdm_str[7]; /* 54M */
+ default: return ofdm_str[8];
+ }
+}
+
+/* mt792x_mcu_get_wtbl_rate - fetch the firmware auto-rate table for a WCID
+ * @dev: device
+ * @wlan_idx: HW WTBL / WCID index (see mt7925_sta_stats.wtbl_idx)
+ * @rate: out, filled with the auto-rate table snapshot
+ *
+ * Mirror of Gen4m wlanoidGetStatWtbl(): send UNI_CMD_GET_STAT_INFO with the
+ * WTBL rate TLV (cmd tag 10) and parse the reply (event tag 12) into @rate.
+ *
+ * Return: 0 on success, negative errno otherwise.
+ */
+int mt792x_mcu_get_wtbl_rate(struct mt792x_dev *dev, u16 wlan_idx,
+ struct mt7925_wtbl_rate *rate)
+{
+ int ret;
+ struct mt7925_wtbl_rate_event *event = NULL;
+ struct sk_buff *skb = NULL;
+ struct mt7925_wtbl_rate_req req = {
+ .tag = cpu_to_le16(UNI_CMD_ID_STATISTICS_WTBL),
+ .len = cpu_to_le16(sizeof(req) - sizeof(req.rsv)),
+ .wlan_idx = cpu_to_le16(wlan_idx),
+ };
+
+ if (!rate)
+ return -EINVAL;
+
+ ret = mt76_mcu_send_and_get_msg(&dev->mt76,
+ MCU_UNI_QUERY(GET_STAT_INFO),
+ &req, sizeof(req), true, &skb);
+ if (ret)
+ return ret;
+
+ if (skb->len < sizeof(*event)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+
+ event = (struct mt7925_wtbl_rate_event *)skb->data;
+
+ if (le16_to_cpu(event->rate.tag) != MT7925_UNI_EVENT_STATISTICS_WTBL) {
+ ret = -EPROTO;
+ goto out;
+ }
+
+ memcpy(rate, &event->rate, sizeof(*rate));
+ ret = 0;
+
+out:
+ dev_kfree_skb(skb);
+ return ret;
+}
+
+/**
+ * mt7925_mcu_get_link_quality - query RSSI and TX link speed for a band
+ * @dev: device
+ * @band_idx: band/BSS index (0..3)
+ * @rssi: out, moving-average beacon RSSI in dBm (may be NULL)
+ * @tx_link_speed_kbps: out, TX rate in kbit/s (may be NULL)
+ *
+ * Return: 0 on success, -ENOENT if the band's link quality is not ready.
+ *
+ * Only RSSI and TX rate come from firmware here; for RX rate read wcid->rate.
+ */
+int mt7925_mcu_get_link_quality(struct mt792x_dev *dev, u8 band_idx,
+ s8 *rssi, u32 *tx_link_speed_kbps)
+{
+ int ret;
+ struct mt7925_lq_event *event;
+ struct mt7925_link_quality *lq;
+ struct sk_buff *skb;
+ struct mt7925_lq_req req = {
+ .tag = cpu_to_le16(UNI_CMD_ID_STATISTICS_LINK_QUALITY),
+ .len = cpu_to_le16(sizeof(req) - sizeof(req.rsv)),
+ };
+
+ if (band_idx >= MT7925_LQ_BAND_NUM)
+ return -EINVAL;
+
+ ret = mt76_mcu_send_and_get_msg(&dev->mt76,
+ MCU_UNI_QUERY(GET_STAT_INFO), &req,
+ sizeof(req), true, &skb);
+ if (ret)
+ return ret;
+
+ if (skb->len < sizeof(*event)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+
+ event = (struct mt7925_lq_event *)skb->data;
+
+ if (le16_to_cpu(event->tag) != UNI_CMD_ID_STATISTICS_LINK_QUALITY) {
+ ret = -EPROTO;
+ goto out;
+ }
+
+ lq = &event->lq[band_idx];
+ if (!lq->is_ready) {
+ ret = -ENOENT;
+ goto out;
+ }
+
+ if (rssi)
+ *rssi = lq->rssi;
+
+ /* firmware reports TX rate in 500 Kb/s units -> kbit/s */
+ if (tx_link_speed_kbps)
+ *tx_link_speed_kbps = le16_to_cpu(lq->link_speed) * 500;
+
+ ret = 0;
+
+out:
+ dev_kfree_skb(skb);
+
+ return ret;
+}
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/stats.h b/drivers/net/wireless/mediatek/mt76/mt7925/stats.h
new file mode 100644
index 000000000000..2e29b20cf27b
--- /dev/null
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/stats.h
@@ -0,0 +1,305 @@
+/* SPDX-License-Identifier: BSD-3-Clause-Clear */
+/* Copyright (C) 2026 MediaTek Inc. */
+
+#ifndef __MT7925_STATS_H
+#define __MT7925_STATS_H
+
+#include <linux/if_ether.h>
+#include <linux/types.h>
+#include "../mt76_connac_mcu.h"
+
+#define MT7925_STA_STATS_AC_NUM 4
+#define MT7925_STA_STATS_BAND_NUM 2
+#define MT7925_STA_STATS_AGG_RANGE_NUM 16
+#define MT7925_STA_STATS_TX_QUALITY_NUM 4
+#define MT7925_TX_MODE_NUM 16
+#define MT7925_TX_RATE_CCK_NUM 5
+#define MT7925_TX_RATE_BW_NUM 6
+
+/* HW rate code (16-bit) field extract, mirror Gen4m CONNAC3X_HW_TX_RATE_TO_*.
+ * The auto-rate table stores one rate code per entry.
+ */
+#define MT7925_HW_RATE_TO_MCS(_x) (((_x) & GENMASK(5, 0)) >> 0)
+#define MT7925_HW_RATE_TO_MODE(_x) (((_x) & GENMASK(9, 6)) >> 6)
+#define MT7925_HW_RATE_TO_NSS(_x) (((_x) & GENMASK(13, 10)) >> 10)
+#define MT7925_HW_RATE_TO_STBC(_x) (((_x) & BIT(14)) >> 14)
+#define MT7925_HW_RATE_TO_DCM(_x) (((_x) & BIT(4)) >> 4)
+#define MT7925_HW_RATE_TO_106T(_x) (((_x) & BIT(5)) >> 5)
+#define MT7925_HW_RATE_UNMASK_DCM(_r) ((u8)(_r) & 0xef)
+#define MT7925_HW_RATE_UNMASK_106T(_r) ((u8)(_r) & 0xdf)
+
+/* Number of entries in the firmware auto-rate table (mirror Gen4m
+ * AUTO_RATE_NUM). RA cycles through these and reports which one is in use
+ * via wtbl->rate_idx.
+ */
+#define MT7925_AUTO_RATE_NUM 8
+
+/* Firmware EVENT_STATISTICS sub-event tags. The WTBL rate request uses cmd tag
+ * UNI_CMD_ID_STATISTICS_WTBL (10) but the firmware replies with event tag 12.
+ */
+#define MT7925_UNI_EVENT_STATISTICS_WTBL 12
+
+/* CONNAC3X TX Vector BBP latch bit fields (mirror cmm_asic_connac3x.h).
+ * These operate on the 3-DWORD tx_vector[band].txv[3] array.
+ */
+#define MT7925_TXV_GET_TX_RATE(_txv) ((_txv)[2] & 0x7f)
+#define MT7925_TXV_GET_TX_LDPC(_txv) (((_txv)[2] & (0x1 << 7)) >> 7)
+#define MT7925_TXV_GET_TX_STBC(_txv) (((_txv)[0] & (0x3 << 6)) >> 6)
+#define MT7925_TXV_GET_TX_FRMODE(_txv) (((_txv)[0] & (0x7 << 8)) >> 8)
+#define MT7925_TXV_GET_TX_MODE(_txv) (((_txv)[0] & (0xf << 12)) >> 12)
+#define MT7925_TXV_GET_TX_NSTS(_txv) (((_txv)[2] & (0xfU << 28)) >> 28)
+#define MT7925_TXV_GET_TX_PWR(_txv) (((_txv)[0] & (0xff << 16)) >> 16)
+#define MT7925_TXV_GET_TX_SGI(_txv) (((_txv)[1] & (0x3 << 26)) >> 26)
+#define MT7925_TXV_GET_TX_SPE_IDX(_txv) (((_txv)[0] & (0x1f << 0)) >> 0)
+#define MT7925_TXV_GET_TX_DCM(_txv) (((_txv)[2] & (0x1 << 4)) >> 4)
+#define MT7925_TXV_GET_TX_106T(_txv) (((_txv)[2] & (0x1 << 5)) >> 5)
+#define MT7925_TXV_RATE_UNMASK_DCM(_r) ((u8)(_r) & 0xef)
+#define MT7925_TXV_RATE_UNMASK_106T(_r) ((u8)(_r) & 0xdf)
+
+extern const char * const mt7925_tx_mode_str[MT7925_TX_MODE_NUM];
+extern const char * const mt7925_tx_rate_cck_str[MT7925_TX_RATE_CCK_NUM];
+extern const char * const mt7925_tx_rate_bw_str[MT7925_TX_RATE_BW_NUM];
+
+/* TX rate mode, mirror Gen4m TX_RATE_MODE_* (see nic_rate.h). Used to decode
+ * both the TX Vector BBP latch and the firmware auto-rate table rate codes.
+ */
+enum mt7925_tx_rate_mode {
+ MT7925_TX_RATE_MODE_CCK = 0,
+ MT7925_TX_RATE_MODE_OFDM = 1,
+ MT7925_TX_RATE_MODE_HTMIX = 2,
+ MT7925_TX_RATE_MODE_HTGF = 3,
+ MT7925_TX_RATE_MODE_VHT = 4,
+ MT7925_TX_RATE_MODE_PLR = 5,
+ MT7925_TX_RATE_MODE_HE_SU = 8,
+ MT7925_TX_RATE_MODE_HE_ER = 9,
+ MT7925_TX_RATE_MODE_HE_TRIG = 10,
+ MT7925_TX_RATE_MODE_HE_MU = 11,
+ MT7925_TX_RATE_MODE_EHT_ER = 13,
+ MT7925_TX_RATE_MODE_EHT_TRIG = 14,
+ MT7925_TX_RATE_MODE_EHT_MU = 15,
+};
+
+enum stats_query_mib_array_index {
+ QUERY_MIB_CNT_RX_FCS_ERR = 0,
+ QUERY_MIB_CNT_RX_FIFO_OVERFLOW,
+ QUERY_MIB_CNT_RX_MPDU,
+ QUERY_MIB_CNT_AMPDU_RX_COUNT,
+ QUERY_MIB_CNT_PF_DROP,
+ QUERY_MIB_CNT_LEN_MISMATCH,
+ QUERY_MIB_CNT_CHANNEL_IDLE,
+ QUERY_MIB_CNT_CCA_NAV_TX_TIME,
+ QUERY_MIB_CNT_MDRDY,
+ QUERY_MIB_CNT_RX_CCK_MDRDY_TIME,
+ QUERY_MIB_CNT_RX_OFDM_LG_MIXED_MDRDY_TIME,
+ QUERY_MIB_CNT_RX_OFDM_GREEN_MDRDY_TIME,
+ QUERY_MIB_CNT_P_CCA_TIME,
+ QUERY_MIB_CNT_S_CCA_TIME,
+ QUERY_MIB_CNT_P_ED_TIME,
+ QUERY_MIB_CNT_BCN_TX,
+ QUERY_MIB_CNT_TX_BW_40MHZ,
+ QUERY_MIB_CNT_TX_BW_80MHZ,
+ QUERY_MIB_CNT_TX_BW_160MHZ,
+ QUERY_MIB_CNT_BSS0_BA_MISS,
+ QUERY_MIB_CNT_BSS0_RTS_TX_CNT,
+ QUERY_MIB_CNT_BSS0_FRAME_RETRY,
+ QUERY_MIB_CNT_BSS0_FRAME_RETRY_2,
+ QUERY_MIB_CNT_BSS0_RTS_RETRY,
+ QUERY_MIB_CNT_BSS0_ACK_FAIL,
+ QUERY_MIB_CNT_AMPDU,
+ QUERY_MIB_CNT_AMPDU_MPDU,
+ QUERY_MIB_CNT_AMPDU_ACKED,
+ QUERY_MIB_CNT_MAX_NUM,
+};
+
+enum stats_trx_agg_range {
+ STAT_MIB_CNT_TRX_AGG_RANGE0_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE1_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE2_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE3_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE4_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE5_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE6_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE7_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE_MAX_NUM
+};
+
+enum {
+ MT7925_BW_20 = 0,
+ MT7925_BW_40,
+ MT7925_BW_80,
+ MT7925_BW_160,
+};
+
+struct mt7925_sta_stats_ac {
+ __le32 tx_fail;
+ __le32 tx_retry;
+} __packed;
+
+struct mt7925_sta_stats_tx_vector {
+ __le32 txv[3];
+} __packed;
+
+struct mt7925_sta_stats_mib {
+ __le32 rx_mpdu_cnt;
+ __le32 fcs_error;
+ __le32 rx_fifo_full;
+ __le32 ampdu_tx_sf_cnt;
+ __le32 ampdu_tx_ack_sf_cnt;
+ __le32 tx_range_ampdu_cnt[MT7925_STA_STATS_AGG_RANGE_NUM];
+} __packed;
+
+/* Firmware EVENT_STA_STATISTICS body. */
+struct mt7925_sta_stats {
+ u8 version;
+ u8 rsv[3];
+ __le32 flags;
+
+ u8 sta_idx;
+ u8 bss_idx;
+ u8 wtbl_idx;
+ u8 rsv1;
+
+ u8 mac_addr[ETH_ALEN];
+ u8 per;
+ u8 rcpi;
+
+ __le32 phy_mode;
+ __le16 link_speed; /* 0.5 Mbit/s units */
+ u8 link_quality;
+ u8 link_reserved;
+
+ __le32 tx_count;
+ __le32 tx_fail_count;
+ __le32 tx_life_timeout_count;
+ __le32 tx_done_air_time;
+ __le32 transmit_count;
+ __le32 transmit_fail_count;
+
+ struct mt7925_sta_stats_ac ac[MT7925_STA_STATS_AC_NUM];
+
+ u8 temperature;
+ u8 skip_ar;
+ u8 ar_table_idx;
+ u8 rate_entry_idx;
+ u8 rate_entry_idx_prev;
+ u8 tx_sgi_detect_pass_cnt;
+ u8 ave_per;
+ u8 rsv2;
+
+ __le32 agg_range_ctrl[2];
+ u8 range_mode;
+ u8 rsv3[3];
+ __le32 agg_range_ctrl_ext[6];
+
+ u8 ar_state_curr;
+ u8 ar_state_prev;
+ u8 ar_action_type;
+ u8 highest_rate_cnt;
+ u8 lowest_rate_cnt;
+ u8 rsv4;
+ __le16 train_up;
+ __le16 train_down;
+ u8 rsv5[2];
+ __le32 rate1_tx_cnt;
+ __le32 rate1_fail_cnt;
+
+ struct mt7925_sta_stats_tx_vector tx_vector[MT7925_STA_STATS_BAND_NUM];
+ struct mt7925_sta_stats_mib mib[MT7925_STA_STATS_BAND_NUM];
+
+ u8 is_force_tx_stream;
+ u8 is_force_se_off;
+ __le16 ra_running_cnt;
+ u8 ra_status;
+ u8 max_ampdu_factor;
+ u8 tx_quality[MT7925_STA_STATS_TX_QUALITY_NUM];
+ u8 tx_rate_up_penalty;
+ u8 low_traffic_mode;
+ u8 low_traffic_count;
+ u8 low_traffic_dashboard;
+ u8 dynamic_sgi_state;
+ u8 dynamic_sgi_score;
+ u8 dynamic_bw_state;
+ u8 dynamic_gband_256qam_state;
+ u8 vht_non_sp_rate_state;
+ u8 rsv6[3];
+ u8 rsv7[2];
+} __packed;
+
+struct mt7925_sta_rec_lookup {
+ int sta_rec_idx;
+};
+
+/* Firmware auto-rate table snapshot, mirror Gen4m struct UNI_EVENT_STAT_WTBL.
+ *
+ * The firmware RA maintains up to MT7925_AUTO_RATE_NUM candidate rates in
+ * rate_code[]. rate_idx tells which entry is currently in use, so a dump can
+ * point at it with "-->". fcap is the bandwidth capability and the ldpc/sgi
+ * flags describe the per-mode coding/guard-interval used for those rates.
+ */
+struct mt7925_wtbl_rate {
+ __le16 tag;
+ __le16 len;
+ u8 rx_rcpi0;
+ u8 rx_rcpi1;
+ u8 cbrn;
+ u8 fcap;
+ u8 spe_idx;
+ u8 g2; /* SGI enabled, non-HE, BW20 */
+ u8 g4; /* SGI enabled, non-HE, BW40 */
+ u8 g8; /* SGI enabled, non-HE, BW80 */
+ u8 g16; /* SGI enabled, non-HE, BW160 */
+ u8 g2_he; /* GI, HE, BW20 */
+ u8 g4_he; /* GI, HE, BW40 */
+ u8 g8_he; /* GI, HE, BW80 */
+ u8 g16_he; /* GI, HE, BW160 */
+ u8 gi_eht; /* GI, EHT */
+ u8 ht_ldpc;
+ u8 vht_ldpc;
+ u8 he_ldpc;
+ u8 eht_ldpc;
+ u8 af;
+ u8 rate_idx; /* index into rate_code[] currently in use */
+ __le16 rate_code[MT7925_AUTO_RATE_NUM];
+ __le32 rsv[4];
+} __packed;
+
+int
+mt7925_get_ap_sta_rec_idx(struct mt792x_dev *dev, u8 *sta_rec_idx);
+
+void
+mt7925_print_last_tx_rate(struct seq_file *s, const __le32 le_txv[3]);
+
+u8
+mt7925_wtbl_get_sgi(const struct mt7925_wtbl_rate *wr, u8 txmode, u8 fcap);
+
+u8
+mt7925_wtbl_get_ldpc(const struct mt7925_wtbl_rate *wr, u8 txmode);
+
+char *mt7925_hw_rate_ofdm_str(u8 ofdm_idx);
+
+int mt792x_mcu_get_wtbl_rate(struct mt792x_dev *dev, u16 wlan_idx,
+ struct mt7925_wtbl_rate *rate);
+
+int mt792x_mcu_get_stat(struct mt792x_dev *dev,
+ struct mt7925_sta_stats *stats);
+
+int mt7925_mcu_get_mib_info(struct mt792x_dev *dev, u8 band_idx,
+ const u32 *counters, u8 counter_num,
+ u64 *values);
+
+/* GET_STAT_INFO (UNI_CMD_ID_GET_STATISTICS = 0x23),
+ * sub-command UNI_CMD_GET_STATISTICS_TAG_LINK_QUALITY = 1.
+ *
+ * @band_idx: band/BSS index (0..3)
+ * @rssi: out, moving-average beacon RSSI (dBm), may be NULL
+ * @tx_link_speed_kbps: out, TX link speed in kbit/s, may be NULL
+ *
+ * Returns 0 on success, -ENOENT if the band's link quality is not ready.
+ *
+ * NOTE: firmware provides only RSSI and TX rate here. RX rate/bandwidth are
+ * not in this event; read wcid->rate for the RX rate.
+ */
+int mt7925_mcu_get_link_quality(struct mt792x_dev *dev, u8 band_idx,
+ s8 *rssi, u32 *tx_link_speed_kbps);
+
+#endif
--
2.45.2
^ permalink raw reply related [flat|nested] 12+ messages in thread* Re: [PATCH v1] wifi: mt76: mt7928: Add debugfs for stats to provide debug purpose
2026-08-24 3:07 [PATCH v1] wifi: mt76: mt7928: Add debugfs for stats to provide debug purpose Eason Lai
@ 2026-08-25 4:00 ` kernel test robot
2026-08-25 7:55 ` kernel test robot
2026-08-27 5:54 ` [PATCH v2] " Eason Lai
2 siblings, 0 replies; 12+ messages in thread
From: kernel test robot @ 2026-08-25 4:00 UTC (permalink / raw)
To: Eason Lai, nbd, lorenzo
Cc: oe-kbuild-all, linux-wireless, linux-mediatek, kun.wu, deren.wu,
sean.wang, quan.zhou, ryder.lee, leon.yen, litien.chang, jb.tsai,
michael.shih, eason.lai
Hi Eason,
kernel test robot noticed the following build errors:
[auto build test ERROR on wireless-next/main]
[also build test ERROR on linus/master next-20260821]
[cannot apply to wireless/main v7.2]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Eason-Lai/wifi-mt76-mt7928-Add-debugfs-for-stats-to-provide-debug-purpose/20260824-110746
base: https://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless-next.git main
patch link: https://lore.kernel.org/r/20260824030746.60706-1-eason.lai%40mediatek.com
patch subject: [PATCH v1] wifi: mt76: mt7928: Add debugfs for stats to provide debug purpose
config: nios2-allmodconfig (https://download.01.org/0day-ci/archive/20260825/202608251101.iQGUrSd0-lkp@intel.com/config)
compiler: nios2-linux-gcc (GCC) 11.5.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260825/202608251101.iQGUrSd0-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202608251101.iQGUrSd0-lkp@intel.com/
All error/warnings (new ones prefixed by >>):
drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c: In function 'mt7925_fixedrate_set':
>> drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:312:16: error: variable 'rate' has initializer but incomplete type
312 | struct mt7925_ra_fixed_rate_v1 rate = {};
| ^~~~~~~~~~~~~~~~~~~~~~~
>> drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:312:40: error: storage size of 'rate' isn't known
312 | struct mt7925_ra_fixed_rate_v1 rate = {};
| ^~~~
>> drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:314:12: error: 'struct mt792x_dev' has no member named 'fixed_rate'
314 | dev->fixed_rate = val;
| ^~
>> drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:329:9: error: implicit declaration of function 'mt7925_mcu_set_fixed_rate'; did you mean 'mt7925_mcu_get_rx_rate'? [-Werror=implicit-function-declaration]
329 | mt7925_mcu_set_fixed_rate(dev, &rate);
| ^~~~~~~~~~~~~~~~~~~~~~~~~
| mt7925_mcu_get_rx_rate
>> drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:312:40: warning: unused variable 'rate' [-Wunused-variable]
312 | struct mt7925_ra_fixed_rate_v1 rate = {};
| ^~~~
drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c: In function 'mt7925_fixedrate_get':
drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:340:19: error: 'struct mt792x_dev' has no member named 'fixed_rate'
340 | *val = dev->fixed_rate;
| ^~
drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c: In function 'mt7925_autorate_set':
>> drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:364:12: error: 'struct mt792x_dev' has no member named 'auto_rate'
364 | dev->auto_rate = val;
| ^~
>> drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:367:9: error: implicit declaration of function 'mt7925_mcu_set_auto_rate'; did you mean 'mt7925_mcu_get_rx_rate'? [-Werror=implicit-function-declaration]
367 | mt7925_mcu_set_auto_rate(dev, wlan_idx, enable, mode);
| ^~~~~~~~~~~~~~~~~~~~~~~~
| mt7925_mcu_get_rx_rate
drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c: In function 'mt7925_autorate_get':
drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:378:19: error: 'struct mt792x_dev' has no member named 'auto_rate'
378 | *val = dev->auto_rate;
| ^~
drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c: In function 'mt7925_wtbl_idx_set':
>> drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:400:12: error: 'struct mt792x_dev' has no member named 'wtbl_dump_idx'
400 | dev->wtbl_dump_idx = (u32)val;
| ^~
drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c: In function 'mt7925_wtbl_idx_get':
drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:410:19: error: 'struct mt792x_dev' has no member named 'wtbl_dump_idx'
410 | *val = dev->wtbl_dump_idx;
| ^~
drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c: In function 'mt7925_wtbl_read':
drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:422:22: error: 'struct mt792x_dev' has no member named 'wtbl_dump_idx'
422 | u32 idx = dev->wtbl_dump_idx;
| ^~
>> drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:443:16: error: implicit declaration of function 'mt7925_mac_wtbl_lmac_addr'; did you mean 'mt7925_mac_wtbl_update'? [-Werror=implicit-function-declaration]
443 | base = mt7925_mac_wtbl_lmac_addr(dev, idx, 0);
| ^~~~~~~~~~~~~~~~~~~~~~~~~
| mt7925_mac_wtbl_update
drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c: In function 'mt7925_vefuse_read':
>> drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:474:23: error: 'struct mt792x_dev' has no member named 'vefuse_cap'
474 | u16 size = dev->vefuse_cap.size;
| ^~
drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:479:17: error: 'struct mt792x_dev' has no member named 'vefuse_cap'
479 | if (!dev->vefuse_cap.present || !size) {
| ^~
>> drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:492:23: error: implicit declaration of function 'mt7925_mcu_read_vefuse'; did you mean 'mt7925_mcu_del_dev'? [-Werror=implicit-function-declaration]
492 | ret = mt7925_mcu_read_vefuse(dev, off, chunk, buf + off);
| ^~~~~~~~~~~~~~~~~~~~~~
| mt7925_mcu_del_dev
drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c: In function 'mt7925_perf_ind_set':
>> drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:520:16: error: implicit declaration of function 'mt792x_perf_ind_trigger'; did you mean 'mt7925_perf_ind_set'? [-Werror=implicit-function-declaration]
520 | return mt792x_perf_ind_trigger(dev, val);
| ^~~~~~~~~~~~~~~~~~~~~~~
| mt7925_perf_ind_set
drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c: In function 'mt7925_perf_ind_enable_get':
>> drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:529:19: error: 'struct mt792x_dev' has no member named 'perf'
529 | *val = dev->perf.enabled;
| ^~
drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c: In function 'mt7925_perf_ind_enable_set':
drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:538:12: error: 'struct mt792x_dev' has no member named 'perf'
538 | dev->perf.enabled = !!val;
| ^~
drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c: In function 'mt7925_coex_info':
>> drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:555:15: error: implicit declaration of function 'mt7925_mcu_chip_config_query'; did you mean 'mt7925_mcu_chip_config'? [-Werror=implicit-function-declaration]
555 | ret = mt7925_mcu_chip_config_query(dev, "coexBwcGetModeInfo 0",
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
| mt7925_mcu_chip_config
>> drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:564:31: error: 'CHIP_CONFIG_TYPE_ASCII' undeclared (first use in this function)
564 | else if (resp_type == CHIP_CONFIG_TYPE_ASCII)
| ^~~~~~~~~~~~~~~~~~~~~~
drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:564:31: note: each undeclared identifier is reported only once for each function it appears in
drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c: In function 'mt7925_dmashdl_read':
>> drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:603:45: warning: format '%x' expects argument of type 'unsigned int', but argument 3 has type 'long unsigned int' [-Wformat=]
603 | seq_printf(s, "\t\tPLE pkt max=0x%03x, PSE pkt max=0x%03x\n",
| ~~~^
| |
| unsigned int
| %03lx
drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:603:65: warning: format '%x' expects argument of type 'unsigned int', but argument 4 has type 'long unsigned int' [-Wformat=]
603 | seq_printf(s, "\t\tPLE pkt max=0x%03x, PSE pkt max=0x%03x\n",
| ~~~^
| |
| unsigned int
| %03lx
In file included from drivers/net/wireless/mediatek/mt76/mt7925/../mt76_connac.h:7,
from drivers/net/wireless/mediatek/mt76/mt7925/../mt76_connac_mcu.h:7,
from drivers/net/wireless/mediatek/mt76/mt7925/../mt792x.h:10,
from drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h:7,
from drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:4:
>> drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:607:28: error: 'MT_DMASHDL_ERROR_FLAG_CTRL' undeclared (first use in this function)
607 | val = mt76_rr(dev, MT_DMASHDL_ERROR_FLAG_CTRL);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~
drivers/net/wireless/mediatek/mt76/mt7925/../mt76.h:1196:69: note: in definition of macro 'mt76_rr'
1196 | #define mt76_rr(dev, ...) (dev)->mt76.bus->rr(&((dev)->mt76), __VA_ARGS__)
| ^~~~~~~~~~~
>> drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:613:43: error: implicit declaration of function 'MT_DMASHDL_STATUS_RD_GP' [-Werror=implicit-function-declaration]
613 | u32 status = mt76_rr(dev, MT_DMASHDL_STATUS_RD_GP(i));
| ^~~~~~~~~~~~~~~~~~~~~~~
drivers/net/wireless/mediatek/mt76/mt7925/../mt76.h:1196:69: note: in definition of macro 'mt76_rr'
1196 | #define mt76_rr(dev, ...) (dev)->mt76.bus->rr(&((dev)->mt76), __VA_ARGS__)
| ^~~~~~~~~~~
In file included from <command-line>:
>> drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:614:37: error: 'MT_DMASHDL_STATUS_RD_GP_RSV' undeclared (first use in this function)
614 | u32 rsv = FIELD_GET(MT_DMASHDL_STATUS_RD_GP_RSV, status);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~
include/linux/compiler_types.h:682:23: note: in definition of macro '__compiletime_assert'
682 | if (!(condition)) \
| ^~~~~~~~~
include/linux/compiler_types.h:702:9: note: in expansion of macro '_compiletime_assert'
702 | _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
| ^~~~~~~~~~~~~~~~~~~
include/linux/build_bug.h:40:37: note: in expansion of macro 'compiletime_assert'
40 | #define BUILD_BUG_ON_MSG(cond, msg) compiletime_assert(!(cond), msg)
| ^~~~~~~~~~~~~~~~~~
include/linux/bitfield.h:79:9: note: in expansion of macro 'BUILD_BUG_ON_MSG'
79 | BUILD_BUG_ON_MSG(__bf_cast_unsigned(mask, mask) > \
| ^~~~~~~~~~~~~~~~
include/linux/bitfield.h:63:43: note: in expansion of macro '__unsigned_scalar_typeof'
63 | #define __bf_cast_unsigned(type, x) ((__unsigned_scalar_typeof(type))(x))
| ^~~~~~~~~~~~~~~~~~~~~~~~
include/linux/bitfield.h:79:26: note: in expansion of macro '__bf_cast_unsigned'
79 | BUILD_BUG_ON_MSG(__bf_cast_unsigned(mask, mask) > \
| ^~~~~~~~~~~~~~~~~~
include/linux/bitfield.h:177:17: note: in expansion of macro '__BF_FIELD_CHECK_REG'
177 | __BF_FIELD_CHECK_REG(_mask, _reg, "FIELD_GET: "); \
| ^~~~~~~~~~~~~~~~~~~~
drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:614:27: note: in expansion of macro 'FIELD_GET'
614 | u32 rsv = FIELD_GET(MT_DMASHDL_STATUS_RD_GP_RSV, status);
| ^~~~~~~~~
>> drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:615:37: error: 'MT_DMASHDL_STATUS_RD_GP_SRC' undeclared (first use in this function)
615 | u32 src = FIELD_GET(MT_DMASHDL_STATUS_RD_GP_SRC, status);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~
include/linux/compiler_types.h:682:23: note: in definition of macro '__compiletime_assert'
682 | if (!(condition)) \
| ^~~~~~~~~
include/linux/compiler_types.h:702:9: note: in expansion of macro '_compiletime_assert'
702 | _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
| ^~~~~~~~~~~~~~~~~~~
include/linux/build_bug.h:40:37: note: in expansion of macro 'compiletime_assert'
40 | #define BUILD_BUG_ON_MSG(cond, msg) compiletime_assert(!(cond), msg)
| ^~~~~~~~~~~~~~~~~~
include/linux/bitfield.h:79:9: note: in expansion of macro 'BUILD_BUG_ON_MSG'
79 | BUILD_BUG_ON_MSG(__bf_cast_unsigned(mask, mask) > \
| ^~~~~~~~~~~~~~~~
include/linux/bitfield.h:63:43: note: in expansion of macro '__unsigned_scalar_typeof'
63 | #define __bf_cast_unsigned(type, x) ((__unsigned_scalar_typeof(type))(x))
| ^~~~~~~~~~~~~~~~~~~~~~~~
include/linux/bitfield.h:79:26: note: in expansion of macro '__bf_cast_unsigned'
79 | BUILD_BUG_ON_MSG(__bf_cast_unsigned(mask, mask) > \
| ^~~~~~~~~~~~~~~~~~
include/linux/bitfield.h:177:17: note: in expansion of macro '__BF_FIELD_CHECK_REG'
177 | __BF_FIELD_CHECK_REG(_mask, _reg, "FIELD_GET: "); \
| ^~~~~~~~~~~~~~~~~~~~
drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:615:27: note: in expansion of macro 'FIELD_GET'
615 | u32 src = FIELD_GET(MT_DMASHDL_STATUS_RD_GP_SRC, status);
| ^~~~~~~~~
drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:617:65: warning: format '%x' expects argument of type 'unsigned int', but argument 4 has type 'long unsigned int' [-Wformat=]
617 | seq_printf(s, "\tGroup %2d: quota(min/max)=0x%03x/0x%03x, used/rsv=0x%03x/0x%03x\n",
| ~~~^
| |
| unsigned int
| %03lx
drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:617:72: warning: format '%x' expects argument of type 'unsigned int', but argument 5 has type 'long unsigned int' [-Wformat=]
617 | seq_printf(s, "\tGroup %2d: quota(min/max)=0x%03x/0x%03x, used/rsv=0x%03x/0x%03x\n",
| ~~~^
| |
| unsigned int
| %03lx
In file included from drivers/net/wireless/mediatek/mt76/mt7925/../mt76_connac.h:7,
from drivers/net/wireless/mediatek/mt76/mt7925/../mt76_connac_mcu.h:7,
from drivers/net/wireless/mediatek/mt76/mt7925/../mt792x.h:10,
from drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h:7,
from drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:4:
drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:626:28: error: 'MT_DMASHDL_STATUS_RD' undeclared (first use in this function)
626 | val = mt76_rr(dev, MT_DMASHDL_STATUS_RD);
| ^~~~~~~~~~~~~~~~~~~~
drivers/net/wireless/mediatek/mt76/mt7925/../mt76.h:1196:69: note: in definition of macro 'mt76_rr'
1196 | #define mt76_rr(dev, ...) (dev)->mt76.bus->rr(&((dev)->mt76), __VA_ARGS__)
| ^~~~~~~~~~~
In file included from <command-line>:
drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:627:33: error: 'MT_DMASHDL_STATUS_RD_FREE_PG' undeclared (first use in this function)
627 | free_pg_cnt = FIELD_GET(MT_DMASHDL_STATUS_RD_FREE_PG, val);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
include/linux/compiler_types.h:682:23: note: in definition of macro '__compiletime_assert'
682 | if (!(condition)) \
| ^~~~~~~~~
include/linux/compiler_types.h:702:9: note: in expansion of macro '_compiletime_assert'
702 | _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
| ^~~~~~~~~~~~~~~~~~~
include/linux/build_bug.h:40:37: note: in expansion of macro 'compiletime_assert'
40 | #define BUILD_BUG_ON_MSG(cond, msg) compiletime_assert(!(cond), msg)
| ^~~~~~~~~~~~~~~~~~
include/linux/bitfield.h:79:9: note: in expansion of macro 'BUILD_BUG_ON_MSG'
79 | BUILD_BUG_ON_MSG(__bf_cast_unsigned(mask, mask) > \
| ^~~~~~~~~~~~~~~~
include/linux/bitfield.h:63:43: note: in expansion of macro '__unsigned_scalar_typeof'
63 | #define __bf_cast_unsigned(type, x) ((__unsigned_scalar_typeof(type))(x))
| ^~~~~~~~~~~~~~~~~~~~~~~~
include/linux/bitfield.h:79:26: note: in expansion of macro '__bf_cast_unsigned'
79 | BUILD_BUG_ON_MSG(__bf_cast_unsigned(mask, mask) > \
| ^~~~~~~~~~~~~~~~~~
include/linux/bitfield.h:177:17: note: in expansion of macro '__BF_FIELD_CHECK_REG'
177 | __BF_FIELD_CHECK_REG(_mask, _reg, "FIELD_GET: "); \
| ^~~~~~~~~~~~~~~~~~~~
drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:627:23: note: in expansion of macro 'FIELD_GET'
627 | free_pg_cnt = FIELD_GET(MT_DMASHDL_STATUS_RD_FREE_PG, val);
| ^~~~~~~~~
drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:628:29: error: 'MT_DMASHDL_STATUS_RD_FFA' undeclared (first use in this function)
628 | ffa_cnt = FIELD_GET(MT_DMASHDL_STATUS_RD_FFA, val);
| ^~~~~~~~~~~~~~~~~~~~~~~~
include/linux/compiler_types.h:682:23: note: in definition of macro '__compiletime_assert'
682 | if (!(condition)) \
| ^~~~~~~~~
include/linux/compiler_types.h:702:9: note: in expansion of macro '_compiletime_assert'
702 | _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
| ^~~~~~~~~~~~~~~~~~~
include/linux/build_bug.h:40:37: note: in expansion of macro 'compiletime_assert'
40 | #define BUILD_BUG_ON_MSG(cond, msg) compiletime_assert(!(cond), msg)
| ^~~~~~~~~~~~~~~~~~
include/linux/bitfield.h:79:9: note: in expansion of macro 'BUILD_BUG_ON_MSG'
79 | BUILD_BUG_ON_MSG(__bf_cast_unsigned(mask, mask) > \
| ^~~~~~~~~~~~~~~~
include/linux/bitfield.h:63:43: note: in expansion of macro '__unsigned_scalar_typeof'
63 | #define __bf_cast_unsigned(type, x) ((__unsigned_scalar_typeof(type))(x))
| ^~~~~~~~~~~~~~~~~~~~~~~~
include/linux/bitfield.h:79:26: note: in expansion of macro '__bf_cast_unsigned'
79 | BUILD_BUG_ON_MSG(__bf_cast_unsigned(mask, mask) > \
| ^~~~~~~~~~~~~~~~~~
include/linux/bitfield.h:177:17: note: in expansion of macro '__BF_FIELD_CHECK_REG'
177 | __BF_FIELD_CHECK_REG(_mask, _reg, "FIELD_GET: "); \
vim +/rate +312 drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c
289
290 /* fixedrate input format (12 hex digits):
291 * 0x[WCID(2)][Mode][BW][MCS][Nss][SGI][Preamble][STBC][LDPC][SPE_EN][HeLtf]
292 * The RA tag is fixed to SET_FIXED_RATE internally.
293 * WCID is 2 nibbles, all others are 1 nibble.
294 *
295 * [WCID] Wireless Client ID
296 * [Mode] CCK=0, OFDM=1, HT=2, GF=3, VHT=4, PLR=5, HE_SU=8, HE_ER_SU=9,
297 * HE_TRIG=10, HE_MU=11, EHT_ER=13, EHT_TB=14, EHT_SU and EHT_MU=15
298 * [BW] BW20=0, BW40=1, BW80=2, BW160=3, BW320=4
299 * [MCS] CCK=0~3, OFDM=0~7, HT=0~32, VHT=0~9, HE=0~11, EHT=0~13, EHT_ER=14~15
300 * [Nss] 1~8
301 * [GI] HT/VHT: 0=Long, 1=Short; HE/EHT: 0=0.8us, 1=1.6us, 2=3.2us
302 * [Preamble] Long=0, Short=other
303 * [STBC] Enable=1, Disable=0
304 * [LDPC] BCC=0, LDPC=1 (driver sends the FW's "on" value 7)
305 * [SPE_EN] spatial extension index
306 * [HeLtf] 1X=0, 2X=1, 4X=2 (NOTE: EHT modes require HeLtf=1)
307 */
308 static int
309 mt7925_fixedrate_set(void *data, u64 val)
310 {
311 struct mt792x_dev *dev = data;
> 312 struct mt7925_ra_fixed_rate_v1 rate = {};
313
> 314 dev->fixed_rate = val;
315
316 rate.wlan_idx = cpu_to_le16((val >> 40) & 0xff);
317 rate.phy_mode = (val >> 36) & 0xf;
318 rate.bw = (val >> 32) & 0xf;
319 rate.mcs = (val >> 28) & 0xf;
320 rate.nss = (val >> 24) & 0xf;
321 rate.short_gi = cpu_to_le16((val >> 20) & 0xf);
322 rate.short_preamble = (val >> 16) & 0xf;
323 rate.stbc = (val >> 12) & 0xf;
324 rate.ecc = ((val >> 8) & 0xf) ? 7 : 0;
325 rate.spe = (val >> 4) & 0xf;
326 rate.he_ltf = cpu_to_le16((val >> 0) & 0xf);
327
328 mt792x_mutex_acquire(dev);
> 329 mt7925_mcu_set_fixed_rate(dev, &rate);
330 mt792x_mutex_release(dev);
331
332 return 0;
333 }
334
335 static int
336 mt7925_fixedrate_get(void *data, u64 *val)
337 {
338 struct mt792x_dev *dev = data;
339
340 *val = dev->fixed_rate;
341
342 return 0;
343 }
> 344 DEFINE_DEBUGFS_ATTRIBUTE(fops_fixedrate, mt7925_fixedrate_get,
345 mt7925_fixedrate_set, "0x%llx\n");
346
347 /* autorate input format (6 hex digits):
348 * 0x[Tag(2)][WCID(2)][En][Mode]
349 * The RA tag is fixed to SET_AUTO_RATE internally.
350 * WCID is 2 nibbles, En and Mode are 1 nibble each.
351 *
352 * [WCID] Wireless Client ID
353 * [En] Auto rate: Enable=1, Disable=0
354 * [Mode] don't care (RA picks the rate)
355 */
356 static int
357 mt7925_autorate_set(void *data, u64 val)
358 {
359 struct mt792x_dev *dev = data;
360 u16 wlan_idx = (val >> 8) & 0xff;
361 bool enable = (val >> 4) & 0xf;
362 u8 mode = val & 0xf;
363
> 364 dev->auto_rate = val;
365
366 mt792x_mutex_acquire(dev);
> 367 mt7925_mcu_set_auto_rate(dev, wlan_idx, enable, mode);
368 mt792x_mutex_release(dev);
369
370 return 0;
371 }
372
373 static int
374 mt7925_autorate_get(void *data, u64 *val)
375 {
376 struct mt792x_dev *dev = data;
377
378 *val = dev->auto_rate;
379
380 return 0;
381 }
> 382 DEFINE_DEBUGFS_ATTRIBUTE(fops_autorate, mt7925_autorate_get,
383 mt7925_autorate_set, "0x%llx\n");
384
385 /* WTBL dump
386 *
387 * Usage:
388 * echo <idx> > /sys/kernel/debug/ieee80211/phy1/mt76/wtbl_idx
389 * cat /sys/kernel/debug/ieee80211/phy1/mt76/get_wtbl
390 *
391 * LMAC WTBL has 36 dwords per entry (connac3x).
392 */
393 #define MT792x_WTBL_LMAC_DWORDS 36
394
395 static int
396 mt7925_wtbl_idx_set(void *data, u64 val)
397 {
398 struct mt792x_dev *dev = data;
399
> 400 dev->wtbl_dump_idx = (u32)val;
401
402 return 0;
403 }
404
405 static int
406 mt7925_wtbl_idx_get(void *data, u64 *val)
407 {
408 struct mt792x_dev *dev = data;
409
410 *val = dev->wtbl_dump_idx;
411
412 return 0;
413 }
414
> 415 DEFINE_DEBUGFS_ATTRIBUTE(fops_wtbl_idx, mt7925_wtbl_idx_get,
416 mt7925_wtbl_idx_set, "%llu\n");
417
418 static int
> 419 mt7925_wtbl_read(struct seq_file *s, void *data)
420 {
421 struct mt792x_dev *dev = dev_get_drvdata(s->private);
422 u32 idx = dev->wtbl_dump_idx;
423 u32 wdu_cr, wducr_val, base;
424 u32 dw0, dw1;
425 int i;
426
427 if (!mt76_is_mmio(&dev->mt76)) {
428 seq_puts(s, "WTBL direct dump not supported on this interface\n");
429 return 0;
430 }
431
432 if (idx >= MT792x_WTBL_SIZE) {
433 seq_printf(s, "Invalid WTBL idx %u (max %u)\n",
434 idx, MT792x_WTBL_SIZE - 1);
435 return 0;
436 }
437
438 mt792x_mutex_acquire(dev);
439
440 /* mt7925_mac_wtbl_lmac_addr() writes WDUCR group selection and
441 * returns the mapped register address for DW 0 of this WCID.
442 */
> 443 base = mt7925_mac_wtbl_lmac_addr(dev, idx, 0);
444 wdu_cr = is_mt7928(&dev->mt76) ? MT7928_WTBLON_TOP_WDUCR :
445 MT7925_WTBLON_TOP_WDUCR;
446 wducr_val = mt76_rr(dev, wdu_cr);
447
448 /* DW0[15:0] + DW1[31:0] encode the link address */
449 dw0 = mt76_rr(dev, base);
450 dw1 = mt76_rr(dev, base + 4);
451
452 seq_printf(s, "Dump WTBL info of WLAN_IDX: %u\n", idx);
453 seq_printf(s, "LMAC WTBL Addr: group:0x%08x=0x%08x addr: 0x%08x\n",
454 wdu_cr, wducr_val, base);
455 seq_printf(s, "LinkAddr: %02x:%02x:%02x:%02x:%02x:%02x\n",
456 dw1 & 0xff, (dw1 >> 8) & 0xff,
457 (dw1 >> 16) & 0xff, (dw1 >> 24) & 0xff,
458 dw0 & 0xff, (dw0 >> 8) & 0xff);
459
460 seq_puts(s, "\nLMAC WTBL raw data:\n");
461 for (i = 0; i < MT792x_WTBL_LMAC_DWORDS; i++)
462 seq_printf(s, "DW%02d: 0x%08x\n", i,
463 mt76_rr(dev, base + i * 4));
464
465 mt792x_mutex_release(dev);
466
467 return 0;
468 }
469
470 static int
> 471 mt7925_vefuse_read(struct seq_file *s, void *data)
472 {
473 struct mt792x_dev *dev = dev_get_drvdata(s->private);
> 474 u16 size = dev->vefuse_cap.size;
475 int off = 0;
476 u8 *buf;
477 int ret = 0;
478
479 if (!dev->vefuse_cap.present || !size) {
480 seq_puts(s, "vefuse: no region in FW image\n");
481 return 0;
482 }
483
484 buf = kzalloc(size, GFP_KERNEL);
485 if (!buf)
486 return -ENOMEM;
487
488 mt792x_mutex_acquire(dev);
489 while (off < (int)size) {
490 u16 chunk = min_t(u16, 512, size - off);
491
> 492 ret = mt7925_mcu_read_vefuse(dev, off, chunk, buf + off);
493 if (ret)
494 break;
495 off += chunk;
496 }
497 mt792x_mutex_release(dev);
498
499 if (ret) {
500 seq_printf(s, "vefuse: read failed at 0x%04x (%d)\n", off, ret);
501 kfree(buf);
502 return 0;
503 }
504
505 seq_printf(s, "vefuse content: %u bytes\n", size);
506 for (off = 0; off < (int)size; off += 16)
507 seq_printf(s, "%04x: %*ph\n", off,
508 (int)min_t(u16, 16, size - off), buf + off);
509
510 kfree(buf);
511
512 return 0;
513 }
514
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 12+ messages in thread* Re: [PATCH v1] wifi: mt76: mt7928: Add debugfs for stats to provide debug purpose
2026-08-24 3:07 [PATCH v1] wifi: mt76: mt7928: Add debugfs for stats to provide debug purpose Eason Lai
2026-08-25 4:00 ` kernel test robot
@ 2026-08-25 7:55 ` kernel test robot
2026-08-27 5:54 ` [PATCH v2] " Eason Lai
2 siblings, 0 replies; 12+ messages in thread
From: kernel test robot @ 2026-08-25 7:55 UTC (permalink / raw)
To: Eason Lai, nbd, lorenzo
Cc: oe-kbuild-all, linux-wireless, linux-mediatek, kun.wu, deren.wu,
sean.wang, quan.zhou, ryder.lee, leon.yen, litien.chang, jb.tsai,
michael.shih, eason.lai
Hi Eason,
kernel test robot noticed the following build errors:
[auto build test ERROR on wireless-next/main]
[also build test ERROR on linus/master next-20260821]
[cannot apply to wireless/main v7.2]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Eason-Lai/wifi-mt76-mt7928-Add-debugfs-for-stats-to-provide-debug-purpose/20260824-110746
base: https://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless-next.git main
patch link: https://lore.kernel.org/r/20260824030746.60706-1-eason.lai%40mediatek.com
patch subject: [PATCH v1] wifi: mt76: mt7928: Add debugfs for stats to provide debug purpose
config: parisc-allyesconfig (https://download.01.org/0day-ci/archive/20260825/202608251526.VgKMT8Lv-lkp@intel.com/config)
compiler: hppa-linux-gcc (GCC) 16.1.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260825/202608251526.VgKMT8Lv-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202608251526.VgKMT8Lv-lkp@intel.com/
All errors (new ones prefixed by >>):
In file included from drivers/net/wireless/mediatek/mt76/mt7996/init.c:13:
>> drivers/net/wireless/mediatek/mt76/mt7996/mcu.h:1000:9: error: redeclaration of enumerator 'UNI_CMD_MIB_DATA'
1000 | UNI_CMD_MIB_DATA,
| ^~~~~~~~~~~~~~~~
In file included from drivers/net/wireless/mediatek/mt76/mt7996/mcu.h:9:
drivers/net/wireless/mediatek/mt76/mt7996/../mt76_connac_mcu.h:1511:9: note: previous definition of 'UNI_CMD_MIB_DATA' with type 'enum <anonymous>'
1511 | UNI_CMD_MIB_DATA,
| ^~~~~~~~~~~~~~~~
vim +/UNI_CMD_MIB_DATA +1000 drivers/net/wireless/mediatek/mt76/mt7996/mcu.h
98686cd21624c7 Shayne Chen 2022-11-22 998
98686cd21624c7 Shayne Chen 2022-11-22 999 enum {
98686cd21624c7 Shayne Chen 2022-11-22 @1000 UNI_CMD_MIB_DATA,
98686cd21624c7 Shayne Chen 2022-11-22 1001 };
98686cd21624c7 Shayne Chen 2022-11-22 1002
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH v2] wifi: mt76: mt7928: Add debugfs for stats to provide debug purpose
2026-08-24 3:07 [PATCH v1] wifi: mt76: mt7928: Add debugfs for stats to provide debug purpose Eason Lai
2026-08-25 4:00 ` kernel test robot
2026-08-25 7:55 ` kernel test robot
@ 2026-08-27 5:54 ` Eason Lai
2026-08-27 8:44 ` [PATCH v3] " Eason Lai
2 siblings, 1 reply; 12+ messages in thread
From: Eason Lai @ 2026-08-27 5:54 UTC (permalink / raw)
To: nbd, lorenzo
Cc: linux-wireless, linux-mediatek, kun.wu, deren.wu, sean.wang,
quan.zhou, ryder.lee, leon.yen, litien.chang, jb.tsai,
michael.shih, eason.lai, kernel test robot
From: Michael Shih <michael.shih@mediatek.com>
Usage:
(1) Connect STA to AP (You may use dhclient CMD to ask for DHCP IP, to use ping to AP)
DHCP IP CMD: dhclient ${WLAN_IF} -v
Example: dhclient wlan0 -v
## (To simulate traffic) Ping 10 packets for every second to AP
ping 192.168.0.XXX -i0.1
(2) (Get stats) Use command, phyX is your WiFi interface:
cat /sys/kernel/debug/ieee80211/phyX/mt76/stats
(3) You will see below stats info:
(STA) connected AP MAC Address = a8:42:a1:46:c4:6b
BssIdx = [0]
StaRecIdx = [1],
RSSI = -25
Link_Speed = 2402000 (kbit/s)
temperature = 40
Tx success = 716
Tx fail to Rcv ACK after retry = 0
Rx Mpdu = 23
Rx Fcs Error = 1 (MPDU)
Rx FIFO full = 0 (MPDU)
TRX_AGG_RANGE[0] = 0 (PPDU)
TRX_AGG_RANGE[1] = 0 (PPDU)
TRX_AGG_RANGE[2] = 0 (PPDU)
TRX_AGG_RANGE[3] = 0 (PPDU)
TRX_AGG_RANGE[4] = 0 (PPDU)
TRX_AGG_RANGE[5] = 0 (PPDU)
TRX_AGG_RANGE[6] = 0 (PPDU)
TRX_AGG_RANGE[7] = 0 (PPDU)
MPDUs in AMPDUs transmitted = 0 (MPDU)
MPDUs in AMPDUs transmitted with ACK reply = 0 (MPDU)
rate1_tx_cnt = 714
rate1_fail_cnt = 223
train_up = 2310
train_down = 3080
is_force_tx_stream = 0
is_force_se_off = 0
max_ampdu_factor = 9
tx_rate_up_penalty = 0
low_traffic_mode = 2
low_traffic_count = 0
low_traffic_dashboard = 3
dynamic_sgi_state = 0
dynamic_sgi_score = 2
dynamic_bw_state = 0
dynamic_gband_256qam_state = 3
vht_non_sp_rate_state = 0
Last TX Rate = 24M, BW20, OFDM, BCC, SPE24
Chip Out TX Power = +16.5 dBm
Cur Rate Index = 0
Rate index[0] --> NSS2_MCS11, BW160/BW8080, SGI, HE_SU, LDPC
Rate index[1] NSS2_MCS9, BW160/BW8080, SGI, HE_SU, LDPC
Rate index[2] NSS2_MCS7, BW160/BW8080, SGI, HE_SU, LDPC
Rate index[3] NSS2_MCS4, BW160/BW8080, SGI, HE_SU, LDPC
Rate index[4] NSS2_MCS3, BW160/BW8080, SGI, HE_SU, LDPC
Rate index[5] NSS1_MCS2, BW160/BW8080, SGI, HE_SU, LDPC
Rate index[6] NSS1_MCS1, BW80, SGI, HE_SU, LDPC
Rate index[7] NSS1_MCS0, BW80, SGI, HE_SU, LDPC
mib state:
=== Rx Related Counters ===
Rx with CRC = 24020 (MPDU)
Rx drop due to out of resource = 0 (MPDU)
Rx Mpdu = 93223 (MPDU)
Rx AMpdu = 668 (PPDU)
Rx PF Drop = 0 (MPDU)
Rx Len Mismatch = 5853 (PPDU)
=== Phy/Timing Related Counters ===
ChannelIdleCnt = 1040434
CCA_NAV_Tx_Time = 18510036
Rx_MDRDY_CNT = 166580 (PPDU)
CCK_MDRDY = 7782
OFDM_MDRDY = 11912731
OFDM_GREEN_MDRDY = 0
Prim CCA Time = 17917650
Sec CCA Time = 14922034
Prim ED Time = 5677528
=== Tx Related Counters(Generic) ===
BeaconTxCnt = 0
Tx 40MHz Cnt = 279 (MPDU)
Tx 80MHz Cnt = 44 (MPDU)
Tx 160MHz Cnt = 712 (MPDU)
=== BSSID[0] Related Counters ===
BA Miss Cnt = 210 (PPDU)
RTS Tx Cnt = 1003 (MPDU)
Frame Retry Cnt = 239 (MPDU)
Frame Retry 2 Cnt = 71 (MPDU)
RTS Retry Cnt = 152 (MPDU)
Ack Failed Cnt = 319 (PPDU)
=== AMPDU Related Counters ===
Tx AMPDU_Pkt_Cnt = 705 (PPDU)
Tx AMPDU_MPDU_Pkt_Cnt = 713 (MPDU)
AMPDU Tx success = 503 (MPDU)
AMPDU Tx fail count = 210 (MPDU), PER=29.4%
Reported-by: kernel test robot <lkp@intel.com>
Closes: https://lore.kernel.org/oe-kbuild-all/202608251101.iQGUrSd0-lkp@intel.com/
Closes: https://lore.kernel.org/oe-kbuild-all/202608251526.VgKMT8Lv-lkp@intel.com/
Signed-off-by: Michael Shih <michael.shih@mediatek.com>
---
v2: (1) Remove redundant code to fix build errors.
(2) Move UNI_CMD_MIB_DATA enum definition from 'mt76/mt76_connac_mcu.h' to
'mt7925/stats.h' to avoid redeclartion with 'mt76/mt7996/mcu.h'
---
.../wireless/mediatek/mt76/mt76_connac_mcu.h | 173 ++++++
.../wireless/mediatek/mt76/mt7925/Makefile | 2 +-
.../wireless/mediatek/mt76/mt7925/debugfs.c | 343 ++++++++++
.../net/wireless/mediatek/mt76/mt7925/stats.c | 585 ++++++++++++++++++
.../net/wireless/mediatek/mt76/mt7925/stats.h | 309 +++++++++
5 files changed, 1411 insertions(+), 1 deletion(-)
create mode 100644 drivers/net/wireless/mediatek/mt76/mt7925/stats.c
create mode 100644 drivers/net/wireless/mediatek/mt76/mt7925/stats.h
diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
index 0ededa569e9a..bf0f105c786e 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
@@ -1483,6 +1483,179 @@ enum {
MT_NIC_CAP_EML_CAP = 0x22,
};
+enum {
+ UNI_CMD_ID_STATISTICS_BASIC = 0,
+ UNI_CMD_ID_STATISTICS_LINK_QUALITY = 1,
+ UNI_CMD_ID_STATISTICS_STA = 2,
+ UNI_CMD_ID_STATISTICS_BUG_REPORT = 3,
+ UNI_CMD_ID_STATISTICS_TX_STATS = 4,
+ UNI_CMD_ID_STATISTICS_ALL_STATS = 5,
+ UNI_CMD_ID_STATISTICS_EML_STATS = 6,
+ UNI_CMD_ID_STATISTICS_REGULAR_STATS = 7,
+ UNI_CMD_ID_STATISTICS_BSS_LINK_QUALITY = 8,
+ UNI_CMD_ID_STATISTICS_ML_CHNL_COND = 9,
+ UNI_CMD_ID_STATISTICS_WTBL = 10,
+ UNI_CMD_ID_STATISTICS_LINK_LAYER_STATS = 128,
+ UNI_CMD_ID_STATISTICS_PPDU_LATENCY = 129,
+ UNI_CMD_ID_STATISTICS_CURRENT_TX_RATE = 130,
+ UNI_CMD_ID_STATISTICS_SET_TX_BITRATE_MONITOR = 131,
+ UNI_CMD_ID_STATISTICS_GET_PREDICT_TX_BITRATE = 132,
+ UNI_CMD_ID_STATISTICS_SET_ATPS_TYPE_RATE_MODE = 133,
+ UNI_CMD_ID_STATISTICS_SET_BEACON_REPORT = 134,
+ UNI_CMD_ID_STATISTICS_BSS_CURRENT_TX_RATE = 135,
+ UNI_CMD_ID_STATISTICS_GET_BSS_PREDICT_TX_BITRATE = 136,
+ UNI_CMD_ID_STATISTICS_MAX_NUM
+};
+
+enum mt7925_mib_counter {
+ UNI_CMD_MIB_CNT_RX_FCS_ERR = 0,
+ UNI_CMD_MIB_CNT_RX_FIFO_OVERFLOW = 1,
+ UNI_CMD_MIB_CNT_RX_MPDU = 2,
+ UNI_CMD_MIB_CNT_AMPDU_RX_COUNT = 3,
+ UNI_CMD_MIB_CNT_RX_TOTAL_BYTE = 4,
+ UNI_CMD_MIB_CNT_RX_VALID_AMPDU_SF = 5,
+ UNI_CMD_MIB_CNT_RX_VALID_BYTE = 6,
+ UNI_CMD_MIB_CNT_CHANNEL_IDLE = 7,
+ UNI_CMD_MIB_CNT_VEC_DROP = 8,
+ UNI_CMD_MIB_CNT_DELIMITER_FAIL = 9,
+ UNI_CMD_MIB_CNT_VEC_MISMATCH = 10,
+ UNI_CMD_MIB_CNT_MDRDY = 11,
+ UNI_CMD_MIB_CNT_RX_CCK_MDRDY_TIME = 12,
+ UNI_CMD_MIB_CNT_RX_OFDM_LG_MIXED_MDRDY_TIME = 13,
+ UNI_CMD_MIB_CNT_RX_OFDM_GREEN_MDRDY_TIME = 14,
+ UNI_CMD_MIB_CNT_PF_DROP = 15,
+ UNI_CMD_MIB_CNT_LEN_MISMATCH = 16,
+ UNI_CMD_MIB_CNT_P_CCA_TIME = 17,
+ UNI_CMD_MIB_CNT_S_CCA_TIME = 18,
+ UNI_CMD_MIB_CNT_CCA_NAV_TX_TIME = 19,
+ UNI_CMD_MIB_CNT_P_ED_TIME = 20,
+ UNI_CMD_MIB_CNT_BCN_TX = 21,
+ UNI_CMD_MIB_CNT_TX_BW_20MHZ = 22,
+ UNI_CMD_MIB_CNT_TX_BW_40MHZ = 23,
+ UNI_CMD_MIB_CNT_TX_BW_80MHZ = 24,
+ UNI_CMD_MIB_CNT_TX_BW_160MHZ = 25,
+ UNI_CMD_RMAC_CNT_OBSS_AIRTIME = 26,
+ UNI_CMD_RMAC_CNT_NONWIFI_AIRTIME = 27,
+ UNI_CMD_MIB_CNT_TX_DUR_CNT = 28,
+ UNI_CMD_MIB_CNT_RX_DUR_CNT = 29,
+ UNI_CMD_MIB_CNT_BA_CNT = 30,
+ UNI_CMD_MIB_CNT_MAC2PHY_TX_TIME = 31,
+ UNI_CMD_MIB_CNT_RX_OUT_OF_RANGE_COUNT = 32,
+ UNI_CMD_MIB_CNT_IBF_TX = 33,
+ UNI_CMD_MIB_CNT_EBF_TX = 34,
+ UNI_CMD_MIB_CNT_MUBF_TX = 35,
+ UNI_CMD_MIB_CNT_RX_BF_VHTFBK = 36,
+ UNI_CMD_MIB_CNT_RX_BF_HTFBK = 37,
+ UNI_CMD_MIB_CNT_RX_BF_HEFBK = 38,
+ UNI_CMD_MIB_CNT_BFEE_TXFBK_BFPOLL_TRI = 39,
+ UNI_CMD_MIB_CNT_BFEE_TXFBK_TRI = 40,
+ UNI_CMD_MIB_CNT_BFEE_RX_FBKCQI = 41,
+ UNI_CMD_MIB_CNT_BFEE_RX_NDP = 42,
+ UNI_CMD_MIB_CNT_RX_BF_IBF_UPT = 43,
+ UNI_CMD_MIB_CNT_RX_BF_EBF_UPT = 44,
+ UNI_CMD_MIB_CNT_BFEE_SP_ABORT = 45,
+ UNI_CMD_MIB_CNT_BFEE_TB_LEN_ERR = 46,
+ UNI_CMD_MIB_CNT_BFEE_TXFBK_MUTE = 47,
+ UNI_CMD_MIB_CNT_BFEE_TMAC_ABORT = 48,
+ UNI_CMD_MIB_CNT_BFEE_TXFBK_CPL = 49,
+ UNI_CMD_MIB_CNT_BFEE_COANT_BLKTX = 50,
+ UNI_CMD_MIB_CNT_BFEE_FBK_SEG = 51,
+ UNI_CMD_MIB_CNT_NAV_TIME = 52,
+ UNI_CMD_MIB_CNT_TX_ABORT_CNT1 = 53,
+ UNI_CMD_MIB_CNT_TX_ABORT_CNT0 = 54,
+ UNI_CMD_MIB_CNT_TX_ABORT_CNT3 = 55,
+ UNI_CMD_MIB_CNT_TX_ABORT_CNT2 = 56,
+ UNI_CMD_MIB_CNT_BSS0_RTS_TX_CNT = 57,
+ UNI_CMD_MIB_CNT_BSS1_RTS_TX_CNT = 58,
+ UNI_CMD_MIB_CNT_BSS2_RTS_TX_CNT = 59,
+ UNI_CMD_MIB_CNT_BSS3_RTS_TX_CNT = 60,
+ UNI_CMD_MIB_CNT_BSS0_RTS_RETRY = 61,
+ UNI_CMD_MIB_CNT_BSS1_RTS_RETRY = 62,
+ UNI_CMD_MIB_CNT_BSS2_RTS_RETRY = 63,
+ UNI_CMD_MIB_CNT_BSS3_RTS_RETRY = 64,
+ UNI_CMD_MIB_CNT_BSS0_BA_MISS = 65,
+ UNI_CMD_MIB_CNT_BSS1_BA_MISS = 66,
+ UNI_CMD_MIB_CNT_BSS2_BA_MISS = 67,
+ UNI_CMD_MIB_CNT_BSS3_BA_MISS = 68,
+ UNI_CMD_MIB_CNT_BSS0_ACK_FAIL = 69,
+ UNI_CMD_MIB_CNT_BSS1_ACK_FAIL = 70,
+ UNI_CMD_MIB_CNT_BSS2_ACK_FAIL = 71,
+ UNI_CMD_MIB_CNT_BSS3_ACK_FAIL = 72,
+ UNI_CMD_MIB_CNT_BSS0_FRAME_RETRY = 73,
+ UNI_CMD_MIB_CNT_BSS1_FRAME_RETRY = 74,
+ UNI_CMD_MIB_CNT_BSS2_FRAME_RETRY = 75,
+ UNI_CMD_MIB_CNT_BSS3_FRAME_RETRY = 76,
+ UNI_CMD_MIB_CNT_BSS0_FRAME_RETRY_2 = 77,
+ UNI_CMD_MIB_CNT_BSS1_FRAME_RETRY_2 = 78,
+ UNI_CMD_MIB_CNT_BSS2_FRAME_RETRY_2 = 79,
+ UNI_CMD_MIB_CNT_BSS3_FRAME_RETRY_2 = 80,
+ UNI_CMD_MIB_CNT_RX_A1_SEARCH = 81,
+ UNI_CMD_MIB_CNT_RX_DROP_MPDU = 82,
+ UNI_CMD_MIB_CNT_RX_UNWANTED = 83,
+ UNI_CMD_MIB_CNT_RX_FCS_OK = 84,
+ UNI_CMD_MIB_CNT_SU_TX_OK = 85,
+ UNI_CMD_MIB_CNT_TX_FULL_BW = 86,
+ UNI_CMD_MIB_CNT_TX_AUTO_BW = 87,
+ UNI_CMD_MIB_CNT_CCA_SAMPLE_IDLE = 88,
+ UNI_CMD_MIB_CNT_CCA_SAMPLE_ACTIVE = 89,
+ UNI_CMD_MIB_CNT_CCA_TIME = 90,
+ UNI_CMD_MIB_CNT_S_20BW_CCA_TIME = 91,
+ UNI_CMD_MIB_CNT_S_40BW_CCA_TIME = 92,
+ UNI_CMD_MIB_CNT_S_80BW_CCA_TIME = 93,
+ UNI_CMD_MIB_CNT_S_160BW_CCA_TIME = 94,
+ UNI_CMD_MIB_CNT_S_P20BW_0_ED_TIME = 95,
+ UNI_CMD_MIB_CNT_S_P20BW_1_ED_TIME = 96,
+ UNI_CMD_MIB_CNT_S_P20BW_2_ED_TIME = 97,
+ UNI_CMD_MIB_CNT_S_P20BW_3_ED_TIME = 98,
+ UNI_CMD_MIB_CNT_S_P20BW_4_ED_TIME = 99,
+ UNI_CMD_MIB_CNT_S_P20BW_5_ED_TIME = 100,
+ UNI_CMD_MIB_CNT_S_P20BW_6_ED_TIME = 101,
+ UNI_CMD_MIB_CNT_S_P20BW_7_ED_TIME = 102,
+ UNI_CMD_MIB_CNT_S_P20BW_8_ED_TIME = 103,
+ UNI_CMD_MIB_CNT_S_P20BW_9_ED_TIME = 104,
+ UNI_CMD_MIB_CNT_S_P20BW_10_ED_TIME = 105,
+ UNI_CMD_MIB_CNT_S_P20BW_11_ED_TIME = 106,
+ UNI_CMD_MIB_CNT_S_P20BW_12_ED_TIME = 107,
+ UNI_CMD_MIB_CNT_S_P20BW_13_ED_TIME = 108,
+ UNI_CMD_MIB_CNT_S_P20BW_14_ED_TIME = 109,
+ UNI_CMD_MIB_CNT_S_P20BW_15_ED_TIME = 110,
+ UNI_CMD_MIB_CNT_TX_BW_320MHZ = 111,
+ UNI_CMD_MIB_CNT_TX_DDLMT_RNG0 = 112, /* Rng0~Rng4 112~116 */
+ UNI_CMD_MIB_CNT_BSS0_FRAME_RETRY_3 = 117, /* 117~120 */
+ UNI_CMD_MIB_CNT_BSS0_TX = 121, /* 121~124 */
+ UNI_CMD_MIB_CNT_BSS0_TX_DATA = 125, /* 125~128 */
+ UNI_CMD_MIB_CNT_BSS0_TX_BYTE = 129, /* 129~132 */
+ UNI_CMD_MIB_CNT_RX_OK_BSS0 = 133, /* 133~136 */
+ UNI_CMD_MIB_CNT_RX_BYTE_BSS0 = 137, /* 137~140 */
+ UNI_CMD_MIB_CNT_RX_DATA_BSS0 = 141, /* 141~144 */
+ UNI_CMD_MIB_CNT_MBSS0_TX_OK = 145, /* 145~160 */
+ UNI_CMD_MIB_CNT_MBSS0_TX_BYTE = 161, /* 161~176 */
+ UNI_CMD_MIB_CNT_RX_OK_MBSS0 = 177, /* 177~192 */
+ UNI_CMD_MIB_CNT_RX_BYTE_MBSS0 = 193, /* 193~208 */
+ UNI_CMD_MIB_CNT_AMPDU = 209,
+ UNI_CMD_MIB_CNT_AMPDU_MPDU = 210,
+ UNI_CMD_MIB_CNT_AMPDU_ACKED = 211,
+ UNI_CMD_MIB_CNT_MAX_NUM
+};
+
+enum ENUM_UNI_CMD_GET_STATISTICS_TAG {
+ UNI_CMD_GET_STATISTICS_TAG_BASIC = 0,
+ UNI_CMD_GET_STATISTICS_TAG_LINK_QUALITY = 1,
+ UNI_CMD_GET_STATISTICS_TAG_STA = 2,
+ UNI_CMD_GET_STATISTICS_TAG_BUG_REPORT = 3,
+ UNI_CMD_GET_STATISTICS_TAG_EML_STATS = 6,
+ UNI_CMD_GET_STATISTICS_TAG_REGULAR_STATS = 7,
+ UNI_CMD_GET_STATISTICS_TAG_BSS_LINK_QUALITY = 8,
+ UNI_CMD_GET_STATISTICS_TAG_GET_ML_CHNL_COND = 9,
+ UNI_CMD_GET_STATISTICS_TAG_STAT_WTBL = 10,
+ UNI_CMD_GET_STATISTICS_TAG_LINK_LAYER_STATS = 0x80,
+ UNI_CMD_GET_STATISTICS_TAG_PPDU_LATENCY,
+ UNI_CMD_GET_STATISTICS_TAG_CURRENT_TX_RATE,
+ UNI_CMD_GET_STATISTICS_TAG_BEACON_REPORT = 0x86,
+ UNI_CMD_GET_STATISTICS_TAG_BSS_CURRENT_TX_RATE = 0x87,
+ UNI_CMD_GET_STATISTICS_TAG_BSS_PRED_TX_BITRATE = 0x88,
+};
+
#define UNI_WOW_DETECT_TYPE_MAGIC BIT(0)
#define UNI_WOW_DETECT_TYPE_ANY BIT(1)
#define UNI_WOW_DETECT_TYPE_DISCONNECT BIT(2)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/Makefile b/drivers/net/wireless/mediatek/mt76/mt7925/Makefile
index f9dcc0bba393..3863a89b8974 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/Makefile
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/Makefile
@@ -4,7 +4,7 @@ obj-$(CONFIG_MT7925_COMMON) += mt7925-common.o
obj-$(CONFIG_MT7925E) += mt7925e.o
obj-$(CONFIG_MT7925U) += mt7925u.o
-mt7925-common-y := mac.o mcu.o regd.o main.o init.o debugfs.o nan.o
+mt7925-common-y := mac.o mcu.o regd.o main.o init.o debugfs.o nan.o stats.o
mt7925-common-$(CONFIG_NL80211_TESTMODE) += testmode.o
mt7925e-y := pci.o pci_mac.o pci_mcu.o
mt7925u-y := usb.o
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c b/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c
index d01ff49de47a..d96a1979a196 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c
@@ -3,6 +3,7 @@
#include "mt7925.h"
#include "mcu.h"
+#include "stats.h"
static int
mt7925_reg_set(void *data, u64 val)
@@ -286,6 +287,345 @@ static int mt7925_chip_reset(void *data, u64 val)
DEFINE_DEBUGFS_ATTRIBUTE(fops_reset, NULL, mt7925_chip_reset, "%lld\n");
+static const u32 stats_query_mib_counters[] = {
+ UNI_CMD_MIB_CNT_RX_FCS_ERR,
+ UNI_CMD_MIB_CNT_RX_FIFO_OVERFLOW,
+ UNI_CMD_MIB_CNT_RX_MPDU,
+ UNI_CMD_MIB_CNT_AMPDU_RX_COUNT,
+ UNI_CMD_MIB_CNT_PF_DROP,
+ UNI_CMD_MIB_CNT_LEN_MISMATCH,
+ UNI_CMD_MIB_CNT_CHANNEL_IDLE,
+ UNI_CMD_MIB_CNT_CCA_NAV_TX_TIME,
+ UNI_CMD_MIB_CNT_MDRDY,
+ UNI_CMD_MIB_CNT_RX_CCK_MDRDY_TIME,
+ UNI_CMD_MIB_CNT_RX_OFDM_LG_MIXED_MDRDY_TIME,
+ UNI_CMD_MIB_CNT_RX_OFDM_GREEN_MDRDY_TIME,
+ UNI_CMD_MIB_CNT_P_CCA_TIME,
+ UNI_CMD_MIB_CNT_S_CCA_TIME,
+ UNI_CMD_MIB_CNT_P_ED_TIME,
+ UNI_CMD_MIB_CNT_BCN_TX,
+ UNI_CMD_MIB_CNT_TX_BW_40MHZ,
+ UNI_CMD_MIB_CNT_TX_BW_80MHZ,
+ UNI_CMD_MIB_CNT_TX_BW_160MHZ,
+ UNI_CMD_MIB_CNT_BSS0_BA_MISS,
+ UNI_CMD_MIB_CNT_BSS0_RTS_TX_CNT,
+ UNI_CMD_MIB_CNT_BSS0_FRAME_RETRY,
+ UNI_CMD_MIB_CNT_BSS0_FRAME_RETRY_2,
+ UNI_CMD_MIB_CNT_BSS0_RTS_RETRY,
+ UNI_CMD_MIB_CNT_BSS0_ACK_FAIL,
+ UNI_CMD_MIB_CNT_AMPDU,
+ UNI_CMD_MIB_CNT_AMPDU_MPDU,
+ UNI_CMD_MIB_CNT_AMPDU_ACKED,
+};
+
+/* Dump the whole firmware auto-rate table and mark the entry currently in use
+ * with "-->"
+ */
+static void
+mt7925_dump_auto_rate_table(struct seq_file *s,
+ const struct mt7925_wtbl_rate *wr)
+{
+ u8 fcap = wr->fcap;
+ u8 rate_idx = wr->rate_idx;
+ u8 cbrn = wr->cbrn;
+ int i;
+
+ seq_printf(s, "%-22s Cur Rate Index = %u\n", " ", rate_idx);
+
+ for (i = 0; i < MT7925_AUTO_RATE_NUM; i++) {
+ u16 rate_code = le16_to_cpu(wr->rate_code[i]);
+ u8 txmode = MT7925_HW_RATE_TO_MODE(rate_code);
+ u8 rate = MT7925_HW_RATE_TO_MCS(rate_code);
+ u8 nsts = MT7925_HW_RATE_TO_NSS(rate_code) + 1;
+ u8 stbc = MT7925_HW_RATE_TO_STBC(rate_code);
+ u8 dcm = MT7925_HW_RATE_TO_DCM(rate_code);
+ u8 ersu106t = MT7925_HW_RATE_TO_106T(rate_code);
+ u8 sgi = mt7925_wtbl_get_sgi(wr, txmode, fcap);
+ u8 ldpc = mt7925_wtbl_get_ldpc(wr, txmode);
+ const char *mode_str = "N/A";
+
+ if (dcm)
+ rate = MT7925_HW_RATE_UNMASK_DCM(rate);
+ if (ersu106t)
+ rate = MT7925_HW_RATE_UNMASK_106T(rate);
+
+ if (txmode < ARRAY_SIZE(mt7925_tx_mode_str))
+ mode_str = mt7925_tx_mode_str[txmode];
+
+ seq_printf(s, "Rate index[%d] %s", i,
+ rate_idx == i ? "--> " : " ");
+
+ /* rate/MCS field */
+ if (txmode == MT7925_TX_RATE_MODE_CCK)
+ seq_printf(s, "%s, ",
+ mt7925_tx_rate_cck_str[rate & 0x3]);
+ else if (txmode == MT7925_TX_RATE_MODE_OFDM)
+ seq_printf(s, "%s, ", mt7925_hw_rate_ofdm_str(rate));
+ else if (txmode == MT7925_TX_RATE_MODE_HTMIX ||
+ txmode == MT7925_TX_RATE_MODE_HTGF)
+ seq_printf(s, "MCS%d, ", rate);
+ else
+ seq_printf(s, "%s%d_MCS%d, ",
+ stbc ? "NSTS" : "NSS", nsts, rate);
+
+ /* bandwidth: mirror the CCK/OFDM -> BW20 and cbrn handling */
+ if (txmode == MT7925_TX_RATE_MODE_CCK ||
+ txmode == MT7925_TX_RATE_MODE_OFDM)
+ seq_printf(s, "%s, ", mt7925_tx_rate_bw_str[0]);
+ else if (i > cbrn)
+ seq_printf(s, "%s, ",
+ fcap < 5 ?
+ (fcap > MT7925_BW_20 ?
+ mt7925_tx_rate_bw_str[fcap - 1] :
+ mt7925_tx_rate_bw_str[fcap]) :
+ mt7925_tx_rate_bw_str[5]);
+ else
+ seq_printf(s, "%s, ",
+ fcap < 5 ? mt7925_tx_rate_bw_str[fcap] :
+ mt7925_tx_rate_bw_str[5]);
+
+ /* GI / preamble */
+ if (txmode == MT7925_TX_RATE_MODE_CCK)
+ seq_printf(s, "%s, ", rate < 4 ? "LP" : "SP");
+ else if (txmode == MT7925_TX_RATE_MODE_OFDM)
+ ; /* OFDM has no GI/preamble to print */
+ else if (txmode == MT7925_TX_RATE_MODE_HTMIX ||
+ txmode == MT7925_TX_RATE_MODE_HTGF ||
+ txmode == MT7925_TX_RATE_MODE_VHT ||
+ txmode == MT7925_TX_RATE_MODE_PLR)
+ seq_printf(s, "%s, ", sgi == 0 ? "LGI" : "SGI");
+ else
+ seq_printf(s, "%s, ",
+ sgi == 0 ? "SGI" :
+ (sgi == 1 ? "MGI" : "LGI"));
+
+ /* mode, DCM, 106t, STBC, coding */
+ seq_printf(s, "%s%s%s%s%s\n",
+ mode_str,
+ dcm ? ", DCM" : "", ersu106t ? ", 106t" : "",
+ stbc ? ", STBC, " : ", ",
+ (ldpc == 0 ||
+ txmode == MT7925_TX_RATE_MODE_CCK ||
+ txmode == MT7925_TX_RATE_MODE_OFDM) ?
+ "BCC" : "LDPC");
+ }
+}
+
+static int mt792x_stats(struct seq_file *s, void *data)
+{
+ s8 rssi = 0;
+ u8 band_idx = 0;
+ bool wtbl_rate_valid = false;
+ int ret = 0;
+ int i = 0;
+ u32 stats_value = 0;
+ u32 tx_link_speed_kbps = 0;
+ u64 tx_total = 0;
+ u64 tx_fail = 0;
+ u64 tx_per = 0;
+ /* UNI_CMD_MIB_DATA */
+ u64 mib_values[ARRAY_SIZE(stats_query_mib_counters)];
+ struct mt792x_dev *dev = dev_get_drvdata(s->private);
+ struct mt7925_wtbl_rate wtbl_rate;
+ struct mt7925_sta_stats *stats = NULL;
+
+ band_idx = dev->mphy.band_idx;
+
+ stats = kzalloc(sizeof(*stats), GFP_KERNEL);
+ if (!stats)
+ return -ENOMEM;
+
+ /* Get STA_STATS */
+ mt792x_mutex_acquire(dev);
+ ret = mt792x_mcu_get_stat(dev, stats);
+ mt792x_mutex_release(dev);
+ if (ret)
+ goto out;
+
+ /* Get MIB_STATS */
+ mt792x_mutex_acquire(dev);
+ ret = mt7925_mcu_get_mib_info(dev, band_idx, stats_query_mib_counters,
+ ARRAY_SIZE(stats_query_mib_counters), mib_values);
+ mt792x_mutex_release(dev);
+ if (ret)
+ goto out;
+
+ /* Get Link_Speed and RSSI */
+ mt792x_mutex_acquire(dev);
+ ret = mt7925_mcu_get_link_quality(dev, stats->bss_idx, &rssi, &tx_link_speed_kbps);
+ mt792x_mutex_release(dev);
+ if (ret)
+ goto out;
+
+ /* Get the firmware auto-rate table (best effort; do not abort the
+ * whole stats dump if the WTBL query is not available).
+ */
+ mt792x_mutex_acquire(dev);
+ if (!mt792x_mcu_get_wtbl_rate(dev, stats->wtbl_idx, &wtbl_rate))
+ wtbl_rate_valid = true;
+ mt792x_mutex_release(dev);
+
+ /* Print STA_STATS info */
+ seq_printf(s, "(STA) connected AP MAC Address = %pM\n", stats->mac_addr);
+ seq_printf(s, "%-22s BssIdx = [%u]\n", " ", stats->bss_idx);
+ seq_printf(s, "%-22s StaRecIdx = [%u]\n",
+ " ", stats->sta_idx);
+ seq_printf(s, "%-22s RSSI = %d\n", " ", rssi);
+ seq_printf(s, "%-22s Link_Speed = %u (kbit/s)\n", " ", tx_link_speed_kbps);
+
+ seq_printf(s, "%-22s temperature = %u\n",
+ " ", stats->temperature);
+
+ stats_value = le32_to_cpu(stats->transmit_count) - le32_to_cpu(stats->transmit_fail_count);
+ seq_printf(s, "%-22s Tx success = %u\n",
+ " ", stats_value);
+
+ stats_value = le32_to_cpu(stats->tx_fail_count) - le32_to_cpu(stats->tx_life_timeout_count);
+ seq_printf(s, "%-22s Tx fail to Rcv ACK after retry = %u\n",
+ " ", stats_value);
+
+ seq_printf(s, "%-22s Rx Mpdu = %u\n",
+ " ", le32_to_cpu(stats->mib[band_idx].rx_mpdu_cnt));
+
+ seq_printf(s, "%-22s Rx Fcs Error = %u (MPDU)\n",
+ " ", le32_to_cpu(stats->mib[band_idx].fcs_error));
+ seq_printf(s, "%-22s Rx FIFO full = %u (MPDU)\n",
+ " ", le32_to_cpu(stats->mib[band_idx].rx_fifo_full));
+
+ for (i = 0; i < STAT_MIB_CNT_TRX_AGG_RANGE_MAX_NUM; i++)
+ seq_printf(s, "%-22s TRX_AGG_RANGE[%d] = %u (PPDU)\n",
+ " ", i, le32_to_cpu(stats->mib[band_idx].tx_range_ampdu_cnt[i]));
+
+ seq_printf(s, "%-22s MPDUs in AMPDUs transmitted = %u (MPDU)\n",
+ " ", le32_to_cpu(stats->mib[band_idx].ampdu_tx_sf_cnt));
+ seq_printf(s, "%-22s MPDUs in AMPDUs transmitted with ACK reply = %u (MPDU)\n",
+ " ", le32_to_cpu(stats->mib[band_idx].ampdu_tx_ack_sf_cnt));
+ seq_printf(s, "%-22s rate1_tx_cnt = %u\n",
+ " ", le32_to_cpu(stats->rate1_tx_cnt));
+ seq_printf(s, "%-22s rate1_fail_cnt = %u\n",
+ " ", le32_to_cpu(stats->rate1_fail_cnt));
+ seq_printf(s, "%-22s train_up = %u\n",
+ " ", le16_to_cpu(stats->train_up));
+ seq_printf(s, "%-22s train_down = %u\n",
+ " ", le16_to_cpu(stats->train_down));
+ seq_printf(s, "%-22s is_force_tx_stream = %u\n",
+ " ", stats->is_force_tx_stream);
+ seq_printf(s, "%-22s is_force_se_off = %u\n",
+ " ", stats->is_force_se_off);
+ seq_printf(s, "%-22s max_ampdu_factor = %u\n",
+ " ", stats->max_ampdu_factor);
+ seq_printf(s, "%-22s tx_rate_up_penalty = %u\n",
+ " ", stats->tx_rate_up_penalty);
+ seq_printf(s, "%-22s low_traffic_mode = %u\n",
+ " ", stats->low_traffic_mode);
+ seq_printf(s, "%-22s low_traffic_count = %u\n",
+ " ", stats->low_traffic_count);
+ seq_printf(s, "%-22s low_traffic_dashboard = %u\n",
+ " ", stats->low_traffic_dashboard);
+ seq_printf(s, "%-22s dynamic_sgi_state = %u\n",
+ " ", stats->dynamic_sgi_state);
+ seq_printf(s, "%-22s dynamic_sgi_score = %u\n",
+ " ", stats->dynamic_sgi_score);
+ seq_printf(s, "%-22s dynamic_bw_state = %u\n",
+ " ", stats->dynamic_bw_state);
+ seq_printf(s, "%-22s dynamic_gband_256qam_state = %u\n",
+ " ", stats->dynamic_gband_256qam_state);
+ seq_printf(s, "%-22s vht_non_sp_rate_state = %u\n",
+ " ", stats->vht_non_sp_rate_state);
+
+ /* Decode the TX Vector BBP latch to show the current TX MCS rate */
+ if (band_idx < MT7925_STA_STATS_BAND_NUM)
+ mt7925_print_last_tx_rate(s, stats->tx_vector[band_idx].txv);
+
+ /* Dump the whole firmware auto-rate table and mark the entry currently */
+ if (wtbl_rate_valid)
+ mt7925_dump_auto_rate_table(s, &wtbl_rate);
+
+ seq_puts(s, "\nmib state:\n");
+ /* ===Rx Related Counters=== */
+ seq_puts(s, "=== Rx Related Counters ===\n");
+ seq_printf(s, "%-22s Rx with CRC = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_RX_FCS_ERR]);
+ seq_printf(s, "%-22s Rx drop due to out of resource = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_RX_FIFO_OVERFLOW]);
+ seq_printf(s, "%-22s Rx Mpdu = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_RX_MPDU]);
+ seq_printf(s, "%-22s Rx AMpdu = %llu (PPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_AMPDU_RX_COUNT]);
+ seq_printf(s, "%-22s Rx PF Drop = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_PF_DROP]);
+ seq_printf(s, "%-22s Rx Len Mismatch = %llu (PPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_LEN_MISMATCH]);
+
+ /* ===Phy/Timing Related Counters=== */
+ seq_puts(s, "\n=== Phy/Timing Related Counters ===\n");
+ seq_printf(s, "%-22s ChannelIdleCnt = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_CHANNEL_IDLE]);
+ seq_printf(s, "%-22s CCA_NAV_Tx_Time = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_CCA_NAV_TX_TIME]);
+ seq_printf(s, "%-22s Rx_MDRDY_CNT = %llu (PPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_MDRDY]);
+ seq_printf(s, "%-22s CCK_MDRDY = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_RX_CCK_MDRDY_TIME]);
+ seq_printf(s, "%-22s OFDM_MDRDY = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_RX_OFDM_LG_MIXED_MDRDY_TIME]);
+ seq_printf(s, "%-22s OFDM_GREEN_MDRDY = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_RX_OFDM_GREEN_MDRDY_TIME]);
+ seq_printf(s, "%-22s Prim CCA Time = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_P_CCA_TIME]);
+ seq_printf(s, "%-22s Sec CCA Time = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_S_CCA_TIME]);
+ seq_printf(s, "%-22s Prim ED Time = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_P_ED_TIME]);
+
+ /* ===Tx Related Counters(Generic)=== */
+ seq_puts(s, "\n=== Tx Related Counters(Generic) ===\n");
+ seq_printf(s, "%-22s BeaconTxCnt = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_BCN_TX]);
+ seq_printf(s, "%-22s Tx 40MHz Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_TX_BW_40MHZ]);
+ seq_printf(s, "%-22s Tx 80MHz Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_TX_BW_80MHZ]);
+ seq_printf(s, "%-22s Tx 160MHz Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_TX_BW_160MHZ]);
+
+ /* ===BSSID[0] Related Counters=== */
+ seq_puts(s, "\n=== BSSID[0] Related Counters ===\n");
+ seq_printf(s, "%-22s BA Miss Cnt = %llu (PPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_BSS0_BA_MISS]);
+ seq_printf(s, "%-22s RTS Tx Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_BSS0_RTS_TX_CNT]);
+ seq_printf(s, "%-22s Frame Retry Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_BSS0_FRAME_RETRY]);
+ seq_printf(s, "%-22s Frame Retry 2 Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_BSS0_FRAME_RETRY_2]);
+ seq_printf(s, "%-22s RTS Retry Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_BSS0_RTS_RETRY]);
+ seq_printf(s, "%-22s Ack Failed Cnt = %llu (PPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_BSS0_ACK_FAIL]);
+
+ /* ===AMPDU Related Counters=== */
+ seq_puts(s, "\n=== AMPDU Related Counters ===\n");
+ seq_printf(s, "%-22s Tx AMPDU_Pkt_Cnt = %llu (PPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_AMPDU]);
+ seq_printf(s, "%-22s Tx AMPDU_MPDU_Pkt_Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_AMPDU_MPDU]);
+ seq_printf(s, "%-22s AMPDU Tx success = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_AMPDU_ACKED]);
+
+ tx_total = mib_values[QUERY_MIB_CNT_AMPDU_MPDU];
+ tx_fail = mib_values[QUERY_MIB_CNT_AMPDU_MPDU] - mib_values[QUERY_MIB_CNT_AMPDU_ACKED];
+ tx_per = tx_total == 0 ? 0 : (1000 * (tx_fail)) / tx_total;
+ seq_printf(s, "%-22s AMPDU Tx fail count = %llu (MPDU), PER=%llu.%1llu%%\n",
+ " ",
+ tx_fail,
+ tx_per / 10, tx_per % 10);
+
+out:
+ kfree(stats);
+ return ret;
+}
+
int mt7925_init_debugfs(struct mt792x_dev *dev)
{
struct dentry *dir;
@@ -313,5 +653,8 @@ int mt7925_init_debugfs(struct mt792x_dev *dev)
mt792x_pm_stats);
debugfs_create_file("deep-sleep", 0600, dir, dev, &fops_ds);
+ debugfs_create_devm_seqfile(dev->mt76.dev, "stats", dir,
+ mt792x_stats);
+
return 0;
}
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/stats.c b/drivers/net/wireless/mediatek/mt76/mt7925/stats.c
new file mode 100644
index 000000000000..9a47953062bc
--- /dev/null
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/stats.c
@@ -0,0 +1,585 @@
+// SPDX-License-Identifier: BSD-3-Clause-Clear
+/* Copyright (C) 2026 MediaTek Inc. */
+
+#include <linux/firmware.h>
+#include <linux/fs.h>
+#include "mt7925.h"
+#include "mcu.h"
+#include "mac.h"
+#include "stats.h"
+
+#define MT7925_STA_STATS_VERSION 1
+#define MT7925_STA_STATS_VALID 1U
+
+#define MT7925_MIB_MAX_CNT 32
+#define MT7925_MIB_CNT_MAX_NUM 512 /* firmware UNI_CMD_MIB_CNT_MAX_NUM upper bound */
+
+#define MT7925_LQ_BAND_NUM 4 /* Link quality band */
+
+const char * const mt7925_tx_mode_str[] = {
+ "CCK", "OFDM", "MM", "GF", "VHT", "PLR",
+ "PLRP", "ALR", "HE_SU", "HE_ER", "HE_TRIG", "HE_MU",
+ "EHT_ER", "TOF", "EHT_TRIG", "EHT_MU"
+};
+
+const char * const mt7925_tx_rate_cck_str[] = {
+ "1M", "2M", "5.5M", "11M", "N/A"
+};
+
+const char * const mt7925_tx_rate_bw_str[] = {
+ "BW20", "BW40", "BW80", "BW160/BW8080", "BW320", "N/A"
+};
+
+struct mt7925_sta_stats_req {
+ u8 rsv[4];
+ __le16 tag;
+ __le16 len;
+ u8 sta_rec_idx;
+ u8 rsv1;
+ u8 lls_read_clear;
+ u8 reset_counter;
+} __packed;
+
+struct mt7925_sta_stats_event {
+ u8 rsv[4];
+ __le16 tag;
+ __le16 len;
+ struct mt7925_sta_stats stats;
+} __packed;
+
+/* WTBL auto-rate request TLV, mirror Gen4m struct UNI_CMD_STAT_WTBL.
+ * The RA tag reuses UNI_CMD_ID_STATISTICS_WTBL (10).
+ */
+struct mt7925_wtbl_rate_req {
+ u8 rsv[4];
+ __le16 tag;
+ __le16 len;
+ __le16 wlan_idx;
+ __le16 rsv1;
+} __packed;
+
+/* WTBL auto-rate event TLV. Firmware answers cmd tag 10 with event tag 12. */
+struct mt7925_wtbl_rate_event {
+ u8 rsv[4];
+ struct mt7925_wtbl_rate rate;
+} __packed;
+
+/* fixed field, mirror UNI_(CMD|EVENT)_MIB_INFO */
+struct mt7925_mib_hdr {
+ u8 band_idx;
+ u8 rsv[3];
+} __packed;
+
+/* One requested/returned MIB counter, mirror UNI_(CMD|EVENT)_MIB_DATA_T. */
+struct mt7925_mib_data {
+ __le16 tag; /* UNI_(CMD|EVENT)_MIB_DATA = 0 */
+ __le16 len;
+ __le32 counter; /* MIB counter ID (enum mt7925_mib_counter) */
+ __le64 data; /* only valid on event; ignored on cmd */
+} __packed;
+
+/* per-band link quality entry, mirror UNI_LINK_QUALITY */
+struct mt7925_link_quality {
+ s8 rssi; /* moving-average beacon RSSI of connected AP */
+ s8 link_quality; /* always 0 */
+ __le16 link_speed; /* TX rate1, unit 500 Kb/s */
+ u8 medium_busy_pct; /* always 0 */
+ u8 is_ready; /* 0: fields invalid, 1: valid */
+ u8 rsv[2];
+} __packed;
+
+/* request TLV, mirror UNI_CMD_LINK_QUALITY (header + tag/len only) */
+struct mt7925_lq_req {
+ u8 rsv[4]; /* UNI_CMD_GET_STATISTICS fixed field */
+ __le16 tag; /* UNI_CMD_GET_STATISTICS_TAG_LINK_QUALITY = 1 */
+ __le16 len;
+} __packed;
+
+/* event TLV, mirror UNI_EVENT_LINK_QUALITY */
+struct mt7925_lq_event {
+ u8 rsv[4]; /* UNI_EVENT_STATISTICS fixed field */
+ __le16 tag; /* UNI_EVENT_ID_STATISTICS_LINK_QUALITY = 1 */
+ __le16 len;
+ struct mt7925_link_quality lq[MT7925_LQ_BAND_NUM];
+} __packed;
+
+static void
+mt7925_get_ap_sta_rec_idx_iter(void *data, u8 *mac,
+ struct ieee80211_vif *vif)
+{
+ struct mt7925_sta_rec_lookup *lookup = data;
+ struct mt792x_vif *mvif;
+ struct mt792x_sta *msta;
+
+ if (lookup->sta_rec_idx >= 0)
+ return;
+
+ if (vif->type != NL80211_IFTYPE_STATION || !vif->cfg.assoc)
+ return;
+
+ mvif = (struct mt792x_vif *)vif->drv_priv;
+ msta = mvif->wep_sta;
+ if (!msta)
+ return;
+
+ if (!msta->deflink.wcid.sta)
+ return;
+
+ lookup->sta_rec_idx = msta->deflink.wcid.idx;
+}
+
+int
+mt7925_get_ap_sta_rec_idx(struct mt792x_dev *dev, u8 *sta_rec_idx)
+{
+ struct mt7925_sta_rec_lookup lookup = {
+ .sta_rec_idx = -1,
+ };
+
+ lockdep_assert_held(&dev->mt76.mutex);
+
+ ieee80211_iterate_active_interfaces(dev->mphy.hw,
+ IEEE80211_IFACE_ITER_RESUME_ALL,
+ mt7925_get_ap_sta_rec_idx_iter,
+ &lookup);
+
+ if (lookup.sta_rec_idx < 0)
+ return -ENOTCONN;
+
+ if (lookup.sta_rec_idx > U8_MAX)
+ return -ERANGE;
+
+ *sta_rec_idx = lookup.sta_rec_idx;
+
+ return 0;
+}
+
+/* Decode the TX Vector BBP latch and print the last TX rate */
+void
+mt7925_print_last_tx_rate(struct seq_file *s, const __le32 le_txv[3])
+{
+ const char *mode_str = "N/A";
+ u8 rate, txmode, frmode, sgi, ldpc, nsts, stbc, spe, dcm, ersu106t;
+ s8 txpwr, pos_txpwr;
+ u32 txv[3];
+
+ txv[0] = le32_to_cpu(le_txv[0]);
+ txv[1] = le32_to_cpu(le_txv[1]);
+ txv[2] = le32_to_cpu(le_txv[2]);
+
+ if (txv[0] == 0xffffffff) {
+ seq_printf(s, "%-22s %s = %s\n", " ", "Last TX Rate", "N/A");
+ seq_printf(s, "%-22s %s = %s\n",
+ " ", "Chip Out TX Power", "N/A");
+ return;
+ }
+
+ rate = MT7925_TXV_GET_TX_RATE(txv);
+ txmode = MT7925_TXV_GET_TX_MODE(txv);
+ frmode = MT7925_TXV_GET_TX_FRMODE(txv);
+ nsts = MT7925_TXV_GET_TX_NSTS(txv) + 1;
+ sgi = MT7925_TXV_GET_TX_SGI(txv);
+ ldpc = MT7925_TXV_GET_TX_LDPC(txv);
+ stbc = MT7925_TXV_GET_TX_STBC(txv);
+ txpwr = MT7925_TXV_GET_TX_PWR(txv);
+ spe = MT7925_TXV_GET_TX_SPE_IDX(txv);
+ dcm = MT7925_TXV_GET_TX_DCM(txv);
+ ersu106t = MT7925_TXV_GET_TX_106T(txv);
+
+ if (dcm)
+ rate = MT7925_TXV_RATE_UNMASK_DCM(rate);
+ if (ersu106t)
+ rate = MT7925_TXV_RATE_UNMASK_106T(rate);
+
+ if (txmode < ARRAY_SIZE(mt7925_tx_mode_str))
+ mode_str = mt7925_tx_mode_str[txmode];
+
+ seq_printf(s, "%-22s %s = ", " ", "Last TX Rate");
+
+ /* rate/MCS field */
+ if (txmode == MT7925_TX_RATE_MODE_CCK)
+ seq_printf(s, "%s, ",
+ rate < 4 ? mt7925_tx_rate_cck_str[rate] :
+ (((rate >= 5) && (rate <= 7)) ?
+ mt7925_tx_rate_cck_str[rate - 4] :
+ mt7925_tx_rate_cck_str[4]));
+ else if (txmode == MT7925_TX_RATE_MODE_OFDM)
+ seq_printf(s, "%s, ", mt7925_hw_rate_ofdm_str(rate));
+ else if (txmode == MT7925_TX_RATE_MODE_HTMIX ||
+ txmode == MT7925_TX_RATE_MODE_HTGF)
+ seq_printf(s, "MCS%d, ", rate);
+ else
+ seq_printf(s, "%s%d_MCS%d, ",
+ stbc ? "NSTS" : "NSS", nsts, rate);
+
+ /* bandwidth */
+ seq_printf(s, "%s, ",
+ frmode < 5 ? mt7925_tx_rate_bw_str[frmode] :
+ mt7925_tx_rate_bw_str[5]);
+
+ /* GI / preamble */
+ if (txmode == MT7925_TX_RATE_MODE_CCK)
+ seq_printf(s, "%s, ", rate < 4 ? "LP" : "SP");
+ else if (txmode == MT7925_TX_RATE_MODE_OFDM)
+ ; /* OFDM has no GI/preamble to print */
+ else if (txmode == MT7925_TX_RATE_MODE_HTMIX ||
+ txmode == MT7925_TX_RATE_MODE_HTGF ||
+ txmode == MT7925_TX_RATE_MODE_VHT ||
+ txmode == MT7925_TX_RATE_MODE_PLR)
+ seq_printf(s, "%s, ", sgi == 0 ? "LGI" : "SGI");
+ else
+ seq_printf(s, "%s, ",
+ sgi == 0 ? "SGI" : (sgi == 1 ? "MGI" : "LGI"));
+
+ /* mode, DCM, 106t, STBC, coding, SPE */
+ seq_printf(s, "%s%s%s%s%s%s%d\n",
+ mode_str,
+ dcm ? ", DCM" : "", ersu106t ? ", 106t" : "",
+ stbc ? ", STBC, " : ", ", ldpc == 0 ? "BCC" : "LDPC",
+ ", SPE", spe);
+
+ /* chip out TX power (unit: 0.5 dBm) */
+ pos_txpwr = (txpwr < 0) ? (~txpwr + 1) : txpwr;
+ seq_printf(s, "%-22s %s = %c%d.%1d dBm\n",
+ " ", "Chip Out TX Power",
+ (txpwr < 0) ? '-' : '+',
+ (pos_txpwr / 2), 5 * (pos_txpwr % 2));
+}
+
+/* Guard interval used for the auto-rate entries, mirror Gen4m
+ * connac3x_get_sgi_info_from_fw(): pick the per-mode/per-BW flag.
+ */
+u8
+mt7925_wtbl_get_sgi(const struct mt7925_wtbl_rate *wr, u8 txmode, u8 fcap)
+{
+ bool he = (txmode == MT7925_TX_RATE_MODE_HE_SU ||
+ txmode == MT7925_TX_RATE_MODE_HE_ER ||
+ txmode == MT7925_TX_RATE_MODE_HE_TRIG ||
+ txmode == MT7925_TX_RATE_MODE_HE_MU);
+ bool eht = (txmode == MT7925_TX_RATE_MODE_EHT_ER ||
+ txmode == MT7925_TX_RATE_MODE_EHT_TRIG ||
+ txmode == MT7925_TX_RATE_MODE_EHT_MU);
+
+ if (eht)
+ return wr->gi_eht;
+
+ switch (fcap) {
+ case MT7925_BW_20:
+ return he ? wr->g2_he : wr->g2;
+ case MT7925_BW_40:
+ return he ? wr->g4_he : wr->g4;
+ case MT7925_BW_80:
+ return he ? wr->g8_he : wr->g8;
+ case MT7925_BW_160:
+ return he ? wr->g16_he : wr->g16;
+ default:
+ return 0;
+ }
+}
+
+/* Coding used for the auto-rate entries, mirror Gen4m
+ * connac3x_wtbl_get_ldpc_info_from_fw(): 0 = BCC, 1 = LDPC.
+ */
+u8
+mt7925_wtbl_get_ldpc(const struct mt7925_wtbl_rate *wr, u8 txmode)
+{
+ switch (txmode) {
+ case MT7925_TX_RATE_MODE_HTMIX:
+ case MT7925_TX_RATE_MODE_HTGF:
+ return wr->ht_ldpc;
+ case MT7925_TX_RATE_MODE_VHT:
+ return wr->vht_ldpc;
+ case MT7925_TX_RATE_MODE_HE_SU:
+ case MT7925_TX_RATE_MODE_HE_ER:
+ case MT7925_TX_RATE_MODE_HE_TRIG:
+ case MT7925_TX_RATE_MODE_HE_MU:
+ return wr->he_ldpc;
+ case MT7925_TX_RATE_MODE_EHT_ER:
+ case MT7925_TX_RATE_MODE_EHT_TRIG:
+ case MT7925_TX_RATE_MODE_EHT_MU:
+ return wr->eht_ldpc;
+ default:
+ return 0;
+ }
+}
+
+int mt792x_mcu_get_stat(struct mt792x_dev *dev,
+ struct mt7925_sta_stats *stats)
+{
+ u16 len = 0;
+ int ret = 0;
+ struct mt7925_sta_stats_event *event = NULL;
+ struct sk_buff *skb = NULL;
+ struct mt7925_sta_stats_req req = {
+ .tag = cpu_to_le16(UNI_CMD_ID_STATISTICS_STA),
+ .len = cpu_to_le16(sizeof(req) - sizeof(req.rsv)),
+ .lls_read_clear = false,
+ .reset_counter = false,
+ };
+
+ ret = mt7925_get_ap_sta_rec_idx(dev, &req.sta_rec_idx);
+ if (ret)
+ return ret;
+
+ if (!stats)
+ return -EINVAL;
+
+ ret = mt76_mcu_send_and_get_msg(&dev->mt76,
+ MCU_UNI_QUERY(GET_STAT_INFO),
+ &req, sizeof(req), true, &skb);
+
+ if (ret)
+ return ret;
+
+ event = (struct mt7925_sta_stats_event *)skb->data;
+
+ if (le16_to_cpu(event->tag) != UNI_CMD_ID_STATISTICS_STA) {
+ ret = -EPROTO;
+ goto out;
+ }
+
+ len = le16_to_cpu(event->len);
+ if (len < sizeof(*event) - sizeof(event->rsv) ||
+ len > skb->len - sizeof(event->rsv) || !IS_ALIGNED(len, 4)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+
+ if (event->stats.version != MT7925_STA_STATS_VERSION) {
+ ret = -EPROTO;
+ goto out;
+ }
+
+ if (!(le32_to_cpu(event->stats.flags) & MT7925_STA_STATS_VALID)) {
+ ret = -ENOENT;
+ goto out;
+ }
+
+ memcpy(stats, &event->stats, sizeof(*stats));
+
+out:
+ dev_kfree_skb(skb);
+ return ret;
+}
+
+int mt7925_mcu_get_mib_info(struct mt792x_dev *dev, u8 band_idx,
+ const u32 *counters, u8 counter_num,
+ u64 *values)
+{
+ u8 *req;
+ u8 *tlv, *tlv_end;
+ u16 req_len;
+ int i, ret;
+ struct mt7925_mib_hdr *req_hdr;
+ struct mt7925_mib_data *req_tlv;
+ struct sk_buff *skb;
+
+ if (!counters || !values || !counter_num ||
+ counter_num > MT7925_MIB_MAX_CNT)
+ return -EINVAL;
+
+ /* clear output; counters not returned by firmware stay 0 */
+ memset(values, 0, counter_num * sizeof(*values));
+
+ req_len = sizeof(struct mt7925_mib_hdr) +
+ counter_num * sizeof(struct mt7925_mib_data);
+
+ req = kzalloc(req_len, GFP_KERNEL);
+ if (!req)
+ return -ENOMEM;
+
+ req_hdr = (struct mt7925_mib_hdr *)req;
+ req_hdr->band_idx = band_idx;
+
+ req_tlv = (struct mt7925_mib_data *)(req + sizeof(*req_hdr));
+ for (i = 0; i < counter_num; i++) {
+ req_tlv[i].tag = cpu_to_le16(UNI_CMD_MIB_DATA);
+ req_tlv[i].len = cpu_to_le16(sizeof(struct mt7925_mib_data));
+ req_tlv[i].counter = cpu_to_le32(counters[i]);
+ }
+
+ ret = mt76_mcu_send_and_get_msg(&dev->mt76,
+ MCU_UNI_QUERY(GET_MIB_INFO), req,
+ req_len, true, &skb);
+ kfree(req);
+ if (ret)
+ return ret;
+
+ if (skb->len < sizeof(struct mt7925_mib_hdr)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+
+ /* walk the returned TLVs, match each counter ID to the request */
+ tlv = skb->data + sizeof(struct mt7925_mib_hdr);
+ tlv_end = skb->data + skb->len;
+
+ while (tlv + sizeof(struct mt7925_mib_data) <= tlv_end) {
+ struct mt7925_mib_data *evt_tlv =
+ (struct mt7925_mib_data *)tlv;
+ u16 tag = le16_to_cpu(evt_tlv->tag);
+ u16 len = le16_to_cpu(evt_tlv->len);
+ u32 counter;
+
+ if (len < sizeof(struct mt7925_mib_data) ||
+ tlv + len > tlv_end)
+ break;
+
+ if (tag == UNI_CMD_MIB_DATA) {
+ counter = le32_to_cpu(evt_tlv->counter);
+
+ if (counter < MT7925_MIB_CNT_MAX_NUM) {
+ for (i = 0; i < counter_num; i++) {
+ if (counters[i] == counter) {
+ values[i] =
+ le64_to_cpu(evt_tlv->data);
+ break;
+ }
+ }
+ }
+ }
+
+ tlv += len;
+ }
+
+ ret = 0;
+
+out:
+ dev_kfree_skb(skb);
+
+ return ret;
+}
+
+char *mt7925_hw_rate_ofdm_str(u8 ofdm_idx)
+{
+ char * const ofdm_str[] = {
+ "6M", "9M", "12M", "18M", "24M", "36M", "48M", "54M", "N/A"
+ };
+
+ switch (ofdm_idx) {
+ case 11: return ofdm_str[0]; /* 6M */
+ case 15: return ofdm_str[1]; /* 9M */
+ case 10: return ofdm_str[2]; /* 12M */
+ case 14: return ofdm_str[3]; /* 18M */
+ case 9: return ofdm_str[4]; /* 24M */
+ case 13: return ofdm_str[5]; /* 36M */
+ case 8: return ofdm_str[6]; /* 48M */
+ case 12: return ofdm_str[7]; /* 54M */
+ default: return ofdm_str[8];
+ }
+}
+
+/* mt792x_mcu_get_wtbl_rate - fetch the firmware auto-rate table for a WCID
+ * @dev: device
+ * @wlan_idx: HW WTBL / WCID index (see mt7925_sta_stats.wtbl_idx)
+ * @rate: out, filled with the auto-rate table snapshot
+ *
+ * Mirror of Gen4m wlanoidGetStatWtbl(): send UNI_CMD_GET_STAT_INFO with the
+ * WTBL rate TLV (cmd tag 10) and parse the reply (event tag 12) into @rate.
+ *
+ * Return: 0 on success, negative errno otherwise.
+ */
+int mt792x_mcu_get_wtbl_rate(struct mt792x_dev *dev, u16 wlan_idx,
+ struct mt7925_wtbl_rate *rate)
+{
+ int ret;
+ struct mt7925_wtbl_rate_event *event = NULL;
+ struct sk_buff *skb = NULL;
+ struct mt7925_wtbl_rate_req req = {
+ .tag = cpu_to_le16(UNI_CMD_ID_STATISTICS_WTBL),
+ .len = cpu_to_le16(sizeof(req) - sizeof(req.rsv)),
+ .wlan_idx = cpu_to_le16(wlan_idx),
+ };
+
+ if (!rate)
+ return -EINVAL;
+
+ ret = mt76_mcu_send_and_get_msg(&dev->mt76,
+ MCU_UNI_QUERY(GET_STAT_INFO),
+ &req, sizeof(req), true, &skb);
+ if (ret)
+ return ret;
+
+ if (skb->len < sizeof(*event)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+
+ event = (struct mt7925_wtbl_rate_event *)skb->data;
+
+ if (le16_to_cpu(event->rate.tag) != MT7925_UNI_EVENT_STATISTICS_WTBL) {
+ ret = -EPROTO;
+ goto out;
+ }
+
+ memcpy(rate, &event->rate, sizeof(*rate));
+ ret = 0;
+
+out:
+ dev_kfree_skb(skb);
+ return ret;
+}
+
+/**
+ * mt7925_mcu_get_link_quality - query RSSI and TX link speed for a band
+ * @dev: device
+ * @band_idx: band/BSS index (0..3)
+ * @rssi: out, moving-average beacon RSSI in dBm (may be NULL)
+ * @tx_link_speed_kbps: out, TX rate in kbit/s (may be NULL)
+ *
+ * Return: 0 on success, -ENOENT if the band's link quality is not ready.
+ *
+ * Only RSSI and TX rate come from firmware here; for RX rate read wcid->rate.
+ */
+int mt7925_mcu_get_link_quality(struct mt792x_dev *dev, u8 band_idx,
+ s8 *rssi, u32 *tx_link_speed_kbps)
+{
+ int ret;
+ struct mt7925_lq_event *event;
+ struct mt7925_link_quality *lq;
+ struct sk_buff *skb;
+ struct mt7925_lq_req req = {
+ .tag = cpu_to_le16(UNI_CMD_ID_STATISTICS_LINK_QUALITY),
+ .len = cpu_to_le16(sizeof(req) - sizeof(req.rsv)),
+ };
+
+ if (band_idx >= MT7925_LQ_BAND_NUM)
+ return -EINVAL;
+
+ ret = mt76_mcu_send_and_get_msg(&dev->mt76,
+ MCU_UNI_QUERY(GET_STAT_INFO), &req,
+ sizeof(req), true, &skb);
+ if (ret)
+ return ret;
+
+ if (skb->len < sizeof(*event)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+
+ event = (struct mt7925_lq_event *)skb->data;
+
+ if (le16_to_cpu(event->tag) != UNI_CMD_ID_STATISTICS_LINK_QUALITY) {
+ ret = -EPROTO;
+ goto out;
+ }
+
+ lq = &event->lq[band_idx];
+ if (!lq->is_ready) {
+ ret = -ENOENT;
+ goto out;
+ }
+
+ if (rssi)
+ *rssi = lq->rssi;
+
+ /* firmware reports TX rate in 500 Kb/s units -> kbit/s */
+ if (tx_link_speed_kbps)
+ *tx_link_speed_kbps = le16_to_cpu(lq->link_speed) * 500;
+
+ ret = 0;
+
+out:
+ dev_kfree_skb(skb);
+
+ return ret;
+}
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/stats.h b/drivers/net/wireless/mediatek/mt76/mt7925/stats.h
new file mode 100644
index 000000000000..a70b9eb3aa80
--- /dev/null
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/stats.h
@@ -0,0 +1,309 @@
+/* SPDX-License-Identifier: BSD-3-Clause-Clear */
+/* Copyright (C) 2026 MediaTek Inc. */
+
+#ifndef __MT7925_STATS_H
+#define __MT7925_STATS_H
+
+#include <linux/if_ether.h>
+#include <linux/types.h>
+#include "../mt76_connac_mcu.h"
+
+#define MT7925_STA_STATS_AC_NUM 4
+#define MT7925_STA_STATS_BAND_NUM 2
+#define MT7925_STA_STATS_AGG_RANGE_NUM 16
+#define MT7925_STA_STATS_TX_QUALITY_NUM 4
+#define MT7925_TX_MODE_NUM 16
+#define MT7925_TX_RATE_CCK_NUM 5
+#define MT7925_TX_RATE_BW_NUM 6
+
+/* HW rate code (16-bit) field extract, mirror Gen4m CONNAC3X_HW_TX_RATE_TO_*.
+ * The auto-rate table stores one rate code per entry.
+ */
+#define MT7925_HW_RATE_TO_MCS(_x) (((_x) & GENMASK(5, 0)) >> 0)
+#define MT7925_HW_RATE_TO_MODE(_x) (((_x) & GENMASK(9, 6)) >> 6)
+#define MT7925_HW_RATE_TO_NSS(_x) (((_x) & GENMASK(13, 10)) >> 10)
+#define MT7925_HW_RATE_TO_STBC(_x) (((_x) & BIT(14)) >> 14)
+#define MT7925_HW_RATE_TO_DCM(_x) (((_x) & BIT(4)) >> 4)
+#define MT7925_HW_RATE_TO_106T(_x) (((_x) & BIT(5)) >> 5)
+#define MT7925_HW_RATE_UNMASK_DCM(_r) ((u8)(_r) & 0xef)
+#define MT7925_HW_RATE_UNMASK_106T(_r) ((u8)(_r) & 0xdf)
+
+/* Number of entries in the firmware auto-rate table (mirror Gen4m
+ * AUTO_RATE_NUM). RA cycles through these and reports which one is in use
+ * via wtbl->rate_idx.
+ */
+#define MT7925_AUTO_RATE_NUM 8
+
+/* Firmware EVENT_STATISTICS sub-event tags. The WTBL rate request uses cmd tag
+ * UNI_CMD_ID_STATISTICS_WTBL (10) but the firmware replies with event tag 12.
+ */
+#define MT7925_UNI_EVENT_STATISTICS_WTBL 12
+
+/* CONNAC3X TX Vector BBP latch bit fields (mirror cmm_asic_connac3x.h).
+ * These operate on the 3-DWORD tx_vector[band].txv[3] array.
+ */
+#define MT7925_TXV_GET_TX_RATE(_txv) ((_txv)[2] & 0x7f)
+#define MT7925_TXV_GET_TX_LDPC(_txv) (((_txv)[2] & (0x1 << 7)) >> 7)
+#define MT7925_TXV_GET_TX_STBC(_txv) (((_txv)[0] & (0x3 << 6)) >> 6)
+#define MT7925_TXV_GET_TX_FRMODE(_txv) (((_txv)[0] & (0x7 << 8)) >> 8)
+#define MT7925_TXV_GET_TX_MODE(_txv) (((_txv)[0] & (0xf << 12)) >> 12)
+#define MT7925_TXV_GET_TX_NSTS(_txv) (((_txv)[2] & (0xfU << 28)) >> 28)
+#define MT7925_TXV_GET_TX_PWR(_txv) (((_txv)[0] & (0xff << 16)) >> 16)
+#define MT7925_TXV_GET_TX_SGI(_txv) (((_txv)[1] & (0x3 << 26)) >> 26)
+#define MT7925_TXV_GET_TX_SPE_IDX(_txv) (((_txv)[0] & (0x1f << 0)) >> 0)
+#define MT7925_TXV_GET_TX_DCM(_txv) (((_txv)[2] & (0x1 << 4)) >> 4)
+#define MT7925_TXV_GET_TX_106T(_txv) (((_txv)[2] & (0x1 << 5)) >> 5)
+#define MT7925_TXV_RATE_UNMASK_DCM(_r) ((u8)(_r) & 0xef)
+#define MT7925_TXV_RATE_UNMASK_106T(_r) ((u8)(_r) & 0xdf)
+
+extern const char * const mt7925_tx_mode_str[MT7925_TX_MODE_NUM];
+extern const char * const mt7925_tx_rate_cck_str[MT7925_TX_RATE_CCK_NUM];
+extern const char * const mt7925_tx_rate_bw_str[MT7925_TX_RATE_BW_NUM];
+
+/* TX rate mode, mirror Gen4m TX_RATE_MODE_* (see nic_rate.h). Used to decode
+ * both the TX Vector BBP latch and the firmware auto-rate table rate codes.
+ */
+enum mt7925_tx_rate_mode {
+ MT7925_TX_RATE_MODE_CCK = 0,
+ MT7925_TX_RATE_MODE_OFDM = 1,
+ MT7925_TX_RATE_MODE_HTMIX = 2,
+ MT7925_TX_RATE_MODE_HTGF = 3,
+ MT7925_TX_RATE_MODE_VHT = 4,
+ MT7925_TX_RATE_MODE_PLR = 5,
+ MT7925_TX_RATE_MODE_HE_SU = 8,
+ MT7925_TX_RATE_MODE_HE_ER = 9,
+ MT7925_TX_RATE_MODE_HE_TRIG = 10,
+ MT7925_TX_RATE_MODE_HE_MU = 11,
+ MT7925_TX_RATE_MODE_EHT_ER = 13,
+ MT7925_TX_RATE_MODE_EHT_TRIG = 14,
+ MT7925_TX_RATE_MODE_EHT_MU = 15,
+};
+
+enum {
+ UNI_CMD_MIB_DATA,
+};
+
+enum stats_query_mib_array_index {
+ QUERY_MIB_CNT_RX_FCS_ERR = 0,
+ QUERY_MIB_CNT_RX_FIFO_OVERFLOW,
+ QUERY_MIB_CNT_RX_MPDU,
+ QUERY_MIB_CNT_AMPDU_RX_COUNT,
+ QUERY_MIB_CNT_PF_DROP,
+ QUERY_MIB_CNT_LEN_MISMATCH,
+ QUERY_MIB_CNT_CHANNEL_IDLE,
+ QUERY_MIB_CNT_CCA_NAV_TX_TIME,
+ QUERY_MIB_CNT_MDRDY,
+ QUERY_MIB_CNT_RX_CCK_MDRDY_TIME,
+ QUERY_MIB_CNT_RX_OFDM_LG_MIXED_MDRDY_TIME,
+ QUERY_MIB_CNT_RX_OFDM_GREEN_MDRDY_TIME,
+ QUERY_MIB_CNT_P_CCA_TIME,
+ QUERY_MIB_CNT_S_CCA_TIME,
+ QUERY_MIB_CNT_P_ED_TIME,
+ QUERY_MIB_CNT_BCN_TX,
+ QUERY_MIB_CNT_TX_BW_40MHZ,
+ QUERY_MIB_CNT_TX_BW_80MHZ,
+ QUERY_MIB_CNT_TX_BW_160MHZ,
+ QUERY_MIB_CNT_BSS0_BA_MISS,
+ QUERY_MIB_CNT_BSS0_RTS_TX_CNT,
+ QUERY_MIB_CNT_BSS0_FRAME_RETRY,
+ QUERY_MIB_CNT_BSS0_FRAME_RETRY_2,
+ QUERY_MIB_CNT_BSS0_RTS_RETRY,
+ QUERY_MIB_CNT_BSS0_ACK_FAIL,
+ QUERY_MIB_CNT_AMPDU,
+ QUERY_MIB_CNT_AMPDU_MPDU,
+ QUERY_MIB_CNT_AMPDU_ACKED,
+ QUERY_MIB_CNT_MAX_NUM,
+};
+
+enum stats_trx_agg_range {
+ STAT_MIB_CNT_TRX_AGG_RANGE0_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE1_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE2_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE3_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE4_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE5_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE6_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE7_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE_MAX_NUM
+};
+
+enum {
+ MT7925_BW_20 = 0,
+ MT7925_BW_40,
+ MT7925_BW_80,
+ MT7925_BW_160,
+};
+
+struct mt7925_sta_stats_ac {
+ __le32 tx_fail;
+ __le32 tx_retry;
+} __packed;
+
+struct mt7925_sta_stats_tx_vector {
+ __le32 txv[3];
+} __packed;
+
+struct mt7925_sta_stats_mib {
+ __le32 rx_mpdu_cnt;
+ __le32 fcs_error;
+ __le32 rx_fifo_full;
+ __le32 ampdu_tx_sf_cnt;
+ __le32 ampdu_tx_ack_sf_cnt;
+ __le32 tx_range_ampdu_cnt[MT7925_STA_STATS_AGG_RANGE_NUM];
+} __packed;
+
+/* Firmware EVENT_STA_STATISTICS body. */
+struct mt7925_sta_stats {
+ u8 version;
+ u8 rsv[3];
+ __le32 flags;
+
+ u8 sta_idx;
+ u8 bss_idx;
+ u8 wtbl_idx;
+ u8 rsv1;
+
+ u8 mac_addr[ETH_ALEN];
+ u8 per;
+ u8 rcpi;
+
+ __le32 phy_mode;
+ __le16 link_speed; /* 0.5 Mbit/s units */
+ u8 link_quality;
+ u8 link_reserved;
+
+ __le32 tx_count;
+ __le32 tx_fail_count;
+ __le32 tx_life_timeout_count;
+ __le32 tx_done_air_time;
+ __le32 transmit_count;
+ __le32 transmit_fail_count;
+
+ struct mt7925_sta_stats_ac ac[MT7925_STA_STATS_AC_NUM];
+
+ u8 temperature;
+ u8 skip_ar;
+ u8 ar_table_idx;
+ u8 rate_entry_idx;
+ u8 rate_entry_idx_prev;
+ u8 tx_sgi_detect_pass_cnt;
+ u8 ave_per;
+ u8 rsv2;
+
+ __le32 agg_range_ctrl[2];
+ u8 range_mode;
+ u8 rsv3[3];
+ __le32 agg_range_ctrl_ext[6];
+
+ u8 ar_state_curr;
+ u8 ar_state_prev;
+ u8 ar_action_type;
+ u8 highest_rate_cnt;
+ u8 lowest_rate_cnt;
+ u8 rsv4;
+ __le16 train_up;
+ __le16 train_down;
+ u8 rsv5[2];
+ __le32 rate1_tx_cnt;
+ __le32 rate1_fail_cnt;
+
+ struct mt7925_sta_stats_tx_vector tx_vector[MT7925_STA_STATS_BAND_NUM];
+ struct mt7925_sta_stats_mib mib[MT7925_STA_STATS_BAND_NUM];
+
+ u8 is_force_tx_stream;
+ u8 is_force_se_off;
+ __le16 ra_running_cnt;
+ u8 ra_status;
+ u8 max_ampdu_factor;
+ u8 tx_quality[MT7925_STA_STATS_TX_QUALITY_NUM];
+ u8 tx_rate_up_penalty;
+ u8 low_traffic_mode;
+ u8 low_traffic_count;
+ u8 low_traffic_dashboard;
+ u8 dynamic_sgi_state;
+ u8 dynamic_sgi_score;
+ u8 dynamic_bw_state;
+ u8 dynamic_gband_256qam_state;
+ u8 vht_non_sp_rate_state;
+ u8 rsv6[3];
+ u8 rsv7[2];
+} __packed;
+
+struct mt7925_sta_rec_lookup {
+ int sta_rec_idx;
+};
+
+/* Firmware auto-rate table snapshot, mirror Gen4m struct UNI_EVENT_STAT_WTBL.
+ *
+ * The firmware RA maintains up to MT7925_AUTO_RATE_NUM candidate rates in
+ * rate_code[]. rate_idx tells which entry is currently in use, so a dump can
+ * point at it with "-->". fcap is the bandwidth capability and the ldpc/sgi
+ * flags describe the per-mode coding/guard-interval used for those rates.
+ */
+struct mt7925_wtbl_rate {
+ __le16 tag;
+ __le16 len;
+ u8 rx_rcpi0;
+ u8 rx_rcpi1;
+ u8 cbrn;
+ u8 fcap;
+ u8 spe_idx;
+ u8 g2; /* SGI enabled, non-HE, BW20 */
+ u8 g4; /* SGI enabled, non-HE, BW40 */
+ u8 g8; /* SGI enabled, non-HE, BW80 */
+ u8 g16; /* SGI enabled, non-HE, BW160 */
+ u8 g2_he; /* GI, HE, BW20 */
+ u8 g4_he; /* GI, HE, BW40 */
+ u8 g8_he; /* GI, HE, BW80 */
+ u8 g16_he; /* GI, HE, BW160 */
+ u8 gi_eht; /* GI, EHT */
+ u8 ht_ldpc;
+ u8 vht_ldpc;
+ u8 he_ldpc;
+ u8 eht_ldpc;
+ u8 af;
+ u8 rate_idx; /* index into rate_code[] currently in use */
+ __le16 rate_code[MT7925_AUTO_RATE_NUM];
+ __le32 rsv[4];
+} __packed;
+
+int
+mt7925_get_ap_sta_rec_idx(struct mt792x_dev *dev, u8 *sta_rec_idx);
+
+void
+mt7925_print_last_tx_rate(struct seq_file *s, const __le32 le_txv[3]);
+
+u8
+mt7925_wtbl_get_sgi(const struct mt7925_wtbl_rate *wr, u8 txmode, u8 fcap);
+
+u8
+mt7925_wtbl_get_ldpc(const struct mt7925_wtbl_rate *wr, u8 txmode);
+
+char *mt7925_hw_rate_ofdm_str(u8 ofdm_idx);
+
+int mt792x_mcu_get_wtbl_rate(struct mt792x_dev *dev, u16 wlan_idx,
+ struct mt7925_wtbl_rate *rate);
+
+int mt792x_mcu_get_stat(struct mt792x_dev *dev,
+ struct mt7925_sta_stats *stats);
+
+int mt7925_mcu_get_mib_info(struct mt792x_dev *dev, u8 band_idx,
+ const u32 *counters, u8 counter_num,
+ u64 *values);
+
+/* GET_STAT_INFO (UNI_CMD_ID_GET_STATISTICS = 0x23),
+ * sub-command UNI_CMD_GET_STATISTICS_TAG_LINK_QUALITY = 1.
+ *
+ * @band_idx: band/BSS index (0..3)
+ * @rssi: out, moving-average beacon RSSI (dBm), may be NULL
+ * @tx_link_speed_kbps: out, TX link speed in kbit/s, may be NULL
+ *
+ * Returns 0 on success, -ENOENT if the band's link quality is not ready.
+ *
+ * NOTE: firmware provides only RSSI and TX rate here. RX rate/bandwidth are
+ * not in this event; read wcid->rate for the RX rate.
+ */
+int mt7925_mcu_get_link_quality(struct mt792x_dev *dev, u8 band_idx,
+ s8 *rssi, u32 *tx_link_speed_kbps);
+
+#endif
--
2.45.2
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH v3] wifi: mt76: mt7928: Add debugfs for stats to provide debug purpose
2026-08-27 5:54 ` [PATCH v2] " Eason Lai
@ 2026-08-27 8:44 ` Eason Lai
2026-08-28 7:39 ` kernel test robot
2026-08-31 3:28 ` [PATCH v4] " Eason Lai
0 siblings, 2 replies; 12+ messages in thread
From: Eason Lai @ 2026-08-27 8:44 UTC (permalink / raw)
To: nbd, lorenzo
Cc: linux-wireless, linux-mediatek, kun.wu, deren.wu, sean.wang,
quan.zhou, ryder.lee, leon.yen, litien.chang, jb.tsai,
michael.shih, eason.lai, kernel test robot
From: Michael Shih <michael.shih@mediatek.com>
Usage:
(1) Connect STA to AP (You may use dhclient CMD to ask for DHCP IP, to use ping to AP)
DHCP IP CMD: dhclient ${WLAN_IF} -v
Example: dhclient wlan0 -v
## (To simulate traffic) Ping 10 packets for every second to AP
ping 192.168.0.XXX -i0.1
(2) (Get stats) Use command, phyX is your WiFi interface:
cat /sys/kernel/debug/ieee80211/phyX/mt76/stats
(3) You will see below stats info:
(STA) connected AP MAC Address = a8:42:a1:46:c4:6b
BssIdx = [0]
StaRecIdx = [1],
RSSI = -25
Link_Speed = 2402000 (kbit/s)
temperature = 40
Tx success = 716
Tx fail to Rcv ACK after retry = 0
Rx Mpdu = 23
Rx Fcs Error = 1 (MPDU)
Rx FIFO full = 0 (MPDU)
TRX_AGG_RANGE[0] = 0 (PPDU)
TRX_AGG_RANGE[1] = 0 (PPDU)
TRX_AGG_RANGE[2] = 0 (PPDU)
TRX_AGG_RANGE[3] = 0 (PPDU)
TRX_AGG_RANGE[4] = 0 (PPDU)
TRX_AGG_RANGE[5] = 0 (PPDU)
TRX_AGG_RANGE[6] = 0 (PPDU)
TRX_AGG_RANGE[7] = 0 (PPDU)
MPDUs in AMPDUs transmitted = 0 (MPDU)
MPDUs in AMPDUs transmitted with ACK reply = 0 (MPDU)
rate1_tx_cnt = 714
rate1_fail_cnt = 223
train_up = 2310
train_down = 3080
is_force_tx_stream = 0
is_force_se_off = 0
max_ampdu_factor = 9
tx_rate_up_penalty = 0
low_traffic_mode = 2
low_traffic_count = 0
low_traffic_dashboard = 3
dynamic_sgi_state = 0
dynamic_sgi_score = 2
dynamic_bw_state = 0
dynamic_gband_256qam_state = 3
vht_non_sp_rate_state = 0
Last TX Rate = 24M, BW20, OFDM, BCC, SPE24
Chip Out TX Power = +16.5 dBm
Cur Rate Index = 0
Rate index[0] --> NSS2_MCS11, BW160/BW8080, SGI, HE_SU, LDPC
Rate index[1] NSS2_MCS9, BW160/BW8080, SGI, HE_SU, LDPC
Rate index[2] NSS2_MCS7, BW160/BW8080, SGI, HE_SU, LDPC
Rate index[3] NSS2_MCS4, BW160/BW8080, SGI, HE_SU, LDPC
Rate index[4] NSS2_MCS3, BW160/BW8080, SGI, HE_SU, LDPC
Rate index[5] NSS1_MCS2, BW160/BW8080, SGI, HE_SU, LDPC
Rate index[6] NSS1_MCS1, BW80, SGI, HE_SU, LDPC
Rate index[7] NSS1_MCS0, BW80, SGI, HE_SU, LDPC
mib state:
=== Rx Related Counters ===
Rx with CRC = 24020 (MPDU)
Rx drop due to out of resource = 0 (MPDU)
Rx Mpdu = 93223 (MPDU)
Rx AMpdu = 668 (PPDU)
Rx PF Drop = 0 (MPDU)
Rx Len Mismatch = 5853 (PPDU)
=== Phy/Timing Related Counters ===
ChannelIdleCnt = 1040434
CCA_NAV_Tx_Time = 18510036
Rx_MDRDY_CNT = 166580 (PPDU)
CCK_MDRDY = 7782
OFDM_MDRDY = 11912731
OFDM_GREEN_MDRDY = 0
Prim CCA Time = 17917650
Sec CCA Time = 14922034
Prim ED Time = 5677528
=== Tx Related Counters(Generic) ===
BeaconTxCnt = 0
Tx 40MHz Cnt = 279 (MPDU)
Tx 80MHz Cnt = 44 (MPDU)
Tx 160MHz Cnt = 712 (MPDU)
=== BSSID[0] Related Counters ===
BA Miss Cnt = 210 (PPDU)
RTS Tx Cnt = 1003 (MPDU)
Frame Retry Cnt = 239 (MPDU)
Frame Retry 2 Cnt = 71 (MPDU)
RTS Retry Cnt = 152 (MPDU)
Ack Failed Cnt = 319 (PPDU)
=== AMPDU Related Counters ===
Tx AMPDU_Pkt_Cnt = 705 (PPDU)
Tx AMPDU_MPDU_Pkt_Cnt = 713 (MPDU)
AMPDU Tx success = 503 (MPDU)
AMPDU Tx fail count = 210 (MPDU), PER=29.4%
Reported-by: kernel test robot <lkp@intel.com>
Closes: https://lore.kernel.org/oe-kbuild-all/202608251101.iQGUrSd0-lkp@intel.com/
Closes: https://lore.kernel.org/oe-kbuild-all/202608251526.VgKMT8Lv-lkp@intel.com/
Signed-off-by: Michael Shih <michael.shih@mediatek.com>
---
v2: (1) Remove redundant code to fix build errors.
(2) Move UNI_CMD_MIB_DATA enum definition from 'mt76/mt76_connac_mcu.h' to
'mt7925/stats.h' to avoid redeclartion with 'mt76/mt7996/mcu.h'
v3: Fixed build errors due to __udivdi3
---
.../wireless/mediatek/mt76/mt76_connac_mcu.h | 173 ++++++
.../wireless/mediatek/mt76/mt7925/Makefile | 2 +-
.../wireless/mediatek/mt76/mt7925/debugfs.c | 346 +++++++++++
.../net/wireless/mediatek/mt76/mt7925/stats.c | 585 ++++++++++++++++++
.../net/wireless/mediatek/mt76/mt7925/stats.h | 309 +++++++++
5 files changed, 1414 insertions(+), 1 deletion(-)
create mode 100644 drivers/net/wireless/mediatek/mt76/mt7925/stats.c
create mode 100644 drivers/net/wireless/mediatek/mt76/mt7925/stats.h
diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
index 0ededa569e9a..bf0f105c786e 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
@@ -1483,6 +1483,179 @@ enum {
MT_NIC_CAP_EML_CAP = 0x22,
};
+enum {
+ UNI_CMD_ID_STATISTICS_BASIC = 0,
+ UNI_CMD_ID_STATISTICS_LINK_QUALITY = 1,
+ UNI_CMD_ID_STATISTICS_STA = 2,
+ UNI_CMD_ID_STATISTICS_BUG_REPORT = 3,
+ UNI_CMD_ID_STATISTICS_TX_STATS = 4,
+ UNI_CMD_ID_STATISTICS_ALL_STATS = 5,
+ UNI_CMD_ID_STATISTICS_EML_STATS = 6,
+ UNI_CMD_ID_STATISTICS_REGULAR_STATS = 7,
+ UNI_CMD_ID_STATISTICS_BSS_LINK_QUALITY = 8,
+ UNI_CMD_ID_STATISTICS_ML_CHNL_COND = 9,
+ UNI_CMD_ID_STATISTICS_WTBL = 10,
+ UNI_CMD_ID_STATISTICS_LINK_LAYER_STATS = 128,
+ UNI_CMD_ID_STATISTICS_PPDU_LATENCY = 129,
+ UNI_CMD_ID_STATISTICS_CURRENT_TX_RATE = 130,
+ UNI_CMD_ID_STATISTICS_SET_TX_BITRATE_MONITOR = 131,
+ UNI_CMD_ID_STATISTICS_GET_PREDICT_TX_BITRATE = 132,
+ UNI_CMD_ID_STATISTICS_SET_ATPS_TYPE_RATE_MODE = 133,
+ UNI_CMD_ID_STATISTICS_SET_BEACON_REPORT = 134,
+ UNI_CMD_ID_STATISTICS_BSS_CURRENT_TX_RATE = 135,
+ UNI_CMD_ID_STATISTICS_GET_BSS_PREDICT_TX_BITRATE = 136,
+ UNI_CMD_ID_STATISTICS_MAX_NUM
+};
+
+enum mt7925_mib_counter {
+ UNI_CMD_MIB_CNT_RX_FCS_ERR = 0,
+ UNI_CMD_MIB_CNT_RX_FIFO_OVERFLOW = 1,
+ UNI_CMD_MIB_CNT_RX_MPDU = 2,
+ UNI_CMD_MIB_CNT_AMPDU_RX_COUNT = 3,
+ UNI_CMD_MIB_CNT_RX_TOTAL_BYTE = 4,
+ UNI_CMD_MIB_CNT_RX_VALID_AMPDU_SF = 5,
+ UNI_CMD_MIB_CNT_RX_VALID_BYTE = 6,
+ UNI_CMD_MIB_CNT_CHANNEL_IDLE = 7,
+ UNI_CMD_MIB_CNT_VEC_DROP = 8,
+ UNI_CMD_MIB_CNT_DELIMITER_FAIL = 9,
+ UNI_CMD_MIB_CNT_VEC_MISMATCH = 10,
+ UNI_CMD_MIB_CNT_MDRDY = 11,
+ UNI_CMD_MIB_CNT_RX_CCK_MDRDY_TIME = 12,
+ UNI_CMD_MIB_CNT_RX_OFDM_LG_MIXED_MDRDY_TIME = 13,
+ UNI_CMD_MIB_CNT_RX_OFDM_GREEN_MDRDY_TIME = 14,
+ UNI_CMD_MIB_CNT_PF_DROP = 15,
+ UNI_CMD_MIB_CNT_LEN_MISMATCH = 16,
+ UNI_CMD_MIB_CNT_P_CCA_TIME = 17,
+ UNI_CMD_MIB_CNT_S_CCA_TIME = 18,
+ UNI_CMD_MIB_CNT_CCA_NAV_TX_TIME = 19,
+ UNI_CMD_MIB_CNT_P_ED_TIME = 20,
+ UNI_CMD_MIB_CNT_BCN_TX = 21,
+ UNI_CMD_MIB_CNT_TX_BW_20MHZ = 22,
+ UNI_CMD_MIB_CNT_TX_BW_40MHZ = 23,
+ UNI_CMD_MIB_CNT_TX_BW_80MHZ = 24,
+ UNI_CMD_MIB_CNT_TX_BW_160MHZ = 25,
+ UNI_CMD_RMAC_CNT_OBSS_AIRTIME = 26,
+ UNI_CMD_RMAC_CNT_NONWIFI_AIRTIME = 27,
+ UNI_CMD_MIB_CNT_TX_DUR_CNT = 28,
+ UNI_CMD_MIB_CNT_RX_DUR_CNT = 29,
+ UNI_CMD_MIB_CNT_BA_CNT = 30,
+ UNI_CMD_MIB_CNT_MAC2PHY_TX_TIME = 31,
+ UNI_CMD_MIB_CNT_RX_OUT_OF_RANGE_COUNT = 32,
+ UNI_CMD_MIB_CNT_IBF_TX = 33,
+ UNI_CMD_MIB_CNT_EBF_TX = 34,
+ UNI_CMD_MIB_CNT_MUBF_TX = 35,
+ UNI_CMD_MIB_CNT_RX_BF_VHTFBK = 36,
+ UNI_CMD_MIB_CNT_RX_BF_HTFBK = 37,
+ UNI_CMD_MIB_CNT_RX_BF_HEFBK = 38,
+ UNI_CMD_MIB_CNT_BFEE_TXFBK_BFPOLL_TRI = 39,
+ UNI_CMD_MIB_CNT_BFEE_TXFBK_TRI = 40,
+ UNI_CMD_MIB_CNT_BFEE_RX_FBKCQI = 41,
+ UNI_CMD_MIB_CNT_BFEE_RX_NDP = 42,
+ UNI_CMD_MIB_CNT_RX_BF_IBF_UPT = 43,
+ UNI_CMD_MIB_CNT_RX_BF_EBF_UPT = 44,
+ UNI_CMD_MIB_CNT_BFEE_SP_ABORT = 45,
+ UNI_CMD_MIB_CNT_BFEE_TB_LEN_ERR = 46,
+ UNI_CMD_MIB_CNT_BFEE_TXFBK_MUTE = 47,
+ UNI_CMD_MIB_CNT_BFEE_TMAC_ABORT = 48,
+ UNI_CMD_MIB_CNT_BFEE_TXFBK_CPL = 49,
+ UNI_CMD_MIB_CNT_BFEE_COANT_BLKTX = 50,
+ UNI_CMD_MIB_CNT_BFEE_FBK_SEG = 51,
+ UNI_CMD_MIB_CNT_NAV_TIME = 52,
+ UNI_CMD_MIB_CNT_TX_ABORT_CNT1 = 53,
+ UNI_CMD_MIB_CNT_TX_ABORT_CNT0 = 54,
+ UNI_CMD_MIB_CNT_TX_ABORT_CNT3 = 55,
+ UNI_CMD_MIB_CNT_TX_ABORT_CNT2 = 56,
+ UNI_CMD_MIB_CNT_BSS0_RTS_TX_CNT = 57,
+ UNI_CMD_MIB_CNT_BSS1_RTS_TX_CNT = 58,
+ UNI_CMD_MIB_CNT_BSS2_RTS_TX_CNT = 59,
+ UNI_CMD_MIB_CNT_BSS3_RTS_TX_CNT = 60,
+ UNI_CMD_MIB_CNT_BSS0_RTS_RETRY = 61,
+ UNI_CMD_MIB_CNT_BSS1_RTS_RETRY = 62,
+ UNI_CMD_MIB_CNT_BSS2_RTS_RETRY = 63,
+ UNI_CMD_MIB_CNT_BSS3_RTS_RETRY = 64,
+ UNI_CMD_MIB_CNT_BSS0_BA_MISS = 65,
+ UNI_CMD_MIB_CNT_BSS1_BA_MISS = 66,
+ UNI_CMD_MIB_CNT_BSS2_BA_MISS = 67,
+ UNI_CMD_MIB_CNT_BSS3_BA_MISS = 68,
+ UNI_CMD_MIB_CNT_BSS0_ACK_FAIL = 69,
+ UNI_CMD_MIB_CNT_BSS1_ACK_FAIL = 70,
+ UNI_CMD_MIB_CNT_BSS2_ACK_FAIL = 71,
+ UNI_CMD_MIB_CNT_BSS3_ACK_FAIL = 72,
+ UNI_CMD_MIB_CNT_BSS0_FRAME_RETRY = 73,
+ UNI_CMD_MIB_CNT_BSS1_FRAME_RETRY = 74,
+ UNI_CMD_MIB_CNT_BSS2_FRAME_RETRY = 75,
+ UNI_CMD_MIB_CNT_BSS3_FRAME_RETRY = 76,
+ UNI_CMD_MIB_CNT_BSS0_FRAME_RETRY_2 = 77,
+ UNI_CMD_MIB_CNT_BSS1_FRAME_RETRY_2 = 78,
+ UNI_CMD_MIB_CNT_BSS2_FRAME_RETRY_2 = 79,
+ UNI_CMD_MIB_CNT_BSS3_FRAME_RETRY_2 = 80,
+ UNI_CMD_MIB_CNT_RX_A1_SEARCH = 81,
+ UNI_CMD_MIB_CNT_RX_DROP_MPDU = 82,
+ UNI_CMD_MIB_CNT_RX_UNWANTED = 83,
+ UNI_CMD_MIB_CNT_RX_FCS_OK = 84,
+ UNI_CMD_MIB_CNT_SU_TX_OK = 85,
+ UNI_CMD_MIB_CNT_TX_FULL_BW = 86,
+ UNI_CMD_MIB_CNT_TX_AUTO_BW = 87,
+ UNI_CMD_MIB_CNT_CCA_SAMPLE_IDLE = 88,
+ UNI_CMD_MIB_CNT_CCA_SAMPLE_ACTIVE = 89,
+ UNI_CMD_MIB_CNT_CCA_TIME = 90,
+ UNI_CMD_MIB_CNT_S_20BW_CCA_TIME = 91,
+ UNI_CMD_MIB_CNT_S_40BW_CCA_TIME = 92,
+ UNI_CMD_MIB_CNT_S_80BW_CCA_TIME = 93,
+ UNI_CMD_MIB_CNT_S_160BW_CCA_TIME = 94,
+ UNI_CMD_MIB_CNT_S_P20BW_0_ED_TIME = 95,
+ UNI_CMD_MIB_CNT_S_P20BW_1_ED_TIME = 96,
+ UNI_CMD_MIB_CNT_S_P20BW_2_ED_TIME = 97,
+ UNI_CMD_MIB_CNT_S_P20BW_3_ED_TIME = 98,
+ UNI_CMD_MIB_CNT_S_P20BW_4_ED_TIME = 99,
+ UNI_CMD_MIB_CNT_S_P20BW_5_ED_TIME = 100,
+ UNI_CMD_MIB_CNT_S_P20BW_6_ED_TIME = 101,
+ UNI_CMD_MIB_CNT_S_P20BW_7_ED_TIME = 102,
+ UNI_CMD_MIB_CNT_S_P20BW_8_ED_TIME = 103,
+ UNI_CMD_MIB_CNT_S_P20BW_9_ED_TIME = 104,
+ UNI_CMD_MIB_CNT_S_P20BW_10_ED_TIME = 105,
+ UNI_CMD_MIB_CNT_S_P20BW_11_ED_TIME = 106,
+ UNI_CMD_MIB_CNT_S_P20BW_12_ED_TIME = 107,
+ UNI_CMD_MIB_CNT_S_P20BW_13_ED_TIME = 108,
+ UNI_CMD_MIB_CNT_S_P20BW_14_ED_TIME = 109,
+ UNI_CMD_MIB_CNT_S_P20BW_15_ED_TIME = 110,
+ UNI_CMD_MIB_CNT_TX_BW_320MHZ = 111,
+ UNI_CMD_MIB_CNT_TX_DDLMT_RNG0 = 112, /* Rng0~Rng4 112~116 */
+ UNI_CMD_MIB_CNT_BSS0_FRAME_RETRY_3 = 117, /* 117~120 */
+ UNI_CMD_MIB_CNT_BSS0_TX = 121, /* 121~124 */
+ UNI_CMD_MIB_CNT_BSS0_TX_DATA = 125, /* 125~128 */
+ UNI_CMD_MIB_CNT_BSS0_TX_BYTE = 129, /* 129~132 */
+ UNI_CMD_MIB_CNT_RX_OK_BSS0 = 133, /* 133~136 */
+ UNI_CMD_MIB_CNT_RX_BYTE_BSS0 = 137, /* 137~140 */
+ UNI_CMD_MIB_CNT_RX_DATA_BSS0 = 141, /* 141~144 */
+ UNI_CMD_MIB_CNT_MBSS0_TX_OK = 145, /* 145~160 */
+ UNI_CMD_MIB_CNT_MBSS0_TX_BYTE = 161, /* 161~176 */
+ UNI_CMD_MIB_CNT_RX_OK_MBSS0 = 177, /* 177~192 */
+ UNI_CMD_MIB_CNT_RX_BYTE_MBSS0 = 193, /* 193~208 */
+ UNI_CMD_MIB_CNT_AMPDU = 209,
+ UNI_CMD_MIB_CNT_AMPDU_MPDU = 210,
+ UNI_CMD_MIB_CNT_AMPDU_ACKED = 211,
+ UNI_CMD_MIB_CNT_MAX_NUM
+};
+
+enum ENUM_UNI_CMD_GET_STATISTICS_TAG {
+ UNI_CMD_GET_STATISTICS_TAG_BASIC = 0,
+ UNI_CMD_GET_STATISTICS_TAG_LINK_QUALITY = 1,
+ UNI_CMD_GET_STATISTICS_TAG_STA = 2,
+ UNI_CMD_GET_STATISTICS_TAG_BUG_REPORT = 3,
+ UNI_CMD_GET_STATISTICS_TAG_EML_STATS = 6,
+ UNI_CMD_GET_STATISTICS_TAG_REGULAR_STATS = 7,
+ UNI_CMD_GET_STATISTICS_TAG_BSS_LINK_QUALITY = 8,
+ UNI_CMD_GET_STATISTICS_TAG_GET_ML_CHNL_COND = 9,
+ UNI_CMD_GET_STATISTICS_TAG_STAT_WTBL = 10,
+ UNI_CMD_GET_STATISTICS_TAG_LINK_LAYER_STATS = 0x80,
+ UNI_CMD_GET_STATISTICS_TAG_PPDU_LATENCY,
+ UNI_CMD_GET_STATISTICS_TAG_CURRENT_TX_RATE,
+ UNI_CMD_GET_STATISTICS_TAG_BEACON_REPORT = 0x86,
+ UNI_CMD_GET_STATISTICS_TAG_BSS_CURRENT_TX_RATE = 0x87,
+ UNI_CMD_GET_STATISTICS_TAG_BSS_PRED_TX_BITRATE = 0x88,
+};
+
#define UNI_WOW_DETECT_TYPE_MAGIC BIT(0)
#define UNI_WOW_DETECT_TYPE_ANY BIT(1)
#define UNI_WOW_DETECT_TYPE_DISCONNECT BIT(2)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/Makefile b/drivers/net/wireless/mediatek/mt76/mt7925/Makefile
index f9dcc0bba393..3863a89b8974 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/Makefile
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/Makefile
@@ -4,7 +4,7 @@ obj-$(CONFIG_MT7925_COMMON) += mt7925-common.o
obj-$(CONFIG_MT7925E) += mt7925e.o
obj-$(CONFIG_MT7925U) += mt7925u.o
-mt7925-common-y := mac.o mcu.o regd.o main.o init.o debugfs.o nan.o
+mt7925-common-y := mac.o mcu.o regd.o main.o init.o debugfs.o nan.o stats.o
mt7925-common-$(CONFIG_NL80211_TESTMODE) += testmode.o
mt7925e-y := pci.o pci_mac.o pci_mcu.o
mt7925u-y := usb.o
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c b/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c
index d01ff49de47a..7a40d5ac47da 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c
@@ -1,8 +1,11 @@
// SPDX-License-Identifier: BSD-3-Clause-Clear
/* Copyright (C) 2023 MediaTek Inc. */
+#include <linux/math64.h>
+
#include "mt7925.h"
#include "mcu.h"
+#include "stats.h"
static int
mt7925_reg_set(void *data, u64 val)
@@ -286,6 +289,346 @@ static int mt7925_chip_reset(void *data, u64 val)
DEFINE_DEBUGFS_ATTRIBUTE(fops_reset, NULL, mt7925_chip_reset, "%lld\n");
+static const u32 stats_query_mib_counters[] = {
+ UNI_CMD_MIB_CNT_RX_FCS_ERR,
+ UNI_CMD_MIB_CNT_RX_FIFO_OVERFLOW,
+ UNI_CMD_MIB_CNT_RX_MPDU,
+ UNI_CMD_MIB_CNT_AMPDU_RX_COUNT,
+ UNI_CMD_MIB_CNT_PF_DROP,
+ UNI_CMD_MIB_CNT_LEN_MISMATCH,
+ UNI_CMD_MIB_CNT_CHANNEL_IDLE,
+ UNI_CMD_MIB_CNT_CCA_NAV_TX_TIME,
+ UNI_CMD_MIB_CNT_MDRDY,
+ UNI_CMD_MIB_CNT_RX_CCK_MDRDY_TIME,
+ UNI_CMD_MIB_CNT_RX_OFDM_LG_MIXED_MDRDY_TIME,
+ UNI_CMD_MIB_CNT_RX_OFDM_GREEN_MDRDY_TIME,
+ UNI_CMD_MIB_CNT_P_CCA_TIME,
+ UNI_CMD_MIB_CNT_S_CCA_TIME,
+ UNI_CMD_MIB_CNT_P_ED_TIME,
+ UNI_CMD_MIB_CNT_BCN_TX,
+ UNI_CMD_MIB_CNT_TX_BW_40MHZ,
+ UNI_CMD_MIB_CNT_TX_BW_80MHZ,
+ UNI_CMD_MIB_CNT_TX_BW_160MHZ,
+ UNI_CMD_MIB_CNT_BSS0_BA_MISS,
+ UNI_CMD_MIB_CNT_BSS0_RTS_TX_CNT,
+ UNI_CMD_MIB_CNT_BSS0_FRAME_RETRY,
+ UNI_CMD_MIB_CNT_BSS0_FRAME_RETRY_2,
+ UNI_CMD_MIB_CNT_BSS0_RTS_RETRY,
+ UNI_CMD_MIB_CNT_BSS0_ACK_FAIL,
+ UNI_CMD_MIB_CNT_AMPDU,
+ UNI_CMD_MIB_CNT_AMPDU_MPDU,
+ UNI_CMD_MIB_CNT_AMPDU_ACKED,
+};
+
+/* Dump the whole firmware auto-rate table and mark the entry currently in use
+ * with "-->"
+ */
+static void
+mt7925_dump_auto_rate_table(struct seq_file *s,
+ const struct mt7925_wtbl_rate *wr)
+{
+ u8 fcap = wr->fcap;
+ u8 rate_idx = wr->rate_idx;
+ u8 cbrn = wr->cbrn;
+ int i;
+
+ seq_printf(s, "%-22s Cur Rate Index = %u\n", " ", rate_idx);
+
+ for (i = 0; i < MT7925_AUTO_RATE_NUM; i++) {
+ u16 rate_code = le16_to_cpu(wr->rate_code[i]);
+ u8 txmode = MT7925_HW_RATE_TO_MODE(rate_code);
+ u8 rate = MT7925_HW_RATE_TO_MCS(rate_code);
+ u8 nsts = MT7925_HW_RATE_TO_NSS(rate_code) + 1;
+ u8 stbc = MT7925_HW_RATE_TO_STBC(rate_code);
+ u8 dcm = MT7925_HW_RATE_TO_DCM(rate_code);
+ u8 ersu106t = MT7925_HW_RATE_TO_106T(rate_code);
+ u8 sgi = mt7925_wtbl_get_sgi(wr, txmode, fcap);
+ u8 ldpc = mt7925_wtbl_get_ldpc(wr, txmode);
+ const char *mode_str = "N/A";
+
+ if (dcm)
+ rate = MT7925_HW_RATE_UNMASK_DCM(rate);
+ if (ersu106t)
+ rate = MT7925_HW_RATE_UNMASK_106T(rate);
+
+ if (txmode < ARRAY_SIZE(mt7925_tx_mode_str))
+ mode_str = mt7925_tx_mode_str[txmode];
+
+ seq_printf(s, "Rate index[%d] %s", i,
+ rate_idx == i ? "--> " : " ");
+
+ /* rate/MCS field */
+ if (txmode == MT7925_TX_RATE_MODE_CCK)
+ seq_printf(s, "%s, ",
+ mt7925_tx_rate_cck_str[rate & 0x3]);
+ else if (txmode == MT7925_TX_RATE_MODE_OFDM)
+ seq_printf(s, "%s, ", mt7925_hw_rate_ofdm_str(rate));
+ else if (txmode == MT7925_TX_RATE_MODE_HTMIX ||
+ txmode == MT7925_TX_RATE_MODE_HTGF)
+ seq_printf(s, "MCS%d, ", rate);
+ else
+ seq_printf(s, "%s%d_MCS%d, ",
+ stbc ? "NSTS" : "NSS", nsts, rate);
+
+ /* bandwidth: mirror the CCK/OFDM -> BW20 and cbrn handling */
+ if (txmode == MT7925_TX_RATE_MODE_CCK ||
+ txmode == MT7925_TX_RATE_MODE_OFDM)
+ seq_printf(s, "%s, ", mt7925_tx_rate_bw_str[0]);
+ else if (i > cbrn)
+ seq_printf(s, "%s, ",
+ fcap < 5 ?
+ (fcap > MT7925_BW_20 ?
+ mt7925_tx_rate_bw_str[fcap - 1] :
+ mt7925_tx_rate_bw_str[fcap]) :
+ mt7925_tx_rate_bw_str[5]);
+ else
+ seq_printf(s, "%s, ",
+ fcap < 5 ? mt7925_tx_rate_bw_str[fcap] :
+ mt7925_tx_rate_bw_str[5]);
+
+ /* GI / preamble */
+ if (txmode == MT7925_TX_RATE_MODE_CCK)
+ seq_printf(s, "%s, ", rate < 4 ? "LP" : "SP");
+ else if (txmode == MT7925_TX_RATE_MODE_OFDM)
+ ; /* OFDM has no GI/preamble to print */
+ else if (txmode == MT7925_TX_RATE_MODE_HTMIX ||
+ txmode == MT7925_TX_RATE_MODE_HTGF ||
+ txmode == MT7925_TX_RATE_MODE_VHT ||
+ txmode == MT7925_TX_RATE_MODE_PLR)
+ seq_printf(s, "%s, ", sgi == 0 ? "LGI" : "SGI");
+ else
+ seq_printf(s, "%s, ",
+ sgi == 0 ? "SGI" :
+ (sgi == 1 ? "MGI" : "LGI"));
+
+ /* mode, DCM, 106t, STBC, coding */
+ seq_printf(s, "%s%s%s%s%s\n",
+ mode_str,
+ dcm ? ", DCM" : "", ersu106t ? ", 106t" : "",
+ stbc ? ", STBC, " : ", ",
+ (ldpc == 0 ||
+ txmode == MT7925_TX_RATE_MODE_CCK ||
+ txmode == MT7925_TX_RATE_MODE_OFDM) ?
+ "BCC" : "LDPC");
+ }
+}
+
+static int mt792x_stats(struct seq_file *s, void *data)
+{
+ s8 rssi = 0;
+ u8 band_idx = 0;
+ bool wtbl_rate_valid = false;
+ int ret = 0;
+ int i = 0;
+ u32 stats_value = 0;
+ u32 tx_link_speed_kbps = 0;
+ u64 tx_total = 0;
+ u64 tx_fail = 0;
+ u64 tx_per = 0;
+ u32 tx_per_rem = 0;
+ /* UNI_CMD_MIB_DATA */
+ u64 mib_values[ARRAY_SIZE(stats_query_mib_counters)];
+ struct mt792x_dev *dev = dev_get_drvdata(s->private);
+ struct mt7925_wtbl_rate wtbl_rate;
+ struct mt7925_sta_stats *stats = NULL;
+
+ band_idx = dev->mphy.band_idx;
+
+ stats = kzalloc(sizeof(*stats), GFP_KERNEL);
+ if (!stats)
+ return -ENOMEM;
+
+ /* Get STA_STATS */
+ mt792x_mutex_acquire(dev);
+ ret = mt792x_mcu_get_stat(dev, stats);
+ mt792x_mutex_release(dev);
+ if (ret)
+ goto out;
+
+ /* Get MIB_STATS */
+ mt792x_mutex_acquire(dev);
+ ret = mt7925_mcu_get_mib_info(dev, band_idx, stats_query_mib_counters,
+ ARRAY_SIZE(stats_query_mib_counters), mib_values);
+ mt792x_mutex_release(dev);
+ if (ret)
+ goto out;
+
+ /* Get Link_Speed and RSSI */
+ mt792x_mutex_acquire(dev);
+ ret = mt7925_mcu_get_link_quality(dev, stats->bss_idx, &rssi, &tx_link_speed_kbps);
+ mt792x_mutex_release(dev);
+ if (ret)
+ goto out;
+
+ /* Get the firmware auto-rate table (best effort; do not abort the
+ * whole stats dump if the WTBL query is not available).
+ */
+ mt792x_mutex_acquire(dev);
+ if (!mt792x_mcu_get_wtbl_rate(dev, stats->wtbl_idx, &wtbl_rate))
+ wtbl_rate_valid = true;
+ mt792x_mutex_release(dev);
+
+ /* Print STA_STATS info */
+ seq_printf(s, "(STA) connected AP MAC Address = %pM\n", stats->mac_addr);
+ seq_printf(s, "%-22s BssIdx = [%u]\n", " ", stats->bss_idx);
+ seq_printf(s, "%-22s StaRecIdx = [%u]\n",
+ " ", stats->sta_idx);
+ seq_printf(s, "%-22s RSSI = %d\n", " ", rssi);
+ seq_printf(s, "%-22s Link_Speed = %u (kbit/s)\n", " ", tx_link_speed_kbps);
+
+ seq_printf(s, "%-22s temperature = %u\n",
+ " ", stats->temperature);
+
+ stats_value = le32_to_cpu(stats->transmit_count) - le32_to_cpu(stats->transmit_fail_count);
+ seq_printf(s, "%-22s Tx success = %u\n",
+ " ", stats_value);
+
+ stats_value = le32_to_cpu(stats->tx_fail_count) - le32_to_cpu(stats->tx_life_timeout_count);
+ seq_printf(s, "%-22s Tx fail to Rcv ACK after retry = %u\n",
+ " ", stats_value);
+
+ seq_printf(s, "%-22s Rx Mpdu = %u\n",
+ " ", le32_to_cpu(stats->mib[band_idx].rx_mpdu_cnt));
+
+ seq_printf(s, "%-22s Rx Fcs Error = %u (MPDU)\n",
+ " ", le32_to_cpu(stats->mib[band_idx].fcs_error));
+ seq_printf(s, "%-22s Rx FIFO full = %u (MPDU)\n",
+ " ", le32_to_cpu(stats->mib[band_idx].rx_fifo_full));
+
+ for (i = 0; i < STAT_MIB_CNT_TRX_AGG_RANGE_MAX_NUM; i++)
+ seq_printf(s, "%-22s TRX_AGG_RANGE[%d] = %u (PPDU)\n",
+ " ", i, le32_to_cpu(stats->mib[band_idx].tx_range_ampdu_cnt[i]));
+
+ seq_printf(s, "%-22s MPDUs in AMPDUs transmitted = %u (MPDU)\n",
+ " ", le32_to_cpu(stats->mib[band_idx].ampdu_tx_sf_cnt));
+ seq_printf(s, "%-22s MPDUs in AMPDUs transmitted with ACK reply = %u (MPDU)\n",
+ " ", le32_to_cpu(stats->mib[band_idx].ampdu_tx_ack_sf_cnt));
+ seq_printf(s, "%-22s rate1_tx_cnt = %u\n",
+ " ", le32_to_cpu(stats->rate1_tx_cnt));
+ seq_printf(s, "%-22s rate1_fail_cnt = %u\n",
+ " ", le32_to_cpu(stats->rate1_fail_cnt));
+ seq_printf(s, "%-22s train_up = %u\n",
+ " ", le16_to_cpu(stats->train_up));
+ seq_printf(s, "%-22s train_down = %u\n",
+ " ", le16_to_cpu(stats->train_down));
+ seq_printf(s, "%-22s is_force_tx_stream = %u\n",
+ " ", stats->is_force_tx_stream);
+ seq_printf(s, "%-22s is_force_se_off = %u\n",
+ " ", stats->is_force_se_off);
+ seq_printf(s, "%-22s max_ampdu_factor = %u\n",
+ " ", stats->max_ampdu_factor);
+ seq_printf(s, "%-22s tx_rate_up_penalty = %u\n",
+ " ", stats->tx_rate_up_penalty);
+ seq_printf(s, "%-22s low_traffic_mode = %u\n",
+ " ", stats->low_traffic_mode);
+ seq_printf(s, "%-22s low_traffic_count = %u\n",
+ " ", stats->low_traffic_count);
+ seq_printf(s, "%-22s low_traffic_dashboard = %u\n",
+ " ", stats->low_traffic_dashboard);
+ seq_printf(s, "%-22s dynamic_sgi_state = %u\n",
+ " ", stats->dynamic_sgi_state);
+ seq_printf(s, "%-22s dynamic_sgi_score = %u\n",
+ " ", stats->dynamic_sgi_score);
+ seq_printf(s, "%-22s dynamic_bw_state = %u\n",
+ " ", stats->dynamic_bw_state);
+ seq_printf(s, "%-22s dynamic_gband_256qam_state = %u\n",
+ " ", stats->dynamic_gband_256qam_state);
+ seq_printf(s, "%-22s vht_non_sp_rate_state = %u\n",
+ " ", stats->vht_non_sp_rate_state);
+
+ /* Decode the TX Vector BBP latch to show the current TX MCS rate */
+ if (band_idx < MT7925_STA_STATS_BAND_NUM)
+ mt7925_print_last_tx_rate(s, stats->tx_vector[band_idx].txv);
+
+ /* Dump the whole firmware auto-rate table and mark the entry currently */
+ if (wtbl_rate_valid)
+ mt7925_dump_auto_rate_table(s, &wtbl_rate);
+
+ seq_puts(s, "\nmib state:\n");
+ /* ===Rx Related Counters=== */
+ seq_puts(s, "=== Rx Related Counters ===\n");
+ seq_printf(s, "%-22s Rx with CRC = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_RX_FCS_ERR]);
+ seq_printf(s, "%-22s Rx drop due to out of resource = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_RX_FIFO_OVERFLOW]);
+ seq_printf(s, "%-22s Rx Mpdu = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_RX_MPDU]);
+ seq_printf(s, "%-22s Rx AMpdu = %llu (PPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_AMPDU_RX_COUNT]);
+ seq_printf(s, "%-22s Rx PF Drop = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_PF_DROP]);
+ seq_printf(s, "%-22s Rx Len Mismatch = %llu (PPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_LEN_MISMATCH]);
+
+ /* ===Phy/Timing Related Counters=== */
+ seq_puts(s, "\n=== Phy/Timing Related Counters ===\n");
+ seq_printf(s, "%-22s ChannelIdleCnt = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_CHANNEL_IDLE]);
+ seq_printf(s, "%-22s CCA_NAV_Tx_Time = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_CCA_NAV_TX_TIME]);
+ seq_printf(s, "%-22s Rx_MDRDY_CNT = %llu (PPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_MDRDY]);
+ seq_printf(s, "%-22s CCK_MDRDY = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_RX_CCK_MDRDY_TIME]);
+ seq_printf(s, "%-22s OFDM_MDRDY = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_RX_OFDM_LG_MIXED_MDRDY_TIME]);
+ seq_printf(s, "%-22s OFDM_GREEN_MDRDY = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_RX_OFDM_GREEN_MDRDY_TIME]);
+ seq_printf(s, "%-22s Prim CCA Time = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_P_CCA_TIME]);
+ seq_printf(s, "%-22s Sec CCA Time = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_S_CCA_TIME]);
+ seq_printf(s, "%-22s Prim ED Time = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_P_ED_TIME]);
+
+ /* ===Tx Related Counters(Generic)=== */
+ seq_puts(s, "\n=== Tx Related Counters(Generic) ===\n");
+ seq_printf(s, "%-22s BeaconTxCnt = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_BCN_TX]);
+ seq_printf(s, "%-22s Tx 40MHz Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_TX_BW_40MHZ]);
+ seq_printf(s, "%-22s Tx 80MHz Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_TX_BW_80MHZ]);
+ seq_printf(s, "%-22s Tx 160MHz Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_TX_BW_160MHZ]);
+
+ /* ===BSSID[0] Related Counters=== */
+ seq_puts(s, "\n=== BSSID[0] Related Counters ===\n");
+ seq_printf(s, "%-22s BA Miss Cnt = %llu (PPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_BSS0_BA_MISS]);
+ seq_printf(s, "%-22s RTS Tx Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_BSS0_RTS_TX_CNT]);
+ seq_printf(s, "%-22s Frame Retry Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_BSS0_FRAME_RETRY]);
+ seq_printf(s, "%-22s Frame Retry 2 Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_BSS0_FRAME_RETRY_2]);
+ seq_printf(s, "%-22s RTS Retry Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_BSS0_RTS_RETRY]);
+ seq_printf(s, "%-22s Ack Failed Cnt = %llu (PPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_BSS0_ACK_FAIL]);
+
+ /* ===AMPDU Related Counters=== */
+ seq_puts(s, "\n=== AMPDU Related Counters ===\n");
+ seq_printf(s, "%-22s Tx AMPDU_Pkt_Cnt = %llu (PPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_AMPDU]);
+ seq_printf(s, "%-22s Tx AMPDU_MPDU_Pkt_Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_AMPDU_MPDU]);
+ seq_printf(s, "%-22s AMPDU Tx success = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_AMPDU_ACKED]);
+
+ tx_total = mib_values[QUERY_MIB_CNT_AMPDU_MPDU];
+ tx_fail = mib_values[QUERY_MIB_CNT_AMPDU_MPDU] - mib_values[QUERY_MIB_CNT_AMPDU_ACKED];
+ tx_per = tx_total == 0 ? 0 : div64_u64(1000 * tx_fail, tx_total);
+ seq_printf(s, "%-22s AMPDU Tx fail count = %llu (MPDU), PER=%llu.%1llu%%\n",
+ " ",
+ tx_fail,
+ div_u64_rem(tx_per, 10, &tx_per_rem), (u64)tx_per_rem);
+
+out:
+ kfree(stats);
+ return ret;
+}
+
int mt7925_init_debugfs(struct mt792x_dev *dev)
{
struct dentry *dir;
@@ -313,5 +656,8 @@ int mt7925_init_debugfs(struct mt792x_dev *dev)
mt792x_pm_stats);
debugfs_create_file("deep-sleep", 0600, dir, dev, &fops_ds);
+ debugfs_create_devm_seqfile(dev->mt76.dev, "stats", dir,
+ mt792x_stats);
+
return 0;
}
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/stats.c b/drivers/net/wireless/mediatek/mt76/mt7925/stats.c
new file mode 100644
index 000000000000..9a47953062bc
--- /dev/null
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/stats.c
@@ -0,0 +1,585 @@
+// SPDX-License-Identifier: BSD-3-Clause-Clear
+/* Copyright (C) 2026 MediaTek Inc. */
+
+#include <linux/firmware.h>
+#include <linux/fs.h>
+#include "mt7925.h"
+#include "mcu.h"
+#include "mac.h"
+#include "stats.h"
+
+#define MT7925_STA_STATS_VERSION 1
+#define MT7925_STA_STATS_VALID 1U
+
+#define MT7925_MIB_MAX_CNT 32
+#define MT7925_MIB_CNT_MAX_NUM 512 /* firmware UNI_CMD_MIB_CNT_MAX_NUM upper bound */
+
+#define MT7925_LQ_BAND_NUM 4 /* Link quality band */
+
+const char * const mt7925_tx_mode_str[] = {
+ "CCK", "OFDM", "MM", "GF", "VHT", "PLR",
+ "PLRP", "ALR", "HE_SU", "HE_ER", "HE_TRIG", "HE_MU",
+ "EHT_ER", "TOF", "EHT_TRIG", "EHT_MU"
+};
+
+const char * const mt7925_tx_rate_cck_str[] = {
+ "1M", "2M", "5.5M", "11M", "N/A"
+};
+
+const char * const mt7925_tx_rate_bw_str[] = {
+ "BW20", "BW40", "BW80", "BW160/BW8080", "BW320", "N/A"
+};
+
+struct mt7925_sta_stats_req {
+ u8 rsv[4];
+ __le16 tag;
+ __le16 len;
+ u8 sta_rec_idx;
+ u8 rsv1;
+ u8 lls_read_clear;
+ u8 reset_counter;
+} __packed;
+
+struct mt7925_sta_stats_event {
+ u8 rsv[4];
+ __le16 tag;
+ __le16 len;
+ struct mt7925_sta_stats stats;
+} __packed;
+
+/* WTBL auto-rate request TLV, mirror Gen4m struct UNI_CMD_STAT_WTBL.
+ * The RA tag reuses UNI_CMD_ID_STATISTICS_WTBL (10).
+ */
+struct mt7925_wtbl_rate_req {
+ u8 rsv[4];
+ __le16 tag;
+ __le16 len;
+ __le16 wlan_idx;
+ __le16 rsv1;
+} __packed;
+
+/* WTBL auto-rate event TLV. Firmware answers cmd tag 10 with event tag 12. */
+struct mt7925_wtbl_rate_event {
+ u8 rsv[4];
+ struct mt7925_wtbl_rate rate;
+} __packed;
+
+/* fixed field, mirror UNI_(CMD|EVENT)_MIB_INFO */
+struct mt7925_mib_hdr {
+ u8 band_idx;
+ u8 rsv[3];
+} __packed;
+
+/* One requested/returned MIB counter, mirror UNI_(CMD|EVENT)_MIB_DATA_T. */
+struct mt7925_mib_data {
+ __le16 tag; /* UNI_(CMD|EVENT)_MIB_DATA = 0 */
+ __le16 len;
+ __le32 counter; /* MIB counter ID (enum mt7925_mib_counter) */
+ __le64 data; /* only valid on event; ignored on cmd */
+} __packed;
+
+/* per-band link quality entry, mirror UNI_LINK_QUALITY */
+struct mt7925_link_quality {
+ s8 rssi; /* moving-average beacon RSSI of connected AP */
+ s8 link_quality; /* always 0 */
+ __le16 link_speed; /* TX rate1, unit 500 Kb/s */
+ u8 medium_busy_pct; /* always 0 */
+ u8 is_ready; /* 0: fields invalid, 1: valid */
+ u8 rsv[2];
+} __packed;
+
+/* request TLV, mirror UNI_CMD_LINK_QUALITY (header + tag/len only) */
+struct mt7925_lq_req {
+ u8 rsv[4]; /* UNI_CMD_GET_STATISTICS fixed field */
+ __le16 tag; /* UNI_CMD_GET_STATISTICS_TAG_LINK_QUALITY = 1 */
+ __le16 len;
+} __packed;
+
+/* event TLV, mirror UNI_EVENT_LINK_QUALITY */
+struct mt7925_lq_event {
+ u8 rsv[4]; /* UNI_EVENT_STATISTICS fixed field */
+ __le16 tag; /* UNI_EVENT_ID_STATISTICS_LINK_QUALITY = 1 */
+ __le16 len;
+ struct mt7925_link_quality lq[MT7925_LQ_BAND_NUM];
+} __packed;
+
+static void
+mt7925_get_ap_sta_rec_idx_iter(void *data, u8 *mac,
+ struct ieee80211_vif *vif)
+{
+ struct mt7925_sta_rec_lookup *lookup = data;
+ struct mt792x_vif *mvif;
+ struct mt792x_sta *msta;
+
+ if (lookup->sta_rec_idx >= 0)
+ return;
+
+ if (vif->type != NL80211_IFTYPE_STATION || !vif->cfg.assoc)
+ return;
+
+ mvif = (struct mt792x_vif *)vif->drv_priv;
+ msta = mvif->wep_sta;
+ if (!msta)
+ return;
+
+ if (!msta->deflink.wcid.sta)
+ return;
+
+ lookup->sta_rec_idx = msta->deflink.wcid.idx;
+}
+
+int
+mt7925_get_ap_sta_rec_idx(struct mt792x_dev *dev, u8 *sta_rec_idx)
+{
+ struct mt7925_sta_rec_lookup lookup = {
+ .sta_rec_idx = -1,
+ };
+
+ lockdep_assert_held(&dev->mt76.mutex);
+
+ ieee80211_iterate_active_interfaces(dev->mphy.hw,
+ IEEE80211_IFACE_ITER_RESUME_ALL,
+ mt7925_get_ap_sta_rec_idx_iter,
+ &lookup);
+
+ if (lookup.sta_rec_idx < 0)
+ return -ENOTCONN;
+
+ if (lookup.sta_rec_idx > U8_MAX)
+ return -ERANGE;
+
+ *sta_rec_idx = lookup.sta_rec_idx;
+
+ return 0;
+}
+
+/* Decode the TX Vector BBP latch and print the last TX rate */
+void
+mt7925_print_last_tx_rate(struct seq_file *s, const __le32 le_txv[3])
+{
+ const char *mode_str = "N/A";
+ u8 rate, txmode, frmode, sgi, ldpc, nsts, stbc, spe, dcm, ersu106t;
+ s8 txpwr, pos_txpwr;
+ u32 txv[3];
+
+ txv[0] = le32_to_cpu(le_txv[0]);
+ txv[1] = le32_to_cpu(le_txv[1]);
+ txv[2] = le32_to_cpu(le_txv[2]);
+
+ if (txv[0] == 0xffffffff) {
+ seq_printf(s, "%-22s %s = %s\n", " ", "Last TX Rate", "N/A");
+ seq_printf(s, "%-22s %s = %s\n",
+ " ", "Chip Out TX Power", "N/A");
+ return;
+ }
+
+ rate = MT7925_TXV_GET_TX_RATE(txv);
+ txmode = MT7925_TXV_GET_TX_MODE(txv);
+ frmode = MT7925_TXV_GET_TX_FRMODE(txv);
+ nsts = MT7925_TXV_GET_TX_NSTS(txv) + 1;
+ sgi = MT7925_TXV_GET_TX_SGI(txv);
+ ldpc = MT7925_TXV_GET_TX_LDPC(txv);
+ stbc = MT7925_TXV_GET_TX_STBC(txv);
+ txpwr = MT7925_TXV_GET_TX_PWR(txv);
+ spe = MT7925_TXV_GET_TX_SPE_IDX(txv);
+ dcm = MT7925_TXV_GET_TX_DCM(txv);
+ ersu106t = MT7925_TXV_GET_TX_106T(txv);
+
+ if (dcm)
+ rate = MT7925_TXV_RATE_UNMASK_DCM(rate);
+ if (ersu106t)
+ rate = MT7925_TXV_RATE_UNMASK_106T(rate);
+
+ if (txmode < ARRAY_SIZE(mt7925_tx_mode_str))
+ mode_str = mt7925_tx_mode_str[txmode];
+
+ seq_printf(s, "%-22s %s = ", " ", "Last TX Rate");
+
+ /* rate/MCS field */
+ if (txmode == MT7925_TX_RATE_MODE_CCK)
+ seq_printf(s, "%s, ",
+ rate < 4 ? mt7925_tx_rate_cck_str[rate] :
+ (((rate >= 5) && (rate <= 7)) ?
+ mt7925_tx_rate_cck_str[rate - 4] :
+ mt7925_tx_rate_cck_str[4]));
+ else if (txmode == MT7925_TX_RATE_MODE_OFDM)
+ seq_printf(s, "%s, ", mt7925_hw_rate_ofdm_str(rate));
+ else if (txmode == MT7925_TX_RATE_MODE_HTMIX ||
+ txmode == MT7925_TX_RATE_MODE_HTGF)
+ seq_printf(s, "MCS%d, ", rate);
+ else
+ seq_printf(s, "%s%d_MCS%d, ",
+ stbc ? "NSTS" : "NSS", nsts, rate);
+
+ /* bandwidth */
+ seq_printf(s, "%s, ",
+ frmode < 5 ? mt7925_tx_rate_bw_str[frmode] :
+ mt7925_tx_rate_bw_str[5]);
+
+ /* GI / preamble */
+ if (txmode == MT7925_TX_RATE_MODE_CCK)
+ seq_printf(s, "%s, ", rate < 4 ? "LP" : "SP");
+ else if (txmode == MT7925_TX_RATE_MODE_OFDM)
+ ; /* OFDM has no GI/preamble to print */
+ else if (txmode == MT7925_TX_RATE_MODE_HTMIX ||
+ txmode == MT7925_TX_RATE_MODE_HTGF ||
+ txmode == MT7925_TX_RATE_MODE_VHT ||
+ txmode == MT7925_TX_RATE_MODE_PLR)
+ seq_printf(s, "%s, ", sgi == 0 ? "LGI" : "SGI");
+ else
+ seq_printf(s, "%s, ",
+ sgi == 0 ? "SGI" : (sgi == 1 ? "MGI" : "LGI"));
+
+ /* mode, DCM, 106t, STBC, coding, SPE */
+ seq_printf(s, "%s%s%s%s%s%s%d\n",
+ mode_str,
+ dcm ? ", DCM" : "", ersu106t ? ", 106t" : "",
+ stbc ? ", STBC, " : ", ", ldpc == 0 ? "BCC" : "LDPC",
+ ", SPE", spe);
+
+ /* chip out TX power (unit: 0.5 dBm) */
+ pos_txpwr = (txpwr < 0) ? (~txpwr + 1) : txpwr;
+ seq_printf(s, "%-22s %s = %c%d.%1d dBm\n",
+ " ", "Chip Out TX Power",
+ (txpwr < 0) ? '-' : '+',
+ (pos_txpwr / 2), 5 * (pos_txpwr % 2));
+}
+
+/* Guard interval used for the auto-rate entries, mirror Gen4m
+ * connac3x_get_sgi_info_from_fw(): pick the per-mode/per-BW flag.
+ */
+u8
+mt7925_wtbl_get_sgi(const struct mt7925_wtbl_rate *wr, u8 txmode, u8 fcap)
+{
+ bool he = (txmode == MT7925_TX_RATE_MODE_HE_SU ||
+ txmode == MT7925_TX_RATE_MODE_HE_ER ||
+ txmode == MT7925_TX_RATE_MODE_HE_TRIG ||
+ txmode == MT7925_TX_RATE_MODE_HE_MU);
+ bool eht = (txmode == MT7925_TX_RATE_MODE_EHT_ER ||
+ txmode == MT7925_TX_RATE_MODE_EHT_TRIG ||
+ txmode == MT7925_TX_RATE_MODE_EHT_MU);
+
+ if (eht)
+ return wr->gi_eht;
+
+ switch (fcap) {
+ case MT7925_BW_20:
+ return he ? wr->g2_he : wr->g2;
+ case MT7925_BW_40:
+ return he ? wr->g4_he : wr->g4;
+ case MT7925_BW_80:
+ return he ? wr->g8_he : wr->g8;
+ case MT7925_BW_160:
+ return he ? wr->g16_he : wr->g16;
+ default:
+ return 0;
+ }
+}
+
+/* Coding used for the auto-rate entries, mirror Gen4m
+ * connac3x_wtbl_get_ldpc_info_from_fw(): 0 = BCC, 1 = LDPC.
+ */
+u8
+mt7925_wtbl_get_ldpc(const struct mt7925_wtbl_rate *wr, u8 txmode)
+{
+ switch (txmode) {
+ case MT7925_TX_RATE_MODE_HTMIX:
+ case MT7925_TX_RATE_MODE_HTGF:
+ return wr->ht_ldpc;
+ case MT7925_TX_RATE_MODE_VHT:
+ return wr->vht_ldpc;
+ case MT7925_TX_RATE_MODE_HE_SU:
+ case MT7925_TX_RATE_MODE_HE_ER:
+ case MT7925_TX_RATE_MODE_HE_TRIG:
+ case MT7925_TX_RATE_MODE_HE_MU:
+ return wr->he_ldpc;
+ case MT7925_TX_RATE_MODE_EHT_ER:
+ case MT7925_TX_RATE_MODE_EHT_TRIG:
+ case MT7925_TX_RATE_MODE_EHT_MU:
+ return wr->eht_ldpc;
+ default:
+ return 0;
+ }
+}
+
+int mt792x_mcu_get_stat(struct mt792x_dev *dev,
+ struct mt7925_sta_stats *stats)
+{
+ u16 len = 0;
+ int ret = 0;
+ struct mt7925_sta_stats_event *event = NULL;
+ struct sk_buff *skb = NULL;
+ struct mt7925_sta_stats_req req = {
+ .tag = cpu_to_le16(UNI_CMD_ID_STATISTICS_STA),
+ .len = cpu_to_le16(sizeof(req) - sizeof(req.rsv)),
+ .lls_read_clear = false,
+ .reset_counter = false,
+ };
+
+ ret = mt7925_get_ap_sta_rec_idx(dev, &req.sta_rec_idx);
+ if (ret)
+ return ret;
+
+ if (!stats)
+ return -EINVAL;
+
+ ret = mt76_mcu_send_and_get_msg(&dev->mt76,
+ MCU_UNI_QUERY(GET_STAT_INFO),
+ &req, sizeof(req), true, &skb);
+
+ if (ret)
+ return ret;
+
+ event = (struct mt7925_sta_stats_event *)skb->data;
+
+ if (le16_to_cpu(event->tag) != UNI_CMD_ID_STATISTICS_STA) {
+ ret = -EPROTO;
+ goto out;
+ }
+
+ len = le16_to_cpu(event->len);
+ if (len < sizeof(*event) - sizeof(event->rsv) ||
+ len > skb->len - sizeof(event->rsv) || !IS_ALIGNED(len, 4)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+
+ if (event->stats.version != MT7925_STA_STATS_VERSION) {
+ ret = -EPROTO;
+ goto out;
+ }
+
+ if (!(le32_to_cpu(event->stats.flags) & MT7925_STA_STATS_VALID)) {
+ ret = -ENOENT;
+ goto out;
+ }
+
+ memcpy(stats, &event->stats, sizeof(*stats));
+
+out:
+ dev_kfree_skb(skb);
+ return ret;
+}
+
+int mt7925_mcu_get_mib_info(struct mt792x_dev *dev, u8 band_idx,
+ const u32 *counters, u8 counter_num,
+ u64 *values)
+{
+ u8 *req;
+ u8 *tlv, *tlv_end;
+ u16 req_len;
+ int i, ret;
+ struct mt7925_mib_hdr *req_hdr;
+ struct mt7925_mib_data *req_tlv;
+ struct sk_buff *skb;
+
+ if (!counters || !values || !counter_num ||
+ counter_num > MT7925_MIB_MAX_CNT)
+ return -EINVAL;
+
+ /* clear output; counters not returned by firmware stay 0 */
+ memset(values, 0, counter_num * sizeof(*values));
+
+ req_len = sizeof(struct mt7925_mib_hdr) +
+ counter_num * sizeof(struct mt7925_mib_data);
+
+ req = kzalloc(req_len, GFP_KERNEL);
+ if (!req)
+ return -ENOMEM;
+
+ req_hdr = (struct mt7925_mib_hdr *)req;
+ req_hdr->band_idx = band_idx;
+
+ req_tlv = (struct mt7925_mib_data *)(req + sizeof(*req_hdr));
+ for (i = 0; i < counter_num; i++) {
+ req_tlv[i].tag = cpu_to_le16(UNI_CMD_MIB_DATA);
+ req_tlv[i].len = cpu_to_le16(sizeof(struct mt7925_mib_data));
+ req_tlv[i].counter = cpu_to_le32(counters[i]);
+ }
+
+ ret = mt76_mcu_send_and_get_msg(&dev->mt76,
+ MCU_UNI_QUERY(GET_MIB_INFO), req,
+ req_len, true, &skb);
+ kfree(req);
+ if (ret)
+ return ret;
+
+ if (skb->len < sizeof(struct mt7925_mib_hdr)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+
+ /* walk the returned TLVs, match each counter ID to the request */
+ tlv = skb->data + sizeof(struct mt7925_mib_hdr);
+ tlv_end = skb->data + skb->len;
+
+ while (tlv + sizeof(struct mt7925_mib_data) <= tlv_end) {
+ struct mt7925_mib_data *evt_tlv =
+ (struct mt7925_mib_data *)tlv;
+ u16 tag = le16_to_cpu(evt_tlv->tag);
+ u16 len = le16_to_cpu(evt_tlv->len);
+ u32 counter;
+
+ if (len < sizeof(struct mt7925_mib_data) ||
+ tlv + len > tlv_end)
+ break;
+
+ if (tag == UNI_CMD_MIB_DATA) {
+ counter = le32_to_cpu(evt_tlv->counter);
+
+ if (counter < MT7925_MIB_CNT_MAX_NUM) {
+ for (i = 0; i < counter_num; i++) {
+ if (counters[i] == counter) {
+ values[i] =
+ le64_to_cpu(evt_tlv->data);
+ break;
+ }
+ }
+ }
+ }
+
+ tlv += len;
+ }
+
+ ret = 0;
+
+out:
+ dev_kfree_skb(skb);
+
+ return ret;
+}
+
+char *mt7925_hw_rate_ofdm_str(u8 ofdm_idx)
+{
+ char * const ofdm_str[] = {
+ "6M", "9M", "12M", "18M", "24M", "36M", "48M", "54M", "N/A"
+ };
+
+ switch (ofdm_idx) {
+ case 11: return ofdm_str[0]; /* 6M */
+ case 15: return ofdm_str[1]; /* 9M */
+ case 10: return ofdm_str[2]; /* 12M */
+ case 14: return ofdm_str[3]; /* 18M */
+ case 9: return ofdm_str[4]; /* 24M */
+ case 13: return ofdm_str[5]; /* 36M */
+ case 8: return ofdm_str[6]; /* 48M */
+ case 12: return ofdm_str[7]; /* 54M */
+ default: return ofdm_str[8];
+ }
+}
+
+/* mt792x_mcu_get_wtbl_rate - fetch the firmware auto-rate table for a WCID
+ * @dev: device
+ * @wlan_idx: HW WTBL / WCID index (see mt7925_sta_stats.wtbl_idx)
+ * @rate: out, filled with the auto-rate table snapshot
+ *
+ * Mirror of Gen4m wlanoidGetStatWtbl(): send UNI_CMD_GET_STAT_INFO with the
+ * WTBL rate TLV (cmd tag 10) and parse the reply (event tag 12) into @rate.
+ *
+ * Return: 0 on success, negative errno otherwise.
+ */
+int mt792x_mcu_get_wtbl_rate(struct mt792x_dev *dev, u16 wlan_idx,
+ struct mt7925_wtbl_rate *rate)
+{
+ int ret;
+ struct mt7925_wtbl_rate_event *event = NULL;
+ struct sk_buff *skb = NULL;
+ struct mt7925_wtbl_rate_req req = {
+ .tag = cpu_to_le16(UNI_CMD_ID_STATISTICS_WTBL),
+ .len = cpu_to_le16(sizeof(req) - sizeof(req.rsv)),
+ .wlan_idx = cpu_to_le16(wlan_idx),
+ };
+
+ if (!rate)
+ return -EINVAL;
+
+ ret = mt76_mcu_send_and_get_msg(&dev->mt76,
+ MCU_UNI_QUERY(GET_STAT_INFO),
+ &req, sizeof(req), true, &skb);
+ if (ret)
+ return ret;
+
+ if (skb->len < sizeof(*event)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+
+ event = (struct mt7925_wtbl_rate_event *)skb->data;
+
+ if (le16_to_cpu(event->rate.tag) != MT7925_UNI_EVENT_STATISTICS_WTBL) {
+ ret = -EPROTO;
+ goto out;
+ }
+
+ memcpy(rate, &event->rate, sizeof(*rate));
+ ret = 0;
+
+out:
+ dev_kfree_skb(skb);
+ return ret;
+}
+
+/**
+ * mt7925_mcu_get_link_quality - query RSSI and TX link speed for a band
+ * @dev: device
+ * @band_idx: band/BSS index (0..3)
+ * @rssi: out, moving-average beacon RSSI in dBm (may be NULL)
+ * @tx_link_speed_kbps: out, TX rate in kbit/s (may be NULL)
+ *
+ * Return: 0 on success, -ENOENT if the band's link quality is not ready.
+ *
+ * Only RSSI and TX rate come from firmware here; for RX rate read wcid->rate.
+ */
+int mt7925_mcu_get_link_quality(struct mt792x_dev *dev, u8 band_idx,
+ s8 *rssi, u32 *tx_link_speed_kbps)
+{
+ int ret;
+ struct mt7925_lq_event *event;
+ struct mt7925_link_quality *lq;
+ struct sk_buff *skb;
+ struct mt7925_lq_req req = {
+ .tag = cpu_to_le16(UNI_CMD_ID_STATISTICS_LINK_QUALITY),
+ .len = cpu_to_le16(sizeof(req) - sizeof(req.rsv)),
+ };
+
+ if (band_idx >= MT7925_LQ_BAND_NUM)
+ return -EINVAL;
+
+ ret = mt76_mcu_send_and_get_msg(&dev->mt76,
+ MCU_UNI_QUERY(GET_STAT_INFO), &req,
+ sizeof(req), true, &skb);
+ if (ret)
+ return ret;
+
+ if (skb->len < sizeof(*event)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+
+ event = (struct mt7925_lq_event *)skb->data;
+
+ if (le16_to_cpu(event->tag) != UNI_CMD_ID_STATISTICS_LINK_QUALITY) {
+ ret = -EPROTO;
+ goto out;
+ }
+
+ lq = &event->lq[band_idx];
+ if (!lq->is_ready) {
+ ret = -ENOENT;
+ goto out;
+ }
+
+ if (rssi)
+ *rssi = lq->rssi;
+
+ /* firmware reports TX rate in 500 Kb/s units -> kbit/s */
+ if (tx_link_speed_kbps)
+ *tx_link_speed_kbps = le16_to_cpu(lq->link_speed) * 500;
+
+ ret = 0;
+
+out:
+ dev_kfree_skb(skb);
+
+ return ret;
+}
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/stats.h b/drivers/net/wireless/mediatek/mt76/mt7925/stats.h
new file mode 100644
index 000000000000..a70b9eb3aa80
--- /dev/null
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/stats.h
@@ -0,0 +1,309 @@
+/* SPDX-License-Identifier: BSD-3-Clause-Clear */
+/* Copyright (C) 2026 MediaTek Inc. */
+
+#ifndef __MT7925_STATS_H
+#define __MT7925_STATS_H
+
+#include <linux/if_ether.h>
+#include <linux/types.h>
+#include "../mt76_connac_mcu.h"
+
+#define MT7925_STA_STATS_AC_NUM 4
+#define MT7925_STA_STATS_BAND_NUM 2
+#define MT7925_STA_STATS_AGG_RANGE_NUM 16
+#define MT7925_STA_STATS_TX_QUALITY_NUM 4
+#define MT7925_TX_MODE_NUM 16
+#define MT7925_TX_RATE_CCK_NUM 5
+#define MT7925_TX_RATE_BW_NUM 6
+
+/* HW rate code (16-bit) field extract, mirror Gen4m CONNAC3X_HW_TX_RATE_TO_*.
+ * The auto-rate table stores one rate code per entry.
+ */
+#define MT7925_HW_RATE_TO_MCS(_x) (((_x) & GENMASK(5, 0)) >> 0)
+#define MT7925_HW_RATE_TO_MODE(_x) (((_x) & GENMASK(9, 6)) >> 6)
+#define MT7925_HW_RATE_TO_NSS(_x) (((_x) & GENMASK(13, 10)) >> 10)
+#define MT7925_HW_RATE_TO_STBC(_x) (((_x) & BIT(14)) >> 14)
+#define MT7925_HW_RATE_TO_DCM(_x) (((_x) & BIT(4)) >> 4)
+#define MT7925_HW_RATE_TO_106T(_x) (((_x) & BIT(5)) >> 5)
+#define MT7925_HW_RATE_UNMASK_DCM(_r) ((u8)(_r) & 0xef)
+#define MT7925_HW_RATE_UNMASK_106T(_r) ((u8)(_r) & 0xdf)
+
+/* Number of entries in the firmware auto-rate table (mirror Gen4m
+ * AUTO_RATE_NUM). RA cycles through these and reports which one is in use
+ * via wtbl->rate_idx.
+ */
+#define MT7925_AUTO_RATE_NUM 8
+
+/* Firmware EVENT_STATISTICS sub-event tags. The WTBL rate request uses cmd tag
+ * UNI_CMD_ID_STATISTICS_WTBL (10) but the firmware replies with event tag 12.
+ */
+#define MT7925_UNI_EVENT_STATISTICS_WTBL 12
+
+/* CONNAC3X TX Vector BBP latch bit fields (mirror cmm_asic_connac3x.h).
+ * These operate on the 3-DWORD tx_vector[band].txv[3] array.
+ */
+#define MT7925_TXV_GET_TX_RATE(_txv) ((_txv)[2] & 0x7f)
+#define MT7925_TXV_GET_TX_LDPC(_txv) (((_txv)[2] & (0x1 << 7)) >> 7)
+#define MT7925_TXV_GET_TX_STBC(_txv) (((_txv)[0] & (0x3 << 6)) >> 6)
+#define MT7925_TXV_GET_TX_FRMODE(_txv) (((_txv)[0] & (0x7 << 8)) >> 8)
+#define MT7925_TXV_GET_TX_MODE(_txv) (((_txv)[0] & (0xf << 12)) >> 12)
+#define MT7925_TXV_GET_TX_NSTS(_txv) (((_txv)[2] & (0xfU << 28)) >> 28)
+#define MT7925_TXV_GET_TX_PWR(_txv) (((_txv)[0] & (0xff << 16)) >> 16)
+#define MT7925_TXV_GET_TX_SGI(_txv) (((_txv)[1] & (0x3 << 26)) >> 26)
+#define MT7925_TXV_GET_TX_SPE_IDX(_txv) (((_txv)[0] & (0x1f << 0)) >> 0)
+#define MT7925_TXV_GET_TX_DCM(_txv) (((_txv)[2] & (0x1 << 4)) >> 4)
+#define MT7925_TXV_GET_TX_106T(_txv) (((_txv)[2] & (0x1 << 5)) >> 5)
+#define MT7925_TXV_RATE_UNMASK_DCM(_r) ((u8)(_r) & 0xef)
+#define MT7925_TXV_RATE_UNMASK_106T(_r) ((u8)(_r) & 0xdf)
+
+extern const char * const mt7925_tx_mode_str[MT7925_TX_MODE_NUM];
+extern const char * const mt7925_tx_rate_cck_str[MT7925_TX_RATE_CCK_NUM];
+extern const char * const mt7925_tx_rate_bw_str[MT7925_TX_RATE_BW_NUM];
+
+/* TX rate mode, mirror Gen4m TX_RATE_MODE_* (see nic_rate.h). Used to decode
+ * both the TX Vector BBP latch and the firmware auto-rate table rate codes.
+ */
+enum mt7925_tx_rate_mode {
+ MT7925_TX_RATE_MODE_CCK = 0,
+ MT7925_TX_RATE_MODE_OFDM = 1,
+ MT7925_TX_RATE_MODE_HTMIX = 2,
+ MT7925_TX_RATE_MODE_HTGF = 3,
+ MT7925_TX_RATE_MODE_VHT = 4,
+ MT7925_TX_RATE_MODE_PLR = 5,
+ MT7925_TX_RATE_MODE_HE_SU = 8,
+ MT7925_TX_RATE_MODE_HE_ER = 9,
+ MT7925_TX_RATE_MODE_HE_TRIG = 10,
+ MT7925_TX_RATE_MODE_HE_MU = 11,
+ MT7925_TX_RATE_MODE_EHT_ER = 13,
+ MT7925_TX_RATE_MODE_EHT_TRIG = 14,
+ MT7925_TX_RATE_MODE_EHT_MU = 15,
+};
+
+enum {
+ UNI_CMD_MIB_DATA,
+};
+
+enum stats_query_mib_array_index {
+ QUERY_MIB_CNT_RX_FCS_ERR = 0,
+ QUERY_MIB_CNT_RX_FIFO_OVERFLOW,
+ QUERY_MIB_CNT_RX_MPDU,
+ QUERY_MIB_CNT_AMPDU_RX_COUNT,
+ QUERY_MIB_CNT_PF_DROP,
+ QUERY_MIB_CNT_LEN_MISMATCH,
+ QUERY_MIB_CNT_CHANNEL_IDLE,
+ QUERY_MIB_CNT_CCA_NAV_TX_TIME,
+ QUERY_MIB_CNT_MDRDY,
+ QUERY_MIB_CNT_RX_CCK_MDRDY_TIME,
+ QUERY_MIB_CNT_RX_OFDM_LG_MIXED_MDRDY_TIME,
+ QUERY_MIB_CNT_RX_OFDM_GREEN_MDRDY_TIME,
+ QUERY_MIB_CNT_P_CCA_TIME,
+ QUERY_MIB_CNT_S_CCA_TIME,
+ QUERY_MIB_CNT_P_ED_TIME,
+ QUERY_MIB_CNT_BCN_TX,
+ QUERY_MIB_CNT_TX_BW_40MHZ,
+ QUERY_MIB_CNT_TX_BW_80MHZ,
+ QUERY_MIB_CNT_TX_BW_160MHZ,
+ QUERY_MIB_CNT_BSS0_BA_MISS,
+ QUERY_MIB_CNT_BSS0_RTS_TX_CNT,
+ QUERY_MIB_CNT_BSS0_FRAME_RETRY,
+ QUERY_MIB_CNT_BSS0_FRAME_RETRY_2,
+ QUERY_MIB_CNT_BSS0_RTS_RETRY,
+ QUERY_MIB_CNT_BSS0_ACK_FAIL,
+ QUERY_MIB_CNT_AMPDU,
+ QUERY_MIB_CNT_AMPDU_MPDU,
+ QUERY_MIB_CNT_AMPDU_ACKED,
+ QUERY_MIB_CNT_MAX_NUM,
+};
+
+enum stats_trx_agg_range {
+ STAT_MIB_CNT_TRX_AGG_RANGE0_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE1_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE2_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE3_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE4_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE5_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE6_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE7_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE_MAX_NUM
+};
+
+enum {
+ MT7925_BW_20 = 0,
+ MT7925_BW_40,
+ MT7925_BW_80,
+ MT7925_BW_160,
+};
+
+struct mt7925_sta_stats_ac {
+ __le32 tx_fail;
+ __le32 tx_retry;
+} __packed;
+
+struct mt7925_sta_stats_tx_vector {
+ __le32 txv[3];
+} __packed;
+
+struct mt7925_sta_stats_mib {
+ __le32 rx_mpdu_cnt;
+ __le32 fcs_error;
+ __le32 rx_fifo_full;
+ __le32 ampdu_tx_sf_cnt;
+ __le32 ampdu_tx_ack_sf_cnt;
+ __le32 tx_range_ampdu_cnt[MT7925_STA_STATS_AGG_RANGE_NUM];
+} __packed;
+
+/* Firmware EVENT_STA_STATISTICS body. */
+struct mt7925_sta_stats {
+ u8 version;
+ u8 rsv[3];
+ __le32 flags;
+
+ u8 sta_idx;
+ u8 bss_idx;
+ u8 wtbl_idx;
+ u8 rsv1;
+
+ u8 mac_addr[ETH_ALEN];
+ u8 per;
+ u8 rcpi;
+
+ __le32 phy_mode;
+ __le16 link_speed; /* 0.5 Mbit/s units */
+ u8 link_quality;
+ u8 link_reserved;
+
+ __le32 tx_count;
+ __le32 tx_fail_count;
+ __le32 tx_life_timeout_count;
+ __le32 tx_done_air_time;
+ __le32 transmit_count;
+ __le32 transmit_fail_count;
+
+ struct mt7925_sta_stats_ac ac[MT7925_STA_STATS_AC_NUM];
+
+ u8 temperature;
+ u8 skip_ar;
+ u8 ar_table_idx;
+ u8 rate_entry_idx;
+ u8 rate_entry_idx_prev;
+ u8 tx_sgi_detect_pass_cnt;
+ u8 ave_per;
+ u8 rsv2;
+
+ __le32 agg_range_ctrl[2];
+ u8 range_mode;
+ u8 rsv3[3];
+ __le32 agg_range_ctrl_ext[6];
+
+ u8 ar_state_curr;
+ u8 ar_state_prev;
+ u8 ar_action_type;
+ u8 highest_rate_cnt;
+ u8 lowest_rate_cnt;
+ u8 rsv4;
+ __le16 train_up;
+ __le16 train_down;
+ u8 rsv5[2];
+ __le32 rate1_tx_cnt;
+ __le32 rate1_fail_cnt;
+
+ struct mt7925_sta_stats_tx_vector tx_vector[MT7925_STA_STATS_BAND_NUM];
+ struct mt7925_sta_stats_mib mib[MT7925_STA_STATS_BAND_NUM];
+
+ u8 is_force_tx_stream;
+ u8 is_force_se_off;
+ __le16 ra_running_cnt;
+ u8 ra_status;
+ u8 max_ampdu_factor;
+ u8 tx_quality[MT7925_STA_STATS_TX_QUALITY_NUM];
+ u8 tx_rate_up_penalty;
+ u8 low_traffic_mode;
+ u8 low_traffic_count;
+ u8 low_traffic_dashboard;
+ u8 dynamic_sgi_state;
+ u8 dynamic_sgi_score;
+ u8 dynamic_bw_state;
+ u8 dynamic_gband_256qam_state;
+ u8 vht_non_sp_rate_state;
+ u8 rsv6[3];
+ u8 rsv7[2];
+} __packed;
+
+struct mt7925_sta_rec_lookup {
+ int sta_rec_idx;
+};
+
+/* Firmware auto-rate table snapshot, mirror Gen4m struct UNI_EVENT_STAT_WTBL.
+ *
+ * The firmware RA maintains up to MT7925_AUTO_RATE_NUM candidate rates in
+ * rate_code[]. rate_idx tells which entry is currently in use, so a dump can
+ * point at it with "-->". fcap is the bandwidth capability and the ldpc/sgi
+ * flags describe the per-mode coding/guard-interval used for those rates.
+ */
+struct mt7925_wtbl_rate {
+ __le16 tag;
+ __le16 len;
+ u8 rx_rcpi0;
+ u8 rx_rcpi1;
+ u8 cbrn;
+ u8 fcap;
+ u8 spe_idx;
+ u8 g2; /* SGI enabled, non-HE, BW20 */
+ u8 g4; /* SGI enabled, non-HE, BW40 */
+ u8 g8; /* SGI enabled, non-HE, BW80 */
+ u8 g16; /* SGI enabled, non-HE, BW160 */
+ u8 g2_he; /* GI, HE, BW20 */
+ u8 g4_he; /* GI, HE, BW40 */
+ u8 g8_he; /* GI, HE, BW80 */
+ u8 g16_he; /* GI, HE, BW160 */
+ u8 gi_eht; /* GI, EHT */
+ u8 ht_ldpc;
+ u8 vht_ldpc;
+ u8 he_ldpc;
+ u8 eht_ldpc;
+ u8 af;
+ u8 rate_idx; /* index into rate_code[] currently in use */
+ __le16 rate_code[MT7925_AUTO_RATE_NUM];
+ __le32 rsv[4];
+} __packed;
+
+int
+mt7925_get_ap_sta_rec_idx(struct mt792x_dev *dev, u8 *sta_rec_idx);
+
+void
+mt7925_print_last_tx_rate(struct seq_file *s, const __le32 le_txv[3]);
+
+u8
+mt7925_wtbl_get_sgi(const struct mt7925_wtbl_rate *wr, u8 txmode, u8 fcap);
+
+u8
+mt7925_wtbl_get_ldpc(const struct mt7925_wtbl_rate *wr, u8 txmode);
+
+char *mt7925_hw_rate_ofdm_str(u8 ofdm_idx);
+
+int mt792x_mcu_get_wtbl_rate(struct mt792x_dev *dev, u16 wlan_idx,
+ struct mt7925_wtbl_rate *rate);
+
+int mt792x_mcu_get_stat(struct mt792x_dev *dev,
+ struct mt7925_sta_stats *stats);
+
+int mt7925_mcu_get_mib_info(struct mt792x_dev *dev, u8 band_idx,
+ const u32 *counters, u8 counter_num,
+ u64 *values);
+
+/* GET_STAT_INFO (UNI_CMD_ID_GET_STATISTICS = 0x23),
+ * sub-command UNI_CMD_GET_STATISTICS_TAG_LINK_QUALITY = 1.
+ *
+ * @band_idx: band/BSS index (0..3)
+ * @rssi: out, moving-average beacon RSSI (dBm), may be NULL
+ * @tx_link_speed_kbps: out, TX link speed in kbit/s, may be NULL
+ *
+ * Returns 0 on success, -ENOENT if the band's link quality is not ready.
+ *
+ * NOTE: firmware provides only RSSI and TX rate here. RX rate/bandwidth are
+ * not in this event; read wcid->rate for the RX rate.
+ */
+int mt7925_mcu_get_link_quality(struct mt792x_dev *dev, u8 band_idx,
+ s8 *rssi, u32 *tx_link_speed_kbps);
+
+#endif
--
2.45.2
^ permalink raw reply related [flat|nested] 12+ messages in thread* Re: [PATCH v3] wifi: mt76: mt7928: Add debugfs for stats to provide debug purpose
2026-08-27 8:44 ` [PATCH v3] " Eason Lai
@ 2026-08-28 7:39 ` kernel test robot
2026-08-31 3:28 ` [PATCH v4] " Eason Lai
1 sibling, 0 replies; 12+ messages in thread
From: kernel test robot @ 2026-08-28 7:39 UTC (permalink / raw)
To: Eason Lai, nbd, lorenzo
Cc: oe-kbuild-all, linux-wireless, linux-mediatek, kun.wu, deren.wu,
sean.wang, quan.zhou, ryder.lee, leon.yen, litien.chang, jb.tsai,
michael.shih, eason.lai, kernel test robot
Hi Eason,
kernel test robot noticed the following build warnings:
[auto build test WARNING on wireless-next/main]
[also build test WARNING on linus/master next-20260826]
[cannot apply to wireless/main v7.2]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Eason-Lai/wifi-mt76-mt7928-Add-debugfs-for-stats-to-provide-debug-purpose/20260827-164427
base: https://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless-next.git main
patch link: https://lore.kernel.org/r/20260827084427.1624598-1-eason.lai%40mediatek.com
patch subject: [PATCH v3] wifi: mt76: mt7928: Add debugfs for stats to provide debug purpose
config: sparc-randconfig-r073-20260828 (https://download.01.org/0day-ci/archive/20260828/202608281532.s7sXQ2Cc-lkp@intel.com/config)
compiler: sparc-linux-gcc (GCC) 14.3.0
smatch: v0.5.0-9187-g5189e3fb
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202608281532.s7sXQ2Cc-lkp@intel.com/
smatch warnings:
drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:437 mt792x_stats() warn: inconsistent indenting
drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:540 mt792x_stats() error: testing array offset 'band_idx' after use.
vim +437 drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c
415
416 static int mt792x_stats(struct seq_file *s, void *data)
417 {
418 s8 rssi = 0;
419 u8 band_idx = 0;
420 bool wtbl_rate_valid = false;
421 int ret = 0;
422 int i = 0;
423 u32 stats_value = 0;
424 u32 tx_link_speed_kbps = 0;
425 u64 tx_total = 0;
426 u64 tx_fail = 0;
427 u64 tx_per = 0;
428 u32 tx_per_rem = 0;
429 /* UNI_CMD_MIB_DATA */
430 u64 mib_values[ARRAY_SIZE(stats_query_mib_counters)];
431 struct mt792x_dev *dev = dev_get_drvdata(s->private);
432 struct mt7925_wtbl_rate wtbl_rate;
433 struct mt7925_sta_stats *stats = NULL;
434
435 band_idx = dev->mphy.band_idx;
436
> 437 stats = kzalloc(sizeof(*stats), GFP_KERNEL);
438 if (!stats)
439 return -ENOMEM;
440
441 /* Get STA_STATS */
442 mt792x_mutex_acquire(dev);
443 ret = mt792x_mcu_get_stat(dev, stats);
444 mt792x_mutex_release(dev);
445 if (ret)
446 goto out;
447
448 /* Get MIB_STATS */
449 mt792x_mutex_acquire(dev);
450 ret = mt7925_mcu_get_mib_info(dev, band_idx, stats_query_mib_counters,
451 ARRAY_SIZE(stats_query_mib_counters), mib_values);
452 mt792x_mutex_release(dev);
453 if (ret)
454 goto out;
455
456 /* Get Link_Speed and RSSI */
457 mt792x_mutex_acquire(dev);
458 ret = mt7925_mcu_get_link_quality(dev, stats->bss_idx, &rssi, &tx_link_speed_kbps);
459 mt792x_mutex_release(dev);
460 if (ret)
461 goto out;
462
463 /* Get the firmware auto-rate table (best effort; do not abort the
464 * whole stats dump if the WTBL query is not available).
465 */
466 mt792x_mutex_acquire(dev);
467 if (!mt792x_mcu_get_wtbl_rate(dev, stats->wtbl_idx, &wtbl_rate))
468 wtbl_rate_valid = true;
469 mt792x_mutex_release(dev);
470
471 /* Print STA_STATS info */
472 seq_printf(s, "(STA) connected AP MAC Address = %pM\n", stats->mac_addr);
473 seq_printf(s, "%-22s BssIdx = [%u]\n", " ", stats->bss_idx);
474 seq_printf(s, "%-22s StaRecIdx = [%u]\n",
475 " ", stats->sta_idx);
476 seq_printf(s, "%-22s RSSI = %d\n", " ", rssi);
477 seq_printf(s, "%-22s Link_Speed = %u (kbit/s)\n", " ", tx_link_speed_kbps);
478
479 seq_printf(s, "%-22s temperature = %u\n",
480 " ", stats->temperature);
481
482 stats_value = le32_to_cpu(stats->transmit_count) - le32_to_cpu(stats->transmit_fail_count);
483 seq_printf(s, "%-22s Tx success = %u\n",
484 " ", stats_value);
485
486 stats_value = le32_to_cpu(stats->tx_fail_count) - le32_to_cpu(stats->tx_life_timeout_count);
487 seq_printf(s, "%-22s Tx fail to Rcv ACK after retry = %u\n",
488 " ", stats_value);
489
490 seq_printf(s, "%-22s Rx Mpdu = %u\n",
491 " ", le32_to_cpu(stats->mib[band_idx].rx_mpdu_cnt));
492
493 seq_printf(s, "%-22s Rx Fcs Error = %u (MPDU)\n",
494 " ", le32_to_cpu(stats->mib[band_idx].fcs_error));
495 seq_printf(s, "%-22s Rx FIFO full = %u (MPDU)\n",
496 " ", le32_to_cpu(stats->mib[band_idx].rx_fifo_full));
497
498 for (i = 0; i < STAT_MIB_CNT_TRX_AGG_RANGE_MAX_NUM; i++)
499 seq_printf(s, "%-22s TRX_AGG_RANGE[%d] = %u (PPDU)\n",
500 " ", i, le32_to_cpu(stats->mib[band_idx].tx_range_ampdu_cnt[i]));
501
502 seq_printf(s, "%-22s MPDUs in AMPDUs transmitted = %u (MPDU)\n",
503 " ", le32_to_cpu(stats->mib[band_idx].ampdu_tx_sf_cnt));
504 seq_printf(s, "%-22s MPDUs in AMPDUs transmitted with ACK reply = %u (MPDU)\n",
505 " ", le32_to_cpu(stats->mib[band_idx].ampdu_tx_ack_sf_cnt));
506 seq_printf(s, "%-22s rate1_tx_cnt = %u\n",
507 " ", le32_to_cpu(stats->rate1_tx_cnt));
508 seq_printf(s, "%-22s rate1_fail_cnt = %u\n",
509 " ", le32_to_cpu(stats->rate1_fail_cnt));
510 seq_printf(s, "%-22s train_up = %u\n",
511 " ", le16_to_cpu(stats->train_up));
512 seq_printf(s, "%-22s train_down = %u\n",
513 " ", le16_to_cpu(stats->train_down));
514 seq_printf(s, "%-22s is_force_tx_stream = %u\n",
515 " ", stats->is_force_tx_stream);
516 seq_printf(s, "%-22s is_force_se_off = %u\n",
517 " ", stats->is_force_se_off);
518 seq_printf(s, "%-22s max_ampdu_factor = %u\n",
519 " ", stats->max_ampdu_factor);
520 seq_printf(s, "%-22s tx_rate_up_penalty = %u\n",
521 " ", stats->tx_rate_up_penalty);
522 seq_printf(s, "%-22s low_traffic_mode = %u\n",
523 " ", stats->low_traffic_mode);
524 seq_printf(s, "%-22s low_traffic_count = %u\n",
525 " ", stats->low_traffic_count);
526 seq_printf(s, "%-22s low_traffic_dashboard = %u\n",
527 " ", stats->low_traffic_dashboard);
528 seq_printf(s, "%-22s dynamic_sgi_state = %u\n",
529 " ", stats->dynamic_sgi_state);
530 seq_printf(s, "%-22s dynamic_sgi_score = %u\n",
531 " ", stats->dynamic_sgi_score);
532 seq_printf(s, "%-22s dynamic_bw_state = %u\n",
533 " ", stats->dynamic_bw_state);
534 seq_printf(s, "%-22s dynamic_gband_256qam_state = %u\n",
535 " ", stats->dynamic_gband_256qam_state);
536 seq_printf(s, "%-22s vht_non_sp_rate_state = %u\n",
537 " ", stats->vht_non_sp_rate_state);
538
539 /* Decode the TX Vector BBP latch to show the current TX MCS rate */
> 540 if (band_idx < MT7925_STA_STATS_BAND_NUM)
541 mt7925_print_last_tx_rate(s, stats->tx_vector[band_idx].txv);
542
543 /* Dump the whole firmware auto-rate table and mark the entry currently */
544 if (wtbl_rate_valid)
545 mt7925_dump_auto_rate_table(s, &wtbl_rate);
546
547 seq_puts(s, "\nmib state:\n");
548 /* ===Rx Related Counters=== */
549 seq_puts(s, "=== Rx Related Counters ===\n");
550 seq_printf(s, "%-22s Rx with CRC = %llu (MPDU)\n",
551 " ", mib_values[QUERY_MIB_CNT_RX_FCS_ERR]);
552 seq_printf(s, "%-22s Rx drop due to out of resource = %llu (MPDU)\n",
553 " ", mib_values[QUERY_MIB_CNT_RX_FIFO_OVERFLOW]);
554 seq_printf(s, "%-22s Rx Mpdu = %llu (MPDU)\n",
555 " ", mib_values[QUERY_MIB_CNT_RX_MPDU]);
556 seq_printf(s, "%-22s Rx AMpdu = %llu (PPDU)\n",
557 " ", mib_values[QUERY_MIB_CNT_AMPDU_RX_COUNT]);
558 seq_printf(s, "%-22s Rx PF Drop = %llu (MPDU)\n",
559 " ", mib_values[QUERY_MIB_CNT_PF_DROP]);
560 seq_printf(s, "%-22s Rx Len Mismatch = %llu (PPDU)\n",
561 " ", mib_values[QUERY_MIB_CNT_LEN_MISMATCH]);
562
563 /* ===Phy/Timing Related Counters=== */
564 seq_puts(s, "\n=== Phy/Timing Related Counters ===\n");
565 seq_printf(s, "%-22s ChannelIdleCnt = %llu\n",
566 " ", mib_values[QUERY_MIB_CNT_CHANNEL_IDLE]);
567 seq_printf(s, "%-22s CCA_NAV_Tx_Time = %llu\n",
568 " ", mib_values[QUERY_MIB_CNT_CCA_NAV_TX_TIME]);
569 seq_printf(s, "%-22s Rx_MDRDY_CNT = %llu (PPDU)\n",
570 " ", mib_values[QUERY_MIB_CNT_MDRDY]);
571 seq_printf(s, "%-22s CCK_MDRDY = %llu\n",
572 " ", mib_values[QUERY_MIB_CNT_RX_CCK_MDRDY_TIME]);
573 seq_printf(s, "%-22s OFDM_MDRDY = %llu\n",
574 " ", mib_values[QUERY_MIB_CNT_RX_OFDM_LG_MIXED_MDRDY_TIME]);
575 seq_printf(s, "%-22s OFDM_GREEN_MDRDY = %llu\n",
576 " ", mib_values[QUERY_MIB_CNT_RX_OFDM_GREEN_MDRDY_TIME]);
577 seq_printf(s, "%-22s Prim CCA Time = %llu\n",
578 " ", mib_values[QUERY_MIB_CNT_P_CCA_TIME]);
579 seq_printf(s, "%-22s Sec CCA Time = %llu\n",
580 " ", mib_values[QUERY_MIB_CNT_S_CCA_TIME]);
581 seq_printf(s, "%-22s Prim ED Time = %llu\n",
582 " ", mib_values[QUERY_MIB_CNT_P_ED_TIME]);
583
584 /* ===Tx Related Counters(Generic)=== */
585 seq_puts(s, "\n=== Tx Related Counters(Generic) ===\n");
586 seq_printf(s, "%-22s BeaconTxCnt = %llu\n",
587 " ", mib_values[QUERY_MIB_CNT_BCN_TX]);
588 seq_printf(s, "%-22s Tx 40MHz Cnt = %llu (MPDU)\n",
589 " ", mib_values[QUERY_MIB_CNT_TX_BW_40MHZ]);
590 seq_printf(s, "%-22s Tx 80MHz Cnt = %llu (MPDU)\n",
591 " ", mib_values[QUERY_MIB_CNT_TX_BW_80MHZ]);
592 seq_printf(s, "%-22s Tx 160MHz Cnt = %llu (MPDU)\n",
593 " ", mib_values[QUERY_MIB_CNT_TX_BW_160MHZ]);
594
595 /* ===BSSID[0] Related Counters=== */
596 seq_puts(s, "\n=== BSSID[0] Related Counters ===\n");
597 seq_printf(s, "%-22s BA Miss Cnt = %llu (PPDU)\n",
598 " ", mib_values[QUERY_MIB_CNT_BSS0_BA_MISS]);
599 seq_printf(s, "%-22s RTS Tx Cnt = %llu (MPDU)\n",
600 " ", mib_values[QUERY_MIB_CNT_BSS0_RTS_TX_CNT]);
601 seq_printf(s, "%-22s Frame Retry Cnt = %llu (MPDU)\n",
602 " ", mib_values[QUERY_MIB_CNT_BSS0_FRAME_RETRY]);
603 seq_printf(s, "%-22s Frame Retry 2 Cnt = %llu (MPDU)\n",
604 " ", mib_values[QUERY_MIB_CNT_BSS0_FRAME_RETRY_2]);
605 seq_printf(s, "%-22s RTS Retry Cnt = %llu (MPDU)\n",
606 " ", mib_values[QUERY_MIB_CNT_BSS0_RTS_RETRY]);
607 seq_printf(s, "%-22s Ack Failed Cnt = %llu (PPDU)\n",
608 " ", mib_values[QUERY_MIB_CNT_BSS0_ACK_FAIL]);
609
610 /* ===AMPDU Related Counters=== */
611 seq_puts(s, "\n=== AMPDU Related Counters ===\n");
612 seq_printf(s, "%-22s Tx AMPDU_Pkt_Cnt = %llu (PPDU)\n",
613 " ", mib_values[QUERY_MIB_CNT_AMPDU]);
614 seq_printf(s, "%-22s Tx AMPDU_MPDU_Pkt_Cnt = %llu (MPDU)\n",
615 " ", mib_values[QUERY_MIB_CNT_AMPDU_MPDU]);
616 seq_printf(s, "%-22s AMPDU Tx success = %llu (MPDU)\n",
617 " ", mib_values[QUERY_MIB_CNT_AMPDU_ACKED]);
618
619 tx_total = mib_values[QUERY_MIB_CNT_AMPDU_MPDU];
620 tx_fail = mib_values[QUERY_MIB_CNT_AMPDU_MPDU] - mib_values[QUERY_MIB_CNT_AMPDU_ACKED];
621 tx_per = tx_total == 0 ? 0 : div64_u64(1000 * tx_fail, tx_total);
622 seq_printf(s, "%-22s AMPDU Tx fail count = %llu (MPDU), PER=%llu.%1llu%%\n",
623 " ",
624 tx_fail,
625 div_u64_rem(tx_per, 10, &tx_per_rem), (u64)tx_per_rem);
626
627 out:
628 kfree(stats);
629 return ret;
630 }
631
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH v4] wifi: mt76: mt7928: Add debugfs for stats to provide debug purpose
2026-08-27 8:44 ` [PATCH v3] " Eason Lai
2026-08-28 7:39 ` kernel test robot
@ 2026-08-31 3:28 ` Eason Lai
2026-11-12 7:55 ` [PATCH v5 0/2] wifi: mt76: mt7925: per-channel survey dump during scan Michael Shih
1 sibling, 1 reply; 12+ messages in thread
From: Eason Lai @ 2026-08-31 3:28 UTC (permalink / raw)
To: nbd, lorenzo
Cc: linux-wireless, linux-mediatek, kun.wu, deren.wu, sean.wang,
quan.zhou, ryder.lee, leon.yen, litien.chang, jb.tsai,
michael.shih, eason.lai, kernel test robot
From: Michael Shih <michael.shih@mediatek.com>
Usage:
(1) Connect STA to AP
(You may use dhclient CMD to ask for DHCP IP, to use ping to AP)
DHCP IP CMD: dhclient ${WLAN_IF} -v
Example: dhclient wlan0 -v
## (To simulate traffic) Ping 10 packets for every second to AP
ping 192.168.0.XXX -i0.1
(2) (Get stats) Use command, phyX is your WiFi interface:
cat /sys/kernel/debug/ieee80211/phyX/mt76/stats
(3) You will see below stats info:
(STA) connected AP MAC Address = a8:42:a1:46:c4:6b
BssIdx = [0]
StaRecIdx = [1],
RSSI = -25
Link_Speed = 2402000 (kbit/s)
temperature = 40
Tx success = 716
Tx fail to Rcv ACK after retry = 0
Rx Mpdu = 23
Rx Fcs Error = 1 (MPDU)
Rx FIFO full = 0 (MPDU)
TRX_AGG_RANGE[0] = 0 (PPDU)
TRX_AGG_RANGE[1] = 0 (PPDU)
TRX_AGG_RANGE[2] = 0 (PPDU)
TRX_AGG_RANGE[3] = 0 (PPDU)
TRX_AGG_RANGE[4] = 0 (PPDU)
TRX_AGG_RANGE[5] = 0 (PPDU)
TRX_AGG_RANGE[6] = 0 (PPDU)
TRX_AGG_RANGE[7] = 0 (PPDU)
MPDUs in AMPDUs transmitted = 0 (MPDU)
MPDUs in AMPDUs transmitted with ACK reply = 0 (MPDU)
rate1_tx_cnt = 714
rate1_fail_cnt = 223
train_up = 2310
train_down = 3080
is_force_tx_stream = 0
is_force_se_off = 0
max_ampdu_factor = 9
tx_rate_up_penalty = 0
low_traffic_mode = 2
low_traffic_count = 0
low_traffic_dashboard = 3
dynamic_sgi_state = 0
dynamic_sgi_score = 2
dynamic_bw_state = 0
dynamic_gband_256qam_state = 3
vht_non_sp_rate_state = 0
Last TX Rate = 24M, BW20, OFDM, BCC, SPE24
Chip Out TX Power = +16.5 dBm
Cur Rate Index = 0
Rate index[0] --> NSS2_MCS11, BW160/BW8080, SGI, HE_SU, LDPC
Rate index[1] NSS2_MCS9, BW160/BW8080, SGI, HE_SU, LDPC
Rate index[2] NSS2_MCS7, BW160/BW8080, SGI, HE_SU, LDPC
Rate index[3] NSS2_MCS4, BW160/BW8080, SGI, HE_SU, LDPC
Rate index[4] NSS2_MCS3, BW160/BW8080, SGI, HE_SU, LDPC
Rate index[5] NSS1_MCS2, BW160/BW8080, SGI, HE_SU, LDPC
Rate index[6] NSS1_MCS1, BW80, SGI, HE_SU, LDPC
Rate index[7] NSS1_MCS0, BW80, SGI, HE_SU, LDPC
mib state:
=== Rx Related Counters ===
Rx with CRC = 24020 (MPDU)
Rx drop due to out of resource = 0 (MPDU)
Rx Mpdu = 93223 (MPDU)
Rx AMpdu = 668 (PPDU)
Rx PF Drop = 0 (MPDU)
Rx Len Mismatch = 5853 (PPDU)
=== Phy/Timing Related Counters ===
ChannelIdleCnt = 1040434
CCA_NAV_Tx_Time = 18510036
Rx_MDRDY_CNT = 166580 (PPDU)
CCK_MDRDY = 7782
OFDM_MDRDY = 11912731
OFDM_GREEN_MDRDY = 0
Prim CCA Time = 17917650
Sec CCA Time = 14922034
Prim ED Time = 5677528
=== Tx Related Counters(Generic) ===
BeaconTxCnt = 0
Tx 40MHz Cnt = 279 (MPDU)
Tx 80MHz Cnt = 44 (MPDU)
Tx 160MHz Cnt = 712 (MPDU)
=== BSSID[0] Related Counters ===
BA Miss Cnt = 210 (PPDU)
RTS Tx Cnt = 1003 (MPDU)
Frame Retry Cnt = 239 (MPDU)
Frame Retry 2 Cnt = 71 (MPDU)
RTS Retry Cnt = 152 (MPDU)
Ack Failed Cnt = 319 (PPDU)
=== AMPDU Related Counters ===
Tx AMPDU_Pkt_Cnt = 705 (PPDU)
Tx AMPDU_MPDU_Pkt_Cnt = 713 (MPDU)
AMPDU Tx success = 503 (MPDU)
AMPDU Tx fail count = 210 (MPDU), PER=29.4%
Reported-by: kernel test robot <lkp@intel.com>
Closes: https://lore.kernel.org/oe-kbuild-all/202608251101.iQGUrSd0-lkp@intel.com/
Closes: https://lore.kernel.org/oe-kbuild-all/202608251526.VgKMT8Lv-lkp@intel.com/
Closes: https://lore.kernel.org/oe-kbuild-all/202608281532.s7sXQ2Cc-lkp@intel.com/
Signed-off-by: Michael Shih <michael.shih@mediatek.com>
---
v2: (1) Remove redundant code to fix build errors.
(2) Move UNI_CMD_MIB_DATA enum definition from 'mt76/mt76_connac_mcu.h' to
'mt7925/stats.h' to avoid redeclartion with 'mt76/mt7996/mcu.h'
v3: (1) Remove redundant code to fix build errors.
(2) Move UNI_CMD_MIB_DATA enum definition from 'mt76/mt76_connac_mcu.h' to
'mt7925/stats.h' to avoid redeclartion with 'mt76/mt7996/mcu.h'
v4: (1) Fix inconsistent indenting.
(2) Fix array offset 'band_idx' as to before use, not after use.
---
.../wireless/mediatek/mt76/mt76_connac_mcu.h | 173 ++++++
.../wireless/mediatek/mt76/mt7925/Makefile | 2 +-
.../wireless/mediatek/mt76/mt7925/debugfs.c | 366 +++++++++++
.../net/wireless/mediatek/mt76/mt7925/stats.c | 587 ++++++++++++++++++
.../net/wireless/mediatek/mt76/mt7925/stats.h | 309 +++++++++
5 files changed, 1436 insertions(+), 1 deletion(-)
create mode 100644 drivers/net/wireless/mediatek/mt76/mt7925/stats.c
create mode 100644 drivers/net/wireless/mediatek/mt76/mt7925/stats.h
diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
index 0ededa569e9a..bf0f105c786e 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
@@ -1483,6 +1483,179 @@ enum {
MT_NIC_CAP_EML_CAP = 0x22,
};
+enum {
+ UNI_CMD_ID_STATISTICS_BASIC = 0,
+ UNI_CMD_ID_STATISTICS_LINK_QUALITY = 1,
+ UNI_CMD_ID_STATISTICS_STA = 2,
+ UNI_CMD_ID_STATISTICS_BUG_REPORT = 3,
+ UNI_CMD_ID_STATISTICS_TX_STATS = 4,
+ UNI_CMD_ID_STATISTICS_ALL_STATS = 5,
+ UNI_CMD_ID_STATISTICS_EML_STATS = 6,
+ UNI_CMD_ID_STATISTICS_REGULAR_STATS = 7,
+ UNI_CMD_ID_STATISTICS_BSS_LINK_QUALITY = 8,
+ UNI_CMD_ID_STATISTICS_ML_CHNL_COND = 9,
+ UNI_CMD_ID_STATISTICS_WTBL = 10,
+ UNI_CMD_ID_STATISTICS_LINK_LAYER_STATS = 128,
+ UNI_CMD_ID_STATISTICS_PPDU_LATENCY = 129,
+ UNI_CMD_ID_STATISTICS_CURRENT_TX_RATE = 130,
+ UNI_CMD_ID_STATISTICS_SET_TX_BITRATE_MONITOR = 131,
+ UNI_CMD_ID_STATISTICS_GET_PREDICT_TX_BITRATE = 132,
+ UNI_CMD_ID_STATISTICS_SET_ATPS_TYPE_RATE_MODE = 133,
+ UNI_CMD_ID_STATISTICS_SET_BEACON_REPORT = 134,
+ UNI_CMD_ID_STATISTICS_BSS_CURRENT_TX_RATE = 135,
+ UNI_CMD_ID_STATISTICS_GET_BSS_PREDICT_TX_BITRATE = 136,
+ UNI_CMD_ID_STATISTICS_MAX_NUM
+};
+
+enum mt7925_mib_counter {
+ UNI_CMD_MIB_CNT_RX_FCS_ERR = 0,
+ UNI_CMD_MIB_CNT_RX_FIFO_OVERFLOW = 1,
+ UNI_CMD_MIB_CNT_RX_MPDU = 2,
+ UNI_CMD_MIB_CNT_AMPDU_RX_COUNT = 3,
+ UNI_CMD_MIB_CNT_RX_TOTAL_BYTE = 4,
+ UNI_CMD_MIB_CNT_RX_VALID_AMPDU_SF = 5,
+ UNI_CMD_MIB_CNT_RX_VALID_BYTE = 6,
+ UNI_CMD_MIB_CNT_CHANNEL_IDLE = 7,
+ UNI_CMD_MIB_CNT_VEC_DROP = 8,
+ UNI_CMD_MIB_CNT_DELIMITER_FAIL = 9,
+ UNI_CMD_MIB_CNT_VEC_MISMATCH = 10,
+ UNI_CMD_MIB_CNT_MDRDY = 11,
+ UNI_CMD_MIB_CNT_RX_CCK_MDRDY_TIME = 12,
+ UNI_CMD_MIB_CNT_RX_OFDM_LG_MIXED_MDRDY_TIME = 13,
+ UNI_CMD_MIB_CNT_RX_OFDM_GREEN_MDRDY_TIME = 14,
+ UNI_CMD_MIB_CNT_PF_DROP = 15,
+ UNI_CMD_MIB_CNT_LEN_MISMATCH = 16,
+ UNI_CMD_MIB_CNT_P_CCA_TIME = 17,
+ UNI_CMD_MIB_CNT_S_CCA_TIME = 18,
+ UNI_CMD_MIB_CNT_CCA_NAV_TX_TIME = 19,
+ UNI_CMD_MIB_CNT_P_ED_TIME = 20,
+ UNI_CMD_MIB_CNT_BCN_TX = 21,
+ UNI_CMD_MIB_CNT_TX_BW_20MHZ = 22,
+ UNI_CMD_MIB_CNT_TX_BW_40MHZ = 23,
+ UNI_CMD_MIB_CNT_TX_BW_80MHZ = 24,
+ UNI_CMD_MIB_CNT_TX_BW_160MHZ = 25,
+ UNI_CMD_RMAC_CNT_OBSS_AIRTIME = 26,
+ UNI_CMD_RMAC_CNT_NONWIFI_AIRTIME = 27,
+ UNI_CMD_MIB_CNT_TX_DUR_CNT = 28,
+ UNI_CMD_MIB_CNT_RX_DUR_CNT = 29,
+ UNI_CMD_MIB_CNT_BA_CNT = 30,
+ UNI_CMD_MIB_CNT_MAC2PHY_TX_TIME = 31,
+ UNI_CMD_MIB_CNT_RX_OUT_OF_RANGE_COUNT = 32,
+ UNI_CMD_MIB_CNT_IBF_TX = 33,
+ UNI_CMD_MIB_CNT_EBF_TX = 34,
+ UNI_CMD_MIB_CNT_MUBF_TX = 35,
+ UNI_CMD_MIB_CNT_RX_BF_VHTFBK = 36,
+ UNI_CMD_MIB_CNT_RX_BF_HTFBK = 37,
+ UNI_CMD_MIB_CNT_RX_BF_HEFBK = 38,
+ UNI_CMD_MIB_CNT_BFEE_TXFBK_BFPOLL_TRI = 39,
+ UNI_CMD_MIB_CNT_BFEE_TXFBK_TRI = 40,
+ UNI_CMD_MIB_CNT_BFEE_RX_FBKCQI = 41,
+ UNI_CMD_MIB_CNT_BFEE_RX_NDP = 42,
+ UNI_CMD_MIB_CNT_RX_BF_IBF_UPT = 43,
+ UNI_CMD_MIB_CNT_RX_BF_EBF_UPT = 44,
+ UNI_CMD_MIB_CNT_BFEE_SP_ABORT = 45,
+ UNI_CMD_MIB_CNT_BFEE_TB_LEN_ERR = 46,
+ UNI_CMD_MIB_CNT_BFEE_TXFBK_MUTE = 47,
+ UNI_CMD_MIB_CNT_BFEE_TMAC_ABORT = 48,
+ UNI_CMD_MIB_CNT_BFEE_TXFBK_CPL = 49,
+ UNI_CMD_MIB_CNT_BFEE_COANT_BLKTX = 50,
+ UNI_CMD_MIB_CNT_BFEE_FBK_SEG = 51,
+ UNI_CMD_MIB_CNT_NAV_TIME = 52,
+ UNI_CMD_MIB_CNT_TX_ABORT_CNT1 = 53,
+ UNI_CMD_MIB_CNT_TX_ABORT_CNT0 = 54,
+ UNI_CMD_MIB_CNT_TX_ABORT_CNT3 = 55,
+ UNI_CMD_MIB_CNT_TX_ABORT_CNT2 = 56,
+ UNI_CMD_MIB_CNT_BSS0_RTS_TX_CNT = 57,
+ UNI_CMD_MIB_CNT_BSS1_RTS_TX_CNT = 58,
+ UNI_CMD_MIB_CNT_BSS2_RTS_TX_CNT = 59,
+ UNI_CMD_MIB_CNT_BSS3_RTS_TX_CNT = 60,
+ UNI_CMD_MIB_CNT_BSS0_RTS_RETRY = 61,
+ UNI_CMD_MIB_CNT_BSS1_RTS_RETRY = 62,
+ UNI_CMD_MIB_CNT_BSS2_RTS_RETRY = 63,
+ UNI_CMD_MIB_CNT_BSS3_RTS_RETRY = 64,
+ UNI_CMD_MIB_CNT_BSS0_BA_MISS = 65,
+ UNI_CMD_MIB_CNT_BSS1_BA_MISS = 66,
+ UNI_CMD_MIB_CNT_BSS2_BA_MISS = 67,
+ UNI_CMD_MIB_CNT_BSS3_BA_MISS = 68,
+ UNI_CMD_MIB_CNT_BSS0_ACK_FAIL = 69,
+ UNI_CMD_MIB_CNT_BSS1_ACK_FAIL = 70,
+ UNI_CMD_MIB_CNT_BSS2_ACK_FAIL = 71,
+ UNI_CMD_MIB_CNT_BSS3_ACK_FAIL = 72,
+ UNI_CMD_MIB_CNT_BSS0_FRAME_RETRY = 73,
+ UNI_CMD_MIB_CNT_BSS1_FRAME_RETRY = 74,
+ UNI_CMD_MIB_CNT_BSS2_FRAME_RETRY = 75,
+ UNI_CMD_MIB_CNT_BSS3_FRAME_RETRY = 76,
+ UNI_CMD_MIB_CNT_BSS0_FRAME_RETRY_2 = 77,
+ UNI_CMD_MIB_CNT_BSS1_FRAME_RETRY_2 = 78,
+ UNI_CMD_MIB_CNT_BSS2_FRAME_RETRY_2 = 79,
+ UNI_CMD_MIB_CNT_BSS3_FRAME_RETRY_2 = 80,
+ UNI_CMD_MIB_CNT_RX_A1_SEARCH = 81,
+ UNI_CMD_MIB_CNT_RX_DROP_MPDU = 82,
+ UNI_CMD_MIB_CNT_RX_UNWANTED = 83,
+ UNI_CMD_MIB_CNT_RX_FCS_OK = 84,
+ UNI_CMD_MIB_CNT_SU_TX_OK = 85,
+ UNI_CMD_MIB_CNT_TX_FULL_BW = 86,
+ UNI_CMD_MIB_CNT_TX_AUTO_BW = 87,
+ UNI_CMD_MIB_CNT_CCA_SAMPLE_IDLE = 88,
+ UNI_CMD_MIB_CNT_CCA_SAMPLE_ACTIVE = 89,
+ UNI_CMD_MIB_CNT_CCA_TIME = 90,
+ UNI_CMD_MIB_CNT_S_20BW_CCA_TIME = 91,
+ UNI_CMD_MIB_CNT_S_40BW_CCA_TIME = 92,
+ UNI_CMD_MIB_CNT_S_80BW_CCA_TIME = 93,
+ UNI_CMD_MIB_CNT_S_160BW_CCA_TIME = 94,
+ UNI_CMD_MIB_CNT_S_P20BW_0_ED_TIME = 95,
+ UNI_CMD_MIB_CNT_S_P20BW_1_ED_TIME = 96,
+ UNI_CMD_MIB_CNT_S_P20BW_2_ED_TIME = 97,
+ UNI_CMD_MIB_CNT_S_P20BW_3_ED_TIME = 98,
+ UNI_CMD_MIB_CNT_S_P20BW_4_ED_TIME = 99,
+ UNI_CMD_MIB_CNT_S_P20BW_5_ED_TIME = 100,
+ UNI_CMD_MIB_CNT_S_P20BW_6_ED_TIME = 101,
+ UNI_CMD_MIB_CNT_S_P20BW_7_ED_TIME = 102,
+ UNI_CMD_MIB_CNT_S_P20BW_8_ED_TIME = 103,
+ UNI_CMD_MIB_CNT_S_P20BW_9_ED_TIME = 104,
+ UNI_CMD_MIB_CNT_S_P20BW_10_ED_TIME = 105,
+ UNI_CMD_MIB_CNT_S_P20BW_11_ED_TIME = 106,
+ UNI_CMD_MIB_CNT_S_P20BW_12_ED_TIME = 107,
+ UNI_CMD_MIB_CNT_S_P20BW_13_ED_TIME = 108,
+ UNI_CMD_MIB_CNT_S_P20BW_14_ED_TIME = 109,
+ UNI_CMD_MIB_CNT_S_P20BW_15_ED_TIME = 110,
+ UNI_CMD_MIB_CNT_TX_BW_320MHZ = 111,
+ UNI_CMD_MIB_CNT_TX_DDLMT_RNG0 = 112, /* Rng0~Rng4 112~116 */
+ UNI_CMD_MIB_CNT_BSS0_FRAME_RETRY_3 = 117, /* 117~120 */
+ UNI_CMD_MIB_CNT_BSS0_TX = 121, /* 121~124 */
+ UNI_CMD_MIB_CNT_BSS0_TX_DATA = 125, /* 125~128 */
+ UNI_CMD_MIB_CNT_BSS0_TX_BYTE = 129, /* 129~132 */
+ UNI_CMD_MIB_CNT_RX_OK_BSS0 = 133, /* 133~136 */
+ UNI_CMD_MIB_CNT_RX_BYTE_BSS0 = 137, /* 137~140 */
+ UNI_CMD_MIB_CNT_RX_DATA_BSS0 = 141, /* 141~144 */
+ UNI_CMD_MIB_CNT_MBSS0_TX_OK = 145, /* 145~160 */
+ UNI_CMD_MIB_CNT_MBSS0_TX_BYTE = 161, /* 161~176 */
+ UNI_CMD_MIB_CNT_RX_OK_MBSS0 = 177, /* 177~192 */
+ UNI_CMD_MIB_CNT_RX_BYTE_MBSS0 = 193, /* 193~208 */
+ UNI_CMD_MIB_CNT_AMPDU = 209,
+ UNI_CMD_MIB_CNT_AMPDU_MPDU = 210,
+ UNI_CMD_MIB_CNT_AMPDU_ACKED = 211,
+ UNI_CMD_MIB_CNT_MAX_NUM
+};
+
+enum ENUM_UNI_CMD_GET_STATISTICS_TAG {
+ UNI_CMD_GET_STATISTICS_TAG_BASIC = 0,
+ UNI_CMD_GET_STATISTICS_TAG_LINK_QUALITY = 1,
+ UNI_CMD_GET_STATISTICS_TAG_STA = 2,
+ UNI_CMD_GET_STATISTICS_TAG_BUG_REPORT = 3,
+ UNI_CMD_GET_STATISTICS_TAG_EML_STATS = 6,
+ UNI_CMD_GET_STATISTICS_TAG_REGULAR_STATS = 7,
+ UNI_CMD_GET_STATISTICS_TAG_BSS_LINK_QUALITY = 8,
+ UNI_CMD_GET_STATISTICS_TAG_GET_ML_CHNL_COND = 9,
+ UNI_CMD_GET_STATISTICS_TAG_STAT_WTBL = 10,
+ UNI_CMD_GET_STATISTICS_TAG_LINK_LAYER_STATS = 0x80,
+ UNI_CMD_GET_STATISTICS_TAG_PPDU_LATENCY,
+ UNI_CMD_GET_STATISTICS_TAG_CURRENT_TX_RATE,
+ UNI_CMD_GET_STATISTICS_TAG_BEACON_REPORT = 0x86,
+ UNI_CMD_GET_STATISTICS_TAG_BSS_CURRENT_TX_RATE = 0x87,
+ UNI_CMD_GET_STATISTICS_TAG_BSS_PRED_TX_BITRATE = 0x88,
+};
+
#define UNI_WOW_DETECT_TYPE_MAGIC BIT(0)
#define UNI_WOW_DETECT_TYPE_ANY BIT(1)
#define UNI_WOW_DETECT_TYPE_DISCONNECT BIT(2)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/Makefile b/drivers/net/wireless/mediatek/mt76/mt7925/Makefile
index f9dcc0bba393..3863a89b8974 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/Makefile
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/Makefile
@@ -4,7 +4,7 @@ obj-$(CONFIG_MT7925_COMMON) += mt7925-common.o
obj-$(CONFIG_MT7925E) += mt7925e.o
obj-$(CONFIG_MT7925U) += mt7925u.o
-mt7925-common-y := mac.o mcu.o regd.o main.o init.o debugfs.o nan.o
+mt7925-common-y := mac.o mcu.o regd.o main.o init.o debugfs.o nan.o stats.o
mt7925-common-$(CONFIG_NL80211_TESTMODE) += testmode.o
mt7925e-y := pci.o pci_mac.o pci_mcu.o
mt7925u-y := usb.o
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c b/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c
index d01ff49de47a..ace501049792 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c
@@ -1,8 +1,11 @@
// SPDX-License-Identifier: BSD-3-Clause-Clear
/* Copyright (C) 2023 MediaTek Inc. */
+#include <linux/math64.h>
+
#include "mt7925.h"
#include "mcu.h"
+#include "stats.h"
static int
mt7925_reg_set(void *data, u64 val)
@@ -286,6 +289,366 @@ static int mt7925_chip_reset(void *data, u64 val)
DEFINE_DEBUGFS_ATTRIBUTE(fops_reset, NULL, mt7925_chip_reset, "%lld\n");
+static const u32 stats_query_mib_counters[] = {
+ UNI_CMD_MIB_CNT_RX_FCS_ERR,
+ UNI_CMD_MIB_CNT_RX_FIFO_OVERFLOW,
+ UNI_CMD_MIB_CNT_RX_MPDU,
+ UNI_CMD_MIB_CNT_AMPDU_RX_COUNT,
+ UNI_CMD_MIB_CNT_PF_DROP,
+ UNI_CMD_MIB_CNT_LEN_MISMATCH,
+ UNI_CMD_MIB_CNT_CHANNEL_IDLE,
+ UNI_CMD_MIB_CNT_CCA_NAV_TX_TIME,
+ UNI_CMD_MIB_CNT_MDRDY,
+ UNI_CMD_MIB_CNT_RX_CCK_MDRDY_TIME,
+ UNI_CMD_MIB_CNT_RX_OFDM_LG_MIXED_MDRDY_TIME,
+ UNI_CMD_MIB_CNT_RX_OFDM_GREEN_MDRDY_TIME,
+ UNI_CMD_MIB_CNT_P_CCA_TIME,
+ UNI_CMD_MIB_CNT_S_CCA_TIME,
+ UNI_CMD_MIB_CNT_P_ED_TIME,
+ UNI_CMD_MIB_CNT_BCN_TX,
+ UNI_CMD_MIB_CNT_TX_BW_40MHZ,
+ UNI_CMD_MIB_CNT_TX_BW_80MHZ,
+ UNI_CMD_MIB_CNT_TX_BW_160MHZ,
+ UNI_CMD_MIB_CNT_BSS0_BA_MISS,
+ UNI_CMD_MIB_CNT_BSS0_RTS_TX_CNT,
+ UNI_CMD_MIB_CNT_BSS0_FRAME_RETRY,
+ UNI_CMD_MIB_CNT_BSS0_FRAME_RETRY_2,
+ UNI_CMD_MIB_CNT_BSS0_RTS_RETRY,
+ UNI_CMD_MIB_CNT_BSS0_ACK_FAIL,
+ UNI_CMD_MIB_CNT_AMPDU,
+ UNI_CMD_MIB_CNT_AMPDU_MPDU,
+ UNI_CMD_MIB_CNT_AMPDU_ACKED,
+};
+
+/* Dump the whole firmware auto-rate table and mark the entry currently in use
+ * with "-->"
+ */
+static void
+mt7925_dump_auto_rate_table(struct seq_file *s,
+ const struct mt7925_wtbl_rate *wr)
+{
+ u8 fcap = wr->fcap;
+ u8 rate_idx = wr->rate_idx;
+ u8 cbrn = wr->cbrn;
+ int i;
+
+ seq_printf(s, "%-22s Cur Rate Index = %u\n", " ", rate_idx);
+
+ for (i = 0; i < MT7925_AUTO_RATE_NUM; i++) {
+ u16 rate_code = le16_to_cpu(wr->rate_code[i]);
+ u8 txmode = MT7925_HW_RATE_TO_MODE(rate_code);
+ u8 rate = MT7925_HW_RATE_TO_MCS(rate_code);
+ u8 nsts = MT7925_HW_RATE_TO_NSS(rate_code) + 1;
+ u8 stbc = MT7925_HW_RATE_TO_STBC(rate_code);
+ u8 dcm = MT7925_HW_RATE_TO_DCM(rate_code);
+ u8 ersu106t = MT7925_HW_RATE_TO_106T(rate_code);
+ u8 sgi = mt7925_wtbl_get_sgi(wr, txmode, fcap);
+ u8 ldpc = mt7925_wtbl_get_ldpc(wr, txmode);
+ const char *mode_str = "N/A";
+
+ if (dcm)
+ rate = MT7925_HW_RATE_UNMASK_DCM(rate);
+ if (ersu106t)
+ rate = MT7925_HW_RATE_UNMASK_106T(rate);
+
+ if (txmode < ARRAY_SIZE(mt7925_tx_mode_str))
+ mode_str = mt7925_tx_mode_str[txmode];
+
+ seq_printf(s, "Rate index[%d] %s", i,
+ rate_idx == i ? "--> " : " ");
+
+ /* rate/MCS field */
+ if (txmode == MT7925_TX_RATE_MODE_CCK)
+ seq_printf(s, "%s, ",
+ mt7925_tx_rate_cck_str[rate & 0x3]);
+ else if (txmode == MT7925_TX_RATE_MODE_OFDM)
+ seq_printf(s, "%s, ", mt7925_hw_rate_ofdm_str(rate));
+ else if (txmode == MT7925_TX_RATE_MODE_HTMIX ||
+ txmode == MT7925_TX_RATE_MODE_HTGF)
+ seq_printf(s, "MCS%d, ", rate);
+ else
+ seq_printf(s, "%s%d_MCS%d, ",
+ stbc ? "NSTS" : "NSS", nsts, rate);
+
+ /* bandwidth: mirror the CCK/OFDM -> BW20 and cbrn handling */
+ if (txmode == MT7925_TX_RATE_MODE_CCK ||
+ txmode == MT7925_TX_RATE_MODE_OFDM)
+ seq_printf(s, "%s, ", mt7925_tx_rate_bw_str[0]);
+ else if (i > cbrn)
+ seq_printf(s, "%s, ",
+ fcap < 5 ?
+ (fcap > MT7925_BW_20 ?
+ mt7925_tx_rate_bw_str[fcap - 1] :
+ mt7925_tx_rate_bw_str[fcap]) :
+ mt7925_tx_rate_bw_str[5]);
+ else
+ seq_printf(s, "%s, ",
+ fcap < 5 ? mt7925_tx_rate_bw_str[fcap] :
+ mt7925_tx_rate_bw_str[5]);
+
+ /* GI / preamble */
+ if (txmode == MT7925_TX_RATE_MODE_CCK)
+ seq_printf(s, "%s, ", rate < 4 ? "LP" : "SP");
+ else if (txmode == MT7925_TX_RATE_MODE_OFDM)
+ ; /* OFDM has no GI/preamble to print */
+ else if (txmode == MT7925_TX_RATE_MODE_HTMIX ||
+ txmode == MT7925_TX_RATE_MODE_HTGF ||
+ txmode == MT7925_TX_RATE_MODE_VHT ||
+ txmode == MT7925_TX_RATE_MODE_PLR)
+ seq_printf(s, "%s, ", sgi == 0 ? "LGI" : "SGI");
+ else
+ seq_printf(s, "%s, ",
+ sgi == 0 ? "SGI" :
+ (sgi == 1 ? "MGI" : "LGI"));
+
+ /* mode, DCM, 106t, STBC, coding */
+ seq_printf(s, "%s%s%s%s%s\n",
+ mode_str,
+ dcm ? ", DCM" : "", ersu106t ? ", 106t" : "",
+ stbc ? ", STBC, " : ", ",
+ (ldpc == 0 ||
+ txmode == MT7925_TX_RATE_MODE_CCK ||
+ txmode == MT7925_TX_RATE_MODE_OFDM) ?
+ "BCC" : "LDPC");
+ }
+}
+
+static int mt792x_stats(struct seq_file *s, void *data)
+{
+ s8 rssi = 0;
+ u8 band_idx = 0;
+ bool wtbl_rate_valid = false;
+ int ret = 0;
+ int i = 0;
+ u32 stats_value = 0;
+ u32 tx_link_speed_kbps = 0;
+ u32 tx_per_rem = 0;
+ __le32 tx_ampdu_cnt = 0;
+ u64 tx_total = 0;
+ u64 tx_fail = 0;
+ u64 tx_per = 0;
+ /* UNI_CMD_MIB_DATA */
+ u64 mib_values[ARRAY_SIZE(stats_query_mib_counters)];
+ struct mt792x_dev *dev = dev_get_drvdata(s->private);
+ struct mt7925_wtbl_rate wtbl_rate;
+ struct mt7925_sta_stats *stats = NULL;
+
+ band_idx = dev->mphy.band_idx;
+
+ if (band_idx >= MT7925_STA_STATS_BAND_NUM)
+ return -EINVAL;
+
+ stats = kzalloc(sizeof(*stats), GFP_KERNEL);
+ if (!stats)
+ return -ENOMEM;
+
+ /* Get STA_STATS */
+ mt792x_mutex_acquire(dev);
+ ret = mt792x_mcu_get_stat(dev, stats);
+ mt792x_mutex_release(dev);
+ if (ret)
+ goto out;
+
+ /* Get MIB_STATS */
+ mt792x_mutex_acquire(dev);
+ ret = mt7925_mcu_get_mib_info(dev,
+ band_idx,
+ stats_query_mib_counters,
+ ARRAY_SIZE(stats_query_mib_counters),
+ mib_values);
+ mt792x_mutex_release(dev);
+ if (ret)
+ goto out;
+
+ /* Get Link_Speed and RSSI */
+ mt792x_mutex_acquire(dev);
+ ret = mt7925_mcu_get_link_quality(dev,
+ stats->bss_idx,
+ &rssi,
+ &tx_link_speed_kbps);
+ mt792x_mutex_release(dev);
+ if (ret)
+ goto out;
+
+ /* Get the firmware auto-rate table (best effort; do not abort the
+ * whole stats dump if the WTBL query is not available).
+ */
+ mt792x_mutex_acquire(dev);
+ if (!mt792x_mcu_get_wtbl_rate(dev, stats->wtbl_idx, &wtbl_rate))
+ wtbl_rate_valid = true;
+ mt792x_mutex_release(dev);
+
+ /* Print STA_STATS info */
+ seq_printf(s, "(STA) connected AP MAC Address = %pM\n",
+ stats->mac_addr);
+ seq_printf(s, "%-22s BssIdx = [%u]\n", " ", stats->bss_idx);
+ seq_printf(s, "%-22s StaRecIdx = [%u]\n",
+ " ", stats->sta_idx);
+ seq_printf(s, "%-22s RSSI = %d\n", " ", rssi);
+ seq_printf(s, "%-22s Link_Speed = %u (kbit/s)\n",
+ " ", tx_link_speed_kbps);
+
+ seq_printf(s, "%-22s temperature = %u\n",
+ " ", stats->temperature);
+
+ stats_value = le32_to_cpu(stats->transmit_count) -
+ le32_to_cpu(stats->transmit_fail_count);
+ seq_printf(s, "%-22s Tx success = %u\n",
+ " ", stats_value);
+
+ stats_value = le32_to_cpu(stats->tx_fail_count) -
+ le32_to_cpu(stats->tx_life_timeout_count);
+ seq_printf(s, "%-22s Tx fail to Rcv ACK after retry = %u\n",
+ " ", stats_value);
+
+ seq_printf(s, "%-22s Rx Mpdu = %u\n",
+ " ", le32_to_cpu(stats->mib[band_idx].rx_mpdu_cnt));
+
+ seq_printf(s, "%-22s Rx Fcs Error = %u (MPDU)\n",
+ " ", le32_to_cpu(stats->mib[band_idx].fcs_error));
+ seq_printf(s, "%-22s Rx FIFO full = %u (MPDU)\n",
+ " ", le32_to_cpu(stats->mib[band_idx].rx_fifo_full));
+
+ for (i = 0; i < STAT_MIB_CNT_TRX_AGG_RANGE_MAX_NUM; i++) {
+ tx_ampdu_cnt = stats->mib[band_idx].tx_range_ampdu_cnt[i];
+ seq_printf(s, "%-22s TRX_AGG_RANGE[%d] = %u (PPDU)\n",
+ " ",
+ i,
+ le32_to_cpu(tx_ampdu_cnt));
+ }
+
+ seq_printf(s, "%-22s MPDUs in AMPDUs transmitted = %u (MPDU)\n",
+ " ", le32_to_cpu(stats->mib[band_idx].ampdu_tx_sf_cnt));
+ seq_printf(s, "%-22s MPDUs in AMPDUs transmitted with ACK reply = %u (MPDU)\n",
+ " ", le32_to_cpu(stats->mib[band_idx].ampdu_tx_ack_sf_cnt));
+ seq_printf(s, "%-22s rate1_tx_cnt = %u\n",
+ " ", le32_to_cpu(stats->rate1_tx_cnt));
+ seq_printf(s, "%-22s rate1_fail_cnt = %u\n",
+ " ", le32_to_cpu(stats->rate1_fail_cnt));
+ seq_printf(s, "%-22s train_up = %u\n",
+ " ", le16_to_cpu(stats->train_up));
+ seq_printf(s, "%-22s train_down = %u\n",
+ " ", le16_to_cpu(stats->train_down));
+ seq_printf(s, "%-22s is_force_tx_stream = %u\n",
+ " ", stats->is_force_tx_stream);
+ seq_printf(s, "%-22s is_force_se_off = %u\n",
+ " ", stats->is_force_se_off);
+ seq_printf(s, "%-22s max_ampdu_factor = %u\n",
+ " ", stats->max_ampdu_factor);
+ seq_printf(s, "%-22s tx_rate_up_penalty = %u\n",
+ " ", stats->tx_rate_up_penalty);
+ seq_printf(s, "%-22s low_traffic_mode = %u\n",
+ " ", stats->low_traffic_mode);
+ seq_printf(s, "%-22s low_traffic_count = %u\n",
+ " ", stats->low_traffic_count);
+ seq_printf(s, "%-22s low_traffic_dashboard = %u\n",
+ " ", stats->low_traffic_dashboard);
+ seq_printf(s, "%-22s dynamic_sgi_state = %u\n",
+ " ", stats->dynamic_sgi_state);
+ seq_printf(s, "%-22s dynamic_sgi_score = %u\n",
+ " ", stats->dynamic_sgi_score);
+ seq_printf(s, "%-22s dynamic_bw_state = %u\n",
+ " ", stats->dynamic_bw_state);
+ seq_printf(s, "%-22s dynamic_gband_256qam_state = %u\n",
+ " ", stats->dynamic_gband_256qam_state);
+ seq_printf(s, "%-22s vht_non_sp_rate_state = %u\n",
+ " ", stats->vht_non_sp_rate_state);
+
+ /* Decode the TX Vector BBP latch to show the current TX MCS rate */
+ mt7925_print_last_tx_rate(s, stats->tx_vector[band_idx].txv);
+
+ /* Dump the whole firmware auto-rate table
+ * and mark the entry currently in use.
+ */
+ if (wtbl_rate_valid)
+ mt7925_dump_auto_rate_table(s, &wtbl_rate);
+
+ seq_puts(s, "\nmib state:\n");
+ /* ===Rx Related Counters=== */
+ seq_puts(s, "=== Rx Related Counters ===\n");
+ seq_printf(s, "%-22s Rx with CRC = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_RX_FCS_ERR]);
+ seq_printf(s, "%-22s Rx drop due to out of resource = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_RX_FIFO_OVERFLOW]);
+ seq_printf(s, "%-22s Rx Mpdu = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_RX_MPDU]);
+ seq_printf(s, "%-22s Rx AMpdu = %llu (PPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_AMPDU_RX_COUNT]);
+ seq_printf(s, "%-22s Rx PF Drop = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_PF_DROP]);
+ seq_printf(s, "%-22s Rx Len Mismatch = %llu (PPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_LEN_MISMATCH]);
+
+ /* ===Phy/Timing Related Counters=== */
+ seq_puts(s, "\n=== Phy/Timing Related Counters ===\n");
+ seq_printf(s, "%-22s ChannelIdleCnt = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_CHANNEL_IDLE]);
+ seq_printf(s, "%-22s CCA_NAV_Tx_Time = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_CCA_NAV_TX_TIME]);
+ seq_printf(s, "%-22s Rx_MDRDY_CNT = %llu (PPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_MDRDY]);
+ seq_printf(s, "%-22s CCK_MDRDY = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_RX_CCK_MDRDY_TIME]);
+ seq_printf(s, "%-22s OFDM_MDRDY = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_RX_OFDM_LG_MIXED_MDRDY_TIME]);
+ seq_printf(s, "%-22s OFDM_GREEN_MDRDY = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_RX_OFDM_GREEN_MDRDY_TIME]);
+ seq_printf(s, "%-22s Prim CCA Time = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_P_CCA_TIME]);
+ seq_printf(s, "%-22s Sec CCA Time = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_S_CCA_TIME]);
+ seq_printf(s, "%-22s Prim ED Time = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_P_ED_TIME]);
+
+ /* ===Tx Related Counters(Generic)=== */
+ seq_puts(s, "\n=== Tx Related Counters(Generic) ===\n");
+ seq_printf(s, "%-22s BeaconTxCnt = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_BCN_TX]);
+ seq_printf(s, "%-22s Tx 40MHz Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_TX_BW_40MHZ]);
+ seq_printf(s, "%-22s Tx 80MHz Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_TX_BW_80MHZ]);
+ seq_printf(s, "%-22s Tx 160MHz Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_TX_BW_160MHZ]);
+
+ /* ===BSSID[0] Related Counters=== */
+ seq_puts(s, "\n=== BSSID[0] Related Counters ===\n");
+ seq_printf(s, "%-22s BA Miss Cnt = %llu (PPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_BSS0_BA_MISS]);
+ seq_printf(s, "%-22s RTS Tx Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_BSS0_RTS_TX_CNT]);
+ seq_printf(s, "%-22s Frame Retry Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_BSS0_FRAME_RETRY]);
+ seq_printf(s, "%-22s Frame Retry 2 Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_BSS0_FRAME_RETRY_2]);
+ seq_printf(s, "%-22s RTS Retry Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_BSS0_RTS_RETRY]);
+ seq_printf(s, "%-22s Ack Failed Cnt = %llu (PPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_BSS0_ACK_FAIL]);
+
+ /* ===AMPDU Related Counters=== */
+ seq_puts(s, "\n=== AMPDU Related Counters ===\n");
+ seq_printf(s, "%-22s Tx AMPDU_Pkt_Cnt = %llu (PPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_AMPDU]);
+ seq_printf(s, "%-22s Tx AMPDU_MPDU_Pkt_Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_AMPDU_MPDU]);
+ seq_printf(s, "%-22s AMPDU Tx success = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_AMPDU_ACKED]);
+
+ tx_total = mib_values[QUERY_MIB_CNT_AMPDU_MPDU];
+ tx_fail = mib_values[QUERY_MIB_CNT_AMPDU_MPDU] -
+ mib_values[QUERY_MIB_CNT_AMPDU_ACKED];
+ tx_per = tx_total == 0 ? 0 : div64_u64(1000 * tx_fail, tx_total);
+ seq_printf(s, "%-22s AMPDU Tx fail count = %llu (MPDU), PER=%llu.%1llu%%\n",
+ " ",
+ tx_fail,
+ div_u64_rem(tx_per, 10, &tx_per_rem), (u64)tx_per_rem);
+
+out:
+ kfree(stats);
+ return ret;
+}
+
int mt7925_init_debugfs(struct mt792x_dev *dev)
{
struct dentry *dir;
@@ -313,5 +676,8 @@ int mt7925_init_debugfs(struct mt792x_dev *dev)
mt792x_pm_stats);
debugfs_create_file("deep-sleep", 0600, dir, dev, &fops_ds);
+ debugfs_create_devm_seqfile(dev->mt76.dev, "stats", dir,
+ mt792x_stats);
+
return 0;
}
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/stats.c b/drivers/net/wireless/mediatek/mt76/mt7925/stats.c
new file mode 100644
index 000000000000..874c0b979c26
--- /dev/null
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/stats.c
@@ -0,0 +1,587 @@
+// SPDX-License-Identifier: BSD-3-Clause-Clear
+/* Copyright (C) 2026 MediaTek Inc. */
+
+#include <linux/firmware.h>
+#include <linux/fs.h>
+#include "mt7925.h"
+#include "mcu.h"
+#include "mac.h"
+#include "stats.h"
+
+#define MT7925_STA_STATS_VERSION 1
+#define MT7925_STA_STATS_VALID 1U
+
+#define MT7925_MIB_MAX_CNT 32
+
+/* firmware UNI_CMD_MIB_CNT_MAX_NUM upper bound */
+#define MT7925_MIB_CNT_MAX_NUM 512
+
+#define MT7925_LQ_BAND_NUM 4 /* Link quality band */
+
+const char * const mt7925_tx_mode_str[] = {
+ "CCK", "OFDM", "MM", "GF", "VHT", "PLR",
+ "PLRP", "ALR", "HE_SU", "HE_ER", "HE_TRIG", "HE_MU",
+ "EHT_ER", "TOF", "EHT_TRIG", "EHT_MU"
+};
+
+const char * const mt7925_tx_rate_cck_str[] = {
+ "1M", "2M", "5.5M", "11M", "N/A"
+};
+
+const char * const mt7925_tx_rate_bw_str[] = {
+ "BW20", "BW40", "BW80", "BW160/BW8080", "BW320", "N/A"
+};
+
+struct mt7925_sta_stats_req {
+ u8 rsv[4];
+ __le16 tag;
+ __le16 len;
+ u8 sta_rec_idx;
+ u8 rsv1;
+ u8 lls_read_clear;
+ u8 reset_counter;
+} __packed;
+
+struct mt7925_sta_stats_event {
+ u8 rsv[4];
+ __le16 tag;
+ __le16 len;
+ struct mt7925_sta_stats stats;
+} __packed;
+
+/* WTBL auto-rate request TLV, mirror Gen4m struct UNI_CMD_STAT_WTBL.
+ * The RA tag reuses UNI_CMD_ID_STATISTICS_WTBL (10).
+ */
+struct mt7925_wtbl_rate_req {
+ u8 rsv[4];
+ __le16 tag;
+ __le16 len;
+ __le16 wlan_idx;
+ __le16 rsv1;
+} __packed;
+
+/* WTBL auto-rate event TLV. Firmware answers cmd tag 10 with event tag 12. */
+struct mt7925_wtbl_rate_event {
+ u8 rsv[4];
+ struct mt7925_wtbl_rate rate;
+} __packed;
+
+/* fixed field, mirror UNI_(CMD|EVENT)_MIB_INFO */
+struct mt7925_mib_hdr {
+ u8 band_idx;
+ u8 rsv[3];
+} __packed;
+
+/* One requested/returned MIB counter, mirror UNI_(CMD|EVENT)_MIB_DATA_T. */
+struct mt7925_mib_data {
+ __le16 tag; /* UNI_(CMD|EVENT)_MIB_DATA = 0 */
+ __le16 len;
+ __le32 counter; /* MIB counter ID (enum mt7925_mib_counter) */
+ __le64 data; /* only valid on event; ignored on cmd */
+} __packed;
+
+/* per-band link quality entry, mirror UNI_LINK_QUALITY */
+struct mt7925_link_quality {
+ s8 rssi; /* moving-average beacon RSSI of connected AP */
+ s8 link_quality; /* always 0 */
+ __le16 link_speed; /* TX rate1, unit 500 Kb/s */
+ u8 medium_busy_pct; /* always 0 */
+ u8 is_ready; /* 0: fields invalid, 1: valid */
+ u8 rsv[2];
+} __packed;
+
+/* request TLV, mirror UNI_CMD_LINK_QUALITY (header + tag/len only) */
+struct mt7925_lq_req {
+ u8 rsv[4]; /* UNI_CMD_GET_STATISTICS fixed field */
+ __le16 tag; /* UNI_CMD_GET_STATISTICS_TAG_LINK_QUALITY = 1 */
+ __le16 len;
+} __packed;
+
+/* event TLV, mirror UNI_EVENT_LINK_QUALITY */
+struct mt7925_lq_event {
+ u8 rsv[4]; /* UNI_EVENT_STATISTICS fixed field */
+ __le16 tag; /* UNI_EVENT_ID_STATISTICS_LINK_QUALITY = 1 */
+ __le16 len;
+ struct mt7925_link_quality lq[MT7925_LQ_BAND_NUM];
+} __packed;
+
+static void
+mt7925_get_ap_sta_rec_idx_iter(void *data, u8 *mac,
+ struct ieee80211_vif *vif)
+{
+ struct mt7925_sta_rec_lookup *lookup = data;
+ struct mt792x_vif *mvif;
+ struct mt792x_sta *msta;
+
+ if (lookup->sta_rec_idx >= 0)
+ return;
+
+ if (vif->type != NL80211_IFTYPE_STATION || !vif->cfg.assoc)
+ return;
+
+ mvif = (struct mt792x_vif *)vif->drv_priv;
+ msta = mvif->wep_sta;
+ if (!msta)
+ return;
+
+ if (!msta->deflink.wcid.sta)
+ return;
+
+ lookup->sta_rec_idx = msta->deflink.wcid.idx;
+}
+
+int
+mt7925_get_ap_sta_rec_idx(struct mt792x_dev *dev, u8 *sta_rec_idx)
+{
+ struct mt7925_sta_rec_lookup lookup = {
+ .sta_rec_idx = -1,
+ };
+
+ lockdep_assert_held(&dev->mt76.mutex);
+
+ ieee80211_iterate_active_interfaces(dev->mphy.hw,
+ IEEE80211_IFACE_ITER_RESUME_ALL,
+ mt7925_get_ap_sta_rec_idx_iter,
+ &lookup);
+
+ if (lookup.sta_rec_idx < 0)
+ return -ENOTCONN;
+
+ if (lookup.sta_rec_idx > U8_MAX)
+ return -ERANGE;
+
+ *sta_rec_idx = lookup.sta_rec_idx;
+
+ return 0;
+}
+
+/* Decode the TX Vector BBP latch and print the last TX rate */
+void
+mt7925_print_last_tx_rate(struct seq_file *s, const __le32 le_txv[3])
+{
+ const char *mode_str = "N/A";
+ u8 rate, txmode, frmode, sgi, ldpc, nsts, stbc, spe, dcm, ersu106t;
+ s8 txpwr, pos_txpwr;
+ u32 txv[3];
+
+ txv[0] = le32_to_cpu(le_txv[0]);
+ txv[1] = le32_to_cpu(le_txv[1]);
+ txv[2] = le32_to_cpu(le_txv[2]);
+
+ if (txv[0] == 0xffffffff) {
+ seq_printf(s, "%-22s %s = %s\n", " ", "Last TX Rate", "N/A");
+ seq_printf(s, "%-22s %s = %s\n",
+ " ", "Chip Out TX Power", "N/A");
+ return;
+ }
+
+ rate = MT7925_TXV_GET_TX_RATE(txv);
+ txmode = MT7925_TXV_GET_TX_MODE(txv);
+ frmode = MT7925_TXV_GET_TX_FRMODE(txv);
+ nsts = MT7925_TXV_GET_TX_NSTS(txv) + 1;
+ sgi = MT7925_TXV_GET_TX_SGI(txv);
+ ldpc = MT7925_TXV_GET_TX_LDPC(txv);
+ stbc = MT7925_TXV_GET_TX_STBC(txv);
+ txpwr = MT7925_TXV_GET_TX_PWR(txv);
+ spe = MT7925_TXV_GET_TX_SPE_IDX(txv);
+ dcm = MT7925_TXV_GET_TX_DCM(txv);
+ ersu106t = MT7925_TXV_GET_TX_106T(txv);
+
+ if (dcm)
+ rate = MT7925_TXV_RATE_UNMASK_DCM(rate);
+ if (ersu106t)
+ rate = MT7925_TXV_RATE_UNMASK_106T(rate);
+
+ if (txmode < ARRAY_SIZE(mt7925_tx_mode_str))
+ mode_str = mt7925_tx_mode_str[txmode];
+
+ seq_printf(s, "%-22s %s = ", " ", "Last TX Rate");
+
+ /* rate/MCS field */
+ if (txmode == MT7925_TX_RATE_MODE_CCK)
+ seq_printf(s, "%s, ",
+ rate < 4 ? mt7925_tx_rate_cck_str[rate] :
+ (((rate >= 5) && (rate <= 7)) ?
+ mt7925_tx_rate_cck_str[rate - 4] :
+ mt7925_tx_rate_cck_str[4]));
+ else if (txmode == MT7925_TX_RATE_MODE_OFDM)
+ seq_printf(s, "%s, ", mt7925_hw_rate_ofdm_str(rate));
+ else if (txmode == MT7925_TX_RATE_MODE_HTMIX ||
+ txmode == MT7925_TX_RATE_MODE_HTGF)
+ seq_printf(s, "MCS%d, ", rate);
+ else
+ seq_printf(s, "%s%d_MCS%d, ",
+ stbc ? "NSTS" : "NSS", nsts, rate);
+
+ /* bandwidth */
+ seq_printf(s, "%s, ",
+ frmode < 5 ? mt7925_tx_rate_bw_str[frmode] :
+ mt7925_tx_rate_bw_str[5]);
+
+ /* GI / preamble */
+ if (txmode == MT7925_TX_RATE_MODE_CCK)
+ seq_printf(s, "%s, ", rate < 4 ? "LP" : "SP");
+ else if (txmode == MT7925_TX_RATE_MODE_OFDM)
+ ; /* OFDM has no GI/preamble to print */
+ else if (txmode == MT7925_TX_RATE_MODE_HTMIX ||
+ txmode == MT7925_TX_RATE_MODE_HTGF ||
+ txmode == MT7925_TX_RATE_MODE_VHT ||
+ txmode == MT7925_TX_RATE_MODE_PLR)
+ seq_printf(s, "%s, ", sgi == 0 ? "LGI" : "SGI");
+ else
+ seq_printf(s, "%s, ",
+ sgi == 0 ? "SGI" : (sgi == 1 ? "MGI" : "LGI"));
+
+ /* mode, DCM, 106t, STBC, coding, SPE */
+ seq_printf(s, "%s%s%s%s%s%s%d\n",
+ mode_str,
+ dcm ? ", DCM" : "", ersu106t ? ", 106t" : "",
+ stbc ? ", STBC, " : ", ", ldpc == 0 ? "BCC" : "LDPC",
+ ", SPE", spe);
+
+ /* chip out TX power (unit: 0.5 dBm) */
+ pos_txpwr = (txpwr < 0) ? (~txpwr + 1) : txpwr;
+ seq_printf(s, "%-22s %s = %c%d.%1d dBm\n",
+ " ", "Chip Out TX Power",
+ (txpwr < 0) ? '-' : '+',
+ (pos_txpwr / 2), 5 * (pos_txpwr % 2));
+}
+
+/* Guard interval used for the auto-rate entries, mirror Gen4m
+ * connac3x_get_sgi_info_from_fw(): pick the per-mode/per-BW flag.
+ */
+u8
+mt7925_wtbl_get_sgi(const struct mt7925_wtbl_rate *wr, u8 txmode, u8 fcap)
+{
+ bool he = (txmode == MT7925_TX_RATE_MODE_HE_SU ||
+ txmode == MT7925_TX_RATE_MODE_HE_ER ||
+ txmode == MT7925_TX_RATE_MODE_HE_TRIG ||
+ txmode == MT7925_TX_RATE_MODE_HE_MU);
+ bool eht = (txmode == MT7925_TX_RATE_MODE_EHT_ER ||
+ txmode == MT7925_TX_RATE_MODE_EHT_TRIG ||
+ txmode == MT7925_TX_RATE_MODE_EHT_MU);
+
+ if (eht)
+ return wr->gi_eht;
+
+ switch (fcap) {
+ case MT7925_BW_20:
+ return he ? wr->g2_he : wr->g2;
+ case MT7925_BW_40:
+ return he ? wr->g4_he : wr->g4;
+ case MT7925_BW_80:
+ return he ? wr->g8_he : wr->g8;
+ case MT7925_BW_160:
+ return he ? wr->g16_he : wr->g16;
+ default:
+ return 0;
+ }
+}
+
+/* Coding used for the auto-rate entries, mirror Gen4m
+ * connac3x_wtbl_get_ldpc_info_from_fw(): 0 = BCC, 1 = LDPC.
+ */
+u8
+mt7925_wtbl_get_ldpc(const struct mt7925_wtbl_rate *wr, u8 txmode)
+{
+ switch (txmode) {
+ case MT7925_TX_RATE_MODE_HTMIX:
+ case MT7925_TX_RATE_MODE_HTGF:
+ return wr->ht_ldpc;
+ case MT7925_TX_RATE_MODE_VHT:
+ return wr->vht_ldpc;
+ case MT7925_TX_RATE_MODE_HE_SU:
+ case MT7925_TX_RATE_MODE_HE_ER:
+ case MT7925_TX_RATE_MODE_HE_TRIG:
+ case MT7925_TX_RATE_MODE_HE_MU:
+ return wr->he_ldpc;
+ case MT7925_TX_RATE_MODE_EHT_ER:
+ case MT7925_TX_RATE_MODE_EHT_TRIG:
+ case MT7925_TX_RATE_MODE_EHT_MU:
+ return wr->eht_ldpc;
+ default:
+ return 0;
+ }
+}
+
+int mt792x_mcu_get_stat(struct mt792x_dev *dev,
+ struct mt7925_sta_stats *stats)
+{
+ u16 len = 0;
+ int ret = 0;
+ struct mt7925_sta_stats_event *event = NULL;
+ struct sk_buff *skb = NULL;
+ struct mt7925_sta_stats_req req = {
+ .tag = cpu_to_le16(UNI_CMD_ID_STATISTICS_STA),
+ .len = cpu_to_le16(sizeof(req) - sizeof(req.rsv)),
+ .lls_read_clear = false,
+ .reset_counter = false,
+ };
+
+ ret = mt7925_get_ap_sta_rec_idx(dev, &req.sta_rec_idx);
+ if (ret)
+ return ret;
+
+ if (!stats)
+ return -EINVAL;
+
+ ret = mt76_mcu_send_and_get_msg(&dev->mt76,
+ MCU_UNI_QUERY(GET_STAT_INFO),
+ &req, sizeof(req), true, &skb);
+
+ if (ret)
+ return ret;
+
+ event = (struct mt7925_sta_stats_event *)skb->data;
+
+ if (le16_to_cpu(event->tag) != UNI_CMD_ID_STATISTICS_STA) {
+ ret = -EPROTO;
+ goto out;
+ }
+
+ len = le16_to_cpu(event->len);
+ if (len < sizeof(*event) - sizeof(event->rsv) ||
+ len > skb->len - sizeof(event->rsv) || !IS_ALIGNED(len, 4)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+
+ if (event->stats.version != MT7925_STA_STATS_VERSION) {
+ ret = -EPROTO;
+ goto out;
+ }
+
+ if (!(le32_to_cpu(event->stats.flags) & MT7925_STA_STATS_VALID)) {
+ ret = -ENOENT;
+ goto out;
+ }
+
+ memcpy(stats, &event->stats, sizeof(*stats));
+
+out:
+ dev_kfree_skb(skb);
+ return ret;
+}
+
+int mt7925_mcu_get_mib_info(struct mt792x_dev *dev, u8 band_idx,
+ const u32 *counters, u8 counter_num,
+ u64 *values)
+{
+ u8 *req;
+ u8 *tlv, *tlv_end;
+ u16 req_len;
+ int i, ret;
+ struct mt7925_mib_hdr *req_hdr;
+ struct mt7925_mib_data *req_tlv;
+ struct sk_buff *skb;
+
+ if (!counters || !values || !counter_num ||
+ counter_num > MT7925_MIB_MAX_CNT)
+ return -EINVAL;
+
+ /* clear output; counters not returned by firmware stay 0 */
+ memset(values, 0, counter_num * sizeof(*values));
+
+ req_len = sizeof(struct mt7925_mib_hdr) +
+ counter_num * sizeof(struct mt7925_mib_data);
+
+ req = kzalloc(req_len, GFP_KERNEL);
+ if (!req)
+ return -ENOMEM;
+
+ req_hdr = (struct mt7925_mib_hdr *)req;
+ req_hdr->band_idx = band_idx;
+
+ req_tlv = (struct mt7925_mib_data *)(req + sizeof(*req_hdr));
+ for (i = 0; i < counter_num; i++) {
+ req_tlv[i].tag = cpu_to_le16(UNI_CMD_MIB_DATA);
+ req_tlv[i].len = cpu_to_le16(sizeof(struct mt7925_mib_data));
+ req_tlv[i].counter = cpu_to_le32(counters[i]);
+ }
+
+ ret = mt76_mcu_send_and_get_msg(&dev->mt76,
+ MCU_UNI_QUERY(GET_MIB_INFO), req,
+ req_len, true, &skb);
+ kfree(req);
+ if (ret)
+ return ret;
+
+ if (skb->len < sizeof(struct mt7925_mib_hdr)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+
+ /* walk the returned TLVs, match each counter ID to the request */
+ tlv = skb->data + sizeof(struct mt7925_mib_hdr);
+ tlv_end = skb->data + skb->len;
+
+ while (tlv + sizeof(struct mt7925_mib_data) <= tlv_end) {
+ struct mt7925_mib_data *evt_tlv =
+ (struct mt7925_mib_data *)tlv;
+ u16 tag = le16_to_cpu(evt_tlv->tag);
+ u16 len = le16_to_cpu(evt_tlv->len);
+ u32 counter;
+
+ if (len < sizeof(struct mt7925_mib_data) ||
+ tlv + len > tlv_end)
+ break;
+
+ if (tag == UNI_CMD_MIB_DATA) {
+ counter = le32_to_cpu(evt_tlv->counter);
+
+ if (counter < MT7925_MIB_CNT_MAX_NUM) {
+ for (i = 0; i < counter_num; i++) {
+ if (counters[i] == counter) {
+ values[i] =
+ le64_to_cpu(evt_tlv->data);
+ break;
+ }
+ }
+ }
+ }
+
+ tlv += len;
+ }
+
+ ret = 0;
+
+out:
+ dev_kfree_skb(skb);
+
+ return ret;
+}
+
+char *mt7925_hw_rate_ofdm_str(u8 ofdm_idx)
+{
+ char * const ofdm_str[] = {
+ "6M", "9M", "12M", "18M", "24M", "36M", "48M", "54M", "N/A"
+ };
+
+ switch (ofdm_idx) {
+ case 11: return ofdm_str[0]; /* 6M */
+ case 15: return ofdm_str[1]; /* 9M */
+ case 10: return ofdm_str[2]; /* 12M */
+ case 14: return ofdm_str[3]; /* 18M */
+ case 9: return ofdm_str[4]; /* 24M */
+ case 13: return ofdm_str[5]; /* 36M */
+ case 8: return ofdm_str[6]; /* 48M */
+ case 12: return ofdm_str[7]; /* 54M */
+ default: return ofdm_str[8];
+ }
+}
+
+/* mt792x_mcu_get_wtbl_rate - fetch the firmware auto-rate table for a WCID
+ * @dev: device
+ * @wlan_idx: HW WTBL / WCID index (see mt7925_sta_stats.wtbl_idx)
+ * @rate: out, filled with the auto-rate table snapshot
+ *
+ * Mirror of Gen4m wlanoidGetStatWtbl(): send UNI_CMD_GET_STAT_INFO with the
+ * WTBL rate TLV (cmd tag 10) and parse the reply (event tag 12) into @rate.
+ *
+ * Return: 0 on success, negative errno otherwise.
+ */
+int mt792x_mcu_get_wtbl_rate(struct mt792x_dev *dev, u16 wlan_idx,
+ struct mt7925_wtbl_rate *rate)
+{
+ int ret;
+ struct mt7925_wtbl_rate_event *event = NULL;
+ struct sk_buff *skb = NULL;
+ struct mt7925_wtbl_rate_req req = {
+ .tag = cpu_to_le16(UNI_CMD_ID_STATISTICS_WTBL),
+ .len = cpu_to_le16(sizeof(req) - sizeof(req.rsv)),
+ .wlan_idx = cpu_to_le16(wlan_idx),
+ };
+
+ if (!rate)
+ return -EINVAL;
+
+ ret = mt76_mcu_send_and_get_msg(&dev->mt76,
+ MCU_UNI_QUERY(GET_STAT_INFO),
+ &req, sizeof(req), true, &skb);
+ if (ret)
+ return ret;
+
+ if (skb->len < sizeof(*event)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+
+ event = (struct mt7925_wtbl_rate_event *)skb->data;
+
+ if (le16_to_cpu(event->rate.tag) != MT7925_UNI_EVENT_STATISTICS_WTBL) {
+ ret = -EPROTO;
+ goto out;
+ }
+
+ memcpy(rate, &event->rate, sizeof(*rate));
+ ret = 0;
+
+out:
+ dev_kfree_skb(skb);
+ return ret;
+}
+
+/**
+ * mt7925_mcu_get_link_quality - query RSSI and TX link speed for a band
+ * @dev: device
+ * @band_idx: band/BSS index (0..3)
+ * @rssi: out, moving-average beacon RSSI in dBm (may be NULL)
+ * @tx_link_speed_kbps: out, TX rate in kbit/s (may be NULL)
+ *
+ * Return: 0 on success, -ENOENT if the band's link quality is not ready.
+ *
+ * Only RSSI and TX rate come from firmware here; for RX rate read wcid->rate.
+ */
+int mt7925_mcu_get_link_quality(struct mt792x_dev *dev, u8 band_idx,
+ s8 *rssi, u32 *tx_link_speed_kbps)
+{
+ int ret;
+ struct mt7925_lq_event *event;
+ struct mt7925_link_quality *lq;
+ struct sk_buff *skb;
+ struct mt7925_lq_req req = {
+ .tag = cpu_to_le16(UNI_CMD_ID_STATISTICS_LINK_QUALITY),
+ .len = cpu_to_le16(sizeof(req) - sizeof(req.rsv)),
+ };
+
+ if (band_idx >= MT7925_LQ_BAND_NUM)
+ return -EINVAL;
+
+ ret = mt76_mcu_send_and_get_msg(&dev->mt76,
+ MCU_UNI_QUERY(GET_STAT_INFO), &req,
+ sizeof(req), true, &skb);
+ if (ret)
+ return ret;
+
+ if (skb->len < sizeof(*event)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+
+ event = (struct mt7925_lq_event *)skb->data;
+
+ if (le16_to_cpu(event->tag) != UNI_CMD_ID_STATISTICS_LINK_QUALITY) {
+ ret = -EPROTO;
+ goto out;
+ }
+
+ lq = &event->lq[band_idx];
+ if (!lq->is_ready) {
+ ret = -ENOENT;
+ goto out;
+ }
+
+ if (rssi)
+ *rssi = lq->rssi;
+
+ /* firmware reports TX rate in 500 Kb/s units -> kbit/s */
+ if (tx_link_speed_kbps)
+ *tx_link_speed_kbps = le16_to_cpu(lq->link_speed) * 500;
+
+ ret = 0;
+
+out:
+ dev_kfree_skb(skb);
+
+ return ret;
+}
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/stats.h b/drivers/net/wireless/mediatek/mt76/mt7925/stats.h
new file mode 100644
index 000000000000..a70b9eb3aa80
--- /dev/null
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/stats.h
@@ -0,0 +1,309 @@
+/* SPDX-License-Identifier: BSD-3-Clause-Clear */
+/* Copyright (C) 2026 MediaTek Inc. */
+
+#ifndef __MT7925_STATS_H
+#define __MT7925_STATS_H
+
+#include <linux/if_ether.h>
+#include <linux/types.h>
+#include "../mt76_connac_mcu.h"
+
+#define MT7925_STA_STATS_AC_NUM 4
+#define MT7925_STA_STATS_BAND_NUM 2
+#define MT7925_STA_STATS_AGG_RANGE_NUM 16
+#define MT7925_STA_STATS_TX_QUALITY_NUM 4
+#define MT7925_TX_MODE_NUM 16
+#define MT7925_TX_RATE_CCK_NUM 5
+#define MT7925_TX_RATE_BW_NUM 6
+
+/* HW rate code (16-bit) field extract, mirror Gen4m CONNAC3X_HW_TX_RATE_TO_*.
+ * The auto-rate table stores one rate code per entry.
+ */
+#define MT7925_HW_RATE_TO_MCS(_x) (((_x) & GENMASK(5, 0)) >> 0)
+#define MT7925_HW_RATE_TO_MODE(_x) (((_x) & GENMASK(9, 6)) >> 6)
+#define MT7925_HW_RATE_TO_NSS(_x) (((_x) & GENMASK(13, 10)) >> 10)
+#define MT7925_HW_RATE_TO_STBC(_x) (((_x) & BIT(14)) >> 14)
+#define MT7925_HW_RATE_TO_DCM(_x) (((_x) & BIT(4)) >> 4)
+#define MT7925_HW_RATE_TO_106T(_x) (((_x) & BIT(5)) >> 5)
+#define MT7925_HW_RATE_UNMASK_DCM(_r) ((u8)(_r) & 0xef)
+#define MT7925_HW_RATE_UNMASK_106T(_r) ((u8)(_r) & 0xdf)
+
+/* Number of entries in the firmware auto-rate table (mirror Gen4m
+ * AUTO_RATE_NUM). RA cycles through these and reports which one is in use
+ * via wtbl->rate_idx.
+ */
+#define MT7925_AUTO_RATE_NUM 8
+
+/* Firmware EVENT_STATISTICS sub-event tags. The WTBL rate request uses cmd tag
+ * UNI_CMD_ID_STATISTICS_WTBL (10) but the firmware replies with event tag 12.
+ */
+#define MT7925_UNI_EVENT_STATISTICS_WTBL 12
+
+/* CONNAC3X TX Vector BBP latch bit fields (mirror cmm_asic_connac3x.h).
+ * These operate on the 3-DWORD tx_vector[band].txv[3] array.
+ */
+#define MT7925_TXV_GET_TX_RATE(_txv) ((_txv)[2] & 0x7f)
+#define MT7925_TXV_GET_TX_LDPC(_txv) (((_txv)[2] & (0x1 << 7)) >> 7)
+#define MT7925_TXV_GET_TX_STBC(_txv) (((_txv)[0] & (0x3 << 6)) >> 6)
+#define MT7925_TXV_GET_TX_FRMODE(_txv) (((_txv)[0] & (0x7 << 8)) >> 8)
+#define MT7925_TXV_GET_TX_MODE(_txv) (((_txv)[0] & (0xf << 12)) >> 12)
+#define MT7925_TXV_GET_TX_NSTS(_txv) (((_txv)[2] & (0xfU << 28)) >> 28)
+#define MT7925_TXV_GET_TX_PWR(_txv) (((_txv)[0] & (0xff << 16)) >> 16)
+#define MT7925_TXV_GET_TX_SGI(_txv) (((_txv)[1] & (0x3 << 26)) >> 26)
+#define MT7925_TXV_GET_TX_SPE_IDX(_txv) (((_txv)[0] & (0x1f << 0)) >> 0)
+#define MT7925_TXV_GET_TX_DCM(_txv) (((_txv)[2] & (0x1 << 4)) >> 4)
+#define MT7925_TXV_GET_TX_106T(_txv) (((_txv)[2] & (0x1 << 5)) >> 5)
+#define MT7925_TXV_RATE_UNMASK_DCM(_r) ((u8)(_r) & 0xef)
+#define MT7925_TXV_RATE_UNMASK_106T(_r) ((u8)(_r) & 0xdf)
+
+extern const char * const mt7925_tx_mode_str[MT7925_TX_MODE_NUM];
+extern const char * const mt7925_tx_rate_cck_str[MT7925_TX_RATE_CCK_NUM];
+extern const char * const mt7925_tx_rate_bw_str[MT7925_TX_RATE_BW_NUM];
+
+/* TX rate mode, mirror Gen4m TX_RATE_MODE_* (see nic_rate.h). Used to decode
+ * both the TX Vector BBP latch and the firmware auto-rate table rate codes.
+ */
+enum mt7925_tx_rate_mode {
+ MT7925_TX_RATE_MODE_CCK = 0,
+ MT7925_TX_RATE_MODE_OFDM = 1,
+ MT7925_TX_RATE_MODE_HTMIX = 2,
+ MT7925_TX_RATE_MODE_HTGF = 3,
+ MT7925_TX_RATE_MODE_VHT = 4,
+ MT7925_TX_RATE_MODE_PLR = 5,
+ MT7925_TX_RATE_MODE_HE_SU = 8,
+ MT7925_TX_RATE_MODE_HE_ER = 9,
+ MT7925_TX_RATE_MODE_HE_TRIG = 10,
+ MT7925_TX_RATE_MODE_HE_MU = 11,
+ MT7925_TX_RATE_MODE_EHT_ER = 13,
+ MT7925_TX_RATE_MODE_EHT_TRIG = 14,
+ MT7925_TX_RATE_MODE_EHT_MU = 15,
+};
+
+enum {
+ UNI_CMD_MIB_DATA,
+};
+
+enum stats_query_mib_array_index {
+ QUERY_MIB_CNT_RX_FCS_ERR = 0,
+ QUERY_MIB_CNT_RX_FIFO_OVERFLOW,
+ QUERY_MIB_CNT_RX_MPDU,
+ QUERY_MIB_CNT_AMPDU_RX_COUNT,
+ QUERY_MIB_CNT_PF_DROP,
+ QUERY_MIB_CNT_LEN_MISMATCH,
+ QUERY_MIB_CNT_CHANNEL_IDLE,
+ QUERY_MIB_CNT_CCA_NAV_TX_TIME,
+ QUERY_MIB_CNT_MDRDY,
+ QUERY_MIB_CNT_RX_CCK_MDRDY_TIME,
+ QUERY_MIB_CNT_RX_OFDM_LG_MIXED_MDRDY_TIME,
+ QUERY_MIB_CNT_RX_OFDM_GREEN_MDRDY_TIME,
+ QUERY_MIB_CNT_P_CCA_TIME,
+ QUERY_MIB_CNT_S_CCA_TIME,
+ QUERY_MIB_CNT_P_ED_TIME,
+ QUERY_MIB_CNT_BCN_TX,
+ QUERY_MIB_CNT_TX_BW_40MHZ,
+ QUERY_MIB_CNT_TX_BW_80MHZ,
+ QUERY_MIB_CNT_TX_BW_160MHZ,
+ QUERY_MIB_CNT_BSS0_BA_MISS,
+ QUERY_MIB_CNT_BSS0_RTS_TX_CNT,
+ QUERY_MIB_CNT_BSS0_FRAME_RETRY,
+ QUERY_MIB_CNT_BSS0_FRAME_RETRY_2,
+ QUERY_MIB_CNT_BSS0_RTS_RETRY,
+ QUERY_MIB_CNT_BSS0_ACK_FAIL,
+ QUERY_MIB_CNT_AMPDU,
+ QUERY_MIB_CNT_AMPDU_MPDU,
+ QUERY_MIB_CNT_AMPDU_ACKED,
+ QUERY_MIB_CNT_MAX_NUM,
+};
+
+enum stats_trx_agg_range {
+ STAT_MIB_CNT_TRX_AGG_RANGE0_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE1_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE2_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE3_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE4_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE5_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE6_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE7_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE_MAX_NUM
+};
+
+enum {
+ MT7925_BW_20 = 0,
+ MT7925_BW_40,
+ MT7925_BW_80,
+ MT7925_BW_160,
+};
+
+struct mt7925_sta_stats_ac {
+ __le32 tx_fail;
+ __le32 tx_retry;
+} __packed;
+
+struct mt7925_sta_stats_tx_vector {
+ __le32 txv[3];
+} __packed;
+
+struct mt7925_sta_stats_mib {
+ __le32 rx_mpdu_cnt;
+ __le32 fcs_error;
+ __le32 rx_fifo_full;
+ __le32 ampdu_tx_sf_cnt;
+ __le32 ampdu_tx_ack_sf_cnt;
+ __le32 tx_range_ampdu_cnt[MT7925_STA_STATS_AGG_RANGE_NUM];
+} __packed;
+
+/* Firmware EVENT_STA_STATISTICS body. */
+struct mt7925_sta_stats {
+ u8 version;
+ u8 rsv[3];
+ __le32 flags;
+
+ u8 sta_idx;
+ u8 bss_idx;
+ u8 wtbl_idx;
+ u8 rsv1;
+
+ u8 mac_addr[ETH_ALEN];
+ u8 per;
+ u8 rcpi;
+
+ __le32 phy_mode;
+ __le16 link_speed; /* 0.5 Mbit/s units */
+ u8 link_quality;
+ u8 link_reserved;
+
+ __le32 tx_count;
+ __le32 tx_fail_count;
+ __le32 tx_life_timeout_count;
+ __le32 tx_done_air_time;
+ __le32 transmit_count;
+ __le32 transmit_fail_count;
+
+ struct mt7925_sta_stats_ac ac[MT7925_STA_STATS_AC_NUM];
+
+ u8 temperature;
+ u8 skip_ar;
+ u8 ar_table_idx;
+ u8 rate_entry_idx;
+ u8 rate_entry_idx_prev;
+ u8 tx_sgi_detect_pass_cnt;
+ u8 ave_per;
+ u8 rsv2;
+
+ __le32 agg_range_ctrl[2];
+ u8 range_mode;
+ u8 rsv3[3];
+ __le32 agg_range_ctrl_ext[6];
+
+ u8 ar_state_curr;
+ u8 ar_state_prev;
+ u8 ar_action_type;
+ u8 highest_rate_cnt;
+ u8 lowest_rate_cnt;
+ u8 rsv4;
+ __le16 train_up;
+ __le16 train_down;
+ u8 rsv5[2];
+ __le32 rate1_tx_cnt;
+ __le32 rate1_fail_cnt;
+
+ struct mt7925_sta_stats_tx_vector tx_vector[MT7925_STA_STATS_BAND_NUM];
+ struct mt7925_sta_stats_mib mib[MT7925_STA_STATS_BAND_NUM];
+
+ u8 is_force_tx_stream;
+ u8 is_force_se_off;
+ __le16 ra_running_cnt;
+ u8 ra_status;
+ u8 max_ampdu_factor;
+ u8 tx_quality[MT7925_STA_STATS_TX_QUALITY_NUM];
+ u8 tx_rate_up_penalty;
+ u8 low_traffic_mode;
+ u8 low_traffic_count;
+ u8 low_traffic_dashboard;
+ u8 dynamic_sgi_state;
+ u8 dynamic_sgi_score;
+ u8 dynamic_bw_state;
+ u8 dynamic_gband_256qam_state;
+ u8 vht_non_sp_rate_state;
+ u8 rsv6[3];
+ u8 rsv7[2];
+} __packed;
+
+struct mt7925_sta_rec_lookup {
+ int sta_rec_idx;
+};
+
+/* Firmware auto-rate table snapshot, mirror Gen4m struct UNI_EVENT_STAT_WTBL.
+ *
+ * The firmware RA maintains up to MT7925_AUTO_RATE_NUM candidate rates in
+ * rate_code[]. rate_idx tells which entry is currently in use, so a dump can
+ * point at it with "-->". fcap is the bandwidth capability and the ldpc/sgi
+ * flags describe the per-mode coding/guard-interval used for those rates.
+ */
+struct mt7925_wtbl_rate {
+ __le16 tag;
+ __le16 len;
+ u8 rx_rcpi0;
+ u8 rx_rcpi1;
+ u8 cbrn;
+ u8 fcap;
+ u8 spe_idx;
+ u8 g2; /* SGI enabled, non-HE, BW20 */
+ u8 g4; /* SGI enabled, non-HE, BW40 */
+ u8 g8; /* SGI enabled, non-HE, BW80 */
+ u8 g16; /* SGI enabled, non-HE, BW160 */
+ u8 g2_he; /* GI, HE, BW20 */
+ u8 g4_he; /* GI, HE, BW40 */
+ u8 g8_he; /* GI, HE, BW80 */
+ u8 g16_he; /* GI, HE, BW160 */
+ u8 gi_eht; /* GI, EHT */
+ u8 ht_ldpc;
+ u8 vht_ldpc;
+ u8 he_ldpc;
+ u8 eht_ldpc;
+ u8 af;
+ u8 rate_idx; /* index into rate_code[] currently in use */
+ __le16 rate_code[MT7925_AUTO_RATE_NUM];
+ __le32 rsv[4];
+} __packed;
+
+int
+mt7925_get_ap_sta_rec_idx(struct mt792x_dev *dev, u8 *sta_rec_idx);
+
+void
+mt7925_print_last_tx_rate(struct seq_file *s, const __le32 le_txv[3]);
+
+u8
+mt7925_wtbl_get_sgi(const struct mt7925_wtbl_rate *wr, u8 txmode, u8 fcap);
+
+u8
+mt7925_wtbl_get_ldpc(const struct mt7925_wtbl_rate *wr, u8 txmode);
+
+char *mt7925_hw_rate_ofdm_str(u8 ofdm_idx);
+
+int mt792x_mcu_get_wtbl_rate(struct mt792x_dev *dev, u16 wlan_idx,
+ struct mt7925_wtbl_rate *rate);
+
+int mt792x_mcu_get_stat(struct mt792x_dev *dev,
+ struct mt7925_sta_stats *stats);
+
+int mt7925_mcu_get_mib_info(struct mt792x_dev *dev, u8 band_idx,
+ const u32 *counters, u8 counter_num,
+ u64 *values);
+
+/* GET_STAT_INFO (UNI_CMD_ID_GET_STATISTICS = 0x23),
+ * sub-command UNI_CMD_GET_STATISTICS_TAG_LINK_QUALITY = 1.
+ *
+ * @band_idx: band/BSS index (0..3)
+ * @rssi: out, moving-average beacon RSSI (dBm), may be NULL
+ * @tx_link_speed_kbps: out, TX link speed in kbit/s, may be NULL
+ *
+ * Returns 0 on success, -ENOENT if the band's link quality is not ready.
+ *
+ * NOTE: firmware provides only RSSI and TX rate here. RX rate/bandwidth are
+ * not in this event; read wcid->rate for the RX rate.
+ */
+int mt7925_mcu_get_link_quality(struct mt792x_dev *dev, u8 band_idx,
+ s8 *rssi, u32 *tx_link_speed_kbps);
+
+#endif
--
2.45.2
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH v5 0/2] wifi: mt76: mt7925: per-channel survey dump during scan
2026-08-31 3:28 ` [PATCH v4] " Eason Lai
@ 2026-11-12 7:55 ` Michael Shih
2026-11-12 7:55 ` [PATCH v5 1/2] wifi: mt76: mt7928: Add debugfs for stats to provide debug purpose Michael Shih
2026-11-12 7:55 ` [PATCH v5 2/2] wifi: mt76: mt7925: support per-channel survey dump during scan Michael Shih
0 siblings, 2 replies; 12+ messages in thread
From: Michael Shih @ 2026-11-12 7:55 UTC (permalink / raw)
To: nbd, lorenzo
Cc: linux-wireless, linux-mediatek, kun.wu, deren.wu, sean.wang,
quan.zhou, ryder.lee, leon.yen, litien.chang, jb.tsai, eason.Lai,
Michael Shih
This series improves channel survey reporting for mt7925 so that
"iw <dev> survey dump" shows meaningful per-channel airtime.
Patch 1 adds a stats debugfs infrastructure (shared stats.c/stats.h and
the firmware MIB plumbing) that patch 2 builds on.
Patch 2 wires up two channel survey accounting paths on top of it:
- the home/operating channel, settled from assign_vif_chanctx() since
mt7925 is firmware/chanctx managed and never goes through
__mt76_set_channel(); and
- the scanned channels, folded in from the per-channel airtime carried
by the firmware scan-done CHNLINFO event.
After the series, both the connected channel and every channel visited
during a firmware-offloaded scan report non-zero active/busy/rx/tx time.
Changes since v4:
---
v2: (1) Remove redundant code to fix build errors.
(2) Move UNI_CMD_MIB_DATA enum definition from 'mt76/mt76_connac_mcu.h' to
'mt7925/stats.h' to avoid redeclartion with 'mt76/mt7996/mcu.h'
v3: (1) Remove redundant code to fix build errors.
(2) Move UNI_CMD_MIB_DATA enum definition from 'mt76/mt76_connac_mcu.h' to
'mt7925/stats.h' to avoid redeclartion with 'mt76/mt7996/mcu.h'
v4: (1) Fix inconsistent indenting.
(2) Fix array offset 'band_idx' as to before use, not after use.
v5: Adding the second patch for supporting channel survey command: "iw <dev> survey dump"
---
Michael Shih (2):
wifi: mt76: mt7928: Add debugfs for stats to provide debug purpose
wifi: mt76: mt7925: support per-channel survey dump during scan
drivers/net/wireless/mediatek/mt76/mac80211.c | 3 +-
drivers/net/wireless/mediatek/mt76/mt76.h | 2 +
.../wireless/mediatek/mt76/mt76_connac_mcu.h | 173 ++++
.../wireless/mediatek/mt76/mt7925/Makefile | 2 +-
.../wireless/mediatek/mt76/mt7925/debugfs.c | 344 +++++++
.../net/wireless/mediatek/mt76/mt7925/main.c | 12 +
.../net/wireless/mediatek/mt76/mt7925/mcu.h | 30 +
.../wireless/mediatek/mt76/mt7925/mt7925.h | 7 +
.../net/wireless/mediatek/mt76/mt7925/pci.c | 2 +-
.../net/wireless/mediatek/mt76/mt7925/stats.c | 884 ++++++++++++++++++
.../net/wireless/mediatek/mt76/mt7925/stats.h | 315 +++++++
.../net/wireless/mediatek/mt76/mt7925/usb.c | 2 +-
drivers/net/wireless/mediatek/mt76/mt792x.h | 6 +
13 files changed, 1778 insertions(+), 4 deletions(-)
create mode 100644 drivers/net/wireless/mediatek/mt76/mt7925/stats.c
create mode 100644 drivers/net/wireless/mediatek/mt76/mt7925/stats.h
--
2.45.2
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH v5 1/2] wifi: mt76: mt7928: Add debugfs for stats to provide debug purpose
2026-11-12 7:55 ` [PATCH v5 0/2] wifi: mt76: mt7925: per-channel survey dump during scan Michael Shih
@ 2026-11-12 7:55 ` Michael Shih
2026-11-13 0:46 ` kernel test robot
2026-11-13 11:09 ` kernel test robot
2026-11-12 7:55 ` [PATCH v5 2/2] wifi: mt76: mt7925: support per-channel survey dump during scan Michael Shih
1 sibling, 2 replies; 12+ messages in thread
From: Michael Shih @ 2026-11-12 7:55 UTC (permalink / raw)
To: nbd, lorenzo
Cc: linux-wireless, linux-mediatek, kun.wu, deren.wu, sean.wang,
quan.zhou, ryder.lee, leon.yen, litien.chang, jb.tsai, eason.Lai,
Michael Shih
Usage:
(1) Connect STA to AP (You can use dhclient CMD to ask for DHCP IP, to use ping to AP)
DHCP IP CMD: dhclient ${WLAN_IF} -v
Example: dhclient wlan0 -v
## (To simulate traffic) Ping 10 packets for every second to AP
ping 192.168.0.XXX -i0.1
(2) (Get stats) Use command, phyX is your WiFi interface:
cat /sys/kernel/debug/ieee80211/phyX/mt76/stats
(3) You will see below stats info:
(STA) connected AP MAC Address = a8:42:a1:46:c4:6b
BssIdx = [0]
StaRecIdx = [1],
RSSI = -25
Link_Speed = 2402000 (kbit/s)
temperature = 40
Tx success = 716
Tx fail to Rcv ACK after retry = 0
Rx Mpdu = 23
Rx Fcs Error = 1 (MPDU)
Rx FIFO full = 0 (MPDU)
TRX_AGG_RANGE[0] = 0 (PPDU)
TRX_AGG_RANGE[1] = 0 (PPDU)
TRX_AGG_RANGE[2] = 0 (PPDU)
TRX_AGG_RANGE[3] = 0 (PPDU)
TRX_AGG_RANGE[4] = 0 (PPDU)
TRX_AGG_RANGE[5] = 0 (PPDU)
TRX_AGG_RANGE[6] = 0 (PPDU)
TRX_AGG_RANGE[7] = 0 (PPDU)
MPDUs in AMPDUs transmitted = 0 (MPDU)
MPDUs in AMPDUs transmitted with ACK reply = 0 (MPDU)
rate1_tx_cnt = 714
rate1_fail_cnt = 223
train_up = 2310
train_down = 3080
is_force_tx_stream = 0
is_force_se_off = 0
max_ampdu_factor = 9
tx_rate_up_penalty = 0
low_traffic_mode = 2
low_traffic_count = 0
low_traffic_dashboard = 3
dynamic_sgi_state = 0
dynamic_sgi_score = 2
dynamic_bw_state = 0
dynamic_gband_256qam_state = 3
vht_non_sp_rate_state = 0
Last TX Rate = 24M, BW20, OFDM, BCC, SPE24
Chip Out TX Power = +16.5 dBm
Cur Rate Index = 0
Rate index[0] --> NSS2_MCS11, BW160/BW8080, SGI, HE_SU, LDPC
Rate index[1] NSS2_MCS9, BW160/BW8080, SGI, HE_SU, LDPC
Rate index[2] NSS2_MCS7, BW160/BW8080, SGI, HE_SU, LDPC
Rate index[3] NSS2_MCS4, BW160/BW8080, SGI, HE_SU, LDPC
Rate index[4] NSS2_MCS3, BW160/BW8080, SGI, HE_SU, LDPC
Rate index[5] NSS1_MCS2, BW160/BW8080, SGI, HE_SU, LDPC
Rate index[6] NSS1_MCS1, BW80, SGI, HE_SU, LDPC
Rate index[7] NSS1_MCS0, BW80, SGI, HE_SU, LDPC
mib state:
=== Rx Related Counters ===
Rx with CRC = 24020 (MPDU)
Rx drop due to out of resource = 0 (MPDU)
Rx Mpdu = 93223 (MPDU)
Rx AMpdu = 668 (PPDU)
Rx PF Drop = 0 (MPDU)
Rx Len Mismatch = 5853 (PPDU)
=== Phy/Timing Related Counters ===
ChannelIdleCnt = 1040434
CCA_NAV_Tx_Time = 18510036
Rx_MDRDY_CNT = 166580 (PPDU)
CCK_MDRDY = 7782
OFDM_MDRDY = 11912731
OFDM_GREEN_MDRDY = 0
Prim CCA Time = 17917650
Sec CCA Time = 14922034
Prim ED Time = 5677528
=== Tx Related Counters(Generic) ===
BeaconTxCnt = 0
Tx 40MHz Cnt = 279 (MPDU)
Tx 80MHz Cnt = 44 (MPDU)
Tx 160MHz Cnt = 712 (MPDU)
=== BSSID[0] Related Counters ===
BA Miss Cnt = 210 (PPDU)
RTS Tx Cnt = 1003 (MPDU)
Frame Retry Cnt = 239 (MPDU)
Frame Retry 2 Cnt = 71 (MPDU)
RTS Retry Cnt = 152 (MPDU)
Ack Failed Cnt = 319 (PPDU)
=== AMPDU Related Counters ===
Tx AMPDU_Pkt_Cnt = 705 (PPDU)
Tx AMPDU_MPDU_Pkt_Cnt = 713 (MPDU)
AMPDU Tx success = 503 (MPDU)
AMPDU Tx fail count = 210 (MPDU), PER=29.4%
Signed-off-by: Michael Shih <michael.shih@mediatek.com>
---
.../wireless/mediatek/mt76/mt76_connac_mcu.h | 173 ++++++
.../wireless/mediatek/mt76/mt7925/Makefile | 2 +-
.../wireless/mediatek/mt76/mt7925/debugfs.c | 344 ++++++++++
.../net/wireless/mediatek/mt76/mt7925/stats.c | 585 ++++++++++++++++++
.../net/wireless/mediatek/mt76/mt7925/stats.h | 309 +++++++++
5 files changed, 1412 insertions(+), 1 deletion(-)
create mode 100644 drivers/net/wireless/mediatek/mt76/mt7925/stats.c
create mode 100644 drivers/net/wireless/mediatek/mt76/mt7925/stats.h
diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
index bb98a6eacac4..937790676edc 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
@@ -1489,6 +1489,179 @@ enum {
MT_NIC_CAP_EML_CAP = 0x22,
};
+enum {
+ UNI_CMD_ID_STATISTICS_BASIC = 0,
+ UNI_CMD_ID_STATISTICS_LINK_QUALITY = 1,
+ UNI_CMD_ID_STATISTICS_STA = 2,
+ UNI_CMD_ID_STATISTICS_BUG_REPORT = 3,
+ UNI_CMD_ID_STATISTICS_TX_STATS = 4,
+ UNI_CMD_ID_STATISTICS_ALL_STATS = 5,
+ UNI_CMD_ID_STATISTICS_EML_STATS = 6,
+ UNI_CMD_ID_STATISTICS_REGULAR_STATS = 7,
+ UNI_CMD_ID_STATISTICS_BSS_LINK_QUALITY = 8,
+ UNI_CMD_ID_STATISTICS_ML_CHNL_COND = 9,
+ UNI_CMD_ID_STATISTICS_WTBL = 10,
+ UNI_CMD_ID_STATISTICS_LINK_LAYER_STATS = 128,
+ UNI_CMD_ID_STATISTICS_PPDU_LATENCY = 129,
+ UNI_CMD_ID_STATISTICS_CURRENT_TX_RATE = 130,
+ UNI_CMD_ID_STATISTICS_SET_TX_BITRATE_MONITOR = 131,
+ UNI_CMD_ID_STATISTICS_GET_PREDICT_TX_BITRATE = 132,
+ UNI_CMD_ID_STATISTICS_SET_ATPS_TYPE_RATE_MODE = 133,
+ UNI_CMD_ID_STATISTICS_SET_BEACON_REPORT = 134,
+ UNI_CMD_ID_STATISTICS_BSS_CURRENT_TX_RATE = 135,
+ UNI_CMD_ID_STATISTICS_GET_BSS_PREDICT_TX_BITRATE = 136,
+ UNI_CMD_ID_STATISTICS_MAX_NUM
+};
+
+enum mt7925_mib_counter {
+ UNI_CMD_MIB_CNT_RX_FCS_ERR = 0,
+ UNI_CMD_MIB_CNT_RX_FIFO_OVERFLOW = 1,
+ UNI_CMD_MIB_CNT_RX_MPDU = 2,
+ UNI_CMD_MIB_CNT_AMPDU_RX_COUNT = 3,
+ UNI_CMD_MIB_CNT_RX_TOTAL_BYTE = 4,
+ UNI_CMD_MIB_CNT_RX_VALID_AMPDU_SF = 5,
+ UNI_CMD_MIB_CNT_RX_VALID_BYTE = 6,
+ UNI_CMD_MIB_CNT_CHANNEL_IDLE = 7,
+ UNI_CMD_MIB_CNT_VEC_DROP = 8,
+ UNI_CMD_MIB_CNT_DELIMITER_FAIL = 9,
+ UNI_CMD_MIB_CNT_VEC_MISMATCH = 10,
+ UNI_CMD_MIB_CNT_MDRDY = 11,
+ UNI_CMD_MIB_CNT_RX_CCK_MDRDY_TIME = 12,
+ UNI_CMD_MIB_CNT_RX_OFDM_LG_MIXED_MDRDY_TIME = 13,
+ UNI_CMD_MIB_CNT_RX_OFDM_GREEN_MDRDY_TIME = 14,
+ UNI_CMD_MIB_CNT_PF_DROP = 15,
+ UNI_CMD_MIB_CNT_LEN_MISMATCH = 16,
+ UNI_CMD_MIB_CNT_P_CCA_TIME = 17,
+ UNI_CMD_MIB_CNT_S_CCA_TIME = 18,
+ UNI_CMD_MIB_CNT_CCA_NAV_TX_TIME = 19,
+ UNI_CMD_MIB_CNT_P_ED_TIME = 20,
+ UNI_CMD_MIB_CNT_BCN_TX = 21,
+ UNI_CMD_MIB_CNT_TX_BW_20MHZ = 22,
+ UNI_CMD_MIB_CNT_TX_BW_40MHZ = 23,
+ UNI_CMD_MIB_CNT_TX_BW_80MHZ = 24,
+ UNI_CMD_MIB_CNT_TX_BW_160MHZ = 25,
+ UNI_CMD_RMAC_CNT_OBSS_AIRTIME = 26,
+ UNI_CMD_RMAC_CNT_NONWIFI_AIRTIME = 27,
+ UNI_CMD_MIB_CNT_TX_DUR_CNT = 28,
+ UNI_CMD_MIB_CNT_RX_DUR_CNT = 29,
+ UNI_CMD_MIB_CNT_BA_CNT = 30,
+ UNI_CMD_MIB_CNT_MAC2PHY_TX_TIME = 31,
+ UNI_CMD_MIB_CNT_RX_OUT_OF_RANGE_COUNT = 32,
+ UNI_CMD_MIB_CNT_IBF_TX = 33,
+ UNI_CMD_MIB_CNT_EBF_TX = 34,
+ UNI_CMD_MIB_CNT_MUBF_TX = 35,
+ UNI_CMD_MIB_CNT_RX_BF_VHTFBK = 36,
+ UNI_CMD_MIB_CNT_RX_BF_HTFBK = 37,
+ UNI_CMD_MIB_CNT_RX_BF_HEFBK = 38,
+ UNI_CMD_MIB_CNT_BFEE_TXFBK_BFPOLL_TRI = 39,
+ UNI_CMD_MIB_CNT_BFEE_TXFBK_TRI = 40,
+ UNI_CMD_MIB_CNT_BFEE_RX_FBKCQI = 41,
+ UNI_CMD_MIB_CNT_BFEE_RX_NDP = 42,
+ UNI_CMD_MIB_CNT_RX_BF_IBF_UPT = 43,
+ UNI_CMD_MIB_CNT_RX_BF_EBF_UPT = 44,
+ UNI_CMD_MIB_CNT_BFEE_SP_ABORT = 45,
+ UNI_CMD_MIB_CNT_BFEE_TB_LEN_ERR = 46,
+ UNI_CMD_MIB_CNT_BFEE_TXFBK_MUTE = 47,
+ UNI_CMD_MIB_CNT_BFEE_TMAC_ABORT = 48,
+ UNI_CMD_MIB_CNT_BFEE_TXFBK_CPL = 49,
+ UNI_CMD_MIB_CNT_BFEE_COANT_BLKTX = 50,
+ UNI_CMD_MIB_CNT_BFEE_FBK_SEG = 51,
+ UNI_CMD_MIB_CNT_NAV_TIME = 52,
+ UNI_CMD_MIB_CNT_TX_ABORT_CNT1 = 53,
+ UNI_CMD_MIB_CNT_TX_ABORT_CNT0 = 54,
+ UNI_CMD_MIB_CNT_TX_ABORT_CNT3 = 55,
+ UNI_CMD_MIB_CNT_TX_ABORT_CNT2 = 56,
+ UNI_CMD_MIB_CNT_BSS0_RTS_TX_CNT = 57,
+ UNI_CMD_MIB_CNT_BSS1_RTS_TX_CNT = 58,
+ UNI_CMD_MIB_CNT_BSS2_RTS_TX_CNT = 59,
+ UNI_CMD_MIB_CNT_BSS3_RTS_TX_CNT = 60,
+ UNI_CMD_MIB_CNT_BSS0_RTS_RETRY = 61,
+ UNI_CMD_MIB_CNT_BSS1_RTS_RETRY = 62,
+ UNI_CMD_MIB_CNT_BSS2_RTS_RETRY = 63,
+ UNI_CMD_MIB_CNT_BSS3_RTS_RETRY = 64,
+ UNI_CMD_MIB_CNT_BSS0_BA_MISS = 65,
+ UNI_CMD_MIB_CNT_BSS1_BA_MISS = 66,
+ UNI_CMD_MIB_CNT_BSS2_BA_MISS = 67,
+ UNI_CMD_MIB_CNT_BSS3_BA_MISS = 68,
+ UNI_CMD_MIB_CNT_BSS0_ACK_FAIL = 69,
+ UNI_CMD_MIB_CNT_BSS1_ACK_FAIL = 70,
+ UNI_CMD_MIB_CNT_BSS2_ACK_FAIL = 71,
+ UNI_CMD_MIB_CNT_BSS3_ACK_FAIL = 72,
+ UNI_CMD_MIB_CNT_BSS0_FRAME_RETRY = 73,
+ UNI_CMD_MIB_CNT_BSS1_FRAME_RETRY = 74,
+ UNI_CMD_MIB_CNT_BSS2_FRAME_RETRY = 75,
+ UNI_CMD_MIB_CNT_BSS3_FRAME_RETRY = 76,
+ UNI_CMD_MIB_CNT_BSS0_FRAME_RETRY_2 = 77,
+ UNI_CMD_MIB_CNT_BSS1_FRAME_RETRY_2 = 78,
+ UNI_CMD_MIB_CNT_BSS2_FRAME_RETRY_2 = 79,
+ UNI_CMD_MIB_CNT_BSS3_FRAME_RETRY_2 = 80,
+ UNI_CMD_MIB_CNT_RX_A1_SEARCH = 81,
+ UNI_CMD_MIB_CNT_RX_DROP_MPDU = 82,
+ UNI_CMD_MIB_CNT_RX_UNWANTED = 83,
+ UNI_CMD_MIB_CNT_RX_FCS_OK = 84,
+ UNI_CMD_MIB_CNT_SU_TX_OK = 85,
+ UNI_CMD_MIB_CNT_TX_FULL_BW = 86,
+ UNI_CMD_MIB_CNT_TX_AUTO_BW = 87,
+ UNI_CMD_MIB_CNT_CCA_SAMPLE_IDLE = 88,
+ UNI_CMD_MIB_CNT_CCA_SAMPLE_ACTIVE = 89,
+ UNI_CMD_MIB_CNT_CCA_TIME = 90,
+ UNI_CMD_MIB_CNT_S_20BW_CCA_TIME = 91,
+ UNI_CMD_MIB_CNT_S_40BW_CCA_TIME = 92,
+ UNI_CMD_MIB_CNT_S_80BW_CCA_TIME = 93,
+ UNI_CMD_MIB_CNT_S_160BW_CCA_TIME = 94,
+ UNI_CMD_MIB_CNT_S_P20BW_0_ED_TIME = 95,
+ UNI_CMD_MIB_CNT_S_P20BW_1_ED_TIME = 96,
+ UNI_CMD_MIB_CNT_S_P20BW_2_ED_TIME = 97,
+ UNI_CMD_MIB_CNT_S_P20BW_3_ED_TIME = 98,
+ UNI_CMD_MIB_CNT_S_P20BW_4_ED_TIME = 99,
+ UNI_CMD_MIB_CNT_S_P20BW_5_ED_TIME = 100,
+ UNI_CMD_MIB_CNT_S_P20BW_6_ED_TIME = 101,
+ UNI_CMD_MIB_CNT_S_P20BW_7_ED_TIME = 102,
+ UNI_CMD_MIB_CNT_S_P20BW_8_ED_TIME = 103,
+ UNI_CMD_MIB_CNT_S_P20BW_9_ED_TIME = 104,
+ UNI_CMD_MIB_CNT_S_P20BW_10_ED_TIME = 105,
+ UNI_CMD_MIB_CNT_S_P20BW_11_ED_TIME = 106,
+ UNI_CMD_MIB_CNT_S_P20BW_12_ED_TIME = 107,
+ UNI_CMD_MIB_CNT_S_P20BW_13_ED_TIME = 108,
+ UNI_CMD_MIB_CNT_S_P20BW_14_ED_TIME = 109,
+ UNI_CMD_MIB_CNT_S_P20BW_15_ED_TIME = 110,
+ UNI_CMD_MIB_CNT_TX_BW_320MHZ = 111,
+ UNI_CMD_MIB_CNT_TX_DDLMT_RNG0 = 112, /* Rng0~Rng4 112~116 */
+ UNI_CMD_MIB_CNT_BSS0_FRAME_RETRY_3 = 117, /* 117~120 */
+ UNI_CMD_MIB_CNT_BSS0_TX = 121, /* 121~124 */
+ UNI_CMD_MIB_CNT_BSS0_TX_DATA = 125, /* 125~128 */
+ UNI_CMD_MIB_CNT_BSS0_TX_BYTE = 129, /* 129~132 */
+ UNI_CMD_MIB_CNT_RX_OK_BSS0 = 133, /* 133~136 */
+ UNI_CMD_MIB_CNT_RX_BYTE_BSS0 = 137, /* 137~140 */
+ UNI_CMD_MIB_CNT_RX_DATA_BSS0 = 141, /* 141~144 */
+ UNI_CMD_MIB_CNT_MBSS0_TX_OK = 145, /* 145~160 */
+ UNI_CMD_MIB_CNT_MBSS0_TX_BYTE = 161, /* 161~176 */
+ UNI_CMD_MIB_CNT_RX_OK_MBSS0 = 177, /* 177~192 */
+ UNI_CMD_MIB_CNT_RX_BYTE_MBSS0 = 193, /* 193~208 */
+ UNI_CMD_MIB_CNT_AMPDU = 209,
+ UNI_CMD_MIB_CNT_AMPDU_MPDU = 210,
+ UNI_CMD_MIB_CNT_AMPDU_ACKED = 211,
+ UNI_CMD_MIB_CNT_MAX_NUM
+};
+
+enum ENUM_UNI_CMD_GET_STATISTICS_TAG {
+ UNI_CMD_GET_STATISTICS_TAG_BASIC = 0,
+ UNI_CMD_GET_STATISTICS_TAG_LINK_QUALITY = 1,
+ UNI_CMD_GET_STATISTICS_TAG_STA = 2,
+ UNI_CMD_GET_STATISTICS_TAG_BUG_REPORT = 3,
+ UNI_CMD_GET_STATISTICS_TAG_EML_STATS = 6,
+ UNI_CMD_GET_STATISTICS_TAG_REGULAR_STATS = 7,
+ UNI_CMD_GET_STATISTICS_TAG_BSS_LINK_QUALITY = 8,
+ UNI_CMD_GET_STATISTICS_TAG_GET_ML_CHNL_COND = 9,
+ UNI_CMD_GET_STATISTICS_TAG_STAT_WTBL = 10,
+ UNI_CMD_GET_STATISTICS_TAG_LINK_LAYER_STATS = 0x80,
+ UNI_CMD_GET_STATISTICS_TAG_PPDU_LATENCY,
+ UNI_CMD_GET_STATISTICS_TAG_CURRENT_TX_RATE,
+ UNI_CMD_GET_STATISTICS_TAG_BEACON_REPORT = 0x86,
+ UNI_CMD_GET_STATISTICS_TAG_BSS_CURRENT_TX_RATE = 0x87,
+ UNI_CMD_GET_STATISTICS_TAG_BSS_PRED_TX_BITRATE = 0x88,
+};
+
#define UNI_WOW_DETECT_TYPE_MAGIC BIT(0)
#define UNI_WOW_DETECT_TYPE_ANY BIT(1)
#define UNI_WOW_DETECT_TYPE_DISCONNECT BIT(2)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/Makefile b/drivers/net/wireless/mediatek/mt76/mt7925/Makefile
index 33539121a15c..dc596b93c7cc 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/Makefile
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/Makefile
@@ -4,7 +4,7 @@ obj-$(CONFIG_MT7925_COMMON) += mt7925-common.o
obj-$(CONFIG_MT7925E) += mt7925e.o
obj-$(CONFIG_MT7925U) += mt7925u.o
-mt7925-common-y := mac.o mcu.o regd.o main.o init.o debugfs.o nan.o
+mt7925-common-y := mac.o mcu.o regd.o main.o init.o debugfs.o nan.o stats.o
mt7925-common-$(CONFIG_NL80211_TESTMODE) += testmode.o
mt7925e-y := pci.o pci_mac.o pci_mcu.o cached_cal.o
mt7925u-y := usb.o
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c b/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c
index d01ff49de47a..efff0b88dec4 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c
@@ -2,7 +2,10 @@
/* Copyright (C) 2023 MediaTek Inc. */
#include "mt7925.h"
+#include "mac.h"
#include "mcu.h"
+#include "regd.h"
+#include "stats.h"
static int
mt7925_reg_set(void *data, u64 val)
@@ -286,6 +289,345 @@ static int mt7925_chip_reset(void *data, u64 val)
DEFINE_DEBUGFS_ATTRIBUTE(fops_reset, NULL, mt7925_chip_reset, "%lld\n");
+static const u32 stats_query_mib_counters[] = {
+ UNI_CMD_MIB_CNT_RX_FCS_ERR,
+ UNI_CMD_MIB_CNT_RX_FIFO_OVERFLOW,
+ UNI_CMD_MIB_CNT_RX_MPDU,
+ UNI_CMD_MIB_CNT_AMPDU_RX_COUNT,
+ UNI_CMD_MIB_CNT_PF_DROP,
+ UNI_CMD_MIB_CNT_LEN_MISMATCH,
+ UNI_CMD_MIB_CNT_CHANNEL_IDLE,
+ UNI_CMD_MIB_CNT_CCA_NAV_TX_TIME,
+ UNI_CMD_MIB_CNT_MDRDY,
+ UNI_CMD_MIB_CNT_RX_CCK_MDRDY_TIME,
+ UNI_CMD_MIB_CNT_RX_OFDM_LG_MIXED_MDRDY_TIME,
+ UNI_CMD_MIB_CNT_RX_OFDM_GREEN_MDRDY_TIME,
+ UNI_CMD_MIB_CNT_P_CCA_TIME,
+ UNI_CMD_MIB_CNT_S_CCA_TIME,
+ UNI_CMD_MIB_CNT_P_ED_TIME,
+ UNI_CMD_MIB_CNT_BCN_TX,
+ UNI_CMD_MIB_CNT_TX_BW_40MHZ,
+ UNI_CMD_MIB_CNT_TX_BW_80MHZ,
+ UNI_CMD_MIB_CNT_TX_BW_160MHZ,
+ UNI_CMD_MIB_CNT_BSS0_BA_MISS,
+ UNI_CMD_MIB_CNT_BSS0_RTS_TX_CNT,
+ UNI_CMD_MIB_CNT_BSS0_FRAME_RETRY,
+ UNI_CMD_MIB_CNT_BSS0_FRAME_RETRY_2,
+ UNI_CMD_MIB_CNT_BSS0_RTS_RETRY,
+ UNI_CMD_MIB_CNT_BSS0_ACK_FAIL,
+ UNI_CMD_MIB_CNT_AMPDU,
+ UNI_CMD_MIB_CNT_AMPDU_MPDU,
+ UNI_CMD_MIB_CNT_AMPDU_ACKED,
+};
+
+/* Dump the whole firmware auto-rate table and mark the entry currently in use
+ * with "-->"
+ */
+static void
+mt7925_dump_auto_rate_table(struct seq_file *s,
+ const struct mt7925_wtbl_rate *wr)
+{
+ u8 fcap = wr->fcap;
+ u8 rate_idx = wr->rate_idx;
+ u8 cbrn = wr->cbrn;
+ int i;
+
+ seq_printf(s, "%-22s Cur Rate Index = %u\n", " ", rate_idx);
+
+ for (i = 0; i < MT7925_AUTO_RATE_NUM; i++) {
+ u16 rate_code = le16_to_cpu(wr->rate_code[i]);
+ u8 txmode = MT7925_HW_RATE_TO_MODE(rate_code);
+ u8 rate = MT7925_HW_RATE_TO_MCS(rate_code);
+ u8 nsts = MT7925_HW_RATE_TO_NSS(rate_code) + 1;
+ u8 stbc = MT7925_HW_RATE_TO_STBC(rate_code);
+ u8 dcm = MT7925_HW_RATE_TO_DCM(rate_code);
+ u8 ersu106t = MT7925_HW_RATE_TO_106T(rate_code);
+ u8 sgi = mt7925_wtbl_get_sgi(wr, txmode, fcap);
+ u8 ldpc = mt7925_wtbl_get_ldpc(wr, txmode);
+ const char *mode_str = "N/A";
+
+ if (dcm)
+ rate = MT7925_HW_RATE_UNMASK_DCM(rate);
+ if (ersu106t)
+ rate = MT7925_HW_RATE_UNMASK_106T(rate);
+
+ if (txmode < ARRAY_SIZE(mt7925_tx_mode_str))
+ mode_str = mt7925_tx_mode_str[txmode];
+
+ seq_printf(s, "Rate index[%d] %s", i,
+ rate_idx == i ? "--> " : " ");
+
+ /* rate/MCS field */
+ if (txmode == MT7925_TX_RATE_MODE_CCK)
+ seq_printf(s, "%s, ",
+ mt7925_tx_rate_cck_str[rate & 0x3]);
+ else if (txmode == MT7925_TX_RATE_MODE_OFDM)
+ seq_printf(s, "%s, ", mt7925_hw_rate_ofdm_str(rate));
+ else if (txmode == MT7925_TX_RATE_MODE_HTMIX ||
+ txmode == MT7925_TX_RATE_MODE_HTGF)
+ seq_printf(s, "MCS%d, ", rate);
+ else
+ seq_printf(s, "%s%d_MCS%d, ",
+ stbc ? "NSTS" : "NSS", nsts, rate);
+
+ /* bandwidth: mirror the CCK/OFDM -> BW20 and cbrn handling */
+ if (txmode == MT7925_TX_RATE_MODE_CCK ||
+ txmode == MT7925_TX_RATE_MODE_OFDM)
+ seq_printf(s, "%s, ", mt7925_tx_rate_bw_str[0]);
+ else if (i > cbrn)
+ seq_printf(s, "%s, ",
+ fcap < 5 ?
+ (fcap > MT7925_BW_20 ?
+ mt7925_tx_rate_bw_str[fcap - 1] :
+ mt7925_tx_rate_bw_str[fcap]) :
+ mt7925_tx_rate_bw_str[5]);
+ else
+ seq_printf(s, "%s, ",
+ fcap < 5 ? mt7925_tx_rate_bw_str[fcap] :
+ mt7925_tx_rate_bw_str[5]);
+
+ /* GI / preamble */
+ if (txmode == MT7925_TX_RATE_MODE_CCK)
+ seq_printf(s, "%s, ", rate < 4 ? "LP" : "SP");
+ else if (txmode == MT7925_TX_RATE_MODE_OFDM)
+ ; /* OFDM has no GI/preamble to print */
+ else if (txmode == MT7925_TX_RATE_MODE_HTMIX ||
+ txmode == MT7925_TX_RATE_MODE_HTGF ||
+ txmode == MT7925_TX_RATE_MODE_VHT ||
+ txmode == MT7925_TX_RATE_MODE_PLR)
+ seq_printf(s, "%s, ", sgi == 0 ? "LGI" : "SGI");
+ else
+ seq_printf(s, "%s, ",
+ sgi == 0 ? "SGI" :
+ (sgi == 1 ? "MGI" : "LGI"));
+
+ /* mode, DCM, 106t, STBC, coding */
+ seq_printf(s, "%s%s%s%s%s\n",
+ mode_str,
+ dcm ? ", DCM" : "", ersu106t ? ", 106t" : "",
+ stbc ? ", STBC, " : ", ",
+ (ldpc == 0 ||
+ txmode == MT7925_TX_RATE_MODE_CCK ||
+ txmode == MT7925_TX_RATE_MODE_OFDM) ?
+ "BCC" : "LDPC");
+ }
+}
+
+static int mt792x_stats(struct seq_file *s, void *data)
+{
+ s8 rssi = 0;
+ u8 band_idx = 0;
+ bool wtbl_rate_valid = false;
+ int ret = 0;
+ int i = 0;
+ u32 stats_value = 0;
+ u32 tx_link_speed_kbps = 0;
+ u64 tx_total = 0;
+ u64 tx_fail = 0;
+ u64 tx_per = 0;
+ /* UNI_CMD_MIB_DATA */
+ u64 mib_values[ARRAY_SIZE(stats_query_mib_counters)];
+ struct mt792x_dev *dev = dev_get_drvdata(s->private);
+ struct mt7925_wtbl_rate wtbl_rate;
+ struct mt7925_sta_stats *stats = NULL;
+
+ band_idx = dev->mphy.band_idx;
+
+ stats = kzalloc(sizeof(*stats), GFP_KERNEL);
+ if (!stats)
+ return -ENOMEM;
+
+ /* Get STA_STATS */
+ mt792x_mutex_acquire(dev);
+ ret = mt792x_mcu_get_stat(dev, stats);
+ mt792x_mutex_release(dev);
+ if (ret)
+ goto out;
+
+ /* Get MIB_STATS */
+ mt792x_mutex_acquire(dev);
+ ret = mt7925_mcu_get_mib_info(dev, band_idx, stats_query_mib_counters,
+ ARRAY_SIZE(stats_query_mib_counters), mib_values);
+ mt792x_mutex_release(dev);
+ if (ret)
+ goto out;
+
+ /* Get Link_Speed and RSSI */
+ mt792x_mutex_acquire(dev);
+ ret = mt7925_mcu_get_link_quality(dev, stats->bss_idx, &rssi, &tx_link_speed_kbps);
+ mt792x_mutex_release(dev);
+ if (ret)
+ goto out;
+
+ /* Get the firmware auto-rate table (best effort; do not abort the
+ * whole stats dump if the WTBL query is not available).
+ */
+ mt792x_mutex_acquire(dev);
+ if (!mt792x_mcu_get_wtbl_rate(dev, stats->wtbl_idx, &wtbl_rate))
+ wtbl_rate_valid = true;
+ mt792x_mutex_release(dev);
+
+ /* Print STA_STATS info */
+ seq_printf(s, "(STA) connected AP MAC Address = %pM\n", stats->mac_addr);
+ seq_printf(s, "%-22s BssIdx = [%u]\n", " ", stats->bss_idx);
+ seq_printf(s, "%-22s StaRecIdx = [%u]\n",
+ " ", stats->sta_idx);
+ seq_printf(s, "%-22s RSSI = %d\n", " ", rssi);
+ seq_printf(s, "%-22s Link_Speed = %u (kbit/s)\n", " ", tx_link_speed_kbps);
+
+ seq_printf(s, "%-22s temperature = %u\n",
+ " ", stats->temperature);
+
+ stats_value = le32_to_cpu(stats->transmit_count) - le32_to_cpu(stats->transmit_fail_count);
+ seq_printf(s, "%-22s Tx success = %u\n",
+ " ", stats_value);
+
+ stats_value = le32_to_cpu(stats->tx_fail_count) - le32_to_cpu(stats->tx_life_timeout_count);
+ seq_printf(s, "%-22s Tx fail to Rcv ACK after retry = %u\n",
+ " ", stats_value);
+
+ seq_printf(s, "%-22s Rx Mpdu = %u\n",
+ " ", le32_to_cpu(stats->mib[band_idx].rx_mpdu_cnt));
+
+ seq_printf(s, "%-22s Rx Fcs Error = %u (MPDU)\n",
+ " ", le32_to_cpu(stats->mib[band_idx].fcs_error));
+ seq_printf(s, "%-22s Rx FIFO full = %u (MPDU)\n",
+ " ", le32_to_cpu(stats->mib[band_idx].rx_fifo_full));
+
+ for (i = 0; i < STAT_MIB_CNT_TRX_AGG_RANGE_MAX_NUM; i++)
+ seq_printf(s, "%-22s TRX_AGG_RANGE[%d] = %u (PPDU)\n",
+ " ", i, le32_to_cpu(stats->mib[band_idx].tx_range_ampdu_cnt[i]));
+
+ seq_printf(s, "%-22s MPDUs in AMPDUs transmitted = %u (MPDU)\n",
+ " ", le32_to_cpu(stats->mib[band_idx].ampdu_tx_sf_cnt));
+ seq_printf(s, "%-22s MPDUs in AMPDUs transmitted with ACK reply = %u (MPDU)\n",
+ " ", le32_to_cpu(stats->mib[band_idx].ampdu_tx_ack_sf_cnt));
+ seq_printf(s, "%-22s rate1_tx_cnt = %u\n",
+ " ", le32_to_cpu(stats->rate1_tx_cnt));
+ seq_printf(s, "%-22s rate1_fail_cnt = %u\n",
+ " ", le32_to_cpu(stats->rate1_fail_cnt));
+ seq_printf(s, "%-22s train_up = %u\n",
+ " ", le16_to_cpu(stats->train_up));
+ seq_printf(s, "%-22s train_down = %u\n",
+ " ", le16_to_cpu(stats->train_down));
+ seq_printf(s, "%-22s is_force_tx_stream = %u\n",
+ " ", stats->is_force_tx_stream);
+ seq_printf(s, "%-22s is_force_se_off = %u\n",
+ " ", stats->is_force_se_off);
+ seq_printf(s, "%-22s max_ampdu_factor = %u\n",
+ " ", stats->max_ampdu_factor);
+ seq_printf(s, "%-22s tx_rate_up_penalty = %u\n",
+ " ", stats->tx_rate_up_penalty);
+ seq_printf(s, "%-22s low_traffic_mode = %u\n",
+ " ", stats->low_traffic_mode);
+ seq_printf(s, "%-22s low_traffic_count = %u\n",
+ " ", stats->low_traffic_count);
+ seq_printf(s, "%-22s low_traffic_dashboard = %u\n",
+ " ", stats->low_traffic_dashboard);
+ seq_printf(s, "%-22s dynamic_sgi_state = %u\n",
+ " ", stats->dynamic_sgi_state);
+ seq_printf(s, "%-22s dynamic_sgi_score = %u\n",
+ " ", stats->dynamic_sgi_score);
+ seq_printf(s, "%-22s dynamic_bw_state = %u\n",
+ " ", stats->dynamic_bw_state);
+ seq_printf(s, "%-22s dynamic_gband_256qam_state = %u\n",
+ " ", stats->dynamic_gband_256qam_state);
+ seq_printf(s, "%-22s vht_non_sp_rate_state = %u\n",
+ " ", stats->vht_non_sp_rate_state);
+
+ /* Decode the TX Vector BBP latch to show the current TX MCS rate */
+ if (band_idx < MT7925_STA_STATS_BAND_NUM)
+ mt7925_print_last_tx_rate(s, stats->tx_vector[band_idx].txv);
+
+ /* Dump the whole firmware auto-rate table and mark the entry currently */
+ if (wtbl_rate_valid)
+ mt7925_dump_auto_rate_table(s, &wtbl_rate);
+
+ seq_puts(s, "\nmib state:\n");
+ /* ===Rx Related Counters=== */
+ seq_puts(s, "=== Rx Related Counters ===\n");
+ seq_printf(s, "%-22s Rx with CRC = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_RX_FCS_ERR]);
+ seq_printf(s, "%-22s Rx drop due to out of resource = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_RX_FIFO_OVERFLOW]);
+ seq_printf(s, "%-22s Rx Mpdu = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_RX_MPDU]);
+ seq_printf(s, "%-22s Rx AMpdu = %llu (PPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_AMPDU_RX_COUNT]);
+ seq_printf(s, "%-22s Rx PF Drop = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_PF_DROP]);
+ seq_printf(s, "%-22s Rx Len Mismatch = %llu (PPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_LEN_MISMATCH]);
+
+ /* ===Phy/Timing Related Counters=== */
+ seq_puts(s, "\n=== Phy/Timing Related Counters ===\n");
+ seq_printf(s, "%-22s ChannelIdleCnt = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_CHANNEL_IDLE]);
+ seq_printf(s, "%-22s CCA_NAV_Tx_Time = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_CCA_NAV_TX_TIME]);
+ seq_printf(s, "%-22s Rx_MDRDY_CNT = %llu (PPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_MDRDY]);
+ seq_printf(s, "%-22s CCK_MDRDY = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_RX_CCK_MDRDY_TIME]);
+ seq_printf(s, "%-22s OFDM_MDRDY = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_RX_OFDM_LG_MIXED_MDRDY_TIME]);
+ seq_printf(s, "%-22s OFDM_GREEN_MDRDY = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_RX_OFDM_GREEN_MDRDY_TIME]);
+ seq_printf(s, "%-22s Prim CCA Time = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_P_CCA_TIME]);
+ seq_printf(s, "%-22s Sec CCA Time = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_S_CCA_TIME]);
+ seq_printf(s, "%-22s Prim ED Time = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_P_ED_TIME]);
+
+ /* ===Tx Related Counters(Generic)=== */
+ seq_puts(s, "\n=== Tx Related Counters(Generic) ===\n");
+ seq_printf(s, "%-22s BeaconTxCnt = %llu\n",
+ " ", mib_values[QUERY_MIB_CNT_BCN_TX]);
+ seq_printf(s, "%-22s Tx 40MHz Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_TX_BW_40MHZ]);
+ seq_printf(s, "%-22s Tx 80MHz Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_TX_BW_80MHZ]);
+ seq_printf(s, "%-22s Tx 160MHz Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_TX_BW_160MHZ]);
+
+ /* ===BSSID[0] Related Counters=== */
+ seq_puts(s, "\n=== BSSID[0] Related Counters ===\n");
+ seq_printf(s, "%-22s BA Miss Cnt = %llu (PPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_BSS0_BA_MISS]);
+ seq_printf(s, "%-22s RTS Tx Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_BSS0_RTS_TX_CNT]);
+ seq_printf(s, "%-22s Frame Retry Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_BSS0_FRAME_RETRY]);
+ seq_printf(s, "%-22s Frame Retry 2 Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_BSS0_FRAME_RETRY_2]);
+ seq_printf(s, "%-22s RTS Retry Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_BSS0_RTS_RETRY]);
+ seq_printf(s, "%-22s Ack Failed Cnt = %llu (PPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_BSS0_ACK_FAIL]);
+
+ /* ===AMPDU Related Counters=== */
+ seq_puts(s, "\n=== AMPDU Related Counters ===\n");
+ seq_printf(s, "%-22s Tx AMPDU_Pkt_Cnt = %llu (PPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_AMPDU]);
+ seq_printf(s, "%-22s Tx AMPDU_MPDU_Pkt_Cnt = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_AMPDU_MPDU]);
+ seq_printf(s, "%-22s AMPDU Tx success = %llu (MPDU)\n",
+ " ", mib_values[QUERY_MIB_CNT_AMPDU_ACKED]);
+
+ tx_total = mib_values[QUERY_MIB_CNT_AMPDU_MPDU];
+ tx_fail = mib_values[QUERY_MIB_CNT_AMPDU_MPDU] - mib_values[QUERY_MIB_CNT_AMPDU_ACKED];
+ tx_per = tx_total == 0 ? 0 : (1000 * (tx_fail)) / tx_total;
+ seq_printf(s, "%-22s AMPDU Tx fail count = %llu (MPDU), PER=%llu.%1llu%%\n",
+ " ",
+ tx_fail,
+ tx_per / 10, tx_per % 10);
+
+out:
+ kfree(stats);
+ return ret;
+}
+
int mt7925_init_debugfs(struct mt792x_dev *dev)
{
struct dentry *dir;
@@ -313,5 +655,7 @@ int mt7925_init_debugfs(struct mt792x_dev *dev)
mt792x_pm_stats);
debugfs_create_file("deep-sleep", 0600, dir, dev, &fops_ds);
+ debugfs_create_devm_seqfile(dev->mt76.dev, "stats", dir,
+ mt792x_stats);
return 0;
}
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/stats.c b/drivers/net/wireless/mediatek/mt76/mt7925/stats.c
new file mode 100644
index 000000000000..9a47953062bc
--- /dev/null
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/stats.c
@@ -0,0 +1,585 @@
+// SPDX-License-Identifier: BSD-3-Clause-Clear
+/* Copyright (C) 2026 MediaTek Inc. */
+
+#include <linux/firmware.h>
+#include <linux/fs.h>
+#include "mt7925.h"
+#include "mcu.h"
+#include "mac.h"
+#include "stats.h"
+
+#define MT7925_STA_STATS_VERSION 1
+#define MT7925_STA_STATS_VALID 1U
+
+#define MT7925_MIB_MAX_CNT 32
+#define MT7925_MIB_CNT_MAX_NUM 512 /* firmware UNI_CMD_MIB_CNT_MAX_NUM upper bound */
+
+#define MT7925_LQ_BAND_NUM 4 /* Link quality band */
+
+const char * const mt7925_tx_mode_str[] = {
+ "CCK", "OFDM", "MM", "GF", "VHT", "PLR",
+ "PLRP", "ALR", "HE_SU", "HE_ER", "HE_TRIG", "HE_MU",
+ "EHT_ER", "TOF", "EHT_TRIG", "EHT_MU"
+};
+
+const char * const mt7925_tx_rate_cck_str[] = {
+ "1M", "2M", "5.5M", "11M", "N/A"
+};
+
+const char * const mt7925_tx_rate_bw_str[] = {
+ "BW20", "BW40", "BW80", "BW160/BW8080", "BW320", "N/A"
+};
+
+struct mt7925_sta_stats_req {
+ u8 rsv[4];
+ __le16 tag;
+ __le16 len;
+ u8 sta_rec_idx;
+ u8 rsv1;
+ u8 lls_read_clear;
+ u8 reset_counter;
+} __packed;
+
+struct mt7925_sta_stats_event {
+ u8 rsv[4];
+ __le16 tag;
+ __le16 len;
+ struct mt7925_sta_stats stats;
+} __packed;
+
+/* WTBL auto-rate request TLV, mirror Gen4m struct UNI_CMD_STAT_WTBL.
+ * The RA tag reuses UNI_CMD_ID_STATISTICS_WTBL (10).
+ */
+struct mt7925_wtbl_rate_req {
+ u8 rsv[4];
+ __le16 tag;
+ __le16 len;
+ __le16 wlan_idx;
+ __le16 rsv1;
+} __packed;
+
+/* WTBL auto-rate event TLV. Firmware answers cmd tag 10 with event tag 12. */
+struct mt7925_wtbl_rate_event {
+ u8 rsv[4];
+ struct mt7925_wtbl_rate rate;
+} __packed;
+
+/* fixed field, mirror UNI_(CMD|EVENT)_MIB_INFO */
+struct mt7925_mib_hdr {
+ u8 band_idx;
+ u8 rsv[3];
+} __packed;
+
+/* One requested/returned MIB counter, mirror UNI_(CMD|EVENT)_MIB_DATA_T. */
+struct mt7925_mib_data {
+ __le16 tag; /* UNI_(CMD|EVENT)_MIB_DATA = 0 */
+ __le16 len;
+ __le32 counter; /* MIB counter ID (enum mt7925_mib_counter) */
+ __le64 data; /* only valid on event; ignored on cmd */
+} __packed;
+
+/* per-band link quality entry, mirror UNI_LINK_QUALITY */
+struct mt7925_link_quality {
+ s8 rssi; /* moving-average beacon RSSI of connected AP */
+ s8 link_quality; /* always 0 */
+ __le16 link_speed; /* TX rate1, unit 500 Kb/s */
+ u8 medium_busy_pct; /* always 0 */
+ u8 is_ready; /* 0: fields invalid, 1: valid */
+ u8 rsv[2];
+} __packed;
+
+/* request TLV, mirror UNI_CMD_LINK_QUALITY (header + tag/len only) */
+struct mt7925_lq_req {
+ u8 rsv[4]; /* UNI_CMD_GET_STATISTICS fixed field */
+ __le16 tag; /* UNI_CMD_GET_STATISTICS_TAG_LINK_QUALITY = 1 */
+ __le16 len;
+} __packed;
+
+/* event TLV, mirror UNI_EVENT_LINK_QUALITY */
+struct mt7925_lq_event {
+ u8 rsv[4]; /* UNI_EVENT_STATISTICS fixed field */
+ __le16 tag; /* UNI_EVENT_ID_STATISTICS_LINK_QUALITY = 1 */
+ __le16 len;
+ struct mt7925_link_quality lq[MT7925_LQ_BAND_NUM];
+} __packed;
+
+static void
+mt7925_get_ap_sta_rec_idx_iter(void *data, u8 *mac,
+ struct ieee80211_vif *vif)
+{
+ struct mt7925_sta_rec_lookup *lookup = data;
+ struct mt792x_vif *mvif;
+ struct mt792x_sta *msta;
+
+ if (lookup->sta_rec_idx >= 0)
+ return;
+
+ if (vif->type != NL80211_IFTYPE_STATION || !vif->cfg.assoc)
+ return;
+
+ mvif = (struct mt792x_vif *)vif->drv_priv;
+ msta = mvif->wep_sta;
+ if (!msta)
+ return;
+
+ if (!msta->deflink.wcid.sta)
+ return;
+
+ lookup->sta_rec_idx = msta->deflink.wcid.idx;
+}
+
+int
+mt7925_get_ap_sta_rec_idx(struct mt792x_dev *dev, u8 *sta_rec_idx)
+{
+ struct mt7925_sta_rec_lookup lookup = {
+ .sta_rec_idx = -1,
+ };
+
+ lockdep_assert_held(&dev->mt76.mutex);
+
+ ieee80211_iterate_active_interfaces(dev->mphy.hw,
+ IEEE80211_IFACE_ITER_RESUME_ALL,
+ mt7925_get_ap_sta_rec_idx_iter,
+ &lookup);
+
+ if (lookup.sta_rec_idx < 0)
+ return -ENOTCONN;
+
+ if (lookup.sta_rec_idx > U8_MAX)
+ return -ERANGE;
+
+ *sta_rec_idx = lookup.sta_rec_idx;
+
+ return 0;
+}
+
+/* Decode the TX Vector BBP latch and print the last TX rate */
+void
+mt7925_print_last_tx_rate(struct seq_file *s, const __le32 le_txv[3])
+{
+ const char *mode_str = "N/A";
+ u8 rate, txmode, frmode, sgi, ldpc, nsts, stbc, spe, dcm, ersu106t;
+ s8 txpwr, pos_txpwr;
+ u32 txv[3];
+
+ txv[0] = le32_to_cpu(le_txv[0]);
+ txv[1] = le32_to_cpu(le_txv[1]);
+ txv[2] = le32_to_cpu(le_txv[2]);
+
+ if (txv[0] == 0xffffffff) {
+ seq_printf(s, "%-22s %s = %s\n", " ", "Last TX Rate", "N/A");
+ seq_printf(s, "%-22s %s = %s\n",
+ " ", "Chip Out TX Power", "N/A");
+ return;
+ }
+
+ rate = MT7925_TXV_GET_TX_RATE(txv);
+ txmode = MT7925_TXV_GET_TX_MODE(txv);
+ frmode = MT7925_TXV_GET_TX_FRMODE(txv);
+ nsts = MT7925_TXV_GET_TX_NSTS(txv) + 1;
+ sgi = MT7925_TXV_GET_TX_SGI(txv);
+ ldpc = MT7925_TXV_GET_TX_LDPC(txv);
+ stbc = MT7925_TXV_GET_TX_STBC(txv);
+ txpwr = MT7925_TXV_GET_TX_PWR(txv);
+ spe = MT7925_TXV_GET_TX_SPE_IDX(txv);
+ dcm = MT7925_TXV_GET_TX_DCM(txv);
+ ersu106t = MT7925_TXV_GET_TX_106T(txv);
+
+ if (dcm)
+ rate = MT7925_TXV_RATE_UNMASK_DCM(rate);
+ if (ersu106t)
+ rate = MT7925_TXV_RATE_UNMASK_106T(rate);
+
+ if (txmode < ARRAY_SIZE(mt7925_tx_mode_str))
+ mode_str = mt7925_tx_mode_str[txmode];
+
+ seq_printf(s, "%-22s %s = ", " ", "Last TX Rate");
+
+ /* rate/MCS field */
+ if (txmode == MT7925_TX_RATE_MODE_CCK)
+ seq_printf(s, "%s, ",
+ rate < 4 ? mt7925_tx_rate_cck_str[rate] :
+ (((rate >= 5) && (rate <= 7)) ?
+ mt7925_tx_rate_cck_str[rate - 4] :
+ mt7925_tx_rate_cck_str[4]));
+ else if (txmode == MT7925_TX_RATE_MODE_OFDM)
+ seq_printf(s, "%s, ", mt7925_hw_rate_ofdm_str(rate));
+ else if (txmode == MT7925_TX_RATE_MODE_HTMIX ||
+ txmode == MT7925_TX_RATE_MODE_HTGF)
+ seq_printf(s, "MCS%d, ", rate);
+ else
+ seq_printf(s, "%s%d_MCS%d, ",
+ stbc ? "NSTS" : "NSS", nsts, rate);
+
+ /* bandwidth */
+ seq_printf(s, "%s, ",
+ frmode < 5 ? mt7925_tx_rate_bw_str[frmode] :
+ mt7925_tx_rate_bw_str[5]);
+
+ /* GI / preamble */
+ if (txmode == MT7925_TX_RATE_MODE_CCK)
+ seq_printf(s, "%s, ", rate < 4 ? "LP" : "SP");
+ else if (txmode == MT7925_TX_RATE_MODE_OFDM)
+ ; /* OFDM has no GI/preamble to print */
+ else if (txmode == MT7925_TX_RATE_MODE_HTMIX ||
+ txmode == MT7925_TX_RATE_MODE_HTGF ||
+ txmode == MT7925_TX_RATE_MODE_VHT ||
+ txmode == MT7925_TX_RATE_MODE_PLR)
+ seq_printf(s, "%s, ", sgi == 0 ? "LGI" : "SGI");
+ else
+ seq_printf(s, "%s, ",
+ sgi == 0 ? "SGI" : (sgi == 1 ? "MGI" : "LGI"));
+
+ /* mode, DCM, 106t, STBC, coding, SPE */
+ seq_printf(s, "%s%s%s%s%s%s%d\n",
+ mode_str,
+ dcm ? ", DCM" : "", ersu106t ? ", 106t" : "",
+ stbc ? ", STBC, " : ", ", ldpc == 0 ? "BCC" : "LDPC",
+ ", SPE", spe);
+
+ /* chip out TX power (unit: 0.5 dBm) */
+ pos_txpwr = (txpwr < 0) ? (~txpwr + 1) : txpwr;
+ seq_printf(s, "%-22s %s = %c%d.%1d dBm\n",
+ " ", "Chip Out TX Power",
+ (txpwr < 0) ? '-' : '+',
+ (pos_txpwr / 2), 5 * (pos_txpwr % 2));
+}
+
+/* Guard interval used for the auto-rate entries, mirror Gen4m
+ * connac3x_get_sgi_info_from_fw(): pick the per-mode/per-BW flag.
+ */
+u8
+mt7925_wtbl_get_sgi(const struct mt7925_wtbl_rate *wr, u8 txmode, u8 fcap)
+{
+ bool he = (txmode == MT7925_TX_RATE_MODE_HE_SU ||
+ txmode == MT7925_TX_RATE_MODE_HE_ER ||
+ txmode == MT7925_TX_RATE_MODE_HE_TRIG ||
+ txmode == MT7925_TX_RATE_MODE_HE_MU);
+ bool eht = (txmode == MT7925_TX_RATE_MODE_EHT_ER ||
+ txmode == MT7925_TX_RATE_MODE_EHT_TRIG ||
+ txmode == MT7925_TX_RATE_MODE_EHT_MU);
+
+ if (eht)
+ return wr->gi_eht;
+
+ switch (fcap) {
+ case MT7925_BW_20:
+ return he ? wr->g2_he : wr->g2;
+ case MT7925_BW_40:
+ return he ? wr->g4_he : wr->g4;
+ case MT7925_BW_80:
+ return he ? wr->g8_he : wr->g8;
+ case MT7925_BW_160:
+ return he ? wr->g16_he : wr->g16;
+ default:
+ return 0;
+ }
+}
+
+/* Coding used for the auto-rate entries, mirror Gen4m
+ * connac3x_wtbl_get_ldpc_info_from_fw(): 0 = BCC, 1 = LDPC.
+ */
+u8
+mt7925_wtbl_get_ldpc(const struct mt7925_wtbl_rate *wr, u8 txmode)
+{
+ switch (txmode) {
+ case MT7925_TX_RATE_MODE_HTMIX:
+ case MT7925_TX_RATE_MODE_HTGF:
+ return wr->ht_ldpc;
+ case MT7925_TX_RATE_MODE_VHT:
+ return wr->vht_ldpc;
+ case MT7925_TX_RATE_MODE_HE_SU:
+ case MT7925_TX_RATE_MODE_HE_ER:
+ case MT7925_TX_RATE_MODE_HE_TRIG:
+ case MT7925_TX_RATE_MODE_HE_MU:
+ return wr->he_ldpc;
+ case MT7925_TX_RATE_MODE_EHT_ER:
+ case MT7925_TX_RATE_MODE_EHT_TRIG:
+ case MT7925_TX_RATE_MODE_EHT_MU:
+ return wr->eht_ldpc;
+ default:
+ return 0;
+ }
+}
+
+int mt792x_mcu_get_stat(struct mt792x_dev *dev,
+ struct mt7925_sta_stats *stats)
+{
+ u16 len = 0;
+ int ret = 0;
+ struct mt7925_sta_stats_event *event = NULL;
+ struct sk_buff *skb = NULL;
+ struct mt7925_sta_stats_req req = {
+ .tag = cpu_to_le16(UNI_CMD_ID_STATISTICS_STA),
+ .len = cpu_to_le16(sizeof(req) - sizeof(req.rsv)),
+ .lls_read_clear = false,
+ .reset_counter = false,
+ };
+
+ ret = mt7925_get_ap_sta_rec_idx(dev, &req.sta_rec_idx);
+ if (ret)
+ return ret;
+
+ if (!stats)
+ return -EINVAL;
+
+ ret = mt76_mcu_send_and_get_msg(&dev->mt76,
+ MCU_UNI_QUERY(GET_STAT_INFO),
+ &req, sizeof(req), true, &skb);
+
+ if (ret)
+ return ret;
+
+ event = (struct mt7925_sta_stats_event *)skb->data;
+
+ if (le16_to_cpu(event->tag) != UNI_CMD_ID_STATISTICS_STA) {
+ ret = -EPROTO;
+ goto out;
+ }
+
+ len = le16_to_cpu(event->len);
+ if (len < sizeof(*event) - sizeof(event->rsv) ||
+ len > skb->len - sizeof(event->rsv) || !IS_ALIGNED(len, 4)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+
+ if (event->stats.version != MT7925_STA_STATS_VERSION) {
+ ret = -EPROTO;
+ goto out;
+ }
+
+ if (!(le32_to_cpu(event->stats.flags) & MT7925_STA_STATS_VALID)) {
+ ret = -ENOENT;
+ goto out;
+ }
+
+ memcpy(stats, &event->stats, sizeof(*stats));
+
+out:
+ dev_kfree_skb(skb);
+ return ret;
+}
+
+int mt7925_mcu_get_mib_info(struct mt792x_dev *dev, u8 band_idx,
+ const u32 *counters, u8 counter_num,
+ u64 *values)
+{
+ u8 *req;
+ u8 *tlv, *tlv_end;
+ u16 req_len;
+ int i, ret;
+ struct mt7925_mib_hdr *req_hdr;
+ struct mt7925_mib_data *req_tlv;
+ struct sk_buff *skb;
+
+ if (!counters || !values || !counter_num ||
+ counter_num > MT7925_MIB_MAX_CNT)
+ return -EINVAL;
+
+ /* clear output; counters not returned by firmware stay 0 */
+ memset(values, 0, counter_num * sizeof(*values));
+
+ req_len = sizeof(struct mt7925_mib_hdr) +
+ counter_num * sizeof(struct mt7925_mib_data);
+
+ req = kzalloc(req_len, GFP_KERNEL);
+ if (!req)
+ return -ENOMEM;
+
+ req_hdr = (struct mt7925_mib_hdr *)req;
+ req_hdr->band_idx = band_idx;
+
+ req_tlv = (struct mt7925_mib_data *)(req + sizeof(*req_hdr));
+ for (i = 0; i < counter_num; i++) {
+ req_tlv[i].tag = cpu_to_le16(UNI_CMD_MIB_DATA);
+ req_tlv[i].len = cpu_to_le16(sizeof(struct mt7925_mib_data));
+ req_tlv[i].counter = cpu_to_le32(counters[i]);
+ }
+
+ ret = mt76_mcu_send_and_get_msg(&dev->mt76,
+ MCU_UNI_QUERY(GET_MIB_INFO), req,
+ req_len, true, &skb);
+ kfree(req);
+ if (ret)
+ return ret;
+
+ if (skb->len < sizeof(struct mt7925_mib_hdr)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+
+ /* walk the returned TLVs, match each counter ID to the request */
+ tlv = skb->data + sizeof(struct mt7925_mib_hdr);
+ tlv_end = skb->data + skb->len;
+
+ while (tlv + sizeof(struct mt7925_mib_data) <= tlv_end) {
+ struct mt7925_mib_data *evt_tlv =
+ (struct mt7925_mib_data *)tlv;
+ u16 tag = le16_to_cpu(evt_tlv->tag);
+ u16 len = le16_to_cpu(evt_tlv->len);
+ u32 counter;
+
+ if (len < sizeof(struct mt7925_mib_data) ||
+ tlv + len > tlv_end)
+ break;
+
+ if (tag == UNI_CMD_MIB_DATA) {
+ counter = le32_to_cpu(evt_tlv->counter);
+
+ if (counter < MT7925_MIB_CNT_MAX_NUM) {
+ for (i = 0; i < counter_num; i++) {
+ if (counters[i] == counter) {
+ values[i] =
+ le64_to_cpu(evt_tlv->data);
+ break;
+ }
+ }
+ }
+ }
+
+ tlv += len;
+ }
+
+ ret = 0;
+
+out:
+ dev_kfree_skb(skb);
+
+ return ret;
+}
+
+char *mt7925_hw_rate_ofdm_str(u8 ofdm_idx)
+{
+ char * const ofdm_str[] = {
+ "6M", "9M", "12M", "18M", "24M", "36M", "48M", "54M", "N/A"
+ };
+
+ switch (ofdm_idx) {
+ case 11: return ofdm_str[0]; /* 6M */
+ case 15: return ofdm_str[1]; /* 9M */
+ case 10: return ofdm_str[2]; /* 12M */
+ case 14: return ofdm_str[3]; /* 18M */
+ case 9: return ofdm_str[4]; /* 24M */
+ case 13: return ofdm_str[5]; /* 36M */
+ case 8: return ofdm_str[6]; /* 48M */
+ case 12: return ofdm_str[7]; /* 54M */
+ default: return ofdm_str[8];
+ }
+}
+
+/* mt792x_mcu_get_wtbl_rate - fetch the firmware auto-rate table for a WCID
+ * @dev: device
+ * @wlan_idx: HW WTBL / WCID index (see mt7925_sta_stats.wtbl_idx)
+ * @rate: out, filled with the auto-rate table snapshot
+ *
+ * Mirror of Gen4m wlanoidGetStatWtbl(): send UNI_CMD_GET_STAT_INFO with the
+ * WTBL rate TLV (cmd tag 10) and parse the reply (event tag 12) into @rate.
+ *
+ * Return: 0 on success, negative errno otherwise.
+ */
+int mt792x_mcu_get_wtbl_rate(struct mt792x_dev *dev, u16 wlan_idx,
+ struct mt7925_wtbl_rate *rate)
+{
+ int ret;
+ struct mt7925_wtbl_rate_event *event = NULL;
+ struct sk_buff *skb = NULL;
+ struct mt7925_wtbl_rate_req req = {
+ .tag = cpu_to_le16(UNI_CMD_ID_STATISTICS_WTBL),
+ .len = cpu_to_le16(sizeof(req) - sizeof(req.rsv)),
+ .wlan_idx = cpu_to_le16(wlan_idx),
+ };
+
+ if (!rate)
+ return -EINVAL;
+
+ ret = mt76_mcu_send_and_get_msg(&dev->mt76,
+ MCU_UNI_QUERY(GET_STAT_INFO),
+ &req, sizeof(req), true, &skb);
+ if (ret)
+ return ret;
+
+ if (skb->len < sizeof(*event)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+
+ event = (struct mt7925_wtbl_rate_event *)skb->data;
+
+ if (le16_to_cpu(event->rate.tag) != MT7925_UNI_EVENT_STATISTICS_WTBL) {
+ ret = -EPROTO;
+ goto out;
+ }
+
+ memcpy(rate, &event->rate, sizeof(*rate));
+ ret = 0;
+
+out:
+ dev_kfree_skb(skb);
+ return ret;
+}
+
+/**
+ * mt7925_mcu_get_link_quality - query RSSI and TX link speed for a band
+ * @dev: device
+ * @band_idx: band/BSS index (0..3)
+ * @rssi: out, moving-average beacon RSSI in dBm (may be NULL)
+ * @tx_link_speed_kbps: out, TX rate in kbit/s (may be NULL)
+ *
+ * Return: 0 on success, -ENOENT if the band's link quality is not ready.
+ *
+ * Only RSSI and TX rate come from firmware here; for RX rate read wcid->rate.
+ */
+int mt7925_mcu_get_link_quality(struct mt792x_dev *dev, u8 band_idx,
+ s8 *rssi, u32 *tx_link_speed_kbps)
+{
+ int ret;
+ struct mt7925_lq_event *event;
+ struct mt7925_link_quality *lq;
+ struct sk_buff *skb;
+ struct mt7925_lq_req req = {
+ .tag = cpu_to_le16(UNI_CMD_ID_STATISTICS_LINK_QUALITY),
+ .len = cpu_to_le16(sizeof(req) - sizeof(req.rsv)),
+ };
+
+ if (band_idx >= MT7925_LQ_BAND_NUM)
+ return -EINVAL;
+
+ ret = mt76_mcu_send_and_get_msg(&dev->mt76,
+ MCU_UNI_QUERY(GET_STAT_INFO), &req,
+ sizeof(req), true, &skb);
+ if (ret)
+ return ret;
+
+ if (skb->len < sizeof(*event)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+
+ event = (struct mt7925_lq_event *)skb->data;
+
+ if (le16_to_cpu(event->tag) != UNI_CMD_ID_STATISTICS_LINK_QUALITY) {
+ ret = -EPROTO;
+ goto out;
+ }
+
+ lq = &event->lq[band_idx];
+ if (!lq->is_ready) {
+ ret = -ENOENT;
+ goto out;
+ }
+
+ if (rssi)
+ *rssi = lq->rssi;
+
+ /* firmware reports TX rate in 500 Kb/s units -> kbit/s */
+ if (tx_link_speed_kbps)
+ *tx_link_speed_kbps = le16_to_cpu(lq->link_speed) * 500;
+
+ ret = 0;
+
+out:
+ dev_kfree_skb(skb);
+
+ return ret;
+}
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/stats.h b/drivers/net/wireless/mediatek/mt76/mt7925/stats.h
new file mode 100644
index 000000000000..a70b9eb3aa80
--- /dev/null
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/stats.h
@@ -0,0 +1,309 @@
+/* SPDX-License-Identifier: BSD-3-Clause-Clear */
+/* Copyright (C) 2026 MediaTek Inc. */
+
+#ifndef __MT7925_STATS_H
+#define __MT7925_STATS_H
+
+#include <linux/if_ether.h>
+#include <linux/types.h>
+#include "../mt76_connac_mcu.h"
+
+#define MT7925_STA_STATS_AC_NUM 4
+#define MT7925_STA_STATS_BAND_NUM 2
+#define MT7925_STA_STATS_AGG_RANGE_NUM 16
+#define MT7925_STA_STATS_TX_QUALITY_NUM 4
+#define MT7925_TX_MODE_NUM 16
+#define MT7925_TX_RATE_CCK_NUM 5
+#define MT7925_TX_RATE_BW_NUM 6
+
+/* HW rate code (16-bit) field extract, mirror Gen4m CONNAC3X_HW_TX_RATE_TO_*.
+ * The auto-rate table stores one rate code per entry.
+ */
+#define MT7925_HW_RATE_TO_MCS(_x) (((_x) & GENMASK(5, 0)) >> 0)
+#define MT7925_HW_RATE_TO_MODE(_x) (((_x) & GENMASK(9, 6)) >> 6)
+#define MT7925_HW_RATE_TO_NSS(_x) (((_x) & GENMASK(13, 10)) >> 10)
+#define MT7925_HW_RATE_TO_STBC(_x) (((_x) & BIT(14)) >> 14)
+#define MT7925_HW_RATE_TO_DCM(_x) (((_x) & BIT(4)) >> 4)
+#define MT7925_HW_RATE_TO_106T(_x) (((_x) & BIT(5)) >> 5)
+#define MT7925_HW_RATE_UNMASK_DCM(_r) ((u8)(_r) & 0xef)
+#define MT7925_HW_RATE_UNMASK_106T(_r) ((u8)(_r) & 0xdf)
+
+/* Number of entries in the firmware auto-rate table (mirror Gen4m
+ * AUTO_RATE_NUM). RA cycles through these and reports which one is in use
+ * via wtbl->rate_idx.
+ */
+#define MT7925_AUTO_RATE_NUM 8
+
+/* Firmware EVENT_STATISTICS sub-event tags. The WTBL rate request uses cmd tag
+ * UNI_CMD_ID_STATISTICS_WTBL (10) but the firmware replies with event tag 12.
+ */
+#define MT7925_UNI_EVENT_STATISTICS_WTBL 12
+
+/* CONNAC3X TX Vector BBP latch bit fields (mirror cmm_asic_connac3x.h).
+ * These operate on the 3-DWORD tx_vector[band].txv[3] array.
+ */
+#define MT7925_TXV_GET_TX_RATE(_txv) ((_txv)[2] & 0x7f)
+#define MT7925_TXV_GET_TX_LDPC(_txv) (((_txv)[2] & (0x1 << 7)) >> 7)
+#define MT7925_TXV_GET_TX_STBC(_txv) (((_txv)[0] & (0x3 << 6)) >> 6)
+#define MT7925_TXV_GET_TX_FRMODE(_txv) (((_txv)[0] & (0x7 << 8)) >> 8)
+#define MT7925_TXV_GET_TX_MODE(_txv) (((_txv)[0] & (0xf << 12)) >> 12)
+#define MT7925_TXV_GET_TX_NSTS(_txv) (((_txv)[2] & (0xfU << 28)) >> 28)
+#define MT7925_TXV_GET_TX_PWR(_txv) (((_txv)[0] & (0xff << 16)) >> 16)
+#define MT7925_TXV_GET_TX_SGI(_txv) (((_txv)[1] & (0x3 << 26)) >> 26)
+#define MT7925_TXV_GET_TX_SPE_IDX(_txv) (((_txv)[0] & (0x1f << 0)) >> 0)
+#define MT7925_TXV_GET_TX_DCM(_txv) (((_txv)[2] & (0x1 << 4)) >> 4)
+#define MT7925_TXV_GET_TX_106T(_txv) (((_txv)[2] & (0x1 << 5)) >> 5)
+#define MT7925_TXV_RATE_UNMASK_DCM(_r) ((u8)(_r) & 0xef)
+#define MT7925_TXV_RATE_UNMASK_106T(_r) ((u8)(_r) & 0xdf)
+
+extern const char * const mt7925_tx_mode_str[MT7925_TX_MODE_NUM];
+extern const char * const mt7925_tx_rate_cck_str[MT7925_TX_RATE_CCK_NUM];
+extern const char * const mt7925_tx_rate_bw_str[MT7925_TX_RATE_BW_NUM];
+
+/* TX rate mode, mirror Gen4m TX_RATE_MODE_* (see nic_rate.h). Used to decode
+ * both the TX Vector BBP latch and the firmware auto-rate table rate codes.
+ */
+enum mt7925_tx_rate_mode {
+ MT7925_TX_RATE_MODE_CCK = 0,
+ MT7925_TX_RATE_MODE_OFDM = 1,
+ MT7925_TX_RATE_MODE_HTMIX = 2,
+ MT7925_TX_RATE_MODE_HTGF = 3,
+ MT7925_TX_RATE_MODE_VHT = 4,
+ MT7925_TX_RATE_MODE_PLR = 5,
+ MT7925_TX_RATE_MODE_HE_SU = 8,
+ MT7925_TX_RATE_MODE_HE_ER = 9,
+ MT7925_TX_RATE_MODE_HE_TRIG = 10,
+ MT7925_TX_RATE_MODE_HE_MU = 11,
+ MT7925_TX_RATE_MODE_EHT_ER = 13,
+ MT7925_TX_RATE_MODE_EHT_TRIG = 14,
+ MT7925_TX_RATE_MODE_EHT_MU = 15,
+};
+
+enum {
+ UNI_CMD_MIB_DATA,
+};
+
+enum stats_query_mib_array_index {
+ QUERY_MIB_CNT_RX_FCS_ERR = 0,
+ QUERY_MIB_CNT_RX_FIFO_OVERFLOW,
+ QUERY_MIB_CNT_RX_MPDU,
+ QUERY_MIB_CNT_AMPDU_RX_COUNT,
+ QUERY_MIB_CNT_PF_DROP,
+ QUERY_MIB_CNT_LEN_MISMATCH,
+ QUERY_MIB_CNT_CHANNEL_IDLE,
+ QUERY_MIB_CNT_CCA_NAV_TX_TIME,
+ QUERY_MIB_CNT_MDRDY,
+ QUERY_MIB_CNT_RX_CCK_MDRDY_TIME,
+ QUERY_MIB_CNT_RX_OFDM_LG_MIXED_MDRDY_TIME,
+ QUERY_MIB_CNT_RX_OFDM_GREEN_MDRDY_TIME,
+ QUERY_MIB_CNT_P_CCA_TIME,
+ QUERY_MIB_CNT_S_CCA_TIME,
+ QUERY_MIB_CNT_P_ED_TIME,
+ QUERY_MIB_CNT_BCN_TX,
+ QUERY_MIB_CNT_TX_BW_40MHZ,
+ QUERY_MIB_CNT_TX_BW_80MHZ,
+ QUERY_MIB_CNT_TX_BW_160MHZ,
+ QUERY_MIB_CNT_BSS0_BA_MISS,
+ QUERY_MIB_CNT_BSS0_RTS_TX_CNT,
+ QUERY_MIB_CNT_BSS0_FRAME_RETRY,
+ QUERY_MIB_CNT_BSS0_FRAME_RETRY_2,
+ QUERY_MIB_CNT_BSS0_RTS_RETRY,
+ QUERY_MIB_CNT_BSS0_ACK_FAIL,
+ QUERY_MIB_CNT_AMPDU,
+ QUERY_MIB_CNT_AMPDU_MPDU,
+ QUERY_MIB_CNT_AMPDU_ACKED,
+ QUERY_MIB_CNT_MAX_NUM,
+};
+
+enum stats_trx_agg_range {
+ STAT_MIB_CNT_TRX_AGG_RANGE0_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE1_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE2_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE3_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE4_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE5_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE6_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE7_IDX,
+ STAT_MIB_CNT_TRX_AGG_RANGE_MAX_NUM
+};
+
+enum {
+ MT7925_BW_20 = 0,
+ MT7925_BW_40,
+ MT7925_BW_80,
+ MT7925_BW_160,
+};
+
+struct mt7925_sta_stats_ac {
+ __le32 tx_fail;
+ __le32 tx_retry;
+} __packed;
+
+struct mt7925_sta_stats_tx_vector {
+ __le32 txv[3];
+} __packed;
+
+struct mt7925_sta_stats_mib {
+ __le32 rx_mpdu_cnt;
+ __le32 fcs_error;
+ __le32 rx_fifo_full;
+ __le32 ampdu_tx_sf_cnt;
+ __le32 ampdu_tx_ack_sf_cnt;
+ __le32 tx_range_ampdu_cnt[MT7925_STA_STATS_AGG_RANGE_NUM];
+} __packed;
+
+/* Firmware EVENT_STA_STATISTICS body. */
+struct mt7925_sta_stats {
+ u8 version;
+ u8 rsv[3];
+ __le32 flags;
+
+ u8 sta_idx;
+ u8 bss_idx;
+ u8 wtbl_idx;
+ u8 rsv1;
+
+ u8 mac_addr[ETH_ALEN];
+ u8 per;
+ u8 rcpi;
+
+ __le32 phy_mode;
+ __le16 link_speed; /* 0.5 Mbit/s units */
+ u8 link_quality;
+ u8 link_reserved;
+
+ __le32 tx_count;
+ __le32 tx_fail_count;
+ __le32 tx_life_timeout_count;
+ __le32 tx_done_air_time;
+ __le32 transmit_count;
+ __le32 transmit_fail_count;
+
+ struct mt7925_sta_stats_ac ac[MT7925_STA_STATS_AC_NUM];
+
+ u8 temperature;
+ u8 skip_ar;
+ u8 ar_table_idx;
+ u8 rate_entry_idx;
+ u8 rate_entry_idx_prev;
+ u8 tx_sgi_detect_pass_cnt;
+ u8 ave_per;
+ u8 rsv2;
+
+ __le32 agg_range_ctrl[2];
+ u8 range_mode;
+ u8 rsv3[3];
+ __le32 agg_range_ctrl_ext[6];
+
+ u8 ar_state_curr;
+ u8 ar_state_prev;
+ u8 ar_action_type;
+ u8 highest_rate_cnt;
+ u8 lowest_rate_cnt;
+ u8 rsv4;
+ __le16 train_up;
+ __le16 train_down;
+ u8 rsv5[2];
+ __le32 rate1_tx_cnt;
+ __le32 rate1_fail_cnt;
+
+ struct mt7925_sta_stats_tx_vector tx_vector[MT7925_STA_STATS_BAND_NUM];
+ struct mt7925_sta_stats_mib mib[MT7925_STA_STATS_BAND_NUM];
+
+ u8 is_force_tx_stream;
+ u8 is_force_se_off;
+ __le16 ra_running_cnt;
+ u8 ra_status;
+ u8 max_ampdu_factor;
+ u8 tx_quality[MT7925_STA_STATS_TX_QUALITY_NUM];
+ u8 tx_rate_up_penalty;
+ u8 low_traffic_mode;
+ u8 low_traffic_count;
+ u8 low_traffic_dashboard;
+ u8 dynamic_sgi_state;
+ u8 dynamic_sgi_score;
+ u8 dynamic_bw_state;
+ u8 dynamic_gband_256qam_state;
+ u8 vht_non_sp_rate_state;
+ u8 rsv6[3];
+ u8 rsv7[2];
+} __packed;
+
+struct mt7925_sta_rec_lookup {
+ int sta_rec_idx;
+};
+
+/* Firmware auto-rate table snapshot, mirror Gen4m struct UNI_EVENT_STAT_WTBL.
+ *
+ * The firmware RA maintains up to MT7925_AUTO_RATE_NUM candidate rates in
+ * rate_code[]. rate_idx tells which entry is currently in use, so a dump can
+ * point at it with "-->". fcap is the bandwidth capability and the ldpc/sgi
+ * flags describe the per-mode coding/guard-interval used for those rates.
+ */
+struct mt7925_wtbl_rate {
+ __le16 tag;
+ __le16 len;
+ u8 rx_rcpi0;
+ u8 rx_rcpi1;
+ u8 cbrn;
+ u8 fcap;
+ u8 spe_idx;
+ u8 g2; /* SGI enabled, non-HE, BW20 */
+ u8 g4; /* SGI enabled, non-HE, BW40 */
+ u8 g8; /* SGI enabled, non-HE, BW80 */
+ u8 g16; /* SGI enabled, non-HE, BW160 */
+ u8 g2_he; /* GI, HE, BW20 */
+ u8 g4_he; /* GI, HE, BW40 */
+ u8 g8_he; /* GI, HE, BW80 */
+ u8 g16_he; /* GI, HE, BW160 */
+ u8 gi_eht; /* GI, EHT */
+ u8 ht_ldpc;
+ u8 vht_ldpc;
+ u8 he_ldpc;
+ u8 eht_ldpc;
+ u8 af;
+ u8 rate_idx; /* index into rate_code[] currently in use */
+ __le16 rate_code[MT7925_AUTO_RATE_NUM];
+ __le32 rsv[4];
+} __packed;
+
+int
+mt7925_get_ap_sta_rec_idx(struct mt792x_dev *dev, u8 *sta_rec_idx);
+
+void
+mt7925_print_last_tx_rate(struct seq_file *s, const __le32 le_txv[3]);
+
+u8
+mt7925_wtbl_get_sgi(const struct mt7925_wtbl_rate *wr, u8 txmode, u8 fcap);
+
+u8
+mt7925_wtbl_get_ldpc(const struct mt7925_wtbl_rate *wr, u8 txmode);
+
+char *mt7925_hw_rate_ofdm_str(u8 ofdm_idx);
+
+int mt792x_mcu_get_wtbl_rate(struct mt792x_dev *dev, u16 wlan_idx,
+ struct mt7925_wtbl_rate *rate);
+
+int mt792x_mcu_get_stat(struct mt792x_dev *dev,
+ struct mt7925_sta_stats *stats);
+
+int mt7925_mcu_get_mib_info(struct mt792x_dev *dev, u8 band_idx,
+ const u32 *counters, u8 counter_num,
+ u64 *values);
+
+/* GET_STAT_INFO (UNI_CMD_ID_GET_STATISTICS = 0x23),
+ * sub-command UNI_CMD_GET_STATISTICS_TAG_LINK_QUALITY = 1.
+ *
+ * @band_idx: band/BSS index (0..3)
+ * @rssi: out, moving-average beacon RSSI (dBm), may be NULL
+ * @tx_link_speed_kbps: out, TX link speed in kbit/s, may be NULL
+ *
+ * Returns 0 on success, -ENOENT if the band's link quality is not ready.
+ *
+ * NOTE: firmware provides only RSSI and TX rate here. RX rate/bandwidth are
+ * not in this event; read wcid->rate for the RX rate.
+ */
+int mt7925_mcu_get_link_quality(struct mt792x_dev *dev, u8 band_idx,
+ s8 *rssi, u32 *tx_link_speed_kbps);
+
+#endif
--
2.45.2
^ permalink raw reply related [flat|nested] 12+ messages in thread* Re: [PATCH v5 1/2] wifi: mt76: mt7928: Add debugfs for stats to provide debug purpose
2026-11-12 7:55 ` [PATCH v5 1/2] wifi: mt76: mt7928: Add debugfs for stats to provide debug purpose Michael Shih
@ 2026-11-13 0:46 ` kernel test robot
2026-11-13 11:09 ` kernel test robot
1 sibling, 0 replies; 12+ messages in thread
From: kernel test robot @ 2026-11-13 0:46 UTC (permalink / raw)
To: Michael Shih, nbd, lorenzo
Cc: oe-kbuild-all, linux-wireless, linux-mediatek, kun.wu, deren.wu,
sean.wang, quan.zhou, ryder.lee, leon.yen, litien.chang, jb.tsai,
eason.Lai, Michael Shih
Hi Michael,
kernel test robot noticed the following build errors:
[auto build test ERROR on wireless-next/main]
[also build test ERROR on wireless/main linus/master v7.4-rc2 next-20261111]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Michael-Shih/wifi-mt76-mt7928-Add-debugfs-for-stats-to-provide-debug-purpose/20261112-155556
base: https://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless-next.git main
patch link: https://lore.kernel.org/r/20261112075558.1813785-2-michael.shih%40mediatek.com
patch subject: [PATCH v5 1/2] wifi: mt76: mt7928: Add debugfs for stats to provide debug purpose
config: nios2-allmodconfig (https://download.01.org/0day-ci/archive/20261113/202611130804.q5nHCDSw-lkp@intel.com/config)
compiler: nios2-linux-gcc (GCC) 11.5.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20261113/202611130804.q5nHCDSw-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202611130804.q5nHCDSw-lkp@intel.com/
All errors (new ones prefixed by >>, old ones prefixed by <<):
>> ERROR: modpost: drivers/net/wireless/mediatek/mt76/mt7925/mt7925-common.ko: symbol '__udivdi3' undefined!
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH v5 1/2] wifi: mt76: mt7928: Add debugfs for stats to provide debug purpose
2026-11-12 7:55 ` [PATCH v5 1/2] wifi: mt76: mt7928: Add debugfs for stats to provide debug purpose Michael Shih
2026-11-13 0:46 ` kernel test robot
@ 2026-11-13 11:09 ` kernel test robot
1 sibling, 0 replies; 12+ messages in thread
From: kernel test robot @ 2026-11-13 11:09 UTC (permalink / raw)
To: Michael Shih, nbd, lorenzo
Cc: oe-kbuild-all, linux-wireless, linux-mediatek, kun.wu, deren.wu,
sean.wang, quan.zhou, ryder.lee, leon.yen, litien.chang, jb.tsai,
eason.Lai, Michael Shih
Hi Michael,
kernel test robot noticed the following build errors:
[auto build test ERROR on wireless-next/main]
[also build test ERROR on wireless/main linus/master v7.4-rc2]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Michael-Shih/wifi-mt76-mt7928-Add-debugfs-for-stats-to-provide-debug-purpose/20261112-155556
base: https://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless-next.git main
patch link: https://lore.kernel.org/r/20261112075558.1813785-2-michael.shih%40mediatek.com
patch subject: [PATCH v5 1/2] wifi: mt76: mt7928: Add debugfs for stats to provide debug purpose
config: openrisc-allmodconfig (https://download.01.org/0day-ci/archive/20261113/202611131943.bgeayFva-lkp@intel.com/config)
compiler: or1k-linux-gcc (GCC) 16.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20261113/202611131943.bgeayFva-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202611131943.bgeayFva-lkp@intel.com/
All errors (new ones prefixed by >>, old ones prefixed by <<):
WARNING: modpost: drivers/net/ethernet/intel/ice/ice.ko: section mismatch in reference: ice_adv_lnk_speed_maps+0x14 (section: .data) -> ice_adv_lnk_speed_100 (section: .init.rodata)
WARNING: modpost: drivers/net/ethernet/intel/ice/ice.ko: section mismatch in reference: ice_adv_lnk_speed_maps+0x30 (section: .data) -> ice_adv_lnk_speed_1000 (section: .init.rodata)
WARNING: modpost: drivers/net/ethernet/intel/ice/ice.ko: section mismatch in reference: ice_adv_lnk_speed_maps+0x4c (section: .data) -> ice_adv_lnk_speed_2500 (section: .init.rodata)
WARNING: modpost: drivers/net/ethernet/intel/ice/ice.ko: section mismatch in reference: ice_adv_lnk_speed_maps+0x68 (section: .data) -> ice_adv_lnk_speed_5000 (section: .init.rodata)
WARNING: modpost: drivers/net/ethernet/intel/ice/ice.ko: section mismatch in reference: ice_adv_lnk_speed_maps+0x84 (section: .data) -> ice_adv_lnk_speed_10000 (section: .init.rodata)
WARNING: modpost: drivers/net/ethernet/intel/ice/ice.ko: section mismatch in reference: ice_adv_lnk_speed_maps+0xa0 (section: .data) -> ice_adv_lnk_speed_25000 (section: .init.rodata)
WARNING: modpost: drivers/net/ethernet/intel/ice/ice.ko: section mismatch in reference: ice_adv_lnk_speed_maps+0xbc (section: .data) -> ice_adv_lnk_speed_40000 (section: .init.rodata)
WARNING: modpost: drivers/net/ethernet/intel/ice/ice.ko: section mismatch in reference: ice_adv_lnk_speed_maps+0xd8 (section: .data) -> ice_adv_lnk_speed_50000 (section: .init.rodata)
WARNING: modpost: drivers/net/ethernet/intel/ice/ice.ko: section mismatch in reference: ice_adv_lnk_speed_maps+0xf4 (section: .data) -> ice_adv_lnk_speed_100000 (section: .init.rodata)
WARNING: modpost: drivers/net/ethernet/intel/ice/ice.ko: section mismatch in reference: ice_adv_lnk_speed_maps+0x110 (section: .data) -> ice_adv_lnk_speed_200000 (section: .init.rodata)
WARNING: modpost: drivers/net/ethernet/qlogic/qed/qed.ko: section mismatch in reference: qed_mfw_ext_maps+0x14 (section: .data) -> qed_mfw_ext_1g (section: .init.rodata)
WARNING: modpost: drivers/net/ethernet/qlogic/qed/qed.ko: section mismatch in reference: qed_mfw_ext_maps+0x30 (section: .data) -> qed_mfw_ext_10g (section: .init.rodata)
WARNING: modpost: drivers/net/ethernet/qlogic/qed/qed.ko: section mismatch in reference: qed_mfw_ext_maps+0x4c (section: .data) -> qed_mfw_ext_25g (section: .init.rodata)
WARNING: modpost: drivers/net/ethernet/qlogic/qed/qed.ko: section mismatch in reference: qed_mfw_ext_maps+0x68 (section: .data) -> qed_mfw_ext_40g (section: .init.rodata)
WARNING: modpost: drivers/net/ethernet/qlogic/qed/qed.ko: section mismatch in reference: qed_mfw_ext_maps+0x84 (section: .data) -> qed_mfw_ext_50g_base_r (section: .init.rodata)
WARNING: modpost: drivers/net/ethernet/qlogic/qed/qed.ko: section mismatch in reference: qed_mfw_ext_maps+0xa0 (section: .data) -> qed_mfw_ext_50g_base_r2 (section: .init.rodata)
WARNING: modpost: drivers/net/ethernet/qlogic/qed/qed.ko: section mismatch in reference: qed_mfw_ext_maps+0xbc (section: .data) -> qed_mfw_ext_100g_base_r2 (section: .init.rodata)
WARNING: modpost: drivers/net/ethernet/qlogic/qed/qed.ko: section mismatch in reference: qed_mfw_ext_maps+0xd8 (section: .data) -> qed_mfw_ext_100g_base_r4 (section: .init.rodata)
WARNING: modpost: drivers/net/ethernet/qlogic/qed/qed.ko: section mismatch in reference: qed_mfw_legacy_maps+0x14 (section: .data) -> qed_mfw_legacy_1g (section: .init.rodata)
WARNING: modpost: drivers/net/ethernet/qlogic/qed/qed.ko: section mismatch in reference: qed_mfw_legacy_maps+0x30 (section: .data) -> qed_mfw_legacy_10g (section: .init.rodata)
WARNING: modpost: drivers/net/ethernet/qlogic/qed/qed.ko: section mismatch in reference: qed_mfw_legacy_maps+0x4c (section: .data) -> qed_mfw_legacy_20g (section: .init.rodata)
WARNING: modpost: drivers/net/ethernet/qlogic/qed/qed.ko: section mismatch in reference: qed_mfw_legacy_maps+0x68 (section: .data) -> qed_mfw_legacy_25g (section: .init.rodata)
WARNING: modpost: drivers/net/ethernet/qlogic/qed/qed.ko: section mismatch in reference: qed_mfw_legacy_maps+0x84 (section: .data) -> qed_mfw_legacy_40g (section: .init.rodata)
WARNING: modpost: drivers/net/ethernet/qlogic/qed/qed.ko: section mismatch in reference: qed_mfw_legacy_maps+0xa0 (section: .data) -> qed_mfw_legacy_50g (section: .init.rodata)
WARNING: modpost: drivers/net/ethernet/qlogic/qed/qed.ko: section mismatch in reference: qed_mfw_legacy_maps+0xbc (section: .data) -> qed_mfw_legacy_bb_100g (section: .init.rodata)
WARNING: modpost: drivers/net/ethernet/qlogic/qede/qede.ko: section mismatch in reference: qede_forced_speed_maps+0x14 (section: .data) -> qede_forced_speed_1000 (section: .init.rodata)
WARNING: modpost: drivers/net/ethernet/qlogic/qede/qede.ko: section mismatch in reference: qede_forced_speed_maps+0x30 (section: .data) -> qede_forced_speed_10000 (section: .init.rodata)
WARNING: modpost: drivers/net/ethernet/qlogic/qede/qede.ko: section mismatch in reference: qede_forced_speed_maps+0x4c (section: .data) -> qede_forced_speed_20000 (section: .init.rodata)
WARNING: modpost: drivers/net/ethernet/qlogic/qede/qede.ko: section mismatch in reference: qede_forced_speed_maps+0x68 (section: .data) -> qede_forced_speed_25000 (section: .init.rodata)
WARNING: modpost: drivers/net/ethernet/qlogic/qede/qede.ko: section mismatch in reference: qede_forced_speed_maps+0x84 (section: .data) -> qede_forced_speed_40000 (section: .init.rodata)
WARNING: modpost: drivers/net/ethernet/qlogic/qede/qede.ko: section mismatch in reference: qede_forced_speed_maps+0xa0 (section: .data) -> qede_forced_speed_50000 (section: .init.rodata)
WARNING: modpost: drivers/net/ethernet/qlogic/qede/qede.ko: section mismatch in reference: qede_forced_speed_maps+0xbc (section: .data) -> qede_forced_speed_100000 (section: .init.rodata)
>> ERROR: modpost: drivers/net/wireless/mediatek/mt76/mt7925/mt7925-common.ko: symbol '__umoddi3' undefined!
ERROR: modpost: drivers/net/wireless/mediatek/mt76/mt7925/mt7925-common.ko: symbol '__udivdi3' undefined!
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v5 2/2] wifi: mt76: mt7925: support per-channel survey dump during scan
2026-11-12 7:55 ` [PATCH v5 0/2] wifi: mt76: mt7925: per-channel survey dump during scan Michael Shih
2026-11-12 7:55 ` [PATCH v5 1/2] wifi: mt76: mt7928: Add debugfs for stats to provide debug purpose Michael Shih
@ 2026-11-12 7:55 ` Michael Shih
1 sibling, 0 replies; 12+ messages in thread
From: Michael Shih @ 2026-11-12 7:55 UTC (permalink / raw)
To: nbd, lorenzo
Cc: linux-wireless, linux-mediatek, kun.wu, deren.wu, sean.wang,
quan.zhou, ryder.lee, leon.yen, litien.chang, jb.tsai, eason.Lai,
Michael Shih
Report per-channel survey airtime (active/busy/rx/tx) so that
"iw <dev> survey dump" shows non-zero airtime not only for the
connected home channel, but also for every channel visited during
a firmware-offloaded scan.
Two accounting paths are wired up:
(1) Home/operating channel (periodic mac_work):
mt7925 is firmware/chanctx managed and never goes through
__mt76_set_channel(), so phy->chan_state used to stay pinned to
its init default and the periodic survey was charged to the wrong
channel. mt7925_survey_switch_channel() is added and called from
assign_vif_chanctx() to settle the old channel, re-point
chan_state/chandef to the operating channel, and re-base the
firmware MIB snapshot so active/busy/rx/tx keep accumulating on
the channel actually dwelt on.
(2) Scanned channels (scan-done event):
The scan-done CHNLINFO event now carries per-channel airtime
(firmware Ch-Survey extension). mt7925_scan_update_survey() parses
the per-channel array and folds the airtime deltas into each
channel's mt76_channel_state. The firmware 6G channel id is
converted back to the real channel number before lookup.
mt76_channel_state() is exported so the mt7925 module can map a
band/channel pair to its channel state.
Usage:
(1) Connect STA to AP and generate traffic:
dhclient ${WLAN_IF} -v
ping 192.168.0.XXX -i0.1
(2) (Optional) Trigger a scan to collect other channels:
iw ${WLAN_IF} scan
or use wpa_supplicant / wpa_cli scan.
(3) Dump the survey:
iw ${WLAN_IF} survey dump
(4) You will see per-channel airtime, e.g.:
Survey data from wls1
frequency: 5240 MHz
channel active time: 163258 ms
channel busy time: 34076 ms
channel receive time: 32084 ms
channel transmit time: 253 ms
- The connected/dwelt channel keeps increasing over time.
- Channels visited during scan accumulate airtime each scan.
Signed-off-by: Michael Shih <michael.shih@mediatek.com>
---
drivers/net/wireless/mediatek/mt76/mac80211.c | 3 +-
drivers/net/wireless/mediatek/mt76/mt76.h | 2 +
.../net/wireless/mediatek/mt76/mt7925/main.c | 12 +
.../net/wireless/mediatek/mt76/mt7925/mcu.h | 30 ++
.../wireless/mediatek/mt76/mt7925/mt7925.h | 7 +
.../net/wireless/mediatek/mt76/mt7925/pci.c | 2 +-
.../net/wireless/mediatek/mt76/mt7925/stats.c | 301 +++++++++++++++++-
.../net/wireless/mediatek/mt76/mt7925/stats.h | 6 +
.../net/wireless/mediatek/mt76/mt7925/usb.c | 2 +-
drivers/net/wireless/mediatek/mt76/mt792x.h | 6 +
10 files changed, 367 insertions(+), 4 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c
index 08ed3fc6d386..11301428c5f9 100644
--- a/drivers/net/wireless/mediatek/mt76/mac80211.c
+++ b/drivers/net/wireless/mediatek/mt76/mac80211.c
@@ -991,7 +991,7 @@ bool mt76_has_tx_pending(struct mt76_phy *phy)
}
EXPORT_SYMBOL_GPL(mt76_has_tx_pending);
-static struct mt76_channel_state *
+struct mt76_channel_state *
mt76_channel_state(struct mt76_phy *phy, struct ieee80211_channel *c)
{
struct mt76_sband *msband;
@@ -1007,6 +1007,7 @@ mt76_channel_state(struct mt76_phy *phy, struct ieee80211_channel *c)
idx = c - &msband->sband.channels[0];
return &msband->chan[idx];
}
+EXPORT_SYMBOL_GPL(mt76_channel_state);
void mt76_update_survey_active_time(struct mt76_phy *phy, ktime_t time)
{
diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h
index 477d33a0022d..f74c6a4b0f9c 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76.h
@@ -1565,6 +1565,8 @@ void mt76_update_survey(struct mt76_phy *phy);
void mt76_phy_chandef_set(struct mt76_phy *phy,
struct cfg80211_chan_def *chandef);
void mt76_update_survey_active_time(struct mt76_phy *phy, ktime_t time);
+struct mt76_channel_state *
+mt76_channel_state(struct mt76_phy *phy, struct ieee80211_channel *c);
int mt76_get_survey(struct ieee80211_hw *hw, int idx,
struct survey_info *survey);
int mt76_rx_signal(u8 chain_mask, s8 *chain_signal);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
index ffbc6b954632..8e48ef0cb3e5 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
@@ -1612,6 +1612,10 @@ void mt7925_scan_work(struct work_struct *work)
mt7925_regd_change(phy, evt->alpha2);
+ mt7925_scan_update_survey(phy, evt,
+ le16_to_cpu(tlv->len) -
+ sizeof(struct tlv));
+
break;
case UNI_EVENT_SCAN_DONE_NLO:
ieee80211_sched_scan_results(phy->mt76->hw);
@@ -2440,7 +2444,15 @@ static int mt7925_assign_vif_chanctx(struct ieee80211_hw *hw,
mconf->mt76.ctx = ctx;
mctx->bss_conf = mconf;
+
+ /* Re-target op-channel survey accounting to the operating channel just
+ * assigned, so cc_active/airtime accrue on the channel we actually
+ * dwell on (mt7925 never goes through __mt76_set_channel()).
+ */
+ mt7925_survey_switch_channel(mvif->phy, ctx->def.chan);
+
mt76_phy_chandef_set(mvif->phy->mt76, &ctx->def);
+
mutex_unlock(&dev->mt76.mutex);
return 0;
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h
index 11f9eac13ffc..2ed95b00e10f 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.h
@@ -199,6 +199,36 @@ enum DMASHDL_GROUP_IDX {
struct mt7925_mcu_scan_chinfo_event {
u8 nr_chan;
u8 alpha2[3];
+ /* Followed by nr_chan entries. The entry layout matches the firmware
+ * UNI_EVENT_IE_CHNLINFO_T: a base part, optionally followed by the
+ * Ch-Survey airtime extension (present when the firmware was built with
+ * CONFIG_WIFI_RAM_CCN16_CHANNEL_SURVEY_STATS). Presence is detected at
+ * runtime from the TLV length (see mt7925_scan_update_survey()).
+ */
+ u8 chan_info[];
+} __packed;
+
+/* Base per-channel info (firmware SCAN_DONE_VERSION_SUPPORT_CU layout). */
+struct mt7925_mcu_scan_chinfo {
+ u8 channel_num;
+ u8 bcn_probe_cnt;
+ u8 mdrdy_cnt;
+ u8 util_val;
+ __le16 idle_time;
+ __le16 scan_time; /* dwell time (ms) */
+} __packed;
+
+/* Ch-Survey extension appended after the base part
+ * (firmware SCAN_DONE_VERSION_SUPPORT_CH_SURVEY layout).
+ */
+struct mt7925_mcu_scan_chinfo_survey {
+ struct mt7925_mcu_scan_chinfo base;
+ u8 band; /* 1=2G, 2=5G, 3=6G (firmware CMD_BAND_*) */
+ u8 rsv[3];
+ __le32 tx_time_us;
+ __le32 rx_time_us; /* rx + obss */
+ __le32 busy_time_us; /* tx + rx + obss + nonwifi */
+ __le32 active_time_us; /* busy + idle = total */
} __packed;
enum {
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h
index 636efe038392..ac66c0a5daf6 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mt7925.h
@@ -282,6 +282,13 @@ extern const struct ieee80211_ops mt7925_ops;
int __mt7925_start(struct mt792x_phy *phy);
int mt7925_register_device(struct mt792x_dev *dev);
void mt7925_unregister_device(struct mt792x_dev *dev);
+void mt7925_update_channel(struct mt76_phy *mphy);
+void mt7925_survey_switch_channel(struct mt792x_phy *phy,
+ struct ieee80211_channel *chan);
+struct mt7925_mcu_scan_chinfo_event;
+void mt7925_scan_update_survey(struct mt792x_phy *phy,
+ struct mt7925_mcu_scan_chinfo_event *evt,
+ int tlv_len);
int mt7925_run_firmware(struct mt792x_dev *dev);
int mt7925_mcu_set_bss_pm(struct mt792x_dev *dev,
struct ieee80211_bss_conf *link_conf,
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
index 0d477dc1086c..939a46c0428b 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/pci.c
@@ -604,7 +604,7 @@ static int mt7925_pci_probe(struct pci_dev *pdev,
.sta_add = mt7925_mac_sta_add,
.sta_event = mt7925_mac_sta_event,
.sta_remove = mt7925_mac_sta_remove,
- .update_survey = mt792x_update_channel,
+ .update_survey = mt7925_update_channel,
};
static const struct mt792x_hif_ops mt7925_pcie_ops = {
.init_reset = mt7925e_init_reset,
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/stats.c b/drivers/net/wireless/mediatek/mt76/mt7925/stats.c
index 9a47953062bc..5d2c9eb81fcd 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/stats.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/stats.c
@@ -11,7 +11,7 @@
#define MT7925_STA_STATS_VERSION 1
#define MT7925_STA_STATS_VALID 1U
-#define MT7925_MIB_MAX_CNT 32
+#define MT7925_MIB_MAX_CNT 40
#define MT7925_MIB_CNT_MAX_NUM 512 /* firmware UNI_CMD_MIB_CNT_MAX_NUM upper bound */
#define MT7925_LQ_BAND_NUM 4 /* Link quality band */
@@ -519,6 +519,305 @@ int mt792x_mcu_get_wtbl_rate(struct mt792x_dev *dev, u16 wlan_idx,
return ret;
}
+/* mt7925_mcu_get_chan_mib_info - refresh per-channel survey airtime from FW.
+ *
+ * Unlike mt7921 (which reads the MT_MIB_SDR* registers directly), MT7925/MT7935
+ * obtain channel airtime through the firmware GET_MIB_INFO query. The firmware
+ * reports *accumulated* counters (unit: microseconds), so we keep the previous
+ * snapshot in phy->state_ts and accumulate only the delta into chan_state,
+ * mirroring mt7996_mcu_get_chan_mib_info().
+ */
+int mt7925_mcu_get_chan_mib_info(struct mt792x_phy *phy)
+{
+ enum {
+ IDX_TX_TIME,
+ IDX_RX_TIME,
+ IDX_OBSS_TIME,
+ IDX_NON_WIFI_TIME,
+ IDX_CHANNEL_IDLE,
+ IDX_NUM
+ };
+ static const u32 counters[IDX_NUM] = {
+ [IDX_TX_TIME] = UNI_CMD_MIB_CNT_TX_DUR_CNT,
+ [IDX_RX_TIME] = UNI_CMD_MIB_CNT_RX_DUR_CNT,
+ [IDX_OBSS_TIME] = UNI_CMD_RMAC_CNT_OBSS_AIRTIME,
+ [IDX_NON_WIFI_TIME] = UNI_CMD_RMAC_CNT_NONWIFI_AIRTIME,
+ [IDX_CHANNEL_IDLE] = UNI_CMD_MIB_CNT_CHANNEL_IDLE,
+ };
+ struct mt76_channel_state *state = phy->mt76->chan_state;
+ struct mt76_channel_state *state_ts = &phy->state_ts;
+ u64 busy, active;
+ u64 val[IDX_NUM];
+ int ret;
+
+ ret = mt7925_mcu_get_mib_info(phy->dev, phy->mt76->band_idx,
+ counters, IDX_NUM, val);
+ if (ret)
+ return ret;
+
+ /* FW counters are accumulated (us); add only the delta since the last
+ * snapshot. busy = tx + rx + obss + nonwifi; active = busy + idle.
+ *
+ * active is sourced from the FW here (not the host clock) because
+ * mt7925 runtime-suspends between activity: the host-side survey_time
+ * accounting in mt76_update_survey_active_time() only advances while
+ * the host is awake, which badly under-counts active and can make
+ * active < busy. Deriving active from the FW busy/idle counters keeps
+ * both in the same (firmware) clock domain, so busy <= active always
+ * holds and the channel-utilisation ratio stays correct across PM
+ * sleep. The host-clock contribution is neutralised in
+ * mt7925_update_channel() by re-basing phy->survey_time to now.
+ */
+ busy = val[IDX_TX_TIME] + val[IDX_RX_TIME] +
+ val[IDX_OBSS_TIME] + val[IDX_NON_WIFI_TIME];
+ active = busy + val[IDX_CHANNEL_IDLE];
+
+ state->cc_tx += val[IDX_TX_TIME] - state_ts->cc_tx;
+ state->cc_bss_rx += val[IDX_RX_TIME] - state_ts->cc_bss_rx;
+ state->cc_rx += val[IDX_RX_TIME] + val[IDX_OBSS_TIME] -
+ state_ts->cc_rx;
+ state->cc_busy += busy - state_ts->cc_busy;
+ state->cc_active += active - state_ts->cc_active;
+
+ state_ts->cc_tx = val[IDX_TX_TIME];
+ state_ts->cc_bss_rx = val[IDX_RX_TIME];
+ state_ts->cc_rx = val[IDX_RX_TIME] + val[IDX_OBSS_TIME];
+ state_ts->cc_busy = busy;
+ state_ts->cc_active = active;
+
+ return 0;
+}
+
+/* mt7925_update_channel - survey update_survey callback for MT7925/MT7935.
+ *
+ * Replaces mt792x_update_channel() (register-based) for chips that source
+ * channel airtime from the firmware MIB query.
+ */
+void mt7925_update_channel(struct mt76_phy *mphy)
+{
+ struct mt792x_dev *dev = container_of(mphy->dev, struct mt792x_dev,
+ mt76);
+ struct mt792x_phy *phy = mphy->priv;
+
+ if (mt76_connac_pm_wake(mphy, &dev->pm))
+ goto out;
+
+ mt7925_mcu_get_chan_mib_info(phy);
+
+ mt76_connac_power_save_sched(mphy, &dev->pm);
+
+out:
+ /* cc_active is sourced from the FW busy/idle counters above, so
+ * neutralise the host-clock contribution the framework is about to
+ * add in mt76_update_survey_active_time(): re-base survey_time to now
+ * so its delta is ~0. This must happen on every call (including the
+ * pm_wake failure path) or the host clock would otherwise charge the
+ * whole suspend/off-channel gap to cc_active.
+ */
+ mphy->survey_time = ktime_get_boottime();
+}
+EXPORT_SYMBOL_GPL(mt7925_update_channel);
+
+/* mt7925_survey_switch_channel - re-target op-channel survey accounting.
+ *
+ * mt7925 is firmware/chanctx managed and never goes through
+ * __mt76_set_channel(), so phy->chan_state stays pinned to its init default
+ * and the periodic mac_work survey (both cc_active time and the FW-MIB airtime
+ * delta) is charged to the wrong channel. Call this when the operating channel
+ * is (re)assigned so that:
+ * 1) the airtime accrued so far is settled into the old channel;
+ * 2) chan_state/chandef are re-pointed to the new operating channel;
+ * 3) the FW-MIB snapshot (state_ts) is re-based to the current counters so
+ * the first delta on the new channel is measured from now, not
+ * underflowed.
+ */
+void mt7925_survey_switch_channel(struct mt792x_phy *phy,
+ struct ieee80211_channel *chan)
+{
+ enum {
+ IDX_TX_TIME,
+ IDX_RX_TIME,
+ IDX_OBSS_TIME,
+ IDX_NON_WIFI_TIME,
+ IDX_CHANNEL_IDLE,
+ IDX_NUM
+ };
+ static const u32 counters[IDX_NUM] = {
+ [IDX_TX_TIME] = UNI_CMD_MIB_CNT_TX_DUR_CNT,
+ [IDX_RX_TIME] = UNI_CMD_MIB_CNT_RX_DUR_CNT,
+ [IDX_OBSS_TIME] = UNI_CMD_RMAC_CNT_OBSS_AIRTIME,
+ [IDX_NON_WIFI_TIME] = UNI_CMD_RMAC_CNT_NONWIFI_AIRTIME,
+ [IDX_CHANNEL_IDLE] = UNI_CMD_MIB_CNT_CHANNEL_IDLE,
+ };
+ struct mt76_phy *mphy = phy->mt76;
+ struct mt76_channel_state *state_ts = &phy->state_ts;
+ struct mt76_channel_state *new_state;
+ u64 val[IDX_NUM];
+
+ if (!chan)
+ return;
+
+ new_state = mt76_channel_state(mphy, chan);
+ if (new_state == mphy->chan_state)
+ return; /* same channel, nothing to switch */
+
+ /* 1) settle the old channel with airtime accrued up to now */
+ if (mphy->chan_state)
+ mt76_update_survey(mphy);
+
+ /* 2) re-target accounting to the new operating channel */
+ mphy->chan_state = new_state;
+ mphy->chandef.chan = chan;
+
+ /* drop any stale airtime left on this channel from a previous session
+ * so cc_active/cc_busy start fresh for this association (mt7925 never
+ * clears per-channel state via __mt76_set_channel()).
+ */
+ memset(new_state, 0, sizeof(*new_state));
+
+ /* 3) re-base the FW-MIB snapshot so the next delta starts from now.
+ * active = busy + idle (see mt7925_mcu_get_chan_mib_info()).
+ */
+ if (!mt7925_mcu_get_mib_info(phy->dev, mphy->band_idx,
+ counters, IDX_NUM, val)) {
+ u64 busy = val[IDX_TX_TIME] + val[IDX_RX_TIME] +
+ val[IDX_OBSS_TIME] + val[IDX_NON_WIFI_TIME];
+
+ state_ts->cc_tx = val[IDX_TX_TIME];
+ state_ts->cc_bss_rx = val[IDX_RX_TIME];
+ state_ts->cc_rx = val[IDX_RX_TIME] + val[IDX_OBSS_TIME];
+ state_ts->cc_busy = busy;
+ state_ts->cc_active = busy + val[IDX_CHANNEL_IDLE];
+ }
+
+ /* reset the host-clock reference so the framework's
+ * mt76_update_survey_active_time() does not charge the switch gap
+ * to cc_active (which is now FW-sourced).
+ */
+ mphy->survey_time = ktime_get_boottime();
+}
+EXPORT_SYMBOL_GPL(mt7925_survey_switch_channel);
+
+/* Map a firmware scan-done channel entry (band + channel number) back to the
+ * corresponding ieee80211_channel. The firmware band uses CMD_BAND_* encoding
+ * (1=2G, 2=5G, 3=6G).
+ *
+ * For 2G/5G, the reported number is the real channel number (== hw_value).
+ * For 6G, the firmware reports an internal channel id produced by
+ * rlmUtilGetChnlId(): chnl_id = ((real_ch - 1) >> 2) + CHNL_NUM_BASE_6G, where
+ * CHNL_NUM_BASE_6G is 181. We invert it back to the real 6G channel number
+ * (1, 5, 9, ... == hw_value) before looking it up.
+ */
+/* firmware CMD_BAND_* encoding used in scan-done per-channel entries */
+enum mt7925_fw_cmd_band {
+ MT7925_FW_CMD_BAND_2G = 1,
+ MT7925_FW_CMD_BAND_5G = 2,
+ MT7925_FW_CMD_BAND_6G = 3,
+};
+
+/* firmware rlmUtilGetChnlId() 6G base (CHNL_NUM_BASE_6G) */
+#define MT7925_FW_CHNL_NUM_BASE_6G 181
+
+/* invert rlmUtilGetChnlId() 6G encoding: chnl_id -> real 6G channel number */
+#define MT7925_FW_6G_CHNL_ID_TO_NUM(_id) \
+ ((((_id) - MT7925_FW_CHNL_NUM_BASE_6G) << 2) + 1)
+
+static struct ieee80211_channel *
+mt7925_scan_survey_get_chan(struct mt76_phy *mphy, u8 fw_band, u8 chan_num)
+{
+ struct mt76_sband *msband;
+ int i;
+
+ switch (fw_band) {
+ case MT7925_FW_CMD_BAND_2G:
+ msband = &mphy->sband_2g;
+ break;
+ case MT7925_FW_CMD_BAND_5G:
+ msband = &mphy->sband_5g;
+ break;
+ case MT7925_FW_CMD_BAND_6G:
+ msband = &mphy->sband_6g;
+ if (chan_num >= MT7925_FW_CHNL_NUM_BASE_6G)
+ chan_num = MT7925_FW_6G_CHNL_ID_TO_NUM(chan_num);
+ break;
+ default:
+ return NULL;
+ }
+
+ for (i = 0; i < msband->sband.n_channels; i++) {
+ if (msband->sband.channels[i].hw_value == chan_num)
+ return &msband->sband.channels[i];
+ }
+
+ return NULL;
+}
+
+/* mt7925_scan_update_survey - fold per-channel scan airtime into survey state.
+ *
+ * The scan-done CHNLINFO event carries a per-channel array. When the firmware
+ * is built with the Ch-Survey extension, each entry also reports the airtime
+ * (tx/rx/busy/active, unit: us) accumulated while scanning that channel. We
+ * accumulate those deltas into each channel's mt76_channel_state so that
+ * "iw survey dump" shows non-zero airtime for scanned channels, not only the
+ * connected home channel.
+ *
+ * @tlv_len is the total CHNLINFO TLV payload length (after the tlv header),
+ * used to (a) detect whether the airtime extension is present from the entry
+ * stride and (b) bound the array walk safely.
+ */
+void mt7925_scan_update_survey(struct mt792x_phy *phy,
+ struct mt7925_mcu_scan_chinfo_event *evt,
+ int tlv_len)
+{
+ struct mt76_phy *mphy = phy->mt76;
+ struct mt76_dev *dev = mphy->dev;
+ size_t hdr_len = sizeof(*evt);
+ size_t stride, avail;
+ const u8 *p;
+ int i;
+
+ if (tlv_len <= (int)hdr_len || !evt->nr_chan)
+ return;
+
+ avail = (size_t)tlv_len - hdr_len;
+
+ /* The airtime extension is only present when each entry is at least as
+ * large as the extended layout. Older firmware sends only the base
+ * entry and thus reports no airtime; nothing to accumulate then.
+ */
+ stride = avail / evt->nr_chan;
+ if (stride < sizeof(struct mt7925_mcu_scan_chinfo_survey))
+ return;
+
+ p = evt->chan_info;
+
+ spin_lock_bh(&dev->cc_lock);
+ for (i = 0; i < evt->nr_chan; i++, p += stride) {
+ const struct mt7925_mcu_scan_chinfo_survey *ci;
+ struct ieee80211_channel *chan;
+ struct mt76_channel_state *state;
+
+ ci = (const void *)p;
+
+ /* ensure the full extended entry is within the payload */
+ if ((size_t)((i + 1) * stride) > avail)
+ break;
+
+ chan = mt7925_scan_survey_get_chan(mphy, ci->band,
+ ci->base.channel_num);
+ if (!chan)
+ continue;
+
+ state = mt76_channel_state(mphy, chan);
+ state->cc_tx += le32_to_cpu(ci->tx_time_us);
+ state->cc_rx += le32_to_cpu(ci->rx_time_us);
+ state->cc_busy += le32_to_cpu(ci->busy_time_us);
+ state->cc_active += le32_to_cpu(ci->active_time_us);
+ }
+ spin_unlock_bh(&dev->cc_lock);
+}
+EXPORT_SYMBOL_GPL(mt7925_scan_update_survey);
+
/**
* mt7925_mcu_get_link_quality - query RSSI and TX link speed for a band
* @dev: device
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/stats.h b/drivers/net/wireless/mediatek/mt76/mt7925/stats.h
index a70b9eb3aa80..a4cb8c4453ea 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/stats.h
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/stats.h
@@ -291,6 +291,12 @@ int mt7925_mcu_get_mib_info(struct mt792x_dev *dev, u8 band_idx,
const u32 *counters, u8 counter_num,
u64 *values);
+/* Refresh per-channel survey airtime (TX/RX/OBSS/non-wifi) from the firmware
+ * MIB query and accumulate the delta into chan_state. Used by the MT7925/MT7935
+ * update_survey callback.
+ */
+int mt7925_mcu_get_chan_mib_info(struct mt792x_phy *phy);
+
/* GET_STAT_INFO (UNI_CMD_ID_GET_STATISTICS = 0x23),
* sub-command UNI_CMD_GET_STATISTICS_TAG_LINK_QUALITY = 1.
*
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/usb.c b/drivers/net/wireless/mediatek/mt76/mt7925/usb.c
index f97f787b2198..2d3bf035fdfc 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/usb.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/usb.c
@@ -174,7 +174,7 @@ static int mt7925u_probe(struct usb_interface *usb_intf,
.sta_add = mt7925_mac_sta_add,
.sta_event = mt7925_mac_sta_event,
.sta_remove = mt7925_mac_sta_remove,
- .update_survey = mt792x_update_channel,
+ .update_survey = mt7925_update_channel,
};
static const struct mt792x_hif_ops hif_ops = {
.mcu_init = mt7925u_mcu_init,
diff --git a/drivers/net/wireless/mediatek/mt76/mt792x.h b/drivers/net/wireless/mediatek/mt76/mt792x.h
index a7d71e007da0..2c7170c6a99e 100644
--- a/drivers/net/wireless/mediatek/mt76/mt792x.h
+++ b/drivers/net/wireless/mediatek/mt76/mt792x.h
@@ -222,6 +222,12 @@ struct mt792x_phy {
struct mt76_mib_stats mib;
+ /* survey: firmware reports accumulated airtime counters; keep the
+ * previous snapshot so mt7925_mcu_get_chan_mib_info() can accumulate
+ * deltas into chan_state (mirror mt7996 phy->state_ts).
+ */
+ struct mt76_channel_state state_ts;
+
u8 sta_work_count;
u8 clc_chan_conf;
enum mt792x_reg_power_type power_type;
--
2.45.2
^ permalink raw reply related [flat|nested] 12+ messages in thread
end of thread, other threads:[~2026-11-13 11:09 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-24 3:07 [PATCH v1] wifi: mt76: mt7928: Add debugfs for stats to provide debug purpose Eason Lai
2026-08-25 4:00 ` kernel test robot
2026-08-25 7:55 ` kernel test robot
2026-08-27 5:54 ` [PATCH v2] " Eason Lai
2026-08-27 8:44 ` [PATCH v3] " Eason Lai
2026-08-28 7:39 ` kernel test robot
2026-08-31 3:28 ` [PATCH v4] " Eason Lai
2026-11-12 7:55 ` [PATCH v5 0/2] wifi: mt76: mt7925: per-channel survey dump during scan Michael Shih
2026-11-12 7:55 ` [PATCH v5 1/2] wifi: mt76: mt7928: Add debugfs for stats to provide debug purpose Michael Shih
2026-11-13 0:46 ` kernel test robot
2026-11-13 11:09 ` kernel test robot
2026-11-12 7:55 ` [PATCH v5 2/2] wifi: mt76: mt7925: support per-channel survey dump during scan Michael Shih
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.