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 88318d78aSJohannes Berg * 93b77d5ecSLuis R. Rodriguez * Permission to use, copy, modify, and/or distribute this software for any 103b77d5ecSLuis R. Rodriguez * purpose with or without fee is hereby granted, provided that the above 113b77d5ecSLuis R. Rodriguez * copyright notice and this permission notice appear in all copies. 123b77d5ecSLuis R. Rodriguez * 133b77d5ecSLuis R. Rodriguez * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 143b77d5ecSLuis R. Rodriguez * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 153b77d5ecSLuis R. Rodriguez * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 163b77d5ecSLuis R. Rodriguez * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 173b77d5ecSLuis R. Rodriguez * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 183b77d5ecSLuis R. Rodriguez * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 193b77d5ecSLuis R. Rodriguez * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 208318d78aSJohannes Berg */ 218318d78aSJohannes Berg 223b77d5ecSLuis R. Rodriguez 23b2e1b302SLuis R. Rodriguez /** 24b2e1b302SLuis R. Rodriguez * DOC: Wireless regulatory infrastructure 258318d78aSJohannes Berg * 268318d78aSJohannes Berg * The usual implementation is for a driver to read a device EEPROM to 278318d78aSJohannes Berg * determine which regulatory domain it should be operating under, then 288318d78aSJohannes Berg * looking up the allowable channels in a driver-local table and finally 298318d78aSJohannes Berg * registering those channels in the wiphy structure. 308318d78aSJohannes Berg * 31b2e1b302SLuis R. Rodriguez * Another set of compliance enforcement is for drivers to use their 32b2e1b302SLuis R. Rodriguez * own compliance limits which can be stored on the EEPROM. The host 33b2e1b302SLuis R. Rodriguez * driver or firmware may ensure these are used. 34b2e1b302SLuis R. Rodriguez * 35b2e1b302SLuis R. Rodriguez * In addition to all this we provide an extra layer of regulatory 36b2e1b302SLuis R. Rodriguez * conformance. For drivers which do not have any regulatory 37b2e1b302SLuis R. Rodriguez * information CRDA provides the complete regulatory solution. 38b2e1b302SLuis R. Rodriguez * For others it provides a community effort on further restrictions 39b2e1b302SLuis R. Rodriguez * to enhance compliance. 40b2e1b302SLuis R. Rodriguez * 41b2e1b302SLuis R. Rodriguez * Note: When number of rules --> infinity we will not be able to 42b2e1b302SLuis R. Rodriguez * index on alpha2 any more, instead we'll probably have to 43b2e1b302SLuis R. Rodriguez * rely on some SHA1 checksum of the regdomain for example. 44b2e1b302SLuis R. Rodriguez * 458318d78aSJohannes Berg */ 46e9c0268fSJoe Perches 47e9c0268fSJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 48e9c0268fSJoe Perches 498318d78aSJohannes Berg #include <linux/kernel.h> 50bc3b2d7fSPaul Gortmaker #include <linux/export.h> 515a0e3ad6STejun Heo #include <linux/slab.h> 52b2e1b302SLuis R. Rodriguez #include <linux/list.h> 53c61029c7SJohn W. Linville #include <linux/ctype.h> 54b2e1b302SLuis R. Rodriguez #include <linux/nl80211.h> 55b2e1b302SLuis R. Rodriguez #include <linux/platform_device.h> 5690a53e44SJohannes Berg #include <linux/verification.h> 57d9b93842SPaul Gortmaker #include <linux/moduleparam.h> 58007f6c5eSJohannes Berg #include <linux/firmware.h> 59b2e1b302SLuis R. Rodriguez #include <net/cfg80211.h> 608318d78aSJohannes Berg #include "core.h" 61b2e1b302SLuis R. Rodriguez #include "reg.h" 62ad932f04SArik Nemtsov #include "rdev-ops.h" 6373d54c9eSLuis R. Rodriguez #include "nl80211.h" 648318d78aSJohannes Berg 65ad932f04SArik Nemtsov /* 66ad932f04SArik Nemtsov * Grace period we give before making sure all current interfaces reside on 67ad932f04SArik Nemtsov * channels allowed by the current regulatory domain. 68ad932f04SArik Nemtsov */ 69ad932f04SArik Nemtsov #define REG_ENFORCE_GRACE_MS 60000 70ad932f04SArik Nemtsov 7152616f2bSIlan Peer /** 7252616f2bSIlan Peer * enum reg_request_treatment - regulatory request treatment 7352616f2bSIlan Peer * 7452616f2bSIlan Peer * @REG_REQ_OK: continue processing the regulatory request 7552616f2bSIlan Peer * @REG_REQ_IGNORE: ignore the regulatory request 7652616f2bSIlan Peer * @REG_REQ_INTERSECT: the regulatory domain resulting from this request should 7752616f2bSIlan Peer * be intersected with the current one. 7852616f2bSIlan Peer * @REG_REQ_ALREADY_SET: the regulatory request will not change the current 7952616f2bSIlan Peer * regulatory settings, and no further processing is required. 8052616f2bSIlan Peer */ 812f92212bSJohannes Berg enum reg_request_treatment { 822f92212bSJohannes Berg REG_REQ_OK, 832f92212bSJohannes Berg REG_REQ_IGNORE, 842f92212bSJohannes Berg REG_REQ_INTERSECT, 852f92212bSJohannes Berg REG_REQ_ALREADY_SET, 862f92212bSJohannes Berg }; 872f92212bSJohannes Berg 88a042994dSLuis R. Rodriguez static struct regulatory_request core_request_world = { 89a042994dSLuis R. Rodriguez .initiator = NL80211_REGDOM_SET_BY_CORE, 90a042994dSLuis R. Rodriguez .alpha2[0] = '0', 91a042994dSLuis R. Rodriguez .alpha2[1] = '0', 92a042994dSLuis R. Rodriguez .intersect = false, 93a042994dSLuis R. Rodriguez .processed = true, 94a042994dSLuis R. Rodriguez .country_ie_env = ENVIRON_ANY, 95a042994dSLuis R. Rodriguez }; 96a042994dSLuis R. Rodriguez 9738fd2143SJohannes Berg /* 9838fd2143SJohannes Berg * Receipt of information from last regulatory request, 9938fd2143SJohannes Berg * protected by RTNL (and can be accessed with RCU protection) 10038fd2143SJohannes Berg */ 101c492db37SJohannes Berg static struct regulatory_request __rcu *last_request = 102cec3f0edSJohannes Berg (void __force __rcu *)&core_request_world; 103734366deSJohannes Berg 104007f6c5eSJohannes Berg /* To trigger userspace events and load firmware */ 105b2e1b302SLuis R. Rodriguez static struct platform_device *reg_pdev; 1068318d78aSJohannes Berg 107fb1fc7adSLuis R. Rodriguez /* 108fb1fc7adSLuis R. Rodriguez * Central wireless core regulatory domains, we only need two, 109734366deSJohannes Berg * the current one and a world regulatory domain in case we have no 110e8da2bb4SJohannes Berg * information to give us an alpha2. 11138fd2143SJohannes Berg * (protected by RTNL, can be read under RCU) 112fb1fc7adSLuis R. Rodriguez */ 113458f4f9eSJohannes Berg const struct ieee80211_regdomain __rcu *cfg80211_regdomain; 114734366deSJohannes Berg 115fb1fc7adSLuis R. Rodriguez /* 11657b5ce07SLuis R. Rodriguez * Number of devices that registered to the core 11757b5ce07SLuis R. Rodriguez * that support cellular base station regulatory hints 11838fd2143SJohannes Berg * (protected by RTNL) 11957b5ce07SLuis R. Rodriguez */ 12057b5ce07SLuis R. Rodriguez static int reg_num_devs_support_basehint; 12157b5ce07SLuis R. Rodriguez 12252616f2bSIlan Peer /* 12352616f2bSIlan Peer * State variable indicating if the platform on which the devices 12452616f2bSIlan Peer * are attached is operating in an indoor environment. The state variable 12552616f2bSIlan Peer * is relevant for all registered devices. 12652616f2bSIlan Peer */ 12752616f2bSIlan Peer static bool reg_is_indoor; 12805050753SIlan peer static spinlock_t reg_indoor_lock; 12905050753SIlan peer 13005050753SIlan peer /* Used to track the userspace process controlling the indoor setting */ 13105050753SIlan peer static u32 reg_is_indoor_portid; 13252616f2bSIlan Peer 133b6863036SJohannes Berg static void restore_regulatory_settings(bool reset_user); 134c37722bdSIlan peer 135458f4f9eSJohannes Berg static const struct ieee80211_regdomain *get_cfg80211_regdom(void) 136458f4f9eSJohannes Berg { 13738fd2143SJohannes Berg return rtnl_dereference(cfg80211_regdomain); 138458f4f9eSJohannes Berg } 139458f4f9eSJohannes Berg 140ad30ca2cSArik Nemtsov const struct ieee80211_regdomain *get_wiphy_regdom(struct wiphy *wiphy) 141458f4f9eSJohannes Berg { 14238fd2143SJohannes Berg return rtnl_dereference(wiphy->regd); 143458f4f9eSJohannes Berg } 144458f4f9eSJohannes Berg 1453ef121b5SLuis R. Rodriguez static const char *reg_dfs_region_str(enum nl80211_dfs_regions dfs_region) 1463ef121b5SLuis R. Rodriguez { 1473ef121b5SLuis R. Rodriguez switch (dfs_region) { 1483ef121b5SLuis R. Rodriguez case NL80211_DFS_UNSET: 1493ef121b5SLuis R. Rodriguez return "unset"; 1503ef121b5SLuis R. Rodriguez case NL80211_DFS_FCC: 1513ef121b5SLuis R. Rodriguez return "FCC"; 1523ef121b5SLuis R. Rodriguez case NL80211_DFS_ETSI: 1533ef121b5SLuis R. Rodriguez return "ETSI"; 1543ef121b5SLuis R. Rodriguez case NL80211_DFS_JP: 1553ef121b5SLuis R. Rodriguez return "JP"; 1563ef121b5SLuis R. Rodriguez } 1573ef121b5SLuis R. Rodriguez return "Unknown"; 1583ef121b5SLuis R. Rodriguez } 1593ef121b5SLuis R. Rodriguez 1606c474799SLuis R. Rodriguez enum nl80211_dfs_regions reg_get_dfs_region(struct wiphy *wiphy) 1616c474799SLuis R. Rodriguez { 1626c474799SLuis R. Rodriguez const struct ieee80211_regdomain *regd = NULL; 1636c474799SLuis R. Rodriguez const struct ieee80211_regdomain *wiphy_regd = NULL; 1646c474799SLuis R. Rodriguez 1656c474799SLuis R. Rodriguez regd = get_cfg80211_regdom(); 1666c474799SLuis R. Rodriguez if (!wiphy) 1676c474799SLuis R. Rodriguez goto out; 1686c474799SLuis R. Rodriguez 1696c474799SLuis R. Rodriguez wiphy_regd = get_wiphy_regdom(wiphy); 1706c474799SLuis R. Rodriguez if (!wiphy_regd) 1716c474799SLuis R. Rodriguez goto out; 1726c474799SLuis R. Rodriguez 1736c474799SLuis R. Rodriguez if (wiphy_regd->dfs_region == regd->dfs_region) 1746c474799SLuis R. Rodriguez goto out; 1756c474799SLuis R. Rodriguez 176c799ba6eSJohannes Berg pr_debug("%s: device specific dfs_region (%s) disagrees with cfg80211's central dfs_region (%s)\n", 1776c474799SLuis R. Rodriguez dev_name(&wiphy->dev), 1786c474799SLuis R. Rodriguez reg_dfs_region_str(wiphy_regd->dfs_region), 1796c474799SLuis R. Rodriguez reg_dfs_region_str(regd->dfs_region)); 1806c474799SLuis R. Rodriguez 1816c474799SLuis R. Rodriguez out: 1826c474799SLuis R. Rodriguez return regd->dfs_region; 1836c474799SLuis R. Rodriguez } 1846c474799SLuis R. Rodriguez 185458f4f9eSJohannes Berg static void rcu_free_regdom(const struct ieee80211_regdomain *r) 186458f4f9eSJohannes Berg { 187458f4f9eSJohannes Berg if (!r) 188458f4f9eSJohannes Berg return; 189458f4f9eSJohannes Berg kfree_rcu((struct ieee80211_regdomain *)r, rcu_head); 190458f4f9eSJohannes Berg } 191458f4f9eSJohannes Berg 192c492db37SJohannes Berg static struct regulatory_request *get_last_request(void) 193c492db37SJohannes Berg { 19438fd2143SJohannes Berg return rcu_dereference_rtnl(last_request); 195c492db37SJohannes Berg } 196c492db37SJohannes Berg 197e38f8a7aSLuis R. Rodriguez /* Used to queue up regulatory hints */ 198fe33eb39SLuis R. Rodriguez static LIST_HEAD(reg_requests_list); 199fe33eb39SLuis R. Rodriguez static spinlock_t reg_requests_lock; 200fe33eb39SLuis R. Rodriguez 201e38f8a7aSLuis R. Rodriguez /* Used to queue up beacon hints for review */ 202e38f8a7aSLuis R. Rodriguez static LIST_HEAD(reg_pending_beacons); 203e38f8a7aSLuis R. Rodriguez static spinlock_t reg_pending_beacons_lock; 204e38f8a7aSLuis R. Rodriguez 205e38f8a7aSLuis R. Rodriguez /* Used to keep track of processed beacon hints */ 206e38f8a7aSLuis R. Rodriguez static LIST_HEAD(reg_beacon_list); 207e38f8a7aSLuis R. Rodriguez 208e38f8a7aSLuis R. Rodriguez struct reg_beacon { 209e38f8a7aSLuis R. Rodriguez struct list_head list; 210e38f8a7aSLuis R. Rodriguez struct ieee80211_channel chan; 211e38f8a7aSLuis R. Rodriguez }; 212e38f8a7aSLuis R. Rodriguez 213ad932f04SArik Nemtsov static void reg_check_chans_work(struct work_struct *work); 214ad932f04SArik Nemtsov static DECLARE_DELAYED_WORK(reg_check_chans, reg_check_chans_work); 215ad932f04SArik Nemtsov 216f333a7a2SLuis R. Rodriguez static void reg_todo(struct work_struct *work); 217f333a7a2SLuis R. Rodriguez static DECLARE_WORK(reg_work, reg_todo); 218f333a7a2SLuis R. Rodriguez 219734366deSJohannes Berg /* We keep a static world regulatory domain in case of the absence of CRDA */ 220734366deSJohannes Berg static const struct ieee80211_regdomain world_regdom = { 22128981e5eSJason Abele .n_reg_rules = 8, 222734366deSJohannes Berg .alpha2 = "00", 223734366deSJohannes Berg .reg_rules = { 22468798a62SLuis R. Rodriguez /* IEEE 802.11b/g, channels 1..11 */ 22568798a62SLuis R. Rodriguez REG_RULE(2412-10, 2462+10, 40, 6, 20, 0), 22643c771a1SJohannes Berg /* IEEE 802.11b/g, channels 12..13. */ 227c3826807SJohannes Berg REG_RULE(2467-10, 2472+10, 20, 6, 20, 228c3826807SJohannes Berg NL80211_RRF_NO_IR | NL80211_RRF_AUTO_BW), 229611b6a82SLuis R. Rodriguez /* IEEE 802.11 channel 14 - Only JP enables 230611b6a82SLuis R. Rodriguez * this and for 802.11b only */ 231611b6a82SLuis R. Rodriguez REG_RULE(2484-10, 2484+10, 20, 6, 20, 2328fe02e16SLuis R. Rodriguez NL80211_RRF_NO_IR | 233611b6a82SLuis R. Rodriguez NL80211_RRF_NO_OFDM), 2343fc71f77SLuis R. Rodriguez /* IEEE 802.11a, channel 36..48 */ 235c3826807SJohannes Berg REG_RULE(5180-10, 5240+10, 80, 6, 20, 236c3826807SJohannes Berg NL80211_RRF_NO_IR | 237c3826807SJohannes Berg NL80211_RRF_AUTO_BW), 2383fc71f77SLuis R. Rodriguez 239131a19bcSJohannes Berg /* IEEE 802.11a, channel 52..64 - DFS required */ 240c3826807SJohannes Berg REG_RULE(5260-10, 5320+10, 80, 6, 20, 2418fe02e16SLuis R. Rodriguez NL80211_RRF_NO_IR | 242c3826807SJohannes Berg NL80211_RRF_AUTO_BW | 243131a19bcSJohannes Berg NL80211_RRF_DFS), 244131a19bcSJohannes Berg 245131a19bcSJohannes Berg /* IEEE 802.11a, channel 100..144 - DFS required */ 246131a19bcSJohannes Berg REG_RULE(5500-10, 5720+10, 160, 6, 20, 2478fe02e16SLuis R. Rodriguez NL80211_RRF_NO_IR | 248131a19bcSJohannes Berg NL80211_RRF_DFS), 2493fc71f77SLuis R. Rodriguez 2503fc71f77SLuis R. Rodriguez /* IEEE 802.11a, channel 149..165 */ 2518ab9d85cSJohannes Berg REG_RULE(5745-10, 5825+10, 80, 6, 20, 2528fe02e16SLuis R. Rodriguez NL80211_RRF_NO_IR), 25390cdc6dfSVladimir Kondratiev 2548047d261SJohannes Berg /* IEEE 802.11ad (60GHz), channels 1..3 */ 25590cdc6dfSVladimir Kondratiev REG_RULE(56160+2160*1-1080, 56160+2160*3+1080, 2160, 0, 0, 0), 256734366deSJohannes Berg } 257734366deSJohannes Berg }; 258734366deSJohannes Berg 25938fd2143SJohannes Berg /* protected by RTNL */ 260a3d2eaf0SJohannes Berg static const struct ieee80211_regdomain *cfg80211_world_regdom = 261a3d2eaf0SJohannes Berg &world_regdom; 262734366deSJohannes Berg 2636ee7d330SLuis R. Rodriguez static char *ieee80211_regdom = "00"; 26409d989d1SLuis R. Rodriguez static char user_alpha2[2]; 2656ee7d330SLuis R. Rodriguez 266734366deSJohannes Berg module_param(ieee80211_regdom, charp, 0444); 267734366deSJohannes Berg MODULE_PARM_DESC(ieee80211_regdom, "IEEE 802.11 regulatory domain code"); 268734366deSJohannes Berg 269c888393bSArik Nemtsov static void reg_free_request(struct regulatory_request *request) 2705ad6ef5eSLuis R. Rodriguez { 271d34265a3SJohannes Berg if (request == &core_request_world) 272d34265a3SJohannes Berg return; 273d34265a3SJohannes Berg 274c888393bSArik Nemtsov if (request != get_last_request()) 275c888393bSArik Nemtsov kfree(request); 276c888393bSArik Nemtsov } 277c888393bSArik Nemtsov 278c888393bSArik Nemtsov static void reg_free_last_request(void) 279c888393bSArik Nemtsov { 280c888393bSArik Nemtsov struct regulatory_request *lr = get_last_request(); 281c888393bSArik Nemtsov 2825ad6ef5eSLuis R. Rodriguez if (lr != &core_request_world && lr) 2835ad6ef5eSLuis R. Rodriguez kfree_rcu(lr, rcu_head); 2845ad6ef5eSLuis R. Rodriguez } 2855ad6ef5eSLuis R. Rodriguez 28605f1a3eaSLuis R. Rodriguez static void reg_update_last_request(struct regulatory_request *request) 28705f1a3eaSLuis R. Rodriguez { 288255e25b0SLuis R. Rodriguez struct regulatory_request *lr; 289255e25b0SLuis R. Rodriguez 290255e25b0SLuis R. Rodriguez lr = get_last_request(); 291255e25b0SLuis R. Rodriguez if (lr == request) 292255e25b0SLuis R. Rodriguez return; 293255e25b0SLuis R. Rodriguez 294c888393bSArik Nemtsov reg_free_last_request(); 29505f1a3eaSLuis R. Rodriguez rcu_assign_pointer(last_request, request); 29605f1a3eaSLuis R. Rodriguez } 29705f1a3eaSLuis R. Rodriguez 298379b82f4SJohannes Berg static void reset_regdomains(bool full_reset, 299379b82f4SJohannes Berg const struct ieee80211_regdomain *new_regdom) 300734366deSJohannes Berg { 301458f4f9eSJohannes Berg const struct ieee80211_regdomain *r; 302458f4f9eSJohannes Berg 30338fd2143SJohannes Berg ASSERT_RTNL(); 304e8da2bb4SJohannes Berg 305458f4f9eSJohannes Berg r = get_cfg80211_regdom(); 306458f4f9eSJohannes Berg 307942b25cfSJohannes Berg /* avoid freeing static information or freeing something twice */ 308458f4f9eSJohannes Berg if (r == cfg80211_world_regdom) 309458f4f9eSJohannes Berg r = NULL; 310942b25cfSJohannes Berg if (cfg80211_world_regdom == &world_regdom) 311942b25cfSJohannes Berg cfg80211_world_regdom = NULL; 312458f4f9eSJohannes Berg if (r == &world_regdom) 313458f4f9eSJohannes Berg r = NULL; 314942b25cfSJohannes Berg 315458f4f9eSJohannes Berg rcu_free_regdom(r); 316458f4f9eSJohannes Berg rcu_free_regdom(cfg80211_world_regdom); 317734366deSJohannes Berg 318a3d2eaf0SJohannes Berg cfg80211_world_regdom = &world_regdom; 319458f4f9eSJohannes Berg rcu_assign_pointer(cfg80211_regdomain, new_regdom); 320a042994dSLuis R. Rodriguez 321a042994dSLuis R. Rodriguez if (!full_reset) 322a042994dSLuis R. Rodriguez return; 323a042994dSLuis R. Rodriguez 32405f1a3eaSLuis R. Rodriguez reg_update_last_request(&core_request_world); 325734366deSJohannes Berg } 326734366deSJohannes Berg 327fb1fc7adSLuis R. Rodriguez /* 328fb1fc7adSLuis R. Rodriguez * Dynamic world regulatory domain requested by the wireless 329fb1fc7adSLuis R. Rodriguez * core upon initialization 330fb1fc7adSLuis R. Rodriguez */ 331a3d2eaf0SJohannes Berg static void update_world_regdomain(const struct ieee80211_regdomain *rd) 332734366deSJohannes Berg { 333c492db37SJohannes Berg struct regulatory_request *lr; 334734366deSJohannes Berg 335c492db37SJohannes Berg lr = get_last_request(); 336c492db37SJohannes Berg 337c492db37SJohannes Berg WARN_ON(!lr); 338e8da2bb4SJohannes Berg 339379b82f4SJohannes Berg reset_regdomains(false, rd); 340734366deSJohannes Berg 341734366deSJohannes Berg cfg80211_world_regdom = rd; 342734366deSJohannes Berg } 343734366deSJohannes Berg 344a3d2eaf0SJohannes Berg bool is_world_regdom(const char *alpha2) 345b2e1b302SLuis R. Rodriguez { 346b2e1b302SLuis R. Rodriguez if (!alpha2) 347b2e1b302SLuis R. Rodriguez return false; 3481a919318SJohannes Berg return alpha2[0] == '0' && alpha2[1] == '0'; 349b2e1b302SLuis R. Rodriguez } 350b2e1b302SLuis R. Rodriguez 351a3d2eaf0SJohannes Berg static bool is_alpha2_set(const char *alpha2) 352b2e1b302SLuis R. Rodriguez { 353b2e1b302SLuis R. Rodriguez if (!alpha2) 354b2e1b302SLuis R. Rodriguez return false; 3551a919318SJohannes Berg return alpha2[0] && alpha2[1]; 356b2e1b302SLuis R. Rodriguez } 357b2e1b302SLuis R. Rodriguez 358a3d2eaf0SJohannes Berg static bool is_unknown_alpha2(const char *alpha2) 359b2e1b302SLuis R. Rodriguez { 360b2e1b302SLuis R. Rodriguez if (!alpha2) 361b2e1b302SLuis R. Rodriguez return false; 362fb1fc7adSLuis R. Rodriguez /* 363fb1fc7adSLuis R. Rodriguez * Special case where regulatory domain was built by driver 364fb1fc7adSLuis R. Rodriguez * but a specific alpha2 cannot be determined 365fb1fc7adSLuis R. Rodriguez */ 3661a919318SJohannes Berg return alpha2[0] == '9' && alpha2[1] == '9'; 367b2e1b302SLuis R. Rodriguez } 368b2e1b302SLuis R. Rodriguez 3693f2355cbSLuis R. Rodriguez static bool is_intersected_alpha2(const char *alpha2) 3703f2355cbSLuis R. Rodriguez { 3713f2355cbSLuis R. Rodriguez if (!alpha2) 3723f2355cbSLuis R. Rodriguez return false; 373fb1fc7adSLuis R. Rodriguez /* 374fb1fc7adSLuis R. Rodriguez * Special case where regulatory domain is the 3753f2355cbSLuis R. Rodriguez * result of an intersection between two regulatory domain 376fb1fc7adSLuis R. Rodriguez * structures 377fb1fc7adSLuis R. Rodriguez */ 3781a919318SJohannes Berg return alpha2[0] == '9' && alpha2[1] == '8'; 3793f2355cbSLuis R. Rodriguez } 3803f2355cbSLuis R. Rodriguez 381a3d2eaf0SJohannes Berg static bool is_an_alpha2(const char *alpha2) 382b2e1b302SLuis R. Rodriguez { 383b2e1b302SLuis R. Rodriguez if (!alpha2) 384b2e1b302SLuis R. Rodriguez return false; 3851a919318SJohannes Berg return isalpha(alpha2[0]) && isalpha(alpha2[1]); 386b2e1b302SLuis R. Rodriguez } 387b2e1b302SLuis R. Rodriguez 388a3d2eaf0SJohannes Berg static bool alpha2_equal(const char *alpha2_x, const char *alpha2_y) 389b2e1b302SLuis R. Rodriguez { 390b2e1b302SLuis R. Rodriguez if (!alpha2_x || !alpha2_y) 391b2e1b302SLuis R. Rodriguez return false; 3921a919318SJohannes Berg return alpha2_x[0] == alpha2_y[0] && alpha2_x[1] == alpha2_y[1]; 393b2e1b302SLuis R. Rodriguez } 394b2e1b302SLuis R. Rodriguez 39569b1572bSLuis R. Rodriguez static bool regdom_changes(const char *alpha2) 396b2e1b302SLuis R. Rodriguez { 397458f4f9eSJohannes Berg const struct ieee80211_regdomain *r = get_cfg80211_regdom(); 398761cf7ecSLuis R. Rodriguez 399458f4f9eSJohannes Berg if (!r) 400b2e1b302SLuis R. Rodriguez return true; 401458f4f9eSJohannes Berg return !alpha2_equal(r->alpha2, alpha2); 402b2e1b302SLuis R. Rodriguez } 403b2e1b302SLuis R. Rodriguez 40409d989d1SLuis R. Rodriguez /* 40509d989d1SLuis R. Rodriguez * The NL80211_REGDOM_SET_BY_USER regdom alpha2 is cached, this lets 40609d989d1SLuis R. Rodriguez * you know if a valid regulatory hint with NL80211_REGDOM_SET_BY_USER 40709d989d1SLuis R. Rodriguez * has ever been issued. 40809d989d1SLuis R. Rodriguez */ 40909d989d1SLuis R. Rodriguez static bool is_user_regdom_saved(void) 41009d989d1SLuis R. Rodriguez { 41109d989d1SLuis R. Rodriguez if (user_alpha2[0] == '9' && user_alpha2[1] == '7') 41209d989d1SLuis R. Rodriguez return false; 41309d989d1SLuis R. Rodriguez 41409d989d1SLuis R. Rodriguez /* This would indicate a mistake on the design */ 4151a919318SJohannes Berg if (WARN(!is_world_regdom(user_alpha2) && !is_an_alpha2(user_alpha2), 41609d989d1SLuis R. Rodriguez "Unexpected user alpha2: %c%c\n", 4171a919318SJohannes Berg user_alpha2[0], user_alpha2[1])) 41809d989d1SLuis R. Rodriguez return false; 41909d989d1SLuis R. Rodriguez 42009d989d1SLuis R. Rodriguez return true; 42109d989d1SLuis R. Rodriguez } 42209d989d1SLuis R. Rodriguez 423e9763c3cSJohannes Berg static const struct ieee80211_regdomain * 424e9763c3cSJohannes Berg reg_copy_regd(const struct ieee80211_regdomain *src_regd) 4253b377ea9SJohn W. Linville { 4263b377ea9SJohn W. Linville struct ieee80211_regdomain *regd; 427e9763c3cSJohannes Berg int size_of_regd; 4283b377ea9SJohn W. Linville unsigned int i; 4293b377ea9SJohn W. Linville 43082f20856SJohannes Berg size_of_regd = 43182f20856SJohannes Berg sizeof(struct ieee80211_regdomain) + 43282f20856SJohannes Berg src_regd->n_reg_rules * sizeof(struct ieee80211_reg_rule); 4333b377ea9SJohn W. Linville 4343b377ea9SJohn W. Linville regd = kzalloc(size_of_regd, GFP_KERNEL); 4353b377ea9SJohn W. Linville if (!regd) 436e9763c3cSJohannes Berg return ERR_PTR(-ENOMEM); 4373b377ea9SJohn W. Linville 4383b377ea9SJohn W. Linville memcpy(regd, src_regd, sizeof(struct ieee80211_regdomain)); 4393b377ea9SJohn W. Linville 4403b377ea9SJohn W. Linville for (i = 0; i < src_regd->n_reg_rules; i++) 4413b377ea9SJohn W. Linville memcpy(®d->reg_rules[i], &src_regd->reg_rules[i], 4423b377ea9SJohn W. Linville sizeof(struct ieee80211_reg_rule)); 4433b377ea9SJohn W. Linville 444e9763c3cSJohannes Berg return regd; 4453b377ea9SJohn W. Linville } 4463b377ea9SJohn W. Linville 447c7d319e5SJohannes Berg struct reg_regdb_apply_request { 4483b377ea9SJohn W. Linville struct list_head list; 449c7d319e5SJohannes Berg const struct ieee80211_regdomain *regdom; 4503b377ea9SJohn W. Linville }; 4513b377ea9SJohn W. Linville 452c7d319e5SJohannes Berg static LIST_HEAD(reg_regdb_apply_list); 453c7d319e5SJohannes Berg static DEFINE_MUTEX(reg_regdb_apply_mutex); 4543b377ea9SJohn W. Linville 455c7d319e5SJohannes Berg static void reg_regdb_apply(struct work_struct *work) 4563b377ea9SJohn W. Linville { 457c7d319e5SJohannes Berg struct reg_regdb_apply_request *request; 458a85d0d7fSLuis R. Rodriguez 4595fe231e8SJohannes Berg rtnl_lock(); 4603b377ea9SJohn W. Linville 461c7d319e5SJohannes Berg mutex_lock(®_regdb_apply_mutex); 462c7d319e5SJohannes Berg while (!list_empty(®_regdb_apply_list)) { 463c7d319e5SJohannes Berg request = list_first_entry(®_regdb_apply_list, 464c7d319e5SJohannes Berg struct reg_regdb_apply_request, 4653b377ea9SJohn W. Linville list); 4663b377ea9SJohn W. Linville list_del(&request->list); 4673b377ea9SJohn W. Linville 468c7d319e5SJohannes Berg set_regdom(request->regdom, REGD_SOURCE_INTERNAL_DB); 4693b377ea9SJohn W. Linville kfree(request); 4703b377ea9SJohn W. Linville } 471c7d319e5SJohannes Berg mutex_unlock(®_regdb_apply_mutex); 472a85d0d7fSLuis R. Rodriguez 4735fe231e8SJohannes Berg rtnl_unlock(); 4743b377ea9SJohn W. Linville } 4753b377ea9SJohn W. Linville 476c7d319e5SJohannes Berg static DECLARE_WORK(reg_regdb_work, reg_regdb_apply); 4773b377ea9SJohn W. Linville 478007f6c5eSJohannes Berg static int reg_schedule_apply(const struct ieee80211_regdomain *regdom) 4793b377ea9SJohn W. Linville { 480c7d319e5SJohannes Berg struct reg_regdb_apply_request *request; 481c7d319e5SJohannes Berg 482c7d319e5SJohannes Berg request = kzalloc(sizeof(struct reg_regdb_apply_request), GFP_KERNEL); 483007f6c5eSJohannes Berg if (!request) { 484007f6c5eSJohannes Berg kfree(regdom); 485c7d319e5SJohannes Berg return -ENOMEM; 486c7d319e5SJohannes Berg } 4873b377ea9SJohn W. Linville 488007f6c5eSJohannes Berg request->regdom = regdom; 489007f6c5eSJohannes Berg 490c7d319e5SJohannes Berg mutex_lock(®_regdb_apply_mutex); 491c7d319e5SJohannes Berg list_add_tail(&request->list, ®_regdb_apply_list); 492c7d319e5SJohannes Berg mutex_unlock(®_regdb_apply_mutex); 4933b377ea9SJohn W. Linville 4943b377ea9SJohn W. Linville schedule_work(®_regdb_work); 495c7d319e5SJohannes Berg return 0; 4963b377ea9SJohn W. Linville } 49780007efeSLuis R. Rodriguez 498b6863036SJohannes Berg #ifdef CONFIG_CFG80211_CRDA_SUPPORT 499b6863036SJohannes Berg /* Max number of consecutive attempts to communicate with CRDA */ 500b6863036SJohannes Berg #define REG_MAX_CRDA_TIMEOUTS 10 501b6863036SJohannes Berg 502b6863036SJohannes Berg static u32 reg_crda_timeouts; 503b6863036SJohannes Berg 504b6863036SJohannes Berg static void crda_timeout_work(struct work_struct *work); 505b6863036SJohannes Berg static DECLARE_DELAYED_WORK(crda_timeout, crda_timeout_work); 506b6863036SJohannes Berg 507b6863036SJohannes Berg static void crda_timeout_work(struct work_struct *work) 508b6863036SJohannes Berg { 509c799ba6eSJohannes Berg pr_debug("Timeout while waiting for CRDA to reply, restoring regulatory settings\n"); 510b6863036SJohannes Berg rtnl_lock(); 511b6863036SJohannes Berg reg_crda_timeouts++; 512b6863036SJohannes Berg restore_regulatory_settings(true); 513b6863036SJohannes Berg rtnl_unlock(); 514b6863036SJohannes Berg } 515b6863036SJohannes Berg 516b6863036SJohannes Berg static void cancel_crda_timeout(void) 517b6863036SJohannes Berg { 518b6863036SJohannes Berg cancel_delayed_work(&crda_timeout); 519b6863036SJohannes Berg } 520b6863036SJohannes Berg 521b6863036SJohannes Berg static void cancel_crda_timeout_sync(void) 522b6863036SJohannes Berg { 523b6863036SJohannes Berg cancel_delayed_work_sync(&crda_timeout); 524b6863036SJohannes Berg } 525b6863036SJohannes Berg 526b6863036SJohannes Berg static void reset_crda_timeouts(void) 527b6863036SJohannes Berg { 528b6863036SJohannes Berg reg_crda_timeouts = 0; 529b6863036SJohannes Berg } 530b6863036SJohannes Berg 531fb1fc7adSLuis R. Rodriguez /* 532fb1fc7adSLuis R. Rodriguez * This lets us keep regulatory code which is updated on a regulatory 5331226d258SJohannes Berg * basis in userspace. 534fb1fc7adSLuis R. Rodriguez */ 535b2e1b302SLuis R. Rodriguez static int call_crda(const char *alpha2) 536b2e1b302SLuis R. Rodriguez { 5371226d258SJohannes Berg char country[12]; 5381226d258SJohannes Berg char *env[] = { country, NULL }; 539c7d319e5SJohannes Berg int ret; 5401226d258SJohannes Berg 5411226d258SJohannes Berg snprintf(country, sizeof(country), "COUNTRY=%c%c", 5421226d258SJohannes Berg alpha2[0], alpha2[1]); 5431226d258SJohannes Berg 544c37722bdSIlan peer if (reg_crda_timeouts > REG_MAX_CRDA_TIMEOUTS) { 545042ab5fcSThomas Petazzoni pr_debug("Exceeded CRDA call max attempts. Not calling CRDA\n"); 546c37722bdSIlan peer return -EINVAL; 547c37722bdSIlan peer } 548c37722bdSIlan peer 549b2e1b302SLuis R. Rodriguez if (!is_world_regdom((char *) alpha2)) 550042ab5fcSThomas Petazzoni pr_debug("Calling CRDA for country: %c%c\n", 551b2e1b302SLuis R. Rodriguez alpha2[0], alpha2[1]); 552b2e1b302SLuis R. Rodriguez else 553042ab5fcSThomas Petazzoni pr_debug("Calling CRDA to update world regulatory domain\n"); 5548318d78aSJohannes Berg 555c7d319e5SJohannes Berg ret = kobject_uevent_env(®_pdev->dev.kobj, KOBJ_CHANGE, env); 556c7d319e5SJohannes Berg if (ret) 557c7d319e5SJohannes Berg return ret; 558c7d319e5SJohannes Berg 559c7d319e5SJohannes Berg queue_delayed_work(system_power_efficient_wq, 560b6863036SJohannes Berg &crda_timeout, msecs_to_jiffies(3142)); 561c7d319e5SJohannes Berg return 0; 562b2e1b302SLuis R. Rodriguez } 563b6863036SJohannes Berg #else 564b6863036SJohannes Berg static inline void cancel_crda_timeout(void) {} 565b6863036SJohannes Berg static inline void cancel_crda_timeout_sync(void) {} 566b6863036SJohannes Berg static inline void reset_crda_timeouts(void) {} 567b6863036SJohannes Berg static inline int call_crda(const char *alpha2) 568b6863036SJohannes Berg { 569b6863036SJohannes Berg return -ENODATA; 570b6863036SJohannes Berg } 571b6863036SJohannes Berg #endif /* CONFIG_CFG80211_CRDA_SUPPORT */ 572b2e1b302SLuis R. Rodriguez 573007f6c5eSJohannes Berg /* code to directly load a firmware database through request_firmware */ 574007f6c5eSJohannes Berg static const struct fwdb_header *regdb; 575007f6c5eSJohannes Berg 576007f6c5eSJohannes Berg struct fwdb_country { 577007f6c5eSJohannes Berg u8 alpha2[2]; 578007f6c5eSJohannes Berg __be16 coll_ptr; 579007f6c5eSJohannes Berg /* this struct cannot be extended */ 580007f6c5eSJohannes Berg } __packed __aligned(4); 581007f6c5eSJohannes Berg 582007f6c5eSJohannes Berg struct fwdb_collection { 583007f6c5eSJohannes Berg u8 len; 584007f6c5eSJohannes Berg u8 n_rules; 585007f6c5eSJohannes Berg u8 dfs_region; 586007f6c5eSJohannes Berg /* no optional data yet */ 587007f6c5eSJohannes Berg /* aligned to 2, then followed by __be16 array of rule pointers */ 588007f6c5eSJohannes Berg } __packed __aligned(4); 589007f6c5eSJohannes Berg 590007f6c5eSJohannes Berg enum fwdb_flags { 591007f6c5eSJohannes Berg FWDB_FLAG_NO_OFDM = BIT(0), 592007f6c5eSJohannes Berg FWDB_FLAG_NO_OUTDOOR = BIT(1), 593007f6c5eSJohannes Berg FWDB_FLAG_DFS = BIT(2), 594007f6c5eSJohannes Berg FWDB_FLAG_NO_IR = BIT(3), 595007f6c5eSJohannes Berg FWDB_FLAG_AUTO_BW = BIT(4), 596007f6c5eSJohannes Berg }; 597007f6c5eSJohannes Berg 598007f6c5eSJohannes Berg struct fwdb_rule { 599007f6c5eSJohannes Berg u8 len; 600007f6c5eSJohannes Berg u8 flags; 601007f6c5eSJohannes Berg __be16 max_eirp; 602007f6c5eSJohannes Berg __be32 start, end, max_bw; 603007f6c5eSJohannes Berg /* start of optional data */ 604007f6c5eSJohannes Berg __be16 cac_timeout; 605007f6c5eSJohannes Berg } __packed __aligned(4); 606007f6c5eSJohannes Berg 607007f6c5eSJohannes Berg #define FWDB_MAGIC 0x52474442 608007f6c5eSJohannes Berg #define FWDB_VERSION 20 609007f6c5eSJohannes Berg 610007f6c5eSJohannes Berg struct fwdb_header { 611007f6c5eSJohannes Berg __be32 magic; 612007f6c5eSJohannes Berg __be32 version; 613007f6c5eSJohannes Berg struct fwdb_country country[]; 614007f6c5eSJohannes Berg } __packed __aligned(4); 615007f6c5eSJohannes Berg 616007f6c5eSJohannes Berg static bool valid_rule(const u8 *data, unsigned int size, u16 rule_ptr) 617007f6c5eSJohannes Berg { 618007f6c5eSJohannes Berg struct fwdb_rule *rule = (void *)(data + (rule_ptr << 2)); 619007f6c5eSJohannes Berg 620007f6c5eSJohannes Berg if ((u8 *)rule + sizeof(rule->len) > data + size) 621007f6c5eSJohannes Berg return false; 622007f6c5eSJohannes Berg 623007f6c5eSJohannes Berg /* mandatory fields */ 624007f6c5eSJohannes Berg if (rule->len < offsetofend(struct fwdb_rule, max_bw)) 625007f6c5eSJohannes Berg return false; 626007f6c5eSJohannes Berg 627007f6c5eSJohannes Berg return true; 628007f6c5eSJohannes Berg } 629007f6c5eSJohannes Berg 630007f6c5eSJohannes Berg static bool valid_country(const u8 *data, unsigned int size, 631007f6c5eSJohannes Berg const struct fwdb_country *country) 632007f6c5eSJohannes Berg { 633007f6c5eSJohannes Berg unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; 634007f6c5eSJohannes Berg struct fwdb_collection *coll = (void *)(data + ptr); 635007f6c5eSJohannes Berg __be16 *rules_ptr; 636007f6c5eSJohannes Berg unsigned int i; 637007f6c5eSJohannes Berg 638007f6c5eSJohannes Berg /* make sure we can read len/n_rules */ 639007f6c5eSJohannes Berg if ((u8 *)coll + offsetofend(typeof(*coll), n_rules) > data + size) 640007f6c5eSJohannes Berg return false; 641007f6c5eSJohannes Berg 642007f6c5eSJohannes Berg /* make sure base struct and all rules fit */ 643007f6c5eSJohannes Berg if ((u8 *)coll + ALIGN(coll->len, 2) + 644007f6c5eSJohannes Berg (coll->n_rules * 2) > data + size) 645007f6c5eSJohannes Berg return false; 646007f6c5eSJohannes Berg 647007f6c5eSJohannes Berg /* mandatory fields must exist */ 648007f6c5eSJohannes Berg if (coll->len < offsetofend(struct fwdb_collection, dfs_region)) 649007f6c5eSJohannes Berg return false; 650007f6c5eSJohannes Berg 651007f6c5eSJohannes Berg rules_ptr = (void *)((u8 *)coll + ALIGN(coll->len, 2)); 652007f6c5eSJohannes Berg 653007f6c5eSJohannes Berg for (i = 0; i < coll->n_rules; i++) { 654007f6c5eSJohannes Berg u16 rule_ptr = be16_to_cpu(rules_ptr[i]); 655007f6c5eSJohannes Berg 656007f6c5eSJohannes Berg if (!valid_rule(data, size, rule_ptr)) 657007f6c5eSJohannes Berg return false; 658007f6c5eSJohannes Berg } 659007f6c5eSJohannes Berg 660007f6c5eSJohannes Berg return true; 661007f6c5eSJohannes Berg } 662007f6c5eSJohannes Berg 66390a53e44SJohannes Berg #ifdef CONFIG_CFG80211_REQUIRE_SIGNED_REGDB 66490a53e44SJohannes Berg static struct key *builtin_regdb_keys; 66590a53e44SJohannes Berg 66690a53e44SJohannes Berg static void __init load_keys_from_buffer(const u8 *p, unsigned int buflen) 66790a53e44SJohannes Berg { 66890a53e44SJohannes Berg const u8 *end = p + buflen; 66990a53e44SJohannes Berg size_t plen; 67090a53e44SJohannes Berg key_ref_t key; 67190a53e44SJohannes Berg 67290a53e44SJohannes Berg while (p < end) { 67390a53e44SJohannes Berg /* Each cert begins with an ASN.1 SEQUENCE tag and must be more 67490a53e44SJohannes Berg * than 256 bytes in size. 67590a53e44SJohannes Berg */ 67690a53e44SJohannes Berg if (end - p < 4) 67790a53e44SJohannes Berg goto dodgy_cert; 67890a53e44SJohannes Berg if (p[0] != 0x30 && 67990a53e44SJohannes Berg p[1] != 0x82) 68090a53e44SJohannes Berg goto dodgy_cert; 68190a53e44SJohannes Berg plen = (p[2] << 8) | p[3]; 68290a53e44SJohannes Berg plen += 4; 68390a53e44SJohannes Berg if (plen > end - p) 68490a53e44SJohannes Berg goto dodgy_cert; 68590a53e44SJohannes Berg 68690a53e44SJohannes Berg key = key_create_or_update(make_key_ref(builtin_regdb_keys, 1), 68790a53e44SJohannes Berg "asymmetric", NULL, p, plen, 68890a53e44SJohannes Berg ((KEY_POS_ALL & ~KEY_POS_SETATTR) | 68990a53e44SJohannes Berg KEY_USR_VIEW | KEY_USR_READ), 69090a53e44SJohannes Berg KEY_ALLOC_NOT_IN_QUOTA | 69190a53e44SJohannes Berg KEY_ALLOC_BUILT_IN | 69290a53e44SJohannes Berg KEY_ALLOC_BYPASS_RESTRICTION); 69390a53e44SJohannes Berg if (IS_ERR(key)) { 69490a53e44SJohannes Berg pr_err("Problem loading in-kernel X.509 certificate (%ld)\n", 69590a53e44SJohannes Berg PTR_ERR(key)); 69690a53e44SJohannes Berg } else { 69790a53e44SJohannes Berg pr_notice("Loaded X.509 cert '%s'\n", 69890a53e44SJohannes Berg key_ref_to_ptr(key)->description); 69990a53e44SJohannes Berg key_ref_put(key); 70090a53e44SJohannes Berg } 70190a53e44SJohannes Berg p += plen; 70290a53e44SJohannes Berg } 70390a53e44SJohannes Berg 70490a53e44SJohannes Berg return; 70590a53e44SJohannes Berg 70690a53e44SJohannes Berg dodgy_cert: 70790a53e44SJohannes Berg pr_err("Problem parsing in-kernel X.509 certificate list\n"); 70890a53e44SJohannes Berg } 70990a53e44SJohannes Berg 71090a53e44SJohannes Berg static int __init load_builtin_regdb_keys(void) 71190a53e44SJohannes Berg { 71290a53e44SJohannes Berg builtin_regdb_keys = 71390a53e44SJohannes Berg keyring_alloc(".builtin_regdb_keys", 71490a53e44SJohannes Berg KUIDT_INIT(0), KGIDT_INIT(0), current_cred(), 71590a53e44SJohannes Berg ((KEY_POS_ALL & ~KEY_POS_SETATTR) | 71690a53e44SJohannes Berg KEY_USR_VIEW | KEY_USR_READ | KEY_USR_SEARCH), 71790a53e44SJohannes Berg KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL); 71890a53e44SJohannes Berg if (IS_ERR(builtin_regdb_keys)) 71990a53e44SJohannes Berg return PTR_ERR(builtin_regdb_keys); 72090a53e44SJohannes Berg 72190a53e44SJohannes Berg pr_notice("Loading compiled-in X.509 certificates for regulatory database\n"); 72290a53e44SJohannes Berg 72390a53e44SJohannes Berg #ifdef CONFIG_CFG80211_USE_KERNEL_REGDB_KEYS 72490a53e44SJohannes Berg load_keys_from_buffer(shipped_regdb_certs, shipped_regdb_certs_len); 72590a53e44SJohannes Berg #endif 72688230ef1SArnd Bergmann #ifdef CONFIG_CFG80211_EXTRA_REGDB_KEYDIR 72790a53e44SJohannes Berg if (CONFIG_CFG80211_EXTRA_REGDB_KEYDIR[0] != '\0') 72890a53e44SJohannes Berg load_keys_from_buffer(extra_regdb_certs, extra_regdb_certs_len); 72990a53e44SJohannes Berg #endif 73090a53e44SJohannes Berg 73190a53e44SJohannes Berg return 0; 73290a53e44SJohannes Berg } 73390a53e44SJohannes Berg 73490a53e44SJohannes Berg static bool regdb_has_valid_signature(const u8 *data, unsigned int size) 73590a53e44SJohannes Berg { 73690a53e44SJohannes Berg const struct firmware *sig; 73790a53e44SJohannes Berg bool result; 73890a53e44SJohannes Berg 73990a53e44SJohannes Berg if (request_firmware(&sig, "regulatory.db.p7s", ®_pdev->dev)) 74090a53e44SJohannes Berg return false; 74190a53e44SJohannes Berg 74290a53e44SJohannes Berg result = verify_pkcs7_signature(data, size, sig->data, sig->size, 74390a53e44SJohannes Berg builtin_regdb_keys, 74490a53e44SJohannes Berg VERIFYING_UNSPECIFIED_SIGNATURE, 74590a53e44SJohannes Berg NULL, NULL) == 0; 74690a53e44SJohannes Berg 74790a53e44SJohannes Berg release_firmware(sig); 74890a53e44SJohannes Berg 74990a53e44SJohannes Berg return result; 75090a53e44SJohannes Berg } 75190a53e44SJohannes Berg 75290a53e44SJohannes Berg static void free_regdb_keyring(void) 75390a53e44SJohannes Berg { 75490a53e44SJohannes Berg key_put(builtin_regdb_keys); 75590a53e44SJohannes Berg } 75690a53e44SJohannes Berg #else 75790a53e44SJohannes Berg static int load_builtin_regdb_keys(void) 75890a53e44SJohannes Berg { 75990a53e44SJohannes Berg return 0; 76090a53e44SJohannes Berg } 76190a53e44SJohannes Berg 76290a53e44SJohannes Berg static bool regdb_has_valid_signature(const u8 *data, unsigned int size) 76390a53e44SJohannes Berg { 76490a53e44SJohannes Berg return true; 76590a53e44SJohannes Berg } 76690a53e44SJohannes Berg 76790a53e44SJohannes Berg static void free_regdb_keyring(void) 76890a53e44SJohannes Berg { 76990a53e44SJohannes Berg } 77090a53e44SJohannes Berg #endif /* CONFIG_CFG80211_REQUIRE_SIGNED_REGDB */ 77190a53e44SJohannes Berg 772007f6c5eSJohannes Berg static bool valid_regdb(const u8 *data, unsigned int size) 773007f6c5eSJohannes Berg { 774007f6c5eSJohannes Berg const struct fwdb_header *hdr = (void *)data; 775007f6c5eSJohannes Berg const struct fwdb_country *country; 776007f6c5eSJohannes Berg 777007f6c5eSJohannes Berg if (size < sizeof(*hdr)) 778007f6c5eSJohannes Berg return false; 779007f6c5eSJohannes Berg 780007f6c5eSJohannes Berg if (hdr->magic != cpu_to_be32(FWDB_MAGIC)) 781007f6c5eSJohannes Berg return false; 782007f6c5eSJohannes Berg 783007f6c5eSJohannes Berg if (hdr->version != cpu_to_be32(FWDB_VERSION)) 784007f6c5eSJohannes Berg return false; 785007f6c5eSJohannes Berg 78690a53e44SJohannes Berg if (!regdb_has_valid_signature(data, size)) 78790a53e44SJohannes Berg return false; 78890a53e44SJohannes Berg 789007f6c5eSJohannes Berg country = &hdr->country[0]; 790007f6c5eSJohannes Berg while ((u8 *)(country + 1) <= data + size) { 791007f6c5eSJohannes Berg if (!country->coll_ptr) 792007f6c5eSJohannes Berg break; 793007f6c5eSJohannes Berg if (!valid_country(data, size, country)) 794007f6c5eSJohannes Berg return false; 795007f6c5eSJohannes Berg country++; 796007f6c5eSJohannes Berg } 797007f6c5eSJohannes Berg 798007f6c5eSJohannes Berg return true; 799007f6c5eSJohannes Berg } 800007f6c5eSJohannes Berg 801007f6c5eSJohannes Berg static int regdb_query_country(const struct fwdb_header *db, 802007f6c5eSJohannes Berg const struct fwdb_country *country) 803007f6c5eSJohannes Berg { 804007f6c5eSJohannes Berg unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2; 805007f6c5eSJohannes Berg struct fwdb_collection *coll = (void *)((u8 *)db + ptr); 806007f6c5eSJohannes Berg struct ieee80211_regdomain *regdom; 807007f6c5eSJohannes Berg unsigned int size_of_regd; 808007f6c5eSJohannes Berg unsigned int i; 809007f6c5eSJohannes Berg 810007f6c5eSJohannes Berg size_of_regd = 811007f6c5eSJohannes Berg sizeof(struct ieee80211_regdomain) + 812007f6c5eSJohannes Berg coll->n_rules * sizeof(struct ieee80211_reg_rule); 813007f6c5eSJohannes Berg 814007f6c5eSJohannes Berg regdom = kzalloc(size_of_regd, GFP_KERNEL); 815007f6c5eSJohannes Berg if (!regdom) 816007f6c5eSJohannes Berg return -ENOMEM; 817007f6c5eSJohannes Berg 818007f6c5eSJohannes Berg regdom->n_reg_rules = coll->n_rules; 819007f6c5eSJohannes Berg regdom->alpha2[0] = country->alpha2[0]; 820007f6c5eSJohannes Berg regdom->alpha2[1] = country->alpha2[1]; 821007f6c5eSJohannes Berg regdom->dfs_region = coll->dfs_region; 822007f6c5eSJohannes Berg 823007f6c5eSJohannes Berg for (i = 0; i < regdom->n_reg_rules; i++) { 824007f6c5eSJohannes Berg __be16 *rules_ptr = (void *)((u8 *)coll + ALIGN(coll->len, 2)); 825007f6c5eSJohannes Berg unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2; 826007f6c5eSJohannes Berg struct fwdb_rule *rule = (void *)((u8 *)db + rule_ptr); 827007f6c5eSJohannes Berg struct ieee80211_reg_rule *rrule = ®dom->reg_rules[i]; 828007f6c5eSJohannes Berg 829007f6c5eSJohannes Berg rrule->freq_range.start_freq_khz = be32_to_cpu(rule->start); 830007f6c5eSJohannes Berg rrule->freq_range.end_freq_khz = be32_to_cpu(rule->end); 831007f6c5eSJohannes Berg rrule->freq_range.max_bandwidth_khz = be32_to_cpu(rule->max_bw); 832007f6c5eSJohannes Berg 833007f6c5eSJohannes Berg rrule->power_rule.max_antenna_gain = 0; 834007f6c5eSJohannes Berg rrule->power_rule.max_eirp = be16_to_cpu(rule->max_eirp); 835007f6c5eSJohannes Berg 836007f6c5eSJohannes Berg rrule->flags = 0; 837007f6c5eSJohannes Berg if (rule->flags & FWDB_FLAG_NO_OFDM) 838007f6c5eSJohannes Berg rrule->flags |= NL80211_RRF_NO_OFDM; 839007f6c5eSJohannes Berg if (rule->flags & FWDB_FLAG_NO_OUTDOOR) 840007f6c5eSJohannes Berg rrule->flags |= NL80211_RRF_NO_OUTDOOR; 841007f6c5eSJohannes Berg if (rule->flags & FWDB_FLAG_DFS) 842007f6c5eSJohannes Berg rrule->flags |= NL80211_RRF_DFS; 843007f6c5eSJohannes Berg if (rule->flags & FWDB_FLAG_NO_IR) 844007f6c5eSJohannes Berg rrule->flags |= NL80211_RRF_NO_IR; 845007f6c5eSJohannes Berg if (rule->flags & FWDB_FLAG_AUTO_BW) 846007f6c5eSJohannes Berg rrule->flags |= NL80211_RRF_AUTO_BW; 847007f6c5eSJohannes Berg 848007f6c5eSJohannes Berg rrule->dfs_cac_ms = 0; 849007f6c5eSJohannes Berg 850007f6c5eSJohannes Berg /* handle optional data */ 851007f6c5eSJohannes Berg if (rule->len >= offsetofend(struct fwdb_rule, cac_timeout)) 852007f6c5eSJohannes Berg rrule->dfs_cac_ms = 853007f6c5eSJohannes Berg 1000 * be16_to_cpu(rule->cac_timeout); 854007f6c5eSJohannes Berg } 855007f6c5eSJohannes Berg 856007f6c5eSJohannes Berg return reg_schedule_apply(regdom); 857007f6c5eSJohannes Berg } 858007f6c5eSJohannes Berg 859007f6c5eSJohannes Berg static int query_regdb(const char *alpha2) 860007f6c5eSJohannes Berg { 861007f6c5eSJohannes Berg const struct fwdb_header *hdr = regdb; 862007f6c5eSJohannes Berg const struct fwdb_country *country; 863007f6c5eSJohannes Berg 8641ea4ff3eSJohannes Berg ASSERT_RTNL(); 8651ea4ff3eSJohannes Berg 866007f6c5eSJohannes Berg if (IS_ERR(regdb)) 867007f6c5eSJohannes Berg return PTR_ERR(regdb); 868007f6c5eSJohannes Berg 869007f6c5eSJohannes Berg country = &hdr->country[0]; 870007f6c5eSJohannes Berg while (country->coll_ptr) { 871007f6c5eSJohannes Berg if (alpha2_equal(alpha2, country->alpha2)) 872007f6c5eSJohannes Berg return regdb_query_country(regdb, country); 873007f6c5eSJohannes Berg country++; 874007f6c5eSJohannes Berg } 875007f6c5eSJohannes Berg 876007f6c5eSJohannes Berg return -ENODATA; 877007f6c5eSJohannes Berg } 878007f6c5eSJohannes Berg 879007f6c5eSJohannes Berg static void regdb_fw_cb(const struct firmware *fw, void *context) 880007f6c5eSJohannes Berg { 8811ea4ff3eSJohannes Berg int set_error = 0; 8821ea4ff3eSJohannes Berg bool restore = true; 883007f6c5eSJohannes Berg void *db; 884007f6c5eSJohannes Berg 885007f6c5eSJohannes Berg if (!fw) { 886007f6c5eSJohannes Berg pr_info("failed to load regulatory.db\n"); 8871ea4ff3eSJohannes Berg set_error = -ENODATA; 8881ea4ff3eSJohannes Berg } else if (!valid_regdb(fw->data, fw->size)) { 88990a53e44SJohannes Berg pr_info("loaded regulatory.db is malformed or signature is missing/invalid\n"); 8901ea4ff3eSJohannes Berg set_error = -EINVAL; 891007f6c5eSJohannes Berg } 892007f6c5eSJohannes Berg 893007f6c5eSJohannes Berg rtnl_lock(); 8941ea4ff3eSJohannes Berg if (WARN_ON(regdb && !IS_ERR(regdb))) { 8951ea4ff3eSJohannes Berg /* just restore and free new db */ 8961ea4ff3eSJohannes Berg } else if (set_error) { 8971ea4ff3eSJohannes Berg regdb = ERR_PTR(set_error); 8981ea4ff3eSJohannes Berg } else if (fw) { 8991ea4ff3eSJohannes Berg db = kmemdup(fw->data, fw->size, GFP_KERNEL); 9001ea4ff3eSJohannes Berg if (db) { 9011ea4ff3eSJohannes Berg regdb = db; 9021ea4ff3eSJohannes Berg restore = context && query_regdb(context); 9031ea4ff3eSJohannes Berg } else { 9041ea4ff3eSJohannes Berg restore = true; 9051ea4ff3eSJohannes Berg } 9061ea4ff3eSJohannes Berg } 9071ea4ff3eSJohannes Berg 9081ea4ff3eSJohannes Berg if (restore) 909007f6c5eSJohannes Berg restore_regulatory_settings(true); 9101ea4ff3eSJohannes Berg 911007f6c5eSJohannes Berg rtnl_unlock(); 9121ea4ff3eSJohannes Berg 913007f6c5eSJohannes Berg kfree(context); 9141ea4ff3eSJohannes Berg 9151ea4ff3eSJohannes Berg release_firmware(fw); 916007f6c5eSJohannes Berg } 917007f6c5eSJohannes Berg 918007f6c5eSJohannes Berg static int query_regdb_file(const char *alpha2) 919007f6c5eSJohannes Berg { 9201ea4ff3eSJohannes Berg ASSERT_RTNL(); 9211ea4ff3eSJohannes Berg 922007f6c5eSJohannes Berg if (regdb) 923007f6c5eSJohannes Berg return query_regdb(alpha2); 924007f6c5eSJohannes Berg 925007f6c5eSJohannes Berg alpha2 = kmemdup(alpha2, 2, GFP_KERNEL); 926007f6c5eSJohannes Berg if (!alpha2) 927007f6c5eSJohannes Berg return -ENOMEM; 928007f6c5eSJohannes Berg 929007f6c5eSJohannes Berg return request_firmware_nowait(THIS_MODULE, true, "regulatory.db", 930007f6c5eSJohannes Berg ®_pdev->dev, GFP_KERNEL, 931007f6c5eSJohannes Berg (void *)alpha2, regdb_fw_cb); 932007f6c5eSJohannes Berg } 933007f6c5eSJohannes Berg 9341ea4ff3eSJohannes Berg int reg_reload_regdb(void) 9351ea4ff3eSJohannes Berg { 9361ea4ff3eSJohannes Berg const struct firmware *fw; 9371ea4ff3eSJohannes Berg void *db; 9381ea4ff3eSJohannes Berg int err; 9391ea4ff3eSJohannes Berg 9401ea4ff3eSJohannes Berg err = request_firmware(&fw, "regulatory.db", ®_pdev->dev); 9411ea4ff3eSJohannes Berg if (err) 9421ea4ff3eSJohannes Berg return err; 9431ea4ff3eSJohannes Berg 9441ea4ff3eSJohannes Berg if (!valid_regdb(fw->data, fw->size)) { 9451ea4ff3eSJohannes Berg err = -ENODATA; 9461ea4ff3eSJohannes Berg goto out; 9471ea4ff3eSJohannes Berg } 9481ea4ff3eSJohannes Berg 9491ea4ff3eSJohannes Berg db = kmemdup(fw->data, fw->size, GFP_KERNEL); 9501ea4ff3eSJohannes Berg if (!db) { 9511ea4ff3eSJohannes Berg err = -ENOMEM; 9521ea4ff3eSJohannes Berg goto out; 9531ea4ff3eSJohannes Berg } 9541ea4ff3eSJohannes Berg 9551ea4ff3eSJohannes Berg rtnl_lock(); 9561ea4ff3eSJohannes Berg if (!IS_ERR_OR_NULL(regdb)) 9571ea4ff3eSJohannes Berg kfree(regdb); 9581ea4ff3eSJohannes Berg regdb = db; 9591ea4ff3eSJohannes Berg rtnl_unlock(); 9601ea4ff3eSJohannes Berg 9611ea4ff3eSJohannes Berg out: 9621ea4ff3eSJohannes Berg release_firmware(fw); 9631ea4ff3eSJohannes Berg return err; 9641ea4ff3eSJohannes Berg } 9651ea4ff3eSJohannes Berg 966cecbb069SJohannes Berg static bool reg_query_database(struct regulatory_request *request) 967fe6631ffSLuis R. Rodriguez { 968007f6c5eSJohannes Berg if (query_regdb_file(request->alpha2) == 0) 969007f6c5eSJohannes Berg return true; 970007f6c5eSJohannes Berg 971c7d319e5SJohannes Berg if (call_crda(request->alpha2) == 0) 972c7d319e5SJohannes Berg return true; 973c7d319e5SJohannes Berg 974c7d319e5SJohannes Berg return false; 975fe6631ffSLuis R. Rodriguez } 976fe6631ffSLuis R. Rodriguez 977e438768fSLuis R. Rodriguez bool reg_is_valid_request(const char *alpha2) 978b2e1b302SLuis R. Rodriguez { 979c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 98061405e97SLuis R. Rodriguez 981c492db37SJohannes Berg if (!lr || lr->processed) 982f6037d09SJohannes Berg return false; 983f6037d09SJohannes Berg 984c492db37SJohannes Berg return alpha2_equal(lr->alpha2, alpha2); 985b2e1b302SLuis R. Rodriguez } 986b2e1b302SLuis R. Rodriguez 987e3961af1SJanusz Dziedzic static const struct ieee80211_regdomain *reg_get_regdomain(struct wiphy *wiphy) 988e3961af1SJanusz Dziedzic { 989e3961af1SJanusz Dziedzic struct regulatory_request *lr = get_last_request(); 990e3961af1SJanusz Dziedzic 991e3961af1SJanusz Dziedzic /* 992e3961af1SJanusz Dziedzic * Follow the driver's regulatory domain, if present, unless a country 993e3961af1SJanusz Dziedzic * IE has been processed or a user wants to help complaince further 994e3961af1SJanusz Dziedzic */ 995e3961af1SJanusz Dziedzic if (lr->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE && 996e3961af1SJanusz Dziedzic lr->initiator != NL80211_REGDOM_SET_BY_USER && 997e3961af1SJanusz Dziedzic wiphy->regd) 998e3961af1SJanusz Dziedzic return get_wiphy_regdom(wiphy); 999e3961af1SJanusz Dziedzic 1000e3961af1SJanusz Dziedzic return get_cfg80211_regdom(); 1001e3961af1SJanusz Dziedzic } 1002e3961af1SJanusz Dziedzic 1003a6d4a534SArik Nemtsov static unsigned int 1004a6d4a534SArik Nemtsov reg_get_max_bandwidth_from_range(const struct ieee80211_regdomain *rd, 100597524820SJanusz Dziedzic const struct ieee80211_reg_rule *rule) 100697524820SJanusz Dziedzic { 100797524820SJanusz Dziedzic const struct ieee80211_freq_range *freq_range = &rule->freq_range; 100897524820SJanusz Dziedzic const struct ieee80211_freq_range *freq_range_tmp; 100997524820SJanusz Dziedzic const struct ieee80211_reg_rule *tmp; 101097524820SJanusz Dziedzic u32 start_freq, end_freq, idx, no; 101197524820SJanusz Dziedzic 101297524820SJanusz Dziedzic for (idx = 0; idx < rd->n_reg_rules; idx++) 101397524820SJanusz Dziedzic if (rule == &rd->reg_rules[idx]) 101497524820SJanusz Dziedzic break; 101597524820SJanusz Dziedzic 101697524820SJanusz Dziedzic if (idx == rd->n_reg_rules) 101797524820SJanusz Dziedzic return 0; 101897524820SJanusz Dziedzic 101997524820SJanusz Dziedzic /* get start_freq */ 102097524820SJanusz Dziedzic no = idx; 102197524820SJanusz Dziedzic 102297524820SJanusz Dziedzic while (no) { 102397524820SJanusz Dziedzic tmp = &rd->reg_rules[--no]; 102497524820SJanusz Dziedzic freq_range_tmp = &tmp->freq_range; 102597524820SJanusz Dziedzic 102697524820SJanusz Dziedzic if (freq_range_tmp->end_freq_khz < freq_range->start_freq_khz) 102797524820SJanusz Dziedzic break; 102897524820SJanusz Dziedzic 102997524820SJanusz Dziedzic freq_range = freq_range_tmp; 103097524820SJanusz Dziedzic } 103197524820SJanusz Dziedzic 103297524820SJanusz Dziedzic start_freq = freq_range->start_freq_khz; 103397524820SJanusz Dziedzic 103497524820SJanusz Dziedzic /* get end_freq */ 103597524820SJanusz Dziedzic freq_range = &rule->freq_range; 103697524820SJanusz Dziedzic no = idx; 103797524820SJanusz Dziedzic 103897524820SJanusz Dziedzic while (no < rd->n_reg_rules - 1) { 103997524820SJanusz Dziedzic tmp = &rd->reg_rules[++no]; 104097524820SJanusz Dziedzic freq_range_tmp = &tmp->freq_range; 104197524820SJanusz Dziedzic 104297524820SJanusz Dziedzic if (freq_range_tmp->start_freq_khz > freq_range->end_freq_khz) 104397524820SJanusz Dziedzic break; 104497524820SJanusz Dziedzic 104597524820SJanusz Dziedzic freq_range = freq_range_tmp; 104697524820SJanusz Dziedzic } 104797524820SJanusz Dziedzic 104897524820SJanusz Dziedzic end_freq = freq_range->end_freq_khz; 104997524820SJanusz Dziedzic 105097524820SJanusz Dziedzic return end_freq - start_freq; 105197524820SJanusz Dziedzic } 105297524820SJanusz Dziedzic 1053a6d4a534SArik Nemtsov unsigned int reg_get_max_bandwidth(const struct ieee80211_regdomain *rd, 1054a6d4a534SArik Nemtsov const struct ieee80211_reg_rule *rule) 1055a6d4a534SArik Nemtsov { 1056a6d4a534SArik Nemtsov unsigned int bw = reg_get_max_bandwidth_from_range(rd, rule); 1057a6d4a534SArik Nemtsov 1058a6d4a534SArik Nemtsov if (rule->flags & NL80211_RRF_NO_160MHZ) 1059a6d4a534SArik Nemtsov bw = min_t(unsigned int, bw, MHZ_TO_KHZ(80)); 1060a6d4a534SArik Nemtsov if (rule->flags & NL80211_RRF_NO_80MHZ) 1061a6d4a534SArik Nemtsov bw = min_t(unsigned int, bw, MHZ_TO_KHZ(40)); 1062a6d4a534SArik Nemtsov 1063a6d4a534SArik Nemtsov /* 1064a6d4a534SArik Nemtsov * HT40+/HT40- limits are handled per-channel. Only limit BW if both 1065a6d4a534SArik Nemtsov * are not allowed. 1066a6d4a534SArik Nemtsov */ 1067a6d4a534SArik Nemtsov if (rule->flags & NL80211_RRF_NO_HT40MINUS && 1068a6d4a534SArik Nemtsov rule->flags & NL80211_RRF_NO_HT40PLUS) 1069a6d4a534SArik Nemtsov bw = min_t(unsigned int, bw, MHZ_TO_KHZ(20)); 1070a6d4a534SArik Nemtsov 1071a6d4a534SArik Nemtsov return bw; 1072a6d4a534SArik Nemtsov } 1073a6d4a534SArik Nemtsov 1074b2e1b302SLuis R. Rodriguez /* Sanity check on a regulatory rule */ 1075a3d2eaf0SJohannes Berg static bool is_valid_reg_rule(const struct ieee80211_reg_rule *rule) 1076b2e1b302SLuis R. Rodriguez { 1077a3d2eaf0SJohannes Berg const struct ieee80211_freq_range *freq_range = &rule->freq_range; 1078b2e1b302SLuis R. Rodriguez u32 freq_diff; 1079b2e1b302SLuis R. Rodriguez 108091e99004SLuis R. Rodriguez if (freq_range->start_freq_khz <= 0 || freq_range->end_freq_khz <= 0) 1081b2e1b302SLuis R. Rodriguez return false; 1082b2e1b302SLuis R. Rodriguez 1083b2e1b302SLuis R. Rodriguez if (freq_range->start_freq_khz > freq_range->end_freq_khz) 1084b2e1b302SLuis R. Rodriguez return false; 1085b2e1b302SLuis R. Rodriguez 1086b2e1b302SLuis R. Rodriguez freq_diff = freq_range->end_freq_khz - freq_range->start_freq_khz; 1087b2e1b302SLuis R. Rodriguez 1088bd05f28eSRoel Kluin if (freq_range->end_freq_khz <= freq_range->start_freq_khz || 1089bd05f28eSRoel Kluin freq_range->max_bandwidth_khz > freq_diff) 1090b2e1b302SLuis R. Rodriguez return false; 1091b2e1b302SLuis R. Rodriguez 1092b2e1b302SLuis R. Rodriguez return true; 1093b2e1b302SLuis R. Rodriguez } 1094b2e1b302SLuis R. Rodriguez 1095a3d2eaf0SJohannes Berg static bool is_valid_rd(const struct ieee80211_regdomain *rd) 1096b2e1b302SLuis R. Rodriguez { 1097a3d2eaf0SJohannes Berg const struct ieee80211_reg_rule *reg_rule = NULL; 1098b2e1b302SLuis R. Rodriguez unsigned int i; 1099b2e1b302SLuis R. Rodriguez 1100b2e1b302SLuis R. Rodriguez if (!rd->n_reg_rules) 1101b2e1b302SLuis R. Rodriguez return false; 1102b2e1b302SLuis R. Rodriguez 110388dc1c3fSLuis R. Rodriguez if (WARN_ON(rd->n_reg_rules > NL80211_MAX_SUPP_REG_RULES)) 110488dc1c3fSLuis R. Rodriguez return false; 110588dc1c3fSLuis R. Rodriguez 1106b2e1b302SLuis R. Rodriguez for (i = 0; i < rd->n_reg_rules; i++) { 1107b2e1b302SLuis R. Rodriguez reg_rule = &rd->reg_rules[i]; 1108b2e1b302SLuis R. Rodriguez if (!is_valid_reg_rule(reg_rule)) 1109b2e1b302SLuis R. Rodriguez return false; 1110b2e1b302SLuis R. Rodriguez } 1111b2e1b302SLuis R. Rodriguez 1112b2e1b302SLuis R. Rodriguez return true; 1113b2e1b302SLuis R. Rodriguez } 1114b2e1b302SLuis R. Rodriguez 11150c7dc45dSLuis R. Rodriguez /** 11160c7dc45dSLuis R. Rodriguez * freq_in_rule_band - tells us if a frequency is in a frequency band 11170c7dc45dSLuis R. Rodriguez * @freq_range: frequency rule we want to query 11180c7dc45dSLuis R. Rodriguez * @freq_khz: frequency we are inquiring about 11190c7dc45dSLuis R. Rodriguez * 11200c7dc45dSLuis R. Rodriguez * This lets us know if a specific frequency rule is or is not relevant to 11210c7dc45dSLuis R. Rodriguez * a specific frequency's band. Bands are device specific and artificial 112264629b9dSVladimir Kondratiev * definitions (the "2.4 GHz band", the "5 GHz band" and the "60GHz band"), 112364629b9dSVladimir Kondratiev * however it is safe for now to assume that a frequency rule should not be 112464629b9dSVladimir Kondratiev * part of a frequency's band if the start freq or end freq are off by more 112564629b9dSVladimir Kondratiev * than 2 GHz for the 2.4 and 5 GHz bands, and by more than 10 GHz for the 112664629b9dSVladimir Kondratiev * 60 GHz band. 11270c7dc45dSLuis R. Rodriguez * This resolution can be lowered and should be considered as we add 11280c7dc45dSLuis R. Rodriguez * regulatory rule support for other "bands". 11290c7dc45dSLuis R. Rodriguez **/ 11300c7dc45dSLuis R. Rodriguez static bool freq_in_rule_band(const struct ieee80211_freq_range *freq_range, 11310c7dc45dSLuis R. Rodriguez u32 freq_khz) 11320c7dc45dSLuis R. Rodriguez { 11330c7dc45dSLuis R. Rodriguez #define ONE_GHZ_IN_KHZ 1000000 113464629b9dSVladimir Kondratiev /* 113564629b9dSVladimir Kondratiev * From 802.11ad: directional multi-gigabit (DMG): 113664629b9dSVladimir Kondratiev * Pertaining to operation in a frequency band containing a channel 113764629b9dSVladimir Kondratiev * with the Channel starting frequency above 45 GHz. 113864629b9dSVladimir Kondratiev */ 113964629b9dSVladimir Kondratiev u32 limit = freq_khz > 45 * ONE_GHZ_IN_KHZ ? 114064629b9dSVladimir Kondratiev 10 * ONE_GHZ_IN_KHZ : 2 * ONE_GHZ_IN_KHZ; 114164629b9dSVladimir Kondratiev if (abs(freq_khz - freq_range->start_freq_khz) <= limit) 11420c7dc45dSLuis R. Rodriguez return true; 114364629b9dSVladimir Kondratiev if (abs(freq_khz - freq_range->end_freq_khz) <= limit) 11440c7dc45dSLuis R. Rodriguez return true; 11450c7dc45dSLuis R. Rodriguez return false; 11460c7dc45dSLuis R. Rodriguez #undef ONE_GHZ_IN_KHZ 11470c7dc45dSLuis R. Rodriguez } 11480c7dc45dSLuis R. Rodriguez 1149fb1fc7adSLuis R. Rodriguez /* 1150adbfb058SLuis R. Rodriguez * Later on we can perhaps use the more restrictive DFS 1151adbfb058SLuis R. Rodriguez * region but we don't have information for that yet so 1152adbfb058SLuis R. Rodriguez * for now simply disallow conflicts. 1153adbfb058SLuis R. Rodriguez */ 1154adbfb058SLuis R. Rodriguez static enum nl80211_dfs_regions 1155adbfb058SLuis R. Rodriguez reg_intersect_dfs_region(const enum nl80211_dfs_regions dfs_region1, 1156adbfb058SLuis R. Rodriguez const enum nl80211_dfs_regions dfs_region2) 1157adbfb058SLuis R. Rodriguez { 1158adbfb058SLuis R. Rodriguez if (dfs_region1 != dfs_region2) 1159adbfb058SLuis R. Rodriguez return NL80211_DFS_UNSET; 1160adbfb058SLuis R. Rodriguez return dfs_region1; 1161adbfb058SLuis R. Rodriguez } 1162adbfb058SLuis R. Rodriguez 1163adbfb058SLuis R. Rodriguez /* 1164fb1fc7adSLuis R. Rodriguez * Helper for regdom_intersect(), this does the real 1165fb1fc7adSLuis R. Rodriguez * mathematical intersection fun 1166fb1fc7adSLuis R. Rodriguez */ 116797524820SJanusz Dziedzic static int reg_rules_intersect(const struct ieee80211_regdomain *rd1, 116897524820SJanusz Dziedzic const struct ieee80211_regdomain *rd2, 116997524820SJanusz Dziedzic const struct ieee80211_reg_rule *rule1, 11709c96477dSLuis R. Rodriguez const struct ieee80211_reg_rule *rule2, 11719c96477dSLuis R. Rodriguez struct ieee80211_reg_rule *intersected_rule) 11729c96477dSLuis R. Rodriguez { 11739c96477dSLuis R. Rodriguez const struct ieee80211_freq_range *freq_range1, *freq_range2; 11749c96477dSLuis R. Rodriguez struct ieee80211_freq_range *freq_range; 11759c96477dSLuis R. Rodriguez const struct ieee80211_power_rule *power_rule1, *power_rule2; 11769c96477dSLuis R. Rodriguez struct ieee80211_power_rule *power_rule; 117797524820SJanusz Dziedzic u32 freq_diff, max_bandwidth1, max_bandwidth2; 11789c96477dSLuis R. Rodriguez 11799c96477dSLuis R. Rodriguez freq_range1 = &rule1->freq_range; 11809c96477dSLuis R. Rodriguez freq_range2 = &rule2->freq_range; 11819c96477dSLuis R. Rodriguez freq_range = &intersected_rule->freq_range; 11829c96477dSLuis R. Rodriguez 11839c96477dSLuis R. Rodriguez power_rule1 = &rule1->power_rule; 11849c96477dSLuis R. Rodriguez power_rule2 = &rule2->power_rule; 11859c96477dSLuis R. Rodriguez power_rule = &intersected_rule->power_rule; 11869c96477dSLuis R. Rodriguez 11879c96477dSLuis R. Rodriguez freq_range->start_freq_khz = max(freq_range1->start_freq_khz, 11889c96477dSLuis R. Rodriguez freq_range2->start_freq_khz); 11899c96477dSLuis R. Rodriguez freq_range->end_freq_khz = min(freq_range1->end_freq_khz, 11909c96477dSLuis R. Rodriguez freq_range2->end_freq_khz); 119197524820SJanusz Dziedzic 119297524820SJanusz Dziedzic max_bandwidth1 = freq_range1->max_bandwidth_khz; 119397524820SJanusz Dziedzic max_bandwidth2 = freq_range2->max_bandwidth_khz; 119497524820SJanusz Dziedzic 1195b0dfd2eaSJanusz Dziedzic if (rule1->flags & NL80211_RRF_AUTO_BW) 119697524820SJanusz Dziedzic max_bandwidth1 = reg_get_max_bandwidth(rd1, rule1); 1197b0dfd2eaSJanusz Dziedzic if (rule2->flags & NL80211_RRF_AUTO_BW) 119897524820SJanusz Dziedzic max_bandwidth2 = reg_get_max_bandwidth(rd2, rule2); 119997524820SJanusz Dziedzic 120097524820SJanusz Dziedzic freq_range->max_bandwidth_khz = min(max_bandwidth1, max_bandwidth2); 12019c96477dSLuis R. Rodriguez 1202b0dfd2eaSJanusz Dziedzic intersected_rule->flags = rule1->flags | rule2->flags; 1203b0dfd2eaSJanusz Dziedzic 1204b0dfd2eaSJanusz Dziedzic /* 1205b0dfd2eaSJanusz Dziedzic * In case NL80211_RRF_AUTO_BW requested for both rules 1206b0dfd2eaSJanusz Dziedzic * set AUTO_BW in intersected rule also. Next we will 1207b0dfd2eaSJanusz Dziedzic * calculate BW correctly in handle_channel function. 1208b0dfd2eaSJanusz Dziedzic * In other case remove AUTO_BW flag while we calculate 1209b0dfd2eaSJanusz Dziedzic * maximum bandwidth correctly and auto calculation is 1210b0dfd2eaSJanusz Dziedzic * not required. 1211b0dfd2eaSJanusz Dziedzic */ 1212b0dfd2eaSJanusz Dziedzic if ((rule1->flags & NL80211_RRF_AUTO_BW) && 1213b0dfd2eaSJanusz Dziedzic (rule2->flags & NL80211_RRF_AUTO_BW)) 1214b0dfd2eaSJanusz Dziedzic intersected_rule->flags |= NL80211_RRF_AUTO_BW; 1215b0dfd2eaSJanusz Dziedzic else 1216b0dfd2eaSJanusz Dziedzic intersected_rule->flags &= ~NL80211_RRF_AUTO_BW; 1217b0dfd2eaSJanusz Dziedzic 12189c96477dSLuis R. Rodriguez freq_diff = freq_range->end_freq_khz - freq_range->start_freq_khz; 12199c96477dSLuis R. Rodriguez if (freq_range->max_bandwidth_khz > freq_diff) 12209c96477dSLuis R. Rodriguez freq_range->max_bandwidth_khz = freq_diff; 12219c96477dSLuis R. Rodriguez 12229c96477dSLuis R. Rodriguez power_rule->max_eirp = min(power_rule1->max_eirp, 12239c96477dSLuis R. Rodriguez power_rule2->max_eirp); 12249c96477dSLuis R. Rodriguez power_rule->max_antenna_gain = min(power_rule1->max_antenna_gain, 12259c96477dSLuis R. Rodriguez power_rule2->max_antenna_gain); 12269c96477dSLuis R. Rodriguez 1227089027e5SJanusz Dziedzic intersected_rule->dfs_cac_ms = max(rule1->dfs_cac_ms, 1228089027e5SJanusz Dziedzic rule2->dfs_cac_ms); 1229089027e5SJanusz Dziedzic 12309c96477dSLuis R. Rodriguez if (!is_valid_reg_rule(intersected_rule)) 12319c96477dSLuis R. Rodriguez return -EINVAL; 12329c96477dSLuis R. Rodriguez 12339c96477dSLuis R. Rodriguez return 0; 12349c96477dSLuis R. Rodriguez } 12359c96477dSLuis R. Rodriguez 1236a62a1aedSEliad Peller /* check whether old rule contains new rule */ 1237a62a1aedSEliad Peller static bool rule_contains(struct ieee80211_reg_rule *r1, 1238a62a1aedSEliad Peller struct ieee80211_reg_rule *r2) 1239a62a1aedSEliad Peller { 1240a62a1aedSEliad Peller /* for simplicity, currently consider only same flags */ 1241a62a1aedSEliad Peller if (r1->flags != r2->flags) 1242a62a1aedSEliad Peller return false; 1243a62a1aedSEliad Peller 1244a62a1aedSEliad Peller /* verify r1 is more restrictive */ 1245a62a1aedSEliad Peller if ((r1->power_rule.max_antenna_gain > 1246a62a1aedSEliad Peller r2->power_rule.max_antenna_gain) || 1247a62a1aedSEliad Peller r1->power_rule.max_eirp > r2->power_rule.max_eirp) 1248a62a1aedSEliad Peller return false; 1249a62a1aedSEliad Peller 1250a62a1aedSEliad Peller /* make sure r2's range is contained within r1 */ 1251a62a1aedSEliad Peller if (r1->freq_range.start_freq_khz > r2->freq_range.start_freq_khz || 1252a62a1aedSEliad Peller r1->freq_range.end_freq_khz < r2->freq_range.end_freq_khz) 1253a62a1aedSEliad Peller return false; 1254a62a1aedSEliad Peller 1255a62a1aedSEliad Peller /* and finally verify that r1.max_bw >= r2.max_bw */ 1256a62a1aedSEliad Peller if (r1->freq_range.max_bandwidth_khz < 1257a62a1aedSEliad Peller r2->freq_range.max_bandwidth_khz) 1258a62a1aedSEliad Peller return false; 1259a62a1aedSEliad Peller 1260a62a1aedSEliad Peller return true; 1261a62a1aedSEliad Peller } 1262a62a1aedSEliad Peller 1263a62a1aedSEliad Peller /* add or extend current rules. do nothing if rule is already contained */ 1264a62a1aedSEliad Peller static void add_rule(struct ieee80211_reg_rule *rule, 1265a62a1aedSEliad Peller struct ieee80211_reg_rule *reg_rules, u32 *n_rules) 1266a62a1aedSEliad Peller { 1267a62a1aedSEliad Peller struct ieee80211_reg_rule *tmp_rule; 1268a62a1aedSEliad Peller int i; 1269a62a1aedSEliad Peller 1270a62a1aedSEliad Peller for (i = 0; i < *n_rules; i++) { 1271a62a1aedSEliad Peller tmp_rule = ®_rules[i]; 1272a62a1aedSEliad Peller /* rule is already contained - do nothing */ 1273a62a1aedSEliad Peller if (rule_contains(tmp_rule, rule)) 1274a62a1aedSEliad Peller return; 1275a62a1aedSEliad Peller 1276a62a1aedSEliad Peller /* extend rule if possible */ 1277a62a1aedSEliad Peller if (rule_contains(rule, tmp_rule)) { 1278a62a1aedSEliad Peller memcpy(tmp_rule, rule, sizeof(*rule)); 1279a62a1aedSEliad Peller return; 1280a62a1aedSEliad Peller } 1281a62a1aedSEliad Peller } 1282a62a1aedSEliad Peller 1283a62a1aedSEliad Peller memcpy(®_rules[*n_rules], rule, sizeof(*rule)); 1284a62a1aedSEliad Peller (*n_rules)++; 1285a62a1aedSEliad Peller } 1286a62a1aedSEliad Peller 12879c96477dSLuis R. Rodriguez /** 12889c96477dSLuis R. Rodriguez * regdom_intersect - do the intersection between two regulatory domains 12899c96477dSLuis R. Rodriguez * @rd1: first regulatory domain 12909c96477dSLuis R. Rodriguez * @rd2: second regulatory domain 12919c96477dSLuis R. Rodriguez * 12929c96477dSLuis R. Rodriguez * Use this function to get the intersection between two regulatory domains. 12939c96477dSLuis R. Rodriguez * Once completed we will mark the alpha2 for the rd as intersected, "98", 12949c96477dSLuis R. Rodriguez * as no one single alpha2 can represent this regulatory domain. 12959c96477dSLuis R. Rodriguez * 12969c96477dSLuis R. Rodriguez * Returns a pointer to the regulatory domain structure which will hold the 12979c96477dSLuis R. Rodriguez * resulting intersection of rules between rd1 and rd2. We will 12989c96477dSLuis R. Rodriguez * kzalloc() this structure for you. 12999c96477dSLuis R. Rodriguez */ 13001a919318SJohannes Berg static struct ieee80211_regdomain * 13011a919318SJohannes Berg regdom_intersect(const struct ieee80211_regdomain *rd1, 13029c96477dSLuis R. Rodriguez const struct ieee80211_regdomain *rd2) 13039c96477dSLuis R. Rodriguez { 13049c96477dSLuis R. Rodriguez int r, size_of_regd; 13059c96477dSLuis R. Rodriguez unsigned int x, y; 1306a62a1aedSEliad Peller unsigned int num_rules = 0; 13079c96477dSLuis R. Rodriguez const struct ieee80211_reg_rule *rule1, *rule2; 1308a62a1aedSEliad Peller struct ieee80211_reg_rule intersected_rule; 13099c96477dSLuis R. Rodriguez struct ieee80211_regdomain *rd; 13109c96477dSLuis R. Rodriguez 13119c96477dSLuis R. Rodriguez if (!rd1 || !rd2) 13129c96477dSLuis R. Rodriguez return NULL; 13139c96477dSLuis R. Rodriguez 1314fb1fc7adSLuis R. Rodriguez /* 1315fb1fc7adSLuis R. Rodriguez * First we get a count of the rules we'll need, then we actually 13169c96477dSLuis R. Rodriguez * build them. This is to so we can malloc() and free() a 13179c96477dSLuis R. Rodriguez * regdomain once. The reason we use reg_rules_intersect() here 13189c96477dSLuis R. Rodriguez * is it will return -EINVAL if the rule computed makes no sense. 1319fb1fc7adSLuis R. Rodriguez * All rules that do check out OK are valid. 1320fb1fc7adSLuis R. Rodriguez */ 13219c96477dSLuis R. Rodriguez 13229c96477dSLuis R. Rodriguez for (x = 0; x < rd1->n_reg_rules; x++) { 13239c96477dSLuis R. Rodriguez rule1 = &rd1->reg_rules[x]; 13249c96477dSLuis R. Rodriguez for (y = 0; y < rd2->n_reg_rules; y++) { 13259c96477dSLuis R. Rodriguez rule2 = &rd2->reg_rules[y]; 132697524820SJanusz Dziedzic if (!reg_rules_intersect(rd1, rd2, rule1, rule2, 1327a62a1aedSEliad Peller &intersected_rule)) 13289c96477dSLuis R. Rodriguez num_rules++; 13299c96477dSLuis R. Rodriguez } 13309c96477dSLuis R. Rodriguez } 13319c96477dSLuis R. Rodriguez 13329c96477dSLuis R. Rodriguez if (!num_rules) 13339c96477dSLuis R. Rodriguez return NULL; 13349c96477dSLuis R. Rodriguez 13359c96477dSLuis R. Rodriguez size_of_regd = sizeof(struct ieee80211_regdomain) + 133682f20856SJohannes Berg num_rules * sizeof(struct ieee80211_reg_rule); 13379c96477dSLuis R. Rodriguez 13389c96477dSLuis R. Rodriguez rd = kzalloc(size_of_regd, GFP_KERNEL); 13399c96477dSLuis R. Rodriguez if (!rd) 13409c96477dSLuis R. Rodriguez return NULL; 13419c96477dSLuis R. Rodriguez 1342a62a1aedSEliad Peller for (x = 0; x < rd1->n_reg_rules; x++) { 13439c96477dSLuis R. Rodriguez rule1 = &rd1->reg_rules[x]; 1344a62a1aedSEliad Peller for (y = 0; y < rd2->n_reg_rules; y++) { 13459c96477dSLuis R. Rodriguez rule2 = &rd2->reg_rules[y]; 134697524820SJanusz Dziedzic r = reg_rules_intersect(rd1, rd2, rule1, rule2, 1347a62a1aedSEliad Peller &intersected_rule); 1348fb1fc7adSLuis R. Rodriguez /* 1349fb1fc7adSLuis R. Rodriguez * No need to memset here the intersected rule here as 1350fb1fc7adSLuis R. Rodriguez * we're not using the stack anymore 1351fb1fc7adSLuis R. Rodriguez */ 13529c96477dSLuis R. Rodriguez if (r) 13539c96477dSLuis R. Rodriguez continue; 1354a62a1aedSEliad Peller 1355a62a1aedSEliad Peller add_rule(&intersected_rule, rd->reg_rules, 1356a62a1aedSEliad Peller &rd->n_reg_rules); 13579c96477dSLuis R. Rodriguez } 13589c96477dSLuis R. Rodriguez } 13599c96477dSLuis R. Rodriguez 13609c96477dSLuis R. Rodriguez rd->alpha2[0] = '9'; 13619c96477dSLuis R. Rodriguez rd->alpha2[1] = '8'; 1362adbfb058SLuis R. Rodriguez rd->dfs_region = reg_intersect_dfs_region(rd1->dfs_region, 1363adbfb058SLuis R. Rodriguez rd2->dfs_region); 13649c96477dSLuis R. Rodriguez 13659c96477dSLuis R. Rodriguez return rd; 13669c96477dSLuis R. Rodriguez } 13679c96477dSLuis R. Rodriguez 1368fb1fc7adSLuis R. Rodriguez /* 1369fb1fc7adSLuis R. Rodriguez * XXX: add support for the rest of enum nl80211_reg_rule_flags, we may 1370fb1fc7adSLuis R. Rodriguez * want to just have the channel structure use these 1371fb1fc7adSLuis R. Rodriguez */ 1372b2e1b302SLuis R. Rodriguez static u32 map_regdom_flags(u32 rd_flags) 1373b2e1b302SLuis R. Rodriguez { 1374b2e1b302SLuis R. Rodriguez u32 channel_flags = 0; 13758fe02e16SLuis R. Rodriguez if (rd_flags & NL80211_RRF_NO_IR_ALL) 13768fe02e16SLuis R. Rodriguez channel_flags |= IEEE80211_CHAN_NO_IR; 1377b2e1b302SLuis R. Rodriguez if (rd_flags & NL80211_RRF_DFS) 1378b2e1b302SLuis R. Rodriguez channel_flags |= IEEE80211_CHAN_RADAR; 137903f6b084SSeth Forshee if (rd_flags & NL80211_RRF_NO_OFDM) 138003f6b084SSeth Forshee channel_flags |= IEEE80211_CHAN_NO_OFDM; 1381570dbde1SDavid Spinadel if (rd_flags & NL80211_RRF_NO_OUTDOOR) 1382570dbde1SDavid Spinadel channel_flags |= IEEE80211_CHAN_INDOOR_ONLY; 138306f207fcSArik Nemtsov if (rd_flags & NL80211_RRF_IR_CONCURRENT) 138406f207fcSArik Nemtsov channel_flags |= IEEE80211_CHAN_IR_CONCURRENT; 1385a6d4a534SArik Nemtsov if (rd_flags & NL80211_RRF_NO_HT40MINUS) 1386a6d4a534SArik Nemtsov channel_flags |= IEEE80211_CHAN_NO_HT40MINUS; 1387a6d4a534SArik Nemtsov if (rd_flags & NL80211_RRF_NO_HT40PLUS) 1388a6d4a534SArik Nemtsov channel_flags |= IEEE80211_CHAN_NO_HT40PLUS; 1389a6d4a534SArik Nemtsov if (rd_flags & NL80211_RRF_NO_80MHZ) 1390a6d4a534SArik Nemtsov channel_flags |= IEEE80211_CHAN_NO_80MHZ; 1391a6d4a534SArik Nemtsov if (rd_flags & NL80211_RRF_NO_160MHZ) 1392a6d4a534SArik Nemtsov channel_flags |= IEEE80211_CHAN_NO_160MHZ; 1393b2e1b302SLuis R. Rodriguez return channel_flags; 1394b2e1b302SLuis R. Rodriguez } 1395b2e1b302SLuis R. Rodriguez 1396361c9c8bSJohannes Berg static const struct ieee80211_reg_rule * 139749172874SMichal Sojka freq_reg_info_regd(u32 center_freq, 13984edd5698SMatthias May const struct ieee80211_regdomain *regd, u32 bw) 13998318d78aSJohannes Berg { 14008318d78aSJohannes Berg int i; 14010c7dc45dSLuis R. Rodriguez bool band_rule_found = false; 1402038659e7SLuis R. Rodriguez bool bw_fits = false; 1403038659e7SLuis R. Rodriguez 14043e0c3ff3SLuis R. Rodriguez if (!regd) 1405361c9c8bSJohannes Berg return ERR_PTR(-EINVAL); 1406b2e1b302SLuis R. Rodriguez 14073e0c3ff3SLuis R. Rodriguez for (i = 0; i < regd->n_reg_rules; i++) { 1408b2e1b302SLuis R. Rodriguez const struct ieee80211_reg_rule *rr; 1409b2e1b302SLuis R. Rodriguez const struct ieee80211_freq_range *fr = NULL; 1410b2e1b302SLuis R. Rodriguez 14113e0c3ff3SLuis R. Rodriguez rr = ®d->reg_rules[i]; 1412b2e1b302SLuis R. Rodriguez fr = &rr->freq_range; 14130c7dc45dSLuis R. Rodriguez 1414fb1fc7adSLuis R. Rodriguez /* 1415fb1fc7adSLuis R. Rodriguez * We only need to know if one frequency rule was 14160c7dc45dSLuis R. Rodriguez * was in center_freq's band, that's enough, so lets 1417fb1fc7adSLuis R. Rodriguez * not overwrite it once found 1418fb1fc7adSLuis R. Rodriguez */ 14190c7dc45dSLuis R. Rodriguez if (!band_rule_found) 14200c7dc45dSLuis R. Rodriguez band_rule_found = freq_in_rule_band(fr, center_freq); 14210c7dc45dSLuis R. Rodriguez 14224787cfa0SRafał Miłecki bw_fits = cfg80211_does_bw_fit_range(fr, center_freq, bw); 14230c7dc45dSLuis R. Rodriguez 1424361c9c8bSJohannes Berg if (band_rule_found && bw_fits) 1425361c9c8bSJohannes Berg return rr; 14268318d78aSJohannes Berg } 14278318d78aSJohannes Berg 14280c7dc45dSLuis R. Rodriguez if (!band_rule_found) 1429361c9c8bSJohannes Berg return ERR_PTR(-ERANGE); 14300c7dc45dSLuis R. Rodriguez 1431361c9c8bSJohannes Berg return ERR_PTR(-EINVAL); 1432b2e1b302SLuis R. Rodriguez } 1433b2e1b302SLuis R. Rodriguez 14348de1c63bSJohannes Berg static const struct ieee80211_reg_rule * 14358de1c63bSJohannes Berg __freq_reg_info(struct wiphy *wiphy, u32 center_freq, u32 min_bw) 14364edd5698SMatthias May { 14374edd5698SMatthias May const struct ieee80211_regdomain *regd = reg_get_regdomain(wiphy); 14384edd5698SMatthias May const struct ieee80211_reg_rule *reg_rule = NULL; 14394edd5698SMatthias May u32 bw; 14404edd5698SMatthias May 14414edd5698SMatthias May for (bw = MHZ_TO_KHZ(20); bw >= min_bw; bw = bw / 2) { 144249172874SMichal Sojka reg_rule = freq_reg_info_regd(center_freq, regd, bw); 14434edd5698SMatthias May if (!IS_ERR(reg_rule)) 14444edd5698SMatthias May return reg_rule; 14454edd5698SMatthias May } 14464edd5698SMatthias May 14474edd5698SMatthias May return reg_rule; 14484edd5698SMatthias May } 14494edd5698SMatthias May 1450361c9c8bSJohannes Berg const struct ieee80211_reg_rule *freq_reg_info(struct wiphy *wiphy, 1451361c9c8bSJohannes Berg u32 center_freq) 14521fa25e41SLuis R. Rodriguez { 14534edd5698SMatthias May return __freq_reg_info(wiphy, center_freq, MHZ_TO_KHZ(20)); 14541fa25e41SLuis R. Rodriguez } 14554f366c5dSJohn W. Linville EXPORT_SYMBOL(freq_reg_info); 1456b2e1b302SLuis R. Rodriguez 1457034c6d6eSLuis R. Rodriguez const char *reg_initiator_name(enum nl80211_reg_initiator initiator) 1458926a0a09SLuis R. Rodriguez { 1459926a0a09SLuis R. Rodriguez switch (initiator) { 1460926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_CORE: 1461034c6d6eSLuis R. Rodriguez return "core"; 1462926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_USER: 1463034c6d6eSLuis R. Rodriguez return "user"; 1464926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_DRIVER: 1465034c6d6eSLuis R. Rodriguez return "driver"; 1466926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_COUNTRY_IE: 1467034c6d6eSLuis R. Rodriguez return "country IE"; 1468926a0a09SLuis R. Rodriguez default: 1469926a0a09SLuis R. Rodriguez WARN_ON(1); 1470034c6d6eSLuis R. Rodriguez return "bug"; 1471926a0a09SLuis R. Rodriguez } 1472926a0a09SLuis R. Rodriguez } 1473034c6d6eSLuis R. Rodriguez EXPORT_SYMBOL(reg_initiator_name); 1474e702d3cfSLuis R. Rodriguez 14751aeb135fSMichal Sojka static uint32_t reg_rule_to_chan_bw_flags(const struct ieee80211_regdomain *regd, 14761aeb135fSMichal Sojka const struct ieee80211_reg_rule *reg_rule, 14771aeb135fSMichal Sojka const struct ieee80211_channel *chan) 14781aeb135fSMichal Sojka { 14791aeb135fSMichal Sojka const struct ieee80211_freq_range *freq_range = NULL; 14801aeb135fSMichal Sojka u32 max_bandwidth_khz, bw_flags = 0; 14811aeb135fSMichal Sojka 14821aeb135fSMichal Sojka freq_range = ®_rule->freq_range; 14831aeb135fSMichal Sojka 14841aeb135fSMichal Sojka max_bandwidth_khz = freq_range->max_bandwidth_khz; 14851aeb135fSMichal Sojka /* Check if auto calculation requested */ 14861aeb135fSMichal Sojka if (reg_rule->flags & NL80211_RRF_AUTO_BW) 14871aeb135fSMichal Sojka max_bandwidth_khz = reg_get_max_bandwidth(regd, reg_rule); 14881aeb135fSMichal Sojka 14891aeb135fSMichal Sojka /* If we get a reg_rule we can assume that at least 5Mhz fit */ 14904787cfa0SRafał Miłecki if (!cfg80211_does_bw_fit_range(freq_range, 14914787cfa0SRafał Miłecki MHZ_TO_KHZ(chan->center_freq), 14921aeb135fSMichal Sojka MHZ_TO_KHZ(10))) 14931aeb135fSMichal Sojka bw_flags |= IEEE80211_CHAN_NO_10MHZ; 14944787cfa0SRafał Miłecki if (!cfg80211_does_bw_fit_range(freq_range, 14954787cfa0SRafał Miłecki MHZ_TO_KHZ(chan->center_freq), 14961aeb135fSMichal Sojka MHZ_TO_KHZ(20))) 14971aeb135fSMichal Sojka bw_flags |= IEEE80211_CHAN_NO_20MHZ; 14981aeb135fSMichal Sojka 14991aeb135fSMichal Sojka if (max_bandwidth_khz < MHZ_TO_KHZ(10)) 15001aeb135fSMichal Sojka bw_flags |= IEEE80211_CHAN_NO_10MHZ; 15011aeb135fSMichal Sojka if (max_bandwidth_khz < MHZ_TO_KHZ(20)) 15021aeb135fSMichal Sojka bw_flags |= IEEE80211_CHAN_NO_20MHZ; 15031aeb135fSMichal Sojka if (max_bandwidth_khz < MHZ_TO_KHZ(40)) 15041aeb135fSMichal Sojka bw_flags |= IEEE80211_CHAN_NO_HT40; 15051aeb135fSMichal Sojka if (max_bandwidth_khz < MHZ_TO_KHZ(80)) 15061aeb135fSMichal Sojka bw_flags |= IEEE80211_CHAN_NO_80MHZ; 15071aeb135fSMichal Sojka if (max_bandwidth_khz < MHZ_TO_KHZ(160)) 15081aeb135fSMichal Sojka bw_flags |= IEEE80211_CHAN_NO_160MHZ; 15091aeb135fSMichal Sojka return bw_flags; 15101aeb135fSMichal Sojka } 15111aeb135fSMichal Sojka 1512e33e2241SJohannes Berg /* 1513e33e2241SJohannes Berg * Note that right now we assume the desired channel bandwidth 1514e33e2241SJohannes Berg * is always 20 MHz for each individual channel (HT40 uses 20 MHz 1515e33e2241SJohannes Berg * per channel, the primary and the extension channel). 1516038659e7SLuis R. Rodriguez */ 15177ca43d03SLuis R. Rodriguez static void handle_channel(struct wiphy *wiphy, 15187ca43d03SLuis R. Rodriguez enum nl80211_reg_initiator initiator, 1519fdc9d7b2SJohannes Berg struct ieee80211_channel *chan) 1520b2e1b302SLuis R. Rodriguez { 1521038659e7SLuis R. Rodriguez u32 flags, bw_flags = 0; 1522b2e1b302SLuis R. Rodriguez const struct ieee80211_reg_rule *reg_rule = NULL; 1523b2e1b302SLuis R. Rodriguez const struct ieee80211_power_rule *power_rule = NULL; 1524fe33eb39SLuis R. Rodriguez struct wiphy *request_wiphy = NULL; 1525c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 152697524820SJanusz Dziedzic const struct ieee80211_regdomain *regd; 1527a92a3ce7SLuis R. Rodriguez 1528c492db37SJohannes Berg request_wiphy = wiphy_idx_to_wiphy(lr->wiphy_idx); 1529a92a3ce7SLuis R. Rodriguez 1530a92a3ce7SLuis R. Rodriguez flags = chan->orig_flags; 1531b2e1b302SLuis R. Rodriguez 1532361c9c8bSJohannes Berg reg_rule = freq_reg_info(wiphy, MHZ_TO_KHZ(chan->center_freq)); 1533361c9c8bSJohannes Berg if (IS_ERR(reg_rule)) { 1534ca4ffe8fSLuis R. Rodriguez /* 1535ca4ffe8fSLuis R. Rodriguez * We will disable all channels that do not match our 153625985edcSLucas De Marchi * received regulatory rule unless the hint is coming 1537ca4ffe8fSLuis R. Rodriguez * from a Country IE and the Country IE had no information 1538ca4ffe8fSLuis R. Rodriguez * about a band. The IEEE 802.11 spec allows for an AP 1539ca4ffe8fSLuis R. Rodriguez * to send only a subset of the regulatory rules allowed, 1540ca4ffe8fSLuis R. Rodriguez * so an AP in the US that only supports 2.4 GHz may only send 1541ca4ffe8fSLuis R. Rodriguez * a country IE with information for the 2.4 GHz band 1542ca4ffe8fSLuis R. Rodriguez * while 5 GHz is still supported. 1543ca4ffe8fSLuis R. Rodriguez */ 1544ca4ffe8fSLuis R. Rodriguez if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE && 1545361c9c8bSJohannes Berg PTR_ERR(reg_rule) == -ERANGE) 15468318d78aSJohannes Berg return; 15478318d78aSJohannes Berg 1548cc493e4fSLuis R. Rodriguez if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER && 1549cc493e4fSLuis R. Rodriguez request_wiphy && request_wiphy == wiphy && 1550a2f73b6cSLuis R. Rodriguez request_wiphy->regulatory_flags & REGULATORY_STRICT_REG) { 1551c799ba6eSJohannes Berg pr_debug("Disabling freq %d MHz for good\n", 1552cc493e4fSLuis R. Rodriguez chan->center_freq); 1553cc493e4fSLuis R. Rodriguez chan->orig_flags |= IEEE80211_CHAN_DISABLED; 1554cc493e4fSLuis R. Rodriguez chan->flags = chan->orig_flags; 1555cc493e4fSLuis R. Rodriguez } else { 1556c799ba6eSJohannes Berg pr_debug("Disabling freq %d MHz\n", 1557cc493e4fSLuis R. Rodriguez chan->center_freq); 1558990de49fSJohannes Berg chan->flags |= IEEE80211_CHAN_DISABLED; 1559cc493e4fSLuis R. Rodriguez } 1560ca4ffe8fSLuis R. Rodriguez return; 1561ca4ffe8fSLuis R. Rodriguez } 1562ca4ffe8fSLuis R. Rodriguez 1563b0dfd2eaSJanusz Dziedzic regd = reg_get_regdomain(wiphy); 1564e702d3cfSLuis R. Rodriguez 1565b2e1b302SLuis R. Rodriguez power_rule = ®_rule->power_rule; 15661aeb135fSMichal Sojka bw_flags = reg_rule_to_chan_bw_flags(regd, reg_rule, chan); 1567b2e1b302SLuis R. Rodriguez 1568c492db37SJohannes Berg if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER && 1569806a9e39SLuis R. Rodriguez request_wiphy && request_wiphy == wiphy && 1570a2f73b6cSLuis R. Rodriguez request_wiphy->regulatory_flags & REGULATORY_STRICT_REG) { 1571fb1fc7adSLuis R. Rodriguez /* 157225985edcSLucas De Marchi * This guarantees the driver's requested regulatory domain 1573f976376dSLuis R. Rodriguez * will always be used as a base for further regulatory 1574fb1fc7adSLuis R. Rodriguez * settings 1575fb1fc7adSLuis R. Rodriguez */ 1576f976376dSLuis R. Rodriguez chan->flags = chan->orig_flags = 1577038659e7SLuis R. Rodriguez map_regdom_flags(reg_rule->flags) | bw_flags; 1578f976376dSLuis R. Rodriguez chan->max_antenna_gain = chan->orig_mag = 1579f976376dSLuis R. Rodriguez (int) MBI_TO_DBI(power_rule->max_antenna_gain); 1580279f0f55SFelix Fietkau chan->max_reg_power = chan->max_power = chan->orig_mpwr = 1581f976376dSLuis R. Rodriguez (int) MBM_TO_DBM(power_rule->max_eirp); 15824f267c11SJanusz Dziedzic 15834f267c11SJanusz Dziedzic if (chan->flags & IEEE80211_CHAN_RADAR) { 15844f267c11SJanusz Dziedzic chan->dfs_cac_ms = IEEE80211_DFS_MIN_CAC_TIME_MS; 15854f267c11SJanusz Dziedzic if (reg_rule->dfs_cac_ms) 15864f267c11SJanusz Dziedzic chan->dfs_cac_ms = reg_rule->dfs_cac_ms; 15874f267c11SJanusz Dziedzic } 15884f267c11SJanusz Dziedzic 1589f976376dSLuis R. Rodriguez return; 1590f976376dSLuis R. Rodriguez } 1591f976376dSLuis R. Rodriguez 159204f39047SSimon Wunderlich chan->dfs_state = NL80211_DFS_USABLE; 159304f39047SSimon Wunderlich chan->dfs_state_entered = jiffies; 159404f39047SSimon Wunderlich 1595aa3d7eefSRajkumar Manoharan chan->beacon_found = false; 1596038659e7SLuis R. Rodriguez chan->flags = flags | bw_flags | map_regdom_flags(reg_rule->flags); 15971a919318SJohannes Berg chan->max_antenna_gain = 15981a919318SJohannes Berg min_t(int, chan->orig_mag, 15991a919318SJohannes Berg MBI_TO_DBI(power_rule->max_antenna_gain)); 1600eccc068eSHong Wu chan->max_reg_power = (int) MBM_TO_DBM(power_rule->max_eirp); 1601089027e5SJanusz Dziedzic 1602089027e5SJanusz Dziedzic if (chan->flags & IEEE80211_CHAN_RADAR) { 1603089027e5SJanusz Dziedzic if (reg_rule->dfs_cac_ms) 1604089027e5SJanusz Dziedzic chan->dfs_cac_ms = reg_rule->dfs_cac_ms; 1605089027e5SJanusz Dziedzic else 1606089027e5SJanusz Dziedzic chan->dfs_cac_ms = IEEE80211_DFS_MIN_CAC_TIME_MS; 1607089027e5SJanusz Dziedzic } 1608089027e5SJanusz Dziedzic 16095e31fc08SStanislaw Gruszka if (chan->orig_mpwr) { 16105e31fc08SStanislaw Gruszka /* 1611a09a85a0SLuis R. Rodriguez * Devices that use REGULATORY_COUNTRY_IE_FOLLOW_POWER 1612a09a85a0SLuis R. Rodriguez * will always follow the passed country IE power settings. 16135e31fc08SStanislaw Gruszka */ 16145e31fc08SStanislaw Gruszka if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE && 1615a09a85a0SLuis R. Rodriguez wiphy->regulatory_flags & REGULATORY_COUNTRY_IE_FOLLOW_POWER) 16165e31fc08SStanislaw Gruszka chan->max_power = chan->max_reg_power; 16175e31fc08SStanislaw Gruszka else 16185e31fc08SStanislaw Gruszka chan->max_power = min(chan->orig_mpwr, 16195e31fc08SStanislaw Gruszka chan->max_reg_power); 16205e31fc08SStanislaw Gruszka } else 16215e31fc08SStanislaw Gruszka chan->max_power = chan->max_reg_power; 16228318d78aSJohannes Berg } 16238318d78aSJohannes Berg 16247ca43d03SLuis R. Rodriguez static void handle_band(struct wiphy *wiphy, 1625fdc9d7b2SJohannes Berg enum nl80211_reg_initiator initiator, 1626fdc9d7b2SJohannes Berg struct ieee80211_supported_band *sband) 16278318d78aSJohannes Berg { 1628a92a3ce7SLuis R. Rodriguez unsigned int i; 1629a92a3ce7SLuis R. Rodriguez 1630fdc9d7b2SJohannes Berg if (!sband) 1631fdc9d7b2SJohannes Berg return; 16328318d78aSJohannes Berg 16338318d78aSJohannes Berg for (i = 0; i < sband->n_channels; i++) 1634fdc9d7b2SJohannes Berg handle_channel(wiphy, initiator, &sband->channels[i]); 16358318d78aSJohannes Berg } 16368318d78aSJohannes Berg 163757b5ce07SLuis R. Rodriguez static bool reg_request_cell_base(struct regulatory_request *request) 163857b5ce07SLuis R. Rodriguez { 163957b5ce07SLuis R. Rodriguez if (request->initiator != NL80211_REGDOM_SET_BY_USER) 164057b5ce07SLuis R. Rodriguez return false; 16411a919318SJohannes Berg return request->user_reg_hint_type == NL80211_USER_REG_HINT_CELL_BASE; 164257b5ce07SLuis R. Rodriguez } 164357b5ce07SLuis R. Rodriguez 164457b5ce07SLuis R. Rodriguez bool reg_last_request_cell_base(void) 164557b5ce07SLuis R. Rodriguez { 164638fd2143SJohannes Berg return reg_request_cell_base(get_last_request()); 164757b5ce07SLuis R. Rodriguez } 164857b5ce07SLuis R. Rodriguez 164994fc661fSIlan Peer #ifdef CONFIG_CFG80211_REG_CELLULAR_HINTS 165057b5ce07SLuis R. Rodriguez /* Core specific check */ 16512f92212bSJohannes Berg static enum reg_request_treatment 16522f92212bSJohannes Berg reg_ignore_cell_hint(struct regulatory_request *pending_request) 165357b5ce07SLuis R. Rodriguez { 1654c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 165557b5ce07SLuis R. Rodriguez 165657b5ce07SLuis R. Rodriguez if (!reg_num_devs_support_basehint) 16572f92212bSJohannes Berg return REG_REQ_IGNORE; 165857b5ce07SLuis R. Rodriguez 1659c492db37SJohannes Berg if (reg_request_cell_base(lr) && 16601a919318SJohannes Berg !regdom_changes(pending_request->alpha2)) 16612f92212bSJohannes Berg return REG_REQ_ALREADY_SET; 16621a919318SJohannes Berg 16632f92212bSJohannes Berg return REG_REQ_OK; 166457b5ce07SLuis R. Rodriguez } 166557b5ce07SLuis R. Rodriguez 166657b5ce07SLuis R. Rodriguez /* Device specific check */ 166757b5ce07SLuis R. Rodriguez static bool reg_dev_ignore_cell_hint(struct wiphy *wiphy) 166857b5ce07SLuis R. Rodriguez { 16691a919318SJohannes Berg return !(wiphy->features & NL80211_FEATURE_CELL_BASE_REG_HINTS); 167057b5ce07SLuis R. Rodriguez } 167157b5ce07SLuis R. Rodriguez #else 1672a515de66SJohannes Berg static enum reg_request_treatment 1673a515de66SJohannes Berg reg_ignore_cell_hint(struct regulatory_request *pending_request) 167457b5ce07SLuis R. Rodriguez { 16752f92212bSJohannes Berg return REG_REQ_IGNORE; 167657b5ce07SLuis R. Rodriguez } 16771a919318SJohannes Berg 16781a919318SJohannes Berg static bool reg_dev_ignore_cell_hint(struct wiphy *wiphy) 167957b5ce07SLuis R. Rodriguez { 168057b5ce07SLuis R. Rodriguez return true; 168157b5ce07SLuis R. Rodriguez } 168257b5ce07SLuis R. Rodriguez #endif 168357b5ce07SLuis R. Rodriguez 1684fa1fb9cbSLuis R. Rodriguez static bool wiphy_strict_alpha2_regd(struct wiphy *wiphy) 1685fa1fb9cbSLuis R. Rodriguez { 1686a2f73b6cSLuis R. Rodriguez if (wiphy->regulatory_flags & REGULATORY_STRICT_REG && 1687a2f73b6cSLuis R. Rodriguez !(wiphy->regulatory_flags & REGULATORY_CUSTOM_REG)) 1688fa1fb9cbSLuis R. Rodriguez return true; 1689fa1fb9cbSLuis R. Rodriguez return false; 1690fa1fb9cbSLuis R. Rodriguez } 169157b5ce07SLuis R. Rodriguez 16927db90f4aSLuis R. Rodriguez static bool ignore_reg_update(struct wiphy *wiphy, 16937db90f4aSLuis R. Rodriguez enum nl80211_reg_initiator initiator) 169414b9815aSLuis R. Rodriguez { 1695c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 1696c492db37SJohannes Berg 1697b0d7aa59SJonathan Doron if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED) 1698b0d7aa59SJonathan Doron return true; 1699b0d7aa59SJonathan Doron 1700c492db37SJohannes Berg if (!lr) { 1701c799ba6eSJohannes Berg pr_debug("Ignoring regulatory request set by %s since last_request is not set\n", 1702926a0a09SLuis R. Rodriguez reg_initiator_name(initiator)); 170314b9815aSLuis R. Rodriguez return true; 1704926a0a09SLuis R. Rodriguez } 1705926a0a09SLuis R. Rodriguez 17067db90f4aSLuis R. Rodriguez if (initiator == NL80211_REGDOM_SET_BY_CORE && 1707a2f73b6cSLuis R. Rodriguez wiphy->regulatory_flags & REGULATORY_CUSTOM_REG) { 1708c799ba6eSJohannes Berg pr_debug("Ignoring regulatory request set by %s since the driver uses its own custom regulatory domain\n", 1709926a0a09SLuis R. Rodriguez reg_initiator_name(initiator)); 171014b9815aSLuis R. Rodriguez return true; 1711926a0a09SLuis R. Rodriguez } 1712926a0a09SLuis R. Rodriguez 1713fb1fc7adSLuis R. Rodriguez /* 1714fb1fc7adSLuis R. Rodriguez * wiphy->regd will be set once the device has its own 1715fb1fc7adSLuis R. Rodriguez * desired regulatory domain set 1716fb1fc7adSLuis R. Rodriguez */ 1717fa1fb9cbSLuis R. Rodriguez if (wiphy_strict_alpha2_regd(wiphy) && !wiphy->regd && 1718749b527bSLuis R. Rodriguez initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE && 1719c492db37SJohannes Berg !is_world_regdom(lr->alpha2)) { 1720c799ba6eSJohannes Berg pr_debug("Ignoring regulatory request set by %s since the driver requires its own regulatory domain to be set first\n", 1721926a0a09SLuis R. Rodriguez reg_initiator_name(initiator)); 172214b9815aSLuis R. Rodriguez return true; 1723926a0a09SLuis R. Rodriguez } 1724926a0a09SLuis R. Rodriguez 1725c492db37SJohannes Berg if (reg_request_cell_base(lr)) 172657b5ce07SLuis R. Rodriguez return reg_dev_ignore_cell_hint(wiphy); 172757b5ce07SLuis R. Rodriguez 172814b9815aSLuis R. Rodriguez return false; 172914b9815aSLuis R. Rodriguez } 173014b9815aSLuis R. Rodriguez 17313195e489SLuis R. Rodriguez static bool reg_is_world_roaming(struct wiphy *wiphy) 17323195e489SLuis R. Rodriguez { 17333195e489SLuis R. Rodriguez const struct ieee80211_regdomain *cr = get_cfg80211_regdom(); 17343195e489SLuis R. Rodriguez const struct ieee80211_regdomain *wr = get_wiphy_regdom(wiphy); 17353195e489SLuis R. Rodriguez struct regulatory_request *lr = get_last_request(); 17363195e489SLuis R. Rodriguez 17373195e489SLuis R. Rodriguez if (is_world_regdom(cr->alpha2) || (wr && is_world_regdom(wr->alpha2))) 17383195e489SLuis R. Rodriguez return true; 17393195e489SLuis R. Rodriguez 17403195e489SLuis R. Rodriguez if (lr && lr->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE && 1741a2f73b6cSLuis R. Rodriguez wiphy->regulatory_flags & REGULATORY_CUSTOM_REG) 17423195e489SLuis R. Rodriguez return true; 17433195e489SLuis R. Rodriguez 17443195e489SLuis R. Rodriguez return false; 17453195e489SLuis R. Rodriguez } 17463195e489SLuis R. Rodriguez 17471a919318SJohannes Berg static void handle_reg_beacon(struct wiphy *wiphy, unsigned int chan_idx, 1748e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon) 1749e38f8a7aSLuis R. Rodriguez { 1750e38f8a7aSLuis R. Rodriguez struct ieee80211_supported_band *sband; 1751e38f8a7aSLuis R. Rodriguez struct ieee80211_channel *chan; 17526bad8766SLuis R. Rodriguez bool channel_changed = false; 17536bad8766SLuis R. Rodriguez struct ieee80211_channel chan_before; 1754e38f8a7aSLuis R. Rodriguez 1755e38f8a7aSLuis R. Rodriguez sband = wiphy->bands[reg_beacon->chan.band]; 1756e38f8a7aSLuis R. Rodriguez chan = &sband->channels[chan_idx]; 1757e38f8a7aSLuis R. Rodriguez 1758e38f8a7aSLuis R. Rodriguez if (likely(chan->center_freq != reg_beacon->chan.center_freq)) 1759e38f8a7aSLuis R. Rodriguez return; 1760e38f8a7aSLuis R. Rodriguez 17616bad8766SLuis R. Rodriguez if (chan->beacon_found) 17626bad8766SLuis R. Rodriguez return; 17636bad8766SLuis R. Rodriguez 17646bad8766SLuis R. Rodriguez chan->beacon_found = true; 17656bad8766SLuis R. Rodriguez 17660f500a5fSLuis R. Rodriguez if (!reg_is_world_roaming(wiphy)) 17670f500a5fSLuis R. Rodriguez return; 17680f500a5fSLuis R. Rodriguez 1769a2f73b6cSLuis R. Rodriguez if (wiphy->regulatory_flags & REGULATORY_DISABLE_BEACON_HINTS) 177037184244SLuis R. Rodriguez return; 177137184244SLuis R. Rodriguez 17726bad8766SLuis R. Rodriguez chan_before.center_freq = chan->center_freq; 17736bad8766SLuis R. Rodriguez chan_before.flags = chan->flags; 17746bad8766SLuis R. Rodriguez 17758fe02e16SLuis R. Rodriguez if (chan->flags & IEEE80211_CHAN_NO_IR) { 17768fe02e16SLuis R. Rodriguez chan->flags &= ~IEEE80211_CHAN_NO_IR; 17776bad8766SLuis R. Rodriguez channel_changed = true; 1778e38f8a7aSLuis R. Rodriguez } 1779e38f8a7aSLuis R. Rodriguez 17806bad8766SLuis R. Rodriguez if (channel_changed) 17816bad8766SLuis R. Rodriguez nl80211_send_beacon_hint_event(wiphy, &chan_before, chan); 1782e38f8a7aSLuis R. Rodriguez } 1783e38f8a7aSLuis R. Rodriguez 1784e38f8a7aSLuis R. Rodriguez /* 1785e38f8a7aSLuis R. Rodriguez * Called when a scan on a wiphy finds a beacon on 1786e38f8a7aSLuis R. Rodriguez * new channel 1787e38f8a7aSLuis R. Rodriguez */ 1788e38f8a7aSLuis R. Rodriguez static void wiphy_update_new_beacon(struct wiphy *wiphy, 1789e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon) 1790e38f8a7aSLuis R. Rodriguez { 1791e38f8a7aSLuis R. Rodriguez unsigned int i; 1792e38f8a7aSLuis R. Rodriguez struct ieee80211_supported_band *sband; 1793e38f8a7aSLuis R. Rodriguez 1794e38f8a7aSLuis R. Rodriguez if (!wiphy->bands[reg_beacon->chan.band]) 1795e38f8a7aSLuis R. Rodriguez return; 1796e38f8a7aSLuis R. Rodriguez 1797e38f8a7aSLuis R. Rodriguez sband = wiphy->bands[reg_beacon->chan.band]; 1798e38f8a7aSLuis R. Rodriguez 1799e38f8a7aSLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 1800e38f8a7aSLuis R. Rodriguez handle_reg_beacon(wiphy, i, reg_beacon); 1801e38f8a7aSLuis R. Rodriguez } 1802e38f8a7aSLuis R. Rodriguez 1803e38f8a7aSLuis R. Rodriguez /* 1804e38f8a7aSLuis R. Rodriguez * Called upon reg changes or a new wiphy is added 1805e38f8a7aSLuis R. Rodriguez */ 1806e38f8a7aSLuis R. Rodriguez static void wiphy_update_beacon_reg(struct wiphy *wiphy) 1807e38f8a7aSLuis R. Rodriguez { 1808e38f8a7aSLuis R. Rodriguez unsigned int i; 1809e38f8a7aSLuis R. Rodriguez struct ieee80211_supported_band *sband; 1810e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon; 1811e38f8a7aSLuis R. Rodriguez 1812e38f8a7aSLuis R. Rodriguez list_for_each_entry(reg_beacon, ®_beacon_list, list) { 1813e38f8a7aSLuis R. Rodriguez if (!wiphy->bands[reg_beacon->chan.band]) 1814e38f8a7aSLuis R. Rodriguez continue; 1815e38f8a7aSLuis R. Rodriguez sband = wiphy->bands[reg_beacon->chan.band]; 1816e38f8a7aSLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 1817e38f8a7aSLuis R. Rodriguez handle_reg_beacon(wiphy, i, reg_beacon); 1818e38f8a7aSLuis R. Rodriguez } 1819e38f8a7aSLuis R. Rodriguez } 1820e38f8a7aSLuis R. Rodriguez 1821e38f8a7aSLuis R. Rodriguez /* Reap the advantages of previously found beacons */ 1822e38f8a7aSLuis R. Rodriguez static void reg_process_beacons(struct wiphy *wiphy) 1823e38f8a7aSLuis R. Rodriguez { 1824b1ed8dddSLuis R. Rodriguez /* 1825b1ed8dddSLuis R. Rodriguez * Means we are just firing up cfg80211, so no beacons would 1826b1ed8dddSLuis R. Rodriguez * have been processed yet. 1827b1ed8dddSLuis R. Rodriguez */ 1828b1ed8dddSLuis R. Rodriguez if (!last_request) 1829b1ed8dddSLuis R. Rodriguez return; 1830e38f8a7aSLuis R. Rodriguez wiphy_update_beacon_reg(wiphy); 1831e38f8a7aSLuis R. Rodriguez } 1832e38f8a7aSLuis R. Rodriguez 18331a919318SJohannes Berg static bool is_ht40_allowed(struct ieee80211_channel *chan) 1834038659e7SLuis R. Rodriguez { 1835038659e7SLuis R. Rodriguez if (!chan) 1836038659e7SLuis R. Rodriguez return false; 18371a919318SJohannes Berg if (chan->flags & IEEE80211_CHAN_DISABLED) 18381a919318SJohannes Berg return false; 18391a919318SJohannes Berg /* This would happen when regulatory rules disallow HT40 completely */ 184055b183adSFelix Fietkau if ((chan->flags & IEEE80211_CHAN_NO_HT40) == IEEE80211_CHAN_NO_HT40) 184155b183adSFelix Fietkau return false; 184255b183adSFelix Fietkau return true; 1843038659e7SLuis R. Rodriguez } 1844038659e7SLuis R. Rodriguez 1845038659e7SLuis R. Rodriguez static void reg_process_ht_flags_channel(struct wiphy *wiphy, 1846fdc9d7b2SJohannes Berg struct ieee80211_channel *channel) 1847038659e7SLuis R. Rodriguez { 1848fdc9d7b2SJohannes Berg struct ieee80211_supported_band *sband = wiphy->bands[channel->band]; 1849038659e7SLuis R. Rodriguez struct ieee80211_channel *channel_before = NULL, *channel_after = NULL; 18504e0854a7SEmmanuel Grumbach const struct ieee80211_regdomain *regd; 1851038659e7SLuis R. Rodriguez unsigned int i; 18524e0854a7SEmmanuel Grumbach u32 flags; 1853038659e7SLuis R. Rodriguez 18541a919318SJohannes Berg if (!is_ht40_allowed(channel)) { 1855038659e7SLuis R. Rodriguez channel->flags |= IEEE80211_CHAN_NO_HT40; 1856038659e7SLuis R. Rodriguez return; 1857038659e7SLuis R. Rodriguez } 1858038659e7SLuis R. Rodriguez 1859038659e7SLuis R. Rodriguez /* 1860038659e7SLuis R. Rodriguez * We need to ensure the extension channels exist to 1861038659e7SLuis R. Rodriguez * be able to use HT40- or HT40+, this finds them (or not) 1862038659e7SLuis R. Rodriguez */ 1863038659e7SLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) { 1864038659e7SLuis R. Rodriguez struct ieee80211_channel *c = &sband->channels[i]; 18651a919318SJohannes Berg 1866038659e7SLuis R. Rodriguez if (c->center_freq == (channel->center_freq - 20)) 1867038659e7SLuis R. Rodriguez channel_before = c; 1868038659e7SLuis R. Rodriguez if (c->center_freq == (channel->center_freq + 20)) 1869038659e7SLuis R. Rodriguez channel_after = c; 1870038659e7SLuis R. Rodriguez } 1871038659e7SLuis R. Rodriguez 18724e0854a7SEmmanuel Grumbach flags = 0; 18734e0854a7SEmmanuel Grumbach regd = get_wiphy_regdom(wiphy); 18744e0854a7SEmmanuel Grumbach if (regd) { 18754e0854a7SEmmanuel Grumbach const struct ieee80211_reg_rule *reg_rule = 18764e0854a7SEmmanuel Grumbach freq_reg_info_regd(MHZ_TO_KHZ(channel->center_freq), 18774e0854a7SEmmanuel Grumbach regd, MHZ_TO_KHZ(20)); 18784e0854a7SEmmanuel Grumbach 18794e0854a7SEmmanuel Grumbach if (!IS_ERR(reg_rule)) 18804e0854a7SEmmanuel Grumbach flags = reg_rule->flags; 18814e0854a7SEmmanuel Grumbach } 18824e0854a7SEmmanuel Grumbach 1883038659e7SLuis R. Rodriguez /* 1884038659e7SLuis R. Rodriguez * Please note that this assumes target bandwidth is 20 MHz, 1885038659e7SLuis R. Rodriguez * if that ever changes we also need to change the below logic 1886038659e7SLuis R. Rodriguez * to include that as well. 1887038659e7SLuis R. Rodriguez */ 18884e0854a7SEmmanuel Grumbach if (!is_ht40_allowed(channel_before) || 18894e0854a7SEmmanuel Grumbach flags & NL80211_RRF_NO_HT40MINUS) 1890689da1b3SLuis R. Rodriguez channel->flags |= IEEE80211_CHAN_NO_HT40MINUS; 1891038659e7SLuis R. Rodriguez else 1892689da1b3SLuis R. Rodriguez channel->flags &= ~IEEE80211_CHAN_NO_HT40MINUS; 1893038659e7SLuis R. Rodriguez 18944e0854a7SEmmanuel Grumbach if (!is_ht40_allowed(channel_after) || 18954e0854a7SEmmanuel Grumbach flags & NL80211_RRF_NO_HT40PLUS) 1896689da1b3SLuis R. Rodriguez channel->flags |= IEEE80211_CHAN_NO_HT40PLUS; 1897038659e7SLuis R. Rodriguez else 1898689da1b3SLuis R. Rodriguez channel->flags &= ~IEEE80211_CHAN_NO_HT40PLUS; 1899038659e7SLuis R. Rodriguez } 1900038659e7SLuis R. Rodriguez 1901038659e7SLuis R. Rodriguez static void reg_process_ht_flags_band(struct wiphy *wiphy, 1902fdc9d7b2SJohannes Berg struct ieee80211_supported_band *sband) 1903038659e7SLuis R. Rodriguez { 1904038659e7SLuis R. Rodriguez unsigned int i; 1905038659e7SLuis R. Rodriguez 1906fdc9d7b2SJohannes Berg if (!sband) 1907fdc9d7b2SJohannes Berg return; 1908038659e7SLuis R. Rodriguez 1909038659e7SLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 1910fdc9d7b2SJohannes Berg reg_process_ht_flags_channel(wiphy, &sband->channels[i]); 1911038659e7SLuis R. Rodriguez } 1912038659e7SLuis R. Rodriguez 1913038659e7SLuis R. Rodriguez static void reg_process_ht_flags(struct wiphy *wiphy) 1914038659e7SLuis R. Rodriguez { 191557fbcce3SJohannes Berg enum nl80211_band band; 1916038659e7SLuis R. Rodriguez 1917038659e7SLuis R. Rodriguez if (!wiphy) 1918038659e7SLuis R. Rodriguez return; 1919038659e7SLuis R. Rodriguez 192057fbcce3SJohannes Berg for (band = 0; band < NUM_NL80211_BANDS; band++) 1921fdc9d7b2SJohannes Berg reg_process_ht_flags_band(wiphy, wiphy->bands[band]); 1922038659e7SLuis R. Rodriguez } 1923038659e7SLuis R. Rodriguez 19240e3802dbSLuis R. Rodriguez static void reg_call_notifier(struct wiphy *wiphy, 19250e3802dbSLuis R. Rodriguez struct regulatory_request *request) 19260e3802dbSLuis R. Rodriguez { 19270e3802dbSLuis R. Rodriguez if (wiphy->reg_notifier) 19280e3802dbSLuis R. Rodriguez wiphy->reg_notifier(wiphy, request); 19290e3802dbSLuis R. Rodriguez } 19300e3802dbSLuis R. Rodriguez 1931ad932f04SArik Nemtsov static bool reg_wdev_chan_valid(struct wiphy *wiphy, struct wireless_dev *wdev) 1932ad932f04SArik Nemtsov { 1933ad932f04SArik Nemtsov struct cfg80211_chan_def chandef; 1934ad932f04SArik Nemtsov struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); 193520658702SArik Nemtsov enum nl80211_iftype iftype; 1936ad932f04SArik Nemtsov 1937ad932f04SArik Nemtsov wdev_lock(wdev); 193820658702SArik Nemtsov iftype = wdev->iftype; 1939ad932f04SArik Nemtsov 194020658702SArik Nemtsov /* make sure the interface is active */ 1941ad932f04SArik Nemtsov if (!wdev->netdev || !netif_running(wdev->netdev)) 194220658702SArik Nemtsov goto wdev_inactive_unlock; 1943ad932f04SArik Nemtsov 194420658702SArik Nemtsov switch (iftype) { 1945ad932f04SArik Nemtsov case NL80211_IFTYPE_AP: 1946ad932f04SArik Nemtsov case NL80211_IFTYPE_P2P_GO: 1947ad932f04SArik Nemtsov if (!wdev->beacon_interval) 194820658702SArik Nemtsov goto wdev_inactive_unlock; 194920658702SArik Nemtsov chandef = wdev->chandef; 1950ad932f04SArik Nemtsov break; 1951185076d6SArik Nemtsov case NL80211_IFTYPE_ADHOC: 1952185076d6SArik Nemtsov if (!wdev->ssid_len) 195320658702SArik Nemtsov goto wdev_inactive_unlock; 195420658702SArik Nemtsov chandef = wdev->chandef; 1955185076d6SArik Nemtsov break; 1956ad932f04SArik Nemtsov case NL80211_IFTYPE_STATION: 1957ad932f04SArik Nemtsov case NL80211_IFTYPE_P2P_CLIENT: 1958ad932f04SArik Nemtsov if (!wdev->current_bss || 1959ad932f04SArik Nemtsov !wdev->current_bss->pub.channel) 196020658702SArik Nemtsov goto wdev_inactive_unlock; 1961ad932f04SArik Nemtsov 196220658702SArik Nemtsov if (!rdev->ops->get_channel || 196320658702SArik Nemtsov rdev_get_channel(rdev, wdev, &chandef)) 196420658702SArik Nemtsov cfg80211_chandef_create(&chandef, 196520658702SArik Nemtsov wdev->current_bss->pub.channel, 196620658702SArik Nemtsov NL80211_CHAN_NO_HT); 1967ad932f04SArik Nemtsov break; 1968ad932f04SArik Nemtsov case NL80211_IFTYPE_MONITOR: 1969ad932f04SArik Nemtsov case NL80211_IFTYPE_AP_VLAN: 1970ad932f04SArik Nemtsov case NL80211_IFTYPE_P2P_DEVICE: 1971ad932f04SArik Nemtsov /* no enforcement required */ 1972ad932f04SArik Nemtsov break; 1973ad932f04SArik Nemtsov default: 1974ad932f04SArik Nemtsov /* others not implemented for now */ 1975ad932f04SArik Nemtsov WARN_ON(1); 1976ad932f04SArik Nemtsov break; 1977ad932f04SArik Nemtsov } 1978ad932f04SArik Nemtsov 1979ad932f04SArik Nemtsov wdev_unlock(wdev); 198020658702SArik Nemtsov 198120658702SArik Nemtsov switch (iftype) { 198220658702SArik Nemtsov case NL80211_IFTYPE_AP: 198320658702SArik Nemtsov case NL80211_IFTYPE_P2P_GO: 198420658702SArik Nemtsov case NL80211_IFTYPE_ADHOC: 1985923b352fSArik Nemtsov return cfg80211_reg_can_beacon_relax(wiphy, &chandef, iftype); 198620658702SArik Nemtsov case NL80211_IFTYPE_STATION: 198720658702SArik Nemtsov case NL80211_IFTYPE_P2P_CLIENT: 198820658702SArik Nemtsov return cfg80211_chandef_usable(wiphy, &chandef, 198920658702SArik Nemtsov IEEE80211_CHAN_DISABLED); 199020658702SArik Nemtsov default: 199120658702SArik Nemtsov break; 199220658702SArik Nemtsov } 199320658702SArik Nemtsov 199420658702SArik Nemtsov return true; 199520658702SArik Nemtsov 199620658702SArik Nemtsov wdev_inactive_unlock: 199720658702SArik Nemtsov wdev_unlock(wdev); 199820658702SArik Nemtsov return true; 1999ad932f04SArik Nemtsov } 2000ad932f04SArik Nemtsov 2001ad932f04SArik Nemtsov static void reg_leave_invalid_chans(struct wiphy *wiphy) 2002ad932f04SArik Nemtsov { 2003ad932f04SArik Nemtsov struct wireless_dev *wdev; 2004ad932f04SArik Nemtsov struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); 2005ad932f04SArik Nemtsov 2006ad932f04SArik Nemtsov ASSERT_RTNL(); 2007ad932f04SArik Nemtsov 200853873f13SJohannes Berg list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) 2009ad932f04SArik Nemtsov if (!reg_wdev_chan_valid(wiphy, wdev)) 2010ad932f04SArik Nemtsov cfg80211_leave(rdev, wdev); 2011ad932f04SArik Nemtsov } 2012ad932f04SArik Nemtsov 2013ad932f04SArik Nemtsov static void reg_check_chans_work(struct work_struct *work) 2014ad932f04SArik Nemtsov { 2015ad932f04SArik Nemtsov struct cfg80211_registered_device *rdev; 2016ad932f04SArik Nemtsov 2017c799ba6eSJohannes Berg pr_debug("Verifying active interfaces after reg change\n"); 2018ad932f04SArik Nemtsov rtnl_lock(); 2019ad932f04SArik Nemtsov 2020ad932f04SArik Nemtsov list_for_each_entry(rdev, &cfg80211_rdev_list, list) 2021ad932f04SArik Nemtsov if (!(rdev->wiphy.regulatory_flags & 2022ad932f04SArik Nemtsov REGULATORY_IGNORE_STALE_KICKOFF)) 2023ad932f04SArik Nemtsov reg_leave_invalid_chans(&rdev->wiphy); 2024ad932f04SArik Nemtsov 2025ad932f04SArik Nemtsov rtnl_unlock(); 2026ad932f04SArik Nemtsov } 2027ad932f04SArik Nemtsov 2028ad932f04SArik Nemtsov static void reg_check_channels(void) 2029ad932f04SArik Nemtsov { 2030ad932f04SArik Nemtsov /* 2031ad932f04SArik Nemtsov * Give usermode a chance to do something nicer (move to another 2032ad932f04SArik Nemtsov * channel, orderly disconnection), before forcing a disconnection. 2033ad932f04SArik Nemtsov */ 2034ad932f04SArik Nemtsov mod_delayed_work(system_power_efficient_wq, 2035ad932f04SArik Nemtsov ®_check_chans, 2036ad932f04SArik Nemtsov msecs_to_jiffies(REG_ENFORCE_GRACE_MS)); 2037ad932f04SArik Nemtsov } 2038ad932f04SArik Nemtsov 2039eac03e38SSven Neumann static void wiphy_update_regulatory(struct wiphy *wiphy, 20407db90f4aSLuis R. Rodriguez enum nl80211_reg_initiator initiator) 20418318d78aSJohannes Berg { 204257fbcce3SJohannes Berg enum nl80211_band band; 2043c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 2044eac03e38SSven Neumann 20450e3802dbSLuis R. Rodriguez if (ignore_reg_update(wiphy, initiator)) { 20460e3802dbSLuis R. Rodriguez /* 20470e3802dbSLuis R. Rodriguez * Regulatory updates set by CORE are ignored for custom 20480e3802dbSLuis R. Rodriguez * regulatory cards. Let us notify the changes to the driver, 20490e3802dbSLuis R. Rodriguez * as some drivers used this to restore its orig_* reg domain. 20500e3802dbSLuis R. Rodriguez */ 20510e3802dbSLuis R. Rodriguez if (initiator == NL80211_REGDOM_SET_BY_CORE && 2052a2f73b6cSLuis R. Rodriguez wiphy->regulatory_flags & REGULATORY_CUSTOM_REG) 20530e3802dbSLuis R. Rodriguez reg_call_notifier(wiphy, lr); 2054a203c2aaSSven Neumann return; 20550e3802dbSLuis R. Rodriguez } 2056a203c2aaSSven Neumann 2057c492db37SJohannes Berg lr->dfs_region = get_cfg80211_regdom()->dfs_region; 2058b68e6b3bSLuis R. Rodriguez 205957fbcce3SJohannes Berg for (band = 0; band < NUM_NL80211_BANDS; band++) 2060fdc9d7b2SJohannes Berg handle_band(wiphy, initiator, wiphy->bands[band]); 2061a203c2aaSSven Neumann 2062e38f8a7aSLuis R. Rodriguez reg_process_beacons(wiphy); 2063038659e7SLuis R. Rodriguez reg_process_ht_flags(wiphy); 20640e3802dbSLuis R. Rodriguez reg_call_notifier(wiphy, lr); 2065b2e1b302SLuis R. Rodriguez } 2066b2e1b302SLuis R. Rodriguez 2067d7549cbbSSven Neumann static void update_all_wiphy_regulatory(enum nl80211_reg_initiator initiator) 2068d7549cbbSSven Neumann { 2069d7549cbbSSven Neumann struct cfg80211_registered_device *rdev; 20704a38994fSRajkumar Manoharan struct wiphy *wiphy; 2071d7549cbbSSven Neumann 20725fe231e8SJohannes Berg ASSERT_RTNL(); 2073458f4f9eSJohannes Berg 20744a38994fSRajkumar Manoharan list_for_each_entry(rdev, &cfg80211_rdev_list, list) { 20754a38994fSRajkumar Manoharan wiphy = &rdev->wiphy; 20764a38994fSRajkumar Manoharan wiphy_update_regulatory(wiphy, initiator); 20774a38994fSRajkumar Manoharan } 2078ad932f04SArik Nemtsov 2079ad932f04SArik Nemtsov reg_check_channels(); 2080d7549cbbSSven Neumann } 2081d7549cbbSSven Neumann 20821fa25e41SLuis R. Rodriguez static void handle_channel_custom(struct wiphy *wiphy, 2083fdc9d7b2SJohannes Berg struct ieee80211_channel *chan, 20841fa25e41SLuis R. Rodriguez const struct ieee80211_regdomain *regd) 20851fa25e41SLuis R. Rodriguez { 2086038659e7SLuis R. Rodriguez u32 bw_flags = 0; 20871fa25e41SLuis R. Rodriguez const struct ieee80211_reg_rule *reg_rule = NULL; 20881fa25e41SLuis R. Rodriguez const struct ieee80211_power_rule *power_rule = NULL; 20894edd5698SMatthias May u32 bw; 20901fa25e41SLuis R. Rodriguez 20914edd5698SMatthias May for (bw = MHZ_TO_KHZ(20); bw >= MHZ_TO_KHZ(5); bw = bw / 2) { 209249172874SMichal Sojka reg_rule = freq_reg_info_regd(MHZ_TO_KHZ(chan->center_freq), 20934edd5698SMatthias May regd, bw); 20944edd5698SMatthias May if (!IS_ERR(reg_rule)) 20954edd5698SMatthias May break; 20964edd5698SMatthias May } 20971fa25e41SLuis R. Rodriguez 2098361c9c8bSJohannes Berg if (IS_ERR(reg_rule)) { 2099c799ba6eSJohannes Berg pr_debug("Disabling freq %d MHz as custom regd has no rule that fits it\n", 2100fe7ef5e9SJohannes Berg chan->center_freq); 2101db8dfee5SArik Nemtsov if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED) { 2102db8dfee5SArik Nemtsov chan->flags |= IEEE80211_CHAN_DISABLED; 2103db8dfee5SArik Nemtsov } else { 2104cc493e4fSLuis R. Rodriguez chan->orig_flags |= IEEE80211_CHAN_DISABLED; 2105cc493e4fSLuis R. Rodriguez chan->flags = chan->orig_flags; 2106db8dfee5SArik Nemtsov } 21071fa25e41SLuis R. Rodriguez return; 21081fa25e41SLuis R. Rodriguez } 21091fa25e41SLuis R. Rodriguez 21101fa25e41SLuis R. Rodriguez power_rule = ®_rule->power_rule; 21111aeb135fSMichal Sojka bw_flags = reg_rule_to_chan_bw_flags(regd, reg_rule, chan); 2112038659e7SLuis R. Rodriguez 21132e18b38fSArik Nemtsov chan->dfs_state_entered = jiffies; 2114c7ab5081SArik Nemtsov chan->dfs_state = NL80211_DFS_USABLE; 2115c7ab5081SArik Nemtsov 2116c7ab5081SArik Nemtsov chan->beacon_found = false; 2117db8dfee5SArik Nemtsov 2118db8dfee5SArik Nemtsov if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED) 2119db8dfee5SArik Nemtsov chan->flags = chan->orig_flags | bw_flags | 2120db8dfee5SArik Nemtsov map_regdom_flags(reg_rule->flags); 2121db8dfee5SArik Nemtsov else 2122038659e7SLuis R. Rodriguez chan->flags |= map_regdom_flags(reg_rule->flags) | bw_flags; 2123db8dfee5SArik Nemtsov 21241fa25e41SLuis R. Rodriguez chan->max_antenna_gain = (int) MBI_TO_DBI(power_rule->max_antenna_gain); 2125279f0f55SFelix Fietkau chan->max_reg_power = chan->max_power = 2126279f0f55SFelix Fietkau (int) MBM_TO_DBM(power_rule->max_eirp); 21272e18b38fSArik Nemtsov 21282e18b38fSArik Nemtsov if (chan->flags & IEEE80211_CHAN_RADAR) { 21292e18b38fSArik Nemtsov if (reg_rule->dfs_cac_ms) 21302e18b38fSArik Nemtsov chan->dfs_cac_ms = reg_rule->dfs_cac_ms; 21312e18b38fSArik Nemtsov else 21322e18b38fSArik Nemtsov chan->dfs_cac_ms = IEEE80211_DFS_MIN_CAC_TIME_MS; 21332e18b38fSArik Nemtsov } 21342e18b38fSArik Nemtsov 21352e18b38fSArik Nemtsov chan->max_power = chan->max_reg_power; 21361fa25e41SLuis R. Rodriguez } 21371fa25e41SLuis R. Rodriguez 2138fdc9d7b2SJohannes Berg static void handle_band_custom(struct wiphy *wiphy, 2139fdc9d7b2SJohannes Berg struct ieee80211_supported_band *sband, 21401fa25e41SLuis R. Rodriguez const struct ieee80211_regdomain *regd) 21411fa25e41SLuis R. Rodriguez { 21421fa25e41SLuis R. Rodriguez unsigned int i; 21431fa25e41SLuis R. Rodriguez 2144fdc9d7b2SJohannes Berg if (!sband) 2145fdc9d7b2SJohannes Berg return; 21461fa25e41SLuis R. Rodriguez 21471fa25e41SLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 2148fdc9d7b2SJohannes Berg handle_channel_custom(wiphy, &sband->channels[i], regd); 21491fa25e41SLuis R. Rodriguez } 21501fa25e41SLuis R. Rodriguez 21511fa25e41SLuis R. Rodriguez /* Used by drivers prior to wiphy registration */ 21521fa25e41SLuis R. Rodriguez void wiphy_apply_custom_regulatory(struct wiphy *wiphy, 21531fa25e41SLuis R. Rodriguez const struct ieee80211_regdomain *regd) 21541fa25e41SLuis R. Rodriguez { 215557fbcce3SJohannes Berg enum nl80211_band band; 2156bbcf3f02SLuis R. Rodriguez unsigned int bands_set = 0; 2157ac46d48eSLuis R. Rodriguez 2158a2f73b6cSLuis R. Rodriguez WARN(!(wiphy->regulatory_flags & REGULATORY_CUSTOM_REG), 2159a2f73b6cSLuis R. Rodriguez "wiphy should have REGULATORY_CUSTOM_REG\n"); 2160a2f73b6cSLuis R. Rodriguez wiphy->regulatory_flags |= REGULATORY_CUSTOM_REG; 2161222ea581SLuis R. Rodriguez 216257fbcce3SJohannes Berg for (band = 0; band < NUM_NL80211_BANDS; band++) { 2163bbcf3f02SLuis R. Rodriguez if (!wiphy->bands[band]) 2164bbcf3f02SLuis R. Rodriguez continue; 2165fdc9d7b2SJohannes Berg handle_band_custom(wiphy, wiphy->bands[band], regd); 2166bbcf3f02SLuis R. Rodriguez bands_set++; 21671fa25e41SLuis R. Rodriguez } 2168bbcf3f02SLuis R. Rodriguez 2169bbcf3f02SLuis R. Rodriguez /* 2170bbcf3f02SLuis R. Rodriguez * no point in calling this if it won't have any effect 21711a919318SJohannes Berg * on your device's supported bands. 2172bbcf3f02SLuis R. Rodriguez */ 2173bbcf3f02SLuis R. Rodriguez WARN_ON(!bands_set); 21741fa25e41SLuis R. Rodriguez } 21751fa25e41SLuis R. Rodriguez EXPORT_SYMBOL(wiphy_apply_custom_regulatory); 21761fa25e41SLuis R. Rodriguez 2177b2e253cfSLuis R. Rodriguez static void reg_set_request_processed(void) 2178b2e253cfSLuis R. Rodriguez { 2179b2e253cfSLuis R. Rodriguez bool need_more_processing = false; 2180c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 2181b2e253cfSLuis R. Rodriguez 2182c492db37SJohannes Berg lr->processed = true; 2183b2e253cfSLuis R. Rodriguez 2184b2e253cfSLuis R. Rodriguez spin_lock(®_requests_lock); 2185b2e253cfSLuis R. Rodriguez if (!list_empty(®_requests_list)) 2186b2e253cfSLuis R. Rodriguez need_more_processing = true; 2187b2e253cfSLuis R. Rodriguez spin_unlock(®_requests_lock); 2188b2e253cfSLuis R. Rodriguez 2189b6863036SJohannes Berg cancel_crda_timeout(); 2190a90c7a31SLuis R. Rodriguez 2191b2e253cfSLuis R. Rodriguez if (need_more_processing) 2192b2e253cfSLuis R. Rodriguez schedule_work(®_work); 2193b2e253cfSLuis R. Rodriguez } 2194b2e253cfSLuis R. Rodriguez 2195d1c96a9aSLuis R. Rodriguez /** 2196b3eb7f3fSLuis R. Rodriguez * reg_process_hint_core - process core regulatory requests 2197b3eb7f3fSLuis R. Rodriguez * @pending_request: a pending core regulatory request 2198b3eb7f3fSLuis R. Rodriguez * 2199b3eb7f3fSLuis R. Rodriguez * The wireless subsystem can use this function to process 2200b3eb7f3fSLuis R. Rodriguez * a regulatory request issued by the regulatory core. 2201b3eb7f3fSLuis R. Rodriguez */ 2202d34265a3SJohannes Berg static enum reg_request_treatment 2203d34265a3SJohannes Berg reg_process_hint_core(struct regulatory_request *core_request) 2204b3eb7f3fSLuis R. Rodriguez { 2205cecbb069SJohannes Berg if (reg_query_database(core_request)) { 2206b3eb7f3fSLuis R. Rodriguez core_request->intersect = false; 2207b3eb7f3fSLuis R. Rodriguez core_request->processed = false; 220805f1a3eaSLuis R. Rodriguez reg_update_last_request(core_request); 2209d34265a3SJohannes Berg return REG_REQ_OK; 221025b20dbdSJohannes Berg } 2211d34265a3SJohannes Berg 2212d34265a3SJohannes Berg return REG_REQ_IGNORE; 2213b3eb7f3fSLuis R. Rodriguez } 2214b3eb7f3fSLuis R. Rodriguez 22150d97a619SLuis R. Rodriguez static enum reg_request_treatment 22160d97a619SLuis R. Rodriguez __reg_process_hint_user(struct regulatory_request *user_request) 22170d97a619SLuis R. Rodriguez { 22180d97a619SLuis R. Rodriguez struct regulatory_request *lr = get_last_request(); 22190d97a619SLuis R. Rodriguez 22200d97a619SLuis R. Rodriguez if (reg_request_cell_base(user_request)) 22210d97a619SLuis R. Rodriguez return reg_ignore_cell_hint(user_request); 22220d97a619SLuis R. Rodriguez 22230d97a619SLuis R. Rodriguez if (reg_request_cell_base(lr)) 22240d97a619SLuis R. Rodriguez return REG_REQ_IGNORE; 22250d97a619SLuis R. Rodriguez 22260d97a619SLuis R. Rodriguez if (lr->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE) 22270d97a619SLuis R. Rodriguez return REG_REQ_INTERSECT; 22280d97a619SLuis R. Rodriguez /* 22290d97a619SLuis R. Rodriguez * If the user knows better the user should set the regdom 22300d97a619SLuis R. Rodriguez * to their country before the IE is picked up 22310d97a619SLuis R. Rodriguez */ 22320d97a619SLuis R. Rodriguez if (lr->initiator == NL80211_REGDOM_SET_BY_USER && 22330d97a619SLuis R. Rodriguez lr->intersect) 22340d97a619SLuis R. Rodriguez return REG_REQ_IGNORE; 22350d97a619SLuis R. Rodriguez /* 22360d97a619SLuis R. Rodriguez * Process user requests only after previous user/driver/core 22370d97a619SLuis R. Rodriguez * requests have been processed 22380d97a619SLuis R. Rodriguez */ 22390d97a619SLuis R. Rodriguez if ((lr->initiator == NL80211_REGDOM_SET_BY_CORE || 22400d97a619SLuis R. Rodriguez lr->initiator == NL80211_REGDOM_SET_BY_DRIVER || 22410d97a619SLuis R. Rodriguez lr->initiator == NL80211_REGDOM_SET_BY_USER) && 22420d97a619SLuis R. Rodriguez regdom_changes(lr->alpha2)) 22430d97a619SLuis R. Rodriguez return REG_REQ_IGNORE; 22440d97a619SLuis R. Rodriguez 22450d97a619SLuis R. Rodriguez if (!regdom_changes(user_request->alpha2)) 22460d97a619SLuis R. Rodriguez return REG_REQ_ALREADY_SET; 22470d97a619SLuis R. Rodriguez 22480d97a619SLuis R. Rodriguez return REG_REQ_OK; 22490d97a619SLuis R. Rodriguez } 22500d97a619SLuis R. Rodriguez 22510d97a619SLuis R. Rodriguez /** 22520d97a619SLuis R. Rodriguez * reg_process_hint_user - process user regulatory requests 22530d97a619SLuis R. Rodriguez * @user_request: a pending user regulatory request 22540d97a619SLuis R. Rodriguez * 22550d97a619SLuis R. Rodriguez * The wireless subsystem can use this function to process 22560d97a619SLuis R. Rodriguez * a regulatory request initiated by userspace. 22570d97a619SLuis R. Rodriguez */ 2258d34265a3SJohannes Berg static enum reg_request_treatment 2259d34265a3SJohannes Berg reg_process_hint_user(struct regulatory_request *user_request) 22600d97a619SLuis R. Rodriguez { 22610d97a619SLuis R. Rodriguez enum reg_request_treatment treatment; 22620d97a619SLuis R. Rodriguez 22630d97a619SLuis R. Rodriguez treatment = __reg_process_hint_user(user_request); 22640d97a619SLuis R. Rodriguez if (treatment == REG_REQ_IGNORE || 2265d34265a3SJohannes Berg treatment == REG_REQ_ALREADY_SET) 2266d34265a3SJohannes Berg return REG_REQ_IGNORE; 22670d97a619SLuis R. Rodriguez 22680d97a619SLuis R. Rodriguez user_request->intersect = treatment == REG_REQ_INTERSECT; 22690d97a619SLuis R. Rodriguez user_request->processed = false; 22705ad6ef5eSLuis R. Rodriguez 2271cecbb069SJohannes Berg if (reg_query_database(user_request)) { 227205f1a3eaSLuis R. Rodriguez reg_update_last_request(user_request); 22730d97a619SLuis R. Rodriguez user_alpha2[0] = user_request->alpha2[0]; 22740d97a619SLuis R. Rodriguez user_alpha2[1] = user_request->alpha2[1]; 2275d34265a3SJohannes Berg return REG_REQ_OK; 227625b20dbdSJohannes Berg } 2277d34265a3SJohannes Berg 2278d34265a3SJohannes Berg return REG_REQ_IGNORE; 22790d97a619SLuis R. Rodriguez } 22800d97a619SLuis R. Rodriguez 228121636c7fSLuis R. Rodriguez static enum reg_request_treatment 228221636c7fSLuis R. Rodriguez __reg_process_hint_driver(struct regulatory_request *driver_request) 228321636c7fSLuis R. Rodriguez { 228421636c7fSLuis R. Rodriguez struct regulatory_request *lr = get_last_request(); 228521636c7fSLuis R. Rodriguez 228621636c7fSLuis R. Rodriguez if (lr->initiator == NL80211_REGDOM_SET_BY_CORE) { 228721636c7fSLuis R. Rodriguez if (regdom_changes(driver_request->alpha2)) 228821636c7fSLuis R. Rodriguez return REG_REQ_OK; 228921636c7fSLuis R. Rodriguez return REG_REQ_ALREADY_SET; 229021636c7fSLuis R. Rodriguez } 229121636c7fSLuis R. Rodriguez 229221636c7fSLuis R. Rodriguez /* 229321636c7fSLuis R. Rodriguez * This would happen if you unplug and plug your card 229421636c7fSLuis R. Rodriguez * back in or if you add a new device for which the previously 229521636c7fSLuis R. Rodriguez * loaded card also agrees on the regulatory domain. 229621636c7fSLuis R. Rodriguez */ 229721636c7fSLuis R. Rodriguez if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER && 229821636c7fSLuis R. Rodriguez !regdom_changes(driver_request->alpha2)) 229921636c7fSLuis R. Rodriguez return REG_REQ_ALREADY_SET; 230021636c7fSLuis R. Rodriguez 230121636c7fSLuis R. Rodriguez return REG_REQ_INTERSECT; 230221636c7fSLuis R. Rodriguez } 230321636c7fSLuis R. Rodriguez 230421636c7fSLuis R. Rodriguez /** 230521636c7fSLuis R. Rodriguez * reg_process_hint_driver - process driver regulatory requests 230621636c7fSLuis R. Rodriguez * @driver_request: a pending driver regulatory request 230721636c7fSLuis R. Rodriguez * 230821636c7fSLuis R. Rodriguez * The wireless subsystem can use this function to process 230921636c7fSLuis R. Rodriguez * a regulatory request issued by an 802.11 driver. 231021636c7fSLuis R. Rodriguez * 231121636c7fSLuis R. Rodriguez * Returns one of the different reg request treatment values. 231221636c7fSLuis R. Rodriguez */ 231321636c7fSLuis R. Rodriguez static enum reg_request_treatment 231421636c7fSLuis R. Rodriguez reg_process_hint_driver(struct wiphy *wiphy, 231521636c7fSLuis R. Rodriguez struct regulatory_request *driver_request) 231621636c7fSLuis R. Rodriguez { 231734f05f54SArik Nemtsov const struct ieee80211_regdomain *regd, *tmp; 231821636c7fSLuis R. Rodriguez enum reg_request_treatment treatment; 231921636c7fSLuis R. Rodriguez 232021636c7fSLuis R. Rodriguez treatment = __reg_process_hint_driver(driver_request); 232121636c7fSLuis R. Rodriguez 232221636c7fSLuis R. Rodriguez switch (treatment) { 232321636c7fSLuis R. Rodriguez case REG_REQ_OK: 232421636c7fSLuis R. Rodriguez break; 232521636c7fSLuis R. Rodriguez case REG_REQ_IGNORE: 2326d34265a3SJohannes Berg return REG_REQ_IGNORE; 232721636c7fSLuis R. Rodriguez case REG_REQ_INTERSECT: 232821636c7fSLuis R. Rodriguez case REG_REQ_ALREADY_SET: 232921636c7fSLuis R. Rodriguez regd = reg_copy_regd(get_cfg80211_regdom()); 2330d34265a3SJohannes Berg if (IS_ERR(regd)) 2331d34265a3SJohannes Berg return REG_REQ_IGNORE; 233234f05f54SArik Nemtsov 233334f05f54SArik Nemtsov tmp = get_wiphy_regdom(wiphy); 233421636c7fSLuis R. Rodriguez rcu_assign_pointer(wiphy->regd, regd); 233534f05f54SArik Nemtsov rcu_free_regdom(tmp); 233621636c7fSLuis R. Rodriguez } 233721636c7fSLuis R. Rodriguez 233821636c7fSLuis R. Rodriguez 233921636c7fSLuis R. Rodriguez driver_request->intersect = treatment == REG_REQ_INTERSECT; 234021636c7fSLuis R. Rodriguez driver_request->processed = false; 23415ad6ef5eSLuis R. Rodriguez 234221636c7fSLuis R. Rodriguez /* 234321636c7fSLuis R. Rodriguez * Since CRDA will not be called in this case as we already 234421636c7fSLuis R. Rodriguez * have applied the requested regulatory domain before we just 234521636c7fSLuis R. Rodriguez * inform userspace we have processed the request 234621636c7fSLuis R. Rodriguez */ 234721636c7fSLuis R. Rodriguez if (treatment == REG_REQ_ALREADY_SET) { 234821636c7fSLuis R. Rodriguez nl80211_send_reg_change_event(driver_request); 234925b20dbdSJohannes Berg reg_update_last_request(driver_request); 235021636c7fSLuis R. Rodriguez reg_set_request_processed(); 2351480908a7SJohannes Berg return REG_REQ_ALREADY_SET; 235221636c7fSLuis R. Rodriguez } 235321636c7fSLuis R. Rodriguez 2354d34265a3SJohannes Berg if (reg_query_database(driver_request)) { 235525b20dbdSJohannes Berg reg_update_last_request(driver_request); 235625b20dbdSJohannes Berg return REG_REQ_OK; 235721636c7fSLuis R. Rodriguez } 235821636c7fSLuis R. Rodriguez 2359d34265a3SJohannes Berg return REG_REQ_IGNORE; 2360d34265a3SJohannes Berg } 2361d34265a3SJohannes Berg 2362b23e7a9eSLuis R. Rodriguez static enum reg_request_treatment 2363b23e7a9eSLuis R. Rodriguez __reg_process_hint_country_ie(struct wiphy *wiphy, 2364b23e7a9eSLuis R. Rodriguez struct regulatory_request *country_ie_request) 2365b23e7a9eSLuis R. Rodriguez { 2366b23e7a9eSLuis R. Rodriguez struct wiphy *last_wiphy = NULL; 2367b23e7a9eSLuis R. Rodriguez struct regulatory_request *lr = get_last_request(); 2368b23e7a9eSLuis R. Rodriguez 2369b23e7a9eSLuis R. Rodriguez if (reg_request_cell_base(lr)) { 2370b23e7a9eSLuis R. Rodriguez /* Trust a Cell base station over the AP's country IE */ 2371b23e7a9eSLuis R. Rodriguez if (regdom_changes(country_ie_request->alpha2)) 2372b23e7a9eSLuis R. Rodriguez return REG_REQ_IGNORE; 2373b23e7a9eSLuis R. Rodriguez return REG_REQ_ALREADY_SET; 23742a901468SLuis R. Rodriguez } else { 23752a901468SLuis R. Rodriguez if (wiphy->regulatory_flags & REGULATORY_COUNTRY_IE_IGNORE) 23762a901468SLuis R. Rodriguez return REG_REQ_IGNORE; 2377b23e7a9eSLuis R. Rodriguez } 2378b23e7a9eSLuis R. Rodriguez 2379b23e7a9eSLuis R. Rodriguez if (unlikely(!is_an_alpha2(country_ie_request->alpha2))) 2380b23e7a9eSLuis R. Rodriguez return -EINVAL; 23812f1c6c57SLuis R. Rodriguez 23822f1c6c57SLuis R. Rodriguez if (lr->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE) 23832f1c6c57SLuis R. Rodriguez return REG_REQ_OK; 23842f1c6c57SLuis R. Rodriguez 23852f1c6c57SLuis R. Rodriguez last_wiphy = wiphy_idx_to_wiphy(lr->wiphy_idx); 23862f1c6c57SLuis R. Rodriguez 2387b23e7a9eSLuis R. Rodriguez if (last_wiphy != wiphy) { 2388b23e7a9eSLuis R. Rodriguez /* 2389b23e7a9eSLuis R. Rodriguez * Two cards with two APs claiming different 2390b23e7a9eSLuis R. Rodriguez * Country IE alpha2s. We could 2391b23e7a9eSLuis R. Rodriguez * intersect them, but that seems unlikely 2392b23e7a9eSLuis R. Rodriguez * to be correct. Reject second one for now. 2393b23e7a9eSLuis R. Rodriguez */ 2394b23e7a9eSLuis R. Rodriguez if (regdom_changes(country_ie_request->alpha2)) 2395b23e7a9eSLuis R. Rodriguez return REG_REQ_IGNORE; 2396b23e7a9eSLuis R. Rodriguez return REG_REQ_ALREADY_SET; 2397b23e7a9eSLuis R. Rodriguez } 239870dcec5aSEmmanuel Grumbach 239970dcec5aSEmmanuel Grumbach if (regdom_changes(country_ie_request->alpha2)) 2400b23e7a9eSLuis R. Rodriguez return REG_REQ_OK; 2401b23e7a9eSLuis R. Rodriguez return REG_REQ_ALREADY_SET; 2402b23e7a9eSLuis R. Rodriguez } 2403b23e7a9eSLuis R. Rodriguez 2404b3eb7f3fSLuis R. Rodriguez /** 2405b23e7a9eSLuis R. Rodriguez * reg_process_hint_country_ie - process regulatory requests from country IEs 2406b23e7a9eSLuis R. Rodriguez * @country_ie_request: a regulatory request from a country IE 2407d1c96a9aSLuis R. Rodriguez * 2408b23e7a9eSLuis R. Rodriguez * The wireless subsystem can use this function to process 2409b23e7a9eSLuis R. Rodriguez * a regulatory request issued by a country Information Element. 2410d1c96a9aSLuis R. Rodriguez * 24112f92212bSJohannes Berg * Returns one of the different reg request treatment values. 2412d1c96a9aSLuis R. Rodriguez */ 24132f92212bSJohannes Berg static enum reg_request_treatment 2414b23e7a9eSLuis R. Rodriguez reg_process_hint_country_ie(struct wiphy *wiphy, 2415b23e7a9eSLuis R. Rodriguez struct regulatory_request *country_ie_request) 2416b2e1b302SLuis R. Rodriguez { 24172f92212bSJohannes Berg enum reg_request_treatment treatment; 2418b2e1b302SLuis R. Rodriguez 2419b23e7a9eSLuis R. Rodriguez treatment = __reg_process_hint_country_ie(wiphy, country_ie_request); 2420761cf7ecSLuis R. Rodriguez 24212f92212bSJohannes Berg switch (treatment) { 24222f92212bSJohannes Berg case REG_REQ_OK: 24232f92212bSJohannes Berg break; 2424b23e7a9eSLuis R. Rodriguez case REG_REQ_IGNORE: 2425d34265a3SJohannes Berg return REG_REQ_IGNORE; 2426b23e7a9eSLuis R. Rodriguez case REG_REQ_ALREADY_SET: 2427c888393bSArik Nemtsov reg_free_request(country_ie_request); 2428480908a7SJohannes Berg return REG_REQ_ALREADY_SET; 2429b23e7a9eSLuis R. Rodriguez case REG_REQ_INTERSECT: 2430fb1fc7adSLuis R. Rodriguez /* 2431b23e7a9eSLuis R. Rodriguez * This doesn't happen yet, not sure we 2432b23e7a9eSLuis R. Rodriguez * ever want to support it for this case. 2433fb1fc7adSLuis R. Rodriguez */ 2434b23e7a9eSLuis R. Rodriguez WARN_ONCE(1, "Unexpected intersection for country IEs"); 2435d34265a3SJohannes Berg return REG_REQ_IGNORE; 2436d951c1ddSLuis R. Rodriguez } 2437b2e1b302SLuis R. Rodriguez 2438b23e7a9eSLuis R. Rodriguez country_ie_request->intersect = false; 2439b23e7a9eSLuis R. Rodriguez country_ie_request->processed = false; 24405ad6ef5eSLuis R. Rodriguez 2441d34265a3SJohannes Berg if (reg_query_database(country_ie_request)) { 244205f1a3eaSLuis R. Rodriguez reg_update_last_request(country_ie_request); 244325b20dbdSJohannes Berg return REG_REQ_OK; 2444b2e1b302SLuis R. Rodriguez } 2445b2e1b302SLuis R. Rodriguez 2446d34265a3SJohannes Berg return REG_REQ_IGNORE; 2447d34265a3SJohannes Berg } 2448d34265a3SJohannes Berg 244989766727SVasanthakumar Thiagarajan bool reg_dfs_domain_same(struct wiphy *wiphy1, struct wiphy *wiphy2) 245089766727SVasanthakumar Thiagarajan { 245189766727SVasanthakumar Thiagarajan const struct ieee80211_regdomain *wiphy1_regd = NULL; 245289766727SVasanthakumar Thiagarajan const struct ieee80211_regdomain *wiphy2_regd = NULL; 245389766727SVasanthakumar Thiagarajan const struct ieee80211_regdomain *cfg80211_regd = NULL; 245489766727SVasanthakumar Thiagarajan bool dfs_domain_same; 245589766727SVasanthakumar Thiagarajan 245689766727SVasanthakumar Thiagarajan rcu_read_lock(); 245789766727SVasanthakumar Thiagarajan 245889766727SVasanthakumar Thiagarajan cfg80211_regd = rcu_dereference(cfg80211_regdomain); 245989766727SVasanthakumar Thiagarajan wiphy1_regd = rcu_dereference(wiphy1->regd); 246089766727SVasanthakumar Thiagarajan if (!wiphy1_regd) 246189766727SVasanthakumar Thiagarajan wiphy1_regd = cfg80211_regd; 246289766727SVasanthakumar Thiagarajan 246389766727SVasanthakumar Thiagarajan wiphy2_regd = rcu_dereference(wiphy2->regd); 246489766727SVasanthakumar Thiagarajan if (!wiphy2_regd) 246589766727SVasanthakumar Thiagarajan wiphy2_regd = cfg80211_regd; 246689766727SVasanthakumar Thiagarajan 246789766727SVasanthakumar Thiagarajan dfs_domain_same = wiphy1_regd->dfs_region == wiphy2_regd->dfs_region; 246889766727SVasanthakumar Thiagarajan 246989766727SVasanthakumar Thiagarajan rcu_read_unlock(); 247089766727SVasanthakumar Thiagarajan 247189766727SVasanthakumar Thiagarajan return dfs_domain_same; 247289766727SVasanthakumar Thiagarajan } 247389766727SVasanthakumar Thiagarajan 247489766727SVasanthakumar Thiagarajan static void reg_copy_dfs_chan_state(struct ieee80211_channel *dst_chan, 247589766727SVasanthakumar Thiagarajan struct ieee80211_channel *src_chan) 247689766727SVasanthakumar Thiagarajan { 247789766727SVasanthakumar Thiagarajan if (!(dst_chan->flags & IEEE80211_CHAN_RADAR) || 247889766727SVasanthakumar Thiagarajan !(src_chan->flags & IEEE80211_CHAN_RADAR)) 247989766727SVasanthakumar Thiagarajan return; 248089766727SVasanthakumar Thiagarajan 248189766727SVasanthakumar Thiagarajan if (dst_chan->flags & IEEE80211_CHAN_DISABLED || 248289766727SVasanthakumar Thiagarajan src_chan->flags & IEEE80211_CHAN_DISABLED) 248389766727SVasanthakumar Thiagarajan return; 248489766727SVasanthakumar Thiagarajan 248589766727SVasanthakumar Thiagarajan if (src_chan->center_freq == dst_chan->center_freq && 248689766727SVasanthakumar Thiagarajan dst_chan->dfs_state == NL80211_DFS_USABLE) { 248789766727SVasanthakumar Thiagarajan dst_chan->dfs_state = src_chan->dfs_state; 248889766727SVasanthakumar Thiagarajan dst_chan->dfs_state_entered = src_chan->dfs_state_entered; 248989766727SVasanthakumar Thiagarajan } 249089766727SVasanthakumar Thiagarajan } 249189766727SVasanthakumar Thiagarajan 249289766727SVasanthakumar Thiagarajan static void wiphy_share_dfs_chan_state(struct wiphy *dst_wiphy, 249389766727SVasanthakumar Thiagarajan struct wiphy *src_wiphy) 249489766727SVasanthakumar Thiagarajan { 249589766727SVasanthakumar Thiagarajan struct ieee80211_supported_band *src_sband, *dst_sband; 249689766727SVasanthakumar Thiagarajan struct ieee80211_channel *src_chan, *dst_chan; 249789766727SVasanthakumar Thiagarajan int i, j, band; 249889766727SVasanthakumar Thiagarajan 249989766727SVasanthakumar Thiagarajan if (!reg_dfs_domain_same(dst_wiphy, src_wiphy)) 250089766727SVasanthakumar Thiagarajan return; 250189766727SVasanthakumar Thiagarajan 250289766727SVasanthakumar Thiagarajan for (band = 0; band < NUM_NL80211_BANDS; band++) { 250389766727SVasanthakumar Thiagarajan dst_sband = dst_wiphy->bands[band]; 250489766727SVasanthakumar Thiagarajan src_sband = src_wiphy->bands[band]; 250589766727SVasanthakumar Thiagarajan if (!dst_sband || !src_sband) 250689766727SVasanthakumar Thiagarajan continue; 250789766727SVasanthakumar Thiagarajan 250889766727SVasanthakumar Thiagarajan for (i = 0; i < dst_sband->n_channels; i++) { 250989766727SVasanthakumar Thiagarajan dst_chan = &dst_sband->channels[i]; 251089766727SVasanthakumar Thiagarajan for (j = 0; j < src_sband->n_channels; j++) { 251189766727SVasanthakumar Thiagarajan src_chan = &src_sband->channels[j]; 251289766727SVasanthakumar Thiagarajan reg_copy_dfs_chan_state(dst_chan, src_chan); 251389766727SVasanthakumar Thiagarajan } 251489766727SVasanthakumar Thiagarajan } 251589766727SVasanthakumar Thiagarajan } 251689766727SVasanthakumar Thiagarajan } 251789766727SVasanthakumar Thiagarajan 251889766727SVasanthakumar Thiagarajan static void wiphy_all_share_dfs_chan_state(struct wiphy *wiphy) 251989766727SVasanthakumar Thiagarajan { 252089766727SVasanthakumar Thiagarajan struct cfg80211_registered_device *rdev; 252189766727SVasanthakumar Thiagarajan 252289766727SVasanthakumar Thiagarajan ASSERT_RTNL(); 252389766727SVasanthakumar Thiagarajan 252489766727SVasanthakumar Thiagarajan list_for_each_entry(rdev, &cfg80211_rdev_list, list) { 252589766727SVasanthakumar Thiagarajan if (wiphy == &rdev->wiphy) 252689766727SVasanthakumar Thiagarajan continue; 252789766727SVasanthakumar Thiagarajan wiphy_share_dfs_chan_state(wiphy, &rdev->wiphy); 252889766727SVasanthakumar Thiagarajan } 252989766727SVasanthakumar Thiagarajan } 253089766727SVasanthakumar Thiagarajan 253130a548c7SLuis R. Rodriguez /* This processes *all* regulatory hints */ 25321daa37c7SLuis R. Rodriguez static void reg_process_hint(struct regulatory_request *reg_request) 2533fe33eb39SLuis R. Rodriguez { 2534fe33eb39SLuis R. Rodriguez struct wiphy *wiphy = NULL; 2535b3eb7f3fSLuis R. Rodriguez enum reg_request_treatment treatment; 2536fe33eb39SLuis R. Rodriguez 2537f4173766SJohannes Berg if (reg_request->wiphy_idx != WIPHY_IDX_INVALID) 2538fe33eb39SLuis R. Rodriguez wiphy = wiphy_idx_to_wiphy(reg_request->wiphy_idx); 2539fe33eb39SLuis R. Rodriguez 2540b3eb7f3fSLuis R. Rodriguez switch (reg_request->initiator) { 2541b3eb7f3fSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_CORE: 2542d34265a3SJohannes Berg treatment = reg_process_hint_core(reg_request); 2543d34265a3SJohannes Berg break; 2544b3eb7f3fSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_USER: 2545d34265a3SJohannes Berg treatment = reg_process_hint_user(reg_request); 2546d34265a3SJohannes Berg break; 2547b3eb7f3fSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_DRIVER: 2548772f0389SIlan Peer if (!wiphy) 2549772f0389SIlan Peer goto out_free; 255021636c7fSLuis R. Rodriguez treatment = reg_process_hint_driver(wiphy, reg_request); 255121636c7fSLuis R. Rodriguez break; 2552b3eb7f3fSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_COUNTRY_IE: 2553772f0389SIlan Peer if (!wiphy) 2554772f0389SIlan Peer goto out_free; 2555b23e7a9eSLuis R. Rodriguez treatment = reg_process_hint_country_ie(wiphy, reg_request); 2556b3eb7f3fSLuis R. Rodriguez break; 2557b3eb7f3fSLuis R. Rodriguez default: 2558b3eb7f3fSLuis R. Rodriguez WARN(1, "invalid initiator %d\n", reg_request->initiator); 2559772f0389SIlan Peer goto out_free; 2560b3eb7f3fSLuis R. Rodriguez } 2561b3eb7f3fSLuis R. Rodriguez 2562d34265a3SJohannes Berg if (treatment == REG_REQ_IGNORE) 2563d34265a3SJohannes Berg goto out_free; 2564d34265a3SJohannes Berg 2565480908a7SJohannes Berg WARN(treatment != REG_REQ_OK && treatment != REG_REQ_ALREADY_SET, 2566480908a7SJohannes Berg "unexpected treatment value %d\n", treatment); 2567480908a7SJohannes Berg 2568841b351cSJohn Linville /* This is required so that the orig_* parameters are saved. 2569841b351cSJohn Linville * NOTE: treatment must be set for any case that reaches here! 2570841b351cSJohn Linville */ 2571b23e7a9eSLuis R. Rodriguez if (treatment == REG_REQ_ALREADY_SET && wiphy && 2572ad932f04SArik Nemtsov wiphy->regulatory_flags & REGULATORY_STRICT_REG) { 25731daa37c7SLuis R. Rodriguez wiphy_update_regulatory(wiphy, reg_request->initiator); 257489766727SVasanthakumar Thiagarajan wiphy_all_share_dfs_chan_state(wiphy); 2575ad932f04SArik Nemtsov reg_check_channels(); 2576ad932f04SArik Nemtsov } 2577772f0389SIlan Peer 2578772f0389SIlan Peer return; 2579772f0389SIlan Peer 2580772f0389SIlan Peer out_free: 2581c888393bSArik Nemtsov reg_free_request(reg_request); 2582fe33eb39SLuis R. Rodriguez } 2583fe33eb39SLuis R. Rodriguez 2584ef51fb1dSArik Nemtsov static bool reg_only_self_managed_wiphys(void) 2585ef51fb1dSArik Nemtsov { 2586ef51fb1dSArik Nemtsov struct cfg80211_registered_device *rdev; 2587ef51fb1dSArik Nemtsov struct wiphy *wiphy; 2588ef51fb1dSArik Nemtsov bool self_managed_found = false; 2589ef51fb1dSArik Nemtsov 2590ef51fb1dSArik Nemtsov ASSERT_RTNL(); 2591ef51fb1dSArik Nemtsov 2592ef51fb1dSArik Nemtsov list_for_each_entry(rdev, &cfg80211_rdev_list, list) { 2593ef51fb1dSArik Nemtsov wiphy = &rdev->wiphy; 2594ef51fb1dSArik Nemtsov if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED) 2595ef51fb1dSArik Nemtsov self_managed_found = true; 2596ef51fb1dSArik Nemtsov else 2597ef51fb1dSArik Nemtsov return false; 2598ef51fb1dSArik Nemtsov } 2599ef51fb1dSArik Nemtsov 2600ef51fb1dSArik Nemtsov /* make sure at least one self-managed wiphy exists */ 2601ef51fb1dSArik Nemtsov return self_managed_found; 2602ef51fb1dSArik Nemtsov } 2603ef51fb1dSArik Nemtsov 2604b2e253cfSLuis R. Rodriguez /* 2605b2e253cfSLuis R. Rodriguez * Processes regulatory hints, this is all the NL80211_REGDOM_SET_BY_* 2606b2e253cfSLuis R. Rodriguez * Regulatory hints come on a first come first serve basis and we 2607b2e253cfSLuis R. Rodriguez * must process each one atomically. 2608b2e253cfSLuis R. Rodriguez */ 2609fe33eb39SLuis R. Rodriguez static void reg_process_pending_hints(void) 2610fe33eb39SLuis R. Rodriguez { 2611c492db37SJohannes Berg struct regulatory_request *reg_request, *lr; 2612fe33eb39SLuis R. Rodriguez 2613c492db37SJohannes Berg lr = get_last_request(); 2614b0e2880bSLuis R. Rodriguez 2615b2e253cfSLuis R. Rodriguez /* When last_request->processed becomes true this will be rescheduled */ 2616c492db37SJohannes Berg if (lr && !lr->processed) { 261796cce12fSLuis R. Rodriguez reg_process_hint(lr); 26185fe231e8SJohannes Berg return; 2619b2e253cfSLuis R. Rodriguez } 2620b2e253cfSLuis R. Rodriguez 2621fe33eb39SLuis R. Rodriguez spin_lock(®_requests_lock); 2622b2e253cfSLuis R. Rodriguez 2623b2e253cfSLuis R. Rodriguez if (list_empty(®_requests_list)) { 2624b2e253cfSLuis R. Rodriguez spin_unlock(®_requests_lock); 26255fe231e8SJohannes Berg return; 2626b2e253cfSLuis R. Rodriguez } 2627b2e253cfSLuis R. Rodriguez 2628fe33eb39SLuis R. Rodriguez reg_request = list_first_entry(®_requests_list, 2629fe33eb39SLuis R. Rodriguez struct regulatory_request, 2630fe33eb39SLuis R. Rodriguez list); 2631fe33eb39SLuis R. Rodriguez list_del_init(®_request->list); 2632fe33eb39SLuis R. Rodriguez 2633d951c1ddSLuis R. Rodriguez spin_unlock(®_requests_lock); 2634b0e2880bSLuis R. Rodriguez 2635ef51fb1dSArik Nemtsov if (reg_only_self_managed_wiphys()) { 2636ef51fb1dSArik Nemtsov reg_free_request(reg_request); 2637ef51fb1dSArik Nemtsov return; 2638ef51fb1dSArik Nemtsov } 2639ef51fb1dSArik Nemtsov 26401daa37c7SLuis R. Rodriguez reg_process_hint(reg_request); 26412e54a689SBen 26422e54a689SBen lr = get_last_request(); 26432e54a689SBen 26442e54a689SBen spin_lock(®_requests_lock); 26452e54a689SBen if (!list_empty(®_requests_list) && lr && lr->processed) 26462e54a689SBen schedule_work(®_work); 26472e54a689SBen spin_unlock(®_requests_lock); 2648fe33eb39SLuis R. Rodriguez } 2649fe33eb39SLuis R. Rodriguez 2650e38f8a7aSLuis R. Rodriguez /* Processes beacon hints -- this has nothing to do with country IEs */ 2651e38f8a7aSLuis R. Rodriguez static void reg_process_pending_beacon_hints(void) 2652e38f8a7aSLuis R. Rodriguez { 265379c97e97SJohannes Berg struct cfg80211_registered_device *rdev; 2654e38f8a7aSLuis R. Rodriguez struct reg_beacon *pending_beacon, *tmp; 2655e38f8a7aSLuis R. Rodriguez 2656e38f8a7aSLuis R. Rodriguez /* This goes through the _pending_ beacon list */ 2657e38f8a7aSLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 2658e38f8a7aSLuis R. Rodriguez 2659e38f8a7aSLuis R. Rodriguez list_for_each_entry_safe(pending_beacon, tmp, 2660e38f8a7aSLuis R. Rodriguez ®_pending_beacons, list) { 2661e38f8a7aSLuis R. Rodriguez list_del_init(&pending_beacon->list); 2662e38f8a7aSLuis R. Rodriguez 2663e38f8a7aSLuis R. Rodriguez /* Applies the beacon hint to current wiphys */ 266479c97e97SJohannes Berg list_for_each_entry(rdev, &cfg80211_rdev_list, list) 266579c97e97SJohannes Berg wiphy_update_new_beacon(&rdev->wiphy, pending_beacon); 2666e38f8a7aSLuis R. Rodriguez 2667e38f8a7aSLuis R. Rodriguez /* Remembers the beacon hint for new wiphys or reg changes */ 2668e38f8a7aSLuis R. Rodriguez list_add_tail(&pending_beacon->list, ®_beacon_list); 2669e38f8a7aSLuis R. Rodriguez } 2670e38f8a7aSLuis R. Rodriguez 2671e38f8a7aSLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 2672e38f8a7aSLuis R. Rodriguez } 2673e38f8a7aSLuis R. Rodriguez 2674b0d7aa59SJonathan Doron static void reg_process_self_managed_hints(void) 2675b0d7aa59SJonathan Doron { 2676b0d7aa59SJonathan Doron struct cfg80211_registered_device *rdev; 2677b0d7aa59SJonathan Doron struct wiphy *wiphy; 2678b0d7aa59SJonathan Doron const struct ieee80211_regdomain *tmp; 2679b0d7aa59SJonathan Doron const struct ieee80211_regdomain *regd; 268057fbcce3SJohannes Berg enum nl80211_band band; 2681b0d7aa59SJonathan Doron struct regulatory_request request = {}; 2682b0d7aa59SJonathan Doron 2683b0d7aa59SJonathan Doron list_for_each_entry(rdev, &cfg80211_rdev_list, list) { 2684b0d7aa59SJonathan Doron wiphy = &rdev->wiphy; 2685b0d7aa59SJonathan Doron 2686b0d7aa59SJonathan Doron spin_lock(®_requests_lock); 2687b0d7aa59SJonathan Doron regd = rdev->requested_regd; 2688b0d7aa59SJonathan Doron rdev->requested_regd = NULL; 2689b0d7aa59SJonathan Doron spin_unlock(®_requests_lock); 2690b0d7aa59SJonathan Doron 2691b0d7aa59SJonathan Doron if (regd == NULL) 2692b0d7aa59SJonathan Doron continue; 2693b0d7aa59SJonathan Doron 2694b0d7aa59SJonathan Doron tmp = get_wiphy_regdom(wiphy); 2695b0d7aa59SJonathan Doron rcu_assign_pointer(wiphy->regd, regd); 2696b0d7aa59SJonathan Doron rcu_free_regdom(tmp); 2697b0d7aa59SJonathan Doron 269857fbcce3SJohannes Berg for (band = 0; band < NUM_NL80211_BANDS; band++) 2699b0d7aa59SJonathan Doron handle_band_custom(wiphy, wiphy->bands[band], regd); 2700b0d7aa59SJonathan Doron 2701b0d7aa59SJonathan Doron reg_process_ht_flags(wiphy); 2702b0d7aa59SJonathan Doron 2703b0d7aa59SJonathan Doron request.wiphy_idx = get_wiphy_idx(wiphy); 2704b0d7aa59SJonathan Doron request.alpha2[0] = regd->alpha2[0]; 2705b0d7aa59SJonathan Doron request.alpha2[1] = regd->alpha2[1]; 2706b0d7aa59SJonathan Doron request.initiator = NL80211_REGDOM_SET_BY_DRIVER; 2707b0d7aa59SJonathan Doron 2708b0d7aa59SJonathan Doron nl80211_send_wiphy_reg_change_event(&request); 2709b0d7aa59SJonathan Doron } 2710b0d7aa59SJonathan Doron 2711b0d7aa59SJonathan Doron reg_check_channels(); 2712b0d7aa59SJonathan Doron } 2713b0d7aa59SJonathan Doron 2714fe33eb39SLuis R. Rodriguez static void reg_todo(struct work_struct *work) 2715fe33eb39SLuis R. Rodriguez { 27165fe231e8SJohannes Berg rtnl_lock(); 2717fe33eb39SLuis R. Rodriguez reg_process_pending_hints(); 2718e38f8a7aSLuis R. Rodriguez reg_process_pending_beacon_hints(); 2719b0d7aa59SJonathan Doron reg_process_self_managed_hints(); 27205fe231e8SJohannes Berg rtnl_unlock(); 2721fe33eb39SLuis R. Rodriguez } 2722fe33eb39SLuis R. Rodriguez 2723fe33eb39SLuis R. Rodriguez static void queue_regulatory_request(struct regulatory_request *request) 2724fe33eb39SLuis R. Rodriguez { 2725c61029c7SJohn W. Linville request->alpha2[0] = toupper(request->alpha2[0]); 2726c61029c7SJohn W. Linville request->alpha2[1] = toupper(request->alpha2[1]); 2727c61029c7SJohn W. Linville 2728fe33eb39SLuis R. Rodriguez spin_lock(®_requests_lock); 2729fe33eb39SLuis R. Rodriguez list_add_tail(&request->list, ®_requests_list); 2730fe33eb39SLuis R. Rodriguez spin_unlock(®_requests_lock); 2731fe33eb39SLuis R. Rodriguez 2732fe33eb39SLuis R. Rodriguez schedule_work(®_work); 2733fe33eb39SLuis R. Rodriguez } 2734fe33eb39SLuis R. Rodriguez 273509d989d1SLuis R. Rodriguez /* 273609d989d1SLuis R. Rodriguez * Core regulatory hint -- happens during cfg80211_init() 273709d989d1SLuis R. Rodriguez * and when we restore regulatory settings. 273809d989d1SLuis R. Rodriguez */ 2739ba25c141SLuis R. Rodriguez static int regulatory_hint_core(const char *alpha2) 2740ba25c141SLuis R. Rodriguez { 2741ba25c141SLuis R. Rodriguez struct regulatory_request *request; 2742ba25c141SLuis R. Rodriguez 27431a919318SJohannes Berg request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL); 2744ba25c141SLuis R. Rodriguez if (!request) 2745ba25c141SLuis R. Rodriguez return -ENOMEM; 2746ba25c141SLuis R. Rodriguez 2747ba25c141SLuis R. Rodriguez request->alpha2[0] = alpha2[0]; 2748ba25c141SLuis R. Rodriguez request->alpha2[1] = alpha2[1]; 27497db90f4aSLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_CORE; 2750ba25c141SLuis R. Rodriguez 275131e99729SLuis R. Rodriguez queue_regulatory_request(request); 27525078b2e3SLuis R. Rodriguez 2753fe33eb39SLuis R. Rodriguez return 0; 2754ba25c141SLuis R. Rodriguez } 2755ba25c141SLuis R. Rodriguez 2756fe33eb39SLuis R. Rodriguez /* User hints */ 275757b5ce07SLuis R. Rodriguez int regulatory_hint_user(const char *alpha2, 275857b5ce07SLuis R. Rodriguez enum nl80211_user_reg_hint_type user_reg_hint_type) 2759b2e1b302SLuis R. Rodriguez { 2760fe33eb39SLuis R. Rodriguez struct regulatory_request *request; 2761fe33eb39SLuis R. Rodriguez 2762fdc9d7b2SJohannes Berg if (WARN_ON(!alpha2)) 2763fdc9d7b2SJohannes Berg return -EINVAL; 2764b2e1b302SLuis R. Rodriguez 2765fe33eb39SLuis R. Rodriguez request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL); 2766fe33eb39SLuis R. Rodriguez if (!request) 2767fe33eb39SLuis R. Rodriguez return -ENOMEM; 2768fe33eb39SLuis R. Rodriguez 2769f4173766SJohannes Berg request->wiphy_idx = WIPHY_IDX_INVALID; 2770fe33eb39SLuis R. Rodriguez request->alpha2[0] = alpha2[0]; 2771fe33eb39SLuis R. Rodriguez request->alpha2[1] = alpha2[1]; 2772e12822e1SLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_USER; 277357b5ce07SLuis R. Rodriguez request->user_reg_hint_type = user_reg_hint_type; 2774fe33eb39SLuis R. Rodriguez 2775c37722bdSIlan peer /* Allow calling CRDA again */ 2776b6863036SJohannes Berg reset_crda_timeouts(); 2777c37722bdSIlan peer 2778fe33eb39SLuis R. Rodriguez queue_regulatory_request(request); 2779fe33eb39SLuis R. Rodriguez 2780fe33eb39SLuis R. Rodriguez return 0; 2781fe33eb39SLuis R. Rodriguez } 2782fe33eb39SLuis R. Rodriguez 278305050753SIlan peer int regulatory_hint_indoor(bool is_indoor, u32 portid) 278452616f2bSIlan Peer { 278505050753SIlan peer spin_lock(®_indoor_lock); 278652616f2bSIlan Peer 278705050753SIlan peer /* It is possible that more than one user space process is trying to 278805050753SIlan peer * configure the indoor setting. To handle such cases, clear the indoor 278905050753SIlan peer * setting in case that some process does not think that the device 279005050753SIlan peer * is operating in an indoor environment. In addition, if a user space 279105050753SIlan peer * process indicates that it is controlling the indoor setting, save its 279205050753SIlan peer * portid, i.e., make it the owner. 279305050753SIlan peer */ 279405050753SIlan peer reg_is_indoor = is_indoor; 279505050753SIlan peer if (reg_is_indoor) { 279605050753SIlan peer if (!reg_is_indoor_portid) 279705050753SIlan peer reg_is_indoor_portid = portid; 279805050753SIlan peer } else { 279905050753SIlan peer reg_is_indoor_portid = 0; 280005050753SIlan peer } 280152616f2bSIlan Peer 280205050753SIlan peer spin_unlock(®_indoor_lock); 280305050753SIlan peer 280405050753SIlan peer if (!is_indoor) 280505050753SIlan peer reg_check_channels(); 280652616f2bSIlan Peer 280752616f2bSIlan Peer return 0; 280852616f2bSIlan Peer } 280952616f2bSIlan Peer 281005050753SIlan peer void regulatory_netlink_notify(u32 portid) 281105050753SIlan peer { 281205050753SIlan peer spin_lock(®_indoor_lock); 281305050753SIlan peer 281405050753SIlan peer if (reg_is_indoor_portid != portid) { 281505050753SIlan peer spin_unlock(®_indoor_lock); 281605050753SIlan peer return; 281705050753SIlan peer } 281805050753SIlan peer 281905050753SIlan peer reg_is_indoor = false; 282005050753SIlan peer reg_is_indoor_portid = 0; 282105050753SIlan peer 282205050753SIlan peer spin_unlock(®_indoor_lock); 282305050753SIlan peer 282405050753SIlan peer reg_check_channels(); 282505050753SIlan peer } 282605050753SIlan peer 2827fe33eb39SLuis R. Rodriguez /* Driver hints */ 2828fe33eb39SLuis R. Rodriguez int regulatory_hint(struct wiphy *wiphy, const char *alpha2) 2829fe33eb39SLuis R. Rodriguez { 2830fe33eb39SLuis R. Rodriguez struct regulatory_request *request; 2831fe33eb39SLuis R. Rodriguez 2832fdc9d7b2SJohannes Berg if (WARN_ON(!alpha2 || !wiphy)) 2833fdc9d7b2SJohannes Berg return -EINVAL; 2834fe33eb39SLuis R. Rodriguez 28354f7b9140SLuis R. Rodriguez wiphy->regulatory_flags &= ~REGULATORY_CUSTOM_REG; 28364f7b9140SLuis R. Rodriguez 2837fe33eb39SLuis R. Rodriguez request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL); 2838fe33eb39SLuis R. Rodriguez if (!request) 2839fe33eb39SLuis R. Rodriguez return -ENOMEM; 2840fe33eb39SLuis R. Rodriguez 2841fe33eb39SLuis R. Rodriguez request->wiphy_idx = get_wiphy_idx(wiphy); 2842fe33eb39SLuis R. Rodriguez 2843fe33eb39SLuis R. Rodriguez request->alpha2[0] = alpha2[0]; 2844fe33eb39SLuis R. Rodriguez request->alpha2[1] = alpha2[1]; 28457db90f4aSLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_DRIVER; 2846fe33eb39SLuis R. Rodriguez 2847c37722bdSIlan peer /* Allow calling CRDA again */ 2848b6863036SJohannes Berg reset_crda_timeouts(); 2849c37722bdSIlan peer 2850fe33eb39SLuis R. Rodriguez queue_regulatory_request(request); 2851fe33eb39SLuis R. Rodriguez 2852fe33eb39SLuis R. Rodriguez return 0; 2853b2e1b302SLuis R. Rodriguez } 2854b2e1b302SLuis R. Rodriguez EXPORT_SYMBOL(regulatory_hint); 2855b2e1b302SLuis R. Rodriguez 285657fbcce3SJohannes Berg void regulatory_hint_country_ie(struct wiphy *wiphy, enum nl80211_band band, 28571a919318SJohannes Berg const u8 *country_ie, u8 country_ie_len) 28583f2355cbSLuis R. Rodriguez { 28593f2355cbSLuis R. Rodriguez char alpha2[2]; 28603f2355cbSLuis R. Rodriguez enum environment_cap env = ENVIRON_ANY; 2861db2424c5SJohannes Berg struct regulatory_request *request = NULL, *lr; 2862d335fe63SLuis R. Rodriguez 28633f2355cbSLuis R. Rodriguez /* IE len must be evenly divisible by 2 */ 28643f2355cbSLuis R. Rodriguez if (country_ie_len & 0x01) 2865db2424c5SJohannes Berg return; 28663f2355cbSLuis R. Rodriguez 28673f2355cbSLuis R. Rodriguez if (country_ie_len < IEEE80211_COUNTRY_IE_MIN_LEN) 2868db2424c5SJohannes Berg return; 2869db2424c5SJohannes Berg 2870db2424c5SJohannes Berg request = kzalloc(sizeof(*request), GFP_KERNEL); 2871db2424c5SJohannes Berg if (!request) 2872db2424c5SJohannes Berg return; 28733f2355cbSLuis R. Rodriguez 28743f2355cbSLuis R. Rodriguez alpha2[0] = country_ie[0]; 28753f2355cbSLuis R. Rodriguez alpha2[1] = country_ie[1]; 28763f2355cbSLuis R. Rodriguez 28773f2355cbSLuis R. Rodriguez if (country_ie[2] == 'I') 28783f2355cbSLuis R. Rodriguez env = ENVIRON_INDOOR; 28793f2355cbSLuis R. Rodriguez else if (country_ie[2] == 'O') 28803f2355cbSLuis R. Rodriguez env = ENVIRON_OUTDOOR; 28813f2355cbSLuis R. Rodriguez 2882db2424c5SJohannes Berg rcu_read_lock(); 2883db2424c5SJohannes Berg lr = get_last_request(); 2884db2424c5SJohannes Berg 2885db2424c5SJohannes Berg if (unlikely(!lr)) 2886db2424c5SJohannes Berg goto out; 2887db2424c5SJohannes Berg 2888fb1fc7adSLuis R. Rodriguez /* 28898b19e6caSLuis R. Rodriguez * We will run this only upon a successful connection on cfg80211. 28904b44c8bcSLuis R. Rodriguez * We leave conflict resolution to the workqueue, where can hold 28915fe231e8SJohannes Berg * the RTNL. 2892fb1fc7adSLuis R. Rodriguez */ 2893c492db37SJohannes Berg if (lr->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE && 2894c492db37SJohannes Berg lr->wiphy_idx != WIPHY_IDX_INVALID) 28953f2355cbSLuis R. Rodriguez goto out; 28963f2355cbSLuis R. Rodriguez 2897fe33eb39SLuis R. Rodriguez request->wiphy_idx = get_wiphy_idx(wiphy); 28984f366c5dSJohn W. Linville request->alpha2[0] = alpha2[0]; 28994f366c5dSJohn W. Linville request->alpha2[1] = alpha2[1]; 29007db90f4aSLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_COUNTRY_IE; 2901fe33eb39SLuis R. Rodriguez request->country_ie_env = env; 29023f2355cbSLuis R. Rodriguez 2903c37722bdSIlan peer /* Allow calling CRDA again */ 2904b6863036SJohannes Berg reset_crda_timeouts(); 2905c37722bdSIlan peer 2906fe33eb39SLuis R. Rodriguez queue_regulatory_request(request); 2907db2424c5SJohannes Berg request = NULL; 29083f2355cbSLuis R. Rodriguez out: 2909db2424c5SJohannes Berg kfree(request); 2910db2424c5SJohannes Berg rcu_read_unlock(); 29113f2355cbSLuis R. Rodriguez } 2912b2e1b302SLuis R. Rodriguez 291309d989d1SLuis R. Rodriguez static void restore_alpha2(char *alpha2, bool reset_user) 291409d989d1SLuis R. Rodriguez { 291509d989d1SLuis R. Rodriguez /* indicates there is no alpha2 to consider for restoration */ 291609d989d1SLuis R. Rodriguez alpha2[0] = '9'; 291709d989d1SLuis R. Rodriguez alpha2[1] = '7'; 291809d989d1SLuis R. Rodriguez 291909d989d1SLuis R. Rodriguez /* The user setting has precedence over the module parameter */ 292009d989d1SLuis R. Rodriguez if (is_user_regdom_saved()) { 292109d989d1SLuis R. Rodriguez /* Unless we're asked to ignore it and reset it */ 292209d989d1SLuis R. Rodriguez if (reset_user) { 2923c799ba6eSJohannes Berg pr_debug("Restoring regulatory settings including user preference\n"); 292409d989d1SLuis R. Rodriguez user_alpha2[0] = '9'; 292509d989d1SLuis R. Rodriguez user_alpha2[1] = '7'; 292609d989d1SLuis R. Rodriguez 292709d989d1SLuis R. Rodriguez /* 292809d989d1SLuis R. Rodriguez * If we're ignoring user settings, we still need to 292909d989d1SLuis R. Rodriguez * check the module parameter to ensure we put things 293009d989d1SLuis R. Rodriguez * back as they were for a full restore. 293109d989d1SLuis R. Rodriguez */ 293209d989d1SLuis R. Rodriguez if (!is_world_regdom(ieee80211_regdom)) { 2933c799ba6eSJohannes Berg pr_debug("Keeping preference on module parameter ieee80211_regdom: %c%c\n", 29341a919318SJohannes Berg ieee80211_regdom[0], ieee80211_regdom[1]); 293509d989d1SLuis R. Rodriguez alpha2[0] = ieee80211_regdom[0]; 293609d989d1SLuis R. Rodriguez alpha2[1] = ieee80211_regdom[1]; 293709d989d1SLuis R. Rodriguez } 293809d989d1SLuis R. Rodriguez } else { 2939c799ba6eSJohannes Berg pr_debug("Restoring regulatory settings while preserving user preference for: %c%c\n", 29401a919318SJohannes Berg user_alpha2[0], user_alpha2[1]); 294109d989d1SLuis R. Rodriguez alpha2[0] = user_alpha2[0]; 294209d989d1SLuis R. Rodriguez alpha2[1] = user_alpha2[1]; 294309d989d1SLuis R. Rodriguez } 294409d989d1SLuis R. Rodriguez } else if (!is_world_regdom(ieee80211_regdom)) { 2945c799ba6eSJohannes Berg pr_debug("Keeping preference on module parameter ieee80211_regdom: %c%c\n", 29461a919318SJohannes Berg ieee80211_regdom[0], ieee80211_regdom[1]); 294709d989d1SLuis R. Rodriguez alpha2[0] = ieee80211_regdom[0]; 294809d989d1SLuis R. Rodriguez alpha2[1] = ieee80211_regdom[1]; 294909d989d1SLuis R. Rodriguez } else 2950c799ba6eSJohannes Berg pr_debug("Restoring regulatory settings\n"); 295109d989d1SLuis R. Rodriguez } 295209d989d1SLuis R. Rodriguez 29535ce543d1SRajkumar Manoharan static void restore_custom_reg_settings(struct wiphy *wiphy) 29545ce543d1SRajkumar Manoharan { 29555ce543d1SRajkumar Manoharan struct ieee80211_supported_band *sband; 295657fbcce3SJohannes Berg enum nl80211_band band; 29575ce543d1SRajkumar Manoharan struct ieee80211_channel *chan; 29585ce543d1SRajkumar Manoharan int i; 29595ce543d1SRajkumar Manoharan 296057fbcce3SJohannes Berg for (band = 0; band < NUM_NL80211_BANDS; band++) { 29615ce543d1SRajkumar Manoharan sband = wiphy->bands[band]; 29625ce543d1SRajkumar Manoharan if (!sband) 29635ce543d1SRajkumar Manoharan continue; 29645ce543d1SRajkumar Manoharan for (i = 0; i < sband->n_channels; i++) { 29655ce543d1SRajkumar Manoharan chan = &sband->channels[i]; 29665ce543d1SRajkumar Manoharan chan->flags = chan->orig_flags; 29675ce543d1SRajkumar Manoharan chan->max_antenna_gain = chan->orig_mag; 29685ce543d1SRajkumar Manoharan chan->max_power = chan->orig_mpwr; 2969899852afSPaul Stewart chan->beacon_found = false; 29705ce543d1SRajkumar Manoharan } 29715ce543d1SRajkumar Manoharan } 29725ce543d1SRajkumar Manoharan } 29735ce543d1SRajkumar Manoharan 297409d989d1SLuis R. Rodriguez /* 297509d989d1SLuis R. Rodriguez * Restoring regulatory settings involves ingoring any 297609d989d1SLuis R. Rodriguez * possibly stale country IE information and user regulatory 297709d989d1SLuis R. Rodriguez * settings if so desired, this includes any beacon hints 297809d989d1SLuis R. Rodriguez * learned as we could have traveled outside to another country 297909d989d1SLuis R. Rodriguez * after disconnection. To restore regulatory settings we do 298009d989d1SLuis R. Rodriguez * exactly what we did at bootup: 298109d989d1SLuis R. Rodriguez * 298209d989d1SLuis R. Rodriguez * - send a core regulatory hint 298309d989d1SLuis R. Rodriguez * - send a user regulatory hint if applicable 298409d989d1SLuis R. Rodriguez * 298509d989d1SLuis R. Rodriguez * Device drivers that send a regulatory hint for a specific country 298609d989d1SLuis R. Rodriguez * keep their own regulatory domain on wiphy->regd so that does does 298709d989d1SLuis R. Rodriguez * not need to be remembered. 298809d989d1SLuis R. Rodriguez */ 298909d989d1SLuis R. Rodriguez static void restore_regulatory_settings(bool reset_user) 299009d989d1SLuis R. Rodriguez { 299109d989d1SLuis R. Rodriguez char alpha2[2]; 2992cee0bec5SDmitry Shmidt char world_alpha2[2]; 299309d989d1SLuis R. Rodriguez struct reg_beacon *reg_beacon, *btmp; 299414609555SLuis R. Rodriguez LIST_HEAD(tmp_reg_req_list); 29955ce543d1SRajkumar Manoharan struct cfg80211_registered_device *rdev; 299609d989d1SLuis R. Rodriguez 29975fe231e8SJohannes Berg ASSERT_RTNL(); 29985fe231e8SJohannes Berg 299905050753SIlan peer /* 300005050753SIlan peer * Clear the indoor setting in case that it is not controlled by user 300105050753SIlan peer * space, as otherwise there is no guarantee that the device is still 300205050753SIlan peer * operating in an indoor environment. 300305050753SIlan peer */ 300405050753SIlan peer spin_lock(®_indoor_lock); 300505050753SIlan peer if (reg_is_indoor && !reg_is_indoor_portid) { 300652616f2bSIlan Peer reg_is_indoor = false; 300705050753SIlan peer reg_check_channels(); 300805050753SIlan peer } 300905050753SIlan peer spin_unlock(®_indoor_lock); 301052616f2bSIlan Peer 30112d319867SJohannes Berg reset_regdomains(true, &world_regdom); 301209d989d1SLuis R. Rodriguez restore_alpha2(alpha2, reset_user); 301309d989d1SLuis R. Rodriguez 301414609555SLuis R. Rodriguez /* 301514609555SLuis R. Rodriguez * If there's any pending requests we simply 301614609555SLuis R. Rodriguez * stash them to a temporary pending queue and 301714609555SLuis R. Rodriguez * add then after we've restored regulatory 301814609555SLuis R. Rodriguez * settings. 301914609555SLuis R. Rodriguez */ 302014609555SLuis R. Rodriguez spin_lock(®_requests_lock); 3021eeca9fceSIlan peer list_splice_tail_init(®_requests_list, &tmp_reg_req_list); 302214609555SLuis R. Rodriguez spin_unlock(®_requests_lock); 302314609555SLuis R. Rodriguez 302409d989d1SLuis R. Rodriguez /* Clear beacon hints */ 302509d989d1SLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 3026fea9bcedSJohannes Berg list_for_each_entry_safe(reg_beacon, btmp, ®_pending_beacons, list) { 302709d989d1SLuis R. Rodriguez list_del(®_beacon->list); 302809d989d1SLuis R. Rodriguez kfree(reg_beacon); 302909d989d1SLuis R. Rodriguez } 303009d989d1SLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 303109d989d1SLuis R. Rodriguez 3032fea9bcedSJohannes Berg list_for_each_entry_safe(reg_beacon, btmp, ®_beacon_list, list) { 303309d989d1SLuis R. Rodriguez list_del(®_beacon->list); 303409d989d1SLuis R. Rodriguez kfree(reg_beacon); 303509d989d1SLuis R. Rodriguez } 303609d989d1SLuis R. Rodriguez 303709d989d1SLuis R. Rodriguez /* First restore to the basic regulatory settings */ 3038379b82f4SJohannes Berg world_alpha2[0] = cfg80211_world_regdom->alpha2[0]; 3039379b82f4SJohannes Berg world_alpha2[1] = cfg80211_world_regdom->alpha2[1]; 304009d989d1SLuis R. Rodriguez 30415ce543d1SRajkumar Manoharan list_for_each_entry(rdev, &cfg80211_rdev_list, list) { 3042b0d7aa59SJonathan Doron if (rdev->wiphy.regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED) 3043b0d7aa59SJonathan Doron continue; 3044a2f73b6cSLuis R. Rodriguez if (rdev->wiphy.regulatory_flags & REGULATORY_CUSTOM_REG) 30455ce543d1SRajkumar Manoharan restore_custom_reg_settings(&rdev->wiphy); 30465ce543d1SRajkumar Manoharan } 30475ce543d1SRajkumar Manoharan 3048cee0bec5SDmitry Shmidt regulatory_hint_core(world_alpha2); 304909d989d1SLuis R. Rodriguez 305009d989d1SLuis R. Rodriguez /* 305109d989d1SLuis R. Rodriguez * This restores the ieee80211_regdom module parameter 305209d989d1SLuis R. Rodriguez * preference or the last user requested regulatory 305309d989d1SLuis R. Rodriguez * settings, user regulatory settings takes precedence. 305409d989d1SLuis R. Rodriguez */ 305509d989d1SLuis R. Rodriguez if (is_an_alpha2(alpha2)) 3056549cc1c5SMaciej S. Szmigiero regulatory_hint_user(alpha2, NL80211_USER_REG_HINT_USER); 305709d989d1SLuis R. Rodriguez 305814609555SLuis R. Rodriguez spin_lock(®_requests_lock); 305911cff96cSJohannes Berg list_splice_tail_init(&tmp_reg_req_list, ®_requests_list); 306014609555SLuis R. Rodriguez spin_unlock(®_requests_lock); 306114609555SLuis R. Rodriguez 3062c799ba6eSJohannes Berg pr_debug("Kicking the queue\n"); 306314609555SLuis R. Rodriguez 306414609555SLuis R. Rodriguez schedule_work(®_work); 306514609555SLuis R. Rodriguez } 306609d989d1SLuis R. Rodriguez 306709d989d1SLuis R. Rodriguez void regulatory_hint_disconnect(void) 306809d989d1SLuis R. Rodriguez { 3069c799ba6eSJohannes Berg pr_debug("All devices are disconnected, going to restore regulatory settings\n"); 307009d989d1SLuis R. Rodriguez restore_regulatory_settings(false); 307109d989d1SLuis R. Rodriguez } 307209d989d1SLuis R. Rodriguez 3073e38f8a7aSLuis R. Rodriguez static bool freq_is_chan_12_13_14(u16 freq) 3074e38f8a7aSLuis R. Rodriguez { 307557fbcce3SJohannes Berg if (freq == ieee80211_channel_to_frequency(12, NL80211_BAND_2GHZ) || 307657fbcce3SJohannes Berg freq == ieee80211_channel_to_frequency(13, NL80211_BAND_2GHZ) || 307757fbcce3SJohannes Berg freq == ieee80211_channel_to_frequency(14, NL80211_BAND_2GHZ)) 3078e38f8a7aSLuis R. Rodriguez return true; 3079e38f8a7aSLuis R. Rodriguez return false; 3080e38f8a7aSLuis R. Rodriguez } 3081e38f8a7aSLuis R. Rodriguez 30823ebfa6e7SLuis R. Rodriguez static bool pending_reg_beacon(struct ieee80211_channel *beacon_chan) 30833ebfa6e7SLuis R. Rodriguez { 30843ebfa6e7SLuis R. Rodriguez struct reg_beacon *pending_beacon; 30853ebfa6e7SLuis R. Rodriguez 30863ebfa6e7SLuis R. Rodriguez list_for_each_entry(pending_beacon, ®_pending_beacons, list) 30873ebfa6e7SLuis R. Rodriguez if (beacon_chan->center_freq == 30883ebfa6e7SLuis R. Rodriguez pending_beacon->chan.center_freq) 30893ebfa6e7SLuis R. Rodriguez return true; 30903ebfa6e7SLuis R. Rodriguez return false; 30913ebfa6e7SLuis R. Rodriguez } 30923ebfa6e7SLuis R. Rodriguez 3093e38f8a7aSLuis R. Rodriguez int regulatory_hint_found_beacon(struct wiphy *wiphy, 3094e38f8a7aSLuis R. Rodriguez struct ieee80211_channel *beacon_chan, 3095e38f8a7aSLuis R. Rodriguez gfp_t gfp) 3096e38f8a7aSLuis R. Rodriguez { 3097e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon; 30983ebfa6e7SLuis R. Rodriguez bool processing; 3099e38f8a7aSLuis R. Rodriguez 31001a919318SJohannes Berg if (beacon_chan->beacon_found || 31011a919318SJohannes Berg beacon_chan->flags & IEEE80211_CHAN_RADAR || 310257fbcce3SJohannes Berg (beacon_chan->band == NL80211_BAND_2GHZ && 31031a919318SJohannes Berg !freq_is_chan_12_13_14(beacon_chan->center_freq))) 3104e38f8a7aSLuis R. Rodriguez return 0; 3105e38f8a7aSLuis R. Rodriguez 31063ebfa6e7SLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 31073ebfa6e7SLuis R. Rodriguez processing = pending_reg_beacon(beacon_chan); 31083ebfa6e7SLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 31093ebfa6e7SLuis R. Rodriguez 31103ebfa6e7SLuis R. Rodriguez if (processing) 3111e38f8a7aSLuis R. Rodriguez return 0; 3112e38f8a7aSLuis R. Rodriguez 3113e38f8a7aSLuis R. Rodriguez reg_beacon = kzalloc(sizeof(struct reg_beacon), gfp); 3114e38f8a7aSLuis R. Rodriguez if (!reg_beacon) 3115e38f8a7aSLuis R. Rodriguez return -ENOMEM; 3116e38f8a7aSLuis R. Rodriguez 3117c799ba6eSJohannes Berg pr_debug("Found new beacon on frequency: %d MHz (Ch %d) on %s\n", 3118e38f8a7aSLuis R. Rodriguez beacon_chan->center_freq, 3119e38f8a7aSLuis R. Rodriguez ieee80211_frequency_to_channel(beacon_chan->center_freq), 3120e38f8a7aSLuis R. Rodriguez wiphy_name(wiphy)); 31214113f751SLuis R. Rodriguez 3122e38f8a7aSLuis R. Rodriguez memcpy(®_beacon->chan, beacon_chan, 3123e38f8a7aSLuis R. Rodriguez sizeof(struct ieee80211_channel)); 3124e38f8a7aSLuis R. Rodriguez 3125e38f8a7aSLuis R. Rodriguez /* 3126e38f8a7aSLuis R. Rodriguez * Since we can be called from BH or and non-BH context 3127e38f8a7aSLuis R. Rodriguez * we must use spin_lock_bh() 3128e38f8a7aSLuis R. Rodriguez */ 3129e38f8a7aSLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 3130e38f8a7aSLuis R. Rodriguez list_add_tail(®_beacon->list, ®_pending_beacons); 3131e38f8a7aSLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 3132e38f8a7aSLuis R. Rodriguez 3133e38f8a7aSLuis R. Rodriguez schedule_work(®_work); 3134e38f8a7aSLuis R. Rodriguez 3135e38f8a7aSLuis R. Rodriguez return 0; 3136e38f8a7aSLuis R. Rodriguez } 3137e38f8a7aSLuis R. Rodriguez 3138a3d2eaf0SJohannes Berg static void print_rd_rules(const struct ieee80211_regdomain *rd) 3139b2e1b302SLuis R. Rodriguez { 3140b2e1b302SLuis R. Rodriguez unsigned int i; 3141a3d2eaf0SJohannes Berg const struct ieee80211_reg_rule *reg_rule = NULL; 3142a3d2eaf0SJohannes Berg const struct ieee80211_freq_range *freq_range = NULL; 3143a3d2eaf0SJohannes Berg const struct ieee80211_power_rule *power_rule = NULL; 3144089027e5SJanusz Dziedzic char bw[32], cac_time[32]; 3145b2e1b302SLuis R. Rodriguez 314694c4fd64SDave Young pr_debug(" (start_freq - end_freq @ bandwidth), (max_antenna_gain, max_eirp), (dfs_cac_time)\n"); 3147b2e1b302SLuis R. Rodriguez 3148b2e1b302SLuis R. Rodriguez for (i = 0; i < rd->n_reg_rules; i++) { 3149b2e1b302SLuis R. Rodriguez reg_rule = &rd->reg_rules[i]; 3150b2e1b302SLuis R. Rodriguez freq_range = ®_rule->freq_range; 3151b2e1b302SLuis R. Rodriguez power_rule = ®_rule->power_rule; 3152b2e1b302SLuis R. Rodriguez 3153b0dfd2eaSJanusz Dziedzic if (reg_rule->flags & NL80211_RRF_AUTO_BW) 3154b0dfd2eaSJanusz Dziedzic snprintf(bw, sizeof(bw), "%d KHz, %d KHz AUTO", 3155b0dfd2eaSJanusz Dziedzic freq_range->max_bandwidth_khz, 315697524820SJanusz Dziedzic reg_get_max_bandwidth(rd, reg_rule)); 315797524820SJanusz Dziedzic else 3158b0dfd2eaSJanusz Dziedzic snprintf(bw, sizeof(bw), "%d KHz", 315997524820SJanusz Dziedzic freq_range->max_bandwidth_khz); 316097524820SJanusz Dziedzic 3161089027e5SJanusz Dziedzic if (reg_rule->flags & NL80211_RRF_DFS) 3162089027e5SJanusz Dziedzic scnprintf(cac_time, sizeof(cac_time), "%u s", 3163089027e5SJanusz Dziedzic reg_rule->dfs_cac_ms/1000); 3164089027e5SJanusz Dziedzic else 3165089027e5SJanusz Dziedzic scnprintf(cac_time, sizeof(cac_time), "N/A"); 3166089027e5SJanusz Dziedzic 3167089027e5SJanusz Dziedzic 3168fb1fc7adSLuis R. Rodriguez /* 3169fb1fc7adSLuis R. Rodriguez * There may not be documentation for max antenna gain 3170fb1fc7adSLuis R. Rodriguez * in certain regions 3171fb1fc7adSLuis R. Rodriguez */ 3172b2e1b302SLuis R. Rodriguez if (power_rule->max_antenna_gain) 317394c4fd64SDave Young pr_debug(" (%d KHz - %d KHz @ %s), (%d mBi, %d mBm), (%s)\n", 3174b2e1b302SLuis R. Rodriguez freq_range->start_freq_khz, 3175b2e1b302SLuis R. Rodriguez freq_range->end_freq_khz, 317697524820SJanusz Dziedzic bw, 3177b2e1b302SLuis R. Rodriguez power_rule->max_antenna_gain, 3178089027e5SJanusz Dziedzic power_rule->max_eirp, 3179089027e5SJanusz Dziedzic cac_time); 3180b2e1b302SLuis R. Rodriguez else 318194c4fd64SDave Young pr_debug(" (%d KHz - %d KHz @ %s), (N/A, %d mBm), (%s)\n", 3182b2e1b302SLuis R. Rodriguez freq_range->start_freq_khz, 3183b2e1b302SLuis R. Rodriguez freq_range->end_freq_khz, 318497524820SJanusz Dziedzic bw, 3185089027e5SJanusz Dziedzic power_rule->max_eirp, 3186089027e5SJanusz Dziedzic cac_time); 3187b2e1b302SLuis R. Rodriguez } 3188b2e1b302SLuis R. Rodriguez } 3189b2e1b302SLuis R. Rodriguez 31904c7d3982SLuis R. Rodriguez bool reg_supported_dfs_region(enum nl80211_dfs_regions dfs_region) 31918b60b078SLuis R. Rodriguez { 31928b60b078SLuis R. Rodriguez switch (dfs_region) { 31938b60b078SLuis R. Rodriguez case NL80211_DFS_UNSET: 31948b60b078SLuis R. Rodriguez case NL80211_DFS_FCC: 31958b60b078SLuis R. Rodriguez case NL80211_DFS_ETSI: 31968b60b078SLuis R. Rodriguez case NL80211_DFS_JP: 31978b60b078SLuis R. Rodriguez return true; 31988b60b078SLuis R. Rodriguez default: 3199c799ba6eSJohannes Berg pr_debug("Ignoring uknown DFS master region: %d\n", dfs_region); 32008b60b078SLuis R. Rodriguez return false; 32018b60b078SLuis R. Rodriguez } 32028b60b078SLuis R. Rodriguez } 32038b60b078SLuis R. Rodriguez 3204a3d2eaf0SJohannes Berg static void print_regdomain(const struct ieee80211_regdomain *rd) 3205b2e1b302SLuis R. Rodriguez { 3206c492db37SJohannes Berg struct regulatory_request *lr = get_last_request(); 3207b2e1b302SLuis R. Rodriguez 32083f2355cbSLuis R. Rodriguez if (is_intersected_alpha2(rd->alpha2)) { 3209c492db37SJohannes Berg if (lr->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE) { 321079c97e97SJohannes Berg struct cfg80211_registered_device *rdev; 3211c492db37SJohannes Berg rdev = cfg80211_rdev_by_wiphy_idx(lr->wiphy_idx); 321279c97e97SJohannes Berg if (rdev) { 321394c4fd64SDave Young pr_debug("Current regulatory domain updated by AP to: %c%c\n", 321479c97e97SJohannes Berg rdev->country_ie_alpha2[0], 321579c97e97SJohannes Berg rdev->country_ie_alpha2[1]); 32163f2355cbSLuis R. Rodriguez } else 321794c4fd64SDave Young pr_debug("Current regulatory domain intersected:\n"); 32183f2355cbSLuis R. Rodriguez } else 321994c4fd64SDave Young pr_debug("Current regulatory domain intersected:\n"); 32201a919318SJohannes Berg } else if (is_world_regdom(rd->alpha2)) { 322194c4fd64SDave Young pr_debug("World regulatory domain updated:\n"); 32221a919318SJohannes Berg } else { 3223b2e1b302SLuis R. Rodriguez if (is_unknown_alpha2(rd->alpha2)) 322494c4fd64SDave Young pr_debug("Regulatory domain changed to driver built-in settings (unknown country)\n"); 322557b5ce07SLuis R. Rodriguez else { 3226c492db37SJohannes Berg if (reg_request_cell_base(lr)) 322794c4fd64SDave Young pr_debug("Regulatory domain changed to country: %c%c by Cell Station\n", 3228b2e1b302SLuis R. Rodriguez rd->alpha2[0], rd->alpha2[1]); 322957b5ce07SLuis R. Rodriguez else 323094c4fd64SDave Young pr_debug("Regulatory domain changed to country: %c%c\n", 323157b5ce07SLuis R. Rodriguez rd->alpha2[0], rd->alpha2[1]); 323257b5ce07SLuis R. Rodriguez } 3233b2e1b302SLuis R. Rodriguez } 32341a919318SJohannes Berg 323594c4fd64SDave Young pr_debug(" DFS Master region: %s", reg_dfs_region_str(rd->dfs_region)); 3236b2e1b302SLuis R. Rodriguez print_rd_rules(rd); 3237b2e1b302SLuis R. Rodriguez } 3238b2e1b302SLuis R. Rodriguez 32392df78167SJohannes Berg static void print_regdomain_info(const struct ieee80211_regdomain *rd) 3240b2e1b302SLuis R. Rodriguez { 324194c4fd64SDave Young pr_debug("Regulatory domain: %c%c\n", rd->alpha2[0], rd->alpha2[1]); 3242b2e1b302SLuis R. Rodriguez print_rd_rules(rd); 3243b2e1b302SLuis R. Rodriguez } 3244b2e1b302SLuis R. Rodriguez 32453b9e5acaSLuis R. Rodriguez static int reg_set_rd_core(const struct ieee80211_regdomain *rd) 32463b9e5acaSLuis R. Rodriguez { 32473b9e5acaSLuis R. Rodriguez if (!is_world_regdom(rd->alpha2)) 32483b9e5acaSLuis R. Rodriguez return -EINVAL; 32493b9e5acaSLuis R. Rodriguez update_world_regdomain(rd); 32503b9e5acaSLuis R. Rodriguez return 0; 32513b9e5acaSLuis R. Rodriguez } 32523b9e5acaSLuis R. Rodriguez 325384721d44SLuis R. Rodriguez static int reg_set_rd_user(const struct ieee80211_regdomain *rd, 325484721d44SLuis R. Rodriguez struct regulatory_request *user_request) 325584721d44SLuis R. Rodriguez { 325684721d44SLuis R. Rodriguez const struct ieee80211_regdomain *intersected_rd = NULL; 325784721d44SLuis R. Rodriguez 325884721d44SLuis R. Rodriguez if (!regdom_changes(rd->alpha2)) 325984721d44SLuis R. Rodriguez return -EALREADY; 326084721d44SLuis R. Rodriguez 326184721d44SLuis R. Rodriguez if (!is_valid_rd(rd)) { 326294c4fd64SDave Young pr_err("Invalid regulatory domain detected: %c%c\n", 326394c4fd64SDave Young rd->alpha2[0], rd->alpha2[1]); 326484721d44SLuis R. Rodriguez print_regdomain_info(rd); 326584721d44SLuis R. Rodriguez return -EINVAL; 326684721d44SLuis R. Rodriguez } 326784721d44SLuis R. Rodriguez 326884721d44SLuis R. Rodriguez if (!user_request->intersect) { 326984721d44SLuis R. Rodriguez reset_regdomains(false, rd); 327084721d44SLuis R. Rodriguez return 0; 327184721d44SLuis R. Rodriguez } 327284721d44SLuis R. Rodriguez 327384721d44SLuis R. Rodriguez intersected_rd = regdom_intersect(rd, get_cfg80211_regdom()); 327484721d44SLuis R. Rodriguez if (!intersected_rd) 327584721d44SLuis R. Rodriguez return -EINVAL; 327684721d44SLuis R. Rodriguez 327784721d44SLuis R. Rodriguez kfree(rd); 327884721d44SLuis R. Rodriguez rd = NULL; 327984721d44SLuis R. Rodriguez reset_regdomains(false, intersected_rd); 328084721d44SLuis R. Rodriguez 328184721d44SLuis R. Rodriguez return 0; 328284721d44SLuis R. Rodriguez } 328384721d44SLuis R. Rodriguez 3284f5fe3247SLuis R. Rodriguez static int reg_set_rd_driver(const struct ieee80211_regdomain *rd, 3285f5fe3247SLuis R. Rodriguez struct regulatory_request *driver_request) 3286b2e1b302SLuis R. Rodriguez { 3287e9763c3cSJohannes Berg const struct ieee80211_regdomain *regd; 32889c96477dSLuis R. Rodriguez const struct ieee80211_regdomain *intersected_rd = NULL; 3289f5fe3247SLuis R. Rodriguez const struct ieee80211_regdomain *tmp; 3290806a9e39SLuis R. Rodriguez struct wiphy *request_wiphy; 32916913b49aSJohannes Berg 3292f5fe3247SLuis R. Rodriguez if (is_world_regdom(rd->alpha2)) 3293b2e1b302SLuis R. Rodriguez return -EINVAL; 3294b2e1b302SLuis R. Rodriguez 3295baeb66feSJohn W. Linville if (!regdom_changes(rd->alpha2)) 329695908535SKalle Valo return -EALREADY; 3297b2e1b302SLuis R. Rodriguez 3298b2e1b302SLuis R. Rodriguez if (!is_valid_rd(rd)) { 329994c4fd64SDave Young pr_err("Invalid regulatory domain detected: %c%c\n", 330094c4fd64SDave Young rd->alpha2[0], rd->alpha2[1]); 3301b2e1b302SLuis R. Rodriguez print_regdomain_info(rd); 3302b2e1b302SLuis R. Rodriguez return -EINVAL; 3303b2e1b302SLuis R. Rodriguez } 3304b2e1b302SLuis R. Rodriguez 3305f5fe3247SLuis R. Rodriguez request_wiphy = wiphy_idx_to_wiphy(driver_request->wiphy_idx); 3306922ec58cSJohannes Berg if (!request_wiphy) 3307de3584bdSJohannes Berg return -ENODEV; 3308806a9e39SLuis R. Rodriguez 3309f5fe3247SLuis R. Rodriguez if (!driver_request->intersect) { 3310558f6d32SLuis R. Rodriguez if (request_wiphy->regd) 3311558f6d32SLuis R. Rodriguez return -EALREADY; 33123e0c3ff3SLuis R. Rodriguez 3313e9763c3cSJohannes Berg regd = reg_copy_regd(rd); 3314e9763c3cSJohannes Berg if (IS_ERR(regd)) 3315e9763c3cSJohannes Berg return PTR_ERR(regd); 33163e0c3ff3SLuis R. Rodriguez 3317458f4f9eSJohannes Berg rcu_assign_pointer(request_wiphy->regd, regd); 3318379b82f4SJohannes Berg reset_regdomains(false, rd); 3319b8295acdSLuis R. Rodriguez return 0; 3320b8295acdSLuis R. Rodriguez } 3321b8295acdSLuis R. Rodriguez 3322458f4f9eSJohannes Berg intersected_rd = regdom_intersect(rd, get_cfg80211_regdom()); 33239c96477dSLuis R. Rodriguez if (!intersected_rd) 33249c96477dSLuis R. Rodriguez return -EINVAL; 3325b8295acdSLuis R. Rodriguez 3326fb1fc7adSLuis R. Rodriguez /* 3327fb1fc7adSLuis R. Rodriguez * We can trash what CRDA provided now. 33283e0c3ff3SLuis R. Rodriguez * However if a driver requested this specific regulatory 3329fb1fc7adSLuis R. Rodriguez * domain we keep it for its private use 3330fb1fc7adSLuis R. Rodriguez */ 3331b7566fc3SLarry Finger tmp = get_wiphy_regdom(request_wiphy); 3332458f4f9eSJohannes Berg rcu_assign_pointer(request_wiphy->regd, rd); 3333b7566fc3SLarry Finger rcu_free_regdom(tmp); 33343e0c3ff3SLuis R. Rodriguez 3335b8295acdSLuis R. Rodriguez rd = NULL; 3336b8295acdSLuis R. Rodriguez 3337379b82f4SJohannes Berg reset_regdomains(false, intersected_rd); 3338b8295acdSLuis R. Rodriguez 3339b8295acdSLuis R. Rodriguez return 0; 33409c96477dSLuis R. Rodriguez } 33419c96477dSLuis R. Rodriguez 334201992406SLuis R. Rodriguez static int reg_set_rd_country_ie(const struct ieee80211_regdomain *rd, 334301992406SLuis R. Rodriguez struct regulatory_request *country_ie_request) 3344f5fe3247SLuis R. Rodriguez { 3345f5fe3247SLuis R. Rodriguez struct wiphy *request_wiphy; 3346f5fe3247SLuis R. Rodriguez 3347f5fe3247SLuis R. Rodriguez if (!is_alpha2_set(rd->alpha2) && !is_an_alpha2(rd->alpha2) && 3348f5fe3247SLuis R. Rodriguez !is_unknown_alpha2(rd->alpha2)) 3349f5fe3247SLuis R. Rodriguez return -EINVAL; 3350f5fe3247SLuis R. Rodriguez 3351f5fe3247SLuis R. Rodriguez /* 3352f5fe3247SLuis R. Rodriguez * Lets only bother proceeding on the same alpha2 if the current 3353f5fe3247SLuis R. Rodriguez * rd is non static (it means CRDA was present and was used last) 3354f5fe3247SLuis R. Rodriguez * and the pending request came in from a country IE 3355f5fe3247SLuis R. Rodriguez */ 3356f5fe3247SLuis R. Rodriguez 3357f5fe3247SLuis R. Rodriguez if (!is_valid_rd(rd)) { 335894c4fd64SDave Young pr_err("Invalid regulatory domain detected: %c%c\n", 335994c4fd64SDave Young rd->alpha2[0], rd->alpha2[1]); 3360f5fe3247SLuis R. Rodriguez print_regdomain_info(rd); 33613f2355cbSLuis R. Rodriguez return -EINVAL; 3362b2e1b302SLuis R. Rodriguez } 3363b2e1b302SLuis R. Rodriguez 336401992406SLuis R. Rodriguez request_wiphy = wiphy_idx_to_wiphy(country_ie_request->wiphy_idx); 3365922ec58cSJohannes Berg if (!request_wiphy) 3366f5fe3247SLuis R. Rodriguez return -ENODEV; 3367f5fe3247SLuis R. Rodriguez 336801992406SLuis R. Rodriguez if (country_ie_request->intersect) 3369f5fe3247SLuis R. Rodriguez return -EINVAL; 3370f5fe3247SLuis R. Rodriguez 3371f5fe3247SLuis R. Rodriguez reset_regdomains(false, rd); 3372f5fe3247SLuis R. Rodriguez return 0; 3373f5fe3247SLuis R. Rodriguez } 3374b2e1b302SLuis R. Rodriguez 3375fb1fc7adSLuis R. Rodriguez /* 3376fb1fc7adSLuis R. Rodriguez * Use this call to set the current regulatory domain. Conflicts with 3377b2e1b302SLuis R. Rodriguez * multiple drivers can be ironed out later. Caller must've already 3378458f4f9eSJohannes Berg * kmalloc'd the rd structure. 3379fb1fc7adSLuis R. Rodriguez */ 3380c37722bdSIlan peer int set_regdom(const struct ieee80211_regdomain *rd, 3381c37722bdSIlan peer enum ieee80211_regd_source regd_src) 3382b2e1b302SLuis R. Rodriguez { 3383c492db37SJohannes Berg struct regulatory_request *lr; 3384092008abSJanusz Dziedzic bool user_reset = false; 3385b2e1b302SLuis R. Rodriguez int r; 3386b2e1b302SLuis R. Rodriguez 33873b9e5acaSLuis R. Rodriguez if (!reg_is_valid_request(rd->alpha2)) { 33883b9e5acaSLuis R. Rodriguez kfree(rd); 33893b9e5acaSLuis R. Rodriguez return -EINVAL; 33903b9e5acaSLuis R. Rodriguez } 33913b9e5acaSLuis R. Rodriguez 3392c37722bdSIlan peer if (regd_src == REGD_SOURCE_CRDA) 3393b6863036SJohannes Berg reset_crda_timeouts(); 3394c37722bdSIlan peer 3395c492db37SJohannes Berg lr = get_last_request(); 3396abc7381bSLuis R. Rodriguez 3397b2e1b302SLuis R. Rodriguez /* Note that this doesn't update the wiphys, this is done below */ 33983b9e5acaSLuis R. Rodriguez switch (lr->initiator) { 33993b9e5acaSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_CORE: 34003b9e5acaSLuis R. Rodriguez r = reg_set_rd_core(rd); 34013b9e5acaSLuis R. Rodriguez break; 34023b9e5acaSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_USER: 340384721d44SLuis R. Rodriguez r = reg_set_rd_user(rd, lr); 3404092008abSJanusz Dziedzic user_reset = true; 340584721d44SLuis R. Rodriguez break; 34063b9e5acaSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_DRIVER: 3407f5fe3247SLuis R. Rodriguez r = reg_set_rd_driver(rd, lr); 3408f5fe3247SLuis R. Rodriguez break; 34093b9e5acaSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_COUNTRY_IE: 341001992406SLuis R. Rodriguez r = reg_set_rd_country_ie(rd, lr); 34113b9e5acaSLuis R. Rodriguez break; 34123b9e5acaSLuis R. Rodriguez default: 34133b9e5acaSLuis R. Rodriguez WARN(1, "invalid initiator %d\n", lr->initiator); 341409d11800SOla Olsson kfree(rd); 34153b9e5acaSLuis R. Rodriguez return -EINVAL; 34163b9e5acaSLuis R. Rodriguez } 34173b9e5acaSLuis R. Rodriguez 3418d2372b31SJohannes Berg if (r) { 3419092008abSJanusz Dziedzic switch (r) { 3420092008abSJanusz Dziedzic case -EALREADY: 342195908535SKalle Valo reg_set_request_processed(); 3422092008abSJanusz Dziedzic break; 3423092008abSJanusz Dziedzic default: 3424092008abSJanusz Dziedzic /* Back to world regulatory in case of errors */ 3425092008abSJanusz Dziedzic restore_regulatory_settings(user_reset); 3426092008abSJanusz Dziedzic } 342795908535SKalle Valo 3428d2372b31SJohannes Berg kfree(rd); 342938fd2143SJohannes Berg return r; 3430d2372b31SJohannes Berg } 3431b2e1b302SLuis R. Rodriguez 3432b2e1b302SLuis R. Rodriguez /* This would make this whole thing pointless */ 343338fd2143SJohannes Berg if (WARN_ON(!lr->intersect && rd != get_cfg80211_regdom())) 343438fd2143SJohannes Berg return -EINVAL; 3435b2e1b302SLuis R. Rodriguez 3436b2e1b302SLuis R. Rodriguez /* update all wiphys now with the new established regulatory domain */ 3437c492db37SJohannes Berg update_all_wiphy_regulatory(lr->initiator); 3438b2e1b302SLuis R. Rodriguez 3439458f4f9eSJohannes Berg print_regdomain(get_cfg80211_regdom()); 3440b2e1b302SLuis R. Rodriguez 3441c492db37SJohannes Berg nl80211_send_reg_change_event(lr); 344273d54c9eSLuis R. Rodriguez 3443b2e253cfSLuis R. Rodriguez reg_set_request_processed(); 3444b2e253cfSLuis R. Rodriguez 344538fd2143SJohannes Berg return 0; 3446b2e1b302SLuis R. Rodriguez } 3447b2e1b302SLuis R. Rodriguez 34482c3e861cSArik Nemtsov static int __regulatory_set_wiphy_regd(struct wiphy *wiphy, 3449b0d7aa59SJonathan Doron struct ieee80211_regdomain *rd) 3450b0d7aa59SJonathan Doron { 3451b0d7aa59SJonathan Doron const struct ieee80211_regdomain *regd; 3452b0d7aa59SJonathan Doron const struct ieee80211_regdomain *prev_regd; 3453b0d7aa59SJonathan Doron struct cfg80211_registered_device *rdev; 3454b0d7aa59SJonathan Doron 3455b0d7aa59SJonathan Doron if (WARN_ON(!wiphy || !rd)) 3456b0d7aa59SJonathan Doron return -EINVAL; 3457b0d7aa59SJonathan Doron 3458b0d7aa59SJonathan Doron if (WARN(!(wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED), 3459b0d7aa59SJonathan Doron "wiphy should have REGULATORY_WIPHY_SELF_MANAGED\n")) 3460b0d7aa59SJonathan Doron return -EPERM; 3461b0d7aa59SJonathan Doron 3462b0d7aa59SJonathan Doron if (WARN(!is_valid_rd(rd), "Invalid regulatory domain detected\n")) { 3463b0d7aa59SJonathan Doron print_regdomain_info(rd); 3464b0d7aa59SJonathan Doron return -EINVAL; 3465b0d7aa59SJonathan Doron } 3466b0d7aa59SJonathan Doron 3467b0d7aa59SJonathan Doron regd = reg_copy_regd(rd); 3468b0d7aa59SJonathan Doron if (IS_ERR(regd)) 3469b0d7aa59SJonathan Doron return PTR_ERR(regd); 3470b0d7aa59SJonathan Doron 3471b0d7aa59SJonathan Doron rdev = wiphy_to_rdev(wiphy); 3472b0d7aa59SJonathan Doron 3473b0d7aa59SJonathan Doron spin_lock(®_requests_lock); 3474b0d7aa59SJonathan Doron prev_regd = rdev->requested_regd; 3475b0d7aa59SJonathan Doron rdev->requested_regd = regd; 3476b0d7aa59SJonathan Doron spin_unlock(®_requests_lock); 3477b0d7aa59SJonathan Doron 3478b0d7aa59SJonathan Doron kfree(prev_regd); 34792c3e861cSArik Nemtsov return 0; 34802c3e861cSArik Nemtsov } 34812c3e861cSArik Nemtsov 34822c3e861cSArik Nemtsov int regulatory_set_wiphy_regd(struct wiphy *wiphy, 34832c3e861cSArik Nemtsov struct ieee80211_regdomain *rd) 34842c3e861cSArik Nemtsov { 34852c3e861cSArik Nemtsov int ret = __regulatory_set_wiphy_regd(wiphy, rd); 34862c3e861cSArik Nemtsov 34872c3e861cSArik Nemtsov if (ret) 34882c3e861cSArik Nemtsov return ret; 3489b0d7aa59SJonathan Doron 3490b0d7aa59SJonathan Doron schedule_work(®_work); 3491b0d7aa59SJonathan Doron return 0; 3492b0d7aa59SJonathan Doron } 3493b0d7aa59SJonathan Doron EXPORT_SYMBOL(regulatory_set_wiphy_regd); 3494b0d7aa59SJonathan Doron 34952c3e861cSArik Nemtsov int regulatory_set_wiphy_regd_sync_rtnl(struct wiphy *wiphy, 34962c3e861cSArik Nemtsov struct ieee80211_regdomain *rd) 34972c3e861cSArik Nemtsov { 34982c3e861cSArik Nemtsov int ret; 34992c3e861cSArik Nemtsov 35002c3e861cSArik Nemtsov ASSERT_RTNL(); 35012c3e861cSArik Nemtsov 35022c3e861cSArik Nemtsov ret = __regulatory_set_wiphy_regd(wiphy, rd); 35032c3e861cSArik Nemtsov if (ret) 35042c3e861cSArik Nemtsov return ret; 35052c3e861cSArik Nemtsov 35062c3e861cSArik Nemtsov /* process the request immediately */ 35072c3e861cSArik Nemtsov reg_process_self_managed_hints(); 35082c3e861cSArik Nemtsov return 0; 35092c3e861cSArik Nemtsov } 35102c3e861cSArik Nemtsov EXPORT_SYMBOL(regulatory_set_wiphy_regd_sync_rtnl); 35112c3e861cSArik Nemtsov 351257b5ce07SLuis R. Rodriguez void wiphy_regulatory_register(struct wiphy *wiphy) 351357b5ce07SLuis R. Rodriguez { 351423df0b73SArik Nemtsov struct regulatory_request *lr; 351523df0b73SArik Nemtsov 3516b0d7aa59SJonathan Doron /* self-managed devices ignore external hints */ 3517b0d7aa59SJonathan Doron if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED) 3518b0d7aa59SJonathan Doron wiphy->regulatory_flags |= REGULATORY_DISABLE_BEACON_HINTS | 3519b0d7aa59SJonathan Doron REGULATORY_COUNTRY_IE_IGNORE; 3520b0d7aa59SJonathan Doron 352157b5ce07SLuis R. Rodriguez if (!reg_dev_ignore_cell_hint(wiphy)) 352257b5ce07SLuis R. Rodriguez reg_num_devs_support_basehint++; 352357b5ce07SLuis R. Rodriguez 352423df0b73SArik Nemtsov lr = get_last_request(); 352523df0b73SArik Nemtsov wiphy_update_regulatory(wiphy, lr->initiator); 352689766727SVasanthakumar Thiagarajan wiphy_all_share_dfs_chan_state(wiphy); 352757b5ce07SLuis R. Rodriguez } 352857b5ce07SLuis R. Rodriguez 3529bfead080SLuis R. Rodriguez void wiphy_regulatory_deregister(struct wiphy *wiphy) 35303f2355cbSLuis R. Rodriguez { 35310ad8acafSLuis R. Rodriguez struct wiphy *request_wiphy = NULL; 3532c492db37SJohannes Berg struct regulatory_request *lr; 3533761cf7ecSLuis R. Rodriguez 3534c492db37SJohannes Berg lr = get_last_request(); 3535abc7381bSLuis R. Rodriguez 353657b5ce07SLuis R. Rodriguez if (!reg_dev_ignore_cell_hint(wiphy)) 353757b5ce07SLuis R. Rodriguez reg_num_devs_support_basehint--; 353857b5ce07SLuis R. Rodriguez 3539458f4f9eSJohannes Berg rcu_free_regdom(get_wiphy_regdom(wiphy)); 354034dd886cSMonam Agarwal RCU_INIT_POINTER(wiphy->regd, NULL); 35410ef9ccddSChris Wright 3542c492db37SJohannes Berg if (lr) 3543c492db37SJohannes Berg request_wiphy = wiphy_idx_to_wiphy(lr->wiphy_idx); 3544806a9e39SLuis R. Rodriguez 35450ef9ccddSChris Wright if (!request_wiphy || request_wiphy != wiphy) 354638fd2143SJohannes Berg return; 35470ef9ccddSChris Wright 3548c492db37SJohannes Berg lr->wiphy_idx = WIPHY_IDX_INVALID; 3549c492db37SJohannes Berg lr->country_ie_env = ENVIRON_ANY; 35503f2355cbSLuis R. Rodriguez } 35513f2355cbSLuis R. Rodriguez 3552174e0cd2SIlan Peer /* 3553174e0cd2SIlan Peer * See http://www.fcc.gov/document/5-ghz-unlicensed-spectrum-unii, for 3554174e0cd2SIlan Peer * UNII band definitions 3555174e0cd2SIlan Peer */ 3556174e0cd2SIlan Peer int cfg80211_get_unii(int freq) 3557174e0cd2SIlan Peer { 3558174e0cd2SIlan Peer /* UNII-1 */ 3559174e0cd2SIlan Peer if (freq >= 5150 && freq <= 5250) 3560174e0cd2SIlan Peer return 0; 3561174e0cd2SIlan Peer 3562174e0cd2SIlan Peer /* UNII-2A */ 3563174e0cd2SIlan Peer if (freq > 5250 && freq <= 5350) 3564174e0cd2SIlan Peer return 1; 3565174e0cd2SIlan Peer 3566174e0cd2SIlan Peer /* UNII-2B */ 3567174e0cd2SIlan Peer if (freq > 5350 && freq <= 5470) 3568174e0cd2SIlan Peer return 2; 3569174e0cd2SIlan Peer 3570174e0cd2SIlan Peer /* UNII-2C */ 3571174e0cd2SIlan Peer if (freq > 5470 && freq <= 5725) 3572174e0cd2SIlan Peer return 3; 3573174e0cd2SIlan Peer 3574174e0cd2SIlan Peer /* UNII-3 */ 3575174e0cd2SIlan Peer if (freq > 5725 && freq <= 5825) 3576174e0cd2SIlan Peer return 4; 3577174e0cd2SIlan Peer 3578174e0cd2SIlan Peer return -EINVAL; 3579174e0cd2SIlan Peer } 3580174e0cd2SIlan Peer 3581c8866e55SIlan Peer bool regulatory_indoor_allowed(void) 3582c8866e55SIlan Peer { 3583c8866e55SIlan Peer return reg_is_indoor; 3584c8866e55SIlan Peer } 3585c8866e55SIlan Peer 3586b35a51c7SVasanthakumar Thiagarajan bool regulatory_pre_cac_allowed(struct wiphy *wiphy) 3587b35a51c7SVasanthakumar Thiagarajan { 3588b35a51c7SVasanthakumar Thiagarajan const struct ieee80211_regdomain *regd = NULL; 3589b35a51c7SVasanthakumar Thiagarajan const struct ieee80211_regdomain *wiphy_regd = NULL; 3590b35a51c7SVasanthakumar Thiagarajan bool pre_cac_allowed = false; 3591b35a51c7SVasanthakumar Thiagarajan 3592b35a51c7SVasanthakumar Thiagarajan rcu_read_lock(); 3593b35a51c7SVasanthakumar Thiagarajan 3594b35a51c7SVasanthakumar Thiagarajan regd = rcu_dereference(cfg80211_regdomain); 3595b35a51c7SVasanthakumar Thiagarajan wiphy_regd = rcu_dereference(wiphy->regd); 3596b35a51c7SVasanthakumar Thiagarajan if (!wiphy_regd) { 3597b35a51c7SVasanthakumar Thiagarajan if (regd->dfs_region == NL80211_DFS_ETSI) 3598b35a51c7SVasanthakumar Thiagarajan pre_cac_allowed = true; 3599b35a51c7SVasanthakumar Thiagarajan 3600b35a51c7SVasanthakumar Thiagarajan rcu_read_unlock(); 3601b35a51c7SVasanthakumar Thiagarajan 3602b35a51c7SVasanthakumar Thiagarajan return pre_cac_allowed; 3603b35a51c7SVasanthakumar Thiagarajan } 3604b35a51c7SVasanthakumar Thiagarajan 3605b35a51c7SVasanthakumar Thiagarajan if (regd->dfs_region == wiphy_regd->dfs_region && 3606b35a51c7SVasanthakumar Thiagarajan wiphy_regd->dfs_region == NL80211_DFS_ETSI) 3607b35a51c7SVasanthakumar Thiagarajan pre_cac_allowed = true; 3608b35a51c7SVasanthakumar Thiagarajan 3609b35a51c7SVasanthakumar Thiagarajan rcu_read_unlock(); 3610b35a51c7SVasanthakumar Thiagarajan 3611b35a51c7SVasanthakumar Thiagarajan return pre_cac_allowed; 3612b35a51c7SVasanthakumar Thiagarajan } 3613b35a51c7SVasanthakumar Thiagarajan 361489766727SVasanthakumar Thiagarajan void regulatory_propagate_dfs_state(struct wiphy *wiphy, 361589766727SVasanthakumar Thiagarajan struct cfg80211_chan_def *chandef, 361689766727SVasanthakumar Thiagarajan enum nl80211_dfs_state dfs_state, 361789766727SVasanthakumar Thiagarajan enum nl80211_radar_event event) 361889766727SVasanthakumar Thiagarajan { 361989766727SVasanthakumar Thiagarajan struct cfg80211_registered_device *rdev; 362089766727SVasanthakumar Thiagarajan 362189766727SVasanthakumar Thiagarajan ASSERT_RTNL(); 362289766727SVasanthakumar Thiagarajan 362389766727SVasanthakumar Thiagarajan if (WARN_ON(!cfg80211_chandef_valid(chandef))) 362489766727SVasanthakumar Thiagarajan return; 362589766727SVasanthakumar Thiagarajan 362689766727SVasanthakumar Thiagarajan list_for_each_entry(rdev, &cfg80211_rdev_list, list) { 362789766727SVasanthakumar Thiagarajan if (wiphy == &rdev->wiphy) 362889766727SVasanthakumar Thiagarajan continue; 362989766727SVasanthakumar Thiagarajan 363089766727SVasanthakumar Thiagarajan if (!reg_dfs_domain_same(wiphy, &rdev->wiphy)) 363189766727SVasanthakumar Thiagarajan continue; 363289766727SVasanthakumar Thiagarajan 363389766727SVasanthakumar Thiagarajan if (!ieee80211_get_channel(&rdev->wiphy, 363489766727SVasanthakumar Thiagarajan chandef->chan->center_freq)) 363589766727SVasanthakumar Thiagarajan continue; 363689766727SVasanthakumar Thiagarajan 363789766727SVasanthakumar Thiagarajan cfg80211_set_dfs_state(&rdev->wiphy, chandef, dfs_state); 363889766727SVasanthakumar Thiagarajan 363989766727SVasanthakumar Thiagarajan if (event == NL80211_RADAR_DETECTED || 364089766727SVasanthakumar Thiagarajan event == NL80211_RADAR_CAC_FINISHED) 364189766727SVasanthakumar Thiagarajan cfg80211_sched_dfs_chan_update(rdev); 364289766727SVasanthakumar Thiagarajan 364389766727SVasanthakumar Thiagarajan nl80211_radar_notify(rdev, chandef, event, NULL, GFP_KERNEL); 364489766727SVasanthakumar Thiagarajan } 364589766727SVasanthakumar Thiagarajan } 364689766727SVasanthakumar Thiagarajan 3647d7be102fSJohannes Berg static int __init regulatory_init_db(void) 3648b2e1b302SLuis R. Rodriguez { 3649d7be102fSJohannes Berg int err; 3650734366deSJohannes Berg 365190a53e44SJohannes Berg err = load_builtin_regdb_keys(); 365290a53e44SJohannes Berg if (err) 365390a53e44SJohannes Berg return err; 365490a53e44SJohannes Berg 3655ae9e4b0dSLuis R. Rodriguez /* We always try to get an update for the static regdomain */ 3656458f4f9eSJohannes Berg err = regulatory_hint_core(cfg80211_world_regdom->alpha2); 3657bcf4f99bSLuis R. Rodriguez if (err) { 365809d11800SOla Olsson if (err == -ENOMEM) { 365909d11800SOla Olsson platform_device_unregister(reg_pdev); 3660bcf4f99bSLuis R. Rodriguez return err; 366109d11800SOla Olsson } 3662bcf4f99bSLuis R. Rodriguez /* 3663bcf4f99bSLuis R. Rodriguez * N.B. kobject_uevent_env() can fail mainly for when we're out 3664bcf4f99bSLuis R. Rodriguez * memory which is handled and propagated appropriately above 3665bcf4f99bSLuis R. Rodriguez * but it can also fail during a netlink_broadcast() or during 3666bcf4f99bSLuis R. Rodriguez * early boot for call_usermodehelper(). For now treat these 3667bcf4f99bSLuis R. Rodriguez * errors as non-fatal. 3668bcf4f99bSLuis R. Rodriguez */ 3669e9c0268fSJoe Perches pr_err("kobject_uevent_env() was unable to call CRDA during init\n"); 3670bcf4f99bSLuis R. Rodriguez } 3671734366deSJohannes Berg 3672ae9e4b0dSLuis R. Rodriguez /* 3673ae9e4b0dSLuis R. Rodriguez * Finally, if the user set the module parameter treat it 3674ae9e4b0dSLuis R. Rodriguez * as a user hint. 3675ae9e4b0dSLuis R. Rodriguez */ 3676ae9e4b0dSLuis R. Rodriguez if (!is_world_regdom(ieee80211_regdom)) 367757b5ce07SLuis R. Rodriguez regulatory_hint_user(ieee80211_regdom, 367857b5ce07SLuis R. Rodriguez NL80211_USER_REG_HINT_USER); 3679ae9e4b0dSLuis R. Rodriguez 3680b2e1b302SLuis R. Rodriguez return 0; 3681b2e1b302SLuis R. Rodriguez } 3682d7be102fSJohannes Berg #ifndef MODULE 3683d7be102fSJohannes Berg late_initcall(regulatory_init_db); 3684d7be102fSJohannes Berg #endif 3685d7be102fSJohannes Berg 3686d7be102fSJohannes Berg int __init regulatory_init(void) 3687d7be102fSJohannes Berg { 3688d7be102fSJohannes Berg reg_pdev = platform_device_register_simple("regulatory", 0, NULL, 0); 3689d7be102fSJohannes Berg if (IS_ERR(reg_pdev)) 3690d7be102fSJohannes Berg return PTR_ERR(reg_pdev); 3691d7be102fSJohannes Berg 3692d7be102fSJohannes Berg spin_lock_init(®_requests_lock); 3693d7be102fSJohannes Berg spin_lock_init(®_pending_beacons_lock); 3694d7be102fSJohannes Berg spin_lock_init(®_indoor_lock); 3695d7be102fSJohannes Berg 3696d7be102fSJohannes Berg rcu_assign_pointer(cfg80211_regdomain, cfg80211_world_regdom); 3697d7be102fSJohannes Berg 3698d7be102fSJohannes Berg user_alpha2[0] = '9'; 3699d7be102fSJohannes Berg user_alpha2[1] = '7'; 3700d7be102fSJohannes Berg 3701d7be102fSJohannes Berg #ifdef MODULE 3702d7be102fSJohannes Berg return regulatory_init_db(); 3703d7be102fSJohannes Berg #else 3704d7be102fSJohannes Berg return 0; 3705d7be102fSJohannes Berg #endif 3706d7be102fSJohannes Berg } 3707b2e1b302SLuis R. Rodriguez 37081a919318SJohannes Berg void regulatory_exit(void) 3709b2e1b302SLuis R. Rodriguez { 3710fe33eb39SLuis R. Rodriguez struct regulatory_request *reg_request, *tmp; 3711e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon, *btmp; 3712fe33eb39SLuis R. Rodriguez 3713fe33eb39SLuis R. Rodriguez cancel_work_sync(®_work); 3714b6863036SJohannes Berg cancel_crda_timeout_sync(); 3715ad932f04SArik Nemtsov cancel_delayed_work_sync(®_check_chans); 3716fe33eb39SLuis R. Rodriguez 37179027b149SJohannes Berg /* Lock to suppress warnings */ 371838fd2143SJohannes Berg rtnl_lock(); 3719379b82f4SJohannes Berg reset_regdomains(true, NULL); 372038fd2143SJohannes Berg rtnl_unlock(); 3721734366deSJohannes Berg 372258ebacc6SLuis R. Rodriguez dev_set_uevent_suppress(®_pdev->dev, true); 3723f6037d09SJohannes Berg 3724b2e1b302SLuis R. Rodriguez platform_device_unregister(reg_pdev); 3725734366deSJohannes Berg 3726fea9bcedSJohannes Berg list_for_each_entry_safe(reg_beacon, btmp, ®_pending_beacons, list) { 3727e38f8a7aSLuis R. Rodriguez list_del(®_beacon->list); 3728e38f8a7aSLuis R. Rodriguez kfree(reg_beacon); 3729e38f8a7aSLuis R. Rodriguez } 3730e38f8a7aSLuis R. Rodriguez 3731fea9bcedSJohannes Berg list_for_each_entry_safe(reg_beacon, btmp, ®_beacon_list, list) { 3732e38f8a7aSLuis R. Rodriguez list_del(®_beacon->list); 3733e38f8a7aSLuis R. Rodriguez kfree(reg_beacon); 3734e38f8a7aSLuis R. Rodriguez } 3735e38f8a7aSLuis R. Rodriguez 3736fea9bcedSJohannes Berg list_for_each_entry_safe(reg_request, tmp, ®_requests_list, list) { 3737fe33eb39SLuis R. Rodriguez list_del(®_request->list); 3738fe33eb39SLuis R. Rodriguez kfree(reg_request); 3739fe33eb39SLuis R. Rodriguez } 3740007f6c5eSJohannes Berg 3741007f6c5eSJohannes Berg if (!IS_ERR_OR_NULL(regdb)) 3742007f6c5eSJohannes Berg kfree(regdb); 374390a53e44SJohannes Berg 374490a53e44SJohannes Berg free_regdb_keyring(); 3745fe33eb39SLuis R. Rodriguez } 3746