AI Trajectory


Trajectory: (Expand/Collapse All)
Seq Timestamp Type Name Duration
0/0 2026/08/27 06:27 flow patch-triage
3m Args:
null

Results:
{
  "EnableConfigs": [
    "MT7925U",
    "MT7925E"
  ],
  "FocusSymbols": [
    "mt792x_mcu_get_stat",
    "mt7925_mcu_get_mib_info",
    "mt792x_mcu_get_wtbl_rate",
    "mt7925_mcu_get_link_quality"
  ],
  "KMSANReasoning": "The patch introduces a new debugfs interface to dump statistics for the mt7925 driver. It allocates memory for the statistics structures using `kzalloc` (which zero-initializes the memory), and local variables are explicitly initialized. The structures sent to the firmware are also fully initialized (either explicitly or via compiler zero-initialization for omitted fields). The responses from the firmware are bounds-checked against the received `skb-\u003elen` before being copied. Finally, the data is exposed to user space via `seq_printf`, which formats the values into a string buffer rather than copying raw kernel memory structures directly to user space. There is no risk of uninitialized kernel memory being leaked or used in control flow. Any potential out-of-bounds accesses (e.g., if the `skb-\u003elen` checks were flawed) would be caught by standard KASAN. Therefore, a dedicated KMSAN fuzzing session is not required.",
  "NeedsKMSAN": false,
  "Reasoning": "The patch adds new debugfs interfaces to query statistics from the MT7925 firmware. This involves sending MCU commands and parsing the firmware responses (e.g., TLV parsing in `mt7925_mcu_get_mib_info`). Since the MT7925 driver supports USB (`CONFIG_MT7925U`), the device can be emulated by the fuzzer (e.g., via dummy_hcd). The fuzzer can provide crafted USB responses to these MCU commands, potentially triggering memory corruption or out-of-bounds accesses in the parsing logic. Therefore, these new firmware response parsing functions should be fuzzed.",
  "WorthFuzzing": true
}

1/1 2026/08/27 06:27 action read-patch-diff
0m Args:
null

