Commit e367c924 authored by Ramya Gnanasekar's avatar Ramya Gnanasekar Committed by Jeff Johnson
Browse files

wifi: ath12k: Request vdev stats from firmware



Add support to request and print vdev stats from firmware through WMI.

Sample output:
-------------
cat /sys/kernel/debug/ath12k/pci-0000\:06\:00.0/mac0/fw_stats/vdev_stats

             ath12k VDEV stats
             =================

                       VDEV ID 0
              VDEV MAC address 00:03:7f:6c:9c:1a
                    beacon snr 96
                      data snr 255
                 num rx frames 0
                  num rts fail 0
               num rts success 0
                    num rx err 0
                num rx discard 0
              num tx not acked 0
            num tx frames [00] 0
            num tx frames [01] 0
            num tx frames [02] 0
            num tx frames [03] 2
    num tx frames retries [00] 0
    num tx frames retries [01] 0
    num tx frames retries [02] 0
    num tx frames retries [03] 0
   num tx frames failures [00] 0
   num tx frames failures [01] 0
   num tx frames failures [02] 0
   num tx frames failures [03] 0
          tx rate history [00] 0x00000000
          tx rate history [01] 0x00000000
          tx rate history [02] 0x00000000
          tx rate history [03] 0x00000000
          tx rate history [04] 0x00000000
          tx rate history [05] 0x00000000
          tx rate history [06] 0x00000000
          tx rate history [07] 0x00000000
          tx rate history [08] 0x00000000
          tx rate history [09] 0x00000000
      beacon rssi history [00] 0
      beacon rssi history [01] 0
      beacon rssi history [02] 0
      beacon rssi history [03] 0
      beacon rssi history [04] 0
      beacon rssi history [05] 0
      beacon rssi history [06] 0
      beacon rssi history [07] 0
      beacon rssi history [08] 0
      beacon rssi history [09] 0

Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.0.1-00029-QCAHKSWPL_SILICONZ-1
Tested-on: WCN7850 hw2.0 PCI WLAN.HMT.1.0.c5-00481-QCAHMTSWPL_V1.0_V2.0_SILICONZ-3

Signed-off-by: default avatarRamya Gnanasekar <ramya.gnanasekar@oss.qualcomm.com>
Reviewed-by: default avatarAditya Kumar Singh <aditya.kumar.singh@oss.qualcomm.com>
Link: https://patch.msgid.link/20250124185330.1244585-2-ramya.gnanasekar@oss.qualcomm.com


Signed-off-by: default avatarJeff Johnson <jeff.johnson@oss.qualcomm.com>
parent 1b24394e
Loading
Loading
Loading
Loading
+21 −0
Original line number Diff line number Diff line
@@ -557,6 +557,7 @@ struct ath12k_fw_stats {
	struct list_head pdevs;
	struct list_head vdevs;
	struct list_head bcn;
	bool fw_stats_done;
};

struct ath12k_dbg_htt_stats {
@@ -728,6 +729,7 @@ struct ath12k {
	struct completion mlo_setup_done;
	u32 mlo_setup_status;
	u8 ftm_msgref;
	struct ath12k_fw_stats fw_stats;
};

struct ath12k_hw {
@@ -1078,6 +1080,25 @@ struct ath12k_pdev_map {
	u8 pdev_idx;
};

struct ath12k_fw_stats_vdev {
	struct list_head list;

	u32 vdev_id;
	u32 beacon_snr;
	u32 data_snr;
	u32 num_tx_frames[WLAN_MAX_AC];
	u32 num_rx_frames;
	u32 num_tx_frames_retries[WLAN_MAX_AC];
	u32 num_tx_frames_failures[WLAN_MAX_AC];
	u32 num_rts_fail;
	u32 num_rts_success;
	u32 num_rx_err;
	u32 num_rx_discard;
	u32 num_tx_not_acked;
	u32 tx_rate_history[MAX_TX_RATE_VALUES];
	u32 beacon_rssi_history[MAX_TX_RATE_VALUES];
};

int ath12k_core_qmi_firmware_ready(struct ath12k_base *ab);
int ath12k_core_pre_init(struct ath12k_base *ab);
int ath12k_core_init(struct ath12k_base *ath12k);
+196 −1
Original line number Diff line number Diff line
// SPDX-License-Identifier: BSD-3-Clause-Clear
/*
 * Copyright (c) 2018-2021 The Linux Foundation. All rights reserved.
 * Copyright (c) 2021-2024 Qualcomm Innovation Center, Inc. All rights reserved.
 * Copyright (c) 2021-2025 Qualcomm Innovation Center, Inc. All rights reserved.
 */

#include "core.h"
#include "debug.h"
#include "debugfs.h"
#include "debugfs_htt_stats.h"

@@ -68,6 +69,199 @@ void ath12k_debugfs_soc_destroy(struct ath12k_base *ab)
	 */
}

static void ath12k_fw_stats_vdevs_free(struct list_head *head)
{
	struct ath12k_fw_stats_vdev *i, *tmp;

	list_for_each_entry_safe(i, tmp, head, list) {
		list_del(&i->list);
		kfree(i);
	}
}

void ath12k_debugfs_fw_stats_reset(struct ath12k *ar)
{
	spin_lock_bh(&ar->data_lock);
	ar->fw_stats.fw_stats_done = false;
	ath12k_fw_stats_vdevs_free(&ar->fw_stats.vdevs);
	spin_unlock_bh(&ar->data_lock);
}

static int ath12k_debugfs_fw_stats_request(struct ath12k *ar,
					   struct ath12k_fw_stats_req_params *param)
{
	struct ath12k_base *ab = ar->ab;
	unsigned long timeout, time_left;
	int ret;

