blob: d9b4df2f02fcb034fe39c32e3539c939d267428d [file] [log] [blame]
/*
* Copyright (c) 2012-2017 The Linux Foundation. All rights reserved.
*
* Previously licensed under the ISC license by Qualcomm Atheros, Inc.
*
*
* Permission to use, copy, modify, and/or distribute this software for
* any purpose with or without fee is hereby granted, provided that the
* above copyright notice and this permission notice appear in all
* copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
* WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
* AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
* PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
/*
* This file was originally distributed by Qualcomm Atheros, Inc.
* under proprietary terms before Copyright ownership was assigned
* to the Linux Foundation.
*/
/**
* DOC: wlan_hdd_scan.c
*
* WLAN Host Device Driver scan implementation
*/
#include <linux/wireless.h>
#include <net/cfg80211.h>
#include "wlan_hdd_includes.h"
#include "cds_api.h"
#include "cds_api.h"
#include "ani_global.h"
#include "dot11f.h"
#include "cds_sched.h"
#include "wlan_hdd_p2p.h"
#include "wlan_hdd_trace.h"
#include "wlan_hdd_scan.h"
#include "cds_concurrency.h"
#include "wma_api.h"
#include "cds_utils.h"
#ifdef WLAN_UMAC_CONVERGENCE
#include "wlan_cfg80211.h"
#endif
#include <qca_vendor.h>
#define MAX_RATES 12
#define HDD_WAKE_LOCK_SCAN_DURATION (5 * 1000) /* in msec */
#define SCAN_DONE_EVENT_BUF_SIZE 4096
#define RATE_MASK 0x7f
/**
* typedef tSSIDBcastType - SSID broadcast type
* @eBCAST_UNKNOWN: Broadcast unknown
* @eBCAST_NORMAL: Broadcast normal
* @eBCAST_HIDDEN: Broadcast hidden
*/
typedef enum eSSIDBcastType {
eBCAST_UNKNOWN = 0,
eBCAST_NORMAL = 1,
eBCAST_HIDDEN = 2,
} tSSIDBcastType;
/**
* typedef hdd_scan_info_t - HDD scan info
* @dev: Pointer to net device
* @info: Pointer to request info
* @start: Start pointer
* @end: End pointer
*/
typedef struct hdd_scan_info {
struct net_device *dev;
struct iw_request_info *info;
char *start;
char *end;
} hdd_scan_info_t, *hdd_scan_info_tp;
/**
* hdd_translate_abg_rate_to_mbps_rate() - translate abg rate to Mbps rate
* @pFcRate: Rate pointer
*
* Return: Mbps rate in integer
*/
static int32_t hdd_translate_abg_rate_to_mbps_rate(uint8_t *pFcRate)
{
/* Slightly more sophisticated processing has to take place here.
* Basic rates are rounded DOWN. HT rates are rounded UP
*/
return ((((int32_t) *pFcRate) & 0x007f) * 1000000) / 2;
}
/**
* hdd_add_iw_stream_event() - add iw stream event
* @cmd: Command
* @length: Length
* @data: Pointer to data
* @pscanInfo: Pointer to scan info
* @last_event: Pointer to pointer to last event
* @current_event: Pointer to pointer to current event
*
* Return: 0 for success, non zero for failure
*/
static int hdd_add_iw_stream_event(int cmd, int length, char *data,
hdd_scan_info_t *pscanInfo,
char **last_event,
char **current_event)
{
struct iw_event event;
*last_event = *current_event;
qdf_mem_zero(&event, sizeof(struct iw_event));
event.cmd = cmd;
event.u.data.flags = 1;
event.u.data.length = length;
*current_event =
iwe_stream_add_point(pscanInfo->info, *current_event,
pscanInfo->end, &event, data);
if (*last_event == *current_event) {
/* no space to add event */
hdd_err("no space left to add event");
return -E2BIG; /* Error code, may be E2BIG */
}
return 0;
}
/**
* hdd_get_wparsn_ies() - get wpa RSN IEs
* @ieFields: Pointer to the Bss Descriptor IEs
* @ie_length: IE Length
* @last_event: Points to last event
* @current_event: Points to current event
*
* This function extract the WPA/RSN IE from the Bss descriptor IEs fields
*
* Return: 0 for success, non zero for failure
*/
static int hdd_get_wparsn_ies(uint8_t *ieFields, uint16_t ie_length,
char **last_event, char **current_event,
hdd_scan_info_t *pscanInfo)
{
uint8_t eid, elen, *element;
uint16_t tie_length = 0;
ENTER();
element = ieFields;
tie_length = ie_length;
while (tie_length > 2 && element != NULL) {
eid = element[0];
elen = element[1];
/* If element length is greater than total remaining ie length,
* break the loop
*/
if ((elen + 2) > tie_length)
break;
switch (eid) {
case DOT11F_EID_WPA:
case DOT11F_EID_RSN:
#ifdef FEATURE_WLAN_WAPI
case DOT11F_EID_WAPI:
#endif
if (hdd_add_iw_stream_event
(IWEVGENIE, elen + 2, (char *)element, pscanInfo,
last_event, current_event) < 0)
return -E2BIG;
break;
default:
break;
}
/* Next element */
tie_length -= (2 + elen);
element += 2 + elen;
}
return 0;
}
/**
* hdd_indicate_scan_result() - indicate scan results
* @scanInfo: Pointer to the scan info structure.
* @descriptor: Pointer to the Bss Descriptor.
*
* This function returns the scan results to the wpa_supplicant
*
* @Return: 0 for success, non zero for failure
*/
#define MAX_CUSTOM_LEN 64
static int hdd_indicate_scan_result(hdd_scan_info_t *scanInfo,
tCsrScanResultInfo *scan_result)
{
hdd_adapter_t *pAdapter = WLAN_HDD_GET_PRIV_PTR(scanInfo->dev);
tHalHandle hHal = WLAN_HDD_GET_HAL_CTX(pAdapter);
tSirBssDescription *descriptor = &scan_result->BssDescriptor;
struct iw_event event;
char *current_event = scanInfo->start;
char *end = scanInfo->end;
char *last_event;
char *current_pad;
uint16_t ie_length = 0;
uint16_t capabilityInfo;
char *modestr;
int error;
char custom[MAX_CUSTOM_LEN];
char *p;
hdd_notice(MAC_ADDRESS_STR, MAC_ADDR_ARRAY(descriptor->bssId));
error = 0;
last_event = current_event;
qdf_mem_zero(&event, sizeof(event));
/* BSSID */
event.cmd = SIOCGIWAP;
event.u.ap_addr.sa_family = ARPHRD_ETHER;
qdf_mem_copy(event.u.ap_addr.sa_data, descriptor->bssId,
sizeof(descriptor->bssId));
current_event = iwe_stream_add_event(scanInfo->info, current_event, end,
&event, IW_EV_ADDR_LEN);
if (last_event == current_event) {
/* no space to add event */
/* Error code may be E2BIG */
hdd_err("no space for SIOCGIWAP");
return -E2BIG;
}
last_event = current_event;
qdf_mem_zero(&event, sizeof(struct iw_event));
/* Protocol Name */
event.cmd = SIOCGIWNAME;
switch (descriptor->nwType) {
case eSIR_11A_NW_TYPE:
modestr = "a";
break;
case eSIR_11B_NW_TYPE:
modestr = "b";
break;
case eSIR_11G_NW_TYPE:
modestr = "g";
break;
case eSIR_11N_NW_TYPE:
modestr = "n";
break;
default:
hdd_warn("Unknown network type [%d]", descriptor->nwType);
modestr = "?";
break;
}
snprintf(event.u.name, IFNAMSIZ, "IEEE 802.11%s", modestr);
current_event = iwe_stream_add_event(scanInfo->info, current_event, end,
&event, IW_EV_CHAR_LEN);
if (last_event == current_event) { /* no space to add event */
hdd_err("no space for SIOCGIWNAME");
/* Error code, may be E2BIG */
return -E2BIG;
}
last_event = current_event;
qdf_mem_zero(&event, sizeof(struct iw_event));
/*Freq */
event.cmd = SIOCGIWFREQ;
event.u.freq.m = descriptor->channelId;
event.u.freq.e = 0;
event.u.freq.i = 0;
current_event = iwe_stream_add_event(scanInfo->info, current_event, end,
&event, IW_EV_FREQ_LEN);
if (last_event == current_event) { /* no space to add event */
hdd_err("no space for SIOCGIWFREQ");
return -E2BIG;
}
last_event = current_event;
qdf_mem_zero(&event, sizeof(struct iw_event));
/* BSS Mode */
event.cmd = SIOCGIWMODE;
capabilityInfo = descriptor->capabilityInfo;
if (SIR_MAC_GET_ESS(capabilityInfo)) {
event.u.mode = IW_MODE_MASTER;
} else if (SIR_MAC_GET_IBSS(capabilityInfo)) {
event.u.mode = IW_MODE_ADHOC;
} else {
/* neither ESS or IBSS */
event.u.mode = IW_MODE_AUTO;
}
current_event = iwe_stream_add_event(scanInfo->info, current_event, end,
&event, IW_EV_UINT_LEN);
if (last_event == current_event) { /* no space to add event */
hdd_err("no space for SIOCGIWMODE");
return -E2BIG;
}
/* To extract SSID */
ie_length = GET_IE_LEN_IN_BSS(descriptor->length);
if (ie_length > 0) {
/*
* dot11BeaconIEs is a large struct, so we make it static to
* avoid stack overflow. This API is only invoked via ioctl,
* so it is serialized by the kernel rtnl_lock and hence does
* not need to be reentrant
*/
static tDot11fBeaconIEs dot11BeaconIEs;
tDot11fIESSID *pDot11SSID;
tDot11fIESuppRates *pDot11SuppRates;
tDot11fIEExtSuppRates *pDot11ExtSuppRates;
tDot11fIEHTCaps *pDot11IEHTCaps;
int numBasicRates = 0;
int maxNumRates = 0;
pDot11IEHTCaps = NULL;
dot11f_unpack_beacon_i_es((tpAniSirGlobal)
hHal, (uint8_t *) descriptor->ieFields,
ie_length, &dot11BeaconIEs);
pDot11SSID = &dot11BeaconIEs.SSID;
if (pDot11SSID->present) {
last_event = current_event;
qdf_mem_zero(&event, sizeof(struct iw_event));
event.cmd = SIOCGIWESSID;
event.u.data.flags = 1;
event.u.data.length = scan_result->ssId.length;
current_event =
iwe_stream_add_point(scanInfo->info, current_event,
end, &event,
(char *)scan_result->ssId.
ssId);
if (last_event == current_event) { /* no space to add event */
hdd_err("no space for SIOCGIWESSID");
return -E2BIG;
}
}
if (hdd_get_wparsn_ies
((uint8_t *) descriptor->ieFields, ie_length, &last_event,
&current_event, scanInfo) < 0) {
hdd_err("no space for SIOCGIWESSID");
return -E2BIG;
}
last_event = current_event;
current_pad = current_event + IW_EV_LCP_LEN;
qdf_mem_zero(&event, sizeof(struct iw_event));
/*Rates */
event.cmd = SIOCGIWRATE;
pDot11SuppRates = &dot11BeaconIEs.SuppRates;
if (pDot11SuppRates->present) {
int i;
numBasicRates = pDot11SuppRates->num_rates;
for (i = 0; i < pDot11SuppRates->num_rates; i++) {
if (0 != (pDot11SuppRates->rates[i] & 0x7F)) {
event.u.bitrate.value =
hdd_translate_abg_rate_to_mbps_rate
(&pDot11SuppRates->rates[i]);
current_pad =
iwe_stream_add_value(scanInfo->info,
current_event,
current_pad,
end, &event,
IW_EV_PARAM_LEN);
}
}
}
pDot11ExtSuppRates = &dot11BeaconIEs.ExtSuppRates;
if (pDot11ExtSuppRates->present) {
int i, no_of_rates;
maxNumRates =
numBasicRates + pDot11ExtSuppRates->num_rates;
/* Check to make sure the total number of rates
* doesn't exceed IW_MAX_BITRATES
*/
maxNumRates = QDF_MIN(maxNumRates, IW_MAX_BITRATES);
if ((maxNumRates - numBasicRates) > MAX_RATES) {
no_of_rates = MAX_RATES;
hdd_warn("Accessing array out of bound that array is pDot11ExtSuppRates->rates ");
} else {
no_of_rates = maxNumRates - numBasicRates;
}
for (i = 0; i < no_of_rates; i++) {
if (0 != (pDot11ExtSuppRates->rates[i] & 0x7F)) {
event.u.bitrate.value =
hdd_translate_abg_rate_to_mbps_rate
(&pDot11ExtSuppRates->rates[i]);
current_pad =
iwe_stream_add_value(scanInfo->info,
current_event,
current_pad,
end, &event,
IW_EV_PARAM_LEN);
}
}
}
if ((current_pad - current_event) >= IW_EV_LCP_LEN) {
current_event = current_pad;
} else {
if (last_event == current_event) { /* no space to add event */
hdd_err("no space for SIOCGIWRATE");
return -E2BIG;
}
}
last_event = current_event;
qdf_mem_zero(&event, sizeof(struct iw_event));
event.cmd = SIOCGIWENCODE;
if (SIR_MAC_GET_PRIVACY(capabilityInfo)) {
event.u.data.flags =
IW_ENCODE_ENABLED | IW_ENCODE_NOKEY;
} else {
event.u.data.flags = IW_ENCODE_DISABLED;
}
event.u.data.length = 0;
current_event =
iwe_stream_add_point(scanInfo->info, current_event, end,
&event, (char *)pDot11SSID->ssid);
if (last_event == current_event) {
hdd_err("no space for SIOCGIWENCODE");
return -E2BIG;
}
}
last_event = current_event;
qdf_mem_zero(&event, sizeof(struct iw_event));
/* RSSI */
event.cmd = IWEVQUAL;
event.u.qual.qual = descriptor->rssi;
event.u.qual.noise = descriptor->sinr;
event.u.qual.level = QDF_MIN((descriptor->rssi + descriptor->sinr), 0);
event.u.qual.updated = IW_QUAL_ALL_UPDATED;
current_event = iwe_stream_add_event(scanInfo->info, current_event,
end, &event, IW_EV_QUAL_LEN);
if (last_event == current_event) { /* no space to add event */
hdd_err("no space for IWEVQUAL");
return -E2BIG;
}
/* AGE */
event.cmd = IWEVCUSTOM;
p = custom;
p += scnprintf(p, MAX_CUSTOM_LEN, " Age: %llu",
qdf_mc_timer_get_system_time() -
descriptor->received_time);
event.u.data.length = p - custom;
current_event = iwe_stream_add_point(scanInfo->info, current_event, end,
&event, custom);
if (last_event == current_event) { /* no space to add event */
hdd_err("no space for IWEVCUSTOM (age)");
return -E2BIG;
}
scanInfo->start = current_event;
return 0;
}
/**
* wlan_hdd_is_scan_pending() - Utility function to check pending scans
* @adapter: Pointer to the adapter
*
* Utility function to check pending scans on a particular adapter
*
* Return: true if scans are pending, false otherwise
*/
static bool wlan_hdd_is_scan_pending(hdd_adapter_t *adapter)
{
hdd_context_t *hdd_ctx = WLAN_HDD_GET_CTX(adapter);
qdf_list_node_t *pnode = NULL, *ppnode = NULL;
struct hdd_scan_req *hdd_scan_req;
qdf_spin_lock(&hdd_ctx->hdd_scan_req_q_lock);
if (qdf_list_empty(&hdd_ctx->hdd_scan_req_q)) {
qdf_spin_unlock(&hdd_ctx->hdd_scan_req_q_lock);
return false;
}
if (QDF_STATUS_SUCCESS != qdf_list_peek_front(&hdd_ctx->hdd_scan_req_q,
&ppnode)) {
qdf_spin_unlock(&hdd_ctx->hdd_scan_req_q_lock);
hdd_err("Failed to remove Scan Req from queue");
return false;
}
do {
pnode = ppnode;
hdd_scan_req = (struct hdd_scan_req *)pnode;
/* Any scan pending on the adapter */
if (adapter == hdd_scan_req->adapter) {
qdf_spin_unlock(&hdd_ctx->hdd_scan_req_q_lock);
hdd_info("pending scan id %d", hdd_scan_req->scan_id);
return true;
}
} while (QDF_STATUS_SUCCESS ==
qdf_list_peek_next(&hdd_ctx->hdd_scan_req_q, pnode, &ppnode));
qdf_spin_unlock(&hdd_ctx->hdd_scan_req_q_lock);
return false;
}
/**
* wlan_hdd_scan_request_enqueue() - enqueue Scan Request
* @adapter: Pointer to the adapter
* @scan_req: Pointer to the scan request
*
* Enqueue scan request in the global HDD scan list.This list
* stores the active scan request information.
*
* Return: 0 on success, error number otherwise
*/
static int wlan_hdd_scan_request_enqueue(hdd_adapter_t *adapter,
struct cfg80211_scan_request *scan_req,
uint8_t source, uint32_t scan_id,
uint32_t timestamp)
{
hdd_context_t *hdd_ctx = WLAN_HDD_GET_CTX(adapter);
struct hdd_scan_req *hdd_scan_req;
QDF_STATUS status;
ENTER();
hdd_scan_req = qdf_mem_malloc(sizeof(*hdd_scan_req));
if (NULL == hdd_scan_req) {
hdd_alert("malloc failed for Scan req");
return -ENOMEM;
}
hdd_scan_req->adapter = adapter;
hdd_scan_req->scan_request = scan_req;
hdd_scan_req->source = source;
hdd_scan_req->scan_id = scan_id;
hdd_scan_req->timestamp = timestamp;
qdf_spin_lock(&hdd_ctx->hdd_scan_req_q_lock);
status = qdf_list_insert_back(&hdd_ctx->hdd_scan_req_q,
&hdd_scan_req->node);
qdf_spin_unlock(&hdd_ctx->hdd_scan_req_q_lock);
if (QDF_STATUS_SUCCESS != status) {
hdd_err("Failed to enqueue Scan Req");
qdf_mem_free(hdd_scan_req);
return -EINVAL;
}
EXIT();
return 0;
}
/**
* wlan_hdd_scan_request_dequeue() - dequeue scan request
* @hdd_ctx: Global HDD context
* @scan_id: scan id
* @req: scan request
* @source : returns source of the scan request
* @timestamp: scan request timestamp
*
* Return: QDF_STATUS
*/
static QDF_STATUS wlan_hdd_scan_request_dequeue(hdd_context_t *hdd_ctx,
uint32_t scan_id, struct cfg80211_scan_request **req, uint8_t *source,
uint32_t *timestamp)
{
QDF_STATUS status = QDF_STATUS_E_FAILURE;
struct hdd_scan_req *hdd_scan_req;
qdf_list_node_t *pNode = NULL, *ppNode = NULL;
hdd_info("Dequeue Scan id: %d", scan_id);
if ((source == NULL) || (timestamp == NULL) || (req == NULL))
return QDF_STATUS_E_NULL_VALUE;
qdf_spin_lock(&hdd_ctx->hdd_scan_req_q_lock);
if (list_empty(&hdd_ctx->hdd_scan_req_q.anchor)) {
qdf_spin_unlock(&hdd_ctx->hdd_scan_req_q_lock);
return QDF_STATUS_E_FAILURE;
}
if (QDF_STATUS_SUCCESS !=
qdf_list_peek_front(&hdd_ctx->hdd_scan_req_q, &ppNode)) {
qdf_spin_unlock(&hdd_ctx->hdd_scan_req_q_lock);
hdd_err("Failed to remove Scan Req from queue");
return QDF_STATUS_E_FAILURE;
}
do {
pNode = ppNode;
hdd_scan_req = (struct hdd_scan_req *)pNode;
if (hdd_scan_req->scan_id == scan_id) {
status = qdf_list_remove_node(&hdd_ctx->hdd_scan_req_q,
pNode);
if (status == QDF_STATUS_SUCCESS) {
*req = hdd_scan_req->scan_request;
*source = hdd_scan_req->source;
*timestamp = hdd_scan_req->timestamp;
qdf_mem_free(hdd_scan_req);
qdf_spin_unlock(&hdd_ctx->hdd_scan_req_q_lock);
hdd_info("removed Scan id: %d, req = %p, pending scans %d",
scan_id, req,
qdf_list_size(&hdd_ctx->hdd_scan_req_q));
return QDF_STATUS_SUCCESS;
}
qdf_spin_unlock(&hdd_ctx->hdd_scan_req_q_lock);
hdd_err("Failed to remove node scan id %d, pending scans %d",
scan_id,
qdf_list_size(&hdd_ctx->hdd_scan_req_q));
return status;
}
} while (QDF_STATUS_SUCCESS ==
qdf_list_peek_next(&hdd_ctx->hdd_scan_req_q, pNode, &ppNode));
qdf_spin_unlock(&hdd_ctx->hdd_scan_req_q_lock);
hdd_err("Failed to find scan id %d", scan_id);
return status;
}
/**
* hdd_scan_request_callback() - scan complete callback from SME
* @halHandle: Pointer to the Hal Handle
* @pContext: Pointer to the data context
* @sessionId: Session identifier
* @scanId: Scan ID
* @status: CSR Status
*
* The sme module calls this callback function once it finish the scan request
* and this function notifies the scan complete event to the wpa_supplicant.
*
* Return: 0 for success, non zero for failure
*/
static QDF_STATUS
hdd_scan_request_callback(tHalHandle halHandle, void *pContext,
uint8_t sessionId, uint32_t scanId,
eCsrScanStatus status)
{
struct net_device *dev = (struct net_device *)pContext;
hdd_adapter_t *pAdapter = WLAN_HDD_GET_PRIV_PTR(dev);
hdd_context_t *hddctx = WLAN_HDD_GET_CTX(pAdapter);
union iwreq_data wrqu;
int we_event;
char *msg;
uint8_t source;
struct cfg80211_scan_request *req;
uint32_t timestamp;
ENTER();
hdd_warn("called with halHandle = %p, pContext = %p, scanID = %d, returned status = %d",
halHandle, pContext, (int)scanId, (int)status);
/* if there is a scan request pending when the wlan driver is unloaded
* we may be invoked as SME flushes its pending queue. If that is the
* case, the underlying net_device may have already been destroyed, so
* do some quick sanity before proceeding
*/
if (pAdapter->dev != dev) {
hdd_warn("device mismatch %p vs %p", pAdapter->dev, dev);
return QDF_STATUS_SUCCESS;
}
wlan_hdd_scan_request_dequeue(hddctx, scanId, &req, &source,
&timestamp);
if (req != NULL)
hdd_err("Got unexpected request struct for Scan id %d",
scanId);
/* Scan is no longer pending */
if (!wlan_hdd_is_scan_pending(pAdapter))
pAdapter->scan_info.mScanPending = false;
/* notify any applications that may be interested */
memset(&wrqu, '\0', sizeof(wrqu));
we_event = SIOCGIWSCAN;
msg = NULL;
wireless_send_event(dev, we_event, &wrqu, msg);
EXIT();
return QDF_STATUS_SUCCESS;
}
/**
* __iw_set_scan() - set scan request
* @dev: Pointer to the net device
* @info: Pointer to the iw_request_info
* @wrqu: Pointer to the iwreq_data
* @extra: Pointer to the data
*
* This function process the scan request from the wpa_supplicant
* and set the scan request to the SME
*
* Return: 0 for success, non zero for failure
*/
static int __iw_set_scan(struct net_device *dev, struct iw_request_info *info,
union iwreq_data *wrqu, char *extra)
{
hdd_adapter_t *pAdapter = WLAN_HDD_GET_PRIV_PTR(dev);
hdd_context_t *hdd_ctx = WLAN_HDD_GET_CTX(pAdapter);
hdd_wext_state_t *pwextBuf = WLAN_HDD_GET_WEXT_STATE_PTR(pAdapter);
tCsrScanRequest scanRequest;
QDF_STATUS status = QDF_STATUS_SUCCESS;
struct iw_scan_req *scanReq = (struct iw_scan_req *)extra;
hdd_adapter_t *con_sap_adapter;
uint16_t con_dfs_ch;
int ret;
ENTER_DEV(dev);
hdd_ctx = WLAN_HDD_GET_CTX(pAdapter);
ret = wlan_hdd_validate_context(hdd_ctx);
if (0 != ret)
return ret;
ret = hdd_check_standard_wext_control(hdd_ctx, info);
if (0 != ret)
return ret;
/* Block All Scan during DFS operation and send null scan result */
con_sap_adapter = hdd_get_con_sap_adapter(pAdapter, true);
if (con_sap_adapter) {
con_dfs_ch = con_sap_adapter->sessionCtx.ap.operatingChannel;
if (CDS_IS_DFS_CH(con_dfs_ch)) {
hdd_warn("##In DFS Master mode. Scan aborted");
return -EOPNOTSUPP;
}
}
qdf_mem_zero(&scanRequest, sizeof(scanRequest));
if (NULL != wrqu->data.pointer) {
/* set scanType, active or passive */
if ((IW_SCAN_TYPE_ACTIVE == scanReq->scan_type) ||
(eSIR_ACTIVE_SCAN == hdd_ctx->ioctl_scan_mode)) {
scanRequest.scanType = eSIR_ACTIVE_SCAN;
} else {
scanRequest.scanType = eSIR_PASSIVE_SCAN;
}
/* set bssid using sockaddr from iw_scan_req */
qdf_mem_copy(scanRequest.bssid.bytes,
&scanReq->bssid.sa_data,
QDF_MAC_ADDR_SIZE);
if (wrqu->data.flags & IW_SCAN_THIS_ESSID) {
if (scanReq->essid_len &&
(scanReq->essid_len <= SIR_MAC_MAX_SSID_LENGTH)) {
scanRequest.SSIDs.numOfSSIDs = 1;
scanRequest.SSIDs.SSIDList =
(tCsrSSIDInfo *)
qdf_mem_malloc(sizeof(tCsrSSIDInfo));
if (scanRequest.SSIDs.SSIDList) {
scanRequest.SSIDs.SSIDList->SSID.
length = scanReq->essid_len;
qdf_mem_copy(scanRequest.SSIDs.
SSIDList->SSID.ssId,
scanReq->essid,
scanReq->essid_len);
} else {
scanRequest.SSIDs.numOfSSIDs = 0;
hdd_err("Unable to allocate memory");
QDF_ASSERT(0);
}
} else {
hdd_err("Invalid essid length : %d",
scanReq->essid_len);
}
}
/* set min and max channel time */
scanRequest.minChnTime = scanReq->min_channel_time;
scanRequest.maxChnTime = scanReq->max_channel_time;
} else {
if (hdd_ctx->ioctl_scan_mode == eSIR_ACTIVE_SCAN) {
/* set the scan type to active */
scanRequest.scanType = eSIR_ACTIVE_SCAN;
} else {
scanRequest.scanType = eSIR_PASSIVE_SCAN;
}
qdf_set_macaddr_broadcast(&scanRequest.bssid);
/* set min and max channel time to zero */
scanRequest.minChnTime = 0;
scanRequest.maxChnTime = 0;
}
/* set BSSType to default type */
scanRequest.BSSType = eCSR_BSS_TYPE_ANY;
/*Scan all the channels */
scanRequest.ChannelInfo.numOfChannels = 0;
scanRequest.ChannelInfo.ChannelList = NULL;
/* set requestType to full scan */
scanRequest.requestType = eCSR_SCAN_REQUEST_FULL_SCAN;
/* if previous genIE is not NULL, update ScanIE */
if (0 != pwextBuf->genIE.length) {
memset(&pAdapter->scan_info.scanAddIE, 0,
sizeof(pAdapter->scan_info.scanAddIE));
memcpy(pAdapter->scan_info.scanAddIE.addIEdata,
pwextBuf->genIE.addIEdata, pwextBuf->genIE.length);
pAdapter->scan_info.scanAddIE.length = pwextBuf->genIE.length;
pwextBuf->roamProfile.pAddIEScan =
pAdapter->scan_info.scanAddIE.addIEdata;
pwextBuf->roamProfile.nAddIEScanLength =
pAdapter->scan_info.scanAddIE.length;
/* clear previous genIE after use it */
memset(&pwextBuf->genIE, 0, sizeof(pwextBuf->genIE));
}
/* push addIEScan in scanRequset if exist */
if (pAdapter->scan_info.scanAddIE.length &&
pAdapter->scan_info.scanAddIE.length <=
sizeof(pAdapter->scan_info.scanAddIE.addIEdata)) {
scanRequest.uIEFieldLen = pAdapter->scan_info.scanAddIE.length;
scanRequest.pIEField = pAdapter->scan_info.scanAddIE.addIEdata;
}
scanRequest.timestamp = qdf_mc_timer_get_system_time();
status = sme_scan_request((WLAN_HDD_GET_CTX(pAdapter))->hHal,
pAdapter->sessionId, &scanRequest,
&hdd_scan_request_callback, dev);
if (!QDF_IS_STATUS_SUCCESS(status)) {
hdd_alert("sme_scan_request fail %d!!!", status);
goto error;
}
wlan_hdd_scan_request_enqueue(pAdapter, NULL, NL_SCAN,
scanRequest.scan_id,
scanRequest.timestamp);
pAdapter->scan_info.mScanPending = true;
error:
if ((wrqu->data.flags & IW_SCAN_THIS_ESSID) && (scanReq->essid_len))
qdf_mem_free(scanRequest.SSIDs.SSIDList);
EXIT();
return status;
}
/**
* iw_set_scan() - SSR wrapper for __iw_set_scan
* @dev: Pointer to the net device.
* @info: Pointer to the iw_request_info.
* @wrqu: Pointer to the iwreq_data.
* @extra: Pointer to the data.
*
* Return: 0 on success, error number otherwise
*/
int iw_set_scan(struct net_device *dev, struct iw_request_info *info,
union iwreq_data *wrqu, char *extra)
{
int ret;
cds_ssr_protect(__func__);
ret = __iw_set_scan(dev, info, wrqu, extra);
cds_ssr_unprotect(__func__);
return ret;
}
/**
* __iw_get_scan() - get scan
* @dev: Pointer to the net device.
* @info: Pointer to the iw_request_info.
* @wrqu: Pointer to the iwreq_data.
* @extra: Pointer to the data.
*
* This function returns the scan results to the wpa_supplicant
*
* Return: 0 for success, non zero for failure
*/
static int __iw_get_scan(struct net_device *dev,
struct iw_request_info *info,
union iwreq_data *wrqu, char *extra)
{
hdd_adapter_t *pAdapter = WLAN_HDD_GET_PRIV_PTR(dev);
hdd_context_t *hdd_ctx;
tHalHandle hHal = WLAN_HDD_GET_HAL_CTX(pAdapter);
tCsrScanResultInfo *pScanResult;
QDF_STATUS status = QDF_STATUS_SUCCESS;
hdd_scan_info_t scanInfo;
tScanResultHandle pResult;
int i = 0;
int ret;
ENTER_DEV(dev);
hdd_ctx = WLAN_HDD_GET_CTX(pAdapter);
ret = wlan_hdd_validate_context(hdd_ctx);
if (0 != ret)
return ret;
ret = hdd_check_standard_wext_control(hdd_ctx, info);
if (0 != ret)
return ret;
hdd_notice("enter buffer length %d!!!",
(wrqu->data.length) ? wrqu->data.length : IW_SCAN_MAX_DATA);
if (true == pAdapter->scan_info.mScanPending) {
hdd_alert("mScanPending is true !!!");
return -EAGAIN;
}
scanInfo.dev = dev;
scanInfo.start = extra;
scanInfo.info = info;
if (0 == wrqu->data.length) {
scanInfo.end = extra + IW_SCAN_MAX_DATA;
} else {
scanInfo.end = extra + wrqu->data.length;
}
status = sme_scan_get_result(hHal, pAdapter->sessionId, NULL, &pResult);
if (NULL == pResult) {
/* no scan results */
hdd_notice("iw_get_scan: NULL Scan Result ");
return 0;
}
pScanResult = sme_scan_result_get_first(hHal, pResult);
while (pScanResult) {
status = hdd_indicate_scan_result(&scanInfo, pScanResult);
if (0 != status) {
break;
}
i++;
pScanResult = sme_scan_result_get_next(hHal, pResult);
}
sme_scan_result_purge(hHal, pResult);
hdd_notice("exit total %d BSS reported !!!", i);
EXIT();
return status;
}
/**
* iw_get_scan() - SSR wrapper function for __iw_get_scan
* @dev: Pointer to the net device.
* @info: Pointer to the iw_request_info.
* @wrqu: Pointer to the iwreq_data.
* @extra: Pointer to the data.
*
* Return: 0 on success, error number otherwise
*/
int iw_get_scan(struct net_device *dev,
struct iw_request_info *info,
union iwreq_data *wrqu, char *extra)
{
int ret;
cds_ssr_protect(__func__);
ret = __iw_get_scan(dev, info, wrqu, extra);
cds_ssr_unprotect(__func__);
return ret;
}
/**
* hdd_abort_mac_scan() - aborts ongoing mac scan
* @pHddCtx: Pointer to hdd context
* @sessionId: session id
* @scan_id: scan id
* @reason: abort reason
*
* Abort any MAC scan if in progress
*
* Return: none
*/
void hdd_abort_mac_scan(hdd_context_t *pHddCtx, uint8_t sessionId,
uint32_t scan_id, eCsrAbortReason reason)
{
sme_abort_mac_scan(pHddCtx->hHal, sessionId, scan_id, reason);
}
/**
* hdd_vendor_scan_callback() - Scan completed callback event
* @hddctx: HDD context
* @req : Scan request
* @aborted : true scan aborted false scan success
*
* This function sends scan completed callback event to NL.
*
* Return: none
*/
static void hdd_vendor_scan_callback(hdd_adapter_t *adapter,
struct cfg80211_scan_request *req,
bool aborted)
{
hdd_context_t *hddctx = WLAN_HDD_GET_CTX(adapter);
struct sk_buff *skb;
struct nlattr *attr;
int i;
uint8_t scan_status;
uint64_t cookie;
ENTER();
if (WLAN_HDD_ADAPTER_MAGIC != adapter->magic) {
hdd_err("Invalid adapter magic");
qdf_mem_free(req);
return;
}
skb = cfg80211_vendor_event_alloc(hddctx->wiphy, &(adapter->wdev),
SCAN_DONE_EVENT_BUF_SIZE + 4 + NLMSG_HDRLEN,
QCA_NL80211_VENDOR_SUBCMD_SCAN_DONE_INDEX,
GFP_KERNEL);
if (!skb) {
hdd_err("skb alloc failed");
qdf_mem_free(req);
return;
}
cookie = (uintptr_t)req;
attr = nla_nest_start(skb, QCA_WLAN_VENDOR_ATTR_SCAN_SSIDS);
if (!attr)
goto nla_put_failure;
for (i = 0; i < req->n_ssids; i++) {
if (nla_put(skb, i, req->ssids[i].ssid_len, req->ssids[i].ssid))
goto nla_put_failure;
}
nla_nest_end(skb, attr);
attr = nla_nest_start(skb, QCA_WLAN_VENDOR_ATTR_SCAN_FREQUENCIES);
if (!attr)
goto nla_put_failure;
for (i = 0; i < req->n_channels; i++) {
if (nla_put_u32(skb, i, req->channels[i]->center_freq))
goto nla_put_failure;
}
nla_nest_end(skb, attr);
if (req->ie &&
nla_put(skb, QCA_WLAN_VENDOR_ATTR_SCAN_IE, req->ie_len,
req->ie))
goto nla_put_failure;
if (req->flags &&
nla_put_u32(skb, QCA_WLAN_VENDOR_ATTR_SCAN_FLAGS, req->flags))
goto nla_put_failure;
if (hdd_wlan_nla_put_u64(skb, QCA_WLAN_VENDOR_ATTR_SCAN_COOKIE, cookie))
goto nla_put_failure;
scan_status = (aborted == true) ? VENDOR_SCAN_STATUS_ABORTED :
VENDOR_SCAN_STATUS_NEW_RESULTS;
if (nla_put_u8(skb, QCA_WLAN_VENDOR_ATTR_SCAN_STATUS, scan_status))
goto nla_put_failure;
cfg80211_vendor_event(skb, GFP_KERNEL);
qdf_mem_free(req);
return;
nla_put_failure:
kfree_skb(skb);
qdf_mem_free(req);
return;
}
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0))
/**
* hdd_cfg80211_scan_done() - Scan completed callback to cfg80211
* @adapter: Pointer to the adapter
* @req : Scan request
* @aborted : true scan aborted false scan success
*
* This function notifies scan done to cfg80211
*
* Return: none
*/
static void hdd_cfg80211_scan_done(hdd_adapter_t *adapter,
struct cfg80211_scan_request *req,
bool aborted)
{
struct cfg80211_scan_info info = {
.aborted = aborted
};
if (adapter->dev->flags & IFF_UP)
cfg80211_scan_done(req, &info);
}
#elif (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 14, 0))
/**
* hdd_cfg80211_scan_done() - Scan completed callback to cfg80211
* @adapter: Pointer to the adapter
* @req : Scan request
* @aborted : true scan aborted false scan success
*
* This function notifies scan done to cfg80211
*
* Return: none
*/
static void hdd_cfg80211_scan_done(hdd_adapter_t *adapter,
struct cfg80211_scan_request *req,
bool aborted)
{
if (adapter->dev->flags & IFF_UP)
cfg80211_scan_done(req, aborted);
}
#else
/**
* hdd_cfg80211_scan_done() - Scan completed callback to cfg80211
* @adapter: Pointer to the adapter
* @req : Scan request
* @aborted : true scan aborted false scan success
*
* This function notifies scan done to cfg80211
*
* Return: none
*/
static void hdd_cfg80211_scan_done(hdd_adapter_t *adapter,
struct cfg80211_scan_request *req,
bool aborted)
{
cfg80211_scan_done(req, aborted);
}
#endif
/**
* hdd_cfg80211_scan_done_callback() - scan done callback function called after
* scan is finished
* @halHandle: Pointer to handle
* @pContext: Pointer to context
* @sessionId: Session Id
* @scanId: Scan Id
* @status: Scan status
*
* Return: QDF status
*/
static QDF_STATUS hdd_cfg80211_scan_done_callback(tHalHandle halHandle,
void *pContext,
uint8_t sessionId,
uint32_t scanId,
eCsrScanStatus status)
{
struct net_device *dev = (struct net_device *)pContext;
hdd_adapter_t *pAdapter = WLAN_HDD_GET_PRIV_PTR(dev);
hdd_scaninfo_t *pScanInfo = &pAdapter->scan_info;
struct cfg80211_scan_request *req = NULL;
bool aborted = false;
hdd_context_t *hddctx = WLAN_HDD_GET_CTX(pAdapter);
int ret = 0;
unsigned int current_timestamp, time_elapsed;
uint8_t source;
uint32_t scan_time;
uint32_t size = 0;
ret = wlan_hdd_validate_context(hddctx);
if (0 != ret)
return QDF_STATUS_E_INVAL;
hdd_notice("called with hal = %p, pContext = %p, ID = %d, status = %d",
halHandle, pContext, (int)scanId, (int)status);
pScanInfo->mScanPendingCounter = 0;
if (pScanInfo->mScanPending != true) {
QDF_ASSERT(pScanInfo->mScanPending);
goto allow_suspend;
}
if (QDF_STATUS_SUCCESS !=
wlan_hdd_scan_request_dequeue(hddctx, scanId, &req, &source,
&scan_time)) {
hdd_err("Dequeue of scan request failed ID: %d", scanId);
goto allow_suspend;
}
/*
* cfg80211_scan_done informing NL80211 about completion
* of scanning
*/
if (status == eCSR_SCAN_ABORT || status == eCSR_SCAN_FAILURE) {
aborted = true;
}
if (!aborted && !hddctx->beacon_probe_rsp_cnt_per_scan) {
hdd_notice("NO SCAN result");
if (hddctx->config->bug_report_for_no_scan_results) {
current_timestamp = jiffies_to_msecs(jiffies);
time_elapsed = current_timestamp -
hddctx->last_nil_scan_bug_report_timestamp;
/*
* check if we have generated bug report in
* MIN_TIME_REQUIRED_FOR_NEXT_BUG_REPORT time.
*/
if (time_elapsed >
MIN_TIME_REQUIRED_FOR_NEXT_BUG_REPORT) {
cds_flush_logs(WLAN_LOG_TYPE_NON_FATAL,
WLAN_LOG_INDICATOR_HOST_DRIVER,
WLAN_LOG_REASON_NO_SCAN_RESULTS,
true, true);
hddctx->last_nil_scan_bug_report_timestamp =
current_timestamp;
}
}
}
hddctx->beacon_probe_rsp_cnt_per_scan = 0;
if (!wlan_hdd_is_scan_pending(pAdapter)) {
/* Scan is no longer pending */
pScanInfo->mScanPending = false;
complete(&pScanInfo->abortscan_event_var);
}
/*
* Scan can be triggred from NL or vendor scan
* - If scan is triggered from NL then cfg80211 scan done should be
* called to updated scan completion to NL.
* - If scan is triggred through vendor command then
* scan done event will be posted
*/
if (NL_SCAN == source)
hdd_cfg80211_scan_done(pAdapter, req, aborted);
else
hdd_vendor_scan_callback(pAdapter, req, aborted);
allow_suspend:
qdf_runtime_pm_allow_suspend(hddctx->runtime_context.scan);
qdf_spin_lock(&hddctx->hdd_scan_req_q_lock);
size = qdf_list_size(&hddctx->hdd_scan_req_q);
if (!size) {
qdf_spin_unlock(&hddctx->hdd_scan_req_q_lock);
/* release the wake lock at the end of the scan */
hdd_allow_suspend(WIFI_POWER_EVENT_WAKELOCK_SCAN);
/* Acquire wakelock to handle the case where APP's tries
* to suspend immediatly after the driver gets connect
* request(i.e after scan) from supplicant, this result in
* app's is suspending and not ableto process the connect
* request to AP
*/
hdd_prevent_suspend_timeout(1000,
WIFI_POWER_EVENT_WAKELOCK_SCAN);
} else {
/* Release the spin lock */
qdf_spin_unlock(&hddctx->hdd_scan_req_q_lock);
}
#ifdef FEATURE_WLAN_TDLS
wlan_hdd_tdls_scan_done_callback(pAdapter);
#endif
EXIT();
return 0;
}
#ifdef FEATURE_WLAN_AP_AP_ACS_OPTIMIZE
/**
* wlan_hdd_sap_skip_scan_check() - The function will check OBSS
* scan skip or not for SAP.
* @hdd_ctx: pointer to hdd context.
* @request: pointer to scan request.
*
* This function will check the scan request's chan list against the
* previous ACS scan chan list. If all the chan are covered by
* previous ACS scan, we can skip the scan and return scan complete
* to save the SAP starting time.
*
* Return: true to skip the scan,
* false to continue the scan
*/
static bool wlan_hdd_sap_skip_scan_check(hdd_context_t *hdd_ctx,
struct cfg80211_scan_request *request)
{
int i, j;
bool skip;
hdd_info("HDD_ACS_SKIP_STATUS = %d",
hdd_ctx->skip_acs_scan_status);
if (hdd_ctx->skip_acs_scan_status != eSAP_SKIP_ACS_SCAN)
return false;
qdf_spin_lock(&hdd_ctx->acs_skip_lock);
if (hdd_ctx->last_acs_channel_list == NULL ||
hdd_ctx->num_of_channels == 0 ||
request->n_channels == 0) {
qdf_spin_unlock(&hdd_ctx->acs_skip_lock);
return false;
}
skip = true;
for (i = 0; i < request->n_channels ; i++) {
bool find = false;
for (j = 0; j < hdd_ctx->num_of_channels; j++) {
if (hdd_ctx->last_acs_channel_list[j] ==
request->channels[i]->hw_value) {
find = true;
break;
}
}
if (!find) {
skip = false;
hdd_info("Chan %d isn't in ACS chan list",
request->channels[i]->hw_value);
break;
}
}
qdf_spin_unlock(&hdd_ctx->acs_skip_lock);
return skip;
}
#else
static bool wlan_hdd_sap_skip_scan_check(hdd_context_t *hdd_ctx,
struct cfg80211_scan_request *request)
{
return false;
}
#endif
/**
* wlan_hdd_cfg80211_scan_block_cb() - scan block work handler
* @work: Pointer to work
*
* Return: none
*/
static void wlan_hdd_cfg80211_scan_block_cb(struct work_struct *work)
{
hdd_adapter_t *adapter = container_of(work,
hdd_adapter_t, scan_block_work);
struct cfg80211_scan_request *request;
if (WLAN_HDD_ADAPTER_MAGIC != adapter->magic) {
hdd_err("HDD adapter context is invalid");
return;
}
request = adapter->request;
if (request) {
request->n_ssids = 0;
request->n_channels = 0;
hdd_err("##In DFS Master mode. Scan aborted. Null result sent");
hdd_cfg80211_scan_done(adapter, request, true);
adapter->request = NULL;
}
}
/**
* wlan_hdd_copy_bssid_scan_request() - API to copy the bssid to Scan request
* @scan_req: Pointer to CSR Scan Request
* @request: scan request from Supplicant
*
* This API copies the BSSID in scan request from Supplicant and copies it to
* the CSR Scan request
*
* Return: None
*/
#if defined(CFG80211_SCAN_BSSID) || \
(LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0))
static inline void wlan_hdd_copy_bssid_scan_request(tCsrScanRequest *scan_req,
struct cfg80211_scan_request *request)
{
qdf_mem_copy(scan_req->bssid.bytes, request->bssid, QDF_MAC_ADDR_SIZE);
}
#else
static inline void wlan_hdd_copy_bssid_scan_request(tCsrScanRequest *scan_req,
struct cfg80211_scan_request *request)
{
}
#endif
/*
* wlan_hdd_update_scan_ies() - API to update the scan IEs of scan request
* with already stored default scan IEs
*
* @adapter: Pointer to HDD adapter
* @scan_info: Pointer to scan info in HDD adapter
* @scan_ie: Pointer to scan IE in scan request
* @scan_ie_len: Pointer to scan IE length in scan request
*
* Return: 0 on success; error number otherwise
*/
static int wlan_hdd_update_scan_ies(hdd_adapter_t *adapter,
hdd_scaninfo_t *scan_info, uint8_t *scan_ie,
uint8_t *scan_ie_len)
{
uint8_t rem_len = scan_info->default_scan_ies_len;
uint8_t *temp_ie = scan_info->default_scan_ies;
uint8_t *current_ie;
uint8_t elem_id;
uint16_t elem_len;
if (!scan_info->default_scan_ies_len || !scan_info->default_scan_ies)
return -EINVAL;
while (rem_len >= 2) {
current_ie = temp_ie;
elem_id = *temp_ie++;
elem_len = *temp_ie++;
rem_len -= 2;
switch (elem_id) {
case DOT11F_EID_EXTCAP:
if (!wlan_hdd_cfg80211_get_ie_ptr(scan_ie, *scan_ie_len,
DOT11F_EID_EXTCAP)) {
qdf_mem_copy(scan_ie + (*scan_ie_len),
current_ie, elem_len + 2);
(*scan_ie_len) += (elem_len + 2);
}
break;
case DOT11F_EID_WPA:
if (!wlan_hdd_get_mbo_ie_ptr(scan_ie, *scan_ie_len)) {
qdf_mem_copy(scan_ie + (*scan_ie_len),
current_ie, elem_len + 2);
(*scan_ie_len) += (elem_len + 2);
}
break;
}
temp_ie += elem_len;
rem_len -= elem_len;
}
return 0;
}
/**
* __wlan_hdd_cfg80211_scan() - API to process cfg80211 scan request
* @wiphy: Pointer to wiphy
* @dev: Pointer to net device
* @request: Pointer to scan request
* @source: scan request source(NL/Vendor scan)
*
* This API responds to scan trigger and update cfg80211 scan database
* later, scan dump command can be used to recieve scan results
*
* Return: 0 for success, non zero for failure
*/
#ifdef WLAN_UMAC_CONVERGENCE
static int __wlan_hdd_cfg80211_scan(struct wiphy *wiphy,
struct cfg80211_scan_request *request,
uint8_t source)
{
return wlan_cfg80211_scan(wiphy, request);
}
#else
static int __wlan_hdd_cfg80211_scan(struct wiphy *wiphy,
struct cfg80211_scan_request *request,
uint8_t source)
{
struct net_device *dev = request->wdev->netdev;
hdd_adapter_t *pAdapter = WLAN_HDD_GET_PRIV_PTR(dev);
hdd_context_t *pHddCtx = WLAN_HDD_GET_CTX(pAdapter);
hdd_wext_state_t *pwextBuf = WLAN_HDD_GET_WEXT_STATE_PTR(pAdapter);
struct hdd_config *cfg_param = NULL;
tCsrScanRequest scan_req;
uint8_t *channelList = NULL, i;
int status;
hdd_scaninfo_t *pScanInfo = NULL;
uint8_t *pP2pIe = NULL;
hdd_adapter_t *con_sap_adapter;
uint16_t con_dfs_ch;
uint8_t num_chan = 0;
bool is_p2p_scan = false;
ENTER();
if (QDF_GLOBAL_FTM_MODE == hdd_get_conparam()) {
hdd_err("Command not allowed in FTM mode");
return -EINVAL;
}
if (wlan_hdd_validate_session_id(pAdapter->sessionId)) {
hdd_err("invalid session id: %d", pAdapter->sessionId);
return -EINVAL;
}
status = wlan_hdd_validate_context(pHddCtx);
if (0 != status)
return status;
MTRACE(qdf_trace(QDF_MODULE_ID_HDD,
TRACE_CODE_HDD_CFG80211_SCAN,
pAdapter->sessionId, request->n_channels));
if (!sme_is_session_id_valid(pHddCtx->hHal, pAdapter->sessionId))
return -EINVAL;
hdd_notice("Device_mode %s(%d)",
hdd_device_mode_to_string(pAdapter->device_mode),
pAdapter->device_mode);
cfg_param = pHddCtx->config;
pScanInfo = &pAdapter->scan_info;
/* Block All Scan during DFS operation and send null scan result */
con_sap_adapter = hdd_get_con_sap_adapter(pAdapter, true);
if (con_sap_adapter) {
con_dfs_ch = con_sap_adapter->sessionCtx.ap.sapConfig.channel;
if (con_dfs_ch == AUTO_CHANNEL_SELECT)
con_dfs_ch =
con_sap_adapter->sessionCtx.ap.operatingChannel;
if (!wma_is_hw_dbs_capable() && CDS_IS_DFS_CH(con_dfs_ch)) {
/* Provide empty scan result during DFS operation since
* scanning not supported during DFS. Reason is
* following case:
* DFS is supported only in SCC for MBSSID Mode.
* We shall not return EBUSY or ENOTSUPP as when Primary
* AP is operating in DFS channel and secondary AP is
* started. Though we force SCC in driver, the hostapd
* issues obss scan before starting secAP. This results
* in MCC in DFS mode. Thus we return null scan result.
* If we return scan failure hostapd fails secondary AP
* startup.
*/
hdd_err("##In DFS Master mode. Scan aborted");
pAdapter->request = request;
INIT_WORK(&pAdapter->scan_block_work,
wlan_hdd_cfg80211_scan_block_cb);
schedule_work(&pAdapter->scan_block_work);
return 0;
}
}
if (!wma_is_hw_dbs_capable()) {
if (true == pScanInfo->mScanPending) {
if (MAX_PENDING_LOG >
pScanInfo->mScanPendingCounter++) {
hdd_err("mScanPending is true");
}
return -EBUSY;
}
/* Don't Allow Scan and return busy if Remain On
* Channel and action frame is pending
* Otherwise Cancel Remain On Channel and allow Scan
* If no action frame pending
*/
if (0 != wlan_hdd_check_remain_on_channel(pAdapter)) {
hdd_err("Remain On Channel Pending");
return -EBUSY;
}
}
#ifdef FEATURE_WLAN_TDLS
/* if tdls disagree scan right now, return immediately.
* tdls will schedule the scan when scan is allowed.
* (return SUCCESS)
* or will reject the scan if any TDLS is in progress.
* (return -EBUSY)
*/
status = wlan_hdd_tdls_scan_callback(pAdapter, wiphy,
request, source);
if (status <= 0) {
if (!status)
hdd_err("TDLS in progress.scan rejected %d",
status);
else
hdd_err("TDLS teardown is ongoing %d",
status);
hdd_wlan_block_scan_by_tdls_event();
return status;
}
#endif
/* Check if scan is allowed at this point of time */
if (cds_is_connection_in_progress()) {
hdd_err("Scan not allowed");
return -EBUSY;
}
/* Check whether SAP scan can be skipped or not */
if (pAdapter->device_mode == QDF_SAP_MODE &&
wlan_hdd_sap_skip_scan_check(pHddCtx, request)) {
hdd_err("sap scan skipped");
pAdapter->request = request;
INIT_WORK(&pAdapter->scan_block_work,
wlan_hdd_cfg80211_scan_block_cb);
schedule_work(&pAdapter->scan_block_work);
return 0;
}
qdf_mem_zero(&scan_req, sizeof(scan_req));
scan_req.timestamp = qdf_mc_timer_get_system_time();
/* Even though supplicant doesn't provide any SSIDs, n_ssids is
* set to 1. Because of this, driver is assuming that this is not
* wildcard scan and so is not aging out the scan results.
*/
if ((request->ssids) && (request->n_ssids == 1) &&
('\0' == request->ssids->ssid[0])) {
request->n_ssids = 0;
}
if ((request->ssids) && (0 < request->n_ssids)) {
tCsrSSIDInfo *SsidInfo;
int j;
scan_req.SSIDs.numOfSSIDs = request->n_ssids;
/* Allocate num_ssid tCsrSSIDInfo structure */
SsidInfo = scan_req.SSIDs.SSIDList =
qdf_mem_malloc(request->n_ssids * sizeof(tCsrSSIDInfo));
if (NULL == scan_req.SSIDs.SSIDList) {
hdd_err("memory alloc failed SSIDInfo buffer");
return -ENOMEM;
}
/* copy all the ssid's and their length */
for (j = 0; j < request->n_ssids; j++, SsidInfo++) {
/* get the ssid length */
SsidInfo->SSID.length = request->ssids[j].ssid_len;
qdf_mem_copy(SsidInfo->SSID.ssId,
&request->ssids[j].ssid[0],
SsidInfo->SSID.length);
SsidInfo->SSID.ssId[SsidInfo->SSID.length] = '\0';
hdd_notice("SSID number %d: %s", j,
SsidInfo->SSID.ssId);
}
/* set the scan type to active */
scan_req.scanType = eSIR_ACTIVE_SCAN;
} else if (QDF_P2P_GO_MODE == pAdapter->device_mode) {
/* set the scan type to active */
scan_req.scanType = eSIR_ACTIVE_SCAN;
} else {
/*
* Set the scan type to passive if there is no ssid list
* provided else set default type configured in the driver.
*/
if (!request->ssids)
scan_req.scanType = eSIR_PASSIVE_SCAN;
else
scan_req.scanType = pHddCtx->ioctl_scan_mode;
}
scan_req.minChnTime = cfg_param->nActiveMinChnTime;
scan_req.maxChnTime = cfg_param->nActiveMaxChnTime;
wlan_hdd_copy_bssid_scan_request(&scan_req, request);
/* set BSSType to default type */
scan_req.BSSType = eCSR_BSS_TYPE_ANY;
if (MAX_CHANNEL < request->n_channels) {
hdd_warn("No of Scan Channels exceeded limit: %d",
request->n_channels);
request->n_channels = MAX_CHANNEL;
}
if (request->n_channels) {
char chList[(request->n_channels * 5) + 1];
int len;
channelList = qdf_mem_malloc(request->n_channels);
if (NULL == channelList) {
hdd_err("channelList malloc failed channelList");
status = -ENOMEM;
goto free_mem;
}
for (i = 0, len = 0; i < request->n_channels; i++) {
if (cds_is_dsrc_channel(cds_chan_to_freq(
request->channels[i]->hw_value)))
continue;
channelList[num_chan] = request->channels[i]->hw_value;
len += snprintf(chList + len, 5, "%d ", channelList[i]);
num_chan++;
}
hdd_notice("Channel-List: %s", chList);
hdd_notice("No. of Scan Channels: %d", num_chan);
}
if (!num_chan) {
hdd_err("Received zero non-dsrc channels");
status = -EINVAL;
goto free_mem;
}
scan_req.ChannelInfo.numOfChannels = num_chan;
scan_req.ChannelInfo.ChannelList = channelList;
/* set requestType to full scan */
scan_req.requestType = eCSR_SCAN_REQUEST_FULL_SCAN;
/* Flush the scan results(only p2p beacons) for STA scan and P2P
* search (Flush on both full scan and social scan but not on single
* channel scan).P2P search happens on 3 social channels (1, 6, 11)
*/
/* Supplicant does single channel scan after 8-way handshake
* and in that case driver shoudnt flush scan results. If
* driver flushes the scan results here and unfortunately if
* the AP doesnt respond to our probe req then association
* fails which is not desired
*/
if ((request->n_ssids == 1) &&
(request->ssids != NULL) &&
qdf_mem_cmp(&request->ssids[0], "DIRECT-", 7))
is_p2p_scan = true;
if (is_p2p_scan ||
(request->n_channels != WLAN_HDD_P2P_SINGLE_CHANNEL_SCAN)) {
hdd_debug("Flushing P2P Results");
sme_scan_flush_p2p_result(WLAN_HDD_GET_HAL_CTX(pAdapter),
pAdapter->sessionId);
}
if (request->ie_len) {
/* save this for future association (join requires this) */
memset(&pScanInfo->scanAddIE, 0, sizeof(pScanInfo->scanAddIE));
memcpy(pScanInfo->scanAddIE.addIEdata, request->ie,
request->ie_len);
pScanInfo->scanAddIE.length = request->ie_len;
if (wlan_hdd_update_scan_ies(pAdapter, pScanInfo,
pScanInfo->scanAddIE.addIEdata,
(uint8_t *)&pScanInfo->scanAddIE.length))
hdd_err("Update scan IEs with default Scan IEs failed");
if ((QDF_STA_MODE == pAdapter->device_mode) ||
(QDF_P2P_CLIENT_MODE == pAdapter->device_mode) ||
(QDF_P2P_DEVICE_MODE == pAdapter->device_mode)
) {
pwextBuf->roamProfile.pAddIEScan =
pScanInfo->scanAddIE.addIEdata;
pwextBuf->roamProfile.nAddIEScanLength =
pScanInfo->scanAddIE.length;
}
scan_req.uIEFieldLen = pScanInfo->scanAddIE.length;
scan_req.pIEField = pScanInfo->scanAddIE.addIEdata;
pP2pIe = wlan_hdd_get_p2p_ie_ptr((uint8_t *) request->ie,
request->ie_len);
if (pP2pIe != NULL) {
#ifdef WLAN_FEATURE_P2P_DEBUG
if (((global_p2p_connection_status == P2P_GO_NEG_COMPLETED)
|| (global_p2p_connection_status ==
P2P_GO_NEG_PROCESS))
&& (QDF_P2P_CLIENT_MODE == pAdapter->device_mode)) {
global_p2p_connection_status =
P2P_CLIENT_CONNECTING_STATE_1;
hdd_err("[P2P State] Changing state from Go nego completed to Connection is started");
hdd_err("[P2P]P2P Scanning is started for 8way Handshake");
} else
if ((global_p2p_connection_status ==
P2P_CLIENT_DISCONNECTED_STATE)
&& (QDF_P2P_CLIENT_MODE ==
pAdapter->device_mode)) {
global_p2p_connection_status =
P2P_CLIENT_CONNECTING_STATE_2;
hdd_err("[P2P State] Changing state from Disconnected state to Connection is started");
hdd_err("[P2P]P2P Scanning is started for 4way Handshake");
}
#endif
/* no_cck will be set during p2p find to disable 11b rates */
if (request->no_cck) {
hdd_notice("This is a P2P Search");
scan_req.p2pSearch = 1;
if (request->n_channels ==
WLAN_HDD_P2P_SOCIAL_CHANNELS) {
/* set requestType to P2P Discovery */
scan_req.requestType =
eCSR_SCAN_P2P_DISCOVERY;
}
/*
* Skip Dfs Channel in case of P2P Search if it is set in
* ini file
*/
if (cfg_param->skipDfsChnlInP2pSearch) {
scan_req.skipDfsChnlInP2pSearch = 1;
} else {
scan_req.skipDfsChnlInP2pSearch = 0;
}
}
}
} else {
if (pScanInfo->default_scan_ies &&
pScanInfo->default_scan_ies_len) {
qdf_mem_copy(pScanInfo->scanAddIE.addIEdata,
pScanInfo->default_scan_ies,
pScanInfo->default_scan_ies_len);
pScanInfo->scanAddIE.length =
pScanInfo->default_scan_ies_len;
}
}
/* acquire the wakelock to avoid the apps suspend during the scan. To
* address the following issues.
* 1) Disconnected scenario: we are not allowing the suspend as WLAN is not in
* BMPS/IMPS this result in android trying to suspend aggressively and backing off
* for long time, this result in apps running at full power for long time.
* 2) Connected scenario: If we allow the suspend during the scan, RIVA will
* be stuck in full power because of resume BMPS
*/
hdd_prevent_suspend_timeout(HDD_WAKE_LOCK_SCAN_DURATION,
WIFI_POWER_EVENT_WAKELOCK_SCAN);
hdd_info("requestType %d, scanType %d, minChnTime %d, maxChnTime %d,p2pSearch %d, skipDfsChnlIn P2pSearch %d",
scan_req.requestType, scan_req.scanType,
scan_req.minChnTime, scan_req.maxChnTime,
scan_req.p2pSearch, scan_req.skipDfsChnlInP2pSearch);
#if (LINUX_VERSION_CODE > KERNEL_VERSION(3, 7, 0))
if (request->flags & NL80211_SCAN_FLAG_FLUSH)
sme_scan_flush_result(WLAN_HDD_GET_HAL_CTX(pAdapter));
#endif
qdf_runtime_pm_prevent_suspend(pHddCtx->runtime_context.scan);
status = sme_scan_request(WLAN_HDD_GET_HAL_CTX(pAdapter),
pAdapter->sessionId, &scan_req,
&hdd_cfg80211_scan_done_callback, dev);
if (QDF_STATUS_SUCCESS != status) {
hdd_err("sme_scan_request returned error %d", status);
if (QDF_STATUS_E_RESOURCES == status) {
hdd_err("HO is in progress.So defer the scan by informing busy");
status = -EBUSY;
} else {
status = -EIO;
}
qdf_runtime_pm_allow_suspend(pHddCtx->runtime_context.scan);
hdd_allow_suspend(WIFI_POWER_EVENT_WAKELOCK_SCAN);
goto free_mem;
}
wlan_hdd_scan_request_enqueue(pAdapter, request, source,
scan_req.scan_id, scan_req.timestamp);
pAdapter->scan_info.mScanPending = true;
pHddCtx->beacon_probe_rsp_cnt_per_scan = 0;
free_mem:
if (scan_req.SSIDs.SSIDList)
qdf_mem_free(scan_req.SSIDs.SSIDList);
if (channelList)
qdf_mem_free(channelList);
EXIT();
return status;
}
#endif
/**
* wlan_hdd_cfg80211_scan() - API to process cfg80211 scan request
* @wiphy: Pointer to wiphy
* @dev: Pointer to net device
* @request: Pointer to scan request
*
* This API responds to scan trigger and update cfg80211 scan database
* later, scan dump command can be used to recieve scan results
*
* Return: 0 for success, non zero for failure
*/
int wlan_hdd_cfg80211_scan(struct wiphy *wiphy,
struct cfg80211_scan_request *request)
{
int ret;
cds_ssr_protect(__func__);
ret = __wlan_hdd_cfg80211_scan(wiphy,
request, NL_SCAN);
cds_ssr_unprotect(__func__);
return ret;
}
/**
* wlan_hdd_cfg80211_tdls_scan() - API to process cfg80211 scan request
* @wiphy: Pointer to wiphy
* @request: Pointer to scan request
* @source: scan request source(NL/Vendor scan)
*
* This API responds to scan trigger and update cfg80211 scan database
* later, scan dump command can be used to recieve scan results. This
* function gets called when tdls module queues the scan request.
*
* Return: 0 for success, non zero for failure.
*/
int wlan_hdd_cfg80211_tdls_scan(struct wiphy *wiphy,
struct cfg80211_scan_request *request,
uint8_t source)
{
int ret;
cds_ssr_protect(__func__);
ret = __wlan_hdd_cfg80211_scan(wiphy,
request, source);
cds_ssr_unprotect(__func__);
return ret;
}
/**
* wlan_hdd_get_rates() -API to get the rates from scan request
* @wiphy: Pointer to wiphy
* @band: Band
* @rates: array of rates
* @rate_count: number of rates
*
* Return: o for failure, rate bitmap for success
*/
static uint32_t wlan_hdd_get_rates(struct wiphy *wiphy,
enum nl80211_band band,
const u8 *rates, unsigned int rate_count)
{
uint32_t j, count, rate_bitmap = 0;
uint32_t rate;
bool found;
for (count = 0; count < rate_count; count++) {
rate = ((rates[count]) & RATE_MASK) * 5;
found = false;
for (j = 0; j < wiphy->bands[band]->n_bitrates; j++) {
if (wiphy->bands[band]->bitrates[j].bitrate == rate) {
found = true;
rate_bitmap |= (1 << j);
break;
}
}
if (!found)
return 0;
}
return rate_bitmap;
}
/**
* wlan_hdd_send_scan_start_event() -API to send the scan start event
* @wiphy: Pointer to wiphy
* @wdev: Pointer to net device
* @cookie: scan identifier
*
* Return: return 0 on success and negative error code on failure
*/
static int wlan_hdd_send_scan_start_event(struct wiphy *wiphy,
struct wireless_dev *wdev, uint64_t cookie)
{
struct sk_buff *skb;
int ret;
skb = cfg80211_vendor_cmd_alloc_reply_skb(wiphy, sizeof(u64) +
NLA_HDRLEN + NLMSG_HDRLEN);
if (!skb) {
hdd_err(" reply skb alloc failed");
return -ENOMEM;
}
if (hdd_wlan_nla_put_u64(skb, QCA_WLAN_VENDOR_ATTR_SCAN_COOKIE,
cookie)) {
hdd_err("nla put fail");
kfree_skb(skb);
return -EINVAL;
}
ret = cfg80211_vendor_cmd_reply(skb);
/* Send a scan started event to supplicant */
skb = cfg80211_vendor_event_alloc(wiphy, wdev,
sizeof(u64) + 4 + NLMSG_HDRLEN,
QCA_NL80211_VENDOR_SUBCMD_SCAN_INDEX, GFP_KERNEL);
if (!skb) {
hdd_err("skb alloc failed");
return -ENOMEM;
}
if (hdd_wlan_nla_put_u64(skb, QCA_WLAN_VENDOR_ATTR_SCAN_COOKIE,
cookie)) {
kfree_skb(skb);
return -EINVAL;
}
cfg80211_vendor_event(skb, GFP_KERNEL);
return ret;
}
/**
* wlan_hdd_copy_bssid() - API to copy the bssid to vendor Scan request
* @request: Pointer to vendor scan request
* @bssid: Pointer to BSSID
*
* This API copies the specific BSSID received from Supplicant and copies it to
* the vendor Scan request
*
* Return: None
*/
#if defined(CFG80211_SCAN_BSSID) || \
(LINUX_VERSION_CODE >= KERNEL_VERSION(4, 7, 0))
static inline void wlan_hdd_copy_bssid(struct cfg80211_scan_request *request,
uint8_t *bssid)
{
qdf_mem_copy(request->bssid, bssid, QDF_MAC_ADDR_SIZE);
}
#else
static inline void wlan_hdd_copy_bssid(struct cfg80211_scan_request *request,
uint8_t *bssid)
{
}
#endif
static void hdd_process_vendor_acs_response(hdd_adapter_t *adapter)
{
if (test_bit(VENDOR_ACS_RESPONSE_PENDING, &adapter->event_flags)) {
if (QDF_TIMER_STATE_RUNNING ==
qdf_mc_timer_get_current_state(&adapter->sessionCtx.
ap.vendor_acs_timer)) {
qdf_mc_timer_stop(&adapter->sessionCtx.
ap.vendor_acs_timer);
}
}
}
/**
* __wlan_hdd_cfg80211_vendor_scan() - API to process venor scan request
* @wiphy: Pointer to wiphy
* @wdev: Pointer to net device
* @data : Pointer to the data
* @data_len : length of the data
*
* API to process venor scan request.
*
* Return: return 0 on success and negative error code on failure
*/
static int __wlan_hdd_cfg80211_vendor_scan(struct wiphy *wiphy,
struct wireless_dev *wdev, const void *data,
int data_len)
{
struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_SCAN_MAX + 1];
struct cfg80211_scan_request *request = NULL;
struct nlattr *attr;
enum nl80211_band band;
uint8_t n_channels = 0, n_ssid = 0, ie_len = 0;
uint32_t tmp, count, j;
unsigned int len;
struct ieee80211_channel *chan;
hdd_context_t *hdd_ctx = wiphy_priv(wiphy);
hdd_adapter_t *adapter = WLAN_HDD_GET_PRIV_PTR(wdev->netdev);
int ret;
ENTER_DEV(wdev->netdev);
ret = wlan_hdd_validate_context(hdd_ctx);
if (0 != ret)
return ret;
if (nla_parse(tb, QCA_WLAN_VENDOR_ATTR_SCAN_MAX, data,
data_len, NULL)) {
hdd_err("Invalid ATTR");
return -EINVAL;
}
if (tb[QCA_WLAN_VENDOR_ATTR_SCAN_FREQUENCIES]) {
nla_for_each_nested(attr,
tb[QCA_WLAN_VENDOR_ATTR_SCAN_FREQUENCIES], tmp)
n_channels++;
} else {
for (band = 0; band < NUM_NL80211_BANDS; band++)
if (wiphy->bands[band])
n_channels += wiphy->bands[band]->n_channels;
}
if (MAX_CHANNEL < n_channels) {
hdd_err("Exceed max number of channels: %d", n_channels);
return -EINVAL;
}
if (tb[QCA_WLAN_VENDOR_ATTR_SCAN_SSIDS])
nla_for_each_nested(attr,
tb[QCA_WLAN_VENDOR_ATTR_SCAN_SSIDS], tmp)
n_ssid++;
if (MAX_SCAN_SSID < n_ssid) {
hdd_err("Exceed max number of SSID: %d", n_ssid);
return -EINVAL;
}
if (tb[QCA_WLAN_VENDOR_ATTR_SCAN_IE])
ie_len = nla_len(tb[QCA_WLAN_VENDOR_ATTR_SCAN_IE]);
else
ie_len = 0;
len = sizeof(*request) + (sizeof(*request->ssids) * n_ssid) +
(sizeof(*request->channels) * n_channels) + ie_len;
request = qdf_mem_malloc(len);
if (!request)
goto error;
if (n_ssid)
request->ssids = (void *)&request->channels[n_channels];
request->n_ssids = n_ssid;
if (ie_len) {
if (request->ssids)
request->ie = (void *)(request->ssids + n_ssid);
else
request->ie = (void *)(request->channels + n_channels);
}
count = 0;
if (tb[QCA_WLAN_VENDOR_ATTR_SCAN_FREQUENCIES]) {
nla_for_each_nested(attr,
tb[QCA_WLAN_VENDOR_ATTR_SCAN_FREQUENCIES],
tmp) {
chan = __ieee80211_get_channel(wiphy,
nla_get_u32(attr));
if (!chan)
goto error;
if (chan->flags & IEEE80211_CHAN_DISABLED)
continue;
request->channels[count] = chan;
count++;
}
} else {
for (band = 0; band < NUM_NL80211_BANDS; band++) {
if (!wiphy->bands[band])
continue;
for (j = 0; j < wiphy->bands[band]->n_channels;
j++) {
chan = &wiphy->bands[band]->channels[j];
if (chan->flags & IEEE80211_CHAN_DISABLED)
continue;
request->channels[count] = chan;
count++;
}
}
}
if (!count)
goto error;
request->n_channels = count;
count = 0;
if (tb[QCA_WLAN_VENDOR_ATTR_SCAN_SSIDS]) {
nla_for_each_nested(attr, tb[QCA_WLAN_VENDOR_ATTR_SCAN_SSIDS],
tmp) {
request->ssids[count].ssid_len = nla_len(attr);
if (request->ssids[count].ssid_len >
SIR_MAC_MAX_SSID_LENGTH) {
hdd_err("SSID Len %d is not correct for network %d",
request->ssids[count].ssid_len, count);
goto error;
}
memcpy(request->ssids[count].ssid, nla_data(attr),
nla_len(attr));
count++;
}
}
if (tb[QCA_WLAN_VENDOR_ATTR_SCAN_IE]) {
request->ie_len = nla_len(tb[QCA_WLAN_VENDOR_ATTR_SCAN_IE]);
memcpy((void *)request->ie,
nla_data(tb[QCA_WLAN_VENDOR_ATTR_SCAN_IE]),
request->ie_len);
}
for (count = 0; count < NUM_NL80211_BANDS; count++)
if (wiphy->bands[count])
request->rates[count] =
(1 << wiphy->bands[count]->n_bitrates) - 1;
if (tb[QCA_WLAN_VENDOR_ATTR_SCAN_SUPP_RATES]) {
nla_for_each_nested(attr,
tb[QCA_WLAN_VENDOR_ATTR_SCAN_SUPP_RATES],
tmp) {
band = nla_type(attr);
if (band >= NUM_NL80211_BANDS)
continue;
if (!wiphy->bands[band])
continue;
request->rates[band] =
wlan_hdd_get_rates(wiphy,
band, nla_data(attr),
nla_len(attr));
}
}
if (tb[QCA_WLAN_VENDOR_ATTR_SCAN_FLAGS]) {
request->flags =
nla_get_u32(tb[QCA_WLAN_VENDOR_ATTR_SCAN_FLAGS]);
if ((request->flags & NL80211_SCAN_FLAG_LOW_PRIORITY) &&
!(wiphy->features & NL80211_FEATURE_LOW_PRIORITY_SCAN)) {
hdd_err("LOW PRIORITY SCAN not supported");
goto error;
}
}
if (tb[QCA_WLAN_VENDOR_ATTR_SCAN_BSSID]) {
if (nla_len(tb[QCA_WLAN_VENDOR_ATTR_SCAN_BSSID]) <
QDF_MAC_ADDR_SIZE) {
hdd_err("invalid bssid length");
goto error;
}
wlan_hdd_copy_bssid(request,
nla_data(tb[QCA_WLAN_VENDOR_ATTR_SCAN_BSSID]));
}
/* Check if external acs was requested on this adapter */
hdd_process_vendor_acs_response(adapter);
request->no_cck =
nla_get_flag(tb[QCA_WLAN_VENDOR_ATTR_SCAN_TX_NO_CCK_RATE]);
request->wdev = wdev;
request->wiphy = wiphy;
request->scan_start = jiffies;
if (0 != __wlan_hdd_cfg80211_scan(wiphy, request, VENDOR_SCAN))
goto error;
ret = wlan_hdd_send_scan_start_event(wiphy, wdev, (uintptr_t)request);
return ret;
error:
hdd_err("Scan Request Failed");
qdf_mem_free(request);
return -EINVAL;
}
/**
* wlan_hdd_cfg80211_vendor_scan() -API to process venor scan request
* @wiphy: Pointer to wiphy
* @dev: Pointer to net device
* @data : Pointer to the data
* @data_len : length of the data
*
* This is called from userspace to request scan.
*
* Return: Return the Success or Failure code.
*/
int wlan_hdd_cfg80211_vendor_scan(struct wiphy *wiphy,
struct wireless_dev *wdev, const void *data,
int data_len)
{
int ret;
cds_ssr_protect(__func__);
ret = __wlan_hdd_cfg80211_vendor_scan(wiphy, wdev,
data, data_len);
cds_ssr_unprotect(__func__);
return ret;
}
/**
* wlan_hdd_get_scanid() - API to get the scan id
* from the scan cookie attribute.
* @hdd_ctx: Pointer to HDD context
* @scan_id: Pointer to scan id
* @cookie : Scan cookie attribute
*
* API to get the scan id from the scan cookie attribute
* sent from supplicant by matching scan request.
*
* Return: 0 for success, non zero for failure
*/
static int wlan_hdd_get_scanid(hdd_context_t *hdd_ctx,
uint32_t *scan_id, uint64_t cookie)
{
struct hdd_scan_req *scan_req;
qdf_list_node_t *node = NULL;
qdf_list_node_t *ptr_node = NULL;
int ret = -EINVAL;
qdf_spin_lock(&hdd_ctx->hdd_scan_req_q_lock);
if (qdf_list_empty(&hdd_ctx->hdd_scan_req_q)) {
qdf_spin_unlock(&hdd_ctx->hdd_scan_req_q_lock);
return ret;
}
if (QDF_STATUS_SUCCESS !=
qdf_list_peek_front(&hdd_ctx->hdd_scan_req_q,
&ptr_node)) {
qdf_spin_unlock(&hdd_ctx->hdd_scan_req_q_lock);
return ret;
}
do {
node = ptr_node;
scan_req = container_of(node, struct hdd_scan_req, node);
if (cookie ==
(uintptr_t)(scan_req->scan_request)) {
*scan_id = scan_req->scan_id;
ret = 0;
break;
}
} while (QDF_STATUS_SUCCESS ==
qdf_list_peek_next(&hdd_ctx->hdd_scan_req_q,
node, &ptr_node));
qdf_spin_unlock(&hdd_ctx->hdd_scan_req_q_lock);
return ret;
}
/**
* __wlan_hdd_vendor_abort_scan() - API to process vendor command for
* abort scan
* @wiphy: Pointer to wiphy
* @wdev: Pointer to net device
* @data : Pointer to the data
* @data_len : length of the data
*
* API to process vendor abort scan
*
* Return: zero for success and non zero for failure
*/
static int __wlan_hdd_vendor_abort_scan(
struct wiphy *wiphy, const void *data,
int data_len)
{
hdd_context_t *hdd_ctx = wiphy_priv(wiphy);
struct nlattr *tb[QCA_WLAN_VENDOR_ATTR_SCAN_MAX + 1];
uint32_t scan_id;
uint64_t cookie;
int ret;
if (QDF_GLOBAL_FTM_MODE == hdd_get_conparam()) {
hdd_err("Command not allowed in FTM mode");
return -EINVAL;
}
ret = wlan_hdd_validate_context(hdd_ctx);
if (0 != ret)
return ret;
ret = -EINVAL;
if (nla_parse(tb, QCA_WLAN_VENDOR_ATTR_SCAN_MAX, data,
data_len, NULL)) {
hdd_err("Invalid ATTR");
return ret;
}
if (tb[QCA_WLAN_VENDOR_ATTR_SCAN_COOKIE]) {
cookie = nla_get_u64(
tb[QCA_WLAN_VENDOR_ATTR_SCAN_COOKIE]);
ret = wlan_hdd_get_scanid(hdd_ctx,
&scan_id,
cookie);
if (ret != 0)
return ret;
hdd_abort_mac_scan(hdd_ctx,
HDD_SESSION_ID_INVALID,
scan_id,
eCSR_SCAN_ABORT_DEFAULT);
}
return ret;
}
/**
* wlan_hdd_vendor_abort_scan() - API to process vendor command for
* abort scan
* @wiphy: Pointer to wiphy
* @wdev: Pointer to net device
* @data : Pointer to the data
* @data_len : length of the data
*
* This is called from supplicant to abort scan
*
* Return: zero for success and non zero for failure
*/
int wlan_hdd_vendor_abort_scan(
struct wiphy *wiphy, struct wireless_dev *wdev,
const void *data, int data_len)
{
int ret;
cds_ssr_protect(__func__);
ret = __wlan_hdd_vendor_abort_scan(wiphy,
data,
data_len);
cds_ssr_unprotect(__func__);
return ret;
}
/**
* wlan_hdd_scan_abort() - abort ongoing scan
* @pAdapter: Pointer to interface adapter
*
* Return: 0 for success, non zero for failure
*/
int wlan_hdd_scan_abort(hdd_adapter_t *pAdapter)
{
hdd_context_t *pHddCtx = WLAN_HDD_GET_CTX(pAdapter);
hdd_scaninfo_t *pScanInfo = NULL;
unsigned long rc;
pScanInfo = &pAdapter->scan_info;
if (pScanInfo->mScanPending) {
INIT_COMPLETION(pScanInfo->abortscan_event_var);
hdd_abort_mac_scan(pHddCtx, pAdapter->sessionId,
INVALID_SCAN_ID, eCSR_SCAN_ABORT_DEFAULT);
rc = wait_for_completion_timeout(
&pScanInfo->abortscan_event_var,
msecs_to_jiffies(5000));
if (!rc) {
hdd_err("Timeout occurred while waiting for abort scan");
return -ETIME;
}
}
return 0;
}
#ifdef FEATURE_WLAN_SCAN_PNO
/**
* hdd_sched_scan_callback - scheduled scan callback
* @callbackContext: Callback context
* @pPrefNetworkFoundInd: Preferred network found indication
*
* This is a callback function that is registerd with SME that is
* invoked when a preferred network is discovered by firmware.
*
* Return: none
*/
static void
hdd_sched_scan_callback(void *callbackContext,
tSirPrefNetworkFoundInd *pPrefNetworkFoundInd)
{
int ret;
hdd_adapter_t *pAdapter = (hdd_adapter_t *) callbackContext;
hdd_context_t *pHddCtx;
ENTER();
if (NULL == pAdapter) {
hdd_err("HDD adapter is Null");
return;
}
pHddCtx = WLAN_HDD_GET_CTX(pAdapter);
if (NULL == pHddCtx) {
hdd_err("HDD context is Null!!!");
return;
}
qdf_spin_lock(&pHddCtx->sched_scan_lock);
if (true == pHddCtx->isWiphySuspended) {
pHddCtx->isSchedScanUpdatePending = true;
qdf_spin_unlock(&pHddCtx->sched_scan_lock);
hdd_notice("Update cfg80211 scan database after it resume");
return;
}
qdf_spin_unlock(&pHddCtx->sched_scan_lock);
ret = wlan_hdd_cfg80211_update_bss(pHddCtx->wiphy, pAdapter, 0);
if (0 > ret)
hdd_notice("NO SCAN result");
cfg80211_sched_scan_results(pHddCtx->wiphy);
hdd_notice("cfg80211 scan result database updated");
}
/**
* wlan_hdd_is_pno_allowed() - Check if PNO is allowed
* @adapter: HDD Device Adapter
*
* The PNO Start request is coming from upper layers.
* It is to be allowed only for Infra STA device type
* and the link should be in a disconnected state.
*
* Return: Success if PNO is allowed, Failure otherwise.
*/
static QDF_STATUS wlan_hdd_is_pno_allowed(hdd_adapter_t *adapter)
{
hdd_notice("dev_mode=%d, conn_state=%d, session ID=%d",
adapter->device_mode,
adapter->sessionCtx.station.conn_info.connState,
adapter->sessionId);
if ((adapter->device_mode == QDF_STA_MODE) &&
(eConnectionState_NotConnected ==
adapter->sessionCtx.station.conn_info.connState))
return QDF_STATUS_SUCCESS;
else
return QDF_STATUS_E_FAILURE;
}
#if ((LINUX_VERSION_CODE >= KERNEL_VERSION(4, 4, 0)) || \
defined(CFG80211_MULTI_SCAN_PLAN_BACKPORT)) && \
defined(FEATURE_WLAN_SCAN_PNO)
/**
* hdd_config_sched_scan_plan() - configures the sched scan plans
* from the framework.
* @pno_req: pointer to PNO scan request
* @request: pointer to scan request from framework
*
* Return: None
*/
static void hdd_config_sched_scan_plan(tpSirPNOScanReq pno_req,
struct cfg80211_sched_scan_request *request,
hdd_context_t *hdd_ctx)
{
/*
* As of now max 2 scan plans were supported by firmware
* if number of scan plan supported by firmware increased below logic
* must change.
*/
if (request->n_scan_plans == SIR_PNO_MAX_PLAN_REQUEST) {
pno_req->fast_scan_period =
request->scan_plans[0].interval * MSEC_PER_SEC;
pno_req->fast_scan_max_cycles =
request->scan_plans[0].iterations;
pno_req->slow_scan_period =
request->scan_plans[1].interval * MSEC_PER_SEC;
hdd_notice("Base scan interval: %d sec, scan cycles: %d, slow scan interval %d",
request->scan_plans[0].interval,
request->scan_plans[0].iterations,
request->scan_plans[1].interval);
} else if (request->n_scan_plans == 1) {
pno_req->fast_scan_period =
request->scan_plans[0].interval * MSEC_PER_SEC;
/*
* if only one scan plan is configured from framework
* then both fast and slow scan should be configured with the
* same value that is why fast scan cycles are hardcoded to one
*/
pno_req->fast_scan_max_cycles = 1;
pno_req->slow_scan_period =
request->scan_plans[0].interval * MSEC_PER_SEC;
} else {
hdd_err("Invalid number of scan plans %d !!",
request->n_scan_plans);
}
}
#else
static void hdd_config_sched_scan_plan(tpSirPNOScanReq pno_req,
struct cfg80211_sched_scan_request *request,
hdd_context_t *hdd_ctx)
{
pno_req->fast_scan_period = request->interval;
pno_req->fast_scan_max_cycles =
hdd_ctx->config->configPNOScanTimerRepeatValue;
pno_req->slow_scan_period =
hdd_ctx->config->pno_slow_scan_multiplier *
pno_req->fast_scan_period;
hdd_notice("Base scan interval: %d sec PNOScanTimerRepeatValue: %d",
(request->interval / 1000),
hdd_ctx->config->configPNOScanTimerRepeatValue);
}
#endif
/**
* __wlan_hdd_cfg80211_sched_scan_start() - cfg80211 scheduled scan(pno) start
* @wiphy: Pointer to wiphy
* @dev: Pointer network device
* @request: Pointer to cfg80211 scheduled scan start request
*
* Return: 0 for success, non zero for failure
*/
static int __wlan_hdd_cfg80211_sched_scan_start(struct wiphy *wiphy,
struct net_device *dev,
struct
cfg80211_sched_scan_request
*request)
{
hdd_adapter_t *pAdapter = WLAN_HDD_GET_PRIV_PTR(dev);
tpSirPNOScanReq pPnoRequest = NULL;
hdd_context_t *pHddCtx;
tHalHandle hHal;
uint32_t i, indx, num_ch, j;
u8 valid_ch[WNI_CFG_VALID_CHANNEL_LIST_LEN] = { 0 };
u8 channels_allowed[WNI_CFG_VALID_CHANNEL_LIST_LEN] = { 0 };
uint32_t num_channels_allowed = WNI_CFG_VALID_CHANNEL_LIST_LEN;
QDF_STATUS status = QDF_STATUS_E_FAILURE;
int ret = 0;
hdd_scaninfo_t *pScanInfo = &pAdapter->scan_info;
struct hdd_config *config = NULL;
uint32_t num_ignore_dfs_ch = 0;
ENTER();
if (QDF_GLOBAL_FTM_MODE == hdd_get_conparam()) {
hdd_err("Command not allowed in FTM mode");
return -EINVAL;
}
if (wlan_hdd_validate_session_id(pAdapter->sessionId)) {
hdd_err("invalid session id: %d", pAdapter->sessionId);
return -EINVAL;
}
pHddCtx = WLAN_HDD_GET_CTX(pAdapter);
ret = wlan_hdd_validate_context(pHddCtx);
if (0 != ret)
return ret;
if (!sme_is_session_id_valid(pHddCtx->hHal, pAdapter->sessionId))
return -EINVAL;
config = pHddCtx->config;
hHal = WLAN_HDD_GET_HAL_CTX(pAdapter);
if (NULL == hHal) {
hdd_err("HAL context is Null!!!");
return -EINVAL;
}
if ((QDF_STA_MODE == pAdapter->device_mode) &&
(eConnectionState_Connecting ==
(WLAN_HDD_GET_STATION_CTX_PTR(pAdapter))->conn_info.connState)) {
hdd_err("%p(%d) Connection in progress: sched_scan_start denied (EBUSY)",
WLAN_HDD_GET_STATION_CTX_PTR(pAdapter),
pAdapter->sessionId);
return -EBUSY;
}
MTRACE(qdf_trace(QDF_MODULE_ID_HDD,
TRACE_CODE_HDD_CFG80211_SCHED_SCAN_START,
pAdapter->sessionId, pAdapter->device_mode));
/*
* The current umac is unable to handle the SCAN_PREEMPT and SCAN_DEQUEUED
* so its necessary to terminate the existing scan which is already issued
* otherwise the host won't enter into the suspend state due to the reason
* that the wlan wakelock which was held in the wlan_hdd_cfg80211_scan
* function.
*/
sme_scan_flush_result(hHal);
if (true == pScanInfo->mScanPending) {
ret = wlan_hdd_scan_abort(pAdapter);
if (ret < 0) {
hdd_err("aborting the existing scan is unsuccessful");
return -EBUSY;
}
}
if (QDF_STATUS_E_FAILURE == wlan_hdd_is_pno_allowed(pAdapter)) {
hdd_err("pno is not allowed");
return -ENOTSUPP;
}
pPnoRequest = (tpSirPNOScanReq) qdf_mem_malloc(sizeof(tSirPNOScanReq));
if (NULL == pPnoRequest) {
hdd_err("qdf_mem_malloc failed");
return -ENOMEM;
}
pPnoRequest->enable = 1; /*Enable PNO */
pPnoRequest->ucNetworksCount = request->n_match_sets;
if ((!pPnoRequest->ucNetworksCount) ||
(pPnoRequest->ucNetworksCount > SIR_PNO_MAX_SUPP_NETWORKS)) {
hdd_err("Network input is not correct %d",
pPnoRequest->ucNetworksCount);
ret = -EINVAL;
goto error;
}
if (SIR_PNO_MAX_NETW_CHANNELS_EX < request->n_channels) {
hdd_err("Incorrect number of channels %d",
request->n_channels);
ret = -EINVAL;
goto error;
}
/*
* Framework provides one set of channels(all)
* common for all saved profile
*/
if (0 != sme_cfg_get_str(hHal, WNI_CFG_VALID_CHANNEL_LIST,
channels_allowed, &num_channels_allowed)) {
hdd_err("failed to get valid channel list");
ret = -EINVAL;
goto error;
}
/* Checking each channel against allowed channel list */
num_ch = 0;
if (request->n_channels) {
char chList[(request->n_channels * 5) + 1];
int len;
for (i = 0, len = 0; i < request->n_channels; i++) {
for (indx = 0; indx < num_channels_allowed; indx++) {
if (request->channels[i]->hw_value ==
channels_allowed[indx]) {
if ((!config->enable_dfs_pno_chnl_scan)
&& (CHANNEL_STATE_DFS ==
cds_get_channel_state(
channels_allowed[indx]))) {
hdd_notice("Dropping DFS channel : %d",
channels_allowed[indx]);
num_ignore_dfs_ch++;
break;
}
if (!cds_is_dsrc_channel(
cds_chan_to_freq(
request->channels[i]->hw_value))) {
valid_ch[num_ch++] = request->
channels[i]->hw_value;
len += snprintf(chList + len,
5, "%d ",
request->channels[i]->
hw_value);
}
break;
}
}
}
hdd_notice("Channel-List: %s ", chList);
/* If all channels are DFS and dropped,
* then ignore the PNO request
*/
if (!num_ch) {
hdd_notice("Channel list empty due to filtering of DSRC,DFS channels");
ret = -EINVAL;
goto error;
}
}
/* Filling per profile params */
for (i = 0; i < pPnoRequest->ucNetworksCount; i++) {
pPnoRequest->aNetworks[i].ssId.length =
request->match_sets[i].ssid.ssid_len;
if ((0 == pPnoRequest->aNetworks[i].ssId.length) ||
(pPnoRequest->aNetworks[i].ssId.length > 32)) {
hdd_err(" SSID Len %d is not correct for network %d",
pPnoRequest->aNetworks[i].ssId.length, i);
ret = -EINVAL;
goto error;
}
memcpy(pPnoRequest->aNetworks[i].ssId.ssId,
request->match_sets[i].ssid.ssid,
request->match_sets[i].ssid.ssid_len);
pPnoRequest->aNetworks[i].authentication = 0; /*eAUTH_TYPE_ANY */
pPnoRequest->aNetworks[i].encryption = 0; /*eED_ANY */
pPnoRequest->aNetworks[i].bcastNetwType = 0; /*eBCAST_UNKNOWN */
/*Copying list of valid channel into request */
memcpy(pPnoRequest->aNetworks[i].aChannels, valid_ch, num_ch);
pPnoRequest->aNetworks[i].ucChannelCount = num_ch;
pPnoRequest->aNetworks[i].rssiThreshold =
request->match_sets[i].rssi_thold;
}
for (i = 0; i < request->n_ssids; i++) {
j = 0;
while (j < pPnoRequest->ucNetworksCount) {
if ((pPnoRequest->aNetworks[j].ssId.length ==
request->ssids[i].ssid_len) &&
(0 == memcmp(pPnoRequest->aNetworks[j].ssId.ssId,
request->ssids[i].ssid,
pPnoRequest->aNetworks[j].ssId.
length))) {
pPnoRequest->aNetworks[j].bcastNetwType =
eBCAST_HIDDEN;
break;
}
j++;
}
}
hdd_notice("Number of hidden networks being Configured = %d",
request->n_ssids);
/*
* Before Kernel 4.4
* Driver gets only one time interval which is hard coded in
* supplicant for 10000ms.
*
* After Kernel 4.4
* User can configure multiple scan_plans, each scan would have
* separate scan cycle and interval. (interval is in unit of second.)
* For our use case, we would only have supplicant set one scan_plan,
* and firmware also support only one as well, so pick up the first
* index.
*
* Taking power consumption into account
* firmware after gPNOScanTimerRepeatValue times fast_scan_period
* switches slow_scan_period. This is less frequent scans and firmware
* shall be in slow_scan_period mode until next PNO Start.
*/
hdd_config_sched_scan_plan(pPnoRequest, request, pHddCtx);
hdd_info("Base scan interval: %d sec PNOScanTimerRepeatValue: %d",
(pPnoRequest->fast_scan_period / 1000),
config->configPNOScanTimerRepeatValue);
pPnoRequest->modePNO = SIR_PNO_MODE_IMMEDIATE;
hdd_info("SessionId %d, enable %d, modePNO %d",
pAdapter->sessionId, pPnoRequest->enable, pPnoRequest->modePNO);
status = sme_set_preferred_network_list(WLAN_HDD_GET_HAL_CTX(pAdapter),
pPnoRequest,
pAdapter->sessionId,
hdd_sched_scan_callback,
pAdapter);
if (QDF_STATUS_SUCCESS != status) {
hdd_err("Failed to enable PNO");
ret = -EINVAL;
goto error;
}
hdd_notice("PNO scanRequest offloaded");
error:
qdf_mem_free(pPnoRequest);
EXIT();
return ret;
}
/**
* wlan_hdd_cfg80211_sched_scan_start() - cfg80211 scheduled scan(pno) start
* @wiphy: Pointer to wiphy
* @dev: Pointer network device
* @request: Pointer to cfg80211 scheduled scan start request
*
* Return: 0 for success, non zero for failure
*/
int wlan_hdd_cfg80211_sched_scan_start(struct wiphy *wiphy,
struct net_device *dev,
struct cfg80211_sched_scan_request
*request)
{
int ret;
cds_ssr_protect(__func__);
ret = __wlan_hdd_cfg80211_sched_scan_start(wiphy, dev, request);
cds_ssr_unprotect(__func__);
return ret;
}
int wlan_hdd_sched_scan_stop(struct net_device *dev)
{
QDF_STATUS status;
hdd_adapter_t *adapter = WLAN_HDD_GET_PRIV_PTR(dev);
hdd_context_t *hdd_ctx;
tHalHandle hHal;
tSirPNOScanReq *pno_req = NULL;
int ret = 0;
ENTER_DEV(dev);
if (QDF_GLOBAL_FTM_MODE == hdd_get_conparam()) {
hdd_err("Command not allowed in FTM mode");
ret = -EINVAL;
goto exit;
}
if (wlan_hdd_validate_session_id(adapter->sessionId)) {
hdd_err("invalid session id: %d", adapter->sessionId);
ret = -EINVAL;
goto exit;
}
hdd_ctx = WLAN_HDD_GET_CTX(adapter);
if (NULL == hdd_ctx) {
hdd_err("HDD context is Null");
ret = -ENODEV;
goto exit;
}
hHal = WLAN_HDD_GET_HAL_CTX(adapter);
if (NULL == hHal) {
hdd_err(" HAL context is Null!!!");
ret = -EINVAL;
goto exit;
}
pno_req = (tpSirPNOScanReq) qdf_mem_malloc(sizeof(tSirPNOScanReq));
if (NULL == pno_req) {
hdd_err("qdf_mem_malloc failed");
ret = -ENOMEM;
goto exit;
}
MTRACE(qdf_trace(QDF_MODULE_ID_HDD,
TRACE_CODE_HDD_CFG80211_SCHED_SCAN_STOP,
adapter->sessionId, adapter->device_mode));
/* Disable PNO */
pno_req->enable = 0;
pno_req->ucNetworksCount = 0;
status = sme_set_preferred_network_list(hHal, pno_req,
adapter->sessionId,
NULL, adapter);
qdf_mem_free(pno_req);
if (QDF_STATUS_SUCCESS != status) {
hdd_err("Failed to disabled PNO");
ret = -EINVAL;
goto exit;
}
hdd_notice("PNO scan disabled");
exit:
EXIT();
return ret;
}
/**
* __wlan_hdd_cfg80211_sched_scan_stop() - stop cfg80211 scheduled scan(pno)
* @dev: Pointer network device
*
* This is a wrapper around wlan_hdd_sched_scan_stop() that returns success
* in the event that the driver is currently recovering or unloading. This
* prevents a race condition where we get a scan stop from kernel during
* a driver unload from PLD.
*
* Return: 0 for success, non zero for failure
*/
static int __wlan_hdd_cfg80211_sched_scan_stop(struct net_device *dev)
{
int err;
ENTER_DEV(dev);
/* The return 0 is intentional when Recovery and Load/Unload in
* progress. We did observe a crash due to a return of
* failure in sched_scan_stop , especially for a case where the unload
* of the happens at the same time. The function __cfg80211_stop_sched_scan
* was clearing rdev->sched_scan_req only when the sched_scan_stop returns
* success. If it returns a failure , then its next invocation due to the
* clean up of the second interface will have the dev pointer corresponding
* to the first one leading to a crash.
*/
if (cds_is_driver_recovering()) {
hdd_err("Recovery in Progress. State: 0x%x Ignore!!!",
cds_get_driver_state());
return 0;
}
if (cds_is_load_or_unload_in_progress()) {
hdd_err("Unload/Load in Progress, state: 0x%x. Ignore!!!",
cds_get_driver_state());
return 0;
}
err = wlan_hdd_sched_scan_stop(dev);
EXIT();
return err;
}
int wlan_hdd_cfg80211_sched_scan_stop(struct wiphy *wiphy,
struct net_device *dev)
{
int ret;
cds_ssr_protect(__func__);
ret = __wlan_hdd_cfg80211_sched_scan_stop(dev);
cds_ssr_unprotect(__func__);
return ret;
}
#endif /*FEATURE_WLAN_SCAN_PNO */
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0)) || \
defined(CFG80211_ABORT_SCAN)
/**
* __wlan_hdd_cfg80211_abort_scan() - cfg80211 abort scan api
* @wiphy: Pointer to wiphy
* @wdev: Pointer to wireless device structure
*
* This function is used to abort an ongoing scan
*
* Return: None
*/
static void __wlan_hdd_cfg80211_abort_scan(struct wiphy *wiphy,
struct wireless_dev *wdev)
{
struct net_device *dev = wdev->netdev;
hdd_adapter_t *adapter = WLAN_HDD_GET_PRIV_PTR(dev);
hdd_context_t *hdd_ctx = wiphy_priv(wiphy);
int ret;
ENTER_DEV(dev);
if (QDF_GLOBAL_FTM_MODE == hdd_get_conparam()) {
hdd_err("Command not allowed in FTM mode");
return;
}
if (wlan_hdd_validate_session_id(adapter->sessionId)) {
hdd_err("invalid session id: %d", adapter->sessionId);
return;
}
ret = wlan_hdd_validate_context(hdd_ctx);
if (!ret)
return;
wlan_hdd_scan_abort(adapter);
EXIT();
}
/**
* wlan_hdd_cfg80211_abort_scan - cfg80211 abort scan api
* @wiphy: Pointer to wiphy
* @wdev: Pointer to wireless device structure
*
* Wrapper to __wlan_hdd_cfg80211_abort_scan() -
* function is used to abort an ongoing scan
*
* Return: None
*/
void wlan_hdd_cfg80211_abort_scan(struct wiphy *wiphy,
struct wireless_dev *wdev)
{
cds_ssr_protect(__func__);
__wlan_hdd_cfg80211_abort_scan(wiphy, wdev);
cds_ssr_unprotect(__func__);
}
#endif
/**
* hdd_cleanup_scan_queue() - remove entries in scan queue
*
* Removes entries in scan queue and sends scan complete event to NL
* Return: None
*/
void hdd_cleanup_scan_queue(hdd_context_t *hdd_ctx)
{
struct hdd_scan_req *hdd_scan_req;
qdf_list_node_t *node = NULL;
struct cfg80211_scan_request *req;
hdd_adapter_t *adapter;
uint8_t source;
bool aborted = true;
if (NULL == hdd_ctx) {
hdd_err("HDD context is Null");
return;
}
qdf_spin_lock(&hdd_ctx->hdd_scan_req_q_lock);
while (!qdf_list_empty(&hdd_ctx->hdd_scan_req_q)) {
if (QDF_STATUS_SUCCESS !=
qdf_list_remove_front(&hdd_ctx->hdd_scan_req_q,
&node)) {
qdf_spin_unlock(&hdd_ctx->hdd_scan_req_q_lock);
hdd_err("Failed to remove scan request");
return;
}
qdf_spin_unlock(&hdd_ctx->hdd_scan_req_q_lock);
hdd_scan_req = (struct hdd_scan_req *)node;
req = hdd_scan_req->scan_request;
source = hdd_scan_req->source;
adapter = hdd_scan_req->adapter;
if (WLAN_HDD_ADAPTER_MAGIC != adapter->magic) {
hdd_err("HDD adapter magic is invalid");
} else {
if (NL_SCAN == source)
hdd_cfg80211_scan_done(adapter, req, aborted);
else
hdd_vendor_scan_callback(adapter, req, aborted);
hdd_info("removed Scan id: %d, req = %p",
hdd_scan_req->scan_id, req);
}
qdf_mem_free(hdd_scan_req);
qdf_spin_lock(&hdd_ctx->hdd_scan_req_q_lock);
}
qdf_spin_unlock(&hdd_ctx->hdd_scan_req_q_lock);
return;
}
/**
* hdd_scan_context_destroy() - Destroy scan context
* @hdd_ctx: HDD context.
*
* Destroy scan context.
*
* Return: None.
*/
void hdd_scan_context_destroy(hdd_context_t *hdd_ctx)
{
qdf_list_destroy(&hdd_ctx->hdd_scan_req_q);
qdf_spinlock_destroy(&hdd_ctx->sched_scan_lock);
}
/**
* hdd_scan_context_init() - Initialize scan context
* @hdd_ctx: HDD context.
*
* Initialize scan related resources like spin lock and lists.
*
* Return: 0 on success and errno on failure.
*/
int hdd_scan_context_init(hdd_context_t *hdd_ctx)
{
qdf_spinlock_create(&hdd_ctx->sched_scan_lock);
qdf_spinlock_create(&hdd_ctx->hdd_scan_req_q_lock);
qdf_list_create(&hdd_ctx->hdd_scan_req_q, CFG_MAX_SCAN_COUNT_MAX);
return 0;
}