Results:
{
  "PatchDiff": "commit a2eaffb5242b0d4dd3e76579542e2ec30c42b743\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate:   Thu Aug 27 06:27:37 2026 +0000\n\n    syz-cluster: applied patch under review\n\ndiff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h\nindex 0ededa569e9a5..bf0f105c786e6 100644\n--- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h\n+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h\n@@ -1483,6 +1483,179 @@ enum {\n \tMT_NIC_CAP_EML_CAP = 0x22,\n };\n \n+enum {\n+\tUNI_CMD_ID_STATISTICS_BASIC = 0,\n+\tUNI_CMD_ID_STATISTICS_LINK_QUALITY = 1,\n+\tUNI_CMD_ID_STATISTICS_STA = 2,\n+\tUNI_CMD_ID_STATISTICS_BUG_REPORT = 3,\n+\tUNI_CMD_ID_STATISTICS_TX_STATS = 4,\n+\tUNI_CMD_ID_STATISTICS_ALL_STATS = 5,\n+\tUNI_CMD_ID_STATISTICS_EML_STATS = 6,\n+\tUNI_CMD_ID_STATISTICS_REGULAR_STATS = 7,\n+\tUNI_CMD_ID_STATISTICS_BSS_LINK_QUALITY = 8,\n+\tUNI_CMD_ID_STATISTICS_ML_CHNL_COND = 9,\n+\tUNI_CMD_ID_STATISTICS_WTBL = 10,\n+\tUNI_CMD_ID_STATISTICS_LINK_LAYER_STATS = 128,\n+\tUNI_CMD_ID_STATISTICS_PPDU_LATENCY = 129,\n+\tUNI_CMD_ID_STATISTICS_CURRENT_TX_RATE = 130,\n+\tUNI_CMD_ID_STATISTICS_SET_TX_BITRATE_MONITOR = 131,\n+\tUNI_CMD_ID_STATISTICS_GET_PREDICT_TX_BITRATE = 132,\n+\tUNI_CMD_ID_STATISTICS_SET_ATPS_TYPE_RATE_MODE = 133,\n+\tUNI_CMD_ID_STATISTICS_SET_BEACON_REPORT = 134,\n+\tUNI_CMD_ID_STATISTICS_BSS_CURRENT_TX_RATE = 135,\n+\tUNI_CMD_ID_STATISTICS_GET_BSS_PREDICT_TX_BITRATE = 136,\n+\tUNI_CMD_ID_STATISTICS_MAX_NUM\n+};\n+\n+enum mt7925_mib_counter {\n+\tUNI_CMD_MIB_CNT_RX_FCS_ERR = 0,\n+\tUNI_CMD_MIB_CNT_RX_FIFO_OVERFLOW = 1,\n+\tUNI_CMD_MIB_CNT_RX_MPDU = 2,\n+\tUNI_CMD_MIB_CNT_AMPDU_RX_COUNT = 3,\n+\tUNI_CMD_MIB_CNT_RX_TOTAL_BYTE = 4,\n+\tUNI_CMD_MIB_CNT_RX_VALID_AMPDU_SF = 5,\n+\tUNI_CMD_MIB_CNT_RX_VALID_BYTE = 6,\n+\tUNI_CMD_MIB_CNT_CHANNEL_IDLE = 7,\n+\tUNI_CMD_MIB_CNT_VEC_DROP = 8,\n+\tUNI_CMD_MIB_CNT_DELIMITER_FAIL = 9,\n+\tUNI_CMD_MIB_CNT_VEC_MISMATCH = 10,\n+\tUNI_CMD_MIB_CNT_MDRDY = 11,\n+\tUNI_CMD_MIB_CNT_RX_CCK_MDRDY_TIME = 12,\n+\tUNI_CMD_MIB_CNT_RX_OFDM_LG_MIXED_MDRDY_TIME = 13,\n+\tUNI_CMD_MIB_CNT_RX_OFDM_GREEN_MDRDY_TIME = 14,\n+\tUNI_CMD_MIB_CNT_PF_DROP = 15,\n+\tUNI_CMD_MIB_CNT_LEN_MISMATCH = 16,\n+\tUNI_CMD_MIB_CNT_P_CCA_TIME = 17,\n+\tUNI_CMD_MIB_CNT_S_CCA_TIME = 18,\n+\tUNI_CMD_MIB_CNT_CCA_NAV_TX_TIME = 19,\n+\tUNI_CMD_MIB_CNT_P_ED_TIME = 20,\n+\tUNI_CMD_MIB_CNT_BCN_TX = 21,\n+\tUNI_CMD_MIB_CNT_TX_BW_20MHZ = 22,\n+\tUNI_CMD_MIB_CNT_TX_BW_40MHZ = 23,\n+\tUNI_CMD_MIB_CNT_TX_BW_80MHZ = 24,\n+\tUNI_CMD_MIB_CNT_TX_BW_160MHZ = 25,\n+\tUNI_CMD_RMAC_CNT_OBSS_AIRTIME = 26,\n+\tUNI_CMD_RMAC_CNT_NONWIFI_AIRTIME = 27,\n+\tUNI_CMD_MIB_CNT_TX_DUR_CNT = 28,\n+\tUNI_CMD_MIB_CNT_RX_DUR_CNT = 29,\n+\tUNI_CMD_MIB_CNT_BA_CNT = 30,\n+\tUNI_CMD_MIB_CNT_MAC2PHY_TX_TIME = 31,\n+\tUNI_CMD_MIB_CNT_RX_OUT_OF_RANGE_COUNT = 32,\n+\tUNI_CMD_MIB_CNT_IBF_TX = 33,\n+\tUNI_CMD_MIB_CNT_EBF_TX = 34,\n+\tUNI_CMD_MIB_CNT_MUBF_TX = 35,\n+\tUNI_CMD_MIB_CNT_RX_BF_VHTFBK = 36,\n+\tUNI_CMD_MIB_CNT_RX_BF_HTFBK = 37,\n+\tUNI_CMD_MIB_CNT_RX_BF_HEFBK = 38,\n+\tUNI_CMD_MIB_CNT_BFEE_TXFBK_BFPOLL_TRI = 39,\n+\tUNI_CMD_MIB_CNT_BFEE_TXFBK_TRI = 40,\n+\tUNI_CMD_MIB_CNT_BFEE_RX_FBKCQI = 41,\n+\tUNI_CMD_MIB_CNT_BFEE_RX_NDP = 42,\n+\tUNI_CMD_MIB_CNT_RX_BF_IBF_UPT = 43,\n+\tUNI_CMD_MIB_CNT_RX_BF_EBF_UPT = 44,\n+\tUNI_CMD_MIB_CNT_BFEE_SP_ABORT = 45,\n+\tUNI_CMD_MIB_CNT_BFEE_TB_LEN_ERR = 46,\n+\tUNI_CMD_MIB_CNT_BFEE_TXFBK_MUTE = 47,\n+\tUNI_CMD_MIB_CNT_BFEE_TMAC_ABORT = 48,\n+\tUNI_CMD_MIB_CNT_BFEE_TXFBK_CPL = 49,\n+\tUNI_CMD_MIB_CNT_BFEE_COANT_BLKTX = 50,\n+\tUNI_CMD_MIB_CNT_BFEE_FBK_SEG = 51,\n+\tUNI_CMD_MIB_CNT_NAV_TIME = 52,\n+\tUNI_CMD_MIB_CNT_TX_ABORT_CNT1 = 53,\n+\tUNI_CMD_MIB_CNT_TX_ABORT_CNT0 = 54,\n+\tUNI_CMD_MIB_CNT_TX_ABORT_CNT3 = 55,\n+\tUNI_CMD_MIB_CNT_TX_ABORT_CNT2 = 56,\n+\tUNI_CMD_MIB_CNT_BSS0_RTS_TX_CNT = 57,\n+\tUNI_CMD_MIB_CNT_BSS1_RTS_TX_CNT = 58,\n+\tUNI_CMD_MIB_CNT_BSS2_RTS_TX_CNT = 59,\n+\tUNI_CMD_MIB_CNT_BSS3_RTS_TX_CNT = 60,\n+\tUNI_CMD_MIB_CNT_BSS0_RTS_RETRY = 61,\n+\tUNI_CMD_MIB_CNT_BSS1_RTS_RETRY = 62,\n+\tUNI_CMD_MIB_CNT_BSS2_RTS_RETRY = 63,\n+\tUNI_CMD_MIB_CNT_BSS3_RTS_RETRY = 64,\n+\tUNI_CMD_MIB_CNT_BSS0_BA_MISS = 65,\n+\tUNI_CMD_MIB_CNT_BSS1_BA_MISS = 66,\n+\tUNI_CMD_MIB_CNT_BSS2_BA_MISS = 67,\n+\tUNI_CMD_MIB_CNT_BSS3_BA_MISS = 68,\n+\tUNI_CMD_MIB_CNT_BSS0_ACK_FAIL = 69,\n+\tUNI_CMD_MIB_CNT_BSS1_ACK_FAIL = 70,\n+\tUNI_CMD_MIB_CNT_BSS2_ACK_FAIL = 71,\n+\tUNI_CMD_MIB_CNT_BSS3_ACK_FAIL = 72,\n+\tUNI_CMD_MIB_CNT_BSS0_FRAME_RETRY = 73,\n+\tUNI_CMD_MIB_CNT_BSS1_FRAME_RETRY = 74,\n+\tUNI_CMD_MIB_CNT_BSS2_FRAME_RETRY = 75,\n+\tUNI_CMD_MIB_CNT_BSS3_FRAME_RETRY = 76,\n+\tUNI_CMD_MIB_CNT_BSS0_FRAME_RETRY_2 = 77,\n+\tUNI_CMD_MIB_CNT_BSS1_FRAME_RETRY_2 = 78,\n+\tUNI_CMD_MIB_CNT_BSS2_FRAME_RETRY_2 = 79,\n+\tUNI_CMD_MIB_CNT_BSS3_FRAME_RETRY_2 = 80,\n+\tUNI_CMD_MIB_CNT_RX_A1_SEARCH = 81,\n+\tUNI_CMD_MIB_CNT_RX_DROP_MPDU = 82,\n+\tUNI_CMD_MIB_CNT_RX_UNWANTED = 83,\n+\tUNI_CMD_MIB_CNT_RX_FCS_OK = 84,\n+\tUNI_CMD_MIB_CNT_SU_TX_OK = 85,\n+\tUNI_CMD_MIB_CNT_TX_FULL_BW = 86,\n+\tUNI_CMD_MIB_CNT_TX_AUTO_BW = 87,\n+\tUNI_CMD_MIB_CNT_CCA_SAMPLE_IDLE = 88,\n+\tUNI_CMD_MIB_CNT_CCA_SAMPLE_ACTIVE = 89,\n+\tUNI_CMD_MIB_CNT_CCA_TIME = 90,\n+\tUNI_CMD_MIB_CNT_S_20BW_CCA_TIME = 91,\n+\tUNI_CMD_MIB_CNT_S_40BW_CCA_TIME = 92,\n+\tUNI_CMD_MIB_CNT_S_80BW_CCA_TIME = 93,\n+\tUNI_CMD_MIB_CNT_S_160BW_CCA_TIME = 94,\n+\tUNI_CMD_MIB_CNT_S_P20BW_0_ED_TIME = 95,\n+\tUNI_CMD_MIB_CNT_S_P20BW_1_ED_TIME = 96,\n+\tUNI_CMD_MIB_CNT_S_P20BW_2_ED_TIME = 97,\n+\tUNI_CMD_MIB_CNT_S_P20BW_3_ED_TIME = 98,\n+\tUNI_CMD_MIB_CNT_S_P20BW_4_ED_TIME = 99,\n+\tUNI_CMD_MIB_CNT_S_P20BW_5_ED_TIME = 100,\n+\tUNI_CMD_MIB_CNT_S_P20BW_6_ED_TIME = 101,\n+\tUNI_CMD_MIB_CNT_S_P20BW_7_ED_TIME = 102,\n+\tUNI_CMD_MIB_CNT_S_P20BW_8_ED_TIME = 103,\n+\tUNI_CMD_MIB_CNT_S_P20BW_9_ED_TIME = 104,\n+\tUNI_CMD_MIB_CNT_S_P20BW_10_ED_TIME = 105,\n+\tUNI_CMD_MIB_CNT_S_P20BW_11_ED_TIME = 106,\n+\tUNI_CMD_MIB_CNT_S_P20BW_12_ED_TIME = 107,\n+\tUNI_CMD_MIB_CNT_S_P20BW_13_ED_TIME = 108,\n+\tUNI_CMD_MIB_CNT_S_P20BW_14_ED_TIME = 109,\n+\tUNI_CMD_MIB_CNT_S_P20BW_15_ED_TIME = 110,\n+\tUNI_CMD_MIB_CNT_TX_BW_320MHZ = 111,\n+\tUNI_CMD_MIB_CNT_TX_DDLMT_RNG0 = 112, /* Rng0~Rng4 112~116 */\n+\tUNI_CMD_MIB_CNT_BSS0_FRAME_RETRY_3 = 117, /* 117~120 */\n+\tUNI_CMD_MIB_CNT_BSS0_TX = 121, /* 121~124 */\n+\tUNI_CMD_MIB_CNT_BSS0_TX_DATA = 125, /* 125~128 */\n+\tUNI_CMD_MIB_CNT_BSS0_TX_BYTE = 129, /* 129~132 */\n+\tUNI_CMD_MIB_CNT_RX_OK_BSS0 = 133, /* 133~136 */\n+\tUNI_CMD_MIB_CNT_RX_BYTE_BSS0 = 137, /* 137~140 */\n+\tUNI_CMD_MIB_CNT_RX_DATA_BSS0 = 141, /* 141~144 */\n+\tUNI_CMD_MIB_CNT_MBSS0_TX_OK = 145, /* 145~160 */\n+\tUNI_CMD_MIB_CNT_MBSS0_TX_BYTE = 161, /* 161~176 */\n+\tUNI_CMD_MIB_CNT_RX_OK_MBSS0 = 177, /* 177~192 */\n+\tUNI_CMD_MIB_CNT_RX_BYTE_MBSS0 = 193, /* 193~208 */\n+\tUNI_CMD_MIB_CNT_AMPDU = 209,\n+\tUNI_CMD_MIB_CNT_AMPDU_MPDU = 210,\n+\tUNI_CMD_MIB_CNT_AMPDU_ACKED = 211,\n+\tUNI_CMD_MIB_CNT_MAX_NUM\n+};\n+\n+enum ENUM_UNI_CMD_GET_STATISTICS_TAG {\n+\tUNI_CMD_GET_STATISTICS_TAG_BASIC = 0,\n+\tUNI_CMD_GET_STATISTICS_TAG_LINK_QUALITY = 1,\n+\tUNI_CMD_GET_STATISTICS_TAG_STA = 2,\n+\tUNI_CMD_GET_STATISTICS_TAG_BUG_REPORT = 3,\n+\tUNI_CMD_GET_STATISTICS_TAG_EML_STATS = 6,\n+\tUNI_CMD_GET_STATISTICS_TAG_REGULAR_STATS = 7,\n+\tUNI_CMD_GET_STATISTICS_TAG_BSS_LINK_QUALITY = 8,\n+\tUNI_CMD_GET_STATISTICS_TAG_GET_ML_CHNL_COND = 9,\n+\tUNI_CMD_GET_STATISTICS_TAG_STAT_WTBL = 10,\n+\tUNI_CMD_GET_STATISTICS_TAG_LINK_LAYER_STATS = 0x80,\n+\tUNI_CMD_GET_STATISTICS_TAG_PPDU_LATENCY,\n+\tUNI_CMD_GET_STATISTICS_TAG_CURRENT_TX_RATE,\n+\tUNI_CMD_GET_STATISTICS_TAG_BEACON_REPORT = 0x86,\n+\tUNI_CMD_GET_STATISTICS_TAG_BSS_CURRENT_TX_RATE = 0x87,\n+\tUNI_CMD_GET_STATISTICS_TAG_BSS_PRED_TX_BITRATE = 0x88,\n+};\n+\n #define UNI_WOW_DETECT_TYPE_MAGIC\t\tBIT(0)\n #define UNI_WOW_DETECT_TYPE_ANY\t\t\tBIT(1)\n #define UNI_WOW_DETECT_TYPE_DISCONNECT\t\tBIT(2)\ndiff --git a/drivers/net/wireless/mediatek/mt76/mt7925/Makefile b/drivers/net/wireless/mediatek/mt76/mt7925/Makefile\nindex f9dcc0bba393c..3863a89b89742 100644\n--- a/drivers/net/wireless/mediatek/mt76/mt7925/Makefile\n+++ b/drivers/net/wireless/mediatek/mt76/mt7925/Makefile\n@@ -4,7 +4,7 @@ obj-$(CONFIG_MT7925_COMMON) += mt7925-common.o\n obj-$(CONFIG_MT7925E) += mt7925e.o\n obj-$(CONFIG_MT7925U) += mt7925u.o\n \n-mt7925-common-y := mac.o mcu.o regd.o main.o init.o debugfs.o nan.o\n+mt7925-common-y := mac.o mcu.o regd.o main.o init.o debugfs.o nan.o stats.o\n mt7925-common-$(CONFIG_NL80211_TESTMODE) += testmode.o\n mt7925e-y := pci.o pci_mac.o pci_mcu.o\n mt7925u-y := usb.o\ndiff --git a/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c b/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c\nindex d01ff49de47af..d96a1979a196f 100644\n--- a/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c\n+++ b/drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c\n@@ -3,6 +3,7 @@\n \n #include \"mt7925.h\"\n #include \"mcu.h\"\n+#include \"stats.h\"\n \n static int\n mt7925_reg_set(void *data, u64 val)\n@@ -286,6 +287,345 @@ static int mt7925_chip_reset(void *data, u64 val)\n \n DEFINE_DEBUGFS_ATTRIBUTE(fops_reset, NULL, mt7925_chip_reset, \"%lld\\n\");\n \n+static const u32 stats_query_mib_counters[] = {\n+\tUNI_CMD_MIB_CNT_RX_FCS_ERR,\n+\tUNI_CMD_MIB_CNT_RX_FIFO_OVERFLOW,\n+\tUNI_CMD_MIB_CNT_RX_MPDU,\n+\tUNI_CMD_MIB_CNT_AMPDU_RX_COUNT,\n+\tUNI_CMD_MIB_CNT_PF_DROP,\n+\tUNI_CMD_MIB_CNT_LEN_MISMATCH,\n+\tUNI_CMD_MIB_CNT_CHANNEL_IDLE,\n+\tUNI_CMD_MIB_CNT_CCA_NAV_TX_TIME,\n+\tUNI_CMD_MIB_CNT_MDRDY,\n+\tUNI_CMD_MIB_CNT_RX_CCK_MDRDY_TIME,\n+\tUNI_CMD_MIB_CNT_RX_OFDM_LG_MIXED_MDRDY_TIME,\n+\tUNI_CMD_MIB_CNT_RX_OFDM_GREEN_MDRDY_TIME,\n+\tUNI_CMD_MIB_CNT_P_CCA_TIME,\n+\tUNI_CMD_MIB_CNT_S_CCA_TIME,\n+\tUNI_CMD_MIB_CNT_P_ED_TIME,\n+\tUNI_CMD_MIB_CNT_BCN_TX,\n+\tUNI_CMD_MIB_CNT_TX_BW_40MHZ,\n+\tUNI_CMD_MIB_CNT_TX_BW_80MHZ,\n+\tUNI_CMD_MIB_CNT_TX_BW_160MHZ,\n+\tUNI_CMD_MIB_CNT_BSS0_BA_MISS,\n+\tUNI_CMD_MIB_CNT_BSS0_RTS_TX_CNT,\n+\tUNI_CMD_MIB_CNT_BSS0_FRAME_RETRY,\n+\tUNI_CMD_MIB_CNT_BSS0_FRAME_RETRY_2,\n+\tUNI_CMD_MIB_CNT_BSS0_RTS_RETRY,\n+\tUNI_CMD_MIB_CNT_BSS0_ACK_FAIL,\n+\tUNI_CMD_MIB_CNT_AMPDU,\n+\tUNI_CMD_MIB_CNT_AMPDU_MPDU,\n+\tUNI_CMD_MIB_CNT_AMPDU_ACKED,\n+};\n+\n+/* Dump the whole firmware auto-rate table and mark the entry currently in use\n+ * with \"--\u003e\"\n+ */\n+static void\n+mt7925_dump_auto_rate_table(struct seq_file *s,\n+\t\t\t    const struct mt7925_wtbl_rate *wr)\n+{\n+\tu8 fcap = wr-\u003efcap;\n+\tu8 rate_idx = wr-\u003erate_idx;\n+\tu8 cbrn = wr-\u003ecbrn;\n+\tint i;\n+\n+\tseq_printf(s, \"%-22s  Cur Rate Index = %u\\n\", \" \", rate_idx);\n+\n+\tfor (i = 0; i \u003c MT7925_AUTO_RATE_NUM; i++) {\n+\t\tu16 rate_code = le16_to_cpu(wr-\u003erate_code[i]);\n+\t\tu8 txmode = MT7925_HW_RATE_TO_MODE(rate_code);\n+\t\tu8 rate = MT7925_HW_RATE_TO_MCS(rate_code);\n+\t\tu8 nsts = MT7925_HW_RATE_TO_NSS(rate_code) + 1;\n+\t\tu8 stbc = MT7925_HW_RATE_TO_STBC(rate_code);\n+\t\tu8 dcm = MT7925_HW_RATE_TO_DCM(rate_code);\n+\t\tu8 ersu106t = MT7925_HW_RATE_TO_106T(rate_code);\n+\t\tu8 sgi = mt7925_wtbl_get_sgi(wr, txmode, fcap);\n+\t\tu8 ldpc = mt7925_wtbl_get_ldpc(wr, txmode);\n+\t\tconst char *mode_str = \"N/A\";\n+\n+\t\tif (dcm)\n+\t\t\trate = MT7925_HW_RATE_UNMASK_DCM(rate);\n+\t\tif (ersu106t)\n+\t\t\trate = MT7925_HW_RATE_UNMASK_106T(rate);\n+\n+\t\tif (txmode \u003c ARRAY_SIZE(mt7925_tx_mode_str))\n+\t\t\tmode_str = mt7925_tx_mode_str[txmode];\n+\n+\t\tseq_printf(s, \"Rate index[%d]    %s\", i,\n+\t\t\t   rate_idx == i ? \"--\u003e \" : \"    \");\n+\n+\t\t/* rate/MCS field */\n+\t\tif (txmode == MT7925_TX_RATE_MODE_CCK)\n+\t\t\tseq_printf(s, \"%s, \",\n+\t\t\t\t   mt7925_tx_rate_cck_str[rate \u0026 0x3]);\n+\t\telse if (txmode == MT7925_TX_RATE_MODE_OFDM)\n+\t\t\tseq_printf(s, \"%s, \", mt7925_hw_rate_ofdm_str(rate));\n+\t\telse if (txmode == MT7925_TX_RATE_MODE_HTMIX ||\n+\t\t\t txmode == MT7925_TX_RATE_MODE_HTGF)\n+\t\t\tseq_printf(s, \"MCS%d, \", rate);\n+\t\telse\n+\t\t\tseq_printf(s, \"%s%d_MCS%d, \",\n+\t\t\t\t   stbc ? \"NSTS\" : \"NSS\", nsts, rate);\n+\n+\t\t/* bandwidth: mirror the CCK/OFDM -\u003e BW20 and cbrn handling */\n+\t\tif (txmode == MT7925_TX_RATE_MODE_CCK ||\n+\t\t    txmode == MT7925_TX_RATE_MODE_OFDM)\n+\t\t\tseq_printf(s, \"%s, \", mt7925_tx_rate_bw_str[0]);\n+\t\telse if (i \u003e cbrn)\n+\t\t\tseq_printf(s, \"%s, \",\n+\t\t\t\t   fcap \u003c 5 ?\n+\t\t\t\t   (fcap \u003e MT7925_BW_20 ?\n+\t\t\t\t    mt7925_tx_rate_bw_str[fcap - 1] :\n+\t\t\t\t    mt7925_tx_rate_bw_str[fcap]) :\n+\t\t\t\t   mt7925_tx_rate_bw_str[5]);\n+\t\telse\n+\t\t\tseq_printf(s, \"%s, \",\n+\t\t\t\t   fcap \u003c 5 ? mt7925_tx_rate_bw_str[fcap] :\n+\t\t\t\t   mt7925_tx_rate_bw_str[5]);\n+\n+\t\t/* GI / preamble */\n+\t\tif (txmode == MT7925_TX_RATE_MODE_CCK)\n+\t\t\tseq_printf(s, \"%s, \", rate \u003c 4 ? \"LP\" : \"SP\");\n+\t\telse if (txmode == MT7925_TX_RATE_MODE_OFDM)\n+\t\t\t; /* OFDM has no GI/preamble to print */\n+\t\telse if (txmode == MT7925_TX_RATE_MODE_HTMIX ||\n+\t\t\t txmode == MT7925_TX_RATE_MODE_HTGF ||\n+\t\t\t txmode == MT7925_TX_RATE_MODE_VHT ||\n+\t\t\t txmode == MT7925_TX_RATE_MODE_PLR)\n+\t\t\tseq_printf(s, \"%s, \", sgi == 0 ? \"LGI\" : \"SGI\");\n+\t\telse\n+\t\t\tseq_printf(s, \"%s, \",\n+\t\t\t\t   sgi == 0 ? \"SGI\" :\n+\t\t\t\t   (sgi == 1 ? \"MGI\" : \"LGI\"));\n+\n+\t\t/* mode, DCM, 106t, STBC, coding */\n+\t\tseq_printf(s, \"%s%s%s%s%s\\n\",\n+\t\t\t   mode_str,\n+\t\t\t   dcm ? \", DCM\" : \"\", ersu106t ? \", 106t\" : \"\",\n+\t\t\t   stbc ? \", STBC, \" : \", \",\n+\t\t\t   (ldpc == 0 ||\n+\t\t\t    txmode == MT7925_TX_RATE_MODE_CCK ||\n+\t\t\t    txmode == MT7925_TX_RATE_MODE_OFDM) ?\n+\t\t\t   \"BCC\" : \"LDPC\");\n+\t}\n+}\n+\n+static int mt792x_stats(struct seq_file *s, void *data)\n+{\n+\ts8 rssi = 0;\n+\tu8 band_idx = 0;\n+\tbool wtbl_rate_valid = false;\n+\tint ret = 0;\n+\tint i = 0;\n+\tu32 stats_value = 0;\n+\tu32 tx_link_speed_kbps = 0;\n+\tu64 tx_total = 0;\n+\tu64 tx_fail = 0;\n+\tu64 tx_per = 0;\n+\t/* UNI_CMD_MIB_DATA */\n+\tu64 mib_values[ARRAY_SIZE(stats_query_mib_counters)];\n+\tstruct mt792x_dev *dev = dev_get_drvdata(s-\u003eprivate);\n+\tstruct mt7925_wtbl_rate wtbl_rate;\n+\tstruct mt7925_sta_stats *stats = NULL;\n+\n+\tband_idx = dev-\u003emphy.band_idx;\n+\n+\t stats = kzalloc(sizeof(*stats), GFP_KERNEL);\n+\tif (!stats)\n+\t\treturn -ENOMEM;\n+\n+\t/* Get STA_STATS */\n+\tmt792x_mutex_acquire(dev);\n+\tret = mt792x_mcu_get_stat(dev, stats);\n+\tmt792x_mutex_release(dev);\n+\tif (ret)\n+\t\tgoto out;\n+\n+\t/* Get MIB_STATS */\n+\tmt792x_mutex_acquire(dev);\n+\tret = mt7925_mcu_get_mib_info(dev, band_idx, stats_query_mib_counters,\n+\t\t\t\t      ARRAY_SIZE(stats_query_mib_counters), mib_values);\n+\tmt792x_mutex_release(dev);\n+\tif (ret)\n+\t\tgoto out;\n+\n+\t/* Get Link_Speed and RSSI */\n+\tmt792x_mutex_acquire(dev);\n+\tret = mt7925_mcu_get_link_quality(dev, stats-\u003ebss_idx, \u0026rssi, \u0026tx_link_speed_kbps);\n+\tmt792x_mutex_release(dev);\n+\tif (ret)\n+\t\tgoto out;\n+\n+\t/* Get the firmware auto-rate table (best effort; do not abort the\n+\t * whole stats dump if the WTBL query is not available).\n+\t */\n+\tmt792x_mutex_acquire(dev);\n+\tif (!mt792x_mcu_get_wtbl_rate(dev, stats-\u003ewtbl_idx, \u0026wtbl_rate))\n+\t\twtbl_rate_valid = true;\n+\tmt792x_mutex_release(dev);\n+\n+\t/* Print STA_STATS info */\n+\tseq_printf(s, \"(STA) connected AP MAC Address = %pM\\n\", stats-\u003emac_addr);\n+\tseq_printf(s, \"%-22s  BssIdx = [%u]\\n\", \" \", stats-\u003ebss_idx);\n+\tseq_printf(s, \"%-22s  StaRecIdx = [%u]\\n\",\n+\t\t   \" \", stats-\u003esta_idx);\n+\tseq_printf(s, \"%-22s  RSSI = %d\\n\", \" \", rssi);\n+\tseq_printf(s, \"%-22s  Link_Speed = %u (kbit/s)\\n\", \" \", tx_link_speed_kbps);\n+\n+\tseq_printf(s, \"%-22s  temperature = %u\\n\",\n+\t\t   \" \", stats-\u003etemperature);\n+\n+\tstats_value = le32_to_cpu(stats-\u003etransmit_count) - le32_to_cpu(stats-\u003etransmit_fail_count);\n+\tseq_printf(s, \"%-22s  Tx success = %u\\n\",\n+\t\t   \" \", stats_value);\n+\n+\tstats_value = le32_to_cpu(stats-\u003etx_fail_count) - le32_to_cpu(stats-\u003etx_life_timeout_count);\n+\tseq_printf(s, \"%-22s  Tx fail to Rcv ACK after retry = %u\\n\",\n+\t\t   \" \", stats_value);\n+\n+\tseq_printf(s, \"%-22s  Rx Mpdu = %u\\n\",\n+\t\t   \" \", le32_to_cpu(stats-\u003emib[band_idx].rx_mpdu_cnt));\n+\n+\tseq_printf(s, \"%-22s  Rx Fcs Error = %u (MPDU)\\n\",\n+\t\t   \" \", le32_to_cpu(stats-\u003emib[band_idx].fcs_error));\n+\tseq_printf(s, \"%-22s  Rx FIFO full = %u (MPDU)\\n\",\n+\t\t   \" \", le32_to_cpu(stats-\u003emib[band_idx].rx_fifo_full));\n+\n+\tfor (i = 0; i \u003c STAT_MIB_CNT_TRX_AGG_RANGE_MAX_NUM; i++)\n+\t\tseq_printf(s, \"%-22s  TRX_AGG_RANGE[%d] = %u (PPDU)\\n\",\n+\t\t\t   \" \", i, le32_to_cpu(stats-\u003emib[band_idx].tx_range_ampdu_cnt[i]));\n+\n+\tseq_printf(s, \"%-22s  MPDUs in AMPDUs transmitted = %u (MPDU)\\n\",\n+\t\t   \" \", le32_to_cpu(stats-\u003emib[band_idx].ampdu_tx_sf_cnt));\n+\tseq_printf(s, \"%-22s  MPDUs in AMPDUs transmitted with ACK reply = %u (MPDU)\\n\",\n+\t\t   \" \", le32_to_cpu(stats-\u003emib[band_idx].ampdu_tx_ack_sf_cnt));\n+\tseq_printf(s, \"%-22s  rate1_tx_cnt = %u\\n\",\n+\t\t   \" \", le32_to_cpu(stats-\u003erate1_tx_cnt));\n+\tseq_printf(s, \"%-22s  rate1_fail_cnt = %u\\n\",\n+\t\t   \" \", le32_to_cpu(stats-\u003erate1_fail_cnt));\n+\tseq_printf(s, \"%-22s  train_up = %u\\n\",\n+\t\t   \" \", le16_to_cpu(stats-\u003etrain_up));\n+\tseq_printf(s, \"%-22s  train_down = %u\\n\",\n+\t\t   \" \", le16_to_cpu(stats-\u003etrain_down));\n+\tseq_printf(s, \"%-22s  is_force_tx_stream = %u\\n\",\n+\t\t   \" \", stats-\u003eis_force_tx_stream);\n+\tseq_printf(s, \"%-22s  is_force_se_off = %u\\n\",\n+\t\t   \" \", stats-\u003eis_force_se_off);\n+\tseq_printf(s, \"%-22s  max_ampdu_factor = %u\\n\",\n+\t\t   \" \", stats-\u003emax_ampdu_factor);\n+\tseq_printf(s, \"%-22s  tx_rate_up_penalty = %u\\n\",\n+\t\t   \" \", stats-\u003etx_rate_up_penalty);\n+\tseq_printf(s, \"%-22s  low_traffic_mode = %u\\n\",\n+\t\t   \" \", stats-\u003elow_traffic_mode);\n+\tseq_printf(s, \"%-22s  low_traffic_count = %u\\n\",\n+\t\t   \" \", stats-\u003elow_traffic_count);\n+\tseq_printf(s, \"%-22s  low_traffic_dashboard = %u\\n\",\n+\t\t   \" \", stats-\u003elow_traffic_dashboard);\n+\tseq_printf(s, \"%-22s  dynamic_sgi_state = %u\\n\",\n+\t\t   \" \", stats-\u003edynamic_sgi_state);\n+\tseq_printf(s, \"%-22s  dynamic_sgi_score = %u\\n\",\n+\t\t   \" \", stats-\u003edynamic_sgi_score);\n+\tseq_printf(s, \"%-22s  dynamic_bw_state = %u\\n\",\n+\t\t   \" \", stats-\u003edynamic_bw_state);\n+\tseq_printf(s, \"%-22s  dynamic_gband_256qam_state = %u\\n\",\n+\t\t   \" \", stats-\u003edynamic_gband_256qam_state);\n+\tseq_printf(s, \"%-22s  vht_non_sp_rate_state = %u\\n\",\n+\t\t   \" \", stats-\u003evht_non_sp_rate_state);\n+\n+\t/* Decode the TX Vector BBP latch to show the current TX MCS rate */\n+\tif (band_idx \u003c MT7925_STA_STATS_BAND_NUM)\n+\t\tmt7925_print_last_tx_rate(s, stats-\u003etx_vector[band_idx].txv);\n+\n+\t/* Dump the whole firmware auto-rate table and mark the entry currently */\n+\tif (wtbl_rate_valid)\n+\t\tmt7925_dump_auto_rate_table(s, \u0026wtbl_rate);\n+\n+\tseq_puts(s, \"\\nmib state:\\n\");\n+\t/* ===Rx Related Counters=== */\n+\tseq_puts(s, \"=== Rx Related Counters ===\\n\");\n+\tseq_printf(s, \"%-22s  Rx with CRC = %llu (MPDU)\\n\",\n+\t\t   \" \", mib_values[QUERY_MIB_CNT_RX_FCS_ERR]);\n+\tseq_printf(s, \"%-22s  Rx drop due to out of resource = %llu (MPDU)\\n\",\n+\t\t   \" \", mib_values[QUERY_MIB_CNT_RX_FIFO_OVERFLOW]);\n+\tseq_printf(s, \"%-22s  Rx Mpdu = %llu (MPDU)\\n\",\n+\t\t   \" \", mib_values[QUERY_MIB_CNT_RX_MPDU]);\n+\tseq_printf(s, \"%-22s  Rx AMpdu = %llu (PPDU)\\n\",\n+\t\t   \" \", mib_values[QUERY_MIB_CNT_AMPDU_RX_COUNT]);\n+\tseq_printf(s, \"%-22s  Rx PF Drop = %llu (MPDU)\\n\",\n+\t\t   \" \", mib_values[QUERY_MIB_CNT_PF_DROP]);\n+\tseq_printf(s, \"%-22s  Rx Len Mismatch = %llu (PPDU)\\n\",\n+\t\t   \" \", mib_values[QUERY_MIB_CNT_LEN_MISMATCH]);\n+\n+\t/* ===Phy/Timing Related Counters=== */\n+\tseq_puts(s, \"\\n=== Phy/Timing Related Counters ===\\n\");\n+\tseq_printf(s, \"%-22s  ChannelIdleCnt = %llu\\n\",\n+\t\t   \" \", mib_values[QUERY_MIB_CNT_CHANNEL_IDLE]);\n+\tseq_printf(s, \"%-22s  CCA_NAV_Tx_Time = %llu\\n\",\n+\t\t   \" \", mib_values[QUERY_MIB_CNT_CCA_NAV_TX_TIME]);\n+\tseq_printf(s, \"%-22s  Rx_MDRDY_CNT = %llu (PPDU)\\n\",\n+\t\t   \" \", mib_values[QUERY_MIB_CNT_MDRDY]);\n+\tseq_printf(s, \"%-22s  CCK_MDRDY = %llu\\n\",\n+\t\t   \" \", mib_values[QUERY_MIB_CNT_RX_CCK_MDRDY_TIME]);\n+\tseq_printf(s, \"%-22s  OFDM_MDRDY = %llu\\n\",\n+\t\t   \" \", mib_values[QUERY_MIB_CNT_RX_OFDM_LG_MIXED_MDRDY_TIME]);\n+\tseq_printf(s, \"%-22s  OFDM_GREEN_MDRDY = %llu\\n\",\n+\t\t   \" \", mib_values[QUERY_MIB_CNT_RX_OFDM_GREEN_MDRDY_TIME]);\n+\tseq_printf(s, \"%-22s  Prim CCA Time = %llu\\n\",\n+\t\t   \" \", mib_values[QUERY_MIB_CNT_P_CCA_TIME]);\n+\tseq_printf(s, \"%-22s  Sec CCA Time = %llu\\n\",\n+\t\t   \" \", mib_values[QUERY_MIB_CNT_S_CCA_TIME]);\n+\tseq_printf(s, \"%-22s  Prim ED Time = %llu\\n\",\n+\t\t   \" \", mib_values[QUERY_MIB_CNT_P_ED_TIME]);\n+\n+\t/* ===Tx Related Counters(Generic)=== */\n+\tseq_puts(s, \"\\n=== Tx Related Counters(Generic) ===\\n\");\n+\tseq_printf(s, \"%-22s  BeaconTxCnt = %llu\\n\",\n+\t\t   \" \", mib_values[QUERY_MIB_CNT_BCN_TX]);\n+\tseq_printf(s, \"%-22s  Tx 40MHz Cnt = %llu  (MPDU)\\n\",\n+\t\t   \" \", mib_values[QUERY_MIB_CNT_TX_BW_40MHZ]);\n+\tseq_printf(s, \"%-22s  Tx 80MHz Cnt = %llu  (MPDU)\\n\",\n+\t\t   \" \", mib_values[QUERY_MIB_CNT_TX_BW_80MHZ]);\n+\tseq_printf(s, \"%-22s  Tx 160MHz Cnt = %llu  (MPDU)\\n\",\n+\t\t   \" \", mib_values[QUERY_MIB_CNT_TX_BW_160MHZ]);\n+\n+\t/* ===BSSID[0] Related Counters=== */\n+\tseq_puts(s, \"\\n=== BSSID[0] Related Counters ===\\n\");\n+\tseq_printf(s, \"%-22s  BA Miss Cnt = %llu  (PPDU)\\n\",\n+\t\t   \" \", mib_values[QUERY_MIB_CNT_BSS0_BA_MISS]);\n+\tseq_printf(s, \"%-22s  RTS Tx Cnt = %llu  (MPDU)\\n\",\n+\t\t   \" \", mib_values[QUERY_MIB_CNT_BSS0_RTS_TX_CNT]);\n+\tseq_printf(s, \"%-22s  Frame Retry Cnt = %llu (MPDU)\\n\",\n+\t\t   \" \", mib_values[QUERY_MIB_CNT_BSS0_FRAME_RETRY]);\n+\tseq_printf(s, \"%-22s  Frame Retry 2 Cnt = %llu (MPDU)\\n\",\n+\t\t   \" \", mib_values[QUERY_MIB_CNT_BSS0_FRAME_RETRY_2]);\n+\tseq_printf(s, \"%-22s  RTS Retry Cnt = %llu (MPDU)\\n\",\n+\t\t   \" \", mib_values[QUERY_MIB_CNT_BSS0_RTS_RETRY]);\n+\tseq_printf(s, \"%-22s  Ack Failed Cnt = %llu (PPDU)\\n\",\n+\t\t   \" \", mib_values[QUERY_MIB_CNT_BSS0_ACK_FAIL]);\n+\n+\t/* ===AMPDU Related Counters=== */\n+\tseq_puts(s, \"\\n=== AMPDU Related Counters ===\\n\");\n+\tseq_printf(s, \"%-22s  Tx AMPDU_Pkt_Cnt = %llu  (PPDU)\\n\",\n+\t\t   \" \", mib_values[QUERY_MIB_CNT_AMPDU]);\n+\tseq_printf(s, \"%-22s  Tx AMPDU_MPDU_Pkt_Cnt = %llu  (MPDU)\\n\",\n+\t\t   \" \", mib_values[QUERY_MIB_CNT_AMPDU_MPDU]);\n+\tseq_printf(s, \"%-22s  AMPDU Tx success = %llu (MPDU)\\n\",\n+\t\t   \" \", mib_values[QUERY_MIB_CNT_AMPDU_ACKED]);\n+\n+\ttx_total = mib_values[QUERY_MIB_CNT_AMPDU_MPDU];\n+\ttx_fail =  mib_values[QUERY_MIB_CNT_AMPDU_MPDU] - mib_values[QUERY_MIB_CNT_AMPDU_ACKED];\n+\ttx_per = tx_total == 0 ? 0 : (1000 * (tx_fail)) / tx_total;\n+\tseq_printf(s, \"%-22s  AMPDU Tx fail count   = %llu  (MPDU), PER=%llu.%1llu%%\\n\",\n+\t\t   \" \",\n+\t\t   tx_fail,\n+\t\t   tx_per / 10, tx_per % 10);\n+\n+out:\n+\tkfree(stats);\n+\treturn ret;\n+}\n+\n int mt7925_init_debugfs(struct mt792x_dev *dev)\n {\n \tstruct dentry *dir;\n@@ -313,5 +653,8 @@ int mt7925_init_debugfs(struct mt792x_dev *dev)\n \t\t\t\t    mt792x_pm_stats);\n \tdebugfs_create_file(\"deep-sleep\", 0600, dir, dev, \u0026fops_ds);\n \n+\tdebugfs_create_devm_seqfile(dev-\u003emt76.dev, \"stats\", dir,\n+\t\t\t\t    mt792x_stats);\n+\n \treturn 0;\n }\ndiff --git a/drivers/net/wireless/mediatek/mt76/mt7925/stats.c b/drivers/net/wireless/mediatek/mt76/mt7925/stats.c\nnew file mode 100644\nindex 0000000000000..9a47953062bc3\n--- /dev/null\n+++ b/drivers/net/wireless/mediatek/mt76/mt7925/stats.c\n@@ -0,0 +1,585 @@\n+// SPDX-License-Identifier: BSD-3-Clause-Clear\n+/* Copyright (C) 2026 MediaTek Inc. */\n+\n+#include \u003clinux/firmware.h\u003e\n+#include \u003clinux/fs.h\u003e\n+#include \"mt7925.h\"\n+#include \"mcu.h\"\n+#include \"mac.h\"\n+#include \"stats.h\"\n+\n+#define MT7925_STA_STATS_VERSION 1\n+#define MT7925_STA_STATS_VALID 1U\n+\n+#define MT7925_MIB_MAX_CNT\t\t\t32\n+#define MT7925_MIB_CNT_MAX_NUM\t\t512  /* firmware UNI_CMD_MIB_CNT_MAX_NUM upper bound */\n+\n+#define MT7925_LQ_BAND_NUM\t4\t\t\t/* Link quality band */\n+\n+const char * const mt7925_tx_mode_str[] = {\n+\t\"CCK\", \"OFDM\", \"MM\", \"GF\", \"VHT\", \"PLR\",\n+\t\"PLRP\", \"ALR\", \"HE_SU\", \"HE_ER\", \"HE_TRIG\", \"HE_MU\",\n+\t\"EHT_ER\", \"TOF\", \"EHT_TRIG\", \"EHT_MU\"\n+};\n+\n+const char * const mt7925_tx_rate_cck_str[] = {\n+\t\"1M\", \"2M\", \"5.5M\", \"11M\", \"N/A\"\n+};\n+\n+const char * const mt7925_tx_rate_bw_str[] = {\n+\t\"BW20\", \"BW40\", \"BW80\", \"BW160/BW8080\", \"BW320\", \"N/A\"\n+};\n+\n+struct mt7925_sta_stats_req {\n+\tu8 rsv[4];\n+\t__le16 tag;\n+\t__le16 len;\n+\tu8 sta_rec_idx;\n+\tu8 rsv1;\n+\tu8 lls_read_clear;\n+\tu8 reset_counter;\n+} __packed;\n+\n+struct mt7925_sta_stats_event {\n+\tu8 rsv[4];\n+\t__le16 tag;\n+\t__le16 len;\n+\tstruct mt7925_sta_stats stats;\n+} __packed;\n+\n+/* WTBL auto-rate request TLV, mirror Gen4m struct UNI_CMD_STAT_WTBL.\n+ * The RA tag reuses UNI_CMD_ID_STATISTICS_WTBL (10).\n+ */\n+struct mt7925_wtbl_rate_req {\n+\tu8 rsv[4];\n+\t__le16 tag;\n+\t__le16 len;\n+\t__le16 wlan_idx;\n+\t__le16 rsv1;\n+} __packed;\n+\n+/* WTBL auto-rate event TLV. Firmware answers cmd tag 10 with event tag 12. */\n+struct mt7925_wtbl_rate_event {\n+\tu8 rsv[4];\n+\tstruct mt7925_wtbl_rate rate;\n+} __packed;\n+\n+/* fixed field, mirror UNI_(CMD|EVENT)_MIB_INFO */\n+struct mt7925_mib_hdr {\n+\tu8 band_idx;\n+\tu8 rsv[3];\n+} __packed;\n+\n+/* One requested/returned MIB counter, mirror UNI_(CMD|EVENT)_MIB_DATA_T. */\n+struct mt7925_mib_data {\n+\t__le16 tag;\t/* UNI_(CMD|EVENT)_MIB_DATA = 0 */\n+\t__le16 len;\n+\t__le32 counter;\t/* MIB counter ID (enum mt7925_mib_counter) */\n+\t__le64 data;\t/* only valid on event; ignored on cmd */\n+} __packed;\n+\n+/* per-band link quality entry, mirror UNI_LINK_QUALITY */\n+struct mt7925_link_quality {\n+\ts8 rssi;\t\t/* moving-average beacon RSSI of connected AP */\n+\ts8 link_quality;\t/* always 0 */\n+\t__le16 link_speed;\t/* TX rate1, unit 500 Kb/s */\n+\tu8 medium_busy_pct;\t/* always 0 */\n+\tu8 is_ready;\t\t/* 0: fields invalid, 1: valid */\n+\tu8 rsv[2];\n+} __packed;\n+\n+/* request TLV, mirror UNI_CMD_LINK_QUALITY (header + tag/len only) */\n+struct mt7925_lq_req {\n+\tu8 rsv[4];\t\t/* UNI_CMD_GET_STATISTICS fixed field */\n+\t__le16 tag;\t\t/* UNI_CMD_GET_STATISTICS_TAG_LINK_QUALITY = 1 */\n+\t__le16 len;\n+} __packed;\n+\n+/* event TLV, mirror UNI_EVENT_LINK_QUALITY */\n+struct mt7925_lq_event {\n+\tu8 rsv[4];\t\t/* UNI_EVENT_STATISTICS fixed field */\n+\t__le16 tag;\t\t/* UNI_EVENT_ID_STATISTICS_LINK_QUALITY = 1 */\n+\t__le16 len;\n+\tstruct mt7925_link_quality lq[MT7925_LQ_BAND_NUM];\n+} __packed;\n+\n+static void\n+mt7925_get_ap_sta_rec_idx_iter(void *data, u8 *mac,\n+\t\t\t       struct ieee80211_vif *vif)\n+{\n+\tstruct mt7925_sta_rec_lookup *lookup = data;\n+\tstruct mt792x_vif *mvif;\n+\tstruct mt792x_sta *msta;\n+\n+\tif (lookup-\u003esta_rec_idx \u003e= 0)\n+\t\treturn;\n+\n+\tif (vif-\u003etype != NL80211_IFTYPE_STATION || !vif-\u003ecfg.assoc)\n+\t\treturn;\n+\n+\tmvif = (struct mt792x_vif *)vif-\u003edrv_priv;\n+\tmsta = mvif-\u003ewep_sta;\n+\tif (!msta)\n+\t\treturn;\n+\n+\tif (!msta-\u003edeflink.wcid.sta)\n+\t\treturn;\n+\n+\tlookup-\u003esta_rec_idx = msta-\u003edeflink.wcid.idx;\n+}\n+\n+int\n+mt7925_get_ap_sta_rec_idx(struct mt792x_dev *dev, u8 *sta_rec_idx)\n+{\n+\tstruct mt7925_sta_rec_lookup lookup = {\n+\t\t.sta_rec_idx = -1,\n+\t};\n+\n+\tlockdep_assert_held(\u0026dev-\u003emt76.mutex);\n+\n+\tieee80211_iterate_active_interfaces(dev-\u003emphy.hw,\n+\t\t\t\t\t    IEEE80211_IFACE_ITER_RESUME_ALL,\n+\t\t\t\t\t    mt7925_get_ap_sta_rec_idx_iter,\n+\t\t\t\t\t    \u0026lookup);\n+\n+\tif (lookup.sta_rec_idx \u003c 0)\n+\t\treturn -ENOTCONN;\n+\n+\tif (lookup.sta_rec_idx \u003e U8_MAX)\n+\t\treturn -ERANGE;\n+\n+\t*sta_rec_idx = lookup.sta_rec_idx;\n+\n+\treturn 0;\n+}\n+\n+/* Decode the TX Vector BBP latch and print the last TX rate */\n+void\n+mt7925_print_last_tx_rate(struct seq_file *s, const __le32 le_txv[3])\n+{\n+\tconst char *mode_str = \"N/A\";\n+\tu8 rate, txmode, frmode, sgi, ldpc, nsts, stbc, spe, dcm, ersu106t;\n+\ts8 txpwr, pos_txpwr;\n+\tu32 txv[3];\n+\n+\ttxv[0] = le32_to_cpu(le_txv[0]);\n+\ttxv[1] = le32_to_cpu(le_txv[1]);\n+\ttxv[2] = le32_to_cpu(le_txv[2]);\n+\n+\tif (txv[0] == 0xffffffff) {\n+\t\tseq_printf(s, \"%-22s  %s = %s\\n\", \" \", \"Last TX Rate\", \"N/A\");\n+\t\tseq_printf(s, \"%-22s  %s = %s\\n\",\n+\t\t\t   \" \", \"Chip Out TX Power\", \"N/A\");\n+\t\treturn;\n+\t}\n+\n+\trate = MT7925_TXV_GET_TX_RATE(txv);\n+\ttxmode = MT7925_TXV_GET_TX_MODE(txv);\n+\tfrmode = MT7925_TXV_GET_TX_FRMODE(txv);\n+\tnsts = MT7925_TXV_GET_TX_NSTS(txv) + 1;\n+\tsgi = MT7925_TXV_GET_TX_SGI(txv);\n+\tldpc = MT7925_TXV_GET_TX_LDPC(txv);\n+\tstbc = MT7925_TXV_GET_TX_STBC(txv);\n+\ttxpwr = MT7925_TXV_GET_TX_PWR(txv);\n+\tspe = MT7925_TXV_GET_TX_SPE_IDX(txv);\n+\tdcm = MT7925_TXV_GET_TX_DCM(txv);\n+\tersu106t = MT7925_TXV_GET_TX_106T(txv);\n+\n+\tif (dcm)\n+\t\trate = MT7925_TXV_RATE_UNMASK_DCM(rate);\n+\tif (ersu106t)\n+\t\trate = MT7925_TXV_RATE_UNMASK_106T(rate);\n+\n+\tif (txmode \u003c ARRAY_SIZE(mt7925_tx_mode_str))\n+\t\tmode_str = mt7925_tx_mode_str[txmode];\n+\n+\tseq_printf(s, \"%-22s  %s = \", \" \", \"Last TX Rate\");\n+\n+\t/* rate/MCS field */\n+\tif (txmode == MT7925_TX_RATE_MODE_CCK)\n+\t\tseq_printf(s, \"%s, \",\n+\t\t\t   rate \u003c 4 ? mt7925_tx_rate_cck_str[rate] :\n+\t\t\t   (((rate \u003e= 5) \u0026\u0026 (rate \u003c= 7)) ?\n+\t\t\t    mt7925_tx_rate_cck_str[rate - 4] :\n+\t\t\t    mt7925_tx_rate_cck_str[4]));\n+\telse if (txmode == MT7925_TX_RATE_MODE_OFDM)\n+\t\tseq_printf(s, \"%s, \", mt7925_hw_rate_ofdm_str(rate));\n+\telse if (txmode == MT7925_TX_RATE_MODE_HTMIX ||\n+\t\t txmode == MT7925_TX_RATE_MODE_HTGF)\n+\t\tseq_printf(s, \"MCS%d, \", rate);\n+\telse\n+\t\tseq_printf(s, \"%s%d_MCS%d, \",\n+\t\t\t   stbc ? \"NSTS\" : \"NSS\", nsts, rate);\n+\n+\t/* bandwidth */\n+\tseq_printf(s, \"%s, \",\n+\t\t   frmode \u003c 5 ? mt7925_tx_rate_bw_str[frmode] :\n+\t\t   mt7925_tx_rate_bw_str[5]);\n+\n+\t/* GI / preamble */\n+\tif (txmode == MT7925_TX_RATE_MODE_CCK)\n+\t\tseq_printf(s, \"%s, \", rate \u003c 4 ? \"LP\" : \"SP\");\n+\telse if (txmode == MT7925_TX_RATE_MODE_OFDM)\n+\t\t; /* OFDM has no GI/preamble to print */\n+\telse if (txmode == MT7925_TX_RATE_MODE_HTMIX ||\n+\t\t txmode == MT7925_TX_RATE_MODE_HTGF ||\n+\t\t txmode == MT7925_TX_RATE_MODE_VHT ||\n+\t\t txmode == MT7925_TX_RATE_MODE_PLR)\n+\t\tseq_printf(s, \"%s, \", sgi == 0 ? \"LGI\" : \"SGI\");\n+\telse\n+\t\tseq_printf(s, \"%s, \",\n+\t\t\t   sgi == 0 ? \"SGI\" : (sgi == 1 ? \"MGI\" : \"LGI\"));\n+\n+\t/* mode, DCM, 106t, STBC, coding, SPE */\n+\tseq_printf(s, \"%s%s%s%s%s%s%d\\n\",\n+\t\t   mode_str,\n+\t\t   dcm ? \", DCM\" : \"\", ersu106t ? \", 106t\" : \"\",\n+\t\t   stbc ? \", STBC, \" : \", \", ldpc == 0 ? \"BCC\" : \"LDPC\",\n+\t\t   \", SPE\", spe);\n+\n+\t/* chip out TX power (unit: 0.5 dBm) */\n+\tpos_txpwr = (txpwr \u003c 0) ? (~txpwr + 1) : txpwr;\n+\tseq_printf(s, \"%-22s  %s = %c%d.%1d dBm\\n\",\n+\t\t   \" \", \"Chip Out TX Power\",\n+\t\t   (txpwr \u003c 0) ? '-' : '+',\n+\t\t   (pos_txpwr / 2), 5 * (pos_txpwr % 2));\n+}\n+\n+/* Guard interval used for the auto-rate entries, mirror Gen4m\n+ * connac3x_get_sgi_info_from_fw(): pick the per-mode/per-BW flag.\n+ */\n+u8\n+mt7925_wtbl_get_sgi(const struct mt7925_wtbl_rate *wr, u8 txmode, u8 fcap)\n+{\n+\tbool he = (txmode == MT7925_TX_RATE_MODE_HE_SU ||\n+\t\t   txmode == MT7925_TX_RATE_MODE_HE_ER ||\n+\t\t   txmode == MT7925_TX_RATE_MODE_HE_TRIG ||\n+\t\t   txmode == MT7925_TX_RATE_MODE_HE_MU);\n+\tbool eht = (txmode == MT7925_TX_RATE_MODE_EHT_ER ||\n+\t\t    txmode == MT7925_TX_RATE_MODE_EHT_TRIG ||\n+\t\t    txmode == MT7925_TX_RATE_MODE_EHT_MU);\n+\n+\tif (eht)\n+\t\treturn wr-\u003egi_eht;\n+\n+\tswitch (fcap) {\n+\tcase MT7925_BW_20:\n+\t\treturn he ? wr-\u003eg2_he : wr-\u003eg2;\n+\tcase MT7925_BW_40:\n+\t\treturn he ? wr-\u003eg4_he : wr-\u003eg4;\n+\tcase MT7925_BW_80:\n+\t\treturn he ? wr-\u003eg8_he : wr-\u003eg8;\n+\tcase MT7925_BW_160:\n+\t\treturn he ? wr-\u003eg16_he : wr-\u003eg16;\n+\tdefault:\n+\t\treturn 0;\n+\t}\n+}\n+\n+/* Coding used for the auto-rate entries, mirror Gen4m\n+ * connac3x_wtbl_get_ldpc_info_from_fw(): 0 = BCC, 1 = LDPC.\n+ */\n+u8\n+mt7925_wtbl_get_ldpc(const struct mt7925_wtbl_rate *wr, u8 txmode)\n+{\n+\tswitch (txmode) {\n+\tcase MT7925_TX_RATE_MODE_HTMIX:\n+\tcase MT7925_TX_RATE_MODE_HTGF:\n+\t\treturn wr-\u003eht_ldpc;\n+\tcase MT7925_TX_RATE_MODE_VHT:\n+\t\treturn wr-\u003evht_ldpc;\n+\tcase MT7925_TX_RATE_MODE_HE_SU:\n+\tcase MT7925_TX_RATE_MODE_HE_ER:\n+\tcase MT7925_TX_RATE_MODE_HE_TRIG:\n+\tcase MT7925_TX_RATE_MODE_HE_MU:\n+\t\treturn wr-\u003ehe_ldpc;\n+\tcase MT7925_TX_RATE_MODE_EHT_ER:\n+\tcase MT7925_TX_RATE_MODE_EHT_TRIG:\n+\tcase MT7925_TX_RATE_MODE_EHT_MU:\n+\t\treturn wr-\u003eeht_ldpc;\n+\tdefault:\n+\t\treturn 0;\n+\t}\n+}\n+\n+int mt792x_mcu_get_stat(struct mt792x_dev *dev,\n+\t\t\tstruct mt7925_sta_stats *stats)\n+{\n+\tu16 len = 0;\n+\tint ret = 0;\n+\tstruct mt7925_sta_stats_event *event = NULL;\n+\tstruct sk_buff *skb = NULL;\n+\tstruct mt7925_sta_stats_req req = {\n+\t\t.tag = cpu_to_le16(UNI_CMD_ID_STATISTICS_STA),\n+\t\t.len = cpu_to_le16(sizeof(req) - sizeof(req.rsv)),\n+\t\t.lls_read_clear = false,\n+\t\t.reset_counter = false,\n+\t};\n+\n+\tret = mt7925_get_ap_sta_rec_idx(dev, \u0026req.sta_rec_idx);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tif (!stats)\n+\t\treturn -EINVAL;\n+\n+\tret = mt76_mcu_send_and_get_msg(\u0026dev-\u003emt76,\n+\t\t\t\t\tMCU_UNI_QUERY(GET_STAT_INFO),\n+\t\t\t\t\t\u0026req, sizeof(req), true, \u0026skb);\n+\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tevent = (struct mt7925_sta_stats_event *)skb-\u003edata;\n+\n+\tif (le16_to_cpu(event-\u003etag) != UNI_CMD_ID_STATISTICS_STA) {\n+\t\tret = -EPROTO;\n+\t\tgoto out;\n+\t}\n+\n+\tlen = le16_to_cpu(event-\u003elen);\n+\tif (len \u003c sizeof(*event) - sizeof(event-\u003ersv) ||\n+\t    len \u003e skb-\u003elen - sizeof(event-\u003ersv) || !IS_ALIGNED(len, 4)) {\n+\t\tret = -EMSGSIZE;\n+\t\tgoto out;\n+\t}\n+\n+\tif (event-\u003estats.version != MT7925_STA_STATS_VERSION) {\n+\t\tret = -EPROTO;\n+\t\tgoto out;\n+\t}\n+\n+\tif (!(le32_to_cpu(event-\u003estats.flags) \u0026 MT7925_STA_STATS_VALID)) {\n+\t\tret = -ENOENT;\n+\t\tgoto out;\n+\t}\n+\n+\tmemcpy(stats, \u0026event-\u003estats, sizeof(*stats));\n+\n+out:\n+\tdev_kfree_skb(skb);\n+\treturn ret;\n+}\n+\n+int mt7925_mcu_get_mib_info(struct mt792x_dev *dev, u8 band_idx,\n+\t\t\t    const u32 *counters, u8 counter_num,\n+\t\t\t    u64 *values)\n+{\n+\tu8 *req;\n+\tu8 *tlv, *tlv_end;\n+\tu16 req_len;\n+\tint i, ret;\n+\tstruct mt7925_mib_hdr *req_hdr;\n+\tstruct mt7925_mib_data *req_tlv;\n+\tstruct sk_buff *skb;\n+\n+\tif (!counters || !values || !counter_num ||\n+\t    counter_num \u003e MT7925_MIB_MAX_CNT)\n+\t\treturn -EINVAL;\n+\n+\t/* clear output; counters not returned by firmware stay 0 */\n+\tmemset(values, 0, counter_num * sizeof(*values));\n+\n+\treq_len = sizeof(struct mt7925_mib_hdr) +\n+\t\t  counter_num * sizeof(struct mt7925_mib_data);\n+\n+\treq = kzalloc(req_len, GFP_KERNEL);\n+\tif (!req)\n+\t\treturn -ENOMEM;\n+\n+\treq_hdr = (struct mt7925_mib_hdr *)req;\n+\treq_hdr-\u003eband_idx = band_idx;\n+\n+\treq_tlv = (struct mt7925_mib_data *)(req + sizeof(*req_hdr));\n+\tfor (i = 0; i \u003c counter_num; i++) {\n+\t\treq_tlv[i].tag = cpu_to_le16(UNI_CMD_MIB_DATA);\n+\t\treq_tlv[i].len = cpu_to_le16(sizeof(struct mt7925_mib_data));\n+\t\treq_tlv[i].counter = cpu_to_le32(counters[i]);\n+\t}\n+\n+\tret = mt76_mcu_send_and_get_msg(\u0026dev-\u003emt76,\n+\t\t\t\t\tMCU_UNI_QUERY(GET_MIB_INFO), req,\n+\t\t\t\t\treq_len, true, \u0026skb);\n+\tkfree(req);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tif (skb-\u003elen \u003c sizeof(struct mt7925_mib_hdr)) {\n+\t\tret = -EMSGSIZE;\n+\t\tgoto out;\n+\t}\n+\n+\t/* walk the returned TLVs, match each counter ID to the request */\n+\ttlv = skb-\u003edata + sizeof(struct mt7925_mib_hdr);\n+\ttlv_end = skb-\u003edata + skb-\u003elen;\n+\n+\twhile (tlv + sizeof(struct mt7925_mib_data) \u003c= tlv_end) {\n+\t\tstruct mt7925_mib_data *evt_tlv =\n+\t\t\t(struct mt7925_mib_data *)tlv;\n+\t\tu16 tag = le16_to_cpu(evt_tlv-\u003etag);\n+\t\tu16 len = le16_to_cpu(evt_tlv-\u003elen);\n+\t\tu32 counter;\n+\n+\t\tif (len \u003c sizeof(struct mt7925_mib_data) ||\n+\t\t    tlv + len \u003e tlv_end)\n+\t\t\tbreak;\n+\n+\t\tif (tag == UNI_CMD_MIB_DATA) {\n+\t\t\tcounter = le32_to_cpu(evt_tlv-\u003ecounter);\n+\n+\t\t\tif (counter \u003c MT7925_MIB_CNT_MAX_NUM) {\n+\t\t\t\tfor (i = 0; i \u003c counter_num; i++) {\n+\t\t\t\t\tif (counters[i] == counter) {\n+\t\t\t\t\t\tvalues[i] =\n+\t\t\t\t\t\t  le64_to_cpu(evt_tlv-\u003edata);\n+\t\t\t\t\t\tbreak;\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\n+\t\ttlv += len;\n+\t}\n+\n+\tret = 0;\n+\n+out:\n+\tdev_kfree_skb(skb);\n+\n+\treturn ret;\n+}\n+\n+char *mt7925_hw_rate_ofdm_str(u8 ofdm_idx)\n+{\n+\tchar * const ofdm_str[] = {\n+\t\t\"6M\", \"9M\", \"12M\", \"18M\", \"24M\", \"36M\", \"48M\", \"54M\", \"N/A\"\n+\t};\n+\n+\tswitch (ofdm_idx) {\n+\tcase 11: return ofdm_str[0];\t/* 6M */\n+\tcase 15: return ofdm_str[1];\t/* 9M */\n+\tcase 10: return ofdm_str[2];\t/* 12M */\n+\tcase 14: return ofdm_str[3];\t/* 18M */\n+\tcase 9:  return ofdm_str[4];\t/* 24M */\n+\tcase 13: return ofdm_str[5];\t/* 36M */\n+\tcase 8:  return ofdm_str[6];\t/* 48M */\n+\tcase 12: return ofdm_str[7];\t/* 54M */\n+\tdefault: return ofdm_str[8];\n+\t}\n+}\n+\n+/* mt792x_mcu_get_wtbl_rate - fetch the firmware auto-rate table for a WCID\n+ * @dev:\tdevice\n+ * @wlan_idx:\tHW WTBL / WCID index (see mt7925_sta_stats.wtbl_idx)\n+ * @rate:\tout, filled with the auto-rate table snapshot\n+ *\n+ * Mirror of Gen4m wlanoidGetStatWtbl(): send UNI_CMD_GET_STAT_INFO with the\n+ * WTBL rate TLV (cmd tag 10) and parse the reply (event tag 12) into @rate.\n+ *\n+ * Return: 0 on success, negative errno otherwise.\n+ */\n+int mt792x_mcu_get_wtbl_rate(struct mt792x_dev *dev, u16 wlan_idx,\n+\t\t\t     struct mt7925_wtbl_rate *rate)\n+{\n+\tint ret;\n+\tstruct mt7925_wtbl_rate_event *event = NULL;\n+\tstruct sk_buff *skb = NULL;\n+\tstruct mt7925_wtbl_rate_req req = {\n+\t\t.tag = cpu_to_le16(UNI_CMD_ID_STATISTICS_WTBL),\n+\t\t.len = cpu_to_le16(sizeof(req) - sizeof(req.rsv)),\n+\t\t.wlan_idx = cpu_to_le16(wlan_idx),\n+\t};\n+\n+\tif (!rate)\n+\t\treturn -EINVAL;\n+\n+\tret = mt76_mcu_send_and_get_msg(\u0026dev-\u003emt76,\n+\t\t\t\t\tMCU_UNI_QUERY(GET_STAT_INFO),\n+\t\t\t\t\t\u0026req, sizeof(req), true, \u0026skb);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tif (skb-\u003elen \u003c sizeof(*event)) {\n+\t\tret = -EMSGSIZE;\n+\t\tgoto out;\n+\t}\n+\n+\tevent = (struct mt7925_wtbl_rate_event *)skb-\u003edata;\n+\n+\tif (le16_to_cpu(event-\u003erate.tag) != MT7925_UNI_EVENT_STATISTICS_WTBL) {\n+\t\tret = -EPROTO;\n+\t\tgoto out;\n+\t}\n+\n+\tmemcpy(rate, \u0026event-\u003erate, sizeof(*rate));\n+\tret = 0;\n+\n+out:\n+\tdev_kfree_skb(skb);\n+\treturn ret;\n+}\n+\n+/**\n+ * mt7925_mcu_get_link_quality - query RSSI and TX link speed for a band\n+ * @dev:\tdevice\n+ * @band_idx:\tband/BSS index (0..3)\n+ * @rssi:\tout, moving-average beacon RSSI in dBm (may be NULL)\n+ * @tx_link_speed_kbps: out, TX rate in kbit/s (may be NULL)\n+ *\n+ * Return: 0 on success, -ENOENT if the band's link quality is not ready.\n+ *\n+ * Only RSSI and TX rate come from firmware here; for RX rate read wcid-\u003erate.\n+ */\n+int mt7925_mcu_get_link_quality(struct mt792x_dev *dev, u8 band_idx,\n+\t\t\t\ts8 *rssi, u32 *tx_link_speed_kbps)\n+{\n+\tint ret;\n+\tstruct mt7925_lq_event *event;\n+\tstruct mt7925_link_quality *lq;\n+\tstruct sk_buff *skb;\n+\tstruct mt7925_lq_req req = {\n+\t\t.tag = cpu_to_le16(UNI_CMD_ID_STATISTICS_LINK_QUALITY),\n+\t\t.len = cpu_to_le16(sizeof(req) - sizeof(req.rsv)),\n+\t};\n+\n+\tif (band_idx \u003e= MT7925_LQ_BAND_NUM)\n+\t\treturn -EINVAL;\n+\n+\tret = mt76_mcu_send_and_get_msg(\u0026dev-\u003emt76,\n+\t\t\t\t\tMCU_UNI_QUERY(GET_STAT_INFO), \u0026req,\n+\t\t\t\t\tsizeof(req), true, \u0026skb);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tif (skb-\u003elen \u003c sizeof(*event)) {\n+\t\tret = -EMSGSIZE;\n+\t\tgoto out;\n+\t}\n+\n+\tevent = (struct mt7925_lq_event *)skb-\u003edata;\n+\n+\tif (le16_to_cpu(event-\u003etag) != UNI_CMD_ID_STATISTICS_LINK_QUALITY) {\n+\t\tret = -EPROTO;\n+\t\tgoto out;\n+\t}\n+\n+\tlq = \u0026event-\u003elq[band_idx];\n+\tif (!lq-\u003eis_ready) {\n+\t\tret = -ENOENT;\n+\t\tgoto out;\n+\t}\n+\n+\tif (rssi)\n+\t\t*rssi = lq-\u003erssi;\n+\n+\t/* firmware reports TX rate in 500 Kb/s units -\u003e kbit/s */\n+\tif (tx_link_speed_kbps)\n+\t\t*tx_link_speed_kbps = le16_to_cpu(lq-\u003elink_speed) * 500;\n+\n+\tret = 0;\n+\n+out:\n+\tdev_kfree_skb(skb);\n+\n+\treturn ret;\n+}\ndiff --git a/drivers/net/wireless/mediatek/mt76/mt7925/stats.h b/drivers/net/wireless/mediatek/mt76/mt7925/stats.h\nnew file mode 100644\nindex 0000000000000..a70b9eb3aa806\n--- /dev/null\n+++ b/drivers/net/wireless/mediatek/mt76/mt7925/stats.h\n@@ -0,0 +1,309 @@\n+/* SPDX-License-Identifier: BSD-3-Clause-Clear */\n+/* Copyright (C) 2026 MediaTek Inc. */\n+\n+#ifndef __MT7925_STATS_H\n+#define __MT7925_STATS_H\n+\n+#include \u003clinux/if_ether.h\u003e\n+#include \u003clinux/types.h\u003e\n+#include \"../mt76_connac_mcu.h\"\n+\n+#define MT7925_STA_STATS_AC_NUM 4\n+#define MT7925_STA_STATS_BAND_NUM 2\n+#define MT7925_STA_STATS_AGG_RANGE_NUM 16\n+#define MT7925_STA_STATS_TX_QUALITY_NUM 4\n+#define MT7925_TX_MODE_NUM      16\n+#define MT7925_TX_RATE_CCK_NUM  5\n+#define MT7925_TX_RATE_BW_NUM   6\n+\n+/* HW rate code (16-bit) field extract, mirror Gen4m CONNAC3X_HW_TX_RATE_TO_*.\n+ * The auto-rate table stores one rate code per entry.\n+ */\n+#define MT7925_HW_RATE_TO_MCS(_x)\t(((_x) \u0026 GENMASK(5, 0)) \u003e\u003e 0)\n+#define MT7925_HW_RATE_TO_MODE(_x)\t(((_x) \u0026 GENMASK(9, 6)) \u003e\u003e 6)\n+#define MT7925_HW_RATE_TO_NSS(_x)\t(((_x) \u0026 GENMASK(13, 10)) \u003e\u003e 10)\n+#define MT7925_HW_RATE_TO_STBC(_x)\t(((_x) \u0026 BIT(14)) \u003e\u003e 14)\n+#define MT7925_HW_RATE_TO_DCM(_x)\t(((_x) \u0026 BIT(4)) \u003e\u003e 4)\n+#define MT7925_HW_RATE_TO_106T(_x)\t(((_x) \u0026 BIT(5)) \u003e\u003e 5)\n+#define MT7925_HW_RATE_UNMASK_DCM(_r)\t((u8)(_r) \u0026 0xef)\n+#define MT7925_HW_RATE_UNMASK_106T(_r)\t((u8)(_r) \u0026 0xdf)\n+\n+/* Number of entries in the firmware auto-rate table (mirror Gen4m\n+ * AUTO_RATE_NUM). RA cycles through these and reports which one is in use\n+ * via wtbl-\u003erate_idx.\n+ */\n+#define MT7925_AUTO_RATE_NUM 8\n+\n+/* Firmware EVENT_STATISTICS sub-event tags. The WTBL rate request uses cmd tag\n+ * UNI_CMD_ID_STATISTICS_WTBL (10) but the firmware replies with event tag 12.\n+ */\n+#define MT7925_UNI_EVENT_STATISTICS_WTBL 12\n+\n+/* CONNAC3X TX Vector BBP latch bit fields (mirror cmm_asic_connac3x.h).\n+ * These operate on the 3-DWORD tx_vector[band].txv[3] array.\n+ */\n+#define MT7925_TXV_GET_TX_RATE(_txv)\t((_txv)[2] \u0026 0x7f)\n+#define MT7925_TXV_GET_TX_LDPC(_txv)\t(((_txv)[2] \u0026 (0x1 \u003c\u003c 7)) \u003e\u003e 7)\n+#define MT7925_TXV_GET_TX_STBC(_txv)\t(((_txv)[0] \u0026 (0x3 \u003c\u003c 6)) \u003e\u003e 6)\n+#define MT7925_TXV_GET_TX_FRMODE(_txv)\t(((_txv)[0] \u0026 (0x7 \u003c\u003c 8)) \u003e\u003e 8)\n+#define MT7925_TXV_GET_TX_MODE(_txv)\t(((_txv)[0] \u0026 (0xf \u003c\u003c 12)) \u003e\u003e 12)\n+#define MT7925_TXV_GET_TX_NSTS(_txv)\t(((_txv)[2] \u0026 (0xfU \u003c\u003c 28)) \u003e\u003e 28)\n+#define MT7925_TXV_GET_TX_PWR(_txv)\t(((_txv)[0] \u0026 (0xff \u003c\u003c 16)) \u003e\u003e 16)\n+#define MT7925_TXV_GET_TX_SGI(_txv)\t(((_txv)[1] \u0026 (0x3 \u003c\u003c 26)) \u003e\u003e 26)\n+#define MT7925_TXV_GET_TX_SPE_IDX(_txv)\t(((_txv)[0] \u0026 (0x1f \u003c\u003c 0)) \u003e\u003e 0)\n+#define MT7925_TXV_GET_TX_DCM(_txv)\t(((_txv)[2] \u0026 (0x1 \u003c\u003c 4)) \u003e\u003e 4)\n+#define MT7925_TXV_GET_TX_106T(_txv)\t(((_txv)[2] \u0026 (0x1 \u003c\u003c 5)) \u003e\u003e 5)\n+#define MT7925_TXV_RATE_UNMASK_DCM(_r)\t((u8)(_r) \u0026 0xef)\n+#define MT7925_TXV_RATE_UNMASK_106T(_r)\t((u8)(_r) \u0026 0xdf)\n+\n+extern const char * const mt7925_tx_mode_str[MT7925_TX_MODE_NUM];\n+extern const char * const mt7925_tx_rate_cck_str[MT7925_TX_RATE_CCK_NUM];\n+extern const char * const mt7925_tx_rate_bw_str[MT7925_TX_RATE_BW_NUM];\n+\n+/* TX rate mode, mirror Gen4m TX_RATE_MODE_* (see nic_rate.h). Used to decode\n+ * both the TX Vector BBP latch and the firmware auto-rate table rate codes.\n+ */\n+enum mt7925_tx_rate_mode {\n+\tMT7925_TX_RATE_MODE_CCK = 0,\n+\tMT7925_TX_RATE_MODE_OFDM = 1,\n+\tMT7925_TX_RATE_MODE_HTMIX = 2,\n+\tMT7925_TX_RATE_MODE_HTGF = 3,\n+\tMT7925_TX_RATE_MODE_VHT = 4,\n+\tMT7925_TX_RATE_MODE_PLR = 5,\n+\tMT7925_TX_RATE_MODE_HE_SU = 8,\n+\tMT7925_TX_RATE_MODE_HE_ER = 9,\n+\tMT7925_TX_RATE_MODE_HE_TRIG = 10,\n+\tMT7925_TX_RATE_MODE_HE_MU = 11,\n+\tMT7925_TX_RATE_MODE_EHT_ER = 13,\n+\tMT7925_TX_RATE_MODE_EHT_TRIG = 14,\n+\tMT7925_TX_RATE_MODE_EHT_MU = 15,\n+};\n+\n+enum {\n+\tUNI_CMD_MIB_DATA,\n+};\n+\n+enum stats_query_mib_array_index {\n+\tQUERY_MIB_CNT_RX_FCS_ERR = 0,\n+\tQUERY_MIB_CNT_RX_FIFO_OVERFLOW,\n+\tQUERY_MIB_CNT_RX_MPDU,\n+\tQUERY_MIB_CNT_AMPDU_RX_COUNT,\n+\tQUERY_MIB_CNT_PF_DROP,\n+\tQUERY_MIB_CNT_LEN_MISMATCH,\n+\tQUERY_MIB_CNT_CHANNEL_IDLE,\n+\tQUERY_MIB_CNT_CCA_NAV_TX_TIME,\n+\tQUERY_MIB_CNT_MDRDY,\n+\tQUERY_MIB_CNT_RX_CCK_MDRDY_TIME,\n+\tQUERY_MIB_CNT_RX_OFDM_LG_MIXED_MDRDY_TIME,\n+\tQUERY_MIB_CNT_RX_OFDM_GREEN_MDRDY_TIME,\n+\tQUERY_MIB_CNT_P_CCA_TIME,\n+\tQUERY_MIB_CNT_S_CCA_TIME,\n+\tQUERY_MIB_CNT_P_ED_TIME,\n+\tQUERY_MIB_CNT_BCN_TX,\n+\tQUERY_MIB_CNT_TX_BW_40MHZ,\n+\tQUERY_MIB_CNT_TX_BW_80MHZ,\n+\tQUERY_MIB_CNT_TX_BW_160MHZ,\n+\tQUERY_MIB_CNT_BSS0_BA_MISS,\n+\tQUERY_MIB_CNT_BSS0_RTS_TX_CNT,\n+\tQUERY_MIB_CNT_BSS0_FRAME_RETRY,\n+\tQUERY_MIB_CNT_BSS0_FRAME_RETRY_2,\n+\tQUERY_MIB_CNT_BSS0_RTS_RETRY,\n+\tQUERY_MIB_CNT_BSS0_ACK_FAIL,\n+\tQUERY_MIB_CNT_AMPDU,\n+\tQUERY_MIB_CNT_AMPDU_MPDU,\n+\tQUERY_MIB_CNT_AMPDU_ACKED,\n+\tQUERY_MIB_CNT_MAX_NUM,\n+};\n+\n+enum stats_trx_agg_range {\n+\tSTAT_MIB_CNT_TRX_AGG_RANGE0_IDX,\n+\tSTAT_MIB_CNT_TRX_AGG_RANGE1_IDX,\n+\tSTAT_MIB_CNT_TRX_AGG_RANGE2_IDX,\n+\tSTAT_MIB_CNT_TRX_AGG_RANGE3_IDX,\n+\tSTAT_MIB_CNT_TRX_AGG_RANGE4_IDX,\n+\tSTAT_MIB_CNT_TRX_AGG_RANGE5_IDX,\n+\tSTAT_MIB_CNT_TRX_AGG_RANGE6_IDX,\n+\tSTAT_MIB_CNT_TRX_AGG_RANGE7_IDX,\n+\tSTAT_MIB_CNT_TRX_AGG_RANGE_MAX_NUM\n+};\n+\n+enum {\n+\tMT7925_BW_20 = 0,\n+\tMT7925_BW_40,\n+\tMT7925_BW_80,\n+\tMT7925_BW_160,\n+};\n+\n+struct mt7925_sta_stats_ac {\n+\t__le32 tx_fail;\n+\t__le32 tx_retry;\n+} __packed;\n+\n+struct mt7925_sta_stats_tx_vector {\n+\t__le32 txv[3];\n+} __packed;\n+\n+struct mt7925_sta_stats_mib {\n+\t__le32 rx_mpdu_cnt;\n+\t__le32 fcs_error;\n+\t__le32 rx_fifo_full;\n+\t__le32 ampdu_tx_sf_cnt;\n+\t__le32 ampdu_tx_ack_sf_cnt;\n+\t__le32 tx_range_ampdu_cnt[MT7925_STA_STATS_AGG_RANGE_NUM];\n+} __packed;\n+\n+/* Firmware EVENT_STA_STATISTICS body. */\n+struct mt7925_sta_stats {\n+\tu8 version;\n+\tu8 rsv[3];\n+\t__le32 flags;\n+\n+\tu8 sta_idx;\n+\tu8 bss_idx;\n+\tu8 wtbl_idx;\n+\tu8 rsv1;\n+\n+\tu8 mac_addr[ETH_ALEN];\n+\tu8 per;\n+\tu8 rcpi;\n+\n+\t__le32 phy_mode;\n+\t__le16 link_speed; /* 0.5 Mbit/s units */\n+\tu8 link_quality;\n+\tu8 link_reserved;\n+\n+\t__le32 tx_count;\n+\t__le32 tx_fail_count;\n+\t__le32 tx_life_timeout_count;\n+\t__le32 tx_done_air_time;\n+\t__le32 transmit_count;\n+\t__le32 transmit_fail_count;\n+\n+\tstruct mt7925_sta_stats_ac ac[MT7925_STA_STATS_AC_NUM];\n+\n+\tu8 temperature;\n+\tu8 skip_ar;\n+\tu8 ar_table_idx;\n+\tu8 rate_entry_idx;\n+\tu8 rate_entry_idx_prev;\n+\tu8 tx_sgi_detect_pass_cnt;\n+\tu8 ave_per;\n+\tu8 rsv2;\n+\n+\t__le32 agg_range_ctrl[2];\n+\tu8 range_mode;\n+\tu8 rsv3[3];\n+\t__le32 agg_range_ctrl_ext[6];\n+\n+\tu8 ar_state_curr;\n+\tu8 ar_state_prev;\n+\tu8 ar_action_type;\n+\tu8 highest_rate_cnt;\n+\tu8 lowest_rate_cnt;\n+\tu8 rsv4;\n+\t__le16 train_up;\n+\t__le16 train_down;\n+\tu8 rsv5[2];\n+\t__le32 rate1_tx_cnt;\n+\t__le32 rate1_fail_cnt;\n+\n+\tstruct mt7925_sta_stats_tx_vector tx_vector[MT7925_STA_STATS_BAND_NUM];\n+\tstruct mt7925_sta_stats_mib mib[MT7925_STA_STATS_BAND_NUM];\n+\n+\tu8 is_force_tx_stream;\n+\tu8 is_force_se_off;\n+\t__le16 ra_running_cnt;\n+\tu8 ra_status;\n+\tu8 max_ampdu_factor;\n+\tu8 tx_quality[MT7925_STA_STATS_TX_QUALITY_NUM];\n+\tu8 tx_rate_up_penalty;\n+\tu8 low_traffic_mode;\n+\tu8 low_traffic_count;\n+\tu8 low_traffic_dashboard;\n+\tu8 dynamic_sgi_state;\n+\tu8 dynamic_sgi_score;\n+\tu8 dynamic_bw_state;\n+\tu8 dynamic_gband_256qam_state;\n+\tu8 vht_non_sp_rate_state;\n+\tu8 rsv6[3];\n+\tu8 rsv7[2];\n+} __packed;\n+\n+struct mt7925_sta_rec_lookup {\n+\tint sta_rec_idx;\n+};\n+\n+/* Firmware auto-rate table snapshot, mirror Gen4m struct UNI_EVENT_STAT_WTBL.\n+ *\n+ * The firmware RA maintains up to MT7925_AUTO_RATE_NUM candidate rates in\n+ * rate_code[]. rate_idx tells which entry is currently in use, so a dump can\n+ * point at it with \"--\u003e\". fcap is the bandwidth capability and the ldpc/sgi\n+ * flags describe the per-mode coding/guard-interval used for those rates.\n+ */\n+struct mt7925_wtbl_rate {\n+\t__le16 tag;\n+\t__le16 len;\n+\tu8 rx_rcpi0;\n+\tu8 rx_rcpi1;\n+\tu8 cbrn;\n+\tu8 fcap;\n+\tu8 spe_idx;\n+\tu8 g2;\t\t\t/* SGI enabled, non-HE, BW20 */\n+\tu8 g4;\t\t\t/* SGI enabled, non-HE, BW40 */\n+\tu8 g8;\t\t\t/* SGI enabled, non-HE, BW80 */\n+\tu8 g16;\t\t\t/* SGI enabled, non-HE, BW160 */\n+\tu8 g2_he;\t\t/* GI, HE, BW20 */\n+\tu8 g4_he;\t\t/* GI, HE, BW40 */\n+\tu8 g8_he;\t\t/* GI, HE, BW80 */\n+\tu8 g16_he;\t\t/* GI, HE, BW160 */\n+\tu8 gi_eht;\t\t/* GI, EHT */\n+\tu8 ht_ldpc;\n+\tu8 vht_ldpc;\n+\tu8 he_ldpc;\n+\tu8 eht_ldpc;\n+\tu8 af;\n+\tu8 rate_idx;\t\t/* index into rate_code[] currently in use */\n+\t__le16 rate_code[MT7925_AUTO_RATE_NUM];\n+\t__le32 rsv[4];\n+} __packed;\n+\n+int\n+mt7925_get_ap_sta_rec_idx(struct mt792x_dev *dev, u8 *sta_rec_idx);\n+\n+void\n+mt7925_print_last_tx_rate(struct seq_file *s, const __le32 le_txv[3]);\n+\n+u8\n+mt7925_wtbl_get_sgi(const struct mt7925_wtbl_rate *wr, u8 txmode, u8 fcap);\n+\n+u8\n+mt7925_wtbl_get_ldpc(const struct mt7925_wtbl_rate *wr, u8 txmode);\n+\n+char *mt7925_hw_rate_ofdm_str(u8 ofdm_idx);\n+\n+int mt792x_mcu_get_wtbl_rate(struct mt792x_dev *dev, u16 wlan_idx,\n+\t\t\t     struct mt7925_wtbl_rate *rate);\n+\n+int mt792x_mcu_get_stat(struct mt792x_dev *dev,\n+\t\t\tstruct mt7925_sta_stats *stats);\n+\n+int mt7925_mcu_get_mib_info(struct mt792x_dev *dev, u8 band_idx,\n+\t\t\t    const u32 *counters, u8 counter_num,\n+\t\t\t    u64 *values);\n+\n+/* GET_STAT_INFO (UNI_CMD_ID_GET_STATISTICS = 0x23),\n+ * sub-command UNI_CMD_GET_STATISTICS_TAG_LINK_QUALITY = 1.\n+ *\n+ * @band_idx:            band/BSS index (0..3)\n+ * @rssi:                out, moving-average beacon RSSI (dBm), may be NULL\n+ * @tx_link_speed_kbps:  out, TX link speed in kbit/s, may be NULL\n+ *\n+ * Returns 0 on success, -ENOENT if the band's link quality is not ready.\n+ *\n+ * NOTE: firmware provides only RSSI and TX rate here. RX rate/bandwidth are\n+ * not in this event; read wcid-\u003erate for the RX rate.\n+ */\n+int mt7925_mcu_get_link_quality(struct mt792x_dev *dev, u8 band_idx,\n+\t\t\t\ts8 *rssi, u32 *tx_link_speed_kbps);\n+\n+#endif\n"
}

