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