| |
| #if defined(WL_ESCAN) |
| #include <bcmendian.h> |
| #include <linux/if_arp.h> |
| #include <asm/uaccess.h> |
| #include <wl_android.h> |
| #include <wl_escan.h> |
| #include <dhd_config.h> |
| |
| #define ESCAN_ERROR(name, arg1, args...) \ |
| do { \ |
| if (android_msg_level & ANDROID_ERROR_LEVEL) { \ |
| printk(KERN_ERR DHD_LOG_PREFIX "[%s] ESCAN-ERROR) %s : " arg1, name, __func__, ## args); \ |
| } \ |
| } while (0) |
| #define ESCAN_TRACE(name, arg1, args...) \ |
| do { \ |
| if (android_msg_level & ANDROID_TRACE_LEVEL) { \ |
| printk(KERN_INFO DHD_LOG_PREFIX "[%s] ESCAN-TRACE) %s : " arg1, name, __func__, ## args); \ |
| } \ |
| } while (0) |
| #define ESCAN_SCAN(name, arg1, args...) \ |
| do { \ |
| if (android_msg_level & ANDROID_SCAN_LEVEL) { \ |
| printk(KERN_INFO DHD_LOG_PREFIX "[%s] ESCAN-SCAN) %s : " arg1, name, __func__, ## args); \ |
| } \ |
| } while (0) |
| #define ESCAN_DBG(name, arg1, args...) \ |
| do { \ |
| if (android_msg_level & ANDROID_DBG_LEVEL) { \ |
| printk(KERN_INFO DHD_LOG_PREFIX "[%s] ESCAN-DBG) %s : " arg1, name, __func__, ## args); \ |
| } \ |
| } while (0) |
| |
| /* IOCTL swapping mode for Big Endian host with Little Endian dongle. Default to off */ |
| #define htod32(i) (i) |
| #define htod16(i) (i) |
| #define dtoh32(i) (i) |
| #define dtoh16(i) (i) |
| #define htodchanspec(i) (i) |
| #define dtohchanspec(i) (i) |
| #define WL_EXTRA_BUF_MAX 2048 |
| |
| #define wl_escan_get_buf(a) ((wl_scan_results_t *) (a)->escan_buf) |
| |
| #define for_each_bss(list, bss, __i) \ |
| for (__i = 0; __i < list->count && __i < IW_MAX_AP; __i++, bss = next_bss(list, bss)) |
| |
| #define wl_escan_set_sync_id(a) ((a) = htod16(0x1234)) |
| |
| #ifdef ESCAN_BUF_OVERFLOW_MGMT |
| #define BUF_OVERFLOW_MGMT_COUNT 3 |
| typedef struct { |
| int RSSI; |
| int length; |
| struct ether_addr BSSID; |
| } removal_element_t; |
| #endif /* ESCAN_BUF_OVERFLOW_MGMT */ |
| |
| /* Return a new chanspec given a legacy chanspec |
| * Returns INVCHANSPEC on error |
| */ |
| static chanspec_t |
| wl_chspec_from_legacy(chanspec_t legacy_chspec) |
| { |
| chanspec_t chspec; |
| |
| /* get the channel number */ |
| chspec = LCHSPEC_CHANNEL(legacy_chspec); |
| |
| /* convert the band */ |
| if (LCHSPEC_IS2G(legacy_chspec)) { |
| chspec |= WL_CHANSPEC_BAND_2G; |
| } else { |
| chspec |= WL_CHANSPEC_BAND_5G; |
| } |
| |
| /* convert the bw and sideband */ |
| if (LCHSPEC_IS20(legacy_chspec)) { |
| chspec |= WL_CHANSPEC_BW_20; |
| } else { |
| chspec |= WL_CHANSPEC_BW_40; |
| if (LCHSPEC_CTL_SB(legacy_chspec) == WL_LCHANSPEC_CTL_SB_LOWER) { |
| chspec |= WL_CHANSPEC_CTL_SB_L; |
| } else { |
| chspec |= WL_CHANSPEC_CTL_SB_U; |
| } |
| } |
| |
| if (wf_chspec_malformed(chspec)) { |
| ESCAN_ERROR("wlan", "wl_chspec_from_legacy: output chanspec (0x%04X) malformed\n", |
| chspec); |
| return INVCHANSPEC; |
| } |
| |
| return chspec; |
| } |
| |
| /* Return a legacy chanspec given a new chanspec |
| * Returns INVCHANSPEC on error |
| */ |
| static chanspec_t |
| wl_chspec_to_legacy(chanspec_t chspec) |
| { |
| chanspec_t lchspec; |
| |
| if (wf_chspec_malformed(chspec)) { |
| ESCAN_ERROR("wlan", "wl_chspec_to_legacy: input chanspec (0x%04X) malformed\n", |
| chspec); |
| return INVCHANSPEC; |
| } |
| |
| /* get the channel number */ |
| lchspec = CHSPEC_CHANNEL(chspec); |
| |
| /* convert the band */ |
| if (CHSPEC_IS2G(chspec)) { |
| lchspec |= WL_LCHANSPEC_BAND_2G; |
| } else { |
| lchspec |= WL_LCHANSPEC_BAND_5G; |
| } |
| |
| /* convert the bw and sideband */ |
| if (CHSPEC_IS20(chspec)) { |
| lchspec |= WL_LCHANSPEC_BW_20; |
| lchspec |= WL_LCHANSPEC_CTL_SB_NONE; |
| } else if (CHSPEC_IS40(chspec)) { |
| lchspec |= WL_LCHANSPEC_BW_40; |
| if (CHSPEC_CTL_SB(chspec) == WL_CHANSPEC_CTL_SB_L) { |
| lchspec |= WL_LCHANSPEC_CTL_SB_LOWER; |
| } else { |
| lchspec |= WL_LCHANSPEC_CTL_SB_UPPER; |
| } |
| } else { |
| /* cannot express the bandwidth */ |
| char chanbuf[CHANSPEC_STR_LEN]; |
| ESCAN_ERROR("wlan", "wl_chspec_to_legacy: unable to convert chanspec %s " |
| "(0x%04X) to pre-11ac format\n", |
| wf_chspec_ntoa(chspec, chanbuf), chspec); |
| return INVCHANSPEC; |
| } |
| |
| return lchspec; |
| } |
| |
| /* given a chanspec value from the driver, do the endian and chanspec version conversion to |
| * a chanspec_t value |
| * Returns INVCHANSPEC on error |
| */ |
| static chanspec_t |
| wl_chspec_driver_to_host(int ioctl_ver, chanspec_t chanspec) |
| { |
| chanspec = dtohchanspec(chanspec); |
| if (ioctl_ver == 1) { |
| chanspec = wl_chspec_from_legacy(chanspec); |
| } |
| |
| return chanspec; |
| } |
| |
| /* given a chanspec value, do the endian and chanspec version conversion to |
| * a chanspec_t value |
| * Returns INVCHANSPEC on error |
| */ |
| static chanspec_t |
| wl_chspec_host_to_driver(int ioctl_ver, chanspec_t chanspec) |
| { |
| if (ioctl_ver == 1) { |
| chanspec = wl_chspec_to_legacy(chanspec); |
| if (chanspec == INVCHANSPEC) { |
| return chanspec; |
| } |
| } |
| chanspec = htodchanspec(chanspec); |
| |
| return chanspec; |
| } |
| |
| /* given a channel value, do the endian and chanspec version conversion to |
| * a chanspec_t value |
| * Returns INVCHANSPEC on error |
| */ |
| static chanspec_t |
| wl_ch_host_to_driver(int ioctl_ver, u16 channel) |
| { |
| chanspec_t chanspec; |
| |
| chanspec = channel & WL_CHANSPEC_CHAN_MASK; |
| |
| if (channel <= CH_MAX_2G_CHANNEL) |
| chanspec |= WL_CHANSPEC_BAND_2G; |
| else |
| chanspec |= WL_CHANSPEC_BAND_5G; |
| |
| chanspec |= WL_CHANSPEC_BW_20; |
| |
| chanspec |= WL_CHANSPEC_CTL_SB_NONE; |
| |
| return wl_chspec_host_to_driver(ioctl_ver, chanspec); |
| } |
| |
| static inline struct wl_bss_info *next_bss(wl_scan_results_t *list, |
| struct wl_bss_info *bss) |
| { |
| return bss = bss ? |
| (struct wl_bss_info *)((uintptr) bss + dtoh32(bss->length)) : list->bss_info; |
| } |
| |
| #if defined(ESCAN_RESULT_PATCH) |
| #ifndef BSSCACHE |
| static void |
| wl_escan_dump_bss(struct net_device *dev, struct wl_escan_info *escan, |
| wl_bss_info_t *bi) |
| { |
| int16 rssi; |
| int channel; |
| chanspec_t chanspec; |
| |
| #if defined(RSSIAVG) |
| rssi = wl_get_avg_rssi(dev, &bi->BSSID, FALSE); |
| if (rssi == RSSI_MINVAL) |
| rssi = MIN(dtoh16(bi->RSSI), RSSI_MAXVAL); |
| #else |
| // terence 20150419: limit the max. rssi to -2 or the bss will be filtered out in android OS |
| rssi = MIN(dtoh16(bi->RSSI), RSSI_MAXVAL); |
| #endif |
| chanspec = wl_chspec_driver_to_host(escan->ioctl_ver, bi->chanspec); |
| channel = wf_chspec_ctlchan(chanspec); |
| ESCAN_SCAN(dev->name, "BSSID %pM, channel %3d(%3d %sMHz), rssi %3d, SSID \"%s\"\n", |
| &bi->BSSID, channel, CHSPEC_CHANNEL(chanspec), |
| CHSPEC_IS20(chanspec)?"20": |
| CHSPEC_IS40(chanspec)?"40": |
| CHSPEC_IS80(chanspec)?"80":"160", |
| rssi, bi->SSID); |
| } |
| #endif /* BSSCACHE */ |
| |
| static s32 |
| wl_escan_inform_bss(struct net_device *dev, struct wl_escan_info *escan) |
| { |
| wl_scan_results_t *bss_list; |
| #ifndef BSSCACHE |
| wl_bss_info_t *bi = NULL; /* must be initialized */ |
| s32 i; |
| #endif |
| s32 err = 0; |
| #if defined(RSSIAVG) |
| int rssi; |
| #endif |
| |
| bss_list = escan->bss_list; |
| |
| ESCAN_SCAN(dev->name, "scanned AP count (%d)\n", bss_list->count); |
| |
| /* Update cache */ |
| #if defined(RSSIAVG) |
| wl_update_rssi_cache(&escan->g_rssi_cache_ctrl, bss_list); |
| if (!in_atomic()) |
| wl_update_connected_rssi_cache(dev, &escan->g_rssi_cache_ctrl, &rssi); |
| #endif |
| #if defined(BSSCACHE) |
| wl_update_bss_cache(&escan->g_bss_cache_ctrl, |
| #if defined(RSSIAVG) |
| &escan->g_rssi_cache_ctrl, |
| #endif |
| bss_list); |
| #endif |
| |
| /* delete dirty cache */ |
| #if defined(RSSIAVG) |
| wl_delete_dirty_rssi_cache(&escan->g_rssi_cache_ctrl); |
| wl_reset_rssi_cache(&escan->g_rssi_cache_ctrl); |
| #endif |
| |
| #if defined(BSSCACHE) |
| wl_delete_dirty_bss_cache(&escan->g_bss_cache_ctrl); |
| wl_reset_bss_cache(&escan->g_bss_cache_ctrl); |
| if (escan->autochannel) |
| wl_ext_get_best_channel(dev, &escan->g_bss_cache_ctrl, |
| escan->ioctl_ver, &escan->best_2g_ch, &escan->best_5g_ch); |
| #else |
| bi = next_bss(bss_list, bi); |
| for_each_bss(bss_list, bi, i) { |
| wl_escan_dump_bss(dev, escan, bi); |
| } |
| if (escan->autochannel) |
| wl_ext_get_best_channel(dev, bss_list, escan->ioctl_ver, |
| &escan->best_2g_ch, &escan->best_5g_ch); |
| #endif |
| |
| return err; |
| } |
| #endif /* ESCAN_RESULT_PATCH */ |
| |
| static wl_scan_params_t * |
| wl_escan_alloc_params(struct net_device *dev, struct wl_escan_info *escan, |
| int channel, int nprobes, int *out_params_size) |
| { |
| wl_scan_params_t *params; |
| int params_size; |
| int num_chans; |
| |
| *out_params_size = 0; |
| |
| /* Our scan params only need space for 1 channel and 0 ssids */ |
| params_size = WL_SCAN_PARAMS_FIXED_SIZE + 1 * sizeof(uint16); |
| params = (wl_scan_params_t*) kzalloc(params_size, GFP_KERNEL); |
| if (params == NULL) { |
| ESCAN_ERROR(dev->name, "mem alloc failed (%d bytes)\n", params_size); |
| return params; |
| } |
| memset(params, 0, params_size); |
| params->nprobes = nprobes; |
| |
| num_chans = (channel == 0) ? 0 : 1; |
| |
| memcpy(¶ms->bssid, ðer_bcast, ETHER_ADDR_LEN); |
| params->bss_type = DOT11_BSSTYPE_ANY; |
| params->scan_type = DOT11_SCANTYPE_ACTIVE; |
| params->nprobes = htod32(1); |
| params->active_time = htod32(-1); |
| params->passive_time = htod32(-1); |
| params->home_time = htod32(10); |
| if (channel == -1) |
| params->channel_list[0] = htodchanspec(channel); |
| else |
| params->channel_list[0] = wl_ch_host_to_driver(escan->ioctl_ver, channel); |
| |
| /* Our scan params have 1 channel and 0 ssids */ |
| params->channel_num = htod32((0 << WL_SCAN_PARAMS_NSSID_SHIFT) | |
| (num_chans & WL_SCAN_PARAMS_COUNT_MASK)); |
| |
| *out_params_size = params_size; /* rtn size to the caller */ |
| return params; |
| } |
| |
| static void |
| wl_escan_abort(struct net_device *dev, struct wl_escan_info *escan) |
| { |
| wl_scan_params_t *params = NULL; |
| s32 params_size = 0; |
| s32 err = BCME_OK; |
| if (!in_atomic()) { |
| /* Our scan params only need space for 1 channel and 0 ssids */ |
| params = wl_escan_alloc_params(dev, escan, -1, 0, ¶ms_size); |
| if (params == NULL) { |
| ESCAN_ERROR(dev->name, "scan params allocation failed \n"); |
| err = -ENOMEM; |
| } else { |
| /* Do a scan abort to stop the driver's scan engine */ |
| err = wldev_ioctl(dev, WLC_SCAN, params, params_size, true); |
| if (err < 0) { |
| ESCAN_ERROR(dev->name, "scan abort failed \n"); |
| } |
| kfree(params); |
| } |
| } |
| } |
| |
| static s32 |
| wl_escan_notify_complete(struct net_device *dev, |
| struct wl_escan_info *escan, bool fw_abort) |
| { |
| s32 err = BCME_OK; |
| #if defined(WL_WIRELESS_EXT) |
| int cmd = 0; |
| #if WIRELESS_EXT > 13 |
| union iwreq_data wrqu; |
| char extra[IW_CUSTOM_MAX + 1]; |
| #endif |
| #endif |
| struct dhd_pub *dhd = dhd_get_pub(dev); |
| |
| ESCAN_TRACE(dev->name, "Enter\n"); |
| |
| if (fw_abort && !in_atomic()) |
| wl_escan_abort(dev, escan); |
| |
| if (timer_pending(&escan->scan_timeout)) |
| del_timer_sync(&escan->scan_timeout); |
| |
| #if defined(ESCAN_RESULT_PATCH) |
| escan->bss_list = wl_escan_get_buf(escan); |
| wl_escan_inform_bss(dev, escan); |
| #endif /* ESCAN_RESULT_PATCH */ |
| |
| escan->escan_state = ESCAN_STATE_IDLE; |
| wake_up_interruptible(&dhd->conf->event_complete); |
| |
| #if defined(WL_WIRELESS_EXT) |
| #if WIRELESS_EXT > 13 |
| #if WIRELESS_EXT > 14 |
| cmd = SIOCGIWSCAN; |
| #endif |
| // terence 20150224: fix "wlan0: (WE) : Wireless Event too big (65306)" |
| memset(&wrqu, 0, sizeof(wrqu)); |
| memset(extra, 0, sizeof(extra)); |
| if (cmd) { |
| if (cmd == SIOCGIWSCAN) { |
| wireless_send_event(dev, cmd, &wrqu, NULL); |
| } else |
| wireless_send_event(dev, cmd, &wrqu, extra); |
| } |
| #endif |
| #endif |
| |
| return err; |
| } |
| |
| #ifdef ESCAN_BUF_OVERFLOW_MGMT |
| static void |
| wl_escan_find_removal_candidate(struct wl_escan_info *escan, |
| wl_bss_info_t *bss, removal_element_t *candidate) |
| { |
| int idx; |
| for (idx = 0; idx < BUF_OVERFLOW_MGMT_COUNT; idx++) { |
| int len = BUF_OVERFLOW_MGMT_COUNT - idx - 1; |
| if (bss->RSSI < candidate[idx].RSSI) { |
| if (len) |
| memcpy(&candidate[idx + 1], &candidate[idx], |
| sizeof(removal_element_t) * len); |
| candidate[idx].RSSI = bss->RSSI; |
| candidate[idx].length = bss->length; |
| memcpy(&candidate[idx].BSSID, &bss->BSSID, ETHER_ADDR_LEN); |
| return; |
| } |
| } |
| } |
| |
| static void |
| wl_escan_remove_lowRSSI_info(struct net_device *dev, struct wl_escan_info *escan, |
| wl_scan_results_t *list, removal_element_t *candidate, wl_bss_info_t *bi) |
| { |
| int idx1, idx2; |
| int total_delete_len = 0; |
| for (idx1 = 0; idx1 < BUF_OVERFLOW_MGMT_COUNT; idx1++) { |
| int cur_len = WL_SCAN_RESULTS_FIXED_SIZE; |
| wl_bss_info_t *bss = NULL; |
| if (candidate[idx1].RSSI >= bi->RSSI) |
| continue; |
| for (idx2 = 0; idx2 < list->count; idx2++) { |
| bss = bss ? (wl_bss_info_t *)((uintptr)bss + dtoh32(bss->length)) : |
| list->bss_info; |
| if (!bcmp(&candidate[idx1].BSSID, &bss->BSSID, ETHER_ADDR_LEN) && |
| candidate[idx1].RSSI == bss->RSSI && |
| candidate[idx1].length == dtoh32(bss->length)) { |
| u32 delete_len = dtoh32(bss->length); |
| ESCAN_DBG(dev->name, |
| "delete scan info of %pM to add new AP\n", &bss->BSSID); |
| if (idx2 < list->count -1) { |
| memmove((u8 *)bss, (u8 *)bss + delete_len, |
| list->buflen - cur_len - delete_len); |
| } |
| list->buflen -= delete_len; |
| list->count--; |
| total_delete_len += delete_len; |
| /* if delete_len is greater than or equal to result length */ |
| if (total_delete_len >= bi->length) { |
| return; |
| } |
| break; |
| } |
| cur_len += dtoh32(bss->length); |
| } |
| } |
| } |
| #endif /* ESCAN_BUF_OVERFLOW_MGMT */ |
| |
| void |
| wl_escan_ext_handler(struct net_device *dev, void *argu, |
| const wl_event_msg_t *e, void *data) |
| { |
| struct wl_escan_info *escan = (struct wl_escan_info *)argu; |
| s32 status = ntoh32(e->status); |
| wl_bss_info_t *bi; |
| wl_escan_result_t *escan_result; |
| wl_bss_info_t *bss = NULL; |
| wl_scan_results_t *list; |
| u32 bi_length; |
| u32 i; |
| u16 channel; |
| |
| mutex_lock(&escan->usr_sync); |
| escan_result = (wl_escan_result_t *)data; |
| |
| if (escan->escan_state != ESCAN_STATE_SCANING) { |
| ESCAN_DBG(dev->name, "Not my scan\n"); |
| goto exit; |
| } |
| |
| ESCAN_DBG(dev->name, "enter event type : %d, status : %d \n", |
| ntoh32(e->event_type), ntoh32(e->status)); |
| |
| if (status == WLC_E_STATUS_PARTIAL) { |
| ESCAN_DBG(dev->name, "WLC_E_STATUS_PARTIAL \n"); |
| if (!escan_result) { |
| ESCAN_ERROR(dev->name, "Invalid escan result (NULL pointer)\n"); |
| goto exit; |
| } |
| if (dtoh16(escan_result->bss_count) != 1) { |
| ESCAN_ERROR(dev->name, "Invalid bss_count %d: ignoring\n", |
| escan_result->bss_count); |
| goto exit; |
| } |
| bi = escan_result->bss_info; |
| if (!bi) { |
| ESCAN_ERROR(dev->name, "Invalid escan bss info (NULL pointer)\n"); |
| goto exit; |
| } |
| bi_length = dtoh32(bi->length); |
| if (bi_length != (dtoh32(escan_result->buflen) - WL_ESCAN_RESULTS_FIXED_SIZE)) { |
| ESCAN_ERROR(dev->name, "Invalid bss_info length %d: ignoring\n", |
| bi_length); |
| goto exit; |
| } |
| |
| /* +++++ terence 20130524: skip invalid bss */ |
| channel = |
| bi->ctl_ch ? bi->ctl_ch : |
| CHSPEC_CHANNEL(wl_chspec_driver_to_host(escan->ioctl_ver, bi->chanspec)); |
| if (!dhd_conf_match_channel(escan->pub, channel)) |
| goto exit; |
| /* ----- terence 20130524: skip invalid bss */ |
| |
| { |
| int cur_len = WL_SCAN_RESULTS_FIXED_SIZE; |
| #ifdef ESCAN_BUF_OVERFLOW_MGMT |
| removal_element_t candidate[BUF_OVERFLOW_MGMT_COUNT]; |
| int remove_lower_rssi = FALSE; |
| |
| bzero(candidate, sizeof(removal_element_t)*BUF_OVERFLOW_MGMT_COUNT); |
| #endif /* ESCAN_BUF_OVERFLOW_MGMT */ |
| |
| list = wl_escan_get_buf(escan); |
| #ifdef ESCAN_BUF_OVERFLOW_MGMT |
| if (bi_length > ESCAN_BUF_SIZE - list->buflen) |
| remove_lower_rssi = TRUE; |
| #endif /* ESCAN_BUF_OVERFLOW_MGMT */ |
| |
| ESCAN_DBG(dev->name, "%s(%pM) RSSI %d flags 0x%x length %d\n", |
| bi->SSID, &bi->BSSID, bi->RSSI, bi->flags, bi->length); |
| for (i = 0; i < list->count; i++) { |
| bss = bss ? (wl_bss_info_t *)((uintptr)bss + dtoh32(bss->length)) |
| : list->bss_info; |
| #ifdef ESCAN_BUF_OVERFLOW_MGMT |
| ESCAN_DBG(dev->name, |
| "%s(%pM), i=%d bss: RSSI %d list->count %d\n", |
| bss->SSID, &bss->BSSID, i, bss->RSSI, list->count); |
| |
| if (remove_lower_rssi) |
| wl_escan_find_removal_candidate(escan, bss, candidate); |
| #endif /* ESCAN_BUF_OVERFLOW_MGMT */ |
| if (!bcmp(&bi->BSSID, &bss->BSSID, ETHER_ADDR_LEN) && |
| (CHSPEC_BAND(wl_chspec_driver_to_host(escan->ioctl_ver, bi->chanspec)) |
| == CHSPEC_BAND(wl_chspec_driver_to_host(escan->ioctl_ver, bss->chanspec))) && |
| bi->SSID_len == bss->SSID_len && |
| !bcmp(bi->SSID, bss->SSID, bi->SSID_len)) { |
| |
| /* do not allow beacon data to update |
| *the data recd from a probe response |
| */ |
| if (!(bss->flags & WL_BSS_FLAGS_FROM_BEACON) && |
| (bi->flags & WL_BSS_FLAGS_FROM_BEACON)) |
| goto exit; |
| |
| ESCAN_DBG(dev->name, |
| "%s(%pM), i=%d prev: RSSI %d flags 0x%x, " |
| "new: RSSI %d flags 0x%x\n", |
| bss->SSID, &bi->BSSID, i, bss->RSSI, bss->flags, |
| bi->RSSI, bi->flags); |
| |
| if ((bss->flags & WL_BSS_FLAGS_RSSI_ONCHANNEL) == |
| (bi->flags & WL_BSS_FLAGS_RSSI_ONCHANNEL)) { |
| /* preserve max RSSI if the measurements are |
| * both on-channel or both off-channel |
| */ |
| ESCAN_DBG(dev->name, |
| "%s(%pM), same onchan, RSSI: prev %d new %d\n", |
| bss->SSID, &bi->BSSID, bss->RSSI, bi->RSSI); |
| bi->RSSI = MAX(bss->RSSI, bi->RSSI); |
| } else if ((bss->flags & WL_BSS_FLAGS_RSSI_ONCHANNEL) && |
| (bi->flags & WL_BSS_FLAGS_RSSI_ONCHANNEL) == 0) { |
| /* preserve the on-channel rssi measurement |
| * if the new measurement is off channel |
| */ |
| ESCAN_DBG(dev->name, |
| "%s(%pM), prev onchan, RSSI: prev %d new %d\n", |
| bss->SSID, &bi->BSSID, bss->RSSI, bi->RSSI); |
| bi->RSSI = bss->RSSI; |
| bi->flags |= WL_BSS_FLAGS_RSSI_ONCHANNEL; |
| } |
| if (dtoh32(bss->length) != bi_length) { |
| u32 prev_len = dtoh32(bss->length); |
| |
| ESCAN_DBG(dev->name, |
| "bss info replacement occured(bcast:%d->probresp%d)\n", |
| bss->ie_length, bi->ie_length); |
| ESCAN_DBG(dev->name, |
| "%s(%pM), replacement!(%d -> %d)\n", |
| bss->SSID, &bi->BSSID, prev_len, bi_length); |
| |
| if (list->buflen - prev_len + bi_length > ESCAN_BUF_SIZE) { |
| ESCAN_ERROR(dev->name, |
| "Buffer is too small: keep the previous result " |
| "of this AP\n"); |
| /* Only update RSSI */ |
| bss->RSSI = bi->RSSI; |
| bss->flags |= (bi->flags |
| & WL_BSS_FLAGS_RSSI_ONCHANNEL); |
| goto exit; |
| } |
| |
| if (i < list->count - 1) { |
| /* memory copy required by this case only */ |
| memmove((u8 *)bss + bi_length, |
| (u8 *)bss + prev_len, |
| list->buflen - cur_len - prev_len); |
| } |
| list->buflen -= prev_len; |
| list->buflen += bi_length; |
| } |
| list->version = dtoh32(bi->version); |
| memcpy((u8 *)bss, (u8 *)bi, bi_length); |
| goto exit; |
| } |
| cur_len += dtoh32(bss->length); |
| } |
| if (bi_length > ESCAN_BUF_SIZE - list->buflen) { |
| #ifdef ESCAN_BUF_OVERFLOW_MGMT |
| wl_escan_remove_lowRSSI_info(dev, escan, list, candidate, bi); |
| if (bi_length > ESCAN_BUF_SIZE - list->buflen) { |
| ESCAN_DBG(dev->name, |
| "RSSI(%pM) is too low(%d) to add Buffer\n", |
| &bi->BSSID, bi->RSSI); |
| goto exit; |
| } |
| #else |
| ESCAN_ERROR(dev->name, "Buffer is too small: ignoring\n"); |
| goto exit; |
| #endif /* ESCAN_BUF_OVERFLOW_MGMT */ |
| } |
| |
| memcpy(&(((char *)list)[list->buflen]), bi, bi_length); |
| list->version = dtoh32(bi->version); |
| list->buflen += bi_length; |
| list->count++; |
| } |
| } |
| else if (status == WLC_E_STATUS_SUCCESS) { |
| ESCAN_DBG(dev->name, "ESCAN COMPLETED\n"); |
| escan->bss_list = wl_escan_get_buf(escan); |
| ESCAN_DBG(dev->name, "SCAN COMPLETED: scanned AP count=%d\n", |
| escan->bss_list->count); |
| wl_escan_notify_complete(dev, escan, false); |
| } else if ((status == WLC_E_STATUS_ABORT) || (status == WLC_E_STATUS_NEWSCAN) || |
| (status == WLC_E_STATUS_11HQUIET) || (status == WLC_E_STATUS_CS_ABORT) || |
| (status == WLC_E_STATUS_NEWASSOC)) { |
| /* Handle all cases of scan abort */ |
| ESCAN_DBG(dev->name, "ESCAN ABORT reason: %d\n", status); |
| escan->bss_list = wl_escan_get_buf(escan); |
| ESCAN_DBG(dev->name, "SCAN ABORT: scanned AP count=%d\n", |
| escan->bss_list->count); |
| wl_escan_notify_complete(dev, escan, false); |
| } else if (status == WLC_E_STATUS_TIMEOUT) { |
| ESCAN_ERROR(dev->name, "WLC_E_STATUS_TIMEOUT\n"); |
| ESCAN_ERROR(dev->name, "reason[0x%x]\n", e->reason); |
| if (e->reason == 0xFFFFFFFF) { |
| wl_escan_notify_complete(dev, escan, true); |
| } |
| } else { |
| ESCAN_ERROR(dev->name, "unexpected Escan Event %d : abort\n", status); |
| escan->bss_list = wl_escan_get_buf(escan); |
| ESCAN_DBG(dev->name, "SCAN ABORTED(UNEXPECTED): scanned AP count=%d\n", |
| escan->bss_list->count); |
| wl_escan_notify_complete(dev, escan, false); |
| } |
| exit: |
| mutex_unlock(&escan->usr_sync); |
| return; |
| } |
| |
| static int |
| wl_escan_prep(struct net_device *dev, struct wl_escan_info *escan, |
| wl_uint32_list_t *list, void *scan_params, wlc_ssid_t *ssid, bool bcast) |
| { |
| int err = 0; |
| wl_scan_results_t *results; |
| char *ptr; |
| int i = 0, j = 0; |
| wlc_ssid_t ssid_tmp; |
| u32 n_channels = 0; |
| uint channel; |
| chanspec_t chanspec; |
| u32 n_ssids = 0; |
| wl_scan_params_t *params = NULL; |
| wl_scan_params_v2_t *params_v2 = NULL; |
| u32 scan_param_size = 0; |
| u32 channel_offset = 0; |
| u32 cur_offset; |
| uint16 *chan_list = NULL; |
| |
| results = wl_escan_get_buf(escan); |
| results->version = 0; |
| results->count = 0; |
| results->buflen = WL_SCAN_RESULTS_FIXED_SIZE; |
| escan->escan_state = ESCAN_STATE_SCANING; |
| |
| /* Arm scan timeout timer */ |
| mod_timer(&escan->scan_timeout, jiffies + msecs_to_jiffies(WL_ESCAN_TIMER_INTERVAL_MS)); |
| |
| if (escan->scan_params_v2) { |
| params_v2 = (wl_scan_params_v2_t *)scan_params; |
| scan_param_size = sizeof(wl_scan_params_v2_t); |
| channel_offset = offsetof(wl_scan_params_v2_t, channel_list); |
| } else { |
| params = (wl_scan_params_t *)scan_params; |
| scan_param_size = sizeof(wl_scan_params_t); |
| channel_offset = offsetof(wl_scan_params_t, channel_list); |
| } |
| |
| if (params_v2) { |
| /* scan params ver2 */ |
| memcpy(¶ms_v2->bssid, ðer_bcast, ETHER_ADDR_LEN); |
| params_v2->version = htod16(WL_SCAN_PARAMS_VERSION_V2); |
| params_v2->length = htod16(sizeof(wl_scan_params_v2_t)); |
| params_v2->bss_type = DOT11_BSSTYPE_ANY; |
| params_v2->scan_type = DOT11_SCANTYPE_ACTIVE; |
| params_v2->nprobes = htod32(-1); |
| params_v2->active_time = htod32(-1); |
| params_v2->passive_time = htod32(-1); |
| params_v2->home_time = htod32(-1); |
| params_v2->channel_num = 0; |
| bzero(¶ms_v2->ssid, sizeof(wlc_ssid_t)); |
| chan_list = params_v2->channel_list; |
| } else { |
| /* scan params ver 1 */ |
| memcpy(¶ms->bssid, ðer_bcast, ETHER_ADDR_LEN); |
| params->bss_type = DOT11_BSSTYPE_ANY; |
| params->scan_type = DOT11_SCANTYPE_ACTIVE; |
| params->nprobes = htod32(-1); |
| params->active_time = htod32(-1); |
| params->passive_time = htod32(-1); |
| params->home_time = htod32(-1); |
| params->channel_num = 0; |
| bzero(¶ms->ssid, sizeof(wlc_ssid_t)); |
| chan_list = params->channel_list; |
| } |
| |
| cur_offset = channel_offset; |
| |
| n_channels = dtoh32(list->count); |
| /* Copy channel array if applicable */ |
| ESCAN_SCAN(dev->name, "### List of channelspecs to scan ###\n"); |
| if (n_channels > 0) { |
| for (i = 0; i < n_channels; i++) { |
| channel = dtoh32(list->element[i]); |
| if (!dhd_conf_match_channel(escan->pub, channel)) |
| continue; |
| chanspec = WL_CHANSPEC_BW_20; |
| if (chanspec == INVCHANSPEC) { |
| ESCAN_ERROR(dev->name, "Invalid chanspec! Skipping channel\n"); |
| continue; |
| } |
| if (channel <= CH_MAX_2G_CHANNEL) { |
| chanspec |= WL_CHANSPEC_BAND_2G; |
| } else { |
| chanspec |= WL_CHANSPEC_BAND_5G; |
| } |
| chan_list[j] = channel; |
| chan_list[j] &= WL_CHANSPEC_CHAN_MASK; |
| chan_list[j] |= chanspec; |
| ESCAN_SCAN(dev->name, "Chan : %d, Channel spec: %x\n", |
| channel, chan_list[j]); |
| chan_list[j] = wl_chspec_host_to_driver(escan->ioctl_ver, |
| chan_list[j]); |
| j++; |
| } |
| cur_offset += (j * (sizeof(u16))); |
| n_channels = j; |
| } else { |
| ESCAN_SCAN(dev->name, "Scanning all channels\n"); |
| } |
| |
| if (ssid && ssid->SSID_len) { |
| /* Copy ssid array if applicable */ |
| ESCAN_SCAN(dev->name, "### List of SSIDs to scan ###\n"); |
| cur_offset = (u32) roundup(cur_offset, sizeof(u32)); |
| ptr = (char*)params + cur_offset; |
| |
| if (bcast) { |
| n_ssids = 2; |
| ESCAN_SCAN(dev->name, "0: Broadcast scan\n"); |
| memset(&ssid_tmp, 0, sizeof(wlc_ssid_t)); |
| ssid_tmp.SSID_len = 0; |
| memcpy(ptr, &ssid_tmp, sizeof(wlc_ssid_t)); |
| ptr += sizeof(wlc_ssid_t); |
| } else { |
| n_ssids = 1; |
| } |
| |
| memset(&ssid_tmp, 0, sizeof(wlc_ssid_t)); |
| ssid_tmp.SSID_len = ssid->SSID_len; |
| memcpy(ssid_tmp.SSID, ssid->SSID, ssid->SSID_len); |
| memcpy(ptr, &ssid_tmp, sizeof(wlc_ssid_t)); |
| ptr += sizeof(wlc_ssid_t); |
| ESCAN_SCAN(dev->name, "1: scan for %s size=%d\n", |
| ssid_tmp.SSID, ssid_tmp.SSID_len); |
| } |
| else { |
| ESCAN_SCAN(dev->name, "Broadcast scan\n"); |
| } |
| |
| if (n_ssids || n_channels) { |
| u32 channel_num = |
| htod32((n_ssids << WL_SCAN_PARAMS_NSSID_SHIFT) | |
| (n_channels & WL_SCAN_PARAMS_COUNT_MASK)); |
| if (params_v2) { |
| params_v2->channel_num = channel_num; |
| } else { |
| params->channel_num = channel_num; |
| } |
| } |
| |
| return err; |
| } |
| |
| static int |
| wl_escan_reset(struct wl_escan_info *escan) |
| { |
| if (timer_pending(&escan->scan_timeout)) |
| del_timer_sync(&escan->scan_timeout); |
| escan->escan_state = ESCAN_STATE_IDLE; |
| |
| return 0; |
| } |
| |
| static void |
| wl_escan_timeout(unsigned long data) |
| { |
| wl_event_msg_t msg; |
| struct wl_escan_info *escan = (struct wl_escan_info *)data; |
| wl_scan_results_t *bss_list; |
| struct wl_bss_info *bi = NULL; |
| s32 i; |
| u32 channel; |
| |
| if (!escan->dev) { |
| ESCAN_ERROR("wlan", "No dev present\n"); |
| return; |
| } |
| |
| bss_list = wl_escan_get_buf(escan); |
| if (!bss_list) { |
| ESCAN_ERROR(escan->dev->name, |
| "bss_list is null. Didn't receive any partial scan results\n"); |
| } else { |
| ESCAN_ERROR(escan->dev->name, "scanned AP count (%d)\n", bss_list->count); |
| bi = next_bss(bss_list, bi); |
| for_each_bss(bss_list, bi, i) { |
| channel = wf_chspec_ctlchan(wl_chspec_driver_to_host(escan->ioctl_ver, |
| bi->chanspec)); |
| ESCAN_ERROR(escan->dev->name, "SSID :%s Channel :%d\n", bi->SSID, channel); |
| } |
| } |
| |
| bzero(&msg, sizeof(wl_event_msg_t)); |
| ESCAN_ERROR(escan->dev->name, "timer expired\n"); |
| |
| msg.ifidx = dhd_net2idx(escan->pub->info, escan->dev); |
| msg.event_type = hton32(WLC_E_ESCAN_RESULT); |
| msg.status = hton32(WLC_E_STATUS_TIMEOUT); |
| msg.reason = 0xFFFFFFFF; |
| wl_ext_event_send(escan->pub->event_params, &msg, NULL); |
| } |
| |
| int |
| wl_escan_set_scan(struct net_device *dev, |
| wlc_ssid_t *ssid, uint16 channel, bool bcast) |
| { |
| struct dhd_pub *dhdp = dhd_get_pub(dev); |
| struct wl_escan_info *escan = dhdp->escan; |
| s32 err = BCME_OK; |
| wl_escan_params_t *eparams = NULL; |
| wl_escan_params_v2_t *eparams_v2 = NULL; |
| u8 *scan_params = NULL; |
| s32 params_size; |
| wl_escan_params_t *params = NULL; |
| scb_val_t scbval; |
| static int cnt = 0; |
| u32 n_channels = 0; |
| wl_uint32_list_t *list; |
| u8 valid_chan_list[sizeof(u32)*(WL_NUMCHANNELS + 1)]; |
| |
| mutex_lock(&escan->usr_sync); |
| if (escan->escan_state == ESCAN_STATE_DOWN) { |
| ESCAN_ERROR(dev->name, "STATE is down\n"); |
| err = -EINVAL; |
| goto exit2; |
| } |
| |
| if (wl_ext_check_scan(dev, dhdp)) { |
| err = -EBUSY; |
| goto exit; |
| } |
| |
| ESCAN_TRACE(dev->name, "Enter \n"); |
| |
| if (escan->scan_params_v2) { |
| params_size = (WL_SCAN_PARAMS_V2_FIXED_SIZE + |
| OFFSETOF(wl_escan_params_v2_t, params)); |
| } else { |
| params_size = (WL_SCAN_PARAMS_FIXED_SIZE + OFFSETOF(wl_escan_params_t, params)); |
| } |
| |
| /* if scan request is not empty parse scan request paramters */ |
| memset(valid_chan_list, 0, sizeof(valid_chan_list)); |
| list = (wl_uint32_list_t *)(void *) valid_chan_list; |
| |
| if (channel) { |
| list->count = htod32(1); |
| list->element[0] = htod32(channel); |
| } else { |
| list->count = htod32(WL_NUMCHANNELS); |
| err = wldev_ioctl(dev, WLC_GET_VALID_CHANNELS, valid_chan_list, |
| sizeof(valid_chan_list), false); |
| if (err != 0) { |
| ESCAN_ERROR(dev->name, "get channels failed with %d\n", err); |
| goto exit; |
| } |
| } |
| |
| n_channels = dtoh32(list->count); |
| /* Allocate space for populating ssids in wl_escan_params_t struct */ |
| if (dtoh32(list->count) % 2) |
| /* If n_channels is odd, add a padd of u16 */ |
| params_size += sizeof(u16) * (n_channels + 1); |
| else |
| params_size += sizeof(u16) * n_channels; |
| if (ssid && ssid->SSID_len) { |
| params_size += sizeof(struct wlc_ssid) * 2; |
| } |
| |
| params = (wl_escan_params_t *) kzalloc(params_size, GFP_KERNEL); |
| if (params == NULL) { |
| err = -ENOMEM; |
| goto exit; |
| } |
| |
| if (escan->scan_params_v2) { |
| eparams_v2 = (wl_escan_params_v2_t *)params; |
| scan_params = (u8 *)&eparams_v2->params; |
| eparams_v2->version = htod32(ESCAN_REQ_VERSION_V2); |
| eparams_v2->action = htod16(WL_SCAN_ACTION_START); |
| } else { |
| eparams = (wl_escan_params_t *)params; |
| scan_params = (u8 *)&eparams->params; |
| eparams->version = htod32(ESCAN_REQ_VERSION); |
| eparams->action = htod16(WL_SCAN_ACTION_START); |
| } |
| wl_escan_set_sync_id(params->sync_id); |
| |
| wl_escan_prep(dev, escan, list, scan_params, ssid, bcast); |
| |
| if (params_size + sizeof("escan") >= WLC_IOCTL_MEDLEN) { |
| ESCAN_ERROR(dev->name, "ioctl buffer length not sufficient\n"); |
| kfree(params); |
| err = -ENOMEM; |
| goto exit; |
| } |
| ESCAN_SCAN(dev->name, "scan_type %d\n", params->params.scan_type); |
| |
| WL_MSG(dev->name, "LEGACY_SCAN\n"); |
| err = wldev_iovar_setbuf(dev, "escan", params, params_size, |
| escan->escan_ioctl_buf, WLC_IOCTL_MEDLEN, NULL); |
| if (unlikely(err)) { |
| if (err == BCME_EPERM) |
| /* Scan Not permitted at this point of time */ |
| ESCAN_TRACE(dev->name, "Escan not permitted at this time (%d)\n", err); |
| else |
| ESCAN_ERROR(dev->name, "Escan set error (%d)\n", err); |
| } |
| kfree(params); |
| |
| if (unlikely(err)) { |
| /* Don't print Error incase of Scan suppress */ |
| if (err == BCME_EPERM) |
| ESCAN_TRACE(dev->name, "Escan failed: Scan Suppressed\n"); |
| else { |
| cnt++; |
| ESCAN_ERROR(dev->name, "error (%d), cnt=%d\n", err, cnt); |
| // terence 20140111: send disassoc to firmware |
| if (cnt >= 4) { |
| memset(&scbval, 0, sizeof(scb_val_t)); |
| wldev_ioctl(dev, WLC_DISASSOC, &scbval, sizeof(scb_val_t), true); |
| ESCAN_ERROR(dev->name, "Send disassoc to break the busy\n"); |
| cnt = 0; |
| } |
| } |
| } else { |
| cnt = 0; |
| escan->dev = dev; |
| } |
| exit: |
| if (unlikely(err)) { |
| wl_escan_reset(escan); |
| } |
| exit2: |
| mutex_unlock(&escan->usr_sync); |
| return err; |
| } |
| |
| #if defined(WL_WIRELESS_EXT) |
| static int |
| rssi_to_qual(int rssi) |
| { |
| if (rssi <= WL_IW_RSSI_NO_SIGNAL) |
| return 0; |
| else if (rssi <= WL_IW_RSSI_VERY_LOW) |
| return 1; |
| else if (rssi <= WL_IW_RSSI_LOW) |
| return 2; |
| else if (rssi <= WL_IW_RSSI_GOOD) |
| return 3; |
| else if (rssi <= WL_IW_RSSI_VERY_GOOD) |
| return 4; |
| else |
| return 5; |
| } |
| |
| static int |
| wl_escan_merge_scan_results(struct net_device *dev, struct wl_escan_info *escan, |
| struct iw_request_info *info, char *extra, wl_bss_info_t *bi, int *len, int max_size) |
| { |
| s32 err = BCME_OK; |
| struct iw_event iwe; |
| int j; |
| char *event = extra, *end = extra + max_size - WE_ADD_EVENT_FIX, *value; |
| int16 rssi; |
| int channel; |
| chanspec_t chanspec; |
| |
| /* overflow check cover fields before wpa IEs */ |
| if (event + ETHER_ADDR_LEN + bi->SSID_len + IW_EV_UINT_LEN + IW_EV_FREQ_LEN + |
| IW_EV_QUAL_LEN >= end) { |
| err = -E2BIG; |
| goto exit; |
| } |
| |
| #if defined(RSSIAVG) |
| rssi = wl_get_avg_rssi(&escan->g_rssi_cache_ctrl, &bi->BSSID); |
| if (rssi == RSSI_MINVAL) |
| rssi = MIN(dtoh16(bi->RSSI), RSSI_MAXVAL); |
| #else |
| // terence 20150419: limit the max. rssi to -2 or the bss will be filtered out in android OS |
| rssi = MIN(dtoh16(bi->RSSI), RSSI_MAXVAL); |
| #endif |
| chanspec = wl_chspec_driver_to_host(escan->ioctl_ver, bi->chanspec); |
| channel = wf_chspec_ctlchan(chanspec); |
| ESCAN_SCAN(dev->name, "BSSID %pM, channel %3d(%3d %sMHz), rssi %3d, SSID \"%s\"\n", |
| &bi->BSSID, channel, CHSPEC_CHANNEL(chanspec), |
| CHSPEC_IS20(chanspec)?"20": |
| CHSPEC_IS40(chanspec)?"40": |
| CHSPEC_IS80(chanspec)?"80":"160", |
| rssi, bi->SSID); |
| |
| /* First entry must be the BSSID */ |
| iwe.cmd = SIOCGIWAP; |
| iwe.u.ap_addr.sa_family = ARPHRD_ETHER; |
| memcpy(iwe.u.ap_addr.sa_data, &bi->BSSID, ETHER_ADDR_LEN); |
| event = IWE_STREAM_ADD_EVENT(info, event, end, &iwe, IW_EV_ADDR_LEN); |
| |
| /* SSID */ |
| iwe.u.data.length = dtoh32(bi->SSID_len); |
| iwe.cmd = SIOCGIWESSID; |
| iwe.u.data.flags = 1; |
| event = IWE_STREAM_ADD_POINT(info, event, end, &iwe, bi->SSID); |
| |
| /* Mode */ |
| if (dtoh16(bi->capability) & (DOT11_CAP_ESS | DOT11_CAP_IBSS)) { |
| iwe.cmd = SIOCGIWMODE; |
| if (dtoh16(bi->capability) & DOT11_CAP_ESS) |
| iwe.u.mode = IW_MODE_INFRA; |
| else |
| iwe.u.mode = IW_MODE_ADHOC; |
| event = IWE_STREAM_ADD_EVENT(info, event, end, &iwe, IW_EV_UINT_LEN); |
| } |
| |
| /* Channel */ |
| iwe.cmd = SIOCGIWFREQ; |
| #if 1 |
| iwe.u.freq.m = wf_channel2mhz(channel, channel <= CH_MAX_2G_CHANNEL ? |
| WF_CHAN_FACTOR_2_4_G : WF_CHAN_FACTOR_5_G); |
| #else |
| iwe.u.freq.m = wf_channel2mhz(bi->n_cap ? |
| bi->ctl_ch : CHSPEC_CHANNEL(bi->chanspec), |
| CHSPEC_CHANNEL(bi->chanspec) <= CH_MAX_2G_CHANNEL ? |
| WF_CHAN_FACTOR_2_4_G : WF_CHAN_FACTOR_5_G); |
| #endif |
| iwe.u.freq.e = 6; |
| event = IWE_STREAM_ADD_EVENT(info, event, end, &iwe, IW_EV_FREQ_LEN); |
| |
| /* Channel quality */ |
| iwe.cmd = IWEVQUAL; |
| iwe.u.qual.qual = rssi_to_qual(rssi); |
| iwe.u.qual.level = 0x100 + rssi; |
| iwe.u.qual.noise = 0x100 + bi->phy_noise; |
| event = IWE_STREAM_ADD_EVENT(info, event, end, &iwe, IW_EV_QUAL_LEN); |
| |
| wl_iw_handle_scanresults_ies(&event, end, info, bi); |
| |
| /* Encryption */ |
| iwe.cmd = SIOCGIWENCODE; |
| if (dtoh16(bi->capability) & DOT11_CAP_PRIVACY) |
| iwe.u.data.flags = IW_ENCODE_ENABLED | IW_ENCODE_NOKEY; |
| else |
| iwe.u.data.flags = IW_ENCODE_DISABLED; |
| iwe.u.data.length = 0; |
| event = IWE_STREAM_ADD_POINT(info, event, end, &iwe, (char *)event); |
| |
| /* Rates */ |
| if (bi->rateset.count <= sizeof(bi->rateset.rates)) { |
| if (event + IW_MAX_BITRATES*IW_EV_PARAM_LEN >= end) { |
| err = -E2BIG; |
| goto exit; |
| } |
| value = event + IW_EV_LCP_LEN; |
| iwe.cmd = SIOCGIWRATE; |
| /* Those two flags are ignored... */ |
| iwe.u.bitrate.fixed = iwe.u.bitrate.disabled = 0; |
| for (j = 0; j < bi->rateset.count && j < IW_MAX_BITRATES; j++) { |
| iwe.u.bitrate.value = (bi->rateset.rates[j] & 0x7f) * 500000; |
| value = IWE_STREAM_ADD_VALUE(info, event, value, end, &iwe, |
| IW_EV_PARAM_LEN); |
| } |
| event = value; |
| } |
| *len = event - extra; |
| if (*len < 0) |
| ESCAN_ERROR(dev->name, "==> Wrong size\n"); |
| |
| exit: |
| return err; |
| } |
| |
| int |
| wl_escan_merge_scan_list(struct net_device *dev, u8 *cur_bssid, |
| struct iw_request_info *info, struct iw_point *dwrq, char *extra, |
| int *len_ret, int *bss_cnt) |
| { |
| struct dhd_pub *dhdp = dhd_get_pub(dev); |
| struct wl_escan_info *escan = dhdp->escan; |
| s32 err = BCME_OK; |
| int i = 0, cnt = 0; |
| int len_prep = 0; |
| wl_bss_info_t *bi = NULL; |
| wl_scan_results_t *bss_list; |
| __u16 buflen_from_user = dwrq->length; |
| |
| bss_list = escan->bss_list; |
| bi = next_bss(bss_list, bi); |
| for_each_bss(bss_list, bi, i) |
| { |
| if (!memcmp(&bi->BSSID, cur_bssid, ETHER_ADDR_LEN)) { |
| ESCAN_SCAN(dev->name, "skip connected AP %pM\n", cur_bssid); |
| continue; |
| } |
| len_prep = 0; |
| err = wl_escan_merge_scan_results(dev, escan, info, extra+*len_ret, bi, |
| &len_prep, buflen_from_user-*len_ret); |
| *len_ret += len_prep; |
| if (err) |
| goto exit; |
| cnt++; |
| } |
| *bss_cnt = cnt; |
| |
| exit: |
| return err; |
| } |
| |
| #if defined(BSSCACHE) |
| int |
| wl_escan_merge_cache_list(struct net_device *dev, u8 *cur_bssid, |
| struct iw_request_info *info, struct iw_point *dwrq, char *extra, |
| int *len_ret, int *bss_cnt) |
| { |
| struct dhd_pub *dhdp = dhd_get_pub(dev); |
| struct wl_escan_info *escan = dhdp->escan; |
| s32 err = BCME_OK; |
| int i = 0, cnt = 0; |
| int len_prep = 0; |
| wl_bss_info_t *bi = NULL; |
| wl_scan_results_t *bss_list; |
| __u16 buflen_from_user = dwrq->length; |
| wl_bss_cache_t *node; |
| |
| bss_list = &escan->g_bss_cache_ctrl.m_cache_head->results; |
| node = escan->g_bss_cache_ctrl.m_cache_head; |
| for (i=0; node && i<IW_MAX_AP; i++) |
| { |
| bi = node->results.bss_info; |
| if (node->dirty > 1) { |
| if (!memcmp(&bi->BSSID, cur_bssid, ETHER_ADDR_LEN)) { |
| ESCAN_SCAN(dev->name, "skip connected AP %pM\n", cur_bssid); |
| node = node->next; |
| continue; |
| } |
| len_prep = 0; |
| err = wl_escan_merge_scan_results(dev, escan, info, extra+*len_ret, bi, |
| &len_prep, buflen_from_user-*len_ret); |
| *len_ret += len_prep; |
| if (err) |
| goto exit; |
| cnt++; |
| } |
| node = node->next; |
| } |
| *bss_cnt = cnt; |
| |
| exit: |
| return err; |
| } |
| #endif |
| |
| int |
| wl_escan_get_scan(struct net_device *dev, |
| struct iw_request_info *info, struct iw_point *dwrq, char *extra) |
| { |
| struct dhd_pub *dhdp = dhd_get_pub(dev); |
| struct wl_escan_info *escan = dhdp->escan; |
| s32 err = BCME_OK; |
| int scan_cnt = 0; |
| #if defined(BSSCACHE) |
| int cache_cnt = 0; |
| #endif |
| int len_prep = 0, len_ret = 0; |
| wl_bss_info_t *bi = NULL; |
| __u16 buflen_from_user = dwrq->length; |
| char *buf = NULL; |
| struct ether_addr cur_bssid; |
| u8 ioctl_buf[WLC_IOCTL_SMLEN]; |
| |
| if (!extra) { |
| ESCAN_TRACE(dev->name, "extra is null\n"); |
| return -EINVAL; |
| } |
| |
| mutex_lock(&escan->usr_sync); |
| |
| /* Check for scan in progress */ |
| if (escan->escan_state == ESCAN_STATE_SCANING) { |
| ESCAN_DBG(dev->name, "SIOCGIWSCAN GET still scanning\n"); |
| err = -EAGAIN; |
| goto exit; |
| } |
| if (!escan->bss_list) { |
| ESCAN_ERROR(dev->name, "scan not ready\n"); |
| err = -EAGAIN; |
| goto exit; |
| } |
| if (dev != escan->dev) { |
| ESCAN_ERROR(dev->name, "not my scan from %s\n", escan->dev->name); |
| err = -EINVAL; |
| goto exit; |
| } |
| |
| ESCAN_SCAN(dev->name, "SIOCGIWSCAN, len=%d\n", dwrq->length); |
| |
| wldev_iovar_getbuf(dev, "cur_etheraddr", NULL, 0, ioctl_buf, WLC_IOCTL_SMLEN, NULL); |
| err = wldev_ioctl(dev, WLC_GET_BSSID, &cur_bssid, sizeof(cur_bssid), false); |
| if (err != BCME_NOTASSOCIATED && |
| memcmp(ðer_null, &cur_bssid, ETHER_ADDR_LEN) && |
| memcmp(ioctl_buf, &cur_bssid, ETHER_ADDR_LEN)) { |
| // merge current connected bss |
| buf = kzalloc(WL_EXTRA_BUF_MAX, GFP_ATOMIC); |
| if (!buf) { |
| ESCAN_ERROR(dev->name, "buffer alloc failed.\n"); |
| err = BCME_NOMEM; |
| goto exit; |
| } |
| *(u32 *)buf = htod32(WL_EXTRA_BUF_MAX); |
| err = wldev_ioctl(dev, WLC_GET_BSS_INFO, buf, WL_EXTRA_BUF_MAX, false); |
| if (unlikely(err)) { |
| ESCAN_ERROR(dev->name, "Could not get bss info %d\n", err); |
| goto exit; |
| } |
| bi = (struct wl_bss_info *)(buf + 4); |
| len_prep = 0; |
| err = wl_escan_merge_scan_results(dev, escan, info, extra+len_ret, bi, |
| &len_prep, buflen_from_user-len_ret); |
| len_ret += len_prep; |
| if (err) |
| goto exit; |
| bi = NULL; |
| } |
| |
| err = wl_escan_merge_scan_list(dev, (u8 *)&cur_bssid, info, dwrq, extra, |
| &len_ret, &scan_cnt); |
| if (err) |
| goto exit; |
| #if defined(BSSCACHE) |
| err = wl_escan_merge_cache_list(dev, (u8 *)&cur_bssid, info, dwrq, extra, |
| &len_ret, &cache_cnt); |
| if (err) |
| goto exit; |
| #endif |
| |
| if ((len_ret + WE_ADD_EVENT_FIX) < dwrq->length) |
| dwrq->length = len_ret; |
| |
| dwrq->flags = 0; /* todo */ |
| ESCAN_SCAN(dev->name, "scanned AP count (%d)\n", scan_cnt); |
| #if defined(BSSCACHE) |
| ESCAN_SCAN(dev->name, "cached AP count (%d)\n", cache_cnt); |
| #endif |
| exit: |
| kfree(buf); |
| dwrq->length = len_ret; |
| mutex_unlock(&escan->usr_sync); |
| return err; |
| } |
| #endif /* WL_WIRELESS_EXT */ |
| |
| #ifdef WLMESH |
| bool |
| wl_escan_meshid_ie(u8 *parse, u32 len, wlc_ssid_t *mesh_id) |
| { |
| bcm_tlv_t *ie; |
| |
| if((ie = bcm_parse_tlvs(parse, (int)len, DOT11_MNG_MESH_ID)) != NULL) { |
| mesh_id->SSID_len = ie->len; |
| if (ie->len) { |
| strncpy(mesh_id->SSID, ie->data, ie->len); |
| } |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| bool |
| wl_escan_rsn_ie(u8 *parse, u32 len) |
| { |
| if (bcm_parse_tlvs(parse, (u32)len, DOT11_MNG_RSN_ID)) { |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| bool |
| wl_escan_mesh_info_ie(struct net_device *dev, u8 *parse, u32 len, |
| struct wl_mesh_params *mesh_info) |
| { |
| bcm_tlv_t *ie; |
| uchar mesh_oui[]={0x00, 0x22, 0xf4}; |
| int totl_len; |
| uint8 *pie; |
| uint max_len; |
| bool found = FALSE; |
| |
| memset(mesh_info, 0, sizeof(struct wl_mesh_params)); |
| if((ie = bcm_parse_tlvs(parse, (int)len, DOT11_MNG_VS_ID)) != NULL) { |
| totl_len = ie->len; |
| if (!memcmp(ie->data, &mesh_oui, sizeof(mesh_oui))) { |
| pie = ie->data + sizeof(mesh_oui); |
| ie = (bcm_tlv_t *)pie; |
| totl_len -= sizeof(mesh_oui); |
| while (totl_len > 2 && ie->len) { |
| if (ie->id == MESH_INFO_MASTER_BSSID && ie->len == ETHER_ADDR_LEN) { |
| memcpy(&mesh_info->master_bssid, ie->data, ETHER_ADDR_LEN); |
| } else if (ie->id == MESH_INFO_MASTER_CHANNEL) { |
| mesh_info->master_channel = ie->data[0]; |
| found = TRUE; |
| } else if (ie->id == MESH_INFO_HOP_CNT) { |
| mesh_info->hop_cnt = ie->data[0]; |
| } else if (ie->id == MESH_INFO_PEER_BSSID) { |
| max_len = min(MAX_HOP_LIST*ETHER_ADDR_LEN, (int)ie->len); |
| memcpy(mesh_info->peer_bssid, ie->data, max_len); |
| } |
| totl_len -= (ie->len + 2); |
| pie = ie->data + ie->len; |
| ie = (bcm_tlv_t *)pie; |
| } |
| } |
| } |
| |
| return found; |
| } |
| |
| bool |
| wl_escan_mesh_info(struct net_device *dev, struct wl_escan_info *escan, |
| struct ether_addr *peer_bssid, struct wl_mesh_params *mesh_info) |
| { |
| int i = 0; |
| wl_bss_info_t *bi = NULL; |
| wl_scan_results_t *bss_list; |
| int16 bi_rssi, bi_chan; |
| wlc_ssid_t bi_meshid; |
| bool is_mesh_peer = FALSE, found = FALSE; |
| struct wl_mesh_params peer_mesh_info; |
| |
| mutex_lock(&escan->usr_sync); |
| |
| /* Check for scan in progress */ |
| if (escan->escan_state == ESCAN_STATE_SCANING) { |
| ESCAN_ERROR(dev->name, "SIOCGIWSCAN GET still scanning\n"); |
| goto exit; |
| } |
| if (!escan->bss_list) { |
| ESCAN_ERROR(dev->name, "scan not ready\n"); |
| goto exit; |
| } |
| if (dev != escan->dev) { |
| ESCAN_ERROR(dev->name, "not my scan from %s\n", escan->dev->name); |
| goto exit; |
| } |
| |
| bss_list = escan->bss_list; |
| bi = next_bss(bss_list, bi); |
| ESCAN_SCAN(dev->name, "scanned AP/Mesh count (%d)\n", bss_list->count); |
| for_each_bss(bss_list, bi, i) |
| { |
| memset(&bi_meshid, 0, sizeof(bi_meshid)); |
| is_mesh_peer = FALSE; |
| bi_chan = wf_chspec_ctlchan( |
| wl_chspec_driver_to_host(escan->ioctl_ver, bi->chanspec)); |
| bi_rssi = MIN(dtoh16(bi->RSSI), RSSI_MAXVAL); |
| is_mesh_peer = wl_escan_meshid_ie(((u8*)bi)+bi->ie_offset, |
| bi->ie_length, &bi_meshid); |
| if (!(bi->capability & (DOT11_CAP_ESS|DOT11_CAP_IBSS)) && is_mesh_peer) { |
| bool bi_sae = FALSE, bss_found = FALSE, prefer = FALSE; |
| if (!memcmp(peer_bssid, &bi->BSSID, ETHER_ADDR_LEN)) { |
| bi_sae = wl_escan_rsn_ie(((u8*)bi)+bi->ie_offset, bi->ie_length); |
| bss_found = wl_escan_mesh_info_ie(dev, ((u8*)bi)+bi->ie_offset, |
| bi->ie_length, &peer_mesh_info); |
| if (bss_found) { |
| memcpy(&mesh_info->master_bssid, &peer_mesh_info.master_bssid, |
| ETHER_ADDR_LEN); |
| mesh_info->master_channel = peer_mesh_info.master_channel; |
| mesh_info->hop_cnt = peer_mesh_info.hop_cnt; |
| memcpy(mesh_info->peer_bssid, peer_mesh_info.peer_bssid, |
| sizeof(peer_mesh_info.peer_bssid)); |
| prefer = TRUE; |
| found = TRUE; |
| } |
| } |
| ESCAN_SCAN(dev->name, |
| "%s[Mesh] BSSID=%pM, channel=%d, RSSI=%d, sec=%s, " |
| "mbssid=%pM, mchannel=%d, hop=%d, pbssid=%pM, MeshID=\"%s\"\n", |
| prefer?"*":" ", &bi->BSSID, bi_chan, bi_rssi, bi_sae?"SAE":"OPEN", |
| &peer_mesh_info.master_bssid, peer_mesh_info.master_channel, |
| peer_mesh_info.hop_cnt, &peer_mesh_info.peer_bssid, bi_meshid.SSID); |
| } |
| } |
| |
| exit: |
| mutex_unlock(&escan->usr_sync); |
| return found; |
| } |
| |
| bool |
| wl_escan_mesh_peer(struct net_device *dev, struct wl_escan_info *escan, |
| wlc_ssid_t *cur_ssid, uint16 cur_chan, bool sae, |
| struct wl_mesh_params *mesh_info) |
| { |
| int i = 0; |
| wl_bss_info_t *bi = NULL; |
| wl_scan_results_t *bss_list; |
| int16 bi_rssi, bi_chan, max_rssi = -100; |
| uint min_hop_cnt = 255; |
| wlc_ssid_t bi_meshid; |
| bool is_mesh_peer = FALSE, chan_matched = FALSE, found = FALSE; |
| struct wl_mesh_params peer_mesh_info; |
| |
| mutex_lock(&escan->usr_sync); |
| |
| /* Check for scan in progress */ |
| if (escan->escan_state == ESCAN_STATE_SCANING) { |
| ESCAN_ERROR(dev->name, "SIOCGIWSCAN GET still scanning\n"); |
| goto exit; |
| } |
| if (!escan->bss_list) { |
| ESCAN_ERROR(dev->name, "scan not ready\n"); |
| goto exit; |
| } |
| if (dev != escan->dev) { |
| ESCAN_ERROR(dev->name, "not my scan from %s\n", escan->dev->name); |
| goto exit; |
| } |
| |
| bss_list = escan->bss_list; |
| bi = next_bss(bss_list, bi); |
| ESCAN_SCAN(dev->name, "scanned AP/Mesh count (%d)\n", bss_list->count); |
| for_each_bss(bss_list, bi, i) |
| { |
| memset(&bi_meshid, 0, sizeof(bi_meshid)); |
| is_mesh_peer = FALSE; |
| bi_chan = wf_chspec_ctlchan( |
| wl_chspec_driver_to_host(escan->ioctl_ver, bi->chanspec)); |
| bi_rssi = MIN(dtoh16(bi->RSSI), RSSI_MAXVAL); |
| is_mesh_peer = wl_escan_meshid_ie(((u8*)bi)+bi->ie_offset, |
| bi->ie_length, &bi_meshid); |
| if (!(bi->capability & (DOT11_CAP_ESS|DOT11_CAP_IBSS)) && is_mesh_peer) { |
| bool meshid_matched = FALSE, sec_matched = FALSE, bi_sae = FALSE, |
| bss_found = FALSE, prefer = FALSE; |
| |
| if (cur_ssid->SSID_len && cur_ssid->SSID_len == bi_meshid.SSID_len && |
| !memcmp(cur_ssid->SSID, bi_meshid.SSID, bi_meshid.SSID_len)) |
| meshid_matched = TRUE; |
| |
| bi_sae = wl_escan_rsn_ie(((u8*)bi)+bi->ie_offset, bi->ie_length); |
| if (bi_sae == sae) |
| sec_matched = TRUE; |
| |
| bss_found = wl_escan_mesh_info_ie(dev, ((u8*)bi)+bi->ie_offset, bi->ie_length, |
| &peer_mesh_info); |
| if (meshid_matched && sec_matched && bss_found && |
| (cur_chan == bi_chan)) { |
| if (peer_mesh_info.hop_cnt < min_hop_cnt) { |
| memcpy(&mesh_info->master_bssid, &peer_mesh_info.master_bssid, |
| ETHER_ADDR_LEN); |
| mesh_info->master_channel = peer_mesh_info.master_channel; |
| mesh_info->hop_cnt = peer_mesh_info.hop_cnt; |
| memcpy(mesh_info->peer_bssid, peer_mesh_info.peer_bssid, |
| sizeof(peer_mesh_info.peer_bssid)); |
| min_hop_cnt = peer_mesh_info.hop_cnt; |
| prefer = TRUE; |
| chan_matched = TRUE; |
| found = TRUE; |
| } |
| } |
| else if (meshid_matched && sec_matched && bss_found && |
| (cur_chan != bi_chan) && !chan_matched) { |
| if (bi_rssi > max_rssi) { |
| memcpy(&mesh_info->master_bssid, &peer_mesh_info.master_bssid, |
| ETHER_ADDR_LEN); |
| mesh_info->master_channel = peer_mesh_info.master_channel; |
| mesh_info->hop_cnt = peer_mesh_info.hop_cnt; |
| memcpy(mesh_info->peer_bssid, peer_mesh_info.peer_bssid, |
| sizeof(peer_mesh_info.peer_bssid)); |
| max_rssi = bi_rssi; |
| prefer = TRUE; |
| found = TRUE; |
| } |
| } |
| |
| ESCAN_SCAN(dev->name, |
| "%s[Mesh] BSSID=%pM, channel=%d, RSSI=%d, sec=%s, " |
| "mbssid=%pM, mchannel=%d, hop=%d, pbssid=%pM, MeshID=\"%s\"\n", |
| prefer?"*":" ", &bi->BSSID, bi_chan, bi_rssi, bi_sae?"SAE":"OPEN", |
| &peer_mesh_info.master_bssid, peer_mesh_info.master_channel, |
| peer_mesh_info.hop_cnt, &peer_mesh_info.peer_bssid, bi_meshid.SSID); |
| } else { |
| ESCAN_SCAN(dev->name, |
| "[AP] BSSID=%pM, channel=%d, RSSI=%d, SSID=\"%s\"\n", |
| &bi->BSSID, bi_chan, bi_rssi, bi->SSID); |
| } |
| } |
| |
| exit: |
| mutex_unlock(&escan->usr_sync); |
| return found; |
| } |
| #endif /* WLMESH */ |
| |
| static void |
| wl_escan_deinit(struct net_device *dev, struct wl_escan_info *escan) |
| { |
| ESCAN_TRACE(dev->name, "Enter\n"); |
| |
| del_timer_sync(&escan->scan_timeout); |
| escan->escan_state = ESCAN_STATE_DOWN; |
| |
| #if defined(RSSIAVG) |
| wl_free_rssi_cache(&escan->g_rssi_cache_ctrl); |
| #endif |
| #if defined(BSSCACHE) |
| wl_free_bss_cache(&escan->g_bss_cache_ctrl); |
| #endif |
| } |
| |
| static s32 |
| wl_escan_init(struct net_device *dev, struct wl_escan_info *escan) |
| { |
| ESCAN_TRACE(dev->name, "Enter\n"); |
| |
| /* Init scan_timeout timer */ |
| init_timer_compat(&escan->scan_timeout, wl_escan_timeout, escan); |
| escan->escan_state = ESCAN_STATE_IDLE; |
| |
| return 0; |
| } |
| |
| void |
| wl_escan_down(struct net_device *dev) |
| { |
| struct dhd_pub *dhdp = dhd_get_pub(dev); |
| struct wl_escan_info *escan = dhdp->escan; |
| |
| ESCAN_TRACE(dev->name, "Enter\n"); |
| if (!escan) { |
| ESCAN_ERROR(dev->name, "escan is NULL\n"); |
| return; |
| } |
| |
| escan->scan_params_v2 = false; |
| |
| wl_escan_deinit(dev, escan); |
| } |
| |
| int |
| wl_escan_up(struct net_device *dev) |
| { |
| struct dhd_pub *dhdp = dhd_get_pub(dev); |
| struct wl_escan_info *escan = dhdp->escan; |
| u8 ioctl_buf[WLC_IOCTL_SMLEN]; |
| s32 val = 0; |
| int ret = -1; |
| |
| ESCAN_TRACE(dev->name, "Enter\n"); |
| if (!escan) { |
| ESCAN_ERROR(dev->name, "escan is NULL\n"); |
| return ret; |
| } |
| |
| ret = wl_escan_init(dev, escan); |
| if (ret) { |
| ESCAN_ERROR(dev->name, "wl_escan_init ret %d\n", ret); |
| return ret; |
| } |
| |
| if (!escan->ioctl_ver) { |
| val = 1; |
| if ((ret = wldev_ioctl(dev, WLC_GET_VERSION, &val, sizeof(int), false) < 0)) { |
| ESCAN_ERROR(dev->name, "WLC_GET_VERSION failed, ret=%d\n", ret); |
| return ret; |
| } |
| val = dtoh32(val); |
| if (val != WLC_IOCTL_VERSION && val != 1) { |
| ESCAN_ERROR(dev->name, |
| "Version mismatch, please upgrade. Got %d, expected %d or 1\n", |
| val, WLC_IOCTL_VERSION); |
| return ret; |
| } |
| escan->ioctl_ver = val; |
| } |
| |
| if ((ret = wldev_iovar_getbuf(dev, "scan_ver", NULL, 0, |
| ioctl_buf, sizeof(ioctl_buf), NULL)) == BCME_OK) { |
| ESCAN_TRACE(dev->name, "scan_params v2\n"); |
| /* use scan_params ver2 */ |
| escan->scan_params_v2 = true; |
| } else { |
| if (ret == BCME_UNSUPPORTED) { |
| ESCAN_TRACE(dev->name, "scan_ver, UNSUPPORTED\n"); |
| ret = BCME_OK; |
| } else { |
| ESCAN_ERROR(dev->name, "get scan_ver err(%d)\n", ret); |
| } |
| } |
| |
| return 0; |
| } |
| |
| int |
| wl_escan_event_dettach(struct net_device *dev, int ifidx) |
| { |
| struct dhd_pub *dhdp = dhd_get_pub(dev); |
| struct wl_escan_info *escan = dhdp->escan; |
| int ret = -1; |
| |
| if (!escan) { |
| ESCAN_ERROR(dev->name, "escan is NULL\n"); |
| return ret; |
| } |
| |
| if (ifidx < DHD_MAX_IFS) { |
| wl_ext_event_deregister(dev, dhdp, WLC_E_ESCAN_RESULT, wl_escan_ext_handler); |
| } |
| |
| return 0; |
| } |
| |
| int |
| wl_escan_event_attach(struct net_device *dev, int ifidx) |
| { |
| struct dhd_pub *dhdp = dhd_get_pub(dev); |
| struct wl_escan_info *escan = dhdp->escan; |
| int ret = -1; |
| |
| if (!escan) { |
| ESCAN_ERROR(dev->name, "escan is NULL\n"); |
| return ret; |
| } |
| |
| if (ifidx < DHD_MAX_IFS) { |
| ret = wl_ext_event_register(dev, dhdp, WLC_E_ESCAN_RESULT, wl_escan_ext_handler, |
| escan, PRIO_EVENT_ESCAN); |
| if (ret) { |
| ESCAN_ERROR(dev->name, "wl_ext_event_register err %d\n", ret); |
| } |
| } |
| |
| return ret; |
| } |
| |
| void |
| wl_escan_detach(struct net_device *dev) |
| { |
| struct dhd_pub *dhdp = dhd_get_pub(dev); |
| struct wl_escan_info *escan = dhdp->escan; |
| |
| ESCAN_TRACE(dev->name, "Enter\n"); |
| |
| if (!escan) |
| return; |
| |
| wl_escan_deinit(dev, escan); |
| if (escan->escan_ioctl_buf) { |
| kfree(escan->escan_ioctl_buf); |
| escan->escan_ioctl_buf = NULL; |
| } |
| wl_ext_event_deregister(dev, dhdp, WLC_E_ESCAN_RESULT, wl_escan_ext_handler); |
| |
| DHD_OS_PREFREE(dhdp, escan, sizeof(struct wl_escan_info)); |
| dhdp->escan = NULL; |
| } |
| |
| int |
| wl_escan_attach(struct net_device *dev) |
| { |
| struct dhd_pub *dhdp = dhd_get_pub(dev); |
| struct wl_escan_info *escan = NULL; |
| int ret = 0; |
| |
| ESCAN_TRACE(dev->name, "Enter\n"); |
| |
| escan = (struct wl_escan_info *)DHD_OS_PREALLOC(dhdp, |
| DHD_PREALLOC_WL_ESCAN, sizeof(struct wl_escan_info)); |
| if (!escan) |
| return -ENOMEM; |
| memset(escan, 0, sizeof(struct wl_escan_info)); |
| |
| dhdp->escan = escan; |
| |
| /* we only care about main interface so save a global here */ |
| escan->pub = dhdp; |
| escan->escan_state = ESCAN_STATE_DOWN; |
| |
| escan->escan_ioctl_buf = (void *)kzalloc(WLC_IOCTL_MAXLEN, GFP_KERNEL); |
| if (unlikely(!escan->escan_ioctl_buf)) { |
| ESCAN_ERROR(dev->name, "Ioctl buf alloc failed\n"); |
| ret = -ENOMEM; |
| goto exit; |
| } |
| ret = wl_escan_init(dev, escan); |
| if (ret) { |
| ESCAN_ERROR(dev->name, "wl_escan_init err %d\n", ret); |
| goto exit; |
| } |
| mutex_init(&escan->usr_sync); |
| |
| return 0; |
| |
| exit: |
| wl_escan_detach(dev); |
| return ret; |
| } |
| |
| #endif /* WL_ESCAN */ |
| |