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> 51c61029c7SJohn W. Linville #include <linux/ctype.h> 52b2e1b302SLuis R. Rodriguez #include <linux/nl80211.h> 53b2e1b302SLuis R. Rodriguez #include <linux/platform_device.h> 54d9b93842SPaul Gortmaker #include <linux/moduleparam.h> 55b2e1b302SLuis R. Rodriguez #include <net/cfg80211.h> 568318d78aSJohannes Berg #include "core.h" 57b2e1b302SLuis R. Rodriguez #include "reg.h" 583b377ea9SJohn W. Linville #include "regdb.h" 5973d54c9eSLuis R. Rodriguez #include "nl80211.h" 608318d78aSJohannes Berg 614113f751SLuis R. Rodriguez #ifdef CONFIG_CFG80211_REG_DEBUG 628271195eSJohn W. Linville #define REG_DBG_PRINT(format, args...) \ 6312c5ffb5SJoe Perches printk(KERN_DEBUG pr_fmt(format), ##args) 644113f751SLuis R. Rodriguez #else 658271195eSJohn W. Linville #define REG_DBG_PRINT(args...) 664113f751SLuis R. Rodriguez #endif 674113f751SLuis R. Rodriguez 682f92212bSJohannes Berg enum reg_request_treatment { 692f92212bSJohannes Berg REG_REQ_OK, 702f92212bSJohannes Berg REG_REQ_IGNORE, 712f92212bSJohannes Berg REG_REQ_INTERSECT, 722f92212bSJohannes Berg REG_REQ_ALREADY_SET, 732f92212bSJohannes Berg }; 742f92212bSJohannes Berg 75a042994dSLuis R. Rodriguez static struct regulatory_request core_request_world = { 76a042994dSLuis R. Rodriguez .initiator = NL80211_REGDOM_SET_BY_CORE, 77a042994dSLuis R. Rodriguez .alpha2[0] = '0', 78a042994dSLuis R. Rodriguez .alpha2[1] = '0', 79a042994dSLuis R. Rodriguez .intersect = false, 80a042994dSLuis R. Rodriguez .processed = true, 81a042994dSLuis R. Rodriguez .country_ie_env = ENVIRON_ANY, 82a042994dSLuis R. Rodriguez }; 83a042994dSLuis R. Rodriguez 8438fd2143SJohannes Berg /* 8538fd2143SJohannes Berg * Receipt of information from last regulatory request, 8638fd2143SJohannes Berg * protected by RTNL (and can be accessed with RCU protection) 8738fd2143SJohannes Berg */ 88c492db37SJohannes Berg static struct regulatory_request __rcu *last_request = 89c492db37SJohannes Berg (void __rcu *)&core_request_world; 90734366deSJohannes Berg 91b2e1b302SLuis R. Rodriguez /* To trigger userspace events */ 92b2e1b302SLuis R. Rodriguez static struct platform_device *reg_pdev; 938318d78aSJohannes Berg 944d9d88d1SScott James Remnant static struct device_type reg_device_type = { 954d9d88d1SScott James Remnant .uevent = reg_device_uevent, 964d9d88d1SScott James Remnant }; 974d9d88d1SScott James Remnant 98fb1fc7adSLuis R. Rodriguez /* 99fb1fc7adSLuis R. Rodriguez * Central wireless core regulatory domains, we only need two, 100734366deSJohannes Berg * the current one and a world regulatory domain in case we have no 101e8da2bb4SJohannes Berg * information to give us an alpha2. 10238fd2143SJohannes Berg * (protected by RTNL, can be read under RCU) 103fb1fc7adSLuis R. Rodriguez */ 104458f4f9eSJohannes Berg const struct ieee80211_regdomain __rcu *cfg80211_regdomain; 105734366deSJohannes Berg 106fb1fc7adSLuis R. Rodriguez /* 10757b5ce07SLuis R. Rodriguez * Number of devices that registered to the core 10857b5ce07SLuis R. Rodriguez * that support cellular base station regulatory hints 10938fd2143SJohannes Berg * (protected by RTNL) 11057b5ce07SLuis R. Rodriguez */ 11157b5ce07SLuis R. Rodriguez static int reg_num_devs_support_basehint; 11257b5ce07SLuis R. Rodriguez 113458f4f9eSJohannes Berg static const struct ieee80211_regdomain *get_cfg80211_regdom(void) 114458f4f9eSJohannes Berg { 11538fd2143SJohannes Berg return rtnl_dereference(cfg80211_regdomain); 116458f4f9eSJohannes Berg } 117458f4f9eSJohannes Berg 118458f4f9eSJohannes Berg static const struct ieee80211_regdomain *get_wiphy_regdom(struct wiphy *wiphy) 119458f4f9eSJohannes Berg { 12038fd2143SJohannes Berg return rtnl_dereference(wiphy->regd); 121458f4f9eSJohannes Berg } 122458f4f9eSJohannes Berg 1233ef121b5SLuis R. Rodriguez static const char *reg_dfs_region_str(enum nl80211_dfs_regions dfs_region) 1243ef121b5SLuis R. Rodriguez { 1253ef121b5SLuis R. Rodriguez switch (dfs_region) { 1263ef121b5SLuis R. Rodriguez case NL80211_DFS_UNSET: 1273ef121b5SLuis R. Rodriguez return "unset"; 1283ef121b5SLuis R. Rodriguez case NL80211_DFS_FCC: 1293ef121b5SLuis R. Rodriguez return "FCC"; 1303ef121b5SLuis R. Rodriguez case NL80211_DFS_ETSI: 1313ef121b5SLuis R. Rodriguez return "ETSI"; 1323ef121b5SLuis R. Rodriguez case NL80211_DFS_JP: 1333ef121b5SLuis R. Rodriguez return "JP"; 1343ef121b5SLuis R. Rodriguez } 1353ef121b5SLuis R. Rodriguez return "Unknown"; 1363ef121b5SLuis R. Rodriguez } 1373ef121b5SLuis R. Rodriguez 1386c474799SLuis R. Rodriguez enum nl80211_dfs_regions reg_get_dfs_region(struct wiphy *wiphy) 1396c474799SLuis R. Rodriguez { 1406c474799SLuis R. Rodriguez const struct ieee80211_regdomain *regd = NULL; 1416c474799SLuis R. Rodriguez const struct ieee80211_regdomain *wiphy_regd = NULL; 1426c474799SLuis R. Rodriguez 1436c474799SLuis R. Rodriguez regd = get_cfg80211_regdom(); 1446c474799SLuis R. Rodriguez if (!wiphy) 1456c474799SLuis R. Rodriguez goto out; 1466c474799SLuis R. Rodriguez 1476c474799SLuis R. Rodriguez wiphy_regd = get_wiphy_regdom(wiphy); 1486c474799SLuis R. Rodriguez if (!wiphy_regd) 1496c474799SLuis R. Rodriguez goto out; 1506c474799SLuis R. Rodriguez 1516c474799SLuis R. Rodriguez if (wiphy_regd->dfs_region == regd->dfs_region) 1526c474799SLuis R. Rodriguez goto out; 1536c474799SLuis R. Rodriguez 1546c474799SLuis R. Rodriguez REG_DBG_PRINT("%s: device specific dfs_region " 1556c474799SLuis R. Rodriguez "(%s) disagrees with cfg80211's " 1566c474799SLuis R. Rodriguez "central dfs_region (%s)\n", 1576c474799SLuis R. Rodriguez dev_name(&wiphy->dev), 1586c474799SLuis R. Rodriguez reg_dfs_region_str(wiphy_regd->dfs_region), 1596c474799SLuis R. Rodriguez reg_dfs_region_str(regd->dfs_region)); 1606c474799SLuis R. Rodriguez 1616c474799SLuis R. Rodriguez out: 1626c474799SLuis R. Rodriguez return regd->dfs_region; 1636c474799SLuis R. Rodriguez } 1646c474799SLuis R. Rodriguez 165458f4f9eSJohannes Berg static void rcu_free_regdom(const struct ieee80211_regdomain *r) 166458f4f9eSJohannes Berg { 167458f4f9eSJohannes Berg if (!r) 168458f4f9eSJohannes Berg return; 169458f4f9eSJohannes Berg kfree_rcu((struct ieee80211_regdomain *)r, rcu_head); 170458f4f9eSJohannes Berg } 171458f4f9eSJohannes Berg 172c492db37SJohannes Berg static struct regulatory_request *get_last_request(void) 173c492db37SJohannes Berg { 17438fd2143SJohannes Berg return rcu_dereference_rtnl(last_request); 175c492db37SJohannes Berg } 176c492db37SJohannes Berg 177e38f8a7aSLuis R. Rodriguez /* Used to queue up regulatory hints */ 178fe33eb39SLuis R. Rodriguez static LIST_HEAD(reg_requests_list); 179fe33eb39SLuis R. Rodriguez static spinlock_t reg_requests_lock; 180fe33eb39SLuis R. Rodriguez 181e38f8a7aSLuis R. Rodriguez /* Used to queue up beacon hints for review */ 182e38f8a7aSLuis R. Rodriguez static LIST_HEAD(reg_pending_beacons); 183e38f8a7aSLuis R. Rodriguez static spinlock_t reg_pending_beacons_lock; 184e38f8a7aSLuis R. Rodriguez 185e38f8a7aSLuis R. Rodriguez /* Used to keep track of processed beacon hints */ 186e38f8a7aSLuis R. Rodriguez static LIST_HEAD(reg_beacon_list); 187e38f8a7aSLuis R. Rodriguez 188e38f8a7aSLuis R. Rodriguez struct reg_beacon { 189e38f8a7aSLuis R. Rodriguez struct list_head list; 190e38f8a7aSLuis R. Rodriguez struct ieee80211_channel chan; 191e38f8a7aSLuis R. Rodriguez }; 192e38f8a7aSLuis R. Rodriguez 193f333a7a2SLuis R. Rodriguez static void reg_todo(struct work_struct *work); 194f333a7a2SLuis R. Rodriguez static DECLARE_WORK(reg_work, reg_todo); 195f333a7a2SLuis R. Rodriguez 196a90c7a31SLuis R. Rodriguez static void reg_timeout_work(struct work_struct *work); 197a90c7a31SLuis R. Rodriguez static DECLARE_DELAYED_WORK(reg_timeout, reg_timeout_work); 198a90c7a31SLuis R. Rodriguez 199734366deSJohannes Berg /* We keep a static world regulatory domain in case of the absence of CRDA */ 200734366deSJohannes Berg static const struct ieee80211_regdomain world_regdom = { 20190cdc6dfSVladimir Kondratiev .n_reg_rules = 6, 202734366deSJohannes Berg .alpha2 = "00", 203734366deSJohannes Berg .reg_rules = { 20468798a62SLuis R. Rodriguez /* IEEE 802.11b/g, channels 1..11 */ 20568798a62SLuis R. Rodriguez REG_RULE(2412-10, 2462+10, 40, 6, 20, 0), 20643c771a1SJohannes Berg /* IEEE 802.11b/g, channels 12..13. */ 20743c771a1SJohannes Berg REG_RULE(2467-10, 2472+10, 40, 6, 20, 2088fe02e16SLuis R. Rodriguez NL80211_RRF_NO_IR), 209611b6a82SLuis R. Rodriguez /* IEEE 802.11 channel 14 - Only JP enables 210611b6a82SLuis R. Rodriguez * this and for 802.11b only */ 211611b6a82SLuis R. Rodriguez REG_RULE(2484-10, 2484+10, 20, 6, 20, 2128fe02e16SLuis R. Rodriguez NL80211_RRF_NO_IR | 213611b6a82SLuis R. Rodriguez NL80211_RRF_NO_OFDM), 2143fc71f77SLuis R. Rodriguez /* IEEE 802.11a, channel 36..48 */ 215131a19bcSJohannes Berg REG_RULE(5180-10, 5240+10, 160, 6, 20, 2168fe02e16SLuis R. Rodriguez NL80211_RRF_NO_IR), 2173fc71f77SLuis R. Rodriguez 218131a19bcSJohannes Berg /* IEEE 802.11a, channel 52..64 - DFS required */ 219131a19bcSJohannes Berg REG_RULE(5260-10, 5320+10, 160, 6, 20, 2208fe02e16SLuis R. Rodriguez NL80211_RRF_NO_IR | 221131a19bcSJohannes Berg NL80211_RRF_DFS), 222131a19bcSJohannes Berg 223131a19bcSJohannes Berg /* IEEE 802.11a, channel 100..144 - DFS required */ 224131a19bcSJohannes Berg REG_RULE(5500-10, 5720+10, 160, 6, 20, 2258fe02e16SLuis R. Rodriguez NL80211_RRF_NO_IR | 226131a19bcSJohannes Berg NL80211_RRF_DFS), 2273fc71f77SLuis R. Rodriguez 2283fc71f77SLuis R. Rodriguez /* IEEE 802.11a, channel 149..165 */ 2298ab9d85cSJohannes Berg REG_RULE(5745-10, 5825+10, 80, 6, 20, 2308fe02e16SLuis R. Rodriguez NL80211_RRF_NO_IR), 23190cdc6dfSVladimir Kondratiev 23290cdc6dfSVladimir Kondratiev /* IEEE 802.11ad (60gHz), channels 1..3 */ 23390cdc6dfSVladimir Kondratiev REG_RULE(56160+2160*1-1080, 56160+2160*3+1080, 2160, 0, 0, 0), 234734366deSJohannes Berg } 235734366deSJohannes Berg }; 236734366deSJohannes Berg 23738fd2143SJohannes Berg /* protected by RTNL */ 238a3d2eaf0SJohannes Berg static const struct ieee80211_regdomain *cfg80211_world_regdom = 239a3d2eaf0SJohannes Berg &world_regdom; 240734366deSJohannes Berg 2416ee7d330SLuis R. Rodriguez static char *ieee80211_regdom = "00"; 24209d989d1SLuis R. Rodriguez static char user_alpha2[2]; 2436ee7d330SLuis R. Rodriguez 244734366deSJohannes Berg module_param(ieee80211_regdom, charp, 0444); 245734366deSJohannes Berg MODULE_PARM_DESC(ieee80211_regdom, "IEEE 802.11 regulatory domain code"); 246734366deSJohannes Berg 2475ad6ef5eSLuis R. Rodriguez static void reg_kfree_last_request(void) 2485ad6ef5eSLuis R. Rodriguez { 2495ad6ef5eSLuis R. Rodriguez struct regulatory_request *lr; 2505ad6ef5eSLuis R. Rodriguez 2515ad6ef5eSLuis R. Rodriguez lr = get_last_request(); 2525ad6ef5eSLuis R. Rodriguez 2535ad6ef5eSLuis R. Rodriguez if (lr != &core_request_world && lr) 2545ad6ef5eSLuis R. Rodriguez kfree_rcu(lr, rcu_head); 2555ad6ef5eSLuis R. Rodriguez } 2565ad6ef5eSLuis R. Rodriguez 25705f1a3eaSLuis R. Rodriguez static void reg_update_last_request(struct regulatory_request *request) 25805f1a3eaSLuis R. Rodriguez { 25905f1a3eaSLuis R. Rodriguez reg_kfree_last_request(); 26005f1a3eaSLuis R. Rodriguez rcu_assign_pointer(last_request, request); 26105f1a3eaSLuis R. Rodriguez } 26205f1a3eaSLuis R. Rodriguez 263379b82f4SJohannes Berg static void reset_regdomains(bool full_reset, 264379b82f4SJohannes Berg const struct ieee80211_regdomain *new_regdom) 265734366deSJohannes Berg { 266458f4f9eSJohannes Berg const struct ieee80211_regdomain *r; 267458f4f9eSJohannes Berg 26838fd2143SJohannes Berg ASSERT_RTNL(); 269e8da2bb4SJohannes Berg 270458f4f9eSJohannes Berg r = get_cfg80211_regdom(); 271458f4f9eSJohannes Berg 272942b25cfSJohannes Berg /* avoid freeing static information or freeing something twice */ 273458f4f9eSJohannes Berg if (r == cfg80211_world_regdom) 274458f4f9eSJohannes Berg r = NULL; 275942b25cfSJohannes Berg if (cfg80211_world_regdom == &world_regdom) 276942b25cfSJohannes Berg cfg80211_world_regdom = NULL; 277458f4f9eSJohannes Berg if (r == &world_regdom) 278458f4f9eSJohannes Berg r = NULL; 279942b25cfSJohannes Berg 280458f4f9eSJohannes Berg rcu_free_regdom(r); 281458f4f9eSJohannes Berg rcu_free_regdom(cfg80211_world_regdom); 282734366deSJohannes Berg 283a3d2eaf0SJohannes Berg cfg80211_world_regdom = &world_regdom; 284458f4f9eSJohannes Berg rcu_assign_pointer(cfg80211_regdomain, new_regdom); 285a042994dSLuis R. Rodriguez 286a042994dSLuis R. Rodriguez if (!full_reset) 287a042994dSLuis R. Rodriguez return; 288a042994dSLuis R. Rodriguez 28905f1a3eaSLuis R. Rodriguez reg_update_last_request(&core_request_world); 290734366deSJohannes Berg } 291734366deSJohannes Berg 292fb1fc7adSLuis R. Rodriguez /* 293fb1fc7adSLuis R. Rodriguez * Dynamic world regulatory domain requested by the wireless 294fb1fc7adSLuis R. Rodriguez * core upon initialization 295fb1fc7adSLuis R. Rodriguez */ 296a3d2eaf0SJohannes Berg static void update_world_regdomain(const struct ieee80211_regdomain *rd) 297734366deSJohannes Berg { 298c492db37SJohannes Berg struct regulatory_request *lr; 299734366deSJohannes Berg 300c492db37SJohannes Berg lr = get_last_request(); 301c492db37SJohannes Berg 302c492db37SJohannes Berg WARN_ON(!lr); 303e8da2bb4SJohannes Berg 304379b82f4SJohannes Berg reset_regdomains(false, rd); 305734366deSJohannes Berg 306734366deSJohannes Berg cfg80211_world_regdom = rd; 307734366deSJohannes Berg } 308734366deSJohannes Berg 309a3d2eaf0SJohannes Berg bool is_world_regdom(const char *alpha2) 310b2e1b302SLuis R. Rodriguez { 311b2e1b302SLuis R. Rodriguez if (!alpha2) 312b2e1b302SLuis R. Rodriguez return false; 3131a919318SJohannes Berg return alpha2[0] == '0' && alpha2[1] == '0'; 314b2e1b302SLuis R. Rodriguez } 315b2e1b302SLuis R. Rodriguez 316a3d2eaf0SJohannes Berg static bool is_alpha2_set(const char *alpha2) 317b2e1b302SLuis R. Rodriguez { 318b2e1b302SLuis R. Rodriguez if (!alpha2) 319b2e1b302SLuis R. Rodriguez return false; 3201a919318SJohannes Berg return alpha2[0] && alpha2[1]; 321b2e1b302SLuis R. Rodriguez } 322b2e1b302SLuis R. Rodriguez 323a3d2eaf0SJohannes Berg static bool is_unknown_alpha2(const char *alpha2) 324b2e1b302SLuis R. Rodriguez { 325b2e1b302SLuis R. Rodriguez if (!alpha2) 326b2e1b302SLuis R. Rodriguez return false; 327fb1fc7adSLuis R. Rodriguez /* 328fb1fc7adSLuis R. Rodriguez * Special case where regulatory domain was built by driver 329fb1fc7adSLuis R. Rodriguez * but a specific alpha2 cannot be determined 330fb1fc7adSLuis R. Rodriguez */ 3311a919318SJohannes Berg return alpha2[0] == '9' && alpha2[1] == '9'; 332b2e1b302SLuis R. Rodriguez } 333b2e1b302SLuis R. Rodriguez 3343f2355cbSLuis R. Rodriguez static bool is_intersected_alpha2(const char *alpha2) 3353f2355cbSLuis R. Rodriguez { 3363f2355cbSLuis R. Rodriguez if (!alpha2) 3373f2355cbSLuis R. Rodriguez return false; 338fb1fc7adSLuis R. Rodriguez /* 339fb1fc7adSLuis R. Rodriguez * Special case where regulatory domain is the 3403f2355cbSLuis R. Rodriguez * result of an intersection between two regulatory domain 341fb1fc7adSLuis R. Rodriguez * structures 342fb1fc7adSLuis R. Rodriguez */ 3431a919318SJohannes Berg return alpha2[0] == '9' && alpha2[1] == '8'; 3443f2355cbSLuis R. Rodriguez } 3453f2355cbSLuis R. Rodriguez 346a3d2eaf0SJohannes Berg static bool is_an_alpha2(const char *alpha2) 347b2e1b302SLuis R. Rodriguez { 348b2e1b302SLuis R. Rodriguez if (!alpha2) 349b2e1b302SLuis R. Rodriguez return false; 3501a919318SJohannes Berg return isalpha(alpha2[0]) && isalpha(alpha2[1]); 351b2e1b302SLuis R. Rodriguez } 352b2e1b302SLuis R. Rodriguez 353a3d2eaf0SJohannes Berg static bool alpha2_equal(const char *alpha2_x, const char *alpha2_y) 354b2e1b302SLuis R. Rodriguez { 355b2e1b302SLuis R. Rodriguez if (!alpha2_x || !alpha2_y) 356b2e1b302SLuis R. Rodriguez return false; 3571a919318SJohannes Berg return alpha2_x[0] == alpha2_y[0] && alpha2_x[1] == alpha2_y[1]; 358b2e1b302SLuis R. Rodriguez } 359b2e1b302SLuis R. Rodriguez 36069b1572bSLuis R. Rodriguez static bool regdom_changes(const char *alpha2) 361b2e1b302SLuis R. Rodriguez { 362458f4f9eSJohannes Berg const struct ieee80211_regdomain *r = get_cfg80211_regdom(); 363761cf7ecSLuis R. Rodriguez 364458f4f9eSJohannes Berg if (!r) 365b2e1b302SLuis R. Rodriguez return true; 366458f4f9eSJohannes Berg return !alpha2_equal(r->alpha2, alpha2); 367b2e1b302SLuis R. Rodriguez } 368b2e1b302SLuis R. Rodriguez 36909d989d1SLuis R. Rodriguez /* 37009d989d1SLuis R. Rodriguez * The NL80211_REGDOM_SET_BY_USER regdom alpha2 is cached, this lets 37109d989d1SLuis R. Rodriguez * you know if a valid regulatory hint with NL80211_REGDOM_SET_BY_USER 37209d989d1SLuis R. Rodriguez * has ever been issued. 37309d989d1SLuis R. Rodriguez */ 37409d989d1SLuis R. Rodriguez static bool is_user_regdom_saved(void) 37509d989d1SLuis R. Rodriguez { 37609d989d1SLuis R. Rodriguez if (user_alpha2[0] == '9' && user_alpha2[1] == '7') 37709d989d1SLuis R. Rodriguez return false; 37809d989d1SLuis R. Rodriguez 37909d989d1SLuis R. Rodriguez /* This would indicate a mistake on the design */ 3801a919318SJohannes Berg if (WARN(!is_world_regdom(user_alpha2) && !is_an_alpha2(user_alpha2), 38109d989d1SLuis R. Rodriguez "Unexpected user alpha2: %c%c\n", 3821a919318SJohannes Berg user_alpha2[0], user_alpha2[1])) 38309d989d1SLuis R. Rodriguez return false; 38409d989d1SLuis R. Rodriguez 38509d989d1SLuis R. Rodriguez return true; 38609d989d1SLuis R. Rodriguez } 38709d989d1SLuis R. Rodriguez 388e9763c3cSJohannes Berg static const struct ieee80211_regdomain * 389e9763c3cSJohannes Berg reg_copy_regd(const struct ieee80211_regdomain *src_regd) 3903b377ea9SJohn W. Linville { 3913b377ea9SJohn W. Linville struct ieee80211_regdomain *regd; 392e9763c3cSJohannes Berg int size_of_regd; 3933b377ea9SJohn W. Linville unsigned int i; 3943b377ea9SJohn W. Linville 39582f20856SJohannes Berg size_of_regd = 39682f20856SJohannes Berg sizeof(struct ieee80211_regdomain) + 39782f20856SJohannes Berg src_regd->n_reg_rules * sizeof(struct ieee80211_reg_rule); 3983b377ea9SJohn W. Linville 3993b377ea9SJohn W. Linville regd = kzalloc(size_of_regd, GFP_KERNEL); 4003b377ea9SJohn W. Linville if (!regd) 401e9763c3cSJohannes Berg return ERR_PTR(-ENOMEM); 4023b377ea9SJohn W. Linville 4033b377ea9SJohn W. Linville memcpy(regd, src_regd, sizeof(struct ieee80211_regdomain)); 4043b377ea9SJohn W. Linville 4053b377ea9SJohn W. Linville for (i = 0; i < src_regd->n_reg_rules; i++) 4063b377ea9SJohn W. Linville memcpy(®d->reg_rules[i], &src_regd->reg_rules[i], 4073b377ea9SJohn W. Linville sizeof(struct ieee80211_reg_rule)); 4083b377ea9SJohn W. Linville 409e9763c3cSJohannes Berg return regd; 4103b377ea9SJohn W. Linville } 4113b377ea9SJohn W. Linville 4123b377ea9SJohn W. Linville #ifdef CONFIG_CFG80211_INTERNAL_REGDB 4133b377ea9SJohn W. Linville struct reg_regdb_search_request { 4143b377ea9SJohn W. Linville char alpha2[2]; 4153b377ea9SJohn W. Linville struct list_head list; 4163b377ea9SJohn W. Linville }; 4173b377ea9SJohn W. Linville 4183b377ea9SJohn W. Linville static LIST_HEAD(reg_regdb_search_list); 419368d06f5SJohn W. Linville static DEFINE_MUTEX(reg_regdb_search_mutex); 4203b377ea9SJohn W. Linville 4213b377ea9SJohn W. Linville static void reg_regdb_search(struct work_struct *work) 4223b377ea9SJohn W. Linville { 4233b377ea9SJohn W. Linville struct reg_regdb_search_request *request; 424e9763c3cSJohannes Berg const struct ieee80211_regdomain *curdom, *regdom = NULL; 425e9763c3cSJohannes Berg int i; 426a85d0d7fSLuis R. Rodriguez 4275fe231e8SJohannes Berg rtnl_lock(); 4283b377ea9SJohn W. Linville 429368d06f5SJohn W. Linville mutex_lock(®_regdb_search_mutex); 4303b377ea9SJohn W. Linville while (!list_empty(®_regdb_search_list)) { 4313b377ea9SJohn W. Linville request = list_first_entry(®_regdb_search_list, 4323b377ea9SJohn W. Linville struct reg_regdb_search_request, 4333b377ea9SJohn W. Linville list); 4343b377ea9SJohn W. Linville list_del(&request->list); 4353b377ea9SJohn W. Linville 4363b377ea9SJohn W. Linville for (i = 0; i < reg_regdb_size; i++) { 4373b377ea9SJohn W. Linville curdom = reg_regdb[i]; 4383b377ea9SJohn W. Linville 4391a919318SJohannes Berg if (alpha2_equal(request->alpha2, curdom->alpha2)) { 440e9763c3cSJohannes Berg regdom = reg_copy_regd(curdom); 4413b377ea9SJohn W. Linville break; 4423b377ea9SJohn W. Linville } 4433b377ea9SJohn W. Linville } 4443b377ea9SJohn W. Linville 4453b377ea9SJohn W. Linville kfree(request); 4463b377ea9SJohn W. Linville } 447368d06f5SJohn W. Linville mutex_unlock(®_regdb_search_mutex); 448a85d0d7fSLuis R. Rodriguez 449e9763c3cSJohannes Berg if (!IS_ERR_OR_NULL(regdom)) 450a85d0d7fSLuis R. Rodriguez set_regdom(regdom); 451a85d0d7fSLuis R. Rodriguez 4525fe231e8SJohannes Berg rtnl_unlock(); 4533b377ea9SJohn W. Linville } 4543b377ea9SJohn W. Linville 4553b377ea9SJohn W. Linville static DECLARE_WORK(reg_regdb_work, reg_regdb_search); 4563b377ea9SJohn W. Linville 4573b377ea9SJohn W. Linville static void reg_regdb_query(const char *alpha2) 4583b377ea9SJohn W. Linville { 4593b377ea9SJohn W. Linville struct reg_regdb_search_request *request; 4603b377ea9SJohn W. Linville 4613b377ea9SJohn W. Linville if (!alpha2) 4623b377ea9SJohn W. Linville return; 4633b377ea9SJohn W. Linville 4643b377ea9SJohn W. Linville request = kzalloc(sizeof(struct reg_regdb_search_request), GFP_KERNEL); 4653b377ea9SJohn W. Linville if (!request) 4663b377ea9SJohn W. Linville return; 4673b377ea9SJohn W. Linville 4683b377ea9SJohn W. Linville memcpy(request->alpha2, alpha2, 2); 4693b377ea9SJohn W. Linville 470368d06f5SJohn W. Linville mutex_lock(®_regdb_search_mutex); 4713b377ea9SJohn W. Linville list_add_tail(&request->list, ®_regdb_search_list); 472368d06f5SJohn W. Linville mutex_unlock(®_regdb_search_mutex); 4733b377ea9SJohn W. Linville 4743b377ea9SJohn W. Linville schedule_work(®_regdb_work); 4753b377ea9SJohn W. Linville } 47680007efeSLuis R. Rodriguez 47780007efeSLuis R. Rodriguez /* Feel free to add any other sanity checks here */ 47880007efeSLuis R. Rodriguez static void reg_regdb_size_check(void) 47980007efeSLuis R. Rodriguez { 48080007efeSLuis R. Rodriguez /* We should ideally BUILD_BUG_ON() but then random builds would fail */ 48180007efeSLuis R. Rodriguez WARN_ONCE(!reg_regdb_size, "db.txt is empty, you should update it..."); 48280007efeSLuis R. Rodriguez } 4833b377ea9SJohn W. Linville #else 48480007efeSLuis R. Rodriguez static inline void reg_regdb_size_check(void) {} 4853b377ea9SJohn W. Linville static inline void reg_regdb_query(const char *alpha2) {} 4863b377ea9SJohn W. Linville #endif /* CONFIG_CFG80211_INTERNAL_REGDB */ 4873b377ea9SJohn W. Linville 488fb1fc7adSLuis R. Rodriguez /* 489fb1fc7adSLuis R. Rodriguez * This lets us keep regulatory code which is updated on a regulatory 4904d9d88d1SScott James Remnant * basis in userspace. Country information is filled in by 4914d9d88d1SScott James Remnant * reg_device_uevent 492fb1fc7adSLuis R. Rodriguez */ 493b2e1b302SLuis R. Rodriguez static int call_crda(const char *alpha2) 494b2e1b302SLuis R. Rodriguez { 495b2e1b302SLuis R. Rodriguez if (!is_world_regdom((char *) alpha2)) 496e9c0268fSJoe Perches pr_info("Calling CRDA for country: %c%c\n", 497b2e1b302SLuis R. Rodriguez alpha2[0], alpha2[1]); 498b2e1b302SLuis R. Rodriguez else 499e9c0268fSJoe Perches pr_info("Calling CRDA to update world regulatory domain\n"); 5008318d78aSJohannes Berg 5013b377ea9SJohn W. Linville /* query internal regulatory database (if it exists) */ 5023b377ea9SJohn W. Linville reg_regdb_query(alpha2); 5033b377ea9SJohn W. Linville 5044d9d88d1SScott James Remnant return kobject_uevent(®_pdev->dev.kobj, KOBJ_CHANGE); 505b2e1b302SLuis R. Rodriguez } 506b2e1b302SLuis R. Rodriguez 507fe6631ffSLuis R. Rodriguez static enum reg_request_treatment 508fe6631ffSLuis R. Rodriguez reg_call_crda(struct regulatory_request *request) 509fe6631ffSLuis R. Rodriguez { 510fe6631ffSLuis R. Rodriguez if (call_crda(request->alpha2)) 511fe6631ffSLuis R. Rodriguez return REG_REQ_IGNORE; 512fe6631ffSLuis R. Rodriguez return REG_REQ_OK; 513fe6631ffSLuis R. Rodriguez } 514fe6631ffSLuis R. Rodriguez 515e438768fSLuis R. Rodriguez bool reg_is_valid_request(const char *alpha2) 516b2e1b302SLuis R. Rodriguez { 517c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 51861405e97SLuis R. Rodriguez 519c492db37SJohannes Berg if (!lr || lr->processed) 520f6037d09SJohannes Berg return false; 521f6037d09SJohannes Berg 522c492db37SJohannes Berg return alpha2_equal(lr->alpha2, alpha2); 523b2e1b302SLuis R. Rodriguez } 524b2e1b302SLuis R. Rodriguez 525b2e1b302SLuis R. Rodriguez /* Sanity check on a regulatory rule */ 526a3d2eaf0SJohannes Berg static bool is_valid_reg_rule(const struct ieee80211_reg_rule *rule) 527b2e1b302SLuis R. Rodriguez { 528a3d2eaf0SJohannes Berg const struct ieee80211_freq_range *freq_range = &rule->freq_range; 529b2e1b302SLuis R. Rodriguez u32 freq_diff; 530b2e1b302SLuis R. Rodriguez 53191e99004SLuis R. Rodriguez if (freq_range->start_freq_khz <= 0 || freq_range->end_freq_khz <= 0) 532b2e1b302SLuis R. Rodriguez return false; 533b2e1b302SLuis R. Rodriguez 534b2e1b302SLuis R. Rodriguez if (freq_range->start_freq_khz > freq_range->end_freq_khz) 535b2e1b302SLuis R. Rodriguez return false; 536b2e1b302SLuis R. Rodriguez 537b2e1b302SLuis R. Rodriguez freq_diff = freq_range->end_freq_khz - freq_range->start_freq_khz; 538b2e1b302SLuis R. Rodriguez 539bd05f28eSRoel Kluin if (freq_range->end_freq_khz <= freq_range->start_freq_khz || 540bd05f28eSRoel Kluin freq_range->max_bandwidth_khz > freq_diff) 541b2e1b302SLuis R. Rodriguez return false; 542b2e1b302SLuis R. Rodriguez 543b2e1b302SLuis R. Rodriguez return true; 544b2e1b302SLuis R. Rodriguez } 545b2e1b302SLuis R. Rodriguez 546a3d2eaf0SJohannes Berg static bool is_valid_rd(const struct ieee80211_regdomain *rd) 547b2e1b302SLuis R. Rodriguez { 548a3d2eaf0SJohannes Berg const struct ieee80211_reg_rule *reg_rule = NULL; 549b2e1b302SLuis R. Rodriguez unsigned int i; 550b2e1b302SLuis R. Rodriguez 551b2e1b302SLuis R. Rodriguez if (!rd->n_reg_rules) 552b2e1b302SLuis R. Rodriguez return false; 553b2e1b302SLuis R. Rodriguez 55488dc1c3fSLuis R. Rodriguez if (WARN_ON(rd->n_reg_rules > NL80211_MAX_SUPP_REG_RULES)) 55588dc1c3fSLuis R. Rodriguez return false; 55688dc1c3fSLuis R. Rodriguez 557b2e1b302SLuis R. Rodriguez for (i = 0; i < rd->n_reg_rules; i++) { 558b2e1b302SLuis R. Rodriguez reg_rule = &rd->reg_rules[i]; 559b2e1b302SLuis R. Rodriguez if (!is_valid_reg_rule(reg_rule)) 560b2e1b302SLuis R. Rodriguez return false; 561b2e1b302SLuis R. Rodriguez } 562b2e1b302SLuis R. Rodriguez 563b2e1b302SLuis R. Rodriguez return true; 564b2e1b302SLuis R. Rodriguez } 565b2e1b302SLuis R. Rodriguez 566038659e7SLuis R. Rodriguez static bool reg_does_bw_fit(const struct ieee80211_freq_range *freq_range, 567fe7ef5e9SJohannes Berg u32 center_freq_khz, u32 bw_khz) 568b2e1b302SLuis R. Rodriguez { 569038659e7SLuis R. Rodriguez u32 start_freq_khz, end_freq_khz; 570038659e7SLuis R. Rodriguez 571038659e7SLuis R. Rodriguez start_freq_khz = center_freq_khz - (bw_khz/2); 572038659e7SLuis R. Rodriguez end_freq_khz = center_freq_khz + (bw_khz/2); 573038659e7SLuis R. Rodriguez 574b2e1b302SLuis R. Rodriguez if (start_freq_khz >= freq_range->start_freq_khz && 575b2e1b302SLuis R. Rodriguez end_freq_khz <= freq_range->end_freq_khz) 576038659e7SLuis R. Rodriguez return true; 577038659e7SLuis R. Rodriguez 578038659e7SLuis R. Rodriguez return false; 579b2e1b302SLuis R. Rodriguez } 580b2e1b302SLuis R. Rodriguez 5810c7dc45dSLuis R. Rodriguez /** 5820c7dc45dSLuis R. Rodriguez * freq_in_rule_band - tells us if a frequency is in a frequency band 5830c7dc45dSLuis R. Rodriguez * @freq_range: frequency rule we want to query 5840c7dc45dSLuis R. Rodriguez * @freq_khz: frequency we are inquiring about 5850c7dc45dSLuis R. Rodriguez * 5860c7dc45dSLuis R. Rodriguez * This lets us know if a specific frequency rule is or is not relevant to 5870c7dc45dSLuis R. Rodriguez * a specific frequency's band. Bands are device specific and artificial 58864629b9dSVladimir Kondratiev * definitions (the "2.4 GHz band", the "5 GHz band" and the "60GHz band"), 58964629b9dSVladimir Kondratiev * however it is safe for now to assume that a frequency rule should not be 59064629b9dSVladimir Kondratiev * part of a frequency's band if the start freq or end freq are off by more 59164629b9dSVladimir Kondratiev * than 2 GHz for the 2.4 and 5 GHz bands, and by more than 10 GHz for the 59264629b9dSVladimir Kondratiev * 60 GHz band. 5930c7dc45dSLuis R. Rodriguez * This resolution can be lowered and should be considered as we add 5940c7dc45dSLuis R. Rodriguez * regulatory rule support for other "bands". 5950c7dc45dSLuis R. Rodriguez **/ 5960c7dc45dSLuis R. Rodriguez static bool freq_in_rule_band(const struct ieee80211_freq_range *freq_range, 5970c7dc45dSLuis R. Rodriguez u32 freq_khz) 5980c7dc45dSLuis R. Rodriguez { 5990c7dc45dSLuis R. Rodriguez #define ONE_GHZ_IN_KHZ 1000000 60064629b9dSVladimir Kondratiev /* 60164629b9dSVladimir Kondratiev * From 802.11ad: directional multi-gigabit (DMG): 60264629b9dSVladimir Kondratiev * Pertaining to operation in a frequency band containing a channel 60364629b9dSVladimir Kondratiev * with the Channel starting frequency above 45 GHz. 60464629b9dSVladimir Kondratiev */ 60564629b9dSVladimir Kondratiev u32 limit = freq_khz > 45 * ONE_GHZ_IN_KHZ ? 60664629b9dSVladimir Kondratiev 10 * ONE_GHZ_IN_KHZ : 2 * ONE_GHZ_IN_KHZ; 60764629b9dSVladimir Kondratiev if (abs(freq_khz - freq_range->start_freq_khz) <= limit) 6080c7dc45dSLuis R. Rodriguez return true; 60964629b9dSVladimir Kondratiev if (abs(freq_khz - freq_range->end_freq_khz) <= limit) 6100c7dc45dSLuis R. Rodriguez return true; 6110c7dc45dSLuis R. Rodriguez return false; 6120c7dc45dSLuis R. Rodriguez #undef ONE_GHZ_IN_KHZ 6130c7dc45dSLuis R. Rodriguez } 6140c7dc45dSLuis R. Rodriguez 615fb1fc7adSLuis R. Rodriguez /* 616adbfb058SLuis R. Rodriguez * Later on we can perhaps use the more restrictive DFS 617adbfb058SLuis R. Rodriguez * region but we don't have information for that yet so 618adbfb058SLuis R. Rodriguez * for now simply disallow conflicts. 619adbfb058SLuis R. Rodriguez */ 620adbfb058SLuis R. Rodriguez static enum nl80211_dfs_regions 621adbfb058SLuis R. Rodriguez reg_intersect_dfs_region(const enum nl80211_dfs_regions dfs_region1, 622adbfb058SLuis R. Rodriguez const enum nl80211_dfs_regions dfs_region2) 623adbfb058SLuis R. Rodriguez { 624adbfb058SLuis R. Rodriguez if (dfs_region1 != dfs_region2) 625adbfb058SLuis R. Rodriguez return NL80211_DFS_UNSET; 626adbfb058SLuis R. Rodriguez return dfs_region1; 627adbfb058SLuis R. Rodriguez } 628adbfb058SLuis R. Rodriguez 629adbfb058SLuis R. Rodriguez /* 630fb1fc7adSLuis R. Rodriguez * Helper for regdom_intersect(), this does the real 631fb1fc7adSLuis R. Rodriguez * mathematical intersection fun 632fb1fc7adSLuis R. Rodriguez */ 6331a919318SJohannes Berg static int reg_rules_intersect(const struct ieee80211_reg_rule *rule1, 6349c96477dSLuis R. Rodriguez const struct ieee80211_reg_rule *rule2, 6359c96477dSLuis R. Rodriguez struct ieee80211_reg_rule *intersected_rule) 6369c96477dSLuis R. Rodriguez { 6379c96477dSLuis R. Rodriguez const struct ieee80211_freq_range *freq_range1, *freq_range2; 6389c96477dSLuis R. Rodriguez struct ieee80211_freq_range *freq_range; 6399c96477dSLuis R. Rodriguez const struct ieee80211_power_rule *power_rule1, *power_rule2; 6409c96477dSLuis R. Rodriguez struct ieee80211_power_rule *power_rule; 6419c96477dSLuis R. Rodriguez u32 freq_diff; 6429c96477dSLuis R. Rodriguez 6439c96477dSLuis R. Rodriguez freq_range1 = &rule1->freq_range; 6449c96477dSLuis R. Rodriguez freq_range2 = &rule2->freq_range; 6459c96477dSLuis R. Rodriguez freq_range = &intersected_rule->freq_range; 6469c96477dSLuis R. Rodriguez 6479c96477dSLuis R. Rodriguez power_rule1 = &rule1->power_rule; 6489c96477dSLuis R. Rodriguez power_rule2 = &rule2->power_rule; 6499c96477dSLuis R. Rodriguez power_rule = &intersected_rule->power_rule; 6509c96477dSLuis R. Rodriguez 6519c96477dSLuis R. Rodriguez freq_range->start_freq_khz = max(freq_range1->start_freq_khz, 6529c96477dSLuis R. Rodriguez freq_range2->start_freq_khz); 6539c96477dSLuis R. Rodriguez freq_range->end_freq_khz = min(freq_range1->end_freq_khz, 6549c96477dSLuis R. Rodriguez freq_range2->end_freq_khz); 6559c96477dSLuis R. Rodriguez freq_range->max_bandwidth_khz = min(freq_range1->max_bandwidth_khz, 6569c96477dSLuis R. Rodriguez freq_range2->max_bandwidth_khz); 6579c96477dSLuis R. Rodriguez 6589c96477dSLuis R. Rodriguez freq_diff = freq_range->end_freq_khz - freq_range->start_freq_khz; 6599c96477dSLuis R. Rodriguez if (freq_range->max_bandwidth_khz > freq_diff) 6609c96477dSLuis R. Rodriguez freq_range->max_bandwidth_khz = freq_diff; 6619c96477dSLuis R. Rodriguez 6629c96477dSLuis R. Rodriguez power_rule->max_eirp = min(power_rule1->max_eirp, 6639c96477dSLuis R. Rodriguez power_rule2->max_eirp); 6649c96477dSLuis R. Rodriguez power_rule->max_antenna_gain = min(power_rule1->max_antenna_gain, 6659c96477dSLuis R. Rodriguez power_rule2->max_antenna_gain); 6669c96477dSLuis R. Rodriguez 6671a919318SJohannes Berg intersected_rule->flags = rule1->flags | rule2->flags; 6689c96477dSLuis R. Rodriguez 6699c96477dSLuis R. Rodriguez if (!is_valid_reg_rule(intersected_rule)) 6709c96477dSLuis R. Rodriguez return -EINVAL; 6719c96477dSLuis R. Rodriguez 6729c96477dSLuis R. Rodriguez return 0; 6739c96477dSLuis R. Rodriguez } 6749c96477dSLuis R. Rodriguez 6759c96477dSLuis R. Rodriguez /** 6769c96477dSLuis R. Rodriguez * regdom_intersect - do the intersection between two regulatory domains 6779c96477dSLuis R. Rodriguez * @rd1: first regulatory domain 6789c96477dSLuis R. Rodriguez * @rd2: second regulatory domain 6799c96477dSLuis R. Rodriguez * 6809c96477dSLuis R. Rodriguez * Use this function to get the intersection between two regulatory domains. 6819c96477dSLuis R. Rodriguez * Once completed we will mark the alpha2 for the rd as intersected, "98", 6829c96477dSLuis R. Rodriguez * as no one single alpha2 can represent this regulatory domain. 6839c96477dSLuis R. Rodriguez * 6849c96477dSLuis R. Rodriguez * Returns a pointer to the regulatory domain structure which will hold the 6859c96477dSLuis R. Rodriguez * resulting intersection of rules between rd1 and rd2. We will 6869c96477dSLuis R. Rodriguez * kzalloc() this structure for you. 6879c96477dSLuis R. Rodriguez */ 6881a919318SJohannes Berg static struct ieee80211_regdomain * 6891a919318SJohannes Berg regdom_intersect(const struct ieee80211_regdomain *rd1, 6909c96477dSLuis R. Rodriguez const struct ieee80211_regdomain *rd2) 6919c96477dSLuis R. Rodriguez { 6929c96477dSLuis R. Rodriguez int r, size_of_regd; 6939c96477dSLuis R. Rodriguez unsigned int x, y; 6949c96477dSLuis R. Rodriguez unsigned int num_rules = 0, rule_idx = 0; 6959c96477dSLuis R. Rodriguez const struct ieee80211_reg_rule *rule1, *rule2; 6969c96477dSLuis R. Rodriguez struct ieee80211_reg_rule *intersected_rule; 6979c96477dSLuis R. Rodriguez struct ieee80211_regdomain *rd; 6989c96477dSLuis R. Rodriguez /* This is just a dummy holder to help us count */ 69974f53cd8SJohannes Berg struct ieee80211_reg_rule dummy_rule; 7009c96477dSLuis R. Rodriguez 7019c96477dSLuis R. Rodriguez if (!rd1 || !rd2) 7029c96477dSLuis R. Rodriguez return NULL; 7039c96477dSLuis R. Rodriguez 704fb1fc7adSLuis R. Rodriguez /* 705fb1fc7adSLuis R. Rodriguez * First we get a count of the rules we'll need, then we actually 7069c96477dSLuis R. Rodriguez * build them. This is to so we can malloc() and free() a 7079c96477dSLuis R. Rodriguez * regdomain once. The reason we use reg_rules_intersect() here 7089c96477dSLuis R. Rodriguez * is it will return -EINVAL if the rule computed makes no sense. 709fb1fc7adSLuis R. Rodriguez * All rules that do check out OK are valid. 710fb1fc7adSLuis R. Rodriguez */ 7119c96477dSLuis R. Rodriguez 7129c96477dSLuis R. Rodriguez for (x = 0; x < rd1->n_reg_rules; x++) { 7139c96477dSLuis R. Rodriguez rule1 = &rd1->reg_rules[x]; 7149c96477dSLuis R. Rodriguez for (y = 0; y < rd2->n_reg_rules; y++) { 7159c96477dSLuis R. Rodriguez rule2 = &rd2->reg_rules[y]; 71674f53cd8SJohannes Berg if (!reg_rules_intersect(rule1, rule2, &dummy_rule)) 7179c96477dSLuis R. Rodriguez num_rules++; 7189c96477dSLuis R. Rodriguez } 7199c96477dSLuis R. Rodriguez } 7209c96477dSLuis R. Rodriguez 7219c96477dSLuis R. Rodriguez if (!num_rules) 7229c96477dSLuis R. Rodriguez return NULL; 7239c96477dSLuis R. Rodriguez 7249c96477dSLuis R. Rodriguez size_of_regd = sizeof(struct ieee80211_regdomain) + 72582f20856SJohannes Berg num_rules * sizeof(struct ieee80211_reg_rule); 7269c96477dSLuis R. Rodriguez 7279c96477dSLuis R. Rodriguez rd = kzalloc(size_of_regd, GFP_KERNEL); 7289c96477dSLuis R. Rodriguez if (!rd) 7299c96477dSLuis R. Rodriguez return NULL; 7309c96477dSLuis R. Rodriguez 7318a57fff0SJohannes Berg for (x = 0; x < rd1->n_reg_rules && rule_idx < num_rules; x++) { 7329c96477dSLuis R. Rodriguez rule1 = &rd1->reg_rules[x]; 7338a57fff0SJohannes Berg for (y = 0; y < rd2->n_reg_rules && rule_idx < num_rules; y++) { 7349c96477dSLuis R. Rodriguez rule2 = &rd2->reg_rules[y]; 735fb1fc7adSLuis R. Rodriguez /* 736fb1fc7adSLuis R. Rodriguez * This time around instead of using the stack lets 7379c96477dSLuis R. Rodriguez * write to the target rule directly saving ourselves 738fb1fc7adSLuis R. Rodriguez * a memcpy() 739fb1fc7adSLuis R. Rodriguez */ 7409c96477dSLuis R. Rodriguez intersected_rule = &rd->reg_rules[rule_idx]; 7411a919318SJohannes Berg r = reg_rules_intersect(rule1, rule2, intersected_rule); 742fb1fc7adSLuis R. Rodriguez /* 743fb1fc7adSLuis R. Rodriguez * No need to memset here the intersected rule here as 744fb1fc7adSLuis R. Rodriguez * we're not using the stack anymore 745fb1fc7adSLuis R. Rodriguez */ 7469c96477dSLuis R. Rodriguez if (r) 7479c96477dSLuis R. Rodriguez continue; 7489c96477dSLuis R. Rodriguez rule_idx++; 7499c96477dSLuis R. Rodriguez } 7509c96477dSLuis R. Rodriguez } 7519c96477dSLuis R. Rodriguez 7529c96477dSLuis R. Rodriguez if (rule_idx != num_rules) { 7539c96477dSLuis R. Rodriguez kfree(rd); 7549c96477dSLuis R. Rodriguez return NULL; 7559c96477dSLuis R. Rodriguez } 7569c96477dSLuis R. Rodriguez 7579c96477dSLuis R. Rodriguez rd->n_reg_rules = num_rules; 7589c96477dSLuis R. Rodriguez rd->alpha2[0] = '9'; 7599c96477dSLuis R. Rodriguez rd->alpha2[1] = '8'; 760adbfb058SLuis R. Rodriguez rd->dfs_region = reg_intersect_dfs_region(rd1->dfs_region, 761adbfb058SLuis R. Rodriguez rd2->dfs_region); 7629c96477dSLuis R. Rodriguez 7639c96477dSLuis R. Rodriguez return rd; 7649c96477dSLuis R. Rodriguez } 7659c96477dSLuis R. Rodriguez 766fb1fc7adSLuis R. Rodriguez /* 767fb1fc7adSLuis R. Rodriguez * XXX: add support for the rest of enum nl80211_reg_rule_flags, we may 768fb1fc7adSLuis R. Rodriguez * want to just have the channel structure use these 769fb1fc7adSLuis R. Rodriguez */ 770b2e1b302SLuis R. Rodriguez static u32 map_regdom_flags(u32 rd_flags) 771b2e1b302SLuis R. Rodriguez { 772b2e1b302SLuis R. Rodriguez u32 channel_flags = 0; 7738fe02e16SLuis R. Rodriguez if (rd_flags & NL80211_RRF_NO_IR_ALL) 7748fe02e16SLuis R. Rodriguez channel_flags |= IEEE80211_CHAN_NO_IR; 775b2e1b302SLuis R. Rodriguez if (rd_flags & NL80211_RRF_DFS) 776b2e1b302SLuis R. Rodriguez channel_flags |= IEEE80211_CHAN_RADAR; 77703f6b084SSeth Forshee if (rd_flags & NL80211_RRF_NO_OFDM) 77803f6b084SSeth Forshee channel_flags |= IEEE80211_CHAN_NO_OFDM; 779b2e1b302SLuis R. Rodriguez return channel_flags; 780b2e1b302SLuis R. Rodriguez } 781b2e1b302SLuis R. Rodriguez 782361c9c8bSJohannes Berg static const struct ieee80211_reg_rule * 783361c9c8bSJohannes Berg freq_reg_info_regd(struct wiphy *wiphy, u32 center_freq, 7845d885b99SJohannes Berg const struct ieee80211_regdomain *regd) 7858318d78aSJohannes Berg { 7868318d78aSJohannes Berg int i; 7870c7dc45dSLuis R. Rodriguez bool band_rule_found = false; 788038659e7SLuis R. Rodriguez bool bw_fits = false; 789038659e7SLuis R. Rodriguez 7903e0c3ff3SLuis R. Rodriguez if (!regd) 791361c9c8bSJohannes Berg return ERR_PTR(-EINVAL); 792b2e1b302SLuis R. Rodriguez 7933e0c3ff3SLuis R. Rodriguez for (i = 0; i < regd->n_reg_rules; i++) { 794b2e1b302SLuis R. Rodriguez const struct ieee80211_reg_rule *rr; 795b2e1b302SLuis R. Rodriguez const struct ieee80211_freq_range *fr = NULL; 796b2e1b302SLuis R. Rodriguez 7973e0c3ff3SLuis R. Rodriguez rr = ®d->reg_rules[i]; 798b2e1b302SLuis R. Rodriguez fr = &rr->freq_range; 7990c7dc45dSLuis R. Rodriguez 800fb1fc7adSLuis R. Rodriguez /* 801fb1fc7adSLuis R. Rodriguez * We only need to know if one frequency rule was 8020c7dc45dSLuis R. Rodriguez * was in center_freq's band, that's enough, so lets 803fb1fc7adSLuis R. Rodriguez * not overwrite it once found 804fb1fc7adSLuis R. Rodriguez */ 8050c7dc45dSLuis R. Rodriguez if (!band_rule_found) 8060c7dc45dSLuis R. Rodriguez band_rule_found = freq_in_rule_band(fr, center_freq); 8070c7dc45dSLuis R. Rodriguez 808fe7ef5e9SJohannes Berg bw_fits = reg_does_bw_fit(fr, center_freq, MHZ_TO_KHZ(20)); 8090c7dc45dSLuis R. Rodriguez 810361c9c8bSJohannes Berg if (band_rule_found && bw_fits) 811361c9c8bSJohannes Berg return rr; 8128318d78aSJohannes Berg } 8138318d78aSJohannes Berg 8140c7dc45dSLuis R. Rodriguez if (!band_rule_found) 815361c9c8bSJohannes Berg return ERR_PTR(-ERANGE); 8160c7dc45dSLuis R. Rodriguez 817361c9c8bSJohannes Berg return ERR_PTR(-EINVAL); 818b2e1b302SLuis R. Rodriguez } 819b2e1b302SLuis R. Rodriguez 820361c9c8bSJohannes Berg const struct ieee80211_reg_rule *freq_reg_info(struct wiphy *wiphy, 821361c9c8bSJohannes Berg u32 center_freq) 8221fa25e41SLuis R. Rodriguez { 8235d885b99SJohannes Berg const struct ieee80211_regdomain *regd; 824c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 8251a919318SJohannes Berg 8265d885b99SJohannes Berg /* 8275d885b99SJohannes Berg * Follow the driver's regulatory domain, if present, unless a country 8285d885b99SJohannes Berg * IE has been processed or a user wants to help complaince further 8295d885b99SJohannes Berg */ 830c492db37SJohannes Berg if (lr->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE && 831c492db37SJohannes Berg lr->initiator != NL80211_REGDOM_SET_BY_USER && 8325d885b99SJohannes Berg wiphy->regd) 833458f4f9eSJohannes Berg regd = get_wiphy_regdom(wiphy); 8345d885b99SJohannes Berg else 835458f4f9eSJohannes Berg regd = get_cfg80211_regdom(); 8365d885b99SJohannes Berg 837361c9c8bSJohannes Berg return freq_reg_info_regd(wiphy, center_freq, regd); 8381fa25e41SLuis R. Rodriguez } 8394f366c5dSJohn W. Linville EXPORT_SYMBOL(freq_reg_info); 840b2e1b302SLuis R. Rodriguez 841034c6d6eSLuis R. Rodriguez const char *reg_initiator_name(enum nl80211_reg_initiator initiator) 842926a0a09SLuis R. Rodriguez { 843926a0a09SLuis R. Rodriguez switch (initiator) { 844926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_CORE: 845034c6d6eSLuis R. Rodriguez return "core"; 846926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_USER: 847034c6d6eSLuis R. Rodriguez return "user"; 848926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_DRIVER: 849034c6d6eSLuis R. Rodriguez return "driver"; 850926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_COUNTRY_IE: 851034c6d6eSLuis R. Rodriguez return "country IE"; 852926a0a09SLuis R. Rodriguez default: 853926a0a09SLuis R. Rodriguez WARN_ON(1); 854034c6d6eSLuis R. Rodriguez return "bug"; 855926a0a09SLuis R. Rodriguez } 856926a0a09SLuis R. Rodriguez } 857034c6d6eSLuis R. Rodriguez EXPORT_SYMBOL(reg_initiator_name); 858e702d3cfSLuis R. Rodriguez 859034c6d6eSLuis R. Rodriguez #ifdef CONFIG_CFG80211_REG_DEBUG 860e702d3cfSLuis R. Rodriguez static void chan_reg_rule_print_dbg(struct ieee80211_channel *chan, 861e702d3cfSLuis R. Rodriguez const struct ieee80211_reg_rule *reg_rule) 862e702d3cfSLuis R. Rodriguez { 863e702d3cfSLuis R. Rodriguez const struct ieee80211_power_rule *power_rule; 864e702d3cfSLuis R. Rodriguez const struct ieee80211_freq_range *freq_range; 865e702d3cfSLuis R. Rodriguez char max_antenna_gain[32]; 866e702d3cfSLuis R. Rodriguez 867e702d3cfSLuis R. Rodriguez power_rule = ®_rule->power_rule; 868e702d3cfSLuis R. Rodriguez freq_range = ®_rule->freq_range; 869e702d3cfSLuis R. Rodriguez 870e702d3cfSLuis R. Rodriguez if (!power_rule->max_antenna_gain) 871e702d3cfSLuis R. Rodriguez snprintf(max_antenna_gain, 32, "N/A"); 872e702d3cfSLuis R. Rodriguez else 873e702d3cfSLuis R. Rodriguez snprintf(max_antenna_gain, 32, "%d", power_rule->max_antenna_gain); 874e702d3cfSLuis R. Rodriguez 875fe7ef5e9SJohannes Berg REG_DBG_PRINT("Updating information on frequency %d MHz with regulatory rule:\n", 876fe7ef5e9SJohannes Berg chan->center_freq); 877e702d3cfSLuis R. Rodriguez 87856e6786eSPavel Roskin REG_DBG_PRINT("%d KHz - %d KHz @ %d KHz), (%s mBi, %d mBm)\n", 8791a919318SJohannes Berg freq_range->start_freq_khz, freq_range->end_freq_khz, 8801a919318SJohannes Berg freq_range->max_bandwidth_khz, max_antenna_gain, 881e702d3cfSLuis R. Rodriguez power_rule->max_eirp); 882e702d3cfSLuis R. Rodriguez } 883e702d3cfSLuis R. Rodriguez #else 884e702d3cfSLuis R. Rodriguez static void chan_reg_rule_print_dbg(struct ieee80211_channel *chan, 885e702d3cfSLuis R. Rodriguez const struct ieee80211_reg_rule *reg_rule) 886e702d3cfSLuis R. Rodriguez { 887e702d3cfSLuis R. Rodriguez return; 888e702d3cfSLuis R. Rodriguez } 889926a0a09SLuis R. Rodriguez #endif 890926a0a09SLuis R. Rodriguez 891038659e7SLuis R. Rodriguez /* 892038659e7SLuis R. Rodriguez * Note that right now we assume the desired channel bandwidth 893038659e7SLuis R. Rodriguez * is always 20 MHz for each individual channel (HT40 uses 20 MHz 894fe7ef5e9SJohannes Berg * per channel, the primary and the extension channel). 895038659e7SLuis R. Rodriguez */ 8967ca43d03SLuis R. Rodriguez static void handle_channel(struct wiphy *wiphy, 8977ca43d03SLuis R. Rodriguez enum nl80211_reg_initiator initiator, 898fdc9d7b2SJohannes Berg struct ieee80211_channel *chan) 899b2e1b302SLuis R. Rodriguez { 900038659e7SLuis R. Rodriguez u32 flags, bw_flags = 0; 901b2e1b302SLuis R. Rodriguez const struct ieee80211_reg_rule *reg_rule = NULL; 902b2e1b302SLuis R. Rodriguez const struct ieee80211_power_rule *power_rule = NULL; 903038659e7SLuis R. Rodriguez const struct ieee80211_freq_range *freq_range = NULL; 904fe33eb39SLuis R. Rodriguez struct wiphy *request_wiphy = NULL; 905c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 906a92a3ce7SLuis R. Rodriguez 907c492db37SJohannes Berg request_wiphy = wiphy_idx_to_wiphy(lr->wiphy_idx); 908a92a3ce7SLuis R. Rodriguez 909a92a3ce7SLuis R. Rodriguez flags = chan->orig_flags; 910b2e1b302SLuis R. Rodriguez 911361c9c8bSJohannes Berg reg_rule = freq_reg_info(wiphy, MHZ_TO_KHZ(chan->center_freq)); 912361c9c8bSJohannes Berg if (IS_ERR(reg_rule)) { 913ca4ffe8fSLuis R. Rodriguez /* 914ca4ffe8fSLuis R. Rodriguez * We will disable all channels that do not match our 91525985edcSLucas De Marchi * received regulatory rule unless the hint is coming 916ca4ffe8fSLuis R. Rodriguez * from a Country IE and the Country IE had no information 917ca4ffe8fSLuis R. Rodriguez * about a band. The IEEE 802.11 spec allows for an AP 918ca4ffe8fSLuis R. Rodriguez * to send only a subset of the regulatory rules allowed, 919ca4ffe8fSLuis R. Rodriguez * so an AP in the US that only supports 2.4 GHz may only send 920ca4ffe8fSLuis R. Rodriguez * a country IE with information for the 2.4 GHz band 921ca4ffe8fSLuis R. Rodriguez * while 5 GHz is still supported. 922ca4ffe8fSLuis R. Rodriguez */ 923ca4ffe8fSLuis R. Rodriguez if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE && 924361c9c8bSJohannes Berg PTR_ERR(reg_rule) == -ERANGE) 9258318d78aSJohannes Berg return; 9268318d78aSJohannes Berg 927cc493e4fSLuis R. Rodriguez if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER && 928cc493e4fSLuis R. Rodriguez request_wiphy && request_wiphy == wiphy && 929a2f73b6cSLuis R. Rodriguez request_wiphy->regulatory_flags & REGULATORY_STRICT_REG) { 930cc493e4fSLuis R. Rodriguez REG_DBG_PRINT("Disabling freq %d MHz for good\n", 931cc493e4fSLuis R. Rodriguez chan->center_freq); 932cc493e4fSLuis R. Rodriguez chan->orig_flags |= IEEE80211_CHAN_DISABLED; 933cc493e4fSLuis R. Rodriguez chan->flags = chan->orig_flags; 934cc493e4fSLuis R. Rodriguez } else { 935cc493e4fSLuis R. Rodriguez REG_DBG_PRINT("Disabling freq %d MHz\n", 936cc493e4fSLuis R. Rodriguez chan->center_freq); 937990de49fSJohannes Berg chan->flags |= IEEE80211_CHAN_DISABLED; 938cc493e4fSLuis R. Rodriguez } 939ca4ffe8fSLuis R. Rodriguez return; 940ca4ffe8fSLuis R. Rodriguez } 941ca4ffe8fSLuis R. Rodriguez 942fe7ef5e9SJohannes Berg chan_reg_rule_print_dbg(chan, reg_rule); 943e702d3cfSLuis R. Rodriguez 944b2e1b302SLuis R. Rodriguez power_rule = ®_rule->power_rule; 945038659e7SLuis R. Rodriguez freq_range = ®_rule->freq_range; 946038659e7SLuis R. Rodriguez 947038659e7SLuis R. Rodriguez if (freq_range->max_bandwidth_khz < MHZ_TO_KHZ(40)) 948038659e7SLuis R. Rodriguez bw_flags = IEEE80211_CHAN_NO_HT40; 949c7a6ee27SJohannes Berg if (freq_range->max_bandwidth_khz < MHZ_TO_KHZ(80)) 950c7a6ee27SJohannes Berg bw_flags |= IEEE80211_CHAN_NO_80MHZ; 951c7a6ee27SJohannes Berg if (freq_range->max_bandwidth_khz < MHZ_TO_KHZ(160)) 952c7a6ee27SJohannes Berg bw_flags |= IEEE80211_CHAN_NO_160MHZ; 953b2e1b302SLuis R. Rodriguez 954c492db37SJohannes Berg if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER && 955806a9e39SLuis R. Rodriguez request_wiphy && request_wiphy == wiphy && 956a2f73b6cSLuis R. Rodriguez request_wiphy->regulatory_flags & REGULATORY_STRICT_REG) { 957fb1fc7adSLuis R. Rodriguez /* 95825985edcSLucas De Marchi * This guarantees the driver's requested regulatory domain 959f976376dSLuis R. Rodriguez * will always be used as a base for further regulatory 960fb1fc7adSLuis R. Rodriguez * settings 961fb1fc7adSLuis R. Rodriguez */ 962f976376dSLuis R. Rodriguez chan->flags = chan->orig_flags = 963038659e7SLuis R. Rodriguez map_regdom_flags(reg_rule->flags) | bw_flags; 964f976376dSLuis R. Rodriguez chan->max_antenna_gain = chan->orig_mag = 965f976376dSLuis R. Rodriguez (int) MBI_TO_DBI(power_rule->max_antenna_gain); 966279f0f55SFelix Fietkau chan->max_reg_power = chan->max_power = chan->orig_mpwr = 967f976376dSLuis R. Rodriguez (int) MBM_TO_DBM(power_rule->max_eirp); 968f976376dSLuis R. Rodriguez return; 969f976376dSLuis R. Rodriguez } 970f976376dSLuis R. Rodriguez 97104f39047SSimon Wunderlich chan->dfs_state = NL80211_DFS_USABLE; 97204f39047SSimon Wunderlich chan->dfs_state_entered = jiffies; 97304f39047SSimon Wunderlich 974aa3d7eefSRajkumar Manoharan chan->beacon_found = false; 975038659e7SLuis R. Rodriguez chan->flags = flags | bw_flags | map_regdom_flags(reg_rule->flags); 9761a919318SJohannes Berg chan->max_antenna_gain = 9771a919318SJohannes Berg min_t(int, chan->orig_mag, 9781a919318SJohannes Berg MBI_TO_DBI(power_rule->max_antenna_gain)); 979eccc068eSHong Wu chan->max_reg_power = (int) MBM_TO_DBM(power_rule->max_eirp); 9805e31fc08SStanislaw Gruszka if (chan->orig_mpwr) { 9815e31fc08SStanislaw Gruszka /* 982a09a85a0SLuis R. Rodriguez * Devices that use REGULATORY_COUNTRY_IE_FOLLOW_POWER 983a09a85a0SLuis R. Rodriguez * will always follow the passed country IE power settings. 9845e31fc08SStanislaw Gruszka */ 9855e31fc08SStanislaw Gruszka if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE && 986a09a85a0SLuis R. Rodriguez wiphy->regulatory_flags & REGULATORY_COUNTRY_IE_FOLLOW_POWER) 9875e31fc08SStanislaw Gruszka chan->max_power = chan->max_reg_power; 9885e31fc08SStanislaw Gruszka else 9895e31fc08SStanislaw Gruszka chan->max_power = min(chan->orig_mpwr, 9905e31fc08SStanislaw Gruszka chan->max_reg_power); 9915e31fc08SStanislaw Gruszka } else 9925e31fc08SStanislaw Gruszka chan->max_power = chan->max_reg_power; 9938318d78aSJohannes Berg } 9948318d78aSJohannes Berg 9957ca43d03SLuis R. Rodriguez static void handle_band(struct wiphy *wiphy, 996fdc9d7b2SJohannes Berg enum nl80211_reg_initiator initiator, 997fdc9d7b2SJohannes Berg struct ieee80211_supported_band *sband) 9988318d78aSJohannes Berg { 999a92a3ce7SLuis R. Rodriguez unsigned int i; 1000a92a3ce7SLuis R. Rodriguez 1001fdc9d7b2SJohannes Berg if (!sband) 1002fdc9d7b2SJohannes Berg return; 10038318d78aSJohannes Berg 10048318d78aSJohannes Berg for (i = 0; i < sband->n_channels; i++) 1005fdc9d7b2SJohannes Berg handle_channel(wiphy, initiator, &sband->channels[i]); 10068318d78aSJohannes Berg } 10078318d78aSJohannes Berg 100857b5ce07SLuis R. Rodriguez static bool reg_request_cell_base(struct regulatory_request *request) 100957b5ce07SLuis R. Rodriguez { 101057b5ce07SLuis R. Rodriguez if (request->initiator != NL80211_REGDOM_SET_BY_USER) 101157b5ce07SLuis R. Rodriguez return false; 10121a919318SJohannes Berg return request->user_reg_hint_type == NL80211_USER_REG_HINT_CELL_BASE; 101357b5ce07SLuis R. Rodriguez } 101457b5ce07SLuis R. Rodriguez 101557b5ce07SLuis R. Rodriguez bool reg_last_request_cell_base(void) 101657b5ce07SLuis R. Rodriguez { 101738fd2143SJohannes Berg return reg_request_cell_base(get_last_request()); 101857b5ce07SLuis R. Rodriguez } 101957b5ce07SLuis R. Rodriguez 102057b5ce07SLuis R. Rodriguez #ifdef CONFIG_CFG80211_CERTIFICATION_ONUS 102157b5ce07SLuis R. Rodriguez /* Core specific check */ 10222f92212bSJohannes Berg static enum reg_request_treatment 10232f92212bSJohannes Berg reg_ignore_cell_hint(struct regulatory_request *pending_request) 102457b5ce07SLuis R. Rodriguez { 1025c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 102657b5ce07SLuis R. Rodriguez 102757b5ce07SLuis R. Rodriguez if (!reg_num_devs_support_basehint) 10282f92212bSJohannes Berg return REG_REQ_IGNORE; 102957b5ce07SLuis R. Rodriguez 1030c492db37SJohannes Berg if (reg_request_cell_base(lr) && 10311a919318SJohannes Berg !regdom_changes(pending_request->alpha2)) 10322f92212bSJohannes Berg return REG_REQ_ALREADY_SET; 10331a919318SJohannes Berg 10342f92212bSJohannes Berg return REG_REQ_OK; 103557b5ce07SLuis R. Rodriguez } 103657b5ce07SLuis R. Rodriguez 103757b5ce07SLuis R. Rodriguez /* Device specific check */ 103857b5ce07SLuis R. Rodriguez static bool reg_dev_ignore_cell_hint(struct wiphy *wiphy) 103957b5ce07SLuis R. Rodriguez { 10401a919318SJohannes Berg return !(wiphy->features & NL80211_FEATURE_CELL_BASE_REG_HINTS); 104157b5ce07SLuis R. Rodriguez } 104257b5ce07SLuis R. Rodriguez #else 104357b5ce07SLuis R. Rodriguez static int reg_ignore_cell_hint(struct regulatory_request *pending_request) 104457b5ce07SLuis R. Rodriguez { 10452f92212bSJohannes Berg return REG_REQ_IGNORE; 104657b5ce07SLuis R. Rodriguez } 10471a919318SJohannes Berg 10481a919318SJohannes Berg static bool reg_dev_ignore_cell_hint(struct wiphy *wiphy) 104957b5ce07SLuis R. Rodriguez { 105057b5ce07SLuis R. Rodriguez return true; 105157b5ce07SLuis R. Rodriguez } 105257b5ce07SLuis R. Rodriguez #endif 105357b5ce07SLuis R. Rodriguez 1054fa1fb9cbSLuis R. Rodriguez static bool wiphy_strict_alpha2_regd(struct wiphy *wiphy) 1055fa1fb9cbSLuis R. Rodriguez { 1056a2f73b6cSLuis R. Rodriguez if (wiphy->regulatory_flags & REGULATORY_STRICT_REG && 1057a2f73b6cSLuis R. Rodriguez !(wiphy->regulatory_flags & REGULATORY_CUSTOM_REG)) 1058fa1fb9cbSLuis R. Rodriguez return true; 1059fa1fb9cbSLuis R. Rodriguez return false; 1060fa1fb9cbSLuis R. Rodriguez } 106157b5ce07SLuis R. Rodriguez 10627db90f4aSLuis R. Rodriguez static bool ignore_reg_update(struct wiphy *wiphy, 10637db90f4aSLuis R. Rodriguez enum nl80211_reg_initiator initiator) 106414b9815aSLuis R. Rodriguez { 1065c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 1066c492db37SJohannes Berg 1067c492db37SJohannes Berg if (!lr) { 1068034c6d6eSLuis R. Rodriguez REG_DBG_PRINT("Ignoring regulatory request set by %s " 1069034c6d6eSLuis R. Rodriguez "since last_request is not set\n", 1070926a0a09SLuis R. Rodriguez reg_initiator_name(initiator)); 107114b9815aSLuis R. Rodriguez return true; 1072926a0a09SLuis R. Rodriguez } 1073926a0a09SLuis R. Rodriguez 10747db90f4aSLuis R. Rodriguez if (initiator == NL80211_REGDOM_SET_BY_CORE && 1075a2f73b6cSLuis R. Rodriguez wiphy->regulatory_flags & REGULATORY_CUSTOM_REG) { 1076034c6d6eSLuis R. Rodriguez REG_DBG_PRINT("Ignoring regulatory request set by %s " 1077034c6d6eSLuis R. Rodriguez "since the driver uses its own custom " 1078034c6d6eSLuis R. Rodriguez "regulatory domain\n", 1079926a0a09SLuis R. Rodriguez reg_initiator_name(initiator)); 108014b9815aSLuis R. Rodriguez return true; 1081926a0a09SLuis R. Rodriguez } 1082926a0a09SLuis R. Rodriguez 1083fb1fc7adSLuis R. Rodriguez /* 1084fb1fc7adSLuis R. Rodriguez * wiphy->regd will be set once the device has its own 1085fb1fc7adSLuis R. Rodriguez * desired regulatory domain set 1086fb1fc7adSLuis R. Rodriguez */ 1087fa1fb9cbSLuis R. Rodriguez if (wiphy_strict_alpha2_regd(wiphy) && !wiphy->regd && 1088749b527bSLuis R. Rodriguez initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE && 1089c492db37SJohannes Berg !is_world_regdom(lr->alpha2)) { 1090034c6d6eSLuis R. Rodriguez REG_DBG_PRINT("Ignoring regulatory request set by %s " 1091034c6d6eSLuis R. Rodriguez "since the driver requires its own regulatory " 1092034c6d6eSLuis R. Rodriguez "domain to be set first\n", 1093926a0a09SLuis R. Rodriguez reg_initiator_name(initiator)); 109414b9815aSLuis R. Rodriguez return true; 1095926a0a09SLuis R. Rodriguez } 1096926a0a09SLuis R. Rodriguez 1097c492db37SJohannes Berg if (reg_request_cell_base(lr)) 109857b5ce07SLuis R. Rodriguez return reg_dev_ignore_cell_hint(wiphy); 109957b5ce07SLuis R. Rodriguez 110014b9815aSLuis R. Rodriguez return false; 110114b9815aSLuis R. Rodriguez } 110214b9815aSLuis R. Rodriguez 11033195e489SLuis R. Rodriguez static bool reg_is_world_roaming(struct wiphy *wiphy) 11043195e489SLuis R. Rodriguez { 11053195e489SLuis R. Rodriguez const struct ieee80211_regdomain *cr = get_cfg80211_regdom(); 11063195e489SLuis R. Rodriguez const struct ieee80211_regdomain *wr = get_wiphy_regdom(wiphy); 11073195e489SLuis R. Rodriguez struct regulatory_request *lr = get_last_request(); 11083195e489SLuis R. Rodriguez 11093195e489SLuis R. Rodriguez if (is_world_regdom(cr->alpha2) || (wr && is_world_regdom(wr->alpha2))) 11103195e489SLuis R. Rodriguez return true; 11113195e489SLuis R. Rodriguez 11123195e489SLuis R. Rodriguez if (lr && lr->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE && 1113a2f73b6cSLuis R. Rodriguez wiphy->regulatory_flags & REGULATORY_CUSTOM_REG) 11143195e489SLuis R. Rodriguez return true; 11153195e489SLuis R. Rodriguez 11163195e489SLuis R. Rodriguez return false; 11173195e489SLuis R. Rodriguez } 11183195e489SLuis R. Rodriguez 11191a919318SJohannes Berg static void handle_reg_beacon(struct wiphy *wiphy, unsigned int chan_idx, 1120e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon) 1121e38f8a7aSLuis R. Rodriguez { 1122e38f8a7aSLuis R. Rodriguez struct ieee80211_supported_band *sband; 1123e38f8a7aSLuis R. Rodriguez struct ieee80211_channel *chan; 11246bad8766SLuis R. Rodriguez bool channel_changed = false; 11256bad8766SLuis R. Rodriguez struct ieee80211_channel chan_before; 1126e38f8a7aSLuis R. Rodriguez 1127e38f8a7aSLuis R. Rodriguez sband = wiphy->bands[reg_beacon->chan.band]; 1128e38f8a7aSLuis R. Rodriguez chan = &sband->channels[chan_idx]; 1129e38f8a7aSLuis R. Rodriguez 1130e38f8a7aSLuis R. Rodriguez if (likely(chan->center_freq != reg_beacon->chan.center_freq)) 1131e38f8a7aSLuis R. Rodriguez return; 1132e38f8a7aSLuis R. Rodriguez 11336bad8766SLuis R. Rodriguez if (chan->beacon_found) 11346bad8766SLuis R. Rodriguez return; 11356bad8766SLuis R. Rodriguez 11366bad8766SLuis R. Rodriguez chan->beacon_found = true; 11376bad8766SLuis R. Rodriguez 11380f500a5fSLuis R. Rodriguez if (!reg_is_world_roaming(wiphy)) 11390f500a5fSLuis R. Rodriguez return; 11400f500a5fSLuis R. Rodriguez 1141a2f73b6cSLuis R. Rodriguez if (wiphy->regulatory_flags & REGULATORY_DISABLE_BEACON_HINTS) 114237184244SLuis R. Rodriguez return; 114337184244SLuis R. Rodriguez 11446bad8766SLuis R. Rodriguez chan_before.center_freq = chan->center_freq; 11456bad8766SLuis R. Rodriguez chan_before.flags = chan->flags; 11466bad8766SLuis R. Rodriguez 11478fe02e16SLuis R. Rodriguez if (chan->flags & IEEE80211_CHAN_NO_IR) { 11488fe02e16SLuis R. Rodriguez chan->flags &= ~IEEE80211_CHAN_NO_IR; 11496bad8766SLuis R. Rodriguez channel_changed = true; 1150e38f8a7aSLuis R. Rodriguez } 1151e38f8a7aSLuis R. Rodriguez 11526bad8766SLuis R. Rodriguez if (channel_changed) 11536bad8766SLuis R. Rodriguez nl80211_send_beacon_hint_event(wiphy, &chan_before, chan); 1154e38f8a7aSLuis R. Rodriguez } 1155e38f8a7aSLuis R. Rodriguez 1156e38f8a7aSLuis R. Rodriguez /* 1157e38f8a7aSLuis R. Rodriguez * Called when a scan on a wiphy finds a beacon on 1158e38f8a7aSLuis R. Rodriguez * new channel 1159e38f8a7aSLuis R. Rodriguez */ 1160e38f8a7aSLuis R. Rodriguez static void wiphy_update_new_beacon(struct wiphy *wiphy, 1161e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon) 1162e38f8a7aSLuis R. Rodriguez { 1163e38f8a7aSLuis R. Rodriguez unsigned int i; 1164e38f8a7aSLuis R. Rodriguez struct ieee80211_supported_band *sband; 1165e38f8a7aSLuis R. Rodriguez 1166e38f8a7aSLuis R. Rodriguez if (!wiphy->bands[reg_beacon->chan.band]) 1167e38f8a7aSLuis R. Rodriguez return; 1168e38f8a7aSLuis R. Rodriguez 1169e38f8a7aSLuis R. Rodriguez sband = wiphy->bands[reg_beacon->chan.band]; 1170e38f8a7aSLuis R. Rodriguez 1171e38f8a7aSLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 1172e38f8a7aSLuis R. Rodriguez handle_reg_beacon(wiphy, i, reg_beacon); 1173e38f8a7aSLuis R. Rodriguez } 1174e38f8a7aSLuis R. Rodriguez 1175e38f8a7aSLuis R. Rodriguez /* 1176e38f8a7aSLuis R. Rodriguez * Called upon reg changes or a new wiphy is added 1177e38f8a7aSLuis R. Rodriguez */ 1178e38f8a7aSLuis R. Rodriguez static void wiphy_update_beacon_reg(struct wiphy *wiphy) 1179e38f8a7aSLuis R. Rodriguez { 1180e38f8a7aSLuis R. Rodriguez unsigned int i; 1181e38f8a7aSLuis R. Rodriguez struct ieee80211_supported_band *sband; 1182e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon; 1183e38f8a7aSLuis R. Rodriguez 1184e38f8a7aSLuis R. Rodriguez list_for_each_entry(reg_beacon, ®_beacon_list, list) { 1185e38f8a7aSLuis R. Rodriguez if (!wiphy->bands[reg_beacon->chan.band]) 1186e38f8a7aSLuis R. Rodriguez continue; 1187e38f8a7aSLuis R. Rodriguez sband = wiphy->bands[reg_beacon->chan.band]; 1188e38f8a7aSLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 1189e38f8a7aSLuis R. Rodriguez handle_reg_beacon(wiphy, i, reg_beacon); 1190e38f8a7aSLuis R. Rodriguez } 1191e38f8a7aSLuis R. Rodriguez } 1192e38f8a7aSLuis R. Rodriguez 1193e38f8a7aSLuis R. Rodriguez /* Reap the advantages of previously found beacons */ 1194e38f8a7aSLuis R. Rodriguez static void reg_process_beacons(struct wiphy *wiphy) 1195e38f8a7aSLuis R. Rodriguez { 1196b1ed8dddSLuis R. Rodriguez /* 1197b1ed8dddSLuis R. Rodriguez * Means we are just firing up cfg80211, so no beacons would 1198b1ed8dddSLuis R. Rodriguez * have been processed yet. 1199b1ed8dddSLuis R. Rodriguez */ 1200b1ed8dddSLuis R. Rodriguez if (!last_request) 1201b1ed8dddSLuis R. Rodriguez return; 1202e38f8a7aSLuis R. Rodriguez wiphy_update_beacon_reg(wiphy); 1203e38f8a7aSLuis R. Rodriguez } 1204e38f8a7aSLuis R. Rodriguez 12051a919318SJohannes Berg static bool is_ht40_allowed(struct ieee80211_channel *chan) 1206038659e7SLuis R. Rodriguez { 1207038659e7SLuis R. Rodriguez if (!chan) 1208038659e7SLuis R. Rodriguez return false; 12091a919318SJohannes Berg if (chan->flags & IEEE80211_CHAN_DISABLED) 12101a919318SJohannes Berg return false; 12111a919318SJohannes Berg /* This would happen when regulatory rules disallow HT40 completely */ 121255b183adSFelix Fietkau if ((chan->flags & IEEE80211_CHAN_NO_HT40) == IEEE80211_CHAN_NO_HT40) 121355b183adSFelix Fietkau return false; 121455b183adSFelix Fietkau return true; 1215038659e7SLuis R. Rodriguez } 1216038659e7SLuis R. Rodriguez 1217038659e7SLuis R. Rodriguez static void reg_process_ht_flags_channel(struct wiphy *wiphy, 1218fdc9d7b2SJohannes Berg struct ieee80211_channel *channel) 1219038659e7SLuis R. Rodriguez { 1220fdc9d7b2SJohannes Berg struct ieee80211_supported_band *sband = wiphy->bands[channel->band]; 1221038659e7SLuis R. Rodriguez struct ieee80211_channel *channel_before = NULL, *channel_after = NULL; 1222038659e7SLuis R. Rodriguez unsigned int i; 1223038659e7SLuis R. Rodriguez 12241a919318SJohannes Berg if (!is_ht40_allowed(channel)) { 1225038659e7SLuis R. Rodriguez channel->flags |= IEEE80211_CHAN_NO_HT40; 1226038659e7SLuis R. Rodriguez return; 1227038659e7SLuis R. Rodriguez } 1228038659e7SLuis R. Rodriguez 1229038659e7SLuis R. Rodriguez /* 1230038659e7SLuis R. Rodriguez * We need to ensure the extension channels exist to 1231038659e7SLuis R. Rodriguez * be able to use HT40- or HT40+, this finds them (or not) 1232038659e7SLuis R. Rodriguez */ 1233038659e7SLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) { 1234038659e7SLuis R. Rodriguez struct ieee80211_channel *c = &sband->channels[i]; 12351a919318SJohannes Berg 1236038659e7SLuis R. Rodriguez if (c->center_freq == (channel->center_freq - 20)) 1237038659e7SLuis R. Rodriguez channel_before = c; 1238038659e7SLuis R. Rodriguez if (c->center_freq == (channel->center_freq + 20)) 1239038659e7SLuis R. Rodriguez channel_after = c; 1240038659e7SLuis R. Rodriguez } 1241038659e7SLuis R. Rodriguez 1242038659e7SLuis R. Rodriguez /* 1243038659e7SLuis R. Rodriguez * Please note that this assumes target bandwidth is 20 MHz, 1244038659e7SLuis R. Rodriguez * if that ever changes we also need to change the below logic 1245038659e7SLuis R. Rodriguez * to include that as well. 1246038659e7SLuis R. Rodriguez */ 12471a919318SJohannes Berg if (!is_ht40_allowed(channel_before)) 1248689da1b3SLuis R. Rodriguez channel->flags |= IEEE80211_CHAN_NO_HT40MINUS; 1249038659e7SLuis R. Rodriguez else 1250689da1b3SLuis R. Rodriguez channel->flags &= ~IEEE80211_CHAN_NO_HT40MINUS; 1251038659e7SLuis R. Rodriguez 12521a919318SJohannes Berg if (!is_ht40_allowed(channel_after)) 1253689da1b3SLuis R. Rodriguez channel->flags |= IEEE80211_CHAN_NO_HT40PLUS; 1254038659e7SLuis R. Rodriguez else 1255689da1b3SLuis R. Rodriguez channel->flags &= ~IEEE80211_CHAN_NO_HT40PLUS; 1256038659e7SLuis R. Rodriguez } 1257038659e7SLuis R. Rodriguez 1258038659e7SLuis R. Rodriguez static void reg_process_ht_flags_band(struct wiphy *wiphy, 1259fdc9d7b2SJohannes Berg struct ieee80211_supported_band *sband) 1260038659e7SLuis R. Rodriguez { 1261038659e7SLuis R. Rodriguez unsigned int i; 1262038659e7SLuis R. Rodriguez 1263fdc9d7b2SJohannes Berg if (!sband) 1264fdc9d7b2SJohannes Berg return; 1265038659e7SLuis R. Rodriguez 1266038659e7SLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 1267fdc9d7b2SJohannes Berg reg_process_ht_flags_channel(wiphy, &sband->channels[i]); 1268038659e7SLuis R. Rodriguez } 1269038659e7SLuis R. Rodriguez 1270038659e7SLuis R. Rodriguez static void reg_process_ht_flags(struct wiphy *wiphy) 1271038659e7SLuis R. Rodriguez { 1272038659e7SLuis R. Rodriguez enum ieee80211_band band; 1273038659e7SLuis R. Rodriguez 1274038659e7SLuis R. Rodriguez if (!wiphy) 1275038659e7SLuis R. Rodriguez return; 1276038659e7SLuis R. Rodriguez 1277fdc9d7b2SJohannes Berg for (band = 0; band < IEEE80211_NUM_BANDS; band++) 1278fdc9d7b2SJohannes Berg reg_process_ht_flags_band(wiphy, wiphy->bands[band]); 1279038659e7SLuis R. Rodriguez } 1280038659e7SLuis R. Rodriguez 12810e3802dbSLuis R. Rodriguez static void reg_call_notifier(struct wiphy *wiphy, 12820e3802dbSLuis R. Rodriguez struct regulatory_request *request) 12830e3802dbSLuis R. Rodriguez { 12840e3802dbSLuis R. Rodriguez if (wiphy->reg_notifier) 12850e3802dbSLuis R. Rodriguez wiphy->reg_notifier(wiphy, request); 12860e3802dbSLuis R. Rodriguez } 12870e3802dbSLuis R. Rodriguez 1288eac03e38SSven Neumann static void wiphy_update_regulatory(struct wiphy *wiphy, 12897db90f4aSLuis R. Rodriguez enum nl80211_reg_initiator initiator) 12908318d78aSJohannes Berg { 12918318d78aSJohannes Berg enum ieee80211_band band; 1292c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 1293eac03e38SSven Neumann 12940e3802dbSLuis R. Rodriguez if (ignore_reg_update(wiphy, initiator)) { 12950e3802dbSLuis R. Rodriguez /* 12960e3802dbSLuis R. Rodriguez * Regulatory updates set by CORE are ignored for custom 12970e3802dbSLuis R. Rodriguez * regulatory cards. Let us notify the changes to the driver, 12980e3802dbSLuis R. Rodriguez * as some drivers used this to restore its orig_* reg domain. 12990e3802dbSLuis R. Rodriguez */ 13000e3802dbSLuis R. Rodriguez if (initiator == NL80211_REGDOM_SET_BY_CORE && 1301a2f73b6cSLuis R. Rodriguez wiphy->regulatory_flags & REGULATORY_CUSTOM_REG) 13020e3802dbSLuis R. Rodriguez reg_call_notifier(wiphy, lr); 1303a203c2aaSSven Neumann return; 13040e3802dbSLuis R. Rodriguez } 1305a203c2aaSSven Neumann 1306c492db37SJohannes Berg lr->dfs_region = get_cfg80211_regdom()->dfs_region; 1307b68e6b3bSLuis R. Rodriguez 1308fdc9d7b2SJohannes Berg for (band = 0; band < IEEE80211_NUM_BANDS; band++) 1309fdc9d7b2SJohannes Berg handle_band(wiphy, initiator, wiphy->bands[band]); 1310a203c2aaSSven Neumann 1311e38f8a7aSLuis R. Rodriguez reg_process_beacons(wiphy); 1312038659e7SLuis R. Rodriguez reg_process_ht_flags(wiphy); 13130e3802dbSLuis R. Rodriguez reg_call_notifier(wiphy, lr); 1314b2e1b302SLuis R. Rodriguez } 1315b2e1b302SLuis R. Rodriguez 1316d7549cbbSSven Neumann static void update_all_wiphy_regulatory(enum nl80211_reg_initiator initiator) 1317d7549cbbSSven Neumann { 1318d7549cbbSSven Neumann struct cfg80211_registered_device *rdev; 13194a38994fSRajkumar Manoharan struct wiphy *wiphy; 1320d7549cbbSSven Neumann 13215fe231e8SJohannes Berg ASSERT_RTNL(); 1322458f4f9eSJohannes Berg 13234a38994fSRajkumar Manoharan list_for_each_entry(rdev, &cfg80211_rdev_list, list) { 13244a38994fSRajkumar Manoharan wiphy = &rdev->wiphy; 13254a38994fSRajkumar Manoharan wiphy_update_regulatory(wiphy, initiator); 13264a38994fSRajkumar Manoharan } 1327d7549cbbSSven Neumann } 1328d7549cbbSSven Neumann 13291fa25e41SLuis R. Rodriguez static void handle_channel_custom(struct wiphy *wiphy, 1330fdc9d7b2SJohannes Berg struct ieee80211_channel *chan, 13311fa25e41SLuis R. Rodriguez const struct ieee80211_regdomain *regd) 13321fa25e41SLuis R. Rodriguez { 1333038659e7SLuis R. Rodriguez u32 bw_flags = 0; 13341fa25e41SLuis R. Rodriguez const struct ieee80211_reg_rule *reg_rule = NULL; 13351fa25e41SLuis R. Rodriguez const struct ieee80211_power_rule *power_rule = NULL; 1336038659e7SLuis R. Rodriguez const struct ieee80211_freq_range *freq_range = NULL; 13371fa25e41SLuis R. Rodriguez 1338361c9c8bSJohannes Berg reg_rule = freq_reg_info_regd(wiphy, MHZ_TO_KHZ(chan->center_freq), 1339038659e7SLuis R. Rodriguez regd); 13401fa25e41SLuis R. Rodriguez 1341361c9c8bSJohannes Berg if (IS_ERR(reg_rule)) { 1342fe7ef5e9SJohannes Berg REG_DBG_PRINT("Disabling freq %d MHz as custom regd has no rule that fits it\n", 1343fe7ef5e9SJohannes Berg chan->center_freq); 1344cc493e4fSLuis R. Rodriguez chan->orig_flags |= IEEE80211_CHAN_DISABLED; 1345cc493e4fSLuis R. Rodriguez chan->flags = chan->orig_flags; 13461fa25e41SLuis R. Rodriguez return; 13471fa25e41SLuis R. Rodriguez } 13481fa25e41SLuis R. Rodriguez 1349fe7ef5e9SJohannes Berg chan_reg_rule_print_dbg(chan, reg_rule); 1350e702d3cfSLuis R. Rodriguez 13511fa25e41SLuis R. Rodriguez power_rule = ®_rule->power_rule; 1352038659e7SLuis R. Rodriguez freq_range = ®_rule->freq_range; 13531fa25e41SLuis R. Rodriguez 1354038659e7SLuis R. Rodriguez if (freq_range->max_bandwidth_khz < MHZ_TO_KHZ(40)) 1355038659e7SLuis R. Rodriguez bw_flags = IEEE80211_CHAN_NO_HT40; 1356c7a6ee27SJohannes Berg if (freq_range->max_bandwidth_khz < MHZ_TO_KHZ(80)) 1357c7a6ee27SJohannes Berg bw_flags |= IEEE80211_CHAN_NO_80MHZ; 1358c7a6ee27SJohannes Berg if (freq_range->max_bandwidth_khz < MHZ_TO_KHZ(160)) 1359c7a6ee27SJohannes Berg bw_flags |= IEEE80211_CHAN_NO_160MHZ; 1360038659e7SLuis R. Rodriguez 1361038659e7SLuis R. Rodriguez chan->flags |= map_regdom_flags(reg_rule->flags) | bw_flags; 13621fa25e41SLuis R. Rodriguez chan->max_antenna_gain = (int) MBI_TO_DBI(power_rule->max_antenna_gain); 1363279f0f55SFelix Fietkau chan->max_reg_power = chan->max_power = 1364279f0f55SFelix Fietkau (int) MBM_TO_DBM(power_rule->max_eirp); 13651fa25e41SLuis R. Rodriguez } 13661fa25e41SLuis R. Rodriguez 1367fdc9d7b2SJohannes Berg static void handle_band_custom(struct wiphy *wiphy, 1368fdc9d7b2SJohannes Berg struct ieee80211_supported_band *sband, 13691fa25e41SLuis R. Rodriguez const struct ieee80211_regdomain *regd) 13701fa25e41SLuis R. Rodriguez { 13711fa25e41SLuis R. Rodriguez unsigned int i; 13721fa25e41SLuis R. Rodriguez 1373fdc9d7b2SJohannes Berg if (!sband) 1374fdc9d7b2SJohannes Berg return; 13751fa25e41SLuis R. Rodriguez 13761fa25e41SLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 1377fdc9d7b2SJohannes Berg handle_channel_custom(wiphy, &sband->channels[i], regd); 13781fa25e41SLuis R. Rodriguez } 13791fa25e41SLuis R. Rodriguez 13801fa25e41SLuis R. Rodriguez /* Used by drivers prior to wiphy registration */ 13811fa25e41SLuis R. Rodriguez void wiphy_apply_custom_regulatory(struct wiphy *wiphy, 13821fa25e41SLuis R. Rodriguez const struct ieee80211_regdomain *regd) 13831fa25e41SLuis R. Rodriguez { 13841fa25e41SLuis R. Rodriguez enum ieee80211_band band; 1385bbcf3f02SLuis R. Rodriguez unsigned int bands_set = 0; 1386ac46d48eSLuis R. Rodriguez 1387a2f73b6cSLuis R. Rodriguez WARN(!(wiphy->regulatory_flags & REGULATORY_CUSTOM_REG), 1388a2f73b6cSLuis R. Rodriguez "wiphy should have REGULATORY_CUSTOM_REG\n"); 1389a2f73b6cSLuis R. Rodriguez wiphy->regulatory_flags |= REGULATORY_CUSTOM_REG; 1390222ea581SLuis R. Rodriguez 13911fa25e41SLuis R. Rodriguez for (band = 0; band < IEEE80211_NUM_BANDS; band++) { 1392bbcf3f02SLuis R. Rodriguez if (!wiphy->bands[band]) 1393bbcf3f02SLuis R. Rodriguez continue; 1394fdc9d7b2SJohannes Berg handle_band_custom(wiphy, wiphy->bands[band], regd); 1395bbcf3f02SLuis R. Rodriguez bands_set++; 13961fa25e41SLuis R. Rodriguez } 1397bbcf3f02SLuis R. Rodriguez 1398bbcf3f02SLuis R. Rodriguez /* 1399bbcf3f02SLuis R. Rodriguez * no point in calling this if it won't have any effect 14001a919318SJohannes Berg * on your device's supported bands. 1401bbcf3f02SLuis R. Rodriguez */ 1402bbcf3f02SLuis R. Rodriguez WARN_ON(!bands_set); 14031fa25e41SLuis R. Rodriguez } 14041fa25e41SLuis R. Rodriguez EXPORT_SYMBOL(wiphy_apply_custom_regulatory); 14051fa25e41SLuis R. Rodriguez 1406b2e253cfSLuis R. Rodriguez static void reg_set_request_processed(void) 1407b2e253cfSLuis R. Rodriguez { 1408b2e253cfSLuis R. Rodriguez bool need_more_processing = false; 1409c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 1410b2e253cfSLuis R. Rodriguez 1411c492db37SJohannes Berg lr->processed = true; 1412b2e253cfSLuis R. Rodriguez 1413b2e253cfSLuis R. Rodriguez spin_lock(®_requests_lock); 1414b2e253cfSLuis R. Rodriguez if (!list_empty(®_requests_list)) 1415b2e253cfSLuis R. Rodriguez need_more_processing = true; 1416b2e253cfSLuis R. Rodriguez spin_unlock(®_requests_lock); 1417b2e253cfSLuis R. Rodriguez 1418c492db37SJohannes Berg if (lr->initiator == NL80211_REGDOM_SET_BY_USER) 1419fe20b39eSEliad Peller cancel_delayed_work(®_timeout); 1420a90c7a31SLuis R. Rodriguez 1421b2e253cfSLuis R. Rodriguez if (need_more_processing) 1422b2e253cfSLuis R. Rodriguez schedule_work(®_work); 1423b2e253cfSLuis R. Rodriguez } 1424b2e253cfSLuis R. Rodriguez 1425d1c96a9aSLuis R. Rodriguez /** 1426b3eb7f3fSLuis R. Rodriguez * reg_process_hint_core - process core regulatory requests 1427b3eb7f3fSLuis R. Rodriguez * @pending_request: a pending core regulatory request 1428b3eb7f3fSLuis R. Rodriguez * 1429b3eb7f3fSLuis R. Rodriguez * The wireless subsystem can use this function to process 1430b3eb7f3fSLuis R. Rodriguez * a regulatory request issued by the regulatory core. 1431b3eb7f3fSLuis R. Rodriguez * 1432b3eb7f3fSLuis R. Rodriguez * Returns one of the different reg request treatment values. 1433b3eb7f3fSLuis R. Rodriguez */ 1434b3eb7f3fSLuis R. Rodriguez static enum reg_request_treatment 1435b3eb7f3fSLuis R. Rodriguez reg_process_hint_core(struct regulatory_request *core_request) 1436b3eb7f3fSLuis R. Rodriguez { 1437b3eb7f3fSLuis R. Rodriguez 1438b3eb7f3fSLuis R. Rodriguez core_request->intersect = false; 1439b3eb7f3fSLuis R. Rodriguez core_request->processed = false; 14405ad6ef5eSLuis R. Rodriguez 144105f1a3eaSLuis R. Rodriguez reg_update_last_request(core_request); 1442b3eb7f3fSLuis R. Rodriguez 1443fe6631ffSLuis R. Rodriguez return reg_call_crda(core_request); 1444b3eb7f3fSLuis R. Rodriguez } 1445b3eb7f3fSLuis R. Rodriguez 14460d97a619SLuis R. Rodriguez static enum reg_request_treatment 14470d97a619SLuis R. Rodriguez __reg_process_hint_user(struct regulatory_request *user_request) 14480d97a619SLuis R. Rodriguez { 14490d97a619SLuis R. Rodriguez struct regulatory_request *lr = get_last_request(); 14500d97a619SLuis R. Rodriguez 14510d97a619SLuis R. Rodriguez if (reg_request_cell_base(user_request)) 14520d97a619SLuis R. Rodriguez return reg_ignore_cell_hint(user_request); 14530d97a619SLuis R. Rodriguez 14540d97a619SLuis R. Rodriguez if (reg_request_cell_base(lr)) 14550d97a619SLuis R. Rodriguez return REG_REQ_IGNORE; 14560d97a619SLuis R. Rodriguez 14570d97a619SLuis R. Rodriguez if (lr->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE) 14580d97a619SLuis R. Rodriguez return REG_REQ_INTERSECT; 14590d97a619SLuis R. Rodriguez /* 14600d97a619SLuis R. Rodriguez * If the user knows better the user should set the regdom 14610d97a619SLuis R. Rodriguez * to their country before the IE is picked up 14620d97a619SLuis R. Rodriguez */ 14630d97a619SLuis R. Rodriguez if (lr->initiator == NL80211_REGDOM_SET_BY_USER && 14640d97a619SLuis R. Rodriguez lr->intersect) 14650d97a619SLuis R. Rodriguez return REG_REQ_IGNORE; 14660d97a619SLuis R. Rodriguez /* 14670d97a619SLuis R. Rodriguez * Process user requests only after previous user/driver/core 14680d97a619SLuis R. Rodriguez * requests have been processed 14690d97a619SLuis R. Rodriguez */ 14700d97a619SLuis R. Rodriguez if ((lr->initiator == NL80211_REGDOM_SET_BY_CORE || 14710d97a619SLuis R. Rodriguez lr->initiator == NL80211_REGDOM_SET_BY_DRIVER || 14720d97a619SLuis R. Rodriguez lr->initiator == NL80211_REGDOM_SET_BY_USER) && 14730d97a619SLuis R. Rodriguez regdom_changes(lr->alpha2)) 14740d97a619SLuis R. Rodriguez return REG_REQ_IGNORE; 14750d97a619SLuis R. Rodriguez 14760d97a619SLuis R. Rodriguez if (!regdom_changes(user_request->alpha2)) 14770d97a619SLuis R. Rodriguez return REG_REQ_ALREADY_SET; 14780d97a619SLuis R. Rodriguez 14790d97a619SLuis R. Rodriguez return REG_REQ_OK; 14800d97a619SLuis R. Rodriguez } 14810d97a619SLuis R. Rodriguez 14820d97a619SLuis R. Rodriguez /** 14830d97a619SLuis R. Rodriguez * reg_process_hint_user - process user regulatory requests 14840d97a619SLuis R. Rodriguez * @user_request: a pending user regulatory request 14850d97a619SLuis R. Rodriguez * 14860d97a619SLuis R. Rodriguez * The wireless subsystem can use this function to process 14870d97a619SLuis R. Rodriguez * a regulatory request initiated by userspace. 14880d97a619SLuis R. Rodriguez * 14890d97a619SLuis R. Rodriguez * Returns one of the different reg request treatment values. 14900d97a619SLuis R. Rodriguez */ 14910d97a619SLuis R. Rodriguez static enum reg_request_treatment 14920d97a619SLuis R. Rodriguez reg_process_hint_user(struct regulatory_request *user_request) 14930d97a619SLuis R. Rodriguez { 14940d97a619SLuis R. Rodriguez enum reg_request_treatment treatment; 14950d97a619SLuis R. Rodriguez 14960d97a619SLuis R. Rodriguez treatment = __reg_process_hint_user(user_request); 14970d97a619SLuis R. Rodriguez if (treatment == REG_REQ_IGNORE || 14980d97a619SLuis R. Rodriguez treatment == REG_REQ_ALREADY_SET) { 14990d97a619SLuis R. Rodriguez kfree(user_request); 15000d97a619SLuis R. Rodriguez return treatment; 15010d97a619SLuis R. Rodriguez } 15020d97a619SLuis R. Rodriguez 15030d97a619SLuis R. Rodriguez user_request->intersect = treatment == REG_REQ_INTERSECT; 15040d97a619SLuis R. Rodriguez user_request->processed = false; 15055ad6ef5eSLuis R. Rodriguez 150605f1a3eaSLuis R. Rodriguez reg_update_last_request(user_request); 15070d97a619SLuis R. Rodriguez 15080d97a619SLuis R. Rodriguez user_alpha2[0] = user_request->alpha2[0]; 15090d97a619SLuis R. Rodriguez user_alpha2[1] = user_request->alpha2[1]; 15100d97a619SLuis R. Rodriguez 1511fe6631ffSLuis R. Rodriguez return reg_call_crda(user_request); 15120d97a619SLuis R. Rodriguez } 15130d97a619SLuis R. Rodriguez 151421636c7fSLuis R. Rodriguez static enum reg_request_treatment 151521636c7fSLuis R. Rodriguez __reg_process_hint_driver(struct regulatory_request *driver_request) 151621636c7fSLuis R. Rodriguez { 151721636c7fSLuis R. Rodriguez struct regulatory_request *lr = get_last_request(); 151821636c7fSLuis R. Rodriguez 151921636c7fSLuis R. Rodriguez if (lr->initiator == NL80211_REGDOM_SET_BY_CORE) { 152021636c7fSLuis R. Rodriguez if (regdom_changes(driver_request->alpha2)) 152121636c7fSLuis R. Rodriguez return REG_REQ_OK; 152221636c7fSLuis R. Rodriguez return REG_REQ_ALREADY_SET; 152321636c7fSLuis R. Rodriguez } 152421636c7fSLuis R. Rodriguez 152521636c7fSLuis R. Rodriguez /* 152621636c7fSLuis R. Rodriguez * This would happen if you unplug and plug your card 152721636c7fSLuis R. Rodriguez * back in or if you add a new device for which the previously 152821636c7fSLuis R. Rodriguez * loaded card also agrees on the regulatory domain. 152921636c7fSLuis R. Rodriguez */ 153021636c7fSLuis R. Rodriguez if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER && 153121636c7fSLuis R. Rodriguez !regdom_changes(driver_request->alpha2)) 153221636c7fSLuis R. Rodriguez return REG_REQ_ALREADY_SET; 153321636c7fSLuis R. Rodriguez 153421636c7fSLuis R. Rodriguez return REG_REQ_INTERSECT; 153521636c7fSLuis R. Rodriguez } 153621636c7fSLuis R. Rodriguez 153721636c7fSLuis R. Rodriguez /** 153821636c7fSLuis R. Rodriguez * reg_process_hint_driver - process driver regulatory requests 153921636c7fSLuis R. Rodriguez * @driver_request: a pending driver regulatory request 154021636c7fSLuis R. Rodriguez * 154121636c7fSLuis R. Rodriguez * The wireless subsystem can use this function to process 154221636c7fSLuis R. Rodriguez * a regulatory request issued by an 802.11 driver. 154321636c7fSLuis R. Rodriguez * 154421636c7fSLuis R. Rodriguez * Returns one of the different reg request treatment values. 154521636c7fSLuis R. Rodriguez */ 154621636c7fSLuis R. Rodriguez static enum reg_request_treatment 154721636c7fSLuis R. Rodriguez reg_process_hint_driver(struct wiphy *wiphy, 154821636c7fSLuis R. Rodriguez struct regulatory_request *driver_request) 154921636c7fSLuis R. Rodriguez { 155021636c7fSLuis R. Rodriguez const struct ieee80211_regdomain *regd; 155121636c7fSLuis R. Rodriguez enum reg_request_treatment treatment; 155221636c7fSLuis R. Rodriguez 155321636c7fSLuis R. Rodriguez treatment = __reg_process_hint_driver(driver_request); 155421636c7fSLuis R. Rodriguez 155521636c7fSLuis R. Rodriguez switch (treatment) { 155621636c7fSLuis R. Rodriguez case REG_REQ_OK: 155721636c7fSLuis R. Rodriguez break; 155821636c7fSLuis R. Rodriguez case REG_REQ_IGNORE: 155921636c7fSLuis R. Rodriguez kfree(driver_request); 156021636c7fSLuis R. Rodriguez return treatment; 156121636c7fSLuis R. Rodriguez case REG_REQ_INTERSECT: 156221636c7fSLuis R. Rodriguez /* fall through */ 156321636c7fSLuis R. Rodriguez case REG_REQ_ALREADY_SET: 156421636c7fSLuis R. Rodriguez regd = reg_copy_regd(get_cfg80211_regdom()); 156521636c7fSLuis R. Rodriguez if (IS_ERR(regd)) { 156621636c7fSLuis R. Rodriguez kfree(driver_request); 156721636c7fSLuis R. Rodriguez return REG_REQ_IGNORE; 156821636c7fSLuis R. Rodriguez } 156921636c7fSLuis R. Rodriguez rcu_assign_pointer(wiphy->regd, regd); 157021636c7fSLuis R. Rodriguez } 157121636c7fSLuis R. Rodriguez 157221636c7fSLuis R. Rodriguez 157321636c7fSLuis R. Rodriguez driver_request->intersect = treatment == REG_REQ_INTERSECT; 157421636c7fSLuis R. Rodriguez driver_request->processed = false; 15755ad6ef5eSLuis R. Rodriguez 157605f1a3eaSLuis R. Rodriguez reg_update_last_request(driver_request); 157721636c7fSLuis R. Rodriguez 157821636c7fSLuis R. Rodriguez /* 157921636c7fSLuis R. Rodriguez * Since CRDA will not be called in this case as we already 158021636c7fSLuis R. Rodriguez * have applied the requested regulatory domain before we just 158121636c7fSLuis R. Rodriguez * inform userspace we have processed the request 158221636c7fSLuis R. Rodriguez */ 158321636c7fSLuis R. Rodriguez if (treatment == REG_REQ_ALREADY_SET) { 158421636c7fSLuis R. Rodriguez nl80211_send_reg_change_event(driver_request); 158521636c7fSLuis R. Rodriguez reg_set_request_processed(); 158621636c7fSLuis R. Rodriguez return treatment; 158721636c7fSLuis R. Rodriguez } 158821636c7fSLuis R. Rodriguez 1589fe6631ffSLuis R. Rodriguez return reg_call_crda(driver_request); 159021636c7fSLuis R. Rodriguez } 159121636c7fSLuis R. Rodriguez 1592b23e7a9eSLuis R. Rodriguez static enum reg_request_treatment 1593b23e7a9eSLuis R. Rodriguez __reg_process_hint_country_ie(struct wiphy *wiphy, 1594b23e7a9eSLuis R. Rodriguez struct regulatory_request *country_ie_request) 1595b23e7a9eSLuis R. Rodriguez { 1596b23e7a9eSLuis R. Rodriguez struct wiphy *last_wiphy = NULL; 1597b23e7a9eSLuis R. Rodriguez struct regulatory_request *lr = get_last_request(); 1598b23e7a9eSLuis R. Rodriguez 1599b23e7a9eSLuis R. Rodriguez if (reg_request_cell_base(lr)) { 1600b23e7a9eSLuis R. Rodriguez /* Trust a Cell base station over the AP's country IE */ 1601b23e7a9eSLuis R. Rodriguez if (regdom_changes(country_ie_request->alpha2)) 1602b23e7a9eSLuis R. Rodriguez return REG_REQ_IGNORE; 1603b23e7a9eSLuis R. Rodriguez return REG_REQ_ALREADY_SET; 16042a901468SLuis R. Rodriguez } else { 16052a901468SLuis R. Rodriguez if (wiphy->regulatory_flags & REGULATORY_COUNTRY_IE_IGNORE) 16062a901468SLuis R. Rodriguez return REG_REQ_IGNORE; 1607b23e7a9eSLuis R. Rodriguez } 1608b23e7a9eSLuis R. Rodriguez 1609b23e7a9eSLuis R. Rodriguez if (unlikely(!is_an_alpha2(country_ie_request->alpha2))) 1610b23e7a9eSLuis R. Rodriguez return -EINVAL; 16112f1c6c57SLuis R. Rodriguez 16122f1c6c57SLuis R. Rodriguez if (lr->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE) 16132f1c6c57SLuis R. Rodriguez return REG_REQ_OK; 16142f1c6c57SLuis R. Rodriguez 16152f1c6c57SLuis R. Rodriguez last_wiphy = wiphy_idx_to_wiphy(lr->wiphy_idx); 16162f1c6c57SLuis R. Rodriguez 1617b23e7a9eSLuis R. Rodriguez if (last_wiphy != wiphy) { 1618b23e7a9eSLuis R. Rodriguez /* 1619b23e7a9eSLuis R. Rodriguez * Two cards with two APs claiming different 1620b23e7a9eSLuis R. Rodriguez * Country IE alpha2s. We could 1621b23e7a9eSLuis R. Rodriguez * intersect them, but that seems unlikely 1622b23e7a9eSLuis R. Rodriguez * to be correct. Reject second one for now. 1623b23e7a9eSLuis R. Rodriguez */ 1624b23e7a9eSLuis R. Rodriguez if (regdom_changes(country_ie_request->alpha2)) 1625b23e7a9eSLuis R. Rodriguez return REG_REQ_IGNORE; 1626b23e7a9eSLuis R. Rodriguez return REG_REQ_ALREADY_SET; 1627b23e7a9eSLuis R. Rodriguez } 1628b23e7a9eSLuis R. Rodriguez /* 1629b23e7a9eSLuis R. Rodriguez * Two consecutive Country IE hints on the same wiphy. 1630b23e7a9eSLuis R. Rodriguez * This should be picked up early by the driver/stack 1631b23e7a9eSLuis R. Rodriguez */ 1632b23e7a9eSLuis R. Rodriguez if (WARN_ON(regdom_changes(country_ie_request->alpha2))) 1633b23e7a9eSLuis R. Rodriguez return REG_REQ_OK; 1634b23e7a9eSLuis R. Rodriguez return REG_REQ_ALREADY_SET; 1635b23e7a9eSLuis R. Rodriguez } 1636b23e7a9eSLuis R. Rodriguez 1637b3eb7f3fSLuis R. Rodriguez /** 1638b23e7a9eSLuis R. Rodriguez * reg_process_hint_country_ie - process regulatory requests from country IEs 1639b23e7a9eSLuis R. Rodriguez * @country_ie_request: a regulatory request from a country IE 1640d1c96a9aSLuis R. Rodriguez * 1641b23e7a9eSLuis R. Rodriguez * The wireless subsystem can use this function to process 1642b23e7a9eSLuis R. Rodriguez * a regulatory request issued by a country Information Element. 1643d1c96a9aSLuis R. Rodriguez * 16442f92212bSJohannes Berg * Returns one of the different reg request treatment values. 1645d1c96a9aSLuis R. Rodriguez */ 16462f92212bSJohannes Berg static enum reg_request_treatment 1647b23e7a9eSLuis R. Rodriguez reg_process_hint_country_ie(struct wiphy *wiphy, 1648b23e7a9eSLuis R. Rodriguez struct regulatory_request *country_ie_request) 1649b2e1b302SLuis R. Rodriguez { 16502f92212bSJohannes Berg enum reg_request_treatment treatment; 1651b2e1b302SLuis R. Rodriguez 1652b23e7a9eSLuis R. Rodriguez treatment = __reg_process_hint_country_ie(wiphy, country_ie_request); 1653761cf7ecSLuis R. Rodriguez 16542f92212bSJohannes Berg switch (treatment) { 16552f92212bSJohannes Berg case REG_REQ_OK: 16562f92212bSJohannes Berg break; 1657b23e7a9eSLuis R. Rodriguez case REG_REQ_IGNORE: 1658b23e7a9eSLuis R. Rodriguez /* fall through */ 1659b23e7a9eSLuis R. Rodriguez case REG_REQ_ALREADY_SET: 1660b23e7a9eSLuis R. Rodriguez kfree(country_ie_request); 1661b23e7a9eSLuis R. Rodriguez return treatment; 1662b23e7a9eSLuis R. Rodriguez case REG_REQ_INTERSECT: 1663b23e7a9eSLuis R. Rodriguez kfree(country_ie_request); 1664fb1fc7adSLuis R. Rodriguez /* 1665b23e7a9eSLuis R. Rodriguez * This doesn't happen yet, not sure we 1666b23e7a9eSLuis R. Rodriguez * ever want to support it for this case. 1667fb1fc7adSLuis R. Rodriguez */ 1668b23e7a9eSLuis R. Rodriguez WARN_ONCE(1, "Unexpected intersection for country IEs"); 16692f92212bSJohannes Berg return REG_REQ_IGNORE; 1670d951c1ddSLuis R. Rodriguez } 1671b2e1b302SLuis R. Rodriguez 1672b23e7a9eSLuis R. Rodriguez country_ie_request->intersect = false; 1673b23e7a9eSLuis R. Rodriguez country_ie_request->processed = false; 16745ad6ef5eSLuis R. Rodriguez 167505f1a3eaSLuis R. Rodriguez reg_update_last_request(country_ie_request); 1676d951c1ddSLuis R. Rodriguez 1677fe6631ffSLuis R. Rodriguez return reg_call_crda(country_ie_request); 1678b2e1b302SLuis R. Rodriguez } 1679b2e1b302SLuis R. Rodriguez 168030a548c7SLuis R. Rodriguez /* This processes *all* regulatory hints */ 16811daa37c7SLuis R. Rodriguez static void reg_process_hint(struct regulatory_request *reg_request) 1682fe33eb39SLuis R. Rodriguez { 1683fe33eb39SLuis R. Rodriguez struct wiphy *wiphy = NULL; 1684b3eb7f3fSLuis R. Rodriguez enum reg_request_treatment treatment; 1685fe33eb39SLuis R. Rodriguez 1686fdc9d7b2SJohannes Berg if (WARN_ON(!reg_request->alpha2)) 1687fdc9d7b2SJohannes Berg return; 1688fe33eb39SLuis R. Rodriguez 1689f4173766SJohannes Berg if (reg_request->wiphy_idx != WIPHY_IDX_INVALID) 1690fe33eb39SLuis R. Rodriguez wiphy = wiphy_idx_to_wiphy(reg_request->wiphy_idx); 1691fe33eb39SLuis R. Rodriguez 16921daa37c7SLuis R. Rodriguez if (reg_request->initiator == NL80211_REGDOM_SET_BY_DRIVER && !wiphy) { 1693d951c1ddSLuis R. Rodriguez kfree(reg_request); 1694b0e2880bSLuis R. Rodriguez return; 1695fe33eb39SLuis R. Rodriguez } 1696fe33eb39SLuis R. Rodriguez 1697b3eb7f3fSLuis R. Rodriguez switch (reg_request->initiator) { 1698b3eb7f3fSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_CORE: 1699b3eb7f3fSLuis R. Rodriguez reg_process_hint_core(reg_request); 1700b3eb7f3fSLuis R. Rodriguez return; 1701b3eb7f3fSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_USER: 17020d97a619SLuis R. Rodriguez treatment = reg_process_hint_user(reg_request); 17030d97a619SLuis R. Rodriguez if (treatment == REG_REQ_OK || 17040d97a619SLuis R. Rodriguez treatment == REG_REQ_ALREADY_SET) 17050d97a619SLuis R. Rodriguez return; 17060d97a619SLuis R. Rodriguez schedule_delayed_work(®_timeout, msecs_to_jiffies(3142)); 17070d97a619SLuis R. Rodriguez return; 1708b3eb7f3fSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_DRIVER: 170921636c7fSLuis R. Rodriguez treatment = reg_process_hint_driver(wiphy, reg_request); 171021636c7fSLuis R. Rodriguez break; 1711b3eb7f3fSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_COUNTRY_IE: 1712b23e7a9eSLuis R. Rodriguez treatment = reg_process_hint_country_ie(wiphy, reg_request); 1713b3eb7f3fSLuis R. Rodriguez break; 1714b3eb7f3fSLuis R. Rodriguez default: 1715b3eb7f3fSLuis R. Rodriguez WARN(1, "invalid initiator %d\n", reg_request->initiator); 1716b3eb7f3fSLuis R. Rodriguez return; 1717b3eb7f3fSLuis R. Rodriguez } 1718b3eb7f3fSLuis R. Rodriguez 1719fe33eb39SLuis R. Rodriguez /* This is required so that the orig_* parameters are saved */ 1720b23e7a9eSLuis R. Rodriguez if (treatment == REG_REQ_ALREADY_SET && wiphy && 1721a2f73b6cSLuis R. Rodriguez wiphy->regulatory_flags & REGULATORY_STRICT_REG) 17221daa37c7SLuis R. Rodriguez wiphy_update_regulatory(wiphy, reg_request->initiator); 1723fe33eb39SLuis R. Rodriguez } 1724fe33eb39SLuis R. Rodriguez 1725b2e253cfSLuis R. Rodriguez /* 1726b2e253cfSLuis R. Rodriguez * Processes regulatory hints, this is all the NL80211_REGDOM_SET_BY_* 1727b2e253cfSLuis R. Rodriguez * Regulatory hints come on a first come first serve basis and we 1728b2e253cfSLuis R. Rodriguez * must process each one atomically. 1729b2e253cfSLuis R. Rodriguez */ 1730fe33eb39SLuis R. Rodriguez static void reg_process_pending_hints(void) 1731fe33eb39SLuis R. Rodriguez { 1732c492db37SJohannes Berg struct regulatory_request *reg_request, *lr; 1733fe33eb39SLuis R. Rodriguez 1734c492db37SJohannes Berg lr = get_last_request(); 1735b0e2880bSLuis R. Rodriguez 1736b2e253cfSLuis R. Rodriguez /* When last_request->processed becomes true this will be rescheduled */ 1737c492db37SJohannes Berg if (lr && !lr->processed) { 17381a919318SJohannes Berg REG_DBG_PRINT("Pending regulatory request, waiting for it to be processed...\n"); 17395fe231e8SJohannes Berg return; 1740b2e253cfSLuis R. Rodriguez } 1741b2e253cfSLuis R. Rodriguez 1742fe33eb39SLuis R. Rodriguez spin_lock(®_requests_lock); 1743b2e253cfSLuis R. Rodriguez 1744b2e253cfSLuis R. Rodriguez if (list_empty(®_requests_list)) { 1745b2e253cfSLuis R. Rodriguez spin_unlock(®_requests_lock); 17465fe231e8SJohannes Berg return; 1747b2e253cfSLuis R. Rodriguez } 1748b2e253cfSLuis R. Rodriguez 1749fe33eb39SLuis R. Rodriguez reg_request = list_first_entry(®_requests_list, 1750fe33eb39SLuis R. Rodriguez struct regulatory_request, 1751fe33eb39SLuis R. Rodriguez list); 1752fe33eb39SLuis R. Rodriguez list_del_init(®_request->list); 1753fe33eb39SLuis R. Rodriguez 1754d951c1ddSLuis R. Rodriguez spin_unlock(®_requests_lock); 1755b0e2880bSLuis R. Rodriguez 17561daa37c7SLuis R. Rodriguez reg_process_hint(reg_request); 1757fe33eb39SLuis R. Rodriguez } 1758fe33eb39SLuis R. Rodriguez 1759e38f8a7aSLuis R. Rodriguez /* Processes beacon hints -- this has nothing to do with country IEs */ 1760e38f8a7aSLuis R. Rodriguez static void reg_process_pending_beacon_hints(void) 1761e38f8a7aSLuis R. Rodriguez { 176279c97e97SJohannes Berg struct cfg80211_registered_device *rdev; 1763e38f8a7aSLuis R. Rodriguez struct reg_beacon *pending_beacon, *tmp; 1764e38f8a7aSLuis R. Rodriguez 1765e38f8a7aSLuis R. Rodriguez /* This goes through the _pending_ beacon list */ 1766e38f8a7aSLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 1767e38f8a7aSLuis R. Rodriguez 1768e38f8a7aSLuis R. Rodriguez list_for_each_entry_safe(pending_beacon, tmp, 1769e38f8a7aSLuis R. Rodriguez ®_pending_beacons, list) { 1770e38f8a7aSLuis R. Rodriguez list_del_init(&pending_beacon->list); 1771e38f8a7aSLuis R. Rodriguez 1772e38f8a7aSLuis R. Rodriguez /* Applies the beacon hint to current wiphys */ 177379c97e97SJohannes Berg list_for_each_entry(rdev, &cfg80211_rdev_list, list) 177479c97e97SJohannes Berg wiphy_update_new_beacon(&rdev->wiphy, pending_beacon); 1775e38f8a7aSLuis R. Rodriguez 1776e38f8a7aSLuis R. Rodriguez /* Remembers the beacon hint for new wiphys or reg changes */ 1777e38f8a7aSLuis R. Rodriguez list_add_tail(&pending_beacon->list, ®_beacon_list); 1778e38f8a7aSLuis R. Rodriguez } 1779e38f8a7aSLuis R. Rodriguez 1780e38f8a7aSLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 1781e38f8a7aSLuis R. Rodriguez } 1782e38f8a7aSLuis R. Rodriguez 1783fe33eb39SLuis R. Rodriguez static void reg_todo(struct work_struct *work) 1784fe33eb39SLuis R. Rodriguez { 17855fe231e8SJohannes Berg rtnl_lock(); 1786fe33eb39SLuis R. Rodriguez reg_process_pending_hints(); 1787e38f8a7aSLuis R. Rodriguez reg_process_pending_beacon_hints(); 17885fe231e8SJohannes Berg rtnl_unlock(); 1789fe33eb39SLuis R. Rodriguez } 1790fe33eb39SLuis R. Rodriguez 1791fe33eb39SLuis R. Rodriguez static void queue_regulatory_request(struct regulatory_request *request) 1792fe33eb39SLuis R. Rodriguez { 1793c61029c7SJohn W. Linville request->alpha2[0] = toupper(request->alpha2[0]); 1794c61029c7SJohn W. Linville request->alpha2[1] = toupper(request->alpha2[1]); 1795c61029c7SJohn W. Linville 1796fe33eb39SLuis R. Rodriguez spin_lock(®_requests_lock); 1797fe33eb39SLuis R. Rodriguez list_add_tail(&request->list, ®_requests_list); 1798fe33eb39SLuis R. Rodriguez spin_unlock(®_requests_lock); 1799fe33eb39SLuis R. Rodriguez 1800fe33eb39SLuis R. Rodriguez schedule_work(®_work); 1801fe33eb39SLuis R. Rodriguez } 1802fe33eb39SLuis R. Rodriguez 180309d989d1SLuis R. Rodriguez /* 180409d989d1SLuis R. Rodriguez * Core regulatory hint -- happens during cfg80211_init() 180509d989d1SLuis R. Rodriguez * and when we restore regulatory settings. 180609d989d1SLuis R. Rodriguez */ 1807ba25c141SLuis R. Rodriguez static int regulatory_hint_core(const char *alpha2) 1808ba25c141SLuis R. Rodriguez { 1809ba25c141SLuis R. Rodriguez struct regulatory_request *request; 1810ba25c141SLuis R. Rodriguez 18111a919318SJohannes Berg request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL); 1812ba25c141SLuis R. Rodriguez if (!request) 1813ba25c141SLuis R. Rodriguez return -ENOMEM; 1814ba25c141SLuis R. Rodriguez 1815ba25c141SLuis R. Rodriguez request->alpha2[0] = alpha2[0]; 1816ba25c141SLuis R. Rodriguez request->alpha2[1] = alpha2[1]; 18177db90f4aSLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_CORE; 1818ba25c141SLuis R. Rodriguez 181931e99729SLuis R. Rodriguez queue_regulatory_request(request); 18205078b2e3SLuis R. Rodriguez 1821fe33eb39SLuis R. Rodriguez return 0; 1822ba25c141SLuis R. Rodriguez } 1823ba25c141SLuis R. Rodriguez 1824fe33eb39SLuis R. Rodriguez /* User hints */ 182557b5ce07SLuis R. Rodriguez int regulatory_hint_user(const char *alpha2, 182657b5ce07SLuis R. Rodriguez enum nl80211_user_reg_hint_type user_reg_hint_type) 1827b2e1b302SLuis R. Rodriguez { 1828fe33eb39SLuis R. Rodriguez struct regulatory_request *request; 1829fe33eb39SLuis R. Rodriguez 1830fdc9d7b2SJohannes Berg if (WARN_ON(!alpha2)) 1831fdc9d7b2SJohannes Berg return -EINVAL; 1832b2e1b302SLuis R. Rodriguez 1833fe33eb39SLuis R. Rodriguez request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL); 1834fe33eb39SLuis R. Rodriguez if (!request) 1835fe33eb39SLuis R. Rodriguez return -ENOMEM; 1836fe33eb39SLuis R. Rodriguez 1837f4173766SJohannes Berg request->wiphy_idx = WIPHY_IDX_INVALID; 1838fe33eb39SLuis R. Rodriguez request->alpha2[0] = alpha2[0]; 1839fe33eb39SLuis R. Rodriguez request->alpha2[1] = alpha2[1]; 1840e12822e1SLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_USER; 184157b5ce07SLuis R. Rodriguez request->user_reg_hint_type = user_reg_hint_type; 1842fe33eb39SLuis R. Rodriguez 1843fe33eb39SLuis R. Rodriguez queue_regulatory_request(request); 1844fe33eb39SLuis R. Rodriguez 1845fe33eb39SLuis R. Rodriguez return 0; 1846fe33eb39SLuis R. Rodriguez } 1847fe33eb39SLuis R. Rodriguez 1848fe33eb39SLuis R. Rodriguez /* Driver hints */ 1849fe33eb39SLuis R. Rodriguez int regulatory_hint(struct wiphy *wiphy, const char *alpha2) 1850fe33eb39SLuis R. Rodriguez { 1851fe33eb39SLuis R. Rodriguez struct regulatory_request *request; 1852fe33eb39SLuis R. Rodriguez 1853fdc9d7b2SJohannes Berg if (WARN_ON(!alpha2 || !wiphy)) 1854fdc9d7b2SJohannes Berg return -EINVAL; 1855fe33eb39SLuis R. Rodriguez 18564f7b9140SLuis R. Rodriguez wiphy->regulatory_flags &= ~REGULATORY_CUSTOM_REG; 18574f7b9140SLuis R. Rodriguez 1858fe33eb39SLuis R. Rodriguez request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL); 1859fe33eb39SLuis R. Rodriguez if (!request) 1860fe33eb39SLuis R. Rodriguez return -ENOMEM; 1861fe33eb39SLuis R. Rodriguez 1862fe33eb39SLuis R. Rodriguez request->wiphy_idx = get_wiphy_idx(wiphy); 1863fe33eb39SLuis R. Rodriguez 1864fe33eb39SLuis R. Rodriguez request->alpha2[0] = alpha2[0]; 1865fe33eb39SLuis R. Rodriguez request->alpha2[1] = alpha2[1]; 18667db90f4aSLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_DRIVER; 1867fe33eb39SLuis R. Rodriguez 1868fe33eb39SLuis R. Rodriguez queue_regulatory_request(request); 1869fe33eb39SLuis R. Rodriguez 1870fe33eb39SLuis R. Rodriguez return 0; 1871b2e1b302SLuis R. Rodriguez } 1872b2e1b302SLuis R. Rodriguez EXPORT_SYMBOL(regulatory_hint); 1873b2e1b302SLuis R. Rodriguez 1874789fd033SLuis R. Rodriguez void regulatory_hint_country_ie(struct wiphy *wiphy, enum ieee80211_band band, 18751a919318SJohannes Berg const u8 *country_ie, u8 country_ie_len) 18763f2355cbSLuis R. Rodriguez { 18773f2355cbSLuis R. Rodriguez char alpha2[2]; 18783f2355cbSLuis R. Rodriguez enum environment_cap env = ENVIRON_ANY; 1879db2424c5SJohannes Berg struct regulatory_request *request = NULL, *lr; 1880d335fe63SLuis R. Rodriguez 18813f2355cbSLuis R. Rodriguez /* IE len must be evenly divisible by 2 */ 18823f2355cbSLuis R. Rodriguez if (country_ie_len & 0x01) 1883db2424c5SJohannes Berg return; 18843f2355cbSLuis R. Rodriguez 18853f2355cbSLuis R. Rodriguez if (country_ie_len < IEEE80211_COUNTRY_IE_MIN_LEN) 1886db2424c5SJohannes Berg return; 1887db2424c5SJohannes Berg 1888db2424c5SJohannes Berg request = kzalloc(sizeof(*request), GFP_KERNEL); 1889db2424c5SJohannes Berg if (!request) 1890db2424c5SJohannes Berg return; 18913f2355cbSLuis R. Rodriguez 18923f2355cbSLuis R. Rodriguez alpha2[0] = country_ie[0]; 18933f2355cbSLuis R. Rodriguez alpha2[1] = country_ie[1]; 18943f2355cbSLuis R. Rodriguez 18953f2355cbSLuis R. Rodriguez if (country_ie[2] == 'I') 18963f2355cbSLuis R. Rodriguez env = ENVIRON_INDOOR; 18973f2355cbSLuis R. Rodriguez else if (country_ie[2] == 'O') 18983f2355cbSLuis R. Rodriguez env = ENVIRON_OUTDOOR; 18993f2355cbSLuis R. Rodriguez 1900db2424c5SJohannes Berg rcu_read_lock(); 1901db2424c5SJohannes Berg lr = get_last_request(); 1902db2424c5SJohannes Berg 1903db2424c5SJohannes Berg if (unlikely(!lr)) 1904db2424c5SJohannes Berg goto out; 1905db2424c5SJohannes Berg 1906fb1fc7adSLuis R. Rodriguez /* 19078b19e6caSLuis R. Rodriguez * We will run this only upon a successful connection on cfg80211. 19084b44c8bcSLuis R. Rodriguez * We leave conflict resolution to the workqueue, where can hold 19095fe231e8SJohannes Berg * the RTNL. 1910fb1fc7adSLuis R. Rodriguez */ 1911c492db37SJohannes Berg if (lr->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE && 1912c492db37SJohannes Berg lr->wiphy_idx != WIPHY_IDX_INVALID) 19133f2355cbSLuis R. Rodriguez goto out; 19143f2355cbSLuis R. Rodriguez 1915fe33eb39SLuis R. Rodriguez request->wiphy_idx = get_wiphy_idx(wiphy); 19164f366c5dSJohn W. Linville request->alpha2[0] = alpha2[0]; 19174f366c5dSJohn W. Linville request->alpha2[1] = alpha2[1]; 19187db90f4aSLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_COUNTRY_IE; 1919fe33eb39SLuis R. Rodriguez request->country_ie_env = env; 19203f2355cbSLuis R. Rodriguez 1921fe33eb39SLuis R. Rodriguez queue_regulatory_request(request); 1922db2424c5SJohannes Berg request = NULL; 19233f2355cbSLuis R. Rodriguez out: 1924db2424c5SJohannes Berg kfree(request); 1925db2424c5SJohannes Berg rcu_read_unlock(); 19263f2355cbSLuis R. Rodriguez } 1927b2e1b302SLuis R. Rodriguez 192809d989d1SLuis R. Rodriguez static void restore_alpha2(char *alpha2, bool reset_user) 192909d989d1SLuis R. Rodriguez { 193009d989d1SLuis R. Rodriguez /* indicates there is no alpha2 to consider for restoration */ 193109d989d1SLuis R. Rodriguez alpha2[0] = '9'; 193209d989d1SLuis R. Rodriguez alpha2[1] = '7'; 193309d989d1SLuis R. Rodriguez 193409d989d1SLuis R. Rodriguez /* The user setting has precedence over the module parameter */ 193509d989d1SLuis R. Rodriguez if (is_user_regdom_saved()) { 193609d989d1SLuis R. Rodriguez /* Unless we're asked to ignore it and reset it */ 193709d989d1SLuis R. Rodriguez if (reset_user) { 19381a919318SJohannes Berg REG_DBG_PRINT("Restoring regulatory settings including user preference\n"); 193909d989d1SLuis R. Rodriguez user_alpha2[0] = '9'; 194009d989d1SLuis R. Rodriguez user_alpha2[1] = '7'; 194109d989d1SLuis R. Rodriguez 194209d989d1SLuis R. Rodriguez /* 194309d989d1SLuis R. Rodriguez * If we're ignoring user settings, we still need to 194409d989d1SLuis R. Rodriguez * check the module parameter to ensure we put things 194509d989d1SLuis R. Rodriguez * back as they were for a full restore. 194609d989d1SLuis R. Rodriguez */ 194709d989d1SLuis R. Rodriguez if (!is_world_regdom(ieee80211_regdom)) { 19481a919318SJohannes Berg REG_DBG_PRINT("Keeping preference on module parameter ieee80211_regdom: %c%c\n", 19491a919318SJohannes Berg ieee80211_regdom[0], ieee80211_regdom[1]); 195009d989d1SLuis R. Rodriguez alpha2[0] = ieee80211_regdom[0]; 195109d989d1SLuis R. Rodriguez alpha2[1] = ieee80211_regdom[1]; 195209d989d1SLuis R. Rodriguez } 195309d989d1SLuis R. Rodriguez } else { 19541a919318SJohannes Berg REG_DBG_PRINT("Restoring regulatory settings while preserving user preference for: %c%c\n", 19551a919318SJohannes Berg user_alpha2[0], user_alpha2[1]); 195609d989d1SLuis R. Rodriguez alpha2[0] = user_alpha2[0]; 195709d989d1SLuis R. Rodriguez alpha2[1] = user_alpha2[1]; 195809d989d1SLuis R. Rodriguez } 195909d989d1SLuis R. Rodriguez } else if (!is_world_regdom(ieee80211_regdom)) { 19601a919318SJohannes Berg REG_DBG_PRINT("Keeping preference on module parameter ieee80211_regdom: %c%c\n", 19611a919318SJohannes Berg ieee80211_regdom[0], ieee80211_regdom[1]); 196209d989d1SLuis R. Rodriguez alpha2[0] = ieee80211_regdom[0]; 196309d989d1SLuis R. Rodriguez alpha2[1] = ieee80211_regdom[1]; 196409d989d1SLuis R. Rodriguez } else 1965d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Restoring regulatory settings\n"); 196609d989d1SLuis R. Rodriguez } 196709d989d1SLuis R. Rodriguez 19685ce543d1SRajkumar Manoharan static void restore_custom_reg_settings(struct wiphy *wiphy) 19695ce543d1SRajkumar Manoharan { 19705ce543d1SRajkumar Manoharan struct ieee80211_supported_band *sband; 19715ce543d1SRajkumar Manoharan enum ieee80211_band band; 19725ce543d1SRajkumar Manoharan struct ieee80211_channel *chan; 19735ce543d1SRajkumar Manoharan int i; 19745ce543d1SRajkumar Manoharan 19755ce543d1SRajkumar Manoharan for (band = 0; band < IEEE80211_NUM_BANDS; band++) { 19765ce543d1SRajkumar Manoharan sband = wiphy->bands[band]; 19775ce543d1SRajkumar Manoharan if (!sband) 19785ce543d1SRajkumar Manoharan continue; 19795ce543d1SRajkumar Manoharan for (i = 0; i < sband->n_channels; i++) { 19805ce543d1SRajkumar Manoharan chan = &sband->channels[i]; 19815ce543d1SRajkumar Manoharan chan->flags = chan->orig_flags; 19825ce543d1SRajkumar Manoharan chan->max_antenna_gain = chan->orig_mag; 19835ce543d1SRajkumar Manoharan chan->max_power = chan->orig_mpwr; 1984899852afSPaul Stewart chan->beacon_found = false; 19855ce543d1SRajkumar Manoharan } 19865ce543d1SRajkumar Manoharan } 19875ce543d1SRajkumar Manoharan } 19885ce543d1SRajkumar Manoharan 198909d989d1SLuis R. Rodriguez /* 199009d989d1SLuis R. Rodriguez * Restoring regulatory settings involves ingoring any 199109d989d1SLuis R. Rodriguez * possibly stale country IE information and user regulatory 199209d989d1SLuis R. Rodriguez * settings if so desired, this includes any beacon hints 199309d989d1SLuis R. Rodriguez * learned as we could have traveled outside to another country 199409d989d1SLuis R. Rodriguez * after disconnection. To restore regulatory settings we do 199509d989d1SLuis R. Rodriguez * exactly what we did at bootup: 199609d989d1SLuis R. Rodriguez * 199709d989d1SLuis R. Rodriguez * - send a core regulatory hint 199809d989d1SLuis R. Rodriguez * - send a user regulatory hint if applicable 199909d989d1SLuis R. Rodriguez * 200009d989d1SLuis R. Rodriguez * Device drivers that send a regulatory hint for a specific country 200109d989d1SLuis R. Rodriguez * keep their own regulatory domain on wiphy->regd so that does does 200209d989d1SLuis R. Rodriguez * not need to be remembered. 200309d989d1SLuis R. Rodriguez */ 200409d989d1SLuis R. Rodriguez static void restore_regulatory_settings(bool reset_user) 200509d989d1SLuis R. Rodriguez { 200609d989d1SLuis R. Rodriguez char alpha2[2]; 2007cee0bec5SDmitry Shmidt char world_alpha2[2]; 200809d989d1SLuis R. Rodriguez struct reg_beacon *reg_beacon, *btmp; 200914609555SLuis R. Rodriguez struct regulatory_request *reg_request, *tmp; 201014609555SLuis R. Rodriguez LIST_HEAD(tmp_reg_req_list); 20115ce543d1SRajkumar Manoharan struct cfg80211_registered_device *rdev; 201209d989d1SLuis R. Rodriguez 20135fe231e8SJohannes Berg ASSERT_RTNL(); 20145fe231e8SJohannes Berg 20152d319867SJohannes Berg reset_regdomains(true, &world_regdom); 201609d989d1SLuis R. Rodriguez restore_alpha2(alpha2, reset_user); 201709d989d1SLuis R. Rodriguez 201814609555SLuis R. Rodriguez /* 201914609555SLuis R. Rodriguez * If there's any pending requests we simply 202014609555SLuis R. Rodriguez * stash them to a temporary pending queue and 202114609555SLuis R. Rodriguez * add then after we've restored regulatory 202214609555SLuis R. Rodriguez * settings. 202314609555SLuis R. Rodriguez */ 202414609555SLuis R. Rodriguez spin_lock(®_requests_lock); 2025fea9bcedSJohannes Berg list_for_each_entry_safe(reg_request, tmp, ®_requests_list, list) { 2026fea9bcedSJohannes Berg if (reg_request->initiator != NL80211_REGDOM_SET_BY_USER) 202714609555SLuis R. Rodriguez continue; 202800a9ac4cSWei Yongjun list_move_tail(®_request->list, &tmp_reg_req_list); 202914609555SLuis R. Rodriguez } 203014609555SLuis R. Rodriguez spin_unlock(®_requests_lock); 203114609555SLuis R. Rodriguez 203209d989d1SLuis R. Rodriguez /* Clear beacon hints */ 203309d989d1SLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 2034fea9bcedSJohannes Berg list_for_each_entry_safe(reg_beacon, btmp, ®_pending_beacons, list) { 203509d989d1SLuis R. Rodriguez list_del(®_beacon->list); 203609d989d1SLuis R. Rodriguez kfree(reg_beacon); 203709d989d1SLuis R. Rodriguez } 203809d989d1SLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 203909d989d1SLuis R. Rodriguez 2040fea9bcedSJohannes Berg list_for_each_entry_safe(reg_beacon, btmp, ®_beacon_list, list) { 204109d989d1SLuis R. Rodriguez list_del(®_beacon->list); 204209d989d1SLuis R. Rodriguez kfree(reg_beacon); 204309d989d1SLuis R. Rodriguez } 204409d989d1SLuis R. Rodriguez 204509d989d1SLuis R. Rodriguez /* First restore to the basic regulatory settings */ 2046379b82f4SJohannes Berg world_alpha2[0] = cfg80211_world_regdom->alpha2[0]; 2047379b82f4SJohannes Berg world_alpha2[1] = cfg80211_world_regdom->alpha2[1]; 204809d989d1SLuis R. Rodriguez 20495ce543d1SRajkumar Manoharan list_for_each_entry(rdev, &cfg80211_rdev_list, list) { 2050a2f73b6cSLuis R. Rodriguez if (rdev->wiphy.regulatory_flags & REGULATORY_CUSTOM_REG) 20515ce543d1SRajkumar Manoharan restore_custom_reg_settings(&rdev->wiphy); 20525ce543d1SRajkumar Manoharan } 20535ce543d1SRajkumar Manoharan 2054cee0bec5SDmitry Shmidt regulatory_hint_core(world_alpha2); 205509d989d1SLuis R. Rodriguez 205609d989d1SLuis R. Rodriguez /* 205709d989d1SLuis R. Rodriguez * This restores the ieee80211_regdom module parameter 205809d989d1SLuis R. Rodriguez * preference or the last user requested regulatory 205909d989d1SLuis R. Rodriguez * settings, user regulatory settings takes precedence. 206009d989d1SLuis R. Rodriguez */ 206109d989d1SLuis R. Rodriguez if (is_an_alpha2(alpha2)) 206257b5ce07SLuis R. Rodriguez regulatory_hint_user(user_alpha2, NL80211_USER_REG_HINT_USER); 206309d989d1SLuis R. Rodriguez 206414609555SLuis R. Rodriguez spin_lock(®_requests_lock); 206511cff96cSJohannes Berg list_splice_tail_init(&tmp_reg_req_list, ®_requests_list); 206614609555SLuis R. Rodriguez spin_unlock(®_requests_lock); 206714609555SLuis R. Rodriguez 206814609555SLuis R. Rodriguez REG_DBG_PRINT("Kicking the queue\n"); 206914609555SLuis R. Rodriguez 207014609555SLuis R. Rodriguez schedule_work(®_work); 207114609555SLuis R. Rodriguez } 207209d989d1SLuis R. Rodriguez 207309d989d1SLuis R. Rodriguez void regulatory_hint_disconnect(void) 207409d989d1SLuis R. Rodriguez { 20751a919318SJohannes Berg REG_DBG_PRINT("All devices are disconnected, going to restore regulatory settings\n"); 207609d989d1SLuis R. Rodriguez restore_regulatory_settings(false); 207709d989d1SLuis R. Rodriguez } 207809d989d1SLuis R. Rodriguez 2079e38f8a7aSLuis R. Rodriguez static bool freq_is_chan_12_13_14(u16 freq) 2080e38f8a7aSLuis R. Rodriguez { 208159eb21a6SBruno Randolf if (freq == ieee80211_channel_to_frequency(12, IEEE80211_BAND_2GHZ) || 208259eb21a6SBruno Randolf freq == ieee80211_channel_to_frequency(13, IEEE80211_BAND_2GHZ) || 208359eb21a6SBruno Randolf freq == ieee80211_channel_to_frequency(14, IEEE80211_BAND_2GHZ)) 2084e38f8a7aSLuis R. Rodriguez return true; 2085e38f8a7aSLuis R. Rodriguez return false; 2086e38f8a7aSLuis R. Rodriguez } 2087e38f8a7aSLuis R. Rodriguez 20883ebfa6e7SLuis R. Rodriguez static bool pending_reg_beacon(struct ieee80211_channel *beacon_chan) 20893ebfa6e7SLuis R. Rodriguez { 20903ebfa6e7SLuis R. Rodriguez struct reg_beacon *pending_beacon; 20913ebfa6e7SLuis R. Rodriguez 20923ebfa6e7SLuis R. Rodriguez list_for_each_entry(pending_beacon, ®_pending_beacons, list) 20933ebfa6e7SLuis R. Rodriguez if (beacon_chan->center_freq == 20943ebfa6e7SLuis R. Rodriguez pending_beacon->chan.center_freq) 20953ebfa6e7SLuis R. Rodriguez return true; 20963ebfa6e7SLuis R. Rodriguez return false; 20973ebfa6e7SLuis R. Rodriguez } 20983ebfa6e7SLuis R. Rodriguez 2099e38f8a7aSLuis R. Rodriguez int regulatory_hint_found_beacon(struct wiphy *wiphy, 2100e38f8a7aSLuis R. Rodriguez struct ieee80211_channel *beacon_chan, 2101e38f8a7aSLuis R. Rodriguez gfp_t gfp) 2102e38f8a7aSLuis R. Rodriguez { 2103e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon; 21043ebfa6e7SLuis R. Rodriguez bool processing; 2105e38f8a7aSLuis R. Rodriguez 21061a919318SJohannes Berg if (beacon_chan->beacon_found || 21071a919318SJohannes Berg beacon_chan->flags & IEEE80211_CHAN_RADAR || 2108e38f8a7aSLuis R. Rodriguez (beacon_chan->band == IEEE80211_BAND_2GHZ && 21091a919318SJohannes Berg !freq_is_chan_12_13_14(beacon_chan->center_freq))) 2110e38f8a7aSLuis R. Rodriguez return 0; 2111e38f8a7aSLuis R. Rodriguez 21123ebfa6e7SLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 21133ebfa6e7SLuis R. Rodriguez processing = pending_reg_beacon(beacon_chan); 21143ebfa6e7SLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 21153ebfa6e7SLuis R. Rodriguez 21163ebfa6e7SLuis R. Rodriguez if (processing) 2117e38f8a7aSLuis R. Rodriguez return 0; 2118e38f8a7aSLuis R. Rodriguez 2119e38f8a7aSLuis R. Rodriguez reg_beacon = kzalloc(sizeof(struct reg_beacon), gfp); 2120e38f8a7aSLuis R. Rodriguez if (!reg_beacon) 2121e38f8a7aSLuis R. Rodriguez return -ENOMEM; 2122e38f8a7aSLuis R. Rodriguez 21231a919318SJohannes Berg REG_DBG_PRINT("Found new beacon on frequency: %d MHz (Ch %d) on %s\n", 2124e38f8a7aSLuis R. Rodriguez beacon_chan->center_freq, 2125e38f8a7aSLuis R. Rodriguez ieee80211_frequency_to_channel(beacon_chan->center_freq), 2126e38f8a7aSLuis R. Rodriguez wiphy_name(wiphy)); 21274113f751SLuis R. Rodriguez 2128e38f8a7aSLuis R. Rodriguez memcpy(®_beacon->chan, beacon_chan, 2129e38f8a7aSLuis R. Rodriguez sizeof(struct ieee80211_channel)); 2130e38f8a7aSLuis R. Rodriguez 2131e38f8a7aSLuis R. Rodriguez /* 2132e38f8a7aSLuis R. Rodriguez * Since we can be called from BH or and non-BH context 2133e38f8a7aSLuis R. Rodriguez * we must use spin_lock_bh() 2134e38f8a7aSLuis R. Rodriguez */ 2135e38f8a7aSLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 2136e38f8a7aSLuis R. Rodriguez list_add_tail(®_beacon->list, ®_pending_beacons); 2137e38f8a7aSLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 2138e38f8a7aSLuis R. Rodriguez 2139e38f8a7aSLuis R. Rodriguez schedule_work(®_work); 2140e38f8a7aSLuis R. Rodriguez 2141e38f8a7aSLuis R. Rodriguez return 0; 2142e38f8a7aSLuis R. Rodriguez } 2143e38f8a7aSLuis R. Rodriguez 2144a3d2eaf0SJohannes Berg static void print_rd_rules(const struct ieee80211_regdomain *rd) 2145b2e1b302SLuis R. Rodriguez { 2146b2e1b302SLuis R. Rodriguez unsigned int i; 2147a3d2eaf0SJohannes Berg const struct ieee80211_reg_rule *reg_rule = NULL; 2148a3d2eaf0SJohannes Berg const struct ieee80211_freq_range *freq_range = NULL; 2149a3d2eaf0SJohannes Berg const struct ieee80211_power_rule *power_rule = NULL; 2150b2e1b302SLuis R. Rodriguez 2151e9c0268fSJoe Perches pr_info(" (start_freq - end_freq @ bandwidth), (max_antenna_gain, max_eirp)\n"); 2152b2e1b302SLuis R. Rodriguez 2153b2e1b302SLuis R. Rodriguez for (i = 0; i < rd->n_reg_rules; i++) { 2154b2e1b302SLuis R. Rodriguez reg_rule = &rd->reg_rules[i]; 2155b2e1b302SLuis R. Rodriguez freq_range = ®_rule->freq_range; 2156b2e1b302SLuis R. Rodriguez power_rule = ®_rule->power_rule; 2157b2e1b302SLuis R. Rodriguez 2158fb1fc7adSLuis R. Rodriguez /* 2159fb1fc7adSLuis R. Rodriguez * There may not be documentation for max antenna gain 2160fb1fc7adSLuis R. Rodriguez * in certain regions 2161fb1fc7adSLuis R. Rodriguez */ 2162b2e1b302SLuis R. Rodriguez if (power_rule->max_antenna_gain) 2163e9c0268fSJoe Perches pr_info(" (%d KHz - %d KHz @ %d KHz), (%d mBi, %d mBm)\n", 2164b2e1b302SLuis R. Rodriguez freq_range->start_freq_khz, 2165b2e1b302SLuis R. Rodriguez freq_range->end_freq_khz, 2166b2e1b302SLuis R. Rodriguez freq_range->max_bandwidth_khz, 2167b2e1b302SLuis R. Rodriguez power_rule->max_antenna_gain, 2168b2e1b302SLuis R. Rodriguez power_rule->max_eirp); 2169b2e1b302SLuis R. Rodriguez else 2170e9c0268fSJoe Perches pr_info(" (%d KHz - %d KHz @ %d KHz), (N/A, %d mBm)\n", 2171b2e1b302SLuis R. Rodriguez freq_range->start_freq_khz, 2172b2e1b302SLuis R. Rodriguez freq_range->end_freq_khz, 2173b2e1b302SLuis R. Rodriguez freq_range->max_bandwidth_khz, 2174b2e1b302SLuis R. Rodriguez power_rule->max_eirp); 2175b2e1b302SLuis R. Rodriguez } 2176b2e1b302SLuis R. Rodriguez } 2177b2e1b302SLuis R. Rodriguez 21784c7d3982SLuis R. Rodriguez bool reg_supported_dfs_region(enum nl80211_dfs_regions dfs_region) 21798b60b078SLuis R. Rodriguez { 21808b60b078SLuis R. Rodriguez switch (dfs_region) { 21818b60b078SLuis R. Rodriguez case NL80211_DFS_UNSET: 21828b60b078SLuis R. Rodriguez case NL80211_DFS_FCC: 21838b60b078SLuis R. Rodriguez case NL80211_DFS_ETSI: 21848b60b078SLuis R. Rodriguez case NL80211_DFS_JP: 21858b60b078SLuis R. Rodriguez return true; 21868b60b078SLuis R. Rodriguez default: 21878b60b078SLuis R. Rodriguez REG_DBG_PRINT("Ignoring uknown DFS master region: %d\n", 21888b60b078SLuis R. Rodriguez dfs_region); 21898b60b078SLuis R. Rodriguez return false; 21908b60b078SLuis R. Rodriguez } 21918b60b078SLuis R. Rodriguez } 21928b60b078SLuis R. Rodriguez 2193a3d2eaf0SJohannes Berg static void print_regdomain(const struct ieee80211_regdomain *rd) 2194b2e1b302SLuis R. Rodriguez { 2195c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 2196b2e1b302SLuis R. Rodriguez 21973f2355cbSLuis R. Rodriguez if (is_intersected_alpha2(rd->alpha2)) { 2198c492db37SJohannes Berg if (lr->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE) { 219979c97e97SJohannes Berg struct cfg80211_registered_device *rdev; 2200c492db37SJohannes Berg rdev = cfg80211_rdev_by_wiphy_idx(lr->wiphy_idx); 220179c97e97SJohannes Berg if (rdev) { 2202e9c0268fSJoe Perches pr_info("Current regulatory domain updated by AP to: %c%c\n", 220379c97e97SJohannes Berg rdev->country_ie_alpha2[0], 220479c97e97SJohannes Berg rdev->country_ie_alpha2[1]); 22053f2355cbSLuis R. Rodriguez } else 2206e9c0268fSJoe Perches pr_info("Current regulatory domain intersected:\n"); 22073f2355cbSLuis R. Rodriguez } else 2208e9c0268fSJoe Perches pr_info("Current regulatory domain intersected:\n"); 22091a919318SJohannes Berg } else if (is_world_regdom(rd->alpha2)) { 2210e9c0268fSJoe Perches pr_info("World regulatory domain updated:\n"); 22111a919318SJohannes Berg } else { 2212b2e1b302SLuis R. Rodriguez if (is_unknown_alpha2(rd->alpha2)) 2213e9c0268fSJoe Perches pr_info("Regulatory domain changed to driver built-in settings (unknown country)\n"); 221457b5ce07SLuis R. Rodriguez else { 2215c492db37SJohannes Berg if (reg_request_cell_base(lr)) 22161a919318SJohannes Berg pr_info("Regulatory domain changed to country: %c%c by Cell Station\n", 2217b2e1b302SLuis R. Rodriguez rd->alpha2[0], rd->alpha2[1]); 221857b5ce07SLuis R. Rodriguez else 22191a919318SJohannes Berg pr_info("Regulatory domain changed to country: %c%c\n", 222057b5ce07SLuis R. Rodriguez rd->alpha2[0], rd->alpha2[1]); 222157b5ce07SLuis R. Rodriguez } 2222b2e1b302SLuis R. Rodriguez } 22231a919318SJohannes Berg 22243ef121b5SLuis R. Rodriguez pr_info(" DFS Master region: %s", reg_dfs_region_str(rd->dfs_region)); 2225b2e1b302SLuis R. Rodriguez print_rd_rules(rd); 2226b2e1b302SLuis R. Rodriguez } 2227b2e1b302SLuis R. Rodriguez 22282df78167SJohannes Berg static void print_regdomain_info(const struct ieee80211_regdomain *rd) 2229b2e1b302SLuis R. Rodriguez { 2230e9c0268fSJoe Perches pr_info("Regulatory domain: %c%c\n", rd->alpha2[0], rd->alpha2[1]); 2231b2e1b302SLuis R. Rodriguez print_rd_rules(rd); 2232b2e1b302SLuis R. Rodriguez } 2233b2e1b302SLuis R. Rodriguez 22343b9e5acaSLuis R. Rodriguez static int reg_set_rd_core(const struct ieee80211_regdomain *rd) 22353b9e5acaSLuis R. Rodriguez { 22363b9e5acaSLuis R. Rodriguez if (!is_world_regdom(rd->alpha2)) 22373b9e5acaSLuis R. Rodriguez return -EINVAL; 22383b9e5acaSLuis R. Rodriguez update_world_regdomain(rd); 22393b9e5acaSLuis R. Rodriguez return 0; 22403b9e5acaSLuis R. Rodriguez } 22413b9e5acaSLuis R. Rodriguez 224284721d44SLuis R. Rodriguez static int reg_set_rd_user(const struct ieee80211_regdomain *rd, 224384721d44SLuis R. Rodriguez struct regulatory_request *user_request) 224484721d44SLuis R. Rodriguez { 224584721d44SLuis R. Rodriguez const struct ieee80211_regdomain *intersected_rd = NULL; 224684721d44SLuis R. Rodriguez 224784721d44SLuis R. Rodriguez if (is_world_regdom(rd->alpha2)) 224884721d44SLuis R. Rodriguez return -EINVAL; 224984721d44SLuis R. Rodriguez 225084721d44SLuis R. Rodriguez if (!regdom_changes(rd->alpha2)) 225184721d44SLuis R. Rodriguez return -EALREADY; 225284721d44SLuis R. Rodriguez 225384721d44SLuis R. Rodriguez if (!is_valid_rd(rd)) { 225484721d44SLuis R. Rodriguez pr_err("Invalid regulatory domain detected:\n"); 225584721d44SLuis R. Rodriguez print_regdomain_info(rd); 225684721d44SLuis R. Rodriguez return -EINVAL; 225784721d44SLuis R. Rodriguez } 225884721d44SLuis R. Rodriguez 225984721d44SLuis R. Rodriguez if (!user_request->intersect) { 226084721d44SLuis R. Rodriguez reset_regdomains(false, rd); 226184721d44SLuis R. Rodriguez return 0; 226284721d44SLuis R. Rodriguez } 226384721d44SLuis R. Rodriguez 226484721d44SLuis R. Rodriguez intersected_rd = regdom_intersect(rd, get_cfg80211_regdom()); 226584721d44SLuis R. Rodriguez if (!intersected_rd) 226684721d44SLuis R. Rodriguez return -EINVAL; 226784721d44SLuis R. Rodriguez 226884721d44SLuis R. Rodriguez kfree(rd); 226984721d44SLuis R. Rodriguez rd = NULL; 227084721d44SLuis R. Rodriguez reset_regdomains(false, intersected_rd); 227184721d44SLuis R. Rodriguez 227284721d44SLuis R. Rodriguez return 0; 227384721d44SLuis R. Rodriguez } 227484721d44SLuis R. Rodriguez 2275f5fe3247SLuis R. Rodriguez static int reg_set_rd_driver(const struct ieee80211_regdomain *rd, 2276f5fe3247SLuis R. Rodriguez struct regulatory_request *driver_request) 2277b2e1b302SLuis R. Rodriguez { 2278e9763c3cSJohannes Berg const struct ieee80211_regdomain *regd; 22799c96477dSLuis R. Rodriguez const struct ieee80211_regdomain *intersected_rd = NULL; 2280f5fe3247SLuis R. Rodriguez const struct ieee80211_regdomain *tmp; 2281806a9e39SLuis R. Rodriguez struct wiphy *request_wiphy; 22826913b49aSJohannes Berg 2283f5fe3247SLuis R. Rodriguez if (is_world_regdom(rd->alpha2)) 2284b2e1b302SLuis R. Rodriguez return -EINVAL; 2285b2e1b302SLuis R. Rodriguez 2286baeb66feSJohn W. Linville if (!regdom_changes(rd->alpha2)) 228795908535SKalle Valo return -EALREADY; 2288b2e1b302SLuis R. Rodriguez 2289b2e1b302SLuis R. Rodriguez if (!is_valid_rd(rd)) { 2290e9c0268fSJoe Perches pr_err("Invalid regulatory domain detected:\n"); 2291b2e1b302SLuis R. Rodriguez print_regdomain_info(rd); 2292b2e1b302SLuis R. Rodriguez return -EINVAL; 2293b2e1b302SLuis R. Rodriguez } 2294b2e1b302SLuis R. Rodriguez 2295f5fe3247SLuis R. Rodriguez request_wiphy = wiphy_idx_to_wiphy(driver_request->wiphy_idx); 2296f5fe3247SLuis R. Rodriguez if (!request_wiphy) { 22970bac71afSLuis R. Rodriguez schedule_delayed_work(®_timeout, 0); 2298de3584bdSJohannes Berg return -ENODEV; 2299de3584bdSJohannes Berg } 2300806a9e39SLuis R. Rodriguez 2301f5fe3247SLuis R. Rodriguez if (!driver_request->intersect) { 2302558f6d32SLuis R. Rodriguez if (request_wiphy->regd) 2303558f6d32SLuis R. Rodriguez return -EALREADY; 23043e0c3ff3SLuis R. Rodriguez 2305e9763c3cSJohannes Berg regd = reg_copy_regd(rd); 2306e9763c3cSJohannes Berg if (IS_ERR(regd)) 2307e9763c3cSJohannes Berg return PTR_ERR(regd); 23083e0c3ff3SLuis R. Rodriguez 2309458f4f9eSJohannes Berg rcu_assign_pointer(request_wiphy->regd, regd); 2310379b82f4SJohannes Berg reset_regdomains(false, rd); 2311b8295acdSLuis R. Rodriguez return 0; 2312b8295acdSLuis R. Rodriguez } 2313b8295acdSLuis R. Rodriguez 2314458f4f9eSJohannes Berg intersected_rd = regdom_intersect(rd, get_cfg80211_regdom()); 23159c96477dSLuis R. Rodriguez if (!intersected_rd) 23169c96477dSLuis R. Rodriguez return -EINVAL; 2317b8295acdSLuis R. Rodriguez 2318fb1fc7adSLuis R. Rodriguez /* 2319fb1fc7adSLuis R. Rodriguez * We can trash what CRDA provided now. 23203e0c3ff3SLuis R. Rodriguez * However if a driver requested this specific regulatory 2321fb1fc7adSLuis R. Rodriguez * domain we keep it for its private use 2322fb1fc7adSLuis R. Rodriguez */ 2323b7566fc3SLarry Finger tmp = get_wiphy_regdom(request_wiphy); 2324458f4f9eSJohannes Berg rcu_assign_pointer(request_wiphy->regd, rd); 2325b7566fc3SLarry Finger rcu_free_regdom(tmp); 23263e0c3ff3SLuis R. Rodriguez 2327b8295acdSLuis R. Rodriguez rd = NULL; 2328b8295acdSLuis R. Rodriguez 2329379b82f4SJohannes Berg reset_regdomains(false, intersected_rd); 2330b8295acdSLuis R. Rodriguez 2331b8295acdSLuis R. Rodriguez return 0; 23329c96477dSLuis R. Rodriguez } 23339c96477dSLuis R. Rodriguez 233401992406SLuis R. Rodriguez static int reg_set_rd_country_ie(const struct ieee80211_regdomain *rd, 233501992406SLuis R. Rodriguez struct regulatory_request *country_ie_request) 2336f5fe3247SLuis R. Rodriguez { 2337f5fe3247SLuis R. Rodriguez struct wiphy *request_wiphy; 2338f5fe3247SLuis R. Rodriguez 2339f5fe3247SLuis R. Rodriguez if (!is_alpha2_set(rd->alpha2) && !is_an_alpha2(rd->alpha2) && 2340f5fe3247SLuis R. Rodriguez !is_unknown_alpha2(rd->alpha2)) 2341f5fe3247SLuis R. Rodriguez return -EINVAL; 2342f5fe3247SLuis R. Rodriguez 2343f5fe3247SLuis R. Rodriguez /* 2344f5fe3247SLuis R. Rodriguez * Lets only bother proceeding on the same alpha2 if the current 2345f5fe3247SLuis R. Rodriguez * rd is non static (it means CRDA was present and was used last) 2346f5fe3247SLuis R. Rodriguez * and the pending request came in from a country IE 2347f5fe3247SLuis R. Rodriguez */ 2348f5fe3247SLuis R. Rodriguez 2349f5fe3247SLuis R. Rodriguez if (!is_valid_rd(rd)) { 2350f5fe3247SLuis R. Rodriguez pr_err("Invalid regulatory domain detected:\n"); 2351f5fe3247SLuis R. Rodriguez print_regdomain_info(rd); 23523f2355cbSLuis R. Rodriguez return -EINVAL; 2353b2e1b302SLuis R. Rodriguez } 2354b2e1b302SLuis R. Rodriguez 235501992406SLuis R. Rodriguez request_wiphy = wiphy_idx_to_wiphy(country_ie_request->wiphy_idx); 2356f5fe3247SLuis R. Rodriguez if (!request_wiphy) { 2357f5fe3247SLuis R. Rodriguez schedule_delayed_work(®_timeout, 0); 2358f5fe3247SLuis R. Rodriguez return -ENODEV; 2359f5fe3247SLuis R. Rodriguez } 2360f5fe3247SLuis R. Rodriguez 236101992406SLuis R. Rodriguez if (country_ie_request->intersect) 2362f5fe3247SLuis R. Rodriguez return -EINVAL; 2363f5fe3247SLuis R. Rodriguez 2364f5fe3247SLuis R. Rodriguez reset_regdomains(false, rd); 2365f5fe3247SLuis R. Rodriguez return 0; 2366f5fe3247SLuis R. Rodriguez } 2367b2e1b302SLuis R. Rodriguez 2368fb1fc7adSLuis R. Rodriguez /* 2369fb1fc7adSLuis R. Rodriguez * Use this call to set the current regulatory domain. Conflicts with 2370b2e1b302SLuis R. Rodriguez * multiple drivers can be ironed out later. Caller must've already 2371458f4f9eSJohannes Berg * kmalloc'd the rd structure. 2372fb1fc7adSLuis R. Rodriguez */ 2373a3d2eaf0SJohannes Berg int set_regdom(const struct ieee80211_regdomain *rd) 2374b2e1b302SLuis R. Rodriguez { 2375c492db37SJohannes Berg struct regulatory_request *lr; 2376b2e1b302SLuis R. Rodriguez int r; 2377b2e1b302SLuis R. Rodriguez 23783b9e5acaSLuis R. Rodriguez if (!reg_is_valid_request(rd->alpha2)) { 23793b9e5acaSLuis R. Rodriguez kfree(rd); 23803b9e5acaSLuis R. Rodriguez return -EINVAL; 23813b9e5acaSLuis R. Rodriguez } 23823b9e5acaSLuis R. Rodriguez 2383c492db37SJohannes Berg lr = get_last_request(); 2384abc7381bSLuis R. Rodriguez 2385b2e1b302SLuis R. Rodriguez /* Note that this doesn't update the wiphys, this is done below */ 23863b9e5acaSLuis R. Rodriguez switch (lr->initiator) { 23873b9e5acaSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_CORE: 23883b9e5acaSLuis R. Rodriguez r = reg_set_rd_core(rd); 23893b9e5acaSLuis R. Rodriguez break; 23903b9e5acaSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_USER: 239184721d44SLuis R. Rodriguez r = reg_set_rd_user(rd, lr); 239284721d44SLuis R. Rodriguez break; 23933b9e5acaSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_DRIVER: 2394f5fe3247SLuis R. Rodriguez r = reg_set_rd_driver(rd, lr); 2395f5fe3247SLuis R. Rodriguez break; 23963b9e5acaSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_COUNTRY_IE: 239701992406SLuis R. Rodriguez r = reg_set_rd_country_ie(rd, lr); 23983b9e5acaSLuis R. Rodriguez break; 23993b9e5acaSLuis R. Rodriguez default: 24003b9e5acaSLuis R. Rodriguez WARN(1, "invalid initiator %d\n", lr->initiator); 24013b9e5acaSLuis R. Rodriguez return -EINVAL; 24023b9e5acaSLuis R. Rodriguez } 24033b9e5acaSLuis R. Rodriguez 2404d2372b31SJohannes Berg if (r) { 240595908535SKalle Valo if (r == -EALREADY) 240695908535SKalle Valo reg_set_request_processed(); 240795908535SKalle Valo 2408d2372b31SJohannes Berg kfree(rd); 240938fd2143SJohannes Berg return r; 2410d2372b31SJohannes Berg } 2411b2e1b302SLuis R. Rodriguez 2412b2e1b302SLuis R. Rodriguez /* This would make this whole thing pointless */ 241338fd2143SJohannes Berg if (WARN_ON(!lr->intersect && rd != get_cfg80211_regdom())) 241438fd2143SJohannes Berg return -EINVAL; 2415b2e1b302SLuis R. Rodriguez 2416b2e1b302SLuis R. Rodriguez /* update all wiphys now with the new established regulatory domain */ 2417c492db37SJohannes Berg update_all_wiphy_regulatory(lr->initiator); 2418b2e1b302SLuis R. Rodriguez 2419458f4f9eSJohannes Berg print_regdomain(get_cfg80211_regdom()); 2420b2e1b302SLuis R. Rodriguez 2421c492db37SJohannes Berg nl80211_send_reg_change_event(lr); 242273d54c9eSLuis R. Rodriguez 2423b2e253cfSLuis R. Rodriguez reg_set_request_processed(); 2424b2e253cfSLuis R. Rodriguez 242538fd2143SJohannes Berg return 0; 2426b2e1b302SLuis R. Rodriguez } 2427b2e1b302SLuis R. Rodriguez 24284d9d88d1SScott James Remnant int reg_device_uevent(struct device *dev, struct kobj_uevent_env *env) 24294d9d88d1SScott James Remnant { 24304a484cffSJohannes Berg struct regulatory_request *lr; 24314a484cffSJohannes Berg u8 alpha2[2]; 24324a484cffSJohannes Berg bool add = false; 24334d9d88d1SScott James Remnant 24344a484cffSJohannes Berg rcu_read_lock(); 24354a484cffSJohannes Berg lr = get_last_request(); 2436c492db37SJohannes Berg if (lr && !lr->processed) { 24374a484cffSJohannes Berg memcpy(alpha2, lr->alpha2, 2); 24384a484cffSJohannes Berg add = true; 24394d9d88d1SScott James Remnant } 24404a484cffSJohannes Berg rcu_read_unlock(); 24414d9d88d1SScott James Remnant 24424a484cffSJohannes Berg if (add) 24434a484cffSJohannes Berg return add_uevent_var(env, "COUNTRY=%c%c", 24444a484cffSJohannes Berg alpha2[0], alpha2[1]); 24454d9d88d1SScott James Remnant return 0; 24464d9d88d1SScott James Remnant } 24474d9d88d1SScott James Remnant 244857b5ce07SLuis R. Rodriguez void wiphy_regulatory_register(struct wiphy *wiphy) 244957b5ce07SLuis R. Rodriguez { 245023df0b73SArik Nemtsov struct regulatory_request *lr; 245123df0b73SArik Nemtsov 245257b5ce07SLuis R. Rodriguez if (!reg_dev_ignore_cell_hint(wiphy)) 245357b5ce07SLuis R. Rodriguez reg_num_devs_support_basehint++; 245457b5ce07SLuis R. Rodriguez 245523df0b73SArik Nemtsov lr = get_last_request(); 245623df0b73SArik Nemtsov wiphy_update_regulatory(wiphy, lr->initiator); 245757b5ce07SLuis R. Rodriguez } 245857b5ce07SLuis R. Rodriguez 2459bfead080SLuis R. Rodriguez void wiphy_regulatory_deregister(struct wiphy *wiphy) 24603f2355cbSLuis R. Rodriguez { 24610ad8acafSLuis R. Rodriguez struct wiphy *request_wiphy = NULL; 2462c492db37SJohannes Berg struct regulatory_request *lr; 2463761cf7ecSLuis R. Rodriguez 2464c492db37SJohannes Berg lr = get_last_request(); 2465abc7381bSLuis R. Rodriguez 246657b5ce07SLuis R. Rodriguez if (!reg_dev_ignore_cell_hint(wiphy)) 246757b5ce07SLuis R. Rodriguez reg_num_devs_support_basehint--; 246857b5ce07SLuis R. Rodriguez 2469458f4f9eSJohannes Berg rcu_free_regdom(get_wiphy_regdom(wiphy)); 2470458f4f9eSJohannes Berg rcu_assign_pointer(wiphy->regd, NULL); 24710ef9ccddSChris Wright 2472c492db37SJohannes Berg if (lr) 2473c492db37SJohannes Berg request_wiphy = wiphy_idx_to_wiphy(lr->wiphy_idx); 2474806a9e39SLuis R. Rodriguez 24750ef9ccddSChris Wright if (!request_wiphy || request_wiphy != wiphy) 247638fd2143SJohannes Berg return; 24770ef9ccddSChris Wright 2478c492db37SJohannes Berg lr->wiphy_idx = WIPHY_IDX_INVALID; 2479c492db37SJohannes Berg lr->country_ie_env = ENVIRON_ANY; 24803f2355cbSLuis R. Rodriguez } 24813f2355cbSLuis R. Rodriguez 2482a90c7a31SLuis R. Rodriguez static void reg_timeout_work(struct work_struct *work) 2483a90c7a31SLuis R. Rodriguez { 24841a919318SJohannes Berg REG_DBG_PRINT("Timeout while waiting for CRDA to reply, restoring regulatory settings\n"); 2485f77b86d7SJohannes Berg rtnl_lock(); 2486a90c7a31SLuis R. Rodriguez restore_regulatory_settings(true); 2487f77b86d7SJohannes Berg rtnl_unlock(); 2488a90c7a31SLuis R. Rodriguez } 2489a90c7a31SLuis R. Rodriguez 24902fcc9f73SUwe Kleine-König int __init regulatory_init(void) 2491b2e1b302SLuis R. Rodriguez { 2492bcf4f99bSLuis R. Rodriguez int err = 0; 2493734366deSJohannes Berg 2494b2e1b302SLuis R. Rodriguez reg_pdev = platform_device_register_simple("regulatory", 0, NULL, 0); 2495b2e1b302SLuis R. Rodriguez if (IS_ERR(reg_pdev)) 2496b2e1b302SLuis R. Rodriguez return PTR_ERR(reg_pdev); 2497734366deSJohannes Berg 24984d9d88d1SScott James Remnant reg_pdev->dev.type = ®_device_type; 24994d9d88d1SScott James Remnant 2500fe33eb39SLuis R. Rodriguez spin_lock_init(®_requests_lock); 2501e38f8a7aSLuis R. Rodriguez spin_lock_init(®_pending_beacons_lock); 2502fe33eb39SLuis R. Rodriguez 250380007efeSLuis R. Rodriguez reg_regdb_size_check(); 250480007efeSLuis R. Rodriguez 2505458f4f9eSJohannes Berg rcu_assign_pointer(cfg80211_regdomain, cfg80211_world_regdom); 2506734366deSJohannes Berg 250709d989d1SLuis R. Rodriguez user_alpha2[0] = '9'; 250809d989d1SLuis R. Rodriguez user_alpha2[1] = '7'; 250909d989d1SLuis R. Rodriguez 2510ae9e4b0dSLuis R. Rodriguez /* We always try to get an update for the static regdomain */ 2511458f4f9eSJohannes Berg err = regulatory_hint_core(cfg80211_world_regdom->alpha2); 2512bcf4f99bSLuis R. Rodriguez if (err) { 2513bcf4f99bSLuis R. Rodriguez if (err == -ENOMEM) 2514bcf4f99bSLuis R. Rodriguez return err; 2515bcf4f99bSLuis R. Rodriguez /* 2516bcf4f99bSLuis R. Rodriguez * N.B. kobject_uevent_env() can fail mainly for when we're out 2517bcf4f99bSLuis R. Rodriguez * memory which is handled and propagated appropriately above 2518bcf4f99bSLuis R. Rodriguez * but it can also fail during a netlink_broadcast() or during 2519bcf4f99bSLuis R. Rodriguez * early boot for call_usermodehelper(). For now treat these 2520bcf4f99bSLuis R. Rodriguez * errors as non-fatal. 2521bcf4f99bSLuis R. Rodriguez */ 2522e9c0268fSJoe Perches pr_err("kobject_uevent_env() was unable to call CRDA during init\n"); 2523bcf4f99bSLuis R. Rodriguez } 2524734366deSJohannes Berg 2525ae9e4b0dSLuis R. Rodriguez /* 2526ae9e4b0dSLuis R. Rodriguez * Finally, if the user set the module parameter treat it 2527ae9e4b0dSLuis R. Rodriguez * as a user hint. 2528ae9e4b0dSLuis R. Rodriguez */ 2529ae9e4b0dSLuis R. Rodriguez if (!is_world_regdom(ieee80211_regdom)) 253057b5ce07SLuis R. Rodriguez regulatory_hint_user(ieee80211_regdom, 253157b5ce07SLuis R. Rodriguez NL80211_USER_REG_HINT_USER); 2532ae9e4b0dSLuis R. Rodriguez 2533b2e1b302SLuis R. Rodriguez return 0; 2534b2e1b302SLuis R. Rodriguez } 2535b2e1b302SLuis R. Rodriguez 25361a919318SJohannes Berg void regulatory_exit(void) 2537b2e1b302SLuis R. Rodriguez { 2538fe33eb39SLuis R. Rodriguez struct regulatory_request *reg_request, *tmp; 2539e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon, *btmp; 2540fe33eb39SLuis R. Rodriguez 2541fe33eb39SLuis R. Rodriguez cancel_work_sync(®_work); 2542a90c7a31SLuis R. Rodriguez cancel_delayed_work_sync(®_timeout); 2543fe33eb39SLuis R. Rodriguez 25449027b149SJohannes Berg /* Lock to suppress warnings */ 254538fd2143SJohannes Berg rtnl_lock(); 2546379b82f4SJohannes Berg reset_regdomains(true, NULL); 254738fd2143SJohannes Berg rtnl_unlock(); 2548734366deSJohannes Berg 254958ebacc6SLuis R. Rodriguez dev_set_uevent_suppress(®_pdev->dev, true); 2550f6037d09SJohannes Berg 2551b2e1b302SLuis R. Rodriguez platform_device_unregister(reg_pdev); 2552734366deSJohannes Berg 2553fea9bcedSJohannes Berg list_for_each_entry_safe(reg_beacon, btmp, ®_pending_beacons, list) { 2554e38f8a7aSLuis R. Rodriguez list_del(®_beacon->list); 2555e38f8a7aSLuis R. Rodriguez kfree(reg_beacon); 2556e38f8a7aSLuis R. Rodriguez } 2557e38f8a7aSLuis R. Rodriguez 2558fea9bcedSJohannes Berg list_for_each_entry_safe(reg_beacon, btmp, ®_beacon_list, list) { 2559e38f8a7aSLuis R. Rodriguez list_del(®_beacon->list); 2560e38f8a7aSLuis R. Rodriguez kfree(reg_beacon); 2561e38f8a7aSLuis R. Rodriguez } 2562e38f8a7aSLuis R. Rodriguez 2563fea9bcedSJohannes Berg list_for_each_entry_safe(reg_request, tmp, ®_requests_list, list) { 2564fe33eb39SLuis R. Rodriguez list_del(®_request->list); 2565fe33eb39SLuis R. Rodriguez kfree(reg_request); 2566fe33eb39SLuis R. Rodriguez } 2567fe33eb39SLuis R. Rodriguez } 2568