Commit | Line | Data |
---|---|---|
84813812 LJ |
1 | /* |
2 | * Broadcom Dongle Host Driver (DHD) | |
3 | * Prefered Network Offload and Wi-Fi Location Service(WLS) code. | |
4 | * | |
5 | * Copyright (C) 1999-2019, Broadcom. | |
6 | * | |
7 | * Unless you and Broadcom execute a separate written software license | |
8 | * agreement governing use of this software, this software is licensed to you | |
9 | * under the terms of the GNU General Public License version 2 (the "GPL"), | |
10 | * available at http://www.broadcom.com/licenses/GPLv2.php, with the | |
11 | * following added to such license: | |
12 | * | |
13 | * As a special exception, the copyright holders of this software give you | |
14 | * permission to link this software with independent modules, and to copy and | |
15 | * distribute the resulting executable under terms of your choice, provided that | |
16 | * you also meet, for each linked independent module, the terms and conditions of | |
17 | * the license of that module. An independent module is a module which is not | |
18 | * derived from this software. The special exception does not apply to any | |
19 | * modifications of the software. | |
20 | * | |
21 | * Notwithstanding the above, under no circumstances may you combine this | |
22 | * software in any way with any other Broadcom software provided under a license | |
23 | * other than the GPL, without Broadcom's express prior written consent. | |
24 | * | |
25 | * | |
26 | * <<Broadcom-WL-IPTag/Open:>> | |
27 | * | |
28 | * $Id: dhd_pno.c 812762 2019-04-02 09:36:26Z $ | |
29 | */ | |
30 | ||
31 | #if defined(GSCAN_SUPPORT) && !defined(PNO_SUPPORT) | |
32 | #error "GSCAN needs PNO to be enabled!" | |
33 | #endif // endif | |
34 | ||
35 | #ifdef PNO_SUPPORT | |
36 | #include <typedefs.h> | |
37 | #include <osl.h> | |
38 | ||
39 | #include <epivers.h> | |
40 | #include <bcmutils.h> | |
41 | ||
42 | #include <bcmendian.h> | |
43 | #include <linuxver.h> | |
44 | #include <linux/init.h> | |
45 | #include <linux/kernel.h> | |
46 | #include <linux/list.h> | |
47 | #include <linux/sort.h> | |
48 | #include <dngl_stats.h> | |
49 | #include <wlioctl.h> | |
50 | ||
51 | #include <bcmevent.h> | |
52 | #include <dhd.h> | |
53 | #include <dhd_pno.h> | |
54 | #include <dhd_dbg.h> | |
55 | #ifdef GSCAN_SUPPORT | |
56 | #include <linux/gcd.h> | |
57 | #endif /* GSCAN_SUPPORT */ | |
58 | #ifdef WL_CFG80211 | |
59 | #include <wl_cfg80211.h> | |
60 | #endif /* WL_CFG80211 */ | |
61 | ||
62 | #ifdef __BIG_ENDIAN | |
63 | #include <bcmendian.h> | |
64 | #define htod32(i) (bcmswap32(i)) | |
65 | #define htod16(i) (bcmswap16(i)) | |
66 | #define dtoh32(i) (bcmswap32(i)) | |
67 | #define dtoh16(i) (bcmswap16(i)) | |
68 | #define htodchanspec(i) htod16(i) | |
69 | #define dtohchanspec(i) dtoh16(i) | |
70 | #else | |
71 | #define htod32(i) (i) | |
72 | #define htod16(i) (i) | |
73 | #define dtoh32(i) (i) | |
74 | #define dtoh16(i) (i) | |
75 | #define htodchanspec(i) (i) | |
76 | #define dtohchanspec(i) (i) | |
77 | #endif /* IL_BIGENDINA */ | |
78 | ||
79 | #define NULL_CHECK(p, s, err) \ | |
80 | do { \ | |
81 | if (!(p)) { \ | |
82 | printf("NULL POINTER (%s) : %s\n", __FUNCTION__, (s)); \ | |
83 | err = BCME_ERROR; \ | |
84 | return err; \ | |
85 | } \ | |
86 | } while (0) | |
87 | #define PNO_GET_PNOSTATE(dhd) ((dhd_pno_status_info_t *)dhd->pno_state) | |
88 | ||
89 | #define PNO_BESTNET_LEN WLC_IOCTL_MEDLEN | |
90 | ||
91 | #define PNO_ON 1 | |
92 | #define PNO_OFF 0 | |
93 | #define CHANNEL_2G_MIN 1 | |
94 | #define CHANNEL_2G_MAX 14 | |
95 | #define CHANNEL_5G_MIN 34 | |
96 | #define CHANNEL_5G_MAX 165 | |
97 | #define IS_2G_CHANNEL(ch) ((ch >= CHANNEL_2G_MIN) && \ | |
98 | (ch <= CHANNEL_2G_MAX)) | |
99 | #define IS_5G_CHANNEL(ch) ((ch >= CHANNEL_5G_MIN) && \ | |
100 | (ch <= CHANNEL_5G_MAX)) | |
101 | #define MAX_NODE_CNT 5 | |
102 | #define WLS_SUPPORTED(pno_state) (pno_state->wls_supported == TRUE) | |
103 | #define TIME_DIFF(timestamp1, timestamp2) (abs((uint32)(timestamp1/1000) \ | |
104 | - (uint32)(timestamp2/1000))) | |
105 | #define TIME_DIFF_MS(timestamp1, timestamp2) (abs((uint32)(timestamp1) \ | |
106 | - (uint32)(timestamp2))) | |
107 | #define TIMESPEC_TO_US(ts) (((uint64)(ts).tv_sec * USEC_PER_SEC) + \ | |
108 | (ts).tv_nsec / NSEC_PER_USEC) | |
109 | ||
110 | #define ENTRY_OVERHEAD strlen("bssid=\nssid=\nfreq=\nlevel=\nage=\ndist=\ndistSd=\n====") | |
111 | #define TIME_MIN_DIFF 5 | |
112 | ||
113 | #define EVENT_DATABUF_MAXLEN (512 - sizeof(bcm_event_t)) | |
114 | #define EVENT_MAX_NETCNT_V1 \ | |
115 | ((EVENT_DATABUF_MAXLEN - sizeof(wl_pfn_scanresults_v1_t)) \ | |
116 | / sizeof(wl_pfn_net_info_v1_t) + 1) | |
117 | #define EVENT_MAX_NETCNT_V2 \ | |
118 | ((EVENT_DATABUF_MAXLEN - sizeof(wl_pfn_scanresults_v2_t)) \ | |
119 | / sizeof(wl_pfn_net_info_v2_t) + 1) | |
120 | ||
121 | #ifdef GSCAN_SUPPORT | |
122 | static int _dhd_pno_flush_ssid(dhd_pub_t *dhd); | |
123 | static wl_pfn_gscan_ch_bucket_cfg_t * | |
124 | dhd_pno_gscan_create_channel_list(dhd_pub_t *dhd, dhd_pno_status_info_t *pno_state, | |
125 | uint16 *chan_list, uint32 *num_buckets, uint32 *num_buckets_to_fw); | |
126 | #endif /* GSCAN_SUPPORT */ | |
127 | ||
128 | static int dhd_pno_set_legacy_pno(dhd_pub_t *dhd, uint16 scan_fr, int pno_repeat, | |
129 | int pno_freq_expo_max, uint16 *channel_list, int nchan); | |
130 | ||
131 | static inline bool | |
132 | is_dfs(dhd_pub_t *dhd, uint16 channel) | |
133 | { | |
134 | u32 ch; | |
135 | s32 err; | |
136 | u8 buf[32]; | |
137 | ||
138 | ch = wl_ch_host_to_driver(channel); | |
139 | err = dhd_iovar(dhd, 0, "per_chan_info", (char *)&ch, | |
140 | sizeof(u32), buf, sizeof(buf), FALSE); | |
141 | if (unlikely(err)) { | |
142 | DHD_ERROR(("get per chan info failed:%d\n", err)); | |
143 | return FALSE; | |
144 | } | |
145 | /* Check the channel flags returned by fw */ | |
146 | if (*((u32 *)buf) & WL_CHAN_PASSIVE) { | |
147 | return TRUE; | |
148 | } | |
149 | return FALSE; | |
150 | } | |
151 | ||
152 | int | |
153 | dhd_pno_clean(dhd_pub_t *dhd) | |
154 | { | |
155 | int pfn = 0; | |
156 | int err; | |
157 | dhd_pno_status_info_t *_pno_state; | |
158 | NULL_CHECK(dhd, "dhd is NULL", err); | |
159 | NULL_CHECK(dhd->pno_state, "pno_state is NULL", err); | |
160 | _pno_state = PNO_GET_PNOSTATE(dhd); | |
161 | DHD_PNO(("%s enter\n", __FUNCTION__)); | |
162 | /* Disable PNO */ | |
163 | err = dhd_iovar(dhd, 0, "pfn", (char *)&pfn, sizeof(pfn), NULL, 0, TRUE); | |
164 | if (err < 0) { | |
165 | DHD_ERROR(("%s : failed to execute pfn(error : %d)\n", | |
166 | __FUNCTION__, err)); | |
167 | goto exit; | |
168 | } | |
169 | _pno_state->pno_status = DHD_PNO_DISABLED; | |
170 | err = dhd_iovar(dhd, 0, "pfnclear", NULL, 0, NULL, 0, TRUE); | |
171 | if (err < 0) { | |
172 | DHD_ERROR(("%s : failed to execute pfnclear(error : %d)\n", | |
173 | __FUNCTION__, err)); | |
174 | } | |
175 | exit: | |
176 | return err; | |
177 | } | |
178 | ||
179 | bool | |
180 | dhd_is_pno_supported(dhd_pub_t *dhd) | |
181 | { | |
182 | dhd_pno_status_info_t *_pno_state; | |
183 | ||
184 | if (!dhd || !dhd->pno_state) { | |
185 | DHD_ERROR(("NULL POINTER : %s\n", | |
186 | __FUNCTION__)); | |
187 | return FALSE; | |
188 | } | |
189 | _pno_state = PNO_GET_PNOSTATE(dhd); | |
190 | return WLS_SUPPORTED(_pno_state); | |
191 | } | |
192 | ||
193 | bool | |
194 | dhd_is_legacy_pno_enabled(dhd_pub_t *dhd) | |
195 | { | |
196 | dhd_pno_status_info_t *_pno_state; | |
197 | ||
198 | if (!dhd || !dhd->pno_state) { | |
199 | DHD_ERROR(("NULL POINTER : %s\n", | |
200 | __FUNCTION__)); | |
201 | return FALSE; | |
202 | } | |
203 | _pno_state = PNO_GET_PNOSTATE(dhd); | |
204 | return ((_pno_state->pno_mode & DHD_PNO_LEGACY_MODE) != 0); | |
205 | } | |
206 | ||
207 | #ifdef GSCAN_SUPPORT | |
208 | static uint64 | |
209 | convert_fw_rel_time_to_systime(struct osl_timespec *ts, uint32 fw_ts_ms) | |
210 | { | |
211 | return ((uint64)(TIMESPEC_TO_US(*ts)) - (uint64)(fw_ts_ms * 1000)); | |
212 | } | |
213 | ||
214 | static void | |
215 | dhd_pno_idx_to_ssid(struct dhd_pno_gscan_params *gscan_params, | |
216 | dhd_epno_results_t *res, uint32 idx) | |
217 | { | |
218 | dhd_pno_ssid_t *iter, *next; | |
219 | int i; | |
220 | ||
221 | /* If idx doesn't make sense */ | |
222 | if (idx >= gscan_params->epno_cfg.num_epno_ssid) { | |
223 | DHD_ERROR(("No match, idx %d num_ssid %d\n", idx, | |
224 | gscan_params->epno_cfg.num_epno_ssid)); | |
225 | goto exit; | |
226 | } | |
227 | ||
228 | if (gscan_params->epno_cfg.num_epno_ssid > 0) { | |
229 | i = 0; | |
230 | ||
231 | GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); | |
232 | list_for_each_entry_safe(iter, next, | |
233 | &gscan_params->epno_cfg.epno_ssid_list, list) { | |
234 | GCC_DIAGNOSTIC_POP(); | |
235 | if (i++ == idx) { | |
236 | memcpy(res->ssid, iter->SSID, iter->SSID_len); | |
237 | res->ssid_len = iter->SSID_len; | |
238 | return; | |
239 | } | |
240 | } | |
241 | } | |
242 | exit: | |
243 | /* If we are here then there was no match */ | |
244 | res->ssid[0] = '\0'; | |
245 | res->ssid_len = 0; | |
246 | return; | |
247 | } | |
248 | ||
249 | /* Translate HAL flag bitmask to BRCM FW flag bitmask */ | |
250 | void | |
251 | dhd_pno_translate_epno_fw_flags(uint32 *flags) | |
252 | { | |
253 | uint32 in_flags, fw_flags = 0; | |
254 | in_flags = *flags; | |
255 | ||
256 | if (in_flags & DHD_EPNO_A_BAND_TRIG) { | |
257 | fw_flags |= WL_PFN_SSID_A_BAND_TRIG; | |
258 | } | |
259 | ||
260 | if (in_flags & DHD_EPNO_BG_BAND_TRIG) { | |
261 | fw_flags |= WL_PFN_SSID_BG_BAND_TRIG; | |
262 | } | |
263 | ||
264 | if (!(in_flags & DHD_EPNO_STRICT_MATCH) && | |
265 | !(in_flags & DHD_EPNO_HIDDEN_SSID)) { | |
266 | fw_flags |= WL_PFN_SSID_IMPRECISE_MATCH; | |
267 | } | |
268 | ||
269 | if (in_flags & DHD_EPNO_SAME_NETWORK) { | |
270 | fw_flags |= WL_PFN_SSID_SAME_NETWORK; | |
271 | } | |
272 | ||
273 | /* Add any hard coded flags needed */ | |
274 | fw_flags |= WL_PFN_SUPPRESS_AGING_MASK; | |
275 | *flags = fw_flags; | |
276 | ||
277 | return; | |
278 | } | |
279 | ||
280 | /* Translate HAL auth bitmask to BRCM FW bitmask */ | |
281 | void | |
282 | dhd_pno_set_epno_auth_flag(uint32 *wpa_auth) | |
283 | { | |
284 | switch (*wpa_auth) { | |
285 | case DHD_PNO_AUTH_CODE_OPEN: | |
286 | *wpa_auth = WPA_AUTH_DISABLED; | |
287 | break; | |
288 | case DHD_PNO_AUTH_CODE_PSK: | |
289 | *wpa_auth = (WPA_AUTH_PSK | WPA2_AUTH_PSK); | |
290 | break; | |
291 | case DHD_PNO_AUTH_CODE_EAPOL: | |
292 | *wpa_auth = ~WPA_AUTH_NONE; | |
293 | break; | |
294 | default: | |
295 | DHD_ERROR(("%s: Unknown auth %d", __FUNCTION__, *wpa_auth)); | |
296 | *wpa_auth = WPA_AUTH_PFN_ANY; | |
297 | break; | |
298 | } | |
299 | return; | |
300 | } | |
301 | ||
302 | /* Cleanup all results */ | |
303 | static void | |
304 | dhd_gscan_clear_all_batch_results(dhd_pub_t *dhd) | |
305 | { | |
306 | struct dhd_pno_gscan_params *gscan_params; | |
307 | dhd_pno_status_info_t *_pno_state; | |
308 | gscan_results_cache_t *iter; | |
309 | ||
310 | _pno_state = PNO_GET_PNOSTATE(dhd); | |
311 | gscan_params = &_pno_state->pno_params_arr[INDEX_OF_GSCAN_PARAMS].params_gscan; | |
312 | iter = gscan_params->gscan_batch_cache; | |
313 | /* Mark everything as consumed */ | |
314 | while (iter) { | |
315 | iter->tot_consumed = iter->tot_count; | |
316 | iter = iter->next; | |
317 | } | |
318 | dhd_gscan_batch_cache_cleanup(dhd); | |
319 | return; | |
320 | } | |
321 | ||
322 | static int | |
323 | _dhd_pno_gscan_cfg(dhd_pub_t *dhd, wl_pfn_gscan_cfg_t *pfncfg_gscan_param, int size) | |
324 | { | |
325 | int err = BCME_OK; | |
326 | NULL_CHECK(dhd, "dhd is NULL", err); | |
327 | ||
328 | DHD_PNO(("%s enter\n", __FUNCTION__)); | |
329 | ||
330 | err = dhd_iovar(dhd, 0, "pfn_gscan_cfg", (char *)pfncfg_gscan_param, size, NULL, 0, TRUE); | |
331 | if (err < 0) { | |
332 | DHD_ERROR(("%s : failed to execute pfncfg_gscan_param\n", __FUNCTION__)); | |
333 | goto exit; | |
334 | } | |
335 | exit: | |
336 | return err; | |
337 | } | |
338 | ||
339 | static int | |
340 | _dhd_pno_flush_ssid(dhd_pub_t *dhd) | |
341 | { | |
342 | int err; | |
343 | wl_pfn_t pfn_elem; | |
344 | memset(&pfn_elem, 0, sizeof(wl_pfn_t)); | |
345 | pfn_elem.flags = htod32(WL_PFN_FLUSH_ALL_SSIDS); | |
346 | ||
347 | err = dhd_iovar(dhd, 0, "pfn_add", (char *)&pfn_elem, sizeof(wl_pfn_t), NULL, 0, TRUE); | |
348 | if (err < 0) { | |
349 | DHD_ERROR(("%s : failed to execute pfn_add\n", __FUNCTION__)); | |
350 | } | |
351 | return err; | |
352 | } | |
353 | ||
354 | static bool | |
355 | is_batch_retrieval_complete(struct dhd_pno_gscan_params *gscan_params) | |
356 | { | |
357 | smp_rmb(); | |
358 | return (gscan_params->get_batch_flag == GSCAN_BATCH_RETRIEVAL_COMPLETE); | |
359 | } | |
360 | #endif /* GSCAN_SUPPORT */ | |
361 | ||
362 | static int | |
363 | _dhd_pno_suspend(dhd_pub_t *dhd) | |
364 | { | |
365 | int err; | |
366 | int suspend = 1; | |
367 | dhd_pno_status_info_t *_pno_state; | |
368 | NULL_CHECK(dhd, "dhd is NULL", err); | |
369 | NULL_CHECK(dhd->pno_state, "pno_state is NULL", err); | |
370 | ||
371 | DHD_PNO(("%s enter\n", __FUNCTION__)); | |
372 | _pno_state = PNO_GET_PNOSTATE(dhd); | |
373 | err = dhd_iovar(dhd, 0, "pfn_suspend", (char *)&suspend, sizeof(suspend), NULL, 0, TRUE); | |
374 | if (err < 0) { | |
375 | DHD_ERROR(("%s : failed to suspend pfn(error :%d)\n", __FUNCTION__, err)); | |
376 | goto exit; | |
377 | ||
378 | } | |
379 | _pno_state->pno_status = DHD_PNO_SUSPEND; | |
380 | exit: | |
381 | return err; | |
382 | } | |
383 | static int | |
384 | _dhd_pno_enable(dhd_pub_t *dhd, int enable) | |
385 | { | |
386 | int err = BCME_OK; | |
387 | dhd_pno_status_info_t *_pno_state; | |
388 | NULL_CHECK(dhd, "dhd is NULL", err); | |
389 | NULL_CHECK(dhd->pno_state, "pno_state is NULL", err); | |
390 | _pno_state = PNO_GET_PNOSTATE(dhd); | |
391 | DHD_PNO(("%s enter\n", __FUNCTION__)); | |
392 | ||
393 | if (enable & 0xfffe) { | |
394 | DHD_ERROR(("%s invalid value\n", __FUNCTION__)); | |
395 | err = BCME_BADARG; | |
396 | goto exit; | |
397 | } | |
398 | if (!dhd_support_sta_mode(dhd)) { | |
399 | DHD_ERROR(("PNO is not allowed for non-STA mode")); | |
400 | err = BCME_BADOPTION; | |
401 | goto exit; | |
402 | } | |
403 | if (enable) { | |
404 | if ((_pno_state->pno_mode & DHD_PNO_LEGACY_MODE) && | |
405 | dhd_is_associated(dhd, 0, NULL)) { | |
406 | DHD_ERROR(("%s Legacy PNO mode cannot be enabled " | |
407 | "in assoc mode , ignore it\n", __FUNCTION__)); | |
408 | err = BCME_BADOPTION; | |
409 | goto exit; | |
410 | } | |
411 | } | |
412 | /* Enable/Disable PNO */ | |
413 | err = dhd_iovar(dhd, 0, "pfn", (char *)&enable, sizeof(enable), NULL, 0, TRUE); | |
414 | if (err < 0) { | |
415 | DHD_ERROR(("%s : failed to execute pfn_set - %d\n", __FUNCTION__, err)); | |
416 | goto exit; | |
417 | } | |
418 | _pno_state->pno_status = (enable)? | |
419 | DHD_PNO_ENABLED : DHD_PNO_DISABLED; | |
420 | if (!enable) | |
421 | _pno_state->pno_mode = DHD_PNO_NONE_MODE; | |
422 | ||
423 | DHD_PNO(("%s set pno as %s\n", | |
424 | __FUNCTION__, enable ? "Enable" : "Disable")); | |
425 | exit: | |
426 | return err; | |
427 | } | |
428 | ||
429 | static int | |
430 | _dhd_pno_set(dhd_pub_t *dhd, const dhd_pno_params_t *pno_params, dhd_pno_mode_t mode) | |
431 | { | |
432 | int err = BCME_OK; | |
433 | wl_pfn_param_t pfn_param; | |
434 | dhd_pno_params_t *_params; | |
435 | dhd_pno_status_info_t *_pno_state; | |
436 | bool combined_scan = FALSE; | |
437 | DHD_PNO(("%s enter\n", __FUNCTION__)); | |
438 | ||
439 | NULL_CHECK(dhd, "dhd is NULL", err); | |
440 | NULL_CHECK(dhd->pno_state, "pno_state is NULL", err); | |
441 | _pno_state = PNO_GET_PNOSTATE(dhd); | |
442 | ||
443 | memset(&pfn_param, 0, sizeof(pfn_param)); | |
444 | ||
445 | /* set pfn parameters */ | |
446 | pfn_param.version = htod32(PFN_VERSION); | |
447 | pfn_param.flags = ((PFN_LIST_ORDER << SORT_CRITERIA_BIT) | | |
448 | (ENABLE << IMMEDIATE_SCAN_BIT) | (ENABLE << REPORT_SEPERATELY_BIT)); | |
449 | if (mode == DHD_PNO_LEGACY_MODE) { | |
450 | /* check and set extra pno params */ | |
451 | if ((pno_params->params_legacy.pno_repeat != 0) || | |
452 | (pno_params->params_legacy.pno_freq_expo_max != 0)) { | |
453 | pfn_param.flags |= htod16(ENABLE << ENABLE_ADAPTSCAN_BIT); | |
454 | pfn_param.repeat = (uchar) (pno_params->params_legacy.pno_repeat); | |
455 | pfn_param.exp = (uchar) (pno_params->params_legacy.pno_freq_expo_max); | |
456 | } | |
457 | /* set up pno scan fr */ | |
458 | if (pno_params->params_legacy.scan_fr != 0) | |
459 | pfn_param.scan_freq = htod32(pno_params->params_legacy.scan_fr); | |
460 | if (_pno_state->pno_mode & DHD_PNO_BATCH_MODE) { | |
461 | DHD_PNO(("will enable combined scan with BATCHIG SCAN MODE\n")); | |
462 | mode |= DHD_PNO_BATCH_MODE; | |
463 | combined_scan = TRUE; | |
464 | } else if (_pno_state->pno_mode & DHD_PNO_HOTLIST_MODE) { | |
465 | DHD_PNO(("will enable combined scan with HOTLIST SCAN MODE\n")); | |
466 | mode |= DHD_PNO_HOTLIST_MODE; | |
467 | combined_scan = TRUE; | |
468 | } | |
469 | #ifdef GSCAN_SUPPORT | |
470 | else if (_pno_state->pno_mode & DHD_PNO_GSCAN_MODE) { | |
471 | DHD_PNO(("will enable combined scan with GSCAN SCAN MODE\n")); | |
472 | mode |= DHD_PNO_GSCAN_MODE; | |
473 | } | |
474 | #endif /* GSCAN_SUPPORT */ | |
475 | } | |
476 | if (mode & (DHD_PNO_BATCH_MODE | DHD_PNO_HOTLIST_MODE)) { | |
477 | /* Scan frequency of 30 sec */ | |
478 | pfn_param.scan_freq = htod32(30); | |
479 | /* slow adapt scan is off by default */ | |
480 | pfn_param.slow_freq = htod32(0); | |
481 | /* RSSI margin of 30 dBm */ | |
482 | pfn_param.rssi_margin = htod16(PNO_RSSI_MARGIN_DBM); | |
483 | /* Network timeout 60 sec */ | |
484 | pfn_param.lost_network_timeout = htod32(60); | |
485 | /* best n = 2 by default */ | |
486 | pfn_param.bestn = DEFAULT_BESTN; | |
487 | /* mscan m=0 by default, so not record best networks by default */ | |
488 | pfn_param.mscan = DEFAULT_MSCAN; | |
489 | /* default repeat = 10 */ | |
490 | pfn_param.repeat = DEFAULT_REPEAT; | |
491 | /* by default, maximum scan interval = 2^2 | |
492 | * scan_freq when adaptive scan is turned on | |
493 | */ | |
494 | pfn_param.exp = DEFAULT_EXP; | |
495 | if (mode == DHD_PNO_BATCH_MODE) { | |
496 | /* In case of BATCH SCAN */ | |
497 | if (pno_params->params_batch.bestn) | |
498 | pfn_param.bestn = pno_params->params_batch.bestn; | |
499 | if (pno_params->params_batch.scan_fr) | |
500 | pfn_param.scan_freq = htod32(pno_params->params_batch.scan_fr); | |
501 | if (pno_params->params_batch.mscan) | |
502 | pfn_param.mscan = pno_params->params_batch.mscan; | |
503 | /* enable broadcast scan */ | |
504 | pfn_param.flags |= (ENABLE << ENABLE_BD_SCAN_BIT); | |
505 | } else if (mode == DHD_PNO_HOTLIST_MODE) { | |
506 | /* In case of HOTLIST SCAN */ | |
507 | if (pno_params->params_hotlist.scan_fr) | |
508 | pfn_param.scan_freq = htod32(pno_params->params_hotlist.scan_fr); | |
509 | pfn_param.bestn = 0; | |
510 | pfn_param.repeat = 0; | |
511 | /* enable broadcast scan */ | |
512 | pfn_param.flags |= (ENABLE << ENABLE_BD_SCAN_BIT); | |
513 | } | |
514 | if (combined_scan) { | |
515 | /* Disable Adaptive Scan */ | |
516 | pfn_param.flags &= ~(htod16(ENABLE << ENABLE_ADAPTSCAN_BIT)); | |
517 | pfn_param.flags |= (ENABLE << ENABLE_BD_SCAN_BIT); | |
518 | pfn_param.repeat = 0; | |
519 | pfn_param.exp = 0; | |
520 | if (_pno_state->pno_mode & DHD_PNO_BATCH_MODE) { | |
521 | /* In case of Legacy PNO + BATCH SCAN */ | |
522 | _params = &(_pno_state->pno_params_arr[INDEX_OF_BATCH_PARAMS]); | |
523 | if (_params->params_batch.bestn) | |
524 | pfn_param.bestn = _params->params_batch.bestn; | |
525 | if (_params->params_batch.scan_fr) | |
526 | pfn_param.scan_freq = htod32(_params->params_batch.scan_fr); | |
527 | if (_params->params_batch.mscan) | |
528 | pfn_param.mscan = _params->params_batch.mscan; | |
529 | } else if (_pno_state->pno_mode & DHD_PNO_HOTLIST_MODE) { | |
530 | /* In case of Legacy PNO + HOTLIST SCAN */ | |
531 | _params = &(_pno_state->pno_params_arr[INDEX_OF_HOTLIST_PARAMS]); | |
532 | if (_params->params_hotlist.scan_fr) | |
533 | pfn_param.scan_freq = htod32(_params->params_hotlist.scan_fr); | |
534 | pfn_param.bestn = 0; | |
535 | pfn_param.repeat = 0; | |
536 | } | |
537 | } | |
538 | } | |
539 | #ifdef GSCAN_SUPPORT | |
540 | if (mode & DHD_PNO_GSCAN_MODE) { | |
541 | uint32 lost_network_timeout; | |
542 | ||
543 | pfn_param.scan_freq = htod32(pno_params->params_gscan.scan_fr); | |
544 | if (pno_params->params_gscan.mscan) { | |
545 | pfn_param.bestn = pno_params->params_gscan.bestn; | |
546 | pfn_param.mscan = pno_params->params_gscan.mscan; | |
547 | pfn_param.flags |= (ENABLE << ENABLE_BD_SCAN_BIT); | |
548 | } | |
549 | /* RSSI margin of 30 dBm */ | |
550 | pfn_param.rssi_margin = htod16(PNO_RSSI_MARGIN_DBM); | |
551 | pfn_param.repeat = 0; | |
552 | pfn_param.exp = 0; | |
553 | pfn_param.slow_freq = 0; | |
554 | pfn_param.flags |= htod16(ENABLE << ENABLE_ADAPTSCAN_BIT); | |
555 | ||
556 | if (_pno_state->pno_mode & DHD_PNO_LEGACY_MODE) { | |
557 | dhd_pno_params_t *params; | |
558 | ||
559 | params = &(_pno_state->pno_params_arr[INDEX_OF_LEGACY_PARAMS]); | |
560 | ||
561 | pfn_param.scan_freq = gcd(pno_params->params_gscan.scan_fr, | |
562 | params->params_legacy.scan_fr); | |
563 | ||
564 | if ((params->params_legacy.pno_repeat != 0) || | |
565 | (params->params_legacy.pno_freq_expo_max != 0)) { | |
566 | pfn_param.repeat = (uchar) (params->params_legacy.pno_repeat); | |
567 | pfn_param.exp = (uchar) (params->params_legacy.pno_freq_expo_max); | |
568 | } | |
569 | } | |
570 | ||
571 | lost_network_timeout = (pno_params->params_gscan.max_ch_bucket_freq * | |
572 | pfn_param.scan_freq * | |
573 | pno_params->params_gscan.lost_ap_window); | |
574 | if (lost_network_timeout) { | |
575 | pfn_param.lost_network_timeout = htod32(MIN(lost_network_timeout, | |
576 | GSCAN_MIN_BSSID_TIMEOUT)); | |
577 | } else { | |
578 | pfn_param.lost_network_timeout = htod32(GSCAN_MIN_BSSID_TIMEOUT); | |
579 | } | |
580 | } else | |
581 | #endif /* GSCAN_SUPPORT */ | |
582 | { | |
583 | if (pfn_param.scan_freq < htod32(PNO_SCAN_MIN_FW_SEC) || | |
584 | pfn_param.scan_freq > htod32(PNO_SCAN_MAX_FW_SEC)) { | |
585 | DHD_ERROR(("%s pno freq(%d sec) is not valid \n", | |
586 | __FUNCTION__, PNO_SCAN_MIN_FW_SEC)); | |
587 | err = BCME_BADARG; | |
588 | goto exit; | |
589 | } | |
590 | } | |
591 | ||
592 | err = dhd_set_rand_mac_oui(dhd); | |
593 | /* Ignore if chip doesnt support the feature */ | |
594 | if (err < 0 && err != BCME_UNSUPPORTED) { | |
595 | DHD_ERROR(("%s : failed to set random mac for PNO scan, %d\n", __FUNCTION__, err)); | |
596 | goto exit; | |
597 | } | |
598 | ||
599 | #ifdef GSCAN_SUPPORT | |
600 | if (mode == DHD_PNO_BATCH_MODE || | |
601 | ((mode & DHD_PNO_GSCAN_MODE) && pno_params->params_gscan.mscan)) | |
602 | #else | |
603 | if (mode == DHD_PNO_BATCH_MODE) | |
604 | #endif /* GSCAN_SUPPORT */ | |
605 | { | |
606 | int _tmp = pfn_param.bestn; | |
607 | /* set bestn to calculate the max mscan which firmware supports */ | |
608 | err = dhd_iovar(dhd, 0, "pfnmem", (char *)&_tmp, sizeof(_tmp), NULL, 0, TRUE); | |
609 | if (err < 0) { | |
610 | DHD_ERROR(("%s : failed to set pfnmem\n", __FUNCTION__)); | |
611 | goto exit; | |
612 | } | |
613 | /* get max mscan which the firmware supports */ | |
614 | err = dhd_iovar(dhd, 0, "pfnmem", NULL, 0, (char *)&_tmp, sizeof(_tmp), FALSE); | |
615 | if (err < 0) { | |
616 | DHD_ERROR(("%s : failed to get pfnmem\n", __FUNCTION__)); | |
617 | goto exit; | |
618 | } | |
619 | pfn_param.mscan = MIN(pfn_param.mscan, _tmp); | |
620 | DHD_PNO((" returned mscan : %d, set bestn : %d mscan %d\n", _tmp, pfn_param.bestn, | |
621 | pfn_param.mscan)); | |
622 | } | |
623 | err = dhd_iovar(dhd, 0, "pfn_set", (char *)&pfn_param, sizeof(pfn_param), NULL, 0, TRUE); | |
624 | if (err < 0) { | |
625 | DHD_ERROR(("%s : failed to execute pfn_set %d\n", __FUNCTION__, err)); | |
626 | goto exit; | |
627 | } | |
628 | /* need to return mscan if this is for batch scan instead of err */ | |
629 | err = (mode == DHD_PNO_BATCH_MODE)? pfn_param.mscan : err; | |
630 | exit: | |
631 | return err; | |
632 | } | |
633 | ||
634 | static int | |
635 | _dhd_pno_add_ssid(dhd_pub_t *dhd, struct list_head* ssid_list, int nssid) | |
636 | { | |
637 | int err = BCME_OK; | |
638 | int i = 0, mem_needed; | |
639 | wl_pfn_t *pfn_elem_buf; | |
640 | struct dhd_pno_ssid *iter, *next; | |
641 | ||
642 | NULL_CHECK(dhd, "dhd is NULL", err); | |
643 | if (!nssid) { | |
644 | NULL_CHECK(ssid_list, "ssid list is NULL", err); | |
645 | return BCME_ERROR; | |
646 | } | |
647 | mem_needed = (sizeof(wl_pfn_t) * nssid); | |
648 | pfn_elem_buf = (wl_pfn_t *) MALLOCZ(dhd->osh, mem_needed); | |
649 | if (!pfn_elem_buf) { | |
650 | DHD_ERROR(("%s: Can't malloc %d bytes!\n", __FUNCTION__, mem_needed)); | |
651 | return BCME_NOMEM; | |
652 | } | |
653 | ||
654 | GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); | |
655 | list_for_each_entry_safe(iter, next, ssid_list, list) { | |
656 | GCC_DIAGNOSTIC_POP(); | |
657 | pfn_elem_buf[i].infra = htod32(1); | |
658 | pfn_elem_buf[i].auth = htod32(DOT11_OPEN_SYSTEM); | |
659 | pfn_elem_buf[i].wpa_auth = htod32(iter->wpa_auth); | |
660 | pfn_elem_buf[i].flags = htod32(iter->flags); | |
661 | if (iter->hidden) | |
662 | pfn_elem_buf[i].flags |= htod32(ENABLE << WL_PFN_HIDDEN_BIT); | |
663 | /* If a single RSSI threshold is defined, use that */ | |
664 | #ifdef PNO_MIN_RSSI_TRIGGER | |
665 | pfn_elem_buf[i].flags |= ((PNO_MIN_RSSI_TRIGGER & 0xFF) << WL_PFN_RSSI_SHIFT); | |
666 | #else | |
667 | pfn_elem_buf[i].flags |= ((iter->rssi_thresh & 0xFF) << WL_PFN_RSSI_SHIFT); | |
668 | #endif /* PNO_MIN_RSSI_TRIGGER */ | |
669 | memcpy((char *)pfn_elem_buf[i].ssid.SSID, iter->SSID, | |
670 | iter->SSID_len); | |
671 | pfn_elem_buf[i].ssid.SSID_len = iter->SSID_len; | |
672 | DHD_PNO(("%s size = %d hidden = %d flags = %x rssi_thresh %d\n", | |
673 | iter->SSID, iter->SSID_len, iter->hidden, | |
674 | iter->flags, iter->rssi_thresh)); | |
675 | if (++i >= nssid) { | |
676 | /* shouldn't happen */ | |
677 | break; | |
678 | } | |
679 | } | |
680 | ||
681 | err = dhd_iovar(dhd, 0, "pfn_add", (char *)pfn_elem_buf, mem_needed, NULL, 0, TRUE); | |
682 | if (err < 0) { | |
683 | DHD_ERROR(("%s : failed to execute pfn_add\n", __FUNCTION__)); | |
684 | } | |
685 | MFREE(dhd->osh, pfn_elem_buf, mem_needed); | |
686 | return err; | |
687 | } | |
688 | ||
689 | /* qsort compare function */ | |
690 | static int | |
691 | _dhd_pno_cmpfunc(const void *a, const void *b) | |
692 | { | |
693 | return (*(const uint16*)a - *(const uint16*)b); | |
694 | } | |
695 | ||
696 | static int | |
697 | _dhd_pno_chan_merge(uint16 *d_chan_list, int *nchan, | |
698 | uint16 *chan_list1, int nchan1, uint16 *chan_list2, int nchan2) | |
699 | { | |
700 | int err = BCME_OK; | |
701 | int i = 0, j = 0, k = 0; | |
702 | uint16 tmp; | |
703 | NULL_CHECK(d_chan_list, "d_chan_list is NULL", err); | |
704 | NULL_CHECK(nchan, "nchan is NULL", err); | |
705 | NULL_CHECK(chan_list1, "chan_list1 is NULL", err); | |
706 | NULL_CHECK(chan_list2, "chan_list2 is NULL", err); | |
707 | /* chan_list1 and chan_list2 should be sorted at first */ | |
708 | while (i < nchan1 && j < nchan2) { | |
709 | tmp = chan_list1[i] < chan_list2[j]? | |
710 | chan_list1[i++] : chan_list2[j++]; | |
711 | for (; i < nchan1 && chan_list1[i] == tmp; i++); | |
712 | for (; j < nchan2 && chan_list2[j] == tmp; j++); | |
713 | d_chan_list[k++] = tmp; | |
714 | } | |
715 | ||
716 | while (i < nchan1) { | |
717 | tmp = chan_list1[i++]; | |
718 | for (; i < nchan1 && chan_list1[i] == tmp; i++); | |
719 | d_chan_list[k++] = tmp; | |
720 | } | |
721 | ||
722 | while (j < nchan2) { | |
723 | tmp = chan_list2[j++]; | |
724 | for (; j < nchan2 && chan_list2[j] == tmp; j++); | |
725 | d_chan_list[k++] = tmp; | |
726 | ||
727 | } | |
728 | *nchan = k; | |
729 | return err; | |
730 | } | |
731 | ||
732 | static int | |
733 | _dhd_pno_get_channels(dhd_pub_t *dhd, uint16 *d_chan_list, | |
734 | int *nchan, uint8 band, bool skip_dfs) | |
735 | { | |
736 | int err = BCME_OK; | |
737 | int i, j; | |
738 | uint32 chan_buf[WL_NUMCHANNELS + 1]; | |
739 | wl_uint32_list_t *list; | |
740 | NULL_CHECK(dhd, "dhd is NULL", err); | |
741 | if (*nchan) { | |
742 | NULL_CHECK(d_chan_list, "d_chan_list is NULL", err); | |
743 | } | |
744 | memset(&chan_buf, 0, sizeof(chan_buf)); | |
745 | list = (wl_uint32_list_t *) (void *)chan_buf; | |
746 | list->count = htod32(WL_NUMCHANNELS); | |
747 | err = dhd_wl_ioctl_cmd(dhd, WLC_GET_VALID_CHANNELS, chan_buf, sizeof(chan_buf), FALSE, 0); | |
748 | if (err < 0) { | |
749 | DHD_ERROR(("failed to get channel list (err: %d)\n", err)); | |
750 | return err; | |
751 | } | |
752 | for (i = 0, j = 0; i < dtoh32(list->count) && i < *nchan; i++) { | |
753 | if (IS_2G_CHANNEL(dtoh32(list->element[i]))) { | |
754 | if (!(band & WLC_BAND_2G)) { | |
755 | /* Skip, if not 2g */ | |
756 | continue; | |
757 | } | |
758 | /* fall through to include the channel */ | |
759 | } else if (IS_5G_CHANNEL(dtoh32(list->element[i]))) { | |
760 | bool dfs_channel = is_dfs(dhd, dtoh32(list->element[i])); | |
761 | if ((skip_dfs && dfs_channel) || | |
762 | (!(band & WLC_BAND_5G) && !dfs_channel)) { | |
763 | /* Skip the channel if: | |
764 | * the DFS bit is NOT set & the channel is a dfs channel | |
765 | * the band 5G is not set & the channel is a non DFS 5G channel | |
766 | */ | |
767 | continue; | |
768 | } | |
769 | /* fall through to include the channel */ | |
770 | } else { | |
771 | /* Not in range. Bad channel */ | |
772 | DHD_ERROR(("Not in range. bad channel\n")); | |
773 | *nchan = 0; | |
774 | return BCME_BADCHAN; | |
775 | } | |
776 | ||
777 | /* Include the channel */ | |
778 | d_chan_list[j++] = (uint16) dtoh32(list->element[i]); | |
779 | } | |
780 | *nchan = j; | |
781 | return err; | |
782 | } | |
783 | ||
784 | static int | |
785 | _dhd_pno_convert_format(dhd_pub_t *dhd, struct dhd_pno_batch_params *params_batch, | |
786 | char *buf, int nbufsize) | |
787 | { | |
788 | int err = BCME_OK; | |
789 | int bytes_written = 0, nreadsize = 0; | |
790 | int t_delta = 0; | |
791 | int nleftsize = nbufsize; | |
792 | uint8 cnt = 0; | |
793 | char *bp = buf; | |
794 | char eabuf[ETHER_ADDR_STR_LEN]; | |
795 | #ifdef PNO_DEBUG | |
796 | char *_base_bp; | |
797 | char msg[150]; | |
798 | #endif // endif | |
799 | dhd_pno_bestnet_entry_t *iter, *next; | |
800 | dhd_pno_scan_results_t *siter, *snext; | |
801 | dhd_pno_best_header_t *phead, *pprev; | |
802 | NULL_CHECK(params_batch, "params_batch is NULL", err); | |
803 | if (nbufsize > 0) | |
804 | NULL_CHECK(buf, "buf is NULL", err); | |
805 | /* initialize the buffer */ | |
806 | memset(buf, 0, nbufsize); | |
807 | DHD_PNO(("%s enter \n", __FUNCTION__)); | |
808 | /* # of scans */ | |
809 | if (!params_batch->get_batch.batch_started) { | |
810 | bp += nreadsize = snprintf(bp, nleftsize, "scancount=%d\n", | |
811 | params_batch->get_batch.expired_tot_scan_cnt); | |
812 | nleftsize -= nreadsize; | |
813 | params_batch->get_batch.batch_started = TRUE; | |
814 | } | |
815 | DHD_PNO(("%s scancount %d\n", __FUNCTION__, params_batch->get_batch.expired_tot_scan_cnt)); | |
816 | /* preestimate scan count until which scan result this report is going to end */ | |
817 | GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); | |
818 | list_for_each_entry_safe(siter, snext, | |
819 | ¶ms_batch->get_batch.expired_scan_results_list, list) { | |
820 | GCC_DIAGNOSTIC_POP(); | |
821 | phead = siter->bestnetheader; | |
822 | while (phead != NULL) { | |
823 | /* if left_size is less than bestheader total size , stop this */ | |
824 | if (nleftsize <= | |
825 | (phead->tot_size + phead->tot_cnt * ENTRY_OVERHEAD)) | |
826 | goto exit; | |
827 | /* increase scan count */ | |
828 | cnt++; | |
829 | /* # best of each scan */ | |
830 | DHD_PNO(("\n<loop : %d, apcount %d>\n", cnt - 1, phead->tot_cnt)); | |
831 | /* attribute of the scan */ | |
832 | if (phead->reason & PNO_STATUS_ABORT_MASK) { | |
833 | bp += nreadsize = snprintf(bp, nleftsize, "trunc\n"); | |
834 | nleftsize -= nreadsize; | |
835 | } | |
836 | GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); | |
837 | list_for_each_entry_safe(iter, next, | |
838 | &phead->entry_list, list) { | |
839 | GCC_DIAGNOSTIC_POP(); | |
840 | t_delta = jiffies_to_msecs(jiffies - iter->recorded_time); | |
841 | #ifdef PNO_DEBUG | |
842 | _base_bp = bp; | |
843 | memset(msg, 0, sizeof(msg)); | |
844 | #endif // endif | |
845 | /* BSSID info */ | |
846 | bp += nreadsize = snprintf(bp, nleftsize, "bssid=%s\n", | |
847 | bcm_ether_ntoa((const struct ether_addr *)&iter->BSSID, eabuf)); | |
848 | nleftsize -= nreadsize; | |
849 | /* SSID */ | |
850 | bp += nreadsize = snprintf(bp, nleftsize, "ssid=%s\n", iter->SSID); | |
851 | nleftsize -= nreadsize; | |
852 | /* channel */ | |
853 | bp += nreadsize = snprintf(bp, nleftsize, "freq=%d\n", | |
854 | wf_channel2mhz(iter->channel, | |
855 | iter->channel <= CH_MAX_2G_CHANNEL? | |
856 | WF_CHAN_FACTOR_2_4_G : WF_CHAN_FACTOR_5_G)); | |
857 | nleftsize -= nreadsize; | |
858 | /* RSSI */ | |
859 | bp += nreadsize = snprintf(bp, nleftsize, "level=%d\n", iter->RSSI); | |
860 | nleftsize -= nreadsize; | |
861 | /* add the time consumed in Driver to the timestamp of firmware */ | |
862 | iter->timestamp += t_delta; | |
863 | bp += nreadsize = snprintf(bp, nleftsize, | |
864 | "age=%d\n", iter->timestamp); | |
865 | nleftsize -= nreadsize; | |
866 | /* RTT0 */ | |
867 | bp += nreadsize = snprintf(bp, nleftsize, "dist=%d\n", | |
868 | (iter->rtt0 == 0)? -1 : iter->rtt0); | |
869 | nleftsize -= nreadsize; | |
870 | /* RTT1 */ | |
871 | bp += nreadsize = snprintf(bp, nleftsize, "distSd=%d\n", | |
872 | (iter->rtt0 == 0)? -1 : iter->rtt1); | |
873 | nleftsize -= nreadsize; | |
874 | bp += nreadsize = snprintf(bp, nleftsize, "%s", AP_END_MARKER); | |
875 | nleftsize -= nreadsize; | |
876 | list_del(&iter->list); | |
877 | MFREE(dhd->osh, iter, BESTNET_ENTRY_SIZE); | |
878 | #ifdef PNO_DEBUG | |
879 | memcpy(msg, _base_bp, bp - _base_bp); | |
880 | DHD_PNO(("Entry : \n%s", msg)); | |
881 | #endif // endif | |
882 | } | |
883 | bp += nreadsize = snprintf(bp, nleftsize, "%s", SCAN_END_MARKER); | |
884 | DHD_PNO(("%s", SCAN_END_MARKER)); | |
885 | nleftsize -= nreadsize; | |
886 | pprev = phead; | |
887 | /* reset the header */ | |
888 | siter->bestnetheader = phead = phead->next; | |
889 | MFREE(dhd->osh, pprev, BEST_HEADER_SIZE); | |
890 | ||
891 | siter->cnt_header--; | |
892 | } | |
893 | if (phead == NULL) { | |
894 | /* we store all entry in this scan , so it is ok to delete */ | |
895 | list_del(&siter->list); | |
896 | MFREE(dhd->osh, siter, SCAN_RESULTS_SIZE); | |
897 | } | |
898 | } | |
899 | exit: | |
900 | if (cnt < params_batch->get_batch.expired_tot_scan_cnt) { | |
901 | DHD_ERROR(("Buffer size is small to save all batch entry," | |
902 | " cnt : %d (remained_scan_cnt): %d\n", | |
903 | cnt, params_batch->get_batch.expired_tot_scan_cnt - cnt)); | |
904 | } | |
905 | params_batch->get_batch.expired_tot_scan_cnt -= cnt; | |
906 | /* set FALSE only if the link list is empty after returning the data */ | |
907 | GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); | |
908 | if (list_empty(¶ms_batch->get_batch.expired_scan_results_list)) { | |
909 | GCC_DIAGNOSTIC_POP(); | |
910 | params_batch->get_batch.batch_started = FALSE; | |
911 | bp += snprintf(bp, nleftsize, "%s", RESULTS_END_MARKER); | |
912 | DHD_PNO(("%s", RESULTS_END_MARKER)); | |
913 | DHD_PNO(("%s : Getting the batching data is complete\n", __FUNCTION__)); | |
914 | } | |
915 | /* return used memory in buffer */ | |
916 | bytes_written = (int32)(bp - buf); | |
917 | return bytes_written; | |
918 | } | |
919 | ||
920 | static int | |
921 | _dhd_pno_clear_all_batch_results(dhd_pub_t *dhd, struct list_head *head, bool only_last) | |
922 | { | |
923 | int err = BCME_OK; | |
924 | int removed_scan_cnt = 0; | |
925 | dhd_pno_scan_results_t *siter, *snext; | |
926 | dhd_pno_best_header_t *phead, *pprev; | |
927 | dhd_pno_bestnet_entry_t *iter, *next; | |
928 | NULL_CHECK(dhd, "dhd is NULL", err); | |
929 | NULL_CHECK(head, "head is NULL", err); | |
930 | NULL_CHECK(head->next, "head->next is NULL", err); | |
931 | DHD_PNO(("%s enter\n", __FUNCTION__)); | |
932 | ||
933 | GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); | |
934 | list_for_each_entry_safe(siter, snext, | |
935 | head, list) { | |
936 | if (only_last) { | |
937 | /* in case that we need to delete only last one */ | |
938 | if (!list_is_last(&siter->list, head)) { | |
939 | /* skip if the one is not last */ | |
940 | continue; | |
941 | } | |
942 | } | |
943 | /* delete all data belong if the one is last */ | |
944 | phead = siter->bestnetheader; | |
945 | while (phead != NULL) { | |
946 | removed_scan_cnt++; | |
947 | list_for_each_entry_safe(iter, next, | |
948 | &phead->entry_list, list) { | |
949 | list_del(&iter->list); | |
950 | MFREE(dhd->osh, iter, BESTNET_ENTRY_SIZE); | |
951 | } | |
952 | pprev = phead; | |
953 | phead = phead->next; | |
954 | MFREE(dhd->osh, pprev, BEST_HEADER_SIZE); | |
955 | } | |
956 | if (phead == NULL) { | |
957 | /* it is ok to delete top node */ | |
958 | list_del(&siter->list); | |
959 | MFREE(dhd->osh, siter, SCAN_RESULTS_SIZE); | |
960 | } | |
961 | } | |
962 | GCC_DIAGNOSTIC_POP(); | |
963 | return removed_scan_cnt; | |
964 | } | |
965 | ||
966 | static int | |
967 | _dhd_pno_cfg(dhd_pub_t *dhd, uint16 *channel_list, int nchan) | |
968 | { | |
969 | int err = BCME_OK; | |
970 | int i = 0; | |
971 | wl_pfn_cfg_t pfncfg_param; | |
972 | NULL_CHECK(dhd, "dhd is NULL", err); | |
973 | if (nchan) { | |
974 | NULL_CHECK(channel_list, "nchan is NULL", err); | |
975 | } | |
976 | if (nchan > WL_NUMCHANNELS) { | |
977 | return BCME_RANGE; | |
978 | } | |
979 | DHD_PNO(("%s enter : nchan : %d\n", __FUNCTION__, nchan)); | |
980 | memset(&pfncfg_param, 0, sizeof(wl_pfn_cfg_t)); | |
981 | /* Setup default values */ | |
982 | pfncfg_param.reporttype = htod32(WL_PFN_REPORT_ALLNET); | |
983 | pfncfg_param.channel_num = htod32(0); | |
984 | ||
985 | for (i = 0; i < nchan; i++) | |
986 | pfncfg_param.channel_list[i] = channel_list[i]; | |
987 | ||
988 | pfncfg_param.channel_num = htod32(nchan); | |
989 | err = dhd_iovar(dhd, 0, "pfn_cfg", (char *)&pfncfg_param, sizeof(pfncfg_param), NULL, 0, | |
990 | TRUE); | |
991 | if (err < 0) { | |
992 | DHD_ERROR(("%s : failed to execute pfn_cfg\n", __FUNCTION__)); | |
993 | goto exit; | |
994 | } | |
995 | exit: | |
996 | return err; | |
997 | } | |
998 | ||
999 | static int | |
1000 | _dhd_pno_reinitialize_prof(dhd_pub_t *dhd, dhd_pno_params_t *params, dhd_pno_mode_t mode) | |
1001 | { | |
1002 | int err = BCME_OK; | |
1003 | dhd_pno_status_info_t *_pno_state; | |
1004 | NULL_CHECK(dhd, "dhd is NULL\n", err); | |
1005 | NULL_CHECK(dhd->pno_state, "pno_state is NULL\n", err); | |
1006 | DHD_PNO(("%s enter\n", __FUNCTION__)); | |
1007 | _pno_state = PNO_GET_PNOSTATE(dhd); | |
1008 | mutex_lock(&_pno_state->pno_mutex); | |
1009 | switch (mode) { | |
1010 | case DHD_PNO_LEGACY_MODE: { | |
1011 | struct dhd_pno_ssid *iter, *next; | |
1012 | if (params->params_legacy.nssid > 0) { | |
1013 | GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); | |
1014 | list_for_each_entry_safe(iter, next, | |
1015 | ¶ms->params_legacy.ssid_list, list) { | |
1016 | GCC_DIAGNOSTIC_POP(); | |
1017 | list_del(&iter->list); | |
1018 | MFREE(dhd->osh, iter, sizeof(struct dhd_pno_ssid)); | |
1019 | } | |
1020 | } | |
1021 | ||
1022 | params->params_legacy.nssid = 0; | |
1023 | params->params_legacy.scan_fr = 0; | |
1024 | params->params_legacy.pno_freq_expo_max = 0; | |
1025 | params->params_legacy.pno_repeat = 0; | |
1026 | params->params_legacy.nchan = 0; | |
1027 | memset(params->params_legacy.chan_list, 0, | |
1028 | sizeof(params->params_legacy.chan_list)); | |
1029 | break; | |
1030 | } | |
1031 | case DHD_PNO_BATCH_MODE: { | |
1032 | params->params_batch.scan_fr = 0; | |
1033 | params->params_batch.mscan = 0; | |
1034 | params->params_batch.nchan = 0; | |
1035 | params->params_batch.rtt = 0; | |
1036 | params->params_batch.bestn = 0; | |
1037 | params->params_batch.nchan = 0; | |
1038 | params->params_batch.band = WLC_BAND_AUTO; | |
1039 | memset(params->params_batch.chan_list, 0, | |
1040 | sizeof(params->params_batch.chan_list)); | |
1041 | params->params_batch.get_batch.batch_started = FALSE; | |
1042 | params->params_batch.get_batch.buf = NULL; | |
1043 | params->params_batch.get_batch.bufsize = 0; | |
1044 | params->params_batch.get_batch.reason = 0; | |
1045 | _dhd_pno_clear_all_batch_results(dhd, | |
1046 | ¶ms->params_batch.get_batch.scan_results_list, FALSE); | |
1047 | _dhd_pno_clear_all_batch_results(dhd, | |
1048 | ¶ms->params_batch.get_batch.expired_scan_results_list, FALSE); | |
1049 | params->params_batch.get_batch.tot_scan_cnt = 0; | |
1050 | params->params_batch.get_batch.expired_tot_scan_cnt = 0; | |
1051 | params->params_batch.get_batch.top_node_cnt = 0; | |
1052 | INIT_LIST_HEAD(¶ms->params_batch.get_batch.scan_results_list); | |
1053 | INIT_LIST_HEAD(¶ms->params_batch.get_batch.expired_scan_results_list); | |
1054 | break; | |
1055 | } | |
1056 | case DHD_PNO_HOTLIST_MODE: { | |
1057 | struct dhd_pno_bssid *iter, *next; | |
1058 | if (params->params_hotlist.nbssid > 0) { | |
1059 | GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); | |
1060 | list_for_each_entry_safe(iter, next, | |
1061 | ¶ms->params_hotlist.bssid_list, list) { | |
1062 | GCC_DIAGNOSTIC_POP(); | |
1063 | list_del(&iter->list); | |
1064 | MFREE(dhd->osh, iter, sizeof(struct dhd_pno_ssid)); | |
1065 | } | |
1066 | } | |
1067 | params->params_hotlist.scan_fr = 0; | |
1068 | params->params_hotlist.nbssid = 0; | |
1069 | params->params_hotlist.nchan = 0; | |
1070 | params->params_batch.band = WLC_BAND_AUTO; | |
1071 | memset(params->params_hotlist.chan_list, 0, | |
1072 | sizeof(params->params_hotlist.chan_list)); | |
1073 | break; | |
1074 | } | |
1075 | default: | |
1076 | DHD_ERROR(("%s : unknown mode : %d\n", __FUNCTION__, mode)); | |
1077 | break; | |
1078 | } | |
1079 | mutex_unlock(&_pno_state->pno_mutex); | |
1080 | return err; | |
1081 | } | |
1082 | ||
1083 | static int | |
1084 | _dhd_pno_add_bssid(dhd_pub_t *dhd, wl_pfn_bssid_t *p_pfn_bssid, int nbssid) | |
1085 | { | |
1086 | int err = BCME_OK; | |
1087 | NULL_CHECK(dhd, "dhd is NULL", err); | |
1088 | if (nbssid) { | |
1089 | NULL_CHECK(p_pfn_bssid, "bssid list is NULL", err); | |
1090 | } | |
1091 | err = dhd_iovar(dhd, 0, "pfn_add_bssid", (char *)p_pfn_bssid, | |
1092 | sizeof(wl_pfn_bssid_t) * nbssid, NULL, 0, TRUE); | |
1093 | if (err < 0) { | |
1094 | DHD_ERROR(("%s : failed to execute pfn_cfg\n", __FUNCTION__)); | |
1095 | goto exit; | |
1096 | } | |
1097 | exit: | |
1098 | return err; | |
1099 | } | |
1100 | ||
1101 | int | |
1102 | dhd_pno_stop_for_ssid(dhd_pub_t *dhd) | |
1103 | { | |
1104 | int err = BCME_OK; | |
1105 | uint32 mode = 0, cnt = 0; | |
1106 | dhd_pno_status_info_t *_pno_state; | |
1107 | dhd_pno_params_t *_params = NULL; | |
1108 | wl_pfn_bssid_t *p_pfn_bssid = NULL, *tmp_bssid; | |
1109 | ||
1110 | NULL_CHECK(dhd, "dev is NULL", err); | |
1111 | NULL_CHECK(dhd->pno_state, "pno_state is NULL", err); | |
1112 | _pno_state = PNO_GET_PNOSTATE(dhd); | |
1113 | if (!(_pno_state->pno_mode & DHD_PNO_LEGACY_MODE)) { | |
1114 | DHD_ERROR(("%s : LEGACY PNO MODE is not enabled\n", __FUNCTION__)); | |
1115 | goto exit; | |
1116 | } | |
1117 | DHD_PNO(("%s enter\n", __FUNCTION__)); | |
1118 | /* If pno mode is PNO_LEGACY_MODE clear the pno values and unset the DHD_PNO_LEGACY_MODE */ | |
1119 | _params = &_pno_state->pno_params_arr[INDEX_OF_LEGACY_PARAMS]; | |
1120 | _dhd_pno_reinitialize_prof(dhd, _params, DHD_PNO_LEGACY_MODE); | |
1121 | _pno_state->pno_mode &= ~DHD_PNO_LEGACY_MODE; | |
1122 | ||
1123 | #ifdef GSCAN_SUPPORT | |
1124 | if (_pno_state->pno_mode & DHD_PNO_GSCAN_MODE) { | |
1125 | struct dhd_pno_gscan_params *gscan_params; | |
1126 | ||
1127 | _params = &_pno_state->pno_params_arr[INDEX_OF_GSCAN_PARAMS]; | |
1128 | gscan_params = &_params->params_gscan; | |
1129 | if (gscan_params->mscan) { | |
1130 | /* retrieve the batching data from firmware into host */ | |
1131 | err = dhd_wait_batch_results_complete(dhd); | |
1132 | if (err != BCME_OK) | |
1133 | goto exit; | |
1134 | } | |
1135 | /* save current pno_mode before calling dhd_pno_clean */ | |
1136 | mutex_lock(&_pno_state->pno_mutex); | |
1137 | mode = _pno_state->pno_mode; | |
1138 | err = dhd_pno_clean(dhd); | |
1139 | if (err < 0) { | |
1140 | DHD_ERROR(("%s : failed to call dhd_pno_clean (err: %d)\n", | |
1141 | __FUNCTION__, err)); | |
1142 | mutex_unlock(&_pno_state->pno_mutex); | |
1143 | goto exit; | |
1144 | } | |
1145 | /* restore previous pno_mode */ | |
1146 | _pno_state->pno_mode = mode; | |
1147 | mutex_unlock(&_pno_state->pno_mutex); | |
1148 | /* Restart gscan */ | |
1149 | err = dhd_pno_initiate_gscan_request(dhd, 1, 0); | |
1150 | goto exit; | |
1151 | } | |
1152 | #endif /* GSCAN_SUPPORT */ | |
1153 | /* restart Batch mode if the batch mode is on */ | |
1154 | if (_pno_state->pno_mode & (DHD_PNO_BATCH_MODE | DHD_PNO_HOTLIST_MODE)) { | |
1155 | /* retrieve the batching data from firmware into host */ | |
1156 | dhd_pno_get_for_batch(dhd, NULL, 0, PNO_STATUS_DISABLE); | |
1157 | /* save current pno_mode before calling dhd_pno_clean */ | |
1158 | mode = _pno_state->pno_mode; | |
1159 | err = dhd_pno_clean(dhd); | |
1160 | if (err < 0) { | |
1161 | err = BCME_ERROR; | |
1162 | DHD_ERROR(("%s : failed to call dhd_pno_clean (err: %d)\n", | |
1163 | __FUNCTION__, err)); | |
1164 | goto exit; | |
1165 | } | |
1166 | ||
1167 | /* restore previous pno_mode */ | |
1168 | _pno_state->pno_mode = mode; | |
1169 | if (_pno_state->pno_mode & DHD_PNO_BATCH_MODE) { | |
1170 | _params = &(_pno_state->pno_params_arr[INDEX_OF_BATCH_PARAMS]); | |
1171 | /* restart BATCH SCAN */ | |
1172 | err = dhd_pno_set_for_batch(dhd, &_params->params_batch); | |
1173 | if (err < 0) { | |
1174 | _pno_state->pno_mode &= ~DHD_PNO_BATCH_MODE; | |
1175 | DHD_ERROR(("%s : failed to restart batch scan(err: %d)\n", | |
1176 | __FUNCTION__, err)); | |
1177 | goto exit; | |
1178 | } | |
1179 | } else if (_pno_state->pno_mode & DHD_PNO_HOTLIST_MODE) { | |
1180 | /* restart HOTLIST SCAN */ | |
1181 | struct dhd_pno_bssid *iter, *next; | |
1182 | _params = &(_pno_state->pno_params_arr[INDEX_OF_HOTLIST_PARAMS]); | |
1183 | p_pfn_bssid = MALLOCZ(dhd->osh, sizeof(wl_pfn_bssid_t) * | |
1184 | _params->params_hotlist.nbssid); | |
1185 | if (p_pfn_bssid == NULL) { | |
1186 | DHD_ERROR(("%s : failed to allocate wl_pfn_bssid_t array" | |
1187 | " (count: %d)", | |
1188 | __FUNCTION__, _params->params_hotlist.nbssid)); | |
1189 | err = BCME_ERROR; | |
1190 | _pno_state->pno_mode &= ~DHD_PNO_HOTLIST_MODE; | |
1191 | goto exit; | |
1192 | } | |
1193 | /* convert dhd_pno_bssid to wl_pfn_bssid */ | |
1194 | GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); | |
1195 | cnt = 0; | |
1196 | tmp_bssid = p_pfn_bssid; | |
1197 | list_for_each_entry_safe(iter, next, | |
1198 | &_params->params_hotlist.bssid_list, list) { | |
1199 | GCC_DIAGNOSTIC_POP(); | |
1200 | memcpy(&tmp_bssid->macaddr, | |
1201 | &iter->macaddr, ETHER_ADDR_LEN); | |
1202 | tmp_bssid->flags = iter->flags; | |
1203 | if (cnt < _params->params_hotlist.nbssid) { | |
1204 | tmp_bssid++; | |
1205 | cnt++; | |
1206 | } else { | |
1207 | DHD_ERROR(("%s: Allocated insufficient memory\n", | |
1208 | __FUNCTION__)); | |
1209 | break; | |
1210 | } | |
1211 | } | |
1212 | err = dhd_pno_set_for_hotlist(dhd, p_pfn_bssid, &_params->params_hotlist); | |
1213 | if (err < 0) { | |
1214 | _pno_state->pno_mode &= ~DHD_PNO_HOTLIST_MODE; | |
1215 | DHD_ERROR(("%s : failed to restart hotlist scan(err: %d)\n", | |
1216 | __FUNCTION__, err)); | |
1217 | goto exit; | |
1218 | } | |
1219 | } | |
1220 | } else { | |
1221 | err = dhd_pno_clean(dhd); | |
1222 | if (err < 0) { | |
1223 | DHD_ERROR(("%s : failed to call dhd_pno_clean (err: %d)\n", | |
1224 | __FUNCTION__, err)); | |
1225 | goto exit; | |
1226 | } | |
1227 | } | |
1228 | exit: | |
1229 | if (p_pfn_bssid) { | |
1230 | MFREE(dhd->osh, p_pfn_bssid, sizeof(wl_pfn_bssid_t) * | |
1231 | _params->params_hotlist.nbssid); | |
1232 | } | |
1233 | return err; | |
1234 | } | |
1235 | ||
1236 | int | |
1237 | dhd_pno_enable(dhd_pub_t *dhd, int enable) | |
1238 | { | |
1239 | int err = BCME_OK; | |
1240 | NULL_CHECK(dhd, "dhd is NULL", err); | |
1241 | DHD_PNO(("%s enter\n", __FUNCTION__)); | |
1242 | return (_dhd_pno_enable(dhd, enable)); | |
1243 | } | |
1244 | ||
1245 | static int | |
1246 | dhd_pno_add_to_ssid_list(dhd_pub_t *dhd, struct list_head *ptr, wlc_ssid_ext_t *ssid_list, | |
1247 | int nssid, int *num_ssid_added) | |
1248 | { | |
1249 | int ret = BCME_OK; | |
1250 | int i; | |
1251 | struct dhd_pno_ssid *_pno_ssid; | |
1252 | ||
1253 | for (i = 0; i < nssid; i++) { | |
1254 | if (ssid_list[i].SSID_len > DOT11_MAX_SSID_LEN) { | |
1255 | DHD_ERROR(("%s : Invalid SSID length %d\n", | |
1256 | __FUNCTION__, ssid_list[i].SSID_len)); | |
1257 | ret = BCME_ERROR; | |
1258 | goto exit; | |
1259 | } | |
1260 | /* Check for broadcast ssid */ | |
1261 | if (!ssid_list[i].SSID_len) { | |
1262 | DHD_ERROR(("%d: Broadcast SSID is illegal for PNO setting\n", i)); | |
1263 | ret = BCME_ERROR; | |
1264 | goto exit; | |
1265 | } | |
1266 | _pno_ssid = (struct dhd_pno_ssid *)MALLOCZ(dhd->osh, | |
1267 | sizeof(struct dhd_pno_ssid)); | |
1268 | if (_pno_ssid == NULL) { | |
1269 | DHD_ERROR(("%s : failed to allocate struct dhd_pno_ssid\n", | |
1270 | __FUNCTION__)); | |
1271 | ret = BCME_ERROR; | |
1272 | goto exit; | |
1273 | } | |
1274 | _pno_ssid->SSID_len = ssid_list[i].SSID_len; | |
1275 | _pno_ssid->hidden = ssid_list[i].hidden; | |
1276 | _pno_ssid->rssi_thresh = ssid_list[i].rssi_thresh; | |
1277 | _pno_ssid->flags = ssid_list[i].flags; | |
1278 | _pno_ssid->wpa_auth = WPA_AUTH_PFN_ANY; | |
1279 | ||
1280 | memcpy(_pno_ssid->SSID, ssid_list[i].SSID, _pno_ssid->SSID_len); | |
1281 | list_add_tail(&_pno_ssid->list, ptr); | |
1282 | } | |
1283 | ||
1284 | exit: | |
1285 | *num_ssid_added = i; | |
1286 | return ret; | |
1287 | } | |
1288 | ||
1289 | int | |
1290 | dhd_pno_set_for_ssid(dhd_pub_t *dhd, wlc_ssid_ext_t* ssid_list, int nssid, | |
1291 | uint16 scan_fr, int pno_repeat, int pno_freq_expo_max, uint16 *channel_list, int nchan) | |
1292 | { | |
1293 | dhd_pno_status_info_t *_pno_state; | |
1294 | dhd_pno_params_t *_params; | |
1295 | struct dhd_pno_legacy_params *params_legacy; | |
1296 | int err = BCME_OK; | |
1297 | ||
1298 | if (!dhd || !dhd->pno_state) { | |
1299 | DHD_ERROR(("%s: PNO Not enabled/Not ready\n", __FUNCTION__)); | |
1300 | return BCME_NOTREADY; | |
1301 | } | |
1302 | ||
1303 | if (!dhd_support_sta_mode(dhd)) { | |
1304 | return BCME_BADOPTION; | |
1305 | } | |
1306 | ||
1307 | _pno_state = PNO_GET_PNOSTATE(dhd); | |
1308 | _params = &(_pno_state->pno_params_arr[INDEX_OF_LEGACY_PARAMS]); | |
1309 | params_legacy = &(_params->params_legacy); | |
1310 | err = _dhd_pno_reinitialize_prof(dhd, _params, DHD_PNO_LEGACY_MODE); | |
1311 | ||
1312 | if (err < 0) { | |
1313 | DHD_ERROR(("%s : failed to reinitialize profile (err %d)\n", | |
1314 | __FUNCTION__, err)); | |
1315 | return err; | |
1316 | } | |
1317 | ||
1318 | INIT_LIST_HEAD(¶ms_legacy->ssid_list); | |
1319 | ||
1320 | if (dhd_pno_add_to_ssid_list(dhd, ¶ms_legacy->ssid_list, ssid_list, | |
1321 | nssid, ¶ms_legacy->nssid) < 0) { | |
1322 | _dhd_pno_reinitialize_prof(dhd, _params, DHD_PNO_LEGACY_MODE); | |
1323 | return BCME_ERROR; | |
1324 | } | |
1325 | ||
1326 | DHD_PNO(("%s enter : nssid %d, scan_fr :%d, pno_repeat :%d," | |
1327 | "pno_freq_expo_max: %d, nchan :%d\n", __FUNCTION__, | |
1328 | params_legacy->nssid, scan_fr, pno_repeat, pno_freq_expo_max, nchan)); | |
1329 | ||
1330 | return dhd_pno_set_legacy_pno(dhd, scan_fr, pno_repeat, | |
1331 | pno_freq_expo_max, channel_list, nchan); | |
1332 | ||
1333 | } | |
1334 | ||
1335 | static int | |
1336 | dhd_pno_set_legacy_pno(dhd_pub_t *dhd, uint16 scan_fr, int pno_repeat, | |
1337 | int pno_freq_expo_max, uint16 *channel_list, int nchan) | |
1338 | { | |
1339 | dhd_pno_params_t *_params; | |
1340 | dhd_pno_params_t *_params2; | |
1341 | dhd_pno_status_info_t *_pno_state; | |
1342 | uint16 _chan_list[WL_NUMCHANNELS]; | |
1343 | int32 tot_nchan = 0; | |
1344 | int err = BCME_OK; | |
1345 | int i, nssid; | |
1346 | int mode = 0; | |
1347 | struct list_head *ssid_list; | |
1348 | ||
1349 | _pno_state = PNO_GET_PNOSTATE(dhd); | |
1350 | ||
1351 | _params = &(_pno_state->pno_params_arr[INDEX_OF_LEGACY_PARAMS]); | |
1352 | /* If GSCAN is also ON will handle this down below */ | |
1353 | #ifdef GSCAN_SUPPORT | |
1354 | if (_pno_state->pno_mode & DHD_PNO_LEGACY_MODE && | |
1355 | !(_pno_state->pno_mode & DHD_PNO_GSCAN_MODE)) | |
1356 | #else | |
1357 | if (_pno_state->pno_mode & DHD_PNO_LEGACY_MODE) | |
1358 | #endif /* GSCAN_SUPPORT */ | |
1359 | { | |
1360 | DHD_ERROR(("%s : Legacy PNO mode was already started, " | |
1361 | "will disable previous one to start new one\n", __FUNCTION__)); | |
1362 | err = dhd_pno_stop_for_ssid(dhd); | |
1363 | if (err < 0) { | |
1364 | DHD_ERROR(("%s : failed to stop legacy PNO (err %d)\n", | |
1365 | __FUNCTION__, err)); | |
1366 | return err; | |
1367 | } | |
1368 | } | |
1369 | _pno_state->pno_mode |= DHD_PNO_LEGACY_MODE; | |
1370 | memset(_chan_list, 0, sizeof(_chan_list)); | |
1371 | tot_nchan = MIN(nchan, WL_NUMCHANNELS); | |
1372 | if (tot_nchan > 0 && channel_list) { | |
1373 | for (i = 0; i < tot_nchan; i++) | |
1374 | _params->params_legacy.chan_list[i] = _chan_list[i] = channel_list[i]; | |
1375 | } | |
1376 | #ifdef GSCAN_SUPPORT | |
1377 | else { | |
1378 | tot_nchan = WL_NUMCHANNELS; | |
1379 | err = _dhd_pno_get_channels(dhd, _chan_list, &tot_nchan, | |
1380 | (WLC_BAND_2G | WLC_BAND_5G), FALSE); | |
1381 | if (err < 0) { | |
1382 | tot_nchan = 0; | |
1383 | DHD_PNO(("Could not get channel list for PNO SSID\n")); | |
1384 | } else { | |
1385 | for (i = 0; i < tot_nchan; i++) | |
1386 | _params->params_legacy.chan_list[i] = _chan_list[i]; | |
1387 | } | |
1388 | } | |
1389 | #endif /* GSCAN_SUPPORT */ | |
1390 | ||
1391 | if (_pno_state->pno_mode & (DHD_PNO_BATCH_MODE | DHD_PNO_HOTLIST_MODE)) { | |
1392 | DHD_PNO(("BATCH SCAN is on progress in firmware\n")); | |
1393 | /* retrieve the batching data from firmware into host */ | |
1394 | dhd_pno_get_for_batch(dhd, NULL, 0, PNO_STATUS_DISABLE); | |
1395 | /* store current pno_mode before disabling pno */ | |
1396 | mode = _pno_state->pno_mode; | |
1397 | err = _dhd_pno_enable(dhd, PNO_OFF); | |
1398 | if (err < 0) { | |
1399 | DHD_ERROR(("%s : failed to disable PNO\n", __FUNCTION__)); | |
1400 | goto exit; | |
1401 | } | |
1402 | /* restore the previous mode */ | |
1403 | _pno_state->pno_mode = mode; | |
1404 | /* use superset of channel list between two mode */ | |
1405 | if (_pno_state->pno_mode & DHD_PNO_BATCH_MODE) { | |
1406 | _params2 = &(_pno_state->pno_params_arr[INDEX_OF_BATCH_PARAMS]); | |
1407 | if (_params2->params_batch.nchan > 0 && tot_nchan > 0) { | |
1408 | err = _dhd_pno_chan_merge(_chan_list, &tot_nchan, | |
1409 | &_params2->params_batch.chan_list[0], | |
1410 | _params2->params_batch.nchan, | |
1411 | &channel_list[0], tot_nchan); | |
1412 | if (err < 0) { | |
1413 | DHD_ERROR(("%s : failed to merge channel list" | |
1414 | " between legacy and batch\n", | |
1415 | __FUNCTION__)); | |
1416 | goto exit; | |
1417 | } | |
1418 | } else { | |
1419 | DHD_PNO(("superset channel will use" | |
1420 | " all channels in firmware\n")); | |
1421 | } | |
1422 | } else if (_pno_state->pno_mode & DHD_PNO_HOTLIST_MODE) { | |
1423 | _params2 = &(_pno_state->pno_params_arr[INDEX_OF_HOTLIST_PARAMS]); | |
1424 | if (_params2->params_hotlist.nchan > 0 && tot_nchan > 0) { | |
1425 | err = _dhd_pno_chan_merge(_chan_list, &tot_nchan, | |
1426 | &_params2->params_hotlist.chan_list[0], | |
1427 | _params2->params_hotlist.nchan, | |
1428 | &channel_list[0], tot_nchan); | |
1429 | if (err < 0) { | |
1430 | DHD_ERROR(("%s : failed to merge channel list" | |
1431 | " between legacy and hotlist\n", | |
1432 | __FUNCTION__)); | |
1433 | goto exit; | |
1434 | } | |
1435 | } | |
1436 | } | |
1437 | } | |
1438 | _params->params_legacy.scan_fr = scan_fr; | |
1439 | _params->params_legacy.pno_repeat = pno_repeat; | |
1440 | _params->params_legacy.pno_freq_expo_max = pno_freq_expo_max; | |
1441 | _params->params_legacy.nchan = tot_nchan; | |
1442 | ssid_list = &_params->params_legacy.ssid_list; | |
1443 | nssid = _params->params_legacy.nssid; | |
1444 | ||
1445 | #ifdef GSCAN_SUPPORT | |
1446 | /* dhd_pno_initiate_gscan_request will handle simultaneous Legacy PNO and GSCAN */ | |
1447 | if (_pno_state->pno_mode & DHD_PNO_GSCAN_MODE) { | |
1448 | struct dhd_pno_gscan_params *gscan_params; | |
1449 | gscan_params = &_pno_state->pno_params_arr[INDEX_OF_GSCAN_PARAMS].params_gscan; | |
1450 | /* ePNO and Legacy PNO do not co-exist */ | |
1451 | if (gscan_params->epno_cfg.num_epno_ssid) { | |
1452 | DHD_PNO(("ePNO and Legacy PNO do not co-exist\n")); | |
1453 | err = BCME_EPERM; | |
1454 | goto exit; | |
1455 | } | |
1456 | DHD_PNO(("GSCAN mode is ON! Will restart GSCAN+Legacy PNO\n")); | |
1457 | err = dhd_pno_initiate_gscan_request(dhd, 1, 0); | |
1458 | goto exit; | |
1459 | } | |
1460 | #endif /* GSCAN_SUPPORT */ | |
1461 | if ((err = _dhd_pno_set(dhd, _params, DHD_PNO_LEGACY_MODE)) < 0) { | |
1462 | DHD_ERROR(("failed to set call pno_set (err %d) in firmware\n", err)); | |
1463 | goto exit; | |
1464 | } | |
1465 | if ((err = _dhd_pno_add_ssid(dhd, ssid_list, nssid)) < 0) { | |
1466 | DHD_ERROR(("failed to add ssid list(err %d), %d in firmware\n", err, nssid)); | |
1467 | goto exit; | |
1468 | } | |
1469 | if (tot_nchan > 0) { | |
1470 | if ((err = _dhd_pno_cfg(dhd, _chan_list, tot_nchan)) < 0) { | |
1471 | DHD_ERROR(("%s : failed to set call pno_cfg (err %d) in firmware\n", | |
1472 | __FUNCTION__, err)); | |
1473 | goto exit; | |
1474 | } | |
1475 | } | |
1476 | if (_pno_state->pno_status == DHD_PNO_DISABLED) { | |
1477 | if ((err = _dhd_pno_enable(dhd, PNO_ON)) < 0) | |
1478 | DHD_ERROR(("%s : failed to enable PNO\n", __FUNCTION__)); | |
1479 | } | |
1480 | exit: | |
1481 | if (err < 0) { | |
1482 | _dhd_pno_reinitialize_prof(dhd, _params, DHD_PNO_LEGACY_MODE); | |
1483 | } | |
1484 | /* clear mode in case of error */ | |
1485 | if (err < 0) { | |
1486 | int ret = dhd_pno_clean(dhd); | |
1487 | ||
1488 | if (ret < 0) { | |
1489 | DHD_ERROR(("%s : failed to call dhd_pno_clean (err: %d)\n", | |
1490 | __FUNCTION__, ret)); | |
1491 | } else { | |
1492 | _pno_state->pno_mode &= ~DHD_PNO_LEGACY_MODE; | |
1493 | } | |
1494 | } | |
1495 | return err; | |
1496 | } | |
1497 | ||
1498 | int | |
1499 | dhd_pno_set_for_batch(dhd_pub_t *dhd, struct dhd_pno_batch_params *batch_params) | |
1500 | { | |
1501 | int err = BCME_OK; | |
1502 | uint16 _chan_list[WL_NUMCHANNELS]; | |
1503 | int rem_nchan = 0, tot_nchan = 0; | |
1504 | int mode = 0, mscan = 0; | |
1505 | dhd_pno_params_t *_params; | |
1506 | dhd_pno_params_t *_params2; | |
1507 | dhd_pno_status_info_t *_pno_state; | |
1508 | NULL_CHECK(dhd, "dhd is NULL", err); | |
1509 | NULL_CHECK(dhd->pno_state, "pno_state is NULL", err); | |
1510 | NULL_CHECK(batch_params, "batch_params is NULL", err); | |
1511 | _pno_state = PNO_GET_PNOSTATE(dhd); | |
1512 | DHD_PNO(("%s enter\n", __FUNCTION__)); | |
1513 | if (!dhd_support_sta_mode(dhd)) { | |
1514 | err = BCME_BADOPTION; | |
1515 | goto exit; | |
1516 | } | |
1517 | if (!WLS_SUPPORTED(_pno_state)) { | |
1518 | DHD_ERROR(("%s : wifi location service is not supported\n", __FUNCTION__)); | |
1519 | err = BCME_UNSUPPORTED; | |
1520 | goto exit; | |
1521 | } | |
1522 | _params = &_pno_state->pno_params_arr[INDEX_OF_BATCH_PARAMS]; | |
1523 | if (!(_pno_state->pno_mode & DHD_PNO_BATCH_MODE)) { | |
1524 | _pno_state->pno_mode |= DHD_PNO_BATCH_MODE; | |
1525 | err = _dhd_pno_reinitialize_prof(dhd, _params, DHD_PNO_BATCH_MODE); | |
1526 | if (err < 0) { | |
1527 | DHD_ERROR(("%s : failed to call _dhd_pno_reinitialize_prof\n", | |
1528 | __FUNCTION__)); | |
1529 | goto exit; | |
1530 | } | |
1531 | } else { | |
1532 | /* batch mode is already started */ | |
1533 | return -EBUSY; | |
1534 | } | |
1535 | _params->params_batch.scan_fr = batch_params->scan_fr; | |
1536 | _params->params_batch.bestn = batch_params->bestn; | |
1537 | _params->params_batch.mscan = (batch_params->mscan)? | |
1538 | batch_params->mscan : DEFAULT_BATCH_MSCAN; | |
1539 | _params->params_batch.nchan = batch_params->nchan; | |
1540 | memcpy(_params->params_batch.chan_list, batch_params->chan_list, | |
1541 | sizeof(_params->params_batch.chan_list)); | |
1542 | ||
1543 | memset(_chan_list, 0, sizeof(_chan_list)); | |
1544 | ||
1545 | rem_nchan = ARRAYSIZE(batch_params->chan_list) - batch_params->nchan; | |
1546 | if (batch_params->band == WLC_BAND_2G || batch_params->band == WLC_BAND_5G) { | |
1547 | /* get a valid channel list based on band B or A */ | |
1548 | err = _dhd_pno_get_channels(dhd, | |
1549 | &_params->params_batch.chan_list[batch_params->nchan], | |
1550 | &rem_nchan, batch_params->band, FALSE); | |
1551 | if (err < 0) { | |
1552 | DHD_ERROR(("%s: failed to get valid channel list(band : %d)\n", | |
1553 | __FUNCTION__, batch_params->band)); | |
1554 | goto exit; | |
1555 | } | |
1556 | /* now we need to update nchan because rem_chan has valid channel count */ | |
1557 | _params->params_batch.nchan += rem_nchan; | |
1558 | /* need to sort channel list */ | |
1559 | sort(_params->params_batch.chan_list, _params->params_batch.nchan, | |
1560 | sizeof(_params->params_batch.chan_list[0]), _dhd_pno_cmpfunc, NULL); | |
1561 | } | |
1562 | #ifdef PNO_DEBUG | |
1563 | { | |
1564 | DHD_PNO(("Channel list : ")); | |
1565 | for (i = 0; i < _params->params_batch.nchan; i++) { | |
1566 | DHD_PNO(("%d ", _params->params_batch.chan_list[i])); | |
1567 | } | |
1568 | DHD_PNO(("\n")); | |
1569 | } | |
1570 | #endif // endif | |
1571 | if (_params->params_batch.nchan) { | |
1572 | /* copy the channel list into local array */ | |
1573 | memcpy(_chan_list, _params->params_batch.chan_list, sizeof(_chan_list)); | |
1574 | tot_nchan = _params->params_batch.nchan; | |
1575 | } | |
1576 | if (_pno_state->pno_mode & DHD_PNO_LEGACY_MODE) { | |
1577 | DHD_PNO(("PNO SSID is on progress in firmware\n")); | |
1578 | /* store current pno_mode before disabling pno */ | |
1579 | mode = _pno_state->pno_mode; | |
1580 | err = _dhd_pno_enable(dhd, PNO_OFF); | |
1581 | if (err < 0) { | |
1582 | DHD_ERROR(("%s : failed to disable PNO\n", __FUNCTION__)); | |
1583 | goto exit; | |
1584 | } | |
1585 | /* restore the previous mode */ | |
1586 | _pno_state->pno_mode = mode; | |
1587 | /* Use the superset for channelist between two mode */ | |
1588 | _params2 = &(_pno_state->pno_params_arr[INDEX_OF_LEGACY_PARAMS]); | |
1589 | if (_params2->params_legacy.nchan > 0 && _params->params_batch.nchan > 0) { | |
1590 | err = _dhd_pno_chan_merge(_chan_list, &tot_nchan, | |
1591 | &_params2->params_legacy.chan_list[0], | |
1592 | _params2->params_legacy.nchan, | |
1593 | &_params->params_batch.chan_list[0], _params->params_batch.nchan); | |
1594 | if (err < 0) { | |
1595 | DHD_ERROR(("%s : failed to merge channel list" | |
1596 | " between legacy and batch\n", | |
1597 | __FUNCTION__)); | |
1598 | goto exit; | |
1599 | } | |
1600 | } else { | |
1601 | DHD_PNO(("superset channel will use all channels in firmware\n")); | |
1602 | } | |
1603 | if ((err = _dhd_pno_add_ssid(dhd, &_params2->params_legacy.ssid_list, | |
1604 | _params2->params_legacy.nssid)) < 0) { | |
1605 | DHD_ERROR(("failed to add ssid list (err %d) in firmware\n", err)); | |
1606 | goto exit; | |
1607 | } | |
1608 | } | |
1609 | if ((err = _dhd_pno_set(dhd, _params, DHD_PNO_BATCH_MODE)) < 0) { | |
1610 | DHD_ERROR(("%s : failed to set call pno_set (err %d) in firmware\n", | |
1611 | __FUNCTION__, err)); | |
1612 | goto exit; | |
1613 | } else { | |
1614 | /* we need to return mscan */ | |
1615 | mscan = err; | |
1616 | } | |
1617 | if (tot_nchan > 0) { | |
1618 | if ((err = _dhd_pno_cfg(dhd, _chan_list, tot_nchan)) < 0) { | |
1619 | DHD_ERROR(("%s : failed to set call pno_cfg (err %d) in firmware\n", | |
1620 | __FUNCTION__, err)); | |
1621 | goto exit; | |
1622 | } | |
1623 | } | |
1624 | if (_pno_state->pno_status == DHD_PNO_DISABLED) { | |
1625 | if ((err = _dhd_pno_enable(dhd, PNO_ON)) < 0) | |
1626 | DHD_ERROR(("%s : failed to enable PNO\n", __FUNCTION__)); | |
1627 | } | |
1628 | exit: | |
1629 | /* clear mode in case of error */ | |
1630 | if (err < 0) | |
1631 | _pno_state->pno_mode &= ~DHD_PNO_BATCH_MODE; | |
1632 | else { | |
1633 | /* return #max scan firmware can do */ | |
1634 | err = mscan; | |
1635 | } | |
1636 | return err; | |
1637 | } | |
1638 | ||
1639 | #ifdef GSCAN_SUPPORT | |
1640 | ||
1641 | static int | |
1642 | dhd_set_epno_params(dhd_pub_t *dhd, wl_ssid_ext_params_t *params, bool set) | |
1643 | { | |
1644 | wl_pfn_ssid_cfg_t cfg; | |
1645 | int err; | |
1646 | NULL_CHECK(dhd, "dhd is NULL\n", err); | |
1647 | memset(&cfg, 0, sizeof(wl_pfn_ssid_cfg_t)); | |
1648 | cfg.version = WL_PFN_SSID_CFG_VERSION; | |
1649 | ||
1650 | /* If asked to clear params (set == FALSE) just set the CLEAR bit */ | |
1651 | if (!set) | |
1652 | cfg.flags |= WL_PFN_SSID_CFG_CLEAR; | |
1653 | else if (params) | |
1654 | memcpy(&cfg.params, params, sizeof(wl_ssid_ext_params_t)); | |
1655 | err = dhd_iovar(dhd, 0, "pfn_ssid_cfg", (char *)&cfg, | |
1656 | sizeof(wl_pfn_ssid_cfg_t), NULL, 0, TRUE); | |
1657 | if (err != BCME_OK) { | |
1658 | DHD_ERROR(("%s : Failed to execute pfn_ssid_cfg %d\n", __FUNCTION__, err)); | |
1659 | } | |
1660 | return err; | |
1661 | } | |
1662 | ||
1663 | int | |
1664 | dhd_pno_flush_fw_epno(dhd_pub_t *dhd) | |
1665 | { | |
1666 | int err; | |
1667 | ||
1668 | NULL_CHECK(dhd, "dhd is NULL\n", err); | |
1669 | ||
1670 | err = dhd_set_epno_params(dhd, NULL, FALSE); | |
1671 | if (err < 0) { | |
1672 | DHD_ERROR(("failed to set ePNO params %d\n", err)); | |
1673 | return err; | |
1674 | } | |
1675 | err = _dhd_pno_flush_ssid(dhd); | |
1676 | return err; | |
1677 | } | |
1678 | ||
1679 | int | |
1680 | dhd_pno_set_epno(dhd_pub_t *dhd) | |
1681 | { | |
1682 | int err = BCME_OK; | |
1683 | dhd_pno_params_t *params; | |
1684 | dhd_pno_status_info_t *_pno_state; | |
1685 | ||
1686 | struct dhd_pno_gscan_params *gscan_params; | |
1687 | ||
1688 | NULL_CHECK(dhd, "dhd is NULL\n", err); | |
1689 | NULL_CHECK(dhd->pno_state, "pno_state is NULL", err); | |
1690 | _pno_state = PNO_GET_PNOSTATE(dhd); | |
1691 | params = &_pno_state->pno_params_arr[INDEX_OF_GSCAN_PARAMS]; | |
1692 | gscan_params = ¶ms->params_gscan; | |
1693 | ||
1694 | if (gscan_params->epno_cfg.num_epno_ssid) { | |
1695 | DHD_PNO(("num_epno_ssid %d\n", gscan_params->epno_cfg.num_epno_ssid)); | |
1696 | if ((err = _dhd_pno_add_ssid(dhd, &gscan_params->epno_cfg.epno_ssid_list, | |
1697 | gscan_params->epno_cfg.num_epno_ssid)) < 0) { | |
1698 | DHD_ERROR(("failed to add ssid list (err %d) to firmware\n", err)); | |
1699 | return err; | |
1700 | } | |
1701 | err = dhd_set_epno_params(dhd, &gscan_params->epno_cfg.params, TRUE); | |
1702 | if (err < 0) { | |
1703 | DHD_ERROR(("failed to set ePNO params %d\n", err)); | |
1704 | } | |
1705 | } | |
1706 | return err; | |
1707 | } | |
1708 | ||
1709 | static void | |
1710 | dhd_pno_reset_cfg_gscan(dhd_pub_t *dhd, dhd_pno_params_t *_params, | |
1711 | dhd_pno_status_info_t *_pno_state, uint8 flags) | |
1712 | { | |
1713 | DHD_PNO(("%s enter\n", __FUNCTION__)); | |
1714 | ||
1715 | if (flags & GSCAN_FLUSH_SCAN_CFG) { | |
1716 | _params->params_gscan.bestn = 0; | |
1717 | _params->params_gscan.mscan = 0; | |
1718 | _params->params_gscan.buffer_threshold = GSCAN_BATCH_NO_THR_SET; | |
1719 | _params->params_gscan.scan_fr = 0; | |
1720 | _params->params_gscan.send_all_results_flag = 0; | |
1721 | memset(_params->params_gscan.channel_bucket, 0, | |
1722 | _params->params_gscan.nchannel_buckets * | |
1723 | sizeof(struct dhd_pno_gscan_channel_bucket)); | |
1724 | _params->params_gscan.nchannel_buckets = 0; | |
1725 | DHD_PNO(("Flush Scan config\n")); | |
1726 | } | |
1727 | if (flags & GSCAN_FLUSH_HOTLIST_CFG) { | |
1728 | struct dhd_pno_bssid *iter, *next; | |
1729 | if (_params->params_gscan.nbssid_hotlist > 0) { | |
1730 | GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); | |
1731 | list_for_each_entry_safe(iter, next, | |
1732 | &_params->params_gscan.hotlist_bssid_list, list) { | |
1733 | GCC_DIAGNOSTIC_POP(); | |
1734 | list_del(&iter->list); | |
1735 | MFREE(dhd->osh, iter, sizeof(struct dhd_pno_bssid)); | |
1736 | } | |
1737 | } | |
1738 | _params->params_gscan.nbssid_hotlist = 0; | |
1739 | DHD_PNO(("Flush Hotlist Config\n")); | |
1740 | } | |
1741 | if (flags & GSCAN_FLUSH_EPNO_CFG) { | |
1742 | dhd_pno_ssid_t *iter, *next; | |
1743 | dhd_epno_ssid_cfg_t *epno_cfg = &_params->params_gscan.epno_cfg; | |
1744 | ||
1745 | if (epno_cfg->num_epno_ssid > 0) { | |
1746 | GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); | |
1747 | list_for_each_entry_safe(iter, next, | |
1748 | &epno_cfg->epno_ssid_list, list) { | |
1749 | GCC_DIAGNOSTIC_POP(); | |
1750 | list_del(&iter->list); | |
1751 | MFREE(dhd->osh, iter, sizeof(struct dhd_pno_bssid)); | |
1752 | } | |
1753 | epno_cfg->num_epno_ssid = 0; | |
1754 | } | |
1755 | memset(&epno_cfg->params, 0, sizeof(wl_ssid_ext_params_t)); | |
1756 | DHD_PNO(("Flushed ePNO Config\n")); | |
1757 | } | |
1758 | ||
1759 | return; | |
1760 | } | |
1761 | ||
1762 | int | |
1763 | dhd_pno_lock_batch_results(dhd_pub_t *dhd) | |
1764 | { | |
1765 | dhd_pno_status_info_t *_pno_state; | |
1766 | int err = BCME_OK; | |
1767 | ||
1768 | NULL_CHECK(dhd, "dhd is NULL", err); | |
1769 | NULL_CHECK(dhd->pno_state, "pno_state is NULL", err); | |
1770 | _pno_state = PNO_GET_PNOSTATE(dhd); | |
1771 | mutex_lock(&_pno_state->pno_mutex); | |
1772 | return err; | |
1773 | } | |
1774 | ||
1775 | void | |
1776 | dhd_pno_unlock_batch_results(dhd_pub_t *dhd) | |
1777 | { | |
1778 | dhd_pno_status_info_t *_pno_state; | |
1779 | _pno_state = PNO_GET_PNOSTATE(dhd); | |
1780 | mutex_unlock(&_pno_state->pno_mutex); | |
1781 | return; | |
1782 | } | |
1783 | ||
1784 | int | |
1785 | dhd_wait_batch_results_complete(dhd_pub_t *dhd) | |
1786 | { | |
1787 | dhd_pno_status_info_t *_pno_state; | |
1788 | dhd_pno_params_t *_params; | |
1789 | int err = BCME_OK; | |
1790 | ||
1791 | NULL_CHECK(dhd, "dhd is NULL", err); | |
1792 | NULL_CHECK(dhd->pno_state, "pno_state is NULL", err); | |
1793 | _pno_state = PNO_GET_PNOSTATE(dhd); | |
1794 | _params = &_pno_state->pno_params_arr[INDEX_OF_GSCAN_PARAMS]; | |
1795 | ||
1796 | /* Has the workqueue finished its job already?? */ | |
1797 | if (_params->params_gscan.get_batch_flag == GSCAN_BATCH_RETRIEVAL_IN_PROGRESS) { | |
1798 | DHD_PNO(("%s: Waiting to complete retrieval..\n", __FUNCTION__)); | |
1799 | wait_event_interruptible_timeout(_pno_state->batch_get_wait, | |
1800 | is_batch_retrieval_complete(&_params->params_gscan), | |
1801 | msecs_to_jiffies(GSCAN_BATCH_GET_MAX_WAIT)); | |
1802 | } else { /* GSCAN_BATCH_RETRIEVAL_COMPLETE */ | |
1803 | gscan_results_cache_t *iter; | |
1804 | uint16 num_results = 0; | |
1805 | ||
1806 | mutex_lock(&_pno_state->pno_mutex); | |
1807 | iter = _params->params_gscan.gscan_batch_cache; | |
1808 | while (iter) { | |
1809 | num_results += iter->tot_count - iter->tot_consumed; | |
1810 | iter = iter->next; | |
1811 | } | |
1812 | mutex_unlock(&_pno_state->pno_mutex); | |
1813 | ||
1814 | /* All results consumed/No results cached?? | |
1815 | * Get fresh results from FW | |
1816 | */ | |
1817 | if ((_pno_state->pno_mode & DHD_PNO_GSCAN_MODE) && !num_results) { | |
1818 | DHD_PNO(("%s: No results cached, getting from FW..\n", __FUNCTION__)); | |
1819 | err = dhd_retreive_batch_scan_results(dhd); | |
1820 | if (err == BCME_OK) { | |
1821 | wait_event_interruptible_timeout(_pno_state->batch_get_wait, | |
1822 | is_batch_retrieval_complete(&_params->params_gscan), | |
1823 | msecs_to_jiffies(GSCAN_BATCH_GET_MAX_WAIT)); | |
1824 | } | |
1825 | } | |
1826 | } | |
1827 | DHD_PNO(("%s: Wait complete\n", __FUNCTION__)); | |
1828 | return err; | |
1829 | } | |
1830 | ||
1831 | int | |
1832 | dhd_pno_set_cfg_gscan(dhd_pub_t *dhd, dhd_pno_gscan_cmd_cfg_t type, | |
1833 | void *buf, bool flush) | |
1834 | { | |
1835 | int err = BCME_OK; | |
1836 | dhd_pno_params_t *_params; | |
1837 | int i; | |
1838 | dhd_pno_status_info_t *_pno_state; | |
1839 | ||
1840 | NULL_CHECK(dhd, "dhd is NULL", err); | |
1841 | NULL_CHECK(dhd->pno_state, "pno_state is NULL", err); | |
1842 | ||
1843 | DHD_PNO(("%s enter\n", __FUNCTION__)); | |
1844 | ||
1845 | _pno_state = PNO_GET_PNOSTATE(dhd); | |
1846 | _params = &_pno_state->pno_params_arr[INDEX_OF_GSCAN_PARAMS]; | |
1847 | mutex_lock(&_pno_state->pno_mutex); | |
1848 | ||
1849 | switch (type) { | |
1850 | case DHD_PNO_BATCH_SCAN_CFG_ID: | |
1851 | { | |
1852 | gscan_batch_params_t *ptr = (gscan_batch_params_t *)buf; | |
1853 | _params->params_gscan.bestn = ptr->bestn; | |
1854 | _params->params_gscan.mscan = ptr->mscan; | |
1855 | _params->params_gscan.buffer_threshold = ptr->buffer_threshold; | |
1856 | } | |
1857 | break; | |
1858 | case DHD_PNO_GEOFENCE_SCAN_CFG_ID: | |
1859 | { | |
1860 | gscan_hotlist_scan_params_t *ptr = (gscan_hotlist_scan_params_t *)buf; | |
1861 | struct dhd_pno_bssid *_pno_bssid; | |
1862 | struct bssid_t *bssid_ptr; | |
1863 | int8 flags; | |
1864 | ||
1865 | if (flush) { | |
1866 | dhd_pno_reset_cfg_gscan(dhd, _params, _pno_state, | |
1867 | GSCAN_FLUSH_HOTLIST_CFG); | |
1868 | } | |
1869 | ||
1870 | if (!ptr->nbssid) { | |
1871 | break; | |
1872 | } | |
1873 | if (!_params->params_gscan.nbssid_hotlist) { | |
1874 | INIT_LIST_HEAD(&_params->params_gscan.hotlist_bssid_list); | |
1875 | } | |
1876 | ||
1877 | if ((_params->params_gscan.nbssid_hotlist + | |
1878 | ptr->nbssid) > PFN_SWC_MAX_NUM_APS) { | |
1879 | DHD_ERROR(("Excessive number of hotlist APs programmed %d\n", | |
1880 | (_params->params_gscan.nbssid_hotlist + | |
1881 | ptr->nbssid))); | |
1882 | err = BCME_RANGE; | |
1883 | goto exit; | |
1884 | } | |
1885 | ||
1886 | for (i = 0, bssid_ptr = ptr->bssid; i < ptr->nbssid; i++, bssid_ptr++) { | |
1887 | _pno_bssid = (struct dhd_pno_bssid *)MALLOCZ(dhd->osh, | |
1888 | sizeof(struct dhd_pno_bssid)); | |
1889 | if (!_pno_bssid) { | |
1890 | DHD_ERROR(("_pno_bssid is NULL, cannot kalloc %zd bytes", | |
1891 | sizeof(struct dhd_pno_bssid))); | |
1892 | err = BCME_NOMEM; | |
1893 | goto exit; | |
1894 | } | |
1895 | memcpy(&_pno_bssid->macaddr, &bssid_ptr->macaddr, ETHER_ADDR_LEN); | |
1896 | ||
1897 | flags = (int8) bssid_ptr->rssi_reporting_threshold; | |
1898 | _pno_bssid->flags = flags << WL_PFN_RSSI_SHIFT; | |
1899 | list_add_tail(&_pno_bssid->list, | |
1900 | &_params->params_gscan.hotlist_bssid_list); | |
1901 | } | |
1902 | ||
1903 | _params->params_gscan.nbssid_hotlist += ptr->nbssid; | |
1904 | _params->params_gscan.lost_ap_window = ptr->lost_ap_window; | |
1905 | } | |
1906 | break; | |
1907 | case DHD_PNO_SCAN_CFG_ID: | |
1908 | { | |
1909 | int k; | |
1910 | uint16 band; | |
1911 | gscan_scan_params_t *ptr = (gscan_scan_params_t *)buf; | |
1912 | struct dhd_pno_gscan_channel_bucket *ch_bucket; | |
1913 | ||
1914 | if (ptr->nchannel_buckets <= GSCAN_MAX_CH_BUCKETS) { | |
1915 | _params->params_gscan.nchannel_buckets = ptr->nchannel_buckets; | |
1916 | ||
1917 | memcpy(_params->params_gscan.channel_bucket, ptr->channel_bucket, | |
1918 | _params->params_gscan.nchannel_buckets * | |
1919 | sizeof(struct dhd_pno_gscan_channel_bucket)); | |
1920 | ch_bucket = _params->params_gscan.channel_bucket; | |
1921 | ||
1922 | for (i = 0; i < ptr->nchannel_buckets; i++) { | |
1923 | band = ch_bucket[i].band; | |
1924 | for (k = 0; k < ptr->channel_bucket[i].num_channels; k++) { | |
1925 | ch_bucket[i].chan_list[k] = | |
1926 | wf_mhz2channel(ptr->channel_bucket[i].chan_list[k], | |
1927 | 0); | |
1928 | } | |
1929 | ch_bucket[i].band = 0; | |
1930 | /* HAL and DHD use different bits for 2.4G and | |
1931 | * 5G in bitmap. Hence translating it here... | |
1932 | */ | |
1933 | if (band & GSCAN_BG_BAND_MASK) { | |
1934 | ch_bucket[i].band |= WLC_BAND_2G; | |
1935 | } | |
1936 | if (band & GSCAN_A_BAND_MASK) { | |
1937 | ch_bucket[i].band |= WLC_BAND_5G; | |
1938 | } | |
1939 | if (band & GSCAN_DFS_MASK) { | |
1940 | ch_bucket[i].band |= GSCAN_DFS_MASK; | |
1941 | } | |
1942 | DHD_PNO(("band %d report_flag %d\n", ch_bucket[i].band, | |
1943 | ch_bucket[i].report_flag)); | |
1944 | } | |
1945 | ||
1946 | for (i = 0; i < ptr->nchannel_buckets; i++) { | |
1947 | ch_bucket[i].bucket_freq_multiple = | |
1948 | ch_bucket[i].bucket_freq_multiple/ptr->scan_fr; | |
1949 | ch_bucket[i].bucket_max_multiple = | |
1950 | ch_bucket[i].bucket_max_multiple/ptr->scan_fr; | |
1951 | DHD_PNO(("mult %d max_mult %d\n", | |
1952 | ch_bucket[i].bucket_freq_multiple, | |
1953 | ch_bucket[i].bucket_max_multiple)); | |
1954 | } | |
1955 | _params->params_gscan.scan_fr = ptr->scan_fr; | |
1956 | ||
1957 | DHD_PNO(("num_buckets %d scan_fr %d\n", ptr->nchannel_buckets, | |
1958 | _params->params_gscan.scan_fr)); | |
1959 | } else { | |
1960 | err = BCME_BADARG; | |
1961 | } | |
1962 | } | |
1963 | break; | |
1964 | case DHD_PNO_EPNO_CFG_ID: | |
1965 | if (flush) { | |
1966 | dhd_pno_reset_cfg_gscan(dhd, _params, _pno_state, | |
1967 | GSCAN_FLUSH_EPNO_CFG); | |
1968 | } | |
1969 | break; | |
1970 | case DHD_PNO_EPNO_PARAMS_ID: | |
1971 | if (flush) { | |
1972 | memset(&_params->params_gscan.epno_cfg.params, 0, | |
1973 | sizeof(wl_ssid_ext_params_t)); | |
1974 | } | |
1975 | if (buf) { | |
1976 | memcpy(&_params->params_gscan.epno_cfg.params, buf, | |
1977 | sizeof(wl_ssid_ext_params_t)); | |
1978 | } | |
1979 | break; | |
1980 | default: | |
1981 | err = BCME_BADARG; | |
1982 | DHD_ERROR(("%s: Unrecognized cmd type - %d\n", __FUNCTION__, type)); | |
1983 | break; | |
1984 | } | |
1985 | exit: | |
1986 | mutex_unlock(&_pno_state->pno_mutex); | |
1987 | return err; | |
1988 | ||
1989 | } | |
1990 | ||
1991 | static bool | |
1992 | validate_gscan_params(struct dhd_pno_gscan_params *gscan_params) | |
1993 | { | |
1994 | unsigned int i, k; | |
1995 | ||
1996 | if (!gscan_params->scan_fr || !gscan_params->nchannel_buckets) { | |
1997 | DHD_ERROR(("%s : Scan freq - %d or number of channel buckets - %d is empty\n", | |
1998 | __FUNCTION__, gscan_params->scan_fr, gscan_params->nchannel_buckets)); | |
1999 | return false; | |
2000 | } | |
2001 | ||
2002 | for (i = 0; i < gscan_params->nchannel_buckets; i++) { | |
2003 | if (!gscan_params->channel_bucket[i].band) { | |
2004 | for (k = 0; k < gscan_params->channel_bucket[i].num_channels; k++) { | |
2005 | if (gscan_params->channel_bucket[i].chan_list[k] > CHANNEL_5G_MAX) { | |
2006 | DHD_ERROR(("%s : Unknown channel %d\n", __FUNCTION__, | |
2007 | gscan_params->channel_bucket[i].chan_list[k])); | |
2008 | return false; | |
2009 | } | |
2010 | } | |
2011 | } | |
2012 | } | |
2013 | ||
2014 | return true; | |
2015 | } | |
2016 | ||
2017 | static int | |
2018 | dhd_pno_set_for_gscan(dhd_pub_t *dhd, struct dhd_pno_gscan_params *gscan_params) | |
2019 | { | |
2020 | int err = BCME_OK; | |
2021 | int mode, i = 0; | |
2022 | uint16 _chan_list[WL_NUMCHANNELS]; | |
2023 | int tot_nchan = 0; | |
2024 | int num_buckets_to_fw, tot_num_buckets, gscan_param_size; | |
2025 | dhd_pno_status_info_t *_pno_state = PNO_GET_PNOSTATE(dhd); | |
2026 | wl_pfn_gscan_ch_bucket_cfg_t *ch_bucket = NULL; | |
2027 | wl_pfn_gscan_cfg_t *pfn_gscan_cfg_t = NULL; | |
2028 | wl_pfn_bssid_t *p_pfn_bssid = NULL; | |
2029 | dhd_pno_params_t *_params; | |
2030 | bool fw_flushed = FALSE; | |
2031 | ||
2032 | _params = &_pno_state->pno_params_arr[INDEX_OF_GSCAN_PARAMS]; | |
2033 | ||
2034 | NULL_CHECK(dhd->pno_state, "pno_state is NULL", err); | |
2035 | NULL_CHECK(gscan_params, "gscan_params is NULL", err); | |
2036 | ||
2037 | DHD_PNO(("%s enter\n", __FUNCTION__)); | |
2038 | ||
2039 | if (!dhd_support_sta_mode(dhd)) { | |
2040 | err = BCME_BADOPTION; | |
2041 | goto exit; | |
2042 | } | |
2043 | if (!WLS_SUPPORTED(_pno_state)) { | |
2044 | DHD_ERROR(("%s : wifi location service is not supported\n", __FUNCTION__)); | |
2045 | err = BCME_UNSUPPORTED; | |
2046 | goto exit; | |
2047 | } | |
2048 | ||
2049 | if (!validate_gscan_params(gscan_params)) { | |
2050 | DHD_ERROR(("%s : Cannot start gscan - bad params\n", __FUNCTION__)); | |
2051 | err = BCME_BADARG; | |
2052 | goto exit; | |
2053 | } | |
2054 | ||
2055 | if (!(ch_bucket = dhd_pno_gscan_create_channel_list(dhd, _pno_state, | |
2056 | _chan_list, &tot_num_buckets, &num_buckets_to_fw))) { | |
2057 | goto exit; | |
2058 | } | |
2059 | ||
2060 | mutex_lock(&_pno_state->pno_mutex); | |
2061 | /* Clear any pre-existing results in our cache | |
2062 | * not consumed by framework | |
2063 | */ | |
2064 | dhd_gscan_clear_all_batch_results(dhd); | |
2065 | if (_pno_state->pno_mode & (DHD_PNO_GSCAN_MODE | DHD_PNO_LEGACY_MODE)) { | |
2066 | /* store current pno_mode before disabling pno */ | |
2067 | mode = _pno_state->pno_mode; | |
2068 | err = dhd_pno_clean(dhd); | |
2069 | if (err < 0) { | |
2070 | DHD_ERROR(("%s : failed to disable PNO\n", __FUNCTION__)); | |
2071 | mutex_unlock(&_pno_state->pno_mutex); | |
2072 | goto exit; | |
2073 | } | |
2074 | fw_flushed = TRUE; | |
2075 | /* restore the previous mode */ | |
2076 | _pno_state->pno_mode = mode; | |
2077 | } | |
2078 | _pno_state->pno_mode |= DHD_PNO_GSCAN_MODE; | |
2079 | mutex_unlock(&_pno_state->pno_mutex); | |
2080 | ||
2081 | if ((_pno_state->pno_mode & DHD_PNO_LEGACY_MODE) && | |
2082 | !gscan_params->epno_cfg.num_epno_ssid) { | |
2083 | struct dhd_pno_legacy_params *params_legacy; | |
2084 | params_legacy = | |
2085 | &(_pno_state->pno_params_arr[INDEX_OF_LEGACY_PARAMS].params_legacy); | |
2086 | ||
2087 | if ((err = _dhd_pno_add_ssid(dhd, ¶ms_legacy->ssid_list, | |
2088 | params_legacy->nssid)) < 0) { | |
2089 | DHD_ERROR(("failed to add ssid list (err %d) in firmware\n", err)); | |
2090 | goto exit; | |
2091 | } | |
2092 | } | |
2093 | ||
2094 | if ((err = _dhd_pno_set(dhd, _params, DHD_PNO_GSCAN_MODE)) < 0) { | |
2095 | DHD_ERROR(("failed to set call pno_set (err %d) in firmware\n", err)); | |
2096 | goto exit; | |
2097 | } | |
2098 | ||
2099 | gscan_param_size = sizeof(wl_pfn_gscan_cfg_t) + | |
2100 | (num_buckets_to_fw - 1) * sizeof(wl_pfn_gscan_ch_bucket_cfg_t); | |
2101 | pfn_gscan_cfg_t = (wl_pfn_gscan_cfg_t *) MALLOCZ(dhd->osh, gscan_param_size); | |
2102 | ||
2103 | if (!pfn_gscan_cfg_t) { | |
2104 | DHD_ERROR(("%s: failed to malloc memory of size %d\n", | |
2105 | __FUNCTION__, gscan_param_size)); | |
2106 | err = BCME_NOMEM; | |
2107 | goto exit; | |
2108 | } | |
2109 | ||
2110 | pfn_gscan_cfg_t->version = WL_GSCAN_CFG_VERSION; | |
2111 | if (gscan_params->mscan) | |
2112 | pfn_gscan_cfg_t->buffer_threshold = gscan_params->buffer_threshold; | |
2113 | else | |
2114 | pfn_gscan_cfg_t->buffer_threshold = GSCAN_BATCH_NO_THR_SET; | |
2115 | ||
2116 | pfn_gscan_cfg_t->flags = | |
2117 | (gscan_params->send_all_results_flag & GSCAN_SEND_ALL_RESULTS_MASK); | |
2118 | pfn_gscan_cfg_t->flags |= GSCAN_ALL_BUCKETS_IN_FIRST_SCAN_MASK; | |
2119 | pfn_gscan_cfg_t->count_of_channel_buckets = num_buckets_to_fw; | |
2120 | pfn_gscan_cfg_t->retry_threshold = GSCAN_RETRY_THRESHOLD; | |
2121 | ||
2122 | for (i = 0; i < num_buckets_to_fw; i++) { | |
2123 | pfn_gscan_cfg_t->channel_bucket[i].bucket_end_index = | |
2124 | ch_bucket[i].bucket_end_index; | |
2125 | pfn_gscan_cfg_t->channel_bucket[i].bucket_freq_multiple = | |
2126 | ch_bucket[i].bucket_freq_multiple; | |
2127 | pfn_gscan_cfg_t->channel_bucket[i].max_freq_multiple = | |
2128 | ch_bucket[i].max_freq_multiple; | |
2129 | pfn_gscan_cfg_t->channel_bucket[i].repeat = | |
2130 | ch_bucket[i].repeat; | |
2131 | pfn_gscan_cfg_t->channel_bucket[i].flag = | |
2132 | ch_bucket[i].flag; | |
2133 | } | |
2134 | ||
2135 | tot_nchan = pfn_gscan_cfg_t->channel_bucket[num_buckets_to_fw - 1].bucket_end_index + 1; | |
2136 | DHD_PNO(("Total channel num %d total ch_buckets %d ch_buckets_to_fw %d \n", tot_nchan, | |
2137 | tot_num_buckets, num_buckets_to_fw)); | |
2138 | ||
2139 | if ((err = _dhd_pno_cfg(dhd, _chan_list, tot_nchan)) < 0) { | |
2140 | DHD_ERROR(("%s : failed to set call pno_cfg (err %d) in firmware\n", | |
2141 | __FUNCTION__, err)); | |
2142 | goto exit; | |
2143 | } | |
2144 | ||
2145 | if ((err = _dhd_pno_gscan_cfg(dhd, pfn_gscan_cfg_t, gscan_param_size)) < 0) { | |
2146 | DHD_ERROR(("%s : failed to set call pno_gscan_cfg (err %d) in firmware\n", | |
2147 | __FUNCTION__, err)); | |
2148 | goto exit; | |
2149 | } | |
2150 | /* Reprogram ePNO cfg from dhd cache if FW has been flushed */ | |
2151 | if (fw_flushed) { | |
2152 | dhd_pno_set_epno(dhd); | |
2153 | } | |
2154 | ||
2155 | if (gscan_params->nbssid_hotlist) { | |
2156 | struct dhd_pno_bssid *iter, *next; | |
2157 | wl_pfn_bssid_t *ptr; | |
2158 | p_pfn_bssid = (wl_pfn_bssid_t *)MALLOCZ(dhd->osh, | |
2159 | sizeof(wl_pfn_bssid_t) * gscan_params->nbssid_hotlist); | |
2160 | if (p_pfn_bssid == NULL) { | |
2161 | DHD_ERROR(("%s : failed to allocate wl_pfn_bssid_t array" | |
2162 | " (count: %d)", | |
2163 | __FUNCTION__, _params->params_hotlist.nbssid)); | |
2164 | err = BCME_NOMEM; | |
2165 | _pno_state->pno_mode &= ~DHD_PNO_HOTLIST_MODE; | |
2166 | goto exit; | |
2167 | } | |
2168 | ptr = p_pfn_bssid; | |
2169 | /* convert dhd_pno_bssid to wl_pfn_bssid */ | |
2170 | DHD_PNO(("nhotlist %d\n", gscan_params->nbssid_hotlist)); | |
2171 | GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); | |
2172 | list_for_each_entry_safe(iter, next, | |
2173 | &gscan_params->hotlist_bssid_list, list) { | |
2174 | char buffer_hotlist[64]; | |
2175 | GCC_DIAGNOSTIC_POP(); | |
2176 | memcpy(&ptr->macaddr, | |
2177 | &iter->macaddr, ETHER_ADDR_LEN); | |
2178 | BCM_REFERENCE(buffer_hotlist); | |
2179 | DHD_PNO(("%s\n", bcm_ether_ntoa(&ptr->macaddr, buffer_hotlist))); | |
2180 | ptr->flags = iter->flags; | |
2181 | ptr++; | |
2182 | } | |
2183 | ||
2184 | err = _dhd_pno_add_bssid(dhd, p_pfn_bssid, gscan_params->nbssid_hotlist); | |
2185 | if (err < 0) { | |
2186 | DHD_ERROR(("%s : failed to call _dhd_pno_add_bssid(err :%d)\n", | |
2187 | __FUNCTION__, err)); | |
2188 | goto exit; | |
2189 | } | |
2190 | } | |
2191 | ||
2192 | if ((err = _dhd_pno_enable(dhd, PNO_ON)) < 0) { | |
2193 | DHD_ERROR(("%s : failed to enable PNO err %d\n", __FUNCTION__, err)); | |
2194 | } | |
2195 | ||
2196 | exit: | |
2197 | /* clear mode in case of error */ | |
2198 | if (err < 0) { | |
2199 | int ret = dhd_pno_clean(dhd); | |
2200 | ||
2201 | if (ret < 0) { | |
2202 | DHD_ERROR(("%s : failed to call dhd_pno_clean (err: %d)\n", | |
2203 | __FUNCTION__, ret)); | |
2204 | } else { | |
2205 | _pno_state->pno_mode &= ~DHD_PNO_GSCAN_MODE; | |
2206 | } | |
2207 | } | |
2208 | MFREE(dhd->osh, p_pfn_bssid, | |
2209 | sizeof(wl_pfn_bssid_t) * gscan_params->nbssid_hotlist); | |
2210 | if (pfn_gscan_cfg_t) { | |
2211 | MFREE(dhd->osh, pfn_gscan_cfg_t, gscan_param_size); | |
2212 | } | |
2213 | if (ch_bucket) { | |
2214 | MFREE(dhd->osh, ch_bucket, | |
2215 | (tot_num_buckets * sizeof(wl_pfn_gscan_ch_bucket_cfg_t))); | |
2216 | } | |
2217 | return err; | |
2218 | ||
2219 | } | |
2220 | ||
2221 | static wl_pfn_gscan_ch_bucket_cfg_t * | |
2222 | dhd_pno_gscan_create_channel_list(dhd_pub_t *dhd, | |
2223 | dhd_pno_status_info_t *_pno_state, | |
2224 | uint16 *chan_list, | |
2225 | uint32 *num_buckets, | |
2226 | uint32 *num_buckets_to_fw) | |
2227 | { | |
2228 | int i, num_channels, err, nchan = WL_NUMCHANNELS, ch_cnt; | |
2229 | uint16 *ptr = chan_list, max; | |
2230 | wl_pfn_gscan_ch_bucket_cfg_t *ch_bucket; | |
2231 | dhd_pno_params_t *_params = &_pno_state->pno_params_arr[INDEX_OF_GSCAN_PARAMS]; | |
2232 | bool is_pno_legacy_running; | |
2233 | dhd_pno_gscan_channel_bucket_t *gscan_buckets = _params->params_gscan.channel_bucket; | |
2234 | ||
2235 | /* ePNO and Legacy PNO do not co-exist */ | |
2236 | is_pno_legacy_running = ((_pno_state->pno_mode & DHD_PNO_LEGACY_MODE) && | |
2237 | !_params->params_gscan.epno_cfg.num_epno_ssid); | |
2238 | ||
2239 | if (is_pno_legacy_running) | |
2240 | *num_buckets = _params->params_gscan.nchannel_buckets + 1; | |
2241 | else | |
2242 | *num_buckets = _params->params_gscan.nchannel_buckets; | |
2243 | ||
2244 | *num_buckets_to_fw = 0; | |
2245 | ||
2246 | ch_bucket = (wl_pfn_gscan_ch_bucket_cfg_t *) MALLOC(dhd->osh, | |
2247 | ((*num_buckets) * sizeof(wl_pfn_gscan_ch_bucket_cfg_t))); | |
2248 | ||
2249 | if (!ch_bucket) { | |
2250 | DHD_ERROR(("%s: failed to malloc memory of size %zd\n", | |
2251 | __FUNCTION__, (*num_buckets) * sizeof(wl_pfn_gscan_ch_bucket_cfg_t))); | |
2252 | *num_buckets_to_fw = *num_buckets = 0; | |
2253 | return NULL; | |
2254 | } | |
2255 | ||
2256 | max = gscan_buckets[0].bucket_freq_multiple; | |
2257 | num_channels = 0; | |
2258 | /* nchan is the remaining space left in chan_list buffer | |
2259 | * So any overflow list of channels is ignored | |
2260 | */ | |
2261 | for (i = 0; i < _params->params_gscan.nchannel_buckets && nchan; i++) { | |
2262 | if (!gscan_buckets[i].band) { | |
2263 | ch_cnt = MIN(gscan_buckets[i].num_channels, (uint8)nchan); | |
2264 | num_channels += ch_cnt; | |
2265 | memcpy(ptr, gscan_buckets[i].chan_list, | |
2266 | ch_cnt * sizeof(uint16)); | |
2267 | ptr = ptr + ch_cnt; | |
2268 | } else { | |
2269 | /* get a valid channel list based on band B or A */ | |
2270 | err = _dhd_pno_get_channels(dhd, ptr, | |
2271 | &nchan, (gscan_buckets[i].band & GSCAN_ABG_BAND_MASK), | |
2272 | !(gscan_buckets[i].band & GSCAN_DFS_MASK)); | |
2273 | ||
2274 | if (err < 0) { | |
2275 | DHD_ERROR(("%s: failed to get valid channel list(band : %d)\n", | |
2276 | __FUNCTION__, gscan_buckets[i].band)); | |
2277 | MFREE(dhd->osh, ch_bucket, | |
2278 | ((*num_buckets) * sizeof(wl_pfn_gscan_ch_bucket_cfg_t))); | |
2279 | *num_buckets_to_fw = *num_buckets = 0; | |
2280 | return NULL; | |
2281 | } | |
2282 | ||
2283 | num_channels += nchan; | |
2284 | ptr = ptr + nchan; | |
2285 | } | |
2286 | ||
2287 | ch_bucket[i].bucket_end_index = num_channels - 1; | |
2288 | ch_bucket[i].bucket_freq_multiple = gscan_buckets[i].bucket_freq_multiple; | |
2289 | ch_bucket[i].repeat = gscan_buckets[i].repeat; | |
2290 | ch_bucket[i].max_freq_multiple = gscan_buckets[i].bucket_max_multiple; | |
2291 | ch_bucket[i].flag = gscan_buckets[i].report_flag; | |
2292 | /* HAL and FW interpretations are opposite for this bit */ | |
2293 | ch_bucket[i].flag ^= DHD_PNO_REPORT_NO_BATCH; | |
2294 | if (max < gscan_buckets[i].bucket_freq_multiple) | |
2295 | max = gscan_buckets[i].bucket_freq_multiple; | |
2296 | nchan = WL_NUMCHANNELS - num_channels; | |
2297 | *num_buckets_to_fw = *num_buckets_to_fw + 1; | |
2298 | DHD_PNO(("end_idx %d freq_mult - %d\n", | |
2299 | ch_bucket[i].bucket_end_index, ch_bucket[i].bucket_freq_multiple)); | |
2300 | } | |
2301 | ||
2302 | _params->params_gscan.max_ch_bucket_freq = max; | |
2303 | /* Legacy PNO maybe running, which means we need to create a legacy PNO bucket | |
2304 | * Get GCF of Legacy PNO and Gscan scanfreq | |
2305 | */ | |
2306 | if (is_pno_legacy_running) { | |
2307 | dhd_pno_params_t *_params1 = &_pno_state->pno_params_arr[INDEX_OF_LEGACY_PARAMS]; | |
2308 | uint16 *legacy_chan_list = _params1->params_legacy.chan_list; | |
2309 | uint16 common_freq; | |
2310 | uint32 legacy_bucket_idx = _params->params_gscan.nchannel_buckets; | |
2311 | /* If no space is left then only gscan buckets will be sent to FW */ | |
2312 | if (nchan) { | |
2313 | common_freq = gcd(_params->params_gscan.scan_fr, | |
2314 | _params1->params_legacy.scan_fr); | |
2315 | max = gscan_buckets[0].bucket_freq_multiple; | |
2316 | /* GSCAN buckets */ | |
2317 | for (i = 0; i < _params->params_gscan.nchannel_buckets; i++) { | |
2318 | ch_bucket[i].bucket_freq_multiple *= _params->params_gscan.scan_fr; | |
2319 | ch_bucket[i].bucket_freq_multiple /= common_freq; | |
2320 | if (max < gscan_buckets[i].bucket_freq_multiple) | |
2321 | max = gscan_buckets[i].bucket_freq_multiple; | |
2322 | } | |
2323 | /* Legacy PNO bucket */ | |
2324 | ch_bucket[legacy_bucket_idx].bucket_freq_multiple = | |
2325 | _params1->params_legacy.scan_fr; | |
2326 | ch_bucket[legacy_bucket_idx].bucket_freq_multiple /= | |
2327 | common_freq; | |
2328 | _params->params_gscan.max_ch_bucket_freq = MAX(max, | |
2329 | ch_bucket[legacy_bucket_idx].bucket_freq_multiple); | |
2330 | ch_bucket[legacy_bucket_idx].flag = CH_BUCKET_REPORT_REGULAR; | |
2331 | /* Now add channels to the legacy scan bucket */ | |
2332 | for (i = 0; i < _params1->params_legacy.nchan && nchan; i++, nchan--) { | |
2333 | ptr[i] = legacy_chan_list[i]; | |
2334 | num_channels++; | |
2335 | } | |
2336 | ch_bucket[legacy_bucket_idx].bucket_end_index = num_channels - 1; | |
2337 | *num_buckets_to_fw = *num_buckets_to_fw + 1; | |
2338 | DHD_PNO(("end_idx %d freq_mult - %d\n", | |
2339 | ch_bucket[legacy_bucket_idx].bucket_end_index, | |
2340 | ch_bucket[legacy_bucket_idx].bucket_freq_multiple)); | |
2341 | } | |
2342 | } | |
2343 | return ch_bucket; | |
2344 | } | |
2345 | ||
2346 | static int | |
2347 | dhd_pno_stop_for_gscan(dhd_pub_t *dhd) | |
2348 | { | |
2349 | int err = BCME_OK; | |
2350 | int mode; | |
2351 | dhd_pno_status_info_t *_pno_state; | |
2352 | ||
2353 | _pno_state = PNO_GET_PNOSTATE(dhd); | |
2354 | DHD_PNO(("%s enter\n", __FUNCTION__)); | |
2355 | ||
2356 | if (!dhd_support_sta_mode(dhd)) { | |
2357 | err = BCME_BADOPTION; | |
2358 | goto exit; | |
2359 | } | |
2360 | if (!WLS_SUPPORTED(_pno_state)) { | |
2361 | DHD_ERROR(("%s : wifi location service is not supported\n", | |
2362 | __FUNCTION__)); | |
2363 | err = BCME_UNSUPPORTED; | |
2364 | goto exit; | |
2365 | } | |
2366 | ||
2367 | if (!(_pno_state->pno_mode & DHD_PNO_GSCAN_MODE)) { | |
2368 | DHD_ERROR(("%s : GSCAN is not enabled\n", __FUNCTION__)); | |
2369 | goto exit; | |
2370 | } | |
2371 | if (_pno_state->pno_params_arr[INDEX_OF_GSCAN_PARAMS].params_gscan.mscan) { | |
2372 | /* retrieve the batching data from firmware into host */ | |
2373 | err = dhd_wait_batch_results_complete(dhd); | |
2374 | if (err != BCME_OK) | |
2375 | goto exit; | |
2376 | } | |
2377 | mutex_lock(&_pno_state->pno_mutex); | |
2378 | mode = _pno_state->pno_mode & ~DHD_PNO_GSCAN_MODE; | |
2379 | err = dhd_pno_clean(dhd); | |
2380 | if (err < 0) { | |
2381 | DHD_ERROR(("%s : failed to call dhd_pno_clean (err: %d)\n", | |
2382 | __FUNCTION__, err)); | |
2383 | mutex_unlock(&_pno_state->pno_mutex); | |
2384 | return err; | |
2385 | } | |
2386 | _pno_state->pno_mode = mode; | |
2387 | mutex_unlock(&_pno_state->pno_mutex); | |
2388 | ||
2389 | /* Reprogram Legacy PNO if it was running */ | |
2390 | if (_pno_state->pno_mode & DHD_PNO_LEGACY_MODE) { | |
2391 | struct dhd_pno_legacy_params *params_legacy; | |
2392 | uint16 chan_list[WL_NUMCHANNELS]; | |
2393 | ||
2394 | params_legacy = &(_pno_state->pno_params_arr[INDEX_OF_LEGACY_PARAMS].params_legacy); | |
2395 | _pno_state->pno_mode &= ~DHD_PNO_LEGACY_MODE; | |
2396 | ||
2397 | DHD_PNO(("Restarting Legacy PNO SSID scan...\n")); | |
2398 | memcpy(chan_list, params_legacy->chan_list, | |
2399 | (params_legacy->nchan * sizeof(uint16))); | |
2400 | err = dhd_pno_set_legacy_pno(dhd, params_legacy->scan_fr, | |
2401 | params_legacy->pno_repeat, params_legacy->pno_freq_expo_max, | |
2402 | chan_list, params_legacy->nchan); | |
2403 | if (err < 0) { | |
2404 | DHD_ERROR(("%s : failed to restart legacy PNO scan(err: %d)\n", | |
2405 | __FUNCTION__, err)); | |
2406 | goto exit; | |
2407 | } | |
2408 | ||
2409 | } | |
2410 | ||
2411 | exit: | |
2412 | return err; | |
2413 | } | |
2414 | ||
2415 | int | |
2416 | dhd_pno_initiate_gscan_request(dhd_pub_t *dhd, bool run, bool flush) | |
2417 | { | |
2418 | int err = BCME_OK; | |
2419 | dhd_pno_params_t *params; | |
2420 | dhd_pno_status_info_t *_pno_state; | |
2421 | struct dhd_pno_gscan_params *gscan_params; | |
2422 | ||
2423 | NULL_CHECK(dhd, "dhd is NULL\n", err); | |
2424 | NULL_CHECK(dhd->pno_state, "pno_state is NULL", err); | |
2425 | _pno_state = PNO_GET_PNOSTATE(dhd); | |
2426 | ||
2427 | DHD_PNO(("%s enter - run %d flush %d\n", __FUNCTION__, run, flush)); | |
2428 | ||
2429 | params = &_pno_state->pno_params_arr[INDEX_OF_GSCAN_PARAMS]; | |
2430 | gscan_params = ¶ms->params_gscan; | |
2431 | ||
2432 | if (run) { | |
2433 | err = dhd_pno_set_for_gscan(dhd, gscan_params); | |
2434 | } else { | |
2435 | if (flush) { | |
2436 | mutex_lock(&_pno_state->pno_mutex); | |
2437 | dhd_pno_reset_cfg_gscan(dhd, params, _pno_state, GSCAN_FLUSH_ALL_CFG); | |
2438 | mutex_unlock(&_pno_state->pno_mutex); | |
2439 | } | |
2440 | /* Need to stop all gscan */ | |
2441 | err = dhd_pno_stop_for_gscan(dhd); | |
2442 | } | |
2443 | ||
2444 | return err; | |
2445 | } | |
2446 | ||
2447 | int | |
2448 | dhd_pno_enable_full_scan_result(dhd_pub_t *dhd, bool real_time_flag) | |
2449 | { | |
2450 | int err = BCME_OK; | |
2451 | dhd_pno_params_t *params; | |
2452 | dhd_pno_status_info_t *_pno_state; | |
2453 | struct dhd_pno_gscan_params *gscan_params; | |
2454 | uint8 old_flag; | |
2455 | ||
2456 | NULL_CHECK(dhd, "dhd is NULL\n", err); | |
2457 | NULL_CHECK(dhd->pno_state, "pno_state is NULL", err); | |
2458 | _pno_state = PNO_GET_PNOSTATE(dhd); | |
2459 | ||
2460 | DHD_PNO(("%s enter\n", __FUNCTION__)); | |
2461 | ||
2462 | if (!WLS_SUPPORTED(_pno_state)) { | |
2463 | DHD_ERROR(("%s : wifi location service is not supported\n", __FUNCTION__)); | |
2464 | err = BCME_UNSUPPORTED; | |
2465 | goto exit; | |
2466 | } | |
2467 | ||
2468 | params = &_pno_state->pno_params_arr[INDEX_OF_GSCAN_PARAMS]; | |
2469 | gscan_params = ¶ms->params_gscan; | |
2470 | ||
2471 | mutex_lock(&_pno_state->pno_mutex); | |
2472 | ||
2473 | old_flag = gscan_params->send_all_results_flag; | |
2474 | gscan_params->send_all_results_flag = (uint8) real_time_flag; | |
2475 | if (_pno_state->pno_mode & DHD_PNO_GSCAN_MODE) { | |
2476 | if (old_flag != gscan_params->send_all_results_flag) { | |
2477 | wl_pfn_gscan_cfg_t gscan_cfg; | |
2478 | ||
2479 | gscan_cfg.version = WL_GSCAN_CFG_VERSION; | |
2480 | gscan_cfg.flags = (gscan_params->send_all_results_flag & | |
2481 | GSCAN_SEND_ALL_RESULTS_MASK); | |
2482 | gscan_cfg.flags |= GSCAN_CFG_FLAGS_ONLY_MASK; | |
2483 | ||
2484 | if ((err = _dhd_pno_gscan_cfg(dhd, &gscan_cfg, | |
2485 | sizeof(wl_pfn_gscan_cfg_t))) < 0) { | |
2486 | DHD_ERROR(("%s : pno_gscan_cfg failed (err %d) in firmware\n", | |
2487 | __FUNCTION__, err)); | |
2488 | goto exit_mutex_unlock; | |
2489 | } | |
2490 | } else { | |
2491 | DHD_PNO(("No change in flag - %d\n", old_flag)); | |
2492 | } | |
2493 | } else { | |
2494 | DHD_PNO(("Gscan not started\n")); | |
2495 | } | |
2496 | exit_mutex_unlock: | |
2497 | mutex_unlock(&_pno_state->pno_mutex); | |
2498 | exit: | |
2499 | return err; | |
2500 | } | |
2501 | ||
2502 | /* Cleanup any consumed results | |
2503 | * Return TRUE if all results consumed else FALSE | |
2504 | */ | |
2505 | int dhd_gscan_batch_cache_cleanup(dhd_pub_t *dhd) | |
2506 | { | |
2507 | int ret = 0; | |
2508 | dhd_pno_params_t *params; | |
2509 | struct dhd_pno_gscan_params *gscan_params; | |
2510 | dhd_pno_status_info_t *_pno_state; | |
2511 | gscan_results_cache_t *iter, *tmp; | |
2512 | ||
2513 | _pno_state = PNO_GET_PNOSTATE(dhd); | |
2514 | params = &_pno_state->pno_params_arr[INDEX_OF_GSCAN_PARAMS]; | |
2515 | gscan_params = ¶ms->params_gscan; | |
2516 | iter = gscan_params->gscan_batch_cache; | |
2517 | ||
2518 | while (iter) { | |
2519 | if (iter->tot_consumed == iter->tot_count) { | |
2520 | tmp = iter->next; | |
2521 | MFREE(dhd->osh, iter, | |
2522 | ((iter->tot_count - 1) * sizeof(wifi_gscan_result_t)) | |
2523 | + sizeof(gscan_results_cache_t)); | |
2524 | iter = tmp; | |
2525 | } else | |
2526 | break; | |
2527 | } | |
2528 | gscan_params->gscan_batch_cache = iter; | |
2529 | ret = (iter == NULL); | |
2530 | return ret; | |
2531 | } | |
2532 | ||
2533 | static int | |
2534 | _dhd_pno_get_gscan_batch_from_fw(dhd_pub_t *dhd) | |
2535 | { | |
2536 | int err = BCME_OK; | |
2537 | uint32 timestamp = 0, ts = 0, i, j, timediff; | |
2538 | dhd_pno_params_t *params; | |
2539 | dhd_pno_status_info_t *_pno_state; | |
2540 | wl_pfn_lnet_info_v1_t *plnetinfo; | |
2541 | wl_pfn_lnet_info_v2_t *plnetinfo_v2; | |
2542 | struct dhd_pno_gscan_params *gscan_params; | |
2543 | wl_pfn_lscanresults_v1_t *plbestnet_v1 = NULL; | |
2544 | wl_pfn_lscanresults_v2_t *plbestnet_v2 = NULL; | |
2545 | gscan_results_cache_t *iter, *tail; | |
2546 | wifi_gscan_result_t *result; | |
2547 | uint8 *nAPs_per_scan = NULL; | |
2548 | uint8 num_scans_in_cur_iter; | |
2549 | uint16 count; | |
2550 | uint16 fwcount; | |
2551 | uint16 fwstatus = PFN_INCOMPLETE; | |
2552 | struct osl_timespec tm_spec; | |
2553 | ||
2554 | /* Static asserts in _dhd_pno_get_for_batch() below guarantee the v1 and v2 | |
2555 | * net_info and subnet_info structures are compatible in size and SSID offset, | |
2556 | * allowing v1 to be safely used in the code below except for lscanresults | |
2557 | * fields themselves (status, count, offset to netinfo). | |
2558 | */ | |
2559 | ||
2560 | NULL_CHECK(dhd, "dhd is NULL\n", err); | |
2561 | NULL_CHECK(dhd->pno_state, "pno_state is NULL", err); | |
2562 | ||
2563 | _pno_state = PNO_GET_PNOSTATE(dhd); | |
2564 | params = &_pno_state->pno_params_arr[INDEX_OF_GSCAN_PARAMS]; | |
2565 | DHD_PNO(("%s enter\n", __FUNCTION__)); | |
2566 | ||
2567 | if (!WLS_SUPPORTED(_pno_state)) { | |
2568 | DHD_ERROR(("%s : wifi location service is not supported\n", __FUNCTION__)); | |
2569 | err = BCME_UNSUPPORTED; | |
2570 | goto exit; | |
2571 | } | |
2572 | if (!(_pno_state->pno_mode & DHD_PNO_GSCAN_MODE)) { | |
2573 | DHD_ERROR(("%s: GSCAN is not enabled\n", __FUNCTION__)); | |
2574 | goto exit; | |
2575 | } | |
2576 | gscan_params = ¶ms->params_gscan; | |
2577 | nAPs_per_scan = (uint8 *) MALLOC(dhd->osh, gscan_params->mscan); | |
2578 | ||
2579 | if (!nAPs_per_scan) { | |
2580 | DHD_ERROR(("%s :Out of memory!! Cant malloc %d bytes\n", __FUNCTION__, | |
2581 | gscan_params->mscan)); | |
2582 | err = BCME_NOMEM; | |
2583 | goto exit; | |
2584 | } | |
2585 | ||
2586 | plbestnet_v1 = (wl_pfn_lscanresults_v1_t *)MALLOC(dhd->osh, PNO_BESTNET_LEN); | |
2587 | if (!plbestnet_v1) { | |
2588 | DHD_ERROR(("%s :Out of memory!! Cant malloc %d bytes\n", __FUNCTION__, | |
2589 | (int)PNO_BESTNET_LEN)); | |
2590 | err = BCME_NOMEM; | |
2591 | goto exit; | |
2592 | } | |
2593 | plbestnet_v2 = (wl_pfn_lscanresults_v2_t *)plbestnet_v1; | |
2594 | ||
2595 | mutex_lock(&_pno_state->pno_mutex); | |
2596 | ||
2597 | dhd_gscan_clear_all_batch_results(dhd); | |
2598 | ||
2599 | if (!(_pno_state->pno_mode & DHD_PNO_GSCAN_MODE)) { | |
2600 | DHD_ERROR(("%s : GSCAN is not enabled\n", __FUNCTION__)); | |
2601 | goto exit_mutex_unlock; | |
2602 | } | |
2603 | ||
2604 | timediff = gscan_params->scan_fr * 1000; | |
2605 | timediff = timediff >> 1; | |
2606 | ||
2607 | /* Ok, now lets start getting results from the FW */ | |
2608 | tail = gscan_params->gscan_batch_cache; | |
2609 | do { | |
2610 | err = dhd_iovar(dhd, 0, "pfnlbest", NULL, 0, (char *)plbestnet_v1, PNO_BESTNET_LEN, | |
2611 | FALSE); | |
2612 | if (err < 0) { | |
2613 | DHD_ERROR(("%s : Cannot get all the batch results, err :%d\n", | |
2614 | __FUNCTION__, err)); | |
2615 | goto exit_mutex_unlock; | |
2616 | } | |
2617 | osl_get_monotonic_boottime(&tm_spec); | |
2618 | ||
2619 | if (plbestnet_v1->version == PFN_LBEST_SCAN_RESULT_VERSION_V1) { | |
2620 | fwstatus = plbestnet_v1->status; | |
2621 | fwcount = plbestnet_v1->count; | |
2622 | plnetinfo = &plbestnet_v1->netinfo[0]; | |
2623 | ||
2624 | DHD_PNO(("ver %d, status : %d, count %d\n", | |
2625 | plbestnet_v1->version, fwstatus, fwcount)); | |
2626 | ||
2627 | if (fwcount == 0) { | |
2628 | DHD_PNO(("No more batch results\n")); | |
2629 | goto exit_mutex_unlock; | |
2630 | } | |
2631 | if (fwcount > BESTN_MAX) { | |
2632 | DHD_ERROR(("%s :fwcount %d is greater than BESTN_MAX %d \n", | |
2633 | __FUNCTION__, fwcount, (int)BESTN_MAX)); | |
2634 | /* Process only BESTN_MAX number of results per batch */ | |
2635 | fwcount = BESTN_MAX; | |
2636 | } | |
2637 | num_scans_in_cur_iter = 0; | |
2638 | ||
2639 | timestamp = plnetinfo->timestamp; | |
2640 | /* find out how many scans' results did we get in | |
2641 | * this batch of FW results | |
2642 | */ | |
2643 | for (i = 0, count = 0; i < fwcount; i++, count++, plnetinfo++) { | |
2644 | /* Unlikely to happen, but just in case the results from | |
2645 | * FW doesnt make sense..... Assume its part of one single scan | |
2646 | */ | |
2647 | if (num_scans_in_cur_iter >= gscan_params->mscan) { | |
2648 | num_scans_in_cur_iter = 0; | |
2649 | count = fwcount; | |
2650 | break; | |
2651 | } | |
2652 | if (TIME_DIFF_MS(timestamp, plnetinfo->timestamp) > timediff) { | |
2653 | nAPs_per_scan[num_scans_in_cur_iter] = count; | |
2654 | count = 0; | |
2655 | num_scans_in_cur_iter++; | |
2656 | } | |
2657 | timestamp = plnetinfo->timestamp; | |
2658 | } | |
2659 | if (num_scans_in_cur_iter < gscan_params->mscan) { | |
2660 | nAPs_per_scan[num_scans_in_cur_iter] = count; | |
2661 | num_scans_in_cur_iter++; | |
2662 | } | |
2663 | ||
2664 | DHD_PNO(("num_scans_in_cur_iter %d\n", num_scans_in_cur_iter)); | |
2665 | /* reset plnetinfo to the first item for the next loop */ | |
2666 | plnetinfo -= i; | |
2667 | ||
2668 | for (i = 0; i < num_scans_in_cur_iter; i++) { | |
2669 | iter = (gscan_results_cache_t *) | |
2670 | MALLOCZ(dhd->osh, ((nAPs_per_scan[i] - 1) * | |
2671 | sizeof(wifi_gscan_result_t)) + | |
2672 | sizeof(gscan_results_cache_t)); | |
2673 | if (!iter) { | |
2674 | DHD_ERROR(("%s :Out of memory!! Cant malloc %d bytes\n", | |
2675 | __FUNCTION__, gscan_params->mscan)); | |
2676 | err = BCME_NOMEM; | |
2677 | goto exit_mutex_unlock; | |
2678 | } | |
2679 | /* Need this check because the new set of results from FW | |
2680 | * maybe a continuation of previous sets' scan results | |
2681 | */ | |
2682 | if (TIME_DIFF_MS(ts, plnetinfo->timestamp) > timediff) { | |
2683 | iter->scan_id = ++gscan_params->scan_id; | |
2684 | } else { | |
2685 | iter->scan_id = gscan_params->scan_id; | |
2686 | } | |
2687 | DHD_PNO(("scan_id %d tot_count %d \n", | |
2688 | gscan_params->scan_id, nAPs_per_scan[i])); | |
2689 | iter->tot_count = nAPs_per_scan[i]; | |
2690 | iter->tot_consumed = 0; | |
2691 | iter->flag = 0; | |
2692 | if (plnetinfo->flags & PFN_PARTIAL_SCAN_MASK) { | |
2693 | DHD_PNO(("This scan is aborted\n")); | |
2694 | iter->flag = (ENABLE << PNO_STATUS_ABORT); | |
2695 | } else if (gscan_params->reason) { | |
2696 | iter->flag = (ENABLE << gscan_params->reason); | |
2697 | } | |
2698 | ||
2699 | if (!tail) { | |
2700 | gscan_params->gscan_batch_cache = iter; | |
2701 | } else { | |
2702 | tail->next = iter; | |
2703 | } | |
2704 | tail = iter; | |
2705 | iter->next = NULL; | |
2706 | for (j = 0; j < nAPs_per_scan[i]; j++, plnetinfo++) { | |
2707 | result = &iter->results[j]; | |
2708 | ||
2709 | result->channel = | |
2710 | wf_channel2mhz(plnetinfo->pfnsubnet.channel, | |
2711 | (plnetinfo->pfnsubnet.channel <= CH_MAX_2G_CHANNEL? | |
2712 | WF_CHAN_FACTOR_2_4_G : WF_CHAN_FACTOR_5_G)); | |
2713 | result->rssi = (int32) plnetinfo->RSSI; | |
2714 | result->beacon_period = 0; | |
2715 | result->capability = 0; | |
2716 | result->rtt = (uint64) plnetinfo->rtt0; | |
2717 | result->rtt_sd = (uint64) plnetinfo->rtt1; | |
2718 | result->ts = convert_fw_rel_time_to_systime(&tm_spec, | |
2719 | plnetinfo->timestamp); | |
2720 | ts = plnetinfo->timestamp; | |
2721 | if (plnetinfo->pfnsubnet.SSID_len > DOT11_MAX_SSID_LEN) { | |
2722 | DHD_ERROR(("%s: Invalid SSID length %d\n", | |
2723 | __FUNCTION__, | |
2724 | plnetinfo->pfnsubnet.SSID_len)); | |
2725 | plnetinfo->pfnsubnet.SSID_len = DOT11_MAX_SSID_LEN; | |
2726 | } | |
2727 | memcpy(result->ssid, plnetinfo->pfnsubnet.SSID, | |
2728 | plnetinfo->pfnsubnet.SSID_len); | |
2729 | result->ssid[plnetinfo->pfnsubnet.SSID_len] = '\0'; | |
2730 | memcpy(&result->macaddr, &plnetinfo->pfnsubnet.BSSID, | |
2731 | ETHER_ADDR_LEN); | |
2732 | ||
2733 | DHD_PNO(("\tSSID : ")); | |
2734 | DHD_PNO(("\n")); | |
2735 | DHD_PNO(("\tBSSID: "MACDBG"\n", | |
2736 | MAC2STRDBG(result->macaddr.octet))); | |
2737 | DHD_PNO(("\tchannel: %d, RSSI: %d, timestamp: %d ms\n", | |
2738 | plnetinfo->pfnsubnet.channel, | |
2739 | plnetinfo->RSSI, plnetinfo->timestamp)); | |
2740 | DHD_PNO(("\tRTT0 : %d, RTT1: %d\n", | |
2741 | plnetinfo->rtt0, plnetinfo->rtt1)); | |
2742 | ||
2743 | } | |
2744 | } | |
2745 | ||
2746 | } else if (plbestnet_v2->version == PFN_LBEST_SCAN_RESULT_VERSION_V2) { | |
2747 | fwstatus = plbestnet_v2->status; | |
2748 | fwcount = plbestnet_v2->count; | |
2749 | plnetinfo_v2 = (wl_pfn_lnet_info_v2_t*)&plbestnet_v2->netinfo[0]; | |
2750 | ||
2751 | DHD_PNO(("ver %d, status : %d, count %d\n", | |
2752 | plbestnet_v2->version, fwstatus, fwcount)); | |
2753 | ||
2754 | if (fwcount == 0) { | |
2755 | DHD_PNO(("No more batch results\n")); | |
2756 | goto exit_mutex_unlock; | |
2757 | } | |
2758 | if (fwcount > BESTN_MAX) { | |
2759 | DHD_ERROR(("%s :fwcount %d is greater than BESTN_MAX %d \n", | |
2760 | __FUNCTION__, fwcount, (int)BESTN_MAX)); | |
2761 | /* Process only BESTN_MAX number of results per batch */ | |
2762 | fwcount = BESTN_MAX; | |
2763 | } | |
2764 | num_scans_in_cur_iter = 0; | |
2765 | ||
2766 | timestamp = plnetinfo_v2->timestamp; | |
2767 | /* find out how many scans' results did we get | |
2768 | * in this batch of FW results | |
2769 | */ | |
2770 | for (i = 0, count = 0; i < fwcount; i++, count++, plnetinfo_v2++) { | |
2771 | /* Unlikely to happen, but just in case the results from | |
2772 | * FW doesnt make sense..... Assume its part of one single scan | |
2773 | */ | |
2774 | if (num_scans_in_cur_iter >= gscan_params->mscan) { | |
2775 | num_scans_in_cur_iter = 0; | |
2776 | count = fwcount; | |
2777 | break; | |
2778 | } | |
2779 | if (TIME_DIFF_MS(timestamp, plnetinfo_v2->timestamp) > timediff) { | |
2780 | nAPs_per_scan[num_scans_in_cur_iter] = count; | |
2781 | count = 0; | |
2782 | num_scans_in_cur_iter++; | |
2783 | } | |
2784 | timestamp = plnetinfo_v2->timestamp; | |
2785 | } | |
2786 | if (num_scans_in_cur_iter < gscan_params->mscan) { | |
2787 | nAPs_per_scan[num_scans_in_cur_iter] = count; | |
2788 | num_scans_in_cur_iter++; | |
2789 | } | |
2790 | ||
2791 | DHD_PNO(("num_scans_in_cur_iter %d\n", num_scans_in_cur_iter)); | |
2792 | /* reset plnetinfo to the first item for the next loop */ | |
2793 | plnetinfo_v2 -= i; | |
2794 | ||
2795 | for (i = 0; i < num_scans_in_cur_iter; i++) { | |
2796 | iter = (gscan_results_cache_t *) | |
2797 | MALLOCZ(dhd->osh, ((nAPs_per_scan[i] - 1) * | |
2798 | sizeof(wifi_gscan_result_t)) + | |
2799 | sizeof(gscan_results_cache_t)); | |
2800 | if (!iter) { | |
2801 | DHD_ERROR(("%s :Out of memory!! Cant malloc %d bytes\n", | |
2802 | __FUNCTION__, gscan_params->mscan)); | |
2803 | err = BCME_NOMEM; | |
2804 | goto exit_mutex_unlock; | |
2805 | } | |
2806 | /* Need this check because the new set of results from FW | |
2807 | * maybe a continuation of previous sets' scan results | |
2808 | */ | |
2809 | if (TIME_DIFF_MS(ts, plnetinfo_v2->timestamp) > timediff) { | |
2810 | iter->scan_id = ++gscan_params->scan_id; | |
2811 | } else { | |
2812 | iter->scan_id = gscan_params->scan_id; | |
2813 | } | |
2814 | DHD_PNO(("scan_id %d tot_count %d ch_bucket %x\n", | |
2815 | gscan_params->scan_id, nAPs_per_scan[i], | |
2816 | plbestnet_v2->scan_ch_buckets[i])); | |
2817 | iter->tot_count = nAPs_per_scan[i]; | |
2818 | iter->scan_ch_bucket = plbestnet_v2->scan_ch_buckets[i]; | |
2819 | iter->tot_consumed = 0; | |
2820 | iter->flag = 0; | |
2821 | if (plnetinfo_v2->flags & PFN_PARTIAL_SCAN_MASK) { | |
2822 | DHD_PNO(("This scan is aborted\n")); | |
2823 | iter->flag = (ENABLE << PNO_STATUS_ABORT); | |
2824 | } else if (gscan_params->reason) { | |
2825 | iter->flag = (ENABLE << gscan_params->reason); | |
2826 | } | |
2827 | ||
2828 | if (!tail) { | |
2829 | gscan_params->gscan_batch_cache = iter; | |
2830 | } else { | |
2831 | tail->next = iter; | |
2832 | } | |
2833 | tail = iter; | |
2834 | iter->next = NULL; | |
2835 | for (j = 0; j < nAPs_per_scan[i]; j++, plnetinfo_v2++) { | |
2836 | result = &iter->results[j]; | |
2837 | ||
2838 | result->channel = | |
2839 | wf_channel2mhz(plnetinfo_v2->pfnsubnet.channel, | |
2840 | (plnetinfo_v2->pfnsubnet.channel <= | |
2841 | CH_MAX_2G_CHANNEL? | |
2842 | WF_CHAN_FACTOR_2_4_G : WF_CHAN_FACTOR_5_G)); | |
2843 | result->rssi = (int32) plnetinfo_v2->RSSI; | |
2844 | /* Info not available & not expected */ | |
2845 | result->beacon_period = 0; | |
2846 | result->capability = 0; | |
2847 | result->rtt = (uint64) plnetinfo_v2->rtt0; | |
2848 | result->rtt_sd = (uint64) plnetinfo_v2->rtt1; | |
2849 | result->ts = convert_fw_rel_time_to_systime(&tm_spec, | |
2850 | plnetinfo_v2->timestamp); | |
2851 | ts = plnetinfo_v2->timestamp; | |
2852 | if (plnetinfo_v2->pfnsubnet.SSID_len > DOT11_MAX_SSID_LEN) { | |
2853 | DHD_ERROR(("%s: Invalid SSID length %d\n", | |
2854 | __FUNCTION__, | |
2855 | plnetinfo_v2->pfnsubnet.SSID_len)); | |
2856 | plnetinfo_v2->pfnsubnet.SSID_len = | |
2857 | DOT11_MAX_SSID_LEN; | |
2858 | } | |
2859 | memcpy(result->ssid, plnetinfo_v2->pfnsubnet.u.SSID, | |
2860 | plnetinfo_v2->pfnsubnet.SSID_len); | |
2861 | result->ssid[plnetinfo_v2->pfnsubnet.SSID_len] = '\0'; | |
2862 | memcpy(&result->macaddr, &plnetinfo_v2->pfnsubnet.BSSID, | |
2863 | ETHER_ADDR_LEN); | |
2864 | ||
2865 | DHD_PNO(("\tSSID : ")); | |
2866 | DHD_PNO(("\n")); | |
2867 | DHD_PNO(("\tBSSID: "MACDBG"\n", | |
2868 | MAC2STRDBG(result->macaddr.octet))); | |
2869 | DHD_PNO(("\tchannel: %d, RSSI: %d, timestamp: %d ms\n", | |
2870 | plnetinfo_v2->pfnsubnet.channel, | |
2871 | plnetinfo_v2->RSSI, plnetinfo_v2->timestamp)); | |
2872 | DHD_PNO(("\tRTT0 : %d, RTT1: %d\n", | |
2873 | plnetinfo_v2->rtt0, plnetinfo_v2->rtt1)); | |
2874 | ||
2875 | } | |
2876 | } | |
2877 | ||
2878 | } else { | |
2879 | err = BCME_VERSION; | |
2880 | DHD_ERROR(("bestnet fw version %d not supported\n", | |
2881 | plbestnet_v1->version)); | |
2882 | goto exit_mutex_unlock; | |
2883 | } | |
2884 | } while (fwstatus == PFN_INCOMPLETE); | |
2885 | ||
2886 | exit_mutex_unlock: | |
2887 | mutex_unlock(&_pno_state->pno_mutex); | |
2888 | exit: | |
2889 | params->params_gscan.get_batch_flag = GSCAN_BATCH_RETRIEVAL_COMPLETE; | |
2890 | smp_wmb(); | |
2891 | wake_up_interruptible(&_pno_state->batch_get_wait); | |
2892 | if (nAPs_per_scan) { | |
2893 | MFREE(dhd->osh, nAPs_per_scan, gscan_params->mscan * sizeof(uint8)); | |
2894 | } | |
2895 | if (plbestnet_v1) { | |
2896 | MFREE(dhd->osh, plbestnet_v1, PNO_BESTNET_LEN); | |
2897 | } | |
2898 | DHD_PNO(("Batch retrieval done!\n")); | |
2899 | return err; | |
2900 | } | |
2901 | #endif /* GSCAN_SUPPORT */ | |
2902 | ||
2903 | #if defined(GSCAN_SUPPORT) || defined(DHD_GET_VALID_CHANNELS) | |
2904 | static void * | |
2905 | dhd_get_gscan_batch_results(dhd_pub_t *dhd, uint32 *len) | |
2906 | { | |
2907 | gscan_results_cache_t *iter, *results; | |
2908 | dhd_pno_status_info_t *_pno_state; | |
2909 | dhd_pno_params_t *_params; | |
2910 | uint16 num_scan_ids = 0, num_results = 0; | |
2911 | ||
2912 | _pno_state = PNO_GET_PNOSTATE(dhd); | |
2913 | _params = &_pno_state->pno_params_arr[INDEX_OF_GSCAN_PARAMS]; | |
2914 | ||
2915 | iter = results = _params->params_gscan.gscan_batch_cache; | |
2916 | while (iter) { | |
2917 | num_results += iter->tot_count - iter->tot_consumed; | |
2918 | num_scan_ids++; | |
2919 | iter = iter->next; | |
2920 | } | |
2921 | ||
2922 | *len = ((num_results << 16) | (num_scan_ids)); | |
2923 | return results; | |
2924 | } | |
2925 | ||
2926 | void * | |
2927 | dhd_pno_get_gscan(dhd_pub_t *dhd, dhd_pno_gscan_cmd_cfg_t type, | |
2928 | void *info, uint32 *len) | |
2929 | { | |
2930 | void *ret = NULL; | |
2931 | dhd_pno_gscan_capabilities_t *ptr; | |
2932 | dhd_pno_ssid_t *ssid_elem; | |
2933 | dhd_pno_params_t *_params; | |
2934 | dhd_epno_ssid_cfg_t *epno_cfg; | |
2935 | dhd_pno_status_info_t *_pno_state; | |
2936 | ||
2937 | if (!dhd || !dhd->pno_state) { | |
2938 | DHD_ERROR(("NULL POINTER : %s\n", __FUNCTION__)); | |
2939 | return NULL; | |
2940 | } | |
2941 | ||
2942 | _pno_state = PNO_GET_PNOSTATE(dhd); | |
2943 | _params = &_pno_state->pno_params_arr[INDEX_OF_GSCAN_PARAMS]; | |
2944 | ||
2945 | if (!len) { | |
2946 | DHD_ERROR(("%s: len is NULL\n", __FUNCTION__)); | |
2947 | return NULL; | |
2948 | } | |
2949 | ||
2950 | switch (type) { | |
2951 | case DHD_PNO_GET_CAPABILITIES: | |
2952 | ptr = (dhd_pno_gscan_capabilities_t *) | |
2953 | MALLOCZ(dhd->osh, sizeof(dhd_pno_gscan_capabilities_t)); | |
2954 | if (!ptr) | |
2955 | break; | |
2956 | /* Hardcoding these values for now, need to get | |
2957 | * these values from FW, will change in a later check-in | |
2958 | */ | |
2959 | ptr->max_scan_cache_size = GSCAN_MAX_AP_CACHE; | |
2960 | ptr->max_scan_buckets = GSCAN_MAX_CH_BUCKETS; | |
2961 | ptr->max_ap_cache_per_scan = GSCAN_MAX_AP_CACHE_PER_SCAN; | |
2962 | ptr->max_rssi_sample_size = PFN_SWC_RSSI_WINDOW_MAX; | |
2963 | ptr->max_scan_reporting_threshold = 100; | |
2964 | ptr->max_hotlist_bssids = PFN_HOTLIST_MAX_NUM_APS; | |
2965 | ptr->max_hotlist_ssids = 0; | |
2966 | ptr->max_significant_wifi_change_aps = 0; | |
2967 | ptr->max_bssid_history_entries = 0; | |
2968 | ptr->max_epno_ssid_crc32 = MAX_EPNO_SSID_NUM; | |
2969 | ptr->max_epno_hidden_ssid = MAX_EPNO_HIDDEN_SSID; | |
2970 | ptr->max_white_list_ssid = MAX_WHITELIST_SSID; | |
2971 | ret = (void *)ptr; | |
2972 | *len = sizeof(dhd_pno_gscan_capabilities_t); | |
2973 | break; | |
2974 | ||
2975 | case DHD_PNO_GET_BATCH_RESULTS: | |
2976 | ret = dhd_get_gscan_batch_results(dhd, len); | |
2977 | break; | |
2978 | case DHD_PNO_GET_CHANNEL_LIST: | |
2979 | if (info) { | |
2980 | uint16 ch_list[WL_NUMCHANNELS]; | |
2981 | uint32 *p, mem_needed, i; | |
2982 | int32 err, nchan = WL_NUMCHANNELS; | |
2983 | uint32 *gscan_band = (uint32 *) info; | |
2984 | uint8 band = 0; | |
2985 | ||
2986 | /* No band specified?, nothing to do */ | |
2987 | if ((*gscan_band & GSCAN_BAND_MASK) == 0) { | |
2988 | DHD_PNO(("No band specified\n")); | |
2989 | *len = 0; | |
2990 | break; | |
2991 | } | |
2992 | ||
2993 | /* HAL and DHD use different bits for 2.4G and | |
2994 | * 5G in bitmap. Hence translating it here... | |
2995 | */ | |
2996 | if (*gscan_band & GSCAN_BG_BAND_MASK) { | |
2997 | band |= WLC_BAND_2G; | |
2998 | } | |
2999 | if (*gscan_band & GSCAN_A_BAND_MASK) { | |
3000 | band |= WLC_BAND_5G; | |
3001 | } | |
3002 | ||
3003 | err = _dhd_pno_get_channels(dhd, ch_list, &nchan, | |
3004 | (band & GSCAN_ABG_BAND_MASK), | |
3005 | !(*gscan_band & GSCAN_DFS_MASK)); | |
3006 | ||
3007 | if (err < 0) { | |
3008 | DHD_ERROR(("%s: failed to get valid channel list\n", | |
3009 | __FUNCTION__)); | |
3010 | *len = 0; | |
3011 | } else { | |
3012 | mem_needed = sizeof(uint32) * nchan; | |
3013 | p = (uint32 *)MALLOC(dhd->osh, mem_needed); | |
3014 | if (!p) { | |
3015 | DHD_ERROR(("%s: Unable to malloc %d bytes\n", | |
3016 | __FUNCTION__, mem_needed)); | |
3017 | break; | |
3018 | } | |
3019 | for (i = 0; i < nchan; i++) { | |
3020 | p[i] = wf_channel2mhz(ch_list[i], | |
3021 | (ch_list[i] <= CH_MAX_2G_CHANNEL? | |
3022 | WF_CHAN_FACTOR_2_4_G : WF_CHAN_FACTOR_5_G)); | |
3023 | } | |
3024 | ret = p; | |
3025 | *len = mem_needed; | |
3026 | } | |
3027 | } else { | |
3028 | *len = 0; | |
3029 | DHD_ERROR(("%s: info buffer is NULL\n", __FUNCTION__)); | |
3030 | } | |
3031 | break; | |
3032 | case DHD_PNO_GET_NEW_EPNO_SSID_ELEM: | |
3033 | epno_cfg = &_params->params_gscan.epno_cfg; | |
3034 | if (epno_cfg->num_epno_ssid >= | |
3035 | MAX_EPNO_SSID_NUM) { | |
3036 | DHD_ERROR(("Excessive number of ePNO SSIDs programmed %d\n", | |
3037 | epno_cfg->num_epno_ssid)); | |
3038 | return NULL; | |
3039 | } | |
3040 | if (!epno_cfg->num_epno_ssid) { | |
3041 | INIT_LIST_HEAD(&epno_cfg->epno_ssid_list); | |
3042 | } | |
3043 | ssid_elem = MALLOCZ(dhd->osh, sizeof(dhd_pno_ssid_t)); | |
3044 | if (!ssid_elem) { | |
3045 | DHD_ERROR(("EPNO ssid: cannot alloc %zd bytes", | |
3046 | sizeof(dhd_pno_ssid_t))); | |
3047 | return NULL; | |
3048 | } | |
3049 | epno_cfg->num_epno_ssid++; | |
3050 | list_add_tail(&ssid_elem->list, &epno_cfg->epno_ssid_list); | |
3051 | ret = ssid_elem; | |
3052 | break; | |
3053 | default: | |
3054 | DHD_ERROR(("%s: Unrecognized cmd type - %d\n", __FUNCTION__, type)); | |
3055 | break; | |
3056 | } | |
3057 | ||
3058 | return ret; | |
3059 | ||
3060 | } | |
3061 | #endif /* GSCAN_SUPPORT || DHD_GET_VALID_CHANNELS */ | |
3062 | ||
3063 | static int | |
3064 | _dhd_pno_get_for_batch(dhd_pub_t *dhd, char *buf, int bufsize, int reason) | |
3065 | { | |
3066 | int err = BCME_OK; | |
3067 | int i, j; | |
3068 | uint32 timestamp = 0; | |
3069 | dhd_pno_params_t *_params = NULL; | |
3070 | dhd_pno_status_info_t *_pno_state = NULL; | |
3071 | wl_pfn_lscanresults_v1_t *plbestnet_v1 = NULL; | |
3072 | wl_pfn_lscanresults_v2_t *plbestnet_v2 = NULL; | |
3073 | wl_pfn_lnet_info_v1_t *plnetinfo; | |
3074 | wl_pfn_lnet_info_v2_t *plnetinfo_v2; | |
3075 | dhd_pno_bestnet_entry_t *pbestnet_entry; | |
3076 | dhd_pno_best_header_t *pbestnetheader = NULL; | |
3077 | dhd_pno_scan_results_t *pscan_results = NULL, *siter, *snext; | |
3078 | bool allocate_header = FALSE; | |
3079 | uint16 fwstatus = PFN_INCOMPLETE; | |
3080 | uint16 fwcount; | |
3081 | ||
3082 | NULL_CHECK(dhd, "dhd is NULL", err); | |
3083 | NULL_CHECK(dhd->pno_state, "pno_state is NULL", err); | |
3084 | ||
3085 | /* The static asserts below guarantee the v1 and v2 net_info and subnet_info | |
3086 | * structures are compatible in size and SSID offset, allowing v1 to be safely | |
3087 | * used in the code below except for lscanresults fields themselves | |
3088 | * (status, count, offset to netinfo). | |
3089 | */ | |
3090 | STATIC_ASSERT(sizeof(wl_pfn_net_info_v1_t) == sizeof(wl_pfn_net_info_v2_t)); | |
3091 | STATIC_ASSERT(sizeof(wl_pfn_lnet_info_v1_t) == sizeof(wl_pfn_lnet_info_v2_t)); | |
3092 | STATIC_ASSERT(sizeof(wl_pfn_subnet_info_v1_t) == sizeof(wl_pfn_subnet_info_v2_t)); | |
3093 | ASSERT(OFFSETOF(wl_pfn_subnet_info_v1_t, SSID) == | |
3094 | OFFSETOF(wl_pfn_subnet_info_v2_t, u.SSID)); | |
3095 | ||
3096 | DHD_PNO(("%s enter\n", __FUNCTION__)); | |
3097 | _pno_state = PNO_GET_PNOSTATE(dhd); | |
3098 | ||
3099 | if (!dhd_support_sta_mode(dhd)) { | |
3100 | err = BCME_BADOPTION; | |
3101 | goto exit_no_unlock; | |
3102 | } | |
3103 | ||
3104 | if (!WLS_SUPPORTED(_pno_state)) { | |
3105 | DHD_ERROR(("%s : wifi location service is not supported\n", __FUNCTION__)); | |
3106 | err = BCME_UNSUPPORTED; | |
3107 | goto exit_no_unlock; | |
3108 | } | |
3109 | ||
3110 | if (!(_pno_state->pno_mode & DHD_PNO_BATCH_MODE)) { | |
3111 | DHD_ERROR(("%s: Batching SCAN mode is not enabled\n", __FUNCTION__)); | |
3112 | goto exit_no_unlock; | |
3113 | } | |
3114 | mutex_lock(&_pno_state->pno_mutex); | |
3115 | _params = &_pno_state->pno_params_arr[INDEX_OF_BATCH_PARAMS]; | |
3116 | if (buf && bufsize) { | |
3117 | if (!list_empty(&_params->params_batch.get_batch.expired_scan_results_list)) { | |
3118 | /* need to check whether we have cashed data or not */ | |
3119 | DHD_PNO(("%s: have cashed batching data in Driver\n", | |
3120 | __FUNCTION__)); | |
3121 | /* convert to results format */ | |
3122 | goto convert_format; | |
3123 | } else { | |
3124 | /* this is a first try to get batching results */ | |
3125 | if (!list_empty(&_params->params_batch.get_batch.scan_results_list)) { | |
3126 | /* move the scan_results_list to expired_scan_results_lists */ | |
3127 | GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); | |
3128 | list_for_each_entry_safe(siter, snext, | |
3129 | &_params->params_batch.get_batch.scan_results_list, list) { | |
3130 | GCC_DIAGNOSTIC_POP(); | |
3131 | list_move_tail(&siter->list, | |
3132 | &_params->params_batch.get_batch.expired_scan_results_list); | |
3133 | } | |
3134 | _params->params_batch.get_batch.top_node_cnt = 0; | |
3135 | _params->params_batch.get_batch.expired_tot_scan_cnt = | |
3136 | _params->params_batch.get_batch.tot_scan_cnt; | |
3137 | _params->params_batch.get_batch.tot_scan_cnt = 0; | |
3138 | goto convert_format; | |
3139 | } | |
3140 | } | |
3141 | } | |
3142 | /* create dhd_pno_scan_results_t whenever we got event WLC_E_PFN_BEST_BATCHING */ | |
3143 | pscan_results = (dhd_pno_scan_results_t *)MALLOC(dhd->osh, SCAN_RESULTS_SIZE); | |
3144 | if (pscan_results == NULL) { | |
3145 | err = BCME_NOMEM; | |
3146 | DHD_ERROR(("failed to allocate dhd_pno_scan_results_t\n")); | |
3147 | goto exit; | |
3148 | } | |
3149 | pscan_results->bestnetheader = NULL; | |
3150 | pscan_results->cnt_header = 0; | |
3151 | /* add the element into list unless total node cnt is less than MAX_NODE_ CNT */ | |
3152 | if (_params->params_batch.get_batch.top_node_cnt < MAX_NODE_CNT) { | |
3153 | list_add(&pscan_results->list, &_params->params_batch.get_batch.scan_results_list); | |
3154 | _params->params_batch.get_batch.top_node_cnt++; | |
3155 | } else { | |
3156 | int _removed_scan_cnt; | |
3157 | /* remove oldest one and add new one */ | |
3158 | DHD_PNO(("%s : Remove oldest node and add new one\n", __FUNCTION__)); | |
3159 | _removed_scan_cnt = _dhd_pno_clear_all_batch_results(dhd, | |
3160 | &_params->params_batch.get_batch.scan_results_list, TRUE); | |
3161 | _params->params_batch.get_batch.tot_scan_cnt -= _removed_scan_cnt; | |
3162 | list_add(&pscan_results->list, &_params->params_batch.get_batch.scan_results_list); | |
3163 | ||
3164 | } | |
3165 | ||
3166 | plbestnet_v1 = (wl_pfn_lscanresults_v1_t *)MALLOC(dhd->osh, PNO_BESTNET_LEN); | |
3167 | NULL_CHECK(plbestnet_v1, "failed to allocate buffer for bestnet", err); | |
3168 | plbestnet_v2 = (wl_pfn_lscanresults_v2_t*)plbestnet_v1; | |
3169 | ||
3170 | DHD_PNO(("%s enter\n", __FUNCTION__)); | |
3171 | do { | |
3172 | err = dhd_iovar(dhd, 0, "pfnlbest", NULL, 0, (char *)plbestnet_v1, PNO_BESTNET_LEN, | |
3173 | FALSE); | |
3174 | if (err < 0) { | |
3175 | if (err == BCME_EPERM) { | |
3176 | DHD_ERROR(("we cannot get the batching data " | |
3177 | "during scanning in firmware, try again\n,")); | |
3178 | msleep(500); | |
3179 | continue; | |
3180 | } else { | |
3181 | DHD_ERROR(("%s : failed to execute pfnlbest (err :%d)\n", | |
3182 | __FUNCTION__, err)); | |
3183 | goto exit; | |
3184 | } | |
3185 | } | |
3186 | ||
3187 | if (plbestnet_v1->version == PFN_LBEST_SCAN_RESULT_VERSION_V1) { | |
3188 | fwstatus = plbestnet_v1->status; | |
3189 | fwcount = plbestnet_v1->count; | |
3190 | plnetinfo = &plbestnet_v1->netinfo[0]; | |
3191 | if (fwcount == 0) { | |
3192 | DHD_PNO(("No more batch results\n")); | |
3193 | goto exit; | |
3194 | } | |
3195 | if (fwcount > BESTN_MAX) { | |
3196 | DHD_ERROR(("%s :fwcount %d is greater than BESTN_MAX %d \n", | |
3197 | __FUNCTION__, fwcount, (int)BESTN_MAX)); | |
3198 | /* Process only BESTN_MAX number of results per batch */ | |
3199 | fwcount = BESTN_MAX; | |
3200 | } | |
3201 | for (i = 0; i < fwcount; i++) { | |
3202 | pbestnet_entry = (dhd_pno_bestnet_entry_t *) | |
3203 | MALLOC(dhd->osh, BESTNET_ENTRY_SIZE); | |
3204 | if (pbestnet_entry == NULL) { | |
3205 | err = BCME_NOMEM; | |
3206 | DHD_ERROR(("failed to allocate dhd_pno_bestnet_entry\n")); | |
3207 | goto exit; | |
3208 | } | |
3209 | memset(pbestnet_entry, 0, BESTNET_ENTRY_SIZE); | |
3210 | /* record the current time */ | |
3211 | pbestnet_entry->recorded_time = jiffies; | |
3212 | /* create header for the first entry */ | |
3213 | allocate_header = (i == 0)? TRUE : FALSE; | |
3214 | /* check whether the new generation is started or not */ | |
3215 | if (timestamp && (TIME_DIFF(timestamp, plnetinfo->timestamp) | |
3216 | > TIME_MIN_DIFF)) | |
3217 | allocate_header = TRUE; | |
3218 | timestamp = plnetinfo->timestamp; | |
3219 | if (allocate_header) { | |
3220 | pbestnetheader = (dhd_pno_best_header_t *) | |
3221 | MALLOC(dhd->osh, BEST_HEADER_SIZE); | |
3222 | if (pbestnetheader == NULL) { | |
3223 | err = BCME_NOMEM; | |
3224 | if (pbestnet_entry) | |
3225 | MFREE(dhd->osh, pbestnet_entry, | |
3226 | BESTNET_ENTRY_SIZE); | |
3227 | DHD_ERROR(("failed to allocate" | |
3228 | " dhd_pno_bestnet_entry\n")); | |
3229 | goto exit; | |
3230 | } | |
3231 | /* increase total cnt of bestnet header */ | |
3232 | pscan_results->cnt_header++; | |
3233 | /* need to record the reason to call dhd_pno_get_for_bach */ | |
3234 | if (reason) | |
3235 | pbestnetheader->reason = (ENABLE << reason); | |
3236 | memset(pbestnetheader, 0, BEST_HEADER_SIZE); | |
3237 | /* initialize the head of linked list */ | |
3238 | INIT_LIST_HEAD(&(pbestnetheader->entry_list)); | |
3239 | /* link the pbestnet heaer into existed list */ | |
3240 | if (pscan_results->bestnetheader == NULL) | |
3241 | /* In case of header */ | |
3242 | pscan_results->bestnetheader = pbestnetheader; | |
3243 | else { | |
3244 | dhd_pno_best_header_t *head = | |
3245 | pscan_results->bestnetheader; | |
3246 | pscan_results->bestnetheader = pbestnetheader; | |
3247 | pbestnetheader->next = head; | |
3248 | } | |
3249 | } | |
3250 | pbestnet_entry->channel = plnetinfo->pfnsubnet.channel; | |
3251 | pbestnet_entry->RSSI = plnetinfo->RSSI; | |
3252 | if (plnetinfo->flags & PFN_PARTIAL_SCAN_MASK) { | |
3253 | /* if RSSI is positive value, we assume that | |
3254 | * this scan is aborted by other scan | |
3255 | */ | |
3256 | DHD_PNO(("This scan is aborted\n")); | |
3257 | pbestnetheader->reason = (ENABLE << PNO_STATUS_ABORT); | |
3258 | } | |
3259 | pbestnet_entry->rtt0 = plnetinfo->rtt0; | |
3260 | pbestnet_entry->rtt1 = plnetinfo->rtt1; | |
3261 | pbestnet_entry->timestamp = plnetinfo->timestamp; | |
3262 | if (plnetinfo->pfnsubnet.SSID_len > DOT11_MAX_SSID_LEN) { | |
3263 | DHD_ERROR(("%s: Invalid SSID length" | |
3264 | " %d: trimming it to max\n", | |
3265 | __FUNCTION__, plnetinfo->pfnsubnet.SSID_len)); | |
3266 | plnetinfo->pfnsubnet.SSID_len = DOT11_MAX_SSID_LEN; | |
3267 | } | |
3268 | pbestnet_entry->SSID_len = plnetinfo->pfnsubnet.SSID_len; | |
3269 | memcpy(pbestnet_entry->SSID, plnetinfo->pfnsubnet.SSID, | |
3270 | pbestnet_entry->SSID_len); | |
3271 | memcpy(&pbestnet_entry->BSSID, &plnetinfo->pfnsubnet.BSSID, | |
3272 | ETHER_ADDR_LEN); | |
3273 | /* add the element into list */ | |
3274 | list_add_tail(&pbestnet_entry->list, &pbestnetheader->entry_list); | |
3275 | /* increase best entry count */ | |
3276 | pbestnetheader->tot_cnt++; | |
3277 | pbestnetheader->tot_size += BESTNET_ENTRY_SIZE; | |
3278 | DHD_PNO(("Header %d\n", pscan_results->cnt_header - 1)); | |
3279 | DHD_PNO(("\tSSID : ")); | |
3280 | for (j = 0; j < plnetinfo->pfnsubnet.SSID_len; j++) | |
3281 | DHD_PNO(("%c", plnetinfo->pfnsubnet.SSID[j])); | |
3282 | DHD_PNO(("\n")); | |
3283 | DHD_PNO(("\tBSSID: "MACDBG"\n", | |
3284 | MAC2STRDBG(plnetinfo->pfnsubnet.BSSID.octet))); | |
3285 | DHD_PNO(("\tchannel: %d, RSSI: %d, timestamp: %d ms\n", | |
3286 | plnetinfo->pfnsubnet.channel, | |
3287 | plnetinfo->RSSI, plnetinfo->timestamp)); | |
3288 | DHD_PNO(("\tRTT0 : %d, RTT1: %d\n", plnetinfo->rtt0, | |
3289 | plnetinfo->rtt1)); | |
3290 | plnetinfo++; | |
3291 | } | |
3292 | } else if (plbestnet_v2->version == PFN_LBEST_SCAN_RESULT_VERSION_V2) { | |
3293 | fwstatus = plbestnet_v2->status; | |
3294 | fwcount = plbestnet_v2->count; | |
3295 | plnetinfo_v2 = (wl_pfn_lnet_info_v2_t*)&plbestnet_v2->netinfo[0]; | |
3296 | if (fwcount == 0) { | |
3297 | DHD_PNO(("No more batch results\n")); | |
3298 | goto exit; | |
3299 | } | |
3300 | if (fwcount > BESTN_MAX) { | |
3301 | DHD_ERROR(("%s :fwcount %d is greater than BESTN_MAX %d \n", | |
3302 | __FUNCTION__, fwcount, (int)BESTN_MAX)); | |
3303 | /* Process only BESTN_MAX number of results per batch */ | |
3304 | fwcount = BESTN_MAX; | |
3305 | } | |
3306 | DHD_PNO(("ver %d, status : %d, count %d\n", | |
3307 | plbestnet_v2->version, fwstatus, fwcount)); | |
3308 | ||
3309 | for (i = 0; i < fwcount; i++) { | |
3310 | pbestnet_entry = (dhd_pno_bestnet_entry_t *) | |
3311 | MALLOC(dhd->osh, BESTNET_ENTRY_SIZE); | |
3312 | if (pbestnet_entry == NULL) { | |
3313 | err = BCME_NOMEM; | |
3314 | DHD_ERROR(("failed to allocate dhd_pno_bestnet_entry\n")); | |
3315 | goto exit; | |
3316 | } | |
3317 | memset(pbestnet_entry, 0, BESTNET_ENTRY_SIZE); | |
3318 | /* record the current time */ | |
3319 | pbestnet_entry->recorded_time = jiffies; | |
3320 | /* create header for the first entry */ | |
3321 | allocate_header = (i == 0)? TRUE : FALSE; | |
3322 | /* check whether the new generation is started or not */ | |
3323 | if (timestamp && (TIME_DIFF(timestamp, plnetinfo_v2->timestamp) | |
3324 | > TIME_MIN_DIFF)) | |
3325 | allocate_header = TRUE; | |
3326 | timestamp = plnetinfo_v2->timestamp; | |
3327 | if (allocate_header) { | |
3328 | pbestnetheader = (dhd_pno_best_header_t *) | |
3329 | MALLOC(dhd->osh, BEST_HEADER_SIZE); | |
3330 | if (pbestnetheader == NULL) { | |
3331 | err = BCME_NOMEM; | |
3332 | if (pbestnet_entry) | |
3333 | MFREE(dhd->osh, pbestnet_entry, | |
3334 | BESTNET_ENTRY_SIZE); | |
3335 | DHD_ERROR(("failed to allocate" | |
3336 | " dhd_pno_bestnet_entry\n")); | |
3337 | goto exit; | |
3338 | } | |
3339 | /* increase total cnt of bestnet header */ | |
3340 | pscan_results->cnt_header++; | |
3341 | /* need to record the reason to call dhd_pno_get_for_bach */ | |
3342 | if (reason) | |
3343 | pbestnetheader->reason = (ENABLE << reason); | |
3344 | memset(pbestnetheader, 0, BEST_HEADER_SIZE); | |
3345 | /* initialize the head of linked list */ | |
3346 | INIT_LIST_HEAD(&(pbestnetheader->entry_list)); | |
3347 | /* link the pbestnet heaer into existed list */ | |
3348 | if (pscan_results->bestnetheader == NULL) | |
3349 | /* In case of header */ | |
3350 | pscan_results->bestnetheader = pbestnetheader; | |
3351 | else { | |
3352 | dhd_pno_best_header_t *head = | |
3353 | pscan_results->bestnetheader; | |
3354 | pscan_results->bestnetheader = pbestnetheader; | |
3355 | pbestnetheader->next = head; | |
3356 | } | |
3357 | } | |
3358 | /* fills the best network info */ | |
3359 | pbestnet_entry->channel = plnetinfo_v2->pfnsubnet.channel; | |
3360 | pbestnet_entry->RSSI = plnetinfo_v2->RSSI; | |
3361 | if (plnetinfo_v2->flags & PFN_PARTIAL_SCAN_MASK) { | |
3362 | /* if RSSI is positive value, we assume that | |
3363 | * this scan is aborted by other scan | |
3364 | */ | |
3365 | DHD_PNO(("This scan is aborted\n")); | |
3366 | pbestnetheader->reason = (ENABLE << PNO_STATUS_ABORT); | |
3367 | } | |
3368 | pbestnet_entry->rtt0 = plnetinfo_v2->rtt0; | |
3369 | pbestnet_entry->rtt1 = plnetinfo_v2->rtt1; | |
3370 | pbestnet_entry->timestamp = plnetinfo_v2->timestamp; | |
3371 | if (plnetinfo_v2->pfnsubnet.SSID_len > DOT11_MAX_SSID_LEN) { | |
3372 | DHD_ERROR(("%s: Invalid SSID length" | |
3373 | " %d: trimming it to max\n", | |
3374 | __FUNCTION__, plnetinfo_v2->pfnsubnet.SSID_len)); | |
3375 | plnetinfo_v2->pfnsubnet.SSID_len = DOT11_MAX_SSID_LEN; | |
3376 | } | |
3377 | pbestnet_entry->SSID_len = plnetinfo_v2->pfnsubnet.SSID_len; | |
3378 | memcpy(pbestnet_entry->SSID, plnetinfo_v2->pfnsubnet.u.SSID, | |
3379 | pbestnet_entry->SSID_len); | |
3380 | memcpy(&pbestnet_entry->BSSID, &plnetinfo_v2->pfnsubnet.BSSID, | |
3381 | ETHER_ADDR_LEN); | |
3382 | /* add the element into list */ | |
3383 | list_add_tail(&pbestnet_entry->list, &pbestnetheader->entry_list); | |
3384 | /* increase best entry count */ | |
3385 | pbestnetheader->tot_cnt++; | |
3386 | pbestnetheader->tot_size += BESTNET_ENTRY_SIZE; | |
3387 | DHD_PNO(("Header %d\n", pscan_results->cnt_header - 1)); | |
3388 | DHD_PNO(("\tSSID : ")); | |
3389 | for (j = 0; j < plnetinfo_v2->pfnsubnet.SSID_len; j++) | |
3390 | DHD_PNO(("%c", plnetinfo_v2->pfnsubnet.u.SSID[j])); | |
3391 | DHD_PNO(("\n")); | |
3392 | DHD_PNO(("\tBSSID: "MACDBG"\n", | |
3393 | MAC2STRDBG(plnetinfo_v2->pfnsubnet.BSSID.octet))); | |
3394 | DHD_PNO(("\tchannel: %d, RSSI: %d, timestamp: %d ms\n", | |
3395 | plnetinfo_v2->pfnsubnet.channel, | |
3396 | plnetinfo_v2->RSSI, plnetinfo_v2->timestamp)); | |
3397 | DHD_PNO(("\tRTT0 : %d, RTT1: %d\n", plnetinfo_v2->rtt0, | |
3398 | plnetinfo_v2->rtt1)); | |
3399 | plnetinfo_v2++; | |
3400 | } | |
3401 | } else { | |
3402 | err = BCME_VERSION; | |
3403 | DHD_ERROR(("bestnet fw version %d not supported\n", | |
3404 | plbestnet_v1->version)); | |
3405 | goto exit; | |
3406 | } | |
3407 | } while (fwstatus != PFN_COMPLETE); | |
3408 | ||
3409 | if (pscan_results->cnt_header == 0) { | |
3410 | /* In case that we didn't get any data from the firmware | |
3411 | * Remove the current scan_result list from get_bach.scan_results_list. | |
3412 | */ | |
3413 | DHD_PNO(("NO BATCH DATA from Firmware, Delete current SCAN RESULT LIST\n")); | |
3414 | list_del(&pscan_results->list); | |
3415 | MFREE(dhd->osh, pscan_results, SCAN_RESULTS_SIZE); | |
3416 | _params->params_batch.get_batch.top_node_cnt--; | |
3417 | } else { | |
3418 | /* increase total scan count using current scan count */ | |
3419 | _params->params_batch.get_batch.tot_scan_cnt += pscan_results->cnt_header; | |
3420 | } | |
3421 | ||
3422 | if (buf && bufsize) { | |
3423 | /* This is a first try to get batching results */ | |
3424 | if (!list_empty(&_params->params_batch.get_batch.scan_results_list)) { | |
3425 | /* move the scan_results_list to expired_scan_results_lists */ | |
3426 | GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); | |
3427 | list_for_each_entry_safe(siter, snext, | |
3428 | &_params->params_batch.get_batch.scan_results_list, list) { | |
3429 | GCC_DIAGNOSTIC_POP(); | |
3430 | list_move_tail(&siter->list, | |
3431 | &_params->params_batch.get_batch.expired_scan_results_list); | |
3432 | } | |
3433 | /* reset gloval values after moving to expired list */ | |
3434 | _params->params_batch.get_batch.top_node_cnt = 0; | |
3435 | _params->params_batch.get_batch.expired_tot_scan_cnt = | |
3436 | _params->params_batch.get_batch.tot_scan_cnt; | |
3437 | _params->params_batch.get_batch.tot_scan_cnt = 0; | |
3438 | } | |
3439 | convert_format: | |
3440 | err = _dhd_pno_convert_format(dhd, &_params->params_batch, buf, bufsize); | |
3441 | if (err < 0) { | |
3442 | DHD_ERROR(("failed to convert the data into upper layer format\n")); | |
3443 | goto exit; | |
3444 | } | |
3445 | } | |
3446 | exit: | |
3447 | if (plbestnet_v1) | |
3448 | MFREE(dhd->osh, plbestnet_v1, PNO_BESTNET_LEN); | |
3449 | if (_params) { | |
3450 | _params->params_batch.get_batch.buf = NULL; | |
3451 | _params->params_batch.get_batch.bufsize = 0; | |
3452 | _params->params_batch.get_batch.bytes_written = err; | |
3453 | } | |
3454 | mutex_unlock(&_pno_state->pno_mutex); | |
3455 | exit_no_unlock: | |
3456 | if (waitqueue_active(&_pno_state->get_batch_done.wait)) | |
3457 | complete(&_pno_state->get_batch_done); | |
3458 | return err; | |
3459 | } | |
3460 | ||
3461 | static void | |
3462 | _dhd_pno_get_batch_handler(struct work_struct *work) | |
3463 | { | |
3464 | dhd_pno_status_info_t *_pno_state; | |
3465 | dhd_pub_t *dhd; | |
3466 | struct dhd_pno_batch_params *params_batch; | |
3467 | DHD_PNO(("%s enter\n", __FUNCTION__)); | |
3468 | GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); | |
3469 | _pno_state = container_of(work, struct dhd_pno_status_info, work); | |
3470 | GCC_DIAGNOSTIC_POP(); | |
3471 | ||
3472 | dhd = _pno_state->dhd; | |
3473 | if (dhd == NULL) { | |
3474 | DHD_ERROR(("%s : dhd is NULL\n", __FUNCTION__)); | |
3475 | return; | |
3476 | } | |
3477 | ||
3478 | #ifdef GSCAN_SUPPORT | |
3479 | _dhd_pno_get_gscan_batch_from_fw(dhd); | |
3480 | #endif /* GSCAN_SUPPORT */ | |
3481 | if (_pno_state->pno_mode & DHD_PNO_BATCH_MODE) { | |
3482 | params_batch = &_pno_state->pno_params_arr[INDEX_OF_BATCH_PARAMS].params_batch; | |
3483 | ||
3484 | _dhd_pno_get_for_batch(dhd, params_batch->get_batch.buf, | |
3485 | params_batch->get_batch.bufsize, params_batch->get_batch.reason); | |
3486 | } | |
3487 | } | |
3488 | ||
3489 | int | |
3490 | dhd_pno_get_for_batch(dhd_pub_t *dhd, char *buf, int bufsize, int reason) | |
3491 | { | |
3492 | int err = BCME_OK; | |
3493 | char *pbuf = buf; | |
3494 | dhd_pno_status_info_t *_pno_state; | |
3495 | struct dhd_pno_batch_params *params_batch; | |
3496 | NULL_CHECK(dhd, "dhd is NULL", err); | |
3497 | NULL_CHECK(dhd->pno_state, "pno_state is NULL", err); | |
3498 | if (!dhd_support_sta_mode(dhd)) { | |
3499 | err = BCME_BADOPTION; | |
3500 | goto exit; | |
3501 | } | |
3502 | DHD_PNO(("%s enter\n", __FUNCTION__)); | |
3503 | _pno_state = PNO_GET_PNOSTATE(dhd); | |
3504 | ||
3505 | if (!WLS_SUPPORTED(_pno_state)) { | |
3506 | DHD_ERROR(("%s : wifi location service is not supported\n", __FUNCTION__)); | |
3507 | err = BCME_UNSUPPORTED; | |
3508 | goto exit; | |
3509 | } | |
3510 | params_batch = &_pno_state->pno_params_arr[INDEX_OF_BATCH_PARAMS].params_batch; | |
3511 | #ifdef GSCAN_SUPPORT | |
3512 | if (_pno_state->pno_mode & DHD_PNO_GSCAN_MODE) { | |
3513 | struct dhd_pno_gscan_params *gscan_params; | |
3514 | gscan_params = &_pno_state->pno_params_arr[INDEX_OF_GSCAN_PARAMS].params_gscan; | |
3515 | gscan_params->reason = reason; | |
3516 | err = dhd_retreive_batch_scan_results(dhd); | |
3517 | if (err == BCME_OK) { | |
3518 | wait_event_interruptible_timeout(_pno_state->batch_get_wait, | |
3519 | is_batch_retrieval_complete(gscan_params), | |
3520 | msecs_to_jiffies(GSCAN_BATCH_GET_MAX_WAIT)); | |
3521 | } | |
3522 | } else | |
3523 | #endif // endif | |
3524 | { | |
3525 | if (!(_pno_state->pno_mode & DHD_PNO_BATCH_MODE)) { | |
3526 | DHD_ERROR(("%s: Batching SCAN mode is not enabled\n", __FUNCTION__)); | |
3527 | memset(pbuf, 0, bufsize); | |
3528 | pbuf += snprintf(pbuf, bufsize, "scancount=%d\n", 0); | |
3529 | snprintf(pbuf, bufsize, "%s", RESULTS_END_MARKER); | |
3530 | err = strlen(buf); | |
3531 | goto exit; | |
3532 | } | |
3533 | params_batch->get_batch.buf = buf; | |
3534 | params_batch->get_batch.bufsize = bufsize; | |
3535 | params_batch->get_batch.reason = reason; | |
3536 | params_batch->get_batch.bytes_written = 0; | |
3537 | schedule_work(&_pno_state->work); | |
3538 | wait_for_completion(&_pno_state->get_batch_done); | |
3539 | } | |
3540 | ||
3541 | #ifdef GSCAN_SUPPORT | |
3542 | if (!(_pno_state->pno_mode & DHD_PNO_GSCAN_MODE)) | |
3543 | #endif // endif | |
3544 | err = params_batch->get_batch.bytes_written; | |
3545 | exit: | |
3546 | return err; | |
3547 | } | |
3548 | ||
3549 | int | |
3550 | dhd_pno_stop_for_batch(dhd_pub_t *dhd) | |
3551 | { | |
3552 | int err = BCME_OK; | |
3553 | int mode = 0; | |
3554 | int i = 0; | |
3555 | dhd_pno_status_info_t *_pno_state; | |
3556 | dhd_pno_params_t *_params; | |
3557 | wl_pfn_bssid_t *p_pfn_bssid = NULL; | |
3558 | NULL_CHECK(dhd, "dhd is NULL", err); | |
3559 | NULL_CHECK(dhd->pno_state, "pno_state is NULL", err); | |
3560 | _pno_state = PNO_GET_PNOSTATE(dhd); | |
3561 | DHD_PNO(("%s enter\n", __FUNCTION__)); | |
3562 | if (!dhd_support_sta_mode(dhd)) { | |
3563 | err = BCME_BADOPTION; | |
3564 | goto exit; | |
3565 | } | |
3566 | if (!WLS_SUPPORTED(_pno_state)) { | |
3567 | DHD_ERROR(("%s : wifi location service is not supported\n", | |
3568 | __FUNCTION__)); | |
3569 | err = BCME_UNSUPPORTED; | |
3570 | goto exit; | |
3571 | } | |
3572 | ||
3573 | #ifdef GSCAN_SUPPORT | |
3574 | if (_pno_state->pno_mode & DHD_PNO_GSCAN_MODE) { | |
3575 | DHD_PNO(("Gscan is ongoing, nothing to stop here\n")); | |
3576 | return err; | |
3577 | } | |
3578 | #endif // endif | |
3579 | ||
3580 | if (!(_pno_state->pno_mode & DHD_PNO_BATCH_MODE)) { | |
3581 | DHD_ERROR(("%s : PNO BATCH MODE is not enabled\n", __FUNCTION__)); | |
3582 | goto exit; | |
3583 | } | |
3584 | _pno_state->pno_mode &= ~DHD_PNO_BATCH_MODE; | |
3585 | if (_pno_state->pno_mode & (DHD_PNO_LEGACY_MODE | DHD_PNO_HOTLIST_MODE)) { | |
3586 | mode = _pno_state->pno_mode; | |
3587 | err = dhd_pno_clean(dhd); | |
3588 | if (err < 0) { | |
3589 | DHD_ERROR(("%s : failed to call dhd_pno_clean (err: %d)\n", | |
3590 | __FUNCTION__, err)); | |
3591 | goto exit; | |
3592 | } | |
3593 | ||
3594 | _pno_state->pno_mode = mode; | |
3595 | /* restart Legacy PNO if the Legacy PNO is on */ | |
3596 | if (_pno_state->pno_mode & DHD_PNO_LEGACY_MODE) { | |
3597 | struct dhd_pno_legacy_params *_params_legacy; | |
3598 | _params_legacy = | |
3599 | &(_pno_state->pno_params_arr[INDEX_OF_LEGACY_PARAMS].params_legacy); | |
3600 | err = dhd_pno_set_legacy_pno(dhd, _params_legacy->scan_fr, | |
3601 | _params_legacy->pno_repeat, | |
3602 | _params_legacy->pno_freq_expo_max, | |
3603 | _params_legacy->chan_list, _params_legacy->nchan); | |
3604 | if (err < 0) { | |
3605 | DHD_ERROR(("%s : failed to restart legacy PNO scan(err: %d)\n", | |
3606 | __FUNCTION__, err)); | |
3607 | goto exit; | |
3608 | } | |
3609 | } else if (_pno_state->pno_mode & DHD_PNO_HOTLIST_MODE) { | |
3610 | struct dhd_pno_bssid *iter, *next; | |
3611 | _params = &(_pno_state->pno_params_arr[INDEX_OF_HOTLIST_PARAMS]); | |
3612 | p_pfn_bssid = (wl_pfn_bssid_t *)MALLOCZ(dhd->osh, | |
3613 | sizeof(wl_pfn_bssid_t) * _params->params_hotlist.nbssid); | |
3614 | if (p_pfn_bssid == NULL) { | |
3615 | DHD_ERROR(("%s : failed to allocate wl_pfn_bssid_t array" | |
3616 | " (count: %d)", | |
3617 | __FUNCTION__, _params->params_hotlist.nbssid)); | |
3618 | err = BCME_ERROR; | |
3619 | _pno_state->pno_mode &= ~DHD_PNO_HOTLIST_MODE; | |
3620 | goto exit; | |
3621 | } | |
3622 | i = 0; | |
3623 | /* convert dhd_pno_bssid to wl_pfn_bssid */ | |
3624 | GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); | |
3625 | list_for_each_entry_safe(iter, next, | |
3626 | &_params->params_hotlist.bssid_list, list) { | |
3627 | GCC_DIAGNOSTIC_POP(); | |
3628 | memcpy(&p_pfn_bssid[i].macaddr, &iter->macaddr, ETHER_ADDR_LEN); | |
3629 | p_pfn_bssid[i].flags = iter->flags; | |
3630 | i++; | |
3631 | } | |
3632 | err = dhd_pno_set_for_hotlist(dhd, p_pfn_bssid, &_params->params_hotlist); | |
3633 | if (err < 0) { | |
3634 | _pno_state->pno_mode &= ~DHD_PNO_HOTLIST_MODE; | |
3635 | DHD_ERROR(("%s : failed to restart hotlist scan(err: %d)\n", | |
3636 | __FUNCTION__, err)); | |
3637 | goto exit; | |
3638 | } | |
3639 | } | |
3640 | } else { | |
3641 | err = dhd_pno_clean(dhd); | |
3642 | if (err < 0) { | |
3643 | DHD_ERROR(("%s : failed to call dhd_pno_clean (err: %d)\n", | |
3644 | __FUNCTION__, err)); | |
3645 | goto exit; | |
3646 | } | |
3647 | } | |
3648 | exit: | |
3649 | _params = &_pno_state->pno_params_arr[INDEX_OF_BATCH_PARAMS]; | |
3650 | _dhd_pno_reinitialize_prof(dhd, _params, DHD_PNO_BATCH_MODE); | |
3651 | MFREE(dhd->osh, p_pfn_bssid, | |
3652 | sizeof(wl_pfn_bssid_t) * _params->params_hotlist.nbssid); | |
3653 | return err; | |
3654 | } | |
3655 | ||
3656 | int | |
3657 | dhd_pno_set_for_hotlist(dhd_pub_t *dhd, wl_pfn_bssid_t *p_pfn_bssid, | |
3658 | struct dhd_pno_hotlist_params *hotlist_params) | |
3659 | { | |
3660 | int err = BCME_OK; | |
3661 | int i; | |
3662 | uint16 _chan_list[WL_NUMCHANNELS]; | |
3663 | int rem_nchan = 0; | |
3664 | int tot_nchan = 0; | |
3665 | int mode = 0; | |
3666 | dhd_pno_params_t *_params; | |
3667 | dhd_pno_params_t *_params2; | |
3668 | struct dhd_pno_bssid *_pno_bssid; | |
3669 | dhd_pno_status_info_t *_pno_state; | |
3670 | NULL_CHECK(dhd, "dhd is NULL", err); | |
3671 | NULL_CHECK(dhd->pno_state, "pno_state is NULL", err); | |
3672 | NULL_CHECK(hotlist_params, "hotlist_params is NULL", err); | |
3673 | NULL_CHECK(p_pfn_bssid, "p_pfn_bssid is NULL", err); | |
3674 | _pno_state = PNO_GET_PNOSTATE(dhd); | |
3675 | DHD_PNO(("%s enter\n", __FUNCTION__)); | |
3676 | ||
3677 | if (!dhd_support_sta_mode(dhd)) { | |
3678 | err = BCME_BADOPTION; | |
3679 | goto exit; | |
3680 | } | |
3681 | if (!WLS_SUPPORTED(_pno_state)) { | |
3682 | DHD_ERROR(("%s : wifi location service is not supported\n", __FUNCTION__)); | |
3683 | err = BCME_UNSUPPORTED; | |
3684 | goto exit; | |
3685 | } | |
3686 | _params = &_pno_state->pno_params_arr[INDEX_OF_HOTLIST_PARAMS]; | |
3687 | if (!(_pno_state->pno_mode & DHD_PNO_HOTLIST_MODE)) { | |
3688 | _pno_state->pno_mode |= DHD_PNO_HOTLIST_MODE; | |
3689 | err = _dhd_pno_reinitialize_prof(dhd, _params, DHD_PNO_HOTLIST_MODE); | |
3690 | if (err < 0) { | |
3691 | DHD_ERROR(("%s : failed to call _dhd_pno_reinitialize_prof\n", | |
3692 | __FUNCTION__)); | |
3693 | goto exit; | |
3694 | } | |
3695 | } | |
3696 | _params->params_batch.nchan = hotlist_params->nchan; | |
3697 | _params->params_batch.scan_fr = hotlist_params->scan_fr; | |
3698 | if (hotlist_params->nchan) | |
3699 | memcpy(_params->params_hotlist.chan_list, hotlist_params->chan_list, | |
3700 | sizeof(_params->params_hotlist.chan_list)); | |
3701 | memset(_chan_list, 0, sizeof(_chan_list)); | |
3702 | ||
3703 | rem_nchan = ARRAYSIZE(hotlist_params->chan_list) - hotlist_params->nchan; | |
3704 | if (hotlist_params->band == WLC_BAND_2G || hotlist_params->band == WLC_BAND_5G) { | |
3705 | /* get a valid channel list based on band B or A */ | |
3706 | err = _dhd_pno_get_channels(dhd, | |
3707 | &_params->params_hotlist.chan_list[hotlist_params->nchan], | |
3708 | &rem_nchan, hotlist_params->band, FALSE); | |
3709 | if (err < 0) { | |
3710 | DHD_ERROR(("%s: failed to get valid channel list(band : %d)\n", | |
3711 | __FUNCTION__, hotlist_params->band)); | |
3712 | goto exit; | |
3713 | } | |
3714 | /* now we need to update nchan because rem_chan has valid channel count */ | |
3715 | _params->params_hotlist.nchan += rem_nchan; | |
3716 | /* need to sort channel list */ | |
3717 | sort(_params->params_hotlist.chan_list, _params->params_hotlist.nchan, | |
3718 | sizeof(_params->params_hotlist.chan_list[0]), _dhd_pno_cmpfunc, NULL); | |
3719 | } | |
3720 | #ifdef PNO_DEBUG | |
3721 | { | |
3722 | int i; | |
3723 | DHD_PNO(("Channel list : ")); | |
3724 | for (i = 0; i < _params->params_batch.nchan; i++) { | |
3725 | DHD_PNO(("%d ", _params->params_batch.chan_list[i])); | |
3726 | } | |
3727 | DHD_PNO(("\n")); | |
3728 | } | |
3729 | #endif // endif | |
3730 | if (_params->params_hotlist.nchan) { | |
3731 | /* copy the channel list into local array */ | |
3732 | memcpy(_chan_list, _params->params_hotlist.chan_list, | |
3733 | sizeof(_chan_list)); | |
3734 | tot_nchan = _params->params_hotlist.nchan; | |
3735 | } | |
3736 | if (_pno_state->pno_mode & DHD_PNO_LEGACY_MODE) { | |
3737 | DHD_PNO(("PNO SSID is on progress in firmware\n")); | |
3738 | /* store current pno_mode before disabling pno */ | |
3739 | mode = _pno_state->pno_mode; | |
3740 | err = _dhd_pno_enable(dhd, PNO_OFF); | |
3741 | if (err < 0) { | |
3742 | DHD_ERROR(("%s : failed to disable PNO\n", __FUNCTION__)); | |
3743 | goto exit; | |
3744 | } | |
3745 | /* restore the previous mode */ | |
3746 | _pno_state->pno_mode = mode; | |
3747 | /* Use the superset for channelist between two mode */ | |
3748 | _params2 = &(_pno_state->pno_params_arr[INDEX_OF_LEGACY_PARAMS]); | |
3749 | if (_params2->params_legacy.nchan > 0 && | |
3750 | _params->params_hotlist.nchan > 0) { | |
3751 | err = _dhd_pno_chan_merge(_chan_list, &tot_nchan, | |
3752 | &_params2->params_legacy.chan_list[0], | |
3753 | _params2->params_legacy.nchan, | |
3754 | &_params->params_hotlist.chan_list[0], | |
3755 | _params->params_hotlist.nchan); | |
3756 | if (err < 0) { | |
3757 | DHD_ERROR(("%s : failed to merge channel list" | |
3758 | "between legacy and hotlist\n", | |
3759 | __FUNCTION__)); | |
3760 | goto exit; | |
3761 | } | |
3762 | } | |
3763 | ||
3764 | } | |
3765 | ||
3766 | INIT_LIST_HEAD(&(_params->params_hotlist.bssid_list)); | |
3767 | ||
3768 | err = _dhd_pno_add_bssid(dhd, p_pfn_bssid, hotlist_params->nbssid); | |
3769 | if (err < 0) { | |
3770 | DHD_ERROR(("%s : failed to call _dhd_pno_add_bssid(err :%d)\n", | |
3771 | __FUNCTION__, err)); | |
3772 | goto exit; | |
3773 | } | |
3774 | if ((err = _dhd_pno_set(dhd, _params, DHD_PNO_HOTLIST_MODE)) < 0) { | |
3775 | DHD_ERROR(("%s : failed to set call pno_set (err %d) in firmware\n", | |
3776 | __FUNCTION__, err)); | |
3777 | goto exit; | |
3778 | } | |
3779 | if (tot_nchan > 0) { | |
3780 | if ((err = _dhd_pno_cfg(dhd, _chan_list, tot_nchan)) < 0) { | |
3781 | DHD_ERROR(("%s : failed to set call pno_cfg (err %d) in firmware\n", | |
3782 | __FUNCTION__, err)); | |
3783 | goto exit; | |
3784 | } | |
3785 | } | |
3786 | for (i = 0; i < hotlist_params->nbssid; i++) { | |
3787 | _pno_bssid = (struct dhd_pno_bssid *)MALLOCZ(dhd->osh, | |
3788 | sizeof(struct dhd_pno_bssid)); | |
3789 | NULL_CHECK(_pno_bssid, "_pfn_bssid is NULL", err); | |
3790 | memcpy(&_pno_bssid->macaddr, &p_pfn_bssid[i].macaddr, ETHER_ADDR_LEN); | |
3791 | _pno_bssid->flags = p_pfn_bssid[i].flags; | |
3792 | list_add_tail(&_pno_bssid->list, &_params->params_hotlist.bssid_list); | |
3793 | } | |
3794 | _params->params_hotlist.nbssid = hotlist_params->nbssid; | |
3795 | if (_pno_state->pno_status == DHD_PNO_DISABLED) { | |
3796 | if ((err = _dhd_pno_enable(dhd, PNO_ON)) < 0) | |
3797 | DHD_ERROR(("%s : failed to enable PNO\n", __FUNCTION__)); | |
3798 | } | |
3799 | exit: | |
3800 | /* clear mode in case of error */ | |
3801 | if (err < 0) | |
3802 | _pno_state->pno_mode &= ~DHD_PNO_HOTLIST_MODE; | |
3803 | return err; | |
3804 | } | |
3805 | ||
3806 | int | |
3807 | dhd_pno_stop_for_hotlist(dhd_pub_t *dhd) | |
3808 | { | |
3809 | int err = BCME_OK; | |
3810 | uint32 mode = 0; | |
3811 | dhd_pno_status_info_t *_pno_state; | |
3812 | dhd_pno_params_t *_params; | |
3813 | NULL_CHECK(dhd, "dhd is NULL", err); | |
3814 | NULL_CHECK(dhd->pno_state, "pno_state is NULL", err); | |
3815 | _pno_state = PNO_GET_PNOSTATE(dhd); | |
3816 | ||
3817 | if (!WLS_SUPPORTED(_pno_state)) { | |
3818 | DHD_ERROR(("%s : wifi location service is not supported\n", | |
3819 | __FUNCTION__)); | |
3820 | err = BCME_UNSUPPORTED; | |
3821 | goto exit; | |
3822 | } | |
3823 | ||
3824 | if (!(_pno_state->pno_mode & DHD_PNO_HOTLIST_MODE)) { | |
3825 | DHD_ERROR(("%s : Hotlist MODE is not enabled\n", | |
3826 | __FUNCTION__)); | |
3827 | goto exit; | |
3828 | } | |
3829 | _pno_state->pno_mode &= ~DHD_PNO_BATCH_MODE; | |
3830 | ||
3831 | if (_pno_state->pno_mode & (DHD_PNO_LEGACY_MODE | DHD_PNO_BATCH_MODE)) { | |
3832 | /* retrieve the batching data from firmware into host */ | |
3833 | dhd_pno_get_for_batch(dhd, NULL, 0, PNO_STATUS_DISABLE); | |
3834 | /* save current pno_mode before calling dhd_pno_clean */ | |
3835 | mode = _pno_state->pno_mode; | |
3836 | err = dhd_pno_clean(dhd); | |
3837 | if (err < 0) { | |
3838 | DHD_ERROR(("%s : failed to call dhd_pno_clean (err: %d)\n", | |
3839 | __FUNCTION__, err)); | |
3840 | goto exit; | |
3841 | } | |
3842 | /* restore previos pno mode */ | |
3843 | _pno_state->pno_mode = mode; | |
3844 | if (_pno_state->pno_mode & DHD_PNO_LEGACY_MODE) { | |
3845 | /* restart Legacy PNO Scan */ | |
3846 | struct dhd_pno_legacy_params *_params_legacy; | |
3847 | _params_legacy = | |
3848 | &(_pno_state->pno_params_arr[INDEX_OF_LEGACY_PARAMS].params_legacy); | |
3849 | err = dhd_pno_set_legacy_pno(dhd, _params_legacy->scan_fr, | |
3850 | _params_legacy->pno_repeat, _params_legacy->pno_freq_expo_max, | |
3851 | _params_legacy->chan_list, _params_legacy->nchan); | |
3852 | if (err < 0) { | |
3853 | DHD_ERROR(("%s : failed to restart legacy PNO scan(err: %d)\n", | |
3854 | __FUNCTION__, err)); | |
3855 | goto exit; | |
3856 | } | |
3857 | } else if (_pno_state->pno_mode & DHD_PNO_BATCH_MODE) { | |
3858 | /* restart Batching Scan */ | |
3859 | _params = &(_pno_state->pno_params_arr[INDEX_OF_BATCH_PARAMS]); | |
3860 | /* restart BATCH SCAN */ | |
3861 | err = dhd_pno_set_for_batch(dhd, &_params->params_batch); | |
3862 | if (err < 0) { | |
3863 | _pno_state->pno_mode &= ~DHD_PNO_BATCH_MODE; | |
3864 | DHD_ERROR(("%s : failed to restart batch scan(err: %d)\n", | |
3865 | __FUNCTION__, err)); | |
3866 | goto exit; | |
3867 | } | |
3868 | } | |
3869 | } else { | |
3870 | err = dhd_pno_clean(dhd); | |
3871 | if (err < 0) { | |
3872 | DHD_ERROR(("%s : failed to call dhd_pno_clean (err: %d)\n", | |
3873 | __FUNCTION__, err)); | |
3874 | goto exit; | |
3875 | } | |
3876 | } | |
3877 | exit: | |
3878 | return err; | |
3879 | } | |
3880 | ||
3881 | #ifdef GSCAN_SUPPORT | |
3882 | int | |
3883 | dhd_retreive_batch_scan_results(dhd_pub_t *dhd) | |
3884 | { | |
3885 | int err = BCME_OK; | |
3886 | dhd_pno_status_info_t *_pno_state; | |
3887 | dhd_pno_params_t *_params; | |
3888 | struct dhd_pno_batch_params *params_batch; | |
3889 | ||
3890 | NULL_CHECK(dhd, "dhd is NULL", err); | |
3891 | NULL_CHECK(dhd->pno_state, "pno_state is NULL", err); | |
3892 | _pno_state = PNO_GET_PNOSTATE(dhd); | |
3893 | _params = &_pno_state->pno_params_arr[INDEX_OF_GSCAN_PARAMS]; | |
3894 | ||
3895 | params_batch = &_pno_state->pno_params_arr[INDEX_OF_BATCH_PARAMS].params_batch; | |
3896 | if (_params->params_gscan.get_batch_flag == GSCAN_BATCH_RETRIEVAL_COMPLETE) { | |
3897 | DHD_PNO(("Retreive batch results\n")); | |
3898 | params_batch->get_batch.buf = NULL; | |
3899 | params_batch->get_batch.bufsize = 0; | |
3900 | params_batch->get_batch.reason = PNO_STATUS_EVENT; | |
3901 | _params->params_gscan.get_batch_flag = GSCAN_BATCH_RETRIEVAL_IN_PROGRESS; | |
3902 | smp_wmb(); | |
3903 | schedule_work(&_pno_state->work); | |
3904 | } else { | |
3905 | DHD_PNO(("%s : WLC_E_PFN_BEST_BATCHING retrieval" | |
3906 | "already in progress, will skip\n", __FUNCTION__)); | |
3907 | err = BCME_ERROR; | |
3908 | } | |
3909 | ||
3910 | return err; | |
3911 | } | |
3912 | ||
3913 | void | |
3914 | dhd_gscan_hotlist_cache_cleanup(dhd_pub_t *dhd, hotlist_type_t type) | |
3915 | { | |
3916 | dhd_pno_status_info_t *_pno_state = PNO_GET_PNOSTATE(dhd); | |
3917 | struct dhd_pno_gscan_params *gscan_params; | |
3918 | gscan_results_cache_t *iter, *tmp; | |
3919 | ||
3920 | if (!_pno_state) { | |
3921 | return; | |
3922 | } | |
3923 | gscan_params = &(_pno_state->pno_params_arr[INDEX_OF_GSCAN_PARAMS].params_gscan); | |
3924 | ||
3925 | if (type == HOTLIST_FOUND) { | |
3926 | iter = gscan_params->gscan_hotlist_found; | |
3927 | gscan_params->gscan_hotlist_found = NULL; | |
3928 | } else { | |
3929 | iter = gscan_params->gscan_hotlist_lost; | |
3930 | gscan_params->gscan_hotlist_lost = NULL; | |
3931 | } | |
3932 | ||
3933 | while (iter) { | |
3934 | tmp = iter->next; | |
3935 | MFREE(dhd->osh, iter, | |
3936 | ((iter->tot_count - 1) * sizeof(wifi_gscan_result_t)) | |
3937 | + sizeof(gscan_results_cache_t)); | |
3938 | iter = tmp; | |
3939 | } | |
3940 | ||
3941 | return; | |
3942 | } | |
3943 | ||
3944 | void * | |
3945 | dhd_process_full_gscan_result(dhd_pub_t *dhd, const void *data, uint32 len, int *size) | |
3946 | { | |
3947 | wl_bss_info_t *bi = NULL; | |
3948 | wl_gscan_result_t *gscan_result; | |
3949 | wifi_gscan_full_result_t *result = NULL; | |
3950 | u32 bi_length = 0; | |
3951 | uint8 channel; | |
3952 | uint32 mem_needed; | |
3953 | struct osl_timespec ts; | |
3954 | u32 bi_ie_length = 0; | |
3955 | u32 bi_ie_offset = 0; | |
3956 | ||
3957 | *size = 0; | |
3958 | GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); | |
3959 | gscan_result = (wl_gscan_result_t *)data; | |
3960 | GCC_DIAGNOSTIC_POP(); | |
3961 | if (!gscan_result) { | |
3962 | DHD_ERROR(("Invalid gscan result (NULL pointer)\n")); | |
3963 | goto exit; | |
3964 | } | |
3965 | ||
3966 | if ((len < sizeof(*gscan_result)) || | |
3967 | (len < dtoh32(gscan_result->buflen)) || | |
3968 | (dtoh32(gscan_result->buflen) > | |
3969 | (sizeof(*gscan_result) + WL_SCAN_IE_LEN_MAX))) { | |
3970 | DHD_ERROR(("%s: invalid gscan buflen:%u\n", __FUNCTION__, | |
3971 | dtoh32(gscan_result->buflen))); | |
3972 | goto exit; | |
3973 | } | |
3974 | ||
3975 | bi = &gscan_result->bss_info[0].info; | |
3976 | bi_length = dtoh32(bi->length); | |
3977 | if (bi_length != (dtoh32(gscan_result->buflen) - | |
3978 | WL_GSCAN_RESULTS_FIXED_SIZE - WL_GSCAN_INFO_FIXED_FIELD_SIZE)) { | |
3979 | DHD_ERROR(("Invalid bss_info length %d: ignoring\n", bi_length)); | |
3980 | goto exit; | |
3981 | } | |
3982 | bi_ie_offset = dtoh32(bi->ie_offset); | |
3983 | bi_ie_length = dtoh32(bi->ie_length); | |
3984 | if ((bi_ie_offset + bi_ie_length) > bi_length) { | |
3985 | DHD_ERROR(("%s: Invalid ie_length:%u or ie_offset:%u\n", | |
3986 | __FUNCTION__, bi_ie_length, bi_ie_offset)); | |
3987 | goto exit; | |
3988 | } | |
3989 | if (bi->SSID_len > DOT11_MAX_SSID_LEN) { | |
3990 | DHD_ERROR(("%s: Invalid SSID length:%u\n", __FUNCTION__, bi->SSID_len)); | |
3991 | goto exit; | |
3992 | } | |
3993 | ||
3994 | mem_needed = OFFSETOF(wifi_gscan_full_result_t, ie_data) + bi->ie_length; | |
3995 | result = (wifi_gscan_full_result_t *)MALLOC(dhd->osh, mem_needed); | |
3996 | if (!result) { | |
3997 | DHD_ERROR(("%s Cannot malloc scan result buffer %d bytes\n", | |
3998 | __FUNCTION__, mem_needed)); | |
3999 | goto exit; | |
4000 | } | |
4001 | ||
4002 | result->scan_ch_bucket = gscan_result->scan_ch_bucket; | |
4003 | memcpy(result->fixed.ssid, bi->SSID, bi->SSID_len); | |
4004 | result->fixed.ssid[bi->SSID_len] = '\0'; | |
4005 | channel = wf_chspec_ctlchan(bi->chanspec); | |
4006 | result->fixed.channel = wf_channel2mhz(channel, | |
4007 | (channel <= CH_MAX_2G_CHANNEL? | |
4008 | WF_CHAN_FACTOR_2_4_G : WF_CHAN_FACTOR_5_G)); | |
4009 | result->fixed.rssi = (int32) bi->RSSI; | |
4010 | result->fixed.rtt = 0; | |
4011 | result->fixed.rtt_sd = 0; | |
4012 | osl_get_monotonic_boottime(&ts); | |
4013 | result->fixed.ts = (uint64) TIMESPEC_TO_US(ts); | |
4014 | result->fixed.beacon_period = dtoh16(bi->beacon_period); | |
4015 | result->fixed.capability = dtoh16(bi->capability); | |
4016 | result->ie_length = bi_ie_length; | |
4017 | memcpy(&result->fixed.macaddr, &bi->BSSID, ETHER_ADDR_LEN); | |
4018 | memcpy(result->ie_data, ((uint8 *)bi + bi_ie_offset), bi_ie_length); | |
4019 | *size = mem_needed; | |
4020 | exit: | |
4021 | return result; | |
4022 | } | |
4023 | ||
4024 | void * | |
4025 | dhd_pno_process_epno_result(dhd_pub_t *dhd, const void *data, uint32 event, int *size) | |
4026 | { | |
4027 | dhd_epno_results_t *results = NULL; | |
4028 | dhd_pno_status_info_t *_pno_state = PNO_GET_PNOSTATE(dhd); | |
4029 | struct dhd_pno_gscan_params *gscan_params; | |
4030 | uint32 count, mem_needed = 0, i; | |
4031 | uint8 ssid[DOT11_MAX_SSID_LEN + 1]; | |
4032 | struct ether_addr *bssid; | |
4033 | ||
4034 | *size = 0; | |
4035 | if (!_pno_state) | |
4036 | return NULL; | |
4037 | gscan_params = &(_pno_state->pno_params_arr[INDEX_OF_GSCAN_PARAMS].params_gscan); | |
4038 | ||
4039 | if (event == WLC_E_PFN_NET_FOUND || event == WLC_E_PFN_NET_LOST) { | |
4040 | wl_pfn_scanresults_v1_t *pfn_result = (wl_pfn_scanresults_v1_t *)data; | |
4041 | wl_pfn_scanresults_v2_t *pfn_result_v2 = (wl_pfn_scanresults_v2_t *)data; | |
4042 | wl_pfn_net_info_v1_t *net; | |
4043 | wl_pfn_net_info_v2_t *net_v2; | |
4044 | ||
4045 | if (pfn_result->version == PFN_SCANRESULT_VERSION_V1) { | |
4046 | if ((pfn_result->count == 0) || (pfn_result->count > EVENT_MAX_NETCNT_V1)) { | |
4047 | DHD_ERROR(("%s event %d: wrong pfn v1 results count %d\n", | |
4048 | __FUNCTION__, event, pfn_result->count)); | |
4049 | return NULL; | |
4050 | } | |
4051 | count = pfn_result->count; | |
4052 | mem_needed = sizeof(dhd_epno_results_t) * count; | |
4053 | results = (dhd_epno_results_t *)MALLOC(dhd->osh, mem_needed); | |
4054 | if (!results) { | |
4055 | DHD_ERROR(("%s: Can't malloc %d bytes for results\n", __FUNCTION__, | |
4056 | mem_needed)); | |
4057 | return NULL; | |
4058 | } | |
4059 | for (i = 0; i < count; i++) { | |
4060 | net = &pfn_result->netinfo[i]; | |
4061 | results[i].rssi = net->RSSI; | |
4062 | results[i].channel = wf_channel2mhz(net->pfnsubnet.channel, | |
4063 | (net->pfnsubnet.channel <= CH_MAX_2G_CHANNEL ? | |
4064 | WF_CHAN_FACTOR_2_4_G : WF_CHAN_FACTOR_5_G)); | |
4065 | results[i].flags = (event == WLC_E_PFN_NET_FOUND) ? | |
4066 | WL_PFN_SSID_EXT_FOUND: WL_PFN_SSID_EXT_LOST; | |
4067 | results[i].ssid_len = min(net->pfnsubnet.SSID_len, | |
4068 | (uint8)DOT11_MAX_SSID_LEN); | |
4069 | bssid = &results[i].bssid; | |
4070 | memcpy(bssid, &net->pfnsubnet.BSSID, ETHER_ADDR_LEN); | |
4071 | if (!net->pfnsubnet.SSID_len) { | |
4072 | DHD_ERROR(("%s: Gscan results indexing is not" | |
4073 | " supported in version 1 \n", __FUNCTION__)); | |
4074 | MFREE(dhd->osh, results, mem_needed); | |
4075 | return NULL; | |
4076 | } else { | |
4077 | memcpy(results[i].ssid, net->pfnsubnet.SSID, | |
4078 | results[i].ssid_len); | |
4079 | } | |
4080 | memcpy(ssid, results[i].ssid, results[i].ssid_len); | |
4081 | ssid[results[i].ssid_len] = '\0'; | |
4082 | DHD_PNO(("ssid - %s bssid "MACDBG" ch %d rssi %d flags %d\n", | |
4083 | ssid, MAC2STRDBG(bssid->octet), results[i].channel, | |
4084 | results[i].rssi, results[i].flags)); | |
4085 | } | |
4086 | } else if (pfn_result_v2->version == PFN_SCANRESULT_VERSION_V2) { | |
4087 | if ((pfn_result->count == 0) || (pfn_result->count > EVENT_MAX_NETCNT_V2)) { | |
4088 | DHD_ERROR(("%s event %d: wrong pfn v2 results count %d\n", | |
4089 | __FUNCTION__, event, pfn_result->count)); | |
4090 | return NULL; | |
4091 | } | |
4092 | count = pfn_result_v2->count; | |
4093 | mem_needed = sizeof(dhd_epno_results_t) * count; | |
4094 | results = (dhd_epno_results_t *)MALLOC(dhd->osh, mem_needed); | |
4095 | if (!results) { | |
4096 | DHD_ERROR(("%s: Can't malloc %d bytes for results\n", __FUNCTION__, | |
4097 | mem_needed)); | |
4098 | return NULL; | |
4099 | } | |
4100 | for (i = 0; i < count; i++) { | |
4101 | net_v2 = &pfn_result_v2->netinfo[i]; | |
4102 | results[i].rssi = net_v2->RSSI; | |
4103 | results[i].channel = wf_channel2mhz(net_v2->pfnsubnet.channel, | |
4104 | (net_v2->pfnsubnet.channel <= CH_MAX_2G_CHANNEL ? | |
4105 | WF_CHAN_FACTOR_2_4_G : WF_CHAN_FACTOR_5_G)); | |
4106 | results[i].flags = (event == WLC_E_PFN_NET_FOUND) ? | |
4107 | WL_PFN_SSID_EXT_FOUND: WL_PFN_SSID_EXT_LOST; | |
4108 | results[i].ssid_len = min(net_v2->pfnsubnet.SSID_len, | |
4109 | (uint8)DOT11_MAX_SSID_LEN); | |
4110 | bssid = &results[i].bssid; | |
4111 | memcpy(bssid, &net_v2->pfnsubnet.BSSID, ETHER_ADDR_LEN); | |
4112 | if (!net_v2->pfnsubnet.SSID_len) { | |
4113 | dhd_pno_idx_to_ssid(gscan_params, &results[i], | |
4114 | net_v2->pfnsubnet.u.index); | |
4115 | } else { | |
4116 | memcpy(results[i].ssid, net_v2->pfnsubnet.u.SSID, | |
4117 | results[i].ssid_len); | |
4118 | } | |
4119 | memcpy(ssid, results[i].ssid, results[i].ssid_len); | |
4120 | ssid[results[i].ssid_len] = '\0'; | |
4121 | DHD_PNO(("ssid - %s bssid "MACDBG" ch %d rssi %d flags %d\n", | |
4122 | ssid, MAC2STRDBG(bssid->octet), results[i].channel, | |
4123 | results[i].rssi, results[i].flags)); | |
4124 | } | |
4125 | } else { | |
4126 | DHD_ERROR(("%s event %d: Incorrect version %d , not supported\n", | |
4127 | __FUNCTION__, event, pfn_result->version)); | |
4128 | return NULL; | |
4129 | } | |
4130 | } | |
4131 | *size = mem_needed; | |
4132 | return results; | |
4133 | } | |
4134 | ||
4135 | void * | |
4136 | dhd_handle_hotlist_scan_evt(dhd_pub_t *dhd, const void *event_data, | |
4137 | int *send_evt_bytes, hotlist_type_t type, u32 *buf_len) | |
4138 | { | |
4139 | void *ptr = NULL; | |
4140 | dhd_pno_status_info_t *_pno_state = PNO_GET_PNOSTATE(dhd); | |
4141 | struct dhd_pno_gscan_params *gscan_params; | |
4142 | wl_pfn_scanresults_v1_t *results_v1 = (wl_pfn_scanresults_v1_t *)event_data; | |
4143 | wl_pfn_scanresults_v2_t *results_v2 = (wl_pfn_scanresults_v2_t *)event_data; | |
4144 | wifi_gscan_result_t *hotlist_found_array; | |
4145 | wl_pfn_net_info_v1_t *pnetinfo; | |
4146 | wl_pfn_net_info_v2_t *pnetinfo_v2; | |
4147 | gscan_results_cache_t *gscan_hotlist_cache; | |
4148 | u32 malloc_size = 0, i, total = 0; | |
4149 | struct osl_timespec tm_spec; | |
4150 | uint16 fwstatus; | |
4151 | uint16 fwcount; | |
4152 | ||
4153 | /* Static asserts in _dhd_pno_get_for_batch() above guarantee the v1 and v2 | |
4154 | * net_info and subnet_info structures are compatible in size and SSID offset, | |
4155 | * allowing v1 to be safely used in the code below except for lscanresults | |
4156 | * fields themselves (status, count, offset to netinfo). | |
4157 | */ | |
4158 | ||
4159 | *buf_len = 0; | |
4160 | if (results_v1->version == PFN_SCANRESULTS_VERSION_V1) { | |
4161 | fwstatus = results_v1->status; | |
4162 | fwcount = results_v1->count; | |
4163 | pnetinfo = &results_v1->netinfo[0]; | |
4164 | ||
4165 | gscan_params = &(_pno_state->pno_params_arr[INDEX_OF_GSCAN_PARAMS].params_gscan); | |
4166 | ||
4167 | if (!fwcount || (fwcount > EVENT_MAX_NETCNT_V1)) { | |
4168 | DHD_ERROR(("%s: wrong v1 fwcount:%d\n", __FUNCTION__, fwcount)); | |
4169 | *send_evt_bytes = 0; | |
4170 | return ptr; | |
4171 | } | |
4172 | ||
4173 | osl_get_monotonic_boottime(&tm_spec); | |
4174 | malloc_size = sizeof(gscan_results_cache_t) + | |
4175 | ((fwcount - 1) * sizeof(wifi_gscan_result_t)); | |
4176 | gscan_hotlist_cache = (gscan_results_cache_t *)MALLOC(dhd->osh, malloc_size); | |
4177 | if (!gscan_hotlist_cache) { | |
4178 | DHD_ERROR(("%s Cannot Malloc %d bytes!!\n", __FUNCTION__, malloc_size)); | |
4179 | *send_evt_bytes = 0; | |
4180 | return ptr; | |
4181 | } | |
4182 | ||
4183 | *buf_len = malloc_size; | |
4184 | if (type == HOTLIST_FOUND) { | |
4185 | gscan_hotlist_cache->next = gscan_params->gscan_hotlist_found; | |
4186 | gscan_params->gscan_hotlist_found = gscan_hotlist_cache; | |
4187 | DHD_PNO(("%s enter, FOUND results count %d\n", __FUNCTION__, fwcount)); | |
4188 | } else { | |
4189 | gscan_hotlist_cache->next = gscan_params->gscan_hotlist_lost; | |
4190 | gscan_params->gscan_hotlist_lost = gscan_hotlist_cache; | |
4191 | DHD_PNO(("%s enter, LOST results count %d\n", __FUNCTION__, fwcount)); | |
4192 | } | |
4193 | ||
4194 | gscan_hotlist_cache->tot_count = fwcount; | |
4195 | gscan_hotlist_cache->tot_consumed = 0; | |
4196 | ||
4197 | for (i = 0; i < fwcount; i++, pnetinfo++) { | |
4198 | hotlist_found_array = &gscan_hotlist_cache->results[i]; | |
4199 | memset(hotlist_found_array, 0, sizeof(wifi_gscan_result_t)); | |
4200 | hotlist_found_array->channel = wf_channel2mhz(pnetinfo->pfnsubnet.channel, | |
4201 | (pnetinfo->pfnsubnet.channel <= CH_MAX_2G_CHANNEL? | |
4202 | WF_CHAN_FACTOR_2_4_G : WF_CHAN_FACTOR_5_G)); | |
4203 | hotlist_found_array->rssi = (int32) pnetinfo->RSSI; | |
4204 | ||
4205 | hotlist_found_array->ts = | |
4206 | convert_fw_rel_time_to_systime(&tm_spec, | |
4207 | (pnetinfo->timestamp * 1000)); | |
4208 | if (pnetinfo->pfnsubnet.SSID_len > DOT11_MAX_SSID_LEN) { | |
4209 | DHD_ERROR(("Invalid SSID length %d: trimming it to max\n", | |
4210 | pnetinfo->pfnsubnet.SSID_len)); | |
4211 | pnetinfo->pfnsubnet.SSID_len = DOT11_MAX_SSID_LEN; | |
4212 | } | |
4213 | memcpy(hotlist_found_array->ssid, pnetinfo->pfnsubnet.SSID, | |
4214 | pnetinfo->pfnsubnet.SSID_len); | |
4215 | hotlist_found_array->ssid[pnetinfo->pfnsubnet.SSID_len] = '\0'; | |
4216 | ||
4217 | memcpy(&hotlist_found_array->macaddr, &pnetinfo->pfnsubnet.BSSID, | |
4218 | ETHER_ADDR_LEN); | |
4219 | DHD_PNO(("\t%s "MACDBG" rssi %d\n", | |
4220 | hotlist_found_array->ssid, | |
4221 | MAC2STRDBG(hotlist_found_array->macaddr.octet), | |
4222 | hotlist_found_array->rssi)); | |
4223 | } | |
4224 | } else if (results_v2->version == PFN_SCANRESULTS_VERSION_V2) { | |
4225 | fwstatus = results_v2->status; | |
4226 | fwcount = results_v2->count; | |
4227 | pnetinfo_v2 = (wl_pfn_net_info_v2_t*)&results_v2->netinfo[0]; | |
4228 | ||
4229 | gscan_params = &(_pno_state->pno_params_arr[INDEX_OF_GSCAN_PARAMS].params_gscan); | |
4230 | ||
4231 | if (!fwcount || (fwcount > EVENT_MAX_NETCNT_V2)) { | |
4232 | DHD_ERROR(("%s: wrong v2 fwcount:%d\n", __FUNCTION__, fwcount)); | |
4233 | *send_evt_bytes = 0; | |
4234 | return ptr; | |
4235 | } | |
4236 | ||
4237 | osl_get_monotonic_boottime(&tm_spec); | |
4238 | malloc_size = sizeof(gscan_results_cache_t) + | |
4239 | ((fwcount - 1) * sizeof(wifi_gscan_result_t)); | |
4240 | gscan_hotlist_cache = | |
4241 | (gscan_results_cache_t *)MALLOC(dhd->osh, malloc_size); | |
4242 | if (!gscan_hotlist_cache) { | |
4243 | DHD_ERROR(("%s Cannot Malloc %d bytes!!\n", __FUNCTION__, malloc_size)); | |
4244 | *send_evt_bytes = 0; | |
4245 | return ptr; | |
4246 | } | |
4247 | *buf_len = malloc_size; | |
4248 | if (type == HOTLIST_FOUND) { | |
4249 | gscan_hotlist_cache->next = gscan_params->gscan_hotlist_found; | |
4250 | gscan_params->gscan_hotlist_found = gscan_hotlist_cache; | |
4251 | DHD_PNO(("%s enter, FOUND results count %d\n", __FUNCTION__, fwcount)); | |
4252 | } else { | |
4253 | gscan_hotlist_cache->next = gscan_params->gscan_hotlist_lost; | |
4254 | gscan_params->gscan_hotlist_lost = gscan_hotlist_cache; | |
4255 | DHD_PNO(("%s enter, LOST results count %d\n", __FUNCTION__, fwcount)); | |
4256 | } | |
4257 | ||
4258 | gscan_hotlist_cache->tot_count = fwcount; | |
4259 | gscan_hotlist_cache->tot_consumed = 0; | |
4260 | gscan_hotlist_cache->scan_ch_bucket = results_v2->scan_ch_bucket; | |
4261 | ||
4262 | for (i = 0; i < fwcount; i++, pnetinfo_v2++) { | |
4263 | hotlist_found_array = &gscan_hotlist_cache->results[i]; | |
4264 | memset(hotlist_found_array, 0, sizeof(wifi_gscan_result_t)); | |
4265 | hotlist_found_array->channel = | |
4266 | wf_channel2mhz(pnetinfo_v2->pfnsubnet.channel, | |
4267 | (pnetinfo_v2->pfnsubnet.channel <= CH_MAX_2G_CHANNEL? | |
4268 | WF_CHAN_FACTOR_2_4_G : WF_CHAN_FACTOR_5_G)); | |
4269 | hotlist_found_array->rssi = (int32) pnetinfo_v2->RSSI; | |
4270 | ||
4271 | hotlist_found_array->ts = | |
4272 | convert_fw_rel_time_to_systime(&tm_spec, | |
4273 | (pnetinfo_v2->timestamp * 1000)); | |
4274 | if (pnetinfo_v2->pfnsubnet.SSID_len > DOT11_MAX_SSID_LEN) { | |
4275 | DHD_ERROR(("Invalid SSID length %d: trimming it to max\n", | |
4276 | pnetinfo_v2->pfnsubnet.SSID_len)); | |
4277 | pnetinfo_v2->pfnsubnet.SSID_len = DOT11_MAX_SSID_LEN; | |
4278 | } | |
4279 | memcpy(hotlist_found_array->ssid, pnetinfo_v2->pfnsubnet.u.SSID, | |
4280 | pnetinfo_v2->pfnsubnet.SSID_len); | |
4281 | hotlist_found_array->ssid[pnetinfo_v2->pfnsubnet.SSID_len] = '\0'; | |
4282 | ||
4283 | memcpy(&hotlist_found_array->macaddr, &pnetinfo_v2->pfnsubnet.BSSID, | |
4284 | ETHER_ADDR_LEN); | |
4285 | DHD_PNO(("\t%s "MACDBG" rssi %d\n", | |
4286 | hotlist_found_array->ssid, | |
4287 | MAC2STRDBG(hotlist_found_array->macaddr.octet), | |
4288 | hotlist_found_array->rssi)); | |
4289 | } | |
4290 | } else { | |
4291 | DHD_ERROR(("%s: event version %d not supported\n", | |
4292 | __FUNCTION__, results_v1->version)); | |
4293 | *send_evt_bytes = 0; | |
4294 | return ptr; | |
4295 | } | |
4296 | if (fwstatus == PFN_COMPLETE) { | |
4297 | ptr = (void *) gscan_hotlist_cache; | |
4298 | while (gscan_hotlist_cache) { | |
4299 | total += gscan_hotlist_cache->tot_count; | |
4300 | gscan_hotlist_cache = gscan_hotlist_cache->next; | |
4301 | } | |
4302 | *send_evt_bytes = total * sizeof(wifi_gscan_result_t); | |
4303 | } | |
4304 | ||
4305 | return ptr; | |
4306 | } | |
4307 | #endif /* GSCAN_SUPPORT */ | |
4308 | ||
4309 | int | |
4310 | dhd_pno_event_handler(dhd_pub_t *dhd, wl_event_msg_t *event, void *event_data) | |
4311 | { | |
4312 | int err = BCME_OK; | |
4313 | uint event_type; | |
4314 | dhd_pno_status_info_t *_pno_state; | |
4315 | NULL_CHECK(dhd, "dhd is NULL", err); | |
4316 | NULL_CHECK(dhd->pno_state, "pno_state is NULL", err); | |
4317 | _pno_state = PNO_GET_PNOSTATE(dhd); | |
4318 | if (!WLS_SUPPORTED(_pno_state)) { | |
4319 | DHD_ERROR(("%s : wifi location service is not supported\n", __FUNCTION__)); | |
4320 | err = BCME_UNSUPPORTED; | |
4321 | goto exit; | |
4322 | } | |
4323 | event_type = ntoh32(event->event_type); | |
4324 | DHD_PNO(("%s enter : event_type :%d\n", __FUNCTION__, event_type)); | |
4325 | switch (event_type) { | |
4326 | case WLC_E_PFN_BSSID_NET_FOUND: | |
4327 | case WLC_E_PFN_BSSID_NET_LOST: | |
4328 | /* TODO : need to implement event logic using generic netlink */ | |
4329 | break; | |
4330 | case WLC_E_PFN_BEST_BATCHING: | |
4331 | #ifndef GSCAN_SUPPORT | |
4332 | { | |
4333 | struct dhd_pno_batch_params *params_batch; | |
4334 | params_batch = &_pno_state->pno_params_arr[INDEX_OF_BATCH_PARAMS].params_batch; | |
4335 | if (!waitqueue_active(&_pno_state->get_batch_done.wait)) { | |
4336 | DHD_PNO(("%s : WLC_E_PFN_BEST_BATCHING\n", __FUNCTION__)); | |
4337 | params_batch->get_batch.buf = NULL; | |
4338 | params_batch->get_batch.bufsize = 0; | |
4339 | params_batch->get_batch.reason = PNO_STATUS_EVENT; | |
4340 | schedule_work(&_pno_state->work); | |
4341 | } else | |
4342 | DHD_PNO(("%s : WLC_E_PFN_BEST_BATCHING" | |
4343 | "will skip this event\n", __FUNCTION__)); | |
4344 | break; | |
4345 | } | |
4346 | #else | |
4347 | break; | |
4348 | #endif /* !GSCAN_SUPPORT */ | |
4349 | default: | |
4350 | DHD_ERROR(("unknown event : %d\n", event_type)); | |
4351 | } | |
4352 | exit: | |
4353 | return err; | |
4354 | } | |
4355 | ||
4356 | int dhd_pno_init(dhd_pub_t *dhd) | |
4357 | { | |
4358 | int err = BCME_OK; | |
4359 | dhd_pno_status_info_t *_pno_state; | |
4360 | char *buf = NULL; | |
4361 | NULL_CHECK(dhd, "dhd is NULL", err); | |
4362 | DHD_PNO(("%s enter\n", __FUNCTION__)); | |
4363 | UNUSED_PARAMETER(_dhd_pno_suspend); | |
4364 | if (dhd->pno_state) | |
4365 | goto exit; | |
4366 | dhd->pno_state = MALLOC(dhd->osh, sizeof(dhd_pno_status_info_t)); | |
4367 | NULL_CHECK(dhd->pno_state, "failed to create dhd_pno_state", err); | |
4368 | memset(dhd->pno_state, 0, sizeof(dhd_pno_status_info_t)); | |
4369 | /* need to check whether current firmware support batching and hotlist scan */ | |
4370 | _pno_state = PNO_GET_PNOSTATE(dhd); | |
4371 | _pno_state->wls_supported = TRUE; | |
4372 | _pno_state->dhd = dhd; | |
4373 | mutex_init(&_pno_state->pno_mutex); | |
4374 | INIT_WORK(&_pno_state->work, _dhd_pno_get_batch_handler); | |
4375 | init_completion(&_pno_state->get_batch_done); | |
4376 | #ifdef GSCAN_SUPPORT | |
4377 | init_waitqueue_head(&_pno_state->batch_get_wait); | |
4378 | #endif /* GSCAN_SUPPORT */ | |
4379 | buf = MALLOC(dhd->osh, WLC_IOCTL_SMLEN); | |
4380 | if (!buf) { | |
4381 | DHD_ERROR((":%s buf alloc err.\n", __FUNCTION__)); | |
4382 | return BCME_NOMEM; | |
4383 | } | |
4384 | err = dhd_iovar(dhd, 0, "pfnlbest", NULL, 0, buf, WLC_IOCTL_SMLEN, | |
4385 | FALSE); | |
4386 | if (err == BCME_UNSUPPORTED) { | |
4387 | _pno_state->wls_supported = FALSE; | |
4388 | DHD_INFO(("Current firmware doesn't support" | |
4389 | " Android Location Service\n")); | |
4390 | } else { | |
4391 | DHD_ERROR(("%s: Support Android Location Service\n", | |
4392 | __FUNCTION__)); | |
4393 | } | |
4394 | exit: | |
4395 | MFREE(dhd->osh, buf, WLC_IOCTL_SMLEN); | |
4396 | return err; | |
4397 | } | |
4398 | ||
4399 | int dhd_pno_deinit(dhd_pub_t *dhd) | |
4400 | { | |
4401 | int err = BCME_OK; | |
4402 | dhd_pno_status_info_t *_pno_state; | |
4403 | dhd_pno_params_t *_params; | |
4404 | NULL_CHECK(dhd, "dhd is NULL", err); | |
4405 | ||
4406 | DHD_PNO(("%s enter\n", __FUNCTION__)); | |
4407 | _pno_state = PNO_GET_PNOSTATE(dhd); | |
4408 | NULL_CHECK(_pno_state, "pno_state is NULL", err); | |
4409 | /* may need to free legacy ssid_list */ | |
4410 | if (_pno_state->pno_mode & DHD_PNO_LEGACY_MODE) { | |
4411 | _params = &_pno_state->pno_params_arr[INDEX_OF_LEGACY_PARAMS]; | |
4412 | _dhd_pno_reinitialize_prof(dhd, _params, DHD_PNO_LEGACY_MODE); | |
4413 | } | |
4414 | ||
4415 | #ifdef GSCAN_SUPPORT | |
4416 | if (_pno_state->pno_mode & DHD_PNO_GSCAN_MODE) { | |
4417 | _params = &_pno_state->pno_params_arr[INDEX_OF_GSCAN_PARAMS]; | |
4418 | mutex_lock(&_pno_state->pno_mutex); | |
4419 | dhd_pno_reset_cfg_gscan(dhd, _params, _pno_state, GSCAN_FLUSH_ALL_CFG); | |
4420 | mutex_unlock(&_pno_state->pno_mutex); | |
4421 | } | |
4422 | #endif /* GSCAN_SUPPORT */ | |
4423 | ||
4424 | if (_pno_state->pno_mode & DHD_PNO_BATCH_MODE) { | |
4425 | _params = &_pno_state->pno_params_arr[INDEX_OF_BATCH_PARAMS]; | |
4426 | /* clear resource if the BATCH MODE is on */ | |
4427 | _dhd_pno_reinitialize_prof(dhd, _params, DHD_PNO_BATCH_MODE); | |
4428 | } | |
4429 | cancel_work_sync(&_pno_state->work); | |
4430 | MFREE(dhd->osh, _pno_state, sizeof(dhd_pno_status_info_t)); | |
4431 | dhd->pno_state = NULL; | |
4432 | return err; | |
4433 | } | |
4434 | #endif /* PNO_SUPPORT */ |