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