	lockdep_assert_wiphy(ath12k_ar_to_hw(ar)->wiphy);

	/* FW stats can get split when exceeding the stats data buffer limit.
	 * In that case, since there is no end marking for the back-to-back
	 * received 'update stats' event, we keep a 3 seconds timeout in case,
	 * fw_stats_done is not marked yet
	 */
	timeout = jiffies + msecs_to_jiffies(3 * 1000);

	ath12k_debugfs_fw_stats_reset(ar);

	reinit_completion(&ar->fw_stats_complete);

	ret = ath12k_wmi_send_stats_request_cmd(ar, param->stats_id,
						param->vdev_id, param->pdev_id);

	if (ret) {
		ath12k_warn(ab, "could not request fw stats (%d)\n",
			    ret);
		return ret;
	}

	time_left = wait_for_completion_timeout(&ar->fw_stats_complete,
						1 * HZ);
	/* If the wait timed out, return -ETIMEDOUT */
	if (!time_left)
		return -ETIMEDOUT;

	/* Firmware sends WMI_UPDATE_STATS_EVENTID back-to-back
	 * when stats data buffer limit is reached. fw_stats_complete
	 * is completed once host receives first event from firmware, but
	 * still end might not be marked in the TLV.
	 * Below loop is to confirm that firmware completed sending all the event
	 * and fw_stats_done is marked true when end is marked in the TLV
	 */
	for (;;) {
		if (time_after(jiffies, timeout))
			break;

		spin_lock_bh(&ar->data_lock);
		if (ar->fw_stats.fw_stats_done) {
			spin_unlock_bh(&ar->data_lock);
			break;
		}
		spin_unlock_bh(&ar->data_lock);
	}
	return 0;
}

void
ath12k_debugfs_fw_stats_process(struct ath12k *ar,
				struct ath12k_fw_stats *stats)
{
	struct ath12k_base *ab = ar->ab;
	struct ath12k_pdev *pdev;
	bool is_end;
	static unsigned int num_vdev;
	size_t total_vdevs_started = 0;
	int i;

