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 74e0854a7SEmmanuel Grumbach * Copyright 2017 Intel Deutschland GmbH 87b0a0e3cSJohannes Berg * Copyright (C) 2018 - 2022 Intel Corporation 98318d78aSJohannes Berg * 103b77d5ecSLuis R. Rodriguez * Permission to use, copy, modify, and/or distribute this software for any 113b77d5ecSLuis R. Rodriguez * purpose with or without fee is hereby granted, provided that the above 123b77d5ecSLuis R. Rodriguez * copyright notice and this permission notice appear in all copies. 133b77d5ecSLuis R. Rodriguez * 143b77d5ecSLuis R. Rodriguez * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 153b77d5ecSLuis R. Rodriguez * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 163b77d5ecSLuis R. Rodriguez * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 173b77d5ecSLuis R. Rodriguez * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 183b77d5ecSLuis R. Rodriguez * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 193b77d5ecSLuis R. Rodriguez * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 203b77d5ecSLuis R. Rodriguez * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 218318d78aSJohannes Berg */ 228318d78aSJohannes Berg 233b77d5ecSLuis R. Rodriguez 24b2e1b302SLuis R. Rodriguez /** 25b2e1b302SLuis R. Rodriguez * DOC: Wireless regulatory infrastructure 268318d78aSJohannes Berg * 278318d78aSJohannes Berg * The usual implementation is for a driver to read a device EEPROM to 288318d78aSJohannes Berg * determine which regulatory domain it should be operating under, then 298318d78aSJohannes Berg * looking up the allowable channels in a driver-local table and finally 308318d78aSJohannes Berg * registering those channels in the wiphy structure. 318318d78aSJohannes Berg * 32b2e1b302SLuis R. Rodriguez * Another set of compliance enforcement is for drivers to use their 33b2e1b302SLuis R. Rodriguez * own compliance limits which can be stored on the EEPROM. The host 34b2e1b302SLuis R. Rodriguez * driver or firmware may ensure these are used. 35b2e1b302SLuis R. Rodriguez * 36b2e1b302SLuis R. Rodriguez * In addition to all this we provide an extra layer of regulatory 37b2e1b302SLuis R. Rodriguez * conformance. For drivers which do not have any regulatory 38b2e1b302SLuis R. Rodriguez * information CRDA provides the complete regulatory solution. 39b2e1b302SLuis R. Rodriguez * For others it provides a community effort on further restrictions 40b2e1b302SLuis R. Rodriguez * to enhance compliance. 41b2e1b302SLuis R. Rodriguez * 42b2e1b302SLuis R. Rodriguez * Note: When number of rules --> infinity we will not be able to 43b2e1b302SLuis R. Rodriguez * index on alpha2 any more, instead we'll probably have to 44b2e1b302SLuis R. Rodriguez * rely on some SHA1 checksum of the regdomain for example. 45b2e1b302SLuis R. Rodriguez * 468318d78aSJohannes Berg */ 47e9c0268fSJoe Perches 48e9c0268fSJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 49e9c0268fSJoe Perches 508318d78aSJohannes Berg #include <linux/kernel.h> 51bc3b2d7fSPaul Gortmaker #include <linux/export.h> 525a0e3ad6STejun Heo #include <linux/slab.h> 53b2e1b302SLuis R. Rodriguez #include <linux/list.h> 54c61029c7SJohn W. Linville #include <linux/ctype.h> 55b2e1b302SLuis R. Rodriguez #include <linux/nl80211.h> 56b2e1b302SLuis R. Rodriguez #include <linux/platform_device.h> 5790a53e44SJohannes Berg #include <linux/verification.h> 58d9b93842SPaul Gortmaker #include <linux/moduleparam.h> 59007f6c5eSJohannes Berg #include <linux/firmware.h> 60b2e1b302SLuis R. Rodriguez #include <net/cfg80211.h> 618318d78aSJohannes Berg #include "core.h" 62b2e1b302SLuis R. Rodriguez #include "reg.h" 63ad932f04SArik Nemtsov #include "rdev-ops.h" 6473d54c9eSLuis R. Rodriguez #include "nl80211.h" 658318d78aSJohannes Berg 66ad932f04SArik Nemtsov /* 67ad932f04SArik Nemtsov * Grace period we give before making sure all current interfaces reside on 68ad932f04SArik Nemtsov * channels allowed by the current regulatory domain. 69ad932f04SArik Nemtsov */ 70ad932f04SArik Nemtsov #define REG_ENFORCE_GRACE_MS 60000 71ad932f04SArik Nemtsov 7252616f2bSIlan Peer /** 7352616f2bSIlan Peer * enum reg_request_treatment - regulatory request treatment 7452616f2bSIlan Peer * 7552616f2bSIlan Peer * @REG_REQ_OK: continue processing the regulatory request 7652616f2bSIlan Peer * @REG_REQ_IGNORE: ignore the regulatory request 7752616f2bSIlan Peer * @REG_REQ_INTERSECT: the regulatory domain resulting from this request should 7852616f2bSIlan Peer * be intersected with the current one. 7952616f2bSIlan Peer * @REG_REQ_ALREADY_SET: the regulatory request will not change the current 8052616f2bSIlan Peer * regulatory settings, and no further processing is required. 8152616f2bSIlan Peer */ 822f92212bSJohannes Berg enum reg_request_treatment { 832f92212bSJohannes Berg REG_REQ_OK, 842f92212bSJohannes Berg REG_REQ_IGNORE, 852f92212bSJohannes Berg REG_REQ_INTERSECT, 862f92212bSJohannes Berg REG_REQ_ALREADY_SET, 872f92212bSJohannes Berg }; 882f92212bSJohannes Berg 89a042994dSLuis R. Rodriguez static struct regulatory_request core_request_world = { 90a042994dSLuis R. Rodriguez .initiator = NL80211_REGDOM_SET_BY_CORE, 91a042994dSLuis R. Rodriguez .alpha2[0] = '0', 92a042994dSLuis R. Rodriguez .alpha2[1] = '0', 93a042994dSLuis R. Rodriguez .intersect = false, 94a042994dSLuis R. Rodriguez .processed = true, 95a042994dSLuis R. Rodriguez .country_ie_env = ENVIRON_ANY, 96a042994dSLuis R. Rodriguez }; 97a042994dSLuis R. Rodriguez 9838fd2143SJohannes Berg /* 9938fd2143SJohannes Berg * Receipt of information from last regulatory request, 10038fd2143SJohannes Berg * protected by RTNL (and can be accessed with RCU protection) 10138fd2143SJohannes Berg */ 102c492db37SJohannes Berg static struct regulatory_request __rcu *last_request = 103cec3f0edSJohannes Berg (void __force __rcu *)&core_request_world; 104734366deSJohannes Berg 105007f6c5eSJohannes Berg /* To trigger userspace events and load firmware */ 106b2e1b302SLuis R. Rodriguez static struct platform_device *reg_pdev; 1078318d78aSJohannes Berg 108fb1fc7adSLuis R. Rodriguez /* 109fb1fc7adSLuis R. Rodriguez * Central wireless core regulatory domains, we only need two, 110734366deSJohannes Berg * the current one and a world regulatory domain in case we have no 111e8da2bb4SJohannes Berg * information to give us an alpha2. 11238fd2143SJohannes Berg * (protected by RTNL, can be read under RCU) 113fb1fc7adSLuis R. Rodriguez */ 114458f4f9eSJohannes Berg const struct ieee80211_regdomain __rcu *cfg80211_regdomain; 115734366deSJohannes Berg 116fb1fc7adSLuis R. Rodriguez /* 11757b5ce07SLuis R. Rodriguez * Number of devices that registered to the core 11857b5ce07SLuis R. Rodriguez * that support cellular base station regulatory hints 11938fd2143SJohannes Berg * (protected by RTNL) 12057b5ce07SLuis R. Rodriguez */ 12157b5ce07SLuis R. Rodriguez static int reg_num_devs_support_basehint; 12257b5ce07SLuis R. Rodriguez 12352616f2bSIlan Peer /* 12452616f2bSIlan Peer * State variable indicating if the platform on which the devices 12552616f2bSIlan Peer * are attached is operating in an indoor environment. The state variable 12652616f2bSIlan Peer * is relevant for all registered devices. 12752616f2bSIlan Peer */ 12852616f2bSIlan Peer static bool reg_is_indoor; 12981d94f47SQiheng Lin static DEFINE_SPINLOCK(reg_indoor_lock); 13005050753SIlan peer 13105050753SIlan peer /* Used to track the userspace process controlling the indoor setting */ 13205050753SIlan peer static u32 reg_is_indoor_portid; 13352616f2bSIlan Peer 134e646a025SJohannes Berg static void restore_regulatory_settings(bool reset_user, bool cached); 135e646a025SJohannes Berg static void print_regdomain(const struct ieee80211_regdomain *rd); 1361eda9191SFinn Behrens static void reg_process_hint(struct regulatory_request *reg_request); 137c37722bdSIlan peer 138458f4f9eSJohannes Berg static const struct ieee80211_regdomain *get_cfg80211_regdom(void) 139458f4f9eSJohannes Berg { 1405bf16a11SJohannes Berg return rcu_dereference_rtnl(cfg80211_regdomain); 141458f4f9eSJohannes Berg } 142458f4f9eSJohannes Berg 14351d62f2fSIlan Peer /* 14451d62f2fSIlan Peer * Returns the regulatory domain associated with the wiphy. 14551d62f2fSIlan Peer * 146a05829a7SJohannes Berg * Requires any of RTNL, wiphy mutex or RCU protection. 14751d62f2fSIlan Peer */ 148ad30ca2cSArik Nemtsov const struct ieee80211_regdomain *get_wiphy_regdom(struct wiphy *wiphy) 149458f4f9eSJohannes Berg { 150a05829a7SJohannes Berg return rcu_dereference_check(wiphy->regd, 151a05829a7SJohannes Berg lockdep_is_held(&wiphy->mtx) || 152a05829a7SJohannes Berg lockdep_rtnl_is_held()); 153458f4f9eSJohannes Berg } 154a05829a7SJohannes Berg EXPORT_SYMBOL(get_wiphy_regdom); 155458f4f9eSJohannes Berg 1563ef121b5SLuis R. Rodriguez static const char *reg_dfs_region_str(enum nl80211_dfs_regions dfs_region) 1573ef121b5SLuis R. Rodriguez { 1583ef121b5SLuis R. Rodriguez switch (dfs_region) { 1593ef121b5SLuis R. Rodriguez case NL80211_DFS_UNSET: 1603ef121b5SLuis R. Rodriguez return "unset"; 1613ef121b5SLuis R. Rodriguez case NL80211_DFS_FCC: 1623ef121b5SLuis R. Rodriguez return "FCC"; 1633ef121b5SLuis R. Rodriguez case NL80211_DFS_ETSI: 1643ef121b5SLuis R. Rodriguez return "ETSI"; 1653ef121b5SLuis R. Rodriguez case NL80211_DFS_JP: 1663ef121b5SLuis R. Rodriguez return "JP"; 1673ef121b5SLuis R. Rodriguez } 1683ef121b5SLuis R. Rodriguez return "Unknown"; 1693ef121b5SLuis R. Rodriguez } 1703ef121b5SLuis R. Rodriguez 1716c474799SLuis R. Rodriguez enum nl80211_dfs_regions reg_get_dfs_region(struct wiphy *wiphy) 1726c474799SLuis R. Rodriguez { 1736c474799SLuis R. Rodriguez const struct ieee80211_regdomain *regd = NULL; 1746c474799SLuis R. Rodriguez const struct ieee80211_regdomain *wiphy_regd = NULL; 17590bd5beeSSriram R enum nl80211_dfs_regions dfs_region; 1766c474799SLuis R. Rodriguez 177a05829a7SJohannes Berg rcu_read_lock(); 1786c474799SLuis R. Rodriguez regd = get_cfg80211_regdom(); 17990bd5beeSSriram R dfs_region = regd->dfs_region; 180a05829a7SJohannes Berg 1816c474799SLuis R. Rodriguez if (!wiphy) 1826c474799SLuis R. Rodriguez goto out; 1836c474799SLuis R. Rodriguez 1846c474799SLuis R. Rodriguez wiphy_regd = get_wiphy_regdom(wiphy); 1856c474799SLuis R. Rodriguez if (!wiphy_regd) 1866c474799SLuis R. Rodriguez goto out; 1876c474799SLuis R. Rodriguez 18890bd5beeSSriram R if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED) { 18990bd5beeSSriram R dfs_region = wiphy_regd->dfs_region; 19090bd5beeSSriram R goto out; 19190bd5beeSSriram R } 19290bd5beeSSriram R 1936c474799SLuis R. Rodriguez if (wiphy_regd->dfs_region == regd->dfs_region) 1946c474799SLuis R. Rodriguez goto out; 1956c474799SLuis R. Rodriguez 196c799ba6eSJohannes Berg pr_debug("%s: device specific dfs_region (%s) disagrees with cfg80211's central dfs_region (%s)\n", 1976c474799SLuis R. Rodriguez dev_name(&wiphy->dev), 1986c474799SLuis R. Rodriguez reg_dfs_region_str(wiphy_regd->dfs_region), 1996c474799SLuis R. Rodriguez reg_dfs_region_str(regd->dfs_region)); 2006c474799SLuis R. Rodriguez 2016c474799SLuis R. Rodriguez out: 202a05829a7SJohannes Berg rcu_read_unlock(); 203a05829a7SJohannes Berg 20490bd5beeSSriram R return dfs_region; 2056c474799SLuis R. Rodriguez } 2066c474799SLuis R. Rodriguez 207458f4f9eSJohannes Berg static void rcu_free_regdom(const struct ieee80211_regdomain *r) 208458f4f9eSJohannes Berg { 209458f4f9eSJohannes Berg if (!r) 210458f4f9eSJohannes Berg return; 211458f4f9eSJohannes Berg kfree_rcu((struct ieee80211_regdomain *)r, rcu_head); 212458f4f9eSJohannes Berg } 213458f4f9eSJohannes Berg 214c492db37SJohannes Berg static struct regulatory_request *get_last_request(void) 215c492db37SJohannes Berg { 21638fd2143SJohannes Berg return rcu_dereference_rtnl(last_request); 217c492db37SJohannes Berg } 218c492db37SJohannes Berg 219e38f8a7aSLuis R. Rodriguez /* Used to queue up regulatory hints */ 220fe33eb39SLuis R. Rodriguez static LIST_HEAD(reg_requests_list); 22181d94f47SQiheng Lin static DEFINE_SPINLOCK(reg_requests_lock); 222fe33eb39SLuis R. Rodriguez 223e38f8a7aSLuis R. Rodriguez /* Used to queue up beacon hints for review */ 224e38f8a7aSLuis R. Rodriguez static LIST_HEAD(reg_pending_beacons); 22581d94f47SQiheng Lin static DEFINE_SPINLOCK(reg_pending_beacons_lock); 226e38f8a7aSLuis R. Rodriguez 227e38f8a7aSLuis R. Rodriguez /* Used to keep track of processed beacon hints */ 228e38f8a7aSLuis R. Rodriguez static LIST_HEAD(reg_beacon_list); 229e38f8a7aSLuis R. Rodriguez 230e38f8a7aSLuis R. Rodriguez struct reg_beacon { 231e38f8a7aSLuis R. Rodriguez struct list_head list; 232e38f8a7aSLuis R. Rodriguez struct ieee80211_channel chan; 233e38f8a7aSLuis R. Rodriguez }; 234e38f8a7aSLuis R. Rodriguez 235ad932f04SArik Nemtsov static void reg_check_chans_work(struct work_struct *work); 236ad932f04SArik Nemtsov static DECLARE_DELAYED_WORK(reg_check_chans, reg_check_chans_work); 237ad932f04SArik Nemtsov 238f333a7a2SLuis R. Rodriguez static void reg_todo(struct work_struct *work); 239f333a7a2SLuis R. Rodriguez static DECLARE_WORK(reg_work, reg_todo); 240f333a7a2SLuis R. Rodriguez 241734366deSJohannes Berg /* We keep a static world regulatory domain in case of the absence of CRDA */ 242734366deSJohannes Berg static const struct ieee80211_regdomain world_regdom = { 24328981e5eSJason Abele .n_reg_rules = 8, 244734366deSJohannes Berg .alpha2 = "00", 245734366deSJohannes Berg .reg_rules = { 24668798a62SLuis R. Rodriguez /* IEEE 802.11b/g, channels 1..11 */ 24768798a62SLuis R. Rodriguez REG_RULE(2412-10, 2462+10, 40, 6, 20, 0), 24843c771a1SJohannes Berg /* IEEE 802.11b/g, channels 12..13. */ 249c3826807SJohannes Berg REG_RULE(2467-10, 2472+10, 20, 6, 20, 250c3826807SJohannes Berg NL80211_RRF_NO_IR | NL80211_RRF_AUTO_BW), 251611b6a82SLuis R. Rodriguez /* IEEE 802.11 channel 14 - Only JP enables 252611b6a82SLuis R. Rodriguez * this and for 802.11b only */ 253611b6a82SLuis R. Rodriguez REG_RULE(2484-10, 2484+10, 20, 6, 20, 2548fe02e16SLuis R. Rodriguez NL80211_RRF_NO_IR | 255611b6a82SLuis R. Rodriguez NL80211_RRF_NO_OFDM), 2563fc71f77SLuis R. Rodriguez /* IEEE 802.11a, channel 36..48 */ 257c3826807SJohannes Berg REG_RULE(5180-10, 5240+10, 80, 6, 20, 258c3826807SJohannes Berg NL80211_RRF_NO_IR | 259c3826807SJohannes Berg NL80211_RRF_AUTO_BW), 2603fc71f77SLuis R. Rodriguez 261131a19bcSJohannes Berg /* IEEE 802.11a, channel 52..64 - DFS required */ 262c3826807SJohannes Berg REG_RULE(5260-10, 5320+10, 80, 6, 20, 2638fe02e16SLuis R. Rodriguez NL80211_RRF_NO_IR | 264c3826807SJohannes Berg NL80211_RRF_AUTO_BW | 265131a19bcSJohannes Berg NL80211_RRF_DFS), 266131a19bcSJohannes Berg 267131a19bcSJohannes Berg /* IEEE 802.11a, channel 100..144 - DFS required */ 268131a19bcSJohannes Berg REG_RULE(5500-10, 5720+10, 160, 6, 20, 2698fe02e16SLuis R. Rodriguez NL80211_RRF_NO_IR | 270131a19bcSJohannes Berg NL80211_RRF_DFS), 2713fc71f77SLuis R. Rodriguez 2723fc71f77SLuis R. Rodriguez /* IEEE 802.11a, channel 149..165 */ 2738ab9d85cSJohannes Berg REG_RULE(5745-10, 5825+10, 80, 6, 20, 2748fe02e16SLuis R. Rodriguez NL80211_RRF_NO_IR), 27590cdc6dfSVladimir Kondratiev 2768047d261SJohannes Berg /* IEEE 802.11ad (60GHz), channels 1..3 */ 27790cdc6dfSVladimir Kondratiev REG_RULE(56160+2160*1-1080, 56160+2160*3+1080, 2160, 0, 0, 0), 278734366deSJohannes Berg } 279734366deSJohannes Berg }; 280734366deSJohannes Berg 28138fd2143SJohannes Berg /* protected by RTNL */ 282a3d2eaf0SJohannes Berg static const struct ieee80211_regdomain *cfg80211_world_regdom = 283a3d2eaf0SJohannes Berg &world_regdom; 284734366deSJohannes Berg 2856ee7d330SLuis R. Rodriguez static char *ieee80211_regdom = "00"; 28609d989d1SLuis R. Rodriguez static char user_alpha2[2]; 287e646a025SJohannes Berg static const struct ieee80211_regdomain *cfg80211_user_regdom; 2886ee7d330SLuis R. Rodriguez 289734366deSJohannes Berg module_param(ieee80211_regdom, charp, 0444); 290734366deSJohannes Berg MODULE_PARM_DESC(ieee80211_regdom, "IEEE 802.11 regulatory domain code"); 291734366deSJohannes Berg 292c888393bSArik Nemtsov static void reg_free_request(struct regulatory_request *request) 2935ad6ef5eSLuis R. Rodriguez { 294d34265a3SJohannes Berg if (request == &core_request_world) 295d34265a3SJohannes Berg return; 296d34265a3SJohannes Berg 297c888393bSArik Nemtsov if (request != get_last_request()) 298c888393bSArik Nemtsov kfree(request); 299c888393bSArik Nemtsov } 300c888393bSArik Nemtsov 301c888393bSArik Nemtsov static void reg_free_last_request(void) 302c888393bSArik Nemtsov { 303c888393bSArik Nemtsov struct regulatory_request *lr = get_last_request(); 304c888393bSArik Nemtsov 3055ad6ef5eSLuis R. Rodriguez if (lr != &core_request_world && lr) 3065ad6ef5eSLuis R. Rodriguez kfree_rcu(lr, rcu_head); 3075ad6ef5eSLuis R. Rodriguez } 3085ad6ef5eSLuis R. Rodriguez 30905f1a3eaSLuis R. Rodriguez static void reg_update_last_request(struct regulatory_request *request) 31005f1a3eaSLuis R. Rodriguez { 311255e25b0SLuis R. Rodriguez struct regulatory_request *lr; 312255e25b0SLuis R. Rodriguez 313255e25b0SLuis R. Rodriguez lr = get_last_request(); 314255e25b0SLuis R. Rodriguez if (lr == request) 315255e25b0SLuis R. Rodriguez return; 316255e25b0SLuis R. Rodriguez 317c888393bSArik Nemtsov reg_free_last_request(); 31805f1a3eaSLuis R. Rodriguez rcu_assign_pointer(last_request, request); 31905f1a3eaSLuis R. Rodriguez } 32005f1a3eaSLuis R. Rodriguez 321379b82f4SJohannes Berg static void reset_regdomains(bool full_reset, 322379b82f4SJohannes Berg const struct ieee80211_regdomain *new_regdom) 323734366deSJohannes Berg { 324458f4f9eSJohannes Berg const struct ieee80211_regdomain *r; 325458f4f9eSJohannes Berg 32638fd2143SJohannes Berg ASSERT_RTNL(); 327e8da2bb4SJohannes Berg 328458f4f9eSJohannes Berg r = get_cfg80211_regdom(); 329458f4f9eSJohannes Berg 330942b25cfSJohannes Berg /* avoid freeing static information or freeing something twice */ 331458f4f9eSJohannes Berg if (r == cfg80211_world_regdom) 332458f4f9eSJohannes Berg r = NULL; 333942b25cfSJohannes Berg if (cfg80211_world_regdom == &world_regdom) 334942b25cfSJohannes Berg cfg80211_world_regdom = NULL; 335458f4f9eSJohannes Berg if (r == &world_regdom) 336458f4f9eSJohannes Berg r = NULL; 337942b25cfSJohannes Berg 338458f4f9eSJohannes Berg rcu_free_regdom(r); 339458f4f9eSJohannes Berg rcu_free_regdom(cfg80211_world_regdom); 340734366deSJohannes Berg 341a3d2eaf0SJohannes Berg cfg80211_world_regdom = &world_regdom; 342458f4f9eSJohannes Berg rcu_assign_pointer(cfg80211_regdomain, new_regdom); 343a042994dSLuis R. Rodriguez 344a042994dSLuis R. Rodriguez if (!full_reset) 345a042994dSLuis R. Rodriguez return; 346a042994dSLuis R. Rodriguez 34705f1a3eaSLuis R. Rodriguez reg_update_last_request(&core_request_world); 348734366deSJohannes Berg } 349734366deSJohannes Berg 350fb1fc7adSLuis R. Rodriguez /* 351fb1fc7adSLuis R. Rodriguez * Dynamic world regulatory domain requested by the wireless 352fb1fc7adSLuis R. Rodriguez * core upon initialization 353fb1fc7adSLuis R. Rodriguez */ 354a3d2eaf0SJohannes Berg static void update_world_regdomain(const struct ieee80211_regdomain *rd) 355734366deSJohannes Berg { 356c492db37SJohannes Berg struct regulatory_request *lr; 357734366deSJohannes Berg 358c492db37SJohannes Berg lr = get_last_request(); 359c492db37SJohannes Berg 360c492db37SJohannes Berg WARN_ON(!lr); 361e8da2bb4SJohannes Berg 362379b82f4SJohannes Berg reset_regdomains(false, rd); 363734366deSJohannes Berg 364734366deSJohannes Berg cfg80211_world_regdom = rd; 365734366deSJohannes Berg } 366734366deSJohannes Berg 367a3d2eaf0SJohannes Berg bool is_world_regdom(const char *alpha2) 368b2e1b302SLuis R. Rodriguez { 369b2e1b302SLuis R. Rodriguez if (!alpha2) 370b2e1b302SLuis R. Rodriguez return false; 3711a919318SJohannes Berg return alpha2[0] == '0' && alpha2[1] == '0'; 372b2e1b302SLuis R. Rodriguez } 373b2e1b302SLuis R. Rodriguez 374a3d2eaf0SJohannes Berg static bool is_alpha2_set(const char *alpha2) 375b2e1b302SLuis R. Rodriguez { 376b2e1b302SLuis R. Rodriguez if (!alpha2) 377b2e1b302SLuis R. Rodriguez return false; 3781a919318SJohannes Berg return alpha2[0] && alpha2[1]; 379b2e1b302SLuis R. Rodriguez } 380b2e1b302SLuis R. Rodriguez 381a3d2eaf0SJohannes Berg static bool is_unknown_alpha2(const char *alpha2) 382b2e1b302SLuis R. Rodriguez { 383b2e1b302SLuis R. Rodriguez if (!alpha2) 384b2e1b302SLuis R. Rodriguez return false; 385fb1fc7adSLuis R. Rodriguez /* 386fb1fc7adSLuis R. Rodriguez * Special case where regulatory domain was built by driver 387fb1fc7adSLuis R. Rodriguez * but a specific alpha2 cannot be determined 388fb1fc7adSLuis R. Rodriguez */ 3891a919318SJohannes Berg return alpha2[0] == '9' && alpha2[1] == '9'; 390b2e1b302SLuis R. Rodriguez } 391b2e1b302SLuis R. Rodriguez 3923f2355cbSLuis R. Rodriguez static bool is_intersected_alpha2(const char *alpha2) 3933f2355cbSLuis R. Rodriguez { 3943f2355cbSLuis R. Rodriguez if (!alpha2) 3953f2355cbSLuis R. Rodriguez return false; 396fb1fc7adSLuis R. Rodriguez /* 397fb1fc7adSLuis R. Rodriguez * Special case where regulatory domain is the 3983f2355cbSLuis R. Rodriguez * result of an intersection between two regulatory domain 399fb1fc7adSLuis R. Rodriguez * structures 400fb1fc7adSLuis R. Rodriguez */ 4011a919318SJohannes Berg return alpha2[0] == '9' && alpha2[1] == '8'; 4023f2355cbSLuis R. Rodriguez } 4033f2355cbSLuis R. Rodriguez 404a3d2eaf0SJohannes Berg static bool is_an_alpha2(const char *alpha2) 405b2e1b302SLuis R. Rodriguez { 406b2e1b302SLuis R. Rodriguez if (!alpha2) 407b2e1b302SLuis R. Rodriguez return false; 4081a919318SJohannes Berg return isalpha(alpha2[0]) && isalpha(alpha2[1]); 409b2e1b302SLuis R. Rodriguez } 410b2e1b302SLuis R. Rodriguez 411a3d2eaf0SJohannes Berg static bool alpha2_equal(const char *alpha2_x, const char *alpha2_y) 412b2e1b302SLuis R. Rodriguez { 413b2e1b302SLuis R. Rodriguez if (!alpha2_x || !alpha2_y) 414b2e1b302SLuis R. Rodriguez return false; 4151a919318SJohannes Berg return alpha2_x[0] == alpha2_y[0] && alpha2_x[1] == alpha2_y[1]; 416b2e1b302SLuis R. Rodriguez } 417b2e1b302SLuis R. Rodriguez 41869b1572bSLuis R. Rodriguez static bool regdom_changes(const char *alpha2) 419b2e1b302SLuis R. Rodriguez { 420458f4f9eSJohannes Berg const struct ieee80211_regdomain *r = get_cfg80211_regdom(); 421761cf7ecSLuis R. Rodriguez 422458f4f9eSJohannes Berg if (!r) 423b2e1b302SLuis R. Rodriguez return true; 424458f4f9eSJohannes Berg return !alpha2_equal(r->alpha2, alpha2); 425b2e1b302SLuis R. Rodriguez } 426b2e1b302SLuis R. Rodriguez 42709d989d1SLuis R. Rodriguez /* 42809d989d1SLuis R. Rodriguez * The NL80211_REGDOM_SET_BY_USER regdom alpha2 is cached, this lets 42909d989d1SLuis R. Rodriguez * you know if a valid regulatory hint with NL80211_REGDOM_SET_BY_USER 43009d989d1SLuis R. Rodriguez * has ever been issued. 43109d989d1SLuis R. Rodriguez */ 43209d989d1SLuis R. Rodriguez static bool is_user_regdom_saved(void) 43309d989d1SLuis R. Rodriguez { 43409d989d1SLuis R. Rodriguez if (user_alpha2[0] == '9' && user_alpha2[1] == '7') 43509d989d1SLuis R. Rodriguez return false; 43609d989d1SLuis R. Rodriguez 43709d989d1SLuis R. Rodriguez /* This would indicate a mistake on the design */ 4381a919318SJohannes Berg if (WARN(!is_world_regdom(user_alpha2) && !is_an_alpha2(user_alpha2), 43909d989d1SLuis R. Rodriguez "Unexpected user alpha2: %c%c\n", 4401a919318SJohannes Berg user_alpha2[0], user_alpha2[1])) 44109d989d1SLuis R. Rodriguez return false; 44209d989d1SLuis R. Rodriguez 44309d989d1SLuis R. Rodriguez return true; 44409d989d1SLuis R. Rodriguez } 44509d989d1SLuis R. Rodriguez 446e9763c3cSJohannes Berg static const struct ieee80211_regdomain * 447e9763c3cSJohannes Berg reg_copy_regd(const struct ieee80211_regdomain *src_regd) 4483b377ea9SJohn W. Linville { 4493b377ea9SJohn W. Linville struct ieee80211_regdomain *regd; 4503b377ea9SJohn W. Linville unsigned int i; 4513b377ea9SJohn W. Linville 4529f8c7136SGustavo A. R. Silva regd = kzalloc(struct_size(regd, reg_rules, src_regd->n_reg_rules), 4539f8c7136SGustavo A. R. Silva GFP_KERNEL); 4543b377ea9SJohn W. Linville if (!regd) 455e9763c3cSJohannes Berg return ERR_PTR(-ENOMEM); 4563b377ea9SJohn W. Linville 4573b377ea9SJohn W. Linville memcpy(regd, src_regd, sizeof(struct ieee80211_regdomain)); 4583b377ea9SJohn W. Linville 45938cb87eeSStanislaw Gruszka for (i = 0; i < src_regd->n_reg_rules; i++) 4603b377ea9SJohn W. Linville memcpy(®d->reg_rules[i], &src_regd->reg_rules[i], 4613b377ea9SJohn W. Linville sizeof(struct ieee80211_reg_rule)); 4623b377ea9SJohn W. Linville 463e9763c3cSJohannes Berg return regd; 4643b377ea9SJohn W. Linville } 4653b377ea9SJohn W. Linville 466e646a025SJohannes Berg static void cfg80211_save_user_regdom(const struct ieee80211_regdomain *rd) 467e646a025SJohannes Berg { 468e646a025SJohannes Berg ASSERT_RTNL(); 469e646a025SJohannes Berg 470e646a025SJohannes Berg if (!IS_ERR(cfg80211_user_regdom)) 471e646a025SJohannes Berg kfree(cfg80211_user_regdom); 472e646a025SJohannes Berg cfg80211_user_regdom = reg_copy_regd(rd); 473e646a025SJohannes Berg } 474e646a025SJohannes Berg 475c7d319e5SJohannes Berg struct reg_regdb_apply_request { 4763b377ea9SJohn W. Linville struct list_head list; 477c7d319e5SJohannes Berg const struct ieee80211_regdomain *regdom; 4783b377ea9SJohn W. Linville }; 4793b377ea9SJohn W. Linville 480c7d319e5SJohannes Berg static LIST_HEAD(reg_regdb_apply_list); 481c7d319e5SJohannes Berg static DEFINE_MUTEX(reg_regdb_apply_mutex); 4823b377ea9SJohn W. Linville 483c7d319e5SJohannes Berg static void reg_regdb_apply(struct work_struct *work) 4843b377ea9SJohn W. Linville { 485c7d319e5SJohannes Berg struct reg_regdb_apply_request *request; 486a85d0d7fSLuis R. Rodriguez 4875fe231e8SJohannes Berg rtnl_lock(); 4883b377ea9SJohn W. Linville 489c7d319e5SJohannes Berg mutex_lock(®_regdb_apply_mutex); 490c7d319e5SJohannes Berg while (!list_empty(®_regdb_apply_list)) { 491c7d319e5SJohannes Berg request = list_first_entry(®_regdb_apply_list, 492c7d319e5SJohannes Berg struct reg_regdb_apply_request, 4933b377ea9SJohn W. Linville list); 4943b377ea9SJohn W. Linville list_del(&request->list); 4953b377ea9SJohn W. Linville 496c7d319e5SJohannes Berg set_regdom(request->regdom, REGD_SOURCE_INTERNAL_DB); 4973b377ea9SJohn W. Linville kfree(request); 4983b377ea9SJohn W. Linville } 499c7d319e5SJohannes Berg mutex_unlock(®_regdb_apply_mutex); 500a85d0d7fSLuis R. Rodriguez 5015fe231e8SJohannes Berg rtnl_unlock(); 5023b377ea9SJohn W. Linville } 5033b377ea9SJohn W. Linville 504c7d319e5SJohannes Berg static DECLARE_WORK(reg_regdb_work, reg_regdb_apply); 5053b377ea9SJohn W. Linville 506007f6c5eSJohannes Berg static int reg_schedule_apply(const struct ieee80211_regdomain *regdom) 5073b377ea9SJohn W. Linville { 508c7d319e5SJohannes Berg struct reg_regdb_apply_request *request; 509c7d319e5SJohannes Berg 510c7d319e5SJohannes Berg request = kzalloc(sizeof(struct reg_regdb_apply_request), GFP_KERNEL); 511007f6c5eSJohannes Berg if (!request) { 512007f6c5eSJohannes Berg kfree(regdom); 513c7d319e5SJohannes Berg return -ENOMEM; 514c7d319e5SJohannes Berg } 5153b377ea9SJohn W. Linville 516007f6c5eSJohannes Berg request->regdom = regdom; 517007f6c5eSJohannes Berg 518c7d319e5SJohannes Berg mutex_lock(®_regdb_apply_mutex); 519c7d319e5SJohannes Berg list_add_tail(&request->list, ®_regdb_apply_list); 520c7d319e5SJohannes Berg mutex_unlock(®_regdb_apply_mutex); 5213b377ea9SJohn W. Linville 5223b377ea9SJohn W. Linville schedule_work(®_regdb_work); 523c7d319e5SJohannes Berg return 0; 5243b377ea9SJohn W. Linville } 52580007efeSLuis R. Rodriguez 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++; 540e646a025SJohannes Berg restore_regulatory_settings(true, false); 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 601007f6c5eSJohannes Berg /* code to directly load a firmware database through request_firmware */ 602007f6c5eSJohannes Berg static const struct fwdb_header *regdb; 603007f6c5eSJohannes Berg 604007f6c5eSJohannes Berg struct fwdb_country { 605007f6c5eSJohannes Berg u8 alpha2[2]; 606007f6c5eSJohannes Berg __be16 coll_ptr; 607007f6c5eSJohannes Berg /* this struct cannot be extended */ 608007f6c5eSJohannes Berg } __packed __aligned(4); 609007f6c5eSJohannes Berg 610007f6c5eSJohannes Berg struct fwdb_collection { 611007f6c5eSJohannes Berg u8 len; 612007f6c5eSJohannes Berg u8 n_rules; 613007f6c5eSJohannes Berg u8 dfs_region; 614007f6c5eSJohannes Berg /* no optional data yet */ 615007f6c5eSJohannes Berg /* aligned to 2, then followed by __be16 array of rule pointers */ 616007f6c5eSJohannes Berg } __packed __aligned(4); 617007f6c5eSJohannes Berg 618007f6c5eSJohannes Berg enum fwdb_flags { 619007f6c5eSJohannes Berg FWDB_FLAG_NO_OFDM = BIT(0), 620007f6c5eSJohannes Berg FWDB_FLAG_NO_OUTDOOR = BIT(1), 621007f6c5eSJohannes Berg FWDB_FLAG_DFS = BIT(2), 622007f6c5eSJohannes Berg FWDB_FLAG_NO_IR = BIT(3), 623007f6c5eSJohannes Berg FWDB_FLAG_AUTO_BW = BIT(4), 624007f6c5eSJohannes Berg }; 625007f6c5eSJohannes Berg 626230ebaa1SHaim Dreyfuss struct fwdb_wmm_ac { 627230ebaa1SHaim Dreyfuss u8 ecw; 628230ebaa1SHaim Dreyfuss u8 aifsn; 629230ebaa1SHaim Dreyfuss __be16 cot; 630230ebaa1SHaim Dreyfuss } __packed; 631230ebaa1SHaim Dreyfuss 632230ebaa1SHaim Dreyfuss struct fwdb_wmm_rule { 633230ebaa1SHaim Dreyfuss struct fwdb_wmm_ac client[IEEE80211_NUM_ACS]; 634230ebaa1SHaim Dreyfuss struct fwdb_wmm_ac ap[IEEE80211_NUM_ACS]; 635230ebaa1SHaim Dreyfuss } __packed; 636230ebaa1SHaim Dreyfuss 637007f6c5eSJohannes Berg struct fwdb_rule { 638007f6c5eSJohannes Berg u8 len; 639007f6c5eSJohannes Berg u8 flags; 640007f6c5eSJohannes Berg __be16 max_eirp; 641007f6c5eSJohannes Berg __be32 start, end, max_bw; 642007f6c5eSJohannes Berg /* start of optional data */ 643007f6c5eSJohannes Berg __be16 cac_timeout; 644230ebaa1SHaim Dreyfuss __be16 wmm_ptr; 645007f6c5eSJohannes Berg } __packed __aligned(4); 646007f6c5eSJohannes Berg 647007f6c5eSJohannes Berg #define FWDB_MAGIC 0x52474442 648007f6c5eSJohannes Berg #define FWDB_VERSION 20 649007f6c5eSJohannes Berg 650007f6c5eSJohannes Berg struct fwdb_header { 651007f6c5eSJohannes Berg __be32 magic; 652007f6c5eSJohannes Berg __be32 version; 653007f6c5eSJohannes Berg struct fwdb_country country[]; 654007f6c5eSJohannes Berg } __packed __aligned(4); 655007f6c5eSJohannes Berg 656230ebaa1SHaim Dreyfuss static int ecw2cw(int ecw) 657230ebaa1SHaim Dreyfuss { 658230ebaa1SHaim Dreyfuss return (1 << ecw) - 1; 659230ebaa1SHaim Dreyfuss } 660230ebaa1SHaim Dreyfuss 661230ebaa1SHaim Dreyfuss static bool valid_wmm(struct fwdb_wmm_rule *rule) 662230ebaa1SHaim Dreyfuss { 663230ebaa1SHaim Dreyfuss struct fwdb_wmm_ac *ac = (struct fwdb_wmm_ac *)rule; 664230ebaa1SHaim Dreyfuss int i; 665230ebaa1SHaim Dreyfuss 666230ebaa1SHaim Dreyfuss for (i = 0; i < IEEE80211_NUM_ACS * 2; i++) { 667230ebaa1SHaim Dreyfuss u16 cw_min = ecw2cw((ac[i].ecw & 0xf0) >> 4); 668230ebaa1SHaim Dreyfuss u16 cw_max = ecw2cw(ac[i].ecw & 0x0f); 669230ebaa1SHaim Dreyfuss u8 aifsn = ac[i].aifsn; 670230ebaa1SHaim Dreyfuss 671230ebaa1SHaim Dreyfuss if (cw_min >= cw_max) 672230ebaa1SHaim Dreyfuss return false; 673230ebaa1SHaim Dreyfuss 674230ebaa1SHaim Dreyfuss if (aifsn < 1) 675230ebaa1SHaim Dreyfuss return false; 676230ebaa1SHaim Dreyfuss } 677230ebaa1SHaim Dreyfuss 678230ebaa1SHaim Dreyfuss return true; 679230ebaa1SHaim Dreyfuss } 680230ebaa1SHaim Dreyfuss 681007f6c5eSJohannes Berg static bool valid_rule(const u8 *data, unsigned int size, u16 rule_ptr) 682007f6c5eSJohannes Berg { 683007f6c5eSJohannes Berg struct fwdb_rule *rule = (void *)(data + (rule_ptr << 2)); 684007f6c5eSJohannes Berg 685007f6c5eSJohannes Berg if ((u8 *)rule + sizeof(rule->len) > data + size) 686007f6c5eSJohannes Berg return false; 687007f6c5eSJohannes Berg 688007f6c5eSJohannes Berg /* mandatory fields */ 689007f6c5eSJohannes Berg if (rule->len < offsetofend(struct fwdb_rule, max_bw)) 690007f6c5eSJohannes Berg return false; 691230ebaa1SHaim Dreyfuss if (rule->len >= offsetofend(struct fwdb_rule, wmm_ptr)) { 692230ebaa1SHaim Dreyfuss u32 wmm_ptr = be16_to_cpu(rule->wmm_ptr) << 2; 693230ebaa1SHaim Dreyfuss struct fwdb_wmm_rule *wmm; 694007f6c5eSJohannes Berg 695230ebaa1SHaim Dreyfuss if (wmm_ptr + sizeof(struct fwdb_wmm_rule) > size) 696230ebaa1SHaim Dreyfuss return false; 697230ebaa1SHaim Dreyfuss 698230ebaa1SHaim Dreyfuss wmm = (void *)(data + wmm_ptr); 699230ebaa1SHaim Dreyfuss 700230ebaa1SHaim Dreyfuss if (!valid_wmm(wmm)) 701230ebaa1SHaim Dreyfuss return false; 702230ebaa1SHaim Dreyfuss } 703007f6c5eSJohannes Berg return true; 704007f6c5eSJohannes Berg } 705007f6c5eSJohannes Berg 706007f6c5eSJohannes Berg static bool valid_country(const u8 *data, unsigned int size, 707007f6c5eSJohannes Berg const struct fwdb_country *country) 708007f6c5eSJohannes Berg { 709007f6c5eSJohannes Berg unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; 710007f6c5eSJohannes Berg struct fwdb_collection *coll = (void *)(data + ptr); 711007f6c5eSJohannes Berg __be16 *rules_ptr; 712007f6c5eSJohannes Berg unsigned int i; 713007f6c5eSJohannes Berg 714007f6c5eSJohannes Berg /* make sure we can read len/n_rules */ 715007f6c5eSJohannes Berg if ((u8 *)coll + offsetofend(typeof(*coll), n_rules) > data + size) 716007f6c5eSJohannes Berg return false; 717007f6c5eSJohannes Berg 718007f6c5eSJohannes Berg /* make sure base struct and all rules fit */ 719007f6c5eSJohannes Berg if ((u8 *)coll + ALIGN(coll->len, 2) + 720007f6c5eSJohannes Berg (coll->n_rules * 2) > data + size) 721007f6c5eSJohannes Berg return false; 722007f6c5eSJohannes Berg 723007f6c5eSJohannes Berg /* mandatory fields must exist */ 724007f6c5eSJohannes Berg if (coll->len < offsetofend(struct fwdb_collection, dfs_region)) 725007f6c5eSJohannes Berg return false; 726007f6c5eSJohannes Berg 727007f6c5eSJohannes Berg rules_ptr = (void *)((u8 *)coll + ALIGN(coll->len, 2)); 728007f6c5eSJohannes Berg 729007f6c5eSJohannes Berg for (i = 0; i < coll->n_rules; i++) { 730007f6c5eSJohannes Berg u16 rule_ptr = be16_to_cpu(rules_ptr[i]); 731007f6c5eSJohannes Berg 732007f6c5eSJohannes Berg if (!valid_rule(data, size, rule_ptr)) 733007f6c5eSJohannes Berg return false; 734007f6c5eSJohannes Berg } 735007f6c5eSJohannes Berg 736007f6c5eSJohannes Berg return true; 737007f6c5eSJohannes Berg } 738007f6c5eSJohannes Berg 73990a53e44SJohannes Berg #ifdef CONFIG_CFG80211_REQUIRE_SIGNED_REGDB 7403609ff64SLukas Wunner #include <keys/asymmetric-type.h> 7413609ff64SLukas Wunner 74290a53e44SJohannes Berg static struct key *builtin_regdb_keys; 74390a53e44SJohannes Berg 74490a53e44SJohannes Berg static int __init load_builtin_regdb_keys(void) 74590a53e44SJohannes Berg { 74690a53e44SJohannes Berg builtin_regdb_keys = 74790a53e44SJohannes Berg keyring_alloc(".builtin_regdb_keys", 74890a53e44SJohannes Berg KUIDT_INIT(0), KGIDT_INIT(0), current_cred(), 749028db3e2SLinus Torvalds ((KEY_POS_ALL & ~KEY_POS_SETATTR) | 750028db3e2SLinus Torvalds KEY_USR_VIEW | KEY_USR_READ | KEY_USR_SEARCH), 75190a53e44SJohannes Berg KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL); 75290a53e44SJohannes Berg if (IS_ERR(builtin_regdb_keys)) 75390a53e44SJohannes Berg return PTR_ERR(builtin_regdb_keys); 75490a53e44SJohannes Berg 75590a53e44SJohannes Berg pr_notice("Loading compiled-in X.509 certificates for regulatory database\n"); 75690a53e44SJohannes Berg 75790a53e44SJohannes Berg #ifdef CONFIG_CFG80211_USE_KERNEL_REGDB_KEYS 7583609ff64SLukas Wunner x509_load_certificate_list(shipped_regdb_certs, 7593609ff64SLukas Wunner shipped_regdb_certs_len, 7603609ff64SLukas Wunner builtin_regdb_keys); 76190a53e44SJohannes Berg #endif 76288230ef1SArnd Bergmann #ifdef CONFIG_CFG80211_EXTRA_REGDB_KEYDIR 76390a53e44SJohannes Berg if (CONFIG_CFG80211_EXTRA_REGDB_KEYDIR[0] != '\0') 7643609ff64SLukas Wunner x509_load_certificate_list(extra_regdb_certs, 7653609ff64SLukas Wunner extra_regdb_certs_len, 7663609ff64SLukas Wunner builtin_regdb_keys); 76790a53e44SJohannes Berg #endif 76890a53e44SJohannes Berg 76990a53e44SJohannes Berg return 0; 77090a53e44SJohannes Berg } 77190a53e44SJohannes Berg 7727bc7981eSDimitri John Ledkov MODULE_FIRMWARE("regulatory.db.p7s"); 7737bc7981eSDimitri John Ledkov 77490a53e44SJohannes Berg static bool regdb_has_valid_signature(const u8 *data, unsigned int size) 77590a53e44SJohannes Berg { 77690a53e44SJohannes Berg const struct firmware *sig; 77790a53e44SJohannes Berg bool result; 77890a53e44SJohannes Berg 77990a53e44SJohannes Berg if (request_firmware(&sig, "regulatory.db.p7s", ®_pdev->dev)) 78090a53e44SJohannes Berg return false; 78190a53e44SJohannes Berg 78290a53e44SJohannes Berg result = verify_pkcs7_signature(data, size, sig->data, sig->size, 78390a53e44SJohannes Berg builtin_regdb_keys, 78490a53e44SJohannes Berg VERIFYING_UNSPECIFIED_SIGNATURE, 78590a53e44SJohannes Berg NULL, NULL) == 0; 78690a53e44SJohannes Berg 78790a53e44SJohannes Berg release_firmware(sig); 78890a53e44SJohannes Berg 78990a53e44SJohannes Berg return result; 79090a53e44SJohannes Berg } 79190a53e44SJohannes Berg 79290a53e44SJohannes Berg static void free_regdb_keyring(void) 79390a53e44SJohannes Berg { 79490a53e44SJohannes Berg key_put(builtin_regdb_keys); 79590a53e44SJohannes Berg } 79690a53e44SJohannes Berg #else 79790a53e44SJohannes Berg static int load_builtin_regdb_keys(void) 79890a53e44SJohannes Berg { 79990a53e44SJohannes Berg return 0; 80090a53e44SJohannes Berg } 80190a53e44SJohannes Berg 80290a53e44SJohannes Berg static bool regdb_has_valid_signature(const u8 *data, unsigned int size) 80390a53e44SJohannes Berg { 80490a53e44SJohannes Berg return true; 80590a53e44SJohannes Berg } 80690a53e44SJohannes Berg 80790a53e44SJohannes Berg static void free_regdb_keyring(void) 80890a53e44SJohannes Berg { 80990a53e44SJohannes Berg } 81090a53e44SJohannes Berg #endif /* CONFIG_CFG80211_REQUIRE_SIGNED_REGDB */ 81190a53e44SJohannes Berg 812007f6c5eSJohannes Berg static bool valid_regdb(const u8 *data, unsigned int size) 813007f6c5eSJohannes Berg { 814007f6c5eSJohannes Berg const struct fwdb_header *hdr = (void *)data; 815007f6c5eSJohannes Berg const struct fwdb_country *country; 816007f6c5eSJohannes Berg 817007f6c5eSJohannes Berg if (size < sizeof(*hdr)) 818007f6c5eSJohannes Berg return false; 819007f6c5eSJohannes Berg 820007f6c5eSJohannes Berg if (hdr->magic != cpu_to_be32(FWDB_MAGIC)) 821007f6c5eSJohannes Berg return false; 822007f6c5eSJohannes Berg 823007f6c5eSJohannes Berg if (hdr->version != cpu_to_be32(FWDB_VERSION)) 824007f6c5eSJohannes Berg return false; 825007f6c5eSJohannes Berg 82690a53e44SJohannes Berg if (!regdb_has_valid_signature(data, size)) 82790a53e44SJohannes Berg return false; 82890a53e44SJohannes Berg 829007f6c5eSJohannes Berg country = &hdr->country[0]; 830007f6c5eSJohannes Berg while ((u8 *)(country + 1) <= data + size) { 831007f6c5eSJohannes Berg if (!country->coll_ptr) 832007f6c5eSJohannes Berg break; 833007f6c5eSJohannes Berg if (!valid_country(data, size, country)) 834007f6c5eSJohannes Berg return false; 835007f6c5eSJohannes Berg country++; 836007f6c5eSJohannes Berg } 837007f6c5eSJohannes Berg 838007f6c5eSJohannes Berg return true; 839007f6c5eSJohannes Berg } 840007f6c5eSJohannes Berg 841014f5a25SStanislaw Gruszka static void set_wmm_rule(const struct fwdb_header *db, 842014f5a25SStanislaw Gruszka const struct fwdb_country *country, 843014f5a25SStanislaw Gruszka const struct fwdb_rule *rule, 844014f5a25SStanislaw Gruszka struct ieee80211_reg_rule *rrule) 845230ebaa1SHaim Dreyfuss { 846014f5a25SStanislaw Gruszka struct ieee80211_wmm_rule *wmm_rule = &rrule->wmm_rule; 847014f5a25SStanislaw Gruszka struct fwdb_wmm_rule *wmm; 848014f5a25SStanislaw Gruszka unsigned int i, wmm_ptr; 849014f5a25SStanislaw Gruszka 850014f5a25SStanislaw Gruszka wmm_ptr = be16_to_cpu(rule->wmm_ptr) << 2; 851014f5a25SStanislaw Gruszka wmm = (void *)((u8 *)db + wmm_ptr); 852014f5a25SStanislaw Gruszka 853014f5a25SStanislaw Gruszka if (!valid_wmm(wmm)) { 854014f5a25SStanislaw Gruszka pr_err("Invalid regulatory WMM rule %u-%u in domain %c%c\n", 855014f5a25SStanislaw Gruszka be32_to_cpu(rule->start), be32_to_cpu(rule->end), 856014f5a25SStanislaw Gruszka country->alpha2[0], country->alpha2[1]); 857014f5a25SStanislaw Gruszka return; 858014f5a25SStanislaw Gruszka } 859230ebaa1SHaim Dreyfuss 860230ebaa1SHaim Dreyfuss for (i = 0; i < IEEE80211_NUM_ACS; i++) { 861014f5a25SStanislaw Gruszka wmm_rule->client[i].cw_min = 862230ebaa1SHaim Dreyfuss ecw2cw((wmm->client[i].ecw & 0xf0) >> 4); 863014f5a25SStanislaw Gruszka wmm_rule->client[i].cw_max = ecw2cw(wmm->client[i].ecw & 0x0f); 864014f5a25SStanislaw Gruszka wmm_rule->client[i].aifsn = wmm->client[i].aifsn; 865014f5a25SStanislaw Gruszka wmm_rule->client[i].cot = 866014f5a25SStanislaw Gruszka 1000 * be16_to_cpu(wmm->client[i].cot); 867014f5a25SStanislaw Gruszka wmm_rule->ap[i].cw_min = ecw2cw((wmm->ap[i].ecw & 0xf0) >> 4); 868014f5a25SStanislaw Gruszka wmm_rule->ap[i].cw_max = ecw2cw(wmm->ap[i].ecw & 0x0f); 869014f5a25SStanislaw Gruszka wmm_rule->ap[i].aifsn = wmm->ap[i].aifsn; 870014f5a25SStanislaw Gruszka wmm_rule->ap[i].cot = 1000 * be16_to_cpu(wmm->ap[i].cot); 871230ebaa1SHaim Dreyfuss } 87238cb87eeSStanislaw Gruszka 87338cb87eeSStanislaw Gruszka rrule->has_wmm = true; 874230ebaa1SHaim Dreyfuss } 875230ebaa1SHaim Dreyfuss 87619d3577eSHaim Dreyfuss static int __regdb_query_wmm(const struct fwdb_header *db, 87719d3577eSHaim Dreyfuss const struct fwdb_country *country, int freq, 878014f5a25SStanislaw Gruszka struct ieee80211_reg_rule *rrule) 87919d3577eSHaim Dreyfuss { 88019d3577eSHaim Dreyfuss unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; 88119d3577eSHaim Dreyfuss struct fwdb_collection *coll = (void *)((u8 *)db + ptr); 88219d3577eSHaim Dreyfuss int i; 88319d3577eSHaim Dreyfuss 88419d3577eSHaim Dreyfuss for (i = 0; i < coll->n_rules; i++) { 88519d3577eSHaim Dreyfuss __be16 *rules_ptr = (void *)((u8 *)coll + ALIGN(coll->len, 2)); 88619d3577eSHaim Dreyfuss unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2; 887014f5a25SStanislaw Gruszka struct fwdb_rule *rule = (void *)((u8 *)db + rule_ptr); 88819d3577eSHaim Dreyfuss 889014f5a25SStanislaw Gruszka if (rule->len < offsetofend(struct fwdb_rule, wmm_ptr)) 89019d3577eSHaim Dreyfuss continue; 89119d3577eSHaim Dreyfuss 892014f5a25SStanislaw Gruszka if (freq >= KHZ_TO_MHZ(be32_to_cpu(rule->start)) && 893014f5a25SStanislaw Gruszka freq <= KHZ_TO_MHZ(be32_to_cpu(rule->end))) { 894014f5a25SStanislaw Gruszka set_wmm_rule(db, country, rule, rrule); 89519d3577eSHaim Dreyfuss return 0; 89619d3577eSHaim Dreyfuss } 89719d3577eSHaim Dreyfuss } 89819d3577eSHaim Dreyfuss 89919d3577eSHaim Dreyfuss return -ENODATA; 90019d3577eSHaim Dreyfuss } 90119d3577eSHaim Dreyfuss 90238cb87eeSStanislaw Gruszka int reg_query_regdb_wmm(char *alpha2, int freq, struct ieee80211_reg_rule *rule) 90319d3577eSHaim Dreyfuss { 90419d3577eSHaim Dreyfuss const struct fwdb_header *hdr = regdb; 90519d3577eSHaim Dreyfuss const struct fwdb_country *country; 90619d3577eSHaim Dreyfuss 9075247a77cSHaim Dreyfuss if (!regdb) 9085247a77cSHaim Dreyfuss return -ENODATA; 9095247a77cSHaim Dreyfuss 91019d3577eSHaim Dreyfuss if (IS_ERR(regdb)) 91119d3577eSHaim Dreyfuss return PTR_ERR(regdb); 91219d3577eSHaim Dreyfuss 91319d3577eSHaim Dreyfuss country = &hdr->country[0]; 91419d3577eSHaim Dreyfuss while (country->coll_ptr) { 91519d3577eSHaim Dreyfuss if (alpha2_equal(alpha2, country->alpha2)) 91638cb87eeSStanislaw Gruszka return __regdb_query_wmm(regdb, country, freq, rule); 91719d3577eSHaim Dreyfuss 91819d3577eSHaim Dreyfuss country++; 91919d3577eSHaim Dreyfuss } 92019d3577eSHaim Dreyfuss 92119d3577eSHaim Dreyfuss return -ENODATA; 92219d3577eSHaim Dreyfuss } 92319d3577eSHaim Dreyfuss EXPORT_SYMBOL(reg_query_regdb_wmm); 92419d3577eSHaim Dreyfuss 925007f6c5eSJohannes Berg static int regdb_query_country(const struct fwdb_header *db, 926007f6c5eSJohannes Berg const struct fwdb_country *country) 927007f6c5eSJohannes Berg { 928007f6c5eSJohannes Berg unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; 929007f6c5eSJohannes Berg struct fwdb_collection *coll = (void *)((u8 *)db + ptr); 930007f6c5eSJohannes Berg struct ieee80211_regdomain *regdom; 9319f8c7136SGustavo A. R. Silva unsigned int i; 932007f6c5eSJohannes Berg 9339f8c7136SGustavo A. R. Silva regdom = kzalloc(struct_size(regdom, reg_rules, coll->n_rules), 9349f8c7136SGustavo A. R. Silva GFP_KERNEL); 935007f6c5eSJohannes Berg if (!regdom) 936007f6c5eSJohannes Berg return -ENOMEM; 937007f6c5eSJohannes Berg 938007f6c5eSJohannes Berg regdom->n_reg_rules = coll->n_rules; 939007f6c5eSJohannes Berg regdom->alpha2[0] = country->alpha2[0]; 940007f6c5eSJohannes Berg regdom->alpha2[1] = country->alpha2[1]; 941007f6c5eSJohannes Berg regdom->dfs_region = coll->dfs_region; 942007f6c5eSJohannes Berg 943007f6c5eSJohannes Berg for (i = 0; i < regdom->n_reg_rules; i++) { 944007f6c5eSJohannes Berg __be16 *rules_ptr = (void *)((u8 *)coll + ALIGN(coll->len, 2)); 945007f6c5eSJohannes Berg unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2; 946007f6c5eSJohannes Berg struct fwdb_rule *rule = (void *)((u8 *)db + rule_ptr); 947007f6c5eSJohannes Berg struct ieee80211_reg_rule *rrule = ®dom->reg_rules[i]; 948007f6c5eSJohannes Berg 949007f6c5eSJohannes Berg rrule->freq_range.start_freq_khz = be32_to_cpu(rule->start); 950007f6c5eSJohannes Berg rrule->freq_range.end_freq_khz = be32_to_cpu(rule->end); 951007f6c5eSJohannes Berg rrule->freq_range.max_bandwidth_khz = be32_to_cpu(rule->max_bw); 952007f6c5eSJohannes Berg 953007f6c5eSJohannes Berg rrule->power_rule.max_antenna_gain = 0; 954007f6c5eSJohannes Berg rrule->power_rule.max_eirp = be16_to_cpu(rule->max_eirp); 955007f6c5eSJohannes Berg 956007f6c5eSJohannes Berg rrule->flags = 0; 957007f6c5eSJohannes Berg if (rule->flags & FWDB_FLAG_NO_OFDM) 958007f6c5eSJohannes Berg rrule->flags |= NL80211_RRF_NO_OFDM; 959007f6c5eSJohannes Berg if (rule->flags & FWDB_FLAG_NO_OUTDOOR) 960007f6c5eSJohannes Berg rrule->flags |= NL80211_RRF_NO_OUTDOOR; 961007f6c5eSJohannes Berg if (rule->flags & FWDB_FLAG_DFS) 962007f6c5eSJohannes Berg rrule->flags |= NL80211_RRF_DFS; 963007f6c5eSJohannes Berg if (rule->flags & FWDB_FLAG_NO_IR) 964007f6c5eSJohannes Berg rrule->flags |= NL80211_RRF_NO_IR; 965007f6c5eSJohannes Berg if (rule->flags & FWDB_FLAG_AUTO_BW) 966007f6c5eSJohannes Berg rrule->flags |= NL80211_RRF_AUTO_BW; 967007f6c5eSJohannes Berg 968007f6c5eSJohannes Berg rrule->dfs_cac_ms = 0; 969007f6c5eSJohannes Berg 970007f6c5eSJohannes Berg /* handle optional data */ 971007f6c5eSJohannes Berg if (rule->len >= offsetofend(struct fwdb_rule, cac_timeout)) 972007f6c5eSJohannes Berg rrule->dfs_cac_ms = 973007f6c5eSJohannes Berg 1000 * be16_to_cpu(rule->cac_timeout); 974014f5a25SStanislaw Gruszka if (rule->len >= offsetofend(struct fwdb_rule, wmm_ptr)) 975014f5a25SStanislaw Gruszka set_wmm_rule(db, country, rule, rrule); 976230ebaa1SHaim Dreyfuss } 977007f6c5eSJohannes Berg 978007f6c5eSJohannes Berg return reg_schedule_apply(regdom); 979007f6c5eSJohannes Berg } 980007f6c5eSJohannes Berg 981007f6c5eSJohannes Berg static int query_regdb(const char *alpha2) 982007f6c5eSJohannes Berg { 983007f6c5eSJohannes Berg const struct fwdb_header *hdr = regdb; 984007f6c5eSJohannes Berg const struct fwdb_country *country; 985007f6c5eSJohannes Berg 9861ea4ff3eSJohannes Berg ASSERT_RTNL(); 9871ea4ff3eSJohannes Berg 988007f6c5eSJohannes Berg if (IS_ERR(regdb)) 989007f6c5eSJohannes Berg return PTR_ERR(regdb); 990007f6c5eSJohannes Berg 991007f6c5eSJohannes Berg country = &hdr->country[0]; 992007f6c5eSJohannes Berg while (country->coll_ptr) { 993007f6c5eSJohannes Berg if (alpha2_equal(alpha2, country->alpha2)) 994007f6c5eSJohannes Berg return regdb_query_country(regdb, country); 995007f6c5eSJohannes Berg country++; 996007f6c5eSJohannes Berg } 997007f6c5eSJohannes Berg 998007f6c5eSJohannes Berg return -ENODATA; 999007f6c5eSJohannes Berg } 1000007f6c5eSJohannes Berg 1001007f6c5eSJohannes Berg static void regdb_fw_cb(const struct firmware *fw, void *context) 1002007f6c5eSJohannes Berg { 10031ea4ff3eSJohannes Berg int set_error = 0; 10041ea4ff3eSJohannes Berg bool restore = true; 1005007f6c5eSJohannes Berg void *db; 1006007f6c5eSJohannes Berg 1007007f6c5eSJohannes Berg if (!fw) { 1008007f6c5eSJohannes Berg pr_info("failed to load regulatory.db\n"); 10091ea4ff3eSJohannes Berg set_error = -ENODATA; 10101ea4ff3eSJohannes Berg } else if (!valid_regdb(fw->data, fw->size)) { 101190a53e44SJohannes Berg pr_info("loaded regulatory.db is malformed or signature is missing/invalid\n"); 10121ea4ff3eSJohannes Berg set_error = -EINVAL; 1013007f6c5eSJohannes Berg } 1014007f6c5eSJohannes Berg 1015007f6c5eSJohannes Berg rtnl_lock(); 1016faae54adSChaitanya Tata if (regdb && !IS_ERR(regdb)) { 1017faae54adSChaitanya Tata /* negative case - a bug 1018faae54adSChaitanya Tata * positive case - can happen due to race in case of multiple cb's in 1019faae54adSChaitanya Tata * queue, due to usage of asynchronous callback 1020faae54adSChaitanya Tata * 1021faae54adSChaitanya Tata * Either case, just restore and free new db. 1022faae54adSChaitanya Tata */ 10231ea4ff3eSJohannes Berg } else if (set_error) { 10241ea4ff3eSJohannes Berg regdb = ERR_PTR(set_error); 10251ea4ff3eSJohannes Berg } else if (fw) { 10261ea4ff3eSJohannes Berg db = kmemdup(fw->data, fw->size, GFP_KERNEL); 10271ea4ff3eSJohannes Berg if (db) { 10281ea4ff3eSJohannes Berg regdb = db; 10291ea4ff3eSJohannes Berg restore = context && query_regdb(context); 10301ea4ff3eSJohannes Berg } else { 10311ea4ff3eSJohannes Berg restore = true; 10321ea4ff3eSJohannes Berg } 10331ea4ff3eSJohannes Berg } 10341ea4ff3eSJohannes Berg 10351ea4ff3eSJohannes Berg if (restore) 1036e646a025SJohannes Berg restore_regulatory_settings(true, false); 10371ea4ff3eSJohannes Berg 1038007f6c5eSJohannes Berg rtnl_unlock(); 10391ea4ff3eSJohannes Berg 1040007f6c5eSJohannes Berg kfree(context); 10411ea4ff3eSJohannes Berg 10421ea4ff3eSJohannes Berg release_firmware(fw); 1043007f6c5eSJohannes Berg } 1044007f6c5eSJohannes Berg 10457bc7981eSDimitri John Ledkov MODULE_FIRMWARE("regulatory.db"); 10467bc7981eSDimitri John Ledkov 1047007f6c5eSJohannes Berg static int query_regdb_file(const char *alpha2) 1048007f6c5eSJohannes Berg { 104957b962e6SArend van Spriel int err; 105057b962e6SArend van Spriel 10511ea4ff3eSJohannes Berg ASSERT_RTNL(); 10521ea4ff3eSJohannes Berg 1053007f6c5eSJohannes Berg if (regdb) 1054007f6c5eSJohannes Berg return query_regdb(alpha2); 1055007f6c5eSJohannes Berg 1056007f6c5eSJohannes Berg alpha2 = kmemdup(alpha2, 2, GFP_KERNEL); 1057007f6c5eSJohannes Berg if (!alpha2) 1058007f6c5eSJohannes Berg return -ENOMEM; 1059007f6c5eSJohannes Berg 106057b962e6SArend van Spriel err = request_firmware_nowait(THIS_MODULE, true, "regulatory.db", 1061007f6c5eSJohannes Berg ®_pdev->dev, GFP_KERNEL, 1062007f6c5eSJohannes Berg (void *)alpha2, regdb_fw_cb); 106357b962e6SArend van Spriel if (err) 106457b962e6SArend van Spriel kfree(alpha2); 106557b962e6SArend van Spriel 106657b962e6SArend van Spriel return err; 1067007f6c5eSJohannes Berg } 1068007f6c5eSJohannes Berg 10691ea4ff3eSJohannes Berg int reg_reload_regdb(void) 10701ea4ff3eSJohannes Berg { 10711ea4ff3eSJohannes Berg const struct firmware *fw; 10721ea4ff3eSJohannes Berg void *db; 10731ea4ff3eSJohannes Berg int err; 10741eda9191SFinn Behrens const struct ieee80211_regdomain *current_regdomain; 10751eda9191SFinn Behrens struct regulatory_request *request; 10761ea4ff3eSJohannes Berg 10771ea4ff3eSJohannes Berg err = request_firmware(&fw, "regulatory.db", ®_pdev->dev); 10781ea4ff3eSJohannes Berg if (err) 10791ea4ff3eSJohannes Berg return err; 10801ea4ff3eSJohannes Berg 10811ea4ff3eSJohannes Berg if (!valid_regdb(fw->data, fw->size)) { 10821ea4ff3eSJohannes Berg err = -ENODATA; 10831ea4ff3eSJohannes Berg goto out; 10841ea4ff3eSJohannes Berg } 10851ea4ff3eSJohannes Berg 10861ea4ff3eSJohannes Berg db = kmemdup(fw->data, fw->size, GFP_KERNEL); 10871ea4ff3eSJohannes Berg if (!db) { 10881ea4ff3eSJohannes Berg err = -ENOMEM; 10891ea4ff3eSJohannes Berg goto out; 10901ea4ff3eSJohannes Berg } 10911ea4ff3eSJohannes Berg 10921ea4ff3eSJohannes Berg rtnl_lock(); 10931ea4ff3eSJohannes Berg if (!IS_ERR_OR_NULL(regdb)) 10941ea4ff3eSJohannes Berg kfree(regdb); 10951ea4ff3eSJohannes Berg regdb = db; 10961ea4ff3eSJohannes Berg 10971eda9191SFinn Behrens /* reset regulatory domain */ 10981eda9191SFinn Behrens current_regdomain = get_cfg80211_regdom(); 10991eda9191SFinn Behrens 11001eda9191SFinn Behrens request = kzalloc(sizeof(*request), GFP_KERNEL); 11011eda9191SFinn Behrens if (!request) { 11021eda9191SFinn Behrens err = -ENOMEM; 11031eda9191SFinn Behrens goto out_unlock; 11041eda9191SFinn Behrens } 11051eda9191SFinn Behrens 11061eda9191SFinn Behrens request->wiphy_idx = WIPHY_IDX_INVALID; 11071eda9191SFinn Behrens request->alpha2[0] = current_regdomain->alpha2[0]; 11081eda9191SFinn Behrens request->alpha2[1] = current_regdomain->alpha2[1]; 110937d33114SFinn Behrens request->initiator = NL80211_REGDOM_SET_BY_CORE; 11101eda9191SFinn Behrens request->user_reg_hint_type = NL80211_USER_REG_HINT_USER; 11111eda9191SFinn Behrens 11121eda9191SFinn Behrens reg_process_hint(request); 11131eda9191SFinn Behrens 11141eda9191SFinn Behrens out_unlock: 11151eda9191SFinn Behrens rtnl_unlock(); 11161ea4ff3eSJohannes Berg out: 11171ea4ff3eSJohannes Berg release_firmware(fw); 11181ea4ff3eSJohannes Berg return err; 11191ea4ff3eSJohannes Berg } 11201ea4ff3eSJohannes Berg 1121cecbb069SJohannes Berg static bool reg_query_database(struct regulatory_request *request) 1122fe6631ffSLuis R. Rodriguez { 1123007f6c5eSJohannes Berg if (query_regdb_file(request->alpha2) == 0) 1124007f6c5eSJohannes Berg return true; 1125007f6c5eSJohannes Berg 1126c7d319e5SJohannes Berg if (call_crda(request->alpha2) == 0) 1127c7d319e5SJohannes Berg return true; 1128c7d319e5SJohannes Berg 1129c7d319e5SJohannes Berg return false; 1130fe6631ffSLuis R. Rodriguez } 1131fe6631ffSLuis R. Rodriguez 1132e438768fSLuis R. Rodriguez bool reg_is_valid_request(const char *alpha2) 1133b2e1b302SLuis R. Rodriguez { 1134c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 113561405e97SLuis R. Rodriguez 1136c492db37SJohannes Berg if (!lr || lr->processed) 1137f6037d09SJohannes Berg return false; 1138f6037d09SJohannes Berg 1139c492db37SJohannes Berg return alpha2_equal(lr->alpha2, alpha2); 1140b2e1b302SLuis R. Rodriguez } 1141b2e1b302SLuis R. Rodriguez 1142e3961af1SJanusz Dziedzic static const struct ieee80211_regdomain *reg_get_regdomain(struct wiphy *wiphy) 1143e3961af1SJanusz Dziedzic { 1144e3961af1SJanusz Dziedzic struct regulatory_request *lr = get_last_request(); 1145e3961af1SJanusz Dziedzic 1146e3961af1SJanusz Dziedzic /* 1147e3961af1SJanusz Dziedzic * Follow the driver's regulatory domain, if present, unless a country 1148e3961af1SJanusz Dziedzic * IE has been processed or a user wants to help complaince further 1149e3961af1SJanusz Dziedzic */ 1150e3961af1SJanusz Dziedzic if (lr->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE && 1151e3961af1SJanusz Dziedzic lr->initiator != NL80211_REGDOM_SET_BY_USER && 1152e3961af1SJanusz Dziedzic wiphy->regd) 1153e3961af1SJanusz Dziedzic return get_wiphy_regdom(wiphy); 1154e3961af1SJanusz Dziedzic 1155e3961af1SJanusz Dziedzic return get_cfg80211_regdom(); 1156e3961af1SJanusz Dziedzic } 1157e3961af1SJanusz Dziedzic 1158a6d4a534SArik Nemtsov static unsigned int 1159a6d4a534SArik Nemtsov reg_get_max_bandwidth_from_range(const struct ieee80211_regdomain *rd, 116097524820SJanusz Dziedzic const struct ieee80211_reg_rule *rule) 116197524820SJanusz Dziedzic { 116297524820SJanusz Dziedzic const struct ieee80211_freq_range *freq_range = &rule->freq_range; 116397524820SJanusz Dziedzic const struct ieee80211_freq_range *freq_range_tmp; 116497524820SJanusz Dziedzic const struct ieee80211_reg_rule *tmp; 116597524820SJanusz Dziedzic u32 start_freq, end_freq, idx, no; 116697524820SJanusz Dziedzic 116797524820SJanusz Dziedzic for (idx = 0; idx < rd->n_reg_rules; idx++) 116897524820SJanusz Dziedzic if (rule == &rd->reg_rules[idx]) 116997524820SJanusz Dziedzic break; 117097524820SJanusz Dziedzic 117197524820SJanusz Dziedzic if (idx == rd->n_reg_rules) 117297524820SJanusz Dziedzic return 0; 117397524820SJanusz Dziedzic 117497524820SJanusz Dziedzic /* get start_freq */ 117597524820SJanusz Dziedzic no = idx; 117697524820SJanusz Dziedzic 117797524820SJanusz Dziedzic while (no) { 117897524820SJanusz Dziedzic tmp = &rd->reg_rules[--no]; 117997524820SJanusz Dziedzic freq_range_tmp = &tmp->freq_range; 118097524820SJanusz Dziedzic 118197524820SJanusz Dziedzic if (freq_range_tmp->end_freq_khz < freq_range->start_freq_khz) 118297524820SJanusz Dziedzic break; 118397524820SJanusz Dziedzic 118497524820SJanusz Dziedzic freq_range = freq_range_tmp; 118597524820SJanusz Dziedzic } 118697524820SJanusz Dziedzic 118797524820SJanusz Dziedzic start_freq = freq_range->start_freq_khz; 118897524820SJanusz Dziedzic 118997524820SJanusz Dziedzic /* get end_freq */ 119097524820SJanusz Dziedzic freq_range = &rule->freq_range; 119197524820SJanusz Dziedzic no = idx; 119297524820SJanusz Dziedzic 119397524820SJanusz Dziedzic while (no < rd->n_reg_rules - 1) { 119497524820SJanusz Dziedzic tmp = &rd->reg_rules[++no]; 119597524820SJanusz Dziedzic freq_range_tmp = &tmp->freq_range; 119697524820SJanusz Dziedzic 119797524820SJanusz Dziedzic if (freq_range_tmp->start_freq_khz > freq_range->end_freq_khz) 119897524820SJanusz Dziedzic break; 119997524820SJanusz Dziedzic 120097524820SJanusz Dziedzic freq_range = freq_range_tmp; 120197524820SJanusz Dziedzic } 120297524820SJanusz Dziedzic 120397524820SJanusz Dziedzic end_freq = freq_range->end_freq_khz; 120497524820SJanusz Dziedzic 120597524820SJanusz Dziedzic return end_freq - start_freq; 120697524820SJanusz Dziedzic } 120797524820SJanusz Dziedzic 1208a6d4a534SArik Nemtsov unsigned int reg_get_max_bandwidth(const struct ieee80211_regdomain *rd, 1209a6d4a534SArik Nemtsov const struct ieee80211_reg_rule *rule) 1210a6d4a534SArik Nemtsov { 1211a6d4a534SArik Nemtsov unsigned int bw = reg_get_max_bandwidth_from_range(rd, rule); 1212a6d4a534SArik Nemtsov 1213c2b3d769SSriram R if (rule->flags & NL80211_RRF_NO_320MHZ) 1214c2b3d769SSriram R bw = min_t(unsigned int, bw, MHZ_TO_KHZ(160)); 1215a6d4a534SArik Nemtsov if (rule->flags & NL80211_RRF_NO_160MHZ) 1216a6d4a534SArik Nemtsov bw = min_t(unsigned int, bw, MHZ_TO_KHZ(80)); 1217a6d4a534SArik Nemtsov if (rule->flags & NL80211_RRF_NO_80MHZ) 1218a6d4a534SArik Nemtsov bw = min_t(unsigned int, bw, MHZ_TO_KHZ(40)); 1219a6d4a534SArik Nemtsov 1220a6d4a534SArik Nemtsov /* 1221a6d4a534SArik Nemtsov * HT40+/HT40- limits are handled per-channel. Only limit BW if both 1222a6d4a534SArik Nemtsov * are not allowed. 1223a6d4a534SArik Nemtsov */ 1224a6d4a534SArik Nemtsov if (rule->flags & NL80211_RRF_NO_HT40MINUS && 1225a6d4a534SArik Nemtsov rule->flags & NL80211_RRF_NO_HT40PLUS) 1226a6d4a534SArik Nemtsov bw = min_t(unsigned int, bw, MHZ_TO_KHZ(20)); 1227a6d4a534SArik Nemtsov 1228a6d4a534SArik Nemtsov return bw; 1229a6d4a534SArik Nemtsov } 1230a6d4a534SArik Nemtsov 1231b2e1b302SLuis R. Rodriguez /* Sanity check on a regulatory rule */ 1232a3d2eaf0SJohannes Berg static bool is_valid_reg_rule(const struct ieee80211_reg_rule *rule) 1233b2e1b302SLuis R. Rodriguez { 1234a3d2eaf0SJohannes Berg const struct ieee80211_freq_range *freq_range = &rule->freq_range; 1235b2e1b302SLuis R. Rodriguez u32 freq_diff; 1236b2e1b302SLuis R. Rodriguez 123791e99004SLuis R. Rodriguez if (freq_range->start_freq_khz <= 0 || freq_range->end_freq_khz <= 0) 1238b2e1b302SLuis R. Rodriguez return false; 1239b2e1b302SLuis R. Rodriguez 1240b2e1b302SLuis R. Rodriguez if (freq_range->start_freq_khz > freq_range->end_freq_khz) 1241b2e1b302SLuis R. Rodriguez return false; 1242b2e1b302SLuis R. Rodriguez 1243b2e1b302SLuis R. Rodriguez freq_diff = freq_range->end_freq_khz - freq_range->start_freq_khz; 1244b2e1b302SLuis R. Rodriguez 1245bd05f28eSRoel Kluin if (freq_range->end_freq_khz <= freq_range->start_freq_khz || 1246bd05f28eSRoel Kluin freq_range->max_bandwidth_khz > freq_diff) 1247b2e1b302SLuis R. Rodriguez return false; 1248b2e1b302SLuis R. Rodriguez 1249b2e1b302SLuis R. Rodriguez return true; 1250b2e1b302SLuis R. Rodriguez } 1251b2e1b302SLuis R. Rodriguez 1252a3d2eaf0SJohannes Berg static bool is_valid_rd(const struct ieee80211_regdomain *rd) 1253b2e1b302SLuis R. Rodriguez { 1254a3d2eaf0SJohannes Berg const struct ieee80211_reg_rule *reg_rule = NULL; 1255b2e1b302SLuis R. Rodriguez unsigned int i; 1256b2e1b302SLuis R. Rodriguez 1257b2e1b302SLuis R. Rodriguez if (!rd->n_reg_rules) 1258b2e1b302SLuis R. Rodriguez return false; 1259b2e1b302SLuis R. Rodriguez 126088dc1c3fSLuis R. Rodriguez if (WARN_ON(rd->n_reg_rules > NL80211_MAX_SUPP_REG_RULES)) 126188dc1c3fSLuis R. Rodriguez return false; 126288dc1c3fSLuis R. Rodriguez 1263b2e1b302SLuis R. Rodriguez for (i = 0; i < rd->n_reg_rules; i++) { 1264b2e1b302SLuis R. Rodriguez reg_rule = &rd->reg_rules[i]; 1265b2e1b302SLuis R. Rodriguez if (!is_valid_reg_rule(reg_rule)) 1266b2e1b302SLuis R. Rodriguez return false; 1267b2e1b302SLuis R. Rodriguez } 1268b2e1b302SLuis R. Rodriguez 1269b2e1b302SLuis R. Rodriguez return true; 1270b2e1b302SLuis R. Rodriguez } 1271b2e1b302SLuis R. Rodriguez 12720c7dc45dSLuis R. Rodriguez /** 12730c7dc45dSLuis R. Rodriguez * freq_in_rule_band - tells us if a frequency is in a frequency band 12740c7dc45dSLuis R. Rodriguez * @freq_range: frequency rule we want to query 12750c7dc45dSLuis R. Rodriguez * @freq_khz: frequency we are inquiring about 12760c7dc45dSLuis R. Rodriguez * 12770c7dc45dSLuis R. Rodriguez * This lets us know if a specific frequency rule is or is not relevant to 12780c7dc45dSLuis R. Rodriguez * a specific frequency's band. Bands are device specific and artificial 127964629b9dSVladimir Kondratiev * definitions (the "2.4 GHz band", the "5 GHz band" and the "60GHz band"), 128064629b9dSVladimir Kondratiev * however it is safe for now to assume that a frequency rule should not be 128164629b9dSVladimir Kondratiev * part of a frequency's band if the start freq or end freq are off by more 128293183bdbSChaitanya Tata * than 2 GHz for the 2.4 and 5 GHz bands, and by more than 20 GHz for the 128364629b9dSVladimir Kondratiev * 60 GHz band. 12840c7dc45dSLuis R. Rodriguez * This resolution can be lowered and should be considered as we add 12850c7dc45dSLuis R. Rodriguez * regulatory rule support for other "bands". 12860c7dc45dSLuis R. Rodriguez **/ 12870c7dc45dSLuis R. Rodriguez static bool freq_in_rule_band(const struct ieee80211_freq_range *freq_range, 12880c7dc45dSLuis R. Rodriguez u32 freq_khz) 12890c7dc45dSLuis R. Rodriguez { 12900c7dc45dSLuis R. Rodriguez #define ONE_GHZ_IN_KHZ 1000000 129164629b9dSVladimir Kondratiev /* 129264629b9dSVladimir Kondratiev * From 802.11ad: directional multi-gigabit (DMG): 129364629b9dSVladimir Kondratiev * Pertaining to operation in a frequency band containing a channel 129464629b9dSVladimir Kondratiev * with the Channel starting frequency above 45 GHz. 129564629b9dSVladimir Kondratiev */ 129664629b9dSVladimir Kondratiev u32 limit = freq_khz > 45 * ONE_GHZ_IN_KHZ ? 129793183bdbSChaitanya Tata 20 * ONE_GHZ_IN_KHZ : 2 * ONE_GHZ_IN_KHZ; 129864629b9dSVladimir Kondratiev if (abs(freq_khz - freq_range->start_freq_khz) <= limit) 12990c7dc45dSLuis R. Rodriguez return true; 130064629b9dSVladimir Kondratiev if (abs(freq_khz - freq_range->end_freq_khz) <= limit) 13010c7dc45dSLuis R. Rodriguez return true; 13020c7dc45dSLuis R. Rodriguez return false; 13030c7dc45dSLuis R. Rodriguez #undef ONE_GHZ_IN_KHZ 13040c7dc45dSLuis R. Rodriguez } 13050c7dc45dSLuis R. Rodriguez 1306fb1fc7adSLuis R. Rodriguez /* 1307adbfb058SLuis R. Rodriguez * Later on we can perhaps use the more restrictive DFS 1308adbfb058SLuis R. Rodriguez * region but we don't have information for that yet so 1309adbfb058SLuis R. Rodriguez * for now simply disallow conflicts. 1310adbfb058SLuis R. Rodriguez */ 1311adbfb058SLuis R. Rodriguez static enum nl80211_dfs_regions 1312adbfb058SLuis R. Rodriguez reg_intersect_dfs_region(const enum nl80211_dfs_regions dfs_region1, 1313adbfb058SLuis R. Rodriguez const enum nl80211_dfs_regions dfs_region2) 1314adbfb058SLuis R. Rodriguez { 1315adbfb058SLuis R. Rodriguez if (dfs_region1 != dfs_region2) 1316adbfb058SLuis R. Rodriguez return NL80211_DFS_UNSET; 1317adbfb058SLuis R. Rodriguez return dfs_region1; 1318adbfb058SLuis R. Rodriguez } 1319adbfb058SLuis R. Rodriguez 132008a75a88SIlan Peer static void reg_wmm_rules_intersect(const struct ieee80211_wmm_ac *wmm_ac1, 132108a75a88SIlan Peer const struct ieee80211_wmm_ac *wmm_ac2, 132208a75a88SIlan Peer struct ieee80211_wmm_ac *intersect) 132308a75a88SIlan Peer { 132408a75a88SIlan Peer intersect->cw_min = max_t(u16, wmm_ac1->cw_min, wmm_ac2->cw_min); 132508a75a88SIlan Peer intersect->cw_max = max_t(u16, wmm_ac1->cw_max, wmm_ac2->cw_max); 132608a75a88SIlan Peer intersect->cot = min_t(u16, wmm_ac1->cot, wmm_ac2->cot); 132708a75a88SIlan Peer intersect->aifsn = max_t(u8, wmm_ac1->aifsn, wmm_ac2->aifsn); 132808a75a88SIlan Peer } 132908a75a88SIlan Peer 1330adbfb058SLuis R. Rodriguez /* 1331fb1fc7adSLuis R. Rodriguez * Helper for regdom_intersect(), this does the real 1332fb1fc7adSLuis R. Rodriguez * mathematical intersection fun 1333fb1fc7adSLuis R. Rodriguez */ 133497524820SJanusz Dziedzic static int reg_rules_intersect(const struct ieee80211_regdomain *rd1, 133597524820SJanusz Dziedzic const struct ieee80211_regdomain *rd2, 133697524820SJanusz Dziedzic const struct ieee80211_reg_rule *rule1, 13379c96477dSLuis R. Rodriguez const struct ieee80211_reg_rule *rule2, 13389c96477dSLuis R. Rodriguez struct ieee80211_reg_rule *intersected_rule) 13399c96477dSLuis R. Rodriguez { 13409c96477dSLuis R. Rodriguez const struct ieee80211_freq_range *freq_range1, *freq_range2; 13419c96477dSLuis R. Rodriguez struct ieee80211_freq_range *freq_range; 13429c96477dSLuis R. Rodriguez const struct ieee80211_power_rule *power_rule1, *power_rule2; 13439c96477dSLuis R. Rodriguez struct ieee80211_power_rule *power_rule; 134408a75a88SIlan Peer const struct ieee80211_wmm_rule *wmm_rule1, *wmm_rule2; 134508a75a88SIlan Peer struct ieee80211_wmm_rule *wmm_rule; 134697524820SJanusz Dziedzic u32 freq_diff, max_bandwidth1, max_bandwidth2; 13479c96477dSLuis R. Rodriguez 13489c96477dSLuis R. Rodriguez freq_range1 = &rule1->freq_range; 13499c96477dSLuis R. Rodriguez freq_range2 = &rule2->freq_range; 13509c96477dSLuis R. Rodriguez freq_range = &intersected_rule->freq_range; 13519c96477dSLuis R. Rodriguez 13529c96477dSLuis R. Rodriguez power_rule1 = &rule1->power_rule; 13539c96477dSLuis R. Rodriguez power_rule2 = &rule2->power_rule; 13549c96477dSLuis R. Rodriguez power_rule = &intersected_rule->power_rule; 13559c96477dSLuis R. Rodriguez 135608a75a88SIlan Peer wmm_rule1 = &rule1->wmm_rule; 135708a75a88SIlan Peer wmm_rule2 = &rule2->wmm_rule; 135808a75a88SIlan Peer wmm_rule = &intersected_rule->wmm_rule; 135908a75a88SIlan Peer 13609c96477dSLuis R. Rodriguez freq_range->start_freq_khz = max(freq_range1->start_freq_khz, 13619c96477dSLuis R. Rodriguez freq_range2->start_freq_khz); 13629c96477dSLuis R. Rodriguez freq_range->end_freq_khz = min(freq_range1->end_freq_khz, 13639c96477dSLuis R. Rodriguez freq_range2->end_freq_khz); 136497524820SJanusz Dziedzic 136597524820SJanusz Dziedzic max_bandwidth1 = freq_range1->max_bandwidth_khz; 136697524820SJanusz Dziedzic max_bandwidth2 = freq_range2->max_bandwidth_khz; 136797524820SJanusz Dziedzic 1368b0dfd2eaSJanusz Dziedzic if (rule1->flags & NL80211_RRF_AUTO_BW) 136997524820SJanusz Dziedzic max_bandwidth1 = reg_get_max_bandwidth(rd1, rule1); 1370b0dfd2eaSJanusz Dziedzic if (rule2->flags & NL80211_RRF_AUTO_BW) 137197524820SJanusz Dziedzic max_bandwidth2 = reg_get_max_bandwidth(rd2, rule2); 137297524820SJanusz Dziedzic 137397524820SJanusz Dziedzic freq_range->max_bandwidth_khz = min(max_bandwidth1, max_bandwidth2); 13749c96477dSLuis R. Rodriguez 1375b0dfd2eaSJanusz Dziedzic intersected_rule->flags = rule1->flags | rule2->flags; 1376b0dfd2eaSJanusz Dziedzic 1377b0dfd2eaSJanusz Dziedzic /* 1378b0dfd2eaSJanusz Dziedzic * In case NL80211_RRF_AUTO_BW requested for both rules 1379b0dfd2eaSJanusz Dziedzic * set AUTO_BW in intersected rule also. Next we will 1380b0dfd2eaSJanusz Dziedzic * calculate BW correctly in handle_channel function. 1381b0dfd2eaSJanusz Dziedzic * In other case remove AUTO_BW flag while we calculate 1382b0dfd2eaSJanusz Dziedzic * maximum bandwidth correctly and auto calculation is 1383b0dfd2eaSJanusz Dziedzic * not required. 1384b0dfd2eaSJanusz Dziedzic */ 1385b0dfd2eaSJanusz Dziedzic if ((rule1->flags & NL80211_RRF_AUTO_BW) && 1386b0dfd2eaSJanusz Dziedzic (rule2->flags & NL80211_RRF_AUTO_BW)) 1387b0dfd2eaSJanusz Dziedzic intersected_rule->flags |= NL80211_RRF_AUTO_BW; 1388b0dfd2eaSJanusz Dziedzic else 1389b0dfd2eaSJanusz Dziedzic intersected_rule->flags &= ~NL80211_RRF_AUTO_BW; 1390b0dfd2eaSJanusz Dziedzic 13919c96477dSLuis R. Rodriguez freq_diff = freq_range->end_freq_khz - freq_range->start_freq_khz; 13929c96477dSLuis R. Rodriguez if (freq_range->max_bandwidth_khz > freq_diff) 13939c96477dSLuis R. Rodriguez freq_range->max_bandwidth_khz = freq_diff; 13949c96477dSLuis R. Rodriguez 13959c96477dSLuis R. Rodriguez power_rule->max_eirp = min(power_rule1->max_eirp, 13969c96477dSLuis R. Rodriguez power_rule2->max_eirp); 13979c96477dSLuis R. Rodriguez power_rule->max_antenna_gain = min(power_rule1->max_antenna_gain, 13989c96477dSLuis R. Rodriguez power_rule2->max_antenna_gain); 13999c96477dSLuis R. Rodriguez 1400089027e5SJanusz Dziedzic intersected_rule->dfs_cac_ms = max(rule1->dfs_cac_ms, 1401089027e5SJanusz Dziedzic rule2->dfs_cac_ms); 1402089027e5SJanusz Dziedzic 140308a75a88SIlan Peer if (rule1->has_wmm && rule2->has_wmm) { 140408a75a88SIlan Peer u8 ac; 140508a75a88SIlan Peer 140608a75a88SIlan Peer for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) { 140708a75a88SIlan Peer reg_wmm_rules_intersect(&wmm_rule1->client[ac], 140808a75a88SIlan Peer &wmm_rule2->client[ac], 140908a75a88SIlan Peer &wmm_rule->client[ac]); 141008a75a88SIlan Peer reg_wmm_rules_intersect(&wmm_rule1->ap[ac], 141108a75a88SIlan Peer &wmm_rule2->ap[ac], 141208a75a88SIlan Peer &wmm_rule->ap[ac]); 141308a75a88SIlan Peer } 141408a75a88SIlan Peer 141508a75a88SIlan Peer intersected_rule->has_wmm = true; 141608a75a88SIlan Peer } else if (rule1->has_wmm) { 141708a75a88SIlan Peer *wmm_rule = *wmm_rule1; 141808a75a88SIlan Peer intersected_rule->has_wmm = true; 141908a75a88SIlan Peer } else if (rule2->has_wmm) { 142008a75a88SIlan Peer *wmm_rule = *wmm_rule2; 142108a75a88SIlan Peer intersected_rule->has_wmm = true; 142208a75a88SIlan Peer } else { 142308a75a88SIlan Peer intersected_rule->has_wmm = false; 142408a75a88SIlan Peer } 142508a75a88SIlan Peer 14269c96477dSLuis R. Rodriguez if (!is_valid_reg_rule(intersected_rule)) 14279c96477dSLuis R. Rodriguez return -EINVAL; 14289c96477dSLuis R. Rodriguez 14299c96477dSLuis R. Rodriguez return 0; 14309c96477dSLuis R. Rodriguez } 14319c96477dSLuis R. Rodriguez 1432a62a1aedSEliad Peller /* check whether old rule contains new rule */ 1433a62a1aedSEliad Peller static bool rule_contains(struct ieee80211_reg_rule *r1, 1434a62a1aedSEliad Peller struct ieee80211_reg_rule *r2) 1435a62a1aedSEliad Peller { 1436a62a1aedSEliad Peller /* for simplicity, currently consider only same flags */ 1437a62a1aedSEliad Peller if (r1->flags != r2->flags) 1438a62a1aedSEliad Peller return false; 1439a62a1aedSEliad Peller 1440a62a1aedSEliad Peller /* verify r1 is more restrictive */ 1441a62a1aedSEliad Peller if ((r1->power_rule.max_antenna_gain > 1442a62a1aedSEliad Peller r2->power_rule.max_antenna_gain) || 1443a62a1aedSEliad Peller r1->power_rule.max_eirp > r2->power_rule.max_eirp) 1444a62a1aedSEliad Peller return false; 1445a62a1aedSEliad Peller 1446a62a1aedSEliad Peller /* make sure r2's range is contained within r1 */ 1447a62a1aedSEliad Peller if (r1->freq_range.start_freq_khz > r2->freq_range.start_freq_khz || 1448a62a1aedSEliad Peller r1->freq_range.end_freq_khz < r2->freq_range.end_freq_khz) 1449a62a1aedSEliad Peller return false; 1450a62a1aedSEliad Peller 1451a62a1aedSEliad Peller /* and finally verify that r1.max_bw >= r2.max_bw */ 1452a62a1aedSEliad Peller if (r1->freq_range.max_bandwidth_khz < 1453a62a1aedSEliad Peller r2->freq_range.max_bandwidth_khz) 1454a62a1aedSEliad Peller return false; 1455a62a1aedSEliad Peller 1456a62a1aedSEliad Peller return true; 1457a62a1aedSEliad Peller } 1458a62a1aedSEliad Peller 1459a62a1aedSEliad Peller /* add or extend current rules. do nothing if rule is already contained */ 1460a62a1aedSEliad Peller static void add_rule(struct ieee80211_reg_rule *rule, 1461a62a1aedSEliad Peller struct ieee80211_reg_rule *reg_rules, u32 *n_rules) 1462a62a1aedSEliad Peller { 1463a62a1aedSEliad Peller struct ieee80211_reg_rule *tmp_rule; 1464a62a1aedSEliad Peller int i; 1465a62a1aedSEliad Peller 1466a62a1aedSEliad Peller for (i = 0; i < *n_rules; i++) { 1467a62a1aedSEliad Peller tmp_rule = ®_rules[i]; 1468a62a1aedSEliad Peller /* rule is already contained - do nothing */ 1469a62a1aedSEliad Peller if (rule_contains(tmp_rule, rule)) 1470a62a1aedSEliad Peller return; 1471a62a1aedSEliad Peller 1472a62a1aedSEliad Peller /* extend rule if possible */ 1473a62a1aedSEliad Peller if (rule_contains(rule, tmp_rule)) { 1474a62a1aedSEliad Peller memcpy(tmp_rule, rule, sizeof(*rule)); 1475a62a1aedSEliad Peller return; 1476a62a1aedSEliad Peller } 1477a62a1aedSEliad Peller } 1478a62a1aedSEliad Peller 1479a62a1aedSEliad Peller memcpy(®_rules[*n_rules], rule, sizeof(*rule)); 1480a62a1aedSEliad Peller (*n_rules)++; 1481a62a1aedSEliad Peller } 1482a62a1aedSEliad Peller 14839c96477dSLuis R. Rodriguez /** 14849c96477dSLuis R. Rodriguez * regdom_intersect - do the intersection between two regulatory domains 14859c96477dSLuis R. Rodriguez * @rd1: first regulatory domain 14869c96477dSLuis R. Rodriguez * @rd2: second regulatory domain 14879c96477dSLuis R. Rodriguez * 14889c96477dSLuis R. Rodriguez * Use this function to get the intersection between two regulatory domains. 14899c96477dSLuis R. Rodriguez * Once completed we will mark the alpha2 for the rd as intersected, "98", 14909c96477dSLuis R. Rodriguez * as no one single alpha2 can represent this regulatory domain. 14919c96477dSLuis R. Rodriguez * 14929c96477dSLuis R. Rodriguez * Returns a pointer to the regulatory domain structure which will hold the 14939c96477dSLuis R. Rodriguez * resulting intersection of rules between rd1 and rd2. We will 14949c96477dSLuis R. Rodriguez * kzalloc() this structure for you. 14959c96477dSLuis R. Rodriguez */ 14961a919318SJohannes Berg static struct ieee80211_regdomain * 14971a919318SJohannes Berg regdom_intersect(const struct ieee80211_regdomain *rd1, 14989c96477dSLuis R. Rodriguez const struct ieee80211_regdomain *rd2) 14999c96477dSLuis R. Rodriguez { 15009f8c7136SGustavo A. R. Silva int r; 15019c96477dSLuis R. Rodriguez unsigned int x, y; 1502a62a1aedSEliad Peller unsigned int num_rules = 0; 15039c96477dSLuis R. Rodriguez const struct ieee80211_reg_rule *rule1, *rule2; 1504a62a1aedSEliad Peller struct ieee80211_reg_rule intersected_rule; 15059c96477dSLuis R. Rodriguez struct ieee80211_regdomain *rd; 15069c96477dSLuis R. Rodriguez 15079c96477dSLuis R. Rodriguez if (!rd1 || !rd2) 15089c96477dSLuis R. Rodriguez return NULL; 15099c96477dSLuis R. Rodriguez 1510fb1fc7adSLuis R. Rodriguez /* 1511fb1fc7adSLuis R. Rodriguez * First we get a count of the rules we'll need, then we actually 15129c96477dSLuis R. Rodriguez * build them. This is to so we can malloc() and free() a 15139c96477dSLuis R. Rodriguez * regdomain once. The reason we use reg_rules_intersect() here 15149c96477dSLuis R. Rodriguez * is it will return -EINVAL if the rule computed makes no sense. 1515fb1fc7adSLuis R. Rodriguez * All rules that do check out OK are valid. 1516fb1fc7adSLuis R. Rodriguez */ 15179c96477dSLuis R. Rodriguez 15189c96477dSLuis R. Rodriguez for (x = 0; x < rd1->n_reg_rules; x++) { 15199c96477dSLuis R. Rodriguez rule1 = &rd1->reg_rules[x]; 15209c96477dSLuis R. Rodriguez for (y = 0; y < rd2->n_reg_rules; y++) { 15219c96477dSLuis R. Rodriguez rule2 = &rd2->reg_rules[y]; 152297524820SJanusz Dziedzic if (!reg_rules_intersect(rd1, rd2, rule1, rule2, 1523a62a1aedSEliad Peller &intersected_rule)) 15249c96477dSLuis R. Rodriguez num_rules++; 15259c96477dSLuis R. Rodriguez } 15269c96477dSLuis R. Rodriguez } 15279c96477dSLuis R. Rodriguez 15289c96477dSLuis R. Rodriguez if (!num_rules) 15299c96477dSLuis R. Rodriguez return NULL; 15309c96477dSLuis R. Rodriguez 15319f8c7136SGustavo A. R. Silva rd = kzalloc(struct_size(rd, reg_rules, num_rules), GFP_KERNEL); 15329c96477dSLuis R. Rodriguez if (!rd) 15339c96477dSLuis R. Rodriguez return NULL; 15349c96477dSLuis R. Rodriguez 1535a62a1aedSEliad Peller for (x = 0; x < rd1->n_reg_rules; x++) { 15369c96477dSLuis R. Rodriguez rule1 = &rd1->reg_rules[x]; 1537a62a1aedSEliad Peller for (y = 0; y < rd2->n_reg_rules; y++) { 15389c96477dSLuis R. Rodriguez rule2 = &rd2->reg_rules[y]; 153997524820SJanusz Dziedzic r = reg_rules_intersect(rd1, rd2, rule1, rule2, 1540a62a1aedSEliad Peller &intersected_rule); 1541fb1fc7adSLuis R. Rodriguez /* 1542fb1fc7adSLuis R. Rodriguez * No need to memset here the intersected rule here as 1543fb1fc7adSLuis R. Rodriguez * we're not using the stack anymore 1544fb1fc7adSLuis R. Rodriguez */ 15459c96477dSLuis R. Rodriguez if (r) 15469c96477dSLuis R. Rodriguez continue; 1547a62a1aedSEliad Peller 1548a62a1aedSEliad Peller add_rule(&intersected_rule, rd->reg_rules, 1549a62a1aedSEliad Peller &rd->n_reg_rules); 15509c96477dSLuis R. Rodriguez } 15519c96477dSLuis R. Rodriguez } 15529c96477dSLuis R. Rodriguez 15539c96477dSLuis R. Rodriguez rd->alpha2[0] = '9'; 15549c96477dSLuis R. Rodriguez rd->alpha2[1] = '8'; 1555adbfb058SLuis R. Rodriguez rd->dfs_region = reg_intersect_dfs_region(rd1->dfs_region, 1556adbfb058SLuis R. Rodriguez rd2->dfs_region); 15579c96477dSLuis R. Rodriguez 15589c96477dSLuis R. Rodriguez return rd; 15599c96477dSLuis R. Rodriguez } 15609c96477dSLuis R. Rodriguez 1561fb1fc7adSLuis R. Rodriguez /* 1562fb1fc7adSLuis R. Rodriguez * XXX: add support for the rest of enum nl80211_reg_rule_flags, we may 1563fb1fc7adSLuis R. Rodriguez * want to just have the channel structure use these 1564fb1fc7adSLuis R. Rodriguez */ 1565b2e1b302SLuis R. Rodriguez static u32 map_regdom_flags(u32 rd_flags) 1566b2e1b302SLuis R. Rodriguez { 1567b2e1b302SLuis R. Rodriguez u32 channel_flags = 0; 15688fe02e16SLuis R. Rodriguez if (rd_flags & NL80211_RRF_NO_IR_ALL) 15698fe02e16SLuis R. Rodriguez channel_flags |= IEEE80211_CHAN_NO_IR; 1570b2e1b302SLuis R. Rodriguez if (rd_flags & NL80211_RRF_DFS) 1571b2e1b302SLuis R. Rodriguez channel_flags |= IEEE80211_CHAN_RADAR; 157203f6b084SSeth Forshee if (rd_flags & NL80211_RRF_NO_OFDM) 157303f6b084SSeth Forshee channel_flags |= IEEE80211_CHAN_NO_OFDM; 1574570dbde1SDavid Spinadel if (rd_flags & NL80211_RRF_NO_OUTDOOR) 1575570dbde1SDavid Spinadel channel_flags |= IEEE80211_CHAN_INDOOR_ONLY; 157606f207fcSArik Nemtsov if (rd_flags & NL80211_RRF_IR_CONCURRENT) 157706f207fcSArik Nemtsov channel_flags |= IEEE80211_CHAN_IR_CONCURRENT; 1578a6d4a534SArik Nemtsov if (rd_flags & NL80211_RRF_NO_HT40MINUS) 1579a6d4a534SArik Nemtsov channel_flags |= IEEE80211_CHAN_NO_HT40MINUS; 1580a6d4a534SArik Nemtsov if (rd_flags & NL80211_RRF_NO_HT40PLUS) 1581a6d4a534SArik Nemtsov channel_flags |= IEEE80211_CHAN_NO_HT40PLUS; 1582a6d4a534SArik Nemtsov if (rd_flags & NL80211_RRF_NO_80MHZ) 1583a6d4a534SArik Nemtsov channel_flags |= IEEE80211_CHAN_NO_80MHZ; 1584a6d4a534SArik Nemtsov if (rd_flags & NL80211_RRF_NO_160MHZ) 1585a6d4a534SArik Nemtsov channel_flags |= IEEE80211_CHAN_NO_160MHZ; 15861e61d82cSHaim Dreyfuss if (rd_flags & NL80211_RRF_NO_HE) 15871e61d82cSHaim Dreyfuss channel_flags |= IEEE80211_CHAN_NO_HE; 1588c2b3d769SSriram R if (rd_flags & NL80211_RRF_NO_320MHZ) 1589c2b3d769SSriram R channel_flags |= IEEE80211_CHAN_NO_320MHZ; 1590b2e1b302SLuis R. Rodriguez return channel_flags; 1591b2e1b302SLuis R. Rodriguez } 1592b2e1b302SLuis R. Rodriguez 1593361c9c8bSJohannes Berg static const struct ieee80211_reg_rule * 159449172874SMichal Sojka freq_reg_info_regd(u32 center_freq, 15954edd5698SMatthias May const struct ieee80211_regdomain *regd, u32 bw) 15968318d78aSJohannes Berg { 15978318d78aSJohannes Berg int i; 15980c7dc45dSLuis R. Rodriguez bool band_rule_found = false; 1599038659e7SLuis R. Rodriguez bool bw_fits = false; 1600038659e7SLuis R. Rodriguez 16013e0c3ff3SLuis R. Rodriguez if (!regd) 1602361c9c8bSJohannes Berg return ERR_PTR(-EINVAL); 1603b2e1b302SLuis R. Rodriguez 16043e0c3ff3SLuis R. Rodriguez for (i = 0; i < regd->n_reg_rules; i++) { 1605b2e1b302SLuis R. Rodriguez const struct ieee80211_reg_rule *rr; 1606b2e1b302SLuis R. Rodriguez const struct ieee80211_freq_range *fr = NULL; 1607b2e1b302SLuis R. Rodriguez 16083e0c3ff3SLuis R. Rodriguez rr = ®d->reg_rules[i]; 1609b2e1b302SLuis R. Rodriguez fr = &rr->freq_range; 16100c7dc45dSLuis R. Rodriguez 1611fb1fc7adSLuis R. Rodriguez /* 1612fb1fc7adSLuis R. Rodriguez * We only need to know if one frequency rule was 1613cc5a639bSRandy Dunlap * in center_freq's band, that's enough, so let's 1614fb1fc7adSLuis R. Rodriguez * not overwrite it once found 1615fb1fc7adSLuis R. Rodriguez */ 16160c7dc45dSLuis R. Rodriguez if (!band_rule_found) 16170c7dc45dSLuis R. Rodriguez band_rule_found = freq_in_rule_band(fr, center_freq); 16180c7dc45dSLuis R. Rodriguez 16194787cfa0SRafał Miłecki bw_fits = cfg80211_does_bw_fit_range(fr, center_freq, bw); 16200c7dc45dSLuis R. Rodriguez 1621361c9c8bSJohannes Berg if (band_rule_found && bw_fits) 1622361c9c8bSJohannes Berg return rr; 16238318d78aSJohannes Berg } 16248318d78aSJohannes Berg 16250c7dc45dSLuis R. Rodriguez if (!band_rule_found) 1626361c9c8bSJohannes Berg return ERR_PTR(-ERANGE); 16270c7dc45dSLuis R. Rodriguez 1628361c9c8bSJohannes Berg return ERR_PTR(-EINVAL); 1629b2e1b302SLuis R. Rodriguez } 1630b2e1b302SLuis R. Rodriguez 16318de1c63bSJohannes Berg static const struct ieee80211_reg_rule * 16328de1c63bSJohannes Berg __freq_reg_info(struct wiphy *wiphy, u32 center_freq, u32 min_bw) 16334edd5698SMatthias May { 16344edd5698SMatthias May const struct ieee80211_regdomain *regd = reg_get_regdomain(wiphy); 1635c7ed0e68SColin Ian King static const u32 bws[] = {0, 1, 2, 4, 5, 8, 10, 16, 20}; 16369e6d5126SLuca Coelho const struct ieee80211_reg_rule *reg_rule = ERR_PTR(-ERANGE); 163768dbad8cSThomas Pedersen int i = ARRAY_SIZE(bws) - 1; 16384edd5698SMatthias May u32 bw; 16394edd5698SMatthias May 164068dbad8cSThomas Pedersen for (bw = MHZ_TO_KHZ(bws[i]); bw >= min_bw; bw = MHZ_TO_KHZ(bws[i--])) { 164149172874SMichal Sojka reg_rule = freq_reg_info_regd(center_freq, regd, bw); 16424edd5698SMatthias May if (!IS_ERR(reg_rule)) 16434edd5698SMatthias May return reg_rule; 16444edd5698SMatthias May } 16454edd5698SMatthias May 16464edd5698SMatthias May return reg_rule; 16474edd5698SMatthias May } 16484edd5698SMatthias May 1649361c9c8bSJohannes Berg const struct ieee80211_reg_rule *freq_reg_info(struct wiphy *wiphy, 1650361c9c8bSJohannes Berg u32 center_freq) 16511fa25e41SLuis R. Rodriguez { 165268dbad8cSThomas Pedersen u32 min_bw = center_freq < MHZ_TO_KHZ(1000) ? 1 : 20; 165368dbad8cSThomas Pedersen 165468dbad8cSThomas Pedersen return __freq_reg_info(wiphy, center_freq, MHZ_TO_KHZ(min_bw)); 16551fa25e41SLuis R. Rodriguez } 16564f366c5dSJohn W. Linville EXPORT_SYMBOL(freq_reg_info); 1657b2e1b302SLuis R. Rodriguez 1658034c6d6eSLuis R. Rodriguez const char *reg_initiator_name(enum nl80211_reg_initiator initiator) 1659926a0a09SLuis R. Rodriguez { 1660926a0a09SLuis R. Rodriguez switch (initiator) { 1661926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_CORE: 1662034c6d6eSLuis R. Rodriguez return "core"; 1663926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_USER: 1664034c6d6eSLuis R. Rodriguez return "user"; 1665926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_DRIVER: 1666034c6d6eSLuis R. Rodriguez return "driver"; 1667926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_COUNTRY_IE: 16688db0c433SToke Høiland-Jørgensen return "country element"; 1669926a0a09SLuis R. Rodriguez default: 1670926a0a09SLuis R. Rodriguez WARN_ON(1); 1671034c6d6eSLuis R. Rodriguez return "bug"; 1672926a0a09SLuis R. Rodriguez } 1673926a0a09SLuis R. Rodriguez } 1674034c6d6eSLuis R. Rodriguez EXPORT_SYMBOL(reg_initiator_name); 1675e702d3cfSLuis R. Rodriguez 16761aeb135fSMichal Sojka static uint32_t reg_rule_to_chan_bw_flags(const struct ieee80211_regdomain *regd, 16771aeb135fSMichal Sojka const struct ieee80211_reg_rule *reg_rule, 16781aeb135fSMichal Sojka const struct ieee80211_channel *chan) 16791aeb135fSMichal Sojka { 16801aeb135fSMichal Sojka const struct ieee80211_freq_range *freq_range = NULL; 1681934f4c7dSThomas Pedersen u32 max_bandwidth_khz, center_freq_khz, bw_flags = 0; 168268dbad8cSThomas Pedersen bool is_s1g = chan->band == NL80211_BAND_S1GHZ; 16831aeb135fSMichal Sojka 16841aeb135fSMichal Sojka freq_range = ®_rule->freq_range; 16851aeb135fSMichal Sojka 16861aeb135fSMichal Sojka max_bandwidth_khz = freq_range->max_bandwidth_khz; 1687934f4c7dSThomas Pedersen center_freq_khz = ieee80211_channel_to_khz(chan); 16881aeb135fSMichal Sojka /* Check if auto calculation requested */ 16891aeb135fSMichal Sojka if (reg_rule->flags & NL80211_RRF_AUTO_BW) 16901aeb135fSMichal Sojka max_bandwidth_khz = reg_get_max_bandwidth(regd, reg_rule); 16911aeb135fSMichal Sojka 16921aeb135fSMichal Sojka /* If we get a reg_rule we can assume that at least 5Mhz fit */ 16934787cfa0SRafał Miłecki if (!cfg80211_does_bw_fit_range(freq_range, 1694934f4c7dSThomas Pedersen center_freq_khz, 16951aeb135fSMichal Sojka MHZ_TO_KHZ(10))) 16961aeb135fSMichal Sojka bw_flags |= IEEE80211_CHAN_NO_10MHZ; 16974787cfa0SRafał Miłecki if (!cfg80211_does_bw_fit_range(freq_range, 1698934f4c7dSThomas Pedersen center_freq_khz, 16991aeb135fSMichal Sojka MHZ_TO_KHZ(20))) 17001aeb135fSMichal Sojka bw_flags |= IEEE80211_CHAN_NO_20MHZ; 17011aeb135fSMichal Sojka 170268dbad8cSThomas Pedersen if (is_s1g) { 170368dbad8cSThomas Pedersen /* S1G is strict about non overlapping channels. We can 170468dbad8cSThomas Pedersen * calculate which bandwidth is allowed per channel by finding 170568dbad8cSThomas Pedersen * the largest bandwidth which cleanly divides the freq_range. 170668dbad8cSThomas Pedersen */ 170768dbad8cSThomas Pedersen int edge_offset; 170868dbad8cSThomas Pedersen int ch_bw = max_bandwidth_khz; 170968dbad8cSThomas Pedersen 171068dbad8cSThomas Pedersen while (ch_bw) { 171168dbad8cSThomas Pedersen edge_offset = (center_freq_khz - ch_bw / 2) - 171268dbad8cSThomas Pedersen freq_range->start_freq_khz; 171368dbad8cSThomas Pedersen if (edge_offset % ch_bw == 0) { 171468dbad8cSThomas Pedersen switch (KHZ_TO_MHZ(ch_bw)) { 171568dbad8cSThomas Pedersen case 1: 171668dbad8cSThomas Pedersen bw_flags |= IEEE80211_CHAN_1MHZ; 171768dbad8cSThomas Pedersen break; 171868dbad8cSThomas Pedersen case 2: 171968dbad8cSThomas Pedersen bw_flags |= IEEE80211_CHAN_2MHZ; 172068dbad8cSThomas Pedersen break; 172168dbad8cSThomas Pedersen case 4: 172268dbad8cSThomas Pedersen bw_flags |= IEEE80211_CHAN_4MHZ; 172368dbad8cSThomas Pedersen break; 172468dbad8cSThomas Pedersen case 8: 172568dbad8cSThomas Pedersen bw_flags |= IEEE80211_CHAN_8MHZ; 172668dbad8cSThomas Pedersen break; 172768dbad8cSThomas Pedersen case 16: 172868dbad8cSThomas Pedersen bw_flags |= IEEE80211_CHAN_16MHZ; 172968dbad8cSThomas Pedersen break; 173068dbad8cSThomas Pedersen default: 173168dbad8cSThomas Pedersen /* If we got here, no bandwidths fit on 173268dbad8cSThomas Pedersen * this frequency, ie. band edge. 173368dbad8cSThomas Pedersen */ 173468dbad8cSThomas Pedersen bw_flags |= IEEE80211_CHAN_DISABLED; 173568dbad8cSThomas Pedersen break; 173668dbad8cSThomas Pedersen } 173768dbad8cSThomas Pedersen break; 173868dbad8cSThomas Pedersen } 173968dbad8cSThomas Pedersen ch_bw /= 2; 174068dbad8cSThomas Pedersen } 174168dbad8cSThomas Pedersen } else { 17421aeb135fSMichal Sojka if (max_bandwidth_khz < MHZ_TO_KHZ(10)) 17431aeb135fSMichal Sojka bw_flags |= IEEE80211_CHAN_NO_10MHZ; 17441aeb135fSMichal Sojka if (max_bandwidth_khz < MHZ_TO_KHZ(20)) 17451aeb135fSMichal Sojka bw_flags |= IEEE80211_CHAN_NO_20MHZ; 17461aeb135fSMichal Sojka if (max_bandwidth_khz < MHZ_TO_KHZ(40)) 17471aeb135fSMichal Sojka bw_flags |= IEEE80211_CHAN_NO_HT40; 17481aeb135fSMichal Sojka if (max_bandwidth_khz < MHZ_TO_KHZ(80)) 17491aeb135fSMichal Sojka bw_flags |= IEEE80211_CHAN_NO_80MHZ; 17501aeb135fSMichal Sojka if (max_bandwidth_khz < MHZ_TO_KHZ(160)) 17511aeb135fSMichal Sojka bw_flags |= IEEE80211_CHAN_NO_160MHZ; 1752c2b3d769SSriram R if (max_bandwidth_khz < MHZ_TO_KHZ(320)) 1753c2b3d769SSriram R bw_flags |= IEEE80211_CHAN_NO_320MHZ; 175468dbad8cSThomas Pedersen } 17551aeb135fSMichal Sojka return bw_flags; 17561aeb135fSMichal Sojka } 17571aeb135fSMichal Sojka 17587c9ff7e2SMarkus Theil static void handle_channel_single_rule(struct wiphy *wiphy, 17597ca43d03SLuis R. Rodriguez enum nl80211_reg_initiator initiator, 17607c9ff7e2SMarkus Theil struct ieee80211_channel *chan, 17617c9ff7e2SMarkus Theil u32 flags, 17627c9ff7e2SMarkus Theil struct regulatory_request *lr, 17637c9ff7e2SMarkus Theil struct wiphy *request_wiphy, 17647c9ff7e2SMarkus Theil const struct ieee80211_reg_rule *reg_rule) 1765b2e1b302SLuis R. Rodriguez { 17667c9ff7e2SMarkus Theil u32 bw_flags = 0; 1767b2e1b302SLuis R. Rodriguez const struct ieee80211_power_rule *power_rule = NULL; 176897524820SJanusz Dziedzic const struct ieee80211_regdomain *regd; 1769a92a3ce7SLuis R. Rodriguez 1770b0dfd2eaSJanusz Dziedzic regd = reg_get_regdomain(wiphy); 1771e702d3cfSLuis R. Rodriguez 1772b2e1b302SLuis R. Rodriguez power_rule = ®_rule->power_rule; 17731aeb135fSMichal Sojka bw_flags = reg_rule_to_chan_bw_flags(regd, reg_rule, chan); 1774b2e1b302SLuis R. Rodriguez 1775c492db37SJohannes Berg if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER && 1776806a9e39SLuis R. Rodriguez request_wiphy && request_wiphy == wiphy && 1777a2f73b6cSLuis R. Rodriguez request_wiphy->regulatory_flags & REGULATORY_STRICT_REG) { 1778fb1fc7adSLuis R. Rodriguez /* 177925985edcSLucas De Marchi * This guarantees the driver's requested regulatory domain 1780f976376dSLuis R. Rodriguez * will always be used as a base for further regulatory 1781fb1fc7adSLuis R. Rodriguez * settings 1782fb1fc7adSLuis R. Rodriguez */ 1783f976376dSLuis R. Rodriguez chan->flags = chan->orig_flags = 1784038659e7SLuis R. Rodriguez map_regdom_flags(reg_rule->flags) | bw_flags; 1785f976376dSLuis R. Rodriguez chan->max_antenna_gain = chan->orig_mag = 1786f976376dSLuis R. Rodriguez (int) MBI_TO_DBI(power_rule->max_antenna_gain); 1787279f0f55SFelix Fietkau chan->max_reg_power = chan->max_power = chan->orig_mpwr = 1788f976376dSLuis R. Rodriguez (int) MBM_TO_DBM(power_rule->max_eirp); 17894f267c11SJanusz Dziedzic 17904f267c11SJanusz Dziedzic if (chan->flags & IEEE80211_CHAN_RADAR) { 17914f267c11SJanusz Dziedzic chan->dfs_cac_ms = IEEE80211_DFS_MIN_CAC_TIME_MS; 17924f267c11SJanusz Dziedzic if (reg_rule->dfs_cac_ms) 17934f267c11SJanusz Dziedzic chan->dfs_cac_ms = reg_rule->dfs_cac_ms; 17944f267c11SJanusz Dziedzic } 17954f267c11SJanusz Dziedzic 1796f976376dSLuis R. Rodriguez return; 1797f976376dSLuis R. Rodriguez } 1798f976376dSLuis R. Rodriguez 179904f39047SSimon Wunderlich chan->dfs_state = NL80211_DFS_USABLE; 180004f39047SSimon Wunderlich chan->dfs_state_entered = jiffies; 180104f39047SSimon Wunderlich 1802aa3d7eefSRajkumar Manoharan chan->beacon_found = false; 1803038659e7SLuis R. Rodriguez chan->flags = flags | bw_flags | map_regdom_flags(reg_rule->flags); 18041a919318SJohannes Berg chan->max_antenna_gain = 18051a919318SJohannes Berg min_t(int, chan->orig_mag, 18061a919318SJohannes Berg MBI_TO_DBI(power_rule->max_antenna_gain)); 1807eccc068eSHong Wu chan->max_reg_power = (int) MBM_TO_DBM(power_rule->max_eirp); 1808089027e5SJanusz Dziedzic 1809089027e5SJanusz Dziedzic if (chan->flags & IEEE80211_CHAN_RADAR) { 1810089027e5SJanusz Dziedzic if (reg_rule->dfs_cac_ms) 1811089027e5SJanusz Dziedzic chan->dfs_cac_ms = reg_rule->dfs_cac_ms; 1812089027e5SJanusz Dziedzic else 1813089027e5SJanusz Dziedzic chan->dfs_cac_ms = IEEE80211_DFS_MIN_CAC_TIME_MS; 1814089027e5SJanusz Dziedzic } 1815089027e5SJanusz Dziedzic 18165e31fc08SStanislaw Gruszka if (chan->orig_mpwr) { 18175e31fc08SStanislaw Gruszka /* 1818a09a85a0SLuis R. Rodriguez * Devices that use REGULATORY_COUNTRY_IE_FOLLOW_POWER 1819a09a85a0SLuis R. Rodriguez * will always follow the passed country IE power settings. 18205e31fc08SStanislaw Gruszka */ 18215e31fc08SStanislaw Gruszka if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE && 1822a09a85a0SLuis R. Rodriguez wiphy->regulatory_flags & REGULATORY_COUNTRY_IE_FOLLOW_POWER) 18235e31fc08SStanislaw Gruszka chan->max_power = chan->max_reg_power; 18245e31fc08SStanislaw Gruszka else 18255e31fc08SStanislaw Gruszka chan->max_power = min(chan->orig_mpwr, 18265e31fc08SStanislaw Gruszka chan->max_reg_power); 18275e31fc08SStanislaw Gruszka } else 18285e31fc08SStanislaw Gruszka chan->max_power = chan->max_reg_power; 18298318d78aSJohannes Berg } 18308318d78aSJohannes Berg 183112adee3cSMarkus Theil static void handle_channel_adjacent_rules(struct wiphy *wiphy, 183212adee3cSMarkus Theil enum nl80211_reg_initiator initiator, 183312adee3cSMarkus Theil struct ieee80211_channel *chan, 183412adee3cSMarkus Theil u32 flags, 183512adee3cSMarkus Theil struct regulatory_request *lr, 183612adee3cSMarkus Theil struct wiphy *request_wiphy, 183712adee3cSMarkus Theil const struct ieee80211_reg_rule *rrule1, 183812adee3cSMarkus Theil const struct ieee80211_reg_rule *rrule2, 183912adee3cSMarkus Theil struct ieee80211_freq_range *comb_range) 184012adee3cSMarkus Theil { 184112adee3cSMarkus Theil u32 bw_flags1 = 0; 184212adee3cSMarkus Theil u32 bw_flags2 = 0; 184312adee3cSMarkus Theil const struct ieee80211_power_rule *power_rule1 = NULL; 184412adee3cSMarkus Theil const struct ieee80211_power_rule *power_rule2 = NULL; 184512adee3cSMarkus Theil const struct ieee80211_regdomain *regd; 184612adee3cSMarkus Theil 184712adee3cSMarkus Theil regd = reg_get_regdomain(wiphy); 184812adee3cSMarkus Theil 184912adee3cSMarkus Theil power_rule1 = &rrule1->power_rule; 185012adee3cSMarkus Theil power_rule2 = &rrule2->power_rule; 185112adee3cSMarkus Theil bw_flags1 = reg_rule_to_chan_bw_flags(regd, rrule1, chan); 185212adee3cSMarkus Theil bw_flags2 = reg_rule_to_chan_bw_flags(regd, rrule2, chan); 185312adee3cSMarkus Theil 185412adee3cSMarkus Theil if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER && 185512adee3cSMarkus Theil request_wiphy && request_wiphy == wiphy && 185612adee3cSMarkus Theil request_wiphy->regulatory_flags & REGULATORY_STRICT_REG) { 185712adee3cSMarkus Theil /* This guarantees the driver's requested regulatory domain 185812adee3cSMarkus Theil * will always be used as a base for further regulatory 185912adee3cSMarkus Theil * settings 186012adee3cSMarkus Theil */ 186112adee3cSMarkus Theil chan->flags = 186212adee3cSMarkus Theil map_regdom_flags(rrule1->flags) | 186312adee3cSMarkus Theil map_regdom_flags(rrule2->flags) | 186412adee3cSMarkus Theil bw_flags1 | 186512adee3cSMarkus Theil bw_flags2; 186612adee3cSMarkus Theil chan->orig_flags = chan->flags; 186712adee3cSMarkus Theil chan->max_antenna_gain = 186812adee3cSMarkus Theil min_t(int, MBI_TO_DBI(power_rule1->max_antenna_gain), 186912adee3cSMarkus Theil MBI_TO_DBI(power_rule2->max_antenna_gain)); 187012adee3cSMarkus Theil chan->orig_mag = chan->max_antenna_gain; 187112adee3cSMarkus Theil chan->max_reg_power = 187212adee3cSMarkus Theil min_t(int, MBM_TO_DBM(power_rule1->max_eirp), 187312adee3cSMarkus Theil MBM_TO_DBM(power_rule2->max_eirp)); 187412adee3cSMarkus Theil chan->max_power = chan->max_reg_power; 187512adee3cSMarkus Theil chan->orig_mpwr = chan->max_reg_power; 187612adee3cSMarkus Theil 187712adee3cSMarkus Theil if (chan->flags & IEEE80211_CHAN_RADAR) { 187812adee3cSMarkus Theil chan->dfs_cac_ms = IEEE80211_DFS_MIN_CAC_TIME_MS; 187912adee3cSMarkus Theil if (rrule1->dfs_cac_ms || rrule2->dfs_cac_ms) 188012adee3cSMarkus Theil chan->dfs_cac_ms = max_t(unsigned int, 188112adee3cSMarkus Theil rrule1->dfs_cac_ms, 188212adee3cSMarkus Theil rrule2->dfs_cac_ms); 188312adee3cSMarkus Theil } 188412adee3cSMarkus Theil 188512adee3cSMarkus Theil return; 188612adee3cSMarkus Theil } 188712adee3cSMarkus Theil 188812adee3cSMarkus Theil chan->dfs_state = NL80211_DFS_USABLE; 188912adee3cSMarkus Theil chan->dfs_state_entered = jiffies; 189012adee3cSMarkus Theil 189112adee3cSMarkus Theil chan->beacon_found = false; 189212adee3cSMarkus Theil chan->flags = flags | bw_flags1 | bw_flags2 | 189312adee3cSMarkus Theil map_regdom_flags(rrule1->flags) | 189412adee3cSMarkus Theil map_regdom_flags(rrule2->flags); 189512adee3cSMarkus Theil 189612adee3cSMarkus Theil /* reg_rule_to_chan_bw_flags may forbids 10 and forbids 20 MHz 189712adee3cSMarkus Theil * (otherwise no adj. rule case), recheck therefore 189812adee3cSMarkus Theil */ 189912adee3cSMarkus Theil if (cfg80211_does_bw_fit_range(comb_range, 190012adee3cSMarkus Theil ieee80211_channel_to_khz(chan), 190112adee3cSMarkus Theil MHZ_TO_KHZ(10))) 190212adee3cSMarkus Theil chan->flags &= ~IEEE80211_CHAN_NO_10MHZ; 190312adee3cSMarkus Theil if (cfg80211_does_bw_fit_range(comb_range, 190412adee3cSMarkus Theil ieee80211_channel_to_khz(chan), 190512adee3cSMarkus Theil MHZ_TO_KHZ(20))) 190612adee3cSMarkus Theil chan->flags &= ~IEEE80211_CHAN_NO_20MHZ; 190712adee3cSMarkus Theil 190812adee3cSMarkus Theil chan->max_antenna_gain = 190912adee3cSMarkus Theil min_t(int, chan->orig_mag, 191012adee3cSMarkus Theil min_t(int, 191112adee3cSMarkus Theil MBI_TO_DBI(power_rule1->max_antenna_gain), 191212adee3cSMarkus Theil MBI_TO_DBI(power_rule2->max_antenna_gain))); 191312adee3cSMarkus Theil chan->max_reg_power = min_t(int, 191412adee3cSMarkus Theil MBM_TO_DBM(power_rule1->max_eirp), 191512adee3cSMarkus Theil MBM_TO_DBM(power_rule2->max_eirp)); 191612adee3cSMarkus Theil 191712adee3cSMarkus Theil if (chan->flags & IEEE80211_CHAN_RADAR) { 191812adee3cSMarkus Theil if (rrule1->dfs_cac_ms || rrule2->dfs_cac_ms) 191912adee3cSMarkus Theil chan->dfs_cac_ms = max_t(unsigned int, 192012adee3cSMarkus Theil rrule1->dfs_cac_ms, 192112adee3cSMarkus Theil rrule2->dfs_cac_ms); 192212adee3cSMarkus Theil else 192312adee3cSMarkus Theil chan->dfs_cac_ms = IEEE80211_DFS_MIN_CAC_TIME_MS; 192412adee3cSMarkus Theil } 192512adee3cSMarkus Theil 192612adee3cSMarkus Theil if (chan->orig_mpwr) { 192712adee3cSMarkus Theil /* Devices that use REGULATORY_COUNTRY_IE_FOLLOW_POWER 192812adee3cSMarkus Theil * will always follow the passed country IE power settings. 192912adee3cSMarkus Theil */ 193012adee3cSMarkus Theil if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE && 193112adee3cSMarkus Theil wiphy->regulatory_flags & REGULATORY_COUNTRY_IE_FOLLOW_POWER) 193212adee3cSMarkus Theil chan->max_power = chan->max_reg_power; 193312adee3cSMarkus Theil else 193412adee3cSMarkus Theil chan->max_power = min(chan->orig_mpwr, 193512adee3cSMarkus Theil chan->max_reg_power); 193612adee3cSMarkus Theil } else { 193712adee3cSMarkus Theil chan->max_power = chan->max_reg_power; 193812adee3cSMarkus Theil } 193912adee3cSMarkus Theil } 194012adee3cSMarkus Theil 19417c9ff7e2SMarkus Theil /* Note that right now we assume the desired channel bandwidth 19427c9ff7e2SMarkus Theil * is always 20 MHz for each individual channel (HT40 uses 20 MHz 19437c9ff7e2SMarkus Theil * per channel, the primary and the extension channel). 19447c9ff7e2SMarkus Theil */ 19457c9ff7e2SMarkus Theil static void handle_channel(struct wiphy *wiphy, 19467c9ff7e2SMarkus Theil enum nl80211_reg_initiator initiator, 19477c9ff7e2SMarkus Theil struct ieee80211_channel *chan) 19487c9ff7e2SMarkus Theil { 194912adee3cSMarkus Theil const u32 orig_chan_freq = ieee80211_channel_to_khz(chan); 19507c9ff7e2SMarkus Theil struct regulatory_request *lr = get_last_request(); 195112adee3cSMarkus Theil struct wiphy *request_wiphy = wiphy_idx_to_wiphy(lr->wiphy_idx); 195212adee3cSMarkus Theil const struct ieee80211_reg_rule *rrule = NULL; 195312adee3cSMarkus Theil const struct ieee80211_reg_rule *rrule1 = NULL; 195412adee3cSMarkus Theil const struct ieee80211_reg_rule *rrule2 = NULL; 19557c9ff7e2SMarkus Theil 195612adee3cSMarkus Theil u32 flags = chan->orig_flags; 19577c9ff7e2SMarkus Theil 195812adee3cSMarkus Theil rrule = freq_reg_info(wiphy, orig_chan_freq); 195912adee3cSMarkus Theil if (IS_ERR(rrule)) { 196012adee3cSMarkus Theil /* check for adjacent match, therefore get rules for 196112adee3cSMarkus Theil * chan - 20 MHz and chan + 20 MHz and test 196212adee3cSMarkus Theil * if reg rules are adjacent 196312adee3cSMarkus Theil */ 196412adee3cSMarkus Theil rrule1 = freq_reg_info(wiphy, 196512adee3cSMarkus Theil orig_chan_freq - MHZ_TO_KHZ(20)); 196612adee3cSMarkus Theil rrule2 = freq_reg_info(wiphy, 196712adee3cSMarkus Theil orig_chan_freq + MHZ_TO_KHZ(20)); 196812adee3cSMarkus Theil if (!IS_ERR(rrule1) && !IS_ERR(rrule2)) { 196912adee3cSMarkus Theil struct ieee80211_freq_range comb_range; 19707c9ff7e2SMarkus Theil 197112adee3cSMarkus Theil if (rrule1->freq_range.end_freq_khz != 197212adee3cSMarkus Theil rrule2->freq_range.start_freq_khz) 197312adee3cSMarkus Theil goto disable_chan; 197412adee3cSMarkus Theil 197512adee3cSMarkus Theil comb_range.start_freq_khz = 197612adee3cSMarkus Theil rrule1->freq_range.start_freq_khz; 197712adee3cSMarkus Theil comb_range.end_freq_khz = 197812adee3cSMarkus Theil rrule2->freq_range.end_freq_khz; 197912adee3cSMarkus Theil comb_range.max_bandwidth_khz = 198012adee3cSMarkus Theil min_t(u32, 198112adee3cSMarkus Theil rrule1->freq_range.max_bandwidth_khz, 198212adee3cSMarkus Theil rrule2->freq_range.max_bandwidth_khz); 198312adee3cSMarkus Theil 198412adee3cSMarkus Theil if (!cfg80211_does_bw_fit_range(&comb_range, 198512adee3cSMarkus Theil orig_chan_freq, 198612adee3cSMarkus Theil MHZ_TO_KHZ(20))) 198712adee3cSMarkus Theil goto disable_chan; 198812adee3cSMarkus Theil 198912adee3cSMarkus Theil handle_channel_adjacent_rules(wiphy, initiator, chan, 199012adee3cSMarkus Theil flags, lr, request_wiphy, 199112adee3cSMarkus Theil rrule1, rrule2, 199212adee3cSMarkus Theil &comb_range); 199312adee3cSMarkus Theil return; 199412adee3cSMarkus Theil } 199512adee3cSMarkus Theil 199612adee3cSMarkus Theil disable_chan: 19977c9ff7e2SMarkus Theil /* We will disable all channels that do not match our 19987c9ff7e2SMarkus Theil * received regulatory rule unless the hint is coming 19997c9ff7e2SMarkus Theil * from a Country IE and the Country IE had no information 20007c9ff7e2SMarkus Theil * about a band. The IEEE 802.11 spec allows for an AP 20017c9ff7e2SMarkus Theil * to send only a subset of the regulatory rules allowed, 20027c9ff7e2SMarkus Theil * so an AP in the US that only supports 2.4 GHz may only send 20037c9ff7e2SMarkus Theil * a country IE with information for the 2.4 GHz band 20047c9ff7e2SMarkus Theil * while 5 GHz is still supported. 20057c9ff7e2SMarkus Theil */ 20067c9ff7e2SMarkus Theil if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE && 200712adee3cSMarkus Theil PTR_ERR(rrule) == -ERANGE) 20087c9ff7e2SMarkus Theil return; 20097c9ff7e2SMarkus Theil 20107c9ff7e2SMarkus Theil if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER && 20117c9ff7e2SMarkus Theil request_wiphy && request_wiphy == wiphy && 20127c9ff7e2SMarkus Theil request_wiphy->regulatory_flags & REGULATORY_STRICT_REG) { 20137c9ff7e2SMarkus Theil pr_debug("Disabling freq %d.%03d MHz for good\n", 20147c9ff7e2SMarkus Theil chan->center_freq, chan->freq_offset); 20157c9ff7e2SMarkus Theil chan->orig_flags |= IEEE80211_CHAN_DISABLED; 20167c9ff7e2SMarkus Theil chan->flags = chan->orig_flags; 20177c9ff7e2SMarkus Theil } else { 20187c9ff7e2SMarkus Theil pr_debug("Disabling freq %d.%03d MHz\n", 20197c9ff7e2SMarkus Theil chan->center_freq, chan->freq_offset); 20207c9ff7e2SMarkus Theil chan->flags |= IEEE80211_CHAN_DISABLED; 20217c9ff7e2SMarkus Theil } 20227c9ff7e2SMarkus Theil return; 20237c9ff7e2SMarkus Theil } 20247c9ff7e2SMarkus Theil 20257c9ff7e2SMarkus Theil handle_channel_single_rule(wiphy, initiator, chan, flags, lr, 202612adee3cSMarkus Theil request_wiphy, rrule); 20277c9ff7e2SMarkus Theil } 20287c9ff7e2SMarkus Theil 20297ca43d03SLuis R. Rodriguez static void handle_band(struct wiphy *wiphy, 2030fdc9d7b2SJohannes Berg enum nl80211_reg_initiator initiator, 2031fdc9d7b2SJohannes Berg struct ieee80211_supported_band *sband) 20328318d78aSJohannes Berg { 2033a92a3ce7SLuis R. Rodriguez unsigned int i; 2034a92a3ce7SLuis R. Rodriguez 2035fdc9d7b2SJohannes Berg if (!sband) 2036fdc9d7b2SJohannes Berg return; 20378318d78aSJohannes Berg 20388318d78aSJohannes Berg for (i = 0; i < sband->n_channels; i++) 2039fdc9d7b2SJohannes Berg handle_channel(wiphy, initiator, &sband->channels[i]); 20408318d78aSJohannes Berg } 20418318d78aSJohannes Berg 204257b5ce07SLuis R. Rodriguez static bool reg_request_cell_base(struct regulatory_request *request) 204357b5ce07SLuis R. Rodriguez { 204457b5ce07SLuis R. Rodriguez if (request->initiator != NL80211_REGDOM_SET_BY_USER) 204557b5ce07SLuis R. Rodriguez return false; 20461a919318SJohannes Berg return request->user_reg_hint_type == NL80211_USER_REG_HINT_CELL_BASE; 204757b5ce07SLuis R. Rodriguez } 204857b5ce07SLuis R. Rodriguez 204957b5ce07SLuis R. Rodriguez bool reg_last_request_cell_base(void) 205057b5ce07SLuis R. Rodriguez { 205138fd2143SJohannes Berg return reg_request_cell_base(get_last_request()); 205257b5ce07SLuis R. Rodriguez } 205357b5ce07SLuis R. Rodriguez 205494fc661fSIlan Peer #ifdef CONFIG_CFG80211_REG_CELLULAR_HINTS 205557b5ce07SLuis R. Rodriguez /* Core specific check */ 20562f92212bSJohannes Berg static enum reg_request_treatment 20572f92212bSJohannes Berg reg_ignore_cell_hint(struct regulatory_request *pending_request) 205857b5ce07SLuis R. Rodriguez { 2059c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 206057b5ce07SLuis R. Rodriguez 206157b5ce07SLuis R. Rodriguez if (!reg_num_devs_support_basehint) 20622f92212bSJohannes Berg return REG_REQ_IGNORE; 206357b5ce07SLuis R. Rodriguez 2064c492db37SJohannes Berg if (reg_request_cell_base(lr) && 20651a919318SJohannes Berg !regdom_changes(pending_request->alpha2)) 20662f92212bSJohannes Berg return REG_REQ_ALREADY_SET; 20671a919318SJohannes Berg 20682f92212bSJohannes Berg return REG_REQ_OK; 206957b5ce07SLuis R. Rodriguez } 207057b5ce07SLuis R. Rodriguez 207157b5ce07SLuis R. Rodriguez /* Device specific check */ 207257b5ce07SLuis R. Rodriguez static bool reg_dev_ignore_cell_hint(struct wiphy *wiphy) 207357b5ce07SLuis R. Rodriguez { 20741a919318SJohannes Berg return !(wiphy->features & NL80211_FEATURE_CELL_BASE_REG_HINTS); 207557b5ce07SLuis R. Rodriguez } 207657b5ce07SLuis R. Rodriguez #else 2077a515de66SJohannes Berg static enum reg_request_treatment 2078a515de66SJohannes Berg reg_ignore_cell_hint(struct regulatory_request *pending_request) 207957b5ce07SLuis R. Rodriguez { 20802f92212bSJohannes Berg return REG_REQ_IGNORE; 208157b5ce07SLuis R. Rodriguez } 20821a919318SJohannes Berg 20831a919318SJohannes Berg static bool reg_dev_ignore_cell_hint(struct wiphy *wiphy) 208457b5ce07SLuis R. Rodriguez { 208557b5ce07SLuis R. Rodriguez return true; 208657b5ce07SLuis R. Rodriguez } 208757b5ce07SLuis R. Rodriguez #endif 208857b5ce07SLuis R. Rodriguez 2089fa1fb9cbSLuis R. Rodriguez static bool wiphy_strict_alpha2_regd(struct wiphy *wiphy) 2090fa1fb9cbSLuis R. Rodriguez { 2091a2f73b6cSLuis R. Rodriguez if (wiphy->regulatory_flags & REGULATORY_STRICT_REG && 2092a2f73b6cSLuis R. Rodriguez !(wiphy->regulatory_flags & REGULATORY_CUSTOM_REG)) 2093fa1fb9cbSLuis R. Rodriguez return true; 2094fa1fb9cbSLuis R. Rodriguez return false; 2095fa1fb9cbSLuis R. Rodriguez } 209657b5ce07SLuis R. Rodriguez 20977db90f4aSLuis R. Rodriguez static bool ignore_reg_update(struct wiphy *wiphy, 20987db90f4aSLuis R. Rodriguez enum nl80211_reg_initiator initiator) 209914b9815aSLuis R. Rodriguez { 2100c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 2101c492db37SJohannes Berg 2102b0d7aa59SJonathan Doron if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED) 2103b0d7aa59SJonathan Doron return true; 2104b0d7aa59SJonathan Doron 2105c492db37SJohannes Berg if (!lr) { 2106c799ba6eSJohannes Berg pr_debug("Ignoring regulatory request set by %s since last_request is not set\n", 2107926a0a09SLuis R. Rodriguez reg_initiator_name(initiator)); 210814b9815aSLuis R. Rodriguez return true; 2109926a0a09SLuis R. Rodriguez } 2110926a0a09SLuis R. Rodriguez 21117db90f4aSLuis R. Rodriguez if (initiator == NL80211_REGDOM_SET_BY_CORE && 2112a2f73b6cSLuis R. Rodriguez wiphy->regulatory_flags & REGULATORY_CUSTOM_REG) { 2113c799ba6eSJohannes Berg pr_debug("Ignoring regulatory request set by %s since the driver uses its own custom regulatory domain\n", 2114926a0a09SLuis R. Rodriguez reg_initiator_name(initiator)); 211514b9815aSLuis R. Rodriguez return true; 2116926a0a09SLuis R. Rodriguez } 2117926a0a09SLuis R. Rodriguez 2118fb1fc7adSLuis R. Rodriguez /* 2119fb1fc7adSLuis R. Rodriguez * wiphy->regd will be set once the device has its own 2120fb1fc7adSLuis R. Rodriguez * desired regulatory domain set 2121fb1fc7adSLuis R. Rodriguez */ 2122fa1fb9cbSLuis R. Rodriguez if (wiphy_strict_alpha2_regd(wiphy) && !wiphy->regd && 2123749b527bSLuis R. Rodriguez initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE && 2124c492db37SJohannes Berg !is_world_regdom(lr->alpha2)) { 2125c799ba6eSJohannes Berg pr_debug("Ignoring regulatory request set by %s since the driver requires its own regulatory domain to be set first\n", 2126926a0a09SLuis R. Rodriguez reg_initiator_name(initiator)); 212714b9815aSLuis R. Rodriguez return true; 2128926a0a09SLuis R. Rodriguez } 2129926a0a09SLuis R. Rodriguez 2130c492db37SJohannes Berg if (reg_request_cell_base(lr)) 213157b5ce07SLuis R. Rodriguez return reg_dev_ignore_cell_hint(wiphy); 213257b5ce07SLuis R. Rodriguez 213314b9815aSLuis R. Rodriguez return false; 213414b9815aSLuis R. Rodriguez } 213514b9815aSLuis R. Rodriguez 21363195e489SLuis R. Rodriguez static bool reg_is_world_roaming(struct wiphy *wiphy) 21373195e489SLuis R. Rodriguez { 21383195e489SLuis R. Rodriguez const struct ieee80211_regdomain *cr = get_cfg80211_regdom(); 21393195e489SLuis R. Rodriguez const struct ieee80211_regdomain *wr = get_wiphy_regdom(wiphy); 21403195e489SLuis R. Rodriguez struct regulatory_request *lr = get_last_request(); 21413195e489SLuis R. Rodriguez 21423195e489SLuis R. Rodriguez if (is_world_regdom(cr->alpha2) || (wr && is_world_regdom(wr->alpha2))) 21433195e489SLuis R. Rodriguez return true; 21443195e489SLuis R. Rodriguez 21453195e489SLuis R. Rodriguez if (lr && lr->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE && 2146a2f73b6cSLuis R. Rodriguez wiphy->regulatory_flags & REGULATORY_CUSTOM_REG) 21473195e489SLuis R. Rodriguez return true; 21483195e489SLuis R. Rodriguez 21493195e489SLuis R. Rodriguez return false; 21503195e489SLuis R. Rodriguez } 21513195e489SLuis R. Rodriguez 21521a919318SJohannes Berg static void handle_reg_beacon(struct wiphy *wiphy, unsigned int chan_idx, 2153e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon) 2154e38f8a7aSLuis R. Rodriguez { 2155e38f8a7aSLuis R. Rodriguez struct ieee80211_supported_band *sband; 2156e38f8a7aSLuis R. Rodriguez struct ieee80211_channel *chan; 21576bad8766SLuis R. Rodriguez bool channel_changed = false; 21586bad8766SLuis R. Rodriguez struct ieee80211_channel chan_before; 2159e38f8a7aSLuis R. Rodriguez 2160e38f8a7aSLuis R. Rodriguez sband = wiphy->bands[reg_beacon->chan.band]; 2161e38f8a7aSLuis R. Rodriguez chan = &sband->channels[chan_idx]; 2162e38f8a7aSLuis R. Rodriguez 2163934f4c7dSThomas Pedersen if (likely(!ieee80211_channel_equal(chan, ®_beacon->chan))) 2164e38f8a7aSLuis R. Rodriguez return; 2165e38f8a7aSLuis R. Rodriguez 21666bad8766SLuis R. Rodriguez if (chan->beacon_found) 21676bad8766SLuis R. Rodriguez return; 21686bad8766SLuis R. Rodriguez 21696bad8766SLuis R. Rodriguez chan->beacon_found = true; 21706bad8766SLuis R. Rodriguez 21710f500a5fSLuis R. Rodriguez if (!reg_is_world_roaming(wiphy)) 21720f500a5fSLuis R. Rodriguez return; 21730f500a5fSLuis R. Rodriguez 2174a2f73b6cSLuis R. Rodriguez if (wiphy->regulatory_flags & REGULATORY_DISABLE_BEACON_HINTS) 217537184244SLuis R. Rodriguez return; 217637184244SLuis R. Rodriguez 2177a48a52b7SJohannes Berg chan_before = *chan; 21786bad8766SLuis R. Rodriguez 21798fe02e16SLuis R. Rodriguez if (chan->flags & IEEE80211_CHAN_NO_IR) { 21808fe02e16SLuis R. Rodriguez chan->flags &= ~IEEE80211_CHAN_NO_IR; 21816bad8766SLuis R. Rodriguez channel_changed = true; 2182e38f8a7aSLuis R. Rodriguez } 2183e38f8a7aSLuis R. Rodriguez 21846bad8766SLuis R. Rodriguez if (channel_changed) 21856bad8766SLuis R. Rodriguez nl80211_send_beacon_hint_event(wiphy, &chan_before, chan); 2186e38f8a7aSLuis R. Rodriguez } 2187e38f8a7aSLuis R. Rodriguez 2188e38f8a7aSLuis R. Rodriguez /* 2189e38f8a7aSLuis R. Rodriguez * Called when a scan on a wiphy finds a beacon on 2190e38f8a7aSLuis R. Rodriguez * new channel 2191e38f8a7aSLuis R. Rodriguez */ 2192e38f8a7aSLuis R. Rodriguez static void wiphy_update_new_beacon(struct wiphy *wiphy, 2193e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon) 2194e38f8a7aSLuis R. Rodriguez { 2195e38f8a7aSLuis R. Rodriguez unsigned int i; 2196e38f8a7aSLuis R. Rodriguez struct ieee80211_supported_band *sband; 2197e38f8a7aSLuis R. Rodriguez 2198e38f8a7aSLuis R. Rodriguez if (!wiphy->bands[reg_beacon->chan.band]) 2199e38f8a7aSLuis R. Rodriguez return; 2200e38f8a7aSLuis R. Rodriguez 2201e38f8a7aSLuis R. Rodriguez sband = wiphy->bands[reg_beacon->chan.band]; 2202e38f8a7aSLuis R. Rodriguez 2203e38f8a7aSLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 2204e38f8a7aSLuis R. Rodriguez handle_reg_beacon(wiphy, i, reg_beacon); 2205e38f8a7aSLuis R. Rodriguez } 2206e38f8a7aSLuis R. Rodriguez 2207e38f8a7aSLuis R. Rodriguez /* 2208e38f8a7aSLuis R. Rodriguez * Called upon reg changes or a new wiphy is added 2209e38f8a7aSLuis R. Rodriguez */ 2210e38f8a7aSLuis R. Rodriguez static void wiphy_update_beacon_reg(struct wiphy *wiphy) 2211e38f8a7aSLuis R. Rodriguez { 2212e38f8a7aSLuis R. Rodriguez unsigned int i; 2213e38f8a7aSLuis R. Rodriguez struct ieee80211_supported_band *sband; 2214e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon; 2215e38f8a7aSLuis R. Rodriguez 2216e38f8a7aSLuis R. Rodriguez list_for_each_entry(reg_beacon, ®_beacon_list, list) { 2217e38f8a7aSLuis R. Rodriguez if (!wiphy->bands[reg_beacon->chan.band]) 2218e38f8a7aSLuis R. Rodriguez continue; 2219e38f8a7aSLuis R. Rodriguez sband = wiphy->bands[reg_beacon->chan.band]; 2220e38f8a7aSLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 2221e38f8a7aSLuis R. Rodriguez handle_reg_beacon(wiphy, i, reg_beacon); 2222e38f8a7aSLuis R. Rodriguez } 2223e38f8a7aSLuis R. Rodriguez } 2224e38f8a7aSLuis R. Rodriguez 2225e38f8a7aSLuis R. Rodriguez /* Reap the advantages of previously found beacons */ 2226e38f8a7aSLuis R. Rodriguez static void reg_process_beacons(struct wiphy *wiphy) 2227e38f8a7aSLuis R. Rodriguez { 2228b1ed8dddSLuis R. Rodriguez /* 2229b1ed8dddSLuis R. Rodriguez * Means we are just firing up cfg80211, so no beacons would 2230b1ed8dddSLuis R. Rodriguez * have been processed yet. 2231b1ed8dddSLuis R. Rodriguez */ 2232b1ed8dddSLuis R. Rodriguez if (!last_request) 2233b1ed8dddSLuis R. Rodriguez return; 2234e38f8a7aSLuis R. Rodriguez wiphy_update_beacon_reg(wiphy); 2235e38f8a7aSLuis R. Rodriguez } 2236e38f8a7aSLuis R. Rodriguez 22371a919318SJohannes Berg static bool is_ht40_allowed(struct ieee80211_channel *chan) 2238038659e7SLuis R. Rodriguez { 2239038659e7SLuis R. Rodriguez if (!chan) 2240038659e7SLuis R. Rodriguez return false; 22411a919318SJohannes Berg if (chan->flags & IEEE80211_CHAN_DISABLED) 22421a919318SJohannes Berg return false; 22431a919318SJohannes Berg /* This would happen when regulatory rules disallow HT40 completely */ 224455b183adSFelix Fietkau if ((chan->flags & IEEE80211_CHAN_NO_HT40) == IEEE80211_CHAN_NO_HT40) 224555b183adSFelix Fietkau return false; 224655b183adSFelix Fietkau return true; 2247038659e7SLuis R. Rodriguez } 2248038659e7SLuis R. Rodriguez 2249038659e7SLuis R. Rodriguez static void reg_process_ht_flags_channel(struct wiphy *wiphy, 2250fdc9d7b2SJohannes Berg struct ieee80211_channel *channel) 2251038659e7SLuis R. Rodriguez { 2252fdc9d7b2SJohannes Berg struct ieee80211_supported_band *sband = wiphy->bands[channel->band]; 2253038659e7SLuis R. Rodriguez struct ieee80211_channel *channel_before = NULL, *channel_after = NULL; 22544e0854a7SEmmanuel Grumbach const struct ieee80211_regdomain *regd; 2255038659e7SLuis R. Rodriguez unsigned int i; 22564e0854a7SEmmanuel Grumbach u32 flags; 2257038659e7SLuis R. Rodriguez 22581a919318SJohannes Berg if (!is_ht40_allowed(channel)) { 2259038659e7SLuis R. Rodriguez channel->flags |= IEEE80211_CHAN_NO_HT40; 2260038659e7SLuis R. Rodriguez return; 2261038659e7SLuis R. Rodriguez } 2262038659e7SLuis R. Rodriguez 2263038659e7SLuis R. Rodriguez /* 2264038659e7SLuis R. Rodriguez * We need to ensure the extension channels exist to 2265038659e7SLuis R. Rodriguez * be able to use HT40- or HT40+, this finds them (or not) 2266038659e7SLuis R. Rodriguez */ 2267038659e7SLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) { 2268038659e7SLuis R. Rodriguez struct ieee80211_channel *c = &sband->channels[i]; 22691a919318SJohannes Berg 2270038659e7SLuis R. Rodriguez if (c->center_freq == (channel->center_freq - 20)) 2271038659e7SLuis R. Rodriguez channel_before = c; 2272038659e7SLuis R. Rodriguez if (c->center_freq == (channel->center_freq + 20)) 2273038659e7SLuis R. Rodriguez channel_after = c; 2274038659e7SLuis R. Rodriguez } 2275038659e7SLuis R. Rodriguez 22764e0854a7SEmmanuel Grumbach flags = 0; 22774e0854a7SEmmanuel Grumbach regd = get_wiphy_regdom(wiphy); 22784e0854a7SEmmanuel Grumbach if (regd) { 22794e0854a7SEmmanuel Grumbach const struct ieee80211_reg_rule *reg_rule = 22804e0854a7SEmmanuel Grumbach freq_reg_info_regd(MHZ_TO_KHZ(channel->center_freq), 22814e0854a7SEmmanuel Grumbach regd, MHZ_TO_KHZ(20)); 22824e0854a7SEmmanuel Grumbach 22834e0854a7SEmmanuel Grumbach if (!IS_ERR(reg_rule)) 22844e0854a7SEmmanuel Grumbach flags = reg_rule->flags; 22854e0854a7SEmmanuel Grumbach } 22864e0854a7SEmmanuel Grumbach 2287038659e7SLuis R. Rodriguez /* 2288038659e7SLuis R. Rodriguez * Please note that this assumes target bandwidth is 20 MHz, 2289038659e7SLuis R. Rodriguez * if that ever changes we also need to change the below logic 2290038659e7SLuis R. Rodriguez * to include that as well. 2291038659e7SLuis R. Rodriguez */ 22924e0854a7SEmmanuel Grumbach if (!is_ht40_allowed(channel_before) || 22934e0854a7SEmmanuel Grumbach flags & NL80211_RRF_NO_HT40MINUS) 2294689da1b3SLuis R. Rodriguez channel->flags |= IEEE80211_CHAN_NO_HT40MINUS; 2295038659e7SLuis R. Rodriguez else 2296689da1b3SLuis R. Rodriguez channel->flags &= ~IEEE80211_CHAN_NO_HT40MINUS; 2297038659e7SLuis R. Rodriguez 22984e0854a7SEmmanuel Grumbach if (!is_ht40_allowed(channel_after) || 22994e0854a7SEmmanuel Grumbach flags & NL80211_RRF_NO_HT40PLUS) 2300689da1b3SLuis R. Rodriguez channel->flags |= IEEE80211_CHAN_NO_HT40PLUS; 2301038659e7SLuis R. Rodriguez else 2302689da1b3SLuis R. Rodriguez channel->flags &= ~IEEE80211_CHAN_NO_HT40PLUS; 2303038659e7SLuis R. Rodriguez } 2304038659e7SLuis R. Rodriguez 2305038659e7SLuis R. Rodriguez static void reg_process_ht_flags_band(struct wiphy *wiphy, 2306fdc9d7b2SJohannes Berg struct ieee80211_supported_band *sband) 2307038659e7SLuis R. Rodriguez { 2308038659e7SLuis R. Rodriguez unsigned int i; 2309038659e7SLuis R. Rodriguez 2310fdc9d7b2SJohannes Berg if (!sband) 2311fdc9d7b2SJohannes Berg return; 2312038659e7SLuis R. Rodriguez 2313038659e7SLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 2314fdc9d7b2SJohannes Berg reg_process_ht_flags_channel(wiphy, &sband->channels[i]); 2315038659e7SLuis R. Rodriguez } 2316038659e7SLuis R. Rodriguez 2317038659e7SLuis R. Rodriguez static void reg_process_ht_flags(struct wiphy *wiphy) 2318038659e7SLuis R. Rodriguez { 231957fbcce3SJohannes Berg enum nl80211_band band; 2320038659e7SLuis R. Rodriguez 2321038659e7SLuis R. Rodriguez if (!wiphy) 2322038659e7SLuis R. Rodriguez return; 2323038659e7SLuis R. Rodriguez 232457fbcce3SJohannes Berg for (band = 0; band < NUM_NL80211_BANDS; band++) 2325fdc9d7b2SJohannes Berg reg_process_ht_flags_band(wiphy, wiphy->bands[band]); 2326038659e7SLuis R. Rodriguez } 2327038659e7SLuis R. Rodriguez 23280e3802dbSLuis R. Rodriguez static void reg_call_notifier(struct wiphy *wiphy, 23290e3802dbSLuis R. Rodriguez struct regulatory_request *request) 23300e3802dbSLuis R. Rodriguez { 23310e3802dbSLuis R. Rodriguez if (wiphy->reg_notifier) 23320e3802dbSLuis R. Rodriguez wiphy->reg_notifier(wiphy, request); 23330e3802dbSLuis R. Rodriguez } 23340e3802dbSLuis R. Rodriguez 2335ad932f04SArik Nemtsov static bool reg_wdev_chan_valid(struct wiphy *wiphy, struct wireless_dev *wdev) 2336ad932f04SArik Nemtsov { 2337f43e5210SJohannes Berg struct cfg80211_chan_def chandef = {}; 2338ad932f04SArik Nemtsov struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); 233920658702SArik Nemtsov enum nl80211_iftype iftype; 2340e08ebd6dSIlan Peer bool ret; 23417b0a0e3cSJohannes Berg int link; 2342ad932f04SArik Nemtsov 2343ad932f04SArik Nemtsov wdev_lock(wdev); 234420658702SArik Nemtsov iftype = wdev->iftype; 2345ad932f04SArik Nemtsov 234620658702SArik Nemtsov /* make sure the interface is active */ 2347ad932f04SArik Nemtsov if (!wdev->netdev || !netif_running(wdev->netdev)) 234820658702SArik Nemtsov goto wdev_inactive_unlock; 2349ad932f04SArik Nemtsov 23507b0a0e3cSJohannes Berg for (link = 0; link < ARRAY_SIZE(wdev->links); link++) { 23517b0a0e3cSJohannes Berg struct ieee80211_channel *chan; 23527b0a0e3cSJohannes Berg 23537b0a0e3cSJohannes Berg if (!wdev->valid_links && link > 0) 23547b0a0e3cSJohannes Berg break; 23557b0a0e3cSJohannes Berg if (!(wdev->valid_links & BIT(link))) 23567b0a0e3cSJohannes Berg continue; 235720658702SArik Nemtsov switch (iftype) { 2358ad932f04SArik Nemtsov case NL80211_IFTYPE_AP: 2359ad932f04SArik Nemtsov case NL80211_IFTYPE_P2P_GO: 2360bc185761SShaul Triebitz if (!wdev->links[link].ap.beacon_interval) 2361bc185761SShaul Triebitz continue; 2362bc185761SShaul Triebitz chandef = wdev->links[link].ap.chandef; 2363bc185761SShaul Triebitz break; 2364701fdfe3SSriram R case NL80211_IFTYPE_MESH_POINT: 23657b0a0e3cSJohannes Berg if (!wdev->u.mesh.beacon_interval) 23667b0a0e3cSJohannes Berg continue; 23677b0a0e3cSJohannes Berg chandef = wdev->u.mesh.chandef; 2368ad932f04SArik Nemtsov break; 2369185076d6SArik Nemtsov case NL80211_IFTYPE_ADHOC: 23707b0a0e3cSJohannes Berg if (!wdev->u.ibss.ssid_len) 23717b0a0e3cSJohannes Berg continue; 23727b0a0e3cSJohannes Berg chandef = wdev->u.ibss.chandef; 2373185076d6SArik Nemtsov break; 2374ad932f04SArik Nemtsov case NL80211_IFTYPE_STATION: 2375ad932f04SArik Nemtsov case NL80211_IFTYPE_P2P_CLIENT: 23767b0a0e3cSJohannes Berg /* Maybe we could consider disabling that link only? */ 23777b0a0e3cSJohannes Berg if (!wdev->links[link].client.current_bss) 23787b0a0e3cSJohannes Berg continue; 23797b0a0e3cSJohannes Berg 23807b0a0e3cSJohannes Berg chan = wdev->links[link].client.current_bss->pub.channel; 23817b0a0e3cSJohannes Berg if (!chan) 23827b0a0e3cSJohannes Berg continue; 2383ad932f04SArik Nemtsov 238420658702SArik Nemtsov if (!rdev->ops->get_channel || 23857b0a0e3cSJohannes Berg rdev_get_channel(rdev, wdev, link, &chandef)) 23867b0a0e3cSJohannes Berg cfg80211_chandef_create(&chandef, chan, 238720658702SArik Nemtsov NL80211_CHAN_NO_HT); 2388ad932f04SArik Nemtsov break; 2389ad932f04SArik Nemtsov case NL80211_IFTYPE_MONITOR: 2390ad932f04SArik Nemtsov case NL80211_IFTYPE_AP_VLAN: 2391ad932f04SArik Nemtsov case NL80211_IFTYPE_P2P_DEVICE: 2392ad932f04SArik Nemtsov /* no enforcement required */ 2393ad932f04SArik Nemtsov break; 2394ad932f04SArik Nemtsov default: 2395ad932f04SArik Nemtsov /* others not implemented for now */ 2396ad932f04SArik Nemtsov WARN_ON(1); 2397ad932f04SArik Nemtsov break; 2398ad932f04SArik Nemtsov } 2399ad932f04SArik Nemtsov 2400ad932f04SArik Nemtsov wdev_unlock(wdev); 240120658702SArik Nemtsov 240220658702SArik Nemtsov switch (iftype) { 240320658702SArik Nemtsov case NL80211_IFTYPE_AP: 240420658702SArik Nemtsov case NL80211_IFTYPE_P2P_GO: 240520658702SArik Nemtsov case NL80211_IFTYPE_ADHOC: 2406701fdfe3SSriram R case NL80211_IFTYPE_MESH_POINT: 2407e08ebd6dSIlan Peer wiphy_lock(wiphy); 24087b0a0e3cSJohannes Berg ret = cfg80211_reg_can_beacon_relax(wiphy, &chandef, 24097b0a0e3cSJohannes Berg iftype); 2410e08ebd6dSIlan Peer wiphy_unlock(wiphy); 2411e08ebd6dSIlan Peer 24127b0a0e3cSJohannes Berg if (!ret) 2413e08ebd6dSIlan Peer return ret; 24147b0a0e3cSJohannes Berg break; 241520658702SArik Nemtsov case NL80211_IFTYPE_STATION: 241620658702SArik Nemtsov case NL80211_IFTYPE_P2P_CLIENT: 24177b0a0e3cSJohannes Berg ret = cfg80211_chandef_usable(wiphy, &chandef, 241820658702SArik Nemtsov IEEE80211_CHAN_DISABLED); 24197b0a0e3cSJohannes Berg if (!ret) 24207b0a0e3cSJohannes Berg return ret; 24217b0a0e3cSJohannes Berg break; 242220658702SArik Nemtsov default: 242320658702SArik Nemtsov break; 242420658702SArik Nemtsov } 242520658702SArik Nemtsov 24267b0a0e3cSJohannes Berg wdev_lock(wdev); 24277b0a0e3cSJohannes Berg } 24287b0a0e3cSJohannes Berg 24297b0a0e3cSJohannes Berg wdev_unlock(wdev); 24307b0a0e3cSJohannes Berg 243120658702SArik Nemtsov return true; 243220658702SArik Nemtsov 243320658702SArik Nemtsov wdev_inactive_unlock: 243420658702SArik Nemtsov wdev_unlock(wdev); 243520658702SArik Nemtsov return true; 2436ad932f04SArik Nemtsov } 2437ad932f04SArik Nemtsov 2438ad932f04SArik Nemtsov static void reg_leave_invalid_chans(struct wiphy *wiphy) 2439ad932f04SArik Nemtsov { 2440ad932f04SArik Nemtsov struct wireless_dev *wdev; 2441ad932f04SArik Nemtsov struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); 2442ad932f04SArik Nemtsov 2443*f7e60032SJohannes Berg wiphy_lock(wiphy); 244453873f13SJohannes Berg list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) 2445ad932f04SArik Nemtsov if (!reg_wdev_chan_valid(wiphy, wdev)) 2446ad932f04SArik Nemtsov cfg80211_leave(rdev, wdev); 2447*f7e60032SJohannes Berg wiphy_unlock(wiphy); 2448ad932f04SArik Nemtsov } 2449ad932f04SArik Nemtsov 2450ad932f04SArik Nemtsov static void reg_check_chans_work(struct work_struct *work) 2451ad932f04SArik Nemtsov { 2452ad932f04SArik Nemtsov struct cfg80211_registered_device *rdev; 2453ad932f04SArik Nemtsov 2454c799ba6eSJohannes Berg pr_debug("Verifying active interfaces after reg change\n"); 2455ad932f04SArik Nemtsov rtnl_lock(); 2456ad932f04SArik Nemtsov 2457ad932f04SArik Nemtsov list_for_each_entry(rdev, &cfg80211_rdev_list, list) 2458ad932f04SArik Nemtsov if (!(rdev->wiphy.regulatory_flags & 2459ad932f04SArik Nemtsov REGULATORY_IGNORE_STALE_KICKOFF)) 2460ad932f04SArik Nemtsov reg_leave_invalid_chans(&rdev->wiphy); 2461ad932f04SArik Nemtsov 2462ad932f04SArik Nemtsov rtnl_unlock(); 2463ad932f04SArik Nemtsov } 2464ad932f04SArik Nemtsov 2465ad932f04SArik Nemtsov static void reg_check_channels(void) 2466ad932f04SArik Nemtsov { 2467ad932f04SArik Nemtsov /* 2468ad932f04SArik Nemtsov * Give usermode a chance to do something nicer (move to another 2469ad932f04SArik Nemtsov * channel, orderly disconnection), before forcing a disconnection. 2470ad932f04SArik Nemtsov */ 2471ad932f04SArik Nemtsov mod_delayed_work(system_power_efficient_wq, 2472ad932f04SArik Nemtsov ®_check_chans, 2473ad932f04SArik Nemtsov msecs_to_jiffies(REG_ENFORCE_GRACE_MS)); 2474ad932f04SArik Nemtsov } 2475ad932f04SArik Nemtsov 2476eac03e38SSven Neumann static void wiphy_update_regulatory(struct wiphy *wiphy, 24777db90f4aSLuis R. Rodriguez enum nl80211_reg_initiator initiator) 24788318d78aSJohannes Berg { 247957fbcce3SJohannes Berg enum nl80211_band band; 2480c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 2481eac03e38SSven Neumann 24820e3802dbSLuis R. Rodriguez if (ignore_reg_update(wiphy, initiator)) { 24830e3802dbSLuis R. Rodriguez /* 24840e3802dbSLuis R. Rodriguez * Regulatory updates set by CORE are ignored for custom 24850e3802dbSLuis R. Rodriguez * regulatory cards. Let us notify the changes to the driver, 24860e3802dbSLuis R. Rodriguez * as some drivers used this to restore its orig_* reg domain. 24870e3802dbSLuis R. Rodriguez */ 24880e3802dbSLuis R. Rodriguez if (initiator == NL80211_REGDOM_SET_BY_CORE && 2489e31f6456SAmar Singhal wiphy->regulatory_flags & REGULATORY_CUSTOM_REG && 2490e31f6456SAmar Singhal !(wiphy->regulatory_flags & 2491e31f6456SAmar Singhal REGULATORY_WIPHY_SELF_MANAGED)) 24920e3802dbSLuis R. Rodriguez reg_call_notifier(wiphy, lr); 2493a203c2aaSSven Neumann return; 24940e3802dbSLuis R. Rodriguez } 2495a203c2aaSSven Neumann 2496c492db37SJohannes Berg lr->dfs_region = get_cfg80211_regdom()->dfs_region; 2497b68e6b3bSLuis R. Rodriguez 249857fbcce3SJohannes Berg for (band = 0; band < NUM_NL80211_BANDS; band++) 2499fdc9d7b2SJohannes Berg handle_band(wiphy, initiator, wiphy->bands[band]); 2500a203c2aaSSven Neumann 2501e38f8a7aSLuis R. Rodriguez reg_process_beacons(wiphy); 2502038659e7SLuis R. Rodriguez reg_process_ht_flags(wiphy); 25030e3802dbSLuis R. Rodriguez reg_call_notifier(wiphy, lr); 2504b2e1b302SLuis R. Rodriguez } 2505b2e1b302SLuis R. Rodriguez 2506d7549cbbSSven Neumann static void update_all_wiphy_regulatory(enum nl80211_reg_initiator initiator) 2507d7549cbbSSven Neumann { 2508d7549cbbSSven Neumann struct cfg80211_registered_device *rdev; 25094a38994fSRajkumar Manoharan struct wiphy *wiphy; 2510d7549cbbSSven Neumann 25115fe231e8SJohannes Berg ASSERT_RTNL(); 2512458f4f9eSJohannes Berg 25134a38994fSRajkumar Manoharan list_for_each_entry(rdev, &cfg80211_rdev_list, list) { 25144a38994fSRajkumar Manoharan wiphy = &rdev->wiphy; 25154a38994fSRajkumar Manoharan wiphy_update_regulatory(wiphy, initiator); 25164a38994fSRajkumar Manoharan } 2517ad932f04SArik Nemtsov 2518ad932f04SArik Nemtsov reg_check_channels(); 2519d7549cbbSSven Neumann } 2520d7549cbbSSven Neumann 25211fa25e41SLuis R. Rodriguez static void handle_channel_custom(struct wiphy *wiphy, 2522fdc9d7b2SJohannes Berg struct ieee80211_channel *chan, 2523c4b9d655SGanapathi Bhat const struct ieee80211_regdomain *regd, 2524c4b9d655SGanapathi Bhat u32 min_bw) 25251fa25e41SLuis R. Rodriguez { 2526038659e7SLuis R. Rodriguez u32 bw_flags = 0; 25271fa25e41SLuis R. Rodriguez const struct ieee80211_reg_rule *reg_rule = NULL; 25281fa25e41SLuis R. Rodriguez const struct ieee80211_power_rule *power_rule = NULL; 2529934f4c7dSThomas Pedersen u32 bw, center_freq_khz; 25301fa25e41SLuis R. Rodriguez 2531934f4c7dSThomas Pedersen center_freq_khz = ieee80211_channel_to_khz(chan); 2532c4b9d655SGanapathi Bhat for (bw = MHZ_TO_KHZ(20); bw >= min_bw; bw = bw / 2) { 2533934f4c7dSThomas Pedersen reg_rule = freq_reg_info_regd(center_freq_khz, regd, bw); 25344edd5698SMatthias May if (!IS_ERR(reg_rule)) 25354edd5698SMatthias May break; 25364edd5698SMatthias May } 25371fa25e41SLuis R. Rodriguez 2538a7ee7d44SJohannes Berg if (IS_ERR_OR_NULL(reg_rule)) { 2539934f4c7dSThomas Pedersen pr_debug("Disabling freq %d.%03d MHz as custom regd has no rule that fits it\n", 2540934f4c7dSThomas Pedersen chan->center_freq, chan->freq_offset); 2541db8dfee5SArik Nemtsov if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED) { 2542db8dfee5SArik Nemtsov chan->flags |= IEEE80211_CHAN_DISABLED; 2543db8dfee5SArik Nemtsov } else { 2544cc493e4fSLuis R. Rodriguez chan->orig_flags |= IEEE80211_CHAN_DISABLED; 2545cc493e4fSLuis R. Rodriguez chan->flags = chan->orig_flags; 2546db8dfee5SArik Nemtsov } 25471fa25e41SLuis R. Rodriguez return; 25481fa25e41SLuis R. Rodriguez } 25491fa25e41SLuis R. Rodriguez 25501fa25e41SLuis R. Rodriguez power_rule = ®_rule->power_rule; 25511aeb135fSMichal Sojka bw_flags = reg_rule_to_chan_bw_flags(regd, reg_rule, chan); 2552038659e7SLuis R. Rodriguez 25532e18b38fSArik Nemtsov chan->dfs_state_entered = jiffies; 2554c7ab5081SArik Nemtsov chan->dfs_state = NL80211_DFS_USABLE; 2555c7ab5081SArik Nemtsov 2556c7ab5081SArik Nemtsov chan->beacon_found = false; 2557db8dfee5SArik Nemtsov 2558db8dfee5SArik Nemtsov if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED) 2559db8dfee5SArik Nemtsov chan->flags = chan->orig_flags | bw_flags | 2560db8dfee5SArik Nemtsov map_regdom_flags(reg_rule->flags); 2561db8dfee5SArik Nemtsov else 2562038659e7SLuis R. Rodriguez chan->flags |= map_regdom_flags(reg_rule->flags) | bw_flags; 2563db8dfee5SArik Nemtsov 25641fa25e41SLuis R. Rodriguez chan->max_antenna_gain = (int) MBI_TO_DBI(power_rule->max_antenna_gain); 2565279f0f55SFelix Fietkau chan->max_reg_power = chan->max_power = 2566279f0f55SFelix Fietkau (int) MBM_TO_DBM(power_rule->max_eirp); 25672e18b38fSArik Nemtsov 25682e18b38fSArik Nemtsov if (chan->flags & IEEE80211_CHAN_RADAR) { 25692e18b38fSArik Nemtsov if (reg_rule->dfs_cac_ms) 25702e18b38fSArik Nemtsov chan->dfs_cac_ms = reg_rule->dfs_cac_ms; 25712e18b38fSArik Nemtsov else 25722e18b38fSArik Nemtsov chan->dfs_cac_ms = IEEE80211_DFS_MIN_CAC_TIME_MS; 25732e18b38fSArik Nemtsov } 25742e18b38fSArik Nemtsov 25752e18b38fSArik Nemtsov chan->max_power = chan->max_reg_power; 25761fa25e41SLuis R. Rodriguez } 25771fa25e41SLuis R. Rodriguez 2578fdc9d7b2SJohannes Berg static void handle_band_custom(struct wiphy *wiphy, 2579fdc9d7b2SJohannes Berg struct ieee80211_supported_band *sband, 25801fa25e41SLuis R. Rodriguez const struct ieee80211_regdomain *regd) 25811fa25e41SLuis R. Rodriguez { 25821fa25e41SLuis R. Rodriguez unsigned int i; 25831fa25e41SLuis R. Rodriguez 2584fdc9d7b2SJohannes Berg if (!sband) 2585fdc9d7b2SJohannes Berg return; 25861fa25e41SLuis R. Rodriguez 2587c4b9d655SGanapathi Bhat /* 2588c4b9d655SGanapathi Bhat * We currently assume that you always want at least 20 MHz, 2589c4b9d655SGanapathi Bhat * otherwise channel 12 might get enabled if this rule is 2590c4b9d655SGanapathi Bhat * compatible to US, which permits 2402 - 2472 MHz. 2591c4b9d655SGanapathi Bhat */ 25921fa25e41SLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 2593c4b9d655SGanapathi Bhat handle_channel_custom(wiphy, &sband->channels[i], regd, 2594c4b9d655SGanapathi Bhat MHZ_TO_KHZ(20)); 25951fa25e41SLuis R. Rodriguez } 25961fa25e41SLuis R. Rodriguez 25971fa25e41SLuis R. Rodriguez /* Used by drivers prior to wiphy registration */ 25981fa25e41SLuis R. Rodriguez void wiphy_apply_custom_regulatory(struct wiphy *wiphy, 25991fa25e41SLuis R. Rodriguez const struct ieee80211_regdomain *regd) 26001fa25e41SLuis R. Rodriguez { 2601beee2469SIlan Peer const struct ieee80211_regdomain *new_regd, *tmp; 260257fbcce3SJohannes Berg enum nl80211_band band; 2603bbcf3f02SLuis R. Rodriguez unsigned int bands_set = 0; 2604ac46d48eSLuis R. Rodriguez 2605a2f73b6cSLuis R. Rodriguez WARN(!(wiphy->regulatory_flags & REGULATORY_CUSTOM_REG), 2606a2f73b6cSLuis R. Rodriguez "wiphy should have REGULATORY_CUSTOM_REG\n"); 2607a2f73b6cSLuis R. Rodriguez wiphy->regulatory_flags |= REGULATORY_CUSTOM_REG; 2608222ea581SLuis R. Rodriguez 260957fbcce3SJohannes Berg for (band = 0; band < NUM_NL80211_BANDS; band++) { 2610bbcf3f02SLuis R. Rodriguez if (!wiphy->bands[band]) 2611bbcf3f02SLuis R. Rodriguez continue; 2612fdc9d7b2SJohannes Berg handle_band_custom(wiphy, wiphy->bands[band], regd); 2613bbcf3f02SLuis R. Rodriguez bands_set++; 26141fa25e41SLuis R. Rodriguez } 2615bbcf3f02SLuis R. Rodriguez 2616bbcf3f02SLuis R. Rodriguez /* 2617bbcf3f02SLuis R. Rodriguez * no point in calling this if it won't have any effect 26181a919318SJohannes Berg * on your device's supported bands. 2619bbcf3f02SLuis R. Rodriguez */ 2620bbcf3f02SLuis R. Rodriguez WARN_ON(!bands_set); 2621beee2469SIlan Peer new_regd = reg_copy_regd(regd); 2622beee2469SIlan Peer if (IS_ERR(new_regd)) 2623beee2469SIlan Peer return; 2624beee2469SIlan Peer 262551d62f2fSIlan Peer rtnl_lock(); 2626a05829a7SJohannes Berg wiphy_lock(wiphy); 262751d62f2fSIlan Peer 2628beee2469SIlan Peer tmp = get_wiphy_regdom(wiphy); 2629beee2469SIlan Peer rcu_assign_pointer(wiphy->regd, new_regd); 2630beee2469SIlan Peer rcu_free_regdom(tmp); 263151d62f2fSIlan Peer 2632a05829a7SJohannes Berg wiphy_unlock(wiphy); 263351d62f2fSIlan Peer rtnl_unlock(); 26341fa25e41SLuis R. Rodriguez } 26351fa25e41SLuis R. Rodriguez EXPORT_SYMBOL(wiphy_apply_custom_regulatory); 26361fa25e41SLuis R. Rodriguez 2637b2e253cfSLuis R. Rodriguez static void reg_set_request_processed(void) 2638b2e253cfSLuis R. Rodriguez { 2639b2e253cfSLuis R. Rodriguez bool need_more_processing = false; 2640c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 2641b2e253cfSLuis R. Rodriguez 2642c492db37SJohannes Berg lr->processed = true; 2643b2e253cfSLuis R. Rodriguez 2644b2e253cfSLuis R. Rodriguez spin_lock(®_requests_lock); 2645b2e253cfSLuis R. Rodriguez if (!list_empty(®_requests_list)) 2646b2e253cfSLuis R. Rodriguez need_more_processing = true; 2647b2e253cfSLuis R. Rodriguez spin_unlock(®_requests_lock); 2648b2e253cfSLuis R. Rodriguez 2649b6863036SJohannes Berg cancel_crda_timeout(); 2650a90c7a31SLuis R. Rodriguez 2651b2e253cfSLuis R. Rodriguez if (need_more_processing) 2652b2e253cfSLuis R. Rodriguez schedule_work(®_work); 2653b2e253cfSLuis R. Rodriguez } 2654b2e253cfSLuis R. Rodriguez 2655d1c96a9aSLuis R. Rodriguez /** 2656b3eb7f3fSLuis R. Rodriguez * reg_process_hint_core - process core regulatory requests 2657726e6af9SAndrew Lunn * @core_request: a pending core regulatory request 2658b3eb7f3fSLuis R. Rodriguez * 2659b3eb7f3fSLuis R. Rodriguez * The wireless subsystem can use this function to process 2660b3eb7f3fSLuis R. Rodriguez * a regulatory request issued by the regulatory core. 2661b3eb7f3fSLuis R. Rodriguez */ 2662d34265a3SJohannes Berg static enum reg_request_treatment 2663d34265a3SJohannes Berg reg_process_hint_core(struct regulatory_request *core_request) 2664b3eb7f3fSLuis R. Rodriguez { 2665cecbb069SJohannes Berg if (reg_query_database(core_request)) { 2666b3eb7f3fSLuis R. Rodriguez core_request->intersect = false; 2667b3eb7f3fSLuis R. Rodriguez core_request->processed = false; 266805f1a3eaSLuis R. Rodriguez reg_update_last_request(core_request); 2669d34265a3SJohannes Berg return REG_REQ_OK; 267025b20dbdSJohannes Berg } 2671d34265a3SJohannes Berg 2672d34265a3SJohannes Berg return REG_REQ_IGNORE; 2673b3eb7f3fSLuis R. Rodriguez } 2674b3eb7f3fSLuis R. Rodriguez 26750d97a619SLuis R. Rodriguez static enum reg_request_treatment 26760d97a619SLuis R. Rodriguez __reg_process_hint_user(struct regulatory_request *user_request) 26770d97a619SLuis R. Rodriguez { 26780d97a619SLuis R. Rodriguez struct regulatory_request *lr = get_last_request(); 26790d97a619SLuis R. Rodriguez 26800d97a619SLuis R. Rodriguez if (reg_request_cell_base(user_request)) 26810d97a619SLuis R. Rodriguez return reg_ignore_cell_hint(user_request); 26820d97a619SLuis R. Rodriguez 26830d97a619SLuis R. Rodriguez if (reg_request_cell_base(lr)) 26840d97a619SLuis R. Rodriguez return REG_REQ_IGNORE; 26850d97a619SLuis R. Rodriguez 26860d97a619SLuis R. Rodriguez if (lr->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE) 26870d97a619SLuis R. Rodriguez return REG_REQ_INTERSECT; 26880d97a619SLuis R. Rodriguez /* 26890d97a619SLuis R. Rodriguez * If the user knows better the user should set the regdom 26900d97a619SLuis R. Rodriguez * to their country before the IE is picked up 26910d97a619SLuis R. Rodriguez */ 26920d97a619SLuis R. Rodriguez if (lr->initiator == NL80211_REGDOM_SET_BY_USER && 26930d97a619SLuis R. Rodriguez lr->intersect) 26940d97a619SLuis R. Rodriguez return REG_REQ_IGNORE; 26950d97a619SLuis R. Rodriguez /* 26960d97a619SLuis R. Rodriguez * Process user requests only after previous user/driver/core 26970d97a619SLuis R. Rodriguez * requests have been processed 26980d97a619SLuis R. Rodriguez */ 26990d97a619SLuis R. Rodriguez if ((lr->initiator == NL80211_REGDOM_SET_BY_CORE || 27000d97a619SLuis R. Rodriguez lr->initiator == NL80211_REGDOM_SET_BY_DRIVER || 27010d97a619SLuis R. Rodriguez lr->initiator == NL80211_REGDOM_SET_BY_USER) && 27020d97a619SLuis R. Rodriguez regdom_changes(lr->alpha2)) 27030d97a619SLuis R. Rodriguez return REG_REQ_IGNORE; 27040d97a619SLuis R. Rodriguez 27050d97a619SLuis R. Rodriguez if (!regdom_changes(user_request->alpha2)) 27060d97a619SLuis R. Rodriguez return REG_REQ_ALREADY_SET; 27070d97a619SLuis R. Rodriguez 27080d97a619SLuis R. Rodriguez return REG_REQ_OK; 27090d97a619SLuis R. Rodriguez } 27100d97a619SLuis R. Rodriguez 27110d97a619SLuis R. Rodriguez /** 27120d97a619SLuis R. Rodriguez * reg_process_hint_user - process user regulatory requests 27130d97a619SLuis R. Rodriguez * @user_request: a pending user regulatory request 27140d97a619SLuis R. Rodriguez * 27150d97a619SLuis R. Rodriguez * The wireless subsystem can use this function to process 27160d97a619SLuis R. Rodriguez * a regulatory request initiated by userspace. 27170d97a619SLuis R. Rodriguez */ 2718d34265a3SJohannes Berg static enum reg_request_treatment 2719d34265a3SJohannes Berg reg_process_hint_user(struct regulatory_request *user_request) 27200d97a619SLuis R. Rodriguez { 27210d97a619SLuis R. Rodriguez enum reg_request_treatment treatment; 27220d97a619SLuis R. Rodriguez 27230d97a619SLuis R. Rodriguez treatment = __reg_process_hint_user(user_request); 27240d97a619SLuis R. Rodriguez if (treatment == REG_REQ_IGNORE || 272537d33114SFinn Behrens treatment == REG_REQ_ALREADY_SET) 2726d34265a3SJohannes Berg return REG_REQ_IGNORE; 27270d97a619SLuis R. Rodriguez 27280d97a619SLuis R. Rodriguez user_request->intersect = treatment == REG_REQ_INTERSECT; 27290d97a619SLuis R. Rodriguez user_request->processed = false; 27305ad6ef5eSLuis R. Rodriguez 2731cecbb069SJohannes Berg if (reg_query_database(user_request)) { 273205f1a3eaSLuis R. Rodriguez reg_update_last_request(user_request); 27330d97a619SLuis R. Rodriguez user_alpha2[0] = user_request->alpha2[0]; 27340d97a619SLuis R. Rodriguez user_alpha2[1] = user_request->alpha2[1]; 2735d34265a3SJohannes Berg return REG_REQ_OK; 273625b20dbdSJohannes Berg } 2737d34265a3SJohannes Berg 2738d34265a3SJohannes Berg return REG_REQ_IGNORE; 27390d97a619SLuis R. Rodriguez } 27400d97a619SLuis R. Rodriguez 274121636c7fSLuis R. Rodriguez static enum reg_request_treatment 274221636c7fSLuis R. Rodriguez __reg_process_hint_driver(struct regulatory_request *driver_request) 274321636c7fSLuis R. Rodriguez { 274421636c7fSLuis R. Rodriguez struct regulatory_request *lr = get_last_request(); 274521636c7fSLuis R. Rodriguez 274621636c7fSLuis R. Rodriguez if (lr->initiator == NL80211_REGDOM_SET_BY_CORE) { 274721636c7fSLuis R. Rodriguez if (regdom_changes(driver_request->alpha2)) 274821636c7fSLuis R. Rodriguez return REG_REQ_OK; 274921636c7fSLuis R. Rodriguez return REG_REQ_ALREADY_SET; 275021636c7fSLuis R. Rodriguez } 275121636c7fSLuis R. Rodriguez 275221636c7fSLuis R. Rodriguez /* 275321636c7fSLuis R. Rodriguez * This would happen if you unplug and plug your card 275421636c7fSLuis R. Rodriguez * back in or if you add a new device for which the previously 275521636c7fSLuis R. Rodriguez * loaded card also agrees on the regulatory domain. 275621636c7fSLuis R. Rodriguez */ 275721636c7fSLuis R. Rodriguez if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER && 275821636c7fSLuis R. Rodriguez !regdom_changes(driver_request->alpha2)) 275921636c7fSLuis R. Rodriguez return REG_REQ_ALREADY_SET; 276021636c7fSLuis R. Rodriguez 276121636c7fSLuis R. Rodriguez return REG_REQ_INTERSECT; 276221636c7fSLuis R. Rodriguez } 276321636c7fSLuis R. Rodriguez 276421636c7fSLuis R. Rodriguez /** 276521636c7fSLuis R. Rodriguez * reg_process_hint_driver - process driver regulatory requests 2766726e6af9SAndrew Lunn * @wiphy: the wireless device for the regulatory request 276721636c7fSLuis R. Rodriguez * @driver_request: a pending driver regulatory request 276821636c7fSLuis R. Rodriguez * 276921636c7fSLuis R. Rodriguez * The wireless subsystem can use this function to process 277021636c7fSLuis R. Rodriguez * a regulatory request issued by an 802.11 driver. 277121636c7fSLuis R. Rodriguez * 277221636c7fSLuis R. Rodriguez * Returns one of the different reg request treatment values. 277321636c7fSLuis R. Rodriguez */ 277421636c7fSLuis R. Rodriguez static enum reg_request_treatment 277521636c7fSLuis R. Rodriguez reg_process_hint_driver(struct wiphy *wiphy, 277621636c7fSLuis R. Rodriguez struct regulatory_request *driver_request) 277721636c7fSLuis R. Rodriguez { 277834f05f54SArik Nemtsov const struct ieee80211_regdomain *regd, *tmp; 277921636c7fSLuis R. Rodriguez enum reg_request_treatment treatment; 278021636c7fSLuis R. Rodriguez 278121636c7fSLuis R. Rodriguez treatment = __reg_process_hint_driver(driver_request); 278221636c7fSLuis R. Rodriguez 278321636c7fSLuis R. Rodriguez switch (treatment) { 278421636c7fSLuis R. Rodriguez case REG_REQ_OK: 278521636c7fSLuis R. Rodriguez break; 278621636c7fSLuis R. Rodriguez case REG_REQ_IGNORE: 2787d34265a3SJohannes Berg return REG_REQ_IGNORE; 278821636c7fSLuis R. Rodriguez case REG_REQ_INTERSECT: 278921636c7fSLuis R. Rodriguez case REG_REQ_ALREADY_SET: 279021636c7fSLuis R. Rodriguez regd = reg_copy_regd(get_cfg80211_regdom()); 2791d34265a3SJohannes Berg if (IS_ERR(regd)) 2792d34265a3SJohannes Berg return REG_REQ_IGNORE; 279334f05f54SArik Nemtsov 279434f05f54SArik Nemtsov tmp = get_wiphy_regdom(wiphy); 2795a05829a7SJohannes Berg ASSERT_RTNL(); 2796a05829a7SJohannes Berg wiphy_lock(wiphy); 279721636c7fSLuis R. Rodriguez rcu_assign_pointer(wiphy->regd, regd); 2798a05829a7SJohannes Berg wiphy_unlock(wiphy); 279934f05f54SArik Nemtsov rcu_free_regdom(tmp); 280021636c7fSLuis R. Rodriguez } 280121636c7fSLuis R. Rodriguez 280221636c7fSLuis R. Rodriguez 280321636c7fSLuis R. Rodriguez driver_request->intersect = treatment == REG_REQ_INTERSECT; 280421636c7fSLuis R. Rodriguez driver_request->processed = false; 28055ad6ef5eSLuis R. Rodriguez 280621636c7fSLuis R. Rodriguez /* 280721636c7fSLuis R. Rodriguez * Since CRDA will not be called in this case as we already 280821636c7fSLuis R. Rodriguez * have applied the requested regulatory domain before we just 280921636c7fSLuis R. Rodriguez * inform userspace we have processed the request 281021636c7fSLuis R. Rodriguez */ 281121636c7fSLuis R. Rodriguez if (treatment == REG_REQ_ALREADY_SET) { 281221636c7fSLuis R. Rodriguez nl80211_send_reg_change_event(driver_request); 281325b20dbdSJohannes Berg reg_update_last_request(driver_request); 281421636c7fSLuis R. Rodriguez reg_set_request_processed(); 2815480908a7SJohannes Berg return REG_REQ_ALREADY_SET; 281621636c7fSLuis R. Rodriguez } 281721636c7fSLuis R. Rodriguez 2818d34265a3SJohannes Berg if (reg_query_database(driver_request)) { 281925b20dbdSJohannes Berg reg_update_last_request(driver_request); 282025b20dbdSJohannes Berg return REG_REQ_OK; 282121636c7fSLuis R. Rodriguez } 282221636c7fSLuis R. Rodriguez 2823d34265a3SJohannes Berg return REG_REQ_IGNORE; 2824d34265a3SJohannes Berg } 2825d34265a3SJohannes Berg 2826b23e7a9eSLuis R. Rodriguez static enum reg_request_treatment 2827b23e7a9eSLuis R. Rodriguez __reg_process_hint_country_ie(struct wiphy *wiphy, 2828b23e7a9eSLuis R. Rodriguez struct regulatory_request *country_ie_request) 2829b23e7a9eSLuis R. Rodriguez { 2830b23e7a9eSLuis R. Rodriguez struct wiphy *last_wiphy = NULL; 2831b23e7a9eSLuis R. Rodriguez struct regulatory_request *lr = get_last_request(); 2832b23e7a9eSLuis R. Rodriguez 2833b23e7a9eSLuis R. Rodriguez if (reg_request_cell_base(lr)) { 2834b23e7a9eSLuis R. Rodriguez /* Trust a Cell base station over the AP's country IE */ 2835b23e7a9eSLuis R. Rodriguez if (regdom_changes(country_ie_request->alpha2)) 2836b23e7a9eSLuis R. Rodriguez return REG_REQ_IGNORE; 2837b23e7a9eSLuis R. Rodriguez return REG_REQ_ALREADY_SET; 28382a901468SLuis R. Rodriguez } else { 28392a901468SLuis R. Rodriguez if (wiphy->regulatory_flags & REGULATORY_COUNTRY_IE_IGNORE) 28402a901468SLuis R. Rodriguez return REG_REQ_IGNORE; 2841b23e7a9eSLuis R. Rodriguez } 2842b23e7a9eSLuis R. Rodriguez 2843b23e7a9eSLuis R. Rodriguez if (unlikely(!is_an_alpha2(country_ie_request->alpha2))) 2844b23e7a9eSLuis R. Rodriguez return -EINVAL; 28452f1c6c57SLuis R. Rodriguez 28462f1c6c57SLuis R. Rodriguez if (lr->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE) 28472f1c6c57SLuis R. Rodriguez return REG_REQ_OK; 28482f1c6c57SLuis R. Rodriguez 28492f1c6c57SLuis R. Rodriguez last_wiphy = wiphy_idx_to_wiphy(lr->wiphy_idx); 28502f1c6c57SLuis R. Rodriguez 2851b23e7a9eSLuis R. Rodriguez if (last_wiphy != wiphy) { 2852b23e7a9eSLuis R. Rodriguez /* 2853b23e7a9eSLuis R. Rodriguez * Two cards with two APs claiming different 2854b23e7a9eSLuis R. Rodriguez * Country IE alpha2s. We could 2855b23e7a9eSLuis R. Rodriguez * intersect them, but that seems unlikely 2856b23e7a9eSLuis R. Rodriguez * to be correct. Reject second one for now. 2857b23e7a9eSLuis R. Rodriguez */ 2858b23e7a9eSLuis R. Rodriguez if (regdom_changes(country_ie_request->alpha2)) 2859b23e7a9eSLuis R. Rodriguez return REG_REQ_IGNORE; 2860b23e7a9eSLuis R. Rodriguez return REG_REQ_ALREADY_SET; 2861b23e7a9eSLuis R. Rodriguez } 286270dcec5aSEmmanuel Grumbach 286370dcec5aSEmmanuel Grumbach if (regdom_changes(country_ie_request->alpha2)) 2864b23e7a9eSLuis R. Rodriguez return REG_REQ_OK; 2865b23e7a9eSLuis R. Rodriguez return REG_REQ_ALREADY_SET; 2866b23e7a9eSLuis R. Rodriguez } 2867b23e7a9eSLuis R. Rodriguez 2868b3eb7f3fSLuis R. Rodriguez /** 2869b23e7a9eSLuis R. Rodriguez * reg_process_hint_country_ie - process regulatory requests from country IEs 2870726e6af9SAndrew Lunn * @wiphy: the wireless device for the regulatory request 2871b23e7a9eSLuis R. Rodriguez * @country_ie_request: a regulatory request from a country IE 2872d1c96a9aSLuis R. Rodriguez * 2873b23e7a9eSLuis R. Rodriguez * The wireless subsystem can use this function to process 2874b23e7a9eSLuis R. Rodriguez * a regulatory request issued by a country Information Element. 2875d1c96a9aSLuis R. Rodriguez * 28762f92212bSJohannes Berg * Returns one of the different reg request treatment values. 2877d1c96a9aSLuis R. Rodriguez */ 28782f92212bSJohannes Berg static enum reg_request_treatment 2879b23e7a9eSLuis R. Rodriguez reg_process_hint_country_ie(struct wiphy *wiphy, 2880b23e7a9eSLuis R. Rodriguez struct regulatory_request *country_ie_request) 2881b2e1b302SLuis R. Rodriguez { 28822f92212bSJohannes Berg enum reg_request_treatment treatment; 2883b2e1b302SLuis R. Rodriguez 2884b23e7a9eSLuis R. Rodriguez treatment = __reg_process_hint_country_ie(wiphy, country_ie_request); 2885761cf7ecSLuis R. Rodriguez 28862f92212bSJohannes Berg switch (treatment) { 28872f92212bSJohannes Berg case REG_REQ_OK: 28882f92212bSJohannes Berg break; 2889b23e7a9eSLuis R. Rodriguez case REG_REQ_IGNORE: 2890d34265a3SJohannes Berg return REG_REQ_IGNORE; 2891b23e7a9eSLuis R. Rodriguez case REG_REQ_ALREADY_SET: 2892c888393bSArik Nemtsov reg_free_request(country_ie_request); 2893480908a7SJohannes Berg return REG_REQ_ALREADY_SET; 2894b23e7a9eSLuis R. Rodriguez case REG_REQ_INTERSECT: 2895fb1fc7adSLuis R. Rodriguez /* 2896b23e7a9eSLuis R. Rodriguez * This doesn't happen yet, not sure we 2897b23e7a9eSLuis R. Rodriguez * ever want to support it for this case. 2898fb1fc7adSLuis R. Rodriguez */ 28998db0c433SToke Høiland-Jørgensen WARN_ONCE(1, "Unexpected intersection for country elements"); 2900d34265a3SJohannes Berg return REG_REQ_IGNORE; 2901d951c1ddSLuis R. Rodriguez } 2902b2e1b302SLuis R. Rodriguez 2903b23e7a9eSLuis R. Rodriguez country_ie_request->intersect = false; 2904b23e7a9eSLuis R. Rodriguez country_ie_request->processed = false; 29055ad6ef5eSLuis R. Rodriguez 2906d34265a3SJohannes Berg if (reg_query_database(country_ie_request)) { 290705f1a3eaSLuis R. Rodriguez reg_update_last_request(country_ie_request); 290825b20dbdSJohannes Berg return REG_REQ_OK; 2909b2e1b302SLuis R. Rodriguez } 2910b2e1b302SLuis R. Rodriguez 2911d34265a3SJohannes Berg return REG_REQ_IGNORE; 2912d34265a3SJohannes Berg } 2913d34265a3SJohannes Berg 291489766727SVasanthakumar Thiagarajan bool reg_dfs_domain_same(struct wiphy *wiphy1, struct wiphy *wiphy2) 291589766727SVasanthakumar Thiagarajan { 291689766727SVasanthakumar Thiagarajan const struct ieee80211_regdomain *wiphy1_regd = NULL; 291789766727SVasanthakumar Thiagarajan const struct ieee80211_regdomain *wiphy2_regd = NULL; 291889766727SVasanthakumar Thiagarajan const struct ieee80211_regdomain *cfg80211_regd = NULL; 291989766727SVasanthakumar Thiagarajan bool dfs_domain_same; 292089766727SVasanthakumar Thiagarajan 292189766727SVasanthakumar Thiagarajan rcu_read_lock(); 292289766727SVasanthakumar Thiagarajan 292389766727SVasanthakumar Thiagarajan cfg80211_regd = rcu_dereference(cfg80211_regdomain); 292489766727SVasanthakumar Thiagarajan wiphy1_regd = rcu_dereference(wiphy1->regd); 292589766727SVasanthakumar Thiagarajan if (!wiphy1_regd) 292689766727SVasanthakumar Thiagarajan wiphy1_regd = cfg80211_regd; 292789766727SVasanthakumar Thiagarajan 292889766727SVasanthakumar Thiagarajan wiphy2_regd = rcu_dereference(wiphy2->regd); 292989766727SVasanthakumar Thiagarajan if (!wiphy2_regd) 293089766727SVasanthakumar Thiagarajan wiphy2_regd = cfg80211_regd; 293189766727SVasanthakumar Thiagarajan 293289766727SVasanthakumar Thiagarajan dfs_domain_same = wiphy1_regd->dfs_region == wiphy2_regd->dfs_region; 293389766727SVasanthakumar Thiagarajan 293489766727SVasanthakumar Thiagarajan rcu_read_unlock(); 293589766727SVasanthakumar Thiagarajan 293689766727SVasanthakumar Thiagarajan return dfs_domain_same; 293789766727SVasanthakumar Thiagarajan } 293889766727SVasanthakumar Thiagarajan 293989766727SVasanthakumar Thiagarajan static void reg_copy_dfs_chan_state(struct ieee80211_channel *dst_chan, 294089766727SVasanthakumar Thiagarajan struct ieee80211_channel *src_chan) 294189766727SVasanthakumar Thiagarajan { 294289766727SVasanthakumar Thiagarajan if (!(dst_chan->flags & IEEE80211_CHAN_RADAR) || 294389766727SVasanthakumar Thiagarajan !(src_chan->flags & IEEE80211_CHAN_RADAR)) 294489766727SVasanthakumar Thiagarajan return; 294589766727SVasanthakumar Thiagarajan 294689766727SVasanthakumar Thiagarajan if (dst_chan->flags & IEEE80211_CHAN_DISABLED || 294789766727SVasanthakumar Thiagarajan src_chan->flags & IEEE80211_CHAN_DISABLED) 294889766727SVasanthakumar Thiagarajan return; 294989766727SVasanthakumar Thiagarajan 295089766727SVasanthakumar Thiagarajan if (src_chan->center_freq == dst_chan->center_freq && 295189766727SVasanthakumar Thiagarajan dst_chan->dfs_state == NL80211_DFS_USABLE) { 295289766727SVasanthakumar Thiagarajan dst_chan->dfs_state = src_chan->dfs_state; 295389766727SVasanthakumar Thiagarajan dst_chan->dfs_state_entered = src_chan->dfs_state_entered; 295489766727SVasanthakumar Thiagarajan } 295589766727SVasanthakumar Thiagarajan } 295689766727SVasanthakumar Thiagarajan 295789766727SVasanthakumar Thiagarajan static void wiphy_share_dfs_chan_state(struct wiphy *dst_wiphy, 295889766727SVasanthakumar Thiagarajan struct wiphy *src_wiphy) 295989766727SVasanthakumar Thiagarajan { 296089766727SVasanthakumar Thiagarajan struct ieee80211_supported_band *src_sband, *dst_sband; 296189766727SVasanthakumar Thiagarajan struct ieee80211_channel *src_chan, *dst_chan; 296289766727SVasanthakumar Thiagarajan int i, j, band; 296389766727SVasanthakumar Thiagarajan 296489766727SVasanthakumar Thiagarajan if (!reg_dfs_domain_same(dst_wiphy, src_wiphy)) 296589766727SVasanthakumar Thiagarajan return; 296689766727SVasanthakumar Thiagarajan 296789766727SVasanthakumar Thiagarajan for (band = 0; band < NUM_NL80211_BANDS; band++) { 296889766727SVasanthakumar Thiagarajan dst_sband = dst_wiphy->bands[band]; 296989766727SVasanthakumar Thiagarajan src_sband = src_wiphy->bands[band]; 297089766727SVasanthakumar Thiagarajan if (!dst_sband || !src_sband) 297189766727SVasanthakumar Thiagarajan continue; 297289766727SVasanthakumar Thiagarajan 297389766727SVasanthakumar Thiagarajan for (i = 0; i < dst_sband->n_channels; i++) { 297489766727SVasanthakumar Thiagarajan dst_chan = &dst_sband->channels[i]; 297589766727SVasanthakumar Thiagarajan for (j = 0; j < src_sband->n_channels; j++) { 297689766727SVasanthakumar Thiagarajan src_chan = &src_sband->channels[j]; 297789766727SVasanthakumar Thiagarajan reg_copy_dfs_chan_state(dst_chan, src_chan); 297889766727SVasanthakumar Thiagarajan } 297989766727SVasanthakumar Thiagarajan } 298089766727SVasanthakumar Thiagarajan } 298189766727SVasanthakumar Thiagarajan } 298289766727SVasanthakumar Thiagarajan 298389766727SVasanthakumar Thiagarajan static void wiphy_all_share_dfs_chan_state(struct wiphy *wiphy) 298489766727SVasanthakumar Thiagarajan { 298589766727SVasanthakumar Thiagarajan struct cfg80211_registered_device *rdev; 298689766727SVasanthakumar Thiagarajan 298789766727SVasanthakumar Thiagarajan ASSERT_RTNL(); 298889766727SVasanthakumar Thiagarajan 298989766727SVasanthakumar Thiagarajan list_for_each_entry(rdev, &cfg80211_rdev_list, list) { 299089766727SVasanthakumar Thiagarajan if (wiphy == &rdev->wiphy) 299189766727SVasanthakumar Thiagarajan continue; 299289766727SVasanthakumar Thiagarajan wiphy_share_dfs_chan_state(wiphy, &rdev->wiphy); 299389766727SVasanthakumar Thiagarajan } 299489766727SVasanthakumar Thiagarajan } 299589766727SVasanthakumar Thiagarajan 299630a548c7SLuis R. Rodriguez /* This processes *all* regulatory hints */ 29971daa37c7SLuis R. Rodriguez static void reg_process_hint(struct regulatory_request *reg_request) 2998fe33eb39SLuis R. Rodriguez { 2999fe33eb39SLuis R. Rodriguez struct wiphy *wiphy = NULL; 3000b3eb7f3fSLuis R. Rodriguez enum reg_request_treatment treatment; 30011db58529SYu Zhao enum nl80211_reg_initiator initiator = reg_request->initiator; 3002fe33eb39SLuis R. Rodriguez 3003f4173766SJohannes Berg if (reg_request->wiphy_idx != WIPHY_IDX_INVALID) 3004fe33eb39SLuis R. Rodriguez wiphy = wiphy_idx_to_wiphy(reg_request->wiphy_idx); 3005fe33eb39SLuis R. Rodriguez 30061db58529SYu Zhao switch (initiator) { 3007b3eb7f3fSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_CORE: 3008d34265a3SJohannes Berg treatment = reg_process_hint_core(reg_request); 3009d34265a3SJohannes Berg break; 3010b3eb7f3fSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_USER: 3011d34265a3SJohannes Berg treatment = reg_process_hint_user(reg_request); 3012d34265a3SJohannes Berg break; 3013b3eb7f3fSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_DRIVER: 3014772f0389SIlan Peer if (!wiphy) 3015772f0389SIlan Peer goto out_free; 301621636c7fSLuis R. Rodriguez treatment = reg_process_hint_driver(wiphy, reg_request); 301721636c7fSLuis R. Rodriguez break; 3018b3eb7f3fSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_COUNTRY_IE: 3019772f0389SIlan Peer if (!wiphy) 3020772f0389SIlan Peer goto out_free; 3021b23e7a9eSLuis R. Rodriguez treatment = reg_process_hint_country_ie(wiphy, reg_request); 3022b3eb7f3fSLuis R. Rodriguez break; 3023b3eb7f3fSLuis R. Rodriguez default: 30241db58529SYu Zhao WARN(1, "invalid initiator %d\n", initiator); 3025772f0389SIlan Peer goto out_free; 3026b3eb7f3fSLuis R. Rodriguez } 3027b3eb7f3fSLuis R. Rodriguez 3028d34265a3SJohannes Berg if (treatment == REG_REQ_IGNORE) 3029d34265a3SJohannes Berg goto out_free; 3030d34265a3SJohannes Berg 3031480908a7SJohannes Berg WARN(treatment != REG_REQ_OK && treatment != REG_REQ_ALREADY_SET, 3032480908a7SJohannes Berg "unexpected treatment value %d\n", treatment); 3033480908a7SJohannes Berg 3034841b351cSJohn Linville /* This is required so that the orig_* parameters are saved. 3035841b351cSJohn Linville * NOTE: treatment must be set for any case that reaches here! 3036841b351cSJohn Linville */ 3037b23e7a9eSLuis R. Rodriguez if (treatment == REG_REQ_ALREADY_SET && wiphy && 3038ad932f04SArik Nemtsov wiphy->regulatory_flags & REGULATORY_STRICT_REG) { 30391db58529SYu Zhao wiphy_update_regulatory(wiphy, initiator); 304089766727SVasanthakumar Thiagarajan wiphy_all_share_dfs_chan_state(wiphy); 3041ad932f04SArik Nemtsov reg_check_channels(); 3042ad932f04SArik Nemtsov } 3043772f0389SIlan Peer 3044772f0389SIlan Peer return; 3045772f0389SIlan Peer 3046772f0389SIlan Peer out_free: 3047c888393bSArik Nemtsov reg_free_request(reg_request); 3048fe33eb39SLuis R. Rodriguez } 3049fe33eb39SLuis R. Rodriguez 3050aced43ceSAmar Singhal static void notify_self_managed_wiphys(struct regulatory_request *request) 3051aced43ceSAmar Singhal { 3052aced43ceSAmar Singhal struct cfg80211_registered_device *rdev; 3053aced43ceSAmar Singhal struct wiphy *wiphy; 3054aced43ceSAmar Singhal 3055aced43ceSAmar Singhal list_for_each_entry(rdev, &cfg80211_rdev_list, list) { 3056aced43ceSAmar Singhal wiphy = &rdev->wiphy; 3057aced43ceSAmar Singhal if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED && 3058c82c06ceSSriram R request->initiator == NL80211_REGDOM_SET_BY_USER) 3059aced43ceSAmar Singhal reg_call_notifier(wiphy, request); 3060aced43ceSAmar Singhal } 3061aced43ceSAmar Singhal } 3062aced43ceSAmar Singhal 3063b2e253cfSLuis R. Rodriguez /* 3064b2e253cfSLuis R. Rodriguez * Processes regulatory hints, this is all the NL80211_REGDOM_SET_BY_* 3065b2e253cfSLuis R. Rodriguez * Regulatory hints come on a first come first serve basis and we 3066b2e253cfSLuis R. Rodriguez * must process each one atomically. 3067b2e253cfSLuis R. Rodriguez */ 3068fe33eb39SLuis R. Rodriguez static void reg_process_pending_hints(void) 3069fe33eb39SLuis R. Rodriguez { 3070c492db37SJohannes Berg struct regulatory_request *reg_request, *lr; 3071fe33eb39SLuis R. Rodriguez 3072c492db37SJohannes Berg lr = get_last_request(); 3073b0e2880bSLuis R. Rodriguez 3074b2e253cfSLuis R. Rodriguez /* When last_request->processed becomes true this will be rescheduled */ 3075c492db37SJohannes Berg if (lr && !lr->processed) { 30760d31d4dbSHodaszi, Robert pr_debug("Pending regulatory request, waiting for it to be processed...\n"); 30775fe231e8SJohannes Berg return; 3078b2e253cfSLuis R. Rodriguez } 3079b2e253cfSLuis R. Rodriguez 3080fe33eb39SLuis R. Rodriguez spin_lock(®_requests_lock); 3081b2e253cfSLuis R. Rodriguez 3082b2e253cfSLuis R. Rodriguez if (list_empty(®_requests_list)) { 3083b2e253cfSLuis R. Rodriguez spin_unlock(®_requests_lock); 30845fe231e8SJohannes Berg return; 3085b2e253cfSLuis R. Rodriguez } 3086b2e253cfSLuis R. Rodriguez 3087fe33eb39SLuis R. Rodriguez reg_request = list_first_entry(®_requests_list, 3088fe33eb39SLuis R. Rodriguez struct regulatory_request, 3089fe33eb39SLuis R. Rodriguez list); 3090fe33eb39SLuis R. Rodriguez list_del_init(®_request->list); 3091fe33eb39SLuis R. Rodriguez 3092d951c1ddSLuis R. Rodriguez spin_unlock(®_requests_lock); 3093b0e2880bSLuis R. Rodriguez 3094aced43ceSAmar Singhal notify_self_managed_wiphys(reg_request); 3095ef51fb1dSArik Nemtsov 30961daa37c7SLuis R. Rodriguez reg_process_hint(reg_request); 30972e54a689SBen 30982e54a689SBen lr = get_last_request(); 30992e54a689SBen 31002e54a689SBen spin_lock(®_requests_lock); 31012e54a689SBen if (!list_empty(®_requests_list) && lr && lr->processed) 31022e54a689SBen schedule_work(®_work); 31032e54a689SBen spin_unlock(®_requests_lock); 3104fe33eb39SLuis R. Rodriguez } 3105fe33eb39SLuis R. Rodriguez 3106e38f8a7aSLuis R. Rodriguez /* Processes beacon hints -- this has nothing to do with country IEs */ 3107e38f8a7aSLuis R. Rodriguez static void reg_process_pending_beacon_hints(void) 3108e38f8a7aSLuis R. Rodriguez { 310979c97e97SJohannes Berg struct cfg80211_registered_device *rdev; 3110e38f8a7aSLuis R. Rodriguez struct reg_beacon *pending_beacon, *tmp; 3111e38f8a7aSLuis R. Rodriguez 3112e38f8a7aSLuis R. Rodriguez /* This goes through the _pending_ beacon list */ 3113e38f8a7aSLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 3114e38f8a7aSLuis R. Rodriguez 3115e38f8a7aSLuis R. Rodriguez list_for_each_entry_safe(pending_beacon, tmp, 3116e38f8a7aSLuis R. Rodriguez ®_pending_beacons, list) { 3117e38f8a7aSLuis R. Rodriguez list_del_init(&pending_beacon->list); 3118e38f8a7aSLuis R. Rodriguez 3119e38f8a7aSLuis R. Rodriguez /* Applies the beacon hint to current wiphys */ 312079c97e97SJohannes Berg list_for_each_entry(rdev, &cfg80211_rdev_list, list) 312179c97e97SJohannes Berg wiphy_update_new_beacon(&rdev->wiphy, pending_beacon); 3122e38f8a7aSLuis R. Rodriguez 3123e38f8a7aSLuis R. Rodriguez /* Remembers the beacon hint for new wiphys or reg changes */ 3124e38f8a7aSLuis R. Rodriguez list_add_tail(&pending_beacon->list, ®_beacon_list); 3125e38f8a7aSLuis R. Rodriguez } 3126e38f8a7aSLuis R. Rodriguez 3127e38f8a7aSLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 3128e38f8a7aSLuis R. Rodriguez } 3129e38f8a7aSLuis R. Rodriguez 3130a05829a7SJohannes Berg static void reg_process_self_managed_hint(struct wiphy *wiphy) 3131b0d7aa59SJonathan Doron { 3132a05829a7SJohannes Berg struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); 3133b0d7aa59SJonathan Doron const struct ieee80211_regdomain *tmp; 3134b0d7aa59SJonathan Doron const struct ieee80211_regdomain *regd; 313557fbcce3SJohannes Berg enum nl80211_band band; 3136b0d7aa59SJonathan Doron struct regulatory_request request = {}; 3137b0d7aa59SJonathan Doron 3138a05829a7SJohannes Berg ASSERT_RTNL(); 3139a05829a7SJohannes Berg lockdep_assert_wiphy(wiphy); 3140b0d7aa59SJonathan Doron 3141b0d7aa59SJonathan Doron spin_lock(®_requests_lock); 3142b0d7aa59SJonathan Doron regd = rdev->requested_regd; 3143b0d7aa59SJonathan Doron rdev->requested_regd = NULL; 3144b0d7aa59SJonathan Doron spin_unlock(®_requests_lock); 3145b0d7aa59SJonathan Doron 3146a05829a7SJohannes Berg if (!regd) 3147a05829a7SJohannes Berg return; 3148b0d7aa59SJonathan Doron 3149b0d7aa59SJonathan Doron tmp = get_wiphy_regdom(wiphy); 3150b0d7aa59SJonathan Doron rcu_assign_pointer(wiphy->regd, regd); 3151b0d7aa59SJonathan Doron rcu_free_regdom(tmp); 3152b0d7aa59SJonathan Doron 315357fbcce3SJohannes Berg for (band = 0; band < NUM_NL80211_BANDS; band++) 3154b0d7aa59SJonathan Doron handle_band_custom(wiphy, wiphy->bands[band], regd); 3155b0d7aa59SJonathan Doron 3156b0d7aa59SJonathan Doron reg_process_ht_flags(wiphy); 3157b0d7aa59SJonathan Doron 3158b0d7aa59SJonathan Doron request.wiphy_idx = get_wiphy_idx(wiphy); 3159b0d7aa59SJonathan Doron request.alpha2[0] = regd->alpha2[0]; 3160b0d7aa59SJonathan Doron request.alpha2[1] = regd->alpha2[1]; 3161b0d7aa59SJonathan Doron request.initiator = NL80211_REGDOM_SET_BY_DRIVER; 3162b0d7aa59SJonathan Doron 3163d99975c4SWen Gong if (wiphy->flags & WIPHY_FLAG_NOTIFY_REGDOM_BY_DRIVER) 3164d99975c4SWen Gong reg_call_notifier(wiphy, &request); 3165d99975c4SWen Gong 3166b0d7aa59SJonathan Doron nl80211_send_wiphy_reg_change_event(&request); 3167b0d7aa59SJonathan Doron } 3168b0d7aa59SJonathan Doron 3169a05829a7SJohannes Berg static void reg_process_self_managed_hints(void) 3170a05829a7SJohannes Berg { 3171a05829a7SJohannes Berg struct cfg80211_registered_device *rdev; 3172a05829a7SJohannes Berg 3173a05829a7SJohannes Berg ASSERT_RTNL(); 3174a05829a7SJohannes Berg 3175a05829a7SJohannes Berg list_for_each_entry(rdev, &cfg80211_rdev_list, list) { 3176a05829a7SJohannes Berg wiphy_lock(&rdev->wiphy); 3177a05829a7SJohannes Berg reg_process_self_managed_hint(&rdev->wiphy); 3178a05829a7SJohannes Berg wiphy_unlock(&rdev->wiphy); 3179a05829a7SJohannes Berg } 3180a05829a7SJohannes Berg 3181b0d7aa59SJonathan Doron reg_check_channels(); 3182b0d7aa59SJonathan Doron } 3183b0d7aa59SJonathan Doron 3184fe33eb39SLuis R. Rodriguez static void reg_todo(struct work_struct *work) 3185fe33eb39SLuis R. Rodriguez { 31865fe231e8SJohannes Berg rtnl_lock(); 3187fe33eb39SLuis R. Rodriguez reg_process_pending_hints(); 3188e38f8a7aSLuis R. Rodriguez reg_process_pending_beacon_hints(); 3189b0d7aa59SJonathan Doron reg_process_self_managed_hints(); 31905fe231e8SJohannes Berg rtnl_unlock(); 3191fe33eb39SLuis R. Rodriguez } 3192fe33eb39SLuis R. Rodriguez 3193fe33eb39SLuis R. Rodriguez static void queue_regulatory_request(struct regulatory_request *request) 3194fe33eb39SLuis R. Rodriguez { 3195c61029c7SJohn W. Linville request->alpha2[0] = toupper(request->alpha2[0]); 3196c61029c7SJohn W. Linville request->alpha2[1] = toupper(request->alpha2[1]); 3197c61029c7SJohn W. Linville 3198fe33eb39SLuis R. Rodriguez spin_lock(®_requests_lock); 3199fe33eb39SLuis R. Rodriguez list_add_tail(&request->list, ®_requests_list); 3200fe33eb39SLuis R. Rodriguez spin_unlock(®_requests_lock); 3201fe33eb39SLuis R. Rodriguez 3202fe33eb39SLuis R. Rodriguez schedule_work(®_work); 3203fe33eb39SLuis R. Rodriguez } 3204fe33eb39SLuis R. Rodriguez 320509d989d1SLuis R. Rodriguez /* 320609d989d1SLuis R. Rodriguez * Core regulatory hint -- happens during cfg80211_init() 320709d989d1SLuis R. Rodriguez * and when we restore regulatory settings. 320809d989d1SLuis R. Rodriguez */ 3209ba25c141SLuis R. Rodriguez static int regulatory_hint_core(const char *alpha2) 3210ba25c141SLuis R. Rodriguez { 3211ba25c141SLuis R. Rodriguez struct regulatory_request *request; 3212ba25c141SLuis R. Rodriguez 32131a919318SJohannes Berg request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL); 3214ba25c141SLuis R. Rodriguez if (!request) 3215ba25c141SLuis R. Rodriguez return -ENOMEM; 3216ba25c141SLuis R. Rodriguez 3217ba25c141SLuis R. Rodriguez request->alpha2[0] = alpha2[0]; 3218ba25c141SLuis R. Rodriguez request->alpha2[1] = alpha2[1]; 32197db90f4aSLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_CORE; 322024f33e64SAndrei Otcheretianski request->wiphy_idx = WIPHY_IDX_INVALID; 3221ba25c141SLuis R. Rodriguez 322231e99729SLuis R. Rodriguez queue_regulatory_request(request); 32235078b2e3SLuis R. Rodriguez 3224fe33eb39SLuis R. Rodriguez return 0; 3225ba25c141SLuis R. Rodriguez } 3226ba25c141SLuis R. Rodriguez 3227fe33eb39SLuis R. Rodriguez /* User hints */ 322857b5ce07SLuis R. Rodriguez int regulatory_hint_user(const char *alpha2, 322957b5ce07SLuis R. Rodriguez enum nl80211_user_reg_hint_type user_reg_hint_type) 3230b2e1b302SLuis R. Rodriguez { 3231fe33eb39SLuis R. Rodriguez struct regulatory_request *request; 3232fe33eb39SLuis R. Rodriguez 3233fdc9d7b2SJohannes Berg if (WARN_ON(!alpha2)) 3234fdc9d7b2SJohannes Berg return -EINVAL; 3235b2e1b302SLuis R. Rodriguez 323647caf685SJohannes Berg if (!is_world_regdom(alpha2) && !is_an_alpha2(alpha2)) 323747caf685SJohannes Berg return -EINVAL; 323847caf685SJohannes Berg 3239fe33eb39SLuis R. Rodriguez request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL); 3240fe33eb39SLuis R. Rodriguez if (!request) 3241fe33eb39SLuis R. Rodriguez return -ENOMEM; 3242fe33eb39SLuis R. Rodriguez 3243f4173766SJohannes Berg request->wiphy_idx = WIPHY_IDX_INVALID; 3244fe33eb39SLuis R. Rodriguez request->alpha2[0] = alpha2[0]; 3245fe33eb39SLuis R. Rodriguez request->alpha2[1] = alpha2[1]; 3246e12822e1SLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_USER; 324757b5ce07SLuis R. Rodriguez request->user_reg_hint_type = user_reg_hint_type; 3248fe33eb39SLuis R. Rodriguez 3249c37722bdSIlan peer /* Allow calling CRDA again */ 3250b6863036SJohannes Berg reset_crda_timeouts(); 3251c37722bdSIlan peer 3252fe33eb39SLuis R. Rodriguez queue_regulatory_request(request); 3253fe33eb39SLuis R. Rodriguez 3254fe33eb39SLuis R. Rodriguez return 0; 3255fe33eb39SLuis R. Rodriguez } 3256fe33eb39SLuis R. Rodriguez 325705050753SIlan peer int regulatory_hint_indoor(bool is_indoor, u32 portid) 325852616f2bSIlan Peer { 325905050753SIlan peer spin_lock(®_indoor_lock); 326052616f2bSIlan Peer 326105050753SIlan peer /* It is possible that more than one user space process is trying to 326205050753SIlan peer * configure the indoor setting. To handle such cases, clear the indoor 326305050753SIlan peer * setting in case that some process does not think that the device 326405050753SIlan peer * is operating in an indoor environment. In addition, if a user space 326505050753SIlan peer * process indicates that it is controlling the indoor setting, save its 326605050753SIlan peer * portid, i.e., make it the owner. 326705050753SIlan peer */ 326805050753SIlan peer reg_is_indoor = is_indoor; 326905050753SIlan peer if (reg_is_indoor) { 327005050753SIlan peer if (!reg_is_indoor_portid) 327105050753SIlan peer reg_is_indoor_portid = portid; 327205050753SIlan peer } else { 327305050753SIlan peer reg_is_indoor_portid = 0; 327405050753SIlan peer } 327552616f2bSIlan Peer 327605050753SIlan peer spin_unlock(®_indoor_lock); 327705050753SIlan peer 327805050753SIlan peer if (!is_indoor) 327905050753SIlan peer reg_check_channels(); 328052616f2bSIlan Peer 328152616f2bSIlan Peer return 0; 328252616f2bSIlan Peer } 328352616f2bSIlan Peer 328405050753SIlan peer void regulatory_netlink_notify(u32 portid) 328505050753SIlan peer { 328605050753SIlan peer spin_lock(®_indoor_lock); 328705050753SIlan peer 328805050753SIlan peer if (reg_is_indoor_portid != portid) { 328905050753SIlan peer spin_unlock(®_indoor_lock); 329005050753SIlan peer return; 329105050753SIlan peer } 329205050753SIlan peer 329305050753SIlan peer reg_is_indoor = false; 329405050753SIlan peer reg_is_indoor_portid = 0; 329505050753SIlan peer 329605050753SIlan peer spin_unlock(®_indoor_lock); 329705050753SIlan peer 329805050753SIlan peer reg_check_channels(); 329905050753SIlan peer } 330005050753SIlan peer 3301fe33eb39SLuis R. Rodriguez /* Driver hints */ 3302fe33eb39SLuis R. Rodriguez int regulatory_hint(struct wiphy *wiphy, const char *alpha2) 3303fe33eb39SLuis R. Rodriguez { 3304fe33eb39SLuis R. Rodriguez struct regulatory_request *request; 3305fe33eb39SLuis R. Rodriguez 3306fdc9d7b2SJohannes Berg if (WARN_ON(!alpha2 || !wiphy)) 3307fdc9d7b2SJohannes Berg return -EINVAL; 3308fe33eb39SLuis R. Rodriguez 33094f7b9140SLuis R. Rodriguez wiphy->regulatory_flags &= ~REGULATORY_CUSTOM_REG; 33104f7b9140SLuis R. Rodriguez 3311fe33eb39SLuis R. Rodriguez request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL); 3312fe33eb39SLuis R. Rodriguez if (!request) 3313fe33eb39SLuis R. Rodriguez return -ENOMEM; 3314fe33eb39SLuis R. Rodriguez 3315fe33eb39SLuis R. Rodriguez request->wiphy_idx = get_wiphy_idx(wiphy); 3316fe33eb39SLuis R. Rodriguez 3317fe33eb39SLuis R. Rodriguez request->alpha2[0] = alpha2[0]; 3318fe33eb39SLuis R. Rodriguez request->alpha2[1] = alpha2[1]; 33197db90f4aSLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_DRIVER; 3320fe33eb39SLuis R. Rodriguez 3321c37722bdSIlan peer /* Allow calling CRDA again */ 3322b6863036SJohannes Berg reset_crda_timeouts(); 3323c37722bdSIlan peer 3324fe33eb39SLuis R. Rodriguez queue_regulatory_request(request); 3325fe33eb39SLuis R. Rodriguez 3326fe33eb39SLuis R. Rodriguez return 0; 3327b2e1b302SLuis R. Rodriguez } 3328b2e1b302SLuis R. Rodriguez EXPORT_SYMBOL(regulatory_hint); 3329b2e1b302SLuis R. Rodriguez 333057fbcce3SJohannes Berg void regulatory_hint_country_ie(struct wiphy *wiphy, enum nl80211_band band, 33311a919318SJohannes Berg const u8 *country_ie, u8 country_ie_len) 33323f2355cbSLuis R. Rodriguez { 33333f2355cbSLuis R. Rodriguez char alpha2[2]; 33343f2355cbSLuis R. Rodriguez enum environment_cap env = ENVIRON_ANY; 3335db2424c5SJohannes Berg struct regulatory_request *request = NULL, *lr; 3336d335fe63SLuis R. Rodriguez 33373f2355cbSLuis R. Rodriguez /* IE len must be evenly divisible by 2 */ 33383f2355cbSLuis R. Rodriguez if (country_ie_len & 0x01) 3339db2424c5SJohannes Berg return; 33403f2355cbSLuis R. Rodriguez 33413f2355cbSLuis R. Rodriguez if (country_ie_len < IEEE80211_COUNTRY_IE_MIN_LEN) 3342db2424c5SJohannes Berg return; 3343db2424c5SJohannes Berg 3344db2424c5SJohannes Berg request = kzalloc(sizeof(*request), GFP_KERNEL); 3345db2424c5SJohannes Berg if (!request) 3346db2424c5SJohannes Berg return; 33473f2355cbSLuis R. Rodriguez 33483f2355cbSLuis R. Rodriguez alpha2[0] = country_ie[0]; 33493f2355cbSLuis R. Rodriguez alpha2[1] = country_ie[1]; 33503f2355cbSLuis R. Rodriguez 33513f2355cbSLuis R. Rodriguez if (country_ie[2] == 'I') 33523f2355cbSLuis R. Rodriguez env = ENVIRON_INDOOR; 33533f2355cbSLuis R. Rodriguez else if (country_ie[2] == 'O') 33543f2355cbSLuis R. Rodriguez env = ENVIRON_OUTDOOR; 33553f2355cbSLuis R. Rodriguez 3356db2424c5SJohannes Berg rcu_read_lock(); 3357db2424c5SJohannes Berg lr = get_last_request(); 3358db2424c5SJohannes Berg 3359db2424c5SJohannes Berg if (unlikely(!lr)) 3360db2424c5SJohannes Berg goto out; 3361db2424c5SJohannes Berg 3362fb1fc7adSLuis R. Rodriguez /* 33638b19e6caSLuis R. Rodriguez * We will run this only upon a successful connection on cfg80211. 33644b44c8bcSLuis R. Rodriguez * We leave conflict resolution to the workqueue, where can hold 33655fe231e8SJohannes Berg * the RTNL. 3366fb1fc7adSLuis R. Rodriguez */ 3367c492db37SJohannes Berg if (lr->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE && 3368c492db37SJohannes Berg lr->wiphy_idx != WIPHY_IDX_INVALID) 33693f2355cbSLuis R. Rodriguez goto out; 33703f2355cbSLuis R. Rodriguez 3371fe33eb39SLuis R. Rodriguez request->wiphy_idx = get_wiphy_idx(wiphy); 33724f366c5dSJohn W. Linville request->alpha2[0] = alpha2[0]; 33734f366c5dSJohn W. Linville request->alpha2[1] = alpha2[1]; 33747db90f4aSLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_COUNTRY_IE; 3375fe33eb39SLuis R. Rodriguez request->country_ie_env = env; 33763f2355cbSLuis R. Rodriguez 3377c37722bdSIlan peer /* Allow calling CRDA again */ 3378b6863036SJohannes Berg reset_crda_timeouts(); 3379c37722bdSIlan peer 3380fe33eb39SLuis R. Rodriguez queue_regulatory_request(request); 3381db2424c5SJohannes Berg request = NULL; 33823f2355cbSLuis R. Rodriguez out: 3383db2424c5SJohannes Berg kfree(request); 3384db2424c5SJohannes Berg rcu_read_unlock(); 33853f2355cbSLuis R. Rodriguez } 3386b2e1b302SLuis R. Rodriguez 338709d989d1SLuis R. Rodriguez static void restore_alpha2(char *alpha2, bool reset_user) 338809d989d1SLuis R. Rodriguez { 338909d989d1SLuis R. Rodriguez /* indicates there is no alpha2 to consider for restoration */ 339009d989d1SLuis R. Rodriguez alpha2[0] = '9'; 339109d989d1SLuis R. Rodriguez alpha2[1] = '7'; 339209d989d1SLuis R. Rodriguez 339309d989d1SLuis R. Rodriguez /* The user setting has precedence over the module parameter */ 339409d989d1SLuis R. Rodriguez if (is_user_regdom_saved()) { 339509d989d1SLuis R. Rodriguez /* Unless we're asked to ignore it and reset it */ 339609d989d1SLuis R. Rodriguez if (reset_user) { 3397c799ba6eSJohannes Berg pr_debug("Restoring regulatory settings including user preference\n"); 339809d989d1SLuis R. Rodriguez user_alpha2[0] = '9'; 339909d989d1SLuis R. Rodriguez user_alpha2[1] = '7'; 340009d989d1SLuis R. Rodriguez 340109d989d1SLuis R. Rodriguez /* 340209d989d1SLuis R. Rodriguez * If we're ignoring user settings, we still need to 340309d989d1SLuis R. Rodriguez * check the module parameter to ensure we put things 340409d989d1SLuis R. Rodriguez * back as they were for a full restore. 340509d989d1SLuis R. Rodriguez */ 340609d989d1SLuis R. Rodriguez if (!is_world_regdom(ieee80211_regdom)) { 3407c799ba6eSJohannes Berg pr_debug("Keeping preference on module parameter ieee80211_regdom: %c%c\n", 34081a919318SJohannes Berg ieee80211_regdom[0], ieee80211_regdom[1]); 340909d989d1SLuis R. Rodriguez alpha2[0] = ieee80211_regdom[0]; 341009d989d1SLuis R. Rodriguez alpha2[1] = ieee80211_regdom[1]; 341109d989d1SLuis R. Rodriguez } 341209d989d1SLuis R. Rodriguez } else { 3413c799ba6eSJohannes Berg pr_debug("Restoring regulatory settings while preserving user preference for: %c%c\n", 34141a919318SJohannes Berg user_alpha2[0], user_alpha2[1]); 341509d989d1SLuis R. Rodriguez alpha2[0] = user_alpha2[0]; 341609d989d1SLuis R. Rodriguez alpha2[1] = user_alpha2[1]; 341709d989d1SLuis R. Rodriguez } 341809d989d1SLuis R. Rodriguez } else if (!is_world_regdom(ieee80211_regdom)) { 3419c799ba6eSJohannes Berg pr_debug("Keeping preference on module parameter ieee80211_regdom: %c%c\n", 34201a919318SJohannes Berg ieee80211_regdom[0], ieee80211_regdom[1]); 342109d989d1SLuis R. Rodriguez alpha2[0] = ieee80211_regdom[0]; 342209d989d1SLuis R. Rodriguez alpha2[1] = ieee80211_regdom[1]; 342309d989d1SLuis R. Rodriguez } else 3424c799ba6eSJohannes Berg pr_debug("Restoring regulatory settings\n"); 342509d989d1SLuis R. Rodriguez } 342609d989d1SLuis R. Rodriguez 34275ce543d1SRajkumar Manoharan static void restore_custom_reg_settings(struct wiphy *wiphy) 34285ce543d1SRajkumar Manoharan { 34295ce543d1SRajkumar Manoharan struct ieee80211_supported_band *sband; 343057fbcce3SJohannes Berg enum nl80211_band band; 34315ce543d1SRajkumar Manoharan struct ieee80211_channel *chan; 34325ce543d1SRajkumar Manoharan int i; 34335ce543d1SRajkumar Manoharan 343457fbcce3SJohannes Berg for (band = 0; band < NUM_NL80211_BANDS; band++) { 34355ce543d1SRajkumar Manoharan sband = wiphy->bands[band]; 34365ce543d1SRajkumar Manoharan if (!sband) 34375ce543d1SRajkumar Manoharan continue; 34385ce543d1SRajkumar Manoharan for (i = 0; i < sband->n_channels; i++) { 34395ce543d1SRajkumar Manoharan chan = &sband->channels[i]; 34405ce543d1SRajkumar Manoharan chan->flags = chan->orig_flags; 34415ce543d1SRajkumar Manoharan chan->max_antenna_gain = chan->orig_mag; 34425ce543d1SRajkumar Manoharan chan->max_power = chan->orig_mpwr; 3443899852afSPaul Stewart chan->beacon_found = false; 34445ce543d1SRajkumar Manoharan } 34455ce543d1SRajkumar Manoharan } 34465ce543d1SRajkumar Manoharan } 34475ce543d1SRajkumar Manoharan 344809d989d1SLuis R. Rodriguez /* 3449f2e30931SBhaskar Chowdhury * Restoring regulatory settings involves ignoring any 345009d989d1SLuis R. Rodriguez * possibly stale country IE information and user regulatory 345109d989d1SLuis R. Rodriguez * settings if so desired, this includes any beacon hints 345209d989d1SLuis R. Rodriguez * learned as we could have traveled outside to another country 345309d989d1SLuis R. Rodriguez * after disconnection. To restore regulatory settings we do 345409d989d1SLuis R. Rodriguez * exactly what we did at bootup: 345509d989d1SLuis R. Rodriguez * 345609d989d1SLuis R. Rodriguez * - send a core regulatory hint 345709d989d1SLuis R. Rodriguez * - send a user regulatory hint if applicable 345809d989d1SLuis R. Rodriguez * 345909d989d1SLuis R. Rodriguez * Device drivers that send a regulatory hint for a specific country 3460cc5a639bSRandy Dunlap * keep their own regulatory domain on wiphy->regd so that does 346109d989d1SLuis R. Rodriguez * not need to be remembered. 346209d989d1SLuis R. Rodriguez */ 3463e646a025SJohannes Berg static void restore_regulatory_settings(bool reset_user, bool cached) 346409d989d1SLuis R. Rodriguez { 346509d989d1SLuis R. Rodriguez char alpha2[2]; 3466cee0bec5SDmitry Shmidt char world_alpha2[2]; 346709d989d1SLuis R. Rodriguez struct reg_beacon *reg_beacon, *btmp; 346814609555SLuis R. Rodriguez LIST_HEAD(tmp_reg_req_list); 34695ce543d1SRajkumar Manoharan struct cfg80211_registered_device *rdev; 347009d989d1SLuis R. Rodriguez 34715fe231e8SJohannes Berg ASSERT_RTNL(); 34725fe231e8SJohannes Berg 347305050753SIlan peer /* 347405050753SIlan peer * Clear the indoor setting in case that it is not controlled by user 347505050753SIlan peer * space, as otherwise there is no guarantee that the device is still 347605050753SIlan peer * operating in an indoor environment. 347705050753SIlan peer */ 347805050753SIlan peer spin_lock(®_indoor_lock); 347905050753SIlan peer if (reg_is_indoor && !reg_is_indoor_portid) { 348052616f2bSIlan Peer reg_is_indoor = false; 348105050753SIlan peer reg_check_channels(); 348205050753SIlan peer } 348305050753SIlan peer spin_unlock(®_indoor_lock); 348452616f2bSIlan Peer 34852d319867SJohannes Berg reset_regdomains(true, &world_regdom); 348609d989d1SLuis R. Rodriguez restore_alpha2(alpha2, reset_user); 348709d989d1SLuis R. Rodriguez 348814609555SLuis R. Rodriguez /* 348914609555SLuis R. Rodriguez * If there's any pending requests we simply 349014609555SLuis R. Rodriguez * stash them to a temporary pending queue and 349114609555SLuis R. Rodriguez * add then after we've restored regulatory 349214609555SLuis R. Rodriguez * settings. 349314609555SLuis R. Rodriguez */ 349414609555SLuis R. Rodriguez spin_lock(®_requests_lock); 3495eeca9fceSIlan peer list_splice_tail_init(®_requests_list, &tmp_reg_req_list); 349614609555SLuis R. Rodriguez spin_unlock(®_requests_lock); 349714609555SLuis R. Rodriguez 349809d989d1SLuis R. Rodriguez /* Clear beacon hints */ 349909d989d1SLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 3500fea9bcedSJohannes Berg list_for_each_entry_safe(reg_beacon, btmp, ®_pending_beacons, list) { 350109d989d1SLuis R. Rodriguez list_del(®_beacon->list); 350209d989d1SLuis R. Rodriguez kfree(reg_beacon); 350309d989d1SLuis R. Rodriguez } 350409d989d1SLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 350509d989d1SLuis R. Rodriguez 3506fea9bcedSJohannes Berg list_for_each_entry_safe(reg_beacon, btmp, ®_beacon_list, list) { 350709d989d1SLuis R. Rodriguez list_del(®_beacon->list); 350809d989d1SLuis R. Rodriguez kfree(reg_beacon); 350909d989d1SLuis R. Rodriguez } 351009d989d1SLuis R. Rodriguez 351109d989d1SLuis R. Rodriguez /* First restore to the basic regulatory settings */ 3512379b82f4SJohannes Berg world_alpha2[0] = cfg80211_world_regdom->alpha2[0]; 3513379b82f4SJohannes Berg world_alpha2[1] = cfg80211_world_regdom->alpha2[1]; 351409d989d1SLuis R. Rodriguez 35155ce543d1SRajkumar Manoharan list_for_each_entry(rdev, &cfg80211_rdev_list, list) { 3516b0d7aa59SJonathan Doron if (rdev->wiphy.regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED) 3517b0d7aa59SJonathan Doron continue; 3518a2f73b6cSLuis R. Rodriguez if (rdev->wiphy.regulatory_flags & REGULATORY_CUSTOM_REG) 35195ce543d1SRajkumar Manoharan restore_custom_reg_settings(&rdev->wiphy); 35205ce543d1SRajkumar Manoharan } 35215ce543d1SRajkumar Manoharan 3522e646a025SJohannes Berg if (cached && (!is_an_alpha2(alpha2) || 3523e646a025SJohannes Berg !IS_ERR_OR_NULL(cfg80211_user_regdom))) { 3524e646a025SJohannes Berg reset_regdomains(false, cfg80211_world_regdom); 3525e646a025SJohannes Berg update_all_wiphy_regulatory(NL80211_REGDOM_SET_BY_CORE); 3526e646a025SJohannes Berg print_regdomain(get_cfg80211_regdom()); 3527e646a025SJohannes Berg nl80211_send_reg_change_event(&core_request_world); 3528e646a025SJohannes Berg reg_set_request_processed(); 3529e646a025SJohannes Berg 3530e646a025SJohannes Berg if (is_an_alpha2(alpha2) && 3531e646a025SJohannes Berg !regulatory_hint_user(alpha2, NL80211_USER_REG_HINT_USER)) { 3532e646a025SJohannes Berg struct regulatory_request *ureq; 3533e646a025SJohannes Berg 3534e646a025SJohannes Berg spin_lock(®_requests_lock); 3535e646a025SJohannes Berg ureq = list_last_entry(®_requests_list, 3536e646a025SJohannes Berg struct regulatory_request, 3537e646a025SJohannes Berg list); 3538e646a025SJohannes Berg list_del(&ureq->list); 3539e646a025SJohannes Berg spin_unlock(®_requests_lock); 3540e646a025SJohannes Berg 3541e646a025SJohannes Berg notify_self_managed_wiphys(ureq); 3542e646a025SJohannes Berg reg_update_last_request(ureq); 3543e646a025SJohannes Berg set_regdom(reg_copy_regd(cfg80211_user_regdom), 3544e646a025SJohannes Berg REGD_SOURCE_CACHED); 3545e646a025SJohannes Berg } 3546e646a025SJohannes Berg } else { 3547cee0bec5SDmitry Shmidt regulatory_hint_core(world_alpha2); 354809d989d1SLuis R. Rodriguez 354909d989d1SLuis R. Rodriguez /* 355009d989d1SLuis R. Rodriguez * This restores the ieee80211_regdom module parameter 355109d989d1SLuis R. Rodriguez * preference or the last user requested regulatory 355209d989d1SLuis R. Rodriguez * settings, user regulatory settings takes precedence. 355309d989d1SLuis R. Rodriguez */ 355409d989d1SLuis R. Rodriguez if (is_an_alpha2(alpha2)) 3555549cc1c5SMaciej S. Szmigiero regulatory_hint_user(alpha2, NL80211_USER_REG_HINT_USER); 3556e646a025SJohannes Berg } 355709d989d1SLuis R. Rodriguez 355814609555SLuis R. Rodriguez spin_lock(®_requests_lock); 355911cff96cSJohannes Berg list_splice_tail_init(&tmp_reg_req_list, ®_requests_list); 356014609555SLuis R. Rodriguez spin_unlock(®_requests_lock); 356114609555SLuis R. Rodriguez 3562c799ba6eSJohannes Berg pr_debug("Kicking the queue\n"); 356314609555SLuis R. Rodriguez 356414609555SLuis R. Rodriguez schedule_work(®_work); 356514609555SLuis R. Rodriguez } 356609d989d1SLuis R. Rodriguez 35677417844bSRajeev Kumar Sirasanagandla static bool is_wiphy_all_set_reg_flag(enum ieee80211_regulatory_flags flag) 35687417844bSRajeev Kumar Sirasanagandla { 35697417844bSRajeev Kumar Sirasanagandla struct cfg80211_registered_device *rdev; 35707417844bSRajeev Kumar Sirasanagandla struct wireless_dev *wdev; 35717417844bSRajeev Kumar Sirasanagandla 35727417844bSRajeev Kumar Sirasanagandla list_for_each_entry(rdev, &cfg80211_rdev_list, list) { 35737417844bSRajeev Kumar Sirasanagandla list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) { 35747417844bSRajeev Kumar Sirasanagandla wdev_lock(wdev); 35757417844bSRajeev Kumar Sirasanagandla if (!(wdev->wiphy->regulatory_flags & flag)) { 35767417844bSRajeev Kumar Sirasanagandla wdev_unlock(wdev); 35777417844bSRajeev Kumar Sirasanagandla return false; 35787417844bSRajeev Kumar Sirasanagandla } 35797417844bSRajeev Kumar Sirasanagandla wdev_unlock(wdev); 35807417844bSRajeev Kumar Sirasanagandla } 35817417844bSRajeev Kumar Sirasanagandla } 35827417844bSRajeev Kumar Sirasanagandla 35837417844bSRajeev Kumar Sirasanagandla return true; 35847417844bSRajeev Kumar Sirasanagandla } 35857417844bSRajeev Kumar Sirasanagandla 358609d989d1SLuis R. Rodriguez void regulatory_hint_disconnect(void) 358709d989d1SLuis R. Rodriguez { 35887417844bSRajeev Kumar Sirasanagandla /* Restore of regulatory settings is not required when wiphy(s) 35897417844bSRajeev Kumar Sirasanagandla * ignore IE from connected access point but clearance of beacon hints 35907417844bSRajeev Kumar Sirasanagandla * is required when wiphy(s) supports beacon hints. 35917417844bSRajeev Kumar Sirasanagandla */ 35927417844bSRajeev Kumar Sirasanagandla if (is_wiphy_all_set_reg_flag(REGULATORY_COUNTRY_IE_IGNORE)) { 35937417844bSRajeev Kumar Sirasanagandla struct reg_beacon *reg_beacon, *btmp; 35947417844bSRajeev Kumar Sirasanagandla 35957417844bSRajeev Kumar Sirasanagandla if (is_wiphy_all_set_reg_flag(REGULATORY_DISABLE_BEACON_HINTS)) 35967417844bSRajeev Kumar Sirasanagandla return; 35977417844bSRajeev Kumar Sirasanagandla 35987417844bSRajeev Kumar Sirasanagandla spin_lock_bh(®_pending_beacons_lock); 35997417844bSRajeev Kumar Sirasanagandla list_for_each_entry_safe(reg_beacon, btmp, 36007417844bSRajeev Kumar Sirasanagandla ®_pending_beacons, list) { 36017417844bSRajeev Kumar Sirasanagandla list_del(®_beacon->list); 36027417844bSRajeev Kumar Sirasanagandla kfree(reg_beacon); 36037417844bSRajeev Kumar Sirasanagandla } 36047417844bSRajeev Kumar Sirasanagandla spin_unlock_bh(®_pending_beacons_lock); 36057417844bSRajeev Kumar Sirasanagandla 36067417844bSRajeev Kumar Sirasanagandla list_for_each_entry_safe(reg_beacon, btmp, 36077417844bSRajeev Kumar Sirasanagandla ®_beacon_list, list) { 36087417844bSRajeev Kumar Sirasanagandla list_del(®_beacon->list); 36097417844bSRajeev Kumar Sirasanagandla kfree(reg_beacon); 36107417844bSRajeev Kumar Sirasanagandla } 36117417844bSRajeev Kumar Sirasanagandla 36127417844bSRajeev Kumar Sirasanagandla return; 36137417844bSRajeev Kumar Sirasanagandla } 36147417844bSRajeev Kumar Sirasanagandla 3615c799ba6eSJohannes Berg pr_debug("All devices are disconnected, going to restore regulatory settings\n"); 3616e646a025SJohannes Berg restore_regulatory_settings(false, true); 361709d989d1SLuis R. Rodriguez } 361809d989d1SLuis R. Rodriguez 36199cf0a0b4SAlexei Avshalom Lazar static bool freq_is_chan_12_13_14(u32 freq) 3620e38f8a7aSLuis R. Rodriguez { 362157fbcce3SJohannes Berg if (freq == ieee80211_channel_to_frequency(12, NL80211_BAND_2GHZ) || 362257fbcce3SJohannes Berg freq == ieee80211_channel_to_frequency(13, NL80211_BAND_2GHZ) || 362357fbcce3SJohannes Berg freq == ieee80211_channel_to_frequency(14, NL80211_BAND_2GHZ)) 3624e38f8a7aSLuis R. Rodriguez return true; 3625e38f8a7aSLuis R. Rodriguez return false; 3626e38f8a7aSLuis R. Rodriguez } 3627e38f8a7aSLuis R. Rodriguez 36283ebfa6e7SLuis R. Rodriguez static bool pending_reg_beacon(struct ieee80211_channel *beacon_chan) 36293ebfa6e7SLuis R. Rodriguez { 36303ebfa6e7SLuis R. Rodriguez struct reg_beacon *pending_beacon; 36313ebfa6e7SLuis R. Rodriguez 36323ebfa6e7SLuis R. Rodriguez list_for_each_entry(pending_beacon, ®_pending_beacons, list) 3633934f4c7dSThomas Pedersen if (ieee80211_channel_equal(beacon_chan, 3634934f4c7dSThomas Pedersen &pending_beacon->chan)) 36353ebfa6e7SLuis R. Rodriguez return true; 36363ebfa6e7SLuis R. Rodriguez return false; 36373ebfa6e7SLuis R. Rodriguez } 36383ebfa6e7SLuis R. Rodriguez 3639e38f8a7aSLuis R. Rodriguez int regulatory_hint_found_beacon(struct wiphy *wiphy, 3640e38f8a7aSLuis R. Rodriguez struct ieee80211_channel *beacon_chan, 3641e38f8a7aSLuis R. Rodriguez gfp_t gfp) 3642e38f8a7aSLuis R. Rodriguez { 3643e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon; 36443ebfa6e7SLuis R. Rodriguez bool processing; 3645e38f8a7aSLuis R. Rodriguez 36461a919318SJohannes Berg if (beacon_chan->beacon_found || 36471a919318SJohannes Berg beacon_chan->flags & IEEE80211_CHAN_RADAR || 364857fbcce3SJohannes Berg (beacon_chan->band == NL80211_BAND_2GHZ && 36491a919318SJohannes Berg !freq_is_chan_12_13_14(beacon_chan->center_freq))) 3650e38f8a7aSLuis R. Rodriguez return 0; 3651e38f8a7aSLuis R. Rodriguez 36523ebfa6e7SLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 36533ebfa6e7SLuis R. Rodriguez processing = pending_reg_beacon(beacon_chan); 36543ebfa6e7SLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 36553ebfa6e7SLuis R. Rodriguez 36563ebfa6e7SLuis R. Rodriguez if (processing) 3657e38f8a7aSLuis R. Rodriguez return 0; 3658e38f8a7aSLuis R. Rodriguez 3659e38f8a7aSLuis R. Rodriguez reg_beacon = kzalloc(sizeof(struct reg_beacon), gfp); 3660e38f8a7aSLuis R. Rodriguez if (!reg_beacon) 3661e38f8a7aSLuis R. Rodriguez return -ENOMEM; 3662e38f8a7aSLuis R. Rodriguez 3663934f4c7dSThomas Pedersen pr_debug("Found new beacon on frequency: %d.%03d MHz (Ch %d) on %s\n", 3664934f4c7dSThomas Pedersen beacon_chan->center_freq, beacon_chan->freq_offset, 3665934f4c7dSThomas Pedersen ieee80211_freq_khz_to_channel( 3666934f4c7dSThomas Pedersen ieee80211_channel_to_khz(beacon_chan)), 3667e38f8a7aSLuis R. Rodriguez wiphy_name(wiphy)); 36684113f751SLuis R. Rodriguez 3669e38f8a7aSLuis R. Rodriguez memcpy(®_beacon->chan, beacon_chan, 3670e38f8a7aSLuis R. Rodriguez sizeof(struct ieee80211_channel)); 3671e38f8a7aSLuis R. Rodriguez 3672e38f8a7aSLuis R. Rodriguez /* 3673e38f8a7aSLuis R. Rodriguez * Since we can be called from BH or and non-BH context 3674e38f8a7aSLuis R. Rodriguez * we must use spin_lock_bh() 3675e38f8a7aSLuis R. Rodriguez */ 3676e38f8a7aSLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 3677e38f8a7aSLuis R. Rodriguez list_add_tail(®_beacon->list, ®_pending_beacons); 3678e38f8a7aSLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 3679e38f8a7aSLuis R. Rodriguez 3680e38f8a7aSLuis R. Rodriguez schedule_work(®_work); 3681e38f8a7aSLuis R. Rodriguez 3682e38f8a7aSLuis R. Rodriguez return 0; 3683e38f8a7aSLuis R. Rodriguez } 3684e38f8a7aSLuis R. Rodriguez 3685a3d2eaf0SJohannes Berg static void print_rd_rules(const struct ieee80211_regdomain *rd) 3686b2e1b302SLuis R. Rodriguez { 3687b2e1b302SLuis R. Rodriguez unsigned int i; 3688a3d2eaf0SJohannes Berg const struct ieee80211_reg_rule *reg_rule = NULL; 3689a3d2eaf0SJohannes Berg const struct ieee80211_freq_range *freq_range = NULL; 3690a3d2eaf0SJohannes Berg const struct ieee80211_power_rule *power_rule = NULL; 3691089027e5SJanusz Dziedzic char bw[32], cac_time[32]; 3692b2e1b302SLuis R. Rodriguez 369394c4fd64SDave Young pr_debug(" (start_freq - end_freq @ bandwidth), (max_antenna_gain, max_eirp), (dfs_cac_time)\n"); 3694b2e1b302SLuis R. Rodriguez 3695b2e1b302SLuis R. Rodriguez for (i = 0; i < rd->n_reg_rules; i++) { 3696b2e1b302SLuis R. Rodriguez reg_rule = &rd->reg_rules[i]; 3697b2e1b302SLuis R. Rodriguez freq_range = ®_rule->freq_range; 3698b2e1b302SLuis R. Rodriguez power_rule = ®_rule->power_rule; 3699b2e1b302SLuis R. Rodriguez 3700b0dfd2eaSJanusz Dziedzic if (reg_rule->flags & NL80211_RRF_AUTO_BW) 3701db18d20dSYe Bin snprintf(bw, sizeof(bw), "%d KHz, %u KHz AUTO", 3702b0dfd2eaSJanusz Dziedzic freq_range->max_bandwidth_khz, 370397524820SJanusz Dziedzic reg_get_max_bandwidth(rd, reg_rule)); 370497524820SJanusz Dziedzic else 3705b0dfd2eaSJanusz Dziedzic snprintf(bw, sizeof(bw), "%d KHz", 370697524820SJanusz Dziedzic freq_range->max_bandwidth_khz); 370797524820SJanusz Dziedzic 3708089027e5SJanusz Dziedzic if (reg_rule->flags & NL80211_RRF_DFS) 3709089027e5SJanusz Dziedzic scnprintf(cac_time, sizeof(cac_time), "%u s", 3710089027e5SJanusz Dziedzic reg_rule->dfs_cac_ms/1000); 3711089027e5SJanusz Dziedzic else 3712089027e5SJanusz Dziedzic scnprintf(cac_time, sizeof(cac_time), "N/A"); 3713089027e5SJanusz Dziedzic 3714089027e5SJanusz Dziedzic 3715fb1fc7adSLuis R. Rodriguez /* 3716fb1fc7adSLuis R. Rodriguez * There may not be documentation for max antenna gain 3717fb1fc7adSLuis R. Rodriguez * in certain regions 3718fb1fc7adSLuis R. Rodriguez */ 3719b2e1b302SLuis R. Rodriguez if (power_rule->max_antenna_gain) 372094c4fd64SDave Young pr_debug(" (%d KHz - %d KHz @ %s), (%d mBi, %d mBm), (%s)\n", 3721b2e1b302SLuis R. Rodriguez freq_range->start_freq_khz, 3722b2e1b302SLuis R. Rodriguez freq_range->end_freq_khz, 372397524820SJanusz Dziedzic bw, 3724b2e1b302SLuis R. Rodriguez power_rule->max_antenna_gain, 3725089027e5SJanusz Dziedzic power_rule->max_eirp, 3726089027e5SJanusz Dziedzic cac_time); 3727b2e1b302SLuis R. Rodriguez else 372894c4fd64SDave Young pr_debug(" (%d KHz - %d KHz @ %s), (N/A, %d mBm), (%s)\n", 3729b2e1b302SLuis R. Rodriguez freq_range->start_freq_khz, 3730b2e1b302SLuis R. Rodriguez freq_range->end_freq_khz, 373197524820SJanusz Dziedzic bw, 3732089027e5SJanusz Dziedzic power_rule->max_eirp, 3733089027e5SJanusz Dziedzic cac_time); 3734b2e1b302SLuis R. Rodriguez } 3735b2e1b302SLuis R. Rodriguez } 3736b2e1b302SLuis R. Rodriguez 37374c7d3982SLuis R. Rodriguez bool reg_supported_dfs_region(enum nl80211_dfs_regions dfs_region) 37388b60b078SLuis R. Rodriguez { 37398b60b078SLuis R. Rodriguez switch (dfs_region) { 37408b60b078SLuis R. Rodriguez case NL80211_DFS_UNSET: 37418b60b078SLuis R. Rodriguez case NL80211_DFS_FCC: 37428b60b078SLuis R. Rodriguez case NL80211_DFS_ETSI: 37438b60b078SLuis R. Rodriguez case NL80211_DFS_JP: 37448b60b078SLuis R. Rodriguez return true; 37458b60b078SLuis R. Rodriguez default: 37464a22b00bSColin Ian King pr_debug("Ignoring unknown DFS master region: %d\n", dfs_region); 37478b60b078SLuis R. Rodriguez return false; 37488b60b078SLuis R. Rodriguez } 37498b60b078SLuis R. Rodriguez } 37508b60b078SLuis R. Rodriguez 3751a3d2eaf0SJohannes Berg static void print_regdomain(const struct ieee80211_regdomain *rd) 3752b2e1b302SLuis R. Rodriguez { 3753c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 3754b2e1b302SLuis R. Rodriguez 37553f2355cbSLuis R. Rodriguez if (is_intersected_alpha2(rd->alpha2)) { 3756c492db37SJohannes Berg if (lr->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE) { 375779c97e97SJohannes Berg struct cfg80211_registered_device *rdev; 3758c492db37SJohannes Berg rdev = cfg80211_rdev_by_wiphy_idx(lr->wiphy_idx); 375979c97e97SJohannes Berg if (rdev) { 376094c4fd64SDave Young pr_debug("Current regulatory domain updated by AP to: %c%c\n", 376179c97e97SJohannes Berg rdev->country_ie_alpha2[0], 376279c97e97SJohannes Berg rdev->country_ie_alpha2[1]); 37633f2355cbSLuis R. Rodriguez } else 376494c4fd64SDave Young pr_debug("Current regulatory domain intersected:\n"); 37653f2355cbSLuis R. Rodriguez } else 376694c4fd64SDave Young pr_debug("Current regulatory domain intersected:\n"); 37671a919318SJohannes Berg } else if (is_world_regdom(rd->alpha2)) { 376894c4fd64SDave Young pr_debug("World regulatory domain updated:\n"); 37691a919318SJohannes Berg } else { 3770b2e1b302SLuis R. Rodriguez if (is_unknown_alpha2(rd->alpha2)) 377194c4fd64SDave Young pr_debug("Regulatory domain changed to driver built-in settings (unknown country)\n"); 377257b5ce07SLuis R. Rodriguez else { 3773c492db37SJohannes Berg if (reg_request_cell_base(lr)) 377494c4fd64SDave Young pr_debug("Regulatory domain changed to country: %c%c by Cell Station\n", 3775b2e1b302SLuis R. Rodriguez rd->alpha2[0], rd->alpha2[1]); 377657b5ce07SLuis R. Rodriguez else 377794c4fd64SDave Young pr_debug("Regulatory domain changed to country: %c%c\n", 377857b5ce07SLuis R. Rodriguez rd->alpha2[0], rd->alpha2[1]); 377957b5ce07SLuis R. Rodriguez } 3780b2e1b302SLuis R. Rodriguez } 37811a919318SJohannes Berg 378294c4fd64SDave Young pr_debug(" DFS Master region: %s", reg_dfs_region_str(rd->dfs_region)); 3783b2e1b302SLuis R. Rodriguez print_rd_rules(rd); 3784b2e1b302SLuis R. Rodriguez } 3785b2e1b302SLuis R. Rodriguez 37862df78167SJohannes Berg static void print_regdomain_info(const struct ieee80211_regdomain *rd) 3787b2e1b302SLuis R. Rodriguez { 378894c4fd64SDave Young pr_debug("Regulatory domain: %c%c\n", rd->alpha2[0], rd->alpha2[1]); 3789b2e1b302SLuis R. Rodriguez print_rd_rules(rd); 3790b2e1b302SLuis R. Rodriguez } 3791b2e1b302SLuis R. Rodriguez 37923b9e5acaSLuis R. Rodriguez static int reg_set_rd_core(const struct ieee80211_regdomain *rd) 37933b9e5acaSLuis R. Rodriguez { 37943b9e5acaSLuis R. Rodriguez if (!is_world_regdom(rd->alpha2)) 37953b9e5acaSLuis R. Rodriguez return -EINVAL; 37963b9e5acaSLuis R. Rodriguez update_world_regdomain(rd); 37973b9e5acaSLuis R. Rodriguez return 0; 37983b9e5acaSLuis R. Rodriguez } 37993b9e5acaSLuis R. Rodriguez 380084721d44SLuis R. Rodriguez static int reg_set_rd_user(const struct ieee80211_regdomain *rd, 380184721d44SLuis R. Rodriguez struct regulatory_request *user_request) 380284721d44SLuis R. Rodriguez { 380384721d44SLuis R. Rodriguez const struct ieee80211_regdomain *intersected_rd = NULL; 380484721d44SLuis R. Rodriguez 380584721d44SLuis R. Rodriguez if (!regdom_changes(rd->alpha2)) 380684721d44SLuis R. Rodriguez return -EALREADY; 380784721d44SLuis R. Rodriguez 380884721d44SLuis R. Rodriguez if (!is_valid_rd(rd)) { 380994c4fd64SDave Young pr_err("Invalid regulatory domain detected: %c%c\n", 381094c4fd64SDave Young rd->alpha2[0], rd->alpha2[1]); 381184721d44SLuis R. Rodriguez print_regdomain_info(rd); 381284721d44SLuis R. Rodriguez return -EINVAL; 381384721d44SLuis R. Rodriguez } 381484721d44SLuis R. Rodriguez 381584721d44SLuis R. Rodriguez if (!user_request->intersect) { 381684721d44SLuis R. Rodriguez reset_regdomains(false, rd); 381784721d44SLuis R. Rodriguez return 0; 381884721d44SLuis R. Rodriguez } 381984721d44SLuis R. Rodriguez 382084721d44SLuis R. Rodriguez intersected_rd = regdom_intersect(rd, get_cfg80211_regdom()); 382184721d44SLuis R. Rodriguez if (!intersected_rd) 382284721d44SLuis R. Rodriguez return -EINVAL; 382384721d44SLuis R. Rodriguez 382484721d44SLuis R. Rodriguez kfree(rd); 382584721d44SLuis R. Rodriguez rd = NULL; 382684721d44SLuis R. Rodriguez reset_regdomains(false, intersected_rd); 382784721d44SLuis R. Rodriguez 382884721d44SLuis R. Rodriguez return 0; 382984721d44SLuis R. Rodriguez } 383084721d44SLuis R. Rodriguez 3831f5fe3247SLuis R. Rodriguez static int reg_set_rd_driver(const struct ieee80211_regdomain *rd, 3832f5fe3247SLuis R. Rodriguez struct regulatory_request *driver_request) 3833b2e1b302SLuis R. Rodriguez { 3834e9763c3cSJohannes Berg const struct ieee80211_regdomain *regd; 38359c96477dSLuis R. Rodriguez const struct ieee80211_regdomain *intersected_rd = NULL; 3836f5fe3247SLuis R. Rodriguez const struct ieee80211_regdomain *tmp; 3837806a9e39SLuis R. Rodriguez struct wiphy *request_wiphy; 38386913b49aSJohannes Berg 3839f5fe3247SLuis R. Rodriguez if (is_world_regdom(rd->alpha2)) 3840b2e1b302SLuis R. Rodriguez return -EINVAL; 3841b2e1b302SLuis R. Rodriguez 3842baeb66feSJohn W. Linville if (!regdom_changes(rd->alpha2)) 384395908535SKalle Valo return -EALREADY; 3844b2e1b302SLuis R. Rodriguez 3845b2e1b302SLuis R. Rodriguez if (!is_valid_rd(rd)) { 384694c4fd64SDave Young pr_err("Invalid regulatory domain detected: %c%c\n", 384794c4fd64SDave Young rd->alpha2[0], rd->alpha2[1]); 3848b2e1b302SLuis R. Rodriguez print_regdomain_info(rd); 3849b2e1b302SLuis R. Rodriguez return -EINVAL; 3850b2e1b302SLuis R. Rodriguez } 3851b2e1b302SLuis R. Rodriguez 3852f5fe3247SLuis R. Rodriguez request_wiphy = wiphy_idx_to_wiphy(driver_request->wiphy_idx); 3853922ec58cSJohannes Berg if (!request_wiphy) 3854de3584bdSJohannes Berg return -ENODEV; 3855806a9e39SLuis R. Rodriguez 3856f5fe3247SLuis R. Rodriguez if (!driver_request->intersect) { 3857a05829a7SJohannes Berg ASSERT_RTNL(); 3858a05829a7SJohannes Berg wiphy_lock(request_wiphy); 3859a05829a7SJohannes Berg if (request_wiphy->regd) { 3860a05829a7SJohannes Berg wiphy_unlock(request_wiphy); 3861558f6d32SLuis R. Rodriguez return -EALREADY; 3862a05829a7SJohannes Berg } 38633e0c3ff3SLuis R. Rodriguez 3864e9763c3cSJohannes Berg regd = reg_copy_regd(rd); 3865a05829a7SJohannes Berg if (IS_ERR(regd)) { 3866a05829a7SJohannes Berg wiphy_unlock(request_wiphy); 3867e9763c3cSJohannes Berg return PTR_ERR(regd); 3868a05829a7SJohannes Berg } 38693e0c3ff3SLuis R. Rodriguez 3870458f4f9eSJohannes Berg rcu_assign_pointer(request_wiphy->regd, regd); 3871a05829a7SJohannes Berg wiphy_unlock(request_wiphy); 3872379b82f4SJohannes Berg reset_regdomains(false, rd); 3873b8295acdSLuis R. Rodriguez return 0; 3874b8295acdSLuis R. Rodriguez } 3875b8295acdSLuis R. Rodriguez 3876458f4f9eSJohannes Berg intersected_rd = regdom_intersect(rd, get_cfg80211_regdom()); 38779c96477dSLuis R. Rodriguez if (!intersected_rd) 38789c96477dSLuis R. Rodriguez return -EINVAL; 3879b8295acdSLuis R. Rodriguez 3880fb1fc7adSLuis R. Rodriguez /* 3881fb1fc7adSLuis R. Rodriguez * We can trash what CRDA provided now. 38823e0c3ff3SLuis R. Rodriguez * However if a driver requested this specific regulatory 3883fb1fc7adSLuis R. Rodriguez * domain we keep it for its private use 3884fb1fc7adSLuis R. Rodriguez */ 3885b7566fc3SLarry Finger tmp = get_wiphy_regdom(request_wiphy); 3886458f4f9eSJohannes Berg rcu_assign_pointer(request_wiphy->regd, rd); 3887b7566fc3SLarry Finger rcu_free_regdom(tmp); 38883e0c3ff3SLuis R. Rodriguez 3889b8295acdSLuis R. Rodriguez rd = NULL; 3890b8295acdSLuis R. Rodriguez 3891379b82f4SJohannes Berg reset_regdomains(false, intersected_rd); 3892b8295acdSLuis R. Rodriguez 3893b8295acdSLuis R. Rodriguez return 0; 38949c96477dSLuis R. Rodriguez } 38959c96477dSLuis R. Rodriguez 389601992406SLuis R. Rodriguez static int reg_set_rd_country_ie(const struct ieee80211_regdomain *rd, 389701992406SLuis R. Rodriguez struct regulatory_request *country_ie_request) 3898f5fe3247SLuis R. Rodriguez { 3899f5fe3247SLuis R. Rodriguez struct wiphy *request_wiphy; 3900f5fe3247SLuis R. Rodriguez 3901f5fe3247SLuis R. Rodriguez if (!is_alpha2_set(rd->alpha2) && !is_an_alpha2(rd->alpha2) && 3902f5fe3247SLuis R. Rodriguez !is_unknown_alpha2(rd->alpha2)) 3903f5fe3247SLuis R. Rodriguez return -EINVAL; 3904f5fe3247SLuis R. Rodriguez 3905f5fe3247SLuis R. Rodriguez /* 3906f5fe3247SLuis R. Rodriguez * Lets only bother proceeding on the same alpha2 if the current 3907f5fe3247SLuis R. Rodriguez * rd is non static (it means CRDA was present and was used last) 3908f5fe3247SLuis R. Rodriguez * and the pending request came in from a country IE 3909f5fe3247SLuis R. Rodriguez */ 3910f5fe3247SLuis R. Rodriguez 3911f5fe3247SLuis R. Rodriguez if (!is_valid_rd(rd)) { 391294c4fd64SDave Young pr_err("Invalid regulatory domain detected: %c%c\n", 391394c4fd64SDave Young rd->alpha2[0], rd->alpha2[1]); 3914f5fe3247SLuis R. Rodriguez print_regdomain_info(rd); 39153f2355cbSLuis R. Rodriguez return -EINVAL; 3916b2e1b302SLuis R. Rodriguez } 3917b2e1b302SLuis R. Rodriguez 391801992406SLuis R. Rodriguez request_wiphy = wiphy_idx_to_wiphy(country_ie_request->wiphy_idx); 3919922ec58cSJohannes Berg if (!request_wiphy) 3920f5fe3247SLuis R. Rodriguez return -ENODEV; 3921f5fe3247SLuis R. Rodriguez 392201992406SLuis R. Rodriguez if (country_ie_request->intersect) 3923f5fe3247SLuis R. Rodriguez return -EINVAL; 3924f5fe3247SLuis R. Rodriguez 3925f5fe3247SLuis R. Rodriguez reset_regdomains(false, rd); 3926f5fe3247SLuis R. Rodriguez return 0; 3927f5fe3247SLuis R. Rodriguez } 3928b2e1b302SLuis R. Rodriguez 3929fb1fc7adSLuis R. Rodriguez /* 3930fb1fc7adSLuis R. Rodriguez * Use this call to set the current regulatory domain. Conflicts with 3931b2e1b302SLuis R. Rodriguez * multiple drivers can be ironed out later. Caller must've already 3932458f4f9eSJohannes Berg * kmalloc'd the rd structure. 3933fb1fc7adSLuis R. Rodriguez */ 3934c37722bdSIlan peer int set_regdom(const struct ieee80211_regdomain *rd, 3935c37722bdSIlan peer enum ieee80211_regd_source regd_src) 3936b2e1b302SLuis R. Rodriguez { 3937c492db37SJohannes Berg struct regulatory_request *lr; 3938092008abSJanusz Dziedzic bool user_reset = false; 3939b2e1b302SLuis R. Rodriguez int r; 3940b2e1b302SLuis R. Rodriguez 3941e646a025SJohannes Berg if (IS_ERR_OR_NULL(rd)) 3942e646a025SJohannes Berg return -ENODATA; 3943e646a025SJohannes Berg 39443b9e5acaSLuis R. Rodriguez if (!reg_is_valid_request(rd->alpha2)) { 39453b9e5acaSLuis R. Rodriguez kfree(rd); 39463b9e5acaSLuis R. Rodriguez return -EINVAL; 39473b9e5acaSLuis R. Rodriguez } 39483b9e5acaSLuis R. Rodriguez 3949c37722bdSIlan peer if (regd_src == REGD_SOURCE_CRDA) 3950b6863036SJohannes Berg reset_crda_timeouts(); 3951c37722bdSIlan peer 3952c492db37SJohannes Berg lr = get_last_request(); 3953abc7381bSLuis R. Rodriguez 3954b2e1b302SLuis R. Rodriguez /* Note that this doesn't update the wiphys, this is done below */ 39553b9e5acaSLuis R. Rodriguez switch (lr->initiator) { 39563b9e5acaSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_CORE: 39573b9e5acaSLuis R. Rodriguez r = reg_set_rd_core(rd); 39583b9e5acaSLuis R. Rodriguez break; 39593b9e5acaSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_USER: 3960e646a025SJohannes Berg cfg80211_save_user_regdom(rd); 396184721d44SLuis R. Rodriguez r = reg_set_rd_user(rd, lr); 3962092008abSJanusz Dziedzic user_reset = true; 396384721d44SLuis R. Rodriguez break; 39643b9e5acaSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_DRIVER: 3965f5fe3247SLuis R. Rodriguez r = reg_set_rd_driver(rd, lr); 3966f5fe3247SLuis R. Rodriguez break; 39673b9e5acaSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_COUNTRY_IE: 396801992406SLuis R. Rodriguez r = reg_set_rd_country_ie(rd, lr); 39693b9e5acaSLuis R. Rodriguez break; 39703b9e5acaSLuis R. Rodriguez default: 39713b9e5acaSLuis R. Rodriguez WARN(1, "invalid initiator %d\n", lr->initiator); 397209d11800SOla Olsson kfree(rd); 39733b9e5acaSLuis R. Rodriguez return -EINVAL; 39743b9e5acaSLuis R. Rodriguez } 39753b9e5acaSLuis R. Rodriguez 3976d2372b31SJohannes Berg if (r) { 3977092008abSJanusz Dziedzic switch (r) { 3978092008abSJanusz Dziedzic case -EALREADY: 397995908535SKalle Valo reg_set_request_processed(); 3980092008abSJanusz Dziedzic break; 3981092008abSJanusz Dziedzic default: 3982092008abSJanusz Dziedzic /* Back to world regulatory in case of errors */ 3983e646a025SJohannes Berg restore_regulatory_settings(user_reset, false); 3984092008abSJanusz Dziedzic } 398595908535SKalle Valo 3986d2372b31SJohannes Berg kfree(rd); 398738fd2143SJohannes Berg return r; 3988d2372b31SJohannes Berg } 3989b2e1b302SLuis R. Rodriguez 3990b2e1b302SLuis R. Rodriguez /* This would make this whole thing pointless */ 399138fd2143SJohannes Berg if (WARN_ON(!lr->intersect && rd != get_cfg80211_regdom())) 399238fd2143SJohannes Berg return -EINVAL; 3993b2e1b302SLuis R. Rodriguez 3994b2e1b302SLuis R. Rodriguez /* update all wiphys now with the new established regulatory domain */ 3995c492db37SJohannes Berg update_all_wiphy_regulatory(lr->initiator); 3996b2e1b302SLuis R. Rodriguez 3997458f4f9eSJohannes Berg print_regdomain(get_cfg80211_regdom()); 3998b2e1b302SLuis R. Rodriguez 3999c492db37SJohannes Berg nl80211_send_reg_change_event(lr); 400073d54c9eSLuis R. Rodriguez 4001b2e253cfSLuis R. Rodriguez reg_set_request_processed(); 4002b2e253cfSLuis R. Rodriguez 400338fd2143SJohannes Berg return 0; 4004b2e1b302SLuis R. Rodriguez } 4005b2e1b302SLuis R. Rodriguez 40062c3e861cSArik Nemtsov static int __regulatory_set_wiphy_regd(struct wiphy *wiphy, 4007b0d7aa59SJonathan Doron struct ieee80211_regdomain *rd) 4008b0d7aa59SJonathan Doron { 4009b0d7aa59SJonathan Doron const struct ieee80211_regdomain *regd; 4010b0d7aa59SJonathan Doron const struct ieee80211_regdomain *prev_regd; 4011b0d7aa59SJonathan Doron struct cfg80211_registered_device *rdev; 4012b0d7aa59SJonathan Doron 4013b0d7aa59SJonathan Doron if (WARN_ON(!wiphy || !rd)) 4014b0d7aa59SJonathan Doron return -EINVAL; 4015b0d7aa59SJonathan Doron 4016b0d7aa59SJonathan Doron if (WARN(!(wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED), 4017b0d7aa59SJonathan Doron "wiphy should have REGULATORY_WIPHY_SELF_MANAGED\n")) 4018b0d7aa59SJonathan Doron return -EPERM; 4019b0d7aa59SJonathan Doron 4020b767ecdaSJohannes Berg if (WARN(!is_valid_rd(rd), 4021b767ecdaSJohannes Berg "Invalid regulatory domain detected: %c%c\n", 4022b767ecdaSJohannes Berg rd->alpha2[0], rd->alpha2[1])) { 4023b0d7aa59SJonathan Doron print_regdomain_info(rd); 4024b0d7aa59SJonathan Doron return -EINVAL; 4025b0d7aa59SJonathan Doron } 4026b0d7aa59SJonathan Doron 4027b0d7aa59SJonathan Doron regd = reg_copy_regd(rd); 4028b0d7aa59SJonathan Doron if (IS_ERR(regd)) 4029b0d7aa59SJonathan Doron return PTR_ERR(regd); 4030b0d7aa59SJonathan Doron 4031b0d7aa59SJonathan Doron rdev = wiphy_to_rdev(wiphy); 4032b0d7aa59SJonathan Doron 4033b0d7aa59SJonathan Doron spin_lock(®_requests_lock); 4034b0d7aa59SJonathan Doron prev_regd = rdev->requested_regd; 4035b0d7aa59SJonathan Doron rdev->requested_regd = regd; 4036b0d7aa59SJonathan Doron spin_unlock(®_requests_lock); 4037b0d7aa59SJonathan Doron 4038b0d7aa59SJonathan Doron kfree(prev_regd); 40392c3e861cSArik Nemtsov return 0; 40402c3e861cSArik Nemtsov } 40412c3e861cSArik Nemtsov 40422c3e861cSArik Nemtsov int regulatory_set_wiphy_regd(struct wiphy *wiphy, 40432c3e861cSArik Nemtsov struct ieee80211_regdomain *rd) 40442c3e861cSArik Nemtsov { 40452c3e861cSArik Nemtsov int ret = __regulatory_set_wiphy_regd(wiphy, rd); 40462c3e861cSArik Nemtsov 40472c3e861cSArik Nemtsov if (ret) 40482c3e861cSArik Nemtsov return ret; 4049b0d7aa59SJonathan Doron 4050b0d7aa59SJonathan Doron schedule_work(®_work); 4051b0d7aa59SJonathan Doron return 0; 4052b0d7aa59SJonathan Doron } 4053b0d7aa59SJonathan Doron EXPORT_SYMBOL(regulatory_set_wiphy_regd); 4054b0d7aa59SJonathan Doron 4055a05829a7SJohannes Berg int regulatory_set_wiphy_regd_sync(struct wiphy *wiphy, 40562c3e861cSArik Nemtsov struct ieee80211_regdomain *rd) 40572c3e861cSArik Nemtsov { 40582c3e861cSArik Nemtsov int ret; 40592c3e861cSArik Nemtsov 40602c3e861cSArik Nemtsov ASSERT_RTNL(); 40612c3e861cSArik Nemtsov 40622c3e861cSArik Nemtsov ret = __regulatory_set_wiphy_regd(wiphy, rd); 40632c3e861cSArik Nemtsov if (ret) 40642c3e861cSArik Nemtsov return ret; 40652c3e861cSArik Nemtsov 40662c3e861cSArik Nemtsov /* process the request immediately */ 4067a05829a7SJohannes Berg reg_process_self_managed_hint(wiphy); 4068a05829a7SJohannes Berg reg_check_channels(); 40692c3e861cSArik Nemtsov return 0; 40702c3e861cSArik Nemtsov } 4071a05829a7SJohannes Berg EXPORT_SYMBOL(regulatory_set_wiphy_regd_sync); 40722c3e861cSArik Nemtsov 407357b5ce07SLuis R. Rodriguez void wiphy_regulatory_register(struct wiphy *wiphy) 407457b5ce07SLuis R. Rodriguez { 4075aced43ceSAmar Singhal struct regulatory_request *lr = get_last_request(); 407623df0b73SArik Nemtsov 4077aced43ceSAmar Singhal /* self-managed devices ignore beacon hints and country IE */ 4078aced43ceSAmar Singhal if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED) { 4079b0d7aa59SJonathan Doron wiphy->regulatory_flags |= REGULATORY_DISABLE_BEACON_HINTS | 4080b0d7aa59SJonathan Doron REGULATORY_COUNTRY_IE_IGNORE; 4081b0d7aa59SJonathan Doron 4082aced43ceSAmar Singhal /* 4083aced43ceSAmar Singhal * The last request may have been received before this 4084aced43ceSAmar Singhal * registration call. Call the driver notifier if 40858772eed9SSriram R * initiator is USER. 4086aced43ceSAmar Singhal */ 40878772eed9SSriram R if (lr->initiator == NL80211_REGDOM_SET_BY_USER) 4088aced43ceSAmar Singhal reg_call_notifier(wiphy, lr); 4089aced43ceSAmar Singhal } 4090aced43ceSAmar Singhal 409157b5ce07SLuis R. Rodriguez if (!reg_dev_ignore_cell_hint(wiphy)) 409257b5ce07SLuis R. Rodriguez reg_num_devs_support_basehint++; 409357b5ce07SLuis R. Rodriguez 409423df0b73SArik Nemtsov wiphy_update_regulatory(wiphy, lr->initiator); 409589766727SVasanthakumar Thiagarajan wiphy_all_share_dfs_chan_state(wiphy); 40961b7b3ac8SMiri Korenblit reg_process_self_managed_hints(); 409757b5ce07SLuis R. Rodriguez } 409857b5ce07SLuis R. Rodriguez 4099bfead080SLuis R. Rodriguez void wiphy_regulatory_deregister(struct wiphy *wiphy) 41003f2355cbSLuis R. Rodriguez { 41010ad8acafSLuis R. Rodriguez struct wiphy *request_wiphy = NULL; 4102c492db37SJohannes Berg struct regulatory_request *lr; 4103761cf7ecSLuis R. Rodriguez 4104c492db37SJohannes Berg lr = get_last_request(); 4105abc7381bSLuis R. Rodriguez 410657b5ce07SLuis R. Rodriguez if (!reg_dev_ignore_cell_hint(wiphy)) 410757b5ce07SLuis R. Rodriguez reg_num_devs_support_basehint--; 410857b5ce07SLuis R. Rodriguez 4109458f4f9eSJohannes Berg rcu_free_regdom(get_wiphy_regdom(wiphy)); 411034dd886cSMonam Agarwal RCU_INIT_POINTER(wiphy->regd, NULL); 41110ef9ccddSChris Wright 4112c492db37SJohannes Berg if (lr) 4113c492db37SJohannes Berg request_wiphy = wiphy_idx_to_wiphy(lr->wiphy_idx); 4114806a9e39SLuis R. Rodriguez 41150ef9ccddSChris Wright if (!request_wiphy || request_wiphy != wiphy) 411638fd2143SJohannes Berg return; 41170ef9ccddSChris Wright 4118c492db37SJohannes Berg lr->wiphy_idx = WIPHY_IDX_INVALID; 4119c492db37SJohannes Berg lr->country_ie_env = ENVIRON_ANY; 41203f2355cbSLuis R. Rodriguez } 41213f2355cbSLuis R. Rodriguez 4122174e0cd2SIlan Peer /* 4123f89769cfSArend van Spriel * See FCC notices for UNII band definitions 4124f89769cfSArend van Spriel * 5GHz: https://www.fcc.gov/document/5-ghz-unlicensed-spectrum-unii 4125f89769cfSArend van Spriel * 6GHz: https://www.fcc.gov/document/fcc-proposes-more-spectrum-unlicensed-use-0 4126174e0cd2SIlan Peer */ 4127174e0cd2SIlan Peer int cfg80211_get_unii(int freq) 4128174e0cd2SIlan Peer { 4129174e0cd2SIlan Peer /* UNII-1 */ 4130174e0cd2SIlan Peer if (freq >= 5150 && freq <= 5250) 4131174e0cd2SIlan Peer return 0; 4132174e0cd2SIlan Peer 4133174e0cd2SIlan Peer /* UNII-2A */ 4134174e0cd2SIlan Peer if (freq > 5250 && freq <= 5350) 4135174e0cd2SIlan Peer return 1; 4136174e0cd2SIlan Peer 4137174e0cd2SIlan Peer /* UNII-2B */ 4138174e0cd2SIlan Peer if (freq > 5350 && freq <= 5470) 4139174e0cd2SIlan Peer return 2; 4140174e0cd2SIlan Peer 4141174e0cd2SIlan Peer /* UNII-2C */ 4142174e0cd2SIlan Peer if (freq > 5470 && freq <= 5725) 4143174e0cd2SIlan Peer return 3; 4144174e0cd2SIlan Peer 4145174e0cd2SIlan Peer /* UNII-3 */ 4146174e0cd2SIlan Peer if (freq > 5725 && freq <= 5825) 4147174e0cd2SIlan Peer return 4; 4148174e0cd2SIlan Peer 4149f89769cfSArend van Spriel /* UNII-5 */ 4150f89769cfSArend van Spriel if (freq > 5925 && freq <= 6425) 4151f89769cfSArend van Spriel return 5; 4152f89769cfSArend van Spriel 4153f89769cfSArend van Spriel /* UNII-6 */ 4154f89769cfSArend van Spriel if (freq > 6425 && freq <= 6525) 4155f89769cfSArend van Spriel return 6; 4156f89769cfSArend van Spriel 4157f89769cfSArend van Spriel /* UNII-7 */ 4158f89769cfSArend van Spriel if (freq > 6525 && freq <= 6875) 4159f89769cfSArend van Spriel return 7; 4160f89769cfSArend van Spriel 4161f89769cfSArend van Spriel /* UNII-8 */ 4162f89769cfSArend van Spriel if (freq > 6875 && freq <= 7125) 4163f89769cfSArend van Spriel return 8; 4164f89769cfSArend van Spriel 4165174e0cd2SIlan Peer return -EINVAL; 4166174e0cd2SIlan Peer } 4167174e0cd2SIlan Peer 4168c8866e55SIlan Peer bool regulatory_indoor_allowed(void) 4169c8866e55SIlan Peer { 4170c8866e55SIlan Peer return reg_is_indoor; 4171c8866e55SIlan Peer } 4172c8866e55SIlan Peer 4173b35a51c7SVasanthakumar Thiagarajan bool regulatory_pre_cac_allowed(struct wiphy *wiphy) 4174b35a51c7SVasanthakumar Thiagarajan { 4175b35a51c7SVasanthakumar Thiagarajan const struct ieee80211_regdomain *regd = NULL; 4176b35a51c7SVasanthakumar Thiagarajan const struct ieee80211_regdomain *wiphy_regd = NULL; 4177b35a51c7SVasanthakumar Thiagarajan bool pre_cac_allowed = false; 4178b35a51c7SVasanthakumar Thiagarajan 4179b35a51c7SVasanthakumar Thiagarajan rcu_read_lock(); 4180b35a51c7SVasanthakumar Thiagarajan 4181b35a51c7SVasanthakumar Thiagarajan regd = rcu_dereference(cfg80211_regdomain); 4182b35a51c7SVasanthakumar Thiagarajan wiphy_regd = rcu_dereference(wiphy->regd); 4183b35a51c7SVasanthakumar Thiagarajan if (!wiphy_regd) { 4184b35a51c7SVasanthakumar Thiagarajan if (regd->dfs_region == NL80211_DFS_ETSI) 4185b35a51c7SVasanthakumar Thiagarajan pre_cac_allowed = true; 4186b35a51c7SVasanthakumar Thiagarajan 4187b35a51c7SVasanthakumar Thiagarajan rcu_read_unlock(); 4188b35a51c7SVasanthakumar Thiagarajan 4189b35a51c7SVasanthakumar Thiagarajan return pre_cac_allowed; 4190b35a51c7SVasanthakumar Thiagarajan } 4191b35a51c7SVasanthakumar Thiagarajan 4192b35a51c7SVasanthakumar Thiagarajan if (regd->dfs_region == wiphy_regd->dfs_region && 4193b35a51c7SVasanthakumar Thiagarajan wiphy_regd->dfs_region == NL80211_DFS_ETSI) 4194b35a51c7SVasanthakumar Thiagarajan pre_cac_allowed = true; 4195b35a51c7SVasanthakumar Thiagarajan 4196b35a51c7SVasanthakumar Thiagarajan rcu_read_unlock(); 4197b35a51c7SVasanthakumar Thiagarajan 4198b35a51c7SVasanthakumar Thiagarajan return pre_cac_allowed; 4199b35a51c7SVasanthakumar Thiagarajan } 4200dc0c18edSAaron Komisar EXPORT_SYMBOL(regulatory_pre_cac_allowed); 4201b35a51c7SVasanthakumar Thiagarajan 420226ec17a1SOrr Mazor static void cfg80211_check_and_end_cac(struct cfg80211_registered_device *rdev) 420326ec17a1SOrr Mazor { 420426ec17a1SOrr Mazor struct wireless_dev *wdev; 420526ec17a1SOrr Mazor /* If we finished CAC or received radar, we should end any 420626ec17a1SOrr Mazor * CAC running on the same channels. 420726ec17a1SOrr Mazor * the check !cfg80211_chandef_dfs_usable contain 2 options: 420826ec17a1SOrr Mazor * either all channels are available - those the CAC_FINISHED 420926ec17a1SOrr Mazor * event has effected another wdev state, or there is a channel 421026ec17a1SOrr Mazor * in unavailable state in wdev chandef - those the RADAR_DETECTED 421126ec17a1SOrr Mazor * event has effected another wdev state. 421226ec17a1SOrr Mazor * In both cases we should end the CAC on the wdev. 421326ec17a1SOrr Mazor */ 421426ec17a1SOrr Mazor list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) { 42157b0a0e3cSJohannes Berg struct cfg80211_chan_def *chandef; 42167b0a0e3cSJohannes Berg 42177b0a0e3cSJohannes Berg if (!wdev->cac_started) 42187b0a0e3cSJohannes Berg continue; 42197b0a0e3cSJohannes Berg 42207b0a0e3cSJohannes Berg /* FIXME: radar detection is tied to link 0 for now */ 42217b0a0e3cSJohannes Berg chandef = wdev_chandef(wdev, 0); 42227b0a0e3cSJohannes Berg if (!chandef) 42237b0a0e3cSJohannes Berg continue; 42247b0a0e3cSJohannes Berg 42257b0a0e3cSJohannes Berg if (!cfg80211_chandef_dfs_usable(&rdev->wiphy, chandef)) 422626ec17a1SOrr Mazor rdev_end_cac(rdev, wdev->netdev); 422726ec17a1SOrr Mazor } 422826ec17a1SOrr Mazor } 422926ec17a1SOrr Mazor 423089766727SVasanthakumar Thiagarajan void regulatory_propagate_dfs_state(struct wiphy *wiphy, 423189766727SVasanthakumar Thiagarajan struct cfg80211_chan_def *chandef, 423289766727SVasanthakumar Thiagarajan enum nl80211_dfs_state dfs_state, 423389766727SVasanthakumar Thiagarajan enum nl80211_radar_event event) 423489766727SVasanthakumar Thiagarajan { 423589766727SVasanthakumar Thiagarajan struct cfg80211_registered_device *rdev; 423689766727SVasanthakumar Thiagarajan 423789766727SVasanthakumar Thiagarajan ASSERT_RTNL(); 423889766727SVasanthakumar Thiagarajan 423989766727SVasanthakumar Thiagarajan if (WARN_ON(!cfg80211_chandef_valid(chandef))) 424089766727SVasanthakumar Thiagarajan return; 424189766727SVasanthakumar Thiagarajan 424289766727SVasanthakumar Thiagarajan list_for_each_entry(rdev, &cfg80211_rdev_list, list) { 424389766727SVasanthakumar Thiagarajan if (wiphy == &rdev->wiphy) 424489766727SVasanthakumar Thiagarajan continue; 424589766727SVasanthakumar Thiagarajan 424689766727SVasanthakumar Thiagarajan if (!reg_dfs_domain_same(wiphy, &rdev->wiphy)) 424789766727SVasanthakumar Thiagarajan continue; 424889766727SVasanthakumar Thiagarajan 424989766727SVasanthakumar Thiagarajan if (!ieee80211_get_channel(&rdev->wiphy, 425089766727SVasanthakumar Thiagarajan chandef->chan->center_freq)) 425189766727SVasanthakumar Thiagarajan continue; 425289766727SVasanthakumar Thiagarajan 425389766727SVasanthakumar Thiagarajan cfg80211_set_dfs_state(&rdev->wiphy, chandef, dfs_state); 425489766727SVasanthakumar Thiagarajan 425589766727SVasanthakumar Thiagarajan if (event == NL80211_RADAR_DETECTED || 425626ec17a1SOrr Mazor event == NL80211_RADAR_CAC_FINISHED) { 425789766727SVasanthakumar Thiagarajan cfg80211_sched_dfs_chan_update(rdev); 425826ec17a1SOrr Mazor cfg80211_check_and_end_cac(rdev); 425926ec17a1SOrr Mazor } 426089766727SVasanthakumar Thiagarajan 426189766727SVasanthakumar Thiagarajan nl80211_radar_notify(rdev, chandef, event, NULL, GFP_KERNEL); 426289766727SVasanthakumar Thiagarajan } 426389766727SVasanthakumar Thiagarajan } 426489766727SVasanthakumar Thiagarajan 4265d7be102fSJohannes Berg static int __init regulatory_init_db(void) 4266b2e1b302SLuis R. Rodriguez { 4267d7be102fSJohannes Berg int err; 4268734366deSJohannes Berg 426971e5e886SJohannes Berg /* 427071e5e886SJohannes Berg * It's possible that - due to other bugs/issues - cfg80211 427171e5e886SJohannes Berg * never called regulatory_init() below, or that it failed; 427271e5e886SJohannes Berg * in that case, don't try to do any further work here as 427371e5e886SJohannes Berg * it's doomed to lead to crashes. 427471e5e886SJohannes Berg */ 427571e5e886SJohannes Berg if (IS_ERR_OR_NULL(reg_pdev)) 427671e5e886SJohannes Berg return -EINVAL; 427771e5e886SJohannes Berg 427890a53e44SJohannes Berg err = load_builtin_regdb_keys(); 4279833a9fd2SChen Zhongjin if (err) { 4280833a9fd2SChen Zhongjin platform_device_unregister(reg_pdev); 428190a53e44SJohannes Berg return err; 4282833a9fd2SChen Zhongjin } 428390a53e44SJohannes Berg 4284ae9e4b0dSLuis R. Rodriguez /* We always try to get an update for the static regdomain */ 4285458f4f9eSJohannes Berg err = regulatory_hint_core(cfg80211_world_regdom->alpha2); 4286bcf4f99bSLuis R. Rodriguez if (err) { 428709d11800SOla Olsson if (err == -ENOMEM) { 428809d11800SOla Olsson platform_device_unregister(reg_pdev); 4289bcf4f99bSLuis R. Rodriguez return err; 429009d11800SOla Olsson } 4291bcf4f99bSLuis R. Rodriguez /* 4292bcf4f99bSLuis R. Rodriguez * N.B. kobject_uevent_env() can fail mainly for when we're out 4293bcf4f99bSLuis R. Rodriguez * memory which is handled and propagated appropriately above 4294bcf4f99bSLuis R. Rodriguez * but it can also fail during a netlink_broadcast() or during 4295bcf4f99bSLuis R. Rodriguez * early boot for call_usermodehelper(). For now treat these 4296bcf4f99bSLuis R. Rodriguez * errors as non-fatal. 4297bcf4f99bSLuis R. Rodriguez */ 4298e9c0268fSJoe Perches pr_err("kobject_uevent_env() was unable to call CRDA during init\n"); 4299bcf4f99bSLuis R. Rodriguez } 4300734366deSJohannes Berg 4301ae9e4b0dSLuis R. Rodriguez /* 4302ae9e4b0dSLuis R. Rodriguez * Finally, if the user set the module parameter treat it 4303ae9e4b0dSLuis R. Rodriguez * as a user hint. 4304ae9e4b0dSLuis R. Rodriguez */ 4305ae9e4b0dSLuis R. Rodriguez if (!is_world_regdom(ieee80211_regdom)) 430657b5ce07SLuis R. Rodriguez regulatory_hint_user(ieee80211_regdom, 430757b5ce07SLuis R. Rodriguez NL80211_USER_REG_HINT_USER); 4308ae9e4b0dSLuis R. Rodriguez 4309b2e1b302SLuis R. Rodriguez return 0; 4310b2e1b302SLuis R. Rodriguez } 4311d7be102fSJohannes Berg #ifndef MODULE 4312d7be102fSJohannes Berg late_initcall(regulatory_init_db); 4313d7be102fSJohannes Berg #endif 4314d7be102fSJohannes Berg 4315d7be102fSJohannes Berg int __init regulatory_init(void) 4316d7be102fSJohannes Berg { 4317d7be102fSJohannes Berg reg_pdev = platform_device_register_simple("regulatory", 0, NULL, 0); 4318d7be102fSJohannes Berg if (IS_ERR(reg_pdev)) 4319d7be102fSJohannes Berg return PTR_ERR(reg_pdev); 4320d7be102fSJohannes Berg 4321d7be102fSJohannes Berg rcu_assign_pointer(cfg80211_regdomain, cfg80211_world_regdom); 4322d7be102fSJohannes Berg 4323d7be102fSJohannes Berg user_alpha2[0] = '9'; 4324d7be102fSJohannes Berg user_alpha2[1] = '7'; 4325d7be102fSJohannes Berg 4326d7be102fSJohannes Berg #ifdef MODULE 4327d7be102fSJohannes Berg return regulatory_init_db(); 4328d7be102fSJohannes Berg #else 4329d7be102fSJohannes Berg return 0; 4330d7be102fSJohannes Berg #endif 4331d7be102fSJohannes Berg } 4332b2e1b302SLuis R. Rodriguez 43331a919318SJohannes Berg void regulatory_exit(void) 4334b2e1b302SLuis R. Rodriguez { 4335fe33eb39SLuis R. Rodriguez struct regulatory_request *reg_request, *tmp; 4336e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon, *btmp; 4337fe33eb39SLuis R. Rodriguez 4338fe33eb39SLuis R. Rodriguez cancel_work_sync(®_work); 4339b6863036SJohannes Berg cancel_crda_timeout_sync(); 4340ad932f04SArik Nemtsov cancel_delayed_work_sync(®_check_chans); 4341fe33eb39SLuis R. Rodriguez 43429027b149SJohannes Berg /* Lock to suppress warnings */ 434338fd2143SJohannes Berg rtnl_lock(); 4344379b82f4SJohannes Berg reset_regdomains(true, NULL); 434538fd2143SJohannes Berg rtnl_unlock(); 4346734366deSJohannes Berg 434758ebacc6SLuis R. Rodriguez dev_set_uevent_suppress(®_pdev->dev, true); 4348f6037d09SJohannes Berg 4349b2e1b302SLuis R. Rodriguez platform_device_unregister(reg_pdev); 4350734366deSJohannes Berg 4351fea9bcedSJohannes Berg list_for_each_entry_safe(reg_beacon, btmp, ®_pending_beacons, list) { 4352e38f8a7aSLuis R. Rodriguez list_del(®_beacon->list); 4353e38f8a7aSLuis R. Rodriguez kfree(reg_beacon); 4354e38f8a7aSLuis R. Rodriguez } 4355e38f8a7aSLuis R. Rodriguez 4356fea9bcedSJohannes Berg list_for_each_entry_safe(reg_beacon, btmp, ®_beacon_list, list) { 4357e38f8a7aSLuis R. Rodriguez list_del(®_beacon->list); 4358e38f8a7aSLuis R. Rodriguez kfree(reg_beacon); 4359e38f8a7aSLuis R. Rodriguez } 4360e38f8a7aSLuis R. Rodriguez 4361fea9bcedSJohannes Berg list_for_each_entry_safe(reg_request, tmp, ®_requests_list, list) { 4362fe33eb39SLuis R. Rodriguez list_del(®_request->list); 4363fe33eb39SLuis R. Rodriguez kfree(reg_request); 4364fe33eb39SLuis R. Rodriguez } 4365007f6c5eSJohannes Berg 4366007f6c5eSJohannes Berg if (!IS_ERR_OR_NULL(regdb)) 4367007f6c5eSJohannes Berg kfree(regdb); 4368e646a025SJohannes Berg if (!IS_ERR_OR_NULL(cfg80211_user_regdom)) 4369e646a025SJohannes Berg kfree(cfg80211_user_regdom); 437090a53e44SJohannes Berg 437190a53e44SJohannes Berg free_regdb_keyring(); 4372fe33eb39SLuis R. Rodriguez } 4373