/*
 * Copyright (c) 2012-2021 The Linux Foundation. All rights reserved.
 * Copyright (c) 2022-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
 * 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_ext_scan.c
 *
 * WLAN Host Device Driver EXT SCAN feature implementation
 *
 */

#ifdef FEATURE_WLAN_EXTSCAN

#include "osif_sync.h"
#include "wlan_hdd_ext_scan.h"
#include "wlan_hdd_regulatory.h"
#include "cds_utils.h"
#include "cds_sched.h"
#include <qca_vendor.h>
#include "wlan_extscan_ucfg_api.h"
#include "wlan_hdd_scan.h"

/* amount of time to wait for a synchronous request/response operation */
#define WLAN_WAIT_TIME_EXTSCAN  1000

/**
 * struct hdd_ext_scan_context - hdd ext scan context
 * @request_id: userspace-assigned ID associated with the request
 * @response_event: Ext scan wait event
 * @response_status: Status returned by FW in response to a request
 * @ignore_cached_results: Flag to ignore cached results or not
 * @context_lock: Spinlock to serialize all context accesses
 * @capability_response: Ext scan capability response data from target
 * @buckets_scanned: bitmask of buckets scanned in extscan cycle
 */
struct hdd_ext_scan_context {
	uint32_t request_id;
	int response_status;
	bool ignore_cached_results;
	struct completion response_event;
	spinlock_t context_lock;
	struct ext_scan_capabilities_response capability_response;
	uint32_t buckets_scanned;
};
static struct hdd_ext_scan_context ext_scan_context;

const struct nla_policy
wlan_hdd_extscan_config_policy[EXTSCAN_PARAM_MAX + 1] = {
	[QCA_WLAN_VENDOR_ATTR_EXTSCAN_SUBCMD_CONFIG_PARAM_REQUEST_ID] = {
				.type = NLA_U32},
	[QCA_WLAN_VENDOR_ATTR_EXTSCAN_CHANNEL_SPEC_CHANNEL] = {.type = NLA_U32},
	[QCA_WLAN_VENDOR_ATTR_EXTSCAN_CHANNEL_SPEC_DWELL_TIME] = {
				.type = NLA_U32},
	[QCA_WLAN_VENDOR_ATTR_EXTSCAN_CHANNEL_SPEC_PASSIVE] = {.type = NLA_U8},
	[QCA_WLAN_VENDOR_ATTR_EXTSCAN_CHANNEL_SPEC_CLASS] = {.type = NLA_U8},

	[QCA_WLAN_VENDOR_ATTR_EXTSCAN_BUCKET_SPEC_INDEX] = {.type = NLA_U8},
	[QCA_WLAN_VENDOR_ATTR_EXTSCAN_BUCKET_SPEC_BAND] = {.type = NLA_U8},
	[QCA_WLAN_VENDOR_ATTR_EXTSCAN_BUCKET_SPEC_PERIOD] = {.type = NLA_U32},
	[QCA_WLAN_VENDOR_ATTR_EXTSCAN_BUCKET_SPEC_REPORT_EVENTS] = {
				.type = NLA_U8},
	[QCA_WLAN_VENDOR_ATTR_EXTSCAN_BUCKET_SPEC_NUM_CHANNEL_SPECS] = {
				.type = NLA_U32},
	[QCA_WLAN_VENDOR_ATTR_EXTSCAN_SCAN_CMD_PARAMS_BASE_PERIOD] = {
				.type = NLA_U32},
	[QCA_WLAN_VENDOR_ATTR_EXTSCAN_SCAN_CMD_PARAMS_MAX_AP_PER_SCAN] = {
				.type = NLA_U32},
	[QCA_WLAN_VENDOR_ATTR_EXTSCAN_SCAN_CMD_PARAMS_REPORT_THRESHOLD_PERCENT] = {
				.type = NLA_U8},
	[QCA_WLAN_VENDOR_ATTR_EXTSCAN_SCAN_CMD_PARAMS_REPORT_THRESHOLD_NUM_SCANS] = {
				.type = NLA_U8 },
	[QCA_WLAN_VENDOR_ATTR_EXTSCAN_SCAN_CMD_PARAMS_NUM_BUCKETS] = {
				.type = NLA_U8},
	[QCA_WLAN_VENDOR_ATTR_EXTSCAN_GET_CACHED_SCAN_RESULTS_CONFIG_PARAM_FLUSH] = {
				.type = NLA_U8},

	[QCA_WLAN_VENDOR_ATTR_EXTSCAN_GET_CACHED_SCAN_RESULTS_CONFIG_PARAM_MAX] = {
				.type = NLA_U32},
	[QCA_WLAN_VENDOR_ATTR_EXTSCAN_AP_THRESHOLD_PARAM_BSSID] =
				VENDOR_NLA_POLICY_MAC_ADDR,
	[QCA_WLAN_VENDOR_ATTR_EXTSCAN_AP_THRESHOLD_PARAM_RSSI_LOW] = {
				.type = NLA_S32},
	[QCA_WLAN_VENDOR_ATTR_EXTSCAN_AP_THRESHOLD_PARAM_RSSI_HIGH] = {
				.type = NLA_S32},
	[QCA_WLAN_VENDOR_ATTR_EXTSCAN_AP_THRESHOLD_PARAM_CHANNEL] = {
				.type = NLA_U32},
	[QCA_WLAN_VENDOR_ATTR_EXTSCAN_BSSID_HOTLIST_PARAMS_NUM_AP] = {
				.type = NLA_U32},
	[QCA_WLAN_VENDOR_ATTR_EXTSCAN_SIGNIFICANT_CHANGE_PARAMS_RSSI_SAMPLE_SIZE] = {
				.type = NLA_U32},
	[QCA_WLAN_VENDOR_ATTR_EXTSCAN_SIGNIFICANT_CHANGE_PARAMS_LOST_AP_SAMPLE_SIZE] = {
				.type = NLA_U32},
	[QCA_WLAN_VENDOR_ATTR_EXTSCAN_SIGNIFICANT_CHANGE_PARAMS_MIN_BREACHING] = {
				.type = NLA_U32},
	[QCA_WLAN_VENDOR_ATTR_EXTSCAN_SIGNIFICANT_CHANGE_PARAMS_NUM_AP] = {
				.type = NLA_U32},
	[QCA_WLAN_VENDOR_ATTR_EXTSCAN_BUCKET_SPEC_MAX_PERIOD] = {
				.type = NLA_U32},
	[QCA_WLAN_VENDOR_ATTR_EXTSCAN_BUCKET_SPEC_BASE] = {
				.type = NLA_U32},
	[QCA_WLAN_VENDOR_ATTR_EXTSCAN_BUCKET_SPEC_STEP_COUNT] = {
				.type = NLA_U32},
	[QCA_WLAN_VENDOR_ATTR_EXTSCAN_SSID_THRESHOLD_PARAM_SSID] = {
				.type = NLA_BINARY,
				.len = IEEE80211_MAX_SSID_LEN + 1 },
	[QCA_WLAN_VENDOR_ATTR_EXTSCAN_SSID_HOTLIST_PARAMS_LOST_SSID_SAMPLE_SIZE] = {
				.type = NLA_U32 },
	[QCA_WLAN_VENDOR_ATTR_EXTSCAN_SSID_HOTLIST_PARAMS_NUM_SSID] = {
				.type = NLA_U32 },
	[QCA_WLAN_VENDOR_ATTR_EXTSCAN_SSID_THRESHOLD_PARAM_BAND] = {
				.type = NLA_U8 },
	[QCA_WLAN_VENDOR_ATTR_EXTSCAN_SSID_THRESHOLD_PARAM_RSSI_LOW] = {
				.type = NLA_S32 },
	[QCA_WLAN_VENDOR_ATTR_EXTSCAN_SSID_THRESHOLD_PARAM_RSSI_HIGH] = {
				.type = NLA_S32 },
	[QCA_WLAN_VENDOR_ATTR_EXTSCAN_CONFIGURATION_FLAGS] = {
				.type = NLA_U32 },
	[QCA_WLAN_VENDOR_ATTR_EXTSCAN_BSSID_HOTLIST_PARAMS_LOST_AP_SAMPLE_SIZE] = {
				.type = NLA_U32},
};

const struct nla_policy
wlan_hdd_pno_config_policy[QCA_WLAN_VENDOR_ATTR_PNO_MAX + 1] = {
	[QCA_WLAN_VENDOR_ATTR_PNO_PASSPOINT_LIST_PARAM_NUM] = {
		.type = NLA_U32
	},
	[QCA_WLAN_VENDOR_ATTR_PNO_PASSPOINT_NETWORK_PARAM_ID] = {
		.type = NLA_U32
	},
	[QCA_WLAN_VENDOR_ATTR_PNO_SET_LIST_PARAM_NUM_NETWORKS] = {
		.type = NLA_U32
	},
	[QCA_WLAN_VENDOR_ATTR_PNO_SET_LIST_PARAM_EPNO_NETWORK_SSID] = {
		.type = NLA_BINARY,
		.len = IEEE80211_MAX_SSID_LEN + 1
	},
	[QCA_WLAN_VENDOR_ATTR_PNO_SET_LIST_PARAM_EPNO_NETWORK_FLAGS] = {
		.type = NLA_U8
	},
	[QCA_WLAN_VENDOR_ATTR_PNO_SET_LIST_PARAM_EPNO_NETWORK_AUTH_BIT] = {
		.type = NLA_U8
	},
	[QCA_WLAN_VENDOR_ATTR_EPNO_MIN5GHZ_RSSI] = {
		.type = NLA_U32
	},
	[QCA_WLAN_VENDOR_ATTR_EPNO_MIN24GHZ_RSSI] = {
		.type = NLA_U32
	},
	[QCA_WLAN_VENDOR_ATTR_EPNO_INITIAL_SCORE_MAX] = {
		.type = NLA_U32
	},
	[QCA_WLAN_VENDOR_ATTR_EPNO_CURRENT_CONNECTION_BONUS] = {
		.type = NLA_U32
	},
	[QCA_WLAN_VENDOR_ATTR_EPNO_SAME_NETWORK_BONUS] = {
		.type = NLA_U32
	},
	[QCA_WLAN_VENDOR_ATTR_EPNO_SECURE_BONUS] = {
		.type = NLA_U32
	},
	[QCA_WLAN_VENDOR_ATTR_EPNO_BAND5GHZ_BONUS] = {
		.type = NLA_U32
	},
	[QCA_WLAN_VENDOR_ATTR_PNO_CONFIG_REQUEST_ID] = {
		.type = NLA_U32
	},
};

/**
 * wlan_hdd_cfg80211_extscan_get_capabilities_rsp() - response from target
 * @hdd_ctx: Pointer to hdd context
 * @data: Pointer to ext scan capabilities response from fw
 *
 * Return: None
 */
static void
wlan_hdd_cfg80211_extscan_get_capabilities_rsp(struct hdd_context *hdd_ctx,
	struct ext_scan_capabilities_response *data)
{
	struct hdd_ext_scan_context *context;

	hdd_enter();

	if (wlan_hdd_validate_context(hdd_ctx))
		return;
	if (!data) {
		hdd_err("data is null");
		return;
	}

	context = &ext_scan_context;

	spin_lock(&context->context_lock);
	/* validate response received from target*/
	if (context->request_id != data->requestId) {
		spin_unlock(&context->context_lock);
		hdd_err("Target response id did not match. request_id: %d response_id: %d",
			context->request_id, data->requestId);
		return;
	}

	context->capability_response = *data;
	complete(&context->response_event);
	spin_unlock(&context->context_lock);
}

/*
 * define short names for the global vendor params
 * used by hdd_extscan_nl_fill_bss()
 */
#define PARAM_TIME_STAMP \
	QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SCAN_RESULT_TIME_STAMP
#define PARAM_SSID \
	QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SCAN_RESULT_SSID
#define PARAM_BSSID \
	QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SCAN_RESULT_BSSID
#define PARAM_CHANNEL \
	QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SCAN_RESULT_CHANNEL
#define PARAM_RSSI \
	QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SCAN_RESULT_RSSI
#define PARAM_RTT \
	QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SCAN_RESULT_RTT
#define PARAM_RTT_SD \
	QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SCAN_RESULT_RTT_SD
#define PARAM_BEACON_PERIOD \
	QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SCAN_RESULT_BEACON_PERIOD
#define PARAM_CAPABILITY \
	QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SCAN_RESULT_CAPABILITY
#define PARAM_IE_LENGTH \
	QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SCAN_RESULT_IE_LENGTH
#define PARAM_IE_DATA \
	QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SCAN_RESULT_IE_DATA

/** hdd_extscan_nl_fill_bss() - extscan nl fill bss
 * @skb: socket buffer
 * @ap: bss information
 * @idx: nesting index
 *
 * Return: 0 on success; error number otherwise
 */
static int hdd_extscan_nl_fill_bss(struct sk_buff *skb, tSirWifiScanResult *ap,
					int idx)
{
	struct nlattr *nla_ap;

	nla_ap = nla_nest_start(skb, idx);
	if (!nla_ap)
		return -EINVAL;

	if (hdd_wlan_nla_put_u64(skb, PARAM_TIME_STAMP, ap->ts) ||
	    nla_put(skb, PARAM_SSID, sizeof(ap->ssid), ap->ssid) ||
	    nla_put(skb, PARAM_BSSID, sizeof(ap->bssid), ap->bssid.bytes) ||
	    nla_put_u32(skb, PARAM_CHANNEL, ap->channel) ||
	    nla_put_s32(skb, PARAM_RSSI, ap->rssi) ||
	    nla_put_u32(skb, PARAM_RTT, ap->rtt) ||
	    nla_put_u32(skb, PARAM_RTT_SD, ap->rtt_sd) ||
	    nla_put_u16(skb, PARAM_BEACON_PERIOD, ap->beaconPeriod) ||
	    nla_put_u16(skb, PARAM_CAPABILITY, ap->capability) ||
	    nla_put_u16(skb, PARAM_IE_LENGTH, ap->ieLength)) {
		hdd_err("put fail");
		return -EINVAL;
	}

	if (ap->ieLength)
		if (nla_put(skb, PARAM_IE_DATA, ap->ieLength, ap->ieData)) {
			hdd_err("put fail");
			return -EINVAL;
		}

	nla_nest_end(skb, nla_ap);

	return 0;
}
/*
 * done with short names for the global vendor params
 * used by hdd_extscan_nl_fill_bss()
 */
#undef PARAM_TIME_STAMP
#undef PARAM_SSID
#undef PARAM_BSSID
#undef PARAM_CHANNEL
#undef PARAM_RSSI
#undef PARAM_RTT
#undef PARAM_RTT_SD
#undef PARAM_BEACON_PERIOD
#undef PARAM_CAPABILITY
#undef PARAM_IE_LENGTH
#undef PARAM_IE_DATA

/**
 * wlan_hdd_cfg80211_extscan_cached_results_ind() - get cached results
 * @hdd_ctx: hdd global context
 * @data: cached results
 *
 * This function reads the cached results %data, populated the NL
 * attributes and sends the NL event to the upper layer.
 *
 * Return: none
 */
static void
wlan_hdd_cfg80211_extscan_cached_results_ind(struct hdd_context *hdd_ctx,
				struct extscan_cached_scan_results *data)
{
	struct sk_buff *skb = NULL;
	struct hdd_ext_scan_context *context;
	struct extscan_cached_scan_result *result;
	tSirWifiScanResult *ap;
	uint32_t i, j, nl_buf_len;
	bool ignore_cached_results = false;

	/* ENTER() intentionally not used in a frequently invoked API */

	if (wlan_hdd_validate_context(hdd_ctx))
		return;
	if (!data) {
		hdd_err("data is null");
		return;
	}

	context = &ext_scan_context;
	spin_lock(&context->context_lock);
	ignore_cached_results = context->ignore_cached_results;
	spin_unlock(&context->context_lock);

	if (ignore_cached_results) {
		hdd_err("Ignore the cached results received after timeout");
		return;
	}

#define EXTSCAN_CACHED_NEST_HDRLEN NLA_HDRLEN
#define EXTSCAN_CACHED_NL_FIXED_TLV \
		((sizeof(data->request_id) + NLA_HDRLEN) + \
		(sizeof(data->num_scan_ids) + NLA_HDRLEN) + \
		(sizeof(data->more_data) + NLA_HDRLEN))
#define EXTSCAN_CACHED_NL_SCAN_ID_TLV \
		((sizeof(result->scan_id) + NLA_HDRLEN) + \
		(sizeof(result->flags) + NLA_HDRLEN) + \
		(sizeof(result->num_results) + NLA_HDRLEN))+ \
		(sizeof(result->buckets_scanned) + NLA_HDRLEN)
#define EXTSCAN_CACHED_NL_SCAN_RESULTS_TLV \
		((sizeof(ap->ts) + NLA_HDRLEN) + \
		(sizeof(ap->ssid) + NLA_HDRLEN) + \
		(sizeof(ap->bssid) + NLA_HDRLEN) + \
		(sizeof(ap->channel) + NLA_HDRLEN) + \
		(sizeof(ap->rssi) + NLA_HDRLEN) + \
		(sizeof(ap->rtt) + NLA_HDRLEN) + \
		(sizeof(ap->rtt_sd) + NLA_HDRLEN) + \
		(sizeof(ap->beaconPeriod) + NLA_HDRLEN) + \
		(sizeof(ap->capability) + NLA_HDRLEN) + \
		(sizeof(ap->ieLength) + NLA_HDRLEN))
#define EXTSCAN_CACHED_NL_SCAN_RESULTS_IE_DATA_TLV \
		(ap->ieLength + NLA_HDRLEN)

