qcacld-3.0: Add debugfs support for roam scan stats

Add debugfs entry to get last five roam scan stats info which includes
old bssid, new bssid, roaming candidates etc., for STA interface.

Change-Id: Ied66beb270d97b6e17a6116c1be0e82783094519
CRs-Fixed: 2203885
diff --git a/Kbuild b/Kbuild
index 84280e1..a336d9f 100755
--- a/Kbuild
+++ b/Kbuild
@@ -73,6 +73,7 @@
 HDD_OBJS += $(HDD_SRC_DIR)/wlan_hdd_debugfs_csr.o
 HDD_OBJS += $(HDD_SRC_DIR)/wlan_hdd_debugfs_connect.o
 HDD_OBJS += $(HDD_SRC_DIR)/wlan_hdd_debugfs_offload.o
+HDD_OBJS += $(HDD_SRC_DIR)/wlan_hdd_debugfs_roam.o
 endif
 endif
 
diff --git a/core/hdd/inc/wlan_hdd_debugfs_csr.h b/core/hdd/inc/wlan_hdd_debugfs_csr.h
index d1b561f..b8ca56e 100644
--- a/core/hdd/inc/wlan_hdd_debugfs_csr.h
+++ b/core/hdd/inc/wlan_hdd_debugfs_csr.h
@@ -41,6 +41,7 @@
 
 #define DEBUGFS_CONNECT_INFO_BUF_SIZE    (4 * 1024)
 #define DEBUGFS_OFFLOAD_INFO_BUF_SIZE    (4 * 1024)
+#define DEBUGFS_ROAM_SCAN_STATS_INFO_BUF_SIZE (4 * 1024)
 
 /**
  * struct wlan_hdd_debugfs_buffer_info - Debugfs buffer info
@@ -62,6 +63,14 @@
 };
 
 /**
+ * struct hdd_roam_scan_stats_debugfs_priv - private data for request mgr
+ * @res: pointer to roam scan stats response
+ */
+struct hdd_roam_scan_stats_debugfs_priv {
+	struct wmi_roam_scan_stats_res *roam_scan_stats_res;
+};
+
+/**
  * wlan_hdd_debugfs_csr_init() - Create wifi diagnostic debugfs files
  * @adapter: pointer to adapter for which debugfs files are to be created
  *
@@ -113,6 +122,19 @@
 				     struct hdd_adapter *adapter,
 				     uint8_t *buf, ssize_t buf_avail_len);
 
+/**
+ * wlan_hdd_debugfs_update_roam_stats() - API to get roam scan stats info
+ * into user buffer
+ * @buf: output buffer to hold roam scan stats info
+ * @buf_avail_len: available buffer length
+ *
+ * Return: No.of bytes copied
+ */
+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);
+
 #else
 /**
  * wlan_hdd_debugfs_csr_init() - Create wifi diagnostic debugfs files
@@ -179,6 +201,22 @@
 	return 0;
 }
 
+/**
+ * wlan_hdd_debugfs_update_roam_stats() - API to get roam scan stats info
+ * into user buffer
+ * @buf: output buffer to hold roam scan stats info
+ * @buf_avail_len: available buffer length
+ *
+ * Return: No.of bytes copied
+ */
+static inline 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)
+{
+	return 0;
+}
+
 #endif
 
 #endif /* _WLAN_HDD_DEBUGFS_CSR_H */
diff --git a/core/hdd/src/wlan_hdd_debugfs_csr.c b/core/hdd/src/wlan_hdd_debugfs_csr.c
index 79208da..f6da1e2 100644
--- a/core/hdd/src/wlan_hdd_debugfs_csr.c
+++ b/core/hdd/src/wlan_hdd_debugfs_csr.c
@@ -81,6 +81,8 @@
 		break;
 	case HDD_DEBUFS_FILE_ID_ROAM_SCAN_STATS_INFO:
 		/* populate roam scan stats info */
+		len = wlan_hdd_debugfs_update_roam_stats(hdd_ctx, adapter,
+							 buf, buf_avail_len);
 		break;
 	case HDD_DEBUFS_FILE_ID_OFFLOAD_INFO:
 		/* populate offload info */
@@ -353,6 +355,18 @@
 		if (!csr->entry)
 			hdd_err("Failed to create generic_info debugfs file");
 	}
