Bladeren bron

qcacld-3.0: Get roam stats by vendor cmd

Cache roam information in wlan driver, when qca vendor
cmd of QCA_NL80211_VENDOR_SUBCMD_ROAM_STATS get roam
information, driver send all the cached information to
user space.
Extend WMI_ROAM_STATS_EVENTID event information for
requirement of:
1.Roam trigger related information, include timestamp,
  trigger reason, trigger condition, abort reason,
  scan type, roam status and fail reason.
2.Roam scan related information, include scan channel
  number, dwell type, max dwell time of each channel
  and total scan time.
3.all types of roam related frame information, include
  timestamp and successful or failed status for Preauth,
  Reassoc, EAPOL-M1/M2/M3/M4.

Change-Id: I2a694b7c1df8f5da09505f65c9c797be2177b398
CRs-Fixed: 3391601
Jianmin Zhu 2 jaren geleden
bovenliggende
commit
748f7555ed
5 gewijzigde bestanden met toevoegingen van 1169 en 1 verwijderingen
  1. 1 0
      Kbuild
  2. 7 0
      configs/default_defconfig
  3. 2 0
      core/hdd/src/wlan_hdd_cfg80211.c
  4. 1122 0
      core/hdd/src/wlan_hdd_stats.c
  5. 37 1
      core/hdd/src/wlan_hdd_stats.h

+ 1 - 0
Kbuild

@@ -3544,6 +3544,7 @@ endif
 cppflags-y += -DCONN_MGR_ADV_FEATURE
 
 cppflags-$(CONFIG_QCACLD_WLAN_LFR3) += -DWLAN_FEATURE_ROAM_OFFLOAD
+cppflags-$(CONFIG_WLAN_FEATURE_ROAM_INFO_STATS) += -DWLAN_FEATURE_ROAM_INFO_STATS
 cppflags-$(CONFIG_QCACLD_WLAN_CONNECTIVITY_LOGGING) += -DWLAN_FEATURE_CONNECTIVITY_LOGGING
 cppflags-$(CONFIG_QCACLD_WLAN_CONNECTIVITY_DIAG_EVENT) += -DCONNECTIVITY_DIAG_EVENT
 cppflags-$(CONFIG_OFDM_SCRAMBLER_SEED) += -DWLAN_FEATURE_OFDM_SCRAMBLER_SEED

+ 7 - 0
configs/default_defconfig

@@ -331,6 +331,13 @@ else
 	CONFIG_QCACLD_WLAN_LFR2 := y
 endif
 
+#Flag to enable Roam stats feature
+ifeq ($(CONFIG_BERYLLIUM), y)
+ifeq (y,$(filter y,$(CONFIG_QCACLD_WLAN_LFR3)))
+       CONFIG_WLAN_FEATURE_ROAM_INFO_STATS := y
+endif
+endif
+
 #Flag to enable Dynamic MAC address update
 CONFIG_WLAN_FEATURE_DYNAMIC_MAC_ADDR_UPDATE := y
 

+ 2 - 0
core/hdd/src/wlan_hdd_cfg80211.c

@@ -1912,6 +1912,7 @@ static const struct nl80211_vendor_cmd_info wlan_hdd_cfg80211_vendor_events[] =
 	},
 #endif
 	FEATURE_GREEN_AP_LOW_LATENCY_PWR_SAVE_EVENT
