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> 62740f0cfSJohannes Berg * Copyright 2013-2014 Intel Mobile Communications GmbH 78318d78aSJohannes Berg * 83b77d5ecSLuis R. Rodriguez * Permission to use, copy, modify, and/or distribute this software for any 93b77d5ecSLuis R. Rodriguez * purpose with or without fee is hereby granted, provided that the above 103b77d5ecSLuis R. Rodriguez * copyright notice and this permission notice appear in all copies. 113b77d5ecSLuis R. Rodriguez * 123b77d5ecSLuis R. Rodriguez * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 133b77d5ecSLuis R. Rodriguez * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 143b77d5ecSLuis R. Rodriguez * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 153b77d5ecSLuis R. Rodriguez * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 163b77d5ecSLuis R. Rodriguez * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 173b77d5ecSLuis R. Rodriguez * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 183b77d5ecSLuis R. Rodriguez * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 198318d78aSJohannes Berg */ 208318d78aSJohannes Berg 213b77d5ecSLuis R. Rodriguez 22b2e1b302SLuis R. Rodriguez /** 23b2e1b302SLuis R. Rodriguez * DOC: Wireless regulatory infrastructure 248318d78aSJohannes Berg * 258318d78aSJohannes Berg * The usual implementation is for a driver to read a device EEPROM to 268318d78aSJohannes Berg * determine which regulatory domain it should be operating under, then 278318d78aSJohannes Berg * looking up the allowable channels in a driver-local table and finally 288318d78aSJohannes Berg * registering those channels in the wiphy structure. 298318d78aSJohannes Berg * 30b2e1b302SLuis R. Rodriguez * Another set of compliance enforcement is for drivers to use their 31b2e1b302SLuis R. Rodriguez * own compliance limits which can be stored on the EEPROM. The host 32b2e1b302SLuis R. Rodriguez * driver or firmware may ensure these are used. 33b2e1b302SLuis R. Rodriguez * 34b2e1b302SLuis R. Rodriguez * In addition to all this we provide an extra layer of regulatory 35b2e1b302SLuis R. Rodriguez * conformance. For drivers which do not have any regulatory 36b2e1b302SLuis R. Rodriguez * information CRDA provides the complete regulatory solution. 37b2e1b302SLuis R. Rodriguez * For others it provides a community effort on further restrictions 38b2e1b302SLuis R. Rodriguez * to enhance compliance. 39b2e1b302SLuis R. Rodriguez * 40b2e1b302SLuis R. Rodriguez * Note: When number of rules --> infinity we will not be able to 41b2e1b302SLuis R. Rodriguez * index on alpha2 any more, instead we'll probably have to 42b2e1b302SLuis R. Rodriguez * rely on some SHA1 checksum of the regdomain for example. 43b2e1b302SLuis R. Rodriguez * 448318d78aSJohannes Berg */ 45e9c0268fSJoe Perches 46e9c0268fSJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 47e9c0268fSJoe Perches 488318d78aSJohannes Berg #include <linux/kernel.h> 49bc3b2d7fSPaul Gortmaker #include <linux/export.h> 505a0e3ad6STejun Heo #include <linux/slab.h> 51b2e1b302SLuis R. Rodriguez #include <linux/list.h> 52c61029c7SJohn W. Linville #include <linux/ctype.h> 53b2e1b302SLuis R. Rodriguez #include <linux/nl80211.h> 54b2e1b302SLuis R. Rodriguez #include <linux/platform_device.h> 55d9b93842SPaul Gortmaker #include <linux/moduleparam.h> 56b2e1b302SLuis R. Rodriguez #include <net/cfg80211.h> 578318d78aSJohannes Berg #include "core.h" 58b2e1b302SLuis R. Rodriguez #include "reg.h" 593b377ea9SJohn W. Linville #include "regdb.h" 6073d54c9eSLuis R. Rodriguez #include "nl80211.h" 618318d78aSJohannes Berg 624113f751SLuis R. Rodriguez #ifdef CONFIG_CFG80211_REG_DEBUG 638271195eSJohn W. Linville #define REG_DBG_PRINT(format, args...) \ 6412c5ffb5SJoe Perches printk(KERN_DEBUG pr_fmt(format), ##args) 654113f751SLuis R. Rodriguez #else 668271195eSJohn W. Linville #define REG_DBG_PRINT(args...) 674113f751SLuis R. Rodriguez #endif 684113f751SLuis R. Rodriguez 6952616f2bSIlan Peer /** 7052616f2bSIlan Peer * enum reg_request_treatment - regulatory request treatment 7152616f2bSIlan Peer * 7252616f2bSIlan Peer * @REG_REQ_OK: continue processing the regulatory request 7352616f2bSIlan Peer * @REG_REQ_IGNORE: ignore the regulatory request 7452616f2bSIlan Peer * @REG_REQ_INTERSECT: the regulatory domain resulting from this request should 7552616f2bSIlan Peer * be intersected with the current one. 7652616f2bSIlan Peer * @REG_REQ_ALREADY_SET: the regulatory request will not change the current 7752616f2bSIlan Peer * regulatory settings, and no further processing is required. 7852616f2bSIlan Peer * @REG_REQ_USER_HINT_HANDLED: a non alpha2 user hint was handled and no 7952616f2bSIlan Peer * further processing is required, i.e., not need to update last_request 8052616f2bSIlan Peer * etc. This should be used for user hints that do not provide an alpha2 8152616f2bSIlan Peer * but some other type of regulatory hint, i.e., indoor operation. 8252616f2bSIlan Peer */ 832f92212bSJohannes Berg enum reg_request_treatment { 842f92212bSJohannes Berg REG_REQ_OK, 852f92212bSJohannes Berg REG_REQ_IGNORE, 862f92212bSJohannes Berg REG_REQ_INTERSECT, 872f92212bSJohannes Berg REG_REQ_ALREADY_SET, 8852616f2bSIlan Peer REG_REQ_USER_HINT_HANDLED, 892f92212bSJohannes Berg }; 902f92212bSJohannes Berg 91a042994dSLuis R. Rodriguez static struct regulatory_request core_request_world = { 92a042994dSLuis R. Rodriguez .initiator = NL80211_REGDOM_SET_BY_CORE, 93a042994dSLuis R. Rodriguez .alpha2[0] = '0', 94a042994dSLuis R. Rodriguez .alpha2[1] = '0', 95a042994dSLuis R. Rodriguez .intersect = false, 96a042994dSLuis R. Rodriguez .processed = true, 97a042994dSLuis R. Rodriguez .country_ie_env = ENVIRON_ANY, 98a042994dSLuis R. Rodriguez }; 99a042994dSLuis R. Rodriguez 10038fd2143SJohannes Berg /* 10138fd2143SJohannes Berg * Receipt of information from last regulatory request, 10238fd2143SJohannes Berg * protected by RTNL (and can be accessed with RCU protection) 10338fd2143SJohannes Berg */ 104c492db37SJohannes Berg static struct regulatory_request __rcu *last_request = 105c492db37SJohannes Berg (void __rcu *)&core_request_world; 106734366deSJohannes Berg 107b2e1b302SLuis R. Rodriguez /* To trigger userspace events */ 108b2e1b302SLuis R. Rodriguez static struct platform_device *reg_pdev; 1098318d78aSJohannes Berg 110fb1fc7adSLuis R. Rodriguez /* 111fb1fc7adSLuis R. Rodriguez * Central wireless core regulatory domains, we only need two, 112734366deSJohannes Berg * the current one and a world regulatory domain in case we have no 113e8da2bb4SJohannes Berg * information to give us an alpha2. 11438fd2143SJohannes Berg * (protected by RTNL, can be read under RCU) 115fb1fc7adSLuis R. Rodriguez */ 116458f4f9eSJohannes Berg const struct ieee80211_regdomain __rcu *cfg80211_regdomain; 117734366deSJohannes Berg 118fb1fc7adSLuis R. Rodriguez /* 11957b5ce07SLuis R. Rodriguez * Number of devices that registered to the core 12057b5ce07SLuis R. Rodriguez * that support cellular base station regulatory hints 12138fd2143SJohannes Berg * (protected by RTNL) 12257b5ce07SLuis R. Rodriguez */ 12357b5ce07SLuis R. Rodriguez static int reg_num_devs_support_basehint; 12457b5ce07SLuis R. Rodriguez 12552616f2bSIlan Peer /* 12652616f2bSIlan Peer * State variable indicating if the platform on which the devices 12752616f2bSIlan Peer * are attached is operating in an indoor environment. The state variable 12852616f2bSIlan Peer * is relevant for all registered devices. 12952616f2bSIlan Peer * (protected by RTNL) 13052616f2bSIlan Peer */ 13152616f2bSIlan Peer static bool reg_is_indoor; 13252616f2bSIlan Peer 133458f4f9eSJohannes Berg static const struct ieee80211_regdomain *get_cfg80211_regdom(void) 134458f4f9eSJohannes Berg { 13538fd2143SJohannes Berg return rtnl_dereference(cfg80211_regdomain); 136458f4f9eSJohannes Berg } 137458f4f9eSJohannes Berg 138458f4f9eSJohannes Berg static const struct ieee80211_regdomain *get_wiphy_regdom(struct wiphy *wiphy) 139458f4f9eSJohannes Berg { 14038fd2143SJohannes Berg return rtnl_dereference(wiphy->regd); 141458f4f9eSJohannes Berg } 142458f4f9eSJohannes Berg 1433ef121b5SLuis R. Rodriguez static const char *reg_dfs_region_str(enum nl80211_dfs_regions dfs_region) 1443ef121b5SLuis R. Rodriguez { 1453ef121b5SLuis R. Rodriguez switch (dfs_region) { 1463ef121b5SLuis R. Rodriguez case NL80211_DFS_UNSET: 1473ef121b5SLuis R. Rodriguez return "unset"; 1483ef121b5SLuis R. Rodriguez case NL80211_DFS_FCC: 1493ef121b5SLuis R. Rodriguez return "FCC"; 1503ef121b5SLuis R. Rodriguez case NL80211_DFS_ETSI: 1513ef121b5SLuis R. Rodriguez return "ETSI"; 1523ef121b5SLuis R. Rodriguez case NL80211_DFS_JP: 1533ef121b5SLuis R. Rodriguez return "JP"; 1543ef121b5SLuis R. Rodriguez } 1553ef121b5SLuis R. Rodriguez return "Unknown"; 1563ef121b5SLuis R. Rodriguez } 1573ef121b5SLuis R. Rodriguez 1586c474799SLuis R. Rodriguez enum nl80211_dfs_regions reg_get_dfs_region(struct wiphy *wiphy) 1596c474799SLuis R. Rodriguez { 1606c474799SLuis R. Rodriguez const struct ieee80211_regdomain *regd = NULL; 1616c474799SLuis R. Rodriguez const struct ieee80211_regdomain *wiphy_regd = NULL; 1626c474799SLuis R. Rodriguez 1636c474799SLuis R. Rodriguez regd = get_cfg80211_regdom(); 1646c474799SLuis R. Rodriguez if (!wiphy) 1656c474799SLuis R. Rodriguez goto out; 1666c474799SLuis R. Rodriguez 1676c474799SLuis R. Rodriguez wiphy_regd = get_wiphy_regdom(wiphy); 1686c474799SLuis R. Rodriguez if (!wiphy_regd) 1696c474799SLuis R. Rodriguez goto out; 1706c474799SLuis R. Rodriguez 1716c474799SLuis R. Rodriguez if (wiphy_regd->dfs_region == regd->dfs_region) 1726c474799SLuis R. Rodriguez goto out; 1736c474799SLuis R. Rodriguez 1746c474799SLuis R. Rodriguez REG_DBG_PRINT("%s: device specific dfs_region " 1756c474799SLuis R. Rodriguez "(%s) disagrees with cfg80211's " 1766c474799SLuis R. Rodriguez "central dfs_region (%s)\n", 1776c474799SLuis R. Rodriguez dev_name(&wiphy->dev), 1786c474799SLuis R. Rodriguez reg_dfs_region_str(wiphy_regd->dfs_region), 1796c474799SLuis R. Rodriguez reg_dfs_region_str(regd->dfs_region)); 1806c474799SLuis R. Rodriguez 1816c474799SLuis R. Rodriguez out: 1826c474799SLuis R. Rodriguez return regd->dfs_region; 1836c474799SLuis R. Rodriguez } 1846c474799SLuis R. Rodriguez 185458f4f9eSJohannes Berg static void rcu_free_regdom(const struct ieee80211_regdomain *r) 186458f4f9eSJohannes Berg { 187458f4f9eSJohannes Berg if (!r) 188458f4f9eSJohannes Berg return; 189458f4f9eSJohannes Berg kfree_rcu((struct ieee80211_regdomain *)r, rcu_head); 190458f4f9eSJohannes Berg } 191458f4f9eSJohannes Berg 192c492db37SJohannes Berg static struct regulatory_request *get_last_request(void) 193c492db37SJohannes Berg { 19438fd2143SJohannes Berg return rcu_dereference_rtnl(last_request); 195c492db37SJohannes Berg } 196c492db37SJohannes Berg 197e38f8a7aSLuis R. Rodriguez /* Used to queue up regulatory hints */ 198fe33eb39SLuis R. Rodriguez static LIST_HEAD(reg_requests_list); 199fe33eb39SLuis R. Rodriguez static spinlock_t reg_requests_lock; 200fe33eb39SLuis R. Rodriguez 201e38f8a7aSLuis R. Rodriguez /* Used to queue up beacon hints for review */ 202e38f8a7aSLuis R. Rodriguez static LIST_HEAD(reg_pending_beacons); 203e38f8a7aSLuis R. Rodriguez static spinlock_t reg_pending_beacons_lock; 204e38f8a7aSLuis R. Rodriguez 205e38f8a7aSLuis R. Rodriguez /* Used to keep track of processed beacon hints */ 206e38f8a7aSLuis R. Rodriguez static LIST_HEAD(reg_beacon_list); 207e38f8a7aSLuis R. Rodriguez 208e38f8a7aSLuis R. Rodriguez struct reg_beacon { 209e38f8a7aSLuis R. Rodriguez struct list_head list; 210e38f8a7aSLuis R. Rodriguez struct ieee80211_channel chan; 211e38f8a7aSLuis R. Rodriguez }; 212e38f8a7aSLuis R. Rodriguez 213f333a7a2SLuis R. Rodriguez static void reg_todo(struct work_struct *work); 214f333a7a2SLuis R. Rodriguez static DECLARE_WORK(reg_work, reg_todo); 215f333a7a2SLuis R. Rodriguez 216a90c7a31SLuis R. Rodriguez static void reg_timeout_work(struct work_struct *work); 217a90c7a31SLuis R. Rodriguez static DECLARE_DELAYED_WORK(reg_timeout, reg_timeout_work); 218a90c7a31SLuis R. Rodriguez 219734366deSJohannes Berg /* We keep a static world regulatory domain in case of the absence of CRDA */ 220734366deSJohannes Berg static const struct ieee80211_regdomain world_regdom = { 22190cdc6dfSVladimir Kondratiev .n_reg_rules = 6, 222734366deSJohannes Berg .alpha2 = "00", 223734366deSJohannes Berg .reg_rules = { 22468798a62SLuis R. Rodriguez /* IEEE 802.11b/g, channels 1..11 */ 22568798a62SLuis R. Rodriguez REG_RULE(2412-10, 2462+10, 40, 6, 20, 0), 22643c771a1SJohannes Berg /* IEEE 802.11b/g, channels 12..13. */ 22743c771a1SJohannes Berg REG_RULE(2467-10, 2472+10, 40, 6, 20, 2288fe02e16SLuis R. Rodriguez NL80211_RRF_NO_IR), 229611b6a82SLuis R. Rodriguez /* IEEE 802.11 channel 14 - Only JP enables 230611b6a82SLuis R. Rodriguez * this and for 802.11b only */ 231611b6a82SLuis R. Rodriguez REG_RULE(2484-10, 2484+10, 20, 6, 20, 2328fe02e16SLuis R. Rodriguez NL80211_RRF_NO_IR | 233611b6a82SLuis R. Rodriguez NL80211_RRF_NO_OFDM), 2343fc71f77SLuis R. Rodriguez /* IEEE 802.11a, channel 36..48 */ 235131a19bcSJohannes Berg REG_RULE(5180-10, 5240+10, 160, 6, 20, 2368fe02e16SLuis R. Rodriguez NL80211_RRF_NO_IR), 2373fc71f77SLuis R. Rodriguez 238131a19bcSJohannes Berg /* IEEE 802.11a, channel 52..64 - DFS required */ 239131a19bcSJohannes Berg REG_RULE(5260-10, 5320+10, 160, 6, 20, 2408fe02e16SLuis R. Rodriguez NL80211_RRF_NO_IR | 241131a19bcSJohannes Berg NL80211_RRF_DFS), 242131a19bcSJohannes Berg 243131a19bcSJohannes Berg /* IEEE 802.11a, channel 100..144 - DFS required */ 244131a19bcSJohannes Berg REG_RULE(5500-10, 5720+10, 160, 6, 20, 2458fe02e16SLuis R. Rodriguez NL80211_RRF_NO_IR | 246131a19bcSJohannes Berg NL80211_RRF_DFS), 2473fc71f77SLuis R. Rodriguez 2483fc71f77SLuis R. Rodriguez /* IEEE 802.11a, channel 149..165 */ 2498ab9d85cSJohannes Berg REG_RULE(5745-10, 5825+10, 80, 6, 20, 2508fe02e16SLuis R. Rodriguez NL80211_RRF_NO_IR), 25190cdc6dfSVladimir Kondratiev 25290cdc6dfSVladimir Kondratiev /* IEEE 802.11ad (60gHz), channels 1..3 */ 25390cdc6dfSVladimir Kondratiev REG_RULE(56160+2160*1-1080, 56160+2160*3+1080, 2160, 0, 0, 0), 254734366deSJohannes Berg } 255734366deSJohannes Berg }; 256734366deSJohannes Berg 25738fd2143SJohannes Berg /* protected by RTNL */ 258a3d2eaf0SJohannes Berg static const struct ieee80211_regdomain *cfg80211_world_regdom = 259a3d2eaf0SJohannes Berg &world_regdom; 260734366deSJohannes Berg 2616ee7d330SLuis R. Rodriguez static char *ieee80211_regdom = "00"; 26209d989d1SLuis R. Rodriguez static char user_alpha2[2]; 2636ee7d330SLuis R. Rodriguez 264734366deSJohannes Berg module_param(ieee80211_regdom, charp, 0444); 265734366deSJohannes Berg MODULE_PARM_DESC(ieee80211_regdom, "IEEE 802.11 regulatory domain code"); 266734366deSJohannes Berg 267c888393bSArik Nemtsov static void reg_free_request(struct regulatory_request *request) 2685ad6ef5eSLuis R. Rodriguez { 269c888393bSArik Nemtsov if (request != get_last_request()) 270c888393bSArik Nemtsov kfree(request); 271c888393bSArik Nemtsov } 272c888393bSArik Nemtsov 273c888393bSArik Nemtsov static void reg_free_last_request(void) 274c888393bSArik Nemtsov { 275c888393bSArik Nemtsov struct regulatory_request *lr = get_last_request(); 276c888393bSArik Nemtsov 2775ad6ef5eSLuis R. Rodriguez if (lr != &core_request_world && lr) 2785ad6ef5eSLuis R. Rodriguez kfree_rcu(lr, rcu_head); 2795ad6ef5eSLuis R. Rodriguez } 2805ad6ef5eSLuis R. Rodriguez 28105f1a3eaSLuis R. Rodriguez static void reg_update_last_request(struct regulatory_request *request) 28205f1a3eaSLuis R. Rodriguez { 283255e25b0SLuis R. Rodriguez struct regulatory_request *lr; 284255e25b0SLuis R. Rodriguez 285255e25b0SLuis R. Rodriguez lr = get_last_request(); 286255e25b0SLuis R. Rodriguez if (lr == request) 287255e25b0SLuis R. Rodriguez return; 288255e25b0SLuis R. Rodriguez 289c888393bSArik Nemtsov reg_free_last_request(); 29005f1a3eaSLuis R. Rodriguez rcu_assign_pointer(last_request, request); 29105f1a3eaSLuis R. Rodriguez } 29205f1a3eaSLuis R. Rodriguez 293379b82f4SJohannes Berg static void reset_regdomains(bool full_reset, 294379b82f4SJohannes Berg const struct ieee80211_regdomain *new_regdom) 295734366deSJohannes Berg { 296458f4f9eSJohannes Berg const struct ieee80211_regdomain *r; 297458f4f9eSJohannes Berg 29838fd2143SJohannes Berg ASSERT_RTNL(); 299e8da2bb4SJohannes Berg 300458f4f9eSJohannes Berg r = get_cfg80211_regdom(); 301458f4f9eSJohannes Berg 302942b25cfSJohannes Berg /* avoid freeing static information or freeing something twice */ 303458f4f9eSJohannes Berg if (r == cfg80211_world_regdom) 304458f4f9eSJohannes Berg r = NULL; 305942b25cfSJohannes Berg if (cfg80211_world_regdom == &world_regdom) 306942b25cfSJohannes Berg cfg80211_world_regdom = NULL; 307458f4f9eSJohannes Berg if (r == &world_regdom) 308458f4f9eSJohannes Berg r = NULL; 309942b25cfSJohannes Berg 310458f4f9eSJohannes Berg rcu_free_regdom(r); 311458f4f9eSJohannes Berg rcu_free_regdom(cfg80211_world_regdom); 312734366deSJohannes Berg 313a3d2eaf0SJohannes Berg cfg80211_world_regdom = &world_regdom; 314458f4f9eSJohannes Berg rcu_assign_pointer(cfg80211_regdomain, new_regdom); 315a042994dSLuis R. Rodriguez 316a042994dSLuis R. Rodriguez if (!full_reset) 317a042994dSLuis R. Rodriguez return; 318a042994dSLuis R. Rodriguez 31905f1a3eaSLuis R. Rodriguez reg_update_last_request(&core_request_world); 320734366deSJohannes Berg } 321734366deSJohannes Berg 322fb1fc7adSLuis R. Rodriguez /* 323fb1fc7adSLuis R. Rodriguez * Dynamic world regulatory domain requested by the wireless 324fb1fc7adSLuis R. Rodriguez * core upon initialization 325fb1fc7adSLuis R. Rodriguez */ 326a3d2eaf0SJohannes Berg static void update_world_regdomain(const struct ieee80211_regdomain *rd) 327734366deSJohannes Berg { 328c492db37SJohannes Berg struct regulatory_request *lr; 329734366deSJohannes Berg 330c492db37SJohannes Berg lr = get_last_request(); 331c492db37SJohannes Berg 332c492db37SJohannes Berg WARN_ON(!lr); 333e8da2bb4SJohannes Berg 334379b82f4SJohannes Berg reset_regdomains(false, rd); 335734366deSJohannes Berg 336734366deSJohannes Berg cfg80211_world_regdom = rd; 337734366deSJohannes Berg } 338734366deSJohannes Berg 339a3d2eaf0SJohannes Berg bool is_world_regdom(const char *alpha2) 340b2e1b302SLuis R. Rodriguez { 341b2e1b302SLuis R. Rodriguez if (!alpha2) 342b2e1b302SLuis R. Rodriguez return false; 3431a919318SJohannes Berg return alpha2[0] == '0' && alpha2[1] == '0'; 344b2e1b302SLuis R. Rodriguez } 345b2e1b302SLuis R. Rodriguez 346a3d2eaf0SJohannes Berg static bool is_alpha2_set(const char *alpha2) 347b2e1b302SLuis R. Rodriguez { 348b2e1b302SLuis R. Rodriguez if (!alpha2) 349b2e1b302SLuis R. Rodriguez return false; 3501a919318SJohannes Berg return alpha2[0] && alpha2[1]; 351b2e1b302SLuis R. Rodriguez } 352b2e1b302SLuis R. Rodriguez 353a3d2eaf0SJohannes Berg static bool is_unknown_alpha2(const char *alpha2) 354b2e1b302SLuis R. Rodriguez { 355b2e1b302SLuis R. Rodriguez if (!alpha2) 356b2e1b302SLuis R. Rodriguez return false; 357fb1fc7adSLuis R. Rodriguez /* 358fb1fc7adSLuis R. Rodriguez * Special case where regulatory domain was built by driver 359fb1fc7adSLuis R. Rodriguez * but a specific alpha2 cannot be determined 360fb1fc7adSLuis R. Rodriguez */ 3611a919318SJohannes Berg return alpha2[0] == '9' && alpha2[1] == '9'; 362b2e1b302SLuis R. Rodriguez } 363b2e1b302SLuis R. Rodriguez 3643f2355cbSLuis R. Rodriguez static bool is_intersected_alpha2(const char *alpha2) 3653f2355cbSLuis R. Rodriguez { 3663f2355cbSLuis R. Rodriguez if (!alpha2) 3673f2355cbSLuis R. Rodriguez return false; 368fb1fc7adSLuis R. Rodriguez /* 369fb1fc7adSLuis R. Rodriguez * Special case where regulatory domain is the 3703f2355cbSLuis R. Rodriguez * result of an intersection between two regulatory domain 371fb1fc7adSLuis R. Rodriguez * structures 372fb1fc7adSLuis R. Rodriguez */ 3731a919318SJohannes Berg return alpha2[0] == '9' && alpha2[1] == '8'; 3743f2355cbSLuis R. Rodriguez } 3753f2355cbSLuis R. Rodriguez 376a3d2eaf0SJohannes Berg static bool is_an_alpha2(const char *alpha2) 377b2e1b302SLuis R. Rodriguez { 378b2e1b302SLuis R. Rodriguez if (!alpha2) 379b2e1b302SLuis R. Rodriguez return false; 3801a919318SJohannes Berg return isalpha(alpha2[0]) && isalpha(alpha2[1]); 381b2e1b302SLuis R. Rodriguez } 382b2e1b302SLuis R. Rodriguez 383a3d2eaf0SJohannes Berg static bool alpha2_equal(const char *alpha2_x, const char *alpha2_y) 384b2e1b302SLuis R. Rodriguez { 385b2e1b302SLuis R. Rodriguez if (!alpha2_x || !alpha2_y) 386b2e1b302SLuis R. Rodriguez return false; 3871a919318SJohannes Berg return alpha2_x[0] == alpha2_y[0] && alpha2_x[1] == alpha2_y[1]; 388b2e1b302SLuis R. Rodriguez } 389b2e1b302SLuis R. Rodriguez 39069b1572bSLuis R. Rodriguez static bool regdom_changes(const char *alpha2) 391b2e1b302SLuis R. Rodriguez { 392458f4f9eSJohannes Berg const struct ieee80211_regdomain *r = get_cfg80211_regdom(); 393761cf7ecSLuis R. Rodriguez 394458f4f9eSJohannes Berg if (!r) 395b2e1b302SLuis R. Rodriguez return true; 396458f4f9eSJohannes Berg return !alpha2_equal(r->alpha2, alpha2); 397b2e1b302SLuis R. Rodriguez } 398b2e1b302SLuis R. Rodriguez 39909d989d1SLuis R. Rodriguez /* 40009d989d1SLuis R. Rodriguez * The NL80211_REGDOM_SET_BY_USER regdom alpha2 is cached, this lets 40109d989d1SLuis R. Rodriguez * you know if a valid regulatory hint with NL80211_REGDOM_SET_BY_USER 40209d989d1SLuis R. Rodriguez * has ever been issued. 40309d989d1SLuis R. Rodriguez */ 40409d989d1SLuis R. Rodriguez static bool is_user_regdom_saved(void) 40509d989d1SLuis R. Rodriguez { 40609d989d1SLuis R. Rodriguez if (user_alpha2[0] == '9' && user_alpha2[1] == '7') 40709d989d1SLuis R. Rodriguez return false; 40809d989d1SLuis R. Rodriguez 40909d989d1SLuis R. Rodriguez /* This would indicate a mistake on the design */ 4101a919318SJohannes Berg if (WARN(!is_world_regdom(user_alpha2) && !is_an_alpha2(user_alpha2), 41109d989d1SLuis R. Rodriguez "Unexpected user alpha2: %c%c\n", 4121a919318SJohannes Berg user_alpha2[0], user_alpha2[1])) 41309d989d1SLuis R. Rodriguez return false; 41409d989d1SLuis R. Rodriguez 41509d989d1SLuis R. Rodriguez return true; 41609d989d1SLuis R. Rodriguez } 41709d989d1SLuis R. Rodriguez 418e9763c3cSJohannes Berg static const struct ieee80211_regdomain * 419e9763c3cSJohannes Berg reg_copy_regd(const struct ieee80211_regdomain *src_regd) 4203b377ea9SJohn W. Linville { 4213b377ea9SJohn W. Linville struct ieee80211_regdomain *regd; 422e9763c3cSJohannes Berg int size_of_regd; 4233b377ea9SJohn W. Linville unsigned int i; 4243b377ea9SJohn W. Linville 42582f20856SJohannes Berg size_of_regd = 42682f20856SJohannes Berg sizeof(struct ieee80211_regdomain) + 42782f20856SJohannes Berg src_regd->n_reg_rules * sizeof(struct ieee80211_reg_rule); 4283b377ea9SJohn W. Linville 4293b377ea9SJohn W. Linville regd = kzalloc(size_of_regd, GFP_KERNEL); 4303b377ea9SJohn W. Linville if (!regd) 431e9763c3cSJohannes Berg return ERR_PTR(-ENOMEM); 4323b377ea9SJohn W. Linville 4333b377ea9SJohn W. Linville memcpy(regd, src_regd, sizeof(struct ieee80211_regdomain)); 4343b377ea9SJohn W. Linville 4353b377ea9SJohn W. Linville for (i = 0; i < src_regd->n_reg_rules; i++) 4363b377ea9SJohn W. Linville memcpy(®d->reg_rules[i], &src_regd->reg_rules[i], 4373b377ea9SJohn W. Linville sizeof(struct ieee80211_reg_rule)); 4383b377ea9SJohn W. Linville 439e9763c3cSJohannes Berg return regd; 4403b377ea9SJohn W. Linville } 4413b377ea9SJohn W. Linville 4423b377ea9SJohn W. Linville #ifdef CONFIG_CFG80211_INTERNAL_REGDB 4433b377ea9SJohn W. Linville struct reg_regdb_search_request { 4443b377ea9SJohn W. Linville char alpha2[2]; 4453b377ea9SJohn W. Linville struct list_head list; 4463b377ea9SJohn W. Linville }; 4473b377ea9SJohn W. Linville 4483b377ea9SJohn W. Linville static LIST_HEAD(reg_regdb_search_list); 449368d06f5SJohn W. Linville static DEFINE_MUTEX(reg_regdb_search_mutex); 4503b377ea9SJohn W. Linville 4513b377ea9SJohn W. Linville static void reg_regdb_search(struct work_struct *work) 4523b377ea9SJohn W. Linville { 4533b377ea9SJohn W. Linville struct reg_regdb_search_request *request; 454e9763c3cSJohannes Berg const struct ieee80211_regdomain *curdom, *regdom = NULL; 455e9763c3cSJohannes Berg int i; 456a85d0d7fSLuis R. Rodriguez 4575fe231e8SJohannes Berg rtnl_lock(); 4583b377ea9SJohn W. Linville 459368d06f5SJohn W. Linville mutex_lock(®_regdb_search_mutex); 4603b377ea9SJohn W. Linville while (!list_empty(®_regdb_search_list)) { 4613b377ea9SJohn W. Linville request = list_first_entry(®_regdb_search_list, 4623b377ea9SJohn W. Linville struct reg_regdb_search_request, 4633b377ea9SJohn W. Linville list); 4643b377ea9SJohn W. Linville list_del(&request->list); 4653b377ea9SJohn W. Linville 4663b377ea9SJohn W. Linville for (i = 0; i < reg_regdb_size; i++) { 4673b377ea9SJohn W. Linville curdom = reg_regdb[i]; 4683b377ea9SJohn W. Linville 4691a919318SJohannes Berg if (alpha2_equal(request->alpha2, curdom->alpha2)) { 470e9763c3cSJohannes Berg regdom = reg_copy_regd(curdom); 4713b377ea9SJohn W. Linville break; 4723b377ea9SJohn W. Linville } 4733b377ea9SJohn W. Linville } 4743b377ea9SJohn W. Linville 4753b377ea9SJohn W. Linville kfree(request); 4763b377ea9SJohn W. Linville } 477368d06f5SJohn W. Linville mutex_unlock(®_regdb_search_mutex); 478a85d0d7fSLuis R. Rodriguez 479e9763c3cSJohannes Berg if (!IS_ERR_OR_NULL(regdom)) 480a85d0d7fSLuis R. Rodriguez set_regdom(regdom); 481a85d0d7fSLuis R. Rodriguez 4825fe231e8SJohannes Berg rtnl_unlock(); 4833b377ea9SJohn W. Linville } 4843b377ea9SJohn W. Linville 4853b377ea9SJohn W. Linville static DECLARE_WORK(reg_regdb_work, reg_regdb_search); 4863b377ea9SJohn W. Linville 4873b377ea9SJohn W. Linville static void reg_regdb_query(const char *alpha2) 4883b377ea9SJohn W. Linville { 4893b377ea9SJohn W. Linville struct reg_regdb_search_request *request; 4903b377ea9SJohn W. Linville 4913b377ea9SJohn W. Linville if (!alpha2) 4923b377ea9SJohn W. Linville return; 4933b377ea9SJohn W. Linville 4943b377ea9SJohn W. Linville request = kzalloc(sizeof(struct reg_regdb_search_request), GFP_KERNEL); 4953b377ea9SJohn W. Linville if (!request) 4963b377ea9SJohn W. Linville return; 4973b377ea9SJohn W. Linville 4983b377ea9SJohn W. Linville memcpy(request->alpha2, alpha2, 2); 4993b377ea9SJohn W. Linville 500368d06f5SJohn W. Linville mutex_lock(®_regdb_search_mutex); 5013b377ea9SJohn W. Linville list_add_tail(&request->list, ®_regdb_search_list); 502368d06f5SJohn W. Linville mutex_unlock(®_regdb_search_mutex); 5033b377ea9SJohn W. Linville 5043b377ea9SJohn W. Linville schedule_work(®_regdb_work); 5053b377ea9SJohn W. Linville } 50680007efeSLuis R. Rodriguez 50780007efeSLuis R. Rodriguez /* Feel free to add any other sanity checks here */ 50880007efeSLuis R. Rodriguez static void reg_regdb_size_check(void) 50980007efeSLuis R. Rodriguez { 51080007efeSLuis R. Rodriguez /* We should ideally BUILD_BUG_ON() but then random builds would fail */ 51180007efeSLuis R. Rodriguez WARN_ONCE(!reg_regdb_size, "db.txt is empty, you should update it..."); 51280007efeSLuis R. Rodriguez } 5133b377ea9SJohn W. Linville #else 51480007efeSLuis R. Rodriguez static inline void reg_regdb_size_check(void) {} 5153b377ea9SJohn W. Linville static inline void reg_regdb_query(const char *alpha2) {} 5163b377ea9SJohn W. Linville #endif /* CONFIG_CFG80211_INTERNAL_REGDB */ 5173b377ea9SJohn W. Linville 518fb1fc7adSLuis R. Rodriguez /* 519fb1fc7adSLuis R. Rodriguez * This lets us keep regulatory code which is updated on a regulatory 5201226d258SJohannes Berg * basis in userspace. 521fb1fc7adSLuis R. Rodriguez */ 522b2e1b302SLuis R. Rodriguez static int call_crda(const char *alpha2) 523b2e1b302SLuis R. Rodriguez { 5241226d258SJohannes Berg char country[12]; 5251226d258SJohannes Berg char *env[] = { country, NULL }; 5261226d258SJohannes Berg 5271226d258SJohannes Berg snprintf(country, sizeof(country), "COUNTRY=%c%c", 5281226d258SJohannes Berg alpha2[0], alpha2[1]); 5291226d258SJohannes Berg 530b2e1b302SLuis R. Rodriguez if (!is_world_regdom((char *) alpha2)) 531e9c0268fSJoe Perches pr_info("Calling CRDA for country: %c%c\n", 532b2e1b302SLuis R. Rodriguez alpha2[0], alpha2[1]); 533b2e1b302SLuis R. Rodriguez else 534e9c0268fSJoe Perches pr_info("Calling CRDA to update world regulatory domain\n"); 5358318d78aSJohannes Berg 5363b377ea9SJohn W. Linville /* query internal regulatory database (if it exists) */ 5373b377ea9SJohn W. Linville reg_regdb_query(alpha2); 5383b377ea9SJohn W. Linville 5391226d258SJohannes Berg return kobject_uevent_env(®_pdev->dev.kobj, KOBJ_CHANGE, env); 540b2e1b302SLuis R. Rodriguez } 541b2e1b302SLuis R. Rodriguez 542fe6631ffSLuis R. Rodriguez static enum reg_request_treatment 543fe6631ffSLuis R. Rodriguez reg_call_crda(struct regulatory_request *request) 544fe6631ffSLuis R. Rodriguez { 545fe6631ffSLuis R. Rodriguez if (call_crda(request->alpha2)) 546fe6631ffSLuis R. Rodriguez return REG_REQ_IGNORE; 547fe6631ffSLuis R. Rodriguez return REG_REQ_OK; 548fe6631ffSLuis R. Rodriguez } 549fe6631ffSLuis R. Rodriguez 550e438768fSLuis R. Rodriguez bool reg_is_valid_request(const char *alpha2) 551b2e1b302SLuis R. Rodriguez { 552c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 55361405e97SLuis R. Rodriguez 554c492db37SJohannes Berg if (!lr || lr->processed) 555f6037d09SJohannes Berg return false; 556f6037d09SJohannes Berg 557c492db37SJohannes Berg return alpha2_equal(lr->alpha2, alpha2); 558b2e1b302SLuis R. Rodriguez } 559b2e1b302SLuis R. Rodriguez 560e3961af1SJanusz Dziedzic static const struct ieee80211_regdomain *reg_get_regdomain(struct wiphy *wiphy) 561e3961af1SJanusz Dziedzic { 562e3961af1SJanusz Dziedzic struct regulatory_request *lr = get_last_request(); 563e3961af1SJanusz Dziedzic 564e3961af1SJanusz Dziedzic /* 565e3961af1SJanusz Dziedzic * Follow the driver's regulatory domain, if present, unless a country 566e3961af1SJanusz Dziedzic * IE has been processed or a user wants to help complaince further 567e3961af1SJanusz Dziedzic */ 568e3961af1SJanusz Dziedzic if (lr->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE && 569e3961af1SJanusz Dziedzic lr->initiator != NL80211_REGDOM_SET_BY_USER && 570e3961af1SJanusz Dziedzic wiphy->regd) 571e3961af1SJanusz Dziedzic return get_wiphy_regdom(wiphy); 572e3961af1SJanusz Dziedzic 573e3961af1SJanusz Dziedzic return get_cfg80211_regdom(); 574e3961af1SJanusz Dziedzic } 575e3961af1SJanusz Dziedzic 576a6d4a534SArik Nemtsov static unsigned int 577a6d4a534SArik Nemtsov reg_get_max_bandwidth_from_range(const struct ieee80211_regdomain *rd, 57897524820SJanusz Dziedzic const struct ieee80211_reg_rule *rule) 57997524820SJanusz Dziedzic { 58097524820SJanusz Dziedzic const struct ieee80211_freq_range *freq_range = &rule->freq_range; 58197524820SJanusz Dziedzic const struct ieee80211_freq_range *freq_range_tmp; 58297524820SJanusz Dziedzic const struct ieee80211_reg_rule *tmp; 58397524820SJanusz Dziedzic u32 start_freq, end_freq, idx, no; 58497524820SJanusz Dziedzic 58597524820SJanusz Dziedzic for (idx = 0; idx < rd->n_reg_rules; idx++) 58697524820SJanusz Dziedzic if (rule == &rd->reg_rules[idx]) 58797524820SJanusz Dziedzic break; 58897524820SJanusz Dziedzic 58997524820SJanusz Dziedzic if (idx == rd->n_reg_rules) 59097524820SJanusz Dziedzic return 0; 59197524820SJanusz Dziedzic 59297524820SJanusz Dziedzic /* get start_freq */ 59397524820SJanusz Dziedzic no = idx; 59497524820SJanusz Dziedzic 59597524820SJanusz Dziedzic while (no) { 59697524820SJanusz Dziedzic tmp = &rd->reg_rules[--no]; 59797524820SJanusz Dziedzic freq_range_tmp = &tmp->freq_range; 59897524820SJanusz Dziedzic 59997524820SJanusz Dziedzic if (freq_range_tmp->end_freq_khz < freq_range->start_freq_khz) 60097524820SJanusz Dziedzic break; 60197524820SJanusz Dziedzic 60297524820SJanusz Dziedzic freq_range = freq_range_tmp; 60397524820SJanusz Dziedzic } 60497524820SJanusz Dziedzic 60597524820SJanusz Dziedzic start_freq = freq_range->start_freq_khz; 60697524820SJanusz Dziedzic 60797524820SJanusz Dziedzic /* get end_freq */ 60897524820SJanusz Dziedzic freq_range = &rule->freq_range; 60997524820SJanusz Dziedzic no = idx; 61097524820SJanusz Dziedzic 61197524820SJanusz Dziedzic while (no < rd->n_reg_rules - 1) { 61297524820SJanusz Dziedzic tmp = &rd->reg_rules[++no]; 61397524820SJanusz Dziedzic freq_range_tmp = &tmp->freq_range; 61497524820SJanusz Dziedzic 61597524820SJanusz Dziedzic if (freq_range_tmp->start_freq_khz > freq_range->end_freq_khz) 61697524820SJanusz Dziedzic break; 61797524820SJanusz Dziedzic 61897524820SJanusz Dziedzic freq_range = freq_range_tmp; 61997524820SJanusz Dziedzic } 62097524820SJanusz Dziedzic 62197524820SJanusz Dziedzic end_freq = freq_range->end_freq_khz; 62297524820SJanusz Dziedzic 62397524820SJanusz Dziedzic return end_freq - start_freq; 62497524820SJanusz Dziedzic } 62597524820SJanusz Dziedzic 626a6d4a534SArik Nemtsov unsigned int reg_get_max_bandwidth(const struct ieee80211_regdomain *rd, 627a6d4a534SArik Nemtsov const struct ieee80211_reg_rule *rule) 628a6d4a534SArik Nemtsov { 629a6d4a534SArik Nemtsov unsigned int bw = reg_get_max_bandwidth_from_range(rd, rule); 630a6d4a534SArik Nemtsov 631a6d4a534SArik Nemtsov if (rule->flags & NL80211_RRF_NO_160MHZ) 632a6d4a534SArik Nemtsov bw = min_t(unsigned int, bw, MHZ_TO_KHZ(80)); 633a6d4a534SArik Nemtsov if (rule->flags & NL80211_RRF_NO_80MHZ) 634a6d4a534SArik Nemtsov bw = min_t(unsigned int, bw, MHZ_TO_KHZ(40)); 635a6d4a534SArik Nemtsov 636a6d4a534SArik Nemtsov /* 637a6d4a534SArik Nemtsov * HT40+/HT40- limits are handled per-channel. Only limit BW if both 638a6d4a534SArik Nemtsov * are not allowed. 639a6d4a534SArik Nemtsov */ 640a6d4a534SArik Nemtsov if (rule->flags & NL80211_RRF_NO_HT40MINUS && 641a6d4a534SArik Nemtsov rule->flags & NL80211_RRF_NO_HT40PLUS) 642a6d4a534SArik Nemtsov bw = min_t(unsigned int, bw, MHZ_TO_KHZ(20)); 643a6d4a534SArik Nemtsov 644a6d4a534SArik Nemtsov return bw; 645a6d4a534SArik Nemtsov } 646a6d4a534SArik Nemtsov 647b2e1b302SLuis R. Rodriguez /* Sanity check on a regulatory rule */ 648a3d2eaf0SJohannes Berg static bool is_valid_reg_rule(const struct ieee80211_reg_rule *rule) 649b2e1b302SLuis R. Rodriguez { 650a3d2eaf0SJohannes Berg const struct ieee80211_freq_range *freq_range = &rule->freq_range; 651b2e1b302SLuis R. Rodriguez u32 freq_diff; 652b2e1b302SLuis R. Rodriguez 65391e99004SLuis R. Rodriguez if (freq_range->start_freq_khz <= 0 || freq_range->end_freq_khz <= 0) 654b2e1b302SLuis R. Rodriguez return false; 655b2e1b302SLuis R. Rodriguez 656b2e1b302SLuis R. Rodriguez if (freq_range->start_freq_khz > freq_range->end_freq_khz) 657b2e1b302SLuis R. Rodriguez return false; 658b2e1b302SLuis R. Rodriguez 659b2e1b302SLuis R. Rodriguez freq_diff = freq_range->end_freq_khz - freq_range->start_freq_khz; 660b2e1b302SLuis R. Rodriguez 661bd05f28eSRoel Kluin if (freq_range->end_freq_khz <= freq_range->start_freq_khz || 662bd05f28eSRoel Kluin freq_range->max_bandwidth_khz > freq_diff) 663b2e1b302SLuis R. Rodriguez return false; 664b2e1b302SLuis R. Rodriguez 665b2e1b302SLuis R. Rodriguez return true; 666b2e1b302SLuis R. Rodriguez } 667b2e1b302SLuis R. Rodriguez 668a3d2eaf0SJohannes Berg static bool is_valid_rd(const struct ieee80211_regdomain *rd) 669b2e1b302SLuis R. Rodriguez { 670a3d2eaf0SJohannes Berg const struct ieee80211_reg_rule *reg_rule = NULL; 671b2e1b302SLuis R. Rodriguez unsigned int i; 672b2e1b302SLuis R. Rodriguez 673b2e1b302SLuis R. Rodriguez if (!rd->n_reg_rules) 674b2e1b302SLuis R. Rodriguez return false; 675b2e1b302SLuis R. Rodriguez 67688dc1c3fSLuis R. Rodriguez if (WARN_ON(rd->n_reg_rules > NL80211_MAX_SUPP_REG_RULES)) 67788dc1c3fSLuis R. Rodriguez return false; 67888dc1c3fSLuis R. Rodriguez 679b2e1b302SLuis R. Rodriguez for (i = 0; i < rd->n_reg_rules; i++) { 680b2e1b302SLuis R. Rodriguez reg_rule = &rd->reg_rules[i]; 681b2e1b302SLuis R. Rodriguez if (!is_valid_reg_rule(reg_rule)) 682b2e1b302SLuis R. Rodriguez return false; 683b2e1b302SLuis R. Rodriguez } 684b2e1b302SLuis R. Rodriguez 685b2e1b302SLuis R. Rodriguez return true; 686b2e1b302SLuis R. Rodriguez } 687b2e1b302SLuis R. Rodriguez 688038659e7SLuis R. Rodriguez static bool reg_does_bw_fit(const struct ieee80211_freq_range *freq_range, 689fe7ef5e9SJohannes Berg u32 center_freq_khz, u32 bw_khz) 690b2e1b302SLuis R. Rodriguez { 691038659e7SLuis R. Rodriguez u32 start_freq_khz, end_freq_khz; 692038659e7SLuis R. Rodriguez 693038659e7SLuis R. Rodriguez start_freq_khz = center_freq_khz - (bw_khz/2); 694038659e7SLuis R. Rodriguez end_freq_khz = center_freq_khz + (bw_khz/2); 695038659e7SLuis R. Rodriguez 696b2e1b302SLuis R. Rodriguez if (start_freq_khz >= freq_range->start_freq_khz && 697b2e1b302SLuis R. Rodriguez end_freq_khz <= freq_range->end_freq_khz) 698038659e7SLuis R. Rodriguez return true; 699038659e7SLuis R. Rodriguez 700038659e7SLuis R. Rodriguez return false; 701b2e1b302SLuis R. Rodriguez } 702b2e1b302SLuis R. Rodriguez 7030c7dc45dSLuis R. Rodriguez /** 7040c7dc45dSLuis R. Rodriguez * freq_in_rule_band - tells us if a frequency is in a frequency band 7050c7dc45dSLuis R. Rodriguez * @freq_range: frequency rule we want to query 7060c7dc45dSLuis R. Rodriguez * @freq_khz: frequency we are inquiring about 7070c7dc45dSLuis R. Rodriguez * 7080c7dc45dSLuis R. Rodriguez * This lets us know if a specific frequency rule is or is not relevant to 7090c7dc45dSLuis R. Rodriguez * a specific frequency's band. Bands are device specific and artificial 71064629b9dSVladimir Kondratiev * definitions (the "2.4 GHz band", the "5 GHz band" and the "60GHz band"), 71164629b9dSVladimir Kondratiev * however it is safe for now to assume that a frequency rule should not be 71264629b9dSVladimir Kondratiev * part of a frequency's band if the start freq or end freq are off by more 71364629b9dSVladimir Kondratiev * than 2 GHz for the 2.4 and 5 GHz bands, and by more than 10 GHz for the 71464629b9dSVladimir Kondratiev * 60 GHz band. 7150c7dc45dSLuis R. Rodriguez * This resolution can be lowered and should be considered as we add 7160c7dc45dSLuis R. Rodriguez * regulatory rule support for other "bands". 7170c7dc45dSLuis R. Rodriguez **/ 7180c7dc45dSLuis R. Rodriguez static bool freq_in_rule_band(const struct ieee80211_freq_range *freq_range, 7190c7dc45dSLuis R. Rodriguez u32 freq_khz) 7200c7dc45dSLuis R. Rodriguez { 7210c7dc45dSLuis R. Rodriguez #define ONE_GHZ_IN_KHZ 1000000 72264629b9dSVladimir Kondratiev /* 72364629b9dSVladimir Kondratiev * From 802.11ad: directional multi-gigabit (DMG): 72464629b9dSVladimir Kondratiev * Pertaining to operation in a frequency band containing a channel 72564629b9dSVladimir Kondratiev * with the Channel starting frequency above 45 GHz. 72664629b9dSVladimir Kondratiev */ 72764629b9dSVladimir Kondratiev u32 limit = freq_khz > 45 * ONE_GHZ_IN_KHZ ? 72864629b9dSVladimir Kondratiev 10 * ONE_GHZ_IN_KHZ : 2 * ONE_GHZ_IN_KHZ; 72964629b9dSVladimir Kondratiev if (abs(freq_khz - freq_range->start_freq_khz) <= limit) 7300c7dc45dSLuis R. Rodriguez return true; 73164629b9dSVladimir Kondratiev if (abs(freq_khz - freq_range->end_freq_khz) <= limit) 7320c7dc45dSLuis R. Rodriguez return true; 7330c7dc45dSLuis R. Rodriguez return false; 7340c7dc45dSLuis R. Rodriguez #undef ONE_GHZ_IN_KHZ 7350c7dc45dSLuis R. Rodriguez } 7360c7dc45dSLuis R. Rodriguez 737fb1fc7adSLuis R. Rodriguez /* 738adbfb058SLuis R. Rodriguez * Later on we can perhaps use the more restrictive DFS 739adbfb058SLuis R. Rodriguez * region but we don't have information for that yet so 740adbfb058SLuis R. Rodriguez * for now simply disallow conflicts. 741adbfb058SLuis R. Rodriguez */ 742adbfb058SLuis R. Rodriguez static enum nl80211_dfs_regions 743adbfb058SLuis R. Rodriguez reg_intersect_dfs_region(const enum nl80211_dfs_regions dfs_region1, 744adbfb058SLuis R. Rodriguez const enum nl80211_dfs_regions dfs_region2) 745adbfb058SLuis R. Rodriguez { 746adbfb058SLuis R. Rodriguez if (dfs_region1 != dfs_region2) 747adbfb058SLuis R. Rodriguez return NL80211_DFS_UNSET; 748adbfb058SLuis R. Rodriguez return dfs_region1; 749adbfb058SLuis R. Rodriguez } 750adbfb058SLuis R. Rodriguez 751adbfb058SLuis R. Rodriguez /* 752fb1fc7adSLuis R. Rodriguez * Helper for regdom_intersect(), this does the real 753fb1fc7adSLuis R. Rodriguez * mathematical intersection fun 754fb1fc7adSLuis R. Rodriguez */ 75597524820SJanusz Dziedzic static int reg_rules_intersect(const struct ieee80211_regdomain *rd1, 75697524820SJanusz Dziedzic const struct ieee80211_regdomain *rd2, 75797524820SJanusz Dziedzic const struct ieee80211_reg_rule *rule1, 7589c96477dSLuis R. Rodriguez const struct ieee80211_reg_rule *rule2, 7599c96477dSLuis R. Rodriguez struct ieee80211_reg_rule *intersected_rule) 7609c96477dSLuis R. Rodriguez { 7619c96477dSLuis R. Rodriguez const struct ieee80211_freq_range *freq_range1, *freq_range2; 7629c96477dSLuis R. Rodriguez struct ieee80211_freq_range *freq_range; 7639c96477dSLuis R. Rodriguez const struct ieee80211_power_rule *power_rule1, *power_rule2; 7649c96477dSLuis R. Rodriguez struct ieee80211_power_rule *power_rule; 76597524820SJanusz Dziedzic u32 freq_diff, max_bandwidth1, max_bandwidth2; 7669c96477dSLuis R. Rodriguez 7679c96477dSLuis R. Rodriguez freq_range1 = &rule1->freq_range; 7689c96477dSLuis R. Rodriguez freq_range2 = &rule2->freq_range; 7699c96477dSLuis R. Rodriguez freq_range = &intersected_rule->freq_range; 7709c96477dSLuis R. Rodriguez 7719c96477dSLuis R. Rodriguez power_rule1 = &rule1->power_rule; 7729c96477dSLuis R. Rodriguez power_rule2 = &rule2->power_rule; 7739c96477dSLuis R. Rodriguez power_rule = &intersected_rule->power_rule; 7749c96477dSLuis R. Rodriguez 7759c96477dSLuis R. Rodriguez freq_range->start_freq_khz = max(freq_range1->start_freq_khz, 7769c96477dSLuis R. Rodriguez freq_range2->start_freq_khz); 7779c96477dSLuis R. Rodriguez freq_range->end_freq_khz = min(freq_range1->end_freq_khz, 7789c96477dSLuis R. Rodriguez freq_range2->end_freq_khz); 77997524820SJanusz Dziedzic 78097524820SJanusz Dziedzic max_bandwidth1 = freq_range1->max_bandwidth_khz; 78197524820SJanusz Dziedzic max_bandwidth2 = freq_range2->max_bandwidth_khz; 78297524820SJanusz Dziedzic 783b0dfd2eaSJanusz Dziedzic if (rule1->flags & NL80211_RRF_AUTO_BW) 78497524820SJanusz Dziedzic max_bandwidth1 = reg_get_max_bandwidth(rd1, rule1); 785b0dfd2eaSJanusz Dziedzic if (rule2->flags & NL80211_RRF_AUTO_BW) 78697524820SJanusz Dziedzic max_bandwidth2 = reg_get_max_bandwidth(rd2, rule2); 78797524820SJanusz Dziedzic 78897524820SJanusz Dziedzic freq_range->max_bandwidth_khz = min(max_bandwidth1, max_bandwidth2); 7899c96477dSLuis R. Rodriguez 790b0dfd2eaSJanusz Dziedzic intersected_rule->flags = rule1->flags | rule2->flags; 791b0dfd2eaSJanusz Dziedzic 792b0dfd2eaSJanusz Dziedzic /* 793b0dfd2eaSJanusz Dziedzic * In case NL80211_RRF_AUTO_BW requested for both rules 794b0dfd2eaSJanusz Dziedzic * set AUTO_BW in intersected rule also. Next we will 795b0dfd2eaSJanusz Dziedzic * calculate BW correctly in handle_channel function. 796b0dfd2eaSJanusz Dziedzic * In other case remove AUTO_BW flag while we calculate 797b0dfd2eaSJanusz Dziedzic * maximum bandwidth correctly and auto calculation is 798b0dfd2eaSJanusz Dziedzic * not required. 799b0dfd2eaSJanusz Dziedzic */ 800b0dfd2eaSJanusz Dziedzic if ((rule1->flags & NL80211_RRF_AUTO_BW) && 801b0dfd2eaSJanusz Dziedzic (rule2->flags & NL80211_RRF_AUTO_BW)) 802b0dfd2eaSJanusz Dziedzic intersected_rule->flags |= NL80211_RRF_AUTO_BW; 803b0dfd2eaSJanusz Dziedzic else 804b0dfd2eaSJanusz Dziedzic intersected_rule->flags &= ~NL80211_RRF_AUTO_BW; 805b0dfd2eaSJanusz Dziedzic 8069c96477dSLuis R. Rodriguez freq_diff = freq_range->end_freq_khz - freq_range->start_freq_khz; 8079c96477dSLuis R. Rodriguez if (freq_range->max_bandwidth_khz > freq_diff) 8089c96477dSLuis R. Rodriguez freq_range->max_bandwidth_khz = freq_diff; 8099c96477dSLuis R. Rodriguez 8109c96477dSLuis R. Rodriguez power_rule->max_eirp = min(power_rule1->max_eirp, 8119c96477dSLuis R. Rodriguez power_rule2->max_eirp); 8129c96477dSLuis R. Rodriguez power_rule->max_antenna_gain = min(power_rule1->max_antenna_gain, 8139c96477dSLuis R. Rodriguez power_rule2->max_antenna_gain); 8149c96477dSLuis R. Rodriguez 815089027e5SJanusz Dziedzic intersected_rule->dfs_cac_ms = max(rule1->dfs_cac_ms, 816089027e5SJanusz Dziedzic rule2->dfs_cac_ms); 817089027e5SJanusz Dziedzic 8189c96477dSLuis R. Rodriguez if (!is_valid_reg_rule(intersected_rule)) 8199c96477dSLuis R. Rodriguez return -EINVAL; 8209c96477dSLuis R. Rodriguez 8219c96477dSLuis R. Rodriguez return 0; 8229c96477dSLuis R. Rodriguez } 8239c96477dSLuis R. Rodriguez 824a62a1aedSEliad Peller /* check whether old rule contains new rule */ 825a62a1aedSEliad Peller static bool rule_contains(struct ieee80211_reg_rule *r1, 826a62a1aedSEliad Peller struct ieee80211_reg_rule *r2) 827a62a1aedSEliad Peller { 828a62a1aedSEliad Peller /* for simplicity, currently consider only same flags */ 829a62a1aedSEliad Peller if (r1->flags != r2->flags) 830a62a1aedSEliad Peller return false; 831a62a1aedSEliad Peller 832a62a1aedSEliad Peller /* verify r1 is more restrictive */ 833a62a1aedSEliad Peller if ((r1->power_rule.max_antenna_gain > 834a62a1aedSEliad Peller r2->power_rule.max_antenna_gain) || 835a62a1aedSEliad Peller r1->power_rule.max_eirp > r2->power_rule.max_eirp) 836a62a1aedSEliad Peller return false; 837a62a1aedSEliad Peller 838a62a1aedSEliad Peller /* make sure r2's range is contained within r1 */ 839a62a1aedSEliad Peller if (r1->freq_range.start_freq_khz > r2->freq_range.start_freq_khz || 840a62a1aedSEliad Peller r1->freq_range.end_freq_khz < r2->freq_range.end_freq_khz) 841a62a1aedSEliad Peller return false; 842a62a1aedSEliad Peller 843a62a1aedSEliad Peller /* and finally verify that r1.max_bw >= r2.max_bw */ 844a62a1aedSEliad Peller if (r1->freq_range.max_bandwidth_khz < 845a62a1aedSEliad Peller r2->freq_range.max_bandwidth_khz) 846a62a1aedSEliad Peller return false; 847a62a1aedSEliad Peller 848a62a1aedSEliad Peller return true; 849a62a1aedSEliad Peller } 850a62a1aedSEliad Peller 851a62a1aedSEliad Peller /* add or extend current rules. do nothing if rule is already contained */ 852a62a1aedSEliad Peller static void add_rule(struct ieee80211_reg_rule *rule, 853a62a1aedSEliad Peller struct ieee80211_reg_rule *reg_rules, u32 *n_rules) 854a62a1aedSEliad Peller { 855a62a1aedSEliad Peller struct ieee80211_reg_rule *tmp_rule; 856a62a1aedSEliad Peller int i; 857a62a1aedSEliad Peller 858a62a1aedSEliad Peller for (i = 0; i < *n_rules; i++) { 859a62a1aedSEliad Peller tmp_rule = ®_rules[i]; 860a62a1aedSEliad Peller /* rule is already contained - do nothing */ 861a62a1aedSEliad Peller if (rule_contains(tmp_rule, rule)) 862a62a1aedSEliad Peller return; 863a62a1aedSEliad Peller 864a62a1aedSEliad Peller /* extend rule if possible */ 865a62a1aedSEliad Peller if (rule_contains(rule, tmp_rule)) { 866a62a1aedSEliad Peller memcpy(tmp_rule, rule, sizeof(*rule)); 867a62a1aedSEliad Peller return; 868a62a1aedSEliad Peller } 869a62a1aedSEliad Peller } 870a62a1aedSEliad Peller 871a62a1aedSEliad Peller memcpy(®_rules[*n_rules], rule, sizeof(*rule)); 872a62a1aedSEliad Peller (*n_rules)++; 873a62a1aedSEliad Peller } 874a62a1aedSEliad Peller 8759c96477dSLuis R. Rodriguez /** 8769c96477dSLuis R. Rodriguez * regdom_intersect - do the intersection between two regulatory domains 8779c96477dSLuis R. Rodriguez * @rd1: first regulatory domain 8789c96477dSLuis R. Rodriguez * @rd2: second regulatory domain 8799c96477dSLuis R. Rodriguez * 8809c96477dSLuis R. Rodriguez * Use this function to get the intersection between two regulatory domains. 8819c96477dSLuis R. Rodriguez * Once completed we will mark the alpha2 for the rd as intersected, "98", 8829c96477dSLuis R. Rodriguez * as no one single alpha2 can represent this regulatory domain. 8839c96477dSLuis R. Rodriguez * 8849c96477dSLuis R. Rodriguez * Returns a pointer to the regulatory domain structure which will hold the 8859c96477dSLuis R. Rodriguez * resulting intersection of rules between rd1 and rd2. We will 8869c96477dSLuis R. Rodriguez * kzalloc() this structure for you. 8879c96477dSLuis R. Rodriguez */ 8881a919318SJohannes Berg static struct ieee80211_regdomain * 8891a919318SJohannes Berg regdom_intersect(const struct ieee80211_regdomain *rd1, 8909c96477dSLuis R. Rodriguez const struct ieee80211_regdomain *rd2) 8919c96477dSLuis R. Rodriguez { 8929c96477dSLuis R. Rodriguez int r, size_of_regd; 8939c96477dSLuis R. Rodriguez unsigned int x, y; 894a62a1aedSEliad Peller unsigned int num_rules = 0; 8959c96477dSLuis R. Rodriguez const struct ieee80211_reg_rule *rule1, *rule2; 896a62a1aedSEliad Peller struct ieee80211_reg_rule intersected_rule; 8979c96477dSLuis R. Rodriguez struct ieee80211_regdomain *rd; 8989c96477dSLuis R. Rodriguez 8999c96477dSLuis R. Rodriguez if (!rd1 || !rd2) 9009c96477dSLuis R. Rodriguez return NULL; 9019c96477dSLuis R. Rodriguez 902fb1fc7adSLuis R. Rodriguez /* 903fb1fc7adSLuis R. Rodriguez * First we get a count of the rules we'll need, then we actually 9049c96477dSLuis R. Rodriguez * build them. This is to so we can malloc() and free() a 9059c96477dSLuis R. Rodriguez * regdomain once. The reason we use reg_rules_intersect() here 9069c96477dSLuis R. Rodriguez * is it will return -EINVAL if the rule computed makes no sense. 907fb1fc7adSLuis R. Rodriguez * All rules that do check out OK are valid. 908fb1fc7adSLuis R. Rodriguez */ 9099c96477dSLuis R. Rodriguez 9109c96477dSLuis R. Rodriguez for (x = 0; x < rd1->n_reg_rules; x++) { 9119c96477dSLuis R. Rodriguez rule1 = &rd1->reg_rules[x]; 9129c96477dSLuis R. Rodriguez for (y = 0; y < rd2->n_reg_rules; y++) { 9139c96477dSLuis R. Rodriguez rule2 = &rd2->reg_rules[y]; 91497524820SJanusz Dziedzic if (!reg_rules_intersect(rd1, rd2, rule1, rule2, 915a62a1aedSEliad Peller &intersected_rule)) 9169c96477dSLuis R. Rodriguez num_rules++; 9179c96477dSLuis R. Rodriguez } 9189c96477dSLuis R. Rodriguez } 9199c96477dSLuis R. Rodriguez 9209c96477dSLuis R. Rodriguez if (!num_rules) 9219c96477dSLuis R. Rodriguez return NULL; 9229c96477dSLuis R. Rodriguez 9239c96477dSLuis R. Rodriguez size_of_regd = sizeof(struct ieee80211_regdomain) + 92482f20856SJohannes Berg num_rules * sizeof(struct ieee80211_reg_rule); 9259c96477dSLuis R. Rodriguez 9269c96477dSLuis R. Rodriguez rd = kzalloc(size_of_regd, GFP_KERNEL); 9279c96477dSLuis R. Rodriguez if (!rd) 9289c96477dSLuis R. Rodriguez return NULL; 9299c96477dSLuis R. Rodriguez 930a62a1aedSEliad Peller for (x = 0; x < rd1->n_reg_rules; x++) { 9319c96477dSLuis R. Rodriguez rule1 = &rd1->reg_rules[x]; 932a62a1aedSEliad Peller for (y = 0; y < rd2->n_reg_rules; y++) { 9339c96477dSLuis R. Rodriguez rule2 = &rd2->reg_rules[y]; 93497524820SJanusz Dziedzic r = reg_rules_intersect(rd1, rd2, rule1, rule2, 935a62a1aedSEliad Peller &intersected_rule); 936fb1fc7adSLuis R. Rodriguez /* 937fb1fc7adSLuis R. Rodriguez * No need to memset here the intersected rule here as 938fb1fc7adSLuis R. Rodriguez * we're not using the stack anymore 939fb1fc7adSLuis R. Rodriguez */ 9409c96477dSLuis R. Rodriguez if (r) 9419c96477dSLuis R. Rodriguez continue; 942a62a1aedSEliad Peller 943a62a1aedSEliad Peller add_rule(&intersected_rule, rd->reg_rules, 944a62a1aedSEliad Peller &rd->n_reg_rules); 9459c96477dSLuis R. Rodriguez } 9469c96477dSLuis R. Rodriguez } 9479c96477dSLuis R. Rodriguez 9489c96477dSLuis R. Rodriguez rd->alpha2[0] = '9'; 9499c96477dSLuis R. Rodriguez rd->alpha2[1] = '8'; 950adbfb058SLuis R. Rodriguez rd->dfs_region = reg_intersect_dfs_region(rd1->dfs_region, 951adbfb058SLuis R. Rodriguez rd2->dfs_region); 9529c96477dSLuis R. Rodriguez 9539c96477dSLuis R. Rodriguez return rd; 9549c96477dSLuis R. Rodriguez } 9559c96477dSLuis R. Rodriguez 956fb1fc7adSLuis R. Rodriguez /* 957fb1fc7adSLuis R. Rodriguez * XXX: add support for the rest of enum nl80211_reg_rule_flags, we may 958fb1fc7adSLuis R. Rodriguez * want to just have the channel structure use these 959fb1fc7adSLuis R. Rodriguez */ 960b2e1b302SLuis R. Rodriguez static u32 map_regdom_flags(u32 rd_flags) 961b2e1b302SLuis R. Rodriguez { 962b2e1b302SLuis R. Rodriguez u32 channel_flags = 0; 9638fe02e16SLuis R. Rodriguez if (rd_flags & NL80211_RRF_NO_IR_ALL) 9648fe02e16SLuis R. Rodriguez channel_flags |= IEEE80211_CHAN_NO_IR; 965b2e1b302SLuis R. Rodriguez if (rd_flags & NL80211_RRF_DFS) 966b2e1b302SLuis R. Rodriguez channel_flags |= IEEE80211_CHAN_RADAR; 96703f6b084SSeth Forshee if (rd_flags & NL80211_RRF_NO_OFDM) 96803f6b084SSeth Forshee channel_flags |= IEEE80211_CHAN_NO_OFDM; 969570dbde1SDavid Spinadel if (rd_flags & NL80211_RRF_NO_OUTDOOR) 970570dbde1SDavid Spinadel channel_flags |= IEEE80211_CHAN_INDOOR_ONLY; 971a6d4a534SArik Nemtsov if (rd_flags & NL80211_RRF_GO_CONCURRENT) 972a6d4a534SArik Nemtsov channel_flags |= IEEE80211_CHAN_GO_CONCURRENT; 973a6d4a534SArik Nemtsov if (rd_flags & NL80211_RRF_NO_HT40MINUS) 974a6d4a534SArik Nemtsov channel_flags |= IEEE80211_CHAN_NO_HT40MINUS; 975a6d4a534SArik Nemtsov if (rd_flags & NL80211_RRF_NO_HT40PLUS) 976a6d4a534SArik Nemtsov channel_flags |= IEEE80211_CHAN_NO_HT40PLUS; 977a6d4a534SArik Nemtsov if (rd_flags & NL80211_RRF_NO_80MHZ) 978a6d4a534SArik Nemtsov channel_flags |= IEEE80211_CHAN_NO_80MHZ; 979a6d4a534SArik Nemtsov if (rd_flags & NL80211_RRF_NO_160MHZ) 980a6d4a534SArik Nemtsov channel_flags |= IEEE80211_CHAN_NO_160MHZ; 981b2e1b302SLuis R. Rodriguez return channel_flags; 982b2e1b302SLuis R. Rodriguez } 983b2e1b302SLuis R. Rodriguez 984361c9c8bSJohannes Berg static const struct ieee80211_reg_rule * 985361c9c8bSJohannes Berg freq_reg_info_regd(struct wiphy *wiphy, u32 center_freq, 9865d885b99SJohannes Berg const struct ieee80211_regdomain *regd) 9878318d78aSJohannes Berg { 9888318d78aSJohannes Berg int i; 9890c7dc45dSLuis R. Rodriguez bool band_rule_found = false; 990038659e7SLuis R. Rodriguez bool bw_fits = false; 991038659e7SLuis R. Rodriguez 9923e0c3ff3SLuis R. Rodriguez if (!regd) 993361c9c8bSJohannes Berg return ERR_PTR(-EINVAL); 994b2e1b302SLuis R. Rodriguez 9953e0c3ff3SLuis R. Rodriguez for (i = 0; i < regd->n_reg_rules; i++) { 996b2e1b302SLuis R. Rodriguez const struct ieee80211_reg_rule *rr; 997b2e1b302SLuis R. Rodriguez const struct ieee80211_freq_range *fr = NULL; 998b2e1b302SLuis R. Rodriguez 9993e0c3ff3SLuis R. Rodriguez rr = ®d->reg_rules[i]; 1000b2e1b302SLuis R. Rodriguez fr = &rr->freq_range; 10010c7dc45dSLuis R. Rodriguez 1002fb1fc7adSLuis R. Rodriguez /* 1003fb1fc7adSLuis R. Rodriguez * We only need to know if one frequency rule was 10040c7dc45dSLuis R. Rodriguez * was in center_freq's band, that's enough, so lets 1005fb1fc7adSLuis R. Rodriguez * not overwrite it once found 1006fb1fc7adSLuis R. Rodriguez */ 10070c7dc45dSLuis R. Rodriguez if (!band_rule_found) 10080c7dc45dSLuis R. Rodriguez band_rule_found = freq_in_rule_band(fr, center_freq); 10090c7dc45dSLuis R. Rodriguez 1010e33e2241SJohannes Berg bw_fits = reg_does_bw_fit(fr, center_freq, MHZ_TO_KHZ(20)); 10110c7dc45dSLuis R. Rodriguez 1012361c9c8bSJohannes Berg if (band_rule_found && bw_fits) 1013361c9c8bSJohannes Berg return rr; 10148318d78aSJohannes Berg } 10158318d78aSJohannes Berg 10160c7dc45dSLuis R. Rodriguez if (!band_rule_found) 1017361c9c8bSJohannes Berg return ERR_PTR(-ERANGE); 10180c7dc45dSLuis R. Rodriguez 1019361c9c8bSJohannes Berg return ERR_PTR(-EINVAL); 1020b2e1b302SLuis R. Rodriguez } 1021b2e1b302SLuis R. Rodriguez 1022361c9c8bSJohannes Berg const struct ieee80211_reg_rule *freq_reg_info(struct wiphy *wiphy, 1023361c9c8bSJohannes Berg u32 center_freq) 10241fa25e41SLuis R. Rodriguez { 10255d885b99SJohannes Berg const struct ieee80211_regdomain *regd; 10261a919318SJohannes Berg 1027e3961af1SJanusz Dziedzic regd = reg_get_regdomain(wiphy); 10285d885b99SJohannes Berg 1029361c9c8bSJohannes Berg return freq_reg_info_regd(wiphy, center_freq, regd); 10301fa25e41SLuis R. Rodriguez } 10314f366c5dSJohn W. Linville EXPORT_SYMBOL(freq_reg_info); 1032b2e1b302SLuis R. Rodriguez 1033034c6d6eSLuis R. Rodriguez const char *reg_initiator_name(enum nl80211_reg_initiator initiator) 1034926a0a09SLuis R. Rodriguez { 1035926a0a09SLuis R. Rodriguez switch (initiator) { 1036926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_CORE: 1037034c6d6eSLuis R. Rodriguez return "core"; 1038926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_USER: 1039034c6d6eSLuis R. Rodriguez return "user"; 1040926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_DRIVER: 1041034c6d6eSLuis R. Rodriguez return "driver"; 1042926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_COUNTRY_IE: 1043034c6d6eSLuis R. Rodriguez return "country IE"; 1044926a0a09SLuis R. Rodriguez default: 1045926a0a09SLuis R. Rodriguez WARN_ON(1); 1046034c6d6eSLuis R. Rodriguez return "bug"; 1047926a0a09SLuis R. Rodriguez } 1048926a0a09SLuis R. Rodriguez } 1049034c6d6eSLuis R. Rodriguez EXPORT_SYMBOL(reg_initiator_name); 1050e702d3cfSLuis R. Rodriguez 1051034c6d6eSLuis R. Rodriguez #ifdef CONFIG_CFG80211_REG_DEBUG 1052b0dfd2eaSJanusz Dziedzic static void chan_reg_rule_print_dbg(const struct ieee80211_regdomain *regd, 1053b0dfd2eaSJanusz Dziedzic struct ieee80211_channel *chan, 1054e702d3cfSLuis R. Rodriguez const struct ieee80211_reg_rule *reg_rule) 1055e702d3cfSLuis R. Rodriguez { 1056e702d3cfSLuis R. Rodriguez const struct ieee80211_power_rule *power_rule; 1057e702d3cfSLuis R. Rodriguez const struct ieee80211_freq_range *freq_range; 1058b0dfd2eaSJanusz Dziedzic char max_antenna_gain[32], bw[32]; 1059e702d3cfSLuis R. Rodriguez 1060e702d3cfSLuis R. Rodriguez power_rule = ®_rule->power_rule; 1061e702d3cfSLuis R. Rodriguez freq_range = ®_rule->freq_range; 1062e702d3cfSLuis R. Rodriguez 1063e702d3cfSLuis R. Rodriguez if (!power_rule->max_antenna_gain) 1064b0dfd2eaSJanusz Dziedzic snprintf(max_antenna_gain, sizeof(max_antenna_gain), "N/A"); 1065e702d3cfSLuis R. Rodriguez else 1066b0dfd2eaSJanusz Dziedzic snprintf(max_antenna_gain, sizeof(max_antenna_gain), "%d", 1067b0dfd2eaSJanusz Dziedzic power_rule->max_antenna_gain); 1068b0dfd2eaSJanusz Dziedzic 1069b0dfd2eaSJanusz Dziedzic if (reg_rule->flags & NL80211_RRF_AUTO_BW) 1070b0dfd2eaSJanusz Dziedzic snprintf(bw, sizeof(bw), "%d KHz, %d KHz AUTO", 1071b0dfd2eaSJanusz Dziedzic freq_range->max_bandwidth_khz, 1072b0dfd2eaSJanusz Dziedzic reg_get_max_bandwidth(regd, reg_rule)); 1073b0dfd2eaSJanusz Dziedzic else 1074b0dfd2eaSJanusz Dziedzic snprintf(bw, sizeof(bw), "%d KHz", 1075b0dfd2eaSJanusz Dziedzic freq_range->max_bandwidth_khz); 1076e702d3cfSLuis R. Rodriguez 1077fe7ef5e9SJohannes Berg REG_DBG_PRINT("Updating information on frequency %d MHz with regulatory rule:\n", 1078fe7ef5e9SJohannes Berg chan->center_freq); 1079e702d3cfSLuis R. Rodriguez 1080b0dfd2eaSJanusz Dziedzic REG_DBG_PRINT("%d KHz - %d KHz @ %s), (%s mBi, %d mBm)\n", 10811a919318SJohannes Berg freq_range->start_freq_khz, freq_range->end_freq_khz, 1082b0dfd2eaSJanusz Dziedzic bw, max_antenna_gain, 1083e702d3cfSLuis R. Rodriguez power_rule->max_eirp); 1084e702d3cfSLuis R. Rodriguez } 1085e702d3cfSLuis R. Rodriguez #else 1086b0dfd2eaSJanusz Dziedzic static void chan_reg_rule_print_dbg(const struct ieee80211_regdomain *regd, 1087b0dfd2eaSJanusz Dziedzic struct ieee80211_channel *chan, 1088e702d3cfSLuis R. Rodriguez const struct ieee80211_reg_rule *reg_rule) 1089e702d3cfSLuis R. Rodriguez { 1090e702d3cfSLuis R. Rodriguez return; 1091e702d3cfSLuis R. Rodriguez } 1092926a0a09SLuis R. Rodriguez #endif 1093926a0a09SLuis R. Rodriguez 1094e33e2241SJohannes Berg /* 1095e33e2241SJohannes Berg * Note that right now we assume the desired channel bandwidth 1096e33e2241SJohannes Berg * is always 20 MHz for each individual channel (HT40 uses 20 MHz 1097e33e2241SJohannes Berg * per channel, the primary and the extension channel). 1098038659e7SLuis R. Rodriguez */ 10997ca43d03SLuis R. Rodriguez static void handle_channel(struct wiphy *wiphy, 11007ca43d03SLuis R. Rodriguez enum nl80211_reg_initiator initiator, 1101fdc9d7b2SJohannes Berg struct ieee80211_channel *chan) 1102b2e1b302SLuis R. Rodriguez { 1103038659e7SLuis R. Rodriguez u32 flags, bw_flags = 0; 1104b2e1b302SLuis R. Rodriguez const struct ieee80211_reg_rule *reg_rule = NULL; 1105b2e1b302SLuis R. Rodriguez const struct ieee80211_power_rule *power_rule = NULL; 1106038659e7SLuis R. Rodriguez const struct ieee80211_freq_range *freq_range = NULL; 1107fe33eb39SLuis R. Rodriguez struct wiphy *request_wiphy = NULL; 1108c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 110997524820SJanusz Dziedzic const struct ieee80211_regdomain *regd; 111097524820SJanusz Dziedzic u32 max_bandwidth_khz; 1111a92a3ce7SLuis R. Rodriguez 1112c492db37SJohannes Berg request_wiphy = wiphy_idx_to_wiphy(lr->wiphy_idx); 1113a92a3ce7SLuis R. Rodriguez 1114a92a3ce7SLuis R. Rodriguez flags = chan->orig_flags; 1115b2e1b302SLuis R. Rodriguez 1116361c9c8bSJohannes Berg reg_rule = freq_reg_info(wiphy, MHZ_TO_KHZ(chan->center_freq)); 1117361c9c8bSJohannes Berg if (IS_ERR(reg_rule)) { 1118ca4ffe8fSLuis R. Rodriguez /* 1119ca4ffe8fSLuis R. Rodriguez * We will disable all channels that do not match our 112025985edcSLucas De Marchi * received regulatory rule unless the hint is coming 1121ca4ffe8fSLuis R. Rodriguez * from a Country IE and the Country IE had no information 1122ca4ffe8fSLuis R. Rodriguez * about a band. The IEEE 802.11 spec allows for an AP 1123ca4ffe8fSLuis R. Rodriguez * to send only a subset of the regulatory rules allowed, 1124ca4ffe8fSLuis R. Rodriguez * so an AP in the US that only supports 2.4 GHz may only send 1125ca4ffe8fSLuis R. Rodriguez * a country IE with information for the 2.4 GHz band 1126ca4ffe8fSLuis R. Rodriguez * while 5 GHz is still supported. 1127ca4ffe8fSLuis R. Rodriguez */ 1128ca4ffe8fSLuis R. Rodriguez if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE && 1129361c9c8bSJohannes Berg PTR_ERR(reg_rule) == -ERANGE) 11308318d78aSJohannes Berg return; 11318318d78aSJohannes Berg 1132cc493e4fSLuis R. Rodriguez if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER && 1133cc493e4fSLuis R. Rodriguez request_wiphy && request_wiphy == wiphy && 1134a2f73b6cSLuis R. Rodriguez request_wiphy->regulatory_flags & REGULATORY_STRICT_REG) { 1135cc493e4fSLuis R. Rodriguez REG_DBG_PRINT("Disabling freq %d MHz for good\n", 1136cc493e4fSLuis R. Rodriguez chan->center_freq); 1137cc493e4fSLuis R. Rodriguez chan->orig_flags |= IEEE80211_CHAN_DISABLED; 1138cc493e4fSLuis R. Rodriguez chan->flags = chan->orig_flags; 1139cc493e4fSLuis R. Rodriguez } else { 1140cc493e4fSLuis R. Rodriguez REG_DBG_PRINT("Disabling freq %d MHz\n", 1141cc493e4fSLuis R. Rodriguez chan->center_freq); 1142990de49fSJohannes Berg chan->flags |= IEEE80211_CHAN_DISABLED; 1143cc493e4fSLuis R. Rodriguez } 1144ca4ffe8fSLuis R. Rodriguez return; 1145ca4ffe8fSLuis R. Rodriguez } 1146ca4ffe8fSLuis R. Rodriguez 1147b0dfd2eaSJanusz Dziedzic regd = reg_get_regdomain(wiphy); 1148b0dfd2eaSJanusz Dziedzic chan_reg_rule_print_dbg(regd, chan, reg_rule); 1149e702d3cfSLuis R. Rodriguez 1150b2e1b302SLuis R. Rodriguez power_rule = ®_rule->power_rule; 1151038659e7SLuis R. Rodriguez freq_range = ®_rule->freq_range; 1152038659e7SLuis R. Rodriguez 115397524820SJanusz Dziedzic max_bandwidth_khz = freq_range->max_bandwidth_khz; 115497524820SJanusz Dziedzic /* Check if auto calculation requested */ 1155b0dfd2eaSJanusz Dziedzic if (reg_rule->flags & NL80211_RRF_AUTO_BW) 115697524820SJanusz Dziedzic max_bandwidth_khz = reg_get_max_bandwidth(regd, reg_rule); 115797524820SJanusz Dziedzic 115897524820SJanusz Dziedzic if (max_bandwidth_khz < MHZ_TO_KHZ(40)) 1159e33e2241SJohannes Berg bw_flags = IEEE80211_CHAN_NO_HT40; 116097524820SJanusz Dziedzic if (max_bandwidth_khz < MHZ_TO_KHZ(80)) 1161c7a6ee27SJohannes Berg bw_flags |= IEEE80211_CHAN_NO_80MHZ; 116297524820SJanusz Dziedzic if (max_bandwidth_khz < MHZ_TO_KHZ(160)) 1163c7a6ee27SJohannes Berg bw_flags |= IEEE80211_CHAN_NO_160MHZ; 1164b2e1b302SLuis R. Rodriguez 1165c492db37SJohannes Berg if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER && 1166806a9e39SLuis R. Rodriguez request_wiphy && request_wiphy == wiphy && 1167a2f73b6cSLuis R. Rodriguez request_wiphy->regulatory_flags & REGULATORY_STRICT_REG) { 1168fb1fc7adSLuis R. Rodriguez /* 116925985edcSLucas De Marchi * This guarantees the driver's requested regulatory domain 1170f976376dSLuis R. Rodriguez * will always be used as a base for further regulatory 1171fb1fc7adSLuis R. Rodriguez * settings 1172fb1fc7adSLuis R. Rodriguez */ 1173f976376dSLuis R. Rodriguez chan->flags = chan->orig_flags = 1174038659e7SLuis R. Rodriguez map_regdom_flags(reg_rule->flags) | bw_flags; 1175f976376dSLuis R. Rodriguez chan->max_antenna_gain = chan->orig_mag = 1176f976376dSLuis R. Rodriguez (int) MBI_TO_DBI(power_rule->max_antenna_gain); 1177279f0f55SFelix Fietkau chan->max_reg_power = chan->max_power = chan->orig_mpwr = 1178f976376dSLuis R. Rodriguez (int) MBM_TO_DBM(power_rule->max_eirp); 11794f267c11SJanusz Dziedzic 11804f267c11SJanusz Dziedzic if (chan->flags & IEEE80211_CHAN_RADAR) { 11814f267c11SJanusz Dziedzic chan->dfs_cac_ms = IEEE80211_DFS_MIN_CAC_TIME_MS; 11824f267c11SJanusz Dziedzic if (reg_rule->dfs_cac_ms) 11834f267c11SJanusz Dziedzic chan->dfs_cac_ms = reg_rule->dfs_cac_ms; 11844f267c11SJanusz Dziedzic } 11854f267c11SJanusz Dziedzic 1186f976376dSLuis R. Rodriguez return; 1187f976376dSLuis R. Rodriguez } 1188f976376dSLuis R. Rodriguez 118904f39047SSimon Wunderlich chan->dfs_state = NL80211_DFS_USABLE; 119004f39047SSimon Wunderlich chan->dfs_state_entered = jiffies; 119104f39047SSimon Wunderlich 1192aa3d7eefSRajkumar Manoharan chan->beacon_found = false; 1193038659e7SLuis R. Rodriguez chan->flags = flags | bw_flags | map_regdom_flags(reg_rule->flags); 11941a919318SJohannes Berg chan->max_antenna_gain = 11951a919318SJohannes Berg min_t(int, chan->orig_mag, 11961a919318SJohannes Berg MBI_TO_DBI(power_rule->max_antenna_gain)); 1197eccc068eSHong Wu chan->max_reg_power = (int) MBM_TO_DBM(power_rule->max_eirp); 1198089027e5SJanusz Dziedzic 1199089027e5SJanusz Dziedzic if (chan->flags & IEEE80211_CHAN_RADAR) { 1200089027e5SJanusz Dziedzic if (reg_rule->dfs_cac_ms) 1201089027e5SJanusz Dziedzic chan->dfs_cac_ms = reg_rule->dfs_cac_ms; 1202089027e5SJanusz Dziedzic else 1203089027e5SJanusz Dziedzic chan->dfs_cac_ms = IEEE80211_DFS_MIN_CAC_TIME_MS; 1204089027e5SJanusz Dziedzic } 1205089027e5SJanusz Dziedzic 12065e31fc08SStanislaw Gruszka if (chan->orig_mpwr) { 12075e31fc08SStanislaw Gruszka /* 1208a09a85a0SLuis R. Rodriguez * Devices that use REGULATORY_COUNTRY_IE_FOLLOW_POWER 1209a09a85a0SLuis R. Rodriguez * will always follow the passed country IE power settings. 12105e31fc08SStanislaw Gruszka */ 12115e31fc08SStanislaw Gruszka if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE && 1212a09a85a0SLuis R. Rodriguez wiphy->regulatory_flags & REGULATORY_COUNTRY_IE_FOLLOW_POWER) 12135e31fc08SStanislaw Gruszka chan->max_power = chan->max_reg_power; 12145e31fc08SStanislaw Gruszka else 12155e31fc08SStanislaw Gruszka chan->max_power = min(chan->orig_mpwr, 12165e31fc08SStanislaw Gruszka chan->max_reg_power); 12175e31fc08SStanislaw Gruszka } else 12185e31fc08SStanislaw Gruszka chan->max_power = chan->max_reg_power; 12198318d78aSJohannes Berg } 12208318d78aSJohannes Berg 12217ca43d03SLuis R. Rodriguez static void handle_band(struct wiphy *wiphy, 1222fdc9d7b2SJohannes Berg enum nl80211_reg_initiator initiator, 1223fdc9d7b2SJohannes Berg struct ieee80211_supported_band *sband) 12248318d78aSJohannes Berg { 1225a92a3ce7SLuis R. Rodriguez unsigned int i; 1226a92a3ce7SLuis R. Rodriguez 1227fdc9d7b2SJohannes Berg if (!sband) 1228fdc9d7b2SJohannes Berg return; 12298318d78aSJohannes Berg 12308318d78aSJohannes Berg for (i = 0; i < sband->n_channels; i++) 1231fdc9d7b2SJohannes Berg handle_channel(wiphy, initiator, &sband->channels[i]); 12328318d78aSJohannes Berg } 12338318d78aSJohannes Berg 123457b5ce07SLuis R. Rodriguez static bool reg_request_cell_base(struct regulatory_request *request) 123557b5ce07SLuis R. Rodriguez { 123657b5ce07SLuis R. Rodriguez if (request->initiator != NL80211_REGDOM_SET_BY_USER) 123757b5ce07SLuis R. Rodriguez return false; 12381a919318SJohannes Berg return request->user_reg_hint_type == NL80211_USER_REG_HINT_CELL_BASE; 123957b5ce07SLuis R. Rodriguez } 124057b5ce07SLuis R. Rodriguez 124152616f2bSIlan Peer static bool reg_request_indoor(struct regulatory_request *request) 124252616f2bSIlan Peer { 124352616f2bSIlan Peer if (request->initiator != NL80211_REGDOM_SET_BY_USER) 124452616f2bSIlan Peer return false; 124552616f2bSIlan Peer return request->user_reg_hint_type == NL80211_USER_REG_HINT_INDOOR; 124652616f2bSIlan Peer } 124752616f2bSIlan Peer 124857b5ce07SLuis R. Rodriguez bool reg_last_request_cell_base(void) 124957b5ce07SLuis R. Rodriguez { 125038fd2143SJohannes Berg return reg_request_cell_base(get_last_request()); 125157b5ce07SLuis R. Rodriguez } 125257b5ce07SLuis R. Rodriguez 125394fc661fSIlan Peer #ifdef CONFIG_CFG80211_REG_CELLULAR_HINTS 125457b5ce07SLuis R. Rodriguez /* Core specific check */ 12552f92212bSJohannes Berg static enum reg_request_treatment 12562f92212bSJohannes Berg reg_ignore_cell_hint(struct regulatory_request *pending_request) 125757b5ce07SLuis R. Rodriguez { 1258c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 125957b5ce07SLuis R. Rodriguez 126057b5ce07SLuis R. Rodriguez if (!reg_num_devs_support_basehint) 12612f92212bSJohannes Berg return REG_REQ_IGNORE; 126257b5ce07SLuis R. Rodriguez 1263c492db37SJohannes Berg if (reg_request_cell_base(lr) && 12641a919318SJohannes Berg !regdom_changes(pending_request->alpha2)) 12652f92212bSJohannes Berg return REG_REQ_ALREADY_SET; 12661a919318SJohannes Berg 12672f92212bSJohannes Berg return REG_REQ_OK; 126857b5ce07SLuis R. Rodriguez } 126957b5ce07SLuis R. Rodriguez 127057b5ce07SLuis R. Rodriguez /* Device specific check */ 127157b5ce07SLuis R. Rodriguez static bool reg_dev_ignore_cell_hint(struct wiphy *wiphy) 127257b5ce07SLuis R. Rodriguez { 12731a919318SJohannes Berg return !(wiphy->features & NL80211_FEATURE_CELL_BASE_REG_HINTS); 127457b5ce07SLuis R. Rodriguez } 127557b5ce07SLuis R. Rodriguez #else 127657b5ce07SLuis R. Rodriguez static int reg_ignore_cell_hint(struct regulatory_request *pending_request) 127757b5ce07SLuis R. Rodriguez { 12782f92212bSJohannes Berg return REG_REQ_IGNORE; 127957b5ce07SLuis R. Rodriguez } 12801a919318SJohannes Berg 12811a919318SJohannes Berg static bool reg_dev_ignore_cell_hint(struct wiphy *wiphy) 128257b5ce07SLuis R. Rodriguez { 128357b5ce07SLuis R. Rodriguez return true; 128457b5ce07SLuis R. Rodriguez } 128557b5ce07SLuis R. Rodriguez #endif 128657b5ce07SLuis R. Rodriguez 1287fa1fb9cbSLuis R. Rodriguez static bool wiphy_strict_alpha2_regd(struct wiphy *wiphy) 1288fa1fb9cbSLuis R. Rodriguez { 1289a2f73b6cSLuis R. Rodriguez if (wiphy->regulatory_flags & REGULATORY_STRICT_REG && 1290a2f73b6cSLuis R. Rodriguez !(wiphy->regulatory_flags & REGULATORY_CUSTOM_REG)) 1291fa1fb9cbSLuis R. Rodriguez return true; 1292fa1fb9cbSLuis R. Rodriguez return false; 1293fa1fb9cbSLuis R. Rodriguez } 129457b5ce07SLuis R. Rodriguez 12957db90f4aSLuis R. Rodriguez static bool ignore_reg_update(struct wiphy *wiphy, 12967db90f4aSLuis R. Rodriguez enum nl80211_reg_initiator initiator) 129714b9815aSLuis R. Rodriguez { 1298c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 1299c492db37SJohannes Berg 1300c492db37SJohannes Berg if (!lr) { 1301034c6d6eSLuis R. Rodriguez REG_DBG_PRINT("Ignoring regulatory request set by %s " 1302034c6d6eSLuis R. Rodriguez "since last_request is not set\n", 1303926a0a09SLuis R. Rodriguez reg_initiator_name(initiator)); 130414b9815aSLuis R. Rodriguez return true; 1305926a0a09SLuis R. Rodriguez } 1306926a0a09SLuis R. Rodriguez 13077db90f4aSLuis R. Rodriguez if (initiator == NL80211_REGDOM_SET_BY_CORE && 1308a2f73b6cSLuis R. Rodriguez wiphy->regulatory_flags & REGULATORY_CUSTOM_REG) { 1309034c6d6eSLuis R. Rodriguez REG_DBG_PRINT("Ignoring regulatory request set by %s " 1310034c6d6eSLuis R. Rodriguez "since the driver uses its own custom " 1311034c6d6eSLuis R. Rodriguez "regulatory domain\n", 1312926a0a09SLuis R. Rodriguez reg_initiator_name(initiator)); 131314b9815aSLuis R. Rodriguez return true; 1314926a0a09SLuis R. Rodriguez } 1315926a0a09SLuis R. Rodriguez 1316fb1fc7adSLuis R. Rodriguez /* 1317fb1fc7adSLuis R. Rodriguez * wiphy->regd will be set once the device has its own 1318fb1fc7adSLuis R. Rodriguez * desired regulatory domain set 1319fb1fc7adSLuis R. Rodriguez */ 1320fa1fb9cbSLuis R. Rodriguez if (wiphy_strict_alpha2_regd(wiphy) && !wiphy->regd && 1321749b527bSLuis R. Rodriguez initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE && 1322c492db37SJohannes Berg !is_world_regdom(lr->alpha2)) { 1323034c6d6eSLuis R. Rodriguez REG_DBG_PRINT("Ignoring regulatory request set by %s " 1324034c6d6eSLuis R. Rodriguez "since the driver requires its own regulatory " 1325034c6d6eSLuis R. Rodriguez "domain to be set first\n", 1326926a0a09SLuis R. Rodriguez reg_initiator_name(initiator)); 132714b9815aSLuis R. Rodriguez return true; 1328926a0a09SLuis R. Rodriguez } 1329926a0a09SLuis R. Rodriguez 1330c492db37SJohannes Berg if (reg_request_cell_base(lr)) 133157b5ce07SLuis R. Rodriguez return reg_dev_ignore_cell_hint(wiphy); 133257b5ce07SLuis R. Rodriguez 133314b9815aSLuis R. Rodriguez return false; 133414b9815aSLuis R. Rodriguez } 133514b9815aSLuis R. Rodriguez 13363195e489SLuis R. Rodriguez static bool reg_is_world_roaming(struct wiphy *wiphy) 13373195e489SLuis R. Rodriguez { 13383195e489SLuis R. Rodriguez const struct ieee80211_regdomain *cr = get_cfg80211_regdom(); 13393195e489SLuis R. Rodriguez const struct ieee80211_regdomain *wr = get_wiphy_regdom(wiphy); 13403195e489SLuis R. Rodriguez struct regulatory_request *lr = get_last_request(); 13413195e489SLuis R. Rodriguez 13423195e489SLuis R. Rodriguez if (is_world_regdom(cr->alpha2) || (wr && is_world_regdom(wr->alpha2))) 13433195e489SLuis R. Rodriguez return true; 13443195e489SLuis R. Rodriguez 13453195e489SLuis R. Rodriguez if (lr && lr->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE && 1346a2f73b6cSLuis R. Rodriguez wiphy->regulatory_flags & REGULATORY_CUSTOM_REG) 13473195e489SLuis R. Rodriguez return true; 13483195e489SLuis R. Rodriguez 13493195e489SLuis R. Rodriguez return false; 13503195e489SLuis R. Rodriguez } 13513195e489SLuis R. Rodriguez 13521a919318SJohannes Berg static void handle_reg_beacon(struct wiphy *wiphy, unsigned int chan_idx, 1353e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon) 1354e38f8a7aSLuis R. Rodriguez { 1355e38f8a7aSLuis R. Rodriguez struct ieee80211_supported_band *sband; 1356e38f8a7aSLuis R. Rodriguez struct ieee80211_channel *chan; 13576bad8766SLuis R. Rodriguez bool channel_changed = false; 13586bad8766SLuis R. Rodriguez struct ieee80211_channel chan_before; 1359e38f8a7aSLuis R. Rodriguez 1360e38f8a7aSLuis R. Rodriguez sband = wiphy->bands[reg_beacon->chan.band]; 1361e38f8a7aSLuis R. Rodriguez chan = &sband->channels[chan_idx]; 1362e38f8a7aSLuis R. Rodriguez 1363e38f8a7aSLuis R. Rodriguez if (likely(chan->center_freq != reg_beacon->chan.center_freq)) 1364e38f8a7aSLuis R. Rodriguez return; 1365e38f8a7aSLuis R. Rodriguez 13666bad8766SLuis R. Rodriguez if (chan->beacon_found) 13676bad8766SLuis R. Rodriguez return; 13686bad8766SLuis R. Rodriguez 13696bad8766SLuis R. Rodriguez chan->beacon_found = true; 13706bad8766SLuis R. Rodriguez 13710f500a5fSLuis R. Rodriguez if (!reg_is_world_roaming(wiphy)) 13720f500a5fSLuis R. Rodriguez return; 13730f500a5fSLuis R. Rodriguez 1374a2f73b6cSLuis R. Rodriguez if (wiphy->regulatory_flags & REGULATORY_DISABLE_BEACON_HINTS) 137537184244SLuis R. Rodriguez return; 137637184244SLuis R. Rodriguez 13776bad8766SLuis R. Rodriguez chan_before.center_freq = chan->center_freq; 13786bad8766SLuis R. Rodriguez chan_before.flags = chan->flags; 13796bad8766SLuis R. Rodriguez 13808fe02e16SLuis R. Rodriguez if (chan->flags & IEEE80211_CHAN_NO_IR) { 13818fe02e16SLuis R. Rodriguez chan->flags &= ~IEEE80211_CHAN_NO_IR; 13826bad8766SLuis R. Rodriguez channel_changed = true; 1383e38f8a7aSLuis R. Rodriguez } 1384e38f8a7aSLuis R. Rodriguez 13856bad8766SLuis R. Rodriguez if (channel_changed) 13866bad8766SLuis R. Rodriguez nl80211_send_beacon_hint_event(wiphy, &chan_before, chan); 1387e38f8a7aSLuis R. Rodriguez } 1388e38f8a7aSLuis R. Rodriguez 1389e38f8a7aSLuis R. Rodriguez /* 1390e38f8a7aSLuis R. Rodriguez * Called when a scan on a wiphy finds a beacon on 1391e38f8a7aSLuis R. Rodriguez * new channel 1392e38f8a7aSLuis R. Rodriguez */ 1393e38f8a7aSLuis R. Rodriguez static void wiphy_update_new_beacon(struct wiphy *wiphy, 1394e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon) 1395e38f8a7aSLuis R. Rodriguez { 1396e38f8a7aSLuis R. Rodriguez unsigned int i; 1397e38f8a7aSLuis R. Rodriguez struct ieee80211_supported_band *sband; 1398e38f8a7aSLuis R. Rodriguez 1399e38f8a7aSLuis R. Rodriguez if (!wiphy->bands[reg_beacon->chan.band]) 1400e38f8a7aSLuis R. Rodriguez return; 1401e38f8a7aSLuis R. Rodriguez 1402e38f8a7aSLuis R. Rodriguez sband = wiphy->bands[reg_beacon->chan.band]; 1403e38f8a7aSLuis R. Rodriguez 1404e38f8a7aSLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 1405e38f8a7aSLuis R. Rodriguez handle_reg_beacon(wiphy, i, reg_beacon); 1406e38f8a7aSLuis R. Rodriguez } 1407e38f8a7aSLuis R. Rodriguez 1408e38f8a7aSLuis R. Rodriguez /* 1409e38f8a7aSLuis R. Rodriguez * Called upon reg changes or a new wiphy is added 1410e38f8a7aSLuis R. Rodriguez */ 1411e38f8a7aSLuis R. Rodriguez static void wiphy_update_beacon_reg(struct wiphy *wiphy) 1412e38f8a7aSLuis R. Rodriguez { 1413e38f8a7aSLuis R. Rodriguez unsigned int i; 1414e38f8a7aSLuis R. Rodriguez struct ieee80211_supported_band *sband; 1415e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon; 1416e38f8a7aSLuis R. Rodriguez 1417e38f8a7aSLuis R. Rodriguez list_for_each_entry(reg_beacon, ®_beacon_list, list) { 1418e38f8a7aSLuis R. Rodriguez if (!wiphy->bands[reg_beacon->chan.band]) 1419e38f8a7aSLuis R. Rodriguez continue; 1420e38f8a7aSLuis R. Rodriguez sband = wiphy->bands[reg_beacon->chan.band]; 1421e38f8a7aSLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 1422e38f8a7aSLuis R. Rodriguez handle_reg_beacon(wiphy, i, reg_beacon); 1423e38f8a7aSLuis R. Rodriguez } 1424e38f8a7aSLuis R. Rodriguez } 1425e38f8a7aSLuis R. Rodriguez 1426e38f8a7aSLuis R. Rodriguez /* Reap the advantages of previously found beacons */ 1427e38f8a7aSLuis R. Rodriguez static void reg_process_beacons(struct wiphy *wiphy) 1428e38f8a7aSLuis R. Rodriguez { 1429b1ed8dddSLuis R. Rodriguez /* 1430b1ed8dddSLuis R. Rodriguez * Means we are just firing up cfg80211, so no beacons would 1431b1ed8dddSLuis R. Rodriguez * have been processed yet. 1432b1ed8dddSLuis R. Rodriguez */ 1433b1ed8dddSLuis R. Rodriguez if (!last_request) 1434b1ed8dddSLuis R. Rodriguez return; 1435e38f8a7aSLuis R. Rodriguez wiphy_update_beacon_reg(wiphy); 1436e38f8a7aSLuis R. Rodriguez } 1437e38f8a7aSLuis R. Rodriguez 14381a919318SJohannes Berg static bool is_ht40_allowed(struct ieee80211_channel *chan) 1439038659e7SLuis R. Rodriguez { 1440038659e7SLuis R. Rodriguez if (!chan) 1441038659e7SLuis R. Rodriguez return false; 14421a919318SJohannes Berg if (chan->flags & IEEE80211_CHAN_DISABLED) 14431a919318SJohannes Berg return false; 14441a919318SJohannes Berg /* This would happen when regulatory rules disallow HT40 completely */ 144555b183adSFelix Fietkau if ((chan->flags & IEEE80211_CHAN_NO_HT40) == IEEE80211_CHAN_NO_HT40) 144655b183adSFelix Fietkau return false; 144755b183adSFelix Fietkau return true; 1448038659e7SLuis R. Rodriguez } 1449038659e7SLuis R. Rodriguez 1450038659e7SLuis R. Rodriguez static void reg_process_ht_flags_channel(struct wiphy *wiphy, 1451fdc9d7b2SJohannes Berg struct ieee80211_channel *channel) 1452038659e7SLuis R. Rodriguez { 1453fdc9d7b2SJohannes Berg struct ieee80211_supported_band *sband = wiphy->bands[channel->band]; 1454038659e7SLuis R. Rodriguez struct ieee80211_channel *channel_before = NULL, *channel_after = NULL; 1455038659e7SLuis R. Rodriguez unsigned int i; 1456038659e7SLuis R. Rodriguez 14571a919318SJohannes Berg if (!is_ht40_allowed(channel)) { 1458038659e7SLuis R. Rodriguez channel->flags |= IEEE80211_CHAN_NO_HT40; 1459038659e7SLuis R. Rodriguez return; 1460038659e7SLuis R. Rodriguez } 1461038659e7SLuis R. Rodriguez 1462038659e7SLuis R. Rodriguez /* 1463038659e7SLuis R. Rodriguez * We need to ensure the extension channels exist to 1464038659e7SLuis R. Rodriguez * be able to use HT40- or HT40+, this finds them (or not) 1465038659e7SLuis R. Rodriguez */ 1466038659e7SLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) { 1467038659e7SLuis R. Rodriguez struct ieee80211_channel *c = &sband->channels[i]; 14681a919318SJohannes Berg 1469038659e7SLuis R. Rodriguez if (c->center_freq == (channel->center_freq - 20)) 1470038659e7SLuis R. Rodriguez channel_before = c; 1471038659e7SLuis R. Rodriguez if (c->center_freq == (channel->center_freq + 20)) 1472038659e7SLuis R. Rodriguez channel_after = c; 1473038659e7SLuis R. Rodriguez } 1474038659e7SLuis R. Rodriguez 1475038659e7SLuis R. Rodriguez /* 1476038659e7SLuis R. Rodriguez * Please note that this assumes target bandwidth is 20 MHz, 1477038659e7SLuis R. Rodriguez * if that ever changes we also need to change the below logic 1478038659e7SLuis R. Rodriguez * to include that as well. 1479038659e7SLuis R. Rodriguez */ 14801a919318SJohannes Berg if (!is_ht40_allowed(channel_before)) 1481689da1b3SLuis R. Rodriguez channel->flags |= IEEE80211_CHAN_NO_HT40MINUS; 1482038659e7SLuis R. Rodriguez else 1483689da1b3SLuis R. Rodriguez channel->flags &= ~IEEE80211_CHAN_NO_HT40MINUS; 1484038659e7SLuis R. Rodriguez 14851a919318SJohannes Berg if (!is_ht40_allowed(channel_after)) 1486689da1b3SLuis R. Rodriguez channel->flags |= IEEE80211_CHAN_NO_HT40PLUS; 1487038659e7SLuis R. Rodriguez else 1488689da1b3SLuis R. Rodriguez channel->flags &= ~IEEE80211_CHAN_NO_HT40PLUS; 1489038659e7SLuis R. Rodriguez } 1490038659e7SLuis R. Rodriguez 1491038659e7SLuis R. Rodriguez static void reg_process_ht_flags_band(struct wiphy *wiphy, 1492fdc9d7b2SJohannes Berg struct ieee80211_supported_band *sband) 1493038659e7SLuis R. Rodriguez { 1494038659e7SLuis R. Rodriguez unsigned int i; 1495038659e7SLuis R. Rodriguez 1496fdc9d7b2SJohannes Berg if (!sband) 1497fdc9d7b2SJohannes Berg return; 1498038659e7SLuis R. Rodriguez 1499038659e7SLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 1500fdc9d7b2SJohannes Berg reg_process_ht_flags_channel(wiphy, &sband->channels[i]); 1501038659e7SLuis R. Rodriguez } 1502038659e7SLuis R. Rodriguez 1503038659e7SLuis R. Rodriguez static void reg_process_ht_flags(struct wiphy *wiphy) 1504038659e7SLuis R. Rodriguez { 1505038659e7SLuis R. Rodriguez enum ieee80211_band band; 1506038659e7SLuis R. Rodriguez 1507038659e7SLuis R. Rodriguez if (!wiphy) 1508038659e7SLuis R. Rodriguez return; 1509038659e7SLuis R. Rodriguez 1510fdc9d7b2SJohannes Berg for (band = 0; band < IEEE80211_NUM_BANDS; band++) 1511fdc9d7b2SJohannes Berg reg_process_ht_flags_band(wiphy, wiphy->bands[band]); 1512038659e7SLuis R. Rodriguez } 1513038659e7SLuis R. Rodriguez 15140e3802dbSLuis R. Rodriguez static void reg_call_notifier(struct wiphy *wiphy, 15150e3802dbSLuis R. Rodriguez struct regulatory_request *request) 15160e3802dbSLuis R. Rodriguez { 15170e3802dbSLuis R. Rodriguez if (wiphy->reg_notifier) 15180e3802dbSLuis R. Rodriguez wiphy->reg_notifier(wiphy, request); 15190e3802dbSLuis R. Rodriguez } 15200e3802dbSLuis R. Rodriguez 1521eac03e38SSven Neumann static void wiphy_update_regulatory(struct wiphy *wiphy, 15227db90f4aSLuis R. Rodriguez enum nl80211_reg_initiator initiator) 15238318d78aSJohannes Berg { 15248318d78aSJohannes Berg enum ieee80211_band band; 1525c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 1526eac03e38SSven Neumann 15270e3802dbSLuis R. Rodriguez if (ignore_reg_update(wiphy, initiator)) { 15280e3802dbSLuis R. Rodriguez /* 15290e3802dbSLuis R. Rodriguez * Regulatory updates set by CORE are ignored for custom 15300e3802dbSLuis R. Rodriguez * regulatory cards. Let us notify the changes to the driver, 15310e3802dbSLuis R. Rodriguez * as some drivers used this to restore its orig_* reg domain. 15320e3802dbSLuis R. Rodriguez */ 15330e3802dbSLuis R. Rodriguez if (initiator == NL80211_REGDOM_SET_BY_CORE && 1534a2f73b6cSLuis R. Rodriguez wiphy->regulatory_flags & REGULATORY_CUSTOM_REG) 15350e3802dbSLuis R. Rodriguez reg_call_notifier(wiphy, lr); 1536a203c2aaSSven Neumann return; 15370e3802dbSLuis R. Rodriguez } 1538a203c2aaSSven Neumann 1539c492db37SJohannes Berg lr->dfs_region = get_cfg80211_regdom()->dfs_region; 1540b68e6b3bSLuis R. Rodriguez 1541fdc9d7b2SJohannes Berg for (band = 0; band < IEEE80211_NUM_BANDS; band++) 1542fdc9d7b2SJohannes Berg handle_band(wiphy, initiator, wiphy->bands[band]); 1543a203c2aaSSven Neumann 1544e38f8a7aSLuis R. Rodriguez reg_process_beacons(wiphy); 1545038659e7SLuis R. Rodriguez reg_process_ht_flags(wiphy); 15460e3802dbSLuis R. Rodriguez reg_call_notifier(wiphy, lr); 1547b2e1b302SLuis R. Rodriguez } 1548b2e1b302SLuis R. Rodriguez 1549d7549cbbSSven Neumann static void update_all_wiphy_regulatory(enum nl80211_reg_initiator initiator) 1550d7549cbbSSven Neumann { 1551d7549cbbSSven Neumann struct cfg80211_registered_device *rdev; 15524a38994fSRajkumar Manoharan struct wiphy *wiphy; 1553d7549cbbSSven Neumann 15545fe231e8SJohannes Berg ASSERT_RTNL(); 1555458f4f9eSJohannes Berg 15564a38994fSRajkumar Manoharan list_for_each_entry(rdev, &cfg80211_rdev_list, list) { 15574a38994fSRajkumar Manoharan wiphy = &rdev->wiphy; 15584a38994fSRajkumar Manoharan wiphy_update_regulatory(wiphy, initiator); 15594a38994fSRajkumar Manoharan } 1560d7549cbbSSven Neumann } 1561d7549cbbSSven Neumann 15621fa25e41SLuis R. Rodriguez static void handle_channel_custom(struct wiphy *wiphy, 1563fdc9d7b2SJohannes Berg struct ieee80211_channel *chan, 15641fa25e41SLuis R. Rodriguez const struct ieee80211_regdomain *regd) 15651fa25e41SLuis R. Rodriguez { 1566038659e7SLuis R. Rodriguez u32 bw_flags = 0; 15671fa25e41SLuis R. Rodriguez const struct ieee80211_reg_rule *reg_rule = NULL; 15681fa25e41SLuis R. Rodriguez const struct ieee80211_power_rule *power_rule = NULL; 1569038659e7SLuis R. Rodriguez const struct ieee80211_freq_range *freq_range = NULL; 157097524820SJanusz Dziedzic u32 max_bandwidth_khz; 15711fa25e41SLuis R. Rodriguez 1572361c9c8bSJohannes Berg reg_rule = freq_reg_info_regd(wiphy, MHZ_TO_KHZ(chan->center_freq), 1573038659e7SLuis R. Rodriguez regd); 15741fa25e41SLuis R. Rodriguez 1575361c9c8bSJohannes Berg if (IS_ERR(reg_rule)) { 1576fe7ef5e9SJohannes Berg REG_DBG_PRINT("Disabling freq %d MHz as custom regd has no rule that fits it\n", 1577fe7ef5e9SJohannes Berg chan->center_freq); 1578cc493e4fSLuis R. Rodriguez chan->orig_flags |= IEEE80211_CHAN_DISABLED; 1579cc493e4fSLuis R. Rodriguez chan->flags = chan->orig_flags; 15801fa25e41SLuis R. Rodriguez return; 15811fa25e41SLuis R. Rodriguez } 15821fa25e41SLuis R. Rodriguez 1583b0dfd2eaSJanusz Dziedzic chan_reg_rule_print_dbg(regd, chan, reg_rule); 1584e702d3cfSLuis R. Rodriguez 15851fa25e41SLuis R. Rodriguez power_rule = ®_rule->power_rule; 1586038659e7SLuis R. Rodriguez freq_range = ®_rule->freq_range; 15871fa25e41SLuis R. Rodriguez 158897524820SJanusz Dziedzic max_bandwidth_khz = freq_range->max_bandwidth_khz; 158997524820SJanusz Dziedzic /* Check if auto calculation requested */ 1590b0dfd2eaSJanusz Dziedzic if (reg_rule->flags & NL80211_RRF_AUTO_BW) 159197524820SJanusz Dziedzic max_bandwidth_khz = reg_get_max_bandwidth(regd, reg_rule); 159297524820SJanusz Dziedzic 159397524820SJanusz Dziedzic if (max_bandwidth_khz < MHZ_TO_KHZ(40)) 1594e33e2241SJohannes Berg bw_flags = IEEE80211_CHAN_NO_HT40; 159597524820SJanusz Dziedzic if (max_bandwidth_khz < MHZ_TO_KHZ(80)) 1596c7a6ee27SJohannes Berg bw_flags |= IEEE80211_CHAN_NO_80MHZ; 159797524820SJanusz Dziedzic if (max_bandwidth_khz < MHZ_TO_KHZ(160)) 1598c7a6ee27SJohannes Berg bw_flags |= IEEE80211_CHAN_NO_160MHZ; 1599038659e7SLuis R. Rodriguez 16002e18b38fSArik Nemtsov chan->dfs_state_entered = jiffies; 1601038659e7SLuis R. Rodriguez chan->flags |= map_regdom_flags(reg_rule->flags) | bw_flags; 16021fa25e41SLuis R. Rodriguez chan->max_antenna_gain = (int) MBI_TO_DBI(power_rule->max_antenna_gain); 1603279f0f55SFelix Fietkau chan->max_reg_power = chan->max_power = 1604279f0f55SFelix Fietkau (int) MBM_TO_DBM(power_rule->max_eirp); 16052e18b38fSArik Nemtsov 16062e18b38fSArik Nemtsov if (chan->flags & IEEE80211_CHAN_RADAR) { 16072e18b38fSArik Nemtsov if (reg_rule->dfs_cac_ms) 16082e18b38fSArik Nemtsov chan->dfs_cac_ms = reg_rule->dfs_cac_ms; 16092e18b38fSArik Nemtsov else 16102e18b38fSArik Nemtsov chan->dfs_cac_ms = IEEE80211_DFS_MIN_CAC_TIME_MS; 16112e18b38fSArik Nemtsov } 16122e18b38fSArik Nemtsov 16132e18b38fSArik Nemtsov chan->max_power = chan->max_reg_power; 16141fa25e41SLuis R. Rodriguez } 16151fa25e41SLuis R. Rodriguez 1616fdc9d7b2SJohannes Berg static void handle_band_custom(struct wiphy *wiphy, 1617fdc9d7b2SJohannes Berg struct ieee80211_supported_band *sband, 16181fa25e41SLuis R. Rodriguez const struct ieee80211_regdomain *regd) 16191fa25e41SLuis R. Rodriguez { 16201fa25e41SLuis R. Rodriguez unsigned int i; 16211fa25e41SLuis R. Rodriguez 1622fdc9d7b2SJohannes Berg if (!sband) 1623fdc9d7b2SJohannes Berg return; 16241fa25e41SLuis R. Rodriguez 16251fa25e41SLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 1626fdc9d7b2SJohannes Berg handle_channel_custom(wiphy, &sband->channels[i], regd); 16271fa25e41SLuis R. Rodriguez } 16281fa25e41SLuis R. Rodriguez 16291fa25e41SLuis R. Rodriguez /* Used by drivers prior to wiphy registration */ 16301fa25e41SLuis R. Rodriguez void wiphy_apply_custom_regulatory(struct wiphy *wiphy, 16311fa25e41SLuis R. Rodriguez const struct ieee80211_regdomain *regd) 16321fa25e41SLuis R. Rodriguez { 16331fa25e41SLuis R. Rodriguez enum ieee80211_band band; 1634bbcf3f02SLuis R. Rodriguez unsigned int bands_set = 0; 1635ac46d48eSLuis R. Rodriguez 1636a2f73b6cSLuis R. Rodriguez WARN(!(wiphy->regulatory_flags & REGULATORY_CUSTOM_REG), 1637a2f73b6cSLuis R. Rodriguez "wiphy should have REGULATORY_CUSTOM_REG\n"); 1638a2f73b6cSLuis R. Rodriguez wiphy->regulatory_flags |= REGULATORY_CUSTOM_REG; 1639222ea581SLuis R. Rodriguez 16401fa25e41SLuis R. Rodriguez for (band = 0; band < IEEE80211_NUM_BANDS; band++) { 1641bbcf3f02SLuis R. Rodriguez if (!wiphy->bands[band]) 1642bbcf3f02SLuis R. Rodriguez continue; 1643fdc9d7b2SJohannes Berg handle_band_custom(wiphy, wiphy->bands[band], regd); 1644bbcf3f02SLuis R. Rodriguez bands_set++; 16451fa25e41SLuis R. Rodriguez } 1646bbcf3f02SLuis R. Rodriguez 1647bbcf3f02SLuis R. Rodriguez /* 1648bbcf3f02SLuis R. Rodriguez * no point in calling this if it won't have any effect 16491a919318SJohannes Berg * on your device's supported bands. 1650bbcf3f02SLuis R. Rodriguez */ 1651bbcf3f02SLuis R. Rodriguez WARN_ON(!bands_set); 16521fa25e41SLuis R. Rodriguez } 16531fa25e41SLuis R. Rodriguez EXPORT_SYMBOL(wiphy_apply_custom_regulatory); 16541fa25e41SLuis R. Rodriguez 1655b2e253cfSLuis R. Rodriguez static void reg_set_request_processed(void) 1656b2e253cfSLuis R. Rodriguez { 1657b2e253cfSLuis R. Rodriguez bool need_more_processing = false; 1658c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 1659b2e253cfSLuis R. Rodriguez 1660c492db37SJohannes Berg lr->processed = true; 1661b2e253cfSLuis R. Rodriguez 1662b2e253cfSLuis R. Rodriguez spin_lock(®_requests_lock); 1663b2e253cfSLuis R. Rodriguez if (!list_empty(®_requests_list)) 1664b2e253cfSLuis R. Rodriguez need_more_processing = true; 1665b2e253cfSLuis R. Rodriguez spin_unlock(®_requests_lock); 1666b2e253cfSLuis R. Rodriguez 1667c492db37SJohannes Berg if (lr->initiator == NL80211_REGDOM_SET_BY_USER) 1668fe20b39eSEliad Peller cancel_delayed_work(®_timeout); 1669a90c7a31SLuis R. Rodriguez 1670b2e253cfSLuis R. Rodriguez if (need_more_processing) 1671b2e253cfSLuis R. Rodriguez schedule_work(®_work); 1672b2e253cfSLuis R. Rodriguez } 1673b2e253cfSLuis R. Rodriguez 1674d1c96a9aSLuis R. Rodriguez /** 1675b3eb7f3fSLuis R. Rodriguez * reg_process_hint_core - process core regulatory requests 1676b3eb7f3fSLuis R. Rodriguez * @pending_request: a pending core regulatory request 1677b3eb7f3fSLuis R. Rodriguez * 1678b3eb7f3fSLuis R. Rodriguez * The wireless subsystem can use this function to process 1679b3eb7f3fSLuis R. Rodriguez * a regulatory request issued by the regulatory core. 1680b3eb7f3fSLuis R. Rodriguez * 1681b3eb7f3fSLuis R. Rodriguez * Returns one of the different reg request treatment values. 1682b3eb7f3fSLuis R. Rodriguez */ 1683b3eb7f3fSLuis R. Rodriguez static enum reg_request_treatment 1684b3eb7f3fSLuis R. Rodriguez reg_process_hint_core(struct regulatory_request *core_request) 1685b3eb7f3fSLuis R. Rodriguez { 1686b3eb7f3fSLuis R. Rodriguez 1687b3eb7f3fSLuis R. Rodriguez core_request->intersect = false; 1688b3eb7f3fSLuis R. Rodriguez core_request->processed = false; 16895ad6ef5eSLuis R. Rodriguez 169005f1a3eaSLuis R. Rodriguez reg_update_last_request(core_request); 1691b3eb7f3fSLuis R. Rodriguez 1692fe6631ffSLuis R. Rodriguez return reg_call_crda(core_request); 1693b3eb7f3fSLuis R. Rodriguez } 1694b3eb7f3fSLuis R. Rodriguez 16950d97a619SLuis R. Rodriguez static enum reg_request_treatment 16960d97a619SLuis R. Rodriguez __reg_process_hint_user(struct regulatory_request *user_request) 16970d97a619SLuis R. Rodriguez { 16980d97a619SLuis R. Rodriguez struct regulatory_request *lr = get_last_request(); 16990d97a619SLuis R. Rodriguez 170052616f2bSIlan Peer if (reg_request_indoor(user_request)) { 170152616f2bSIlan Peer reg_is_indoor = true; 170252616f2bSIlan Peer return REG_REQ_USER_HINT_HANDLED; 170352616f2bSIlan Peer } 170452616f2bSIlan Peer 17050d97a619SLuis R. Rodriguez if (reg_request_cell_base(user_request)) 17060d97a619SLuis R. Rodriguez return reg_ignore_cell_hint(user_request); 17070d97a619SLuis R. Rodriguez 17080d97a619SLuis R. Rodriguez if (reg_request_cell_base(lr)) 17090d97a619SLuis R. Rodriguez return REG_REQ_IGNORE; 17100d97a619SLuis R. Rodriguez 17110d97a619SLuis R. Rodriguez if (lr->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE) 17120d97a619SLuis R. Rodriguez return REG_REQ_INTERSECT; 17130d97a619SLuis R. Rodriguez /* 17140d97a619SLuis R. Rodriguez * If the user knows better the user should set the regdom 17150d97a619SLuis R. Rodriguez * to their country before the IE is picked up 17160d97a619SLuis R. Rodriguez */ 17170d97a619SLuis R. Rodriguez if (lr->initiator == NL80211_REGDOM_SET_BY_USER && 17180d97a619SLuis R. Rodriguez lr->intersect) 17190d97a619SLuis R. Rodriguez return REG_REQ_IGNORE; 17200d97a619SLuis R. Rodriguez /* 17210d97a619SLuis R. Rodriguez * Process user requests only after previous user/driver/core 17220d97a619SLuis R. Rodriguez * requests have been processed 17230d97a619SLuis R. Rodriguez */ 17240d97a619SLuis R. Rodriguez if ((lr->initiator == NL80211_REGDOM_SET_BY_CORE || 17250d97a619SLuis R. Rodriguez lr->initiator == NL80211_REGDOM_SET_BY_DRIVER || 17260d97a619SLuis R. Rodriguez lr->initiator == NL80211_REGDOM_SET_BY_USER) && 17270d97a619SLuis R. Rodriguez regdom_changes(lr->alpha2)) 17280d97a619SLuis R. Rodriguez return REG_REQ_IGNORE; 17290d97a619SLuis R. Rodriguez 17300d97a619SLuis R. Rodriguez if (!regdom_changes(user_request->alpha2)) 17310d97a619SLuis R. Rodriguez return REG_REQ_ALREADY_SET; 17320d97a619SLuis R. Rodriguez 17330d97a619SLuis R. Rodriguez return REG_REQ_OK; 17340d97a619SLuis R. Rodriguez } 17350d97a619SLuis R. Rodriguez 17360d97a619SLuis R. Rodriguez /** 17370d97a619SLuis R. Rodriguez * reg_process_hint_user - process user regulatory requests 17380d97a619SLuis R. Rodriguez * @user_request: a pending user regulatory request 17390d97a619SLuis R. Rodriguez * 17400d97a619SLuis R. Rodriguez * The wireless subsystem can use this function to process 17410d97a619SLuis R. Rodriguez * a regulatory request initiated by userspace. 17420d97a619SLuis R. Rodriguez * 17430d97a619SLuis R. Rodriguez * Returns one of the different reg request treatment values. 17440d97a619SLuis R. Rodriguez */ 17450d97a619SLuis R. Rodriguez static enum reg_request_treatment 17460d97a619SLuis R. Rodriguez reg_process_hint_user(struct regulatory_request *user_request) 17470d97a619SLuis R. Rodriguez { 17480d97a619SLuis R. Rodriguez enum reg_request_treatment treatment; 17490d97a619SLuis R. Rodriguez 17500d97a619SLuis R. Rodriguez treatment = __reg_process_hint_user(user_request); 17510d97a619SLuis R. Rodriguez if (treatment == REG_REQ_IGNORE || 175252616f2bSIlan Peer treatment == REG_REQ_ALREADY_SET || 175352616f2bSIlan Peer treatment == REG_REQ_USER_HINT_HANDLED) { 1754c888393bSArik Nemtsov reg_free_request(user_request); 17550d97a619SLuis R. Rodriguez return treatment; 17560d97a619SLuis R. Rodriguez } 17570d97a619SLuis R. Rodriguez 17580d97a619SLuis R. Rodriguez user_request->intersect = treatment == REG_REQ_INTERSECT; 17590d97a619SLuis R. Rodriguez user_request->processed = false; 17605ad6ef5eSLuis R. Rodriguez 176105f1a3eaSLuis R. Rodriguez reg_update_last_request(user_request); 17620d97a619SLuis R. Rodriguez 17630d97a619SLuis R. Rodriguez user_alpha2[0] = user_request->alpha2[0]; 17640d97a619SLuis R. Rodriguez user_alpha2[1] = user_request->alpha2[1]; 17650d97a619SLuis R. Rodriguez 1766fe6631ffSLuis R. Rodriguez return reg_call_crda(user_request); 17670d97a619SLuis R. Rodriguez } 17680d97a619SLuis R. Rodriguez 176921636c7fSLuis R. Rodriguez static enum reg_request_treatment 177021636c7fSLuis R. Rodriguez __reg_process_hint_driver(struct regulatory_request *driver_request) 177121636c7fSLuis R. Rodriguez { 177221636c7fSLuis R. Rodriguez struct regulatory_request *lr = get_last_request(); 177321636c7fSLuis R. Rodriguez 177421636c7fSLuis R. Rodriguez if (lr->initiator == NL80211_REGDOM_SET_BY_CORE) { 177521636c7fSLuis R. Rodriguez if (regdom_changes(driver_request->alpha2)) 177621636c7fSLuis R. Rodriguez return REG_REQ_OK; 177721636c7fSLuis R. Rodriguez return REG_REQ_ALREADY_SET; 177821636c7fSLuis R. Rodriguez } 177921636c7fSLuis R. Rodriguez 178021636c7fSLuis R. Rodriguez /* 178121636c7fSLuis R. Rodriguez * This would happen if you unplug and plug your card 178221636c7fSLuis R. Rodriguez * back in or if you add a new device for which the previously 178321636c7fSLuis R. Rodriguez * loaded card also agrees on the regulatory domain. 178421636c7fSLuis R. Rodriguez */ 178521636c7fSLuis R. Rodriguez if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER && 178621636c7fSLuis R. Rodriguez !regdom_changes(driver_request->alpha2)) 178721636c7fSLuis R. Rodriguez return REG_REQ_ALREADY_SET; 178821636c7fSLuis R. Rodriguez 178921636c7fSLuis R. Rodriguez return REG_REQ_INTERSECT; 179021636c7fSLuis R. Rodriguez } 179121636c7fSLuis R. Rodriguez 179221636c7fSLuis R. Rodriguez /** 179321636c7fSLuis R. Rodriguez * reg_process_hint_driver - process driver regulatory requests 179421636c7fSLuis R. Rodriguez * @driver_request: a pending driver regulatory request 179521636c7fSLuis R. Rodriguez * 179621636c7fSLuis R. Rodriguez * The wireless subsystem can use this function to process 179721636c7fSLuis R. Rodriguez * a regulatory request issued by an 802.11 driver. 179821636c7fSLuis R. Rodriguez * 179921636c7fSLuis R. Rodriguez * Returns one of the different reg request treatment values. 180021636c7fSLuis R. Rodriguez */ 180121636c7fSLuis R. Rodriguez static enum reg_request_treatment 180221636c7fSLuis R. Rodriguez reg_process_hint_driver(struct wiphy *wiphy, 180321636c7fSLuis R. Rodriguez struct regulatory_request *driver_request) 180421636c7fSLuis R. Rodriguez { 180521636c7fSLuis R. Rodriguez const struct ieee80211_regdomain *regd; 180621636c7fSLuis R. Rodriguez enum reg_request_treatment treatment; 180721636c7fSLuis R. Rodriguez 180821636c7fSLuis R. Rodriguez treatment = __reg_process_hint_driver(driver_request); 180921636c7fSLuis R. Rodriguez 181021636c7fSLuis R. Rodriguez switch (treatment) { 181121636c7fSLuis R. Rodriguez case REG_REQ_OK: 181221636c7fSLuis R. Rodriguez break; 181321636c7fSLuis R. Rodriguez case REG_REQ_IGNORE: 181452616f2bSIlan Peer case REG_REQ_USER_HINT_HANDLED: 1815c888393bSArik Nemtsov reg_free_request(driver_request); 181621636c7fSLuis R. Rodriguez return treatment; 181721636c7fSLuis R. Rodriguez case REG_REQ_INTERSECT: 181821636c7fSLuis R. Rodriguez /* fall through */ 181921636c7fSLuis R. Rodriguez case REG_REQ_ALREADY_SET: 182021636c7fSLuis R. Rodriguez regd = reg_copy_regd(get_cfg80211_regdom()); 182121636c7fSLuis R. Rodriguez if (IS_ERR(regd)) { 1822c888393bSArik Nemtsov reg_free_request(driver_request); 182321636c7fSLuis R. Rodriguez return REG_REQ_IGNORE; 182421636c7fSLuis R. Rodriguez } 182521636c7fSLuis R. Rodriguez rcu_assign_pointer(wiphy->regd, regd); 182621636c7fSLuis R. Rodriguez } 182721636c7fSLuis R. Rodriguez 182821636c7fSLuis R. Rodriguez 182921636c7fSLuis R. Rodriguez driver_request->intersect = treatment == REG_REQ_INTERSECT; 183021636c7fSLuis R. Rodriguez driver_request->processed = false; 18315ad6ef5eSLuis R. Rodriguez 183205f1a3eaSLuis R. Rodriguez reg_update_last_request(driver_request); 183321636c7fSLuis R. Rodriguez 183421636c7fSLuis R. Rodriguez /* 183521636c7fSLuis R. Rodriguez * Since CRDA will not be called in this case as we already 183621636c7fSLuis R. Rodriguez * have applied the requested regulatory domain before we just 183721636c7fSLuis R. Rodriguez * inform userspace we have processed the request 183821636c7fSLuis R. Rodriguez */ 183921636c7fSLuis R. Rodriguez if (treatment == REG_REQ_ALREADY_SET) { 184021636c7fSLuis R. Rodriguez nl80211_send_reg_change_event(driver_request); 184121636c7fSLuis R. Rodriguez reg_set_request_processed(); 184221636c7fSLuis R. Rodriguez return treatment; 184321636c7fSLuis R. Rodriguez } 184421636c7fSLuis R. Rodriguez 1845fe6631ffSLuis R. Rodriguez return reg_call_crda(driver_request); 184621636c7fSLuis R. Rodriguez } 184721636c7fSLuis R. Rodriguez 1848b23e7a9eSLuis R. Rodriguez static enum reg_request_treatment 1849b23e7a9eSLuis R. Rodriguez __reg_process_hint_country_ie(struct wiphy *wiphy, 1850b23e7a9eSLuis R. Rodriguez struct regulatory_request *country_ie_request) 1851b23e7a9eSLuis R. Rodriguez { 1852b23e7a9eSLuis R. Rodriguez struct wiphy *last_wiphy = NULL; 1853b23e7a9eSLuis R. Rodriguez struct regulatory_request *lr = get_last_request(); 1854b23e7a9eSLuis R. Rodriguez 1855b23e7a9eSLuis R. Rodriguez if (reg_request_cell_base(lr)) { 1856b23e7a9eSLuis R. Rodriguez /* Trust a Cell base station over the AP's country IE */ 1857b23e7a9eSLuis R. Rodriguez if (regdom_changes(country_ie_request->alpha2)) 1858b23e7a9eSLuis R. Rodriguez return REG_REQ_IGNORE; 1859b23e7a9eSLuis R. Rodriguez return REG_REQ_ALREADY_SET; 18602a901468SLuis R. Rodriguez } else { 18612a901468SLuis R. Rodriguez if (wiphy->regulatory_flags & REGULATORY_COUNTRY_IE_IGNORE) 18622a901468SLuis R. Rodriguez return REG_REQ_IGNORE; 1863b23e7a9eSLuis R. Rodriguez } 1864b23e7a9eSLuis R. Rodriguez 1865b23e7a9eSLuis R. Rodriguez if (unlikely(!is_an_alpha2(country_ie_request->alpha2))) 1866b23e7a9eSLuis R. Rodriguez return -EINVAL; 18672f1c6c57SLuis R. Rodriguez 18682f1c6c57SLuis R. Rodriguez if (lr->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE) 18692f1c6c57SLuis R. Rodriguez return REG_REQ_OK; 18702f1c6c57SLuis R. Rodriguez 18712f1c6c57SLuis R. Rodriguez last_wiphy = wiphy_idx_to_wiphy(lr->wiphy_idx); 18722f1c6c57SLuis R. Rodriguez 1873b23e7a9eSLuis R. Rodriguez if (last_wiphy != wiphy) { 1874b23e7a9eSLuis R. Rodriguez /* 1875b23e7a9eSLuis R. Rodriguez * Two cards with two APs claiming different 1876b23e7a9eSLuis R. Rodriguez * Country IE alpha2s. We could 1877b23e7a9eSLuis R. Rodriguez * intersect them, but that seems unlikely 1878b23e7a9eSLuis R. Rodriguez * to be correct. Reject second one for now. 1879b23e7a9eSLuis R. Rodriguez */ 1880b23e7a9eSLuis R. Rodriguez if (regdom_changes(country_ie_request->alpha2)) 1881b23e7a9eSLuis R. Rodriguez return REG_REQ_IGNORE; 1882b23e7a9eSLuis R. Rodriguez return REG_REQ_ALREADY_SET; 1883b23e7a9eSLuis R. Rodriguez } 1884b23e7a9eSLuis R. Rodriguez /* 1885b23e7a9eSLuis R. Rodriguez * Two consecutive Country IE hints on the same wiphy. 1886b23e7a9eSLuis R. Rodriguez * This should be picked up early by the driver/stack 1887b23e7a9eSLuis R. Rodriguez */ 1888b23e7a9eSLuis R. Rodriguez if (WARN_ON(regdom_changes(country_ie_request->alpha2))) 1889b23e7a9eSLuis R. Rodriguez return REG_REQ_OK; 1890b23e7a9eSLuis R. Rodriguez return REG_REQ_ALREADY_SET; 1891b23e7a9eSLuis R. Rodriguez } 1892b23e7a9eSLuis R. Rodriguez 1893b3eb7f3fSLuis R. Rodriguez /** 1894b23e7a9eSLuis R. Rodriguez * reg_process_hint_country_ie - process regulatory requests from country IEs 1895b23e7a9eSLuis R. Rodriguez * @country_ie_request: a regulatory request from a country IE 1896d1c96a9aSLuis R. Rodriguez * 1897b23e7a9eSLuis R. Rodriguez * The wireless subsystem can use this function to process 1898b23e7a9eSLuis R. Rodriguez * a regulatory request issued by a country Information Element. 1899d1c96a9aSLuis R. Rodriguez * 19002f92212bSJohannes Berg * Returns one of the different reg request treatment values. 1901d1c96a9aSLuis R. Rodriguez */ 19022f92212bSJohannes Berg static enum reg_request_treatment 1903b23e7a9eSLuis R. Rodriguez reg_process_hint_country_ie(struct wiphy *wiphy, 1904b23e7a9eSLuis R. Rodriguez struct regulatory_request *country_ie_request) 1905b2e1b302SLuis R. Rodriguez { 19062f92212bSJohannes Berg enum reg_request_treatment treatment; 1907b2e1b302SLuis R. Rodriguez 1908b23e7a9eSLuis R. Rodriguez treatment = __reg_process_hint_country_ie(wiphy, country_ie_request); 1909761cf7ecSLuis R. Rodriguez 19102f92212bSJohannes Berg switch (treatment) { 19112f92212bSJohannes Berg case REG_REQ_OK: 19122f92212bSJohannes Berg break; 1913b23e7a9eSLuis R. Rodriguez case REG_REQ_IGNORE: 191452616f2bSIlan Peer case REG_REQ_USER_HINT_HANDLED: 1915b23e7a9eSLuis R. Rodriguez /* fall through */ 1916b23e7a9eSLuis R. Rodriguez case REG_REQ_ALREADY_SET: 1917c888393bSArik Nemtsov reg_free_request(country_ie_request); 1918b23e7a9eSLuis R. Rodriguez return treatment; 1919b23e7a9eSLuis R. Rodriguez case REG_REQ_INTERSECT: 1920c888393bSArik Nemtsov reg_free_request(country_ie_request); 1921fb1fc7adSLuis R. Rodriguez /* 1922b23e7a9eSLuis R. Rodriguez * This doesn't happen yet, not sure we 1923b23e7a9eSLuis R. Rodriguez * ever want to support it for this case. 1924fb1fc7adSLuis R. Rodriguez */ 1925b23e7a9eSLuis R. Rodriguez WARN_ONCE(1, "Unexpected intersection for country IEs"); 19262f92212bSJohannes Berg return REG_REQ_IGNORE; 1927d951c1ddSLuis R. Rodriguez } 1928b2e1b302SLuis R. Rodriguez 1929b23e7a9eSLuis R. Rodriguez country_ie_request->intersect = false; 1930b23e7a9eSLuis R. Rodriguez country_ie_request->processed = false; 19315ad6ef5eSLuis R. Rodriguez 193205f1a3eaSLuis R. Rodriguez reg_update_last_request(country_ie_request); 1933d951c1ddSLuis R. Rodriguez 1934fe6631ffSLuis R. Rodriguez return reg_call_crda(country_ie_request); 1935b2e1b302SLuis R. Rodriguez } 1936b2e1b302SLuis R. Rodriguez 193730a548c7SLuis R. Rodriguez /* This processes *all* regulatory hints */ 19381daa37c7SLuis R. Rodriguez static void reg_process_hint(struct regulatory_request *reg_request) 1939fe33eb39SLuis R. Rodriguez { 1940fe33eb39SLuis R. Rodriguez struct wiphy *wiphy = NULL; 1941b3eb7f3fSLuis R. Rodriguez enum reg_request_treatment treatment; 1942fe33eb39SLuis R. Rodriguez 1943f4173766SJohannes Berg if (reg_request->wiphy_idx != WIPHY_IDX_INVALID) 1944fe33eb39SLuis R. Rodriguez wiphy = wiphy_idx_to_wiphy(reg_request->wiphy_idx); 1945fe33eb39SLuis R. Rodriguez 1946b3eb7f3fSLuis R. Rodriguez switch (reg_request->initiator) { 1947b3eb7f3fSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_CORE: 1948b3eb7f3fSLuis R. Rodriguez reg_process_hint_core(reg_request); 1949b3eb7f3fSLuis R. Rodriguez return; 1950b3eb7f3fSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_USER: 19510d97a619SLuis R. Rodriguez treatment = reg_process_hint_user(reg_request); 195250c11eb9SInbal Hacohen if (treatment == REG_REQ_IGNORE || 19534d3df547SArik Nemtsov treatment == REG_REQ_ALREADY_SET || 19544d3df547SArik Nemtsov treatment == REG_REQ_USER_HINT_HANDLED) 19550d97a619SLuis R. Rodriguez return; 1956845f3351SShaibal Dutta queue_delayed_work(system_power_efficient_wq, 1957845f3351SShaibal Dutta ®_timeout, msecs_to_jiffies(3142)); 19580d97a619SLuis R. Rodriguez return; 1959b3eb7f3fSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_DRIVER: 1960772f0389SIlan Peer if (!wiphy) 1961772f0389SIlan Peer goto out_free; 196221636c7fSLuis R. Rodriguez treatment = reg_process_hint_driver(wiphy, reg_request); 196321636c7fSLuis R. Rodriguez break; 1964b3eb7f3fSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_COUNTRY_IE: 1965772f0389SIlan Peer if (!wiphy) 1966772f0389SIlan Peer goto out_free; 1967b23e7a9eSLuis R. Rodriguez treatment = reg_process_hint_country_ie(wiphy, reg_request); 1968b3eb7f3fSLuis R. Rodriguez break; 1969b3eb7f3fSLuis R. Rodriguez default: 1970b3eb7f3fSLuis R. Rodriguez WARN(1, "invalid initiator %d\n", reg_request->initiator); 1971772f0389SIlan Peer goto out_free; 1972b3eb7f3fSLuis R. Rodriguez } 1973b3eb7f3fSLuis R. Rodriguez 1974fe33eb39SLuis R. Rodriguez /* This is required so that the orig_* parameters are saved */ 1975b23e7a9eSLuis R. Rodriguez if (treatment == REG_REQ_ALREADY_SET && wiphy && 1976a2f73b6cSLuis R. Rodriguez wiphy->regulatory_flags & REGULATORY_STRICT_REG) 19771daa37c7SLuis R. Rodriguez wiphy_update_regulatory(wiphy, reg_request->initiator); 1978772f0389SIlan Peer 1979772f0389SIlan Peer return; 1980772f0389SIlan Peer 1981772f0389SIlan Peer out_free: 1982c888393bSArik Nemtsov reg_free_request(reg_request); 1983fe33eb39SLuis R. Rodriguez } 1984fe33eb39SLuis R. Rodriguez 1985b2e253cfSLuis R. Rodriguez /* 1986b2e253cfSLuis R. Rodriguez * Processes regulatory hints, this is all the NL80211_REGDOM_SET_BY_* 1987b2e253cfSLuis R. Rodriguez * Regulatory hints come on a first come first serve basis and we 1988b2e253cfSLuis R. Rodriguez * must process each one atomically. 1989b2e253cfSLuis R. Rodriguez */ 1990fe33eb39SLuis R. Rodriguez static void reg_process_pending_hints(void) 1991fe33eb39SLuis R. Rodriguez { 1992c492db37SJohannes Berg struct regulatory_request *reg_request, *lr; 1993fe33eb39SLuis R. Rodriguez 1994c492db37SJohannes Berg lr = get_last_request(); 1995b0e2880bSLuis R. Rodriguez 1996b2e253cfSLuis R. Rodriguez /* When last_request->processed becomes true this will be rescheduled */ 1997c492db37SJohannes Berg if (lr && !lr->processed) { 199896cce12fSLuis R. Rodriguez reg_process_hint(lr); 19995fe231e8SJohannes Berg return; 2000b2e253cfSLuis R. Rodriguez } 2001b2e253cfSLuis R. Rodriguez 2002fe33eb39SLuis R. Rodriguez spin_lock(®_requests_lock); 2003b2e253cfSLuis R. Rodriguez 2004b2e253cfSLuis R. Rodriguez if (list_empty(®_requests_list)) { 2005b2e253cfSLuis R. Rodriguez spin_unlock(®_requests_lock); 20065fe231e8SJohannes Berg return; 2007b2e253cfSLuis R. Rodriguez } 2008b2e253cfSLuis R. Rodriguez 2009fe33eb39SLuis R. Rodriguez reg_request = list_first_entry(®_requests_list, 2010fe33eb39SLuis R. Rodriguez struct regulatory_request, 2011fe33eb39SLuis R. Rodriguez list); 2012fe33eb39SLuis R. Rodriguez list_del_init(®_request->list); 2013fe33eb39SLuis R. Rodriguez 2014d951c1ddSLuis R. Rodriguez spin_unlock(®_requests_lock); 2015b0e2880bSLuis R. Rodriguez 20161daa37c7SLuis R. Rodriguez reg_process_hint(reg_request); 2017fe33eb39SLuis R. Rodriguez } 2018fe33eb39SLuis R. Rodriguez 2019e38f8a7aSLuis R. Rodriguez /* Processes beacon hints -- this has nothing to do with country IEs */ 2020e38f8a7aSLuis R. Rodriguez static void reg_process_pending_beacon_hints(void) 2021e38f8a7aSLuis R. Rodriguez { 202279c97e97SJohannes Berg struct cfg80211_registered_device *rdev; 2023e38f8a7aSLuis R. Rodriguez struct reg_beacon *pending_beacon, *tmp; 2024e38f8a7aSLuis R. Rodriguez 2025e38f8a7aSLuis R. Rodriguez /* This goes through the _pending_ beacon list */ 2026e38f8a7aSLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 2027e38f8a7aSLuis R. Rodriguez 2028e38f8a7aSLuis R. Rodriguez list_for_each_entry_safe(pending_beacon, tmp, 2029e38f8a7aSLuis R. Rodriguez ®_pending_beacons, list) { 2030e38f8a7aSLuis R. Rodriguez list_del_init(&pending_beacon->list); 2031e38f8a7aSLuis R. Rodriguez 2032e38f8a7aSLuis R. Rodriguez /* Applies the beacon hint to current wiphys */ 203379c97e97SJohannes Berg list_for_each_entry(rdev, &cfg80211_rdev_list, list) 203479c97e97SJohannes Berg wiphy_update_new_beacon(&rdev->wiphy, pending_beacon); 2035e38f8a7aSLuis R. Rodriguez 2036e38f8a7aSLuis R. Rodriguez /* Remembers the beacon hint for new wiphys or reg changes */ 2037e38f8a7aSLuis R. Rodriguez list_add_tail(&pending_beacon->list, ®_beacon_list); 2038e38f8a7aSLuis R. Rodriguez } 2039e38f8a7aSLuis R. Rodriguez 2040e38f8a7aSLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 2041e38f8a7aSLuis R. Rodriguez } 2042e38f8a7aSLuis R. Rodriguez 2043fe33eb39SLuis R. Rodriguez static void reg_todo(struct work_struct *work) 2044fe33eb39SLuis R. Rodriguez { 20455fe231e8SJohannes Berg rtnl_lock(); 2046fe33eb39SLuis R. Rodriguez reg_process_pending_hints(); 2047e38f8a7aSLuis R. Rodriguez reg_process_pending_beacon_hints(); 20485fe231e8SJohannes Berg rtnl_unlock(); 2049fe33eb39SLuis R. Rodriguez } 2050fe33eb39SLuis R. Rodriguez 2051fe33eb39SLuis R. Rodriguez static void queue_regulatory_request(struct regulatory_request *request) 2052fe33eb39SLuis R. Rodriguez { 2053c61029c7SJohn W. Linville request->alpha2[0] = toupper(request->alpha2[0]); 2054c61029c7SJohn W. Linville request->alpha2[1] = toupper(request->alpha2[1]); 2055c61029c7SJohn W. Linville 2056fe33eb39SLuis R. Rodriguez spin_lock(®_requests_lock); 2057fe33eb39SLuis R. Rodriguez list_add_tail(&request->list, ®_requests_list); 2058fe33eb39SLuis R. Rodriguez spin_unlock(®_requests_lock); 2059fe33eb39SLuis R. Rodriguez 2060fe33eb39SLuis R. Rodriguez schedule_work(®_work); 2061fe33eb39SLuis R. Rodriguez } 2062fe33eb39SLuis R. Rodriguez 206309d989d1SLuis R. Rodriguez /* 206409d989d1SLuis R. Rodriguez * Core regulatory hint -- happens during cfg80211_init() 206509d989d1SLuis R. Rodriguez * and when we restore regulatory settings. 206609d989d1SLuis R. Rodriguez */ 2067ba25c141SLuis R. Rodriguez static int regulatory_hint_core(const char *alpha2) 2068ba25c141SLuis R. Rodriguez { 2069ba25c141SLuis R. Rodriguez struct regulatory_request *request; 2070ba25c141SLuis R. Rodriguez 20711a919318SJohannes Berg request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL); 2072ba25c141SLuis R. Rodriguez if (!request) 2073ba25c141SLuis R. Rodriguez return -ENOMEM; 2074ba25c141SLuis R. Rodriguez 2075ba25c141SLuis R. Rodriguez request->alpha2[0] = alpha2[0]; 2076ba25c141SLuis R. Rodriguez request->alpha2[1] = alpha2[1]; 20777db90f4aSLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_CORE; 2078ba25c141SLuis R. Rodriguez 207931e99729SLuis R. Rodriguez queue_regulatory_request(request); 20805078b2e3SLuis R. Rodriguez 2081fe33eb39SLuis R. Rodriguez return 0; 2082ba25c141SLuis R. Rodriguez } 2083ba25c141SLuis R. Rodriguez 2084fe33eb39SLuis R. Rodriguez /* User hints */ 208557b5ce07SLuis R. Rodriguez int regulatory_hint_user(const char *alpha2, 208657b5ce07SLuis R. Rodriguez enum nl80211_user_reg_hint_type user_reg_hint_type) 2087b2e1b302SLuis R. Rodriguez { 2088fe33eb39SLuis R. Rodriguez struct regulatory_request *request; 2089fe33eb39SLuis R. Rodriguez 2090fdc9d7b2SJohannes Berg if (WARN_ON(!alpha2)) 2091fdc9d7b2SJohannes Berg return -EINVAL; 2092b2e1b302SLuis R. Rodriguez 2093fe33eb39SLuis R. Rodriguez request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL); 2094fe33eb39SLuis R. Rodriguez if (!request) 2095fe33eb39SLuis R. Rodriguez return -ENOMEM; 2096fe33eb39SLuis R. Rodriguez 2097f4173766SJohannes Berg request->wiphy_idx = WIPHY_IDX_INVALID; 2098fe33eb39SLuis R. Rodriguez request->alpha2[0] = alpha2[0]; 2099fe33eb39SLuis R. Rodriguez request->alpha2[1] = alpha2[1]; 2100e12822e1SLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_USER; 210157b5ce07SLuis R. Rodriguez request->user_reg_hint_type = user_reg_hint_type; 2102fe33eb39SLuis R. Rodriguez 2103fe33eb39SLuis R. Rodriguez queue_regulatory_request(request); 2104fe33eb39SLuis R. Rodriguez 2105fe33eb39SLuis R. Rodriguez return 0; 2106fe33eb39SLuis R. Rodriguez } 2107fe33eb39SLuis R. Rodriguez 210852616f2bSIlan Peer int regulatory_hint_indoor_user(void) 210952616f2bSIlan Peer { 211052616f2bSIlan Peer struct regulatory_request *request; 211152616f2bSIlan Peer 211252616f2bSIlan Peer request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL); 211352616f2bSIlan Peer if (!request) 211452616f2bSIlan Peer return -ENOMEM; 211552616f2bSIlan Peer 211652616f2bSIlan Peer request->wiphy_idx = WIPHY_IDX_INVALID; 211752616f2bSIlan Peer request->initiator = NL80211_REGDOM_SET_BY_USER; 211852616f2bSIlan Peer request->user_reg_hint_type = NL80211_USER_REG_HINT_INDOOR; 211952616f2bSIlan Peer queue_regulatory_request(request); 212052616f2bSIlan Peer 212152616f2bSIlan Peer return 0; 212252616f2bSIlan Peer } 212352616f2bSIlan Peer 2124fe33eb39SLuis R. Rodriguez /* Driver hints */ 2125fe33eb39SLuis R. Rodriguez int regulatory_hint(struct wiphy *wiphy, const char *alpha2) 2126fe33eb39SLuis R. Rodriguez { 2127fe33eb39SLuis R. Rodriguez struct regulatory_request *request; 2128fe33eb39SLuis R. Rodriguez 2129fdc9d7b2SJohannes Berg if (WARN_ON(!alpha2 || !wiphy)) 2130fdc9d7b2SJohannes Berg return -EINVAL; 2131fe33eb39SLuis R. Rodriguez 21324f7b9140SLuis R. Rodriguez wiphy->regulatory_flags &= ~REGULATORY_CUSTOM_REG; 21334f7b9140SLuis R. Rodriguez 2134fe33eb39SLuis R. Rodriguez request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL); 2135fe33eb39SLuis R. Rodriguez if (!request) 2136fe33eb39SLuis R. Rodriguez return -ENOMEM; 2137fe33eb39SLuis R. Rodriguez 2138fe33eb39SLuis R. Rodriguez request->wiphy_idx = get_wiphy_idx(wiphy); 2139fe33eb39SLuis R. Rodriguez 2140fe33eb39SLuis R. Rodriguez request->alpha2[0] = alpha2[0]; 2141fe33eb39SLuis R. Rodriguez request->alpha2[1] = alpha2[1]; 21427db90f4aSLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_DRIVER; 2143fe33eb39SLuis R. Rodriguez 2144fe33eb39SLuis R. Rodriguez queue_regulatory_request(request); 2145fe33eb39SLuis R. Rodriguez 2146fe33eb39SLuis R. Rodriguez return 0; 2147b2e1b302SLuis R. Rodriguez } 2148b2e1b302SLuis R. Rodriguez EXPORT_SYMBOL(regulatory_hint); 2149b2e1b302SLuis R. Rodriguez 2150789fd033SLuis R. Rodriguez void regulatory_hint_country_ie(struct wiphy *wiphy, enum ieee80211_band band, 21511a919318SJohannes Berg const u8 *country_ie, u8 country_ie_len) 21523f2355cbSLuis R. Rodriguez { 21533f2355cbSLuis R. Rodriguez char alpha2[2]; 21543f2355cbSLuis R. Rodriguez enum environment_cap env = ENVIRON_ANY; 2155db2424c5SJohannes Berg struct regulatory_request *request = NULL, *lr; 2156d335fe63SLuis R. Rodriguez 21573f2355cbSLuis R. Rodriguez /* IE len must be evenly divisible by 2 */ 21583f2355cbSLuis R. Rodriguez if (country_ie_len & 0x01) 2159db2424c5SJohannes Berg return; 21603f2355cbSLuis R. Rodriguez 21613f2355cbSLuis R. Rodriguez if (country_ie_len < IEEE80211_COUNTRY_IE_MIN_LEN) 2162db2424c5SJohannes Berg return; 2163db2424c5SJohannes Berg 2164db2424c5SJohannes Berg request = kzalloc(sizeof(*request), GFP_KERNEL); 2165db2424c5SJohannes Berg if (!request) 2166db2424c5SJohannes Berg return; 21673f2355cbSLuis R. Rodriguez 21683f2355cbSLuis R. Rodriguez alpha2[0] = country_ie[0]; 21693f2355cbSLuis R. Rodriguez alpha2[1] = country_ie[1]; 21703f2355cbSLuis R. Rodriguez 21713f2355cbSLuis R. Rodriguez if (country_ie[2] == 'I') 21723f2355cbSLuis R. Rodriguez env = ENVIRON_INDOOR; 21733f2355cbSLuis R. Rodriguez else if (country_ie[2] == 'O') 21743f2355cbSLuis R. Rodriguez env = ENVIRON_OUTDOOR; 21753f2355cbSLuis R. Rodriguez 2176db2424c5SJohannes Berg rcu_read_lock(); 2177db2424c5SJohannes Berg lr = get_last_request(); 2178db2424c5SJohannes Berg 2179db2424c5SJohannes Berg if (unlikely(!lr)) 2180db2424c5SJohannes Berg goto out; 2181db2424c5SJohannes Berg 2182fb1fc7adSLuis R. Rodriguez /* 21838b19e6caSLuis R. Rodriguez * We will run this only upon a successful connection on cfg80211. 21844b44c8bcSLuis R. Rodriguez * We leave conflict resolution to the workqueue, where can hold 21855fe231e8SJohannes Berg * the RTNL. 2186fb1fc7adSLuis R. Rodriguez */ 2187c492db37SJohannes Berg if (lr->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE && 2188c492db37SJohannes Berg lr->wiphy_idx != WIPHY_IDX_INVALID) 21893f2355cbSLuis R. Rodriguez goto out; 21903f2355cbSLuis R. Rodriguez 2191fe33eb39SLuis R. Rodriguez request->wiphy_idx = get_wiphy_idx(wiphy); 21924f366c5dSJohn W. Linville request->alpha2[0] = alpha2[0]; 21934f366c5dSJohn W. Linville request->alpha2[1] = alpha2[1]; 21947db90f4aSLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_COUNTRY_IE; 2195fe33eb39SLuis R. Rodriguez request->country_ie_env = env; 21963f2355cbSLuis R. Rodriguez 2197fe33eb39SLuis R. Rodriguez queue_regulatory_request(request); 2198db2424c5SJohannes Berg request = NULL; 21993f2355cbSLuis R. Rodriguez out: 2200db2424c5SJohannes Berg kfree(request); 2201db2424c5SJohannes Berg rcu_read_unlock(); 22023f2355cbSLuis R. Rodriguez } 2203b2e1b302SLuis R. Rodriguez 220409d989d1SLuis R. Rodriguez static void restore_alpha2(char *alpha2, bool reset_user) 220509d989d1SLuis R. Rodriguez { 220609d989d1SLuis R. Rodriguez /* indicates there is no alpha2 to consider for restoration */ 220709d989d1SLuis R. Rodriguez alpha2[0] = '9'; 220809d989d1SLuis R. Rodriguez alpha2[1] = '7'; 220909d989d1SLuis R. Rodriguez 221009d989d1SLuis R. Rodriguez /* The user setting has precedence over the module parameter */ 221109d989d1SLuis R. Rodriguez if (is_user_regdom_saved()) { 221209d989d1SLuis R. Rodriguez /* Unless we're asked to ignore it and reset it */ 221309d989d1SLuis R. Rodriguez if (reset_user) { 22141a919318SJohannes Berg REG_DBG_PRINT("Restoring regulatory settings including user preference\n"); 221509d989d1SLuis R. Rodriguez user_alpha2[0] = '9'; 221609d989d1SLuis R. Rodriguez user_alpha2[1] = '7'; 221709d989d1SLuis R. Rodriguez 221809d989d1SLuis R. Rodriguez /* 221909d989d1SLuis R. Rodriguez * If we're ignoring user settings, we still need to 222009d989d1SLuis R. Rodriguez * check the module parameter to ensure we put things 222109d989d1SLuis R. Rodriguez * back as they were for a full restore. 222209d989d1SLuis R. Rodriguez */ 222309d989d1SLuis R. Rodriguez if (!is_world_regdom(ieee80211_regdom)) { 22241a919318SJohannes Berg REG_DBG_PRINT("Keeping preference on module parameter ieee80211_regdom: %c%c\n", 22251a919318SJohannes Berg ieee80211_regdom[0], ieee80211_regdom[1]); 222609d989d1SLuis R. Rodriguez alpha2[0] = ieee80211_regdom[0]; 222709d989d1SLuis R. Rodriguez alpha2[1] = ieee80211_regdom[1]; 222809d989d1SLuis R. Rodriguez } 222909d989d1SLuis R. Rodriguez } else { 22301a919318SJohannes Berg REG_DBG_PRINT("Restoring regulatory settings while preserving user preference for: %c%c\n", 22311a919318SJohannes Berg user_alpha2[0], user_alpha2[1]); 223209d989d1SLuis R. Rodriguez alpha2[0] = user_alpha2[0]; 223309d989d1SLuis R. Rodriguez alpha2[1] = user_alpha2[1]; 223409d989d1SLuis R. Rodriguez } 223509d989d1SLuis R. Rodriguez } else if (!is_world_regdom(ieee80211_regdom)) { 22361a919318SJohannes Berg REG_DBG_PRINT("Keeping preference on module parameter ieee80211_regdom: %c%c\n", 22371a919318SJohannes Berg ieee80211_regdom[0], ieee80211_regdom[1]); 223809d989d1SLuis R. Rodriguez alpha2[0] = ieee80211_regdom[0]; 223909d989d1SLuis R. Rodriguez alpha2[1] = ieee80211_regdom[1]; 224009d989d1SLuis R. Rodriguez } else 2241d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Restoring regulatory settings\n"); 224209d989d1SLuis R. Rodriguez } 224309d989d1SLuis R. Rodriguez 22445ce543d1SRajkumar Manoharan static void restore_custom_reg_settings(struct wiphy *wiphy) 22455ce543d1SRajkumar Manoharan { 22465ce543d1SRajkumar Manoharan struct ieee80211_supported_band *sband; 22475ce543d1SRajkumar Manoharan enum ieee80211_band band; 22485ce543d1SRajkumar Manoharan struct ieee80211_channel *chan; 22495ce543d1SRajkumar Manoharan int i; 22505ce543d1SRajkumar Manoharan 22515ce543d1SRajkumar Manoharan for (band = 0; band < IEEE80211_NUM_BANDS; band++) { 22525ce543d1SRajkumar Manoharan sband = wiphy->bands[band]; 22535ce543d1SRajkumar Manoharan if (!sband) 22545ce543d1SRajkumar Manoharan continue; 22555ce543d1SRajkumar Manoharan for (i = 0; i < sband->n_channels; i++) { 22565ce543d1SRajkumar Manoharan chan = &sband->channels[i]; 22575ce543d1SRajkumar Manoharan chan->flags = chan->orig_flags; 22585ce543d1SRajkumar Manoharan chan->max_antenna_gain = chan->orig_mag; 22595ce543d1SRajkumar Manoharan chan->max_power = chan->orig_mpwr; 2260899852afSPaul Stewart chan->beacon_found = false; 22615ce543d1SRajkumar Manoharan } 22625ce543d1SRajkumar Manoharan } 22635ce543d1SRajkumar Manoharan } 22645ce543d1SRajkumar Manoharan 226509d989d1SLuis R. Rodriguez /* 226609d989d1SLuis R. Rodriguez * Restoring regulatory settings involves ingoring any 226709d989d1SLuis R. Rodriguez * possibly stale country IE information and user regulatory 226809d989d1SLuis R. Rodriguez * settings if so desired, this includes any beacon hints 226909d989d1SLuis R. Rodriguez * learned as we could have traveled outside to another country 227009d989d1SLuis R. Rodriguez * after disconnection. To restore regulatory settings we do 227109d989d1SLuis R. Rodriguez * exactly what we did at bootup: 227209d989d1SLuis R. Rodriguez * 227309d989d1SLuis R. Rodriguez * - send a core regulatory hint 227409d989d1SLuis R. Rodriguez * - send a user regulatory hint if applicable 227509d989d1SLuis R. Rodriguez * 227609d989d1SLuis R. Rodriguez * Device drivers that send a regulatory hint for a specific country 227709d989d1SLuis R. Rodriguez * keep their own regulatory domain on wiphy->regd so that does does 227809d989d1SLuis R. Rodriguez * not need to be remembered. 227909d989d1SLuis R. Rodriguez */ 228009d989d1SLuis R. Rodriguez static void restore_regulatory_settings(bool reset_user) 228109d989d1SLuis R. Rodriguez { 228209d989d1SLuis R. Rodriguez char alpha2[2]; 2283cee0bec5SDmitry Shmidt char world_alpha2[2]; 228409d989d1SLuis R. Rodriguez struct reg_beacon *reg_beacon, *btmp; 228514609555SLuis R. Rodriguez struct regulatory_request *reg_request, *tmp; 228614609555SLuis R. Rodriguez LIST_HEAD(tmp_reg_req_list); 22875ce543d1SRajkumar Manoharan struct cfg80211_registered_device *rdev; 228809d989d1SLuis R. Rodriguez 22895fe231e8SJohannes Berg ASSERT_RTNL(); 22905fe231e8SJohannes Berg 229152616f2bSIlan Peer reg_is_indoor = false; 229252616f2bSIlan Peer 22932d319867SJohannes Berg reset_regdomains(true, &world_regdom); 229409d989d1SLuis R. Rodriguez restore_alpha2(alpha2, reset_user); 229509d989d1SLuis R. Rodriguez 229614609555SLuis R. Rodriguez /* 229714609555SLuis R. Rodriguez * If there's any pending requests we simply 229814609555SLuis R. Rodriguez * stash them to a temporary pending queue and 229914609555SLuis R. Rodriguez * add then after we've restored regulatory 230014609555SLuis R. Rodriguez * settings. 230114609555SLuis R. Rodriguez */ 230214609555SLuis R. Rodriguez spin_lock(®_requests_lock); 2303fea9bcedSJohannes Berg list_for_each_entry_safe(reg_request, tmp, ®_requests_list, list) { 2304fea9bcedSJohannes Berg if (reg_request->initiator != NL80211_REGDOM_SET_BY_USER) 230514609555SLuis R. Rodriguez continue; 230600a9ac4cSWei Yongjun list_move_tail(®_request->list, &tmp_reg_req_list); 230714609555SLuis R. Rodriguez } 230814609555SLuis R. Rodriguez spin_unlock(®_requests_lock); 230914609555SLuis R. Rodriguez 231009d989d1SLuis R. Rodriguez /* Clear beacon hints */ 231109d989d1SLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 2312fea9bcedSJohannes Berg list_for_each_entry_safe(reg_beacon, btmp, ®_pending_beacons, list) { 231309d989d1SLuis R. Rodriguez list_del(®_beacon->list); 231409d989d1SLuis R. Rodriguez kfree(reg_beacon); 231509d989d1SLuis R. Rodriguez } 231609d989d1SLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 231709d989d1SLuis R. Rodriguez 2318fea9bcedSJohannes Berg list_for_each_entry_safe(reg_beacon, btmp, ®_beacon_list, list) { 231909d989d1SLuis R. Rodriguez list_del(®_beacon->list); 232009d989d1SLuis R. Rodriguez kfree(reg_beacon); 232109d989d1SLuis R. Rodriguez } 232209d989d1SLuis R. Rodriguez 232309d989d1SLuis R. Rodriguez /* First restore to the basic regulatory settings */ 2324379b82f4SJohannes Berg world_alpha2[0] = cfg80211_world_regdom->alpha2[0]; 2325379b82f4SJohannes Berg world_alpha2[1] = cfg80211_world_regdom->alpha2[1]; 232609d989d1SLuis R. Rodriguez 23275ce543d1SRajkumar Manoharan list_for_each_entry(rdev, &cfg80211_rdev_list, list) { 2328a2f73b6cSLuis R. Rodriguez if (rdev->wiphy.regulatory_flags & REGULATORY_CUSTOM_REG) 23295ce543d1SRajkumar Manoharan restore_custom_reg_settings(&rdev->wiphy); 23305ce543d1SRajkumar Manoharan } 23315ce543d1SRajkumar Manoharan 2332cee0bec5SDmitry Shmidt regulatory_hint_core(world_alpha2); 233309d989d1SLuis R. Rodriguez 233409d989d1SLuis R. Rodriguez /* 233509d989d1SLuis R. Rodriguez * This restores the ieee80211_regdom module parameter 233609d989d1SLuis R. Rodriguez * preference or the last user requested regulatory 233709d989d1SLuis R. Rodriguez * settings, user regulatory settings takes precedence. 233809d989d1SLuis R. Rodriguez */ 233909d989d1SLuis R. Rodriguez if (is_an_alpha2(alpha2)) 234057b5ce07SLuis R. Rodriguez regulatory_hint_user(user_alpha2, NL80211_USER_REG_HINT_USER); 234109d989d1SLuis R. Rodriguez 234214609555SLuis R. Rodriguez spin_lock(®_requests_lock); 234311cff96cSJohannes Berg list_splice_tail_init(&tmp_reg_req_list, ®_requests_list); 234414609555SLuis R. Rodriguez spin_unlock(®_requests_lock); 234514609555SLuis R. Rodriguez 234614609555SLuis R. Rodriguez REG_DBG_PRINT("Kicking the queue\n"); 234714609555SLuis R. Rodriguez 234814609555SLuis R. Rodriguez schedule_work(®_work); 234914609555SLuis R. Rodriguez } 235009d989d1SLuis R. Rodriguez 235109d989d1SLuis R. Rodriguez void regulatory_hint_disconnect(void) 235209d989d1SLuis R. Rodriguez { 23531a919318SJohannes Berg REG_DBG_PRINT("All devices are disconnected, going to restore regulatory settings\n"); 235409d989d1SLuis R. Rodriguez restore_regulatory_settings(false); 235509d989d1SLuis R. Rodriguez } 235609d989d1SLuis R. Rodriguez 2357e38f8a7aSLuis R. Rodriguez static bool freq_is_chan_12_13_14(u16 freq) 2358e38f8a7aSLuis R. Rodriguez { 235959eb21a6SBruno Randolf if (freq == ieee80211_channel_to_frequency(12, IEEE80211_BAND_2GHZ) || 236059eb21a6SBruno Randolf freq == ieee80211_channel_to_frequency(13, IEEE80211_BAND_2GHZ) || 236159eb21a6SBruno Randolf freq == ieee80211_channel_to_frequency(14, IEEE80211_BAND_2GHZ)) 2362e38f8a7aSLuis R. Rodriguez return true; 2363e38f8a7aSLuis R. Rodriguez return false; 2364e38f8a7aSLuis R. Rodriguez } 2365e38f8a7aSLuis R. Rodriguez 23663ebfa6e7SLuis R. Rodriguez static bool pending_reg_beacon(struct ieee80211_channel *beacon_chan) 23673ebfa6e7SLuis R. Rodriguez { 23683ebfa6e7SLuis R. Rodriguez struct reg_beacon *pending_beacon; 23693ebfa6e7SLuis R. Rodriguez 23703ebfa6e7SLuis R. Rodriguez list_for_each_entry(pending_beacon, ®_pending_beacons, list) 23713ebfa6e7SLuis R. Rodriguez if (beacon_chan->center_freq == 23723ebfa6e7SLuis R. Rodriguez pending_beacon->chan.center_freq) 23733ebfa6e7SLuis R. Rodriguez return true; 23743ebfa6e7SLuis R. Rodriguez return false; 23753ebfa6e7SLuis R. Rodriguez } 23763ebfa6e7SLuis R. Rodriguez 2377e38f8a7aSLuis R. Rodriguez int regulatory_hint_found_beacon(struct wiphy *wiphy, 2378e38f8a7aSLuis R. Rodriguez struct ieee80211_channel *beacon_chan, 2379e38f8a7aSLuis R. Rodriguez gfp_t gfp) 2380e38f8a7aSLuis R. Rodriguez { 2381e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon; 23823ebfa6e7SLuis R. Rodriguez bool processing; 2383e38f8a7aSLuis R. Rodriguez 23841a919318SJohannes Berg if (beacon_chan->beacon_found || 23851a919318SJohannes Berg beacon_chan->flags & IEEE80211_CHAN_RADAR || 2386e38f8a7aSLuis R. Rodriguez (beacon_chan->band == IEEE80211_BAND_2GHZ && 23871a919318SJohannes Berg !freq_is_chan_12_13_14(beacon_chan->center_freq))) 2388e38f8a7aSLuis R. Rodriguez return 0; 2389e38f8a7aSLuis R. Rodriguez 23903ebfa6e7SLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 23913ebfa6e7SLuis R. Rodriguez processing = pending_reg_beacon(beacon_chan); 23923ebfa6e7SLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 23933ebfa6e7SLuis R. Rodriguez 23943ebfa6e7SLuis R. Rodriguez if (processing) 2395e38f8a7aSLuis R. Rodriguez return 0; 2396e38f8a7aSLuis R. Rodriguez 2397e38f8a7aSLuis R. Rodriguez reg_beacon = kzalloc(sizeof(struct reg_beacon), gfp); 2398e38f8a7aSLuis R. Rodriguez if (!reg_beacon) 2399e38f8a7aSLuis R. Rodriguez return -ENOMEM; 2400e38f8a7aSLuis R. Rodriguez 24011a919318SJohannes Berg REG_DBG_PRINT("Found new beacon on frequency: %d MHz (Ch %d) on %s\n", 2402e38f8a7aSLuis R. Rodriguez beacon_chan->center_freq, 2403e38f8a7aSLuis R. Rodriguez ieee80211_frequency_to_channel(beacon_chan->center_freq), 2404e38f8a7aSLuis R. Rodriguez wiphy_name(wiphy)); 24054113f751SLuis R. Rodriguez 2406e38f8a7aSLuis R. Rodriguez memcpy(®_beacon->chan, beacon_chan, 2407e38f8a7aSLuis R. Rodriguez sizeof(struct ieee80211_channel)); 2408e38f8a7aSLuis R. Rodriguez 2409e38f8a7aSLuis R. Rodriguez /* 2410e38f8a7aSLuis R. Rodriguez * Since we can be called from BH or and non-BH context 2411e38f8a7aSLuis R. Rodriguez * we must use spin_lock_bh() 2412e38f8a7aSLuis R. Rodriguez */ 2413e38f8a7aSLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 2414e38f8a7aSLuis R. Rodriguez list_add_tail(®_beacon->list, ®_pending_beacons); 2415e38f8a7aSLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 2416e38f8a7aSLuis R. Rodriguez 2417e38f8a7aSLuis R. Rodriguez schedule_work(®_work); 2418e38f8a7aSLuis R. Rodriguez 2419e38f8a7aSLuis R. Rodriguez return 0; 2420e38f8a7aSLuis R. Rodriguez } 2421e38f8a7aSLuis R. Rodriguez 2422a3d2eaf0SJohannes Berg static void print_rd_rules(const struct ieee80211_regdomain *rd) 2423b2e1b302SLuis R. Rodriguez { 2424b2e1b302SLuis R. Rodriguez unsigned int i; 2425a3d2eaf0SJohannes Berg const struct ieee80211_reg_rule *reg_rule = NULL; 2426a3d2eaf0SJohannes Berg const struct ieee80211_freq_range *freq_range = NULL; 2427a3d2eaf0SJohannes Berg const struct ieee80211_power_rule *power_rule = NULL; 2428089027e5SJanusz Dziedzic char bw[32], cac_time[32]; 2429b2e1b302SLuis R. Rodriguez 2430089027e5SJanusz Dziedzic pr_info(" (start_freq - end_freq @ bandwidth), (max_antenna_gain, max_eirp), (dfs_cac_time)\n"); 2431b2e1b302SLuis R. Rodriguez 2432b2e1b302SLuis R. Rodriguez for (i = 0; i < rd->n_reg_rules; i++) { 2433b2e1b302SLuis R. Rodriguez reg_rule = &rd->reg_rules[i]; 2434b2e1b302SLuis R. Rodriguez freq_range = ®_rule->freq_range; 2435b2e1b302SLuis R. Rodriguez power_rule = ®_rule->power_rule; 2436b2e1b302SLuis R. Rodriguez 2437b0dfd2eaSJanusz Dziedzic if (reg_rule->flags & NL80211_RRF_AUTO_BW) 2438b0dfd2eaSJanusz Dziedzic snprintf(bw, sizeof(bw), "%d KHz, %d KHz AUTO", 2439b0dfd2eaSJanusz Dziedzic freq_range->max_bandwidth_khz, 244097524820SJanusz Dziedzic reg_get_max_bandwidth(rd, reg_rule)); 244197524820SJanusz Dziedzic else 2442b0dfd2eaSJanusz Dziedzic snprintf(bw, sizeof(bw), "%d KHz", 244397524820SJanusz Dziedzic freq_range->max_bandwidth_khz); 244497524820SJanusz Dziedzic 2445089027e5SJanusz Dziedzic if (reg_rule->flags & NL80211_RRF_DFS) 2446089027e5SJanusz Dziedzic scnprintf(cac_time, sizeof(cac_time), "%u s", 2447089027e5SJanusz Dziedzic reg_rule->dfs_cac_ms/1000); 2448089027e5SJanusz Dziedzic else 2449089027e5SJanusz Dziedzic scnprintf(cac_time, sizeof(cac_time), "N/A"); 2450089027e5SJanusz Dziedzic 2451089027e5SJanusz Dziedzic 2452fb1fc7adSLuis R. Rodriguez /* 2453fb1fc7adSLuis R. Rodriguez * There may not be documentation for max antenna gain 2454fb1fc7adSLuis R. Rodriguez * in certain regions 2455fb1fc7adSLuis R. Rodriguez */ 2456b2e1b302SLuis R. Rodriguez if (power_rule->max_antenna_gain) 2457089027e5SJanusz Dziedzic pr_info(" (%d KHz - %d KHz @ %s), (%d mBi, %d mBm), (%s)\n", 2458b2e1b302SLuis R. Rodriguez freq_range->start_freq_khz, 2459b2e1b302SLuis R. Rodriguez freq_range->end_freq_khz, 246097524820SJanusz Dziedzic bw, 2461b2e1b302SLuis R. Rodriguez power_rule->max_antenna_gain, 2462089027e5SJanusz Dziedzic power_rule->max_eirp, 2463089027e5SJanusz Dziedzic cac_time); 2464b2e1b302SLuis R. Rodriguez else 2465089027e5SJanusz Dziedzic pr_info(" (%d KHz - %d KHz @ %s), (N/A, %d mBm), (%s)\n", 2466b2e1b302SLuis R. Rodriguez freq_range->start_freq_khz, 2467b2e1b302SLuis R. Rodriguez freq_range->end_freq_khz, 246897524820SJanusz Dziedzic bw, 2469089027e5SJanusz Dziedzic power_rule->max_eirp, 2470089027e5SJanusz Dziedzic cac_time); 2471b2e1b302SLuis R. Rodriguez } 2472b2e1b302SLuis R. Rodriguez } 2473b2e1b302SLuis R. Rodriguez 24744c7d3982SLuis R. Rodriguez bool reg_supported_dfs_region(enum nl80211_dfs_regions dfs_region) 24758b60b078SLuis R. Rodriguez { 24768b60b078SLuis R. Rodriguez switch (dfs_region) { 24778b60b078SLuis R. Rodriguez case NL80211_DFS_UNSET: 24788b60b078SLuis R. Rodriguez case NL80211_DFS_FCC: 24798b60b078SLuis R. Rodriguez case NL80211_DFS_ETSI: 24808b60b078SLuis R. Rodriguez case NL80211_DFS_JP: 24818b60b078SLuis R. Rodriguez return true; 24828b60b078SLuis R. Rodriguez default: 24838b60b078SLuis R. Rodriguez REG_DBG_PRINT("Ignoring uknown DFS master region: %d\n", 24848b60b078SLuis R. Rodriguez dfs_region); 24858b60b078SLuis R. Rodriguez return false; 24868b60b078SLuis R. Rodriguez } 24878b60b078SLuis R. Rodriguez } 24888b60b078SLuis R. Rodriguez 2489a3d2eaf0SJohannes Berg static void print_regdomain(const struct ieee80211_regdomain *rd) 2490b2e1b302SLuis R. Rodriguez { 2491c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 2492b2e1b302SLuis R. Rodriguez 24933f2355cbSLuis R. Rodriguez if (is_intersected_alpha2(rd->alpha2)) { 2494c492db37SJohannes Berg if (lr->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE) { 249579c97e97SJohannes Berg struct cfg80211_registered_device *rdev; 2496c492db37SJohannes Berg rdev = cfg80211_rdev_by_wiphy_idx(lr->wiphy_idx); 249779c97e97SJohannes Berg if (rdev) { 2498e9c0268fSJoe Perches pr_info("Current regulatory domain updated by AP to: %c%c\n", 249979c97e97SJohannes Berg rdev->country_ie_alpha2[0], 250079c97e97SJohannes Berg rdev->country_ie_alpha2[1]); 25013f2355cbSLuis R. Rodriguez } else 2502e9c0268fSJoe Perches pr_info("Current regulatory domain intersected:\n"); 25033f2355cbSLuis R. Rodriguez } else 2504e9c0268fSJoe Perches pr_info("Current regulatory domain intersected:\n"); 25051a919318SJohannes Berg } else if (is_world_regdom(rd->alpha2)) { 2506e9c0268fSJoe Perches pr_info("World regulatory domain updated:\n"); 25071a919318SJohannes Berg } else { 2508b2e1b302SLuis R. Rodriguez if (is_unknown_alpha2(rd->alpha2)) 2509e9c0268fSJoe Perches pr_info("Regulatory domain changed to driver built-in settings (unknown country)\n"); 251057b5ce07SLuis R. Rodriguez else { 2511c492db37SJohannes Berg if (reg_request_cell_base(lr)) 25121a919318SJohannes Berg pr_info("Regulatory domain changed to country: %c%c by Cell Station\n", 2513b2e1b302SLuis R. Rodriguez rd->alpha2[0], rd->alpha2[1]); 251457b5ce07SLuis R. Rodriguez else 25151a919318SJohannes Berg pr_info("Regulatory domain changed to country: %c%c\n", 251657b5ce07SLuis R. Rodriguez rd->alpha2[0], rd->alpha2[1]); 251757b5ce07SLuis R. Rodriguez } 2518b2e1b302SLuis R. Rodriguez } 25191a919318SJohannes Berg 25203ef121b5SLuis R. Rodriguez pr_info(" DFS Master region: %s", reg_dfs_region_str(rd->dfs_region)); 2521b2e1b302SLuis R. Rodriguez print_rd_rules(rd); 2522b2e1b302SLuis R. Rodriguez } 2523b2e1b302SLuis R. Rodriguez 25242df78167SJohannes Berg static void print_regdomain_info(const struct ieee80211_regdomain *rd) 2525b2e1b302SLuis R. Rodriguez { 2526e9c0268fSJoe Perches pr_info("Regulatory domain: %c%c\n", rd->alpha2[0], rd->alpha2[1]); 2527b2e1b302SLuis R. Rodriguez print_rd_rules(rd); 2528b2e1b302SLuis R. Rodriguez } 2529b2e1b302SLuis R. Rodriguez 25303b9e5acaSLuis R. Rodriguez static int reg_set_rd_core(const struct ieee80211_regdomain *rd) 25313b9e5acaSLuis R. Rodriguez { 25323b9e5acaSLuis R. Rodriguez if (!is_world_regdom(rd->alpha2)) 25333b9e5acaSLuis R. Rodriguez return -EINVAL; 25343b9e5acaSLuis R. Rodriguez update_world_regdomain(rd); 25353b9e5acaSLuis R. Rodriguez return 0; 25363b9e5acaSLuis R. Rodriguez } 25373b9e5acaSLuis R. Rodriguez 253884721d44SLuis R. Rodriguez static int reg_set_rd_user(const struct ieee80211_regdomain *rd, 253984721d44SLuis R. Rodriguez struct regulatory_request *user_request) 254084721d44SLuis R. Rodriguez { 254184721d44SLuis R. Rodriguez const struct ieee80211_regdomain *intersected_rd = NULL; 254284721d44SLuis R. Rodriguez 254384721d44SLuis R. Rodriguez if (!regdom_changes(rd->alpha2)) 254484721d44SLuis R. Rodriguez return -EALREADY; 254584721d44SLuis R. Rodriguez 254684721d44SLuis R. Rodriguez if (!is_valid_rd(rd)) { 254784721d44SLuis R. Rodriguez pr_err("Invalid regulatory domain detected:\n"); 254884721d44SLuis R. Rodriguez print_regdomain_info(rd); 254984721d44SLuis R. Rodriguez return -EINVAL; 255084721d44SLuis R. Rodriguez } 255184721d44SLuis R. Rodriguez 255284721d44SLuis R. Rodriguez if (!user_request->intersect) { 255384721d44SLuis R. Rodriguez reset_regdomains(false, rd); 255484721d44SLuis R. Rodriguez return 0; 255584721d44SLuis R. Rodriguez } 255684721d44SLuis R. Rodriguez 255784721d44SLuis R. Rodriguez intersected_rd = regdom_intersect(rd, get_cfg80211_regdom()); 255884721d44SLuis R. Rodriguez if (!intersected_rd) 255984721d44SLuis R. Rodriguez return -EINVAL; 256084721d44SLuis R. Rodriguez 256184721d44SLuis R. Rodriguez kfree(rd); 256284721d44SLuis R. Rodriguez rd = NULL; 256384721d44SLuis R. Rodriguez reset_regdomains(false, intersected_rd); 256484721d44SLuis R. Rodriguez 256584721d44SLuis R. Rodriguez return 0; 256684721d44SLuis R. Rodriguez } 256784721d44SLuis R. Rodriguez 2568f5fe3247SLuis R. Rodriguez static int reg_set_rd_driver(const struct ieee80211_regdomain *rd, 2569f5fe3247SLuis R. Rodriguez struct regulatory_request *driver_request) 2570b2e1b302SLuis R. Rodriguez { 2571e9763c3cSJohannes Berg const struct ieee80211_regdomain *regd; 25729c96477dSLuis R. Rodriguez const struct ieee80211_regdomain *intersected_rd = NULL; 2573f5fe3247SLuis R. Rodriguez const struct ieee80211_regdomain *tmp; 2574806a9e39SLuis R. Rodriguez struct wiphy *request_wiphy; 25756913b49aSJohannes Berg 2576f5fe3247SLuis R. Rodriguez if (is_world_regdom(rd->alpha2)) 2577b2e1b302SLuis R. Rodriguez return -EINVAL; 2578b2e1b302SLuis R. Rodriguez 2579baeb66feSJohn W. Linville if (!regdom_changes(rd->alpha2)) 258095908535SKalle Valo return -EALREADY; 2581b2e1b302SLuis R. Rodriguez 2582b2e1b302SLuis R. Rodriguez if (!is_valid_rd(rd)) { 2583e9c0268fSJoe Perches pr_err("Invalid regulatory domain detected:\n"); 2584b2e1b302SLuis R. Rodriguez print_regdomain_info(rd); 2585b2e1b302SLuis R. Rodriguez return -EINVAL; 2586b2e1b302SLuis R. Rodriguez } 2587b2e1b302SLuis R. Rodriguez 2588f5fe3247SLuis R. Rodriguez request_wiphy = wiphy_idx_to_wiphy(driver_request->wiphy_idx); 2589f5fe3247SLuis R. Rodriguez if (!request_wiphy) { 2590845f3351SShaibal Dutta queue_delayed_work(system_power_efficient_wq, 2591845f3351SShaibal Dutta ®_timeout, 0); 2592de3584bdSJohannes Berg return -ENODEV; 2593de3584bdSJohannes Berg } 2594806a9e39SLuis R. Rodriguez 2595f5fe3247SLuis R. Rodriguez if (!driver_request->intersect) { 2596558f6d32SLuis R. Rodriguez if (request_wiphy->regd) 2597558f6d32SLuis R. Rodriguez return -EALREADY; 25983e0c3ff3SLuis R. Rodriguez 2599e9763c3cSJohannes Berg regd = reg_copy_regd(rd); 2600e9763c3cSJohannes Berg if (IS_ERR(regd)) 2601e9763c3cSJohannes Berg return PTR_ERR(regd); 26023e0c3ff3SLuis R. Rodriguez 2603458f4f9eSJohannes Berg rcu_assign_pointer(request_wiphy->regd, regd); 2604379b82f4SJohannes Berg reset_regdomains(false, rd); 2605b8295acdSLuis R. Rodriguez return 0; 2606b8295acdSLuis R. Rodriguez } 2607b8295acdSLuis R. Rodriguez 2608458f4f9eSJohannes Berg intersected_rd = regdom_intersect(rd, get_cfg80211_regdom()); 26099c96477dSLuis R. Rodriguez if (!intersected_rd) 26109c96477dSLuis R. Rodriguez return -EINVAL; 2611b8295acdSLuis R. Rodriguez 2612fb1fc7adSLuis R. Rodriguez /* 2613fb1fc7adSLuis R. Rodriguez * We can trash what CRDA provided now. 26143e0c3ff3SLuis R. Rodriguez * However if a driver requested this specific regulatory 2615fb1fc7adSLuis R. Rodriguez * domain we keep it for its private use 2616fb1fc7adSLuis R. Rodriguez */ 2617b7566fc3SLarry Finger tmp = get_wiphy_regdom(request_wiphy); 2618458f4f9eSJohannes Berg rcu_assign_pointer(request_wiphy->regd, rd); 2619b7566fc3SLarry Finger rcu_free_regdom(tmp); 26203e0c3ff3SLuis R. Rodriguez 2621b8295acdSLuis R. Rodriguez rd = NULL; 2622b8295acdSLuis R. Rodriguez 2623379b82f4SJohannes Berg reset_regdomains(false, intersected_rd); 2624b8295acdSLuis R. Rodriguez 2625b8295acdSLuis R. Rodriguez return 0; 26269c96477dSLuis R. Rodriguez } 26279c96477dSLuis R. Rodriguez 262801992406SLuis R. Rodriguez static int reg_set_rd_country_ie(const struct ieee80211_regdomain *rd, 262901992406SLuis R. Rodriguez struct regulatory_request *country_ie_request) 2630f5fe3247SLuis R. Rodriguez { 2631f5fe3247SLuis R. Rodriguez struct wiphy *request_wiphy; 2632f5fe3247SLuis R. Rodriguez 2633f5fe3247SLuis R. Rodriguez if (!is_alpha2_set(rd->alpha2) && !is_an_alpha2(rd->alpha2) && 2634f5fe3247SLuis R. Rodriguez !is_unknown_alpha2(rd->alpha2)) 2635f5fe3247SLuis R. Rodriguez return -EINVAL; 2636f5fe3247SLuis R. Rodriguez 2637f5fe3247SLuis R. Rodriguez /* 2638f5fe3247SLuis R. Rodriguez * Lets only bother proceeding on the same alpha2 if the current 2639f5fe3247SLuis R. Rodriguez * rd is non static (it means CRDA was present and was used last) 2640f5fe3247SLuis R. Rodriguez * and the pending request came in from a country IE 2641f5fe3247SLuis R. Rodriguez */ 2642f5fe3247SLuis R. Rodriguez 2643f5fe3247SLuis R. Rodriguez if (!is_valid_rd(rd)) { 2644f5fe3247SLuis R. Rodriguez pr_err("Invalid regulatory domain detected:\n"); 2645f5fe3247SLuis R. Rodriguez print_regdomain_info(rd); 26463f2355cbSLuis R. Rodriguez return -EINVAL; 2647b2e1b302SLuis R. Rodriguez } 2648b2e1b302SLuis R. Rodriguez 264901992406SLuis R. Rodriguez request_wiphy = wiphy_idx_to_wiphy(country_ie_request->wiphy_idx); 2650f5fe3247SLuis R. Rodriguez if (!request_wiphy) { 2651845f3351SShaibal Dutta queue_delayed_work(system_power_efficient_wq, 2652845f3351SShaibal Dutta ®_timeout, 0); 2653f5fe3247SLuis R. Rodriguez return -ENODEV; 2654f5fe3247SLuis R. Rodriguez } 2655f5fe3247SLuis R. Rodriguez 265601992406SLuis R. Rodriguez if (country_ie_request->intersect) 2657f5fe3247SLuis R. Rodriguez return -EINVAL; 2658f5fe3247SLuis R. Rodriguez 2659f5fe3247SLuis R. Rodriguez reset_regdomains(false, rd); 2660f5fe3247SLuis R. Rodriguez return 0; 2661f5fe3247SLuis R. Rodriguez } 2662b2e1b302SLuis R. Rodriguez 2663fb1fc7adSLuis R. Rodriguez /* 2664fb1fc7adSLuis R. Rodriguez * Use this call to set the current regulatory domain. Conflicts with 2665b2e1b302SLuis R. Rodriguez * multiple drivers can be ironed out later. Caller must've already 2666458f4f9eSJohannes Berg * kmalloc'd the rd structure. 2667fb1fc7adSLuis R. Rodriguez */ 2668a3d2eaf0SJohannes Berg int set_regdom(const struct ieee80211_regdomain *rd) 2669b2e1b302SLuis R. Rodriguez { 2670c492db37SJohannes Berg struct regulatory_request *lr; 2671092008abSJanusz Dziedzic bool user_reset = false; 2672b2e1b302SLuis R. Rodriguez int r; 2673b2e1b302SLuis R. Rodriguez 26743b9e5acaSLuis R. Rodriguez if (!reg_is_valid_request(rd->alpha2)) { 26753b9e5acaSLuis R. Rodriguez kfree(rd); 26763b9e5acaSLuis R. Rodriguez return -EINVAL; 26773b9e5acaSLuis R. Rodriguez } 26783b9e5acaSLuis R. Rodriguez 2679c492db37SJohannes Berg lr = get_last_request(); 2680abc7381bSLuis R. Rodriguez 2681b2e1b302SLuis R. Rodriguez /* Note that this doesn't update the wiphys, this is done below */ 26823b9e5acaSLuis R. Rodriguez switch (lr->initiator) { 26833b9e5acaSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_CORE: 26843b9e5acaSLuis R. Rodriguez r = reg_set_rd_core(rd); 26853b9e5acaSLuis R. Rodriguez break; 26863b9e5acaSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_USER: 268784721d44SLuis R. Rodriguez r = reg_set_rd_user(rd, lr); 2688092008abSJanusz Dziedzic user_reset = true; 268984721d44SLuis R. Rodriguez break; 26903b9e5acaSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_DRIVER: 2691f5fe3247SLuis R. Rodriguez r = reg_set_rd_driver(rd, lr); 2692f5fe3247SLuis R. Rodriguez break; 26933b9e5acaSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_COUNTRY_IE: 269401992406SLuis R. Rodriguez r = reg_set_rd_country_ie(rd, lr); 26953b9e5acaSLuis R. Rodriguez break; 26963b9e5acaSLuis R. Rodriguez default: 26973b9e5acaSLuis R. Rodriguez WARN(1, "invalid initiator %d\n", lr->initiator); 26983b9e5acaSLuis R. Rodriguez return -EINVAL; 26993b9e5acaSLuis R. Rodriguez } 27003b9e5acaSLuis R. Rodriguez 2701d2372b31SJohannes Berg if (r) { 2702092008abSJanusz Dziedzic switch (r) { 2703092008abSJanusz Dziedzic case -EALREADY: 270495908535SKalle Valo reg_set_request_processed(); 2705092008abSJanusz Dziedzic break; 2706092008abSJanusz Dziedzic default: 2707092008abSJanusz Dziedzic /* Back to world regulatory in case of errors */ 2708092008abSJanusz Dziedzic restore_regulatory_settings(user_reset); 2709092008abSJanusz Dziedzic } 271095908535SKalle Valo 2711d2372b31SJohannes Berg kfree(rd); 271238fd2143SJohannes Berg return r; 2713d2372b31SJohannes Berg } 2714b2e1b302SLuis R. Rodriguez 2715b2e1b302SLuis R. Rodriguez /* This would make this whole thing pointless */ 271638fd2143SJohannes Berg if (WARN_ON(!lr->intersect && rd != get_cfg80211_regdom())) 271738fd2143SJohannes Berg return -EINVAL; 2718b2e1b302SLuis R. Rodriguez 2719b2e1b302SLuis R. Rodriguez /* update all wiphys now with the new established regulatory domain */ 2720c492db37SJohannes Berg update_all_wiphy_regulatory(lr->initiator); 2721b2e1b302SLuis R. Rodriguez 2722458f4f9eSJohannes Berg print_regdomain(get_cfg80211_regdom()); 2723b2e1b302SLuis R. Rodriguez 2724c492db37SJohannes Berg nl80211_send_reg_change_event(lr); 272573d54c9eSLuis R. Rodriguez 2726b2e253cfSLuis R. Rodriguez reg_set_request_processed(); 2727b2e253cfSLuis R. Rodriguez 272838fd2143SJohannes Berg return 0; 2729b2e1b302SLuis R. Rodriguez } 2730b2e1b302SLuis R. Rodriguez 273157b5ce07SLuis R. Rodriguez void wiphy_regulatory_register(struct wiphy *wiphy) 273257b5ce07SLuis R. Rodriguez { 273323df0b73SArik Nemtsov struct regulatory_request *lr; 273423df0b73SArik Nemtsov 273557b5ce07SLuis R. Rodriguez if (!reg_dev_ignore_cell_hint(wiphy)) 273657b5ce07SLuis R. Rodriguez reg_num_devs_support_basehint++; 273757b5ce07SLuis R. Rodriguez 273823df0b73SArik Nemtsov lr = get_last_request(); 273923df0b73SArik Nemtsov wiphy_update_regulatory(wiphy, lr->initiator); 274057b5ce07SLuis R. Rodriguez } 274157b5ce07SLuis R. Rodriguez 2742bfead080SLuis R. Rodriguez void wiphy_regulatory_deregister(struct wiphy *wiphy) 27433f2355cbSLuis R. Rodriguez { 27440ad8acafSLuis R. Rodriguez struct wiphy *request_wiphy = NULL; 2745c492db37SJohannes Berg struct regulatory_request *lr; 2746761cf7ecSLuis R. Rodriguez 2747c492db37SJohannes Berg lr = get_last_request(); 2748abc7381bSLuis R. Rodriguez 274957b5ce07SLuis R. Rodriguez if (!reg_dev_ignore_cell_hint(wiphy)) 275057b5ce07SLuis R. Rodriguez reg_num_devs_support_basehint--; 275157b5ce07SLuis R. Rodriguez 2752458f4f9eSJohannes Berg rcu_free_regdom(get_wiphy_regdom(wiphy)); 275334dd886cSMonam Agarwal RCU_INIT_POINTER(wiphy->regd, NULL); 27540ef9ccddSChris Wright 2755c492db37SJohannes Berg if (lr) 2756c492db37SJohannes Berg request_wiphy = wiphy_idx_to_wiphy(lr->wiphy_idx); 2757806a9e39SLuis R. Rodriguez 27580ef9ccddSChris Wright if (!request_wiphy || request_wiphy != wiphy) 275938fd2143SJohannes Berg return; 27600ef9ccddSChris Wright 2761c492db37SJohannes Berg lr->wiphy_idx = WIPHY_IDX_INVALID; 2762c492db37SJohannes Berg lr->country_ie_env = ENVIRON_ANY; 27633f2355cbSLuis R. Rodriguez } 27643f2355cbSLuis R. Rodriguez 2765a90c7a31SLuis R. Rodriguez static void reg_timeout_work(struct work_struct *work) 2766a90c7a31SLuis R. Rodriguez { 27671a919318SJohannes Berg REG_DBG_PRINT("Timeout while waiting for CRDA to reply, restoring regulatory settings\n"); 2768f77b86d7SJohannes Berg rtnl_lock(); 2769a90c7a31SLuis R. Rodriguez restore_regulatory_settings(true); 2770f77b86d7SJohannes Berg rtnl_unlock(); 2771a90c7a31SLuis R. Rodriguez } 2772a90c7a31SLuis R. Rodriguez 2773174e0cd2SIlan Peer /* 2774174e0cd2SIlan Peer * See http://www.fcc.gov/document/5-ghz-unlicensed-spectrum-unii, for 2775174e0cd2SIlan Peer * UNII band definitions 2776174e0cd2SIlan Peer */ 2777174e0cd2SIlan Peer int cfg80211_get_unii(int freq) 2778174e0cd2SIlan Peer { 2779174e0cd2SIlan Peer /* UNII-1 */ 2780174e0cd2SIlan Peer if (freq >= 5150 && freq <= 5250) 2781174e0cd2SIlan Peer return 0; 2782174e0cd2SIlan Peer 2783174e0cd2SIlan Peer /* UNII-2A */ 2784174e0cd2SIlan Peer if (freq > 5250 && freq <= 5350) 2785174e0cd2SIlan Peer return 1; 2786174e0cd2SIlan Peer 2787174e0cd2SIlan Peer /* UNII-2B */ 2788174e0cd2SIlan Peer if (freq > 5350 && freq <= 5470) 2789174e0cd2SIlan Peer return 2; 2790174e0cd2SIlan Peer 2791174e0cd2SIlan Peer /* UNII-2C */ 2792174e0cd2SIlan Peer if (freq > 5470 && freq <= 5725) 2793174e0cd2SIlan Peer return 3; 2794174e0cd2SIlan Peer 2795174e0cd2SIlan Peer /* UNII-3 */ 2796174e0cd2SIlan Peer if (freq > 5725 && freq <= 5825) 2797174e0cd2SIlan Peer return 4; 2798174e0cd2SIlan Peer 2799174e0cd2SIlan Peer return -EINVAL; 2800174e0cd2SIlan Peer } 2801174e0cd2SIlan Peer 2802c8866e55SIlan Peer bool regulatory_indoor_allowed(void) 2803c8866e55SIlan Peer { 2804c8866e55SIlan Peer return reg_is_indoor; 2805c8866e55SIlan Peer } 2806c8866e55SIlan Peer 28072fcc9f73SUwe Kleine-König int __init regulatory_init(void) 2808b2e1b302SLuis R. Rodriguez { 2809bcf4f99bSLuis R. Rodriguez int err = 0; 2810734366deSJohannes Berg 2811b2e1b302SLuis R. Rodriguez reg_pdev = platform_device_register_simple("regulatory", 0, NULL, 0); 2812b2e1b302SLuis R. Rodriguez if (IS_ERR(reg_pdev)) 2813b2e1b302SLuis R. Rodriguez return PTR_ERR(reg_pdev); 2814734366deSJohannes Berg 2815fe33eb39SLuis R. Rodriguez spin_lock_init(®_requests_lock); 2816e38f8a7aSLuis R. Rodriguez spin_lock_init(®_pending_beacons_lock); 2817fe33eb39SLuis R. Rodriguez 281880007efeSLuis R. Rodriguez reg_regdb_size_check(); 281980007efeSLuis R. Rodriguez 2820458f4f9eSJohannes Berg rcu_assign_pointer(cfg80211_regdomain, cfg80211_world_regdom); 2821734366deSJohannes Berg 282209d989d1SLuis R. Rodriguez user_alpha2[0] = '9'; 282309d989d1SLuis R. Rodriguez user_alpha2[1] = '7'; 282409d989d1SLuis R. Rodriguez 2825ae9e4b0dSLuis R. Rodriguez /* We always try to get an update for the static regdomain */ 2826458f4f9eSJohannes Berg err = regulatory_hint_core(cfg80211_world_regdom->alpha2); 2827bcf4f99bSLuis R. Rodriguez if (err) { 2828bcf4f99bSLuis R. Rodriguez if (err == -ENOMEM) 2829bcf4f99bSLuis R. Rodriguez return err; 2830bcf4f99bSLuis R. Rodriguez /* 2831bcf4f99bSLuis R. Rodriguez * N.B. kobject_uevent_env() can fail mainly for when we're out 2832bcf4f99bSLuis R. Rodriguez * memory which is handled and propagated appropriately above 2833bcf4f99bSLuis R. Rodriguez * but it can also fail during a netlink_broadcast() or during 2834bcf4f99bSLuis R. Rodriguez * early boot for call_usermodehelper(). For now treat these 2835bcf4f99bSLuis R. Rodriguez * errors as non-fatal. 2836bcf4f99bSLuis R. Rodriguez */ 2837e9c0268fSJoe Perches pr_err("kobject_uevent_env() was unable to call CRDA during init\n"); 2838bcf4f99bSLuis R. Rodriguez } 2839734366deSJohannes Berg 2840ae9e4b0dSLuis R. Rodriguez /* 2841ae9e4b0dSLuis R. Rodriguez * Finally, if the user set the module parameter treat it 2842ae9e4b0dSLuis R. Rodriguez * as a user hint. 2843ae9e4b0dSLuis R. Rodriguez */ 2844ae9e4b0dSLuis R. Rodriguez if (!is_world_regdom(ieee80211_regdom)) 284557b5ce07SLuis R. Rodriguez regulatory_hint_user(ieee80211_regdom, 284657b5ce07SLuis R. Rodriguez NL80211_USER_REG_HINT_USER); 2847ae9e4b0dSLuis R. Rodriguez 2848b2e1b302SLuis R. Rodriguez return 0; 2849b2e1b302SLuis R. Rodriguez } 2850b2e1b302SLuis R. Rodriguez 28511a919318SJohannes Berg void regulatory_exit(void) 2852b2e1b302SLuis R. Rodriguez { 2853fe33eb39SLuis R. Rodriguez struct regulatory_request *reg_request, *tmp; 2854e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon, *btmp; 2855fe33eb39SLuis R. Rodriguez 2856fe33eb39SLuis R. Rodriguez cancel_work_sync(®_work); 2857a90c7a31SLuis R. Rodriguez cancel_delayed_work_sync(®_timeout); 2858fe33eb39SLuis R. Rodriguez 28599027b149SJohannes Berg /* Lock to suppress warnings */ 286038fd2143SJohannes Berg rtnl_lock(); 2861379b82f4SJohannes Berg reset_regdomains(true, NULL); 286238fd2143SJohannes Berg rtnl_unlock(); 2863734366deSJohannes Berg 286458ebacc6SLuis R. Rodriguez dev_set_uevent_suppress(®_pdev->dev, true); 2865f6037d09SJohannes Berg 2866b2e1b302SLuis R. Rodriguez platform_device_unregister(reg_pdev); 2867734366deSJohannes Berg 2868fea9bcedSJohannes Berg list_for_each_entry_safe(reg_beacon, btmp, ®_pending_beacons, list) { 2869e38f8a7aSLuis R. Rodriguez list_del(®_beacon->list); 2870e38f8a7aSLuis R. Rodriguez kfree(reg_beacon); 2871e38f8a7aSLuis R. Rodriguez } 2872e38f8a7aSLuis R. Rodriguez 2873fea9bcedSJohannes Berg list_for_each_entry_safe(reg_beacon, btmp, ®_beacon_list, list) { 2874e38f8a7aSLuis R. Rodriguez list_del(®_beacon->list); 2875e38f8a7aSLuis R. Rodriguez kfree(reg_beacon); 2876e38f8a7aSLuis R. Rodriguez } 2877e38f8a7aSLuis R. Rodriguez 2878fea9bcedSJohannes Berg list_for_each_entry_safe(reg_request, tmp, ®_requests_list, list) { 2879fe33eb39SLuis R. Rodriguez list_del(®_request->list); 2880fe33eb39SLuis R. Rodriguez kfree(reg_request); 2881fe33eb39SLuis R. Rodriguez } 2882fe33eb39SLuis R. Rodriguez } 2883