Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3255290e authored by Dedy Lansky's avatar Dedy Lansky Committed by Maya Erez
Browse files

wil6210: add support for link statistics



Driver can request FW to report link statistics using
WMI_LINK_STATS_CMDID.
FW will report statistics with WMI_LINK_STATS_EVENTID.
Two categories of statistics defined: basic and global.

New "link_stats" debugfs is used for requesting basic statistics
report (write) and for reading the basic statistics (read).
"link_stats_global" debugfs is used for requesting and reading the
global statistics.

Change-Id: I45575baae44ee32fbe0ed655e0a36e63c2364142
Signed-off-by: default avatarDedy Lansky <dlansky@codeaurora.org>
Signed-off-by: default avatarMaya Erez <merez@codeaurora.org>
Signed-off-by: default avatarKalle Valo <kvalo@codeaurora.org>
Git-commit: 0c936b3c96337c3fd5ad4951ca7bdc54fa578a02
Git-repo: git://git.kernel.org/pub/scm/linux/kernel/git/kvalo/ath.git


Signed-off-by: default avatarMaya Erez <merez@codeaurora.org>
parent 9c5104a7
Loading
Loading
Loading
Loading
+219 −0
Original line number Diff line number Diff line
@@ -1961,6 +1961,223 @@ static const struct file_operations fops_tx_latency = {
	.llseek		= seq_lseek,
};

static void wil_link_stats_print_basic(struct wil6210_vif *vif,
				       struct seq_file *s,
				       struct wmi_link_stats_basic *basic)
{
	char per[5] = "?";

	if (basic->per_average != 0xff)
		snprintf(per, sizeof(per), "%d%%", basic->per_average);

	seq_printf(s, "CID %d {\n"
		   "\tTxMCS %d TxTpt %d\n"
		   "\tGoodput(rx:tx) %d:%d\n"
		   "\tRxBcastFrames %d\n"
		   "\tRSSI %d SQI %d SNR %d PER %s\n"
		   "\tRx RFC %d Ant num %d\n"
		   "\tSectors(rx:tx) my %d:%d peer %d:%d\n"
		   "}\n",
		   basic->cid,
		   basic->bf_mcs, le32_to_cpu(basic->tx_tpt),
		   le32_to_cpu(basic->rx_goodput),
		   le32_to_cpu(basic->tx_goodput),
		   le32_to_cpu(basic->rx_bcast_frames),
		   basic->rssi, basic->sqi, basic->snr, per,
		   basic->selected_rfc, basic->rx_effective_ant_num,
		   basic->my_rx_sector, basic->my_tx_sector,
		   basic->other_rx_sector, basic->other_tx_sector);
}

static void wil_link_stats_print_global(struct wil6210_priv *wil,
					struct seq_file *s,
					struct wmi_link_stats_global *global)
{
	seq_printf(s, "Frames(rx:tx) %d:%d\n"
		   "BA Frames(rx:tx) %d:%d\n"
		   "Beacons %d\n"
		   "Rx Errors (MIC:CRC) %d:%d\n"
		   "Tx Errors (no ack) %d\n",
		   le32_to_cpu(global->rx_frames),
		   le32_to_cpu(global->tx_frames),
		   le32_to_cpu(global->rx_ba_frames),
		   le32_to_cpu(global->tx_ba_frames),
		   le32_to_cpu(global->tx_beacons),
		   le32_to_cpu(global->rx_mic_errors),
		   le32_to_cpu(global->rx_crc_errors),
		   le32_to_cpu(global->tx_fail_no_ack));
}

static void wil_link_stats_debugfs_show_vif(struct wil6210_vif *vif,
					    struct seq_file *s)
{
	struct wil6210_priv *wil = vif_to_wil(vif);
	struct wmi_link_stats_basic *stats;
	int i;

	if (!vif->fw_stats_ready) {
		seq_puts(s, "no statistics\n");
		return;
	}

	seq_printf(s, "TSF %lld\n", vif->fw_stats_tsf);
	for (i = 0; i < ARRAY_SIZE(wil->sta); i++) {
		if (wil->sta[i].status == wil_sta_unused)
			continue;
		if (wil->sta[i].mid != vif->mid)
			continue;

		stats = &wil->sta[i].fw_stats_basic;
		wil_link_stats_print_basic(vif, s, stats);
	}
}