	nl_buf_len = NLMSG_HDRLEN;
	nl_buf_len += EXTSCAN_CACHED_NL_FIXED_TLV;
	if (data->num_scan_ids) {
		nl_buf_len += sizeof(result->scan_id) + NLA_HDRLEN;
		nl_buf_len += EXTSCAN_CACHED_NEST_HDRLEN;
		result = &data->result[0];
		for (i = 0; i < data->num_scan_ids; i++) {
			nl_buf_len += EXTSCAN_CACHED_NEST_HDRLEN;
			nl_buf_len += EXTSCAN_CACHED_NL_SCAN_ID_TLV;
			nl_buf_len += EXTSCAN_CACHED_NEST_HDRLEN;

			ap = &result->ap[0];
			for (j = 0; j < result->num_results; j++) {
				nl_buf_len += EXTSCAN_CACHED_NEST_HDRLEN;
				nl_buf_len +=
					EXTSCAN_CACHED_NL_SCAN_RESULTS_TLV;
				if (ap->ieLength)
					nl_buf_len +=
					EXTSCAN_CACHED_NL_SCAN_RESULTS_IE_DATA_TLV;
				ap++;
			}
			result++;
		}
	}

	skb = wlan_cfg80211_vendor_cmd_alloc_reply_skb(hdd_ctx->wiphy,
						       nl_buf_len);
	if (!skb) {
		hdd_err("wlan_cfg80211_vendor_cmd_alloc_reply_skb failed");
		goto fail;
	}
	hdd_debug("Req Id %u Num_scan_ids %u More Data %u",
		data->request_id, data->num_scan_ids, data->more_data);

	result = &data->result[0];
	for (i = 0; i < data->num_scan_ids; i++) {
		hdd_debug("[i=%d] scan_id %u flags %u num_results %u buckets scanned %u",
			i, result->scan_id, result->flags, result->num_results,
			result->buckets_scanned);

		ap = &result->ap[0];
		for (j = 0; j < result->num_results; j++) {
			/*
			 * Firmware returns timestamp from ext scan start till
			 * BSSID was cached (in micro seconds). Add this with
			 * time gap between system boot up to ext scan start
			 * to derive the time since boot when the
			 * BSSID was cached.
			 */
			ap->ts += hdd_ctx->ext_scan_start_since_boot;
			hdd_debug("Timestamp %llu Ssid: " QDF_SSID_FMT " Bssid (" QDF_MAC_ADDR_FMT ") Channel %u Rssi %d RTT %u RTT_SD %u Beacon Period %u Capability 0x%x Ie length %d",
				  ap->ts,
				  QDF_SSID_REF(WLAN_SSID_MAX_LEN, ap->ssid),
				  QDF_MAC_ADDR_REF(ap->bssid.bytes),
				  ap->channel, ap->rssi, ap->rtt, ap->rtt_sd,
				  ap->beaconPeriod, ap->capability,
				  ap->ieLength);
			ap++;
		}
		result++;
	}

	if (nla_put_u32(skb, QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_REQUEST_ID,
		data->request_id) ||
	    nla_put_u32(skb,
		QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_NUM_RESULTS_AVAILABLE,
		data->num_scan_ids) ||
	    nla_put_u8(skb,
		QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SCAN_RESULT_MORE_DATA,
		data->more_data)) {
		hdd_err("put fail");
		goto fail;
	}

	if (data->num_scan_ids) {
		struct nlattr *nla_results;

		result = &data->result[0];

		if (nla_put_u32(skb,
			QCA_WLAN_VENDOR_ATTR_EXTSCAN_CACHED_RESULTS_SCAN_ID,
			result->scan_id)) {
			hdd_err("put fail");
			goto fail;
		}
		nla_results = nla_nest_start(skb,
			      QCA_WLAN_VENDOR_ATTR_EXTSCAN_CACHED_RESULTS_LIST);
		if (!nla_results)
			goto fail;

		for (i = 0; i < data->num_scan_ids; i++) {
			struct nlattr *nla_result;
			struct nlattr *nla_aps;

			nla_result = nla_nest_start(skb, i);
			if (!nla_result)
				goto fail;

			if (nla_put_u32(skb,
				QCA_WLAN_VENDOR_ATTR_EXTSCAN_CACHED_RESULTS_SCAN_ID,
				result->scan_id) ||
			    nla_put_u32(skb,
				QCA_WLAN_VENDOR_ATTR_EXTSCAN_CACHED_RESULTS_FLAGS,
				result->flags) ||
			    nla_put_u32(skb,
				QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_BUCKETS_SCANNED,
				result->buckets_scanned) ||
			    nla_put_u32(skb,
				QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_NUM_RESULTS_AVAILABLE,
				result->num_results)) {
				hdd_err("put fail");
				goto fail;
			}

			nla_aps = nla_nest_start(skb,
				     QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_LIST);
			if (!nla_aps)
				goto fail;

			ap = &result->ap[0];
			for (j = 0; j < result->num_results; j++) {
				if (hdd_extscan_nl_fill_bss(skb, ap, j))
					goto fail;

				ap++;
			}
			nla_nest_end(skb, nla_aps);
			nla_nest_end(skb, nla_result);
			result++;
		}
		nla_nest_end(skb, nla_results);
	}

	wlan_cfg80211_vendor_cmd_reply(skb);

	if (!data->more_data) {
		spin_lock(&context->context_lock);
		context->response_status = 0;
		complete(&context->response_event);
		spin_unlock(&context->context_lock);
	}
	return;

fail:
	wlan_cfg80211_vendor_free_skb(skb);
	spin_lock(&context->context_lock);
	context->response_status = -EINVAL;
	spin_unlock(&context->context_lock);
}

/**
 * wlan_hdd_cfg80211_extscan_hotlist_match_ind() - hot list match ind
 * @hdd_ctx: Pointer to hdd context
 * @data: Pointer to ext scan result event
 *
 * This callback execute in atomic context and must not invoke any
 * blocking calls.
 *
 * Return: none
 */
static void
wlan_hdd_cfg80211_extscan_hotlist_match_ind(struct hdd_context *hdd_ctx,
					    struct extscan_hotlist_match *data)
{
	struct sk_buff *skb = NULL;
	uint32_t i, index;
	int flags = cds_get_gfp_flags();

	hdd_enter();

	if (wlan_hdd_validate_context(hdd_ctx))
		return;
	if (!data) {
		hdd_err("data is null");
		return;
	}

	if (data->ap_found)
		index = QCA_NL80211_VENDOR_SUBCMD_EXTSCAN_HOTLIST_AP_FOUND_INDEX;
	else
		index = QCA_NL80211_VENDOR_SUBCMD_EXTSCAN_HOTLIST_AP_LOST_INDEX;

	skb = wlan_cfg80211_vendor_event_alloc(
		  hdd_ctx->wiphy,
		  NULL,
		  EXTSCAN_EVENT_BUF_SIZE + NLMSG_HDRLEN,
		  index, flags);

	if (!skb) {
		hdd_err("wlan_cfg80211_vendor_event_alloc failed");
		return;
	}
	hdd_debug("Req Id: %u Num_APs: %u MoreData: %u ap_found: %u",
			data->requestId, data->numOfAps, data->moreData,
			data->ap_found);

	for (i = 0; i < data->numOfAps; i++) {
		data->ap[i].ts = qdf_get_monotonic_boottime();
		hdd_debug("[i=%d] Timestamp %llu Ssid: " QDF_SSID_FMT " Bssid (" QDF_MAC_ADDR_FMT ") Channel %u Rssi %d RTT %u RTT_SD %u",
			  i, data->ap[i].ts,
			  QDF_SSID_REF(WLAN_SSID_MAX_LEN, data->ap[i].ssid),
			  QDF_MAC_ADDR_REF(data->ap[i].bssid.bytes),
			  data->ap[i].channel, data->ap[i].rssi,
			  data->ap[i].rtt, data->ap[i].rtt_sd);
	}

	if (nla_put_u32(skb,
		QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_REQUEST_ID,
		data->requestId) ||
	    nla_put_u32(skb,
		QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_NUM_RESULTS_AVAILABLE,
		data->numOfAps)) {
		hdd_err("put fail");
		goto fail;
	}

	if (data->numOfAps) {
		struct nlattr *aps;

		aps = nla_nest_start(skb,
			       QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_LIST);
		if (!aps)
			goto fail;

		for (i = 0; i < data->numOfAps; i++) {
			struct nlattr *ap;

			ap = nla_nest_start(skb, i);
			if (!ap)
				goto fail;

			if (hdd_wlan_nla_put_u64(skb,
				QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SCAN_RESULT_TIME_STAMP,
				data->ap[i].ts) ||
			    nla_put(skb,
				QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SCAN_RESULT_SSID,
				sizeof(data->ap[i].ssid),
				data->ap[i].ssid) ||
			    nla_put(skb,
				QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SCAN_RESULT_BSSID,
				sizeof(data->ap[i].bssid),
				data->ap[i].bssid.bytes) ||
			    nla_put_u32(skb,
				QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SCAN_RESULT_CHANNEL,
				data->ap[i].channel) ||
			    nla_put_s32(skb,
				QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SCAN_RESULT_RSSI,
				data->ap[i].rssi) ||
			    nla_put_u32(skb,
				QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SCAN_RESULT_RTT,
				data->ap[i].rtt) ||
			    nla_put_u32(skb,
				QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SCAN_RESULT_RTT_SD,
				data->ap[i].rtt_sd))
				goto fail;

			nla_nest_end(skb, ap);
		}
		nla_nest_end(skb, aps);

		if (nla_put_u8(skb,
		       QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SCAN_RESULT_MORE_DATA,
		       data->moreData))
			goto fail;
	}

	wlan_cfg80211_vendor_event(skb, flags);
	hdd_exit();
	return;

fail:
	wlan_cfg80211_vendor_free_skb(skb);
}

/**
 * wlan_hdd_cfg80211_extscan_signif_wifi_change_results_ind() -
 *	significant wifi change results indication
 * @hdd_ctx: Pointer to hdd context
 * @data: Pointer to signif wifi change event
 *
 * This callback execute in atomic context and must not invoke any
 * blocking calls.
 *
 * Return: none
 */
static void
wlan_hdd_cfg80211_extscan_signif_wifi_change_results_ind(
			struct hdd_context *hdd_ctx,
			tpSirWifiSignificantChangeEvent data)
{
	struct sk_buff *skb = NULL;
	tSirWifiSignificantChange *ap_info;
	int32_t *rssi;
	uint32_t i, j;
	int flags = cds_get_gfp_flags();

	hdd_enter();

	if (wlan_hdd_validate_context(hdd_ctx))
		return;
	if (!data) {
		hdd_err("data is null");
		return;
	}

	skb = wlan_cfg80211_vendor_event_alloc(
		hdd_ctx->wiphy,
		NULL,
		EXTSCAN_EVENT_BUF_SIZE + NLMSG_HDRLEN,
		QCA_NL80211_VENDOR_SUBCMD_EXTSCAN_SIGNIFICANT_CHANGE_INDEX,
		flags);

	if (!skb) {
		hdd_err("wlan_cfg80211_vendor_event_alloc failed");
		return;
	}
	hdd_debug("Req Id %u Num results %u More Data %u",
		data->requestId, data->numResults, data->moreData);

	ap_info = &data->ap[0];
	for (i = 0; i < data->numResults; i++) {
		hdd_debug("[i=%d] "
		       "Bssid (" QDF_MAC_ADDR_FMT ") "
		       "Channel %u "
		       "numOfRssi %d",
		       i,
		       QDF_MAC_ADDR_REF(ap_info->bssid.bytes),
		       ap_info->channel, ap_info->numOfRssi);
		rssi = &(ap_info)->rssi[0];
		for (j = 0; j < ap_info->numOfRssi; j++)
			hdd_debug("Rssi %d", *rssi++);

		ap_info = (tSirWifiSignificantChange *)((char *)ap_info +
				ap_info->numOfRssi * sizeof(*rssi) +
				sizeof(*ap_info));
	}

	if (nla_put_u32(skb,
		QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_REQUEST_ID,
		data->requestId) ||
	    nla_put_u32(skb,
		QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_NUM_RESULTS_AVAILABLE,
		data->numResults)) {
		hdd_err("put fail");
		goto fail;
	}

	if (data->numResults) {
		struct nlattr *aps;

		aps = nla_nest_start(skb,
			       QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_LIST);
		if (!aps)
			goto fail;

		ap_info = &data->ap[0];
		for (i = 0; i < data->numResults; i++) {
			struct nlattr *ap;

			ap = nla_nest_start(skb, i);
			if (!ap)
				goto fail;

			if (nla_put(skb,
				QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SIGNIFICANT_CHANGE_RESULT_BSSID,
				QDF_MAC_ADDR_SIZE, ap_info->bssid.bytes) ||
			    nla_put_u32(skb,
				QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SIGNIFICANT_CHANGE_RESULT_CHANNEL,
				ap_info->channel) ||
			    nla_put_u32(skb,
				QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SIGNIFICANT_CHANGE_RESULT_NUM_RSSI,
				ap_info->numOfRssi) ||
			    nla_put(skb,
				QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SIGNIFICANT_CHANGE_RESULT_RSSI_LIST,
				sizeof(s32) * ap_info->numOfRssi,
				&(ap_info)->rssi[0]))
				goto fail;

			nla_nest_end(skb, ap);

			ap_info = (tSirWifiSignificantChange *)((char *)ap_info
					+ ap_info->numOfRssi * sizeof(*rssi) +
					sizeof(*ap_info));
		}
		nla_nest_end(skb, aps);

		if (nla_put_u8(skb,
		     QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SCAN_RESULT_MORE_DATA,
		     data->moreData))
			goto fail;
	}

	wlan_cfg80211_vendor_event(skb, flags);
	return;

fail:
	wlan_cfg80211_vendor_free_skb(skb);
	return;

}

/**
 * wlan_hdd_cfg80211_extscan_full_scan_result_event() - full scan result event
 * @hdd_ctx: Pointer to hdd context
 * @data: Pointer to full scan result event
 *
 * This callback execute in atomic context and must not invoke any
 * blocking calls.
 *
 * Return: none
 */
static void
wlan_hdd_cfg80211_extscan_full_scan_result_event(struct hdd_context *hdd_ctx,
						 tpSirWifiFullScanResultEvent
						 data)
{
	struct sk_buff *skb;
	struct hdd_ext_scan_context *context;

	int flags = cds_get_gfp_flags();

	/* ENTER() intentionally not used in a frequently invoked API */

	if (wlan_hdd_validate_context(hdd_ctx))
		return;
	if (!data) {
		hdd_err("data is null");
		return;
	}

	if ((sizeof(*data) + data->ap.ieLength) >= EXTSCAN_EVENT_BUF_SIZE) {
		hdd_err("Frame exceeded NL size limitation, drop it!!");
		return;
	}
	skb = wlan_cfg80211_vendor_event_alloc(
		  hdd_ctx->wiphy,
		  NULL,
		  EXTSCAN_EVENT_BUF_SIZE + NLMSG_HDRLEN,
		  QCA_NL80211_VENDOR_SUBCMD_EXTSCAN_FULL_SCAN_RESULT_INDEX,
		  flags);

	if (!skb) {
		hdd_err("wlan_cfg80211_vendor_event_alloc failed");
		return;
	}

	data->ap.channel = cds_chan_to_freq(data->ap.channel);

	/*
	 * Android does not want the time stamp from the frame.
	 * Instead it wants a monotonic increasing value since boot
	 */
	data->ap.ts = qdf_get_monotonic_boottime();

	hdd_debug("Req Id %u More Data %u", data->requestId, data->moreData);
	hdd_debug("AP Info: Timestamp %llu Ssid: " QDF_SSID_FMT " Bssid (" QDF_MAC_ADDR_FMT ") Channel %u Rssi %d RTT %u RTT_SD %u Bcn Period %d Capability 0x%X IE Length %d",
		  data->ap.ts,
		  QDF_SSID_REF(WLAN_SSID_MAX_LEN, data->ap.ssid),
		  QDF_MAC_ADDR_REF(data->ap.bssid.bytes),
		  data->ap.channel, data->ap.rssi, data->ap.rtt,
		  data->ap.rtt_sd, data->ap.beaconPeriod,
		  data->ap.capability, data->ap.ieLength);

	if (nla_put_u32(skb,
		QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_REQUEST_ID,
		data->requestId) ||
	    hdd_wlan_nla_put_u64(skb,
		QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SCAN_RESULT_TIME_STAMP,
		data->ap.ts) ||
	    nla_put(skb,
		QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SCAN_RESULT_SSID,
		sizeof(data->ap.ssid),
		data->ap.ssid) ||
	    nla_put(skb, QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SCAN_RESULT_BSSID,
		sizeof(data->ap.bssid),
		data->ap.bssid.bytes) ||
	    nla_put_u32(skb,
		QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SCAN_RESULT_CHANNEL,
		data->ap.channel) ||
	    nla_put_s32(skb,
		QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SCAN_RESULT_RSSI,
		data->ap.rssi) ||
	    nla_put_u32(skb,
		QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SCAN_RESULT_RTT,
		data->ap.rtt) ||
	    nla_put_u32(skb,
		QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SCAN_RESULT_RTT_SD,
		data->ap.rtt_sd) ||
	    nla_put_u16(skb,
		QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SCAN_RESULT_BEACON_PERIOD,
		data->ap.beaconPeriod) ||
	    nla_put_u16(skb,
		QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SCAN_RESULT_CAPABILITY,
		data->ap.capability) ||
	    nla_put_u32(skb,
		QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SCAN_RESULT_IE_LENGTH,
		data->ap.ieLength) ||
	    nla_put_u8(skb,
		QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SCAN_RESULT_MORE_DATA,
		data->moreData)) {
		hdd_err("nla put fail");
		goto nla_put_failure;
	}

	if (data->ap.ieLength) {
		if (nla_put(skb,
		    QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SCAN_RESULT_IE_DATA,
		    data->ap.ieLength, data->ap.ieData))
			goto nla_put_failure;
	}

	context = &ext_scan_context;
	spin_lock(&context->context_lock);
	if (nla_put_u32(skb,
		QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_BUCKETS_SCANNED,
		context->buckets_scanned)) {
		spin_unlock(&context->context_lock);
		hdd_debug("Failed to include buckets_scanned");
		goto nla_put_failure;
	}
	spin_unlock(&context->context_lock);

	wlan_cfg80211_vendor_event(skb, flags);
	return;

nla_put_failure:
	wlan_cfg80211_vendor_free_skb(skb);
}

