wil6210: add support for link statistics
authorDedy Lansky <dlansky@codeaurora.org>
Tue, 24 Jul 2018 07:44:31 +0000 (10:44 +0300)
committerKalle Valo <kvalo@codeaurora.org>
Tue, 31 Jul 2018 08:00:51 +0000 (11:00 +0300)
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.

Signed-off-by: Dedy Lansky <dlansky@codeaurora.org>
Signed-off-by: Maya Erez <merez@codeaurora.org>
Signed-off-by: Kalle Valo <kvalo@codeaurora.org>
drivers/net/wireless/ath/wil6210/debugfs.c
drivers/net/wireless/ath/wil6210/wil6210.h
drivers/net/wireless/ath/wil6210/wmi.c
drivers/net/wireless/ath/wil6210/wmi.h

index f2eab39376eec7b94a52cbfa2142568ef7caf822..51c3330bc316f8c47ff005a55e16a46ec234ef10 100644 (file)
@@ -1922,6 +1922,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)
 {
@@ -2256,6 +2473,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,
index 379f7ce664c5d4cb17b90864764bf6b6fb217252..e87c88957033a7385949e45048bb57edce038cb8 100644 (file)
@@ -728,6 +728,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 */
@@ -842,6 +843,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 */
 };
 
 /**
@@ -869,6 +872,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;
@@ -1002,6 +1011,8 @@ struct wil6210_priv {
        bool use_rx_hw_reordering;
        bool secured_boot;
        u8 boot_config;
+
+       struct wil_fw_stats_global fw_stats_global;
 };
 
 #define wil_to_wiphy(i) (i->wiphy)
@@ -1201,6 +1212,7 @@ int wmi_new_sta(struct wil6210_vif *vif, const u8 *mac, u8 aid);
 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);
index 71056c834fff339eb5239134061033dc6b5fa436..45a71fd7cc28208b1c77052a3e23584b7e365049 100644 (file)
@@ -464,6 +464,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";
        }
@@ -598,6 +600,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";
        }
@@ -1329,6 +1335,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"
@@ -1359,6 +1489,7 @@ static const struct {
        {WMI_RING_EN_EVENTID,           wmi_evt_ring_en},
        {WMI_DATA_PORT_OPEN_EVENTID,            wmi_evt_ignore},
        {WMI_SCHED_SCAN_RESULT_EVENTID,         wmi_evt_sched_scan_result},
+       {WMI_LINK_STATS_EVENTID,                wmi_evt_link_stats},
 };
 
 /*
@@ -3242,3 +3373,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;
+}
index 13f6f62101175d0a5b5e6c9f7ee3f3def93efcd1..00cf3c4221337271fc8becdda63dc6257386f518 100644 (file)
@@ -1715,9 +1715,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 */
@@ -1731,7 +1729,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;
 
@@ -3779,32 +3777,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 */