static int wil_link_stats_debugfs_show(struct seq_file *s, void *data)
{
	struct wil6210_priv *wil = s->private;
	struct wil6210_vif *vif;
	int i, rc;

	rc = mutex_lock_interruptible(&wil->vif_mutex);
	if (rc)
		return rc;

	/* iterate over all MIDs and show per-cid statistics. Then show the
	 * global statistics
	 */
	for (i = 0; i < wil->max_vifs; i++) {
		vif = wil->vifs[i];

		seq_printf(s, "MID %d ", i);
		if (!vif) {
			seq_puts(s, "unused\n");
			continue;
		}

		wil_link_stats_debugfs_show_vif(vif, s);
	}

	mutex_unlock(&wil->vif_mutex);

	return 0;
}

static int wil_link_stats_seq_open(struct inode *inode, struct file *file)
{
	return single_open(file, wil_link_stats_debugfs_show, inode->i_private);
}

static ssize_t wil_link_stats_write(struct file *file, const char __user *buf,
				    size_t len, loff_t *ppos)
{
	struct seq_file *s = file->private_data;
	struct wil6210_priv *wil = s->private;
	int cid, interval, rc, i;
	struct wil6210_vif *vif;
	char *kbuf = kmalloc(len + 1, GFP_KERNEL);

	if (!kbuf)
		return -ENOMEM;

	rc = simple_write_to_buffer(kbuf, len, ppos, buf, len);
	if (rc != len) {
		kfree(kbuf);
		return rc >= 0 ? -EIO : rc;
	}

	kbuf[len] = '\0';
	/* specify cid (use -1 for all cids) and snapshot interval in ms */
	rc = sscanf(kbuf, "%d %d", &cid, &interval);
	kfree(kbuf);
	if (rc < 0)
		return rc;
	if (rc < 2 || interval < 0)
		return -EINVAL;

	wil_info(wil, "request link statistics, cid %d interval %d\n",
		 cid, interval);

	rc = mutex_lock_interruptible(&wil->vif_mutex);
	if (rc)
		return rc;

	for (i = 0; i < wil->max_vifs; i++) {
		vif = wil->vifs[i];
		if (!vif)
			continue;

		rc = wmi_link_stats_cfg(vif, WMI_LINK_STATS_TYPE_BASIC,
					(cid == -1 ? 0xff : cid), interval);
		if (rc)
			wil_err(wil, "link statistics failed for mid %d\n", i);
	}
	mutex_unlock(&wil->vif_mutex);

	return len;
}

static const struct file_operations fops_link_stats = {
	.open		= wil_link_stats_seq_open,
	.release	= single_release,
	.read		= seq_read,
	.write		= wil_link_stats_write,
	.llseek		= seq_lseek,
};

static int
wil_link_stats_global_debugfs_show(struct seq_file *s, void *data)
{
	struct wil6210_priv *wil = s->private;

	if (!wil->fw_stats_global.ready)
		return 0;

	seq_printf(s, "TSF %lld\n", wil->fw_stats_global.tsf);
	wil_link_stats_print_global(wil, s, &wil->fw_stats_global.stats);

	return 0;
}

static int
wil_link_stats_global_seq_open(struct inode *inode, struct file *file)
{
	return single_open(file, wil_link_stats_global_debugfs_show,
			   inode->i_private);
}

static ssize_t
wil_link_stats_global_write(struct file *file, const char __user *buf,
			    size_t len, loff_t *ppos)
{
	struct seq_file *s = file->private_data;
	struct wil6210_priv *wil = s->private;
	int interval, rc;
	struct wil6210_vif *vif = ndev_to_vif(wil->main_ndev);

	/* specify snapshot interval in ms */
	rc = kstrtoint_from_user(buf, len, 0, &interval);
	if (rc || interval < 0) {
		wil_err(wil, "Invalid argument\n");
		return -EINVAL;
	}

	wil_info(wil, "request global link stats, interval %d\n", interval);

	rc = wmi_link_stats_cfg(vif, WMI_LINK_STATS_TYPE_GLOBAL, 0, interval);
	if (rc)
		wil_err(wil, "global link stats failed %d\n", rc);

	return rc ? rc : len;
}

static const struct file_operations fops_link_stats_global = {
	.open		= wil_link_stats_global_seq_open,
	.release	= single_release,
	.read		= seq_read,
	.write		= wil_link_stats_global_write,
	.llseek		= seq_lseek,
};