+
+	csr = &adapter->csr_file[HDD_DEBUFS_FILE_ID_ROAM_SCAN_STATS_INFO];
+	if (!csr->entry) {
+		strlcpy(csr->name, "roam_stats", max_len);
+		csr->id = HDD_DEBUFS_FILE_ID_ROAM_SCAN_STATS_INFO;
+		csr->buf_max_size = DEBUGFS_ROAM_SCAN_STATS_INFO_BUF_SIZE;
+		csr->entry = debugfs_create_file(csr->name, 0444,
+						 adapter->debugfs_phy,
+						 csr, &fops_csr_debugfs);
+		if (!csr->entry)
+			hdd_err("Failed to create generic_info debugfs file");
+	}
 }
 
 void wlan_hdd_debugfs_csr_deinit(struct hdd_adapter *adapter)
diff --git a/core/hdd/src/wlan_hdd_debugfs_roam.c b/core/hdd/src/wlan_hdd_debugfs_roam.c
new file mode 100644
index 0000000..6b3b210
--- /dev/null
+++ b/core/hdd/src/wlan_hdd_debugfs_roam.c
@@ -0,0 +1,571 @@
+/*
+ * 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(&params);
+	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;
+}
diff --git a/core/mac/inc/sir_api.h b/core/mac/inc/sir_api.h
index 709260e..f188366 100644
--- a/core/mac/inc/sir_api.h
+++ b/core/mac/inc/sir_api.h
@@ -7187,6 +7187,21 @@
 	bool skip_dfs_chans;
 };
 
+typedef void (*roam_scan_stats_cb)(void *context,
+				   struct wmi_roam_scan_stats_res *res);
+
+/**
+ * struct sir_roam_scan_stats - Stores roam scan context
+ * @vdev_id: vdev id
+ * @cb: callback to be invoked for roam scan stats response
+ * @context: context of callback
+ */
+struct sir_roam_scan_stats {
+	uint32_t vdev_id;
+	roam_scan_stats_cb cb;
+	void *context;
+};
+
 /**
  * struct sae_info - SAE info used for commit/confirm messages
  * @msg_type: Message type
diff --git a/core/mac/src/include/sir_params.h b/core/mac/src/include/sir_params.h
index 67f93a2..d037f8a 100644
--- a/core/mac/src/include/sir_params.h
+++ b/core/mac/src/include/sir_params.h
@@ -686,6 +686,7 @@
 #define SIR_HAL_OBSS_COLOR_COLLISION_INFO   (SIR_HAL_ITC_MSG_TYPES_BEGIN + 397)
 
 #define SIR_HAL_SEND_ADDBA_REQ              (SIR_HAL_ITC_MSG_TYPES_BEGIN + 398)
+#define SIR_HAL_GET_ROAM_SCAN_STATS         (SIR_HAL_ITC_MSG_TYPES_BEGIN + 399)
 #define SIR_HAL_MSG_TYPES_END               (SIR_HAL_MSG_TYPES_BEGIN + 0x1FF)
 
 /* CFG message types */
diff --git a/core/mac/src/sys/legacy/src/utils/src/mac_trace.c b/core/mac/src/sys/legacy/src/utils/src/mac_trace.c
index df0301f..4f75537 100644
--- a/core/mac/src/sys/legacy/src/utils/src/mac_trace.c
+++ b/core/mac/src/sys/legacy/src/utils/src/mac_trace.c
@@ -651,6 +651,7 @@
 		CASE_RETURN_STRING(WMA_SET_PER_ROAM_CONFIG_CMD);
 		CASE_RETURN_STRING(WMA_GET_RCPI_REQ);
 		CASE_RETURN_STRING(WMA_SET_DBS_SCAN_SEL_CONF_PARAMS);
+		CASE_RETURN_STRING(WMA_GET_ROAM_SCAN_STATS);
 	default:
 		return (uint8_t *) "UNKNOWN";
 		break;
diff --git a/core/sme/inc/sme_api.h b/core/sme/inc/sme_api.h
index 8c8c17b..81aef58 100644
--- a/core/sme/inc/sme_api.h
+++ b/core/sme/inc/sme_api.h
@@ -2775,4 +2775,18 @@
  *         false - if not has the session
  */
 bool sme_find_session_by_bssid(tHalHandle hal, uint8_t *bssid);
+
+/**
+ * sme_get_roam_scan_stats() - Send roam scan stats cmd to wma
+ * @hal: handle returned by mac_open
+ * @cb: call-back invoked for roam scan stats response
+ * @context: context of callback
+ * @vdev_id: vdev id
+ *
+ * Return: QDF_STATUS
+ */
+QDF_STATUS
+sme_get_roam_scan_stats(tHalHandle hal, roam_scan_stats_cb cb, void *context,
+			uint32_t vdev_id);
+
 #endif /* #if !defined( __SME_API_H ) */
