1 /*
2 * Copyright (c) 2012-2021 The Linux Foundation. All rights reserved.
3 * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved.
4 *
5 * Permission to use, copy, modify, and/or distribute this software for
6 * any purpose with or without fee is hereby granted, provided that the
7 * above copyright notice and this permission notice appear in all
8 * copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
11 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
12 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
13 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
14 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
15 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
16 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17 * PERFORMANCE OF THIS SOFTWARE.
18 */
19
20 /**
21 * DOC: wlan_hdd_debugfs_llstat.c
22 *
23 * WLAN Host Device Driver implementation to update
24 * debugfs with Link Layer statistics
25 */
26
27 #include <cds_sched.h>
28 #include "osif_sync.h"
29 #include <wlan_hdd_debugfs_llstat.h>
30 #include <wlan_hdd_stats.h>
31 #include <wma_api.h>
32
33 struct ll_stats_buf {
34 ssize_t len;
35 uint8_t *result;
36 };
37
38 static struct ll_stats_buf ll_stats;
39
40 static DEFINE_MUTEX(llstats_mutex);
41
hdd_debugfs_process_iface_stats(struct wlan_hdd_link_info * link_info,void * data,uint32_t num_peers)42 void hdd_debugfs_process_iface_stats(struct wlan_hdd_link_info *link_info,
43 void *data, uint32_t num_peers)
44 {
45 struct wifi_interface_stats *iface_stat;
46 struct wifi_interface_info *iface_info;
47 wmi_iface_link_stats *link_stats;
48 wmi_wmm_ac_stats *ac_stats;
49 wmi_iface_offload_stats *offload_stats;
50 wmi_iface_powersave_stats *powersave_stats;
51 uint64_t average_tsf_offset;
52 int i;
53 ssize_t len = 0;
54 uint8_t *buffer;
55
56 hdd_enter();
57
58 mutex_lock(&llstats_mutex);
59 if (!ll_stats.result) {
60 mutex_unlock(&llstats_mutex);
61 hdd_err("LL statistics buffer is NULL");
62 return;
63 }
64
65 iface_stat = data;
66 buffer = ll_stats.result;
67 buffer += ll_stats.len;
68 len = scnprintf(buffer, DEBUGFS_LLSTATS_BUF_SIZE - ll_stats.len,
69 "\n\n===LL_STATS_IFACE: num_peers: %d===", num_peers);
70
71 if (!hdd_get_interface_info(link_info, &iface_stat->info)) {
72 mutex_unlock(&llstats_mutex);
73 hdd_err("hdd_get_interface_info get fail");
74 return;
75 }
76
77 iface_info = &iface_stat->info;
78 buffer += len;
79 ll_stats.len += len;
80 len = scnprintf(buffer, DEBUGFS_LLSTATS_BUF_SIZE - ll_stats.len,
81 "\nmode: %u, MAC_ADDR: " QDF_MAC_ADDR_FMT ", state: %u,"
82 " roaming: %u, capabilities: %u, SSID: %s,"
83 " BSSID_MAC: " QDF_MAC_ADDR_FMT
84 ", ap_country_str: %s, country_str: %s",
85 iface_info->mode,
86 QDF_MAC_ADDR_REF(&iface_info->macAddr.bytes[0]),
87 iface_info->state, iface_info->roaming,
88 iface_info->capabilities, iface_info->ssid,
89 QDF_MAC_ADDR_REF(&iface_info->bssid.bytes[0]),
90 iface_info->apCountryStr, iface_info->countryStr);
91
92 link_stats = &iface_stat->link_stats;
93 average_tsf_offset = link_stats->avg_bcn_spread_offset_high;
94 average_tsf_offset = (average_tsf_offset << 32) |
95 link_stats->avg_bcn_spread_offset_low;
96
97 buffer += len;
98 ll_stats.len += len;
99 len = scnprintf(buffer, DEBUGFS_LLSTATS_BUF_SIZE - ll_stats.len,
100 "\nbeacon_rx: %u, mgmt_rx: %u, mgmt_action_rx: %u, mgmt_action_tx: %u, rssi_mgmt: %d, rssi_data: %d, rssi_ack: %d, is_leaky_ap: %u, avg_rx_frms_leaked: %u, rx_leak_window: %u, average_tsf_offset: %llu, Tx RTS success count: %u, Tx RTS fail count: %u, Tx ppdu success count: %u, Tx ppdu fail count: %u, Connected duration: %u, Disconnected duration: %u, RTT ranging duration: %u, RTT responder duration: %u, Num tx probes: %u, Num beacon miss: %u, nf_cal %d\n\nNumber of AC: %d",
101 link_stats->beacon_rx, link_stats->mgmt_rx,
102 link_stats->mgmt_action_rx, link_stats->mgmt_action_tx,
103 link_stats->rssi_mgmt, link_stats->rssi_data,
104 link_stats->rssi_ack, link_stats->is_leaky_ap,
105 link_stats->avg_rx_frms_leaked,
106 link_stats->rx_leak_window, average_tsf_offset,
107 link_stats->tx_rts_succ_cnt,
108 link_stats->tx_rts_fail_cnt,
109 link_stats->tx_ppdu_succ_cnt,
110 link_stats->tx_ppdu_fail_cnt,
111 link_stats->connected_duration,
112 link_stats->disconnected_duration,
113 link_stats->rtt_ranging_duration,
114 link_stats->rtt_responder_duration,
115 link_stats->num_probes_tx, link_stats->num_beacon_miss,
116 link_stats->nf_cal_val,
117 link_stats->num_ac);
118
119 for (i = 0; i < link_stats->num_ac; i++) {
120 ac_stats = &iface_stat->ac_stats[i];
121 buffer += len;
122 ll_stats.len += len;
123 len = scnprintf(buffer,
124 DEBUGFS_LLSTATS_BUF_SIZE - ll_stats.len,
125 "\nac_type: %d, tx_mpdu: %u, rx_mpdu: %u, "
126 "tx_mcast: %u, rx_mcast: %u, tx_ampdu: %u, "
127 "rx_ampdu: %u, mpdu_lost: %u, retries: %u, "
128 "retries_short: %u, retries_long: %u, "
129 "contention_time: min-%u max-%u avg-%u, "
130 "contention num samples: %u, "
131 "tx_pending_msdu: %u",
132 ac_stats->ac_type,
133 ac_stats->tx_mpdu, ac_stats->rx_mpdu,
134 ac_stats->tx_mcast, ac_stats->rx_mcast,
135 ac_stats->tx_ampdu, ac_stats->rx_ampdu,
136 ac_stats->mpdu_lost, ac_stats->retries,
137 ac_stats->retries_short, ac_stats->retries_long,
138 ac_stats->contention_time_min,
139 ac_stats->contention_time_max,
140 ac_stats->contention_time_avg,
141 ac_stats->contention_num_samples,
142 ac_stats->tx_pending_msdu);
143 }
144
145 buffer += len;
146 ll_stats.len += len;
147 len = scnprintf(buffer, DEBUGFS_LLSTATS_BUF_SIZE - ll_stats.len,
148 "\n\nNumber of offload stats: %d",
149 iface_stat->num_offload_stats);
150
151 for (i = 0; i < iface_stat->num_offload_stats; i++) {
152 offload_stats = &iface_stat->offload_stats[i];
153 buffer += len;
154 ll_stats.len += len;
155 len = scnprintf(buffer,
156 DEBUGFS_LLSTATS_BUF_SIZE - ll_stats.len,
157 "\ntype: %d, rx_count: %u, drp_count: %u, fwd_count: %u",
158 offload_stats->type, offload_stats->rx_count,
159 offload_stats->drp_count,
160 offload_stats->fwd_count);
161 }
162
163 powersave_stats = &iface_stat->powersave_stats;
164 buffer += len;
165 ll_stats.len += len;
166 len = scnprintf(buffer, DEBUGFS_LLSTATS_BUF_SIZE - ll_stats.len,
167 "\ntot_tim_bcn: %u tot_err_tim_bcn: %u",
168 powersave_stats->tot_tim_bcn,
169 powersave_stats->tot_err_tim_bcn);
170
171 ll_stats.len += len;
172 mutex_unlock(&llstats_mutex);
173 hdd_exit();
174 }
175
hdd_debugfs_process_peer_stats(struct hdd_adapter * adapter,void * data)176 void hdd_debugfs_process_peer_stats(struct hdd_adapter *adapter, void *data)
177 {
178 struct wifi_peer_stat *peer_stat;
179 struct wifi_peer_info *peer_info;
180 struct wifi_rate_stat *rate_stat;
181 int i, j, num_rate;
182 ssize_t len = 0;
183 uint8_t *buffer;
184
185 hdd_enter();
186
187 mutex_lock(&llstats_mutex);
188 if (!ll_stats.result) {
189 mutex_unlock(&llstats_mutex);
190 hdd_err("LL statistics buffer is NULL");
191 return;
192 }
193
194 peer_stat = data;
195
196 buffer = ll_stats.result;
197 buffer += ll_stats.len;
198 len = scnprintf(buffer, DEBUGFS_LLSTATS_BUF_SIZE - ll_stats.len,
199 "\n\n===LL_STATS_PEER_ALL : num_peers %u===",
200 peer_stat->num_peers);
201
202 peer_info = peer_stat->peer_info;
203 for (i = 1; i <= peer_stat->num_peers; i++) {
204 buffer += len;
205 ll_stats.len += len;
206 len = scnprintf(buffer,
207 DEBUGFS_LLSTATS_BUF_SIZE - ll_stats.len,
208 "\nType: %d, peer_mac: " QDF_MAC_ADDR_FMT
209 ", capabilities: %u\nnum_rates: %d",
210 wmi_to_sir_peer_type(peer_info->type),
211 QDF_MAC_ADDR_REF(&peer_info->peer_macaddr.bytes[0]),
212 peer_info->capabilities, peer_info->num_rate);
213
214 num_rate = peer_info->num_rate;
215 for (j = 0; j < num_rate; j++) {
216 rate_stat = &peer_info->rate_stats[j];
217 buffer += len;
218 ll_stats.len += len;
219 len = scnprintf(buffer,
220 DEBUGFS_LLSTATS_BUF_SIZE - ll_stats.len,
221 "\npreamble: %0x, nss: %0x, bw: %0x, mcs: %0x, bitrate: %0x, txmpdu: %u, rxmpdu: %u, mpdu_lost: %u, retries: %u, retries_short: %u, retries_long: %u",
222 rate_stat->rate.preamble, rate_stat->rate.nss,
223 rate_stat->rate.bw,
224 rate_stat->rate.rate_or_mcs_index,
225 rate_stat->rate.bitrate, rate_stat->tx_mpdu,
226 rate_stat->rx_mpdu, rate_stat->mpdu_lost,
227 rate_stat->retries, rate_stat->retries_short,
228 rate_stat->retries_long);
229 }
230 peer_info = (struct wifi_peer_info *) ((uint8_t *)
231 peer_stat->peer_info + (i *
232 sizeof(struct wifi_peer_info)) +
233 (num_rate * sizeof(struct wifi_rate_stat)));
234 }
235 ll_stats.len += len;
236 mutex_unlock(&llstats_mutex);
237 hdd_exit();
238
239 }
240
hdd_debugfs_process_radio_stats(struct hdd_adapter * adapter,uint32_t more_data,void * data,uint32_t num_radio)241 void hdd_debugfs_process_radio_stats(struct hdd_adapter *adapter,
242 uint32_t more_data, void *data, uint32_t num_radio)
243 {
244 int i, j;
245 ssize_t len = 0;
246 uint8_t *buffer;
247 struct wifi_radio_stats *radio_stat = (struct wifi_radio_stats *) data;
248 struct wifi_channel_stats *chan_stat;
249
250 hdd_enter();
251
252 mutex_lock(&llstats_mutex);
253 if (!ll_stats.result) {
254 mutex_unlock(&llstats_mutex);
255 hdd_err("LL statistics buffer is NULL");
256 return;
257 }
258
259 buffer = ll_stats.result;
260 buffer += ll_stats.len;
261 len = scnprintf(buffer, DEBUGFS_LLSTATS_BUF_SIZE - ll_stats.len,
262 "\n\n===LL_STATS_RADIO: number of radios: %u===",
263 num_radio);
264
265 for (i = 0; i < num_radio; i++) {
266 buffer += len;
267 ll_stats.len += len;
268 len = scnprintf(buffer,
269 DEBUGFS_LLSTATS_BUF_SIZE - ll_stats.len,
270 "\nRadio: %u on_time: %u, tx_time: %u, rx_time: %u, on_time_scan: %u, on_time_nbd: %u, on_time_gscan: %u, on_time_roam_scan: %u, on_time_pno_scan: %u on_time_hs20: %u, on_time_host_scan: %u, on_time_lpi_scan: %u\ntotal_num_tx_pwr_levels: %u\n",
271 radio_stat->radio, radio_stat->on_time,
272 radio_stat->tx_time, radio_stat->rx_time,
273 radio_stat->on_time_scan, radio_stat->on_time_nbd,
274 radio_stat->on_time_gscan,
275 radio_stat->on_time_roam_scan,
276 radio_stat->on_time_pno_scan,
277 radio_stat->on_time_hs20,
278 radio_stat->on_time_host_scan,
279 radio_stat->on_time_lpi_scan,
280 radio_stat->total_num_tx_power_levels);
281
282 for (j = 0; j < radio_stat->total_num_tx_power_levels; j++) {
283 buffer += len;
284 ll_stats.len += len;
285 len = scnprintf(buffer,
286 DEBUGFS_LLSTATS_BUF_SIZE - ll_stats.len,
287 "%d ", radio_stat->tx_time_per_power_level[j]);
288 }
289
290 buffer += len;
291 ll_stats.len += len;
292 len = scnprintf(buffer,
293 DEBUGFS_LLSTATS_BUF_SIZE - ll_stats.len,
294 "\nNum channels: %d", radio_stat->num_channels);
295
296 for (j = 0; j < radio_stat->num_channels; j++) {
297 chan_stat = (struct wifi_channel_stats *)
298 ((uint8_t *)radio_stat->channels +
299 (j * sizeof(struct wifi_channel_stats)));
300
301 buffer += len;
302 ll_stats.len += len;
303 len = scnprintf(buffer,
304 DEBUGFS_LLSTATS_BUF_SIZE - ll_stats.len,
305 "\nChan width: %u, center_freq: %u, center_freq0: %u, center_freq1: %u, on_time: %u, cca_busy_time: %u",
306 chan_stat->channel.width,
307 chan_stat->channel.center_freq,
308 chan_stat->channel.center_freq0,
309 chan_stat->channel.center_freq1,
310 chan_stat->on_time, chan_stat->cca_busy_time);
311
312 if (adapter->hdd_ctx &&
313 adapter->hdd_ctx->ll_stats_per_chan_rx_tx_time) {
314 buffer += len;
315 ll_stats.len += len;
316 len = scnprintf(
317 buffer,
318 DEBUGFS_LLSTATS_BUF_SIZE - ll_stats.len,
319 ", tx_time: %u, rx_time: %u",
320 chan_stat->tx_time, chan_stat->rx_time);
321 }
322 }
323
324 radio_stat++;
325 }
326 ll_stats.len += len;
327 mutex_unlock(&llstats_mutex);
328 hdd_exit();
329 }
330
wlan_hdd_llstats_free_buf(void)331 static inline void wlan_hdd_llstats_free_buf(void)
332 {
333 mutex_lock(&llstats_mutex);
334 qdf_mem_free(ll_stats.result);
335 ll_stats.result = NULL;
336 ll_stats.len = 0;
337 mutex_unlock(&llstats_mutex);
338 }
339
wlan_hdd_llstats_alloc_buf(void)340 static int wlan_hdd_llstats_alloc_buf(void)
341 {
342 mutex_lock(&llstats_mutex);
343 if (ll_stats.result) {
344 mutex_unlock(&llstats_mutex);
345 hdd_err("Buffer is already allocated");
346 return 0;
347 }
348 ll_stats.len = 0;
349 ll_stats.result = qdf_mem_malloc(DEBUGFS_LLSTATS_BUF_SIZE);
350 if (!ll_stats.result) {
351 mutex_unlock(&llstats_mutex);
352 return -EINVAL;
353 }
354 mutex_unlock(&llstats_mutex);
355 return 0;
356 }
357
358 /**
359 * hdd_debugfs_stats_update() - Update userspace with local statistics buffer
360 * @buf: userspace buffer (to which data is being copied into)
361 * @count: max data that can be copied into buf
362 * @pos: offset (where data should be copied into)
363 *
364 * This function should copies link layer statistics buffer into debugfs
365 * entry.
366 *
367 * Return: number of characters copied; 0 on no-copy
368 */
hdd_debugfs_stats_update(char __user * buf,size_t count,loff_t * pos)369 static ssize_t hdd_debugfs_stats_update(char __user *buf, size_t count,
370 loff_t *pos)
371 {
372 ssize_t ret_cnt;
373
374 hdd_enter();
375 mutex_lock(&llstats_mutex);
376 if (!ll_stats.result) {
377 mutex_unlock(&llstats_mutex);
378 hdd_err("Trying to read from NULL buffer");
379 return 0;
380 }
381
382 ret_cnt = simple_read_from_buffer(buf, count, pos,
383 ll_stats.result, ll_stats.len);
384 mutex_unlock(&llstats_mutex);
385 hdd_debug("LL stats read req: count: %zu, pos: %lld", count, *pos);
386
387 hdd_exit();
388 return ret_cnt;
389 }
390
391 /**
392 * __wlan_hdd_read_ll_stats_debugfs() - API to collect LL stats from FW
393 * @net_dev: net_device context used to register the debugfs file
394 * @buf: buffer
395 * @count: count
396 * @pos: position pointer
397 *
398 * Return: Number of bytes read on success, error number otherwise
399 */
__wlan_hdd_read_ll_stats_debugfs(struct net_device * net_dev,char __user * buf,size_t count,loff_t * pos)400 static ssize_t __wlan_hdd_read_ll_stats_debugfs(struct net_device *net_dev,
401 char __user *buf, size_t count,
402 loff_t *pos)
403 {
404 struct hdd_adapter *adapter = WLAN_HDD_GET_PRIV_PTR(net_dev);
405 struct hdd_context *hdd_ctx;
406 ssize_t ret;
407
408 hdd_enter();
409
410 if (adapter->magic != WLAN_HDD_ADAPTER_MAGIC) {
411 hdd_err("Invalid adapter or adapter has invalid magic");
412 return -EINVAL;
413 }
414
415 hdd_ctx = WLAN_HDD_GET_CTX(adapter);
416 ret = wlan_hdd_validate_context(hdd_ctx);
417 if (ret)
418 return ret;
419
420 /* All the events are received and buffer is populated */
421 ret = hdd_debugfs_stats_update(buf, count, pos);
422 hdd_debug("%zu characters written into debugfs", ret);
423
424 hdd_exit();
425
426 return ret;
427 }
428
429 /**
430 * wlan_hdd_read_ll_stats_debugfs() - SSR wrapper function to read LL debugfs
431 * @file: file pointer
432 * @buf: buffer
433 * @count: count
434 * @pos: position pointer
435 *
436 * Return: Number of bytes read on success, error number otherwise
437 */
wlan_hdd_read_ll_stats_debugfs(struct file * file,char __user * buf,size_t count,loff_t * pos)438 static ssize_t wlan_hdd_read_ll_stats_debugfs(struct file *file,
439 char __user *buf, size_t count,
440 loff_t *pos)
441 {
442 struct net_device *net_dev = file_inode(file)->i_private;
443 struct osif_vdev_sync *vdev_sync;
444 ssize_t err_size;
445
446 err_size = osif_vdev_sync_op_start(net_dev, &vdev_sync);
447 if (err_size)
448 return err_size;
449
450 err_size = __wlan_hdd_read_ll_stats_debugfs(net_dev, buf, count, pos);
451
452 osif_vdev_sync_op_stop(vdev_sync);
453
454 return err_size;
455 }
456
457 /**
458 * __wlan_hdd_open_ll_stats_debugfs() - Function to save private on open
459 * @net_dev: net_device context used to register the debugfs file
460 *
461 * Return: Errno
462 */
__wlan_hdd_open_ll_stats_debugfs(struct net_device * net_dev)463 static int __wlan_hdd_open_ll_stats_debugfs(struct net_device *net_dev)
464 {
465 struct hdd_adapter *adapter = WLAN_HDD_GET_PRIV_PTR(net_dev);
466 struct hdd_context *hdd_ctx;
467 int errno;
468
469 hdd_enter();
470
471 errno = hdd_validate_adapter(adapter);
472 if (errno)
473 return errno;
474
475 hdd_ctx = WLAN_HDD_GET_CTX(adapter);
476 errno = wlan_hdd_validate_context(hdd_ctx);
477 if (errno)
478 return errno;
479
480 errno = wlan_hdd_llstats_alloc_buf();
481 if (errno)
482 return errno;
483
484 errno = wlan_hdd_ll_stats_get(adapter->deflink,
485 DEBUGFS_LLSTATS_REQID,
486 DEBUGFS_LLSTATS_REQMASK);
487 if (errno)
488 goto free_buf;
489
490 hdd_exit();
491
492 return 0;
493
494 free_buf:
495 wlan_hdd_llstats_free_buf();
496
497 hdd_exit();
498
499 return errno;
500 }
501
502 /**
503 * wlan_hdd_open_ll_stats_debugfs() - SSR wrapper function to save private
504 * on open
505 * @inode: Pointer to inode structure
506 * @file: file pointer
507 *
508 * Return: Errno
509 */
wlan_hdd_open_ll_stats_debugfs(struct inode * inode,struct file * file)510 static int wlan_hdd_open_ll_stats_debugfs(struct inode *inode,
511 struct file *file)
512 {
513 struct net_device *net_dev = inode->i_private;
514 struct osif_vdev_sync *vdev_sync;
515 int errno;
516
517 errno = osif_vdev_sync_op_start(net_dev, &vdev_sync);
518 if (errno)
519 return errno;
520
521 errno = __wlan_hdd_open_ll_stats_debugfs(net_dev);
522
523 osif_vdev_sync_op_stop(vdev_sync);
524
525 return errno;
526 }
527
528 /**
529 * wlan_hdd_release_ll_stats_debugfs() - SSR wrapper function to save private
530 * on release
531 * @inode: Pointer to inode structure
532 * @file: file pointer
533 *
534 * Return: Errno
535 */
wlan_hdd_release_ll_stats_debugfs(struct inode * inode,struct file * file)536 static int wlan_hdd_release_ll_stats_debugfs(struct inode *inode,
537 struct file *file)
538 {
539 /* Memory allocated during open_ll_stats_debugfs is static to this file
540 * and not related to vdev/psoc, and hence it can be freed without DSC
541 * protection during release file op.
542 *
543 * Since ll_stats buffer is allocated during debugfs file open
544 * it needs to be freed in file release but, DSC vdev op-protection is
545 * not needed for releasing the ll_stats buffer. Adding DSC protection
546 * will lead to resource leak because DSC will reject file release
547 * op call if it is in the middle of vdev/psoc/driver transition.
548 */
549 wlan_hdd_llstats_free_buf();
550
551 return 0;
552 }
553
554 static const struct file_operations fops_ll_stats_debugfs = {
555 .read = wlan_hdd_read_ll_stats_debugfs,
556 .open = wlan_hdd_open_ll_stats_debugfs,
557 .release = wlan_hdd_release_ll_stats_debugfs,
558 .owner = THIS_MODULE,
559 .llseek = default_llseek,
560 };
561
wlan_hdd_create_ll_stats_file(struct hdd_adapter * adapter)562 int wlan_hdd_create_ll_stats_file(struct hdd_adapter *adapter)
563 {
564 if (!debugfs_create_file("ll_stats", 0444, adapter->debugfs_phy,
565 adapter->dev, &fops_ll_stats_debugfs))
566 return -EINVAL;
567
568 return 0;
569 }
570