diff options
author | Johannes Berg <johannes@sipsolutions.net> | 2009-10-27 15:59:55 -0400 |
---|---|---|
committer | John W. Linville <linville@tuxdriver.com> | 2009-10-30 16:49:19 -0400 |
commit | 4d36ec58239eec44d77839ef6c25108efcbbb58c (patch) | |
tree | 7074195389af214ba0081169afb4b64514f2419a | |
parent | b59f04cbf8ab4dce63f0d2ed658624b0ad21c67d (diff) |
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: Johannes Berg <johannes@sipsolutions.net>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
-rw-r--r-- | net/mac80211/ieee80211_i.h | 8 | ||||
-rw-r--r-- | net/mac80211/scan.c | 96 | ||||
-rw-r--r-- | net/mac80211/util.c | 8 |
3 files changed, 80 insertions, 32 deletions
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h index 60c2822802f0..6365079e6375 100644 --- a/net/mac80211/ieee80211_i.h +++ b/net/mac80211/ieee80211_i.h | |||
@@ -667,10 +667,9 @@ struct ieee80211_local { | |||
667 | unsigned long scanning; | 667 | unsigned long scanning; |
668 | struct cfg80211_ssid scan_ssid; | 668 | struct cfg80211_ssid scan_ssid; |
669 | struct cfg80211_scan_request *int_scan_req; | 669 | struct cfg80211_scan_request *int_scan_req; |
670 | struct cfg80211_scan_request *scan_req; | 670 | struct cfg80211_scan_request *scan_req, *hw_scan_req; |
671 | struct ieee80211_channel *scan_channel; | 671 | struct ieee80211_channel *scan_channel; |
672 | const u8 *orig_ies; | 672 | enum ieee80211_band hw_scan_band; |
673 | int orig_ies_len; | ||
674 | int scan_channel_idx; | 673 | int scan_channel_idx; |
675 | int scan_ies_len; | 674 | int scan_ies_len; |
676 | 675 | ||
@@ -1050,7 +1049,8 @@ void ieee80211_send_auth(struct ieee80211_sub_if_data *sdata, | |||
1050 | u8 *extra, size_t extra_len, const u8 *bssid, | 1049 | u8 *extra, size_t extra_len, const u8 *bssid, |
1051 | const u8 *key, u8 key_len, u8 key_idx); | 1050 | const u8 *key, u8 key_len, u8 key_idx); |
1052 | int ieee80211_build_preq_ies(struct ieee80211_local *local, u8 *buffer, | 1051 | int ieee80211_build_preq_ies(struct ieee80211_local *local, u8 *buffer, |
1053 | const u8 *ie, size_t ie_len); | 1052 | const u8 *ie, size_t ie_len, |
1053 | enum ieee80211_band band); | ||
1054 | void ieee80211_send_probe_req(struct ieee80211_sub_if_data *sdata, u8 *dst, | 1054 | void ieee80211_send_probe_req(struct ieee80211_sub_if_data *sdata, u8 *dst, |
1055 | const u8 *ssid, size_t ssid_len, | 1055 | const u8 *ssid, size_t ssid_len, |
1056 | const u8 *ie, size_t ie_len); | 1056 | const u8 *ie, size_t ie_len); |
diff --git a/net/mac80211/scan.c b/net/mac80211/scan.c index 1a643fe5250e..c46ac01e2a8f 100644 --- a/net/mac80211/scan.c +++ b/net/mac80211/scan.c | |||
@@ -187,6 +187,39 @@ ieee80211_scan_rx(struct ieee80211_sub_if_data *sdata, struct sk_buff *skb) | |||
187 | return RX_QUEUED; | 187 | return RX_QUEUED; |
188 | } | 188 | } |
189 | 189 | ||
190 | /* return false if no more work */ | ||
191 | static bool ieee80211_prep_hw_scan(struct ieee80211_local *local) | ||
192 | { | ||
193 | struct cfg80211_scan_request *req = local->scan_req; | ||
194 | enum ieee80211_band band; | ||
195 | int i, ielen, n_chans; | ||
196 | |||
197 | do { | ||
198 | if (local->hw_scan_band == IEEE80211_NUM_BANDS) | ||
199 | return false; | ||
200 | |||
201 | band = local->hw_scan_band; | ||
202 | n_chans = 0; | ||
203 | for (i = 0; i < req->n_channels; i++) { | ||
204 | if (req->channels[i]->band == band) { | ||
205 | local->hw_scan_req->channels[n_chans] = | ||
206 | req->channels[i]; | ||
207 | n_chans++; | ||
208 | } | ||
209 | } | ||
210 | |||
211 | local->hw_scan_band++; | ||
212 | } while (!n_chans); | ||
213 | |||
214 | local->hw_scan_req->n_channels = n_chans; | ||
215 | |||
216 | ielen = ieee80211_build_preq_ies(local, (u8 *)local->hw_scan_req->ie, | ||
217 | req->ie, req->ie_len, band); | ||
218 | local->hw_scan_req->ie_len = ielen; | ||
219 | |||
220 | return true; | ||
221 | } | ||
222 | |||
190 | /* | 223 | /* |
191 | * inform AP that we will go to sleep so that it will buffer the frames | 224 | * inform AP that we will go to sleep so that it will buffer the frames |
192 | * while we scan | 225 | * while we scan |
@@ -247,13 +280,6 @@ static void ieee80211_scan_ps_disable(struct ieee80211_sub_if_data *sdata) | |||
247 | } | 280 | } |
248 | } | 281 | } |
249 | 282 | ||
250 | static void ieee80211_restore_scan_ies(struct ieee80211_local *local) | ||
251 | { | ||
252 | kfree(local->scan_req->ie); | ||
253 | local->scan_req->ie = local->orig_ies; | ||
254 | local->scan_req->ie_len = local->orig_ies_len; | ||
255 | } | ||
256 | |||
257 | void ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted) | 283 | void ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted) |
258 | { | 284 | { |
259 | struct ieee80211_local *local = hw_to_local(hw); | 285 | struct ieee80211_local *local = hw_to_local(hw); |
@@ -272,15 +298,22 @@ void ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted) | |||
272 | return; | 298 | return; |
273 | } | 299 | } |
274 | 300 | ||
275 | if (test_bit(SCAN_HW_SCANNING, &local->scanning)) | 301 | was_hw_scan = test_bit(SCAN_HW_SCANNING, &local->scanning); |
276 | ieee80211_restore_scan_ies(local); | 302 | if (was_hw_scan && !aborted && ieee80211_prep_hw_scan(local)) { |
303 | ieee80211_queue_delayed_work(&local->hw, | ||
304 | &local->scan_work, 0); | ||
305 | mutex_unlock(&local->scan_mtx); | ||
306 | return; | ||
307 | } | ||
308 | |||
309 | kfree(local->hw_scan_req); | ||
310 | local->hw_scan_req = NULL; | ||
277 | 311 | ||
278 | if (local->scan_req != local->int_scan_req) | 312 | if (local->scan_req != local->int_scan_req) |
279 | cfg80211_scan_done(local->scan_req, aborted); | 313 | cfg80211_scan_done(local->scan_req, aborted); |
280 | local->scan_req = NULL; | 314 | local->scan_req = NULL; |
281 | local->scan_sdata = NULL; | 315 | local->scan_sdata = NULL; |
282 | 316 | ||
283 | was_hw_scan = test_bit(SCAN_HW_SCANNING, &local->scanning); | ||
284 | local->scanning = 0; | 317 | local->scanning = 0; |
285 | local->scan_channel = NULL; | 318 | local->scan_channel = NULL; |
286 | 319 | ||
@@ -392,19 +425,23 @@ static int __ieee80211_start_scan(struct ieee80211_sub_if_data *sdata, | |||
392 | 425 | ||
393 | if (local->ops->hw_scan) { | 426 | if (local->ops->hw_scan) { |
394 | u8 *ies; | 427 | u8 *ies; |
395 | int ielen; | ||
396 | 428 | ||
397 | ies = kmalloc(2 + IEEE80211_MAX_SSID_LEN + | 429 | local->hw_scan_req = kmalloc( |
398 | local->scan_ies_len + req->ie_len, GFP_KERNEL); | 430 | sizeof(*local->hw_scan_req) + |
399 | if (!ies) | 431 | req->n_channels * sizeof(req->channels[0]) + |
432 | 2 + IEEE80211_MAX_SSID_LEN + local->scan_ies_len + | ||
433 | req->ie_len, GFP_KERNEL); | ||
434 | if (!local->hw_scan_req) | ||
400 | return -ENOMEM; | 435 | return -ENOMEM; |
401 | 436 | ||
402 | ielen = ieee80211_build_preq_ies(local, ies, | 437 | local->hw_scan_req->ssids = req->ssids; |
403 | req->ie, req->ie_len); | 438 | local->hw_scan_req->n_ssids = req->n_ssids; |
404 | local->orig_ies = req->ie; | 439 | ies = (u8 *)local->hw_scan_req + |
405 | local->orig_ies_len = req->ie_len; | 440 | sizeof(*local->hw_scan_req) + |
406 | req->ie = ies; | 441 | req->n_channels * sizeof(req->channels[0]); |
407 | req->ie_len = ielen; | 442 | local->hw_scan_req->ie = ies; |
443 | |||
444 | local->hw_scan_band = 0; | ||
408 | } | 445 | } |
409 | 446 | ||
410 | local->scan_req = req; | 447 | local->scan_req = req; |
@@ -436,16 +473,17 @@ static int __ieee80211_start_scan(struct ieee80211_sub_if_data *sdata, | |||
436 | ieee80211_recalc_idle(local); | 473 | ieee80211_recalc_idle(local); |
437 | mutex_unlock(&local->scan_mtx); | 474 | mutex_unlock(&local->scan_mtx); |
438 | 475 | ||
439 | if (local->ops->hw_scan) | 476 | if (local->ops->hw_scan) { |
440 | rc = drv_hw_scan(local, local->scan_req); | 477 | WARN_ON(!ieee80211_prep_hw_scan(local)); |
441 | else | 478 | rc = drv_hw_scan(local, local->hw_scan_req); |
479 | } else | ||
442 | rc = ieee80211_start_sw_scan(local); | 480 | rc = ieee80211_start_sw_scan(local); |
443 | 481 | ||
444 | mutex_lock(&local->scan_mtx); | 482 | mutex_lock(&local->scan_mtx); |
445 | 483 | ||
446 | if (rc) { | 484 | if (rc) { |
447 | if (local->ops->hw_scan) | 485 | kfree(local->hw_scan_req); |
448 | ieee80211_restore_scan_ies(local); | 486 | local->hw_scan_req = NULL; |
449 | local->scanning = 0; | 487 | local->scanning = 0; |
450 | 488 | ||
451 | ieee80211_recalc_idle(local); | 489 | ieee80211_recalc_idle(local); |
@@ -654,6 +692,14 @@ void ieee80211_scan_work(struct work_struct *work) | |||
654 | return; | 692 | return; |
655 | } | 693 | } |
656 | 694 | ||
695 | if (local->hw_scan_req) { | ||
696 | int rc = drv_hw_scan(local, local->hw_scan_req); | ||
697 | mutex_unlock(&local->scan_mtx); | ||
698 | if (rc) | ||
699 | ieee80211_scan_completed(&local->hw, true); | ||
700 | return; | ||
701 | } | ||
702 | |||
657 | if (local->scan_req && !local->scanning) { | 703 | if (local->scan_req && !local->scanning) { |
658 | struct cfg80211_scan_request *req = local->scan_req; | 704 | struct cfg80211_scan_request *req = local->scan_req; |
659 | int rc; | 705 | int rc; |
diff --git a/net/mac80211/util.c b/net/mac80211/util.c index aeb65b3d2295..aedbaaa067e6 100644 --- a/net/mac80211/util.c +++ b/net/mac80211/util.c | |||
@@ -872,13 +872,14 @@ void ieee80211_send_auth(struct ieee80211_sub_if_data *sdata, | |||
872 | } | 872 | } |
873 | 873 | ||
874 | int ieee80211_build_preq_ies(struct ieee80211_local *local, u8 *buffer, | 874 | int ieee80211_build_preq_ies(struct ieee80211_local *local, u8 *buffer, |
875 | const u8 *ie, size_t ie_len) | 875 | const u8 *ie, size_t ie_len, |
876 | enum ieee80211_band band) | ||
876 | { | 877 | { |
877 | struct ieee80211_supported_band *sband; | 878 | struct ieee80211_supported_band *sband; |
878 | u8 *pos, *supp_rates_len, *esupp_rates_len = NULL; | 879 | u8 *pos, *supp_rates_len, *esupp_rates_len = NULL; |
879 | int i; | 880 | int i; |
880 | 881 | ||
881 | sband = local->hw.wiphy->bands[local->hw.conf.channel->band]; | 882 | sband = local->hw.wiphy->bands[band]; |
882 | 883 | ||
883 | pos = buffer; | 884 | pos = buffer; |
884 | 885 | ||
@@ -966,7 +967,8 @@ void ieee80211_send_probe_req(struct ieee80211_sub_if_data *sdata, u8 *dst, | |||
966 | memcpy(pos, ssid, ssid_len); | 967 | memcpy(pos, ssid, ssid_len); |
967 | pos += ssid_len; | 968 | pos += ssid_len; |
968 | 969 | ||
969 | skb_put(skb, ieee80211_build_preq_ies(local, pos, ie, ie_len)); | 970 | skb_put(skb, ieee80211_build_preq_ies(local, pos, ie, ie_len, |
971 | local->hw.conf.channel->band)); | ||
970 | 972 | ||
971 | ieee80211_tx_skb(sdata, skb, 0); | 973 | ieee80211_tx_skb(sdata, skb, 0); |
972 | } | 974 | } |