162306a36Sopenharmony_ci/* Helpers for managing scan queues 262306a36Sopenharmony_ci * 362306a36Sopenharmony_ci * See copyright notice in main.c 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci#include <linux/gfp.h> 762306a36Sopenharmony_ci#include <linux/kernel.h> 862306a36Sopenharmony_ci#include <linux/string.h> 962306a36Sopenharmony_ci#include <linux/ieee80211.h> 1062306a36Sopenharmony_ci#include <net/cfg80211.h> 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include "hermes.h" 1362306a36Sopenharmony_ci#include "orinoco.h" 1462306a36Sopenharmony_ci#include "main.h" 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci#include "scan.h" 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#define ZERO_DBM_OFFSET 0x95 1962306a36Sopenharmony_ci#define MAX_SIGNAL_LEVEL 0x8A 2062306a36Sopenharmony_ci#define MIN_SIGNAL_LEVEL 0x2F 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci#define SIGNAL_TO_DBM(x) \ 2362306a36Sopenharmony_ci (clamp_t(s32, (x), MIN_SIGNAL_LEVEL, MAX_SIGNAL_LEVEL) \ 2462306a36Sopenharmony_ci - ZERO_DBM_OFFSET) 2562306a36Sopenharmony_ci#define SIGNAL_TO_MBM(x) (SIGNAL_TO_DBM(x) * 100) 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_cistatic int symbol_build_supp_rates(u8 *buf, const __le16 *rates) 2862306a36Sopenharmony_ci{ 2962306a36Sopenharmony_ci int i; 3062306a36Sopenharmony_ci u8 rate; 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci buf[0] = WLAN_EID_SUPP_RATES; 3362306a36Sopenharmony_ci for (i = 0; i < 5; i++) { 3462306a36Sopenharmony_ci rate = le16_to_cpu(rates[i]); 3562306a36Sopenharmony_ci /* NULL terminated */ 3662306a36Sopenharmony_ci if (rate == 0x0) 3762306a36Sopenharmony_ci break; 3862306a36Sopenharmony_ci buf[i + 2] = rate; 3962306a36Sopenharmony_ci } 4062306a36Sopenharmony_ci buf[1] = i; 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci return i + 2; 4362306a36Sopenharmony_ci} 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_cistatic int prism_build_supp_rates(u8 *buf, const u8 *rates) 4662306a36Sopenharmony_ci{ 4762306a36Sopenharmony_ci int i; 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci buf[0] = WLAN_EID_SUPP_RATES; 5062306a36Sopenharmony_ci for (i = 0; i < 8; i++) { 5162306a36Sopenharmony_ci /* NULL terminated */ 5262306a36Sopenharmony_ci if (rates[i] == 0x0) 5362306a36Sopenharmony_ci break; 5462306a36Sopenharmony_ci buf[i + 2] = rates[i]; 5562306a36Sopenharmony_ci } 5662306a36Sopenharmony_ci buf[1] = i; 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci /* We might still have another 2 rates, which need to go in 5962306a36Sopenharmony_ci * extended supported rates */ 6062306a36Sopenharmony_ci if (i == 8 && rates[i] > 0) { 6162306a36Sopenharmony_ci buf[10] = WLAN_EID_EXT_SUPP_RATES; 6262306a36Sopenharmony_ci for (; i < 10; i++) { 6362306a36Sopenharmony_ci /* NULL terminated */ 6462306a36Sopenharmony_ci if (rates[i] == 0x0) 6562306a36Sopenharmony_ci break; 6662306a36Sopenharmony_ci buf[i + 2] = rates[i]; 6762306a36Sopenharmony_ci } 6862306a36Sopenharmony_ci buf[11] = i - 8; 6962306a36Sopenharmony_ci } 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci return (i < 8) ? i + 2 : i + 4; 7262306a36Sopenharmony_ci} 7362306a36Sopenharmony_ci 7462306a36Sopenharmony_cistatic void orinoco_add_hostscan_result(struct orinoco_private *priv, 7562306a36Sopenharmony_ci const union hermes_scan_info *bss) 7662306a36Sopenharmony_ci{ 7762306a36Sopenharmony_ci struct wiphy *wiphy = priv_to_wiphy(priv); 7862306a36Sopenharmony_ci struct ieee80211_channel *channel; 7962306a36Sopenharmony_ci struct cfg80211_bss *cbss; 8062306a36Sopenharmony_ci u8 *ie; 8162306a36Sopenharmony_ci u8 ie_buf[46]; 8262306a36Sopenharmony_ci u64 timestamp; 8362306a36Sopenharmony_ci s32 signal; 8462306a36Sopenharmony_ci u16 capability; 8562306a36Sopenharmony_ci u16 beacon_interval; 8662306a36Sopenharmony_ci int ie_len; 8762306a36Sopenharmony_ci int freq; 8862306a36Sopenharmony_ci int len; 8962306a36Sopenharmony_ci 9062306a36Sopenharmony_ci len = le16_to_cpu(bss->a.essid_len); 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci /* Reconstruct SSID and bitrate IEs to pass up */ 9362306a36Sopenharmony_ci ie_buf[0] = WLAN_EID_SSID; 9462306a36Sopenharmony_ci ie_buf[1] = len; 9562306a36Sopenharmony_ci memcpy(&ie_buf[2], bss->a.essid, len); 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci ie = ie_buf + len + 2; 9862306a36Sopenharmony_ci ie_len = ie_buf[1] + 2; 9962306a36Sopenharmony_ci switch (priv->firmware_type) { 10062306a36Sopenharmony_ci case FIRMWARE_TYPE_SYMBOL: 10162306a36Sopenharmony_ci ie_len += symbol_build_supp_rates(ie, bss->s.rates); 10262306a36Sopenharmony_ci break; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci case FIRMWARE_TYPE_INTERSIL: 10562306a36Sopenharmony_ci ie_len += prism_build_supp_rates(ie, bss->p.rates); 10662306a36Sopenharmony_ci break; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci case FIRMWARE_TYPE_AGERE: 10962306a36Sopenharmony_ci default: 11062306a36Sopenharmony_ci break; 11162306a36Sopenharmony_ci } 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_ci freq = ieee80211_channel_to_frequency( 11462306a36Sopenharmony_ci le16_to_cpu(bss->a.channel), NL80211_BAND_2GHZ); 11562306a36Sopenharmony_ci channel = ieee80211_get_channel(wiphy, freq); 11662306a36Sopenharmony_ci if (!channel) { 11762306a36Sopenharmony_ci printk(KERN_DEBUG "Invalid channel designation %04X(%04X)", 11862306a36Sopenharmony_ci bss->a.channel, freq); 11962306a36Sopenharmony_ci return; /* Then ignore it for now */ 12062306a36Sopenharmony_ci } 12162306a36Sopenharmony_ci timestamp = 0; 12262306a36Sopenharmony_ci capability = le16_to_cpu(bss->a.capabilities); 12362306a36Sopenharmony_ci beacon_interval = le16_to_cpu(bss->a.beacon_interv); 12462306a36Sopenharmony_ci signal = SIGNAL_TO_MBM(le16_to_cpu(bss->a.level)); 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci cbss = cfg80211_inform_bss(wiphy, channel, CFG80211_BSS_FTYPE_UNKNOWN, 12762306a36Sopenharmony_ci bss->a.bssid, timestamp, capability, 12862306a36Sopenharmony_ci beacon_interval, ie_buf, ie_len, signal, 12962306a36Sopenharmony_ci GFP_KERNEL); 13062306a36Sopenharmony_ci cfg80211_put_bss(wiphy, cbss); 13162306a36Sopenharmony_ci} 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_civoid orinoco_add_extscan_result(struct orinoco_private *priv, 13462306a36Sopenharmony_ci struct agere_ext_scan_info *bss, 13562306a36Sopenharmony_ci size_t len) 13662306a36Sopenharmony_ci{ 13762306a36Sopenharmony_ci struct wiphy *wiphy = priv_to_wiphy(priv); 13862306a36Sopenharmony_ci struct ieee80211_channel *channel; 13962306a36Sopenharmony_ci struct cfg80211_bss *cbss; 14062306a36Sopenharmony_ci const u8 *ie; 14162306a36Sopenharmony_ci u64 timestamp; 14262306a36Sopenharmony_ci s32 signal; 14362306a36Sopenharmony_ci u16 capability; 14462306a36Sopenharmony_ci u16 beacon_interval; 14562306a36Sopenharmony_ci size_t ie_len; 14662306a36Sopenharmony_ci int chan, freq; 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci ie_len = len - sizeof(*bss); 14962306a36Sopenharmony_ci ie = cfg80211_find_ie(WLAN_EID_DS_PARAMS, bss->data, ie_len); 15062306a36Sopenharmony_ci chan = ie ? ie[2] : 0; 15162306a36Sopenharmony_ci freq = ieee80211_channel_to_frequency(chan, NL80211_BAND_2GHZ); 15262306a36Sopenharmony_ci channel = ieee80211_get_channel(wiphy, freq); 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci timestamp = le64_to_cpu(bss->timestamp); 15562306a36Sopenharmony_ci capability = le16_to_cpu(bss->capabilities); 15662306a36Sopenharmony_ci beacon_interval = le16_to_cpu(bss->beacon_interval); 15762306a36Sopenharmony_ci ie = bss->data; 15862306a36Sopenharmony_ci signal = SIGNAL_TO_MBM(bss->level); 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci cbss = cfg80211_inform_bss(wiphy, channel, CFG80211_BSS_FTYPE_UNKNOWN, 16162306a36Sopenharmony_ci bss->bssid, timestamp, capability, 16262306a36Sopenharmony_ci beacon_interval, ie, ie_len, signal, 16362306a36Sopenharmony_ci GFP_KERNEL); 16462306a36Sopenharmony_ci cfg80211_put_bss(wiphy, cbss); 16562306a36Sopenharmony_ci} 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_civoid orinoco_add_hostscan_results(struct orinoco_private *priv, 16862306a36Sopenharmony_ci unsigned char *buf, 16962306a36Sopenharmony_ci size_t len) 17062306a36Sopenharmony_ci{ 17162306a36Sopenharmony_ci int offset; /* In the scan data */ 17262306a36Sopenharmony_ci size_t atom_len; 17362306a36Sopenharmony_ci bool abort = false; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci switch (priv->firmware_type) { 17662306a36Sopenharmony_ci case FIRMWARE_TYPE_AGERE: 17762306a36Sopenharmony_ci atom_len = sizeof(struct agere_scan_apinfo); 17862306a36Sopenharmony_ci offset = 0; 17962306a36Sopenharmony_ci break; 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci case FIRMWARE_TYPE_SYMBOL: 18262306a36Sopenharmony_ci /* Lack of documentation necessitates this hack. 18362306a36Sopenharmony_ci * Different firmwares have 68 or 76 byte long atoms. 18462306a36Sopenharmony_ci * We try modulo first. If the length divides by both, 18562306a36Sopenharmony_ci * we check what would be the channel in the second 18662306a36Sopenharmony_ci * frame for a 68-byte atom. 76-byte atoms have 0 there. 18762306a36Sopenharmony_ci * Valid channel cannot be 0. */ 18862306a36Sopenharmony_ci if (len % 76) 18962306a36Sopenharmony_ci atom_len = 68; 19062306a36Sopenharmony_ci else if (len % 68) 19162306a36Sopenharmony_ci atom_len = 76; 19262306a36Sopenharmony_ci else if (len >= 1292 && buf[68] == 0) 19362306a36Sopenharmony_ci atom_len = 76; 19462306a36Sopenharmony_ci else 19562306a36Sopenharmony_ci atom_len = 68; 19662306a36Sopenharmony_ci offset = 0; 19762306a36Sopenharmony_ci break; 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci case FIRMWARE_TYPE_INTERSIL: 20062306a36Sopenharmony_ci offset = 4; 20162306a36Sopenharmony_ci if (priv->has_hostscan) { 20262306a36Sopenharmony_ci atom_len = le16_to_cpup((__le16 *)buf); 20362306a36Sopenharmony_ci /* Sanity check for atom_len */ 20462306a36Sopenharmony_ci if (atom_len < sizeof(struct prism2_scan_apinfo)) { 20562306a36Sopenharmony_ci printk(KERN_ERR "%s: Invalid atom_len in scan " 20662306a36Sopenharmony_ci "data: %zu\n", priv->ndev->name, 20762306a36Sopenharmony_ci atom_len); 20862306a36Sopenharmony_ci abort = true; 20962306a36Sopenharmony_ci goto scan_abort; 21062306a36Sopenharmony_ci } 21162306a36Sopenharmony_ci } else 21262306a36Sopenharmony_ci atom_len = offsetof(struct prism2_scan_apinfo, atim); 21362306a36Sopenharmony_ci break; 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci default: 21662306a36Sopenharmony_ci abort = true; 21762306a36Sopenharmony_ci goto scan_abort; 21862306a36Sopenharmony_ci } 21962306a36Sopenharmony_ci 22062306a36Sopenharmony_ci /* Check that we got an whole number of atoms */ 22162306a36Sopenharmony_ci if ((len - offset) % atom_len) { 22262306a36Sopenharmony_ci printk(KERN_ERR "%s: Unexpected scan data length %zu, " 22362306a36Sopenharmony_ci "atom_len %zu, offset %d\n", priv->ndev->name, len, 22462306a36Sopenharmony_ci atom_len, offset); 22562306a36Sopenharmony_ci abort = true; 22662306a36Sopenharmony_ci goto scan_abort; 22762306a36Sopenharmony_ci } 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci /* Process the entries one by one */ 23062306a36Sopenharmony_ci for (; offset + atom_len <= len; offset += atom_len) { 23162306a36Sopenharmony_ci union hermes_scan_info *atom; 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci atom = (union hermes_scan_info *) (buf + offset); 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci orinoco_add_hostscan_result(priv, atom); 23662306a36Sopenharmony_ci } 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci scan_abort: 23962306a36Sopenharmony_ci if (priv->scan_request) { 24062306a36Sopenharmony_ci struct cfg80211_scan_info info = { 24162306a36Sopenharmony_ci .aborted = abort, 24262306a36Sopenharmony_ci }; 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci cfg80211_scan_done(priv->scan_request, &info); 24562306a36Sopenharmony_ci priv->scan_request = NULL; 24662306a36Sopenharmony_ci } 24762306a36Sopenharmony_ci} 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_civoid orinoco_scan_done(struct orinoco_private *priv, bool abort) 25062306a36Sopenharmony_ci{ 25162306a36Sopenharmony_ci if (priv->scan_request) { 25262306a36Sopenharmony_ci struct cfg80211_scan_info info = { 25362306a36Sopenharmony_ci .aborted = abort, 25462306a36Sopenharmony_ci }; 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci cfg80211_scan_done(priv->scan_request, &info); 25762306a36Sopenharmony_ci priv->scan_request = NULL; 25862306a36Sopenharmony_ci } 25962306a36Sopenharmony_ci} 260