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; 353a85d0d7fSLuis R. Rodriguez bool set_reg = false; 354a85d0d7fSLuis R. Rodriguez 355a85d0d7fSLuis R. Rodriguez mutex_lock(&cfg80211_mutex); 3563b377ea9SJohn W. Linville 357368d06f5SJohn W. Linville mutex_lock(®_regdb_search_mutex); 3583b377ea9SJohn W. Linville while (!list_empty(®_regdb_search_list)) { 3593b377ea9SJohn W. Linville request = list_first_entry(®_regdb_search_list, 3603b377ea9SJohn W. Linville struct reg_regdb_search_request, 3613b377ea9SJohn W. Linville list); 3623b377ea9SJohn W. Linville list_del(&request->list); 3633b377ea9SJohn W. Linville 3643b377ea9SJohn W. Linville for (i=0; i<reg_regdb_size; i++) { 3653b377ea9SJohn W. Linville curdom = reg_regdb[i]; 3663b377ea9SJohn W. Linville 3673b377ea9SJohn W. Linville if (!memcmp(request->alpha2, curdom->alpha2, 2)) { 3683b377ea9SJohn W. Linville r = reg_copy_regd(®dom, curdom); 3693b377ea9SJohn W. Linville if (r) 3703b377ea9SJohn W. Linville break; 371a85d0d7fSLuis R. Rodriguez set_reg = true; 3723b377ea9SJohn W. Linville break; 3733b377ea9SJohn W. Linville } 3743b377ea9SJohn W. Linville } 3753b377ea9SJohn W. Linville 3763b377ea9SJohn W. Linville kfree(request); 3773b377ea9SJohn W. Linville } 378368d06f5SJohn W. Linville mutex_unlock(®_regdb_search_mutex); 379a85d0d7fSLuis R. Rodriguez 380a85d0d7fSLuis R. Rodriguez if (set_reg) 381a85d0d7fSLuis R. Rodriguez set_regdom(regdom); 382a85d0d7fSLuis R. Rodriguez 383a85d0d7fSLuis R. Rodriguez mutex_unlock(&cfg80211_mutex); 3843b377ea9SJohn W. Linville } 3853b377ea9SJohn W. Linville 3863b377ea9SJohn W. Linville static DECLARE_WORK(reg_regdb_work, reg_regdb_search); 3873b377ea9SJohn W. Linville 3883b377ea9SJohn W. Linville static void reg_regdb_query(const char *alpha2) 3893b377ea9SJohn W. Linville { 3903b377ea9SJohn W. Linville struct reg_regdb_search_request *request; 3913b377ea9SJohn W. Linville 3923b377ea9SJohn W. Linville if (!alpha2) 3933b377ea9SJohn W. Linville return; 3943b377ea9SJohn W. Linville 3953b377ea9SJohn W. Linville request = kzalloc(sizeof(struct reg_regdb_search_request), GFP_KERNEL); 3963b377ea9SJohn W. Linville if (!request) 3973b377ea9SJohn W. Linville return; 3983b377ea9SJohn W. Linville 3993b377ea9SJohn W. Linville memcpy(request->alpha2, alpha2, 2); 4003b377ea9SJohn W. Linville 401368d06f5SJohn W. Linville mutex_lock(®_regdb_search_mutex); 4023b377ea9SJohn W. Linville list_add_tail(&request->list, ®_regdb_search_list); 403368d06f5SJohn W. Linville mutex_unlock(®_regdb_search_mutex); 4043b377ea9SJohn W. Linville 4053b377ea9SJohn W. Linville schedule_work(®_regdb_work); 4063b377ea9SJohn W. Linville } 40780007efeSLuis R. Rodriguez 40880007efeSLuis R. Rodriguez /* Feel free to add any other sanity checks here */ 40980007efeSLuis R. Rodriguez static void reg_regdb_size_check(void) 41080007efeSLuis R. Rodriguez { 41180007efeSLuis R. Rodriguez /* We should ideally BUILD_BUG_ON() but then random builds would fail */ 41280007efeSLuis R. Rodriguez WARN_ONCE(!reg_regdb_size, "db.txt is empty, you should update it..."); 41380007efeSLuis R. Rodriguez } 4143b377ea9SJohn W. Linville #else 41580007efeSLuis R. Rodriguez static inline void reg_regdb_size_check(void) {} 4163b377ea9SJohn W. Linville static inline void reg_regdb_query(const char *alpha2) {} 4173b377ea9SJohn W. Linville #endif /* CONFIG_CFG80211_INTERNAL_REGDB */ 4183b377ea9SJohn W. Linville 419fb1fc7adSLuis R. Rodriguez /* 420fb1fc7adSLuis R. Rodriguez * This lets us keep regulatory code which is updated on a regulatory 4214d9d88d1SScott James Remnant * basis in userspace. Country information is filled in by 4224d9d88d1SScott James Remnant * reg_device_uevent 423fb1fc7adSLuis R. Rodriguez */ 424b2e1b302SLuis R. Rodriguez static int call_crda(const char *alpha2) 425b2e1b302SLuis R. Rodriguez { 426b2e1b302SLuis R. Rodriguez if (!is_world_regdom((char *) alpha2)) 427e9c0268fSJoe Perches pr_info("Calling CRDA for country: %c%c\n", 428b2e1b302SLuis R. Rodriguez alpha2[0], alpha2[1]); 429b2e1b302SLuis R. Rodriguez else 430e9c0268fSJoe Perches pr_info("Calling CRDA to update world regulatory domain\n"); 4318318d78aSJohannes Berg 4323b377ea9SJohn W. Linville /* query internal regulatory database (if it exists) */ 4333b377ea9SJohn W. Linville reg_regdb_query(alpha2); 4343b377ea9SJohn W. Linville 4354d9d88d1SScott James Remnant return kobject_uevent(®_pdev->dev.kobj, KOBJ_CHANGE); 436b2e1b302SLuis R. Rodriguez } 437b2e1b302SLuis R. Rodriguez 438b2e1b302SLuis R. Rodriguez /* Used by nl80211 before kmalloc'ing our regulatory domain */ 439a3d2eaf0SJohannes Berg bool reg_is_valid_request(const char *alpha2) 440b2e1b302SLuis R. Rodriguez { 44161405e97SLuis R. Rodriguez assert_cfg80211_lock(); 44261405e97SLuis R. Rodriguez 443f6037d09SJohannes Berg if (!last_request) 444f6037d09SJohannes Berg return false; 445f6037d09SJohannes Berg 446f6037d09SJohannes Berg return alpha2_equal(last_request->alpha2, alpha2); 447b2e1b302SLuis R. Rodriguez } 448b2e1b302SLuis R. Rodriguez 449b2e1b302SLuis R. Rodriguez /* Sanity check on a regulatory rule */ 450a3d2eaf0SJohannes Berg static bool is_valid_reg_rule(const struct ieee80211_reg_rule *rule) 451b2e1b302SLuis R. Rodriguez { 452a3d2eaf0SJohannes Berg const struct ieee80211_freq_range *freq_range = &rule->freq_range; 453b2e1b302SLuis R. Rodriguez u32 freq_diff; 454b2e1b302SLuis R. Rodriguez 45591e99004SLuis R. Rodriguez if (freq_range->start_freq_khz <= 0 || freq_range->end_freq_khz <= 0) 456b2e1b302SLuis R. Rodriguez return false; 457b2e1b302SLuis R. Rodriguez 458b2e1b302SLuis R. Rodriguez if (freq_range->start_freq_khz > freq_range->end_freq_khz) 459b2e1b302SLuis R. Rodriguez return false; 460b2e1b302SLuis R. Rodriguez 461b2e1b302SLuis R. Rodriguez freq_diff = freq_range->end_freq_khz - freq_range->start_freq_khz; 462b2e1b302SLuis R. Rodriguez 463bd05f28eSRoel Kluin if (freq_range->end_freq_khz <= freq_range->start_freq_khz || 464bd05f28eSRoel Kluin freq_range->max_bandwidth_khz > freq_diff) 465b2e1b302SLuis R. Rodriguez return false; 466b2e1b302SLuis R. Rodriguez 467b2e1b302SLuis R. Rodriguez return true; 468b2e1b302SLuis R. Rodriguez } 469b2e1b302SLuis R. Rodriguez 470a3d2eaf0SJohannes Berg static bool is_valid_rd(const struct ieee80211_regdomain *rd) 471b2e1b302SLuis R. Rodriguez { 472a3d2eaf0SJohannes Berg const struct ieee80211_reg_rule *reg_rule = NULL; 473b2e1b302SLuis R. Rodriguez unsigned int i; 474b2e1b302SLuis R. Rodriguez 475b2e1b302SLuis R. Rodriguez if (!rd->n_reg_rules) 476b2e1b302SLuis R. Rodriguez return false; 477b2e1b302SLuis R. Rodriguez 47888dc1c3fSLuis R. Rodriguez if (WARN_ON(rd->n_reg_rules > NL80211_MAX_SUPP_REG_RULES)) 47988dc1c3fSLuis R. Rodriguez return false; 48088dc1c3fSLuis R. Rodriguez 481b2e1b302SLuis R. Rodriguez for (i = 0; i < rd->n_reg_rules; i++) { 482b2e1b302SLuis R. Rodriguez reg_rule = &rd->reg_rules[i]; 483b2e1b302SLuis R. Rodriguez if (!is_valid_reg_rule(reg_rule)) 484b2e1b302SLuis R. Rodriguez return false; 485b2e1b302SLuis R. Rodriguez } 486b2e1b302SLuis R. Rodriguez 487b2e1b302SLuis R. Rodriguez return true; 488b2e1b302SLuis R. Rodriguez } 489b2e1b302SLuis R. Rodriguez 490038659e7SLuis R. Rodriguez static bool reg_does_bw_fit(const struct ieee80211_freq_range *freq_range, 491038659e7SLuis R. Rodriguez u32 center_freq_khz, 492038659e7SLuis R. Rodriguez u32 bw_khz) 493b2e1b302SLuis R. Rodriguez { 494038659e7SLuis R. Rodriguez u32 start_freq_khz, end_freq_khz; 495038659e7SLuis R. Rodriguez 496038659e7SLuis R. Rodriguez start_freq_khz = center_freq_khz - (bw_khz/2); 497038659e7SLuis R. Rodriguez end_freq_khz = center_freq_khz + (bw_khz/2); 498038659e7SLuis R. Rodriguez 499b2e1b302SLuis R. Rodriguez if (start_freq_khz >= freq_range->start_freq_khz && 500b2e1b302SLuis R. Rodriguez end_freq_khz <= freq_range->end_freq_khz) 501038659e7SLuis R. Rodriguez return true; 502038659e7SLuis R. Rodriguez 503038659e7SLuis R. Rodriguez return false; 504b2e1b302SLuis R. Rodriguez } 505b2e1b302SLuis R. Rodriguez 5060c7dc45dSLuis R. Rodriguez /** 5070c7dc45dSLuis R. Rodriguez * freq_in_rule_band - tells us if a frequency is in a frequency band 5080c7dc45dSLuis R. Rodriguez * @freq_range: frequency rule we want to query 5090c7dc45dSLuis R. Rodriguez * @freq_khz: frequency we are inquiring about 5100c7dc45dSLuis R. Rodriguez * 5110c7dc45dSLuis R. Rodriguez * This lets us know if a specific frequency rule is or is not relevant to 5120c7dc45dSLuis R. Rodriguez * a specific frequency's band. Bands are device specific and artificial 51364629b9dSVladimir Kondratiev * definitions (the "2.4 GHz band", the "5 GHz band" and the "60GHz band"), 51464629b9dSVladimir Kondratiev * however it is safe for now to assume that a frequency rule should not be 51564629b9dSVladimir Kondratiev * part of a frequency's band if the start freq or end freq are off by more 51664629b9dSVladimir Kondratiev * than 2 GHz for the 2.4 and 5 GHz bands, and by more than 10 GHz for the 51764629b9dSVladimir Kondratiev * 60 GHz band. 5180c7dc45dSLuis R. Rodriguez * This resolution can be lowered and should be considered as we add 5190c7dc45dSLuis R. Rodriguez * regulatory rule support for other "bands". 5200c7dc45dSLuis R. Rodriguez **/ 5210c7dc45dSLuis R. Rodriguez static bool freq_in_rule_band(const struct ieee80211_freq_range *freq_range, 5220c7dc45dSLuis R. Rodriguez u32 freq_khz) 5230c7dc45dSLuis R. Rodriguez { 5240c7dc45dSLuis R. Rodriguez #define ONE_GHZ_IN_KHZ 1000000 52564629b9dSVladimir Kondratiev /* 52664629b9dSVladimir Kondratiev * From 802.11ad: directional multi-gigabit (DMG): 52764629b9dSVladimir Kondratiev * Pertaining to operation in a frequency band containing a channel 52864629b9dSVladimir Kondratiev * with the Channel starting frequency above 45 GHz. 52964629b9dSVladimir Kondratiev */ 53064629b9dSVladimir Kondratiev u32 limit = freq_khz > 45 * ONE_GHZ_IN_KHZ ? 53164629b9dSVladimir Kondratiev 10 * ONE_GHZ_IN_KHZ : 2 * ONE_GHZ_IN_KHZ; 53264629b9dSVladimir Kondratiev if (abs(freq_khz - freq_range->start_freq_khz) <= limit) 5330c7dc45dSLuis R. Rodriguez return true; 53464629b9dSVladimir Kondratiev if (abs(freq_khz - freq_range->end_freq_khz) <= limit) 5350c7dc45dSLuis R. Rodriguez return true; 5360c7dc45dSLuis R. Rodriguez return false; 5370c7dc45dSLuis R. Rodriguez #undef ONE_GHZ_IN_KHZ 5380c7dc45dSLuis R. Rodriguez } 5390c7dc45dSLuis R. Rodriguez 540fb1fc7adSLuis R. Rodriguez /* 541fb1fc7adSLuis R. Rodriguez * Helper for regdom_intersect(), this does the real 542fb1fc7adSLuis R. Rodriguez * mathematical intersection fun 543fb1fc7adSLuis R. Rodriguez */ 5449c96477dSLuis R. Rodriguez static int reg_rules_intersect( 5459c96477dSLuis R. Rodriguez const struct ieee80211_reg_rule *rule1, 5469c96477dSLuis R. Rodriguez const struct ieee80211_reg_rule *rule2, 5479c96477dSLuis R. Rodriguez struct ieee80211_reg_rule *intersected_rule) 5489c96477dSLuis R. Rodriguez { 5499c96477dSLuis R. Rodriguez const struct ieee80211_freq_range *freq_range1, *freq_range2; 5509c96477dSLuis R. Rodriguez struct ieee80211_freq_range *freq_range; 5519c96477dSLuis R. Rodriguez const struct ieee80211_power_rule *power_rule1, *power_rule2; 5529c96477dSLuis R. Rodriguez struct ieee80211_power_rule *power_rule; 5539c96477dSLuis R. Rodriguez u32 freq_diff; 5549c96477dSLuis R. Rodriguez 5559c96477dSLuis R. Rodriguez freq_range1 = &rule1->freq_range; 5569c96477dSLuis R. Rodriguez freq_range2 = &rule2->freq_range; 5579c96477dSLuis R. Rodriguez freq_range = &intersected_rule->freq_range; 5589c96477dSLuis R. Rodriguez 5599c96477dSLuis R. Rodriguez power_rule1 = &rule1->power_rule; 5609c96477dSLuis R. Rodriguez power_rule2 = &rule2->power_rule; 5619c96477dSLuis R. Rodriguez power_rule = &intersected_rule->power_rule; 5629c96477dSLuis R. Rodriguez 5639c96477dSLuis R. Rodriguez freq_range->start_freq_khz = max(freq_range1->start_freq_khz, 5649c96477dSLuis R. Rodriguez freq_range2->start_freq_khz); 5659c96477dSLuis R. Rodriguez freq_range->end_freq_khz = min(freq_range1->end_freq_khz, 5669c96477dSLuis R. Rodriguez freq_range2->end_freq_khz); 5679c96477dSLuis R. Rodriguez freq_range->max_bandwidth_khz = min(freq_range1->max_bandwidth_khz, 5689c96477dSLuis R. Rodriguez freq_range2->max_bandwidth_khz); 5699c96477dSLuis R. Rodriguez 5709c96477dSLuis R. Rodriguez freq_diff = freq_range->end_freq_khz - freq_range->start_freq_khz; 5719c96477dSLuis R. Rodriguez if (freq_range->max_bandwidth_khz > freq_diff) 5729c96477dSLuis R. Rodriguez freq_range->max_bandwidth_khz = freq_diff; 5739c96477dSLuis R. Rodriguez 5749c96477dSLuis R. Rodriguez power_rule->max_eirp = min(power_rule1->max_eirp, 5759c96477dSLuis R. Rodriguez power_rule2->max_eirp); 5769c96477dSLuis R. Rodriguez power_rule->max_antenna_gain = min(power_rule1->max_antenna_gain, 5779c96477dSLuis R. Rodriguez power_rule2->max_antenna_gain); 5789c96477dSLuis R. Rodriguez 5799c96477dSLuis R. Rodriguez intersected_rule->flags = (rule1->flags | rule2->flags); 5809c96477dSLuis R. Rodriguez 5819c96477dSLuis R. Rodriguez if (!is_valid_reg_rule(intersected_rule)) 5829c96477dSLuis R. Rodriguez return -EINVAL; 5839c96477dSLuis R. Rodriguez 5849c96477dSLuis R. Rodriguez return 0; 5859c96477dSLuis R. Rodriguez } 5869c96477dSLuis R. Rodriguez 5879c96477dSLuis R. Rodriguez /** 5889c96477dSLuis R. Rodriguez * regdom_intersect - do the intersection between two regulatory domains 5899c96477dSLuis R. Rodriguez * @rd1: first regulatory domain 5909c96477dSLuis R. Rodriguez * @rd2: second regulatory domain 5919c96477dSLuis R. Rodriguez * 5929c96477dSLuis R. Rodriguez * Use this function to get the intersection between two regulatory domains. 5939c96477dSLuis R. Rodriguez * Once completed we will mark the alpha2 for the rd as intersected, "98", 5949c96477dSLuis R. Rodriguez * as no one single alpha2 can represent this regulatory domain. 5959c96477dSLuis R. Rodriguez * 5969c96477dSLuis R. Rodriguez * Returns a pointer to the regulatory domain structure which will hold the 5979c96477dSLuis R. Rodriguez * resulting intersection of rules between rd1 and rd2. We will 5989c96477dSLuis R. Rodriguez * kzalloc() this structure for you. 5999c96477dSLuis R. Rodriguez */ 6009c96477dSLuis R. Rodriguez static struct ieee80211_regdomain *regdom_intersect( 6019c96477dSLuis R. Rodriguez const struct ieee80211_regdomain *rd1, 6029c96477dSLuis R. Rodriguez const struct ieee80211_regdomain *rd2) 6039c96477dSLuis R. Rodriguez { 6049c96477dSLuis R. Rodriguez int r, size_of_regd; 6059c96477dSLuis R. Rodriguez unsigned int x, y; 6069c96477dSLuis R. Rodriguez unsigned int num_rules = 0, rule_idx = 0; 6079c96477dSLuis R. Rodriguez const struct ieee80211_reg_rule *rule1, *rule2; 6089c96477dSLuis R. Rodriguez struct ieee80211_reg_rule *intersected_rule; 6099c96477dSLuis R. Rodriguez struct ieee80211_regdomain *rd; 6109c96477dSLuis R. Rodriguez /* This is just a dummy holder to help us count */ 6119c96477dSLuis R. Rodriguez struct ieee80211_reg_rule irule; 6129c96477dSLuis R. Rodriguez 6139c96477dSLuis R. Rodriguez /* Uses the stack temporarily for counter arithmetic */ 6149c96477dSLuis R. Rodriguez intersected_rule = &irule; 6159c96477dSLuis R. Rodriguez 6169c96477dSLuis R. Rodriguez memset(intersected_rule, 0, sizeof(struct ieee80211_reg_rule)); 6179c96477dSLuis R. Rodriguez 6189c96477dSLuis R. Rodriguez if (!rd1 || !rd2) 6199c96477dSLuis R. Rodriguez return NULL; 6209c96477dSLuis R. Rodriguez 621fb1fc7adSLuis R. Rodriguez /* 622fb1fc7adSLuis R. Rodriguez * First we get a count of the rules we'll need, then we actually 6239c96477dSLuis R. Rodriguez * build them. This is to so we can malloc() and free() a 6249c96477dSLuis R. Rodriguez * regdomain once. The reason we use reg_rules_intersect() here 6259c96477dSLuis R. Rodriguez * is it will return -EINVAL if the rule computed makes no sense. 626fb1fc7adSLuis R. Rodriguez * All rules that do check out OK are valid. 627fb1fc7adSLuis R. Rodriguez */ 6289c96477dSLuis R. Rodriguez 6299c96477dSLuis R. Rodriguez for (x = 0; x < rd1->n_reg_rules; x++) { 6309c96477dSLuis R. Rodriguez rule1 = &rd1->reg_rules[x]; 6319c96477dSLuis R. Rodriguez for (y = 0; y < rd2->n_reg_rules; y++) { 6329c96477dSLuis R. Rodriguez rule2 = &rd2->reg_rules[y]; 6339c96477dSLuis R. Rodriguez if (!reg_rules_intersect(rule1, rule2, 6349c96477dSLuis R. Rodriguez intersected_rule)) 6359c96477dSLuis R. Rodriguez num_rules++; 6369c96477dSLuis R. Rodriguez memset(intersected_rule, 0, 6379c96477dSLuis R. Rodriguez sizeof(struct ieee80211_reg_rule)); 6389c96477dSLuis R. Rodriguez } 6399c96477dSLuis R. Rodriguez } 6409c96477dSLuis R. Rodriguez 6419c96477dSLuis R. Rodriguez if (!num_rules) 6429c96477dSLuis R. Rodriguez return NULL; 6439c96477dSLuis R. Rodriguez 6449c96477dSLuis R. Rodriguez size_of_regd = sizeof(struct ieee80211_regdomain) + 6459c96477dSLuis R. Rodriguez ((num_rules + 1) * sizeof(struct ieee80211_reg_rule)); 6469c96477dSLuis R. Rodriguez 6479c96477dSLuis R. Rodriguez rd = kzalloc(size_of_regd, GFP_KERNEL); 6489c96477dSLuis R. Rodriguez if (!rd) 6499c96477dSLuis R. Rodriguez return NULL; 6509c96477dSLuis R. Rodriguez 6519c96477dSLuis R. Rodriguez for (x = 0; x < rd1->n_reg_rules; x++) { 6529c96477dSLuis R. Rodriguez rule1 = &rd1->reg_rules[x]; 6539c96477dSLuis R. Rodriguez for (y = 0; y < rd2->n_reg_rules; y++) { 6549c96477dSLuis R. Rodriguez rule2 = &rd2->reg_rules[y]; 655fb1fc7adSLuis R. Rodriguez /* 656fb1fc7adSLuis R. Rodriguez * This time around instead of using the stack lets 6579c96477dSLuis R. Rodriguez * write to the target rule directly saving ourselves 658fb1fc7adSLuis R. Rodriguez * a memcpy() 659fb1fc7adSLuis R. Rodriguez */ 6609c96477dSLuis R. Rodriguez intersected_rule = &rd->reg_rules[rule_idx]; 6619c96477dSLuis R. Rodriguez r = reg_rules_intersect(rule1, rule2, 6629c96477dSLuis R. Rodriguez intersected_rule); 663fb1fc7adSLuis R. Rodriguez /* 664fb1fc7adSLuis R. Rodriguez * No need to memset here the intersected rule here as 665fb1fc7adSLuis R. Rodriguez * we're not using the stack anymore 666fb1fc7adSLuis R. Rodriguez */ 6679c96477dSLuis R. Rodriguez if (r) 6689c96477dSLuis R. Rodriguez continue; 6699c96477dSLuis R. Rodriguez rule_idx++; 6709c96477dSLuis R. Rodriguez } 6719c96477dSLuis R. Rodriguez } 6729c96477dSLuis R. Rodriguez 6739c96477dSLuis R. Rodriguez if (rule_idx != num_rules) { 6749c96477dSLuis R. Rodriguez kfree(rd); 6759c96477dSLuis R. Rodriguez return NULL; 6769c96477dSLuis R. Rodriguez } 6779c96477dSLuis R. Rodriguez 6789c96477dSLuis R. Rodriguez rd->n_reg_rules = num_rules; 6799c96477dSLuis R. Rodriguez rd->alpha2[0] = '9'; 6809c96477dSLuis R. Rodriguez rd->alpha2[1] = '8'; 6819c96477dSLuis R. Rodriguez 6829c96477dSLuis R. Rodriguez return rd; 6839c96477dSLuis R. Rodriguez } 6849c96477dSLuis R. Rodriguez 685fb1fc7adSLuis R. Rodriguez /* 686fb1fc7adSLuis R. Rodriguez * XXX: add support for the rest of enum nl80211_reg_rule_flags, we may 687fb1fc7adSLuis R. Rodriguez * want to just have the channel structure use these 688fb1fc7adSLuis R. Rodriguez */ 689b2e1b302SLuis R. Rodriguez static u32 map_regdom_flags(u32 rd_flags) 690b2e1b302SLuis R. Rodriguez { 691b2e1b302SLuis R. Rodriguez u32 channel_flags = 0; 692b2e1b302SLuis R. Rodriguez if (rd_flags & NL80211_RRF_PASSIVE_SCAN) 693b2e1b302SLuis R. Rodriguez channel_flags |= IEEE80211_CHAN_PASSIVE_SCAN; 694b2e1b302SLuis R. Rodriguez if (rd_flags & NL80211_RRF_NO_IBSS) 695b2e1b302SLuis R. Rodriguez channel_flags |= IEEE80211_CHAN_NO_IBSS; 696b2e1b302SLuis R. Rodriguez if (rd_flags & NL80211_RRF_DFS) 697b2e1b302SLuis R. Rodriguez channel_flags |= IEEE80211_CHAN_RADAR; 69803f6b084SSeth Forshee if (rd_flags & NL80211_RRF_NO_OFDM) 69903f6b084SSeth Forshee channel_flags |= IEEE80211_CHAN_NO_OFDM; 700b2e1b302SLuis R. Rodriguez return channel_flags; 701b2e1b302SLuis R. Rodriguez } 702b2e1b302SLuis R. Rodriguez 7031fa25e41SLuis R. Rodriguez static int freq_reg_info_regd(struct wiphy *wiphy, 7041fa25e41SLuis R. Rodriguez u32 center_freq, 705038659e7SLuis R. Rodriguez u32 desired_bw_khz, 7061fa25e41SLuis R. Rodriguez const struct ieee80211_reg_rule **reg_rule, 7071fa25e41SLuis R. Rodriguez const struct ieee80211_regdomain *custom_regd) 7088318d78aSJohannes Berg { 7098318d78aSJohannes Berg int i; 7100c7dc45dSLuis R. Rodriguez bool band_rule_found = false; 7113e0c3ff3SLuis R. Rodriguez const struct ieee80211_regdomain *regd; 712038659e7SLuis R. Rodriguez bool bw_fits = false; 713038659e7SLuis R. Rodriguez 714038659e7SLuis R. Rodriguez if (!desired_bw_khz) 715038659e7SLuis R. Rodriguez desired_bw_khz = MHZ_TO_KHZ(20); 7168318d78aSJohannes Berg 7171fa25e41SLuis R. Rodriguez regd = custom_regd ? custom_regd : cfg80211_regdomain; 7183e0c3ff3SLuis R. Rodriguez 719fb1fc7adSLuis R. Rodriguez /* 720fb1fc7adSLuis R. Rodriguez * Follow the driver's regulatory domain, if present, unless a country 721fb1fc7adSLuis R. Rodriguez * IE has been processed or a user wants to help complaince further 722fb1fc7adSLuis R. Rodriguez */ 7232784fe91SLuis R. Rodriguez if (!custom_regd && 7242784fe91SLuis R. Rodriguez last_request->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE && 7257db90f4aSLuis R. Rodriguez last_request->initiator != NL80211_REGDOM_SET_BY_USER && 7263e0c3ff3SLuis R. Rodriguez wiphy->regd) 7273e0c3ff3SLuis R. Rodriguez regd = wiphy->regd; 7283e0c3ff3SLuis R. Rodriguez 7293e0c3ff3SLuis R. Rodriguez if (!regd) 730b2e1b302SLuis R. Rodriguez return -EINVAL; 731b2e1b302SLuis R. Rodriguez 7323e0c3ff3SLuis R. Rodriguez for (i = 0; i < regd->n_reg_rules; i++) { 733b2e1b302SLuis R. Rodriguez const struct ieee80211_reg_rule *rr; 734b2e1b302SLuis R. Rodriguez const struct ieee80211_freq_range *fr = NULL; 735b2e1b302SLuis R. Rodriguez 7363e0c3ff3SLuis R. Rodriguez rr = ®d->reg_rules[i]; 737b2e1b302SLuis R. Rodriguez fr = &rr->freq_range; 7380c7dc45dSLuis R. Rodriguez 739fb1fc7adSLuis R. Rodriguez /* 740fb1fc7adSLuis R. Rodriguez * We only need to know if one frequency rule was 7410c7dc45dSLuis R. Rodriguez * was in center_freq's band, that's enough, so lets 742fb1fc7adSLuis R. Rodriguez * not overwrite it once found 743fb1fc7adSLuis R. Rodriguez */ 7440c7dc45dSLuis R. Rodriguez if (!band_rule_found) 7450c7dc45dSLuis R. Rodriguez band_rule_found = freq_in_rule_band(fr, center_freq); 7460c7dc45dSLuis R. Rodriguez 747038659e7SLuis R. Rodriguez bw_fits = reg_does_bw_fit(fr, 748038659e7SLuis R. Rodriguez center_freq, 749038659e7SLuis R. Rodriguez desired_bw_khz); 7500c7dc45dSLuis R. Rodriguez 751038659e7SLuis R. Rodriguez if (band_rule_found && bw_fits) { 752b2e1b302SLuis R. Rodriguez *reg_rule = rr; 753038659e7SLuis R. Rodriguez return 0; 7548318d78aSJohannes Berg } 7558318d78aSJohannes Berg } 7568318d78aSJohannes Berg 7570c7dc45dSLuis R. Rodriguez if (!band_rule_found) 7580c7dc45dSLuis R. Rodriguez return -ERANGE; 7590c7dc45dSLuis R. Rodriguez 760038659e7SLuis R. Rodriguez return -EINVAL; 761b2e1b302SLuis R. Rodriguez } 762b2e1b302SLuis R. Rodriguez 763038659e7SLuis R. Rodriguez int freq_reg_info(struct wiphy *wiphy, 764038659e7SLuis R. Rodriguez u32 center_freq, 765038659e7SLuis R. Rodriguez u32 desired_bw_khz, 7661fa25e41SLuis R. Rodriguez const struct ieee80211_reg_rule **reg_rule) 7671fa25e41SLuis R. Rodriguez { 768ac46d48eSLuis R. Rodriguez assert_cfg80211_lock(); 769038659e7SLuis R. Rodriguez return freq_reg_info_regd(wiphy, 770038659e7SLuis R. Rodriguez center_freq, 771038659e7SLuis R. Rodriguez desired_bw_khz, 772038659e7SLuis R. Rodriguez reg_rule, 773038659e7SLuis R. Rodriguez NULL); 7741fa25e41SLuis R. Rodriguez } 7754f366c5dSJohn W. Linville EXPORT_SYMBOL(freq_reg_info); 776b2e1b302SLuis R. Rodriguez 777926a0a09SLuis R. Rodriguez #ifdef CONFIG_CFG80211_REG_DEBUG 778926a0a09SLuis R. Rodriguez static const char *reg_initiator_name(enum nl80211_reg_initiator initiator) 779926a0a09SLuis R. Rodriguez { 780926a0a09SLuis R. Rodriguez switch (initiator) { 781926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_CORE: 782926a0a09SLuis R. Rodriguez return "Set by core"; 783926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_USER: 784926a0a09SLuis R. Rodriguez return "Set by user"; 785926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_DRIVER: 786926a0a09SLuis R. Rodriguez return "Set by driver"; 787926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_COUNTRY_IE: 788926a0a09SLuis R. Rodriguez return "Set by country IE"; 789926a0a09SLuis R. Rodriguez default: 790926a0a09SLuis R. Rodriguez WARN_ON(1); 791926a0a09SLuis R. Rodriguez return "Set by bug"; 792926a0a09SLuis R. Rodriguez } 793926a0a09SLuis R. Rodriguez } 794e702d3cfSLuis R. Rodriguez 795e702d3cfSLuis R. Rodriguez static void chan_reg_rule_print_dbg(struct ieee80211_channel *chan, 796e702d3cfSLuis R. Rodriguez u32 desired_bw_khz, 797e702d3cfSLuis R. Rodriguez const struct ieee80211_reg_rule *reg_rule) 798e702d3cfSLuis R. Rodriguez { 799e702d3cfSLuis R. Rodriguez const struct ieee80211_power_rule *power_rule; 800e702d3cfSLuis R. Rodriguez const struct ieee80211_freq_range *freq_range; 801e702d3cfSLuis R. Rodriguez char max_antenna_gain[32]; 802e702d3cfSLuis R. Rodriguez 803e702d3cfSLuis R. Rodriguez power_rule = ®_rule->power_rule; 804e702d3cfSLuis R. Rodriguez freq_range = ®_rule->freq_range; 805e702d3cfSLuis R. Rodriguez 806e702d3cfSLuis R. Rodriguez if (!power_rule->max_antenna_gain) 807e702d3cfSLuis R. Rodriguez snprintf(max_antenna_gain, 32, "N/A"); 808e702d3cfSLuis R. Rodriguez else 809e702d3cfSLuis R. Rodriguez snprintf(max_antenna_gain, 32, "%d", power_rule->max_antenna_gain); 810e702d3cfSLuis R. Rodriguez 811d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Updating information on frequency %d MHz " 812ff039c6fSBob Copeland "for a %d MHz width channel with regulatory rule:\n", 813e702d3cfSLuis R. Rodriguez chan->center_freq, 814e702d3cfSLuis R. Rodriguez KHZ_TO_MHZ(desired_bw_khz)); 815e702d3cfSLuis R. Rodriguez 81656e6786eSPavel Roskin REG_DBG_PRINT("%d KHz - %d KHz @ %d KHz), (%s mBi, %d mBm)\n", 817e702d3cfSLuis R. Rodriguez freq_range->start_freq_khz, 818e702d3cfSLuis R. Rodriguez freq_range->end_freq_khz, 81956e6786eSPavel Roskin freq_range->max_bandwidth_khz, 820e702d3cfSLuis R. Rodriguez max_antenna_gain, 821e702d3cfSLuis R. Rodriguez power_rule->max_eirp); 822e702d3cfSLuis R. Rodriguez } 823e702d3cfSLuis R. Rodriguez #else 824e702d3cfSLuis R. Rodriguez static void chan_reg_rule_print_dbg(struct ieee80211_channel *chan, 825e702d3cfSLuis R. Rodriguez u32 desired_bw_khz, 826e702d3cfSLuis R. Rodriguez const struct ieee80211_reg_rule *reg_rule) 827e702d3cfSLuis R. Rodriguez { 828e702d3cfSLuis R. Rodriguez return; 829e702d3cfSLuis R. Rodriguez } 830926a0a09SLuis R. Rodriguez #endif 831926a0a09SLuis R. Rodriguez 832038659e7SLuis R. Rodriguez /* 833038659e7SLuis R. Rodriguez * Note that right now we assume the desired channel bandwidth 834038659e7SLuis R. Rodriguez * is always 20 MHz for each individual channel (HT40 uses 20 MHz 835038659e7SLuis R. Rodriguez * per channel, the primary and the extension channel). To support 836038659e7SLuis R. Rodriguez * smaller custom bandwidths such as 5 MHz or 10 MHz we'll need a 837038659e7SLuis R. Rodriguez * new ieee80211_channel.target_bw and re run the regulatory check 838038659e7SLuis R. Rodriguez * on the wiphy with the target_bw specified. Then we can simply use 839038659e7SLuis R. Rodriguez * that below for the desired_bw_khz below. 840038659e7SLuis R. Rodriguez */ 8417ca43d03SLuis R. Rodriguez static void handle_channel(struct wiphy *wiphy, 8427ca43d03SLuis R. Rodriguez enum nl80211_reg_initiator initiator, 8437ca43d03SLuis R. Rodriguez enum ieee80211_band band, 844a92a3ce7SLuis R. Rodriguez unsigned int chan_idx) 845b2e1b302SLuis R. Rodriguez { 846b2e1b302SLuis R. Rodriguez int r; 847038659e7SLuis R. Rodriguez u32 flags, bw_flags = 0; 848038659e7SLuis R. Rodriguez u32 desired_bw_khz = MHZ_TO_KHZ(20); 849b2e1b302SLuis R. Rodriguez const struct ieee80211_reg_rule *reg_rule = NULL; 850b2e1b302SLuis R. Rodriguez const struct ieee80211_power_rule *power_rule = NULL; 851038659e7SLuis R. Rodriguez const struct ieee80211_freq_range *freq_range = NULL; 852a92a3ce7SLuis R. Rodriguez struct ieee80211_supported_band *sband; 853a92a3ce7SLuis R. Rodriguez struct ieee80211_channel *chan; 854fe33eb39SLuis R. Rodriguez struct wiphy *request_wiphy = NULL; 855a92a3ce7SLuis R. Rodriguez 856761cf7ecSLuis R. Rodriguez assert_cfg80211_lock(); 857761cf7ecSLuis R. Rodriguez 858806a9e39SLuis R. Rodriguez request_wiphy = wiphy_idx_to_wiphy(last_request->wiphy_idx); 859806a9e39SLuis R. Rodriguez 860a92a3ce7SLuis R. Rodriguez sband = wiphy->bands[band]; 861a92a3ce7SLuis R. Rodriguez BUG_ON(chan_idx >= sband->n_channels); 862a92a3ce7SLuis R. Rodriguez chan = &sband->channels[chan_idx]; 863a92a3ce7SLuis R. Rodriguez 864a92a3ce7SLuis R. Rodriguez flags = chan->orig_flags; 865b2e1b302SLuis R. Rodriguez 866038659e7SLuis R. Rodriguez r = freq_reg_info(wiphy, 867038659e7SLuis R. Rodriguez MHZ_TO_KHZ(chan->center_freq), 868038659e7SLuis R. Rodriguez desired_bw_khz, 869038659e7SLuis R. Rodriguez ®_rule); 870b2e1b302SLuis R. Rodriguez 871ca4ffe8fSLuis R. Rodriguez if (r) { 872ca4ffe8fSLuis R. Rodriguez /* 873ca4ffe8fSLuis R. Rodriguez * We will disable all channels that do not match our 87425985edcSLucas De Marchi * received regulatory rule unless the hint is coming 875ca4ffe8fSLuis R. Rodriguez * from a Country IE and the Country IE had no information 876ca4ffe8fSLuis R. Rodriguez * about a band. The IEEE 802.11 spec allows for an AP 877ca4ffe8fSLuis R. Rodriguez * to send only a subset of the regulatory rules allowed, 878ca4ffe8fSLuis R. Rodriguez * so an AP in the US that only supports 2.4 GHz may only send 879ca4ffe8fSLuis R. Rodriguez * a country IE with information for the 2.4 GHz band 880ca4ffe8fSLuis R. Rodriguez * while 5 GHz is still supported. 881ca4ffe8fSLuis R. Rodriguez */ 882ca4ffe8fSLuis R. Rodriguez if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE && 883ca4ffe8fSLuis R. Rodriguez r == -ERANGE) 8848318d78aSJohannes Berg return; 8858318d78aSJohannes Berg 886d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Disabling freq %d MHz\n", chan->center_freq); 887ca4ffe8fSLuis R. Rodriguez chan->flags = IEEE80211_CHAN_DISABLED; 888ca4ffe8fSLuis R. Rodriguez return; 889ca4ffe8fSLuis R. Rodriguez } 890ca4ffe8fSLuis R. Rodriguez 891e702d3cfSLuis R. Rodriguez chan_reg_rule_print_dbg(chan, desired_bw_khz, reg_rule); 892e702d3cfSLuis R. Rodriguez 893b2e1b302SLuis R. Rodriguez power_rule = ®_rule->power_rule; 894038659e7SLuis R. Rodriguez freq_range = ®_rule->freq_range; 895038659e7SLuis R. Rodriguez 896038659e7SLuis R. Rodriguez if (freq_range->max_bandwidth_khz < MHZ_TO_KHZ(40)) 897038659e7SLuis R. Rodriguez bw_flags = IEEE80211_CHAN_NO_HT40; 898b2e1b302SLuis R. Rodriguez 8997db90f4aSLuis R. Rodriguez if (last_request->initiator == NL80211_REGDOM_SET_BY_DRIVER && 900806a9e39SLuis R. Rodriguez request_wiphy && request_wiphy == wiphy && 9015be83de5SJohannes Berg request_wiphy->flags & WIPHY_FLAG_STRICT_REGULATORY) { 902fb1fc7adSLuis R. Rodriguez /* 90325985edcSLucas De Marchi * This guarantees the driver's requested regulatory domain 904f976376dSLuis R. Rodriguez * will always be used as a base for further regulatory 905fb1fc7adSLuis R. Rodriguez * settings 906fb1fc7adSLuis R. Rodriguez */ 907f976376dSLuis R. Rodriguez chan->flags = chan->orig_flags = 908038659e7SLuis R. Rodriguez map_regdom_flags(reg_rule->flags) | bw_flags; 909f976376dSLuis R. Rodriguez chan->max_antenna_gain = chan->orig_mag = 910f976376dSLuis R. Rodriguez (int) MBI_TO_DBI(power_rule->max_antenna_gain); 911279f0f55SFelix Fietkau chan->max_reg_power = chan->max_power = chan->orig_mpwr = 912f976376dSLuis R. Rodriguez (int) MBM_TO_DBM(power_rule->max_eirp); 913f976376dSLuis R. Rodriguez return; 914f976376dSLuis R. Rodriguez } 915f976376dSLuis R. Rodriguez 916aa3d7eefSRajkumar Manoharan chan->beacon_found = false; 917038659e7SLuis R. Rodriguez chan->flags = flags | bw_flags | map_regdom_flags(reg_rule->flags); 9188318d78aSJohannes Berg chan->max_antenna_gain = min(chan->orig_mag, 919b2e1b302SLuis R. Rodriguez (int) MBI_TO_DBI(power_rule->max_antenna_gain)); 920eccc068eSHong Wu chan->max_reg_power = (int) MBM_TO_DBM(power_rule->max_eirp); 9215e31fc08SStanislaw Gruszka if (chan->orig_mpwr) { 9225e31fc08SStanislaw Gruszka /* 9235e31fc08SStanislaw Gruszka * Devices that have their own custom regulatory domain 9245e31fc08SStanislaw Gruszka * but also use WIPHY_FLAG_STRICT_REGULATORY will follow the 9255e31fc08SStanislaw Gruszka * passed country IE power settings. 9265e31fc08SStanislaw Gruszka */ 9275e31fc08SStanislaw Gruszka if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE && 9285e31fc08SStanislaw Gruszka wiphy->flags & WIPHY_FLAG_CUSTOM_REGULATORY && 9295e31fc08SStanislaw Gruszka wiphy->flags & WIPHY_FLAG_STRICT_REGULATORY) 9305e31fc08SStanislaw Gruszka chan->max_power = chan->max_reg_power; 9315e31fc08SStanislaw Gruszka else 9325e31fc08SStanislaw Gruszka chan->max_power = min(chan->orig_mpwr, 9335e31fc08SStanislaw Gruszka chan->max_reg_power); 9345e31fc08SStanislaw Gruszka } else 9355e31fc08SStanislaw Gruszka chan->max_power = chan->max_reg_power; 9368318d78aSJohannes Berg } 9378318d78aSJohannes Berg 9387ca43d03SLuis R. Rodriguez static void handle_band(struct wiphy *wiphy, 9397ca43d03SLuis R. Rodriguez enum ieee80211_band band, 9407ca43d03SLuis R. Rodriguez enum nl80211_reg_initiator initiator) 9418318d78aSJohannes Berg { 942a92a3ce7SLuis R. Rodriguez unsigned int i; 943a92a3ce7SLuis R. Rodriguez struct ieee80211_supported_band *sband; 944a92a3ce7SLuis R. Rodriguez 945a92a3ce7SLuis R. Rodriguez BUG_ON(!wiphy->bands[band]); 946a92a3ce7SLuis R. Rodriguez sband = wiphy->bands[band]; 9478318d78aSJohannes Berg 9488318d78aSJohannes Berg for (i = 0; i < sband->n_channels; i++) 9497ca43d03SLuis R. Rodriguez handle_channel(wiphy, initiator, band, i); 9508318d78aSJohannes Berg } 9518318d78aSJohannes Berg 95257b5ce07SLuis R. Rodriguez static bool reg_request_cell_base(struct regulatory_request *request) 95357b5ce07SLuis R. Rodriguez { 95457b5ce07SLuis R. Rodriguez if (request->initiator != NL80211_REGDOM_SET_BY_USER) 95557b5ce07SLuis R. Rodriguez return false; 95657b5ce07SLuis R. Rodriguez if (request->user_reg_hint_type != NL80211_USER_REG_HINT_CELL_BASE) 95757b5ce07SLuis R. Rodriguez return false; 95857b5ce07SLuis R. Rodriguez return true; 95957b5ce07SLuis R. Rodriguez } 96057b5ce07SLuis R. Rodriguez 96157b5ce07SLuis R. Rodriguez bool reg_last_request_cell_base(void) 96257b5ce07SLuis R. Rodriguez { 963ebd0fd2bSMohammed Shafi Shajakhan bool val; 96457b5ce07SLuis R. Rodriguez assert_cfg80211_lock(); 96557b5ce07SLuis R. Rodriguez 96657b5ce07SLuis R. Rodriguez mutex_lock(®_mutex); 967ebd0fd2bSMohammed Shafi Shajakhan val = reg_request_cell_base(last_request); 96857b5ce07SLuis R. Rodriguez mutex_unlock(®_mutex); 969ebd0fd2bSMohammed Shafi Shajakhan return val; 97057b5ce07SLuis R. Rodriguez } 97157b5ce07SLuis R. Rodriguez 97257b5ce07SLuis R. Rodriguez #ifdef CONFIG_CFG80211_CERTIFICATION_ONUS 97357b5ce07SLuis R. Rodriguez 97457b5ce07SLuis R. Rodriguez /* Core specific check */ 97557b5ce07SLuis R. Rodriguez static int reg_ignore_cell_hint(struct regulatory_request *pending_request) 97657b5ce07SLuis R. Rodriguez { 97757b5ce07SLuis R. Rodriguez if (!reg_num_devs_support_basehint) 97857b5ce07SLuis R. Rodriguez return -EOPNOTSUPP; 97957b5ce07SLuis R. Rodriguez 98057b5ce07SLuis R. Rodriguez if (reg_request_cell_base(last_request)) { 98157b5ce07SLuis R. Rodriguez if (!regdom_changes(pending_request->alpha2)) 98257b5ce07SLuis R. Rodriguez return -EALREADY; 98357b5ce07SLuis R. Rodriguez return 0; 98457b5ce07SLuis R. Rodriguez } 98557b5ce07SLuis R. Rodriguez return 0; 98657b5ce07SLuis R. Rodriguez } 98757b5ce07SLuis R. Rodriguez 98857b5ce07SLuis R. Rodriguez /* Device specific check */ 98957b5ce07SLuis R. Rodriguez static bool reg_dev_ignore_cell_hint(struct wiphy *wiphy) 99057b5ce07SLuis R. Rodriguez { 99157b5ce07SLuis R. Rodriguez if (!(wiphy->features & NL80211_FEATURE_CELL_BASE_REG_HINTS)) 99257b5ce07SLuis R. Rodriguez return true; 99357b5ce07SLuis R. Rodriguez return false; 99457b5ce07SLuis R. Rodriguez } 99557b5ce07SLuis R. Rodriguez #else 99657b5ce07SLuis R. Rodriguez static int reg_ignore_cell_hint(struct regulatory_request *pending_request) 99757b5ce07SLuis R. Rodriguez { 99857b5ce07SLuis R. Rodriguez return -EOPNOTSUPP; 99957b5ce07SLuis R. Rodriguez } 100057b5ce07SLuis R. Rodriguez static int reg_dev_ignore_cell_hint(struct wiphy *wiphy) 100157b5ce07SLuis R. Rodriguez { 100257b5ce07SLuis R. Rodriguez return true; 100357b5ce07SLuis R. Rodriguez } 100457b5ce07SLuis R. Rodriguez #endif 100557b5ce07SLuis R. Rodriguez 100657b5ce07SLuis R. Rodriguez 10077db90f4aSLuis R. Rodriguez static bool ignore_reg_update(struct wiphy *wiphy, 10087db90f4aSLuis R. Rodriguez enum nl80211_reg_initiator initiator) 100914b9815aSLuis R. Rodriguez { 1010926a0a09SLuis R. Rodriguez if (!last_request) { 1011d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Ignoring regulatory request %s since " 1012926a0a09SLuis R. Rodriguez "last_request is not set\n", 1013926a0a09SLuis R. Rodriguez reg_initiator_name(initiator)); 101414b9815aSLuis R. Rodriguez return true; 1015926a0a09SLuis R. Rodriguez } 1016926a0a09SLuis R. Rodriguez 10177db90f4aSLuis R. Rodriguez if (initiator == NL80211_REGDOM_SET_BY_CORE && 1018926a0a09SLuis R. Rodriguez wiphy->flags & WIPHY_FLAG_CUSTOM_REGULATORY) { 1019d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Ignoring regulatory request %s " 1020926a0a09SLuis R. Rodriguez "since the driver uses its own custom " 102112c5ffb5SJoe Perches "regulatory domain\n", 1022926a0a09SLuis R. Rodriguez reg_initiator_name(initiator)); 102314b9815aSLuis R. Rodriguez return true; 1024926a0a09SLuis R. Rodriguez } 1025926a0a09SLuis R. Rodriguez 1026fb1fc7adSLuis R. Rodriguez /* 1027fb1fc7adSLuis R. Rodriguez * wiphy->regd will be set once the device has its own 1028fb1fc7adSLuis R. Rodriguez * desired regulatory domain set 1029fb1fc7adSLuis R. Rodriguez */ 10305be83de5SJohannes Berg if (wiphy->flags & WIPHY_FLAG_STRICT_REGULATORY && !wiphy->regd && 1031749b527bSLuis R. Rodriguez initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE && 1032926a0a09SLuis R. Rodriguez !is_world_regdom(last_request->alpha2)) { 1033d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Ignoring regulatory request %s " 10345bc91db8SMihai Moldovan "since the driver requires its own regulatory " 103512c5ffb5SJoe Perches "domain to be set first\n", 1036926a0a09SLuis R. Rodriguez reg_initiator_name(initiator)); 103714b9815aSLuis R. Rodriguez return true; 1038926a0a09SLuis R. Rodriguez } 1039926a0a09SLuis R. Rodriguez 104057b5ce07SLuis R. Rodriguez if (reg_request_cell_base(last_request)) 104157b5ce07SLuis R. Rodriguez return reg_dev_ignore_cell_hint(wiphy); 104257b5ce07SLuis R. Rodriguez 104314b9815aSLuis R. Rodriguez return false; 104414b9815aSLuis R. Rodriguez } 104514b9815aSLuis R. Rodriguez 1046e38f8a7aSLuis R. Rodriguez static void handle_reg_beacon(struct wiphy *wiphy, 1047e38f8a7aSLuis R. Rodriguez unsigned int chan_idx, 1048e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon) 1049e38f8a7aSLuis R. Rodriguez { 1050e38f8a7aSLuis R. Rodriguez struct ieee80211_supported_band *sband; 1051e38f8a7aSLuis R. Rodriguez struct ieee80211_channel *chan; 10526bad8766SLuis R. Rodriguez bool channel_changed = false; 10536bad8766SLuis R. Rodriguez struct ieee80211_channel chan_before; 1054e38f8a7aSLuis R. Rodriguez 1055e38f8a7aSLuis R. Rodriguez assert_cfg80211_lock(); 1056e38f8a7aSLuis R. Rodriguez 1057e38f8a7aSLuis R. Rodriguez sband = wiphy->bands[reg_beacon->chan.band]; 1058e38f8a7aSLuis R. Rodriguez chan = &sband->channels[chan_idx]; 1059e38f8a7aSLuis R. Rodriguez 1060e38f8a7aSLuis R. Rodriguez if (likely(chan->center_freq != reg_beacon->chan.center_freq)) 1061e38f8a7aSLuis R. Rodriguez return; 1062e38f8a7aSLuis R. Rodriguez 10636bad8766SLuis R. Rodriguez if (chan->beacon_found) 10646bad8766SLuis R. Rodriguez return; 10656bad8766SLuis R. Rodriguez 10666bad8766SLuis R. Rodriguez chan->beacon_found = true; 10676bad8766SLuis R. Rodriguez 10685be83de5SJohannes Berg if (wiphy->flags & WIPHY_FLAG_DISABLE_BEACON_HINTS) 106937184244SLuis R. Rodriguez return; 107037184244SLuis R. Rodriguez 10716bad8766SLuis R. Rodriguez chan_before.center_freq = chan->center_freq; 10726bad8766SLuis R. Rodriguez chan_before.flags = chan->flags; 10736bad8766SLuis R. Rodriguez 107437184244SLuis R. Rodriguez if (chan->flags & IEEE80211_CHAN_PASSIVE_SCAN) { 1075e38f8a7aSLuis R. Rodriguez chan->flags &= ~IEEE80211_CHAN_PASSIVE_SCAN; 10766bad8766SLuis R. Rodriguez channel_changed = true; 1077e38f8a7aSLuis R. Rodriguez } 1078e38f8a7aSLuis R. Rodriguez 107937184244SLuis R. Rodriguez if (chan->flags & IEEE80211_CHAN_NO_IBSS) { 1080e38f8a7aSLuis R. Rodriguez chan->flags &= ~IEEE80211_CHAN_NO_IBSS; 10816bad8766SLuis R. Rodriguez channel_changed = true; 1082e38f8a7aSLuis R. Rodriguez } 1083e38f8a7aSLuis R. Rodriguez 10846bad8766SLuis R. Rodriguez if (channel_changed) 10856bad8766SLuis R. Rodriguez nl80211_send_beacon_hint_event(wiphy, &chan_before, chan); 1086e38f8a7aSLuis R. Rodriguez } 1087e38f8a7aSLuis R. Rodriguez 1088e38f8a7aSLuis R. Rodriguez /* 1089e38f8a7aSLuis R. Rodriguez * Called when a scan on a wiphy finds a beacon on 1090e38f8a7aSLuis R. Rodriguez * new channel 1091e38f8a7aSLuis R. Rodriguez */ 1092e38f8a7aSLuis R. Rodriguez static void wiphy_update_new_beacon(struct wiphy *wiphy, 1093e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon) 1094e38f8a7aSLuis R. Rodriguez { 1095e38f8a7aSLuis R. Rodriguez unsigned int i; 1096e38f8a7aSLuis R. Rodriguez struct ieee80211_supported_band *sband; 1097e38f8a7aSLuis R. Rodriguez 1098e38f8a7aSLuis R. Rodriguez assert_cfg80211_lock(); 1099e38f8a7aSLuis R. Rodriguez 1100e38f8a7aSLuis R. Rodriguez if (!wiphy->bands[reg_beacon->chan.band]) 1101e38f8a7aSLuis R. Rodriguez return; 1102e38f8a7aSLuis R. Rodriguez 1103e38f8a7aSLuis R. Rodriguez sband = wiphy->bands[reg_beacon->chan.band]; 1104e38f8a7aSLuis R. Rodriguez 1105e38f8a7aSLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 1106e38f8a7aSLuis R. Rodriguez handle_reg_beacon(wiphy, i, reg_beacon); 1107e38f8a7aSLuis R. Rodriguez } 1108e38f8a7aSLuis R. Rodriguez 1109e38f8a7aSLuis R. Rodriguez /* 1110e38f8a7aSLuis R. Rodriguez * Called upon reg changes or a new wiphy is added 1111e38f8a7aSLuis R. Rodriguez */ 1112e38f8a7aSLuis R. Rodriguez static void wiphy_update_beacon_reg(struct wiphy *wiphy) 1113e38f8a7aSLuis R. Rodriguez { 1114e38f8a7aSLuis R. Rodriguez unsigned int i; 1115e38f8a7aSLuis R. Rodriguez struct ieee80211_supported_band *sband; 1116e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon; 1117e38f8a7aSLuis R. Rodriguez 1118e38f8a7aSLuis R. Rodriguez assert_cfg80211_lock(); 1119e38f8a7aSLuis R. Rodriguez 1120e38f8a7aSLuis R. Rodriguez if (list_empty(®_beacon_list)) 1121e38f8a7aSLuis R. Rodriguez return; 1122e38f8a7aSLuis R. Rodriguez 1123e38f8a7aSLuis R. Rodriguez list_for_each_entry(reg_beacon, ®_beacon_list, list) { 1124e38f8a7aSLuis R. Rodriguez if (!wiphy->bands[reg_beacon->chan.band]) 1125e38f8a7aSLuis R. Rodriguez continue; 1126e38f8a7aSLuis R. Rodriguez sband = wiphy->bands[reg_beacon->chan.band]; 1127e38f8a7aSLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 1128e38f8a7aSLuis R. Rodriguez handle_reg_beacon(wiphy, i, reg_beacon); 1129e38f8a7aSLuis R. Rodriguez } 1130e38f8a7aSLuis R. Rodriguez } 1131e38f8a7aSLuis R. Rodriguez 1132e38f8a7aSLuis R. Rodriguez static bool reg_is_world_roaming(struct wiphy *wiphy) 1133e38f8a7aSLuis R. Rodriguez { 1134e38f8a7aSLuis R. Rodriguez if (is_world_regdom(cfg80211_regdomain->alpha2) || 1135e38f8a7aSLuis R. Rodriguez (wiphy->regd && is_world_regdom(wiphy->regd->alpha2))) 1136e38f8a7aSLuis R. Rodriguez return true; 1137b1ed8dddSLuis R. Rodriguez if (last_request && 1138b1ed8dddSLuis R. Rodriguez last_request->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE && 11395be83de5SJohannes Berg wiphy->flags & WIPHY_FLAG_CUSTOM_REGULATORY) 1140e38f8a7aSLuis R. Rodriguez return true; 1141e38f8a7aSLuis R. Rodriguez return false; 1142e38f8a7aSLuis R. Rodriguez } 1143e38f8a7aSLuis R. Rodriguez 1144e38f8a7aSLuis R. Rodriguez /* Reap the advantages of previously found beacons */ 1145e38f8a7aSLuis R. Rodriguez static void reg_process_beacons(struct wiphy *wiphy) 1146e38f8a7aSLuis R. Rodriguez { 1147b1ed8dddSLuis R. Rodriguez /* 1148b1ed8dddSLuis R. Rodriguez * Means we are just firing up cfg80211, so no beacons would 1149b1ed8dddSLuis R. Rodriguez * have been processed yet. 1150b1ed8dddSLuis R. Rodriguez */ 1151b1ed8dddSLuis R. Rodriguez if (!last_request) 1152b1ed8dddSLuis R. Rodriguez return; 1153e38f8a7aSLuis R. Rodriguez if (!reg_is_world_roaming(wiphy)) 1154e38f8a7aSLuis R. Rodriguez return; 1155e38f8a7aSLuis R. Rodriguez wiphy_update_beacon_reg(wiphy); 1156e38f8a7aSLuis R. Rodriguez } 1157e38f8a7aSLuis R. Rodriguez 1158038659e7SLuis R. Rodriguez static bool is_ht40_not_allowed(struct ieee80211_channel *chan) 1159038659e7SLuis R. Rodriguez { 1160038659e7SLuis R. Rodriguez if (!chan) 1161038659e7SLuis R. Rodriguez return true; 1162038659e7SLuis R. Rodriguez if (chan->flags & IEEE80211_CHAN_DISABLED) 1163038659e7SLuis R. Rodriguez return true; 1164038659e7SLuis R. Rodriguez /* This would happen when regulatory rules disallow HT40 completely */ 1165038659e7SLuis R. Rodriguez if (IEEE80211_CHAN_NO_HT40 == (chan->flags & (IEEE80211_CHAN_NO_HT40))) 1166038659e7SLuis R. Rodriguez return true; 1167038659e7SLuis R. Rodriguez return false; 1168038659e7SLuis R. Rodriguez } 1169038659e7SLuis R. Rodriguez 1170038659e7SLuis R. Rodriguez static void reg_process_ht_flags_channel(struct wiphy *wiphy, 1171038659e7SLuis R. Rodriguez enum ieee80211_band band, 1172038659e7SLuis R. Rodriguez unsigned int chan_idx) 1173038659e7SLuis R. Rodriguez { 1174038659e7SLuis R. Rodriguez struct ieee80211_supported_band *sband; 1175038659e7SLuis R. Rodriguez struct ieee80211_channel *channel; 1176038659e7SLuis R. Rodriguez struct ieee80211_channel *channel_before = NULL, *channel_after = NULL; 1177038659e7SLuis R. Rodriguez unsigned int i; 1178038659e7SLuis R. Rodriguez 1179038659e7SLuis R. Rodriguez assert_cfg80211_lock(); 1180038659e7SLuis R. Rodriguez 1181038659e7SLuis R. Rodriguez sband = wiphy->bands[band]; 1182038659e7SLuis R. Rodriguez BUG_ON(chan_idx >= sband->n_channels); 1183038659e7SLuis R. Rodriguez channel = &sband->channels[chan_idx]; 1184038659e7SLuis R. Rodriguez 1185038659e7SLuis R. Rodriguez if (is_ht40_not_allowed(channel)) { 1186038659e7SLuis R. Rodriguez channel->flags |= IEEE80211_CHAN_NO_HT40; 1187038659e7SLuis R. Rodriguez return; 1188038659e7SLuis R. Rodriguez } 1189038659e7SLuis R. Rodriguez 1190038659e7SLuis R. Rodriguez /* 1191038659e7SLuis R. Rodriguez * We need to ensure the extension channels exist to 1192038659e7SLuis R. Rodriguez * be able to use HT40- or HT40+, this finds them (or not) 1193038659e7SLuis R. Rodriguez */ 1194038659e7SLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) { 1195038659e7SLuis R. Rodriguez struct ieee80211_channel *c = &sband->channels[i]; 1196038659e7SLuis R. Rodriguez if (c->center_freq == (channel->center_freq - 20)) 1197038659e7SLuis R. Rodriguez channel_before = c; 1198038659e7SLuis R. Rodriguez if (c->center_freq == (channel->center_freq + 20)) 1199038659e7SLuis R. Rodriguez channel_after = c; 1200038659e7SLuis R. Rodriguez } 1201038659e7SLuis R. Rodriguez 1202038659e7SLuis R. Rodriguez /* 1203038659e7SLuis R. Rodriguez * Please note that this assumes target bandwidth is 20 MHz, 1204038659e7SLuis R. Rodriguez * if that ever changes we also need to change the below logic 1205038659e7SLuis R. Rodriguez * to include that as well. 1206038659e7SLuis R. Rodriguez */ 1207038659e7SLuis R. Rodriguez if (is_ht40_not_allowed(channel_before)) 1208689da1b3SLuis R. Rodriguez channel->flags |= IEEE80211_CHAN_NO_HT40MINUS; 1209038659e7SLuis R. Rodriguez else 1210689da1b3SLuis R. Rodriguez channel->flags &= ~IEEE80211_CHAN_NO_HT40MINUS; 1211038659e7SLuis R. Rodriguez 1212038659e7SLuis R. Rodriguez if (is_ht40_not_allowed(channel_after)) 1213689da1b3SLuis R. Rodriguez channel->flags |= IEEE80211_CHAN_NO_HT40PLUS; 1214038659e7SLuis R. Rodriguez else 1215689da1b3SLuis R. Rodriguez channel->flags &= ~IEEE80211_CHAN_NO_HT40PLUS; 1216038659e7SLuis R. Rodriguez } 1217038659e7SLuis R. Rodriguez 1218038659e7SLuis R. Rodriguez static void reg_process_ht_flags_band(struct wiphy *wiphy, 1219038659e7SLuis R. Rodriguez enum ieee80211_band band) 1220038659e7SLuis R. Rodriguez { 1221038659e7SLuis R. Rodriguez unsigned int i; 1222038659e7SLuis R. Rodriguez struct ieee80211_supported_band *sband; 1223038659e7SLuis R. Rodriguez 1224038659e7SLuis R. Rodriguez BUG_ON(!wiphy->bands[band]); 1225038659e7SLuis R. Rodriguez sband = wiphy->bands[band]; 1226038659e7SLuis R. Rodriguez 1227038659e7SLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 1228038659e7SLuis R. Rodriguez reg_process_ht_flags_channel(wiphy, band, i); 1229038659e7SLuis R. Rodriguez } 1230038659e7SLuis R. Rodriguez 1231038659e7SLuis R. Rodriguez static void reg_process_ht_flags(struct wiphy *wiphy) 1232038659e7SLuis R. Rodriguez { 1233038659e7SLuis R. Rodriguez enum ieee80211_band band; 1234038659e7SLuis R. Rodriguez 1235038659e7SLuis R. Rodriguez if (!wiphy) 1236038659e7SLuis R. Rodriguez return; 1237038659e7SLuis R. Rodriguez 1238038659e7SLuis R. Rodriguez for (band = 0; band < IEEE80211_NUM_BANDS; band++) { 1239038659e7SLuis R. Rodriguez if (wiphy->bands[band]) 1240038659e7SLuis R. Rodriguez reg_process_ht_flags_band(wiphy, band); 1241038659e7SLuis R. Rodriguez } 1242038659e7SLuis R. Rodriguez 1243038659e7SLuis R. Rodriguez } 1244038659e7SLuis R. Rodriguez 1245eac03e38SSven Neumann static void wiphy_update_regulatory(struct wiphy *wiphy, 12467db90f4aSLuis R. Rodriguez enum nl80211_reg_initiator initiator) 12478318d78aSJohannes Berg { 12488318d78aSJohannes Berg enum ieee80211_band band; 1249d46e5b1dSLuis R. Rodriguez 1250eac03e38SSven Neumann assert_reg_lock(); 1251eac03e38SSven Neumann 12527db90f4aSLuis R. Rodriguez if (ignore_reg_update(wiphy, initiator)) 1253a203c2aaSSven Neumann return; 1254a203c2aaSSven Neumann 1255b68e6b3bSLuis R. Rodriguez last_request->dfs_region = cfg80211_regdomain->dfs_region; 1256b68e6b3bSLuis R. Rodriguez 1257b2e1b302SLuis R. Rodriguez for (band = 0; band < IEEE80211_NUM_BANDS; band++) { 12588318d78aSJohannes Berg if (wiphy->bands[band]) 12597ca43d03SLuis R. Rodriguez handle_band(wiphy, band, initiator); 1260b2e1b302SLuis R. Rodriguez } 1261a203c2aaSSven Neumann 1262e38f8a7aSLuis R. Rodriguez reg_process_beacons(wiphy); 1263038659e7SLuis R. Rodriguez reg_process_ht_flags(wiphy); 1264b2e1b302SLuis R. Rodriguez if (wiphy->reg_notifier) 1265716f9392SLuis R. Rodriguez wiphy->reg_notifier(wiphy, last_request); 1266b2e1b302SLuis R. Rodriguez } 1267b2e1b302SLuis R. Rodriguez 1268d7549cbbSSven Neumann static void update_all_wiphy_regulatory(enum nl80211_reg_initiator initiator) 1269d7549cbbSSven Neumann { 1270d7549cbbSSven Neumann struct cfg80211_registered_device *rdev; 12714a38994fSRajkumar Manoharan struct wiphy *wiphy; 1272d7549cbbSSven Neumann 12734a38994fSRajkumar Manoharan list_for_each_entry(rdev, &cfg80211_rdev_list, list) { 12744a38994fSRajkumar Manoharan wiphy = &rdev->wiphy; 12754a38994fSRajkumar Manoharan wiphy_update_regulatory(wiphy, initiator); 12764a38994fSRajkumar Manoharan /* 12774a38994fSRajkumar Manoharan * Regulatory updates set by CORE are ignored for custom 12784a38994fSRajkumar Manoharan * regulatory cards. Let us notify the changes to the driver, 12794a38994fSRajkumar Manoharan * as some drivers used this to restore its orig_* reg domain. 12804a38994fSRajkumar Manoharan */ 12814a38994fSRajkumar Manoharan if (initiator == NL80211_REGDOM_SET_BY_CORE && 12824a38994fSRajkumar Manoharan wiphy->flags & WIPHY_FLAG_CUSTOM_REGULATORY && 12834a38994fSRajkumar Manoharan wiphy->reg_notifier) 12844a38994fSRajkumar Manoharan wiphy->reg_notifier(wiphy, last_request); 12854a38994fSRajkumar Manoharan } 1286d7549cbbSSven Neumann } 1287d7549cbbSSven Neumann 12881fa25e41SLuis R. Rodriguez static void handle_channel_custom(struct wiphy *wiphy, 12891fa25e41SLuis R. Rodriguez enum ieee80211_band band, 12901fa25e41SLuis R. Rodriguez unsigned int chan_idx, 12911fa25e41SLuis R. Rodriguez const struct ieee80211_regdomain *regd) 12921fa25e41SLuis R. Rodriguez { 12931fa25e41SLuis R. Rodriguez int r; 1294038659e7SLuis R. Rodriguez u32 desired_bw_khz = MHZ_TO_KHZ(20); 1295038659e7SLuis R. Rodriguez u32 bw_flags = 0; 12961fa25e41SLuis R. Rodriguez const struct ieee80211_reg_rule *reg_rule = NULL; 12971fa25e41SLuis R. Rodriguez const struct ieee80211_power_rule *power_rule = NULL; 1298038659e7SLuis R. Rodriguez const struct ieee80211_freq_range *freq_range = NULL; 12991fa25e41SLuis R. Rodriguez struct ieee80211_supported_band *sband; 13001fa25e41SLuis R. Rodriguez struct ieee80211_channel *chan; 13011fa25e41SLuis R. Rodriguez 1302abc7381bSLuis R. Rodriguez assert_reg_lock(); 1303ac46d48eSLuis R. Rodriguez 13041fa25e41SLuis R. Rodriguez sband = wiphy->bands[band]; 13051fa25e41SLuis R. Rodriguez BUG_ON(chan_idx >= sband->n_channels); 13061fa25e41SLuis R. Rodriguez chan = &sband->channels[chan_idx]; 13071fa25e41SLuis R. Rodriguez 1308038659e7SLuis R. Rodriguez r = freq_reg_info_regd(wiphy, 1309038659e7SLuis R. Rodriguez MHZ_TO_KHZ(chan->center_freq), 1310038659e7SLuis R. Rodriguez desired_bw_khz, 1311038659e7SLuis R. Rodriguez ®_rule, 1312038659e7SLuis R. Rodriguez regd); 13131fa25e41SLuis R. Rodriguez 13141fa25e41SLuis R. Rodriguez if (r) { 1315d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Disabling freq %d MHz as custom " 1316a6518536SLuis R. Rodriguez "regd has no rule that fits a %d MHz " 1317a6518536SLuis R. Rodriguez "wide channel\n", 1318a6518536SLuis R. Rodriguez chan->center_freq, 1319a6518536SLuis R. Rodriguez KHZ_TO_MHZ(desired_bw_khz)); 13201fa25e41SLuis R. Rodriguez chan->flags = IEEE80211_CHAN_DISABLED; 13211fa25e41SLuis R. Rodriguez return; 13221fa25e41SLuis R. Rodriguez } 13231fa25e41SLuis R. Rodriguez 1324e702d3cfSLuis R. Rodriguez chan_reg_rule_print_dbg(chan, desired_bw_khz, reg_rule); 1325e702d3cfSLuis R. Rodriguez 13261fa25e41SLuis R. Rodriguez power_rule = ®_rule->power_rule; 1327038659e7SLuis R. Rodriguez freq_range = ®_rule->freq_range; 13281fa25e41SLuis R. Rodriguez 1329038659e7SLuis R. Rodriguez if (freq_range->max_bandwidth_khz < MHZ_TO_KHZ(40)) 1330038659e7SLuis R. Rodriguez bw_flags = IEEE80211_CHAN_NO_HT40; 1331038659e7SLuis R. Rodriguez 1332038659e7SLuis R. Rodriguez chan->flags |= map_regdom_flags(reg_rule->flags) | bw_flags; 13331fa25e41SLuis R. Rodriguez chan->max_antenna_gain = (int) MBI_TO_DBI(power_rule->max_antenna_gain); 1334279f0f55SFelix Fietkau chan->max_reg_power = chan->max_power = 1335279f0f55SFelix Fietkau (int) MBM_TO_DBM(power_rule->max_eirp); 13361fa25e41SLuis R. Rodriguez } 13371fa25e41SLuis R. Rodriguez 13381fa25e41SLuis R. Rodriguez static void handle_band_custom(struct wiphy *wiphy, enum ieee80211_band band, 13391fa25e41SLuis R. Rodriguez const struct ieee80211_regdomain *regd) 13401fa25e41SLuis R. Rodriguez { 13411fa25e41SLuis R. Rodriguez unsigned int i; 13421fa25e41SLuis R. Rodriguez struct ieee80211_supported_band *sband; 13431fa25e41SLuis R. Rodriguez 13441fa25e41SLuis R. Rodriguez BUG_ON(!wiphy->bands[band]); 13451fa25e41SLuis R. Rodriguez sband = wiphy->bands[band]; 13461fa25e41SLuis R. Rodriguez 13471fa25e41SLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 13481fa25e41SLuis R. Rodriguez handle_channel_custom(wiphy, band, i, regd); 13491fa25e41SLuis R. Rodriguez } 13501fa25e41SLuis R. Rodriguez 13511fa25e41SLuis R. Rodriguez /* Used by drivers prior to wiphy registration */ 13521fa25e41SLuis R. Rodriguez void wiphy_apply_custom_regulatory(struct wiphy *wiphy, 13531fa25e41SLuis R. Rodriguez const struct ieee80211_regdomain *regd) 13541fa25e41SLuis R. Rodriguez { 13551fa25e41SLuis R. Rodriguez enum ieee80211_band band; 1356bbcf3f02SLuis R. Rodriguez unsigned int bands_set = 0; 1357ac46d48eSLuis R. Rodriguez 1358abc7381bSLuis R. Rodriguez mutex_lock(®_mutex); 13591fa25e41SLuis R. Rodriguez for (band = 0; band < IEEE80211_NUM_BANDS; band++) { 1360bbcf3f02SLuis R. Rodriguez if (!wiphy->bands[band]) 1361bbcf3f02SLuis R. Rodriguez continue; 13621fa25e41SLuis R. Rodriguez handle_band_custom(wiphy, band, regd); 1363bbcf3f02SLuis R. Rodriguez bands_set++; 13641fa25e41SLuis R. Rodriguez } 1365abc7381bSLuis R. Rodriguez mutex_unlock(®_mutex); 1366bbcf3f02SLuis R. Rodriguez 1367bbcf3f02SLuis R. Rodriguez /* 1368bbcf3f02SLuis R. Rodriguez * no point in calling this if it won't have any effect 1369bbcf3f02SLuis R. Rodriguez * on your device's supportd bands. 1370bbcf3f02SLuis R. Rodriguez */ 1371bbcf3f02SLuis R. Rodriguez WARN_ON(!bands_set); 13721fa25e41SLuis R. Rodriguez } 13731fa25e41SLuis R. Rodriguez EXPORT_SYMBOL(wiphy_apply_custom_regulatory); 13741fa25e41SLuis R. Rodriguez 1375fb1fc7adSLuis R. Rodriguez /* 1376fb1fc7adSLuis R. Rodriguez * Return value which can be used by ignore_request() to indicate 1377fb1fc7adSLuis R. Rodriguez * it has been determined we should intersect two regulatory domains 1378fb1fc7adSLuis R. Rodriguez */ 13799c96477dSLuis R. Rodriguez #define REG_INTERSECT 1 13809c96477dSLuis R. Rodriguez 138184fa4f43SJohannes Berg /* This has the logic which determines when a new request 138284fa4f43SJohannes Berg * should be ignored. */ 13832f92cd2eSLuis R. Rodriguez static int ignore_request(struct wiphy *wiphy, 13842f92cd2eSLuis R. Rodriguez struct regulatory_request *pending_request) 138584fa4f43SJohannes Berg { 1386806a9e39SLuis R. Rodriguez struct wiphy *last_wiphy = NULL; 1387761cf7ecSLuis R. Rodriguez 1388761cf7ecSLuis R. Rodriguez assert_cfg80211_lock(); 1389761cf7ecSLuis R. Rodriguez 139084fa4f43SJohannes Berg /* All initial requests are respected */ 139184fa4f43SJohannes Berg if (!last_request) 139284fa4f43SJohannes Berg return 0; 139384fa4f43SJohannes Berg 13942f92cd2eSLuis R. Rodriguez switch (pending_request->initiator) { 13957db90f4aSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_CORE: 139609d989d1SLuis R. Rodriguez return 0; 13977db90f4aSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_COUNTRY_IE: 1398806a9e39SLuis R. Rodriguez 139957b5ce07SLuis R. Rodriguez if (reg_request_cell_base(last_request)) { 140057b5ce07SLuis R. Rodriguez /* Trust a Cell base station over the AP's country IE */ 140157b5ce07SLuis R. Rodriguez if (regdom_changes(pending_request->alpha2)) 140257b5ce07SLuis R. Rodriguez return -EOPNOTSUPP; 140357b5ce07SLuis R. Rodriguez return -EALREADY; 140457b5ce07SLuis R. Rodriguez } 140557b5ce07SLuis R. Rodriguez 1406806a9e39SLuis R. Rodriguez last_wiphy = wiphy_idx_to_wiphy(last_request->wiphy_idx); 1407806a9e39SLuis R. Rodriguez 14082f92cd2eSLuis R. Rodriguez if (unlikely(!is_an_alpha2(pending_request->alpha2))) 140984fa4f43SJohannes Berg return -EINVAL; 14107db90f4aSLuis R. Rodriguez if (last_request->initiator == 14117db90f4aSLuis R. Rodriguez NL80211_REGDOM_SET_BY_COUNTRY_IE) { 1412806a9e39SLuis R. Rodriguez if (last_wiphy != wiphy) { 141384fa4f43SJohannes Berg /* 141484fa4f43SJohannes Berg * Two cards with two APs claiming different 14151fe90b03SThadeu Lima de Souza Cascardo * Country IE alpha2s. We could 141684fa4f43SJohannes Berg * intersect them, but that seems unlikely 141784fa4f43SJohannes Berg * to be correct. Reject second one for now. 141884fa4f43SJohannes Berg */ 14192f92cd2eSLuis R. Rodriguez if (regdom_changes(pending_request->alpha2)) 142084fa4f43SJohannes Berg return -EOPNOTSUPP; 142184fa4f43SJohannes Berg return -EALREADY; 142284fa4f43SJohannes Berg } 1423fb1fc7adSLuis R. Rodriguez /* 1424fb1fc7adSLuis R. Rodriguez * Two consecutive Country IE hints on the same wiphy. 1425fb1fc7adSLuis R. Rodriguez * This should be picked up early by the driver/stack 1426fb1fc7adSLuis R. Rodriguez */ 14272f92cd2eSLuis R. Rodriguez if (WARN_ON(regdom_changes(pending_request->alpha2))) 142884fa4f43SJohannes Berg return 0; 142984fa4f43SJohannes Berg return -EALREADY; 143084fa4f43SJohannes Berg } 1431a171fba4SLuis R. Rodriguez return 0; 14327db90f4aSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_DRIVER: 14337db90f4aSLuis R. Rodriguez if (last_request->initiator == NL80211_REGDOM_SET_BY_CORE) { 14342f92cd2eSLuis R. Rodriguez if (regdom_changes(pending_request->alpha2)) 1435e74b1e7fSLuis R. Rodriguez return 0; 1436e74b1e7fSLuis R. Rodriguez return -EALREADY; 1437e74b1e7fSLuis R. Rodriguez } 1438fff32c04SLuis R. Rodriguez 1439fff32c04SLuis R. Rodriguez /* 1440fff32c04SLuis R. Rodriguez * This would happen if you unplug and plug your card 1441fff32c04SLuis R. Rodriguez * back in or if you add a new device for which the previously 1442fff32c04SLuis R. Rodriguez * loaded card also agrees on the regulatory domain. 1443fff32c04SLuis R. Rodriguez */ 14447db90f4aSLuis R. Rodriguez if (last_request->initiator == NL80211_REGDOM_SET_BY_DRIVER && 14452f92cd2eSLuis R. Rodriguez !regdom_changes(pending_request->alpha2)) 1446fff32c04SLuis R. Rodriguez return -EALREADY; 1447fff32c04SLuis R. Rodriguez 14483e0c3ff3SLuis R. Rodriguez return REG_INTERSECT; 14497db90f4aSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_USER: 145057b5ce07SLuis R. Rodriguez if (reg_request_cell_base(pending_request)) 145157b5ce07SLuis R. Rodriguez return reg_ignore_cell_hint(pending_request); 145257b5ce07SLuis R. Rodriguez 145357b5ce07SLuis R. Rodriguez if (reg_request_cell_base(last_request)) 145457b5ce07SLuis R. Rodriguez return -EOPNOTSUPP; 145557b5ce07SLuis R. Rodriguez 14567db90f4aSLuis R. Rodriguez if (last_request->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE) 14579c96477dSLuis R. Rodriguez return REG_INTERSECT; 1458fb1fc7adSLuis R. Rodriguez /* 1459fb1fc7adSLuis R. Rodriguez * If the user knows better the user should set the regdom 1460fb1fc7adSLuis R. Rodriguez * to their country before the IE is picked up 1461fb1fc7adSLuis R. Rodriguez */ 14627db90f4aSLuis R. Rodriguez if (last_request->initiator == NL80211_REGDOM_SET_BY_USER && 14633f2355cbSLuis R. Rodriguez last_request->intersect) 14643f2355cbSLuis R. Rodriguez return -EOPNOTSUPP; 1465fb1fc7adSLuis R. Rodriguez /* 1466fb1fc7adSLuis R. Rodriguez * Process user requests only after previous user/driver/core 1467fb1fc7adSLuis R. Rodriguez * requests have been processed 1468fb1fc7adSLuis R. Rodriguez */ 14697db90f4aSLuis R. Rodriguez if (last_request->initiator == NL80211_REGDOM_SET_BY_CORE || 14707db90f4aSLuis R. Rodriguez last_request->initiator == NL80211_REGDOM_SET_BY_DRIVER || 14717db90f4aSLuis R. Rodriguez last_request->initiator == NL80211_REGDOM_SET_BY_USER) { 147269b1572bSLuis R. Rodriguez if (regdom_changes(last_request->alpha2)) 14735eebade6SLuis R. Rodriguez return -EAGAIN; 14745eebade6SLuis R. Rodriguez } 14755eebade6SLuis R. Rodriguez 1476baeb66feSJohn W. Linville if (!regdom_changes(pending_request->alpha2)) 1477e74b1e7fSLuis R. Rodriguez return -EALREADY; 1478e74b1e7fSLuis R. Rodriguez 147984fa4f43SJohannes Berg return 0; 148084fa4f43SJohannes Berg } 148184fa4f43SJohannes Berg 148284fa4f43SJohannes Berg return -EINVAL; 148384fa4f43SJohannes Berg } 148484fa4f43SJohannes Berg 1485b2e253cfSLuis R. Rodriguez static void reg_set_request_processed(void) 1486b2e253cfSLuis R. Rodriguez { 1487b2e253cfSLuis R. Rodriguez bool need_more_processing = false; 1488b2e253cfSLuis R. Rodriguez 1489b2e253cfSLuis R. Rodriguez last_request->processed = true; 1490b2e253cfSLuis R. Rodriguez 1491b2e253cfSLuis R. Rodriguez spin_lock(®_requests_lock); 1492b2e253cfSLuis R. Rodriguez if (!list_empty(®_requests_list)) 1493b2e253cfSLuis R. Rodriguez need_more_processing = true; 1494b2e253cfSLuis R. Rodriguez spin_unlock(®_requests_lock); 1495b2e253cfSLuis R. Rodriguez 1496a90c7a31SLuis R. Rodriguez if (last_request->initiator == NL80211_REGDOM_SET_BY_USER) 1497fe20b39eSEliad Peller cancel_delayed_work(®_timeout); 1498a90c7a31SLuis R. Rodriguez 1499b2e253cfSLuis R. Rodriguez if (need_more_processing) 1500b2e253cfSLuis R. Rodriguez schedule_work(®_work); 1501b2e253cfSLuis R. Rodriguez } 1502b2e253cfSLuis R. Rodriguez 1503d1c96a9aSLuis R. Rodriguez /** 1504d1c96a9aSLuis R. Rodriguez * __regulatory_hint - hint to the wireless core a regulatory domain 1505d1c96a9aSLuis R. Rodriguez * @wiphy: if the hint comes from country information from an AP, this 1506d1c96a9aSLuis R. Rodriguez * is required to be set to the wiphy that received the information 150728da32d7SLuis R. Rodriguez * @pending_request: the regulatory request currently being processed 1508d1c96a9aSLuis R. Rodriguez * 1509d1c96a9aSLuis R. Rodriguez * The Wireless subsystem can use this function to hint to the wireless core 151028da32d7SLuis R. Rodriguez * what it believes should be the current regulatory domain. 1511d1c96a9aSLuis R. Rodriguez * 1512d1c96a9aSLuis R. Rodriguez * Returns zero if all went fine, %-EALREADY if a regulatory domain had 1513d1c96a9aSLuis R. Rodriguez * already been set or other standard error codes. 1514d1c96a9aSLuis R. Rodriguez * 1515abc7381bSLuis R. Rodriguez * Caller must hold &cfg80211_mutex and ®_mutex 1516d1c96a9aSLuis R. Rodriguez */ 151728da32d7SLuis R. Rodriguez static int __regulatory_hint(struct wiphy *wiphy, 151828da32d7SLuis R. Rodriguez struct regulatory_request *pending_request) 1519b2e1b302SLuis R. Rodriguez { 15209c96477dSLuis R. Rodriguez bool intersect = false; 1521b2e1b302SLuis R. Rodriguez int r = 0; 1522b2e1b302SLuis R. Rodriguez 1523761cf7ecSLuis R. Rodriguez assert_cfg80211_lock(); 1524761cf7ecSLuis R. Rodriguez 15252f92cd2eSLuis R. Rodriguez r = ignore_request(wiphy, pending_request); 15269c96477dSLuis R. Rodriguez 15273e0c3ff3SLuis R. Rodriguez if (r == REG_INTERSECT) { 15287db90f4aSLuis R. Rodriguez if (pending_request->initiator == 15297db90f4aSLuis R. Rodriguez NL80211_REGDOM_SET_BY_DRIVER) { 15303e0c3ff3SLuis R. Rodriguez r = reg_copy_regd(&wiphy->regd, cfg80211_regdomain); 1531d951c1ddSLuis R. Rodriguez if (r) { 1532d951c1ddSLuis R. Rodriguez kfree(pending_request); 1533b2e1b302SLuis R. Rodriguez return r; 15343e0c3ff3SLuis R. Rodriguez } 1535d951c1ddSLuis R. Rodriguez } 15363e0c3ff3SLuis R. Rodriguez intersect = true; 15373e0c3ff3SLuis R. Rodriguez } else if (r) { 1538fb1fc7adSLuis R. Rodriguez /* 1539fb1fc7adSLuis R. Rodriguez * If the regulatory domain being requested by the 15403e0c3ff3SLuis R. Rodriguez * driver has already been set just copy it to the 1541fb1fc7adSLuis R. Rodriguez * wiphy 1542fb1fc7adSLuis R. Rodriguez */ 154328da32d7SLuis R. Rodriguez if (r == -EALREADY && 15447db90f4aSLuis R. Rodriguez pending_request->initiator == 15457db90f4aSLuis R. Rodriguez NL80211_REGDOM_SET_BY_DRIVER) { 15463e0c3ff3SLuis R. Rodriguez r = reg_copy_regd(&wiphy->regd, cfg80211_regdomain); 1547d951c1ddSLuis R. Rodriguez if (r) { 1548d951c1ddSLuis R. Rodriguez kfree(pending_request); 15493e0c3ff3SLuis R. Rodriguez return r; 1550d951c1ddSLuis R. Rodriguez } 15513e0c3ff3SLuis R. Rodriguez r = -EALREADY; 15523e0c3ff3SLuis R. Rodriguez goto new_request; 15533e0c3ff3SLuis R. Rodriguez } 1554d951c1ddSLuis R. Rodriguez kfree(pending_request); 15553e0c3ff3SLuis R. Rodriguez return r; 15563e0c3ff3SLuis R. Rodriguez } 1557b2e1b302SLuis R. Rodriguez 15583e0c3ff3SLuis R. Rodriguez new_request: 1559a042994dSLuis R. Rodriguez if (last_request != &core_request_world) 1560f6037d09SJohannes Berg kfree(last_request); 1561d951c1ddSLuis R. Rodriguez 1562d951c1ddSLuis R. Rodriguez last_request = pending_request; 1563d951c1ddSLuis R. Rodriguez last_request->intersect = intersect; 1564d951c1ddSLuis R. Rodriguez 1565d951c1ddSLuis R. Rodriguez pending_request = NULL; 15663e0c3ff3SLuis R. Rodriguez 156709d989d1SLuis R. Rodriguez if (last_request->initiator == NL80211_REGDOM_SET_BY_USER) { 156809d989d1SLuis R. Rodriguez user_alpha2[0] = last_request->alpha2[0]; 156909d989d1SLuis R. Rodriguez user_alpha2[1] = last_request->alpha2[1]; 157009d989d1SLuis R. Rodriguez } 157109d989d1SLuis R. Rodriguez 15723e0c3ff3SLuis R. Rodriguez /* When r == REG_INTERSECT we do need to call CRDA */ 157373d54c9eSLuis R. Rodriguez if (r < 0) { 157473d54c9eSLuis R. Rodriguez /* 157573d54c9eSLuis R. Rodriguez * Since CRDA will not be called in this case as we already 157673d54c9eSLuis R. Rodriguez * have applied the requested regulatory domain before we just 157773d54c9eSLuis R. Rodriguez * inform userspace we have processed the request 157873d54c9eSLuis R. Rodriguez */ 1579b2e253cfSLuis R. Rodriguez if (r == -EALREADY) { 158073d54c9eSLuis R. Rodriguez nl80211_send_reg_change_event(last_request); 1581b2e253cfSLuis R. Rodriguez reg_set_request_processed(); 1582b2e253cfSLuis R. Rodriguez } 15833e0c3ff3SLuis R. Rodriguez return r; 158473d54c9eSLuis R. Rodriguez } 15853e0c3ff3SLuis R. Rodriguez 1586d951c1ddSLuis R. Rodriguez return call_crda(last_request->alpha2); 1587b2e1b302SLuis R. Rodriguez } 1588b2e1b302SLuis R. Rodriguez 158930a548c7SLuis R. Rodriguez /* This processes *all* regulatory hints */ 15908848bef0SLuis R. Rodriguez static void reg_process_hint(struct regulatory_request *reg_request, 15918848bef0SLuis R. Rodriguez enum nl80211_reg_initiator reg_initiator) 1592fe33eb39SLuis R. Rodriguez { 1593fe33eb39SLuis R. Rodriguez int r = 0; 1594fe33eb39SLuis R. Rodriguez struct wiphy *wiphy = NULL; 1595fe33eb39SLuis R. Rodriguez 1596fe33eb39SLuis R. Rodriguez BUG_ON(!reg_request->alpha2); 1597fe33eb39SLuis R. Rodriguez 1598fe33eb39SLuis R. Rodriguez if (wiphy_idx_valid(reg_request->wiphy_idx)) 1599fe33eb39SLuis R. Rodriguez wiphy = wiphy_idx_to_wiphy(reg_request->wiphy_idx); 1600fe33eb39SLuis R. Rodriguez 16018848bef0SLuis R. Rodriguez if (reg_initiator == NL80211_REGDOM_SET_BY_DRIVER && 1602fe33eb39SLuis R. Rodriguez !wiphy) { 1603d951c1ddSLuis R. Rodriguez kfree(reg_request); 1604b0e2880bSLuis R. Rodriguez return; 1605fe33eb39SLuis R. Rodriguez } 1606fe33eb39SLuis R. Rodriguez 160728da32d7SLuis R. Rodriguez r = __regulatory_hint(wiphy, reg_request); 1608fe33eb39SLuis R. Rodriguez /* This is required so that the orig_* parameters are saved */ 16095be83de5SJohannes Berg if (r == -EALREADY && wiphy && 1610a90c7a31SLuis R. Rodriguez wiphy->flags & WIPHY_FLAG_STRICT_REGULATORY) { 16118848bef0SLuis R. Rodriguez wiphy_update_regulatory(wiphy, reg_initiator); 1612a90c7a31SLuis R. Rodriguez return; 1613a90c7a31SLuis R. Rodriguez } 1614a90c7a31SLuis R. Rodriguez 1615a90c7a31SLuis R. Rodriguez /* 1616a90c7a31SLuis R. Rodriguez * We only time out user hints, given that they should be the only 1617a90c7a31SLuis R. Rodriguez * source of bogus requests. 1618a90c7a31SLuis R. Rodriguez */ 1619c989bb15SLuis R. Rodriguez if (r != -EALREADY && 16208848bef0SLuis R. Rodriguez reg_initiator == NL80211_REGDOM_SET_BY_USER) 1621a90c7a31SLuis R. Rodriguez schedule_delayed_work(®_timeout, msecs_to_jiffies(3142)); 1622fe33eb39SLuis R. Rodriguez } 1623fe33eb39SLuis R. Rodriguez 1624b2e253cfSLuis R. Rodriguez /* 1625b2e253cfSLuis R. Rodriguez * Processes regulatory hints, this is all the NL80211_REGDOM_SET_BY_* 1626b2e253cfSLuis R. Rodriguez * Regulatory hints come on a first come first serve basis and we 1627b2e253cfSLuis R. Rodriguez * must process each one atomically. 1628b2e253cfSLuis R. Rodriguez */ 1629fe33eb39SLuis R. Rodriguez static void reg_process_pending_hints(void) 1630fe33eb39SLuis R. Rodriguez { 1631fe33eb39SLuis R. Rodriguez struct regulatory_request *reg_request; 1632fe33eb39SLuis R. Rodriguez 1633b0e2880bSLuis R. Rodriguez mutex_lock(&cfg80211_mutex); 1634b0e2880bSLuis R. Rodriguez mutex_lock(®_mutex); 1635b0e2880bSLuis R. Rodriguez 1636b2e253cfSLuis R. Rodriguez /* When last_request->processed becomes true this will be rescheduled */ 1637b2e253cfSLuis R. Rodriguez if (last_request && !last_request->processed) { 1638b2e253cfSLuis R. Rodriguez REG_DBG_PRINT("Pending regulatory request, waiting " 163912c5ffb5SJoe Perches "for it to be processed...\n"); 1640b2e253cfSLuis R. Rodriguez goto out; 1641b2e253cfSLuis R. Rodriguez } 1642b2e253cfSLuis R. Rodriguez 1643fe33eb39SLuis R. Rodriguez spin_lock(®_requests_lock); 1644b2e253cfSLuis R. Rodriguez 1645b2e253cfSLuis R. Rodriguez if (list_empty(®_requests_list)) { 1646b2e253cfSLuis R. Rodriguez spin_unlock(®_requests_lock); 1647b2e253cfSLuis R. Rodriguez goto out; 1648b2e253cfSLuis R. Rodriguez } 1649b2e253cfSLuis R. Rodriguez 1650fe33eb39SLuis R. Rodriguez reg_request = list_first_entry(®_requests_list, 1651fe33eb39SLuis R. Rodriguez struct regulatory_request, 1652fe33eb39SLuis R. Rodriguez list); 1653fe33eb39SLuis R. Rodriguez list_del_init(®_request->list); 1654fe33eb39SLuis R. Rodriguez 1655d951c1ddSLuis R. Rodriguez spin_unlock(®_requests_lock); 1656b0e2880bSLuis R. Rodriguez 16578848bef0SLuis R. Rodriguez reg_process_hint(reg_request, reg_request->initiator); 1658b2e253cfSLuis R. Rodriguez 1659b2e253cfSLuis R. Rodriguez out: 1660b0e2880bSLuis R. Rodriguez mutex_unlock(®_mutex); 1661b0e2880bSLuis R. Rodriguez mutex_unlock(&cfg80211_mutex); 1662fe33eb39SLuis R. Rodriguez } 1663fe33eb39SLuis R. Rodriguez 1664e38f8a7aSLuis R. Rodriguez /* Processes beacon hints -- this has nothing to do with country IEs */ 1665e38f8a7aSLuis R. Rodriguez static void reg_process_pending_beacon_hints(void) 1666e38f8a7aSLuis R. Rodriguez { 166779c97e97SJohannes Berg struct cfg80211_registered_device *rdev; 1668e38f8a7aSLuis R. Rodriguez struct reg_beacon *pending_beacon, *tmp; 1669e38f8a7aSLuis R. Rodriguez 1670abc7381bSLuis R. Rodriguez /* 1671abc7381bSLuis R. Rodriguez * No need to hold the reg_mutex here as we just touch wiphys 1672abc7381bSLuis R. Rodriguez * and do not read or access regulatory variables. 1673abc7381bSLuis R. Rodriguez */ 1674e38f8a7aSLuis R. Rodriguez mutex_lock(&cfg80211_mutex); 1675e38f8a7aSLuis R. Rodriguez 1676e38f8a7aSLuis R. Rodriguez /* This goes through the _pending_ beacon list */ 1677e38f8a7aSLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 1678e38f8a7aSLuis R. Rodriguez 1679e38f8a7aSLuis R. Rodriguez if (list_empty(®_pending_beacons)) { 1680e38f8a7aSLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 1681e38f8a7aSLuis R. Rodriguez goto out; 1682e38f8a7aSLuis R. Rodriguez } 1683e38f8a7aSLuis R. Rodriguez 1684e38f8a7aSLuis R. Rodriguez list_for_each_entry_safe(pending_beacon, tmp, 1685e38f8a7aSLuis R. Rodriguez ®_pending_beacons, list) { 1686e38f8a7aSLuis R. Rodriguez 1687e38f8a7aSLuis R. Rodriguez list_del_init(&pending_beacon->list); 1688e38f8a7aSLuis R. Rodriguez 1689e38f8a7aSLuis R. Rodriguez /* Applies the beacon hint to current wiphys */ 169079c97e97SJohannes Berg list_for_each_entry(rdev, &cfg80211_rdev_list, list) 169179c97e97SJohannes Berg wiphy_update_new_beacon(&rdev->wiphy, pending_beacon); 1692e38f8a7aSLuis R. Rodriguez 1693e38f8a7aSLuis R. Rodriguez /* Remembers the beacon hint for new wiphys or reg changes */ 1694e38f8a7aSLuis R. Rodriguez list_add_tail(&pending_beacon->list, ®_beacon_list); 1695e38f8a7aSLuis R. Rodriguez } 1696e38f8a7aSLuis R. Rodriguez 1697e38f8a7aSLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 1698e38f8a7aSLuis R. Rodriguez out: 1699e38f8a7aSLuis R. Rodriguez mutex_unlock(&cfg80211_mutex); 1700e38f8a7aSLuis R. Rodriguez } 1701e38f8a7aSLuis R. Rodriguez 1702fe33eb39SLuis R. Rodriguez static void reg_todo(struct work_struct *work) 1703fe33eb39SLuis R. Rodriguez { 1704fe33eb39SLuis R. Rodriguez reg_process_pending_hints(); 1705e38f8a7aSLuis R. Rodriguez reg_process_pending_beacon_hints(); 1706fe33eb39SLuis R. Rodriguez } 1707fe33eb39SLuis R. Rodriguez 1708fe33eb39SLuis R. Rodriguez static void queue_regulatory_request(struct regulatory_request *request) 1709fe33eb39SLuis R. Rodriguez { 1710c61029c7SJohn W. Linville if (isalpha(request->alpha2[0])) 1711c61029c7SJohn W. Linville request->alpha2[0] = toupper(request->alpha2[0]); 1712c61029c7SJohn W. Linville if (isalpha(request->alpha2[1])) 1713c61029c7SJohn W. Linville request->alpha2[1] = toupper(request->alpha2[1]); 1714c61029c7SJohn W. Linville 1715fe33eb39SLuis R. Rodriguez spin_lock(®_requests_lock); 1716fe33eb39SLuis R. Rodriguez list_add_tail(&request->list, ®_requests_list); 1717fe33eb39SLuis R. Rodriguez spin_unlock(®_requests_lock); 1718fe33eb39SLuis R. Rodriguez 1719fe33eb39SLuis R. Rodriguez schedule_work(®_work); 1720fe33eb39SLuis R. Rodriguez } 1721fe33eb39SLuis R. Rodriguez 172209d989d1SLuis R. Rodriguez /* 172309d989d1SLuis R. Rodriguez * Core regulatory hint -- happens during cfg80211_init() 172409d989d1SLuis R. Rodriguez * and when we restore regulatory settings. 172509d989d1SLuis R. Rodriguez */ 1726ba25c141SLuis R. Rodriguez static int regulatory_hint_core(const char *alpha2) 1727ba25c141SLuis R. Rodriguez { 1728ba25c141SLuis R. Rodriguez struct regulatory_request *request; 1729ba25c141SLuis R. Rodriguez 1730ba25c141SLuis R. Rodriguez request = kzalloc(sizeof(struct regulatory_request), 1731ba25c141SLuis R. Rodriguez GFP_KERNEL); 1732ba25c141SLuis R. Rodriguez if (!request) 1733ba25c141SLuis R. Rodriguez return -ENOMEM; 1734ba25c141SLuis R. Rodriguez 1735ba25c141SLuis R. Rodriguez request->alpha2[0] = alpha2[0]; 1736ba25c141SLuis R. Rodriguez request->alpha2[1] = alpha2[1]; 17377db90f4aSLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_CORE; 1738ba25c141SLuis R. Rodriguez 173931e99729SLuis R. Rodriguez queue_regulatory_request(request); 17405078b2e3SLuis R. Rodriguez 1741fe33eb39SLuis R. Rodriguez return 0; 1742ba25c141SLuis R. Rodriguez } 1743ba25c141SLuis R. Rodriguez 1744fe33eb39SLuis R. Rodriguez /* User hints */ 174557b5ce07SLuis R. Rodriguez int regulatory_hint_user(const char *alpha2, 174657b5ce07SLuis R. Rodriguez enum nl80211_user_reg_hint_type user_reg_hint_type) 1747b2e1b302SLuis R. Rodriguez { 1748fe33eb39SLuis R. Rodriguez struct regulatory_request *request; 1749fe33eb39SLuis R. Rodriguez 1750be3d4810SJohannes Berg BUG_ON(!alpha2); 1751b2e1b302SLuis R. Rodriguez 1752fe33eb39SLuis R. Rodriguez request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL); 1753fe33eb39SLuis R. Rodriguez if (!request) 1754fe33eb39SLuis R. Rodriguez return -ENOMEM; 1755fe33eb39SLuis R. Rodriguez 1756fe33eb39SLuis R. Rodriguez request->wiphy_idx = WIPHY_IDX_STALE; 1757fe33eb39SLuis R. Rodriguez request->alpha2[0] = alpha2[0]; 1758fe33eb39SLuis R. Rodriguez request->alpha2[1] = alpha2[1]; 1759e12822e1SLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_USER; 176057b5ce07SLuis R. Rodriguez request->user_reg_hint_type = user_reg_hint_type; 1761fe33eb39SLuis R. Rodriguez 1762fe33eb39SLuis R. Rodriguez queue_regulatory_request(request); 1763fe33eb39SLuis R. Rodriguez 1764fe33eb39SLuis R. Rodriguez return 0; 1765fe33eb39SLuis R. Rodriguez } 1766fe33eb39SLuis R. Rodriguez 1767fe33eb39SLuis R. Rodriguez /* Driver hints */ 1768fe33eb39SLuis R. Rodriguez int regulatory_hint(struct wiphy *wiphy, const char *alpha2) 1769fe33eb39SLuis R. Rodriguez { 1770fe33eb39SLuis R. Rodriguez struct regulatory_request *request; 1771fe33eb39SLuis R. Rodriguez 1772fe33eb39SLuis R. Rodriguez BUG_ON(!alpha2); 1773fe33eb39SLuis R. Rodriguez BUG_ON(!wiphy); 1774fe33eb39SLuis R. Rodriguez 1775fe33eb39SLuis R. Rodriguez request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL); 1776fe33eb39SLuis R. Rodriguez if (!request) 1777fe33eb39SLuis R. Rodriguez return -ENOMEM; 1778fe33eb39SLuis R. Rodriguez 1779fe33eb39SLuis R. Rodriguez request->wiphy_idx = get_wiphy_idx(wiphy); 1780fe33eb39SLuis R. Rodriguez 1781fe33eb39SLuis R. Rodriguez /* Must have registered wiphy first */ 1782fe33eb39SLuis R. Rodriguez BUG_ON(!wiphy_idx_valid(request->wiphy_idx)); 1783fe33eb39SLuis R. Rodriguez 1784fe33eb39SLuis R. Rodriguez request->alpha2[0] = alpha2[0]; 1785fe33eb39SLuis R. Rodriguez request->alpha2[1] = alpha2[1]; 17867db90f4aSLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_DRIVER; 1787fe33eb39SLuis R. Rodriguez 1788fe33eb39SLuis R. Rodriguez queue_regulatory_request(request); 1789fe33eb39SLuis R. Rodriguez 1790fe33eb39SLuis R. Rodriguez return 0; 1791b2e1b302SLuis R. Rodriguez } 1792b2e1b302SLuis R. Rodriguez EXPORT_SYMBOL(regulatory_hint); 1793b2e1b302SLuis R. Rodriguez 17944b44c8bcSLuis R. Rodriguez /* 17954b44c8bcSLuis R. Rodriguez * We hold wdev_lock() here so we cannot hold cfg80211_mutex() and 17964b44c8bcSLuis R. Rodriguez * therefore cannot iterate over the rdev list here. 17974b44c8bcSLuis R. Rodriguez */ 17983f2355cbSLuis R. Rodriguez void regulatory_hint_11d(struct wiphy *wiphy, 179984920e3eSLuis R. Rodriguez enum ieee80211_band band, 18009caf0364SJohannes Berg const u8 *country_ie, 18013f2355cbSLuis R. Rodriguez u8 country_ie_len) 18023f2355cbSLuis R. Rodriguez { 18033f2355cbSLuis R. Rodriguez char alpha2[2]; 18043f2355cbSLuis R. Rodriguez enum environment_cap env = ENVIRON_ANY; 1805fe33eb39SLuis R. Rodriguez struct regulatory_request *request; 18063f2355cbSLuis R. Rodriguez 1807abc7381bSLuis R. Rodriguez mutex_lock(®_mutex); 18083f2355cbSLuis R. Rodriguez 18099828b017SLuis R. Rodriguez if (unlikely(!last_request)) 18109828b017SLuis R. Rodriguez goto out; 1811d335fe63SLuis R. Rodriguez 18123f2355cbSLuis R. Rodriguez /* IE len must be evenly divisible by 2 */ 18133f2355cbSLuis R. Rodriguez if (country_ie_len & 0x01) 18143f2355cbSLuis R. Rodriguez goto out; 18153f2355cbSLuis R. Rodriguez 18163f2355cbSLuis R. Rodriguez if (country_ie_len < IEEE80211_COUNTRY_IE_MIN_LEN) 18173f2355cbSLuis R. Rodriguez goto out; 18183f2355cbSLuis R. Rodriguez 18193f2355cbSLuis R. Rodriguez alpha2[0] = country_ie[0]; 18203f2355cbSLuis R. Rodriguez alpha2[1] = country_ie[1]; 18213f2355cbSLuis R. Rodriguez 18223f2355cbSLuis R. Rodriguez if (country_ie[2] == 'I') 18233f2355cbSLuis R. Rodriguez env = ENVIRON_INDOOR; 18243f2355cbSLuis R. Rodriguez else if (country_ie[2] == 'O') 18253f2355cbSLuis R. Rodriguez env = ENVIRON_OUTDOOR; 18263f2355cbSLuis R. Rodriguez 1827fb1fc7adSLuis R. Rodriguez /* 18288b19e6caSLuis R. Rodriguez * We will run this only upon a successful connection on cfg80211. 18294b44c8bcSLuis R. Rodriguez * We leave conflict resolution to the workqueue, where can hold 18304b44c8bcSLuis R. Rodriguez * cfg80211_mutex. 1831fb1fc7adSLuis R. Rodriguez */ 1832cc0b6fe8SLuis R. Rodriguez if (likely(last_request->initiator == 1833cc0b6fe8SLuis R. Rodriguez NL80211_REGDOM_SET_BY_COUNTRY_IE && 18344b44c8bcSLuis R. Rodriguez wiphy_idx_valid(last_request->wiphy_idx))) 18353f2355cbSLuis R. Rodriguez goto out; 18363f2355cbSLuis R. Rodriguez 1837fe33eb39SLuis R. Rodriguez request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL); 1838fe33eb39SLuis R. Rodriguez if (!request) 1839f9f9b6e3SDan Carpenter goto out; 1840fe33eb39SLuis R. Rodriguez 1841fe33eb39SLuis R. Rodriguez request->wiphy_idx = get_wiphy_idx(wiphy); 18424f366c5dSJohn W. Linville request->alpha2[0] = alpha2[0]; 18434f366c5dSJohn W. Linville request->alpha2[1] = alpha2[1]; 18447db90f4aSLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_COUNTRY_IE; 1845fe33eb39SLuis R. Rodriguez request->country_ie_env = env; 18463f2355cbSLuis R. Rodriguez 1847abc7381bSLuis R. Rodriguez mutex_unlock(®_mutex); 1848fe33eb39SLuis R. Rodriguez 1849fe33eb39SLuis R. Rodriguez queue_regulatory_request(request); 1850fe33eb39SLuis R. Rodriguez 1851fe33eb39SLuis R. Rodriguez return; 18520441d6ffSLuis R. Rodriguez 18533f2355cbSLuis R. Rodriguez out: 1854abc7381bSLuis R. Rodriguez mutex_unlock(®_mutex); 18553f2355cbSLuis R. Rodriguez } 1856b2e1b302SLuis R. Rodriguez 185709d989d1SLuis R. Rodriguez static void restore_alpha2(char *alpha2, bool reset_user) 185809d989d1SLuis R. Rodriguez { 185909d989d1SLuis R. Rodriguez /* indicates there is no alpha2 to consider for restoration */ 186009d989d1SLuis R. Rodriguez alpha2[0] = '9'; 186109d989d1SLuis R. Rodriguez alpha2[1] = '7'; 186209d989d1SLuis R. Rodriguez 186309d989d1SLuis R. Rodriguez /* The user setting has precedence over the module parameter */ 186409d989d1SLuis R. Rodriguez if (is_user_regdom_saved()) { 186509d989d1SLuis R. Rodriguez /* Unless we're asked to ignore it and reset it */ 186609d989d1SLuis R. Rodriguez if (reset_user) { 1867d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Restoring regulatory settings " 186809d989d1SLuis R. Rodriguez "including user preference\n"); 186909d989d1SLuis R. Rodriguez user_alpha2[0] = '9'; 187009d989d1SLuis R. Rodriguez user_alpha2[1] = '7'; 187109d989d1SLuis R. Rodriguez 187209d989d1SLuis R. Rodriguez /* 187309d989d1SLuis R. Rodriguez * If we're ignoring user settings, we still need to 187409d989d1SLuis R. Rodriguez * check the module parameter to ensure we put things 187509d989d1SLuis R. Rodriguez * back as they were for a full restore. 187609d989d1SLuis R. Rodriguez */ 187709d989d1SLuis R. Rodriguez if (!is_world_regdom(ieee80211_regdom)) { 1878d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Keeping preference on " 187909d989d1SLuis R. Rodriguez "module parameter ieee80211_regdom: %c%c\n", 188009d989d1SLuis R. Rodriguez ieee80211_regdom[0], 188109d989d1SLuis R. Rodriguez ieee80211_regdom[1]); 188209d989d1SLuis R. Rodriguez alpha2[0] = ieee80211_regdom[0]; 188309d989d1SLuis R. Rodriguez alpha2[1] = ieee80211_regdom[1]; 188409d989d1SLuis R. Rodriguez } 188509d989d1SLuis R. Rodriguez } else { 1886d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Restoring regulatory settings " 188709d989d1SLuis R. Rodriguez "while preserving user preference for: %c%c\n", 188809d989d1SLuis R. Rodriguez user_alpha2[0], 188909d989d1SLuis R. Rodriguez user_alpha2[1]); 189009d989d1SLuis R. Rodriguez alpha2[0] = user_alpha2[0]; 189109d989d1SLuis R. Rodriguez alpha2[1] = user_alpha2[1]; 189209d989d1SLuis R. Rodriguez } 189309d989d1SLuis R. Rodriguez } else if (!is_world_regdom(ieee80211_regdom)) { 1894d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Keeping preference on " 189509d989d1SLuis R. Rodriguez "module parameter ieee80211_regdom: %c%c\n", 189609d989d1SLuis R. Rodriguez ieee80211_regdom[0], 189709d989d1SLuis R. Rodriguez ieee80211_regdom[1]); 189809d989d1SLuis R. Rodriguez alpha2[0] = ieee80211_regdom[0]; 189909d989d1SLuis R. Rodriguez alpha2[1] = ieee80211_regdom[1]; 190009d989d1SLuis R. Rodriguez } else 1901d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Restoring regulatory settings\n"); 190209d989d1SLuis R. Rodriguez } 190309d989d1SLuis R. Rodriguez 19045ce543d1SRajkumar Manoharan static void restore_custom_reg_settings(struct wiphy *wiphy) 19055ce543d1SRajkumar Manoharan { 19065ce543d1SRajkumar Manoharan struct ieee80211_supported_band *sband; 19075ce543d1SRajkumar Manoharan enum ieee80211_band band; 19085ce543d1SRajkumar Manoharan struct ieee80211_channel *chan; 19095ce543d1SRajkumar Manoharan int i; 19105ce543d1SRajkumar Manoharan 19115ce543d1SRajkumar Manoharan for (band = 0; band < IEEE80211_NUM_BANDS; band++) { 19125ce543d1SRajkumar Manoharan sband = wiphy->bands[band]; 19135ce543d1SRajkumar Manoharan if (!sband) 19145ce543d1SRajkumar Manoharan continue; 19155ce543d1SRajkumar Manoharan for (i = 0; i < sband->n_channels; i++) { 19165ce543d1SRajkumar Manoharan chan = &sband->channels[i]; 19175ce543d1SRajkumar Manoharan chan->flags = chan->orig_flags; 19185ce543d1SRajkumar Manoharan chan->max_antenna_gain = chan->orig_mag; 19195ce543d1SRajkumar Manoharan chan->max_power = chan->orig_mpwr; 1920899852afSPaul Stewart chan->beacon_found = false; 19215ce543d1SRajkumar Manoharan } 19225ce543d1SRajkumar Manoharan } 19235ce543d1SRajkumar Manoharan } 19245ce543d1SRajkumar Manoharan 192509d989d1SLuis R. Rodriguez /* 192609d989d1SLuis R. Rodriguez * Restoring regulatory settings involves ingoring any 192709d989d1SLuis R. Rodriguez * possibly stale country IE information and user regulatory 192809d989d1SLuis R. Rodriguez * settings if so desired, this includes any beacon hints 192909d989d1SLuis R. Rodriguez * learned as we could have traveled outside to another country 193009d989d1SLuis R. Rodriguez * after disconnection. To restore regulatory settings we do 193109d989d1SLuis R. Rodriguez * exactly what we did at bootup: 193209d989d1SLuis R. Rodriguez * 193309d989d1SLuis R. Rodriguez * - send a core regulatory hint 193409d989d1SLuis R. Rodriguez * - send a user regulatory hint if applicable 193509d989d1SLuis R. Rodriguez * 193609d989d1SLuis R. Rodriguez * Device drivers that send a regulatory hint for a specific country 193709d989d1SLuis R. Rodriguez * keep their own regulatory domain on wiphy->regd so that does does 193809d989d1SLuis R. Rodriguez * not need to be remembered. 193909d989d1SLuis R. Rodriguez */ 194009d989d1SLuis R. Rodriguez static void restore_regulatory_settings(bool reset_user) 194109d989d1SLuis R. Rodriguez { 194209d989d1SLuis R. Rodriguez char alpha2[2]; 1943cee0bec5SDmitry Shmidt char world_alpha2[2]; 194409d989d1SLuis R. Rodriguez struct reg_beacon *reg_beacon, *btmp; 194514609555SLuis R. Rodriguez struct regulatory_request *reg_request, *tmp; 194614609555SLuis R. Rodriguez LIST_HEAD(tmp_reg_req_list); 19475ce543d1SRajkumar Manoharan struct cfg80211_registered_device *rdev; 194809d989d1SLuis R. Rodriguez 194909d989d1SLuis R. Rodriguez mutex_lock(&cfg80211_mutex); 195009d989d1SLuis R. Rodriguez mutex_lock(®_mutex); 195109d989d1SLuis R. Rodriguez 1952a042994dSLuis R. Rodriguez reset_regdomains(true); 195309d989d1SLuis R. Rodriguez restore_alpha2(alpha2, reset_user); 195409d989d1SLuis R. Rodriguez 195514609555SLuis R. Rodriguez /* 195614609555SLuis R. Rodriguez * If there's any pending requests we simply 195714609555SLuis R. Rodriguez * stash them to a temporary pending queue and 195814609555SLuis R. Rodriguez * add then after we've restored regulatory 195914609555SLuis R. Rodriguez * settings. 196014609555SLuis R. Rodriguez */ 196114609555SLuis R. Rodriguez spin_lock(®_requests_lock); 196214609555SLuis R. Rodriguez if (!list_empty(®_requests_list)) { 196314609555SLuis R. Rodriguez list_for_each_entry_safe(reg_request, tmp, 196414609555SLuis R. Rodriguez ®_requests_list, list) { 196514609555SLuis R. Rodriguez if (reg_request->initiator != 196614609555SLuis R. Rodriguez NL80211_REGDOM_SET_BY_USER) 196714609555SLuis R. Rodriguez continue; 196800a9ac4cSWei Yongjun list_move_tail(®_request->list, &tmp_reg_req_list); 196914609555SLuis R. Rodriguez } 197014609555SLuis R. Rodriguez } 197114609555SLuis R. Rodriguez spin_unlock(®_requests_lock); 197214609555SLuis R. Rodriguez 197309d989d1SLuis R. Rodriguez /* Clear beacon hints */ 197409d989d1SLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 197509d989d1SLuis R. Rodriguez if (!list_empty(®_pending_beacons)) { 197609d989d1SLuis R. Rodriguez list_for_each_entry_safe(reg_beacon, btmp, 197709d989d1SLuis R. Rodriguez ®_pending_beacons, list) { 197809d989d1SLuis R. Rodriguez list_del(®_beacon->list); 197909d989d1SLuis R. Rodriguez kfree(reg_beacon); 198009d989d1SLuis R. Rodriguez } 198109d989d1SLuis R. Rodriguez } 198209d989d1SLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 198309d989d1SLuis R. Rodriguez 198409d989d1SLuis R. Rodriguez if (!list_empty(®_beacon_list)) { 198509d989d1SLuis R. Rodriguez list_for_each_entry_safe(reg_beacon, btmp, 198609d989d1SLuis R. Rodriguez ®_beacon_list, list) { 198709d989d1SLuis R. Rodriguez list_del(®_beacon->list); 198809d989d1SLuis R. Rodriguez kfree(reg_beacon); 198909d989d1SLuis R. Rodriguez } 199009d989d1SLuis R. Rodriguez } 199109d989d1SLuis R. Rodriguez 199209d989d1SLuis R. Rodriguez /* First restore to the basic regulatory settings */ 199309d989d1SLuis R. Rodriguez cfg80211_regdomain = cfg80211_world_regdom; 1994cee0bec5SDmitry Shmidt world_alpha2[0] = cfg80211_regdomain->alpha2[0]; 1995cee0bec5SDmitry Shmidt world_alpha2[1] = cfg80211_regdomain->alpha2[1]; 199609d989d1SLuis R. Rodriguez 19975ce543d1SRajkumar Manoharan list_for_each_entry(rdev, &cfg80211_rdev_list, list) { 19985ce543d1SRajkumar Manoharan if (rdev->wiphy.flags & WIPHY_FLAG_CUSTOM_REGULATORY) 19995ce543d1SRajkumar Manoharan restore_custom_reg_settings(&rdev->wiphy); 20005ce543d1SRajkumar Manoharan } 20015ce543d1SRajkumar Manoharan 200209d989d1SLuis R. Rodriguez mutex_unlock(®_mutex); 200309d989d1SLuis R. Rodriguez mutex_unlock(&cfg80211_mutex); 200409d989d1SLuis R. Rodriguez 2005cee0bec5SDmitry Shmidt regulatory_hint_core(world_alpha2); 200609d989d1SLuis R. Rodriguez 200709d989d1SLuis R. Rodriguez /* 200809d989d1SLuis R. Rodriguez * This restores the ieee80211_regdom module parameter 200909d989d1SLuis R. Rodriguez * preference or the last user requested regulatory 201009d989d1SLuis R. Rodriguez * settings, user regulatory settings takes precedence. 201109d989d1SLuis R. Rodriguez */ 201209d989d1SLuis R. Rodriguez if (is_an_alpha2(alpha2)) 201357b5ce07SLuis R. Rodriguez regulatory_hint_user(user_alpha2, NL80211_USER_REG_HINT_USER); 201409d989d1SLuis R. Rodriguez 201514609555SLuis R. Rodriguez if (list_empty(&tmp_reg_req_list)) 201614609555SLuis R. Rodriguez return; 201714609555SLuis R. Rodriguez 201814609555SLuis R. Rodriguez mutex_lock(&cfg80211_mutex); 201914609555SLuis R. Rodriguez mutex_lock(®_mutex); 202014609555SLuis R. Rodriguez 202114609555SLuis R. Rodriguez spin_lock(®_requests_lock); 202214609555SLuis R. Rodriguez list_for_each_entry_safe(reg_request, tmp, &tmp_reg_req_list, list) { 202314609555SLuis R. Rodriguez REG_DBG_PRINT("Adding request for country %c%c back " 202414609555SLuis R. Rodriguez "into the queue\n", 202514609555SLuis R. Rodriguez reg_request->alpha2[0], 202614609555SLuis R. Rodriguez reg_request->alpha2[1]); 202700a9ac4cSWei Yongjun list_move_tail(®_request->list, ®_requests_list); 202814609555SLuis R. Rodriguez } 202914609555SLuis R. Rodriguez spin_unlock(®_requests_lock); 203014609555SLuis R. Rodriguez 203114609555SLuis R. Rodriguez mutex_unlock(®_mutex); 203214609555SLuis R. Rodriguez mutex_unlock(&cfg80211_mutex); 203314609555SLuis R. Rodriguez 203414609555SLuis R. Rodriguez REG_DBG_PRINT("Kicking the queue\n"); 203514609555SLuis R. Rodriguez 203614609555SLuis R. Rodriguez schedule_work(®_work); 203714609555SLuis R. Rodriguez } 203809d989d1SLuis R. Rodriguez 203909d989d1SLuis R. Rodriguez void regulatory_hint_disconnect(void) 204009d989d1SLuis R. Rodriguez { 2041d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("All devices are disconnected, going to " 204209d989d1SLuis R. Rodriguez "restore regulatory settings\n"); 204309d989d1SLuis R. Rodriguez restore_regulatory_settings(false); 204409d989d1SLuis R. Rodriguez } 204509d989d1SLuis R. Rodriguez 2046e38f8a7aSLuis R. Rodriguez static bool freq_is_chan_12_13_14(u16 freq) 2047e38f8a7aSLuis R. Rodriguez { 204859eb21a6SBruno Randolf if (freq == ieee80211_channel_to_frequency(12, IEEE80211_BAND_2GHZ) || 204959eb21a6SBruno Randolf freq == ieee80211_channel_to_frequency(13, IEEE80211_BAND_2GHZ) || 205059eb21a6SBruno Randolf freq == ieee80211_channel_to_frequency(14, IEEE80211_BAND_2GHZ)) 2051e38f8a7aSLuis R. Rodriguez return true; 2052e38f8a7aSLuis R. Rodriguez return false; 2053e38f8a7aSLuis R. Rodriguez } 2054e38f8a7aSLuis R. Rodriguez 2055e38f8a7aSLuis R. Rodriguez int regulatory_hint_found_beacon(struct wiphy *wiphy, 2056e38f8a7aSLuis R. Rodriguez struct ieee80211_channel *beacon_chan, 2057e38f8a7aSLuis R. Rodriguez gfp_t gfp) 2058e38f8a7aSLuis R. Rodriguez { 2059e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon; 2060e38f8a7aSLuis R. Rodriguez 2061e38f8a7aSLuis R. Rodriguez if (likely((beacon_chan->beacon_found || 2062e38f8a7aSLuis R. Rodriguez (beacon_chan->flags & IEEE80211_CHAN_RADAR) || 2063e38f8a7aSLuis R. Rodriguez (beacon_chan->band == IEEE80211_BAND_2GHZ && 2064e38f8a7aSLuis R. Rodriguez !freq_is_chan_12_13_14(beacon_chan->center_freq))))) 2065e38f8a7aSLuis R. Rodriguez return 0; 2066e38f8a7aSLuis R. Rodriguez 2067e38f8a7aSLuis R. Rodriguez reg_beacon = kzalloc(sizeof(struct reg_beacon), gfp); 2068e38f8a7aSLuis R. Rodriguez if (!reg_beacon) 2069e38f8a7aSLuis R. Rodriguez return -ENOMEM; 2070e38f8a7aSLuis R. Rodriguez 2071d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Found new beacon on " 2072e38f8a7aSLuis R. Rodriguez "frequency: %d MHz (Ch %d) on %s\n", 2073e38f8a7aSLuis R. Rodriguez beacon_chan->center_freq, 2074e38f8a7aSLuis R. Rodriguez ieee80211_frequency_to_channel(beacon_chan->center_freq), 2075e38f8a7aSLuis R. Rodriguez wiphy_name(wiphy)); 20764113f751SLuis R. Rodriguez 2077e38f8a7aSLuis R. Rodriguez memcpy(®_beacon->chan, beacon_chan, 2078e38f8a7aSLuis R. Rodriguez sizeof(struct ieee80211_channel)); 2079e38f8a7aSLuis R. Rodriguez 2080e38f8a7aSLuis R. Rodriguez 2081e38f8a7aSLuis R. Rodriguez /* 2082e38f8a7aSLuis R. Rodriguez * Since we can be called from BH or and non-BH context 2083e38f8a7aSLuis R. Rodriguez * we must use spin_lock_bh() 2084e38f8a7aSLuis R. Rodriguez */ 2085e38f8a7aSLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 2086e38f8a7aSLuis R. Rodriguez list_add_tail(®_beacon->list, ®_pending_beacons); 2087e38f8a7aSLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 2088e38f8a7aSLuis R. Rodriguez 2089e38f8a7aSLuis R. Rodriguez schedule_work(®_work); 2090e38f8a7aSLuis R. Rodriguez 2091e38f8a7aSLuis R. Rodriguez return 0; 2092e38f8a7aSLuis R. Rodriguez } 2093e38f8a7aSLuis R. Rodriguez 2094a3d2eaf0SJohannes Berg static void print_rd_rules(const struct ieee80211_regdomain *rd) 2095b2e1b302SLuis R. Rodriguez { 2096b2e1b302SLuis R. Rodriguez unsigned int i; 2097a3d2eaf0SJohannes Berg const struct ieee80211_reg_rule *reg_rule = NULL; 2098a3d2eaf0SJohannes Berg const struct ieee80211_freq_range *freq_range = NULL; 2099a3d2eaf0SJohannes Berg const struct ieee80211_power_rule *power_rule = NULL; 2100b2e1b302SLuis R. Rodriguez 2101e9c0268fSJoe Perches pr_info(" (start_freq - end_freq @ bandwidth), (max_antenna_gain, max_eirp)\n"); 2102b2e1b302SLuis R. Rodriguez 2103b2e1b302SLuis R. Rodriguez for (i = 0; i < rd->n_reg_rules; i++) { 2104b2e1b302SLuis R. Rodriguez reg_rule = &rd->reg_rules[i]; 2105b2e1b302SLuis R. Rodriguez freq_range = ®_rule->freq_range; 2106b2e1b302SLuis R. Rodriguez power_rule = ®_rule->power_rule; 2107b2e1b302SLuis R. Rodriguez 2108fb1fc7adSLuis R. Rodriguez /* 2109fb1fc7adSLuis R. Rodriguez * There may not be documentation for max antenna gain 2110fb1fc7adSLuis R. Rodriguez * in certain regions 2111fb1fc7adSLuis R. Rodriguez */ 2112b2e1b302SLuis R. Rodriguez if (power_rule->max_antenna_gain) 2113e9c0268fSJoe Perches pr_info(" (%d KHz - %d KHz @ %d KHz), (%d mBi, %d mBm)\n", 2114b2e1b302SLuis R. Rodriguez freq_range->start_freq_khz, 2115b2e1b302SLuis R. Rodriguez freq_range->end_freq_khz, 2116b2e1b302SLuis R. Rodriguez freq_range->max_bandwidth_khz, 2117b2e1b302SLuis R. Rodriguez power_rule->max_antenna_gain, 2118b2e1b302SLuis R. Rodriguez power_rule->max_eirp); 2119b2e1b302SLuis R. Rodriguez else 2120e9c0268fSJoe Perches pr_info(" (%d KHz - %d KHz @ %d KHz), (N/A, %d mBm)\n", 2121b2e1b302SLuis R. Rodriguez freq_range->start_freq_khz, 2122b2e1b302SLuis R. Rodriguez freq_range->end_freq_khz, 2123b2e1b302SLuis R. Rodriguez freq_range->max_bandwidth_khz, 2124b2e1b302SLuis R. Rodriguez power_rule->max_eirp); 2125b2e1b302SLuis R. Rodriguez } 2126b2e1b302SLuis R. Rodriguez } 2127b2e1b302SLuis R. Rodriguez 21288b60b078SLuis R. Rodriguez bool reg_supported_dfs_region(u8 dfs_region) 21298b60b078SLuis R. Rodriguez { 21308b60b078SLuis R. Rodriguez switch (dfs_region) { 21318b60b078SLuis R. Rodriguez case NL80211_DFS_UNSET: 21328b60b078SLuis R. Rodriguez case NL80211_DFS_FCC: 21338b60b078SLuis R. Rodriguez case NL80211_DFS_ETSI: 21348b60b078SLuis R. Rodriguez case NL80211_DFS_JP: 21358b60b078SLuis R. Rodriguez return true; 21368b60b078SLuis R. Rodriguez default: 21378b60b078SLuis R. Rodriguez REG_DBG_PRINT("Ignoring uknown DFS master region: %d\n", 21388b60b078SLuis R. Rodriguez dfs_region); 21398b60b078SLuis R. Rodriguez return false; 21408b60b078SLuis R. Rodriguez } 21418b60b078SLuis R. Rodriguez } 21428b60b078SLuis R. Rodriguez 21438b60b078SLuis R. Rodriguez static void print_dfs_region(u8 dfs_region) 21448b60b078SLuis R. Rodriguez { 21458b60b078SLuis R. Rodriguez if (!dfs_region) 21468b60b078SLuis R. Rodriguez return; 21478b60b078SLuis R. Rodriguez 21488b60b078SLuis R. Rodriguez switch (dfs_region) { 21498b60b078SLuis R. Rodriguez case NL80211_DFS_FCC: 21508b60b078SLuis R. Rodriguez pr_info(" DFS Master region FCC"); 21518b60b078SLuis R. Rodriguez break; 21528b60b078SLuis R. Rodriguez case NL80211_DFS_ETSI: 21538b60b078SLuis R. Rodriguez pr_info(" DFS Master region ETSI"); 21548b60b078SLuis R. Rodriguez break; 21558b60b078SLuis R. Rodriguez case NL80211_DFS_JP: 21568b60b078SLuis R. Rodriguez pr_info(" DFS Master region JP"); 21578b60b078SLuis R. Rodriguez break; 21588b60b078SLuis R. Rodriguez default: 21598b60b078SLuis R. Rodriguez pr_info(" DFS Master region Uknown"); 21608b60b078SLuis R. Rodriguez break; 21618b60b078SLuis R. Rodriguez } 21628b60b078SLuis R. Rodriguez } 21638b60b078SLuis R. Rodriguez 2164a3d2eaf0SJohannes Berg static void print_regdomain(const struct ieee80211_regdomain *rd) 2165b2e1b302SLuis R. Rodriguez { 2166b2e1b302SLuis R. Rodriguez 21673f2355cbSLuis R. Rodriguez if (is_intersected_alpha2(rd->alpha2)) { 21683f2355cbSLuis R. Rodriguez 21697db90f4aSLuis R. Rodriguez if (last_request->initiator == 21707db90f4aSLuis R. Rodriguez NL80211_REGDOM_SET_BY_COUNTRY_IE) { 217179c97e97SJohannes Berg struct cfg80211_registered_device *rdev; 217279c97e97SJohannes Berg rdev = cfg80211_rdev_by_wiphy_idx( 2173806a9e39SLuis R. Rodriguez last_request->wiphy_idx); 217479c97e97SJohannes Berg if (rdev) { 2175e9c0268fSJoe Perches pr_info("Current regulatory domain updated by AP to: %c%c\n", 217679c97e97SJohannes Berg rdev->country_ie_alpha2[0], 217779c97e97SJohannes Berg rdev->country_ie_alpha2[1]); 21783f2355cbSLuis R. Rodriguez } else 2179e9c0268fSJoe Perches pr_info("Current regulatory domain intersected:\n"); 21803f2355cbSLuis R. Rodriguez } else 2181e9c0268fSJoe Perches pr_info("Current regulatory domain intersected:\n"); 21823f2355cbSLuis R. Rodriguez } else if (is_world_regdom(rd->alpha2)) 2183e9c0268fSJoe Perches pr_info("World regulatory domain updated:\n"); 2184b2e1b302SLuis R. Rodriguez else { 2185b2e1b302SLuis R. Rodriguez if (is_unknown_alpha2(rd->alpha2)) 2186e9c0268fSJoe Perches pr_info("Regulatory domain changed to driver built-in settings (unknown country)\n"); 218757b5ce07SLuis R. Rodriguez else { 218857b5ce07SLuis R. Rodriguez if (reg_request_cell_base(last_request)) 218957b5ce07SLuis R. Rodriguez pr_info("Regulatory domain changed " 219057b5ce07SLuis R. Rodriguez "to country: %c%c by Cell Station\n", 2191b2e1b302SLuis R. Rodriguez rd->alpha2[0], rd->alpha2[1]); 219257b5ce07SLuis R. Rodriguez else 219357b5ce07SLuis R. Rodriguez pr_info("Regulatory domain changed " 219457b5ce07SLuis R. Rodriguez "to country: %c%c\n", 219557b5ce07SLuis R. Rodriguez rd->alpha2[0], rd->alpha2[1]); 219657b5ce07SLuis R. Rodriguez } 2197b2e1b302SLuis R. Rodriguez } 21988b60b078SLuis R. Rodriguez print_dfs_region(rd->dfs_region); 2199b2e1b302SLuis R. Rodriguez print_rd_rules(rd); 2200b2e1b302SLuis R. Rodriguez } 2201b2e1b302SLuis R. Rodriguez 22022df78167SJohannes Berg static void print_regdomain_info(const struct ieee80211_regdomain *rd) 2203b2e1b302SLuis R. Rodriguez { 2204e9c0268fSJoe Perches pr_info("Regulatory domain: %c%c\n", rd->alpha2[0], rd->alpha2[1]); 2205b2e1b302SLuis R. Rodriguez print_rd_rules(rd); 2206b2e1b302SLuis R. Rodriguez } 2207b2e1b302SLuis R. Rodriguez 2208d2372b31SJohannes Berg /* Takes ownership of rd only if it doesn't fail */ 2209a3d2eaf0SJohannes Berg static int __set_regdom(const struct ieee80211_regdomain *rd) 2210b2e1b302SLuis R. Rodriguez { 22119c96477dSLuis R. Rodriguez const struct ieee80211_regdomain *intersected_rd = NULL; 2212806a9e39SLuis R. Rodriguez struct wiphy *request_wiphy; 2213b2e1b302SLuis R. Rodriguez /* Some basic sanity checks first */ 2214b2e1b302SLuis R. Rodriguez 2215b2e1b302SLuis R. Rodriguez if (is_world_regdom(rd->alpha2)) { 2216f6037d09SJohannes Berg if (WARN_ON(!reg_is_valid_request(rd->alpha2))) 2217b2e1b302SLuis R. Rodriguez return -EINVAL; 2218b2e1b302SLuis R. Rodriguez update_world_regdomain(rd); 2219b2e1b302SLuis R. Rodriguez return 0; 2220b2e1b302SLuis R. Rodriguez } 2221b2e1b302SLuis R. Rodriguez 2222b2e1b302SLuis R. Rodriguez if (!is_alpha2_set(rd->alpha2) && !is_an_alpha2(rd->alpha2) && 2223b2e1b302SLuis R. Rodriguez !is_unknown_alpha2(rd->alpha2)) 2224b2e1b302SLuis R. Rodriguez return -EINVAL; 2225b2e1b302SLuis R. Rodriguez 2226f6037d09SJohannes Berg if (!last_request) 2227b2e1b302SLuis R. Rodriguez return -EINVAL; 2228b2e1b302SLuis R. Rodriguez 2229fb1fc7adSLuis R. Rodriguez /* 2230fb1fc7adSLuis R. Rodriguez * Lets only bother proceeding on the same alpha2 if the current 22313f2355cbSLuis R. Rodriguez * rd is non static (it means CRDA was present and was used last) 2232fb1fc7adSLuis R. Rodriguez * and the pending request came in from a country IE 2233fb1fc7adSLuis R. Rodriguez */ 22347db90f4aSLuis R. Rodriguez if (last_request->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE) { 2235fb1fc7adSLuis R. Rodriguez /* 2236fb1fc7adSLuis R. Rodriguez * If someone else asked us to change the rd lets only bother 2237fb1fc7adSLuis R. Rodriguez * checking if the alpha2 changes if CRDA was already called 2238fb1fc7adSLuis R. Rodriguez */ 2239baeb66feSJohn W. Linville if (!regdom_changes(rd->alpha2)) 224095908535SKalle Valo return -EALREADY; 22413f2355cbSLuis R. Rodriguez } 22423f2355cbSLuis R. Rodriguez 2243fb1fc7adSLuis R. Rodriguez /* 2244fb1fc7adSLuis R. Rodriguez * Now lets set the regulatory domain, update all driver channels 2245b2e1b302SLuis R. Rodriguez * and finally inform them of what we have done, in case they want 2246b2e1b302SLuis R. Rodriguez * to review or adjust their own settings based on their own 2247fb1fc7adSLuis R. Rodriguez * internal EEPROM data 2248fb1fc7adSLuis R. Rodriguez */ 2249b2e1b302SLuis R. Rodriguez 2250f6037d09SJohannes Berg if (WARN_ON(!reg_is_valid_request(rd->alpha2))) 2251b2e1b302SLuis R. Rodriguez return -EINVAL; 2252b2e1b302SLuis R. Rodriguez 2253b2e1b302SLuis R. Rodriguez if (!is_valid_rd(rd)) { 2254e9c0268fSJoe Perches pr_err("Invalid regulatory domain detected:\n"); 2255b2e1b302SLuis R. Rodriguez print_regdomain_info(rd); 2256b2e1b302SLuis R. Rodriguez return -EINVAL; 2257b2e1b302SLuis R. Rodriguez } 2258b2e1b302SLuis R. Rodriguez 2259806a9e39SLuis R. Rodriguez request_wiphy = wiphy_idx_to_wiphy(last_request->wiphy_idx); 22600bac71afSLuis R. Rodriguez if (!request_wiphy && 22610bac71afSLuis R. Rodriguez (last_request->initiator == NL80211_REGDOM_SET_BY_DRIVER || 22620bac71afSLuis R. Rodriguez last_request->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE)) { 22630bac71afSLuis R. Rodriguez schedule_delayed_work(®_timeout, 0); 2264de3584bdSJohannes Berg return -ENODEV; 2265de3584bdSJohannes Berg } 2266806a9e39SLuis R. Rodriguez 2267b8295acdSLuis R. Rodriguez if (!last_request->intersect) { 22683e0c3ff3SLuis R. Rodriguez int r; 22693e0c3ff3SLuis R. Rodriguez 22707db90f4aSLuis R. Rodriguez if (last_request->initiator != NL80211_REGDOM_SET_BY_DRIVER) { 2271a042994dSLuis R. Rodriguez reset_regdomains(false); 22723e0c3ff3SLuis R. Rodriguez cfg80211_regdomain = rd; 22733e0c3ff3SLuis R. Rodriguez return 0; 22743e0c3ff3SLuis R. Rodriguez } 22753e0c3ff3SLuis R. Rodriguez 2276fb1fc7adSLuis R. Rodriguez /* 2277fb1fc7adSLuis R. Rodriguez * For a driver hint, lets copy the regulatory domain the 2278fb1fc7adSLuis R. Rodriguez * driver wanted to the wiphy to deal with conflicts 2279fb1fc7adSLuis R. Rodriguez */ 22803e0c3ff3SLuis R. Rodriguez 2281558f6d32SLuis R. Rodriguez /* 2282558f6d32SLuis R. Rodriguez * Userspace could have sent two replies with only 2283558f6d32SLuis R. Rodriguez * one kernel request. 2284558f6d32SLuis R. Rodriguez */ 2285558f6d32SLuis R. Rodriguez if (request_wiphy->regd) 2286558f6d32SLuis R. Rodriguez return -EALREADY; 22873e0c3ff3SLuis R. Rodriguez 2288806a9e39SLuis R. Rodriguez r = reg_copy_regd(&request_wiphy->regd, rd); 22893e0c3ff3SLuis R. Rodriguez if (r) 22903e0c3ff3SLuis R. Rodriguez return r; 22913e0c3ff3SLuis R. Rodriguez 2292a042994dSLuis R. Rodriguez reset_regdomains(false); 2293b8295acdSLuis R. Rodriguez cfg80211_regdomain = rd; 2294b8295acdSLuis R. Rodriguez return 0; 2295b8295acdSLuis R. Rodriguez } 2296b8295acdSLuis R. Rodriguez 2297b8295acdSLuis R. Rodriguez /* Intersection requires a bit more work */ 2298b8295acdSLuis R. Rodriguez 22997db90f4aSLuis R. Rodriguez if (last_request->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE) { 2300b8295acdSLuis R. Rodriguez 23019c96477dSLuis R. Rodriguez intersected_rd = regdom_intersect(rd, cfg80211_regdomain); 23029c96477dSLuis R. Rodriguez if (!intersected_rd) 23039c96477dSLuis R. Rodriguez return -EINVAL; 2304b8295acdSLuis R. Rodriguez 2305fb1fc7adSLuis R. Rodriguez /* 2306fb1fc7adSLuis R. Rodriguez * We can trash what CRDA provided now. 23073e0c3ff3SLuis R. Rodriguez * However if a driver requested this specific regulatory 2308fb1fc7adSLuis R. Rodriguez * domain we keep it for its private use 2309fb1fc7adSLuis R. Rodriguez */ 23107db90f4aSLuis R. Rodriguez if (last_request->initiator == NL80211_REGDOM_SET_BY_DRIVER) 2311806a9e39SLuis R. Rodriguez request_wiphy->regd = rd; 23123e0c3ff3SLuis R. Rodriguez else 23139c96477dSLuis R. Rodriguez kfree(rd); 23143e0c3ff3SLuis R. Rodriguez 2315b8295acdSLuis R. Rodriguez rd = NULL; 2316b8295acdSLuis R. Rodriguez 2317a042994dSLuis R. Rodriguez reset_regdomains(false); 2318b8295acdSLuis R. Rodriguez cfg80211_regdomain = intersected_rd; 2319b8295acdSLuis R. Rodriguez 2320b8295acdSLuis R. Rodriguez return 0; 23219c96477dSLuis R. Rodriguez } 23229c96477dSLuis R. Rodriguez 23233f2355cbSLuis R. Rodriguez return -EINVAL; 2324b2e1b302SLuis R. Rodriguez } 2325b2e1b302SLuis R. Rodriguez 2326b2e1b302SLuis R. Rodriguez 2327fb1fc7adSLuis R. Rodriguez /* 2328fb1fc7adSLuis R. Rodriguez * Use this call to set the current regulatory domain. Conflicts with 2329b2e1b302SLuis R. Rodriguez * multiple drivers can be ironed out later. Caller must've already 2330fb1fc7adSLuis R. Rodriguez * kmalloc'd the rd structure. Caller must hold cfg80211_mutex 2331fb1fc7adSLuis R. Rodriguez */ 2332a3d2eaf0SJohannes Berg int set_regdom(const struct ieee80211_regdomain *rd) 2333b2e1b302SLuis R. Rodriguez { 2334b2e1b302SLuis R. Rodriguez int r; 2335b2e1b302SLuis R. Rodriguez 2336761cf7ecSLuis R. Rodriguez assert_cfg80211_lock(); 2337761cf7ecSLuis R. Rodriguez 2338abc7381bSLuis R. Rodriguez mutex_lock(®_mutex); 2339abc7381bSLuis R. Rodriguez 2340b2e1b302SLuis R. Rodriguez /* Note that this doesn't update the wiphys, this is done below */ 2341b2e1b302SLuis R. Rodriguez r = __set_regdom(rd); 2342d2372b31SJohannes Berg if (r) { 234395908535SKalle Valo if (r == -EALREADY) 234495908535SKalle Valo reg_set_request_processed(); 234595908535SKalle Valo 2346d2372b31SJohannes Berg kfree(rd); 2347abc7381bSLuis R. Rodriguez mutex_unlock(®_mutex); 2348b2e1b302SLuis R. Rodriguez return r; 2349d2372b31SJohannes Berg } 2350b2e1b302SLuis R. Rodriguez 2351b2e1b302SLuis R. Rodriguez /* This would make this whole thing pointless */ 2352a01ddafdSLuis R. Rodriguez if (!last_request->intersect) 2353b2e1b302SLuis R. Rodriguez BUG_ON(rd != cfg80211_regdomain); 2354b2e1b302SLuis R. Rodriguez 2355b2e1b302SLuis R. Rodriguez /* update all wiphys now with the new established regulatory domain */ 2356f6037d09SJohannes Berg update_all_wiphy_regulatory(last_request->initiator); 2357b2e1b302SLuis R. Rodriguez 2358a01ddafdSLuis R. Rodriguez print_regdomain(cfg80211_regdomain); 2359b2e1b302SLuis R. Rodriguez 236073d54c9eSLuis R. Rodriguez nl80211_send_reg_change_event(last_request); 236173d54c9eSLuis R. Rodriguez 2362b2e253cfSLuis R. Rodriguez reg_set_request_processed(); 2363b2e253cfSLuis R. Rodriguez 2364abc7381bSLuis R. Rodriguez mutex_unlock(®_mutex); 2365abc7381bSLuis R. Rodriguez 2366b2e1b302SLuis R. Rodriguez return r; 2367b2e1b302SLuis R. Rodriguez } 2368b2e1b302SLuis R. Rodriguez 23694d9d88d1SScott James Remnant #ifdef CONFIG_HOTPLUG 23704d9d88d1SScott James Remnant int reg_device_uevent(struct device *dev, struct kobj_uevent_env *env) 23714d9d88d1SScott James Remnant { 23724d9d88d1SScott James Remnant if (last_request && !last_request->processed) { 23734d9d88d1SScott James Remnant if (add_uevent_var(env, "COUNTRY=%c%c", 23744d9d88d1SScott James Remnant last_request->alpha2[0], 23754d9d88d1SScott James Remnant last_request->alpha2[1])) 23764d9d88d1SScott James Remnant return -ENOMEM; 23774d9d88d1SScott James Remnant } 23784d9d88d1SScott James Remnant 23794d9d88d1SScott James Remnant return 0; 23804d9d88d1SScott James Remnant } 23814d9d88d1SScott James Remnant #else 23824d9d88d1SScott James Remnant int reg_device_uevent(struct device *dev, struct kobj_uevent_env *env) 23834d9d88d1SScott James Remnant { 23844d9d88d1SScott James Remnant return -ENODEV; 23854d9d88d1SScott James Remnant } 23864d9d88d1SScott James Remnant #endif /* CONFIG_HOTPLUG */ 23874d9d88d1SScott James Remnant 238857b5ce07SLuis R. Rodriguez void wiphy_regulatory_register(struct wiphy *wiphy) 238957b5ce07SLuis R. Rodriguez { 239057b5ce07SLuis R. Rodriguez assert_cfg80211_lock(); 239157b5ce07SLuis R. Rodriguez 239257b5ce07SLuis R. Rodriguez mutex_lock(®_mutex); 239357b5ce07SLuis R. Rodriguez 239457b5ce07SLuis R. Rodriguez if (!reg_dev_ignore_cell_hint(wiphy)) 239557b5ce07SLuis R. Rodriguez reg_num_devs_support_basehint++; 239657b5ce07SLuis R. Rodriguez 239714cdf112SLuis R. Rodriguez wiphy_update_regulatory(wiphy, NL80211_REGDOM_SET_BY_CORE); 2398f8a1c774SLuis R. Rodriguez 239914cdf112SLuis R. Rodriguez mutex_unlock(®_mutex); 240057b5ce07SLuis R. Rodriguez } 240157b5ce07SLuis R. Rodriguez 2402a1794390SLuis R. Rodriguez /* Caller must hold cfg80211_mutex */ 2403bfead080SLuis R. Rodriguez void wiphy_regulatory_deregister(struct wiphy *wiphy) 24043f2355cbSLuis R. Rodriguez { 24050ad8acafSLuis R. Rodriguez struct wiphy *request_wiphy = NULL; 2406806a9e39SLuis R. Rodriguez 2407761cf7ecSLuis R. Rodriguez assert_cfg80211_lock(); 2408761cf7ecSLuis R. Rodriguez 2409abc7381bSLuis R. Rodriguez mutex_lock(®_mutex); 2410abc7381bSLuis R. Rodriguez 241157b5ce07SLuis R. Rodriguez if (!reg_dev_ignore_cell_hint(wiphy)) 241257b5ce07SLuis R. Rodriguez reg_num_devs_support_basehint--; 241357b5ce07SLuis R. Rodriguez 24140ef9ccddSChris Wright kfree(wiphy->regd); 24150ef9ccddSChris Wright 24160ad8acafSLuis R. Rodriguez if (last_request) 2417806a9e39SLuis R. Rodriguez request_wiphy = wiphy_idx_to_wiphy(last_request->wiphy_idx); 2418806a9e39SLuis R. Rodriguez 24190ef9ccddSChris Wright if (!request_wiphy || request_wiphy != wiphy) 2420abc7381bSLuis R. Rodriguez goto out; 24210ef9ccddSChris Wright 2422806a9e39SLuis R. Rodriguez last_request->wiphy_idx = WIPHY_IDX_STALE; 24233f2355cbSLuis R. Rodriguez last_request->country_ie_env = ENVIRON_ANY; 2424abc7381bSLuis R. Rodriguez out: 2425abc7381bSLuis R. Rodriguez mutex_unlock(®_mutex); 24263f2355cbSLuis R. Rodriguez } 24273f2355cbSLuis R. Rodriguez 2428a90c7a31SLuis R. Rodriguez static void reg_timeout_work(struct work_struct *work) 2429a90c7a31SLuis R. Rodriguez { 2430a90c7a31SLuis R. Rodriguez REG_DBG_PRINT("Timeout while waiting for CRDA to reply, " 243112c5ffb5SJoe Perches "restoring regulatory settings\n"); 2432a90c7a31SLuis R. Rodriguez restore_regulatory_settings(true); 2433a90c7a31SLuis R. Rodriguez } 2434a90c7a31SLuis R. Rodriguez 24352fcc9f73SUwe Kleine-König int __init regulatory_init(void) 2436b2e1b302SLuis R. Rodriguez { 2437bcf4f99bSLuis R. Rodriguez int err = 0; 2438734366deSJohannes Berg 2439b2e1b302SLuis R. Rodriguez reg_pdev = platform_device_register_simple("regulatory", 0, NULL, 0); 2440b2e1b302SLuis R. Rodriguez if (IS_ERR(reg_pdev)) 2441b2e1b302SLuis R. Rodriguez return PTR_ERR(reg_pdev); 2442734366deSJohannes Berg 24434d9d88d1SScott James Remnant reg_pdev->dev.type = ®_device_type; 24444d9d88d1SScott James Remnant 2445fe33eb39SLuis R. Rodriguez spin_lock_init(®_requests_lock); 2446e38f8a7aSLuis R. Rodriguez spin_lock_init(®_pending_beacons_lock); 2447fe33eb39SLuis R. Rodriguez 244880007efeSLuis R. Rodriguez reg_regdb_size_check(); 244980007efeSLuis R. Rodriguez 2450a3d2eaf0SJohannes Berg cfg80211_regdomain = cfg80211_world_regdom; 2451734366deSJohannes Berg 245209d989d1SLuis R. Rodriguez user_alpha2[0] = '9'; 245309d989d1SLuis R. Rodriguez user_alpha2[1] = '7'; 245409d989d1SLuis R. Rodriguez 2455ae9e4b0dSLuis R. Rodriguez /* We always try to get an update for the static regdomain */ 2456ae9e4b0dSLuis R. Rodriguez err = regulatory_hint_core(cfg80211_regdomain->alpha2); 2457bcf4f99bSLuis R. Rodriguez if (err) { 2458bcf4f99bSLuis R. Rodriguez if (err == -ENOMEM) 2459bcf4f99bSLuis R. Rodriguez return err; 2460bcf4f99bSLuis R. Rodriguez /* 2461bcf4f99bSLuis R. Rodriguez * N.B. kobject_uevent_env() can fail mainly for when we're out 2462bcf4f99bSLuis R. Rodriguez * memory which is handled and propagated appropriately above 2463bcf4f99bSLuis R. Rodriguez * but it can also fail during a netlink_broadcast() or during 2464bcf4f99bSLuis R. Rodriguez * early boot for call_usermodehelper(). For now treat these 2465bcf4f99bSLuis R. Rodriguez * errors as non-fatal. 2466bcf4f99bSLuis R. Rodriguez */ 2467e9c0268fSJoe Perches pr_err("kobject_uevent_env() was unable to call CRDA during init\n"); 2468bcf4f99bSLuis R. Rodriguez #ifdef CONFIG_CFG80211_REG_DEBUG 2469bcf4f99bSLuis R. Rodriguez /* We want to find out exactly why when debugging */ 2470bcf4f99bSLuis R. Rodriguez WARN_ON(err); 2471bcf4f99bSLuis R. Rodriguez #endif 2472bcf4f99bSLuis R. Rodriguez } 2473734366deSJohannes Berg 2474ae9e4b0dSLuis R. Rodriguez /* 2475ae9e4b0dSLuis R. Rodriguez * Finally, if the user set the module parameter treat it 2476ae9e4b0dSLuis R. Rodriguez * as a user hint. 2477ae9e4b0dSLuis R. Rodriguez */ 2478ae9e4b0dSLuis R. Rodriguez if (!is_world_regdom(ieee80211_regdom)) 247957b5ce07SLuis R. Rodriguez regulatory_hint_user(ieee80211_regdom, 248057b5ce07SLuis R. Rodriguez NL80211_USER_REG_HINT_USER); 2481ae9e4b0dSLuis R. Rodriguez 2482b2e1b302SLuis R. Rodriguez return 0; 2483b2e1b302SLuis R. Rodriguez } 2484b2e1b302SLuis R. Rodriguez 24852fcc9f73SUwe Kleine-König void /* __init_or_exit */ regulatory_exit(void) 2486b2e1b302SLuis R. Rodriguez { 2487fe33eb39SLuis R. Rodriguez struct regulatory_request *reg_request, *tmp; 2488e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon, *btmp; 2489fe33eb39SLuis R. Rodriguez 2490fe33eb39SLuis R. Rodriguez cancel_work_sync(®_work); 2491a90c7a31SLuis R. Rodriguez cancel_delayed_work_sync(®_timeout); 2492fe33eb39SLuis R. Rodriguez 2493a1794390SLuis R. Rodriguez mutex_lock(&cfg80211_mutex); 2494abc7381bSLuis R. Rodriguez mutex_lock(®_mutex); 2495734366deSJohannes Berg 2496a042994dSLuis R. Rodriguez reset_regdomains(true); 2497734366deSJohannes Berg 249858ebacc6SLuis R. Rodriguez dev_set_uevent_suppress(®_pdev->dev, true); 2499f6037d09SJohannes Berg 2500b2e1b302SLuis R. Rodriguez platform_device_unregister(reg_pdev); 2501734366deSJohannes Berg 2502e38f8a7aSLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 2503e38f8a7aSLuis R. Rodriguez if (!list_empty(®_pending_beacons)) { 2504e38f8a7aSLuis R. Rodriguez list_for_each_entry_safe(reg_beacon, btmp, 2505e38f8a7aSLuis R. Rodriguez ®_pending_beacons, list) { 2506e38f8a7aSLuis R. Rodriguez list_del(®_beacon->list); 2507e38f8a7aSLuis R. Rodriguez kfree(reg_beacon); 2508e38f8a7aSLuis R. Rodriguez } 2509e38f8a7aSLuis R. Rodriguez } 2510e38f8a7aSLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 2511e38f8a7aSLuis R. Rodriguez 2512e38f8a7aSLuis R. Rodriguez if (!list_empty(®_beacon_list)) { 2513e38f8a7aSLuis R. Rodriguez list_for_each_entry_safe(reg_beacon, btmp, 2514e38f8a7aSLuis R. Rodriguez ®_beacon_list, list) { 2515e38f8a7aSLuis R. Rodriguez list_del(®_beacon->list); 2516e38f8a7aSLuis R. Rodriguez kfree(reg_beacon); 2517e38f8a7aSLuis R. Rodriguez } 2518e38f8a7aSLuis R. Rodriguez } 2519e38f8a7aSLuis R. Rodriguez 2520fe33eb39SLuis R. Rodriguez spin_lock(®_requests_lock); 2521fe33eb39SLuis R. Rodriguez if (!list_empty(®_requests_list)) { 2522fe33eb39SLuis R. Rodriguez list_for_each_entry_safe(reg_request, tmp, 2523fe33eb39SLuis R. Rodriguez ®_requests_list, list) { 2524fe33eb39SLuis R. Rodriguez list_del(®_request->list); 2525fe33eb39SLuis R. Rodriguez kfree(reg_request); 2526fe33eb39SLuis R. Rodriguez } 2527fe33eb39SLuis R. Rodriguez } 2528fe33eb39SLuis R. Rodriguez spin_unlock(®_requests_lock); 2529fe33eb39SLuis R. Rodriguez 2530abc7381bSLuis R. Rodriguez mutex_unlock(®_mutex); 2531a1794390SLuis R. Rodriguez mutex_unlock(&cfg80211_mutex); 25328318d78aSJohannes Berg } 2533