/**
 * wlan_hdd_cfg80211_extscan_scan_res_available_event() - scan result event
 * @hdd_ctx: Pointer to hdd context
 * @data: Pointer to scan results available indication param
 *
 * This callback execute in atomic context and must not invoke any
 * blocking calls.
 *
 * Return: none
 */
static void
wlan_hdd_cfg80211_extscan_scan_res_available_event(
			struct hdd_context *hdd_ctx,
			tpSirExtScanResultsAvailableIndParams data)
{
	struct sk_buff *skb;
	int flags = cds_get_gfp_flags();

	hdd_enter();

	if (wlan_hdd_validate_context(hdd_ctx))
		return;
	if (!data) {
		hdd_err("data is null");
		return;
	}

	skb = wlan_cfg80211_vendor_event_alloc(
		 hdd_ctx->wiphy,
		 NULL,
		 EXTSCAN_EVENT_BUF_SIZE + NLMSG_HDRLEN,
		 QCA_NL80211_VENDOR_SUBCMD_EXTSCAN_SCAN_RESULTS_AVAILABLE_INDEX,
		 flags);

	if (!skb) {
		hdd_err("wlan_cfg80211_vendor_event_alloc failed");
		return;
	}

	hdd_debug("Req Id %u Num results %u",
	       data->requestId, data->numResultsAvailable);
	if (nla_put_u32(skb,
		QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_REQUEST_ID,
		data->requestId) ||
	    nla_put_u32(skb,
		QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_NUM_RESULTS_AVAILABLE,
		data->numResultsAvailable)) {
		hdd_err("nla put fail");
		goto nla_put_failure;
	}

	wlan_cfg80211_vendor_event(skb, flags);
	hdd_exit();
	return;

nla_put_failure:
	wlan_cfg80211_vendor_free_skb(skb);
}

/**
 * wlan_hdd_cfg80211_extscan_scan_progress_event() - scan progress event
 * @hdd_ctx: Pointer to hdd context
 * @data: Pointer to scan event indication param
 *
 * This callback execute in atomic context and must not invoke any
 * blocking calls.
 *
 * Return: none
 */
static void
wlan_hdd_cfg80211_extscan_scan_progress_event(struct hdd_context *hdd_ctx,
					      tpSirExtScanOnScanEventIndParams
					      data)
{
	struct sk_buff *skb;
	int flags = cds_get_gfp_flags();
	struct hdd_ext_scan_context *context;

	/* ENTER() intentionally not used in a frequently invoked API */

	if (wlan_hdd_validate_context(hdd_ctx))
		return;
	if (!data) {
		hdd_err("data is null");
		return;
	}

	skb = wlan_cfg80211_vendor_event_alloc(
			hdd_ctx->wiphy,
			NULL,
			EXTSCAN_EVENT_BUF_SIZE + NLMSG_HDRLEN,
			QCA_NL80211_VENDOR_SUBCMD_EXTSCAN_SCAN_EVENT_INDEX,
			flags);

	if (!skb) {
		hdd_err("wlan_cfg80211_vendor_event_alloc failed");
		return;
	}

	hdd_debug("Request Id: %u Scan event type: %u Scan event status: %u buckets scanned: %u",
		  data->requestId, data->scanEventType, data->status,
		  data->buckets_scanned);

	context = &ext_scan_context;
	spin_lock(&context->context_lock);
	if (data->scanEventType == WIFI_EXTSCAN_CYCLE_COMPLETED_EVENT) {
		context->buckets_scanned = 0;
		data->scanEventType = WIFI_EXTSCAN_RESULTS_AVAILABLE;
		spin_unlock(&context->context_lock);
	} else if (data->scanEventType == WIFI_EXTSCAN_CYCLE_STARTED_EVENT) {
		context->buckets_scanned = data->buckets_scanned;
		/* No need to report to user space */
		spin_unlock(&context->context_lock);
		goto nla_put_failure;
	} else {
		spin_unlock(&context->context_lock);
	}

	if (nla_put_u32(skb, QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_REQUEST_ID,
			data->requestId) ||
	    nla_put_u8(skb,
		       QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SCAN_EVENT_TYPE,
		       data->scanEventType)) {
		hdd_err("nla put fail");
		goto nla_put_failure;
	}

	wlan_cfg80211_vendor_event(skb, flags);
	return;

nla_put_failure:
	wlan_cfg80211_vendor_free_skb(skb);
}

/**
 * wlan_hdd_cfg80211_extscan_epno_match_found() - pno match found
 * @hdd_ctx: HDD context
 * @data: matched network data
 *
 * This function reads the matched network data and fills NL vendor attributes
 * and send it to upper layer.
 * This callback execute in atomic context and must not invoke any
 * blocking calls.
 *
 * Return: 0 on success, error number otherwise
 */
static void
wlan_hdd_cfg80211_extscan_epno_match_found(struct hdd_context *hdd_ctx,
					   struct pno_match_found *data)
{
	struct sk_buff *skb;
	uint32_t len, i;
	int flags = cds_get_gfp_flags();
	enum qca_nl80211_vendor_subcmds_index index =
		QCA_NL80211_VENDOR_SUBCMD_EXTSCAN_PNO_NETWORK_FOUND_INDEX;

	hdd_enter();

	if (wlan_hdd_validate_context(hdd_ctx))
		return;
	if (!data) {
		hdd_err("data is null");
		return;
	}

	/*
	 * If the number of match found APs including IE data exceeds NL 4K size
	 * limitation, drop that beacon/probe rsp frame.
	 */
	len = sizeof(*data) +
			(data->num_results + sizeof(tSirWifiScanResult));
	for (i = 0; i < data->num_results; i++)
		len += data->ap[i].ieLength;

	if (len >= EXTSCAN_EVENT_BUF_SIZE) {
		hdd_err("Frame exceeded NL size limitation, drop it!");
		return;
	}

	skb = wlan_cfg80211_vendor_event_alloc(hdd_ctx->wiphy, NULL,
					       EXTSCAN_EVENT_BUF_SIZE +
					       NLMSG_HDRLEN,
					       index, flags);
	if (!skb) {
		hdd_err("wlan_cfg80211_vendor_event_alloc failed");
		return;
	}

	hdd_debug("Req Id %u More Data %u num_results %d",
		data->request_id, data->more_data, data->num_results);
	for (i = 0; i < data->num_results; i++) {
		data->ap[i].channel = cds_chan_to_freq(data->ap[i].channel);
		hdd_debug("AP Info: Timestamp %llu) Ssid: " QDF_SSID_FMT " Bssid (" QDF_MAC_ADDR_FMT ") Channel %u Rssi %d RTT %u RTT_SD %u Bcn Period %d Capability 0x%X IE Length %d",
			  data->ap[i].ts,
			  QDF_SSID_REF(WLAN_SSID_MAX_LEN, data->ap[i].ssid),
			  QDF_MAC_ADDR_REF(data->ap[i].bssid.bytes),
			  data->ap[i].channel, data->ap[i].rssi,
			  data->ap[i].rtt, data->ap[i].rtt_sd,
			  data->ap[i].beaconPeriod, data->ap[i].capability,
			  data->ap[i].ieLength);
	}

	if (nla_put_u32(skb, QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_REQUEST_ID,
		data->request_id) ||
	    nla_put_u32(skb,
		QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_NUM_RESULTS_AVAILABLE,
		data->num_results) ||
	    nla_put_u8(skb,
		QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SCAN_RESULT_MORE_DATA,
		data->more_data)) {
		hdd_err("nla put fail");
		goto fail;
	}

	if (data->num_results) {
		struct nlattr *nla_aps;

		nla_aps = nla_nest_start(skb,
			QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_LIST);
		if (!nla_aps)
			goto fail;

		for (i = 0; i < data->num_results; i++) {
			if (hdd_extscan_nl_fill_bss(skb, &data->ap[i], i))
				goto fail;
		}
		nla_nest_end(skb, nla_aps);
	}

	wlan_cfg80211_vendor_event(skb, flags);
	return;

fail:
	wlan_cfg80211_vendor_free_skb(skb);
}

/**
 * wlan_hdd_cfg80211_passpoint_match_found() - passpoint match found
 * @ctx: HDD context
 * @data: matched network data
 *
 * This function reads the match network %data and fill in the skb with
 * NL attributes and send up the NL event
 * This callback execute in atomic context and must not invoke any
 * blocking calls.
 *
 * Return: none
 */
static void
wlan_hdd_cfg80211_passpoint_match_found(void *ctx,
					struct wifi_passpoint_match *data)
{
	struct hdd_context *hdd_ctx  = ctx;
	struct sk_buff *skb     = NULL;
	uint32_t len, i, num_matches = 1, more_data = 0;
	struct nlattr *nla_aps, *nla_bss;
	int flags = cds_get_gfp_flags();
	enum qca_nl80211_vendor_subcmds_index index =
		QCA_NL80211_VENDOR_SUBCMD_EXTSCAN_PNO_PASSPOINT_NETWORK_FOUND_INDEX;

	hdd_enter();

	if (wlan_hdd_validate_context(hdd_ctx))
		return;
	if (!data) {
		hdd_err("data is null");
		return;
	}

	len = sizeof(*data) + data->ap.ieLength + data->anqp_len;
	if (len >= EXTSCAN_EVENT_BUF_SIZE) {
		hdd_err("Result exceeded NL size limitation, drop it");
		return;
	}

	skb = wlan_cfg80211_vendor_event_alloc(hdd_ctx->wiphy, NULL,
					       EXTSCAN_EVENT_BUF_SIZE +
					       NLMSG_HDRLEN,
					       index, flags);
	if (!skb) {
		hdd_err("wlan_cfg80211_vendor_event_alloc failed");
		return;
	}

	hdd_debug("Req Id %u Id %u ANQP length %u num_matches %u",
		data->request_id, data->id, data->anqp_len, num_matches);
	for (i = 0; i < num_matches; i++) {
		hdd_debug("AP Info: Timestamp %llu Ssid: " QDF_SSID_FMT " Bssid (" QDF_MAC_ADDR_FMT ") Channel %u Rssi %d RTT %u RTT_SD %u Bcn Period %d Capability 0x%X IE Length %d",
			  data->ap.ts,
			  QDF_SSID_REF(WLAN_SSID_MAX_LEN, data->ap.ssid),
			  QDF_MAC_ADDR_REF(data->ap.bssid.bytes),
			  data->ap.channel, data->ap.rssi, data->ap.rtt,
			  data->ap.rtt_sd, data->ap.beaconPeriod,
			  data->ap.capability, data->ap.ieLength);
	}

	if (nla_put_u32(skb, QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_REQUEST_ID,
		data->request_id) ||
	    nla_put_u32(skb,
		QCA_WLAN_VENDOR_ATTR_EXTSCAN_PNO_RESULTS_PASSPOINT_NETWORK_FOUND_NUM_MATCHES,
		num_matches) ||
	    nla_put_u8(skb,
		QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_SCAN_RESULT_MORE_DATA,
		more_data)) {
		hdd_err("nla put fail");
		goto fail;
	}

	nla_aps = nla_nest_start(skb,
		QCA_WLAN_VENDOR_ATTR_EXTSCAN_PNO_RESULTS_PASSPOINT_MATCH_RESULT_LIST);
	if (!nla_aps)
		goto fail;

	for (i = 0; i < num_matches; i++) {
		struct nlattr *nla_ap;

		nla_ap = nla_nest_start(skb, i);
		if (!nla_ap)
			goto fail;

		if (nla_put_u32(skb,
			QCA_WLAN_VENDOR_ATTR_EXTSCAN_PNO_RESULTS_PASSPOINT_MATCH_ID,
			data->id) ||
		    nla_put_u32(skb,
			QCA_WLAN_VENDOR_ATTR_EXTSCAN_PNO_RESULTS_PASSPOINT_MATCH_ANQP_LEN,
			data->anqp_len)) {
			goto fail;
		}

		if (data->anqp_len)
			if (nla_put(skb,
				QCA_WLAN_VENDOR_ATTR_EXTSCAN_PNO_RESULTS_PASSPOINT_MATCH_ANQP,
				data->anqp_len, data->anqp))
				goto fail;

		nla_bss = nla_nest_start(skb,
				QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_LIST);
		if (!nla_bss)
			goto fail;

		if (hdd_extscan_nl_fill_bss(skb, &data->ap, 0))
			goto fail;

		nla_nest_end(skb, nla_bss);
		nla_nest_end(skb, nla_ap);
	}
	nla_nest_end(skb, nla_aps);

	wlan_cfg80211_vendor_event(skb, flags);
	return;

fail:
	wlan_cfg80211_vendor_free_skb(skb);
}

/**
 * wlan_hdd_cfg80211_extscan_generic_rsp() -
 *	Handle a generic ExtScan Response message
 * @hdd_ctx: HDD context registered with SME
 * @response: The ExtScan response from firmware
 *
 * This function will handle a generic ExtScan response message from
 * firmware and will communicate the result to the userspace thread
 * that is waiting for the response.
 *
 * Return: none
 */
static void
wlan_hdd_cfg80211_extscan_generic_rsp(struct hdd_context *hdd_ctx,
	 struct sir_extscan_generic_response *response)
{
	struct hdd_ext_scan_context *context;

	hdd_enter();

	if (wlan_hdd_validate_context(hdd_ctx) || !response) {
		hdd_err("HDD context is not valid or response(%pK) is null",
		       response);
		return;
	}

	hdd_debug("request %u status %u",
	       response->request_id, response->status);

	context = &ext_scan_context;
	spin_lock(&context->context_lock);
	if (context->request_id == response->request_id) {
		context->response_status = response->status ? -EINVAL : 0;
		complete(&context->response_event);
	}
	spin_unlock(&context->context_lock);
}

void wlan_hdd_cfg80211_extscan_callback(hdd_handle_t hdd_handle,
					const uint16_t event_id, void *msg)
{
	struct hdd_context *hdd_ctx = hdd_handle_to_context(hdd_handle);

	/* ENTER() intentionally not used in a frequently invoked API */

	if (wlan_hdd_validate_context(hdd_ctx))
		return;

	hdd_debug("Rcvd Event %d", event_id);

	switch (event_id) {
	case eSIR_EXTSCAN_CACHED_RESULTS_RSP:
		/* There is no need to send this response to upper layer
		 * Just log the message
		 */
		hdd_debug("Rcvd eSIR_EXTSCAN_CACHED_RESULTS_RSP");
		break;

	case eSIR_EXTSCAN_GET_CAPABILITIES_IND:
		wlan_hdd_cfg80211_extscan_get_capabilities_rsp(hdd_ctx, msg);
		break;

	case eSIR_EXTSCAN_HOTLIST_MATCH_IND:
		wlan_hdd_cfg80211_extscan_hotlist_match_ind(hdd_ctx, msg);
		break;

	case eSIR_EXTSCAN_SIGNIFICANT_WIFI_CHANGE_RESULTS_IND:
		wlan_hdd_cfg80211_extscan_signif_wifi_change_results_ind(hdd_ctx,
									 msg);
		break;

	case eSIR_EXTSCAN_CACHED_RESULTS_IND:
		wlan_hdd_cfg80211_extscan_cached_results_ind(hdd_ctx, msg);
		break;

	case eSIR_EXTSCAN_SCAN_RES_AVAILABLE_IND:
		wlan_hdd_cfg80211_extscan_scan_res_available_event(hdd_ctx,
								   msg);
		break;

	case eSIR_EXTSCAN_FULL_SCAN_RESULT_IND:
		wlan_hdd_cfg80211_extscan_full_scan_result_event(hdd_ctx, msg);
		break;

	case eSIR_EPNO_NETWORK_FOUND_IND:
		wlan_hdd_cfg80211_extscan_epno_match_found(hdd_ctx, msg);
		break;

	case eSIR_EXTSCAN_SCAN_PROGRESS_EVENT_IND:
		wlan_hdd_cfg80211_extscan_scan_progress_event(hdd_ctx, msg);
		break;

	case eSIR_PASSPOINT_NETWORK_FOUND_IND:
		wlan_hdd_cfg80211_passpoint_match_found(hdd_ctx, msg);
		break;

	case eSIR_EXTSCAN_START_RSP:
	case eSIR_EXTSCAN_STOP_RSP:
	case eSIR_EXTSCAN_SET_BSSID_HOTLIST_RSP:
	case eSIR_EXTSCAN_RESET_BSSID_HOTLIST_RSP:
	case eSIR_EXTSCAN_SET_SIGNIFICANT_WIFI_CHANGE_RSP:
	case eSIR_EXTSCAN_RESET_SIGNIFICANT_WIFI_CHANGE_RSP:
	case eSIR_EXTSCAN_SET_SSID_HOTLIST_RSP:
	case eSIR_EXTSCAN_RESET_SSID_HOTLIST_RSP:
		wlan_hdd_cfg80211_extscan_generic_rsp(hdd_ctx, msg);
		break;

	default:
		hdd_err("Unknown event type: %u", event_id);
		break;
	}
}

/*
 * define short names for the global vendor params
 * used by wlan_hdd_send_ext_scan_capability()
 */
#define PARAM_REQUEST_ID \
	QCA_WLAN_VENDOR_ATTR_EXTSCAN_SUBCMD_CONFIG_PARAM_REQUEST_ID
#define PARAM_STATUS \
	QCA_WLAN_VENDOR_ATTR_EXTSCAN_STATUS
#define MAX_EXTSCAN_CACHE_SIZE \
	QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_CAPABILITIES_MAX_SCAN_CACHE_SIZE
#define MAX_SCAN_BUCKETS \
	QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_CAPABILITIES_MAX_SCAN_BUCKETS