static ssize_t wil_read_file_led_cfg(struct file *file, char __user *user_buf,
				     size_t count, loff_t *ppos)
{
@@ -2296,6 +2513,8 @@ static const struct {
	{"status_msg",	0444,		&fops_status_msg},
	{"rx_buff_mgmt",	0444,	&fops_rx_buff_mgmt},
	{"tx_latency",	0644,		&fops_tx_latency},
	{"link_stats",	0644,		&fops_link_stats},
	{"link_stats_global",	0644,	&fops_link_stats_global},
};

static void wil6210_debugfs_init_files(struct wil6210_priv *wil,
+12 −0
Original line number Diff line number Diff line
@@ -735,6 +735,7 @@ struct wil_sta_info {
	 * tx_latency_res is configured from "tx_latency" debug-fs.
	 */
	u64 *tx_latency_bins;
	struct wmi_link_stats_basic fw_stats_basic;
	/* Rx BACK */
	struct wil_tid_ampdu_rx *tid_rx[WIL_STA_TID_NUM];
	spinlock_t tid_rx_lock; /* guarding tid_rx array */
@@ -852,6 +853,8 @@ struct wil6210_vif {
	struct mutex probe_client_mutex; /* protect @probe_client_pending */
	struct work_struct probe_client_worker;
	int net_queue_stopped; /* netif_tx_stop_all_queues invoked */
	bool fw_stats_ready; /* per-cid statistics are ready inside sta_info */
	u64 fw_stats_tsf; /* measurement timestamp */
};

/**
@@ -879,6 +882,12 @@ struct wil_rx_buff_mgmt {
	unsigned long free_list_empty_cnt; /* statistics */
};

struct wil_fw_stats_global {
	bool ready;
	u64 tsf; /* measurement timestamp */
	struct wmi_link_stats_global stats;
};

struct wil6210_priv {
	struct pci_dev *pdev;
	u32 bar_size;
@@ -1035,6 +1044,8 @@ struct wil6210_priv {

	u32 max_agg_wsize;
	u32 max_ampdu_size;

	struct wil_fw_stats_global fw_stats_global;
};

#define wil_to_wiphy(i) (i->wiphy)
@@ -1240,6 +1251,7 @@ int wmi_get_tt_cfg(struct wil6210_priv *wil, struct wmi_tt_data *tt_data);
int wmi_port_allocate(struct wil6210_priv *wil, u8 mid,
		      const u8 *mac, enum nl80211_iftype iftype);
int wmi_port_delete(struct wil6210_priv *wil, u8 mid);
int wmi_link_stats_cfg(struct wil6210_vif *vif, u32 type, u8 cid, u32 interval);
int wil_addba_rx_request(struct wil6210_priv *wil, u8 mid,
			 u8 cidxtid, u8 dialog_token, __le16 ba_param_set,
			 __le16 ba_timeout, __le16 ba_seq_ctrl);
+165 −0
Original line number Diff line number Diff line
@@ -465,6 +465,8 @@ static const char *cmdid2name(u16 cmdid)
		return "WMI_BCAST_DESC_RING_ADD_CMD";
	case WMI_CFG_DEF_RX_OFFLOAD_CMDID:
		return "WMI_CFG_DEF_RX_OFFLOAD_CMD";
	case WMI_LINK_STATS_CMDID:
		return "WMI_LINK_STATS_CMD";
	default:
		return "Untracked CMD";
	}
@@ -599,6 +601,10 @@ static const char *eventid2name(u16 eventid)
		return "WMI_RX_DESC_RING_CFG_DONE_EVENT";
	case WMI_CFG_DEF_RX_OFFLOAD_DONE_EVENTID:
		return "WMI_CFG_DEF_RX_OFFLOAD_DONE_EVENT";
	case WMI_LINK_STATS_CONFIG_DONE_EVENTID:
		return "WMI_LINK_STATS_CONFIG_DONE_EVENT";
	case WMI_LINK_STATS_EVENTID:
		return "WMI_LINK_STATS_EVENT";
	default:
		return "Untracked EVENT";
	}
@@ -1372,6 +1378,130 @@ wmi_evt_sched_scan_result(struct wil6210_vif *vif, int id, void *d, int len)
	cfg80211_sched_scan_results(wiphy, 0);
}

static void wil_link_stats_store_basic(struct wil6210_vif *vif,
				       struct wmi_link_stats_basic *basic)
{
	struct wil6210_priv *wil = vif_to_wil(vif);
	u8 cid = basic->cid;
	struct wil_sta_info *sta;

	if (cid < 0 || cid >= WIL6210_MAX_CID) {
		wil_err(wil, "invalid cid %d\n", cid);
		return;
	}

