blob: b2bdcc8e7b426ee235cf2ca42d7fb1a1796ad7a6 [file] [log] [blame]
/*
* Broadcom Dongle Host Driver (DHD)
* Prefered Network Offload and Wi-Fi Location Service(WLS) code.
*
* Copyright (C) 1999-2014, Broadcom Corporation
*
* Unless you and Broadcom execute a separate written software license
* agreement governing use of this software, this software is licensed to you
* under the terms of the GNU General Public License version 2 (the "GPL"),
* available at http://www.broadcom.com/licenses/GPLv2.php, with the
* following added to such license:
*
* As a special exception, the copyright holders of this software give you
* permission to link this software with independent modules, and to copy and
* distribute the resulting executable under terms of your choice, provided that
* you also meet, for each linked independent module, the terms and conditions of
* the license of that module. An independent module is a module which is not
* derived from this software. The special exception does not apply to any
* modifications of the software.
*
* Notwithstanding the above, under no circumstances may you combine this
* software in any way with any other Broadcom software provided under a license
* other than the GPL, without Broadcom's express prior written consent.
*
* $Id: dhd_pno.c 423669 2013-09-18 13:01:55Z yangj$
*/
#ifdef PNO_SUPPORT
#include <typedefs.h>
#include <osl.h>
#include <epivers.h>
#include <bcmutils.h>
#include <bcmendian.h>
#include <linuxver.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/sort.h>
#include <dngl_stats.h>
#include <wlioctl.h>
#include <proto/bcmevent.h>
#include <dhd.h>
#include <dhd_pno.h>
#include <dhd_dbg.h>
#ifdef GSCAN_SUPPORT
#include <linux/gcd.h>
#endif /* GSCAN_SUPPORT */
#ifdef __BIG_ENDIAN
#include <bcmendian.h>
#define htod32(i) (bcmswap32(i))
#define htod16(i) (bcmswap16(i))
#define dtoh32(i) (bcmswap32(i))
#define dtoh16(i) (bcmswap16(i))
#define htodchanspec(i) htod16(i)
#define dtohchanspec(i) dtoh16(i)
#else
#define htod32(i) (i)
#define htod16(i) (i)
#define dtoh32(i) (i)
#define dtoh16(i) (i)
#define htodchanspec(i) (i)
#define dtohchanspec(i) (i)
#endif /* IL_BIGENDINA */
#define NULL_CHECK(p, s, err) \
do { \
if (!(p)) { \
printf("NULL POINTER (%s) : %s\n", __FUNCTION__, (s)); \
err = BCME_ERROR; \
return err; \
} \
} while (0)
#define PNO_GET_PNOSTATE(dhd) ((dhd_pno_status_info_t *)dhd->pno_state)
#define PNO_BESTNET_LEN 2048
#define PNO_ON 1
#define PNO_OFF 0
#define CHANNEL_2G_MAX 14
#define CHANNEL_5G_MAX 165
#define MAX_NODE_CNT 5
#define WLS_SUPPORTED(pno_state) (pno_state->wls_supported == TRUE)
#define TIME_DIFF(timestamp1, timestamp2) (abs((uint32)(timestamp1/1000) \
- (uint32)(timestamp2/1000)))
#define TIME_DIFF_MS(timestamp1, timestamp2) (abs((uint32)(timestamp1) \
- (uint32)(timestamp2)))
#define TIMESPEC_TO_US(ts) (((uint64)(ts).tv_sec * USEC_PER_SEC) + \
(ts).tv_nsec / NSEC_PER_USEC)
#define ENTRY_OVERHEAD strlen("bssid=\nssid=\nfreq=\nlevel=\nage=\ndist=\ndistSd=\n====")
#define TIME_MIN_DIFF 5
static wlc_ssid_ext_t * dhd_pno_get_legacy_pno_ssid(dhd_pub_t *dhd,
dhd_pno_status_info_t *pno_state);
#ifdef GSCAN_SUPPORT
static wl_pfn_gscan_ch_bucket_cfg_t *
dhd_pno_gscan_create_channel_list(dhd_pub_t *dhd, dhd_pno_status_info_t *pno_state,
uint16 *chan_list, uint32 *num_buckets, uint32 *num_buckets_to_fw);
#endif /* GSCAN_SUPPORT */
static inline bool
is_dfs(uint16 channel)
{
if (channel >= 52 && channel <= 64) /* class 2 */
return TRUE;
else if (channel >= 100 && channel <= 144) /* class 4 */
return TRUE;
else
return FALSE;
}
int
dhd_pno_clean(dhd_pub_t *dhd)
{
int pfn = 0;
int err;
dhd_pno_status_info_t *_pno_state;
NULL_CHECK(dhd, "dhd is NULL", err);
NULL_CHECK(dhd->pno_state, "pno_state is NULL", err);
_pno_state = PNO_GET_PNOSTATE(dhd);
DHD_PNO(("%s enter\n", __FUNCTION__));
/* Disable PNO */
err = dhd_iovar(dhd, 0, "pfn", (char *)&pfn, sizeof(pfn), 1);
if (err < 0) {
DHD_ERROR(("%s : failed to execute pfn(error : %d)\n",
__FUNCTION__, err));
goto exit;
}
_pno_state->pno_status = DHD_PNO_DISABLED;
err = dhd_iovar(dhd, 0, "pfnclear", NULL, 0, 1);
if (err < 0) {
DHD_ERROR(("%s : failed to execute pfnclear(error : %d)\n",
__FUNCTION__, err));
}
exit:
return err;
}
bool dhd_is_pno_supported(dhd_pub_t *dhd)
{
dhd_pno_status_info_t *_pno_state;
if (!dhd || !dhd->pno_state) {
DHD_ERROR(("NULL POINTER : %s\n",
__FUNCTION__));
return FALSE;
}
_pno_state = PNO_GET_PNOSTATE(dhd);
return WLS_SUPPORTED(_pno_state);
}
bool dhd_is_legacy_pno_enabled(dhd_pub_t *dhd)
{
dhd_pno_status_info_t *_pno_state;
if (!dhd || !dhd->pno_state) {
DHD_ERROR(("NULL POINTER : %s\n",
__FUNCTION__));
return FALSE;
}
_pno_state = PNO_GET_PNOSTATE(dhd);
return ((_pno_state->pno_mode & DHD_PNO_LEGACY_MODE) != 0);
}
#ifdef GSCAN_SUPPORT
static uint64 convert_fw_rel_time_to_systime(struct timespec *ts, uint32 fw_ts_ms)
{
return ((uint64)(TIMESPEC_TO_US(*ts)) - (uint64)(fw_ts_ms * 1000));
}
static void
dhd_pno_idx_to_ssid(struct dhd_pno_gscan_params *gscan_params,
dhd_epno_results_t *res, uint32 idx)
{
dhd_epno_params_t *iter, *next;
if (gscan_params->num_epno_ssid > 0) {
list_for_each_entry_safe(iter, next,
&gscan_params->epno_ssid_list, list) {
if (iter->index == idx) {
memcpy(res->ssid, iter->ssid, iter->ssid_len);
res->ssid_len = iter->ssid_len;
return;
}
}
}
/* If we are here then there was no match */
res->ssid[0] = '\0';
res->ssid_len = 0;
return;
}
/* Cleanup all results */
static void
dhd_gscan_clear_all_batch_results(dhd_pub_t *dhd)
{
struct dhd_pno_gscan_params *gscan_params;
dhd_pno_status_info_t *_pno_state;
gscan_results_cache_t *iter;
_pno_state = PNO_GET_PNOSTATE(dhd);
gscan_params = &_pno_state->pno_params_arr[INDEX_OF_GSCAN_PARAMS].params_gscan;
iter = gscan_params->gscan_batch_cache;
/* Mark everything as consumed */
while (iter) {
iter->tot_consumed = iter->tot_count;
iter = iter->next;
}
dhd_gscan_batch_cache_cleanup(dhd);
return;
}
static int
_dhd_pno_gscan_cfg(dhd_pub_t *dhd, wl_pfn_gscan_cfg_t *pfncfg_gscan_param, int size)
{
int err = BCME_OK;
NULL_CHECK(dhd, "dhd is NULL", err);
DHD_PNO(("%s enter\n", __FUNCTION__));
err = dhd_iovar(dhd, 0, "pfn_gscan_cfg", (char *)pfncfg_gscan_param, size, 1);
if (err < 0) {
DHD_ERROR(("%s : failed to execute pfncfg_gscan_param\n", __FUNCTION__));
goto exit;
}
exit:
return err;
}
static bool
is_batch_retrieval_complete(struct dhd_pno_gscan_params *gscan_params)
{
smp_rmb();
return (gscan_params->get_batch_flag == GSCAN_BATCH_RETRIEVAL_COMPLETE);
}
#endif /* GSCAN_SUPPORT */
static int
_dhd_pno_suspend(dhd_pub_t *dhd)
{
int err;
int suspend = 1;
dhd_pno_status_info_t *_pno_state;
NULL_CHECK(dhd, "dhd is NULL", err);
NULL_CHECK(dhd->pno_state, "pno_state is NULL", err);
DHD_PNO(("%s enter\n", __FUNCTION__));
_pno_state = PNO_GET_PNOSTATE(dhd);
err = dhd_iovar(dhd, 0, "pfn_suspend", (char *)&suspend, sizeof(suspend), 1);
if (err < 0) {
DHD_ERROR(("%s : failed to suspend pfn(error :%d)\n", __FUNCTION__, err));
goto exit;
}
_pno_state->pno_status = DHD_PNO_SUSPEND;
exit:
return err;
}
static int
_dhd_pno_enable(dhd_pub_t *dhd, int enable)
{
int err = BCME_OK;
dhd_pno_status_info_t *_pno_state;
NULL_CHECK(dhd, "dhd is NULL", err);
NULL_CHECK(dhd->pno_state, "pno_state is NULL", err);
_pno_state = PNO_GET_PNOSTATE(dhd);
DHD_PNO(("%s enter\n", __FUNCTION__));
if (enable & 0xfffe) {
DHD_ERROR(("%s invalid value\n", __FUNCTION__));
err = BCME_BADARG;
goto exit;
}
if (!dhd_support_sta_mode(dhd)) {
DHD_ERROR(("PNO is not allowed for non-STA mode"));
err = BCME_BADOPTION;
goto exit;
}
if (enable) {
if ((_pno_state->pno_mode & DHD_PNO_LEGACY_MODE) &&
dhd_is_associated(dhd, NULL, NULL)) {
DHD_ERROR(("%s Legacy PNO mode cannot be enabled "
"in assoc mode , ignore it\n", __FUNCTION__));
err = BCME_BADOPTION;
goto exit;
}
}
/* Enable/Disable PNO */
err = dhd_iovar(dhd, 0, "pfn", (char *)&enable, sizeof(enable), 1);
if (err < 0) {
DHD_ERROR(("%s : failed to execute pfn_set - %d\n", __FUNCTION__, err));
goto exit;
}
_pno_state->pno_status = (enable)?
DHD_PNO_ENABLED : DHD_PNO_DISABLED;
if (!enable)
_pno_state->pno_mode = DHD_PNO_NONE_MODE;
DHD_PNO(("%s set pno as %s\n",
__FUNCTION__, enable ? "Enable" : "Disable"));
exit:
return err;
}
static int
_dhd_pno_set(dhd_pub_t *dhd, const dhd_pno_params_t *pno_params, dhd_pno_mode_t mode)
{
int err = BCME_OK;
wl_pfn_param_t pfn_param;
dhd_pno_params_t *_params;
dhd_pno_status_info_t *_pno_state;
bool combined_scan = FALSE;
DHD_PNO(("%s enter\n", __FUNCTION__));
NULL_CHECK(dhd, "dhd is NULL", err);
NULL_CHECK(dhd->pno_state, "pno_state is NULL", err);
_pno_state = PNO_GET_PNOSTATE(dhd);
memset(&pfn_param, 0, sizeof(pfn_param));
/* set pfn parameters */
pfn_param.version = htod32(PFN_VERSION);
pfn_param.flags = ((PFN_LIST_ORDER << SORT_CRITERIA_BIT) |
(ENABLE << IMMEDIATE_SCAN_BIT) | (ENABLE << REPORT_SEPERATELY_BIT));
if (mode == DHD_PNO_LEGACY_MODE) {
/* check and set extra pno params */
if ((pno_params->params_legacy.pno_repeat != 0) ||
(pno_params->params_legacy.pno_freq_expo_max != 0)) {
pfn_param.flags |= htod16(ENABLE << ENABLE_ADAPTSCAN_BIT);
pfn_param.repeat = (uchar) (pno_params->params_legacy.pno_repeat);
pfn_param.exp = (uchar) (pno_params->params_legacy.pno_freq_expo_max);
}
/* set up pno scan fr */
if (pno_params->params_legacy.scan_fr != 0)
pfn_param.scan_freq = htod32(pno_params->params_legacy.scan_fr);
if (_pno_state->pno_mode & DHD_PNO_BATCH_MODE) {
DHD_PNO(("will enable combined scan with BATCHIG SCAN MODE\n"));
mode |= DHD_PNO_BATCH_MODE;
combined_scan = TRUE;
} else if (_pno_state->pno_mode & DHD_PNO_HOTLIST_MODE) {
DHD_PNO(("will enable combined scan with HOTLIST SCAN MODE\n"));
mode |= DHD_PNO_HOTLIST_MODE;
combined_scan = TRUE;
}
#ifdef GSCAN_SUPPORT
else if (_pno_state->pno_mode & DHD_PNO_GSCAN_MODE) {
DHD_PNO(("will enable combined scan with GSCAN SCAN MODE\n"));
mode |= DHD_PNO_GSCAN_MODE;
}
#endif /* GSCAN_SUPPORT */
}
if (mode & (DHD_PNO_BATCH_MODE | DHD_PNO_HOTLIST_MODE)) {
/* Scan frequency of 30 sec */
pfn_param.scan_freq = htod32(30);
/* slow adapt scan is off by default */
pfn_param.slow_freq = htod32(0);
/* RSSI margin of 30 dBm */
pfn_param.rssi_margin = htod16(PNO_RSSI_MARGIN_DBM);
/* Network timeout 60 sec */
pfn_param.lost_network_timeout = htod32(60);
/* best n = 2 by default */
pfn_param.bestn = DEFAULT_BESTN;
/* mscan m=0 by default, so not record best networks by default */
pfn_param.mscan = DEFAULT_MSCAN;
/* default repeat = 10 */
pfn_param.repeat = DEFAULT_REPEAT;
/* by default, maximum scan interval = 2^2
* scan_freq when adaptive scan is turned on
*/
pfn_param.exp = DEFAULT_EXP;
if (mode == DHD_PNO_BATCH_MODE) {
/* In case of BATCH SCAN */
if (pno_params->params_batch.bestn)
pfn_param.bestn = pno_params->params_batch.bestn;
if (pno_params->params_batch.scan_fr)
pfn_param.scan_freq = htod32(pno_params->params_batch.scan_fr);
if (pno_params->params_batch.mscan)
pfn_param.mscan = pno_params->params_batch.mscan;
/* enable broadcast scan */
pfn_param.flags |= (ENABLE << ENABLE_BD_SCAN_BIT);
} else if (mode == DHD_PNO_HOTLIST_MODE) {
/* In case of HOTLIST SCAN */
if (pno_params->params_hotlist.scan_fr)
pfn_param.scan_freq = htod32(pno_params->params_hotlist.scan_fr);
pfn_param.bestn = 0;
pfn_param.repeat = 0;
/* enable broadcast scan */
pfn_param.flags |= (ENABLE << ENABLE_BD_SCAN_BIT);
}
if (combined_scan) {
/* Disable Adaptive Scan */
pfn_param.flags &= ~(htod16(ENABLE << ENABLE_ADAPTSCAN_BIT));
pfn_param.flags |= (ENABLE << ENABLE_BD_SCAN_BIT);
pfn_param.repeat = 0;
pfn_param.exp = 0;
if (_pno_state->pno_mode & DHD_PNO_BATCH_MODE) {
/* In case of Legacy PNO + BATCH SCAN */
_params = &(_pno_state->pno_params_arr[INDEX_OF_BATCH_PARAMS]);
if (_params->params_batch.bestn)
pfn_param.bestn = _params->params_batch.bestn;
if (_params->params_batch.scan_fr)
pfn_param.scan_freq = htod32(_params->params_batch.scan_fr);
if (_params->params_batch.mscan)
pfn_param.mscan = _params->params_batch.mscan;
} else if (_pno_state->pno_mode & DHD_PNO_HOTLIST_MODE) {
/* In case of Legacy PNO + HOTLIST SCAN */
_params = &(_pno_state->pno_params_arr[INDEX_OF_HOTLIST_PARAMS]);
if (_params->params_hotlist.scan_fr)
pfn_param.scan_freq = htod32(_params->params_hotlist.scan_fr);
pfn_param.bestn = 0;
pfn_param.repeat = 0;
}
}
}
#ifdef GSCAN_SUPPORT
if (mode & DHD_PNO_GSCAN_MODE) {
uint32 lost_network_timeout;
pfn_param.scan_freq = htod32(pno_params->params_gscan.scan_fr);
if (pno_params->params_gscan.mscan) {
pfn_param.bestn = pno_params->params_gscan.bestn;
pfn_param.mscan = pno_params->params_gscan.mscan;
pfn_param.flags |= (ENABLE << ENABLE_BD_SCAN_BIT);
}
/* RSSI margin of 30 dBm */
pfn_param.rssi_margin = htod16(PNO_RSSI_MARGIN_DBM);
pfn_param.repeat = 0;
pfn_param.exp = 0;
pfn_param.slow_freq = 0;
pfn_param.flags |= htod16(ENABLE << ENABLE_ADAPTSCAN_BIT);
if (_pno_state->pno_mode & DHD_PNO_LEGACY_MODE) {
dhd_pno_status_info_t *_pno_state = PNO_GET_PNOSTATE(dhd);
dhd_pno_params_t *_params;
_params = &(_pno_state->pno_params_arr[INDEX_OF_LEGACY_PARAMS]);
pfn_param.scan_freq = gcd(pno_params->params_gscan.scan_fr,
_params->params_legacy.scan_fr);
if ((_params->params_legacy.pno_repeat != 0) ||
(_params->params_legacy.pno_freq_expo_max != 0)) {
pfn_param.repeat = (uchar) (_params->params_legacy.pno_repeat);
pfn_param.exp = (uchar) (_params->params_legacy.pno_freq_expo_max);
}
}
lost_network_timeout = (pno_params->params_gscan.max_ch_bucket_freq *
pfn_param.scan_freq *
pno_params->params_gscan.lost_ap_window);
if (lost_network_timeout) {
pfn_param.lost_network_timeout = htod32(MIN(lost_network_timeout,
GSCAN_MIN_BSSID_TIMEOUT));
} else {
pfn_param.lost_network_timeout = htod32(GSCAN_MIN_BSSID_TIMEOUT);
}
} else
#endif /* GSCAN_SUPPORT */
{
if (pfn_param.scan_freq < htod32(PNO_SCAN_MIN_FW_SEC) ||
pfn_param.scan_freq > htod32(PNO_SCAN_MAX_FW_SEC)) {
DHD_ERROR(("%s pno freq(%d sec) is not valid \n",
__FUNCTION__, PNO_SCAN_MIN_FW_SEC));
err = BCME_BADARG;
goto exit;
}
}
err = dhd_set_rand_mac_oui(dhd);
/* Ignore if chip doesnt support the feature */
if (err < 0 && err != BCME_UNSUPPORTED) {
DHD_ERROR(("%s : failed to set random mac for PNO scan, %d\n", __FUNCTION__, err));
goto exit;
}
#ifdef GSCAN_SUPPORT
if (mode == DHD_PNO_BATCH_MODE ||
((mode & DHD_PNO_GSCAN_MODE) && pno_params->params_gscan.mscan)) {
#else
if (mode == DHD_PNO_BATCH_MODE) {
#endif /* GSCAN_SUPPORT */
int _tmp = pfn_param.bestn;
/* set bestn to calculate the max mscan which firmware supports */
err = dhd_iovar(dhd, 0, "pfnmem", (char *)&_tmp, sizeof(_tmp), 1);
if (err < 0) {
DHD_ERROR(("%s : failed to set pfnmem\n", __FUNCTION__));
goto exit;
}
/* get max mscan which the firmware supports */
err = dhd_iovar(dhd, 0, "pfnmem", (char *)&_tmp, sizeof(_tmp), 0);
if (err < 0) {
DHD_ERROR(("%s : failed to get pfnmem\n", __FUNCTION__));
goto exit;
}
pfn_param.mscan = MIN(pfn_param.mscan, _tmp);
DHD_PNO((" returned mscan : %d, set bestn : %d mscan %d\n", _tmp, pfn_param.bestn,
pfn_param.mscan));
}
err = dhd_iovar(dhd, 0, "pfn_set", (char *)&pfn_param, sizeof(pfn_param), 1);
if (err < 0) {
DHD_ERROR(("%s : failed to execute pfn_set %d\n", __FUNCTION__, err));
goto exit;
}
/* need to return mscan if this is for batch scan instead of err */
err = (mode == DHD_PNO_BATCH_MODE)? pfn_param.mscan : err;
exit:
return err;
}
static int
_dhd_pno_add_ssid(dhd_pub_t *dhd, wlc_ssid_ext_t* ssids_list, int nssid)
{
int err = BCME_OK;
int i = 0;
wl_pfn_t pfn_element;
NULL_CHECK(dhd, "dhd is NULL", err);
if (nssid) {
NULL_CHECK(ssids_list, "ssid list is NULL", err);
}
memset(&pfn_element, 0, sizeof(pfn_element));
{
int j;
for (j = 0; j < nssid; j++) {
DHD_PNO(("%s size = %d hidden = %d flags = %x rssi_thresh %d\n",
ssids_list[j].SSID, ssids_list[j].SSID_len, ssids_list[j].hidden,
ssids_list[j].flags, ssids_list[i].rssi_thresh));
}
}
/* Check for broadcast ssid */
for (i = 0; i < nssid; i++) {
if (!ssids_list[i].SSID_len) {
DHD_ERROR(("%d: Broadcast SSID is ilegal for PNO setting\n", i));
err = BCME_ERROR;
goto exit;
}
}
/* set all pfn ssid */
for (i = 0; i < nssid; i++) {
pfn_element.infra = htod32(DOT11_BSSTYPE_INFRASTRUCTURE);
pfn_element.auth = (DOT11_OPEN_SYSTEM);
pfn_element.wpa_auth = htod32(WPA_AUTH_PFN_ANY);
pfn_element.wsec = htod32(0);
pfn_element.infra = htod32(1);
if (ssids_list[i].hidden)
pfn_element.flags = htod32(ENABLE << WL_PFN_HIDDEN_BIT);
else
pfn_element.flags = 0;
pfn_element.flags |= htod32(ssids_list[i].flags);
/* If a single RSSI threshold is defined, use that */
#ifdef PNO_MIN_RSSI_TRIGGER
pfn_element.flags |= ((PNO_MIN_RSSI_TRIGGER & 0xFF) << WL_PFN_RSSI_SHIFT);
#else
pfn_element.flags |= ((ssids_list[i].rssi_thresh & 0xFF) << WL_PFN_RSSI_SHIFT);
#endif /* PNO_MIN_RSSI_TRIGGER */
memcpy((char *)pfn_element.ssid.SSID, ssids_list[i].SSID,
ssids_list[i].SSID_len);
pfn_element.ssid.SSID_len = ssids_list[i].SSID_len;
err = dhd_iovar(dhd, 0, "pfn_add", (char *)&pfn_element,
sizeof(pfn_element), 1);
if (err < 0) {
DHD_ERROR(("%s : failed to execute pfn_add\n", __FUNCTION__));
goto exit;
}
}
exit:
return err;
}
/* qsort compare function */
static int
_dhd_pno_cmpfunc(const void *a, const void *b)
{
return (*(uint16*)a - *(uint16*)b);
}
static int
_dhd_pno_chan_merge(uint16 *d_chan_list, int *nchan,
uint16 *chan_list1, int nchan1, uint16 *chan_list2, int nchan2)
{
int err = BCME_OK;
int i = 0, j = 0, k = 0;
uint16 tmp;
NULL_CHECK(d_chan_list, "d_chan_list is NULL", err);
NULL_CHECK(nchan, "nchan is NULL", err);
NULL_CHECK(chan_list1, "chan_list1 is NULL", err);
NULL_CHECK(chan_list2, "chan_list2 is NULL", err);
/* chan_list1 and chan_list2 should be sorted at first */
while (i < nchan1 && j < nchan2) {
tmp = chan_list1[i] < chan_list2[j]?
chan_list1[i++] : chan_list2[j++];
for (; i < nchan1 && chan_list1[i] == tmp; i++);
for (; j < nchan2 && chan_list2[j] == tmp; j++);
d_chan_list[k++] = tmp;
}
while (i < nchan1) {
tmp = chan_list1[i++];
for (; i < nchan1 && chan_list1[i] == tmp; i++);
d_chan_list[k++] = tmp;
}
while (j < nchan2) {
tmp = chan_list2[j++];
for (; j < nchan2 && chan_list2[j] == tmp; j++);
d_chan_list[k++] = tmp;
}
*nchan = k;
return err;
}
static int
_dhd_pno_get_channels(dhd_pub_t *dhd, uint16 *d_chan_list,
int *nchan, uint8 band, bool skip_dfs)
{
int err = BCME_OK;
int i, j;
uint32 chan_buf[WL_NUMCHANNELS + 1];
wl_uint32_list_t *list;
NULL_CHECK(dhd, "dhd is NULL", err);
if (*nchan) {
NULL_CHECK(d_chan_list, "d_chan_list is NULL", err);
}
list = (wl_uint32_list_t *) (void *)chan_buf;
list->count = htod32(WL_NUMCHANNELS);
err = dhd_wl_ioctl_cmd(dhd, WLC_GET_VALID_CHANNELS, chan_buf, sizeof(chan_buf), FALSE, 0);
if (err < 0) {
DHD_ERROR(("failed to get channel list (err: %d)\n", err));
goto exit;
}
for (i = 0, j = 0; i < dtoh32(list->count) && i < *nchan; i++) {
if (band == WLC_BAND_2G) {
if (dtoh32(list->element[i]) > CHANNEL_2G_MAX)
continue;
} else if (band == WLC_BAND_5G) {
if (dtoh32(list->element[i]) <= CHANNEL_2G_MAX)
continue;
if (skip_dfs && is_dfs(dtoh32(list->element[i])))
continue;
} else if (band == WLC_BAND_AUTO) {
if (skip_dfs || !is_dfs(dtoh32(list->element[i])))
continue;
} else { /* All channels */
if (skip_dfs && is_dfs(dtoh32(list->element[i])))
continue;
}
if (dtoh32(list->element[i]) <= CHANNEL_5G_MAX) {
d_chan_list[j++] = (uint16) dtoh32(list->element[i]);
} else {
err = BCME_BADCHAN;
goto exit;
}
}
*nchan = j;
exit:
return err;
}
static int
_dhd_pno_convert_format(dhd_pub_t *dhd, struct dhd_pno_batch_params *params_batch,
char *buf, int nbufsize)
{
int err = BCME_OK;
int bytes_written = 0, nreadsize = 0;
int t_delta = 0;
int nleftsize = nbufsize;
uint8 cnt = 0;
char *bp = buf;
char eabuf[ETHER_ADDR_STR_LEN];
#ifdef PNO_DEBUG
char *_base_bp;
char msg[150];
#endif
dhd_pno_bestnet_entry_t *iter, *next;
dhd_pno_scan_results_t *siter, *snext;
dhd_pno_best_header_t *phead, *pprev;
NULL_CHECK(params_batch, "params_batch is NULL", err);
if (nbufsize > 0)
NULL_CHECK(buf, "buf is NULL", err);
/* initialize the buffer */
memset(buf, 0, nbufsize);
DHD_PNO(("%s enter \n", __FUNCTION__));
/* # of scans */
if (!params_batch->get_batch.batch_started) {
bp += nreadsize = sprintf(bp, "scancount=%d\n",
params_batch->get_batch.expired_tot_scan_cnt);
nleftsize -= nreadsize;
params_batch->get_batch.batch_started = TRUE;
}
DHD_PNO(("%s scancount %d\n", __FUNCTION__, params_batch->get_batch.expired_tot_scan_cnt));
/* preestimate scan count until which scan result this report is going to end */
list_for_each_entry_safe(siter, snext,
&params_batch->get_batch.expired_scan_results_list, list) {
phead = siter->bestnetheader;
while (phead != NULL) {
/* if left_size is less than bestheader total size , stop this */
if (nleftsize <=
(phead->tot_size + phead->tot_cnt * ENTRY_OVERHEAD))
goto exit;
/* increase scan count */
cnt++;
/* # best of each scan */
DHD_PNO(("\n<loop : %d, apcount %d>\n", cnt - 1, phead->tot_cnt));
/* attribute of the scan */
if (phead->reason & PNO_STATUS_ABORT_MASK) {
bp += nreadsize = sprintf(bp, "trunc\n");
nleftsize -= nreadsize;
}
list_for_each_entry_safe(iter, next,
&phead->entry_list, list) {
t_delta = jiffies_to_msecs(jiffies - iter->recorded_time);
#ifdef PNO_DEBUG
_base_bp = bp;
memset(msg, 0, sizeof(msg));
#endif
/* BSSID info */
bp += nreadsize = sprintf(bp, "bssid=%s\n",
bcm_ether_ntoa((const struct ether_addr *)&iter->BSSID, eabuf));
nleftsize -= nreadsize;
/* SSID */
bp += nreadsize = sprintf(bp, "ssid=%s\n", iter->SSID);
nleftsize -= nreadsize;
/* channel */
bp += nreadsize = sprintf(bp, "freq=%d\n",
wf_channel2mhz(iter->channel,
iter->channel <= CH_MAX_2G_CHANNEL?
WF_CHAN_FACTOR_2_4_G : WF_CHAN_FACTOR_5_G));
nleftsize -= nreadsize;
/* RSSI */
bp += nreadsize = sprintf(bp, "level=%d\n", iter->RSSI);
nleftsize -= nreadsize;
/* add the time consumed in Driver to the timestamp of firmware */
iter->timestamp += t_delta;
bp += nreadsize = sprintf(bp, "age=%d\n", iter->timestamp);
nleftsize -= nreadsize;
/* RTT0 */
bp += nreadsize = sprintf(bp, "dist=%d\n",
(iter->rtt0 == 0)? -1 : iter->rtt0);
nleftsize -= nreadsize;
/* RTT1 */
bp += nreadsize = sprintf(bp, "distSd=%d\n",
(iter->rtt0 == 0)? -1 : iter->rtt1);
nleftsize -= nreadsize;
bp += nreadsize = sprintf(bp, "%s", AP_END_MARKER);
nleftsize -= nreadsize;
list_del(&iter->list);
MFREE(dhd->osh, iter, BESTNET_ENTRY_SIZE);
#ifdef PNO_DEBUG
memcpy(msg, _base_bp, bp - _base_bp);
DHD_PNO(("Entry : \n%s", msg));
#endif
}
bp += nreadsize = sprintf(bp, "%s", SCAN_END_MARKER);
DHD_PNO(("%s", SCAN_END_MARKER));
nleftsize -= nreadsize;
pprev = phead;
/* reset the header */
siter->bestnetheader = phead = phead->next;
MFREE(dhd->osh, pprev, BEST_HEADER_SIZE);
siter->cnt_header--;
}
if (phead == NULL) {
/* we store all entry in this scan , so it is ok to delete */
list_del(&siter->list);
MFREE(dhd->osh, siter, SCAN_RESULTS_SIZE);
}
}
exit:
if (cnt < params_batch->get_batch.expired_tot_scan_cnt) {
DHD_ERROR(("Buffer size is small to save all batch entry,"
" cnt : %d (remained_scan_cnt): %d\n",
cnt, params_batch->get_batch.expired_tot_scan_cnt - cnt));
}
params_batch->get_batch.expired_tot_scan_cnt -= cnt;
/* set FALSE only if the link list is empty after returning the data */
if (list_empty(&params_batch->get_batch.expired_scan_results_list)) {
params_batch->get_batch.batch_started = FALSE;
bp += sprintf(bp, "%s", RESULTS_END_MARKER);
DHD_PNO(("%s", RESULTS_END_MARKER));
DHD_PNO(("%s : Getting the batching data is complete\n", __FUNCTION__));
}
/* return used memory in buffer */
bytes_written = (int32)(bp - buf);
return bytes_written;
}
static int
_dhd_pno_clear_all_batch_results(dhd_pub_t *dhd, struct list_head *head, bool only_last)
{
int err = BCME_OK;
int removed_scan_cnt = 0;
dhd_pno_scan_results_t *siter, *snext;
dhd_pno_best_header_t *phead, *pprev;
dhd_pno_bestnet_entry_t *iter, *next;
NULL_CHECK(dhd, "dhd is NULL", err);
NULL_CHECK(head, "head is NULL", err);
NULL_CHECK(head->next, "head->next is NULL", err);
DHD_PNO(("%s enter\n", __FUNCTION__));
list_for_each_entry_safe(siter, snext,
head, list) {
if (only_last) {
/* in case that we need to delete only last one */
if (!list_is_last(&siter->list, head)) {
/* skip if the one is not last */
continue;
}
}
/* delete all data belong if the one is last */
phead = siter->bestnetheader;
while (phead != NULL) {
removed_scan_cnt++;
list_for_each_entry_safe(iter, next,
&phead->entry_list, list) {
list_del(&iter->list);
MFREE(dhd->osh, iter, BESTNET_ENTRY_SIZE);
}
pprev = phead;
phead = phead->next;
MFREE(dhd->osh, pprev, BEST_HEADER_SIZE);
}
if (phead == NULL) {
/* it is ok to delete top node */
list_del(&siter->list);
MFREE(dhd->osh, siter, SCAN_RESULTS_SIZE);
}
}
return removed_scan_cnt;
}
static int
_dhd_pno_cfg(dhd_pub_t *dhd, uint16 *channel_list, int nchan)
{
int err = BCME_OK;
int i = 0;
wl_pfn_cfg_t pfncfg_param;
NULL_CHECK(dhd, "dhd is NULL", err);
if (nchan) {
NULL_CHECK(channel_list, "nchan is NULL", err);
}
if (nchan > WL_NUMCHANNELS) {
return BCME_RANGE;
}
DHD_PNO(("%s enter : nchan : %d\n", __FUNCTION__, nchan));
memset(&pfncfg_param, 0, sizeof(wl_pfn_cfg_t));
/* Setup default values */
pfncfg_param.reporttype = htod32(WL_PFN_REPORT_ALLNET);
pfncfg_param.channel_num = htod32(0);
for (i = 0; i < nchan; i++)
pfncfg_param.channel_list[i] = channel_list[i];
pfncfg_param.channel_num = htod32(nchan);
err = dhd_iovar(dhd, 0, "pfn_cfg", (char *)&pfncfg_param, sizeof(pfncfg_param), 1);
if (err < 0) {
DHD_ERROR(("%s : failed to execute pfn_cfg\n", __FUNCTION__));
goto exit;
}
exit:
return err;
}
static int
_dhd_pno_reinitialize_prof(dhd_pub_t *dhd, dhd_pno_params_t *params, dhd_pno_mode_t mode)
{
int err = BCME_OK;
dhd_pno_status_info_t *_pno_state;
NULL_CHECK(dhd, "dhd is NULL\n", err);
NULL_CHECK(dhd->pno_state, "pno_state is NULL\n", err);
DHD_PNO(("%s enter\n", __FUNCTION__));
_pno_state = PNO_GET_PNOSTATE(dhd);
mutex_lock(&_pno_state->pno_mutex);
switch (mode) {
case DHD_PNO_LEGACY_MODE: {
struct dhd_pno_ssid *iter, *next;
if (params->params_legacy.nssid > 0) {
list_for_each_entry_safe(iter, next,
&params->params_legacy.ssid_list, list) {
list_del(&iter->list);
kfree(iter);
}
}
params->params_legacy.nssid = 0;
params->params_legacy.scan_fr = 0;
params->params_legacy.pno_freq_expo_max = 0;
params->params_legacy.pno_repeat = 0;
params->params_legacy.nchan = 0;
memset(params->params_legacy.chan_list, 0,
sizeof(params->params_legacy.chan_list));
break;
}
case DHD_PNO_BATCH_MODE: {
params->params_batch.scan_fr = 0;
params->params_batch.mscan = 0;
params->params_batch.nchan = 0;
params->params_batch.rtt = 0;
params->params_batch.bestn = 0;
params->params_batch.nchan = 0;
params->params_batch.band = WLC_BAND_AUTO;
memset(params->params_batch.chan_list, 0,
sizeof(params->params_batch.chan_list));
params->params_batch.get_batch.batch_started = FALSE;
params->params_batch.get_batch.buf = NULL;
params->params_batch.get_batch.bufsize = 0;
params->params_batch.get_batch.reason = 0;
_dhd_pno_clear_all_batch_results(dhd,
&params->params_batch.get_batch.scan_results_list, FALSE);
_dhd_pno_clear_all_batch_results(dhd,
&params->params_batch.get_batch.expired_scan_results_list, FALSE);
params->params_batch.get_batch.tot_scan_cnt = 0;
params->params_batch.get_batch.expired_tot_scan_cnt = 0;
params->params_batch.get_batch.top_node_cnt = 0;
INIT_LIST_HEAD(&params->params_batch.get_batch.scan_results_list);
INIT_LIST_HEAD(&params->params_batch.get_batch.expired_scan_results_list);
break;
}
case DHD_PNO_HOTLIST_MODE: {
struct dhd_pno_bssid *iter, *next;
if (params->params_hotlist.nbssid > 0) {
list_for_each_entry_safe(iter, next,
&params->params_hotlist.bssid_list, list) {
list_del(&iter->list);
kfree(iter);
}
}
params->params_hotlist.scan_fr = 0;
params->params_hotlist.nbssid = 0;
params->params_hotlist.nchan = 0;
params->params_batch.band = WLC_BAND_AUTO;
memset(params->params_hotlist.chan_list, 0,
sizeof(params->params_hotlist.chan_list));
break;
}
default:
DHD_ERROR(("%s : unknown mode : %d\n", __FUNCTION__, mode));
break;
}
mutex_unlock(&_pno_state->pno_mutex);
return err;
}
static int
_dhd_pno_add_bssid(dhd_pub_t *dhd, wl_pfn_bssid_t *p_pfn_bssid, int nbssid)
{
int err = BCME_OK;
NULL_CHECK(dhd, "dhd is NULL", err);
if (nbssid) {
NULL_CHECK(p_pfn_bssid, "bssid list is NULL", err);
}
err = dhd_iovar(dhd, 0, "pfn_add_bssid", (char *)p_pfn_bssid,
sizeof(wl_pfn_bssid_t) * nbssid, 1);
if (err < 0) {
DHD_ERROR(("%s : failed to execute pfn_cfg\n", __FUNCTION__));
goto exit;
}
exit:
return err;
}
#ifdef GSCAN_SUPPORT
static int
_dhd_pno_add_significant_bssid(dhd_pub_t *dhd,
wl_pfn_significant_bssid_t *p_pfn_significant_bssid, int nbssid)
{
int err = BCME_OK;
NULL_CHECK(dhd, "dhd is NULL", err);
if (!nbssid) {
err = BCME_ERROR;
goto exit;
}
NULL_CHECK(p_pfn_significant_bssid, "bssid list is NULL", err);
err = dhd_iovar(dhd, 0, "pfn_add_swc_bssid", (char *)p_pfn_significant_bssid,
sizeof(wl_pfn_significant_bssid_t) * nbssid, 1);
if (err < 0) {
DHD_ERROR(("%s : failed to execute pfn_significant_bssid %d\n", __FUNCTION__, err));
goto exit;
}
exit:
return err;
}
#endif /* GSCAN_SUPPORT */
int
dhd_pno_stop_for_ssid(dhd_pub_t *dhd)
{
int err = BCME_OK;
uint32 mode = 0;
dhd_pno_status_info_t *_pno_state;
dhd_pno_params_t *_params;
wl_pfn_bssid_t *p_pfn_bssid = NULL;
NULL_CHECK(dhd, "dev is NULL", err);
NULL_CHECK(dhd->pno_state, "pno_state is NULL", err);
_pno_state = PNO_GET_PNOSTATE(dhd);
if (!(_pno_state->pno_mode & DHD_PNO_LEGACY_MODE)) {
DHD_ERROR(("%s : LEGACY PNO MODE is not enabled\n", __FUNCTION__));
goto exit;
}
DHD_PNO(("%s enter\n", __FUNCTION__));
_pno_state->pno_mode &= ~DHD_PNO_LEGACY_MODE;
#ifdef GSCAN_SUPPORT
if (_pno_state->pno_mode & DHD_PNO_GSCAN_MODE) {
struct dhd_pno_gscan_params *gscan_params;
_params = &_pno_state->pno_params_arr[INDEX_OF_GSCAN_PARAMS];
gscan_params = &_params->params_gscan;
if (gscan_params->mscan) {
/* retrieve the batching data from firmware into host */
err = dhd_wait_batch_results_complete(dhd);
if (err != BCME_OK)
goto exit;
}
/* save current pno_mode before calling dhd_pno_clean */
mutex_lock(&_pno_state->pno_mutex);
mode = _pno_state->pno_mode;
err = dhd_pno_clean(dhd);
if (err < 0) {
DHD_ERROR(("%s : failed to call dhd_pno_clean (err: %d)\n",
__FUNCTION__, err));
mutex_unlock(&_pno_state->pno_mutex);
goto exit;
}
/* restore previous pno_mode */
_pno_state->pno_mode = mode;
mutex_unlock(&_pno_state->pno_mutex);
/* Restart gscan */
err = dhd_pno_initiate_gscan_request(dhd, 1, 0);
goto exit;
}
#endif /* GSCAN_SUPPORT */
/* restart Batch mode if the batch mode is on */
if (_pno_state->pno_mode & (DHD_PNO_BATCH_MODE | DHD_PNO_HOTLIST_MODE)) {
/* retrieve the batching data from firmware into host */
dhd_pno_get_for_batch(dhd, NULL, 0, PNO_STATUS_DISABLE);
/* save current pno_mode before calling dhd_pno_clean */
mode = _pno_state->pno_mode;
dhd_pno_clean(dhd);
/* restore previous pno_mode */
_pno_state->pno_mode = mode;
if (_pno_state->pno_mode & DHD_PNO_BATCH_MODE) {
_params = &(_pno_state->pno_params_arr[INDEX_OF_BATCH_PARAMS]);
/* restart BATCH SCAN */
err = dhd_pno_set_for_batch(dhd, &_params->params_batch);
if (err < 0) {
_pno_state->pno_mode &= ~DHD_PNO_BATCH_MODE;
DHD_ERROR(("%s : failed to restart batch scan(err: %d)\n",
__FUNCTION__, err));
goto exit;
}
} else if (_pno_state->pno_mode & DHD_PNO_HOTLIST_MODE) {
/* restart HOTLIST SCAN */
struct dhd_pno_bssid *iter, *next;
_params = &(_pno_state->pno_params_arr[INDEX_OF_HOTLIST_PARAMS]);
p_pfn_bssid = kzalloc(sizeof(wl_pfn_bssid_t) *
_params->params_hotlist.nbssid, GFP_KERNEL);
if (p_pfn_bssid == NULL) {
DHD_ERROR(("%s : failed to allocate wl_pfn_bssid_t array"
" (count: %d)",
__FUNCTION__, _params->params_hotlist.nbssid));
err = BCME_ERROR;
_pno_state->pno_mode &= ~DHD_PNO_HOTLIST_MODE;
goto exit;
}
/* convert dhd_pno_bssid to wl_pfn_bssid */
list_for_each_entry_safe(iter, next,
&_params->params_hotlist.bssid_list, list) {
memcpy(&p_pfn_bssid->macaddr,
&iter->macaddr, ETHER_ADDR_LEN);
p_pfn_bssid->flags = iter->flags;
p_pfn_bssid++;
}
err = dhd_pno_set_for_hotlist(dhd, p_pfn_bssid, &_params->params_hotlist);
if (err < 0) {
_pno_state->pno_mode &= ~DHD_PNO_HOTLIST_MODE;
DHD_ERROR(("%s : failed to restart hotlist scan(err: %d)\n",
__FUNCTION__, err));
goto exit;
}
}
} else {
err = dhd_pno_clean(dhd);
if (err < 0) {
DHD_ERROR(("%s : failed to call dhd_pno_clean (err: %d)\n",
__FUNCTION__, err));
goto exit;
}
}
exit:
kfree(p_pfn_bssid);
return err;
}
int
dhd_pno_enable(dhd_pub_t *dhd, int enable)
{
int err = BCME_OK;
NULL_CHECK(dhd, "dhd is NULL", err);
DHD_PNO(("%s enter\n", __FUNCTION__));
return (_dhd_pno_enable(dhd, enable));
}
static wlc_ssid_ext_t * dhd_pno_get_legacy_pno_ssid(dhd_pub_t *dhd,
dhd_pno_status_info_t *pno_state)
{
int err = BCME_OK;
int i;
struct dhd_pno_ssid *iter, *next;
dhd_pno_params_t *_params1 = &pno_state->pno_params_arr[INDEX_OF_LEGACY_PARAMS];
wlc_ssid_ext_t *p_ssid_list;
p_ssid_list = kzalloc(sizeof(wlc_ssid_ext_t) *
_params1->params_legacy.nssid, GFP_KERNEL);
if (p_ssid_list == NULL) {
DHD_ERROR(("%s : failed to allocate wlc_ssid_ext_t array (count: %d)",
__FUNCTION__, _params1->params_legacy.nssid));
err = BCME_ERROR;
pno_state->pno_mode &= ~DHD_PNO_LEGACY_MODE;
goto exit;
}
i = 0;
/* convert dhd_pno_ssid to wlc_ssid_ext_t */
list_for_each_entry_safe(iter, next, &_params1->params_legacy.ssid_list, list) {
p_ssid_list[i].SSID_len = iter->SSID_len;
p_ssid_list[i].hidden = iter->hidden;
p_ssid_list[i].rssi_thresh = iter->rssi_thresh;
memcpy(p_ssid_list[i].SSID, iter->SSID, p_ssid_list[i].SSID_len);
i++;
}
exit:
return p_ssid_list;
}
#ifdef GSCAN_SUPPORT
static int dhd_epno_set_ssid(dhd_pub_t *dhd,
dhd_pno_status_info_t *pno_state)
{
int err = BCME_OK;
dhd_epno_params_t *iter, *next;
dhd_pno_params_t *_params1 = &pno_state->pno_params_arr[INDEX_OF_GSCAN_PARAMS];
struct dhd_pno_gscan_params *gscan_params;
wlc_ssid_ext_t ssid_elem;
wl_pfn_ext_list_t *p_ssid_ext_elem = NULL;
uint32 mem_needed = 0, i = 0;
uint16 num_visible_epno_ssid;
uint8 flags;
gscan_params = &_params1->params_gscan;
num_visible_epno_ssid = gscan_params->num_visible_epno_ssid;
if (num_visible_epno_ssid) {
mem_needed = sizeof(wl_pfn_ext_list_t) + (sizeof(wl_pfn_ext_t) *
(num_visible_epno_ssid - 1));
p_ssid_ext_elem = kzalloc(mem_needed, GFP_KERNEL);
if (p_ssid_ext_elem == NULL) {
DHD_ERROR(("%s : failed to allocate memory %u\n",
__FUNCTION__, mem_needed));
err = BCME_NOMEM;
goto exit;
}
p_ssid_ext_elem->version = PFN_SSID_EXT_VERSION;
p_ssid_ext_elem->count = num_visible_epno_ssid;
}
DHD_PNO(("Total ssids %d, visible SSIDs %d\n", gscan_params->num_epno_ssid,
num_visible_epno_ssid));
/* convert dhd_pno_ssid to wlc_ssid_ext_t */
list_for_each_entry_safe(iter, next, &gscan_params->epno_ssid_list, list) {
if (iter->flags & DHD_PNO_USE_SSID) {
memset(&ssid_elem, 0, sizeof(ssid_elem));
ssid_elem.SSID_len = iter->ssid_len;
ssid_elem.hidden = TRUE;
flags = (iter->flags & DHD_EPNO_A_BAND_TRIG) ?
WL_PFN_SSID_A_BAND_TRIG: 0;
flags |= (iter->flags & DHD_EPNO_BG_BAND_TRIG) ?
WL_PFN_SSID_BG_BAND_TRIG: 0;
ssid_elem.flags = flags;
ssid_elem.rssi_thresh = iter->rssi_thresh;
memcpy(ssid_elem.SSID, iter->ssid, iter->ssid_len);
if ((err = _dhd_pno_add_ssid(dhd, &ssid_elem, 1)) < 0) {
DHD_ERROR(("failed to add ssid list (err %d) in firmware\n", err));
goto exit;
}
} else if (i < num_visible_epno_ssid) {
p_ssid_ext_elem->pfn_ext[i].rssi_thresh = iter->rssi_thresh;
switch (iter->auth) {
case DHD_PNO_AUTH_CODE_OPEN:
p_ssid_ext_elem->pfn_ext[i].wpa_auth = WPA_AUTH_DISABLED;
break;
case DHD_PNO_AUTH_CODE_PSK:
p_ssid_ext_elem->pfn_ext[i].wpa_auth =
(WPA2_AUTH_PSK | WPA_AUTH_PSK);
break;
case DHD_PNO_AUTH_CODE_EAPOL:
p_ssid_ext_elem->pfn_ext[i].wpa_auth =
(uint16)WPA_AUTH_PFN_ANY;
break;
default:
p_ssid_ext_elem->pfn_ext[i].wpa_auth =
(uint16)WPA_AUTH_PFN_ANY;
break;
}
memcpy(p_ssid_ext_elem->pfn_ext[i].ssid, iter->ssid, iter->ssid_len);
p_ssid_ext_elem->pfn_ext[i].ssid_len = iter->ssid_len;
iter->index = gscan_params->ssid_ext_last_used_index++;
flags = (iter->flags & DHD_EPNO_A_BAND_TRIG) ?
WL_PFN_SSID_A_BAND_TRIG: 0;
flags |= (iter->flags & DHD_EPNO_BG_BAND_TRIG) ?
WL_PFN_SSID_BG_BAND_TRIG: 0;
p_ssid_ext_elem->pfn_ext[i].flags = flags;
DHD_PNO(("SSID %s idx %d rssi thresh %d flags %x\n", iter->ssid,
iter->index, iter->rssi_thresh, flags));
i++;
}
}
if (num_visible_epno_ssid) {
err = dhd_iovar(dhd, 0, "pfn_add_ssid_ext", (char *)p_ssid_ext_elem,
mem_needed, 1);
if (err < 0) {
DHD_ERROR(("%s : failed to execute pfn_add_pno_ext_ssid %d\n", __FUNCTION__,
err));
}
}
exit:
kfree(p_ssid_ext_elem);
return err;
}
#endif /* GSCAN_SUPPORT */
static int
dhd_pno_add_to_ssid_list(dhd_pno_params_t *params, wlc_ssid_ext_t *ssid_list,
int nssid)
{
int ret = 0;
int i;
struct dhd_pno_ssid *_pno_ssid;
for (i = 0; i < nssid; i++) {
if (ssid_list[i].SSID_len > DOT11_MAX_SSID_LEN) {
DHD_ERROR(("%s : Invalid SSID length %d\n",
__FUNCTION__, ssid_list[i].SSID_len));
ret = BCME_ERROR;
goto exit;
}
_pno_ssid = kzalloc(sizeof(struct dhd_pno_ssid), GFP_KERNEL);
if (_pno_ssid == NULL) {
DHD_ERROR(("%s : failed to allocate struct dhd_pno_ssid\n",
__FUNCTION__));
ret = BCME_ERROR;
goto exit;
}
_pno_ssid->SSID_len = ssid_list[i].SSID_len;
_pno_ssid->hidden = ssid_list[i].hidden;
_pno_ssid->rssi_thresh = ssid_list[i].rssi_thresh;
memcpy(_pno_ssid->SSID, ssid_list[i].SSID, _pno_ssid->SSID_len);
list_add_tail(&_pno_ssid->list, &params->params_legacy.ssid_list);
params->params_legacy.nssid++;
}
exit:
return ret;
}
int
dhd_pno_set_for_ssid(dhd_pub_t *dhd, wlc_ssid_ext_t* ssid_list, int nssid,
uint16 scan_fr, int pno_repeat, int pno_freq_expo_max, uint16 *channel_list, int nchan)
{
dhd_pno_params_t *_params;
dhd_pno_params_t *_params2;
dhd_pno_status_info_t *_pno_state;
uint16 _chan_list[WL_NUMCHANNELS];
int32 tot_nchan = 0;
int err = BCME_OK;
int i;
int mode = 0;
NULL_CHECK(dhd, "dhd is NULL", err);
NULL_CHECK(dhd->pno_state, "pno_state is NULL", err);
_pno_state = PNO_GET_PNOSTATE(dhd);
if (!dhd_support_sta_mode(dhd)) {
err = BCME_BADOPTION;
goto exit_no_clear;
}
DHD_PNO(("%s enter : scan_fr :%d, pno_repeat :%d,"
"pno_freq_expo_max: %d, nchan :%d\n", __FUNCTION__,
scan_fr, pno_repeat, pno_freq_expo_max, nchan));
_params = &(_pno_state->pno_params_arr[INDEX_OF_LEGACY_PARAMS]);
/* If GSCAN is also ON will handle this down below */
#ifdef GSCAN_SUPPORT
if (_pno_state->pno_mode & DHD_PNO_LEGACY_MODE &&
!(_pno_state->pno_mode & DHD_PNO_GSCAN_MODE)) {
#else
if (_pno_state->pno_mode & DHD_PNO_LEGACY_MODE) {
#endif /* GSCAN_SUPPORT */
DHD_ERROR(("%s : Legacy PNO mode was already started, "
"will disable previous one to start new one\n", __FUNCTION__));
err = dhd_pno_stop_for_ssid(dhd);
if (err < 0) {
DHD_ERROR(("%s : failed to stop legacy PNO (err %d)\n",
__FUNCTION__, err));
goto exit_no_clear;
}
}
_pno_state->pno_mode |= DHD_PNO_LEGACY_MODE;
err = _dhd_pno_reinitialize_prof(dhd, _params, DHD_PNO_LEGACY_MODE);
if (err < 0) {
DHD_ERROR(("%s : failed to reinitialize profile (err %d)\n",
__FUNCTION__, err));
goto exit_no_clear;
}
memset(_chan_list, 0, sizeof(_chan_list));
tot_nchan = MIN(nchan, WL_NUMCHANNELS);
if (tot_nchan > 0 && channel_list) {
for (i = 0; i < tot_nchan; i++)
_params->params_legacy.chan_list[i] = _chan_list[i] = channel_list[i];
}
#ifdef GSCAN_SUPPORT
else {
tot_nchan = WL_NUMCHANNELS;
err = _dhd_pno_get_channels(dhd, _chan_list, &tot_nchan,
(WLC_BAND_2G | WLC_BAND_5G), FALSE);
if (err < 0) {
tot_nchan = 0;
DHD_PNO(("Could not get channel list for PNO SSID\n"));
} else {
for (i = 0; i < tot_nchan; i++)
_params->params_legacy.chan_list[i] = _chan_list[i];
}
}
#endif /* GSCAN_SUPPORT */
if (_pno_state->pno_mode & (DHD_PNO_BATCH_MODE | DHD_PNO_HOTLIST_MODE)) {
DHD_PNO(("BATCH SCAN is on progress in firmware\n"));
/* retrieve the batching data from firmware into host */
dhd_pno_get_for_batch(dhd, NULL, 0, PNO_STATUS_DISABLE);
/* store current pno_mode before disabling pno */
mode = _pno_state->pno_mode;
err = _dhd_pno_enable(dhd, PNO_OFF);
if (err < 0) {
DHD_ERROR(("%s : failed to disable PNO\n", __FUNCTION__));
goto exit_no_clear;
}
/* restore the previous mode */
_pno_state->pno_mode = mode;
/* use superset of channel list between two mode */
if (_pno_state->pno_mode & DHD_PNO_BATCH_MODE) {
_params2 = &(_pno_state->pno_params_arr[INDEX_OF_BATCH_PARAMS]);
if (_params2->params_batch.nchan > 0 && tot_nchan > 0) {
err = _dhd_pno_chan_merge(_chan_list, &tot_nchan,
&_params2->params_batch.chan_list[0],
_params2->params_batch.nchan,
&channel_list[0], tot_nchan);
if (err < 0) {
DHD_ERROR(("%s : failed to merge channel list"
" between legacy and batch\n",
__FUNCTION__));
goto exit_no_clear;
}
} else {
DHD_PNO(("superset channel will use"
" all channels in firmware\n"));
}
} else if (_pno_state->pno_mode & DHD_PNO_HOTLIST_MODE) {
_params2 = &(_pno_state->pno_params_arr[INDEX_OF_HOTLIST_PARAMS]);
if (_params2->params_hotlist.nchan > 0 && tot_nchan > 0) {
err = _dhd_pno_chan_merge(_chan_list, &tot_nchan,
&_params2->params_hotlist.chan_list[0],
_params2->params_hotlist.nchan,
&channel_list[0], tot_nchan);
if (err < 0) {
DHD_ERROR(("%s : failed to merge channel list"
" between legacy and hotlist\n",
__FUNCTION__));
goto exit_no_clear;
}
}
}
}
_params->params_legacy.scan_fr = scan_fr;
_params->params_legacy.pno_repeat = pno_repeat;
_params->params_legacy.pno_freq_expo_max = pno_freq_expo_max;
_params->params_legacy.nchan = tot_nchan;
_params->params_legacy.nssid = 0;
INIT_LIST_HEAD(&_params->params_legacy.ssid_list);
#ifdef GSCAN_SUPPORT
/* dhd_pno_initiate_gscan_request will handle simultaneous Legacy PNO and GSCAN */
if (_pno_state->pno_mode & DHD_PNO_GSCAN_MODE) {
if (dhd_pno_add_to_ssid_list(_params, ssid_list, nssid) < 0) {
err = BCME_ERROR;
goto exit;
}
DHD_PNO(("GSCAN mode is ON! Will restart GSCAN+Legacy PNO\n"));
err = dhd_pno_initiate_gscan_request(dhd, 1, 0);
goto exit;
}
#endif /* GSCAN_SUPPORT */
if ((err = _dhd_pno_set(dhd, _params, DHD_PNO_LEGACY_MODE)) < 0) {
DHD_ERROR(("failed to set call pno_set (err %d) in firmware\n", err));
goto exit;
}
if ((err = _dhd_pno_add_ssid(dhd, ssid_list, nssid)) < 0) {
DHD_ERROR(("failed to add ssid list(err %d), %d in firmware\n", err, nssid));
goto exit;
}
if (dhd_pno_add_to_ssid_list(_params, ssid_list, nssid) < 0) {
err = BCME_ERROR;
goto exit;
}
if (tot_nchan > 0) {
if ((err = _dhd_pno_cfg(dhd, _chan_list, tot_nchan)) < 0) {
DHD_ERROR(("%s : failed to set call pno_cfg (err %d) in firmware\n",
__FUNCTION__, err));
goto exit;
}
}
if (_pno_state->pno_status == DHD_PNO_DISABLED) {
if ((err = _dhd_pno_enable(dhd, PNO_ON)) < 0)
DHD_ERROR(("%s : failed to enable PNO\n", __FUNCTION__));
}
exit:
if (err < 0)
_dhd_pno_reinitialize_prof(dhd, _params, DHD_PNO_LEGACY_MODE);
exit_no_clear:
/* clear mode in case of error */
if (err < 0) {
int ret = dhd_pno_clean(dhd);
if (ret < 0) {
DHD_ERROR(("%s : dhd_pno_clean failure (err: %d)\n",
__FUNCTION__, ret));
} else {
_pno_state->pno_mode &= ~DHD_PNO_LEGACY_MODE;
}
}
return err;
}
int
dhd_pno_set_for_batch(dhd_pub_t *dhd, struct dhd_pno_batch_params *batch_params)
{
int err = BCME_OK;
uint16 _chan_list[WL_NUMCHANNELS];
int rem_nchan = 0, tot_nchan = 0;
int mode = 0, mscan = 0;
dhd_pno_params_t *_params;
dhd_pno_params_t *_params2;
dhd_pno_status_info_t *_pno_state;
wlc_ssid_ext_t *p_ssid_list = NULL;
NULL_CHECK(dhd, "dhd is NULL", err);
NULL_CHECK(dhd->pno_state, "pno_state is NULL", err);
NULL_CHECK(batch_params, "batch_params is NULL", err);
_pno_state = PNO_GET_PNOSTATE(dhd);
DHD_PNO(("%s enter\n", __FUNCTION__));
if (!dhd_support_sta_mode(dhd)) {
err = BCME_BADOPTION;
goto exit;
}
if (!WLS_SUPPORTED(_pno_state)) {
DHD_ERROR(("%s : wifi location service is not supported\n", __FUNCTION__));
err = BCME_UNSUPPORTED;
goto exit;
}
_params = &_pno_state->pno_params_arr[INDEX_OF_BATCH_PARAMS];
if (!(_pno_state->pno_mode & DHD_PNO_BATCH_MODE)) {
_pno_state->pno_mode |= DHD_PNO_BATCH_MODE;
err = _dhd_pno_reinitialize_prof(dhd, _params, DHD_PNO_BATCH_MODE);
if (err < 0) {
DHD_ERROR(("%s : failed to call _dhd_pno_reinitialize_prof\n",
__FUNCTION__));
goto exit;
}
} else {
/* batch mode is already started */
return -EBUSY;
}
_params->params_batch.scan_fr = batch_params->scan_fr;
_params->params_batch.bestn = batch_params->bestn;
_params->params_batch.mscan = (batch_params->mscan)?
batch_params->mscan : DEFAULT_BATCH_MSCAN;
_params->params_batch.nchan = batch_params->nchan;
memcpy(_params->params_batch.chan_list, batch_params->chan_list,
sizeof(_params->params_batch.chan_list));
memset(_chan_list, 0, sizeof(_chan_list));
rem_nchan = ARRAYSIZE(batch_params->chan_list) - batch_params->nchan;
if (batch_params->band == WLC_BAND_2G || batch_params->band == WLC_BAND_5G) {
/* get a valid channel list based on band B or A */
err = _dhd_pno_get_channels(dhd,
&_params->params_batch.chan_list[batch_params->nchan],
&rem_nchan, batch_params->band, FALSE);
if (err < 0) {
DHD_ERROR(("%s: failed to get valid channel list(band : %d)\n",
__FUNCTION__, batch_params->band));
goto exit;
}
/* now we need to update nchan because rem_chan has valid channel count */
_params->params_batch.nchan += rem_nchan;
/* need to sort channel list */
sort(_params->params_batch.chan_list, _params->params_batch.nchan,
sizeof(_params->params_batch.chan_list[0]), _dhd_pno_cmpfunc, NULL);
}
#ifdef PNO_DEBUG
{
DHD_PNO(("Channel list : "));
for (i = 0; i < _params->params_batch.nchan; i++) {
DHD_PNO(("%d ", _params->params_batch.chan_list[i]));
}
DHD_PNO(("\n"));
}
#endif
if (_params->params_batch.nchan) {
/* copy the channel list into local array */
memcpy(_chan_list, _params->params_batch.chan_list, sizeof(_chan_list));
tot_nchan = _params->params_batch.nchan;
}
if (_pno_state->pno_mode & DHD_PNO_LEGACY_MODE) {
DHD_PNO(("PNO SSID is on progress in firmware\n"));
/* store current pno_mode before disabling pno */
mode = _pno_state->pno_mode;
err = _dhd_pno_enable(dhd, PNO_OFF);
if (err < 0) {
DHD_ERROR(("%s : failed to disable PNO\n", __FUNCTION__));
goto exit;
}
/* restore the previous mode */
_pno_state->pno_mode = mode;
/* Use the superset for channelist between two mode */
_params2 = &(_pno_state->pno_params_arr[INDEX_OF_LEGACY_PARAMS]);
if (_params2->params_legacy.nchan > 0 && _params->params_batch.nchan > 0) {
err = _dhd_pno_chan_merge(_chan_list, &tot_nchan,
&_params2->params_legacy.chan_list[0],
_params2->params_legacy.nchan,
&_params->params_batch.chan_list[0], _params->params_batch.nchan);
if (err < 0) {
DHD_ERROR(("%s : failed to merge channel list"
" between legacy and batch\n",
__FUNCTION__));
goto exit;
}
} else {
DHD_PNO(("superset channel will use all channels in firmware\n"));
}
p_ssid_list = dhd_pno_get_legacy_pno_ssid(dhd, _pno_state);
if (!p_ssid_list) {
err = BCME_NOMEM;
DHD_ERROR(("failed to get Legacy PNO SSID list\n"));
goto exit;
}
if ((err = _dhd_pno_add_ssid(dhd, p_ssid_list,
_params2->params_legacy.nssid)) < 0) {
DHD_ERROR(("failed to add ssid list (err %d) in firmware\n", err));
goto exit;
}
}
if ((err = _dhd_pno_set(dhd, _params, DHD_PNO_BATCH_MODE)) < 0) {
DHD_ERROR(("%s : failed to set call pno_set (err %d) in firmware\n",
__FUNCTION__, err));
goto exit;
} else {
/* we need to return mscan */
mscan = err;
}
if (tot_nchan > 0) {
if ((err = _dhd_pno_cfg(dhd, _chan_list, tot_nchan)) < 0) {
DHD_ERROR(("%s : failed to set call pno_cfg (err %d) in firmware\n",
__FUNCTION__, err));
goto exit;
}
}
if (_pno_state->pno_status == DHD_PNO_DISABLED) {
if ((err = _dhd_pno_enable(dhd, PNO_ON)) < 0)
DHD_ERROR(("%s : failed to enable PNO\n", __FUNCTION__));
}
exit:
/* clear mode in case of error */
if (err < 0)
_pno_state->pno_mode &= ~DHD_PNO_BATCH_MODE;
else {
/* return #max scan firmware can do */
err = mscan;
}
kfree(p_ssid_list);
return err;
}
#ifdef GSCAN_SUPPORT
static void dhd_pno_reset_cfg_gscan(dhd_pno_params_t *_params,
dhd_pno_status_info_t *_pno_state, uint8 flags)
{
DHD_PNO(("%s enter\n", __FUNCTION__));
if (flags & GSCAN_FLUSH_SCAN_CFG) {
_params->params_gscan.bestn = 0;
_params->params_gscan.mscan = 0;
_params->params_gscan.buffer_threshold = GSCAN_BATCH_NO_THR_SET;
_params->params_gscan.scan_fr = 0;
_params->params_gscan.send_all_results_flag = 0;
memset(_params->params_gscan.channel_bucket, 0,
_params->params_gscan.nchannel_buckets *
sizeof(struct dhd_pno_gscan_channel_bucket));
_params->params_gscan.nchannel_buckets = 0;
DHD_PNO(("Flush Scan config\n"));
}
if (flags & GSCAN_FLUSH_HOTLIST_CFG) {
struct dhd_pno_bssid *iter, *next;
if (_params->params_gscan.nbssid_hotlist > 0) {
list_for_each_entry_safe(iter, next,
&_params->params_gscan.hotlist_bssid_list, list) {
list_del(&iter->list);
kfree(iter);
}
}
_params->params_gscan.nbssid_hotlist = 0;
DHD_PNO(("Flush Hotlist Config\n"));
}
if (flags & GSCAN_FLUSH_SIGNIFICANT_CFG) {
dhd_pno_significant_bssid_t *iter, *next;
if (_params->params_gscan.nbssid_significant_change > 0) {
list_for_each_entry_safe(iter, next,
&_params->params_gscan.significant_bssid_list, list) {
list_del(&iter->list);
kfree(iter);
}
}
_params->params_gscan.nbssid_significant_change = 0;
DHD_PNO(("Flush Significant Change Config\n"));
}
if (flags & GSCAN_FLUSH_EPNO_CFG) {
dhd_epno_params_t *iter, *next;
if (_params->params_gscan.num_epno_ssid > 0) {
list_for_each_entry_safe(iter, next,
&_params->params_gscan.epno_ssid_list, list) {
list_del(&iter->list);
kfree(iter);
}
}
_params->params_gscan.num_epno_ssid = 0;
_params->params_gscan.num_visible_epno_ssid = 0;
_params->params_gscan.ssid_ext_last_used_index = 0;
DHD_PNO(("Flushed ePNO Config\n"));
}
return;
}
int dhd_pno_lock_batch_results(dhd_pub_t *dhd)
{
dhd_pno_status_info_t *_pno_state;
int err = BCME_OK;
NULL_CHECK(dhd, "dhd is NULL", err);
NULL_CHECK(dhd->pno_state, "pno_state is NULL", err);
_pno_state = PNO_GET_PNOSTATE(dhd);
mutex_lock(&_pno_state->pno_mutex);
return err;
}
void dhd_pno_unlock_batch_results(dhd_pub_t *dhd)
{
dhd_pno_status_info_t *_pno_state;
_pno_state = PNO_GET_PNOSTATE(dhd);
mutex_unlock(&_pno_state->pno_mutex);
return;
}
int dhd_wait_batch_results_complete(dhd_pub_t *dhd)
{
dhd_pno_status_info_t *_pno_state;
dhd_pno_params_t *_params;
int err = BCME_OK;
NULL_CHECK(dhd, "dhd is NULL", err);
NULL_CHECK(dhd->pno_state, "pno_state is NULL", err);
_pno_state = PNO_GET_PNOSTATE(dhd);
_params = &_pno_state->pno_params_arr[INDEX_OF_GSCAN_PARAMS];
/* Has the workqueue finished its job already?? */
if (_params->params_gscan.get_batch_flag == GSCAN_BATCH_RETRIEVAL_IN_PROGRESS) {
DHD_PNO(("%s: Waiting to complete retrieval..\n", __FUNCTION__));
wait_event_interruptible_timeout(_pno_state->batch_get_wait,
is_batch_retrieval_complete(&_params->params_gscan),
msecs_to_jiffies(GSCAN_BATCH_GET_MAX_WAIT));
} else { /* GSCAN_BATCH_RETRIEVAL_COMPLETE */
gscan_results_cache_t *iter;
uint16 num_results = 0;
mutex_lock(&_pno_state->pno_mutex);
iter = _params->params_gscan.gscan_batch_cache;
while (iter) {
num_results += iter->tot_count - iter->tot_consumed;
iter = iter->next;
}
mutex_unlock(&_pno_state->pno_mutex);
/* All results consumed/No results cached??
* Get fresh results from FW
*/
if ((_pno_state->pno_mode & DHD_PNO_GSCAN_MODE) && !num_results) {
DHD_PNO(("%s: No results cached, getting from FW..\n", __FUNCTION__));
err = dhd_retreive_batch_scan_results(dhd);
if (err == BCME_OK) {
wait_event_interruptible_timeout(_pno_state->batch_get_wait,
is_batch_retrieval_complete(&_params->params_gscan),
msecs_to_jiffies(GSCAN_BATCH_GET_MAX_WAIT));
}
}
}
DHD_PNO(("%s: Wait complete\n", __FUNCTION__));
return err;
}
static void *dhd_get_gscan_batch_results(dhd_pub_t *dhd, uint32 *len)
{
gscan_results_cache_t *iter, *results;
dhd_pno_status_info_t *_pno_state;
dhd_pno_params_t *_params;
uint16 num_scan_ids = 0, num_results = 0;
_pno_state = PNO_GET_PNOSTATE(dhd);
_params = &_pno_state->pno_params_arr[INDEX_OF_GSCAN_PARAMS];
iter = results = _params->params_gscan.gscan_batch_cache;
while (iter) {
num_results += iter->tot_count - iter->tot_consumed;
num_scan_ids++;
iter = iter->next;
}
*len = ((num_results << 16) | (num_scan_ids));
return results;
}
void * dhd_pno_get_gscan(dhd_pub_t *dhd, dhd_pno_gscan_cmd_cfg_t type,
void *info, uint32 *len)
{
void *ret = NULL;
dhd_pno_gscan_capabilities_t *ptr;
dhd_epno_params_t *epno_params;
dhd_pno_params_t *_params;
dhd_pno_status_info_t *_pno_state;
if (!dhd || !dhd->pno_state) {
DHD_ERROR(("NULL POINTER : %s\n", __FUNCTION__));
return NULL;
}
_pno_state = PNO_GET_PNOSTATE(dhd);
_params = &_pno_state->pno_params_arr[INDEX_OF_GSCAN_PARAMS];
if (!len) {
DHD_ERROR(("%s: len is NULL\n", __FUNCTION__));
return NULL;
}
switch (type) {
case DHD_PNO_GET_CAPABILITIES:
ptr = (dhd_pno_gscan_capabilities_t *)
kmalloc(sizeof(dhd_pno_gscan_capabilities_t), GFP_KERNEL);
if (!ptr)
break;
/* Hardcoding these values for now, need to get
* these values from FW, will change in a later check-in
*/
ptr->max_scan_cache_size = GSCAN_MAX_AP_CACHE;
ptr->max_scan_buckets = GSCAN_MAX_CH_BUCKETS;
ptr->max_ap_cache_per_scan = GSCAN_MAX_AP_CACHE_PER_SCAN;
ptr->max_rssi_sample_size = PFN_SWC_RSSI_WINDOW_MAX;
ptr->max_scan_reporting_threshold = 100;
ptr->max_hotlist_aps = PFN_HOTLIST_MAX_NUM_APS;
ptr->max_significant_wifi_change_aps = PFN_SWC_MAX_NUM_APS;
ptr->max_epno_ssid_crc32 = MAX_EPNO_SSID_NUM;
ptr->max_epno_hidden_ssid = MAX_EPNO_HIDDEN_SSID;
ptr->max_white_list_ssid = MAX_WHITELIST_SSID;
ret = (void *)ptr;
*len = sizeof(dhd_pno_gscan_capabilities_t);
break;
case DHD_PNO_GET_BATCH_RESULTS:
ret = dhd_get_gscan_batch_results(dhd, len);
break;
case DHD_PNO_GET_CHANNEL_LIST:
if (info) {
uint16 ch_list[WL_NUMCHANNELS];
uint32 *ptr, mem_needed, i;
int32 err, nchan = WL_NUMCHANNELS;
uint32 *gscan_band = (uint32 *) info;
uint8 band = 0;
/* No band specified?, nothing to do */
if ((*gscan_band & GSCAN_BAND_MASK) == 0) {
DHD_PNO(("No band specified\n"));
*len = 0;
break;
}
/* HAL and DHD use different bits for 2.4G and
* 5G in bitmap. Hence translating it here...
*/
if (*gscan_band & GSCAN_BG_BAND_MASK)
band |= WLC_BAND_2G;
if (*gscan_band & GSCAN_A_BAND_MASK)
band |= WLC_BAND_5G;
err = _dhd_pno_get_channels(dhd, ch_list, &nchan,
(band & GSCAN_ABG_BAND_MASK),
!(*gscan_band & GSCAN_DFS_MASK));
if (err < 0) {
DHD_ERROR(("%s: failed to get valid channel list\n",
__FUNCTION__));
*len = 0;
} else {
mem_needed = sizeof(uint32) * nchan;
ptr = (uint32 *) kmalloc(mem_needed, GFP_KERNEL);
if (!ptr) {
DHD_ERROR(("%s: Unable to malloc %d bytes\n",
__FUNCTION__, mem_needed));
break;
}
for (i = 0; i < nchan; i++) {
ptr[i] = wf_channel2mhz(ch_list[i],
(ch_list[i] <= CH_MAX_2G_CHANNEL?
WF_CHAN_FACTOR_2_4_G : WF_CHAN_FACTOR_5_G));
}
ret = ptr;
*len = mem_needed;
}
} else {
*len = 0;
DHD_ERROR(("%s: info buffer is NULL\n", __FUNCTION__));
}
break;
case DHD_PNO_GET_EPNO_SSID_ELEM:
if (_params->params_gscan.num_epno_ssid >=
(MAX_EPNO_SSID_NUM + MAX_EPNO_HIDDEN_SSID)) {
DHD_ERROR(("Excessive number of ePNO SSIDs programmed %d\n",
_params->params_gscan.num_epno_ssid));
return NULL;
}
if (!_params->params_gscan.num_epno_ssid)
INIT_LIST_HEAD(&_params->params_gscan.epno_ssid_list);
epno_params = kzalloc(sizeof(dhd_epno_params_t), GFP_KERNEL);
if (!epno_params) {
DHD_ERROR(("EPNO ssid: cannot alloc %zd bytes",
sizeof(dhd_epno_params_t)));
return NULL;
}
_params->params_gscan.num_epno_ssid++;
epno_params->index = DHD_EPNO_DEFAULT_INDEX;
list_add_tail(&epno_params->list, &_params->params_gscan.epno_ssid_list);
ret = epno_params;
default:
break;
}
return ret;
}
int dhd_pno_set_cfg_gscan(dhd_pub_t *dhd, dhd_pno_gscan_cmd_cfg_t type,
void *buf, uint8 flush)
{
int err = BCME_OK;
dhd_pno_params_t *_params;
int i;
dhd_pno_status_info_t *_pno_state;
NULL_CHECK(dhd, "dhd is NULL", err);
NULL_CHECK(dhd->pno_state, "pno_state is NULL", err);
DHD_PNO(("%s enter\n", __FUNCTION__));
_pno_state = PNO_GET_PNOSTATE(dhd);
_params = &_pno_state->pno_params_arr[INDEX_OF_GSCAN_PARAMS];
mutex_lock(&_pno_state->pno_mutex);
switch (type) {
case DHD_PNO_BATCH_SCAN_CFG_ID:
{
gscan_batch_params_t *ptr = (gscan_batch_params_t *)buf;
_params->params_gscan.bestn = ptr->bestn;
_params->params_gscan.mscan = ptr->mscan;
_params->params_gscan.buffer_threshold = ptr->buffer_threshold;
}
break;
case DHD_PNO_GEOFENCE_SCAN_CFG_ID:
{
gscan_hotlist_scan_params_t *ptr = (gscan_hotlist_scan_params_t *)buf;
struct dhd_pno_bssid *_pno_bssid;
struct bssid_t *bssid_ptr;
int8 flags;
if (flush) {
dhd_pno_reset_cfg_gscan(_params, _pno_state,
GSCAN_FLUSH_HOTLIST_CFG);
}
if (!ptr->nbssid)
break;
if (!_params->params_gscan.nbssid_hotlist)
INIT_LIST_HEAD(&_params->params_gscan.hotlist_bssid_list);
if ((_params->params_gscan.nbssid_hotlist +
ptr->nbssid) > PFN_SWC_MAX_NUM_APS) {
DHD_ERROR(("Excessive number of hotlist APs programmed %d\n",
(_params->params_gscan.nbssid_hotlist +
ptr->nbssid)));
err = BCME_RANGE;
goto exit;
}
for (i = 0, bssid_ptr = ptr->bssid; i < ptr->nbssid; i++, bssid_ptr++) {
_pno_bssid = kzalloc(sizeof(struct dhd_pno_bssid), GFP_KERNEL);
if (!_pno_bssid) {
DHD_ERROR(("_pno_bssid is NULL, cannot kalloc %zd bytes",
sizeof(struct dhd_pno_bssid)));
err = BCME_NOMEM;
goto exit;
}
memcpy(&_pno_bssid->macaddr, &bssid_ptr->macaddr, ETHER_ADDR_LEN);
flags = (int8) bssid_ptr->rssi_reporting_threshold;
_pno_bssid->flags = flags << WL_PFN_RSSI_SHIFT;
list_add_tail(&_pno_bssid->list,
&_params->params_gscan.hotlist_bssid_list);
}
_params->params_gscan.nbssid_hotlist += ptr->nbssid;
_params->params_gscan.lost_ap_window = ptr->lost_ap_window;
}
break;
case DHD_PNO_SIGNIFICANT_SCAN_CFG_ID:
{
gscan_swc_params_t *ptr = (gscan_swc_params_t *)buf;
dhd_pno_significant_bssid_t *_pno_significant_change_bssid;
wl_pfn_significant_bssid_t *significant_bssid_ptr;
if (flush) {
dhd_pno_reset_cfg_gscan(_params, _pno_state,
GSCAN_FLUSH_SIGNIFICANT_CFG);
}
if (!ptr->nbssid)
break;
if (!_params->params_gscan.nbssid_significant_change)
INIT_LIST_HEAD(&_params->params_gscan.significant_bssid_list);
if ((_params->params_gscan.nbssid_significant_change +
ptr->nbssid) > PFN_SWC_MAX_NUM_APS) {
DHD_ERROR(("Excessive number of SWC APs programmed %d\n",
(_params->params_gscan.nbssid_significant_change +
ptr->nbssid)));
err = BCME_RANGE;
goto exit;
}
for (i = 0, significant_bssid_ptr = ptr->bssid_elem_list;
i < ptr->nbssid; i++, significant_bssid_ptr++) {
_pno_significant_change_bssid =
kzalloc(sizeof(dhd_pno_significant_bssid_t),
GFP_KERNEL);
if (!_pno_significant_change_bssid) {
DHD_ERROR(("SWC bssidptr is NULL, cannot kalloc %zd bytes",
sizeof(dhd_pno_significant_bssid_t)));
err = BCME_NOMEM;
goto exit;
}
memcpy(&_pno_significant_change_bssid->BSSID,
&significant_bssid_ptr->macaddr, ETHER_ADDR_LEN);
_pno_significant_change_bssid->rssi_low_threshold =
significant_bssid_ptr->rssi_low_threshold;
_pno_significant_change_bssid->rssi_high_threshold =
significant_bssid_ptr->rssi_high_threshold;
list_add_tail(&_pno_significant_change_bssid->list,
&_params->params_gscan.significant_bssid_list);
}
_params->params_gscan.swc_nbssid_threshold = ptr->swc_threshold;
_params->params_gscan.swc_rssi_window_size = ptr->rssi_window;
_params->params_gscan.lost_ap_window = ptr->lost_ap_window;
_params->params_gscan.nbssid_significant_change += ptr->nbssid;
}
break;
case DHD_PNO_SCAN_CFG_ID:
{
int i, k;
uint16 band;
gscan_scan_params_t *ptr = (gscan_scan_params_t *)buf;
struct dhd_pno_gscan_channel_bucket *ch_bucket;
if (ptr->nchannel_buckets <= GSCAN_MAX_CH_BUCKETS) {
_params->params_gscan.nchannel_buckets = ptr->nchannel_buckets;
memcpy(_params->params_gscan.channel_bucket, ptr->channel_bucket,
_params->params_gscan.nchannel_buckets *
sizeof(struct dhd_pno_gscan_channel_bucket));
ch_bucket = _params->params_gscan.channel_bucket;
for (i = 0; i < ptr->nchannel_buckets; i++) {
band = ch_bucket[i].band;
for (k = 0; k < ptr->channel_bucket[i].num_channels; k++) {
ch_bucket[i].chan_list[k] =
wf_mhz2channel(ptr->channel_bucket[i].chan_list[k],
0);
}
ch_bucket[i].band = 0;
/* HAL and DHD use different bits for 2.4G and
* 5G in bitmap. Hence translating it here...
*/
if (band & GSCAN_BG_BAND_MASK)
ch_bucket[i].band |= WLC_BAND_2G;
if (band & GSCAN_A_BAND_MASK)
ch_bucket[i].band |= WLC_BAND_5G;
if (band & GSCAN_DFS_MASK)
ch_bucket[i].band |= GSCAN_DFS_MASK;
DHD_PNO(("band %d report_flag %d\n", ch_bucket[i].band,
ch_bucket[i].report_flag));
}
for (i = 0; i < ptr->nchannel_buckets; i++) {
ch_bucket[i].bucket_freq_multiple =
ch_bucket[i].bucket_freq_multiple/ptr->scan_fr;
ch_bucket[i].bucket_max_multiple =
ch_bucket[i].bucket_max_multiple/ptr->scan_fr;
DHD_PNO(("mult %d max_mult %d\n", ch_bucket[i].bucket_freq_multiple,
ch_bucket[i].bucket_max_multiple));
}
_params->params_gscan.scan_fr = ptr->scan_fr;
DHD_PNO(("num_buckets %d scan_fr %d\n", ptr->nchannel_buckets,
_params->params_gscan.scan_fr));
} else {
err = BCME_BADARG;
}
}
break;
case DHD_PNO_EPNO_CFG_ID:
if (flush) {
dhd_pno_reset_cfg_gscan(_params, _pno_state,
GSCAN_FLUSH_EPNO_CFG);
} else {
_params->params_gscan.num_visible_epno_ssid += *((uint16 *)buf);
}
break;
default:
err = BCME_BADARG;
break;
}
exit:
mutex_unlock(&_pno_state->pno_mutex);
return err;
}
static bool
validate_gscan_params(struct dhd_pno_gscan_params *gscan_params)
{
unsigned int i, k;
if (!gscan_params->scan_fr || !gscan_params->nchannel_buckets) {
DHD_ERROR(("%s : Scan freq - %d or number of channel buckets - %d is empty\n",
__FUNCTION__, gscan_params->scan_fr, gscan_params->nchannel_buckets));
return false;
}
for (i = 0; i < gscan_params->nchannel_buckets; i++) {
if (!gscan_params->channel_bucket[i].band) {
for (k = 0; k < gscan_params->channel_bucket[i].num_channels; k++) {
if (gscan_params->channel_bucket[i].chan_list[k] > CHANNEL_5G_MAX) {
DHD_ERROR(("%s : Unknown channel %d\n", __FUNCTION__,
gscan_params->channel_bucket[i].chan_list[k]));
return false;
}
}
}
}
return true;
}
static int
dhd_pno_set_for_gscan(dhd_pub_t *dhd, struct dhd_pno_gscan_params *gscan_params)
{
int err = BCME_OK;
int mode, i = 0, k;
uint16 _chan_list[WL_NUMCHANNELS];
int tot_nchan = 0;
int num_buckets_to_fw, tot_num_buckets, gscan_param_size;
dhd_pno_status_info_t *_pno_state = PNO_GET_PNOSTATE(dhd);
wl_pfn_gscan_ch_bucket_cfg_t *ch_bucket = NULL;
wl_pfn_gscan_cfg_t *pfn_gscan_cfg_t = NULL;
wl_pfn_significant_bssid_t *p_pfn_significant_bssid = NULL;
wl_pfn_bssid_t *p_pfn_bssid = NULL;
wlc_ssid_ext_t *pssid_list = NULL;
dhd_pno_params_t *params_legacy;
dhd_pno_params_t *_params;
params_legacy = &_pno_state->pno_params_arr[INDEX_OF_LEGACY_PARAMS];
_params = &_pno_state->pno_params_arr[INDEX_OF_GSCAN_PARAMS];
NULL_CHECK(dhd->pno_state, "pno_state is NULL", err);
NULL_CHECK(gscan_params, "gscan_params is NULL", err);
DHD_PNO(("%s enter\n", __FUNCTION__));
if (!dhd_support_sta_mode(dhd)) {
err = BCME_BADOPTION;
goto exit;
}
if (!WLS_SUPPORTED(_pno_state)) {
DHD_ERROR(("%s : wifi location service is not supported\n", __FUNCTION__));
err = BCME_UNSUPPORTED;
goto exit;
}
if (!validate_gscan_params(gscan_params)) {
DHD_ERROR(("%s : Cannot start gscan - bad params\n", __FUNCTION__));
err = BCME_BADARG;
goto exit;
}
if (!(ch_bucket = dhd_pno_gscan_create_channel_list(dhd, _pno_state,
_chan_list, &tot_num_buckets, &num_buckets_to_fw)))
goto exit;
mutex_lock(&_pno_state->pno_mutex);
/* Clear any pre-existing results in our cache
* not consumed by framework
*/
dhd_gscan_clear_all_batch_results(dhd);
if (_pno_state->pno_mode & (DHD_PNO_GSCAN_MODE | DHD_PNO_LEGACY_MODE)) {
/* store current pno_mode before disabling pno */
mode = _pno_state->pno_mode;
err = dhd_pno_clean(dhd);
if (err < 0) {
DHD_ERROR(("%s : failed to disable PNO\n", __FUNCTION__));
mutex_unlock(&_pno_state->pno_mutex);
goto exit;
}
/* restore the previous mode */
_pno_state->pno_mode = mode;
}
_pno_state->pno_mode |= DHD_PNO_GSCAN_MODE;
mutex_unlock(&_pno_state->pno_mutex);
if (_pno_state->pno_mode & DHD_PNO_LEGACY_MODE) {
pssid_list = dhd_pno_get_legacy_pno_ssid(dhd, _pno_state);
if (!pssid_list) {
err = BCME_NOMEM;
DHD_ERROR(("failed to get Legacy PNO SSID list\n"));
goto exit;
}
if ((err = _dhd_pno_add_ssid(dhd, pssid_list,
params_legacy->params_legacy.nssid)) < 0) {
DHD_ERROR(("failed to add ssid list (err %d) in firmware\n", err));
goto exit;
}
}
if ((err = _dhd_pno_set(dhd, _params, DHD_PNO_GSCAN_MODE)) < 0) {
DHD_ERROR(("failed to set call pno_set (err %d) in firmware\n", err));
goto exit;
}
gscan_param_size = sizeof(wl_pfn_gscan_cfg_t) +
(num_buckets_to_fw - 1) * sizeof(wl_pfn_gscan_ch_bucket_cfg_t);
pfn_gscan_cfg_t = (wl_pfn_gscan_cfg_t *) MALLOC(dhd->osh, gscan_param_size);
if (!pfn_gscan_cfg_t) {
DHD_ERROR(("%s: failed to malloc memory of size %d\n",
__FUNCTION__, gscan_param_size));
err = BCME_NOMEM;
goto exit;
}
pfn_gscan_cfg_t->version = WL_GSCAN_CFG_VERSION;
if (gscan_params->mscan)
pfn_gscan_cfg_t->buffer_threshold = gscan_params->buffer_threshold;
else
pfn_gscan_cfg_t->buffer_threshold = GSCAN_BATCH_NO_THR_SET;
if (gscan_params->nbssid_significant_change) {
pfn_gscan_cfg_t->swc_nbssid_threshold = gscan_params->swc_nbssid_threshold;
pfn_gscan_cfg_t->swc_rssi_window_size = gscan_params->swc_rssi_window_size;
pfn_gscan_cfg_t->lost_ap_window = gscan_params->lost_ap_window;
} else {
pfn_gscan_cfg_t->swc_nbssid_threshold = 0;
pfn_gscan_cfg_t->swc_rssi_window_size = 0;
pfn_gscan_cfg_t->lost_ap_window = 0;
}
pfn_gscan_cfg_t->flags =
(gscan_params->send_all_results_flag & GSCAN_SEND_ALL_RESULTS_MASK);
pfn_gscan_cfg_t->count_of_channel_buckets = num_buckets_to_fw;
pfn_gscan_cfg_t->retry_threshold = GSCAN_RETRY_THRESHOLD;
for (i = 0, k = 0; i < tot_num_buckets; i++) {
if (ch_bucket[i].bucket_end_index != CHANNEL_BUCKET_EMPTY_INDEX) {
pfn_gscan_cfg_t->channel_bucket[k].bucket_end_index =
ch_bucket[i].bucket_end_index;
pfn_gscan_cfg_t->channel_bucket[k].bucket_freq_multiple =
ch_bucket[i].bucket_freq_multiple;
pfn_gscan_cfg_t->channel_bucket[k].max_freq_multiple =
ch_bucket[i].max_freq_multiple;
pfn_gscan_cfg_t->channel_bucket[k].repeat =
ch_bucket[i].repeat;
pfn_gscan_cfg_t->channel_bucket[k].flag =
ch_bucket[i].flag;
k++;
}
}
tot_nchan = pfn_gscan_cfg_t->channel_bucket[num_buckets_to_fw - 1].bucket_end_index + 1;
DHD_PNO(("Total channel num %d total ch_buckets %d ch_buckets_to_fw %d \n", tot_nchan,
tot_num_buckets, num_buckets_to_fw));
if ((err = _dhd_pno_cfg(dhd, _chan_list, tot_nchan)) < 0) {
DHD_ERROR(("%s : failed to set call pno_cfg (err %d) in firmware\n",
__FUNCTION__, err));
goto exit;
}
if ((err = _dhd_pno_gscan_cfg(dhd, pfn_gscan_cfg_t, gscan_param_size)) < 0) {
DHD_ERROR(("%s : failed to set call pno_gscan_cfg (err %d) in firmware\n",
__FUNCTION__, err));
goto exit;
}
if (gscan_params->nbssid_significant_change) {
dhd_pno_significant_bssid_t *iter, *next;
p_pfn_significant_bssid = kzalloc(sizeof(wl_pfn_significant_bssid_t) *
gscan_params->nbssid_significant_change, GFP_KERNEL);
if (p_pfn_significant_bssid == NULL) {
DHD_ERROR(("%s : failed to allocate memory %zd\n",
__FUNCTION__,
sizeof(wl_pfn_significant_bssid_t) *
gscan_params->nbssid_significant_change));
err = BCME_NOMEM;
goto exit;
}
i = 0;
/* convert dhd_pno_significant_bssid_t to wl_pfn_significant_bssid_t */
list_for_each_entry_safe(iter, next, &gscan_params->significant_bssid_list, list) {
p_pfn_significant_bssid[i].rssi_low_threshold = iter->rssi_low_threshold;
p_pfn_significant_bssid[i].rssi_high_threshold = iter->rssi_high_threshold;
memcpy(&p_pfn_significant_bssid[i].macaddr, &iter->BSSID, ETHER_ADDR_LEN);
i++;
}
DHD_PNO(("nbssid_significant_change %d \n",
gscan_params->nbssid_significant_change));
err = _dhd_pno_add_significant_bssid(dhd, p_pfn_significant_bssid,
gscan_params->nbssid_significant_change);
if (err < 0) {
DHD_ERROR(("%s : failed to call _dhd_pno_add_significant_bssid(err :%d)\n",
__FUNCTION__, err));
goto exit;
}
}
if (gscan_params->nbssid_hotlist) {
struct dhd_pno_bssid *iter, *next;
wl_pfn_bssid_t *ptr;
p_pfn_bssid = (wl_pfn_bssid_t *)kzalloc(sizeof(wl_pfn_bssid_t) *
gscan_params->nbssid_hotlist, GFP_KERNEL);
if (p_pfn_bssid == NULL) {
DHD_ERROR(("%s : failed to allocate wl_pfn_bssid_t array"
" (count: %d)",
__FUNCTION__, _params->params_hotlist.nbssid));
err = BCME_ERROR;
_pno_state->pno_mode &= ~DHD_PNO_HOTLIST_MODE;
goto exit;
}
ptr = p_pfn_bssid;
/* convert dhd_pno_bssid to wl_pfn_bssid */
DHD_PNO(("nhotlist %d\n", gscan_params->nbssid_hotlist));
list_for_each_entry_safe(iter, next,
&gscan_params->hotlist_bssid_list, list) {
memcpy(&ptr->macaddr,
&iter->macaddr, ETHER_ADDR_LEN);
ptr->flags = iter->flags;
ptr++;
}
err = _dhd_pno_add_bssid(dhd, p_pfn_bssid, gscan_params->nbssid_hotlist);
if (err < 0) {
DHD_ERROR(("%s : failed to call _dhd_pno_add_bssid(err :%d)\n",
__FUNCTION__, err));
goto exit;
}
}
if (gscan_params->num_epno_ssid > 0) {
DHD_PNO(("num_epno_ssid %d\n", gscan_params->num_epno_ssid));
err = dhd_epno_set_ssid(dhd, _pno_state);
if (err < 0) {
DHD_ERROR(("failed to add ssid list (err %d) in firmware\n", err));
goto exit;
}
}
if ((err = _dhd_pno_enable(dhd, PNO_ON)) < 0)
DHD_ERROR(("%s : failed to enable PNO err %d\n", __FUNCTION__, err));
exit:
/* clear mode in case of error */
if (err < 0) {
int ret = dhd_pno_clean(dhd);
if (ret < 0) {
DHD_ERROR(("%s : failed to call dhd_pno_clean (err: %d)\n",
__FUNCTION__, ret));
} else {
_pno_state->pno_mode &= ~DHD_PNO_GSCAN_MODE;
}
}
kfree(pssid_list);
kfree(p_pfn_significant_bssid);
kfree(p_pfn_bssid);
if (pfn_gscan_cfg_t)
MFREE(dhd->osh, pfn_gscan_cfg_t, gscan_param_size);
if (ch_bucket)
MFREE(dhd->osh, ch_bucket,
(tot_num_buckets * sizeof(wl_pfn_gscan_ch_bucket_cfg_t)));
return err;
}
static wl_pfn_gscan_ch_bucket_cfg_t *
dhd_pno_gscan_create_channel_list(dhd_pub_t *dhd,
dhd_pno_status_info_t *_pno_state,
uint16 *chan_list,
uint32 *num_buckets,
uint32 *num_buckets_to_fw)
{
int i, num_channels, err, nchan = WL_NUMCHANNELS, ch_cnt;
uint16 *ptr = chan_list, max;
wl_pfn_gscan_ch_bucket_cfg_t *ch_bucket;
dhd_pno_params_t *_params = &_pno_state->pno_params_arr[INDEX_OF_GSCAN_PARAMS];
bool is_pno_legacy_running = _pno_state->pno_mode & DHD_PNO_LEGACY_MODE;
dhd_pno_gscan_channel_bucket_t *gscan_buckets = _params->params_gscan.channel_bucket;
if (is_pno_legacy_running)
*num_buckets = _params->params_gscan.nchannel_buckets + 1;
else
*num_buckets = _params->params_gscan.nchannel_buckets;
*num_buckets_to_fw = 0;
ch_bucket = (wl_pfn_gscan_ch_bucket_cfg_t *) MALLOC(dhd->osh,
((*num_buckets) * sizeof(wl_pfn_gscan_ch_bucket_cfg_t)));
if (!ch_bucket) {
DHD_ERROR(("%s: failed to malloc memory of size %zd\n",
__FUNCTION__, (*num_buckets) * sizeof(wl_pfn_gscan_ch_bucket_cfg_t)));
*num_buckets_to_fw = *num_buckets = 0;
return NULL;
}
max = gscan_buckets[0].bucket_freq_multiple;
num_channels = 0;
/* nchan is the remaining space left in chan_list buffer
* So any overflow list of channels is ignored
*/
for (i = 0; i < _params->params_gscan.nchannel_buckets && nchan; i++) {
if (!gscan_buckets[i].band) {
ch_cnt = MIN(gscan_buckets[i].num_channels, (uint8)nchan);
num_channels += ch_cnt;
memcpy(ptr, gscan_buckets[i].chan_list,
ch_cnt * sizeof(uint16));
ptr = ptr + ch_cnt;
} else {
/* get a valid channel list based on band B or A */
err = _dhd_pno_get_channels(dhd, ptr,
&nchan, (gscan_buckets[i].band & GSCAN_ABG_BAND_MASK),
!(gscan_buckets[i].band & GSCAN_DFS_MASK));
if (err < 0) {
DHD_ERROR(("%s: failed to get valid channel list(band : %d)\n",
__FUNCTION__, gscan_buckets[i].band));
MFREE(dhd->osh, ch_bucket,
((*num_buckets) * sizeof(wl_pfn_gscan_ch_bucket_cfg_t)));
*num_buckets_to_fw = *num_buckets = 0;
return NULL;
}
num_channels += nchan;
ptr = ptr + nchan;
}
ch_bucket[i].bucket_end_index = num_channels - 1;
ch_bucket[i].bucket_freq_multiple = gscan_buckets[i].bucket_freq_multiple;
ch_bucket[i].repeat = gscan_buckets[i].repeat;
ch_bucket[i].max_freq_multiple = gscan_buckets[i].bucket_max_multiple;
ch_bucket[i].flag = gscan_buckets[i].report_flag;
/* HAL and FW interpretations are opposite for this bit */
ch_bucket[i].flag ^= DHD_PNO_REPORT_NO_BATCH;
if (max < gscan_buckets[i].bucket_freq_multiple)
max = gscan_buckets[i].bucket_freq_multiple;
nchan = WL_NUMCHANNELS - num_channels;
*num_buckets_to_fw = *num_buckets_to_fw + 1;
DHD_PNO(("end_idx %d freq_mult - %d\n",
ch_bucket[i].bucket_end_index, ch_bucket[i].bucket_freq_multiple));
}
_params->params_gscan.max_ch_bucket_freq = max;
/* Legacy PNO maybe running, which means we need to create a legacy PNO bucket
* Get GCF of Legacy PNO and Gscan scanfreq
*/
if (is_pno_legacy_running) {
dhd_pno_params_t *_params1 = &_pno_state->pno_params_arr[INDEX_OF_LEGACY_PARAMS];
uint16 *legacy_chan_list = _params1->params_legacy.chan_list;
uint16 common_freq;
uint32 legacy_bucket_idx = _params->params_gscan.nchannel_buckets;
/* If no space is left then only gscan buckets will be sent to FW */
if (nchan) {
common_freq = gcd(_params->params_gscan.scan_fr,
_params1->params_legacy.scan_fr);
max = gscan_buckets[0].bucket_freq_multiple;
/* GSCAN buckets */
for (i = 0; i < _params->params_gscan.nchannel_buckets; i++) {
ch_bucket[i].bucket_freq_multiple *= _params->params_gscan.scan_fr;
ch_bucket[i].bucket_freq_multiple /= common_freq;
if (max < gscan_buckets[i].bucket_freq_multiple)
max = gscan_buckets[i].bucket_freq_multiple;
}
/* Legacy PNO bucket */
ch_bucket[legacy_bucket_idx].bucket_freq_multiple =
_params1->params_legacy.scan_fr;
ch_bucket[legacy_bucket_idx].bucket_freq_multiple /=
common_freq;
_params->params_gscan.max_ch_bucket_freq = MAX(max,
ch_bucket[legacy_bucket_idx].bucket_freq_multiple);
ch_bucket[legacy_bucket_idx].flag = CH_BUCKET_REPORT_REGULAR;
/* Now add channels to the legacy scan bucket */
for (i = 0; i < _params1->params_legacy.nchan && nchan; i++, nchan--) {
ptr[i] = legacy_chan_list[i];
num_channels++;
}
ch_bucket[legacy_bucket_idx].bucket_end_index = num_channels - 1;
*num_buckets_to_fw = *num_buckets_to_fw + 1;
DHD_PNO(("end_idx %d freq_mult - %d\n",
ch_bucket[legacy_bucket_idx].bucket_end_index,
ch_bucket[legacy_bucket_idx].bucket_freq_multiple));
}
}
return ch_bucket;
}
static int dhd_pno_stop_for_gscan(dhd_pub_t *dhd)
{
int err = BCME_OK;
int mode;
dhd_pno_status_info_t *_pno_state;
wlc_ssid_ext_t *pssid_list = NULL;
_pno_state = PNO_GET_PNOSTATE(dhd);
DHD_PNO(("%s enter\n", __FUNCTION__));
if (!dhd_support_sta_mode(dhd)) {
err = BCME_BADOPTION;
goto exit;
}
if (!WLS_SUPPORTED(_pno_state)) {
DHD_ERROR(("%s : wifi location service is not supported\n",
__FUNCTION__));
err = BCME_UNSUPPORTED;
goto exit;
}
if (!(_pno_state->pno_mode & DHD_PNO_GSCAN_MODE)) {
DHD_ERROR(("%s : GSCAN is not enabled\n", __FUNCTION__));
goto exit;
}
if (_pno_state->pno_params_arr[INDEX_OF_GSCAN_PARAMS].params_gscan.mscan) {
/* retrieve the batching data from firmware into host */
err = dhd_wait_batch_results_complete(dhd);
if (err != BCME_OK)
goto exit;
}
mutex_lock(&_pno_state->pno_mutex);
mode = _pno_state->pno_mode & ~DHD_PNO_GSCAN_MODE;
err = dhd_pno_clean(dhd);
if (err < 0) {
DHD_ERROR(("%s : failed to call dhd_pno_clean (err: %d)\n",
__FUNCTION__, err));
mutex_unlock(&_pno_state->pno_mutex);
return err;
}
_pno_state->pno_mode = mode;
mutex_unlock(&_pno_state->pno_mutex);
_pno_state->pno_params_arr[INDEX_OF_GSCAN_PARAMS].params_gscan.ssid_ext_last_used_index = 0;
/* Reprogram Legacy PNO if it was running */
if (_pno_state->pno_mode & DHD_PNO_LEGACY_MODE) {
struct dhd_pno_legacy_params *params_legacy;
uint16 chan_list[WL_NUMCHANNELS];
params_legacy = &(_pno_state->pno_params_arr[INDEX_OF_LEGACY_PARAMS].params_legacy);
_pno_state->pno_mode &= ~DHD_PNO_LEGACY_MODE;
pssid_list = dhd_pno_get_legacy_pno_ssid(dhd, _pno_state);
if (!pssid_list) {
err = BCME_NOMEM;
DHD_ERROR(("failed to get Legacy PNO SSID list\n"));
goto exit;
}
DHD_PNO(("Restarting Legacy PNO SSID scan...\n"));
memcpy(chan_list, params_legacy->chan_list,
(params_legacy->nchan * sizeof(uint16)));
err = dhd_pno_set_for_ssid(dhd, pssid_list, params_legacy->nssid,
params_legacy->scan_fr, params_legacy->pno_repeat,
params_legacy->pno_freq_expo_max, chan_list,
params_legacy->nchan);
if (err < 0) {
DHD_ERROR(("%s : failed to restart legacy PNO scan(err: %d)\n",
__FUNCTION__, err));
goto exit;
}