diff options
-rw-r--r-- | include/linux/ieee80211.h | 62 | ||||
-rw-r--r-- | include/net/wireless.h | 15 | ||||
-rw-r--r-- | net/mac80211/mlme.c | 7 | ||||
-rw-r--r-- | net/wireless/Kconfig | 11 | ||||
-rw-r--r-- | net/wireless/core.c | 5 | ||||
-rw-r--r-- | net/wireless/core.h | 13 | ||||
-rw-r--r-- | net/wireless/nl80211.c | 2 | ||||
-rw-r--r-- | net/wireless/reg.c | 479 | ||||
-rw-r--r-- | net/wireless/reg.h | 21 |
9 files changed, 591 insertions, 24 deletions
diff --git a/include/linux/ieee80211.h b/include/linux/ieee80211.h index 56b0eb25d927..a6ec928186ad 100644 --- a/include/linux/ieee80211.h +++ b/include/linux/ieee80211.h | |||
@@ -1042,6 +1042,68 @@ enum ieee80211_spectrum_mgmt_actioncode { | |||
1042 | WLAN_ACTION_SPCT_CHL_SWITCH = 4, | 1042 | WLAN_ACTION_SPCT_CHL_SWITCH = 4, |
1043 | }; | 1043 | }; |
1044 | 1044 | ||
1045 | /* | ||
1046 | * IEEE 802.11-2007 7.3.2.9 Country information element | ||
1047 | * | ||
1048 | * Minimum length is 8 octets, ie len must be evenly | ||
1049 | * divisible by 2 | ||
1050 | */ | ||
1051 | |||
1052 | /* Although the spec says 8 I'm seeing 6 in practice */ | ||
1053 | #define IEEE80211_COUNTRY_IE_MIN_LEN 6 | ||
1054 | |||
1055 | /* | ||
1056 | * For regulatory extension stuff see IEEE 802.11-2007 | ||
1057 | * Annex I (page 1141) and Annex J (page 1147). Also | ||
1058 | * review 7.3.2.9. | ||
1059 | * | ||
1060 | * When dot11RegulatoryClassesRequired is true and the | ||
1061 | * first_channel/reg_extension_id is >= 201 then the IE | ||
1062 | * compromises of the 'ext' struct represented below: | ||
1063 | * | ||
1064 | * - Regulatory extension ID - when generating IE this just needs | ||
1065 | * to be monotonically increasing for each triplet passed in | ||
1066 | * the IE | ||
1067 | * - Regulatory class - index into set of rules | ||
1068 | * - Coverage class - index into air propagation time (Table 7-27), | ||
1069 | * in microseconds, you can compute the air propagation time from | ||
1070 | * the index by multiplying by 3, so index 10 yields a propagation | ||
1071 | * of 10 us. Valid values are 0-31, values 32-255 are not defined | ||
1072 | * yet. A value of 0 inicates air propagation of <= 1 us. | ||
1073 | * | ||
1074 | * See also Table I.2 for Emission limit sets and table | ||
1075 | * I.3 for Behavior limit sets. Table J.1 indicates how to map | ||
1076 | * a reg_class to an emission limit set and behavior limit set. | ||
1077 | */ | ||
1078 | #define IEEE80211_COUNTRY_EXTENSION_ID 201 | ||
1079 | |||
1080 | /* | ||
1081 | * Channels numbers in the IE must be monotonically increasing | ||
1082 | * if dot11RegulatoryClassesRequired is not true. | ||
1083 | * | ||
1084 | * If dot11RegulatoryClassesRequired is true consecutive | ||
1085 | * subband triplets following a regulatory triplet shall | ||
1086 | * have monotonically increasing first_channel number fields. | ||
1087 | * | ||
1088 | * Channel numbers shall not overlap. | ||
1089 | * | ||
1090 | * Note that max_power is signed. | ||
1091 | */ | ||
1092 | struct ieee80211_country_ie_triplet { | ||
1093 | union { | ||
1094 | struct { | ||
1095 | u8 first_channel; | ||
1096 | u8 num_channels; | ||
1097 | s8 max_power; | ||
1098 | } __attribute__ ((packed)) chans; | ||
1099 | struct { | ||
1100 | u8 reg_extension_id; | ||
1101 | u8 reg_class; | ||
1102 | u8 coverage_class; | ||
1103 | } __attribute__ ((packed)) ext; | ||
1104 | }; | ||
1105 | } __attribute__ ((packed)); | ||
1106 | |||
1045 | /* BACK action code */ | 1107 | /* BACK action code */ |
1046 | enum ieee80211_back_actioncode { | 1108 | enum ieee80211_back_actioncode { |
1047 | WLAN_ACTION_ADDBA_REQ = 0, | 1109 | WLAN_ACTION_ADDBA_REQ = 0, |
diff --git a/include/net/wireless.h b/include/net/wireless.h index 412351560b76..c85fa0ea5c51 100644 --- a/include/net/wireless.h +++ b/include/net/wireless.h | |||
@@ -373,4 +373,19 @@ ieee80211_get_response_rate(struct ieee80211_supported_band *sband, | |||
373 | * for a regulatory domain structure for the respective country. | 373 | * for a regulatory domain structure for the respective country. |
374 | */ | 374 | */ |
375 | extern void regulatory_hint(struct wiphy *wiphy, const char *alpha2); | 375 | extern void regulatory_hint(struct wiphy *wiphy, const char *alpha2); |
376 | |||
377 | /** | ||
378 | * regulatory_hint_11d - hints a country IE as a regulatory domain | ||
379 | * @wiphy: the wireless device giving the hint (used only for reporting | ||
380 | * conflicts) | ||
381 | * @country_ie: pointer to the country IE | ||
382 | * @country_ie_len: length of the country IE | ||
383 | * | ||
384 | * We will intersect the rd with the what CRDA tells us should apply | ||
385 | * for the alpha2 this country IE belongs to, this prevents APs from | ||
386 | * sending us incorrect or outdated information against a country. | ||
387 | */ | ||
388 | extern void regulatory_hint_11d(struct wiphy *wiphy, | ||
389 | u8 *country_ie, | ||
390 | u8 country_ie_len); | ||
376 | #endif /* __NET_WIRELESS_H */ | 391 | #endif /* __NET_WIRELESS_H */ |
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c index d81a4d2cd3aa..30adaddeed27 100644 --- a/net/mac80211/mlme.c +++ b/net/mac80211/mlme.c | |||
@@ -1736,6 +1736,13 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_sub_if_data *sdata, | |||
1736 | ap_ht_cap_flags); | 1736 | ap_ht_cap_flags); |
1737 | } | 1737 | } |
1738 | 1738 | ||
1739 | if (elems.country_elem) { | ||
1740 | /* Note we are only reviewing this on beacons | ||
1741 | * for the BSSID we are associated to */ | ||
1742 | regulatory_hint_11d(local->hw.wiphy, | ||
1743 | elems.country_elem, elems.country_elem_len); | ||
1744 | } | ||
1745 | |||
1739 | ieee80211_bss_info_change_notify(sdata, changed); | 1746 | ieee80211_bss_info_change_notify(sdata, changed); |
1740 | } | 1747 | } |
1741 | 1748 | ||
diff --git a/net/wireless/Kconfig b/net/wireless/Kconfig index f7c64dbe86cc..e28e2b8fa436 100644 --- a/net/wireless/Kconfig +++ b/net/wireless/Kconfig | |||
@@ -1,6 +1,15 @@ | |||
1 | config CFG80211 | 1 | config CFG80211 |
2 | tristate "Improved wireless configuration API" | 2 | tristate "Improved wireless configuration API" |
3 | 3 | ||
4 | config CFG80211_REG_DEBUG | ||
5 | bool "cfg80211 regulatory debugging" | ||
6 | depends on CFG80211 | ||
7 | default n | ||
8 | ---help--- | ||
9 | You can enable this if you want to debug regulatory changes. | ||
10 | |||
11 | If unsure, say N. | ||
12 | |||
4 | config NL80211 | 13 | config NL80211 |
5 | bool "nl80211 new netlink interface support" | 14 | bool "nl80211 new netlink interface support" |
6 | depends on CFG80211 | 15 | depends on CFG80211 |
@@ -40,6 +49,8 @@ config WIRELESS_OLD_REGULATORY | |||
40 | ieee80211_regdom module parameter. This is being phased out and you | 49 | ieee80211_regdom module parameter. This is being phased out and you |
41 | should stop using them ASAP. | 50 | should stop using them ASAP. |
42 | 51 | ||
52 | Note: You will need CRDA if you want 802.11d support | ||
53 | |||
43 | Say Y unless you have installed a new userspace application. | 54 | Say Y unless you have installed a new userspace application. |
44 | Also say Y if have one currently depending on the ieee80211_regdom | 55 | Also say Y if have one currently depending on the ieee80211_regdom |
45 | module parameter and cannot port it to use the new userspace | 56 | module parameter and cannot port it to use the new userspace |
diff --git a/net/wireless/core.c b/net/wireless/core.c index 39e3d10fccde..b96fc0c3f1c4 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c | |||
@@ -19,7 +19,6 @@ | |||
19 | #include "nl80211.h" | 19 | #include "nl80211.h" |
20 | #include "core.h" | 20 | #include "core.h" |
21 | #include "sysfs.h" | 21 | #include "sysfs.h" |
22 | #include "reg.h" | ||
23 | 22 | ||
24 | /* name for sysfs, %d is appended */ | 23 | /* name for sysfs, %d is appended */ |
25 | #define PHY_NAME "phy" | 24 | #define PHY_NAME "phy" |
@@ -348,6 +347,10 @@ void wiphy_unregister(struct wiphy *wiphy) | |||
348 | /* unlock again before freeing */ | 347 | /* unlock again before freeing */ |
349 | mutex_unlock(&drv->mtx); | 348 | mutex_unlock(&drv->mtx); |
350 | 349 | ||
350 | /* If this device got a regulatory hint tell core its | ||
351 | * free to listen now to a new shiny device regulatory hint */ | ||
352 | reg_device_remove(wiphy); | ||
353 | |||
351 | list_del(&drv->list); | 354 | list_del(&drv->list); |
352 | device_del(&drv->wiphy.dev); | 355 | device_del(&drv->wiphy.dev); |
353 | debugfs_remove(drv->wiphy.debugfsdir); | 356 | debugfs_remove(drv->wiphy.debugfsdir); |
diff --git a/net/wireless/core.h b/net/wireless/core.h index 771cc5cc7658..f7fb9f413028 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h | |||
@@ -11,6 +11,7 @@ | |||
11 | #include <net/genetlink.h> | 11 | #include <net/genetlink.h> |
12 | #include <net/wireless.h> | 12 | #include <net/wireless.h> |
13 | #include <net/cfg80211.h> | 13 | #include <net/cfg80211.h> |
14 | #include "reg.h" | ||
14 | 15 | ||
15 | struct cfg80211_registered_device { | 16 | struct cfg80211_registered_device { |
16 | struct cfg80211_ops *ops; | 17 | struct cfg80211_ops *ops; |
@@ -21,6 +22,18 @@ struct cfg80211_registered_device { | |||
21 | * any call is in progress */ | 22 | * any call is in progress */ |
22 | struct mutex mtx; | 23 | struct mutex mtx; |
23 | 24 | ||
25 | /* ISO / IEC 3166 alpha2 for which this device is receiving | ||
26 | * country IEs on, this can help disregard country IEs from APs | ||
27 | * on the same alpha2 quickly. The alpha2 may differ from | ||
28 | * cfg80211_regdomain's alpha2 when an intersection has occurred. | ||
29 | * If the AP is reconfigured this can also be used to tell us if | ||
30 | * the country on the country IE changed. */ | ||
31 | char country_ie_alpha2[2]; | ||
32 | |||
33 | /* If a Country IE has been received this tells us the environment | ||
34 | * which its telling us its in. This defaults to ENVIRON_ANY */ | ||
35 | enum environment_cap env; | ||
36 | |||
24 | /* wiphy index, internal only */ | 37 | /* wiphy index, internal only */ |
25 | int idx; | 38 | int idx; |
26 | 39 | ||
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index e3e1494e769a..00121ceddb14 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c | |||
@@ -1760,7 +1760,7 @@ static int nl80211_req_set_reg(struct sk_buff *skb, struct genl_info *info) | |||
1760 | return -EINVAL; | 1760 | return -EINVAL; |
1761 | #endif | 1761 | #endif |
1762 | mutex_lock(&cfg80211_drv_mutex); | 1762 | mutex_lock(&cfg80211_drv_mutex); |
1763 | r = __regulatory_hint(NULL, REGDOM_SET_BY_USER, data); | 1763 | r = __regulatory_hint(NULL, REGDOM_SET_BY_USER, data, 0, ENVIRON_ANY); |
1764 | mutex_unlock(&cfg80211_drv_mutex); | 1764 | mutex_unlock(&cfg80211_drv_mutex); |
1765 | return r; | 1765 | return r; |
1766 | } | 1766 | } |
diff --git a/net/wireless/reg.c b/net/wireless/reg.c index f0ff3d1779da..4dab993ea488 100644 --- a/net/wireless/reg.c +++ b/net/wireless/reg.c | |||
@@ -60,12 +60,18 @@ | |||
60 | * @intersect: indicates whether the wireless core should intersect | 60 | * @intersect: indicates whether the wireless core should intersect |
61 | * the requested regulatory domain with the presently set regulatory | 61 | * the requested regulatory domain with the presently set regulatory |
62 | * domain. | 62 | * domain. |
63 | * @country_ie_checksum: checksum of the last processed and accepted | ||
64 | * country IE | ||
65 | * @country_ie_env: lets us know if the AP is telling us we are outdoor, | ||
66 | * indoor, or if it doesn't matter | ||
63 | */ | 67 | */ |
64 | struct regulatory_request { | 68 | struct regulatory_request { |
65 | struct wiphy *wiphy; | 69 | struct wiphy *wiphy; |
66 | enum reg_set_by initiator; | 70 | enum reg_set_by initiator; |
67 | char alpha2[2]; | 71 | char alpha2[2]; |
68 | bool intersect; | 72 | bool intersect; |
73 | u32 country_ie_checksum; | ||
74 | enum environment_cap country_ie_env; | ||
69 | }; | 75 | }; |
70 | 76 | ||
71 | /* Receipt of information from last regulatory request */ | 77 | /* Receipt of information from last regulatory request */ |
@@ -85,6 +91,11 @@ static u32 supported_bandwidths[] = { | |||
85 | * information to give us an alpha2 */ | 91 | * information to give us an alpha2 */ |
86 | static const struct ieee80211_regdomain *cfg80211_regdomain; | 92 | static const struct ieee80211_regdomain *cfg80211_regdomain; |
87 | 93 | ||
94 | /* We use this as a place for the rd structure built from the | ||
95 | * last parsed country IE to rest until CRDA gets back to us with | ||
96 | * what it thinks should apply for the same country */ | ||
97 | static const struct ieee80211_regdomain *country_ie_regdomain; | ||
98 | |||
88 | /* We keep a static world regulatory domain in case of the absence of CRDA */ | 99 | /* We keep a static world regulatory domain in case of the absence of CRDA */ |
89 | static const struct ieee80211_regdomain world_regdom = { | 100 | static const struct ieee80211_regdomain world_regdom = { |
90 | .n_reg_rules = 1, | 101 | .n_reg_rules = 1, |
@@ -264,6 +275,18 @@ static bool is_unknown_alpha2(const char *alpha2) | |||
264 | return false; | 275 | return false; |
265 | } | 276 | } |
266 | 277 | ||
278 | static bool is_intersected_alpha2(const char *alpha2) | ||
279 | { | ||
280 | if (!alpha2) | ||
281 | return false; | ||
282 | /* Special case where regulatory domain is the | ||
283 | * result of an intersection between two regulatory domain | ||
284 | * structures */ | ||
285 | if (alpha2[0] == '9' && alpha2[1] == '8') | ||
286 | return true; | ||
287 | return false; | ||
288 | } | ||
289 | |||
267 | static bool is_an_alpha2(const char *alpha2) | 290 | static bool is_an_alpha2(const char *alpha2) |
268 | { | 291 | { |
269 | if (!alpha2) | 292 | if (!alpha2) |
@@ -292,6 +315,25 @@ static bool regdom_changed(const char *alpha2) | |||
292 | return true; | 315 | return true; |
293 | } | 316 | } |
294 | 317 | ||
318 | /** | ||
319 | * country_ie_integrity_changes - tells us if the country IE has changed | ||
320 | * @checksum: checksum of country IE of fields we are interested in | ||
321 | * | ||
322 | * If the country IE has not changed you can ignore it safely. This is | ||
323 | * useful to determine if two devices are seeing two different country IEs | ||
324 | * even on the same alpha2. Note that this will return false if no IE has | ||
325 | * been set on the wireless core yet. | ||
326 | */ | ||
327 | static bool country_ie_integrity_changes(u32 checksum) | ||
328 | { | ||
329 | /* If no IE has been set then the checksum doesn't change */ | ||
330 | if (unlikely(!last_request->country_ie_checksum)) | ||
331 | return false; | ||
332 | if (unlikely(last_request->country_ie_checksum != checksum)) | ||
333 | return true; | ||
334 | return false; | ||
335 | } | ||
336 | |||
295 | /* This lets us keep regulatory code which is updated on a regulatory | 337 | /* This lets us keep regulatory code which is updated on a regulatory |
296 | * basis in userspace. */ | 338 | * basis in userspace. */ |
297 | static int call_crda(const char *alpha2) | 339 | static int call_crda(const char *alpha2) |
@@ -379,6 +421,174 @@ static u32 freq_max_bandwidth(const struct ieee80211_freq_range *freq_range, | |||
379 | return 0; | 421 | return 0; |
380 | } | 422 | } |
381 | 423 | ||
424 | /* Converts a country IE to a regulatory domain. A regulatory domain | ||
425 | * structure has a lot of information which the IE doesn't yet have, | ||
426 | * so for the other values we use upper max values as we will intersect | ||
427 | * with our userspace regulatory agent to get lower bounds. */ | ||
428 | static struct ieee80211_regdomain *country_ie_2_rd( | ||
429 | u8 *country_ie, | ||
430 | u8 country_ie_len, | ||
431 | u32 *checksum) | ||
432 | { | ||
433 | struct ieee80211_regdomain *rd = NULL; | ||
434 | unsigned int i = 0; | ||
435 | char alpha2[2]; | ||
436 | u32 flags = 0; | ||
437 | u32 num_rules = 0, size_of_regd = 0; | ||
438 | u8 *triplets_start = NULL; | ||
439 | u8 len_at_triplet = 0; | ||
440 | /* the last channel we have registered in a subband (triplet) */ | ||
441 | int last_sub_max_channel = 0; | ||
442 | |||
443 | *checksum = 0xDEADBEEF; | ||
444 | |||
445 | /* Country IE requirements */ | ||
446 | BUG_ON(country_ie_len < IEEE80211_COUNTRY_IE_MIN_LEN || | ||
447 | country_ie_len & 0x01); | ||
448 | |||
449 | alpha2[0] = country_ie[0]; | ||
450 | alpha2[1] = country_ie[1]; | ||
451 | |||
452 | /* | ||
453 | * Third octet can be: | ||
454 | * 'I' - Indoor | ||
455 | * 'O' - Outdoor | ||
456 | * | ||
457 | * anything else we assume is no restrictions | ||
458 | */ | ||
459 | if (country_ie[2] == 'I') | ||
460 | flags = NL80211_RRF_NO_OUTDOOR; | ||
461 | else if (country_ie[2] == 'O') | ||
462 | flags = NL80211_RRF_NO_INDOOR; | ||
463 | |||
464 | country_ie += 3; | ||
465 | country_ie_len -= 3; | ||
466 | |||
467 | triplets_start = country_ie; | ||
468 | len_at_triplet = country_ie_len; | ||
469 | |||
470 | *checksum ^= ((flags ^ alpha2[0] ^ alpha2[1]) << 8); | ||
471 | |||
472 | /* We need to build a reg rule for each triplet, but first we must | ||
473 | * calculate the number of reg rules we will need. We will need one | ||
474 | * for each channel subband */ | ||
475 | while (country_ie_len >= 3) { | ||
476 | struct ieee80211_country_ie_triplet *triplet = | ||
477 | (struct ieee80211_country_ie_triplet *) country_ie; | ||
478 | int cur_sub_max_channel = 0, cur_channel = 0; | ||
479 | |||
480 | if (triplet->ext.reg_extension_id >= | ||
481 | IEEE80211_COUNTRY_EXTENSION_ID) { | ||
482 | country_ie += 3; | ||
483 | country_ie_len -= 3; | ||
484 | continue; | ||
485 | } | ||
486 | |||
487 | cur_channel = triplet->chans.first_channel; | ||
488 | cur_sub_max_channel = ieee80211_channel_to_frequency( | ||
489 | cur_channel + triplet->chans.num_channels); | ||
490 | |||
491 | /* Basic sanity check */ | ||
492 | if (cur_sub_max_channel < cur_channel) | ||
493 | return NULL; | ||
494 | |||
495 | /* Do not allow overlapping channels. Also channels | ||
496 | * passed in each subband must be monotonically | ||
497 | * increasing */ | ||
498 | if (last_sub_max_channel) { | ||
499 | if (cur_channel <= last_sub_max_channel) | ||
500 | return NULL; | ||
501 | if (cur_sub_max_channel <= last_sub_max_channel) | ||
502 | return NULL; | ||
503 | } | ||
504 | |||
505 | /* When dot11RegulatoryClassesRequired is supported | ||
506 | * we can throw ext triplets as part of this soup, | ||
507 | * for now we don't care when those change as we | ||
508 | * don't support them */ | ||
509 | *checksum ^= ((cur_channel ^ cur_sub_max_channel) << 8) | | ||
510 | ((cur_sub_max_channel ^ cur_sub_max_channel) << 16) | | ||
511 | ((triplet->chans.max_power ^ cur_sub_max_channel) << 24); | ||
512 | |||
513 | last_sub_max_channel = cur_sub_max_channel; | ||
514 | |||
515 | country_ie += 3; | ||
516 | country_ie_len -= 3; | ||
517 | num_rules++; | ||
518 | |||
519 | /* Note: this is not a IEEE requirement but | ||
520 | * simply a memory requirement */ | ||
521 | if (num_rules > NL80211_MAX_SUPP_REG_RULES) | ||
522 | return NULL; | ||
523 | } | ||
524 | |||
525 | country_ie = triplets_start; | ||
526 | country_ie_len = len_at_triplet; | ||
527 | |||
528 | size_of_regd = sizeof(struct ieee80211_regdomain) + | ||
529 | (num_rules * sizeof(struct ieee80211_reg_rule)); | ||
530 | |||
531 | rd = kzalloc(size_of_regd, GFP_KERNEL); | ||
532 | if (!rd) | ||
533 | return NULL; | ||
534 | |||
535 | rd->n_reg_rules = num_rules; | ||
536 | rd->alpha2[0] = alpha2[0]; | ||
537 | rd->alpha2[1] = alpha2[1]; | ||
538 | |||
539 | /* This time around we fill in the rd */ | ||
540 | while (country_ie_len >= 3) { | ||
541 | struct ieee80211_country_ie_triplet *triplet = | ||
542 | (struct ieee80211_country_ie_triplet *) country_ie; | ||
543 | struct ieee80211_reg_rule *reg_rule = NULL; | ||
544 | struct ieee80211_freq_range *freq_range = NULL; | ||
545 | struct ieee80211_power_rule *power_rule = NULL; | ||
546 | |||
547 | /* Must parse if dot11RegulatoryClassesRequired is true, | ||
548 | * we don't support this yet */ | ||
549 | if (triplet->ext.reg_extension_id >= | ||
550 | IEEE80211_COUNTRY_EXTENSION_ID) { | ||
551 | country_ie += 3; | ||
552 | country_ie_len -= 3; | ||
553 | continue; | ||
554 | } | ||
555 | |||
556 | reg_rule = &rd->reg_rules[i]; | ||
557 | freq_range = ®_rule->freq_range; | ||
558 | power_rule = ®_rule->power_rule; | ||
559 | |||
560 | reg_rule->flags = flags; | ||
561 | |||
562 | /* The +10 is since the regulatory domain expects | ||
563 | * the actual band edge, not the center of freq for | ||
564 | * its start and end freqs, assuming 20 MHz bandwidth on | ||
565 | * the channels passed */ | ||
566 | freq_range->start_freq_khz = | ||
567 | MHZ_TO_KHZ(ieee80211_channel_to_frequency( | ||
568 | triplet->chans.first_channel) - 10); | ||
569 | freq_range->end_freq_khz = | ||
570 | MHZ_TO_KHZ(ieee80211_channel_to_frequency( | ||
571 | triplet->chans.first_channel + | ||
572 | triplet->chans.num_channels) + 10); | ||
573 | |||
574 | /* Large arbitrary values, we intersect later */ | ||
575 | /* Increment this if we ever support >= 40 MHz channels | ||
576 | * in IEEE 802.11 */ | ||
577 | freq_range->max_bandwidth_khz = MHZ_TO_KHZ(40); | ||
578 | power_rule->max_antenna_gain = DBI_TO_MBI(100); | ||
579 | power_rule->max_eirp = DBM_TO_MBM(100); | ||
580 | |||
581 | country_ie += 3; | ||
582 | country_ie_len -= 3; | ||
583 | i++; | ||
584 | |||
585 | BUG_ON(i > NL80211_MAX_SUPP_REG_RULES); | ||
586 | } | ||
587 | |||
588 | return rd; | ||
589 | } | ||
590 | |||
591 | |||
382 | /* Helper for regdom_intersect(), this does the real | 592 | /* Helper for regdom_intersect(), this does the real |
383 | * mathematical intersection fun */ | 593 | * mathematical intersection fun */ |
384 | static int reg_rules_intersect( | 594 | static int reg_rules_intersect( |
@@ -663,16 +873,14 @@ static int ignore_request(struct wiphy *wiphy, enum reg_set_by set_by, | |||
663 | return -EOPNOTSUPP; | 873 | return -EOPNOTSUPP; |
664 | return -EALREADY; | 874 | return -EALREADY; |
665 | } | 875 | } |
666 | /* Two consecutive Country IE hints on the same wiphy */ | 876 | /* Two consecutive Country IE hints on the same wiphy. |
667 | if (!alpha2_equal(cfg80211_regdomain->alpha2, alpha2)) | 877 | * This should be picked up early by the driver/stack */ |
878 | if (WARN_ON(!alpha2_equal(cfg80211_regdomain->alpha2, | ||
879 | alpha2))) | ||
668 | return 0; | 880 | return 0; |
669 | return -EALREADY; | 881 | return -EALREADY; |
670 | } | 882 | } |
671 | /* | 883 | return REG_INTERSECT; |
672 | * Ignore Country IE hints for now, need to think about | ||
673 | * what we need to do to support multi-domain operation. | ||
674 | */ | ||
675 | return -EOPNOTSUPP; | ||
676 | case REGDOM_SET_BY_DRIVER: | 884 | case REGDOM_SET_BY_DRIVER: |
677 | if (last_request->initiator == REGDOM_SET_BY_DRIVER) | 885 | if (last_request->initiator == REGDOM_SET_BY_DRIVER) |
678 | return -EALREADY; | 886 | return -EALREADY; |
@@ -680,6 +888,11 @@ static int ignore_request(struct wiphy *wiphy, enum reg_set_by set_by, | |||
680 | case REGDOM_SET_BY_USER: | 888 | case REGDOM_SET_BY_USER: |
681 | if (last_request->initiator == REGDOM_SET_BY_COUNTRY_IE) | 889 | if (last_request->initiator == REGDOM_SET_BY_COUNTRY_IE) |
682 | return REG_INTERSECT; | 890 | return REG_INTERSECT; |
891 | /* If the user knows better the user should set the regdom | ||
892 | * to their country before the IE is picked up */ | ||
893 | if (last_request->initiator == REGDOM_SET_BY_USER && | ||
894 | last_request->intersect) | ||
895 | return -EOPNOTSUPP; | ||
683 | return 0; | 896 | return 0; |
684 | } | 897 | } |
685 | 898 | ||
@@ -688,7 +901,9 @@ static int ignore_request(struct wiphy *wiphy, enum reg_set_by set_by, | |||
688 | 901 | ||
689 | /* Caller must hold &cfg80211_drv_mutex */ | 902 | /* Caller must hold &cfg80211_drv_mutex */ |
690 | int __regulatory_hint(struct wiphy *wiphy, enum reg_set_by set_by, | 903 | int __regulatory_hint(struct wiphy *wiphy, enum reg_set_by set_by, |
691 | const char *alpha2) | 904 | const char *alpha2, |
905 | u32 country_ie_checksum, | ||
906 | enum environment_cap env) | ||
692 | { | 907 | { |
693 | struct regulatory_request *request; | 908 | struct regulatory_request *request; |
694 | bool intersect = false; | 909 | bool intersect = false; |
@@ -711,9 +926,21 @@ int __regulatory_hint(struct wiphy *wiphy, enum reg_set_by set_by, | |||
711 | request->initiator = set_by; | 926 | request->initiator = set_by; |
712 | request->wiphy = wiphy; | 927 | request->wiphy = wiphy; |
713 | request->intersect = intersect; | 928 | request->intersect = intersect; |
929 | request->country_ie_checksum = country_ie_checksum; | ||
930 | request->country_ie_env = env; | ||
714 | 931 | ||
715 | kfree(last_request); | 932 | kfree(last_request); |
716 | last_request = request; | 933 | last_request = request; |
934 | /* | ||
935 | * Note: When CONFIG_WIRELESS_OLD_REGULATORY is enabled | ||
936 | * AND if CRDA is NOT present nothing will happen, if someone | ||
937 | * wants to bother with 11d with OLD_REG you can add a timer. | ||
938 | * If after x amount of time nothing happens you can call: | ||
939 | * | ||
940 | * return set_regdom(country_ie_regdomain); | ||
941 | * | ||
942 | * to intersect with the static rd | ||
943 | */ | ||
717 | return call_crda(alpha2); | 944 | return call_crda(alpha2); |
718 | } | 945 | } |
719 | 946 | ||
@@ -722,11 +949,120 @@ void regulatory_hint(struct wiphy *wiphy, const char *alpha2) | |||
722 | BUG_ON(!alpha2); | 949 | BUG_ON(!alpha2); |
723 | 950 | ||
724 | mutex_lock(&cfg80211_drv_mutex); | 951 | mutex_lock(&cfg80211_drv_mutex); |
725 | __regulatory_hint(wiphy, REGDOM_SET_BY_DRIVER, alpha2); | 952 | __regulatory_hint(wiphy, REGDOM_SET_BY_DRIVER, alpha2, 0, ENVIRON_ANY); |
726 | mutex_unlock(&cfg80211_drv_mutex); | 953 | mutex_unlock(&cfg80211_drv_mutex); |
727 | } | 954 | } |
728 | EXPORT_SYMBOL(regulatory_hint); | 955 | EXPORT_SYMBOL(regulatory_hint); |
729 | 956 | ||
957 | static bool reg_same_country_ie_hint(struct wiphy *wiphy, | ||
958 | u32 country_ie_checksum) | ||
959 | { | ||
960 | if (!last_request->wiphy) | ||
961 | return false; | ||
962 | if (likely(last_request->wiphy != wiphy)) | ||
963 | return !country_ie_integrity_changes(country_ie_checksum); | ||
964 | /* We should not have let these through at this point, they | ||
965 | * should have been picked up earlier by the first alpha2 check | ||
966 | * on the device */ | ||
967 | if (WARN_ON(!country_ie_integrity_changes(country_ie_checksum))) | ||
968 | return true; | ||
969 | return false; | ||
970 | } | ||
971 | |||
972 | void regulatory_hint_11d(struct wiphy *wiphy, | ||
973 | u8 *country_ie, | ||
974 | u8 country_ie_len) | ||
975 | { | ||
976 | struct ieee80211_regdomain *rd = NULL; | ||
977 | char alpha2[2]; | ||
978 | u32 checksum = 0; | ||
979 | enum environment_cap env = ENVIRON_ANY; | ||
980 | |||
981 | mutex_lock(&cfg80211_drv_mutex); | ||
982 | |||
983 | /* IE len must be evenly divisible by 2 */ | ||
984 | if (country_ie_len & 0x01) | ||
985 | goto out; | ||
986 | |||
987 | if (country_ie_len < IEEE80211_COUNTRY_IE_MIN_LEN) | ||
988 | goto out; | ||
989 | |||
990 | /* Pending country IE processing, this can happen after we | ||
991 | * call CRDA and wait for a response if a beacon was received before | ||
992 | * we were able to process the last regulatory_hint_11d() call */ | ||
993 | if (country_ie_regdomain) | ||
994 | goto out; | ||
995 | |||
996 | alpha2[0] = country_ie[0]; | ||
997 | alpha2[1] = country_ie[1]; | ||
998 | |||
999 | if (country_ie[2] == 'I') | ||
1000 | env = ENVIRON_INDOOR; | ||
1001 | else if (country_ie[2] == 'O') | ||
1002 | env = ENVIRON_OUTDOOR; | ||
1003 | |||
1004 | /* We will run this for *every* beacon processed for the BSSID, so | ||
1005 | * we optimize an early check to exit out early if we don't have to | ||
1006 | * do anything */ | ||
1007 | if (likely(last_request->wiphy)) { | ||
1008 | struct cfg80211_registered_device *drv_last_ie; | ||
1009 | |||
1010 | drv_last_ie = wiphy_to_dev(last_request->wiphy); | ||
1011 | |||
1012 | /* Lets keep this simple -- we trust the first AP | ||
1013 | * after we intersect with CRDA */ | ||
1014 | if (likely(last_request->wiphy == wiphy)) { | ||
1015 | /* Ignore IEs coming in on this wiphy with | ||
1016 | * the same alpha2 and environment cap */ | ||
1017 | if (likely(alpha2_equal(drv_last_ie->country_ie_alpha2, | ||
1018 | alpha2) && | ||
1019 | env == drv_last_ie->env)) { | ||
1020 | goto out; | ||
1021 | } | ||
1022 | /* the wiphy moved on to another BSSID or the AP | ||
1023 | * was reconfigured. XXX: We need to deal with the | ||
1024 | * case where the user suspends and goes to goes | ||
1025 | * to another country, and then gets IEs from an | ||
1026 | * AP with different settings */ | ||
1027 | goto out; | ||
1028 | } else { | ||
1029 | /* Ignore IEs coming in on two separate wiphys with | ||
1030 | * the same alpha2 and environment cap */ | ||
1031 | if (likely(alpha2_equal(drv_last_ie->country_ie_alpha2, | ||
1032 | alpha2) && | ||
1033 | env == drv_last_ie->env)) { | ||
1034 | goto out; | ||
1035 | } | ||
1036 | /* We could potentially intersect though */ | ||
1037 | goto out; | ||
1038 | } | ||
1039 | } | ||
1040 | |||
1041 | rd = country_ie_2_rd(country_ie, country_ie_len, &checksum); | ||
1042 | if (!rd) | ||
1043 | goto out; | ||
1044 | |||
1045 | /* This will not happen right now but we leave it here for the | ||
1046 | * the future when we want to add suspend/resume support and having | ||
1047 | * the user move to another country after doing so, or having the user | ||
1048 | * move to another AP. Right now we just trust the first AP. This is why | ||
1049 | * this is marked as likley(). If we hit this before we add this support | ||
1050 | * we want to be informed of it as it would indicate a mistake in the | ||
1051 | * current design */ | ||
1052 | if (likely(WARN_ON(reg_same_country_ie_hint(wiphy, checksum)))) | ||
1053 | goto out; | ||
1054 | |||
1055 | /* We keep this around for when CRDA comes back with a response so | ||
1056 | * we can intersect with that */ | ||
1057 | country_ie_regdomain = rd; | ||
1058 | |||
1059 | __regulatory_hint(wiphy, REGDOM_SET_BY_COUNTRY_IE, | ||
1060 | country_ie_regdomain->alpha2, checksum, env); | ||
1061 | |||
1062 | out: | ||
1063 | mutex_unlock(&cfg80211_drv_mutex); | ||
1064 | } | ||
1065 | EXPORT_SYMBOL(regulatory_hint_11d); | ||
730 | 1066 | ||
731 | static void print_rd_rules(const struct ieee80211_regdomain *rd) | 1067 | static void print_rd_rules(const struct ieee80211_regdomain *rd) |
732 | { | 1068 | { |
@@ -766,7 +1102,25 @@ static void print_rd_rules(const struct ieee80211_regdomain *rd) | |||
766 | static void print_regdomain(const struct ieee80211_regdomain *rd) | 1102 | static void print_regdomain(const struct ieee80211_regdomain *rd) |
767 | { | 1103 | { |
768 | 1104 | ||
769 | if (is_world_regdom(rd->alpha2)) | 1105 | if (is_intersected_alpha2(rd->alpha2)) { |
1106 | struct wiphy *wiphy = NULL; | ||
1107 | struct cfg80211_registered_device *drv; | ||
1108 | |||
1109 | if (last_request->initiator == REGDOM_SET_BY_COUNTRY_IE) { | ||
1110 | if (last_request->wiphy) { | ||
1111 | wiphy = last_request->wiphy; | ||
1112 | drv = wiphy_to_dev(wiphy); | ||
1113 | printk(KERN_INFO "cfg80211: Current regulatory " | ||
1114 | "domain updated by AP to: %c%c\n", | ||
1115 | drv->country_ie_alpha2[0], | ||
1116 | drv->country_ie_alpha2[1]); | ||
1117 | } else | ||
1118 | printk(KERN_INFO "cfg80211: Current regulatory " | ||
1119 | "domain intersected: \n"); | ||
1120 | } else | ||
1121 | printk(KERN_INFO "cfg80211: Current regulatory " | ||
1122 | "intersected: \n"); | ||
1123 | } else if (is_world_regdom(rd->alpha2)) | ||
770 | printk(KERN_INFO "cfg80211: World regulatory " | 1124 | printk(KERN_INFO "cfg80211: World regulatory " |
771 | "domain updated:\n"); | 1125 | "domain updated:\n"); |
772 | else { | 1126 | else { |
@@ -789,10 +1143,39 @@ static void print_regdomain_info(const struct ieee80211_regdomain *rd) | |||
789 | print_rd_rules(rd); | 1143 | print_rd_rules(rd); |
790 | } | 1144 | } |
791 | 1145 | ||
1146 | #ifdef CONFIG_CFG80211_REG_DEBUG | ||
1147 | static void reg_country_ie_process_debug( | ||
1148 | const struct ieee80211_regdomain *rd, | ||
1149 | const struct ieee80211_regdomain *country_ie_regdomain, | ||
1150 | const struct ieee80211_regdomain *intersected_rd) | ||
1151 | { | ||
1152 | printk(KERN_DEBUG "cfg80211: Received country IE:\n"); | ||
1153 | print_regdomain_info(country_ie_regdomain); | ||
1154 | printk(KERN_DEBUG "cfg80211: CRDA thinks this should applied:\n"); | ||
1155 | print_regdomain_info(rd); | ||
1156 | if (intersected_rd) { | ||
1157 | printk(KERN_DEBUG "cfg80211: We intersect both of these " | ||
1158 | "and get:\n"); | ||
1159 | print_regdomain_info(rd); | ||
1160 | return; | ||
1161 | } | ||
1162 | printk(KERN_DEBUG "cfg80211: Intersection between both failed\n"); | ||
1163 | } | ||
1164 | #else | ||
1165 | static inline void reg_country_ie_process_debug( | ||
1166 | const struct ieee80211_regdomain *rd, | ||
1167 | const struct ieee80211_regdomain *country_ie_regdomain, | ||
1168 | const struct ieee80211_regdomain *intersected_rd) | ||
1169 | { | ||
1170 | } | ||
1171 | #endif | ||
1172 | |||
792 | /* Takes ownership of rd only if it doesn't fail */ | 1173 | /* Takes ownership of rd only if it doesn't fail */ |
793 | static int __set_regdom(const struct ieee80211_regdomain *rd) | 1174 | static int __set_regdom(const struct ieee80211_regdomain *rd) |
794 | { | 1175 | { |
795 | const struct ieee80211_regdomain *intersected_rd = NULL; | 1176 | const struct ieee80211_regdomain *intersected_rd = NULL; |
1177 | struct cfg80211_registered_device *drv = NULL; | ||
1178 | struct wiphy *wiphy = NULL; | ||
796 | /* Some basic sanity checks first */ | 1179 | /* Some basic sanity checks first */ |
797 | 1180 | ||
798 | if (is_world_regdom(rd->alpha2)) { | 1181 | if (is_world_regdom(rd->alpha2)) { |
@@ -809,10 +1192,18 @@ static int __set_regdom(const struct ieee80211_regdomain *rd) | |||
809 | if (!last_request) | 1192 | if (!last_request) |
810 | return -EINVAL; | 1193 | return -EINVAL; |
811 | 1194 | ||
812 | /* allow overriding the static definitions if CRDA is present */ | 1195 | /* Lets only bother proceeding on the same alpha2 if the current |
813 | if (!is_old_static_regdom(cfg80211_regdomain) && | 1196 | * rd is non static (it means CRDA was present and was used last) |
814 | !regdom_changed(rd->alpha2)) | 1197 | * and the pending request came in from a country IE */ |
815 | return -EINVAL; | 1198 | if (last_request->initiator != REGDOM_SET_BY_COUNTRY_IE) { |
1199 | /* If someone else asked us to change the rd lets only bother | ||
1200 | * checking if the alpha2 changes if CRDA was already called */ | ||
1201 | if (!is_old_static_regdom(cfg80211_regdomain) && | ||
1202 | !regdom_changed(rd->alpha2)) | ||
1203 | return -EINVAL; | ||
1204 | } | ||
1205 | |||
1206 | wiphy = last_request->wiphy; | ||
816 | 1207 | ||
817 | /* Now lets set the regulatory domain, update all driver channels | 1208 | /* Now lets set the regulatory domain, update all driver channels |
818 | * and finally inform them of what we have done, in case they want | 1209 | * and finally inform them of what we have done, in case they want |
@@ -853,9 +1244,47 @@ static int __set_regdom(const struct ieee80211_regdomain *rd) | |||
853 | return 0; | 1244 | return 0; |
854 | } | 1245 | } |
855 | 1246 | ||
856 | /* Country IE parsing coming soon */ | 1247 | /* |
1248 | * Country IE requests are handled a bit differently, we intersect | ||
1249 | * the country IE rd with what CRDA believes that country should have | ||
1250 | */ | ||
1251 | |||
1252 | BUG_ON(!country_ie_regdomain); | ||
1253 | |||
1254 | if (rd != country_ie_regdomain) { | ||
1255 | /* Intersect what CRDA returned and our what we | ||
1256 | * had built from the Country IE received */ | ||
1257 | |||
1258 | intersected_rd = regdom_intersect(rd, country_ie_regdomain); | ||
1259 | |||
1260 | reg_country_ie_process_debug(rd, country_ie_regdomain, | ||
1261 | intersected_rd); | ||
1262 | |||
1263 | kfree(country_ie_regdomain); | ||
1264 | country_ie_regdomain = NULL; | ||
1265 | } else { | ||
1266 | /* This would happen when CRDA was not present and | ||
1267 | * OLD_REGULATORY was enabled. We intersect our Country | ||
1268 | * IE rd and what was set on cfg80211 originally */ | ||
1269 | intersected_rd = regdom_intersect(rd, cfg80211_regdomain); | ||
1270 | } | ||
1271 | |||
1272 | if (!intersected_rd) | ||
1273 | return -EINVAL; | ||
1274 | |||
1275 | drv = wiphy_to_dev(wiphy); | ||
1276 | |||
1277 | drv->country_ie_alpha2[0] = rd->alpha2[0]; | ||
1278 | drv->country_ie_alpha2[1] = rd->alpha2[1]; | ||
1279 | drv->env = last_request->country_ie_env; | ||
1280 | |||
1281 | BUG_ON(intersected_rd == rd); | ||
1282 | |||
1283 | kfree(rd); | ||
1284 | rd = NULL; | ||
1285 | |||
857 | reset_regdomains(); | 1286 | reset_regdomains(); |
858 | WARN_ON(1); | 1287 | cfg80211_regdomain = intersected_rd; |
859 | 1288 | ||
860 | return 0; | 1289 | return 0; |
861 | } | 1290 | } |
@@ -887,6 +1316,17 @@ int set_regdom(const struct ieee80211_regdomain *rd) | |||
887 | return r; | 1316 | return r; |
888 | } | 1317 | } |
889 | 1318 | ||
1319 | /* Caller must hold cfg80211_drv_mutex */ | ||
1320 | void reg_device_remove(struct wiphy *wiphy) | ||
1321 | { | ||
1322 | if (!last_request->wiphy) | ||
1323 | return; | ||
1324 | if (last_request->wiphy != wiphy) | ||
1325 | return; | ||
1326 | last_request->wiphy = NULL; | ||
1327 | last_request->country_ie_env = ENVIRON_ANY; | ||
1328 | } | ||
1329 | |||
890 | int regulatory_init(void) | 1330 | int regulatory_init(void) |
891 | { | 1331 | { |
892 | int err; | 1332 | int err; |
@@ -906,11 +1346,11 @@ int regulatory_init(void) | |||
906 | * that is not a valid ISO / IEC 3166 alpha2 */ | 1346 | * that is not a valid ISO / IEC 3166 alpha2 */ |
907 | if (ieee80211_regdom[0] != 'E' || ieee80211_regdom[1] != 'U') | 1347 | if (ieee80211_regdom[0] != 'E' || ieee80211_regdom[1] != 'U') |
908 | err = __regulatory_hint(NULL, REGDOM_SET_BY_CORE, | 1348 | err = __regulatory_hint(NULL, REGDOM_SET_BY_CORE, |
909 | ieee80211_regdom); | 1349 | ieee80211_regdom, 0, ENVIRON_ANY); |
910 | #else | 1350 | #else |
911 | cfg80211_regdomain = cfg80211_world_regdom; | 1351 | cfg80211_regdomain = cfg80211_world_regdom; |
912 | 1352 | ||
913 | err = __regulatory_hint(NULL, REGDOM_SET_BY_CORE, "00"); | 1353 | err = __regulatory_hint(NULL, REGDOM_SET_BY_CORE, "00", 0, ENVIRON_ANY); |
914 | if (err) | 1354 | if (err) |
915 | printk(KERN_ERR "cfg80211: calling CRDA failed - " | 1355 | printk(KERN_ERR "cfg80211: calling CRDA failed - " |
916 | "unable to update world regulatory domain, " | 1356 | "unable to update world regulatory domain, " |
@@ -926,6 +1366,9 @@ void regulatory_exit(void) | |||
926 | 1366 | ||
927 | reset_regdomains(); | 1367 | reset_regdomains(); |
928 | 1368 | ||
1369 | kfree(country_ie_regdomain); | ||
1370 | country_ie_regdomain = NULL; | ||
1371 | |||
929 | kfree(last_request); | 1372 | kfree(last_request); |
930 | 1373 | ||
931 | platform_device_unregister(reg_pdev); | 1374 | platform_device_unregister(reg_pdev); |
diff --git a/net/wireless/reg.h b/net/wireless/reg.h index c9b6b6358bbe..a76ea3ff7cd6 100644 --- a/net/wireless/reg.h +++ b/net/wireless/reg.h | |||
@@ -4,28 +4,41 @@ | |||
4 | bool is_world_regdom(const char *alpha2); | 4 | bool is_world_regdom(const char *alpha2); |
5 | bool reg_is_valid_request(const char *alpha2); | 5 | bool reg_is_valid_request(const char *alpha2); |
6 | 6 | ||
7 | void reg_device_remove(struct wiphy *wiphy); | ||
8 | |||
7 | int regulatory_init(void); | 9 | int regulatory_init(void); |
8 | void regulatory_exit(void); | 10 | void regulatory_exit(void); |
9 | 11 | ||
10 | int set_regdom(const struct ieee80211_regdomain *rd); | 12 | int set_regdom(const struct ieee80211_regdomain *rd); |
11 | 13 | ||
14 | enum environment_cap { | ||
15 | ENVIRON_ANY, | ||
16 | ENVIRON_INDOOR, | ||
17 | ENVIRON_OUTDOOR, | ||
18 | }; | ||
19 | |||
20 | |||
12 | /** | 21 | /** |
13 | * __regulatory_hint - hint to the wireless core a regulatory domain | 22 | * __regulatory_hint - hint to the wireless core a regulatory domain |
14 | * @wiphy: if the hint comes from country information from an AP, this | 23 | * @wiphy: if the hint comes from country information from an AP, this |
15 | * is required to be set to the wiphy that received the information | 24 | * is required to be set to the wiphy that received the information |
16 | * @alpha2: the ISO/IEC 3166 alpha2 being claimed the regulatory domain | 25 | * @alpha2: the ISO/IEC 3166 alpha2 being claimed the regulatory domain |
17 | * should be in. | 26 | * should be in. |
27 | * @country_ie_checksum: checksum of processed country IE, set this to 0 | ||
28 | * if the hint did not come from a country IE | ||
29 | * @country_ie_env: the environment the IE told us we are in, %ENVIRON_* | ||
18 | * | 30 | * |
19 | * The Wireless subsystem can use this function to hint to the wireless core | 31 | * The Wireless subsystem can use this function to hint to the wireless core |
20 | * what it believes should be the current regulatory domain by | 32 | * what it believes should be the current regulatory domain by giving it an |
21 | * giving it an ISO/IEC 3166 alpha2 country code it knows its regulatory | 33 | * ISO/IEC 3166 alpha2 country code it knows its regulatory domain should be |
22 | * domain should be in. | 34 | * in. |
23 | * | 35 | * |
24 | * Returns zero if all went fine, %-EALREADY if a regulatory domain had | 36 | * Returns zero if all went fine, %-EALREADY if a regulatory domain had |
25 | * already been set or other standard error codes. | 37 | * already been set or other standard error codes. |
26 | * | 38 | * |
27 | */ | 39 | */ |
28 | extern int __regulatory_hint(struct wiphy *wiphy, enum reg_set_by set_by, | 40 | extern int __regulatory_hint(struct wiphy *wiphy, enum reg_set_by set_by, |
29 | const char *alpha2); | 41 | const char *alpha2, u32 country_ie_checksum, |
42 | enum environment_cap country_ie_env); | ||
30 | 43 | ||
31 | #endif /* __NET_WIRELESS_REG_H */ | 44 | #endif /* __NET_WIRELESS_REG_H */ |