	if (stats->stats_id == WMI_REQUEST_VDEV_STAT) {
		if (list_empty(&stats->vdevs)) {
			ath12k_warn(ab, "empty vdev stats");
			return;
		}
		/* FW sends all the active VDEV stats irrespective of PDEV,
		 * hence limit until the count of all VDEVs started
		 */
		rcu_read_lock();
		for (i = 0; i < ab->num_radios; i++) {
			pdev = rcu_dereference(ab->pdevs_active[i]);
			if (pdev && pdev->ar)
				total_vdevs_started += pdev->ar->num_started_vdevs;
		}
		rcu_read_unlock();

		is_end = ((++num_vdev) == total_vdevs_started);

		list_splice_tail_init(&stats->vdevs,
				      &ar->fw_stats.vdevs);

		if (is_end) {
			ar->fw_stats.fw_stats_done = true;
			num_vdev = 0;
		}
		return;
	}
}

static int ath12k_open_vdev_stats(struct inode *inode, struct file *file)
{
	struct ath12k *ar = inode->i_private;
	struct ath12k_fw_stats_req_params param;
	struct ath12k_hw *ah = ath12k_ar_to_ah(ar);
	int ret;

	guard(wiphy)(ath12k_ar_to_hw(ar)->wiphy);

	if (!ah)
		return -ENETDOWN;

	if (ah->state != ATH12K_HW_STATE_ON)
		return -ENETDOWN;

	void *buf __free(kfree) = kzalloc(ATH12K_FW_STATS_BUF_SIZE, GFP_ATOMIC);
	if (!buf)
		return -ENOMEM;

	param.pdev_id = ath12k_mac_get_target_pdev_id(ar);
	/* VDEV stats is always sent for all active VDEVs from FW */
	param.vdev_id = 0;
	param.stats_id = WMI_REQUEST_VDEV_STAT;

	ret = ath12k_debugfs_fw_stats_request(ar, &param);
	if (ret) {
		ath12k_warn(ar->ab, "failed to request fw vdev stats: %d\n", ret);
		return ret;
	}

	ath12k_wmi_fw_stats_dump(ar, &ar->fw_stats, param.stats_id,
				 buf);

	file->private_data = no_free_ptr(buf);

	return 0;
}

static int ath12k_release_vdev_stats(struct inode *inode, struct file *file)
{
	kfree(file->private_data);

	return 0;
}

static ssize_t ath12k_read_vdev_stats(struct file *file,
				      char __user *user_buf,
				      size_t count, loff_t *ppos)
{
	const char *buf = file->private_data;
	size_t len = strlen(buf);

	return simple_read_from_buffer(user_buf, count, ppos, buf, len);
}

static const struct file_operations fops_vdev_stats = {
	.open = ath12k_open_vdev_stats,
	.release = ath12k_release_vdev_stats,
	.read = ath12k_read_vdev_stats,
	.owner = THIS_MODULE,
	.llseek = default_llseek,
};

static
void ath12k_debugfs_fw_stats_register(struct ath12k *ar)
{
	struct dentry *fwstats_dir = debugfs_create_dir("fw_stats",
							ar->debug.debugfs_pdev);

	/* all stats debugfs files created are under "fw_stats" directory
	 * created per PDEV
	 */
	debugfs_create_file("vdev_stats", 0600, fwstats_dir, ar,
			    &fops_vdev_stats);

	INIT_LIST_HEAD(&ar->fw_stats.vdevs);
	init_completion(&ar->fw_stats_complete);
}

void ath12k_debugfs_register(struct ath12k *ar)
{
	struct ath12k_base *ab = ar->ab;
@@ -92,6 +286,7 @@ void ath12k_debugfs_register(struct ath12k *ar)
	}

	ath12k_debugfs_htt_stats_register(ar);
	ath12k_debugfs_fw_stats_register(ar);
}

void ath12k_debugfs_unregister(struct ath12k *ar)
+12 −1
Original line number Diff line number Diff line
/* SPDX-License-Identifier: BSD-3-Clause-Clear */
/*
 * Copyright (c) 2018-2021 The Linux Foundation. All rights reserved.
 * Copyright (c) 2021-2024 Qualcomm Innovation Center, Inc. All rights reserved.
 * Copyright (c) 2021-2025 Qualcomm Innovation Center, Inc. All rights reserved.
 */

#ifndef _ATH12K_DEBUGFS_H_
@@ -12,6 +12,9 @@ void ath12k_debugfs_soc_create(struct ath12k_base *ab);
void ath12k_debugfs_soc_destroy(struct ath12k_base *ab);
void ath12k_debugfs_register(struct ath12k *ar);
void ath12k_debugfs_unregister(struct ath12k *ar);
void ath12k_debugfs_fw_stats_process(struct ath12k *ar,
				     struct ath12k_fw_stats *stats);