2/1 2026/08/27 06:27 agent patch-evaluator
2m Model:
best-expensive

Args:
null

Results:
{
  "EnableConfigs": [
    "MT7925U",
    "MT7925E"
  ],
  "FocusSymbols": [
    "mt792x_mcu_get_stat",
    "mt7925_mcu_get_mib_info",
    "mt792x_mcu_get_wtbl_rate",
    "mt7925_mcu_get_link_quality"
  ],
  "Reasoning": "The patch adds new debugfs interfaces to query statistics from the MT7925 firmware. This involves sending MCU commands and parsing the firmware responses (e.g., TLV parsing in `mt7925_mcu_get_mib_info`). Since the MT7925 driver supports USB (`CONFIG_MT7925U`), the device can be emulated by the fuzzer (e.g., via dummy_hcd). The fuzzer can provide crafted USB responses to these MCU commands, potentially triggering memory corruption or out-of-bounds accesses in the parsing logic. Therefore, these new firmware response parsing functions should be fuzzed.",
  "WorthFuzzing": true
}

Instruction:
You are an expert Linux kernel maintainer.
Your job is to review a provided patch series and determine
if it makes functional changes to the kernel that should be fuzzed.

IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in
your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively
use your code access tools to examine the actual source code and confirm any assumptions.