#define MAX_AP_CACHE_PER_SCAN \
	QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_CAPABILITIES_MAX_AP_CACHE_PER_SCAN
#define MAX_RSSI_SAMPLE_SIZE \
	QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_CAPABILITIES_MAX_RSSI_SAMPLE_SIZE
#define MAX_SCAN_RPT_THRHOLD \
	QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_CAPABILITIES_MAX_SCAN_REPORTING_THRESHOLD
#define MAX_HOTLIST_BSSIDS \
	QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_CAPABILITIES_MAX_HOTLIST_BSSIDS
#define MAX_SIGNIFICANT_WIFI_CHANGE_APS \
	QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_CAPABILITIES_MAX_SIGNIFICANT_WIFI_CHANGE_APS
#define MAX_BSSID_HISTORY_ENTRIES \
	QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_CAPABILITIES_MAX_BSSID_HISTORY_ENTRIES
#define MAX_HOTLIST_SSIDS \
	QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_CAPABILITIES_MAX_HOTLIST_SSIDS
#define MAX_NUM_EPNO_NETS \
	QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_CAPABILITIES_MAX_NUM_EPNO_NETS
#define MAX_NUM_EPNO_NETS_BY_SSID \
	QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_CAPABILITIES_MAX_NUM_EPNO_NETS_BY_SSID
#define MAX_NUM_WHITELISTED_SSID \
	QCA_WLAN_VENDOR_ATTR_EXTSCAN_RESULTS_CAPABILITIES_MAX_NUM_WHITELISTED_SSID
#define MAX_NUM_BLACKLISTED_BSSID \
	QCA_WLAN_VENDOR_ATTR_EXTSCAN_MAX_NUM_BLACKLISTED_BSSID
/**
 * wlan_hdd_send_ext_scan_capability - send ext scan capability to user space
 * @hdd_ctx: Pointer to hdd context
 *
 * Return: 0 for success, non-zero for failure
 */
static int wlan_hdd_send_ext_scan_capability(struct hdd_context *hdd_ctx)
{
	int ret;
	struct sk_buff *skb;
	struct ext_scan_capabilities_response *data;
	uint32_t nl_buf_len;

	ret = wlan_hdd_validate_context(hdd_ctx);
	if (0 != ret)
		return ret;

	data = &(ext_scan_context.capability_response);
	nl_buf_len = NLMSG_HDRLEN;
	nl_buf_len += (sizeof(data->requestId) + NLA_HDRLEN) +
	(sizeof(data->status) + NLA_HDRLEN) +
	(sizeof(data->max_scan_cache_size) + NLA_HDRLEN) +
	(sizeof(data->max_scan_buckets) + NLA_HDRLEN) +
	(sizeof(data->max_ap_cache_per_scan) + NLA_HDRLEN) +
	(sizeof(data->max_rssi_sample_size) + NLA_HDRLEN) +
	(sizeof(data->max_scan_reporting_threshold) + NLA_HDRLEN) +
	(sizeof(data->max_hotlist_bssids) + NLA_HDRLEN) +
	(sizeof(data->max_significant_wifi_change_aps) + NLA_HDRLEN) +
	(sizeof(data->max_bssid_history_entries) + NLA_HDRLEN) +
	(sizeof(data->max_hotlist_ssids) + NLA_HDRLEN) +
	(sizeof(data->max_number_epno_networks) + NLA_HDRLEN) +
	(sizeof(data->max_number_epno_networks_by_ssid) + NLA_HDRLEN) +
	(sizeof(data->max_number_of_allow_listed_ssid) + NLA_HDRLEN) +
	(sizeof(data->max_number_of_deny_listed_bssid) + NLA_HDRLEN);
	skb = wlan_cfg80211_vendor_cmd_alloc_reply_skb(hdd_ctx->wiphy,
						       nl_buf_len);
	if (!skb) {
		hdd_err("wlan_cfg80211_vendor_cmd_alloc_reply_skb failed");
		return -ENOMEM;
	}


	hdd_debug("Req Id %u", data->requestId);
	hdd_debug("Status %u", data->status);
	hdd_debug("Scan cache size %u",
	       data->max_scan_cache_size);
	hdd_debug("Scan buckets %u", data->max_scan_buckets);
	hdd_debug("Max AP per scan %u",
	       data->max_ap_cache_per_scan);
	hdd_debug("max_rssi_sample_size %u",
	       data->max_rssi_sample_size);
	hdd_debug("max_scan_reporting_threshold %u",
	       data->max_scan_reporting_threshold);
	hdd_debug("max_hotlist_bssids %u",
	       data->max_hotlist_bssids);
	hdd_debug("max_significant_wifi_change_aps %u",
	       data->max_significant_wifi_change_aps);
	hdd_debug("max_bssid_history_entries %u",
	       data->max_bssid_history_entries);
	hdd_debug("max_hotlist_ssids %u", data->max_hotlist_ssids);
	hdd_debug("max_number_epno_networks %u",
					data->max_number_epno_networks);
	hdd_debug("max_number_epno_networks_by_ssid %u",
					data->max_number_epno_networks_by_ssid);
	hdd_debug("max_number_of_allow_listed_ssid %u",
		  data->max_number_of_allow_listed_ssid);
	hdd_debug("max_number_of_deny_listed_bssid (%u)",
		  data->max_number_of_deny_listed_bssid);

	if (nla_put_u32(skb, PARAM_REQUEST_ID, data->requestId) ||
	    nla_put_u32(skb, PARAM_STATUS, data->status) ||
	    nla_put_u32(skb, MAX_EXTSCAN_CACHE_SIZE,
					data->max_scan_cache_size) ||
	    nla_put_u32(skb, MAX_SCAN_BUCKETS, data->max_scan_buckets) ||
	    nla_put_u32(skb, MAX_AP_CACHE_PER_SCAN,
			data->max_ap_cache_per_scan) ||
	    nla_put_u32(skb, MAX_RSSI_SAMPLE_SIZE,
			data->max_rssi_sample_size) ||
	    nla_put_u32(skb, MAX_SCAN_RPT_THRHOLD,
			data->max_scan_reporting_threshold) ||
	    nla_put_u32(skb, MAX_HOTLIST_BSSIDS, data->max_hotlist_bssids) ||
	    nla_put_u32(skb, MAX_SIGNIFICANT_WIFI_CHANGE_APS,
			data->max_significant_wifi_change_aps) ||
	    nla_put_u32(skb, MAX_BSSID_HISTORY_ENTRIES,
			data->max_bssid_history_entries) ||
	    nla_put_u32(skb, MAX_HOTLIST_SSIDS,	data->max_hotlist_ssids) ||
	    nla_put_u32(skb, MAX_NUM_EPNO_NETS,
			data->max_number_epno_networks) ||
	    nla_put_u32(skb, MAX_NUM_EPNO_NETS_BY_SSID,
			data->max_number_epno_networks_by_ssid) ||
	    nla_put_u32(skb, MAX_NUM_WHITELISTED_SSID,
			data->max_number_of_allow_listed_ssid) ||
	    nla_put_u32(skb, MAX_NUM_BLACKLISTED_BSSID,
			data->max_number_of_deny_listed_bssid)) {
		hdd_err("nla put fail");
		goto nla_put_failure;
	}

	wlan_cfg80211_vendor_cmd_reply(skb);
	return 0;

nla_put_failure:
	wlan_cfg80211_vendor_free_skb(skb);
	return -EINVAL;
}
/*
 * done with short names for the global vendor params
 * used by wlan_hdd_send_ext_scan_capability()
 */
#undef PARAM_REQUEST_ID
#undef PARAM_STATUS
#undef MAX_EXTSCAN_CACHE_SIZE
#undef MAX_SCAN_BUCKETS
#undef MAX_AP_CACHE_PER_SCAN
#undef MAX_RSSI_SAMPLE_SIZE
#undef MAX_SCAN_RPT_THRHOLD
#undef MAX_HOTLIST_BSSIDS
#undef MAX_SIGNIFICANT_WIFI_CHANGE_APS
#undef MAX_BSSID_HISTORY_ENTRIES
#undef MAX_HOTLIST_SSIDS
#undef MAX_NUM_EPNO_NETS
#undef MAX_NUM_EPNO_NETS_BY_SSID
#undef MAX_NUM_WHITELISTED_SSID
#undef MAX_NUM_BLACKLISTED_BSSID

/**
 * __wlan_hdd_cfg80211_extscan_get_capabilities() - get ext scan capabilities
 * @wiphy: Pointer to wireless phy
 * @wdev: Pointer to wireless device
 * @data: Pointer to data
 * @data_len: Data length
 *
 * Return: none
 */
static int
__wlan_hdd_cfg80211_extscan_get_capabilities(struct wiphy *wiphy,
					     struct wireless_dev *wdev,
					     const void *data, int data_len)
{
	int id, ret;
	unsigned long rc;
	struct hdd_ext_scan_context *context;
	struct extscan_capabilities_params params;
	struct net_device *dev = wdev->netdev;
	struct hdd_adapter *adapter = WLAN_HDD_GET_PRIV_PTR(dev);
	struct hdd_context *hdd_ctx = wiphy_priv(wiphy);
	struct nlattr *tb[EXTSCAN_PARAM_MAX + 1];
	QDF_STATUS status;

	hdd_enter_dev(dev);

	if (QDF_GLOBAL_FTM_MODE == hdd_get_conparam()) {
		hdd_err("Command not allowed in FTM mode");
		return -EPERM;
	}

	ret = wlan_hdd_validate_context(hdd_ctx);
	if (0 != ret)
		return -EINVAL;

	if (hdd_ctx->driver_status == DRIVER_MODULES_CLOSED) {
		hdd_err("Driver Modules are closed");
		return -EINVAL;
	}

	if (!ucfg_extscan_get_enable(hdd_ctx->psoc)) {
		hdd_err("extscan not supported");
		return -ENOTSUPP;
	}
	if (wlan_cfg80211_nla_parse(tb, EXTSCAN_PARAM_MAX, data, data_len,
				    wlan_hdd_extscan_config_policy)) {
		hdd_err("Invalid ATTR");
		return -EINVAL;
	}

	/* Parse and fetch request Id */
	id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_SUBCMD_CONFIG_PARAM_REQUEST_ID;
	if (!tb[id]) {
		hdd_err("attr request id failed");
		return -EINVAL;
	}

	params.request_id = nla_get_u32(tb[id]);
	params.vdev_id = adapter->deflink->vdev_id;
	hdd_debug("Req Id %d Vdev Id %d", params.request_id, params.vdev_id);

	context = &ext_scan_context;
	spin_lock(&context->context_lock);
	context->request_id = params.request_id;
	INIT_COMPLETION(context->response_event);
	spin_unlock(&context->context_lock);

	status = sme_ext_scan_get_capabilities(hdd_ctx->mac_handle, &params);
	if (!QDF_IS_STATUS_SUCCESS(status)) {
		hdd_err("sme_ext_scan_get_capabilities failed(err=%d)",
			status);
		return -EINVAL;
	}

	rc = wait_for_completion_timeout(&context->response_event,
		msecs_to_jiffies(WLAN_WAIT_TIME_EXTSCAN));
	if (!rc) {
		hdd_err("Target response timed out");
		return -ETIMEDOUT;
	}

	ret = wlan_hdd_send_ext_scan_capability(hdd_ctx);
	if (ret)
		hdd_err("Failed to send ext scan capability to user space");
	hdd_exit();
	return ret;
}

/**
 * wlan_hdd_cfg80211_extscan_get_capabilities() - get ext scan capabilities
 * @wiphy: Pointer to wiphy
 * @wdev: Pointer to wdev
 * @data: Pointer to data
 * @data_len: Data length
 *
 * Return: 0 for success, non-zero for failure
 */
int wlan_hdd_cfg80211_extscan_get_capabilities(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_extscan_get_capabilities(wiphy, wdev,
							     data, data_len);

	osif_vdev_sync_op_stop(vdev_sync);

	return errno;
}

/**
 * __wlan_hdd_cfg80211_extscan_get_cached_results() - extscan get cached results
 * @wiphy: wiphy pointer
 * @wdev: pointer to struct wireless_dev
 * @data: pointer to incoming NL vendor data
 * @data_len: length of @data
 *
 * This function parses the incoming NL vendor command data attributes and
 * invokes the SME Api and blocks on a completion variable.
 * Each WMI event with cached scan results data chunk results in
 * function call wlan_hdd_cfg80211_extscan_cached_results_ind and each
 * data chunk is sent up the layer in wlan_cfg80211_vendor_cmd_alloc_reply_skb.
 *
 * If timeout happens before receiving all of the data, this function sets
 * a context variable @ignore_cached_results to %true, all of the next data
 * chunks are checked against this variable and dropped.
 *
 * Return: 0 on success; error number otherwise.
 */
static int
__wlan_hdd_cfg80211_extscan_get_cached_results(struct wiphy *wiphy,
					       struct wireless_dev *wdev,
					       const void *data,
					       int data_len)
{
	struct extscan_cached_result_params params;
	struct net_device *dev = wdev->netdev;
	struct hdd_adapter *adapter = WLAN_HDD_GET_PRIV_PTR(dev);
	struct hdd_context *hdd_ctx = wiphy_priv(wiphy);
	struct nlattr *tb[EXTSCAN_PARAM_MAX + 1];
	struct hdd_ext_scan_context *context;
	QDF_STATUS status;
	int id, retval;
	unsigned long rc;

	/* ENTER_DEV() intentionally not used in a frequently invoked API */

	if (QDF_GLOBAL_FTM_MODE == hdd_get_conparam()) {
		hdd_err("Command not allowed in FTM mode");
		return -EPERM;
	}

	retval = wlan_hdd_validate_context(hdd_ctx);
	if (0 != retval)
		return -EINVAL;

	if (!ucfg_extscan_get_enable(hdd_ctx->psoc)) {
		hdd_err("extscan not supported");
		return -ENOTSUPP;
	}
	if (wlan_cfg80211_nla_parse(tb, EXTSCAN_PARAM_MAX, data, data_len,
				    wlan_hdd_extscan_config_policy)) {
		hdd_err("Invalid ATTR");
		return -EINVAL;
	}

	/* Parse and fetch request Id */
	id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_SUBCMD_CONFIG_PARAM_REQUEST_ID;
	if (!tb[id]) {
		hdd_err("attr request id failed");
		return -EINVAL;
	}

	params.request_id = nla_get_u32(tb[id]);
	params.vdev_id = adapter->deflink->vdev_id;

	/* Parse and fetch flush parameter */
	id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_GET_CACHED_SCAN_RESULTS_CONFIG_PARAM_FLUSH;
	if (!tb[id]) {
		hdd_err("attr flush failed");
		return -EINVAL;
	}
	params.flush = nla_get_u8(tb[id]);
	hdd_debug("Req Id: %u Vdev Id: %d Flush: %d",
		  params.request_id, params.vdev_id, params.flush);

	context = &ext_scan_context;
	spin_lock(&context->context_lock);
	context->request_id = params.request_id;
	context->ignore_cached_results = false;
	INIT_COMPLETION(context->response_event);
	spin_unlock(&context->context_lock);

	status = sme_get_cached_results(hdd_ctx->mac_handle, &params);
	if (!QDF_IS_STATUS_SUCCESS(status)) {
		hdd_err("sme_get_cached_results failed(err=%d)", status);
		return -EINVAL;
	}

	rc = wait_for_completion_timeout(&context->response_event,
			msecs_to_jiffies(WLAN_WAIT_TIME_EXTSCAN));
	if (!rc) {
		hdd_err("Target response timed out");
		retval = -ETIMEDOUT;
		spin_lock(&context->context_lock);
		context->ignore_cached_results = true;
		spin_unlock(&context->context_lock);
	} else {
		spin_lock(&context->context_lock);
		retval = context->response_status;
		spin_unlock(&context->context_lock);
	}
	return retval;
}

/**
 * wlan_hdd_cfg80211_extscan_get_cached_results() - extscan get cached results
 * @wiphy: wiphy pointer
 * @wdev: pointer to struct wireless_dev
 * @data: pointer to incoming NL vendor data
 * @data_len: length of @data
 *
 * This function parses the incoming NL vendor command data attributes and
 * invokes the SME Api and blocks on a completion variable.
 * Each WMI event with cached scan results data chunk results in
 * function call wlan_hdd_cfg80211_extscan_cached_results_ind and each
 * data chunk is sent up the layer in wlan_cfg80211_vendor_cmd_alloc_reply_skb.
 *
 * If timeout happens before receiving all of the data, this function sets
 * a context variable @ignore_cached_results to %true, all of the next data
 * chunks are checked against this variable and dropped.
 *
 * Return: 0 on success; error number otherwise.
 */
int wlan_hdd_cfg80211_extscan_get_cached_results(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_extscan_get_cached_results(wiphy, wdev,
							       data, data_len);

	osif_vdev_sync_op_stop(vdev_sync);

	return errno;
}

/**
 * hdd_parse_ap_rssi_threshold() - parse AP RSSI threshold parameters
 * @attr: netlink attribute containing the AP RSSI threshold parameters
 * @ap: destination buffer for the parsed parameters
 *
 * This function parses the BSSID, low RSSI and high RSSI values from
 * the @attr netlink attribute, storing the parsed values in @ap.
 *
 * Return: 0 if @attr is parsed and all required attributes are
 * present, otherwise a negative errno.
 */
static int hdd_parse_ap_rssi_threshold(struct nlattr *attr,
				       struct ap_threshold_params *ap)
{
	struct nlattr *tb[EXTSCAN_PARAM_MAX + 1];
	int id;

	if (wlan_cfg80211_nla_parse(tb, EXTSCAN_PARAM_MAX,
				    nla_data(attr), nla_len(attr),
				    wlan_hdd_extscan_config_policy)) {
		hdd_err("nla_parse failed");
		return -EINVAL;
	}