+	FEATURE_ROAM_STATS_EVENTS
 };
 
 /**
@@ -18892,6 +18893,7 @@ const struct wiphy_vendor_command hdd_wiphy_vendor_commands[] = {
 			      QCA_WLAN_VENDOR_ATTR_ROAM_EVENTS_MAX)
 	},
 #endif
+	FEATURE_ROAM_STATS_COMMANDS
 	FEATURE_WIFI_POS_11AZ_AUTH_COMMANDS
 	FEATURE_WIFI_POS_SET_SECURE_RANGING_CONTEXT_COMMANDS
 	FEATURE_MCC_QUOTA_VENDOR_COMMANDS

+ 1122 - 0
core/hdd/src/wlan_hdd_stats.c

@@ -48,6 +48,7 @@
 #include "wlan_hdd_object_manager.h"
 #include "wlan_hdd_eht.h"
 #include "wlan_dp_ucfg_api.h"
+#include "wlan_cm_roam_ucfg_api.h"
 
 #if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 0, 0)) && !defined(WITH_BACKPORTS)
 #define HDD_INFO_SIGNAL                 STATION_INFO_SIGNAL
@@ -7836,3 +7837,1124 @@ void wlan_hdd_register_cp_stats_cb(struct hdd_context *hdd_ctx)
 					hdd_lost_link_cp_stats_info_cb);
 }
 #endif
+
+#if defined(WLAN_FEATURE_ROAM_OFFLOAD) && defined(WLAN_FEATURE_ROAM_INFO_STATS)
+#define ROAM_CACHED_STATS_MAX QCA_WLAN_VENDOR_ATTR_ROAM_CACHED_STATS_MAX
+
+#define EVENTS_CONFIGURE QCA_WLAN_VENDOR_ATTR_ROAM_EVENTS_CONFIGURE
+#define SUSPEND_STATE    QCA_WLAN_VENDOR_ATTR_ROAM_EVENTS_SUSPEND_STATE
+
+#define ROAM_STATS_ROAM_TRIGGER_TIMESTAMP \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_ROAM_TRIGGER_TIMESTAMP
+#define ROAM_STATS_TRIGGER_REASON \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_TRIGGER_REASON
+#define ROAM_STATS_PER_RXRATE_THRESHOLD_PERCENT \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_PER_RXRATE_THRESHOLD_PERCENT
+#define ROAM_STATS_PER_TXRATE_THRESHOLD_PERCENT \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_PER_TXRATE_THRESHOLD_PERCENT
+#define ROAM_STATS_FINAL_BMISS_CNT \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_FINAL_BMISS_CNT
+#define ROAM_STATS_CONSECUTIVE_BMISS_CNT \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_CONSECUTIVE_BMISS_CNT
+#define ROAM_STATS_BMISS_QOS_NULL_SUCCESS \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_BMISS_QOS_NULL_SUCCESS
+#define ROAM_STATS_POOR_RSSI_CURRENT_RSSI \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_POOR_RSSI_CURRENT_RSSI
+#define ROAM_STATS_POOR_RSSI_ROAM_RSSI_THRESHOLD \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_POOR_RSSI_ROAM_RSSI_THRESHOLD
+#define ROAM_STATS_POOR_RSSI_RX_LINKSPEED_STATUS \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_POOR_RSSI_RX_LINKSPEED_STATUS
+#define ROAM_STATS_BETTER_RSSI_CURRENT_RSSI \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_BETTER_RSSI_CURRENT_RSSI
+#define ROAM_STATS_BETTER_RSSI_HIGH_RSSI_THRESHOLD \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_BETTER_RSSI_HIGH_RSSI_THRESHOLD
+#define ROAM_STATS_CONGESTION_RX_TPUT \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_CONGESTION_RX_TPUT
+#define ROAM_STATS_CONGESTION_TX_TPUT \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_CONGESTION_TX_TPUT
+#define ROAM_STATS_CONGESTION_ROAMABLE_CNT \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_CONGESTION_ROAMABLE_CNT
+#define ROAM_STATS_USER_TRIGGER_INVOKE_REASON \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_USER_TRIGGER_INVOKE_REASON
+#define ROAM_STATS_BTM_REQUEST_MODE \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_BTM_REQUEST_MODE
+#define ROAM_STATS_BTM_DISASSOC_IMMINENT_TIME \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_BTM_DISASSOC_IMMINENT_TIME
+#define ROAM_STATS_BTM_VALID_INTERNAL \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_BTM_VALID_INTERNAL
+#define ROAM_STATS_BTM_CANDIDATE_LIST_CNT \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_BTM_CANDIDATE_LIST_CNT
+#define ROAM_STATS_BTM_RESPONSE_STATUS_CODE \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_BTM_RESPONSE_STATUS_CODE
+#define ROAM_STATS_BTM_BSS_TERMINATION_TIMEOUT \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_BTM_BSS_TERMINATION_TIMEOUT
+#define ROAM_STATS_BTM_MBO_ASSOC_RETRY_TIMEOUT \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_BTM_MBO_ASSOC_RETRY_TIMEOUT
+#define ROAM_STATS_BTM_REQ_DIALOG_TOKEN \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_BTM_REQ_DIALOG_TOKEN
+#define ROAM_STATS_BSS_CU_LOAD \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_BSS_CU_LOAD
+#define ROAM_STATS_DISCONNECTION_TYPE \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_DISCONNECTION_TYPE
+#define ROAM_STATS_DISCONNECTION_REASON \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_DISCONNECTION_REASON
+#define ROAM_STATS_PERIODIC_TIMER_MS \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_PERIODIC_TIMER_MS
+#define ROAM_STATS_BACKGROUND_SCAN_CURRENT_RSSI \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_BACKGROUND_SCAN_CURRENT_RSSI
+#define ROAM_STATS_BACKGROUND_SCAN_DATA_RSSI \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_BACKGROUND_SCAN_DATA_RSSI
+#define ROAM_STATS_BACKGROUND_SCAN_DATA_RSSI_TH \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_BACKGROUND_SCAN_DATA_RSSI_THRESH
+#define ROAM_STATS_TX_FAILURES_THRESHOLD \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_TX_FAILURES_THRESHOLD
+#define ROAM_STATS_TX_FAILURES_REASON \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_TX_FAILURES_REASON
+#define ROAM_STATS_ABORT_REASON \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_ABORT_REASON
+#define ROAM_STATS_DATA_RSSI \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_DATA_RSSI
+#define ROAM_STATS_DATA_RSSI_THRESHOLD \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_DATA_RSSI_THRESHOLD
+#define ROAM_STATS_DATA_RX_LINKSPEED_STATUS \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_DATA_RX_LINKSPEED_STATUS
+#define ROAM_STATS_SCAN_TYPE \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_SCAN_TYPE
+#define ROAM_STATS_ROAM_STATUS \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_ROAM_STATUS
+#define ROAM_STATS_FAIL_REASON \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_FAIL_REASON
+#define ROAM_STATS_SCAN_CHAN_INFO \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_SCAN_CHAN_INFO
+#define ROAM_STATS_TOTAL_SCAN_TIME \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_TOTAL_SCAN_TIME
+#define ROAM_STATS_FRAME_INFO  \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_FRAME_INFO
+#define ROAM_STATS_SCAN_CHANNEL_FREQ \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_SCAN_CHANNEL_FREQ
+#define ROAM_STATS_SCAN_DWELL_TYPE \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_SCAN_DWELL_TYPE
+#define ROAM_STATS_MAX_DWELL_TIME \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_MAX_DWELL_TIME
+#define ROAM_STATS_FRAME_SUBTYPE \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_FRAME_SUBTYPE
+#define ROAM_STATS_FRAME_STATUS \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_FRAME_STATUS
+#define ROAM_STATS_FRAME_TIMESTAMP \
+	QCA_WLAN_VENDOR_ATTR_ROAM_STATS_FRAME_TIMESTAMP
+
+static enum qca_roam_reason
+hdd_convert_roam_trigger_reason(enum roam_trigger_reason reason)
+{
+	switch (reason) {
+	case ROAM_TRIGGER_REASON_NONE:
+		return QCA_ROAM_REASON_UNKNOWN;
+	case ROAM_TRIGGER_REASON_PER:
+		return QCA_ROAM_REASON_PER;
+	case ROAM_TRIGGER_REASON_BMISS:
+		return QCA_ROAM_REASON_BEACON_MISS;
+	case ROAM_TRIGGER_REASON_LOW_RSSI:
+		return QCA_ROAM_REASON_POOR_RSSI;
+	case ROAM_TRIGGER_REASON_HIGH_RSSI:
+		return QCA_ROAM_REASON_BETTER_RSSI;
+	case ROAM_TRIGGER_REASON_PERIODIC:
+		return QCA_ROAM_REASON_PERIODIC_TIMER;
+	case ROAM_TRIGGER_REASON_DENSE:
+		return QCA_ROAM_REASON_CONGESTION;
+	case ROAM_TRIGGER_REASON_BACKGROUND:
+		return QCA_ROAM_REASON_BACKGROUND_SCAN;
+	case ROAM_TRIGGER_REASON_FORCED:
+		return QCA_ROAM_REASON_USER_TRIGGER;
+	case ROAM_TRIGGER_REASON_BTM:
+		return QCA_ROAM_REASON_BTM;
+	case ROAM_TRIGGER_REASON_BSS_LOAD:
+		return QCA_ROAM_REASON_BSS_LOAD;
+	case ROAM_TRIGGER_REASON_DEAUTH:
+		return QCA_ROAM_REASON_DISCONNECTION;
+	case ROAM_TRIGGER_REASON_STA_KICKOUT:
+		return QCA_ROAM_REASON_STA_KICKOUT;
+	default:
+		hdd_err("Invalid invoke reason received: %d", reason);
+		break;
+	}
+
+	return QCA_ROAM_REASON_UNKNOWN;
+}
+
+static enum qca_wlan_roam_stats_invoke_reason
+hdd_convert_roam_invoke_reason(enum roam_invoke_reason invoke)
+{
+	switch (invoke) {
+	case WLAN_ROAM_STATS_INVOKE_REASON_UNDEFINED:
+		return QCA_WLAN_ROAM_STATS_INVOKE_REASON_UNDEFINED;
+	case WLAN_ROAM_STATS_INVOKE_REASON_NUD_FAILURE:
+		return QCA_WLAN_ROAM_STATS_INVOKE_REASON_NUD_FAILURE;
+	case WLAN_ROAM_STATS_INVOKE_REASON_USER_SPACE:
+		return QCA_WLAN_ROAM_STATS_INVOKE_REASON_USER_SPACE;
+	default:
+		hdd_err("Invalid invoke reason received: %d", invoke);
+		break;
+	}
+
+	return QCA_WLAN_ROAM_STATS_INVOKE_REASON_UNDEFINED;
+}
+
+static enum qca_wlan_roam_stats_tx_failures_reason
+hdd_convert_roam_tx_failures_reason(enum roam_tx_failures_reason tx_failures)
+{
+	switch (tx_failures) {
+	case WLAN_ROAM_STATS_KICKOUT_REASON_UNSPECIFIED:
+		return QCA_WLAN_ROAM_STATS_KICKOUT_REASON_UNSPECIFIED;
+	case WLAN_ROAM_STATS_KICKOUT_REASON_XRETRY:
+		return QCA_WLAN_ROAM_STATS_KICKOUT_REASON_XRETRY;
+	case WLAN_ROAM_STATS_KICKOUT_REASON_INACTIVITY:
+		return QCA_WLAN_ROAM_STATS_KICKOUT_REASON_INACTIVITY;
+	case WLAN_ROAM_STATS_KICKOUT_REASON_IBSS_DISCONNECT:
+		return QCA_WLAN_ROAM_STATS_KICKOUT_REASON_IBSS_DISCONNECT;
+	case WLAN_ROAM_STATS_KICKOUT_REASON_TDLS_DISCONNECT:
+		return QCA_WLAN_ROAM_STATS_KICKOUT_REASON_TDLS_DISCONNECT;
+	case WLAN_ROAM_STATS_KICKOUT_REASON_SA_QUERY_TIMEOUT:
+		return QCA_WLAN_ROAM_STATS_KICKOUT_REASON_SA_QUERY_TIMEOUT;
+	case WLAN_ROAM_STATS_KICKOUT_REASON_ROAMING_EVENT:
+		return QCA_WLAN_ROAM_STATS_KICKOUT_REASON_ROAMING_EVENT;
+	default:
+		hdd_err("Invalid tx_failures reason received: %d", tx_failures);
+		break;
+	}
+
+	return QCA_WLAN_ROAM_STATS_KICKOUT_REASON_UNSPECIFIED;
+}
+
+static enum qca_wlan_roam_stats_abort_reason
+hdd_convert_roam_abort_reason(enum roam_abort_reason abort)
+{
+	switch (abort) {
+	case WLAN_ROAM_STATS_ABORT_UNSPECIFIED:
+		return QCA_WLAN_ROAM_STATS_ABORT_UNSPECIFIED;
+	case WLAN_ROAM_STATS_ABORT_LOWRSSI_DATA_RSSI_HIGH:
+		return QCA_WLAN_ROAM_STATS_ABORT_LOWRSSI_DATA_RSSI_HIGH;
+	case WLAN_ROAM_STATS_ABORT_LOWRSSI_LINK_SPEED_GOOD:
+		return QCA_WLAN_ROAM_STATS_ABORT_LOWRSSI_LINK_SPEED_GOOD;
+	case WLAN_ROAM_STATS_ABORT_BG_DATA_RSSI_HIGH:
+		return QCA_WLAN_ROAM_STATS_ABORT_BG_DATA_RSSI_HIGH;
+	case WLAN_ROAM_STATS_ABORT_BG_RSSI_ABOVE_THRESHOLD:
+		return QCA_WLAN_ROAM_STATS_ABORT_BG_RSSI_ABOVE_THRESHOLD;
+	default:
+		hdd_err("Invalid abort reason received: %d", abort);
+		break;
+	}
+
+	return QCA_WLAN_ROAM_STATS_ABORT_UNSPECIFIED;
+}
+
+static enum qca_wlan_roam_stats_scan_type
+hdd_convert_roam_scan_type(enum roam_stats_scan_type              type)
+{
+	switch (type) {
+	case ROAM_STATS_SCAN_TYPE_PARTIAL:
+		return QCA_WLAN_ROAM_STATS_SCAN_TYPE_PARTIAL;
+	case ROAM_STATS_SCAN_TYPE_FULL:
+		return QCA_WLAN_ROAM_STATS_SCAN_TYPE_FULL;
+	case ROAM_STATS_SCAN_TYPE_NO_SCAN:
+		return QCA_WLAN_ROAM_STATS_SCAN_TYPE_NO_SCAN;
+	case ROAM_STATS_SCAN_TYPE_HIGHER_BAND_5GHZ_6GHZ:
+		return QCA_WLAN_ROAM_STATS_SCAN_TYPE_HIGHER_BAND_5GHZ_6GHZ;
+	case ROAM_STATS_SCAN_TYPE_HIGHER_BAND_6GHZ:
+		return QCA_WLAN_ROAM_STATS_SCAN_TYPE_HIGHER_BAND_6GHZ;
+	default:
+		hdd_err("Invalid roam scan type received: %d", type);
+		break;
+	}
+
+	return QCA_WLAN_ROAM_STATS_SCAN_TYPE_PARTIAL;
+}
+
+static enum qca_wlan_roam_stats_scan_dwell_type
+hdd_convert_roam_chn_dwell_type(enum roam_scan_dwell_type type)
+{
+	switch (type) {
+	case WLAN_ROAM_DWELL_TYPE_UNSPECIFIED:
+		return QCA_WLAN_ROAM_STATS_DWELL_TYPE_UNSPECIFIED;
+	case WLAN_ROAM_DWELL_ACTIVE_TYPE:
+		return QCA_WLAN_ROAM_STATS_DWELL_TYPE_ACTIVE;
+	case WLAN_ROAM_DWELL_PASSIVE_TYPE:
+		return QCA_WLAN_ROAM_STATS_DWELL_TYPE_PASSIVE;
+	default:
+		hdd_err("Invalid abort reason received: %d", type);
+		break;
+	}
+
+	return QCA_WLAN_ROAM_STATS_DWELL_TYPE_UNSPECIFIED;
+}
+
+static enum qca_wlan_roam_stats_frame_subtype
+hdd_convert_roam_frame_type(enum eroam_frame_subtype type)
+{
+	switch (type) {
+	case WLAN_ROAM_STATS_FRAME_SUBTYPE_PREAUTH:
+		return QCA_WLAN_ROAM_STATS_FRAME_SUBTYPE_PREAUTH;
+	case WLAN_ROAM_STATS_FRAME_SUBTYPE_REASSOC:
+		return QCA_WLAN_ROAM_STATS_FRAME_SUBTYPE_REASSOC;
+	case WLAN_ROAM_STATS_FRAME_SUBTYPE_EAPOL_M1:
+		return QCA_WLAN_ROAM_STATS_FRAME_SUBTYPE_EAPOL_M1;
+	case WLAN_ROAM_STATS_FRAME_SUBTYPE_EAPOL_M2:
+		return QCA_WLAN_ROAM_STATS_FRAME_SUBTYPE_EAPOL_M2;
+	case WLAN_ROAM_STATS_FRAME_SUBTYPE_EAPOL_M3:
+		return QCA_WLAN_ROAM_STATS_FRAME_SUBTYPE_EAPOL_M3;
+	case WLAN_ROAM_STATS_FRAME_SUBTYPE_EAPOL_M4:
+		return QCA_WLAN_ROAM_STATS_FRAME_SUBTYPE_EAPOL_M4;
+	case WLAN_ROAM_STATS_FRAME_SUBTYPE_EAPOL_GTK_M1:
+		return QCA_WLAN_ROAM_STATS_FRAME_SUBTYPE_EAPOL_GTK_M1;
+	case WLAN_ROAM_STATS_FRAME_SUBTYPE_EAPOL_GTK_M2:
+		return QCA_WLAN_ROAM_STATS_FRAME_SUBTYPE_EAPOL_GTK_M2;
+	default:
+		hdd_err_rl("Invalid roam frame type received: %d", type);
+		break;
+	}
+
+	return QCA_WLAN_ROAM_STATS_FRAME_SUBTYPE_PREAUTH;
+};
+
+static enum qca_wlan_roam_stats_frame_status
+hdd_convert_roam_frame_status(enum eroam_frame_status status)
+{
+	switch (status) {
+	case WLAN_ROAM_STATS_FRAME_STATUS_SUCCESS:
+		return QCA_WLAN_ROAM_STATS_FRAME_STATUS_SUCCESS;
+	case WLAN_ROAM_STATS_FRAME_STATUS_FAIL:
+		return QCA_WLAN_ROAM_STATS_FRAME_STATUS_FAIL;
+	default:
+		hdd_err("Invalid roam frame status received: %d", status);
+		break;
+	}
+
+	return QCA_WLAN_ROAM_STATS_FRAME_STATUS_FAIL;
+};
+
+static enum qca_vendor_roam_fail_reasons
+hdd_convert_roam_failures_reason(enum wlan_roam_failure_reason_code fail)
+{
+	switch (fail) {
+	case ROAM_FAIL_REASON_NO_SCAN_START:
+		return QCA_ROAM_FAIL_REASON_SCAN_NOT_ALLOWED;
+	case ROAM_FAIL_REASON_NO_AP_FOUND:
+		return QCA_ROAM_FAIL_REASON_NO_AP_FOUND;
+	case ROAM_FAIL_REASON_NO_CAND_AP_FOUND:
+		return QCA_ROAM_FAIL_REASON_NO_CAND_AP_FOUND;
+	case ROAM_FAIL_REASON_HOST:
+		return QCA_ROAM_FAIL_REASON_HOST;
+	case ROAM_FAIL_REASON_AUTH_SEND:
+		return QCA_ROAM_FAIL_REASON_AUTH_SEND;
+	case ROAM_FAIL_REASON_NO_AUTH_RESP:
+		return QCA_ROAM_FAIL_REASON_NO_AUTH_RESP;
+	case ROAM_FAIL_REASON_AUTH_RECV:
+		return QCA_ROAM_FAIL_REASON_AUTH_RECV;
+	case ROAM_FAIL_REASON_REASSOC_SEND:
+		return QCA_ROAM_FAIL_REASON_REASSOC_SEND;
+	case ROAM_FAIL_REASON_REASSOC_RECV:
+		return QCA_ROAM_FAIL_REASON_REASSOC_RECV;
+	case ROAM_FAIL_REASON_NO_REASSOC_RESP:
+		return QCA_ROAM_FAIL_REASON_NO_REASSOC_RESP;
+	case ROAM_FAIL_REASON_EAPOL_TIMEOUT:
+		return QCA_ROAM_FAIL_REASON_EAPOL_M1_TIMEOUT;
+	case ROAM_FAIL_REASON_SCAN_START:
+		return QCA_ROAM_FAIL_REASON_SCAN_FAIL;
+	case ROAM_FAIL_REASON_AUTH_NO_ACK:
+		return QCA_ROAM_FAIL_REASON_AUTH_NO_ACK;
+	case ROAM_FAIL_REASON_AUTH_INTERNAL_DROP:
+		return QCA_ROAM_FAIL_REASON_AUTH_INTERNAL_DROP;
+	case ROAM_FAIL_REASON_REASSOC_NO_ACK:
+		return QCA_ROAM_FAIL_REASON_REASSOC_NO_ACK;
+	case ROAM_FAIL_REASON_REASSOC_INTERNAL_DROP:
+		return QCA_ROAM_FAIL_REASON_REASSOC_INTERNAL_DROP;
+	case ROAM_FAIL_REASON_EAPOL_M2_SEND:
+		return QCA_ROAM_FAIL_REASON_EAPOL_M2_SEND;
+	case ROAM_FAIL_REASON_EAPOL_M2_INTERNAL_DROP:
+		return QCA_ROAM_FAIL_REASON_EAPOL_M2_INTERNAL_DROP;
+	case ROAM_FAIL_REASON_EAPOL_M2_NO_ACK:
+		return QCA_ROAM_FAIL_REASON_EAPOL_M2_NO_ACK;
+	case ROAM_FAIL_REASON_EAPOL_M3_TIMEOUT:
+		return QCA_ROAM_FAIL_REASON_EAPOL_M3_TIMEOUT;
+	case ROAM_FAIL_REASON_EAPOL_M4_SEND:
+		return QCA_ROAM_FAIL_REASON_EAPOL_M4_SEND;
+	case ROAM_FAIL_REASON_EAPOL_M4_INTERNAL_DROP:
+		return QCA_ROAM_FAIL_REASON_EAPOL_M4_INTERNAL_DROP;
+	case ROAM_FAIL_REASON_EAPOL_M4_NO_ACK:
+		return QCA_ROAM_FAIL_REASON_EAPOL_M4_NO_ACK;
+	case ROAM_FAIL_REASON_NO_SCAN_FOR_FINAL_BMISS:
+		return QCA_ROAM_FAIL_REASON_NO_SCAN_FOR_FINAL_BEACON_MISS;
+	case ROAM_FAIL_REASON_DISCONNECT:
+		return QCA_ROAM_FAIL_REASON_DISCONNECT;
+	case ROAM_FAIL_REASON_SYNC:
+		return QCA_ROAM_FAIL_REASON_RESUME_ABORT;
+	case ROAM_FAIL_REASON_SAE_INVALID_PMKID:
+		return QCA_ROAM_FAIL_REASON_SAE_INVALID_PMKID;
+	case ROAM_FAIL_REASON_SAE_PREAUTH_TIMEOUT:
+		return QCA_ROAM_FAIL_REASON_SAE_PREAUTH_TIMEOUT;
+	case ROAM_FAIL_REASON_SAE_PREAUTH_FAIL:
+		return QCA_ROAM_FAIL_REASON_SAE_PREAUTH_FAIL;
+	case ROAM_FAIL_REASON_MLME:
+	case ROAM_FAIL_REASON_INTERNAL_ABORT:
+	case ROAM_FAIL_REASON_UNABLE_TO_START_ROAM_HO:
+	case ROAM_FAIL_REASON_UNKNOWN:
+		hdd_err("Invalid roam failres reason");
+		break;
+	}
+
+	return QCA_ROAM_FAIL_REASON_NONE;
+}
+
+/**
+ * hdd_get_roam_stats_individual_record_len() - calculates the required length
+ * of an individual record of roaming stats
+ *
+ * @roam_info: pointer to roam info
+ * @index:     index of roam info cached in driver
+ *
+ * Return: required length of an individual record of roaming stats
+ */
+static uint32_t hdd_get_roam_stats_individual_record_len(struct enhance_roam_info *roam_info,
+							 uint32_t index)
+{
+	struct enhance_roam_info *info;
+	enum qca_roam_reason vendor_trigger_reason;
+	uint32_t len, i;
+
+	if (!roam_info) {
+		hdd_err("invalid param");
+		return 0;
+	}
+
+	info  = &roam_info[index];
+	vendor_trigger_reason =
+		hdd_convert_roam_trigger_reason(info->trigger.trigger_reason);
+
+	len = 0;
+	/* ROAM_STATS_ROAM_TRIGGER_TIMESTAMP */
+	len += nla_total_size_64bit(sizeof(uint64_t));
+	/* ROAM_STATS_TRIGGER_REASON */
+	len += nla_total_size(sizeof(uint32_t));
+
+	switch (vendor_trigger_reason) {
+	case QCA_ROAM_REASON_PER:
+		/* ROAM_STATS_PER_RXRATE_THRESHOLD_PERCENT */
+		len += nla_total_size(sizeof(uint8_t));
+		/* ROAM_STATS_PER_TXRATE_THRESHOLD_PERCENT */
+		len += nla_total_size(sizeof(uint8_t));
+		break;
+	case QCA_ROAM_REASON_BEACON_MISS:
+		/* ROAM_STATS_FINAL_BMISS_CNT */
+		len += nla_total_size(sizeof(uint32_t));
+		/* ROAM_STATS_CONSECUTIVE_BMISS_CNT */
+		len += nla_total_size(sizeof(uint32_t));
+		/* ROAM_STATS_BMISS_QOS_NULL_SUCCESS */
+		len += nla_total_size(sizeof(uint8_t));
+		break;
+	case QCA_ROAM_REASON_POOR_RSSI:
+		/* ROAM_STATS_POOR_RSSI_CURRENT_RSSI */
+		len += nla_total_size(sizeof(int8_t));
+		/* ROAM_STATS_POOR_RSSI_ROAM_RSSI_THRESHOLD */
+		len += nla_total_size(sizeof(int8_t));
+		/* ROAM_STATS_POOR_RSSI_RX_LINKSPEED_STATUS */
+		len += nla_total_size(sizeof(uint8_t));
+		break;
+	case QCA_ROAM_REASON_BETTER_RSSI:
+		/* ROAM_STATS_BETTER_RSSI_CURRENT_RSSI */
+		len += nla_total_size(sizeof(int8_t));
+		/* ROAM_STATS_BETTER_RSSI_HIGH_RSSI_THRESHOLD */
+		len += nla_total_size(sizeof(int8_t));
+		break;
+	case QCA_ROAM_REASON_PERIODIC_TIMER:
+		/* ROAM_STATS_PERIODIC_TIMER_MS */
+		len += nla_total_size(sizeof(uint32_t));
+		break;
+	case QCA_ROAM_REASON_CONGESTION:
+		/* ROAM_STATS_CONGESTION_RX_TPUT */
+		len += nla_total_size(sizeof(uint32_t));
+		/* ROAM_STATS_CONGESTION_TX_TPUT */
+		len += nla_total_size(sizeof(uint32_t));
+		/* ROAM_STATS_CONGESTION_ROAMABLE_CNT */
+		len += nla_total_size(sizeof(uint8_t));
+		break;
+	case QCA_ROAM_REASON_BACKGROUND_SCAN:
+		/* ROAM_STATS_BACKGROUND_SCAN_CURRENT_RSSI */
+		len += nla_total_size(sizeof(int8_t));
+		/* ROAM_STATS_BACKGROUND_SCAN_DATA_RSSI */
+		len += nla_total_size(sizeof(int8_t));
+		/* ROAM_STATS_BACKGROUND_SCAN_DATA_RSSI_TH */
+		len += nla_total_size(sizeof(int8_t));
+		break;
+	case QCA_ROAM_REASON_USER_TRIGGER:
+		/* ROAM_STATS_USER_TRIGGER_INVOKE_REASON */
+		len += nla_total_size(sizeof(uint8_t));
+		break;
+	case QCA_ROAM_REASON_BTM:
+		/* ROAM_STATS_BTM_REQUEST_MODE */
+		len += nla_total_size(sizeof(uint8_t));
+		/* ROAM_STATS_BTM_DISASSOC_IMMINENT_TIME */
+		len += nla_total_size(sizeof(uint32_t));
+		/* ROAM_STATS_BTM_VALID_INTERNAL */
+		len += nla_total_size(sizeof(uint32_t));
+		/* ROAM_STATS_BTM_CANDIDATE_LIST_CNT */
+		len += nla_total_size(sizeof(uint8_t));
+		/* ROAM_STATS_BTM_RESPONSE_STATUS_CODE */
+		len += nla_total_size(sizeof(uint8_t));
+		/* ROAM_STATS_BTM_BSS_TERMINATION_TIMEOUT */
+		len += nla_total_size(sizeof(uint32_t));
+		/* ROAM_STATS_BTM_MBO_ASSOC_RETRY_TIMEOUT */
+		len += nla_total_size(sizeof(uint32_t));
+		/* ROAM_STATS_BTM_REQ_DIALOG_TOKEN */
+		len += nla_total_size(sizeof(uint8_t));
+		break;
+	case QCA_ROAM_REASON_BSS_LOAD:
+		/* ROAM_STATS_BSS_CU_LOAD */
+		len += nla_total_size(sizeof(uint8_t));
+		break;
+	case QCA_ROAM_REASON_DISCONNECTION:
+		/* ROAM_STATS_DISCONNECTION_TYPE */
+		len += nla_total_size(sizeof(uint8_t));
+		/* ROAM_STATS_DISCONNECTION_REASON */
+		len += nla_total_size(sizeof(uint16_t));
+		break;
+	case QCA_ROAM_REASON_STA_KICKOUT:
+		/* ROAM_STATS_TX_FAILURES_THRESHOLD */
+		len += nla_total_size(sizeof(uint32_t));
+		/* ROAM_STATS_TX_FAILURES_REASON */
+		len += nla_total_size(sizeof(uint8_t));
+		break;
+	default:
+		break;
+	}
+
+	/* ROAM_STATS_SCAN_TYPE */
+	len += nla_total_size(sizeof(uint8_t));
+	/* ROAM_STATS_ROAM_STATUS */
+	len += nla_total_size(sizeof(uint8_t));
+
+	if (info->trigger.roam_status) {
+		/* ROAM_STATS_FAIL_REASON */
+		len += nla_total_size(sizeof(uint8_t));
+		if (info->trigger.abort.abort_reason_code) {
+			/* ROAM_STATS_ABORT_REASON */
+			len += nla_total_size(sizeof(uint8_t));
+			/* ROAM_STATS_DATA_RSSI */
+			len += nla_total_size(sizeof(int8_t));
+			/* ROAM_STATS_DATA_RSSI_THRESHOLD */
+			len += nla_total_size(sizeof(int8_t));
+			/* ROAM_STATS_DATA_RX_LINKSPEED_STATUS */
+			len += nla_total_size(sizeof(uint8_t));
+		}
+	}
+
+	/* ROAM_STATS_SCAN_CHAN_INFO */
+	len += nla_total_size(0);
+	for (i = 0; i < info->scan.num_channels; i++) {
+		/* nest attribute */
+		len += nla_total_size(0);
+		/* ROAM_STATS_SCAN_CHANNEL_FREQ */
+		len += nla_total_size(sizeof(uint32_t));
+		/* ROAM_STATS_SCAN_DWELL_TYPE */
+		len += nla_total_size(sizeof(uint32_t));
+		/* ROAM_STATS_MAX_DWELL_TIME */
+		len += nla_total_size(sizeof(uint32_t));
+	}
+
+	/* ROAM_STATS_TOTAL_SCAN_TIME */
+	len += nla_total_size(sizeof(uint32_t));
+
+	/* ROAM_STATS_FRAME_INFO */
+	len += nla_total_size(0);
+	for (i = 0; i < ROAM_FRAME_NUM; i++) {
+		/* nest attribute */
+		len += nla_total_size(0);
+		/* ROAM_STATS_FRAME_SUBTYPE */
+		len += nla_total_size(sizeof(uint8_t));
+		/* ROAM_STATS_FRAME_STATUS */
+		len += nla_total_size(sizeof(uint8_t));
+		/* ROAM_STATS_FRAME_TIMESTAMP */
+		len += nla_total_size_64bit(sizeof(uint64_t));
+	}
+
+	return len;
+}
+
+/**
+ * hdd_get_roam_stats_info_len() - calculate the length required by skb
+ * @roam_info: pointer to roam info
+ * @roam_cache_num: roam cache number
+ *
+ * Calculate the required length to send roam stats to upper layer
+ *
+ * Return: required len
+ */
+static uint32_t
+hdd_get_roam_stats_info_len(struct enhance_roam_info *roam_info,
+			    uint8_t roam_cache_num)
+{
+	uint32_t len, i;
+
+	len = 0;
+	/* QCA_WLAN_VENDOR_ATTR_ROAM_STATS_INFO */
+	len += nla_total_size(0);
+	for (i = 0; i < roam_cache_num; i++) {
+		/* nest attribute */
+		len += nla_total_size(0);
+		len += hdd_get_roam_stats_individual_record_len(roam_info, i);
+	}
+
+	return len;
+}
+
+/**
+ * hdd_nla_put_roam_stats_info() - put roam statistics info attribute
+ * values to userspace
+ *
+ * @skb:       pointer to sk buff
+ * @roam_info: pointer to roam info
+ * @index:     index of roam info cached in driver
+ *
+ * Return: 0 if success else error status
+ */
+static int hdd_nla_put_roam_stats_info(struct sk_buff *skb,
+				       struct enhance_roam_info *roam_info,
+				       uint32_t index)
+{
+	struct nlattr *roam_chn_info, *roam_chn;
+	struct nlattr *roam_frame_info, *roam_frame;
+	struct enhance_roam_info *info;
+	enum roam_invoke_reason driver_invoke_reason;
+	enum qca_wlan_roam_stats_invoke_reason vendor_invoke_reason;
+	enum roam_tx_failures_reason driver_tx_failures_reason;
+	enum qca_wlan_roam_stats_tx_failures_reason vendor_tx_failures_reason;
+	enum roam_abort_reason driver_abort_reason;
+	enum qca_wlan_roam_stats_abort_reason vendor_abort_reason;
+	enum qca_wlan_roam_stats_scan_type vendor_scan_type;
+	enum roam_scan_dwell_type driver_dwell_type;
+	enum qca_wlan_roam_stats_scan_dwell_type vendor_dwell_type;
+	enum eroam_frame_subtype driver_frame_type;
+	enum qca_wlan_roam_stats_frame_subtype vendor_frame_type;
+	enum eroam_frame_status driver_frame_status;
+	enum qca_wlan_roam_stats_frame_status vendor_frame_status;
+	enum qca_roam_reason vendor_trigger_reason;
+	enum qca_vendor_roam_fail_reasons vendor_fail_reason;
+	uint32_t i;
+	int ret;
+
+	if (!roam_info) {
+		hdd_err("invalid param");
+		return -EINVAL;
+	}
+	info  = &roam_info[index];
+
+	vendor_trigger_reason =
+		hdd_convert_roam_trigger_reason(info->trigger.trigger_reason);
+
+	if (wlan_cfg80211_nla_put_u64(skb, ROAM_STATS_ROAM_TRIGGER_TIMESTAMP,
+				      info->trigger.timestamp)) {
+		hdd_err("timestamp put fail");
+		return -EINVAL;
+	}
+
+	if (nla_put_u32(skb, ROAM_STATS_TRIGGER_REASON, vendor_trigger_reason)) {
+		hdd_err(" put fail");
+		return -EINVAL;
+	}
+
+	switch (vendor_trigger_reason) {
+	case QCA_ROAM_REASON_PER:
+		if (nla_put_u8(skb,  ROAM_STATS_PER_RXRATE_THRESHOLD_PERCENT,
+			       info->trigger.condition.roam_per.rx_rate_thresh_percent)) {
+			hdd_err("roam_per.rx_rate_thresh_percent put fail");
+			return -EINVAL;
+		}
+		if (nla_put_u8(skb,  ROAM_STATS_PER_TXRATE_THRESHOLD_PERCENT,
+			       info->trigger.condition.roam_per.tx_rate_thresh_percent)) {
+			hdd_err("roam_per.rx_rate_thresh_percent put fail");
+			return -EINVAL;
+		}
+		break;
+	case QCA_ROAM_REASON_BEACON_MISS:
+		if (nla_put_u32(skb, ROAM_STATS_FINAL_BMISS_CNT,
+				info->trigger.condition.roam_bmiss.final_bmiss_cnt)) {
+			hdd_err("roam_bmiss.final_bmiss_cnt put fail");
+			return -EINVAL;
+		}
+		if (nla_put_u32(skb, ROAM_STATS_CONSECUTIVE_BMISS_CNT,
+				info->trigger.condition.roam_bmiss.consecutive_bmiss_cnt)) {
+			hdd_err("roam_bmiss.consecutive_bmiss_cnt put fail");
+			return -EINVAL;
+		}
+		if (nla_put_u8(skb, ROAM_STATS_BMISS_QOS_NULL_SUCCESS,
+			       info->trigger.condition.roam_bmiss.qos_null_success)) {
+			hdd_err("roam_bmiss.qos_null_success put fail");
+			return -EINVAL;
+		}
+		break;
+	case QCA_ROAM_REASON_POOR_RSSI:
+		if (nla_put_s8(skb, ROAM_STATS_POOR_RSSI_CURRENT_RSSI,
+			       info->trigger.condition.roam_poor_rssi.current_rssi)) {
+			hdd_err("roam_poor_rssi.current_rssi put fail");
+			return -EINVAL;
+		}
+		if (nla_put_s8(skb, ROAM_STATS_POOR_RSSI_ROAM_RSSI_THRESHOLD,
+			       info->trigger.condition.roam_poor_rssi.roam_rssi_threshold)) {
+			hdd_err("roam_poor_rssi.roam_rssi_threshold put fail");
+			return -EINVAL;
+		}
+		if (nla_put_u8(skb, ROAM_STATS_POOR_RSSI_RX_LINKSPEED_STATUS,
+			       info->trigger.condition.roam_poor_rssi.rx_linkspeed_status)) {
+			hdd_err("roam_poor_rssi.rx_linkspeed_status put fail");
+			return -EINVAL;
+		}
+		break;
+	case QCA_ROAM_REASON_BETTER_RSSI:
+		if (nla_put_s8(skb, ROAM_STATS_BETTER_RSSI_CURRENT_RSSI,
+			       info->trigger.condition.roam_better_rssi.current_rssi)) {
+			hdd_err("roam_better_rssi.current_rssi put fail");
+			return -EINVAL;
+		}
+		if (nla_put_s8(skb, ROAM_STATS_BETTER_RSSI_HIGH_RSSI_THRESHOLD,
+			       info->trigger.condition.roam_better_rssi.hi_rssi_threshold)) {
+			hdd_err("roam_better_rssi.hi_rssi_threshold put fail");
+			return -EINVAL;
+		}
+		break;
+	case QCA_ROAM_REASON_PERIODIC_TIMER:
+		if (nla_put_u32(skb, ROAM_STATS_PERIODIC_TIMER_MS,
+				info->trigger.condition.roam_periodic.periodic_timer_ms)) {
+			hdd_err("roam_periodic.periodic_timer_ms put fail");
+			return -EINVAL;
+		}
+		break;
+	case QCA_ROAM_REASON_CONGESTION:
+		if (nla_put_u32(skb, ROAM_STATS_CONGESTION_RX_TPUT,
+				info->trigger.condition.roam_congestion.rx_tput)) {
+			hdd_err("roam_congestion.rx_tput put fail");
+			return -EINVAL;
+		}
+		if (nla_put_u32(skb, ROAM_STATS_CONGESTION_TX_TPUT,
+				info->trigger.condition.roam_congestion.tx_tput)) {
+			hdd_err("roam_congestion.tx_tput put fail");
+			return -EINVAL;
+		}
+		if (nla_put_u8(skb, ROAM_STATS_CONGESTION_ROAMABLE_CNT,
+			       info->trigger.condition.roam_congestion.roamable_count)) {
+			hdd_err("roam_congestion.roamable_count put fail");
+			return -EINVAL;
+		}
+		break;
+	case QCA_ROAM_REASON_BACKGROUND_SCAN:
+		if (nla_put_s8(skb, ROAM_STATS_BACKGROUND_SCAN_CURRENT_RSSI,
+			       info->trigger.condition.roam_background.current_rssi)) {
+			hdd_err("roam_background.current_rssi put fail");
+			return -EINVAL;
+		}
+		if (nla_put_s8(skb, ROAM_STATS_BACKGROUND_SCAN_DATA_RSSI,
+			       info->trigger.condition.roam_background.data_rssi)) {
+			hdd_err("roam_background.data_rssi put fail");
+			return -EINVAL;
+		}
+		if (nla_put_s8(skb, ROAM_STATS_BACKGROUND_SCAN_DATA_RSSI_TH,
+			       info->trigger.condition.roam_background.data_rssi_threshold)) {
+			hdd_err("roam_background.data_rssi_threshold put fail");
+			return -EINVAL;
+		}
+		break;
+	case QCA_ROAM_REASON_USER_TRIGGER:
+		driver_invoke_reason =
+			info->trigger.condition.roam_user_trigger.invoke_reason;
+		vendor_invoke_reason = hdd_convert_roam_invoke_reason(driver_invoke_reason);
+		if (nla_put_u8(skb, ROAM_STATS_USER_TRIGGER_INVOKE_REASON,
+			       vendor_invoke_reason)) {
+			hdd_err("roam_user_trigger.invoke_reason put fail");
+			return -EINVAL;
+		}
+		break;
+	case QCA_ROAM_REASON_BTM:
+		if (nla_put_u8(skb, ROAM_STATS_BTM_REQUEST_MODE,
+			       info->trigger.condition.roam_btm.btm_request_mode)) {
+			hdd_err("roam_btm.btm_request_mode put fail");
+			return -EINVAL;
+		}
+		if (nla_put_u32(skb, ROAM_STATS_BTM_DISASSOC_IMMINENT_TIME,
+				info->trigger.condition.roam_btm.disassoc_imminent_timer)) {
+			hdd_err("roam_btm.disassoc_imminent_timer put fail");
+			return -EINVAL;
+		}
+		if (nla_put_u32(skb, ROAM_STATS_BTM_VALID_INTERNAL,
+				info->trigger.condition.roam_btm.validity_internal)) {
+			hdd_err("roam_btm.validity_internal put fail");
+			return -EINVAL;
+		}
+		if (nla_put_u8(skb, ROAM_STATS_BTM_CANDIDATE_LIST_CNT,
+			       info->trigger.condition.roam_btm.candidate_list_count)) {
+			hdd_err("roam_btm.candidate_list_count put fail");
+			return -EINVAL;
+		}
+		if (nla_put_u8(skb, ROAM_STATS_BTM_RESPONSE_STATUS_CODE,
+			       info->trigger.condition.roam_btm.btm_response_status_code)) {
+			hdd_err("roam_btm.btm_response_status_code put fail");
+			return -EINVAL;
+		}
+		if (nla_put_u32(skb, ROAM_STATS_BTM_BSS_TERMINATION_TIMEOUT,
+				info->trigger.condition.roam_btm.btm_bss_termination_timeout)) {
+			hdd_err("roam btm_bss_termination_timeout put fail");
+			return -EINVAL;
+		}
+		if (nla_put_u32(skb, ROAM_STATS_BTM_MBO_ASSOC_RETRY_TIMEOUT,
+				info->trigger.condition.roam_btm.btm_mbo_assoc_retry_timeout)) {
+			hdd_err("roam btm_mbo_assoc_retry_timeout put fail");
+			return -EINVAL;
+		}
+		if (nla_put_u8(skb, ROAM_STATS_BTM_REQ_DIALOG_TOKEN,
+			       info->trigger.condition.roam_btm.btm_req_dialog_token)) {
+			hdd_err("roam_btm.btm_req_dialog_token put fail");
+			return -EINVAL;
+		}
+		break;
+	case QCA_ROAM_REASON_BSS_LOAD:
+		if (nla_put_u8(skb, ROAM_STATS_BSS_CU_LOAD,
+			       info->trigger.condition.roam_bss_load.cu_load)) {
+			hdd_err("roam_bss_load.cu_load put fail");
+			return -EINVAL;
+		}
+		break;
+	case QCA_ROAM_REASON_DISCONNECTION:
+		if (nla_put_u8(skb, ROAM_STATS_DISCONNECTION_TYPE,
+			       info->trigger.condition.roam_disconnection.deauth_type)) {
+			hdd_err("roam_disconnection.deauth_type put fail");
+			return -EINVAL;
+		}
+		if (nla_put_u16(skb, ROAM_STATS_DISCONNECTION_REASON,
+				info->trigger.condition.roam_disconnection.deauth_reason)) {
+			hdd_err("roam_disconnection.deauth_reason put fail");
+			return -EINVAL;
+		}
+		break;
+	case QCA_ROAM_REASON_STA_KICKOUT:
+		driver_tx_failures_reason =
+			info->trigger.condition.roam_tx_failures.kickout_threshold;
+		vendor_tx_failures_reason =
+			hdd_convert_roam_tx_failures_reason(driver_tx_failures_reason);
+		if (nla_put_u32(skb, ROAM_STATS_TX_FAILURES_THRESHOLD,
+				vendor_tx_failures_reason)) {
+			hdd_err("roam_tx_failures.kickout_threshold put fail");
+			return -EINVAL;
+		}
+		if (nla_put_u8(skb, ROAM_STATS_TX_FAILURES_REASON,
+			       info->trigger.condition.roam_tx_failures.kickout_reason)) {
+			hdd_err("roam_tx_failures.kickout_reason put fail");
+			return -EINVAL;
+		}
+		break;
+	default:
+		break;
+	}
+
+	vendor_scan_type = hdd_convert_roam_scan_type(info->trigger.roam_scan_type);
+	if (nla_put_u8(skb, ROAM_STATS_SCAN_TYPE, vendor_scan_type)) {
+		hdd_err("roam_scan_type put fail");
+		return -EINVAL;
+	}
+
+	if (nla_put_u8(skb, ROAM_STATS_ROAM_STATUS,
+		       info->trigger.roam_status)) {
+		hdd_err("roam_status put fail");
+		return -EINVAL;
+	}
+
+	if (info->trigger.roam_status) {
+		vendor_fail_reason = hdd_convert_roam_failures_reason(info->trigger.roam_fail_reason);
+		if (nla_put_u8(skb, ROAM_STATS_FAIL_REASON,
+			       vendor_fail_reason)) {
+			hdd_err("roam_fail_reason put fail");
+			return -EINVAL;
+		}
+
+		driver_abort_reason = info->trigger.abort.abort_reason_code;
+		vendor_abort_reason = hdd_convert_roam_abort_reason(driver_abort_reason);
+		if (info->trigger.abort.abort_reason_code) {
+			if (nla_put_u8(skb, ROAM_STATS_ABORT_REASON, vendor_abort_reason)) {
+				hdd_err("abort.abort_reason_code put fail");
+				return -EINVAL;
+			}
+			if (nla_put_s8(skb, ROAM_STATS_DATA_RSSI,
+				       info->trigger.abort.data_rssi)) {
+				hdd_err("abort.data_rssi put fail");
+				return -EINVAL;
+			}
+			if (nla_put_s8(skb, ROAM_STATS_DATA_RSSI_THRESHOLD,
+				       info->trigger.abort.data_rssi_threshold)) {
+				hdd_err("abort.data_rssi_threshold put fail");
+				return -EINVAL;
+			}
+			if (nla_put_u8(skb, ROAM_STATS_DATA_RX_LINKSPEED_STATUS,
+				       info->trigger.abort.rx_linkspeed_status)) {
+				hdd_err("abort.rx_linkspeed_status put fail");
+				return -EINVAL;
+			}
+		}
+	}
+
+	roam_chn_info = nla_nest_start(skb, ROAM_STATS_SCAN_CHAN_INFO);
+	if (!roam_chn_info) {
+		hdd_err("nla_nest_start fail");
+		return -EINVAL;
+	}
+
+	for (i = 0; i < info->scan.num_channels; i++) {
+		roam_chn = nla_nest_start(skb, i);
+		if (!roam_chn) {
+			hdd_err("nla_nest_start fail");
+			return -EINVAL;
+		}
+
+		if (nla_put_u32(skb, ROAM_STATS_SCAN_CHANNEL_FREQ,
+				info->scan.roam_chn[i].chan_freq)) {
+			hdd_err("roam_chn[%u].chan_freq put fail", i);
+			return -EINVAL;
+		}
+
+		driver_dwell_type = info->scan.roam_chn[i].dwell_type;
+		vendor_dwell_type = hdd_convert_roam_chn_dwell_type(driver_dwell_type);
+		if (nla_put_u32(skb, ROAM_STATS_SCAN_DWELL_TYPE,
+				vendor_dwell_type)) {
+			hdd_err("roam_chn[%u].dwell_type put fail", i);
+			return -EINVAL;
+		}
+		if (nla_put_u32(skb, ROAM_STATS_MAX_DWELL_TIME,
+				info->scan.roam_chn[i].max_dwell_time)) {
+			hdd_err("roam_chn[%u].max_dwell_time put fail", i);
+			return -EINVAL;
+		}
+		nla_nest_end(skb, roam_chn);
+	}
+	nla_nest_end(skb, roam_chn_info);
+
+	if (nla_put_u32(skb, ROAM_STATS_TOTAL_SCAN_TIME,
+			info->scan.total_scan_time)) {
+		hdd_err("roam_scan total_scan_time put fail");
+		return -EINVAL;
+	}
+
+	roam_frame_info = nla_nest_start(skb, ROAM_STATS_FRAME_INFO);
+	if (!roam_frame_info) {
+		hdd_err("nla_nest_start fail");
+		return -EINVAL;
+	}
+
+	for (i = 0; i < ROAM_FRAME_NUM; i++) {
+		roam_frame = nla_nest_start(skb, i);
+		if (!roam_frame) {
+			hdd_err("nla_nest_start fail");
+			return -EINVAL;
+		}
+		driver_frame_type = info->timestamp[i].frame_type;
+		vendor_frame_type = hdd_convert_roam_frame_type(driver_frame_type);
+		ret = nla_put_u8(skb, ROAM_STATS_FRAME_SUBTYPE,
+				 vendor_frame_type);
+		if (ret) {
+			hdd_err("roam_frame[%u].type put fail %d", i, ret);
+			return -EINVAL;
+		}
+		driver_frame_status = info->timestamp[i].status;
+		vendor_frame_status = hdd_convert_roam_frame_status(driver_frame_status);
+		ret = nla_put_u8(skb, ROAM_STATS_FRAME_STATUS,
+				 vendor_frame_status);
+		if (ret) {
+			hdd_err("frame[%u].status put fail %d", i, ret);
+			return -EINVAL;
+		}
+		ret = wlan_cfg80211_nla_put_u64(skb, ROAM_STATS_FRAME_TIMESTAMP,
+						info->timestamp[i].timestamp);
+		if (ret) {
+			hdd_err("frame[%u].timestamp put fail %d", i, ret);
+			return -EINVAL;
+		}
+		nla_nest_end(skb, roam_frame);
+	}
+	nla_nest_end(skb, roam_frame_info);
+
+	return 0;
+}
+
+/**
+ * hdd_get_roam_stats_info() - get roam statistics info to userspace,
+ * for STA mode only
+ * @skb:     pointer to sk buff
+ * @hdd_ctx: pointer to hdd context
+ * @roam_info: pointer to roam info
+ * @roam_cache_num: roam cache number
+ *
+ * Return: 0 if success else error status
+ */
+static int hdd_get_roam_stats_info(struct sk_buff *skb,
+				   struct hdd_context *hdd_ctx,
+				   struct enhance_roam_info *roam_info,
+				   uint32_t  roam_cache_num)
+{
+	struct nlattr *config, *roam_params;
+	uint32_t i;
+	int ret;
+
+	config = nla_nest_start(skb, QCA_WLAN_VENDOR_ATTR_ROAM_STATS_INFO);
+	if (!config) {
+		hdd_err("nla nest start failure");
+		return -EINVAL;
+	}
+
+	/* Send all driver cached roam info to user space one time,
+	 * and don't flush them, since they will be cover by
+	 * new roam event info.
+	 */
+	for (i = 0; i < roam_cache_num; i++) {
+		roam_params = nla_nest_start(skb, i);
+		if (!roam_params)
+			return -EINVAL;
+
+		ret = hdd_nla_put_roam_stats_info(skb, roam_info, i);
+		if (ret) {
+			hdd_err("nla put failure");
+			return -EINVAL;
+		}
+
+		nla_nest_end(skb, roam_params);
+	}
+	nla_nest_end(skb, config);
+
+	return 0;
+}
+
+/**
+ * hdd_get_roam_stats() - send roam statistics info to userspace
+ * @hdd_ctx: pointer to hdd context
+ * @adapter: pointer to adapter
+ *
+ * Return: 0 if success else error status
+ */
+static int
+hdd_get_roam_stats(struct hdd_context *hdd_ctx,
+		   struct hdd_adapter *adapter)
+{
+	struct sk_buff *skb;
+	uint32_t skb_len;
+	int ret = 0;
+	struct wlan_objmgr_vdev *vdev;
+	QDF_STATUS status;
+	struct enhance_roam_info *roam_info = NULL;
+	uint32_t roam_num = 0;
+
+	vdev = hdd_objmgr_get_vdev_by_user(adapter, WLAN_OSIF_STATS_ID);
+	if (!vdev)
+		return -EINVAL;
+
+	status = ucfg_cm_roam_stats_info_get(vdev, &roam_info, &roam_num);
+	if (QDF_IS_STATUS_ERROR(status)) {
+		hdd_err("Failed to get roam info : %d", status);
+		wlan_objmgr_vdev_release_ref(vdev, WLAN_OSIF_STATS_ID);
+		return qdf_status_to_os_return(status);
+	}
+	wlan_objmgr_vdev_release_ref(vdev, WLAN_OSIF_STATS_ID);
+
+	skb_len = hdd_get_roam_stats_info_len(roam_info, roam_num);
+	if (!skb_len) {
+		hdd_err("No data requested");
+		ucfg_cm_roam_stats_info_put(roam_info);
+		return -EINVAL;
+	}
+
+	skb_len += NLMSG_HDRLEN;
+	skb = wlan_cfg80211_vendor_cmd_alloc_reply_skb(hdd_ctx->wiphy, skb_len);
+	if (!skb) {
+		hdd_info("wlan_cfg80211_vendor_cmd_alloc_reply_skb failed");
+		ucfg_cm_roam_stats_info_put(roam_info);
+		return -ENOMEM;
+	}
+
+	ret = hdd_get_roam_stats_info(skb, hdd_ctx, roam_info, roam_num);
+	if (ret) {
+		hdd_info("get roam stats fail");
+		wlan_cfg80211_vendor_free_skb(skb);
+		ucfg_cm_roam_stats_info_put(roam_info);
+		return -ENOMEM;
+	}
+
+	ucfg_cm_roam_stats_info_put(roam_info);
+
+	return wlan_cfg80211_vendor_cmd_reply(skb);
+}
+
+/**
+ * __wlan_hdd_cfg80211_get_roam_stats() - get roam statstics information
+ * @wiphy: wiphy pointer
+ * @wdev: pointer to struct wireless_dev
+ * @data: pointer to incoming NL vendor data
+ * @data_len: length of @data
+ *
+ * Return: 0 on success; error number otherwise.
+ */
+static int
+__wlan_hdd_cfg80211_get_roam_stats(struct wiphy *wiphy,
+				   struct wireless_dev *wdev,
+				   const void *data,
+				   int data_len)
+{
+	struct hdd_context *hdd_ctx = wiphy_priv(wiphy);
+	struct net_device *dev = wdev->netdev;
+	struct hdd_adapter *adapter = WLAN_HDD_GET_PRIV_PTR(dev);
+	int32_t status;
+
+	hdd_enter_dev(dev);
+
+	if (hdd_get_conparam() == QDF_GLOBAL_FTM_MODE ||
+	    hdd_get_conparam() == QDF_GLOBAL_MONITOR_MODE) {
+		hdd_err_rl("Command not allowed in FTM / Monitor mode");
+		status = -EPERM;
+		goto out;
+	}
+
+	status = wlan_hdd_validate_context(hdd_ctx);
+	if (status != 0)
+		goto out;
+
+	if (adapter->device_mode == QDF_STA_MODE) {
+		status = hdd_get_roam_stats(hdd_ctx, adapter);
+	} else {
+		hdd_err_rl("Invalid device_mode: %d", adapter->device_mode);
+		status = -EINVAL;
+	}
+
+	hdd_exit();
+out:
+	return status;
+}
+
+/**
+ * wlan_hdd_cfg80211_get_roam_stats() - get roam statstics information
+ * @wiphy: wiphy pointer
+ * @wdev: pointer to struct wireless_dev
+ * @data: pointer to incoming NL vendor data
+ * @data_len: length of @data
+ *
+ * Return: 0 on success; error number otherwise.
+ */
+int wlan_hdd_cfg80211_get_roam_stats(struct wiphy *wiphy,
+				     struct wireless_dev *wdev,
+				     const void *data,
+				     int data_len)
+{
+	int errno;
+	struct osif_vdev_sync *vdev_sync;
+
+	errno = osif_vdev_sync_op_start(wdev->netdev, &vdev_sync);
+	if (errno)
+		return errno;
+
+	errno = __wlan_hdd_cfg80211_get_roam_stats(wiphy, wdev,
+						   data, data_len);
+
+	osif_vdev_sync_op_stop(vdev_sync);
+
+	return errno;
+}
+#endif

