diff options
Diffstat (limited to 'drivers/net/wireless/libertas/scan.c')
-rw-r--r-- | drivers/net/wireless/libertas/scan.c | 2044 |
1 files changed, 2044 insertions, 0 deletions
diff --git a/drivers/net/wireless/libertas/scan.c b/drivers/net/wireless/libertas/scan.c new file mode 100644 index 00000000000..e1870623895 --- /dev/null +++ b/drivers/net/wireless/libertas/scan.c @@ -0,0 +1,2044 @@ +/* -*- mode: C; tab-width: 4; indent-tabs-mode: nil -*- */ +/* vi: set expandtab shiftwidth=4 tabstop=4 textwidth=78: */ + +/** + * Functions implementing wlan scan IOCTL and firmware command APIs + * + * IOCTL handlers as well as command preperation and response routines + * for sending scan commands to the firmware. + */ +#include <linux/ctype.h> +#include <linux/if.h> +#include <linux/netdevice.h> +#include <linux/wireless.h> + +#include <net/ieee80211.h> +#include <net/iw_handler.h> + +#include "host.h" +#include "decl.h" +#include "dev.h" +#include "scan.h" + +//! Approximate amount of data needed to pass a scan result back to iwlist +#define MAX_SCAN_CELL_SIZE (IW_EV_ADDR_LEN \ + + IW_ESSID_MAX_SIZE \ + + IW_EV_UINT_LEN \ + + IW_EV_FREQ_LEN \ + + IW_EV_QUAL_LEN \ + + IW_ESSID_MAX_SIZE \ + + IW_EV_PARAM_LEN \ + + 40) /* 40 for WPAIE */ + +//! Memory needed to store a max sized channel List TLV for a firmware scan +#define CHAN_TLV_MAX_SIZE (sizeof(struct mrvlietypesheader) \ + + (MRVDRV_MAX_CHANNELS_PER_SCAN \ + * sizeof(struct chanscanparamset))) + +//! Memory needed to store a max number/size SSID TLV for a firmware scan +#define SSID_TLV_MAX_SIZE (1 * sizeof(struct mrvlietypes_ssidparamset)) + +//! Maximum memory needed for a wlan_scan_cmd_config with all TLVs at max +#define MAX_SCAN_CFG_ALLOC (sizeof(struct wlan_scan_cmd_config) \ + + sizeof(struct mrvlietypes_numprobes) \ + + CHAN_TLV_MAX_SIZE \ + + SSID_TLV_MAX_SIZE) + +//! The maximum number of channels the firmware can scan per command +#define MRVDRV_MAX_CHANNELS_PER_SCAN 14 + +/** + * @brief Number of channels to scan per firmware scan command issuance. + * + * Number restricted to prevent hitting the limit on the amount of scan data + * returned in a single firmware scan command. + */ +#define MRVDRV_CHANNELS_PER_SCAN_CMD 4 + +//! Scan time specified in the channel TLV for each channel for passive scans +#define MRVDRV_PASSIVE_SCAN_CHAN_TIME 100 + +//! Scan time specified in the channel TLV for each channel for active scans +#define MRVDRV_ACTIVE_SCAN_CHAN_TIME 100 + +//! Macro to enable/disable SSID checking before storing a scan table +#ifdef DISCARD_BAD_SSID +#define CHECK_SSID_IS_VALID(x) ssid_valid(&bssidEntry.ssid) +#else +#define CHECK_SSID_IS_VALID(x) 1 +#endif + +/** + * @brief Check if a scanned network compatible with the driver settings + * + * WEP WPA WPA2 ad-hoc encrypt Network + * enabled enabled enabled AES mode privacy WPA WPA2 Compatible + * 0 0 0 0 NONE 0 0 0 yes No security + * 1 0 0 0 NONE 1 0 0 yes Static WEP + * 0 1 0 0 x 1x 1 x yes WPA + * 0 0 1 0 x 1x x 1 yes WPA2 + * 0 0 0 1 NONE 1 0 0 yes Ad-hoc AES + * 0 0 0 0 !=NONE 1 0 0 yes Dynamic WEP + * + * + * @param adapter A pointer to wlan_adapter + * @param index Index in scantable to check against current driver settings + * @param mode Network mode: Infrastructure or IBSS + * + * @return Index in scantable, or error code if negative + */ +static int is_network_compatible(wlan_adapter * adapter, int index, int mode) +{ + ENTER(); + + if (adapter->scantable[index].inframode == mode) { + if (adapter->secinfo.WEPstatus == wlan802_11WEPdisabled + && !adapter->secinfo.WPAenabled + && !adapter->secinfo.WPA2enabled + && adapter->scantable[index].wpa_supplicant.wpa_ie[0] != + WPA_IE + && adapter->scantable[index].wpa2_supplicant.wpa_ie[0] != + WPA2_IE && adapter->secinfo.Encryptionmode == CIPHER_NONE + && !adapter->scantable[index].privacy) { + /* no security */ + LEAVE(); + return index; + } else if (adapter->secinfo.WEPstatus == wlan802_11WEPenabled + && !adapter->secinfo.WPAenabled + && !adapter->secinfo.WPA2enabled + && adapter->scantable[index].privacy) { + /* static WEP enabled */ + LEAVE(); + return index; + } else if (adapter->secinfo.WEPstatus == wlan802_11WEPdisabled + && adapter->secinfo.WPAenabled + && !adapter->secinfo.WPA2enabled + && (adapter->scantable[index].wpa_supplicant. + wpa_ie[0] + == WPA_IE) + /* privacy bit may NOT be set in some APs like LinkSys WRT54G + && adapter->scantable[index].privacy */ + ) { + /* WPA enabled */ + lbs_pr_debug(1, + "is_network_compatible() WPA: index=%d wpa_ie=%#x " + "wpa2_ie=%#x WEP=%s WPA=%s WPA2=%s Encmode=%#x " + "privacy=%#x\n", index, + adapter->scantable[index].wpa_supplicant. + wpa_ie[0], + adapter->scantable[index].wpa2_supplicant. + wpa_ie[0], + (adapter->secinfo.WEPstatus == + wlan802_11WEPenabled) ? "e" : "d", + (adapter->secinfo.WPAenabled) ? "e" : "d", + (adapter->secinfo.WPA2enabled) ? "e" : "d", + adapter->secinfo.Encryptionmode, + adapter->scantable[index].privacy); + LEAVE(); + return index; + } else if (adapter->secinfo.WEPstatus == wlan802_11WEPdisabled + && !adapter->secinfo.WPAenabled + && adapter->secinfo.WPA2enabled + && (adapter->scantable[index].wpa2_supplicant. + wpa_ie[0] + == WPA2_IE) + /* privacy bit may NOT be set in some APs like LinkSys WRT54G + && adapter->scantable[index].privacy */ + ) { + /* WPA2 enabled */ + lbs_pr_debug(1, + "is_network_compatible() WPA2: index=%d wpa_ie=%#x " + "wpa2_ie=%#x WEP=%s WPA=%s WPA2=%s Encmode=%#x " + "privacy=%#x\n", index, + adapter->scantable[index].wpa_supplicant. + wpa_ie[0], + adapter->scantable[index].wpa2_supplicant. + wpa_ie[0], + (adapter->secinfo.WEPstatus == + wlan802_11WEPenabled) ? "e" : "d", + (adapter->secinfo.WPAenabled) ? "e" : "d", + (adapter->secinfo.WPA2enabled) ? "e" : "d", + adapter->secinfo.Encryptionmode, + adapter->scantable[index].privacy); + LEAVE(); + return index; + } else if (adapter->secinfo.WEPstatus == wlan802_11WEPdisabled + && !adapter->secinfo.WPAenabled + && !adapter->secinfo.WPA2enabled + && (adapter->scantable[index].wpa_supplicant. + wpa_ie[0] + != WPA_IE) + && (adapter->scantable[index].wpa2_supplicant. + wpa_ie[0] + != WPA2_IE) + && adapter->secinfo.Encryptionmode != CIPHER_NONE + && adapter->scantable[index].privacy) { + /* dynamic WEP enabled */ + lbs_pr_debug(1, + "is_network_compatible() dynamic WEP: index=%d " + "wpa_ie=%#x wpa2_ie=%#x Encmode=%#x privacy=%#x\n", + index, + adapter->scantable[index].wpa_supplicant. + wpa_ie[0], + adapter->scantable[index].wpa2_supplicant. + wpa_ie[0], adapter->secinfo.Encryptionmode, + adapter->scantable[index].privacy); + LEAVE(); + return index; + } + + /* security doesn't match */ + lbs_pr_debug(1, + "is_network_compatible() FAILED: index=%d wpa_ie=%#x " + "wpa2_ie=%#x WEP=%s WPA=%s WPA2=%s Encmode=%#x privacy=%#x\n", + index, + adapter->scantable[index].wpa_supplicant.wpa_ie[0], + adapter->scantable[index].wpa2_supplicant.wpa_ie[0], + (adapter->secinfo.WEPstatus == + wlan802_11WEPenabled) ? "e" : "d", + (adapter->secinfo.WPAenabled) ? "e" : "d", + (adapter->secinfo.WPA2enabled) ? "e" : "d", + adapter->secinfo.Encryptionmode, + adapter->scantable[index].privacy); + LEAVE(); + return -ECONNREFUSED; + } + + /* mode doesn't match */ + LEAVE(); + return -ENETUNREACH; +} + +/** + * @brief This function validates a SSID as being able to be printed + * + * @param pssid SSID structure to validate + * + * @return TRUE or FALSE + */ +static u8 ssid_valid(struct WLAN_802_11_SSID *pssid) +{ + int ssididx; + + for (ssididx = 0; ssididx < pssid->ssidlength; ssididx++) { + if (!isprint(pssid->ssid[ssididx])) { + return 0; + } + } + + return 1; +} + +/** + * @brief Post process the scan table after a new scan command has completed + * + * Inspect each entry of the scan table and try to find an entry that + * matches our current associated/joined network from the scan. If + * one is found, update the stored copy of the bssdescriptor for our + * current network. + * + * Debug dump the current scan table contents if compiled accordingly. + * + * @param priv A pointer to wlan_private structure + * + * @return void + */ +static void wlan_scan_process_results(wlan_private * priv) +{ + wlan_adapter *adapter = priv->adapter; + int foundcurrent; + int i; + + foundcurrent = 0; + + if (adapter->connect_status == libertas_connected) { + /* try to find the current BSSID in the new scan list */ + for (i = 0; i < adapter->numinscantable; i++) { + if (!libertas_SSID_cmp(&adapter->scantable[i].ssid, + &adapter->curbssparams.ssid) && + !memcmp(adapter->curbssparams.bssid, + adapter->scantable[i].macaddress, + ETH_ALEN)) { + foundcurrent = 1; + } + } + + if (foundcurrent) { + /* Make a copy of current BSSID descriptor */ + memcpy(&adapter->curbssparams.bssdescriptor, + &adapter->scantable[i], + sizeof(adapter->curbssparams.bssdescriptor)); + } + } + + for (i = 0; i < adapter->numinscantable; i++) { + lbs_pr_debug(1, "Scan:(%02d) %02x:%02x:%02x:%02x:%02x:%02x, " + "RSSI[%03d], SSID[%s]\n", + i, + adapter->scantable[i].macaddress[0], + adapter->scantable[i].macaddress[1], + adapter->scantable[i].macaddress[2], + adapter->scantable[i].macaddress[3], + adapter->scantable[i].macaddress[4], + adapter->scantable[i].macaddress[5], + (s32) adapter->scantable[i].rssi, + adapter->scantable[i].ssid.ssid); + } +} + +/** + * @brief Create a channel list for the driver to scan based on region info + * + * Use the driver region/band information to construct a comprehensive list + * of channels to scan. This routine is used for any scan that is not + * provided a specific channel list to scan. + * + * @param priv A pointer to wlan_private structure + * @param scanchanlist Output parameter: resulting channel list to scan + * @param filteredscan Flag indicating whether or not a BSSID or SSID filter + * is being sent in the command to firmware. Used to + * increase the number of channels sent in a scan + * command and to disable the firmware channel scan + * filter. + * + * @return void + */ +static void wlan_scan_create_channel_list(wlan_private * priv, + struct chanscanparamset * scanchanlist, + u8 filteredscan) +{ + + wlan_adapter *adapter = priv->adapter; + struct region_channel *scanregion; + struct chan_freq_power *cfp; + int rgnidx; + int chanidx; + int nextchan; + u8 scantype; + + chanidx = 0; + + /* Set the default scan type to the user specified type, will later + * be changed to passive on a per channel basis if restricted by + * regulatory requirements (11d or 11h) + */ + scantype = adapter->scantype; + + for (rgnidx = 0; rgnidx < ARRAY_SIZE(adapter->region_channel); rgnidx++) { + if (priv->adapter->enable11d && + adapter->connect_status != libertas_connected) { + /* Scan all the supported chan for the first scan */ + if (!adapter->universal_channel[rgnidx].valid) + continue; + scanregion = &adapter->universal_channel[rgnidx]; + + /* clear the parsed_region_chan for the first scan */ + memset(&adapter->parsed_region_chan, 0x00, + sizeof(adapter->parsed_region_chan)); + } else { + if (!adapter->region_channel[rgnidx].valid) + continue; + scanregion = &adapter->region_channel[rgnidx]; + } + + for (nextchan = 0; + nextchan < scanregion->nrcfp; nextchan++, chanidx++) { + + cfp = scanregion->CFP + nextchan; + + if (priv->adapter->enable11d) { + scantype = + libertas_get_scan_type_11d(cfp->channel, + &adapter-> + parsed_region_chan); + } + + switch (scanregion->band) { + case BAND_B: + case BAND_G: + default: + scanchanlist[chanidx].radiotype = + cmd_scan_radio_type_bg; + break; + } + + if (scantype == cmd_scan_type_passive) { + scanchanlist[chanidx].maxscantime = + cpu_to_le16 + (MRVDRV_PASSIVE_SCAN_CHAN_TIME); + scanchanlist[chanidx].chanscanmode.passivescan = + 1; + } else { + scanchanlist[chanidx].maxscantime = + cpu_to_le16 + (MRVDRV_ACTIVE_SCAN_CHAN_TIME); + scanchanlist[chanidx].chanscanmode.passivescan = + 0; + } + + scanchanlist[chanidx].channumber = cfp->channel; + + if (filteredscan) { + scanchanlist[chanidx].chanscanmode. + disablechanfilt = 1; + } + } + } +} + +/** + * @brief Construct a wlan_scan_cmd_config structure to use in issue scan cmds + * + * Application layer or other functions can invoke wlan_scan_networks + * with a scan configuration supplied in a wlan_ioctl_user_scan_cfg struct. + * This structure is used as the basis of one or many wlan_scan_cmd_config + * commands that are sent to the command processing module and sent to + * firmware. + * + * Create a wlan_scan_cmd_config based on the following user supplied + * parameters (if present): + * - SSID filter + * - BSSID filter + * - Number of Probes to be sent + * - channel list + * + * If the SSID or BSSID filter is not present, disable/clear the filter. + * If the number of probes is not set, use the adapter default setting + * Qualify the channel + * + * @param priv A pointer to wlan_private structure + * @param puserscanin NULL or pointer to scan configuration parameters + * @param ppchantlvout Output parameter: Pointer to the start of the + * channel TLV portion of the output scan config + * @param pscanchanlist Output parameter: Pointer to the resulting channel + * list to scan + * @param pmaxchanperscan Output parameter: Number of channels to scan for + * each issuance of the firmware scan command + * @param pfilteredscan Output parameter: Flag indicating whether or not + * a BSSID or SSID filter is being sent in the + * command to firmware. Used to increase the number + * of channels sent in a scan command and to + * disable the firmware channel scan filter. + * @param pscancurrentonly Output parameter: Flag indicating whether or not + * we are only scanning our current active channel + * + * @return resulting scan configuration + */ +static struct wlan_scan_cmd_config * +wlan_scan_setup_scan_config(wlan_private * priv, + const struct wlan_ioctl_user_scan_cfg * puserscanin, + struct mrvlietypes_chanlistparamset ** ppchantlvout, + struct chanscanparamset * pscanchanlist, + int *pmaxchanperscan, + u8 * pfilteredscan, + u8 * pscancurrentonly) +{ + wlan_adapter *adapter = priv->adapter; + const u8 zeromac[ETH_ALEN] = { 0, 0, 0, 0, 0, 0 }; + struct mrvlietypes_numprobes *pnumprobestlv; + struct mrvlietypes_ssidparamset *pssidtlv; + struct wlan_scan_cmd_config * pscancfgout = NULL; + u8 *ptlvpos; + u16 numprobes; + u16 ssidlen; + int chanidx; + int scantype; + int scandur; + int channel; + int radiotype; + + pscancfgout = kzalloc(MAX_SCAN_CFG_ALLOC, GFP_KERNEL); + if (pscancfgout == NULL) + goto out; + + /* The tlvbufferlen is calculated for each scan command. The TLVs added + * in this routine will be preserved since the routine that sends + * the command will append channelTLVs at *ppchantlvout. The difference + * between the *ppchantlvout and the tlvbuffer start will be used + * to calculate the size of anything we add in this routine. + */ + pscancfgout->tlvbufferlen = 0; + + /* Running tlv pointer. Assigned to ppchantlvout at end of function + * so later routines know where channels can be added to the command buf + */ + ptlvpos = pscancfgout->tlvbuffer; + + /* + * Set the initial scan paramters for progressive scanning. If a specific + * BSSID or SSID is used, the number of channels in the scan command + * will be increased to the absolute maximum + */ + *pmaxchanperscan = MRVDRV_CHANNELS_PER_SCAN_CMD; + + /* Initialize the scan as un-filtered by firmware, set to TRUE below if + * a SSID or BSSID filter is sent in the command + */ + *pfilteredscan = 0; + + /* Initialize the scan as not being only on the current channel. If + * the channel list is customized, only contains one channel, and + * is the active channel, this is set true and data flow is not halted. + */ + *pscancurrentonly = 0; + + if (puserscanin) { + + /* Set the bss type scan filter, use adapter setting if unset */ + pscancfgout->bsstype = + (puserscanin->bsstype ? puserscanin->bsstype : adapter-> + scanmode); + + /* Set the number of probes to send, use adapter setting if unset */ + numprobes = (puserscanin->numprobes ? puserscanin->numprobes : + adapter->scanprobes); + + /* + * Set the BSSID filter to the incoming configuration, + * if non-zero. If not set, it will remain disabled (all zeros). + */ + memcpy(pscancfgout->specificBSSID, + puserscanin->specificBSSID, + sizeof(pscancfgout->specificBSSID)); + + ssidlen = strlen(puserscanin->specificSSID); + + if (ssidlen) { + pssidtlv = + (struct mrvlietypes_ssidparamset *) pscancfgout-> + tlvbuffer; + pssidtlv->header.type = cpu_to_le16(TLV_TYPE_SSID); + pssidtlv->header.len = cpu_to_le16(ssidlen); + memcpy(pssidtlv->ssid, puserscanin->specificSSID, + ssidlen); + ptlvpos += sizeof(pssidtlv->header) + ssidlen; + } + + /* + * The default number of channels sent in the command is low to + * ensure the response buffer from the firmware does not truncate + * scan results. That is not an issue with an SSID or BSSID + * filter applied to the scan results in the firmware. + */ + if (ssidlen || (memcmp(pscancfgout->specificBSSID, + &zeromac, sizeof(zeromac)) != 0)) { + *pmaxchanperscan = MRVDRV_MAX_CHANNELS_PER_SCAN; + *pfilteredscan = 1; + } + } else { + pscancfgout->bsstype = adapter->scanmode; + numprobes = adapter->scanprobes; + } + + /* If the input config or adapter has the number of Probes set, add tlv */ + if (numprobes) { + pnumprobestlv = (struct mrvlietypes_numprobes *) ptlvpos; + pnumprobestlv->header.type = + cpu_to_le16(TLV_TYPE_NUMPROBES); + pnumprobestlv->header.len = sizeof(pnumprobestlv->numprobes); + pnumprobestlv->numprobes = cpu_to_le16(numprobes); + + ptlvpos += + sizeof(pnumprobestlv->header) + pnumprobestlv->header.len; + + pnumprobestlv->header.len = + cpu_to_le16(pnumprobestlv->header.len); + } + + /* + * Set the output for the channel TLV to the address in the tlv buffer + * past any TLVs that were added in this fuction (SSID, numprobes). + * channel TLVs will be added past this for each scan command, preserving + * the TLVs that were previously added. + */ + *ppchantlvout = (struct mrvlietypes_chanlistparamset *) ptlvpos; + + if (puserscanin && puserscanin->chanlist[0].channumber) { + + lbs_pr_debug(1, "Scan: Using supplied channel list\n"); + + for (chanidx = 0; + chanidx < WLAN_IOCTL_USER_SCAN_CHAN_MAX + && puserscanin->chanlist[chanidx].channumber; chanidx++) { + + channel = puserscanin->chanlist[chanidx].channumber; + (pscanchanlist + chanidx)->channumber = channel; + + radiotype = puserscanin->chanlist[chanidx].radiotype; + (pscanchanlist + chanidx)->radiotype = radiotype; + + scantype = puserscanin->chanlist[chanidx].scantype; + + if (scantype == cmd_scan_type_passive) { + (pscanchanlist + + chanidx)->chanscanmode.passivescan = 1; + } else { + (pscanchanlist + + chanidx)->chanscanmode.passivescan = 0; + } + + if (puserscanin->chanlist[chanidx].scantime) { + scandur = + puserscanin->chanlist[chanidx].scantime; + } else { + if (scantype == cmd_scan_type_passive) { + scandur = MRVDRV_PASSIVE_SCAN_CHAN_TIME; + } else { + scandur = MRVDRV_ACTIVE_SCAN_CHAN_TIME; + } + } + + (pscanchanlist + chanidx)->minscantime = + cpu_to_le16(scandur); + (pscanchanlist + chanidx)->maxscantime = + cpu_to_le16(scandur); + } + + /* Check if we are only scanning the current channel */ + if ((chanidx == 1) && (puserscanin->chanlist[0].channumber + == + priv->adapter->curbssparams.channel)) { + *pscancurrentonly = 1; + lbs_pr_debug(1, "Scan: Scanning current channel only"); + } + + } else { + lbs_pr_debug(1, "Scan: Creating full region channel list\n"); + wlan_scan_create_channel_list(priv, pscanchanlist, + *pfilteredscan); + } + +out: + return pscancfgout; +} + +/** + * @brief Construct and send multiple scan config commands to the firmware + * + * Previous routines have created a wlan_scan_cmd_config with any requested + * TLVs. This function splits the channel TLV into maxchanperscan lists + * and sends the portion of the channel TLV along with the other TLVs + * to the wlan_cmd routines for execution in the firmware. + * + * @param priv A pointer to wlan_private structure + * @param maxchanperscan Maximum number channels to be included in each + * scan command sent to firmware + * @param filteredscan Flag indicating whether or not a BSSID or SSID + * filter is being used for the firmware command + * scan command sent to firmware + * @param pscancfgout Scan configuration used for this scan. + * @param pchantlvout Pointer in the pscancfgout where the channel TLV + * should start. This is past any other TLVs that + * must be sent down in each firmware command. + * @param pscanchanlist List of channels to scan in maxchanperscan segments + * + * @return 0 or error return otherwise + */ +static int wlan_scan_channel_list(wlan_private * priv, + int maxchanperscan, + u8 filteredscan, + struct wlan_scan_cmd_config * pscancfgout, + struct mrvlietypes_chanlistparamset * pchantlvout, + struct chanscanparamset * pscanchanlist) +{ + struct chanscanparamset *ptmpchan; + struct chanscanparamset *pstartchan; + u8 scanband; + int doneearly; + int tlvidx; + int ret = 0; + + ENTER(); + + if (pscancfgout == 0 || pchantlvout == 0 || pscanchanlist == 0) { + lbs_pr_debug(1, "Scan: Null detect: %p, %p, %p\n", + pscancfgout, pchantlvout, pscanchanlist); + return -1; + } + + pchantlvout->header.type = cpu_to_le16(TLV_TYPE_CHANLIST); + + /* Set the temp channel struct pointer to the start of the desired list */ + ptmpchan = pscanchanlist; + + /* Loop through the desired channel list, sending a new firmware scan + * commands for each maxchanperscan channels (or for 1,6,11 individually + * if configured accordingly) + */ + while (ptmpchan->channumber) { + + tlvidx = 0; + pchantlvout->header.len = 0; + scanband = ptmpchan->radiotype; + pstartchan = ptmpchan; + doneearly = 0; + + /* Construct the channel TLV for the scan command. Continue to + * insert channel TLVs until: + * - the tlvidx hits the maximum configured per scan command + * - the next channel to insert is 0 (end of desired channel list) + * - doneearly is set (controlling individual scanning of 1,6,11) + */ + while (tlvidx < maxchanperscan && ptmpchan->channumber + && !doneearly) { + + lbs_pr_debug(1, + "Scan: Chan(%3d), Radio(%d), mode(%d,%d), Dur(%d)\n", + ptmpchan->channumber, ptmpchan->radiotype, + ptmpchan->chanscanmode.passivescan, + ptmpchan->chanscanmode.disablechanfilt, + ptmpchan->maxscantime); + + /* Copy the current channel TLV to the command being prepared */ + memcpy(pchantlvout->chanscanparam + tlvidx, + ptmpchan, sizeof(pchantlvout->chanscanparam)); + + /* Increment the TLV header length by the size appended */ + pchantlvout->header.len += + sizeof(pchantlvout->chanscanparam); + + /* + * The tlv buffer length is set to the number of bytes of the + * between the channel tlv pointer and the start of the + * tlv buffer. This compensates for any TLVs that were appended + * before the channel list. + */ + pscancfgout->tlvbufferlen = ((u8 *) pchantlvout + - pscancfgout->tlvbuffer); + + /* Add the size of the channel tlv header and the data length */ + pscancfgout->tlvbufferlen += + (sizeof(pchantlvout->header) + + pchantlvout->header.len); + + /* Increment the index to the channel tlv we are constructing */ + tlvidx++; + + doneearly = 0; + + /* Stop the loop if the *current* channel is in the 1,6,11 set + * and we are not filtering on a BSSID or SSID. + */ + if (!filteredscan && (ptmpchan->channumber == 1 + || ptmpchan->channumber == 6 + || ptmpchan->channumber == 11)) { + doneearly = 1; + } + + /* Increment the tmp pointer to the next channel to be scanned */ + ptmpchan++; + + /* Stop the loop if the *next* channel is in the 1,6,11 set. + * This will cause it to be the only channel scanned on the next + * interation + */ + if (!filteredscan && (ptmpchan->channumber == 1 + || ptmpchan->channumber == 6 + || ptmpchan->channumber == 11)) { + doneearly = 1; + } + } + + /* Send the scan command to the firmware with the specified cfg */ + ret = libertas_prepare_and_send_command(priv, cmd_802_11_scan, 0, + 0, 0, pscancfgout); + } + + LEAVE(); + return ret; +} + +/** + * @brief Internal function used to start a scan based on an input config + * + * Use the input user scan configuration information when provided in + * order to send the appropriate scan commands to firmware to populate or + * update the internal driver scan table + * + * @param priv A pointer to wlan_private structure + * @param puserscanin Pointer to the input configuration for the requested + * scan. + * + * @return 0 or < 0 if error + */ +int wlan_scan_networks(wlan_private * priv, + const struct wlan_ioctl_user_scan_cfg * puserscanin) +{ + wlan_adapter *adapter = priv->adapter; + struct mrvlietypes_chanlistparamset *pchantlvout; + struct chanscanparamset * scan_chan_list = NULL; + struct wlan_scan_cmd_config * scan_cfg = NULL; + u8 keeppreviousscan; + u8 filteredscan; + u8 scancurrentchanonly; + int maxchanperscan; + int ret; + + ENTER(); + + scan_chan_list = kzalloc(sizeof(struct chanscanparamset) * + WLAN_IOCTL_USER_SCAN_CHAN_MAX, GFP_KERNEL); + if (scan_chan_list == NULL) { + ret = -ENOMEM; + goto out; + } + + scan_cfg = wlan_scan_setup_scan_config(priv, + puserscanin, + &pchantlvout, + scan_chan_list, + &maxchanperscan, + &filteredscan, + &scancurrentchanonly); + if (scan_cfg == NULL) { + ret = -ENOMEM; + goto out; + } + + keeppreviousscan = 0; + + if (puserscanin) { + keeppreviousscan = puserscanin->keeppreviousscan; + } + + if (!keeppreviousscan) { + memset(adapter->scantable, 0x00, + sizeof(struct bss_descriptor) * MRVDRV_MAX_BSSID_LIST); + adapter->numinscantable = 0; + } + + /* Keep the data path active if we are only scanning our current channel */ + if (!scancurrentchanonly) { + netif_stop_queue(priv->wlan_dev.netdev); + netif_carrier_off(priv->wlan_dev.netdev); + } + + ret = wlan_scan_channel_list(priv, + maxchanperscan, + filteredscan, + scan_cfg, + pchantlvout, + scan_chan_list); + + /* Process the resulting scan table: + * - Remove any bad ssids + * - Update our current BSS information from scan data + */ + wlan_scan_process_results(priv); + + if (priv->adapter->connect_status == libertas_connected) { + netif_carrier_on(priv->wlan_dev.netdev); + netif_wake_queue(priv->wlan_dev.netdev); + } + +out: + if (scan_cfg) + kfree(scan_cfg); + + if (scan_chan_list) + kfree(scan_chan_list); + + LEAVE(); + return ret; +} + +/** + * @brief Inspect the scan response buffer for pointers to expected TLVs + * + * TLVs can be included at the end of the scan response BSS information. + * Parse the data in the buffer for pointers to TLVs that can potentially + * be passed back in the response + * + * @param ptlv Pointer to the start of the TLV buffer to parse + * @param tlvbufsize size of the TLV buffer + * @param ptsftlv Output parameter: Pointer to the TSF TLV if found + * + * @return void + */ +static +void wlan_ret_802_11_scan_get_tlv_ptrs(struct mrvlietypes_data * ptlv, + int tlvbufsize, + struct mrvlietypes_tsftimestamp ** ptsftlv) +{ + struct mrvlietypes_data *pcurrenttlv; + int tlvbufleft; + u16 tlvtype; + u16 tlvlen; + + pcurrenttlv = ptlv; + tlvbufleft = tlvbufsize; + *ptsftlv = NULL; + + lbs_pr_debug(1, "SCAN_RESP: tlvbufsize = %d\n", tlvbufsize); + lbs_dbg_hex("SCAN_RESP: TLV Buf", (u8 *) ptlv, tlvbufsize); + + while (tlvbufleft >= sizeof(struct mrvlietypesheader)) { + tlvtype = le16_to_cpu(pcurrenttlv->header.type); + tlvlen = le16_to_cpu(pcurrenttlv->header.len); + + switch (tlvtype) { + case TLV_TYPE_TSFTIMESTAMP: + *ptsftlv = (struct mrvlietypes_tsftimestamp *) pcurrenttlv; + break; + + default: + lbs_pr_debug(1, "SCAN_RESP: Unhandled TLV = %d\n", + tlvtype); + /* Give up, this seems corrupted */ + return; + } /* switch */ + + tlvbufleft -= (sizeof(ptlv->header) + tlvlen); + pcurrenttlv = + (struct mrvlietypes_data *) (pcurrenttlv->Data + tlvlen); + } /* while */ +} + +/** + * @brief Interpret a BSS scan response returned from the firmware + * + * Parse the various fixed fields and IEs passed back for a a BSS probe + * response or beacon from the scan command. Record information as needed + * in the scan table struct bss_descriptor for that entry. + * + * @param pBSSIDEntry Output parameter: Pointer to the BSS Entry + * + * @return 0 or -1 + */ +static int InterpretBSSDescriptionWithIE(struct bss_descriptor * pBSSEntry, + u8 ** pbeaconinfo, int *bytesleft) +{ + enum ieeetypes_elementid elemID; + struct ieeetypes_fhparamset *pFH; + struct ieeetypes_dsparamset *pDS; + struct ieeetypes_cfparamset *pCF; + struct ieeetypes_ibssparamset *pibss; + struct ieeetypes_capinfo *pcap; + struct WLAN_802_11_FIXED_IEs fixedie; + u8 *pcurrentptr; + u8 *pRate; + u8 elemlen; + u8 bytestocopy; + u8 ratesize; + u16 beaconsize; + u8 founddatarateie; + int bytesleftforcurrentbeacon; + + struct WPA_SUPPLICANT *pwpa_supplicant; + struct WPA_SUPPLICANT *pwpa2_supplicant; + struct IE_WPA *pIe; + const u8 oui01[4] = { 0x00, 0x50, 0xf2, 0x01 }; + + struct ieeetypes_countryinfoset *pcountryinfo; + + ENTER(); + + founddatarateie = 0; + ratesize = 0; + beaconsize = 0; + + if (*bytesleft >= sizeof(beaconsize)) { + /* Extract & convert beacon size from the command buffer */ + memcpy(&beaconsize, *pbeaconinfo, sizeof(beaconsize)); + beaconsize = le16_to_cpu(beaconsize); + *bytesleft -= sizeof(beaconsize); + *pbeaconinfo += sizeof(beaconsize); + } + + if (beaconsize == 0 || beaconsize > *bytesleft) { + + *pbeaconinfo += *bytesleft; + *bytesleft = 0; + + return -1; + } + + /* Initialize the current working beacon pointer for this BSS iteration */ + pcurrentptr = *pbeaconinfo; + + /* Advance the return beacon pointer past the current beacon */ + *pbeaconinfo += beaconsize; + *bytesleft -= beaconsize; + + bytesleftforcurrentbeacon = beaconsize; + + pwpa_supplicant = &pBSSEntry->wpa_supplicant; + pwpa2_supplicant = &pBSSEntry->wpa2_supplicant; + + memcpy(pBSSEntry->macaddress, pcurrentptr, ETH_ALEN); + lbs_pr_debug(1, "InterpretIE: AP MAC Addr-%x:%x:%x:%x:%x:%x\n", + pBSSEntry->macaddress[0], pBSSEntry->macaddress[1], + pBSSEntry->macaddress[2], pBSSEntry->macaddress[3], + pBSSEntry->macaddress[4], pBSSEntry->macaddress[5]); + + pcurrentptr += ETH_ALEN; + bytesleftforcurrentbeacon -= ETH_ALEN; + + if (bytesleftforcurrentbeacon < 12) { + lbs_pr_debug(1, "InterpretIE: Not enough bytes left\n"); + return -1; + } + + /* + * next 4 fields are RSSI, time stamp, beacon interval, + * and capability information + */ + + /* RSSI is 1 byte long */ + pBSSEntry->rssi = le32_to_cpu((long)(*pcurrentptr)); + lbs_pr_debug(1, "InterpretIE: RSSI=%02X\n", *pcurrentptr); + pcurrentptr += 1; + bytesleftforcurrentbeacon -= 1; + + /* time stamp is 8 bytes long */ + memcpy(fixedie.timestamp, pcurrentptr, 8); + memcpy(pBSSEntry->timestamp, pcurrentptr, 8); + pcurrentptr += 8; + bytesleftforcurrentbeacon -= 8; + + /* beacon interval is 2 bytes long */ + memcpy(&fixedie.beaconinterval, pcurrentptr, 2); + pBSSEntry->beaconperiod = le16_to_cpu(fixedie.beaconinterval); + pcurrentptr += 2; + bytesleftforcurrentbeacon -= 2; + + /* capability information is 2 bytes long */ + memcpy(&fixedie.capabilities, pcurrentptr, 2); + lbs_pr_debug(1, "InterpretIE: fixedie.capabilities=0x%X\n", + fixedie.capabilities); + fixedie.capabilities = le16_to_cpu(fixedie.capabilities); + pcap = (struct ieeetypes_capinfo *) & fixedie.capabilities; + memcpy(&pBSSEntry->cap, pcap, sizeof(struct ieeetypes_capinfo)); + pcurrentptr += 2; + bytesleftforcurrentbeacon -= 2; + + /* rest of the current buffer are IE's */ + lbs_pr_debug(1, "InterpretIE: IElength for this AP = %d\n", + bytesleftforcurrentbeacon); + + lbs_dbg_hex("InterpretIE: IE info", (u8 *) pcurrentptr, + bytesleftforcurrentbeacon); + + if (pcap->privacy) { + lbs_pr_debug(1, "InterpretIE: AP WEP enabled\n"); + pBSSEntry->privacy = wlan802_11privfilter8021xWEP; + } else { + pBSSEntry->privacy = wlan802_11privfilteracceptall; + } + + if (pcap->ibss == 1) { + pBSSEntry->inframode = wlan802_11ibss; + } else { + pBSSEntry->inframode = wlan802_11infrastructure; + } + + /* process variable IE */ + while (bytesleftforcurrentbeacon >= 2) { + elemID = (enum ieeetypes_elementid) (*((u8 *) pcurrentptr)); + elemlen = *((u8 *) pcurrentptr + 1); + + if (bytesleftforcurrentbeacon < elemlen) { + lbs_pr_debug(1, "InterpretIE: error in processing IE, " + "bytes left < IE length\n"); + bytesleftforcurrentbeacon = 0; + continue; + } + + switch (elemID) { + + case SSID: + pBSSEntry->ssid.ssidlength = elemlen; + memcpy(pBS |