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