	sta = &wil->sta[cid];
	sta->fw_stats_basic = *basic;
}

static void wil_link_stats_store_global(struct wil6210_vif *vif,
					struct wmi_link_stats_global *global)
{
	struct wil6210_priv *wil = vif_to_wil(vif);

	wil->fw_stats_global.stats = *global;
}

static void wmi_link_stats_parse(struct wil6210_vif *vif, u64 tsf,
				 bool has_next, void *payload,
				 size_t payload_size)
{
	struct wil6210_priv *wil = vif_to_wil(vif);
	size_t hdr_size = sizeof(struct wmi_link_stats_record);
	size_t stats_size, record_size, expected_size;
	struct wmi_link_stats_record *hdr;

	if (payload_size < hdr_size) {
		wil_err(wil, "link stats wrong event size %zu\n", payload_size);
		return;
	}

	while (payload_size >= hdr_size) {
		hdr = payload;
		stats_size = le16_to_cpu(hdr->record_size);
		record_size = hdr_size + stats_size;

		if (payload_size < record_size) {
			wil_err(wil, "link stats payload ended unexpectedly, size %zu < %zu\n",
				payload_size, record_size);
			return;
		}

		switch (hdr->record_type_id) {
		case WMI_LINK_STATS_TYPE_BASIC:
			expected_size = sizeof(struct wmi_link_stats_basic);
			if (stats_size < expected_size) {
				wil_err(wil, "link stats invalid basic record size %zu < %zu\n",
					stats_size, expected_size);
				return;
			}
			if (vif->fw_stats_ready) {
				/* clean old statistics */
				vif->fw_stats_tsf = 0;
				vif->fw_stats_ready = 0;
			}

			wil_link_stats_store_basic(vif, payload + hdr_size);

			if (!has_next) {
				vif->fw_stats_tsf = tsf;
				vif->fw_stats_ready = 1;
			}

			break;
		case WMI_LINK_STATS_TYPE_GLOBAL:
			expected_size = sizeof(struct wmi_link_stats_global);
			if (stats_size < sizeof(struct wmi_link_stats_global)) {
				wil_err(wil, "link stats invalid global record size %zu < %zu\n",
					stats_size, expected_size);
				return;
			}

			if (wil->fw_stats_global.ready) {
				/* clean old statistics */
				wil->fw_stats_global.tsf = 0;
				wil->fw_stats_global.ready = 0;
			}

			wil_link_stats_store_global(vif, payload + hdr_size);

			if (!has_next) {
				wil->fw_stats_global.tsf = tsf;
				wil->fw_stats_global.ready = 1;
			}

			break;
		default:
			break;
		}

		/* skip to next record */
		payload += record_size;
		payload_size -= record_size;
	}
}

static void
wmi_evt_link_stats(struct wil6210_vif *vif, int id, void *d, int len)
{
	struct wil6210_priv *wil = vif_to_wil(vif);
	struct wmi_link_stats_event *evt = d;
	size_t payload_size;

	if (len < offsetof(struct wmi_link_stats_event, payload)) {
		wil_err(wil, "stats event way too short %d\n", len);
		return;
	}
	payload_size = le16_to_cpu(evt->payload_size);
	if (len < sizeof(struct wmi_link_stats_event) + payload_size) {
		wil_err(wil, "stats event too short %d\n", len);
		return;
	}

	wmi_link_stats_parse(vif, le64_to_cpu(evt->tsf), evt->has_next,
			     evt->payload, payload_size);
}