Return WorthFuzzing=false if the patch only contains:
- Modifications to Documentation/, Kconfig files, or code comments.
- Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints.
- Changes to numeric constants or macros that do not functionally alter execution flow.
- Code paths that are impossible to reach in virtualized environments like GCE or QEMU,
  even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim).
- Code in vendor-specific PCIe switch, SmartNIC, or GPU drivers (e.g., mlxsw, pds_core, qed,
  ionic, amdgpu) that require physical PCIe hardware cards not emulated in standard QEMU.
- Driver .remove, .shutdown, or pci_unregister_driver teardown callbacks (e.g., igb_remove)
  that are executed only during PCI hot-unplug or sysfs driver unbind operations.

If it modifies reachable core kernel logic, drivers, or architectures, use your code search
tools to verify the code can be executed, then return WorthFuzzing=true.

When returning WorthFuzzing=true, you MUST ALSO:
1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols.
   Avoid listing generic hot-path functions to prevent skewed test distributions.
   Prefer non-static, non-inlined API entrypoint functions over internal static helper functions
   (which are inlined by the compiler and do not have distinct symbol addresses).
2. Identify any specific CONFIG_ options required to properly test this new/modified feature.
   Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions.
   Also check "depends on" lines in Kconfig to include any non-standard parent subsystem configs
   needed for Kbuild to compile the code statically into vmlinux. List them in the EnableConfigs
   output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4").
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit a2eaffb5242b0d4dd3e76579542e2ec30c42b743
Author: syz-cluster <triage@syzkaller.com>
Date:   Thu Aug 27 06:27:37 2026 +0000

    syz-cluster: applied patch under review

diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
index 0ededa569e9a5..bf0f105c786e6 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 f9dcc0bba393c..3863a89b89742 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 d01ff49de47af..d96a1979a196f 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 0000000000000..9a47953062bc3
--- /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 0000000000000..a70b9eb3aa806
--- /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

3/2 2026/08/27 06:27 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 25963
							output: 21
							thoughts: 1173
						

Thoughts:
**Fuzzing Feasibility Assessment: MediaTek MT7925 Statistics Patch**

Okay, let's break this down. We have a patch adding statistics querying to the MediaTek MT7925 wireless driver, specifically focused on a `stats` debugfs entry. The patch hooks into the firmware via MCU commands and presents the data through debugfs. My initial thought is to assess if this introduces new fuzzing targets.

First, I need to check for any potentially reachable core kernel logic, drivers, or architectures that are modified. The changes are contained within `drivers/net/wireless/mediatek/mt76/mt7925/`, so it's localized. The functions in `stats.c`, like `mt7925_mcu_get_stat`, `mt7925_mcu_get_mib_info`, etc., are the core of this. These are all called within `mt792x_stats`, a debugfs read callback. While debugfs is often considered "decorative," its interaction with MCU commands and the resulting parsing is what I need to focus on.

The prompt notes that purely decorative debugfs changes might be exempt from fuzzing. However, this isn't purely decorative. This patch triggers actual MCU commands. Crucially, the MT7925 is a Wi-Fi 7 driver, and it has both PCIe (`mt7925e`) and USB (`mt7925u`) variants. USB devices are emulatable in QEMU, and syzkaller *does* support USB fuzzing, specifically via `dummy_hcd`. Syzkaller can, in theory, send crafted USB responses to these MCU commands, making me think this is worth pursuing. The key is that we can emulate the USB device and potentially trigger bugs in the parsing logic for those responses.