	/* Parse and fetch MAC address */
	id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_AP_THRESHOLD_PARAM_BSSID;
	if (!tb[id]) {
		hdd_err("attr mac address failed");
		return -EINVAL;
	}
	nla_memcpy(ap->bssid.bytes, tb[id], QDF_MAC_ADDR_SIZE);
	hdd_debug("BSSID: " QDF_MAC_ADDR_FMT,
		  QDF_MAC_ADDR_REF(ap->bssid.bytes));

	/* Parse and fetch low RSSI */
	id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_AP_THRESHOLD_PARAM_RSSI_LOW;
	if (!tb[id]) {
		hdd_err("attr low RSSI failed");
		return -EINVAL;
	}
	ap->low = nla_get_s32(tb[id]);
	hdd_debug("RSSI low %d", ap->low);

	/* Parse and fetch high RSSI */
	id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_AP_THRESHOLD_PARAM_RSSI_HIGH;
	if (!tb[id]) {
		hdd_err("attr high RSSI failed");
		return -EINVAL;
	}
	ap->high = nla_get_s32(tb[id]);
	hdd_debug("RSSI High %d", ap->high);

	return 0;
}

/**
 * __wlan_hdd_cfg80211_extscan_set_bssid_hotlist() - set bssid hot list
 * @wiphy: Pointer to wireless phy
 * @wdev: Pointer to wireless device
 * @data: Pointer to data
 * @data_len: Data length
 *
 * Return: none
 */
static int
__wlan_hdd_cfg80211_extscan_set_bssid_hotlist(struct wiphy *wiphy,
					      struct wireless_dev *wdev,
					      const void *data,
					      int data_len)
{
	struct extscan_bssid_hotlist_set_params *params;
	struct net_device *dev = wdev->netdev;
	struct hdd_adapter *adapter = WLAN_HDD_GET_PRIV_PTR(dev);
	struct hdd_context *hdd_ctx = wiphy_priv(wiphy);
	struct nlattr *tb[EXTSCAN_PARAM_MAX + 1];
	struct nlattr *apth;
	struct hdd_ext_scan_context *context;
	QDF_STATUS status;
	uint8_t i;
	int id, rem, retval;
	unsigned long rc;

	hdd_enter_dev(dev);

	if (QDF_GLOBAL_FTM_MODE == hdd_get_conparam()) {
		hdd_err("Command not allowed in FTM mode");
		return -EPERM;
	}

	retval = wlan_hdd_validate_context(hdd_ctx);
	if (0 != retval)
		return -EINVAL;

	if (!ucfg_extscan_get_enable(hdd_ctx->psoc)) {
		hdd_err("extscan not supported");
		return -ENOTSUPP;
	}

	if (wlan_cfg80211_nla_parse(tb, EXTSCAN_PARAM_MAX,
				    data, data_len,
				    wlan_hdd_extscan_config_policy)) {
		hdd_err("Invalid ATTR");
		return -EINVAL;
	}

	params = qdf_mem_malloc(sizeof(*params));
	if (!params)
		return -ENOMEM;

	/* assume the worst until proven otherwise */
	retval = -EINVAL;

	/* Parse and fetch request Id */
	id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_SUBCMD_CONFIG_PARAM_REQUEST_ID;
	if (!tb[id]) {
		hdd_err("attr request id failed");
		goto fail;
	}

	params->request_id = nla_get_u32(tb[id]);
	hdd_debug("Req Id %d", params->request_id);

	/* Parse and fetch number of APs */
	id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_BSSID_HOTLIST_PARAMS_NUM_AP;
	if (!tb[id]) {
		hdd_err("attr number of AP failed");
		goto fail;
	}

	params->num_ap = nla_get_u32(tb[id]);
	if (params->num_ap > WMI_WLAN_EXTSCAN_MAX_HOTLIST_APS) {
		hdd_err("Number of AP: %u exceeds max: %u",
			params->num_ap, WMI_WLAN_EXTSCAN_MAX_HOTLIST_APS);
		goto fail;
	}
	params->vdev_id = adapter->deflink->vdev_id;
	hdd_debug("Number of AP %d vdev Id %d",
		  params->num_ap, params->vdev_id);

	/* Parse and fetch lost ap sample size */
	id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_BSSID_HOTLIST_PARAMS_LOST_AP_SAMPLE_SIZE;
	if (!tb[id]) {
		hdd_err("attr lost ap sample size failed");
		goto fail;
	}

	params->lost_ap_sample_size = nla_get_u32(tb[id]);
	hdd_debug("Lost ap sample size %d",
		  params->lost_ap_sample_size);

	/* Parse the AP Threshold array */
	id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_AP_THRESHOLD_PARAM;
	if (!tb[id]) {
		hdd_err("attr ap threshold failed");
		goto fail;
	}

	i = 0;
	nla_for_each_nested(apth, tb[id], rem) {
		if (i == params->num_ap) {
			hdd_warn("Ignoring excess AP");
			break;
		}

		retval = hdd_parse_ap_rssi_threshold(apth, &params->ap[i]);
		if (retval)
			goto fail;

		i++;
	}

	if (i < params->num_ap) {
		hdd_warn("Number of AP %u less than expected %u",
			 i, params->num_ap);
		params->num_ap = i;
	}

	context = &ext_scan_context;
	spin_lock(&context->context_lock);
	INIT_COMPLETION(context->response_event);
	context->request_id = params->request_id;
	spin_unlock(&context->context_lock);

	status = sme_set_bss_hotlist(hdd_ctx->mac_handle, params);
	if (!QDF_IS_STATUS_SUCCESS(status)) {
		hdd_err("sme_set_bss_hotlist failed(err=%d)", status);
		retval = qdf_status_to_os_return(status);
		goto fail;
	}

	/* request was sent -- wait for the response */
	rc = wait_for_completion_timeout
		(&context->response_event,
		 msecs_to_jiffies(WLAN_WAIT_TIME_EXTSCAN));

	if (!rc) {
		hdd_err("sme_set_bss_hotlist timed out");
		retval = -ETIMEDOUT;
	} else {
		spin_lock(&context->context_lock);
		if (context->request_id == params->request_id)
			retval = context->response_status;
		else
			retval = -EINVAL;
		spin_unlock(&context->context_lock);
	}
	hdd_exit();

fail:
	qdf_mem_free(params);
	return retval;
}

/**
 * wlan_hdd_cfg80211_extscan_set_bssid_hotlist() - set ext scan bssid hotlist
 * @wiphy: Pointer to wiphy
 * @wdev: Pointer to wdev
 * @data: Pointer to data
 * @data_len: Data length
 *
 * Return: 0 for success, non-zero for failure
 */
int wlan_hdd_cfg80211_extscan_set_bssid_hotlist(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_extscan_set_bssid_hotlist(wiphy, wdev,
							      data, data_len);

	osif_vdev_sync_op_stop(vdev_sync);

	return errno;
}


/**
 * __wlan_hdd_cfg80211_extscan_set_significant_change() - set significant change
 * @wiphy: Pointer to wireless phy
 * @wdev: Pointer to wireless device
 * @data: Pointer to data
 * @data_len: Data length
 *
 * Return: none
 */
static int
__wlan_hdd_cfg80211_extscan_set_significant_change(struct wiphy *wiphy,
						   struct wireless_dev *wdev,
						   const void *data,
						   int data_len)
{
	struct extscan_set_sig_changereq_params *params;
	struct net_device *dev = wdev->netdev;
	struct hdd_adapter *adapter = WLAN_HDD_GET_PRIV_PTR(dev);
	struct hdd_context *hdd_ctx = wiphy_priv(wiphy);
	struct nlattr *tb[EXTSCAN_PARAM_MAX + 1];
	struct nlattr *apth;
	struct hdd_ext_scan_context *context;
	QDF_STATUS status;
	uint8_t i;
	int id, rem, retval;
	unsigned long rc;

	hdd_enter_dev(dev);

	if (QDF_GLOBAL_FTM_MODE == hdd_get_conparam()) {
		hdd_err("Command not allowed in FTM mode");
		return -EPERM;
	}

	retval = wlan_hdd_validate_context(hdd_ctx);
	if (0 != retval)
		return -EINVAL;

	if (wlan_cfg80211_nla_parse(tb, EXTSCAN_PARAM_MAX,
				    data, data_len,
				    wlan_hdd_extscan_config_policy)) {
		hdd_err("Invalid ATTR");
		return -EINVAL;
	}

	params = qdf_mem_malloc(sizeof(*params));
	if (!params)
		return -ENOMEM;

	/* assume the worst until proven otherwise */
	retval = -EINVAL;

	/* Parse and fetch request Id */
	id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_SUBCMD_CONFIG_PARAM_REQUEST_ID;
	if (!tb[id]) {
		hdd_err("attr request id failed");
		goto fail;
	}

	params->request_id = nla_get_u32(tb[id]);
	hdd_debug("Req Id %d", params->request_id);

	/* Parse and fetch RSSI sample size */
	id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_SIGNIFICANT_CHANGE_PARAMS_RSSI_SAMPLE_SIZE;
	if (!tb[id]) {
		hdd_err("attr RSSI sample size failed");
		goto fail;
	}
	params->rssi_sample_size = nla_get_u32(tb[id]);
	hdd_debug("RSSI sample size %u", params->rssi_sample_size);

	/* Parse and fetch lost AP sample size */
	id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_SIGNIFICANT_CHANGE_PARAMS_LOST_AP_SAMPLE_SIZE;
	if (!tb[id]) {
		hdd_err("attr lost AP sample size failed");
		goto fail;
	}
	params->lostap_sample_size = nla_get_u32(tb[id]);
	hdd_debug("Lost AP sample size %u", params->lostap_sample_size);

	/* Parse and fetch AP min breaching */
	id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_SIGNIFICANT_CHANGE_PARAMS_MIN_BREACHING;
	if (!tb[id]) {
		hdd_err("attr AP min breaching");
		goto fail;
	}
	params->min_breaching = nla_get_u32(tb[id]);
	hdd_debug("AP min breaching %u", params->min_breaching);

	/* Parse and fetch number of APs */
	id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_SIGNIFICANT_CHANGE_PARAMS_NUM_AP;
	if (!tb[id]) {
		hdd_err("attr number of AP failed");
		goto fail;
	}
	params->num_ap = nla_get_u32(tb[id]);
	if (params->num_ap > WLAN_EXTSCAN_MAX_SIGNIFICANT_CHANGE_APS) {
		hdd_err("Number of AP %u exceeds max %u",
			params->num_ap,
			WLAN_EXTSCAN_MAX_SIGNIFICANT_CHANGE_APS);
		goto fail;
	}

	params->vdev_id = adapter->deflink->vdev_id;
	hdd_debug("Number of AP %d Vdev Id %d",
		  params->num_ap, params->vdev_id);

	id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_AP_THRESHOLD_PARAM;
	if (!tb[id]) {
		hdd_err("attr ap threshold failed");
		goto fail;
	}
	i = 0;
	nla_for_each_nested(apth, tb[id], rem) {

		if (i == params->num_ap) {
			hdd_warn("Ignoring excess AP");
			break;
		}

		retval = hdd_parse_ap_rssi_threshold(apth, &params->ap[i]);
		if (retval)
			goto fail;

		i++;
	}
	if (i < params->num_ap) {
		hdd_warn("Number of AP %u less than expected %u",
			 i, params->num_ap);
		params->num_ap = i;
	}

	context = &ext_scan_context;
	spin_lock(&context->context_lock);
	INIT_COMPLETION(context->response_event);
	context->request_id = params->request_id;
	spin_unlock(&context->context_lock);

	status = sme_set_significant_change(hdd_ctx->mac_handle, params);
	if (!QDF_IS_STATUS_SUCCESS(status)) {
		hdd_err("sme_set_significant_change failed(err=%d)", status);
		retval = qdf_status_to_os_return(status);
		goto fail;
	}

	/* request was sent -- wait for the response */
	rc = wait_for_completion_timeout(&context->response_event,
				 msecs_to_jiffies(WLAN_WAIT_TIME_EXTSCAN));

	if (!rc) {
		hdd_err("sme_set_significant_change timed out");
		retval = -ETIMEDOUT;
	} else {
		spin_lock(&context->context_lock);
		if (context->request_id == params->request_id)
			retval = context->response_status;
		else
			retval = -EINVAL;
		spin_unlock(&context->context_lock);
	}
	hdd_exit();

fail:
	qdf_mem_free(params);
	return retval;
}

/**
 * wlan_hdd_cfg80211_extscan_set_significant_change() - set significant change
 * @wiphy: Pointer to wireless phy
 * @wdev: Pointer to wireless device
 * @data: Pointer to data
 * @data_len: Data length
 *
 * Return: 0 on success, negative errno on failure
 */
int wlan_hdd_cfg80211_extscan_set_significant_change(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_extscan_set_significant_change(wiphy, wdev,
								   data,
								   data_len);

	osif_vdev_sync_op_stop(vdev_sync);

	return errno;
}

/**
 * hdd_extscan_update_dwell_time_limits() - update dwell times
 * @req_msg: Pointer to request message
 * @bkt_idx: Index of current bucket being processed
 * @active_min: minimum active dwell time
 * @active_max: maximum active dwell time
 * @passive_min: minimum passive dwell time
 * @passive_max: maximum passive dwell time
 *
 * Return: none
 */
static void
hdd_extscan_update_dwell_time_limits(struct wifi_scan_cmd_req_params *req_msg,
				     uint32_t bkt_idx, uint32_t active_min,
				     uint32_t active_max, uint32_t passive_min,
				     uint32_t passive_max)
{
	/* update per-bucket dwell times */
	if (req_msg->buckets[bkt_idx].min_dwell_time_active >
			active_min) {
		req_msg->buckets[bkt_idx].min_dwell_time_active =
			active_min;
	}
	if (req_msg->buckets[bkt_idx].max_dwell_time_active <
			active_max) {
		req_msg->buckets[bkt_idx].max_dwell_time_active =
			active_max;
	}
	if (req_msg->buckets[bkt_idx].min_dwell_time_passive >
			passive_min) {
		req_msg->buckets[bkt_idx].min_dwell_time_passive =
			passive_min;
	}
	if (req_msg->buckets[bkt_idx].max_dwell_time_passive <
			passive_max) {
		req_msg->buckets[bkt_idx].max_dwell_time_passive =
			passive_max;
	}
	/* update dwell-time across all buckets */
	if (req_msg->min_dwell_time_active >
			req_msg->buckets[bkt_idx].min_dwell_time_active) {
		req_msg->min_dwell_time_active =
			req_msg->buckets[bkt_idx].min_dwell_time_active;
	}
	if (req_msg->max_dwell_time_active <
			req_msg->buckets[bkt_idx].max_dwell_time_active) {
		req_msg->max_dwell_time_active =
			req_msg->buckets[bkt_idx].max_dwell_time_active;
	}
	if (req_msg->min_dwell_time_passive >
			req_msg->buckets[bkt_idx].min_dwell_time_passive) {
		req_msg->min_dwell_time_passive =
			req_msg->buckets[bkt_idx].min_dwell_time_passive;
	}
	if (req_msg->max_dwell_time_passive >
			req_msg->buckets[bkt_idx].max_dwell_time_passive) {
		req_msg->max_dwell_time_passive =
			req_msg->buckets[bkt_idx].max_dwell_time_passive;
	}
}

/**
 * hdd_extscan_channel_max_reached() - channel max reached
 * @req: extscan request structure
 * @total_channels: total number of channels
 *
 * Return: true if total channels reached max, false otherwise
 */
static bool
hdd_extscan_channel_max_reached(struct wifi_scan_cmd_req_params *req,
				uint8_t total_channels)
{
	if (total_channels == WMI_WLAN_EXTSCAN_MAX_CHANNELS) {
		hdd_warn("max #of channels %d reached, take only first %d bucket(s)",
			 total_channels, req->num_buckets);
		return true;
	}
	return false;
}

/**
 * hdd_extscan_start_fill_bucket_channel_spec() - fill bucket channel spec
 * @hdd_ctx: HDD global context
 * @req_msg: Pointer to request structure
 * @bucket_attr: pointer to bucket attribute
 *
 * Return: 0 on success; error number otherwise
 */
static int hdd_extscan_start_fill_bucket_channel_spec(
			struct hdd_context *hdd_ctx,
			struct wifi_scan_cmd_req_params *req_msg,
			struct nlattr *bucket_attr)
{
	mac_handle_t mac_handle;
	struct nlattr *bucket_tb[EXTSCAN_PARAM_MAX + 1];
	struct nlattr *channel_tb[EXTSCAN_PARAM_MAX + 1];
	struct nlattr *buckets;
	struct nlattr *channels;
	int id, rem1, rem2;
	QDF_STATUS status;
	uint8_t bkt_index, j, num_channels, total_channels = 0;
	uint32_t expected_buckets;
	uint32_t expected_channels;
	uint32_t chan_list[CFG_VALID_CHANNEL_LIST_LEN] = {0};
	uint32_t extscan_active_min_chn_time;
	uint32_t min_dwell_time_active_bucket;
	uint32_t max_dwell_time_active_bucket;
	uint32_t min_dwell_time_passive_bucket;
	uint32_t max_dwell_time_passive_bucket;
	struct wifi_scan_bucket_params *bucket;
	struct wifi_scan_channelspec_params *channel;
	struct nlattr *channel_attr;

	ucfg_extscan_get_active_min_time(hdd_ctx->psoc,
					&extscan_active_min_chn_time);
	ucfg_extscan_get_active_max_time(hdd_ctx->psoc,
					 &max_dwell_time_active_bucket);
	ucfg_extscan_get_passive_max_time(hdd_ctx->psoc,
					 &max_dwell_time_passive_bucket);

	min_dwell_time_active_bucket = max_dwell_time_active_bucket;
	min_dwell_time_passive_bucket = max_dwell_time_passive_bucket;

	req_msg->min_dwell_time_active =
		req_msg->max_dwell_time_active = max_dwell_time_active_bucket;

	req_msg->min_dwell_time_passive =
		req_msg->max_dwell_time_passive = max_dwell_time_passive_bucket;