void ath12k_debugfs_fw_stats_reset(struct ath12k *ar);
#else
static inline void ath12k_debugfs_soc_create(struct ath12k_base *ab)
{
@@ -29,6 +32,14 @@ static inline void ath12k_debugfs_unregister(struct ath12k *ar)
{
}

static inline void ath12k_debugfs_fw_stats_process(struct ath12k *ar,
						   struct ath12k_fw_stats *stats)
{
}

static inline void ath12k_debugfs_fw_stats_reset(struct ath12k *ar)
{
}
#endif /* CONFIG_ATH12K_DEBUGFS */

#endif /* _ATH12K_DEBUGFS_H_ */
+155 −0
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@
#include <linux/time.h>
#include <linux/of.h>
#include "core.h"
#include "debugfs.h"
#include "debug.h"
#include "mac.h"
#include "hw.h"
@@ -6853,12 +6854,156 @@ static void ath12k_peer_assoc_conf_event(struct ath12k_base *ab, struct sk_buff
	rcu_read_unlock();
}

static void
ath12k_wmi_fw_vdev_stats_dump(struct ath12k *ar,
			      struct ath12k_fw_stats *fw_stats,
			      char *buf, u32 *length)
{
	const struct ath12k_fw_stats_vdev *vdev;
	u32 buf_len = ATH12K_FW_STATS_BUF_SIZE;
	struct ath12k_link_vif *arvif;
	u32 len = *length;
	u8 *vif_macaddr;
	int i;

	len += scnprintf(buf + len, buf_len - len, "\n");
	len += scnprintf(buf + len, buf_len - len, "%30s\n",
			 "ath12k VDEV stats");
	len += scnprintf(buf + len, buf_len - len, "%30s\n\n",
			 "=================");

	list_for_each_entry(vdev, &fw_stats->vdevs, list) {
		arvif = ath12k_mac_get_arvif(ar, vdev->vdev_id);
		if (!arvif)
			continue;
		vif_macaddr = arvif->ahvif->vif->addr;

		len += scnprintf(buf + len, buf_len - len, "%30s %u\n",
				 "VDEV ID", vdev->vdev_id);
		len += scnprintf(buf + len, buf_len - len, "%30s %pM\n",
				 "VDEV MAC address", vif_macaddr);
		len += scnprintf(buf + len, buf_len - len, "%30s %u\n",
				 "beacon snr", vdev->beacon_snr);
		len += scnprintf(buf + len, buf_len - len, "%30s %u\n",
				 "data snr", vdev->data_snr);
		len += scnprintf(buf + len, buf_len - len, "%30s %u\n",
				 "num rx frames", vdev->num_rx_frames);
		len += scnprintf(buf + len, buf_len - len, "%30s %u\n",
				 "num rts fail", vdev->num_rts_fail);
		len += scnprintf(buf + len, buf_len - len, "%30s %u\n",
				 "num rts success", vdev->num_rts_success);
		len += scnprintf(buf + len, buf_len - len, "%30s %u\n",
				 "num rx err", vdev->num_rx_err);
		len += scnprintf(buf + len, buf_len - len, "%30s %u\n",
				 "num rx discard", vdev->num_rx_discard);
		len += scnprintf(buf + len, buf_len - len, "%30s %u\n",
				 "num tx not acked", vdev->num_tx_not_acked);

		for (i = 0 ; i < WLAN_MAX_AC; i++)
			len += scnprintf(buf + len, buf_len - len,
					"%25s [%02d] %u\n",
					"num tx frames", i,
					vdev->num_tx_frames[i]);

		for (i = 0 ; i < WLAN_MAX_AC; i++)
			len += scnprintf(buf + len, buf_len - len,
					"%25s [%02d] %u\n",
					"num tx frames retries", i,
					vdev->num_tx_frames_retries[i]);

		for (i = 0 ; i < WLAN_MAX_AC; i++)
			len += scnprintf(buf + len, buf_len - len,
					"%25s [%02d] %u\n",
					"num tx frames failures", i,
					vdev->num_tx_frames_failures[i]);

		for (i = 0 ; i < MAX_TX_RATE_VALUES; i++)
			len += scnprintf(buf + len, buf_len - len,
					"%25s [%02d] 0x%08x\n",
					"tx rate history", i,
					vdev->tx_rate_history[i]);
		for (i = 0 ; i < MAX_TX_RATE_VALUES; i++)
			len += scnprintf(buf + len, buf_len - len,
					"%25s [%02d] %u\n",
					"beacon rssi history", i,
					vdev->beacon_rssi_history[i]);

		len += scnprintf(buf + len, buf_len - len, "\n");
		*length = len;
	}
}

void ath12k_wmi_fw_stats_dump(struct ath12k *ar,
			      struct ath12k_fw_stats *fw_stats,
			      u32 stats_id, char *buf)
{
	u32 len = 0;
	u32 buf_len = ATH12K_FW_STATS_BUF_SIZE;

	spin_lock_bh(&ar->data_lock);

	switch (stats_id) {
	case WMI_REQUEST_VDEV_STAT:
		ath12k_wmi_fw_vdev_stats_dump(ar, fw_stats, buf, &len);
		break;
	default:
		break;
	}

	spin_unlock_bh(&ar->data_lock);

	if (len >= buf_len)
		buf[len - 1] = 0;
	else
		buf[len] = 0;

	ath12k_debugfs_fw_stats_reset(ar);
}

static void
ath12k_wmi_pull_vdev_stats(const struct wmi_vdev_stats_params *src,
			   struct ath12k_fw_stats_vdev *dst)
{
	int i;

	dst->vdev_id = le32_to_cpu(src->vdev_id);
	dst->beacon_snr = le32_to_cpu(src->beacon_snr);
	dst->data_snr = le32_to_cpu(src->data_snr);
	dst->num_rx_frames = le32_to_cpu(src->num_rx_frames);
	dst->num_rts_fail = le32_to_cpu(src->num_rts_fail);
	dst->num_rts_success = le32_to_cpu(src->num_rts_success);
	dst->num_rx_err = le32_to_cpu(src->num_rx_err);
	dst->num_rx_discard = le32_to_cpu(src->num_rx_discard);
	dst->num_tx_not_acked = le32_to_cpu(src->num_tx_not_acked);

	for (i = 0; i < WLAN_MAX_AC; i++)
		dst->num_tx_frames[i] =
			le32_to_cpu(src->num_tx_frames[i]);

	for (i = 0; i < WLAN_MAX_AC; i++)
		dst->num_tx_frames_retries[i] =
			le32_to_cpu(src->num_tx_frames_retries[i]);

	for (i = 0; i < WLAN_MAX_AC; i++)
		dst->num_tx_frames_failures[i] =
			le32_to_cpu(src->num_tx_frames_failures[i]);

	for (i = 0; i < MAX_TX_RATE_VALUES; i++)
		dst->tx_rate_history[i] =
			le32_to_cpu(src->tx_rate_history[i]);

	for (i = 0; i < MAX_TX_RATE_VALUES; i++)
		dst->beacon_rssi_history[i] =
			le32_to_cpu(src->beacon_rssi_history[i]);
}

static int ath12k_wmi_tlv_fw_stats_data_parse(struct ath12k_base *ab,
					      struct wmi_tlv_fw_stats_parse *parse,
					      const void *ptr,
					      u16 len)
{
	const struct wmi_stats_event *ev = parse->ev;
	struct ath12k_fw_stats stats = {0};
	struct ath12k *ar;
	struct ath12k_link_vif *arvif;
	struct ieee80211_sta *sta;
@@ -6867,6 +7012,8 @@ static int ath12k_wmi_tlv_fw_stats_data_parse(struct ath12k_base *ab,
	int i, ret = 0;
	const void *data = ptr;

	INIT_LIST_HEAD(&stats.vdevs);

	if (!ev) {
		ath12k_warn(ab, "failed to fetch update stats ev");
		return -EPROTO;
@@ -6884,6 +7031,7 @@ static int ath12k_wmi_tlv_fw_stats_data_parse(struct ath12k_base *ab,

	for (i = 0; i < le32_to_cpu(ev->num_vdev_stats); i++) {
		const struct wmi_vdev_stats_params *src;
		struct ath12k_fw_stats_vdev *dst;

		src = data;
		if (len < sizeof(*src)) {
@@ -6912,9 +7060,16 @@ static int ath12k_wmi_tlv_fw_stats_data_parse(struct ath12k_base *ab,

		data += sizeof(*src);
		len -= sizeof(*src);
		dst = kzalloc(sizeof(*dst), GFP_ATOMIC);
		if (!dst)
			continue;
		ath12k_wmi_pull_vdev_stats(src, dst);
		stats.stats_id = WMI_REQUEST_VDEV_STAT;
		list_add_tail(&dst->list, &stats.vdevs);
	}

	complete(&ar->fw_stats_complete);
	ath12k_debugfs_fw_stats_process(ar, &stats);
exit:
	rcu_read_unlock();
	return ret;
+10 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@
struct ath12k_base;
struct ath12k;
struct ath12k_link_vif;
struct ath12k_fw_stats;

/* There is no signed version of __le32, so for a temporary solution come
 * up with our own version. The idea is from fs/ntfs/endian.h.
@@ -5695,6 +5696,12 @@ struct wmi_vdev_stats_params {
	__le32 beacon_rssi_history[MAX_TX_RATE_VALUES];
} __packed;

struct ath12k_fw_stats_req_params {
	u32 stats_id;
	u32 vdev_id;
	u32 pdev_id;
};

void ath12k_wmi_init_qcn9274(struct ath12k_base *ab,
			     struct ath12k_wmi_resource_config_arg *config);
void ath12k_wmi_init_wcn7850(struct ath12k_base *ab,
@@ -5876,5 +5883,8 @@ int ath12k_wmi_sta_keepalive(struct ath12k *ar,
int ath12k_wmi_mlo_setup(struct ath12k *ar, struct wmi_mlo_setup_arg *mlo_params);
int ath12k_wmi_mlo_ready(struct ath12k *ar);
int ath12k_wmi_mlo_teardown(struct ath12k *ar);
void ath12k_wmi_fw_stats_dump(struct ath12k *ar,
			      struct ath12k_fw_stats *fw_stats, u32 stats_id,
			      char *buf);

#endif