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 851d62f2fSIlan Peer * Copyright (C) 2018 - 2021 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 74090a53e44SJohannes Berg static struct key *builtin_regdb_keys; 74190a53e44SJohannes Berg 74290a53e44SJohannes Berg static void __init load_keys_from_buffer(const u8 *p, unsigned int buflen) 74390a53e44SJohannes Berg { 74490a53e44SJohannes Berg const u8 *end = p + buflen; 74590a53e44SJohannes Berg size_t plen; 74690a53e44SJohannes Berg key_ref_t key; 74790a53e44SJohannes Berg 74890a53e44SJohannes Berg while (p < end) { 74990a53e44SJohannes Berg /* Each cert begins with an ASN.1 SEQUENCE tag and must be more 75090a53e44SJohannes Berg * than 256 bytes in size. 75190a53e44SJohannes Berg */ 75290a53e44SJohannes Berg if (end - p < 4) 75390a53e44SJohannes Berg goto dodgy_cert; 75490a53e44SJohannes Berg if (p[0] != 0x30 && 75590a53e44SJohannes Berg p[1] != 0x82) 75690a53e44SJohannes Berg goto dodgy_cert; 75790a53e44SJohannes Berg plen = (p[2] << 8) | p[3]; 75890a53e44SJohannes Berg plen += 4; 75990a53e44SJohannes Berg if (plen > end - p) 76090a53e44SJohannes Berg goto dodgy_cert; 76190a53e44SJohannes Berg 76290a53e44SJohannes Berg key = key_create_or_update(make_key_ref(builtin_regdb_keys, 1), 76390a53e44SJohannes Berg "asymmetric", NULL, p, plen, 764028db3e2SLinus Torvalds ((KEY_POS_ALL & ~KEY_POS_SETATTR) | 765028db3e2SLinus Torvalds KEY_USR_VIEW | KEY_USR_READ), 76690a53e44SJohannes Berg KEY_ALLOC_NOT_IN_QUOTA | 76790a53e44SJohannes Berg KEY_ALLOC_BUILT_IN | 76890a53e44SJohannes Berg KEY_ALLOC_BYPASS_RESTRICTION); 76990a53e44SJohannes Berg if (IS_ERR(key)) { 77090a53e44SJohannes Berg pr_err("Problem loading in-kernel X.509 certificate (%ld)\n", 77190a53e44SJohannes Berg PTR_ERR(key)); 77290a53e44SJohannes Berg } else { 77390a53e44SJohannes Berg pr_notice("Loaded X.509 cert '%s'\n", 77490a53e44SJohannes Berg key_ref_to_ptr(key)->description); 77590a53e44SJohannes Berg key_ref_put(key); 77690a53e44SJohannes Berg } 77790a53e44SJohannes Berg p += plen; 77890a53e44SJohannes Berg } 77990a53e44SJohannes Berg 78090a53e44SJohannes Berg return; 78190a53e44SJohannes Berg 78290a53e44SJohannes Berg dodgy_cert: 78390a53e44SJohannes Berg pr_err("Problem parsing in-kernel X.509 certificate list\n"); 78490a53e44SJohannes Berg } 78590a53e44SJohannes Berg 78690a53e44SJohannes Berg static int __init load_builtin_regdb_keys(void) 78790a53e44SJohannes Berg { 78890a53e44SJohannes Berg builtin_regdb_keys = 78990a53e44SJohannes Berg keyring_alloc(".builtin_regdb_keys", 79090a53e44SJohannes Berg KUIDT_INIT(0), KGIDT_INIT(0), current_cred(), 791028db3e2SLinus Torvalds ((KEY_POS_ALL & ~KEY_POS_SETATTR) | 792028db3e2SLinus Torvalds KEY_USR_VIEW | KEY_USR_READ | KEY_USR_SEARCH), 79390a53e44SJohannes Berg KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL); 79490a53e44SJohannes Berg if (IS_ERR(builtin_regdb_keys)) 79590a53e44SJohannes Berg return PTR_ERR(builtin_regdb_keys); 79690a53e44SJohannes Berg 79790a53e44SJohannes Berg pr_notice("Loading compiled-in X.509 certificates for regulatory database\n"); 79890a53e44SJohannes Berg 79990a53e44SJohannes Berg #ifdef CONFIG_CFG80211_USE_KERNEL_REGDB_KEYS 80090a53e44SJohannes Berg load_keys_from_buffer(shipped_regdb_certs, shipped_regdb_certs_len); 80190a53e44SJohannes Berg #endif 80288230ef1SArnd Bergmann #ifdef CONFIG_CFG80211_EXTRA_REGDB_KEYDIR 80390a53e44SJohannes Berg if (CONFIG_CFG80211_EXTRA_REGDB_KEYDIR[0] != '\0') 80490a53e44SJohannes Berg load_keys_from_buffer(extra_regdb_certs, extra_regdb_certs_len); 80590a53e44SJohannes Berg #endif 80690a53e44SJohannes Berg 80790a53e44SJohannes Berg return 0; 80890a53e44SJohannes Berg } 80990a53e44SJohannes Berg 81090a53e44SJohannes Berg static bool regdb_has_valid_signature(const u8 *data, unsigned int size) 81190a53e44SJohannes Berg { 81290a53e44SJohannes Berg const struct firmware *sig; 81390a53e44SJohannes Berg bool result; 81490a53e44SJohannes Berg 81590a53e44SJohannes Berg if (request_firmware(&sig, "regulatory.db.p7s", ®_pdev->dev)) 81690a53e44SJohannes Berg return false; 81790a53e44SJohannes Berg 81890a53e44SJohannes Berg result = verify_pkcs7_signature(data, size, sig->data, sig->size, 81990a53e44SJohannes Berg builtin_regdb_keys, 82090a53e44SJohannes Berg VERIFYING_UNSPECIFIED_SIGNATURE, 82190a53e44SJohannes Berg NULL, NULL) == 0; 82290a53e44SJohannes Berg 82390a53e44SJohannes Berg release_firmware(sig); 82490a53e44SJohannes Berg 82590a53e44SJohannes Berg return result; 82690a53e44SJohannes Berg } 82790a53e44SJohannes Berg 82890a53e44SJohannes Berg static void free_regdb_keyring(void) 82990a53e44SJohannes Berg { 83090a53e44SJohannes Berg key_put(builtin_regdb_keys); 83190a53e44SJohannes Berg } 83290a53e44SJohannes Berg #else 83390a53e44SJohannes Berg static int load_builtin_regdb_keys(void) 83490a53e44SJohannes Berg { 83590a53e44SJohannes Berg return 0; 83690a53e44SJohannes Berg } 83790a53e44SJohannes Berg 83890a53e44SJohannes Berg static bool regdb_has_valid_signature(const u8 *data, unsigned int size) 83990a53e44SJohannes Berg { 84090a53e44SJohannes Berg return true; 84190a53e44SJohannes Berg } 84290a53e44SJohannes Berg 84390a53e44SJohannes Berg static void free_regdb_keyring(void) 84490a53e44SJohannes Berg { 84590a53e44SJohannes Berg } 84690a53e44SJohannes Berg #endif /* CONFIG_CFG80211_REQUIRE_SIGNED_REGDB */ 84790a53e44SJohannes Berg 848007f6c5eSJohannes Berg static bool valid_regdb(const u8 *data, unsigned int size) 849007f6c5eSJohannes Berg { 850007f6c5eSJohannes Berg const struct fwdb_header *hdr = (void *)data; 851007f6c5eSJohannes Berg const struct fwdb_country *country; 852007f6c5eSJohannes Berg 853007f6c5eSJohannes Berg if (size < sizeof(*hdr)) 854007f6c5eSJohannes Berg return false; 855007f6c5eSJohannes Berg 856007f6c5eSJohannes Berg if (hdr->magic != cpu_to_be32(FWDB_MAGIC)) 857007f6c5eSJohannes Berg return false; 858007f6c5eSJohannes Berg 859007f6c5eSJohannes Berg if (hdr->version != cpu_to_be32(FWDB_VERSION)) 860007f6c5eSJohannes Berg return false; 861007f6c5eSJohannes Berg 86290a53e44SJohannes Berg if (!regdb_has_valid_signature(data, size)) 86390a53e44SJohannes Berg return false; 86490a53e44SJohannes Berg 865007f6c5eSJohannes Berg country = &hdr->country[0]; 866007f6c5eSJohannes Berg while ((u8 *)(country + 1) <= data + size) { 867007f6c5eSJohannes Berg if (!country->coll_ptr) 868007f6c5eSJohannes Berg break; 869007f6c5eSJohannes Berg if (!valid_country(data, size, country)) 870007f6c5eSJohannes Berg return false; 871007f6c5eSJohannes Berg country++; 872007f6c5eSJohannes Berg } 873007f6c5eSJohannes Berg 874007f6c5eSJohannes Berg return true; 875007f6c5eSJohannes Berg } 876007f6c5eSJohannes Berg 877014f5a25SStanislaw Gruszka static void set_wmm_rule(const struct fwdb_header *db, 878014f5a25SStanislaw Gruszka const struct fwdb_country *country, 879014f5a25SStanislaw Gruszka const struct fwdb_rule *rule, 880014f5a25SStanislaw Gruszka struct ieee80211_reg_rule *rrule) 881230ebaa1SHaim Dreyfuss { 882014f5a25SStanislaw Gruszka struct ieee80211_wmm_rule *wmm_rule = &rrule->wmm_rule; 883014f5a25SStanislaw Gruszka struct fwdb_wmm_rule *wmm; 884014f5a25SStanislaw Gruszka unsigned int i, wmm_ptr; 885014f5a25SStanislaw Gruszka 886014f5a25SStanislaw Gruszka wmm_ptr = be16_to_cpu(rule->wmm_ptr) << 2; 887014f5a25SStanislaw Gruszka wmm = (void *)((u8 *)db + wmm_ptr); 888014f5a25SStanislaw Gruszka 889014f5a25SStanislaw Gruszka if (!valid_wmm(wmm)) { 890014f5a25SStanislaw Gruszka pr_err("Invalid regulatory WMM rule %u-%u in domain %c%c\n", 891014f5a25SStanislaw Gruszka be32_to_cpu(rule->start), be32_to_cpu(rule->end), 892014f5a25SStanislaw Gruszka country->alpha2[0], country->alpha2[1]); 893014f5a25SStanislaw Gruszka return; 894014f5a25SStanislaw Gruszka } 895230ebaa1SHaim Dreyfuss 896230ebaa1SHaim Dreyfuss for (i = 0; i < IEEE80211_NUM_ACS; i++) { 897014f5a25SStanislaw Gruszka wmm_rule->client[i].cw_min = 898230ebaa1SHaim Dreyfuss ecw2cw((wmm->client[i].ecw & 0xf0) >> 4); 899014f5a25SStanislaw Gruszka wmm_rule->client[i].cw_max = ecw2cw(wmm->client[i].ecw & 0x0f); 900014f5a25SStanislaw Gruszka wmm_rule->client[i].aifsn = wmm->client[i].aifsn; 901014f5a25SStanislaw Gruszka wmm_rule->client[i].cot = 902014f5a25SStanislaw Gruszka 1000 * be16_to_cpu(wmm->client[i].cot); 903014f5a25SStanislaw Gruszka wmm_rule->ap[i].cw_min = ecw2cw((wmm->ap[i].ecw & 0xf0) >> 4); 904014f5a25SStanislaw Gruszka wmm_rule->ap[i].cw_max = ecw2cw(wmm->ap[i].ecw & 0x0f); 905014f5a25SStanislaw Gruszka wmm_rule->ap[i].aifsn = wmm->ap[i].aifsn; 906014f5a25SStanislaw Gruszka wmm_rule->ap[i].cot = 1000 * be16_to_cpu(wmm->ap[i].cot); 907230ebaa1SHaim Dreyfuss } 90838cb87eeSStanislaw Gruszka 90938cb87eeSStanislaw Gruszka rrule->has_wmm = true; 910230ebaa1SHaim Dreyfuss } 911230ebaa1SHaim Dreyfuss 91219d3577eSHaim Dreyfuss static int __regdb_query_wmm(const struct fwdb_header *db, 91319d3577eSHaim Dreyfuss const struct fwdb_country *country, int freq, 914014f5a25SStanislaw Gruszka struct ieee80211_reg_rule *rrule) 91519d3577eSHaim Dreyfuss { 91619d3577eSHaim Dreyfuss unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; 91719d3577eSHaim Dreyfuss struct fwdb_collection *coll = (void *)((u8 *)db + ptr); 91819d3577eSHaim Dreyfuss int i; 91919d3577eSHaim Dreyfuss 92019d3577eSHaim Dreyfuss for (i = 0; i < coll->n_rules; i++) { 92119d3577eSHaim Dreyfuss __be16 *rules_ptr = (void *)((u8 *)coll + ALIGN(coll->len, 2)); 92219d3577eSHaim Dreyfuss unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2; 923014f5a25SStanislaw Gruszka struct fwdb_rule *rule = (void *)((u8 *)db + rule_ptr); 92419d3577eSHaim Dreyfuss 925014f5a25SStanislaw Gruszka if (rule->len < offsetofend(struct fwdb_rule, wmm_ptr)) 92619d3577eSHaim Dreyfuss continue; 92719d3577eSHaim Dreyfuss 928014f5a25SStanislaw Gruszka if (freq >= KHZ_TO_MHZ(be32_to_cpu(rule->start)) && 929014f5a25SStanislaw Gruszka freq <= KHZ_TO_MHZ(be32_to_cpu(rule->end))) { 930014f5a25SStanislaw Gruszka set_wmm_rule(db, country, rule, rrule); 93119d3577eSHaim Dreyfuss return 0; 93219d3577eSHaim Dreyfuss } 93319d3577eSHaim Dreyfuss } 93419d3577eSHaim Dreyfuss 93519d3577eSHaim Dreyfuss return -ENODATA; 93619d3577eSHaim Dreyfuss } 93719d3577eSHaim Dreyfuss 93838cb87eeSStanislaw Gruszka int reg_query_regdb_wmm(char *alpha2, int freq, struct ieee80211_reg_rule *rule) 93919d3577eSHaim Dreyfuss { 94019d3577eSHaim Dreyfuss const struct fwdb_header *hdr = regdb; 94119d3577eSHaim Dreyfuss const struct fwdb_country *country; 94219d3577eSHaim Dreyfuss 9435247a77cSHaim Dreyfuss if (!regdb) 9445247a77cSHaim Dreyfuss return -ENODATA; 9455247a77cSHaim Dreyfuss 94619d3577eSHaim Dreyfuss if (IS_ERR(regdb)) 94719d3577eSHaim Dreyfuss return PTR_ERR(regdb); 94819d3577eSHaim Dreyfuss 94919d3577eSHaim Dreyfuss country = &hdr->country[0]; 95019d3577eSHaim Dreyfuss while (country->coll_ptr) { 95119d3577eSHaim Dreyfuss if (alpha2_equal(alpha2, country->alpha2)) 95238cb87eeSStanislaw Gruszka return __regdb_query_wmm(regdb, country, freq, rule); 95319d3577eSHaim Dreyfuss 95419d3577eSHaim Dreyfuss country++; 95519d3577eSHaim Dreyfuss } 95619d3577eSHaim Dreyfuss 95719d3577eSHaim Dreyfuss return -ENODATA; 95819d3577eSHaim Dreyfuss } 95919d3577eSHaim Dreyfuss EXPORT_SYMBOL(reg_query_regdb_wmm); 96019d3577eSHaim Dreyfuss 961007f6c5eSJohannes Berg static int regdb_query_country(const struct fwdb_header *db, 962007f6c5eSJohannes Berg const struct fwdb_country *country) 963007f6c5eSJohannes Berg { 964007f6c5eSJohannes Berg unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; 965007f6c5eSJohannes Berg struct fwdb_collection *coll = (void *)((u8 *)db + ptr); 966007f6c5eSJohannes Berg struct ieee80211_regdomain *regdom; 9679f8c7136SGustavo A. R. Silva unsigned int i; 968007f6c5eSJohannes Berg 9699f8c7136SGustavo A. R. Silva regdom = kzalloc(struct_size(regdom, reg_rules, coll->n_rules), 9709f8c7136SGustavo A. R. Silva GFP_KERNEL); 971007f6c5eSJohannes Berg if (!regdom) 972007f6c5eSJohannes Berg return -ENOMEM; 973007f6c5eSJohannes Berg 974007f6c5eSJohannes Berg regdom->n_reg_rules = coll->n_rules; 975007f6c5eSJohannes Berg regdom->alpha2[0] = country->alpha2[0]; 976007f6c5eSJohannes Berg regdom->alpha2[1] = country->alpha2[1]; 977007f6c5eSJohannes Berg regdom->dfs_region = coll->dfs_region; 978007f6c5eSJohannes Berg 979007f6c5eSJohannes Berg for (i = 0; i < regdom->n_reg_rules; i++) { 980007f6c5eSJohannes Berg __be16 *rules_ptr = (void *)((u8 *)coll + ALIGN(coll->len, 2)); 981007f6c5eSJohannes Berg unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2; 982007f6c5eSJohannes Berg struct fwdb_rule *rule = (void *)((u8 *)db + rule_ptr); 983007f6c5eSJohannes Berg struct ieee80211_reg_rule *rrule = ®dom->reg_rules[i]; 984007f6c5eSJohannes Berg 985007f6c5eSJohannes Berg rrule->freq_range.start_freq_khz = be32_to_cpu(rule->start); 986007f6c5eSJohannes Berg rrule->freq_range.end_freq_khz = be32_to_cpu(rule->end); 987007f6c5eSJohannes Berg rrule->freq_range.max_bandwidth_khz = be32_to_cpu(rule->max_bw); 988007f6c5eSJohannes Berg 989007f6c5eSJohannes Berg rrule->power_rule.max_antenna_gain = 0; 990007f6c5eSJohannes Berg rrule->power_rule.max_eirp = be16_to_cpu(rule->max_eirp); 991007f6c5eSJohannes Berg 992007f6c5eSJohannes Berg rrule->flags = 0; 993007f6c5eSJohannes Berg if (rule->flags & FWDB_FLAG_NO_OFDM) 994007f6c5eSJohannes Berg rrule->flags |= NL80211_RRF_NO_OFDM; 995007f6c5eSJohannes Berg if (rule->flags & FWDB_FLAG_NO_OUTDOOR) 996007f6c5eSJohannes Berg rrule->flags |= NL80211_RRF_NO_OUTDOOR; 997007f6c5eSJohannes Berg if (rule->flags & FWDB_FLAG_DFS) 998007f6c5eSJohannes Berg rrule->flags |= NL80211_RRF_DFS; 999007f6c5eSJohannes Berg if (rule->flags & FWDB_FLAG_NO_IR) 1000007f6c5eSJohannes Berg rrule->flags |= NL80211_RRF_NO_IR; 1001007f6c5eSJohannes Berg if (rule->flags & FWDB_FLAG_AUTO_BW) 1002007f6c5eSJohannes Berg rrule->flags |= NL80211_RRF_AUTO_BW; 1003007f6c5eSJohannes Berg 1004007f6c5eSJohannes Berg rrule->dfs_cac_ms = 0; 1005007f6c5eSJohannes Berg 1006007f6c5eSJohannes Berg /* handle optional data */ 1007007f6c5eSJohannes Berg if (rule->len >= offsetofend(struct fwdb_rule, cac_timeout)) 1008007f6c5eSJohannes Berg rrule->dfs_cac_ms = 1009007f6c5eSJohannes Berg 1000 * be16_to_cpu(rule->cac_timeout); 1010014f5a25SStanislaw Gruszka if (rule->len >= offsetofend(struct fwdb_rule, wmm_ptr)) 1011014f5a25SStanislaw Gruszka set_wmm_rule(db, country, rule, rrule); 1012230ebaa1SHaim Dreyfuss } 1013007f6c5eSJohannes Berg 1014007f6c5eSJohannes Berg return reg_schedule_apply(regdom); 1015007f6c5eSJohannes Berg } 1016007f6c5eSJohannes Berg 1017007f6c5eSJohannes Berg static int query_regdb(const char *alpha2) 1018007f6c5eSJohannes Berg { 1019007f6c5eSJohannes Berg const struct fwdb_header *hdr = regdb; 1020007f6c5eSJohannes Berg const struct fwdb_country *country; 1021007f6c5eSJohannes Berg 10221ea4ff3eSJohannes Berg ASSERT_RTNL(); 10231ea4ff3eSJohannes Berg 1024007f6c5eSJohannes Berg if (IS_ERR(regdb)) 1025007f6c5eSJohannes Berg return PTR_ERR(regdb); 1026007f6c5eSJohannes Berg 1027007f6c5eSJohannes Berg country = &hdr->country[0]; 1028007f6c5eSJohannes Berg while (country->coll_ptr) { 1029007f6c5eSJohannes Berg if (alpha2_equal(alpha2, country->alpha2)) 1030007f6c5eSJohannes Berg return regdb_query_country(regdb, country); 1031007f6c5eSJohannes Berg country++; 1032007f6c5eSJohannes Berg } 1033007f6c5eSJohannes Berg 1034007f6c5eSJohannes Berg return -ENODATA; 1035007f6c5eSJohannes Berg } 1036007f6c5eSJohannes Berg 1037007f6c5eSJohannes Berg static void regdb_fw_cb(const struct firmware *fw, void *context) 1038007f6c5eSJohannes Berg { 10391ea4ff3eSJohannes Berg int set_error = 0; 10401ea4ff3eSJohannes Berg bool restore = true; 1041007f6c5eSJohannes Berg void *db; 1042007f6c5eSJohannes Berg 1043007f6c5eSJohannes Berg if (!fw) { 1044007f6c5eSJohannes Berg pr_info("failed to load regulatory.db\n"); 10451ea4ff3eSJohannes Berg set_error = -ENODATA; 10461ea4ff3eSJohannes Berg } else if (!valid_regdb(fw->data, fw->size)) { 104790a53e44SJohannes Berg pr_info("loaded regulatory.db is malformed or signature is missing/invalid\n"); 10481ea4ff3eSJohannes Berg set_error = -EINVAL; 1049007f6c5eSJohannes Berg } 1050007f6c5eSJohannes Berg 1051007f6c5eSJohannes Berg rtnl_lock(); 1052faae54adSChaitanya Tata if (regdb && !IS_ERR(regdb)) { 1053faae54adSChaitanya Tata /* negative case - a bug 1054faae54adSChaitanya Tata * positive case - can happen due to race in case of multiple cb's in 1055faae54adSChaitanya Tata * queue, due to usage of asynchronous callback 1056faae54adSChaitanya Tata * 1057faae54adSChaitanya Tata * Either case, just restore and free new db. 1058faae54adSChaitanya Tata */ 10591ea4ff3eSJohannes Berg } else if (set_error) { 10601ea4ff3eSJohannes Berg regdb = ERR_PTR(set_error); 10611ea4ff3eSJohannes Berg } else if (fw) { 10621ea4ff3eSJohannes Berg db = kmemdup(fw->data, fw->size, GFP_KERNEL); 10631ea4ff3eSJohannes Berg if (db) { 10641ea4ff3eSJohannes Berg regdb = db; 10651ea4ff3eSJohannes Berg restore = context && query_regdb(context); 10661ea4ff3eSJohannes Berg } else { 10671ea4ff3eSJohannes Berg restore = true; 10681ea4ff3eSJohannes Berg } 10691ea4ff3eSJohannes Berg } 10701ea4ff3eSJohannes Berg 10711ea4ff3eSJohannes Berg if (restore) 1072e646a025SJohannes Berg restore_regulatory_settings(true, false); 10731ea4ff3eSJohannes Berg 1074007f6c5eSJohannes Berg rtnl_unlock(); 10751ea4ff3eSJohannes Berg 1076007f6c5eSJohannes Berg kfree(context); 10771ea4ff3eSJohannes Berg 10781ea4ff3eSJohannes Berg release_firmware(fw); 1079007f6c5eSJohannes Berg } 1080007f6c5eSJohannes Berg 1081007f6c5eSJohannes Berg static int query_regdb_file(const char *alpha2) 1082007f6c5eSJohannes Berg { 10831ea4ff3eSJohannes Berg ASSERT_RTNL(); 10841ea4ff3eSJohannes Berg 1085007f6c5eSJohannes Berg if (regdb) 1086007f6c5eSJohannes Berg return query_regdb(alpha2); 1087007f6c5eSJohannes Berg 1088007f6c5eSJohannes Berg alpha2 = kmemdup(alpha2, 2, GFP_KERNEL); 1089007f6c5eSJohannes Berg if (!alpha2) 1090007f6c5eSJohannes Berg return -ENOMEM; 1091007f6c5eSJohannes Berg 1092007f6c5eSJohannes Berg return request_firmware_nowait(THIS_MODULE, true, "regulatory.db", 1093007f6c5eSJohannes Berg ®_pdev->dev, GFP_KERNEL, 1094007f6c5eSJohannes Berg (void *)alpha2, regdb_fw_cb); 1095007f6c5eSJohannes Berg } 1096007f6c5eSJohannes Berg 10971ea4ff3eSJohannes Berg int reg_reload_regdb(void) 10981ea4ff3eSJohannes Berg { 10991ea4ff3eSJohannes Berg const struct firmware *fw; 11001ea4ff3eSJohannes Berg void *db; 11011ea4ff3eSJohannes Berg int err; 11021eda9191SFinn Behrens const struct ieee80211_regdomain *current_regdomain; 11031eda9191SFinn Behrens struct regulatory_request *request; 11041ea4ff3eSJohannes Berg 11051ea4ff3eSJohannes Berg err = request_firmware(&fw, "regulatory.db", ®_pdev->dev); 11061ea4ff3eSJohannes Berg if (err) 11071ea4ff3eSJohannes Berg return err; 11081ea4ff3eSJohannes Berg 11091ea4ff3eSJohannes Berg if (!valid_regdb(fw->data, fw->size)) { 11101ea4ff3eSJohannes Berg err = -ENODATA; 11111ea4ff3eSJohannes Berg goto out; 11121ea4ff3eSJohannes Berg } 11131ea4ff3eSJohannes Berg 11141ea4ff3eSJohannes Berg db = kmemdup(fw->data, fw->size, GFP_KERNEL); 11151ea4ff3eSJohannes Berg if (!db) { 11161ea4ff3eSJohannes Berg err = -ENOMEM; 11171ea4ff3eSJohannes Berg goto out; 11181ea4ff3eSJohannes Berg } 11191ea4ff3eSJohannes Berg 11201ea4ff3eSJohannes Berg rtnl_lock(); 11211ea4ff3eSJohannes Berg if (!IS_ERR_OR_NULL(regdb)) 11221ea4ff3eSJohannes Berg kfree(regdb); 11231ea4ff3eSJohannes Berg regdb = db; 11241ea4ff3eSJohannes Berg 11251eda9191SFinn Behrens /* reset regulatory domain */ 11261eda9191SFinn Behrens current_regdomain = get_cfg80211_regdom(); 11271eda9191SFinn Behrens 11281eda9191SFinn Behrens request = kzalloc(sizeof(*request), GFP_KERNEL); 11291eda9191SFinn Behrens if (!request) { 11301eda9191SFinn Behrens err = -ENOMEM; 11311eda9191SFinn Behrens goto out_unlock; 11321eda9191SFinn Behrens } 11331eda9191SFinn Behrens 11341eda9191SFinn Behrens request->wiphy_idx = WIPHY_IDX_INVALID; 11351eda9191SFinn Behrens request->alpha2[0] = current_regdomain->alpha2[0]; 11361eda9191SFinn Behrens request->alpha2[1] = current_regdomain->alpha2[1]; 113737d33114SFinn Behrens request->initiator = NL80211_REGDOM_SET_BY_CORE; 11381eda9191SFinn Behrens request->user_reg_hint_type = NL80211_USER_REG_HINT_USER; 11391eda9191SFinn Behrens 11401eda9191SFinn Behrens reg_process_hint(request); 11411eda9191SFinn Behrens 11421eda9191SFinn Behrens out_unlock: 11431eda9191SFinn Behrens rtnl_unlock(); 11441ea4ff3eSJohannes Berg out: 11451ea4ff3eSJohannes Berg release_firmware(fw); 11461ea4ff3eSJohannes Berg return err; 11471ea4ff3eSJohannes Berg } 11481ea4ff3eSJohannes Berg 1149cecbb069SJohannes Berg static bool reg_query_database(struct regulatory_request *request) 1150fe6631ffSLuis R. Rodriguez { 1151007f6c5eSJohannes Berg if (query_regdb_file(request->alpha2) == 0) 1152007f6c5eSJohannes Berg return true; 1153007f6c5eSJohannes Berg 1154c7d319e5SJohannes Berg if (call_crda(request->alpha2) == 0) 1155c7d319e5SJohannes Berg return true; 1156c7d319e5SJohannes Berg 1157c7d319e5SJohannes Berg return false; 1158fe6631ffSLuis R. Rodriguez } 1159fe6631ffSLuis R. Rodriguez 1160e438768fSLuis R. Rodriguez bool reg_is_valid_request(const char *alpha2) 1161b2e1b302SLuis R. Rodriguez { 1162c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 116361405e97SLuis R. Rodriguez 1164c492db37SJohannes Berg if (!lr || lr->processed) 1165f6037d09SJohannes Berg return false; 1166f6037d09SJohannes Berg 1167c492db37SJohannes Berg return alpha2_equal(lr->alpha2, alpha2); 1168b2e1b302SLuis R. Rodriguez } 1169b2e1b302SLuis R. Rodriguez 1170e3961af1SJanusz Dziedzic static const struct ieee80211_regdomain *reg_get_regdomain(struct wiphy *wiphy) 1171e3961af1SJanusz Dziedzic { 1172e3961af1SJanusz Dziedzic struct regulatory_request *lr = get_last_request(); 1173e3961af1SJanusz Dziedzic 1174e3961af1SJanusz Dziedzic /* 1175e3961af1SJanusz Dziedzic * Follow the driver's regulatory domain, if present, unless a country 1176e3961af1SJanusz Dziedzic * IE has been processed or a user wants to help complaince further 1177e3961af1SJanusz Dziedzic */ 1178e3961af1SJanusz Dziedzic if (lr->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE && 1179e3961af1SJanusz Dziedzic lr->initiator != NL80211_REGDOM_SET_BY_USER && 1180e3961af1SJanusz Dziedzic wiphy->regd) 1181e3961af1SJanusz Dziedzic return get_wiphy_regdom(wiphy); 1182e3961af1SJanusz Dziedzic 1183e3961af1SJanusz Dziedzic return get_cfg80211_regdom(); 1184e3961af1SJanusz Dziedzic } 1185e3961af1SJanusz Dziedzic 1186a6d4a534SArik Nemtsov static unsigned int 1187a6d4a534SArik Nemtsov reg_get_max_bandwidth_from_range(const struct ieee80211_regdomain *rd, 118897524820SJanusz Dziedzic const struct ieee80211_reg_rule *rule) 118997524820SJanusz Dziedzic { 119097524820SJanusz Dziedzic const struct ieee80211_freq_range *freq_range = &rule->freq_range; 119197524820SJanusz Dziedzic const struct ieee80211_freq_range *freq_range_tmp; 119297524820SJanusz Dziedzic const struct ieee80211_reg_rule *tmp; 119397524820SJanusz Dziedzic u32 start_freq, end_freq, idx, no; 119497524820SJanusz Dziedzic 119597524820SJanusz Dziedzic for (idx = 0; idx < rd->n_reg_rules; idx++) 119697524820SJanusz Dziedzic if (rule == &rd->reg_rules[idx]) 119797524820SJanusz Dziedzic break; 119897524820SJanusz Dziedzic 119997524820SJanusz Dziedzic if (idx == rd->n_reg_rules) 120097524820SJanusz Dziedzic return 0; 120197524820SJanusz Dziedzic 120297524820SJanusz Dziedzic /* get start_freq */ 120397524820SJanusz Dziedzic no = idx; 120497524820SJanusz Dziedzic 120597524820SJanusz Dziedzic while (no) { 120697524820SJanusz Dziedzic tmp = &rd->reg_rules[--no]; 120797524820SJanusz Dziedzic freq_range_tmp = &tmp->freq_range; 120897524820SJanusz Dziedzic 120997524820SJanusz Dziedzic if (freq_range_tmp->end_freq_khz < freq_range->start_freq_khz) 121097524820SJanusz Dziedzic break; 121197524820SJanusz Dziedzic 121297524820SJanusz Dziedzic freq_range = freq_range_tmp; 121397524820SJanusz Dziedzic } 121497524820SJanusz Dziedzic 121597524820SJanusz Dziedzic start_freq = freq_range->start_freq_khz; 121697524820SJanusz Dziedzic 121797524820SJanusz Dziedzic /* get end_freq */ 121897524820SJanusz Dziedzic freq_range = &rule->freq_range; 121997524820SJanusz Dziedzic no = idx; 122097524820SJanusz Dziedzic 122197524820SJanusz Dziedzic while (no < rd->n_reg_rules - 1) { 122297524820SJanusz Dziedzic tmp = &rd->reg_rules[++no]; 122397524820SJanusz Dziedzic freq_range_tmp = &tmp->freq_range; 122497524820SJanusz Dziedzic 122597524820SJanusz Dziedzic if (freq_range_tmp->start_freq_khz > freq_range->end_freq_khz) 122697524820SJanusz Dziedzic break; 122797524820SJanusz Dziedzic 122897524820SJanusz Dziedzic freq_range = freq_range_tmp; 122997524820SJanusz Dziedzic } 123097524820SJanusz Dziedzic 123197524820SJanusz Dziedzic end_freq = freq_range->end_freq_khz; 123297524820SJanusz Dziedzic 123397524820SJanusz Dziedzic return end_freq - start_freq; 123497524820SJanusz Dziedzic } 123597524820SJanusz Dziedzic 1236a6d4a534SArik Nemtsov unsigned int reg_get_max_bandwidth(const struct ieee80211_regdomain *rd, 1237a6d4a534SArik Nemtsov const struct ieee80211_reg_rule *rule) 1238a6d4a534SArik Nemtsov { 1239a6d4a534SArik Nemtsov unsigned int bw = reg_get_max_bandwidth_from_range(rd, rule); 1240a6d4a534SArik Nemtsov 1241*c2b3d769SSriram R if (rule->flags & NL80211_RRF_NO_320MHZ) 1242*c2b3d769SSriram R bw = min_t(unsigned int, bw, MHZ_TO_KHZ(160)); 1243a6d4a534SArik Nemtsov if (rule->flags & NL80211_RRF_NO_160MHZ) 1244a6d4a534SArik Nemtsov bw = min_t(unsigned int, bw, MHZ_TO_KHZ(80)); 1245a6d4a534SArik Nemtsov if (rule->flags & NL80211_RRF_NO_80MHZ) 1246a6d4a534SArik Nemtsov bw = min_t(unsigned int, bw, MHZ_TO_KHZ(40)); 1247a6d4a534SArik Nemtsov 1248a6d4a534SArik Nemtsov /* 1249a6d4a534SArik Nemtsov * HT40+/HT40- limits are handled per-channel. Only limit BW if both 1250a6d4a534SArik Nemtsov * are not allowed. 1251a6d4a534SArik Nemtsov */ 1252a6d4a534SArik Nemtsov if (rule->flags & NL80211_RRF_NO_HT40MINUS && 1253a6d4a534SArik Nemtsov rule->flags & NL80211_RRF_NO_HT40PLUS) 1254a6d4a534SArik Nemtsov bw = min_t(unsigned int, bw, MHZ_TO_KHZ(20)); 1255a6d4a534SArik Nemtsov 1256a6d4a534SArik Nemtsov return bw; 1257a6d4a534SArik Nemtsov } 1258a6d4a534SArik Nemtsov 1259b2e1b302SLuis R. Rodriguez /* Sanity check on a regulatory rule */ 1260a3d2eaf0SJohannes Berg static bool is_valid_reg_rule(const struct ieee80211_reg_rule *rule) 1261b2e1b302SLuis R. Rodriguez { 1262a3d2eaf0SJohannes Berg const struct ieee80211_freq_range *freq_range = &rule->freq_range; 1263b2e1b302SLuis R. Rodriguez u32 freq_diff; 1264b2e1b302SLuis R. Rodriguez 126591e99004SLuis R. Rodriguez if (freq_range->start_freq_khz <= 0 || freq_range->end_freq_khz <= 0) 1266b2e1b302SLuis R. Rodriguez return false; 1267b2e1b302SLuis R. Rodriguez 1268b2e1b302SLuis R. Rodriguez if (freq_range->start_freq_khz > freq_range->end_freq_khz) 1269b2e1b302SLuis R. Rodriguez return false; 1270b2e1b302SLuis R. Rodriguez 1271b2e1b302SLuis R. Rodriguez freq_diff = freq_range->end_freq_khz - freq_range->start_freq_khz; 1272b2e1b302SLuis R. Rodriguez 1273bd05f28eSRoel Kluin if (freq_range->end_freq_khz <= freq_range->start_freq_khz || 1274bd05f28eSRoel Kluin freq_range->max_bandwidth_khz > freq_diff) 1275b2e1b302SLuis R. Rodriguez return false; 1276b2e1b302SLuis R. Rodriguez 1277b2e1b302SLuis R. Rodriguez return true; 1278b2e1b302SLuis R. Rodriguez } 1279b2e1b302SLuis R. Rodriguez 1280a3d2eaf0SJohannes Berg static bool is_valid_rd(const struct ieee80211_regdomain *rd) 1281b2e1b302SLuis R. Rodriguez { 1282a3d2eaf0SJohannes Berg const struct ieee80211_reg_rule *reg_rule = NULL; 1283b2e1b302SLuis R. Rodriguez unsigned int i; 1284b2e1b302SLuis R. Rodriguez 1285b2e1b302SLuis R. Rodriguez if (!rd->n_reg_rules) 1286b2e1b302SLuis R. Rodriguez return false; 1287b2e1b302SLuis R. Rodriguez 128888dc1c3fSLuis R. Rodriguez if (WARN_ON(rd->n_reg_rules > NL80211_MAX_SUPP_REG_RULES)) 128988dc1c3fSLuis R. Rodriguez return false; 129088dc1c3fSLuis R. Rodriguez 1291b2e1b302SLuis R. Rodriguez for (i = 0; i < rd->n_reg_rules; i++) { 1292b2e1b302SLuis R. Rodriguez reg_rule = &rd->reg_rules[i]; 1293b2e1b302SLuis R. Rodriguez if (!is_valid_reg_rule(reg_rule)) 1294b2e1b302SLuis R. Rodriguez return false; 1295b2e1b302SLuis R. Rodriguez } 1296b2e1b302SLuis R. Rodriguez 1297b2e1b302SLuis R. Rodriguez return true; 1298b2e1b302SLuis R. Rodriguez } 1299b2e1b302SLuis R. Rodriguez 13000c7dc45dSLuis R. Rodriguez /** 13010c7dc45dSLuis R. Rodriguez * freq_in_rule_band - tells us if a frequency is in a frequency band 13020c7dc45dSLuis R. Rodriguez * @freq_range: frequency rule we want to query 13030c7dc45dSLuis R. Rodriguez * @freq_khz: frequency we are inquiring about 13040c7dc45dSLuis R. Rodriguez * 13050c7dc45dSLuis R. Rodriguez * This lets us know if a specific frequency rule is or is not relevant to 13060c7dc45dSLuis R. Rodriguez * a specific frequency's band. Bands are device specific and artificial 130764629b9dSVladimir Kondratiev * definitions (the "2.4 GHz band", the "5 GHz band" and the "60GHz band"), 130864629b9dSVladimir Kondratiev * however it is safe for now to assume that a frequency rule should not be 130964629b9dSVladimir Kondratiev * part of a frequency's band if the start freq or end freq are off by more 131093183bdbSChaitanya Tata * than 2 GHz for the 2.4 and 5 GHz bands, and by more than 20 GHz for the 131164629b9dSVladimir Kondratiev * 60 GHz band. 13120c7dc45dSLuis R. Rodriguez * This resolution can be lowered and should be considered as we add 13130c7dc45dSLuis R. Rodriguez * regulatory rule support for other "bands". 13140c7dc45dSLuis R. Rodriguez **/ 13150c7dc45dSLuis R. Rodriguez static bool freq_in_rule_band(const struct ieee80211_freq_range *freq_range, 13160c7dc45dSLuis R. Rodriguez u32 freq_khz) 13170c7dc45dSLuis R. Rodriguez { 13180c7dc45dSLuis R. Rodriguez #define ONE_GHZ_IN_KHZ 1000000 131964629b9dSVladimir Kondratiev /* 132064629b9dSVladimir Kondratiev * From 802.11ad: directional multi-gigabit (DMG): 132164629b9dSVladimir Kondratiev * Pertaining to operation in a frequency band containing a channel 132264629b9dSVladimir Kondratiev * with the Channel starting frequency above 45 GHz. 132364629b9dSVladimir Kondratiev */ 132464629b9dSVladimir Kondratiev u32 limit = freq_khz > 45 * ONE_GHZ_IN_KHZ ? 132593183bdbSChaitanya Tata 20 * ONE_GHZ_IN_KHZ : 2 * ONE_GHZ_IN_KHZ; 132664629b9dSVladimir Kondratiev if (abs(freq_khz - freq_range->start_freq_khz) <= limit) 13270c7dc45dSLuis R. Rodriguez return true; 132864629b9dSVladimir Kondratiev if (abs(freq_khz - freq_range->end_freq_khz) <= limit) 13290c7dc45dSLuis R. Rodriguez return true; 13300c7dc45dSLuis R. Rodriguez return false; 13310c7dc45dSLuis R. Rodriguez #undef ONE_GHZ_IN_KHZ 13320c7dc45dSLuis R. Rodriguez } 13330c7dc45dSLuis R. Rodriguez 1334fb1fc7adSLuis R. Rodriguez /* 1335adbfb058SLuis R. Rodriguez * Later on we can perhaps use the more restrictive DFS 1336adbfb058SLuis R. Rodriguez * region but we don't have information for that yet so 1337adbfb058SLuis R. Rodriguez * for now simply disallow conflicts. 1338adbfb058SLuis R. Rodriguez */ 1339adbfb058SLuis R. Rodriguez static enum nl80211_dfs_regions 1340adbfb058SLuis R. Rodriguez reg_intersect_dfs_region(const enum nl80211_dfs_regions dfs_region1, 1341adbfb058SLuis R. Rodriguez const enum nl80211_dfs_regions dfs_region2) 1342adbfb058SLuis R. Rodriguez { 1343adbfb058SLuis R. Rodriguez if (dfs_region1 != dfs_region2) 1344adbfb058SLuis R. Rodriguez return NL80211_DFS_UNSET; 1345adbfb058SLuis R. Rodriguez return dfs_region1; 1346adbfb058SLuis R. Rodriguez } 1347adbfb058SLuis R. Rodriguez 134808a75a88SIlan Peer static void reg_wmm_rules_intersect(const struct ieee80211_wmm_ac *wmm_ac1, 134908a75a88SIlan Peer const struct ieee80211_wmm_ac *wmm_ac2, 135008a75a88SIlan Peer struct ieee80211_wmm_ac *intersect) 135108a75a88SIlan Peer { 135208a75a88SIlan Peer intersect->cw_min = max_t(u16, wmm_ac1->cw_min, wmm_ac2->cw_min); 135308a75a88SIlan Peer intersect->cw_max = max_t(u16, wmm_ac1->cw_max, wmm_ac2->cw_max); 135408a75a88SIlan Peer intersect->cot = min_t(u16, wmm_ac1->cot, wmm_ac2->cot); 135508a75a88SIlan Peer intersect->aifsn = max_t(u8, wmm_ac1->aifsn, wmm_ac2->aifsn); 135608a75a88SIlan Peer } 135708a75a88SIlan Peer 1358adbfb058SLuis R. Rodriguez /* 1359fb1fc7adSLuis R. Rodriguez * Helper for regdom_intersect(), this does the real 1360fb1fc7adSLuis R. Rodriguez * mathematical intersection fun 1361fb1fc7adSLuis R. Rodriguez */ 136297524820SJanusz Dziedzic static int reg_rules_intersect(const struct ieee80211_regdomain *rd1, 136397524820SJanusz Dziedzic const struct ieee80211_regdomain *rd2, 136497524820SJanusz Dziedzic const struct ieee80211_reg_rule *rule1, 13659c96477dSLuis R. Rodriguez const struct ieee80211_reg_rule *rule2, 13669c96477dSLuis R. Rodriguez struct ieee80211_reg_rule *intersected_rule) 13679c96477dSLuis R. Rodriguez { 13689c96477dSLuis R. Rodriguez const struct ieee80211_freq_range *freq_range1, *freq_range2; 13699c96477dSLuis R. Rodriguez struct ieee80211_freq_range *freq_range; 13709c96477dSLuis R. Rodriguez const struct ieee80211_power_rule *power_rule1, *power_rule2; 13719c96477dSLuis R. Rodriguez struct ieee80211_power_rule *power_rule; 137208a75a88SIlan Peer const struct ieee80211_wmm_rule *wmm_rule1, *wmm_rule2; 137308a75a88SIlan Peer struct ieee80211_wmm_rule *wmm_rule; 137497524820SJanusz Dziedzic u32 freq_diff, max_bandwidth1, max_bandwidth2; 13759c96477dSLuis R. Rodriguez 13769c96477dSLuis R. Rodriguez freq_range1 = &rule1->freq_range; 13779c96477dSLuis R. Rodriguez freq_range2 = &rule2->freq_range; 13789c96477dSLuis R. Rodriguez freq_range = &intersected_rule->freq_range; 13799c96477dSLuis R. Rodriguez 13809c96477dSLuis R. Rodriguez power_rule1 = &rule1->power_rule; 13819c96477dSLuis R. Rodriguez power_rule2 = &rule2->power_rule; 13829c96477dSLuis R. Rodriguez power_rule = &intersected_rule->power_rule; 13839c96477dSLuis R. Rodriguez 138408a75a88SIlan Peer wmm_rule1 = &rule1->wmm_rule; 138508a75a88SIlan Peer wmm_rule2 = &rule2->wmm_rule; 138608a75a88SIlan Peer wmm_rule = &intersected_rule->wmm_rule; 138708a75a88SIlan Peer 13889c96477dSLuis R. Rodriguez freq_range->start_freq_khz = max(freq_range1->start_freq_khz, 13899c96477dSLuis R. Rodriguez freq_range2->start_freq_khz); 13909c96477dSLuis R. Rodriguez freq_range->end_freq_khz = min(freq_range1->end_freq_khz, 13919c96477dSLuis R. Rodriguez freq_range2->end_freq_khz); 139297524820SJanusz Dziedzic 139397524820SJanusz Dziedzic max_bandwidth1 = freq_range1->max_bandwidth_khz; 139497524820SJanusz Dziedzic max_bandwidth2 = freq_range2->max_bandwidth_khz; 139597524820SJanusz Dziedzic 1396b0dfd2eaSJanusz Dziedzic if (rule1->flags & NL80211_RRF_AUTO_BW) 139797524820SJanusz Dziedzic max_bandwidth1 = reg_get_max_bandwidth(rd1, rule1); 1398b0dfd2eaSJanusz Dziedzic if (rule2->flags & NL80211_RRF_AUTO_BW) 139997524820SJanusz Dziedzic max_bandwidth2 = reg_get_max_bandwidth(rd2, rule2); 140097524820SJanusz Dziedzic 140197524820SJanusz Dziedzic freq_range->max_bandwidth_khz = min(max_bandwidth1, max_bandwidth2); 14029c96477dSLuis R. Rodriguez 1403b0dfd2eaSJanusz Dziedzic intersected_rule->flags = rule1->flags | rule2->flags; 1404b0dfd2eaSJanusz Dziedzic 1405b0dfd2eaSJanusz Dziedzic /* 1406b0dfd2eaSJanusz Dziedzic * In case NL80211_RRF_AUTO_BW requested for both rules 1407b0dfd2eaSJanusz Dziedzic * set AUTO_BW in intersected rule also. Next we will 1408b0dfd2eaSJanusz Dziedzic * calculate BW correctly in handle_channel function. 1409b0dfd2eaSJanusz Dziedzic * In other case remove AUTO_BW flag while we calculate 1410b0dfd2eaSJanusz Dziedzic * maximum bandwidth correctly and auto calculation is 1411b0dfd2eaSJanusz Dziedzic * not required. 1412b0dfd2eaSJanusz Dziedzic */ 1413b0dfd2eaSJanusz Dziedzic if ((rule1->flags & NL80211_RRF_AUTO_BW) && 1414b0dfd2eaSJanusz Dziedzic (rule2->flags & NL80211_RRF_AUTO_BW)) 1415b0dfd2eaSJanusz Dziedzic intersected_rule->flags |= NL80211_RRF_AUTO_BW; 1416b0dfd2eaSJanusz Dziedzic else 1417b0dfd2eaSJanusz Dziedzic intersected_rule->flags &= ~NL80211_RRF_AUTO_BW; 1418b0dfd2eaSJanusz Dziedzic 14199c96477dSLuis R. Rodriguez freq_diff = freq_range->end_freq_khz - freq_range->start_freq_khz; 14209c96477dSLuis R. Rodriguez if (freq_range->max_bandwidth_khz > freq_diff) 14219c96477dSLuis R. Rodriguez freq_range->max_bandwidth_khz = freq_diff; 14229c96477dSLuis R. Rodriguez 14239c96477dSLuis R. Rodriguez power_rule->max_eirp = min(power_rule1->max_eirp, 14249c96477dSLuis R. Rodriguez power_rule2->max_eirp); 14259c96477dSLuis R. Rodriguez power_rule->max_antenna_gain = min(power_rule1->max_antenna_gain, 14269c96477dSLuis R. Rodriguez power_rule2->max_antenna_gain); 14279c96477dSLuis R. Rodriguez 1428089027e5SJanusz Dziedzic intersected_rule->dfs_cac_ms = max(rule1->dfs_cac_ms, 1429089027e5SJanusz Dziedzic rule2->dfs_cac_ms); 1430089027e5SJanusz Dziedzic 143108a75a88SIlan Peer if (rule1->has_wmm && rule2->has_wmm) { 143208a75a88SIlan Peer u8 ac; 143308a75a88SIlan Peer 143408a75a88SIlan Peer for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) { 143508a75a88SIlan Peer reg_wmm_rules_intersect(&wmm_rule1->client[ac], 143608a75a88SIlan Peer &wmm_rule2->client[ac], 143708a75a88SIlan Peer &wmm_rule->client[ac]); 143808a75a88SIlan Peer reg_wmm_rules_intersect(&wmm_rule1->ap[ac], 143908a75a88SIlan Peer &wmm_rule2->ap[ac], 144008a75a88SIlan Peer &wmm_rule->ap[ac]); 144108a75a88SIlan Peer } 144208a75a88SIlan Peer 144308a75a88SIlan Peer intersected_rule->has_wmm = true; 144408a75a88SIlan Peer } else if (rule1->has_wmm) { 144508a75a88SIlan Peer *wmm_rule = *wmm_rule1; 144608a75a88SIlan Peer intersected_rule->has_wmm = true; 144708a75a88SIlan Peer } else if (rule2->has_wmm) { 144808a75a88SIlan Peer *wmm_rule = *wmm_rule2; 144908a75a88SIlan Peer intersected_rule->has_wmm = true; 145008a75a88SIlan Peer } else { 145108a75a88SIlan Peer intersected_rule->has_wmm = false; 145208a75a88SIlan Peer } 145308a75a88SIlan Peer 14549c96477dSLuis R. Rodriguez if (!is_valid_reg_rule(intersected_rule)) 14559c96477dSLuis R. Rodriguez return -EINVAL; 14569c96477dSLuis R. Rodriguez 14579c96477dSLuis R. Rodriguez return 0; 14589c96477dSLuis R. Rodriguez } 14599c96477dSLuis R. Rodriguez 1460a62a1aedSEliad Peller /* check whether old rule contains new rule */ 1461a62a1aedSEliad Peller static bool rule_contains(struct ieee80211_reg_rule *r1, 1462a62a1aedSEliad Peller struct ieee80211_reg_rule *r2) 1463a62a1aedSEliad Peller { 1464a62a1aedSEliad Peller /* for simplicity, currently consider only same flags */ 1465a62a1aedSEliad Peller if (r1->flags != r2->flags) 1466a62a1aedSEliad Peller return false; 1467a62a1aedSEliad Peller 1468a62a1aedSEliad Peller /* verify r1 is more restrictive */ 1469a62a1aedSEliad Peller if ((r1->power_rule.max_antenna_gain > 1470a62a1aedSEliad Peller r2->power_rule.max_antenna_gain) || 1471a62a1aedSEliad Peller r1->power_rule.max_eirp > r2->power_rule.max_eirp) 1472a62a1aedSEliad Peller return false; 1473a62a1aedSEliad Peller 1474a62a1aedSEliad Peller /* make sure r2's range is contained within r1 */ 1475a62a1aedSEliad Peller if (r1->freq_range.start_freq_khz > r2->freq_range.start_freq_khz || 1476a62a1aedSEliad Peller r1->freq_range.end_freq_khz < r2->freq_range.end_freq_khz) 1477a62a1aedSEliad Peller return false; 1478a62a1aedSEliad Peller 1479a62a1aedSEliad Peller /* and finally verify that r1.max_bw >= r2.max_bw */ 1480a62a1aedSEliad Peller if (r1->freq_range.max_bandwidth_khz < 1481a62a1aedSEliad Peller r2->freq_range.max_bandwidth_khz) 1482a62a1aedSEliad Peller return false; 1483a62a1aedSEliad Peller 1484a62a1aedSEliad Peller return true; 1485a62a1aedSEliad Peller } 1486a62a1aedSEliad Peller 1487a62a1aedSEliad Peller /* add or extend current rules. do nothing if rule is already contained */ 1488a62a1aedSEliad Peller static void add_rule(struct ieee80211_reg_rule *rule, 1489a62a1aedSEliad Peller struct ieee80211_reg_rule *reg_rules, u32 *n_rules) 1490a62a1aedSEliad Peller { 1491a62a1aedSEliad Peller struct ieee80211_reg_rule *tmp_rule; 1492a62a1aedSEliad Peller int i; 1493a62a1aedSEliad Peller 1494a62a1aedSEliad Peller for (i = 0; i < *n_rules; i++) { 1495a62a1aedSEliad Peller tmp_rule = ®_rules[i]; 1496a62a1aedSEliad Peller /* rule is already contained - do nothing */ 1497a62a1aedSEliad Peller if (rule_contains(tmp_rule, rule)) 1498a62a1aedSEliad Peller return; 1499a62a1aedSEliad Peller 1500a62a1aedSEliad Peller /* extend rule if possible */ 1501a62a1aedSEliad Peller if (rule_contains(rule, tmp_rule)) { 1502a62a1aedSEliad Peller memcpy(tmp_rule, rule, sizeof(*rule)); 1503a62a1aedSEliad Peller return; 1504a62a1aedSEliad Peller } 1505a62a1aedSEliad Peller } 1506a62a1aedSEliad Peller 1507a62a1aedSEliad Peller memcpy(®_rules[*n_rules], rule, sizeof(*rule)); 1508a62a1aedSEliad Peller (*n_rules)++; 1509a62a1aedSEliad Peller } 1510a62a1aedSEliad Peller 15119c96477dSLuis R. Rodriguez /** 15129c96477dSLuis R. Rodriguez * regdom_intersect - do the intersection between two regulatory domains 15139c96477dSLuis R. Rodriguez * @rd1: first regulatory domain 15149c96477dSLuis R. Rodriguez * @rd2: second regulatory domain 15159c96477dSLuis R. Rodriguez * 15169c96477dSLuis R. Rodriguez * Use this function to get the intersection between two regulatory domains. 15179c96477dSLuis R. Rodriguez * Once completed we will mark the alpha2 for the rd as intersected, "98", 15189c96477dSLuis R. Rodriguez * as no one single alpha2 can represent this regulatory domain. 15199c96477dSLuis R. Rodriguez * 15209c96477dSLuis R. Rodriguez * Returns a pointer to the regulatory domain structure which will hold the 15219c96477dSLuis R. Rodriguez * resulting intersection of rules between rd1 and rd2. We will 15229c96477dSLuis R. Rodriguez * kzalloc() this structure for you. 15239c96477dSLuis R. Rodriguez */ 15241a919318SJohannes Berg static struct ieee80211_regdomain * 15251a919318SJohannes Berg regdom_intersect(const struct ieee80211_regdomain *rd1, 15269c96477dSLuis R. Rodriguez const struct ieee80211_regdomain *rd2) 15279c96477dSLuis R. Rodriguez { 15289f8c7136SGustavo A. R. Silva int r; 15299c96477dSLuis R. Rodriguez unsigned int x, y; 1530a62a1aedSEliad Peller unsigned int num_rules = 0; 15319c96477dSLuis R. Rodriguez const struct ieee80211_reg_rule *rule1, *rule2; 1532a62a1aedSEliad Peller struct ieee80211_reg_rule intersected_rule; 15339c96477dSLuis R. Rodriguez struct ieee80211_regdomain *rd; 15349c96477dSLuis R. Rodriguez 15359c96477dSLuis R. Rodriguez if (!rd1 || !rd2) 15369c96477dSLuis R. Rodriguez return NULL; 15379c96477dSLuis R. Rodriguez 1538fb1fc7adSLuis R. Rodriguez /* 1539fb1fc7adSLuis R. Rodriguez * First we get a count of the rules we'll need, then we actually 15409c96477dSLuis R. Rodriguez * build them. This is to so we can malloc() and free() a 15419c96477dSLuis R. Rodriguez * regdomain once. The reason we use reg_rules_intersect() here 15429c96477dSLuis R. Rodriguez * is it will return -EINVAL if the rule computed makes no sense. 1543fb1fc7adSLuis R. Rodriguez * All rules that do check out OK are valid. 1544fb1fc7adSLuis R. Rodriguez */ 15459c96477dSLuis R. Rodriguez 15469c96477dSLuis R. Rodriguez for (x = 0; x < rd1->n_reg_rules; x++) { 15479c96477dSLuis R. Rodriguez rule1 = &rd1->reg_rules[x]; 15489c96477dSLuis R. Rodriguez for (y = 0; y < rd2->n_reg_rules; y++) { 15499c96477dSLuis R. Rodriguez rule2 = &rd2->reg_rules[y]; 155097524820SJanusz Dziedzic if (!reg_rules_intersect(rd1, rd2, rule1, rule2, 1551a62a1aedSEliad Peller &intersected_rule)) 15529c96477dSLuis R. Rodriguez num_rules++; 15539c96477dSLuis R. Rodriguez } 15549c96477dSLuis R. Rodriguez } 15559c96477dSLuis R. Rodriguez 15569c96477dSLuis R. Rodriguez if (!num_rules) 15579c96477dSLuis R. Rodriguez return NULL; 15589c96477dSLuis R. Rodriguez 15599f8c7136SGustavo A. R. Silva rd = kzalloc(struct_size(rd, reg_rules, num_rules), GFP_KERNEL); 15609c96477dSLuis R. Rodriguez if (!rd) 15619c96477dSLuis R. Rodriguez return NULL; 15629c96477dSLuis R. Rodriguez 1563a62a1aedSEliad Peller for (x = 0; x < rd1->n_reg_rules; x++) { 15649c96477dSLuis R. Rodriguez rule1 = &rd1->reg_rules[x]; 1565a62a1aedSEliad Peller for (y = 0; y < rd2->n_reg_rules; y++) { 15669c96477dSLuis R. Rodriguez rule2 = &rd2->reg_rules[y]; 156797524820SJanusz Dziedzic r = reg_rules_intersect(rd1, rd2, rule1, rule2, 1568a62a1aedSEliad Peller &intersected_rule); 1569fb1fc7adSLuis R. Rodriguez /* 1570fb1fc7adSLuis R. Rodriguez * No need to memset here the intersected rule here as 1571fb1fc7adSLuis R. Rodriguez * we're not using the stack anymore 1572fb1fc7adSLuis R. Rodriguez */ 15739c96477dSLuis R. Rodriguez if (r) 15749c96477dSLuis R. Rodriguez continue; 1575a62a1aedSEliad Peller 1576a62a1aedSEliad Peller add_rule(&intersected_rule, rd->reg_rules, 1577a62a1aedSEliad Peller &rd->n_reg_rules); 15789c96477dSLuis R. Rodriguez } 15799c96477dSLuis R. Rodriguez } 15809c96477dSLuis R. Rodriguez 15819c96477dSLuis R. Rodriguez rd->alpha2[0] = '9'; 15829c96477dSLuis R. Rodriguez rd->alpha2[1] = '8'; 1583adbfb058SLuis R. Rodriguez rd->dfs_region = reg_intersect_dfs_region(rd1->dfs_region, 1584adbfb058SLuis R. Rodriguez rd2->dfs_region); 15859c96477dSLuis R. Rodriguez 15869c96477dSLuis R. Rodriguez return rd; 15879c96477dSLuis R. Rodriguez } 15889c96477dSLuis R. Rodriguez 1589fb1fc7adSLuis R. Rodriguez /* 1590fb1fc7adSLuis R. Rodriguez * XXX: add support for the rest of enum nl80211_reg_rule_flags, we may 1591fb1fc7adSLuis R. Rodriguez * want to just have the channel structure use these 1592fb1fc7adSLuis R. Rodriguez */ 1593b2e1b302SLuis R. Rodriguez static u32 map_regdom_flags(u32 rd_flags) 1594b2e1b302SLuis R. Rodriguez { 1595b2e1b302SLuis R. Rodriguez u32 channel_flags = 0; 15968fe02e16SLuis R. Rodriguez if (rd_flags & NL80211_RRF_NO_IR_ALL) 15978fe02e16SLuis R. Rodriguez channel_flags |= IEEE80211_CHAN_NO_IR; 1598b2e1b302SLuis R. Rodriguez if (rd_flags & NL80211_RRF_DFS) 1599b2e1b302SLuis R. Rodriguez channel_flags |= IEEE80211_CHAN_RADAR; 160003f6b084SSeth Forshee if (rd_flags & NL80211_RRF_NO_OFDM) 160103f6b084SSeth Forshee channel_flags |= IEEE80211_CHAN_NO_OFDM; 1602570dbde1SDavid Spinadel if (rd_flags & NL80211_RRF_NO_OUTDOOR) 1603570dbde1SDavid Spinadel channel_flags |= IEEE80211_CHAN_INDOOR_ONLY; 160406f207fcSArik Nemtsov if (rd_flags & NL80211_RRF_IR_CONCURRENT) 160506f207fcSArik Nemtsov channel_flags |= IEEE80211_CHAN_IR_CONCURRENT; 1606a6d4a534SArik Nemtsov if (rd_flags & NL80211_RRF_NO_HT40MINUS) 1607a6d4a534SArik Nemtsov channel_flags |= IEEE80211_CHAN_NO_HT40MINUS; 1608a6d4a534SArik Nemtsov if (rd_flags & NL80211_RRF_NO_HT40PLUS) 1609a6d4a534SArik Nemtsov channel_flags |= IEEE80211_CHAN_NO_HT40PLUS; 1610a6d4a534SArik Nemtsov if (rd_flags & NL80211_RRF_NO_80MHZ) 1611a6d4a534SArik Nemtsov channel_flags |= IEEE80211_CHAN_NO_80MHZ; 1612a6d4a534SArik Nemtsov if (rd_flags & NL80211_RRF_NO_160MHZ) 1613a6d4a534SArik Nemtsov channel_flags |= IEEE80211_CHAN_NO_160MHZ; 16141e61d82cSHaim Dreyfuss if (rd_flags & NL80211_RRF_NO_HE) 16151e61d82cSHaim Dreyfuss channel_flags |= IEEE80211_CHAN_NO_HE; 1616*c2b3d769SSriram R if (rd_flags & NL80211_RRF_NO_320MHZ) 1617*c2b3d769SSriram R channel_flags |= IEEE80211_CHAN_NO_320MHZ; 1618b2e1b302SLuis R. Rodriguez return channel_flags; 1619b2e1b302SLuis R. Rodriguez } 1620b2e1b302SLuis R. Rodriguez 1621361c9c8bSJohannes Berg static const struct ieee80211_reg_rule * 162249172874SMichal Sojka freq_reg_info_regd(u32 center_freq, 16234edd5698SMatthias May const struct ieee80211_regdomain *regd, u32 bw) 16248318d78aSJohannes Berg { 16258318d78aSJohannes Berg int i; 16260c7dc45dSLuis R. Rodriguez bool band_rule_found = false; 1627038659e7SLuis R. Rodriguez bool bw_fits = false; 1628038659e7SLuis R. Rodriguez 16293e0c3ff3SLuis R. Rodriguez if (!regd) 1630361c9c8bSJohannes Berg return ERR_PTR(-EINVAL); 1631b2e1b302SLuis R. Rodriguez 16323e0c3ff3SLuis R. Rodriguez for (i = 0; i < regd->n_reg_rules; i++) { 1633b2e1b302SLuis R. Rodriguez const struct ieee80211_reg_rule *rr; 1634b2e1b302SLuis R. Rodriguez const struct ieee80211_freq_range *fr = NULL; 1635b2e1b302SLuis R. Rodriguez 16363e0c3ff3SLuis R. Rodriguez rr = ®d->reg_rules[i]; 1637b2e1b302SLuis R. Rodriguez fr = &rr->freq_range; 16380c7dc45dSLuis R. Rodriguez 1639fb1fc7adSLuis R. Rodriguez /* 1640fb1fc7adSLuis R. Rodriguez * We only need to know if one frequency rule was 1641cc5a639bSRandy Dunlap * in center_freq's band, that's enough, so let's 1642fb1fc7adSLuis R. Rodriguez * not overwrite it once found 1643fb1fc7adSLuis R. Rodriguez */ 16440c7dc45dSLuis R. Rodriguez if (!band_rule_found) 16450c7dc45dSLuis R. Rodriguez band_rule_found = freq_in_rule_band(fr, center_freq); 16460c7dc45dSLuis R. Rodriguez 16474787cfa0SRafał Miłecki bw_fits = cfg80211_does_bw_fit_range(fr, center_freq, bw); 16480c7dc45dSLuis R. Rodriguez 1649361c9c8bSJohannes Berg if (band_rule_found && bw_fits) 1650361c9c8bSJohannes Berg return rr; 16518318d78aSJohannes Berg } 16528318d78aSJohannes Berg 16530c7dc45dSLuis R. Rodriguez if (!band_rule_found) 1654361c9c8bSJohannes Berg return ERR_PTR(-ERANGE); 16550c7dc45dSLuis R. Rodriguez 1656361c9c8bSJohannes Berg return ERR_PTR(-EINVAL); 1657b2e1b302SLuis R. Rodriguez } 1658b2e1b302SLuis R. Rodriguez 16598de1c63bSJohannes Berg static const struct ieee80211_reg_rule * 16608de1c63bSJohannes Berg __freq_reg_info(struct wiphy *wiphy, u32 center_freq, u32 min_bw) 16614edd5698SMatthias May { 16624edd5698SMatthias May const struct ieee80211_regdomain *regd = reg_get_regdomain(wiphy); 1663c7ed0e68SColin Ian King static const u32 bws[] = {0, 1, 2, 4, 5, 8, 10, 16, 20}; 16649e6d5126SLuca Coelho const struct ieee80211_reg_rule *reg_rule = ERR_PTR(-ERANGE); 166568dbad8cSThomas Pedersen int i = ARRAY_SIZE(bws) - 1; 16664edd5698SMatthias May u32 bw; 16674edd5698SMatthias May 166868dbad8cSThomas Pedersen for (bw = MHZ_TO_KHZ(bws[i]); bw >= min_bw; bw = MHZ_TO_KHZ(bws[i--])) { 166949172874SMichal Sojka reg_rule = freq_reg_info_regd(center_freq, regd, bw); 16704edd5698SMatthias May if (!IS_ERR(reg_rule)) 16714edd5698SMatthias May return reg_rule; 16724edd5698SMatthias May } 16734edd5698SMatthias May 16744edd5698SMatthias May return reg_rule; 16754edd5698SMatthias May } 16764edd5698SMatthias May 1677361c9c8bSJohannes Berg const struct ieee80211_reg_rule *freq_reg_info(struct wiphy *wiphy, 1678361c9c8bSJohannes Berg u32 center_freq) 16791fa25e41SLuis R. Rodriguez { 168068dbad8cSThomas Pedersen u32 min_bw = center_freq < MHZ_TO_KHZ(1000) ? 1 : 20; 168168dbad8cSThomas Pedersen 168268dbad8cSThomas Pedersen return __freq_reg_info(wiphy, center_freq, MHZ_TO_KHZ(min_bw)); 16831fa25e41SLuis R. Rodriguez } 16844f366c5dSJohn W. Linville EXPORT_SYMBOL(freq_reg_info); 1685b2e1b302SLuis R. Rodriguez 1686034c6d6eSLuis R. Rodriguez const char *reg_initiator_name(enum nl80211_reg_initiator initiator) 1687926a0a09SLuis R. Rodriguez { 1688926a0a09SLuis R. Rodriguez switch (initiator) { 1689926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_CORE: 1690034c6d6eSLuis R. Rodriguez return "core"; 1691926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_USER: 1692034c6d6eSLuis R. Rodriguez return "user"; 1693926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_DRIVER: 1694034c6d6eSLuis R. Rodriguez return "driver"; 1695926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_COUNTRY_IE: 16968db0c433SToke Høiland-Jørgensen return "country element"; 1697926a0a09SLuis R. Rodriguez default: 1698926a0a09SLuis R. Rodriguez WARN_ON(1); 1699034c6d6eSLuis R. Rodriguez return "bug"; 1700926a0a09SLuis R. Rodriguez } 1701926a0a09SLuis R. Rodriguez } 1702034c6d6eSLuis R. Rodriguez EXPORT_SYMBOL(reg_initiator_name); 1703e702d3cfSLuis R. Rodriguez 17041aeb135fSMichal Sojka static uint32_t reg_rule_to_chan_bw_flags(const struct ieee80211_regdomain *regd, 17051aeb135fSMichal Sojka const struct ieee80211_reg_rule *reg_rule, 17061aeb135fSMichal Sojka const struct ieee80211_channel *chan) 17071aeb135fSMichal Sojka { 17081aeb135fSMichal Sojka const struct ieee80211_freq_range *freq_range = NULL; 1709934f4c7dSThomas Pedersen u32 max_bandwidth_khz, center_freq_khz, bw_flags = 0; 171068dbad8cSThomas Pedersen bool is_s1g = chan->band == NL80211_BAND_S1GHZ; 17111aeb135fSMichal Sojka 17121aeb135fSMichal Sojka freq_range = ®_rule->freq_range; 17131aeb135fSMichal Sojka 17141aeb135fSMichal Sojka max_bandwidth_khz = freq_range->max_bandwidth_khz; 1715934f4c7dSThomas Pedersen center_freq_khz = ieee80211_channel_to_khz(chan); 17161aeb135fSMichal Sojka /* Check if auto calculation requested */ 17171aeb135fSMichal Sojka if (reg_rule->flags & NL80211_RRF_AUTO_BW) 17181aeb135fSMichal Sojka max_bandwidth_khz = reg_get_max_bandwidth(regd, reg_rule); 17191aeb135fSMichal Sojka 17201aeb135fSMichal Sojka /* If we get a reg_rule we can assume that at least 5Mhz fit */ 17214787cfa0SRafał Miłecki if (!cfg80211_does_bw_fit_range(freq_range, 1722934f4c7dSThomas Pedersen center_freq_khz, 17231aeb135fSMichal Sojka MHZ_TO_KHZ(10))) 17241aeb135fSMichal Sojka bw_flags |= IEEE80211_CHAN_NO_10MHZ; 17254787cfa0SRafał Miłecki if (!cfg80211_does_bw_fit_range(freq_range, 1726934f4c7dSThomas Pedersen center_freq_khz, 17271aeb135fSMichal Sojka MHZ_TO_KHZ(20))) 17281aeb135fSMichal Sojka bw_flags |= IEEE80211_CHAN_NO_20MHZ; 17291aeb135fSMichal Sojka 173068dbad8cSThomas Pedersen if (is_s1g) { 173168dbad8cSThomas Pedersen /* S1G is strict about non overlapping channels. We can 173268dbad8cSThomas Pedersen * calculate which bandwidth is allowed per channel by finding 173368dbad8cSThomas Pedersen * the largest bandwidth which cleanly divides the freq_range. 173468dbad8cSThomas Pedersen */ 173568dbad8cSThomas Pedersen int edge_offset; 173668dbad8cSThomas Pedersen int ch_bw = max_bandwidth_khz; 173768dbad8cSThomas Pedersen 173868dbad8cSThomas Pedersen while (ch_bw) { 173968dbad8cSThomas Pedersen edge_offset = (center_freq_khz - ch_bw / 2) - 174068dbad8cSThomas Pedersen freq_range->start_freq_khz; 174168dbad8cSThomas Pedersen if (edge_offset % ch_bw == 0) { 174268dbad8cSThomas Pedersen switch (KHZ_TO_MHZ(ch_bw)) { 174368dbad8cSThomas Pedersen case 1: 174468dbad8cSThomas Pedersen bw_flags |= IEEE80211_CHAN_1MHZ; 174568dbad8cSThomas Pedersen break; 174668dbad8cSThomas Pedersen case 2: 174768dbad8cSThomas Pedersen bw_flags |= IEEE80211_CHAN_2MHZ; 174868dbad8cSThomas Pedersen break; 174968dbad8cSThomas Pedersen case 4: 175068dbad8cSThomas Pedersen bw_flags |= IEEE80211_CHAN_4MHZ; 175168dbad8cSThomas Pedersen break; 175268dbad8cSThomas Pedersen case 8: 175368dbad8cSThomas Pedersen bw_flags |= IEEE80211_CHAN_8MHZ; 175468dbad8cSThomas Pedersen break; 175568dbad8cSThomas Pedersen case 16: 175668dbad8cSThomas Pedersen bw_flags |= IEEE80211_CHAN_16MHZ; 175768dbad8cSThomas Pedersen break; 175868dbad8cSThomas Pedersen default: 175968dbad8cSThomas Pedersen /* If we got here, no bandwidths fit on 176068dbad8cSThomas Pedersen * this frequency, ie. band edge. 176168dbad8cSThomas Pedersen */ 176268dbad8cSThomas Pedersen bw_flags |= IEEE80211_CHAN_DISABLED; 176368dbad8cSThomas Pedersen break; 176468dbad8cSThomas Pedersen } 176568dbad8cSThomas Pedersen break; 176668dbad8cSThomas Pedersen } 176768dbad8cSThomas Pedersen ch_bw /= 2; 176868dbad8cSThomas Pedersen } 176968dbad8cSThomas Pedersen } else { 17701aeb135fSMichal Sojka if (max_bandwidth_khz < MHZ_TO_KHZ(10)) 17711aeb135fSMichal Sojka bw_flags |= IEEE80211_CHAN_NO_10MHZ; 17721aeb135fSMichal Sojka if (max_bandwidth_khz < MHZ_TO_KHZ(20)) 17731aeb135fSMichal Sojka bw_flags |= IEEE80211_CHAN_NO_20MHZ; 17741aeb135fSMichal Sojka if (max_bandwidth_khz < MHZ_TO_KHZ(40)) 17751aeb135fSMichal Sojka bw_flags |= IEEE80211_CHAN_NO_HT40; 17761aeb135fSMichal Sojka if (max_bandwidth_khz < MHZ_TO_KHZ(80)) 17771aeb135fSMichal Sojka bw_flags |= IEEE80211_CHAN_NO_80MHZ; 17781aeb135fSMichal Sojka if (max_bandwidth_khz < MHZ_TO_KHZ(160)) 17791aeb135fSMichal Sojka bw_flags |= IEEE80211_CHAN_NO_160MHZ; 1780*c2b3d769SSriram R if (max_bandwidth_khz < MHZ_TO_KHZ(320)) 1781*c2b3d769SSriram R bw_flags |= IEEE80211_CHAN_NO_320MHZ; 178268dbad8cSThomas Pedersen } 17831aeb135fSMichal Sojka return bw_flags; 17841aeb135fSMichal Sojka } 17851aeb135fSMichal Sojka 17867c9ff7e2SMarkus Theil static void handle_channel_single_rule(struct wiphy *wiphy, 17877ca43d03SLuis R. Rodriguez enum nl80211_reg_initiator initiator, 17887c9ff7e2SMarkus Theil struct ieee80211_channel *chan, 17897c9ff7e2SMarkus Theil u32 flags, 17907c9ff7e2SMarkus Theil struct regulatory_request *lr, 17917c9ff7e2SMarkus Theil struct wiphy *request_wiphy, 17927c9ff7e2SMarkus Theil const struct ieee80211_reg_rule *reg_rule) 1793b2e1b302SLuis R. Rodriguez { 17947c9ff7e2SMarkus Theil u32 bw_flags = 0; 1795b2e1b302SLuis R. Rodriguez const struct ieee80211_power_rule *power_rule = NULL; 179697524820SJanusz Dziedzic const struct ieee80211_regdomain *regd; 1797a92a3ce7SLuis R. Rodriguez 1798b0dfd2eaSJanusz Dziedzic regd = reg_get_regdomain(wiphy); 1799e702d3cfSLuis R. Rodriguez 1800b2e1b302SLuis R. Rodriguez power_rule = ®_rule->power_rule; 18011aeb135fSMichal Sojka bw_flags = reg_rule_to_chan_bw_flags(regd, reg_rule, chan); 1802b2e1b302SLuis R. Rodriguez 1803c492db37SJohannes Berg if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER && 1804806a9e39SLuis R. Rodriguez request_wiphy && request_wiphy == wiphy && 1805a2f73b6cSLuis R. Rodriguez request_wiphy->regulatory_flags & REGULATORY_STRICT_REG) { 1806fb1fc7adSLuis R. Rodriguez /* 180725985edcSLucas De Marchi * This guarantees the driver's requested regulatory domain 1808f976376dSLuis R. Rodriguez * will always be used as a base for further regulatory 1809fb1fc7adSLuis R. Rodriguez * settings 1810fb1fc7adSLuis R. Rodriguez */ 1811f976376dSLuis R. Rodriguez chan->flags = chan->orig_flags = 1812038659e7SLuis R. Rodriguez map_regdom_flags(reg_rule->flags) | bw_flags; 1813f976376dSLuis R. Rodriguez chan->max_antenna_gain = chan->orig_mag = 1814f976376dSLuis R. Rodriguez (int) MBI_TO_DBI(power_rule->max_antenna_gain); 1815279f0f55SFelix Fietkau chan->max_reg_power = chan->max_power = chan->orig_mpwr = 1816f976376dSLuis R. Rodriguez (int) MBM_TO_DBM(power_rule->max_eirp); 18174f267c11SJanusz Dziedzic 18184f267c11SJanusz Dziedzic if (chan->flags & IEEE80211_CHAN_RADAR) { 18194f267c11SJanusz Dziedzic chan->dfs_cac_ms = IEEE80211_DFS_MIN_CAC_TIME_MS; 18204f267c11SJanusz Dziedzic if (reg_rule->dfs_cac_ms) 18214f267c11SJanusz Dziedzic chan->dfs_cac_ms = reg_rule->dfs_cac_ms; 18224f267c11SJanusz Dziedzic } 18234f267c11SJanusz Dziedzic 1824f976376dSLuis R. Rodriguez return; 1825f976376dSLuis R. Rodriguez } 1826f976376dSLuis R. Rodriguez 182704f39047SSimon Wunderlich chan->dfs_state = NL80211_DFS_USABLE; 182804f39047SSimon Wunderlich chan->dfs_state_entered = jiffies; 182904f39047SSimon Wunderlich 1830aa3d7eefSRajkumar Manoharan chan->beacon_found = false; 1831038659e7SLuis R. Rodriguez chan->flags = flags | bw_flags | map_regdom_flags(reg_rule->flags); 18321a919318SJohannes Berg chan->max_antenna_gain = 18331a919318SJohannes Berg min_t(int, chan->orig_mag, 18341a919318SJohannes Berg MBI_TO_DBI(power_rule->max_antenna_gain)); 1835eccc068eSHong Wu chan->max_reg_power = (int) MBM_TO_DBM(power_rule->max_eirp); 1836089027e5SJanusz Dziedzic 1837089027e5SJanusz Dziedzic if (chan->flags & IEEE80211_CHAN_RADAR) { 1838089027e5SJanusz Dziedzic if (reg_rule->dfs_cac_ms) 1839089027e5SJanusz Dziedzic chan->dfs_cac_ms = reg_rule->dfs_cac_ms; 1840089027e5SJanusz Dziedzic else 1841089027e5SJanusz Dziedzic chan->dfs_cac_ms = IEEE80211_DFS_MIN_CAC_TIME_MS; 1842089027e5SJanusz Dziedzic } 1843089027e5SJanusz Dziedzic 18445e31fc08SStanislaw Gruszka if (chan->orig_mpwr) { 18455e31fc08SStanislaw Gruszka /* 1846a09a85a0SLuis R. Rodriguez * Devices that use REGULATORY_COUNTRY_IE_FOLLOW_POWER 1847a09a85a0SLuis R. Rodriguez * will always follow the passed country IE power settings. 18485e31fc08SStanislaw Gruszka */ 18495e31fc08SStanislaw Gruszka if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE && 1850a09a85a0SLuis R. Rodriguez wiphy->regulatory_flags & REGULATORY_COUNTRY_IE_FOLLOW_POWER) 18515e31fc08SStanislaw Gruszka chan->max_power = chan->max_reg_power; 18525e31fc08SStanislaw Gruszka else 18535e31fc08SStanislaw Gruszka chan->max_power = min(chan->orig_mpwr, 18545e31fc08SStanislaw Gruszka chan->max_reg_power); 18555e31fc08SStanislaw Gruszka } else 18565e31fc08SStanislaw Gruszka chan->max_power = chan->max_reg_power; 18578318d78aSJohannes Berg } 18588318d78aSJohannes Berg 185912adee3cSMarkus Theil static void handle_channel_adjacent_rules(struct wiphy *wiphy, 186012adee3cSMarkus Theil enum nl80211_reg_initiator initiator, 186112adee3cSMarkus Theil struct ieee80211_channel *chan, 186212adee3cSMarkus Theil u32 flags, 186312adee3cSMarkus Theil struct regulatory_request *lr, 186412adee3cSMarkus Theil struct wiphy *request_wiphy, 186512adee3cSMarkus Theil const struct ieee80211_reg_rule *rrule1, 186612adee3cSMarkus Theil const struct ieee80211_reg_rule *rrule2, 186712adee3cSMarkus Theil struct ieee80211_freq_range *comb_range) 186812adee3cSMarkus Theil { 186912adee3cSMarkus Theil u32 bw_flags1 = 0; 187012adee3cSMarkus Theil u32 bw_flags2 = 0; 187112adee3cSMarkus Theil const struct ieee80211_power_rule *power_rule1 = NULL; 187212adee3cSMarkus Theil const struct ieee80211_power_rule *power_rule2 = NULL; 187312adee3cSMarkus Theil const struct ieee80211_regdomain *regd; 187412adee3cSMarkus Theil 187512adee3cSMarkus Theil regd = reg_get_regdomain(wiphy); 187612adee3cSMarkus Theil 187712adee3cSMarkus Theil power_rule1 = &rrule1->power_rule; 187812adee3cSMarkus Theil power_rule2 = &rrule2->power_rule; 187912adee3cSMarkus Theil bw_flags1 = reg_rule_to_chan_bw_flags(regd, rrule1, chan); 188012adee3cSMarkus Theil bw_flags2 = reg_rule_to_chan_bw_flags(regd, rrule2, chan); 188112adee3cSMarkus Theil 188212adee3cSMarkus Theil if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER && 188312adee3cSMarkus Theil request_wiphy && request_wiphy == wiphy && 188412adee3cSMarkus Theil request_wiphy->regulatory_flags & REGULATORY_STRICT_REG) { 188512adee3cSMarkus Theil /* This guarantees the driver's requested regulatory domain 188612adee3cSMarkus Theil * will always be used as a base for further regulatory 188712adee3cSMarkus Theil * settings 188812adee3cSMarkus Theil */ 188912adee3cSMarkus Theil chan->flags = 189012adee3cSMarkus Theil map_regdom_flags(rrule1->flags) | 189112adee3cSMarkus Theil map_regdom_flags(rrule2->flags) | 189212adee3cSMarkus Theil bw_flags1 | 189312adee3cSMarkus Theil bw_flags2; 189412adee3cSMarkus Theil chan->orig_flags = chan->flags; 189512adee3cSMarkus Theil chan->max_antenna_gain = 189612adee3cSMarkus Theil min_t(int, MBI_TO_DBI(power_rule1->max_antenna_gain), 189712adee3cSMarkus Theil MBI_TO_DBI(power_rule2->max_antenna_gain)); 189812adee3cSMarkus Theil chan->orig_mag = chan->max_antenna_gain; 189912adee3cSMarkus Theil chan->max_reg_power = 190012adee3cSMarkus Theil min_t(int, MBM_TO_DBM(power_rule1->max_eirp), 190112adee3cSMarkus Theil MBM_TO_DBM(power_rule2->max_eirp)); 190212adee3cSMarkus Theil chan->max_power = chan->max_reg_power; 190312adee3cSMarkus Theil chan->orig_mpwr = chan->max_reg_power; 190412adee3cSMarkus Theil 190512adee3cSMarkus Theil if (chan->flags & IEEE80211_CHAN_RADAR) { 190612adee3cSMarkus Theil chan->dfs_cac_ms = IEEE80211_DFS_MIN_CAC_TIME_MS; 190712adee3cSMarkus Theil if (rrule1->dfs_cac_ms || rrule2->dfs_cac_ms) 190812adee3cSMarkus Theil chan->dfs_cac_ms = max_t(unsigned int, 190912adee3cSMarkus Theil rrule1->dfs_cac_ms, 191012adee3cSMarkus Theil rrule2->dfs_cac_ms); 191112adee3cSMarkus Theil } 191212adee3cSMarkus Theil 191312adee3cSMarkus Theil return; 191412adee3cSMarkus Theil } 191512adee3cSMarkus Theil 191612adee3cSMarkus Theil chan->dfs_state = NL80211_DFS_USABLE; 191712adee3cSMarkus Theil chan->dfs_state_entered = jiffies; 191812adee3cSMarkus Theil 191912adee3cSMarkus Theil chan->beacon_found = false; 192012adee3cSMarkus Theil chan->flags = flags | bw_flags1 | bw_flags2 | 192112adee3cSMarkus Theil map_regdom_flags(rrule1->flags) | 192212adee3cSMarkus Theil map_regdom_flags(rrule2->flags); 192312adee3cSMarkus Theil 192412adee3cSMarkus Theil /* reg_rule_to_chan_bw_flags may forbids 10 and forbids 20 MHz 192512adee3cSMarkus Theil * (otherwise no adj. rule case), recheck therefore 192612adee3cSMarkus Theil */ 192712adee3cSMarkus Theil if (cfg80211_does_bw_fit_range(comb_range, 192812adee3cSMarkus Theil ieee80211_channel_to_khz(chan), 192912adee3cSMarkus Theil MHZ_TO_KHZ(10))) 193012adee3cSMarkus Theil chan->flags &= ~IEEE80211_CHAN_NO_10MHZ; 193112adee3cSMarkus Theil if (cfg80211_does_bw_fit_range(comb_range, 193212adee3cSMarkus Theil ieee80211_channel_to_khz(chan), 193312adee3cSMarkus Theil MHZ_TO_KHZ(20))) 193412adee3cSMarkus Theil chan->flags &= ~IEEE80211_CHAN_NO_20MHZ; 193512adee3cSMarkus Theil 193612adee3cSMarkus Theil chan->max_antenna_gain = 193712adee3cSMarkus Theil min_t(int, chan->orig_mag, 193812adee3cSMarkus Theil min_t(int, 193912adee3cSMarkus Theil MBI_TO_DBI(power_rule1->max_antenna_gain), 194012adee3cSMarkus Theil MBI_TO_DBI(power_rule2->max_antenna_gain))); 194112adee3cSMarkus Theil chan->max_reg_power = min_t(int, 194212adee3cSMarkus Theil MBM_TO_DBM(power_rule1->max_eirp), 194312adee3cSMarkus Theil MBM_TO_DBM(power_rule2->max_eirp)); 194412adee3cSMarkus Theil 194512adee3cSMarkus Theil if (chan->flags & IEEE80211_CHAN_RADAR) { 194612adee3cSMarkus Theil if (rrule1->dfs_cac_ms || rrule2->dfs_cac_ms) 194712adee3cSMarkus Theil chan->dfs_cac_ms = max_t(unsigned int, 194812adee3cSMarkus Theil rrule1->dfs_cac_ms, 194912adee3cSMarkus Theil rrule2->dfs_cac_ms); 195012adee3cSMarkus Theil else 195112adee3cSMarkus Theil chan->dfs_cac_ms = IEEE80211_DFS_MIN_CAC_TIME_MS; 195212adee3cSMarkus Theil } 195312adee3cSMarkus Theil 195412adee3cSMarkus Theil if (chan->orig_mpwr) { 195512adee3cSMarkus Theil /* Devices that use REGULATORY_COUNTRY_IE_FOLLOW_POWER 195612adee3cSMarkus Theil * will always follow the passed country IE power settings. 195712adee3cSMarkus Theil */ 195812adee3cSMarkus Theil if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE && 195912adee3cSMarkus Theil wiphy->regulatory_flags & REGULATORY_COUNTRY_IE_FOLLOW_POWER) 196012adee3cSMarkus Theil chan->max_power = chan->max_reg_power; 196112adee3cSMarkus Theil else 196212adee3cSMarkus Theil chan->max_power = min(chan->orig_mpwr, 196312adee3cSMarkus Theil chan->max_reg_power); 196412adee3cSMarkus Theil } else { 196512adee3cSMarkus Theil chan->max_power = chan->max_reg_power; 196612adee3cSMarkus Theil } 196712adee3cSMarkus Theil } 196812adee3cSMarkus Theil 19697c9ff7e2SMarkus Theil /* Note that right now we assume the desired channel bandwidth 19707c9ff7e2SMarkus Theil * is always 20 MHz for each individual channel (HT40 uses 20 MHz 19717c9ff7e2SMarkus Theil * per channel, the primary and the extension channel). 19727c9ff7e2SMarkus Theil */ 19737c9ff7e2SMarkus Theil static void handle_channel(struct wiphy *wiphy, 19747c9ff7e2SMarkus Theil enum nl80211_reg_initiator initiator, 19757c9ff7e2SMarkus Theil struct ieee80211_channel *chan) 19767c9ff7e2SMarkus Theil { 197712adee3cSMarkus Theil const u32 orig_chan_freq = ieee80211_channel_to_khz(chan); 19787c9ff7e2SMarkus Theil struct regulatory_request *lr = get_last_request(); 197912adee3cSMarkus Theil struct wiphy *request_wiphy = wiphy_idx_to_wiphy(lr->wiphy_idx); 198012adee3cSMarkus Theil const struct ieee80211_reg_rule *rrule = NULL; 198112adee3cSMarkus Theil const struct ieee80211_reg_rule *rrule1 = NULL; 198212adee3cSMarkus Theil const struct ieee80211_reg_rule *rrule2 = NULL; 19837c9ff7e2SMarkus Theil 198412adee3cSMarkus Theil u32 flags = chan->orig_flags; 19857c9ff7e2SMarkus Theil 198612adee3cSMarkus Theil rrule = freq_reg_info(wiphy, orig_chan_freq); 198712adee3cSMarkus Theil if (IS_ERR(rrule)) { 198812adee3cSMarkus Theil /* check for adjacent match, therefore get rules for 198912adee3cSMarkus Theil * chan - 20 MHz and chan + 20 MHz and test 199012adee3cSMarkus Theil * if reg rules are adjacent 199112adee3cSMarkus Theil */ 199212adee3cSMarkus Theil rrule1 = freq_reg_info(wiphy, 199312adee3cSMarkus Theil orig_chan_freq - MHZ_TO_KHZ(20)); 199412adee3cSMarkus Theil rrule2 = freq_reg_info(wiphy, 199512adee3cSMarkus Theil orig_chan_freq + MHZ_TO_KHZ(20)); 199612adee3cSMarkus Theil if (!IS_ERR(rrule1) && !IS_ERR(rrule2)) { 199712adee3cSMarkus Theil struct ieee80211_freq_range comb_range; 19987c9ff7e2SMarkus Theil 199912adee3cSMarkus Theil if (rrule1->freq_range.end_freq_khz != 200012adee3cSMarkus Theil rrule2->freq_range.start_freq_khz) 200112adee3cSMarkus Theil goto disable_chan; 200212adee3cSMarkus Theil 200312adee3cSMarkus Theil comb_range.start_freq_khz = 200412adee3cSMarkus Theil rrule1->freq_range.start_freq_khz; 200512adee3cSMarkus Theil comb_range.end_freq_khz = 200612adee3cSMarkus Theil rrule2->freq_range.end_freq_khz; 200712adee3cSMarkus Theil comb_range.max_bandwidth_khz = 200812adee3cSMarkus Theil min_t(u32, 200912adee3cSMarkus Theil rrule1->freq_range.max_bandwidth_khz, 201012adee3cSMarkus Theil rrule2->freq_range.max_bandwidth_khz); 201112adee3cSMarkus Theil 201212adee3cSMarkus Theil if (!cfg80211_does_bw_fit_range(&comb_range, 201312adee3cSMarkus Theil orig_chan_freq, 201412adee3cSMarkus Theil MHZ_TO_KHZ(20))) 201512adee3cSMarkus Theil goto disable_chan; 201612adee3cSMarkus Theil 201712adee3cSMarkus Theil handle_channel_adjacent_rules(wiphy, initiator, chan, 201812adee3cSMarkus Theil flags, lr, request_wiphy, 201912adee3cSMarkus Theil rrule1, rrule2, 202012adee3cSMarkus Theil &comb_range); 202112adee3cSMarkus Theil return; 202212adee3cSMarkus Theil } 202312adee3cSMarkus Theil 202412adee3cSMarkus Theil disable_chan: 20257c9ff7e2SMarkus Theil /* We will disable all channels that do not match our 20267c9ff7e2SMarkus Theil * received regulatory rule unless the hint is coming 20277c9ff7e2SMarkus Theil * from a Country IE and the Country IE had no information 20287c9ff7e2SMarkus Theil * about a band. The IEEE 802.11 spec allows for an AP 20297c9ff7e2SMarkus Theil * to send only a subset of the regulatory rules allowed, 20307c9ff7e2SMarkus Theil * so an AP in the US that only supports 2.4 GHz may only send 20317c9ff7e2SMarkus Theil * a country IE with information for the 2.4 GHz band 20327c9ff7e2SMarkus Theil * while 5 GHz is still supported. 20337c9ff7e2SMarkus Theil */ 20347c9ff7e2SMarkus Theil if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE && 203512adee3cSMarkus Theil PTR_ERR(rrule) == -ERANGE) 20367c9ff7e2SMarkus Theil return; 20377c9ff7e2SMarkus Theil 20387c9ff7e2SMarkus Theil if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER && 20397c9ff7e2SMarkus Theil request_wiphy && request_wiphy == wiphy && 20407c9ff7e2SMarkus Theil request_wiphy->regulatory_flags & REGULATORY_STRICT_REG) { 20417c9ff7e2SMarkus Theil pr_debug("Disabling freq %d.%03d MHz for good\n", 20427c9ff7e2SMarkus Theil chan->center_freq, chan->freq_offset); 20437c9ff7e2SMarkus Theil chan->orig_flags |= IEEE80211_CHAN_DISABLED; 20447c9ff7e2SMarkus Theil chan->flags = chan->orig_flags; 20457c9ff7e2SMarkus Theil } else { 20467c9ff7e2SMarkus Theil pr_debug("Disabling freq %d.%03d MHz\n", 20477c9ff7e2SMarkus Theil chan->center_freq, chan->freq_offset); 20487c9ff7e2SMarkus Theil chan->flags |= IEEE80211_CHAN_DISABLED; 20497c9ff7e2SMarkus Theil } 20507c9ff7e2SMarkus Theil return; 20517c9ff7e2SMarkus Theil } 20527c9ff7e2SMarkus Theil 20537c9ff7e2SMarkus Theil handle_channel_single_rule(wiphy, initiator, chan, flags, lr, 205412adee3cSMarkus Theil request_wiphy, rrule); 20557c9ff7e2SMarkus Theil } 20567c9ff7e2SMarkus Theil 20577ca43d03SLuis R. Rodriguez static void handle_band(struct wiphy *wiphy, 2058fdc9d7b2SJohannes Berg enum nl80211_reg_initiator initiator, 2059fdc9d7b2SJohannes Berg struct ieee80211_supported_band *sband) 20608318d78aSJohannes Berg { 2061a92a3ce7SLuis R. Rodriguez unsigned int i; 2062a92a3ce7SLuis R. Rodriguez 2063fdc9d7b2SJohannes Berg if (!sband) 2064fdc9d7b2SJohannes Berg return; 20658318d78aSJohannes Berg 20668318d78aSJohannes Berg for (i = 0; i < sband->n_channels; i++) 2067fdc9d7b2SJohannes Berg handle_channel(wiphy, initiator, &sband->channels[i]); 20688318d78aSJohannes Berg } 20698318d78aSJohannes Berg 207057b5ce07SLuis R. Rodriguez static bool reg_request_cell_base(struct regulatory_request *request) 207157b5ce07SLuis R. Rodriguez { 207257b5ce07SLuis R. Rodriguez if (request->initiator != NL80211_REGDOM_SET_BY_USER) 207357b5ce07SLuis R. Rodriguez return false; 20741a919318SJohannes Berg return request->user_reg_hint_type == NL80211_USER_REG_HINT_CELL_BASE; 207557b5ce07SLuis R. Rodriguez } 207657b5ce07SLuis R. Rodriguez 207757b5ce07SLuis R. Rodriguez bool reg_last_request_cell_base(void) 207857b5ce07SLuis R. Rodriguez { 207938fd2143SJohannes Berg return reg_request_cell_base(get_last_request()); 208057b5ce07SLuis R. Rodriguez } 208157b5ce07SLuis R. Rodriguez 208294fc661fSIlan Peer #ifdef CONFIG_CFG80211_REG_CELLULAR_HINTS 208357b5ce07SLuis R. Rodriguez /* Core specific check */ 20842f92212bSJohannes Berg static enum reg_request_treatment 20852f92212bSJohannes Berg reg_ignore_cell_hint(struct regulatory_request *pending_request) 208657b5ce07SLuis R. Rodriguez { 2087c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 208857b5ce07SLuis R. Rodriguez 208957b5ce07SLuis R. Rodriguez if (!reg_num_devs_support_basehint) 20902f92212bSJohannes Berg return REG_REQ_IGNORE; 209157b5ce07SLuis R. Rodriguez 2092c492db37SJohannes Berg if (reg_request_cell_base(lr) && 20931a919318SJohannes Berg !regdom_changes(pending_request->alpha2)) 20942f92212bSJohannes Berg return REG_REQ_ALREADY_SET; 20951a919318SJohannes Berg 20962f92212bSJohannes Berg return REG_REQ_OK; 209757b5ce07SLuis R. Rodriguez } 209857b5ce07SLuis R. Rodriguez 209957b5ce07SLuis R. Rodriguez /* Device specific check */ 210057b5ce07SLuis R. Rodriguez static bool reg_dev_ignore_cell_hint(struct wiphy *wiphy) 210157b5ce07SLuis R. Rodriguez { 21021a919318SJohannes Berg return !(wiphy->features & NL80211_FEATURE_CELL_BASE_REG_HINTS); 210357b5ce07SLuis R. Rodriguez } 210457b5ce07SLuis R. Rodriguez #else 2105a515de66SJohannes Berg static enum reg_request_treatment 2106a515de66SJohannes Berg reg_ignore_cell_hint(struct regulatory_request *pending_request) 210757b5ce07SLuis R. Rodriguez { 21082f92212bSJohannes Berg return REG_REQ_IGNORE; 210957b5ce07SLuis R. Rodriguez } 21101a919318SJohannes Berg 21111a919318SJohannes Berg static bool reg_dev_ignore_cell_hint(struct wiphy *wiphy) 211257b5ce07SLuis R. Rodriguez { 211357b5ce07SLuis R. Rodriguez return true; 211457b5ce07SLuis R. Rodriguez } 211557b5ce07SLuis R. Rodriguez #endif 211657b5ce07SLuis R. Rodriguez 2117fa1fb9cbSLuis R. Rodriguez static bool wiphy_strict_alpha2_regd(struct wiphy *wiphy) 2118fa1fb9cbSLuis R. Rodriguez { 2119a2f73b6cSLuis R. Rodriguez if (wiphy->regulatory_flags & REGULATORY_STRICT_REG && 2120a2f73b6cSLuis R. Rodriguez !(wiphy->regulatory_flags & REGULATORY_CUSTOM_REG)) 2121fa1fb9cbSLuis R. Rodriguez return true; 2122fa1fb9cbSLuis R. Rodriguez return false; 2123fa1fb9cbSLuis R. Rodriguez } 212457b5ce07SLuis R. Rodriguez 21257db90f4aSLuis R. Rodriguez static bool ignore_reg_update(struct wiphy *wiphy, 21267db90f4aSLuis R. Rodriguez enum nl80211_reg_initiator initiator) 212714b9815aSLuis R. Rodriguez { 2128c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 2129c492db37SJohannes Berg 2130b0d7aa59SJonathan Doron if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED) 2131b0d7aa59SJonathan Doron return true; 2132b0d7aa59SJonathan Doron 2133c492db37SJohannes Berg if (!lr) { 2134c799ba6eSJohannes Berg pr_debug("Ignoring regulatory request set by %s since last_request is not set\n", 2135926a0a09SLuis R. Rodriguez reg_initiator_name(initiator)); 213614b9815aSLuis R. Rodriguez return true; 2137926a0a09SLuis R. Rodriguez } 2138926a0a09SLuis R. Rodriguez 21397db90f4aSLuis R. Rodriguez if (initiator == NL80211_REGDOM_SET_BY_CORE && 2140a2f73b6cSLuis R. Rodriguez wiphy->regulatory_flags & REGULATORY_CUSTOM_REG) { 2141c799ba6eSJohannes Berg pr_debug("Ignoring regulatory request set by %s since the driver uses its own custom regulatory domain\n", 2142926a0a09SLuis R. Rodriguez reg_initiator_name(initiator)); 214314b9815aSLuis R. Rodriguez return true; 2144926a0a09SLuis R. Rodriguez } 2145926a0a09SLuis R. Rodriguez 2146fb1fc7adSLuis R. Rodriguez /* 2147fb1fc7adSLuis R. Rodriguez * wiphy->regd will be set once the device has its own 2148fb1fc7adSLuis R. Rodriguez * desired regulatory domain set 2149fb1fc7adSLuis R. Rodriguez */ 2150fa1fb9cbSLuis R. Rodriguez if (wiphy_strict_alpha2_regd(wiphy) && !wiphy->regd && 2151749b527bSLuis R. Rodriguez initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE && 2152c492db37SJohannes Berg !is_world_regdom(lr->alpha2)) { 2153c799ba6eSJohannes Berg pr_debug("Ignoring regulatory request set by %s since the driver requires its own regulatory domain to be set first\n", 2154926a0a09SLuis R. Rodriguez reg_initiator_name(initiator)); 215514b9815aSLuis R. Rodriguez return true; 2156926a0a09SLuis R. Rodriguez } 2157926a0a09SLuis R. Rodriguez 2158c492db37SJohannes Berg if (reg_request_cell_base(lr)) 215957b5ce07SLuis R. Rodriguez return reg_dev_ignore_cell_hint(wiphy); 216057b5ce07SLuis R. Rodriguez 216114b9815aSLuis R. Rodriguez return false; 216214b9815aSLuis R. Rodriguez } 216314b9815aSLuis R. Rodriguez 21643195e489SLuis R. Rodriguez static bool reg_is_world_roaming(struct wiphy *wiphy) 21653195e489SLuis R. Rodriguez { 21663195e489SLuis R. Rodriguez const struct ieee80211_regdomain *cr = get_cfg80211_regdom(); 21673195e489SLuis R. Rodriguez const struct ieee80211_regdomain *wr = get_wiphy_regdom(wiphy); 21683195e489SLuis R. Rodriguez struct regulatory_request *lr = get_last_request(); 21693195e489SLuis R. Rodriguez 21703195e489SLuis R. Rodriguez if (is_world_regdom(cr->alpha2) || (wr && is_world_regdom(wr->alpha2))) 21713195e489SLuis R. Rodriguez return true; 21723195e489SLuis R. Rodriguez 21733195e489SLuis R. Rodriguez if (lr && lr->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE && 2174a2f73b6cSLuis R. Rodriguez wiphy->regulatory_flags & REGULATORY_CUSTOM_REG) 21753195e489SLuis R. Rodriguez return true; 21763195e489SLuis R. Rodriguez 21773195e489SLuis R. Rodriguez return false; 21783195e489SLuis R. Rodriguez } 21793195e489SLuis R. Rodriguez 21801a919318SJohannes Berg static void handle_reg_beacon(struct wiphy *wiphy, unsigned int chan_idx, 2181e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon) 2182e38f8a7aSLuis R. Rodriguez { 2183e38f8a7aSLuis R. Rodriguez struct ieee80211_supported_band *sband; 2184e38f8a7aSLuis R. Rodriguez struct ieee80211_channel *chan; 21856bad8766SLuis R. Rodriguez bool channel_changed = false; 21866bad8766SLuis R. Rodriguez struct ieee80211_channel chan_before; 2187e38f8a7aSLuis R. Rodriguez 2188e38f8a7aSLuis R. Rodriguez sband = wiphy->bands[reg_beacon->chan.band]; 2189e38f8a7aSLuis R. Rodriguez chan = &sband->channels[chan_idx]; 2190e38f8a7aSLuis R. Rodriguez 2191934f4c7dSThomas Pedersen if (likely(!ieee80211_channel_equal(chan, ®_beacon->chan))) 2192e38f8a7aSLuis R. Rodriguez return; 2193e38f8a7aSLuis R. Rodriguez 21946bad8766SLuis R. Rodriguez if (chan->beacon_found) 21956bad8766SLuis R. Rodriguez return; 21966bad8766SLuis R. Rodriguez 21976bad8766SLuis R. Rodriguez chan->beacon_found = true; 21986bad8766SLuis R. Rodriguez 21990f500a5fSLuis R. Rodriguez if (!reg_is_world_roaming(wiphy)) 22000f500a5fSLuis R. Rodriguez return; 22010f500a5fSLuis R. Rodriguez 2202a2f73b6cSLuis R. Rodriguez if (wiphy->regulatory_flags & REGULATORY_DISABLE_BEACON_HINTS) 220337184244SLuis R. Rodriguez return; 220437184244SLuis R. Rodriguez 2205a48a52b7SJohannes Berg chan_before = *chan; 22066bad8766SLuis R. Rodriguez 22078fe02e16SLuis R. Rodriguez if (chan->flags & IEEE80211_CHAN_NO_IR) { 22088fe02e16SLuis R. Rodriguez chan->flags &= ~IEEE80211_CHAN_NO_IR; 22096bad8766SLuis R. Rodriguez channel_changed = true; 2210e38f8a7aSLuis R. Rodriguez } 2211e38f8a7aSLuis R. Rodriguez 22126bad8766SLuis R. Rodriguez if (channel_changed) 22136bad8766SLuis R. Rodriguez nl80211_send_beacon_hint_event(wiphy, &chan_before, chan); 2214e38f8a7aSLuis R. Rodriguez } 2215e38f8a7aSLuis R. Rodriguez 2216e38f8a7aSLuis R. Rodriguez /* 2217e38f8a7aSLuis R. Rodriguez * Called when a scan on a wiphy finds a beacon on 2218e38f8a7aSLuis R. Rodriguez * new channel 2219e38f8a7aSLuis R. Rodriguez */ 2220e38f8a7aSLuis R. Rodriguez static void wiphy_update_new_beacon(struct wiphy *wiphy, 2221e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon) 2222e38f8a7aSLuis R. Rodriguez { 2223e38f8a7aSLuis R. Rodriguez unsigned int i; 2224e38f8a7aSLuis R. Rodriguez struct ieee80211_supported_band *sband; 2225e38f8a7aSLuis R. Rodriguez 2226e38f8a7aSLuis R. Rodriguez if (!wiphy->bands[reg_beacon->chan.band]) 2227e38f8a7aSLuis R. Rodriguez return; 2228e38f8a7aSLuis R. Rodriguez 2229e38f8a7aSLuis R. Rodriguez sband = wiphy->bands[reg_beacon->chan.band]; 2230e38f8a7aSLuis R. Rodriguez 2231e38f8a7aSLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 2232e38f8a7aSLuis R. Rodriguez handle_reg_beacon(wiphy, i, reg_beacon); 2233e38f8a7aSLuis R. Rodriguez } 2234e38f8a7aSLuis R. Rodriguez 2235e38f8a7aSLuis R. Rodriguez /* 2236e38f8a7aSLuis R. Rodriguez * Called upon reg changes or a new wiphy is added 2237e38f8a7aSLuis R. Rodriguez */ 2238e38f8a7aSLuis R. Rodriguez static void wiphy_update_beacon_reg(struct wiphy *wiphy) 2239e38f8a7aSLuis R. Rodriguez { 2240e38f8a7aSLuis R. Rodriguez unsigned int i; 2241e38f8a7aSLuis R. Rodriguez struct ieee80211_supported_band *sband; 2242e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon; 2243e38f8a7aSLuis R. Rodriguez 2244e38f8a7aSLuis R. Rodriguez list_for_each_entry(reg_beacon, ®_beacon_list, list) { 2245e38f8a7aSLuis R. Rodriguez if (!wiphy->bands[reg_beacon->chan.band]) 2246e38f8a7aSLuis R. Rodriguez continue; 2247e38f8a7aSLuis R. Rodriguez sband = wiphy->bands[reg_beacon->chan.band]; 2248e38f8a7aSLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 2249e38f8a7aSLuis R. Rodriguez handle_reg_beacon(wiphy, i, reg_beacon); 2250e38f8a7aSLuis R. Rodriguez } 2251e38f8a7aSLuis R. Rodriguez } 2252e38f8a7aSLuis R. Rodriguez 2253e38f8a7aSLuis R. Rodriguez /* Reap the advantages of previously found beacons */ 2254e38f8a7aSLuis R. Rodriguez static void reg_process_beacons(struct wiphy *wiphy) 2255e38f8a7aSLuis R. Rodriguez { 2256b1ed8dddSLuis R. Rodriguez /* 2257b1ed8dddSLuis R. Rodriguez * Means we are just firing up cfg80211, so no beacons would 2258b1ed8dddSLuis R. Rodriguez * have been processed yet. 2259b1ed8dddSLuis R. Rodriguez */ 2260b1ed8dddSLuis R. Rodriguez if (!last_request) 2261b1ed8dddSLuis R. Rodriguez return; 2262e38f8a7aSLuis R. Rodriguez wiphy_update_beacon_reg(wiphy); 2263e38f8a7aSLuis R. Rodriguez } 2264e38f8a7aSLuis R. Rodriguez 22651a919318SJohannes Berg static bool is_ht40_allowed(struct ieee80211_channel *chan) 2266038659e7SLuis R. Rodriguez { 2267038659e7SLuis R. Rodriguez if (!chan) 2268038659e7SLuis R. Rodriguez return false; 22691a919318SJohannes Berg if (chan->flags & IEEE80211_CHAN_DISABLED) 22701a919318SJohannes Berg return false; 22711a919318SJohannes Berg /* This would happen when regulatory rules disallow HT40 completely */ 227255b183adSFelix Fietkau if ((chan->flags & IEEE80211_CHAN_NO_HT40) == IEEE80211_CHAN_NO_HT40) 227355b183adSFelix Fietkau return false; 227455b183adSFelix Fietkau return true; 2275038659e7SLuis R. Rodriguez } 2276038659e7SLuis R. Rodriguez 2277038659e7SLuis R. Rodriguez static void reg_process_ht_flags_channel(struct wiphy *wiphy, 2278fdc9d7b2SJohannes Berg struct ieee80211_channel *channel) 2279038659e7SLuis R. Rodriguez { 2280fdc9d7b2SJohannes Berg struct ieee80211_supported_band *sband = wiphy->bands[channel->band]; 2281038659e7SLuis R. Rodriguez struct ieee80211_channel *channel_before = NULL, *channel_after = NULL; 22824e0854a7SEmmanuel Grumbach const struct ieee80211_regdomain *regd; 2283038659e7SLuis R. Rodriguez unsigned int i; 22844e0854a7SEmmanuel Grumbach u32 flags; 2285038659e7SLuis R. Rodriguez 22861a919318SJohannes Berg if (!is_ht40_allowed(channel)) { 2287038659e7SLuis R. Rodriguez channel->flags |= IEEE80211_CHAN_NO_HT40; 2288038659e7SLuis R. Rodriguez return; 2289038659e7SLuis R. Rodriguez } 2290038659e7SLuis R. Rodriguez 2291038659e7SLuis R. Rodriguez /* 2292038659e7SLuis R. Rodriguez * We need to ensure the extension channels exist to 2293038659e7SLuis R. Rodriguez * be able to use HT40- or HT40+, this finds them (or not) 2294038659e7SLuis R. Rodriguez */ 2295038659e7SLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) { 2296038659e7SLuis R. Rodriguez struct ieee80211_channel *c = &sband->channels[i]; 22971a919318SJohannes Berg 2298038659e7SLuis R. Rodriguez if (c->center_freq == (channel->center_freq - 20)) 2299038659e7SLuis R. Rodriguez channel_before = c; 2300038659e7SLuis R. Rodriguez if (c->center_freq == (channel->center_freq + 20)) 2301038659e7SLuis R. Rodriguez channel_after = c; 2302038659e7SLuis R. Rodriguez } 2303038659e7SLuis R. Rodriguez 23044e0854a7SEmmanuel Grumbach flags = 0; 23054e0854a7SEmmanuel Grumbach regd = get_wiphy_regdom(wiphy); 23064e0854a7SEmmanuel Grumbach if (regd) { 23074e0854a7SEmmanuel Grumbach const struct ieee80211_reg_rule *reg_rule = 23084e0854a7SEmmanuel Grumbach freq_reg_info_regd(MHZ_TO_KHZ(channel->center_freq), 23094e0854a7SEmmanuel Grumbach regd, MHZ_TO_KHZ(20)); 23104e0854a7SEmmanuel Grumbach 23114e0854a7SEmmanuel Grumbach if (!IS_ERR(reg_rule)) 23124e0854a7SEmmanuel Grumbach flags = reg_rule->flags; 23134e0854a7SEmmanuel Grumbach } 23144e0854a7SEmmanuel Grumbach 2315038659e7SLuis R. Rodriguez /* 2316038659e7SLuis R. Rodriguez * Please note that this assumes target bandwidth is 20 MHz, 2317038659e7SLuis R. Rodriguez * if that ever changes we also need to change the below logic 2318038659e7SLuis R. Rodriguez * to include that as well. 2319038659e7SLuis R. Rodriguez */ 23204e0854a7SEmmanuel Grumbach if (!is_ht40_allowed(channel_before) || 23214e0854a7SEmmanuel Grumbach flags & NL80211_RRF_NO_HT40MINUS) 2322689da1b3SLuis R. Rodriguez channel->flags |= IEEE80211_CHAN_NO_HT40MINUS; 2323038659e7SLuis R. Rodriguez else 2324689da1b3SLuis R. Rodriguez channel->flags &= ~IEEE80211_CHAN_NO_HT40MINUS; 2325038659e7SLuis R. Rodriguez 23264e0854a7SEmmanuel Grumbach if (!is_ht40_allowed(channel_after) || 23274e0854a7SEmmanuel Grumbach flags & NL80211_RRF_NO_HT40PLUS) 2328689da1b3SLuis R. Rodriguez channel->flags |= IEEE80211_CHAN_NO_HT40PLUS; 2329038659e7SLuis R. Rodriguez else 2330689da1b3SLuis R. Rodriguez channel->flags &= ~IEEE80211_CHAN_NO_HT40PLUS; 2331038659e7SLuis R. Rodriguez } 2332038659e7SLuis R. Rodriguez 2333038659e7SLuis R. Rodriguez static void reg_process_ht_flags_band(struct wiphy *wiphy, 2334fdc9d7b2SJohannes Berg struct ieee80211_supported_band *sband) 2335038659e7SLuis R. Rodriguez { 2336038659e7SLuis R. Rodriguez unsigned int i; 2337038659e7SLuis R. Rodriguez 2338fdc9d7b2SJohannes Berg if (!sband) 2339fdc9d7b2SJohannes Berg return; 2340038659e7SLuis R. Rodriguez 2341038659e7SLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 2342fdc9d7b2SJohannes Berg reg_process_ht_flags_channel(wiphy, &sband->channels[i]); 2343038659e7SLuis R. Rodriguez } 2344038659e7SLuis R. Rodriguez 2345038659e7SLuis R. Rodriguez static void reg_process_ht_flags(struct wiphy *wiphy) 2346038659e7SLuis R. Rodriguez { 234757fbcce3SJohannes Berg enum nl80211_band band; 2348038659e7SLuis R. Rodriguez 2349038659e7SLuis R. Rodriguez if (!wiphy) 2350038659e7SLuis R. Rodriguez return; 2351038659e7SLuis R. Rodriguez 235257fbcce3SJohannes Berg for (band = 0; band < NUM_NL80211_BANDS; band++) 2353fdc9d7b2SJohannes Berg reg_process_ht_flags_band(wiphy, wiphy->bands[band]); 2354038659e7SLuis R. Rodriguez } 2355038659e7SLuis R. Rodriguez 23560e3802dbSLuis R. Rodriguez static void reg_call_notifier(struct wiphy *wiphy, 23570e3802dbSLuis R. Rodriguez struct regulatory_request *request) 23580e3802dbSLuis R. Rodriguez { 23590e3802dbSLuis R. Rodriguez if (wiphy->reg_notifier) 23600e3802dbSLuis R. Rodriguez wiphy->reg_notifier(wiphy, request); 23610e3802dbSLuis R. Rodriguez } 23620e3802dbSLuis R. Rodriguez 2363ad932f04SArik Nemtsov static bool reg_wdev_chan_valid(struct wiphy *wiphy, struct wireless_dev *wdev) 2364ad932f04SArik Nemtsov { 2365f43e5210SJohannes Berg struct cfg80211_chan_def chandef = {}; 2366ad932f04SArik Nemtsov struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); 236720658702SArik Nemtsov enum nl80211_iftype iftype; 2368e08ebd6dSIlan Peer bool ret; 2369ad932f04SArik Nemtsov 2370ad932f04SArik Nemtsov wdev_lock(wdev); 237120658702SArik Nemtsov iftype = wdev->iftype; 2372ad932f04SArik Nemtsov 237320658702SArik Nemtsov /* make sure the interface is active */ 2374ad932f04SArik Nemtsov if (!wdev->netdev || !netif_running(wdev->netdev)) 237520658702SArik Nemtsov goto wdev_inactive_unlock; 2376ad932f04SArik Nemtsov 237720658702SArik Nemtsov switch (iftype) { 2378ad932f04SArik Nemtsov case NL80211_IFTYPE_AP: 2379ad932f04SArik Nemtsov case NL80211_IFTYPE_P2P_GO: 2380701fdfe3SSriram R case NL80211_IFTYPE_MESH_POINT: 2381ad932f04SArik Nemtsov if (!wdev->beacon_interval) 238220658702SArik Nemtsov goto wdev_inactive_unlock; 238320658702SArik Nemtsov chandef = wdev->chandef; 2384ad932f04SArik Nemtsov break; 2385185076d6SArik Nemtsov case NL80211_IFTYPE_ADHOC: 2386185076d6SArik Nemtsov if (!wdev->ssid_len) 238720658702SArik Nemtsov goto wdev_inactive_unlock; 238820658702SArik Nemtsov chandef = wdev->chandef; 2389185076d6SArik Nemtsov break; 2390ad932f04SArik Nemtsov case NL80211_IFTYPE_STATION: 2391ad932f04SArik Nemtsov case NL80211_IFTYPE_P2P_CLIENT: 2392ad932f04SArik Nemtsov if (!wdev->current_bss || 2393ad932f04SArik Nemtsov !wdev->current_bss->pub.channel) 239420658702SArik Nemtsov goto wdev_inactive_unlock; 2395ad932f04SArik Nemtsov 239620658702SArik Nemtsov if (!rdev->ops->get_channel || 239720658702SArik Nemtsov rdev_get_channel(rdev, wdev, &chandef)) 239820658702SArik Nemtsov cfg80211_chandef_create(&chandef, 239920658702SArik Nemtsov wdev->current_bss->pub.channel, 240020658702SArik Nemtsov NL80211_CHAN_NO_HT); 2401ad932f04SArik Nemtsov break; 2402ad932f04SArik Nemtsov case NL80211_IFTYPE_MONITOR: 2403ad932f04SArik Nemtsov case NL80211_IFTYPE_AP_VLAN: 2404ad932f04SArik Nemtsov case NL80211_IFTYPE_P2P_DEVICE: 2405ad932f04SArik Nemtsov /* no enforcement required */ 2406ad932f04SArik Nemtsov break; 2407ad932f04SArik Nemtsov default: 2408ad932f04SArik Nemtsov /* others not implemented for now */ 2409ad932f04SArik Nemtsov WARN_ON(1); 2410ad932f04SArik Nemtsov break; 2411ad932f04SArik Nemtsov } 2412ad932f04SArik Nemtsov 2413ad932f04SArik Nemtsov wdev_unlock(wdev); 241420658702SArik Nemtsov 241520658702SArik Nemtsov switch (iftype) { 241620658702SArik Nemtsov case NL80211_IFTYPE_AP: 241720658702SArik Nemtsov case NL80211_IFTYPE_P2P_GO: 241820658702SArik Nemtsov case NL80211_IFTYPE_ADHOC: 2419701fdfe3SSriram R case NL80211_IFTYPE_MESH_POINT: 2420e08ebd6dSIlan Peer wiphy_lock(wiphy); 2421e08ebd6dSIlan Peer ret = cfg80211_reg_can_beacon_relax(wiphy, &chandef, iftype); 2422e08ebd6dSIlan Peer wiphy_unlock(wiphy); 2423e08ebd6dSIlan Peer 2424e08ebd6dSIlan Peer return ret; 242520658702SArik Nemtsov case NL80211_IFTYPE_STATION: 242620658702SArik Nemtsov case NL80211_IFTYPE_P2P_CLIENT: 242720658702SArik Nemtsov return cfg80211_chandef_usable(wiphy, &chandef, 242820658702SArik Nemtsov IEEE80211_CHAN_DISABLED); 242920658702SArik Nemtsov default: 243020658702SArik Nemtsov break; 243120658702SArik Nemtsov } 243220658702SArik Nemtsov 243320658702SArik Nemtsov return true; 243420658702SArik Nemtsov 243520658702SArik Nemtsov wdev_inactive_unlock: 243620658702SArik Nemtsov wdev_unlock(wdev); 243720658702SArik Nemtsov return true; 2438ad932f04SArik Nemtsov } 2439ad932f04SArik Nemtsov 2440ad932f04SArik Nemtsov static void reg_leave_invalid_chans(struct wiphy *wiphy) 2441ad932f04SArik Nemtsov { 2442ad932f04SArik Nemtsov struct wireless_dev *wdev; 2443ad932f04SArik Nemtsov struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); 2444ad932f04SArik Nemtsov 2445ad932f04SArik Nemtsov ASSERT_RTNL(); 2446ad932f04SArik Nemtsov 244753873f13SJohannes Berg list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) 2448ad932f04SArik Nemtsov if (!reg_wdev_chan_valid(wiphy, wdev)) 2449ad932f04SArik Nemtsov cfg80211_leave(rdev, wdev); 2450ad932f04SArik Nemtsov } 2451ad932f04SArik Nemtsov 2452ad932f04SArik Nemtsov static void reg_check_chans_work(struct work_struct *work) 2453ad932f04SArik Nemtsov { 2454ad932f04SArik Nemtsov struct cfg80211_registered_device *rdev; 2455ad932f04SArik Nemtsov 2456c799ba6eSJohannes Berg pr_debug("Verifying active interfaces after reg change\n"); 2457ad932f04SArik Nemtsov rtnl_lock(); 2458ad932f04SArik Nemtsov 2459ad932f04SArik Nemtsov list_for_each_entry(rdev, &cfg80211_rdev_list, list) 2460ad932f04SArik Nemtsov if (!(rdev->wiphy.regulatory_flags & 2461ad932f04SArik Nemtsov REGULATORY_IGNORE_STALE_KICKOFF)) 2462ad932f04SArik Nemtsov reg_leave_invalid_chans(&rdev->wiphy); 2463ad932f04SArik Nemtsov 2464ad932f04SArik Nemtsov rtnl_unlock(); 2465ad932f04SArik Nemtsov } 2466ad932f04SArik Nemtsov 2467ad932f04SArik Nemtsov static void reg_check_channels(void) 2468ad932f04SArik Nemtsov { 2469ad932f04SArik Nemtsov /* 2470ad932f04SArik Nemtsov * Give usermode a chance to do something nicer (move to another 2471ad932f04SArik Nemtsov * channel, orderly disconnection), before forcing a disconnection. 2472ad932f04SArik Nemtsov */ 2473ad932f04SArik Nemtsov mod_delayed_work(system_power_efficient_wq, 2474ad932f04SArik Nemtsov ®_check_chans, 2475ad932f04SArik Nemtsov msecs_to_jiffies(REG_ENFORCE_GRACE_MS)); 2476ad932f04SArik Nemtsov } 2477ad932f04SArik Nemtsov 2478eac03e38SSven Neumann static void wiphy_update_regulatory(struct wiphy *wiphy, 24797db90f4aSLuis R. Rodriguez enum nl80211_reg_initiator initiator) 24808318d78aSJohannes Berg { 248157fbcce3SJohannes Berg enum nl80211_band band; 2482c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 2483eac03e38SSven Neumann 24840e3802dbSLuis R. Rodriguez if (ignore_reg_update(wiphy, initiator)) { 24850e3802dbSLuis R. Rodriguez /* 24860e3802dbSLuis R. Rodriguez * Regulatory updates set by CORE are ignored for custom 24870e3802dbSLuis R. Rodriguez * regulatory cards. Let us notify the changes to the driver, 24880e3802dbSLuis R. Rodriguez * as some drivers used this to restore its orig_* reg domain. 24890e3802dbSLuis R. Rodriguez */ 24900e3802dbSLuis R. Rodriguez if (initiator == NL80211_REGDOM_SET_BY_CORE && 2491e31f6456SAmar Singhal wiphy->regulatory_flags & REGULATORY_CUSTOM_REG && 2492e31f6456SAmar Singhal !(wiphy->regulatory_flags & 2493e31f6456SAmar Singhal REGULATORY_WIPHY_SELF_MANAGED)) 24940e3802dbSLuis R. Rodriguez reg_call_notifier(wiphy, lr); 2495a203c2aaSSven Neumann return; 24960e3802dbSLuis R. Rodriguez } 2497a203c2aaSSven Neumann 2498c492db37SJohannes Berg lr->dfs_region = get_cfg80211_regdom()->dfs_region; 2499b68e6b3bSLuis R. Rodriguez 250057fbcce3SJohannes Berg for (band = 0; band < NUM_NL80211_BANDS; band++) 2501fdc9d7b2SJohannes Berg handle_band(wiphy, initiator, wiphy->bands[band]); 2502a203c2aaSSven Neumann 2503e38f8a7aSLuis R. Rodriguez reg_process_beacons(wiphy); 2504038659e7SLuis R. Rodriguez reg_process_ht_flags(wiphy); 25050e3802dbSLuis R. Rodriguez reg_call_notifier(wiphy, lr); 2506b2e1b302SLuis R. Rodriguez } 2507b2e1b302SLuis R. Rodriguez 2508d7549cbbSSven Neumann static void update_all_wiphy_regulatory(enum nl80211_reg_initiator initiator) 2509d7549cbbSSven Neumann { 2510d7549cbbSSven Neumann struct cfg80211_registered_device *rdev; 25114a38994fSRajkumar Manoharan struct wiphy *wiphy; 2512d7549cbbSSven Neumann 25135fe231e8SJohannes Berg ASSERT_RTNL(); 2514458f4f9eSJohannes Berg 25154a38994fSRajkumar Manoharan list_for_each_entry(rdev, &cfg80211_rdev_list, list) { 25164a38994fSRajkumar Manoharan wiphy = &rdev->wiphy; 25174a38994fSRajkumar Manoharan wiphy_update_regulatory(wiphy, initiator); 25184a38994fSRajkumar Manoharan } 2519ad932f04SArik Nemtsov 2520ad932f04SArik Nemtsov reg_check_channels(); 2521d7549cbbSSven Neumann } 2522d7549cbbSSven Neumann 25231fa25e41SLuis R. Rodriguez static void handle_channel_custom(struct wiphy *wiphy, 2524fdc9d7b2SJohannes Berg struct ieee80211_channel *chan, 2525c4b9d655SGanapathi Bhat const struct ieee80211_regdomain *regd, 2526c4b9d655SGanapathi Bhat u32 min_bw) 25271fa25e41SLuis R. Rodriguez { 2528038659e7SLuis R. Rodriguez u32 bw_flags = 0; 25291fa25e41SLuis R. Rodriguez const struct ieee80211_reg_rule *reg_rule = NULL; 25301fa25e41SLuis R. Rodriguez const struct ieee80211_power_rule *power_rule = NULL; 2531934f4c7dSThomas Pedersen u32 bw, center_freq_khz; 25321fa25e41SLuis R. Rodriguez 2533934f4c7dSThomas Pedersen center_freq_khz = ieee80211_channel_to_khz(chan); 2534c4b9d655SGanapathi Bhat for (bw = MHZ_TO_KHZ(20); bw >= min_bw; bw = bw / 2) { 2535934f4c7dSThomas Pedersen reg_rule = freq_reg_info_regd(center_freq_khz, regd, bw); 25364edd5698SMatthias May if (!IS_ERR(reg_rule)) 25374edd5698SMatthias May break; 25384edd5698SMatthias May } 25391fa25e41SLuis R. Rodriguez 2540a7ee7d44SJohannes Berg if (IS_ERR_OR_NULL(reg_rule)) { 2541934f4c7dSThomas Pedersen pr_debug("Disabling freq %d.%03d MHz as custom regd has no rule that fits it\n", 2542934f4c7dSThomas Pedersen chan->center_freq, chan->freq_offset); 2543db8dfee5SArik Nemtsov if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED) { 2544db8dfee5SArik Nemtsov chan->flags |= IEEE80211_CHAN_DISABLED; 2545db8dfee5SArik Nemtsov } else { 2546cc493e4fSLuis R. Rodriguez chan->orig_flags |= IEEE80211_CHAN_DISABLED; 2547cc493e4fSLuis R. Rodriguez chan->flags = chan->orig_flags; 2548db8dfee5SArik Nemtsov } 25491fa25e41SLuis R. Rodriguez return; 25501fa25e41SLuis R. Rodriguez } 25511fa25e41SLuis R. Rodriguez 25521fa25e41SLuis R. Rodriguez power_rule = ®_rule->power_rule; 25531aeb135fSMichal Sojka bw_flags = reg_rule_to_chan_bw_flags(regd, reg_rule, chan); 2554038659e7SLuis R. Rodriguez 25552e18b38fSArik Nemtsov chan->dfs_state_entered = jiffies; 2556c7ab5081SArik Nemtsov chan->dfs_state = NL80211_DFS_USABLE; 2557c7ab5081SArik Nemtsov 2558c7ab5081SArik Nemtsov chan->beacon_found = false; 2559db8dfee5SArik Nemtsov 2560db8dfee5SArik Nemtsov if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED) 2561db8dfee5SArik Nemtsov chan->flags = chan->orig_flags | bw_flags | 2562db8dfee5SArik Nemtsov map_regdom_flags(reg_rule->flags); 2563db8dfee5SArik Nemtsov else 2564038659e7SLuis R. Rodriguez chan->flags |= map_regdom_flags(reg_rule->flags) | bw_flags; 2565db8dfee5SArik Nemtsov 25661fa25e41SLuis R. Rodriguez chan->max_antenna_gain = (int) MBI_TO_DBI(power_rule->max_antenna_gain); 2567279f0f55SFelix Fietkau chan->max_reg_power = chan->max_power = 2568279f0f55SFelix Fietkau (int) MBM_TO_DBM(power_rule->max_eirp); 25692e18b38fSArik Nemtsov 25702e18b38fSArik Nemtsov if (chan->flags & IEEE80211_CHAN_RADAR) { 25712e18b38fSArik Nemtsov if (reg_rule->dfs_cac_ms) 25722e18b38fSArik Nemtsov chan->dfs_cac_ms = reg_rule->dfs_cac_ms; 25732e18b38fSArik Nemtsov else 25742e18b38fSArik Nemtsov chan->dfs_cac_ms = IEEE80211_DFS_MIN_CAC_TIME_MS; 25752e18b38fSArik Nemtsov } 25762e18b38fSArik Nemtsov 25772e18b38fSArik Nemtsov chan->max_power = chan->max_reg_power; 25781fa25e41SLuis R. Rodriguez } 25791fa25e41SLuis R. Rodriguez 2580fdc9d7b2SJohannes Berg static void handle_band_custom(struct wiphy *wiphy, 2581fdc9d7b2SJohannes Berg struct ieee80211_supported_band *sband, 25821fa25e41SLuis R. Rodriguez const struct ieee80211_regdomain *regd) 25831fa25e41SLuis R. Rodriguez { 25841fa25e41SLuis R. Rodriguez unsigned int i; 25851fa25e41SLuis R. Rodriguez 2586fdc9d7b2SJohannes Berg if (!sband) 2587fdc9d7b2SJohannes Berg return; 25881fa25e41SLuis R. Rodriguez 2589c4b9d655SGanapathi Bhat /* 2590c4b9d655SGanapathi Bhat * We currently assume that you always want at least 20 MHz, 2591c4b9d655SGanapathi Bhat * otherwise channel 12 might get enabled if this rule is 2592c4b9d655SGanapathi Bhat * compatible to US, which permits 2402 - 2472 MHz. 2593c4b9d655SGanapathi Bhat */ 25941fa25e41SLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 2595c4b9d655SGanapathi Bhat handle_channel_custom(wiphy, &sband->channels[i], regd, 2596c4b9d655SGanapathi Bhat MHZ_TO_KHZ(20)); 25971fa25e41SLuis R. Rodriguez } 25981fa25e41SLuis R. Rodriguez 25991fa25e41SLuis R. Rodriguez /* Used by drivers prior to wiphy registration */ 26001fa25e41SLuis R. Rodriguez void wiphy_apply_custom_regulatory(struct wiphy *wiphy, 26011fa25e41SLuis R. Rodriguez const struct ieee80211_regdomain *regd) 26021fa25e41SLuis R. Rodriguez { 2603beee2469SIlan Peer const struct ieee80211_regdomain *new_regd, *tmp; 260457fbcce3SJohannes Berg enum nl80211_band band; 2605bbcf3f02SLuis R. Rodriguez unsigned int bands_set = 0; 2606ac46d48eSLuis R. Rodriguez 2607a2f73b6cSLuis R. Rodriguez WARN(!(wiphy->regulatory_flags & REGULATORY_CUSTOM_REG), 2608a2f73b6cSLuis R. Rodriguez "wiphy should have REGULATORY_CUSTOM_REG\n"); 2609a2f73b6cSLuis R. Rodriguez wiphy->regulatory_flags |= REGULATORY_CUSTOM_REG; 2610222ea581SLuis R. Rodriguez 261157fbcce3SJohannes Berg for (band = 0; band < NUM_NL80211_BANDS; band++) { 2612bbcf3f02SLuis R. Rodriguez if (!wiphy->bands[band]) 2613bbcf3f02SLuis R. Rodriguez continue; 2614fdc9d7b2SJohannes Berg handle_band_custom(wiphy, wiphy->bands[band], regd); 2615bbcf3f02SLuis R. Rodriguez bands_set++; 26161fa25e41SLuis R. Rodriguez } 2617bbcf3f02SLuis R. Rodriguez 2618bbcf3f02SLuis R. Rodriguez /* 2619bbcf3f02SLuis R. Rodriguez * no point in calling this if it won't have any effect 26201a919318SJohannes Berg * on your device's supported bands. 2621bbcf3f02SLuis R. Rodriguez */ 2622bbcf3f02SLuis R. Rodriguez WARN_ON(!bands_set); 2623beee2469SIlan Peer new_regd = reg_copy_regd(regd); 2624beee2469SIlan Peer if (IS_ERR(new_regd)) 2625beee2469SIlan Peer return; 2626beee2469SIlan Peer 262751d62f2fSIlan Peer rtnl_lock(); 2628a05829a7SJohannes Berg wiphy_lock(wiphy); 262951d62f2fSIlan Peer 2630beee2469SIlan Peer tmp = get_wiphy_regdom(wiphy); 2631beee2469SIlan Peer rcu_assign_pointer(wiphy->regd, new_regd); 2632beee2469SIlan Peer rcu_free_regdom(tmp); 263351d62f2fSIlan Peer 2634a05829a7SJohannes Berg wiphy_unlock(wiphy); 263551d62f2fSIlan Peer rtnl_unlock(); 26361fa25e41SLuis R. Rodriguez } 26371fa25e41SLuis R. Rodriguez EXPORT_SYMBOL(wiphy_apply_custom_regulatory); 26381fa25e41SLuis R. Rodriguez 2639b2e253cfSLuis R. Rodriguez static void reg_set_request_processed(void) 2640b2e253cfSLuis R. Rodriguez { 2641b2e253cfSLuis R. Rodriguez bool need_more_processing = false; 2642c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 2643b2e253cfSLuis R. Rodriguez 2644c492db37SJohannes Berg lr->processed = true; 2645b2e253cfSLuis R. Rodriguez 2646b2e253cfSLuis R. Rodriguez spin_lock(®_requests_lock); 2647b2e253cfSLuis R. Rodriguez if (!list_empty(®_requests_list)) 2648b2e253cfSLuis R. Rodriguez need_more_processing = true; 2649b2e253cfSLuis R. Rodriguez spin_unlock(®_requests_lock); 2650b2e253cfSLuis R. Rodriguez 2651b6863036SJohannes Berg cancel_crda_timeout(); 2652a90c7a31SLuis R. Rodriguez 2653b2e253cfSLuis R. Rodriguez if (need_more_processing) 2654b2e253cfSLuis R. Rodriguez schedule_work(®_work); 2655b2e253cfSLuis R. Rodriguez } 2656b2e253cfSLuis R. Rodriguez 2657d1c96a9aSLuis R. Rodriguez /** 2658b3eb7f3fSLuis R. Rodriguez * reg_process_hint_core - process core regulatory requests 2659726e6af9SAndrew Lunn * @core_request: a pending core regulatory request 2660b3eb7f3fSLuis R. Rodriguez * 2661b3eb7f3fSLuis R. Rodriguez * The wireless subsystem can use this function to process 2662b3eb7f3fSLuis R. Rodriguez * a regulatory request issued by the regulatory core. 2663b3eb7f3fSLuis R. Rodriguez */ 2664d34265a3SJohannes Berg static enum reg_request_treatment 2665d34265a3SJohannes Berg reg_process_hint_core(struct regulatory_request *core_request) 2666b3eb7f3fSLuis R. Rodriguez { 2667cecbb069SJohannes Berg if (reg_query_database(core_request)) { 2668b3eb7f3fSLuis R. Rodriguez core_request->intersect = false; 2669b3eb7f3fSLuis R. Rodriguez core_request->processed = false; 267005f1a3eaSLuis R. Rodriguez reg_update_last_request(core_request); 2671d34265a3SJohannes Berg return REG_REQ_OK; 267225b20dbdSJohannes Berg } 2673d34265a3SJohannes Berg 2674d34265a3SJohannes Berg return REG_REQ_IGNORE; 2675b3eb7f3fSLuis R. Rodriguez } 2676b3eb7f3fSLuis R. Rodriguez 26770d97a619SLuis R. Rodriguez static enum reg_request_treatment 26780d97a619SLuis R. Rodriguez __reg_process_hint_user(struct regulatory_request *user_request) 26790d97a619SLuis R. Rodriguez { 26800d97a619SLuis R. Rodriguez struct regulatory_request *lr = get_last_request(); 26810d97a619SLuis R. Rodriguez 26820d97a619SLuis R. Rodriguez if (reg_request_cell_base(user_request)) 26830d97a619SLuis R. Rodriguez return reg_ignore_cell_hint(user_request); 26840d97a619SLuis R. Rodriguez 26850d97a619SLuis R. Rodriguez if (reg_request_cell_base(lr)) 26860d97a619SLuis R. Rodriguez return REG_REQ_IGNORE; 26870d97a619SLuis R. Rodriguez 26880d97a619SLuis R. Rodriguez if (lr->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE) 26890d97a619SLuis R. Rodriguez return REG_REQ_INTERSECT; 26900d97a619SLuis R. Rodriguez /* 26910d97a619SLuis R. Rodriguez * If the user knows better the user should set the regdom 26920d97a619SLuis R. Rodriguez * to their country before the IE is picked up 26930d97a619SLuis R. Rodriguez */ 26940d97a619SLuis R. Rodriguez if (lr->initiator == NL80211_REGDOM_SET_BY_USER && 26950d97a619SLuis R. Rodriguez lr->intersect) 26960d97a619SLuis R. Rodriguez return REG_REQ_IGNORE; 26970d97a619SLuis R. Rodriguez /* 26980d97a619SLuis R. Rodriguez * Process user requests only after previous user/driver/core 26990d97a619SLuis R. Rodriguez * requests have been processed 27000d97a619SLuis R. Rodriguez */ 27010d97a619SLuis R. Rodriguez if ((lr->initiator == NL80211_REGDOM_SET_BY_CORE || 27020d97a619SLuis R. Rodriguez lr->initiator == NL80211_REGDOM_SET_BY_DRIVER || 27030d97a619SLuis R. Rodriguez lr->initiator == NL80211_REGDOM_SET_BY_USER) && 27040d97a619SLuis R. Rodriguez regdom_changes(lr->alpha2)) 27050d97a619SLuis R. Rodriguez return REG_REQ_IGNORE; 27060d97a619SLuis R. Rodriguez 27070d97a619SLuis R. Rodriguez if (!regdom_changes(user_request->alpha2)) 27080d97a619SLuis R. Rodriguez return REG_REQ_ALREADY_SET; 27090d97a619SLuis R. Rodriguez 27100d97a619SLuis R. Rodriguez return REG_REQ_OK; 27110d97a619SLuis R. Rodriguez } 27120d97a619SLuis R. Rodriguez 27130d97a619SLuis R. Rodriguez /** 27140d97a619SLuis R. Rodriguez * reg_process_hint_user - process user regulatory requests 27150d97a619SLuis R. Rodriguez * @user_request: a pending user regulatory request 27160d97a619SLuis R. Rodriguez * 27170d97a619SLuis R. Rodriguez * The wireless subsystem can use this function to process 27180d97a619SLuis R. Rodriguez * a regulatory request initiated by userspace. 27190d97a619SLuis R. Rodriguez */ 2720d34265a3SJohannes Berg static enum reg_request_treatment 2721d34265a3SJohannes Berg reg_process_hint_user(struct regulatory_request *user_request) 27220d97a619SLuis R. Rodriguez { 27230d97a619SLuis R. Rodriguez enum reg_request_treatment treatment; 27240d97a619SLuis R. Rodriguez 27250d97a619SLuis R. Rodriguez treatment = __reg_process_hint_user(user_request); 27260d97a619SLuis R. Rodriguez if (treatment == REG_REQ_IGNORE || 272737d33114SFinn Behrens treatment == REG_REQ_ALREADY_SET) 2728d34265a3SJohannes Berg return REG_REQ_IGNORE; 27290d97a619SLuis R. Rodriguez 27300d97a619SLuis R. Rodriguez user_request->intersect = treatment == REG_REQ_INTERSECT; 27310d97a619SLuis R. Rodriguez user_request->processed = false; 27325ad6ef5eSLuis R. Rodriguez 2733cecbb069SJohannes Berg if (reg_query_database(user_request)) { 273405f1a3eaSLuis R. Rodriguez reg_update_last_request(user_request); 27350d97a619SLuis R. Rodriguez user_alpha2[0] = user_request->alpha2[0]; 27360d97a619SLuis R. Rodriguez user_alpha2[1] = user_request->alpha2[1]; 2737d34265a3SJohannes Berg return REG_REQ_OK; 273825b20dbdSJohannes Berg } 2739d34265a3SJohannes Berg 2740d34265a3SJohannes Berg return REG_REQ_IGNORE; 27410d97a619SLuis R. Rodriguez } 27420d97a619SLuis R. Rodriguez 274321636c7fSLuis R. Rodriguez static enum reg_request_treatment 274421636c7fSLuis R. Rodriguez __reg_process_hint_driver(struct regulatory_request *driver_request) 274521636c7fSLuis R. Rodriguez { 274621636c7fSLuis R. Rodriguez struct regulatory_request *lr = get_last_request(); 274721636c7fSLuis R. Rodriguez 274821636c7fSLuis R. Rodriguez if (lr->initiator == NL80211_REGDOM_SET_BY_CORE) { 274921636c7fSLuis R. Rodriguez if (regdom_changes(driver_request->alpha2)) 275021636c7fSLuis R. Rodriguez return REG_REQ_OK; 275121636c7fSLuis R. Rodriguez return REG_REQ_ALREADY_SET; 275221636c7fSLuis R. Rodriguez } 275321636c7fSLuis R. Rodriguez 275421636c7fSLuis R. Rodriguez /* 275521636c7fSLuis R. Rodriguez * This would happen if you unplug and plug your card 275621636c7fSLuis R. Rodriguez * back in or if you add a new device for which the previously 275721636c7fSLuis R. Rodriguez * loaded card also agrees on the regulatory domain. 275821636c7fSLuis R. Rodriguez */ 275921636c7fSLuis R. Rodriguez if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER && 276021636c7fSLuis R. Rodriguez !regdom_changes(driver_request->alpha2)) 276121636c7fSLuis R. Rodriguez return REG_REQ_ALREADY_SET; 276221636c7fSLuis R. Rodriguez 276321636c7fSLuis R. Rodriguez return REG_REQ_INTERSECT; 276421636c7fSLuis R. Rodriguez } 276521636c7fSLuis R. Rodriguez 276621636c7fSLuis R. Rodriguez /** 276721636c7fSLuis R. Rodriguez * reg_process_hint_driver - process driver regulatory requests 2768726e6af9SAndrew Lunn * @wiphy: the wireless device for the regulatory request 276921636c7fSLuis R. Rodriguez * @driver_request: a pending driver regulatory request 277021636c7fSLuis R. Rodriguez * 277121636c7fSLuis R. Rodriguez * The wireless subsystem can use this function to process 277221636c7fSLuis R. Rodriguez * a regulatory request issued by an 802.11 driver. 277321636c7fSLuis R. Rodriguez * 277421636c7fSLuis R. Rodriguez * Returns one of the different reg request treatment values. 277521636c7fSLuis R. Rodriguez */ 277621636c7fSLuis R. Rodriguez static enum reg_request_treatment 277721636c7fSLuis R. Rodriguez reg_process_hint_driver(struct wiphy *wiphy, 277821636c7fSLuis R. Rodriguez struct regulatory_request *driver_request) 277921636c7fSLuis R. Rodriguez { 278034f05f54SArik Nemtsov const struct ieee80211_regdomain *regd, *tmp; 278121636c7fSLuis R. Rodriguez enum reg_request_treatment treatment; 278221636c7fSLuis R. Rodriguez 278321636c7fSLuis R. Rodriguez treatment = __reg_process_hint_driver(driver_request); 278421636c7fSLuis R. Rodriguez 278521636c7fSLuis R. Rodriguez switch (treatment) { 278621636c7fSLuis R. Rodriguez case REG_REQ_OK: 278721636c7fSLuis R. Rodriguez break; 278821636c7fSLuis R. Rodriguez case REG_REQ_IGNORE: 2789d34265a3SJohannes Berg return REG_REQ_IGNORE; 279021636c7fSLuis R. Rodriguez case REG_REQ_INTERSECT: 279121636c7fSLuis R. Rodriguez case REG_REQ_ALREADY_SET: 279221636c7fSLuis R. Rodriguez regd = reg_copy_regd(get_cfg80211_regdom()); 2793d34265a3SJohannes Berg if (IS_ERR(regd)) 2794d34265a3SJohannes Berg return REG_REQ_IGNORE; 279534f05f54SArik Nemtsov 279634f05f54SArik Nemtsov tmp = get_wiphy_regdom(wiphy); 2797a05829a7SJohannes Berg ASSERT_RTNL(); 2798a05829a7SJohannes Berg wiphy_lock(wiphy); 279921636c7fSLuis R. Rodriguez rcu_assign_pointer(wiphy->regd, regd); 2800a05829a7SJohannes Berg wiphy_unlock(wiphy); 280134f05f54SArik Nemtsov rcu_free_regdom(tmp); 280221636c7fSLuis R. Rodriguez } 280321636c7fSLuis R. Rodriguez 280421636c7fSLuis R. Rodriguez 280521636c7fSLuis R. Rodriguez driver_request->intersect = treatment == REG_REQ_INTERSECT; 280621636c7fSLuis R. Rodriguez driver_request->processed = false; 28075ad6ef5eSLuis R. Rodriguez 280821636c7fSLuis R. Rodriguez /* 280921636c7fSLuis R. Rodriguez * Since CRDA will not be called in this case as we already 281021636c7fSLuis R. Rodriguez * have applied the requested regulatory domain before we just 281121636c7fSLuis R. Rodriguez * inform userspace we have processed the request 281221636c7fSLuis R. Rodriguez */ 281321636c7fSLuis R. Rodriguez if (treatment == REG_REQ_ALREADY_SET) { 281421636c7fSLuis R. Rodriguez nl80211_send_reg_change_event(driver_request); 281525b20dbdSJohannes Berg reg_update_last_request(driver_request); 281621636c7fSLuis R. Rodriguez reg_set_request_processed(); 2817480908a7SJohannes Berg return REG_REQ_ALREADY_SET; 281821636c7fSLuis R. Rodriguez } 281921636c7fSLuis R. Rodriguez 2820d34265a3SJohannes Berg if (reg_query_database(driver_request)) { 282125b20dbdSJohannes Berg reg_update_last_request(driver_request); 282225b20dbdSJohannes Berg return REG_REQ_OK; 282321636c7fSLuis R. Rodriguez } 282421636c7fSLuis R. Rodriguez 2825d34265a3SJohannes Berg return REG_REQ_IGNORE; 2826d34265a3SJohannes Berg } 2827d34265a3SJohannes Berg 2828b23e7a9eSLuis R. Rodriguez static enum reg_request_treatment 2829b23e7a9eSLuis R. Rodriguez __reg_process_hint_country_ie(struct wiphy *wiphy, 2830b23e7a9eSLuis R. Rodriguez struct regulatory_request *country_ie_request) 2831b23e7a9eSLuis R. Rodriguez { 2832b23e7a9eSLuis R. Rodriguez struct wiphy *last_wiphy = NULL; 2833b23e7a9eSLuis R. Rodriguez struct regulatory_request *lr = get_last_request(); 2834b23e7a9eSLuis R. Rodriguez 2835b23e7a9eSLuis R. Rodriguez if (reg_request_cell_base(lr)) { 2836b23e7a9eSLuis R. Rodriguez /* Trust a Cell base station over the AP's country IE */ 2837b23e7a9eSLuis R. Rodriguez if (regdom_changes(country_ie_request->alpha2)) 2838b23e7a9eSLuis R. Rodriguez return REG_REQ_IGNORE; 2839b23e7a9eSLuis R. Rodriguez return REG_REQ_ALREADY_SET; 28402a901468SLuis R. Rodriguez } else { 28412a901468SLuis R. Rodriguez if (wiphy->regulatory_flags & REGULATORY_COUNTRY_IE_IGNORE) 28422a901468SLuis R. Rodriguez return REG_REQ_IGNORE; 2843b23e7a9eSLuis R. Rodriguez } 2844b23e7a9eSLuis R. Rodriguez 2845b23e7a9eSLuis R. Rodriguez if (unlikely(!is_an_alpha2(country_ie_request->alpha2))) 2846b23e7a9eSLuis R. Rodriguez return -EINVAL; 28472f1c6c57SLuis R. Rodriguez 28482f1c6c57SLuis R. Rodriguez if (lr->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE) 28492f1c6c57SLuis R. Rodriguez return REG_REQ_OK; 28502f1c6c57SLuis R. Rodriguez 28512f1c6c57SLuis R. Rodriguez last_wiphy = wiphy_idx_to_wiphy(lr->wiphy_idx); 28522f1c6c57SLuis R. Rodriguez 2853b23e7a9eSLuis R. Rodriguez if (last_wiphy != wiphy) { 2854b23e7a9eSLuis R. Rodriguez /* 2855b23e7a9eSLuis R. Rodriguez * Two cards with two APs claiming different 2856b23e7a9eSLuis R. Rodriguez * Country IE alpha2s. We could 2857b23e7a9eSLuis R. Rodriguez * intersect them, but that seems unlikely 2858b23e7a9eSLuis R. Rodriguez * to be correct. Reject second one for now. 2859b23e7a9eSLuis R. Rodriguez */ 2860b23e7a9eSLuis R. Rodriguez if (regdom_changes(country_ie_request->alpha2)) 2861b23e7a9eSLuis R. Rodriguez return REG_REQ_IGNORE; 2862b23e7a9eSLuis R. Rodriguez return REG_REQ_ALREADY_SET; 2863b23e7a9eSLuis R. Rodriguez } 286470dcec5aSEmmanuel Grumbach 286570dcec5aSEmmanuel Grumbach if (regdom_changes(country_ie_request->alpha2)) 2866b23e7a9eSLuis R. Rodriguez return REG_REQ_OK; 2867b23e7a9eSLuis R. Rodriguez return REG_REQ_ALREADY_SET; 2868b23e7a9eSLuis R. Rodriguez } 2869b23e7a9eSLuis R. Rodriguez 2870b3eb7f3fSLuis R. Rodriguez /** 2871b23e7a9eSLuis R. Rodriguez * reg_process_hint_country_ie - process regulatory requests from country IEs 2872726e6af9SAndrew Lunn * @wiphy: the wireless device for the regulatory request 2873b23e7a9eSLuis R. Rodriguez * @country_ie_request: a regulatory request from a country IE 2874d1c96a9aSLuis R. Rodriguez * 2875b23e7a9eSLuis R. Rodriguez * The wireless subsystem can use this function to process 2876b23e7a9eSLuis R. Rodriguez * a regulatory request issued by a country Information Element. 2877d1c96a9aSLuis R. Rodriguez * 28782f92212bSJohannes Berg * Returns one of the different reg request treatment values. 2879d1c96a9aSLuis R. Rodriguez */ 28802f92212bSJohannes Berg static enum reg_request_treatment 2881b23e7a9eSLuis R. Rodriguez reg_process_hint_country_ie(struct wiphy *wiphy, 2882b23e7a9eSLuis R. Rodriguez struct regulatory_request *country_ie_request) 2883b2e1b302SLuis R. Rodriguez { 28842f92212bSJohannes Berg enum reg_request_treatment treatment; 2885b2e1b302SLuis R. Rodriguez 2886b23e7a9eSLuis R. Rodriguez treatment = __reg_process_hint_country_ie(wiphy, country_ie_request); 2887761cf7ecSLuis R. Rodriguez 28882f92212bSJohannes Berg switch (treatment) { 28892f92212bSJohannes Berg case REG_REQ_OK: 28902f92212bSJohannes Berg break; 2891b23e7a9eSLuis R. Rodriguez case REG_REQ_IGNORE: 2892d34265a3SJohannes Berg return REG_REQ_IGNORE; 2893b23e7a9eSLuis R. Rodriguez case REG_REQ_ALREADY_SET: 2894c888393bSArik Nemtsov reg_free_request(country_ie_request); 2895480908a7SJohannes Berg return REG_REQ_ALREADY_SET; 2896b23e7a9eSLuis R. Rodriguez case REG_REQ_INTERSECT: 2897fb1fc7adSLuis R. Rodriguez /* 2898b23e7a9eSLuis R. Rodriguez * This doesn't happen yet, not sure we 2899b23e7a9eSLuis R. Rodriguez * ever want to support it for this case. 2900fb1fc7adSLuis R. Rodriguez */ 29018db0c433SToke Høiland-Jørgensen WARN_ONCE(1, "Unexpected intersection for country elements"); 2902d34265a3SJohannes Berg return REG_REQ_IGNORE; 2903d951c1ddSLuis R. Rodriguez } 2904b2e1b302SLuis R. Rodriguez 2905b23e7a9eSLuis R. Rodriguez country_ie_request->intersect = false; 2906b23e7a9eSLuis R. Rodriguez country_ie_request->processed = false; 29075ad6ef5eSLuis R. Rodriguez 2908d34265a3SJohannes Berg if (reg_query_database(country_ie_request)) { 290905f1a3eaSLuis R. Rodriguez reg_update_last_request(country_ie_request); 291025b20dbdSJohannes Berg return REG_REQ_OK; 2911b2e1b302SLuis R. Rodriguez } 2912b2e1b302SLuis R. Rodriguez 2913d34265a3SJohannes Berg return REG_REQ_IGNORE; 2914d34265a3SJohannes Berg } 2915d34265a3SJohannes Berg 291689766727SVasanthakumar Thiagarajan bool reg_dfs_domain_same(struct wiphy *wiphy1, struct wiphy *wiphy2) 291789766727SVasanthakumar Thiagarajan { 291889766727SVasanthakumar Thiagarajan const struct ieee80211_regdomain *wiphy1_regd = NULL; 291989766727SVasanthakumar Thiagarajan const struct ieee80211_regdomain *wiphy2_regd = NULL; 292089766727SVasanthakumar Thiagarajan const struct ieee80211_regdomain *cfg80211_regd = NULL; 292189766727SVasanthakumar Thiagarajan bool dfs_domain_same; 292289766727SVasanthakumar Thiagarajan 292389766727SVasanthakumar Thiagarajan rcu_read_lock(); 292489766727SVasanthakumar Thiagarajan 292589766727SVasanthakumar Thiagarajan cfg80211_regd = rcu_dereference(cfg80211_regdomain); 292689766727SVasanthakumar Thiagarajan wiphy1_regd = rcu_dereference(wiphy1->regd); 292789766727SVasanthakumar Thiagarajan if (!wiphy1_regd) 292889766727SVasanthakumar Thiagarajan wiphy1_regd = cfg80211_regd; 292989766727SVasanthakumar Thiagarajan 293089766727SVasanthakumar Thiagarajan wiphy2_regd = rcu_dereference(wiphy2->regd); 293189766727SVasanthakumar Thiagarajan if (!wiphy2_regd) 293289766727SVasanthakumar Thiagarajan wiphy2_regd = cfg80211_regd; 293389766727SVasanthakumar Thiagarajan 293489766727SVasanthakumar Thiagarajan dfs_domain_same = wiphy1_regd->dfs_region == wiphy2_regd->dfs_region; 293589766727SVasanthakumar Thiagarajan 293689766727SVasanthakumar Thiagarajan rcu_read_unlock(); 293789766727SVasanthakumar Thiagarajan 293889766727SVasanthakumar Thiagarajan return dfs_domain_same; 293989766727SVasanthakumar Thiagarajan } 294089766727SVasanthakumar Thiagarajan 294189766727SVasanthakumar Thiagarajan static void reg_copy_dfs_chan_state(struct ieee80211_channel *dst_chan, 294289766727SVasanthakumar Thiagarajan struct ieee80211_channel *src_chan) 294389766727SVasanthakumar Thiagarajan { 294489766727SVasanthakumar Thiagarajan if (!(dst_chan->flags & IEEE80211_CHAN_RADAR) || 294589766727SVasanthakumar Thiagarajan !(src_chan->flags & IEEE80211_CHAN_RADAR)) 294689766727SVasanthakumar Thiagarajan return; 294789766727SVasanthakumar Thiagarajan 294889766727SVasanthakumar Thiagarajan if (dst_chan->flags & IEEE80211_CHAN_DISABLED || 294989766727SVasanthakumar Thiagarajan src_chan->flags & IEEE80211_CHAN_DISABLED) 295089766727SVasanthakumar Thiagarajan return; 295189766727SVasanthakumar Thiagarajan 295289766727SVasanthakumar Thiagarajan if (src_chan->center_freq == dst_chan->center_freq && 295389766727SVasanthakumar Thiagarajan dst_chan->dfs_state == NL80211_DFS_USABLE) { 295489766727SVasanthakumar Thiagarajan dst_chan->dfs_state = src_chan->dfs_state; 295589766727SVasanthakumar Thiagarajan dst_chan->dfs_state_entered = src_chan->dfs_state_entered; 295689766727SVasanthakumar Thiagarajan } 295789766727SVasanthakumar Thiagarajan } 295889766727SVasanthakumar Thiagarajan 295989766727SVasanthakumar Thiagarajan static void wiphy_share_dfs_chan_state(struct wiphy *dst_wiphy, 296089766727SVasanthakumar Thiagarajan struct wiphy *src_wiphy) 296189766727SVasanthakumar Thiagarajan { 296289766727SVasanthakumar Thiagarajan struct ieee80211_supported_band *src_sband, *dst_sband; 296389766727SVasanthakumar Thiagarajan struct ieee80211_channel *src_chan, *dst_chan; 296489766727SVasanthakumar Thiagarajan int i, j, band; 296589766727SVasanthakumar Thiagarajan 296689766727SVasanthakumar Thiagarajan if (!reg_dfs_domain_same(dst_wiphy, src_wiphy)) 296789766727SVasanthakumar Thiagarajan return; 296889766727SVasanthakumar Thiagarajan 296989766727SVasanthakumar Thiagarajan for (band = 0; band < NUM_NL80211_BANDS; band++) { 297089766727SVasanthakumar Thiagarajan dst_sband = dst_wiphy->bands[band]; 297189766727SVasanthakumar Thiagarajan src_sband = src_wiphy->bands[band]; 297289766727SVasanthakumar Thiagarajan if (!dst_sband || !src_sband) 297389766727SVasanthakumar Thiagarajan continue; 297489766727SVasanthakumar Thiagarajan 297589766727SVasanthakumar Thiagarajan for (i = 0; i < dst_sband->n_channels; i++) { 297689766727SVasanthakumar Thiagarajan dst_chan = &dst_sband->channels[i]; 297789766727SVasanthakumar Thiagarajan for (j = 0; j < src_sband->n_channels; j++) { 297889766727SVasanthakumar Thiagarajan src_chan = &src_sband->channels[j]; 297989766727SVasanthakumar Thiagarajan reg_copy_dfs_chan_state(dst_chan, src_chan); 298089766727SVasanthakumar Thiagarajan } 298189766727SVasanthakumar Thiagarajan } 298289766727SVasanthakumar Thiagarajan } 298389766727SVasanthakumar Thiagarajan } 298489766727SVasanthakumar Thiagarajan 298589766727SVasanthakumar Thiagarajan static void wiphy_all_share_dfs_chan_state(struct wiphy *wiphy) 298689766727SVasanthakumar Thiagarajan { 298789766727SVasanthakumar Thiagarajan struct cfg80211_registered_device *rdev; 298889766727SVasanthakumar Thiagarajan 298989766727SVasanthakumar Thiagarajan ASSERT_RTNL(); 299089766727SVasanthakumar Thiagarajan 299189766727SVasanthakumar Thiagarajan list_for_each_entry(rdev, &cfg80211_rdev_list, list) { 299289766727SVasanthakumar Thiagarajan if (wiphy == &rdev->wiphy) 299389766727SVasanthakumar Thiagarajan continue; 299489766727SVasanthakumar Thiagarajan wiphy_share_dfs_chan_state(wiphy, &rdev->wiphy); 299589766727SVasanthakumar Thiagarajan } 299689766727SVasanthakumar Thiagarajan } 299789766727SVasanthakumar Thiagarajan 299830a548c7SLuis R. Rodriguez /* This processes *all* regulatory hints */ 29991daa37c7SLuis R. Rodriguez static void reg_process_hint(struct regulatory_request *reg_request) 3000fe33eb39SLuis R. Rodriguez { 3001fe33eb39SLuis R. Rodriguez struct wiphy *wiphy = NULL; 3002b3eb7f3fSLuis R. Rodriguez enum reg_request_treatment treatment; 30031db58529SYu Zhao enum nl80211_reg_initiator initiator = reg_request->initiator; 3004fe33eb39SLuis R. Rodriguez 3005f4173766SJohannes Berg if (reg_request->wiphy_idx != WIPHY_IDX_INVALID) 3006fe33eb39SLuis R. Rodriguez wiphy = wiphy_idx_to_wiphy(reg_request->wiphy_idx); 3007fe33eb39SLuis R. Rodriguez 30081db58529SYu Zhao switch (initiator) { 3009b3eb7f3fSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_CORE: 3010d34265a3SJohannes Berg treatment = reg_process_hint_core(reg_request); 3011d34265a3SJohannes Berg break; 3012b3eb7f3fSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_USER: 3013d34265a3SJohannes Berg treatment = reg_process_hint_user(reg_request); 3014d34265a3SJohannes Berg break; 3015b3eb7f3fSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_DRIVER: 3016772f0389SIlan Peer if (!wiphy) 3017772f0389SIlan Peer goto out_free; 301821636c7fSLuis R. Rodriguez treatment = reg_process_hint_driver(wiphy, reg_request); 301921636c7fSLuis R. Rodriguez break; 3020b3eb7f3fSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_COUNTRY_IE: 3021772f0389SIlan Peer if (!wiphy) 3022772f0389SIlan Peer goto out_free; 3023b23e7a9eSLuis R. Rodriguez treatment = reg_process_hint_country_ie(wiphy, reg_request); 3024b3eb7f3fSLuis R. Rodriguez break; 3025b3eb7f3fSLuis R. Rodriguez default: 30261db58529SYu Zhao WARN(1, "invalid initiator %d\n", initiator); 3027772f0389SIlan Peer goto out_free; 3028b3eb7f3fSLuis R. Rodriguez } 3029b3eb7f3fSLuis R. Rodriguez 3030d34265a3SJohannes Berg if (treatment == REG_REQ_IGNORE) 3031d34265a3SJohannes Berg goto out_free; 3032d34265a3SJohannes Berg 3033480908a7SJohannes Berg WARN(treatment != REG_REQ_OK && treatment != REG_REQ_ALREADY_SET, 3034480908a7SJohannes Berg "unexpected treatment value %d\n", treatment); 3035480908a7SJohannes Berg 3036841b351cSJohn Linville /* This is required so that the orig_* parameters are saved. 3037841b351cSJohn Linville * NOTE: treatment must be set for any case that reaches here! 3038841b351cSJohn Linville */ 3039b23e7a9eSLuis R. Rodriguez if (treatment == REG_REQ_ALREADY_SET && wiphy && 3040ad932f04SArik Nemtsov wiphy->regulatory_flags & REGULATORY_STRICT_REG) { 30411db58529SYu Zhao wiphy_update_regulatory(wiphy, initiator); 304289766727SVasanthakumar Thiagarajan wiphy_all_share_dfs_chan_state(wiphy); 3043ad932f04SArik Nemtsov reg_check_channels(); 3044ad932f04SArik Nemtsov } 3045772f0389SIlan Peer 3046772f0389SIlan Peer return; 3047772f0389SIlan Peer 3048772f0389SIlan Peer out_free: 3049c888393bSArik Nemtsov reg_free_request(reg_request); 3050fe33eb39SLuis R. Rodriguez } 3051fe33eb39SLuis R. Rodriguez 3052aced43ceSAmar Singhal static void notify_self_managed_wiphys(struct regulatory_request *request) 3053aced43ceSAmar Singhal { 3054aced43ceSAmar Singhal struct cfg80211_registered_device *rdev; 3055aced43ceSAmar Singhal struct wiphy *wiphy; 3056aced43ceSAmar Singhal 3057aced43ceSAmar Singhal list_for_each_entry(rdev, &cfg80211_rdev_list, list) { 3058aced43ceSAmar Singhal wiphy = &rdev->wiphy; 3059aced43ceSAmar Singhal if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED && 3060c82c06ceSSriram R request->initiator == NL80211_REGDOM_SET_BY_USER) 3061aced43ceSAmar Singhal reg_call_notifier(wiphy, request); 3062aced43ceSAmar Singhal } 3063aced43ceSAmar Singhal } 3064aced43ceSAmar Singhal 3065b2e253cfSLuis R. Rodriguez /* 3066b2e253cfSLuis R. Rodriguez * Processes regulatory hints, this is all the NL80211_REGDOM_SET_BY_* 3067b2e253cfSLuis R. Rodriguez * Regulatory hints come on a first come first serve basis and we 3068b2e253cfSLuis R. Rodriguez * must process each one atomically. 3069b2e253cfSLuis R. Rodriguez */ 3070fe33eb39SLuis R. Rodriguez static void reg_process_pending_hints(void) 3071fe33eb39SLuis R. Rodriguez { 3072c492db37SJohannes Berg struct regulatory_request *reg_request, *lr; 3073fe33eb39SLuis R. Rodriguez 3074c492db37SJohannes Berg lr = get_last_request(); 3075b0e2880bSLuis R. Rodriguez 3076b2e253cfSLuis R. Rodriguez /* When last_request->processed becomes true this will be rescheduled */ 3077c492db37SJohannes Berg if (lr && !lr->processed) { 30780d31d4dbSHodaszi, Robert pr_debug("Pending regulatory request, waiting for it to be processed...\n"); 30795fe231e8SJohannes Berg return; 3080b2e253cfSLuis R. Rodriguez } 3081b2e253cfSLuis R. Rodriguez 3082fe33eb39SLuis R. Rodriguez spin_lock(®_requests_lock); 3083b2e253cfSLuis R. Rodriguez 3084b2e253cfSLuis R. Rodriguez if (list_empty(®_requests_list)) { 3085b2e253cfSLuis R. Rodriguez spin_unlock(®_requests_lock); 30865fe231e8SJohannes Berg return; 3087b2e253cfSLuis R. Rodriguez } 3088b2e253cfSLuis R. Rodriguez 3089fe33eb39SLuis R. Rodriguez reg_request = list_first_entry(®_requests_list, 3090fe33eb39SLuis R. Rodriguez struct regulatory_request, 3091fe33eb39SLuis R. Rodriguez list); 3092fe33eb39SLuis R. Rodriguez list_del_init(®_request->list); 3093fe33eb39SLuis R. Rodriguez 3094d951c1ddSLuis R. Rodriguez spin_unlock(®_requests_lock); 3095b0e2880bSLuis R. Rodriguez 3096aced43ceSAmar Singhal notify_self_managed_wiphys(reg_request); 3097ef51fb1dSArik Nemtsov 30981daa37c7SLuis R. Rodriguez reg_process_hint(reg_request); 30992e54a689SBen 31002e54a689SBen lr = get_last_request(); 31012e54a689SBen 31022e54a689SBen spin_lock(®_requests_lock); 31032e54a689SBen if (!list_empty(®_requests_list) && lr && lr->processed) 31042e54a689SBen schedule_work(®_work); 31052e54a689SBen spin_unlock(®_requests_lock); 3106fe33eb39SLuis R. Rodriguez } 3107fe33eb39SLuis R. Rodriguez 3108e38f8a7aSLuis R. Rodriguez /* Processes beacon hints -- this has nothing to do with country IEs */ 3109e38f8a7aSLuis R. Rodriguez static void reg_process_pending_beacon_hints(void) 3110e38f8a7aSLuis R. Rodriguez { 311179c97e97SJohannes Berg struct cfg80211_registered_device *rdev; 3112e38f8a7aSLuis R. Rodriguez struct reg_beacon *pending_beacon, *tmp; 3113e38f8a7aSLuis R. Rodriguez 3114e38f8a7aSLuis R. Rodriguez /* This goes through the _pending_ beacon list */ 3115e38f8a7aSLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 3116e38f8a7aSLuis R. Rodriguez 3117e38f8a7aSLuis R. Rodriguez list_for_each_entry_safe(pending_beacon, tmp, 3118e38f8a7aSLuis R. Rodriguez ®_pending_beacons, list) { 3119e38f8a7aSLuis R. Rodriguez list_del_init(&pending_beacon->list); 3120e38f8a7aSLuis R. Rodriguez 3121e38f8a7aSLuis R. Rodriguez /* Applies the beacon hint to current wiphys */ 312279c97e97SJohannes Berg list_for_each_entry(rdev, &cfg80211_rdev_list, list) 312379c97e97SJohannes Berg wiphy_update_new_beacon(&rdev->wiphy, pending_beacon); 3124e38f8a7aSLuis R. Rodriguez 3125e38f8a7aSLuis R. Rodriguez /* Remembers the beacon hint for new wiphys or reg changes */ 3126e38f8a7aSLuis R. Rodriguez list_add_tail(&pending_beacon->list, ®_beacon_list); 3127e38f8a7aSLuis R. Rodriguez } 3128e38f8a7aSLuis R. Rodriguez 3129e38f8a7aSLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 3130e38f8a7aSLuis R. Rodriguez } 3131e38f8a7aSLuis R. Rodriguez 3132a05829a7SJohannes Berg static void reg_process_self_managed_hint(struct wiphy *wiphy) 3133b0d7aa59SJonathan Doron { 3134a05829a7SJohannes Berg struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); 3135b0d7aa59SJonathan Doron const struct ieee80211_regdomain *tmp; 3136b0d7aa59SJonathan Doron const struct ieee80211_regdomain *regd; 313757fbcce3SJohannes Berg enum nl80211_band band; 3138b0d7aa59SJonathan Doron struct regulatory_request request = {}; 3139b0d7aa59SJonathan Doron 3140a05829a7SJohannes Berg ASSERT_RTNL(); 3141a05829a7SJohannes Berg lockdep_assert_wiphy(wiphy); 3142b0d7aa59SJonathan Doron 3143b0d7aa59SJonathan Doron spin_lock(®_requests_lock); 3144b0d7aa59SJonathan Doron regd = rdev->requested_regd; 3145b0d7aa59SJonathan Doron rdev->requested_regd = NULL; 3146b0d7aa59SJonathan Doron spin_unlock(®_requests_lock); 3147b0d7aa59SJonathan Doron 3148a05829a7SJohannes Berg if (!regd) 3149a05829a7SJohannes Berg return; 3150b0d7aa59SJonathan Doron 3151b0d7aa59SJonathan Doron tmp = get_wiphy_regdom(wiphy); 3152b0d7aa59SJonathan Doron rcu_assign_pointer(wiphy->regd, regd); 3153b0d7aa59SJonathan Doron rcu_free_regdom(tmp); 3154b0d7aa59SJonathan Doron 315557fbcce3SJohannes Berg for (band = 0; band < NUM_NL80211_BANDS; band++) 3156b0d7aa59SJonathan Doron handle_band_custom(wiphy, wiphy->bands[band], regd); 3157b0d7aa59SJonathan Doron 3158b0d7aa59SJonathan Doron reg_process_ht_flags(wiphy); 3159b0d7aa59SJonathan Doron 3160b0d7aa59SJonathan Doron request.wiphy_idx = get_wiphy_idx(wiphy); 3161b0d7aa59SJonathan Doron request.alpha2[0] = regd->alpha2[0]; 3162b0d7aa59SJonathan Doron request.alpha2[1] = regd->alpha2[1]; 3163b0d7aa59SJonathan Doron request.initiator = NL80211_REGDOM_SET_BY_DRIVER; 3164b0d7aa59SJonathan Doron 3165b0d7aa59SJonathan Doron nl80211_send_wiphy_reg_change_event(&request); 3166b0d7aa59SJonathan Doron } 3167b0d7aa59SJonathan Doron 3168a05829a7SJohannes Berg static void reg_process_self_managed_hints(void) 3169a05829a7SJohannes Berg { 3170a05829a7SJohannes Berg struct cfg80211_registered_device *rdev; 3171a05829a7SJohannes Berg 3172a05829a7SJohannes Berg ASSERT_RTNL(); 3173a05829a7SJohannes Berg 3174a05829a7SJohannes Berg list_for_each_entry(rdev, &cfg80211_rdev_list, list) { 3175a05829a7SJohannes Berg wiphy_lock(&rdev->wiphy); 3176a05829a7SJohannes Berg reg_process_self_managed_hint(&rdev->wiphy); 3177a05829a7SJohannes Berg wiphy_unlock(&rdev->wiphy); 3178a05829a7SJohannes Berg } 3179a05829a7SJohannes Berg 3180b0d7aa59SJonathan Doron reg_check_channels(); 3181b0d7aa59SJonathan Doron } 3182b0d7aa59SJonathan Doron 3183fe33eb39SLuis R. Rodriguez static void reg_todo(struct work_struct *work) 3184fe33eb39SLuis R. Rodriguez { 31855fe231e8SJohannes Berg rtnl_lock(); 3186fe33eb39SLuis R. Rodriguez reg_process_pending_hints(); 3187e38f8a7aSLuis R. Rodriguez reg_process_pending_beacon_hints(); 3188b0d7aa59SJonathan Doron reg_process_self_managed_hints(); 31895fe231e8SJohannes Berg rtnl_unlock(); 3190fe33eb39SLuis R. Rodriguez } 3191fe33eb39SLuis R. Rodriguez 3192fe33eb39SLuis R. Rodriguez static void queue_regulatory_request(struct regulatory_request *request) 3193fe33eb39SLuis R. Rodriguez { 3194c61029c7SJohn W. Linville request->alpha2[0] = toupper(request->alpha2[0]); 3195c61029c7SJohn W. Linville request->alpha2[1] = toupper(request->alpha2[1]); 3196c61029c7SJohn W. Linville 3197fe33eb39SLuis R. Rodriguez spin_lock(®_requests_lock); 3198fe33eb39SLuis R. Rodriguez list_add_tail(&request->list, ®_requests_list); 3199fe33eb39SLuis R. Rodriguez spin_unlock(®_requests_lock); 3200fe33eb39SLuis R. Rodriguez 3201fe33eb39SLuis R. Rodriguez schedule_work(®_work); 3202fe33eb39SLuis R. Rodriguez } 3203fe33eb39SLuis R. Rodriguez 320409d989d1SLuis R. Rodriguez /* 320509d989d1SLuis R. Rodriguez * Core regulatory hint -- happens during cfg80211_init() 320609d989d1SLuis R. Rodriguez * and when we restore regulatory settings. 320709d989d1SLuis R. Rodriguez */ 3208ba25c141SLuis R. Rodriguez static int regulatory_hint_core(const char *alpha2) 3209ba25c141SLuis R. Rodriguez { 3210ba25c141SLuis R. Rodriguez struct regulatory_request *request; 3211ba25c141SLuis R. Rodriguez 32121a919318SJohannes Berg request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL); 3213ba25c141SLuis R. Rodriguez if (!request) 3214ba25c141SLuis R. Rodriguez return -ENOMEM; 3215ba25c141SLuis R. Rodriguez 3216ba25c141SLuis R. Rodriguez request->alpha2[0] = alpha2[0]; 3217ba25c141SLuis R. Rodriguez request->alpha2[1] = alpha2[1]; 32187db90f4aSLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_CORE; 321924f33e64SAndrei Otcheretianski request->wiphy_idx = WIPHY_IDX_INVALID; 3220ba25c141SLuis R. Rodriguez 322131e99729SLuis R. Rodriguez queue_regulatory_request(request); 32225078b2e3SLuis R. Rodriguez 3223fe33eb39SLuis R. Rodriguez return 0; 3224ba25c141SLuis R. Rodriguez } 3225ba25c141SLuis R. Rodriguez 3226fe33eb39SLuis R. Rodriguez /* User hints */ 322757b5ce07SLuis R. Rodriguez int regulatory_hint_user(const char *alpha2, 322857b5ce07SLuis R. Rodriguez enum nl80211_user_reg_hint_type user_reg_hint_type) 3229b2e1b302SLuis R. Rodriguez { 3230fe33eb39SLuis R. Rodriguez struct regulatory_request *request; 3231fe33eb39SLuis R. Rodriguez 3232fdc9d7b2SJohannes Berg if (WARN_ON(!alpha2)) 3233fdc9d7b2SJohannes Berg return -EINVAL; 3234b2e1b302SLuis R. Rodriguez 323547caf685SJohannes Berg if (!is_world_regdom(alpha2) && !is_an_alpha2(alpha2)) 323647caf685SJohannes Berg return -EINVAL; 323747caf685SJohannes Berg 3238fe33eb39SLuis R. Rodriguez request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL); 3239fe33eb39SLuis R. Rodriguez if (!request) 3240fe33eb39SLuis R. Rodriguez return -ENOMEM; 3241fe33eb39SLuis R. Rodriguez 3242f4173766SJohannes Berg request->wiphy_idx = WIPHY_IDX_INVALID; 3243fe33eb39SLuis R. Rodriguez request->alpha2[0] = alpha2[0]; 3244fe33eb39SLuis R. Rodriguez request->alpha2[1] = alpha2[1]; 3245e12822e1SLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_USER; 324657b5ce07SLuis R. Rodriguez request->user_reg_hint_type = user_reg_hint_type; 3247fe33eb39SLuis R. Rodriguez 3248c37722bdSIlan peer /* Allow calling CRDA again */ 3249b6863036SJohannes Berg reset_crda_timeouts(); 3250c37722bdSIlan peer 3251fe33eb39SLuis R. Rodriguez queue_regulatory_request(request); 3252fe33eb39SLuis R. Rodriguez 3253fe33eb39SLuis R. Rodriguez return 0; 3254fe33eb39SLuis R. Rodriguez } 3255fe33eb39SLuis R. Rodriguez 325605050753SIlan peer int regulatory_hint_indoor(bool is_indoor, u32 portid) 325752616f2bSIlan Peer { 325805050753SIlan peer spin_lock(®_indoor_lock); 325952616f2bSIlan Peer 326005050753SIlan peer /* It is possible that more than one user space process is trying to 326105050753SIlan peer * configure the indoor setting. To handle such cases, clear the indoor 326205050753SIlan peer * setting in case that some process does not think that the device 326305050753SIlan peer * is operating in an indoor environment. In addition, if a user space 326405050753SIlan peer * process indicates that it is controlling the indoor setting, save its 326505050753SIlan peer * portid, i.e., make it the owner. 326605050753SIlan peer */ 326705050753SIlan peer reg_is_indoor = is_indoor; 326805050753SIlan peer if (reg_is_indoor) { 326905050753SIlan peer if (!reg_is_indoor_portid) 327005050753SIlan peer reg_is_indoor_portid = portid; 327105050753SIlan peer } else { 327205050753SIlan peer reg_is_indoor_portid = 0; 327305050753SIlan peer } 327452616f2bSIlan Peer 327505050753SIlan peer spin_unlock(®_indoor_lock); 327605050753SIlan peer 327705050753SIlan peer if (!is_indoor) 327805050753SIlan peer reg_check_channels(); 327952616f2bSIlan Peer 328052616f2bSIlan Peer return 0; 328152616f2bSIlan Peer } 328252616f2bSIlan Peer 328305050753SIlan peer void regulatory_netlink_notify(u32 portid) 328405050753SIlan peer { 328505050753SIlan peer spin_lock(®_indoor_lock); 328605050753SIlan peer 328705050753SIlan peer if (reg_is_indoor_portid != portid) { 328805050753SIlan peer spin_unlock(®_indoor_lock); 328905050753SIlan peer return; 329005050753SIlan peer } 329105050753SIlan peer 329205050753SIlan peer reg_is_indoor = false; 329305050753SIlan peer reg_is_indoor_portid = 0; 329405050753SIlan peer 329505050753SIlan peer spin_unlock(®_indoor_lock); 329605050753SIlan peer 329705050753SIlan peer reg_check_channels(); 329805050753SIlan peer } 329905050753SIlan peer 3300fe33eb39SLuis R. Rodriguez /* Driver hints */ 3301fe33eb39SLuis R. Rodriguez int regulatory_hint(struct wiphy *wiphy, const char *alpha2) 3302fe33eb39SLuis R. Rodriguez { 3303fe33eb39SLuis R. Rodriguez struct regulatory_request *request; 3304fe33eb39SLuis R. Rodriguez 3305fdc9d7b2SJohannes Berg if (WARN_ON(!alpha2 || !wiphy)) 3306fdc9d7b2SJohannes Berg return -EINVAL; 3307fe33eb39SLuis R. Rodriguez 33084f7b9140SLuis R. Rodriguez wiphy->regulatory_flags &= ~REGULATORY_CUSTOM_REG; 33094f7b9140SLuis R. Rodriguez 3310fe33eb39SLuis R. Rodriguez request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL); 3311fe33eb39SLuis R. Rodriguez if (!request) 3312fe33eb39SLuis R. Rodriguez return -ENOMEM; 3313fe33eb39SLuis R. Rodriguez 3314fe33eb39SLuis R. Rodriguez request->wiphy_idx = get_wiphy_idx(wiphy); 3315fe33eb39SLuis R. Rodriguez 3316fe33eb39SLuis R. Rodriguez request->alpha2[0] = alpha2[0]; 3317fe33eb39SLuis R. Rodriguez request->alpha2[1] = alpha2[1]; 33187db90f4aSLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_DRIVER; 3319fe33eb39SLuis R. Rodriguez 3320c37722bdSIlan peer /* Allow calling CRDA again */ 3321b6863036SJohannes Berg reset_crda_timeouts(); 3322c37722bdSIlan peer 3323fe33eb39SLuis R. Rodriguez queue_regulatory_request(request); 3324fe33eb39SLuis R. Rodriguez 3325fe33eb39SLuis R. Rodriguez return 0; 3326b2e1b302SLuis R. Rodriguez } 3327b2e1b302SLuis R. Rodriguez EXPORT_SYMBOL(regulatory_hint); 3328b2e1b302SLuis R. Rodriguez 332957fbcce3SJohannes Berg void regulatory_hint_country_ie(struct wiphy *wiphy, enum nl80211_band band, 33301a919318SJohannes Berg const u8 *country_ie, u8 country_ie_len) 33313f2355cbSLuis R. Rodriguez { 33323f2355cbSLuis R. Rodriguez char alpha2[2]; 33333f2355cbSLuis R. Rodriguez enum environment_cap env = ENVIRON_ANY; 3334db2424c5SJohannes Berg struct regulatory_request *request = NULL, *lr; 3335d335fe63SLuis R. Rodriguez 33363f2355cbSLuis R. Rodriguez /* IE len must be evenly divisible by 2 */ 33373f2355cbSLuis R. Rodriguez if (country_ie_len & 0x01) 3338db2424c5SJohannes Berg return; 33393f2355cbSLuis R. Rodriguez 33403f2355cbSLuis R. Rodriguez if (country_ie_len < IEEE80211_COUNTRY_IE_MIN_LEN) 3341db2424c5SJohannes Berg return; 3342db2424c5SJohannes Berg 3343db2424c5SJohannes Berg request = kzalloc(sizeof(*request), GFP_KERNEL); 3344db2424c5SJohannes Berg if (!request) 3345db2424c5SJohannes Berg return; 33463f2355cbSLuis R. Rodriguez 33473f2355cbSLuis R. Rodriguez alpha2[0] = country_ie[0]; 33483f2355cbSLuis R. Rodriguez alpha2[1] = country_ie[1]; 33493f2355cbSLuis R. Rodriguez 33503f2355cbSLuis R. Rodriguez if (country_ie[2] == 'I') 33513f2355cbSLuis R. Rodriguez env = ENVIRON_INDOOR; 33523f2355cbSLuis R. Rodriguez else if (country_ie[2] == 'O') 33533f2355cbSLuis R. Rodriguez env = ENVIRON_OUTDOOR; 33543f2355cbSLuis R. Rodriguez 3355db2424c5SJohannes Berg rcu_read_lock(); 3356db2424c5SJohannes Berg lr = get_last_request(); 3357db2424c5SJohannes Berg 3358db2424c5SJohannes Berg if (unlikely(!lr)) 3359db2424c5SJohannes Berg goto out; 3360db2424c5SJohannes Berg 3361fb1fc7adSLuis R. Rodriguez /* 33628b19e6caSLuis R. Rodriguez * We will run this only upon a successful connection on cfg80211. 33634b44c8bcSLuis R. Rodriguez * We leave conflict resolution to the workqueue, where can hold 33645fe231e8SJohannes Berg * the RTNL. 3365fb1fc7adSLuis R. Rodriguez */ 3366c492db37SJohannes Berg if (lr->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE && 3367c492db37SJohannes Berg lr->wiphy_idx != WIPHY_IDX_INVALID) 33683f2355cbSLuis R. Rodriguez goto out; 33693f2355cbSLuis R. Rodriguez 3370fe33eb39SLuis R. Rodriguez request->wiphy_idx = get_wiphy_idx(wiphy); 33714f366c5dSJohn W. Linville request->alpha2[0] = alpha2[0]; 33724f366c5dSJohn W. Linville request->alpha2[1] = alpha2[1]; 33737db90f4aSLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_COUNTRY_IE; 3374fe33eb39SLuis R. Rodriguez request->country_ie_env = env; 33753f2355cbSLuis R. Rodriguez 3376c37722bdSIlan peer /* Allow calling CRDA again */ 3377b6863036SJohannes Berg reset_crda_timeouts(); 3378c37722bdSIlan peer 3379fe33eb39SLuis R. Rodriguez queue_regulatory_request(request); 3380db2424c5SJohannes Berg request = NULL; 33813f2355cbSLuis R. Rodriguez out: 3382db2424c5SJohannes Berg kfree(request); 3383db2424c5SJohannes Berg rcu_read_unlock(); 33843f2355cbSLuis R. Rodriguez } 3385b2e1b302SLuis R. Rodriguez 338609d989d1SLuis R. Rodriguez static void restore_alpha2(char *alpha2, bool reset_user) 338709d989d1SLuis R. Rodriguez { 338809d989d1SLuis R. Rodriguez /* indicates there is no alpha2 to consider for restoration */ 338909d989d1SLuis R. Rodriguez alpha2[0] = '9'; 339009d989d1SLuis R. Rodriguez alpha2[1] = '7'; 339109d989d1SLuis R. Rodriguez 339209d989d1SLuis R. Rodriguez /* The user setting has precedence over the module parameter */ 339309d989d1SLuis R. Rodriguez if (is_user_regdom_saved()) { 339409d989d1SLuis R. Rodriguez /* Unless we're asked to ignore it and reset it */ 339509d989d1SLuis R. Rodriguez if (reset_user) { 3396c799ba6eSJohannes Berg pr_debug("Restoring regulatory settings including user preference\n"); 339709d989d1SLuis R. Rodriguez user_alpha2[0] = '9'; 339809d989d1SLuis R. Rodriguez user_alpha2[1] = '7'; 339909d989d1SLuis R. Rodriguez 340009d989d1SLuis R. Rodriguez /* 340109d989d1SLuis R. Rodriguez * If we're ignoring user settings, we still need to 340209d989d1SLuis R. Rodriguez * check the module parameter to ensure we put things 340309d989d1SLuis R. Rodriguez * back as they were for a full restore. 340409d989d1SLuis R. Rodriguez */ 340509d989d1SLuis R. Rodriguez if (!is_world_regdom(ieee80211_regdom)) { 3406c799ba6eSJohannes Berg pr_debug("Keeping preference on module parameter ieee80211_regdom: %c%c\n", 34071a919318SJohannes Berg ieee80211_regdom[0], ieee80211_regdom[1]); 340809d989d1SLuis R. Rodriguez alpha2[0] = ieee80211_regdom[0]; 340909d989d1SLuis R. Rodriguez alpha2[1] = ieee80211_regdom[1]; 341009d989d1SLuis R. Rodriguez } 341109d989d1SLuis R. Rodriguez } else { 3412c799ba6eSJohannes Berg pr_debug("Restoring regulatory settings while preserving user preference for: %c%c\n", 34131a919318SJohannes Berg user_alpha2[0], user_alpha2[1]); 341409d989d1SLuis R. Rodriguez alpha2[0] = user_alpha2[0]; 341509d989d1SLuis R. Rodriguez alpha2[1] = user_alpha2[1]; 341609d989d1SLuis R. Rodriguez } 341709d989d1SLuis R. Rodriguez } else if (!is_world_regdom(ieee80211_regdom)) { 3418c799ba6eSJohannes Berg pr_debug("Keeping preference on module parameter ieee80211_regdom: %c%c\n", 34191a919318SJohannes Berg ieee80211_regdom[0], ieee80211_regdom[1]); 342009d989d1SLuis R. Rodriguez alpha2[0] = ieee80211_regdom[0]; 342109d989d1SLuis R. Rodriguez alpha2[1] = ieee80211_regdom[1]; 342209d989d1SLuis R. Rodriguez } else 3423c799ba6eSJohannes Berg pr_debug("Restoring regulatory settings\n"); 342409d989d1SLuis R. Rodriguez } 342509d989d1SLuis R. Rodriguez 34265ce543d1SRajkumar Manoharan static void restore_custom_reg_settings(struct wiphy *wiphy) 34275ce543d1SRajkumar Manoharan { 34285ce543d1SRajkumar Manoharan struct ieee80211_supported_band *sband; 342957fbcce3SJohannes Berg enum nl80211_band band; 34305ce543d1SRajkumar Manoharan struct ieee80211_channel *chan; 34315ce543d1SRajkumar Manoharan int i; 34325ce543d1SRajkumar Manoharan 343357fbcce3SJohannes Berg for (band = 0; band < NUM_NL80211_BANDS; band++) { 34345ce543d1SRajkumar Manoharan sband = wiphy->bands[band]; 34355ce543d1SRajkumar Manoharan if (!sband) 34365ce543d1SRajkumar Manoharan continue; 34375ce543d1SRajkumar Manoharan for (i = 0; i < sband->n_channels; i++) { 34385ce543d1SRajkumar Manoharan chan = &sband->channels[i]; 34395ce543d1SRajkumar Manoharan chan->flags = chan->orig_flags; 34405ce543d1SRajkumar Manoharan chan->max_antenna_gain = chan->orig_mag; 34415ce543d1SRajkumar Manoharan chan->max_power = chan->orig_mpwr; 3442899852afSPaul Stewart chan->beacon_found = false; 34435ce543d1SRajkumar Manoharan } 34445ce543d1SRajkumar Manoharan } 34455ce543d1SRajkumar Manoharan } 34465ce543d1SRajkumar Manoharan 344709d989d1SLuis R. Rodriguez /* 3448f2e30931SBhaskar Chowdhury * Restoring regulatory settings involves ignoring any 344909d989d1SLuis R. Rodriguez * possibly stale country IE information and user regulatory 345009d989d1SLuis R. Rodriguez * settings if so desired, this includes any beacon hints 345109d989d1SLuis R. Rodriguez * learned as we could have traveled outside to another country 345209d989d1SLuis R. Rodriguez * after disconnection. To restore regulatory settings we do 345309d989d1SLuis R. Rodriguez * exactly what we did at bootup: 345409d989d1SLuis R. Rodriguez * 345509d989d1SLuis R. Rodriguez * - send a core regulatory hint 345609d989d1SLuis R. Rodriguez * - send a user regulatory hint if applicable 345709d989d1SLuis R. Rodriguez * 345809d989d1SLuis R. Rodriguez * Device drivers that send a regulatory hint for a specific country 3459cc5a639bSRandy Dunlap * keep their own regulatory domain on wiphy->regd so that does 346009d989d1SLuis R. Rodriguez * not need to be remembered. 346109d989d1SLuis R. Rodriguez */ 3462e646a025SJohannes Berg static void restore_regulatory_settings(bool reset_user, bool cached) 346309d989d1SLuis R. Rodriguez { 346409d989d1SLuis R. Rodriguez char alpha2[2]; 3465cee0bec5SDmitry Shmidt char world_alpha2[2]; 346609d989d1SLuis R. Rodriguez struct reg_beacon *reg_beacon, *btmp; 346714609555SLuis R. Rodriguez LIST_HEAD(tmp_reg_req_list); 34685ce543d1SRajkumar Manoharan struct cfg80211_registered_device *rdev; 346909d989d1SLuis R. Rodriguez 34705fe231e8SJohannes Berg ASSERT_RTNL(); 34715fe231e8SJohannes Berg 347205050753SIlan peer /* 347305050753SIlan peer * Clear the indoor setting in case that it is not controlled by user 347405050753SIlan peer * space, as otherwise there is no guarantee that the device is still 347505050753SIlan peer * operating in an indoor environment. 347605050753SIlan peer */ 347705050753SIlan peer spin_lock(®_indoor_lock); 347805050753SIlan peer if (reg_is_indoor && !reg_is_indoor_portid) { 347952616f2bSIlan Peer reg_is_indoor = false; 348005050753SIlan peer reg_check_channels(); 348105050753SIlan peer } 348205050753SIlan peer spin_unlock(®_indoor_lock); 348352616f2bSIlan Peer 34842d319867SJohannes Berg reset_regdomains(true, &world_regdom); 348509d989d1SLuis R. Rodriguez restore_alpha2(alpha2, reset_user); 348609d989d1SLuis R. Rodriguez 348714609555SLuis R. Rodriguez /* 348814609555SLuis R. Rodriguez * If there's any pending requests we simply 348914609555SLuis R. Rodriguez * stash them to a temporary pending queue and 349014609555SLuis R. Rodriguez * add then after we've restored regulatory 349114609555SLuis R. Rodriguez * settings. 349214609555SLuis R. Rodriguez */ 349314609555SLuis R. Rodriguez spin_lock(®_requests_lock); 3494eeca9fceSIlan peer list_splice_tail_init(®_requests_list, &tmp_reg_req_list); 349514609555SLuis R. Rodriguez spin_unlock(®_requests_lock); 349614609555SLuis R. Rodriguez 349709d989d1SLuis R. Rodriguez /* Clear beacon hints */ 349809d989d1SLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 3499fea9bcedSJohannes Berg list_for_each_entry_safe(reg_beacon, btmp, ®_pending_beacons, list) { 350009d989d1SLuis R. Rodriguez list_del(®_beacon->list); 350109d989d1SLuis R. Rodriguez kfree(reg_beacon); 350209d989d1SLuis R. Rodriguez } 350309d989d1SLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 350409d989d1SLuis R. Rodriguez 3505fea9bcedSJohannes Berg list_for_each_entry_safe(reg_beacon, btmp, ®_beacon_list, list) { 350609d989d1SLuis R. Rodriguez list_del(®_beacon->list); 350709d989d1SLuis R. Rodriguez kfree(reg_beacon); 350809d989d1SLuis R. Rodriguez } 350909d989d1SLuis R. Rodriguez 351009d989d1SLuis R. Rodriguez /* First restore to the basic regulatory settings */ 3511379b82f4SJohannes Berg world_alpha2[0] = cfg80211_world_regdom->alpha2[0]; 3512379b82f4SJohannes Berg world_alpha2[1] = cfg80211_world_regdom->alpha2[1]; 351309d989d1SLuis R. Rodriguez 35145ce543d1SRajkumar Manoharan list_for_each_entry(rdev, &cfg80211_rdev_list, list) { 3515b0d7aa59SJonathan Doron if (rdev->wiphy.regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED) 3516b0d7aa59SJonathan Doron continue; 3517a2f73b6cSLuis R. Rodriguez if (rdev->wiphy.regulatory_flags & REGULATORY_CUSTOM_REG) 35185ce543d1SRajkumar Manoharan restore_custom_reg_settings(&rdev->wiphy); 35195ce543d1SRajkumar Manoharan } 35205ce543d1SRajkumar Manoharan 3521e646a025SJohannes Berg if (cached && (!is_an_alpha2(alpha2) || 3522e646a025SJohannes Berg !IS_ERR_OR_NULL(cfg80211_user_regdom))) { 3523e646a025SJohannes Berg reset_regdomains(false, cfg80211_world_regdom); 3524e646a025SJohannes Berg update_all_wiphy_regulatory(NL80211_REGDOM_SET_BY_CORE); 3525e646a025SJohannes Berg print_regdomain(get_cfg80211_regdom()); 3526e646a025SJohannes Berg nl80211_send_reg_change_event(&core_request_world); 3527e646a025SJohannes Berg reg_set_request_processed(); 3528e646a025SJohannes Berg 3529e646a025SJohannes Berg if (is_an_alpha2(alpha2) && 3530e646a025SJohannes Berg !regulatory_hint_user(alpha2, NL80211_USER_REG_HINT_USER)) { 3531e646a025SJohannes Berg struct regulatory_request *ureq; 3532e646a025SJohannes Berg 3533e646a025SJohannes Berg spin_lock(®_requests_lock); 3534e646a025SJohannes Berg ureq = list_last_entry(®_requests_list, 3535e646a025SJohannes Berg struct regulatory_request, 3536e646a025SJohannes Berg list); 3537e646a025SJohannes Berg list_del(&ureq->list); 3538e646a025SJohannes Berg spin_unlock(®_requests_lock); 3539e646a025SJohannes Berg 3540e646a025SJohannes Berg notify_self_managed_wiphys(ureq); 3541e646a025SJohannes Berg reg_update_last_request(ureq); 3542e646a025SJohannes Berg set_regdom(reg_copy_regd(cfg80211_user_regdom), 3543e646a025SJohannes Berg REGD_SOURCE_CACHED); 3544e646a025SJohannes Berg } 3545e646a025SJohannes Berg } else { 3546cee0bec5SDmitry Shmidt regulatory_hint_core(world_alpha2); 354709d989d1SLuis R. Rodriguez 354809d989d1SLuis R. Rodriguez /* 354909d989d1SLuis R. Rodriguez * This restores the ieee80211_regdom module parameter 355009d989d1SLuis R. Rodriguez * preference or the last user requested regulatory 355109d989d1SLuis R. Rodriguez * settings, user regulatory settings takes precedence. 355209d989d1SLuis R. Rodriguez */ 355309d989d1SLuis R. Rodriguez if (is_an_alpha2(alpha2)) 3554549cc1c5SMaciej S. Szmigiero regulatory_hint_user(alpha2, NL80211_USER_REG_HINT_USER); 3555e646a025SJohannes Berg } 355609d989d1SLuis R. Rodriguez 355714609555SLuis R. Rodriguez spin_lock(®_requests_lock); 355811cff96cSJohannes Berg list_splice_tail_init(&tmp_reg_req_list, ®_requests_list); 355914609555SLuis R. Rodriguez spin_unlock(®_requests_lock); 356014609555SLuis R. Rodriguez 3561c799ba6eSJohannes Berg pr_debug("Kicking the queue\n"); 356214609555SLuis R. Rodriguez 356314609555SLuis R. Rodriguez schedule_work(®_work); 356414609555SLuis R. Rodriguez } 356509d989d1SLuis R. Rodriguez 35667417844bSRajeev Kumar Sirasanagandla static bool is_wiphy_all_set_reg_flag(enum ieee80211_regulatory_flags flag) 35677417844bSRajeev Kumar Sirasanagandla { 35687417844bSRajeev Kumar Sirasanagandla struct cfg80211_registered_device *rdev; 35697417844bSRajeev Kumar Sirasanagandla struct wireless_dev *wdev; 35707417844bSRajeev Kumar Sirasanagandla 35717417844bSRajeev Kumar Sirasanagandla list_for_each_entry(rdev, &cfg80211_rdev_list, list) { 35727417844bSRajeev Kumar Sirasanagandla list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) { 35737417844bSRajeev Kumar Sirasanagandla wdev_lock(wdev); 35747417844bSRajeev Kumar Sirasanagandla if (!(wdev->wiphy->regulatory_flags & flag)) { 35757417844bSRajeev Kumar Sirasanagandla wdev_unlock(wdev); 35767417844bSRajeev Kumar Sirasanagandla return false; 35777417844bSRajeev Kumar Sirasanagandla } 35787417844bSRajeev Kumar Sirasanagandla wdev_unlock(wdev); 35797417844bSRajeev Kumar Sirasanagandla } 35807417844bSRajeev Kumar Sirasanagandla } 35817417844bSRajeev Kumar Sirasanagandla 35827417844bSRajeev Kumar Sirasanagandla return true; 35837417844bSRajeev Kumar Sirasanagandla } 35847417844bSRajeev Kumar Sirasanagandla 358509d989d1SLuis R. Rodriguez void regulatory_hint_disconnect(void) 358609d989d1SLuis R. Rodriguez { 35877417844bSRajeev Kumar Sirasanagandla /* Restore of regulatory settings is not required when wiphy(s) 35887417844bSRajeev Kumar Sirasanagandla * ignore IE from connected access point but clearance of beacon hints 35897417844bSRajeev Kumar Sirasanagandla * is required when wiphy(s) supports beacon hints. 35907417844bSRajeev Kumar Sirasanagandla */ 35917417844bSRajeev Kumar Sirasanagandla if (is_wiphy_all_set_reg_flag(REGULATORY_COUNTRY_IE_IGNORE)) { 35927417844bSRajeev Kumar Sirasanagandla struct reg_beacon *reg_beacon, *btmp; 35937417844bSRajeev Kumar Sirasanagandla 35947417844bSRajeev Kumar Sirasanagandla if (is_wiphy_all_set_reg_flag(REGULATORY_DISABLE_BEACON_HINTS)) 35957417844bSRajeev Kumar Sirasanagandla return; 35967417844bSRajeev Kumar Sirasanagandla 35977417844bSRajeev Kumar Sirasanagandla spin_lock_bh(®_pending_beacons_lock); 35987417844bSRajeev Kumar Sirasanagandla list_for_each_entry_safe(reg_beacon, btmp, 35997417844bSRajeev Kumar Sirasanagandla ®_pending_beacons, list) { 36007417844bSRajeev Kumar Sirasanagandla list_del(®_beacon->list); 36017417844bSRajeev Kumar Sirasanagandla kfree(reg_beacon); 36027417844bSRajeev Kumar Sirasanagandla } 36037417844bSRajeev Kumar Sirasanagandla spin_unlock_bh(®_pending_beacons_lock); 36047417844bSRajeev Kumar Sirasanagandla 36057417844bSRajeev Kumar Sirasanagandla list_for_each_entry_safe(reg_beacon, btmp, 36067417844bSRajeev Kumar Sirasanagandla ®_beacon_list, list) { 36077417844bSRajeev Kumar Sirasanagandla list_del(®_beacon->list); 36087417844bSRajeev Kumar Sirasanagandla kfree(reg_beacon); 36097417844bSRajeev Kumar Sirasanagandla } 36107417844bSRajeev Kumar Sirasanagandla 36117417844bSRajeev Kumar Sirasanagandla return; 36127417844bSRajeev Kumar Sirasanagandla } 36137417844bSRajeev Kumar Sirasanagandla 3614c799ba6eSJohannes Berg pr_debug("All devices are disconnected, going to restore regulatory settings\n"); 3615e646a025SJohannes Berg restore_regulatory_settings(false, true); 361609d989d1SLuis R. Rodriguez } 361709d989d1SLuis R. Rodriguez 36189cf0a0b4SAlexei Avshalom Lazar static bool freq_is_chan_12_13_14(u32 freq) 3619e38f8a7aSLuis R. Rodriguez { 362057fbcce3SJohannes Berg if (freq == ieee80211_channel_to_frequency(12, NL80211_BAND_2GHZ) || 362157fbcce3SJohannes Berg freq == ieee80211_channel_to_frequency(13, NL80211_BAND_2GHZ) || 362257fbcce3SJohannes Berg freq == ieee80211_channel_to_frequency(14, NL80211_BAND_2GHZ)) 3623e38f8a7aSLuis R. Rodriguez return true; 3624e38f8a7aSLuis R. Rodriguez return false; 3625e38f8a7aSLuis R. Rodriguez } 3626e38f8a7aSLuis R. Rodriguez 36273ebfa6e7SLuis R. Rodriguez static bool pending_reg_beacon(struct ieee80211_channel *beacon_chan) 36283ebfa6e7SLuis R. Rodriguez { 36293ebfa6e7SLuis R. Rodriguez struct reg_beacon *pending_beacon; 36303ebfa6e7SLuis R. Rodriguez 36313ebfa6e7SLuis R. Rodriguez list_for_each_entry(pending_beacon, ®_pending_beacons, list) 3632934f4c7dSThomas Pedersen if (ieee80211_channel_equal(beacon_chan, 3633934f4c7dSThomas Pedersen &pending_beacon->chan)) 36343ebfa6e7SLuis R. Rodriguez return true; 36353ebfa6e7SLuis R. Rodriguez return false; 36363ebfa6e7SLuis R. Rodriguez } 36373ebfa6e7SLuis R. Rodriguez 3638e38f8a7aSLuis R. Rodriguez int regulatory_hint_found_beacon(struct wiphy *wiphy, 3639e38f8a7aSLuis R. Rodriguez struct ieee80211_channel *beacon_chan, 3640e38f8a7aSLuis R. Rodriguez gfp_t gfp) 3641e38f8a7aSLuis R. Rodriguez { 3642e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon; 36433ebfa6e7SLuis R. Rodriguez bool processing; 3644e38f8a7aSLuis R. Rodriguez 36451a919318SJohannes Berg if (beacon_chan->beacon_found || 36461a919318SJohannes Berg beacon_chan->flags & IEEE80211_CHAN_RADAR || 364757fbcce3SJohannes Berg (beacon_chan->band == NL80211_BAND_2GHZ && 36481a919318SJohannes Berg !freq_is_chan_12_13_14(beacon_chan->center_freq))) 3649e38f8a7aSLuis R. Rodriguez return 0; 3650e38f8a7aSLuis R. Rodriguez 36513ebfa6e7SLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 36523ebfa6e7SLuis R. Rodriguez processing = pending_reg_beacon(beacon_chan); 36533ebfa6e7SLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 36543ebfa6e7SLuis R. Rodriguez 36553ebfa6e7SLuis R. Rodriguez if (processing) 3656e38f8a7aSLuis R. Rodriguez return 0; 3657e38f8a7aSLuis R. Rodriguez 3658e38f8a7aSLuis R. Rodriguez reg_beacon = kzalloc(sizeof(struct reg_beacon), gfp); 3659e38f8a7aSLuis R. Rodriguez if (!reg_beacon) 3660e38f8a7aSLuis R. Rodriguez return -ENOMEM; 3661e38f8a7aSLuis R. Rodriguez 3662934f4c7dSThomas Pedersen pr_debug("Found new beacon on frequency: %d.%03d MHz (Ch %d) on %s\n", 3663934f4c7dSThomas Pedersen beacon_chan->center_freq, beacon_chan->freq_offset, 3664934f4c7dSThomas Pedersen ieee80211_freq_khz_to_channel( 3665934f4c7dSThomas Pedersen ieee80211_channel_to_khz(beacon_chan)), 3666e38f8a7aSLuis R. Rodriguez wiphy_name(wiphy)); 36674113f751SLuis R. Rodriguez 3668e38f8a7aSLuis R. Rodriguez memcpy(®_beacon->chan, beacon_chan, 3669e38f8a7aSLuis R. Rodriguez sizeof(struct ieee80211_channel)); 3670e38f8a7aSLuis R. Rodriguez 3671e38f8a7aSLuis R. Rodriguez /* 3672e38f8a7aSLuis R. Rodriguez * Since we can be called from BH or and non-BH context 3673e38f8a7aSLuis R. Rodriguez * we must use spin_lock_bh() 3674e38f8a7aSLuis R. Rodriguez */ 3675e38f8a7aSLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 3676e38f8a7aSLuis R. Rodriguez list_add_tail(®_beacon->list, ®_pending_beacons); 3677e38f8a7aSLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 3678e38f8a7aSLuis R. Rodriguez 3679e38f8a7aSLuis R. Rodriguez schedule_work(®_work); 3680e38f8a7aSLuis R. Rodriguez 3681e38f8a7aSLuis R. Rodriguez return 0; 3682e38f8a7aSLuis R. Rodriguez } 3683e38f8a7aSLuis R. Rodriguez 3684a3d2eaf0SJohannes Berg static void print_rd_rules(const struct ieee80211_regdomain *rd) 3685b2e1b302SLuis R. Rodriguez { 3686b2e1b302SLuis R. Rodriguez unsigned int i; 3687a3d2eaf0SJohannes Berg const struct ieee80211_reg_rule *reg_rule = NULL; 3688a3d2eaf0SJohannes Berg const struct ieee80211_freq_range *freq_range = NULL; 3689a3d2eaf0SJohannes Berg const struct ieee80211_power_rule *power_rule = NULL; 3690089027e5SJanusz Dziedzic char bw[32], cac_time[32]; 3691b2e1b302SLuis R. Rodriguez 369294c4fd64SDave Young pr_debug(" (start_freq - end_freq @ bandwidth), (max_antenna_gain, max_eirp), (dfs_cac_time)\n"); 3693b2e1b302SLuis R. Rodriguez 3694b2e1b302SLuis R. Rodriguez for (i = 0; i < rd->n_reg_rules; i++) { 3695b2e1b302SLuis R. Rodriguez reg_rule = &rd->reg_rules[i]; 3696b2e1b302SLuis R. Rodriguez freq_range = ®_rule->freq_range; 3697b2e1b302SLuis R. Rodriguez power_rule = ®_rule->power_rule; 3698b2e1b302SLuis R. Rodriguez 3699b0dfd2eaSJanusz Dziedzic if (reg_rule->flags & NL80211_RRF_AUTO_BW) 3700db18d20dSYe Bin snprintf(bw, sizeof(bw), "%d KHz, %u KHz AUTO", 3701b0dfd2eaSJanusz Dziedzic freq_range->max_bandwidth_khz, 370297524820SJanusz Dziedzic reg_get_max_bandwidth(rd, reg_rule)); 370397524820SJanusz Dziedzic else 3704b0dfd2eaSJanusz Dziedzic snprintf(bw, sizeof(bw), "%d KHz", 370597524820SJanusz Dziedzic freq_range->max_bandwidth_khz); 370697524820SJanusz Dziedzic 3707089027e5SJanusz Dziedzic if (reg_rule->flags & NL80211_RRF_DFS) 3708089027e5SJanusz Dziedzic scnprintf(cac_time, sizeof(cac_time), "%u s", 3709089027e5SJanusz Dziedzic reg_rule->dfs_cac_ms/1000); 3710089027e5SJanusz Dziedzic else 3711089027e5SJanusz Dziedzic scnprintf(cac_time, sizeof(cac_time), "N/A"); 3712089027e5SJanusz Dziedzic 3713089027e5SJanusz Dziedzic 3714fb1fc7adSLuis R. Rodriguez /* 3715fb1fc7adSLuis R. Rodriguez * There may not be documentation for max antenna gain 3716fb1fc7adSLuis R. Rodriguez * in certain regions 3717fb1fc7adSLuis R. Rodriguez */ 3718b2e1b302SLuis R. Rodriguez if (power_rule->max_antenna_gain) 371994c4fd64SDave Young pr_debug(" (%d KHz - %d KHz @ %s), (%d mBi, %d mBm), (%s)\n", 3720b2e1b302SLuis R. Rodriguez freq_range->start_freq_khz, 3721b2e1b302SLuis R. Rodriguez freq_range->end_freq_khz, 372297524820SJanusz Dziedzic bw, 3723b2e1b302SLuis R. Rodriguez power_rule->max_antenna_gain, 3724089027e5SJanusz Dziedzic power_rule->max_eirp, 3725089027e5SJanusz Dziedzic cac_time); 3726b2e1b302SLuis R. Rodriguez else 372794c4fd64SDave Young pr_debug(" (%d KHz - %d KHz @ %s), (N/A, %d mBm), (%s)\n", 3728b2e1b302SLuis R. Rodriguez freq_range->start_freq_khz, 3729b2e1b302SLuis R. Rodriguez freq_range->end_freq_khz, 373097524820SJanusz Dziedzic bw, 3731089027e5SJanusz Dziedzic power_rule->max_eirp, 3732089027e5SJanusz Dziedzic cac_time); 3733b2e1b302SLuis R. Rodriguez } 3734b2e1b302SLuis R. Rodriguez } 3735b2e1b302SLuis R. Rodriguez 37364c7d3982SLuis R. Rodriguez bool reg_supported_dfs_region(enum nl80211_dfs_regions dfs_region) 37378b60b078SLuis R. Rodriguez { 37388b60b078SLuis R. Rodriguez switch (dfs_region) { 37398b60b078SLuis R. Rodriguez case NL80211_DFS_UNSET: 37408b60b078SLuis R. Rodriguez case NL80211_DFS_FCC: 37418b60b078SLuis R. Rodriguez case NL80211_DFS_ETSI: 37428b60b078SLuis R. Rodriguez case NL80211_DFS_JP: 37438b60b078SLuis R. Rodriguez return true; 37448b60b078SLuis R. Rodriguez default: 37454a22b00bSColin Ian King pr_debug("Ignoring unknown DFS master region: %d\n", dfs_region); 37468b60b078SLuis R. Rodriguez return false; 37478b60b078SLuis R. Rodriguez } 37488b60b078SLuis R. Rodriguez } 37498b60b078SLuis R. Rodriguez 3750a3d2eaf0SJohannes Berg static void print_regdomain(const struct ieee80211_regdomain *rd) 3751b2e1b302SLuis R. Rodriguez { 3752c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 3753b2e1b302SLuis R. Rodriguez 37543f2355cbSLuis R. Rodriguez if (is_intersected_alpha2(rd->alpha2)) { 3755c492db37SJohannes Berg if (lr->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE) { 375679c97e97SJohannes Berg struct cfg80211_registered_device *rdev; 3757c492db37SJohannes Berg rdev = cfg80211_rdev_by_wiphy_idx(lr->wiphy_idx); 375879c97e97SJohannes Berg if (rdev) { 375994c4fd64SDave Young pr_debug("Current regulatory domain updated by AP to: %c%c\n", 376079c97e97SJohannes Berg rdev->country_ie_alpha2[0], 376179c97e97SJohannes Berg rdev->country_ie_alpha2[1]); 37623f2355cbSLuis R. Rodriguez } else 376394c4fd64SDave Young pr_debug("Current regulatory domain intersected:\n"); 37643f2355cbSLuis R. Rodriguez } else 376594c4fd64SDave Young pr_debug("Current regulatory domain intersected:\n"); 37661a919318SJohannes Berg } else if (is_world_regdom(rd->alpha2)) { 376794c4fd64SDave Young pr_debug("World regulatory domain updated:\n"); 37681a919318SJohannes Berg } else { 3769b2e1b302SLuis R. Rodriguez if (is_unknown_alpha2(rd->alpha2)) 377094c4fd64SDave Young pr_debug("Regulatory domain changed to driver built-in settings (unknown country)\n"); 377157b5ce07SLuis R. Rodriguez else { 3772c492db37SJohannes Berg if (reg_request_cell_base(lr)) 377394c4fd64SDave Young pr_debug("Regulatory domain changed to country: %c%c by Cell Station\n", 3774b2e1b302SLuis R. Rodriguez rd->alpha2[0], rd->alpha2[1]); 377557b5ce07SLuis R. Rodriguez else 377694c4fd64SDave Young pr_debug("Regulatory domain changed to country: %c%c\n", 377757b5ce07SLuis R. Rodriguez rd->alpha2[0], rd->alpha2[1]); 377857b5ce07SLuis R. Rodriguez } 3779b2e1b302SLuis R. Rodriguez } 37801a919318SJohannes Berg 378194c4fd64SDave Young pr_debug(" DFS Master region: %s", reg_dfs_region_str(rd->dfs_region)); 3782b2e1b302SLuis R. Rodriguez print_rd_rules(rd); 3783b2e1b302SLuis R. Rodriguez } 3784b2e1b302SLuis R. Rodriguez 37852df78167SJohannes Berg static void print_regdomain_info(const struct ieee80211_regdomain *rd) 3786b2e1b302SLuis R. Rodriguez { 378794c4fd64SDave Young pr_debug("Regulatory domain: %c%c\n", rd->alpha2[0], rd->alpha2[1]); 3788b2e1b302SLuis R. Rodriguez print_rd_rules(rd); 3789b2e1b302SLuis R. Rodriguez } 3790b2e1b302SLuis R. Rodriguez 37913b9e5acaSLuis R. Rodriguez static int reg_set_rd_core(const struct ieee80211_regdomain *rd) 37923b9e5acaSLuis R. Rodriguez { 37933b9e5acaSLuis R. Rodriguez if (!is_world_regdom(rd->alpha2)) 37943b9e5acaSLuis R. Rodriguez return -EINVAL; 37953b9e5acaSLuis R. Rodriguez update_world_regdomain(rd); 37963b9e5acaSLuis R. Rodriguez return 0; 37973b9e5acaSLuis R. Rodriguez } 37983b9e5acaSLuis R. Rodriguez 379984721d44SLuis R. Rodriguez static int reg_set_rd_user(const struct ieee80211_regdomain *rd, 380084721d44SLuis R. Rodriguez struct regulatory_request *user_request) 380184721d44SLuis R. Rodriguez { 380284721d44SLuis R. Rodriguez const struct ieee80211_regdomain *intersected_rd = NULL; 380384721d44SLuis R. Rodriguez 380484721d44SLuis R. Rodriguez if (!regdom_changes(rd->alpha2)) 380584721d44SLuis R. Rodriguez return -EALREADY; 380684721d44SLuis R. Rodriguez 380784721d44SLuis R. Rodriguez if (!is_valid_rd(rd)) { 380894c4fd64SDave Young pr_err("Invalid regulatory domain detected: %c%c\n", 380994c4fd64SDave Young rd->alpha2[0], rd->alpha2[1]); 381084721d44SLuis R. Rodriguez print_regdomain_info(rd); 381184721d44SLuis R. Rodriguez return -EINVAL; 381284721d44SLuis R. Rodriguez } 381384721d44SLuis R. Rodriguez 381484721d44SLuis R. Rodriguez if (!user_request->intersect) { 381584721d44SLuis R. Rodriguez reset_regdomains(false, rd); 381684721d44SLuis R. Rodriguez return 0; 381784721d44SLuis R. Rodriguez } 381884721d44SLuis R. Rodriguez 381984721d44SLuis R. Rodriguez intersected_rd = regdom_intersect(rd, get_cfg80211_regdom()); 382084721d44SLuis R. Rodriguez if (!intersected_rd) 382184721d44SLuis R. Rodriguez return -EINVAL; 382284721d44SLuis R. Rodriguez 382384721d44SLuis R. Rodriguez kfree(rd); 382484721d44SLuis R. Rodriguez rd = NULL; 382584721d44SLuis R. Rodriguez reset_regdomains(false, intersected_rd); 382684721d44SLuis R. Rodriguez 382784721d44SLuis R. Rodriguez return 0; 382884721d44SLuis R. Rodriguez } 382984721d44SLuis R. Rodriguez 3830f5fe3247SLuis R. Rodriguez static int reg_set_rd_driver(const struct ieee80211_regdomain *rd, 3831f5fe3247SLuis R. Rodriguez struct regulatory_request *driver_request) 3832b2e1b302SLuis R. Rodriguez { 3833e9763c3cSJohannes Berg const struct ieee80211_regdomain *regd; 38349c96477dSLuis R. Rodriguez const struct ieee80211_regdomain *intersected_rd = NULL; 3835f5fe3247SLuis R. Rodriguez const struct ieee80211_regdomain *tmp; 3836806a9e39SLuis R. Rodriguez struct wiphy *request_wiphy; 38376913b49aSJohannes Berg 3838f5fe3247SLuis R. Rodriguez if (is_world_regdom(rd->alpha2)) 3839b2e1b302SLuis R. Rodriguez return -EINVAL; 3840b2e1b302SLuis R. Rodriguez 3841baeb66feSJohn W. Linville if (!regdom_changes(rd->alpha2)) 384295908535SKalle Valo return -EALREADY; 3843b2e1b302SLuis R. Rodriguez 3844b2e1b302SLuis R. Rodriguez if (!is_valid_rd(rd)) { 384594c4fd64SDave Young pr_err("Invalid regulatory domain detected: %c%c\n", 384694c4fd64SDave Young rd->alpha2[0], rd->alpha2[1]); 3847b2e1b302SLuis R. Rodriguez print_regdomain_info(rd); 3848b2e1b302SLuis R. Rodriguez return -EINVAL; 3849b2e1b302SLuis R. Rodriguez } 3850b2e1b302SLuis R. Rodriguez 3851f5fe3247SLuis R. Rodriguez request_wiphy = wiphy_idx_to_wiphy(driver_request->wiphy_idx); 3852922ec58cSJohannes Berg if (!request_wiphy) 3853de3584bdSJohannes Berg return -ENODEV; 3854806a9e39SLuis R. Rodriguez 3855f5fe3247SLuis R. Rodriguez if (!driver_request->intersect) { 3856a05829a7SJohannes Berg ASSERT_RTNL(); 3857a05829a7SJohannes Berg wiphy_lock(request_wiphy); 3858a05829a7SJohannes Berg if (request_wiphy->regd) { 3859a05829a7SJohannes Berg wiphy_unlock(request_wiphy); 3860558f6d32SLuis R. Rodriguez return -EALREADY; 3861a05829a7SJohannes Berg } 38623e0c3ff3SLuis R. Rodriguez 3863e9763c3cSJohannes Berg regd = reg_copy_regd(rd); 3864a05829a7SJohannes Berg if (IS_ERR(regd)) { 3865a05829a7SJohannes Berg wiphy_unlock(request_wiphy); 3866e9763c3cSJohannes Berg return PTR_ERR(regd); 3867a05829a7SJohannes Berg } 38683e0c3ff3SLuis R. Rodriguez 3869458f4f9eSJohannes Berg rcu_assign_pointer(request_wiphy->regd, regd); 3870a05829a7SJohannes Berg wiphy_unlock(request_wiphy); 3871379b82f4SJohannes Berg reset_regdomains(false, rd); 3872b8295acdSLuis R. Rodriguez return 0; 3873b8295acdSLuis R. Rodriguez } 3874b8295acdSLuis R. Rodriguez 3875458f4f9eSJohannes Berg intersected_rd = regdom_intersect(rd, get_cfg80211_regdom()); 38769c96477dSLuis R. Rodriguez if (!intersected_rd) 38779c96477dSLuis R. Rodriguez return -EINVAL; 3878b8295acdSLuis R. Rodriguez 3879fb1fc7adSLuis R. Rodriguez /* 3880fb1fc7adSLuis R. Rodriguez * We can trash what CRDA provided now. 38813e0c3ff3SLuis R. Rodriguez * However if a driver requested this specific regulatory 3882fb1fc7adSLuis R. Rodriguez * domain we keep it for its private use 3883fb1fc7adSLuis R. Rodriguez */ 3884b7566fc3SLarry Finger tmp = get_wiphy_regdom(request_wiphy); 3885458f4f9eSJohannes Berg rcu_assign_pointer(request_wiphy->regd, rd); 3886b7566fc3SLarry Finger rcu_free_regdom(tmp); 38873e0c3ff3SLuis R. Rodriguez 3888b8295acdSLuis R. Rodriguez rd = NULL; 3889b8295acdSLuis R. Rodriguez 3890379b82f4SJohannes Berg reset_regdomains(false, intersected_rd); 3891b8295acdSLuis R. Rodriguez 3892b8295acdSLuis R. Rodriguez return 0; 38939c96477dSLuis R. Rodriguez } 38949c96477dSLuis R. Rodriguez 389501992406SLuis R. Rodriguez static int reg_set_rd_country_ie(const struct ieee80211_regdomain *rd, 389601992406SLuis R. Rodriguez struct regulatory_request *country_ie_request) 3897f5fe3247SLuis R. Rodriguez { 3898f5fe3247SLuis R. Rodriguez struct wiphy *request_wiphy; 3899f5fe3247SLuis R. Rodriguez 3900f5fe3247SLuis R. Rodriguez if (!is_alpha2_set(rd->alpha2) && !is_an_alpha2(rd->alpha2) && 3901f5fe3247SLuis R. Rodriguez !is_unknown_alpha2(rd->alpha2)) 3902f5fe3247SLuis R. Rodriguez return -EINVAL; 3903f5fe3247SLuis R. Rodriguez 3904f5fe3247SLuis R. Rodriguez /* 3905f5fe3247SLuis R. Rodriguez * Lets only bother proceeding on the same alpha2 if the current 3906f5fe3247SLuis R. Rodriguez * rd is non static (it means CRDA was present and was used last) 3907f5fe3247SLuis R. Rodriguez * and the pending request came in from a country IE 3908f5fe3247SLuis R. Rodriguez */ 3909f5fe3247SLuis R. Rodriguez 3910f5fe3247SLuis R. Rodriguez if (!is_valid_rd(rd)) { 391194c4fd64SDave Young pr_err("Invalid regulatory domain detected: %c%c\n", 391294c4fd64SDave Young rd->alpha2[0], rd->alpha2[1]); 3913f5fe3247SLuis R. Rodriguez print_regdomain_info(rd); 39143f2355cbSLuis R. Rodriguez return -EINVAL; 3915b2e1b302SLuis R. Rodriguez } 3916b2e1b302SLuis R. Rodriguez 391701992406SLuis R. Rodriguez request_wiphy = wiphy_idx_to_wiphy(country_ie_request->wiphy_idx); 3918922ec58cSJohannes Berg if (!request_wiphy) 3919f5fe3247SLuis R. Rodriguez return -ENODEV; 3920f5fe3247SLuis R. Rodriguez 392101992406SLuis R. Rodriguez if (country_ie_request->intersect) 3922f5fe3247SLuis R. Rodriguez return -EINVAL; 3923f5fe3247SLuis R. Rodriguez 3924f5fe3247SLuis R. Rodriguez reset_regdomains(false, rd); 3925f5fe3247SLuis R. Rodriguez return 0; 3926f5fe3247SLuis R. Rodriguez } 3927b2e1b302SLuis R. Rodriguez 3928fb1fc7adSLuis R. Rodriguez /* 3929fb1fc7adSLuis R. Rodriguez * Use this call to set the current regulatory domain. Conflicts with 3930b2e1b302SLuis R. Rodriguez * multiple drivers can be ironed out later. Caller must've already 3931458f4f9eSJohannes Berg * kmalloc'd the rd structure. 3932fb1fc7adSLuis R. Rodriguez */ 3933c37722bdSIlan peer int set_regdom(const struct ieee80211_regdomain *rd, 3934c37722bdSIlan peer enum ieee80211_regd_source regd_src) 3935b2e1b302SLuis R. Rodriguez { 3936c492db37SJohannes Berg struct regulatory_request *lr; 3937092008abSJanusz Dziedzic bool user_reset = false; 3938b2e1b302SLuis R. Rodriguez int r; 3939b2e1b302SLuis R. Rodriguez 3940e646a025SJohannes Berg if (IS_ERR_OR_NULL(rd)) 3941e646a025SJohannes Berg return -ENODATA; 3942e646a025SJohannes Berg 39433b9e5acaSLuis R. Rodriguez if (!reg_is_valid_request(rd->alpha2)) { 39443b9e5acaSLuis R. Rodriguez kfree(rd); 39453b9e5acaSLuis R. Rodriguez return -EINVAL; 39463b9e5acaSLuis R. Rodriguez } 39473b9e5acaSLuis R. Rodriguez 3948c37722bdSIlan peer if (regd_src == REGD_SOURCE_CRDA) 3949b6863036SJohannes Berg reset_crda_timeouts(); 3950c37722bdSIlan peer 3951c492db37SJohannes Berg lr = get_last_request(); 3952abc7381bSLuis R. Rodriguez 3953b2e1b302SLuis R. Rodriguez /* Note that this doesn't update the wiphys, this is done below */ 39543b9e5acaSLuis R. Rodriguez switch (lr->initiator) { 39553b9e5acaSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_CORE: 39563b9e5acaSLuis R. Rodriguez r = reg_set_rd_core(rd); 39573b9e5acaSLuis R. Rodriguez break; 39583b9e5acaSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_USER: 3959e646a025SJohannes Berg cfg80211_save_user_regdom(rd); 396084721d44SLuis R. Rodriguez r = reg_set_rd_user(rd, lr); 3961092008abSJanusz Dziedzic user_reset = true; 396284721d44SLuis R. Rodriguez break; 39633b9e5acaSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_DRIVER: 3964f5fe3247SLuis R. Rodriguez r = reg_set_rd_driver(rd, lr); 3965f5fe3247SLuis R. Rodriguez break; 39663b9e5acaSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_COUNTRY_IE: 396701992406SLuis R. Rodriguez r = reg_set_rd_country_ie(rd, lr); 39683b9e5acaSLuis R. Rodriguez break; 39693b9e5acaSLuis R. Rodriguez default: 39703b9e5acaSLuis R. Rodriguez WARN(1, "invalid initiator %d\n", lr->initiator); 397109d11800SOla Olsson kfree(rd); 39723b9e5acaSLuis R. Rodriguez return -EINVAL; 39733b9e5acaSLuis R. Rodriguez } 39743b9e5acaSLuis R. Rodriguez 3975d2372b31SJohannes Berg if (r) { 3976092008abSJanusz Dziedzic switch (r) { 3977092008abSJanusz Dziedzic case -EALREADY: 397895908535SKalle Valo reg_set_request_processed(); 3979092008abSJanusz Dziedzic break; 3980092008abSJanusz Dziedzic default: 3981092008abSJanusz Dziedzic /* Back to world regulatory in case of errors */ 3982e646a025SJohannes Berg restore_regulatory_settings(user_reset, false); 3983092008abSJanusz Dziedzic } 398495908535SKalle Valo 3985d2372b31SJohannes Berg kfree(rd); 398638fd2143SJohannes Berg return r; 3987d2372b31SJohannes Berg } 3988b2e1b302SLuis R. Rodriguez 3989b2e1b302SLuis R. Rodriguez /* This would make this whole thing pointless */ 399038fd2143SJohannes Berg if (WARN_ON(!lr->intersect && rd != get_cfg80211_regdom())) 399138fd2143SJohannes Berg return -EINVAL; 3992b2e1b302SLuis R. Rodriguez 3993b2e1b302SLuis R. Rodriguez /* update all wiphys now with the new established regulatory domain */ 3994c492db37SJohannes Berg update_all_wiphy_regulatory(lr->initiator); 3995b2e1b302SLuis R. Rodriguez 3996458f4f9eSJohannes Berg print_regdomain(get_cfg80211_regdom()); 3997b2e1b302SLuis R. Rodriguez 3998c492db37SJohannes Berg nl80211_send_reg_change_event(lr); 399973d54c9eSLuis R. Rodriguez 4000b2e253cfSLuis R. Rodriguez reg_set_request_processed(); 4001b2e253cfSLuis R. Rodriguez 400238fd2143SJohannes Berg return 0; 4003b2e1b302SLuis R. Rodriguez } 4004b2e1b302SLuis R. Rodriguez 40052c3e861cSArik Nemtsov static int __regulatory_set_wiphy_regd(struct wiphy *wiphy, 4006b0d7aa59SJonathan Doron struct ieee80211_regdomain *rd) 4007b0d7aa59SJonathan Doron { 4008b0d7aa59SJonathan Doron const struct ieee80211_regdomain *regd; 4009b0d7aa59SJonathan Doron const struct ieee80211_regdomain *prev_regd; 4010b0d7aa59SJonathan Doron struct cfg80211_registered_device *rdev; 4011b0d7aa59SJonathan Doron 4012b0d7aa59SJonathan Doron if (WARN_ON(!wiphy || !rd)) 4013b0d7aa59SJonathan Doron return -EINVAL; 4014b0d7aa59SJonathan Doron 4015b0d7aa59SJonathan Doron if (WARN(!(wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED), 4016b0d7aa59SJonathan Doron "wiphy should have REGULATORY_WIPHY_SELF_MANAGED\n")) 4017b0d7aa59SJonathan Doron return -EPERM; 4018b0d7aa59SJonathan Doron 4019b767ecdaSJohannes Berg if (WARN(!is_valid_rd(rd), 4020b767ecdaSJohannes Berg "Invalid regulatory domain detected: %c%c\n", 4021b767ecdaSJohannes Berg rd->alpha2[0], rd->alpha2[1])) { 4022b0d7aa59SJonathan Doron print_regdomain_info(rd); 4023b0d7aa59SJonathan Doron return -EINVAL; 4024b0d7aa59SJonathan Doron } 4025b0d7aa59SJonathan Doron 4026b0d7aa59SJonathan Doron regd = reg_copy_regd(rd); 4027b0d7aa59SJonathan Doron if (IS_ERR(regd)) 4028b0d7aa59SJonathan Doron return PTR_ERR(regd); 4029b0d7aa59SJonathan Doron 4030b0d7aa59SJonathan Doron rdev = wiphy_to_rdev(wiphy); 4031b0d7aa59SJonathan Doron 4032b0d7aa59SJonathan Doron spin_lock(®_requests_lock); 4033b0d7aa59SJonathan Doron prev_regd = rdev->requested_regd; 4034b0d7aa59SJonathan Doron rdev->requested_regd = regd; 4035b0d7aa59SJonathan Doron spin_unlock(®_requests_lock); 4036b0d7aa59SJonathan Doron 4037b0d7aa59SJonathan Doron kfree(prev_regd); 40382c3e861cSArik Nemtsov return 0; 40392c3e861cSArik Nemtsov } 40402c3e861cSArik Nemtsov 40412c3e861cSArik Nemtsov int regulatory_set_wiphy_regd(struct wiphy *wiphy, 40422c3e861cSArik Nemtsov struct ieee80211_regdomain *rd) 40432c3e861cSArik Nemtsov { 40442c3e861cSArik Nemtsov int ret = __regulatory_set_wiphy_regd(wiphy, rd); 40452c3e861cSArik Nemtsov 40462c3e861cSArik Nemtsov if (ret) 40472c3e861cSArik Nemtsov return ret; 4048b0d7aa59SJonathan Doron 4049b0d7aa59SJonathan Doron schedule_work(®_work); 4050b0d7aa59SJonathan Doron return 0; 4051b0d7aa59SJonathan Doron } 4052b0d7aa59SJonathan Doron EXPORT_SYMBOL(regulatory_set_wiphy_regd); 4053b0d7aa59SJonathan Doron 4054a05829a7SJohannes Berg int regulatory_set_wiphy_regd_sync(struct wiphy *wiphy, 40552c3e861cSArik Nemtsov struct ieee80211_regdomain *rd) 40562c3e861cSArik Nemtsov { 40572c3e861cSArik Nemtsov int ret; 40582c3e861cSArik Nemtsov 40592c3e861cSArik Nemtsov ASSERT_RTNL(); 40602c3e861cSArik Nemtsov 40612c3e861cSArik Nemtsov ret = __regulatory_set_wiphy_regd(wiphy, rd); 40622c3e861cSArik Nemtsov if (ret) 40632c3e861cSArik Nemtsov return ret; 40642c3e861cSArik Nemtsov 40652c3e861cSArik Nemtsov /* process the request immediately */ 4066a05829a7SJohannes Berg reg_process_self_managed_hint(wiphy); 4067a05829a7SJohannes Berg reg_check_channels(); 40682c3e861cSArik Nemtsov return 0; 40692c3e861cSArik Nemtsov } 4070a05829a7SJohannes Berg EXPORT_SYMBOL(regulatory_set_wiphy_regd_sync); 40712c3e861cSArik Nemtsov 407257b5ce07SLuis R. Rodriguez void wiphy_regulatory_register(struct wiphy *wiphy) 407357b5ce07SLuis R. Rodriguez { 4074aced43ceSAmar Singhal struct regulatory_request *lr = get_last_request(); 407523df0b73SArik Nemtsov 4076aced43ceSAmar Singhal /* self-managed devices ignore beacon hints and country IE */ 4077aced43ceSAmar Singhal if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED) { 4078b0d7aa59SJonathan Doron wiphy->regulatory_flags |= REGULATORY_DISABLE_BEACON_HINTS | 4079b0d7aa59SJonathan Doron REGULATORY_COUNTRY_IE_IGNORE; 4080b0d7aa59SJonathan Doron 4081aced43ceSAmar Singhal /* 4082aced43ceSAmar Singhal * The last request may have been received before this 4083aced43ceSAmar Singhal * registration call. Call the driver notifier if 40848772eed9SSriram R * initiator is USER. 4085aced43ceSAmar Singhal */ 40868772eed9SSriram R if (lr->initiator == NL80211_REGDOM_SET_BY_USER) 4087aced43ceSAmar Singhal reg_call_notifier(wiphy, lr); 4088aced43ceSAmar Singhal } 4089aced43ceSAmar Singhal 409057b5ce07SLuis R. Rodriguez if (!reg_dev_ignore_cell_hint(wiphy)) 409157b5ce07SLuis R. Rodriguez reg_num_devs_support_basehint++; 409257b5ce07SLuis R. Rodriguez 409323df0b73SArik Nemtsov wiphy_update_regulatory(wiphy, lr->initiator); 409489766727SVasanthakumar Thiagarajan wiphy_all_share_dfs_chan_state(wiphy); 40951b7b3ac8SMiri Korenblit reg_process_self_managed_hints(); 409657b5ce07SLuis R. Rodriguez } 409757b5ce07SLuis R. Rodriguez 4098bfead080SLuis R. Rodriguez void wiphy_regulatory_deregister(struct wiphy *wiphy) 40993f2355cbSLuis R. Rodriguez { 41000ad8acafSLuis R. Rodriguez struct wiphy *request_wiphy = NULL; 4101c492db37SJohannes Berg struct regulatory_request *lr; 4102761cf7ecSLuis R. Rodriguez 4103c492db37SJohannes Berg lr = get_last_request(); 4104abc7381bSLuis R. Rodriguez 410557b5ce07SLuis R. Rodriguez if (!reg_dev_ignore_cell_hint(wiphy)) 410657b5ce07SLuis R. Rodriguez reg_num_devs_support_basehint--; 410757b5ce07SLuis R. Rodriguez 4108458f4f9eSJohannes Berg rcu_free_regdom(get_wiphy_regdom(wiphy)); 410934dd886cSMonam Agarwal RCU_INIT_POINTER(wiphy->regd, NULL); 41100ef9ccddSChris Wright 4111c492db37SJohannes Berg if (lr) 4112c492db37SJohannes Berg request_wiphy = wiphy_idx_to_wiphy(lr->wiphy_idx); 4113806a9e39SLuis R. Rodriguez 41140ef9ccddSChris Wright if (!request_wiphy || request_wiphy != wiphy) 411538fd2143SJohannes Berg return; 41160ef9ccddSChris Wright 4117c492db37SJohannes Berg lr->wiphy_idx = WIPHY_IDX_INVALID; 4118c492db37SJohannes Berg lr->country_ie_env = ENVIRON_ANY; 41193f2355cbSLuis R. Rodriguez } 41203f2355cbSLuis R. Rodriguez 4121174e0cd2SIlan Peer /* 4122f89769cfSArend van Spriel * See FCC notices for UNII band definitions 4123f89769cfSArend van Spriel * 5GHz: https://www.fcc.gov/document/5-ghz-unlicensed-spectrum-unii 4124f89769cfSArend van Spriel * 6GHz: https://www.fcc.gov/document/fcc-proposes-more-spectrum-unlicensed-use-0 4125174e0cd2SIlan Peer */ 4126174e0cd2SIlan Peer int cfg80211_get_unii(int freq) 4127174e0cd2SIlan Peer { 4128174e0cd2SIlan Peer /* UNII-1 */ 4129174e0cd2SIlan Peer if (freq >= 5150 && freq <= 5250) 4130174e0cd2SIlan Peer return 0; 4131174e0cd2SIlan Peer 4132174e0cd2SIlan Peer /* UNII-2A */ 4133174e0cd2SIlan Peer if (freq > 5250 && freq <= 5350) 4134174e0cd2SIlan Peer return 1; 4135174e0cd2SIlan Peer 4136174e0cd2SIlan Peer /* UNII-2B */ 4137174e0cd2SIlan Peer if (freq > 5350 && freq <= 5470) 4138174e0cd2SIlan Peer return 2; 4139174e0cd2SIlan Peer 4140174e0cd2SIlan Peer /* UNII-2C */ 4141174e0cd2SIlan Peer if (freq > 5470 && freq <= 5725) 4142174e0cd2SIlan Peer return 3; 4143174e0cd2SIlan Peer 4144174e0cd2SIlan Peer /* UNII-3 */ 4145174e0cd2SIlan Peer if (freq > 5725 && freq <= 5825) 4146174e0cd2SIlan Peer return 4; 4147174e0cd2SIlan Peer 4148f89769cfSArend van Spriel /* UNII-5 */ 4149f89769cfSArend van Spriel if (freq > 5925 && freq <= 6425) 4150f89769cfSArend van Spriel return 5; 4151f89769cfSArend van Spriel 4152f89769cfSArend van Spriel /* UNII-6 */ 4153f89769cfSArend van Spriel if (freq > 6425 && freq <= 6525) 4154f89769cfSArend van Spriel return 6; 4155f89769cfSArend van Spriel 4156f89769cfSArend van Spriel /* UNII-7 */ 4157f89769cfSArend van Spriel if (freq > 6525 && freq <= 6875) 4158f89769cfSArend van Spriel return 7; 4159f89769cfSArend van Spriel 4160f89769cfSArend van Spriel /* UNII-8 */ 4161f89769cfSArend van Spriel if (freq > 6875 && freq <= 7125) 4162f89769cfSArend van Spriel return 8; 4163f89769cfSArend van Spriel 4164174e0cd2SIlan Peer return -EINVAL; 4165174e0cd2SIlan Peer } 4166174e0cd2SIlan Peer 4167c8866e55SIlan Peer bool regulatory_indoor_allowed(void) 4168c8866e55SIlan Peer { 4169c8866e55SIlan Peer return reg_is_indoor; 4170c8866e55SIlan Peer } 4171c8866e55SIlan Peer 4172b35a51c7SVasanthakumar Thiagarajan bool regulatory_pre_cac_allowed(struct wiphy *wiphy) 4173b35a51c7SVasanthakumar Thiagarajan { 4174b35a51c7SVasanthakumar Thiagarajan const struct ieee80211_regdomain *regd = NULL; 4175b35a51c7SVasanthakumar Thiagarajan const struct ieee80211_regdomain *wiphy_regd = NULL; 4176b35a51c7SVasanthakumar Thiagarajan bool pre_cac_allowed = false; 4177b35a51c7SVasanthakumar Thiagarajan 4178b35a51c7SVasanthakumar Thiagarajan rcu_read_lock(); 4179b35a51c7SVasanthakumar Thiagarajan 4180b35a51c7SVasanthakumar Thiagarajan regd = rcu_dereference(cfg80211_regdomain); 4181b35a51c7SVasanthakumar Thiagarajan wiphy_regd = rcu_dereference(wiphy->regd); 4182b35a51c7SVasanthakumar Thiagarajan if (!wiphy_regd) { 4183b35a51c7SVasanthakumar Thiagarajan if (regd->dfs_region == NL80211_DFS_ETSI) 4184b35a51c7SVasanthakumar Thiagarajan pre_cac_allowed = true; 4185b35a51c7SVasanthakumar Thiagarajan 4186b35a51c7SVasanthakumar Thiagarajan rcu_read_unlock(); 4187b35a51c7SVasanthakumar Thiagarajan 4188b35a51c7SVasanthakumar Thiagarajan return pre_cac_allowed; 4189b35a51c7SVasanthakumar Thiagarajan } 4190b35a51c7SVasanthakumar Thiagarajan 4191b35a51c7SVasanthakumar Thiagarajan if (regd->dfs_region == wiphy_regd->dfs_region && 4192b35a51c7SVasanthakumar Thiagarajan wiphy_regd->dfs_region == NL80211_DFS_ETSI) 4193b35a51c7SVasanthakumar Thiagarajan pre_cac_allowed = true; 4194b35a51c7SVasanthakumar Thiagarajan 4195b35a51c7SVasanthakumar Thiagarajan rcu_read_unlock(); 4196b35a51c7SVasanthakumar Thiagarajan 4197b35a51c7SVasanthakumar Thiagarajan return pre_cac_allowed; 4198b35a51c7SVasanthakumar Thiagarajan } 4199dc0c18edSAaron Komisar EXPORT_SYMBOL(regulatory_pre_cac_allowed); 4200b35a51c7SVasanthakumar Thiagarajan 420126ec17a1SOrr Mazor static void cfg80211_check_and_end_cac(struct cfg80211_registered_device *rdev) 420226ec17a1SOrr Mazor { 420326ec17a1SOrr Mazor struct wireless_dev *wdev; 420426ec17a1SOrr Mazor /* If we finished CAC or received radar, we should end any 420526ec17a1SOrr Mazor * CAC running on the same channels. 420626ec17a1SOrr Mazor * the check !cfg80211_chandef_dfs_usable contain 2 options: 420726ec17a1SOrr Mazor * either all channels are available - those the CAC_FINISHED 420826ec17a1SOrr Mazor * event has effected another wdev state, or there is a channel 420926ec17a1SOrr Mazor * in unavailable state in wdev chandef - those the RADAR_DETECTED 421026ec17a1SOrr Mazor * event has effected another wdev state. 421126ec17a1SOrr Mazor * In both cases we should end the CAC on the wdev. 421226ec17a1SOrr Mazor */ 421326ec17a1SOrr Mazor list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) { 421426ec17a1SOrr Mazor if (wdev->cac_started && 421526ec17a1SOrr Mazor !cfg80211_chandef_dfs_usable(&rdev->wiphy, &wdev->chandef)) 421626ec17a1SOrr Mazor rdev_end_cac(rdev, wdev->netdev); 421726ec17a1SOrr Mazor } 421826ec17a1SOrr Mazor } 421926ec17a1SOrr Mazor 422089766727SVasanthakumar Thiagarajan void regulatory_propagate_dfs_state(struct wiphy *wiphy, 422189766727SVasanthakumar Thiagarajan struct cfg80211_chan_def *chandef, 422289766727SVasanthakumar Thiagarajan enum nl80211_dfs_state dfs_state, 422389766727SVasanthakumar Thiagarajan enum nl80211_radar_event event) 422489766727SVasanthakumar Thiagarajan { 422589766727SVasanthakumar Thiagarajan struct cfg80211_registered_device *rdev; 422689766727SVasanthakumar Thiagarajan 422789766727SVasanthakumar Thiagarajan ASSERT_RTNL(); 422889766727SVasanthakumar Thiagarajan 422989766727SVasanthakumar Thiagarajan if (WARN_ON(!cfg80211_chandef_valid(chandef))) 423089766727SVasanthakumar Thiagarajan return; 423189766727SVasanthakumar Thiagarajan 423289766727SVasanthakumar Thiagarajan list_for_each_entry(rdev, &cfg80211_rdev_list, list) { 423389766727SVasanthakumar Thiagarajan if (wiphy == &rdev->wiphy) 423489766727SVasanthakumar Thiagarajan continue; 423589766727SVasanthakumar Thiagarajan 423689766727SVasanthakumar Thiagarajan if (!reg_dfs_domain_same(wiphy, &rdev->wiphy)) 423789766727SVasanthakumar Thiagarajan continue; 423889766727SVasanthakumar Thiagarajan 423989766727SVasanthakumar Thiagarajan if (!ieee80211_get_channel(&rdev->wiphy, 424089766727SVasanthakumar Thiagarajan chandef->chan->center_freq)) 424189766727SVasanthakumar Thiagarajan continue; 424289766727SVasanthakumar Thiagarajan 424389766727SVasanthakumar Thiagarajan cfg80211_set_dfs_state(&rdev->wiphy, chandef, dfs_state); 424489766727SVasanthakumar Thiagarajan 424589766727SVasanthakumar Thiagarajan if (event == NL80211_RADAR_DETECTED || 424626ec17a1SOrr Mazor event == NL80211_RADAR_CAC_FINISHED) { 424789766727SVasanthakumar Thiagarajan cfg80211_sched_dfs_chan_update(rdev); 424826ec17a1SOrr Mazor cfg80211_check_and_end_cac(rdev); 424926ec17a1SOrr Mazor } 425089766727SVasanthakumar Thiagarajan 425189766727SVasanthakumar Thiagarajan nl80211_radar_notify(rdev, chandef, event, NULL, GFP_KERNEL); 425289766727SVasanthakumar Thiagarajan } 425389766727SVasanthakumar Thiagarajan } 425489766727SVasanthakumar Thiagarajan 4255d7be102fSJohannes Berg static int __init regulatory_init_db(void) 4256b2e1b302SLuis R. Rodriguez { 4257d7be102fSJohannes Berg int err; 4258734366deSJohannes Berg 425971e5e886SJohannes Berg /* 426071e5e886SJohannes Berg * It's possible that - due to other bugs/issues - cfg80211 426171e5e886SJohannes Berg * never called regulatory_init() below, or that it failed; 426271e5e886SJohannes Berg * in that case, don't try to do any further work here as 426371e5e886SJohannes Berg * it's doomed to lead to crashes. 426471e5e886SJohannes Berg */ 426571e5e886SJohannes Berg if (IS_ERR_OR_NULL(reg_pdev)) 426671e5e886SJohannes Berg return -EINVAL; 426771e5e886SJohannes Berg 426890a53e44SJohannes Berg err = load_builtin_regdb_keys(); 426990a53e44SJohannes Berg if (err) 427090a53e44SJohannes Berg return err; 427190a53e44SJohannes Berg 4272ae9e4b0dSLuis R. Rodriguez /* We always try to get an update for the static regdomain */ 4273458f4f9eSJohannes Berg err = regulatory_hint_core(cfg80211_world_regdom->alpha2); 4274bcf4f99bSLuis R. Rodriguez if (err) { 427509d11800SOla Olsson if (err == -ENOMEM) { 427609d11800SOla Olsson platform_device_unregister(reg_pdev); 4277bcf4f99bSLuis R. Rodriguez return err; 427809d11800SOla Olsson } 4279bcf4f99bSLuis R. Rodriguez /* 4280bcf4f99bSLuis R. Rodriguez * N.B. kobject_uevent_env() can fail mainly for when we're out 4281bcf4f99bSLuis R. Rodriguez * memory which is handled and propagated appropriately above 4282bcf4f99bSLuis R. Rodriguez * but it can also fail during a netlink_broadcast() or during 4283bcf4f99bSLuis R. Rodriguez * early boot for call_usermodehelper(). For now treat these 4284bcf4f99bSLuis R. Rodriguez * errors as non-fatal. 4285bcf4f99bSLuis R. Rodriguez */ 4286e9c0268fSJoe Perches pr_err("kobject_uevent_env() was unable to call CRDA during init\n"); 4287bcf4f99bSLuis R. Rodriguez } 4288734366deSJohannes Berg 4289ae9e4b0dSLuis R. Rodriguez /* 4290ae9e4b0dSLuis R. Rodriguez * Finally, if the user set the module parameter treat it 4291ae9e4b0dSLuis R. Rodriguez * as a user hint. 4292ae9e4b0dSLuis R. Rodriguez */ 4293ae9e4b0dSLuis R. Rodriguez if (!is_world_regdom(ieee80211_regdom)) 429457b5ce07SLuis R. Rodriguez regulatory_hint_user(ieee80211_regdom, 429557b5ce07SLuis R. Rodriguez NL80211_USER_REG_HINT_USER); 4296ae9e4b0dSLuis R. Rodriguez 4297b2e1b302SLuis R. Rodriguez return 0; 4298b2e1b302SLuis R. Rodriguez } 4299d7be102fSJohannes Berg #ifndef MODULE 4300d7be102fSJohannes Berg late_initcall(regulatory_init_db); 4301d7be102fSJohannes Berg #endif 4302d7be102fSJohannes Berg 4303d7be102fSJohannes Berg int __init regulatory_init(void) 4304d7be102fSJohannes Berg { 4305d7be102fSJohannes Berg reg_pdev = platform_device_register_simple("regulatory", 0, NULL, 0); 4306d7be102fSJohannes Berg if (IS_ERR(reg_pdev)) 4307d7be102fSJohannes Berg return PTR_ERR(reg_pdev); 4308d7be102fSJohannes Berg 4309d7be102fSJohannes Berg rcu_assign_pointer(cfg80211_regdomain, cfg80211_world_regdom); 4310d7be102fSJohannes Berg 4311d7be102fSJohannes Berg user_alpha2[0] = '9'; 4312d7be102fSJohannes Berg user_alpha2[1] = '7'; 4313d7be102fSJohannes Berg 4314d7be102fSJohannes Berg #ifdef MODULE 4315d7be102fSJohannes Berg return regulatory_init_db(); 4316d7be102fSJohannes Berg #else 4317d7be102fSJohannes Berg return 0; 4318d7be102fSJohannes Berg #endif 4319d7be102fSJohannes Berg } 4320b2e1b302SLuis R. Rodriguez 43211a919318SJohannes Berg void regulatory_exit(void) 4322b2e1b302SLuis R. Rodriguez { 4323fe33eb39SLuis R. Rodriguez struct regulatory_request *reg_request, *tmp; 4324e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon, *btmp; 4325fe33eb39SLuis R. Rodriguez 4326fe33eb39SLuis R. Rodriguez cancel_work_sync(®_work); 4327b6863036SJohannes Berg cancel_crda_timeout_sync(); 4328ad932f04SArik Nemtsov cancel_delayed_work_sync(®_check_chans); 4329fe33eb39SLuis R. Rodriguez 43309027b149SJohannes Berg /* Lock to suppress warnings */ 433138fd2143SJohannes Berg rtnl_lock(); 4332379b82f4SJohannes Berg reset_regdomains(true, NULL); 433338fd2143SJohannes Berg rtnl_unlock(); 4334734366deSJohannes Berg 433558ebacc6SLuis R. Rodriguez dev_set_uevent_suppress(®_pdev->dev, true); 4336f6037d09SJohannes Berg 4337b2e1b302SLuis R. Rodriguez platform_device_unregister(reg_pdev); 4338734366deSJohannes Berg 4339fea9bcedSJohannes Berg list_for_each_entry_safe(reg_beacon, btmp, ®_pending_beacons, list) { 4340e38f8a7aSLuis R. Rodriguez list_del(®_beacon->list); 4341e38f8a7aSLuis R. Rodriguez kfree(reg_beacon); 4342e38f8a7aSLuis R. Rodriguez } 4343e38f8a7aSLuis R. Rodriguez 4344fea9bcedSJohannes Berg list_for_each_entry_safe(reg_beacon, btmp, ®_beacon_list, list) { 4345e38f8a7aSLuis R. Rodriguez list_del(®_beacon->list); 4346e38f8a7aSLuis R. Rodriguez kfree(reg_beacon); 4347e38f8a7aSLuis R. Rodriguez } 4348e38f8a7aSLuis R. Rodriguez 4349fea9bcedSJohannes Berg list_for_each_entry_safe(reg_request, tmp, ®_requests_list, list) { 4350fe33eb39SLuis R. Rodriguez list_del(®_request->list); 4351fe33eb39SLuis R. Rodriguez kfree(reg_request); 4352fe33eb39SLuis R. Rodriguez } 4353007f6c5eSJohannes Berg 4354007f6c5eSJohannes Berg if (!IS_ERR_OR_NULL(regdb)) 4355007f6c5eSJohannes Berg kfree(regdb); 4356e646a025SJohannes Berg if (!IS_ERR_OR_NULL(cfg80211_user_regdom)) 4357e646a025SJohannes Berg kfree(cfg80211_user_regdom); 435890a53e44SJohannes Berg 435990a53e44SJohannes Berg free_regdb_keyring(); 4360fe33eb39SLuis R. Rodriguez } 4361