18318d78aSJohannes Berg /* 28318d78aSJohannes Berg * Copyright 2002-2005, Instant802 Networks, Inc. 38318d78aSJohannes Berg * Copyright 2005-2006, Devicescape Software, Inc. 48318d78aSJohannes Berg * Copyright 2007 Johannes Berg <johannes@sipsolutions.net> 53b77d5ecSLuis R. Rodriguez * Copyright 2008-2011 Luis R. Rodriguez <mcgrof@qca.qualcomm.com> 68318d78aSJohannes Berg * 73b77d5ecSLuis R. Rodriguez * Permission to use, copy, modify, and/or distribute this software for any 83b77d5ecSLuis R. Rodriguez * purpose with or without fee is hereby granted, provided that the above 93b77d5ecSLuis R. Rodriguez * copyright notice and this permission notice appear in all copies. 103b77d5ecSLuis R. Rodriguez * 113b77d5ecSLuis R. Rodriguez * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 123b77d5ecSLuis R. Rodriguez * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 133b77d5ecSLuis R. Rodriguez * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 143b77d5ecSLuis R. Rodriguez * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 153b77d5ecSLuis R. Rodriguez * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 163b77d5ecSLuis R. Rodriguez * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 173b77d5ecSLuis R. Rodriguez * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 188318d78aSJohannes Berg */ 198318d78aSJohannes Berg 203b77d5ecSLuis R. Rodriguez 21b2e1b302SLuis R. Rodriguez /** 22b2e1b302SLuis R. Rodriguez * DOC: Wireless regulatory infrastructure 238318d78aSJohannes Berg * 248318d78aSJohannes Berg * The usual implementation is for a driver to read a device EEPROM to 258318d78aSJohannes Berg * determine which regulatory domain it should be operating under, then 268318d78aSJohannes Berg * looking up the allowable channels in a driver-local table and finally 278318d78aSJohannes Berg * registering those channels in the wiphy structure. 288318d78aSJohannes Berg * 29b2e1b302SLuis R. Rodriguez * Another set of compliance enforcement is for drivers to use their 30b2e1b302SLuis R. Rodriguez * own compliance limits which can be stored on the EEPROM. The host 31b2e1b302SLuis R. Rodriguez * driver or firmware may ensure these are used. 32b2e1b302SLuis R. Rodriguez * 33b2e1b302SLuis R. Rodriguez * In addition to all this we provide an extra layer of regulatory 34b2e1b302SLuis R. Rodriguez * conformance. For drivers which do not have any regulatory 35b2e1b302SLuis R. Rodriguez * information CRDA provides the complete regulatory solution. 36b2e1b302SLuis R. Rodriguez * For others it provides a community effort on further restrictions 37b2e1b302SLuis R. Rodriguez * to enhance compliance. 38b2e1b302SLuis R. Rodriguez * 39b2e1b302SLuis R. Rodriguez * Note: When number of rules --> infinity we will not be able to 40b2e1b302SLuis R. Rodriguez * index on alpha2 any more, instead we'll probably have to 41b2e1b302SLuis R. Rodriguez * rely on some SHA1 checksum of the regdomain for example. 42b2e1b302SLuis R. Rodriguez * 438318d78aSJohannes Berg */ 44e9c0268fSJoe Perches 45e9c0268fSJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 46e9c0268fSJoe Perches 478318d78aSJohannes Berg #include <linux/kernel.h> 48bc3b2d7fSPaul Gortmaker #include <linux/export.h> 495a0e3ad6STejun Heo #include <linux/slab.h> 50b2e1b302SLuis R. Rodriguez #include <linux/list.h> 51b2e1b302SLuis R. Rodriguez #include <linux/random.h> 52c61029c7SJohn W. Linville #include <linux/ctype.h> 53b2e1b302SLuis R. Rodriguez #include <linux/nl80211.h> 54b2e1b302SLuis R. Rodriguez #include <linux/platform_device.h> 55d9b93842SPaul Gortmaker #include <linux/moduleparam.h> 56b2e1b302SLuis R. Rodriguez #include <net/cfg80211.h> 578318d78aSJohannes Berg #include "core.h" 58b2e1b302SLuis R. Rodriguez #include "reg.h" 593b377ea9SJohn W. Linville #include "regdb.h" 6073d54c9eSLuis R. Rodriguez #include "nl80211.h" 618318d78aSJohannes Berg 624113f751SLuis R. Rodriguez #ifdef CONFIG_CFG80211_REG_DEBUG 638271195eSJohn W. Linville #define REG_DBG_PRINT(format, args...) \ 6412c5ffb5SJoe Perches printk(KERN_DEBUG pr_fmt(format), ##args) 654113f751SLuis R. Rodriguez #else 668271195eSJohn W. Linville #define REG_DBG_PRINT(args...) 674113f751SLuis R. Rodriguez #endif 684113f751SLuis R. Rodriguez 69a042994dSLuis R. Rodriguez static struct regulatory_request core_request_world = { 70a042994dSLuis R. Rodriguez .initiator = NL80211_REGDOM_SET_BY_CORE, 71a042994dSLuis R. Rodriguez .alpha2[0] = '0', 72a042994dSLuis R. Rodriguez .alpha2[1] = '0', 73a042994dSLuis R. Rodriguez .intersect = false, 74a042994dSLuis R. Rodriguez .processed = true, 75a042994dSLuis R. Rodriguez .country_ie_env = ENVIRON_ANY, 76a042994dSLuis R. Rodriguez }; 77a042994dSLuis R. Rodriguez 785166ccd2SLuis R. Rodriguez /* Receipt of information from last regulatory request */ 79a042994dSLuis R. Rodriguez static struct regulatory_request *last_request = &core_request_world; 80734366deSJohannes Berg 81b2e1b302SLuis R. Rodriguez /* To trigger userspace events */ 82b2e1b302SLuis R. Rodriguez static struct platform_device *reg_pdev; 838318d78aSJohannes Berg 844d9d88d1SScott James Remnant static struct device_type reg_device_type = { 854d9d88d1SScott James Remnant .uevent = reg_device_uevent, 864d9d88d1SScott James Remnant }; 874d9d88d1SScott James Remnant 88fb1fc7adSLuis R. Rodriguez /* 89fb1fc7adSLuis R. Rodriguez * Central wireless core regulatory domains, we only need two, 90734366deSJohannes Berg * the current one and a world regulatory domain in case we have no 91fb1fc7adSLuis R. Rodriguez * information to give us an alpha2 92fb1fc7adSLuis R. Rodriguez */ 93f130347cSLuis R. Rodriguez const struct ieee80211_regdomain *cfg80211_regdomain; 94734366deSJohannes Berg 95fb1fc7adSLuis R. Rodriguez /* 96abc7381bSLuis R. Rodriguez * Protects static reg.c components: 97abc7381bSLuis R. Rodriguez * - cfg80211_world_regdom 98abc7381bSLuis R. Rodriguez * - cfg80211_regdom 99abc7381bSLuis R. Rodriguez * - last_request 10057b5ce07SLuis R. Rodriguez * - reg_num_devs_support_basehint 101abc7381bSLuis R. Rodriguez */ 102670b7f11SJohn W. Linville static DEFINE_MUTEX(reg_mutex); 10346a5ebafSJohannes Berg 10457b5ce07SLuis R. Rodriguez /* 10557b5ce07SLuis R. Rodriguez * Number of devices that registered to the core 10657b5ce07SLuis R. Rodriguez * that support cellular base station regulatory hints 10757b5ce07SLuis R. Rodriguez */ 10857b5ce07SLuis R. Rodriguez static int reg_num_devs_support_basehint; 10957b5ce07SLuis R. Rodriguez 11046a5ebafSJohannes Berg static inline void assert_reg_lock(void) 11146a5ebafSJohannes Berg { 11246a5ebafSJohannes Berg lockdep_assert_held(®_mutex); 11346a5ebafSJohannes Berg } 114abc7381bSLuis R. Rodriguez 115e38f8a7aSLuis R. Rodriguez /* Used to queue up regulatory hints */ 116fe33eb39SLuis R. Rodriguez static LIST_HEAD(reg_requests_list); 117fe33eb39SLuis R. Rodriguez static spinlock_t reg_requests_lock; 118fe33eb39SLuis R. Rodriguez 119e38f8a7aSLuis R. Rodriguez /* Used to queue up beacon hints for review */ 120e38f8a7aSLuis R. Rodriguez static LIST_HEAD(reg_pending_beacons); 121e38f8a7aSLuis R. Rodriguez static spinlock_t reg_pending_beacons_lock; 122e38f8a7aSLuis R. Rodriguez 123e38f8a7aSLuis R. Rodriguez /* Used to keep track of processed beacon hints */ 124e38f8a7aSLuis R. Rodriguez static LIST_HEAD(reg_beacon_list); 125e38f8a7aSLuis R. Rodriguez 126e38f8a7aSLuis R. Rodriguez struct reg_beacon { 127e38f8a7aSLuis R. Rodriguez struct list_head list; 128e38f8a7aSLuis R. Rodriguez struct ieee80211_channel chan; 129e38f8a7aSLuis R. Rodriguez }; 130e38f8a7aSLuis R. Rodriguez 131f333a7a2SLuis R. Rodriguez static void reg_todo(struct work_struct *work); 132f333a7a2SLuis R. Rodriguez static DECLARE_WORK(reg_work, reg_todo); 133f333a7a2SLuis R. Rodriguez 134a90c7a31SLuis R. Rodriguez static void reg_timeout_work(struct work_struct *work); 135a90c7a31SLuis R. Rodriguez static DECLARE_DELAYED_WORK(reg_timeout, reg_timeout_work); 136a90c7a31SLuis R. Rodriguez 137734366deSJohannes Berg /* We keep a static world regulatory domain in case of the absence of CRDA */ 138734366deSJohannes Berg static const struct ieee80211_regdomain world_regdom = { 13990cdc6dfSVladimir Kondratiev .n_reg_rules = 6, 140734366deSJohannes Berg .alpha2 = "00", 141734366deSJohannes Berg .reg_rules = { 14268798a62SLuis R. Rodriguez /* IEEE 802.11b/g, channels 1..11 */ 14368798a62SLuis R. Rodriguez REG_RULE(2412-10, 2462+10, 40, 6, 20, 0), 144611b6a82SLuis R. Rodriguez /* IEEE 802.11b/g, channels 12..13. No HT40 145611b6a82SLuis R. Rodriguez * channel fits here. */ 146611b6a82SLuis R. Rodriguez REG_RULE(2467-10, 2472+10, 20, 6, 20, 147611b6a82SLuis R. Rodriguez NL80211_RRF_PASSIVE_SCAN | 148611b6a82SLuis R. Rodriguez NL80211_RRF_NO_IBSS), 149611b6a82SLuis R. Rodriguez /* IEEE 802.11 channel 14 - Only JP enables 150611b6a82SLuis R. Rodriguez * this and for 802.11b only */ 151611b6a82SLuis R. Rodriguez REG_RULE(2484-10, 2484+10, 20, 6, 20, 152611b6a82SLuis R. Rodriguez NL80211_RRF_PASSIVE_SCAN | 153611b6a82SLuis R. Rodriguez NL80211_RRF_NO_IBSS | 154611b6a82SLuis R. Rodriguez NL80211_RRF_NO_OFDM), 1553fc71f77SLuis R. Rodriguez /* IEEE 802.11a, channel 36..48 */ 156ec329aceSLuis R. Rodriguez REG_RULE(5180-10, 5240+10, 40, 6, 20, 1573fc71f77SLuis R. Rodriguez NL80211_RRF_PASSIVE_SCAN | 1583fc71f77SLuis R. Rodriguez NL80211_RRF_NO_IBSS), 1593fc71f77SLuis R. Rodriguez 1603fc71f77SLuis R. Rodriguez /* NB: 5260 MHz - 5700 MHz requies DFS */ 1613fc71f77SLuis R. Rodriguez 1623fc71f77SLuis R. Rodriguez /* IEEE 802.11a, channel 149..165 */ 163ec329aceSLuis R. Rodriguez REG_RULE(5745-10, 5825+10, 40, 6, 20, 1643fc71f77SLuis R. Rodriguez NL80211_RRF_PASSIVE_SCAN | 1653fc71f77SLuis R. Rodriguez NL80211_RRF_NO_IBSS), 16690cdc6dfSVladimir Kondratiev 16790cdc6dfSVladimir Kondratiev /* IEEE 802.11ad (60gHz), channels 1..3 */ 16890cdc6dfSVladimir Kondratiev REG_RULE(56160+2160*1-1080, 56160+2160*3+1080, 2160, 0, 0, 0), 169734366deSJohannes Berg } 170734366deSJohannes Berg }; 171734366deSJohannes Berg 172a3d2eaf0SJohannes Berg static const struct ieee80211_regdomain *cfg80211_world_regdom = 173a3d2eaf0SJohannes Berg &world_regdom; 174734366deSJohannes Berg 1756ee7d330SLuis R. Rodriguez static char *ieee80211_regdom = "00"; 17609d989d1SLuis R. Rodriguez static char user_alpha2[2]; 1776ee7d330SLuis R. Rodriguez 178734366deSJohannes Berg module_param(ieee80211_regdom, charp, 0444); 179734366deSJohannes Berg MODULE_PARM_DESC(ieee80211_regdom, "IEEE 802.11 regulatory domain code"); 180734366deSJohannes Berg 181a042994dSLuis R. Rodriguez static void reset_regdomains(bool full_reset) 182734366deSJohannes Berg { 183942b25cfSJohannes Berg /* avoid freeing static information or freeing something twice */ 184942b25cfSJohannes Berg if (cfg80211_regdomain == cfg80211_world_regdom) 185942b25cfSJohannes Berg cfg80211_regdomain = NULL; 186942b25cfSJohannes Berg if (cfg80211_world_regdom == &world_regdom) 187942b25cfSJohannes Berg cfg80211_world_regdom = NULL; 188942b25cfSJohannes Berg if (cfg80211_regdomain == &world_regdom) 189942b25cfSJohannes Berg cfg80211_regdomain = NULL; 190942b25cfSJohannes Berg 191734366deSJohannes Berg kfree(cfg80211_regdomain); 192734366deSJohannes Berg kfree(cfg80211_world_regdom); 193734366deSJohannes Berg 194a3d2eaf0SJohannes Berg cfg80211_world_regdom = &world_regdom; 195734366deSJohannes Berg cfg80211_regdomain = NULL; 196a042994dSLuis R. Rodriguez 197a042994dSLuis R. Rodriguez if (!full_reset) 198a042994dSLuis R. Rodriguez return; 199a042994dSLuis R. Rodriguez 200a042994dSLuis R. Rodriguez if (last_request != &core_request_world) 201a042994dSLuis R. Rodriguez kfree(last_request); 202a042994dSLuis R. Rodriguez last_request = &core_request_world; 203734366deSJohannes Berg } 204734366deSJohannes Berg 205fb1fc7adSLuis R. Rodriguez /* 206fb1fc7adSLuis R. Rodriguez * Dynamic world regulatory domain requested by the wireless 207fb1fc7adSLuis R. Rodriguez * core upon initialization 208fb1fc7adSLuis R. Rodriguez */ 209a3d2eaf0SJohannes Berg static void update_world_regdomain(const struct ieee80211_regdomain *rd) 210734366deSJohannes Berg { 211f6037d09SJohannes Berg BUG_ON(!last_request); 212734366deSJohannes Berg 213a042994dSLuis R. Rodriguez reset_regdomains(false); 214734366deSJohannes Berg 215734366deSJohannes Berg cfg80211_world_regdom = rd; 216734366deSJohannes Berg cfg80211_regdomain = rd; 217734366deSJohannes Berg } 218734366deSJohannes Berg 219a3d2eaf0SJohannes Berg bool is_world_regdom(const char *alpha2) 220b2e1b302SLuis R. Rodriguez { 221b2e1b302SLuis R. Rodriguez if (!alpha2) 222b2e1b302SLuis R. Rodriguez return false; 223b2e1b302SLuis R. Rodriguez if (alpha2[0] == '0' && alpha2[1] == '0') 224b2e1b302SLuis R. Rodriguez return true; 225b2e1b302SLuis R. Rodriguez return false; 226b2e1b302SLuis R. Rodriguez } 227b2e1b302SLuis R. Rodriguez 228a3d2eaf0SJohannes Berg static bool is_alpha2_set(const char *alpha2) 229b2e1b302SLuis R. Rodriguez { 230b2e1b302SLuis R. Rodriguez if (!alpha2) 231b2e1b302SLuis R. Rodriguez return false; 232b2e1b302SLuis R. Rodriguez if (alpha2[0] != 0 && alpha2[1] != 0) 233b2e1b302SLuis R. Rodriguez return true; 234b2e1b302SLuis R. Rodriguez return false; 235b2e1b302SLuis R. Rodriguez } 236b2e1b302SLuis R. Rodriguez 237a3d2eaf0SJohannes Berg static bool is_unknown_alpha2(const char *alpha2) 238b2e1b302SLuis R. Rodriguez { 239b2e1b302SLuis R. Rodriguez if (!alpha2) 240b2e1b302SLuis R. Rodriguez return false; 241fb1fc7adSLuis R. Rodriguez /* 242fb1fc7adSLuis R. Rodriguez * Special case where regulatory domain was built by driver 243fb1fc7adSLuis R. Rodriguez * but a specific alpha2 cannot be determined 244fb1fc7adSLuis R. Rodriguez */ 245b2e1b302SLuis R. Rodriguez if (alpha2[0] == '9' && alpha2[1] == '9') 246b2e1b302SLuis R. Rodriguez return true; 247b2e1b302SLuis R. Rodriguez return false; 248b2e1b302SLuis R. Rodriguez } 249b2e1b302SLuis R. Rodriguez 2503f2355cbSLuis R. Rodriguez static bool is_intersected_alpha2(const char *alpha2) 2513f2355cbSLuis R. Rodriguez { 2523f2355cbSLuis R. Rodriguez if (!alpha2) 2533f2355cbSLuis R. Rodriguez return false; 254fb1fc7adSLuis R. Rodriguez /* 255fb1fc7adSLuis R. Rodriguez * Special case where regulatory domain is the 2563f2355cbSLuis R. Rodriguez * result of an intersection between two regulatory domain 257fb1fc7adSLuis R. Rodriguez * structures 258fb1fc7adSLuis R. Rodriguez */ 2593f2355cbSLuis R. Rodriguez if (alpha2[0] == '9' && alpha2[1] == '8') 2603f2355cbSLuis R. Rodriguez return true; 2613f2355cbSLuis R. Rodriguez return false; 2623f2355cbSLuis R. Rodriguez } 2633f2355cbSLuis R. Rodriguez 264a3d2eaf0SJohannes Berg static bool is_an_alpha2(const char *alpha2) 265b2e1b302SLuis R. Rodriguez { 266b2e1b302SLuis R. Rodriguez if (!alpha2) 267b2e1b302SLuis R. Rodriguez return false; 268c61029c7SJohn W. Linville if (isalpha(alpha2[0]) && isalpha(alpha2[1])) 269b2e1b302SLuis R. Rodriguez return true; 270b2e1b302SLuis R. Rodriguez return false; 271b2e1b302SLuis R. Rodriguez } 272b2e1b302SLuis R. Rodriguez 273a3d2eaf0SJohannes Berg static bool alpha2_equal(const char *alpha2_x, const char *alpha2_y) 274b2e1b302SLuis R. Rodriguez { 275b2e1b302SLuis R. Rodriguez if (!alpha2_x || !alpha2_y) 276b2e1b302SLuis R. Rodriguez return false; 277b2e1b302SLuis R. Rodriguez if (alpha2_x[0] == alpha2_y[0] && 278b2e1b302SLuis R. Rodriguez alpha2_x[1] == alpha2_y[1]) 279b2e1b302SLuis R. Rodriguez return true; 280b2e1b302SLuis R. Rodriguez return false; 281b2e1b302SLuis R. Rodriguez } 282b2e1b302SLuis R. Rodriguez 28369b1572bSLuis R. Rodriguez static bool regdom_changes(const char *alpha2) 284b2e1b302SLuis R. Rodriguez { 285761cf7ecSLuis R. Rodriguez assert_cfg80211_lock(); 286761cf7ecSLuis R. Rodriguez 287b2e1b302SLuis R. Rodriguez if (!cfg80211_regdomain) 288b2e1b302SLuis R. Rodriguez return true; 289b2e1b302SLuis R. Rodriguez if (alpha2_equal(cfg80211_regdomain->alpha2, alpha2)) 290b2e1b302SLuis R. Rodriguez return false; 291b2e1b302SLuis R. Rodriguez return true; 292b2e1b302SLuis R. Rodriguez } 293b2e1b302SLuis R. Rodriguez 29409d989d1SLuis R. Rodriguez /* 29509d989d1SLuis R. Rodriguez * The NL80211_REGDOM_SET_BY_USER regdom alpha2 is cached, this lets 29609d989d1SLuis R. Rodriguez * you know if a valid regulatory hint with NL80211_REGDOM_SET_BY_USER 29709d989d1SLuis R. Rodriguez * has ever been issued. 29809d989d1SLuis R. Rodriguez */ 29909d989d1SLuis R. Rodriguez static bool is_user_regdom_saved(void) 30009d989d1SLuis R. Rodriguez { 30109d989d1SLuis R. Rodriguez if (user_alpha2[0] == '9' && user_alpha2[1] == '7') 30209d989d1SLuis R. Rodriguez return false; 30309d989d1SLuis R. Rodriguez 30409d989d1SLuis R. Rodriguez /* This would indicate a mistake on the design */ 30509d989d1SLuis R. Rodriguez if (WARN((!is_world_regdom(user_alpha2) && 30609d989d1SLuis R. Rodriguez !is_an_alpha2(user_alpha2)), 30709d989d1SLuis R. Rodriguez "Unexpected user alpha2: %c%c\n", 30809d989d1SLuis R. Rodriguez user_alpha2[0], 30909d989d1SLuis R. Rodriguez user_alpha2[1])) 31009d989d1SLuis R. Rodriguez return false; 31109d989d1SLuis R. Rodriguez 31209d989d1SLuis R. Rodriguez return true; 31309d989d1SLuis R. Rodriguez } 31409d989d1SLuis R. Rodriguez 3153b377ea9SJohn W. Linville static int reg_copy_regd(const struct ieee80211_regdomain **dst_regd, 3163b377ea9SJohn W. Linville const struct ieee80211_regdomain *src_regd) 3173b377ea9SJohn W. Linville { 3183b377ea9SJohn W. Linville struct ieee80211_regdomain *regd; 3193b377ea9SJohn W. Linville int size_of_regd = 0; 3203b377ea9SJohn W. Linville unsigned int i; 3213b377ea9SJohn W. Linville 3223b377ea9SJohn W. Linville size_of_regd = sizeof(struct ieee80211_regdomain) + 3233b377ea9SJohn W. Linville ((src_regd->n_reg_rules + 1) * sizeof(struct ieee80211_reg_rule)); 3243b377ea9SJohn W. Linville 3253b377ea9SJohn W. Linville regd = kzalloc(size_of_regd, GFP_KERNEL); 3263b377ea9SJohn W. Linville if (!regd) 3273b377ea9SJohn W. Linville return -ENOMEM; 3283b377ea9SJohn W. Linville 3293b377ea9SJohn W. Linville memcpy(regd, src_regd, sizeof(struct ieee80211_regdomain)); 3303b377ea9SJohn W. Linville 3313b377ea9SJohn W. Linville for (i = 0; i < src_regd->n_reg_rules; i++) 3323b377ea9SJohn W. Linville memcpy(®d->reg_rules[i], &src_regd->reg_rules[i], 3333b377ea9SJohn W. Linville sizeof(struct ieee80211_reg_rule)); 3343b377ea9SJohn W. Linville 3353b377ea9SJohn W. Linville *dst_regd = regd; 3363b377ea9SJohn W. Linville return 0; 3373b377ea9SJohn W. Linville } 3383b377ea9SJohn W. Linville 3393b377ea9SJohn W. Linville #ifdef CONFIG_CFG80211_INTERNAL_REGDB 3403b377ea9SJohn W. Linville struct reg_regdb_search_request { 3413b377ea9SJohn W. Linville char alpha2[2]; 3423b377ea9SJohn W. Linville struct list_head list; 3433b377ea9SJohn W. Linville }; 3443b377ea9SJohn W. Linville 3453b377ea9SJohn W. Linville static LIST_HEAD(reg_regdb_search_list); 346368d06f5SJohn W. Linville static DEFINE_MUTEX(reg_regdb_search_mutex); 3473b377ea9SJohn W. Linville 3483b377ea9SJohn W. Linville static void reg_regdb_search(struct work_struct *work) 3493b377ea9SJohn W. Linville { 3503b377ea9SJohn W. Linville struct reg_regdb_search_request *request; 3513b377ea9SJohn W. Linville const struct ieee80211_regdomain *curdom, *regdom; 3523b377ea9SJohn W. Linville int i, r; 3533b377ea9SJohn W. Linville 354368d06f5SJohn W. Linville mutex_lock(®_regdb_search_mutex); 3553b377ea9SJohn W. Linville while (!list_empty(®_regdb_search_list)) { 3563b377ea9SJohn W. Linville request = list_first_entry(®_regdb_search_list, 3573b377ea9SJohn W. Linville struct reg_regdb_search_request, 3583b377ea9SJohn W. Linville list); 3593b377ea9SJohn W. Linville list_del(&request->list); 3603b377ea9SJohn W. Linville 3613b377ea9SJohn W. Linville for (i=0; i<reg_regdb_size; i++) { 3623b377ea9SJohn W. Linville curdom = reg_regdb[i]; 3633b377ea9SJohn W. Linville 3643b377ea9SJohn W. Linville if (!memcmp(request->alpha2, curdom->alpha2, 2)) { 3653b377ea9SJohn W. Linville r = reg_copy_regd(®dom, curdom); 3663b377ea9SJohn W. Linville if (r) 3673b377ea9SJohn W. Linville break; 3683b377ea9SJohn W. Linville mutex_lock(&cfg80211_mutex); 3693b377ea9SJohn W. Linville set_regdom(regdom); 3703b377ea9SJohn W. Linville mutex_unlock(&cfg80211_mutex); 3713b377ea9SJohn W. Linville break; 3723b377ea9SJohn W. Linville } 3733b377ea9SJohn W. Linville } 3743b377ea9SJohn W. Linville 3753b377ea9SJohn W. Linville kfree(request); 3763b377ea9SJohn W. Linville } 377368d06f5SJohn W. Linville mutex_unlock(®_regdb_search_mutex); 3783b377ea9SJohn W. Linville } 3793b377ea9SJohn W. Linville 3803b377ea9SJohn W. Linville static DECLARE_WORK(reg_regdb_work, reg_regdb_search); 3813b377ea9SJohn W. Linville 3823b377ea9SJohn W. Linville static void reg_regdb_query(const char *alpha2) 3833b377ea9SJohn W. Linville { 3843b377ea9SJohn W. Linville struct reg_regdb_search_request *request; 3853b377ea9SJohn W. Linville 3863b377ea9SJohn W. Linville if (!alpha2) 3873b377ea9SJohn W. Linville return; 3883b377ea9SJohn W. Linville 3893b377ea9SJohn W. Linville request = kzalloc(sizeof(struct reg_regdb_search_request), GFP_KERNEL); 3903b377ea9SJohn W. Linville if (!request) 3913b377ea9SJohn W. Linville return; 3923b377ea9SJohn W. Linville 3933b377ea9SJohn W. Linville memcpy(request->alpha2, alpha2, 2); 3943b377ea9SJohn W. Linville 395368d06f5SJohn W. Linville mutex_lock(®_regdb_search_mutex); 3963b377ea9SJohn W. Linville list_add_tail(&request->list, ®_regdb_search_list); 397368d06f5SJohn W. Linville mutex_unlock(®_regdb_search_mutex); 3983b377ea9SJohn W. Linville 3993b377ea9SJohn W. Linville schedule_work(®_regdb_work); 4003b377ea9SJohn W. Linville } 40180007efeSLuis R. Rodriguez 40280007efeSLuis R. Rodriguez /* Feel free to add any other sanity checks here */ 40380007efeSLuis R. Rodriguez static void reg_regdb_size_check(void) 40480007efeSLuis R. Rodriguez { 40580007efeSLuis R. Rodriguez /* We should ideally BUILD_BUG_ON() but then random builds would fail */ 40680007efeSLuis R. Rodriguez WARN_ONCE(!reg_regdb_size, "db.txt is empty, you should update it..."); 40780007efeSLuis R. Rodriguez } 4083b377ea9SJohn W. Linville #else 40980007efeSLuis R. Rodriguez static inline void reg_regdb_size_check(void) {} 4103b377ea9SJohn W. Linville static inline void reg_regdb_query(const char *alpha2) {} 4113b377ea9SJohn W. Linville #endif /* CONFIG_CFG80211_INTERNAL_REGDB */ 4123b377ea9SJohn W. Linville 413fb1fc7adSLuis R. Rodriguez /* 414fb1fc7adSLuis R. Rodriguez * This lets us keep regulatory code which is updated on a regulatory 4154d9d88d1SScott James Remnant * basis in userspace. Country information is filled in by 4164d9d88d1SScott James Remnant * reg_device_uevent 417fb1fc7adSLuis R. Rodriguez */ 418b2e1b302SLuis R. Rodriguez static int call_crda(const char *alpha2) 419b2e1b302SLuis R. Rodriguez { 420b2e1b302SLuis R. Rodriguez if (!is_world_regdom((char *) alpha2)) 421e9c0268fSJoe Perches pr_info("Calling CRDA for country: %c%c\n", 422b2e1b302SLuis R. Rodriguez alpha2[0], alpha2[1]); 423b2e1b302SLuis R. Rodriguez else 424e9c0268fSJoe Perches pr_info("Calling CRDA to update world regulatory domain\n"); 4258318d78aSJohannes Berg 4263b377ea9SJohn W. Linville /* query internal regulatory database (if it exists) */ 4273b377ea9SJohn W. Linville reg_regdb_query(alpha2); 4283b377ea9SJohn W. Linville 4294d9d88d1SScott James Remnant return kobject_uevent(®_pdev->dev.kobj, KOBJ_CHANGE); 430b2e1b302SLuis R. Rodriguez } 431b2e1b302SLuis R. Rodriguez 432b2e1b302SLuis R. Rodriguez /* Used by nl80211 before kmalloc'ing our regulatory domain */ 433a3d2eaf0SJohannes Berg bool reg_is_valid_request(const char *alpha2) 434b2e1b302SLuis R. Rodriguez { 43561405e97SLuis R. Rodriguez assert_cfg80211_lock(); 43661405e97SLuis R. Rodriguez 437f6037d09SJohannes Berg if (!last_request) 438f6037d09SJohannes Berg return false; 439f6037d09SJohannes Berg 440f6037d09SJohannes Berg return alpha2_equal(last_request->alpha2, alpha2); 441b2e1b302SLuis R. Rodriguez } 442b2e1b302SLuis R. Rodriguez 443b2e1b302SLuis R. Rodriguez /* Sanity check on a regulatory rule */ 444a3d2eaf0SJohannes Berg static bool is_valid_reg_rule(const struct ieee80211_reg_rule *rule) 445b2e1b302SLuis R. Rodriguez { 446a3d2eaf0SJohannes Berg const struct ieee80211_freq_range *freq_range = &rule->freq_range; 447b2e1b302SLuis R. Rodriguez u32 freq_diff; 448b2e1b302SLuis R. Rodriguez 44991e99004SLuis R. Rodriguez if (freq_range->start_freq_khz <= 0 || freq_range->end_freq_khz <= 0) 450b2e1b302SLuis R. Rodriguez return false; 451b2e1b302SLuis R. Rodriguez 452b2e1b302SLuis R. Rodriguez if (freq_range->start_freq_khz > freq_range->end_freq_khz) 453b2e1b302SLuis R. Rodriguez return false; 454b2e1b302SLuis R. Rodriguez 455b2e1b302SLuis R. Rodriguez freq_diff = freq_range->end_freq_khz - freq_range->start_freq_khz; 456b2e1b302SLuis R. Rodriguez 457bd05f28eSRoel Kluin if (freq_range->end_freq_khz <= freq_range->start_freq_khz || 458bd05f28eSRoel Kluin freq_range->max_bandwidth_khz > freq_diff) 459b2e1b302SLuis R. Rodriguez return false; 460b2e1b302SLuis R. Rodriguez 461b2e1b302SLuis R. Rodriguez return true; 462b2e1b302SLuis R. Rodriguez } 463b2e1b302SLuis R. Rodriguez 464a3d2eaf0SJohannes Berg static bool is_valid_rd(const struct ieee80211_regdomain *rd) 465b2e1b302SLuis R. Rodriguez { 466a3d2eaf0SJohannes Berg const struct ieee80211_reg_rule *reg_rule = NULL; 467b2e1b302SLuis R. Rodriguez unsigned int i; 468b2e1b302SLuis R. Rodriguez 469b2e1b302SLuis R. Rodriguez if (!rd->n_reg_rules) 470b2e1b302SLuis R. Rodriguez return false; 471b2e1b302SLuis R. Rodriguez 47288dc1c3fSLuis R. Rodriguez if (WARN_ON(rd->n_reg_rules > NL80211_MAX_SUPP_REG_RULES)) 47388dc1c3fSLuis R. Rodriguez return false; 47488dc1c3fSLuis R. Rodriguez 475b2e1b302SLuis R. Rodriguez for (i = 0; i < rd->n_reg_rules; i++) { 476b2e1b302SLuis R. Rodriguez reg_rule = &rd->reg_rules[i]; 477b2e1b302SLuis R. Rodriguez if (!is_valid_reg_rule(reg_rule)) 478b2e1b302SLuis R. Rodriguez return false; 479b2e1b302SLuis R. Rodriguez } 480b2e1b302SLuis R. Rodriguez 481b2e1b302SLuis R. Rodriguez return true; 482b2e1b302SLuis R. Rodriguez } 483b2e1b302SLuis R. Rodriguez 484038659e7SLuis R. Rodriguez static bool reg_does_bw_fit(const struct ieee80211_freq_range *freq_range, 485038659e7SLuis R. Rodriguez u32 center_freq_khz, 486038659e7SLuis R. Rodriguez u32 bw_khz) 487b2e1b302SLuis R. Rodriguez { 488038659e7SLuis R. Rodriguez u32 start_freq_khz, end_freq_khz; 489038659e7SLuis R. Rodriguez 490038659e7SLuis R. Rodriguez start_freq_khz = center_freq_khz - (bw_khz/2); 491038659e7SLuis R. Rodriguez end_freq_khz = center_freq_khz + (bw_khz/2); 492038659e7SLuis R. Rodriguez 493b2e1b302SLuis R. Rodriguez if (start_freq_khz >= freq_range->start_freq_khz && 494b2e1b302SLuis R. Rodriguez end_freq_khz <= freq_range->end_freq_khz) 495038659e7SLuis R. Rodriguez return true; 496038659e7SLuis R. Rodriguez 497038659e7SLuis R. Rodriguez return false; 498b2e1b302SLuis R. Rodriguez } 499b2e1b302SLuis R. Rodriguez 5000c7dc45dSLuis R. Rodriguez /** 5010c7dc45dSLuis R. Rodriguez * freq_in_rule_band - tells us if a frequency is in a frequency band 5020c7dc45dSLuis R. Rodriguez * @freq_range: frequency rule we want to query 5030c7dc45dSLuis R. Rodriguez * @freq_khz: frequency we are inquiring about 5040c7dc45dSLuis R. Rodriguez * 5050c7dc45dSLuis R. Rodriguez * This lets us know if a specific frequency rule is or is not relevant to 5060c7dc45dSLuis R. Rodriguez * a specific frequency's band. Bands are device specific and artificial 5070c7dc45dSLuis R. Rodriguez * definitions (the "2.4 GHz band" and the "5 GHz band"), however it is 5080c7dc45dSLuis R. Rodriguez * safe for now to assume that a frequency rule should not be part of a 5090c7dc45dSLuis R. Rodriguez * frequency's band if the start freq or end freq are off by more than 2 GHz. 5100c7dc45dSLuis R. Rodriguez * This resolution can be lowered and should be considered as we add 5110c7dc45dSLuis R. Rodriguez * regulatory rule support for other "bands". 5120c7dc45dSLuis R. Rodriguez **/ 5130c7dc45dSLuis R. Rodriguez static bool freq_in_rule_band(const struct ieee80211_freq_range *freq_range, 5140c7dc45dSLuis R. Rodriguez u32 freq_khz) 5150c7dc45dSLuis R. Rodriguez { 5160c7dc45dSLuis R. Rodriguez #define ONE_GHZ_IN_KHZ 1000000 5170c7dc45dSLuis R. Rodriguez if (abs(freq_khz - freq_range->start_freq_khz) <= (2 * ONE_GHZ_IN_KHZ)) 5180c7dc45dSLuis R. Rodriguez return true; 5190c7dc45dSLuis R. Rodriguez if (abs(freq_khz - freq_range->end_freq_khz) <= (2 * ONE_GHZ_IN_KHZ)) 5200c7dc45dSLuis R. Rodriguez return true; 5210c7dc45dSLuis R. Rodriguez return false; 5220c7dc45dSLuis R. Rodriguez #undef ONE_GHZ_IN_KHZ 5230c7dc45dSLuis R. Rodriguez } 5240c7dc45dSLuis R. Rodriguez 525fb1fc7adSLuis R. Rodriguez /* 526fb1fc7adSLuis R. Rodriguez * Helper for regdom_intersect(), this does the real 527fb1fc7adSLuis R. Rodriguez * mathematical intersection fun 528fb1fc7adSLuis R. Rodriguez */ 5299c96477dSLuis R. Rodriguez static int reg_rules_intersect( 5309c96477dSLuis R. Rodriguez const struct ieee80211_reg_rule *rule1, 5319c96477dSLuis R. Rodriguez const struct ieee80211_reg_rule *rule2, 5329c96477dSLuis R. Rodriguez struct ieee80211_reg_rule *intersected_rule) 5339c96477dSLuis R. Rodriguez { 5349c96477dSLuis R. Rodriguez const struct ieee80211_freq_range *freq_range1, *freq_range2; 5359c96477dSLuis R. Rodriguez struct ieee80211_freq_range *freq_range; 5369c96477dSLuis R. Rodriguez const struct ieee80211_power_rule *power_rule1, *power_rule2; 5379c96477dSLuis R. Rodriguez struct ieee80211_power_rule *power_rule; 5389c96477dSLuis R. Rodriguez u32 freq_diff; 5399c96477dSLuis R. Rodriguez 5409c96477dSLuis R. Rodriguez freq_range1 = &rule1->freq_range; 5419c96477dSLuis R. Rodriguez freq_range2 = &rule2->freq_range; 5429c96477dSLuis R. Rodriguez freq_range = &intersected_rule->freq_range; 5439c96477dSLuis R. Rodriguez 5449c96477dSLuis R. Rodriguez power_rule1 = &rule1->power_rule; 5459c96477dSLuis R. Rodriguez power_rule2 = &rule2->power_rule; 5469c96477dSLuis R. Rodriguez power_rule = &intersected_rule->power_rule; 5479c96477dSLuis R. Rodriguez 5489c96477dSLuis R. Rodriguez freq_range->start_freq_khz = max(freq_range1->start_freq_khz, 5499c96477dSLuis R. Rodriguez freq_range2->start_freq_khz); 5509c96477dSLuis R. Rodriguez freq_range->end_freq_khz = min(freq_range1->end_freq_khz, 5519c96477dSLuis R. Rodriguez freq_range2->end_freq_khz); 5529c96477dSLuis R. Rodriguez freq_range->max_bandwidth_khz = min(freq_range1->max_bandwidth_khz, 5539c96477dSLuis R. Rodriguez freq_range2->max_bandwidth_khz); 5549c96477dSLuis R. Rodriguez 5559c96477dSLuis R. Rodriguez freq_diff = freq_range->end_freq_khz - freq_range->start_freq_khz; 5569c96477dSLuis R. Rodriguez if (freq_range->max_bandwidth_khz > freq_diff) 5579c96477dSLuis R. Rodriguez freq_range->max_bandwidth_khz = freq_diff; 5589c96477dSLuis R. Rodriguez 5599c96477dSLuis R. Rodriguez power_rule->max_eirp = min(power_rule1->max_eirp, 5609c96477dSLuis R. Rodriguez power_rule2->max_eirp); 5619c96477dSLuis R. Rodriguez power_rule->max_antenna_gain = min(power_rule1->max_antenna_gain, 5629c96477dSLuis R. Rodriguez power_rule2->max_antenna_gain); 5639c96477dSLuis R. Rodriguez 5649c96477dSLuis R. Rodriguez intersected_rule->flags = (rule1->flags | rule2->flags); 5659c96477dSLuis R. Rodriguez 5669c96477dSLuis R. Rodriguez if (!is_valid_reg_rule(intersected_rule)) 5679c96477dSLuis R. Rodriguez return -EINVAL; 5689c96477dSLuis R. Rodriguez 5699c96477dSLuis R. Rodriguez return 0; 5709c96477dSLuis R. Rodriguez } 5719c96477dSLuis R. Rodriguez 5729c96477dSLuis R. Rodriguez /** 5739c96477dSLuis R. Rodriguez * regdom_intersect - do the intersection between two regulatory domains 5749c96477dSLuis R. Rodriguez * @rd1: first regulatory domain 5759c96477dSLuis R. Rodriguez * @rd2: second regulatory domain 5769c96477dSLuis R. Rodriguez * 5779c96477dSLuis R. Rodriguez * Use this function to get the intersection between two regulatory domains. 5789c96477dSLuis R. Rodriguez * Once completed we will mark the alpha2 for the rd as intersected, "98", 5799c96477dSLuis R. Rodriguez * as no one single alpha2 can represent this regulatory domain. 5809c96477dSLuis R. Rodriguez * 5819c96477dSLuis R. Rodriguez * Returns a pointer to the regulatory domain structure which will hold the 5829c96477dSLuis R. Rodriguez * resulting intersection of rules between rd1 and rd2. We will 5839c96477dSLuis R. Rodriguez * kzalloc() this structure for you. 5849c96477dSLuis R. Rodriguez */ 5859c96477dSLuis R. Rodriguez static struct ieee80211_regdomain *regdom_intersect( 5869c96477dSLuis R. Rodriguez const struct ieee80211_regdomain *rd1, 5879c96477dSLuis R. Rodriguez const struct ieee80211_regdomain *rd2) 5889c96477dSLuis R. Rodriguez { 5899c96477dSLuis R. Rodriguez int r, size_of_regd; 5909c96477dSLuis R. Rodriguez unsigned int x, y; 5919c96477dSLuis R. Rodriguez unsigned int num_rules = 0, rule_idx = 0; 5929c96477dSLuis R. Rodriguez const struct ieee80211_reg_rule *rule1, *rule2; 5939c96477dSLuis R. Rodriguez struct ieee80211_reg_rule *intersected_rule; 5949c96477dSLuis R. Rodriguez struct ieee80211_regdomain *rd; 5959c96477dSLuis R. Rodriguez /* This is just a dummy holder to help us count */ 5969c96477dSLuis R. Rodriguez struct ieee80211_reg_rule irule; 5979c96477dSLuis R. Rodriguez 5989c96477dSLuis R. Rodriguez /* Uses the stack temporarily for counter arithmetic */ 5999c96477dSLuis R. Rodriguez intersected_rule = &irule; 6009c96477dSLuis R. Rodriguez 6019c96477dSLuis R. Rodriguez memset(intersected_rule, 0, sizeof(struct ieee80211_reg_rule)); 6029c96477dSLuis R. Rodriguez 6039c96477dSLuis R. Rodriguez if (!rd1 || !rd2) 6049c96477dSLuis R. Rodriguez return NULL; 6059c96477dSLuis R. Rodriguez 606fb1fc7adSLuis R. Rodriguez /* 607fb1fc7adSLuis R. Rodriguez * First we get a count of the rules we'll need, then we actually 6089c96477dSLuis R. Rodriguez * build them. This is to so we can malloc() and free() a 6099c96477dSLuis R. Rodriguez * regdomain once. The reason we use reg_rules_intersect() here 6109c96477dSLuis R. Rodriguez * is it will return -EINVAL if the rule computed makes no sense. 611fb1fc7adSLuis R. Rodriguez * All rules that do check out OK are valid. 612fb1fc7adSLuis R. Rodriguez */ 6139c96477dSLuis R. Rodriguez 6149c96477dSLuis R. Rodriguez for (x = 0; x < rd1->n_reg_rules; x++) { 6159c96477dSLuis R. Rodriguez rule1 = &rd1->reg_rules[x]; 6169c96477dSLuis R. Rodriguez for (y = 0; y < rd2->n_reg_rules; y++) { 6179c96477dSLuis R. Rodriguez rule2 = &rd2->reg_rules[y]; 6189c96477dSLuis R. Rodriguez if (!reg_rules_intersect(rule1, rule2, 6199c96477dSLuis R. Rodriguez intersected_rule)) 6209c96477dSLuis R. Rodriguez num_rules++; 6219c96477dSLuis R. Rodriguez memset(intersected_rule, 0, 6229c96477dSLuis R. Rodriguez sizeof(struct ieee80211_reg_rule)); 6239c96477dSLuis R. Rodriguez } 6249c96477dSLuis R. Rodriguez } 6259c96477dSLuis R. Rodriguez 6269c96477dSLuis R. Rodriguez if (!num_rules) 6279c96477dSLuis R. Rodriguez return NULL; 6289c96477dSLuis R. Rodriguez 6299c96477dSLuis R. Rodriguez size_of_regd = sizeof(struct ieee80211_regdomain) + 6309c96477dSLuis R. Rodriguez ((num_rules + 1) * sizeof(struct ieee80211_reg_rule)); 6319c96477dSLuis R. Rodriguez 6329c96477dSLuis R. Rodriguez rd = kzalloc(size_of_regd, GFP_KERNEL); 6339c96477dSLuis R. Rodriguez if (!rd) 6349c96477dSLuis R. Rodriguez return NULL; 6359c96477dSLuis R. Rodriguez 6369c96477dSLuis R. Rodriguez for (x = 0; x < rd1->n_reg_rules; x++) { 6379c96477dSLuis R. Rodriguez rule1 = &rd1->reg_rules[x]; 6389c96477dSLuis R. Rodriguez for (y = 0; y < rd2->n_reg_rules; y++) { 6399c96477dSLuis R. Rodriguez rule2 = &rd2->reg_rules[y]; 640fb1fc7adSLuis R. Rodriguez /* 641fb1fc7adSLuis R. Rodriguez * This time around instead of using the stack lets 6429c96477dSLuis R. Rodriguez * write to the target rule directly saving ourselves 643fb1fc7adSLuis R. Rodriguez * a memcpy() 644fb1fc7adSLuis R. Rodriguez */ 6459c96477dSLuis R. Rodriguez intersected_rule = &rd->reg_rules[rule_idx]; 6469c96477dSLuis R. Rodriguez r = reg_rules_intersect(rule1, rule2, 6479c96477dSLuis R. Rodriguez intersected_rule); 648fb1fc7adSLuis R. Rodriguez /* 649fb1fc7adSLuis R. Rodriguez * No need to memset here the intersected rule here as 650fb1fc7adSLuis R. Rodriguez * we're not using the stack anymore 651fb1fc7adSLuis R. Rodriguez */ 6529c96477dSLuis R. Rodriguez if (r) 6539c96477dSLuis R. Rodriguez continue; 6549c96477dSLuis R. Rodriguez rule_idx++; 6559c96477dSLuis R. Rodriguez } 6569c96477dSLuis R. Rodriguez } 6579c96477dSLuis R. Rodriguez 6589c96477dSLuis R. Rodriguez if (rule_idx != num_rules) { 6599c96477dSLuis R. Rodriguez kfree(rd); 6609c96477dSLuis R. Rodriguez return NULL; 6619c96477dSLuis R. Rodriguez } 6629c96477dSLuis R. Rodriguez 6639c96477dSLuis R. Rodriguez rd->n_reg_rules = num_rules; 6649c96477dSLuis R. Rodriguez rd->alpha2[0] = '9'; 6659c96477dSLuis R. Rodriguez rd->alpha2[1] = '8'; 6669c96477dSLuis R. Rodriguez 6679c96477dSLuis R. Rodriguez return rd; 6689c96477dSLuis R. Rodriguez } 6699c96477dSLuis R. Rodriguez 670fb1fc7adSLuis R. Rodriguez /* 671fb1fc7adSLuis R. Rodriguez * XXX: add support for the rest of enum nl80211_reg_rule_flags, we may 672fb1fc7adSLuis R. Rodriguez * want to just have the channel structure use these 673fb1fc7adSLuis R. Rodriguez */ 674b2e1b302SLuis R. Rodriguez static u32 map_regdom_flags(u32 rd_flags) 675b2e1b302SLuis R. Rodriguez { 676b2e1b302SLuis R. Rodriguez u32 channel_flags = 0; 677b2e1b302SLuis R. Rodriguez if (rd_flags & NL80211_RRF_PASSIVE_SCAN) 678b2e1b302SLuis R. Rodriguez channel_flags |= IEEE80211_CHAN_PASSIVE_SCAN; 679b2e1b302SLuis R. Rodriguez if (rd_flags & NL80211_RRF_NO_IBSS) 680b2e1b302SLuis R. Rodriguez channel_flags |= IEEE80211_CHAN_NO_IBSS; 681b2e1b302SLuis R. Rodriguez if (rd_flags & NL80211_RRF_DFS) 682b2e1b302SLuis R. Rodriguez channel_flags |= IEEE80211_CHAN_RADAR; 683b2e1b302SLuis R. Rodriguez return channel_flags; 684b2e1b302SLuis R. Rodriguez } 685b2e1b302SLuis R. Rodriguez 6861fa25e41SLuis R. Rodriguez static int freq_reg_info_regd(struct wiphy *wiphy, 6871fa25e41SLuis R. Rodriguez u32 center_freq, 688038659e7SLuis R. Rodriguez u32 desired_bw_khz, 6891fa25e41SLuis R. Rodriguez const struct ieee80211_reg_rule **reg_rule, 6901fa25e41SLuis R. Rodriguez const struct ieee80211_regdomain *custom_regd) 6918318d78aSJohannes Berg { 6928318d78aSJohannes Berg int i; 6930c7dc45dSLuis R. Rodriguez bool band_rule_found = false; 6943e0c3ff3SLuis R. Rodriguez const struct ieee80211_regdomain *regd; 695038659e7SLuis R. Rodriguez bool bw_fits = false; 696038659e7SLuis R. Rodriguez 697038659e7SLuis R. Rodriguez if (!desired_bw_khz) 698038659e7SLuis R. Rodriguez desired_bw_khz = MHZ_TO_KHZ(20); 6998318d78aSJohannes Berg 7001fa25e41SLuis R. Rodriguez regd = custom_regd ? custom_regd : cfg80211_regdomain; 7013e0c3ff3SLuis R. Rodriguez 702fb1fc7adSLuis R. Rodriguez /* 703fb1fc7adSLuis R. Rodriguez * Follow the driver's regulatory domain, if present, unless a country 704fb1fc7adSLuis R. Rodriguez * IE has been processed or a user wants to help complaince further 705fb1fc7adSLuis R. Rodriguez */ 7062784fe91SLuis R. Rodriguez if (!custom_regd && 7072784fe91SLuis R. Rodriguez last_request->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE && 7087db90f4aSLuis R. Rodriguez last_request->initiator != NL80211_REGDOM_SET_BY_USER && 7093e0c3ff3SLuis R. Rodriguez wiphy->regd) 7103e0c3ff3SLuis R. Rodriguez regd = wiphy->regd; 7113e0c3ff3SLuis R. Rodriguez 7123e0c3ff3SLuis R. Rodriguez if (!regd) 713b2e1b302SLuis R. Rodriguez return -EINVAL; 714b2e1b302SLuis R. Rodriguez 7153e0c3ff3SLuis R. Rodriguez for (i = 0; i < regd->n_reg_rules; i++) { 716b2e1b302SLuis R. Rodriguez const struct ieee80211_reg_rule *rr; 717b2e1b302SLuis R. Rodriguez const struct ieee80211_freq_range *fr = NULL; 718b2e1b302SLuis R. Rodriguez 7193e0c3ff3SLuis R. Rodriguez rr = ®d->reg_rules[i]; 720b2e1b302SLuis R. Rodriguez fr = &rr->freq_range; 7210c7dc45dSLuis R. Rodriguez 722fb1fc7adSLuis R. Rodriguez /* 723fb1fc7adSLuis R. Rodriguez * We only need to know if one frequency rule was 7240c7dc45dSLuis R. Rodriguez * was in center_freq's band, that's enough, so lets 725fb1fc7adSLuis R. Rodriguez * not overwrite it once found 726fb1fc7adSLuis R. Rodriguez */ 7270c7dc45dSLuis R. Rodriguez if (!band_rule_found) 7280c7dc45dSLuis R. Rodriguez band_rule_found = freq_in_rule_band(fr, center_freq); 7290c7dc45dSLuis R. Rodriguez 730038659e7SLuis R. Rodriguez bw_fits = reg_does_bw_fit(fr, 731038659e7SLuis R. Rodriguez center_freq, 732038659e7SLuis R. Rodriguez desired_bw_khz); 7330c7dc45dSLuis R. Rodriguez 734038659e7SLuis R. Rodriguez if (band_rule_found && bw_fits) { 735b2e1b302SLuis R. Rodriguez *reg_rule = rr; 736038659e7SLuis R. Rodriguez return 0; 7378318d78aSJohannes Berg } 7388318d78aSJohannes Berg } 7398318d78aSJohannes Berg 7400c7dc45dSLuis R. Rodriguez if (!band_rule_found) 7410c7dc45dSLuis R. Rodriguez return -ERANGE; 7420c7dc45dSLuis R. Rodriguez 743038659e7SLuis R. Rodriguez return -EINVAL; 744b2e1b302SLuis R. Rodriguez } 745b2e1b302SLuis R. Rodriguez 746038659e7SLuis R. Rodriguez int freq_reg_info(struct wiphy *wiphy, 747038659e7SLuis R. Rodriguez u32 center_freq, 748038659e7SLuis R. Rodriguez u32 desired_bw_khz, 7491fa25e41SLuis R. Rodriguez const struct ieee80211_reg_rule **reg_rule) 7501fa25e41SLuis R. Rodriguez { 751ac46d48eSLuis R. Rodriguez assert_cfg80211_lock(); 752038659e7SLuis R. Rodriguez return freq_reg_info_regd(wiphy, 753038659e7SLuis R. Rodriguez center_freq, 754038659e7SLuis R. Rodriguez desired_bw_khz, 755038659e7SLuis R. Rodriguez reg_rule, 756038659e7SLuis R. Rodriguez NULL); 7571fa25e41SLuis R. Rodriguez } 7584f366c5dSJohn W. Linville EXPORT_SYMBOL(freq_reg_info); 759b2e1b302SLuis R. Rodriguez 760926a0a09SLuis R. Rodriguez #ifdef CONFIG_CFG80211_REG_DEBUG 761926a0a09SLuis R. Rodriguez static const char *reg_initiator_name(enum nl80211_reg_initiator initiator) 762926a0a09SLuis R. Rodriguez { 763926a0a09SLuis R. Rodriguez switch (initiator) { 764926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_CORE: 765926a0a09SLuis R. Rodriguez return "Set by core"; 766926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_USER: 767926a0a09SLuis R. Rodriguez return "Set by user"; 768926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_DRIVER: 769926a0a09SLuis R. Rodriguez return "Set by driver"; 770926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_COUNTRY_IE: 771926a0a09SLuis R. Rodriguez return "Set by country IE"; 772926a0a09SLuis R. Rodriguez default: 773926a0a09SLuis R. Rodriguez WARN_ON(1); 774926a0a09SLuis R. Rodriguez return "Set by bug"; 775926a0a09SLuis R. Rodriguez } 776926a0a09SLuis R. Rodriguez } 777e702d3cfSLuis R. Rodriguez 778e702d3cfSLuis R. Rodriguez static void chan_reg_rule_print_dbg(struct ieee80211_channel *chan, 779e702d3cfSLuis R. Rodriguez u32 desired_bw_khz, 780e702d3cfSLuis R. Rodriguez const struct ieee80211_reg_rule *reg_rule) 781e702d3cfSLuis R. Rodriguez { 782e702d3cfSLuis R. Rodriguez const struct ieee80211_power_rule *power_rule; 783e702d3cfSLuis R. Rodriguez const struct ieee80211_freq_range *freq_range; 784e702d3cfSLuis R. Rodriguez char max_antenna_gain[32]; 785e702d3cfSLuis R. Rodriguez 786e702d3cfSLuis R. Rodriguez power_rule = ®_rule->power_rule; 787e702d3cfSLuis R. Rodriguez freq_range = ®_rule->freq_range; 788e702d3cfSLuis R. Rodriguez 789e702d3cfSLuis R. Rodriguez if (!power_rule->max_antenna_gain) 790e702d3cfSLuis R. Rodriguez snprintf(max_antenna_gain, 32, "N/A"); 791e702d3cfSLuis R. Rodriguez else 792e702d3cfSLuis R. Rodriguez snprintf(max_antenna_gain, 32, "%d", power_rule->max_antenna_gain); 793e702d3cfSLuis R. Rodriguez 794d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Updating information on frequency %d MHz " 795ff039c6fSBob Copeland "for a %d MHz width channel with regulatory rule:\n", 796e702d3cfSLuis R. Rodriguez chan->center_freq, 797e702d3cfSLuis R. Rodriguez KHZ_TO_MHZ(desired_bw_khz)); 798e702d3cfSLuis R. Rodriguez 79956e6786eSPavel Roskin REG_DBG_PRINT("%d KHz - %d KHz @ %d KHz), (%s mBi, %d mBm)\n", 800e702d3cfSLuis R. Rodriguez freq_range->start_freq_khz, 801e702d3cfSLuis R. Rodriguez freq_range->end_freq_khz, 80256e6786eSPavel Roskin freq_range->max_bandwidth_khz, 803e702d3cfSLuis R. Rodriguez max_antenna_gain, 804e702d3cfSLuis R. Rodriguez power_rule->max_eirp); 805e702d3cfSLuis R. Rodriguez } 806e702d3cfSLuis R. Rodriguez #else 807e702d3cfSLuis R. Rodriguez static void chan_reg_rule_print_dbg(struct ieee80211_channel *chan, 808e702d3cfSLuis R. Rodriguez u32 desired_bw_khz, 809e702d3cfSLuis R. Rodriguez const struct ieee80211_reg_rule *reg_rule) 810e702d3cfSLuis R. Rodriguez { 811e702d3cfSLuis R. Rodriguez return; 812e702d3cfSLuis R. Rodriguez } 813926a0a09SLuis R. Rodriguez #endif 814926a0a09SLuis R. Rodriguez 815038659e7SLuis R. Rodriguez /* 816038659e7SLuis R. Rodriguez * Note that right now we assume the desired channel bandwidth 817038659e7SLuis R. Rodriguez * is always 20 MHz for each individual channel (HT40 uses 20 MHz 818038659e7SLuis R. Rodriguez * per channel, the primary and the extension channel). To support 819038659e7SLuis R. Rodriguez * smaller custom bandwidths such as 5 MHz or 10 MHz we'll need a 820038659e7SLuis R. Rodriguez * new ieee80211_channel.target_bw and re run the regulatory check 821038659e7SLuis R. Rodriguez * on the wiphy with the target_bw specified. Then we can simply use 822038659e7SLuis R. Rodriguez * that below for the desired_bw_khz below. 823038659e7SLuis R. Rodriguez */ 8247ca43d03SLuis R. Rodriguez static void handle_channel(struct wiphy *wiphy, 8257ca43d03SLuis R. Rodriguez enum nl80211_reg_initiator initiator, 8267ca43d03SLuis R. Rodriguez enum ieee80211_band band, 827a92a3ce7SLuis R. Rodriguez unsigned int chan_idx) 828b2e1b302SLuis R. Rodriguez { 829b2e1b302SLuis R. Rodriguez int r; 830038659e7SLuis R. Rodriguez u32 flags, bw_flags = 0; 831038659e7SLuis R. Rodriguez u32 desired_bw_khz = MHZ_TO_KHZ(20); 832b2e1b302SLuis R. Rodriguez const struct ieee80211_reg_rule *reg_rule = NULL; 833b2e1b302SLuis R. Rodriguez const struct ieee80211_power_rule *power_rule = NULL; 834038659e7SLuis R. Rodriguez const struct ieee80211_freq_range *freq_range = NULL; 835a92a3ce7SLuis R. Rodriguez struct ieee80211_supported_band *sband; 836a92a3ce7SLuis R. Rodriguez struct ieee80211_channel *chan; 837fe33eb39SLuis R. Rodriguez struct wiphy *request_wiphy = NULL; 838a92a3ce7SLuis R. Rodriguez 839761cf7ecSLuis R. Rodriguez assert_cfg80211_lock(); 840761cf7ecSLuis R. Rodriguez 841806a9e39SLuis R. Rodriguez request_wiphy = wiphy_idx_to_wiphy(last_request->wiphy_idx); 842806a9e39SLuis R. Rodriguez 843a92a3ce7SLuis R. Rodriguez sband = wiphy->bands[band]; 844a92a3ce7SLuis R. Rodriguez BUG_ON(chan_idx >= sband->n_channels); 845a92a3ce7SLuis R. Rodriguez chan = &sband->channels[chan_idx]; 846a92a3ce7SLuis R. Rodriguez 847a92a3ce7SLuis R. Rodriguez flags = chan->orig_flags; 848b2e1b302SLuis R. Rodriguez 849038659e7SLuis R. Rodriguez r = freq_reg_info(wiphy, 850038659e7SLuis R. Rodriguez MHZ_TO_KHZ(chan->center_freq), 851038659e7SLuis R. Rodriguez desired_bw_khz, 852038659e7SLuis R. Rodriguez ®_rule); 853b2e1b302SLuis R. Rodriguez 854ca4ffe8fSLuis R. Rodriguez if (r) { 855ca4ffe8fSLuis R. Rodriguez /* 856ca4ffe8fSLuis R. Rodriguez * We will disable all channels that do not match our 85725985edcSLucas De Marchi * received regulatory rule unless the hint is coming 858ca4ffe8fSLuis R. Rodriguez * from a Country IE and the Country IE had no information 859ca4ffe8fSLuis R. Rodriguez * about a band. The IEEE 802.11 spec allows for an AP 860ca4ffe8fSLuis R. Rodriguez * to send only a subset of the regulatory rules allowed, 861ca4ffe8fSLuis R. Rodriguez * so an AP in the US that only supports 2.4 GHz may only send 862ca4ffe8fSLuis R. Rodriguez * a country IE with information for the 2.4 GHz band 863ca4ffe8fSLuis R. Rodriguez * while 5 GHz is still supported. 864ca4ffe8fSLuis R. Rodriguez */ 865ca4ffe8fSLuis R. Rodriguez if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE && 866ca4ffe8fSLuis R. Rodriguez r == -ERANGE) 8678318d78aSJohannes Berg return; 8688318d78aSJohannes Berg 869d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Disabling freq %d MHz\n", chan->center_freq); 870ca4ffe8fSLuis R. Rodriguez chan->flags = IEEE80211_CHAN_DISABLED; 871ca4ffe8fSLuis R. Rodriguez return; 872ca4ffe8fSLuis R. Rodriguez } 873ca4ffe8fSLuis R. Rodriguez 874e702d3cfSLuis R. Rodriguez chan_reg_rule_print_dbg(chan, desired_bw_khz, reg_rule); 875e702d3cfSLuis R. Rodriguez 876b2e1b302SLuis R. Rodriguez power_rule = ®_rule->power_rule; 877038659e7SLuis R. Rodriguez freq_range = ®_rule->freq_range; 878038659e7SLuis R. Rodriguez 879038659e7SLuis R. Rodriguez if (freq_range->max_bandwidth_khz < MHZ_TO_KHZ(40)) 880038659e7SLuis R. Rodriguez bw_flags = IEEE80211_CHAN_NO_HT40; 881b2e1b302SLuis R. Rodriguez 8827db90f4aSLuis R. Rodriguez if (last_request->initiator == NL80211_REGDOM_SET_BY_DRIVER && 883806a9e39SLuis R. Rodriguez request_wiphy && request_wiphy == wiphy && 8845be83de5SJohannes Berg request_wiphy->flags & WIPHY_FLAG_STRICT_REGULATORY) { 885fb1fc7adSLuis R. Rodriguez /* 88625985edcSLucas De Marchi * This guarantees the driver's requested regulatory domain 887f976376dSLuis R. Rodriguez * will always be used as a base for further regulatory 888fb1fc7adSLuis R. Rodriguez * settings 889fb1fc7adSLuis R. Rodriguez */ 890f976376dSLuis R. Rodriguez chan->flags = chan->orig_flags = 891038659e7SLuis R. Rodriguez map_regdom_flags(reg_rule->flags) | bw_flags; 892f976376dSLuis R. Rodriguez chan->max_antenna_gain = chan->orig_mag = 893f976376dSLuis R. Rodriguez (int) MBI_TO_DBI(power_rule->max_antenna_gain); 894f976376dSLuis R. Rodriguez chan->max_power = chan->orig_mpwr = 895f976376dSLuis R. Rodriguez (int) MBM_TO_DBM(power_rule->max_eirp); 896f976376dSLuis R. Rodriguez return; 897f976376dSLuis R. Rodriguez } 898f976376dSLuis R. Rodriguez 899aa3d7eefSRajkumar Manoharan chan->beacon_found = false; 900038659e7SLuis R. Rodriguez chan->flags = flags | bw_flags | map_regdom_flags(reg_rule->flags); 9018318d78aSJohannes Berg chan->max_antenna_gain = min(chan->orig_mag, 902b2e1b302SLuis R. Rodriguez (int) MBI_TO_DBI(power_rule->max_antenna_gain)); 903eccc068eSHong Wu chan->max_reg_power = (int) MBM_TO_DBM(power_rule->max_eirp); 904eccc068eSHong Wu chan->max_power = min(chan->max_power, chan->max_reg_power); 9058318d78aSJohannes Berg } 9068318d78aSJohannes Berg 9077ca43d03SLuis R. Rodriguez static void handle_band(struct wiphy *wiphy, 9087ca43d03SLuis R. Rodriguez enum ieee80211_band band, 9097ca43d03SLuis R. Rodriguez enum nl80211_reg_initiator initiator) 9108318d78aSJohannes Berg { 911a92a3ce7SLuis R. Rodriguez unsigned int i; 912a92a3ce7SLuis R. Rodriguez struct ieee80211_supported_band *sband; 913a92a3ce7SLuis R. Rodriguez 914a92a3ce7SLuis R. Rodriguez BUG_ON(!wiphy->bands[band]); 915a92a3ce7SLuis R. Rodriguez sband = wiphy->bands[band]; 9168318d78aSJohannes Berg 9178318d78aSJohannes Berg for (i = 0; i < sband->n_channels; i++) 9187ca43d03SLuis R. Rodriguez handle_channel(wiphy, initiator, band, i); 9198318d78aSJohannes Berg } 9208318d78aSJohannes Berg 92157b5ce07SLuis R. Rodriguez static bool reg_request_cell_base(struct regulatory_request *request) 92257b5ce07SLuis R. Rodriguez { 92357b5ce07SLuis R. Rodriguez if (request->initiator != NL80211_REGDOM_SET_BY_USER) 92457b5ce07SLuis R. Rodriguez return false; 92557b5ce07SLuis R. Rodriguez if (request->user_reg_hint_type != NL80211_USER_REG_HINT_CELL_BASE) 92657b5ce07SLuis R. Rodriguez return false; 92757b5ce07SLuis R. Rodriguez return true; 92857b5ce07SLuis R. Rodriguez } 92957b5ce07SLuis R. Rodriguez 93057b5ce07SLuis R. Rodriguez bool reg_last_request_cell_base(void) 93157b5ce07SLuis R. Rodriguez { 93257b5ce07SLuis R. Rodriguez assert_cfg80211_lock(); 93357b5ce07SLuis R. Rodriguez 93457b5ce07SLuis R. Rodriguez mutex_lock(®_mutex); 93557b5ce07SLuis R. Rodriguez return reg_request_cell_base(last_request); 93657b5ce07SLuis R. Rodriguez mutex_unlock(®_mutex); 93757b5ce07SLuis R. Rodriguez } 93857b5ce07SLuis R. Rodriguez 93957b5ce07SLuis R. Rodriguez #ifdef CONFIG_CFG80211_CERTIFICATION_ONUS 94057b5ce07SLuis R. Rodriguez 94157b5ce07SLuis R. Rodriguez /* Core specific check */ 94257b5ce07SLuis R. Rodriguez static int reg_ignore_cell_hint(struct regulatory_request *pending_request) 94357b5ce07SLuis R. Rodriguez { 94457b5ce07SLuis R. Rodriguez if (!reg_num_devs_support_basehint) 94557b5ce07SLuis R. Rodriguez return -EOPNOTSUPP; 94657b5ce07SLuis R. Rodriguez 94757b5ce07SLuis R. Rodriguez if (reg_request_cell_base(last_request)) { 94857b5ce07SLuis R. Rodriguez if (!regdom_changes(pending_request->alpha2)) 94957b5ce07SLuis R. Rodriguez return -EALREADY; 95057b5ce07SLuis R. Rodriguez return 0; 95157b5ce07SLuis R. Rodriguez } 95257b5ce07SLuis R. Rodriguez return 0; 95357b5ce07SLuis R. Rodriguez } 95457b5ce07SLuis R. Rodriguez 95557b5ce07SLuis R. Rodriguez /* Device specific check */ 95657b5ce07SLuis R. Rodriguez static bool reg_dev_ignore_cell_hint(struct wiphy *wiphy) 95757b5ce07SLuis R. Rodriguez { 95857b5ce07SLuis R. Rodriguez if (!(wiphy->features & NL80211_FEATURE_CELL_BASE_REG_HINTS)) 95957b5ce07SLuis R. Rodriguez return true; 96057b5ce07SLuis R. Rodriguez return false; 96157b5ce07SLuis R. Rodriguez } 96257b5ce07SLuis R. Rodriguez #else 96357b5ce07SLuis R. Rodriguez static int reg_ignore_cell_hint(struct regulatory_request *pending_request) 96457b5ce07SLuis R. Rodriguez { 96557b5ce07SLuis R. Rodriguez return -EOPNOTSUPP; 96657b5ce07SLuis R. Rodriguez } 96757b5ce07SLuis R. Rodriguez static int reg_dev_ignore_cell_hint(struct wiphy *wiphy) 96857b5ce07SLuis R. Rodriguez { 96957b5ce07SLuis R. Rodriguez return true; 97057b5ce07SLuis R. Rodriguez } 97157b5ce07SLuis R. Rodriguez #endif 97257b5ce07SLuis R. Rodriguez 97357b5ce07SLuis R. Rodriguez 9747db90f4aSLuis R. Rodriguez static bool ignore_reg_update(struct wiphy *wiphy, 9757db90f4aSLuis R. Rodriguez enum nl80211_reg_initiator initiator) 97614b9815aSLuis R. Rodriguez { 977926a0a09SLuis R. Rodriguez if (!last_request) { 978d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Ignoring regulatory request %s since " 979926a0a09SLuis R. Rodriguez "last_request is not set\n", 980926a0a09SLuis R. Rodriguez reg_initiator_name(initiator)); 98114b9815aSLuis R. Rodriguez return true; 982926a0a09SLuis R. Rodriguez } 983926a0a09SLuis R. Rodriguez 9847db90f4aSLuis R. Rodriguez if (initiator == NL80211_REGDOM_SET_BY_CORE && 985926a0a09SLuis R. Rodriguez wiphy->flags & WIPHY_FLAG_CUSTOM_REGULATORY) { 986d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Ignoring regulatory request %s " 987926a0a09SLuis R. Rodriguez "since the driver uses its own custom " 98812c5ffb5SJoe Perches "regulatory domain\n", 989926a0a09SLuis R. Rodriguez reg_initiator_name(initiator)); 99014b9815aSLuis R. Rodriguez return true; 991926a0a09SLuis R. Rodriguez } 992926a0a09SLuis R. Rodriguez 993fb1fc7adSLuis R. Rodriguez /* 994fb1fc7adSLuis R. Rodriguez * wiphy->regd will be set once the device has its own 995fb1fc7adSLuis R. Rodriguez * desired regulatory domain set 996fb1fc7adSLuis R. Rodriguez */ 9975be83de5SJohannes Berg if (wiphy->flags & WIPHY_FLAG_STRICT_REGULATORY && !wiphy->regd && 998749b527bSLuis R. Rodriguez initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE && 999926a0a09SLuis R. Rodriguez !is_world_regdom(last_request->alpha2)) { 1000d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Ignoring regulatory request %s " 10015bc91db8SMihai Moldovan "since the driver requires its own regulatory " 100212c5ffb5SJoe Perches "domain to be set first\n", 1003926a0a09SLuis R. Rodriguez reg_initiator_name(initiator)); 100414b9815aSLuis R. Rodriguez return true; 1005926a0a09SLuis R. Rodriguez } 1006926a0a09SLuis R. Rodriguez 100757b5ce07SLuis R. Rodriguez if (reg_request_cell_base(last_request)) 100857b5ce07SLuis R. Rodriguez return reg_dev_ignore_cell_hint(wiphy); 100957b5ce07SLuis R. Rodriguez 101014b9815aSLuis R. Rodriguez return false; 101114b9815aSLuis R. Rodriguez } 101214b9815aSLuis R. Rodriguez 1013e38f8a7aSLuis R. Rodriguez static void handle_reg_beacon(struct wiphy *wiphy, 1014e38f8a7aSLuis R. Rodriguez unsigned int chan_idx, 1015e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon) 1016e38f8a7aSLuis R. Rodriguez { 1017e38f8a7aSLuis R. Rodriguez struct ieee80211_supported_band *sband; 1018e38f8a7aSLuis R. Rodriguez struct ieee80211_channel *chan; 10196bad8766SLuis R. Rodriguez bool channel_changed = false; 10206bad8766SLuis R. Rodriguez struct ieee80211_channel chan_before; 1021e38f8a7aSLuis R. Rodriguez 1022e38f8a7aSLuis R. Rodriguez assert_cfg80211_lock(); 1023e38f8a7aSLuis R. Rodriguez 1024e38f8a7aSLuis R. Rodriguez sband = wiphy->bands[reg_beacon->chan.band]; 1025e38f8a7aSLuis R. Rodriguez chan = &sband->channels[chan_idx]; 1026e38f8a7aSLuis R. Rodriguez 1027e38f8a7aSLuis R. Rodriguez if (likely(chan->center_freq != reg_beacon->chan.center_freq)) 1028e38f8a7aSLuis R. Rodriguez return; 1029e38f8a7aSLuis R. Rodriguez 10306bad8766SLuis R. Rodriguez if (chan->beacon_found) 10316bad8766SLuis R. Rodriguez return; 10326bad8766SLuis R. Rodriguez 10336bad8766SLuis R. Rodriguez chan->beacon_found = true; 10346bad8766SLuis R. Rodriguez 10355be83de5SJohannes Berg if (wiphy->flags & WIPHY_FLAG_DISABLE_BEACON_HINTS) 103637184244SLuis R. Rodriguez return; 103737184244SLuis R. Rodriguez 10386bad8766SLuis R. Rodriguez chan_before.center_freq = chan->center_freq; 10396bad8766SLuis R. Rodriguez chan_before.flags = chan->flags; 10406bad8766SLuis R. Rodriguez 104137184244SLuis R. Rodriguez if (chan->flags & IEEE80211_CHAN_PASSIVE_SCAN) { 1042e38f8a7aSLuis R. Rodriguez chan->flags &= ~IEEE80211_CHAN_PASSIVE_SCAN; 10436bad8766SLuis R. Rodriguez channel_changed = true; 1044e38f8a7aSLuis R. Rodriguez } 1045e38f8a7aSLuis R. Rodriguez 104637184244SLuis R. Rodriguez if (chan->flags & IEEE80211_CHAN_NO_IBSS) { 1047e38f8a7aSLuis R. Rodriguez chan->flags &= ~IEEE80211_CHAN_NO_IBSS; 10486bad8766SLuis R. Rodriguez channel_changed = true; 1049e38f8a7aSLuis R. Rodriguez } 1050e38f8a7aSLuis R. Rodriguez 10516bad8766SLuis R. Rodriguez if (channel_changed) 10526bad8766SLuis R. Rodriguez nl80211_send_beacon_hint_event(wiphy, &chan_before, chan); 1053e38f8a7aSLuis R. Rodriguez } 1054e38f8a7aSLuis R. Rodriguez 1055e38f8a7aSLuis R. Rodriguez /* 1056e38f8a7aSLuis R. Rodriguez * Called when a scan on a wiphy finds a beacon on 1057e38f8a7aSLuis R. Rodriguez * new channel 1058e38f8a7aSLuis R. Rodriguez */ 1059e38f8a7aSLuis R. Rodriguez static void wiphy_update_new_beacon(struct wiphy *wiphy, 1060e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon) 1061e38f8a7aSLuis R. Rodriguez { 1062e38f8a7aSLuis R. Rodriguez unsigned int i; 1063e38f8a7aSLuis R. Rodriguez struct ieee80211_supported_band *sband; 1064e38f8a7aSLuis R. Rodriguez 1065e38f8a7aSLuis R. Rodriguez assert_cfg80211_lock(); 1066e38f8a7aSLuis R. Rodriguez 1067e38f8a7aSLuis R. Rodriguez if (!wiphy->bands[reg_beacon->chan.band]) 1068e38f8a7aSLuis R. Rodriguez return; 1069e38f8a7aSLuis R. Rodriguez 1070e38f8a7aSLuis R. Rodriguez sband = wiphy->bands[reg_beacon->chan.band]; 1071e38f8a7aSLuis R. Rodriguez 1072e38f8a7aSLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 1073e38f8a7aSLuis R. Rodriguez handle_reg_beacon(wiphy, i, reg_beacon); 1074e38f8a7aSLuis R. Rodriguez } 1075e38f8a7aSLuis R. Rodriguez 1076e38f8a7aSLuis R. Rodriguez /* 1077e38f8a7aSLuis R. Rodriguez * Called upon reg changes or a new wiphy is added 1078e38f8a7aSLuis R. Rodriguez */ 1079e38f8a7aSLuis R. Rodriguez static void wiphy_update_beacon_reg(struct wiphy *wiphy) 1080e38f8a7aSLuis R. Rodriguez { 1081e38f8a7aSLuis R. Rodriguez unsigned int i; 1082e38f8a7aSLuis R. Rodriguez struct ieee80211_supported_band *sband; 1083e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon; 1084e38f8a7aSLuis R. Rodriguez 1085e38f8a7aSLuis R. Rodriguez assert_cfg80211_lock(); 1086e38f8a7aSLuis R. Rodriguez 1087e38f8a7aSLuis R. Rodriguez if (list_empty(®_beacon_list)) 1088e38f8a7aSLuis R. Rodriguez return; 1089e38f8a7aSLuis R. Rodriguez 1090e38f8a7aSLuis R. Rodriguez list_for_each_entry(reg_beacon, ®_beacon_list, list) { 1091e38f8a7aSLuis R. Rodriguez if (!wiphy->bands[reg_beacon->chan.band]) 1092e38f8a7aSLuis R. Rodriguez continue; 1093e38f8a7aSLuis R. Rodriguez sband = wiphy->bands[reg_beacon->chan.band]; 1094e38f8a7aSLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 1095e38f8a7aSLuis R. Rodriguez handle_reg_beacon(wiphy, i, reg_beacon); 1096e38f8a7aSLuis R. Rodriguez } 1097e38f8a7aSLuis R. Rodriguez } 1098e38f8a7aSLuis R. Rodriguez 1099e38f8a7aSLuis R. Rodriguez static bool reg_is_world_roaming(struct wiphy *wiphy) 1100e38f8a7aSLuis R. Rodriguez { 1101e38f8a7aSLuis R. Rodriguez if (is_world_regdom(cfg80211_regdomain->alpha2) || 1102e38f8a7aSLuis R. Rodriguez (wiphy->regd && is_world_regdom(wiphy->regd->alpha2))) 1103e38f8a7aSLuis R. Rodriguez return true; 1104b1ed8dddSLuis R. Rodriguez if (last_request && 1105b1ed8dddSLuis R. Rodriguez last_request->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE && 11065be83de5SJohannes Berg wiphy->flags & WIPHY_FLAG_CUSTOM_REGULATORY) 1107e38f8a7aSLuis R. Rodriguez return true; 1108e38f8a7aSLuis R. Rodriguez return false; 1109e38f8a7aSLuis R. Rodriguez } 1110e38f8a7aSLuis R. Rodriguez 1111e38f8a7aSLuis R. Rodriguez /* Reap the advantages of previously found beacons */ 1112e38f8a7aSLuis R. Rodriguez static void reg_process_beacons(struct wiphy *wiphy) 1113e38f8a7aSLuis R. Rodriguez { 1114b1ed8dddSLuis R. Rodriguez /* 1115b1ed8dddSLuis R. Rodriguez * Means we are just firing up cfg80211, so no beacons would 1116b1ed8dddSLuis R. Rodriguez * have been processed yet. 1117b1ed8dddSLuis R. Rodriguez */ 1118b1ed8dddSLuis R. Rodriguez if (!last_request) 1119b1ed8dddSLuis R. Rodriguez return; 1120e38f8a7aSLuis R. Rodriguez if (!reg_is_world_roaming(wiphy)) 1121e38f8a7aSLuis R. Rodriguez return; 1122e38f8a7aSLuis R. Rodriguez wiphy_update_beacon_reg(wiphy); 1123e38f8a7aSLuis R. Rodriguez } 1124e38f8a7aSLuis R. Rodriguez 1125038659e7SLuis R. Rodriguez static bool is_ht40_not_allowed(struct ieee80211_channel *chan) 1126038659e7SLuis R. Rodriguez { 1127038659e7SLuis R. Rodriguez if (!chan) 1128038659e7SLuis R. Rodriguez return true; 1129038659e7SLuis R. Rodriguez if (chan->flags & IEEE80211_CHAN_DISABLED) 1130038659e7SLuis R. Rodriguez return true; 1131038659e7SLuis R. Rodriguez /* This would happen when regulatory rules disallow HT40 completely */ 1132038659e7SLuis R. Rodriguez if (IEEE80211_CHAN_NO_HT40 == (chan->flags & (IEEE80211_CHAN_NO_HT40))) 1133038659e7SLuis R. Rodriguez return true; 1134038659e7SLuis R. Rodriguez return false; 1135038659e7SLuis R. Rodriguez } 1136038659e7SLuis R. Rodriguez 1137038659e7SLuis R. Rodriguez static void reg_process_ht_flags_channel(struct wiphy *wiphy, 1138038659e7SLuis R. Rodriguez enum ieee80211_band band, 1139038659e7SLuis R. Rodriguez unsigned int chan_idx) 1140038659e7SLuis R. Rodriguez { 1141038659e7SLuis R. Rodriguez struct ieee80211_supported_band *sband; 1142038659e7SLuis R. Rodriguez struct ieee80211_channel *channel; 1143038659e7SLuis R. Rodriguez struct ieee80211_channel *channel_before = NULL, *channel_after = NULL; 1144038659e7SLuis R. Rodriguez unsigned int i; 1145038659e7SLuis R. Rodriguez 1146038659e7SLuis R. Rodriguez assert_cfg80211_lock(); 1147038659e7SLuis R. Rodriguez 1148038659e7SLuis R. Rodriguez sband = wiphy->bands[band]; 1149038659e7SLuis R. Rodriguez BUG_ON(chan_idx >= sband->n_channels); 1150038659e7SLuis R. Rodriguez channel = &sband->channels[chan_idx]; 1151038659e7SLuis R. Rodriguez 1152038659e7SLuis R. Rodriguez if (is_ht40_not_allowed(channel)) { 1153038659e7SLuis R. Rodriguez channel->flags |= IEEE80211_CHAN_NO_HT40; 1154038659e7SLuis R. Rodriguez return; 1155038659e7SLuis R. Rodriguez } 1156038659e7SLuis R. Rodriguez 1157038659e7SLuis R. Rodriguez /* 1158038659e7SLuis R. Rodriguez * We need to ensure the extension channels exist to 1159038659e7SLuis R. Rodriguez * be able to use HT40- or HT40+, this finds them (or not) 1160038659e7SLuis R. Rodriguez */ 1161038659e7SLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) { 1162038659e7SLuis R. Rodriguez struct ieee80211_channel *c = &sband->channels[i]; 1163038659e7SLuis R. Rodriguez if (c->center_freq == (channel->center_freq - 20)) 1164038659e7SLuis R. Rodriguez channel_before = c; 1165038659e7SLuis R. Rodriguez if (c->center_freq == (channel->center_freq + 20)) 1166038659e7SLuis R. Rodriguez channel_after = c; 1167038659e7SLuis R. Rodriguez } 1168038659e7SLuis R. Rodriguez 1169038659e7SLuis R. Rodriguez /* 1170038659e7SLuis R. Rodriguez * Please note that this assumes target bandwidth is 20 MHz, 1171038659e7SLuis R. Rodriguez * if that ever changes we also need to change the below logic 1172038659e7SLuis R. Rodriguez * to include that as well. 1173038659e7SLuis R. Rodriguez */ 1174038659e7SLuis R. Rodriguez if (is_ht40_not_allowed(channel_before)) 1175689da1b3SLuis R. Rodriguez channel->flags |= IEEE80211_CHAN_NO_HT40MINUS; 1176038659e7SLuis R. Rodriguez else 1177689da1b3SLuis R. Rodriguez channel->flags &= ~IEEE80211_CHAN_NO_HT40MINUS; 1178038659e7SLuis R. Rodriguez 1179038659e7SLuis R. Rodriguez if (is_ht40_not_allowed(channel_after)) 1180689da1b3SLuis R. Rodriguez channel->flags |= IEEE80211_CHAN_NO_HT40PLUS; 1181038659e7SLuis R. Rodriguez else 1182689da1b3SLuis R. Rodriguez channel->flags &= ~IEEE80211_CHAN_NO_HT40PLUS; 1183038659e7SLuis R. Rodriguez } 1184038659e7SLuis R. Rodriguez 1185038659e7SLuis R. Rodriguez static void reg_process_ht_flags_band(struct wiphy *wiphy, 1186038659e7SLuis R. Rodriguez enum ieee80211_band band) 1187038659e7SLuis R. Rodriguez { 1188038659e7SLuis R. Rodriguez unsigned int i; 1189038659e7SLuis R. Rodriguez struct ieee80211_supported_band *sband; 1190038659e7SLuis R. Rodriguez 1191038659e7SLuis R. Rodriguez BUG_ON(!wiphy->bands[band]); 1192038659e7SLuis R. Rodriguez sband = wiphy->bands[band]; 1193038659e7SLuis R. Rodriguez 1194038659e7SLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 1195038659e7SLuis R. Rodriguez reg_process_ht_flags_channel(wiphy, band, i); 1196038659e7SLuis R. Rodriguez } 1197038659e7SLuis R. Rodriguez 1198038659e7SLuis R. Rodriguez static void reg_process_ht_flags(struct wiphy *wiphy) 1199038659e7SLuis R. Rodriguez { 1200038659e7SLuis R. Rodriguez enum ieee80211_band band; 1201038659e7SLuis R. Rodriguez 1202038659e7SLuis R. Rodriguez if (!wiphy) 1203038659e7SLuis R. Rodriguez return; 1204038659e7SLuis R. Rodriguez 1205038659e7SLuis R. Rodriguez for (band = 0; band < IEEE80211_NUM_BANDS; band++) { 1206038659e7SLuis R. Rodriguez if (wiphy->bands[band]) 1207038659e7SLuis R. Rodriguez reg_process_ht_flags_band(wiphy, band); 1208038659e7SLuis R. Rodriguez } 1209038659e7SLuis R. Rodriguez 1210038659e7SLuis R. Rodriguez } 1211038659e7SLuis R. Rodriguez 1212eac03e38SSven Neumann static void wiphy_update_regulatory(struct wiphy *wiphy, 12137db90f4aSLuis R. Rodriguez enum nl80211_reg_initiator initiator) 12148318d78aSJohannes Berg { 12158318d78aSJohannes Berg enum ieee80211_band band; 1216d46e5b1dSLuis R. Rodriguez 1217eac03e38SSven Neumann assert_reg_lock(); 1218eac03e38SSven Neumann 12197db90f4aSLuis R. Rodriguez if (ignore_reg_update(wiphy, initiator)) 1220a203c2aaSSven Neumann return; 1221a203c2aaSSven Neumann 1222b68e6b3bSLuis R. Rodriguez last_request->dfs_region = cfg80211_regdomain->dfs_region; 1223b68e6b3bSLuis R. Rodriguez 1224b2e1b302SLuis R. Rodriguez for (band = 0; band < IEEE80211_NUM_BANDS; band++) { 12258318d78aSJohannes Berg if (wiphy->bands[band]) 12267ca43d03SLuis R. Rodriguez handle_band(wiphy, band, initiator); 1227b2e1b302SLuis R. Rodriguez } 1228a203c2aaSSven Neumann 1229e38f8a7aSLuis R. Rodriguez reg_process_beacons(wiphy); 1230038659e7SLuis R. Rodriguez reg_process_ht_flags(wiphy); 1231b2e1b302SLuis R. Rodriguez if (wiphy->reg_notifier) 1232716f9392SLuis R. Rodriguez wiphy->reg_notifier(wiphy, last_request); 1233b2e1b302SLuis R. Rodriguez } 1234b2e1b302SLuis R. Rodriguez 1235f8a1c774SLuis R. Rodriguez static void regulatory_update(struct wiphy *wiphy, 1236eac03e38SSven Neumann enum nl80211_reg_initiator setby) 1237eac03e38SSven Neumann { 1238eac03e38SSven Neumann mutex_lock(®_mutex); 1239eac03e38SSven Neumann wiphy_update_regulatory(wiphy, setby); 1240eac03e38SSven Neumann mutex_unlock(®_mutex); 1241eac03e38SSven Neumann } 1242eac03e38SSven Neumann 1243d7549cbbSSven Neumann static void update_all_wiphy_regulatory(enum nl80211_reg_initiator initiator) 1244d7549cbbSSven Neumann { 1245d7549cbbSSven Neumann struct cfg80211_registered_device *rdev; 12464a38994fSRajkumar Manoharan struct wiphy *wiphy; 1247d7549cbbSSven Neumann 12484a38994fSRajkumar Manoharan list_for_each_entry(rdev, &cfg80211_rdev_list, list) { 12494a38994fSRajkumar Manoharan wiphy = &rdev->wiphy; 12504a38994fSRajkumar Manoharan wiphy_update_regulatory(wiphy, initiator); 12514a38994fSRajkumar Manoharan /* 12524a38994fSRajkumar Manoharan * Regulatory updates set by CORE are ignored for custom 12534a38994fSRajkumar Manoharan * regulatory cards. Let us notify the changes to the driver, 12544a38994fSRajkumar Manoharan * as some drivers used this to restore its orig_* reg domain. 12554a38994fSRajkumar Manoharan */ 12564a38994fSRajkumar Manoharan if (initiator == NL80211_REGDOM_SET_BY_CORE && 12574a38994fSRajkumar Manoharan wiphy->flags & WIPHY_FLAG_CUSTOM_REGULATORY && 12584a38994fSRajkumar Manoharan wiphy->reg_notifier) 12594a38994fSRajkumar Manoharan wiphy->reg_notifier(wiphy, last_request); 12604a38994fSRajkumar Manoharan } 1261d7549cbbSSven Neumann } 1262d7549cbbSSven Neumann 12631fa25e41SLuis R. Rodriguez static void handle_channel_custom(struct wiphy *wiphy, 12641fa25e41SLuis R. Rodriguez enum ieee80211_band band, 12651fa25e41SLuis R. Rodriguez unsigned int chan_idx, 12661fa25e41SLuis R. Rodriguez const struct ieee80211_regdomain *regd) 12671fa25e41SLuis R. Rodriguez { 12681fa25e41SLuis R. Rodriguez int r; 1269038659e7SLuis R. Rodriguez u32 desired_bw_khz = MHZ_TO_KHZ(20); 1270038659e7SLuis R. Rodriguez u32 bw_flags = 0; 12711fa25e41SLuis R. Rodriguez const struct ieee80211_reg_rule *reg_rule = NULL; 12721fa25e41SLuis R. Rodriguez const struct ieee80211_power_rule *power_rule = NULL; 1273038659e7SLuis R. Rodriguez const struct ieee80211_freq_range *freq_range = NULL; 12741fa25e41SLuis R. Rodriguez struct ieee80211_supported_band *sband; 12751fa25e41SLuis R. Rodriguez struct ieee80211_channel *chan; 12761fa25e41SLuis R. Rodriguez 1277abc7381bSLuis R. Rodriguez assert_reg_lock(); 1278ac46d48eSLuis R. Rodriguez 12791fa25e41SLuis R. Rodriguez sband = wiphy->bands[band]; 12801fa25e41SLuis R. Rodriguez BUG_ON(chan_idx >= sband->n_channels); 12811fa25e41SLuis R. Rodriguez chan = &sband->channels[chan_idx]; 12821fa25e41SLuis R. Rodriguez 1283038659e7SLuis R. Rodriguez r = freq_reg_info_regd(wiphy, 1284038659e7SLuis R. Rodriguez MHZ_TO_KHZ(chan->center_freq), 1285038659e7SLuis R. Rodriguez desired_bw_khz, 1286038659e7SLuis R. Rodriguez ®_rule, 1287038659e7SLuis R. Rodriguez regd); 12881fa25e41SLuis R. Rodriguez 12891fa25e41SLuis R. Rodriguez if (r) { 1290d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Disabling freq %d MHz as custom " 1291a6518536SLuis R. Rodriguez "regd has no rule that fits a %d MHz " 1292a6518536SLuis R. Rodriguez "wide channel\n", 1293a6518536SLuis R. Rodriguez chan->center_freq, 1294a6518536SLuis R. Rodriguez KHZ_TO_MHZ(desired_bw_khz)); 12951fa25e41SLuis R. Rodriguez chan->flags = IEEE80211_CHAN_DISABLED; 12961fa25e41SLuis R. Rodriguez return; 12971fa25e41SLuis R. Rodriguez } 12981fa25e41SLuis R. Rodriguez 1299e702d3cfSLuis R. Rodriguez chan_reg_rule_print_dbg(chan, desired_bw_khz, reg_rule); 1300e702d3cfSLuis R. Rodriguez 13011fa25e41SLuis R. Rodriguez power_rule = ®_rule->power_rule; 1302038659e7SLuis R. Rodriguez freq_range = ®_rule->freq_range; 13031fa25e41SLuis R. Rodriguez 1304038659e7SLuis R. Rodriguez if (freq_range->max_bandwidth_khz < MHZ_TO_KHZ(40)) 1305038659e7SLuis R. Rodriguez bw_flags = IEEE80211_CHAN_NO_HT40; 1306038659e7SLuis R. Rodriguez 1307038659e7SLuis R. Rodriguez chan->flags |= map_regdom_flags(reg_rule->flags) | bw_flags; 13081fa25e41SLuis R. Rodriguez chan->max_antenna_gain = (int) MBI_TO_DBI(power_rule->max_antenna_gain); 13091fa25e41SLuis R. Rodriguez chan->max_power = (int) MBM_TO_DBM(power_rule->max_eirp); 13101fa25e41SLuis R. Rodriguez } 13111fa25e41SLuis R. Rodriguez 13121fa25e41SLuis R. Rodriguez static void handle_band_custom(struct wiphy *wiphy, enum ieee80211_band band, 13131fa25e41SLuis R. Rodriguez const struct ieee80211_regdomain *regd) 13141fa25e41SLuis R. Rodriguez { 13151fa25e41SLuis R. Rodriguez unsigned int i; 13161fa25e41SLuis R. Rodriguez struct ieee80211_supported_band *sband; 13171fa25e41SLuis R. Rodriguez 13181fa25e41SLuis R. Rodriguez BUG_ON(!wiphy->bands[band]); 13191fa25e41SLuis R. Rodriguez sband = wiphy->bands[band]; 13201fa25e41SLuis R. Rodriguez 13211fa25e41SLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 13221fa25e41SLuis R. Rodriguez handle_channel_custom(wiphy, band, i, regd); 13231fa25e41SLuis R. Rodriguez } 13241fa25e41SLuis R. Rodriguez 13251fa25e41SLuis R. Rodriguez /* Used by drivers prior to wiphy registration */ 13261fa25e41SLuis R. Rodriguez void wiphy_apply_custom_regulatory(struct wiphy *wiphy, 13271fa25e41SLuis R. Rodriguez const struct ieee80211_regdomain *regd) 13281fa25e41SLuis R. Rodriguez { 13291fa25e41SLuis R. Rodriguez enum ieee80211_band band; 1330bbcf3f02SLuis R. Rodriguez unsigned int bands_set = 0; 1331ac46d48eSLuis R. Rodriguez 1332abc7381bSLuis R. Rodriguez mutex_lock(®_mutex); 13331fa25e41SLuis R. Rodriguez for (band = 0; band < IEEE80211_NUM_BANDS; band++) { 1334bbcf3f02SLuis R. Rodriguez if (!wiphy->bands[band]) 1335bbcf3f02SLuis R. Rodriguez continue; 13361fa25e41SLuis R. Rodriguez handle_band_custom(wiphy, band, regd); 1337bbcf3f02SLuis R. Rodriguez bands_set++; 13381fa25e41SLuis R. Rodriguez } 1339abc7381bSLuis R. Rodriguez mutex_unlock(®_mutex); 1340bbcf3f02SLuis R. Rodriguez 1341bbcf3f02SLuis R. Rodriguez /* 1342bbcf3f02SLuis R. Rodriguez * no point in calling this if it won't have any effect 1343bbcf3f02SLuis R. Rodriguez * on your device's supportd bands. 1344bbcf3f02SLuis R. Rodriguez */ 1345bbcf3f02SLuis R. Rodriguez WARN_ON(!bands_set); 13461fa25e41SLuis R. Rodriguez } 13471fa25e41SLuis R. Rodriguez EXPORT_SYMBOL(wiphy_apply_custom_regulatory); 13481fa25e41SLuis R. Rodriguez 1349fb1fc7adSLuis R. Rodriguez /* 1350fb1fc7adSLuis R. Rodriguez * Return value which can be used by ignore_request() to indicate 1351fb1fc7adSLuis R. Rodriguez * it has been determined we should intersect two regulatory domains 1352fb1fc7adSLuis R. Rodriguez */ 13539c96477dSLuis R. Rodriguez #define REG_INTERSECT 1 13549c96477dSLuis R. Rodriguez 135584fa4f43SJohannes Berg /* This has the logic which determines when a new request 135684fa4f43SJohannes Berg * should be ignored. */ 13572f92cd2eSLuis R. Rodriguez static int ignore_request(struct wiphy *wiphy, 13582f92cd2eSLuis R. Rodriguez struct regulatory_request *pending_request) 135984fa4f43SJohannes Berg { 1360806a9e39SLuis R. Rodriguez struct wiphy *last_wiphy = NULL; 1361761cf7ecSLuis R. Rodriguez 1362761cf7ecSLuis R. Rodriguez assert_cfg80211_lock(); 1363761cf7ecSLuis R. Rodriguez 136484fa4f43SJohannes Berg /* All initial requests are respected */ 136584fa4f43SJohannes Berg if (!last_request) 136684fa4f43SJohannes Berg return 0; 136784fa4f43SJohannes Berg 13682f92cd2eSLuis R. Rodriguez switch (pending_request->initiator) { 13697db90f4aSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_CORE: 137009d989d1SLuis R. Rodriguez return 0; 13717db90f4aSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_COUNTRY_IE: 1372806a9e39SLuis R. Rodriguez 137357b5ce07SLuis R. Rodriguez if (reg_request_cell_base(last_request)) { 137457b5ce07SLuis R. Rodriguez /* Trust a Cell base station over the AP's country IE */ 137557b5ce07SLuis R. Rodriguez if (regdom_changes(pending_request->alpha2)) 137657b5ce07SLuis R. Rodriguez return -EOPNOTSUPP; 137757b5ce07SLuis R. Rodriguez return -EALREADY; 137857b5ce07SLuis R. Rodriguez } 137957b5ce07SLuis R. Rodriguez 1380806a9e39SLuis R. Rodriguez last_wiphy = wiphy_idx_to_wiphy(last_request->wiphy_idx); 1381806a9e39SLuis R. Rodriguez 13822f92cd2eSLuis R. Rodriguez if (unlikely(!is_an_alpha2(pending_request->alpha2))) 138384fa4f43SJohannes Berg return -EINVAL; 13847db90f4aSLuis R. Rodriguez if (last_request->initiator == 13857db90f4aSLuis R. Rodriguez NL80211_REGDOM_SET_BY_COUNTRY_IE) { 1386806a9e39SLuis R. Rodriguez if (last_wiphy != wiphy) { 138784fa4f43SJohannes Berg /* 138884fa4f43SJohannes Berg * Two cards with two APs claiming different 13891fe90b03SThadeu Lima de Souza Cascardo * Country IE alpha2s. We could 139084fa4f43SJohannes Berg * intersect them, but that seems unlikely 139184fa4f43SJohannes Berg * to be correct. Reject second one for now. 139284fa4f43SJohannes Berg */ 13932f92cd2eSLuis R. Rodriguez if (regdom_changes(pending_request->alpha2)) 139484fa4f43SJohannes Berg return -EOPNOTSUPP; 139584fa4f43SJohannes Berg return -EALREADY; 139684fa4f43SJohannes Berg } 1397fb1fc7adSLuis R. Rodriguez /* 1398fb1fc7adSLuis R. Rodriguez * Two consecutive Country IE hints on the same wiphy. 1399fb1fc7adSLuis R. Rodriguez * This should be picked up early by the driver/stack 1400fb1fc7adSLuis R. Rodriguez */ 14012f92cd2eSLuis R. Rodriguez if (WARN_ON(regdom_changes(pending_request->alpha2))) 140284fa4f43SJohannes Berg return 0; 140384fa4f43SJohannes Berg return -EALREADY; 140484fa4f43SJohannes Berg } 1405a171fba4SLuis R. Rodriguez return 0; 14067db90f4aSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_DRIVER: 14077db90f4aSLuis R. Rodriguez if (last_request->initiator == NL80211_REGDOM_SET_BY_CORE) { 14082f92cd2eSLuis R. Rodriguez if (regdom_changes(pending_request->alpha2)) 1409e74b1e7fSLuis R. Rodriguez return 0; 1410e74b1e7fSLuis R. Rodriguez return -EALREADY; 1411e74b1e7fSLuis R. Rodriguez } 1412fff32c04SLuis R. Rodriguez 1413fff32c04SLuis R. Rodriguez /* 1414fff32c04SLuis R. Rodriguez * This would happen if you unplug and plug your card 1415fff32c04SLuis R. Rodriguez * back in or if you add a new device for which the previously 1416fff32c04SLuis R. Rodriguez * loaded card also agrees on the regulatory domain. 1417fff32c04SLuis R. Rodriguez */ 14187db90f4aSLuis R. Rodriguez if (last_request->initiator == NL80211_REGDOM_SET_BY_DRIVER && 14192f92cd2eSLuis R. Rodriguez !regdom_changes(pending_request->alpha2)) 1420fff32c04SLuis R. Rodriguez return -EALREADY; 1421fff32c04SLuis R. Rodriguez 14223e0c3ff3SLuis R. Rodriguez return REG_INTERSECT; 14237db90f4aSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_USER: 142457b5ce07SLuis R. Rodriguez if (reg_request_cell_base(pending_request)) 142557b5ce07SLuis R. Rodriguez return reg_ignore_cell_hint(pending_request); 142657b5ce07SLuis R. Rodriguez 142757b5ce07SLuis R. Rodriguez if (reg_request_cell_base(last_request)) 142857b5ce07SLuis R. Rodriguez return -EOPNOTSUPP; 142957b5ce07SLuis R. Rodriguez 14307db90f4aSLuis R. Rodriguez if (last_request->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE) 14319c96477dSLuis R. Rodriguez return REG_INTERSECT; 1432fb1fc7adSLuis R. Rodriguez /* 1433fb1fc7adSLuis R. Rodriguez * If the user knows better the user should set the regdom 1434fb1fc7adSLuis R. Rodriguez * to their country before the IE is picked up 1435fb1fc7adSLuis R. Rodriguez */ 14367db90f4aSLuis R. Rodriguez if (last_request->initiator == NL80211_REGDOM_SET_BY_USER && 14373f2355cbSLuis R. Rodriguez last_request->intersect) 14383f2355cbSLuis R. Rodriguez return -EOPNOTSUPP; 1439fb1fc7adSLuis R. Rodriguez /* 1440fb1fc7adSLuis R. Rodriguez * Process user requests only after previous user/driver/core 1441fb1fc7adSLuis R. Rodriguez * requests have been processed 1442fb1fc7adSLuis R. Rodriguez */ 14437db90f4aSLuis R. Rodriguez if (last_request->initiator == NL80211_REGDOM_SET_BY_CORE || 14447db90f4aSLuis R. Rodriguez last_request->initiator == NL80211_REGDOM_SET_BY_DRIVER || 14457db90f4aSLuis R. Rodriguez last_request->initiator == NL80211_REGDOM_SET_BY_USER) { 144669b1572bSLuis R. Rodriguez if (regdom_changes(last_request->alpha2)) 14475eebade6SLuis R. Rodriguez return -EAGAIN; 14485eebade6SLuis R. Rodriguez } 14495eebade6SLuis R. Rodriguez 1450baeb66feSJohn W. Linville if (!regdom_changes(pending_request->alpha2)) 1451e74b1e7fSLuis R. Rodriguez return -EALREADY; 1452e74b1e7fSLuis R. Rodriguez 145384fa4f43SJohannes Berg return 0; 145484fa4f43SJohannes Berg } 145584fa4f43SJohannes Berg 145684fa4f43SJohannes Berg return -EINVAL; 145784fa4f43SJohannes Berg } 145884fa4f43SJohannes Berg 1459b2e253cfSLuis R. Rodriguez static void reg_set_request_processed(void) 1460b2e253cfSLuis R. Rodriguez { 1461b2e253cfSLuis R. Rodriguez bool need_more_processing = false; 1462b2e253cfSLuis R. Rodriguez 1463b2e253cfSLuis R. Rodriguez last_request->processed = true; 1464b2e253cfSLuis R. Rodriguez 1465b2e253cfSLuis R. Rodriguez spin_lock(®_requests_lock); 1466b2e253cfSLuis R. Rodriguez if (!list_empty(®_requests_list)) 1467b2e253cfSLuis R. Rodriguez need_more_processing = true; 1468b2e253cfSLuis R. Rodriguez spin_unlock(®_requests_lock); 1469b2e253cfSLuis R. Rodriguez 1470a90c7a31SLuis R. Rodriguez if (last_request->initiator == NL80211_REGDOM_SET_BY_USER) 1471fe20b39eSEliad Peller cancel_delayed_work(®_timeout); 1472a90c7a31SLuis R. Rodriguez 1473b2e253cfSLuis R. Rodriguez if (need_more_processing) 1474b2e253cfSLuis R. Rodriguez schedule_work(®_work); 1475b2e253cfSLuis R. Rodriguez } 1476b2e253cfSLuis R. Rodriguez 1477d1c96a9aSLuis R. Rodriguez /** 1478d1c96a9aSLuis R. Rodriguez * __regulatory_hint - hint to the wireless core a regulatory domain 1479d1c96a9aSLuis R. Rodriguez * @wiphy: if the hint comes from country information from an AP, this 1480d1c96a9aSLuis R. Rodriguez * is required to be set to the wiphy that received the information 148128da32d7SLuis R. Rodriguez * @pending_request: the regulatory request currently being processed 1482d1c96a9aSLuis R. Rodriguez * 1483d1c96a9aSLuis R. Rodriguez * The Wireless subsystem can use this function to hint to the wireless core 148428da32d7SLuis R. Rodriguez * what it believes should be the current regulatory domain. 1485d1c96a9aSLuis R. Rodriguez * 1486d1c96a9aSLuis R. Rodriguez * Returns zero if all went fine, %-EALREADY if a regulatory domain had 1487d1c96a9aSLuis R. Rodriguez * already been set or other standard error codes. 1488d1c96a9aSLuis R. Rodriguez * 1489abc7381bSLuis R. Rodriguez * Caller must hold &cfg80211_mutex and ®_mutex 1490d1c96a9aSLuis R. Rodriguez */ 149128da32d7SLuis R. Rodriguez static int __regulatory_hint(struct wiphy *wiphy, 149228da32d7SLuis R. Rodriguez struct regulatory_request *pending_request) 1493b2e1b302SLuis R. Rodriguez { 14949c96477dSLuis R. Rodriguez bool intersect = false; 1495b2e1b302SLuis R. Rodriguez int r = 0; 1496b2e1b302SLuis R. Rodriguez 1497761cf7ecSLuis R. Rodriguez assert_cfg80211_lock(); 1498761cf7ecSLuis R. Rodriguez 14992f92cd2eSLuis R. Rodriguez r = ignore_request(wiphy, pending_request); 15009c96477dSLuis R. Rodriguez 15013e0c3ff3SLuis R. Rodriguez if (r == REG_INTERSECT) { 15027db90f4aSLuis R. Rodriguez if (pending_request->initiator == 15037db90f4aSLuis R. Rodriguez NL80211_REGDOM_SET_BY_DRIVER) { 15043e0c3ff3SLuis R. Rodriguez r = reg_copy_regd(&wiphy->regd, cfg80211_regdomain); 1505d951c1ddSLuis R. Rodriguez if (r) { 1506d951c1ddSLuis R. Rodriguez kfree(pending_request); 1507b2e1b302SLuis R. Rodriguez return r; 15083e0c3ff3SLuis R. Rodriguez } 1509d951c1ddSLuis R. Rodriguez } 15103e0c3ff3SLuis R. Rodriguez intersect = true; 15113e0c3ff3SLuis R. Rodriguez } else if (r) { 1512fb1fc7adSLuis R. Rodriguez /* 1513fb1fc7adSLuis R. Rodriguez * If the regulatory domain being requested by the 15143e0c3ff3SLuis R. Rodriguez * driver has already been set just copy it to the 1515fb1fc7adSLuis R. Rodriguez * wiphy 1516fb1fc7adSLuis R. Rodriguez */ 151728da32d7SLuis R. Rodriguez if (r == -EALREADY && 15187db90f4aSLuis R. Rodriguez pending_request->initiator == 15197db90f4aSLuis R. Rodriguez NL80211_REGDOM_SET_BY_DRIVER) { 15203e0c3ff3SLuis R. Rodriguez r = reg_copy_regd(&wiphy->regd, cfg80211_regdomain); 1521d951c1ddSLuis R. Rodriguez if (r) { 1522d951c1ddSLuis R. Rodriguez kfree(pending_request); 15233e0c3ff3SLuis R. Rodriguez return r; 1524d951c1ddSLuis R. Rodriguez } 15253e0c3ff3SLuis R. Rodriguez r = -EALREADY; 15263e0c3ff3SLuis R. Rodriguez goto new_request; 15273e0c3ff3SLuis R. Rodriguez } 1528d951c1ddSLuis R. Rodriguez kfree(pending_request); 15293e0c3ff3SLuis R. Rodriguez return r; 15303e0c3ff3SLuis R. Rodriguez } 1531b2e1b302SLuis R. Rodriguez 15323e0c3ff3SLuis R. Rodriguez new_request: 1533a042994dSLuis R. Rodriguez if (last_request != &core_request_world) 1534f6037d09SJohannes Berg kfree(last_request); 1535d951c1ddSLuis R. Rodriguez 1536d951c1ddSLuis R. Rodriguez last_request = pending_request; 1537d951c1ddSLuis R. Rodriguez last_request->intersect = intersect; 1538d951c1ddSLuis R. Rodriguez 1539d951c1ddSLuis R. Rodriguez pending_request = NULL; 15403e0c3ff3SLuis R. Rodriguez 154109d989d1SLuis R. Rodriguez if (last_request->initiator == NL80211_REGDOM_SET_BY_USER) { 154209d989d1SLuis R. Rodriguez user_alpha2[0] = last_request->alpha2[0]; 154309d989d1SLuis R. Rodriguez user_alpha2[1] = last_request->alpha2[1]; 154409d989d1SLuis R. Rodriguez } 154509d989d1SLuis R. Rodriguez 15463e0c3ff3SLuis R. Rodriguez /* When r == REG_INTERSECT we do need to call CRDA */ 154773d54c9eSLuis R. Rodriguez if (r < 0) { 154873d54c9eSLuis R. Rodriguez /* 154973d54c9eSLuis R. Rodriguez * Since CRDA will not be called in this case as we already 155073d54c9eSLuis R. Rodriguez * have applied the requested regulatory domain before we just 155173d54c9eSLuis R. Rodriguez * inform userspace we have processed the request 155273d54c9eSLuis R. Rodriguez */ 1553b2e253cfSLuis R. Rodriguez if (r == -EALREADY) { 155473d54c9eSLuis R. Rodriguez nl80211_send_reg_change_event(last_request); 1555b2e253cfSLuis R. Rodriguez reg_set_request_processed(); 1556b2e253cfSLuis R. Rodriguez } 15573e0c3ff3SLuis R. Rodriguez return r; 155873d54c9eSLuis R. Rodriguez } 15593e0c3ff3SLuis R. Rodriguez 1560d951c1ddSLuis R. Rodriguez return call_crda(last_request->alpha2); 1561b2e1b302SLuis R. Rodriguez } 1562b2e1b302SLuis R. Rodriguez 156330a548c7SLuis R. Rodriguez /* This processes *all* regulatory hints */ 15648848bef0SLuis R. Rodriguez static void reg_process_hint(struct regulatory_request *reg_request, 15658848bef0SLuis R. Rodriguez enum nl80211_reg_initiator reg_initiator) 1566fe33eb39SLuis R. Rodriguez { 1567fe33eb39SLuis R. Rodriguez int r = 0; 1568fe33eb39SLuis R. Rodriguez struct wiphy *wiphy = NULL; 1569fe33eb39SLuis R. Rodriguez 1570fe33eb39SLuis R. Rodriguez BUG_ON(!reg_request->alpha2); 1571fe33eb39SLuis R. Rodriguez 1572fe33eb39SLuis R. Rodriguez if (wiphy_idx_valid(reg_request->wiphy_idx)) 1573fe33eb39SLuis R. Rodriguez wiphy = wiphy_idx_to_wiphy(reg_request->wiphy_idx); 1574fe33eb39SLuis R. Rodriguez 15758848bef0SLuis R. Rodriguez if (reg_initiator == NL80211_REGDOM_SET_BY_DRIVER && 1576fe33eb39SLuis R. Rodriguez !wiphy) { 1577d951c1ddSLuis R. Rodriguez kfree(reg_request); 1578b0e2880bSLuis R. Rodriguez return; 1579fe33eb39SLuis R. Rodriguez } 1580fe33eb39SLuis R. Rodriguez 158128da32d7SLuis R. Rodriguez r = __regulatory_hint(wiphy, reg_request); 1582fe33eb39SLuis R. Rodriguez /* This is required so that the orig_* parameters are saved */ 15835be83de5SJohannes Berg if (r == -EALREADY && wiphy && 1584a90c7a31SLuis R. Rodriguez wiphy->flags & WIPHY_FLAG_STRICT_REGULATORY) { 15858848bef0SLuis R. Rodriguez wiphy_update_regulatory(wiphy, reg_initiator); 1586a90c7a31SLuis R. Rodriguez return; 1587a90c7a31SLuis R. Rodriguez } 1588a90c7a31SLuis R. Rodriguez 1589a90c7a31SLuis R. Rodriguez /* 1590a90c7a31SLuis R. Rodriguez * We only time out user hints, given that they should be the only 1591a90c7a31SLuis R. Rodriguez * source of bogus requests. 1592a90c7a31SLuis R. Rodriguez */ 1593c989bb15SLuis R. Rodriguez if (r != -EALREADY && 15948848bef0SLuis R. Rodriguez reg_initiator == NL80211_REGDOM_SET_BY_USER) 1595a90c7a31SLuis R. Rodriguez schedule_delayed_work(®_timeout, msecs_to_jiffies(3142)); 1596fe33eb39SLuis R. Rodriguez } 1597fe33eb39SLuis R. Rodriguez 1598b2e253cfSLuis R. Rodriguez /* 1599b2e253cfSLuis R. Rodriguez * Processes regulatory hints, this is all the NL80211_REGDOM_SET_BY_* 1600b2e253cfSLuis R. Rodriguez * Regulatory hints come on a first come first serve basis and we 1601b2e253cfSLuis R. Rodriguez * must process each one atomically. 1602b2e253cfSLuis R. Rodriguez */ 1603fe33eb39SLuis R. Rodriguez static void reg_process_pending_hints(void) 1604fe33eb39SLuis R. Rodriguez { 1605fe33eb39SLuis R. Rodriguez struct regulatory_request *reg_request; 1606fe33eb39SLuis R. Rodriguez 1607b0e2880bSLuis R. Rodriguez mutex_lock(&cfg80211_mutex); 1608b0e2880bSLuis R. Rodriguez mutex_lock(®_mutex); 1609b0e2880bSLuis R. Rodriguez 1610b2e253cfSLuis R. Rodriguez /* When last_request->processed becomes true this will be rescheduled */ 1611b2e253cfSLuis R. Rodriguez if (last_request && !last_request->processed) { 1612b2e253cfSLuis R. Rodriguez REG_DBG_PRINT("Pending regulatory request, waiting " 161312c5ffb5SJoe Perches "for it to be processed...\n"); 1614b2e253cfSLuis R. Rodriguez goto out; 1615b2e253cfSLuis R. Rodriguez } 1616b2e253cfSLuis R. Rodriguez 1617fe33eb39SLuis R. Rodriguez spin_lock(®_requests_lock); 1618b2e253cfSLuis R. Rodriguez 1619b2e253cfSLuis R. Rodriguez if (list_empty(®_requests_list)) { 1620b2e253cfSLuis R. Rodriguez spin_unlock(®_requests_lock); 1621b2e253cfSLuis R. Rodriguez goto out; 1622b2e253cfSLuis R. Rodriguez } 1623b2e253cfSLuis R. Rodriguez 1624fe33eb39SLuis R. Rodriguez reg_request = list_first_entry(®_requests_list, 1625fe33eb39SLuis R. Rodriguez struct regulatory_request, 1626fe33eb39SLuis R. Rodriguez list); 1627fe33eb39SLuis R. Rodriguez list_del_init(®_request->list); 1628fe33eb39SLuis R. Rodriguez 1629d951c1ddSLuis R. Rodriguez spin_unlock(®_requests_lock); 1630b0e2880bSLuis R. Rodriguez 16318848bef0SLuis R. Rodriguez reg_process_hint(reg_request, reg_request->initiator); 1632b2e253cfSLuis R. Rodriguez 1633b2e253cfSLuis R. Rodriguez out: 1634b0e2880bSLuis R. Rodriguez mutex_unlock(®_mutex); 1635b0e2880bSLuis R. Rodriguez mutex_unlock(&cfg80211_mutex); 1636fe33eb39SLuis R. Rodriguez } 1637fe33eb39SLuis R. Rodriguez 1638e38f8a7aSLuis R. Rodriguez /* Processes beacon hints -- this has nothing to do with country IEs */ 1639e38f8a7aSLuis R. Rodriguez static void reg_process_pending_beacon_hints(void) 1640e38f8a7aSLuis R. Rodriguez { 164179c97e97SJohannes Berg struct cfg80211_registered_device *rdev; 1642e38f8a7aSLuis R. Rodriguez struct reg_beacon *pending_beacon, *tmp; 1643e38f8a7aSLuis R. Rodriguez 1644abc7381bSLuis R. Rodriguez /* 1645abc7381bSLuis R. Rodriguez * No need to hold the reg_mutex here as we just touch wiphys 1646abc7381bSLuis R. Rodriguez * and do not read or access regulatory variables. 1647abc7381bSLuis R. Rodriguez */ 1648e38f8a7aSLuis R. Rodriguez mutex_lock(&cfg80211_mutex); 1649e38f8a7aSLuis R. Rodriguez 1650e38f8a7aSLuis R. Rodriguez /* This goes through the _pending_ beacon list */ 1651e38f8a7aSLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 1652e38f8a7aSLuis R. Rodriguez 1653e38f8a7aSLuis R. Rodriguez if (list_empty(®_pending_beacons)) { 1654e38f8a7aSLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 1655e38f8a7aSLuis R. Rodriguez goto out; 1656e38f8a7aSLuis R. Rodriguez } 1657e38f8a7aSLuis R. Rodriguez 1658e38f8a7aSLuis R. Rodriguez list_for_each_entry_safe(pending_beacon, tmp, 1659e38f8a7aSLuis R. Rodriguez ®_pending_beacons, list) { 1660e38f8a7aSLuis R. Rodriguez 1661e38f8a7aSLuis R. Rodriguez list_del_init(&pending_beacon->list); 1662e38f8a7aSLuis R. Rodriguez 1663e38f8a7aSLuis R. Rodriguez /* Applies the beacon hint to current wiphys */ 166479c97e97SJohannes Berg list_for_each_entry(rdev, &cfg80211_rdev_list, list) 166579c97e97SJohannes Berg wiphy_update_new_beacon(&rdev->wiphy, pending_beacon); 1666e38f8a7aSLuis R. Rodriguez 1667e38f8a7aSLuis R. Rodriguez /* Remembers the beacon hint for new wiphys or reg changes */ 1668e38f8a7aSLuis R. Rodriguez list_add_tail(&pending_beacon->list, ®_beacon_list); 1669e38f8a7aSLuis R. Rodriguez } 1670e38f8a7aSLuis R. Rodriguez 1671e38f8a7aSLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 1672e38f8a7aSLuis R. Rodriguez out: 1673e38f8a7aSLuis R. Rodriguez mutex_unlock(&cfg80211_mutex); 1674e38f8a7aSLuis R. Rodriguez } 1675e38f8a7aSLuis R. Rodriguez 1676fe33eb39SLuis R. Rodriguez static void reg_todo(struct work_struct *work) 1677fe33eb39SLuis R. Rodriguez { 1678fe33eb39SLuis R. Rodriguez reg_process_pending_hints(); 1679e38f8a7aSLuis R. Rodriguez reg_process_pending_beacon_hints(); 1680fe33eb39SLuis R. Rodriguez } 1681fe33eb39SLuis R. Rodriguez 1682fe33eb39SLuis R. Rodriguez static void queue_regulatory_request(struct regulatory_request *request) 1683fe33eb39SLuis R. Rodriguez { 1684c61029c7SJohn W. Linville if (isalpha(request->alpha2[0])) 1685c61029c7SJohn W. Linville request->alpha2[0] = toupper(request->alpha2[0]); 1686c61029c7SJohn W. Linville if (isalpha(request->alpha2[1])) 1687c61029c7SJohn W. Linville request->alpha2[1] = toupper(request->alpha2[1]); 1688c61029c7SJohn W. Linville 1689fe33eb39SLuis R. Rodriguez spin_lock(®_requests_lock); 1690fe33eb39SLuis R. Rodriguez list_add_tail(&request->list, ®_requests_list); 1691fe33eb39SLuis R. Rodriguez spin_unlock(®_requests_lock); 1692fe33eb39SLuis R. Rodriguez 1693fe33eb39SLuis R. Rodriguez schedule_work(®_work); 1694fe33eb39SLuis R. Rodriguez } 1695fe33eb39SLuis R. Rodriguez 169609d989d1SLuis R. Rodriguez /* 169709d989d1SLuis R. Rodriguez * Core regulatory hint -- happens during cfg80211_init() 169809d989d1SLuis R. Rodriguez * and when we restore regulatory settings. 169909d989d1SLuis R. Rodriguez */ 1700ba25c141SLuis R. Rodriguez static int regulatory_hint_core(const char *alpha2) 1701ba25c141SLuis R. Rodriguez { 1702ba25c141SLuis R. Rodriguez struct regulatory_request *request; 1703ba25c141SLuis R. Rodriguez 1704ba25c141SLuis R. Rodriguez request = kzalloc(sizeof(struct regulatory_request), 1705ba25c141SLuis R. Rodriguez GFP_KERNEL); 1706ba25c141SLuis R. Rodriguez if (!request) 1707ba25c141SLuis R. Rodriguez return -ENOMEM; 1708ba25c141SLuis R. Rodriguez 1709ba25c141SLuis R. Rodriguez request->alpha2[0] = alpha2[0]; 1710ba25c141SLuis R. Rodriguez request->alpha2[1] = alpha2[1]; 17117db90f4aSLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_CORE; 1712ba25c141SLuis R. Rodriguez 171331e99729SLuis R. Rodriguez queue_regulatory_request(request); 17145078b2e3SLuis R. Rodriguez 1715fe33eb39SLuis R. Rodriguez return 0; 1716ba25c141SLuis R. Rodriguez } 1717ba25c141SLuis R. Rodriguez 1718fe33eb39SLuis R. Rodriguez /* User hints */ 171957b5ce07SLuis R. Rodriguez int regulatory_hint_user(const char *alpha2, 172057b5ce07SLuis R. Rodriguez enum nl80211_user_reg_hint_type user_reg_hint_type) 1721b2e1b302SLuis R. Rodriguez { 1722fe33eb39SLuis R. Rodriguez struct regulatory_request *request; 1723fe33eb39SLuis R. Rodriguez 1724be3d4810SJohannes Berg BUG_ON(!alpha2); 1725b2e1b302SLuis R. Rodriguez 1726fe33eb39SLuis R. Rodriguez request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL); 1727fe33eb39SLuis R. Rodriguez if (!request) 1728fe33eb39SLuis R. Rodriguez return -ENOMEM; 1729fe33eb39SLuis R. Rodriguez 1730fe33eb39SLuis R. Rodriguez request->wiphy_idx = WIPHY_IDX_STALE; 1731fe33eb39SLuis R. Rodriguez request->alpha2[0] = alpha2[0]; 1732fe33eb39SLuis R. Rodriguez request->alpha2[1] = alpha2[1]; 1733e12822e1SLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_USER; 173457b5ce07SLuis R. Rodriguez request->user_reg_hint_type = user_reg_hint_type; 1735fe33eb39SLuis R. Rodriguez 1736fe33eb39SLuis R. Rodriguez queue_regulatory_request(request); 1737fe33eb39SLuis R. Rodriguez 1738fe33eb39SLuis R. Rodriguez return 0; 1739fe33eb39SLuis R. Rodriguez } 1740fe33eb39SLuis R. Rodriguez 1741fe33eb39SLuis R. Rodriguez /* Driver hints */ 1742fe33eb39SLuis R. Rodriguez int regulatory_hint(struct wiphy *wiphy, const char *alpha2) 1743fe33eb39SLuis R. Rodriguez { 1744fe33eb39SLuis R. Rodriguez struct regulatory_request *request; 1745fe33eb39SLuis R. Rodriguez 1746fe33eb39SLuis R. Rodriguez BUG_ON(!alpha2); 1747fe33eb39SLuis R. Rodriguez BUG_ON(!wiphy); 1748fe33eb39SLuis R. Rodriguez 1749fe33eb39SLuis R. Rodriguez request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL); 1750fe33eb39SLuis R. Rodriguez if (!request) 1751fe33eb39SLuis R. Rodriguez return -ENOMEM; 1752fe33eb39SLuis R. Rodriguez 1753fe33eb39SLuis R. Rodriguez request->wiphy_idx = get_wiphy_idx(wiphy); 1754fe33eb39SLuis R. Rodriguez 1755fe33eb39SLuis R. Rodriguez /* Must have registered wiphy first */ 1756fe33eb39SLuis R. Rodriguez BUG_ON(!wiphy_idx_valid(request->wiphy_idx)); 1757fe33eb39SLuis R. Rodriguez 1758fe33eb39SLuis R. Rodriguez request->alpha2[0] = alpha2[0]; 1759fe33eb39SLuis R. Rodriguez request->alpha2[1] = alpha2[1]; 17607db90f4aSLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_DRIVER; 1761fe33eb39SLuis R. Rodriguez 1762fe33eb39SLuis R. Rodriguez queue_regulatory_request(request); 1763fe33eb39SLuis R. Rodriguez 1764fe33eb39SLuis R. Rodriguez return 0; 1765b2e1b302SLuis R. Rodriguez } 1766b2e1b302SLuis R. Rodriguez EXPORT_SYMBOL(regulatory_hint); 1767b2e1b302SLuis R. Rodriguez 17684b44c8bcSLuis R. Rodriguez /* 17694b44c8bcSLuis R. Rodriguez * We hold wdev_lock() here so we cannot hold cfg80211_mutex() and 17704b44c8bcSLuis R. Rodriguez * therefore cannot iterate over the rdev list here. 17714b44c8bcSLuis R. Rodriguez */ 17723f2355cbSLuis R. Rodriguez void regulatory_hint_11d(struct wiphy *wiphy, 177384920e3eSLuis R. Rodriguez enum ieee80211_band band, 17743f2355cbSLuis R. Rodriguez u8 *country_ie, 17753f2355cbSLuis R. Rodriguez u8 country_ie_len) 17763f2355cbSLuis R. Rodriguez { 17773f2355cbSLuis R. Rodriguez char alpha2[2]; 17783f2355cbSLuis R. Rodriguez enum environment_cap env = ENVIRON_ANY; 1779fe33eb39SLuis R. Rodriguez struct regulatory_request *request; 17803f2355cbSLuis R. Rodriguez 1781abc7381bSLuis R. Rodriguez mutex_lock(®_mutex); 17823f2355cbSLuis R. Rodriguez 17839828b017SLuis R. Rodriguez if (unlikely(!last_request)) 17849828b017SLuis R. Rodriguez goto out; 1785d335fe63SLuis R. Rodriguez 17863f2355cbSLuis R. Rodriguez /* IE len must be evenly divisible by 2 */ 17873f2355cbSLuis R. Rodriguez if (country_ie_len & 0x01) 17883f2355cbSLuis R. Rodriguez goto out; 17893f2355cbSLuis R. Rodriguez 17903f2355cbSLuis R. Rodriguez if (country_ie_len < IEEE80211_COUNTRY_IE_MIN_LEN) 17913f2355cbSLuis R. Rodriguez goto out; 17923f2355cbSLuis R. Rodriguez 17933f2355cbSLuis R. Rodriguez alpha2[0] = country_ie[0]; 17943f2355cbSLuis R. Rodriguez alpha2[1] = country_ie[1]; 17953f2355cbSLuis R. Rodriguez 17963f2355cbSLuis R. Rodriguez if (country_ie[2] == 'I') 17973f2355cbSLuis R. Rodriguez env = ENVIRON_INDOOR; 17983f2355cbSLuis R. Rodriguez else if (country_ie[2] == 'O') 17993f2355cbSLuis R. Rodriguez env = ENVIRON_OUTDOOR; 18003f2355cbSLuis R. Rodriguez 1801fb1fc7adSLuis R. Rodriguez /* 18028b19e6caSLuis R. Rodriguez * We will run this only upon a successful connection on cfg80211. 18034b44c8bcSLuis R. Rodriguez * We leave conflict resolution to the workqueue, where can hold 18044b44c8bcSLuis R. Rodriguez * cfg80211_mutex. 1805fb1fc7adSLuis R. Rodriguez */ 1806cc0b6fe8SLuis R. Rodriguez if (likely(last_request->initiator == 1807cc0b6fe8SLuis R. Rodriguez NL80211_REGDOM_SET_BY_COUNTRY_IE && 18084b44c8bcSLuis R. Rodriguez wiphy_idx_valid(last_request->wiphy_idx))) 18093f2355cbSLuis R. Rodriguez goto out; 18103f2355cbSLuis R. Rodriguez 1811fe33eb39SLuis R. Rodriguez request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL); 1812fe33eb39SLuis R. Rodriguez if (!request) 1813f9f9b6e3SDan Carpenter goto out; 1814fe33eb39SLuis R. Rodriguez 1815fe33eb39SLuis R. Rodriguez request->wiphy_idx = get_wiphy_idx(wiphy); 18164f366c5dSJohn W. Linville request->alpha2[0] = alpha2[0]; 18174f366c5dSJohn W. Linville request->alpha2[1] = alpha2[1]; 18187db90f4aSLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_COUNTRY_IE; 1819fe33eb39SLuis R. Rodriguez request->country_ie_env = env; 18203f2355cbSLuis R. Rodriguez 1821abc7381bSLuis R. Rodriguez mutex_unlock(®_mutex); 1822fe33eb39SLuis R. Rodriguez 1823fe33eb39SLuis R. Rodriguez queue_regulatory_request(request); 1824fe33eb39SLuis R. Rodriguez 1825fe33eb39SLuis R. Rodriguez return; 18260441d6ffSLuis R. Rodriguez 18273f2355cbSLuis R. Rodriguez out: 1828abc7381bSLuis R. Rodriguez mutex_unlock(®_mutex); 18293f2355cbSLuis R. Rodriguez } 1830b2e1b302SLuis R. Rodriguez 183109d989d1SLuis R. Rodriguez static void restore_alpha2(char *alpha2, bool reset_user) 183209d989d1SLuis R. Rodriguez { 183309d989d1SLuis R. Rodriguez /* indicates there is no alpha2 to consider for restoration */ 183409d989d1SLuis R. Rodriguez alpha2[0] = '9'; 183509d989d1SLuis R. Rodriguez alpha2[1] = '7'; 183609d989d1SLuis R. Rodriguez 183709d989d1SLuis R. Rodriguez /* The user setting has precedence over the module parameter */ 183809d989d1SLuis R. Rodriguez if (is_user_regdom_saved()) { 183909d989d1SLuis R. Rodriguez /* Unless we're asked to ignore it and reset it */ 184009d989d1SLuis R. Rodriguez if (reset_user) { 1841d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Restoring regulatory settings " 184209d989d1SLuis R. Rodriguez "including user preference\n"); 184309d989d1SLuis R. Rodriguez user_alpha2[0] = '9'; 184409d989d1SLuis R. Rodriguez user_alpha2[1] = '7'; 184509d989d1SLuis R. Rodriguez 184609d989d1SLuis R. Rodriguez /* 184709d989d1SLuis R. Rodriguez * If we're ignoring user settings, we still need to 184809d989d1SLuis R. Rodriguez * check the module parameter to ensure we put things 184909d989d1SLuis R. Rodriguez * back as they were for a full restore. 185009d989d1SLuis R. Rodriguez */ 185109d989d1SLuis R. Rodriguez if (!is_world_regdom(ieee80211_regdom)) { 1852d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Keeping preference on " 185309d989d1SLuis R. Rodriguez "module parameter ieee80211_regdom: %c%c\n", 185409d989d1SLuis R. Rodriguez ieee80211_regdom[0], 185509d989d1SLuis R. Rodriguez ieee80211_regdom[1]); 185609d989d1SLuis R. Rodriguez alpha2[0] = ieee80211_regdom[0]; 185709d989d1SLuis R. Rodriguez alpha2[1] = ieee80211_regdom[1]; 185809d989d1SLuis R. Rodriguez } 185909d989d1SLuis R. Rodriguez } else { 1860d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Restoring regulatory settings " 186109d989d1SLuis R. Rodriguez "while preserving user preference for: %c%c\n", 186209d989d1SLuis R. Rodriguez user_alpha2[0], 186309d989d1SLuis R. Rodriguez user_alpha2[1]); 186409d989d1SLuis R. Rodriguez alpha2[0] = user_alpha2[0]; 186509d989d1SLuis R. Rodriguez alpha2[1] = user_alpha2[1]; 186609d989d1SLuis R. Rodriguez } 186709d989d1SLuis R. Rodriguez } else if (!is_world_regdom(ieee80211_regdom)) { 1868d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Keeping preference on " 186909d989d1SLuis R. Rodriguez "module parameter ieee80211_regdom: %c%c\n", 187009d989d1SLuis R. Rodriguez ieee80211_regdom[0], 187109d989d1SLuis R. Rodriguez ieee80211_regdom[1]); 187209d989d1SLuis R. Rodriguez alpha2[0] = ieee80211_regdom[0]; 187309d989d1SLuis R. Rodriguez alpha2[1] = ieee80211_regdom[1]; 187409d989d1SLuis R. Rodriguez } else 1875d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Restoring regulatory settings\n"); 187609d989d1SLuis R. Rodriguez } 187709d989d1SLuis R. Rodriguez 18785ce543d1SRajkumar Manoharan static void restore_custom_reg_settings(struct wiphy *wiphy) 18795ce543d1SRajkumar Manoharan { 18805ce543d1SRajkumar Manoharan struct ieee80211_supported_band *sband; 18815ce543d1SRajkumar Manoharan enum ieee80211_band band; 18825ce543d1SRajkumar Manoharan struct ieee80211_channel *chan; 18835ce543d1SRajkumar Manoharan int i; 18845ce543d1SRajkumar Manoharan 18855ce543d1SRajkumar Manoharan for (band = 0; band < IEEE80211_NUM_BANDS; band++) { 18865ce543d1SRajkumar Manoharan sband = wiphy->bands[band]; 18875ce543d1SRajkumar Manoharan if (!sband) 18885ce543d1SRajkumar Manoharan continue; 18895ce543d1SRajkumar Manoharan for (i = 0; i < sband->n_channels; i++) { 18905ce543d1SRajkumar Manoharan chan = &sband->channels[i]; 18915ce543d1SRajkumar Manoharan chan->flags = chan->orig_flags; 18925ce543d1SRajkumar Manoharan chan->max_antenna_gain = chan->orig_mag; 18935ce543d1SRajkumar Manoharan chan->max_power = chan->orig_mpwr; 18945ce543d1SRajkumar Manoharan } 18955ce543d1SRajkumar Manoharan } 18965ce543d1SRajkumar Manoharan } 18975ce543d1SRajkumar Manoharan 189809d989d1SLuis R. Rodriguez /* 189909d989d1SLuis R. Rodriguez * Restoring regulatory settings involves ingoring any 190009d989d1SLuis R. Rodriguez * possibly stale country IE information and user regulatory 190109d989d1SLuis R. Rodriguez * settings if so desired, this includes any beacon hints 190209d989d1SLuis R. Rodriguez * learned as we could have traveled outside to another country 190309d989d1SLuis R. Rodriguez * after disconnection. To restore regulatory settings we do 190409d989d1SLuis R. Rodriguez * exactly what we did at bootup: 190509d989d1SLuis R. Rodriguez * 190609d989d1SLuis R. Rodriguez * - send a core regulatory hint 190709d989d1SLuis R. Rodriguez * - send a user regulatory hint if applicable 190809d989d1SLuis R. Rodriguez * 190909d989d1SLuis R. Rodriguez * Device drivers that send a regulatory hint for a specific country 191009d989d1SLuis R. Rodriguez * keep their own regulatory domain on wiphy->regd so that does does 191109d989d1SLuis R. Rodriguez * not need to be remembered. 191209d989d1SLuis R. Rodriguez */ 191309d989d1SLuis R. Rodriguez static void restore_regulatory_settings(bool reset_user) 191409d989d1SLuis R. Rodriguez { 191509d989d1SLuis R. Rodriguez char alpha2[2]; 1916cee0bec5SDmitry Shmidt char world_alpha2[2]; 191709d989d1SLuis R. Rodriguez struct reg_beacon *reg_beacon, *btmp; 191814609555SLuis R. Rodriguez struct regulatory_request *reg_request, *tmp; 191914609555SLuis R. Rodriguez LIST_HEAD(tmp_reg_req_list); 19205ce543d1SRajkumar Manoharan struct cfg80211_registered_device *rdev; 192109d989d1SLuis R. Rodriguez 192209d989d1SLuis R. Rodriguez mutex_lock(&cfg80211_mutex); 192309d989d1SLuis R. Rodriguez mutex_lock(®_mutex); 192409d989d1SLuis R. Rodriguez 1925a042994dSLuis R. Rodriguez reset_regdomains(true); 192609d989d1SLuis R. Rodriguez restore_alpha2(alpha2, reset_user); 192709d989d1SLuis R. Rodriguez 192814609555SLuis R. Rodriguez /* 192914609555SLuis R. Rodriguez * If there's any pending requests we simply 193014609555SLuis R. Rodriguez * stash them to a temporary pending queue and 193114609555SLuis R. Rodriguez * add then after we've restored regulatory 193214609555SLuis R. Rodriguez * settings. 193314609555SLuis R. Rodriguez */ 193414609555SLuis R. Rodriguez spin_lock(®_requests_lock); 193514609555SLuis R. Rodriguez if (!list_empty(®_requests_list)) { 193614609555SLuis R. Rodriguez list_for_each_entry_safe(reg_request, tmp, 193714609555SLuis R. Rodriguez ®_requests_list, list) { 193814609555SLuis R. Rodriguez if (reg_request->initiator != 193914609555SLuis R. Rodriguez NL80211_REGDOM_SET_BY_USER) 194014609555SLuis R. Rodriguez continue; 194114609555SLuis R. Rodriguez list_del(®_request->list); 194214609555SLuis R. Rodriguez list_add_tail(®_request->list, &tmp_reg_req_list); 194314609555SLuis R. Rodriguez } 194414609555SLuis R. Rodriguez } 194514609555SLuis R. Rodriguez spin_unlock(®_requests_lock); 194614609555SLuis R. Rodriguez 194709d989d1SLuis R. Rodriguez /* Clear beacon hints */ 194809d989d1SLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 194909d989d1SLuis R. Rodriguez if (!list_empty(®_pending_beacons)) { 195009d989d1SLuis R. Rodriguez list_for_each_entry_safe(reg_beacon, btmp, 195109d989d1SLuis R. Rodriguez ®_pending_beacons, list) { 195209d989d1SLuis R. Rodriguez list_del(®_beacon->list); 195309d989d1SLuis R. Rodriguez kfree(reg_beacon); 195409d989d1SLuis R. Rodriguez } 195509d989d1SLuis R. Rodriguez } 195609d989d1SLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 195709d989d1SLuis R. Rodriguez 195809d989d1SLuis R. Rodriguez if (!list_empty(®_beacon_list)) { 195909d989d1SLuis R. Rodriguez list_for_each_entry_safe(reg_beacon, btmp, 196009d989d1SLuis R. Rodriguez ®_beacon_list, list) { 196109d989d1SLuis R. Rodriguez list_del(®_beacon->list); 196209d989d1SLuis R. Rodriguez kfree(reg_beacon); 196309d989d1SLuis R. Rodriguez } 196409d989d1SLuis R. Rodriguez } 196509d989d1SLuis R. Rodriguez 196609d989d1SLuis R. Rodriguez /* First restore to the basic regulatory settings */ 196709d989d1SLuis R. Rodriguez cfg80211_regdomain = cfg80211_world_regdom; 1968cee0bec5SDmitry Shmidt world_alpha2[0] = cfg80211_regdomain->alpha2[0]; 1969cee0bec5SDmitry Shmidt world_alpha2[1] = cfg80211_regdomain->alpha2[1]; 197009d989d1SLuis R. Rodriguez 19715ce543d1SRajkumar Manoharan list_for_each_entry(rdev, &cfg80211_rdev_list, list) { 19725ce543d1SRajkumar Manoharan if (rdev->wiphy.flags & WIPHY_FLAG_CUSTOM_REGULATORY) 19735ce543d1SRajkumar Manoharan restore_custom_reg_settings(&rdev->wiphy); 19745ce543d1SRajkumar Manoharan } 19755ce543d1SRajkumar Manoharan 197609d989d1SLuis R. Rodriguez mutex_unlock(®_mutex); 197709d989d1SLuis R. Rodriguez mutex_unlock(&cfg80211_mutex); 197809d989d1SLuis R. Rodriguez 1979cee0bec5SDmitry Shmidt regulatory_hint_core(world_alpha2); 198009d989d1SLuis R. Rodriguez 198109d989d1SLuis R. Rodriguez /* 198209d989d1SLuis R. Rodriguez * This restores the ieee80211_regdom module parameter 198309d989d1SLuis R. Rodriguez * preference or the last user requested regulatory 198409d989d1SLuis R. Rodriguez * settings, user regulatory settings takes precedence. 198509d989d1SLuis R. Rodriguez */ 198609d989d1SLuis R. Rodriguez if (is_an_alpha2(alpha2)) 198757b5ce07SLuis R. Rodriguez regulatory_hint_user(user_alpha2, NL80211_USER_REG_HINT_USER); 198809d989d1SLuis R. Rodriguez 198914609555SLuis R. Rodriguez if (list_empty(&tmp_reg_req_list)) 199014609555SLuis R. Rodriguez return; 199114609555SLuis R. Rodriguez 199214609555SLuis R. Rodriguez mutex_lock(&cfg80211_mutex); 199314609555SLuis R. Rodriguez mutex_lock(®_mutex); 199414609555SLuis R. Rodriguez 199514609555SLuis R. Rodriguez spin_lock(®_requests_lock); 199614609555SLuis R. Rodriguez list_for_each_entry_safe(reg_request, tmp, &tmp_reg_req_list, list) { 199714609555SLuis R. Rodriguez REG_DBG_PRINT("Adding request for country %c%c back " 199814609555SLuis R. Rodriguez "into the queue\n", 199914609555SLuis R. Rodriguez reg_request->alpha2[0], 200014609555SLuis R. Rodriguez reg_request->alpha2[1]); 200114609555SLuis R. Rodriguez list_del(®_request->list); 200214609555SLuis R. Rodriguez list_add_tail(®_request->list, ®_requests_list); 200314609555SLuis R. Rodriguez } 200414609555SLuis R. Rodriguez spin_unlock(®_requests_lock); 200514609555SLuis R. Rodriguez 200614609555SLuis R. Rodriguez mutex_unlock(®_mutex); 200714609555SLuis R. Rodriguez mutex_unlock(&cfg80211_mutex); 200814609555SLuis R. Rodriguez 200914609555SLuis R. Rodriguez REG_DBG_PRINT("Kicking the queue\n"); 201014609555SLuis R. Rodriguez 201114609555SLuis R. Rodriguez schedule_work(®_work); 201214609555SLuis R. Rodriguez } 201309d989d1SLuis R. Rodriguez 201409d989d1SLuis R. Rodriguez void regulatory_hint_disconnect(void) 201509d989d1SLuis R. Rodriguez { 2016d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("All devices are disconnected, going to " 201709d989d1SLuis R. Rodriguez "restore regulatory settings\n"); 201809d989d1SLuis R. Rodriguez restore_regulatory_settings(false); 201909d989d1SLuis R. Rodriguez } 202009d989d1SLuis R. Rodriguez 2021e38f8a7aSLuis R. Rodriguez static bool freq_is_chan_12_13_14(u16 freq) 2022e38f8a7aSLuis R. Rodriguez { 202359eb21a6SBruno Randolf if (freq == ieee80211_channel_to_frequency(12, IEEE80211_BAND_2GHZ) || 202459eb21a6SBruno Randolf freq == ieee80211_channel_to_frequency(13, IEEE80211_BAND_2GHZ) || 202559eb21a6SBruno Randolf freq == ieee80211_channel_to_frequency(14, IEEE80211_BAND_2GHZ)) 2026e38f8a7aSLuis R. Rodriguez return true; 2027e38f8a7aSLuis R. Rodriguez return false; 2028e38f8a7aSLuis R. Rodriguez } 2029e38f8a7aSLuis R. Rodriguez 2030e38f8a7aSLuis R. Rodriguez int regulatory_hint_found_beacon(struct wiphy *wiphy, 2031e38f8a7aSLuis R. Rodriguez struct ieee80211_channel *beacon_chan, 2032e38f8a7aSLuis R. Rodriguez gfp_t gfp) 2033e38f8a7aSLuis R. Rodriguez { 2034e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon; 2035e38f8a7aSLuis R. Rodriguez 2036e38f8a7aSLuis R. Rodriguez if (likely((beacon_chan->beacon_found || 2037e38f8a7aSLuis R. Rodriguez (beacon_chan->flags & IEEE80211_CHAN_RADAR) || 2038e38f8a7aSLuis R. Rodriguez (beacon_chan->band == IEEE80211_BAND_2GHZ && 2039e38f8a7aSLuis R. Rodriguez !freq_is_chan_12_13_14(beacon_chan->center_freq))))) 2040e38f8a7aSLuis R. Rodriguez return 0; 2041e38f8a7aSLuis R. Rodriguez 2042e38f8a7aSLuis R. Rodriguez reg_beacon = kzalloc(sizeof(struct reg_beacon), gfp); 2043e38f8a7aSLuis R. Rodriguez if (!reg_beacon) 2044e38f8a7aSLuis R. Rodriguez return -ENOMEM; 2045e38f8a7aSLuis R. Rodriguez 2046d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Found new beacon on " 2047e38f8a7aSLuis R. Rodriguez "frequency: %d MHz (Ch %d) on %s\n", 2048e38f8a7aSLuis R. Rodriguez beacon_chan->center_freq, 2049e38f8a7aSLuis R. Rodriguez ieee80211_frequency_to_channel(beacon_chan->center_freq), 2050e38f8a7aSLuis R. Rodriguez wiphy_name(wiphy)); 20514113f751SLuis R. Rodriguez 2052e38f8a7aSLuis R. Rodriguez memcpy(®_beacon->chan, beacon_chan, 2053e38f8a7aSLuis R. Rodriguez sizeof(struct ieee80211_channel)); 2054e38f8a7aSLuis R. Rodriguez 2055e38f8a7aSLuis R. Rodriguez 2056e38f8a7aSLuis R. Rodriguez /* 2057e38f8a7aSLuis R. Rodriguez * Since we can be called from BH or and non-BH context 2058e38f8a7aSLuis R. Rodriguez * we must use spin_lock_bh() 2059e38f8a7aSLuis R. Rodriguez */ 2060e38f8a7aSLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 2061e38f8a7aSLuis R. Rodriguez list_add_tail(®_beacon->list, ®_pending_beacons); 2062e38f8a7aSLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 2063e38f8a7aSLuis R. Rodriguez 2064e38f8a7aSLuis R. Rodriguez schedule_work(®_work); 2065e38f8a7aSLuis R. Rodriguez 2066e38f8a7aSLuis R. Rodriguez return 0; 2067e38f8a7aSLuis R. Rodriguez } 2068e38f8a7aSLuis R. Rodriguez 2069a3d2eaf0SJohannes Berg static void print_rd_rules(const struct ieee80211_regdomain *rd) 2070b2e1b302SLuis R. Rodriguez { 2071b2e1b302SLuis R. Rodriguez unsigned int i; 2072a3d2eaf0SJohannes Berg const struct ieee80211_reg_rule *reg_rule = NULL; 2073a3d2eaf0SJohannes Berg const struct ieee80211_freq_range *freq_range = NULL; 2074a3d2eaf0SJohannes Berg const struct ieee80211_power_rule *power_rule = NULL; 2075b2e1b302SLuis R. Rodriguez 2076e9c0268fSJoe Perches pr_info(" (start_freq - end_freq @ bandwidth), (max_antenna_gain, max_eirp)\n"); 2077b2e1b302SLuis R. Rodriguez 2078b2e1b302SLuis R. Rodriguez for (i = 0; i < rd->n_reg_rules; i++) { 2079b2e1b302SLuis R. Rodriguez reg_rule = &rd->reg_rules[i]; 2080b2e1b302SLuis R. Rodriguez freq_range = ®_rule->freq_range; 2081b2e1b302SLuis R. Rodriguez power_rule = ®_rule->power_rule; 2082b2e1b302SLuis R. Rodriguez 2083fb1fc7adSLuis R. Rodriguez /* 2084fb1fc7adSLuis R. Rodriguez * There may not be documentation for max antenna gain 2085fb1fc7adSLuis R. Rodriguez * in certain regions 2086fb1fc7adSLuis R. Rodriguez */ 2087b2e1b302SLuis R. Rodriguez if (power_rule->max_antenna_gain) 2088e9c0268fSJoe Perches pr_info(" (%d KHz - %d KHz @ %d KHz), (%d mBi, %d mBm)\n", 2089b2e1b302SLuis R. Rodriguez freq_range->start_freq_khz, 2090b2e1b302SLuis R. Rodriguez freq_range->end_freq_khz, 2091b2e1b302SLuis R. Rodriguez freq_range->max_bandwidth_khz, 2092b2e1b302SLuis R. Rodriguez power_rule->max_antenna_gain, 2093b2e1b302SLuis R. Rodriguez power_rule->max_eirp); 2094b2e1b302SLuis R. Rodriguez else 2095e9c0268fSJoe Perches pr_info(" (%d KHz - %d KHz @ %d KHz), (N/A, %d mBm)\n", 2096b2e1b302SLuis R. Rodriguez freq_range->start_freq_khz, 2097b2e1b302SLuis R. Rodriguez freq_range->end_freq_khz, 2098b2e1b302SLuis R. Rodriguez freq_range->max_bandwidth_khz, 2099b2e1b302SLuis R. Rodriguez power_rule->max_eirp); 2100b2e1b302SLuis R. Rodriguez } 2101b2e1b302SLuis R. Rodriguez } 2102b2e1b302SLuis R. Rodriguez 21038b60b078SLuis R. Rodriguez bool reg_supported_dfs_region(u8 dfs_region) 21048b60b078SLuis R. Rodriguez { 21058b60b078SLuis R. Rodriguez switch (dfs_region) { 21068b60b078SLuis R. Rodriguez case NL80211_DFS_UNSET: 21078b60b078SLuis R. Rodriguez case NL80211_DFS_FCC: 21088b60b078SLuis R. Rodriguez case NL80211_DFS_ETSI: 21098b60b078SLuis R. Rodriguez case NL80211_DFS_JP: 21108b60b078SLuis R. Rodriguez return true; 21118b60b078SLuis R. Rodriguez default: 21128b60b078SLuis R. Rodriguez REG_DBG_PRINT("Ignoring uknown DFS master region: %d\n", 21138b60b078SLuis R. Rodriguez dfs_region); 21148b60b078SLuis R. Rodriguez return false; 21158b60b078SLuis R. Rodriguez } 21168b60b078SLuis R. Rodriguez } 21178b60b078SLuis R. Rodriguez 21188b60b078SLuis R. Rodriguez static void print_dfs_region(u8 dfs_region) 21198b60b078SLuis R. Rodriguez { 21208b60b078SLuis R. Rodriguez if (!dfs_region) 21218b60b078SLuis R. Rodriguez return; 21228b60b078SLuis R. Rodriguez 21238b60b078SLuis R. Rodriguez switch (dfs_region) { 21248b60b078SLuis R. Rodriguez case NL80211_DFS_FCC: 21258b60b078SLuis R. Rodriguez pr_info(" DFS Master region FCC"); 21268b60b078SLuis R. Rodriguez break; 21278b60b078SLuis R. Rodriguez case NL80211_DFS_ETSI: 21288b60b078SLuis R. Rodriguez pr_info(" DFS Master region ETSI"); 21298b60b078SLuis R. Rodriguez break; 21308b60b078SLuis R. Rodriguez case NL80211_DFS_JP: 21318b60b078SLuis R. Rodriguez pr_info(" DFS Master region JP"); 21328b60b078SLuis R. Rodriguez break; 21338b60b078SLuis R. Rodriguez default: 21348b60b078SLuis R. Rodriguez pr_info(" DFS Master region Uknown"); 21358b60b078SLuis R. Rodriguez break; 21368b60b078SLuis R. Rodriguez } 21378b60b078SLuis R. Rodriguez } 21388b60b078SLuis R. Rodriguez 2139a3d2eaf0SJohannes Berg static void print_regdomain(const struct ieee80211_regdomain *rd) 2140b2e1b302SLuis R. Rodriguez { 2141b2e1b302SLuis R. Rodriguez 21423f2355cbSLuis R. Rodriguez if (is_intersected_alpha2(rd->alpha2)) { 21433f2355cbSLuis R. Rodriguez 21447db90f4aSLuis R. Rodriguez if (last_request->initiator == 21457db90f4aSLuis R. Rodriguez NL80211_REGDOM_SET_BY_COUNTRY_IE) { 214679c97e97SJohannes Berg struct cfg80211_registered_device *rdev; 214779c97e97SJohannes Berg rdev = cfg80211_rdev_by_wiphy_idx( 2148806a9e39SLuis R. Rodriguez last_request->wiphy_idx); 214979c97e97SJohannes Berg if (rdev) { 2150e9c0268fSJoe Perches pr_info("Current regulatory domain updated by AP to: %c%c\n", 215179c97e97SJohannes Berg rdev->country_ie_alpha2[0], 215279c97e97SJohannes Berg rdev->country_ie_alpha2[1]); 21533f2355cbSLuis R. Rodriguez } else 2154e9c0268fSJoe Perches pr_info("Current regulatory domain intersected:\n"); 21553f2355cbSLuis R. Rodriguez } else 2156e9c0268fSJoe Perches pr_info("Current regulatory domain intersected:\n"); 21573f2355cbSLuis R. Rodriguez } else if (is_world_regdom(rd->alpha2)) 2158e9c0268fSJoe Perches pr_info("World regulatory domain updated:\n"); 2159b2e1b302SLuis R. Rodriguez else { 2160b2e1b302SLuis R. Rodriguez if (is_unknown_alpha2(rd->alpha2)) 2161e9c0268fSJoe Perches pr_info("Regulatory domain changed to driver built-in settings (unknown country)\n"); 216257b5ce07SLuis R. Rodriguez else { 216357b5ce07SLuis R. Rodriguez if (reg_request_cell_base(last_request)) 216457b5ce07SLuis R. Rodriguez pr_info("Regulatory domain changed " 216557b5ce07SLuis R. Rodriguez "to country: %c%c by Cell Station\n", 2166b2e1b302SLuis R. Rodriguez rd->alpha2[0], rd->alpha2[1]); 216757b5ce07SLuis R. Rodriguez else 216857b5ce07SLuis R. Rodriguez pr_info("Regulatory domain changed " 216957b5ce07SLuis R. Rodriguez "to country: %c%c\n", 217057b5ce07SLuis R. Rodriguez rd->alpha2[0], rd->alpha2[1]); 217157b5ce07SLuis R. Rodriguez } 2172b2e1b302SLuis R. Rodriguez } 21738b60b078SLuis R. Rodriguez print_dfs_region(rd->dfs_region); 2174b2e1b302SLuis R. Rodriguez print_rd_rules(rd); 2175b2e1b302SLuis R. Rodriguez } 2176b2e1b302SLuis R. Rodriguez 21772df78167SJohannes Berg static void print_regdomain_info(const struct ieee80211_regdomain *rd) 2178b2e1b302SLuis R. Rodriguez { 2179e9c0268fSJoe Perches pr_info("Regulatory domain: %c%c\n", rd->alpha2[0], rd->alpha2[1]); 2180b2e1b302SLuis R. Rodriguez print_rd_rules(rd); 2181b2e1b302SLuis R. Rodriguez } 2182b2e1b302SLuis R. Rodriguez 2183d2372b31SJohannes Berg /* Takes ownership of rd only if it doesn't fail */ 2184a3d2eaf0SJohannes Berg static int __set_regdom(const struct ieee80211_regdomain *rd) 2185b2e1b302SLuis R. Rodriguez { 21869c96477dSLuis R. Rodriguez const struct ieee80211_regdomain *intersected_rd = NULL; 218779c97e97SJohannes Berg struct cfg80211_registered_device *rdev = NULL; 2188806a9e39SLuis R. Rodriguez struct wiphy *request_wiphy; 2189b2e1b302SLuis R. Rodriguez /* Some basic sanity checks first */ 2190b2e1b302SLuis R. Rodriguez 2191b2e1b302SLuis R. Rodriguez if (is_world_regdom(rd->alpha2)) { 2192f6037d09SJohannes Berg if (WARN_ON(!reg_is_valid_request(rd->alpha2))) 2193b2e1b302SLuis R. Rodriguez return -EINVAL; 2194b2e1b302SLuis R. Rodriguez update_world_regdomain(rd); 2195b2e1b302SLuis R. Rodriguez return 0; 2196b2e1b302SLuis R. Rodriguez } 2197b2e1b302SLuis R. Rodriguez 2198b2e1b302SLuis R. Rodriguez if (!is_alpha2_set(rd->alpha2) && !is_an_alpha2(rd->alpha2) && 2199b2e1b302SLuis R. Rodriguez !is_unknown_alpha2(rd->alpha2)) 2200b2e1b302SLuis R. Rodriguez return -EINVAL; 2201b2e1b302SLuis R. Rodriguez 2202f6037d09SJohannes Berg if (!last_request) 2203b2e1b302SLuis R. Rodriguez return -EINVAL; 2204b2e1b302SLuis R. Rodriguez 2205fb1fc7adSLuis R. Rodriguez /* 2206fb1fc7adSLuis R. Rodriguez * Lets only bother proceeding on the same alpha2 if the current 22073f2355cbSLuis R. Rodriguez * rd is non static (it means CRDA was present and was used last) 2208fb1fc7adSLuis R. Rodriguez * and the pending request came in from a country IE 2209fb1fc7adSLuis R. Rodriguez */ 22107db90f4aSLuis R. Rodriguez if (last_request->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE) { 2211fb1fc7adSLuis R. Rodriguez /* 2212fb1fc7adSLuis R. Rodriguez * If someone else asked us to change the rd lets only bother 2213fb1fc7adSLuis R. Rodriguez * checking if the alpha2 changes if CRDA was already called 2214fb1fc7adSLuis R. Rodriguez */ 2215baeb66feSJohn W. Linville if (!regdom_changes(rd->alpha2)) 221695908535SKalle Valo return -EALREADY; 22173f2355cbSLuis R. Rodriguez } 22183f2355cbSLuis R. Rodriguez 2219fb1fc7adSLuis R. Rodriguez /* 2220fb1fc7adSLuis R. Rodriguez * Now lets set the regulatory domain, update all driver channels 2221b2e1b302SLuis R. Rodriguez * and finally inform them of what we have done, in case they want 2222b2e1b302SLuis R. Rodriguez * to review or adjust their own settings based on their own 2223fb1fc7adSLuis R. Rodriguez * internal EEPROM data 2224fb1fc7adSLuis R. Rodriguez */ 2225b2e1b302SLuis R. Rodriguez 2226f6037d09SJohannes Berg if (WARN_ON(!reg_is_valid_request(rd->alpha2))) 2227b2e1b302SLuis R. Rodriguez return -EINVAL; 2228b2e1b302SLuis R. Rodriguez 2229b2e1b302SLuis R. Rodriguez if (!is_valid_rd(rd)) { 2230e9c0268fSJoe Perches pr_err("Invalid regulatory domain detected:\n"); 2231b2e1b302SLuis R. Rodriguez print_regdomain_info(rd); 2232b2e1b302SLuis R. Rodriguez return -EINVAL; 2233b2e1b302SLuis R. Rodriguez } 2234b2e1b302SLuis R. Rodriguez 2235806a9e39SLuis R. Rodriguez request_wiphy = wiphy_idx_to_wiphy(last_request->wiphy_idx); 22360bac71afSLuis R. Rodriguez if (!request_wiphy && 22370bac71afSLuis R. Rodriguez (last_request->initiator == NL80211_REGDOM_SET_BY_DRIVER || 22380bac71afSLuis R. Rodriguez last_request->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE)) { 22390bac71afSLuis R. Rodriguez schedule_delayed_work(®_timeout, 0); 2240de3584bdSJohannes Berg return -ENODEV; 2241de3584bdSJohannes Berg } 2242806a9e39SLuis R. Rodriguez 2243b8295acdSLuis R. Rodriguez if (!last_request->intersect) { 22443e0c3ff3SLuis R. Rodriguez int r; 22453e0c3ff3SLuis R. Rodriguez 22467db90f4aSLuis R. Rodriguez if (last_request->initiator != NL80211_REGDOM_SET_BY_DRIVER) { 2247a042994dSLuis R. Rodriguez reset_regdomains(false); 22483e0c3ff3SLuis R. Rodriguez cfg80211_regdomain = rd; 22493e0c3ff3SLuis R. Rodriguez return 0; 22503e0c3ff3SLuis R. Rodriguez } 22513e0c3ff3SLuis R. Rodriguez 2252fb1fc7adSLuis R. Rodriguez /* 2253fb1fc7adSLuis R. Rodriguez * For a driver hint, lets copy the regulatory domain the 2254fb1fc7adSLuis R. Rodriguez * driver wanted to the wiphy to deal with conflicts 2255fb1fc7adSLuis R. Rodriguez */ 22563e0c3ff3SLuis R. Rodriguez 2257558f6d32SLuis R. Rodriguez /* 2258558f6d32SLuis R. Rodriguez * Userspace could have sent two replies with only 2259558f6d32SLuis R. Rodriguez * one kernel request. 2260558f6d32SLuis R. Rodriguez */ 2261558f6d32SLuis R. Rodriguez if (request_wiphy->regd) 2262558f6d32SLuis R. Rodriguez return -EALREADY; 22633e0c3ff3SLuis R. Rodriguez 2264806a9e39SLuis R. Rodriguez r = reg_copy_regd(&request_wiphy->regd, rd); 22653e0c3ff3SLuis R. Rodriguez if (r) 22663e0c3ff3SLuis R. Rodriguez return r; 22673e0c3ff3SLuis R. Rodriguez 2268a042994dSLuis R. Rodriguez reset_regdomains(false); 2269b8295acdSLuis R. Rodriguez cfg80211_regdomain = rd; 2270b8295acdSLuis R. Rodriguez return 0; 2271b8295acdSLuis R. Rodriguez } 2272b8295acdSLuis R. Rodriguez 2273b8295acdSLuis R. Rodriguez /* Intersection requires a bit more work */ 2274b8295acdSLuis R. Rodriguez 22757db90f4aSLuis R. Rodriguez if (last_request->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE) { 2276b8295acdSLuis R. Rodriguez 22779c96477dSLuis R. Rodriguez intersected_rd = regdom_intersect(rd, cfg80211_regdomain); 22789c96477dSLuis R. Rodriguez if (!intersected_rd) 22799c96477dSLuis R. Rodriguez return -EINVAL; 2280b8295acdSLuis R. Rodriguez 2281fb1fc7adSLuis R. Rodriguez /* 2282fb1fc7adSLuis R. Rodriguez * We can trash what CRDA provided now. 22833e0c3ff3SLuis R. Rodriguez * However if a driver requested this specific regulatory 2284fb1fc7adSLuis R. Rodriguez * domain we keep it for its private use 2285fb1fc7adSLuis R. Rodriguez */ 22867db90f4aSLuis R. Rodriguez if (last_request->initiator == NL80211_REGDOM_SET_BY_DRIVER) 2287806a9e39SLuis R. Rodriguez request_wiphy->regd = rd; 22883e0c3ff3SLuis R. Rodriguez else 22899c96477dSLuis R. Rodriguez kfree(rd); 22903e0c3ff3SLuis R. Rodriguez 2291b8295acdSLuis R. Rodriguez rd = NULL; 2292b8295acdSLuis R. Rodriguez 2293a042994dSLuis R. Rodriguez reset_regdomains(false); 2294b8295acdSLuis R. Rodriguez cfg80211_regdomain = intersected_rd; 2295b8295acdSLuis R. Rodriguez 2296b8295acdSLuis R. Rodriguez return 0; 22979c96477dSLuis R. Rodriguez } 22989c96477dSLuis R. Rodriguez 22993f2355cbSLuis R. Rodriguez if (!intersected_rd) 23003f2355cbSLuis R. Rodriguez return -EINVAL; 23013f2355cbSLuis R. Rodriguez 230279c97e97SJohannes Berg rdev = wiphy_to_dev(request_wiphy); 23033f2355cbSLuis R. Rodriguez 230479c97e97SJohannes Berg rdev->country_ie_alpha2[0] = rd->alpha2[0]; 230579c97e97SJohannes Berg rdev->country_ie_alpha2[1] = rd->alpha2[1]; 230679c97e97SJohannes Berg rdev->env = last_request->country_ie_env; 23073f2355cbSLuis R. Rodriguez 23083f2355cbSLuis R. Rodriguez BUG_ON(intersected_rd == rd); 23093f2355cbSLuis R. Rodriguez 23103f2355cbSLuis R. Rodriguez kfree(rd); 23113f2355cbSLuis R. Rodriguez rd = NULL; 23123f2355cbSLuis R. Rodriguez 2313a042994dSLuis R. Rodriguez reset_regdomains(false); 23143f2355cbSLuis R. Rodriguez cfg80211_regdomain = intersected_rd; 2315b2e1b302SLuis R. Rodriguez 2316b2e1b302SLuis R. Rodriguez return 0; 2317b2e1b302SLuis R. Rodriguez } 2318b2e1b302SLuis R. Rodriguez 2319b2e1b302SLuis R. Rodriguez 2320fb1fc7adSLuis R. Rodriguez /* 2321fb1fc7adSLuis R. Rodriguez * Use this call to set the current regulatory domain. Conflicts with 2322b2e1b302SLuis R. Rodriguez * multiple drivers can be ironed out later. Caller must've already 2323fb1fc7adSLuis R. Rodriguez * kmalloc'd the rd structure. Caller must hold cfg80211_mutex 2324fb1fc7adSLuis R. Rodriguez */ 2325a3d2eaf0SJohannes Berg int set_regdom(const struct ieee80211_regdomain *rd) 2326b2e1b302SLuis R. Rodriguez { 2327b2e1b302SLuis R. Rodriguez int r; 2328b2e1b302SLuis R. Rodriguez 2329761cf7ecSLuis R. Rodriguez assert_cfg80211_lock(); 2330761cf7ecSLuis R. Rodriguez 2331abc7381bSLuis R. Rodriguez mutex_lock(®_mutex); 2332abc7381bSLuis R. Rodriguez 2333b2e1b302SLuis R. Rodriguez /* Note that this doesn't update the wiphys, this is done below */ 2334b2e1b302SLuis R. Rodriguez r = __set_regdom(rd); 2335d2372b31SJohannes Berg if (r) { 233695908535SKalle Valo if (r == -EALREADY) 233795908535SKalle Valo reg_set_request_processed(); 233895908535SKalle Valo 2339d2372b31SJohannes Berg kfree(rd); 2340abc7381bSLuis R. Rodriguez mutex_unlock(®_mutex); 2341b2e1b302SLuis R. Rodriguez return r; 2342d2372b31SJohannes Berg } 2343b2e1b302SLuis R. Rodriguez 2344b2e1b302SLuis R. Rodriguez /* This would make this whole thing pointless */ 2345a01ddafdSLuis R. Rodriguez if (!last_request->intersect) 2346b2e1b302SLuis R. Rodriguez BUG_ON(rd != cfg80211_regdomain); 2347b2e1b302SLuis R. Rodriguez 2348b2e1b302SLuis R. Rodriguez /* update all wiphys now with the new established regulatory domain */ 2349f6037d09SJohannes Berg update_all_wiphy_regulatory(last_request->initiator); 2350b2e1b302SLuis R. Rodriguez 2351a01ddafdSLuis R. Rodriguez print_regdomain(cfg80211_regdomain); 2352b2e1b302SLuis R. Rodriguez 235373d54c9eSLuis R. Rodriguez nl80211_send_reg_change_event(last_request); 235473d54c9eSLuis R. Rodriguez 2355b2e253cfSLuis R. Rodriguez reg_set_request_processed(); 2356b2e253cfSLuis R. Rodriguez 2357abc7381bSLuis R. Rodriguez mutex_unlock(®_mutex); 2358abc7381bSLuis R. Rodriguez 2359b2e1b302SLuis R. Rodriguez return r; 2360b2e1b302SLuis R. Rodriguez } 2361b2e1b302SLuis R. Rodriguez 23624d9d88d1SScott James Remnant #ifdef CONFIG_HOTPLUG 23634d9d88d1SScott James Remnant int reg_device_uevent(struct device *dev, struct kobj_uevent_env *env) 23644d9d88d1SScott James Remnant { 23654d9d88d1SScott James Remnant if (last_request && !last_request->processed) { 23664d9d88d1SScott James Remnant if (add_uevent_var(env, "COUNTRY=%c%c", 23674d9d88d1SScott James Remnant last_request->alpha2[0], 23684d9d88d1SScott James Remnant last_request->alpha2[1])) 23694d9d88d1SScott James Remnant return -ENOMEM; 23704d9d88d1SScott James Remnant } 23714d9d88d1SScott James Remnant 23724d9d88d1SScott James Remnant return 0; 23734d9d88d1SScott James Remnant } 23744d9d88d1SScott James Remnant #else 23754d9d88d1SScott James Remnant int reg_device_uevent(struct device *dev, struct kobj_uevent_env *env) 23764d9d88d1SScott James Remnant { 23774d9d88d1SScott James Remnant return -ENODEV; 23784d9d88d1SScott James Remnant } 23794d9d88d1SScott James Remnant #endif /* CONFIG_HOTPLUG */ 23804d9d88d1SScott James Remnant 238157b5ce07SLuis R. Rodriguez void wiphy_regulatory_register(struct wiphy *wiphy) 238257b5ce07SLuis R. Rodriguez { 238357b5ce07SLuis R. Rodriguez assert_cfg80211_lock(); 238457b5ce07SLuis R. Rodriguez 238557b5ce07SLuis R. Rodriguez mutex_lock(®_mutex); 238657b5ce07SLuis R. Rodriguez 238757b5ce07SLuis R. Rodriguez if (!reg_dev_ignore_cell_hint(wiphy)) 238857b5ce07SLuis R. Rodriguez reg_num_devs_support_basehint++; 238957b5ce07SLuis R. Rodriguez 239057b5ce07SLuis R. Rodriguez mutex_unlock(®_mutex); 2391f8a1c774SLuis R. Rodriguez 2392f8a1c774SLuis R. Rodriguez regulatory_update(wiphy, NL80211_REGDOM_SET_BY_CORE); 239357b5ce07SLuis R. Rodriguez } 239457b5ce07SLuis R. Rodriguez 2395a1794390SLuis R. Rodriguez /* Caller must hold cfg80211_mutex */ 2396bfead080SLuis R. Rodriguez void wiphy_regulatory_deregister(struct wiphy *wiphy) 23973f2355cbSLuis R. Rodriguez { 23980ad8acafSLuis R. Rodriguez struct wiphy *request_wiphy = NULL; 2399806a9e39SLuis R. Rodriguez 2400761cf7ecSLuis R. Rodriguez assert_cfg80211_lock(); 2401761cf7ecSLuis R. Rodriguez 2402abc7381bSLuis R. Rodriguez mutex_lock(®_mutex); 2403abc7381bSLuis R. Rodriguez 240457b5ce07SLuis R. Rodriguez if (!reg_dev_ignore_cell_hint(wiphy)) 240557b5ce07SLuis R. Rodriguez reg_num_devs_support_basehint--; 240657b5ce07SLuis R. Rodriguez 24070ef9ccddSChris Wright kfree(wiphy->regd); 24080ef9ccddSChris Wright 24090ad8acafSLuis R. Rodriguez if (last_request) 2410806a9e39SLuis R. Rodriguez request_wiphy = wiphy_idx_to_wiphy(last_request->wiphy_idx); 2411806a9e39SLuis R. Rodriguez 24120ef9ccddSChris Wright if (!request_wiphy || request_wiphy != wiphy) 2413abc7381bSLuis R. Rodriguez goto out; 24140ef9ccddSChris Wright 2415806a9e39SLuis R. Rodriguez last_request->wiphy_idx = WIPHY_IDX_STALE; 24163f2355cbSLuis R. Rodriguez last_request->country_ie_env = ENVIRON_ANY; 2417abc7381bSLuis R. Rodriguez out: 2418abc7381bSLuis R. Rodriguez mutex_unlock(®_mutex); 24193f2355cbSLuis R. Rodriguez } 24203f2355cbSLuis R. Rodriguez 2421a90c7a31SLuis R. Rodriguez static void reg_timeout_work(struct work_struct *work) 2422a90c7a31SLuis R. Rodriguez { 2423a90c7a31SLuis R. Rodriguez REG_DBG_PRINT("Timeout while waiting for CRDA to reply, " 242412c5ffb5SJoe Perches "restoring regulatory settings\n"); 2425a90c7a31SLuis R. Rodriguez restore_regulatory_settings(true); 2426a90c7a31SLuis R. Rodriguez } 2427a90c7a31SLuis R. Rodriguez 24282fcc9f73SUwe Kleine-König int __init regulatory_init(void) 2429b2e1b302SLuis R. Rodriguez { 2430bcf4f99bSLuis R. Rodriguez int err = 0; 2431734366deSJohannes Berg 2432b2e1b302SLuis R. Rodriguez reg_pdev = platform_device_register_simple("regulatory", 0, NULL, 0); 2433b2e1b302SLuis R. Rodriguez if (IS_ERR(reg_pdev)) 2434b2e1b302SLuis R. Rodriguez return PTR_ERR(reg_pdev); 2435734366deSJohannes Berg 24364d9d88d1SScott James Remnant reg_pdev->dev.type = ®_device_type; 24374d9d88d1SScott James Remnant 2438fe33eb39SLuis R. Rodriguez spin_lock_init(®_requests_lock); 2439e38f8a7aSLuis R. Rodriguez spin_lock_init(®_pending_beacons_lock); 2440fe33eb39SLuis R. Rodriguez 244180007efeSLuis R. Rodriguez reg_regdb_size_check(); 244280007efeSLuis R. Rodriguez 2443a3d2eaf0SJohannes Berg cfg80211_regdomain = cfg80211_world_regdom; 2444734366deSJohannes Berg 244509d989d1SLuis R. Rodriguez user_alpha2[0] = '9'; 244609d989d1SLuis R. Rodriguez user_alpha2[1] = '7'; 244709d989d1SLuis R. Rodriguez 2448ae9e4b0dSLuis R. Rodriguez /* We always try to get an update for the static regdomain */ 2449ae9e4b0dSLuis R. Rodriguez err = regulatory_hint_core(cfg80211_regdomain->alpha2); 2450bcf4f99bSLuis R. Rodriguez if (err) { 2451bcf4f99bSLuis R. Rodriguez if (err == -ENOMEM) 2452bcf4f99bSLuis R. Rodriguez return err; 2453bcf4f99bSLuis R. Rodriguez /* 2454bcf4f99bSLuis R. Rodriguez * N.B. kobject_uevent_env() can fail mainly for when we're out 2455bcf4f99bSLuis R. Rodriguez * memory which is handled and propagated appropriately above 2456bcf4f99bSLuis R. Rodriguez * but it can also fail during a netlink_broadcast() or during 2457bcf4f99bSLuis R. Rodriguez * early boot for call_usermodehelper(). For now treat these 2458bcf4f99bSLuis R. Rodriguez * errors as non-fatal. 2459bcf4f99bSLuis R. Rodriguez */ 2460e9c0268fSJoe Perches pr_err("kobject_uevent_env() was unable to call CRDA during init\n"); 2461bcf4f99bSLuis R. Rodriguez #ifdef CONFIG_CFG80211_REG_DEBUG 2462bcf4f99bSLuis R. Rodriguez /* We want to find out exactly why when debugging */ 2463bcf4f99bSLuis R. Rodriguez WARN_ON(err); 2464bcf4f99bSLuis R. Rodriguez #endif 2465bcf4f99bSLuis R. Rodriguez } 2466734366deSJohannes Berg 2467ae9e4b0dSLuis R. Rodriguez /* 2468ae9e4b0dSLuis R. Rodriguez * Finally, if the user set the module parameter treat it 2469ae9e4b0dSLuis R. Rodriguez * as a user hint. 2470ae9e4b0dSLuis R. Rodriguez */ 2471ae9e4b0dSLuis R. Rodriguez if (!is_world_regdom(ieee80211_regdom)) 247257b5ce07SLuis R. Rodriguez regulatory_hint_user(ieee80211_regdom, 247357b5ce07SLuis R. Rodriguez NL80211_USER_REG_HINT_USER); 2474ae9e4b0dSLuis R. Rodriguez 2475b2e1b302SLuis R. Rodriguez return 0; 2476b2e1b302SLuis R. Rodriguez } 2477b2e1b302SLuis R. Rodriguez 24782fcc9f73SUwe Kleine-König void /* __init_or_exit */ regulatory_exit(void) 2479b2e1b302SLuis R. Rodriguez { 2480fe33eb39SLuis R. Rodriguez struct regulatory_request *reg_request, *tmp; 2481e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon, *btmp; 2482fe33eb39SLuis R. Rodriguez 2483fe33eb39SLuis R. Rodriguez cancel_work_sync(®_work); 2484a90c7a31SLuis R. Rodriguez cancel_delayed_work_sync(®_timeout); 2485fe33eb39SLuis R. Rodriguez 2486a1794390SLuis R. Rodriguez mutex_lock(&cfg80211_mutex); 2487abc7381bSLuis R. Rodriguez mutex_lock(®_mutex); 2488734366deSJohannes Berg 2489a042994dSLuis R. Rodriguez reset_regdomains(true); 2490734366deSJohannes Berg 249158ebacc6SLuis R. Rodriguez dev_set_uevent_suppress(®_pdev->dev, true); 2492f6037d09SJohannes Berg 2493b2e1b302SLuis R. Rodriguez platform_device_unregister(reg_pdev); 2494734366deSJohannes Berg 2495e38f8a7aSLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 2496e38f8a7aSLuis R. Rodriguez if (!list_empty(®_pending_beacons)) { 2497e38f8a7aSLuis R. Rodriguez list_for_each_entry_safe(reg_beacon, btmp, 2498e38f8a7aSLuis R. Rodriguez ®_pending_beacons, list) { 2499e38f8a7aSLuis R. Rodriguez list_del(®_beacon->list); 2500e38f8a7aSLuis R. Rodriguez kfree(reg_beacon); 2501e38f8a7aSLuis R. Rodriguez } 2502e38f8a7aSLuis R. Rodriguez } 2503e38f8a7aSLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 2504e38f8a7aSLuis R. Rodriguez 2505e38f8a7aSLuis R. Rodriguez if (!list_empty(®_beacon_list)) { 2506e38f8a7aSLuis R. Rodriguez list_for_each_entry_safe(reg_beacon, btmp, 2507e38f8a7aSLuis R. Rodriguez ®_beacon_list, list) { 2508e38f8a7aSLuis R. Rodriguez list_del(®_beacon->list); 2509e38f8a7aSLuis R. Rodriguez kfree(reg_beacon); 2510e38f8a7aSLuis R. Rodriguez } 2511e38f8a7aSLuis R. Rodriguez } 2512e38f8a7aSLuis R. Rodriguez 2513fe33eb39SLuis R. Rodriguez spin_lock(®_requests_lock); 2514fe33eb39SLuis R. Rodriguez if (!list_empty(®_requests_list)) { 2515fe33eb39SLuis R. Rodriguez list_for_each_entry_safe(reg_request, tmp, 2516fe33eb39SLuis R. Rodriguez ®_requests_list, list) { 2517fe33eb39SLuis R. Rodriguez list_del(®_request->list); 2518fe33eb39SLuis R. Rodriguez kfree(reg_request); 2519fe33eb39SLuis R. Rodriguez } 2520fe33eb39SLuis R. Rodriguez } 2521fe33eb39SLuis R. Rodriguez spin_unlock(®_requests_lock); 2522fe33eb39SLuis R. Rodriguez 2523abc7381bSLuis R. Rodriguez mutex_unlock(®_mutex); 2524a1794390SLuis R. Rodriguez mutex_unlock(&cfg80211_mutex); 25258318d78aSJohannes Berg } 2526