Looking at the parsing code itself (specifically, the snippet provided), this confirms my suspicions. We're dealing with parsing `skb` data, including checks for the event tag, length validation, and alignment. This `mt7925_sta_stats_event` structure's parsing is now reachable via debugfs, which syzkaller can readily access. Parsing firmware responses, especially when done in a controlled emulated environment, is a prime target for fuzzing. Buffer overflows or out-of-bounds reads are likely candidates for exploitation if the parsing is mishandled.

Therefore, this patch *does* introduce functional changes that can be fuzzed. It's not just logging or documentation; it's a new path for sending commands to the firmware and parsing the response data.

Finally, I need to confirm if `mt7925u` is actually enabled in syzkaller or if a test is easily possible. The configuration indicates `CONFIG_MT7925U` (USB) and `CONFIG_MT7925E` (PCIe) are present. With the USB variant, I am optimistic, and I will now check the codebase using `grepper` to see if `CONFIG_MT7925U` is enabled, or if the driver is tested.



4/2 2026/08/27 06:28 tool grepper
0m Args:
{
  "Expression": "config MT7925U"
}

Results:
{
  "Output": "drivers/net/wireless/mediatek/mt76/mt7925/Kconfig=7=config MT7925E\n--\ndrivers/net/wireless/mediatek/mt76/mt7925/Kconfig-18-\ndrivers/net/wireless/mediatek/mt76/mt7925/Kconfig:19:config MT7925U\ndrivers/net/wireless/mediatek/mt76/mt7925/Kconfig-20-\ttristate \"MediaTek MT7925U (USB) support\"\n"
}

5/2 2026/08/27 06:28 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 27972
							output: 43
							thoughts: 508
						

Thoughts:
**Fuzzing Target Identification: MT7925U Statistics**

Okay, so `CONFIG_MT7925U` is definitely a thing, good start. The relevant code is located in `drivers/net/wireless/mediatek/mt76/mt7925/stats.c` and `debugfs.c`. My initial scan highlights a few functions that seem particularly interesting for fuzzing: `mt792x_mcu_get_stat`, `mt7925_mcu_get_mib_info`, `mt792x_mcu_get_wtbl_rate`, `mt7925_mcu_get_link_quality`, and of course, `mt792x_stats`.

Ah, right, `mt792x_stats` is a `seq_file` show function, meaning it will be called to format information for debugfs. It calls those other functions I mentioned. Thinking about it, the device is accessible via USB, so that means the fuzzer can provide arbitrary responses to MCU commands. That is a juicy target. The listed functions are all sending MCU commands, and then parsing the responses. This is a very promising area for fuzzing, given the direct interaction with potentially untrusted data.

Just to be sure, I'll jot down the exact function names again: `mt792x_mcu_get_stat`, `mt7925_mcu_get_mib_info`, `mt792x_mcu_get_wtbl_rate`, `mt7925_mcu_get_link_quality`, and finally, `mt792x_stats`.

As for enabling it for fuzzing: I'll need `MT7925U` of course. `MT7925E` is another possibility, though USB is easier to fuzz. And then, since this is in `debugfs.c`, I'll need to double check that `MAC80211_DEBUGFS` is enabled, or at least that debugfs itself is enabled for the build. I'll need to check the `drivers/net/wireless/mediatek/mt76/mt7925/Makefile` and `Kconfig` files to be certain.



