Loading drivers/net/wireless/ath/wil6210/cfg80211.c +42 −8 Original line number Diff line number Diff line Loading @@ -943,11 +943,12 @@ static int wil_cfg80211_scan(struct wiphy *wiphy, wil_dbg_misc(wil, "scan: wdev=0x%p iftype=%d\n", wdev, wdev->iftype); /* check we are client side */ /* scan is supported on client interfaces and on AP interface */ switch (wdev->iftype) { case NL80211_IFTYPE_STATION: case NL80211_IFTYPE_P2P_CLIENT: case NL80211_IFTYPE_P2P_DEVICE: case NL80211_IFTYPE_AP: break; default: return -EOPNOTSUPP; Loading Loading @@ -1353,18 +1354,51 @@ int wil_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, int rc; bool tx_status; /* Note, currently we do not support the "wait" parameter, user-space * must call remain_on_channel before mgmt_tx or listen on a channel * another way (AP/PCP or connected station) * in addition we need to check if specified "chan" argument is * different from currently "listened" channel and fail if it is. wil_dbg_misc(wil, "mgmt_tx: channel %d offchan %d, wait %d\n", params->chan ? params->chan->hw_value : -1, params->offchan, params->wait); /* Note, currently we support the "wait" parameter only on AP mode. * In other modes, user-space must call remain_on_channel before * mgmt_tx or listen on a channel other than active one. */ if (params->chan && params->chan->hw_value == 0) { wil_err(wil, "invalid channel\n"); return -EINVAL; } if (wdev->iftype != NL80211_IFTYPE_AP) { wil_dbg_misc(wil, "send WMI_SW_TX_REQ_CMDID on non-AP interfaces\n"); rc = wmi_mgmt_tx(vif, buf, len); tx_status = (rc == 0); goto out; } if (!params->chan || params->chan->hw_value == vif->channel) { wil_dbg_misc(wil, "send WMI_SW_TX_REQ_CMDID for on-channel\n"); rc = wmi_mgmt_tx(vif, buf, len); goto out; } if (params->offchan == 0) { wil_err(wil, "invalid channel params: current %d requested %d, off-channel not allowed\n", vif->channel, params->chan->hw_value); return -EBUSY; } /* use the wmi_mgmt_tx_ext only on AP mode and off-channel */ rc = wmi_mgmt_tx_ext(vif, buf, len, params->chan->hw_value, params->wait); out: tx_status = (rc == 0); cfg80211_mgmt_tx_status(wdev, cookie ? *cookie : 0, buf, len, tx_status, GFP_KERNEL); return rc; } Loading drivers/net/wireless/ath/wil6210/debugfs.c +348 −3 Original line number Diff line number Diff line Loading @@ -1433,7 +1433,7 @@ static const struct file_operations fops_ssid = { }; /*---------temp------------*/ static void print_temp(struct seq_file *s, const char *prefix, u32 t) static void print_temp(struct seq_file *s, const char *prefix, s32 t) { switch (t) { case 0: Loading @@ -1441,7 +1441,8 @@ static void print_temp(struct seq_file *s, const char *prefix, u32 t) seq_printf(s, "%s N/A\n", prefix); break; default: seq_printf(s, "%s %d.%03d\n", prefix, t / 1000, t % 1000); seq_printf(s, "%s %s%d.%03d\n", prefix, (t < 0 ? "-" : ""), abs(t / 1000), abs(t % 1000)); break; } } Loading @@ -1449,7 +1450,7 @@ static void print_temp(struct seq_file *s, const char *prefix, u32 t) static int wil_temp_debugfs_show(struct seq_file *s, void *data) { struct wil6210_priv *wil = s->private; u32 t_m, t_r; s32 t_m, t_r; int rc = wmi_get_temperature(wil, &t_m, &t_r); if (rc) { Loading Loading @@ -1840,6 +1841,343 @@ static const struct file_operations fops_mids = { .llseek = seq_lseek, }; static int wil_tx_latency_debugfs_show(struct seq_file *s, void *data) __acquires(&p->tid_rx_lock) __releases(&p->tid_rx_lock) { struct wil6210_priv *wil = s->private; int i, bin; for (i = 0; i < ARRAY_SIZE(wil->sta); i++) { struct wil_sta_info *p = &wil->sta[i]; char *status = "unknown"; u8 aid = 0; u8 mid; if (!p->tx_latency_bins) continue; switch (p->status) { case wil_sta_unused: status = "unused "; break; case wil_sta_conn_pending: status = "pending "; break; case wil_sta_connected: status = "connected"; aid = p->aid; break; } mid = (p->status != wil_sta_unused) ? p->mid : U8_MAX; seq_printf(s, "[%d] %pM %s MID %d AID %d\n", i, p->addr, status, mid, aid); if (p->status == wil_sta_connected) { u64 num_packets = 0; u64 tx_latency_avg = p->stats.tx_latency_total_us; seq_puts(s, "Tx/Latency bin:"); for (bin = 0; bin < WIL_NUM_LATENCY_BINS; bin++) { seq_printf(s, " %lld", p->tx_latency_bins[bin]); num_packets += p->tx_latency_bins[bin]; } seq_puts(s, "\n"); if (!num_packets) continue; do_div(tx_latency_avg, num_packets); seq_printf(s, "Tx/Latency min/avg/max (us): %d/%lld/%d", p->stats.tx_latency_min_us, tx_latency_avg, p->stats.tx_latency_max_us); seq_puts(s, "\n"); } } return 0; } static int wil_tx_latency_seq_open(struct inode *inode, struct file *file) { return single_open(file, wil_tx_latency_debugfs_show, inode->i_private); } static ssize_t wil_tx_latency_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 val, rc, i; bool enable; rc = kstrtoint_from_user(buf, len, 0, &val); if (rc) { wil_err(wil, "Invalid argument\n"); return rc; } if (val == 1) /* default resolution */ val = 500; if (val && (val < 50 || val > 1000)) { wil_err(wil, "Invalid resolution %d\n", val); return -EINVAL; } enable = !!val; if (wil->tx_latency == enable) return len; wil_info(wil, "%s TX latency measurements (resolution %dusec)\n", enable ? "Enabling" : "Disabling", val); if (enable) { size_t sz = sizeof(u64) * WIL_NUM_LATENCY_BINS; wil->tx_latency_res = val; for (i = 0; i < ARRAY_SIZE(wil->sta); i++) { struct wil_sta_info *sta = &wil->sta[i]; kfree(sta->tx_latency_bins); sta->tx_latency_bins = kzalloc(sz, GFP_KERNEL); if (!sta->tx_latency_bins) return -ENOMEM; sta->stats.tx_latency_min_us = U32_MAX; sta->stats.tx_latency_max_us = 0; sta->stats.tx_latency_total_us = 0; } } wil->tx_latency = enable; return len; } static const struct file_operations fops_tx_latency = { .open = wil_tx_latency_seq_open, .release = single_release, .read = seq_read, .write = wil_tx_latency_write, .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) { Loading Loading @@ -2174,6 +2512,9 @@ static const struct { {"srings", 0444, &fops_srings}, {"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, Loading Loading @@ -2292,10 +2633,14 @@ int wil6210_debugfs_init(struct wil6210_priv *wil) void wil6210_debugfs_remove(struct wil6210_priv *wil) { int i; debugfs_remove_recursive(wil->debug); wil->debug = NULL; kfree(wil->dbg_data.data_arr); for (i = 0; i < ARRAY_SIZE(wil->sta); i++) kfree(wil->sta[i].tx_latency_bins); /* free pmc memory without sending command to fw, as it will * be reset on the way down anyway Loading drivers/net/wireless/ath/wil6210/main.c +5 −1 Original line number Diff line number Diff line Loading @@ -281,6 +281,7 @@ __acquires(&sta->tid_rx_lock) __releases(&sta->tid_rx_lock) } /* statistics */ memset(&sta->stats, 0, sizeof(sta->stats)); sta->stats.tx_latency_min_us = U32_MAX; } static bool wil_vif_is_connected(struct wil6210_priv *wil, u8 mid) Loading Loading @@ -1133,6 +1134,9 @@ void wil_refresh_fw_capabilities(struct wil6210_priv *wil) wiphy->max_sched_scan_plans = WMI_MAX_PLANS_NUM; } if (test_bit(WMI_FW_CAPABILITY_TX_REQ_EXT, wil->fw_capabilities)) wiphy->flags |= WIPHY_FLAG_OFFCHAN_TX; if (wil->platform_ops.set_features) { features = (test_bit(WMI_FW_CAPABILITY_REF_CLOCK_CONTROL, wil->fw_capabilities) && Loading Loading @@ -1330,7 +1334,7 @@ static int wil_get_otp_info(struct wil6210_priv *wil) static int wil_wait_for_fw_ready(struct wil6210_priv *wil) { ulong to = msecs_to_jiffies(1000); ulong to = msecs_to_jiffies(2000); ulong left = wait_for_completion_timeout(&wil->wmi_ready, to); if (0 == left) { Loading drivers/net/wireless/ath/wil6210/txrx.c +38 −0 Original line number Diff line number Diff line Loading @@ -1735,6 +1735,11 @@ static int __wil_tx_vring_tso(struct wil6210_priv *wil, struct wil6210_vif *vif, */ wmb(); if (wil->tx_latency) *(ktime_t *)&skb->cb = ktime_get(); else memset(skb->cb, 0, sizeof(ktime_t)); wil_w(wil, vring->hwtail, vring->swhead); return 0; Loading Loading @@ -1886,6 +1891,11 @@ static int __wil_tx_ring(struct wil6210_priv *wil, struct wil6210_vif *vif, */ wmb(); if (wil->tx_latency) *(ktime_t *)&skb->cb = ktime_get(); else memset(skb->cb, 0, sizeof(ktime_t)); wil_w(wil, ring->hwtail, ring->swhead); return 0; Loading Loading @@ -2108,6 +2118,31 @@ netdev_tx_t wil_start_xmit(struct sk_buff *skb, struct net_device *ndev) return NET_XMIT_DROP; } void wil_tx_latency_calc(struct wil6210_priv *wil, struct sk_buff *skb, struct wil_sta_info *sta) { int skb_time_us; int bin; if (!wil->tx_latency) return; if (ktime_to_ms(*(ktime_t *)&skb->cb) == 0) return; skb_time_us = ktime_us_delta(ktime_get(), *(ktime_t *)&skb->cb); bin = skb_time_us / wil->tx_latency_res; bin = min_t(int, bin, WIL_NUM_LATENCY_BINS - 1); wil_dbg_txrx(wil, "skb time %dus => bin %d\n", skb_time_us, bin); sta->tx_latency_bins[bin]++; sta->stats.tx_latency_total_us += skb_time_us; if (skb_time_us < sta->stats.tx_latency_min_us) sta->stats.tx_latency_min_us = skb_time_us; if (skb_time_us > sta->stats.tx_latency_max_us) sta->stats.tx_latency_max_us = skb_time_us; } /** * Clean up transmitted skb's from the Tx VRING * Loading Loading @@ -2194,6 +2229,9 @@ int wil_tx_complete(struct wil6210_vif *vif, int ringid) if (stats) { stats->tx_packets++; stats->tx_bytes += skb->len; wil_tx_latency_calc(wil, skb, &wil->sta[cid]); } } else { ndev->stats.tx_errors++; Loading drivers/net/wireless/ath/wil6210/txrx.h +2 −0 Original line number Diff line number Diff line Loading @@ -620,5 +620,7 @@ void wil_tid_ampdu_rx_free(struct wil6210_priv *wil, struct wil_tid_ampdu_rx *r); void wil_tx_data_init(struct wil_ring_tx_data *txdata); void wil_init_txrx_ops_legacy_dma(struct wil6210_priv *wil); void wil_tx_latency_calc(struct wil6210_priv *wil, struct sk_buff *skb, struct wil_sta_info *sta); #endif /* WIL6210_TXRX_H */ Loading
drivers/net/wireless/ath/wil6210/cfg80211.c +42 −8 Original line number Diff line number Diff line Loading @@ -943,11 +943,12 @@ static int wil_cfg80211_scan(struct wiphy *wiphy, wil_dbg_misc(wil, "scan: wdev=0x%p iftype=%d\n", wdev, wdev->iftype); /* check we are client side */ /* scan is supported on client interfaces and on AP interface */ switch (wdev->iftype) { case NL80211_IFTYPE_STATION: case NL80211_IFTYPE_P2P_CLIENT: case NL80211_IFTYPE_P2P_DEVICE: case NL80211_IFTYPE_AP: break; default: return -EOPNOTSUPP; Loading Loading @@ -1353,18 +1354,51 @@ int wil_cfg80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev, int rc; bool tx_status; /* Note, currently we do not support the "wait" parameter, user-space * must call remain_on_channel before mgmt_tx or listen on a channel * another way (AP/PCP or connected station) * in addition we need to check if specified "chan" argument is * different from currently "listened" channel and fail if it is. wil_dbg_misc(wil, "mgmt_tx: channel %d offchan %d, wait %d\n", params->chan ? params->chan->hw_value : -1, params->offchan, params->wait); /* Note, currently we support the "wait" parameter only on AP mode. * In other modes, user-space must call remain_on_channel before * mgmt_tx or listen on a channel other than active one. */ if (params->chan && params->chan->hw_value == 0) { wil_err(wil, "invalid channel\n"); return -EINVAL; } if (wdev->iftype != NL80211_IFTYPE_AP) { wil_dbg_misc(wil, "send WMI_SW_TX_REQ_CMDID on non-AP interfaces\n"); rc = wmi_mgmt_tx(vif, buf, len); tx_status = (rc == 0); goto out; } if (!params->chan || params->chan->hw_value == vif->channel) { wil_dbg_misc(wil, "send WMI_SW_TX_REQ_CMDID for on-channel\n"); rc = wmi_mgmt_tx(vif, buf, len); goto out; } if (params->offchan == 0) { wil_err(wil, "invalid channel params: current %d requested %d, off-channel not allowed\n", vif->channel, params->chan->hw_value); return -EBUSY; } /* use the wmi_mgmt_tx_ext only on AP mode and off-channel */ rc = wmi_mgmt_tx_ext(vif, buf, len, params->chan->hw_value, params->wait); out: tx_status = (rc == 0); cfg80211_mgmt_tx_status(wdev, cookie ? *cookie : 0, buf, len, tx_status, GFP_KERNEL); return rc; } Loading
drivers/net/wireless/ath/wil6210/debugfs.c +348 −3 Original line number Diff line number Diff line Loading @@ -1433,7 +1433,7 @@ static const struct file_operations fops_ssid = { }; /*---------temp------------*/ static void print_temp(struct seq_file *s, const char *prefix, u32 t) static void print_temp(struct seq_file *s, const char *prefix, s32 t) { switch (t) { case 0: Loading @@ -1441,7 +1441,8 @@ static void print_temp(struct seq_file *s, const char *prefix, u32 t) seq_printf(s, "%s N/A\n", prefix); break; default: seq_printf(s, "%s %d.%03d\n", prefix, t / 1000, t % 1000); seq_printf(s, "%s %s%d.%03d\n", prefix, (t < 0 ? "-" : ""), abs(t / 1000), abs(t % 1000)); break; } } Loading @@ -1449,7 +1450,7 @@ static void print_temp(struct seq_file *s, const char *prefix, u32 t) static int wil_temp_debugfs_show(struct seq_file *s, void *data) { struct wil6210_priv *wil = s->private; u32 t_m, t_r; s32 t_m, t_r; int rc = wmi_get_temperature(wil, &t_m, &t_r); if (rc) { Loading Loading @@ -1840,6 +1841,343 @@ static const struct file_operations fops_mids = { .llseek = seq_lseek, }; static int wil_tx_latency_debugfs_show(struct seq_file *s, void *data) __acquires(&p->tid_rx_lock) __releases(&p->tid_rx_lock) { struct wil6210_priv *wil = s->private; int i, bin; for (i = 0; i < ARRAY_SIZE(wil->sta); i++) { struct wil_sta_info *p = &wil->sta[i]; char *status = "unknown"; u8 aid = 0; u8 mid; if (!p->tx_latency_bins) continue; switch (p->status) { case wil_sta_unused: status = "unused "; break; case wil_sta_conn_pending: status = "pending "; break; case wil_sta_connected: status = "connected"; aid = p->aid; break; } mid = (p->status != wil_sta_unused) ? p->mid : U8_MAX; seq_printf(s, "[%d] %pM %s MID %d AID %d\n", i, p->addr, status, mid, aid); if (p->status == wil_sta_connected) { u64 num_packets = 0; u64 tx_latency_avg = p->stats.tx_latency_total_us; seq_puts(s, "Tx/Latency bin:"); for (bin = 0; bin < WIL_NUM_LATENCY_BINS; bin++) { seq_printf(s, " %lld", p->tx_latency_bins[bin]); num_packets += p->tx_latency_bins[bin]; } seq_puts(s, "\n"); if (!num_packets) continue; do_div(tx_latency_avg, num_packets); seq_printf(s, "Tx/Latency min/avg/max (us): %d/%lld/%d", p->stats.tx_latency_min_us, tx_latency_avg, p->stats.tx_latency_max_us); seq_puts(s, "\n"); } } return 0; } static int wil_tx_latency_seq_open(struct inode *inode, struct file *file) { return single_open(file, wil_tx_latency_debugfs_show, inode->i_private); } static ssize_t wil_tx_latency_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 val, rc, i; bool enable; rc = kstrtoint_from_user(buf, len, 0, &val); if (rc) { wil_err(wil, "Invalid argument\n"); return rc; } if (val == 1) /* default resolution */ val = 500; if (val && (val < 50 || val > 1000)) { wil_err(wil, "Invalid resolution %d\n", val); return -EINVAL; } enable = !!val; if (wil->tx_latency == enable) return len; wil_info(wil, "%s TX latency measurements (resolution %dusec)\n", enable ? "Enabling" : "Disabling", val); if (enable) { size_t sz = sizeof(u64) * WIL_NUM_LATENCY_BINS; wil->tx_latency_res = val; for (i = 0; i < ARRAY_SIZE(wil->sta); i++) { struct wil_sta_info *sta = &wil->sta[i]; kfree(sta->tx_latency_bins); sta->tx_latency_bins = kzalloc(sz, GFP_KERNEL); if (!sta->tx_latency_bins) return -ENOMEM; sta->stats.tx_latency_min_us = U32_MAX; sta->stats.tx_latency_max_us = 0; sta->stats.tx_latency_total_us = 0; } } wil->tx_latency = enable; return len; } static const struct file_operations fops_tx_latency = { .open = wil_tx_latency_seq_open, .release = single_release, .read = seq_read, .write = wil_tx_latency_write, .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) { Loading Loading @@ -2174,6 +2512,9 @@ static const struct { {"srings", 0444, &fops_srings}, {"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, Loading Loading @@ -2292,10 +2633,14 @@ int wil6210_debugfs_init(struct wil6210_priv *wil) void wil6210_debugfs_remove(struct wil6210_priv *wil) { int i; debugfs_remove_recursive(wil->debug); wil->debug = NULL; kfree(wil->dbg_data.data_arr); for (i = 0; i < ARRAY_SIZE(wil->sta); i++) kfree(wil->sta[i].tx_latency_bins); /* free pmc memory without sending command to fw, as it will * be reset on the way down anyway Loading
drivers/net/wireless/ath/wil6210/main.c +5 −1 Original line number Diff line number Diff line Loading @@ -281,6 +281,7 @@ __acquires(&sta->tid_rx_lock) __releases(&sta->tid_rx_lock) } /* statistics */ memset(&sta->stats, 0, sizeof(sta->stats)); sta->stats.tx_latency_min_us = U32_MAX; } static bool wil_vif_is_connected(struct wil6210_priv *wil, u8 mid) Loading Loading @@ -1133,6 +1134,9 @@ void wil_refresh_fw_capabilities(struct wil6210_priv *wil) wiphy->max_sched_scan_plans = WMI_MAX_PLANS_NUM; } if (test_bit(WMI_FW_CAPABILITY_TX_REQ_EXT, wil->fw_capabilities)) wiphy->flags |= WIPHY_FLAG_OFFCHAN_TX; if (wil->platform_ops.set_features) { features = (test_bit(WMI_FW_CAPABILITY_REF_CLOCK_CONTROL, wil->fw_capabilities) && Loading Loading @@ -1330,7 +1334,7 @@ static int wil_get_otp_info(struct wil6210_priv *wil) static int wil_wait_for_fw_ready(struct wil6210_priv *wil) { ulong to = msecs_to_jiffies(1000); ulong to = msecs_to_jiffies(2000); ulong left = wait_for_completion_timeout(&wil->wmi_ready, to); if (0 == left) { Loading
drivers/net/wireless/ath/wil6210/txrx.c +38 −0 Original line number Diff line number Diff line Loading @@ -1735,6 +1735,11 @@ static int __wil_tx_vring_tso(struct wil6210_priv *wil, struct wil6210_vif *vif, */ wmb(); if (wil->tx_latency) *(ktime_t *)&skb->cb = ktime_get(); else memset(skb->cb, 0, sizeof(ktime_t)); wil_w(wil, vring->hwtail, vring->swhead); return 0; Loading Loading @@ -1886,6 +1891,11 @@ static int __wil_tx_ring(struct wil6210_priv *wil, struct wil6210_vif *vif, */ wmb(); if (wil->tx_latency) *(ktime_t *)&skb->cb = ktime_get(); else memset(skb->cb, 0, sizeof(ktime_t)); wil_w(wil, ring->hwtail, ring->swhead); return 0; Loading Loading @@ -2108,6 +2118,31 @@ netdev_tx_t wil_start_xmit(struct sk_buff *skb, struct net_device *ndev) return NET_XMIT_DROP; } void wil_tx_latency_calc(struct wil6210_priv *wil, struct sk_buff *skb, struct wil_sta_info *sta) { int skb_time_us; int bin; if (!wil->tx_latency) return; if (ktime_to_ms(*(ktime_t *)&skb->cb) == 0) return; skb_time_us = ktime_us_delta(ktime_get(), *(ktime_t *)&skb->cb); bin = skb_time_us / wil->tx_latency_res; bin = min_t(int, bin, WIL_NUM_LATENCY_BINS - 1); wil_dbg_txrx(wil, "skb time %dus => bin %d\n", skb_time_us, bin); sta->tx_latency_bins[bin]++; sta->stats.tx_latency_total_us += skb_time_us; if (skb_time_us < sta->stats.tx_latency_min_us) sta->stats.tx_latency_min_us = skb_time_us; if (skb_time_us > sta->stats.tx_latency_max_us) sta->stats.tx_latency_max_us = skb_time_us; } /** * Clean up transmitted skb's from the Tx VRING * Loading Loading @@ -2194,6 +2229,9 @@ int wil_tx_complete(struct wil6210_vif *vif, int ringid) if (stats) { stats->tx_packets++; stats->tx_bytes += skb->len; wil_tx_latency_calc(wil, skb, &wil->sta[cid]); } } else { ndev->stats.tx_errors++; Loading
drivers/net/wireless/ath/wil6210/txrx.h +2 −0 Original line number Diff line number Diff line Loading @@ -620,5 +620,7 @@ void wil_tid_ampdu_rx_free(struct wil6210_priv *wil, struct wil_tid_ampdu_rx *r); void wil_tx_data_init(struct wil_ring_tx_data *txdata); void wil_init_txrx_ops_legacy_dma(struct wil6210_priv *wil); void wil_tx_latency_calc(struct wil6210_priv *wil, struct sk_buff *skb, struct wil_sta_info *sta); #endif /* WIL6210_TXRX_H */