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 94fb1fc7adSLuis R. Rodriguez /* 95fb1fc7adSLuis R. Rodriguez * Central wireless core regulatory domains, we only need two, 96734366deSJohannes Berg * the current one and a world regulatory domain in case we have no 97e8da2bb4SJohannes Berg * information to give us an alpha2. 9838fd2143SJohannes Berg * (protected by RTNL, can be read under RCU) 99fb1fc7adSLuis R. Rodriguez */ 100458f4f9eSJohannes Berg const struct ieee80211_regdomain __rcu *cfg80211_regdomain; 101734366deSJohannes Berg 102fb1fc7adSLuis R. Rodriguez /* 10357b5ce07SLuis R. Rodriguez * Number of devices that registered to the core 10457b5ce07SLuis R. Rodriguez * that support cellular base station regulatory hints 10538fd2143SJohannes Berg * (protected by RTNL) 10657b5ce07SLuis R. Rodriguez */ 10757b5ce07SLuis R. Rodriguez static int reg_num_devs_support_basehint; 10857b5ce07SLuis R. Rodriguez 109458f4f9eSJohannes Berg static const struct ieee80211_regdomain *get_cfg80211_regdom(void) 110458f4f9eSJohannes Berg { 11138fd2143SJohannes Berg return rtnl_dereference(cfg80211_regdomain); 112458f4f9eSJohannes Berg } 113458f4f9eSJohannes Berg 114458f4f9eSJohannes Berg static const struct ieee80211_regdomain *get_wiphy_regdom(struct wiphy *wiphy) 115458f4f9eSJohannes Berg { 11638fd2143SJohannes Berg return rtnl_dereference(wiphy->regd); 117458f4f9eSJohannes Berg } 118458f4f9eSJohannes Berg 1193ef121b5SLuis R. Rodriguez static const char *reg_dfs_region_str(enum nl80211_dfs_regions dfs_region) 1203ef121b5SLuis R. Rodriguez { 1213ef121b5SLuis R. Rodriguez switch (dfs_region) { 1223ef121b5SLuis R. Rodriguez case NL80211_DFS_UNSET: 1233ef121b5SLuis R. Rodriguez return "unset"; 1243ef121b5SLuis R. Rodriguez case NL80211_DFS_FCC: 1253ef121b5SLuis R. Rodriguez return "FCC"; 1263ef121b5SLuis R. Rodriguez case NL80211_DFS_ETSI: 1273ef121b5SLuis R. Rodriguez return "ETSI"; 1283ef121b5SLuis R. Rodriguez case NL80211_DFS_JP: 1293ef121b5SLuis R. Rodriguez return "JP"; 1303ef121b5SLuis R. Rodriguez } 1313ef121b5SLuis R. Rodriguez return "Unknown"; 1323ef121b5SLuis R. Rodriguez } 1333ef121b5SLuis R. Rodriguez 1346c474799SLuis R. Rodriguez enum nl80211_dfs_regions reg_get_dfs_region(struct wiphy *wiphy) 1356c474799SLuis R. Rodriguez { 1366c474799SLuis R. Rodriguez const struct ieee80211_regdomain *regd = NULL; 1376c474799SLuis R. Rodriguez const struct ieee80211_regdomain *wiphy_regd = NULL; 1386c474799SLuis R. Rodriguez 1396c474799SLuis R. Rodriguez regd = get_cfg80211_regdom(); 1406c474799SLuis R. Rodriguez if (!wiphy) 1416c474799SLuis R. Rodriguez goto out; 1426c474799SLuis R. Rodriguez 1436c474799SLuis R. Rodriguez wiphy_regd = get_wiphy_regdom(wiphy); 1446c474799SLuis R. Rodriguez if (!wiphy_regd) 1456c474799SLuis R. Rodriguez goto out; 1466c474799SLuis R. Rodriguez 1476c474799SLuis R. Rodriguez if (wiphy_regd->dfs_region == regd->dfs_region) 1486c474799SLuis R. Rodriguez goto out; 1496c474799SLuis R. Rodriguez 1506c474799SLuis R. Rodriguez REG_DBG_PRINT("%s: device specific dfs_region " 1516c474799SLuis R. Rodriguez "(%s) disagrees with cfg80211's " 1526c474799SLuis R. Rodriguez "central dfs_region (%s)\n", 1536c474799SLuis R. Rodriguez dev_name(&wiphy->dev), 1546c474799SLuis R. Rodriguez reg_dfs_region_str(wiphy_regd->dfs_region), 1556c474799SLuis R. Rodriguez reg_dfs_region_str(regd->dfs_region)); 1566c474799SLuis R. Rodriguez 1576c474799SLuis R. Rodriguez out: 1586c474799SLuis R. Rodriguez return regd->dfs_region; 1596c474799SLuis R. Rodriguez } 1606c474799SLuis R. Rodriguez 161458f4f9eSJohannes Berg static void rcu_free_regdom(const struct ieee80211_regdomain *r) 162458f4f9eSJohannes Berg { 163458f4f9eSJohannes Berg if (!r) 164458f4f9eSJohannes Berg return; 165458f4f9eSJohannes Berg kfree_rcu((struct ieee80211_regdomain *)r, rcu_head); 166458f4f9eSJohannes Berg } 167458f4f9eSJohannes Berg 168c492db37SJohannes Berg static struct regulatory_request *get_last_request(void) 169c492db37SJohannes Berg { 17038fd2143SJohannes Berg return rcu_dereference_rtnl(last_request); 171c492db37SJohannes Berg } 172c492db37SJohannes Berg 173e38f8a7aSLuis R. Rodriguez /* Used to queue up regulatory hints */ 174fe33eb39SLuis R. Rodriguez static LIST_HEAD(reg_requests_list); 175fe33eb39SLuis R. Rodriguez static spinlock_t reg_requests_lock; 176fe33eb39SLuis R. Rodriguez 177e38f8a7aSLuis R. Rodriguez /* Used to queue up beacon hints for review */ 178e38f8a7aSLuis R. Rodriguez static LIST_HEAD(reg_pending_beacons); 179e38f8a7aSLuis R. Rodriguez static spinlock_t reg_pending_beacons_lock; 180e38f8a7aSLuis R. Rodriguez 181e38f8a7aSLuis R. Rodriguez /* Used to keep track of processed beacon hints */ 182e38f8a7aSLuis R. Rodriguez static LIST_HEAD(reg_beacon_list); 183e38f8a7aSLuis R. Rodriguez 184e38f8a7aSLuis R. Rodriguez struct reg_beacon { 185e38f8a7aSLuis R. Rodriguez struct list_head list; 186e38f8a7aSLuis R. Rodriguez struct ieee80211_channel chan; 187e38f8a7aSLuis R. Rodriguez }; 188e38f8a7aSLuis R. Rodriguez 189f333a7a2SLuis R. Rodriguez static void reg_todo(struct work_struct *work); 190f333a7a2SLuis R. Rodriguez static DECLARE_WORK(reg_work, reg_todo); 191f333a7a2SLuis R. Rodriguez 192a90c7a31SLuis R. Rodriguez static void reg_timeout_work(struct work_struct *work); 193a90c7a31SLuis R. Rodriguez static DECLARE_DELAYED_WORK(reg_timeout, reg_timeout_work); 194a90c7a31SLuis R. Rodriguez 195734366deSJohannes Berg /* We keep a static world regulatory domain in case of the absence of CRDA */ 196734366deSJohannes Berg static const struct ieee80211_regdomain world_regdom = { 19790cdc6dfSVladimir Kondratiev .n_reg_rules = 6, 198734366deSJohannes Berg .alpha2 = "00", 199734366deSJohannes Berg .reg_rules = { 20068798a62SLuis R. Rodriguez /* IEEE 802.11b/g, channels 1..11 */ 20168798a62SLuis R. Rodriguez REG_RULE(2412-10, 2462+10, 40, 6, 20, 0), 20243c771a1SJohannes Berg /* IEEE 802.11b/g, channels 12..13. */ 20343c771a1SJohannes Berg REG_RULE(2467-10, 2472+10, 40, 6, 20, 2048fe02e16SLuis R. Rodriguez NL80211_RRF_NO_IR), 205611b6a82SLuis R. Rodriguez /* IEEE 802.11 channel 14 - Only JP enables 206611b6a82SLuis R. Rodriguez * this and for 802.11b only */ 207611b6a82SLuis R. Rodriguez REG_RULE(2484-10, 2484+10, 20, 6, 20, 2088fe02e16SLuis R. Rodriguez NL80211_RRF_NO_IR | 209611b6a82SLuis R. Rodriguez NL80211_RRF_NO_OFDM), 2103fc71f77SLuis R. Rodriguez /* IEEE 802.11a, channel 36..48 */ 211131a19bcSJohannes Berg REG_RULE(5180-10, 5240+10, 160, 6, 20, 2128fe02e16SLuis R. Rodriguez NL80211_RRF_NO_IR), 2133fc71f77SLuis R. Rodriguez 214131a19bcSJohannes Berg /* IEEE 802.11a, channel 52..64 - DFS required */ 215131a19bcSJohannes Berg REG_RULE(5260-10, 5320+10, 160, 6, 20, 2168fe02e16SLuis R. Rodriguez NL80211_RRF_NO_IR | 217131a19bcSJohannes Berg NL80211_RRF_DFS), 218131a19bcSJohannes Berg 219131a19bcSJohannes Berg /* IEEE 802.11a, channel 100..144 - DFS required */ 220131a19bcSJohannes Berg REG_RULE(5500-10, 5720+10, 160, 6, 20, 2218fe02e16SLuis R. Rodriguez NL80211_RRF_NO_IR | 222131a19bcSJohannes Berg NL80211_RRF_DFS), 2233fc71f77SLuis R. Rodriguez 2243fc71f77SLuis R. Rodriguez /* IEEE 802.11a, channel 149..165 */ 2258ab9d85cSJohannes Berg REG_RULE(5745-10, 5825+10, 80, 6, 20, 2268fe02e16SLuis R. Rodriguez NL80211_RRF_NO_IR), 22790cdc6dfSVladimir Kondratiev 22890cdc6dfSVladimir Kondratiev /* IEEE 802.11ad (60gHz), channels 1..3 */ 22990cdc6dfSVladimir Kondratiev REG_RULE(56160+2160*1-1080, 56160+2160*3+1080, 2160, 0, 0, 0), 230734366deSJohannes Berg } 231734366deSJohannes Berg }; 232734366deSJohannes Berg 23338fd2143SJohannes Berg /* protected by RTNL */ 234a3d2eaf0SJohannes Berg static const struct ieee80211_regdomain *cfg80211_world_regdom = 235a3d2eaf0SJohannes Berg &world_regdom; 236734366deSJohannes Berg 2376ee7d330SLuis R. Rodriguez static char *ieee80211_regdom = "00"; 23809d989d1SLuis R. Rodriguez static char user_alpha2[2]; 2396ee7d330SLuis R. Rodriguez 240734366deSJohannes Berg module_param(ieee80211_regdom, charp, 0444); 241734366deSJohannes Berg MODULE_PARM_DESC(ieee80211_regdom, "IEEE 802.11 regulatory domain code"); 242734366deSJohannes Berg 243255e25b0SLuis R. Rodriguez static void reg_free_request(struct regulatory_request *lr) 2445ad6ef5eSLuis R. Rodriguez { 2455ad6ef5eSLuis R. Rodriguez if (lr != &core_request_world && lr) 2465ad6ef5eSLuis R. Rodriguez kfree_rcu(lr, rcu_head); 2475ad6ef5eSLuis R. Rodriguez } 2485ad6ef5eSLuis R. Rodriguez 24905f1a3eaSLuis R. Rodriguez static void reg_update_last_request(struct regulatory_request *request) 25005f1a3eaSLuis R. Rodriguez { 251255e25b0SLuis R. Rodriguez struct regulatory_request *lr; 252255e25b0SLuis R. Rodriguez 253255e25b0SLuis R. Rodriguez lr = get_last_request(); 254255e25b0SLuis R. Rodriguez if (lr == request) 255255e25b0SLuis R. Rodriguez return; 256255e25b0SLuis R. Rodriguez 257255e25b0SLuis R. Rodriguez reg_free_request(lr); 25805f1a3eaSLuis R. Rodriguez rcu_assign_pointer(last_request, request); 25905f1a3eaSLuis R. Rodriguez } 26005f1a3eaSLuis R. Rodriguez 261379b82f4SJohannes Berg static void reset_regdomains(bool full_reset, 262379b82f4SJohannes Berg const struct ieee80211_regdomain *new_regdom) 263734366deSJohannes Berg { 264458f4f9eSJohannes Berg const struct ieee80211_regdomain *r; 265458f4f9eSJohannes Berg 26638fd2143SJohannes Berg ASSERT_RTNL(); 267e8da2bb4SJohannes Berg 268458f4f9eSJohannes Berg r = get_cfg80211_regdom(); 269458f4f9eSJohannes Berg 270942b25cfSJohannes Berg /* avoid freeing static information or freeing something twice */ 271458f4f9eSJohannes Berg if (r == cfg80211_world_regdom) 272458f4f9eSJohannes Berg r = NULL; 273942b25cfSJohannes Berg if (cfg80211_world_regdom == &world_regdom) 274942b25cfSJohannes Berg cfg80211_world_regdom = NULL; 275458f4f9eSJohannes Berg if (r == &world_regdom) 276458f4f9eSJohannes Berg r = NULL; 277942b25cfSJohannes Berg 278458f4f9eSJohannes Berg rcu_free_regdom(r); 279458f4f9eSJohannes Berg rcu_free_regdom(cfg80211_world_regdom); 280734366deSJohannes Berg 281a3d2eaf0SJohannes Berg cfg80211_world_regdom = &world_regdom; 282458f4f9eSJohannes Berg rcu_assign_pointer(cfg80211_regdomain, new_regdom); 283a042994dSLuis R. Rodriguez 284a042994dSLuis R. Rodriguez if (!full_reset) 285a042994dSLuis R. Rodriguez return; 286a042994dSLuis R. Rodriguez 28705f1a3eaSLuis R. Rodriguez reg_update_last_request(&core_request_world); 288734366deSJohannes Berg } 289734366deSJohannes Berg 290fb1fc7adSLuis R. Rodriguez /* 291fb1fc7adSLuis R. Rodriguez * Dynamic world regulatory domain requested by the wireless 292fb1fc7adSLuis R. Rodriguez * core upon initialization 293fb1fc7adSLuis R. Rodriguez */ 294a3d2eaf0SJohannes Berg static void update_world_regdomain(const struct ieee80211_regdomain *rd) 295734366deSJohannes Berg { 296c492db37SJohannes Berg struct regulatory_request *lr; 297734366deSJohannes Berg 298c492db37SJohannes Berg lr = get_last_request(); 299c492db37SJohannes Berg 300c492db37SJohannes Berg WARN_ON(!lr); 301e8da2bb4SJohannes Berg 302379b82f4SJohannes Berg reset_regdomains(false, rd); 303734366deSJohannes Berg 304734366deSJohannes Berg cfg80211_world_regdom = rd; 305734366deSJohannes Berg } 306734366deSJohannes Berg 307a3d2eaf0SJohannes Berg bool is_world_regdom(const char *alpha2) 308b2e1b302SLuis R. Rodriguez { 309b2e1b302SLuis R. Rodriguez if (!alpha2) 310b2e1b302SLuis R. Rodriguez return false; 3111a919318SJohannes Berg return alpha2[0] == '0' && alpha2[1] == '0'; 312b2e1b302SLuis R. Rodriguez } 313b2e1b302SLuis R. Rodriguez 314a3d2eaf0SJohannes Berg static bool is_alpha2_set(const char *alpha2) 315b2e1b302SLuis R. Rodriguez { 316b2e1b302SLuis R. Rodriguez if (!alpha2) 317b2e1b302SLuis R. Rodriguez return false; 3181a919318SJohannes Berg return alpha2[0] && alpha2[1]; 319b2e1b302SLuis R. Rodriguez } 320b2e1b302SLuis R. Rodriguez 321a3d2eaf0SJohannes Berg static bool is_unknown_alpha2(const char *alpha2) 322b2e1b302SLuis R. Rodriguez { 323b2e1b302SLuis R. Rodriguez if (!alpha2) 324b2e1b302SLuis R. Rodriguez return false; 325fb1fc7adSLuis R. Rodriguez /* 326fb1fc7adSLuis R. Rodriguez * Special case where regulatory domain was built by driver 327fb1fc7adSLuis R. Rodriguez * but a specific alpha2 cannot be determined 328fb1fc7adSLuis R. Rodriguez */ 3291a919318SJohannes Berg return alpha2[0] == '9' && alpha2[1] == '9'; 330b2e1b302SLuis R. Rodriguez } 331b2e1b302SLuis R. Rodriguez 3323f2355cbSLuis R. Rodriguez static bool is_intersected_alpha2(const char *alpha2) 3333f2355cbSLuis R. Rodriguez { 3343f2355cbSLuis R. Rodriguez if (!alpha2) 3353f2355cbSLuis R. Rodriguez return false; 336fb1fc7adSLuis R. Rodriguez /* 337fb1fc7adSLuis R. Rodriguez * Special case where regulatory domain is the 3383f2355cbSLuis R. Rodriguez * result of an intersection between two regulatory domain 339fb1fc7adSLuis R. Rodriguez * structures 340fb1fc7adSLuis R. Rodriguez */ 3411a919318SJohannes Berg return alpha2[0] == '9' && alpha2[1] == '8'; 3423f2355cbSLuis R. Rodriguez } 3433f2355cbSLuis R. Rodriguez 344a3d2eaf0SJohannes Berg static bool is_an_alpha2(const char *alpha2) 345b2e1b302SLuis R. Rodriguez { 346b2e1b302SLuis R. Rodriguez if (!alpha2) 347b2e1b302SLuis R. Rodriguez return false; 3481a919318SJohannes Berg return isalpha(alpha2[0]) && isalpha(alpha2[1]); 349b2e1b302SLuis R. Rodriguez } 350b2e1b302SLuis R. Rodriguez 351a3d2eaf0SJohannes Berg static bool alpha2_equal(const char *alpha2_x, const char *alpha2_y) 352b2e1b302SLuis R. Rodriguez { 353b2e1b302SLuis R. Rodriguez if (!alpha2_x || !alpha2_y) 354b2e1b302SLuis R. Rodriguez return false; 3551a919318SJohannes Berg return alpha2_x[0] == alpha2_y[0] && alpha2_x[1] == alpha2_y[1]; 356b2e1b302SLuis R. Rodriguez } 357b2e1b302SLuis R. Rodriguez 35869b1572bSLuis R. Rodriguez static bool regdom_changes(const char *alpha2) 359b2e1b302SLuis R. Rodriguez { 360458f4f9eSJohannes Berg const struct ieee80211_regdomain *r = get_cfg80211_regdom(); 361761cf7ecSLuis R. Rodriguez 362458f4f9eSJohannes Berg if (!r) 363b2e1b302SLuis R. Rodriguez return true; 364458f4f9eSJohannes Berg return !alpha2_equal(r->alpha2, alpha2); 365b2e1b302SLuis R. Rodriguez } 366b2e1b302SLuis R. Rodriguez 36709d989d1SLuis R. Rodriguez /* 36809d989d1SLuis R. Rodriguez * The NL80211_REGDOM_SET_BY_USER regdom alpha2 is cached, this lets 36909d989d1SLuis R. Rodriguez * you know if a valid regulatory hint with NL80211_REGDOM_SET_BY_USER 37009d989d1SLuis R. Rodriguez * has ever been issued. 37109d989d1SLuis R. Rodriguez */ 37209d989d1SLuis R. Rodriguez static bool is_user_regdom_saved(void) 37309d989d1SLuis R. Rodriguez { 37409d989d1SLuis R. Rodriguez if (user_alpha2[0] == '9' && user_alpha2[1] == '7') 37509d989d1SLuis R. Rodriguez return false; 37609d989d1SLuis R. Rodriguez 37709d989d1SLuis R. Rodriguez /* This would indicate a mistake on the design */ 3781a919318SJohannes Berg if (WARN(!is_world_regdom(user_alpha2) && !is_an_alpha2(user_alpha2), 37909d989d1SLuis R. Rodriguez "Unexpected user alpha2: %c%c\n", 3801a919318SJohannes Berg user_alpha2[0], user_alpha2[1])) 38109d989d1SLuis R. Rodriguez return false; 38209d989d1SLuis R. Rodriguez 38309d989d1SLuis R. Rodriguez return true; 38409d989d1SLuis R. Rodriguez } 38509d989d1SLuis R. Rodriguez 386e9763c3cSJohannes Berg static const struct ieee80211_regdomain * 387e9763c3cSJohannes Berg reg_copy_regd(const struct ieee80211_regdomain *src_regd) 3883b377ea9SJohn W. Linville { 3893b377ea9SJohn W. Linville struct ieee80211_regdomain *regd; 390e9763c3cSJohannes Berg int size_of_regd; 3913b377ea9SJohn W. Linville unsigned int i; 3923b377ea9SJohn W. Linville 39382f20856SJohannes Berg size_of_regd = 39482f20856SJohannes Berg sizeof(struct ieee80211_regdomain) + 39582f20856SJohannes Berg src_regd->n_reg_rules * sizeof(struct ieee80211_reg_rule); 3963b377ea9SJohn W. Linville 3973b377ea9SJohn W. Linville regd = kzalloc(size_of_regd, GFP_KERNEL); 3983b377ea9SJohn W. Linville if (!regd) 399e9763c3cSJohannes Berg return ERR_PTR(-ENOMEM); 4003b377ea9SJohn W. Linville 4013b377ea9SJohn W. Linville memcpy(regd, src_regd, sizeof(struct ieee80211_regdomain)); 4023b377ea9SJohn W. Linville 4033b377ea9SJohn W. Linville for (i = 0; i < src_regd->n_reg_rules; i++) 4043b377ea9SJohn W. Linville memcpy(®d->reg_rules[i], &src_regd->reg_rules[i], 4053b377ea9SJohn W. Linville sizeof(struct ieee80211_reg_rule)); 4063b377ea9SJohn W. Linville 407e9763c3cSJohannes Berg return regd; 4083b377ea9SJohn W. Linville } 4093b377ea9SJohn W. Linville 4103b377ea9SJohn W. Linville #ifdef CONFIG_CFG80211_INTERNAL_REGDB 4113b377ea9SJohn W. Linville struct reg_regdb_search_request { 4123b377ea9SJohn W. Linville char alpha2[2]; 4133b377ea9SJohn W. Linville struct list_head list; 4143b377ea9SJohn W. Linville }; 4153b377ea9SJohn W. Linville 4163b377ea9SJohn W. Linville static LIST_HEAD(reg_regdb_search_list); 417368d06f5SJohn W. Linville static DEFINE_MUTEX(reg_regdb_search_mutex); 4183b377ea9SJohn W. Linville 4193b377ea9SJohn W. Linville static void reg_regdb_search(struct work_struct *work) 4203b377ea9SJohn W. Linville { 4213b377ea9SJohn W. Linville struct reg_regdb_search_request *request; 422e9763c3cSJohannes Berg const struct ieee80211_regdomain *curdom, *regdom = NULL; 423e9763c3cSJohannes Berg int i; 424a85d0d7fSLuis R. Rodriguez 4255fe231e8SJohannes Berg rtnl_lock(); 4263b377ea9SJohn W. Linville 427368d06f5SJohn W. Linville mutex_lock(®_regdb_search_mutex); 4283b377ea9SJohn W. Linville while (!list_empty(®_regdb_search_list)) { 4293b377ea9SJohn W. Linville request = list_first_entry(®_regdb_search_list, 4303b377ea9SJohn W. Linville struct reg_regdb_search_request, 4313b377ea9SJohn W. Linville list); 4323b377ea9SJohn W. Linville list_del(&request->list); 4333b377ea9SJohn W. Linville 4343b377ea9SJohn W. Linville for (i = 0; i < reg_regdb_size; i++) { 4353b377ea9SJohn W. Linville curdom = reg_regdb[i]; 4363b377ea9SJohn W. Linville 4371a919318SJohannes Berg if (alpha2_equal(request->alpha2, curdom->alpha2)) { 438e9763c3cSJohannes Berg regdom = reg_copy_regd(curdom); 4393b377ea9SJohn W. Linville break; 4403b377ea9SJohn W. Linville } 4413b377ea9SJohn W. Linville } 4423b377ea9SJohn W. Linville 4433b377ea9SJohn W. Linville kfree(request); 4443b377ea9SJohn W. Linville } 445368d06f5SJohn W. Linville mutex_unlock(®_regdb_search_mutex); 446a85d0d7fSLuis R. Rodriguez 447e9763c3cSJohannes Berg if (!IS_ERR_OR_NULL(regdom)) 448a85d0d7fSLuis R. Rodriguez set_regdom(regdom); 449a85d0d7fSLuis R. Rodriguez 4505fe231e8SJohannes Berg rtnl_unlock(); 4513b377ea9SJohn W. Linville } 4523b377ea9SJohn W. Linville 4533b377ea9SJohn W. Linville static DECLARE_WORK(reg_regdb_work, reg_regdb_search); 4543b377ea9SJohn W. Linville 4553b377ea9SJohn W. Linville static void reg_regdb_query(const char *alpha2) 4563b377ea9SJohn W. Linville { 4573b377ea9SJohn W. Linville struct reg_regdb_search_request *request; 4583b377ea9SJohn W. Linville 4593b377ea9SJohn W. Linville if (!alpha2) 4603b377ea9SJohn W. Linville return; 4613b377ea9SJohn W. Linville 4623b377ea9SJohn W. Linville request = kzalloc(sizeof(struct reg_regdb_search_request), GFP_KERNEL); 4633b377ea9SJohn W. Linville if (!request) 4643b377ea9SJohn W. Linville return; 4653b377ea9SJohn W. Linville 4663b377ea9SJohn W. Linville memcpy(request->alpha2, alpha2, 2); 4673b377ea9SJohn W. Linville 468368d06f5SJohn W. Linville mutex_lock(®_regdb_search_mutex); 4693b377ea9SJohn W. Linville list_add_tail(&request->list, ®_regdb_search_list); 470368d06f5SJohn W. Linville mutex_unlock(®_regdb_search_mutex); 4713b377ea9SJohn W. Linville 4723b377ea9SJohn W. Linville schedule_work(®_regdb_work); 4733b377ea9SJohn W. Linville } 47480007efeSLuis R. Rodriguez 47580007efeSLuis R. Rodriguez /* Feel free to add any other sanity checks here */ 47680007efeSLuis R. Rodriguez static void reg_regdb_size_check(void) 47780007efeSLuis R. Rodriguez { 47880007efeSLuis R. Rodriguez /* We should ideally BUILD_BUG_ON() but then random builds would fail */ 47980007efeSLuis R. Rodriguez WARN_ONCE(!reg_regdb_size, "db.txt is empty, you should update it..."); 48080007efeSLuis R. Rodriguez } 4813b377ea9SJohn W. Linville #else 48280007efeSLuis R. Rodriguez static inline void reg_regdb_size_check(void) {} 4833b377ea9SJohn W. Linville static inline void reg_regdb_query(const char *alpha2) {} 4843b377ea9SJohn W. Linville #endif /* CONFIG_CFG80211_INTERNAL_REGDB */ 4853b377ea9SJohn W. Linville 486fb1fc7adSLuis R. Rodriguez /* 487fb1fc7adSLuis R. Rodriguez * This lets us keep regulatory code which is updated on a regulatory 4881226d258SJohannes Berg * basis in userspace. 489fb1fc7adSLuis R. Rodriguez */ 490b2e1b302SLuis R. Rodriguez static int call_crda(const char *alpha2) 491b2e1b302SLuis R. Rodriguez { 4921226d258SJohannes Berg char country[12]; 4931226d258SJohannes Berg char *env[] = { country, NULL }; 4941226d258SJohannes Berg 4951226d258SJohannes Berg snprintf(country, sizeof(country), "COUNTRY=%c%c", 4961226d258SJohannes Berg alpha2[0], alpha2[1]); 4971226d258SJohannes Berg 498b2e1b302SLuis R. Rodriguez if (!is_world_regdom((char *) alpha2)) 499e9c0268fSJoe Perches pr_info("Calling CRDA for country: %c%c\n", 500b2e1b302SLuis R. Rodriguez alpha2[0], alpha2[1]); 501b2e1b302SLuis R. Rodriguez else 502e9c0268fSJoe Perches pr_info("Calling CRDA to update world regulatory domain\n"); 5038318d78aSJohannes Berg 5043b377ea9SJohn W. Linville /* query internal regulatory database (if it exists) */ 5053b377ea9SJohn W. Linville reg_regdb_query(alpha2); 5063b377ea9SJohn W. Linville 5071226d258SJohannes Berg return kobject_uevent_env(®_pdev->dev.kobj, KOBJ_CHANGE, env); 508b2e1b302SLuis R. Rodriguez } 509b2e1b302SLuis R. Rodriguez 510fe6631ffSLuis R. Rodriguez static enum reg_request_treatment 511fe6631ffSLuis R. Rodriguez reg_call_crda(struct regulatory_request *request) 512fe6631ffSLuis R. Rodriguez { 513fe6631ffSLuis R. Rodriguez if (call_crda(request->alpha2)) 514fe6631ffSLuis R. Rodriguez return REG_REQ_IGNORE; 515fe6631ffSLuis R. Rodriguez return REG_REQ_OK; 516fe6631ffSLuis R. Rodriguez } 517fe6631ffSLuis R. Rodriguez 518e438768fSLuis R. Rodriguez bool reg_is_valid_request(const char *alpha2) 519b2e1b302SLuis R. Rodriguez { 520c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 52161405e97SLuis R. Rodriguez 522c492db37SJohannes Berg if (!lr || lr->processed) 523f6037d09SJohannes Berg return false; 524f6037d09SJohannes Berg 525c492db37SJohannes Berg return alpha2_equal(lr->alpha2, alpha2); 526b2e1b302SLuis R. Rodriguez } 527b2e1b302SLuis R. Rodriguez 528e3961af1SJanusz Dziedzic static const struct ieee80211_regdomain *reg_get_regdomain(struct wiphy *wiphy) 529e3961af1SJanusz Dziedzic { 530e3961af1SJanusz Dziedzic struct regulatory_request *lr = get_last_request(); 531e3961af1SJanusz Dziedzic 532e3961af1SJanusz Dziedzic /* 533e3961af1SJanusz Dziedzic * Follow the driver's regulatory domain, if present, unless a country 534e3961af1SJanusz Dziedzic * IE has been processed or a user wants to help complaince further 535e3961af1SJanusz Dziedzic */ 536e3961af1SJanusz Dziedzic if (lr->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE && 537e3961af1SJanusz Dziedzic lr->initiator != NL80211_REGDOM_SET_BY_USER && 538e3961af1SJanusz Dziedzic wiphy->regd) 539e3961af1SJanusz Dziedzic return get_wiphy_regdom(wiphy); 540e3961af1SJanusz Dziedzic 541e3961af1SJanusz Dziedzic return get_cfg80211_regdom(); 542e3961af1SJanusz Dziedzic } 543e3961af1SJanusz Dziedzic 54497524820SJanusz Dziedzic unsigned int reg_get_max_bandwidth(const struct ieee80211_regdomain *rd, 54597524820SJanusz Dziedzic const struct ieee80211_reg_rule *rule) 54697524820SJanusz Dziedzic { 54797524820SJanusz Dziedzic const struct ieee80211_freq_range *freq_range = &rule->freq_range; 54897524820SJanusz Dziedzic const struct ieee80211_freq_range *freq_range_tmp; 54997524820SJanusz Dziedzic const struct ieee80211_reg_rule *tmp; 55097524820SJanusz Dziedzic u32 start_freq, end_freq, idx, no; 55197524820SJanusz Dziedzic 55297524820SJanusz Dziedzic for (idx = 0; idx < rd->n_reg_rules; idx++) 55397524820SJanusz Dziedzic if (rule == &rd->reg_rules[idx]) 55497524820SJanusz Dziedzic break; 55597524820SJanusz Dziedzic 55697524820SJanusz Dziedzic if (idx == rd->n_reg_rules) 55797524820SJanusz Dziedzic return 0; 55897524820SJanusz Dziedzic 55997524820SJanusz Dziedzic /* get start_freq */ 56097524820SJanusz Dziedzic no = idx; 56197524820SJanusz Dziedzic 56297524820SJanusz Dziedzic while (no) { 56397524820SJanusz Dziedzic tmp = &rd->reg_rules[--no]; 56497524820SJanusz Dziedzic freq_range_tmp = &tmp->freq_range; 56597524820SJanusz Dziedzic 56697524820SJanusz Dziedzic if (freq_range_tmp->end_freq_khz < freq_range->start_freq_khz) 56797524820SJanusz Dziedzic break; 56897524820SJanusz Dziedzic 56997524820SJanusz Dziedzic freq_range = freq_range_tmp; 57097524820SJanusz Dziedzic } 57197524820SJanusz Dziedzic 57297524820SJanusz Dziedzic start_freq = freq_range->start_freq_khz; 57397524820SJanusz Dziedzic 57497524820SJanusz Dziedzic /* get end_freq */ 57597524820SJanusz Dziedzic freq_range = &rule->freq_range; 57697524820SJanusz Dziedzic no = idx; 57797524820SJanusz Dziedzic 57897524820SJanusz Dziedzic while (no < rd->n_reg_rules - 1) { 57997524820SJanusz Dziedzic tmp = &rd->reg_rules[++no]; 58097524820SJanusz Dziedzic freq_range_tmp = &tmp->freq_range; 58197524820SJanusz Dziedzic 58297524820SJanusz Dziedzic if (freq_range_tmp->start_freq_khz > freq_range->end_freq_khz) 58397524820SJanusz Dziedzic break; 58497524820SJanusz Dziedzic 58597524820SJanusz Dziedzic freq_range = freq_range_tmp; 58697524820SJanusz Dziedzic } 58797524820SJanusz Dziedzic 58897524820SJanusz Dziedzic end_freq = freq_range->end_freq_khz; 58997524820SJanusz Dziedzic 59097524820SJanusz Dziedzic return end_freq - start_freq; 59197524820SJanusz Dziedzic } 59297524820SJanusz Dziedzic 593b2e1b302SLuis R. Rodriguez /* Sanity check on a regulatory rule */ 594a3d2eaf0SJohannes Berg static bool is_valid_reg_rule(const struct ieee80211_reg_rule *rule) 595b2e1b302SLuis R. Rodriguez { 596a3d2eaf0SJohannes Berg const struct ieee80211_freq_range *freq_range = &rule->freq_range; 597b2e1b302SLuis R. Rodriguez u32 freq_diff; 598b2e1b302SLuis R. Rodriguez 59991e99004SLuis R. Rodriguez if (freq_range->start_freq_khz <= 0 || freq_range->end_freq_khz <= 0) 600b2e1b302SLuis R. Rodriguez return false; 601b2e1b302SLuis R. Rodriguez 602b2e1b302SLuis R. Rodriguez if (freq_range->start_freq_khz > freq_range->end_freq_khz) 603b2e1b302SLuis R. Rodriguez return false; 604b2e1b302SLuis R. Rodriguez 605b2e1b302SLuis R. Rodriguez freq_diff = freq_range->end_freq_khz - freq_range->start_freq_khz; 606b2e1b302SLuis R. Rodriguez 607bd05f28eSRoel Kluin if (freq_range->end_freq_khz <= freq_range->start_freq_khz || 608bd05f28eSRoel Kluin freq_range->max_bandwidth_khz > freq_diff) 609b2e1b302SLuis R. Rodriguez return false; 610b2e1b302SLuis R. Rodriguez 611b2e1b302SLuis R. Rodriguez return true; 612b2e1b302SLuis R. Rodriguez } 613b2e1b302SLuis R. Rodriguez 614a3d2eaf0SJohannes Berg static bool is_valid_rd(const struct ieee80211_regdomain *rd) 615b2e1b302SLuis R. Rodriguez { 616a3d2eaf0SJohannes Berg const struct ieee80211_reg_rule *reg_rule = NULL; 617b2e1b302SLuis R. Rodriguez unsigned int i; 618b2e1b302SLuis R. Rodriguez 619b2e1b302SLuis R. Rodriguez if (!rd->n_reg_rules) 620b2e1b302SLuis R. Rodriguez return false; 621b2e1b302SLuis R. Rodriguez 62288dc1c3fSLuis R. Rodriguez if (WARN_ON(rd->n_reg_rules > NL80211_MAX_SUPP_REG_RULES)) 62388dc1c3fSLuis R. Rodriguez return false; 62488dc1c3fSLuis R. Rodriguez 625b2e1b302SLuis R. Rodriguez for (i = 0; i < rd->n_reg_rules; i++) { 626b2e1b302SLuis R. Rodriguez reg_rule = &rd->reg_rules[i]; 627b2e1b302SLuis R. Rodriguez if (!is_valid_reg_rule(reg_rule)) 628b2e1b302SLuis R. Rodriguez return false; 629b2e1b302SLuis R. Rodriguez } 630b2e1b302SLuis R. Rodriguez 631b2e1b302SLuis R. Rodriguez return true; 632b2e1b302SLuis R. Rodriguez } 633b2e1b302SLuis R. Rodriguez 634038659e7SLuis R. Rodriguez static bool reg_does_bw_fit(const struct ieee80211_freq_range *freq_range, 635fe7ef5e9SJohannes Berg u32 center_freq_khz, u32 bw_khz) 636b2e1b302SLuis R. Rodriguez { 637038659e7SLuis R. Rodriguez u32 start_freq_khz, end_freq_khz; 638038659e7SLuis R. Rodriguez 639038659e7SLuis R. Rodriguez start_freq_khz = center_freq_khz - (bw_khz/2); 640038659e7SLuis R. Rodriguez end_freq_khz = center_freq_khz + (bw_khz/2); 641038659e7SLuis R. Rodriguez 642b2e1b302SLuis R. Rodriguez if (start_freq_khz >= freq_range->start_freq_khz && 643b2e1b302SLuis R. Rodriguez end_freq_khz <= freq_range->end_freq_khz) 644038659e7SLuis R. Rodriguez return true; 645038659e7SLuis R. Rodriguez 646038659e7SLuis R. Rodriguez return false; 647b2e1b302SLuis R. Rodriguez } 648b2e1b302SLuis R. Rodriguez 6490c7dc45dSLuis R. Rodriguez /** 6500c7dc45dSLuis R. Rodriguez * freq_in_rule_band - tells us if a frequency is in a frequency band 6510c7dc45dSLuis R. Rodriguez * @freq_range: frequency rule we want to query 6520c7dc45dSLuis R. Rodriguez * @freq_khz: frequency we are inquiring about 6530c7dc45dSLuis R. Rodriguez * 6540c7dc45dSLuis R. Rodriguez * This lets us know if a specific frequency rule is or is not relevant to 6550c7dc45dSLuis R. Rodriguez * a specific frequency's band. Bands are device specific and artificial 65664629b9dSVladimir Kondratiev * definitions (the "2.4 GHz band", the "5 GHz band" and the "60GHz band"), 65764629b9dSVladimir Kondratiev * however it is safe for now to assume that a frequency rule should not be 65864629b9dSVladimir Kondratiev * part of a frequency's band if the start freq or end freq are off by more 65964629b9dSVladimir Kondratiev * than 2 GHz for the 2.4 and 5 GHz bands, and by more than 10 GHz for the 66064629b9dSVladimir Kondratiev * 60 GHz band. 6610c7dc45dSLuis R. Rodriguez * This resolution can be lowered and should be considered as we add 6620c7dc45dSLuis R. Rodriguez * regulatory rule support for other "bands". 6630c7dc45dSLuis R. Rodriguez **/ 6640c7dc45dSLuis R. Rodriguez static bool freq_in_rule_band(const struct ieee80211_freq_range *freq_range, 6650c7dc45dSLuis R. Rodriguez u32 freq_khz) 6660c7dc45dSLuis R. Rodriguez { 6670c7dc45dSLuis R. Rodriguez #define ONE_GHZ_IN_KHZ 1000000 66864629b9dSVladimir Kondratiev /* 66964629b9dSVladimir Kondratiev * From 802.11ad: directional multi-gigabit (DMG): 67064629b9dSVladimir Kondratiev * Pertaining to operation in a frequency band containing a channel 67164629b9dSVladimir Kondratiev * with the Channel starting frequency above 45 GHz. 67264629b9dSVladimir Kondratiev */ 67364629b9dSVladimir Kondratiev u32 limit = freq_khz > 45 * ONE_GHZ_IN_KHZ ? 67464629b9dSVladimir Kondratiev 10 * ONE_GHZ_IN_KHZ : 2 * ONE_GHZ_IN_KHZ; 67564629b9dSVladimir Kondratiev if (abs(freq_khz - freq_range->start_freq_khz) <= limit) 6760c7dc45dSLuis R. Rodriguez return true; 67764629b9dSVladimir Kondratiev if (abs(freq_khz - freq_range->end_freq_khz) <= limit) 6780c7dc45dSLuis R. Rodriguez return true; 6790c7dc45dSLuis R. Rodriguez return false; 6800c7dc45dSLuis R. Rodriguez #undef ONE_GHZ_IN_KHZ 6810c7dc45dSLuis R. Rodriguez } 6820c7dc45dSLuis R. Rodriguez 683fb1fc7adSLuis R. Rodriguez /* 684adbfb058SLuis R. Rodriguez * Later on we can perhaps use the more restrictive DFS 685adbfb058SLuis R. Rodriguez * region but we don't have information for that yet so 686adbfb058SLuis R. Rodriguez * for now simply disallow conflicts. 687adbfb058SLuis R. Rodriguez */ 688adbfb058SLuis R. Rodriguez static enum nl80211_dfs_regions 689adbfb058SLuis R. Rodriguez reg_intersect_dfs_region(const enum nl80211_dfs_regions dfs_region1, 690adbfb058SLuis R. Rodriguez const enum nl80211_dfs_regions dfs_region2) 691adbfb058SLuis R. Rodriguez { 692adbfb058SLuis R. Rodriguez if (dfs_region1 != dfs_region2) 693adbfb058SLuis R. Rodriguez return NL80211_DFS_UNSET; 694adbfb058SLuis R. Rodriguez return dfs_region1; 695adbfb058SLuis R. Rodriguez } 696adbfb058SLuis R. Rodriguez 697adbfb058SLuis R. Rodriguez /* 698fb1fc7adSLuis R. Rodriguez * Helper for regdom_intersect(), this does the real 699fb1fc7adSLuis R. Rodriguez * mathematical intersection fun 700fb1fc7adSLuis R. Rodriguez */ 70197524820SJanusz Dziedzic static int reg_rules_intersect(const struct ieee80211_regdomain *rd1, 70297524820SJanusz Dziedzic const struct ieee80211_regdomain *rd2, 70397524820SJanusz Dziedzic const struct ieee80211_reg_rule *rule1, 7049c96477dSLuis R. Rodriguez const struct ieee80211_reg_rule *rule2, 7059c96477dSLuis R. Rodriguez struct ieee80211_reg_rule *intersected_rule) 7069c96477dSLuis R. Rodriguez { 7079c96477dSLuis R. Rodriguez const struct ieee80211_freq_range *freq_range1, *freq_range2; 7089c96477dSLuis R. Rodriguez struct ieee80211_freq_range *freq_range; 7099c96477dSLuis R. Rodriguez const struct ieee80211_power_rule *power_rule1, *power_rule2; 7109c96477dSLuis R. Rodriguez struct ieee80211_power_rule *power_rule; 71197524820SJanusz Dziedzic u32 freq_diff, max_bandwidth1, max_bandwidth2; 7129c96477dSLuis R. Rodriguez 7139c96477dSLuis R. Rodriguez freq_range1 = &rule1->freq_range; 7149c96477dSLuis R. Rodriguez freq_range2 = &rule2->freq_range; 7159c96477dSLuis R. Rodriguez freq_range = &intersected_rule->freq_range; 7169c96477dSLuis R. Rodriguez 7179c96477dSLuis R. Rodriguez power_rule1 = &rule1->power_rule; 7189c96477dSLuis R. Rodriguez power_rule2 = &rule2->power_rule; 7199c96477dSLuis R. Rodriguez power_rule = &intersected_rule->power_rule; 7209c96477dSLuis R. Rodriguez 7219c96477dSLuis R. Rodriguez freq_range->start_freq_khz = max(freq_range1->start_freq_khz, 7229c96477dSLuis R. Rodriguez freq_range2->start_freq_khz); 7239c96477dSLuis R. Rodriguez freq_range->end_freq_khz = min(freq_range1->end_freq_khz, 7249c96477dSLuis R. Rodriguez freq_range2->end_freq_khz); 72597524820SJanusz Dziedzic 72697524820SJanusz Dziedzic max_bandwidth1 = freq_range1->max_bandwidth_khz; 72797524820SJanusz Dziedzic max_bandwidth2 = freq_range2->max_bandwidth_khz; 72897524820SJanusz Dziedzic 729b0dfd2eaSJanusz Dziedzic if (rule1->flags & NL80211_RRF_AUTO_BW) 73097524820SJanusz Dziedzic max_bandwidth1 = reg_get_max_bandwidth(rd1, rule1); 731b0dfd2eaSJanusz Dziedzic if (rule2->flags & NL80211_RRF_AUTO_BW) 73297524820SJanusz Dziedzic max_bandwidth2 = reg_get_max_bandwidth(rd2, rule2); 73397524820SJanusz Dziedzic 73497524820SJanusz Dziedzic freq_range->max_bandwidth_khz = min(max_bandwidth1, max_bandwidth2); 7359c96477dSLuis R. Rodriguez 736b0dfd2eaSJanusz Dziedzic intersected_rule->flags = rule1->flags | rule2->flags; 737b0dfd2eaSJanusz Dziedzic 738b0dfd2eaSJanusz Dziedzic /* 739b0dfd2eaSJanusz Dziedzic * In case NL80211_RRF_AUTO_BW requested for both rules 740b0dfd2eaSJanusz Dziedzic * set AUTO_BW in intersected rule also. Next we will 741b0dfd2eaSJanusz Dziedzic * calculate BW correctly in handle_channel function. 742b0dfd2eaSJanusz Dziedzic * In other case remove AUTO_BW flag while we calculate 743b0dfd2eaSJanusz Dziedzic * maximum bandwidth correctly and auto calculation is 744b0dfd2eaSJanusz Dziedzic * not required. 745b0dfd2eaSJanusz Dziedzic */ 746b0dfd2eaSJanusz Dziedzic if ((rule1->flags & NL80211_RRF_AUTO_BW) && 747b0dfd2eaSJanusz Dziedzic (rule2->flags & NL80211_RRF_AUTO_BW)) 748b0dfd2eaSJanusz Dziedzic intersected_rule->flags |= NL80211_RRF_AUTO_BW; 749b0dfd2eaSJanusz Dziedzic else 750b0dfd2eaSJanusz Dziedzic intersected_rule->flags &= ~NL80211_RRF_AUTO_BW; 751b0dfd2eaSJanusz Dziedzic 7529c96477dSLuis R. Rodriguez freq_diff = freq_range->end_freq_khz - freq_range->start_freq_khz; 7539c96477dSLuis R. Rodriguez if (freq_range->max_bandwidth_khz > freq_diff) 7549c96477dSLuis R. Rodriguez freq_range->max_bandwidth_khz = freq_diff; 7559c96477dSLuis R. Rodriguez 7569c96477dSLuis R. Rodriguez power_rule->max_eirp = min(power_rule1->max_eirp, 7579c96477dSLuis R. Rodriguez power_rule2->max_eirp); 7589c96477dSLuis R. Rodriguez power_rule->max_antenna_gain = min(power_rule1->max_antenna_gain, 7599c96477dSLuis R. Rodriguez power_rule2->max_antenna_gain); 7609c96477dSLuis R. Rodriguez 761089027e5SJanusz Dziedzic intersected_rule->dfs_cac_ms = max(rule1->dfs_cac_ms, 762089027e5SJanusz Dziedzic rule2->dfs_cac_ms); 763089027e5SJanusz Dziedzic 7649c96477dSLuis R. Rodriguez if (!is_valid_reg_rule(intersected_rule)) 7659c96477dSLuis R. Rodriguez return -EINVAL; 7669c96477dSLuis R. Rodriguez 7679c96477dSLuis R. Rodriguez return 0; 7689c96477dSLuis R. Rodriguez } 7699c96477dSLuis R. Rodriguez 7709c96477dSLuis R. Rodriguez /** 7719c96477dSLuis R. Rodriguez * regdom_intersect - do the intersection between two regulatory domains 7729c96477dSLuis R. Rodriguez * @rd1: first regulatory domain 7739c96477dSLuis R. Rodriguez * @rd2: second regulatory domain 7749c96477dSLuis R. Rodriguez * 7759c96477dSLuis R. Rodriguez * Use this function to get the intersection between two regulatory domains. 7769c96477dSLuis R. Rodriguez * Once completed we will mark the alpha2 for the rd as intersected, "98", 7779c96477dSLuis R. Rodriguez * as no one single alpha2 can represent this regulatory domain. 7789c96477dSLuis R. Rodriguez * 7799c96477dSLuis R. Rodriguez * Returns a pointer to the regulatory domain structure which will hold the 7809c96477dSLuis R. Rodriguez * resulting intersection of rules between rd1 and rd2. We will 7819c96477dSLuis R. Rodriguez * kzalloc() this structure for you. 7829c96477dSLuis R. Rodriguez */ 7831a919318SJohannes Berg static struct ieee80211_regdomain * 7841a919318SJohannes Berg regdom_intersect(const struct ieee80211_regdomain *rd1, 7859c96477dSLuis R. Rodriguez const struct ieee80211_regdomain *rd2) 7869c96477dSLuis R. Rodriguez { 7879c96477dSLuis R. Rodriguez int r, size_of_regd; 7889c96477dSLuis R. Rodriguez unsigned int x, y; 7899c96477dSLuis R. Rodriguez unsigned int num_rules = 0, rule_idx = 0; 7909c96477dSLuis R. Rodriguez const struct ieee80211_reg_rule *rule1, *rule2; 7919c96477dSLuis R. Rodriguez struct ieee80211_reg_rule *intersected_rule; 7929c96477dSLuis R. Rodriguez struct ieee80211_regdomain *rd; 7939c96477dSLuis R. Rodriguez /* This is just a dummy holder to help us count */ 79474f53cd8SJohannes Berg struct ieee80211_reg_rule dummy_rule; 7959c96477dSLuis R. Rodriguez 7969c96477dSLuis R. Rodriguez if (!rd1 || !rd2) 7979c96477dSLuis R. Rodriguez return NULL; 7989c96477dSLuis R. Rodriguez 799fb1fc7adSLuis R. Rodriguez /* 800fb1fc7adSLuis R. Rodriguez * First we get a count of the rules we'll need, then we actually 8019c96477dSLuis R. Rodriguez * build them. This is to so we can malloc() and free() a 8029c96477dSLuis R. Rodriguez * regdomain once. The reason we use reg_rules_intersect() here 8039c96477dSLuis R. Rodriguez * is it will return -EINVAL if the rule computed makes no sense. 804fb1fc7adSLuis R. Rodriguez * All rules that do check out OK are valid. 805fb1fc7adSLuis R. Rodriguez */ 8069c96477dSLuis R. Rodriguez 8079c96477dSLuis R. Rodriguez for (x = 0; x < rd1->n_reg_rules; x++) { 8089c96477dSLuis R. Rodriguez rule1 = &rd1->reg_rules[x]; 8099c96477dSLuis R. Rodriguez for (y = 0; y < rd2->n_reg_rules; y++) { 8109c96477dSLuis R. Rodriguez rule2 = &rd2->reg_rules[y]; 81197524820SJanusz Dziedzic if (!reg_rules_intersect(rd1, rd2, rule1, rule2, 81297524820SJanusz Dziedzic &dummy_rule)) 8139c96477dSLuis R. Rodriguez num_rules++; 8149c96477dSLuis R. Rodriguez } 8159c96477dSLuis R. Rodriguez } 8169c96477dSLuis R. Rodriguez 8179c96477dSLuis R. Rodriguez if (!num_rules) 8189c96477dSLuis R. Rodriguez return NULL; 8199c96477dSLuis R. Rodriguez 8209c96477dSLuis R. Rodriguez size_of_regd = sizeof(struct ieee80211_regdomain) + 82182f20856SJohannes Berg num_rules * sizeof(struct ieee80211_reg_rule); 8229c96477dSLuis R. Rodriguez 8239c96477dSLuis R. Rodriguez rd = kzalloc(size_of_regd, GFP_KERNEL); 8249c96477dSLuis R. Rodriguez if (!rd) 8259c96477dSLuis R. Rodriguez return NULL; 8269c96477dSLuis R. Rodriguez 8278a57fff0SJohannes Berg for (x = 0; x < rd1->n_reg_rules && rule_idx < num_rules; x++) { 8289c96477dSLuis R. Rodriguez rule1 = &rd1->reg_rules[x]; 8298a57fff0SJohannes Berg for (y = 0; y < rd2->n_reg_rules && rule_idx < num_rules; y++) { 8309c96477dSLuis R. Rodriguez rule2 = &rd2->reg_rules[y]; 831fb1fc7adSLuis R. Rodriguez /* 832fb1fc7adSLuis R. Rodriguez * This time around instead of using the stack lets 8339c96477dSLuis R. Rodriguez * write to the target rule directly saving ourselves 834fb1fc7adSLuis R. Rodriguez * a memcpy() 835fb1fc7adSLuis R. Rodriguez */ 8369c96477dSLuis R. Rodriguez intersected_rule = &rd->reg_rules[rule_idx]; 83797524820SJanusz Dziedzic r = reg_rules_intersect(rd1, rd2, rule1, rule2, 83897524820SJanusz Dziedzic intersected_rule); 839fb1fc7adSLuis R. Rodriguez /* 840fb1fc7adSLuis R. Rodriguez * No need to memset here the intersected rule here as 841fb1fc7adSLuis R. Rodriguez * we're not using the stack anymore 842fb1fc7adSLuis R. Rodriguez */ 8439c96477dSLuis R. Rodriguez if (r) 8449c96477dSLuis R. Rodriguez continue; 8459c96477dSLuis R. Rodriguez rule_idx++; 8469c96477dSLuis R. Rodriguez } 8479c96477dSLuis R. Rodriguez } 8489c96477dSLuis R. Rodriguez 8499c96477dSLuis R. Rodriguez if (rule_idx != num_rules) { 8509c96477dSLuis R. Rodriguez kfree(rd); 8519c96477dSLuis R. Rodriguez return NULL; 8529c96477dSLuis R. Rodriguez } 8539c96477dSLuis R. Rodriguez 8549c96477dSLuis R. Rodriguez rd->n_reg_rules = num_rules; 8559c96477dSLuis R. Rodriguez rd->alpha2[0] = '9'; 8569c96477dSLuis R. Rodriguez rd->alpha2[1] = '8'; 857adbfb058SLuis R. Rodriguez rd->dfs_region = reg_intersect_dfs_region(rd1->dfs_region, 858adbfb058SLuis R. Rodriguez rd2->dfs_region); 8599c96477dSLuis R. Rodriguez 8609c96477dSLuis R. Rodriguez return rd; 8619c96477dSLuis R. Rodriguez } 8629c96477dSLuis R. Rodriguez 863fb1fc7adSLuis R. Rodriguez /* 864fb1fc7adSLuis R. Rodriguez * XXX: add support for the rest of enum nl80211_reg_rule_flags, we may 865fb1fc7adSLuis R. Rodriguez * want to just have the channel structure use these 866fb1fc7adSLuis R. Rodriguez */ 867b2e1b302SLuis R. Rodriguez static u32 map_regdom_flags(u32 rd_flags) 868b2e1b302SLuis R. Rodriguez { 869b2e1b302SLuis R. Rodriguez u32 channel_flags = 0; 8708fe02e16SLuis R. Rodriguez if (rd_flags & NL80211_RRF_NO_IR_ALL) 8718fe02e16SLuis R. Rodriguez channel_flags |= IEEE80211_CHAN_NO_IR; 872b2e1b302SLuis R. Rodriguez if (rd_flags & NL80211_RRF_DFS) 873b2e1b302SLuis R. Rodriguez channel_flags |= IEEE80211_CHAN_RADAR; 87403f6b084SSeth Forshee if (rd_flags & NL80211_RRF_NO_OFDM) 87503f6b084SSeth Forshee channel_flags |= IEEE80211_CHAN_NO_OFDM; 876570dbde1SDavid Spinadel if (rd_flags & NL80211_RRF_NO_OUTDOOR) 877570dbde1SDavid Spinadel channel_flags |= IEEE80211_CHAN_INDOOR_ONLY; 878b2e1b302SLuis R. Rodriguez return channel_flags; 879b2e1b302SLuis R. Rodriguez } 880b2e1b302SLuis R. Rodriguez 881361c9c8bSJohannes Berg static const struct ieee80211_reg_rule * 882361c9c8bSJohannes Berg freq_reg_info_regd(struct wiphy *wiphy, u32 center_freq, 8835d885b99SJohannes Berg const struct ieee80211_regdomain *regd) 8848318d78aSJohannes Berg { 8858318d78aSJohannes Berg int i; 8860c7dc45dSLuis R. Rodriguez bool band_rule_found = false; 887038659e7SLuis R. Rodriguez bool bw_fits = false; 888038659e7SLuis R. Rodriguez 8893e0c3ff3SLuis R. Rodriguez if (!regd) 890361c9c8bSJohannes Berg return ERR_PTR(-EINVAL); 891b2e1b302SLuis R. Rodriguez 8923e0c3ff3SLuis R. Rodriguez for (i = 0; i < regd->n_reg_rules; i++) { 893b2e1b302SLuis R. Rodriguez const struct ieee80211_reg_rule *rr; 894b2e1b302SLuis R. Rodriguez const struct ieee80211_freq_range *fr = NULL; 895b2e1b302SLuis R. Rodriguez 8963e0c3ff3SLuis R. Rodriguez rr = ®d->reg_rules[i]; 897b2e1b302SLuis R. Rodriguez fr = &rr->freq_range; 8980c7dc45dSLuis R. Rodriguez 899fb1fc7adSLuis R. Rodriguez /* 900fb1fc7adSLuis R. Rodriguez * We only need to know if one frequency rule was 9010c7dc45dSLuis R. Rodriguez * was in center_freq's band, that's enough, so lets 902fb1fc7adSLuis R. Rodriguez * not overwrite it once found 903fb1fc7adSLuis R. Rodriguez */ 9040c7dc45dSLuis R. Rodriguez if (!band_rule_found) 9050c7dc45dSLuis R. Rodriguez band_rule_found = freq_in_rule_band(fr, center_freq); 9060c7dc45dSLuis R. Rodriguez 907fe7ef5e9SJohannes Berg bw_fits = reg_does_bw_fit(fr, center_freq, MHZ_TO_KHZ(20)); 9080c7dc45dSLuis R. Rodriguez 909361c9c8bSJohannes Berg if (band_rule_found && bw_fits) 910361c9c8bSJohannes Berg return rr; 9118318d78aSJohannes Berg } 9128318d78aSJohannes Berg 9130c7dc45dSLuis R. Rodriguez if (!band_rule_found) 914361c9c8bSJohannes Berg return ERR_PTR(-ERANGE); 9150c7dc45dSLuis R. Rodriguez 916361c9c8bSJohannes Berg return ERR_PTR(-EINVAL); 917b2e1b302SLuis R. Rodriguez } 918b2e1b302SLuis R. Rodriguez 919361c9c8bSJohannes Berg const struct ieee80211_reg_rule *freq_reg_info(struct wiphy *wiphy, 920361c9c8bSJohannes Berg u32 center_freq) 9211fa25e41SLuis R. Rodriguez { 9225d885b99SJohannes Berg const struct ieee80211_regdomain *regd; 9231a919318SJohannes Berg 924e3961af1SJanusz Dziedzic regd = reg_get_regdomain(wiphy); 9255d885b99SJohannes Berg 926361c9c8bSJohannes Berg return freq_reg_info_regd(wiphy, center_freq, regd); 9271fa25e41SLuis R. Rodriguez } 9284f366c5dSJohn W. Linville EXPORT_SYMBOL(freq_reg_info); 929b2e1b302SLuis R. Rodriguez 930034c6d6eSLuis R. Rodriguez const char *reg_initiator_name(enum nl80211_reg_initiator initiator) 931926a0a09SLuis R. Rodriguez { 932926a0a09SLuis R. Rodriguez switch (initiator) { 933926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_CORE: 934034c6d6eSLuis R. Rodriguez return "core"; 935926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_USER: 936034c6d6eSLuis R. Rodriguez return "user"; 937926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_DRIVER: 938034c6d6eSLuis R. Rodriguez return "driver"; 939926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_COUNTRY_IE: 940034c6d6eSLuis R. Rodriguez return "country IE"; 941926a0a09SLuis R. Rodriguez default: 942926a0a09SLuis R. Rodriguez WARN_ON(1); 943034c6d6eSLuis R. Rodriguez return "bug"; 944926a0a09SLuis R. Rodriguez } 945926a0a09SLuis R. Rodriguez } 946034c6d6eSLuis R. Rodriguez EXPORT_SYMBOL(reg_initiator_name); 947e702d3cfSLuis R. Rodriguez 948034c6d6eSLuis R. Rodriguez #ifdef CONFIG_CFG80211_REG_DEBUG 949b0dfd2eaSJanusz Dziedzic static void chan_reg_rule_print_dbg(const struct ieee80211_regdomain *regd, 950b0dfd2eaSJanusz Dziedzic struct ieee80211_channel *chan, 951e702d3cfSLuis R. Rodriguez const struct ieee80211_reg_rule *reg_rule) 952e702d3cfSLuis R. Rodriguez { 953e702d3cfSLuis R. Rodriguez const struct ieee80211_power_rule *power_rule; 954e702d3cfSLuis R. Rodriguez const struct ieee80211_freq_range *freq_range; 955b0dfd2eaSJanusz Dziedzic char max_antenna_gain[32], bw[32]; 956e702d3cfSLuis R. Rodriguez 957e702d3cfSLuis R. Rodriguez power_rule = ®_rule->power_rule; 958e702d3cfSLuis R. Rodriguez freq_range = ®_rule->freq_range; 959e702d3cfSLuis R. Rodriguez 960e702d3cfSLuis R. Rodriguez if (!power_rule->max_antenna_gain) 961b0dfd2eaSJanusz Dziedzic snprintf(max_antenna_gain, sizeof(max_antenna_gain), "N/A"); 962e702d3cfSLuis R. Rodriguez else 963b0dfd2eaSJanusz Dziedzic snprintf(max_antenna_gain, sizeof(max_antenna_gain), "%d", 964b0dfd2eaSJanusz Dziedzic power_rule->max_antenna_gain); 965b0dfd2eaSJanusz Dziedzic 966b0dfd2eaSJanusz Dziedzic if (reg_rule->flags & NL80211_RRF_AUTO_BW) 967b0dfd2eaSJanusz Dziedzic snprintf(bw, sizeof(bw), "%d KHz, %d KHz AUTO", 968b0dfd2eaSJanusz Dziedzic freq_range->max_bandwidth_khz, 969b0dfd2eaSJanusz Dziedzic reg_get_max_bandwidth(regd, reg_rule)); 970b0dfd2eaSJanusz Dziedzic else 971b0dfd2eaSJanusz Dziedzic snprintf(bw, sizeof(bw), "%d KHz", 972b0dfd2eaSJanusz Dziedzic freq_range->max_bandwidth_khz); 973e702d3cfSLuis R. Rodriguez 974fe7ef5e9SJohannes Berg REG_DBG_PRINT("Updating information on frequency %d MHz with regulatory rule:\n", 975fe7ef5e9SJohannes Berg chan->center_freq); 976e702d3cfSLuis R. Rodriguez 977b0dfd2eaSJanusz Dziedzic REG_DBG_PRINT("%d KHz - %d KHz @ %s), (%s mBi, %d mBm)\n", 9781a919318SJohannes Berg freq_range->start_freq_khz, freq_range->end_freq_khz, 979b0dfd2eaSJanusz Dziedzic bw, max_antenna_gain, 980e702d3cfSLuis R. Rodriguez power_rule->max_eirp); 981e702d3cfSLuis R. Rodriguez } 982e702d3cfSLuis R. Rodriguez #else 983b0dfd2eaSJanusz Dziedzic static void chan_reg_rule_print_dbg(const struct ieee80211_regdomain *regd, 984b0dfd2eaSJanusz Dziedzic struct ieee80211_channel *chan, 985e702d3cfSLuis R. Rodriguez const struct ieee80211_reg_rule *reg_rule) 986e702d3cfSLuis R. Rodriguez { 987e702d3cfSLuis R. Rodriguez return; 988e702d3cfSLuis R. Rodriguez } 989926a0a09SLuis R. Rodriguez #endif 990926a0a09SLuis R. Rodriguez 991038659e7SLuis R. Rodriguez /* 992038659e7SLuis R. Rodriguez * Note that right now we assume the desired channel bandwidth 993038659e7SLuis R. Rodriguez * is always 20 MHz for each individual channel (HT40 uses 20 MHz 994fe7ef5e9SJohannes Berg * per channel, the primary and the extension channel). 995038659e7SLuis R. Rodriguez */ 9967ca43d03SLuis R. Rodriguez static void handle_channel(struct wiphy *wiphy, 9977ca43d03SLuis R. Rodriguez enum nl80211_reg_initiator initiator, 998fdc9d7b2SJohannes Berg struct ieee80211_channel *chan) 999b2e1b302SLuis R. Rodriguez { 1000038659e7SLuis R. Rodriguez u32 flags, bw_flags = 0; 1001b2e1b302SLuis R. Rodriguez const struct ieee80211_reg_rule *reg_rule = NULL; 1002b2e1b302SLuis R. Rodriguez const struct ieee80211_power_rule *power_rule = NULL; 1003038659e7SLuis R. Rodriguez const struct ieee80211_freq_range *freq_range = NULL; 1004fe33eb39SLuis R. Rodriguez struct wiphy *request_wiphy = NULL; 1005c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 100697524820SJanusz Dziedzic const struct ieee80211_regdomain *regd; 100797524820SJanusz Dziedzic u32 max_bandwidth_khz; 1008a92a3ce7SLuis R. Rodriguez 1009c492db37SJohannes Berg request_wiphy = wiphy_idx_to_wiphy(lr->wiphy_idx); 1010a92a3ce7SLuis R. Rodriguez 1011a92a3ce7SLuis R. Rodriguez flags = chan->orig_flags; 1012b2e1b302SLuis R. Rodriguez 1013361c9c8bSJohannes Berg reg_rule = freq_reg_info(wiphy, MHZ_TO_KHZ(chan->center_freq)); 1014361c9c8bSJohannes Berg if (IS_ERR(reg_rule)) { 1015ca4ffe8fSLuis R. Rodriguez /* 1016ca4ffe8fSLuis R. Rodriguez * We will disable all channels that do not match our 101725985edcSLucas De Marchi * received regulatory rule unless the hint is coming 1018ca4ffe8fSLuis R. Rodriguez * from a Country IE and the Country IE had no information 1019ca4ffe8fSLuis R. Rodriguez * about a band. The IEEE 802.11 spec allows for an AP 1020ca4ffe8fSLuis R. Rodriguez * to send only a subset of the regulatory rules allowed, 1021ca4ffe8fSLuis R. Rodriguez * so an AP in the US that only supports 2.4 GHz may only send 1022ca4ffe8fSLuis R. Rodriguez * a country IE with information for the 2.4 GHz band 1023ca4ffe8fSLuis R. Rodriguez * while 5 GHz is still supported. 1024ca4ffe8fSLuis R. Rodriguez */ 1025ca4ffe8fSLuis R. Rodriguez if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE && 1026361c9c8bSJohannes Berg PTR_ERR(reg_rule) == -ERANGE) 10278318d78aSJohannes Berg return; 10288318d78aSJohannes Berg 1029cc493e4fSLuis R. Rodriguez if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER && 1030cc493e4fSLuis R. Rodriguez request_wiphy && request_wiphy == wiphy && 1031a2f73b6cSLuis R. Rodriguez request_wiphy->regulatory_flags & REGULATORY_STRICT_REG) { 1032cc493e4fSLuis R. Rodriguez REG_DBG_PRINT("Disabling freq %d MHz for good\n", 1033cc493e4fSLuis R. Rodriguez chan->center_freq); 1034cc493e4fSLuis R. Rodriguez chan->orig_flags |= IEEE80211_CHAN_DISABLED; 1035cc493e4fSLuis R. Rodriguez chan->flags = chan->orig_flags; 1036cc493e4fSLuis R. Rodriguez } else { 1037cc493e4fSLuis R. Rodriguez REG_DBG_PRINT("Disabling freq %d MHz\n", 1038cc493e4fSLuis R. Rodriguez chan->center_freq); 1039990de49fSJohannes Berg chan->flags |= IEEE80211_CHAN_DISABLED; 1040cc493e4fSLuis R. Rodriguez } 1041ca4ffe8fSLuis R. Rodriguez return; 1042ca4ffe8fSLuis R. Rodriguez } 1043ca4ffe8fSLuis R. Rodriguez 1044b0dfd2eaSJanusz Dziedzic regd = reg_get_regdomain(wiphy); 1045b0dfd2eaSJanusz Dziedzic chan_reg_rule_print_dbg(regd, chan, reg_rule); 1046e702d3cfSLuis R. Rodriguez 1047b2e1b302SLuis R. Rodriguez power_rule = ®_rule->power_rule; 1048038659e7SLuis R. Rodriguez freq_range = ®_rule->freq_range; 1049038659e7SLuis R. Rodriguez 105097524820SJanusz Dziedzic max_bandwidth_khz = freq_range->max_bandwidth_khz; 105197524820SJanusz Dziedzic /* Check if auto calculation requested */ 1052b0dfd2eaSJanusz Dziedzic if (reg_rule->flags & NL80211_RRF_AUTO_BW) 105397524820SJanusz Dziedzic max_bandwidth_khz = reg_get_max_bandwidth(regd, reg_rule); 105497524820SJanusz Dziedzic 105597524820SJanusz Dziedzic if (max_bandwidth_khz < MHZ_TO_KHZ(40)) 1056038659e7SLuis R. Rodriguez bw_flags = IEEE80211_CHAN_NO_HT40; 105797524820SJanusz Dziedzic if (max_bandwidth_khz < MHZ_TO_KHZ(80)) 1058c7a6ee27SJohannes Berg bw_flags |= IEEE80211_CHAN_NO_80MHZ; 105997524820SJanusz Dziedzic if (max_bandwidth_khz < MHZ_TO_KHZ(160)) 1060c7a6ee27SJohannes Berg bw_flags |= IEEE80211_CHAN_NO_160MHZ; 1061b2e1b302SLuis R. Rodriguez 1062c492db37SJohannes Berg if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER && 1063806a9e39SLuis R. Rodriguez request_wiphy && request_wiphy == wiphy && 1064a2f73b6cSLuis R. Rodriguez request_wiphy->regulatory_flags & REGULATORY_STRICT_REG) { 1065fb1fc7adSLuis R. Rodriguez /* 106625985edcSLucas De Marchi * This guarantees the driver's requested regulatory domain 1067f976376dSLuis R. Rodriguez * will always be used as a base for further regulatory 1068fb1fc7adSLuis R. Rodriguez * settings 1069fb1fc7adSLuis R. Rodriguez */ 1070f976376dSLuis R. Rodriguez chan->flags = chan->orig_flags = 1071038659e7SLuis R. Rodriguez map_regdom_flags(reg_rule->flags) | bw_flags; 1072f976376dSLuis R. Rodriguez chan->max_antenna_gain = chan->orig_mag = 1073f976376dSLuis R. Rodriguez (int) MBI_TO_DBI(power_rule->max_antenna_gain); 1074279f0f55SFelix Fietkau chan->max_reg_power = chan->max_power = chan->orig_mpwr = 1075f976376dSLuis R. Rodriguez (int) MBM_TO_DBM(power_rule->max_eirp); 1076f976376dSLuis R. Rodriguez return; 1077f976376dSLuis R. Rodriguez } 1078f976376dSLuis R. Rodriguez 107904f39047SSimon Wunderlich chan->dfs_state = NL80211_DFS_USABLE; 108004f39047SSimon Wunderlich chan->dfs_state_entered = jiffies; 108104f39047SSimon Wunderlich 1082aa3d7eefSRajkumar Manoharan chan->beacon_found = false; 1083038659e7SLuis R. Rodriguez chan->flags = flags | bw_flags | map_regdom_flags(reg_rule->flags); 10841a919318SJohannes Berg chan->max_antenna_gain = 10851a919318SJohannes Berg min_t(int, chan->orig_mag, 10861a919318SJohannes Berg MBI_TO_DBI(power_rule->max_antenna_gain)); 1087eccc068eSHong Wu chan->max_reg_power = (int) MBM_TO_DBM(power_rule->max_eirp); 1088089027e5SJanusz Dziedzic 1089089027e5SJanusz Dziedzic if (chan->flags & IEEE80211_CHAN_RADAR) { 1090089027e5SJanusz Dziedzic if (reg_rule->dfs_cac_ms) 1091089027e5SJanusz Dziedzic chan->dfs_cac_ms = reg_rule->dfs_cac_ms; 1092089027e5SJanusz Dziedzic else 1093089027e5SJanusz Dziedzic chan->dfs_cac_ms = IEEE80211_DFS_MIN_CAC_TIME_MS; 1094089027e5SJanusz Dziedzic } 1095089027e5SJanusz Dziedzic 10965e31fc08SStanislaw Gruszka if (chan->orig_mpwr) { 10975e31fc08SStanislaw Gruszka /* 1098a09a85a0SLuis R. Rodriguez * Devices that use REGULATORY_COUNTRY_IE_FOLLOW_POWER 1099a09a85a0SLuis R. Rodriguez * will always follow the passed country IE power settings. 11005e31fc08SStanislaw Gruszka */ 11015e31fc08SStanislaw Gruszka if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE && 1102a09a85a0SLuis R. Rodriguez wiphy->regulatory_flags & REGULATORY_COUNTRY_IE_FOLLOW_POWER) 11035e31fc08SStanislaw Gruszka chan->max_power = chan->max_reg_power; 11045e31fc08SStanislaw Gruszka else 11055e31fc08SStanislaw Gruszka chan->max_power = min(chan->orig_mpwr, 11065e31fc08SStanislaw Gruszka chan->max_reg_power); 11075e31fc08SStanislaw Gruszka } else 11085e31fc08SStanislaw Gruszka chan->max_power = chan->max_reg_power; 11098318d78aSJohannes Berg } 11108318d78aSJohannes Berg 11117ca43d03SLuis R. Rodriguez static void handle_band(struct wiphy *wiphy, 1112fdc9d7b2SJohannes Berg enum nl80211_reg_initiator initiator, 1113fdc9d7b2SJohannes Berg struct ieee80211_supported_band *sband) 11148318d78aSJohannes Berg { 1115a92a3ce7SLuis R. Rodriguez unsigned int i; 1116a92a3ce7SLuis R. Rodriguez 1117fdc9d7b2SJohannes Berg if (!sband) 1118fdc9d7b2SJohannes Berg return; 11198318d78aSJohannes Berg 11208318d78aSJohannes Berg for (i = 0; i < sband->n_channels; i++) 1121fdc9d7b2SJohannes Berg handle_channel(wiphy, initiator, &sband->channels[i]); 11228318d78aSJohannes Berg } 11238318d78aSJohannes Berg 112457b5ce07SLuis R. Rodriguez static bool reg_request_cell_base(struct regulatory_request *request) 112557b5ce07SLuis R. Rodriguez { 112657b5ce07SLuis R. Rodriguez if (request->initiator != NL80211_REGDOM_SET_BY_USER) 112757b5ce07SLuis R. Rodriguez return false; 11281a919318SJohannes Berg return request->user_reg_hint_type == NL80211_USER_REG_HINT_CELL_BASE; 112957b5ce07SLuis R. Rodriguez } 113057b5ce07SLuis R. Rodriguez 113157b5ce07SLuis R. Rodriguez bool reg_last_request_cell_base(void) 113257b5ce07SLuis R. Rodriguez { 113338fd2143SJohannes Berg return reg_request_cell_base(get_last_request()); 113457b5ce07SLuis R. Rodriguez } 113557b5ce07SLuis R. Rodriguez 113657b5ce07SLuis R. Rodriguez #ifdef CONFIG_CFG80211_CERTIFICATION_ONUS 113757b5ce07SLuis R. Rodriguez /* Core specific check */ 11382f92212bSJohannes Berg static enum reg_request_treatment 11392f92212bSJohannes Berg reg_ignore_cell_hint(struct regulatory_request *pending_request) 114057b5ce07SLuis R. Rodriguez { 1141c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 114257b5ce07SLuis R. Rodriguez 114357b5ce07SLuis R. Rodriguez if (!reg_num_devs_support_basehint) 11442f92212bSJohannes Berg return REG_REQ_IGNORE; 114557b5ce07SLuis R. Rodriguez 1146c492db37SJohannes Berg if (reg_request_cell_base(lr) && 11471a919318SJohannes Berg !regdom_changes(pending_request->alpha2)) 11482f92212bSJohannes Berg return REG_REQ_ALREADY_SET; 11491a919318SJohannes Berg 11502f92212bSJohannes Berg return REG_REQ_OK; 115157b5ce07SLuis R. Rodriguez } 115257b5ce07SLuis R. Rodriguez 115357b5ce07SLuis R. Rodriguez /* Device specific check */ 115457b5ce07SLuis R. Rodriguez static bool reg_dev_ignore_cell_hint(struct wiphy *wiphy) 115557b5ce07SLuis R. Rodriguez { 11561a919318SJohannes Berg return !(wiphy->features & NL80211_FEATURE_CELL_BASE_REG_HINTS); 115757b5ce07SLuis R. Rodriguez } 115857b5ce07SLuis R. Rodriguez #else 115957b5ce07SLuis R. Rodriguez static int reg_ignore_cell_hint(struct regulatory_request *pending_request) 116057b5ce07SLuis R. Rodriguez { 11612f92212bSJohannes Berg return REG_REQ_IGNORE; 116257b5ce07SLuis R. Rodriguez } 11631a919318SJohannes Berg 11641a919318SJohannes Berg static bool reg_dev_ignore_cell_hint(struct wiphy *wiphy) 116557b5ce07SLuis R. Rodriguez { 116657b5ce07SLuis R. Rodriguez return true; 116757b5ce07SLuis R. Rodriguez } 116857b5ce07SLuis R. Rodriguez #endif 116957b5ce07SLuis R. Rodriguez 1170fa1fb9cbSLuis R. Rodriguez static bool wiphy_strict_alpha2_regd(struct wiphy *wiphy) 1171fa1fb9cbSLuis R. Rodriguez { 1172a2f73b6cSLuis R. Rodriguez if (wiphy->regulatory_flags & REGULATORY_STRICT_REG && 1173a2f73b6cSLuis R. Rodriguez !(wiphy->regulatory_flags & REGULATORY_CUSTOM_REG)) 1174fa1fb9cbSLuis R. Rodriguez return true; 1175fa1fb9cbSLuis R. Rodriguez return false; 1176fa1fb9cbSLuis R. Rodriguez } 117757b5ce07SLuis R. Rodriguez 11787db90f4aSLuis R. Rodriguez static bool ignore_reg_update(struct wiphy *wiphy, 11797db90f4aSLuis R. Rodriguez enum nl80211_reg_initiator initiator) 118014b9815aSLuis R. Rodriguez { 1181c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 1182c492db37SJohannes Berg 1183c492db37SJohannes Berg if (!lr) { 1184034c6d6eSLuis R. Rodriguez REG_DBG_PRINT("Ignoring regulatory request set by %s " 1185034c6d6eSLuis R. Rodriguez "since last_request is not set\n", 1186926a0a09SLuis R. Rodriguez reg_initiator_name(initiator)); 118714b9815aSLuis R. Rodriguez return true; 1188926a0a09SLuis R. Rodriguez } 1189926a0a09SLuis R. Rodriguez 11907db90f4aSLuis R. Rodriguez if (initiator == NL80211_REGDOM_SET_BY_CORE && 1191a2f73b6cSLuis R. Rodriguez wiphy->regulatory_flags & REGULATORY_CUSTOM_REG) { 1192034c6d6eSLuis R. Rodriguez REG_DBG_PRINT("Ignoring regulatory request set by %s " 1193034c6d6eSLuis R. Rodriguez "since the driver uses its own custom " 1194034c6d6eSLuis R. Rodriguez "regulatory domain\n", 1195926a0a09SLuis R. Rodriguez reg_initiator_name(initiator)); 119614b9815aSLuis R. Rodriguez return true; 1197926a0a09SLuis R. Rodriguez } 1198926a0a09SLuis R. Rodriguez 1199fb1fc7adSLuis R. Rodriguez /* 1200fb1fc7adSLuis R. Rodriguez * wiphy->regd will be set once the device has its own 1201fb1fc7adSLuis R. Rodriguez * desired regulatory domain set 1202fb1fc7adSLuis R. Rodriguez */ 1203fa1fb9cbSLuis R. Rodriguez if (wiphy_strict_alpha2_regd(wiphy) && !wiphy->regd && 1204749b527bSLuis R. Rodriguez initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE && 1205c492db37SJohannes Berg !is_world_regdom(lr->alpha2)) { 1206034c6d6eSLuis R. Rodriguez REG_DBG_PRINT("Ignoring regulatory request set by %s " 1207034c6d6eSLuis R. Rodriguez "since the driver requires its own regulatory " 1208034c6d6eSLuis R. Rodriguez "domain to be set first\n", 1209926a0a09SLuis R. Rodriguez reg_initiator_name(initiator)); 121014b9815aSLuis R. Rodriguez return true; 1211926a0a09SLuis R. Rodriguez } 1212926a0a09SLuis R. Rodriguez 1213c492db37SJohannes Berg if (reg_request_cell_base(lr)) 121457b5ce07SLuis R. Rodriguez return reg_dev_ignore_cell_hint(wiphy); 121557b5ce07SLuis R. Rodriguez 121614b9815aSLuis R. Rodriguez return false; 121714b9815aSLuis R. Rodriguez } 121814b9815aSLuis R. Rodriguez 12193195e489SLuis R. Rodriguez static bool reg_is_world_roaming(struct wiphy *wiphy) 12203195e489SLuis R. Rodriguez { 12213195e489SLuis R. Rodriguez const struct ieee80211_regdomain *cr = get_cfg80211_regdom(); 12223195e489SLuis R. Rodriguez const struct ieee80211_regdomain *wr = get_wiphy_regdom(wiphy); 12233195e489SLuis R. Rodriguez struct regulatory_request *lr = get_last_request(); 12243195e489SLuis R. Rodriguez 12253195e489SLuis R. Rodriguez if (is_world_regdom(cr->alpha2) || (wr && is_world_regdom(wr->alpha2))) 12263195e489SLuis R. Rodriguez return true; 12273195e489SLuis R. Rodriguez 12283195e489SLuis R. Rodriguez if (lr && lr->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE && 1229a2f73b6cSLuis R. Rodriguez wiphy->regulatory_flags & REGULATORY_CUSTOM_REG) 12303195e489SLuis R. Rodriguez return true; 12313195e489SLuis R. Rodriguez 12323195e489SLuis R. Rodriguez return false; 12333195e489SLuis R. Rodriguez } 12343195e489SLuis R. Rodriguez 12351a919318SJohannes Berg static void handle_reg_beacon(struct wiphy *wiphy, unsigned int chan_idx, 1236e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon) 1237e38f8a7aSLuis R. Rodriguez { 1238e38f8a7aSLuis R. Rodriguez struct ieee80211_supported_band *sband; 1239e38f8a7aSLuis R. Rodriguez struct ieee80211_channel *chan; 12406bad8766SLuis R. Rodriguez bool channel_changed = false; 12416bad8766SLuis R. Rodriguez struct ieee80211_channel chan_before; 1242e38f8a7aSLuis R. Rodriguez 1243e38f8a7aSLuis R. Rodriguez sband = wiphy->bands[reg_beacon->chan.band]; 1244e38f8a7aSLuis R. Rodriguez chan = &sband->channels[chan_idx]; 1245e38f8a7aSLuis R. Rodriguez 1246e38f8a7aSLuis R. Rodriguez if (likely(chan->center_freq != reg_beacon->chan.center_freq)) 1247e38f8a7aSLuis R. Rodriguez return; 1248e38f8a7aSLuis R. Rodriguez 12496bad8766SLuis R. Rodriguez if (chan->beacon_found) 12506bad8766SLuis R. Rodriguez return; 12516bad8766SLuis R. Rodriguez 12526bad8766SLuis R. Rodriguez chan->beacon_found = true; 12536bad8766SLuis R. Rodriguez 12540f500a5fSLuis R. Rodriguez if (!reg_is_world_roaming(wiphy)) 12550f500a5fSLuis R. Rodriguez return; 12560f500a5fSLuis R. Rodriguez 1257a2f73b6cSLuis R. Rodriguez if (wiphy->regulatory_flags & REGULATORY_DISABLE_BEACON_HINTS) 125837184244SLuis R. Rodriguez return; 125937184244SLuis R. Rodriguez 12606bad8766SLuis R. Rodriguez chan_before.center_freq = chan->center_freq; 12616bad8766SLuis R. Rodriguez chan_before.flags = chan->flags; 12626bad8766SLuis R. Rodriguez 12638fe02e16SLuis R. Rodriguez if (chan->flags & IEEE80211_CHAN_NO_IR) { 12648fe02e16SLuis R. Rodriguez chan->flags &= ~IEEE80211_CHAN_NO_IR; 12656bad8766SLuis R. Rodriguez channel_changed = true; 1266e38f8a7aSLuis R. Rodriguez } 1267e38f8a7aSLuis R. Rodriguez 12686bad8766SLuis R. Rodriguez if (channel_changed) 12696bad8766SLuis R. Rodriguez nl80211_send_beacon_hint_event(wiphy, &chan_before, chan); 1270e38f8a7aSLuis R. Rodriguez } 1271e38f8a7aSLuis R. Rodriguez 1272e38f8a7aSLuis R. Rodriguez /* 1273e38f8a7aSLuis R. Rodriguez * Called when a scan on a wiphy finds a beacon on 1274e38f8a7aSLuis R. Rodriguez * new channel 1275e38f8a7aSLuis R. Rodriguez */ 1276e38f8a7aSLuis R. Rodriguez static void wiphy_update_new_beacon(struct wiphy *wiphy, 1277e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon) 1278e38f8a7aSLuis R. Rodriguez { 1279e38f8a7aSLuis R. Rodriguez unsigned int i; 1280e38f8a7aSLuis R. Rodriguez struct ieee80211_supported_band *sband; 1281e38f8a7aSLuis R. Rodriguez 1282e38f8a7aSLuis R. Rodriguez if (!wiphy->bands[reg_beacon->chan.band]) 1283e38f8a7aSLuis R. Rodriguez return; 1284e38f8a7aSLuis R. Rodriguez 1285e38f8a7aSLuis R. Rodriguez sband = wiphy->bands[reg_beacon->chan.band]; 1286e38f8a7aSLuis R. Rodriguez 1287e38f8a7aSLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 1288e38f8a7aSLuis R. Rodriguez handle_reg_beacon(wiphy, i, reg_beacon); 1289e38f8a7aSLuis R. Rodriguez } 1290e38f8a7aSLuis R. Rodriguez 1291e38f8a7aSLuis R. Rodriguez /* 1292e38f8a7aSLuis R. Rodriguez * Called upon reg changes or a new wiphy is added 1293e38f8a7aSLuis R. Rodriguez */ 1294e38f8a7aSLuis R. Rodriguez static void wiphy_update_beacon_reg(struct wiphy *wiphy) 1295e38f8a7aSLuis R. Rodriguez { 1296e38f8a7aSLuis R. Rodriguez unsigned int i; 1297e38f8a7aSLuis R. Rodriguez struct ieee80211_supported_band *sband; 1298e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon; 1299e38f8a7aSLuis R. Rodriguez 1300e38f8a7aSLuis R. Rodriguez list_for_each_entry(reg_beacon, ®_beacon_list, list) { 1301e38f8a7aSLuis R. Rodriguez if (!wiphy->bands[reg_beacon->chan.band]) 1302e38f8a7aSLuis R. Rodriguez continue; 1303e38f8a7aSLuis R. Rodriguez sband = wiphy->bands[reg_beacon->chan.band]; 1304e38f8a7aSLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 1305e38f8a7aSLuis R. Rodriguez handle_reg_beacon(wiphy, i, reg_beacon); 1306e38f8a7aSLuis R. Rodriguez } 1307e38f8a7aSLuis R. Rodriguez } 1308e38f8a7aSLuis R. Rodriguez 1309e38f8a7aSLuis R. Rodriguez /* Reap the advantages of previously found beacons */ 1310e38f8a7aSLuis R. Rodriguez static void reg_process_beacons(struct wiphy *wiphy) 1311e38f8a7aSLuis R. Rodriguez { 1312b1ed8dddSLuis R. Rodriguez /* 1313b1ed8dddSLuis R. Rodriguez * Means we are just firing up cfg80211, so no beacons would 1314b1ed8dddSLuis R. Rodriguez * have been processed yet. 1315b1ed8dddSLuis R. Rodriguez */ 1316b1ed8dddSLuis R. Rodriguez if (!last_request) 1317b1ed8dddSLuis R. Rodriguez return; 1318e38f8a7aSLuis R. Rodriguez wiphy_update_beacon_reg(wiphy); 1319e38f8a7aSLuis R. Rodriguez } 1320e38f8a7aSLuis R. Rodriguez 13211a919318SJohannes Berg static bool is_ht40_allowed(struct ieee80211_channel *chan) 1322038659e7SLuis R. Rodriguez { 1323038659e7SLuis R. Rodriguez if (!chan) 1324038659e7SLuis R. Rodriguez return false; 13251a919318SJohannes Berg if (chan->flags & IEEE80211_CHAN_DISABLED) 13261a919318SJohannes Berg return false; 13271a919318SJohannes Berg /* This would happen when regulatory rules disallow HT40 completely */ 132855b183adSFelix Fietkau if ((chan->flags & IEEE80211_CHAN_NO_HT40) == IEEE80211_CHAN_NO_HT40) 132955b183adSFelix Fietkau return false; 133055b183adSFelix Fietkau return true; 1331038659e7SLuis R. Rodriguez } 1332038659e7SLuis R. Rodriguez 1333038659e7SLuis R. Rodriguez static void reg_process_ht_flags_channel(struct wiphy *wiphy, 1334fdc9d7b2SJohannes Berg struct ieee80211_channel *channel) 1335038659e7SLuis R. Rodriguez { 1336fdc9d7b2SJohannes Berg struct ieee80211_supported_band *sband = wiphy->bands[channel->band]; 1337038659e7SLuis R. Rodriguez struct ieee80211_channel *channel_before = NULL, *channel_after = NULL; 1338038659e7SLuis R. Rodriguez unsigned int i; 1339038659e7SLuis R. Rodriguez 13401a919318SJohannes Berg if (!is_ht40_allowed(channel)) { 1341038659e7SLuis R. Rodriguez channel->flags |= IEEE80211_CHAN_NO_HT40; 1342038659e7SLuis R. Rodriguez return; 1343038659e7SLuis R. Rodriguez } 1344038659e7SLuis R. Rodriguez 1345038659e7SLuis R. Rodriguez /* 1346038659e7SLuis R. Rodriguez * We need to ensure the extension channels exist to 1347038659e7SLuis R. Rodriguez * be able to use HT40- or HT40+, this finds them (or not) 1348038659e7SLuis R. Rodriguez */ 1349038659e7SLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) { 1350038659e7SLuis R. Rodriguez struct ieee80211_channel *c = &sband->channels[i]; 13511a919318SJohannes Berg 1352038659e7SLuis R. Rodriguez if (c->center_freq == (channel->center_freq - 20)) 1353038659e7SLuis R. Rodriguez channel_before = c; 1354038659e7SLuis R. Rodriguez if (c->center_freq == (channel->center_freq + 20)) 1355038659e7SLuis R. Rodriguez channel_after = c; 1356038659e7SLuis R. Rodriguez } 1357038659e7SLuis R. Rodriguez 1358038659e7SLuis R. Rodriguez /* 1359038659e7SLuis R. Rodriguez * Please note that this assumes target bandwidth is 20 MHz, 1360038659e7SLuis R. Rodriguez * if that ever changes we also need to change the below logic 1361038659e7SLuis R. Rodriguez * to include that as well. 1362038659e7SLuis R. Rodriguez */ 13631a919318SJohannes Berg if (!is_ht40_allowed(channel_before)) 1364689da1b3SLuis R. Rodriguez channel->flags |= IEEE80211_CHAN_NO_HT40MINUS; 1365038659e7SLuis R. Rodriguez else 1366689da1b3SLuis R. Rodriguez channel->flags &= ~IEEE80211_CHAN_NO_HT40MINUS; 1367038659e7SLuis R. Rodriguez 13681a919318SJohannes Berg if (!is_ht40_allowed(channel_after)) 1369689da1b3SLuis R. Rodriguez channel->flags |= IEEE80211_CHAN_NO_HT40PLUS; 1370038659e7SLuis R. Rodriguez else 1371689da1b3SLuis R. Rodriguez channel->flags &= ~IEEE80211_CHAN_NO_HT40PLUS; 1372038659e7SLuis R. Rodriguez } 1373038659e7SLuis R. Rodriguez 1374038659e7SLuis R. Rodriguez static void reg_process_ht_flags_band(struct wiphy *wiphy, 1375fdc9d7b2SJohannes Berg struct ieee80211_supported_band *sband) 1376038659e7SLuis R. Rodriguez { 1377038659e7SLuis R. Rodriguez unsigned int i; 1378038659e7SLuis R. Rodriguez 1379fdc9d7b2SJohannes Berg if (!sband) 1380fdc9d7b2SJohannes Berg return; 1381038659e7SLuis R. Rodriguez 1382038659e7SLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 1383fdc9d7b2SJohannes Berg reg_process_ht_flags_channel(wiphy, &sband->channels[i]); 1384038659e7SLuis R. Rodriguez } 1385038659e7SLuis R. Rodriguez 1386038659e7SLuis R. Rodriguez static void reg_process_ht_flags(struct wiphy *wiphy) 1387038659e7SLuis R. Rodriguez { 1388038659e7SLuis R. Rodriguez enum ieee80211_band band; 1389038659e7SLuis R. Rodriguez 1390038659e7SLuis R. Rodriguez if (!wiphy) 1391038659e7SLuis R. Rodriguez return; 1392038659e7SLuis R. Rodriguez 1393fdc9d7b2SJohannes Berg for (band = 0; band < IEEE80211_NUM_BANDS; band++) 1394fdc9d7b2SJohannes Berg reg_process_ht_flags_band(wiphy, wiphy->bands[band]); 1395038659e7SLuis R. Rodriguez } 1396038659e7SLuis R. Rodriguez 13970e3802dbSLuis R. Rodriguez static void reg_call_notifier(struct wiphy *wiphy, 13980e3802dbSLuis R. Rodriguez struct regulatory_request *request) 13990e3802dbSLuis R. Rodriguez { 14000e3802dbSLuis R. Rodriguez if (wiphy->reg_notifier) 14010e3802dbSLuis R. Rodriguez wiphy->reg_notifier(wiphy, request); 14020e3802dbSLuis R. Rodriguez } 14030e3802dbSLuis R. Rodriguez 1404eac03e38SSven Neumann static void wiphy_update_regulatory(struct wiphy *wiphy, 14057db90f4aSLuis R. Rodriguez enum nl80211_reg_initiator initiator) 14068318d78aSJohannes Berg { 14078318d78aSJohannes Berg enum ieee80211_band band; 1408c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 1409eac03e38SSven Neumann 14100e3802dbSLuis R. Rodriguez if (ignore_reg_update(wiphy, initiator)) { 14110e3802dbSLuis R. Rodriguez /* 14120e3802dbSLuis R. Rodriguez * Regulatory updates set by CORE are ignored for custom 14130e3802dbSLuis R. Rodriguez * regulatory cards. Let us notify the changes to the driver, 14140e3802dbSLuis R. Rodriguez * as some drivers used this to restore its orig_* reg domain. 14150e3802dbSLuis R. Rodriguez */ 14160e3802dbSLuis R. Rodriguez if (initiator == NL80211_REGDOM_SET_BY_CORE && 1417a2f73b6cSLuis R. Rodriguez wiphy->regulatory_flags & REGULATORY_CUSTOM_REG) 14180e3802dbSLuis R. Rodriguez reg_call_notifier(wiphy, lr); 1419a203c2aaSSven Neumann return; 14200e3802dbSLuis R. Rodriguez } 1421a203c2aaSSven Neumann 1422c492db37SJohannes Berg lr->dfs_region = get_cfg80211_regdom()->dfs_region; 1423b68e6b3bSLuis R. Rodriguez 1424fdc9d7b2SJohannes Berg for (band = 0; band < IEEE80211_NUM_BANDS; band++) 1425fdc9d7b2SJohannes Berg handle_band(wiphy, initiator, wiphy->bands[band]); 1426a203c2aaSSven Neumann 1427e38f8a7aSLuis R. Rodriguez reg_process_beacons(wiphy); 1428038659e7SLuis R. Rodriguez reg_process_ht_flags(wiphy); 14290e3802dbSLuis R. Rodriguez reg_call_notifier(wiphy, lr); 1430b2e1b302SLuis R. Rodriguez } 1431b2e1b302SLuis R. Rodriguez 1432d7549cbbSSven Neumann static void update_all_wiphy_regulatory(enum nl80211_reg_initiator initiator) 1433d7549cbbSSven Neumann { 1434d7549cbbSSven Neumann struct cfg80211_registered_device *rdev; 14354a38994fSRajkumar Manoharan struct wiphy *wiphy; 1436d7549cbbSSven Neumann 14375fe231e8SJohannes Berg ASSERT_RTNL(); 1438458f4f9eSJohannes Berg 14394a38994fSRajkumar Manoharan list_for_each_entry(rdev, &cfg80211_rdev_list, list) { 14404a38994fSRajkumar Manoharan wiphy = &rdev->wiphy; 14414a38994fSRajkumar Manoharan wiphy_update_regulatory(wiphy, initiator); 14424a38994fSRajkumar Manoharan } 1443d7549cbbSSven Neumann } 1444d7549cbbSSven Neumann 14451fa25e41SLuis R. Rodriguez static void handle_channel_custom(struct wiphy *wiphy, 1446fdc9d7b2SJohannes Berg struct ieee80211_channel *chan, 14471fa25e41SLuis R. Rodriguez const struct ieee80211_regdomain *regd) 14481fa25e41SLuis R. Rodriguez { 1449038659e7SLuis R. Rodriguez u32 bw_flags = 0; 14501fa25e41SLuis R. Rodriguez const struct ieee80211_reg_rule *reg_rule = NULL; 14511fa25e41SLuis R. Rodriguez const struct ieee80211_power_rule *power_rule = NULL; 1452038659e7SLuis R. Rodriguez const struct ieee80211_freq_range *freq_range = NULL; 145397524820SJanusz Dziedzic u32 max_bandwidth_khz; 14541fa25e41SLuis R. Rodriguez 1455361c9c8bSJohannes Berg reg_rule = freq_reg_info_regd(wiphy, MHZ_TO_KHZ(chan->center_freq), 1456038659e7SLuis R. Rodriguez regd); 14571fa25e41SLuis R. Rodriguez 1458361c9c8bSJohannes Berg if (IS_ERR(reg_rule)) { 1459fe7ef5e9SJohannes Berg REG_DBG_PRINT("Disabling freq %d MHz as custom regd has no rule that fits it\n", 1460fe7ef5e9SJohannes Berg chan->center_freq); 1461cc493e4fSLuis R. Rodriguez chan->orig_flags |= IEEE80211_CHAN_DISABLED; 1462cc493e4fSLuis R. Rodriguez chan->flags = chan->orig_flags; 14631fa25e41SLuis R. Rodriguez return; 14641fa25e41SLuis R. Rodriguez } 14651fa25e41SLuis R. Rodriguez 1466b0dfd2eaSJanusz Dziedzic chan_reg_rule_print_dbg(regd, chan, reg_rule); 1467e702d3cfSLuis R. Rodriguez 14681fa25e41SLuis R. Rodriguez power_rule = ®_rule->power_rule; 1469038659e7SLuis R. Rodriguez freq_range = ®_rule->freq_range; 14701fa25e41SLuis R. Rodriguez 147197524820SJanusz Dziedzic max_bandwidth_khz = freq_range->max_bandwidth_khz; 147297524820SJanusz Dziedzic /* Check if auto calculation requested */ 1473b0dfd2eaSJanusz Dziedzic if (reg_rule->flags & NL80211_RRF_AUTO_BW) 147497524820SJanusz Dziedzic max_bandwidth_khz = reg_get_max_bandwidth(regd, reg_rule); 147597524820SJanusz Dziedzic 147697524820SJanusz Dziedzic if (max_bandwidth_khz < MHZ_TO_KHZ(40)) 1477038659e7SLuis R. Rodriguez bw_flags = IEEE80211_CHAN_NO_HT40; 147897524820SJanusz Dziedzic if (max_bandwidth_khz < MHZ_TO_KHZ(80)) 1479c7a6ee27SJohannes Berg bw_flags |= IEEE80211_CHAN_NO_80MHZ; 148097524820SJanusz Dziedzic if (max_bandwidth_khz < MHZ_TO_KHZ(160)) 1481c7a6ee27SJohannes Berg bw_flags |= IEEE80211_CHAN_NO_160MHZ; 1482038659e7SLuis R. Rodriguez 1483038659e7SLuis R. Rodriguez chan->flags |= map_regdom_flags(reg_rule->flags) | bw_flags; 14841fa25e41SLuis R. Rodriguez chan->max_antenna_gain = (int) MBI_TO_DBI(power_rule->max_antenna_gain); 1485279f0f55SFelix Fietkau chan->max_reg_power = chan->max_power = 1486279f0f55SFelix Fietkau (int) MBM_TO_DBM(power_rule->max_eirp); 14871fa25e41SLuis R. Rodriguez } 14881fa25e41SLuis R. Rodriguez 1489fdc9d7b2SJohannes Berg static void handle_band_custom(struct wiphy *wiphy, 1490fdc9d7b2SJohannes Berg struct ieee80211_supported_band *sband, 14911fa25e41SLuis R. Rodriguez const struct ieee80211_regdomain *regd) 14921fa25e41SLuis R. Rodriguez { 14931fa25e41SLuis R. Rodriguez unsigned int i; 14941fa25e41SLuis R. Rodriguez 1495fdc9d7b2SJohannes Berg if (!sband) 1496fdc9d7b2SJohannes Berg return; 14971fa25e41SLuis R. Rodriguez 14981fa25e41SLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 1499fdc9d7b2SJohannes Berg handle_channel_custom(wiphy, &sband->channels[i], regd); 15001fa25e41SLuis R. Rodriguez } 15011fa25e41SLuis R. Rodriguez 15021fa25e41SLuis R. Rodriguez /* Used by drivers prior to wiphy registration */ 15031fa25e41SLuis R. Rodriguez void wiphy_apply_custom_regulatory(struct wiphy *wiphy, 15041fa25e41SLuis R. Rodriguez const struct ieee80211_regdomain *regd) 15051fa25e41SLuis R. Rodriguez { 15061fa25e41SLuis R. Rodriguez enum ieee80211_band band; 1507bbcf3f02SLuis R. Rodriguez unsigned int bands_set = 0; 1508ac46d48eSLuis R. Rodriguez 1509a2f73b6cSLuis R. Rodriguez WARN(!(wiphy->regulatory_flags & REGULATORY_CUSTOM_REG), 1510a2f73b6cSLuis R. Rodriguez "wiphy should have REGULATORY_CUSTOM_REG\n"); 1511a2f73b6cSLuis R. Rodriguez wiphy->regulatory_flags |= REGULATORY_CUSTOM_REG; 1512222ea581SLuis R. Rodriguez 15131fa25e41SLuis R. Rodriguez for (band = 0; band < IEEE80211_NUM_BANDS; band++) { 1514bbcf3f02SLuis R. Rodriguez if (!wiphy->bands[band]) 1515bbcf3f02SLuis R. Rodriguez continue; 1516fdc9d7b2SJohannes Berg handle_band_custom(wiphy, wiphy->bands[band], regd); 1517bbcf3f02SLuis R. Rodriguez bands_set++; 15181fa25e41SLuis R. Rodriguez } 1519bbcf3f02SLuis R. Rodriguez 1520bbcf3f02SLuis R. Rodriguez /* 1521bbcf3f02SLuis R. Rodriguez * no point in calling this if it won't have any effect 15221a919318SJohannes Berg * on your device's supported bands. 1523bbcf3f02SLuis R. Rodriguez */ 1524bbcf3f02SLuis R. Rodriguez WARN_ON(!bands_set); 15251fa25e41SLuis R. Rodriguez } 15261fa25e41SLuis R. Rodriguez EXPORT_SYMBOL(wiphy_apply_custom_regulatory); 15271fa25e41SLuis R. Rodriguez 1528b2e253cfSLuis R. Rodriguez static void reg_set_request_processed(void) 1529b2e253cfSLuis R. Rodriguez { 1530b2e253cfSLuis R. Rodriguez bool need_more_processing = false; 1531c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 1532b2e253cfSLuis R. Rodriguez 1533c492db37SJohannes Berg lr->processed = true; 1534b2e253cfSLuis R. Rodriguez 1535b2e253cfSLuis R. Rodriguez spin_lock(®_requests_lock); 1536b2e253cfSLuis R. Rodriguez if (!list_empty(®_requests_list)) 1537b2e253cfSLuis R. Rodriguez need_more_processing = true; 1538b2e253cfSLuis R. Rodriguez spin_unlock(®_requests_lock); 1539b2e253cfSLuis R. Rodriguez 1540c492db37SJohannes Berg if (lr->initiator == NL80211_REGDOM_SET_BY_USER) 1541fe20b39eSEliad Peller cancel_delayed_work(®_timeout); 1542a90c7a31SLuis R. Rodriguez 1543b2e253cfSLuis R. Rodriguez if (need_more_processing) 1544b2e253cfSLuis R. Rodriguez schedule_work(®_work); 1545b2e253cfSLuis R. Rodriguez } 1546b2e253cfSLuis R. Rodriguez 1547d1c96a9aSLuis R. Rodriguez /** 1548b3eb7f3fSLuis R. Rodriguez * reg_process_hint_core - process core regulatory requests 1549b3eb7f3fSLuis R. Rodriguez * @pending_request: a pending core regulatory request 1550b3eb7f3fSLuis R. Rodriguez * 1551b3eb7f3fSLuis R. Rodriguez * The wireless subsystem can use this function to process 1552b3eb7f3fSLuis R. Rodriguez * a regulatory request issued by the regulatory core. 1553b3eb7f3fSLuis R. Rodriguez * 1554b3eb7f3fSLuis R. Rodriguez * Returns one of the different reg request treatment values. 1555b3eb7f3fSLuis R. Rodriguez */ 1556b3eb7f3fSLuis R. Rodriguez static enum reg_request_treatment 1557b3eb7f3fSLuis R. Rodriguez reg_process_hint_core(struct regulatory_request *core_request) 1558b3eb7f3fSLuis R. Rodriguez { 1559b3eb7f3fSLuis R. Rodriguez 1560b3eb7f3fSLuis R. Rodriguez core_request->intersect = false; 1561b3eb7f3fSLuis R. Rodriguez core_request->processed = false; 15625ad6ef5eSLuis R. Rodriguez 156305f1a3eaSLuis R. Rodriguez reg_update_last_request(core_request); 1564b3eb7f3fSLuis R. Rodriguez 1565fe6631ffSLuis R. Rodriguez return reg_call_crda(core_request); 1566b3eb7f3fSLuis R. Rodriguez } 1567b3eb7f3fSLuis R. Rodriguez 15680d97a619SLuis R. Rodriguez static enum reg_request_treatment 15690d97a619SLuis R. Rodriguez __reg_process_hint_user(struct regulatory_request *user_request) 15700d97a619SLuis R. Rodriguez { 15710d97a619SLuis R. Rodriguez struct regulatory_request *lr = get_last_request(); 15720d97a619SLuis R. Rodriguez 15730d97a619SLuis R. Rodriguez if (reg_request_cell_base(user_request)) 15740d97a619SLuis R. Rodriguez return reg_ignore_cell_hint(user_request); 15750d97a619SLuis R. Rodriguez 15760d97a619SLuis R. Rodriguez if (reg_request_cell_base(lr)) 15770d97a619SLuis R. Rodriguez return REG_REQ_IGNORE; 15780d97a619SLuis R. Rodriguez 15790d97a619SLuis R. Rodriguez if (lr->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE) 15800d97a619SLuis R. Rodriguez return REG_REQ_INTERSECT; 15810d97a619SLuis R. Rodriguez /* 15820d97a619SLuis R. Rodriguez * If the user knows better the user should set the regdom 15830d97a619SLuis R. Rodriguez * to their country before the IE is picked up 15840d97a619SLuis R. Rodriguez */ 15850d97a619SLuis R. Rodriguez if (lr->initiator == NL80211_REGDOM_SET_BY_USER && 15860d97a619SLuis R. Rodriguez lr->intersect) 15870d97a619SLuis R. Rodriguez return REG_REQ_IGNORE; 15880d97a619SLuis R. Rodriguez /* 15890d97a619SLuis R. Rodriguez * Process user requests only after previous user/driver/core 15900d97a619SLuis R. Rodriguez * requests have been processed 15910d97a619SLuis R. Rodriguez */ 15920d97a619SLuis R. Rodriguez if ((lr->initiator == NL80211_REGDOM_SET_BY_CORE || 15930d97a619SLuis R. Rodriguez lr->initiator == NL80211_REGDOM_SET_BY_DRIVER || 15940d97a619SLuis R. Rodriguez lr->initiator == NL80211_REGDOM_SET_BY_USER) && 15950d97a619SLuis R. Rodriguez regdom_changes(lr->alpha2)) 15960d97a619SLuis R. Rodriguez return REG_REQ_IGNORE; 15970d97a619SLuis R. Rodriguez 15980d97a619SLuis R. Rodriguez if (!regdom_changes(user_request->alpha2)) 15990d97a619SLuis R. Rodriguez return REG_REQ_ALREADY_SET; 16000d97a619SLuis R. Rodriguez 16010d97a619SLuis R. Rodriguez return REG_REQ_OK; 16020d97a619SLuis R. Rodriguez } 16030d97a619SLuis R. Rodriguez 16040d97a619SLuis R. Rodriguez /** 16050d97a619SLuis R. Rodriguez * reg_process_hint_user - process user regulatory requests 16060d97a619SLuis R. Rodriguez * @user_request: a pending user regulatory request 16070d97a619SLuis R. Rodriguez * 16080d97a619SLuis R. Rodriguez * The wireless subsystem can use this function to process 16090d97a619SLuis R. Rodriguez * a regulatory request initiated by userspace. 16100d97a619SLuis R. Rodriguez * 16110d97a619SLuis R. Rodriguez * Returns one of the different reg request treatment values. 16120d97a619SLuis R. Rodriguez */ 16130d97a619SLuis R. Rodriguez static enum reg_request_treatment 16140d97a619SLuis R. Rodriguez reg_process_hint_user(struct regulatory_request *user_request) 16150d97a619SLuis R. Rodriguez { 16160d97a619SLuis R. Rodriguez enum reg_request_treatment treatment; 16170d97a619SLuis R. Rodriguez 16180d97a619SLuis R. Rodriguez treatment = __reg_process_hint_user(user_request); 16190d97a619SLuis R. Rodriguez if (treatment == REG_REQ_IGNORE || 16200d97a619SLuis R. Rodriguez treatment == REG_REQ_ALREADY_SET) { 16210d97a619SLuis R. Rodriguez kfree(user_request); 16220d97a619SLuis R. Rodriguez return treatment; 16230d97a619SLuis R. Rodriguez } 16240d97a619SLuis R. Rodriguez 16250d97a619SLuis R. Rodriguez user_request->intersect = treatment == REG_REQ_INTERSECT; 16260d97a619SLuis R. Rodriguez user_request->processed = false; 16275ad6ef5eSLuis R. Rodriguez 162805f1a3eaSLuis R. Rodriguez reg_update_last_request(user_request); 16290d97a619SLuis R. Rodriguez 16300d97a619SLuis R. Rodriguez user_alpha2[0] = user_request->alpha2[0]; 16310d97a619SLuis R. Rodriguez user_alpha2[1] = user_request->alpha2[1]; 16320d97a619SLuis R. Rodriguez 1633fe6631ffSLuis R. Rodriguez return reg_call_crda(user_request); 16340d97a619SLuis R. Rodriguez } 16350d97a619SLuis R. Rodriguez 163621636c7fSLuis R. Rodriguez static enum reg_request_treatment 163721636c7fSLuis R. Rodriguez __reg_process_hint_driver(struct regulatory_request *driver_request) 163821636c7fSLuis R. Rodriguez { 163921636c7fSLuis R. Rodriguez struct regulatory_request *lr = get_last_request(); 164021636c7fSLuis R. Rodriguez 164121636c7fSLuis R. Rodriguez if (lr->initiator == NL80211_REGDOM_SET_BY_CORE) { 164221636c7fSLuis R. Rodriguez if (regdom_changes(driver_request->alpha2)) 164321636c7fSLuis R. Rodriguez return REG_REQ_OK; 164421636c7fSLuis R. Rodriguez return REG_REQ_ALREADY_SET; 164521636c7fSLuis R. Rodriguez } 164621636c7fSLuis R. Rodriguez 164721636c7fSLuis R. Rodriguez /* 164821636c7fSLuis R. Rodriguez * This would happen if you unplug and plug your card 164921636c7fSLuis R. Rodriguez * back in or if you add a new device for which the previously 165021636c7fSLuis R. Rodriguez * loaded card also agrees on the regulatory domain. 165121636c7fSLuis R. Rodriguez */ 165221636c7fSLuis R. Rodriguez if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER && 165321636c7fSLuis R. Rodriguez !regdom_changes(driver_request->alpha2)) 165421636c7fSLuis R. Rodriguez return REG_REQ_ALREADY_SET; 165521636c7fSLuis R. Rodriguez 165621636c7fSLuis R. Rodriguez return REG_REQ_INTERSECT; 165721636c7fSLuis R. Rodriguez } 165821636c7fSLuis R. Rodriguez 165921636c7fSLuis R. Rodriguez /** 166021636c7fSLuis R. Rodriguez * reg_process_hint_driver - process driver regulatory requests 166121636c7fSLuis R. Rodriguez * @driver_request: a pending driver regulatory request 166221636c7fSLuis R. Rodriguez * 166321636c7fSLuis R. Rodriguez * The wireless subsystem can use this function to process 166421636c7fSLuis R. Rodriguez * a regulatory request issued by an 802.11 driver. 166521636c7fSLuis R. Rodriguez * 166621636c7fSLuis R. Rodriguez * Returns one of the different reg request treatment values. 166721636c7fSLuis R. Rodriguez */ 166821636c7fSLuis R. Rodriguez static enum reg_request_treatment 166921636c7fSLuis R. Rodriguez reg_process_hint_driver(struct wiphy *wiphy, 167021636c7fSLuis R. Rodriguez struct regulatory_request *driver_request) 167121636c7fSLuis R. Rodriguez { 167221636c7fSLuis R. Rodriguez const struct ieee80211_regdomain *regd; 167321636c7fSLuis R. Rodriguez enum reg_request_treatment treatment; 167421636c7fSLuis R. Rodriguez 167521636c7fSLuis R. Rodriguez treatment = __reg_process_hint_driver(driver_request); 167621636c7fSLuis R. Rodriguez 167721636c7fSLuis R. Rodriguez switch (treatment) { 167821636c7fSLuis R. Rodriguez case REG_REQ_OK: 167921636c7fSLuis R. Rodriguez break; 168021636c7fSLuis R. Rodriguez case REG_REQ_IGNORE: 168121636c7fSLuis R. Rodriguez kfree(driver_request); 168221636c7fSLuis R. Rodriguez return treatment; 168321636c7fSLuis R. Rodriguez case REG_REQ_INTERSECT: 168421636c7fSLuis R. Rodriguez /* fall through */ 168521636c7fSLuis R. Rodriguez case REG_REQ_ALREADY_SET: 168621636c7fSLuis R. Rodriguez regd = reg_copy_regd(get_cfg80211_regdom()); 168721636c7fSLuis R. Rodriguez if (IS_ERR(regd)) { 168821636c7fSLuis R. Rodriguez kfree(driver_request); 168921636c7fSLuis R. Rodriguez return REG_REQ_IGNORE; 169021636c7fSLuis R. Rodriguez } 169121636c7fSLuis R. Rodriguez rcu_assign_pointer(wiphy->regd, regd); 169221636c7fSLuis R. Rodriguez } 169321636c7fSLuis R. Rodriguez 169421636c7fSLuis R. Rodriguez 169521636c7fSLuis R. Rodriguez driver_request->intersect = treatment == REG_REQ_INTERSECT; 169621636c7fSLuis R. Rodriguez driver_request->processed = false; 16975ad6ef5eSLuis R. Rodriguez 169805f1a3eaSLuis R. Rodriguez reg_update_last_request(driver_request); 169921636c7fSLuis R. Rodriguez 170021636c7fSLuis R. Rodriguez /* 170121636c7fSLuis R. Rodriguez * Since CRDA will not be called in this case as we already 170221636c7fSLuis R. Rodriguez * have applied the requested regulatory domain before we just 170321636c7fSLuis R. Rodriguez * inform userspace we have processed the request 170421636c7fSLuis R. Rodriguez */ 170521636c7fSLuis R. Rodriguez if (treatment == REG_REQ_ALREADY_SET) { 170621636c7fSLuis R. Rodriguez nl80211_send_reg_change_event(driver_request); 170721636c7fSLuis R. Rodriguez reg_set_request_processed(); 170821636c7fSLuis R. Rodriguez return treatment; 170921636c7fSLuis R. Rodriguez } 171021636c7fSLuis R. Rodriguez 1711fe6631ffSLuis R. Rodriguez return reg_call_crda(driver_request); 171221636c7fSLuis R. Rodriguez } 171321636c7fSLuis R. Rodriguez 1714b23e7a9eSLuis R. Rodriguez static enum reg_request_treatment 1715b23e7a9eSLuis R. Rodriguez __reg_process_hint_country_ie(struct wiphy *wiphy, 1716b23e7a9eSLuis R. Rodriguez struct regulatory_request *country_ie_request) 1717b23e7a9eSLuis R. Rodriguez { 1718b23e7a9eSLuis R. Rodriguez struct wiphy *last_wiphy = NULL; 1719b23e7a9eSLuis R. Rodriguez struct regulatory_request *lr = get_last_request(); 1720b23e7a9eSLuis R. Rodriguez 1721b23e7a9eSLuis R. Rodriguez if (reg_request_cell_base(lr)) { 1722b23e7a9eSLuis R. Rodriguez /* Trust a Cell base station over the AP's country IE */ 1723b23e7a9eSLuis R. Rodriguez if (regdom_changes(country_ie_request->alpha2)) 1724b23e7a9eSLuis R. Rodriguez return REG_REQ_IGNORE; 1725b23e7a9eSLuis R. Rodriguez return REG_REQ_ALREADY_SET; 17262a901468SLuis R. Rodriguez } else { 17272a901468SLuis R. Rodriguez if (wiphy->regulatory_flags & REGULATORY_COUNTRY_IE_IGNORE) 17282a901468SLuis R. Rodriguez return REG_REQ_IGNORE; 1729b23e7a9eSLuis R. Rodriguez } 1730b23e7a9eSLuis R. Rodriguez 1731b23e7a9eSLuis R. Rodriguez if (unlikely(!is_an_alpha2(country_ie_request->alpha2))) 1732b23e7a9eSLuis R. Rodriguez return -EINVAL; 17332f1c6c57SLuis R. Rodriguez 17342f1c6c57SLuis R. Rodriguez if (lr->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE) 17352f1c6c57SLuis R. Rodriguez return REG_REQ_OK; 17362f1c6c57SLuis R. Rodriguez 17372f1c6c57SLuis R. Rodriguez last_wiphy = wiphy_idx_to_wiphy(lr->wiphy_idx); 17382f1c6c57SLuis R. Rodriguez 1739b23e7a9eSLuis R. Rodriguez if (last_wiphy != wiphy) { 1740b23e7a9eSLuis R. Rodriguez /* 1741b23e7a9eSLuis R. Rodriguez * Two cards with two APs claiming different 1742b23e7a9eSLuis R. Rodriguez * Country IE alpha2s. We could 1743b23e7a9eSLuis R. Rodriguez * intersect them, but that seems unlikely 1744b23e7a9eSLuis R. Rodriguez * to be correct. Reject second one for now. 1745b23e7a9eSLuis R. Rodriguez */ 1746b23e7a9eSLuis R. Rodriguez if (regdom_changes(country_ie_request->alpha2)) 1747b23e7a9eSLuis R. Rodriguez return REG_REQ_IGNORE; 1748b23e7a9eSLuis R. Rodriguez return REG_REQ_ALREADY_SET; 1749b23e7a9eSLuis R. Rodriguez } 1750b23e7a9eSLuis R. Rodriguez /* 1751b23e7a9eSLuis R. Rodriguez * Two consecutive Country IE hints on the same wiphy. 1752b23e7a9eSLuis R. Rodriguez * This should be picked up early by the driver/stack 1753b23e7a9eSLuis R. Rodriguez */ 1754b23e7a9eSLuis R. Rodriguez if (WARN_ON(regdom_changes(country_ie_request->alpha2))) 1755b23e7a9eSLuis R. Rodriguez return REG_REQ_OK; 1756b23e7a9eSLuis R. Rodriguez return REG_REQ_ALREADY_SET; 1757b23e7a9eSLuis R. Rodriguez } 1758b23e7a9eSLuis R. Rodriguez 1759b3eb7f3fSLuis R. Rodriguez /** 1760b23e7a9eSLuis R. Rodriguez * reg_process_hint_country_ie - process regulatory requests from country IEs 1761b23e7a9eSLuis R. Rodriguez * @country_ie_request: a regulatory request from a country IE 1762d1c96a9aSLuis R. Rodriguez * 1763b23e7a9eSLuis R. Rodriguez * The wireless subsystem can use this function to process 1764b23e7a9eSLuis R. Rodriguez * a regulatory request issued by a country Information Element. 1765d1c96a9aSLuis R. Rodriguez * 17662f92212bSJohannes Berg * Returns one of the different reg request treatment values. 1767d1c96a9aSLuis R. Rodriguez */ 17682f92212bSJohannes Berg static enum reg_request_treatment 1769b23e7a9eSLuis R. Rodriguez reg_process_hint_country_ie(struct wiphy *wiphy, 1770b23e7a9eSLuis R. Rodriguez struct regulatory_request *country_ie_request) 1771b2e1b302SLuis R. Rodriguez { 17722f92212bSJohannes Berg enum reg_request_treatment treatment; 1773b2e1b302SLuis R. Rodriguez 1774b23e7a9eSLuis R. Rodriguez treatment = __reg_process_hint_country_ie(wiphy, country_ie_request); 1775761cf7ecSLuis R. Rodriguez 17762f92212bSJohannes Berg switch (treatment) { 17772f92212bSJohannes Berg case REG_REQ_OK: 17782f92212bSJohannes Berg break; 1779b23e7a9eSLuis R. Rodriguez case REG_REQ_IGNORE: 1780b23e7a9eSLuis R. Rodriguez /* fall through */ 1781b23e7a9eSLuis R. Rodriguez case REG_REQ_ALREADY_SET: 1782b23e7a9eSLuis R. Rodriguez kfree(country_ie_request); 1783b23e7a9eSLuis R. Rodriguez return treatment; 1784b23e7a9eSLuis R. Rodriguez case REG_REQ_INTERSECT: 1785b23e7a9eSLuis R. Rodriguez kfree(country_ie_request); 1786fb1fc7adSLuis R. Rodriguez /* 1787b23e7a9eSLuis R. Rodriguez * This doesn't happen yet, not sure we 1788b23e7a9eSLuis R. Rodriguez * ever want to support it for this case. 1789fb1fc7adSLuis R. Rodriguez */ 1790b23e7a9eSLuis R. Rodriguez WARN_ONCE(1, "Unexpected intersection for country IEs"); 17912f92212bSJohannes Berg return REG_REQ_IGNORE; 1792d951c1ddSLuis R. Rodriguez } 1793b2e1b302SLuis R. Rodriguez 1794b23e7a9eSLuis R. Rodriguez country_ie_request->intersect = false; 1795b23e7a9eSLuis R. Rodriguez country_ie_request->processed = false; 17965ad6ef5eSLuis R. Rodriguez 179705f1a3eaSLuis R. Rodriguez reg_update_last_request(country_ie_request); 1798d951c1ddSLuis R. Rodriguez 1799fe6631ffSLuis R. Rodriguez return reg_call_crda(country_ie_request); 1800b2e1b302SLuis R. Rodriguez } 1801b2e1b302SLuis R. Rodriguez 180230a548c7SLuis R. Rodriguez /* This processes *all* regulatory hints */ 18031daa37c7SLuis R. Rodriguez static void reg_process_hint(struct regulatory_request *reg_request) 1804fe33eb39SLuis R. Rodriguez { 1805fe33eb39SLuis R. Rodriguez struct wiphy *wiphy = NULL; 1806b3eb7f3fSLuis R. Rodriguez enum reg_request_treatment treatment; 1807fe33eb39SLuis R. Rodriguez 1808f4173766SJohannes Berg if (reg_request->wiphy_idx != WIPHY_IDX_INVALID) 1809fe33eb39SLuis R. Rodriguez wiphy = wiphy_idx_to_wiphy(reg_request->wiphy_idx); 1810fe33eb39SLuis R. Rodriguez 1811b3eb7f3fSLuis R. Rodriguez switch (reg_request->initiator) { 1812b3eb7f3fSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_CORE: 1813b3eb7f3fSLuis R. Rodriguez reg_process_hint_core(reg_request); 1814b3eb7f3fSLuis R. Rodriguez return; 1815b3eb7f3fSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_USER: 18160d97a619SLuis R. Rodriguez treatment = reg_process_hint_user(reg_request); 181750c11eb9SInbal Hacohen if (treatment == REG_REQ_IGNORE || 18180d97a619SLuis R. Rodriguez treatment == REG_REQ_ALREADY_SET) 18190d97a619SLuis R. Rodriguez return; 1820845f3351SShaibal Dutta queue_delayed_work(system_power_efficient_wq, 1821845f3351SShaibal Dutta ®_timeout, msecs_to_jiffies(3142)); 18220d97a619SLuis R. Rodriguez return; 1823b3eb7f3fSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_DRIVER: 1824772f0389SIlan Peer if (!wiphy) 1825772f0389SIlan Peer goto out_free; 182621636c7fSLuis R. Rodriguez treatment = reg_process_hint_driver(wiphy, reg_request); 182721636c7fSLuis R. Rodriguez break; 1828b3eb7f3fSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_COUNTRY_IE: 1829772f0389SIlan Peer if (!wiphy) 1830772f0389SIlan Peer goto out_free; 1831b23e7a9eSLuis R. Rodriguez treatment = reg_process_hint_country_ie(wiphy, reg_request); 1832b3eb7f3fSLuis R. Rodriguez break; 1833b3eb7f3fSLuis R. Rodriguez default: 1834b3eb7f3fSLuis R. Rodriguez WARN(1, "invalid initiator %d\n", reg_request->initiator); 1835772f0389SIlan Peer goto out_free; 1836b3eb7f3fSLuis R. Rodriguez } 1837b3eb7f3fSLuis R. Rodriguez 1838fe33eb39SLuis R. Rodriguez /* This is required so that the orig_* parameters are saved */ 1839b23e7a9eSLuis R. Rodriguez if (treatment == REG_REQ_ALREADY_SET && wiphy && 1840a2f73b6cSLuis R. Rodriguez wiphy->regulatory_flags & REGULATORY_STRICT_REG) 18411daa37c7SLuis R. Rodriguez wiphy_update_regulatory(wiphy, reg_request->initiator); 1842772f0389SIlan Peer 1843772f0389SIlan Peer return; 1844772f0389SIlan Peer 1845772f0389SIlan Peer out_free: 1846772f0389SIlan Peer kfree(reg_request); 1847fe33eb39SLuis R. Rodriguez } 1848fe33eb39SLuis R. Rodriguez 1849b2e253cfSLuis R. Rodriguez /* 1850b2e253cfSLuis R. Rodriguez * Processes regulatory hints, this is all the NL80211_REGDOM_SET_BY_* 1851b2e253cfSLuis R. Rodriguez * Regulatory hints come on a first come first serve basis and we 1852b2e253cfSLuis R. Rodriguez * must process each one atomically. 1853b2e253cfSLuis R. Rodriguez */ 1854fe33eb39SLuis R. Rodriguez static void reg_process_pending_hints(void) 1855fe33eb39SLuis R. Rodriguez { 1856c492db37SJohannes Berg struct regulatory_request *reg_request, *lr; 1857fe33eb39SLuis R. Rodriguez 1858c492db37SJohannes Berg lr = get_last_request(); 1859b0e2880bSLuis R. Rodriguez 1860b2e253cfSLuis R. Rodriguez /* When last_request->processed becomes true this will be rescheduled */ 1861c492db37SJohannes Berg if (lr && !lr->processed) { 18621a919318SJohannes Berg REG_DBG_PRINT("Pending regulatory request, waiting for it to be processed...\n"); 18635fe231e8SJohannes Berg return; 1864b2e253cfSLuis R. Rodriguez } 1865b2e253cfSLuis R. Rodriguez 1866fe33eb39SLuis R. Rodriguez spin_lock(®_requests_lock); 1867b2e253cfSLuis R. Rodriguez 1868b2e253cfSLuis R. Rodriguez if (list_empty(®_requests_list)) { 1869b2e253cfSLuis R. Rodriguez spin_unlock(®_requests_lock); 18705fe231e8SJohannes Berg return; 1871b2e253cfSLuis R. Rodriguez } 1872b2e253cfSLuis R. Rodriguez 1873fe33eb39SLuis R. Rodriguez reg_request = list_first_entry(®_requests_list, 1874fe33eb39SLuis R. Rodriguez struct regulatory_request, 1875fe33eb39SLuis R. Rodriguez list); 1876fe33eb39SLuis R. Rodriguez list_del_init(®_request->list); 1877fe33eb39SLuis R. Rodriguez 1878d951c1ddSLuis R. Rodriguez spin_unlock(®_requests_lock); 1879b0e2880bSLuis R. Rodriguez 18801daa37c7SLuis R. Rodriguez reg_process_hint(reg_request); 1881fe33eb39SLuis R. Rodriguez } 1882fe33eb39SLuis R. Rodriguez 1883e38f8a7aSLuis R. Rodriguez /* Processes beacon hints -- this has nothing to do with country IEs */ 1884e38f8a7aSLuis R. Rodriguez static void reg_process_pending_beacon_hints(void) 1885e38f8a7aSLuis R. Rodriguez { 188679c97e97SJohannes Berg struct cfg80211_registered_device *rdev; 1887e38f8a7aSLuis R. Rodriguez struct reg_beacon *pending_beacon, *tmp; 1888e38f8a7aSLuis R. Rodriguez 1889e38f8a7aSLuis R. Rodriguez /* This goes through the _pending_ beacon list */ 1890e38f8a7aSLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 1891e38f8a7aSLuis R. Rodriguez 1892e38f8a7aSLuis R. Rodriguez list_for_each_entry_safe(pending_beacon, tmp, 1893e38f8a7aSLuis R. Rodriguez ®_pending_beacons, list) { 1894e38f8a7aSLuis R. Rodriguez list_del_init(&pending_beacon->list); 1895e38f8a7aSLuis R. Rodriguez 1896e38f8a7aSLuis R. Rodriguez /* Applies the beacon hint to current wiphys */ 189779c97e97SJohannes Berg list_for_each_entry(rdev, &cfg80211_rdev_list, list) 189879c97e97SJohannes Berg wiphy_update_new_beacon(&rdev->wiphy, pending_beacon); 1899e38f8a7aSLuis R. Rodriguez 1900e38f8a7aSLuis R. Rodriguez /* Remembers the beacon hint for new wiphys or reg changes */ 1901e38f8a7aSLuis R. Rodriguez list_add_tail(&pending_beacon->list, ®_beacon_list); 1902e38f8a7aSLuis R. Rodriguez } 1903e38f8a7aSLuis R. Rodriguez 1904e38f8a7aSLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 1905e38f8a7aSLuis R. Rodriguez } 1906e38f8a7aSLuis R. Rodriguez 1907fe33eb39SLuis R. Rodriguez static void reg_todo(struct work_struct *work) 1908fe33eb39SLuis R. Rodriguez { 19095fe231e8SJohannes Berg rtnl_lock(); 1910fe33eb39SLuis R. Rodriguez reg_process_pending_hints(); 1911e38f8a7aSLuis R. Rodriguez reg_process_pending_beacon_hints(); 19125fe231e8SJohannes Berg rtnl_unlock(); 1913fe33eb39SLuis R. Rodriguez } 1914fe33eb39SLuis R. Rodriguez 1915fe33eb39SLuis R. Rodriguez static void queue_regulatory_request(struct regulatory_request *request) 1916fe33eb39SLuis R. Rodriguez { 1917c61029c7SJohn W. Linville request->alpha2[0] = toupper(request->alpha2[0]); 1918c61029c7SJohn W. Linville request->alpha2[1] = toupper(request->alpha2[1]); 1919c61029c7SJohn W. Linville 1920fe33eb39SLuis R. Rodriguez spin_lock(®_requests_lock); 1921fe33eb39SLuis R. Rodriguez list_add_tail(&request->list, ®_requests_list); 1922fe33eb39SLuis R. Rodriguez spin_unlock(®_requests_lock); 1923fe33eb39SLuis R. Rodriguez 1924fe33eb39SLuis R. Rodriguez schedule_work(®_work); 1925fe33eb39SLuis R. Rodriguez } 1926fe33eb39SLuis R. Rodriguez 192709d989d1SLuis R. Rodriguez /* 192809d989d1SLuis R. Rodriguez * Core regulatory hint -- happens during cfg80211_init() 192909d989d1SLuis R. Rodriguez * and when we restore regulatory settings. 193009d989d1SLuis R. Rodriguez */ 1931ba25c141SLuis R. Rodriguez static int regulatory_hint_core(const char *alpha2) 1932ba25c141SLuis R. Rodriguez { 1933ba25c141SLuis R. Rodriguez struct regulatory_request *request; 1934ba25c141SLuis R. Rodriguez 19351a919318SJohannes Berg request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL); 1936ba25c141SLuis R. Rodriguez if (!request) 1937ba25c141SLuis R. Rodriguez return -ENOMEM; 1938ba25c141SLuis R. Rodriguez 1939ba25c141SLuis R. Rodriguez request->alpha2[0] = alpha2[0]; 1940ba25c141SLuis R. Rodriguez request->alpha2[1] = alpha2[1]; 19417db90f4aSLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_CORE; 1942ba25c141SLuis R. Rodriguez 194331e99729SLuis R. Rodriguez queue_regulatory_request(request); 19445078b2e3SLuis R. Rodriguez 1945fe33eb39SLuis R. Rodriguez return 0; 1946ba25c141SLuis R. Rodriguez } 1947ba25c141SLuis R. Rodriguez 1948fe33eb39SLuis R. Rodriguez /* User hints */ 194957b5ce07SLuis R. Rodriguez int regulatory_hint_user(const char *alpha2, 195057b5ce07SLuis R. Rodriguez enum nl80211_user_reg_hint_type user_reg_hint_type) 1951b2e1b302SLuis R. Rodriguez { 1952fe33eb39SLuis R. Rodriguez struct regulatory_request *request; 1953fe33eb39SLuis R. Rodriguez 1954fdc9d7b2SJohannes Berg if (WARN_ON(!alpha2)) 1955fdc9d7b2SJohannes Berg return -EINVAL; 1956b2e1b302SLuis R. Rodriguez 1957fe33eb39SLuis R. Rodriguez request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL); 1958fe33eb39SLuis R. Rodriguez if (!request) 1959fe33eb39SLuis R. Rodriguez return -ENOMEM; 1960fe33eb39SLuis R. Rodriguez 1961f4173766SJohannes Berg request->wiphy_idx = WIPHY_IDX_INVALID; 1962fe33eb39SLuis R. Rodriguez request->alpha2[0] = alpha2[0]; 1963fe33eb39SLuis R. Rodriguez request->alpha2[1] = alpha2[1]; 1964e12822e1SLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_USER; 196557b5ce07SLuis R. Rodriguez request->user_reg_hint_type = user_reg_hint_type; 1966fe33eb39SLuis R. Rodriguez 1967fe33eb39SLuis R. Rodriguez queue_regulatory_request(request); 1968fe33eb39SLuis R. Rodriguez 1969fe33eb39SLuis R. Rodriguez return 0; 1970fe33eb39SLuis R. Rodriguez } 1971fe33eb39SLuis R. Rodriguez 1972fe33eb39SLuis R. Rodriguez /* Driver hints */ 1973fe33eb39SLuis R. Rodriguez int regulatory_hint(struct wiphy *wiphy, const char *alpha2) 1974fe33eb39SLuis R. Rodriguez { 1975fe33eb39SLuis R. Rodriguez struct regulatory_request *request; 1976fe33eb39SLuis R. Rodriguez 1977fdc9d7b2SJohannes Berg if (WARN_ON(!alpha2 || !wiphy)) 1978fdc9d7b2SJohannes Berg return -EINVAL; 1979fe33eb39SLuis R. Rodriguez 19804f7b9140SLuis R. Rodriguez wiphy->regulatory_flags &= ~REGULATORY_CUSTOM_REG; 19814f7b9140SLuis R. Rodriguez 1982fe33eb39SLuis R. Rodriguez request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL); 1983fe33eb39SLuis R. Rodriguez if (!request) 1984fe33eb39SLuis R. Rodriguez return -ENOMEM; 1985fe33eb39SLuis R. Rodriguez 1986fe33eb39SLuis R. Rodriguez request->wiphy_idx = get_wiphy_idx(wiphy); 1987fe33eb39SLuis R. Rodriguez 1988fe33eb39SLuis R. Rodriguez request->alpha2[0] = alpha2[0]; 1989fe33eb39SLuis R. Rodriguez request->alpha2[1] = alpha2[1]; 19907db90f4aSLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_DRIVER; 1991fe33eb39SLuis R. Rodriguez 1992fe33eb39SLuis R. Rodriguez queue_regulatory_request(request); 1993fe33eb39SLuis R. Rodriguez 1994fe33eb39SLuis R. Rodriguez return 0; 1995b2e1b302SLuis R. Rodriguez } 1996b2e1b302SLuis R. Rodriguez EXPORT_SYMBOL(regulatory_hint); 1997b2e1b302SLuis R. Rodriguez 1998789fd033SLuis R. Rodriguez void regulatory_hint_country_ie(struct wiphy *wiphy, enum ieee80211_band band, 19991a919318SJohannes Berg const u8 *country_ie, u8 country_ie_len) 20003f2355cbSLuis R. Rodriguez { 20013f2355cbSLuis R. Rodriguez char alpha2[2]; 20023f2355cbSLuis R. Rodriguez enum environment_cap env = ENVIRON_ANY; 2003db2424c5SJohannes Berg struct regulatory_request *request = NULL, *lr; 2004d335fe63SLuis R. Rodriguez 20053f2355cbSLuis R. Rodriguez /* IE len must be evenly divisible by 2 */ 20063f2355cbSLuis R. Rodriguez if (country_ie_len & 0x01) 2007db2424c5SJohannes Berg return; 20083f2355cbSLuis R. Rodriguez 20093f2355cbSLuis R. Rodriguez if (country_ie_len < IEEE80211_COUNTRY_IE_MIN_LEN) 2010db2424c5SJohannes Berg return; 2011db2424c5SJohannes Berg 2012db2424c5SJohannes Berg request = kzalloc(sizeof(*request), GFP_KERNEL); 2013db2424c5SJohannes Berg if (!request) 2014db2424c5SJohannes Berg return; 20153f2355cbSLuis R. Rodriguez 20163f2355cbSLuis R. Rodriguez alpha2[0] = country_ie[0]; 20173f2355cbSLuis R. Rodriguez alpha2[1] = country_ie[1]; 20183f2355cbSLuis R. Rodriguez 20193f2355cbSLuis R. Rodriguez if (country_ie[2] == 'I') 20203f2355cbSLuis R. Rodriguez env = ENVIRON_INDOOR; 20213f2355cbSLuis R. Rodriguez else if (country_ie[2] == 'O') 20223f2355cbSLuis R. Rodriguez env = ENVIRON_OUTDOOR; 20233f2355cbSLuis R. Rodriguez 2024db2424c5SJohannes Berg rcu_read_lock(); 2025db2424c5SJohannes Berg lr = get_last_request(); 2026db2424c5SJohannes Berg 2027db2424c5SJohannes Berg if (unlikely(!lr)) 2028db2424c5SJohannes Berg goto out; 2029db2424c5SJohannes Berg 2030fb1fc7adSLuis R. Rodriguez /* 20318b19e6caSLuis R. Rodriguez * We will run this only upon a successful connection on cfg80211. 20324b44c8bcSLuis R. Rodriguez * We leave conflict resolution to the workqueue, where can hold 20335fe231e8SJohannes Berg * the RTNL. 2034fb1fc7adSLuis R. Rodriguez */ 2035c492db37SJohannes Berg if (lr->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE && 2036c492db37SJohannes Berg lr->wiphy_idx != WIPHY_IDX_INVALID) 20373f2355cbSLuis R. Rodriguez goto out; 20383f2355cbSLuis R. Rodriguez 2039fe33eb39SLuis R. Rodriguez request->wiphy_idx = get_wiphy_idx(wiphy); 20404f366c5dSJohn W. Linville request->alpha2[0] = alpha2[0]; 20414f366c5dSJohn W. Linville request->alpha2[1] = alpha2[1]; 20427db90f4aSLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_COUNTRY_IE; 2043fe33eb39SLuis R. Rodriguez request->country_ie_env = env; 20443f2355cbSLuis R. Rodriguez 2045fe33eb39SLuis R. Rodriguez queue_regulatory_request(request); 2046db2424c5SJohannes Berg request = NULL; 20473f2355cbSLuis R. Rodriguez out: 2048db2424c5SJohannes Berg kfree(request); 2049db2424c5SJohannes Berg rcu_read_unlock(); 20503f2355cbSLuis R. Rodriguez } 2051b2e1b302SLuis R. Rodriguez 205209d989d1SLuis R. Rodriguez static void restore_alpha2(char *alpha2, bool reset_user) 205309d989d1SLuis R. Rodriguez { 205409d989d1SLuis R. Rodriguez /* indicates there is no alpha2 to consider for restoration */ 205509d989d1SLuis R. Rodriguez alpha2[0] = '9'; 205609d989d1SLuis R. Rodriguez alpha2[1] = '7'; 205709d989d1SLuis R. Rodriguez 205809d989d1SLuis R. Rodriguez /* The user setting has precedence over the module parameter */ 205909d989d1SLuis R. Rodriguez if (is_user_regdom_saved()) { 206009d989d1SLuis R. Rodriguez /* Unless we're asked to ignore it and reset it */ 206109d989d1SLuis R. Rodriguez if (reset_user) { 20621a919318SJohannes Berg REG_DBG_PRINT("Restoring regulatory settings including user preference\n"); 206309d989d1SLuis R. Rodriguez user_alpha2[0] = '9'; 206409d989d1SLuis R. Rodriguez user_alpha2[1] = '7'; 206509d989d1SLuis R. Rodriguez 206609d989d1SLuis R. Rodriguez /* 206709d989d1SLuis R. Rodriguez * If we're ignoring user settings, we still need to 206809d989d1SLuis R. Rodriguez * check the module parameter to ensure we put things 206909d989d1SLuis R. Rodriguez * back as they were for a full restore. 207009d989d1SLuis R. Rodriguez */ 207109d989d1SLuis R. Rodriguez if (!is_world_regdom(ieee80211_regdom)) { 20721a919318SJohannes Berg REG_DBG_PRINT("Keeping preference on module parameter ieee80211_regdom: %c%c\n", 20731a919318SJohannes Berg ieee80211_regdom[0], ieee80211_regdom[1]); 207409d989d1SLuis R. Rodriguez alpha2[0] = ieee80211_regdom[0]; 207509d989d1SLuis R. Rodriguez alpha2[1] = ieee80211_regdom[1]; 207609d989d1SLuis R. Rodriguez } 207709d989d1SLuis R. Rodriguez } else { 20781a919318SJohannes Berg REG_DBG_PRINT("Restoring regulatory settings while preserving user preference for: %c%c\n", 20791a919318SJohannes Berg user_alpha2[0], user_alpha2[1]); 208009d989d1SLuis R. Rodriguez alpha2[0] = user_alpha2[0]; 208109d989d1SLuis R. Rodriguez alpha2[1] = user_alpha2[1]; 208209d989d1SLuis R. Rodriguez } 208309d989d1SLuis R. Rodriguez } else if (!is_world_regdom(ieee80211_regdom)) { 20841a919318SJohannes Berg REG_DBG_PRINT("Keeping preference on module parameter ieee80211_regdom: %c%c\n", 20851a919318SJohannes Berg ieee80211_regdom[0], ieee80211_regdom[1]); 208609d989d1SLuis R. Rodriguez alpha2[0] = ieee80211_regdom[0]; 208709d989d1SLuis R. Rodriguez alpha2[1] = ieee80211_regdom[1]; 208809d989d1SLuis R. Rodriguez } else 2089d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Restoring regulatory settings\n"); 209009d989d1SLuis R. Rodriguez } 209109d989d1SLuis R. Rodriguez 20925ce543d1SRajkumar Manoharan static void restore_custom_reg_settings(struct wiphy *wiphy) 20935ce543d1SRajkumar Manoharan { 20945ce543d1SRajkumar Manoharan struct ieee80211_supported_band *sband; 20955ce543d1SRajkumar Manoharan enum ieee80211_band band; 20965ce543d1SRajkumar Manoharan struct ieee80211_channel *chan; 20975ce543d1SRajkumar Manoharan int i; 20985ce543d1SRajkumar Manoharan 20995ce543d1SRajkumar Manoharan for (band = 0; band < IEEE80211_NUM_BANDS; band++) { 21005ce543d1SRajkumar Manoharan sband = wiphy->bands[band]; 21015ce543d1SRajkumar Manoharan if (!sband) 21025ce543d1SRajkumar Manoharan continue; 21035ce543d1SRajkumar Manoharan for (i = 0; i < sband->n_channels; i++) { 21045ce543d1SRajkumar Manoharan chan = &sband->channels[i]; 21055ce543d1SRajkumar Manoharan chan->flags = chan->orig_flags; 21065ce543d1SRajkumar Manoharan chan->max_antenna_gain = chan->orig_mag; 21075ce543d1SRajkumar Manoharan chan->max_power = chan->orig_mpwr; 2108899852afSPaul Stewart chan->beacon_found = false; 21095ce543d1SRajkumar Manoharan } 21105ce543d1SRajkumar Manoharan } 21115ce543d1SRajkumar Manoharan } 21125ce543d1SRajkumar Manoharan 211309d989d1SLuis R. Rodriguez /* 211409d989d1SLuis R. Rodriguez * Restoring regulatory settings involves ingoring any 211509d989d1SLuis R. Rodriguez * possibly stale country IE information and user regulatory 211609d989d1SLuis R. Rodriguez * settings if so desired, this includes any beacon hints 211709d989d1SLuis R. Rodriguez * learned as we could have traveled outside to another country 211809d989d1SLuis R. Rodriguez * after disconnection. To restore regulatory settings we do 211909d989d1SLuis R. Rodriguez * exactly what we did at bootup: 212009d989d1SLuis R. Rodriguez * 212109d989d1SLuis R. Rodriguez * - send a core regulatory hint 212209d989d1SLuis R. Rodriguez * - send a user regulatory hint if applicable 212309d989d1SLuis R. Rodriguez * 212409d989d1SLuis R. Rodriguez * Device drivers that send a regulatory hint for a specific country 212509d989d1SLuis R. Rodriguez * keep their own regulatory domain on wiphy->regd so that does does 212609d989d1SLuis R. Rodriguez * not need to be remembered. 212709d989d1SLuis R. Rodriguez */ 212809d989d1SLuis R. Rodriguez static void restore_regulatory_settings(bool reset_user) 212909d989d1SLuis R. Rodriguez { 213009d989d1SLuis R. Rodriguez char alpha2[2]; 2131cee0bec5SDmitry Shmidt char world_alpha2[2]; 213209d989d1SLuis R. Rodriguez struct reg_beacon *reg_beacon, *btmp; 213314609555SLuis R. Rodriguez struct regulatory_request *reg_request, *tmp; 213414609555SLuis R. Rodriguez LIST_HEAD(tmp_reg_req_list); 21355ce543d1SRajkumar Manoharan struct cfg80211_registered_device *rdev; 213609d989d1SLuis R. Rodriguez 21375fe231e8SJohannes Berg ASSERT_RTNL(); 21385fe231e8SJohannes Berg 21392d319867SJohannes Berg reset_regdomains(true, &world_regdom); 214009d989d1SLuis R. Rodriguez restore_alpha2(alpha2, reset_user); 214109d989d1SLuis R. Rodriguez 214214609555SLuis R. Rodriguez /* 214314609555SLuis R. Rodriguez * If there's any pending requests we simply 214414609555SLuis R. Rodriguez * stash them to a temporary pending queue and 214514609555SLuis R. Rodriguez * add then after we've restored regulatory 214614609555SLuis R. Rodriguez * settings. 214714609555SLuis R. Rodriguez */ 214814609555SLuis R. Rodriguez spin_lock(®_requests_lock); 2149fea9bcedSJohannes Berg list_for_each_entry_safe(reg_request, tmp, ®_requests_list, list) { 2150fea9bcedSJohannes Berg if (reg_request->initiator != NL80211_REGDOM_SET_BY_USER) 215114609555SLuis R. Rodriguez continue; 215200a9ac4cSWei Yongjun list_move_tail(®_request->list, &tmp_reg_req_list); 215314609555SLuis R. Rodriguez } 215414609555SLuis R. Rodriguez spin_unlock(®_requests_lock); 215514609555SLuis R. Rodriguez 215609d989d1SLuis R. Rodriguez /* Clear beacon hints */ 215709d989d1SLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 2158fea9bcedSJohannes Berg list_for_each_entry_safe(reg_beacon, btmp, ®_pending_beacons, list) { 215909d989d1SLuis R. Rodriguez list_del(®_beacon->list); 216009d989d1SLuis R. Rodriguez kfree(reg_beacon); 216109d989d1SLuis R. Rodriguez } 216209d989d1SLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 216309d989d1SLuis R. Rodriguez 2164fea9bcedSJohannes Berg list_for_each_entry_safe(reg_beacon, btmp, ®_beacon_list, list) { 216509d989d1SLuis R. Rodriguez list_del(®_beacon->list); 216609d989d1SLuis R. Rodriguez kfree(reg_beacon); 216709d989d1SLuis R. Rodriguez } 216809d989d1SLuis R. Rodriguez 216909d989d1SLuis R. Rodriguez /* First restore to the basic regulatory settings */ 2170379b82f4SJohannes Berg world_alpha2[0] = cfg80211_world_regdom->alpha2[0]; 2171379b82f4SJohannes Berg world_alpha2[1] = cfg80211_world_regdom->alpha2[1]; 217209d989d1SLuis R. Rodriguez 21735ce543d1SRajkumar Manoharan list_for_each_entry(rdev, &cfg80211_rdev_list, list) { 2174a2f73b6cSLuis R. Rodriguez if (rdev->wiphy.regulatory_flags & REGULATORY_CUSTOM_REG) 21755ce543d1SRajkumar Manoharan restore_custom_reg_settings(&rdev->wiphy); 21765ce543d1SRajkumar Manoharan } 21775ce543d1SRajkumar Manoharan 2178cee0bec5SDmitry Shmidt regulatory_hint_core(world_alpha2); 217909d989d1SLuis R. Rodriguez 218009d989d1SLuis R. Rodriguez /* 218109d989d1SLuis R. Rodriguez * This restores the ieee80211_regdom module parameter 218209d989d1SLuis R. Rodriguez * preference or the last user requested regulatory 218309d989d1SLuis R. Rodriguez * settings, user regulatory settings takes precedence. 218409d989d1SLuis R. Rodriguez */ 218509d989d1SLuis R. Rodriguez if (is_an_alpha2(alpha2)) 218657b5ce07SLuis R. Rodriguez regulatory_hint_user(user_alpha2, NL80211_USER_REG_HINT_USER); 218709d989d1SLuis R. Rodriguez 218814609555SLuis R. Rodriguez spin_lock(®_requests_lock); 218911cff96cSJohannes Berg list_splice_tail_init(&tmp_reg_req_list, ®_requests_list); 219014609555SLuis R. Rodriguez spin_unlock(®_requests_lock); 219114609555SLuis R. Rodriguez 219214609555SLuis R. Rodriguez REG_DBG_PRINT("Kicking the queue\n"); 219314609555SLuis R. Rodriguez 219414609555SLuis R. Rodriguez schedule_work(®_work); 219514609555SLuis R. Rodriguez } 219609d989d1SLuis R. Rodriguez 219709d989d1SLuis R. Rodriguez void regulatory_hint_disconnect(void) 219809d989d1SLuis R. Rodriguez { 21991a919318SJohannes Berg REG_DBG_PRINT("All devices are disconnected, going to restore regulatory settings\n"); 220009d989d1SLuis R. Rodriguez restore_regulatory_settings(false); 220109d989d1SLuis R. Rodriguez } 220209d989d1SLuis R. Rodriguez 2203e38f8a7aSLuis R. Rodriguez static bool freq_is_chan_12_13_14(u16 freq) 2204e38f8a7aSLuis R. Rodriguez { 220559eb21a6SBruno Randolf if (freq == ieee80211_channel_to_frequency(12, IEEE80211_BAND_2GHZ) || 220659eb21a6SBruno Randolf freq == ieee80211_channel_to_frequency(13, IEEE80211_BAND_2GHZ) || 220759eb21a6SBruno Randolf freq == ieee80211_channel_to_frequency(14, IEEE80211_BAND_2GHZ)) 2208e38f8a7aSLuis R. Rodriguez return true; 2209e38f8a7aSLuis R. Rodriguez return false; 2210e38f8a7aSLuis R. Rodriguez } 2211e38f8a7aSLuis R. Rodriguez 22123ebfa6e7SLuis R. Rodriguez static bool pending_reg_beacon(struct ieee80211_channel *beacon_chan) 22133ebfa6e7SLuis R. Rodriguez { 22143ebfa6e7SLuis R. Rodriguez struct reg_beacon *pending_beacon; 22153ebfa6e7SLuis R. Rodriguez 22163ebfa6e7SLuis R. Rodriguez list_for_each_entry(pending_beacon, ®_pending_beacons, list) 22173ebfa6e7SLuis R. Rodriguez if (beacon_chan->center_freq == 22183ebfa6e7SLuis R. Rodriguez pending_beacon->chan.center_freq) 22193ebfa6e7SLuis R. Rodriguez return true; 22203ebfa6e7SLuis R. Rodriguez return false; 22213ebfa6e7SLuis R. Rodriguez } 22223ebfa6e7SLuis R. Rodriguez 2223e38f8a7aSLuis R. Rodriguez int regulatory_hint_found_beacon(struct wiphy *wiphy, 2224e38f8a7aSLuis R. Rodriguez struct ieee80211_channel *beacon_chan, 2225e38f8a7aSLuis R. Rodriguez gfp_t gfp) 2226e38f8a7aSLuis R. Rodriguez { 2227e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon; 22283ebfa6e7SLuis R. Rodriguez bool processing; 2229e38f8a7aSLuis R. Rodriguez 22301a919318SJohannes Berg if (beacon_chan->beacon_found || 22311a919318SJohannes Berg beacon_chan->flags & IEEE80211_CHAN_RADAR || 2232e38f8a7aSLuis R. Rodriguez (beacon_chan->band == IEEE80211_BAND_2GHZ && 22331a919318SJohannes Berg !freq_is_chan_12_13_14(beacon_chan->center_freq))) 2234e38f8a7aSLuis R. Rodriguez return 0; 2235e38f8a7aSLuis R. Rodriguez 22363ebfa6e7SLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 22373ebfa6e7SLuis R. Rodriguez processing = pending_reg_beacon(beacon_chan); 22383ebfa6e7SLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 22393ebfa6e7SLuis R. Rodriguez 22403ebfa6e7SLuis R. Rodriguez if (processing) 2241e38f8a7aSLuis R. Rodriguez return 0; 2242e38f8a7aSLuis R. Rodriguez 2243e38f8a7aSLuis R. Rodriguez reg_beacon = kzalloc(sizeof(struct reg_beacon), gfp); 2244e38f8a7aSLuis R. Rodriguez if (!reg_beacon) 2245e38f8a7aSLuis R. Rodriguez return -ENOMEM; 2246e38f8a7aSLuis R. Rodriguez 22471a919318SJohannes Berg REG_DBG_PRINT("Found new beacon on frequency: %d MHz (Ch %d) on %s\n", 2248e38f8a7aSLuis R. Rodriguez beacon_chan->center_freq, 2249e38f8a7aSLuis R. Rodriguez ieee80211_frequency_to_channel(beacon_chan->center_freq), 2250e38f8a7aSLuis R. Rodriguez wiphy_name(wiphy)); 22514113f751SLuis R. Rodriguez 2252e38f8a7aSLuis R. Rodriguez memcpy(®_beacon->chan, beacon_chan, 2253e38f8a7aSLuis R. Rodriguez sizeof(struct ieee80211_channel)); 2254e38f8a7aSLuis R. Rodriguez 2255e38f8a7aSLuis R. Rodriguez /* 2256e38f8a7aSLuis R. Rodriguez * Since we can be called from BH or and non-BH context 2257e38f8a7aSLuis R. Rodriguez * we must use spin_lock_bh() 2258e38f8a7aSLuis R. Rodriguez */ 2259e38f8a7aSLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 2260e38f8a7aSLuis R. Rodriguez list_add_tail(®_beacon->list, ®_pending_beacons); 2261e38f8a7aSLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 2262e38f8a7aSLuis R. Rodriguez 2263e38f8a7aSLuis R. Rodriguez schedule_work(®_work); 2264e38f8a7aSLuis R. Rodriguez 2265e38f8a7aSLuis R. Rodriguez return 0; 2266e38f8a7aSLuis R. Rodriguez } 2267e38f8a7aSLuis R. Rodriguez 2268a3d2eaf0SJohannes Berg static void print_rd_rules(const struct ieee80211_regdomain *rd) 2269b2e1b302SLuis R. Rodriguez { 2270b2e1b302SLuis R. Rodriguez unsigned int i; 2271a3d2eaf0SJohannes Berg const struct ieee80211_reg_rule *reg_rule = NULL; 2272a3d2eaf0SJohannes Berg const struct ieee80211_freq_range *freq_range = NULL; 2273a3d2eaf0SJohannes Berg const struct ieee80211_power_rule *power_rule = NULL; 2274089027e5SJanusz Dziedzic char bw[32], cac_time[32]; 2275b2e1b302SLuis R. Rodriguez 2276089027e5SJanusz Dziedzic pr_info(" (start_freq - end_freq @ bandwidth), (max_antenna_gain, max_eirp), (dfs_cac_time)\n"); 2277b2e1b302SLuis R. Rodriguez 2278b2e1b302SLuis R. Rodriguez for (i = 0; i < rd->n_reg_rules; i++) { 2279b2e1b302SLuis R. Rodriguez reg_rule = &rd->reg_rules[i]; 2280b2e1b302SLuis R. Rodriguez freq_range = ®_rule->freq_range; 2281b2e1b302SLuis R. Rodriguez power_rule = ®_rule->power_rule; 2282b2e1b302SLuis R. Rodriguez 2283b0dfd2eaSJanusz Dziedzic if (reg_rule->flags & NL80211_RRF_AUTO_BW) 2284b0dfd2eaSJanusz Dziedzic snprintf(bw, sizeof(bw), "%d KHz, %d KHz AUTO", 2285b0dfd2eaSJanusz Dziedzic freq_range->max_bandwidth_khz, 228697524820SJanusz Dziedzic reg_get_max_bandwidth(rd, reg_rule)); 228797524820SJanusz Dziedzic else 2288b0dfd2eaSJanusz Dziedzic snprintf(bw, sizeof(bw), "%d KHz", 228997524820SJanusz Dziedzic freq_range->max_bandwidth_khz); 229097524820SJanusz Dziedzic 2291089027e5SJanusz Dziedzic if (reg_rule->flags & NL80211_RRF_DFS) 2292089027e5SJanusz Dziedzic scnprintf(cac_time, sizeof(cac_time), "%u s", 2293089027e5SJanusz Dziedzic reg_rule->dfs_cac_ms/1000); 2294089027e5SJanusz Dziedzic else 2295089027e5SJanusz Dziedzic scnprintf(cac_time, sizeof(cac_time), "N/A"); 2296089027e5SJanusz Dziedzic 2297089027e5SJanusz Dziedzic 2298fb1fc7adSLuis R. Rodriguez /* 2299fb1fc7adSLuis R. Rodriguez * There may not be documentation for max antenna gain 2300fb1fc7adSLuis R. Rodriguez * in certain regions 2301fb1fc7adSLuis R. Rodriguez */ 2302b2e1b302SLuis R. Rodriguez if (power_rule->max_antenna_gain) 2303089027e5SJanusz Dziedzic pr_info(" (%d KHz - %d KHz @ %s), (%d mBi, %d mBm), (%s)\n", 2304b2e1b302SLuis R. Rodriguez freq_range->start_freq_khz, 2305b2e1b302SLuis R. Rodriguez freq_range->end_freq_khz, 230697524820SJanusz Dziedzic bw, 2307b2e1b302SLuis R. Rodriguez power_rule->max_antenna_gain, 2308089027e5SJanusz Dziedzic power_rule->max_eirp, 2309089027e5SJanusz Dziedzic cac_time); 2310b2e1b302SLuis R. Rodriguez else 2311089027e5SJanusz Dziedzic pr_info(" (%d KHz - %d KHz @ %s), (N/A, %d mBm), (%s)\n", 2312b2e1b302SLuis R. Rodriguez freq_range->start_freq_khz, 2313b2e1b302SLuis R. Rodriguez freq_range->end_freq_khz, 231497524820SJanusz Dziedzic bw, 2315089027e5SJanusz Dziedzic power_rule->max_eirp, 2316089027e5SJanusz Dziedzic cac_time); 2317b2e1b302SLuis R. Rodriguez } 2318b2e1b302SLuis R. Rodriguez } 2319b2e1b302SLuis R. Rodriguez 23204c7d3982SLuis R. Rodriguez bool reg_supported_dfs_region(enum nl80211_dfs_regions dfs_region) 23218b60b078SLuis R. Rodriguez { 23228b60b078SLuis R. Rodriguez switch (dfs_region) { 23238b60b078SLuis R. Rodriguez case NL80211_DFS_UNSET: 23248b60b078SLuis R. Rodriguez case NL80211_DFS_FCC: 23258b60b078SLuis R. Rodriguez case NL80211_DFS_ETSI: 23268b60b078SLuis R. Rodriguez case NL80211_DFS_JP: 23278b60b078SLuis R. Rodriguez return true; 23288b60b078SLuis R. Rodriguez default: 23298b60b078SLuis R. Rodriguez REG_DBG_PRINT("Ignoring uknown DFS master region: %d\n", 23308b60b078SLuis R. Rodriguez dfs_region); 23318b60b078SLuis R. Rodriguez return false; 23328b60b078SLuis R. Rodriguez } 23338b60b078SLuis R. Rodriguez } 23348b60b078SLuis R. Rodriguez 2335a3d2eaf0SJohannes Berg static void print_regdomain(const struct ieee80211_regdomain *rd) 2336b2e1b302SLuis R. Rodriguez { 2337c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 2338b2e1b302SLuis R. Rodriguez 23393f2355cbSLuis R. Rodriguez if (is_intersected_alpha2(rd->alpha2)) { 2340c492db37SJohannes Berg if (lr->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE) { 234179c97e97SJohannes Berg struct cfg80211_registered_device *rdev; 2342c492db37SJohannes Berg rdev = cfg80211_rdev_by_wiphy_idx(lr->wiphy_idx); 234379c97e97SJohannes Berg if (rdev) { 2344e9c0268fSJoe Perches pr_info("Current regulatory domain updated by AP to: %c%c\n", 234579c97e97SJohannes Berg rdev->country_ie_alpha2[0], 234679c97e97SJohannes Berg rdev->country_ie_alpha2[1]); 23473f2355cbSLuis R. Rodriguez } else 2348e9c0268fSJoe Perches pr_info("Current regulatory domain intersected:\n"); 23493f2355cbSLuis R. Rodriguez } else 2350e9c0268fSJoe Perches pr_info("Current regulatory domain intersected:\n"); 23511a919318SJohannes Berg } else if (is_world_regdom(rd->alpha2)) { 2352e9c0268fSJoe Perches pr_info("World regulatory domain updated:\n"); 23531a919318SJohannes Berg } else { 2354b2e1b302SLuis R. Rodriguez if (is_unknown_alpha2(rd->alpha2)) 2355e9c0268fSJoe Perches pr_info("Regulatory domain changed to driver built-in settings (unknown country)\n"); 235657b5ce07SLuis R. Rodriguez else { 2357c492db37SJohannes Berg if (reg_request_cell_base(lr)) 23581a919318SJohannes Berg pr_info("Regulatory domain changed to country: %c%c by Cell Station\n", 2359b2e1b302SLuis R. Rodriguez rd->alpha2[0], rd->alpha2[1]); 236057b5ce07SLuis R. Rodriguez else 23611a919318SJohannes Berg pr_info("Regulatory domain changed to country: %c%c\n", 236257b5ce07SLuis R. Rodriguez rd->alpha2[0], rd->alpha2[1]); 236357b5ce07SLuis R. Rodriguez } 2364b2e1b302SLuis R. Rodriguez } 23651a919318SJohannes Berg 23663ef121b5SLuis R. Rodriguez pr_info(" DFS Master region: %s", reg_dfs_region_str(rd->dfs_region)); 2367b2e1b302SLuis R. Rodriguez print_rd_rules(rd); 2368b2e1b302SLuis R. Rodriguez } 2369b2e1b302SLuis R. Rodriguez 23702df78167SJohannes Berg static void print_regdomain_info(const struct ieee80211_regdomain *rd) 2371b2e1b302SLuis R. Rodriguez { 2372e9c0268fSJoe Perches pr_info("Regulatory domain: %c%c\n", rd->alpha2[0], rd->alpha2[1]); 2373b2e1b302SLuis R. Rodriguez print_rd_rules(rd); 2374b2e1b302SLuis R. Rodriguez } 2375b2e1b302SLuis R. Rodriguez 23763b9e5acaSLuis R. Rodriguez static int reg_set_rd_core(const struct ieee80211_regdomain *rd) 23773b9e5acaSLuis R. Rodriguez { 23783b9e5acaSLuis R. Rodriguez if (!is_world_regdom(rd->alpha2)) 23793b9e5acaSLuis R. Rodriguez return -EINVAL; 23803b9e5acaSLuis R. Rodriguez update_world_regdomain(rd); 23813b9e5acaSLuis R. Rodriguez return 0; 23823b9e5acaSLuis R. Rodriguez } 23833b9e5acaSLuis R. Rodriguez 238484721d44SLuis R. Rodriguez static int reg_set_rd_user(const struct ieee80211_regdomain *rd, 238584721d44SLuis R. Rodriguez struct regulatory_request *user_request) 238684721d44SLuis R. Rodriguez { 238784721d44SLuis R. Rodriguez const struct ieee80211_regdomain *intersected_rd = NULL; 238884721d44SLuis R. Rodriguez 238984721d44SLuis R. Rodriguez if (!regdom_changes(rd->alpha2)) 239084721d44SLuis R. Rodriguez return -EALREADY; 239184721d44SLuis R. Rodriguez 239284721d44SLuis R. Rodriguez if (!is_valid_rd(rd)) { 239384721d44SLuis R. Rodriguez pr_err("Invalid regulatory domain detected:\n"); 239484721d44SLuis R. Rodriguez print_regdomain_info(rd); 239584721d44SLuis R. Rodriguez return -EINVAL; 239684721d44SLuis R. Rodriguez } 239784721d44SLuis R. Rodriguez 239884721d44SLuis R. Rodriguez if (!user_request->intersect) { 239984721d44SLuis R. Rodriguez reset_regdomains(false, rd); 240084721d44SLuis R. Rodriguez return 0; 240184721d44SLuis R. Rodriguez } 240284721d44SLuis R. Rodriguez 240384721d44SLuis R. Rodriguez intersected_rd = regdom_intersect(rd, get_cfg80211_regdom()); 240484721d44SLuis R. Rodriguez if (!intersected_rd) 240584721d44SLuis R. Rodriguez return -EINVAL; 240684721d44SLuis R. Rodriguez 240784721d44SLuis R. Rodriguez kfree(rd); 240884721d44SLuis R. Rodriguez rd = NULL; 240984721d44SLuis R. Rodriguez reset_regdomains(false, intersected_rd); 241084721d44SLuis R. Rodriguez 241184721d44SLuis R. Rodriguez return 0; 241284721d44SLuis R. Rodriguez } 241384721d44SLuis R. Rodriguez 2414f5fe3247SLuis R. Rodriguez static int reg_set_rd_driver(const struct ieee80211_regdomain *rd, 2415f5fe3247SLuis R. Rodriguez struct regulatory_request *driver_request) 2416b2e1b302SLuis R. Rodriguez { 2417e9763c3cSJohannes Berg const struct ieee80211_regdomain *regd; 24189c96477dSLuis R. Rodriguez const struct ieee80211_regdomain *intersected_rd = NULL; 2419f5fe3247SLuis R. Rodriguez const struct ieee80211_regdomain *tmp; 2420806a9e39SLuis R. Rodriguez struct wiphy *request_wiphy; 24216913b49aSJohannes Berg 2422f5fe3247SLuis R. Rodriguez if (is_world_regdom(rd->alpha2)) 2423b2e1b302SLuis R. Rodriguez return -EINVAL; 2424b2e1b302SLuis R. Rodriguez 2425baeb66feSJohn W. Linville if (!regdom_changes(rd->alpha2)) 242695908535SKalle Valo return -EALREADY; 2427b2e1b302SLuis R. Rodriguez 2428b2e1b302SLuis R. Rodriguez if (!is_valid_rd(rd)) { 2429e9c0268fSJoe Perches pr_err("Invalid regulatory domain detected:\n"); 2430b2e1b302SLuis R. Rodriguez print_regdomain_info(rd); 2431b2e1b302SLuis R. Rodriguez return -EINVAL; 2432b2e1b302SLuis R. Rodriguez } 2433b2e1b302SLuis R. Rodriguez 2434f5fe3247SLuis R. Rodriguez request_wiphy = wiphy_idx_to_wiphy(driver_request->wiphy_idx); 2435f5fe3247SLuis R. Rodriguez if (!request_wiphy) { 2436845f3351SShaibal Dutta queue_delayed_work(system_power_efficient_wq, 2437845f3351SShaibal Dutta ®_timeout, 0); 2438de3584bdSJohannes Berg return -ENODEV; 2439de3584bdSJohannes Berg } 2440806a9e39SLuis R. Rodriguez 2441f5fe3247SLuis R. Rodriguez if (!driver_request->intersect) { 2442558f6d32SLuis R. Rodriguez if (request_wiphy->regd) 2443558f6d32SLuis R. Rodriguez return -EALREADY; 24443e0c3ff3SLuis R. Rodriguez 2445e9763c3cSJohannes Berg regd = reg_copy_regd(rd); 2446e9763c3cSJohannes Berg if (IS_ERR(regd)) 2447e9763c3cSJohannes Berg return PTR_ERR(regd); 24483e0c3ff3SLuis R. Rodriguez 2449458f4f9eSJohannes Berg rcu_assign_pointer(request_wiphy->regd, regd); 2450379b82f4SJohannes Berg reset_regdomains(false, rd); 2451b8295acdSLuis R. Rodriguez return 0; 2452b8295acdSLuis R. Rodriguez } 2453b8295acdSLuis R. Rodriguez 2454458f4f9eSJohannes Berg intersected_rd = regdom_intersect(rd, get_cfg80211_regdom()); 24559c96477dSLuis R. Rodriguez if (!intersected_rd) 24569c96477dSLuis R. Rodriguez return -EINVAL; 2457b8295acdSLuis R. Rodriguez 2458fb1fc7adSLuis R. Rodriguez /* 2459fb1fc7adSLuis R. Rodriguez * We can trash what CRDA provided now. 24603e0c3ff3SLuis R. Rodriguez * However if a driver requested this specific regulatory 2461fb1fc7adSLuis R. Rodriguez * domain we keep it for its private use 2462fb1fc7adSLuis R. Rodriguez */ 2463b7566fc3SLarry Finger tmp = get_wiphy_regdom(request_wiphy); 2464458f4f9eSJohannes Berg rcu_assign_pointer(request_wiphy->regd, rd); 2465b7566fc3SLarry Finger rcu_free_regdom(tmp); 24663e0c3ff3SLuis R. Rodriguez 2467b8295acdSLuis R. Rodriguez rd = NULL; 2468b8295acdSLuis R. Rodriguez 2469379b82f4SJohannes Berg reset_regdomains(false, intersected_rd); 2470b8295acdSLuis R. Rodriguez 2471b8295acdSLuis R. Rodriguez return 0; 24729c96477dSLuis R. Rodriguez } 24739c96477dSLuis R. Rodriguez 247401992406SLuis R. Rodriguez static int reg_set_rd_country_ie(const struct ieee80211_regdomain *rd, 247501992406SLuis R. Rodriguez struct regulatory_request *country_ie_request) 2476f5fe3247SLuis R. Rodriguez { 2477f5fe3247SLuis R. Rodriguez struct wiphy *request_wiphy; 2478f5fe3247SLuis R. Rodriguez 2479f5fe3247SLuis R. Rodriguez if (!is_alpha2_set(rd->alpha2) && !is_an_alpha2(rd->alpha2) && 2480f5fe3247SLuis R. Rodriguez !is_unknown_alpha2(rd->alpha2)) 2481f5fe3247SLuis R. Rodriguez return -EINVAL; 2482f5fe3247SLuis R. Rodriguez 2483f5fe3247SLuis R. Rodriguez /* 2484f5fe3247SLuis R. Rodriguez * Lets only bother proceeding on the same alpha2 if the current 2485f5fe3247SLuis R. Rodriguez * rd is non static (it means CRDA was present and was used last) 2486f5fe3247SLuis R. Rodriguez * and the pending request came in from a country IE 2487f5fe3247SLuis R. Rodriguez */ 2488f5fe3247SLuis R. Rodriguez 2489f5fe3247SLuis R. Rodriguez if (!is_valid_rd(rd)) { 2490f5fe3247SLuis R. Rodriguez pr_err("Invalid regulatory domain detected:\n"); 2491f5fe3247SLuis R. Rodriguez print_regdomain_info(rd); 24923f2355cbSLuis R. Rodriguez return -EINVAL; 2493b2e1b302SLuis R. Rodriguez } 2494b2e1b302SLuis R. Rodriguez 249501992406SLuis R. Rodriguez request_wiphy = wiphy_idx_to_wiphy(country_ie_request->wiphy_idx); 2496f5fe3247SLuis R. Rodriguez if (!request_wiphy) { 2497845f3351SShaibal Dutta queue_delayed_work(system_power_efficient_wq, 2498845f3351SShaibal Dutta ®_timeout, 0); 2499f5fe3247SLuis R. Rodriguez return -ENODEV; 2500f5fe3247SLuis R. Rodriguez } 2501f5fe3247SLuis R. Rodriguez 250201992406SLuis R. Rodriguez if (country_ie_request->intersect) 2503f5fe3247SLuis R. Rodriguez return -EINVAL; 2504f5fe3247SLuis R. Rodriguez 2505f5fe3247SLuis R. Rodriguez reset_regdomains(false, rd); 2506f5fe3247SLuis R. Rodriguez return 0; 2507f5fe3247SLuis R. Rodriguez } 2508b2e1b302SLuis R. Rodriguez 2509fb1fc7adSLuis R. Rodriguez /* 2510fb1fc7adSLuis R. Rodriguez * Use this call to set the current regulatory domain. Conflicts with 2511b2e1b302SLuis R. Rodriguez * multiple drivers can be ironed out later. Caller must've already 2512458f4f9eSJohannes Berg * kmalloc'd the rd structure. 2513fb1fc7adSLuis R. Rodriguez */ 2514a3d2eaf0SJohannes Berg int set_regdom(const struct ieee80211_regdomain *rd) 2515b2e1b302SLuis R. Rodriguez { 2516c492db37SJohannes Berg struct regulatory_request *lr; 2517092008abSJanusz Dziedzic bool user_reset = false; 2518b2e1b302SLuis R. Rodriguez int r; 2519b2e1b302SLuis R. Rodriguez 25203b9e5acaSLuis R. Rodriguez if (!reg_is_valid_request(rd->alpha2)) { 25213b9e5acaSLuis R. Rodriguez kfree(rd); 25223b9e5acaSLuis R. Rodriguez return -EINVAL; 25233b9e5acaSLuis R. Rodriguez } 25243b9e5acaSLuis R. Rodriguez 2525c492db37SJohannes Berg lr = get_last_request(); 2526abc7381bSLuis R. Rodriguez 2527b2e1b302SLuis R. Rodriguez /* Note that this doesn't update the wiphys, this is done below */ 25283b9e5acaSLuis R. Rodriguez switch (lr->initiator) { 25293b9e5acaSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_CORE: 25303b9e5acaSLuis R. Rodriguez r = reg_set_rd_core(rd); 25313b9e5acaSLuis R. Rodriguez break; 25323b9e5acaSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_USER: 253384721d44SLuis R. Rodriguez r = reg_set_rd_user(rd, lr); 2534092008abSJanusz Dziedzic user_reset = true; 253584721d44SLuis R. Rodriguez break; 25363b9e5acaSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_DRIVER: 2537f5fe3247SLuis R. Rodriguez r = reg_set_rd_driver(rd, lr); 2538f5fe3247SLuis R. Rodriguez break; 25393b9e5acaSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_COUNTRY_IE: 254001992406SLuis R. Rodriguez r = reg_set_rd_country_ie(rd, lr); 25413b9e5acaSLuis R. Rodriguez break; 25423b9e5acaSLuis R. Rodriguez default: 25433b9e5acaSLuis R. Rodriguez WARN(1, "invalid initiator %d\n", lr->initiator); 25443b9e5acaSLuis R. Rodriguez return -EINVAL; 25453b9e5acaSLuis R. Rodriguez } 25463b9e5acaSLuis R. Rodriguez 2547d2372b31SJohannes Berg if (r) { 2548092008abSJanusz Dziedzic switch (r) { 2549092008abSJanusz Dziedzic case -EALREADY: 255095908535SKalle Valo reg_set_request_processed(); 2551092008abSJanusz Dziedzic break; 2552092008abSJanusz Dziedzic default: 2553092008abSJanusz Dziedzic /* Back to world regulatory in case of errors */ 2554092008abSJanusz Dziedzic restore_regulatory_settings(user_reset); 2555092008abSJanusz Dziedzic } 255695908535SKalle Valo 2557d2372b31SJohannes Berg kfree(rd); 255838fd2143SJohannes Berg return r; 2559d2372b31SJohannes Berg } 2560b2e1b302SLuis R. Rodriguez 2561b2e1b302SLuis R. Rodriguez /* This would make this whole thing pointless */ 256238fd2143SJohannes Berg if (WARN_ON(!lr->intersect && rd != get_cfg80211_regdom())) 256338fd2143SJohannes Berg return -EINVAL; 2564b2e1b302SLuis R. Rodriguez 2565b2e1b302SLuis R. Rodriguez /* update all wiphys now with the new established regulatory domain */ 2566c492db37SJohannes Berg update_all_wiphy_regulatory(lr->initiator); 2567b2e1b302SLuis R. Rodriguez 2568458f4f9eSJohannes Berg print_regdomain(get_cfg80211_regdom()); 2569b2e1b302SLuis R. Rodriguez 2570c492db37SJohannes Berg nl80211_send_reg_change_event(lr); 257173d54c9eSLuis R. Rodriguez 2572b2e253cfSLuis R. Rodriguez reg_set_request_processed(); 2573b2e253cfSLuis R. Rodriguez 257438fd2143SJohannes Berg return 0; 2575b2e1b302SLuis R. Rodriguez } 2576b2e1b302SLuis R. Rodriguez 257757b5ce07SLuis R. Rodriguez void wiphy_regulatory_register(struct wiphy *wiphy) 257857b5ce07SLuis R. Rodriguez { 257923df0b73SArik Nemtsov struct regulatory_request *lr; 258023df0b73SArik Nemtsov 258157b5ce07SLuis R. Rodriguez if (!reg_dev_ignore_cell_hint(wiphy)) 258257b5ce07SLuis R. Rodriguez reg_num_devs_support_basehint++; 258357b5ce07SLuis R. Rodriguez 258423df0b73SArik Nemtsov lr = get_last_request(); 258523df0b73SArik Nemtsov wiphy_update_regulatory(wiphy, lr->initiator); 258657b5ce07SLuis R. Rodriguez } 258757b5ce07SLuis R. Rodriguez 2588bfead080SLuis R. Rodriguez void wiphy_regulatory_deregister(struct wiphy *wiphy) 25893f2355cbSLuis R. Rodriguez { 25900ad8acafSLuis R. Rodriguez struct wiphy *request_wiphy = NULL; 2591c492db37SJohannes Berg struct regulatory_request *lr; 2592761cf7ecSLuis R. Rodriguez 2593c492db37SJohannes Berg lr = get_last_request(); 2594abc7381bSLuis R. Rodriguez 259557b5ce07SLuis R. Rodriguez if (!reg_dev_ignore_cell_hint(wiphy)) 259657b5ce07SLuis R. Rodriguez reg_num_devs_support_basehint--; 259757b5ce07SLuis R. Rodriguez 2598458f4f9eSJohannes Berg rcu_free_regdom(get_wiphy_regdom(wiphy)); 259934dd886cSMonam Agarwal RCU_INIT_POINTER(wiphy->regd, NULL); 26000ef9ccddSChris Wright 2601c492db37SJohannes Berg if (lr) 2602c492db37SJohannes Berg request_wiphy = wiphy_idx_to_wiphy(lr->wiphy_idx); 2603806a9e39SLuis R. Rodriguez 26040ef9ccddSChris Wright if (!request_wiphy || request_wiphy != wiphy) 260538fd2143SJohannes Berg return; 26060ef9ccddSChris Wright 2607c492db37SJohannes Berg lr->wiphy_idx = WIPHY_IDX_INVALID; 2608c492db37SJohannes Berg lr->country_ie_env = ENVIRON_ANY; 26093f2355cbSLuis R. Rodriguez } 26103f2355cbSLuis R. Rodriguez 2611a90c7a31SLuis R. Rodriguez static void reg_timeout_work(struct work_struct *work) 2612a90c7a31SLuis R. Rodriguez { 26131a919318SJohannes Berg REG_DBG_PRINT("Timeout while waiting for CRDA to reply, restoring regulatory settings\n"); 2614f77b86d7SJohannes Berg rtnl_lock(); 2615a90c7a31SLuis R. Rodriguez restore_regulatory_settings(true); 2616f77b86d7SJohannes Berg rtnl_unlock(); 2617a90c7a31SLuis R. Rodriguez } 2618a90c7a31SLuis R. Rodriguez 26192fcc9f73SUwe Kleine-König int __init regulatory_init(void) 2620b2e1b302SLuis R. Rodriguez { 2621bcf4f99bSLuis R. Rodriguez int err = 0; 2622734366deSJohannes Berg 2623b2e1b302SLuis R. Rodriguez reg_pdev = platform_device_register_simple("regulatory", 0, NULL, 0); 2624b2e1b302SLuis R. Rodriguez if (IS_ERR(reg_pdev)) 2625b2e1b302SLuis R. Rodriguez return PTR_ERR(reg_pdev); 2626734366deSJohannes Berg 2627fe33eb39SLuis R. Rodriguez spin_lock_init(®_requests_lock); 2628e38f8a7aSLuis R. Rodriguez spin_lock_init(®_pending_beacons_lock); 2629fe33eb39SLuis R. Rodriguez 263080007efeSLuis R. Rodriguez reg_regdb_size_check(); 263180007efeSLuis R. Rodriguez 2632458f4f9eSJohannes Berg rcu_assign_pointer(cfg80211_regdomain, cfg80211_world_regdom); 2633734366deSJohannes Berg 263409d989d1SLuis R. Rodriguez user_alpha2[0] = '9'; 263509d989d1SLuis R. Rodriguez user_alpha2[1] = '7'; 263609d989d1SLuis R. Rodriguez 2637ae9e4b0dSLuis R. Rodriguez /* We always try to get an update for the static regdomain */ 2638458f4f9eSJohannes Berg err = regulatory_hint_core(cfg80211_world_regdom->alpha2); 2639bcf4f99bSLuis R. Rodriguez if (err) { 2640bcf4f99bSLuis R. Rodriguez if (err == -ENOMEM) 2641bcf4f99bSLuis R. Rodriguez return err; 2642bcf4f99bSLuis R. Rodriguez /* 2643bcf4f99bSLuis R. Rodriguez * N.B. kobject_uevent_env() can fail mainly for when we're out 2644bcf4f99bSLuis R. Rodriguez * memory which is handled and propagated appropriately above 2645bcf4f99bSLuis R. Rodriguez * but it can also fail during a netlink_broadcast() or during 2646bcf4f99bSLuis R. Rodriguez * early boot for call_usermodehelper(). For now treat these 2647bcf4f99bSLuis R. Rodriguez * errors as non-fatal. 2648bcf4f99bSLuis R. Rodriguez */ 2649e9c0268fSJoe Perches pr_err("kobject_uevent_env() was unable to call CRDA during init\n"); 2650bcf4f99bSLuis R. Rodriguez } 2651734366deSJohannes Berg 2652ae9e4b0dSLuis R. Rodriguez /* 2653ae9e4b0dSLuis R. Rodriguez * Finally, if the user set the module parameter treat it 2654ae9e4b0dSLuis R. Rodriguez * as a user hint. 2655ae9e4b0dSLuis R. Rodriguez */ 2656ae9e4b0dSLuis R. Rodriguez if (!is_world_regdom(ieee80211_regdom)) 265757b5ce07SLuis R. Rodriguez regulatory_hint_user(ieee80211_regdom, 265857b5ce07SLuis R. Rodriguez NL80211_USER_REG_HINT_USER); 2659ae9e4b0dSLuis R. Rodriguez 2660b2e1b302SLuis R. Rodriguez return 0; 2661b2e1b302SLuis R. Rodriguez } 2662b2e1b302SLuis R. Rodriguez 26631a919318SJohannes Berg void regulatory_exit(void) 2664b2e1b302SLuis R. Rodriguez { 2665fe33eb39SLuis R. Rodriguez struct regulatory_request *reg_request, *tmp; 2666e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon, *btmp; 2667fe33eb39SLuis R. Rodriguez 2668fe33eb39SLuis R. Rodriguez cancel_work_sync(®_work); 2669a90c7a31SLuis R. Rodriguez cancel_delayed_work_sync(®_timeout); 2670fe33eb39SLuis R. Rodriguez 26719027b149SJohannes Berg /* Lock to suppress warnings */ 267238fd2143SJohannes Berg rtnl_lock(); 2673379b82f4SJohannes Berg reset_regdomains(true, NULL); 267438fd2143SJohannes Berg rtnl_unlock(); 2675734366deSJohannes Berg 267658ebacc6SLuis R. Rodriguez dev_set_uevent_suppress(®_pdev->dev, true); 2677f6037d09SJohannes Berg 2678b2e1b302SLuis R. Rodriguez platform_device_unregister(reg_pdev); 2679734366deSJohannes Berg 2680fea9bcedSJohannes Berg list_for_each_entry_safe(reg_beacon, btmp, ®_pending_beacons, list) { 2681e38f8a7aSLuis R. Rodriguez list_del(®_beacon->list); 2682e38f8a7aSLuis R. Rodriguez kfree(reg_beacon); 2683e38f8a7aSLuis R. Rodriguez } 2684e38f8a7aSLuis R. Rodriguez 2685fea9bcedSJohannes Berg list_for_each_entry_safe(reg_beacon, btmp, ®_beacon_list, list) { 2686e38f8a7aSLuis R. Rodriguez list_del(®_beacon->list); 2687e38f8a7aSLuis R. Rodriguez kfree(reg_beacon); 2688e38f8a7aSLuis R. Rodriguez } 2689e38f8a7aSLuis R. Rodriguez 2690fea9bcedSJohannes Berg list_for_each_entry_safe(reg_request, tmp, ®_requests_list, list) { 2691fe33eb39SLuis R. Rodriguez list_del(®_request->list); 2692fe33eb39SLuis R. Rodriguez kfree(reg_request); 2693fe33eb39SLuis R. Rodriguez } 2694fe33eb39SLuis R. Rodriguez } 2695