Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

cfg80211: Fix regulatory bug with multiple cards and delays

When two cards are connected with the same regulatory domain
if CRDA had a delayed response then cfg80211's own set regulatory
domain would still be the world regulatory domain. There was a bug
on cfg80211's logic such that it assumed that once you pegged a
request as the last request it was already the currently set
regulatory domain. This would mean we would race setting a stale
regulatory domain to secondary cards which had the same regulatory
domain since the alpha2 would match.

We fix this by processing each regulatory request atomically,
and only move on to the next one once we get it fully processed.
In the case CRDA is not present we will simply world roam.

This issue is only present when you have a slow system and the
CRDA processing is delayed. Because of this it is not a known
regression.

Without this fix when a delay is present with CRDA the second card
would end up with an intersected regulatory domain and not allow it
to use the channels it really is designed for. When two cards with
two different regulatory domains were inserted you'd end up
rejecting the second card's regulatory domain request.
This fails with mac80211_hswim's regtest=2 (two requests, same alpha2)
and regtest=3 (two requests, different alpha2) module parameter
options.

This was reproduced and tested against mac80211_hwsim using this
CRDA delayer:

#!/bin/bash
echo $COUNTRY >> /tmp/log
sleep 2
/sbin/crda.orig

And these regulatory tests:

modprobe mac80211_hwsim regtest=2
modprobe mac80211_hwsim regtest=3

Reported-by: Mark Mentovai <mark@moxienet.com>
Signed-off-by: Luis R. Rodriguez <lrodriguez@atheros.com>
Tested-by: Mark Mentovai <mark@moxienet.com>
Tested-by: Bruno Randolf <br1@einfach.org>
Signed-off-by: John W. Linville <linville@tuxdriver.com>

authored by

Luis R. Rodriguez and committed by
John W. Linville
b2e253cf b0e2880b

+53 -12
+7
include/net/regulatory.h
··· 43 43 * @intersect: indicates whether the wireless core should intersect 44 44 * the requested regulatory domain with the presently set regulatory 45 45 * domain. 46 + * @processed: indicates whether or not this requests has already been 47 + * processed. When the last request is processed it means that the 48 + * currently regulatory domain set on cfg80211 is updated from 49 + * CRDA and can be used by other regulatory requests. When a 50 + * the last request is not yet processed we must yield until it 51 + * is processed before processing any new requests. 46 52 * @country_ie_checksum: checksum of the last processed and accepted 47 53 * country IE 48 54 * @country_ie_env: lets us know if the AP is telling us we are outdoor, ··· 60 54 enum nl80211_reg_initiator initiator; 61 55 char alpha2[2]; 62 56 bool intersect; 57 + bool processed; 63 58 enum environment_cap country_ie_env; 64 59 struct list_head list; 65 60 };
+46 -12
net/wireless/reg.c
··· 1320 1320 return -EINVAL; 1321 1321 } 1322 1322 1323 + static void reg_set_request_processed(void) 1324 + { 1325 + bool need_more_processing = false; 1326 + 1327 + last_request->processed = true; 1328 + 1329 + spin_lock(&reg_requests_lock); 1330 + if (!list_empty(&reg_requests_list)) 1331 + need_more_processing = true; 1332 + spin_unlock(&reg_requests_lock); 1333 + 1334 + if (need_more_processing) 1335 + schedule_work(&reg_work); 1336 + } 1337 + 1323 1338 /** 1324 1339 * __regulatory_hint - hint to the wireless core a regulatory domain 1325 1340 * @wiphy: if the hint comes from country information from an AP, this ··· 1410 1395 * have applied the requested regulatory domain before we just 1411 1396 * inform userspace we have processed the request 1412 1397 */ 1413 - if (r == -EALREADY) 1398 + if (r == -EALREADY) { 1414 1399 nl80211_send_reg_change_event(last_request); 1400 + reg_set_request_processed(); 1401 + } 1415 1402 return r; 1416 1403 } 1417 1404 ··· 1445 1428 wiphy_update_regulatory(wiphy, initiator); 1446 1429 } 1447 1430 1448 - /* Processes regulatory hints, this is all the NL80211_REGDOM_SET_BY_* */ 1431 + /* 1432 + * Processes regulatory hints, this is all the NL80211_REGDOM_SET_BY_* 1433 + * Regulatory hints come on a first come first serve basis and we 1434 + * must process each one atomically. 1435 + */ 1449 1436 static void reg_process_pending_hints(void) 1450 1437 { 1451 1438 struct regulatory_request *reg_request; ··· 1457 1436 mutex_lock(&cfg80211_mutex); 1458 1437 mutex_lock(&reg_mutex); 1459 1438 1460 - spin_lock(&reg_requests_lock); 1461 - while (!list_empty(&reg_requests_list)) { 1462 - reg_request = list_first_entry(&reg_requests_list, 1463 - struct regulatory_request, 1464 - list); 1465 - list_del_init(&reg_request->list); 1466 - 1467 - spin_unlock(&reg_requests_lock); 1468 - reg_process_hint(reg_request); 1469 - spin_lock(&reg_requests_lock); 1439 + /* When last_request->processed becomes true this will be rescheduled */ 1440 + if (last_request && !last_request->processed) { 1441 + REG_DBG_PRINT("Pending regulatory request, waiting " 1442 + "for it to be processed..."); 1443 + goto out; 1470 1444 } 1445 + 1446 + spin_lock(&reg_requests_lock); 1447 + 1448 + if (list_empty(&reg_requests_list)) { 1449 + spin_unlock(&reg_requests_lock); 1450 + goto out; 1451 + } 1452 + 1453 + reg_request = list_first_entry(&reg_requests_list, 1454 + struct regulatory_request, 1455 + list); 1456 + list_del_init(&reg_request->list); 1457 + 1471 1458 spin_unlock(&reg_requests_lock); 1472 1459 1460 + reg_process_hint(reg_request); 1461 + 1462 + out: 1473 1463 mutex_unlock(&reg_mutex); 1474 1464 mutex_unlock(&cfg80211_mutex); 1475 1465 } ··· 2088 2056 print_regdomain(cfg80211_regdomain); 2089 2057 2090 2058 nl80211_send_reg_change_event(last_request); 2059 + 2060 + reg_set_request_processed(); 2091 2061 2092 2062 mutex_unlock(&reg_mutex); 2093 2063