linux_dsm_epyc7002/drivers/net/wireless/intersil/orinoco/scan.c
Avraham Stern 1d76250bd3 nl80211: support beacon report scanning
Beacon report radio measurement requires reporting observed BSSs
on the channels specified in the beacon request. If the measurement
mode is set to passive or active, it requires actually performing a
scan (passive or active, accordingly), and reporting the time that
the scan was started and the time each beacon/probe was received
(both in terms of TSF of the BSS of the requesting AP). If the
request mode is table, this information is optional.
In addition, the radio measurement request specifies the channel
dwell time for the measurement.

In order to use scan for beacon report when the mode is active or
passive, add a parameter to scan request that specifies the
channel dwell time, and add scan start time and beacon received time
to scan results information.

Supporting beacon report is required for Multi Band Operation (MBO).

Signed-off-by: Assaf Krauss <assaf.krauss@intel.com>
Signed-off-by: David Spinadel <david.spinadel@intel.com>
Signed-off-by: Avraham Stern <avraham.stern@intel.com>
Signed-off-by: Luca Coelho <luciano.coelho@intel.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
2016-07-06 14:51:31 +02:00

260 lines
6.0 KiB
C

/* Helpers for managing scan queues
*
* See copyright notice in main.c
*/
#include <linux/gfp.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/ieee80211.h>
#include <net/cfg80211.h>
#include "hermes.h"
#include "orinoco.h"
#include "main.h"
#include "scan.h"
#define ZERO_DBM_OFFSET 0x95
#define MAX_SIGNAL_LEVEL 0x8A
#define MIN_SIGNAL_LEVEL 0x2F
#define SIGNAL_TO_DBM(x) \
(clamp_t(s32, (x), MIN_SIGNAL_LEVEL, MAX_SIGNAL_LEVEL) \
- ZERO_DBM_OFFSET)
#define SIGNAL_TO_MBM(x) (SIGNAL_TO_DBM(x) * 100)
static int symbol_build_supp_rates(u8 *buf, const __le16 *rates)
{
int i;
u8 rate;
buf[0] = WLAN_EID_SUPP_RATES;
for (i = 0; i < 5; i++) {
rate = le16_to_cpu(rates[i]);
/* NULL terminated */
if (rate == 0x0)
break;
buf[i + 2] = rate;
}
buf[1] = i;
return i + 2;
}
static int prism_build_supp_rates(u8 *buf, const u8 *rates)
{
int i;
buf[0] = WLAN_EID_SUPP_RATES;
for (i = 0; i < 8; i++) {
/* NULL terminated */
if (rates[i] == 0x0)
break;
buf[i + 2] = rates[i];
}
buf[1] = i;
/* We might still have another 2 rates, which need to go in
* extended supported rates */
if (i == 8 && rates[i] > 0) {
buf[10] = WLAN_EID_EXT_SUPP_RATES;
for (; i < 10; i++) {
/* NULL terminated */
if (rates[i] == 0x0)
break;
buf[i + 2] = rates[i];
}
buf[11] = i - 8;
}
return (i < 8) ? i + 2 : i + 4;
}
static void orinoco_add_hostscan_result(struct orinoco_private *priv,
const union hermes_scan_info *bss)
{
struct wiphy *wiphy = priv_to_wiphy(priv);
struct ieee80211_channel *channel;
struct cfg80211_bss *cbss;
u8 *ie;
u8 ie_buf[46];
u64 timestamp;
s32 signal;
u16 capability;
u16 beacon_interval;
int ie_len;
int freq;
int len;
len = le16_to_cpu(bss->a.essid_len);
/* Reconstruct SSID and bitrate IEs to pass up */
ie_buf[0] = WLAN_EID_SSID;
ie_buf[1] = len;
memcpy(&ie_buf[2], bss->a.essid, len);
ie = ie_buf + len + 2;
ie_len = ie_buf[1] + 2;
switch (priv->firmware_type) {
case FIRMWARE_TYPE_SYMBOL:
ie_len += symbol_build_supp_rates(ie, bss->s.rates);
break;
case FIRMWARE_TYPE_INTERSIL:
ie_len += prism_build_supp_rates(ie, bss->p.rates);
break;
case FIRMWARE_TYPE_AGERE:
default:
break;
}
freq = ieee80211_channel_to_frequency(
le16_to_cpu(bss->a.channel), NL80211_BAND_2GHZ);
channel = ieee80211_get_channel(wiphy, freq);
if (!channel) {
printk(KERN_DEBUG "Invalid channel designation %04X(%04X)",
bss->a.channel, freq);
return; /* Then ignore it for now */
}
timestamp = 0;
capability = le16_to_cpu(bss->a.capabilities);
beacon_interval = le16_to_cpu(bss->a.beacon_interv);
signal = SIGNAL_TO_MBM(le16_to_cpu(bss->a.level));
cbss = cfg80211_inform_bss(wiphy, channel, CFG80211_BSS_FTYPE_UNKNOWN,
bss->a.bssid, timestamp, capability,
beacon_interval, ie_buf, ie_len, signal,
GFP_KERNEL);
cfg80211_put_bss(wiphy, cbss);
}
void orinoco_add_extscan_result(struct orinoco_private *priv,
struct agere_ext_scan_info *bss,
size_t len)
{
struct wiphy *wiphy = priv_to_wiphy(priv);
struct ieee80211_channel *channel;
struct cfg80211_bss *cbss;
const u8 *ie;
u64 timestamp;
s32 signal;
u16 capability;
u16 beacon_interval;
size_t ie_len;
int chan, freq;
ie_len = len - sizeof(*bss);
ie = cfg80211_find_ie(WLAN_EID_DS_PARAMS, bss->data, ie_len);
chan = ie ? ie[2] : 0;
freq = ieee80211_channel_to_frequency(chan, NL80211_BAND_2GHZ);
channel = ieee80211_get_channel(wiphy, freq);
timestamp = le64_to_cpu(bss->timestamp);
capability = le16_to_cpu(bss->capabilities);
beacon_interval = le16_to_cpu(bss->beacon_interval);
ie = bss->data;
signal = SIGNAL_TO_MBM(bss->level);
cbss = cfg80211_inform_bss(wiphy, channel, CFG80211_BSS_FTYPE_UNKNOWN,
bss->bssid, timestamp, capability,
beacon_interval, ie, ie_len, signal,
GFP_KERNEL);
cfg80211_put_bss(wiphy, cbss);
}
void orinoco_add_hostscan_results(struct orinoco_private *priv,
unsigned char *buf,
size_t len)
{
int offset; /* In the scan data */
size_t atom_len;
bool abort = false;
switch (priv->firmware_type) {
case FIRMWARE_TYPE_AGERE:
atom_len = sizeof(struct agere_scan_apinfo);
offset = 0;
break;
case FIRMWARE_TYPE_SYMBOL:
/* Lack of documentation necessitates this hack.
* Different firmwares have 68 or 76 byte long atoms.
* We try modulo first. If the length divides by both,
* we check what would be the channel in the second
* frame for a 68-byte atom. 76-byte atoms have 0 there.
* Valid channel cannot be 0. */
if (len % 76)
atom_len = 68;
else if (len % 68)
atom_len = 76;
else if (len >= 1292 && buf[68] == 0)
atom_len = 76;
else
atom_len = 68;
offset = 0;
break;
case FIRMWARE_TYPE_INTERSIL:
offset = 4;
if (priv->has_hostscan) {
atom_len = le16_to_cpup((__le16 *)buf);
/* Sanity check for atom_len */
if (atom_len < sizeof(struct prism2_scan_apinfo)) {
printk(KERN_ERR "%s: Invalid atom_len in scan "
"data: %zu\n", priv->ndev->name,
atom_len);
abort = true;
goto scan_abort;
}
} else
atom_len = offsetof(struct prism2_scan_apinfo, atim);
break;
default:
abort = true;
goto scan_abort;
}
/* Check that we got an whole number of atoms */
if ((len - offset) % atom_len) {
printk(KERN_ERR "%s: Unexpected scan data length %zu, "
"atom_len %zu, offset %d\n", priv->ndev->name, len,
atom_len, offset);
abort = true;
goto scan_abort;
}
/* Process the entries one by one */
for (; offset + atom_len <= len; offset += atom_len) {
union hermes_scan_info *atom;
atom = (union hermes_scan_info *) (buf + offset);
orinoco_add_hostscan_result(priv, atom);
}
scan_abort:
if (priv->scan_request) {
struct cfg80211_scan_info info = {
.aborted = abort,
};
cfg80211_scan_done(priv->scan_request, &info);
priv->scan_request = NULL;
}
}
void orinoco_scan_done(struct orinoco_private *priv, bool abort)
{
if (priv->scan_request) {
struct cfg80211_scan_info info = {
.aborted = abort,
};
cfg80211_scan_done(priv->scan_request, &info);
priv->scan_request = NULL;
}
}