	expected_buckets = req_msg->num_buckets;
	req_msg->num_buckets = 0;
	bkt_index = 0;

	mac_handle = hdd_ctx->mac_handle;
	nla_for_each_nested(buckets, bucket_attr, rem1) {

		if (bkt_index >= expected_buckets) {
			hdd_warn("ignoring excess buckets");
			break;
		}

		if (wlan_cfg80211_nla_parse(bucket_tb, EXTSCAN_PARAM_MAX,
					    nla_data(buckets), nla_len(buckets),
					    wlan_hdd_extscan_config_policy)) {
			hdd_err("nla_parse failed");
			return -EINVAL;
		}

		bucket = &req_msg->buckets[bkt_index];

		/* Parse and fetch bucket spec */
		id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_BUCKET_SPEC_INDEX;
		if (!bucket_tb[id]) {
			hdd_err("attr bucket index failed");
			return -EINVAL;
		}
		bucket->bucket = nla_get_u8(bucket_tb[id]);

		/* Parse and fetch wifi band */
		id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_BUCKET_SPEC_BAND;
		if (!bucket_tb[id]) {
			hdd_err("attr wifi band failed");
			return -EINVAL;
		}
		bucket->band = nla_get_u8(bucket_tb[id]);

		/* Parse and fetch period */
		id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_BUCKET_SPEC_PERIOD;
		if (!bucket_tb[id]) {
			hdd_err("attr period failed");
			return -EINVAL;
		}
		bucket->period = nla_get_u32(bucket_tb[id]);

		/* Parse and fetch report events */
		id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_BUCKET_SPEC_REPORT_EVENTS;
		if (!bucket_tb[id]) {
			hdd_err("attr report events failed");
			return -EINVAL;
		}
		bucket->report_events = nla_get_u8(bucket_tb[id]);

		/* Parse and fetch max period */
		id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_BUCKET_SPEC_MAX_PERIOD;
		if (!bucket_tb[id]) {
			hdd_err("attr max period failed");
			return -EINVAL;
		}
		bucket->max_period = nla_get_u32(bucket_tb[id]);

		/* Parse and fetch base */
		id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_BUCKET_SPEC_BASE;
		if (!bucket_tb[id]) {
			hdd_err("attr base failed");
			return -EINVAL;
		}
		bucket->exponent = nla_get_u32(bucket_tb[id]);

		/* Parse and fetch step count */
		id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_BUCKET_SPEC_STEP_COUNT;
		if (!bucket_tb[id]) {
			hdd_err("attr step count failed");
			return -EINVAL;
		}
		bucket->step_count = nla_get_u32(bucket_tb[id]);

		hdd_debug("Bucket spec Index: %d Wifi band: %d period: %d report events: %d max period: %u base: %u Step count: %u",
			  bucket->bucket,
			  bucket->band,
			  bucket->period,
			  bucket->report_events,
			  bucket->max_period,
			  bucket->exponent,
			  bucket->step_count);

		/* start with known good values for bucket dwell times */
		bucket->min_dwell_time_active = max_dwell_time_active_bucket;
		bucket->max_dwell_time_active = max_dwell_time_active_bucket;
		bucket->min_dwell_time_passive = max_dwell_time_passive_bucket;
		bucket->max_dwell_time_passive = max_dwell_time_passive_bucket;

		/* Framework shall pass the channel list if the input
		 * WiFi band is WMI_WIFI_BAND_UNSPECIFIED.  If the
		 * input WiFi band is specified (any value other than
		 * WMI_WIFI_BAND_UNSPECIFIED) then driver populates
		 * the channel list.
		 */
		if (bucket->band != WMI_WIFI_BAND_UNSPECIFIED) {
			if (hdd_extscan_channel_max_reached(req_msg,
							    total_channels))
				return 0;

			num_channels = 0;
			hdd_debug("WiFi band is specified, driver to fill channel list");
			status = sme_get_valid_channels_by_band(mac_handle,
								bucket->band,
								chan_list,
								&num_channels);
			if (!QDF_IS_STATUS_SUCCESS(status)) {
				hdd_err("sme_get_valid_channels_by_band failed (err=%d)",
					status);
				return -EINVAL;
			}
			hdd_debug("before trimming, num_channels: %d",
				  num_channels);

			bucket->num_channels =
				QDF_MIN(num_channels,
					(WMI_WLAN_EXTSCAN_MAX_CHANNELS -
						total_channels));
			hdd_debug("Adj Num channels/bucket: %d total_channels: %d",
				  bucket->num_channels, total_channels);
			total_channels += bucket->num_channels;

			for (j = 0; j < bucket->num_channels; j++) {
				channel = &bucket->channels[j];

				channel->channel = chan_list[j];
				channel->channel_class = 0;
				if ((wlan_reg_get_channel_state_for_pwrmode(
				     hdd_ctx->pdev, chan_list[j],
				     REG_CURRENT_PWR_MODE)) !=
				    CHANNEL_STATE_ENABLE) {
					channel->passive = 1;
					channel->dwell_time_ms =
						max_dwell_time_passive_bucket;
					/* reconfigure per-bucket dwell time */
					if (min_dwell_time_passive_bucket >
							channel->dwell_time_ms) {
						min_dwell_time_passive_bucket =
							channel->dwell_time_ms;
					}
					if (max_dwell_time_passive_bucket <
							channel->dwell_time_ms) {
						max_dwell_time_passive_bucket =
							channel->dwell_time_ms;
					}

				} else {
					channel->passive = 0;
					channel->dwell_time_ms =
						max_dwell_time_active_bucket;
					/* reconfigure per-bucket dwell times */
					if (min_dwell_time_active_bucket >
							channel->dwell_time_ms) {
						min_dwell_time_active_bucket =
							channel->dwell_time_ms;
					}
					if (max_dwell_time_active_bucket <
							channel->dwell_time_ms) {
						max_dwell_time_active_bucket =
							channel->dwell_time_ms;
					}

				}

				hdd_debug("Channel: %u Passive: %u Dwell time: %u ms Class: %u",
					  channel->channel,
					  channel->passive,
					  channel->dwell_time_ms,
					  channel->channel_class);
			}

			hdd_extscan_update_dwell_time_limits(
					req_msg, bkt_index,
					min_dwell_time_active_bucket,
					max_dwell_time_active_bucket,
					min_dwell_time_passive_bucket,
					max_dwell_time_passive_bucket);

			hdd_debug("bkt_index:%d actv_min:%d actv_max:%d pass_min:%d pass_max:%d",
					bkt_index,
					bucket->min_dwell_time_active,
					bucket->max_dwell_time_active,
					bucket->min_dwell_time_passive,
					bucket->max_dwell_time_passive);

			bkt_index++;
			req_msg->num_buckets++;
			continue;
		}

		/* Parse and fetch number of channels */
		id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_BUCKET_SPEC_NUM_CHANNEL_SPECS;
		if (!bucket_tb[id]) {
			hdd_err("attr num channels failed");
			return -EINVAL;
		}
		bucket->num_channels = nla_get_u32(bucket_tb[id]);
		hdd_debug("before trimming: num channels %d",
			  bucket->num_channels);

		bucket->num_channels =
			QDF_MIN(bucket->num_channels,
				(WMI_WLAN_EXTSCAN_MAX_CHANNELS -
							total_channels));
		hdd_debug("Num channels/bucket: %d total_channels: %d",
			  bucket->num_channels, total_channels);

		if (hdd_extscan_channel_max_reached(req_msg, total_channels))
			return 0;

		id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_CHANNEL_SPEC;
		channel_attr = bucket_tb[id];
		if (!channel_attr) {
			hdd_err("attr channel spec failed");
			return -EINVAL;
		}

		expected_channels = bucket->num_channels;
		bucket->num_channels = 0;

		nla_for_each_nested(channels, channel_attr, rem2) {
			if ((bucket->num_channels >= expected_channels) ||
			    hdd_extscan_channel_max_reached(req_msg,
							    total_channels))
				break;

			if (wlan_cfg80211_nla_parse(channel_tb,
						    EXTSCAN_PARAM_MAX,
						    nla_data(channels),
						    nla_len(channels),
						    wlan_hdd_extscan_config_policy)) {
				hdd_err("nla_parse failed");
				return -EINVAL;
			}

			channel = &bucket->channels[bucket->num_channels];
			/* Parse and fetch channel */
			if (!channel_tb[
				QCA_WLAN_VENDOR_ATTR_EXTSCAN_CHANNEL_SPEC_CHANNEL]) {
				hdd_err("attr channel failed");
				return -EINVAL;
			}
			channel->channel =
				nla_get_u32(channel_tb[
				QCA_WLAN_VENDOR_ATTR_EXTSCAN_CHANNEL_SPEC_CHANNEL]);
			hdd_debug("channel %u",
				channel->channel);

			/* Parse and fetch dwell time */
			if (!channel_tb[
				QCA_WLAN_VENDOR_ATTR_EXTSCAN_CHANNEL_SPEC_DWELL_TIME]) {
				hdd_err("attr dwelltime failed");
				return -EINVAL;
			}
			channel->dwell_time_ms =
				nla_get_u32(channel_tb[
				QCA_WLAN_VENDOR_ATTR_EXTSCAN_CHANNEL_SPEC_DWELL_TIME]);

			/* Override dwell time if required */
			if (channel->dwell_time_ms <
						extscan_active_min_chn_time ||
			    channel->dwell_time_ms >
						max_dwell_time_active_bucket) {
				hdd_debug("WiFi band is unspecified, dwellTime:%d",
						channel->dwell_time_ms);

				if ((wlan_reg_get_channel_state_for_pwrmode(
				     hdd_ctx->pdev, channel->channel,
				     REG_CURRENT_PWR_MODE)) !=
				    CHANNEL_STATE_ENABLE) {
					channel->dwell_time_ms =
						max_dwell_time_passive_bucket;
				} else {
					channel->dwell_time_ms =
						max_dwell_time_active_bucket;
				}
			}

			hdd_debug("New Dwell time %u ms",
				channel->dwell_time_ms);

			if ((wlan_reg_get_channel_state_for_pwrmode(
			     hdd_ctx->pdev, channel->channel,
			     REG_CURRENT_PWR_MODE)) !=
			    CHANNEL_STATE_ENABLE) {
				if (min_dwell_time_passive_bucket >
						channel->dwell_time_ms) {
					min_dwell_time_passive_bucket =
						channel->dwell_time_ms;
				}
				if (max_dwell_time_passive_bucket <
						channel->dwell_time_ms) {
					max_dwell_time_passive_bucket =
						channel->dwell_time_ms;
				}
			} else {
				if (min_dwell_time_active_bucket >
						channel->dwell_time_ms) {
					min_dwell_time_active_bucket =
						channel->dwell_time_ms;
				}
				if (max_dwell_time_active_bucket <
						channel->dwell_time_ms) {
					max_dwell_time_active_bucket =
						channel->dwell_time_ms;
				}
			}

			/* Parse and fetch channel spec passive */
			if (!channel_tb[
				QCA_WLAN_VENDOR_ATTR_EXTSCAN_CHANNEL_SPEC_PASSIVE]) {
				hdd_err("attr channel spec passive failed");
				return -EINVAL;
			}
			channel->passive =
				nla_get_u8(channel_tb[
				QCA_WLAN_VENDOR_ATTR_EXTSCAN_CHANNEL_SPEC_PASSIVE]);
			hdd_debug("Chnl spec passive %u",
				channel->passive);
			/* Override scan type if required */
			if ((wlan_reg_get_channel_state_for_pwrmode(
							hdd_ctx->pdev,
							channel->channel,
							REG_CURRENT_PWR_MODE))
			    != CHANNEL_STATE_ENABLE) {
				channel->passive = true;
			} else {
				channel->passive = false;
			}
			total_channels++;
			bucket->num_channels++;
		}

		if (bucket->num_channels != expected_channels)
			hdd_warn("channels: Expected %u got %u",
				 expected_channels,
				 bucket->num_channels);

		hdd_extscan_update_dwell_time_limits(
					req_msg, bkt_index,
					min_dwell_time_active_bucket,
					max_dwell_time_active_bucket,
					min_dwell_time_passive_bucket,
					max_dwell_time_passive_bucket);

		hdd_debug("bktIndex:%d actv_min:%d actv_max:%d pass_min:%d pass_max:%d",
				bkt_index,
				bucket->min_dwell_time_active,
				bucket->max_dwell_time_active,
				bucket->min_dwell_time_passive,
				bucket->max_dwell_time_passive);

		bkt_index++;
		req_msg->num_buckets++;
	}

	hdd_debug("Global: actv_min:%d actv_max:%d pass_min:%d pass_max:%d",
				req_msg->min_dwell_time_active,
				req_msg->max_dwell_time_active,
				req_msg->min_dwell_time_passive,
				req_msg->max_dwell_time_passive);
	return 0;
}

/*
 * hdd_extscan_map_usr_drv_config_flags() - map userspace to driver config flags
 * @config_flags - [input] configuration flags.
 *
 * This function maps user space received configuration flags to
 * driver representation.
 *
 * Return: configuration flags
 */
static uint32_t hdd_extscan_map_usr_drv_config_flags(uint32_t config_flags)
{
	uint32_t configuration_flags = 0;

	if (config_flags & EXTSCAN_LP_EXTENDED_BATCHING)
		configuration_flags |= EXTSCAN_LP_EXTENDED_BATCHING;

	return configuration_flags;
}

/**
 * __wlan_hdd_cfg80211_extscan_start() - ext scan start
 * @wiphy: Pointer to wireless phy
 * @wdev: Pointer to wireless device
 * @data: Pointer to data
 * @data_len: Length of @data
 *
 * Return: 0 on success; error number otherwise
 */
static int
__wlan_hdd_cfg80211_extscan_start(struct wiphy *wiphy,
				  struct wireless_dev *wdev,
				  const void *data,
				  int data_len)
{
	struct wifi_scan_cmd_req_params *params;
	struct net_device *dev = wdev->netdev;
	struct hdd_adapter *adapter = WLAN_HDD_GET_PRIV_PTR(dev);
	struct hdd_context *hdd_ctx = wiphy_priv(wiphy);
	struct nlattr *tb[EXTSCAN_PARAM_MAX + 1];
	struct hdd_ext_scan_context *context;
	uint32_t num_buckets;
	QDF_STATUS status;
	int retval, id;
	unsigned long rc;

	hdd_enter_dev(dev);

	if (QDF_GLOBAL_FTM_MODE == hdd_get_conparam()) {
		hdd_err("Command not allowed in FTM mode");
		return -EPERM;
	}

	if (QDF_NDI_MODE == adapter->device_mode) {
		hdd_err("Command not allowed for NDI interface");
		return -EPERM;
	}

	retval = wlan_hdd_validate_context(hdd_ctx);
	if (0 != retval)
		return -EINVAL;

	if (!ucfg_extscan_get_enable(hdd_ctx->psoc)) {
		hdd_err("extscan not supported");
		return -ENOTSUPP;
	}
	if (wlan_cfg80211_nla_parse(tb, EXTSCAN_PARAM_MAX, data, data_len,
				    wlan_hdd_extscan_config_policy)) {
		hdd_err("Invalid ATTR");
		return -EINVAL;
	}

	params = qdf_mem_malloc(sizeof(*params));
	if (!params)
		return -ENOMEM;

	/* assume the worst until proven otherwise */
	retval = -EINVAL;

	/* Parse and fetch request Id */
	id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_SUBCMD_CONFIG_PARAM_REQUEST_ID;
	if (!tb[id]) {
		hdd_err("attr request id failed");
		goto fail;
	}

	params->request_id = nla_get_u32(tb[id]);
	params->vdev_id = adapter->deflink->vdev_id;

	/* Parse and fetch base period */
	id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_SCAN_CMD_PARAMS_BASE_PERIOD;
	if (!tb[id]) {
		hdd_err("attr base period failed");
		goto fail;
	}
	params->base_period = nla_get_u32(tb[id]);

	/* Parse and fetch max AP per scan */
	id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_SCAN_CMD_PARAMS_MAX_AP_PER_SCAN;
	if (!tb[id]) {
		hdd_err("attr max_ap_per_scan failed");
		goto fail;
	}
	params->max_ap_per_scan = nla_get_u32(tb[id]);

	/* Parse and fetch report threshold percent */
	id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_SCAN_CMD_PARAMS_REPORT_THRESHOLD_PERCENT;
	if (!tb[id]) {
		hdd_err("attr report_threshold percent failed");
		goto fail;
	}
	params->report_threshold_percent = nla_get_u8(tb[id]);

	/* Parse and fetch report threshold num scans */
	id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_SCAN_CMD_PARAMS_REPORT_THRESHOLD_NUM_SCANS;
	if (!tb[id]) {
		hdd_err("attr report_threshold num scans failed");
		goto fail;
	}
	params->report_threshold_num_scans = nla_get_u8(tb[id]);
	hdd_debug("Req Id: %d Vdev Id: %d Base Period: %d Max AP per Scan: %d Report Threshold percent: %d Report Threshold num scans: %d",
		  params->request_id, params->vdev_id,
		  params->base_period, params->max_ap_per_scan,
		  params->report_threshold_percent,
		  params->report_threshold_num_scans);

	/* Parse and fetch number of buckets */
	id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_SCAN_CMD_PARAMS_NUM_BUCKETS;
	if (!tb[id]) {
		hdd_err("attr number of buckets failed");
		goto fail;
	}
	num_buckets = nla_get_u8(tb[id]);

	if (num_buckets > WMI_WLAN_EXTSCAN_MAX_BUCKETS) {
		hdd_warn("Exceeded MAX number of buckets: %d",
			 WMI_WLAN_EXTSCAN_MAX_BUCKETS);
		num_buckets = WMI_WLAN_EXTSCAN_MAX_BUCKETS;
	}
	hdd_debug("Input: Number of Buckets %d", num_buckets);
	params->num_buckets = num_buckets;

	/* This is optional attribute, if not present set it to 0 */
	id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_CONFIGURATION_FLAGS;
	if (!tb[id])
		params->configuration_flags = 0;
	else
		params->configuration_flags =
			hdd_extscan_map_usr_drv_config_flags(
				nla_get_u32(tb[id]));

	params->extscan_adaptive_dwell_mode =
		ucfg_scan_get_extscan_adaptive_dwell_mode(hdd_ctx->psoc);

	hdd_debug("Configuration flags: %u",
		  params->configuration_flags);

	/* Parse and fetch number the array of buckets */
	id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_BUCKET_SPEC;
	if (!tb[id]) {
		hdd_err("attr bucket spec failed");
		goto fail;
	}
	retval = hdd_extscan_start_fill_bucket_channel_spec(hdd_ctx, params,
							    tb[id]);
	if (retval)
		goto fail;

	context = &ext_scan_context;
	spin_lock(&context->context_lock);
	INIT_COMPLETION(context->response_event);
	context->request_id = params->request_id;
	context->buckets_scanned = 0;
	spin_unlock(&context->context_lock);

	status = sme_ext_scan_start(hdd_ctx->mac_handle, params);
	if (!QDF_IS_STATUS_SUCCESS(status)) {
		hdd_err("sme_ext_scan_start failed(err=%d)", status);
		retval = qdf_status_to_os_return(status);
		goto fail;
	}

	hdd_ctx->ext_scan_start_since_boot = qdf_get_monotonic_boottime();
	hdd_debug("Timestamp since boot: %llu",
		  hdd_ctx->ext_scan_start_since_boot);

	/* request was sent -- wait for the response */
	rc = wait_for_completion_timeout(&context->response_event,
				msecs_to_jiffies(WLAN_WAIT_TIME_EXTSCAN));

	if (!rc) {
		hdd_err("sme_ext_scan_start timed out");
		retval = -ETIMEDOUT;
	} else {
		spin_lock(&context->context_lock);
		if (context->request_id == params->request_id)
			retval = context->response_status;
		else
			retval = -EINVAL;
		spin_unlock(&context->context_lock);
	}
	hdd_exit();

fail:
	qdf_mem_free(params);
	return retval;
}