diff --git a/core/sme/src/common/sme_api.c b/core/sme/src/common/sme_api.c
index 8ebd3ad..da3c8da 100644
--- a/core/sme/src/common/sme_api.c
+++ b/core/sme/src/common/sme_api.c
@@ -16117,3 +16117,43 @@
 
 	return ret;
 }
+
+QDF_STATUS
+sme_get_roam_scan_stats(tHalHandle hal, roam_scan_stats_cb cb, void *context,
+			uint32_t vdev_id)
+{
+	QDF_STATUS status = QDF_STATUS_E_FAILURE;
+	tpAniSirGlobal mac = PMAC_STRUCT(hal);
+	struct scheduler_msg msg = {0};
+	struct sir_roam_scan_stats *req;
+
+	req = qdf_mem_malloc(sizeof(*req));
+	if (!req) {
+		sme_err("Failed allocate memory for roam scan stats req");
+		return QDF_STATUS_E_NOMEM;
+	}
+
+	req->vdev_id = vdev_id;
+	req->cb = cb;
+	req->context = context;
+
+	status = sme_acquire_global_lock(&mac->sme);
+	if (QDF_IS_STATUS_SUCCESS(status)) {
+		msg.bodyptr = req;
+		msg.type = WMA_GET_ROAM_SCAN_STATS;
+		msg.reserved = 0;
+		status = scheduler_post_msg(QDF_MODULE_ID_WMA, &msg);
+		sme_release_global_lock(&mac->sme);
+		if (!QDF_IS_STATUS_SUCCESS(status)) {
+			QDF_TRACE(QDF_MODULE_ID_SME, QDF_TRACE_LEVEL_ERROR,
+				  FL("post roam scan stats req failed"));
+			status = QDF_STATUS_E_FAILURE;
+			qdf_mem_free(req);
+		}
+	} else {
+		sme_err("sme_acquire_global_lock failed");
+		qdf_mem_free(req);
+	}
+
+	return status;
+}
diff --git a/core/wma/inc/wma.h b/core/wma/inc/wma.h
index 0199797..4388644 100644
--- a/core/wma/inc/wma.h
+++ b/core/wma/inc/wma.h
@@ -835,6 +835,7 @@
  * @vdev_stop_wakelock: wakelock to protect vdev stop op with firmware
  * @vdev_set_key_wakelock: wakelock to protect vdev set key op with firmware
  * @channel: channel
+ * @roam_scan_stats_req: cached roam scan stats request
  *
  * It stores parameters per vdev in wma.
  */
@@ -924,6 +925,7 @@
 	struct roam_synch_frame_ind roam_synch_frame_ind;
 	bool is_waiting_for_key;
 	uint8_t channel;
+	struct sir_roam_scan_stats *roam_scan_stats_req;
 };
 
 /**
diff --git a/core/wma/inc/wma_internal.h b/core/wma/inc/wma_internal.h
index 431c395..bd7064d 100644
--- a/core/wma/inc/wma_internal.h
+++ b/core/wma/inc/wma_internal.h
@@ -1418,4 +1418,25 @@
 
 int wma_twt_en_complete_event_handler(void *handle,
 				      uint8_t *event, uint32_t len);
+/**
+ * wma_get_roam_scan_stats() - Get roam scan stats request
+ * @handle: wma handle
+ * @req: request details
+ *
+ * Return: QDF_STATUS
+ */
+QDF_STATUS wma_get_roam_scan_stats(WMA_HANDLE handle,
+				   struct sir_roam_scan_stats *req);
+
+/**
+ * wma_roam_scan_stats_event_handler() - roam scan stats event handler
+ * @handle: wma handle
+ * @event: event data
+ * @len: length of data
+ *
+ * Return: Success or Failure status
+ */
+int wma_roam_scan_stats_event_handler(void *handle, uint8_t *event,
+				      uint32_t len);
+
 #endif
diff --git a/core/wma/inc/wma_types.h b/core/wma/inc/wma_types.h
index bede100..05c4e10 100644
--- a/core/wma/inc/wma_types.h
+++ b/core/wma/inc/wma_types.h
@@ -455,6 +455,8 @@
 #define WMA_OBSS_COLOR_COLLISION_REQ         SIR_HAL_OBSS_COLOR_COLLISION_REQ
 #define WMA_OBSS_COLOR_COLLISION_INFO        SIR_HAL_OBSS_COLOR_COLLISION_INFO
 