+ 37 - 1
core/hdd/src/wlan_hdd_stats.h

@@ -1,6 +1,6 @@
 /*
  * Copyright (c) 2012-2021 The Linux Foundation. All rights reserved.
- * Copyright (c) 2021-2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ * Copyright (c) 2021-2023 Qualcomm Innovation Center, Inc. All rights reserved.
  *
  * Permission to use, copy, modify, and/or distribute this software for
  * any purpose with or without fee is hereby granted, provided that the
@@ -671,4 +671,40 @@ void wlan_hdd_register_cp_stats_cb(struct hdd_context *hdd_ctx);
 #else
 static inline void wlan_hdd_register_cp_stats_cb(struct hdd_context *hdd_ctx) {}
 #endif
+
+#if defined(WLAN_FEATURE_ROAM_OFFLOAD) && defined(WLAN_FEATURE_ROAM_INFO_STATS)
+#define FEATURE_ROAM_STATS_COMMANDS	\
+	{	\
+	.info.vendor_id = QCA_NL80211_VENDOR_ID,	\
+	.info.subcmd = QCA_NL80211_VENDOR_SUBCMD_ROAM_STATS,	\
+	.flags = WIPHY_VENDOR_CMD_NEED_WDEV |	\
+		 WIPHY_VENDOR_CMD_NEED_NETDEV |	\
+		 WIPHY_VENDOR_CMD_NEED_RUNNING,	\
+	.doit = wlan_hdd_cfg80211_get_roam_stats,	\
+	vendor_command_policy(VENDOR_CMD_RAW_DATA, 0)	\
+	},	\
+
+#define FEATURE_ROAM_STATS_EVENTS	\
+	[QCA_NL80211_VENDOR_SUBCMD_ROAM_STATS_INDEX] = {	\
+		.vendor_id = QCA_NL80211_VENDOR_ID,	\
+		.subcmd = QCA_NL80211_VENDOR_SUBCMD_ROAM_STATS,	\
+	},	\
+
+/**
+ * wlan_hdd_cfg80211_get_roam_stats() - get roam statstics information
+ * @wiphy: wiphy pointer
+ * @wdev: pointer to struct wireless_dev
+ * @data: pointer to incoming NL vendor data
+ * @data_len: length of @data
+ *
+ * Return: 0 on success; error number otherwise.
+ */
+int wlan_hdd_cfg80211_get_roam_stats(struct wiphy *wiphy,
+				     struct wireless_dev *wdev,
+				     const void *data,
+				     int data_len);
+#else
+#define FEATURE_ROAM_STATS_COMMANDS
+#define FEATURE_ROAM_STATS_EVENTS
+#endif
 #endif /* end #if !defined(WLAN_HDD_STATS_H) */