6/2 2026/08/27 06:28 tool grepper
0m Args:
{
  "Expression": "debugfs\\.o",
  "PathPrefix": "drivers/net/wireless/mediatek/mt76/mt7925/"
}

Results:
{
  "Output": "drivers/net/wireless/mediatek/mt76/mt7925/Makefile=5=obj-$(CONFIG_MT7925U) += mt7925u.o\ndrivers/net/wireless/mediatek/mt76/mt7925/Makefile-6-\ndrivers/net/wireless/mediatek/mt76/mt7925/Makefile:7:mt7925-common-y := mac.o mcu.o regd.o main.o init.o debugfs.o nan.o stats.o\ndrivers/net/wireless/mediatek/mt76/mt7925/Makefile-8-mt7925-common-$(CONFIG_NL80211_TESTMODE) += testmode.o\n"
}

7/2 2026/08/27 06:28 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 29213
							output: 49
							thoughts: 45
						

Thoughts:
Here's my thought process, summarized as if I were considering the code:

**Analyzing the mt7925 Debugfs Compilation**

Okay, so `debugfs.o` is consistently included in `mt7925-common-y`. That's the baseline assumption. But, before I move forward, I really need to verify something crucial. I have to make sure that the function `mt7925_init_debugfs` is *actually* called unconditionally, or at least in a way I'm expecting. It's one thing to have the object compiled, it's quite another for the debug interface to *always* be initialized. I need to trace the call paths to be 100% certain it's behaving as expected. Otherwise, I might be chasing a phantom or misinterpreting the actual state of things.



8/2 2026/08/27 06:28 tool grepper
0m Args:
{
  "Expression": "mt7925_init_debugfs",
  "PathPrefix": "drivers/net/wireless/mediatek/mt76/mt7925/"
}

Results:
{
  "Output": "drivers/net/wireless/mediatek/mt76/mt7925/debugfs.c=414=static int mt792x_stats(struct seq_file *s, void *data)\n--\ndrivers/net/wireless/mediatek/mt76/mt7925/debugfs.c-628-\ndrivers/net/wireless/mediatek/mt76/mt7925/debugfs.c:629:int mt7925_init_debugfs(struct mt792x_dev *dev)\ndrivers/net/wireless/mediatek/mt76/mt7925/debugfs.c-630-{\n--\ndrivers/net/wireless/mediatek/mt76/mt7925/init.c=190=static void mt7925_init_work(struct work_struct *work)\n--\ndrivers/net/wireless/mediatek/mt76/mt7925/init.c-218-\ndrivers/net/wireless/mediatek/mt76/mt7925/init.c:219:\tret = mt7925_init_debugfs(dev);\ndrivers/net/wireless/mediatek/mt76/mt7925/init.c-220-\tif (ret) {\n--\ndrivers/net/wireless/mediatek/mt76/mt7925/mt7925.h=321=int mt7925_init_mlo_caps(struct mt792x_phy *phy);\ndrivers/net/wireless/mediatek/mt76/mt7925/mt7925.h:322:int mt7925_init_debugfs(struct mt792x_dev *dev);\ndrivers/net/wireless/mediatek/mt76/mt7925/mt7925.h-323-\n"
}

9/2 2026/08/27 06:28 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 29966
							output: 46
							thoughts: 81
						

Thoughts:
**My Initial Thoughts on the MT7925 Driver Initialization**

Okay, so I'm digging into this `mt7925_init_work` function. That's where the initial setup happens, right? I need to figure out how these MT7925 drivers are enabled. The obvious ones to look at are `MT7925U` and `MT7925E`. Ah, wait a minute... `MT7925U` has some dependencies. It's tied to `USB` and `MAC80211`. That makes sense for a USB-based implementation. It's a good starting point, but I need to quickly refresh myself on those dependencies to be completely clear. Let's delve deeper into the `MT7925U` dependencies to gain a firmer grasp of the overall activation process.



10/2 2026/08/27 06:29 tool grepper
0m Args:
{
  "Expression": "config MT7925U",
  "PathPrefix": "drivers/net/wireless/mediatek/mt76/mt7925/"
}

Results:
{
  "Output": "drivers/net/wireless/mediatek/mt76/mt7925/Kconfig=7=config MT7925E\n--\ndrivers/net/wireless/mediatek/mt76/mt7925/Kconfig-18-\ndrivers/net/wireless/mediatek/mt76/mt7925/Kconfig:19:config MT7925U\ndrivers/net/wireless/mediatek/mt76/mt7925/Kconfig-20-\ttristate \"MediaTek MT7925U (USB) support\"\n"
}

11/2 2026/08/27 06:29 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 30426
							output: 47
							thoughts: 27
						

Thoughts:
Here's my summary, as though I'm reading and processing the `drivers/net/wireless/mediatek/mt76/mt7925/Kconfig` file:

**Diving into the MT7925 Kconfig**

Okay, time to dive into the configuration file for the MT7925 wireless driver, tucked away in the MediaTek directory. This `Kconfig` file is the heart of how this driver gets integrated and configured within the broader kernel build system. My brain's already gearing up to identify dependencies, default values, and the overall structure that defines how users can interact with this driver during kernel configuration.

I need to understand: What features are conditionally compiled based on other settings? Are there any hard dependencies on other parts of the kernel or other drivers? Are there options for debugging, power management, or specific radio frequencies that users can select? Knowing the structure helps me to build a strategy for how I might change settings.

I'll be looking for things like `config MT7925`, which should be the main switch for the entire driver. From there, it's about following the chain of dependencies. I'm expecting to see things like `depends on` clauses that link to other network configurations, device tree support, or maybe even platform-specific settings. Then, I'll be interested in the `help` sections – those are goldmines for understanding the purpose of each option and what tradeoffs might be involved in enabling or disabling certain features. The `default` statements are important for understanding reasonable values, and the `bool`, `tristate`, or `string` options will show how values are determined.

It's a process of dissecting the relationships between these configurations, the choices available to a user, and how they ultimately translate into the compiled kernel. This Kconfig file is the blueprint for how the MT7925 driver integrates into the system, and understanding it is key to ensuring its proper function and optimal performance. Let's see what secrets it holds.



12/2 2026/08/27 06:29 tool read-file
0m Args:
{
  "File": "drivers/net/wireless/mediatek/mt76/mt7925/Kconfig",
  "FirstLine": 1,
  "LineCount": 30
}

Results:
{
  "Contents": "   1:\t# SPDX-License-Identifier: BSD-3-Clause-Clear\n   2:\tconfig MT7925_COMMON\n   3:\t\ttristate\n   4:\t\tselect MT792x_LIB\n   5:\t\tselect WANT_DEV_COREDUMP\n   6:\t\n   7:\tconfig MT7925E\n   8:\t\ttristate \"MediaTek MT7925E (PCIe) support\"\n   9:\t\tselect MT7925_COMMON\n  10:\t\tdepends on MAC80211\n  11:\t\tdepends on PCI\n  12:\t\thelp\n  13:\t\t  This adds support for MT7925-based wireless PCIe devices,\n  14:\t\t  which support operation at 6GHz, 5GHz, and 2.4GHz IEEE 802.11be\n  15:\t\t  2x2:2SS 4096-QAM, 160MHz channels.\n  16:\t\n  17:\t\t  To compile this driver as a module, choose M here.\n  18:\t\n  19:\tconfig MT7925U\n  20:\t\ttristate \"MediaTek MT7925U (USB) support\"\n  21:\t\tselect MT792x_USB\n  22:\t\tselect MT7925_COMMON\n  23:\t\tdepends on MAC80211\n  24:\t\tdepends on USB\n  25:\t\thelp\n  26:\t\t  This adds support for MT7925-based wireless USB devices,\n  27:\t\t  which support operation at 6GHz, 5GHz, and 2.4GHz IEEE 802.11be\n  28:\t\t  2x2:2SS 4096-QAM, 160MHz channels.\n  29:\t\n  30:\t\t  To compile this driver as a module, choose M here.\n"
}

13/2 2026/08/27 06:29 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 31406
							output: 60
							thoughts: 301
						

Thoughts:
**My Analysis of the MT7925U Driver**

Okay, let's break this down. From what I'm seeing, this `MT7925U` chip is dependent on `MAC80211` and the `USB` subsystem, which makes perfect sense. To enable it, I need to make sure the `MT7925U` and `MT7925E` configurations are set.

Now, the core of this seems to revolve around a few key functions that I need to investigate in more detail: `mt792x_mcu_get_stat`, `mt7925_mcu_get_mib_info`, `mt792x_mcu_get_wtbl_rate`, `mt7925_mcu_get_link_quality`, and `mt792x_stats`. My immediate thought is, are these static or not? Static functions, of course, are only visible within their compilation unit, which could be an issue if they need to be accessed from elsewhere.

So, let's see... `mt792x_mcu_get_stat`, `mt7925_mcu_get_mib_info`, `mt792x_mcu_get_wtbl_rate`, and `mt7925_mcu_get_link_quality` are *not* static, good. They should be exported as symbols then. But... `mt792x_stats` *is* static, and it's located in `debugfs.c`. Hmm, that might be problematic if we expect to call it from outside that file.

Alright, a quick check of `stats.c` is in order. We will probably find those non-static functions there! Let's get to it.



14/2 2026/08/27 06:29 tool grepper
0m Args:
{
  "Expression": "^[a-zA-Z_].*mt7925?_.*\\(",
  "PathPrefix": "drivers/net/wireless/mediatek/mt76/mt7925/stats.c"
}

Results:
{
  "Output": "drivers/net/wireless/mediatek/mt76/mt7925/stats.c=305=int mt792x_mcu_get_stat(struct mt792x_dev *dev,\n--\ndrivers/net/wireless/mediatek/mt76/mt7925/stats.c-363-\ndrivers/net/wireless/mediatek/mt76/mt7925/stats.c:364:int mt7925_mcu_get_mib_info(struct mt792x_dev *dev, u8 band_idx,\ndrivers/net/wireless/mediatek/mt76/mt7925/stats.c-365-\t\t\t    const u32 *counters, u8 counter_num,\n--\ndrivers/net/wireless/mediatek/mt76/mt7925/stats.c-451-\ndrivers/net/wireless/mediatek/mt76/mt7925/stats.c:452:char *mt7925_hw_rate_ofdm_str(u8 ofdm_idx)\ndrivers/net/wireless/mediatek/mt76/mt7925/stats.c-453-{\n--\ndrivers/net/wireless/mediatek/mt76/mt7925/stats.c=481=int mt792x_mcu_get_wtbl_rate(struct mt792x_dev *dev, u16 wlan_idx,\n--\ndrivers/net/wireless/mediatek/mt76/mt7925/stats.c-532- */\ndrivers/net/wireless/mediatek/mt76/mt7925/stats.c:533:int mt7925_mcu_get_link_quality(struct mt792x_dev *dev, u8 band_idx,\ndrivers/net/wireless/mediatek/mt76/mt7925/stats.c-534-\t\t\t\ts8 *rssi, u32 *tx_link_speed_kbps)\n"
}

15/2 2026/08/27 06:29 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 32680
							output: 57
							thoughts: 103
						

Thoughts:
Here's my summary, as though I'm thinking through this process:

**Analyzing These Function Signatures**

Alright, let's break this down. My initial scan identifies a set of functions that are clearly related to retrieving status information or data from the MCU (Microcontroller Unit) within these MT792x/MT7925 wireless chipset implementations. I'm seeing: `mt792x_mcu_get_stat`, `mt7925_mcu_get_mib_info`, `mt792x_mcu_get_wtbl_rate`, and `mt7925_mcu_get_link_quality`. These are fairly standard names, suggestive of calls to read statistics, MIB (Management Information Base) data, wireless table rates, and link quality metrics, respectively. The prefix "mt792x/mt7925" clearly indicates which chipset family each function is associated with.

Hold on... I need to also add `mt7925_get_ap_sta_rec_idx` to that non-static list. That one wasn't immediately apparent. But it's clearly part of the broader set of functions that are *not* declared static. Now, the context of those functions becomes more important to consider. Why are they non-static, and how are they being used? The non-static nature implies that these functions are likely designed to be called from multiple parts of the driver or even potentially from external modules. Time to dig deeper into the code to understand their usage and dependencies.



16/2 2026/08/27 06:29 tool grepper
0m Args:
{
  "Expression": "mt7925_get_ap_sta_rec_idx",
  "PathPrefix": "drivers/net/wireless/mediatek/mt76/mt7925/stats.c"
}

Results:
{
  "Output": "drivers/net/wireless/mediatek/mt76/mt7925/stats.c=106=static void\ndrivers/net/wireless/mediatek/mt76/mt7925/stats.c:107:mt7925_get_ap_sta_rec_idx_iter(void *data, u8 *mac,\ndrivers/net/wireless/mediatek/mt76/mt7925/stats.c-108-\t\t\t       struct ieee80211_vif *vif)\n--\ndrivers/net/wireless/mediatek/mt76/mt7925/stats.c=131=int\ndrivers/net/wireless/mediatek/mt76/mt7925/stats.c:132:mt7925_get_ap_sta_rec_idx(struct mt792x_dev *dev, u8 *sta_rec_idx)\ndrivers/net/wireless/mediatek/mt76/mt7925/stats.c-133-{\n--\ndrivers/net/wireless/mediatek/mt76/mt7925/stats.c-141-\t\t\t\t\t    IEEE80211_IFACE_ITER_RESUME_ALL,\ndrivers/net/wireless/mediatek/mt76/mt7925/stats.c:142:\t\t\t\t\t    mt7925_get_ap_sta_rec_idx_iter,\ndrivers/net/wireless/mediatek/mt76/mt7925/stats.c-143-\t\t\t\t\t    \u0026lookup);\n--\ndrivers/net/wireless/mediatek/mt76/mt7925/stats.c=305=int mt792x_mcu_get_stat(struct mt792x_dev *dev,\n--\ndrivers/net/wireless/mediatek/mt76/mt7925/stats.c-318-\ndrivers/net/wireless/mediatek/mt76/mt7925/stats.c:319:\tret = mt7925_get_ap_sta_rec_idx(dev, \u0026req.sta_rec_idx);\ndrivers/net/wireless/mediatek/mt76/mt7925/stats.c-320-\tif (ret)\n"
}

17/2 2026/08/27 06:29 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 33747
							output: 23
							thoughts: 182
						

Thoughts:
**My Fuzzing Focus**

Okay, so I've been taking a look at this `mt7925` driver. I see that functions like `mt7925_get_ap_sta_rec_idx`, `mt7925_print_last_tx_rate`, `mt7925_wtbl_get_sgi`, and `mt7925_wtbl_get_ldpc` are all non-static. That's a good starting point, but they don't jump out at me as the most critical for fuzzing. The real juicy targets are the ones that actually *talk* to the firmware and process the responses – those are the functions that are going to be most likely to expose vulnerabilities. I'm thinking specifically about:

*   `mt792x_mcu_get_stat`
*   `mt7925_mcu_get_mib_info`
*   `mt792x_mcu_get_wtbl_rate`
*   `mt7925_mcu_get_link_quality`

Those are the ones that are likely to be processing untrusted data from the firmware, so they're where I should focus my initial fuzzing efforts. I should also check to see if `MAC80211_DEBUGFS` is needed. That might give some visibility to other areas.



18/2 2026/08/27 06:29 tool grepper
0m Args:
{
  "Expression": "config MAC80211_DEBUGFS"
}

Results:
{
  "Output": "net/mac80211/Kconfig=79=config MAC80211_LEDS\n--\nnet/mac80211/Kconfig-87-\nnet/mac80211/Kconfig:88:config MAC80211_DEBUGFS\nnet/mac80211/Kconfig-89-\tbool \"Export mac80211 internals in DebugFS\"\n"
}

19/2 2026/08/27 06:29 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 34371
							output: 41
							thoughts: 38
						

Thoughts:
Okay, here's my interpretation of those thoughts, summarized from my perspective as someone well-versed in this area:

**Debugging Deeper: A Quick Assessment of mt7925_init_debugfs**

Alright, I'm taking a look at this `mt7925_init_debugfs` function. My initial question is: where does this reside? Oh, it's right in the `mt7925` driver. Now, the next logical step is to quickly determine if `MAC80211_DEBUGFS` is in play. Is there an implied dependency here? Does the debugfs initialization hinge on whether the debug filesystem is enabled through that particular configuration option? I need to know if I need to worry about the relationship between `mt7925_init_debugfs` and this other configuration option. Gotta think about build configurations here. I need to figure out the conditions under which this function is expected to work.



20/2 2026/08/27 06:30 tool grepper
0m Args:
{
  "Expression": "MAC80211_DEBUGFS",
  "PathPrefix": "drivers/net/wireless/mediatek/mt76/"
}

