Commit 4d36ec58 authored by Johannes Berg's avatar Johannes Berg Committed by John W. Linville

mac80211: split hardware scan by band

There's currently a very odd bug in mac80211 -- a
hardware scan that is done while the hardware is
really operating on 2.4 GHz will include CCK rates
in the probe request frame, even on 5 GHz (if the
driver uses the mac80211 IEs). Vice versa, if the
hardware is operating on 5 GHz the 2.4 GHz probe
requests will not include CCK rates even though
they should.

Fix this by splitting up cfg80211 scan requests by
band -- recalculating the IEs every time -- and
requesting only per-band scans from the driver.

Apparently this bug hasn't been a problem yet, but
it is imaginable that some older access points get
confused if confronted with such behaviour.
Signed-off-by: default avatarJohannes Berg <johannes@sipsolutions.net>
Signed-off-by: default avatarJohn W. Linville <linville@tuxdriver.com>
parent b59f04cb
...@@ -667,10 +667,9 @@ struct ieee80211_local { ...@@ -667,10 +667,9 @@ struct ieee80211_local {
unsigned long scanning; unsigned long scanning;
struct cfg80211_ssid scan_ssid; struct cfg80211_ssid scan_ssid;
struct cfg80211_scan_request *int_scan_req; struct cfg80211_scan_request *int_scan_req;
struct cfg80211_scan_request *scan_req; struct cfg80211_scan_request *scan_req, *hw_scan_req;
struct ieee80211_channel *scan_channel; struct ieee80211_channel *scan_channel;
const u8 *orig_ies; enum ieee80211_band hw_scan_band;
int orig_ies_len;
int scan_channel_idx; int scan_channel_idx;
int scan_ies_len; int scan_ies_len;
...@@ -1050,7 +1049,8 @@ void ieee80211_send_auth(struct ieee80211_sub_if_data *sdata, ...@@ -1050,7 +1049,8 @@ void ieee80211_send_auth(struct ieee80211_sub_if_data *sdata,
u8 *extra, size_t extra_len, const u8 *bssid, u8 *extra, size_t extra_len, const u8 *bssid,
const u8 *key, u8 key_len, u8 key_idx); const u8 *key, u8 key_len, u8 key_idx);
int ieee80211_build_preq_ies(struct ieee80211_local *local, u8 *buffer, int ieee80211_build_preq_ies(struct ieee80211_local *local, u8 *buffer,
const u8 *ie, size_t ie_len); const u8 *ie, size_t ie_len,
enum ieee80211_band band);
void ieee80211_send_probe_req(struct ieee80211_sub_if_data *sdata, u8 *dst, void ieee80211_send_probe_req(struct ieee80211_sub_if_data *sdata, u8 *dst,
const u8 *ssid, size_t ssid_len, const u8 *ssid, size_t ssid_len,
const u8 *ie, size_t ie_len); const u8 *ie, size_t ie_len);
......
...@@ -187,6 +187,39 @@ ieee80211_scan_rx(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb) ...@@ -187,6 +187,39 @@ ieee80211_scan_rx(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb)
return RX_QUEUED; return RX_QUEUED;
} }
/* return false if no more work */
static bool ieee80211_prep_hw_scan(struct ieee80211_local *local)
{
struct cfg80211_scan_request *req = local->scan_req;
enum ieee80211_band band;
int i, ielen, n_chans;
do {
if (local->hw_scan_band == IEEE80211_NUM_BANDS)
return false;
band = local->hw_scan_band;
n_chans = 0;
for (i = 0; i < req->n_channels; i++) {
if (req->channels[i]->band == band) {
local->hw_scan_req->channels[n_chans] =
req->channels[i];
n_chans++;
}
}
local->hw_scan_band++;
} while (!n_chans);
local->hw_scan_req->n_channels = n_chans;
ielen = ieee80211_build_preq_ies(local, (u8 *)local->hw_scan_req->ie,
req->ie, req->ie_len, band);
local->hw_scan_req->ie_len = ielen;
return true;
}
/* /*
* inform AP that we will go to sleep so that it will buffer the frames * inform AP that we will go to sleep so that it will buffer the frames
* while we scan * while we scan
...@@ -247,13 +280,6 @@ static void ieee80211_scan_ps_disable(struct ieee80211_sub_if_data *sdata) ...@@ -247,13 +280,6 @@ static void ieee80211_scan_ps_disable(struct ieee80211_sub_if_data *sdata)
} }
} }
static void ieee80211_restore_scan_ies(struct ieee80211_local *local)
{
kfree(local->scan_req->ie);
local->scan_req->ie = local->orig_ies;
local->scan_req->ie_len = local->orig_ies_len;
}
void ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted) void ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted)
{ {
struct ieee80211_local *local = hw_to_local(hw); struct ieee80211_local *local = hw_to_local(hw);
...@@ -272,15 +298,22 @@ void ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted) ...@@ -272,15 +298,22 @@ void ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted)
return; return;
} }
if (test_bit(SCAN_HW_SCANNING, &local->scanning)) was_hw_scan = test_bit(SCAN_HW_SCANNING, &local->scanning);
ieee80211_restore_scan_ies(local); if (was_hw_scan && !aborted && ieee80211_prep_hw_scan(local)) {
ieee80211_queue_delayed_work(&local->hw,
&local->scan_work, 0);
mutex_unlock(&local->scan_mtx);
return;
}
kfree(local->hw_scan_req);
local->hw_scan_req = NULL;
if (local->scan_req != local->int_scan_req) if (local->scan_req != local->int_scan_req)
cfg80211_scan_done(local->scan_req, aborted); cfg80211_scan_done(local->scan_req, aborted);
local->scan_req = NULL; local->scan_req = NULL;
local->scan_sdata = NULL; local->scan_sdata = NULL;
was_hw_scan = test_bit(SCAN_HW_SCANNING, &local->scanning);
local->scanning = 0; local->scanning = 0;
local->scan_channel = NULL; local->scan_channel = NULL;
...@@ -392,19 +425,23 @@ static int __ieee80211_start_scan(struct ieee80211_sub_if_data *sdata, ...@@ -392,19 +425,23 @@ static int __ieee80211_start_scan(struct ieee80211_sub_if_data *sdata,
if (local->ops->hw_scan) { if (local->ops->hw_scan) {
u8 *ies; u8 *ies;
int ielen;
ies = kmalloc(2 + IEEE80211_MAX_SSID_LEN + local->hw_scan_req = kmalloc(
local->scan_ies_len + req->ie_len, GFP_KERNEL); sizeof(*local->hw_scan_req) +
if (!ies) req->n_channels * sizeof(req->channels[0]) +
2 + IEEE80211_MAX_SSID_LEN + local->scan_ies_len +
req->ie_len, GFP_KERNEL);
if (!local->hw_scan_req)
return -ENOMEM; return -ENOMEM;
ielen = ieee80211_build_preq_ies(local, ies, local->hw_scan_req->ssids = req->ssids;
req->ie, req->ie_len); local->hw_scan_req->n_ssids = req->n_ssids;
local->orig_ies = req->ie; ies = (u8 *)local->hw_scan_req +
local->orig_ies_len = req->ie_len; sizeof(*local->hw_scan_req) +
req->ie = ies; req->n_channels * sizeof(req->channels[0]);
req->ie_len = ielen; local->hw_scan_req->ie = ies;
local->hw_scan_band = 0;
} }
local->scan_req = req; local->scan_req = req;
...@@ -436,16 +473,17 @@ static int __ieee80211_start_scan(struct ieee80211_sub_if_data *sdata, ...@@ -436,16 +473,17 @@ static int __ieee80211_start_scan(struct ieee80211_sub_if_data *sdata,
ieee80211_recalc_idle(local); ieee80211_recalc_idle(local);
mutex_unlock(&local->scan_mtx); mutex_unlock(&local->scan_mtx);
if (local->ops->hw_scan) if (local->ops->hw_scan) {
rc = drv_hw_scan(local, local->scan_req); WARN_ON(!ieee80211_prep_hw_scan(local));
else rc = drv_hw_scan(local, local->hw_scan_req);
} else
rc = ieee80211_start_sw_scan(local); rc = ieee80211_start_sw_scan(local);
mutex_lock(&local->scan_mtx); mutex_lock(&local->scan_mtx);
if (rc) { if (rc) {
if (local->ops->hw_scan) kfree(local->hw_scan_req);
ieee80211_restore_scan_ies(local); local->hw_scan_req = NULL;
local->scanning = 0; local->scanning = 0;
ieee80211_recalc_idle(local); ieee80211_recalc_idle(local);
...@@ -654,6 +692,14 @@ void ieee80211_scan_work(struct work_struct *work) ...@@ -654,6 +692,14 @@ void ieee80211_scan_work(struct work_struct *work)
return; return;
} }
if (local->hw_scan_req) {
int rc = drv_hw_scan(local, local->hw_scan_req);
mutex_unlock(&local->scan_mtx);
if (rc)
ieee80211_scan_completed(&local->hw, true);
return;
}
if (local->scan_req && !local->scanning) { if (local->scan_req && !local->scanning) {
struct cfg80211_scan_request *req = local->scan_req; struct cfg80211_scan_request *req = local->scan_req;
int rc; int rc;
......
...@@ -872,13 +872,14 @@ void ieee80211_send_auth(struct ieee80211_sub_if_data *sdata, ...@@ -872,13 +872,14 @@ void ieee80211_send_auth(struct ieee80211_sub_if_data *sdata,
} }
int ieee80211_build_preq_ies(struct ieee80211_local *local, u8 *buffer, int ieee80211_build_preq_ies(struct ieee80211_local *local, u8 *buffer,
const u8 *ie, size_t ie_len) const u8 *ie, size_t ie_len,
enum ieee80211_band band)
{ {
struct ieee80211_supported_band *sband; struct ieee80211_supported_band *sband;
u8 *pos, *supp_rates_len, *esupp_rates_len = NULL; u8 *pos, *supp_rates_len, *esupp_rates_len = NULL;
int i; int i;
sband = local->hw.wiphy->bands[local->hw.conf.channel->band]; sband = local->hw.wiphy->bands[band];
pos = buffer; pos = buffer;
...@@ -966,7 +967,8 @@ void ieee80211_send_probe_req(struct ieee80211_sub_if_data *sdata, u8 *dst, ...@@ -966,7 +967,8 @@ void ieee80211_send_probe_req(struct ieee80211_sub_if_data *sdata, u8 *dst,
memcpy(pos, ssid, ssid_len); memcpy(pos, ssid, ssid_len);
pos += ssid_len; pos += ssid_len;
skb_put(skb, ieee80211_build_preq_ies(local, pos, ie, ie_len)); skb_put(skb, ieee80211_build_preq_ies(local, pos, ie, ie_len,
local->hw.conf.channel->band));
ieee80211_tx_skb(sdata, skb, 0); ieee80211_tx_skb(sdata, skb, 0);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment