]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
wifi: ath12k: Support channel change stats
authorHarish Rachakonda <quic_rachakon@quicinc.com>
Thu, 26 Mar 2026 05:06:41 +0000 (10:36 +0530)
committerJeff Johnson <jeff.johnson@oss.qualcomm.com>
Wed, 8 Apr 2026 00:28:19 +0000 (17:28 -0700)
Add support to request channel change stats from the firmware through
HTT stats type 76. These stats give channel switch details like the
channel that the radio changed to, its center frequency, time taken
for the switch, chainmask details, etc.

Sample output:
echo 76 > /sys/kernel/debug/ath12k/pci-0000\:06\:00.0/mac0/htt_stats_type
cat /sys/kernel/debug/ath12k/pci-0000\:06\:00.0/mac0/htt_stats
Channel Change Timings:
|PRIMARY CHANNEL FREQ|BANDWIDTH CENTER FREQ|PHYMODE|TX_CHAINMASK|RX_CHAINMASK|SWITCH TIME(us)|INI(us)|TPC+CTL(us)|CAL(us)|MISC(us)|CTL(us)|SW PROFILE|
|                5200|                 5200|     24|          15|          15|         448850|   2410|      10546| 434593|    1071|   1100|         4|
|                5240|                 5240|     24|          15|          15|         450730|   4106|      10524| 434528|    1306|   1150|         4|
|                5180|                 5210|     26|          15|          15|         467894|   4764|      10438| 451101|    1337|   1508|         4|
|                5200|                 5200|      0|          15|          15|          13838|   2692|       1736|   8558|     686|    802|         6|
|                5180|                 5180|      0|          15|          15|          13465|   3207|        855|   8579|     578|    760|         6|
|                5200|                 5200|     24|          15|          15|         570321|   2441|      10439| 555661|    1574|    949|         4|

Note: QCC2072 and WCN7850 firmware does not support HTT stats type 76.

Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01181-QCAHKSWPL_SILICONZ-1

Signed-off-by: Harish Rachakonda <quic_rachakon@quicinc.com>
Signed-off-by: Roopni Devanathan <roopni.devanathan@oss.qualcomm.com>
Reviewed-by: Rameshkumar Sundaram <rameshkumar.sundaram@oss.qualcomm.com>
Reviewed-by: Baochen Qiang <baochen.qiang@oss.qualcomm.com>
Link: https://patch.msgid.link/20260326050641.3066562-1-roopni.devanathan@oss.qualcomm.com
Signed-off-by: Jeff Johnson <jeff.johnson@oss.qualcomm.com>
drivers/net/wireless/ath/ath12k/debugfs_htt_stats.c
drivers/net/wireless/ath/ath12k/debugfs_htt_stats.h

index 7f6ca07fb3359984acc08fee6dbb071adeba5a19..b772181a496ecc99e9090974b4223d17c1f9183a 100644 (file)
@@ -5722,6 +5722,75 @@ ath12k_htt_print_tx_hwq_stats_cmn_tlv(const void *tag_buf, u16 tag_len,
        stats_req->buf_len = len;
 }
 
+static void
+ath12k_htt_print_chan_switch_stats_tlv(const void *tag_buf, u16 tag_len,
+                                      struct debug_htt_stats_req *stats_req)
+{
+       const struct ath12k_htt_chan_switch_stats_tlv *sbuf = tag_buf;
+       u32 buf_len = ATH12K_HTT_STATS_BUF_SIZE;
+       u32 switch_freq, switch_profile;
+       u32 len = stats_req->buf_len;
+       u8 *buf = stats_req->buf;
+       u8 i;
+
+       if (tag_len < sizeof(*sbuf))
+               return;
+
+       i = min(le32_to_cpu(sbuf->switch_count), ATH12K_HTT_CHAN_SWITCH_STATS_BUF_LEN);
+       if (!i)
+               return;
+
+       len += scnprintf(buf + len, buf_len - len, "Channel Change Timings:\n");
+       len += scnprintf(buf + len, buf_len - len,
+                        "|%-20s|%-21s|%-7s|%-12s|%-12s|%-15s|",
+                        "PRIMARY CHANNEL FREQ", "BANDWIDTH CENTER FREQ", "PHYMODE",
+                        "TX_CHAINMASK", "RX_CHAINMASK", "SWITCH TIME(us)");
+       len += scnprintf(buf + len, buf_len - len,
+                        "%-7s|%-11s|%-7s|%-8s|%-7s|%-10s|\n",
+                        "INI(us)", "TPC+CTL(us)", "CAL(us)", "MISC(us)", "CTL(us)",
+                        "SW PROFILE");
+
+       /*
+        * sbuf->switch_count has the number of successful channel changes. The firmware
+        * sends the record of channel change in such a way that sbuf->chan_stats[0] will
+        * point to the channel change that occurred first and the recent channel change
+        * records will be stored in sbuf->chan_stats[9]. As and when new channel change
+        * occurs, sbuf->chan_stats[0] will be replaced by records from the next index,
+        * sbuf->chan_stats[1]. While printing the records, reverse chronological order
+        * is followed, i.e., the most recent channel change records are printed first
+        * and the oldest one, last.
+        */
+       while (i--) {
+               switch_freq = le32_to_cpu(sbuf->chan_stats[i].chan_switch_freq);
+               switch_profile = le32_to_cpu(sbuf->chan_stats[i].chan_switch_profile);
+
+               len += scnprintf(buf + len, buf_len - len,
+                                "|%20u|%21u|%7u|%12u|%12u|%15u|",
+                                u32_get_bits(switch_freq,
+                                             ATH12K_HTT_STATS_CHAN_SWITCH_BW_MHZ),
+                                u32_get_bits(switch_freq,
+                                             ATH12K_HTT_STATS_CHAN_SWITCH_BAND_FREQ),
+                                u32_get_bits(switch_profile,
+                                             ATH12K_HTT_STATS_CHAN_SWITCH_PHY_MODE),
+                                u32_get_bits(switch_profile,
+                                             ATH12K_HTT_STATS_CHAN_SWITCH_TX_CHAINMASK),
+                                u32_get_bits(switch_profile,
+                                             ATH12K_HTT_STATS_CHAN_SWITCH_RX_CHAINMASK),
+                                le32_to_cpu(sbuf->chan_stats[i].chan_switch_time));
+               len += scnprintf(buf + len, buf_len - len,
+                                "%7u|%11u|%7u|%8u|%7u|%10u|\n",
+                                le32_to_cpu(sbuf->chan_stats[i].ini_module_time),
+                                le32_to_cpu(sbuf->chan_stats[i].tpc_module_time),
+                                le32_to_cpu(sbuf->chan_stats[i].cal_module_time),
+                                le32_to_cpu(sbuf->chan_stats[i].misc_module_time),
+                                le32_to_cpu(sbuf->chan_stats[i].ctl_module_time),
+                                u32_get_bits(switch_profile,
+                                             ATH12K_HTT_STATS_CHAN_SWITCH_SW_PROFILE));
+       }
+
+       stats_req->buf_len = len;
+}
+
 static int ath12k_dbg_htt_ext_stats_parse(struct ath12k_base *ab,
                                          u16 tag, u16 len, const void *tag_buf,
                                          void *user_data)
