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