+#define WMA_GET_ROAM_SCAN_STATS              SIR_HAL_GET_ROAM_SCAN_STATS
+
 /* Bit 6 will be used to control BD rate for Management frames */
 #define HAL_USE_BD_RATE2_FOR_MANAGEMENT_FRAME 0x40
 
diff --git a/core/wma/src/wma_dev_if.c b/core/wma/src/wma_dev_if.c
index 4ec710b..b73d8c7 100644
--- a/core/wma/src/wma_dev_if.c
+++ b/core/wma/src/wma_dev_if.c
@@ -466,6 +466,14 @@
 		}
 	}
 
+	if (iface->roam_scan_stats_req) {
+		struct sir_roam_scan_stats *roam_scan_stats_req =
+						iface->roam_scan_stats_req;
+
+		iface->roam_scan_stats_req = NULL;
+		qdf_mem_free(roam_scan_stats_req);
+	}
+
 	wma_vdev_deinit(iface);
 	qdf_mem_zero(iface, sizeof(*iface));
 	wma_vdev_init(iface);
@@ -5508,6 +5516,14 @@
 		qdf_mem_free(rcpi_req);
 	}
 
+	if (iface->roam_scan_stats_req) {
+		struct sir_roam_scan_stats *roam_scan_stats_req =
+						iface->roam_scan_stats_req;
+
+		iface->roam_scan_stats_req = NULL;
+		qdf_mem_free(roam_scan_stats_req);
+	}
+
 	qdf_mem_zero(&iface->ns_offload_req,
 			sizeof(iface->ns_offload_req));
 	qdf_mem_zero(&iface->arp_offload_req,
@@ -5676,6 +5692,14 @@
 		qdf_mem_free(rcpi_req);
 	}
 
+	if (iface->roam_scan_stats_req) {
+		struct sir_roam_scan_stats *roam_scan_stats_req =
+						iface->roam_scan_stats_req;
+
+		iface->roam_scan_stats_req = NULL;
+		qdf_mem_free(roam_scan_stats_req);
+	}
+
 	if (wlan_op_mode_ibss == cdp_get_opmode(soc, txrx_vdev))
 		wma->ibss_started = 0;
 
diff --git a/core/wma/src/wma_main.c b/core/wma/src/wma_main.c
index 6c9cde8..e052386 100644
--- a/core/wma/src/wma_main.c
+++ b/core/wma/src/wma_main.c
@@ -2997,6 +2997,14 @@
 		vdev->rcpi_req = NULL;
 	}
 
+	if (vdev->roam_scan_stats_req) {
+		struct sir_roam_scan_stats *req;
+
+		req = vdev->roam_scan_stats_req;
+		vdev->roam_scan_stats_req = NULL;
+		qdf_mem_free(req);
+	}
+
 	if (vdev->roam_synch_frame_ind.bcn_probe_rsp) {
 		qdf_mem_free(vdev->roam_synch_frame_ind.bcn_probe_rsp);
 		vdev->roam_synch_frame_ind.bcn_probe_rsp = NULL;
@@ -3428,6 +3436,12 @@
 					   wmi_update_vdev_rate_stats_event_id,
 					   wma_link_status_event_handler,
 					   WMA_RX_SERIALIZER_CTX);
