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" 59ad932f04SArik Nemtsov #include "rdev-ops.h" 603b377ea9SJohn W. Linville #include "regdb.h" 6173d54c9eSLuis R. Rodriguez #include "nl80211.h" 628318d78aSJohannes Berg 63ad932f04SArik Nemtsov /* 64ad932f04SArik Nemtsov * Grace period we give before making sure all current interfaces reside on 65ad932f04SArik Nemtsov * channels allowed by the current regulatory domain. 66ad932f04SArik Nemtsov */ 67ad932f04SArik Nemtsov #define REG_ENFORCE_GRACE_MS 60000 68ad932f04SArik Nemtsov 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 */ 792f92212bSJohannes Berg enum reg_request_treatment { 802f92212bSJohannes Berg REG_REQ_OK, 812f92212bSJohannes Berg REG_REQ_IGNORE, 822f92212bSJohannes Berg REG_REQ_INTERSECT, 832f92212bSJohannes Berg REG_REQ_ALREADY_SET, 842f92212bSJohannes Berg }; 852f92212bSJohannes Berg 86a042994dSLuis R. Rodriguez static struct regulatory_request core_request_world = { 87a042994dSLuis R. Rodriguez .initiator = NL80211_REGDOM_SET_BY_CORE, 88a042994dSLuis R. Rodriguez .alpha2[0] = '0', 89a042994dSLuis R. Rodriguez .alpha2[1] = '0', 90a042994dSLuis R. Rodriguez .intersect = false, 91a042994dSLuis R. Rodriguez .processed = true, 92a042994dSLuis R. Rodriguez .country_ie_env = ENVIRON_ANY, 93a042994dSLuis R. Rodriguez }; 94a042994dSLuis R. Rodriguez 9538fd2143SJohannes Berg /* 9638fd2143SJohannes Berg * Receipt of information from last regulatory request, 9738fd2143SJohannes Berg * protected by RTNL (and can be accessed with RCU protection) 9838fd2143SJohannes Berg */ 99c492db37SJohannes Berg static struct regulatory_request __rcu *last_request = 100cec3f0edSJohannes Berg (void __force __rcu *)&core_request_world; 101734366deSJohannes Berg 102b2e1b302SLuis R. Rodriguez /* To trigger userspace events */ 103b2e1b302SLuis R. Rodriguez static struct platform_device *reg_pdev; 1048318d78aSJohannes Berg 105fb1fc7adSLuis R. Rodriguez /* 106fb1fc7adSLuis R. Rodriguez * Central wireless core regulatory domains, we only need two, 107734366deSJohannes Berg * the current one and a world regulatory domain in case we have no 108e8da2bb4SJohannes Berg * information to give us an alpha2. 10938fd2143SJohannes Berg * (protected by RTNL, can be read under RCU) 110fb1fc7adSLuis R. Rodriguez */ 111458f4f9eSJohannes Berg const struct ieee80211_regdomain __rcu *cfg80211_regdomain; 112734366deSJohannes Berg 113fb1fc7adSLuis R. Rodriguez /* 11457b5ce07SLuis R. Rodriguez * Number of devices that registered to the core 11557b5ce07SLuis R. Rodriguez * that support cellular base station regulatory hints 11638fd2143SJohannes Berg * (protected by RTNL) 11757b5ce07SLuis R. Rodriguez */ 11857b5ce07SLuis R. Rodriguez static int reg_num_devs_support_basehint; 11957b5ce07SLuis R. Rodriguez 12052616f2bSIlan Peer /* 12152616f2bSIlan Peer * State variable indicating if the platform on which the devices 12252616f2bSIlan Peer * are attached is operating in an indoor environment. The state variable 12352616f2bSIlan Peer * is relevant for all registered devices. 12452616f2bSIlan Peer */ 12552616f2bSIlan Peer static bool reg_is_indoor; 12605050753SIlan peer static spinlock_t reg_indoor_lock; 12705050753SIlan peer 12805050753SIlan peer /* Used to track the userspace process controlling the indoor setting */ 12905050753SIlan peer static u32 reg_is_indoor_portid; 13052616f2bSIlan Peer 131b6863036SJohannes Berg static void restore_regulatory_settings(bool reset_user); 132c37722bdSIlan 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 138ad30ca2cSArik Nemtsov 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 174c799ba6eSJohannes Berg pr_debug("%s: device specific dfs_region (%s) disagrees with cfg80211's central dfs_region (%s)\n", 1756c474799SLuis R. Rodriguez dev_name(&wiphy->dev), 1766c474799SLuis R. Rodriguez reg_dfs_region_str(wiphy_regd->dfs_region), 1776c474799SLuis R. Rodriguez reg_dfs_region_str(regd->dfs_region)); 1786c474799SLuis R. Rodriguez 1796c474799SLuis R. Rodriguez out: 1806c474799SLuis R. Rodriguez return regd->dfs_region; 1816c474799SLuis R. Rodriguez } 1826c474799SLuis R. Rodriguez 183458f4f9eSJohannes Berg static void rcu_free_regdom(const struct ieee80211_regdomain *r) 184458f4f9eSJohannes Berg { 185458f4f9eSJohannes Berg if (!r) 186458f4f9eSJohannes Berg return; 187458f4f9eSJohannes Berg kfree_rcu((struct ieee80211_regdomain *)r, rcu_head); 188458f4f9eSJohannes Berg } 189458f4f9eSJohannes Berg 190c492db37SJohannes Berg static struct regulatory_request *get_last_request(void) 191c492db37SJohannes Berg { 19238fd2143SJohannes Berg return rcu_dereference_rtnl(last_request); 193c492db37SJohannes Berg } 194c492db37SJohannes Berg 195e38f8a7aSLuis R. Rodriguez /* Used to queue up regulatory hints */ 196fe33eb39SLuis R. Rodriguez static LIST_HEAD(reg_requests_list); 197fe33eb39SLuis R. Rodriguez static spinlock_t reg_requests_lock; 198fe33eb39SLuis R. Rodriguez 199e38f8a7aSLuis R. Rodriguez /* Used to queue up beacon hints for review */ 200e38f8a7aSLuis R. Rodriguez static LIST_HEAD(reg_pending_beacons); 201e38f8a7aSLuis R. Rodriguez static spinlock_t reg_pending_beacons_lock; 202e38f8a7aSLuis R. Rodriguez 203e38f8a7aSLuis R. Rodriguez /* Used to keep track of processed beacon hints */ 204e38f8a7aSLuis R. Rodriguez static LIST_HEAD(reg_beacon_list); 205e38f8a7aSLuis R. Rodriguez 206e38f8a7aSLuis R. Rodriguez struct reg_beacon { 207e38f8a7aSLuis R. Rodriguez struct list_head list; 208e38f8a7aSLuis R. Rodriguez struct ieee80211_channel chan; 209e38f8a7aSLuis R. Rodriguez }; 210e38f8a7aSLuis R. Rodriguez 211ad932f04SArik Nemtsov static void reg_check_chans_work(struct work_struct *work); 212ad932f04SArik Nemtsov static DECLARE_DELAYED_WORK(reg_check_chans, reg_check_chans_work); 213ad932f04SArik Nemtsov 214f333a7a2SLuis R. Rodriguez static void reg_todo(struct work_struct *work); 215f333a7a2SLuis R. Rodriguez static DECLARE_WORK(reg_work, reg_todo); 216f333a7a2SLuis R. Rodriguez 217734366deSJohannes Berg /* We keep a static world regulatory domain in case of the absence of CRDA */ 218734366deSJohannes Berg static const struct ieee80211_regdomain world_regdom = { 21928981e5eSJason Abele .n_reg_rules = 8, 220734366deSJohannes Berg .alpha2 = "00", 221734366deSJohannes Berg .reg_rules = { 22268798a62SLuis R. Rodriguez /* IEEE 802.11b/g, channels 1..11 */ 22368798a62SLuis R. Rodriguez REG_RULE(2412-10, 2462+10, 40, 6, 20, 0), 22443c771a1SJohannes Berg /* IEEE 802.11b/g, channels 12..13. */ 225c3826807SJohannes Berg REG_RULE(2467-10, 2472+10, 20, 6, 20, 226c3826807SJohannes Berg NL80211_RRF_NO_IR | NL80211_RRF_AUTO_BW), 227611b6a82SLuis R. Rodriguez /* IEEE 802.11 channel 14 - Only JP enables 228611b6a82SLuis R. Rodriguez * this and for 802.11b only */ 229611b6a82SLuis R. Rodriguez REG_RULE(2484-10, 2484+10, 20, 6, 20, 2308fe02e16SLuis R. Rodriguez NL80211_RRF_NO_IR | 231611b6a82SLuis R. Rodriguez NL80211_RRF_NO_OFDM), 2323fc71f77SLuis R. Rodriguez /* IEEE 802.11a, channel 36..48 */ 233c3826807SJohannes Berg REG_RULE(5180-10, 5240+10, 80, 6, 20, 234c3826807SJohannes Berg NL80211_RRF_NO_IR | 235c3826807SJohannes Berg NL80211_RRF_AUTO_BW), 2363fc71f77SLuis R. Rodriguez 237131a19bcSJohannes Berg /* IEEE 802.11a, channel 52..64 - DFS required */ 238c3826807SJohannes Berg REG_RULE(5260-10, 5320+10, 80, 6, 20, 2398fe02e16SLuis R. Rodriguez NL80211_RRF_NO_IR | 240c3826807SJohannes Berg NL80211_RRF_AUTO_BW | 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 2528047d261SJohannes Berg /* 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 { 269d34265a3SJohannes Berg if (request == &core_request_world) 270d34265a3SJohannes Berg return; 271d34265a3SJohannes Berg 272c888393bSArik Nemtsov if (request != get_last_request()) 273c888393bSArik Nemtsov kfree(request); 274c888393bSArik Nemtsov } 275c888393bSArik Nemtsov 276c888393bSArik Nemtsov static void reg_free_last_request(void) 277c888393bSArik Nemtsov { 278c888393bSArik Nemtsov struct regulatory_request *lr = get_last_request(); 279c888393bSArik Nemtsov 2805ad6ef5eSLuis R. Rodriguez if (lr != &core_request_world && lr) 2815ad6ef5eSLuis R. Rodriguez kfree_rcu(lr, rcu_head); 2825ad6ef5eSLuis R. Rodriguez } 2835ad6ef5eSLuis R. Rodriguez 28405f1a3eaSLuis R. Rodriguez static void reg_update_last_request(struct regulatory_request *request) 28505f1a3eaSLuis R. Rodriguez { 286255e25b0SLuis R. Rodriguez struct regulatory_request *lr; 287255e25b0SLuis R. Rodriguez 288255e25b0SLuis R. Rodriguez lr = get_last_request(); 289255e25b0SLuis R. Rodriguez if (lr == request) 290255e25b0SLuis R. Rodriguez return; 291255e25b0SLuis R. Rodriguez 292c888393bSArik Nemtsov reg_free_last_request(); 29305f1a3eaSLuis R. Rodriguez rcu_assign_pointer(last_request, request); 29405f1a3eaSLuis R. Rodriguez } 29505f1a3eaSLuis R. Rodriguez 296379b82f4SJohannes Berg static void reset_regdomains(bool full_reset, 297379b82f4SJohannes Berg const struct ieee80211_regdomain *new_regdom) 298734366deSJohannes Berg { 299458f4f9eSJohannes Berg const struct ieee80211_regdomain *r; 300458f4f9eSJohannes Berg 30138fd2143SJohannes Berg ASSERT_RTNL(); 302e8da2bb4SJohannes Berg 303458f4f9eSJohannes Berg r = get_cfg80211_regdom(); 304458f4f9eSJohannes Berg 305942b25cfSJohannes Berg /* avoid freeing static information or freeing something twice */ 306458f4f9eSJohannes Berg if (r == cfg80211_world_regdom) 307458f4f9eSJohannes Berg r = NULL; 308942b25cfSJohannes Berg if (cfg80211_world_regdom == &world_regdom) 309942b25cfSJohannes Berg cfg80211_world_regdom = NULL; 310458f4f9eSJohannes Berg if (r == &world_regdom) 311458f4f9eSJohannes Berg r = NULL; 312942b25cfSJohannes Berg 313458f4f9eSJohannes Berg rcu_free_regdom(r); 314458f4f9eSJohannes Berg rcu_free_regdom(cfg80211_world_regdom); 315734366deSJohannes Berg 316a3d2eaf0SJohannes Berg cfg80211_world_regdom = &world_regdom; 317458f4f9eSJohannes Berg rcu_assign_pointer(cfg80211_regdomain, new_regdom); 318a042994dSLuis R. Rodriguez 319a042994dSLuis R. Rodriguez if (!full_reset) 320a042994dSLuis R. Rodriguez return; 321a042994dSLuis R. Rodriguez 32205f1a3eaSLuis R. Rodriguez reg_update_last_request(&core_request_world); 323734366deSJohannes Berg } 324734366deSJohannes Berg 325fb1fc7adSLuis R. Rodriguez /* 326fb1fc7adSLuis R. Rodriguez * Dynamic world regulatory domain requested by the wireless 327fb1fc7adSLuis R. Rodriguez * core upon initialization 328fb1fc7adSLuis R. Rodriguez */ 329a3d2eaf0SJohannes Berg static void update_world_regdomain(const struct ieee80211_regdomain *rd) 330734366deSJohannes Berg { 331c492db37SJohannes Berg struct regulatory_request *lr; 332734366deSJohannes Berg 333c492db37SJohannes Berg lr = get_last_request(); 334c492db37SJohannes Berg 335c492db37SJohannes Berg WARN_ON(!lr); 336e8da2bb4SJohannes Berg 337379b82f4SJohannes Berg reset_regdomains(false, rd); 338734366deSJohannes Berg 339734366deSJohannes Berg cfg80211_world_regdom = rd; 340734366deSJohannes Berg } 341734366deSJohannes Berg 342a3d2eaf0SJohannes Berg bool is_world_regdom(const char *alpha2) 343b2e1b302SLuis R. Rodriguez { 344b2e1b302SLuis R. Rodriguez if (!alpha2) 345b2e1b302SLuis R. Rodriguez return false; 3461a919318SJohannes Berg return alpha2[0] == '0' && alpha2[1] == '0'; 347b2e1b302SLuis R. Rodriguez } 348b2e1b302SLuis R. Rodriguez 349a3d2eaf0SJohannes Berg static bool is_alpha2_set(const char *alpha2) 350b2e1b302SLuis R. Rodriguez { 351b2e1b302SLuis R. Rodriguez if (!alpha2) 352b2e1b302SLuis R. Rodriguez return false; 3531a919318SJohannes Berg return alpha2[0] && alpha2[1]; 354b2e1b302SLuis R. Rodriguez } 355b2e1b302SLuis R. Rodriguez 356a3d2eaf0SJohannes Berg static bool is_unknown_alpha2(const char *alpha2) 357b2e1b302SLuis R. Rodriguez { 358b2e1b302SLuis R. Rodriguez if (!alpha2) 359b2e1b302SLuis R. Rodriguez return false; 360fb1fc7adSLuis R. Rodriguez /* 361fb1fc7adSLuis R. Rodriguez * Special case where regulatory domain was built by driver 362fb1fc7adSLuis R. Rodriguez * but a specific alpha2 cannot be determined 363fb1fc7adSLuis R. Rodriguez */ 3641a919318SJohannes Berg return alpha2[0] == '9' && alpha2[1] == '9'; 365b2e1b302SLuis R. Rodriguez } 366b2e1b302SLuis R. Rodriguez 3673f2355cbSLuis R. Rodriguez static bool is_intersected_alpha2(const char *alpha2) 3683f2355cbSLuis R. Rodriguez { 3693f2355cbSLuis R. Rodriguez if (!alpha2) 3703f2355cbSLuis R. Rodriguez return false; 371fb1fc7adSLuis R. Rodriguez /* 372fb1fc7adSLuis R. Rodriguez * Special case where regulatory domain is the 3733f2355cbSLuis R. Rodriguez * result of an intersection between two regulatory domain 374fb1fc7adSLuis R. Rodriguez * structures 375fb1fc7adSLuis R. Rodriguez */ 3761a919318SJohannes Berg return alpha2[0] == '9' && alpha2[1] == '8'; 3773f2355cbSLuis R. Rodriguez } 3783f2355cbSLuis R. Rodriguez 379a3d2eaf0SJohannes Berg static bool is_an_alpha2(const char *alpha2) 380b2e1b302SLuis R. Rodriguez { 381b2e1b302SLuis R. Rodriguez if (!alpha2) 382b2e1b302SLuis R. Rodriguez return false; 3831a919318SJohannes Berg return isalpha(alpha2[0]) && isalpha(alpha2[1]); 384b2e1b302SLuis R. Rodriguez } 385b2e1b302SLuis R. Rodriguez 386a3d2eaf0SJohannes Berg static bool alpha2_equal(const char *alpha2_x, const char *alpha2_y) 387b2e1b302SLuis R. Rodriguez { 388b2e1b302SLuis R. Rodriguez if (!alpha2_x || !alpha2_y) 389b2e1b302SLuis R. Rodriguez return false; 3901a919318SJohannes Berg return alpha2_x[0] == alpha2_y[0] && alpha2_x[1] == alpha2_y[1]; 391b2e1b302SLuis R. Rodriguez } 392b2e1b302SLuis R. Rodriguez 39369b1572bSLuis R. Rodriguez static bool regdom_changes(const char *alpha2) 394b2e1b302SLuis R. Rodriguez { 395458f4f9eSJohannes Berg const struct ieee80211_regdomain *r = get_cfg80211_regdom(); 396761cf7ecSLuis R. Rodriguez 397458f4f9eSJohannes Berg if (!r) 398b2e1b302SLuis R. Rodriguez return true; 399458f4f9eSJohannes Berg return !alpha2_equal(r->alpha2, alpha2); 400b2e1b302SLuis R. Rodriguez } 401b2e1b302SLuis R. Rodriguez 40209d989d1SLuis R. Rodriguez /* 40309d989d1SLuis R. Rodriguez * The NL80211_REGDOM_SET_BY_USER regdom alpha2 is cached, this lets 40409d989d1SLuis R. Rodriguez * you know if a valid regulatory hint with NL80211_REGDOM_SET_BY_USER 40509d989d1SLuis R. Rodriguez * has ever been issued. 40609d989d1SLuis R. Rodriguez */ 40709d989d1SLuis R. Rodriguez static bool is_user_regdom_saved(void) 40809d989d1SLuis R. Rodriguez { 40909d989d1SLuis R. Rodriguez if (user_alpha2[0] == '9' && user_alpha2[1] == '7') 41009d989d1SLuis R. Rodriguez return false; 41109d989d1SLuis R. Rodriguez 41209d989d1SLuis R. Rodriguez /* This would indicate a mistake on the design */ 4131a919318SJohannes Berg if (WARN(!is_world_regdom(user_alpha2) && !is_an_alpha2(user_alpha2), 41409d989d1SLuis R. Rodriguez "Unexpected user alpha2: %c%c\n", 4151a919318SJohannes Berg user_alpha2[0], user_alpha2[1])) 41609d989d1SLuis R. Rodriguez return false; 41709d989d1SLuis R. Rodriguez 41809d989d1SLuis R. Rodriguez return true; 41909d989d1SLuis R. Rodriguez } 42009d989d1SLuis R. Rodriguez 421e9763c3cSJohannes Berg static const struct ieee80211_regdomain * 422e9763c3cSJohannes Berg reg_copy_regd(const struct ieee80211_regdomain *src_regd) 4233b377ea9SJohn W. Linville { 4243b377ea9SJohn W. Linville struct ieee80211_regdomain *regd; 425e9763c3cSJohannes Berg int size_of_regd; 4263b377ea9SJohn W. Linville unsigned int i; 4273b377ea9SJohn W. Linville 42882f20856SJohannes Berg size_of_regd = 42982f20856SJohannes Berg sizeof(struct ieee80211_regdomain) + 43082f20856SJohannes Berg src_regd->n_reg_rules * sizeof(struct ieee80211_reg_rule); 4313b377ea9SJohn W. Linville 4323b377ea9SJohn W. Linville regd = kzalloc(size_of_regd, GFP_KERNEL); 4333b377ea9SJohn W. Linville if (!regd) 434e9763c3cSJohannes Berg return ERR_PTR(-ENOMEM); 4353b377ea9SJohn W. Linville 4363b377ea9SJohn W. Linville memcpy(regd, src_regd, sizeof(struct ieee80211_regdomain)); 4373b377ea9SJohn W. Linville 4383b377ea9SJohn W. Linville for (i = 0; i < src_regd->n_reg_rules; i++) 4393b377ea9SJohn W. Linville memcpy(®d->reg_rules[i], &src_regd->reg_rules[i], 4403b377ea9SJohn W. Linville sizeof(struct ieee80211_reg_rule)); 4413b377ea9SJohn W. Linville 442e9763c3cSJohannes Berg return regd; 4433b377ea9SJohn W. Linville } 4443b377ea9SJohn W. Linville 4453b377ea9SJohn W. Linville #ifdef CONFIG_CFG80211_INTERNAL_REGDB 446c7d319e5SJohannes Berg struct reg_regdb_apply_request { 4473b377ea9SJohn W. Linville struct list_head list; 448c7d319e5SJohannes Berg const struct ieee80211_regdomain *regdom; 4493b377ea9SJohn W. Linville }; 4503b377ea9SJohn W. Linville 451c7d319e5SJohannes Berg static LIST_HEAD(reg_regdb_apply_list); 452c7d319e5SJohannes Berg static DEFINE_MUTEX(reg_regdb_apply_mutex); 4533b377ea9SJohn W. Linville 454c7d319e5SJohannes Berg static void reg_regdb_apply(struct work_struct *work) 4553b377ea9SJohn W. Linville { 456c7d319e5SJohannes Berg struct reg_regdb_apply_request *request; 457a85d0d7fSLuis R. Rodriguez 4585fe231e8SJohannes Berg rtnl_lock(); 4593b377ea9SJohn W. Linville 460c7d319e5SJohannes Berg mutex_lock(®_regdb_apply_mutex); 461c7d319e5SJohannes Berg while (!list_empty(®_regdb_apply_list)) { 462c7d319e5SJohannes Berg request = list_first_entry(®_regdb_apply_list, 463c7d319e5SJohannes Berg struct reg_regdb_apply_request, 4643b377ea9SJohn W. Linville list); 4653b377ea9SJohn W. Linville list_del(&request->list); 4663b377ea9SJohn W. Linville 467c7d319e5SJohannes Berg set_regdom(request->regdom, REGD_SOURCE_INTERNAL_DB); 4683b377ea9SJohn W. Linville kfree(request); 4693b377ea9SJohn W. Linville } 470c7d319e5SJohannes Berg mutex_unlock(®_regdb_apply_mutex); 471a85d0d7fSLuis R. Rodriguez 4725fe231e8SJohannes Berg rtnl_unlock(); 4733b377ea9SJohn W. Linville } 4743b377ea9SJohn W. Linville 475c7d319e5SJohannes Berg static DECLARE_WORK(reg_regdb_work, reg_regdb_apply); 4763b377ea9SJohn W. Linville 477fd453d3cSJohannes Berg static int reg_query_builtin(const char *alpha2) 4783b377ea9SJohn W. Linville { 479c7d319e5SJohannes Berg const struct ieee80211_regdomain *regdom = NULL; 480c7d319e5SJohannes Berg struct reg_regdb_apply_request *request; 481c7d319e5SJohannes Berg unsigned int i; 4823b377ea9SJohn W. Linville 483c7d319e5SJohannes Berg for (i = 0; i < reg_regdb_size; i++) { 484c7d319e5SJohannes Berg if (alpha2_equal(alpha2, reg_regdb[i]->alpha2)) { 485c7d319e5SJohannes Berg regdom = reg_regdb[i]; 486c7d319e5SJohannes Berg break; 487c7d319e5SJohannes Berg } 488c7d319e5SJohannes Berg } 489c7d319e5SJohannes Berg 490c7d319e5SJohannes Berg if (!regdom) 491c7d319e5SJohannes Berg return -ENODATA; 492c7d319e5SJohannes Berg 493c7d319e5SJohannes Berg request = kzalloc(sizeof(struct reg_regdb_apply_request), GFP_KERNEL); 4943b377ea9SJohn W. Linville if (!request) 495c7d319e5SJohannes Berg return -ENOMEM; 4963b377ea9SJohn W. Linville 497c7d319e5SJohannes Berg request->regdom = reg_copy_regd(regdom); 498c7d319e5SJohannes Berg if (IS_ERR_OR_NULL(request->regdom)) { 499c7d319e5SJohannes Berg kfree(request); 500c7d319e5SJohannes Berg return -ENOMEM; 501c7d319e5SJohannes Berg } 5023b377ea9SJohn W. Linville 503c7d319e5SJohannes Berg mutex_lock(®_regdb_apply_mutex); 504c7d319e5SJohannes Berg list_add_tail(&request->list, ®_regdb_apply_list); 505c7d319e5SJohannes Berg mutex_unlock(®_regdb_apply_mutex); 5063b377ea9SJohn W. Linville 5073b377ea9SJohn W. Linville schedule_work(®_regdb_work); 508c7d319e5SJohannes Berg 509c7d319e5SJohannes Berg return 0; 5103b377ea9SJohn W. Linville } 51180007efeSLuis R. Rodriguez 51280007efeSLuis R. Rodriguez /* Feel free to add any other sanity checks here */ 51380007efeSLuis R. Rodriguez static void reg_regdb_size_check(void) 51480007efeSLuis R. Rodriguez { 51580007efeSLuis R. Rodriguez /* We should ideally BUILD_BUG_ON() but then random builds would fail */ 51680007efeSLuis R. Rodriguez WARN_ONCE(!reg_regdb_size, "db.txt is empty, you should update it..."); 51780007efeSLuis R. Rodriguez } 5183b377ea9SJohn W. Linville #else 51980007efeSLuis R. Rodriguez static inline void reg_regdb_size_check(void) {} 520fd453d3cSJohannes Berg static inline int reg_query_builtin(const char *alpha2) 521c7d319e5SJohannes Berg { 522c7d319e5SJohannes Berg return -ENODATA; 523c7d319e5SJohannes Berg } 5243b377ea9SJohn W. Linville #endif /* CONFIG_CFG80211_INTERNAL_REGDB */ 5253b377ea9SJohn W. Linville 526b6863036SJohannes Berg #ifdef CONFIG_CFG80211_CRDA_SUPPORT 527b6863036SJohannes Berg /* Max number of consecutive attempts to communicate with CRDA */ 528b6863036SJohannes Berg #define REG_MAX_CRDA_TIMEOUTS 10 529b6863036SJohannes Berg 530b6863036SJohannes Berg static u32 reg_crda_timeouts; 531b6863036SJohannes Berg 532b6863036SJohannes Berg static void crda_timeout_work(struct work_struct *work); 533b6863036SJohannes Berg static DECLARE_DELAYED_WORK(crda_timeout, crda_timeout_work); 534b6863036SJohannes Berg 535b6863036SJohannes Berg static void crda_timeout_work(struct work_struct *work) 536b6863036SJohannes Berg { 537c799ba6eSJohannes Berg pr_debug("Timeout while waiting for CRDA to reply, restoring regulatory settings\n"); 538b6863036SJohannes Berg rtnl_lock(); 539b6863036SJohannes Berg reg_crda_timeouts++; 540b6863036SJohannes Berg restore_regulatory_settings(true); 541b6863036SJohannes Berg rtnl_unlock(); 542b6863036SJohannes Berg } 543b6863036SJohannes Berg 544b6863036SJohannes Berg static void cancel_crda_timeout(void) 545b6863036SJohannes Berg { 546b6863036SJohannes Berg cancel_delayed_work(&crda_timeout); 547b6863036SJohannes Berg } 548b6863036SJohannes Berg 549b6863036SJohannes Berg static void cancel_crda_timeout_sync(void) 550b6863036SJohannes Berg { 551b6863036SJohannes Berg cancel_delayed_work_sync(&crda_timeout); 552b6863036SJohannes Berg } 553b6863036SJohannes Berg 554b6863036SJohannes Berg static void reset_crda_timeouts(void) 555b6863036SJohannes Berg { 556b6863036SJohannes Berg reg_crda_timeouts = 0; 557b6863036SJohannes Berg } 558b6863036SJohannes Berg 559fb1fc7adSLuis R. Rodriguez /* 560fb1fc7adSLuis R. Rodriguez * This lets us keep regulatory code which is updated on a regulatory 5611226d258SJohannes Berg * basis in userspace. 562fb1fc7adSLuis R. Rodriguez */ 563b2e1b302SLuis R. Rodriguez static int call_crda(const char *alpha2) 564b2e1b302SLuis R. Rodriguez { 5651226d258SJohannes Berg char country[12]; 5661226d258SJohannes Berg char *env[] = { country, NULL }; 567c7d319e5SJohannes Berg int ret; 5681226d258SJohannes Berg 5691226d258SJohannes Berg snprintf(country, sizeof(country), "COUNTRY=%c%c", 5701226d258SJohannes Berg alpha2[0], alpha2[1]); 5711226d258SJohannes Berg 572c37722bdSIlan peer if (reg_crda_timeouts > REG_MAX_CRDA_TIMEOUTS) { 573042ab5fcSThomas Petazzoni pr_debug("Exceeded CRDA call max attempts. Not calling CRDA\n"); 574c37722bdSIlan peer return -EINVAL; 575c37722bdSIlan peer } 576c37722bdSIlan peer 577b2e1b302SLuis R. Rodriguez if (!is_world_regdom((char *) alpha2)) 578042ab5fcSThomas Petazzoni pr_debug("Calling CRDA for country: %c%c\n", 579b2e1b302SLuis R. Rodriguez alpha2[0], alpha2[1]); 580b2e1b302SLuis R. Rodriguez else 581042ab5fcSThomas Petazzoni pr_debug("Calling CRDA to update world regulatory domain\n"); 5828318d78aSJohannes Berg 583c7d319e5SJohannes Berg ret = kobject_uevent_env(®_pdev->dev.kobj, KOBJ_CHANGE, env); 584c7d319e5SJohannes Berg if (ret) 585c7d319e5SJohannes Berg return ret; 586c7d319e5SJohannes Berg 587c7d319e5SJohannes Berg queue_delayed_work(system_power_efficient_wq, 588b6863036SJohannes Berg &crda_timeout, msecs_to_jiffies(3142)); 589c7d319e5SJohannes Berg return 0; 590b2e1b302SLuis R. Rodriguez } 591b6863036SJohannes Berg #else 592b6863036SJohannes Berg static inline void cancel_crda_timeout(void) {} 593b6863036SJohannes Berg static inline void cancel_crda_timeout_sync(void) {} 594b6863036SJohannes Berg static inline void reset_crda_timeouts(void) {} 595b6863036SJohannes Berg static inline int call_crda(const char *alpha2) 596b6863036SJohannes Berg { 597b6863036SJohannes Berg return -ENODATA; 598b6863036SJohannes Berg } 599b6863036SJohannes Berg #endif /* CONFIG_CFG80211_CRDA_SUPPORT */ 600b2e1b302SLuis R. Rodriguez 601cecbb069SJohannes Berg static bool reg_query_database(struct regulatory_request *request) 602fe6631ffSLuis R. Rodriguez { 603c7d319e5SJohannes Berg /* query internal regulatory database (if it exists) */ 604fd453d3cSJohannes Berg if (reg_query_builtin(request->alpha2) == 0) 60525b20dbdSJohannes Berg return true; 606c7d319e5SJohannes Berg 607c7d319e5SJohannes Berg if (call_crda(request->alpha2) == 0) 608c7d319e5SJohannes Berg return true; 609c7d319e5SJohannes Berg 610c7d319e5SJohannes Berg return false; 611fe6631ffSLuis R. Rodriguez } 612fe6631ffSLuis R. Rodriguez 613e438768fSLuis R. Rodriguez bool reg_is_valid_request(const char *alpha2) 614b2e1b302SLuis R. Rodriguez { 615c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 61661405e97SLuis R. Rodriguez 617c492db37SJohannes Berg if (!lr || lr->processed) 618f6037d09SJohannes Berg return false; 619f6037d09SJohannes Berg 620c492db37SJohannes Berg return alpha2_equal(lr->alpha2, alpha2); 621b2e1b302SLuis R. Rodriguez } 622b2e1b302SLuis R. Rodriguez 623e3961af1SJanusz Dziedzic static const struct ieee80211_regdomain *reg_get_regdomain(struct wiphy *wiphy) 624e3961af1SJanusz Dziedzic { 625e3961af1SJanusz Dziedzic struct regulatory_request *lr = get_last_request(); 626e3961af1SJanusz Dziedzic 627e3961af1SJanusz Dziedzic /* 628e3961af1SJanusz Dziedzic * Follow the driver's regulatory domain, if present, unless a country 629e3961af1SJanusz Dziedzic * IE has been processed or a user wants to help complaince further 630e3961af1SJanusz Dziedzic */ 631e3961af1SJanusz Dziedzic if (lr->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE && 632e3961af1SJanusz Dziedzic lr->initiator != NL80211_REGDOM_SET_BY_USER && 633e3961af1SJanusz Dziedzic wiphy->regd) 634e3961af1SJanusz Dziedzic return get_wiphy_regdom(wiphy); 635e3961af1SJanusz Dziedzic 636e3961af1SJanusz Dziedzic return get_cfg80211_regdom(); 637e3961af1SJanusz Dziedzic } 638e3961af1SJanusz Dziedzic 639a6d4a534SArik Nemtsov static unsigned int 640a6d4a534SArik Nemtsov reg_get_max_bandwidth_from_range(const struct ieee80211_regdomain *rd, 64197524820SJanusz Dziedzic const struct ieee80211_reg_rule *rule) 64297524820SJanusz Dziedzic { 64397524820SJanusz Dziedzic const struct ieee80211_freq_range *freq_range = &rule->freq_range; 64497524820SJanusz Dziedzic const struct ieee80211_freq_range *freq_range_tmp; 64597524820SJanusz Dziedzic const struct ieee80211_reg_rule *tmp; 64697524820SJanusz Dziedzic u32 start_freq, end_freq, idx, no; 64797524820SJanusz Dziedzic 64897524820SJanusz Dziedzic for (idx = 0; idx < rd->n_reg_rules; idx++) 64997524820SJanusz Dziedzic if (rule == &rd->reg_rules[idx]) 65097524820SJanusz Dziedzic break; 65197524820SJanusz Dziedzic 65297524820SJanusz Dziedzic if (idx == rd->n_reg_rules) 65397524820SJanusz Dziedzic return 0; 65497524820SJanusz Dziedzic 65597524820SJanusz Dziedzic /* get start_freq */ 65697524820SJanusz Dziedzic no = idx; 65797524820SJanusz Dziedzic 65897524820SJanusz Dziedzic while (no) { 65997524820SJanusz Dziedzic tmp = &rd->reg_rules[--no]; 66097524820SJanusz Dziedzic freq_range_tmp = &tmp->freq_range; 66197524820SJanusz Dziedzic 66297524820SJanusz Dziedzic if (freq_range_tmp->end_freq_khz < freq_range->start_freq_khz) 66397524820SJanusz Dziedzic break; 66497524820SJanusz Dziedzic 66597524820SJanusz Dziedzic freq_range = freq_range_tmp; 66697524820SJanusz Dziedzic } 66797524820SJanusz Dziedzic 66897524820SJanusz Dziedzic start_freq = freq_range->start_freq_khz; 66997524820SJanusz Dziedzic 67097524820SJanusz Dziedzic /* get end_freq */ 67197524820SJanusz Dziedzic freq_range = &rule->freq_range; 67297524820SJanusz Dziedzic no = idx; 67397524820SJanusz Dziedzic 67497524820SJanusz Dziedzic while (no < rd->n_reg_rules - 1) { 67597524820SJanusz Dziedzic tmp = &rd->reg_rules[++no]; 67697524820SJanusz Dziedzic freq_range_tmp = &tmp->freq_range; 67797524820SJanusz Dziedzic 67897524820SJanusz Dziedzic if (freq_range_tmp->start_freq_khz > freq_range->end_freq_khz) 67997524820SJanusz Dziedzic break; 68097524820SJanusz Dziedzic 68197524820SJanusz Dziedzic freq_range = freq_range_tmp; 68297524820SJanusz Dziedzic } 68397524820SJanusz Dziedzic 68497524820SJanusz Dziedzic end_freq = freq_range->end_freq_khz; 68597524820SJanusz Dziedzic 68697524820SJanusz Dziedzic return end_freq - start_freq; 68797524820SJanusz Dziedzic } 68897524820SJanusz Dziedzic 689a6d4a534SArik Nemtsov unsigned int reg_get_max_bandwidth(const struct ieee80211_regdomain *rd, 690a6d4a534SArik Nemtsov const struct ieee80211_reg_rule *rule) 691a6d4a534SArik Nemtsov { 692a6d4a534SArik Nemtsov unsigned int bw = reg_get_max_bandwidth_from_range(rd, rule); 693a6d4a534SArik Nemtsov 694a6d4a534SArik Nemtsov if (rule->flags & NL80211_RRF_NO_160MHZ) 695a6d4a534SArik Nemtsov bw = min_t(unsigned int, bw, MHZ_TO_KHZ(80)); 696a6d4a534SArik Nemtsov if (rule->flags & NL80211_RRF_NO_80MHZ) 697a6d4a534SArik Nemtsov bw = min_t(unsigned int, bw, MHZ_TO_KHZ(40)); 698a6d4a534SArik Nemtsov 699a6d4a534SArik Nemtsov /* 700a6d4a534SArik Nemtsov * HT40+/HT40- limits are handled per-channel. Only limit BW if both 701a6d4a534SArik Nemtsov * are not allowed. 702a6d4a534SArik Nemtsov */ 703a6d4a534SArik Nemtsov if (rule->flags & NL80211_RRF_NO_HT40MINUS && 704a6d4a534SArik Nemtsov rule->flags & NL80211_RRF_NO_HT40PLUS) 705a6d4a534SArik Nemtsov bw = min_t(unsigned int, bw, MHZ_TO_KHZ(20)); 706a6d4a534SArik Nemtsov 707a6d4a534SArik Nemtsov return bw; 708a6d4a534SArik Nemtsov } 709a6d4a534SArik Nemtsov 710b2e1b302SLuis R. Rodriguez /* Sanity check on a regulatory rule */ 711a3d2eaf0SJohannes Berg static bool is_valid_reg_rule(const struct ieee80211_reg_rule *rule) 712b2e1b302SLuis R. Rodriguez { 713a3d2eaf0SJohannes Berg const struct ieee80211_freq_range *freq_range = &rule->freq_range; 714b2e1b302SLuis R. Rodriguez u32 freq_diff; 715b2e1b302SLuis R. Rodriguez 71691e99004SLuis R. Rodriguez if (freq_range->start_freq_khz <= 0 || freq_range->end_freq_khz <= 0) 717b2e1b302SLuis R. Rodriguez return false; 718b2e1b302SLuis R. Rodriguez 719b2e1b302SLuis R. Rodriguez if (freq_range->start_freq_khz > freq_range->end_freq_khz) 720b2e1b302SLuis R. Rodriguez return false; 721b2e1b302SLuis R. Rodriguez 722b2e1b302SLuis R. Rodriguez freq_diff = freq_range->end_freq_khz - freq_range->start_freq_khz; 723b2e1b302SLuis R. Rodriguez 724bd05f28eSRoel Kluin if (freq_range->end_freq_khz <= freq_range->start_freq_khz || 725bd05f28eSRoel Kluin freq_range->max_bandwidth_khz > freq_diff) 726b2e1b302SLuis R. Rodriguez return false; 727b2e1b302SLuis R. Rodriguez 728b2e1b302SLuis R. Rodriguez return true; 729b2e1b302SLuis R. Rodriguez } 730b2e1b302SLuis R. Rodriguez 731a3d2eaf0SJohannes Berg static bool is_valid_rd(const struct ieee80211_regdomain *rd) 732b2e1b302SLuis R. Rodriguez { 733a3d2eaf0SJohannes Berg const struct ieee80211_reg_rule *reg_rule = NULL; 734b2e1b302SLuis R. Rodriguez unsigned int i; 735b2e1b302SLuis R. Rodriguez 736b2e1b302SLuis R. Rodriguez if (!rd->n_reg_rules) 737b2e1b302SLuis R. Rodriguez return false; 738b2e1b302SLuis R. Rodriguez 73988dc1c3fSLuis R. Rodriguez if (WARN_ON(rd->n_reg_rules > NL80211_MAX_SUPP_REG_RULES)) 74088dc1c3fSLuis R. Rodriguez return false; 74188dc1c3fSLuis R. Rodriguez 742b2e1b302SLuis R. Rodriguez for (i = 0; i < rd->n_reg_rules; i++) { 743b2e1b302SLuis R. Rodriguez reg_rule = &rd->reg_rules[i]; 744b2e1b302SLuis R. Rodriguez if (!is_valid_reg_rule(reg_rule)) 745b2e1b302SLuis R. Rodriguez return false; 746b2e1b302SLuis R. Rodriguez } 747b2e1b302SLuis R. Rodriguez 748b2e1b302SLuis R. Rodriguez return true; 749b2e1b302SLuis R. Rodriguez } 750b2e1b302SLuis R. Rodriguez 7510c7dc45dSLuis R. Rodriguez /** 7520c7dc45dSLuis R. Rodriguez * freq_in_rule_band - tells us if a frequency is in a frequency band 7530c7dc45dSLuis R. Rodriguez * @freq_range: frequency rule we want to query 7540c7dc45dSLuis R. Rodriguez * @freq_khz: frequency we are inquiring about 7550c7dc45dSLuis R. Rodriguez * 7560c7dc45dSLuis R. Rodriguez * This lets us know if a specific frequency rule is or is not relevant to 7570c7dc45dSLuis R. Rodriguez * a specific frequency's band. Bands are device specific and artificial 75864629b9dSVladimir Kondratiev * definitions (the "2.4 GHz band", the "5 GHz band" and the "60GHz band"), 75964629b9dSVladimir Kondratiev * however it is safe for now to assume that a frequency rule should not be 76064629b9dSVladimir Kondratiev * part of a frequency's band if the start freq or end freq are off by more 76164629b9dSVladimir Kondratiev * than 2 GHz for the 2.4 and 5 GHz bands, and by more than 10 GHz for the 76264629b9dSVladimir Kondratiev * 60 GHz band. 7630c7dc45dSLuis R. Rodriguez * This resolution can be lowered and should be considered as we add 7640c7dc45dSLuis R. Rodriguez * regulatory rule support for other "bands". 7650c7dc45dSLuis R. Rodriguez **/ 7660c7dc45dSLuis R. Rodriguez static bool freq_in_rule_band(const struct ieee80211_freq_range *freq_range, 7670c7dc45dSLuis R. Rodriguez u32 freq_khz) 7680c7dc45dSLuis R. Rodriguez { 7690c7dc45dSLuis R. Rodriguez #define ONE_GHZ_IN_KHZ 1000000 77064629b9dSVladimir Kondratiev /* 77164629b9dSVladimir Kondratiev * From 802.11ad: directional multi-gigabit (DMG): 77264629b9dSVladimir Kondratiev * Pertaining to operation in a frequency band containing a channel 77364629b9dSVladimir Kondratiev * with the Channel starting frequency above 45 GHz. 77464629b9dSVladimir Kondratiev */ 77564629b9dSVladimir Kondratiev u32 limit = freq_khz > 45 * ONE_GHZ_IN_KHZ ? 77664629b9dSVladimir Kondratiev 10 * ONE_GHZ_IN_KHZ : 2 * ONE_GHZ_IN_KHZ; 77764629b9dSVladimir Kondratiev if (abs(freq_khz - freq_range->start_freq_khz) <= limit) 7780c7dc45dSLuis R. Rodriguez return true; 77964629b9dSVladimir Kondratiev if (abs(freq_khz - freq_range->end_freq_khz) <= limit) 7800c7dc45dSLuis R. Rodriguez return true; 7810c7dc45dSLuis R. Rodriguez return false; 7820c7dc45dSLuis R. Rodriguez #undef ONE_GHZ_IN_KHZ 7830c7dc45dSLuis R. Rodriguez } 7840c7dc45dSLuis R. Rodriguez 785fb1fc7adSLuis R. Rodriguez /* 786adbfb058SLuis R. Rodriguez * Later on we can perhaps use the more restrictive DFS 787adbfb058SLuis R. Rodriguez * region but we don't have information for that yet so 788adbfb058SLuis R. Rodriguez * for now simply disallow conflicts. 789adbfb058SLuis R. Rodriguez */ 790adbfb058SLuis R. Rodriguez static enum nl80211_dfs_regions 791adbfb058SLuis R. Rodriguez reg_intersect_dfs_region(const enum nl80211_dfs_regions dfs_region1, 792adbfb058SLuis R. Rodriguez const enum nl80211_dfs_regions dfs_region2) 793adbfb058SLuis R. Rodriguez { 794adbfb058SLuis R. Rodriguez if (dfs_region1 != dfs_region2) 795adbfb058SLuis R. Rodriguez return NL80211_DFS_UNSET; 796adbfb058SLuis R. Rodriguez return dfs_region1; 797adbfb058SLuis R. Rodriguez } 798adbfb058SLuis R. Rodriguez 799adbfb058SLuis R. Rodriguez /* 800fb1fc7adSLuis R. Rodriguez * Helper for regdom_intersect(), this does the real 801fb1fc7adSLuis R. Rodriguez * mathematical intersection fun 802fb1fc7adSLuis R. Rodriguez */ 80397524820SJanusz Dziedzic static int reg_rules_intersect(const struct ieee80211_regdomain *rd1, 80497524820SJanusz Dziedzic const struct ieee80211_regdomain *rd2, 80597524820SJanusz Dziedzic const struct ieee80211_reg_rule *rule1, 8069c96477dSLuis R. Rodriguez const struct ieee80211_reg_rule *rule2, 8079c96477dSLuis R. Rodriguez struct ieee80211_reg_rule *intersected_rule) 8089c96477dSLuis R. Rodriguez { 8099c96477dSLuis R. Rodriguez const struct ieee80211_freq_range *freq_range1, *freq_range2; 8109c96477dSLuis R. Rodriguez struct ieee80211_freq_range *freq_range; 8119c96477dSLuis R. Rodriguez const struct ieee80211_power_rule *power_rule1, *power_rule2; 8129c96477dSLuis R. Rodriguez struct ieee80211_power_rule *power_rule; 81397524820SJanusz Dziedzic u32 freq_diff, max_bandwidth1, max_bandwidth2; 8149c96477dSLuis R. Rodriguez 8159c96477dSLuis R. Rodriguez freq_range1 = &rule1->freq_range; 8169c96477dSLuis R. Rodriguez freq_range2 = &rule2->freq_range; 8179c96477dSLuis R. Rodriguez freq_range = &intersected_rule->freq_range; 8189c96477dSLuis R. Rodriguez 8199c96477dSLuis R. Rodriguez power_rule1 = &rule1->power_rule; 8209c96477dSLuis R. Rodriguez power_rule2 = &rule2->power_rule; 8219c96477dSLuis R. Rodriguez power_rule = &intersected_rule->power_rule; 8229c96477dSLuis R. Rodriguez 8239c96477dSLuis R. Rodriguez freq_range->start_freq_khz = max(freq_range1->start_freq_khz, 8249c96477dSLuis R. Rodriguez freq_range2->start_freq_khz); 8259c96477dSLuis R. Rodriguez freq_range->end_freq_khz = min(freq_range1->end_freq_khz, 8269c96477dSLuis R. Rodriguez freq_range2->end_freq_khz); 82797524820SJanusz Dziedzic 82897524820SJanusz Dziedzic max_bandwidth1 = freq_range1->max_bandwidth_khz; 82997524820SJanusz Dziedzic max_bandwidth2 = freq_range2->max_bandwidth_khz; 83097524820SJanusz Dziedzic 831b0dfd2eaSJanusz Dziedzic if (rule1->flags & NL80211_RRF_AUTO_BW) 83297524820SJanusz Dziedzic max_bandwidth1 = reg_get_max_bandwidth(rd1, rule1); 833b0dfd2eaSJanusz Dziedzic if (rule2->flags & NL80211_RRF_AUTO_BW) 83497524820SJanusz Dziedzic max_bandwidth2 = reg_get_max_bandwidth(rd2, rule2); 83597524820SJanusz Dziedzic 83697524820SJanusz Dziedzic freq_range->max_bandwidth_khz = min(max_bandwidth1, max_bandwidth2); 8379c96477dSLuis R. Rodriguez 838b0dfd2eaSJanusz Dziedzic intersected_rule->flags = rule1->flags | rule2->flags; 839b0dfd2eaSJanusz Dziedzic 840b0dfd2eaSJanusz Dziedzic /* 841b0dfd2eaSJanusz Dziedzic * In case NL80211_RRF_AUTO_BW requested for both rules 842b0dfd2eaSJanusz Dziedzic * set AUTO_BW in intersected rule also. Next we will 843b0dfd2eaSJanusz Dziedzic * calculate BW correctly in handle_channel function. 844b0dfd2eaSJanusz Dziedzic * In other case remove AUTO_BW flag while we calculate 845b0dfd2eaSJanusz Dziedzic * maximum bandwidth correctly and auto calculation is 846b0dfd2eaSJanusz Dziedzic * not required. 847b0dfd2eaSJanusz Dziedzic */ 848b0dfd2eaSJanusz Dziedzic if ((rule1->flags & NL80211_RRF_AUTO_BW) && 849b0dfd2eaSJanusz Dziedzic (rule2->flags & NL80211_RRF_AUTO_BW)) 850b0dfd2eaSJanusz Dziedzic intersected_rule->flags |= NL80211_RRF_AUTO_BW; 851b0dfd2eaSJanusz Dziedzic else 852b0dfd2eaSJanusz Dziedzic intersected_rule->flags &= ~NL80211_RRF_AUTO_BW; 853b0dfd2eaSJanusz Dziedzic 8549c96477dSLuis R. Rodriguez freq_diff = freq_range->end_freq_khz - freq_range->start_freq_khz; 8559c96477dSLuis R. Rodriguez if (freq_range->max_bandwidth_khz > freq_diff) 8569c96477dSLuis R. Rodriguez freq_range->max_bandwidth_khz = freq_diff; 8579c96477dSLuis R. Rodriguez 8589c96477dSLuis R. Rodriguez power_rule->max_eirp = min(power_rule1->max_eirp, 8599c96477dSLuis R. Rodriguez power_rule2->max_eirp); 8609c96477dSLuis R. Rodriguez power_rule->max_antenna_gain = min(power_rule1->max_antenna_gain, 8619c96477dSLuis R. Rodriguez power_rule2->max_antenna_gain); 8629c96477dSLuis R. Rodriguez 863089027e5SJanusz Dziedzic intersected_rule->dfs_cac_ms = max(rule1->dfs_cac_ms, 864089027e5SJanusz Dziedzic rule2->dfs_cac_ms); 865089027e5SJanusz Dziedzic 8669c96477dSLuis R. Rodriguez if (!is_valid_reg_rule(intersected_rule)) 8679c96477dSLuis R. Rodriguez return -EINVAL; 8689c96477dSLuis R. Rodriguez 8699c96477dSLuis R. Rodriguez return 0; 8709c96477dSLuis R. Rodriguez } 8719c96477dSLuis R. Rodriguez 872a62a1aedSEliad Peller /* check whether old rule contains new rule */ 873a62a1aedSEliad Peller static bool rule_contains(struct ieee80211_reg_rule *r1, 874a62a1aedSEliad Peller struct ieee80211_reg_rule *r2) 875a62a1aedSEliad Peller { 876a62a1aedSEliad Peller /* for simplicity, currently consider only same flags */ 877a62a1aedSEliad Peller if (r1->flags != r2->flags) 878a62a1aedSEliad Peller return false; 879a62a1aedSEliad Peller 880a62a1aedSEliad Peller /* verify r1 is more restrictive */ 881a62a1aedSEliad Peller if ((r1->power_rule.max_antenna_gain > 882a62a1aedSEliad Peller r2->power_rule.max_antenna_gain) || 883a62a1aedSEliad Peller r1->power_rule.max_eirp > r2->power_rule.max_eirp) 884a62a1aedSEliad Peller return false; 885a62a1aedSEliad Peller 886a62a1aedSEliad Peller /* make sure r2's range is contained within r1 */ 887a62a1aedSEliad Peller if (r1->freq_range.start_freq_khz > r2->freq_range.start_freq_khz || 888a62a1aedSEliad Peller r1->freq_range.end_freq_khz < r2->freq_range.end_freq_khz) 889a62a1aedSEliad Peller return false; 890a62a1aedSEliad Peller 891a62a1aedSEliad Peller /* and finally verify that r1.max_bw >= r2.max_bw */ 892a62a1aedSEliad Peller if (r1->freq_range.max_bandwidth_khz < 893a62a1aedSEliad Peller r2->freq_range.max_bandwidth_khz) 894a62a1aedSEliad Peller return false; 895a62a1aedSEliad Peller 896a62a1aedSEliad Peller return true; 897a62a1aedSEliad Peller } 898a62a1aedSEliad Peller 899a62a1aedSEliad Peller /* add or extend current rules. do nothing if rule is already contained */ 900a62a1aedSEliad Peller static void add_rule(struct ieee80211_reg_rule *rule, 901a62a1aedSEliad Peller struct ieee80211_reg_rule *reg_rules, u32 *n_rules) 902a62a1aedSEliad Peller { 903a62a1aedSEliad Peller struct ieee80211_reg_rule *tmp_rule; 904a62a1aedSEliad Peller int i; 905a62a1aedSEliad Peller 906a62a1aedSEliad Peller for (i = 0; i < *n_rules; i++) { 907a62a1aedSEliad Peller tmp_rule = ®_rules[i]; 908a62a1aedSEliad Peller /* rule is already contained - do nothing */ 909a62a1aedSEliad Peller if (rule_contains(tmp_rule, rule)) 910a62a1aedSEliad Peller return; 911a62a1aedSEliad Peller 912a62a1aedSEliad Peller /* extend rule if possible */ 913a62a1aedSEliad Peller if (rule_contains(rule, tmp_rule)) { 914a62a1aedSEliad Peller memcpy(tmp_rule, rule, sizeof(*rule)); 915a62a1aedSEliad Peller return; 916a62a1aedSEliad Peller } 917a62a1aedSEliad Peller } 918a62a1aedSEliad Peller 919a62a1aedSEliad Peller memcpy(®_rules[*n_rules], rule, sizeof(*rule)); 920a62a1aedSEliad Peller (*n_rules)++; 921a62a1aedSEliad Peller } 922a62a1aedSEliad Peller 9239c96477dSLuis R. Rodriguez /** 9249c96477dSLuis R. Rodriguez * regdom_intersect - do the intersection between two regulatory domains 9259c96477dSLuis R. Rodriguez * @rd1: first regulatory domain 9269c96477dSLuis R. Rodriguez * @rd2: second regulatory domain 9279c96477dSLuis R. Rodriguez * 9289c96477dSLuis R. Rodriguez * Use this function to get the intersection between two regulatory domains. 9299c96477dSLuis R. Rodriguez * Once completed we will mark the alpha2 for the rd as intersected, "98", 9309c96477dSLuis R. Rodriguez * as no one single alpha2 can represent this regulatory domain. 9319c96477dSLuis R. Rodriguez * 9329c96477dSLuis R. Rodriguez * Returns a pointer to the regulatory domain structure which will hold the 9339c96477dSLuis R. Rodriguez * resulting intersection of rules between rd1 and rd2. We will 9349c96477dSLuis R. Rodriguez * kzalloc() this structure for you. 9359c96477dSLuis R. Rodriguez */ 9361a919318SJohannes Berg static struct ieee80211_regdomain * 9371a919318SJohannes Berg regdom_intersect(const struct ieee80211_regdomain *rd1, 9389c96477dSLuis R. Rodriguez const struct ieee80211_regdomain *rd2) 9399c96477dSLuis R. Rodriguez { 9409c96477dSLuis R. Rodriguez int r, size_of_regd; 9419c96477dSLuis R. Rodriguez unsigned int x, y; 942a62a1aedSEliad Peller unsigned int num_rules = 0; 9439c96477dSLuis R. Rodriguez const struct ieee80211_reg_rule *rule1, *rule2; 944a62a1aedSEliad Peller struct ieee80211_reg_rule intersected_rule; 9459c96477dSLuis R. Rodriguez struct ieee80211_regdomain *rd; 9469c96477dSLuis R. Rodriguez 9479c96477dSLuis R. Rodriguez if (!rd1 || !rd2) 9489c96477dSLuis R. Rodriguez return NULL; 9499c96477dSLuis R. Rodriguez 950fb1fc7adSLuis R. Rodriguez /* 951fb1fc7adSLuis R. Rodriguez * First we get a count of the rules we'll need, then we actually 9529c96477dSLuis R. Rodriguez * build them. This is to so we can malloc() and free() a 9539c96477dSLuis R. Rodriguez * regdomain once. The reason we use reg_rules_intersect() here 9549c96477dSLuis R. Rodriguez * is it will return -EINVAL if the rule computed makes no sense. 955fb1fc7adSLuis R. Rodriguez * All rules that do check out OK are valid. 956fb1fc7adSLuis R. Rodriguez */ 9579c96477dSLuis R. Rodriguez 9589c96477dSLuis R. Rodriguez for (x = 0; x < rd1->n_reg_rules; x++) { 9599c96477dSLuis R. Rodriguez rule1 = &rd1->reg_rules[x]; 9609c96477dSLuis R. Rodriguez for (y = 0; y < rd2->n_reg_rules; y++) { 9619c96477dSLuis R. Rodriguez rule2 = &rd2->reg_rules[y]; 96297524820SJanusz Dziedzic if (!reg_rules_intersect(rd1, rd2, rule1, rule2, 963a62a1aedSEliad Peller &intersected_rule)) 9649c96477dSLuis R. Rodriguez num_rules++; 9659c96477dSLuis R. Rodriguez } 9669c96477dSLuis R. Rodriguez } 9679c96477dSLuis R. Rodriguez 9689c96477dSLuis R. Rodriguez if (!num_rules) 9699c96477dSLuis R. Rodriguez return NULL; 9709c96477dSLuis R. Rodriguez 9719c96477dSLuis R. Rodriguez size_of_regd = sizeof(struct ieee80211_regdomain) + 97282f20856SJohannes Berg num_rules * sizeof(struct ieee80211_reg_rule); 9739c96477dSLuis R. Rodriguez 9749c96477dSLuis R. Rodriguez rd = kzalloc(size_of_regd, GFP_KERNEL); 9759c96477dSLuis R. Rodriguez if (!rd) 9769c96477dSLuis R. Rodriguez return NULL; 9779c96477dSLuis R. Rodriguez 978a62a1aedSEliad Peller for (x = 0; x < rd1->n_reg_rules; x++) { 9799c96477dSLuis R. Rodriguez rule1 = &rd1->reg_rules[x]; 980a62a1aedSEliad Peller for (y = 0; y < rd2->n_reg_rules; y++) { 9819c96477dSLuis R. Rodriguez rule2 = &rd2->reg_rules[y]; 98297524820SJanusz Dziedzic r = reg_rules_intersect(rd1, rd2, rule1, rule2, 983a62a1aedSEliad Peller &intersected_rule); 984fb1fc7adSLuis R. Rodriguez /* 985fb1fc7adSLuis R. Rodriguez * No need to memset here the intersected rule here as 986fb1fc7adSLuis R. Rodriguez * we're not using the stack anymore 987fb1fc7adSLuis R. Rodriguez */ 9889c96477dSLuis R. Rodriguez if (r) 9899c96477dSLuis R. Rodriguez continue; 990a62a1aedSEliad Peller 991a62a1aedSEliad Peller add_rule(&intersected_rule, rd->reg_rules, 992a62a1aedSEliad Peller &rd->n_reg_rules); 9939c96477dSLuis R. Rodriguez } 9949c96477dSLuis R. Rodriguez } 9959c96477dSLuis R. Rodriguez 9969c96477dSLuis R. Rodriguez rd->alpha2[0] = '9'; 9979c96477dSLuis R. Rodriguez rd->alpha2[1] = '8'; 998adbfb058SLuis R. Rodriguez rd->dfs_region = reg_intersect_dfs_region(rd1->dfs_region, 999adbfb058SLuis R. Rodriguez rd2->dfs_region); 10009c96477dSLuis R. Rodriguez 10019c96477dSLuis R. Rodriguez return rd; 10029c96477dSLuis R. Rodriguez } 10039c96477dSLuis R. Rodriguez 1004fb1fc7adSLuis R. Rodriguez /* 1005fb1fc7adSLuis R. Rodriguez * XXX: add support for the rest of enum nl80211_reg_rule_flags, we may 1006fb1fc7adSLuis R. Rodriguez * want to just have the channel structure use these 1007fb1fc7adSLuis R. Rodriguez */ 1008b2e1b302SLuis R. Rodriguez static u32 map_regdom_flags(u32 rd_flags) 1009b2e1b302SLuis R. Rodriguez { 1010b2e1b302SLuis R. Rodriguez u32 channel_flags = 0; 10118fe02e16SLuis R. Rodriguez if (rd_flags & NL80211_RRF_NO_IR_ALL) 10128fe02e16SLuis R. Rodriguez channel_flags |= IEEE80211_CHAN_NO_IR; 1013b2e1b302SLuis R. Rodriguez if (rd_flags & NL80211_RRF_DFS) 1014b2e1b302SLuis R. Rodriguez channel_flags |= IEEE80211_CHAN_RADAR; 101503f6b084SSeth Forshee if (rd_flags & NL80211_RRF_NO_OFDM) 101603f6b084SSeth Forshee channel_flags |= IEEE80211_CHAN_NO_OFDM; 1017570dbde1SDavid Spinadel if (rd_flags & NL80211_RRF_NO_OUTDOOR) 1018570dbde1SDavid Spinadel channel_flags |= IEEE80211_CHAN_INDOOR_ONLY; 101906f207fcSArik Nemtsov if (rd_flags & NL80211_RRF_IR_CONCURRENT) 102006f207fcSArik Nemtsov channel_flags |= IEEE80211_CHAN_IR_CONCURRENT; 1021a6d4a534SArik Nemtsov if (rd_flags & NL80211_RRF_NO_HT40MINUS) 1022a6d4a534SArik Nemtsov channel_flags |= IEEE80211_CHAN_NO_HT40MINUS; 1023a6d4a534SArik Nemtsov if (rd_flags & NL80211_RRF_NO_HT40PLUS) 1024a6d4a534SArik Nemtsov channel_flags |= IEEE80211_CHAN_NO_HT40PLUS; 1025a6d4a534SArik Nemtsov if (rd_flags & NL80211_RRF_NO_80MHZ) 1026a6d4a534SArik Nemtsov channel_flags |= IEEE80211_CHAN_NO_80MHZ; 1027a6d4a534SArik Nemtsov if (rd_flags & NL80211_RRF_NO_160MHZ) 1028a6d4a534SArik Nemtsov channel_flags |= IEEE80211_CHAN_NO_160MHZ; 1029b2e1b302SLuis R. Rodriguez return channel_flags; 1030b2e1b302SLuis R. Rodriguez } 1031b2e1b302SLuis R. Rodriguez 1032361c9c8bSJohannes Berg static const struct ieee80211_reg_rule * 103349172874SMichal Sojka freq_reg_info_regd(u32 center_freq, 10344edd5698SMatthias May const struct ieee80211_regdomain *regd, u32 bw) 10358318d78aSJohannes Berg { 10368318d78aSJohannes Berg int i; 10370c7dc45dSLuis R. Rodriguez bool band_rule_found = false; 1038038659e7SLuis R. Rodriguez bool bw_fits = false; 1039038659e7SLuis R. Rodriguez 10403e0c3ff3SLuis R. Rodriguez if (!regd) 1041361c9c8bSJohannes Berg return ERR_PTR(-EINVAL); 1042b2e1b302SLuis R. Rodriguez 10433e0c3ff3SLuis R. Rodriguez for (i = 0; i < regd->n_reg_rules; i++) { 1044b2e1b302SLuis R. Rodriguez const struct ieee80211_reg_rule *rr; 1045b2e1b302SLuis R. Rodriguez const struct ieee80211_freq_range *fr = NULL; 1046b2e1b302SLuis R. Rodriguez 10473e0c3ff3SLuis R. Rodriguez rr = ®d->reg_rules[i]; 1048b2e1b302SLuis R. Rodriguez fr = &rr->freq_range; 10490c7dc45dSLuis R. Rodriguez 1050fb1fc7adSLuis R. Rodriguez /* 1051fb1fc7adSLuis R. Rodriguez * We only need to know if one frequency rule was 10520c7dc45dSLuis R. Rodriguez * was in center_freq's band, that's enough, so lets 1053fb1fc7adSLuis R. Rodriguez * not overwrite it once found 1054fb1fc7adSLuis R. Rodriguez */ 10550c7dc45dSLuis R. Rodriguez if (!band_rule_found) 10560c7dc45dSLuis R. Rodriguez band_rule_found = freq_in_rule_band(fr, center_freq); 10570c7dc45dSLuis R. Rodriguez 10584787cfa0SRafał Miłecki bw_fits = cfg80211_does_bw_fit_range(fr, center_freq, bw); 10590c7dc45dSLuis R. Rodriguez 1060361c9c8bSJohannes Berg if (band_rule_found && bw_fits) 1061361c9c8bSJohannes Berg return rr; 10628318d78aSJohannes Berg } 10638318d78aSJohannes Berg 10640c7dc45dSLuis R. Rodriguez if (!band_rule_found) 1065361c9c8bSJohannes Berg return ERR_PTR(-ERANGE); 10660c7dc45dSLuis R. Rodriguez 1067361c9c8bSJohannes Berg return ERR_PTR(-EINVAL); 1068b2e1b302SLuis R. Rodriguez } 1069b2e1b302SLuis R. Rodriguez 10708de1c63bSJohannes Berg static const struct ieee80211_reg_rule * 10718de1c63bSJohannes Berg __freq_reg_info(struct wiphy *wiphy, u32 center_freq, u32 min_bw) 10724edd5698SMatthias May { 10734edd5698SMatthias May const struct ieee80211_regdomain *regd = reg_get_regdomain(wiphy); 10744edd5698SMatthias May const struct ieee80211_reg_rule *reg_rule = NULL; 10754edd5698SMatthias May u32 bw; 10764edd5698SMatthias May 10774edd5698SMatthias May for (bw = MHZ_TO_KHZ(20); bw >= min_bw; bw = bw / 2) { 107849172874SMichal Sojka reg_rule = freq_reg_info_regd(center_freq, regd, bw); 10794edd5698SMatthias May if (!IS_ERR(reg_rule)) 10804edd5698SMatthias May return reg_rule; 10814edd5698SMatthias May } 10824edd5698SMatthias May 10834edd5698SMatthias May return reg_rule; 10844edd5698SMatthias May } 10854edd5698SMatthias May 1086361c9c8bSJohannes Berg const struct ieee80211_reg_rule *freq_reg_info(struct wiphy *wiphy, 1087361c9c8bSJohannes Berg u32 center_freq) 10881fa25e41SLuis R. Rodriguez { 10894edd5698SMatthias May return __freq_reg_info(wiphy, center_freq, MHZ_TO_KHZ(20)); 10901fa25e41SLuis R. Rodriguez } 10914f366c5dSJohn W. Linville EXPORT_SYMBOL(freq_reg_info); 1092b2e1b302SLuis R. Rodriguez 1093034c6d6eSLuis R. Rodriguez const char *reg_initiator_name(enum nl80211_reg_initiator initiator) 1094926a0a09SLuis R. Rodriguez { 1095926a0a09SLuis R. Rodriguez switch (initiator) { 1096926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_CORE: 1097034c6d6eSLuis R. Rodriguez return "core"; 1098926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_USER: 1099034c6d6eSLuis R. Rodriguez return "user"; 1100926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_DRIVER: 1101034c6d6eSLuis R. Rodriguez return "driver"; 1102926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_COUNTRY_IE: 1103034c6d6eSLuis R. Rodriguez return "country IE"; 1104926a0a09SLuis R. Rodriguez default: 1105926a0a09SLuis R. Rodriguez WARN_ON(1); 1106034c6d6eSLuis R. Rodriguez return "bug"; 1107926a0a09SLuis R. Rodriguez } 1108926a0a09SLuis R. Rodriguez } 1109034c6d6eSLuis R. Rodriguez EXPORT_SYMBOL(reg_initiator_name); 1110e702d3cfSLuis R. Rodriguez 11111aeb135fSMichal Sojka static uint32_t reg_rule_to_chan_bw_flags(const struct ieee80211_regdomain *regd, 11121aeb135fSMichal Sojka const struct ieee80211_reg_rule *reg_rule, 11131aeb135fSMichal Sojka const struct ieee80211_channel *chan) 11141aeb135fSMichal Sojka { 11151aeb135fSMichal Sojka const struct ieee80211_freq_range *freq_range = NULL; 11161aeb135fSMichal Sojka u32 max_bandwidth_khz, bw_flags = 0; 11171aeb135fSMichal Sojka 11181aeb135fSMichal Sojka freq_range = ®_rule->freq_range; 11191aeb135fSMichal Sojka 11201aeb135fSMichal Sojka max_bandwidth_khz = freq_range->max_bandwidth_khz; 11211aeb135fSMichal Sojka /* Check if auto calculation requested */ 11221aeb135fSMichal Sojka if (reg_rule->flags & NL80211_RRF_AUTO_BW) 11231aeb135fSMichal Sojka max_bandwidth_khz = reg_get_max_bandwidth(regd, reg_rule); 11241aeb135fSMichal Sojka 11251aeb135fSMichal Sojka /* If we get a reg_rule we can assume that at least 5Mhz fit */ 11264787cfa0SRafał Miłecki if (!cfg80211_does_bw_fit_range(freq_range, 11274787cfa0SRafał Miłecki MHZ_TO_KHZ(chan->center_freq), 11281aeb135fSMichal Sojka MHZ_TO_KHZ(10))) 11291aeb135fSMichal Sojka bw_flags |= IEEE80211_CHAN_NO_10MHZ; 11304787cfa0SRafał Miłecki if (!cfg80211_does_bw_fit_range(freq_range, 11314787cfa0SRafał Miłecki MHZ_TO_KHZ(chan->center_freq), 11321aeb135fSMichal Sojka MHZ_TO_KHZ(20))) 11331aeb135fSMichal Sojka bw_flags |= IEEE80211_CHAN_NO_20MHZ; 11341aeb135fSMichal Sojka 11351aeb135fSMichal Sojka if (max_bandwidth_khz < MHZ_TO_KHZ(10)) 11361aeb135fSMichal Sojka bw_flags |= IEEE80211_CHAN_NO_10MHZ; 11371aeb135fSMichal Sojka if (max_bandwidth_khz < MHZ_TO_KHZ(20)) 11381aeb135fSMichal Sojka bw_flags |= IEEE80211_CHAN_NO_20MHZ; 11391aeb135fSMichal Sojka if (max_bandwidth_khz < MHZ_TO_KHZ(40)) 11401aeb135fSMichal Sojka bw_flags |= IEEE80211_CHAN_NO_HT40; 11411aeb135fSMichal Sojka if (max_bandwidth_khz < MHZ_TO_KHZ(80)) 11421aeb135fSMichal Sojka bw_flags |= IEEE80211_CHAN_NO_80MHZ; 11431aeb135fSMichal Sojka if (max_bandwidth_khz < MHZ_TO_KHZ(160)) 11441aeb135fSMichal Sojka bw_flags |= IEEE80211_CHAN_NO_160MHZ; 11451aeb135fSMichal Sojka return bw_flags; 11461aeb135fSMichal Sojka } 11471aeb135fSMichal Sojka 1148e33e2241SJohannes Berg /* 1149e33e2241SJohannes Berg * Note that right now we assume the desired channel bandwidth 1150e33e2241SJohannes Berg * is always 20 MHz for each individual channel (HT40 uses 20 MHz 1151e33e2241SJohannes Berg * per channel, the primary and the extension channel). 1152038659e7SLuis R. Rodriguez */ 11537ca43d03SLuis R. Rodriguez static void handle_channel(struct wiphy *wiphy, 11547ca43d03SLuis R. Rodriguez enum nl80211_reg_initiator initiator, 1155fdc9d7b2SJohannes Berg struct ieee80211_channel *chan) 1156b2e1b302SLuis R. Rodriguez { 1157038659e7SLuis R. Rodriguez u32 flags, bw_flags = 0; 1158b2e1b302SLuis R. Rodriguez const struct ieee80211_reg_rule *reg_rule = NULL; 1159b2e1b302SLuis R. Rodriguez const struct ieee80211_power_rule *power_rule = NULL; 1160fe33eb39SLuis R. Rodriguez struct wiphy *request_wiphy = NULL; 1161c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 116297524820SJanusz Dziedzic const struct ieee80211_regdomain *regd; 1163a92a3ce7SLuis R. Rodriguez 1164c492db37SJohannes Berg request_wiphy = wiphy_idx_to_wiphy(lr->wiphy_idx); 1165a92a3ce7SLuis R. Rodriguez 1166a92a3ce7SLuis R. Rodriguez flags = chan->orig_flags; 1167b2e1b302SLuis R. Rodriguez 1168361c9c8bSJohannes Berg reg_rule = freq_reg_info(wiphy, MHZ_TO_KHZ(chan->center_freq)); 1169361c9c8bSJohannes Berg if (IS_ERR(reg_rule)) { 1170ca4ffe8fSLuis R. Rodriguez /* 1171ca4ffe8fSLuis R. Rodriguez * We will disable all channels that do not match our 117225985edcSLucas De Marchi * received regulatory rule unless the hint is coming 1173ca4ffe8fSLuis R. Rodriguez * from a Country IE and the Country IE had no information 1174ca4ffe8fSLuis R. Rodriguez * about a band. The IEEE 802.11 spec allows for an AP 1175ca4ffe8fSLuis R. Rodriguez * to send only a subset of the regulatory rules allowed, 1176ca4ffe8fSLuis R. Rodriguez * so an AP in the US that only supports 2.4 GHz may only send 1177ca4ffe8fSLuis R. Rodriguez * a country IE with information for the 2.4 GHz band 1178ca4ffe8fSLuis R. Rodriguez * while 5 GHz is still supported. 1179ca4ffe8fSLuis R. Rodriguez */ 1180ca4ffe8fSLuis R. Rodriguez if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE && 1181361c9c8bSJohannes Berg PTR_ERR(reg_rule) == -ERANGE) 11828318d78aSJohannes Berg return; 11838318d78aSJohannes Berg 1184cc493e4fSLuis R. Rodriguez if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER && 1185cc493e4fSLuis R. Rodriguez request_wiphy && request_wiphy == wiphy && 1186a2f73b6cSLuis R. Rodriguez request_wiphy->regulatory_flags & REGULATORY_STRICT_REG) { 1187c799ba6eSJohannes Berg pr_debug("Disabling freq %d MHz for good\n", 1188cc493e4fSLuis R. Rodriguez chan->center_freq); 1189cc493e4fSLuis R. Rodriguez chan->orig_flags |= IEEE80211_CHAN_DISABLED; 1190cc493e4fSLuis R. Rodriguez chan->flags = chan->orig_flags; 1191cc493e4fSLuis R. Rodriguez } else { 1192c799ba6eSJohannes Berg pr_debug("Disabling freq %d MHz\n", 1193cc493e4fSLuis R. Rodriguez chan->center_freq); 1194990de49fSJohannes Berg chan->flags |= IEEE80211_CHAN_DISABLED; 1195cc493e4fSLuis R. Rodriguez } 1196ca4ffe8fSLuis R. Rodriguez return; 1197ca4ffe8fSLuis R. Rodriguez } 1198ca4ffe8fSLuis R. Rodriguez 1199b0dfd2eaSJanusz Dziedzic regd = reg_get_regdomain(wiphy); 1200e702d3cfSLuis R. Rodriguez 1201b2e1b302SLuis R. Rodriguez power_rule = ®_rule->power_rule; 12021aeb135fSMichal Sojka bw_flags = reg_rule_to_chan_bw_flags(regd, reg_rule, chan); 1203b2e1b302SLuis R. Rodriguez 1204c492db37SJohannes Berg if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER && 1205806a9e39SLuis R. Rodriguez request_wiphy && request_wiphy == wiphy && 1206a2f73b6cSLuis R. Rodriguez request_wiphy->regulatory_flags & REGULATORY_STRICT_REG) { 1207fb1fc7adSLuis R. Rodriguez /* 120825985edcSLucas De Marchi * This guarantees the driver's requested regulatory domain 1209f976376dSLuis R. Rodriguez * will always be used as a base for further regulatory 1210fb1fc7adSLuis R. Rodriguez * settings 1211fb1fc7adSLuis R. Rodriguez */ 1212f976376dSLuis R. Rodriguez chan->flags = chan->orig_flags = 1213038659e7SLuis R. Rodriguez map_regdom_flags(reg_rule->flags) | bw_flags; 1214f976376dSLuis R. Rodriguez chan->max_antenna_gain = chan->orig_mag = 1215f976376dSLuis R. Rodriguez (int) MBI_TO_DBI(power_rule->max_antenna_gain); 1216279f0f55SFelix Fietkau chan->max_reg_power = chan->max_power = chan->orig_mpwr = 1217f976376dSLuis R. Rodriguez (int) MBM_TO_DBM(power_rule->max_eirp); 12184f267c11SJanusz Dziedzic 12194f267c11SJanusz Dziedzic if (chan->flags & IEEE80211_CHAN_RADAR) { 12204f267c11SJanusz Dziedzic chan->dfs_cac_ms = IEEE80211_DFS_MIN_CAC_TIME_MS; 12214f267c11SJanusz Dziedzic if (reg_rule->dfs_cac_ms) 12224f267c11SJanusz Dziedzic chan->dfs_cac_ms = reg_rule->dfs_cac_ms; 12234f267c11SJanusz Dziedzic } 12244f267c11SJanusz Dziedzic 1225f976376dSLuis R. Rodriguez return; 1226f976376dSLuis R. Rodriguez } 1227f976376dSLuis R. Rodriguez 122804f39047SSimon Wunderlich chan->dfs_state = NL80211_DFS_USABLE; 122904f39047SSimon Wunderlich chan->dfs_state_entered = jiffies; 123004f39047SSimon Wunderlich 1231aa3d7eefSRajkumar Manoharan chan->beacon_found = false; 1232038659e7SLuis R. Rodriguez chan->flags = flags | bw_flags | map_regdom_flags(reg_rule->flags); 12331a919318SJohannes Berg chan->max_antenna_gain = 12341a919318SJohannes Berg min_t(int, chan->orig_mag, 12351a919318SJohannes Berg MBI_TO_DBI(power_rule->max_antenna_gain)); 1236eccc068eSHong Wu chan->max_reg_power = (int) MBM_TO_DBM(power_rule->max_eirp); 1237089027e5SJanusz Dziedzic 1238089027e5SJanusz Dziedzic if (chan->flags & IEEE80211_CHAN_RADAR) { 1239089027e5SJanusz Dziedzic if (reg_rule->dfs_cac_ms) 1240089027e5SJanusz Dziedzic chan->dfs_cac_ms = reg_rule->dfs_cac_ms; 1241089027e5SJanusz Dziedzic else 1242089027e5SJanusz Dziedzic chan->dfs_cac_ms = IEEE80211_DFS_MIN_CAC_TIME_MS; 1243089027e5SJanusz Dziedzic } 1244089027e5SJanusz Dziedzic 12455e31fc08SStanislaw Gruszka if (chan->orig_mpwr) { 12465e31fc08SStanislaw Gruszka /* 1247a09a85a0SLuis R. Rodriguez * Devices that use REGULATORY_COUNTRY_IE_FOLLOW_POWER 1248a09a85a0SLuis R. Rodriguez * will always follow the passed country IE power settings. 12495e31fc08SStanislaw Gruszka */ 12505e31fc08SStanislaw Gruszka if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE && 1251a09a85a0SLuis R. Rodriguez wiphy->regulatory_flags & REGULATORY_COUNTRY_IE_FOLLOW_POWER) 12525e31fc08SStanislaw Gruszka chan->max_power = chan->max_reg_power; 12535e31fc08SStanislaw Gruszka else 12545e31fc08SStanislaw Gruszka chan->max_power = min(chan->orig_mpwr, 12555e31fc08SStanislaw Gruszka chan->max_reg_power); 12565e31fc08SStanislaw Gruszka } else 12575e31fc08SStanislaw Gruszka chan->max_power = chan->max_reg_power; 12588318d78aSJohannes Berg } 12598318d78aSJohannes Berg 12607ca43d03SLuis R. Rodriguez static void handle_band(struct wiphy *wiphy, 1261fdc9d7b2SJohannes Berg enum nl80211_reg_initiator initiator, 1262fdc9d7b2SJohannes Berg struct ieee80211_supported_band *sband) 12638318d78aSJohannes Berg { 1264a92a3ce7SLuis R. Rodriguez unsigned int i; 1265a92a3ce7SLuis R. Rodriguez 1266fdc9d7b2SJohannes Berg if (!sband) 1267fdc9d7b2SJohannes Berg return; 12688318d78aSJohannes Berg 12698318d78aSJohannes Berg for (i = 0; i < sband->n_channels; i++) 1270fdc9d7b2SJohannes Berg handle_channel(wiphy, initiator, &sband->channels[i]); 12718318d78aSJohannes Berg } 12728318d78aSJohannes Berg 127357b5ce07SLuis R. Rodriguez static bool reg_request_cell_base(struct regulatory_request *request) 127457b5ce07SLuis R. Rodriguez { 127557b5ce07SLuis R. Rodriguez if (request->initiator != NL80211_REGDOM_SET_BY_USER) 127657b5ce07SLuis R. Rodriguez return false; 12771a919318SJohannes Berg return request->user_reg_hint_type == NL80211_USER_REG_HINT_CELL_BASE; 127857b5ce07SLuis R. Rodriguez } 127957b5ce07SLuis R. Rodriguez 128057b5ce07SLuis R. Rodriguez bool reg_last_request_cell_base(void) 128157b5ce07SLuis R. Rodriguez { 128238fd2143SJohannes Berg return reg_request_cell_base(get_last_request()); 128357b5ce07SLuis R. Rodriguez } 128457b5ce07SLuis R. Rodriguez 128594fc661fSIlan Peer #ifdef CONFIG_CFG80211_REG_CELLULAR_HINTS 128657b5ce07SLuis R. Rodriguez /* Core specific check */ 12872f92212bSJohannes Berg static enum reg_request_treatment 12882f92212bSJohannes Berg reg_ignore_cell_hint(struct regulatory_request *pending_request) 128957b5ce07SLuis R. Rodriguez { 1290c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 129157b5ce07SLuis R. Rodriguez 129257b5ce07SLuis R. Rodriguez if (!reg_num_devs_support_basehint) 12932f92212bSJohannes Berg return REG_REQ_IGNORE; 129457b5ce07SLuis R. Rodriguez 1295c492db37SJohannes Berg if (reg_request_cell_base(lr) && 12961a919318SJohannes Berg !regdom_changes(pending_request->alpha2)) 12972f92212bSJohannes Berg return REG_REQ_ALREADY_SET; 12981a919318SJohannes Berg 12992f92212bSJohannes Berg return REG_REQ_OK; 130057b5ce07SLuis R. Rodriguez } 130157b5ce07SLuis R. Rodriguez 130257b5ce07SLuis R. Rodriguez /* Device specific check */ 130357b5ce07SLuis R. Rodriguez static bool reg_dev_ignore_cell_hint(struct wiphy *wiphy) 130457b5ce07SLuis R. Rodriguez { 13051a919318SJohannes Berg return !(wiphy->features & NL80211_FEATURE_CELL_BASE_REG_HINTS); 130657b5ce07SLuis R. Rodriguez } 130757b5ce07SLuis R. Rodriguez #else 1308a515de66SJohannes Berg static enum reg_request_treatment 1309a515de66SJohannes Berg reg_ignore_cell_hint(struct regulatory_request *pending_request) 131057b5ce07SLuis R. Rodriguez { 13112f92212bSJohannes Berg return REG_REQ_IGNORE; 131257b5ce07SLuis R. Rodriguez } 13131a919318SJohannes Berg 13141a919318SJohannes Berg static bool reg_dev_ignore_cell_hint(struct wiphy *wiphy) 131557b5ce07SLuis R. Rodriguez { 131657b5ce07SLuis R. Rodriguez return true; 131757b5ce07SLuis R. Rodriguez } 131857b5ce07SLuis R. Rodriguez #endif 131957b5ce07SLuis R. Rodriguez 1320fa1fb9cbSLuis R. Rodriguez static bool wiphy_strict_alpha2_regd(struct wiphy *wiphy) 1321fa1fb9cbSLuis R. Rodriguez { 1322a2f73b6cSLuis R. Rodriguez if (wiphy->regulatory_flags & REGULATORY_STRICT_REG && 1323a2f73b6cSLuis R. Rodriguez !(wiphy->regulatory_flags & REGULATORY_CUSTOM_REG)) 1324fa1fb9cbSLuis R. Rodriguez return true; 1325fa1fb9cbSLuis R. Rodriguez return false; 1326fa1fb9cbSLuis R. Rodriguez } 132757b5ce07SLuis R. Rodriguez 13287db90f4aSLuis R. Rodriguez static bool ignore_reg_update(struct wiphy *wiphy, 13297db90f4aSLuis R. Rodriguez enum nl80211_reg_initiator initiator) 133014b9815aSLuis R. Rodriguez { 1331c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 1332c492db37SJohannes Berg 1333b0d7aa59SJonathan Doron if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED) 1334b0d7aa59SJonathan Doron return true; 1335b0d7aa59SJonathan Doron 1336c492db37SJohannes Berg if (!lr) { 1337c799ba6eSJohannes Berg pr_debug("Ignoring regulatory request set by %s since last_request is not set\n", 1338926a0a09SLuis R. Rodriguez reg_initiator_name(initiator)); 133914b9815aSLuis R. Rodriguez return true; 1340926a0a09SLuis R. Rodriguez } 1341926a0a09SLuis R. Rodriguez 13427db90f4aSLuis R. Rodriguez if (initiator == NL80211_REGDOM_SET_BY_CORE && 1343a2f73b6cSLuis R. Rodriguez wiphy->regulatory_flags & REGULATORY_CUSTOM_REG) { 1344c799ba6eSJohannes Berg pr_debug("Ignoring regulatory request set by %s since the driver uses its own custom regulatory domain\n", 1345926a0a09SLuis R. Rodriguez reg_initiator_name(initiator)); 134614b9815aSLuis R. Rodriguez return true; 1347926a0a09SLuis R. Rodriguez } 1348926a0a09SLuis R. Rodriguez 1349fb1fc7adSLuis R. Rodriguez /* 1350fb1fc7adSLuis R. Rodriguez * wiphy->regd will be set once the device has its own 1351fb1fc7adSLuis R. Rodriguez * desired regulatory domain set 1352fb1fc7adSLuis R. Rodriguez */ 1353fa1fb9cbSLuis R. Rodriguez if (wiphy_strict_alpha2_regd(wiphy) && !wiphy->regd && 1354749b527bSLuis R. Rodriguez initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE && 1355c492db37SJohannes Berg !is_world_regdom(lr->alpha2)) { 1356c799ba6eSJohannes Berg pr_debug("Ignoring regulatory request set by %s since the driver requires its own regulatory domain to be set first\n", 1357926a0a09SLuis R. Rodriguez reg_initiator_name(initiator)); 135814b9815aSLuis R. Rodriguez return true; 1359926a0a09SLuis R. Rodriguez } 1360926a0a09SLuis R. Rodriguez 1361c492db37SJohannes Berg if (reg_request_cell_base(lr)) 136257b5ce07SLuis R. Rodriguez return reg_dev_ignore_cell_hint(wiphy); 136357b5ce07SLuis R. Rodriguez 136414b9815aSLuis R. Rodriguez return false; 136514b9815aSLuis R. Rodriguez } 136614b9815aSLuis R. Rodriguez 13673195e489SLuis R. Rodriguez static bool reg_is_world_roaming(struct wiphy *wiphy) 13683195e489SLuis R. Rodriguez { 13693195e489SLuis R. Rodriguez const struct ieee80211_regdomain *cr = get_cfg80211_regdom(); 13703195e489SLuis R. Rodriguez const struct ieee80211_regdomain *wr = get_wiphy_regdom(wiphy); 13713195e489SLuis R. Rodriguez struct regulatory_request *lr = get_last_request(); 13723195e489SLuis R. Rodriguez 13733195e489SLuis R. Rodriguez if (is_world_regdom(cr->alpha2) || (wr && is_world_regdom(wr->alpha2))) 13743195e489SLuis R. Rodriguez return true; 13753195e489SLuis R. Rodriguez 13763195e489SLuis R. Rodriguez if (lr && lr->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE && 1377a2f73b6cSLuis R. Rodriguez wiphy->regulatory_flags & REGULATORY_CUSTOM_REG) 13783195e489SLuis R. Rodriguez return true; 13793195e489SLuis R. Rodriguez 13803195e489SLuis R. Rodriguez return false; 13813195e489SLuis R. Rodriguez } 13823195e489SLuis R. Rodriguez 13831a919318SJohannes Berg static void handle_reg_beacon(struct wiphy *wiphy, unsigned int chan_idx, 1384e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon) 1385e38f8a7aSLuis R. Rodriguez { 1386e38f8a7aSLuis R. Rodriguez struct ieee80211_supported_band *sband; 1387e38f8a7aSLuis R. Rodriguez struct ieee80211_channel *chan; 13886bad8766SLuis R. Rodriguez bool channel_changed = false; 13896bad8766SLuis R. Rodriguez struct ieee80211_channel chan_before; 1390e38f8a7aSLuis R. Rodriguez 1391e38f8a7aSLuis R. Rodriguez sband = wiphy->bands[reg_beacon->chan.band]; 1392e38f8a7aSLuis R. Rodriguez chan = &sband->channels[chan_idx]; 1393e38f8a7aSLuis R. Rodriguez 1394e38f8a7aSLuis R. Rodriguez if (likely(chan->center_freq != reg_beacon->chan.center_freq)) 1395e38f8a7aSLuis R. Rodriguez return; 1396e38f8a7aSLuis R. Rodriguez 13976bad8766SLuis R. Rodriguez if (chan->beacon_found) 13986bad8766SLuis R. Rodriguez return; 13996bad8766SLuis R. Rodriguez 14006bad8766SLuis R. Rodriguez chan->beacon_found = true; 14016bad8766SLuis R. Rodriguez 14020f500a5fSLuis R. Rodriguez if (!reg_is_world_roaming(wiphy)) 14030f500a5fSLuis R. Rodriguez return; 14040f500a5fSLuis R. Rodriguez 1405a2f73b6cSLuis R. Rodriguez if (wiphy->regulatory_flags & REGULATORY_DISABLE_BEACON_HINTS) 140637184244SLuis R. Rodriguez return; 140737184244SLuis R. Rodriguez 14086bad8766SLuis R. Rodriguez chan_before.center_freq = chan->center_freq; 14096bad8766SLuis R. Rodriguez chan_before.flags = chan->flags; 14106bad8766SLuis R. Rodriguez 14118fe02e16SLuis R. Rodriguez if (chan->flags & IEEE80211_CHAN_NO_IR) { 14128fe02e16SLuis R. Rodriguez chan->flags &= ~IEEE80211_CHAN_NO_IR; 14136bad8766SLuis R. Rodriguez channel_changed = true; 1414e38f8a7aSLuis R. Rodriguez } 1415e38f8a7aSLuis R. Rodriguez 14166bad8766SLuis R. Rodriguez if (channel_changed) 14176bad8766SLuis R. Rodriguez nl80211_send_beacon_hint_event(wiphy, &chan_before, chan); 1418e38f8a7aSLuis R. Rodriguez } 1419e38f8a7aSLuis R. Rodriguez 1420e38f8a7aSLuis R. Rodriguez /* 1421e38f8a7aSLuis R. Rodriguez * Called when a scan on a wiphy finds a beacon on 1422e38f8a7aSLuis R. Rodriguez * new channel 1423e38f8a7aSLuis R. Rodriguez */ 1424e38f8a7aSLuis R. Rodriguez static void wiphy_update_new_beacon(struct wiphy *wiphy, 1425e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon) 1426e38f8a7aSLuis R. Rodriguez { 1427e38f8a7aSLuis R. Rodriguez unsigned int i; 1428e38f8a7aSLuis R. Rodriguez struct ieee80211_supported_band *sband; 1429e38f8a7aSLuis R. Rodriguez 1430e38f8a7aSLuis R. Rodriguez if (!wiphy->bands[reg_beacon->chan.band]) 1431e38f8a7aSLuis R. Rodriguez return; 1432e38f8a7aSLuis R. Rodriguez 1433e38f8a7aSLuis R. Rodriguez sband = wiphy->bands[reg_beacon->chan.band]; 1434e38f8a7aSLuis R. Rodriguez 1435e38f8a7aSLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 1436e38f8a7aSLuis R. Rodriguez handle_reg_beacon(wiphy, i, reg_beacon); 1437e38f8a7aSLuis R. Rodriguez } 1438e38f8a7aSLuis R. Rodriguez 1439e38f8a7aSLuis R. Rodriguez /* 1440e38f8a7aSLuis R. Rodriguez * Called upon reg changes or a new wiphy is added 1441e38f8a7aSLuis R. Rodriguez */ 1442e38f8a7aSLuis R. Rodriguez static void wiphy_update_beacon_reg(struct wiphy *wiphy) 1443e38f8a7aSLuis R. Rodriguez { 1444e38f8a7aSLuis R. Rodriguez unsigned int i; 1445e38f8a7aSLuis R. Rodriguez struct ieee80211_supported_band *sband; 1446e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon; 1447e38f8a7aSLuis R. Rodriguez 1448e38f8a7aSLuis R. Rodriguez list_for_each_entry(reg_beacon, ®_beacon_list, list) { 1449e38f8a7aSLuis R. Rodriguez if (!wiphy->bands[reg_beacon->chan.band]) 1450e38f8a7aSLuis R. Rodriguez continue; 1451e38f8a7aSLuis R. Rodriguez sband = wiphy->bands[reg_beacon->chan.band]; 1452e38f8a7aSLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 1453e38f8a7aSLuis R. Rodriguez handle_reg_beacon(wiphy, i, reg_beacon); 1454e38f8a7aSLuis R. Rodriguez } 1455e38f8a7aSLuis R. Rodriguez } 1456e38f8a7aSLuis R. Rodriguez 1457e38f8a7aSLuis R. Rodriguez /* Reap the advantages of previously found beacons */ 1458e38f8a7aSLuis R. Rodriguez static void reg_process_beacons(struct wiphy *wiphy) 1459e38f8a7aSLuis R. Rodriguez { 1460b1ed8dddSLuis R. Rodriguez /* 1461b1ed8dddSLuis R. Rodriguez * Means we are just firing up cfg80211, so no beacons would 1462b1ed8dddSLuis R. Rodriguez * have been processed yet. 1463b1ed8dddSLuis R. Rodriguez */ 1464b1ed8dddSLuis R. Rodriguez if (!last_request) 1465b1ed8dddSLuis R. Rodriguez return; 1466e38f8a7aSLuis R. Rodriguez wiphy_update_beacon_reg(wiphy); 1467e38f8a7aSLuis R. Rodriguez } 1468e38f8a7aSLuis R. Rodriguez 14691a919318SJohannes Berg static bool is_ht40_allowed(struct ieee80211_channel *chan) 1470038659e7SLuis R. Rodriguez { 1471038659e7SLuis R. Rodriguez if (!chan) 1472038659e7SLuis R. Rodriguez return false; 14731a919318SJohannes Berg if (chan->flags & IEEE80211_CHAN_DISABLED) 14741a919318SJohannes Berg return false; 14751a919318SJohannes Berg /* This would happen when regulatory rules disallow HT40 completely */ 147655b183adSFelix Fietkau if ((chan->flags & IEEE80211_CHAN_NO_HT40) == IEEE80211_CHAN_NO_HT40) 147755b183adSFelix Fietkau return false; 147855b183adSFelix Fietkau return true; 1479038659e7SLuis R. Rodriguez } 1480038659e7SLuis R. Rodriguez 1481038659e7SLuis R. Rodriguez static void reg_process_ht_flags_channel(struct wiphy *wiphy, 1482fdc9d7b2SJohannes Berg struct ieee80211_channel *channel) 1483038659e7SLuis R. Rodriguez { 1484fdc9d7b2SJohannes Berg struct ieee80211_supported_band *sband = wiphy->bands[channel->band]; 1485038659e7SLuis R. Rodriguez struct ieee80211_channel *channel_before = NULL, *channel_after = NULL; 1486038659e7SLuis R. Rodriguez unsigned int i; 1487038659e7SLuis R. Rodriguez 14881a919318SJohannes Berg if (!is_ht40_allowed(channel)) { 1489038659e7SLuis R. Rodriguez channel->flags |= IEEE80211_CHAN_NO_HT40; 1490038659e7SLuis R. Rodriguez return; 1491038659e7SLuis R. Rodriguez } 1492038659e7SLuis R. Rodriguez 1493038659e7SLuis R. Rodriguez /* 1494038659e7SLuis R. Rodriguez * We need to ensure the extension channels exist to 1495038659e7SLuis R. Rodriguez * be able to use HT40- or HT40+, this finds them (or not) 1496038659e7SLuis R. Rodriguez */ 1497038659e7SLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) { 1498038659e7SLuis R. Rodriguez struct ieee80211_channel *c = &sband->channels[i]; 14991a919318SJohannes Berg 1500038659e7SLuis R. Rodriguez if (c->center_freq == (channel->center_freq - 20)) 1501038659e7SLuis R. Rodriguez channel_before = c; 1502038659e7SLuis R. Rodriguez if (c->center_freq == (channel->center_freq + 20)) 1503038659e7SLuis R. Rodriguez channel_after = c; 1504038659e7SLuis R. Rodriguez } 1505038659e7SLuis R. Rodriguez 1506038659e7SLuis R. Rodriguez /* 1507038659e7SLuis R. Rodriguez * Please note that this assumes target bandwidth is 20 MHz, 1508038659e7SLuis R. Rodriguez * if that ever changes we also need to change the below logic 1509038659e7SLuis R. Rodriguez * to include that as well. 1510038659e7SLuis R. Rodriguez */ 15111a919318SJohannes Berg if (!is_ht40_allowed(channel_before)) 1512689da1b3SLuis R. Rodriguez channel->flags |= IEEE80211_CHAN_NO_HT40MINUS; 1513038659e7SLuis R. Rodriguez else 1514689da1b3SLuis R. Rodriguez channel->flags &= ~IEEE80211_CHAN_NO_HT40MINUS; 1515038659e7SLuis R. Rodriguez 15161a919318SJohannes Berg if (!is_ht40_allowed(channel_after)) 1517689da1b3SLuis R. Rodriguez channel->flags |= IEEE80211_CHAN_NO_HT40PLUS; 1518038659e7SLuis R. Rodriguez else 1519689da1b3SLuis R. Rodriguez channel->flags &= ~IEEE80211_CHAN_NO_HT40PLUS; 1520038659e7SLuis R. Rodriguez } 1521038659e7SLuis R. Rodriguez 1522038659e7SLuis R. Rodriguez static void reg_process_ht_flags_band(struct wiphy *wiphy, 1523fdc9d7b2SJohannes Berg struct ieee80211_supported_band *sband) 1524038659e7SLuis R. Rodriguez { 1525038659e7SLuis R. Rodriguez unsigned int i; 1526038659e7SLuis R. Rodriguez 1527fdc9d7b2SJohannes Berg if (!sband) 1528fdc9d7b2SJohannes Berg return; 1529038659e7SLuis R. Rodriguez 1530038659e7SLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 1531fdc9d7b2SJohannes Berg reg_process_ht_flags_channel(wiphy, &sband->channels[i]); 1532038659e7SLuis R. Rodriguez } 1533038659e7SLuis R. Rodriguez 1534038659e7SLuis R. Rodriguez static void reg_process_ht_flags(struct wiphy *wiphy) 1535038659e7SLuis R. Rodriguez { 153657fbcce3SJohannes Berg enum nl80211_band band; 1537038659e7SLuis R. Rodriguez 1538038659e7SLuis R. Rodriguez if (!wiphy) 1539038659e7SLuis R. Rodriguez return; 1540038659e7SLuis R. Rodriguez 154157fbcce3SJohannes Berg for (band = 0; band < NUM_NL80211_BANDS; band++) 1542fdc9d7b2SJohannes Berg reg_process_ht_flags_band(wiphy, wiphy->bands[band]); 1543038659e7SLuis R. Rodriguez } 1544038659e7SLuis R. Rodriguez 15450e3802dbSLuis R. Rodriguez static void reg_call_notifier(struct wiphy *wiphy, 15460e3802dbSLuis R. Rodriguez struct regulatory_request *request) 15470e3802dbSLuis R. Rodriguez { 15480e3802dbSLuis R. Rodriguez if (wiphy->reg_notifier) 15490e3802dbSLuis R. Rodriguez wiphy->reg_notifier(wiphy, request); 15500e3802dbSLuis R. Rodriguez } 15510e3802dbSLuis R. Rodriguez 1552ad932f04SArik Nemtsov static bool reg_wdev_chan_valid(struct wiphy *wiphy, struct wireless_dev *wdev) 1553ad932f04SArik Nemtsov { 1554ad932f04SArik Nemtsov struct cfg80211_chan_def chandef; 1555ad932f04SArik Nemtsov struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); 155620658702SArik Nemtsov enum nl80211_iftype iftype; 1557ad932f04SArik Nemtsov 1558ad932f04SArik Nemtsov wdev_lock(wdev); 155920658702SArik Nemtsov iftype = wdev->iftype; 1560ad932f04SArik Nemtsov 156120658702SArik Nemtsov /* make sure the interface is active */ 1562ad932f04SArik Nemtsov if (!wdev->netdev || !netif_running(wdev->netdev)) 156320658702SArik Nemtsov goto wdev_inactive_unlock; 1564ad932f04SArik Nemtsov 156520658702SArik Nemtsov switch (iftype) { 1566ad932f04SArik Nemtsov case NL80211_IFTYPE_AP: 1567ad932f04SArik Nemtsov case NL80211_IFTYPE_P2P_GO: 1568ad932f04SArik Nemtsov if (!wdev->beacon_interval) 156920658702SArik Nemtsov goto wdev_inactive_unlock; 157020658702SArik Nemtsov chandef = wdev->chandef; 1571ad932f04SArik Nemtsov break; 1572185076d6SArik Nemtsov case NL80211_IFTYPE_ADHOC: 1573185076d6SArik Nemtsov if (!wdev->ssid_len) 157420658702SArik Nemtsov goto wdev_inactive_unlock; 157520658702SArik Nemtsov chandef = wdev->chandef; 1576185076d6SArik Nemtsov break; 1577ad932f04SArik Nemtsov case NL80211_IFTYPE_STATION: 1578ad932f04SArik Nemtsov case NL80211_IFTYPE_P2P_CLIENT: 1579ad932f04SArik Nemtsov if (!wdev->current_bss || 1580ad932f04SArik Nemtsov !wdev->current_bss->pub.channel) 158120658702SArik Nemtsov goto wdev_inactive_unlock; 1582ad932f04SArik Nemtsov 158320658702SArik Nemtsov if (!rdev->ops->get_channel || 158420658702SArik Nemtsov rdev_get_channel(rdev, wdev, &chandef)) 158520658702SArik Nemtsov cfg80211_chandef_create(&chandef, 158620658702SArik Nemtsov wdev->current_bss->pub.channel, 158720658702SArik Nemtsov NL80211_CHAN_NO_HT); 1588ad932f04SArik Nemtsov break; 1589ad932f04SArik Nemtsov case NL80211_IFTYPE_MONITOR: 1590ad932f04SArik Nemtsov case NL80211_IFTYPE_AP_VLAN: 1591ad932f04SArik Nemtsov case NL80211_IFTYPE_P2P_DEVICE: 1592ad932f04SArik Nemtsov /* no enforcement required */ 1593ad932f04SArik Nemtsov break; 1594ad932f04SArik Nemtsov default: 1595ad932f04SArik Nemtsov /* others not implemented for now */ 1596ad932f04SArik Nemtsov WARN_ON(1); 1597ad932f04SArik Nemtsov break; 1598ad932f04SArik Nemtsov } 1599ad932f04SArik Nemtsov 1600ad932f04SArik Nemtsov wdev_unlock(wdev); 160120658702SArik Nemtsov 160220658702SArik Nemtsov switch (iftype) { 160320658702SArik Nemtsov case NL80211_IFTYPE_AP: 160420658702SArik Nemtsov case NL80211_IFTYPE_P2P_GO: 160520658702SArik Nemtsov case NL80211_IFTYPE_ADHOC: 1606923b352fSArik Nemtsov return cfg80211_reg_can_beacon_relax(wiphy, &chandef, iftype); 160720658702SArik Nemtsov case NL80211_IFTYPE_STATION: 160820658702SArik Nemtsov case NL80211_IFTYPE_P2P_CLIENT: 160920658702SArik Nemtsov return cfg80211_chandef_usable(wiphy, &chandef, 161020658702SArik Nemtsov IEEE80211_CHAN_DISABLED); 161120658702SArik Nemtsov default: 161220658702SArik Nemtsov break; 161320658702SArik Nemtsov } 161420658702SArik Nemtsov 161520658702SArik Nemtsov return true; 161620658702SArik Nemtsov 161720658702SArik Nemtsov wdev_inactive_unlock: 161820658702SArik Nemtsov wdev_unlock(wdev); 161920658702SArik Nemtsov return true; 1620ad932f04SArik Nemtsov } 1621ad932f04SArik Nemtsov 1622ad932f04SArik Nemtsov static void reg_leave_invalid_chans(struct wiphy *wiphy) 1623ad932f04SArik Nemtsov { 1624ad932f04SArik Nemtsov struct wireless_dev *wdev; 1625ad932f04SArik Nemtsov struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); 1626ad932f04SArik Nemtsov 1627ad932f04SArik Nemtsov ASSERT_RTNL(); 1628ad932f04SArik Nemtsov 162953873f13SJohannes Berg list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) 1630ad932f04SArik Nemtsov if (!reg_wdev_chan_valid(wiphy, wdev)) 1631ad932f04SArik Nemtsov cfg80211_leave(rdev, wdev); 1632ad932f04SArik Nemtsov } 1633ad932f04SArik Nemtsov 1634ad932f04SArik Nemtsov static void reg_check_chans_work(struct work_struct *work) 1635ad932f04SArik Nemtsov { 1636ad932f04SArik Nemtsov struct cfg80211_registered_device *rdev; 1637ad932f04SArik Nemtsov 1638c799ba6eSJohannes Berg pr_debug("Verifying active interfaces after reg change\n"); 1639ad932f04SArik Nemtsov rtnl_lock(); 1640ad932f04SArik Nemtsov 1641ad932f04SArik Nemtsov list_for_each_entry(rdev, &cfg80211_rdev_list, list) 1642ad932f04SArik Nemtsov if (!(rdev->wiphy.regulatory_flags & 1643ad932f04SArik Nemtsov REGULATORY_IGNORE_STALE_KICKOFF)) 1644ad932f04SArik Nemtsov reg_leave_invalid_chans(&rdev->wiphy); 1645ad932f04SArik Nemtsov 1646ad932f04SArik Nemtsov rtnl_unlock(); 1647ad932f04SArik Nemtsov } 1648ad932f04SArik Nemtsov 1649ad932f04SArik Nemtsov static void reg_check_channels(void) 1650ad932f04SArik Nemtsov { 1651ad932f04SArik Nemtsov /* 1652ad932f04SArik Nemtsov * Give usermode a chance to do something nicer (move to another 1653ad932f04SArik Nemtsov * channel, orderly disconnection), before forcing a disconnection. 1654ad932f04SArik Nemtsov */ 1655ad932f04SArik Nemtsov mod_delayed_work(system_power_efficient_wq, 1656ad932f04SArik Nemtsov ®_check_chans, 1657ad932f04SArik Nemtsov msecs_to_jiffies(REG_ENFORCE_GRACE_MS)); 1658ad932f04SArik Nemtsov } 1659ad932f04SArik Nemtsov 1660eac03e38SSven Neumann static void wiphy_update_regulatory(struct wiphy *wiphy, 16617db90f4aSLuis R. Rodriguez enum nl80211_reg_initiator initiator) 16628318d78aSJohannes Berg { 166357fbcce3SJohannes Berg enum nl80211_band band; 1664c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 1665eac03e38SSven Neumann 16660e3802dbSLuis R. Rodriguez if (ignore_reg_update(wiphy, initiator)) { 16670e3802dbSLuis R. Rodriguez /* 16680e3802dbSLuis R. Rodriguez * Regulatory updates set by CORE are ignored for custom 16690e3802dbSLuis R. Rodriguez * regulatory cards. Let us notify the changes to the driver, 16700e3802dbSLuis R. Rodriguez * as some drivers used this to restore its orig_* reg domain. 16710e3802dbSLuis R. Rodriguez */ 16720e3802dbSLuis R. Rodriguez if (initiator == NL80211_REGDOM_SET_BY_CORE && 1673a2f73b6cSLuis R. Rodriguez wiphy->regulatory_flags & REGULATORY_CUSTOM_REG) 16740e3802dbSLuis R. Rodriguez reg_call_notifier(wiphy, lr); 1675a203c2aaSSven Neumann return; 16760e3802dbSLuis R. Rodriguez } 1677a203c2aaSSven Neumann 1678c492db37SJohannes Berg lr->dfs_region = get_cfg80211_regdom()->dfs_region; 1679b68e6b3bSLuis R. Rodriguez 168057fbcce3SJohannes Berg for (band = 0; band < NUM_NL80211_BANDS; band++) 1681fdc9d7b2SJohannes Berg handle_band(wiphy, initiator, wiphy->bands[band]); 1682a203c2aaSSven Neumann 1683e38f8a7aSLuis R. Rodriguez reg_process_beacons(wiphy); 1684038659e7SLuis R. Rodriguez reg_process_ht_flags(wiphy); 16850e3802dbSLuis R. Rodriguez reg_call_notifier(wiphy, lr); 1686b2e1b302SLuis R. Rodriguez } 1687b2e1b302SLuis R. Rodriguez 1688d7549cbbSSven Neumann static void update_all_wiphy_regulatory(enum nl80211_reg_initiator initiator) 1689d7549cbbSSven Neumann { 1690d7549cbbSSven Neumann struct cfg80211_registered_device *rdev; 16914a38994fSRajkumar Manoharan struct wiphy *wiphy; 1692d7549cbbSSven Neumann 16935fe231e8SJohannes Berg ASSERT_RTNL(); 1694458f4f9eSJohannes Berg 16954a38994fSRajkumar Manoharan list_for_each_entry(rdev, &cfg80211_rdev_list, list) { 16964a38994fSRajkumar Manoharan wiphy = &rdev->wiphy; 16974a38994fSRajkumar Manoharan wiphy_update_regulatory(wiphy, initiator); 16984a38994fSRajkumar Manoharan } 1699ad932f04SArik Nemtsov 1700ad932f04SArik Nemtsov reg_check_channels(); 1701d7549cbbSSven Neumann } 1702d7549cbbSSven Neumann 17031fa25e41SLuis R. Rodriguez static void handle_channel_custom(struct wiphy *wiphy, 1704fdc9d7b2SJohannes Berg struct ieee80211_channel *chan, 17051fa25e41SLuis R. Rodriguez const struct ieee80211_regdomain *regd) 17061fa25e41SLuis R. Rodriguez { 1707038659e7SLuis R. Rodriguez u32 bw_flags = 0; 17081fa25e41SLuis R. Rodriguez const struct ieee80211_reg_rule *reg_rule = NULL; 17091fa25e41SLuis R. Rodriguez const struct ieee80211_power_rule *power_rule = NULL; 17104edd5698SMatthias May u32 bw; 17111fa25e41SLuis R. Rodriguez 17124edd5698SMatthias May for (bw = MHZ_TO_KHZ(20); bw >= MHZ_TO_KHZ(5); bw = bw / 2) { 171349172874SMichal Sojka reg_rule = freq_reg_info_regd(MHZ_TO_KHZ(chan->center_freq), 17144edd5698SMatthias May regd, bw); 17154edd5698SMatthias May if (!IS_ERR(reg_rule)) 17164edd5698SMatthias May break; 17174edd5698SMatthias May } 17181fa25e41SLuis R. Rodriguez 1719361c9c8bSJohannes Berg if (IS_ERR(reg_rule)) { 1720c799ba6eSJohannes Berg pr_debug("Disabling freq %d MHz as custom regd has no rule that fits it\n", 1721fe7ef5e9SJohannes Berg chan->center_freq); 1722db8dfee5SArik Nemtsov if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED) { 1723db8dfee5SArik Nemtsov chan->flags |= IEEE80211_CHAN_DISABLED; 1724db8dfee5SArik Nemtsov } else { 1725cc493e4fSLuis R. Rodriguez chan->orig_flags |= IEEE80211_CHAN_DISABLED; 1726cc493e4fSLuis R. Rodriguez chan->flags = chan->orig_flags; 1727db8dfee5SArik Nemtsov } 17281fa25e41SLuis R. Rodriguez return; 17291fa25e41SLuis R. Rodriguez } 17301fa25e41SLuis R. Rodriguez 17311fa25e41SLuis R. Rodriguez power_rule = ®_rule->power_rule; 17321aeb135fSMichal Sojka bw_flags = reg_rule_to_chan_bw_flags(regd, reg_rule, chan); 1733038659e7SLuis R. Rodriguez 17342e18b38fSArik Nemtsov chan->dfs_state_entered = jiffies; 1735c7ab5081SArik Nemtsov chan->dfs_state = NL80211_DFS_USABLE; 1736c7ab5081SArik Nemtsov 1737c7ab5081SArik Nemtsov chan->beacon_found = false; 1738db8dfee5SArik Nemtsov 1739db8dfee5SArik Nemtsov if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED) 1740db8dfee5SArik Nemtsov chan->flags = chan->orig_flags | bw_flags | 1741db8dfee5SArik Nemtsov map_regdom_flags(reg_rule->flags); 1742db8dfee5SArik Nemtsov else 1743038659e7SLuis R. Rodriguez chan->flags |= map_regdom_flags(reg_rule->flags) | bw_flags; 1744db8dfee5SArik Nemtsov 17451fa25e41SLuis R. Rodriguez chan->max_antenna_gain = (int) MBI_TO_DBI(power_rule->max_antenna_gain); 1746279f0f55SFelix Fietkau chan->max_reg_power = chan->max_power = 1747279f0f55SFelix Fietkau (int) MBM_TO_DBM(power_rule->max_eirp); 17482e18b38fSArik Nemtsov 17492e18b38fSArik Nemtsov if (chan->flags & IEEE80211_CHAN_RADAR) { 17502e18b38fSArik Nemtsov if (reg_rule->dfs_cac_ms) 17512e18b38fSArik Nemtsov chan->dfs_cac_ms = reg_rule->dfs_cac_ms; 17522e18b38fSArik Nemtsov else 17532e18b38fSArik Nemtsov chan->dfs_cac_ms = IEEE80211_DFS_MIN_CAC_TIME_MS; 17542e18b38fSArik Nemtsov } 17552e18b38fSArik Nemtsov 17562e18b38fSArik Nemtsov chan->max_power = chan->max_reg_power; 17571fa25e41SLuis R. Rodriguez } 17581fa25e41SLuis R. Rodriguez 1759fdc9d7b2SJohannes Berg static void handle_band_custom(struct wiphy *wiphy, 1760fdc9d7b2SJohannes Berg struct ieee80211_supported_band *sband, 17611fa25e41SLuis R. Rodriguez const struct ieee80211_regdomain *regd) 17621fa25e41SLuis R. Rodriguez { 17631fa25e41SLuis R. Rodriguez unsigned int i; 17641fa25e41SLuis R. Rodriguez 1765fdc9d7b2SJohannes Berg if (!sband) 1766fdc9d7b2SJohannes Berg return; 17671fa25e41SLuis R. Rodriguez 17681fa25e41SLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 1769fdc9d7b2SJohannes Berg handle_channel_custom(wiphy, &sband->channels[i], regd); 17701fa25e41SLuis R. Rodriguez } 17711fa25e41SLuis R. Rodriguez 17721fa25e41SLuis R. Rodriguez /* Used by drivers prior to wiphy registration */ 17731fa25e41SLuis R. Rodriguez void wiphy_apply_custom_regulatory(struct wiphy *wiphy, 17741fa25e41SLuis R. Rodriguez const struct ieee80211_regdomain *regd) 17751fa25e41SLuis R. Rodriguez { 177657fbcce3SJohannes Berg enum nl80211_band band; 1777bbcf3f02SLuis R. Rodriguez unsigned int bands_set = 0; 1778ac46d48eSLuis R. Rodriguez 1779a2f73b6cSLuis R. Rodriguez WARN(!(wiphy->regulatory_flags & REGULATORY_CUSTOM_REG), 1780a2f73b6cSLuis R. Rodriguez "wiphy should have REGULATORY_CUSTOM_REG\n"); 1781a2f73b6cSLuis R. Rodriguez wiphy->regulatory_flags |= REGULATORY_CUSTOM_REG; 1782222ea581SLuis R. Rodriguez 178357fbcce3SJohannes Berg for (band = 0; band < NUM_NL80211_BANDS; band++) { 1784bbcf3f02SLuis R. Rodriguez if (!wiphy->bands[band]) 1785bbcf3f02SLuis R. Rodriguez continue; 1786fdc9d7b2SJohannes Berg handle_band_custom(wiphy, wiphy->bands[band], regd); 1787bbcf3f02SLuis R. Rodriguez bands_set++; 17881fa25e41SLuis R. Rodriguez } 1789bbcf3f02SLuis R. Rodriguez 1790bbcf3f02SLuis R. Rodriguez /* 1791bbcf3f02SLuis R. Rodriguez * no point in calling this if it won't have any effect 17921a919318SJohannes Berg * on your device's supported bands. 1793bbcf3f02SLuis R. Rodriguez */ 1794bbcf3f02SLuis R. Rodriguez WARN_ON(!bands_set); 17951fa25e41SLuis R. Rodriguez } 17961fa25e41SLuis R. Rodriguez EXPORT_SYMBOL(wiphy_apply_custom_regulatory); 17971fa25e41SLuis R. Rodriguez 1798b2e253cfSLuis R. Rodriguez static void reg_set_request_processed(void) 1799b2e253cfSLuis R. Rodriguez { 1800b2e253cfSLuis R. Rodriguez bool need_more_processing = false; 1801c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 1802b2e253cfSLuis R. Rodriguez 1803c492db37SJohannes Berg lr->processed = true; 1804b2e253cfSLuis R. Rodriguez 1805b2e253cfSLuis R. Rodriguez spin_lock(®_requests_lock); 1806b2e253cfSLuis R. Rodriguez if (!list_empty(®_requests_list)) 1807b2e253cfSLuis R. Rodriguez need_more_processing = true; 1808b2e253cfSLuis R. Rodriguez spin_unlock(®_requests_lock); 1809b2e253cfSLuis R. Rodriguez 1810b6863036SJohannes Berg cancel_crda_timeout(); 1811a90c7a31SLuis R. Rodriguez 1812b2e253cfSLuis R. Rodriguez if (need_more_processing) 1813b2e253cfSLuis R. Rodriguez schedule_work(®_work); 1814b2e253cfSLuis R. Rodriguez } 1815b2e253cfSLuis R. Rodriguez 1816d1c96a9aSLuis R. Rodriguez /** 1817b3eb7f3fSLuis R. Rodriguez * reg_process_hint_core - process core regulatory requests 1818b3eb7f3fSLuis R. Rodriguez * @pending_request: a pending core regulatory request 1819b3eb7f3fSLuis R. Rodriguez * 1820b3eb7f3fSLuis R. Rodriguez * The wireless subsystem can use this function to process 1821b3eb7f3fSLuis R. Rodriguez * a regulatory request issued by the regulatory core. 1822b3eb7f3fSLuis R. Rodriguez */ 1823d34265a3SJohannes Berg static enum reg_request_treatment 1824d34265a3SJohannes Berg reg_process_hint_core(struct regulatory_request *core_request) 1825b3eb7f3fSLuis R. Rodriguez { 1826cecbb069SJohannes Berg if (reg_query_database(core_request)) { 1827b3eb7f3fSLuis R. Rodriguez core_request->intersect = false; 1828b3eb7f3fSLuis R. Rodriguez core_request->processed = false; 182905f1a3eaSLuis R. Rodriguez reg_update_last_request(core_request); 1830d34265a3SJohannes Berg return REG_REQ_OK; 183125b20dbdSJohannes Berg } 1832d34265a3SJohannes Berg 1833d34265a3SJohannes Berg return REG_REQ_IGNORE; 1834b3eb7f3fSLuis R. Rodriguez } 1835b3eb7f3fSLuis R. Rodriguez 18360d97a619SLuis R. Rodriguez static enum reg_request_treatment 18370d97a619SLuis R. Rodriguez __reg_process_hint_user(struct regulatory_request *user_request) 18380d97a619SLuis R. Rodriguez { 18390d97a619SLuis R. Rodriguez struct regulatory_request *lr = get_last_request(); 18400d97a619SLuis R. Rodriguez 18410d97a619SLuis R. Rodriguez if (reg_request_cell_base(user_request)) 18420d97a619SLuis R. Rodriguez return reg_ignore_cell_hint(user_request); 18430d97a619SLuis R. Rodriguez 18440d97a619SLuis R. Rodriguez if (reg_request_cell_base(lr)) 18450d97a619SLuis R. Rodriguez return REG_REQ_IGNORE; 18460d97a619SLuis R. Rodriguez 18470d97a619SLuis R. Rodriguez if (lr->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE) 18480d97a619SLuis R. Rodriguez return REG_REQ_INTERSECT; 18490d97a619SLuis R. Rodriguez /* 18500d97a619SLuis R. Rodriguez * If the user knows better the user should set the regdom 18510d97a619SLuis R. Rodriguez * to their country before the IE is picked up 18520d97a619SLuis R. Rodriguez */ 18530d97a619SLuis R. Rodriguez if (lr->initiator == NL80211_REGDOM_SET_BY_USER && 18540d97a619SLuis R. Rodriguez lr->intersect) 18550d97a619SLuis R. Rodriguez return REG_REQ_IGNORE; 18560d97a619SLuis R. Rodriguez /* 18570d97a619SLuis R. Rodriguez * Process user requests only after previous user/driver/core 18580d97a619SLuis R. Rodriguez * requests have been processed 18590d97a619SLuis R. Rodriguez */ 18600d97a619SLuis R. Rodriguez if ((lr->initiator == NL80211_REGDOM_SET_BY_CORE || 18610d97a619SLuis R. Rodriguez lr->initiator == NL80211_REGDOM_SET_BY_DRIVER || 18620d97a619SLuis R. Rodriguez lr->initiator == NL80211_REGDOM_SET_BY_USER) && 18630d97a619SLuis R. Rodriguez regdom_changes(lr->alpha2)) 18640d97a619SLuis R. Rodriguez return REG_REQ_IGNORE; 18650d97a619SLuis R. Rodriguez 18660d97a619SLuis R. Rodriguez if (!regdom_changes(user_request->alpha2)) 18670d97a619SLuis R. Rodriguez return REG_REQ_ALREADY_SET; 18680d97a619SLuis R. Rodriguez 18690d97a619SLuis R. Rodriguez return REG_REQ_OK; 18700d97a619SLuis R. Rodriguez } 18710d97a619SLuis R. Rodriguez 18720d97a619SLuis R. Rodriguez /** 18730d97a619SLuis R. Rodriguez * reg_process_hint_user - process user regulatory requests 18740d97a619SLuis R. Rodriguez * @user_request: a pending user regulatory request 18750d97a619SLuis R. Rodriguez * 18760d97a619SLuis R. Rodriguez * The wireless subsystem can use this function to process 18770d97a619SLuis R. Rodriguez * a regulatory request initiated by userspace. 18780d97a619SLuis R. Rodriguez */ 1879d34265a3SJohannes Berg static enum reg_request_treatment 1880d34265a3SJohannes Berg reg_process_hint_user(struct regulatory_request *user_request) 18810d97a619SLuis R. Rodriguez { 18820d97a619SLuis R. Rodriguez enum reg_request_treatment treatment; 18830d97a619SLuis R. Rodriguez 18840d97a619SLuis R. Rodriguez treatment = __reg_process_hint_user(user_request); 18850d97a619SLuis R. Rodriguez if (treatment == REG_REQ_IGNORE || 1886d34265a3SJohannes Berg treatment == REG_REQ_ALREADY_SET) 1887d34265a3SJohannes Berg return REG_REQ_IGNORE; 18880d97a619SLuis R. Rodriguez 18890d97a619SLuis R. Rodriguez user_request->intersect = treatment == REG_REQ_INTERSECT; 18900d97a619SLuis R. Rodriguez user_request->processed = false; 18915ad6ef5eSLuis R. Rodriguez 1892cecbb069SJohannes Berg if (reg_query_database(user_request)) { 189305f1a3eaSLuis R. Rodriguez reg_update_last_request(user_request); 18940d97a619SLuis R. Rodriguez user_alpha2[0] = user_request->alpha2[0]; 18950d97a619SLuis R. Rodriguez user_alpha2[1] = user_request->alpha2[1]; 1896d34265a3SJohannes Berg return REG_REQ_OK; 189725b20dbdSJohannes Berg } 1898d34265a3SJohannes Berg 1899d34265a3SJohannes Berg return REG_REQ_IGNORE; 19000d97a619SLuis R. Rodriguez } 19010d97a619SLuis R. Rodriguez 190221636c7fSLuis R. Rodriguez static enum reg_request_treatment 190321636c7fSLuis R. Rodriguez __reg_process_hint_driver(struct regulatory_request *driver_request) 190421636c7fSLuis R. Rodriguez { 190521636c7fSLuis R. Rodriguez struct regulatory_request *lr = get_last_request(); 190621636c7fSLuis R. Rodriguez 190721636c7fSLuis R. Rodriguez if (lr->initiator == NL80211_REGDOM_SET_BY_CORE) { 190821636c7fSLuis R. Rodriguez if (regdom_changes(driver_request->alpha2)) 190921636c7fSLuis R. Rodriguez return REG_REQ_OK; 191021636c7fSLuis R. Rodriguez return REG_REQ_ALREADY_SET; 191121636c7fSLuis R. Rodriguez } 191221636c7fSLuis R. Rodriguez 191321636c7fSLuis R. Rodriguez /* 191421636c7fSLuis R. Rodriguez * This would happen if you unplug and plug your card 191521636c7fSLuis R. Rodriguez * back in or if you add a new device for which the previously 191621636c7fSLuis R. Rodriguez * loaded card also agrees on the regulatory domain. 191721636c7fSLuis R. Rodriguez */ 191821636c7fSLuis R. Rodriguez if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER && 191921636c7fSLuis R. Rodriguez !regdom_changes(driver_request->alpha2)) 192021636c7fSLuis R. Rodriguez return REG_REQ_ALREADY_SET; 192121636c7fSLuis R. Rodriguez 192221636c7fSLuis R. Rodriguez return REG_REQ_INTERSECT; 192321636c7fSLuis R. Rodriguez } 192421636c7fSLuis R. Rodriguez 192521636c7fSLuis R. Rodriguez /** 192621636c7fSLuis R. Rodriguez * reg_process_hint_driver - process driver regulatory requests 192721636c7fSLuis R. Rodriguez * @driver_request: a pending driver regulatory request 192821636c7fSLuis R. Rodriguez * 192921636c7fSLuis R. Rodriguez * The wireless subsystem can use this function to process 193021636c7fSLuis R. Rodriguez * a regulatory request issued by an 802.11 driver. 193121636c7fSLuis R. Rodriguez * 193221636c7fSLuis R. Rodriguez * Returns one of the different reg request treatment values. 193321636c7fSLuis R. Rodriguez */ 193421636c7fSLuis R. Rodriguez static enum reg_request_treatment 193521636c7fSLuis R. Rodriguez reg_process_hint_driver(struct wiphy *wiphy, 193621636c7fSLuis R. Rodriguez struct regulatory_request *driver_request) 193721636c7fSLuis R. Rodriguez { 193834f05f54SArik Nemtsov const struct ieee80211_regdomain *regd, *tmp; 193921636c7fSLuis R. Rodriguez enum reg_request_treatment treatment; 194021636c7fSLuis R. Rodriguez 194121636c7fSLuis R. Rodriguez treatment = __reg_process_hint_driver(driver_request); 194221636c7fSLuis R. Rodriguez 194321636c7fSLuis R. Rodriguez switch (treatment) { 194421636c7fSLuis R. Rodriguez case REG_REQ_OK: 194521636c7fSLuis R. Rodriguez break; 194621636c7fSLuis R. Rodriguez case REG_REQ_IGNORE: 1947d34265a3SJohannes Berg return REG_REQ_IGNORE; 194821636c7fSLuis R. Rodriguez case REG_REQ_INTERSECT: 194921636c7fSLuis R. Rodriguez case REG_REQ_ALREADY_SET: 195021636c7fSLuis R. Rodriguez regd = reg_copy_regd(get_cfg80211_regdom()); 1951d34265a3SJohannes Berg if (IS_ERR(regd)) 1952d34265a3SJohannes Berg return REG_REQ_IGNORE; 195334f05f54SArik Nemtsov 195434f05f54SArik Nemtsov tmp = get_wiphy_regdom(wiphy); 195521636c7fSLuis R. Rodriguez rcu_assign_pointer(wiphy->regd, regd); 195634f05f54SArik Nemtsov rcu_free_regdom(tmp); 195721636c7fSLuis R. Rodriguez } 195821636c7fSLuis R. Rodriguez 195921636c7fSLuis R. Rodriguez 196021636c7fSLuis R. Rodriguez driver_request->intersect = treatment == REG_REQ_INTERSECT; 196121636c7fSLuis R. Rodriguez driver_request->processed = false; 19625ad6ef5eSLuis R. Rodriguez 196321636c7fSLuis R. Rodriguez /* 196421636c7fSLuis R. Rodriguez * Since CRDA will not be called in this case as we already 196521636c7fSLuis R. Rodriguez * have applied the requested regulatory domain before we just 196621636c7fSLuis R. Rodriguez * inform userspace we have processed the request 196721636c7fSLuis R. Rodriguez */ 196821636c7fSLuis R. Rodriguez if (treatment == REG_REQ_ALREADY_SET) { 196921636c7fSLuis R. Rodriguez nl80211_send_reg_change_event(driver_request); 197025b20dbdSJohannes Berg reg_update_last_request(driver_request); 197121636c7fSLuis R. Rodriguez reg_set_request_processed(); 1972480908a7SJohannes Berg return REG_REQ_ALREADY_SET; 197321636c7fSLuis R. Rodriguez } 197421636c7fSLuis R. Rodriguez 1975d34265a3SJohannes Berg if (reg_query_database(driver_request)) { 197625b20dbdSJohannes Berg reg_update_last_request(driver_request); 197725b20dbdSJohannes Berg return REG_REQ_OK; 197821636c7fSLuis R. Rodriguez } 197921636c7fSLuis R. Rodriguez 1980d34265a3SJohannes Berg return REG_REQ_IGNORE; 1981d34265a3SJohannes Berg } 1982d34265a3SJohannes Berg 1983b23e7a9eSLuis R. Rodriguez static enum reg_request_treatment 1984b23e7a9eSLuis R. Rodriguez __reg_process_hint_country_ie(struct wiphy *wiphy, 1985b23e7a9eSLuis R. Rodriguez struct regulatory_request *country_ie_request) 1986b23e7a9eSLuis R. Rodriguez { 1987b23e7a9eSLuis R. Rodriguez struct wiphy *last_wiphy = NULL; 1988b23e7a9eSLuis R. Rodriguez struct regulatory_request *lr = get_last_request(); 1989b23e7a9eSLuis R. Rodriguez 1990b23e7a9eSLuis R. Rodriguez if (reg_request_cell_base(lr)) { 1991b23e7a9eSLuis R. Rodriguez /* Trust a Cell base station over the AP's country IE */ 1992b23e7a9eSLuis R. Rodriguez if (regdom_changes(country_ie_request->alpha2)) 1993b23e7a9eSLuis R. Rodriguez return REG_REQ_IGNORE; 1994b23e7a9eSLuis R. Rodriguez return REG_REQ_ALREADY_SET; 19952a901468SLuis R. Rodriguez } else { 19962a901468SLuis R. Rodriguez if (wiphy->regulatory_flags & REGULATORY_COUNTRY_IE_IGNORE) 19972a901468SLuis R. Rodriguez return REG_REQ_IGNORE; 1998b23e7a9eSLuis R. Rodriguez } 1999b23e7a9eSLuis R. Rodriguez 2000b23e7a9eSLuis R. Rodriguez if (unlikely(!is_an_alpha2(country_ie_request->alpha2))) 2001b23e7a9eSLuis R. Rodriguez return -EINVAL; 20022f1c6c57SLuis R. Rodriguez 20032f1c6c57SLuis R. Rodriguez if (lr->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE) 20042f1c6c57SLuis R. Rodriguez return REG_REQ_OK; 20052f1c6c57SLuis R. Rodriguez 20062f1c6c57SLuis R. Rodriguez last_wiphy = wiphy_idx_to_wiphy(lr->wiphy_idx); 20072f1c6c57SLuis R. Rodriguez 2008b23e7a9eSLuis R. Rodriguez if (last_wiphy != wiphy) { 2009b23e7a9eSLuis R. Rodriguez /* 2010b23e7a9eSLuis R. Rodriguez * Two cards with two APs claiming different 2011b23e7a9eSLuis R. Rodriguez * Country IE alpha2s. We could 2012b23e7a9eSLuis R. Rodriguez * intersect them, but that seems unlikely 2013b23e7a9eSLuis R. Rodriguez * to be correct. Reject second one for now. 2014b23e7a9eSLuis R. Rodriguez */ 2015b23e7a9eSLuis R. Rodriguez if (regdom_changes(country_ie_request->alpha2)) 2016b23e7a9eSLuis R. Rodriguez return REG_REQ_IGNORE; 2017b23e7a9eSLuis R. Rodriguez return REG_REQ_ALREADY_SET; 2018b23e7a9eSLuis R. Rodriguez } 201970dcec5aSEmmanuel Grumbach 202070dcec5aSEmmanuel Grumbach if (regdom_changes(country_ie_request->alpha2)) 2021b23e7a9eSLuis R. Rodriguez return REG_REQ_OK; 2022b23e7a9eSLuis R. Rodriguez return REG_REQ_ALREADY_SET; 2023b23e7a9eSLuis R. Rodriguez } 2024b23e7a9eSLuis R. Rodriguez 2025b3eb7f3fSLuis R. Rodriguez /** 2026b23e7a9eSLuis R. Rodriguez * reg_process_hint_country_ie - process regulatory requests from country IEs 2027b23e7a9eSLuis R. Rodriguez * @country_ie_request: a regulatory request from a country IE 2028d1c96a9aSLuis R. Rodriguez * 2029b23e7a9eSLuis R. Rodriguez * The wireless subsystem can use this function to process 2030b23e7a9eSLuis R. Rodriguez * a regulatory request issued by a country Information Element. 2031d1c96a9aSLuis R. Rodriguez * 20322f92212bSJohannes Berg * Returns one of the different reg request treatment values. 2033d1c96a9aSLuis R. Rodriguez */ 20342f92212bSJohannes Berg static enum reg_request_treatment 2035b23e7a9eSLuis R. Rodriguez reg_process_hint_country_ie(struct wiphy *wiphy, 2036b23e7a9eSLuis R. Rodriguez struct regulatory_request *country_ie_request) 2037b2e1b302SLuis R. Rodriguez { 20382f92212bSJohannes Berg enum reg_request_treatment treatment; 2039b2e1b302SLuis R. Rodriguez 2040b23e7a9eSLuis R. Rodriguez treatment = __reg_process_hint_country_ie(wiphy, country_ie_request); 2041761cf7ecSLuis R. Rodriguez 20422f92212bSJohannes Berg switch (treatment) { 20432f92212bSJohannes Berg case REG_REQ_OK: 20442f92212bSJohannes Berg break; 2045b23e7a9eSLuis R. Rodriguez case REG_REQ_IGNORE: 2046d34265a3SJohannes Berg return REG_REQ_IGNORE; 2047b23e7a9eSLuis R. Rodriguez case REG_REQ_ALREADY_SET: 2048c888393bSArik Nemtsov reg_free_request(country_ie_request); 2049480908a7SJohannes Berg return REG_REQ_ALREADY_SET; 2050b23e7a9eSLuis R. Rodriguez case REG_REQ_INTERSECT: 2051fb1fc7adSLuis R. Rodriguez /* 2052b23e7a9eSLuis R. Rodriguez * This doesn't happen yet, not sure we 2053b23e7a9eSLuis R. Rodriguez * ever want to support it for this case. 2054fb1fc7adSLuis R. Rodriguez */ 2055b23e7a9eSLuis R. Rodriguez WARN_ONCE(1, "Unexpected intersection for country IEs"); 2056d34265a3SJohannes Berg return REG_REQ_IGNORE; 2057d951c1ddSLuis R. Rodriguez } 2058b2e1b302SLuis R. Rodriguez 2059b23e7a9eSLuis R. Rodriguez country_ie_request->intersect = false; 2060b23e7a9eSLuis R. Rodriguez country_ie_request->processed = false; 20615ad6ef5eSLuis R. Rodriguez 2062d34265a3SJohannes Berg if (reg_query_database(country_ie_request)) { 206305f1a3eaSLuis R. Rodriguez reg_update_last_request(country_ie_request); 206425b20dbdSJohannes Berg return REG_REQ_OK; 2065b2e1b302SLuis R. Rodriguez } 2066b2e1b302SLuis R. Rodriguez 2067d34265a3SJohannes Berg return REG_REQ_IGNORE; 2068d34265a3SJohannes Berg } 2069d34265a3SJohannes Berg 207089766727SVasanthakumar Thiagarajan bool reg_dfs_domain_same(struct wiphy *wiphy1, struct wiphy *wiphy2) 207189766727SVasanthakumar Thiagarajan { 207289766727SVasanthakumar Thiagarajan const struct ieee80211_regdomain *wiphy1_regd = NULL; 207389766727SVasanthakumar Thiagarajan const struct ieee80211_regdomain *wiphy2_regd = NULL; 207489766727SVasanthakumar Thiagarajan const struct ieee80211_regdomain *cfg80211_regd = NULL; 207589766727SVasanthakumar Thiagarajan bool dfs_domain_same; 207689766727SVasanthakumar Thiagarajan 207789766727SVasanthakumar Thiagarajan rcu_read_lock(); 207889766727SVasanthakumar Thiagarajan 207989766727SVasanthakumar Thiagarajan cfg80211_regd = rcu_dereference(cfg80211_regdomain); 208089766727SVasanthakumar Thiagarajan wiphy1_regd = rcu_dereference(wiphy1->regd); 208189766727SVasanthakumar Thiagarajan if (!wiphy1_regd) 208289766727SVasanthakumar Thiagarajan wiphy1_regd = cfg80211_regd; 208389766727SVasanthakumar Thiagarajan 208489766727SVasanthakumar Thiagarajan wiphy2_regd = rcu_dereference(wiphy2->regd); 208589766727SVasanthakumar Thiagarajan if (!wiphy2_regd) 208689766727SVasanthakumar Thiagarajan wiphy2_regd = cfg80211_regd; 208789766727SVasanthakumar Thiagarajan 208889766727SVasanthakumar Thiagarajan dfs_domain_same = wiphy1_regd->dfs_region == wiphy2_regd->dfs_region; 208989766727SVasanthakumar Thiagarajan 209089766727SVasanthakumar Thiagarajan rcu_read_unlock(); 209189766727SVasanthakumar Thiagarajan 209289766727SVasanthakumar Thiagarajan return dfs_domain_same; 209389766727SVasanthakumar Thiagarajan } 209489766727SVasanthakumar Thiagarajan 209589766727SVasanthakumar Thiagarajan static void reg_copy_dfs_chan_state(struct ieee80211_channel *dst_chan, 209689766727SVasanthakumar Thiagarajan struct ieee80211_channel *src_chan) 209789766727SVasanthakumar Thiagarajan { 209889766727SVasanthakumar Thiagarajan if (!(dst_chan->flags & IEEE80211_CHAN_RADAR) || 209989766727SVasanthakumar Thiagarajan !(src_chan->flags & IEEE80211_CHAN_RADAR)) 210089766727SVasanthakumar Thiagarajan return; 210189766727SVasanthakumar Thiagarajan 210289766727SVasanthakumar Thiagarajan if (dst_chan->flags & IEEE80211_CHAN_DISABLED || 210389766727SVasanthakumar Thiagarajan src_chan->flags & IEEE80211_CHAN_DISABLED) 210489766727SVasanthakumar Thiagarajan return; 210589766727SVasanthakumar Thiagarajan 210689766727SVasanthakumar Thiagarajan if (src_chan->center_freq == dst_chan->center_freq && 210789766727SVasanthakumar Thiagarajan dst_chan->dfs_state == NL80211_DFS_USABLE) { 210889766727SVasanthakumar Thiagarajan dst_chan->dfs_state = src_chan->dfs_state; 210989766727SVasanthakumar Thiagarajan dst_chan->dfs_state_entered = src_chan->dfs_state_entered; 211089766727SVasanthakumar Thiagarajan } 211189766727SVasanthakumar Thiagarajan } 211289766727SVasanthakumar Thiagarajan 211389766727SVasanthakumar Thiagarajan static void wiphy_share_dfs_chan_state(struct wiphy *dst_wiphy, 211489766727SVasanthakumar Thiagarajan struct wiphy *src_wiphy) 211589766727SVasanthakumar Thiagarajan { 211689766727SVasanthakumar Thiagarajan struct ieee80211_supported_band *src_sband, *dst_sband; 211789766727SVasanthakumar Thiagarajan struct ieee80211_channel *src_chan, *dst_chan; 211889766727SVasanthakumar Thiagarajan int i, j, band; 211989766727SVasanthakumar Thiagarajan 212089766727SVasanthakumar Thiagarajan if (!reg_dfs_domain_same(dst_wiphy, src_wiphy)) 212189766727SVasanthakumar Thiagarajan return; 212289766727SVasanthakumar Thiagarajan 212389766727SVasanthakumar Thiagarajan for (band = 0; band < NUM_NL80211_BANDS; band++) { 212489766727SVasanthakumar Thiagarajan dst_sband = dst_wiphy->bands[band]; 212589766727SVasanthakumar Thiagarajan src_sband = src_wiphy->bands[band]; 212689766727SVasanthakumar Thiagarajan if (!dst_sband || !src_sband) 212789766727SVasanthakumar Thiagarajan continue; 212889766727SVasanthakumar Thiagarajan 212989766727SVasanthakumar Thiagarajan for (i = 0; i < dst_sband->n_channels; i++) { 213089766727SVasanthakumar Thiagarajan dst_chan = &dst_sband->channels[i]; 213189766727SVasanthakumar Thiagarajan for (j = 0; j < src_sband->n_channels; j++) { 213289766727SVasanthakumar Thiagarajan src_chan = &src_sband->channels[j]; 213389766727SVasanthakumar Thiagarajan reg_copy_dfs_chan_state(dst_chan, src_chan); 213489766727SVasanthakumar Thiagarajan } 213589766727SVasanthakumar Thiagarajan } 213689766727SVasanthakumar Thiagarajan } 213789766727SVasanthakumar Thiagarajan } 213889766727SVasanthakumar Thiagarajan 213989766727SVasanthakumar Thiagarajan static void wiphy_all_share_dfs_chan_state(struct wiphy *wiphy) 214089766727SVasanthakumar Thiagarajan { 214189766727SVasanthakumar Thiagarajan struct cfg80211_registered_device *rdev; 214289766727SVasanthakumar Thiagarajan 214389766727SVasanthakumar Thiagarajan ASSERT_RTNL(); 214489766727SVasanthakumar Thiagarajan 214589766727SVasanthakumar Thiagarajan list_for_each_entry(rdev, &cfg80211_rdev_list, list) { 214689766727SVasanthakumar Thiagarajan if (wiphy == &rdev->wiphy) 214789766727SVasanthakumar Thiagarajan continue; 214889766727SVasanthakumar Thiagarajan wiphy_share_dfs_chan_state(wiphy, &rdev->wiphy); 214989766727SVasanthakumar Thiagarajan } 215089766727SVasanthakumar Thiagarajan } 215189766727SVasanthakumar Thiagarajan 215230a548c7SLuis R. Rodriguez /* This processes *all* regulatory hints */ 21531daa37c7SLuis R. Rodriguez static void reg_process_hint(struct regulatory_request *reg_request) 2154fe33eb39SLuis R. Rodriguez { 2155fe33eb39SLuis R. Rodriguez struct wiphy *wiphy = NULL; 2156b3eb7f3fSLuis R. Rodriguez enum reg_request_treatment treatment; 2157fe33eb39SLuis R. Rodriguez 2158f4173766SJohannes Berg if (reg_request->wiphy_idx != WIPHY_IDX_INVALID) 2159fe33eb39SLuis R. Rodriguez wiphy = wiphy_idx_to_wiphy(reg_request->wiphy_idx); 2160fe33eb39SLuis R. Rodriguez 2161b3eb7f3fSLuis R. Rodriguez switch (reg_request->initiator) { 2162b3eb7f3fSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_CORE: 2163d34265a3SJohannes Berg treatment = reg_process_hint_core(reg_request); 2164d34265a3SJohannes Berg break; 2165b3eb7f3fSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_USER: 2166d34265a3SJohannes Berg treatment = reg_process_hint_user(reg_request); 2167d34265a3SJohannes Berg break; 2168b3eb7f3fSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_DRIVER: 2169772f0389SIlan Peer if (!wiphy) 2170772f0389SIlan Peer goto out_free; 217121636c7fSLuis R. Rodriguez treatment = reg_process_hint_driver(wiphy, reg_request); 217221636c7fSLuis R. Rodriguez break; 2173b3eb7f3fSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_COUNTRY_IE: 2174772f0389SIlan Peer if (!wiphy) 2175772f0389SIlan Peer goto out_free; 2176b23e7a9eSLuis R. Rodriguez treatment = reg_process_hint_country_ie(wiphy, reg_request); 2177b3eb7f3fSLuis R. Rodriguez break; 2178b3eb7f3fSLuis R. Rodriguez default: 2179b3eb7f3fSLuis R. Rodriguez WARN(1, "invalid initiator %d\n", reg_request->initiator); 2180772f0389SIlan Peer goto out_free; 2181b3eb7f3fSLuis R. Rodriguez } 2182b3eb7f3fSLuis R. Rodriguez 2183d34265a3SJohannes Berg if (treatment == REG_REQ_IGNORE) 2184d34265a3SJohannes Berg goto out_free; 2185d34265a3SJohannes Berg 2186480908a7SJohannes Berg WARN(treatment != REG_REQ_OK && treatment != REG_REQ_ALREADY_SET, 2187480908a7SJohannes Berg "unexpected treatment value %d\n", treatment); 2188480908a7SJohannes Berg 2189841b351cSJohn Linville /* This is required so that the orig_* parameters are saved. 2190841b351cSJohn Linville * NOTE: treatment must be set for any case that reaches here! 2191841b351cSJohn Linville */ 2192b23e7a9eSLuis R. Rodriguez if (treatment == REG_REQ_ALREADY_SET && wiphy && 2193ad932f04SArik Nemtsov wiphy->regulatory_flags & REGULATORY_STRICT_REG) { 21941daa37c7SLuis R. Rodriguez wiphy_update_regulatory(wiphy, reg_request->initiator); 219589766727SVasanthakumar Thiagarajan wiphy_all_share_dfs_chan_state(wiphy); 2196ad932f04SArik Nemtsov reg_check_channels(); 2197ad932f04SArik Nemtsov } 2198772f0389SIlan Peer 2199772f0389SIlan Peer return; 2200772f0389SIlan Peer 2201772f0389SIlan Peer out_free: 2202c888393bSArik Nemtsov reg_free_request(reg_request); 2203fe33eb39SLuis R. Rodriguez } 2204fe33eb39SLuis R. Rodriguez 2205ef51fb1dSArik Nemtsov static bool reg_only_self_managed_wiphys(void) 2206ef51fb1dSArik Nemtsov { 2207ef51fb1dSArik Nemtsov struct cfg80211_registered_device *rdev; 2208ef51fb1dSArik Nemtsov struct wiphy *wiphy; 2209ef51fb1dSArik Nemtsov bool self_managed_found = false; 2210ef51fb1dSArik Nemtsov 2211ef51fb1dSArik Nemtsov ASSERT_RTNL(); 2212ef51fb1dSArik Nemtsov 2213ef51fb1dSArik Nemtsov list_for_each_entry(rdev, &cfg80211_rdev_list, list) { 2214ef51fb1dSArik Nemtsov wiphy = &rdev->wiphy; 2215ef51fb1dSArik Nemtsov if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED) 2216ef51fb1dSArik Nemtsov self_managed_found = true; 2217ef51fb1dSArik Nemtsov else 2218ef51fb1dSArik Nemtsov return false; 2219ef51fb1dSArik Nemtsov } 2220ef51fb1dSArik Nemtsov 2221ef51fb1dSArik Nemtsov /* make sure at least one self-managed wiphy exists */ 2222ef51fb1dSArik Nemtsov return self_managed_found; 2223ef51fb1dSArik Nemtsov } 2224ef51fb1dSArik Nemtsov 2225b2e253cfSLuis R. Rodriguez /* 2226b2e253cfSLuis R. Rodriguez * Processes regulatory hints, this is all the NL80211_REGDOM_SET_BY_* 2227b2e253cfSLuis R. Rodriguez * Regulatory hints come on a first come first serve basis and we 2228b2e253cfSLuis R. Rodriguez * must process each one atomically. 2229b2e253cfSLuis R. Rodriguez */ 2230fe33eb39SLuis R. Rodriguez static void reg_process_pending_hints(void) 2231fe33eb39SLuis R. Rodriguez { 2232c492db37SJohannes Berg struct regulatory_request *reg_request, *lr; 2233fe33eb39SLuis R. Rodriguez 2234c492db37SJohannes Berg lr = get_last_request(); 2235b0e2880bSLuis R. Rodriguez 2236b2e253cfSLuis R. Rodriguez /* When last_request->processed becomes true this will be rescheduled */ 2237c492db37SJohannes Berg if (lr && !lr->processed) { 223896cce12fSLuis R. Rodriguez reg_process_hint(lr); 22395fe231e8SJohannes Berg return; 2240b2e253cfSLuis R. Rodriguez } 2241b2e253cfSLuis R. Rodriguez 2242fe33eb39SLuis R. Rodriguez spin_lock(®_requests_lock); 2243b2e253cfSLuis R. Rodriguez 2244b2e253cfSLuis R. Rodriguez if (list_empty(®_requests_list)) { 2245b2e253cfSLuis R. Rodriguez spin_unlock(®_requests_lock); 22465fe231e8SJohannes Berg return; 2247b2e253cfSLuis R. Rodriguez } 2248b2e253cfSLuis R. Rodriguez 2249fe33eb39SLuis R. Rodriguez reg_request = list_first_entry(®_requests_list, 2250fe33eb39SLuis R. Rodriguez struct regulatory_request, 2251fe33eb39SLuis R. Rodriguez list); 2252fe33eb39SLuis R. Rodriguez list_del_init(®_request->list); 2253fe33eb39SLuis R. Rodriguez 2254d951c1ddSLuis R. Rodriguez spin_unlock(®_requests_lock); 2255b0e2880bSLuis R. Rodriguez 2256ef51fb1dSArik Nemtsov if (reg_only_self_managed_wiphys()) { 2257ef51fb1dSArik Nemtsov reg_free_request(reg_request); 2258ef51fb1dSArik Nemtsov return; 2259ef51fb1dSArik Nemtsov } 2260ef51fb1dSArik Nemtsov 22611daa37c7SLuis R. Rodriguez reg_process_hint(reg_request); 22622e54a689SBen 22632e54a689SBen lr = get_last_request(); 22642e54a689SBen 22652e54a689SBen spin_lock(®_requests_lock); 22662e54a689SBen if (!list_empty(®_requests_list) && lr && lr->processed) 22672e54a689SBen schedule_work(®_work); 22682e54a689SBen spin_unlock(®_requests_lock); 2269fe33eb39SLuis R. Rodriguez } 2270fe33eb39SLuis R. Rodriguez 2271e38f8a7aSLuis R. Rodriguez /* Processes beacon hints -- this has nothing to do with country IEs */ 2272e38f8a7aSLuis R. Rodriguez static void reg_process_pending_beacon_hints(void) 2273e38f8a7aSLuis R. Rodriguez { 227479c97e97SJohannes Berg struct cfg80211_registered_device *rdev; 2275e38f8a7aSLuis R. Rodriguez struct reg_beacon *pending_beacon, *tmp; 2276e38f8a7aSLuis R. Rodriguez 2277e38f8a7aSLuis R. Rodriguez /* This goes through the _pending_ beacon list */ 2278e38f8a7aSLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 2279e38f8a7aSLuis R. Rodriguez 2280e38f8a7aSLuis R. Rodriguez list_for_each_entry_safe(pending_beacon, tmp, 2281e38f8a7aSLuis R. Rodriguez ®_pending_beacons, list) { 2282e38f8a7aSLuis R. Rodriguez list_del_init(&pending_beacon->list); 2283e38f8a7aSLuis R. Rodriguez 2284e38f8a7aSLuis R. Rodriguez /* Applies the beacon hint to current wiphys */ 228579c97e97SJohannes Berg list_for_each_entry(rdev, &cfg80211_rdev_list, list) 228679c97e97SJohannes Berg wiphy_update_new_beacon(&rdev->wiphy, pending_beacon); 2287e38f8a7aSLuis R. Rodriguez 2288e38f8a7aSLuis R. Rodriguez /* Remembers the beacon hint for new wiphys or reg changes */ 2289e38f8a7aSLuis R. Rodriguez list_add_tail(&pending_beacon->list, ®_beacon_list); 2290e38f8a7aSLuis R. Rodriguez } 2291e38f8a7aSLuis R. Rodriguez 2292e38f8a7aSLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 2293e38f8a7aSLuis R. Rodriguez } 2294e38f8a7aSLuis R. Rodriguez 2295b0d7aa59SJonathan Doron static void reg_process_self_managed_hints(void) 2296b0d7aa59SJonathan Doron { 2297b0d7aa59SJonathan Doron struct cfg80211_registered_device *rdev; 2298b0d7aa59SJonathan Doron struct wiphy *wiphy; 2299b0d7aa59SJonathan Doron const struct ieee80211_regdomain *tmp; 2300b0d7aa59SJonathan Doron const struct ieee80211_regdomain *regd; 230157fbcce3SJohannes Berg enum nl80211_band band; 2302b0d7aa59SJonathan Doron struct regulatory_request request = {}; 2303b0d7aa59SJonathan Doron 2304b0d7aa59SJonathan Doron list_for_each_entry(rdev, &cfg80211_rdev_list, list) { 2305b0d7aa59SJonathan Doron wiphy = &rdev->wiphy; 2306b0d7aa59SJonathan Doron 2307b0d7aa59SJonathan Doron spin_lock(®_requests_lock); 2308b0d7aa59SJonathan Doron regd = rdev->requested_regd; 2309b0d7aa59SJonathan Doron rdev->requested_regd = NULL; 2310b0d7aa59SJonathan Doron spin_unlock(®_requests_lock); 2311b0d7aa59SJonathan Doron 2312b0d7aa59SJonathan Doron if (regd == NULL) 2313b0d7aa59SJonathan Doron continue; 2314b0d7aa59SJonathan Doron 2315b0d7aa59SJonathan Doron tmp = get_wiphy_regdom(wiphy); 2316b0d7aa59SJonathan Doron rcu_assign_pointer(wiphy->regd, regd); 2317b0d7aa59SJonathan Doron rcu_free_regdom(tmp); 2318b0d7aa59SJonathan Doron 231957fbcce3SJohannes Berg for (band = 0; band < NUM_NL80211_BANDS; band++) 2320b0d7aa59SJonathan Doron handle_band_custom(wiphy, wiphy->bands[band], regd); 2321b0d7aa59SJonathan Doron 2322b0d7aa59SJonathan Doron reg_process_ht_flags(wiphy); 2323b0d7aa59SJonathan Doron 2324b0d7aa59SJonathan Doron request.wiphy_idx = get_wiphy_idx(wiphy); 2325b0d7aa59SJonathan Doron request.alpha2[0] = regd->alpha2[0]; 2326b0d7aa59SJonathan Doron request.alpha2[1] = regd->alpha2[1]; 2327b0d7aa59SJonathan Doron request.initiator = NL80211_REGDOM_SET_BY_DRIVER; 2328b0d7aa59SJonathan Doron 2329b0d7aa59SJonathan Doron nl80211_send_wiphy_reg_change_event(&request); 2330b0d7aa59SJonathan Doron } 2331b0d7aa59SJonathan Doron 2332b0d7aa59SJonathan Doron reg_check_channels(); 2333b0d7aa59SJonathan Doron } 2334b0d7aa59SJonathan Doron 2335fe33eb39SLuis R. Rodriguez static void reg_todo(struct work_struct *work) 2336fe33eb39SLuis R. Rodriguez { 23375fe231e8SJohannes Berg rtnl_lock(); 2338fe33eb39SLuis R. Rodriguez reg_process_pending_hints(); 2339e38f8a7aSLuis R. Rodriguez reg_process_pending_beacon_hints(); 2340b0d7aa59SJonathan Doron reg_process_self_managed_hints(); 23415fe231e8SJohannes Berg rtnl_unlock(); 2342fe33eb39SLuis R. Rodriguez } 2343fe33eb39SLuis R. Rodriguez 2344fe33eb39SLuis R. Rodriguez static void queue_regulatory_request(struct regulatory_request *request) 2345fe33eb39SLuis R. Rodriguez { 2346c61029c7SJohn W. Linville request->alpha2[0] = toupper(request->alpha2[0]); 2347c61029c7SJohn W. Linville request->alpha2[1] = toupper(request->alpha2[1]); 2348c61029c7SJohn W. Linville 2349fe33eb39SLuis R. Rodriguez spin_lock(®_requests_lock); 2350fe33eb39SLuis R. Rodriguez list_add_tail(&request->list, ®_requests_list); 2351fe33eb39SLuis R. Rodriguez spin_unlock(®_requests_lock); 2352fe33eb39SLuis R. Rodriguez 2353fe33eb39SLuis R. Rodriguez schedule_work(®_work); 2354fe33eb39SLuis R. Rodriguez } 2355fe33eb39SLuis R. Rodriguez 235609d989d1SLuis R. Rodriguez /* 235709d989d1SLuis R. Rodriguez * Core regulatory hint -- happens during cfg80211_init() 235809d989d1SLuis R. Rodriguez * and when we restore regulatory settings. 235909d989d1SLuis R. Rodriguez */ 2360ba25c141SLuis R. Rodriguez static int regulatory_hint_core(const char *alpha2) 2361ba25c141SLuis R. Rodriguez { 2362ba25c141SLuis R. Rodriguez struct regulatory_request *request; 2363ba25c141SLuis R. Rodriguez 23641a919318SJohannes Berg request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL); 2365ba25c141SLuis R. Rodriguez if (!request) 2366ba25c141SLuis R. Rodriguez return -ENOMEM; 2367ba25c141SLuis R. Rodriguez 2368ba25c141SLuis R. Rodriguez request->alpha2[0] = alpha2[0]; 2369ba25c141SLuis R. Rodriguez request->alpha2[1] = alpha2[1]; 23707db90f4aSLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_CORE; 2371ba25c141SLuis R. Rodriguez 237231e99729SLuis R. Rodriguez queue_regulatory_request(request); 23735078b2e3SLuis R. Rodriguez 2374fe33eb39SLuis R. Rodriguez return 0; 2375ba25c141SLuis R. Rodriguez } 2376ba25c141SLuis R. Rodriguez 2377fe33eb39SLuis R. Rodriguez /* User hints */ 237857b5ce07SLuis R. Rodriguez int regulatory_hint_user(const char *alpha2, 237957b5ce07SLuis R. Rodriguez enum nl80211_user_reg_hint_type user_reg_hint_type) 2380b2e1b302SLuis R. Rodriguez { 2381fe33eb39SLuis R. Rodriguez struct regulatory_request *request; 2382fe33eb39SLuis R. Rodriguez 2383fdc9d7b2SJohannes Berg if (WARN_ON(!alpha2)) 2384fdc9d7b2SJohannes Berg return -EINVAL; 2385b2e1b302SLuis R. Rodriguez 2386fe33eb39SLuis R. Rodriguez request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL); 2387fe33eb39SLuis R. Rodriguez if (!request) 2388fe33eb39SLuis R. Rodriguez return -ENOMEM; 2389fe33eb39SLuis R. Rodriguez 2390f4173766SJohannes Berg request->wiphy_idx = WIPHY_IDX_INVALID; 2391fe33eb39SLuis R. Rodriguez request->alpha2[0] = alpha2[0]; 2392fe33eb39SLuis R. Rodriguez request->alpha2[1] = alpha2[1]; 2393e12822e1SLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_USER; 239457b5ce07SLuis R. Rodriguez request->user_reg_hint_type = user_reg_hint_type; 2395fe33eb39SLuis R. Rodriguez 2396c37722bdSIlan peer /* Allow calling CRDA again */ 2397b6863036SJohannes Berg reset_crda_timeouts(); 2398c37722bdSIlan peer 2399fe33eb39SLuis R. Rodriguez queue_regulatory_request(request); 2400fe33eb39SLuis R. Rodriguez 2401fe33eb39SLuis R. Rodriguez return 0; 2402fe33eb39SLuis R. Rodriguez } 2403fe33eb39SLuis R. Rodriguez 240405050753SIlan peer int regulatory_hint_indoor(bool is_indoor, u32 portid) 240552616f2bSIlan Peer { 240605050753SIlan peer spin_lock(®_indoor_lock); 240752616f2bSIlan Peer 240805050753SIlan peer /* It is possible that more than one user space process is trying to 240905050753SIlan peer * configure the indoor setting. To handle such cases, clear the indoor 241005050753SIlan peer * setting in case that some process does not think that the device 241105050753SIlan peer * is operating in an indoor environment. In addition, if a user space 241205050753SIlan peer * process indicates that it is controlling the indoor setting, save its 241305050753SIlan peer * portid, i.e., make it the owner. 241405050753SIlan peer */ 241505050753SIlan peer reg_is_indoor = is_indoor; 241605050753SIlan peer if (reg_is_indoor) { 241705050753SIlan peer if (!reg_is_indoor_portid) 241805050753SIlan peer reg_is_indoor_portid = portid; 241905050753SIlan peer } else { 242005050753SIlan peer reg_is_indoor_portid = 0; 242105050753SIlan peer } 242252616f2bSIlan Peer 242305050753SIlan peer spin_unlock(®_indoor_lock); 242405050753SIlan peer 242505050753SIlan peer if (!is_indoor) 242605050753SIlan peer reg_check_channels(); 242752616f2bSIlan Peer 242852616f2bSIlan Peer return 0; 242952616f2bSIlan Peer } 243052616f2bSIlan Peer 243105050753SIlan peer void regulatory_netlink_notify(u32 portid) 243205050753SIlan peer { 243305050753SIlan peer spin_lock(®_indoor_lock); 243405050753SIlan peer 243505050753SIlan peer if (reg_is_indoor_portid != portid) { 243605050753SIlan peer spin_unlock(®_indoor_lock); 243705050753SIlan peer return; 243805050753SIlan peer } 243905050753SIlan peer 244005050753SIlan peer reg_is_indoor = false; 244105050753SIlan peer reg_is_indoor_portid = 0; 244205050753SIlan peer 244305050753SIlan peer spin_unlock(®_indoor_lock); 244405050753SIlan peer 244505050753SIlan peer reg_check_channels(); 244605050753SIlan peer } 244705050753SIlan peer 2448fe33eb39SLuis R. Rodriguez /* Driver hints */ 2449fe33eb39SLuis R. Rodriguez int regulatory_hint(struct wiphy *wiphy, const char *alpha2) 2450fe33eb39SLuis R. Rodriguez { 2451fe33eb39SLuis R. Rodriguez struct regulatory_request *request; 2452fe33eb39SLuis R. Rodriguez 2453fdc9d7b2SJohannes Berg if (WARN_ON(!alpha2 || !wiphy)) 2454fdc9d7b2SJohannes Berg return -EINVAL; 2455fe33eb39SLuis R. Rodriguez 24564f7b9140SLuis R. Rodriguez wiphy->regulatory_flags &= ~REGULATORY_CUSTOM_REG; 24574f7b9140SLuis R. Rodriguez 2458fe33eb39SLuis R. Rodriguez request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL); 2459fe33eb39SLuis R. Rodriguez if (!request) 2460fe33eb39SLuis R. Rodriguez return -ENOMEM; 2461fe33eb39SLuis R. Rodriguez 2462fe33eb39SLuis R. Rodriguez request->wiphy_idx = get_wiphy_idx(wiphy); 2463fe33eb39SLuis R. Rodriguez 2464fe33eb39SLuis R. Rodriguez request->alpha2[0] = alpha2[0]; 2465fe33eb39SLuis R. Rodriguez request->alpha2[1] = alpha2[1]; 24667db90f4aSLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_DRIVER; 2467fe33eb39SLuis R. Rodriguez 2468c37722bdSIlan peer /* Allow calling CRDA again */ 2469b6863036SJohannes Berg reset_crda_timeouts(); 2470c37722bdSIlan peer 2471fe33eb39SLuis R. Rodriguez queue_regulatory_request(request); 2472fe33eb39SLuis R. Rodriguez 2473fe33eb39SLuis R. Rodriguez return 0; 2474b2e1b302SLuis R. Rodriguez } 2475b2e1b302SLuis R. Rodriguez EXPORT_SYMBOL(regulatory_hint); 2476b2e1b302SLuis R. Rodriguez 247757fbcce3SJohannes Berg void regulatory_hint_country_ie(struct wiphy *wiphy, enum nl80211_band band, 24781a919318SJohannes Berg const u8 *country_ie, u8 country_ie_len) 24793f2355cbSLuis R. Rodriguez { 24803f2355cbSLuis R. Rodriguez char alpha2[2]; 24813f2355cbSLuis R. Rodriguez enum environment_cap env = ENVIRON_ANY; 2482db2424c5SJohannes Berg struct regulatory_request *request = NULL, *lr; 2483d335fe63SLuis R. Rodriguez 24843f2355cbSLuis R. Rodriguez /* IE len must be evenly divisible by 2 */ 24853f2355cbSLuis R. Rodriguez if (country_ie_len & 0x01) 2486db2424c5SJohannes Berg return; 24873f2355cbSLuis R. Rodriguez 24883f2355cbSLuis R. Rodriguez if (country_ie_len < IEEE80211_COUNTRY_IE_MIN_LEN) 2489db2424c5SJohannes Berg return; 2490db2424c5SJohannes Berg 2491db2424c5SJohannes Berg request = kzalloc(sizeof(*request), GFP_KERNEL); 2492db2424c5SJohannes Berg if (!request) 2493db2424c5SJohannes Berg return; 24943f2355cbSLuis R. Rodriguez 24953f2355cbSLuis R. Rodriguez alpha2[0] = country_ie[0]; 24963f2355cbSLuis R. Rodriguez alpha2[1] = country_ie[1]; 24973f2355cbSLuis R. Rodriguez 24983f2355cbSLuis R. Rodriguez if (country_ie[2] == 'I') 24993f2355cbSLuis R. Rodriguez env = ENVIRON_INDOOR; 25003f2355cbSLuis R. Rodriguez else if (country_ie[2] == 'O') 25013f2355cbSLuis R. Rodriguez env = ENVIRON_OUTDOOR; 25023f2355cbSLuis R. Rodriguez 2503db2424c5SJohannes Berg rcu_read_lock(); 2504db2424c5SJohannes Berg lr = get_last_request(); 2505db2424c5SJohannes Berg 2506db2424c5SJohannes Berg if (unlikely(!lr)) 2507db2424c5SJohannes Berg goto out; 2508db2424c5SJohannes Berg 2509fb1fc7adSLuis R. Rodriguez /* 25108b19e6caSLuis R. Rodriguez * We will run this only upon a successful connection on cfg80211. 25114b44c8bcSLuis R. Rodriguez * We leave conflict resolution to the workqueue, where can hold 25125fe231e8SJohannes Berg * the RTNL. 2513fb1fc7adSLuis R. Rodriguez */ 2514c492db37SJohannes Berg if (lr->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE && 2515c492db37SJohannes Berg lr->wiphy_idx != WIPHY_IDX_INVALID) 25163f2355cbSLuis R. Rodriguez goto out; 25173f2355cbSLuis R. Rodriguez 2518fe33eb39SLuis R. Rodriguez request->wiphy_idx = get_wiphy_idx(wiphy); 25194f366c5dSJohn W. Linville request->alpha2[0] = alpha2[0]; 25204f366c5dSJohn W. Linville request->alpha2[1] = alpha2[1]; 25217db90f4aSLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_COUNTRY_IE; 2522fe33eb39SLuis R. Rodriguez request->country_ie_env = env; 25233f2355cbSLuis R. Rodriguez 2524c37722bdSIlan peer /* Allow calling CRDA again */ 2525b6863036SJohannes Berg reset_crda_timeouts(); 2526c37722bdSIlan peer 2527fe33eb39SLuis R. Rodriguez queue_regulatory_request(request); 2528db2424c5SJohannes Berg request = NULL; 25293f2355cbSLuis R. Rodriguez out: 2530db2424c5SJohannes Berg kfree(request); 2531db2424c5SJohannes Berg rcu_read_unlock(); 25323f2355cbSLuis R. Rodriguez } 2533b2e1b302SLuis R. Rodriguez 253409d989d1SLuis R. Rodriguez static void restore_alpha2(char *alpha2, bool reset_user) 253509d989d1SLuis R. Rodriguez { 253609d989d1SLuis R. Rodriguez /* indicates there is no alpha2 to consider for restoration */ 253709d989d1SLuis R. Rodriguez alpha2[0] = '9'; 253809d989d1SLuis R. Rodriguez alpha2[1] = '7'; 253909d989d1SLuis R. Rodriguez 254009d989d1SLuis R. Rodriguez /* The user setting has precedence over the module parameter */ 254109d989d1SLuis R. Rodriguez if (is_user_regdom_saved()) { 254209d989d1SLuis R. Rodriguez /* Unless we're asked to ignore it and reset it */ 254309d989d1SLuis R. Rodriguez if (reset_user) { 2544c799ba6eSJohannes Berg pr_debug("Restoring regulatory settings including user preference\n"); 254509d989d1SLuis R. Rodriguez user_alpha2[0] = '9'; 254609d989d1SLuis R. Rodriguez user_alpha2[1] = '7'; 254709d989d1SLuis R. Rodriguez 254809d989d1SLuis R. Rodriguez /* 254909d989d1SLuis R. Rodriguez * If we're ignoring user settings, we still need to 255009d989d1SLuis R. Rodriguez * check the module parameter to ensure we put things 255109d989d1SLuis R. Rodriguez * back as they were for a full restore. 255209d989d1SLuis R. Rodriguez */ 255309d989d1SLuis R. Rodriguez if (!is_world_regdom(ieee80211_regdom)) { 2554c799ba6eSJohannes Berg pr_debug("Keeping preference on module parameter ieee80211_regdom: %c%c\n", 25551a919318SJohannes Berg ieee80211_regdom[0], ieee80211_regdom[1]); 255609d989d1SLuis R. Rodriguez alpha2[0] = ieee80211_regdom[0]; 255709d989d1SLuis R. Rodriguez alpha2[1] = ieee80211_regdom[1]; 255809d989d1SLuis R. Rodriguez } 255909d989d1SLuis R. Rodriguez } else { 2560c799ba6eSJohannes Berg pr_debug("Restoring regulatory settings while preserving user preference for: %c%c\n", 25611a919318SJohannes Berg user_alpha2[0], user_alpha2[1]); 256209d989d1SLuis R. Rodriguez alpha2[0] = user_alpha2[0]; 256309d989d1SLuis R. Rodriguez alpha2[1] = user_alpha2[1]; 256409d989d1SLuis R. Rodriguez } 256509d989d1SLuis R. Rodriguez } else if (!is_world_regdom(ieee80211_regdom)) { 2566c799ba6eSJohannes Berg pr_debug("Keeping preference on module parameter ieee80211_regdom: %c%c\n", 25671a919318SJohannes Berg ieee80211_regdom[0], ieee80211_regdom[1]); 256809d989d1SLuis R. Rodriguez alpha2[0] = ieee80211_regdom[0]; 256909d989d1SLuis R. Rodriguez alpha2[1] = ieee80211_regdom[1]; 257009d989d1SLuis R. Rodriguez } else 2571c799ba6eSJohannes Berg pr_debug("Restoring regulatory settings\n"); 257209d989d1SLuis R. Rodriguez } 257309d989d1SLuis R. Rodriguez 25745ce543d1SRajkumar Manoharan static void restore_custom_reg_settings(struct wiphy *wiphy) 25755ce543d1SRajkumar Manoharan { 25765ce543d1SRajkumar Manoharan struct ieee80211_supported_band *sband; 257757fbcce3SJohannes Berg enum nl80211_band band; 25785ce543d1SRajkumar Manoharan struct ieee80211_channel *chan; 25795ce543d1SRajkumar Manoharan int i; 25805ce543d1SRajkumar Manoharan 258157fbcce3SJohannes Berg for (band = 0; band < NUM_NL80211_BANDS; band++) { 25825ce543d1SRajkumar Manoharan sband = wiphy->bands[band]; 25835ce543d1SRajkumar Manoharan if (!sband) 25845ce543d1SRajkumar Manoharan continue; 25855ce543d1SRajkumar Manoharan for (i = 0; i < sband->n_channels; i++) { 25865ce543d1SRajkumar Manoharan chan = &sband->channels[i]; 25875ce543d1SRajkumar Manoharan chan->flags = chan->orig_flags; 25885ce543d1SRajkumar Manoharan chan->max_antenna_gain = chan->orig_mag; 25895ce543d1SRajkumar Manoharan chan->max_power = chan->orig_mpwr; 2590899852afSPaul Stewart chan->beacon_found = false; 25915ce543d1SRajkumar Manoharan } 25925ce543d1SRajkumar Manoharan } 25935ce543d1SRajkumar Manoharan } 25945ce543d1SRajkumar Manoharan 259509d989d1SLuis R. Rodriguez /* 259609d989d1SLuis R. Rodriguez * Restoring regulatory settings involves ingoring any 259709d989d1SLuis R. Rodriguez * possibly stale country IE information and user regulatory 259809d989d1SLuis R. Rodriguez * settings if so desired, this includes any beacon hints 259909d989d1SLuis R. Rodriguez * learned as we could have traveled outside to another country 260009d989d1SLuis R. Rodriguez * after disconnection. To restore regulatory settings we do 260109d989d1SLuis R. Rodriguez * exactly what we did at bootup: 260209d989d1SLuis R. Rodriguez * 260309d989d1SLuis R. Rodriguez * - send a core regulatory hint 260409d989d1SLuis R. Rodriguez * - send a user regulatory hint if applicable 260509d989d1SLuis R. Rodriguez * 260609d989d1SLuis R. Rodriguez * Device drivers that send a regulatory hint for a specific country 260709d989d1SLuis R. Rodriguez * keep their own regulatory domain on wiphy->regd so that does does 260809d989d1SLuis R. Rodriguez * not need to be remembered. 260909d989d1SLuis R. Rodriguez */ 261009d989d1SLuis R. Rodriguez static void restore_regulatory_settings(bool reset_user) 261109d989d1SLuis R. Rodriguez { 261209d989d1SLuis R. Rodriguez char alpha2[2]; 2613cee0bec5SDmitry Shmidt char world_alpha2[2]; 261409d989d1SLuis R. Rodriguez struct reg_beacon *reg_beacon, *btmp; 261514609555SLuis R. Rodriguez LIST_HEAD(tmp_reg_req_list); 26165ce543d1SRajkumar Manoharan struct cfg80211_registered_device *rdev; 261709d989d1SLuis R. Rodriguez 26185fe231e8SJohannes Berg ASSERT_RTNL(); 26195fe231e8SJohannes Berg 262005050753SIlan peer /* 262105050753SIlan peer * Clear the indoor setting in case that it is not controlled by user 262205050753SIlan peer * space, as otherwise there is no guarantee that the device is still 262305050753SIlan peer * operating in an indoor environment. 262405050753SIlan peer */ 262505050753SIlan peer spin_lock(®_indoor_lock); 262605050753SIlan peer if (reg_is_indoor && !reg_is_indoor_portid) { 262752616f2bSIlan Peer reg_is_indoor = false; 262805050753SIlan peer reg_check_channels(); 262905050753SIlan peer } 263005050753SIlan peer spin_unlock(®_indoor_lock); 263152616f2bSIlan Peer 26322d319867SJohannes Berg reset_regdomains(true, &world_regdom); 263309d989d1SLuis R. Rodriguez restore_alpha2(alpha2, reset_user); 263409d989d1SLuis R. Rodriguez 263514609555SLuis R. Rodriguez /* 263614609555SLuis R. Rodriguez * If there's any pending requests we simply 263714609555SLuis R. Rodriguez * stash them to a temporary pending queue and 263814609555SLuis R. Rodriguez * add then after we've restored regulatory 263914609555SLuis R. Rodriguez * settings. 264014609555SLuis R. Rodriguez */ 264114609555SLuis R. Rodriguez spin_lock(®_requests_lock); 2642eeca9fceSIlan peer list_splice_tail_init(®_requests_list, &tmp_reg_req_list); 264314609555SLuis R. Rodriguez spin_unlock(®_requests_lock); 264414609555SLuis R. Rodriguez 264509d989d1SLuis R. Rodriguez /* Clear beacon hints */ 264609d989d1SLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 2647fea9bcedSJohannes Berg list_for_each_entry_safe(reg_beacon, btmp, ®_pending_beacons, list) { 264809d989d1SLuis R. Rodriguez list_del(®_beacon->list); 264909d989d1SLuis R. Rodriguez kfree(reg_beacon); 265009d989d1SLuis R. Rodriguez } 265109d989d1SLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 265209d989d1SLuis R. Rodriguez 2653fea9bcedSJohannes Berg list_for_each_entry_safe(reg_beacon, btmp, ®_beacon_list, list) { 265409d989d1SLuis R. Rodriguez list_del(®_beacon->list); 265509d989d1SLuis R. Rodriguez kfree(reg_beacon); 265609d989d1SLuis R. Rodriguez } 265709d989d1SLuis R. Rodriguez 265809d989d1SLuis R. Rodriguez /* First restore to the basic regulatory settings */ 2659379b82f4SJohannes Berg world_alpha2[0] = cfg80211_world_regdom->alpha2[0]; 2660379b82f4SJohannes Berg world_alpha2[1] = cfg80211_world_regdom->alpha2[1]; 266109d989d1SLuis R. Rodriguez 26625ce543d1SRajkumar Manoharan list_for_each_entry(rdev, &cfg80211_rdev_list, list) { 2663b0d7aa59SJonathan Doron if (rdev->wiphy.regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED) 2664b0d7aa59SJonathan Doron continue; 2665a2f73b6cSLuis R. Rodriguez if (rdev->wiphy.regulatory_flags & REGULATORY_CUSTOM_REG) 26665ce543d1SRajkumar Manoharan restore_custom_reg_settings(&rdev->wiphy); 26675ce543d1SRajkumar Manoharan } 26685ce543d1SRajkumar Manoharan 2669cee0bec5SDmitry Shmidt regulatory_hint_core(world_alpha2); 267009d989d1SLuis R. Rodriguez 267109d989d1SLuis R. Rodriguez /* 267209d989d1SLuis R. Rodriguez * This restores the ieee80211_regdom module parameter 267309d989d1SLuis R. Rodriguez * preference or the last user requested regulatory 267409d989d1SLuis R. Rodriguez * settings, user regulatory settings takes precedence. 267509d989d1SLuis R. Rodriguez */ 267609d989d1SLuis R. Rodriguez if (is_an_alpha2(alpha2)) 2677549cc1c5SMaciej S. Szmigiero regulatory_hint_user(alpha2, NL80211_USER_REG_HINT_USER); 267809d989d1SLuis R. Rodriguez 267914609555SLuis R. Rodriguez spin_lock(®_requests_lock); 268011cff96cSJohannes Berg list_splice_tail_init(&tmp_reg_req_list, ®_requests_list); 268114609555SLuis R. Rodriguez spin_unlock(®_requests_lock); 268214609555SLuis R. Rodriguez 2683c799ba6eSJohannes Berg pr_debug("Kicking the queue\n"); 268414609555SLuis R. Rodriguez 268514609555SLuis R. Rodriguez schedule_work(®_work); 268614609555SLuis R. Rodriguez } 268709d989d1SLuis R. Rodriguez 268809d989d1SLuis R. Rodriguez void regulatory_hint_disconnect(void) 268909d989d1SLuis R. Rodriguez { 2690c799ba6eSJohannes Berg pr_debug("All devices are disconnected, going to restore regulatory settings\n"); 269109d989d1SLuis R. Rodriguez restore_regulatory_settings(false); 269209d989d1SLuis R. Rodriguez } 269309d989d1SLuis R. Rodriguez 2694e38f8a7aSLuis R. Rodriguez static bool freq_is_chan_12_13_14(u16 freq) 2695e38f8a7aSLuis R. Rodriguez { 269657fbcce3SJohannes Berg if (freq == ieee80211_channel_to_frequency(12, NL80211_BAND_2GHZ) || 269757fbcce3SJohannes Berg freq == ieee80211_channel_to_frequency(13, NL80211_BAND_2GHZ) || 269857fbcce3SJohannes Berg freq == ieee80211_channel_to_frequency(14, NL80211_BAND_2GHZ)) 2699e38f8a7aSLuis R. Rodriguez return true; 2700e38f8a7aSLuis R. Rodriguez return false; 2701e38f8a7aSLuis R. Rodriguez } 2702e38f8a7aSLuis R. Rodriguez 27033ebfa6e7SLuis R. Rodriguez static bool pending_reg_beacon(struct ieee80211_channel *beacon_chan) 27043ebfa6e7SLuis R. Rodriguez { 27053ebfa6e7SLuis R. Rodriguez struct reg_beacon *pending_beacon; 27063ebfa6e7SLuis R. Rodriguez 27073ebfa6e7SLuis R. Rodriguez list_for_each_entry(pending_beacon, ®_pending_beacons, list) 27083ebfa6e7SLuis R. Rodriguez if (beacon_chan->center_freq == 27093ebfa6e7SLuis R. Rodriguez pending_beacon->chan.center_freq) 27103ebfa6e7SLuis R. Rodriguez return true; 27113ebfa6e7SLuis R. Rodriguez return false; 27123ebfa6e7SLuis R. Rodriguez } 27133ebfa6e7SLuis R. Rodriguez 2714e38f8a7aSLuis R. Rodriguez int regulatory_hint_found_beacon(struct wiphy *wiphy, 2715e38f8a7aSLuis R. Rodriguez struct ieee80211_channel *beacon_chan, 2716e38f8a7aSLuis R. Rodriguez gfp_t gfp) 2717e38f8a7aSLuis R. Rodriguez { 2718e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon; 27193ebfa6e7SLuis R. Rodriguez bool processing; 2720e38f8a7aSLuis R. Rodriguez 27211a919318SJohannes Berg if (beacon_chan->beacon_found || 27221a919318SJohannes Berg beacon_chan->flags & IEEE80211_CHAN_RADAR || 272357fbcce3SJohannes Berg (beacon_chan->band == NL80211_BAND_2GHZ && 27241a919318SJohannes Berg !freq_is_chan_12_13_14(beacon_chan->center_freq))) 2725e38f8a7aSLuis R. Rodriguez return 0; 2726e38f8a7aSLuis R. Rodriguez 27273ebfa6e7SLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 27283ebfa6e7SLuis R. Rodriguez processing = pending_reg_beacon(beacon_chan); 27293ebfa6e7SLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 27303ebfa6e7SLuis R. Rodriguez 27313ebfa6e7SLuis R. Rodriguez if (processing) 2732e38f8a7aSLuis R. Rodriguez return 0; 2733e38f8a7aSLuis R. Rodriguez 2734e38f8a7aSLuis R. Rodriguez reg_beacon = kzalloc(sizeof(struct reg_beacon), gfp); 2735e38f8a7aSLuis R. Rodriguez if (!reg_beacon) 2736e38f8a7aSLuis R. Rodriguez return -ENOMEM; 2737e38f8a7aSLuis R. Rodriguez 2738c799ba6eSJohannes Berg pr_debug("Found new beacon on frequency: %d MHz (Ch %d) on %s\n", 2739e38f8a7aSLuis R. Rodriguez beacon_chan->center_freq, 2740e38f8a7aSLuis R. Rodriguez ieee80211_frequency_to_channel(beacon_chan->center_freq), 2741e38f8a7aSLuis R. Rodriguez wiphy_name(wiphy)); 27424113f751SLuis R. Rodriguez 2743e38f8a7aSLuis R. Rodriguez memcpy(®_beacon->chan, beacon_chan, 2744e38f8a7aSLuis R. Rodriguez sizeof(struct ieee80211_channel)); 2745e38f8a7aSLuis R. Rodriguez 2746e38f8a7aSLuis R. Rodriguez /* 2747e38f8a7aSLuis R. Rodriguez * Since we can be called from BH or and non-BH context 2748e38f8a7aSLuis R. Rodriguez * we must use spin_lock_bh() 2749e38f8a7aSLuis R. Rodriguez */ 2750e38f8a7aSLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 2751e38f8a7aSLuis R. Rodriguez list_add_tail(®_beacon->list, ®_pending_beacons); 2752e38f8a7aSLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 2753e38f8a7aSLuis R. Rodriguez 2754e38f8a7aSLuis R. Rodriguez schedule_work(®_work); 2755e38f8a7aSLuis R. Rodriguez 2756e38f8a7aSLuis R. Rodriguez return 0; 2757e38f8a7aSLuis R. Rodriguez } 2758e38f8a7aSLuis R. Rodriguez 2759a3d2eaf0SJohannes Berg static void print_rd_rules(const struct ieee80211_regdomain *rd) 2760b2e1b302SLuis R. Rodriguez { 2761b2e1b302SLuis R. Rodriguez unsigned int i; 2762a3d2eaf0SJohannes Berg const struct ieee80211_reg_rule *reg_rule = NULL; 2763a3d2eaf0SJohannes Berg const struct ieee80211_freq_range *freq_range = NULL; 2764a3d2eaf0SJohannes Berg const struct ieee80211_power_rule *power_rule = NULL; 2765089027e5SJanusz Dziedzic char bw[32], cac_time[32]; 2766b2e1b302SLuis R. Rodriguez 276794c4fd64SDave Young pr_debug(" (start_freq - end_freq @ bandwidth), (max_antenna_gain, max_eirp), (dfs_cac_time)\n"); 2768b2e1b302SLuis R. Rodriguez 2769b2e1b302SLuis R. Rodriguez for (i = 0; i < rd->n_reg_rules; i++) { 2770b2e1b302SLuis R. Rodriguez reg_rule = &rd->reg_rules[i]; 2771b2e1b302SLuis R. Rodriguez freq_range = ®_rule->freq_range; 2772b2e1b302SLuis R. Rodriguez power_rule = ®_rule->power_rule; 2773b2e1b302SLuis R. Rodriguez 2774b0dfd2eaSJanusz Dziedzic if (reg_rule->flags & NL80211_RRF_AUTO_BW) 2775b0dfd2eaSJanusz Dziedzic snprintf(bw, sizeof(bw), "%d KHz, %d KHz AUTO", 2776b0dfd2eaSJanusz Dziedzic freq_range->max_bandwidth_khz, 277797524820SJanusz Dziedzic reg_get_max_bandwidth(rd, reg_rule)); 277897524820SJanusz Dziedzic else 2779b0dfd2eaSJanusz Dziedzic snprintf(bw, sizeof(bw), "%d KHz", 278097524820SJanusz Dziedzic freq_range->max_bandwidth_khz); 278197524820SJanusz Dziedzic 2782089027e5SJanusz Dziedzic if (reg_rule->flags & NL80211_RRF_DFS) 2783089027e5SJanusz Dziedzic scnprintf(cac_time, sizeof(cac_time), "%u s", 2784089027e5SJanusz Dziedzic reg_rule->dfs_cac_ms/1000); 2785089027e5SJanusz Dziedzic else 2786089027e5SJanusz Dziedzic scnprintf(cac_time, sizeof(cac_time), "N/A"); 2787089027e5SJanusz Dziedzic 2788089027e5SJanusz Dziedzic 2789fb1fc7adSLuis R. Rodriguez /* 2790fb1fc7adSLuis R. Rodriguez * There may not be documentation for max antenna gain 2791fb1fc7adSLuis R. Rodriguez * in certain regions 2792fb1fc7adSLuis R. Rodriguez */ 2793b2e1b302SLuis R. Rodriguez if (power_rule->max_antenna_gain) 279494c4fd64SDave Young pr_debug(" (%d KHz - %d KHz @ %s), (%d mBi, %d mBm), (%s)\n", 2795b2e1b302SLuis R. Rodriguez freq_range->start_freq_khz, 2796b2e1b302SLuis R. Rodriguez freq_range->end_freq_khz, 279797524820SJanusz Dziedzic bw, 2798b2e1b302SLuis R. Rodriguez power_rule->max_antenna_gain, 2799089027e5SJanusz Dziedzic power_rule->max_eirp, 2800089027e5SJanusz Dziedzic cac_time); 2801b2e1b302SLuis R. Rodriguez else 280294c4fd64SDave Young pr_debug(" (%d KHz - %d KHz @ %s), (N/A, %d mBm), (%s)\n", 2803b2e1b302SLuis R. Rodriguez freq_range->start_freq_khz, 2804b2e1b302SLuis R. Rodriguez freq_range->end_freq_khz, 280597524820SJanusz Dziedzic bw, 2806089027e5SJanusz Dziedzic power_rule->max_eirp, 2807089027e5SJanusz Dziedzic cac_time); 2808b2e1b302SLuis R. Rodriguez } 2809b2e1b302SLuis R. Rodriguez } 2810b2e1b302SLuis R. Rodriguez 28114c7d3982SLuis R. Rodriguez bool reg_supported_dfs_region(enum nl80211_dfs_regions dfs_region) 28128b60b078SLuis R. Rodriguez { 28138b60b078SLuis R. Rodriguez switch (dfs_region) { 28148b60b078SLuis R. Rodriguez case NL80211_DFS_UNSET: 28158b60b078SLuis R. Rodriguez case NL80211_DFS_FCC: 28168b60b078SLuis R. Rodriguez case NL80211_DFS_ETSI: 28178b60b078SLuis R. Rodriguez case NL80211_DFS_JP: 28188b60b078SLuis R. Rodriguez return true; 28198b60b078SLuis R. Rodriguez default: 2820c799ba6eSJohannes Berg pr_debug("Ignoring uknown DFS master region: %d\n", dfs_region); 28218b60b078SLuis R. Rodriguez return false; 28228b60b078SLuis R. Rodriguez } 28238b60b078SLuis R. Rodriguez } 28248b60b078SLuis R. Rodriguez 2825a3d2eaf0SJohannes Berg static void print_regdomain(const struct ieee80211_regdomain *rd) 2826b2e1b302SLuis R. Rodriguez { 2827c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 2828b2e1b302SLuis R. Rodriguez 28293f2355cbSLuis R. Rodriguez if (is_intersected_alpha2(rd->alpha2)) { 2830c492db37SJohannes Berg if (lr->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE) { 283179c97e97SJohannes Berg struct cfg80211_registered_device *rdev; 2832c492db37SJohannes Berg rdev = cfg80211_rdev_by_wiphy_idx(lr->wiphy_idx); 283379c97e97SJohannes Berg if (rdev) { 283494c4fd64SDave Young pr_debug("Current regulatory domain updated by AP to: %c%c\n", 283579c97e97SJohannes Berg rdev->country_ie_alpha2[0], 283679c97e97SJohannes Berg rdev->country_ie_alpha2[1]); 28373f2355cbSLuis R. Rodriguez } else 283894c4fd64SDave Young pr_debug("Current regulatory domain intersected:\n"); 28393f2355cbSLuis R. Rodriguez } else 284094c4fd64SDave Young pr_debug("Current regulatory domain intersected:\n"); 28411a919318SJohannes Berg } else if (is_world_regdom(rd->alpha2)) { 284294c4fd64SDave Young pr_debug("World regulatory domain updated:\n"); 28431a919318SJohannes Berg } else { 2844b2e1b302SLuis R. Rodriguez if (is_unknown_alpha2(rd->alpha2)) 284594c4fd64SDave Young pr_debug("Regulatory domain changed to driver built-in settings (unknown country)\n"); 284657b5ce07SLuis R. Rodriguez else { 2847c492db37SJohannes Berg if (reg_request_cell_base(lr)) 284894c4fd64SDave Young pr_debug("Regulatory domain changed to country: %c%c by Cell Station\n", 2849b2e1b302SLuis R. Rodriguez rd->alpha2[0], rd->alpha2[1]); 285057b5ce07SLuis R. Rodriguez else 285194c4fd64SDave Young pr_debug("Regulatory domain changed to country: %c%c\n", 285257b5ce07SLuis R. Rodriguez rd->alpha2[0], rd->alpha2[1]); 285357b5ce07SLuis R. Rodriguez } 2854b2e1b302SLuis R. Rodriguez } 28551a919318SJohannes Berg 285694c4fd64SDave Young pr_debug(" DFS Master region: %s", reg_dfs_region_str(rd->dfs_region)); 2857b2e1b302SLuis R. Rodriguez print_rd_rules(rd); 2858b2e1b302SLuis R. Rodriguez } 2859b2e1b302SLuis R. Rodriguez 28602df78167SJohannes Berg static void print_regdomain_info(const struct ieee80211_regdomain *rd) 2861b2e1b302SLuis R. Rodriguez { 286294c4fd64SDave Young pr_debug("Regulatory domain: %c%c\n", rd->alpha2[0], rd->alpha2[1]); 2863b2e1b302SLuis R. Rodriguez print_rd_rules(rd); 2864b2e1b302SLuis R. Rodriguez } 2865b2e1b302SLuis R. Rodriguez 28663b9e5acaSLuis R. Rodriguez static int reg_set_rd_core(const struct ieee80211_regdomain *rd) 28673b9e5acaSLuis R. Rodriguez { 28683b9e5acaSLuis R. Rodriguez if (!is_world_regdom(rd->alpha2)) 28693b9e5acaSLuis R. Rodriguez return -EINVAL; 28703b9e5acaSLuis R. Rodriguez update_world_regdomain(rd); 28713b9e5acaSLuis R. Rodriguez return 0; 28723b9e5acaSLuis R. Rodriguez } 28733b9e5acaSLuis R. Rodriguez 287484721d44SLuis R. Rodriguez static int reg_set_rd_user(const struct ieee80211_regdomain *rd, 287584721d44SLuis R. Rodriguez struct regulatory_request *user_request) 287684721d44SLuis R. Rodriguez { 287784721d44SLuis R. Rodriguez const struct ieee80211_regdomain *intersected_rd = NULL; 287884721d44SLuis R. Rodriguez 287984721d44SLuis R. Rodriguez if (!regdom_changes(rd->alpha2)) 288084721d44SLuis R. Rodriguez return -EALREADY; 288184721d44SLuis R. Rodriguez 288284721d44SLuis R. Rodriguez if (!is_valid_rd(rd)) { 288394c4fd64SDave Young pr_err("Invalid regulatory domain detected: %c%c\n", 288494c4fd64SDave Young rd->alpha2[0], rd->alpha2[1]); 288584721d44SLuis R. Rodriguez print_regdomain_info(rd); 288684721d44SLuis R. Rodriguez return -EINVAL; 288784721d44SLuis R. Rodriguez } 288884721d44SLuis R. Rodriguez 288984721d44SLuis R. Rodriguez if (!user_request->intersect) { 289084721d44SLuis R. Rodriguez reset_regdomains(false, rd); 289184721d44SLuis R. Rodriguez return 0; 289284721d44SLuis R. Rodriguez } 289384721d44SLuis R. Rodriguez 289484721d44SLuis R. Rodriguez intersected_rd = regdom_intersect(rd, get_cfg80211_regdom()); 289584721d44SLuis R. Rodriguez if (!intersected_rd) 289684721d44SLuis R. Rodriguez return -EINVAL; 289784721d44SLuis R. Rodriguez 289884721d44SLuis R. Rodriguez kfree(rd); 289984721d44SLuis R. Rodriguez rd = NULL; 290084721d44SLuis R. Rodriguez reset_regdomains(false, intersected_rd); 290184721d44SLuis R. Rodriguez 290284721d44SLuis R. Rodriguez return 0; 290384721d44SLuis R. Rodriguez } 290484721d44SLuis R. Rodriguez 2905f5fe3247SLuis R. Rodriguez static int reg_set_rd_driver(const struct ieee80211_regdomain *rd, 2906f5fe3247SLuis R. Rodriguez struct regulatory_request *driver_request) 2907b2e1b302SLuis R. Rodriguez { 2908e9763c3cSJohannes Berg const struct ieee80211_regdomain *regd; 29099c96477dSLuis R. Rodriguez const struct ieee80211_regdomain *intersected_rd = NULL; 2910f5fe3247SLuis R. Rodriguez const struct ieee80211_regdomain *tmp; 2911806a9e39SLuis R. Rodriguez struct wiphy *request_wiphy; 29126913b49aSJohannes Berg 2913f5fe3247SLuis R. Rodriguez if (is_world_regdom(rd->alpha2)) 2914b2e1b302SLuis R. Rodriguez return -EINVAL; 2915b2e1b302SLuis R. Rodriguez 2916baeb66feSJohn W. Linville if (!regdom_changes(rd->alpha2)) 291795908535SKalle Valo return -EALREADY; 2918b2e1b302SLuis R. Rodriguez 2919b2e1b302SLuis R. Rodriguez if (!is_valid_rd(rd)) { 292094c4fd64SDave Young pr_err("Invalid regulatory domain detected: %c%c\n", 292194c4fd64SDave Young rd->alpha2[0], rd->alpha2[1]); 2922b2e1b302SLuis R. Rodriguez print_regdomain_info(rd); 2923b2e1b302SLuis R. Rodriguez return -EINVAL; 2924b2e1b302SLuis R. Rodriguez } 2925b2e1b302SLuis R. Rodriguez 2926f5fe3247SLuis R. Rodriguez request_wiphy = wiphy_idx_to_wiphy(driver_request->wiphy_idx); 2927922ec58cSJohannes Berg if (!request_wiphy) 2928de3584bdSJohannes Berg return -ENODEV; 2929806a9e39SLuis R. Rodriguez 2930f5fe3247SLuis R. Rodriguez if (!driver_request->intersect) { 2931558f6d32SLuis R. Rodriguez if (request_wiphy->regd) 2932558f6d32SLuis R. Rodriguez return -EALREADY; 29333e0c3ff3SLuis R. Rodriguez 2934e9763c3cSJohannes Berg regd = reg_copy_regd(rd); 2935e9763c3cSJohannes Berg if (IS_ERR(regd)) 2936e9763c3cSJohannes Berg return PTR_ERR(regd); 29373e0c3ff3SLuis R. Rodriguez 2938458f4f9eSJohannes Berg rcu_assign_pointer(request_wiphy->regd, regd); 2939379b82f4SJohannes Berg reset_regdomains(false, rd); 2940b8295acdSLuis R. Rodriguez return 0; 2941b8295acdSLuis R. Rodriguez } 2942b8295acdSLuis R. Rodriguez 2943458f4f9eSJohannes Berg intersected_rd = regdom_intersect(rd, get_cfg80211_regdom()); 29449c96477dSLuis R. Rodriguez if (!intersected_rd) 29459c96477dSLuis R. Rodriguez return -EINVAL; 2946b8295acdSLuis R. Rodriguez 2947fb1fc7adSLuis R. Rodriguez /* 2948fb1fc7adSLuis R. Rodriguez * We can trash what CRDA provided now. 29493e0c3ff3SLuis R. Rodriguez * However if a driver requested this specific regulatory 2950fb1fc7adSLuis R. Rodriguez * domain we keep it for its private use 2951fb1fc7adSLuis R. Rodriguez */ 2952b7566fc3SLarry Finger tmp = get_wiphy_regdom(request_wiphy); 2953458f4f9eSJohannes Berg rcu_assign_pointer(request_wiphy->regd, rd); 2954b7566fc3SLarry Finger rcu_free_regdom(tmp); 29553e0c3ff3SLuis R. Rodriguez 2956b8295acdSLuis R. Rodriguez rd = NULL; 2957b8295acdSLuis R. Rodriguez 2958379b82f4SJohannes Berg reset_regdomains(false, intersected_rd); 2959b8295acdSLuis R. Rodriguez 2960b8295acdSLuis R. Rodriguez return 0; 29619c96477dSLuis R. Rodriguez } 29629c96477dSLuis R. Rodriguez 296301992406SLuis R. Rodriguez static int reg_set_rd_country_ie(const struct ieee80211_regdomain *rd, 296401992406SLuis R. Rodriguez struct regulatory_request *country_ie_request) 2965f5fe3247SLuis R. Rodriguez { 2966f5fe3247SLuis R. Rodriguez struct wiphy *request_wiphy; 2967f5fe3247SLuis R. Rodriguez 2968f5fe3247SLuis R. Rodriguez if (!is_alpha2_set(rd->alpha2) && !is_an_alpha2(rd->alpha2) && 2969f5fe3247SLuis R. Rodriguez !is_unknown_alpha2(rd->alpha2)) 2970f5fe3247SLuis R. Rodriguez return -EINVAL; 2971f5fe3247SLuis R. Rodriguez 2972f5fe3247SLuis R. Rodriguez /* 2973f5fe3247SLuis R. Rodriguez * Lets only bother proceeding on the same alpha2 if the current 2974f5fe3247SLuis R. Rodriguez * rd is non static (it means CRDA was present and was used last) 2975f5fe3247SLuis R. Rodriguez * and the pending request came in from a country IE 2976f5fe3247SLuis R. Rodriguez */ 2977f5fe3247SLuis R. Rodriguez 2978f5fe3247SLuis R. Rodriguez if (!is_valid_rd(rd)) { 297994c4fd64SDave Young pr_err("Invalid regulatory domain detected: %c%c\n", 298094c4fd64SDave Young rd->alpha2[0], rd->alpha2[1]); 2981f5fe3247SLuis R. Rodriguez print_regdomain_info(rd); 29823f2355cbSLuis R. Rodriguez return -EINVAL; 2983b2e1b302SLuis R. Rodriguez } 2984b2e1b302SLuis R. Rodriguez 298501992406SLuis R. Rodriguez request_wiphy = wiphy_idx_to_wiphy(country_ie_request->wiphy_idx); 2986922ec58cSJohannes Berg if (!request_wiphy) 2987f5fe3247SLuis R. Rodriguez return -ENODEV; 2988f5fe3247SLuis R. Rodriguez 298901992406SLuis R. Rodriguez if (country_ie_request->intersect) 2990f5fe3247SLuis R. Rodriguez return -EINVAL; 2991f5fe3247SLuis R. Rodriguez 2992f5fe3247SLuis R. Rodriguez reset_regdomains(false, rd); 2993f5fe3247SLuis R. Rodriguez return 0; 2994f5fe3247SLuis R. Rodriguez } 2995b2e1b302SLuis R. Rodriguez 2996fb1fc7adSLuis R. Rodriguez /* 2997fb1fc7adSLuis R. Rodriguez * Use this call to set the current regulatory domain. Conflicts with 2998b2e1b302SLuis R. Rodriguez * multiple drivers can be ironed out later. Caller must've already 2999458f4f9eSJohannes Berg * kmalloc'd the rd structure. 3000fb1fc7adSLuis R. Rodriguez */ 3001c37722bdSIlan peer int set_regdom(const struct ieee80211_regdomain *rd, 3002c37722bdSIlan peer enum ieee80211_regd_source regd_src) 3003b2e1b302SLuis R. Rodriguez { 3004c492db37SJohannes Berg struct regulatory_request *lr; 3005092008abSJanusz Dziedzic bool user_reset = false; 3006b2e1b302SLuis R. Rodriguez int r; 3007b2e1b302SLuis R. Rodriguez 30083b9e5acaSLuis R. Rodriguez if (!reg_is_valid_request(rd->alpha2)) { 30093b9e5acaSLuis R. Rodriguez kfree(rd); 30103b9e5acaSLuis R. Rodriguez return -EINVAL; 30113b9e5acaSLuis R. Rodriguez } 30123b9e5acaSLuis R. Rodriguez 3013c37722bdSIlan peer if (regd_src == REGD_SOURCE_CRDA) 3014b6863036SJohannes Berg reset_crda_timeouts(); 3015c37722bdSIlan peer 3016c492db37SJohannes Berg lr = get_last_request(); 3017abc7381bSLuis R. Rodriguez 3018b2e1b302SLuis R. Rodriguez /* Note that this doesn't update the wiphys, this is done below */ 30193b9e5acaSLuis R. Rodriguez switch (lr->initiator) { 30203b9e5acaSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_CORE: 30213b9e5acaSLuis R. Rodriguez r = reg_set_rd_core(rd); 30223b9e5acaSLuis R. Rodriguez break; 30233b9e5acaSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_USER: 302484721d44SLuis R. Rodriguez r = reg_set_rd_user(rd, lr); 3025092008abSJanusz Dziedzic user_reset = true; 302684721d44SLuis R. Rodriguez break; 30273b9e5acaSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_DRIVER: 3028f5fe3247SLuis R. Rodriguez r = reg_set_rd_driver(rd, lr); 3029f5fe3247SLuis R. Rodriguez break; 30303b9e5acaSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_COUNTRY_IE: 303101992406SLuis R. Rodriguez r = reg_set_rd_country_ie(rd, lr); 30323b9e5acaSLuis R. Rodriguez break; 30333b9e5acaSLuis R. Rodriguez default: 30343b9e5acaSLuis R. Rodriguez WARN(1, "invalid initiator %d\n", lr->initiator); 303509d11800SOla Olsson kfree(rd); 30363b9e5acaSLuis R. Rodriguez return -EINVAL; 30373b9e5acaSLuis R. Rodriguez } 30383b9e5acaSLuis R. Rodriguez 3039d2372b31SJohannes Berg if (r) { 3040092008abSJanusz Dziedzic switch (r) { 3041092008abSJanusz Dziedzic case -EALREADY: 304295908535SKalle Valo reg_set_request_processed(); 3043092008abSJanusz Dziedzic break; 3044092008abSJanusz Dziedzic default: 3045092008abSJanusz Dziedzic /* Back to world regulatory in case of errors */ 3046092008abSJanusz Dziedzic restore_regulatory_settings(user_reset); 3047092008abSJanusz Dziedzic } 304895908535SKalle Valo 3049d2372b31SJohannes Berg kfree(rd); 305038fd2143SJohannes Berg return r; 3051d2372b31SJohannes Berg } 3052b2e1b302SLuis R. Rodriguez 3053b2e1b302SLuis R. Rodriguez /* This would make this whole thing pointless */ 305438fd2143SJohannes Berg if (WARN_ON(!lr->intersect && rd != get_cfg80211_regdom())) 305538fd2143SJohannes Berg return -EINVAL; 3056b2e1b302SLuis R. Rodriguez 3057b2e1b302SLuis R. Rodriguez /* update all wiphys now with the new established regulatory domain */ 3058c492db37SJohannes Berg update_all_wiphy_regulatory(lr->initiator); 3059b2e1b302SLuis R. Rodriguez 3060458f4f9eSJohannes Berg print_regdomain(get_cfg80211_regdom()); 3061b2e1b302SLuis R. Rodriguez 3062c492db37SJohannes Berg nl80211_send_reg_change_event(lr); 306373d54c9eSLuis R. Rodriguez 3064b2e253cfSLuis R. Rodriguez reg_set_request_processed(); 3065b2e253cfSLuis R. Rodriguez 306638fd2143SJohannes Berg return 0; 3067b2e1b302SLuis R. Rodriguez } 3068b2e1b302SLuis R. Rodriguez 30692c3e861cSArik Nemtsov static int __regulatory_set_wiphy_regd(struct wiphy *wiphy, 3070b0d7aa59SJonathan Doron struct ieee80211_regdomain *rd) 3071b0d7aa59SJonathan Doron { 3072b0d7aa59SJonathan Doron const struct ieee80211_regdomain *regd; 3073b0d7aa59SJonathan Doron const struct ieee80211_regdomain *prev_regd; 3074b0d7aa59SJonathan Doron struct cfg80211_registered_device *rdev; 3075b0d7aa59SJonathan Doron 3076b0d7aa59SJonathan Doron if (WARN_ON(!wiphy || !rd)) 3077b0d7aa59SJonathan Doron return -EINVAL; 3078b0d7aa59SJonathan Doron 3079b0d7aa59SJonathan Doron if (WARN(!(wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED), 3080b0d7aa59SJonathan Doron "wiphy should have REGULATORY_WIPHY_SELF_MANAGED\n")) 3081b0d7aa59SJonathan Doron return -EPERM; 3082b0d7aa59SJonathan Doron 3083b0d7aa59SJonathan Doron if (WARN(!is_valid_rd(rd), "Invalid regulatory domain detected\n")) { 3084b0d7aa59SJonathan Doron print_regdomain_info(rd); 3085b0d7aa59SJonathan Doron return -EINVAL; 3086b0d7aa59SJonathan Doron } 3087b0d7aa59SJonathan Doron 3088b0d7aa59SJonathan Doron regd = reg_copy_regd(rd); 3089b0d7aa59SJonathan Doron if (IS_ERR(regd)) 3090b0d7aa59SJonathan Doron return PTR_ERR(regd); 3091b0d7aa59SJonathan Doron 3092b0d7aa59SJonathan Doron rdev = wiphy_to_rdev(wiphy); 3093b0d7aa59SJonathan Doron 3094b0d7aa59SJonathan Doron spin_lock(®_requests_lock); 3095b0d7aa59SJonathan Doron prev_regd = rdev->requested_regd; 3096b0d7aa59SJonathan Doron rdev->requested_regd = regd; 3097b0d7aa59SJonathan Doron spin_unlock(®_requests_lock); 3098b0d7aa59SJonathan Doron 3099b0d7aa59SJonathan Doron kfree(prev_regd); 31002c3e861cSArik Nemtsov return 0; 31012c3e861cSArik Nemtsov } 31022c3e861cSArik Nemtsov 31032c3e861cSArik Nemtsov int regulatory_set_wiphy_regd(struct wiphy *wiphy, 31042c3e861cSArik Nemtsov struct ieee80211_regdomain *rd) 31052c3e861cSArik Nemtsov { 31062c3e861cSArik Nemtsov int ret = __regulatory_set_wiphy_regd(wiphy, rd); 31072c3e861cSArik Nemtsov 31082c3e861cSArik Nemtsov if (ret) 31092c3e861cSArik Nemtsov return ret; 3110b0d7aa59SJonathan Doron 3111b0d7aa59SJonathan Doron schedule_work(®_work); 3112b0d7aa59SJonathan Doron return 0; 3113b0d7aa59SJonathan Doron } 3114b0d7aa59SJonathan Doron EXPORT_SYMBOL(regulatory_set_wiphy_regd); 3115b0d7aa59SJonathan Doron 31162c3e861cSArik Nemtsov int regulatory_set_wiphy_regd_sync_rtnl(struct wiphy *wiphy, 31172c3e861cSArik Nemtsov struct ieee80211_regdomain *rd) 31182c3e861cSArik Nemtsov { 31192c3e861cSArik Nemtsov int ret; 31202c3e861cSArik Nemtsov 31212c3e861cSArik Nemtsov ASSERT_RTNL(); 31222c3e861cSArik Nemtsov 31232c3e861cSArik Nemtsov ret = __regulatory_set_wiphy_regd(wiphy, rd); 31242c3e861cSArik Nemtsov if (ret) 31252c3e861cSArik Nemtsov return ret; 31262c3e861cSArik Nemtsov 31272c3e861cSArik Nemtsov /* process the request immediately */ 31282c3e861cSArik Nemtsov reg_process_self_managed_hints(); 31292c3e861cSArik Nemtsov return 0; 31302c3e861cSArik Nemtsov } 31312c3e861cSArik Nemtsov EXPORT_SYMBOL(regulatory_set_wiphy_regd_sync_rtnl); 31322c3e861cSArik Nemtsov 313357b5ce07SLuis R. Rodriguez void wiphy_regulatory_register(struct wiphy *wiphy) 313457b5ce07SLuis R. Rodriguez { 313523df0b73SArik Nemtsov struct regulatory_request *lr; 313623df0b73SArik Nemtsov 3137b0d7aa59SJonathan Doron /* self-managed devices ignore external hints */ 3138b0d7aa59SJonathan Doron if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED) 3139b0d7aa59SJonathan Doron wiphy->regulatory_flags |= REGULATORY_DISABLE_BEACON_HINTS | 3140b0d7aa59SJonathan Doron REGULATORY_COUNTRY_IE_IGNORE; 3141b0d7aa59SJonathan Doron 314257b5ce07SLuis R. Rodriguez if (!reg_dev_ignore_cell_hint(wiphy)) 314357b5ce07SLuis R. Rodriguez reg_num_devs_support_basehint++; 314457b5ce07SLuis R. Rodriguez 314523df0b73SArik Nemtsov lr = get_last_request(); 314623df0b73SArik Nemtsov wiphy_update_regulatory(wiphy, lr->initiator); 314789766727SVasanthakumar Thiagarajan wiphy_all_share_dfs_chan_state(wiphy); 314857b5ce07SLuis R. Rodriguez } 314957b5ce07SLuis R. Rodriguez 3150bfead080SLuis R. Rodriguez void wiphy_regulatory_deregister(struct wiphy *wiphy) 31513f2355cbSLuis R. Rodriguez { 31520ad8acafSLuis R. Rodriguez struct wiphy *request_wiphy = NULL; 3153c492db37SJohannes Berg struct regulatory_request *lr; 3154761cf7ecSLuis R. Rodriguez 3155c492db37SJohannes Berg lr = get_last_request(); 3156abc7381bSLuis R. Rodriguez 315757b5ce07SLuis R. Rodriguez if (!reg_dev_ignore_cell_hint(wiphy)) 315857b5ce07SLuis R. Rodriguez reg_num_devs_support_basehint--; 315957b5ce07SLuis R. Rodriguez 3160458f4f9eSJohannes Berg rcu_free_regdom(get_wiphy_regdom(wiphy)); 316134dd886cSMonam Agarwal RCU_INIT_POINTER(wiphy->regd, NULL); 31620ef9ccddSChris Wright 3163c492db37SJohannes Berg if (lr) 3164c492db37SJohannes Berg request_wiphy = wiphy_idx_to_wiphy(lr->wiphy_idx); 3165806a9e39SLuis R. Rodriguez 31660ef9ccddSChris Wright if (!request_wiphy || request_wiphy != wiphy) 316738fd2143SJohannes Berg return; 31680ef9ccddSChris Wright 3169c492db37SJohannes Berg lr->wiphy_idx = WIPHY_IDX_INVALID; 3170c492db37SJohannes Berg lr->country_ie_env = ENVIRON_ANY; 31713f2355cbSLuis R. Rodriguez } 31723f2355cbSLuis R. Rodriguez 3173174e0cd2SIlan Peer /* 3174174e0cd2SIlan Peer * See http://www.fcc.gov/document/5-ghz-unlicensed-spectrum-unii, for 3175174e0cd2SIlan Peer * UNII band definitions 3176174e0cd2SIlan Peer */ 3177174e0cd2SIlan Peer int cfg80211_get_unii(int freq) 3178174e0cd2SIlan Peer { 3179174e0cd2SIlan Peer /* UNII-1 */ 3180174e0cd2SIlan Peer if (freq >= 5150 && freq <= 5250) 3181174e0cd2SIlan Peer return 0; 3182174e0cd2SIlan Peer 3183174e0cd2SIlan Peer /* UNII-2A */ 3184174e0cd2SIlan Peer if (freq > 5250 && freq <= 5350) 3185174e0cd2SIlan Peer return 1; 3186174e0cd2SIlan Peer 3187174e0cd2SIlan Peer /* UNII-2B */ 3188174e0cd2SIlan Peer if (freq > 5350 && freq <= 5470) 3189174e0cd2SIlan Peer return 2; 3190174e0cd2SIlan Peer 3191174e0cd2SIlan Peer /* UNII-2C */ 3192174e0cd2SIlan Peer if (freq > 5470 && freq <= 5725) 3193174e0cd2SIlan Peer return 3; 3194174e0cd2SIlan Peer 3195174e0cd2SIlan Peer /* UNII-3 */ 3196174e0cd2SIlan Peer if (freq > 5725 && freq <= 5825) 3197174e0cd2SIlan Peer return 4; 3198174e0cd2SIlan Peer 3199174e0cd2SIlan Peer return -EINVAL; 3200174e0cd2SIlan Peer } 3201174e0cd2SIlan Peer 3202c8866e55SIlan Peer bool regulatory_indoor_allowed(void) 3203c8866e55SIlan Peer { 3204c8866e55SIlan Peer return reg_is_indoor; 3205c8866e55SIlan Peer } 3206c8866e55SIlan Peer 3207b35a51c7SVasanthakumar Thiagarajan bool regulatory_pre_cac_allowed(struct wiphy *wiphy) 3208b35a51c7SVasanthakumar Thiagarajan { 3209b35a51c7SVasanthakumar Thiagarajan const struct ieee80211_regdomain *regd = NULL; 3210b35a51c7SVasanthakumar Thiagarajan const struct ieee80211_regdomain *wiphy_regd = NULL; 3211b35a51c7SVasanthakumar Thiagarajan bool pre_cac_allowed = false; 3212b35a51c7SVasanthakumar Thiagarajan 3213b35a51c7SVasanthakumar Thiagarajan rcu_read_lock(); 3214b35a51c7SVasanthakumar Thiagarajan 3215b35a51c7SVasanthakumar Thiagarajan regd = rcu_dereference(cfg80211_regdomain); 3216b35a51c7SVasanthakumar Thiagarajan wiphy_regd = rcu_dereference(wiphy->regd); 3217b35a51c7SVasanthakumar Thiagarajan if (!wiphy_regd) { 3218b35a51c7SVasanthakumar Thiagarajan if (regd->dfs_region == NL80211_DFS_ETSI) 3219b35a51c7SVasanthakumar Thiagarajan pre_cac_allowed = true; 3220b35a51c7SVasanthakumar Thiagarajan 3221b35a51c7SVasanthakumar Thiagarajan rcu_read_unlock(); 3222b35a51c7SVasanthakumar Thiagarajan 3223b35a51c7SVasanthakumar Thiagarajan return pre_cac_allowed; 3224b35a51c7SVasanthakumar Thiagarajan } 3225b35a51c7SVasanthakumar Thiagarajan 3226b35a51c7SVasanthakumar Thiagarajan if (regd->dfs_region == wiphy_regd->dfs_region && 3227b35a51c7SVasanthakumar Thiagarajan wiphy_regd->dfs_region == NL80211_DFS_ETSI) 3228b35a51c7SVasanthakumar Thiagarajan pre_cac_allowed = true; 3229b35a51c7SVasanthakumar Thiagarajan 3230b35a51c7SVasanthakumar Thiagarajan rcu_read_unlock(); 3231b35a51c7SVasanthakumar Thiagarajan 3232b35a51c7SVasanthakumar Thiagarajan return pre_cac_allowed; 3233b35a51c7SVasanthakumar Thiagarajan } 3234b35a51c7SVasanthakumar Thiagarajan 323589766727SVasanthakumar Thiagarajan void regulatory_propagate_dfs_state(struct wiphy *wiphy, 323689766727SVasanthakumar Thiagarajan struct cfg80211_chan_def *chandef, 323789766727SVasanthakumar Thiagarajan enum nl80211_dfs_state dfs_state, 323889766727SVasanthakumar Thiagarajan enum nl80211_radar_event event) 323989766727SVasanthakumar Thiagarajan { 324089766727SVasanthakumar Thiagarajan struct cfg80211_registered_device *rdev; 324189766727SVasanthakumar Thiagarajan 324289766727SVasanthakumar Thiagarajan ASSERT_RTNL(); 324389766727SVasanthakumar Thiagarajan 324489766727SVasanthakumar Thiagarajan if (WARN_ON(!cfg80211_chandef_valid(chandef))) 324589766727SVasanthakumar Thiagarajan return; 324689766727SVasanthakumar Thiagarajan 324789766727SVasanthakumar Thiagarajan if (WARN_ON(!(chandef->chan->flags & IEEE80211_CHAN_RADAR))) 324889766727SVasanthakumar Thiagarajan return; 324989766727SVasanthakumar Thiagarajan 325089766727SVasanthakumar Thiagarajan list_for_each_entry(rdev, &cfg80211_rdev_list, list) { 325189766727SVasanthakumar Thiagarajan if (wiphy == &rdev->wiphy) 325289766727SVasanthakumar Thiagarajan continue; 325389766727SVasanthakumar Thiagarajan 325489766727SVasanthakumar Thiagarajan if (!reg_dfs_domain_same(wiphy, &rdev->wiphy)) 325589766727SVasanthakumar Thiagarajan continue; 325689766727SVasanthakumar Thiagarajan 325789766727SVasanthakumar Thiagarajan if (!ieee80211_get_channel(&rdev->wiphy, 325889766727SVasanthakumar Thiagarajan chandef->chan->center_freq)) 325989766727SVasanthakumar Thiagarajan continue; 326089766727SVasanthakumar Thiagarajan 326189766727SVasanthakumar Thiagarajan cfg80211_set_dfs_state(&rdev->wiphy, chandef, dfs_state); 326289766727SVasanthakumar Thiagarajan 326389766727SVasanthakumar Thiagarajan if (event == NL80211_RADAR_DETECTED || 326489766727SVasanthakumar Thiagarajan event == NL80211_RADAR_CAC_FINISHED) 326589766727SVasanthakumar Thiagarajan cfg80211_sched_dfs_chan_update(rdev); 326689766727SVasanthakumar Thiagarajan 326789766727SVasanthakumar Thiagarajan nl80211_radar_notify(rdev, chandef, event, NULL, GFP_KERNEL); 326889766727SVasanthakumar Thiagarajan } 326989766727SVasanthakumar Thiagarajan } 327089766727SVasanthakumar Thiagarajan 32712fcc9f73SUwe Kleine-König int __init regulatory_init(void) 3272b2e1b302SLuis R. Rodriguez { 3273bcf4f99bSLuis R. Rodriguez int err = 0; 3274734366deSJohannes Berg 3275b2e1b302SLuis R. Rodriguez reg_pdev = platform_device_register_simple("regulatory", 0, NULL, 0); 3276b2e1b302SLuis R. Rodriguez if (IS_ERR(reg_pdev)) 3277b2e1b302SLuis R. Rodriguez return PTR_ERR(reg_pdev); 3278734366deSJohannes Berg 3279fe33eb39SLuis R. Rodriguez spin_lock_init(®_requests_lock); 3280e38f8a7aSLuis R. Rodriguez spin_lock_init(®_pending_beacons_lock); 328105050753SIlan peer spin_lock_init(®_indoor_lock); 3282fe33eb39SLuis R. Rodriguez 328380007efeSLuis R. Rodriguez reg_regdb_size_check(); 328480007efeSLuis R. Rodriguez 3285458f4f9eSJohannes Berg rcu_assign_pointer(cfg80211_regdomain, cfg80211_world_regdom); 3286734366deSJohannes Berg 328709d989d1SLuis R. Rodriguez user_alpha2[0] = '9'; 328809d989d1SLuis R. Rodriguez user_alpha2[1] = '7'; 328909d989d1SLuis R. Rodriguez 3290ae9e4b0dSLuis R. Rodriguez /* We always try to get an update for the static regdomain */ 3291458f4f9eSJohannes Berg err = regulatory_hint_core(cfg80211_world_regdom->alpha2); 3292bcf4f99bSLuis R. Rodriguez if (err) { 329309d11800SOla Olsson if (err == -ENOMEM) { 329409d11800SOla Olsson platform_device_unregister(reg_pdev); 3295bcf4f99bSLuis R. Rodriguez return err; 329609d11800SOla Olsson } 3297bcf4f99bSLuis R. Rodriguez /* 3298bcf4f99bSLuis R. Rodriguez * N.B. kobject_uevent_env() can fail mainly for when we're out 3299bcf4f99bSLuis R. Rodriguez * memory which is handled and propagated appropriately above 3300bcf4f99bSLuis R. Rodriguez * but it can also fail during a netlink_broadcast() or during 3301bcf4f99bSLuis R. Rodriguez * early boot for call_usermodehelper(). For now treat these 3302bcf4f99bSLuis R. Rodriguez * errors as non-fatal. 3303bcf4f99bSLuis R. Rodriguez */ 3304e9c0268fSJoe Perches pr_err("kobject_uevent_env() was unable to call CRDA during init\n"); 3305bcf4f99bSLuis R. Rodriguez } 3306734366deSJohannes Berg 3307ae9e4b0dSLuis R. Rodriguez /* 3308ae9e4b0dSLuis R. Rodriguez * Finally, if the user set the module parameter treat it 3309ae9e4b0dSLuis R. Rodriguez * as a user hint. 3310ae9e4b0dSLuis R. Rodriguez */ 3311ae9e4b0dSLuis R. Rodriguez if (!is_world_regdom(ieee80211_regdom)) 331257b5ce07SLuis R. Rodriguez regulatory_hint_user(ieee80211_regdom, 331357b5ce07SLuis R. Rodriguez NL80211_USER_REG_HINT_USER); 3314ae9e4b0dSLuis R. Rodriguez 3315b2e1b302SLuis R. Rodriguez return 0; 3316b2e1b302SLuis R. Rodriguez } 3317b2e1b302SLuis R. Rodriguez 33181a919318SJohannes Berg void regulatory_exit(void) 3319b2e1b302SLuis R. Rodriguez { 3320fe33eb39SLuis R. Rodriguez struct regulatory_request *reg_request, *tmp; 3321e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon, *btmp; 3322fe33eb39SLuis R. Rodriguez 3323fe33eb39SLuis R. Rodriguez cancel_work_sync(®_work); 3324b6863036SJohannes Berg cancel_crda_timeout_sync(); 3325ad932f04SArik Nemtsov cancel_delayed_work_sync(®_check_chans); 3326fe33eb39SLuis R. Rodriguez 33279027b149SJohannes Berg /* Lock to suppress warnings */ 332838fd2143SJohannes Berg rtnl_lock(); 3329379b82f4SJohannes Berg reset_regdomains(true, NULL); 333038fd2143SJohannes Berg rtnl_unlock(); 3331734366deSJohannes Berg 333258ebacc6SLuis R. Rodriguez dev_set_uevent_suppress(®_pdev->dev, true); 3333f6037d09SJohannes Berg 3334b2e1b302SLuis R. Rodriguez platform_device_unregister(reg_pdev); 3335734366deSJohannes Berg 3336fea9bcedSJohannes Berg list_for_each_entry_safe(reg_beacon, btmp, ®_pending_beacons, list) { 3337e38f8a7aSLuis R. Rodriguez list_del(®_beacon->list); 3338e38f8a7aSLuis R. Rodriguez kfree(reg_beacon); 3339e38f8a7aSLuis R. Rodriguez } 3340e38f8a7aSLuis R. Rodriguez 3341fea9bcedSJohannes Berg list_for_each_entry_safe(reg_beacon, btmp, ®_beacon_list, list) { 3342e38f8a7aSLuis R. Rodriguez list_del(®_beacon->list); 3343e38f8a7aSLuis R. Rodriguez kfree(reg_beacon); 3344e38f8a7aSLuis R. Rodriguez } 3345e38f8a7aSLuis R. Rodriguez 3346fea9bcedSJohannes Berg list_for_each_entry_safe(reg_request, tmp, ®_requests_list, list) { 3347fe33eb39SLuis R. Rodriguez list_del(®_request->list); 3348fe33eb39SLuis R. Rodriguez kfree(reg_request); 3349fe33eb39SLuis R. Rodriguez } 3350fe33eb39SLuis R. Rodriguez } 3351