| /* |
| * Copyright (c) 2018 The Linux Foundation. All rights reserved. |
| * |
| * Previously licensed under the ISC license by Qualcomm Atheros, Inc. |
| * |
| * |
| * Permission to use, copy, modify, and/or distribute this software for |
| * any purpose with or without fee is hereby granted, provided that the |
| * above copyright notice and this permission notice appear in all |
| * copies. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL |
| * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE |
| * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL |
| * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR |
| * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER |
| * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR |
| * PERFORMANCE OF THIS SOFTWARE. |
| */ |
| |
| /** |
| * DOC: wlan_hdd_debugfs_roam.c |
| * |
| * WLAN Host Device Driver implementation to update |
| * debugfs with roaming information |
| */ |
| |
| #include <wlan_hdd_debugfs_csr.h> |
| #include <wlan_hdd_main.h> |
| #include <cds_sched.h> |
| #include <wma_api.h> |
| #include "qwlan_version.h" |
| #include "wmi_unified_param.h" |
| #include "wlan_osif_request_manager.h" |
| |
| /** |
| * hdd_roam_scan_stats_debugfs_dealloc() - Dealloc objects in hdd request mgr |
| * @priv: Pointer to private data of hdd request object |
| * |
| * Return: None |
| */ |
| static void hdd_roam_scan_stats_debugfs_dealloc(void *priv) |
| { |
| struct hdd_roam_scan_stats_debugfs_priv *local_priv = priv; |
| struct wmi_roam_scan_stats_res *roam_scan_stats_res; |
| |
| if (!local_priv) |
| return; |
| |
| roam_scan_stats_res = local_priv->roam_scan_stats_res; |
| local_priv->roam_scan_stats_res = NULL; |
| |
| qdf_mem_free(roam_scan_stats_res); |
| } |
| |
| /** |
| * hdd_roam_scan_stats_cb() - Call back invoked from roam scan stats evt |
| * @context: cookie to get request object |
| * @res: roam scan stats response from firmware |
| * |
| * Return: None |
| */ |
| static void |
| hdd_roam_scan_stats_cb(void *context, struct wmi_roam_scan_stats_res *res) |
| { |
| struct osif_request *request; |
| struct hdd_roam_scan_stats_debugfs_priv *priv; |
| struct wmi_roam_scan_stats_res *stats_res; |
| uint32_t total_len; |
| |
| hdd_enter(); |
| |
| request = osif_request_get(context); |
| if (!request) { |
| hdd_err("Obsolete roam scan stats request"); |
| return; |
| } |
| |
| if (!res) { |
| hdd_err("Invalid response"); |
| goto end; |
| } |
| |
| priv = osif_request_priv(request); |
| |
| total_len = sizeof(*res) + res->num_roam_scans * |
| sizeof(struct wmi_roam_scan_stats_params); |
| |
| stats_res = qdf_mem_malloc(total_len); |
| if (!stats_res) { |
| hdd_err("No memory for response"); |
| goto end; |
| } |
| |
| qdf_mem_copy(stats_res, res, total_len); |
| priv->roam_scan_stats_res = stats_res; |
| |
| end: |
| osif_request_complete(request); |
| osif_request_put(request); |
| |
| hdd_exit(); |
| } |
| |
| /** |
| * hdd_get_roam_scan_stats() - Get roam scan stats using request manager |
| * @hdd_ctx: hdd context |
| * @adapter: pointer to adapter |
| * |
| * Return: Pointer to struct wmi_roam_scan_stats_res which conatins response |
| * from firmware |
| */ |
| static struct |
| wmi_roam_scan_stats_res *hdd_get_roam_scan_stats(struct hdd_context *hdd_ctx, |
| struct hdd_adapter *adapter) |
| { |
| struct wmi_roam_scan_stats_res *res; |
| struct wmi_roam_scan_stats_res *stats_res = NULL; |
| void *context; |
| struct osif_request *request; |
| struct hdd_roam_scan_stats_debugfs_priv *priv; |
| static const struct osif_request_params params = { |
| .priv_size = sizeof(*priv), |
| .timeout_ms = WLAN_WAIT_TIME_FW_ROAM_STATS, |
| .dealloc = hdd_roam_scan_stats_debugfs_dealloc, |
| }; |
| QDF_STATUS status; |
| int ret; |
| uint32_t total_len; |
| |
| hdd_enter(); |
| |
| if (!wlan_hdd_validate_modules_state(hdd_ctx)) |
| return NULL; |
| |
| request = osif_request_alloc(¶ms); |
| if (!request) { |
| hdd_err("Request allocation failure"); |
| return NULL; |
| } |
| |
| context = osif_request_cookie(request); |
| |
| status = sme_get_roam_scan_stats(hdd_ctx->mac_handle, |
| hdd_roam_scan_stats_cb, |
| context, adapter->session_id); |
| if (!QDF_IS_STATUS_SUCCESS(status)) { |
| hdd_err("roam scan stats request failed"); |
| goto cleanup; |
| } |
| |
| ret = osif_request_wait_for_response(request); |
| if (ret) { |
| hdd_err("roam scan stats response time out"); |
| goto cleanup; |
| } |
| |
| priv = osif_request_priv(request); |
| res = priv->roam_scan_stats_res; |
| if (!res) { |
| hdd_err("Failure of roam scan stats response retrieval"); |
| goto cleanup; |
| } |
| |
| total_len = sizeof(*res) + res->num_roam_scans * |
| sizeof(struct wmi_roam_scan_stats_params); |
| |
| stats_res = qdf_mem_malloc(total_len); |
| if (!stats_res) { |
| hdd_err("No memory for response"); |
| goto cleanup; |
| } |
| |
| qdf_mem_copy(stats_res, res, total_len); |
| |
| cleanup: |
| osif_request_put(request); |
| hdd_exit(); |
| |
| return stats_res; |
| } |
| |
| /** |
| * hdd_roam_scan_trigger_to_str() - Get string for |
| * enum WMI_ROAM_TRIGGER_REASON_ID |
| * @roam_scan_trigger: roam scan trigger ID |
| * |
| * Return: Meaningful string from enum WMI_ROAM_TRIGGER_REASON_ID |
| */ |
| static char *hdd_roam_scan_trigger_to_str(uint32_t roam_scan_trigger) |
| { |
| switch (roam_scan_trigger) { |
| case WMI_ROAM_TRIGGER_REASON_PER: |
| return "PER"; |
| case WMI_ROAM_TRIGGER_REASON_BMISS: |
| return "BEACON MISS"; |
| case WMI_ROAM_TRIGGER_REASON_LOW_RSSI: |
| return "LOW RSSI"; |
| case WMI_ROAM_TRIGGER_REASON_HIGH_RSSI: |
| return "HIGH RSSI"; |
| case WMI_ROAM_TRIGGER_REASON_PERIODIC: |
| return "PERIODIC SCAN"; |
| case WMI_ROAM_TRIGGER_REASON_MAWC: |
| return "WMI_ROAM_TRIGGER_REASON_MAWC"; |
| case WMI_ROAM_TRIGGER_REASON_DENSE: |
| return "DENSE ENVIRONMENT"; |
| case WMI_ROAM_TRIGGER_REASON_BACKGROUND: |
| return "BACKGROUND SCAN"; |
| case WMI_ROAM_TRIGGER_REASON_FORCED: |
| return "FORCED SCAN"; |
| case WMI_ROAM_TRIGGER_REASON_BTM: |
| return "BTM TRIGGER"; |
| case WMI_ROAM_TRIGGER_REASON_UNIT_TEST: |
| return "TEST COMMMAND"; |
| default: |
| return "UNKNOWN REASON"; |
| } |
| return "UNKNOWN REASON"; |
| } |
| |
| /** |
| * hdd_roam_scan_trigger_value_to_str() - Get trigger value string for |
| * enum WMI_ROAM_TRIGGER_REASON_ID |
| * @roam_scan_trigger: roam scan trigger ID |
| * @bool: output pointer to hold whether to print trigger value |
| * |
| * Return: Meaningful string from trigger value |
| */ |
| static char *hdd_roam_scan_trigger_value(uint32_t roam_scan_trigger, |
| bool *print) |
| { |
| *print = true; |
| |
| switch (roam_scan_trigger) { |
| case WMI_ROAM_TRIGGER_REASON_PER: |
| return "percentage"; |
| case WMI_ROAM_TRIGGER_REASON_LOW_RSSI: |
| return "dB"; |
| case WMI_ROAM_TRIGGER_REASON_HIGH_RSSI: |
| return "dB"; |
| case WMI_ROAM_TRIGGER_REASON_PERIODIC: |
| return "ms"; |
| case WMI_ROAM_TRIGGER_REASON_DENSE: |
| return "(1 - Rx, 2 - Tx)"; |
| default: |
| *print = false; |
| return NULL; |
| } |
| } |
| |
| /** |
| * hdd_client_id_to_str() - Helper func to get meaninful string from client id |
| * @client_id: Id of the client which triggered roam scan in firmware |
| * |
| * Return: Meaningful string from enum WMI_SCAN_CLIENT_ID |
| */ |
| static char *hdd_client_id_to_str(uint32_t client_id) |
| { |
| switch (client_id) { |
| case WMI_SCAN_CLIENT_NLO: |
| return "PNO"; |
| case WMI_SCAN_CLIENT_EXTSCAN: |
| return "GSCAN"; |
| case WMI_SCAN_CLIENT_ROAM: |
| return "ROAM"; |
| case WMI_SCAN_CLIENT_P2P: |
| return "P2P"; |
| case WMI_SCAN_CLIENT_LPI: |
| return "LPI"; |
| case WMI_SCAN_CLIENT_NAN: |
| return "NAN"; |
| case WMI_SCAN_CLIENT_ANQP: |
| return "ANQP"; |
| case WMI_SCAN_CLIENT_OBSS: |
| return "OBSS"; |
| case WMI_SCAN_CLIENT_PLM: |
| return "PLM"; |
| case WMI_SCAN_CLIENT_HOST: |
| return "HOST"; |
| default: |
| return "UNKNOWN"; |
| } |
| return "UNKNOWN"; |
| } |
| |
| /** |
| * hdd_roam_scan_trigger() - Print roam scan trigger info into buffer |
| * @scan: roam scan event data |
| * @buf: buffer to write roam scan trigger info |
| * @buf_avail_len: available buffer length |
| * |
| * Return: No.of bytes populated by this function in buffer |
| */ |
| static ssize_t |
| hdd_roam_scan_trigger(struct wmi_roam_scan_stats_params *scan, |
| uint8_t *buf, ssize_t buf_avail_len) |
| { |
| ssize_t length = 0; |
| int ret; |
| char *str; |
| bool print_trigger_value; |
| |
| ret = scnprintf(buf, buf_avail_len, |
| "Trigger reason is %s\n", |
| hdd_roam_scan_trigger_to_str(scan->trigger_id)); |
| if (ret <= 0) |
| return length; |
| |
| length = ret; |
| |
| str = hdd_roam_scan_trigger_value(scan->trigger_id, |
| &print_trigger_value); |
| if (!print_trigger_value || !str) |
| return length; |
| |
| if (length >= buf_avail_len) { |
| hdd_err("No sufficient buf_avail_len"); |
| length = buf_avail_len; |
| return length; |
| } |
| |
| ret = scnprintf(buf + length, buf_avail_len - length, |
| "Trigger value is: %u %s\n", |
| scan->trigger_value, str); |
| if (ret <= 0) |
| return length; |
| |
| length += ret; |
| return length; |
| } |
| |
| /** |
| * hdd_roam_scan_chan() - Print roam scan chan freq info into buffer |
| * @scan: roam scan event data |
| * @buf: buffer to write roam scan freq info |
| * @buf_avail_len: available buffer length |
| * |
| * Return: No.of bytes populated by this function in buffer |
| */ |
| static ssize_t |
| hdd_roam_scan_chan(struct wmi_roam_scan_stats_params *scan, |
| uint8_t *buf, ssize_t buf_avail_len) |
| { |
| ssize_t length = 0; |
| uint32_t i; |
| int ret; |
| |
| ret = scnprintf(buf, buf_avail_len, |
| "Num of scan channels: %u\n" |
| "scan channel list:", |
| scan->num_scan_chans); |
| if (ret <= 0) |
| return length; |
| |
| length = ret; |
| |
| for (i = 0; i < scan->num_scan_chans; i++) { |
| if (length >= buf_avail_len) { |
| hdd_err("No sufficient buf_avail_len"); |
| length = buf_avail_len; |
| return length; |
| } |
| |
| ret = scnprintf(buf + length, buf_avail_len - length, |
| "%u ", scan->scan_freqs[i]); |
| if (ret <= 0) |
| return length; |
| |
| length += ret; |
| } |
| |
| return length; |
| } |
| |
| /** |
| * wlan_hdd_update_roam_stats() - Internal function to get roam scan stats |
| * @hdd_ctx: hdd context |
| * @adapter: pointer to adapter |
| * @buf: buffer to hold the stats |
| * @len: maximum available length in response buffer |
| * |
| * Return: Size of formatted roam scan response stats |
| */ |
| 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) |
| { |
| ssize_t length = 0; |
| struct wmi_roam_scan_stats_res *roam_stats; |
| struct wmi_roam_scan_stats_params *scan; |
| int ret; |
| int rsi; /* roam scan iterator */ |
| int rci; /* roam candidate iterator */ |
| |
| roam_stats = hdd_get_roam_scan_stats(hdd_ctx, adapter); |
| if (!roam_stats) { |
| hdd_err("Couldn't get roam stats"); |
| ret = scnprintf(buf, buf_avail_len, |
| "Failed to fetch roam stats\n"); |
| if (ret <= 0) |
| return length; |
| length += ret; |
| return length; |
| } |
| |
| ret = scnprintf(buf, buf_avail_len, |
| "\n\nStats of last %u roam scans\n", |
| roam_stats->num_roam_scans); |
| if (ret <= 0) |
| goto free_mem; |
| length += ret; |
| |
| for (rsi = 0; rsi < roam_stats->num_roam_scans; rsi++) { |
| if (length >= buf_avail_len) { |
| hdd_err("No sufficient buf_avail_len"); |
| length = buf_avail_len; |
| goto free_mem; |
| } |
| |
| scan = &roam_stats->roam_scan[rsi]; |
| ret = scnprintf(buf + length, buf_avail_len - length, |
| "\nRoam scan[%u] details\n", rsi); |
| if (ret <= 0) |
| goto free_mem; |
| length += ret; |
| |
| if (length >= buf_avail_len) { |
| hdd_err("No sufficient buf_avail_len"); |
| length = buf_avail_len; |
| goto free_mem; |
| } |
| |
| ret = scnprintf(buf + length, buf_avail_len - length, |
| "This scan is triggered by \"%s\" scan client\n", |
| hdd_client_id_to_str(scan->client_id)); |
| |
| if (ret <= 0) |
| goto free_mem; |
| length += ret; |
| |
| if (length >= buf_avail_len) { |
| hdd_err("No sufficient buf_avail_len"); |
| length = buf_avail_len; |
| goto free_mem; |
| } |
| |
| length += hdd_roam_scan_trigger(scan, buf + length, |
| buf_avail_len - length); |
| if (length >= buf_avail_len) { |
| hdd_err("No sufficient buf_avail_len"); |
| length = buf_avail_len; |
| goto free_mem; |
| } |
| |
| length += hdd_roam_scan_chan(scan, buf + length, |
| buf_avail_len - length); |
| if (length >= buf_avail_len) { |
| hdd_err("No sufficient buf_avail_len"); |
| length = buf_avail_len; |
| goto free_mem; |
| } |
| |
| if (scan->is_roam_successful) { |
| ret = scnprintf(buf + length, |
| buf_avail_len - length, |
| "\nSTA roamed from " |
| MAC_ADDRESS_STR " to " |
| MAC_ADDRESS_STR "\n", |
| MAC_ADDR_ARRAY(scan->old_bssid), |
| MAC_ADDR_ARRAY(scan->new_bssid)); |
| } else { |
| ret = scnprintf(buf + length, |
| buf_avail_len - length, |
| "\nSTA is connected to " MAC_ADDRESS_STR |
| " before and after scan, not roamed\n", |
| MAC_ADDR_ARRAY(scan->old_bssid)); |
| } |
| if (ret <= 0) |
| goto free_mem; |
| length += ret; |
| |
| if (length >= buf_avail_len) { |
| hdd_err("No sufficient buf_avail_len"); |
| length = buf_avail_len; |
| goto free_mem; |
| } |
| |
| ret = scnprintf(buf + length, buf_avail_len - length, |
| "Roam candidate details\n"); |
| if (ret <= 0) |
| goto free_mem; |
| length += ret; |
| |
| if (length >= buf_avail_len) { |
| hdd_err("No sufficient buf_avail_len"); |
| length = buf_avail_len; |
| goto free_mem; |
| } |
| |
| ret = scnprintf(buf + length, buf_avail_len - length, |
| " BSSID FREQ SCORE RSSI\n"); |
| if (ret <= 0) |
| goto free_mem; |
| length += ret; |
| |
| for (rci = 0; rci < scan->num_roam_candidates; rci++) { |
| uint8_t *bssid = scan->cand[rci].bssid; |
| |
| if (length >= buf_avail_len) { |
| hdd_err("No sufficient buf_avail_len"); |
| length = buf_avail_len; |
| goto free_mem; |
| } |
| |
| ret = scnprintf(buf + length, |
| buf_avail_len - length, |
| MAC_ADDRESS_STR " %4u %3u %3u\n", |
| MAC_ADDR_ARRAY(bssid), |
| scan->cand[rci].freq, |
| scan->cand[rci].score, |
| scan->cand[rci].rssi); |
| if (ret <= 0) |
| goto free_mem; |
| length += ret; |
| } |
| } |
| |
| free_mem: |
| qdf_mem_free(roam_stats); |
| return length; |
| } |
| |
| 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) |
| { |
| ssize_t len = 0; |
| int ret_val; |
| |
| hdd_enter(); |
| |
| len = wlan_hdd_current_time_info_debugfs(buf, buf_avail_len - len); |
| |
| if (len >= buf_avail_len) { |
| hdd_err("No sufficient buf_avail_len"); |
| return buf_avail_len; |
| } |
| if (adapter->device_mode != QDF_STA_MODE) { |
| ret_val = scnprintf(buf + len, buf_avail_len - len, |
| "Interface is not in STA Mode\n"); |
| if (ret_val <= 0) |
| return len; |
| |
| len += ret_val; |
| return len; |
| } |
| |
| if (len >= buf_avail_len) { |
| hdd_err("No sufficient buf_avail_len"); |
| return buf_avail_len; |
| } |
| len += wlan_hdd_update_roam_stats(hdd_ctx, adapter, buf + len, |
| buf_avail_len - len); |
| |
| hdd_exit(); |
| |
| return len; |
| } |