+
+	wmi_unified_register_event_handler(wma_handle->wmi_handle,
+					   wmi_roam_scan_stats_event_id,
+					   wma_roam_scan_stats_event_handler,
+					   WMA_RX_SERIALIZER_CTX);
+
 #ifdef WLAN_FEATURE_LINK_LAYER_STATS
 	/* Register event handler for processing Link Layer Stats
 	 * response from the FW
@@ -8506,6 +8520,10 @@
 		wma_process_obss_color_collision_req(wma_handle, msg->bodyptr);
 		qdf_mem_free(msg->bodyptr);
 		break;
+	case WMA_GET_ROAM_SCAN_STATS:
+		wma_get_roam_scan_stats(wma_handle, msg->bodyptr);
+		qdf_mem_free(msg->bodyptr);
+		break;
 	default:
 		WMA_LOGD("Unhandled WMA message of type %d", msg->type);
 		if (msg->bodyptr)
diff --git a/core/wma/src/wma_utils.c b/core/wma/src/wma_utils.c
index 9d5812d..c296d6e 100644
--- a/core/wma/src/wma_utils.c
+++ b/core/wma/src/wma_utils.c
@@ -4426,7 +4426,7 @@
 }
 
 int wma_rcpi_event_handler(void *handle, uint8_t *cmd_param_info,
-			    uint32_t len)
+			   uint32_t len)
 {
 	struct rcpi_res res = {0};
 	struct sme_rcpi_req *rcpi_req;
@@ -4609,3 +4609,116 @@
 	WMA_LOGD("%s: Invoke HDD pwr_save_fail callback", __func__);
 	return 0;
 }
+
+int wma_roam_scan_stats_event_handler(void *handle, uint8_t *event,
+				      uint32_t len)
+{
+	tp_wma_handle wma_handle;
+	wmi_unified_t wmi_handle;
+	struct sir_roam_scan_stats *roam_scan_stats_req = NULL;
+	struct wma_txrx_node *iface = NULL;
+	struct wmi_roam_scan_stats_res *res = NULL;
+	int ret = 0;
+	uint32_t vdev_id;
+	QDF_STATUS status;
+
+	wma_handle = handle;
+	if (!wma_handle) {
+		WMA_LOGE(FL("NULL wma_handle"));
+		return -EINVAL;
+	}
+
+	wmi_handle = wma_handle->wmi_handle;
+	if (!wmi_handle) {
+		WMA_LOGE(FL("NULL wmi_handle"));
+		return -EINVAL;
+	}
+
+	status = wmi_extract_roam_scan_stats_res_evt(wmi_handle, event,
+						     &vdev_id,
+						     &res);
+
+	/* vdev_id can be invalid though status is success, hence validate */
+	if (vdev_id >= wma_handle->max_bssid) {
+		WMA_LOGE(FL("Received invalid vdev_id: %d"), vdev_id);
+		ret  = -EINVAL;
+		goto free_res;
+	}
+
+	/* Get interface for valid vdev_id */
+	iface = &wma_handle->interfaces[vdev_id];
+	if (!iface) {
+		WMI_LOGE(FL("Interface not available for vdev_id: %d"),
+			 vdev_id);
+		ret  = -EINVAL;
+		goto free_res;
+	}
+
+	roam_scan_stats_req = iface->roam_scan_stats_req;
+	iface->roam_scan_stats_req = NULL;
+	if (!roam_scan_stats_req) {
+		WMI_LOGE(FL("No pending request vdev_id: %d"), vdev_id);
+		ret  = -EINVAL;
+		goto free_res;
+	}
+
+	if (!QDF_IS_STATUS_SUCCESS(status) ||
+	    !roam_scan_stats_req->cb ||
+	    roam_scan_stats_req->vdev_id != vdev_id) {
+		WMI_LOGE(FL("roam_scan_stats buffer not available"));
+		ret = -EINVAL;
+		goto free_roam_scan_stats_req;
+	}
+
+	roam_scan_stats_req->cb(roam_scan_stats_req->context, res);
+
+free_roam_scan_stats_req:
+	qdf_mem_free(roam_scan_stats_req);
+	roam_scan_stats_req = NULL;
+
+free_res:
+	qdf_mem_free(res);
+	res = NULL;
+
+	return ret;
+}
+
+QDF_STATUS wma_get_roam_scan_stats(WMA_HANDLE handle,
+				   struct sir_roam_scan_stats *req)
+{
+	tp_wma_handle wma_handle = (tp_wma_handle)handle;
+	struct wmi_roam_scan_stats_req cmd = {0};
+	struct wma_txrx_node *iface;
+	struct sir_roam_scan_stats *node_req = NULL;
+
+	WMA_LOGD("%s: Enter", __func__);
+	iface = &wma_handle->interfaces[req->vdev_id];
+	/* command is in progress */
+	if (iface->roam_scan_stats_req) {
+		WMA_LOGE(FL("previous roam scan stats req is pending"));
+		return QDF_STATUS_SUCCESS;
+	}
+
+	node_req = qdf_mem_malloc(sizeof(*node_req));
+	if (!node_req) {
+		WMA_LOGE("Failed to allocate memory for roam scan stats req");
+		return QDF_STATUS_E_NOMEM;
+	}
+
+	*node_req = *req;
+	iface->roam_scan_stats_req = node_req;
+	cmd.vdev_id = req->vdev_id;
+
+	if (wmi_unified_send_roam_scan_stats_cmd(wma_handle->wmi_handle,
+						 &cmd)) {
+		WMA_LOGE("%s: Failed to send WMI_REQUEST_ROAM_SCAN_STATS_CMDID",
+			 __func__);
+		iface->roam_scan_stats_req = NULL;
+		qdf_mem_free(node_req);
+		return QDF_STATUS_E_FAILURE;
+	}
+
+	WMA_LOGD("%s: Exit", __func__);
+
+	return QDF_STATUS_SUCCESS;
+}