/**
 * wlan_hdd_cfg80211_extscan_start() - start extscan
 * @wiphy: Pointer to wireless phy.
 * @wdev: Pointer to wireless device.
 * @data: Pointer to input data.
 * @data_len: Length of @data.
 *
 * Return: 0 on success, negative errno on failure
 */
int wlan_hdd_cfg80211_extscan_start(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_extscan_start(wiphy, wdev, data, data_len);

	osif_vdev_sync_op_stop(vdev_sync);

	return errno;
}


/**
 * __wlan_hdd_cfg80211_extscan_stop() - ext scan stop
 * @wiphy: Pointer to wireless phy
 * @wdev: Pointer to wireless device
 * @data: Pointer to data
 * @data_len: Data length
 *
 * Return: none
 */
static int
__wlan_hdd_cfg80211_extscan_stop(struct wiphy *wiphy,
				 struct wireless_dev *wdev,
				 const void *data, int data_len)
{
	struct extscan_stop_req_params params;
	struct net_device *dev = wdev->netdev;
	struct hdd_adapter *adapter = WLAN_HDD_GET_PRIV_PTR(dev);
	struct hdd_context *hdd_ctx = wiphy_priv(wiphy);
	struct nlattr *tb[EXTSCAN_PARAM_MAX + 1];
	struct hdd_ext_scan_context *context;
	QDF_STATUS status;
	int id, retval;
	unsigned long rc;

	hdd_enter_dev(dev);

	if (QDF_GLOBAL_FTM_MODE == hdd_get_conparam()) {
		hdd_err("Command not allowed in FTM mode");
		return -EPERM;
	}

	retval = wlan_hdd_validate_context(hdd_ctx);
	if (0 != retval)
		return -EINVAL;

	if (!ucfg_extscan_get_enable(hdd_ctx->psoc)) {
		hdd_err("extscan not supported");
		return -ENOTSUPP;
	}

	if (wlan_cfg80211_nla_parse(tb, EXTSCAN_PARAM_MAX, data, data_len,
				    wlan_hdd_extscan_config_policy)) {
		hdd_err("Invalid ATTR");
		return -EINVAL;
	}

	/* Parse and fetch request Id */
	id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_SUBCMD_CONFIG_PARAM_REQUEST_ID;
	if (!tb[id]) {
		hdd_err("attr request id failed");
		return -EINVAL;
	}
	params.request_id = nla_get_u32(tb[id]);
	params.vdev_id = adapter->deflink->vdev_id;
	hdd_debug("Req Id %d Vdev Id %d",
		  params.request_id, params.vdev_id);

	context = &ext_scan_context;
	spin_lock(&context->context_lock);
	INIT_COMPLETION(context->response_event);
	context->request_id = params.request_id;
	spin_unlock(&context->context_lock);

	status = sme_ext_scan_stop(hdd_ctx->mac_handle, &params);
	if (!QDF_IS_STATUS_SUCCESS(status)) {
		hdd_err("sme_ext_scan_stop failed(err=%d)", status);
		return qdf_status_to_os_return(status);
	}

	/* request was sent -- wait for the response */
	rc = wait_for_completion_timeout(&context->response_event,
				msecs_to_jiffies(WLAN_WAIT_TIME_EXTSCAN));

	if (!rc) {
		hdd_err("sme_ext_scan_stop timed out");
		retval = -ETIMEDOUT;
	} else {
		spin_lock(&context->context_lock);
		if (context->request_id == params.request_id)
			retval = context->response_status;
		else
			retval = -EINVAL;
		spin_unlock(&context->context_lock);
	}
	hdd_exit();
	return retval;
}

/**
 * wlan_hdd_cfg80211_extscan_stop() - stop extscan
 * @wiphy: Pointer to wireless phy.
 * @wdev: Pointer to wireless device.
 * @data: Pointer to input data.
 * @data_len: Length of @data.
 *
 * Return: 0 on success, negative errno on failure
 */
int wlan_hdd_cfg80211_extscan_stop(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_extscan_stop(wiphy, wdev, data, data_len);

	osif_vdev_sync_op_stop(vdev_sync);

	return errno;
}

/**
 * __wlan_hdd_cfg80211_extscan_reset_bssid_hotlist() - reset bssid hotlist
 * @wiphy: Pointer to wireless phy
 * @wdev: Pointer to wireless device
 * @data: Pointer to data
 * @data_len: Data length
 *
 * Return: none
 */
static int
__wlan_hdd_cfg80211_extscan_reset_bssid_hotlist(struct wiphy *wiphy,
						struct wireless_dev *wdev,
						const void *data,
						int data_len)
{
	struct extscan_bssid_hotlist_reset_params params;
	struct net_device *dev = wdev->netdev;
	struct hdd_adapter *adapter = WLAN_HDD_GET_PRIV_PTR(dev);
	struct hdd_context *hdd_ctx = wiphy_priv(wiphy);
	struct nlattr *tb[EXTSCAN_PARAM_MAX + 1];
	struct hdd_ext_scan_context *context;
	QDF_STATUS status;
	int id, retval;
	unsigned long rc;

	hdd_enter_dev(dev);

	if (QDF_GLOBAL_FTM_MODE == hdd_get_conparam()) {
		hdd_err("Command not allowed in FTM mode");
		return -EPERM;
	}

	retval = wlan_hdd_validate_context(hdd_ctx);
	if (0 != retval)
		return -EINVAL;

	if (!ucfg_extscan_get_enable(hdd_ctx->psoc)) {
		hdd_err("extscan not supported");
		return -ENOTSUPP;
	}

	if (wlan_cfg80211_nla_parse(tb, EXTSCAN_PARAM_MAX,
				    data, data_len,
				    wlan_hdd_extscan_config_policy)) {
		hdd_err("Invalid ATTR");
		return -EINVAL;
	}

	/* Parse and fetch request Id */
	id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_SUBCMD_CONFIG_PARAM_REQUEST_ID;
	if (!tb[id]) {
		hdd_err("attr request id failed");
		return -EINVAL;
	}

	params.request_id = nla_get_u32(tb[id]);
	params.vdev_id = adapter->deflink->vdev_id;
	hdd_debug("Req Id %d vdev Id %d", params.request_id, params.vdev_id);

	context = &ext_scan_context;
	spin_lock(&context->context_lock);
	INIT_COMPLETION(context->response_event);
	context->request_id = params.request_id;
	spin_unlock(&context->context_lock);

	status = sme_reset_bss_hotlist(hdd_ctx->mac_handle, &params);
	if (!QDF_IS_STATUS_SUCCESS(status)) {
		hdd_err("sme_reset_bss_hotlist failed(err=%d)", status);
		return qdf_status_to_os_return(status);
	}

	/* request was sent -- wait for the response */
	rc = wait_for_completion_timeout
		(&context->response_event,
		 msecs_to_jiffies(WLAN_WAIT_TIME_EXTSCAN));
	if (!rc) {
		hdd_err("sme_reset_bss_hotlist timed out");
		retval = -ETIMEDOUT;
	} else {
		spin_lock(&context->context_lock);
		if (context->request_id == params.request_id)
			retval = context->response_status;
		else
			retval = -EINVAL;
		spin_unlock(&context->context_lock);
	}
	hdd_exit();
	return retval;
}

/**
 * wlan_hdd_cfg80211_extscan_reset_bssid_hotlist() - reset bssid hot list
 * @wiphy: Pointer to wireless phy
 * @wdev: Pointer to wireless device
 * @data: Pointer to data
 * @data_len: Data length
 *
 * Return: 0 on success, negative errno on failure
 */
int wlan_hdd_cfg80211_extscan_reset_bssid_hotlist(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_extscan_reset_bssid_hotlist(wiphy, wdev,
								data, data_len);

	osif_vdev_sync_op_stop(vdev_sync);

	return errno;
}

/**
 * __wlan_hdd_cfg80211_extscan_reset_significant_change() -
 *		reset significant change
 * @wiphy: Pointer to wireless phy
 * @wdev: Pointer to wireless device
 * @data: Pointer to data
 * @data_len: Data length
 *
 * Return: none
 */
static int
__wlan_hdd_cfg80211_extscan_reset_significant_change(struct wiphy *wiphy,
						     struct wireless_dev *wdev,
						     const void *data,
						     int data_len)
{
	struct extscan_capabilities_reset_params params;
	struct net_device *dev = wdev->netdev;
	struct hdd_adapter *adapter = WLAN_HDD_GET_PRIV_PTR(dev);
	struct hdd_context *hdd_ctx = wiphy_priv(wiphy);
	struct nlattr *tb[EXTSCAN_PARAM_MAX + 1];
	struct hdd_ext_scan_context *context;
	QDF_STATUS status;
	int id, retval;
	unsigned long rc;

	hdd_enter_dev(dev);

	if (QDF_GLOBAL_FTM_MODE == hdd_get_conparam()) {
		hdd_err("Command not allowed in FTM mode");
		return -EPERM;
	}

	retval = wlan_hdd_validate_context(hdd_ctx);
	if (0 != retval)
		return -EINVAL;

	if (!ucfg_extscan_get_enable(hdd_ctx->psoc)) {
		hdd_err("extscan not supported");
		return -ENOTSUPP;
	}

	if (wlan_cfg80211_nla_parse(tb, EXTSCAN_PARAM_MAX, data, data_len,
				    wlan_hdd_extscan_config_policy)) {
		hdd_err("Invalid ATTR");
		return -EINVAL;
	}

	/* Parse and fetch request Id */
	id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_SUBCMD_CONFIG_PARAM_REQUEST_ID;
	if (!tb[id]) {
		hdd_err("attr request id failed");
		return -EINVAL;
	}

	params.request_id = nla_get_u32(tb[id]);
	params.vdev_id = adapter->deflink->vdev_id;
	hdd_debug("Req Id %d Vdev Id %d", params.request_id, params.vdev_id);

	context = &ext_scan_context;
	spin_lock(&context->context_lock);
	INIT_COMPLETION(context->response_event);
	context->request_id = params.request_id;
	spin_unlock(&context->context_lock);

	status = sme_reset_significant_change(hdd_ctx->mac_handle, &params);
	if (!QDF_IS_STATUS_SUCCESS(status)) {
		hdd_err("sme_reset_significant_change failed(err=%d)",
			status);
		return -EINVAL;
	}

	/* request was sent -- wait for the response */
	rc = wait_for_completion_timeout(&context->response_event,
				msecs_to_jiffies(WLAN_WAIT_TIME_EXTSCAN));

	if (!rc) {
		hdd_err("sme_ResetSignificantChange timed out");
		retval = -ETIMEDOUT;
	} else {
		spin_lock(&context->context_lock);
		if (context->request_id == params.request_id)
			retval = context->response_status;
		else
			retval = -EINVAL;
		spin_unlock(&context->context_lock);
	}
	hdd_exit();
	return retval;
}

/**
 * wlan_hdd_cfg80211_extscan_reset_significant_change() - reset significant
 *							change
 * @wiphy: Pointer to wireless phy
 * @wdev: Pointer to wireless device
 * @data: Pointer to data
 * @data_len: Data length
 *
 * Return: 0 on success, negative errno on failure
 */
int wlan_hdd_cfg80211_extscan_reset_significant_change(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_extscan_reset_significant_change(wiphy,
								     wdev,
								     data,
								     data_len);

	osif_vdev_sync_op_stop(vdev_sync);

	return errno;
}


/**
 * hdd_extscan_epno_fill_network() - epno fill single network
 * @network: aggregate network attribute
 * @nw: epno network record to be filled
 *
 * This function takes a single network block NL vendor attribute from
 * @network and decodes it into the internal record @nw.
 *
 * Return: 0 on success, error number otherwise
 */
static int
hdd_extscan_epno_fill_network(struct nlattr *network,
			      struct wifi_epno_network_params *nw)
{
	struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_PNO_MAX + 1];
	int id, ssid_len;
	uint8_t *ssid;

	if (!network) {
		hdd_err("attr network attr failed");
		return -EINVAL;
	}

	if (wlan_cfg80211_nla_parse(tb, QCA_WLAN_VENDOR_ATTR_PNO_MAX,
				    nla_data(network),
				    nla_len(network),
				    wlan_hdd_pno_config_policy)) {
		hdd_err("nla_parse failed");
		return -EINVAL;
	}

	/* Parse and fetch ssid */
	id = QCA_WLAN_VENDOR_ATTR_PNO_SET_LIST_PARAM_EPNO_NETWORK_SSID;
	if (!tb[id]) {
		hdd_err("attr network ssid failed");
		return -EINVAL;
	}
	ssid_len = nla_len(tb[id]);

	/* nla_parse will detect overflow but not underflow */
	if (0 == ssid_len) {
		hdd_err("zero ssid length");
		return -EINVAL;
	}

	/* Decrement by 1, don't count null character */
	ssid_len--;

	nw->ssid.length = ssid_len;
	hdd_debug("network ssid length %d", ssid_len);
	ssid = nla_data(tb[id]);
	qdf_mem_copy(nw->ssid.ssid, ssid, ssid_len);
	hdd_debug("Ssid (" QDF_SSID_FMT ")",
		  QDF_SSID_REF(nw->ssid.length, nw->ssid.ssid));

	/* Parse and fetch epno flags */
	id = QCA_WLAN_VENDOR_ATTR_PNO_SET_LIST_PARAM_EPNO_NETWORK_FLAGS;
	if (!tb[id]) {
		hdd_err("attr epno flags failed");
		return -EINVAL;
	}
	nw->flags = nla_get_u8(tb[id]);
	hdd_debug("flags %u", nw->flags);

	/* Parse and fetch auth bit */
	id = QCA_WLAN_VENDOR_ATTR_PNO_SET_LIST_PARAM_EPNO_NETWORK_AUTH_BIT;
	if (!tb[id]) {
		hdd_err("attr auth bit failed");
		return -EINVAL;
	}
	nw->auth_bit_field = nla_get_u8(tb[id]);
	hdd_debug("auth bit %u", nw->auth_bit_field);

	return 0;
}

/**
 * hdd_extscan_epno_fill_network_list() - epno fill network list
 * @req_msg: request message
 * @networks: aggregate network list attribute
 *
 * This function reads the network block NL vendor attributes from
 * @networks and fills in the epno request message @req_msg.
 *
 * Return: 0 on success, error number otherwise
 */
static int
hdd_extscan_epno_fill_network_list(struct wifi_enhanced_pno_params *req_msg,
				   struct nlattr *networks)
{
	struct nlattr *network;
	int rem;
	uint32_t index;
	uint32_t expected_networks;
	struct wifi_epno_network_params *nw;

	if (!networks) {
		hdd_err("attr networks list failed");
		return -EINVAL;
	}

	expected_networks = req_msg->num_networks;
	index = 0;

	nla_for_each_nested(network, networks, rem) {
		if (index == expected_networks) {
			hdd_warn("ignoring excess networks");
			break;
		}

		nw = &req_msg->networks[index++];
		if (hdd_extscan_epno_fill_network(network, nw))
			return -EINVAL;
	}
	req_msg->num_networks = index;
	return 0;
}

/**
 * __wlan_hdd_cfg80211_set_epno_list() - epno set network list
 * @wiphy: wiphy
 * @wdev: pointer to wireless dev
 * @data: data pointer
 * @data_len: data length
 *
 * This function reads the NL vendor attributes from @data and
 * fills in the epno request message.
 *
 * Return: 0 on success, error number otherwise
 */