/**
 * Some events are ignored for purpose; and need not be interpreted as
 * "unhandled events"
@@ -1409,6 +1539,7 @@ static const struct {
	{WMI_TOF_FTM_PER_DEST_RES_EVENTID,	wmi_evt_per_dest_res},
	{WMI_TOF_CHANNEL_INFO_EVENTID,		wmi_evt_ignore},
	{WMI_SCHED_SCAN_RESULT_EVENTID,		wmi_evt_sched_scan_result},
	{WMI_LINK_STATS_EVENTID,		wmi_evt_link_stats},
};

/*
@@ -3453,3 +3584,37 @@ int wil_wmi_bcast_desc_ring_add(struct wil6210_vif *vif, int ring_id)

	return 0;
}

int wmi_link_stats_cfg(struct wil6210_vif *vif, u32 type, u8 cid, u32 interval)
{
	struct wil6210_priv *wil = vif_to_wil(vif);
	struct wmi_link_stats_cmd cmd = {
		.record_type_mask = cpu_to_le32(type),
		.cid = cid,
		.action = WMI_LINK_STATS_SNAPSHOT,
		.interval_msec = cpu_to_le32(interval),
	};
	struct {
		struct wmi_cmd_hdr wmi;
		struct wmi_link_stats_config_done_event evt;
	} __packed reply = {
		.evt = {.status = WMI_FW_STATUS_FAILURE},
	};
	int rc;

	rc = wmi_call(wil, WMI_LINK_STATS_CMDID, vif->mid, &cmd, sizeof(cmd),
		      WMI_LINK_STATS_CONFIG_DONE_EVENTID, &reply,
		      sizeof(reply), WIL_WMI_CALL_GENERAL_TO_MS);
	if (rc) {
		wil_err(wil, "WMI_LINK_STATS_CMDID failed, rc %d\n", rc);
		return rc;
	}

	if (reply.evt.status != WMI_FW_STATUS_SUCCESS) {
		wil_err(wil, "Link statistics config failed, status %d\n",
			reply.evt.status);
		return -EINVAL;
	}

	return 0;
}
+41 −16
Original line number Diff line number Diff line
@@ -1740,9 +1740,7 @@ enum wmi_link_stats_action {
/* WMI_LINK_STATS_EVENT record identifiers */
enum wmi_link_stats_record_type {
	WMI_LINK_STATS_TYPE_BASIC	= 0x01,
	WMI_LINK_STATS_TYPE_MAC		= 0x02,
	WMI_LINK_STATS_TYPE_PHY		= 0x04,
	WMI_LINK_STATS_TYPE_OTA		= 0x08,
	WMI_LINK_STATS_TYPE_GLOBAL	= 0x02,
};

/* WMI_LINK_STATS_CMDID */
@@ -1756,7 +1754,7 @@ struct wmi_link_stats_cmd {
	/* wmi_link_stats_action_e */
	u8 action;
	u8 reserved[6];
	/* for WMI_LINK_STATS_PERIODIC */
	/* range = 100 - 10000 */
	__le32 interval_msec;
} __packed;

@@ -3808,32 +3806,59 @@ struct wmi_link_stats_config_done_event {

/* WMI_LINK_STATS_EVENTID */
struct wmi_link_stats_event {
	__le64 tsf;
	__le16 payload_size;
	u8 has_next;
	u8 reserved[5];
	/* a stream of records, e.g. wmi_link_stats_basic_s */
	/* a stream of wmi_link_stats_record_s */
	u8 payload[0];
} __packed;

/* WMI_LINK_STATS_EVENT record struct */
struct wmi_link_stats_basic {
	/* WMI_LINK_STATS_TYPE_BASIC */
/* WMI_LINK_STATS_EVENT */
struct wmi_link_stats_record {
	/* wmi_link_stats_record_type_e */
	u8 record_type_id;
	u8 reserved;
	__le16 record_size;
	u8 record[0];
} __packed;

/* WMI_LINK_STATS_TYPE_BASIC */
struct wmi_link_stats_basic {
	u8 cid;
	/* 0: fail; 1: OK; 2: retrying */
	u8 bf_status;
	s8 rssi;
	u8 sqi;
	u8 bf_mcs;
	u8 per_average;
	u8 selected_rfc;
	__le16 bf_mcs;
	u8 rx_effective_ant_num;
	u8 my_rx_sector;
	u8 my_tx_sector;
	u8 other_rx_sector;
	u8 other_tx_sector;
	u8 reserved[7];
	/* 1/4 Db units */
	__le16 snr;
	__le32 tx_tpt;
	__le32 tx_goodput;
	__le32 rx_goodput;
	__le16 my_rx_sector;
	__le16 my_tx_sector;
	__le16 other_rx_sector;
	__le16 other_tx_sector;
	__le32 reserved[2];
	__le32 bf_count;
	__le32 rx_bcast_frames;
} __packed;

/* WMI_LINK_STATS_TYPE_GLOBAL */
struct wmi_link_stats_global {
	/* all ack-able frames */
	__le32 rx_frames;
	/* all ack-able frames */
	__le32 tx_frames;
	__le32 rx_ba_frames;
	__le32 tx_ba_frames;
	__le32 tx_beacons;
	__le32 rx_mic_errors;
	__le32 rx_crc_errors;
	__le32 tx_fail_no_ack;
	u8 reserved[8];
} __packed;

/* WMI_SET_GRANT_MCS_EVENTID */