Results:
{
  "Output": "drivers/net/wireless/mediatek/mt76/mt7915/debugfs.c=1376=bool mt7915_debugfs_rx_log(struct mt7915_dev *dev, const void *data, int len)\n--\ndrivers/net/wireless/mediatek/mt76/mt7915/debugfs.c-1386-\ndrivers/net/wireless/mediatek/mt76/mt7915/debugfs.c:1387:#ifdef CONFIG_MAC80211_DEBUGFS\ndrivers/net/wireless/mediatek/mt76/mt7915/debugfs.c-1388-/** per-station debugfs **/\n--\ndrivers/net/wireless/mediatek/mt76/mt7915/main.c=1776=const struct ieee80211_ops mt7915_ops = {\n--\ndrivers/net/wireless/mediatek/mt76/mt7915/main.c-1825-\tCFG80211_TESTMODE_DUMP(mt76_testmode_dump)\ndrivers/net/wireless/mediatek/mt76/mt7915/main.c:1826:#ifdef CONFIG_MAC80211_DEBUGFS\ndrivers/net/wireless/mediatek/mt76/mt7915/main.c-1827-\t.sta_add_debugfs = mt7915_sta_add_debugfs,\n--\ndrivers/net/wireless/mediatek/mt76/mt7915/mt7915.h=637=bool mt7915_debugfs_rx_log(struct mt7915_dev *dev, const void *data, int len);\ndrivers/net/wireless/mediatek/mt76/mt7915/mt7915.h:638:#ifdef CONFIG_MAC80211_DEBUGFS\ndrivers/net/wireless/mediatek/mt76/mt7915/mt7915.h-639-void mt7915_sta_add_debugfs(struct ieee80211_hw *hw, struct ieee80211_vif *vif,\n--\ndrivers/net/wireless/mediatek/mt76/mt7996/debugfs.c=959=bool mt7996_debugfs_rx_log(struct mt7996_dev *dev, const void *data, int len)\n--\ndrivers/net/wireless/mediatek/mt76/mt7996/debugfs.c-969-\ndrivers/net/wireless/mediatek/mt76/mt7996/debugfs.c:970:#ifdef CONFIG_MAC80211_DEBUGFS\ndrivers/net/wireless/mediatek/mt76/mt7996/debugfs.c-971-/** per-station debugfs **/\n--\ndrivers/net/wireless/mediatek/mt76/mt7996/main.c=2545=const struct ieee80211_ops mt7996_ops = {\n--\ndrivers/net/wireless/mediatek/mt76/mt7996/main.c-2593-\t.twt_teardown_request = mt7996_twt_teardown_request,\ndrivers/net/wireless/mediatek/mt76/mt7996/main.c:2594:#ifdef CONFIG_MAC80211_DEBUGFS\ndrivers/net/wireless/mediatek/mt76/mt7996/main.c-2595-\t.sta_add_debugfs = mt7996_sta_add_debugfs,\n--\ndrivers/net/wireless/mediatek/mt76/mt7996/mt7996.h=934=int mt7996_mcu_set_emlsr_mode(struct mt7996_dev *dev,\n--\ndrivers/net/wireless/mediatek/mt76/mt7996/mt7996.h-937-\t\t\t      struct ieee80211_eml_params *eml_params);\ndrivers/net/wireless/mediatek/mt76/mt7996/mt7996.h:938:#ifdef CONFIG_MAC80211_DEBUGFS\ndrivers/net/wireless/mediatek/mt76/mt7996/mt7996.h-939-void mt7996_sta_add_debugfs(struct ieee80211_hw *hw, struct ieee80211_vif *vif,\n"
}

21/2 2026/08/27 06:30 llm patch-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 35824
							output: 254
							thoughts: 1113
						

Thoughts:
**Fuzzing Justification for MT7925 Debugfs Interfaces**

Okay, so I'm looking at this patch for the `mt7925` driver and how it's setting up debugfs. It seems they're not using the standard `MAC80211_DEBUGFS` approach, but rather `debugfs_create_devm_seqfile`.  That's fine, as long as `DEBUG_FS` is enabled, which it likely is given that syzkaller usually enables it. I should double-check, but I'm pretty sure we're good there.

Now, let's nail down what functions are important to focus on. Based on the code, it looks like these are the key functions: `mt792x_mcu_get_stat`, `mt7925_mcu_get_mib_info`, `mt792x_mcu_get_wtbl_rate`, and `mt7925_mcu_get_link_quality`. They're all in `drivers/net/wireless/mediatek/mt76/mt7925/stats.c`, so I know where to find them. And, critically, they're exported and non-static, so they're accessible.

What about configurations? Well, `MT7925U` is the main one and `MT7925E` is the PCIe variant. I'll make sure to note both.

But the real question is: Does this qualify as "WorthFuzzing"?  The criteria say "Purely decorative changes... such as logging" are out.  But is adding a debugfs interface that *queries* the firmware just decoration? I'd argue not. It involves sending commands to the firmware and parsing the responses, which could be fuzzed.

Take this snippet for example:
```c
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;
}
```
At first glance, it looks like `len` is checked against `skb->len`. But then there's this `memcpy(stats, &event->stats, sizeof(*stats))`.
Wait, if `len` is smaller than `sizeof(*event)`, we're good because the code checks for `len < sizeof(*event) - sizeof(event->rsv)`. So, that specific code looks safe, but what about other areas?

Now, look at `mt7925_mcu_get_mib_info`.  That's a TLV parser:
```c
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;
```
This is *exactly* the kind of code that can be vulnerable.  A fuzzer could craft malicious responses to this debugfs query, specifically to the MT7925U device. This could be done by sending an emulated USB device via `dummy_hcd` to cause memory corruption in the parsing logic due to out-of-bounds accesses or similar issues.

Therefore, the reasoning is this: This patch introduces new debugfs interfaces that interact with the MT7925 firmware.  The firmware responses are parsed, especially with TLVs. Since the driver supports USB and can be emulated, a fuzzer can provide crafted responses, and that makes it worth fuzzing. The key functions are `mt792x_mcu_get_stat`, `mt7925_mcu_get_mib_info`, `mt792x_mcu_get_wtbl


22/2 2026/08/27 06:30 tool set-results
0m Args:
{
  "EnableConfigs": [
    "MT7925U",
    "MT7925E"
  ],
  "FocusSymbols": [
    "mt792x_mcu_get_stat",
    "mt7925_mcu_get_mib_info",
    "mt792x_mcu_get_wtbl_rate",
    "mt7925_mcu_get_link_quality"
  ],
  "Reasoning": "The patch adds new debugfs interfaces to query statistics from the MT7925 firmware. This involves sending MCU commands and parsing the firmware responses (e.g., TLV parsing in `mt7925_mcu_get_mib_info`). Since the MT7925 driver supports USB (`CONFIG_MT7925U`), the device can be emulated by the fuzzer (e.g., via dummy_hcd). The fuzzer can provide crafted USB responses to these MCU commands, potentially triggering memory corruption or out-of-bounds accesses in the parsing logic. Therefore, these new firmware response parsing functions should be fuzzed.",
  "WorthFuzzing": true
}

Results:
{
  "EnableConfigs": [
    "MT7925U",
    "MT7925E"
  ],
  "FocusSymbols": [
    "mt792x_mcu_get_stat",
    "mt7925_mcu_get_mib_info",
    "mt792x_mcu_get_wtbl_rate",
    "mt7925_mcu_get_link_quality"
  ],
  "Reasoning": "The patch adds new debugfs interfaces to query statistics from the MT7925 firmware. This involves sending MCU commands and parsing the firmware responses (e.g., TLV parsing in `mt7925_mcu_get_mib_info`). Since the MT7925 driver supports USB (`CONFIG_MT7925U`), the device can be emulated by the fuzzer (e.g., via dummy_hcd). The fuzzer can provide crafted USB responses to these MCU commands, potentially triggering memory corruption or out-of-bounds accesses in the parsing logic. Therefore, these new firmware response parsing functions should be fuzzed.",
  "WorthFuzzing": true
}

23/1 2026/08/27 06:30 action If
0m Args:
{
  "WorthFuzzing": true
}

Results:
null

24/2 2026/08/27 06:30 agent kmsan-evaluator
0m Model:
best-expensive

Args:
null

Results:
{
  "KMSANReasoning": "The patch introduces a new debugfs interface to dump statistics for the mt7925 driver. It allocates memory for the statistics structures using `kzalloc` (which zero-initializes the memory), and local variables are explicitly initialized. The structures sent to the firmware are also fully initialized (either explicitly or via compiler zero-initialization for omitted fields). The responses from the firmware are bounds-checked against the received `skb-\u003elen` before being copied. Finally, the data is exposed to user space via `seq_printf`, which formats the values into a string buffer rather than copying raw kernel memory structures directly to user space. There is no risk of uninitialized kernel memory being leaked or used in control flow. Any potential out-of-bounds accesses (e.g., if the `skb-\u003elen` checks were flawed) would be caught by standard KASAN. Therefore, a dedicated KMSAN fuzzing session is not required.",
  "NeedsKMSAN": false
}

Instruction:
You are an expert Linux kernel security engineer specializing in kernel memory
error detectors (KASAN and KMSAN). Your job is to review the provided patch series and
determine if the code changes justify spawning a dedicated KMSAN (KernelMemorySanitizer)
fuzzing session in addition to standard KASAN fuzzing.

CRITICAL DISTINCTION BETWEEN KASAN AND KMSAN:
- Standard KASAN kernel builds (upstream-apparmor-kasan.config) already enable
  a comprehensive suite of debugging tools and sanitizers, including KASAN
  (out-of-bounds accesses, use-after-free, double free, invalid free), LOCKDEP
  (locking bugs and deadlocks), UB-sanitizers, and memory corruption checks.
- KMSAN (KernelMemorySanitizer) detects reads of UNINITIALIZED memory (stack, heap,
  or page allocations) and kernel-to-user memory info-leaks.

Rule: THERE IS NO SENSE IN RUNNING A KMSAN SESSION IF A BUG CAN BE CAUGHT BY KASAN,
LOCKDEP, OR OTHER STANDARD BUG DETECTORS.
A dedicated KMSAN fuzzing session incurs significant resource costs. You must ONLY
set NeedsKMSAN=true if the code changes introduce or expose UNINITIALIZED MEMORY risks
that are detected ONLY by KMSAN.

Look holistically at the patch series and surrounding code. Even if no direct
uninitialized field accesses or new buffer allocations are added in the diff itself,
a patch may alter control flow, bounds checking, or data length calculations in ways
that change how the rest of the code operates on existing buffers (e.g. allowing
uninitialized stack/heap memory to be read, copied to user space, or used in control
flow). Do not hesitate to use your code access tools to inspect the surrounding code,
called functions, and callers.

Set NeedsKMSAN=true ONLY IF the patch introduces or modifies:
1. Kernel structures sent to user space (via copy_to_user, put_user, netlink skb
   attributes, ioctl output arguments, socket options, or BPF buffers) where fields
   or structure padding might not be fully initialized/zeroed.
2. Conditional logic or branching that depends on potentially uninitialized variables
   or struct fields.
3. Allocation or initialization of complex data structures where uninitialized fields
   could be read later in reachable code paths.
4. Bounds checks, lengths, or logic in a way that allows surrounding code to access
   uninitialized bytes of existing buffers.

Set NeedsKMSAN=false IF:
- The code changes primarily risk out-of-bounds access, array overflows, NULL pointer
  dereferences, locking deadlocks, or use-after-free bugs (these are already caught
  by KASAN, LOCKDEP, or standard bug detectors).
- All stack/heap structures touched or introduced by the patch are fully zeroed
  or initialized (e.g. using = {0}, memset, kzalloc) before being read or copied.
- The patch does not introduce any risk of uninitialized memory usage or info-leaks.

Use your code access tools to inspect the surrounding code if necessary, then provide
detailed KMSANReasoning contrasting KASAN vs KMSAN applicability for this patch.
Prefer calling several tools at the same time to save round-trips.


Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.

Prompt:
For your convenience, here is the diff of the changes:
commit a2eaffb5242b0d4dd3e76579542e2ec30c42b743
Author: syz-cluster <triage@syzkaller.com>
Date:   Thu Aug 27 06:27:37 2026 +0000

    syz-cluster: applied patch under review

diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.h
index 0ededa569e9a5..bf0f105c786e6 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 f9dcc0bba393c..3863a89b89742 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 d01ff49de47af..d96a1979a196f 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 0000000000000..9a47953062bc3
--- /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 0000000000000..a70b9eb3aa806
--- /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

25/3 2026/08/27 06:30 llm kmsan-evaluator
0m Model:
gemini-3.1-pro-preview

Args:
null

Results:
null

Tokens:
							input: 25960
							output: 219
							thoughts: 3492
						

Thoughts:
**Analysis of the mt7925 Driver Debugfs Statistics Patch**

Okay, so this patch introduces a `stats` debugfs file for the `mt7925` driver. My initial concern was centered on potential information leaks via uninitialized memory being printed to user space. Debugfs can be a privileged interface, but the data here goes through `seq_printf`, which formats data into a string, not raw memory. That's a good first check. Still, I need to ensure that the structs used internally are properly initialized before their fields are accessed, even if it's just for formatting.

I've carefully examined the `mt7925_sta_stats`, `mt7925_wtbl_rate`, `mt7925_mib_data`, and `mt7925_link_quality` structures. I've broken down their allocation and population. The key is how these structures are populated: they're filled by querying the firmware via MCU commands, and then printed in debugfs.

I looked at `mt792x_stats` first. `stats` is allocated with `kzalloc`, which means zeroed memory. That's a win. Then `mt792x_mcu_get_stat` is called to populate it. The interesting part is the `skb->data` where the firmware data comes in. The code checks the length (`event->len`) of the incoming firmware message against the size of the expected data and ensures the data is at least the size of the event struct. So, the `event->stats` portion is always fully backed by the data from the firmware, and no uninitialized kernel memory gets copied over. The firmware does the DMA from the device.

The `mib_values` are also initialized using `memset(values, 0, ...)` before getting populated from firmware. Zeroing is good! `wtbl_rate` is allocated on the stack but is only used if `mt792x_mcu_get_wtbl_rate` returns successfully. The `skb` length is also checked there, and the `rate` field comes from the firmware data. `rssi` and `tx_link_speed_kbps` are both initialized to zero. The requests (`req` structs) sent to the firmware are also fully zeroed via `kzalloc` or properly initialized.

Then I checked for padding to ensure that all members are initialized. I verified that the structures are packed, which means no hidden uninitialized padding that could be printed.

Therefore, this patch doesn't copy raw data to user space. It only uses formatted strings. It properly checks the size of firmware responses against the expected data structure sizes. And all structures used internally are either zeroed or initialized before being populated.

I also checked `mt7925_get_ap_sta_rec_idx`, but there are no problems there.

I am confident that the code is safe and does not present a risk of leaking uninitialized kernel memory. KASAN should catch any out-of-bounds access. KMSAN is not needed here.



26/3 2026/08/27 06:30 tool set-results
0m Args:
{
  "KMSANReasoning": "The patch introduces a new debugfs interface to dump statistics for the mt7925 driver. It allocates memory for the statistics structures using `kzalloc` (which zero-initializes the memory), and local variables are explicitly initialized. The structures sent to the firmware are also fully initialized (either explicitly or via compiler zero-initialization for omitted fields). The responses from the firmware are bounds-checked against the received `skb-\u003elen` before being copied. Finally, the data is exposed to user space via `seq_printf`, which formats the values into a string buffer rather than copying raw kernel memory structures directly to user space. There is no risk of uninitialized kernel memory being leaked or used in control flow. Any potential out-of-bounds accesses (e.g., if the `skb-\u003elen` checks were flawed) would be caught by standard KASAN. Therefore, a dedicated KMSAN fuzzing session is not required.",
  "NeedsKMSAN": false
}

Results:
{
  "KMSANReasoning": "The patch introduces a new debugfs interface to dump statistics for the mt7925 driver. It allocates memory for the statistics structures using `kzalloc` (which zero-initializes the memory), and local variables are explicitly initialized. The structures sent to the firmware are also fully initialized (either explicitly or via compiler zero-initialization for omitted fields). The responses from the firmware are bounds-checked against the received `skb-\u003elen` before being copied. Finally, the data is exposed to user space via `seq_printf`, which formats the values into a string buffer rather than copying raw kernel memory structures directly to user space. There is no risk of uninitialized kernel memory being leaked or used in control flow. Any potential out-of-bounds accesses (e.g., if the `skb-\u003elen` checks were flawed) would be caught by standard KASAN. Therefore, a dedicated KMSAN fuzzing session is not required.",
  "NeedsKMSAN": false
}

LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)