static int __wlan_hdd_cfg80211_set_epno_list(struct wiphy *wiphy,
					     struct wireless_dev *wdev,
					     const void *data,
					     int data_len)
{
	struct wifi_enhanced_pno_params *req_msg;
	struct net_device *dev = wdev->netdev;
	struct hdd_adapter *adapter = WLAN_HDD_GET_PRIV_PTR(dev);
	struct hdd_context *hdd_ctx = wiphy_priv(wiphy);
	struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_PNO_MAX + 1];
	struct nlattr *networks;
	QDF_STATUS status;
	uint32_t num_networks, len;
	int id, ret_val;

	hdd_enter_dev(dev);

	ret_val = wlan_hdd_validate_context(hdd_ctx);
	if (ret_val)
		return ret_val;

	if (!ucfg_extscan_get_enable(hdd_ctx->psoc)) {
		hdd_err("extscan not supported");
		return -ENOTSUPP;
	}

	if (QDF_GLOBAL_FTM_MODE == hdd_get_conparam()) {
		hdd_err("Command not allowed in FTM mode");
		return -EPERM;
	}

	if (wlan_cfg80211_nla_parse(tb, QCA_WLAN_VENDOR_ATTR_PNO_MAX, data,
				    data_len, wlan_hdd_pno_config_policy)) {
		hdd_err("Invalid ATTR");
		return -EINVAL;
	}

	/* Parse and fetch number of networks */
	id = QCA_WLAN_VENDOR_ATTR_PNO_SET_LIST_PARAM_NUM_NETWORKS;
	if (!tb[id]) {
		hdd_err("attr num networks failed");
		return -EINVAL;
	}

	/*
	 * num_networks is also used as EPNO SET/RESET request.
	 * if num_networks is zero then it is treated as RESET.
	 */
	num_networks = nla_get_u32(tb[id]);

	if (num_networks > MAX_EPNO_NETWORKS) {
		hdd_debug("num of nw: %d exceeded max: %d, resetting to: %d",
			num_networks, MAX_EPNO_NETWORKS, MAX_EPNO_NETWORKS);
		num_networks = MAX_EPNO_NETWORKS;
	}

	hdd_debug("num networks %u", num_networks);
	len = sizeof(*req_msg) +
			(num_networks * sizeof(req_msg->networks[0]));

	req_msg = qdf_mem_malloc(len);
	if (!req_msg)
		return -ENOMEM;

	req_msg->num_networks = num_networks;

	/* Parse and fetch request Id */
	id = QCA_WLAN_VENDOR_ATTR_PNO_CONFIG_REQUEST_ID;
	if (!tb[id]) {
		hdd_err("attr request id failed");
		goto fail;
	}
	req_msg->request_id = nla_get_u32(tb[id]);
	hdd_debug("Req Id %u", req_msg->request_id);

	req_msg->vdev_id = adapter->deflink->vdev_id;
	hdd_debug("Vdev Id %d", req_msg->vdev_id);

	if (num_networks) {
		/* Parse and fetch min_5ghz_rssi */
		id = QCA_WLAN_VENDOR_ATTR_EPNO_MIN5GHZ_RSSI;
		if (!tb[id]) {
			hdd_err("min_5ghz_rssi id failed");
			goto fail;
		}
		req_msg->min_5ghz_rssi = nla_get_u32(tb[id]);

		/* Parse and fetch min_24ghz_rssi */
		id = QCA_WLAN_VENDOR_ATTR_EPNO_MIN24GHZ_RSSI;
		if (!tb[id]) {
			hdd_err("min_24ghz_rssi id failed");
			goto fail;
		}
		req_msg->min_24ghz_rssi = nla_get_u32(tb[id]);

		/* Parse and fetch initial_score_max */
		id = QCA_WLAN_VENDOR_ATTR_EPNO_INITIAL_SCORE_MAX;
		if (!tb[id]) {
			hdd_err("initial_score_max id failed");
			goto fail;
		}
		req_msg->initial_score_max = nla_get_u32(tb[id]);

		/* Parse and fetch current_connection_bonus */
		id = QCA_WLAN_VENDOR_ATTR_EPNO_CURRENT_CONNECTION_BONUS;
		if (!tb[id]) {
			hdd_err("current_connection_bonus id failed");
			goto fail;
		}
		req_msg->current_connection_bonus = nla_get_u32(tb[id]);

		/* Parse and fetch same_network_bonus */
		id = QCA_WLAN_VENDOR_ATTR_EPNO_SAME_NETWORK_BONUS;
		if (!tb[id]) {
			hdd_err("same_network_bonus id failed");
			goto fail;
		}
		req_msg->same_network_bonus = nla_get_u32(tb[id]);

		/* Parse and fetch secure_bonus */
		id = QCA_WLAN_VENDOR_ATTR_EPNO_SECURE_BONUS;
		if (!tb[id]) {
			hdd_err("secure_bonus id failed");
			goto fail;
		}
		req_msg->secure_bonus = nla_get_u32(tb[id]);

		/* Parse and fetch band_5ghz_bonus */
		id = QCA_WLAN_VENDOR_ATTR_EPNO_BAND5GHZ_BONUS;
		if (!tb[id]) {
			hdd_err("band_5ghz_bonus id failed");
			goto fail;
		}
		req_msg->band_5ghz_bonus = nla_get_u32(tb[id]);

		hdd_debug("min_5ghz_rssi: %d min_24ghz_rssi: %d",
			req_msg->min_5ghz_rssi,
			req_msg->min_24ghz_rssi);
		hdd_debug("initial_score_max: %d current_connection_bonus:%d",
			req_msg->initial_score_max,
			req_msg->current_connection_bonus);
		hdd_debug("Bonuses same_network: %d secure: %d band_5ghz: %d",
			req_msg->same_network_bonus,
			req_msg->secure_bonus,
			req_msg->band_5ghz_bonus);

		id = QCA_WLAN_VENDOR_ATTR_PNO_SET_LIST_PARAM_EPNO_NETWORKS_LIST;
		networks = tb[id];
		if (hdd_extscan_epno_fill_network_list(req_msg, networks))
			goto fail;

	}

	status = sme_set_epno_list(hdd_ctx->mac_handle, req_msg);
	if (!QDF_IS_STATUS_SUCCESS(status)) {
		hdd_err("sme_set_epno_list failed(err=%d)", status);
		goto fail;
	}

	hdd_exit();
	qdf_mem_free(req_msg);
	return 0;

fail:
	qdf_mem_free(req_msg);
	return -EINVAL;
}

/**
 * wlan_hdd_cfg80211_set_epno_list() - epno set network list
 * @wiphy: wiphy
 * @wdev: pointer to wireless dev
 * @data: data pointer
 * @data_len: data length
 *
 * This function reads the NL vendor attributes from %tb and
 * fill in the epno request message.
 *
 * Return: 0 on success, error number otherwise
 */
int wlan_hdd_cfg80211_set_epno_list(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_set_epno_list(wiphy, wdev, data, data_len);

	osif_vdev_sync_op_stop(vdev_sync);

	return errno;
}

/**
 * hdd_extscan_passpoint_fill_network() - passpoint fill single network
 * @network: aggregate network attribute
 * @nw: passpoint network record to be filled
 *
 * This function takes a single network block NL vendor attribute from
 * @network and decodes it into the internal record @nw.
 *
 * Return: 0 on success, error number otherwise
 */
static int
hdd_extscan_passpoint_fill_network(struct nlattr *network,
				   struct wifi_passpoint_network_param *nw)
{
	struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_PNO_MAX + 1];
	int id;
	size_t len;

	if (!network) {
		hdd_err("attr network attr failed");
		return -EINVAL;
	}

	if (wlan_cfg80211_nla_parse(tb, QCA_WLAN_VENDOR_ATTR_PNO_MAX,
				    nla_data(network),
				    nla_len(network),
				    wlan_hdd_pno_config_policy)) {
		hdd_err("nla_parse failed");
		return -EINVAL;
	}

	/* Parse and fetch identifier */
	id = QCA_WLAN_VENDOR_ATTR_PNO_PASSPOINT_NETWORK_PARAM_ID;
	if (!tb[id]) {
		hdd_err("attr passpoint id failed");
		return -EINVAL;
	}
	nw->id = nla_get_u32(tb[id]);
	hdd_debug("Id %u", nw->id);

	/* Parse and fetch realm */
	id = QCA_WLAN_VENDOR_ATTR_PNO_PASSPOINT_NETWORK_PARAM_REALM;
	if (!tb[id]) {
		hdd_err("attr realm failed");
		return -EINVAL;
	}
	len = wlan_cfg80211_nla_strscpy(nw->realm, tb[id],
					WMI_PASSPOINT_REALM_LEN);
	/* Don't send partial realm to firmware */
	if (len >= WMI_PASSPOINT_REALM_LEN) {
		hdd_err("user passed invalid realm, len:%zu", len);
		return -EINVAL;
	}

	hdd_debug("realm: %s", nw->realm);

	/* Parse and fetch roaming consortium ids */
	id = QCA_WLAN_VENDOR_ATTR_PNO_PASSPOINT_NETWORK_PARAM_ROAM_CNSRTM_ID;
	if (!tb[id]) {
		hdd_err("attr roaming consortium ids failed");
		return -EINVAL;
	}
	nla_memcpy(&nw->roaming_consortium_ids, tb[id],
		   sizeof(nw->roaming_consortium_ids));
	hdd_debug("roaming consortium ids");

	/* Parse and fetch plmn */
	id = QCA_WLAN_VENDOR_ATTR_PNO_PASSPOINT_NETWORK_PARAM_ROAM_PLMN;
	if (!tb[id]) {
		hdd_err("attr plmn failed");
		return -EINVAL;
	}
	nla_memcpy(&nw->plmn, tb[id],
		   WMI_PASSPOINT_PLMN_LEN);
	hdd_debug("plmn %02x:%02x:%02x)",
		  nw->plmn[0],
		  nw->plmn[1],
		  nw->plmn[2]);

	return 0;
}

/**
 * hdd_extscan_passpoint_fill_networks() - passpoint fill network list
 * @req_msg: request message
 * @networks: aggregate network list attribute
 *
 * This function reads the network block NL vendor attributes from
 * @networks and fills in the passpoint request message.
 *
 * Return: 0 on success, error number otherwise
 */
static int
hdd_extscan_passpoint_fill_networks(struct wifi_passpoint_req_param *req_msg,
				    struct nlattr *networks)
{
	struct nlattr *network;
	int rem;
	uint32_t index;
	uint32_t expected_networks;
	struct wifi_passpoint_network_param *nw;

	if (!networks) {
		hdd_err("attr networks list failed");
		return -EINVAL;
	}

	expected_networks = req_msg->num_networks;
	index = 0;

	nla_for_each_nested(network, networks, rem) {
		if (index == expected_networks) {
			hdd_warn("ignoring excess networks");
			break;
		}

		nw = &req_msg->networks[index++];
		if (hdd_extscan_passpoint_fill_network(network, nw))
			return -EINVAL;
	}
	req_msg->num_networks = index;
	return 0;
}

/**
 * __wlan_hdd_cfg80211_set_passpoint_list() - set passpoint network list
 * @wiphy: wiphy
 * @wdev: pointer to wireless dev
 * @data: data pointer
 * @data_len: data length
 *
 * This function reads the NL vendor attributes from %tb and
 * fill in the passpoint request message.
 *
 * Return: 0 on success, error number otherwise
 */
static int __wlan_hdd_cfg80211_set_passpoint_list(struct wiphy *wiphy,
						  struct wireless_dev *wdev,
						  const void *data,
						  int data_len)
{
	struct wifi_passpoint_req_param *req_msg;
	struct net_device *dev = wdev->netdev;
	struct hdd_adapter *adapter = WLAN_HDD_GET_PRIV_PTR(dev);
	struct hdd_context *hdd_ctx = wiphy_priv(wiphy);
	struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_PNO_MAX + 1];
	struct nlattr *networks;
	uint32_t num_networks;
	QDF_STATUS status;
	int id, ret;

	hdd_enter_dev(dev);

	ret = wlan_hdd_validate_context(hdd_ctx);
	if (ret)
		return ret;

	if (QDF_GLOBAL_FTM_MODE == hdd_get_conparam()) {
		hdd_err("Command not allowed in FTM mode");
		return -EPERM;
	}

	if (wlan_cfg80211_nla_parse(tb, QCA_WLAN_VENDOR_ATTR_PNO_MAX, data,
				    data_len, wlan_hdd_pno_config_policy)) {
		hdd_err("Invalid ATTR");
		return -EINVAL;
	}

	/* Parse and fetch number of networks */
	id = QCA_WLAN_VENDOR_ATTR_PNO_PASSPOINT_LIST_PARAM_NUM;
	if (!tb[id]) {
		hdd_err("attr num networks failed");
		return -EINVAL;
	}
	num_networks = nla_get_u32(tb[id]);
	if (num_networks > SIR_PASSPOINT_LIST_MAX_NETWORKS) {
		hdd_err("num networks %u exceeds max %u",
			num_networks, SIR_PASSPOINT_LIST_MAX_NETWORKS);
		return -EINVAL;
	}

	hdd_debug("num networks %u", num_networks);

	req_msg = qdf_mem_malloc(sizeof(*req_msg) +
			(num_networks * sizeof(req_msg->networks[0])));
	if (!req_msg)
		return -ENOMEM;

	req_msg->num_networks = num_networks;

	/* Parse and fetch request Id */
	id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_SUBCMD_CONFIG_PARAM_REQUEST_ID;
	if (!tb[id]) {
		hdd_err("attr request id failed");
		goto fail;
	}
	req_msg->request_id = nla_get_u32(tb[id]);

	req_msg->vdev_id = adapter->deflink->vdev_id;
	hdd_debug("Req Id %u Vdev Id %d",
		  req_msg->request_id, req_msg->vdev_id);

	id = QCA_WLAN_VENDOR_ATTR_PNO_PASSPOINT_LIST_PARAM_NETWORK_ARRAY;
	networks = tb[id];
	if (hdd_extscan_passpoint_fill_networks(req_msg, networks))
		goto fail;

	status = sme_set_passpoint_list(hdd_ctx->mac_handle, req_msg);
	if (!QDF_IS_STATUS_SUCCESS(status)) {
		hdd_err("sme_set_passpoint_list failed(err=%d)", status);
		goto fail;
	}

	hdd_exit();
	qdf_mem_free(req_msg);
	return 0;

fail:
	qdf_mem_free(req_msg);
	return -EINVAL;
}

/**
 * wlan_hdd_cfg80211_set_passpoint_list() - set passpoint network list
 * @wiphy: wiphy
 * @wdev: pointer to wireless dev
 * @data: data pointer
 * @data_len: data length
 *
 * This function reads the NL vendor attributes from %tb and
 * fill in the passpoint request message.
 *
 * Return: 0 on success, error number otherwise
 */
int wlan_hdd_cfg80211_set_passpoint_list(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_set_passpoint_list(wiphy, wdev,
						       data, data_len);

	osif_vdev_sync_op_stop(vdev_sync);

	return errno;
}

/**
 * __wlan_hdd_cfg80211_reset_passpoint_list() - reset passpoint network list
 * @wiphy: wiphy
 * @wdev: pointer to wireless dev
 * @data: data pointer
 * @data_len: data length
 *
 * This function resets passpoint networks list
 *
 * Return: 0 on success, error number otherwise
 */
static int __wlan_hdd_cfg80211_reset_passpoint_list(struct wiphy *wiphy,
						    struct wireless_dev *wdev,
						    const void *data,
						    int data_len)
{
	struct wifi_passpoint_req_param *req_msg;
	struct net_device *dev = wdev->netdev;
	struct hdd_adapter *adapter = WLAN_HDD_GET_PRIV_PTR(dev);
	struct hdd_context *hdd_ctx = wiphy_priv(wiphy);
	struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_PNO_MAX + 1];
	QDF_STATUS status;
	int id, ret;

	hdd_enter_dev(dev);

	ret = wlan_hdd_validate_context(hdd_ctx);
	if (ret)
		return ret;

	if (QDF_GLOBAL_FTM_MODE == hdd_get_conparam()) {
		hdd_err("Command not allowed in FTM mode");
		return -EPERM;
	}

	if (wlan_cfg80211_nla_parse(tb, QCA_WLAN_VENDOR_ATTR_PNO_MAX, data,
				    data_len, wlan_hdd_extscan_config_policy)) {
		hdd_err("Invalid ATTR");
		return -EINVAL;
	}

	req_msg = qdf_mem_malloc(sizeof(*req_msg));
	if (!req_msg)
		return -ENOMEM;

	/* Parse and fetch request Id */
	id = QCA_WLAN_VENDOR_ATTR_EXTSCAN_SUBCMD_CONFIG_PARAM_REQUEST_ID;
	if (!tb[id]) {
		hdd_err("attr request id failed");
		goto fail;
	}
	req_msg->request_id = nla_get_u32(tb[id]);

	req_msg->vdev_id = adapter->deflink->vdev_id;
	hdd_debug("Req Id %u Vdev Id %d",
		  req_msg->request_id, req_msg->vdev_id);

	status = sme_reset_passpoint_list(hdd_ctx->mac_handle, req_msg);
	if (!QDF_IS_STATUS_SUCCESS(status)) {
		hdd_err("sme_reset_passpoint_list failed(err=%d)", status);
		goto fail;
	}

	hdd_exit();
	qdf_mem_free(req_msg);
	return 0;

fail:
	qdf_mem_free(req_msg);
	return -EINVAL;
}

/**
 * wlan_hdd_cfg80211_reset_passpoint_list() - reset passpoint network list
 * @wiphy: wiphy
 * @wdev: pointer to wireless dev
 * @data: data pointer
 * @data_len: data length
 *
 * This function resets passpoint networks list
 *
 * Return: 0 on success, error number otherwise
 */
int wlan_hdd_cfg80211_reset_passpoint_list(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_reset_passpoint_list(wiphy, wdev,
							 data, data_len);

	osif_vdev_sync_op_stop(vdev_sync);

	return errno;
}

/**
 * wlan_hdd_cfg80211_extscan_init() - Initialize the ExtScan feature
 * @hdd_ctx: Global HDD context
 *
 * Return: none
 */
void wlan_hdd_cfg80211_extscan_init(struct hdd_context *hdd_ctx)
{
	init_completion(&ext_scan_context.response_event);
	spin_lock_init(&ext_scan_context.context_lock);
}

#endif /* FEATURE_WLAN_EXTSCAN */
