1  /*
2   * Copyright (c) 2018-2020 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_roam.c
22   *
23   * WLAN Host Device Driver implementation to update
24   * debugfs with roaming information
25   */
26  
27  #include <wlan_hdd_debugfs_csr.h>
28  #include <wlan_hdd_main.h>
29  #include <cds_sched.h>
30  #include <wma_api.h>
31  #include "qwlan_version.h"
32  #include "wmi_unified_param.h"
33  #include "wlan_osif_request_manager.h"
34  
35  /**
36   * hdd_roam_scan_stats_debugfs_dealloc() - Dealloc objects in hdd request mgr
37   * @priv: Pointer to private data of hdd request object
38   *
39   * Return: None
40   */
hdd_roam_scan_stats_debugfs_dealloc(void * priv)41  static void hdd_roam_scan_stats_debugfs_dealloc(void *priv)
42  {
43  	struct hdd_roam_scan_stats_debugfs_priv *local_priv = priv;
44  	struct wmi_roam_scan_stats_res *roam_scan_stats_res;
45  
46  	if (!local_priv)
47  		return;
48  
49  	roam_scan_stats_res = local_priv->roam_scan_stats_res;
50  	local_priv->roam_scan_stats_res = NULL;
51  
52  	qdf_mem_free(roam_scan_stats_res);
53  }
54  
55  /**
56   * hdd_roam_scan_stats_cb() - Call back invoked from roam scan stats evt
57   * @context: cookie to get request object
58   * @res: roam scan stats response from firmware
59   *
60   * Return: None
61   */
62  static void
hdd_roam_scan_stats_cb(void * context,struct wmi_roam_scan_stats_res * res)63  hdd_roam_scan_stats_cb(void *context, struct wmi_roam_scan_stats_res *res)
64  {
65  	struct osif_request *request;
66  	struct hdd_roam_scan_stats_debugfs_priv *priv;
67  	struct wmi_roam_scan_stats_res *stats_res;
68  	uint32_t total_len;
69  
70  	hdd_enter();
71  
72  	request = osif_request_get(context);
73  	if (!request) {
74  		hdd_err("Obsolete roam scan stats request");
75  		return;
76  	}
77  
78  	if (!res) {
79  		hdd_err("Invalid response");
80  		goto end;
81  	}
82  
83  	priv = osif_request_priv(request);
84  
85  	total_len = sizeof(*res) + res->num_roam_scans *
86  		    sizeof(struct wmi_roam_scan_stats_params);
87  
88  	stats_res = qdf_mem_malloc(total_len);
89  	if (!stats_res)
90  		goto end;
91  
92  	qdf_mem_copy(stats_res, res, total_len);
93  	priv->roam_scan_stats_res = stats_res;
94  
95  end:
96  	osif_request_complete(request);
97  	osif_request_put(request);
98  
99  	hdd_exit();
100  }
101  
102  /**
103   * hdd_get_roam_scan_stats() - Get roam scan stats using request manager
104   * @hdd_ctx: hdd context
105   * @adapter: pointer to adapter
106   *
107   * Return: Pointer to struct wmi_roam_scan_stats_res which contains response
108   * from firmware
109   */
110  static struct
hdd_get_roam_scan_stats(struct hdd_context * hdd_ctx,struct hdd_adapter * adapter)111  wmi_roam_scan_stats_res *hdd_get_roam_scan_stats(struct hdd_context *hdd_ctx,
112  						 struct hdd_adapter *adapter)
113  {
114  	struct wmi_roam_scan_stats_res *res;
115  	struct wmi_roam_scan_stats_res *stats_res = NULL;
116  	void *context;
117  	struct osif_request *request;
118  	struct hdd_roam_scan_stats_debugfs_priv *priv;
119  	static const struct osif_request_params params = {
120  		.priv_size = sizeof(*priv),
121  		.timeout_ms = WLAN_WAIT_TIME_FW_ROAM_STATS,
122  		.dealloc = hdd_roam_scan_stats_debugfs_dealloc,
123  	};
124  	QDF_STATUS status;
125  	int ret;
126  	uint32_t total_len;
127  
128  	hdd_enter();
129  
130  	if (!wlan_hdd_validate_modules_state(hdd_ctx))
131  		return NULL;
132  
133  	request = osif_request_alloc(&params);
134  	if (!request) {
135  		hdd_err("Request allocation failure");
136  		return NULL;
137  	}
138  
139  	context = osif_request_cookie(request);
140  
141  	status = sme_get_roam_scan_stats(hdd_ctx->mac_handle,
142  					 hdd_roam_scan_stats_cb,
143  					 context, adapter->deflink->vdev_id);
144  	if (!QDF_IS_STATUS_SUCCESS(status)) {
145  		hdd_err("roam scan stats request failed");
146  		goto cleanup;
147  	}
148  
149  	ret = osif_request_wait_for_response(request);
150  	if (ret) {
151  		hdd_err("roam scan stats response time out");
152  		goto cleanup;
153  	}
154  
155  	priv = osif_request_priv(request);
156  	res = priv->roam_scan_stats_res;
157  	if (!res) {
158  		hdd_err("Failure of roam scan stats response retrieval");
159  		goto cleanup;
160  	}
161  
162  	total_len = sizeof(*res) + res->num_roam_scans *
163  		    sizeof(struct wmi_roam_scan_stats_params);
164  
165  	stats_res = qdf_mem_malloc(total_len);
166  	if (!stats_res)
167  		goto cleanup;
168  
169  	qdf_mem_copy(stats_res, res, total_len);
170  
171  cleanup:
172  	osif_request_put(request);
173  	hdd_exit();
174  
175  	return stats_res;
176  }
177  
178  /**
179   * hdd_roam_scan_trigger_to_str() - Get string for
180   * enum WMI_ROAM_TRIGGER_REASON_ID
181   * @roam_scan_trigger: roam scan trigger ID
182   *
183   * Return: Meaningful string from enum WMI_ROAM_TRIGGER_REASON_ID
184   */
hdd_roam_scan_trigger_to_str(uint32_t roam_scan_trigger)185  static char *hdd_roam_scan_trigger_to_str(uint32_t roam_scan_trigger)
186  {
187  	switch (roam_scan_trigger) {
188  	case WMI_ROAM_TRIGGER_REASON_PER:
189  		return "PER";
190  	case WMI_ROAM_TRIGGER_REASON_BMISS:
191  		return "BEACON MISS";
192  	case WMI_ROAM_TRIGGER_REASON_LOW_RSSI:
193  		return "LOW RSSI";
194  	case WMI_ROAM_TRIGGER_REASON_HIGH_RSSI:
195  		return "HIGH RSSI";
196  	case WMI_ROAM_TRIGGER_REASON_PERIODIC:
197  		return "PERIODIC SCAN";
198  	case WMI_ROAM_TRIGGER_REASON_MAWC:
199  		return "WMI_ROAM_TRIGGER_REASON_MAWC";
200  	case WMI_ROAM_TRIGGER_REASON_DENSE:
201  		return "DENSE ENVIRONMENT";
202  	case WMI_ROAM_TRIGGER_REASON_BACKGROUND:
203  		return "BACKGROUND SCAN";
204  	case WMI_ROAM_TRIGGER_REASON_FORCED:
205  		return "FORCED SCAN";
206  	case WMI_ROAM_TRIGGER_REASON_BTM:
207  		return "BTM TRIGGER";
208  	case WMI_ROAM_TRIGGER_REASON_UNIT_TEST:
209  		return "TEST COMMAND";
210  	default:
211  		return "UNKNOWN REASON";
212  	}
213  	return "UNKNOWN REASON";
214  }
215  
216  /**
217   * hdd_roam_scan_trigger_value() - Get trigger value string for
218   * enum WMI_ROAM_TRIGGER_REASON_ID
219   * @roam_scan_trigger: roam scan trigger ID
220   * @print: output pointer to hold whether to print trigger value
221   *
222   * Return: Meaningful string from trigger value
223   */
hdd_roam_scan_trigger_value(uint32_t roam_scan_trigger,bool * print)224  static char *hdd_roam_scan_trigger_value(uint32_t roam_scan_trigger,
225  					 bool *print)
226  {
227  	*print = true;
228  
229  	switch (roam_scan_trigger) {
230  	case WMI_ROAM_TRIGGER_REASON_PER:
231  		return "percentage";
232  	case WMI_ROAM_TRIGGER_REASON_LOW_RSSI:
233  		return "dB";
234  	case WMI_ROAM_TRIGGER_REASON_HIGH_RSSI:
235  		return "dB";
236  	case WMI_ROAM_TRIGGER_REASON_PERIODIC:
237  		return "ms";
238  	case WMI_ROAM_TRIGGER_REASON_DENSE:
239  		return "(1 - Rx, 2 - Tx)";
240  	default:
241  		*print = false;
242  		return NULL;
243  	}
244  }
245  
246  /**
247   * hdd_client_id_to_str() - Helper func to get meaningful string from client id
248   * @client_id: Id of the client which triggered roam scan in firmware
249   *
250   * Return: Meaningful string from enum WMI_SCAN_CLIENT_ID
251   */
hdd_client_id_to_str(uint32_t client_id)252  static char *hdd_client_id_to_str(uint32_t client_id)
253  {
254  	switch (client_id) {
255  	case WMI_SCAN_CLIENT_NLO:
256  		return "PNO";
257  	case WMI_SCAN_CLIENT_EXTSCAN:
258  		return "GSCAN";
259  	case WMI_SCAN_CLIENT_ROAM:
260  		return "ROAM";
261  	case WMI_SCAN_CLIENT_P2P:
262  		return "P2P";
263  	case WMI_SCAN_CLIENT_LPI:
264  		return "LPI";
265  	case WMI_SCAN_CLIENT_NAN:
266  		return "NAN";
267  	case WMI_SCAN_CLIENT_ANQP:
268  		return "ANQP";
269  	case WMI_SCAN_CLIENT_OBSS:
270  		return "OBSS";
271  	case WMI_SCAN_CLIENT_PLM:
272  		return "PLM";
273  	case WMI_SCAN_CLIENT_HOST:
274  		return "HOST";
275  	default:
276  		return "UNKNOWN";
277  	}
278  	return "UNKNOWN";
279  }
280  
281  /**
282   * hdd_roam_scan_trigger() - Print roam scan trigger info into buffer
283   * @scan: roam scan event data
284   * @buf: buffer to write roam scan trigger info
285   * @buf_avail_len: available buffer length
286   *
287   * Return: No.of bytes populated by this function in buffer
288   */
289  static ssize_t
hdd_roam_scan_trigger(struct wmi_roam_scan_stats_params * scan,uint8_t * buf,ssize_t buf_avail_len)290  hdd_roam_scan_trigger(struct wmi_roam_scan_stats_params *scan,
291  		      uint8_t *buf, ssize_t buf_avail_len)
292  {
293  	ssize_t length = 0;
294  	int ret;
295  	char *str;
296  	bool print_trigger_value;
297  
298  	ret = scnprintf(buf, buf_avail_len,
299  			"Trigger reason is %s\n",
300  			hdd_roam_scan_trigger_to_str(scan->trigger_id));
301  	if (ret <= 0)
302  		return length;
303  
304  	length = ret;
305  
306  	str = hdd_roam_scan_trigger_value(scan->trigger_id,
307  					  &print_trigger_value);
308  	if (!print_trigger_value || !str)
309  		return length;
310  
311  	if (length >= buf_avail_len) {
312  		hdd_err("No sufficient buf_avail_len");
313  		length = buf_avail_len;
314  		return length;
315  	}
316  
317  	ret = scnprintf(buf + length, buf_avail_len - length,
318  			"Trigger value is: %u %s\n",
319  			scan->trigger_value, str);
320  	if (ret <= 0)
321  		return length;
322  
323  	length += ret;
324  	return length;
325  }
326  
327  /**
328   * hdd_roam_scan_chan() - Print roam scan chan freq info into buffer
329   * @scan: roam scan event data
330   * @buf: buffer to write roam scan freq info
331   * @buf_avail_len: available buffer length
332   *
333   * Return: No.of bytes populated by this function in buffer
334   */
335  static ssize_t
hdd_roam_scan_chan(struct wmi_roam_scan_stats_params * scan,uint8_t * buf,ssize_t buf_avail_len)336  hdd_roam_scan_chan(struct wmi_roam_scan_stats_params *scan,
337  		   uint8_t *buf, ssize_t buf_avail_len)
338  {
339  	ssize_t length = 0;
340  	uint32_t i;
341  	int ret;
342  
343  	ret = scnprintf(buf, buf_avail_len,
344  			"Num of scan channels: %u\n"
345  			"scan channel list:",
346  			scan->num_scan_chans);
347  	if (ret <= 0)
348  		return length;
349  
350  	length = ret;
351  
352  	for (i = 0; i < scan->num_scan_chans; i++) {
353  		if (length >= buf_avail_len) {
354  			hdd_err("No sufficient buf_avail_len");
355  			length = buf_avail_len;
356  			return length;
357  		}
358  
359  		ret = scnprintf(buf + length, buf_avail_len - length,
360  				"%u ", scan->scan_freqs[i]);
361  		if (ret <= 0)
362  			return length;
363  
364  		length += ret;
365  	}
366  
367  	return length;
368  }
369  
370  /**
371   * wlan_hdd_update_roam_stats() - Internal function to get roam scan stats
372   * @hdd_ctx: hdd context
373   * @adapter: pointer to adapter
374   * @buf: buffer to hold the stats
375   * @buf_avail_len: maximum available length in response buffer
376   *
377   * Return: Size of formatted roam scan response stats
378   */
379  static ssize_t
wlan_hdd_update_roam_stats(struct hdd_context * hdd_ctx,struct hdd_adapter * adapter,uint8_t * buf,ssize_t buf_avail_len)380  wlan_hdd_update_roam_stats(struct hdd_context *hdd_ctx,
381  			   struct hdd_adapter *adapter,
382  			   uint8_t *buf, ssize_t buf_avail_len)
383  {
384  	ssize_t length = 0;
385  	struct wmi_roam_scan_stats_res *roam_stats;
386  	struct wmi_roam_scan_stats_params *scan;
387  	int ret;
388  	int rsi; /* roam scan iterator */
389  	int rci; /* roam candidate iterator */
390  
391  	roam_stats = hdd_get_roam_scan_stats(hdd_ctx, adapter);
392  	if (!roam_stats) {
393  		hdd_err("Couldn't get roam stats");
394  		ret = scnprintf(buf, buf_avail_len,
395  				"Failed to fetch roam stats\n");
396  		if (ret <= 0)
397  			return length;
398  		length += ret;
399  		return length;
400  	}
401  
402  	ret = scnprintf(buf, buf_avail_len,
403  			"\n\nStats of last %u roam scans\n",
404  			roam_stats->num_roam_scans);
405  	if (ret <= 0)
406  		goto free_mem;
407  	length += ret;
408  
409  	for (rsi = 0; rsi < roam_stats->num_roam_scans; rsi++) {
410  		if (length >= buf_avail_len) {
411  			hdd_err("No sufficient buf_avail_len");
412  			length = buf_avail_len;
413  			goto free_mem;
414  		}
415  
416  		scan = &roam_stats->roam_scan[rsi];
417  		ret = scnprintf(buf + length, buf_avail_len - length,
418  				"\nRoam scan[%u] details\n", rsi);
419  		if (ret <= 0)
420  			goto free_mem;
421  		length += ret;
422  
423  		if (length >= buf_avail_len) {
424  			hdd_err("No sufficient buf_avail_len");
425  			length = buf_avail_len;
426  			goto free_mem;
427  		}
428  
429  		ret = scnprintf(buf + length, buf_avail_len - length,
430  				"This scan is triggered by \"%s\" scan client\n",
431  				hdd_client_id_to_str(scan->client_id));
432  
433  		if (ret <= 0)
434  			goto free_mem;
435  		length += ret;
436  
437  		if (length >= buf_avail_len) {
438  			hdd_err("No sufficient buf_avail_len");
439  			length = buf_avail_len;
440  			goto free_mem;
441  		}
442  
443  		length += hdd_roam_scan_trigger(scan, buf + length,
444  						buf_avail_len - length);
445  		if (length >= buf_avail_len) {
446  			hdd_err("No sufficient buf_avail_len");
447  			length = buf_avail_len;
448  			goto free_mem;
449  		}
450  
451  		length += hdd_roam_scan_chan(scan, buf + length,
452  					     buf_avail_len - length);
453  		if (length >= buf_avail_len) {
454  			hdd_err("No sufficient buf_avail_len");
455  			length = buf_avail_len;
456  			goto free_mem;
457  		}
458  
459  		ret = scnprintf(buf + length, buf_avail_len - length,
460  				"\nRoam Scan time: 0x%llx\n",
461  				roam_stats->roam_scan[rsi].time_stamp);
462  		if (ret <= 0)
463  			goto free_mem;
464  		length += ret;
465  
466  		if (length >= buf_avail_len) {
467  			hdd_err("No sufficient buf_avail_len");
468  			length = buf_avail_len;
469  			goto free_mem;
470  		}
471  
472  		if (scan->is_roam_successful) {
473  			ret = scnprintf(buf + length,
474  					buf_avail_len - length,
475  					"\nSTA roamed from "
476  					QDF_MAC_ADDR_FMT " to "
477  					QDF_MAC_ADDR_FMT "\n",
478  					QDF_MAC_ADDR_REF(scan->old_bssid),
479  					QDF_MAC_ADDR_REF(scan->new_bssid));
480  		} else {
481  			ret = scnprintf(buf + length,
482  					buf_avail_len - length,
483  					"\nSTA is connected to " QDF_MAC_ADDR_FMT
484  					" before and after scan, not roamed\n",
485  					QDF_MAC_ADDR_REF(scan->old_bssid));
486  		}
487  		if (ret <= 0)
488  			goto free_mem;
489  		length += ret;
490  
491  		if (length >= buf_avail_len) {
492  			hdd_err("No sufficient buf_avail_len");
493  			length = buf_avail_len;
494  			goto free_mem;
495  		}
496  
497  		ret = scnprintf(buf + length, buf_avail_len - length,
498  				"Roam candidate details\n");
499  		if (ret <= 0)
500  			goto free_mem;
501  		length += ret;
502  
503  		if (length >= buf_avail_len) {
504  			hdd_err("No sufficient buf_avail_len");
505  			length = buf_avail_len;
506  			goto free_mem;
507  		}
508  
509  		ret = scnprintf(buf + length, buf_avail_len - length,
510  				"      BSSID     FREQ   SCORE  RSSI\n");
511  		if (ret <= 0)
512  			goto free_mem;
513  		length += ret;
514  
515  		for (rci = 0; rci < scan->num_roam_candidates; rci++) {
516  			uint8_t *bssid = scan->cand[rci].bssid;
517  
518  			if (length >= buf_avail_len) {
519  				hdd_err("No sufficient buf_avail_len");
520  				length = buf_avail_len;
521  				goto free_mem;
522  			}
523  
524  			ret = scnprintf(buf + length,
525  					buf_avail_len - length,
526  					QDF_MAC_ADDR_FMT " %4u  %3u   %3u\n",
527  					QDF_MAC_ADDR_REF(bssid),
528  					scan->cand[rci].freq,
529  					scan->cand[rci].score,
530  					scan->cand[rci].rssi);
531  			if (ret <= 0)
532  				goto free_mem;
533  			length += ret;
534  		}
535  	}
536  
537  free_mem:
538  	qdf_mem_free(roam_stats);
539  	return length;
540  }
541  
542  ssize_t
wlan_hdd_debugfs_update_roam_stats(struct hdd_context * hdd_ctx,struct hdd_adapter * adapter,uint8_t * buf,ssize_t buf_avail_len)543  wlan_hdd_debugfs_update_roam_stats(struct hdd_context *hdd_ctx,
544  				   struct hdd_adapter *adapter,
545  				   uint8_t *buf, ssize_t buf_avail_len)
546  {
547  	ssize_t len = 0;
548  	int ret_val;
549  
550  	hdd_enter();
551  
552  	len = wlan_hdd_current_time_info_debugfs(buf, buf_avail_len - len);
553  
554  	if (len >= buf_avail_len) {
555  		hdd_err("No sufficient buf_avail_len");
556  		return buf_avail_len;
557  	}
558  	if (adapter->device_mode != QDF_STA_MODE) {
559  		ret_val = scnprintf(buf + len, buf_avail_len - len,
560  				    "Interface is not in STA Mode\n");
561  		if (ret_val <= 0)
562  			return len;
563  
564  		len += ret_val;
565  		return len;
566  	}
567  
568  	if (len >= buf_avail_len) {
569  		hdd_err("No sufficient buf_avail_len");
570  		return buf_avail_len;
571  	}
572  	len += wlan_hdd_update_roam_stats(hdd_ctx, adapter, buf + len,
573  					  buf_avail_len - len);
574  
575  	hdd_exit();
576  
577  	return len;
578  }
579