@@ -6024,6 +6093,9 @@ static int ath12k_dbg_htt_ext_stats_parse(struct ath12k_base *ab,
        case HTT_STATS_TX_HWQ_CMN_TAG:
                ath12k_htt_print_tx_hwq_stats_cmn_tlv(tag_buf, len, stats_req);
                break;
+       case HTT_STATS_CHAN_SWITCH_STATS_TAG:
+               ath12k_htt_print_chan_switch_stats_tlv(tag_buf, len, stats_req);
+               break;
        default:
                break;
        }
index bfabe6500d44d9031f8355a135462235f2504356..82ab7b9e4db90c9fdbe05f4c165dba287ac558bb 100644 (file)
@@ -164,6 +164,7 @@ enum ath12k_dbg_htt_ext_stats_type {
        ATH12K_DBG_HTT_PDEV_MLO_IPC_STATS                       = 64,
        ATH12K_DBG_HTT_EXT_PDEV_RTT_RESP_STATS                  = 65,
        ATH12K_DBG_HTT_EXT_PDEV_RTT_INITIATOR_STATS             = 66,
+       ATH12K_DBG_HTT_EXT_CHAN_SWITCH_STATS                    = 76,
 
        /* keep this last */
        ATH12K_DBG_HTT_NUM_EXT_STATS,
@@ -267,6 +268,7 @@ enum ath12k_dbg_htt_tlv_tag {
        HTT_STATS_PDEV_RTT_HW_STATS_TAG                 = 196,
        HTT_STATS_PDEV_RTT_TBR_SELFGEN_QUEUED_STATS_TAG = 197,
        HTT_STATS_PDEV_RTT_TBR_CMD_RESULT_STATS_TAG     = 198,
+       HTT_STATS_CHAN_SWITCH_STATS_TAG                 = 213,
 
        HTT_STATS_MAX_TAG,
 };
@@ -2156,4 +2158,28 @@ struct htt_tx_hwq_stats_cmn_tlv {
        __le32 txq_timeout;
 } __packed;
 
+#define ATH12K_HTT_CHAN_SWITCH_STATS_BUF_LEN   10
+
+#define ATH12K_HTT_STATS_CHAN_SWITCH_BW_MHZ            GENMASK(15, 0)
+#define ATH12K_HTT_STATS_CHAN_SWITCH_BAND_FREQ         GENMASK(31, 16)
+#define ATH12K_HTT_STATS_CHAN_SWITCH_PHY_MODE          GENMASK(7, 0)
+#define ATH12K_HTT_STATS_CHAN_SWITCH_TX_CHAINMASK      GENMASK(15, 8)
+#define ATH12K_HTT_STATS_CHAN_SWITCH_RX_CHAINMASK      GENMASK(23, 16)
+#define ATH12K_HTT_STATS_CHAN_SWITCH_SW_PROFILE                GENMASK(31, 24)
+
+struct ath12k_htt_chan_switch_stats_tlv {
+       struct {
+               __le32 chan_switch_freq;
+               __le32 chan_switch_profile;
+               __le32 chan_switch_time;
+               __le32 cal_module_time;
+               __le32 ini_module_time;
+               __le32 tpc_module_time;
+               __le32 misc_module_time;
+               __le32 ctl_module_time;
+               __le32 reserved;
+       } chan_stats[ATH12K_HTT_CHAN_SWITCH_STATS_BUF_LEN];
+       __le32 switch_count; /* shows how many channel changes have occurred */
+} __packed;
+
 #endif