xref: /openbmc/linux/net/wireless/reg.c (revision 6c5b9a32)
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
8*6c5b9a32SJohannes Berg  * Copyright (C) 2018 - 2023 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 
get_cfg80211_regdom(void)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  */
get_wiphy_regdom(struct wiphy * wiphy)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 
reg_dfs_region_str(enum nl80211_dfs_regions dfs_region)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 
reg_get_dfs_region(struct wiphy * wiphy)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 
rcu_free_regdom(const struct ieee80211_regdomain * r)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 
get_last_request(void)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 
reg_free_request(struct regulatory_request * request)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 
reg_free_last_request(void)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 
reg_update_last_request(struct regulatory_request * request)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 
reset_regdomains(bool full_reset,const struct ieee80211_regdomain * new_regdom)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  */
update_world_regdomain(const struct ieee80211_regdomain * rd)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 
is_world_regdom(const char * alpha2)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 
is_alpha2_set(const char * alpha2)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 
is_unknown_alpha2(const char * alpha2)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 
is_intersected_alpha2(const char * alpha2)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 
is_an_alpha2(const char * alpha2)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 
alpha2_equal(const char * alpha2_x,const char * alpha2_y)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 
regdom_changes(const char * alpha2)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  */
is_user_regdom_saved(void)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 *
reg_copy_regd(const struct ieee80211_regdomain * src_regd)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 
cfg80211_save_user_regdom(const struct ieee80211_regdomain * rd)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 
reg_regdb_apply(struct work_struct * work)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 
reg_schedule_apply(const struct ieee80211_regdomain * regdom)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 
crda_timeout_work(struct work_struct * work)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 
cancel_crda_timeout(void)544b6863036SJohannes Berg static void cancel_crda_timeout(void)
545b6863036SJohannes Berg {
546b6863036SJohannes Berg 	cancel_delayed_work(&crda_timeout);
547b6863036SJohannes Berg }
548b6863036SJohannes Berg 
cancel_crda_timeout_sync(void)549b6863036SJohannes Berg static void cancel_crda_timeout_sync(void)
550b6863036SJohannes Berg {
551b6863036SJohannes Berg 	cancel_delayed_work_sync(&crda_timeout);
552b6863036SJohannes Berg }
553b6863036SJohannes Berg 
reset_crda_timeouts(void)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  */
call_crda(const char * alpha2)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
cancel_crda_timeout(void)592b6863036SJohannes Berg static inline void cancel_crda_timeout(void) {}
cancel_crda_timeout_sync(void)593b6863036SJohannes Berg static inline void cancel_crda_timeout_sync(void) {}
reset_crda_timeouts(void)594b6863036SJohannes Berg static inline void reset_crda_timeouts(void) {}
call_crda(const char * alpha2)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 
ecw2cw(int ecw)656230ebaa1SHaim Dreyfuss static int ecw2cw(int ecw)
657230ebaa1SHaim Dreyfuss {
658230ebaa1SHaim Dreyfuss 	return (1 << ecw) - 1;
659230ebaa1SHaim Dreyfuss }
660230ebaa1SHaim Dreyfuss 
valid_wmm(struct fwdb_wmm_rule * rule)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 
valid_rule(const u8 * data,unsigned int size,u16 rule_ptr)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 
valid_country(const u8 * data,unsigned int size,const struct fwdb_country * country)706007f6c5eSJohannes Berg static bool valid_country(const u8 *data, unsigned int size,
707007f6c5eSJohannes Berg 			  const struct fwdb_country *country)
708007f6c5eSJohannes Berg {
709007f6c5eSJohannes Berg 	unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2;
710007f6c5eSJohannes Berg 	struct fwdb_collection *coll = (void *)(data + ptr);
711007f6c5eSJohannes Berg 	__be16 *rules_ptr;
712007f6c5eSJohannes Berg 	unsigned int i;
713007f6c5eSJohannes Berg 
714007f6c5eSJohannes Berg 	/* make sure we can read len/n_rules */
715007f6c5eSJohannes Berg 	if ((u8 *)coll + offsetofend(typeof(*coll), n_rules) > data + size)
716007f6c5eSJohannes Berg 		return false;
717007f6c5eSJohannes Berg 
718007f6c5eSJohannes Berg 	/* make sure base struct and all rules fit */
719007f6c5eSJohannes Berg 	if ((u8 *)coll + ALIGN(coll->len, 2) +
720007f6c5eSJohannes Berg 	    (coll->n_rules * 2) > data + size)
721007f6c5eSJohannes Berg 		return false;
722007f6c5eSJohannes Berg 
723007f6c5eSJohannes Berg 	/* mandatory fields must exist */
724007f6c5eSJohannes Berg 	if (coll->len < offsetofend(struct fwdb_collection, dfs_region))
725007f6c5eSJohannes Berg 		return false;
726007f6c5eSJohannes Berg 
727007f6c5eSJohannes Berg 	rules_ptr = (void *)((u8 *)coll + ALIGN(coll->len, 2));
728007f6c5eSJohannes Berg 
729007f6c5eSJohannes Berg 	for (i = 0; i < coll->n_rules; i++) {
730007f6c5eSJohannes Berg 		u16 rule_ptr = be16_to_cpu(rules_ptr[i]);
731007f6c5eSJohannes Berg 
732007f6c5eSJohannes Berg 		if (!valid_rule(data, size, rule_ptr))
733007f6c5eSJohannes Berg 			return false;
734007f6c5eSJohannes Berg 	}
735007f6c5eSJohannes Berg 
736007f6c5eSJohannes Berg 	return true;
737007f6c5eSJohannes Berg }
738007f6c5eSJohannes Berg 
73990a53e44SJohannes Berg #ifdef CONFIG_CFG80211_REQUIRE_SIGNED_REGDB
7403609ff64SLukas Wunner #include <keys/asymmetric-type.h>
7413609ff64SLukas Wunner 
74290a53e44SJohannes Berg static struct key *builtin_regdb_keys;
74390a53e44SJohannes Berg 
load_builtin_regdb_keys(void)74490a53e44SJohannes Berg static int __init load_builtin_regdb_keys(void)
74590a53e44SJohannes Berg {
74690a53e44SJohannes Berg 	builtin_regdb_keys =
74790a53e44SJohannes Berg 		keyring_alloc(".builtin_regdb_keys",
74890a53e44SJohannes Berg 			      KUIDT_INIT(0), KGIDT_INIT(0), current_cred(),
749028db3e2SLinus Torvalds 			      ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
750028db3e2SLinus Torvalds 			      KEY_USR_VIEW | KEY_USR_READ | KEY_USR_SEARCH),
75190a53e44SJohannes Berg 			      KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
75290a53e44SJohannes Berg 	if (IS_ERR(builtin_regdb_keys))
75390a53e44SJohannes Berg 		return PTR_ERR(builtin_regdb_keys);
75490a53e44SJohannes Berg 
75590a53e44SJohannes Berg 	pr_notice("Loading compiled-in X.509 certificates for regulatory database\n");
75690a53e44SJohannes Berg 
75790a53e44SJohannes Berg #ifdef CONFIG_CFG80211_USE_KERNEL_REGDB_KEYS
7583609ff64SLukas Wunner 	x509_load_certificate_list(shipped_regdb_certs,
7593609ff64SLukas Wunner 				   shipped_regdb_certs_len,
7603609ff64SLukas Wunner 				   builtin_regdb_keys);
76190a53e44SJohannes Berg #endif
76288230ef1SArnd Bergmann #ifdef CONFIG_CFG80211_EXTRA_REGDB_KEYDIR
76390a53e44SJohannes Berg 	if (CONFIG_CFG80211_EXTRA_REGDB_KEYDIR[0] != '\0')
7643609ff64SLukas Wunner 		x509_load_certificate_list(extra_regdb_certs,
7653609ff64SLukas Wunner 					   extra_regdb_certs_len,
7663609ff64SLukas Wunner 					   builtin_regdb_keys);
76790a53e44SJohannes Berg #endif
76890a53e44SJohannes Berg 
76990a53e44SJohannes Berg 	return 0;
77090a53e44SJohannes Berg }
77190a53e44SJohannes Berg 
7727bc7981eSDimitri John Ledkov MODULE_FIRMWARE("regulatory.db.p7s");
7737bc7981eSDimitri John Ledkov 
regdb_has_valid_signature(const u8 * data,unsigned int size)77490a53e44SJohannes Berg static bool regdb_has_valid_signature(const u8 *data, unsigned int size)
77590a53e44SJohannes Berg {
77690a53e44SJohannes Berg 	const struct firmware *sig;
77790a53e44SJohannes Berg 	bool result;
77890a53e44SJohannes Berg 
77990a53e44SJohannes Berg 	if (request_firmware(&sig, "regulatory.db.p7s", &reg_pdev->dev))
78090a53e44SJohannes Berg 		return false;
78190a53e44SJohannes Berg 
78290a53e44SJohannes Berg 	result = verify_pkcs7_signature(data, size, sig->data, sig->size,
78390a53e44SJohannes Berg 					builtin_regdb_keys,
78490a53e44SJohannes Berg 					VERIFYING_UNSPECIFIED_SIGNATURE,
78590a53e44SJohannes Berg 					NULL, NULL) == 0;
78690a53e44SJohannes Berg 
78790a53e44SJohannes Berg 	release_firmware(sig);
78890a53e44SJohannes Berg 
78990a53e44SJohannes Berg 	return result;
79090a53e44SJohannes Berg }
79190a53e44SJohannes Berg 
free_regdb_keyring(void)79290a53e44SJohannes Berg static void free_regdb_keyring(void)
79390a53e44SJohannes Berg {
79490a53e44SJohannes Berg 	key_put(builtin_regdb_keys);
79590a53e44SJohannes Berg }
79690a53e44SJohannes Berg #else
load_builtin_regdb_keys(void)79790a53e44SJohannes Berg static int load_builtin_regdb_keys(void)
79890a53e44SJohannes Berg {
79990a53e44SJohannes Berg 	return 0;
80090a53e44SJohannes Berg }
80190a53e44SJohannes Berg 
regdb_has_valid_signature(const u8 * data,unsigned int size)80290a53e44SJohannes Berg static bool regdb_has_valid_signature(const u8 *data, unsigned int size)
80390a53e44SJohannes Berg {
80490a53e44SJohannes Berg 	return true;
80590a53e44SJohannes Berg }
80690a53e44SJohannes Berg 
free_regdb_keyring(void)80790a53e44SJohannes Berg static void free_regdb_keyring(void)
80890a53e44SJohannes Berg {
80990a53e44SJohannes Berg }
81090a53e44SJohannes Berg #endif /* CONFIG_CFG80211_REQUIRE_SIGNED_REGDB */
81190a53e44SJohannes Berg 
valid_regdb(const u8 * data,unsigned int size)812007f6c5eSJohannes Berg static bool valid_regdb(const u8 *data, unsigned int size)
813007f6c5eSJohannes Berg {
814007f6c5eSJohannes Berg 	const struct fwdb_header *hdr = (void *)data;
815007f6c5eSJohannes Berg 	const struct fwdb_country *country;
816007f6c5eSJohannes Berg 
817007f6c5eSJohannes Berg 	if (size < sizeof(*hdr))
818007f6c5eSJohannes Berg 		return false;
819007f6c5eSJohannes Berg 
820007f6c5eSJohannes Berg 	if (hdr->magic != cpu_to_be32(FWDB_MAGIC))
821007f6c5eSJohannes Berg 		return false;
822007f6c5eSJohannes Berg 
823007f6c5eSJohannes Berg 	if (hdr->version != cpu_to_be32(FWDB_VERSION))
824007f6c5eSJohannes Berg 		return false;
825007f6c5eSJohannes Berg 
82690a53e44SJohannes Berg 	if (!regdb_has_valid_signature(data, size))
82790a53e44SJohannes Berg 		return false;
82890a53e44SJohannes Berg 
829007f6c5eSJohannes Berg 	country = &hdr->country[0];
830007f6c5eSJohannes Berg 	while ((u8 *)(country + 1) <= data + size) {
831007f6c5eSJohannes Berg 		if (!country->coll_ptr)
832007f6c5eSJohannes Berg 			break;
833007f6c5eSJohannes Berg 		if (!valid_country(data, size, country))
834007f6c5eSJohannes Berg 			return false;
835007f6c5eSJohannes Berg 		country++;
836007f6c5eSJohannes Berg 	}
837007f6c5eSJohannes Berg 
838007f6c5eSJohannes Berg 	return true;
839007f6c5eSJohannes Berg }
840007f6c5eSJohannes Berg 
set_wmm_rule(const struct fwdb_header * db,const struct fwdb_country * country,const struct fwdb_rule * rule,struct ieee80211_reg_rule * rrule)841014f5a25SStanislaw Gruszka static void set_wmm_rule(const struct fwdb_header *db,
842014f5a25SStanislaw Gruszka 			 const struct fwdb_country *country,
843014f5a25SStanislaw Gruszka 			 const struct fwdb_rule *rule,
844014f5a25SStanislaw Gruszka 			 struct ieee80211_reg_rule *rrule)
845230ebaa1SHaim Dreyfuss {
846014f5a25SStanislaw Gruszka 	struct ieee80211_wmm_rule *wmm_rule = &rrule->wmm_rule;
847014f5a25SStanislaw Gruszka 	struct fwdb_wmm_rule *wmm;
848014f5a25SStanislaw Gruszka 	unsigned int i, wmm_ptr;
849014f5a25SStanislaw Gruszka 
850014f5a25SStanislaw Gruszka 	wmm_ptr = be16_to_cpu(rule->wmm_ptr) << 2;
851014f5a25SStanislaw Gruszka 	wmm = (void *)((u8 *)db + wmm_ptr);
852014f5a25SStanislaw Gruszka 
853014f5a25SStanislaw Gruszka 	if (!valid_wmm(wmm)) {
854014f5a25SStanislaw Gruszka 		pr_err("Invalid regulatory WMM rule %u-%u in domain %c%c\n",
855014f5a25SStanislaw Gruszka 		       be32_to_cpu(rule->start), be32_to_cpu(rule->end),
856014f5a25SStanislaw Gruszka 		       country->alpha2[0], country->alpha2[1]);
857014f5a25SStanislaw Gruszka 		return;
858014f5a25SStanislaw Gruszka 	}
859230ebaa1SHaim Dreyfuss 
860230ebaa1SHaim Dreyfuss 	for (i = 0; i < IEEE80211_NUM_ACS; i++) {
861014f5a25SStanislaw Gruszka 		wmm_rule->client[i].cw_min =
862230ebaa1SHaim Dreyfuss 			ecw2cw((wmm->client[i].ecw & 0xf0) >> 4);
863014f5a25SStanislaw Gruszka 		wmm_rule->client[i].cw_max = ecw2cw(wmm->client[i].ecw & 0x0f);
864014f5a25SStanislaw Gruszka 		wmm_rule->client[i].aifsn =  wmm->client[i].aifsn;
865014f5a25SStanislaw Gruszka 		wmm_rule->client[i].cot =
866014f5a25SStanislaw Gruszka 			1000 * be16_to_cpu(wmm->client[i].cot);
867014f5a25SStanislaw Gruszka 		wmm_rule->ap[i].cw_min = ecw2cw((wmm->ap[i].ecw & 0xf0) >> 4);
868014f5a25SStanislaw Gruszka 		wmm_rule->ap[i].cw_max = ecw2cw(wmm->ap[i].ecw & 0x0f);
869014f5a25SStanislaw Gruszka 		wmm_rule->ap[i].aifsn = wmm->ap[i].aifsn;
870014f5a25SStanislaw Gruszka 		wmm_rule->ap[i].cot = 1000 * be16_to_cpu(wmm->ap[i].cot);
871230ebaa1SHaim Dreyfuss 	}
87238cb87eeSStanislaw Gruszka 
87338cb87eeSStanislaw Gruszka 	rrule->has_wmm = true;
874230ebaa1SHaim Dreyfuss }
875230ebaa1SHaim Dreyfuss 
__regdb_query_wmm(const struct fwdb_header * db,const struct fwdb_country * country,int freq,struct ieee80211_reg_rule * rrule)87619d3577eSHaim Dreyfuss static int __regdb_query_wmm(const struct fwdb_header *db,
87719d3577eSHaim Dreyfuss 			     const struct fwdb_country *country, int freq,
878014f5a25SStanislaw Gruszka 			     struct ieee80211_reg_rule *rrule)
87919d3577eSHaim Dreyfuss {
88019d3577eSHaim Dreyfuss 	unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2;
88119d3577eSHaim Dreyfuss 	struct fwdb_collection *coll = (void *)((u8 *)db + ptr);
88219d3577eSHaim Dreyfuss 	int i;
88319d3577eSHaim Dreyfuss 
88419d3577eSHaim Dreyfuss 	for (i = 0; i < coll->n_rules; i++) {
88519d3577eSHaim Dreyfuss 		__be16 *rules_ptr = (void *)((u8 *)coll + ALIGN(coll->len, 2));
88619d3577eSHaim Dreyfuss 		unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2;
887014f5a25SStanislaw Gruszka 		struct fwdb_rule *rule = (void *)((u8 *)db + rule_ptr);
88819d3577eSHaim Dreyfuss 
889014f5a25SStanislaw Gruszka 		if (rule->len < offsetofend(struct fwdb_rule, wmm_ptr))
89019d3577eSHaim Dreyfuss 			continue;
89119d3577eSHaim Dreyfuss 
892014f5a25SStanislaw Gruszka 		if (freq >= KHZ_TO_MHZ(be32_to_cpu(rule->start)) &&
893014f5a25SStanislaw Gruszka 		    freq <= KHZ_TO_MHZ(be32_to_cpu(rule->end))) {
894014f5a25SStanislaw Gruszka 			set_wmm_rule(db, country, rule, rrule);
89519d3577eSHaim Dreyfuss 			return 0;
89619d3577eSHaim Dreyfuss 		}
89719d3577eSHaim Dreyfuss 	}
89819d3577eSHaim Dreyfuss 
89919d3577eSHaim Dreyfuss 	return -ENODATA;
90019d3577eSHaim Dreyfuss }
90119d3577eSHaim Dreyfuss 
reg_query_regdb_wmm(char * alpha2,int freq,struct ieee80211_reg_rule * rule)90238cb87eeSStanislaw Gruszka int reg_query_regdb_wmm(char *alpha2, int freq, struct ieee80211_reg_rule *rule)
90319d3577eSHaim Dreyfuss {
90419d3577eSHaim Dreyfuss 	const struct fwdb_header *hdr = regdb;
90519d3577eSHaim Dreyfuss 	const struct fwdb_country *country;
90619d3577eSHaim Dreyfuss 
9075247a77cSHaim Dreyfuss 	if (!regdb)
9085247a77cSHaim Dreyfuss 		return -ENODATA;
9095247a77cSHaim Dreyfuss 
91019d3577eSHaim Dreyfuss 	if (IS_ERR(regdb))
91119d3577eSHaim Dreyfuss 		return PTR_ERR(regdb);
91219d3577eSHaim Dreyfuss 
91319d3577eSHaim Dreyfuss 	country = &hdr->country[0];
91419d3577eSHaim Dreyfuss 	while (country->coll_ptr) {
91519d3577eSHaim Dreyfuss 		if (alpha2_equal(alpha2, country->alpha2))
91638cb87eeSStanislaw Gruszka 			return __regdb_query_wmm(regdb, country, freq, rule);
91719d3577eSHaim Dreyfuss 
91819d3577eSHaim Dreyfuss 		country++;
91919d3577eSHaim Dreyfuss 	}
92019d3577eSHaim Dreyfuss 
92119d3577eSHaim Dreyfuss 	return -ENODATA;
92219d3577eSHaim Dreyfuss }
92319d3577eSHaim Dreyfuss EXPORT_SYMBOL(reg_query_regdb_wmm);
92419d3577eSHaim Dreyfuss 
regdb_query_country(const struct fwdb_header * db,const struct fwdb_country * country)925007f6c5eSJohannes Berg static int regdb_query_country(const struct fwdb_header *db,
926007f6c5eSJohannes Berg 			       const struct fwdb_country *country)
927007f6c5eSJohannes Berg {
928007f6c5eSJohannes Berg 	unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2;
929007f6c5eSJohannes Berg 	struct fwdb_collection *coll = (void *)((u8 *)db + ptr);
930007f6c5eSJohannes Berg 	struct ieee80211_regdomain *regdom;
9319f8c7136SGustavo A. R. Silva 	unsigned int i;
932007f6c5eSJohannes Berg 
9339f8c7136SGustavo A. R. Silva 	regdom = kzalloc(struct_size(regdom, reg_rules, coll->n_rules),
9349f8c7136SGustavo A. R. Silva 			 GFP_KERNEL);
935007f6c5eSJohannes Berg 	if (!regdom)
936007f6c5eSJohannes Berg 		return -ENOMEM;
937007f6c5eSJohannes Berg 
938007f6c5eSJohannes Berg 	regdom->n_reg_rules = coll->n_rules;
939007f6c5eSJohannes Berg 	regdom->alpha2[0] = country->alpha2[0];
940007f6c5eSJohannes Berg 	regdom->alpha2[1] = country->alpha2[1];
941007f6c5eSJohannes Berg 	regdom->dfs_region = coll->dfs_region;
942007f6c5eSJohannes Berg 
943007f6c5eSJohannes Berg 	for (i = 0; i < regdom->n_reg_rules; i++) {
944007f6c5eSJohannes Berg 		__be16 *rules_ptr = (void *)((u8 *)coll + ALIGN(coll->len, 2));
945007f6c5eSJohannes Berg 		unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2;
946007f6c5eSJohannes Berg 		struct fwdb_rule *rule = (void *)((u8 *)db + rule_ptr);
947007f6c5eSJohannes Berg 		struct ieee80211_reg_rule *rrule = &regdom->reg_rules[i];
948007f6c5eSJohannes Berg 
949007f6c5eSJohannes Berg 		rrule->freq_range.start_freq_khz = be32_to_cpu(rule->start);
950007f6c5eSJohannes Berg 		rrule->freq_range.end_freq_khz = be32_to_cpu(rule->end);
951007f6c5eSJohannes Berg 		rrule->freq_range.max_bandwidth_khz = be32_to_cpu(rule->max_bw);
952007f6c5eSJohannes Berg 
953007f6c5eSJohannes Berg 		rrule->power_rule.max_antenna_gain = 0;
954007f6c5eSJohannes Berg 		rrule->power_rule.max_eirp = be16_to_cpu(rule->max_eirp);
955007f6c5eSJohannes Berg 
956007f6c5eSJohannes Berg 		rrule->flags = 0;
957007f6c5eSJohannes Berg 		if (rule->flags & FWDB_FLAG_NO_OFDM)
958007f6c5eSJohannes Berg 			rrule->flags |= NL80211_RRF_NO_OFDM;
959007f6c5eSJohannes Berg 		if (rule->flags & FWDB_FLAG_NO_OUTDOOR)
960007f6c5eSJohannes Berg 			rrule->flags |= NL80211_RRF_NO_OUTDOOR;
961007f6c5eSJohannes Berg 		if (rule->flags & FWDB_FLAG_DFS)
962007f6c5eSJohannes Berg 			rrule->flags |= NL80211_RRF_DFS;
963007f6c5eSJohannes Berg 		if (rule->flags & FWDB_FLAG_NO_IR)
964007f6c5eSJohannes Berg 			rrule->flags |= NL80211_RRF_NO_IR;
965007f6c5eSJohannes Berg 		if (rule->flags & FWDB_FLAG_AUTO_BW)
966007f6c5eSJohannes Berg 			rrule->flags |= NL80211_RRF_AUTO_BW;
967007f6c5eSJohannes Berg 
968007f6c5eSJohannes Berg 		rrule->dfs_cac_ms = 0;
969007f6c5eSJohannes Berg 
970007f6c5eSJohannes Berg 		/* handle optional data */
971007f6c5eSJohannes Berg 		if (rule->len >= offsetofend(struct fwdb_rule, cac_timeout))
972007f6c5eSJohannes Berg 			rrule->dfs_cac_ms =
973007f6c5eSJohannes Berg 				1000 * be16_to_cpu(rule->cac_timeout);
974014f5a25SStanislaw Gruszka 		if (rule->len >= offsetofend(struct fwdb_rule, wmm_ptr))
975014f5a25SStanislaw Gruszka 			set_wmm_rule(db, country, rule, rrule);
976230ebaa1SHaim Dreyfuss 	}
977007f6c5eSJohannes Berg 
978007f6c5eSJohannes Berg 	return reg_schedule_apply(regdom);
979007f6c5eSJohannes Berg }
980007f6c5eSJohannes Berg 
query_regdb(const char * alpha2)981007f6c5eSJohannes Berg static int query_regdb(const char *alpha2)
982007f6c5eSJohannes Berg {
983007f6c5eSJohannes Berg 	const struct fwdb_header *hdr = regdb;
984007f6c5eSJohannes Berg 	const struct fwdb_country *country;
985007f6c5eSJohannes Berg 
9861ea4ff3eSJohannes Berg 	ASSERT_RTNL();
9871ea4ff3eSJohannes Berg 
988007f6c5eSJohannes Berg 	if (IS_ERR(regdb))
989007f6c5eSJohannes Berg 		return PTR_ERR(regdb);
990007f6c5eSJohannes Berg 
991007f6c5eSJohannes Berg 	country = &hdr->country[0];
992007f6c5eSJohannes Berg 	while (country->coll_ptr) {
993007f6c5eSJohannes Berg 		if (alpha2_equal(alpha2, country->alpha2))
994007f6c5eSJohannes Berg 			return regdb_query_country(regdb, country);
995007f6c5eSJohannes Berg 		country++;
996007f6c5eSJohannes Berg 	}
997007f6c5eSJohannes Berg 
998007f6c5eSJohannes Berg 	return -ENODATA;
999007f6c5eSJohannes Berg }
1000007f6c5eSJohannes Berg 
regdb_fw_cb(const struct firmware * fw,void * context)1001007f6c5eSJohannes Berg static void regdb_fw_cb(const struct firmware *fw, void *context)
1002007f6c5eSJohannes Berg {
10031ea4ff3eSJohannes Berg 	int set_error = 0;
10041ea4ff3eSJohannes Berg 	bool restore = true;
1005007f6c5eSJohannes Berg 	void *db;
1006007f6c5eSJohannes Berg 
1007007f6c5eSJohannes Berg 	if (!fw) {
1008007f6c5eSJohannes Berg 		pr_info("failed to load regulatory.db\n");
10091ea4ff3eSJohannes Berg 		set_error = -ENODATA;
10101ea4ff3eSJohannes Berg 	} else if (!valid_regdb(fw->data, fw->size)) {
101190a53e44SJohannes Berg 		pr_info("loaded regulatory.db is malformed or signature is missing/invalid\n");
10121ea4ff3eSJohannes Berg 		set_error = -EINVAL;
1013007f6c5eSJohannes Berg 	}
1014007f6c5eSJohannes Berg 
1015007f6c5eSJohannes Berg 	rtnl_lock();
1016faae54adSChaitanya Tata 	if (regdb && !IS_ERR(regdb)) {
1017faae54adSChaitanya Tata 		/* negative case - a bug
1018faae54adSChaitanya Tata 		 * positive case - can happen due to race in case of multiple cb's in
1019faae54adSChaitanya Tata 		 * queue, due to usage of asynchronous callback
1020faae54adSChaitanya Tata 		 *
1021faae54adSChaitanya Tata 		 * Either case, just restore and free new db.
1022faae54adSChaitanya Tata 		 */
10231ea4ff3eSJohannes Berg 	} else if (set_error) {
10241ea4ff3eSJohannes Berg 		regdb = ERR_PTR(set_error);
10251ea4ff3eSJohannes Berg 	} else if (fw) {
10261ea4ff3eSJohannes Berg 		db = kmemdup(fw->data, fw->size, GFP_KERNEL);
10271ea4ff3eSJohannes Berg 		if (db) {
10281ea4ff3eSJohannes Berg 			regdb = db;
10291ea4ff3eSJohannes Berg 			restore = context && query_regdb(context);
10301ea4ff3eSJohannes Berg 		} else {
10311ea4ff3eSJohannes Berg 			restore = true;
10321ea4ff3eSJohannes Berg 		}
10331ea4ff3eSJohannes Berg 	}
10341ea4ff3eSJohannes Berg 
10351ea4ff3eSJohannes Berg 	if (restore)
1036e646a025SJohannes Berg 		restore_regulatory_settings(true, false);
10371ea4ff3eSJohannes Berg 
1038007f6c5eSJohannes Berg 	rtnl_unlock();
10391ea4ff3eSJohannes Berg 
1040007f6c5eSJohannes Berg 	kfree(context);
10411ea4ff3eSJohannes Berg 
10421ea4ff3eSJohannes Berg 	release_firmware(fw);
1043007f6c5eSJohannes Berg }
1044007f6c5eSJohannes Berg 
10457bc7981eSDimitri John Ledkov MODULE_FIRMWARE("regulatory.db");
10467bc7981eSDimitri John Ledkov 
query_regdb_file(const char * alpha2)1047007f6c5eSJohannes Berg static int query_regdb_file(const char *alpha2)
1048007f6c5eSJohannes Berg {
104957b962e6SArend van Spriel 	int err;
105057b962e6SArend van Spriel 
10511ea4ff3eSJohannes Berg 	ASSERT_RTNL();
10521ea4ff3eSJohannes Berg 
1053007f6c5eSJohannes Berg 	if (regdb)
1054007f6c5eSJohannes Berg 		return query_regdb(alpha2);
1055007f6c5eSJohannes Berg 
1056007f6c5eSJohannes Berg 	alpha2 = kmemdup(alpha2, 2, GFP_KERNEL);
1057007f6c5eSJohannes Berg 	if (!alpha2)
1058007f6c5eSJohannes Berg 		return -ENOMEM;
1059007f6c5eSJohannes Berg 
106057b962e6SArend van Spriel 	err = request_firmware_nowait(THIS_MODULE, true, "regulatory.db",
1061007f6c5eSJohannes Berg 				      &reg_pdev->dev, GFP_KERNEL,
1062007f6c5eSJohannes Berg 				      (void *)alpha2, regdb_fw_cb);
106357b962e6SArend van Spriel 	if (err)
106457b962e6SArend van Spriel 		kfree(alpha2);
106557b962e6SArend van Spriel 
106657b962e6SArend van Spriel 	return err;
1067007f6c5eSJohannes Berg }
1068007f6c5eSJohannes Berg 
reg_reload_regdb(void)10691ea4ff3eSJohannes Berg int reg_reload_regdb(void)
10701ea4ff3eSJohannes Berg {
10711ea4ff3eSJohannes Berg 	const struct firmware *fw;
10721ea4ff3eSJohannes Berg 	void *db;
10731ea4ff3eSJohannes Berg 	int err;
10741eda9191SFinn Behrens 	const struct ieee80211_regdomain *current_regdomain;
10751eda9191SFinn Behrens 	struct regulatory_request *request;
10761ea4ff3eSJohannes Berg 
10771ea4ff3eSJohannes Berg 	err = request_firmware(&fw, "regulatory.db", &reg_pdev->dev);
10781ea4ff3eSJohannes Berg 	if (err)
10791ea4ff3eSJohannes Berg 		return err;
10801ea4ff3eSJohannes Berg 
10811ea4ff3eSJohannes Berg 	if (!valid_regdb(fw->data, fw->size)) {
10821ea4ff3eSJohannes Berg 		err = -ENODATA;
10831ea4ff3eSJohannes Berg 		goto out;
10841ea4ff3eSJohannes Berg 	}
10851ea4ff3eSJohannes Berg 
10861ea4ff3eSJohannes Berg 	db = kmemdup(fw->data, fw->size, GFP_KERNEL);
10871ea4ff3eSJohannes Berg 	if (!db) {
10881ea4ff3eSJohannes Berg 		err = -ENOMEM;
10891ea4ff3eSJohannes Berg 		goto out;
10901ea4ff3eSJohannes Berg 	}
10911ea4ff3eSJohannes Berg 
10921ea4ff3eSJohannes Berg 	rtnl_lock();
10931ea4ff3eSJohannes Berg 	if (!IS_ERR_OR_NULL(regdb))
10941ea4ff3eSJohannes Berg 		kfree(regdb);
10951ea4ff3eSJohannes Berg 	regdb = db;
10961ea4ff3eSJohannes Berg 
10971eda9191SFinn Behrens 	/* reset regulatory domain */
10981eda9191SFinn Behrens 	current_regdomain = get_cfg80211_regdom();
10991eda9191SFinn Behrens 
11001eda9191SFinn Behrens 	request = kzalloc(sizeof(*request), GFP_KERNEL);
11011eda9191SFinn Behrens 	if (!request) {
11021eda9191SFinn Behrens 		err = -ENOMEM;
11031eda9191SFinn Behrens 		goto out_unlock;
11041eda9191SFinn Behrens 	}
11051eda9191SFinn Behrens 
11061eda9191SFinn Behrens 	request->wiphy_idx = WIPHY_IDX_INVALID;
11071eda9191SFinn Behrens 	request->alpha2[0] = current_regdomain->alpha2[0];
11081eda9191SFinn Behrens 	request->alpha2[1] = current_regdomain->alpha2[1];
110937d33114SFinn Behrens 	request->initiator = NL80211_REGDOM_SET_BY_CORE;
11101eda9191SFinn Behrens 	request->user_reg_hint_type = NL80211_USER_REG_HINT_USER;
11111eda9191SFinn Behrens 
11121eda9191SFinn Behrens 	reg_process_hint(request);
11131eda9191SFinn Behrens 
11141eda9191SFinn Behrens out_unlock:
11151eda9191SFinn Behrens 	rtnl_unlock();
11161ea4ff3eSJohannes Berg  out:
11171ea4ff3eSJohannes Berg 	release_firmware(fw);
11181ea4ff3eSJohannes Berg 	return err;
11191ea4ff3eSJohannes Berg }
11201ea4ff3eSJohannes Berg 
reg_query_database(struct regulatory_request * request)1121cecbb069SJohannes Berg static bool reg_query_database(struct regulatory_request *request)
1122fe6631ffSLuis R. Rodriguez {
1123007f6c5eSJohannes Berg 	if (query_regdb_file(request->alpha2) == 0)
1124007f6c5eSJohannes Berg 		return true;
1125007f6c5eSJohannes Berg 
1126c7d319e5SJohannes Berg 	if (call_crda(request->alpha2) == 0)
1127c7d319e5SJohannes Berg 		return true;
1128c7d319e5SJohannes Berg 
1129c7d319e5SJohannes Berg 	return false;
1130fe6631ffSLuis R. Rodriguez }
1131fe6631ffSLuis R. Rodriguez 
reg_is_valid_request(const char * alpha2)1132e438768fSLuis R. Rodriguez bool reg_is_valid_request(const char *alpha2)
1133b2e1b302SLuis R. Rodriguez {
1134c492db37SJohannes Berg 	struct regulatory_request *lr = get_last_request();
113561405e97SLuis R. Rodriguez 
1136c492db37SJohannes Berg 	if (!lr || lr->processed)
1137f6037d09SJohannes Berg 		return false;
1138f6037d09SJohannes Berg 
1139c492db37SJohannes Berg 	return alpha2_equal(lr->alpha2, alpha2);
1140b2e1b302SLuis R. Rodriguez }
1141b2e1b302SLuis R. Rodriguez 
reg_get_regdomain(struct wiphy * wiphy)1142e3961af1SJanusz Dziedzic static const struct ieee80211_regdomain *reg_get_regdomain(struct wiphy *wiphy)
1143e3961af1SJanusz Dziedzic {
1144e3961af1SJanusz Dziedzic 	struct regulatory_request *lr = get_last_request();
1145e3961af1SJanusz Dziedzic 
1146e3961af1SJanusz Dziedzic 	/*
1147e3961af1SJanusz Dziedzic 	 * Follow the driver's regulatory domain, if present, unless a country
1148e3961af1SJanusz Dziedzic 	 * IE has been processed or a user wants to help complaince further
1149e3961af1SJanusz Dziedzic 	 */
1150e3961af1SJanusz Dziedzic 	if (lr->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE &&
1151e3961af1SJanusz Dziedzic 	    lr->initiator != NL80211_REGDOM_SET_BY_USER &&
1152e3961af1SJanusz Dziedzic 	    wiphy->regd)
1153e3961af1SJanusz Dziedzic 		return get_wiphy_regdom(wiphy);
1154e3961af1SJanusz Dziedzic 
1155e3961af1SJanusz Dziedzic 	return get_cfg80211_regdom();
1156e3961af1SJanusz Dziedzic }
1157e3961af1SJanusz Dziedzic 
1158a6d4a534SArik Nemtsov static unsigned int
reg_get_max_bandwidth_from_range(const struct ieee80211_regdomain * rd,const struct ieee80211_reg_rule * rule)1159a6d4a534SArik Nemtsov reg_get_max_bandwidth_from_range(const struct ieee80211_regdomain *rd,
116097524820SJanusz Dziedzic 				 const struct ieee80211_reg_rule *rule)
116197524820SJanusz Dziedzic {
116297524820SJanusz Dziedzic 	const struct ieee80211_freq_range *freq_range = &rule->freq_range;
116397524820SJanusz Dziedzic 	const struct ieee80211_freq_range *freq_range_tmp;
116497524820SJanusz Dziedzic 	const struct ieee80211_reg_rule *tmp;
116597524820SJanusz Dziedzic 	u32 start_freq, end_freq, idx, no;
116697524820SJanusz Dziedzic 
116797524820SJanusz Dziedzic 	for (idx = 0; idx < rd->n_reg_rules; idx++)
116897524820SJanusz Dziedzic 		if (rule == &rd->reg_rules[idx])
116997524820SJanusz Dziedzic 			break;
117097524820SJanusz Dziedzic 
117197524820SJanusz Dziedzic 	if (idx == rd->n_reg_rules)
117297524820SJanusz Dziedzic 		return 0;
117397524820SJanusz Dziedzic 
117497524820SJanusz Dziedzic 	/* get start_freq */
117597524820SJanusz Dziedzic 	no = idx;
117697524820SJanusz Dziedzic 
117797524820SJanusz Dziedzic 	while (no) {
117897524820SJanusz Dziedzic 		tmp = &rd->reg_rules[--no];
117997524820SJanusz Dziedzic 		freq_range_tmp = &tmp->freq_range;
118097524820SJanusz Dziedzic 
118197524820SJanusz Dziedzic 		if (freq_range_tmp->end_freq_khz < freq_range->start_freq_khz)
118297524820SJanusz Dziedzic 			break;
118397524820SJanusz Dziedzic 
118497524820SJanusz Dziedzic 		freq_range = freq_range_tmp;
118597524820SJanusz Dziedzic 	}
118697524820SJanusz Dziedzic 
118797524820SJanusz Dziedzic 	start_freq = freq_range->start_freq_khz;
118897524820SJanusz Dziedzic 
118997524820SJanusz Dziedzic 	/* get end_freq */
119097524820SJanusz Dziedzic 	freq_range = &rule->freq_range;
119197524820SJanusz Dziedzic 	no = idx;
119297524820SJanusz Dziedzic 
119397524820SJanusz Dziedzic 	while (no < rd->n_reg_rules - 1) {
119497524820SJanusz Dziedzic 		tmp = &rd->reg_rules[++no];
119597524820SJanusz Dziedzic 		freq_range_tmp = &tmp->freq_range;
119697524820SJanusz Dziedzic 
119797524820SJanusz Dziedzic 		if (freq_range_tmp->start_freq_khz > freq_range->end_freq_khz)
119897524820SJanusz Dziedzic 			break;
119997524820SJanusz Dziedzic 
120097524820SJanusz Dziedzic 		freq_range = freq_range_tmp;
120197524820SJanusz Dziedzic 	}
120297524820SJanusz Dziedzic 
120397524820SJanusz Dziedzic 	end_freq = freq_range->end_freq_khz;
120497524820SJanusz Dziedzic 
120597524820SJanusz Dziedzic 	return end_freq - start_freq;
120697524820SJanusz Dziedzic }
120797524820SJanusz Dziedzic 
reg_get_max_bandwidth(const struct ieee80211_regdomain * rd,const struct ieee80211_reg_rule * rule)1208a6d4a534SArik Nemtsov unsigned int reg_get_max_bandwidth(const struct ieee80211_regdomain *rd,
1209a6d4a534SArik Nemtsov 				   const struct ieee80211_reg_rule *rule)
1210a6d4a534SArik Nemtsov {
1211a6d4a534SArik Nemtsov 	unsigned int bw = reg_get_max_bandwidth_from_range(rd, rule);
1212a6d4a534SArik Nemtsov 
1213c2b3d769SSriram R 	if (rule->flags & NL80211_RRF_NO_320MHZ)
1214c2b3d769SSriram R 		bw = min_t(unsigned int, bw, MHZ_TO_KHZ(160));
1215a6d4a534SArik Nemtsov 	if (rule->flags & NL80211_RRF_NO_160MHZ)
1216a6d4a534SArik Nemtsov 		bw = min_t(unsigned int, bw, MHZ_TO_KHZ(80));
1217a6d4a534SArik Nemtsov 	if (rule->flags & NL80211_RRF_NO_80MHZ)
1218a6d4a534SArik Nemtsov 		bw = min_t(unsigned int, bw, MHZ_TO_KHZ(40));
1219a6d4a534SArik Nemtsov 
1220a6d4a534SArik Nemtsov 	/*
1221a6d4a534SArik Nemtsov 	 * HT40+/HT40- limits are handled per-channel. Only limit BW if both
1222a6d4a534SArik Nemtsov 	 * are not allowed.
1223a6d4a534SArik Nemtsov 	 */
1224a6d4a534SArik Nemtsov 	if (rule->flags & NL80211_RRF_NO_HT40MINUS &&
1225a6d4a534SArik Nemtsov 	    rule->flags & NL80211_RRF_NO_HT40PLUS)
1226a6d4a534SArik Nemtsov 		bw = min_t(unsigned int, bw, MHZ_TO_KHZ(20));
1227a6d4a534SArik Nemtsov 
1228a6d4a534SArik Nemtsov 	return bw;
1229a6d4a534SArik Nemtsov }
1230a6d4a534SArik Nemtsov 
1231b2e1b302SLuis R. Rodriguez /* Sanity check on a regulatory rule */
is_valid_reg_rule(const struct ieee80211_reg_rule * rule)1232a3d2eaf0SJohannes Berg static bool is_valid_reg_rule(const struct ieee80211_reg_rule *rule)
1233b2e1b302SLuis R. Rodriguez {
1234a3d2eaf0SJohannes Berg 	const struct ieee80211_freq_range *freq_range = &rule->freq_range;
1235b2e1b302SLuis R. Rodriguez 	u32 freq_diff;
1236b2e1b302SLuis R. Rodriguez 
123791e99004SLuis R. Rodriguez 	if (freq_range->start_freq_khz <= 0 || freq_range->end_freq_khz <= 0)
1238b2e1b302SLuis R. Rodriguez 		return false;
1239b2e1b302SLuis R. Rodriguez 
1240b2e1b302SLuis R. Rodriguez 	if (freq_range->start_freq_khz > freq_range->end_freq_khz)
1241b2e1b302SLuis R. Rodriguez 		return false;
1242b2e1b302SLuis R. Rodriguez 
1243b2e1b302SLuis R. Rodriguez 	freq_diff = freq_range->end_freq_khz - freq_range->start_freq_khz;
1244b2e1b302SLuis R. Rodriguez 
1245bd05f28eSRoel Kluin 	if (freq_range->end_freq_khz <= freq_range->start_freq_khz ||
1246bd05f28eSRoel Kluin 	    freq_range->max_bandwidth_khz > freq_diff)
1247b2e1b302SLuis R. Rodriguez 		return false;
1248b2e1b302SLuis R. Rodriguez 
1249b2e1b302SLuis R. Rodriguez 	return true;
1250b2e1b302SLuis R. Rodriguez }
1251b2e1b302SLuis R. Rodriguez 
is_valid_rd(const struct ieee80211_regdomain * rd)1252a3d2eaf0SJohannes Berg static bool is_valid_rd(const struct ieee80211_regdomain *rd)
1253b2e1b302SLuis R. Rodriguez {
1254a3d2eaf0SJohannes Berg 	const struct ieee80211_reg_rule *reg_rule = NULL;
1255b2e1b302SLuis R. Rodriguez 	unsigned int i;
1256b2e1b302SLuis R. Rodriguez 
1257b2e1b302SLuis R. Rodriguez 	if (!rd->n_reg_rules)
1258b2e1b302SLuis R. Rodriguez 		return false;
1259b2e1b302SLuis R. Rodriguez 
126088dc1c3fSLuis R. Rodriguez 	if (WARN_ON(rd->n_reg_rules > NL80211_MAX_SUPP_REG_RULES))
126188dc1c3fSLuis R. Rodriguez 		return false;
126288dc1c3fSLuis R. Rodriguez 
1263b2e1b302SLuis R. Rodriguez 	for (i = 0; i < rd->n_reg_rules; i++) {
1264b2e1b302SLuis R. Rodriguez 		reg_rule = &rd->reg_rules[i];
1265b2e1b302SLuis R. Rodriguez 		if (!is_valid_reg_rule(reg_rule))
1266b2e1b302SLuis R. Rodriguez 			return false;
1267b2e1b302SLuis R. Rodriguez 	}
1268b2e1b302SLuis R. Rodriguez 
1269b2e1b302SLuis R. Rodriguez 	return true;
1270b2e1b302SLuis R. Rodriguez }
1271b2e1b302SLuis R. Rodriguez 
12720c7dc45dSLuis R. Rodriguez /**
12730c7dc45dSLuis R. Rodriguez  * freq_in_rule_band - tells us if a frequency is in a frequency band
12740c7dc45dSLuis R. Rodriguez  * @freq_range: frequency rule we want to query
12750c7dc45dSLuis R. Rodriguez  * @freq_khz: frequency we are inquiring about
12760c7dc45dSLuis R. Rodriguez  *
12770c7dc45dSLuis R. Rodriguez  * This lets us know if a specific frequency rule is or is not relevant to
12780c7dc45dSLuis R. Rodriguez  * a specific frequency's band. Bands are device specific and artificial
127964629b9dSVladimir Kondratiev  * definitions (the "2.4 GHz band", the "5 GHz band" and the "60GHz band"),
128064629b9dSVladimir Kondratiev  * however it is safe for now to assume that a frequency rule should not be
128164629b9dSVladimir Kondratiev  * part of a frequency's band if the start freq or end freq are off by more
128293183bdbSChaitanya Tata  * than 2 GHz for the 2.4 and 5 GHz bands, and by more than 20 GHz for the
128364629b9dSVladimir Kondratiev  * 60 GHz band.
12840c7dc45dSLuis R. Rodriguez  * This resolution can be lowered and should be considered as we add
12850c7dc45dSLuis R. Rodriguez  * regulatory rule support for other "bands".
12860c7dc45dSLuis R. Rodriguez  **/
freq_in_rule_band(const struct ieee80211_freq_range * freq_range,u32 freq_khz)12870c7dc45dSLuis R. Rodriguez static bool freq_in_rule_band(const struct ieee80211_freq_range *freq_range,
12880c7dc45dSLuis R. Rodriguez 			      u32 freq_khz)
12890c7dc45dSLuis R. Rodriguez {
12900c7dc45dSLuis R. Rodriguez #define ONE_GHZ_IN_KHZ	1000000
129164629b9dSVladimir Kondratiev 	/*
129264629b9dSVladimir Kondratiev 	 * From 802.11ad: directional multi-gigabit (DMG):
129364629b9dSVladimir Kondratiev 	 * Pertaining to operation in a frequency band containing a channel
129464629b9dSVladimir Kondratiev 	 * with the Channel starting frequency above 45 GHz.
129564629b9dSVladimir Kondratiev 	 */
129664629b9dSVladimir Kondratiev 	u32 limit = freq_khz > 45 * ONE_GHZ_IN_KHZ ?
129793183bdbSChaitanya Tata 			20 * ONE_GHZ_IN_KHZ : 2 * ONE_GHZ_IN_KHZ;
129864629b9dSVladimir Kondratiev 	if (abs(freq_khz - freq_range->start_freq_khz) <= limit)
12990c7dc45dSLuis R. Rodriguez 		return true;
130064629b9dSVladimir Kondratiev 	if (abs(freq_khz - freq_range->end_freq_khz) <= limit)
13010c7dc45dSLuis R. Rodriguez 		return true;
13020c7dc45dSLuis R. Rodriguez 	return false;
13030c7dc45dSLuis R. Rodriguez #undef ONE_GHZ_IN_KHZ
13040c7dc45dSLuis R. Rodriguez }
13050c7dc45dSLuis R. Rodriguez 
1306fb1fc7adSLuis R. Rodriguez /*
1307adbfb058SLuis R. Rodriguez  * Later on we can perhaps use the more restrictive DFS
1308adbfb058SLuis R. Rodriguez  * region but we don't have information for that yet so
1309adbfb058SLuis R. Rodriguez  * for now simply disallow conflicts.
1310adbfb058SLuis R. Rodriguez  */
1311adbfb058SLuis R. Rodriguez static enum nl80211_dfs_regions
reg_intersect_dfs_region(const enum nl80211_dfs_regions dfs_region1,const enum nl80211_dfs_regions dfs_region2)1312adbfb058SLuis R. Rodriguez reg_intersect_dfs_region(const enum nl80211_dfs_regions dfs_region1,
1313adbfb058SLuis R. Rodriguez 			 const enum nl80211_dfs_regions dfs_region2)
1314adbfb058SLuis R. Rodriguez {
1315adbfb058SLuis R. Rodriguez 	if (dfs_region1 != dfs_region2)
1316adbfb058SLuis R. Rodriguez 		return NL80211_DFS_UNSET;
1317adbfb058SLuis R. Rodriguez 	return dfs_region1;
1318adbfb058SLuis R. Rodriguez }
1319adbfb058SLuis R. Rodriguez 
reg_wmm_rules_intersect(const struct ieee80211_wmm_ac * wmm_ac1,const struct ieee80211_wmm_ac * wmm_ac2,struct ieee80211_wmm_ac * intersect)132008a75a88SIlan Peer static void reg_wmm_rules_intersect(const struct ieee80211_wmm_ac *wmm_ac1,
132108a75a88SIlan Peer 				    const struct ieee80211_wmm_ac *wmm_ac2,
132208a75a88SIlan Peer 				    struct ieee80211_wmm_ac *intersect)
132308a75a88SIlan Peer {
132408a75a88SIlan Peer 	intersect->cw_min = max_t(u16, wmm_ac1->cw_min, wmm_ac2->cw_min);
132508a75a88SIlan Peer 	intersect->cw_max = max_t(u16, wmm_ac1->cw_max, wmm_ac2->cw_max);
132608a75a88SIlan Peer 	intersect->cot = min_t(u16, wmm_ac1->cot, wmm_ac2->cot);
132708a75a88SIlan Peer 	intersect->aifsn = max_t(u8, wmm_ac1->aifsn, wmm_ac2->aifsn);
132808a75a88SIlan Peer }
132908a75a88SIlan Peer 
1330adbfb058SLuis R. Rodriguez /*
1331fb1fc7adSLuis R. Rodriguez  * Helper for regdom_intersect(), this does the real
1332fb1fc7adSLuis R. Rodriguez  * mathematical intersection fun
1333fb1fc7adSLuis R. Rodriguez  */
reg_rules_intersect(const struct ieee80211_regdomain * rd1,const struct ieee80211_regdomain * rd2,const struct ieee80211_reg_rule * rule1,const struct ieee80211_reg_rule * rule2,struct ieee80211_reg_rule * intersected_rule)133497524820SJanusz Dziedzic static int reg_rules_intersect(const struct ieee80211_regdomain *rd1,
133597524820SJanusz Dziedzic 			       const struct ieee80211_regdomain *rd2,
133697524820SJanusz Dziedzic 			       const struct ieee80211_reg_rule *rule1,
13379c96477dSLuis R. Rodriguez 			       const struct ieee80211_reg_rule *rule2,
13389c96477dSLuis R. Rodriguez 			       struct ieee80211_reg_rule *intersected_rule)
13399c96477dSLuis R. Rodriguez {
13409c96477dSLuis R. Rodriguez 	const struct ieee80211_freq_range *freq_range1, *freq_range2;
13419c96477dSLuis R. Rodriguez 	struct ieee80211_freq_range *freq_range;
13429c96477dSLuis R. Rodriguez 	const struct ieee80211_power_rule *power_rule1, *power_rule2;
13439c96477dSLuis R. Rodriguez 	struct ieee80211_power_rule *power_rule;
134408a75a88SIlan Peer 	const struct ieee80211_wmm_rule *wmm_rule1, *wmm_rule2;
134508a75a88SIlan Peer 	struct ieee80211_wmm_rule *wmm_rule;
134697524820SJanusz Dziedzic 	u32 freq_diff, max_bandwidth1, max_bandwidth2;
13479c96477dSLuis R. Rodriguez 
13489c96477dSLuis R. Rodriguez 	freq_range1 = &rule1->freq_range;
13499c96477dSLuis R. Rodriguez 	freq_range2 = &rule2->freq_range;
13509c96477dSLuis R. Rodriguez 	freq_range = &intersected_rule->freq_range;
13519c96477dSLuis R. Rodriguez 
13529c96477dSLuis R. Rodriguez 	power_rule1 = &rule1->power_rule;
13539c96477dSLuis R. Rodriguez 	power_rule2 = &rule2->power_rule;
13549c96477dSLuis R. Rodriguez 	power_rule = &intersected_rule->power_rule;
13559c96477dSLuis R. Rodriguez 
135608a75a88SIlan Peer 	wmm_rule1 = &rule1->wmm_rule;
135708a75a88SIlan Peer 	wmm_rule2 = &rule2->wmm_rule;
135808a75a88SIlan Peer 	wmm_rule = &intersected_rule->wmm_rule;
135908a75a88SIlan Peer 
13609c96477dSLuis R. Rodriguez 	freq_range->start_freq_khz = max(freq_range1->start_freq_khz,
13619c96477dSLuis R. Rodriguez 					 freq_range2->start_freq_khz);
13629c96477dSLuis R. Rodriguez 	freq_range->end_freq_khz = min(freq_range1->end_freq_khz,
13639c96477dSLuis R. Rodriguez 				       freq_range2->end_freq_khz);
136497524820SJanusz Dziedzic 
136597524820SJanusz Dziedzic 	max_bandwidth1 = freq_range1->max_bandwidth_khz;
136697524820SJanusz Dziedzic 	max_bandwidth2 = freq_range2->max_bandwidth_khz;
136797524820SJanusz Dziedzic 
1368b0dfd2eaSJanusz Dziedzic 	if (rule1->flags & NL80211_RRF_AUTO_BW)
136997524820SJanusz Dziedzic 		max_bandwidth1 = reg_get_max_bandwidth(rd1, rule1);
1370b0dfd2eaSJanusz Dziedzic 	if (rule2->flags & NL80211_RRF_AUTO_BW)
137197524820SJanusz Dziedzic 		max_bandwidth2 = reg_get_max_bandwidth(rd2, rule2);
137297524820SJanusz Dziedzic 
137397524820SJanusz Dziedzic 	freq_range->max_bandwidth_khz = min(max_bandwidth1, max_bandwidth2);
13749c96477dSLuis R. Rodriguez 
1375b0dfd2eaSJanusz Dziedzic 	intersected_rule->flags = rule1->flags | rule2->flags;
1376b0dfd2eaSJanusz Dziedzic 
1377b0dfd2eaSJanusz Dziedzic 	/*
1378b0dfd2eaSJanusz Dziedzic 	 * In case NL80211_RRF_AUTO_BW requested for both rules
1379b0dfd2eaSJanusz Dziedzic 	 * set AUTO_BW in intersected rule also. Next we will
1380b0dfd2eaSJanusz Dziedzic 	 * calculate BW correctly in handle_channel function.
1381b0dfd2eaSJanusz Dziedzic 	 * In other case remove AUTO_BW flag while we calculate
1382b0dfd2eaSJanusz Dziedzic 	 * maximum bandwidth correctly and auto calculation is
1383b0dfd2eaSJanusz Dziedzic 	 * not required.
1384b0dfd2eaSJanusz Dziedzic 	 */
1385b0dfd2eaSJanusz Dziedzic 	if ((rule1->flags & NL80211_RRF_AUTO_BW) &&
1386b0dfd2eaSJanusz Dziedzic 	    (rule2->flags & NL80211_RRF_AUTO_BW))
1387b0dfd2eaSJanusz Dziedzic 		intersected_rule->flags |= NL80211_RRF_AUTO_BW;
1388b0dfd2eaSJanusz Dziedzic 	else
1389b0dfd2eaSJanusz Dziedzic 		intersected_rule->flags &= ~NL80211_RRF_AUTO_BW;
1390b0dfd2eaSJanusz Dziedzic 
13919c96477dSLuis R. Rodriguez 	freq_diff = freq_range->end_freq_khz - freq_range->start_freq_khz;
13929c96477dSLuis R. Rodriguez 	if (freq_range->max_bandwidth_khz > freq_diff)
13939c96477dSLuis R. Rodriguez 		freq_range->max_bandwidth_khz = freq_diff;
13949c96477dSLuis R. Rodriguez 
13959c96477dSLuis R. Rodriguez 	power_rule->max_eirp = min(power_rule1->max_eirp,
13969c96477dSLuis R. Rodriguez 		power_rule2->max_eirp);
13979c96477dSLuis R. Rodriguez 	power_rule->max_antenna_gain = min(power_rule1->max_antenna_gain,
13989c96477dSLuis R. Rodriguez 		power_rule2->max_antenna_gain);
13999c96477dSLuis R. Rodriguez 
1400089027e5SJanusz Dziedzic 	intersected_rule->dfs_cac_ms = max(rule1->dfs_cac_ms,
1401089027e5SJanusz Dziedzic 					   rule2->dfs_cac_ms);
1402089027e5SJanusz Dziedzic 
140308a75a88SIlan Peer 	if (rule1->has_wmm && rule2->has_wmm) {
140408a75a88SIlan Peer 		u8 ac;
140508a75a88SIlan Peer 
140608a75a88SIlan Peer 		for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
140708a75a88SIlan Peer 			reg_wmm_rules_intersect(&wmm_rule1->client[ac],
140808a75a88SIlan Peer 						&wmm_rule2->client[ac],
140908a75a88SIlan Peer 						&wmm_rule->client[ac]);
141008a75a88SIlan Peer 			reg_wmm_rules_intersect(&wmm_rule1->ap[ac],
141108a75a88SIlan Peer 						&wmm_rule2->ap[ac],
141208a75a88SIlan Peer 						&wmm_rule->ap[ac]);
141308a75a88SIlan Peer 		}
141408a75a88SIlan Peer 
141508a75a88SIlan Peer 		intersected_rule->has_wmm = true;
141608a75a88SIlan Peer 	} else if (rule1->has_wmm) {
141708a75a88SIlan Peer 		*wmm_rule = *wmm_rule1;
141808a75a88SIlan Peer 		intersected_rule->has_wmm = true;
141908a75a88SIlan Peer 	} else if (rule2->has_wmm) {
142008a75a88SIlan Peer 		*wmm_rule = *wmm_rule2;
142108a75a88SIlan Peer 		intersected_rule->has_wmm = true;
142208a75a88SIlan Peer 	} else {
142308a75a88SIlan Peer 		intersected_rule->has_wmm = false;
142408a75a88SIlan Peer 	}
142508a75a88SIlan Peer 
14269c96477dSLuis R. Rodriguez 	if (!is_valid_reg_rule(intersected_rule))
14279c96477dSLuis R. Rodriguez 		return -EINVAL;
14289c96477dSLuis R. Rodriguez 
14299c96477dSLuis R. Rodriguez 	return 0;
14309c96477dSLuis R. Rodriguez }
14319c96477dSLuis R. Rodriguez 
1432a62a1aedSEliad Peller /* check whether old rule contains new rule */
rule_contains(struct ieee80211_reg_rule * r1,struct ieee80211_reg_rule * r2)1433a62a1aedSEliad Peller static bool rule_contains(struct ieee80211_reg_rule *r1,
1434a62a1aedSEliad Peller 			  struct ieee80211_reg_rule *r2)
1435a62a1aedSEliad Peller {
1436a62a1aedSEliad Peller 	/* for simplicity, currently consider only same flags */
1437a62a1aedSEliad Peller 	if (r1->flags != r2->flags)
1438a62a1aedSEliad Peller 		return false;
1439a62a1aedSEliad Peller 
1440a62a1aedSEliad Peller 	/* verify r1 is more restrictive */
1441a62a1aedSEliad Peller 	if ((r1->power_rule.max_antenna_gain >
1442a62a1aedSEliad Peller 	     r2->power_rule.max_antenna_gain) ||
1443a62a1aedSEliad Peller 	    r1->power_rule.max_eirp > r2->power_rule.max_eirp)
1444a62a1aedSEliad Peller 		return false;
1445a62a1aedSEliad Peller 
1446a62a1aedSEliad Peller 	/* make sure r2's range is contained within r1 */
1447a62a1aedSEliad Peller 	if (r1->freq_range.start_freq_khz > r2->freq_range.start_freq_khz ||
1448a62a1aedSEliad Peller 	    r1->freq_range.end_freq_khz < r2->freq_range.end_freq_khz)
1449a62a1aedSEliad Peller 		return false;
1450a62a1aedSEliad Peller 
1451a62a1aedSEliad Peller 	/* and finally verify that r1.max_bw >= r2.max_bw */
1452a62a1aedSEliad Peller 	if (r1->freq_range.max_bandwidth_khz <
1453a62a1aedSEliad Peller 	    r2->freq_range.max_bandwidth_khz)
1454a62a1aedSEliad Peller 		return false;
1455a62a1aedSEliad Peller 
1456a62a1aedSEliad Peller 	return true;
1457a62a1aedSEliad Peller }
1458a62a1aedSEliad Peller 
1459a62a1aedSEliad Peller /* add or extend current rules. do nothing if rule is already contained */
add_rule(struct ieee80211_reg_rule * rule,struct ieee80211_reg_rule * reg_rules,u32 * n_rules)1460a62a1aedSEliad Peller static void add_rule(struct ieee80211_reg_rule *rule,
1461a62a1aedSEliad Peller 		     struct ieee80211_reg_rule *reg_rules, u32 *n_rules)
1462a62a1aedSEliad Peller {
1463a62a1aedSEliad Peller 	struct ieee80211_reg_rule *tmp_rule;
1464a62a1aedSEliad Peller 	int i;
1465a62a1aedSEliad Peller 
1466a62a1aedSEliad Peller 	for (i = 0; i < *n_rules; i++) {
1467a62a1aedSEliad Peller 		tmp_rule = &reg_rules[i];
1468a62a1aedSEliad Peller 		/* rule is already contained - do nothing */
1469a62a1aedSEliad Peller 		if (rule_contains(tmp_rule, rule))
1470a62a1aedSEliad Peller 			return;
1471a62a1aedSEliad Peller 
1472a62a1aedSEliad Peller 		/* extend rule if possible */
1473a62a1aedSEliad Peller 		if (rule_contains(rule, tmp_rule)) {
1474a62a1aedSEliad Peller 			memcpy(tmp_rule, rule, sizeof(*rule));
1475a62a1aedSEliad Peller 			return;
1476a62a1aedSEliad Peller 		}
1477a62a1aedSEliad Peller 	}
1478a62a1aedSEliad Peller 
1479a62a1aedSEliad Peller 	memcpy(&reg_rules[*n_rules], rule, sizeof(*rule));
1480a62a1aedSEliad Peller 	(*n_rules)++;
1481a62a1aedSEliad Peller }
1482a62a1aedSEliad Peller 
14839c96477dSLuis R. Rodriguez /**
14849c96477dSLuis R. Rodriguez  * regdom_intersect - do the intersection between two regulatory domains
14859c96477dSLuis R. Rodriguez  * @rd1: first regulatory domain
14869c96477dSLuis R. Rodriguez  * @rd2: second regulatory domain
14879c96477dSLuis R. Rodriguez  *
14889c96477dSLuis R. Rodriguez  * Use this function to get the intersection between two regulatory domains.
14899c96477dSLuis R. Rodriguez  * Once completed we will mark the alpha2 for the rd as intersected, "98",
14909c96477dSLuis R. Rodriguez  * as no one single alpha2 can represent this regulatory domain.
14919c96477dSLuis R. Rodriguez  *
14929c96477dSLuis R. Rodriguez  * Returns a pointer to the regulatory domain structure which will hold the
14939c96477dSLuis R. Rodriguez  * resulting intersection of rules between rd1 and rd2. We will
14949c96477dSLuis R. Rodriguez  * kzalloc() this structure for you.
14959c96477dSLuis R. Rodriguez  */
14961a919318SJohannes Berg static struct ieee80211_regdomain *
regdom_intersect(const struct ieee80211_regdomain * rd1,const struct ieee80211_regdomain * rd2)14971a919318SJohannes Berg regdom_intersect(const struct ieee80211_regdomain *rd1,
14989c96477dSLuis R. Rodriguez 		 const struct ieee80211_regdomain *rd2)
14999c96477dSLuis R. Rodriguez {
15009f8c7136SGustavo A. R. Silva 	int r;
15019c96477dSLuis R. Rodriguez 	unsigned int x, y;
1502a62a1aedSEliad Peller 	unsigned int num_rules = 0;
15039c96477dSLuis R. Rodriguez 	const struct ieee80211_reg_rule *rule1, *rule2;
1504a62a1aedSEliad Peller 	struct ieee80211_reg_rule intersected_rule;
15059c96477dSLuis R. Rodriguez 	struct ieee80211_regdomain *rd;
15069c96477dSLuis R. Rodriguez 
15079c96477dSLuis R. Rodriguez 	if (!rd1 || !rd2)
15089c96477dSLuis R. Rodriguez 		return NULL;
15099c96477dSLuis R. Rodriguez 
1510fb1fc7adSLuis R. Rodriguez 	/*
1511fb1fc7adSLuis R. Rodriguez 	 * First we get a count of the rules we'll need, then we actually
15129c96477dSLuis R. Rodriguez 	 * build them. This is to so we can malloc() and free() a
15139c96477dSLuis R. Rodriguez 	 * regdomain once. The reason we use reg_rules_intersect() here
15149c96477dSLuis R. Rodriguez 	 * is it will return -EINVAL if the rule computed makes no sense.
1515fb1fc7adSLuis R. Rodriguez 	 * All rules that do check out OK are valid.
1516fb1fc7adSLuis R. Rodriguez 	 */
15179c96477dSLuis R. Rodriguez 
15189c96477dSLuis R. Rodriguez 	for (x = 0; x < rd1->n_reg_rules; x++) {
15199c96477dSLuis R. Rodriguez 		rule1 = &rd1->reg_rules[x];
15209c96477dSLuis R. Rodriguez 		for (y = 0; y < rd2->n_reg_rules; y++) {
15219c96477dSLuis R. Rodriguez 			rule2 = &rd2->reg_rules[y];
152297524820SJanusz Dziedzic 			if (!reg_rules_intersect(rd1, rd2, rule1, rule2,
1523a62a1aedSEliad Peller 						 &intersected_rule))
15249c96477dSLuis R. Rodriguez 				num_rules++;
15259c96477dSLuis R. Rodriguez 		}
15269c96477dSLuis R. Rodriguez 	}
15279c96477dSLuis R. Rodriguez 
15289c96477dSLuis R. Rodriguez 	if (!num_rules)
15299c96477dSLuis R. Rodriguez 		return NULL;
15309c96477dSLuis R. Rodriguez 
15319f8c7136SGustavo A. R. Silva 	rd = kzalloc(struct_size(rd, reg_rules, num_rules), GFP_KERNEL);
15329c96477dSLuis R. Rodriguez 	if (!rd)
15339c96477dSLuis R. Rodriguez 		return NULL;
15349c96477dSLuis R. Rodriguez 
1535a62a1aedSEliad Peller 	for (x = 0; x < rd1->n_reg_rules; x++) {
15369c96477dSLuis R. Rodriguez 		rule1 = &rd1->reg_rules[x];
1537a62a1aedSEliad Peller 		for (y = 0; y < rd2->n_reg_rules; y++) {
15389c96477dSLuis R. Rodriguez 			rule2 = &rd2->reg_rules[y];
153997524820SJanusz Dziedzic 			r = reg_rules_intersect(rd1, rd2, rule1, rule2,
1540a62a1aedSEliad Peller 						&intersected_rule);
1541fb1fc7adSLuis R. Rodriguez 			/*
1542fb1fc7adSLuis R. Rodriguez 			 * No need to memset here the intersected rule here as
1543fb1fc7adSLuis R. Rodriguez 			 * we're not using the stack anymore
1544fb1fc7adSLuis R. Rodriguez 			 */
15459c96477dSLuis R. Rodriguez 			if (r)
15469c96477dSLuis R. Rodriguez 				continue;
1547a62a1aedSEliad Peller 
1548a62a1aedSEliad Peller 			add_rule(&intersected_rule, rd->reg_rules,
1549a62a1aedSEliad Peller 				 &rd->n_reg_rules);
15509c96477dSLuis R. Rodriguez 		}
15519c96477dSLuis R. Rodriguez 	}
15529c96477dSLuis R. Rodriguez 
15539c96477dSLuis R. Rodriguez 	rd->alpha2[0] = '9';
15549c96477dSLuis R. Rodriguez 	rd->alpha2[1] = '8';
1555adbfb058SLuis R. Rodriguez 	rd->dfs_region = reg_intersect_dfs_region(rd1->dfs_region,
1556adbfb058SLuis R. Rodriguez 						  rd2->dfs_region);
15579c96477dSLuis R. Rodriguez 
15589c96477dSLuis R. Rodriguez 	return rd;
15599c96477dSLuis R. Rodriguez }
15609c96477dSLuis R. Rodriguez 
1561fb1fc7adSLuis R. Rodriguez /*
1562fb1fc7adSLuis R. Rodriguez  * XXX: add support for the rest of enum nl80211_reg_rule_flags, we may
1563fb1fc7adSLuis R. Rodriguez  * want to just have the channel structure use these
1564fb1fc7adSLuis R. Rodriguez  */
map_regdom_flags(u32 rd_flags)1565b2e1b302SLuis R. Rodriguez static u32 map_regdom_flags(u32 rd_flags)
1566b2e1b302SLuis R. Rodriguez {
1567b2e1b302SLuis R. Rodriguez 	u32 channel_flags = 0;
15688fe02e16SLuis R. Rodriguez 	if (rd_flags & NL80211_RRF_NO_IR_ALL)
15698fe02e16SLuis R. Rodriguez 		channel_flags |= IEEE80211_CHAN_NO_IR;
1570b2e1b302SLuis R. Rodriguez 	if (rd_flags & NL80211_RRF_DFS)
1571b2e1b302SLuis R. Rodriguez 		channel_flags |= IEEE80211_CHAN_RADAR;
157203f6b084SSeth Forshee 	if (rd_flags & NL80211_RRF_NO_OFDM)
157303f6b084SSeth Forshee 		channel_flags |= IEEE80211_CHAN_NO_OFDM;
1574570dbde1SDavid Spinadel 	if (rd_flags & NL80211_RRF_NO_OUTDOOR)
1575570dbde1SDavid Spinadel 		channel_flags |= IEEE80211_CHAN_INDOOR_ONLY;
157606f207fcSArik Nemtsov 	if (rd_flags & NL80211_RRF_IR_CONCURRENT)
157706f207fcSArik Nemtsov 		channel_flags |= IEEE80211_CHAN_IR_CONCURRENT;
1578a6d4a534SArik Nemtsov 	if (rd_flags & NL80211_RRF_NO_HT40MINUS)
1579a6d4a534SArik Nemtsov 		channel_flags |= IEEE80211_CHAN_NO_HT40MINUS;
1580a6d4a534SArik Nemtsov 	if (rd_flags & NL80211_RRF_NO_HT40PLUS)
1581a6d4a534SArik Nemtsov 		channel_flags |= IEEE80211_CHAN_NO_HT40PLUS;
1582a6d4a534SArik Nemtsov 	if (rd_flags & NL80211_RRF_NO_80MHZ)
1583a6d4a534SArik Nemtsov 		channel_flags |= IEEE80211_CHAN_NO_80MHZ;
1584a6d4a534SArik Nemtsov 	if (rd_flags & NL80211_RRF_NO_160MHZ)
1585a6d4a534SArik Nemtsov 		channel_flags |= IEEE80211_CHAN_NO_160MHZ;
15861e61d82cSHaim Dreyfuss 	if (rd_flags & NL80211_RRF_NO_HE)
15871e61d82cSHaim Dreyfuss 		channel_flags |= IEEE80211_CHAN_NO_HE;
1588c2b3d769SSriram R 	if (rd_flags & NL80211_RRF_NO_320MHZ)
1589c2b3d769SSriram R 		channel_flags |= IEEE80211_CHAN_NO_320MHZ;
1590*6c5b9a32SJohannes Berg 	if (rd_flags & NL80211_RRF_NO_EHT)
1591*6c5b9a32SJohannes Berg 		channel_flags |= IEEE80211_CHAN_NO_EHT;
1592b2e1b302SLuis R. Rodriguez 	return channel_flags;
1593b2e1b302SLuis R. Rodriguez }
1594b2e1b302SLuis R. Rodriguez 
1595361c9c8bSJohannes Berg static const struct ieee80211_reg_rule *
freq_reg_info_regd(u32 center_freq,const struct ieee80211_regdomain * regd,u32 bw)159649172874SMichal Sojka freq_reg_info_regd(u32 center_freq,
15974edd5698SMatthias May 		   const struct ieee80211_regdomain *regd, u32 bw)
15988318d78aSJohannes Berg {
15998318d78aSJohannes Berg 	int i;
16000c7dc45dSLuis R. Rodriguez 	bool band_rule_found = false;
1601038659e7SLuis R. Rodriguez 	bool bw_fits = false;
1602038659e7SLuis R. Rodriguez 
16033e0c3ff3SLuis R. Rodriguez 	if (!regd)
1604361c9c8bSJohannes Berg 		return ERR_PTR(-EINVAL);
1605b2e1b302SLuis R. Rodriguez 
16063e0c3ff3SLuis R. Rodriguez 	for (i = 0; i < regd->n_reg_rules; i++) {
1607b2e1b302SLuis R. Rodriguez 		const struct ieee80211_reg_rule *rr;
1608b2e1b302SLuis R. Rodriguez 		const struct ieee80211_freq_range *fr = NULL;
1609b2e1b302SLuis R. Rodriguez 
16103e0c3ff3SLuis R. Rodriguez 		rr = &regd->reg_rules[i];
1611b2e1b302SLuis R. Rodriguez 		fr = &rr->freq_range;
16120c7dc45dSLuis R. Rodriguez 
1613fb1fc7adSLuis R. Rodriguez 		/*
1614fb1fc7adSLuis R. Rodriguez 		 * We only need to know if one frequency rule was
1615cc5a639bSRandy Dunlap 		 * in center_freq's band, that's enough, so let's
1616fb1fc7adSLuis R. Rodriguez 		 * not overwrite it once found
1617fb1fc7adSLuis R. Rodriguez 		 */
16180c7dc45dSLuis R. Rodriguez 		if (!band_rule_found)
16190c7dc45dSLuis R. Rodriguez 			band_rule_found = freq_in_rule_band(fr, center_freq);
16200c7dc45dSLuis R. Rodriguez 
16214787cfa0SRafał Miłecki 		bw_fits = cfg80211_does_bw_fit_range(fr, center_freq, bw);
16220c7dc45dSLuis R. Rodriguez 
1623361c9c8bSJohannes Berg 		if (band_rule_found && bw_fits)
1624361c9c8bSJohannes Berg 			return rr;
16258318d78aSJohannes Berg 	}
16268318d78aSJohannes Berg 
16270c7dc45dSLuis R. Rodriguez 	if (!band_rule_found)
1628361c9c8bSJohannes Berg 		return ERR_PTR(-ERANGE);
16290c7dc45dSLuis R. Rodriguez 
1630361c9c8bSJohannes Berg 	return ERR_PTR(-EINVAL);
1631b2e1b302SLuis R. Rodriguez }
1632b2e1b302SLuis R. Rodriguez 
16338de1c63bSJohannes Berg static const struct ieee80211_reg_rule *
__freq_reg_info(struct wiphy * wiphy,u32 center_freq,u32 min_bw)16348de1c63bSJohannes Berg __freq_reg_info(struct wiphy *wiphy, u32 center_freq, u32 min_bw)
16354edd5698SMatthias May {
16364edd5698SMatthias May 	const struct ieee80211_regdomain *regd = reg_get_regdomain(wiphy);
1637c7ed0e68SColin Ian King 	static const u32 bws[] = {0, 1, 2, 4, 5, 8, 10, 16, 20};
16389e6d5126SLuca Coelho 	const struct ieee80211_reg_rule *reg_rule = ERR_PTR(-ERANGE);
163968dbad8cSThomas Pedersen 	int i = ARRAY_SIZE(bws) - 1;
16404edd5698SMatthias May 	u32 bw;
16414edd5698SMatthias May 
164268dbad8cSThomas Pedersen 	for (bw = MHZ_TO_KHZ(bws[i]); bw >= min_bw; bw = MHZ_TO_KHZ(bws[i--])) {
164349172874SMichal Sojka 		reg_rule = freq_reg_info_regd(center_freq, regd, bw);
16444edd5698SMatthias May 		if (!IS_ERR(reg_rule))
16454edd5698SMatthias May 			return reg_rule;
16464edd5698SMatthias May 	}
16474edd5698SMatthias May 
16484edd5698SMatthias May 	return reg_rule;
16494edd5698SMatthias May }
16504edd5698SMatthias May 
freq_reg_info(struct wiphy * wiphy,u32 center_freq)1651361c9c8bSJohannes Berg const struct ieee80211_reg_rule *freq_reg_info(struct wiphy *wiphy,
1652361c9c8bSJohannes Berg 					       u32 center_freq)
16531fa25e41SLuis R. Rodriguez {
165468dbad8cSThomas Pedersen 	u32 min_bw = center_freq < MHZ_TO_KHZ(1000) ? 1 : 20;
165568dbad8cSThomas Pedersen 
165668dbad8cSThomas Pedersen 	return __freq_reg_info(wiphy, center_freq, MHZ_TO_KHZ(min_bw));
16571fa25e41SLuis R. Rodriguez }
16584f366c5dSJohn W. Linville EXPORT_SYMBOL(freq_reg_info);
1659b2e1b302SLuis R. Rodriguez 
reg_initiator_name(enum nl80211_reg_initiator initiator)1660034c6d6eSLuis R. Rodriguez const char *reg_initiator_name(enum nl80211_reg_initiator initiator)
1661926a0a09SLuis R. Rodriguez {
1662926a0a09SLuis R. Rodriguez 	switch (initiator) {
1663926a0a09SLuis R. Rodriguez 	case NL80211_REGDOM_SET_BY_CORE:
1664034c6d6eSLuis R. Rodriguez 		return "core";
1665926a0a09SLuis R. Rodriguez 	case NL80211_REGDOM_SET_BY_USER:
1666034c6d6eSLuis R. Rodriguez 		return "user";
1667926a0a09SLuis R. Rodriguez 	case NL80211_REGDOM_SET_BY_DRIVER:
1668034c6d6eSLuis R. Rodriguez 		return "driver";
1669926a0a09SLuis R. Rodriguez 	case NL80211_REGDOM_SET_BY_COUNTRY_IE:
16708db0c433SToke Høiland-Jørgensen 		return "country element";
1671926a0a09SLuis R. Rodriguez 	default:
1672926a0a09SLuis R. Rodriguez 		WARN_ON(1);
1673034c6d6eSLuis R. Rodriguez 		return "bug";
1674926a0a09SLuis R. Rodriguez 	}
1675926a0a09SLuis R. Rodriguez }
1676034c6d6eSLuis R. Rodriguez EXPORT_SYMBOL(reg_initiator_name);
1677e702d3cfSLuis R. Rodriguez 
reg_rule_to_chan_bw_flags(const struct ieee80211_regdomain * regd,const struct ieee80211_reg_rule * reg_rule,const struct ieee80211_channel * chan)16781aeb135fSMichal Sojka static uint32_t reg_rule_to_chan_bw_flags(const struct ieee80211_regdomain *regd,
16791aeb135fSMichal Sojka 					  const struct ieee80211_reg_rule *reg_rule,
16801aeb135fSMichal Sojka 					  const struct ieee80211_channel *chan)
16811aeb135fSMichal Sojka {
16821aeb135fSMichal Sojka 	const struct ieee80211_freq_range *freq_range = NULL;
1683934f4c7dSThomas Pedersen 	u32 max_bandwidth_khz, center_freq_khz, bw_flags = 0;
168468dbad8cSThomas Pedersen 	bool is_s1g = chan->band == NL80211_BAND_S1GHZ;
16851aeb135fSMichal Sojka 
16861aeb135fSMichal Sojka 	freq_range = &reg_rule->freq_range;
16871aeb135fSMichal Sojka 
16881aeb135fSMichal Sojka 	max_bandwidth_khz = freq_range->max_bandwidth_khz;
1689934f4c7dSThomas Pedersen 	center_freq_khz = ieee80211_channel_to_khz(chan);
16901aeb135fSMichal Sojka 	/* Check if auto calculation requested */
16911aeb135fSMichal Sojka 	if (reg_rule->flags & NL80211_RRF_AUTO_BW)
16921aeb135fSMichal Sojka 		max_bandwidth_khz = reg_get_max_bandwidth(regd, reg_rule);
16931aeb135fSMichal Sojka 
16941aeb135fSMichal Sojka 	/* If we get a reg_rule we can assume that at least 5Mhz fit */
16954787cfa0SRafał Miłecki 	if (!cfg80211_does_bw_fit_range(freq_range,
1696934f4c7dSThomas Pedersen 					center_freq_khz,
16971aeb135fSMichal Sojka 					MHZ_TO_KHZ(10)))
16981aeb135fSMichal Sojka 		bw_flags |= IEEE80211_CHAN_NO_10MHZ;
16994787cfa0SRafał Miłecki 	if (!cfg80211_does_bw_fit_range(freq_range,
1700934f4c7dSThomas Pedersen 					center_freq_khz,
17011aeb135fSMichal Sojka 					MHZ_TO_KHZ(20)))
17021aeb135fSMichal Sojka 		bw_flags |= IEEE80211_CHAN_NO_20MHZ;
17031aeb135fSMichal Sojka 
170468dbad8cSThomas Pedersen 	if (is_s1g) {
170568dbad8cSThomas Pedersen 		/* S1G is strict about non overlapping channels. We can
170668dbad8cSThomas Pedersen 		 * calculate which bandwidth is allowed per channel by finding
170768dbad8cSThomas Pedersen 		 * the largest bandwidth which cleanly divides the freq_range.
170868dbad8cSThomas Pedersen 		 */
170968dbad8cSThomas Pedersen 		int edge_offset;
171068dbad8cSThomas Pedersen 		int ch_bw = max_bandwidth_khz;
171168dbad8cSThomas Pedersen 
171268dbad8cSThomas Pedersen 		while (ch_bw) {
171368dbad8cSThomas Pedersen 			edge_offset = (center_freq_khz - ch_bw / 2) -
171468dbad8cSThomas Pedersen 				      freq_range->start_freq_khz;
171568dbad8cSThomas Pedersen 			if (edge_offset % ch_bw == 0) {
171668dbad8cSThomas Pedersen 				switch (KHZ_TO_MHZ(ch_bw)) {
171768dbad8cSThomas Pedersen 				case 1:
171868dbad8cSThomas Pedersen 					bw_flags |= IEEE80211_CHAN_1MHZ;
171968dbad8cSThomas Pedersen 					break;
172068dbad8cSThomas Pedersen 				case 2:
172168dbad8cSThomas Pedersen 					bw_flags |= IEEE80211_CHAN_2MHZ;
172268dbad8cSThomas Pedersen 					break;
172368dbad8cSThomas Pedersen 				case 4:
172468dbad8cSThomas Pedersen 					bw_flags |= IEEE80211_CHAN_4MHZ;
172568dbad8cSThomas Pedersen 					break;
172668dbad8cSThomas Pedersen 				case 8:
172768dbad8cSThomas Pedersen 					bw_flags |= IEEE80211_CHAN_8MHZ;
172868dbad8cSThomas Pedersen 					break;
172968dbad8cSThomas Pedersen 				case 16:
173068dbad8cSThomas Pedersen 					bw_flags |= IEEE80211_CHAN_16MHZ;
173168dbad8cSThomas Pedersen 					break;
173268dbad8cSThomas Pedersen 				default:
173368dbad8cSThomas Pedersen 					/* If we got here, no bandwidths fit on
173468dbad8cSThomas Pedersen 					 * this frequency, ie. band edge.
173568dbad8cSThomas Pedersen 					 */
173668dbad8cSThomas Pedersen 					bw_flags |= IEEE80211_CHAN_DISABLED;
173768dbad8cSThomas Pedersen 					break;
173868dbad8cSThomas Pedersen 				}
173968dbad8cSThomas Pedersen 				break;
174068dbad8cSThomas Pedersen 			}
174168dbad8cSThomas Pedersen 			ch_bw /= 2;
174268dbad8cSThomas Pedersen 		}
174368dbad8cSThomas Pedersen 	} else {
17441aeb135fSMichal Sojka 		if (max_bandwidth_khz < MHZ_TO_KHZ(10))
17451aeb135fSMichal Sojka 			bw_flags |= IEEE80211_CHAN_NO_10MHZ;
17461aeb135fSMichal Sojka 		if (max_bandwidth_khz < MHZ_TO_KHZ(20))
17471aeb135fSMichal Sojka 			bw_flags |= IEEE80211_CHAN_NO_20MHZ;
17481aeb135fSMichal Sojka 		if (max_bandwidth_khz < MHZ_TO_KHZ(40))
17491aeb135fSMichal Sojka 			bw_flags |= IEEE80211_CHAN_NO_HT40;
17501aeb135fSMichal Sojka 		if (max_bandwidth_khz < MHZ_TO_KHZ(80))
17511aeb135fSMichal Sojka 			bw_flags |= IEEE80211_CHAN_NO_80MHZ;
17521aeb135fSMichal Sojka 		if (max_bandwidth_khz < MHZ_TO_KHZ(160))
17531aeb135fSMichal Sojka 			bw_flags |= IEEE80211_CHAN_NO_160MHZ;
1754c2b3d769SSriram R 		if (max_bandwidth_khz < MHZ_TO_KHZ(320))
1755c2b3d769SSriram R 			bw_flags |= IEEE80211_CHAN_NO_320MHZ;
175668dbad8cSThomas Pedersen 	}
17571aeb135fSMichal Sojka 	return bw_flags;
17581aeb135fSMichal Sojka }
17591aeb135fSMichal Sojka 
handle_channel_single_rule(struct wiphy * wiphy,enum nl80211_reg_initiator initiator,struct ieee80211_channel * chan,u32 flags,struct regulatory_request * lr,struct wiphy * request_wiphy,const struct ieee80211_reg_rule * reg_rule)17607c9ff7e2SMarkus Theil static void handle_channel_single_rule(struct wiphy *wiphy,
17617ca43d03SLuis R. Rodriguez 				       enum nl80211_reg_initiator initiator,
17627c9ff7e2SMarkus Theil 				       struct ieee80211_channel *chan,
17637c9ff7e2SMarkus Theil 				       u32 flags,
17647c9ff7e2SMarkus Theil 				       struct regulatory_request *lr,
17657c9ff7e2SMarkus Theil 				       struct wiphy *request_wiphy,
17667c9ff7e2SMarkus Theil 				       const struct ieee80211_reg_rule *reg_rule)
1767b2e1b302SLuis R. Rodriguez {
17687c9ff7e2SMarkus Theil 	u32 bw_flags = 0;
1769b2e1b302SLuis R. Rodriguez 	const struct ieee80211_power_rule *power_rule = NULL;
177097524820SJanusz Dziedzic 	const struct ieee80211_regdomain *regd;
1771a92a3ce7SLuis R. Rodriguez 
1772b0dfd2eaSJanusz Dziedzic 	regd = reg_get_regdomain(wiphy);
1773e702d3cfSLuis R. Rodriguez 
1774b2e1b302SLuis R. Rodriguez 	power_rule = &reg_rule->power_rule;
17751aeb135fSMichal Sojka 	bw_flags = reg_rule_to_chan_bw_flags(regd, reg_rule, chan);
1776b2e1b302SLuis R. Rodriguez 
1777c492db37SJohannes Berg 	if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER &&
1778806a9e39SLuis R. Rodriguez 	    request_wiphy && request_wiphy == wiphy &&
1779a2f73b6cSLuis R. Rodriguez 	    request_wiphy->regulatory_flags & REGULATORY_STRICT_REG) {
1780fb1fc7adSLuis R. Rodriguez 		/*
178125985edcSLucas De Marchi 		 * This guarantees the driver's requested regulatory domain
1782f976376dSLuis R. Rodriguez 		 * will always be used as a base for further regulatory
1783fb1fc7adSLuis R. Rodriguez 		 * settings
1784fb1fc7adSLuis R. Rodriguez 		 */
1785f976376dSLuis R. Rodriguez 		chan->flags = chan->orig_flags =
1786038659e7SLuis R. Rodriguez 			map_regdom_flags(reg_rule->flags) | bw_flags;
1787f976376dSLuis R. Rodriguez 		chan->max_antenna_gain = chan->orig_mag =
1788f976376dSLuis R. Rodriguez 			(int) MBI_TO_DBI(power_rule->max_antenna_gain);
1789279f0f55SFelix Fietkau 		chan->max_reg_power = chan->max_power = chan->orig_mpwr =
1790f976376dSLuis R. Rodriguez 			(int) MBM_TO_DBM(power_rule->max_eirp);
17914f267c11SJanusz Dziedzic 
17924f267c11SJanusz Dziedzic 		if (chan->flags & IEEE80211_CHAN_RADAR) {
17934f267c11SJanusz Dziedzic 			chan->dfs_cac_ms = IEEE80211_DFS_MIN_CAC_TIME_MS;
17944f267c11SJanusz Dziedzic 			if (reg_rule->dfs_cac_ms)
17954f267c11SJanusz Dziedzic 				chan->dfs_cac_ms = reg_rule->dfs_cac_ms;
17964f267c11SJanusz Dziedzic 		}
17974f267c11SJanusz Dziedzic 
1798f976376dSLuis R. Rodriguez 		return;
1799f976376dSLuis R. Rodriguez 	}
1800f976376dSLuis R. Rodriguez 
180104f39047SSimon Wunderlich 	chan->dfs_state = NL80211_DFS_USABLE;
180204f39047SSimon Wunderlich 	chan->dfs_state_entered = jiffies;
180304f39047SSimon Wunderlich 
1804aa3d7eefSRajkumar Manoharan 	chan->beacon_found = false;
1805038659e7SLuis R. Rodriguez 	chan->flags = flags | bw_flags | map_regdom_flags(reg_rule->flags);
18061a919318SJohannes Berg 	chan->max_antenna_gain =
18071a919318SJohannes Berg 		min_t(int, chan->orig_mag,
18081a919318SJohannes Berg 		      MBI_TO_DBI(power_rule->max_antenna_gain));
1809eccc068eSHong Wu 	chan->max_reg_power = (int) MBM_TO_DBM(power_rule->max_eirp);
1810089027e5SJanusz Dziedzic 
1811089027e5SJanusz Dziedzic 	if (chan->flags & IEEE80211_CHAN_RADAR) {
1812089027e5SJanusz Dziedzic 		if (reg_rule->dfs_cac_ms)
1813089027e5SJanusz Dziedzic 			chan->dfs_cac_ms = reg_rule->dfs_cac_ms;
1814089027e5SJanusz Dziedzic 		else
1815089027e5SJanusz Dziedzic 			chan->dfs_cac_ms = IEEE80211_DFS_MIN_CAC_TIME_MS;
1816089027e5SJanusz Dziedzic 	}
1817089027e5SJanusz Dziedzic 
18185e31fc08SStanislaw Gruszka 	if (chan->orig_mpwr) {
18195e31fc08SStanislaw Gruszka 		/*
1820a09a85a0SLuis R. Rodriguez 		 * Devices that use REGULATORY_COUNTRY_IE_FOLLOW_POWER
1821a09a85a0SLuis R. Rodriguez 		 * will always follow the passed country IE power settings.
18225e31fc08SStanislaw Gruszka 		 */
18235e31fc08SStanislaw Gruszka 		if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE &&
1824a09a85a0SLuis R. Rodriguez 		    wiphy->regulatory_flags & REGULATORY_COUNTRY_IE_FOLLOW_POWER)
18255e31fc08SStanislaw Gruszka 			chan->max_power = chan->max_reg_power;
18265e31fc08SStanislaw Gruszka 		else
18275e31fc08SStanislaw Gruszka 			chan->max_power = min(chan->orig_mpwr,
18285e31fc08SStanislaw Gruszka 					      chan->max_reg_power);
18295e31fc08SStanislaw Gruszka 	} else
18305e31fc08SStanislaw Gruszka 		chan->max_power = chan->max_reg_power;
18318318d78aSJohannes Berg }
18328318d78aSJohannes Berg 
handle_channel_adjacent_rules(struct wiphy * wiphy,enum nl80211_reg_initiator initiator,struct ieee80211_channel * chan,u32 flags,struct regulatory_request * lr,struct wiphy * request_wiphy,const struct ieee80211_reg_rule * rrule1,const struct ieee80211_reg_rule * rrule2,struct ieee80211_freq_range * comb_range)183312adee3cSMarkus Theil static void handle_channel_adjacent_rules(struct wiphy *wiphy,
183412adee3cSMarkus Theil 					  enum nl80211_reg_initiator initiator,
183512adee3cSMarkus Theil 					  struct ieee80211_channel *chan,
183612adee3cSMarkus Theil 					  u32 flags,
183712adee3cSMarkus Theil 					  struct regulatory_request *lr,
183812adee3cSMarkus Theil 					  struct wiphy *request_wiphy,
183912adee3cSMarkus Theil 					  const struct ieee80211_reg_rule *rrule1,
184012adee3cSMarkus Theil 					  const struct ieee80211_reg_rule *rrule2,
184112adee3cSMarkus Theil 					  struct ieee80211_freq_range *comb_range)
184212adee3cSMarkus Theil {
184312adee3cSMarkus Theil 	u32 bw_flags1 = 0;
184412adee3cSMarkus Theil 	u32 bw_flags2 = 0;
184512adee3cSMarkus Theil 	const struct ieee80211_power_rule *power_rule1 = NULL;
184612adee3cSMarkus Theil 	const struct ieee80211_power_rule *power_rule2 = NULL;
184712adee3cSMarkus Theil 	const struct ieee80211_regdomain *regd;
184812adee3cSMarkus Theil 
184912adee3cSMarkus Theil 	regd = reg_get_regdomain(wiphy);
185012adee3cSMarkus Theil 
185112adee3cSMarkus Theil 	power_rule1 = &rrule1->power_rule;
185212adee3cSMarkus Theil 	power_rule2 = &rrule2->power_rule;
185312adee3cSMarkus Theil 	bw_flags1 = reg_rule_to_chan_bw_flags(regd, rrule1, chan);
185412adee3cSMarkus Theil 	bw_flags2 = reg_rule_to_chan_bw_flags(regd, rrule2, chan);
185512adee3cSMarkus Theil 
185612adee3cSMarkus Theil 	if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER &&
185712adee3cSMarkus Theil 	    request_wiphy && request_wiphy == wiphy &&
185812adee3cSMarkus Theil 	    request_wiphy->regulatory_flags & REGULATORY_STRICT_REG) {
185912adee3cSMarkus Theil 		/* This guarantees the driver's requested regulatory domain
186012adee3cSMarkus Theil 		 * will always be used as a base for further regulatory
186112adee3cSMarkus Theil 		 * settings
186212adee3cSMarkus Theil 		 */
186312adee3cSMarkus Theil 		chan->flags =
186412adee3cSMarkus Theil 			map_regdom_flags(rrule1->flags) |
186512adee3cSMarkus Theil 			map_regdom_flags(rrule2->flags) |
186612adee3cSMarkus Theil 			bw_flags1 |
186712adee3cSMarkus Theil 			bw_flags2;
186812adee3cSMarkus Theil 		chan->orig_flags = chan->flags;
186912adee3cSMarkus Theil 		chan->max_antenna_gain =
187012adee3cSMarkus Theil 			min_t(int, MBI_TO_DBI(power_rule1->max_antenna_gain),
187112adee3cSMarkus Theil 			      MBI_TO_DBI(power_rule2->max_antenna_gain));
187212adee3cSMarkus Theil 		chan->orig_mag = chan->max_antenna_gain;
187312adee3cSMarkus Theil 		chan->max_reg_power =
187412adee3cSMarkus Theil 			min_t(int, MBM_TO_DBM(power_rule1->max_eirp),
187512adee3cSMarkus Theil 			      MBM_TO_DBM(power_rule2->max_eirp));
187612adee3cSMarkus Theil 		chan->max_power = chan->max_reg_power;
187712adee3cSMarkus Theil 		chan->orig_mpwr = chan->max_reg_power;
187812adee3cSMarkus Theil 
187912adee3cSMarkus Theil 		if (chan->flags & IEEE80211_CHAN_RADAR) {
188012adee3cSMarkus Theil 			chan->dfs_cac_ms = IEEE80211_DFS_MIN_CAC_TIME_MS;
188112adee3cSMarkus Theil 			if (rrule1->dfs_cac_ms || rrule2->dfs_cac_ms)
188212adee3cSMarkus Theil 				chan->dfs_cac_ms = max_t(unsigned int,
188312adee3cSMarkus Theil 							 rrule1->dfs_cac_ms,
188412adee3cSMarkus Theil 							 rrule2->dfs_cac_ms);
188512adee3cSMarkus Theil 		}
188612adee3cSMarkus Theil 
188712adee3cSMarkus Theil 		return;
188812adee3cSMarkus Theil 	}
188912adee3cSMarkus Theil 
189012adee3cSMarkus Theil 	chan->dfs_state = NL80211_DFS_USABLE;
189112adee3cSMarkus Theil 	chan->dfs_state_entered = jiffies;
189212adee3cSMarkus Theil 
189312adee3cSMarkus Theil 	chan->beacon_found = false;
189412adee3cSMarkus Theil 	chan->flags = flags | bw_flags1 | bw_flags2 |
189512adee3cSMarkus Theil 		      map_regdom_flags(rrule1->flags) |
189612adee3cSMarkus Theil 		      map_regdom_flags(rrule2->flags);
189712adee3cSMarkus Theil 
189812adee3cSMarkus Theil 	/* reg_rule_to_chan_bw_flags may forbids 10 and forbids 20 MHz
189912adee3cSMarkus Theil 	 * (otherwise no adj. rule case), recheck therefore
190012adee3cSMarkus Theil 	 */
190112adee3cSMarkus Theil 	if (cfg80211_does_bw_fit_range(comb_range,
190212adee3cSMarkus Theil 				       ieee80211_channel_to_khz(chan),
190312adee3cSMarkus Theil 				       MHZ_TO_KHZ(10)))
190412adee3cSMarkus Theil 		chan->flags &= ~IEEE80211_CHAN_NO_10MHZ;
190512adee3cSMarkus Theil 	if (cfg80211_does_bw_fit_range(comb_range,
190612adee3cSMarkus Theil 				       ieee80211_channel_to_khz(chan),
190712adee3cSMarkus Theil 				       MHZ_TO_KHZ(20)))
190812adee3cSMarkus Theil 		chan->flags &= ~IEEE80211_CHAN_NO_20MHZ;
190912adee3cSMarkus Theil 
191012adee3cSMarkus Theil 	chan->max_antenna_gain =
191112adee3cSMarkus Theil 		min_t(int, chan->orig_mag,
191212adee3cSMarkus Theil 		      min_t(int,
191312adee3cSMarkus Theil 			    MBI_TO_DBI(power_rule1->max_antenna_gain),
191412adee3cSMarkus Theil 			    MBI_TO_DBI(power_rule2->max_antenna_gain)));
191512adee3cSMarkus Theil 	chan->max_reg_power = min_t(int,
191612adee3cSMarkus Theil 				    MBM_TO_DBM(power_rule1->max_eirp),
191712adee3cSMarkus Theil 				    MBM_TO_DBM(power_rule2->max_eirp));
191812adee3cSMarkus Theil 
191912adee3cSMarkus Theil 	if (chan->flags & IEEE80211_CHAN_RADAR) {
192012adee3cSMarkus Theil 		if (rrule1->dfs_cac_ms || rrule2->dfs_cac_ms)
192112adee3cSMarkus Theil 			chan->dfs_cac_ms = max_t(unsigned int,
192212adee3cSMarkus Theil 						 rrule1->dfs_cac_ms,
192312adee3cSMarkus Theil 						 rrule2->dfs_cac_ms);
192412adee3cSMarkus Theil 		else
192512adee3cSMarkus Theil 			chan->dfs_cac_ms = IEEE80211_DFS_MIN_CAC_TIME_MS;
192612adee3cSMarkus Theil 	}
192712adee3cSMarkus Theil 
192812adee3cSMarkus Theil 	if (chan->orig_mpwr) {
192912adee3cSMarkus Theil 		/* Devices that use REGULATORY_COUNTRY_IE_FOLLOW_POWER
193012adee3cSMarkus Theil 		 * will always follow the passed country IE power settings.
193112adee3cSMarkus Theil 		 */
193212adee3cSMarkus Theil 		if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE &&
193312adee3cSMarkus Theil 		    wiphy->regulatory_flags & REGULATORY_COUNTRY_IE_FOLLOW_POWER)
193412adee3cSMarkus Theil 			chan->max_power = chan->max_reg_power;
193512adee3cSMarkus Theil 		else
193612adee3cSMarkus Theil 			chan->max_power = min(chan->orig_mpwr,
193712adee3cSMarkus Theil 					      chan->max_reg_power);
193812adee3cSMarkus Theil 	} else {
193912adee3cSMarkus Theil 		chan->max_power = chan->max_reg_power;
194012adee3cSMarkus Theil 	}
194112adee3cSMarkus Theil }
194212adee3cSMarkus Theil 
19437c9ff7e2SMarkus Theil /* Note that right now we assume the desired channel bandwidth
19447c9ff7e2SMarkus Theil  * is always 20 MHz for each individual channel (HT40 uses 20 MHz
19457c9ff7e2SMarkus Theil  * per channel, the primary and the extension channel).
19467c9ff7e2SMarkus Theil  */
handle_channel(struct wiphy * wiphy,enum nl80211_reg_initiator initiator,struct ieee80211_channel * chan)19477c9ff7e2SMarkus Theil static void handle_channel(struct wiphy *wiphy,
19487c9ff7e2SMarkus Theil 			   enum nl80211_reg_initiator initiator,
19497c9ff7e2SMarkus Theil 			   struct ieee80211_channel *chan)
19507c9ff7e2SMarkus Theil {
195112adee3cSMarkus Theil 	const u32 orig_chan_freq = ieee80211_channel_to_khz(chan);
19527c9ff7e2SMarkus Theil 	struct regulatory_request *lr = get_last_request();
195312adee3cSMarkus Theil 	struct wiphy *request_wiphy = wiphy_idx_to_wiphy(lr->wiphy_idx);
195412adee3cSMarkus Theil 	const struct ieee80211_reg_rule *rrule = NULL;
195512adee3cSMarkus Theil 	const struct ieee80211_reg_rule *rrule1 = NULL;
195612adee3cSMarkus Theil 	const struct ieee80211_reg_rule *rrule2 = NULL;
19577c9ff7e2SMarkus Theil 
195812adee3cSMarkus Theil 	u32 flags = chan->orig_flags;
19597c9ff7e2SMarkus Theil 
196012adee3cSMarkus Theil 	rrule = freq_reg_info(wiphy, orig_chan_freq);
196112adee3cSMarkus Theil 	if (IS_ERR(rrule)) {
196212adee3cSMarkus Theil 		/* check for adjacent match, therefore get rules for
196312adee3cSMarkus Theil 		 * chan - 20 MHz and chan + 20 MHz and test
196412adee3cSMarkus Theil 		 * if reg rules are adjacent
196512adee3cSMarkus Theil 		 */
196612adee3cSMarkus Theil 		rrule1 = freq_reg_info(wiphy,
196712adee3cSMarkus Theil 				       orig_chan_freq - MHZ_TO_KHZ(20));
196812adee3cSMarkus Theil 		rrule2 = freq_reg_info(wiphy,
196912adee3cSMarkus Theil 				       orig_chan_freq + MHZ_TO_KHZ(20));
197012adee3cSMarkus Theil 		if (!IS_ERR(rrule1) && !IS_ERR(rrule2)) {
197112adee3cSMarkus Theil 			struct ieee80211_freq_range comb_range;
19727c9ff7e2SMarkus Theil 
197312adee3cSMarkus Theil 			if (rrule1->freq_range.end_freq_khz !=
197412adee3cSMarkus Theil 			    rrule2->freq_range.start_freq_khz)
197512adee3cSMarkus Theil 				goto disable_chan;
197612adee3cSMarkus Theil 
197712adee3cSMarkus Theil 			comb_range.start_freq_khz =
197812adee3cSMarkus Theil 				rrule1->freq_range.start_freq_khz;
197912adee3cSMarkus Theil 			comb_range.end_freq_khz =
198012adee3cSMarkus Theil 				rrule2->freq_range.end_freq_khz;
198112adee3cSMarkus Theil 			comb_range.max_bandwidth_khz =
198212adee3cSMarkus Theil 				min_t(u32,
198312adee3cSMarkus Theil 				      rrule1->freq_range.max_bandwidth_khz,
198412adee3cSMarkus Theil 				      rrule2->freq_range.max_bandwidth_khz);
198512adee3cSMarkus Theil 
198612adee3cSMarkus Theil 			if (!cfg80211_does_bw_fit_range(&comb_range,
198712adee3cSMarkus Theil 							orig_chan_freq,
198812adee3cSMarkus Theil 							MHZ_TO_KHZ(20)))
198912adee3cSMarkus Theil 				goto disable_chan;
199012adee3cSMarkus Theil 
199112adee3cSMarkus Theil 			handle_channel_adjacent_rules(wiphy, initiator, chan,
199212adee3cSMarkus Theil 						      flags, lr, request_wiphy,
199312adee3cSMarkus Theil 						      rrule1, rrule2,
199412adee3cSMarkus Theil 						      &comb_range);
199512adee3cSMarkus Theil 			return;
199612adee3cSMarkus Theil 		}
199712adee3cSMarkus Theil 
199812adee3cSMarkus Theil disable_chan:
19997c9ff7e2SMarkus Theil 		/* We will disable all channels that do not match our
20007c9ff7e2SMarkus Theil 		 * received regulatory rule unless the hint is coming
20017c9ff7e2SMarkus Theil 		 * from a Country IE and the Country IE had no information
20027c9ff7e2SMarkus Theil 		 * about a band. The IEEE 802.11 spec allows for an AP
20037c9ff7e2SMarkus Theil 		 * to send only a subset of the regulatory rules allowed,
20047c9ff7e2SMarkus Theil 		 * so an AP in the US that only supports 2.4 GHz may only send
20057c9ff7e2SMarkus Theil 		 * a country IE with information for the 2.4 GHz band
20067c9ff7e2SMarkus Theil 		 * while 5 GHz is still supported.
20077c9ff7e2SMarkus Theil 		 */
20087c9ff7e2SMarkus Theil 		if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE &&
200912adee3cSMarkus Theil 		    PTR_ERR(rrule) == -ERANGE)
20107c9ff7e2SMarkus Theil 			return;
20117c9ff7e2SMarkus Theil 
20127c9ff7e2SMarkus Theil 		if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER &&
20137c9ff7e2SMarkus Theil 		    request_wiphy && request_wiphy == wiphy &&
20147c9ff7e2SMarkus Theil 		    request_wiphy->regulatory_flags & REGULATORY_STRICT_REG) {
20157c9ff7e2SMarkus Theil 			pr_debug("Disabling freq %d.%03d MHz for good\n",
20167c9ff7e2SMarkus Theil 				 chan->center_freq, chan->freq_offset);
20177c9ff7e2SMarkus Theil 			chan->orig_flags |= IEEE80211_CHAN_DISABLED;
20187c9ff7e2SMarkus Theil 			chan->flags = chan->orig_flags;
20197c9ff7e2SMarkus Theil 		} else {
20207c9ff7e2SMarkus Theil 			pr_debug("Disabling freq %d.%03d MHz\n",
20217c9ff7e2SMarkus Theil 				 chan->center_freq, chan->freq_offset);
20227c9ff7e2SMarkus Theil 			chan->flags |= IEEE80211_CHAN_DISABLED;
20237c9ff7e2SMarkus Theil 		}
20247c9ff7e2SMarkus Theil 		return;
20257c9ff7e2SMarkus Theil 	}
20267c9ff7e2SMarkus Theil 
20277c9ff7e2SMarkus Theil 	handle_channel_single_rule(wiphy, initiator, chan, flags, lr,
202812adee3cSMarkus Theil 				   request_wiphy, rrule);
20297c9ff7e2SMarkus Theil }
20307c9ff7e2SMarkus Theil 
handle_band(struct wiphy * wiphy,enum nl80211_reg_initiator initiator,struct ieee80211_supported_band * sband)20317ca43d03SLuis R. Rodriguez static void handle_band(struct wiphy *wiphy,
2032fdc9d7b2SJohannes Berg 			enum nl80211_reg_initiator initiator,
2033fdc9d7b2SJohannes Berg 			struct ieee80211_supported_band *sband)
20348318d78aSJohannes Berg {
2035a92a3ce7SLuis R. Rodriguez 	unsigned int i;
2036a92a3ce7SLuis R. Rodriguez 
2037fdc9d7b2SJohannes Berg 	if (!sband)
2038fdc9d7b2SJohannes Berg 		return;
20398318d78aSJohannes Berg 
20408318d78aSJohannes Berg 	for (i = 0; i < sband->n_channels; i++)
2041fdc9d7b2SJohannes Berg 		handle_channel(wiphy, initiator, &sband->channels[i]);
20428318d78aSJohannes Berg }
20438318d78aSJohannes Berg 
reg_request_cell_base(struct regulatory_request * request)204457b5ce07SLuis R. Rodriguez static bool reg_request_cell_base(struct regulatory_request *request)
204557b5ce07SLuis R. Rodriguez {
204657b5ce07SLuis R. Rodriguez 	if (request->initiator != NL80211_REGDOM_SET_BY_USER)
204757b5ce07SLuis R. Rodriguez 		return false;
20481a919318SJohannes Berg 	return request->user_reg_hint_type == NL80211_USER_REG_HINT_CELL_BASE;
204957b5ce07SLuis R. Rodriguez }
205057b5ce07SLuis R. Rodriguez 
reg_last_request_cell_base(void)205157b5ce07SLuis R. Rodriguez bool reg_last_request_cell_base(void)
205257b5ce07SLuis R. Rodriguez {
205338fd2143SJohannes Berg 	return reg_request_cell_base(get_last_request());
205457b5ce07SLuis R. Rodriguez }
205557b5ce07SLuis R. Rodriguez 
205694fc661fSIlan Peer #ifdef CONFIG_CFG80211_REG_CELLULAR_HINTS
205757b5ce07SLuis R. Rodriguez /* Core specific check */
20582f92212bSJohannes Berg static enum reg_request_treatment
reg_ignore_cell_hint(struct regulatory_request * pending_request)20592f92212bSJohannes Berg reg_ignore_cell_hint(struct regulatory_request *pending_request)
206057b5ce07SLuis R. Rodriguez {
2061c492db37SJohannes Berg 	struct regulatory_request *lr = get_last_request();
206257b5ce07SLuis R. Rodriguez 
206357b5ce07SLuis R. Rodriguez 	if (!reg_num_devs_support_basehint)
20642f92212bSJohannes Berg 		return REG_REQ_IGNORE;
206557b5ce07SLuis R. Rodriguez 
2066c492db37SJohannes Berg 	if (reg_request_cell_base(lr) &&
20671a919318SJohannes Berg 	    !regdom_changes(pending_request->alpha2))
20682f92212bSJohannes Berg 		return REG_REQ_ALREADY_SET;
20691a919318SJohannes Berg 
20702f92212bSJohannes Berg 	return REG_REQ_OK;
207157b5ce07SLuis R. Rodriguez }
207257b5ce07SLuis R. Rodriguez 
207357b5ce07SLuis R. Rodriguez /* Device specific check */
reg_dev_ignore_cell_hint(struct wiphy * wiphy)207457b5ce07SLuis R. Rodriguez static bool reg_dev_ignore_cell_hint(struct wiphy *wiphy)
207557b5ce07SLuis R. Rodriguez {
20761a919318SJohannes Berg 	return !(wiphy->features & NL80211_FEATURE_CELL_BASE_REG_HINTS);
207757b5ce07SLuis R. Rodriguez }
207857b5ce07SLuis R. Rodriguez #else
2079a515de66SJohannes Berg static enum reg_request_treatment
reg_ignore_cell_hint(struct regulatory_request * pending_request)2080a515de66SJohannes Berg reg_ignore_cell_hint(struct regulatory_request *pending_request)
208157b5ce07SLuis R. Rodriguez {
20822f92212bSJohannes Berg 	return REG_REQ_IGNORE;
208357b5ce07SLuis R. Rodriguez }
20841a919318SJohannes Berg 
reg_dev_ignore_cell_hint(struct wiphy * wiphy)20851a919318SJohannes Berg static bool reg_dev_ignore_cell_hint(struct wiphy *wiphy)
208657b5ce07SLuis R. Rodriguez {
208757b5ce07SLuis R. Rodriguez 	return true;
208857b5ce07SLuis R. Rodriguez }
208957b5ce07SLuis R. Rodriguez #endif
209057b5ce07SLuis R. Rodriguez 
wiphy_strict_alpha2_regd(struct wiphy * wiphy)2091fa1fb9cbSLuis R. Rodriguez static bool wiphy_strict_alpha2_regd(struct wiphy *wiphy)
2092fa1fb9cbSLuis R. Rodriguez {
2093a2f73b6cSLuis R. Rodriguez 	if (wiphy->regulatory_flags & REGULATORY_STRICT_REG &&
2094a2f73b6cSLuis R. Rodriguez 	    !(wiphy->regulatory_flags & REGULATORY_CUSTOM_REG))
2095fa1fb9cbSLuis R. Rodriguez 		return true;
2096fa1fb9cbSLuis R. Rodriguez 	return false;
2097fa1fb9cbSLuis R. Rodriguez }
209857b5ce07SLuis R. Rodriguez 
ignore_reg_update(struct wiphy * wiphy,enum nl80211_reg_initiator initiator)20997db90f4aSLuis R. Rodriguez static bool ignore_reg_update(struct wiphy *wiphy,
21007db90f4aSLuis R. Rodriguez 			      enum nl80211_reg_initiator initiator)
210114b9815aSLuis R. Rodriguez {
2102c492db37SJohannes Berg 	struct regulatory_request *lr = get_last_request();
2103c492db37SJohannes Berg 
2104b0d7aa59SJonathan Doron 	if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED)
2105b0d7aa59SJonathan Doron 		return true;
2106b0d7aa59SJonathan Doron 
2107c492db37SJohannes Berg 	if (!lr) {
2108c799ba6eSJohannes Berg 		pr_debug("Ignoring regulatory request set by %s since last_request is not set\n",
2109926a0a09SLuis R. Rodriguez 			 reg_initiator_name(initiator));
211014b9815aSLuis R. Rodriguez 		return true;
2111926a0a09SLuis R. Rodriguez 	}
2112926a0a09SLuis R. Rodriguez 
21137db90f4aSLuis R. Rodriguez 	if (initiator == NL80211_REGDOM_SET_BY_CORE &&
2114a2f73b6cSLuis R. Rodriguez 	    wiphy->regulatory_flags & REGULATORY_CUSTOM_REG) {
2115c799ba6eSJohannes Berg 		pr_debug("Ignoring regulatory request set by %s since the driver uses its own custom regulatory domain\n",
2116926a0a09SLuis R. Rodriguez 			 reg_initiator_name(initiator));
211714b9815aSLuis R. Rodriguez 		return true;
2118926a0a09SLuis R. Rodriguez 	}
2119926a0a09SLuis R. Rodriguez 
2120fb1fc7adSLuis R. Rodriguez 	/*
2121fb1fc7adSLuis R. Rodriguez 	 * wiphy->regd will be set once the device has its own
2122fb1fc7adSLuis R. Rodriguez 	 * desired regulatory domain set
2123fb1fc7adSLuis R. Rodriguez 	 */
2124fa1fb9cbSLuis R. Rodriguez 	if (wiphy_strict_alpha2_regd(wiphy) && !wiphy->regd &&
2125749b527bSLuis R. Rodriguez 	    initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE &&
2126c492db37SJohannes Berg 	    !is_world_regdom(lr->alpha2)) {
2127c799ba6eSJohannes Berg 		pr_debug("Ignoring regulatory request set by %s since the driver requires its own regulatory domain to be set first\n",
2128926a0a09SLuis R. Rodriguez 			 reg_initiator_name(initiator));
212914b9815aSLuis R. Rodriguez 		return true;
2130926a0a09SLuis R. Rodriguez 	}
2131926a0a09SLuis R. Rodriguez 
2132c492db37SJohannes Berg 	if (reg_request_cell_base(lr))
213357b5ce07SLuis R. Rodriguez 		return reg_dev_ignore_cell_hint(wiphy);
213457b5ce07SLuis R. Rodriguez 
213514b9815aSLuis R. Rodriguez 	return false;
213614b9815aSLuis R. Rodriguez }
213714b9815aSLuis R. Rodriguez 
reg_is_world_roaming(struct wiphy * wiphy)21383195e489SLuis R. Rodriguez static bool reg_is_world_roaming(struct wiphy *wiphy)
21393195e489SLuis R. Rodriguez {
21403195e489SLuis R. Rodriguez 	const struct ieee80211_regdomain *cr = get_cfg80211_regdom();
21413195e489SLuis R. Rodriguez 	const struct ieee80211_regdomain *wr = get_wiphy_regdom(wiphy);
21423195e489SLuis R. Rodriguez 	struct regulatory_request *lr = get_last_request();
21433195e489SLuis R. Rodriguez 
21443195e489SLuis R. Rodriguez 	if (is_world_regdom(cr->alpha2) || (wr && is_world_regdom(wr->alpha2)))
21453195e489SLuis R. Rodriguez 		return true;
21463195e489SLuis R. Rodriguez 
21473195e489SLuis R. Rodriguez 	if (lr && lr->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE &&
2148a2f73b6cSLuis R. Rodriguez 	    wiphy->regulatory_flags & REGULATORY_CUSTOM_REG)
21493195e489SLuis R. Rodriguez 		return true;
21503195e489SLuis R. Rodriguez 
21513195e489SLuis R. Rodriguez 	return false;
21523195e489SLuis R. Rodriguez }
21533195e489SLuis R. Rodriguez 
handle_reg_beacon(struct wiphy * wiphy,unsigned int chan_idx,struct reg_beacon * reg_beacon)21541a919318SJohannes Berg static void handle_reg_beacon(struct wiphy *wiphy, unsigned int chan_idx,
2155e38f8a7aSLuis R. Rodriguez 			      struct reg_beacon *reg_beacon)
2156e38f8a7aSLuis R. Rodriguez {
2157e38f8a7aSLuis R. Rodriguez 	struct ieee80211_supported_band *sband;
2158e38f8a7aSLuis R. Rodriguez 	struct ieee80211_channel *chan;
21596bad8766SLuis R. Rodriguez 	bool channel_changed = false;
21606bad8766SLuis R. Rodriguez 	struct ieee80211_channel chan_before;
2161e38f8a7aSLuis R. Rodriguez 
2162e38f8a7aSLuis R. Rodriguez 	sband = wiphy->bands[reg_beacon->chan.band];
2163e38f8a7aSLuis R. Rodriguez 	chan = &sband->channels[chan_idx];
2164e38f8a7aSLuis R. Rodriguez 
2165934f4c7dSThomas Pedersen 	if (likely(!ieee80211_channel_equal(chan, &reg_beacon->chan)))
2166e38f8a7aSLuis R. Rodriguez 		return;
2167e38f8a7aSLuis R. Rodriguez 
21686bad8766SLuis R. Rodriguez 	if (chan->beacon_found)
21696bad8766SLuis R. Rodriguez 		return;
21706bad8766SLuis R. Rodriguez 
21716bad8766SLuis R. Rodriguez 	chan->beacon_found = true;
21726bad8766SLuis R. Rodriguez 
21730f500a5fSLuis R. Rodriguez 	if (!reg_is_world_roaming(wiphy))
21740f500a5fSLuis R. Rodriguez 		return;
21750f500a5fSLuis R. Rodriguez 
2176a2f73b6cSLuis R. Rodriguez 	if (wiphy->regulatory_flags & REGULATORY_DISABLE_BEACON_HINTS)
217737184244SLuis R. Rodriguez 		return;
217837184244SLuis R. Rodriguez 
2179a48a52b7SJohannes Berg 	chan_before = *chan;
21806bad8766SLuis R. Rodriguez 
21818fe02e16SLuis R. Rodriguez 	if (chan->flags & IEEE80211_CHAN_NO_IR) {
21828fe02e16SLuis R. Rodriguez 		chan->flags &= ~IEEE80211_CHAN_NO_IR;
21836bad8766SLuis R. Rodriguez 		channel_changed = true;
2184e38f8a7aSLuis R. Rodriguez 	}
2185e38f8a7aSLuis R. Rodriguez 
21866bad8766SLuis R. Rodriguez 	if (channel_changed)
21876bad8766SLuis R. Rodriguez 		nl80211_send_beacon_hint_event(wiphy, &chan_before, chan);
2188e38f8a7aSLuis R. Rodriguez }
2189e38f8a7aSLuis R. Rodriguez 
2190e38f8a7aSLuis R. Rodriguez /*
2191e38f8a7aSLuis R. Rodriguez  * Called when a scan on a wiphy finds a beacon on
2192e38f8a7aSLuis R. Rodriguez  * new channel
2193e38f8a7aSLuis R. Rodriguez  */
wiphy_update_new_beacon(struct wiphy * wiphy,struct reg_beacon * reg_beacon)2194e38f8a7aSLuis R. Rodriguez static void wiphy_update_new_beacon(struct wiphy *wiphy,
2195e38f8a7aSLuis R. Rodriguez 				    struct reg_beacon *reg_beacon)
2196e38f8a7aSLuis R. Rodriguez {
2197e38f8a7aSLuis R. Rodriguez 	unsigned int i;
2198e38f8a7aSLuis R. Rodriguez 	struct ieee80211_supported_band *sband;
2199e38f8a7aSLuis R. Rodriguez 
2200e38f8a7aSLuis R. Rodriguez 	if (!wiphy->bands[reg_beacon->chan.band])
2201e38f8a7aSLuis R. Rodriguez 		return;
2202e38f8a7aSLuis R. Rodriguez 
2203e38f8a7aSLuis R. Rodriguez 	sband = wiphy->bands[reg_beacon->chan.band];
2204e38f8a7aSLuis R. Rodriguez 
2205e38f8a7aSLuis R. Rodriguez 	for (i = 0; i < sband->n_channels; i++)
2206e38f8a7aSLuis R. Rodriguez 		handle_reg_beacon(wiphy, i, reg_beacon);
2207e38f8a7aSLuis R. Rodriguez }
2208e38f8a7aSLuis R. Rodriguez 
2209e38f8a7aSLuis R. Rodriguez /*
2210e38f8a7aSLuis R. Rodriguez  * Called upon reg changes or a new wiphy is added
2211e38f8a7aSLuis R. Rodriguez  */
wiphy_update_beacon_reg(struct wiphy * wiphy)2212e38f8a7aSLuis R. Rodriguez static void wiphy_update_beacon_reg(struct wiphy *wiphy)
2213e38f8a7aSLuis R. Rodriguez {
2214e38f8a7aSLuis R. Rodriguez 	unsigned int i;
2215e38f8a7aSLuis R. Rodriguez 	struct ieee80211_supported_band *sband;
2216e38f8a7aSLuis R. Rodriguez 	struct reg_beacon *reg_beacon;
2217e38f8a7aSLuis R. Rodriguez 
2218e38f8a7aSLuis R. Rodriguez 	list_for_each_entry(reg_beacon, &reg_beacon_list, list) {
2219e38f8a7aSLuis R. Rodriguez 		if (!wiphy->bands[reg_beacon->chan.band])
2220e38f8a7aSLuis R. Rodriguez 			continue;
2221e38f8a7aSLuis R. Rodriguez 		sband = wiphy->bands[reg_beacon->chan.band];
2222e38f8a7aSLuis R. Rodriguez 		for (i = 0; i < sband->n_channels; i++)
2223e38f8a7aSLuis R. Rodriguez 			handle_reg_beacon(wiphy, i, reg_beacon);
2224e38f8a7aSLuis R. Rodriguez 	}
2225e38f8a7aSLuis R. Rodriguez }
2226e38f8a7aSLuis R. Rodriguez 
2227e38f8a7aSLuis R. Rodriguez /* Reap the advantages of previously found beacons */
reg_process_beacons(struct wiphy * wiphy)2228e38f8a7aSLuis R. Rodriguez static void reg_process_beacons(struct wiphy *wiphy)
2229e38f8a7aSLuis R. Rodriguez {
2230b1ed8dddSLuis R. Rodriguez 	/*
2231b1ed8dddSLuis R. Rodriguez 	 * Means we are just firing up cfg80211, so no beacons would
2232b1ed8dddSLuis R. Rodriguez 	 * have been processed yet.
2233b1ed8dddSLuis R. Rodriguez 	 */
2234b1ed8dddSLuis R. Rodriguez 	if (!last_request)
2235b1ed8dddSLuis R. Rodriguez 		return;
2236e38f8a7aSLuis R. Rodriguez 	wiphy_update_beacon_reg(wiphy);
2237e38f8a7aSLuis R. Rodriguez }
2238e38f8a7aSLuis R. Rodriguez 
is_ht40_allowed(struct ieee80211_channel * chan)22391a919318SJohannes Berg static bool is_ht40_allowed(struct ieee80211_channel *chan)
2240038659e7SLuis R. Rodriguez {
2241038659e7SLuis R. Rodriguez 	if (!chan)
2242038659e7SLuis R. Rodriguez 		return false;
22431a919318SJohannes Berg 	if (chan->flags & IEEE80211_CHAN_DISABLED)
22441a919318SJohannes Berg 		return false;
22451a919318SJohannes Berg 	/* This would happen when regulatory rules disallow HT40 completely */
224655b183adSFelix Fietkau 	if ((chan->flags & IEEE80211_CHAN_NO_HT40) == IEEE80211_CHAN_NO_HT40)
224755b183adSFelix Fietkau 		return false;
224855b183adSFelix Fietkau 	return true;
2249038659e7SLuis R. Rodriguez }
2250038659e7SLuis R. Rodriguez 
reg_process_ht_flags_channel(struct wiphy * wiphy,struct ieee80211_channel * channel)2251038659e7SLuis R. Rodriguez static void reg_process_ht_flags_channel(struct wiphy *wiphy,
2252fdc9d7b2SJohannes Berg 					 struct ieee80211_channel *channel)
2253038659e7SLuis R. Rodriguez {
2254fdc9d7b2SJohannes Berg 	struct ieee80211_supported_band *sband = wiphy->bands[channel->band];
2255038659e7SLuis R. Rodriguez 	struct ieee80211_channel *channel_before = NULL, *channel_after = NULL;
22564e0854a7SEmmanuel Grumbach 	const struct ieee80211_regdomain *regd;
2257038659e7SLuis R. Rodriguez 	unsigned int i;
22584e0854a7SEmmanuel Grumbach 	u32 flags;
2259038659e7SLuis R. Rodriguez 
22601a919318SJohannes Berg 	if (!is_ht40_allowed(channel)) {
2261038659e7SLuis R. Rodriguez 		channel->flags |= IEEE80211_CHAN_NO_HT40;
2262038659e7SLuis R. Rodriguez 		return;
2263038659e7SLuis R. Rodriguez 	}
2264038659e7SLuis R. Rodriguez 
2265038659e7SLuis R. Rodriguez 	/*
2266038659e7SLuis R. Rodriguez 	 * We need to ensure the extension channels exist to
2267038659e7SLuis R. Rodriguez 	 * be able to use HT40- or HT40+, this finds them (or not)
2268038659e7SLuis R. Rodriguez 	 */
2269038659e7SLuis R. Rodriguez 	for (i = 0; i < sband->n_channels; i++) {
2270038659e7SLuis R. Rodriguez 		struct ieee80211_channel *c = &sband->channels[i];
22711a919318SJohannes Berg 
2272038659e7SLuis R. Rodriguez 		if (c->center_freq == (channel->center_freq - 20))
2273038659e7SLuis R. Rodriguez 			channel_before = c;
2274038659e7SLuis R. Rodriguez 		if (c->center_freq == (channel->center_freq + 20))
2275038659e7SLuis R. Rodriguez 			channel_after = c;
2276038659e7SLuis R. Rodriguez 	}
2277038659e7SLuis R. Rodriguez 
22784e0854a7SEmmanuel Grumbach 	flags = 0;
22794e0854a7SEmmanuel Grumbach 	regd = get_wiphy_regdom(wiphy);
22804e0854a7SEmmanuel Grumbach 	if (regd) {
22814e0854a7SEmmanuel Grumbach 		const struct ieee80211_reg_rule *reg_rule =
22824e0854a7SEmmanuel Grumbach 			freq_reg_info_regd(MHZ_TO_KHZ(channel->center_freq),
22834e0854a7SEmmanuel Grumbach 					   regd, MHZ_TO_KHZ(20));
22844e0854a7SEmmanuel Grumbach 
22854e0854a7SEmmanuel Grumbach 		if (!IS_ERR(reg_rule))
22864e0854a7SEmmanuel Grumbach 			flags = reg_rule->flags;
22874e0854a7SEmmanuel Grumbach 	}
22884e0854a7SEmmanuel Grumbach 
2289038659e7SLuis R. Rodriguez 	/*
2290038659e7SLuis R. Rodriguez 	 * Please note that this assumes target bandwidth is 20 MHz,
2291038659e7SLuis R. Rodriguez 	 * if that ever changes we also need to change the below logic
2292038659e7SLuis R. Rodriguez 	 * to include that as well.
2293038659e7SLuis R. Rodriguez 	 */
22944e0854a7SEmmanuel Grumbach 	if (!is_ht40_allowed(channel_before) ||
22954e0854a7SEmmanuel Grumbach 	    flags & NL80211_RRF_NO_HT40MINUS)
2296689da1b3SLuis R. Rodriguez 		channel->flags |= IEEE80211_CHAN_NO_HT40MINUS;
2297038659e7SLuis R. Rodriguez 	else
2298689da1b3SLuis R. Rodriguez 		channel->flags &= ~IEEE80211_CHAN_NO_HT40MINUS;
2299038659e7SLuis R. Rodriguez 
23004e0854a7SEmmanuel Grumbach 	if (!is_ht40_allowed(channel_after) ||
23014e0854a7SEmmanuel Grumbach 	    flags & NL80211_RRF_NO_HT40PLUS)
2302689da1b3SLuis R. Rodriguez 		channel->flags |= IEEE80211_CHAN_NO_HT40PLUS;
2303038659e7SLuis R. Rodriguez 	else
2304689da1b3SLuis R. Rodriguez 		channel->flags &= ~IEEE80211_CHAN_NO_HT40PLUS;
2305038659e7SLuis R. Rodriguez }
2306038659e7SLuis R. Rodriguez 
reg_process_ht_flags_band(struct wiphy * wiphy,struct ieee80211_supported_band * sband)2307038659e7SLuis R. Rodriguez static void reg_process_ht_flags_band(struct wiphy *wiphy,
2308fdc9d7b2SJohannes Berg 				      struct ieee80211_supported_band *sband)
2309038659e7SLuis R. Rodriguez {
2310038659e7SLuis R. Rodriguez 	unsigned int i;
2311038659e7SLuis R. Rodriguez 
2312fdc9d7b2SJohannes Berg 	if (!sband)
2313fdc9d7b2SJohannes Berg 		return;
2314038659e7SLuis R. Rodriguez 
2315038659e7SLuis R. Rodriguez 	for (i = 0; i < sband->n_channels; i++)
2316fdc9d7b2SJohannes Berg 		reg_process_ht_flags_channel(wiphy, &sband->channels[i]);
2317038659e7SLuis R. Rodriguez }
2318038659e7SLuis R. Rodriguez 
reg_process_ht_flags(struct wiphy * wiphy)2319038659e7SLuis R. Rodriguez static void reg_process_ht_flags(struct wiphy *wiphy)
2320038659e7SLuis R. Rodriguez {
232157fbcce3SJohannes Berg 	enum nl80211_band band;
2322038659e7SLuis R. Rodriguez 
2323038659e7SLuis R. Rodriguez 	if (!wiphy)
2324038659e7SLuis R. Rodriguez 		return;
2325038659e7SLuis R. Rodriguez 
232657fbcce3SJohannes Berg 	for (band = 0; band < NUM_NL80211_BANDS; band++)
2327fdc9d7b2SJohannes Berg 		reg_process_ht_flags_band(wiphy, wiphy->bands[band]);
2328038659e7SLuis R. Rodriguez }
2329038659e7SLuis R. Rodriguez 
reg_call_notifier(struct wiphy * wiphy,struct regulatory_request * request)23300e3802dbSLuis R. Rodriguez static void reg_call_notifier(struct wiphy *wiphy,
23310e3802dbSLuis R. Rodriguez 			      struct regulatory_request *request)
23320e3802dbSLuis R. Rodriguez {
23330e3802dbSLuis R. Rodriguez 	if (wiphy->reg_notifier)
23340e3802dbSLuis R. Rodriguez 		wiphy->reg_notifier(wiphy, request);
23350e3802dbSLuis R. Rodriguez }
23360e3802dbSLuis R. Rodriguez 
reg_wdev_chan_valid(struct wiphy * wiphy,struct wireless_dev * wdev)2337ad932f04SArik Nemtsov static bool reg_wdev_chan_valid(struct wiphy *wiphy, struct wireless_dev *wdev)
2338ad932f04SArik Nemtsov {
2339f43e5210SJohannes Berg 	struct cfg80211_chan_def chandef = {};
2340ad932f04SArik Nemtsov 	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
234120658702SArik Nemtsov 	enum nl80211_iftype iftype;
2342e08ebd6dSIlan Peer 	bool ret;
23437b0a0e3cSJohannes Berg 	int link;
2344ad932f04SArik Nemtsov 
2345ad932f04SArik Nemtsov 	wdev_lock(wdev);
234620658702SArik Nemtsov 	iftype = wdev->iftype;
2347ad932f04SArik Nemtsov 
234820658702SArik Nemtsov 	/* make sure the interface is active */
2349ad932f04SArik Nemtsov 	if (!wdev->netdev || !netif_running(wdev->netdev))
235020658702SArik Nemtsov 		goto wdev_inactive_unlock;
2351ad932f04SArik Nemtsov 
23527b0a0e3cSJohannes Berg 	for (link = 0; link < ARRAY_SIZE(wdev->links); link++) {
23537b0a0e3cSJohannes Berg 		struct ieee80211_channel *chan;
23547b0a0e3cSJohannes Berg 
23557b0a0e3cSJohannes Berg 		if (!wdev->valid_links && link > 0)
23567b0a0e3cSJohannes Berg 			break;
2357b22552fcSJohannes Berg 		if (wdev->valid_links && !(wdev->valid_links & BIT(link)))
23587b0a0e3cSJohannes Berg 			continue;
235920658702SArik Nemtsov 		switch (iftype) {
2360ad932f04SArik Nemtsov 		case NL80211_IFTYPE_AP:
2361ad932f04SArik Nemtsov 		case NL80211_IFTYPE_P2P_GO:
2362bc185761SShaul Triebitz 			if (!wdev->links[link].ap.beacon_interval)
2363bc185761SShaul Triebitz 				continue;
2364bc185761SShaul Triebitz 			chandef = wdev->links[link].ap.chandef;
2365bc185761SShaul Triebitz 			break;
2366701fdfe3SSriram R 		case NL80211_IFTYPE_MESH_POINT:
23677b0a0e3cSJohannes Berg 			if (!wdev->u.mesh.beacon_interval)
23687b0a0e3cSJohannes Berg 				continue;
23697b0a0e3cSJohannes Berg 			chandef = wdev->u.mesh.chandef;
2370ad932f04SArik Nemtsov 			break;
2371185076d6SArik Nemtsov 		case NL80211_IFTYPE_ADHOC:
23727b0a0e3cSJohannes Berg 			if (!wdev->u.ibss.ssid_len)
23737b0a0e3cSJohannes Berg 				continue;
23747b0a0e3cSJohannes Berg 			chandef = wdev->u.ibss.chandef;
2375185076d6SArik Nemtsov 			break;
2376ad932f04SArik Nemtsov 		case NL80211_IFTYPE_STATION:
2377ad932f04SArik Nemtsov 		case NL80211_IFTYPE_P2P_CLIENT:
23787b0a0e3cSJohannes Berg 			/* Maybe we could consider disabling that link only? */
23797b0a0e3cSJohannes Berg 			if (!wdev->links[link].client.current_bss)
23807b0a0e3cSJohannes Berg 				continue;
23817b0a0e3cSJohannes Berg 
23827b0a0e3cSJohannes Berg 			chan = wdev->links[link].client.current_bss->pub.channel;
23837b0a0e3cSJohannes Berg 			if (!chan)
23847b0a0e3cSJohannes Berg 				continue;
2385ad932f04SArik Nemtsov 
238620658702SArik Nemtsov 			if (!rdev->ops->get_channel ||
23877b0a0e3cSJohannes Berg 			    rdev_get_channel(rdev, wdev, link, &chandef))
23887b0a0e3cSJohannes Berg 				cfg80211_chandef_create(&chandef, chan,
238920658702SArik Nemtsov 							NL80211_CHAN_NO_HT);
2390ad932f04SArik Nemtsov 			break;
2391ad932f04SArik Nemtsov 		case NL80211_IFTYPE_MONITOR:
2392ad932f04SArik Nemtsov 		case NL80211_IFTYPE_AP_VLAN:
2393ad932f04SArik Nemtsov 		case NL80211_IFTYPE_P2P_DEVICE:
2394ad932f04SArik Nemtsov 			/* no enforcement required */
2395ad932f04SArik Nemtsov 			break;
2396e8c2af66SJohannes Berg 		case NL80211_IFTYPE_OCB:
2397e8c2af66SJohannes Berg 			if (!wdev->u.ocb.chandef.chan)
2398e8c2af66SJohannes Berg 				continue;
2399e8c2af66SJohannes Berg 			chandef = wdev->u.ocb.chandef;
2400e8c2af66SJohannes Berg 			break;
2401e8c2af66SJohannes Berg 		case NL80211_IFTYPE_NAN:
2402e8c2af66SJohannes Berg 			/* we have no info, but NAN is also pretty universal */
2403e8c2af66SJohannes Berg 			continue;
2404ad932f04SArik Nemtsov 		default:
2405ad932f04SArik Nemtsov 			/* others not implemented for now */
2406e8c2af66SJohannes Berg 			WARN_ON_ONCE(1);
2407ad932f04SArik Nemtsov 			break;
2408ad932f04SArik Nemtsov 		}
2409ad932f04SArik Nemtsov 
2410ad932f04SArik Nemtsov 		wdev_unlock(wdev);
241120658702SArik Nemtsov 
241220658702SArik Nemtsov 		switch (iftype) {
241320658702SArik Nemtsov 		case NL80211_IFTYPE_AP:
241420658702SArik Nemtsov 		case NL80211_IFTYPE_P2P_GO:
241520658702SArik Nemtsov 		case NL80211_IFTYPE_ADHOC:
2416701fdfe3SSriram R 		case NL80211_IFTYPE_MESH_POINT:
24177b0a0e3cSJohannes Berg 			ret = cfg80211_reg_can_beacon_relax(wiphy, &chandef,
24187b0a0e3cSJohannes Berg 							    iftype);
24197b0a0e3cSJohannes Berg 			if (!ret)
2420e08ebd6dSIlan Peer 				return ret;
24217b0a0e3cSJohannes Berg 			break;
242220658702SArik Nemtsov 		case NL80211_IFTYPE_STATION:
242320658702SArik Nemtsov 		case NL80211_IFTYPE_P2P_CLIENT:
24247b0a0e3cSJohannes Berg 			ret = cfg80211_chandef_usable(wiphy, &chandef,
242520658702SArik Nemtsov 						      IEEE80211_CHAN_DISABLED);
24267b0a0e3cSJohannes Berg 			if (!ret)
24277b0a0e3cSJohannes Berg 				return ret;
24287b0a0e3cSJohannes Berg 			break;
242920658702SArik Nemtsov 		default:
243020658702SArik Nemtsov 			break;
243120658702SArik Nemtsov 		}
243220658702SArik Nemtsov 
24337b0a0e3cSJohannes Berg 		wdev_lock(wdev);
24347b0a0e3cSJohannes Berg 	}
24357b0a0e3cSJohannes Berg 
24367b0a0e3cSJohannes Berg 	wdev_unlock(wdev);
24377b0a0e3cSJohannes Berg 
243820658702SArik Nemtsov 	return true;
243920658702SArik Nemtsov 
244020658702SArik Nemtsov wdev_inactive_unlock:
244120658702SArik Nemtsov 	wdev_unlock(wdev);
244220658702SArik Nemtsov 	return true;
2443ad932f04SArik Nemtsov }
2444ad932f04SArik Nemtsov 
reg_leave_invalid_chans(struct wiphy * wiphy)2445ad932f04SArik Nemtsov static void reg_leave_invalid_chans(struct wiphy *wiphy)
2446ad932f04SArik Nemtsov {
2447ad932f04SArik Nemtsov 	struct wireless_dev *wdev;
2448ad932f04SArik Nemtsov 	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
2449ad932f04SArik Nemtsov 
2450f7e60032SJohannes Berg 	wiphy_lock(wiphy);
245153873f13SJohannes Berg 	list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list)
2452ad932f04SArik Nemtsov 		if (!reg_wdev_chan_valid(wiphy, wdev))
2453ad932f04SArik Nemtsov 			cfg80211_leave(rdev, wdev);
2454f7e60032SJohannes Berg 	wiphy_unlock(wiphy);
2455ad932f04SArik Nemtsov }
2456ad932f04SArik Nemtsov 
reg_check_chans_work(struct work_struct * work)2457ad932f04SArik Nemtsov static void reg_check_chans_work(struct work_struct *work)
2458ad932f04SArik Nemtsov {
2459ad932f04SArik Nemtsov 	struct cfg80211_registered_device *rdev;
2460ad932f04SArik Nemtsov 
2461c799ba6eSJohannes Berg 	pr_debug("Verifying active interfaces after reg change\n");
2462ad932f04SArik Nemtsov 	rtnl_lock();
2463ad932f04SArik Nemtsov 
2464ad932f04SArik Nemtsov 	list_for_each_entry(rdev, &cfg80211_rdev_list, list)
2465ad932f04SArik Nemtsov 		reg_leave_invalid_chans(&rdev->wiphy);
2466ad932f04SArik Nemtsov 
2467ad932f04SArik Nemtsov 	rtnl_unlock();
2468ad932f04SArik Nemtsov }
2469ad932f04SArik Nemtsov 
reg_check_channels(void)2470ad932f04SArik Nemtsov static void reg_check_channels(void)
2471ad932f04SArik Nemtsov {
2472ad932f04SArik Nemtsov 	/*
2473ad932f04SArik Nemtsov 	 * Give usermode a chance to do something nicer (move to another
2474ad932f04SArik Nemtsov 	 * channel, orderly disconnection), before forcing a disconnection.
2475ad932f04SArik Nemtsov 	 */
2476ad932f04SArik Nemtsov 	mod_delayed_work(system_power_efficient_wq,
2477ad932f04SArik Nemtsov 			 &reg_check_chans,
2478ad932f04SArik Nemtsov 			 msecs_to_jiffies(REG_ENFORCE_GRACE_MS));
2479ad932f04SArik Nemtsov }
2480ad932f04SArik Nemtsov 
wiphy_update_regulatory(struct wiphy * wiphy,enum nl80211_reg_initiator initiator)2481eac03e38SSven Neumann static void wiphy_update_regulatory(struct wiphy *wiphy,
24827db90f4aSLuis R. Rodriguez 				    enum nl80211_reg_initiator initiator)
24838318d78aSJohannes Berg {
248457fbcce3SJohannes Berg 	enum nl80211_band band;
2485c492db37SJohannes Berg 	struct regulatory_request *lr = get_last_request();
2486eac03e38SSven Neumann 
24870e3802dbSLuis R. Rodriguez 	if (ignore_reg_update(wiphy, initiator)) {
24880e3802dbSLuis R. Rodriguez 		/*
24890e3802dbSLuis R. Rodriguez 		 * Regulatory updates set by CORE are ignored for custom
24900e3802dbSLuis R. Rodriguez 		 * regulatory cards. Let us notify the changes to the driver,
24910e3802dbSLuis R. Rodriguez 		 * as some drivers used this to restore its orig_* reg domain.
24920e3802dbSLuis R. Rodriguez 		 */
24930e3802dbSLuis R. Rodriguez 		if (initiator == NL80211_REGDOM_SET_BY_CORE &&
2494e31f6456SAmar Singhal 		    wiphy->regulatory_flags & REGULATORY_CUSTOM_REG &&
2495e31f6456SAmar Singhal 		    !(wiphy->regulatory_flags &
2496e31f6456SAmar Singhal 		      REGULATORY_WIPHY_SELF_MANAGED))
24970e3802dbSLuis R. Rodriguez 			reg_call_notifier(wiphy, lr);
2498a203c2aaSSven Neumann 		return;
24990e3802dbSLuis R. Rodriguez 	}
2500a203c2aaSSven Neumann 
2501c492db37SJohannes Berg 	lr->dfs_region = get_cfg80211_regdom()->dfs_region;
2502b68e6b3bSLuis R. Rodriguez 
250357fbcce3SJohannes Berg 	for (band = 0; band < NUM_NL80211_BANDS; band++)
2504fdc9d7b2SJohannes Berg 		handle_band(wiphy, initiator, wiphy->bands[band]);
2505a203c2aaSSven Neumann 
2506e38f8a7aSLuis R. Rodriguez 	reg_process_beacons(wiphy);
2507038659e7SLuis R. Rodriguez 	reg_process_ht_flags(wiphy);
25080e3802dbSLuis R. Rodriguez 	reg_call_notifier(wiphy, lr);
2509b2e1b302SLuis R. Rodriguez }
2510b2e1b302SLuis R. Rodriguez 
update_all_wiphy_regulatory(enum nl80211_reg_initiator initiator)2511d7549cbbSSven Neumann static void update_all_wiphy_regulatory(enum nl80211_reg_initiator initiator)
2512d7549cbbSSven Neumann {
2513d7549cbbSSven Neumann 	struct cfg80211_registered_device *rdev;
25144a38994fSRajkumar Manoharan 	struct wiphy *wiphy;
2515d7549cbbSSven Neumann 
25165fe231e8SJohannes Berg 	ASSERT_RTNL();
2517458f4f9eSJohannes Berg 
25184a38994fSRajkumar Manoharan 	list_for_each_entry(rdev, &cfg80211_rdev_list, list) {
25194a38994fSRajkumar Manoharan 		wiphy = &rdev->wiphy;
25204a38994fSRajkumar Manoharan 		wiphy_update_regulatory(wiphy, initiator);
25214a38994fSRajkumar Manoharan 	}
2522ad932f04SArik Nemtsov 
2523ad932f04SArik Nemtsov 	reg_check_channels();
2524d7549cbbSSven Neumann }
2525d7549cbbSSven Neumann 
handle_channel_custom(struct wiphy * wiphy,struct ieee80211_channel * chan,const struct ieee80211_regdomain * regd,u32 min_bw)25261fa25e41SLuis R. Rodriguez static void handle_channel_custom(struct wiphy *wiphy,
2527fdc9d7b2SJohannes Berg 				  struct ieee80211_channel *chan,
2528c4b9d655SGanapathi Bhat 				  const struct ieee80211_regdomain *regd,
2529c4b9d655SGanapathi Bhat 				  u32 min_bw)
25301fa25e41SLuis R. Rodriguez {
2531038659e7SLuis R. Rodriguez 	u32 bw_flags = 0;
25321fa25e41SLuis R. Rodriguez 	const struct ieee80211_reg_rule *reg_rule = NULL;
25331fa25e41SLuis R. Rodriguez 	const struct ieee80211_power_rule *power_rule = NULL;
2534934f4c7dSThomas Pedersen 	u32 bw, center_freq_khz;
25351fa25e41SLuis R. Rodriguez 
2536934f4c7dSThomas Pedersen 	center_freq_khz = ieee80211_channel_to_khz(chan);
2537c4b9d655SGanapathi Bhat 	for (bw = MHZ_TO_KHZ(20); bw >= min_bw; bw = bw / 2) {
2538934f4c7dSThomas Pedersen 		reg_rule = freq_reg_info_regd(center_freq_khz, regd, bw);
25394edd5698SMatthias May 		if (!IS_ERR(reg_rule))
25404edd5698SMatthias May 			break;
25414edd5698SMatthias May 	}
25421fa25e41SLuis R. Rodriguez 
2543a7ee7d44SJohannes Berg 	if (IS_ERR_OR_NULL(reg_rule)) {
2544934f4c7dSThomas Pedersen 		pr_debug("Disabling freq %d.%03d MHz as custom regd has no rule that fits it\n",
2545934f4c7dSThomas Pedersen 			 chan->center_freq, chan->freq_offset);
2546db8dfee5SArik Nemtsov 		if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED) {
2547db8dfee5SArik Nemtsov 			chan->flags |= IEEE80211_CHAN_DISABLED;
2548db8dfee5SArik Nemtsov 		} else {
2549cc493e4fSLuis R. Rodriguez 			chan->orig_flags |= IEEE80211_CHAN_DISABLED;
2550cc493e4fSLuis R. Rodriguez 			chan->flags = chan->orig_flags;
2551db8dfee5SArik Nemtsov 		}
25521fa25e41SLuis R. Rodriguez 		return;
25531fa25e41SLuis R. Rodriguez 	}
25541fa25e41SLuis R. Rodriguez 
25551fa25e41SLuis R. Rodriguez 	power_rule = &reg_rule->power_rule;
25561aeb135fSMichal Sojka 	bw_flags = reg_rule_to_chan_bw_flags(regd, reg_rule, chan);
2557038659e7SLuis R. Rodriguez 
25582e18b38fSArik Nemtsov 	chan->dfs_state_entered = jiffies;
2559c7ab5081SArik Nemtsov 	chan->dfs_state = NL80211_DFS_USABLE;
2560c7ab5081SArik Nemtsov 
2561c7ab5081SArik Nemtsov 	chan->beacon_found = false;
2562db8dfee5SArik Nemtsov 
2563db8dfee5SArik Nemtsov 	if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED)
2564db8dfee5SArik Nemtsov 		chan->flags = chan->orig_flags | bw_flags |
2565db8dfee5SArik Nemtsov 			      map_regdom_flags(reg_rule->flags);
2566db8dfee5SArik Nemtsov 	else
2567038659e7SLuis R. Rodriguez 		chan->flags |= map_regdom_flags(reg_rule->flags) | bw_flags;
2568db8dfee5SArik Nemtsov 
25691fa25e41SLuis R. Rodriguez 	chan->max_antenna_gain = (int) MBI_TO_DBI(power_rule->max_antenna_gain);
2570279f0f55SFelix Fietkau 	chan->max_reg_power = chan->max_power =
2571279f0f55SFelix Fietkau 		(int) MBM_TO_DBM(power_rule->max_eirp);
25722e18b38fSArik Nemtsov 
25732e18b38fSArik Nemtsov 	if (chan->flags & IEEE80211_CHAN_RADAR) {
25742e18b38fSArik Nemtsov 		if (reg_rule->dfs_cac_ms)
25752e18b38fSArik Nemtsov 			chan->dfs_cac_ms = reg_rule->dfs_cac_ms;
25762e18b38fSArik Nemtsov 		else
25772e18b38fSArik Nemtsov 			chan->dfs_cac_ms = IEEE80211_DFS_MIN_CAC_TIME_MS;
25782e18b38fSArik Nemtsov 	}
25792e18b38fSArik Nemtsov 
25802e18b38fSArik Nemtsov 	chan->max_power = chan->max_reg_power;
25811fa25e41SLuis R. Rodriguez }
25821fa25e41SLuis R. Rodriguez 
handle_band_custom(struct wiphy * wiphy,struct ieee80211_supported_band * sband,const struct ieee80211_regdomain * regd)2583fdc9d7b2SJohannes Berg static void handle_band_custom(struct wiphy *wiphy,
2584fdc9d7b2SJohannes Berg 			       struct ieee80211_supported_band *sband,
25851fa25e41SLuis R. Rodriguez 			       const struct ieee80211_regdomain *regd)
25861fa25e41SLuis R. Rodriguez {
25871fa25e41SLuis R. Rodriguez 	unsigned int i;
25881fa25e41SLuis R. Rodriguez 
2589fdc9d7b2SJohannes Berg 	if (!sband)
2590fdc9d7b2SJohannes Berg 		return;
25911fa25e41SLuis R. Rodriguez 
2592c4b9d655SGanapathi Bhat 	/*
2593c4b9d655SGanapathi Bhat 	 * We currently assume that you always want at least 20 MHz,
2594c4b9d655SGanapathi Bhat 	 * otherwise channel 12 might get enabled if this rule is
2595c4b9d655SGanapathi Bhat 	 * compatible to US, which permits 2402 - 2472 MHz.
2596c4b9d655SGanapathi Bhat 	 */
25971fa25e41SLuis R. Rodriguez 	for (i = 0; i < sband->n_channels; i++)
2598c4b9d655SGanapathi Bhat 		handle_channel_custom(wiphy, &sband->channels[i], regd,
2599c4b9d655SGanapathi Bhat 				      MHZ_TO_KHZ(20));
26001fa25e41SLuis R. Rodriguez }
26011fa25e41SLuis R. Rodriguez 
26021fa25e41SLuis R. Rodriguez /* Used by drivers prior to wiphy registration */
wiphy_apply_custom_regulatory(struct wiphy * wiphy,const struct ieee80211_regdomain * regd)26031fa25e41SLuis R. Rodriguez void wiphy_apply_custom_regulatory(struct wiphy *wiphy,
26041fa25e41SLuis R. Rodriguez 				   const struct ieee80211_regdomain *regd)
26051fa25e41SLuis R. Rodriguez {
2606beee2469SIlan Peer 	const struct ieee80211_regdomain *new_regd, *tmp;
260757fbcce3SJohannes Berg 	enum nl80211_band band;
2608bbcf3f02SLuis R. Rodriguez 	unsigned int bands_set = 0;
2609ac46d48eSLuis R. Rodriguez 
2610a2f73b6cSLuis R. Rodriguez 	WARN(!(wiphy->regulatory_flags & REGULATORY_CUSTOM_REG),
2611a2f73b6cSLuis R. Rodriguez 	     "wiphy should have REGULATORY_CUSTOM_REG\n");
2612a2f73b6cSLuis R. Rodriguez 	wiphy->regulatory_flags |= REGULATORY_CUSTOM_REG;
2613222ea581SLuis R. Rodriguez 
261457fbcce3SJohannes Berg 	for (band = 0; band < NUM_NL80211_BANDS; band++) {
2615bbcf3f02SLuis R. Rodriguez 		if (!wiphy->bands[band])
2616bbcf3f02SLuis R. Rodriguez 			continue;
2617fdc9d7b2SJohannes Berg 		handle_band_custom(wiphy, wiphy->bands[band], regd);
2618bbcf3f02SLuis R. Rodriguez 		bands_set++;
26191fa25e41SLuis R. Rodriguez 	}
2620bbcf3f02SLuis R. Rodriguez 
2621bbcf3f02SLuis R. Rodriguez 	/*
2622bbcf3f02SLuis R. Rodriguez 	 * no point in calling this if it won't have any effect
26231a919318SJohannes Berg 	 * on your device's supported bands.
2624bbcf3f02SLuis R. Rodriguez 	 */
2625bbcf3f02SLuis R. Rodriguez 	WARN_ON(!bands_set);
2626beee2469SIlan Peer 	new_regd = reg_copy_regd(regd);
2627beee2469SIlan Peer 	if (IS_ERR(new_regd))
2628beee2469SIlan Peer 		return;
2629beee2469SIlan Peer 
263051d62f2fSIlan Peer 	rtnl_lock();
2631a05829a7SJohannes Berg 	wiphy_lock(wiphy);
263251d62f2fSIlan Peer 
2633beee2469SIlan Peer 	tmp = get_wiphy_regdom(wiphy);
2634beee2469SIlan Peer 	rcu_assign_pointer(wiphy->regd, new_regd);
2635beee2469SIlan Peer 	rcu_free_regdom(tmp);
263651d62f2fSIlan Peer 
2637a05829a7SJohannes Berg 	wiphy_unlock(wiphy);
263851d62f2fSIlan Peer 	rtnl_unlock();
26391fa25e41SLuis R. Rodriguez }
26401fa25e41SLuis R. Rodriguez EXPORT_SYMBOL(wiphy_apply_custom_regulatory);
26411fa25e41SLuis R. Rodriguez 
reg_set_request_processed(void)2642b2e253cfSLuis R. Rodriguez static void reg_set_request_processed(void)
2643b2e253cfSLuis R. Rodriguez {
2644b2e253cfSLuis R. Rodriguez 	bool need_more_processing = false;
2645c492db37SJohannes Berg 	struct regulatory_request *lr = get_last_request();
2646b2e253cfSLuis R. Rodriguez 
2647c492db37SJohannes Berg 	lr->processed = true;
2648b2e253cfSLuis R. Rodriguez 
2649b2e253cfSLuis R. Rodriguez 	spin_lock(&reg_requests_lock);
2650b2e253cfSLuis R. Rodriguez 	if (!list_empty(&reg_requests_list))
2651b2e253cfSLuis R. Rodriguez 		need_more_processing = true;
2652b2e253cfSLuis R. Rodriguez 	spin_unlock(&reg_requests_lock);
2653b2e253cfSLuis R. Rodriguez 
2654b6863036SJohannes Berg 	cancel_crda_timeout();
2655a90c7a31SLuis R. Rodriguez 
2656b2e253cfSLuis R. Rodriguez 	if (need_more_processing)
2657b2e253cfSLuis R. Rodriguez 		schedule_work(&reg_work);
2658b2e253cfSLuis R. Rodriguez }
2659b2e253cfSLuis R. Rodriguez 
2660d1c96a9aSLuis R. Rodriguez /**
2661b3eb7f3fSLuis R. Rodriguez  * reg_process_hint_core - process core regulatory requests
2662726e6af9SAndrew Lunn  * @core_request: a pending core regulatory request
2663b3eb7f3fSLuis R. Rodriguez  *
2664b3eb7f3fSLuis R. Rodriguez  * The wireless subsystem can use this function to process
2665b3eb7f3fSLuis R. Rodriguez  * a regulatory request issued by the regulatory core.
2666b3eb7f3fSLuis R. Rodriguez  */
2667d34265a3SJohannes Berg static enum reg_request_treatment
reg_process_hint_core(struct regulatory_request * core_request)2668d34265a3SJohannes Berg reg_process_hint_core(struct regulatory_request *core_request)
2669b3eb7f3fSLuis R. Rodriguez {
2670cecbb069SJohannes Berg 	if (reg_query_database(core_request)) {
2671b3eb7f3fSLuis R. Rodriguez 		core_request->intersect = false;
2672b3eb7f3fSLuis R. Rodriguez 		core_request->processed = false;
267305f1a3eaSLuis R. Rodriguez 		reg_update_last_request(core_request);
2674d34265a3SJohannes Berg 		return REG_REQ_OK;
267525b20dbdSJohannes Berg 	}
2676d34265a3SJohannes Berg 
2677d34265a3SJohannes Berg 	return REG_REQ_IGNORE;
2678b3eb7f3fSLuis R. Rodriguez }
2679b3eb7f3fSLuis R. Rodriguez 
26800d97a619SLuis R. Rodriguez static enum reg_request_treatment
__reg_process_hint_user(struct regulatory_request * user_request)26810d97a619SLuis R. Rodriguez __reg_process_hint_user(struct regulatory_request *user_request)
26820d97a619SLuis R. Rodriguez {
26830d97a619SLuis R. Rodriguez 	struct regulatory_request *lr = get_last_request();
26840d97a619SLuis R. Rodriguez 
26850d97a619SLuis R. Rodriguez 	if (reg_request_cell_base(user_request))
26860d97a619SLuis R. Rodriguez 		return reg_ignore_cell_hint(user_request);
26870d97a619SLuis R. Rodriguez 
26880d97a619SLuis R. Rodriguez 	if (reg_request_cell_base(lr))
26890d97a619SLuis R. Rodriguez 		return REG_REQ_IGNORE;
26900d97a619SLuis R. Rodriguez 
26910d97a619SLuis R. Rodriguez 	if (lr->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE)
26920d97a619SLuis R. Rodriguez 		return REG_REQ_INTERSECT;
26930d97a619SLuis R. Rodriguez 	/*
26940d97a619SLuis R. Rodriguez 	 * If the user knows better the user should set the regdom
26950d97a619SLuis R. Rodriguez 	 * to their country before the IE is picked up
26960d97a619SLuis R. Rodriguez 	 */
26970d97a619SLuis R. Rodriguez 	if (lr->initiator == NL80211_REGDOM_SET_BY_USER &&
26980d97a619SLuis R. Rodriguez 	    lr->intersect)
26990d97a619SLuis R. Rodriguez 		return REG_REQ_IGNORE;
27000d97a619SLuis R. Rodriguez 	/*
27010d97a619SLuis R. Rodriguez 	 * Process user requests only after previous user/driver/core
27020d97a619SLuis R. Rodriguez 	 * requests have been processed
27030d97a619SLuis R. Rodriguez 	 */
27040d97a619SLuis R. Rodriguez 	if ((lr->initiator == NL80211_REGDOM_SET_BY_CORE ||
27050d97a619SLuis R. Rodriguez 	     lr->initiator == NL80211_REGDOM_SET_BY_DRIVER ||
27060d97a619SLuis R. Rodriguez 	     lr->initiator == NL80211_REGDOM_SET_BY_USER) &&
27070d97a619SLuis R. Rodriguez 	    regdom_changes(lr->alpha2))
27080d97a619SLuis R. Rodriguez 		return REG_REQ_IGNORE;
27090d97a619SLuis R. Rodriguez 
27100d97a619SLuis R. Rodriguez 	if (!regdom_changes(user_request->alpha2))
27110d97a619SLuis R. Rodriguez 		return REG_REQ_ALREADY_SET;
27120d97a619SLuis R. Rodriguez 
27130d97a619SLuis R. Rodriguez 	return REG_REQ_OK;
27140d97a619SLuis R. Rodriguez }
27150d97a619SLuis R. Rodriguez 
27160d97a619SLuis R. Rodriguez /**
27170d97a619SLuis R. Rodriguez  * reg_process_hint_user - process user regulatory requests
27180d97a619SLuis R. Rodriguez  * @user_request: a pending user regulatory request
27190d97a619SLuis R. Rodriguez  *
27200d97a619SLuis R. Rodriguez  * The wireless subsystem can use this function to process
27210d97a619SLuis R. Rodriguez  * a regulatory request initiated by userspace.
27220d97a619SLuis R. Rodriguez  */
2723d34265a3SJohannes Berg static enum reg_request_treatment
reg_process_hint_user(struct regulatory_request * user_request)2724d34265a3SJohannes Berg reg_process_hint_user(struct regulatory_request *user_request)
27250d97a619SLuis R. Rodriguez {
27260d97a619SLuis R. Rodriguez 	enum reg_request_treatment treatment;
27270d97a619SLuis R. Rodriguez 
27280d97a619SLuis R. Rodriguez 	treatment = __reg_process_hint_user(user_request);
27290d97a619SLuis R. Rodriguez 	if (treatment == REG_REQ_IGNORE ||
273037d33114SFinn Behrens 	    treatment == REG_REQ_ALREADY_SET)
2731d34265a3SJohannes Berg 		return REG_REQ_IGNORE;
27320d97a619SLuis R. Rodriguez 
27330d97a619SLuis R. Rodriguez 	user_request->intersect = treatment == REG_REQ_INTERSECT;
27340d97a619SLuis R. Rodriguez 	user_request->processed = false;
27355ad6ef5eSLuis R. Rodriguez 
2736cecbb069SJohannes Berg 	if (reg_query_database(user_request)) {
273705f1a3eaSLuis R. Rodriguez 		reg_update_last_request(user_request);
27380d97a619SLuis R. Rodriguez 		user_alpha2[0] = user_request->alpha2[0];
27390d97a619SLuis R. Rodriguez 		user_alpha2[1] = user_request->alpha2[1];
2740d34265a3SJohannes Berg 		return REG_REQ_OK;
274125b20dbdSJohannes Berg 	}
2742d34265a3SJohannes Berg 
2743d34265a3SJohannes Berg 	return REG_REQ_IGNORE;
27440d97a619SLuis R. Rodriguez }
27450d97a619SLuis R. Rodriguez 
274621636c7fSLuis R. Rodriguez static enum reg_request_treatment
__reg_process_hint_driver(struct regulatory_request * driver_request)274721636c7fSLuis R. Rodriguez __reg_process_hint_driver(struct regulatory_request *driver_request)
274821636c7fSLuis R. Rodriguez {
274921636c7fSLuis R. Rodriguez 	struct regulatory_request *lr = get_last_request();
275021636c7fSLuis R. Rodriguez 
275121636c7fSLuis R. Rodriguez 	if (lr->initiator == NL80211_REGDOM_SET_BY_CORE) {
275221636c7fSLuis R. Rodriguez 		if (regdom_changes(driver_request->alpha2))
275321636c7fSLuis R. Rodriguez 			return REG_REQ_OK;
275421636c7fSLuis R. Rodriguez 		return REG_REQ_ALREADY_SET;
275521636c7fSLuis R. Rodriguez 	}
275621636c7fSLuis R. Rodriguez 
275721636c7fSLuis R. Rodriguez 	/*
275821636c7fSLuis R. Rodriguez 	 * This would happen if you unplug and plug your card
275921636c7fSLuis R. Rodriguez 	 * back in or if you add a new device for which the previously
276021636c7fSLuis R. Rodriguez 	 * loaded card also agrees on the regulatory domain.
276121636c7fSLuis R. Rodriguez 	 */
276221636c7fSLuis R. Rodriguez 	if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER &&
276321636c7fSLuis R. Rodriguez 	    !regdom_changes(driver_request->alpha2))
276421636c7fSLuis R. Rodriguez 		return REG_REQ_ALREADY_SET;
276521636c7fSLuis R. Rodriguez 
276621636c7fSLuis R. Rodriguez 	return REG_REQ_INTERSECT;
276721636c7fSLuis R. Rodriguez }
276821636c7fSLuis R. Rodriguez 
276921636c7fSLuis R. Rodriguez /**
277021636c7fSLuis R. Rodriguez  * reg_process_hint_driver - process driver regulatory requests
2771726e6af9SAndrew Lunn  * @wiphy: the wireless device for the regulatory request
277221636c7fSLuis R. Rodriguez  * @driver_request: a pending driver regulatory request
277321636c7fSLuis R. Rodriguez  *
277421636c7fSLuis R. Rodriguez  * The wireless subsystem can use this function to process
277521636c7fSLuis R. Rodriguez  * a regulatory request issued by an 802.11 driver.
277621636c7fSLuis R. Rodriguez  *
277721636c7fSLuis R. Rodriguez  * Returns one of the different reg request treatment values.
277821636c7fSLuis R. Rodriguez  */
277921636c7fSLuis R. Rodriguez static enum reg_request_treatment
reg_process_hint_driver(struct wiphy * wiphy,struct regulatory_request * driver_request)278021636c7fSLuis R. Rodriguez reg_process_hint_driver(struct wiphy *wiphy,
278121636c7fSLuis R. Rodriguez 			struct regulatory_request *driver_request)
278221636c7fSLuis R. Rodriguez {
278334f05f54SArik Nemtsov 	const struct ieee80211_regdomain *regd, *tmp;
278421636c7fSLuis R. Rodriguez 	enum reg_request_treatment treatment;
278521636c7fSLuis R. Rodriguez 
278621636c7fSLuis R. Rodriguez 	treatment = __reg_process_hint_driver(driver_request);
278721636c7fSLuis R. Rodriguez 
278821636c7fSLuis R. Rodriguez 	switch (treatment) {
278921636c7fSLuis R. Rodriguez 	case REG_REQ_OK:
279021636c7fSLuis R. Rodriguez 		break;
279121636c7fSLuis R. Rodriguez 	case REG_REQ_IGNORE:
2792d34265a3SJohannes Berg 		return REG_REQ_IGNORE;
279321636c7fSLuis R. Rodriguez 	case REG_REQ_INTERSECT:
279421636c7fSLuis R. Rodriguez 	case REG_REQ_ALREADY_SET:
279521636c7fSLuis R. Rodriguez 		regd = reg_copy_regd(get_cfg80211_regdom());
2796d34265a3SJohannes Berg 		if (IS_ERR(regd))
2797d34265a3SJohannes Berg 			return REG_REQ_IGNORE;
279834f05f54SArik Nemtsov 
279934f05f54SArik Nemtsov 		tmp = get_wiphy_regdom(wiphy);
2800a05829a7SJohannes Berg 		ASSERT_RTNL();
2801a05829a7SJohannes Berg 		wiphy_lock(wiphy);
280221636c7fSLuis R. Rodriguez 		rcu_assign_pointer(wiphy->regd, regd);
2803a05829a7SJohannes Berg 		wiphy_unlock(wiphy);
280434f05f54SArik Nemtsov 		rcu_free_regdom(tmp);
280521636c7fSLuis R. Rodriguez 	}
280621636c7fSLuis R. Rodriguez 
280721636c7fSLuis R. Rodriguez 
280821636c7fSLuis R. Rodriguez 	driver_request->intersect = treatment == REG_REQ_INTERSECT;
280921636c7fSLuis R. Rodriguez 	driver_request->processed = false;
28105ad6ef5eSLuis R. Rodriguez 
281121636c7fSLuis R. Rodriguez 	/*
281221636c7fSLuis R. Rodriguez 	 * Since CRDA will not be called in this case as we already
281321636c7fSLuis R. Rodriguez 	 * have applied the requested regulatory domain before we just
281421636c7fSLuis R. Rodriguez 	 * inform userspace we have processed the request
281521636c7fSLuis R. Rodriguez 	 */
281621636c7fSLuis R. Rodriguez 	if (treatment == REG_REQ_ALREADY_SET) {
281721636c7fSLuis R. Rodriguez 		nl80211_send_reg_change_event(driver_request);
281825b20dbdSJohannes Berg 		reg_update_last_request(driver_request);
281921636c7fSLuis R. Rodriguez 		reg_set_request_processed();
2820480908a7SJohannes Berg 		return REG_REQ_ALREADY_SET;
282121636c7fSLuis R. Rodriguez 	}
282221636c7fSLuis R. Rodriguez 
2823d34265a3SJohannes Berg 	if (reg_query_database(driver_request)) {
282425b20dbdSJohannes Berg 		reg_update_last_request(driver_request);
282525b20dbdSJohannes Berg 		return REG_REQ_OK;
282621636c7fSLuis R. Rodriguez 	}
282721636c7fSLuis R. Rodriguez 
2828d34265a3SJohannes Berg 	return REG_REQ_IGNORE;
2829d34265a3SJohannes Berg }
2830d34265a3SJohannes Berg 
2831b23e7a9eSLuis R. Rodriguez static enum reg_request_treatment
__reg_process_hint_country_ie(struct wiphy * wiphy,struct regulatory_request * country_ie_request)2832b23e7a9eSLuis R. Rodriguez __reg_process_hint_country_ie(struct wiphy *wiphy,
2833b23e7a9eSLuis R. Rodriguez 			      struct regulatory_request *country_ie_request)
2834b23e7a9eSLuis R. Rodriguez {
2835b23e7a9eSLuis R. Rodriguez 	struct wiphy *last_wiphy = NULL;
2836b23e7a9eSLuis R. Rodriguez 	struct regulatory_request *lr = get_last_request();
2837b23e7a9eSLuis R. Rodriguez 
2838b23e7a9eSLuis R. Rodriguez 	if (reg_request_cell_base(lr)) {
2839b23e7a9eSLuis R. Rodriguez 		/* Trust a Cell base station over the AP's country IE */
2840b23e7a9eSLuis R. Rodriguez 		if (regdom_changes(country_ie_request->alpha2))
2841b23e7a9eSLuis R. Rodriguez 			return REG_REQ_IGNORE;
2842b23e7a9eSLuis R. Rodriguez 		return REG_REQ_ALREADY_SET;
28432a901468SLuis R. Rodriguez 	} else {
28442a901468SLuis R. Rodriguez 		if (wiphy->regulatory_flags & REGULATORY_COUNTRY_IE_IGNORE)
28452a901468SLuis R. Rodriguez 			return REG_REQ_IGNORE;
2846b23e7a9eSLuis R. Rodriguez 	}
2847b23e7a9eSLuis R. Rodriguez 
2848b23e7a9eSLuis R. Rodriguez 	if (unlikely(!is_an_alpha2(country_ie_request->alpha2)))
2849b23e7a9eSLuis R. Rodriguez 		return -EINVAL;
28502f1c6c57SLuis R. Rodriguez 
28512f1c6c57SLuis R. Rodriguez 	if (lr->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE)
28522f1c6c57SLuis R. Rodriguez 		return REG_REQ_OK;
28532f1c6c57SLuis R. Rodriguez 
28542f1c6c57SLuis R. Rodriguez 	last_wiphy = wiphy_idx_to_wiphy(lr->wiphy_idx);
28552f1c6c57SLuis R. Rodriguez 
2856b23e7a9eSLuis R. Rodriguez 	if (last_wiphy != wiphy) {
2857b23e7a9eSLuis R. Rodriguez 		/*
2858b23e7a9eSLuis R. Rodriguez 		 * Two cards with two APs claiming different
2859b23e7a9eSLuis R. Rodriguez 		 * Country IE alpha2s. We could
2860b23e7a9eSLuis R. Rodriguez 		 * intersect them, but that seems unlikely
2861b23e7a9eSLuis R. Rodriguez 		 * to be correct. Reject second one for now.
2862b23e7a9eSLuis R. Rodriguez 		 */
2863b23e7a9eSLuis R. Rodriguez 		if (regdom_changes(country_ie_request->alpha2))
2864b23e7a9eSLuis R. Rodriguez 			return REG_REQ_IGNORE;
2865b23e7a9eSLuis R. Rodriguez 		return REG_REQ_ALREADY_SET;
2866b23e7a9eSLuis R. Rodriguez 	}
286770dcec5aSEmmanuel Grumbach 
286870dcec5aSEmmanuel Grumbach 	if (regdom_changes(country_ie_request->alpha2))
2869b23e7a9eSLuis R. Rodriguez 		return REG_REQ_OK;
2870b23e7a9eSLuis R. Rodriguez 	return REG_REQ_ALREADY_SET;
2871b23e7a9eSLuis R. Rodriguez }
2872b23e7a9eSLuis R. Rodriguez 
2873b3eb7f3fSLuis R. Rodriguez /**
2874b23e7a9eSLuis R. Rodriguez  * reg_process_hint_country_ie - process regulatory requests from country IEs
2875726e6af9SAndrew Lunn  * @wiphy: the wireless device for the regulatory request
2876b23e7a9eSLuis R. Rodriguez  * @country_ie_request: a regulatory request from a country IE
2877d1c96a9aSLuis R. Rodriguez  *
2878b23e7a9eSLuis R. Rodriguez  * The wireless subsystem can use this function to process
2879b23e7a9eSLuis R. Rodriguez  * a regulatory request issued by a country Information Element.
2880d1c96a9aSLuis R. Rodriguez  *
28812f92212bSJohannes Berg  * Returns one of the different reg request treatment values.
2882d1c96a9aSLuis R. Rodriguez  */
28832f92212bSJohannes Berg static enum reg_request_treatment
reg_process_hint_country_ie(struct wiphy * wiphy,struct regulatory_request * country_ie_request)2884b23e7a9eSLuis R. Rodriguez reg_process_hint_country_ie(struct wiphy *wiphy,
2885b23e7a9eSLuis R. Rodriguez 			    struct regulatory_request *country_ie_request)
2886b2e1b302SLuis R. Rodriguez {
28872f92212bSJohannes Berg 	enum reg_request_treatment treatment;
2888b2e1b302SLuis R. Rodriguez 
2889b23e7a9eSLuis R. Rodriguez 	treatment = __reg_process_hint_country_ie(wiphy, country_ie_request);
2890761cf7ecSLuis R. Rodriguez 
28912f92212bSJohannes Berg 	switch (treatment) {
28922f92212bSJohannes Berg 	case REG_REQ_OK:
28932f92212bSJohannes Berg 		break;
2894b23e7a9eSLuis R. Rodriguez 	case REG_REQ_IGNORE:
2895d34265a3SJohannes Berg 		return REG_REQ_IGNORE;
2896b23e7a9eSLuis R. Rodriguez 	case REG_REQ_ALREADY_SET:
2897c888393bSArik Nemtsov 		reg_free_request(country_ie_request);
2898480908a7SJohannes Berg 		return REG_REQ_ALREADY_SET;
2899b23e7a9eSLuis R. Rodriguez 	case REG_REQ_INTERSECT:
2900fb1fc7adSLuis R. Rodriguez 		/*
2901b23e7a9eSLuis R. Rodriguez 		 * This doesn't happen yet, not sure we
2902b23e7a9eSLuis R. Rodriguez 		 * ever want to support it for this case.
2903fb1fc7adSLuis R. Rodriguez 		 */
29048db0c433SToke Høiland-Jørgensen 		WARN_ONCE(1, "Unexpected intersection for country elements");
2905d34265a3SJohannes Berg 		return REG_REQ_IGNORE;
2906d951c1ddSLuis R. Rodriguez 	}
2907b2e1b302SLuis R. Rodriguez 
2908b23e7a9eSLuis R. Rodriguez 	country_ie_request->intersect = false;
2909b23e7a9eSLuis R. Rodriguez 	country_ie_request->processed = false;
29105ad6ef5eSLuis R. Rodriguez 
2911d34265a3SJohannes Berg 	if (reg_query_database(country_ie_request)) {
291205f1a3eaSLuis R. Rodriguez 		reg_update_last_request(country_ie_request);
291325b20dbdSJohannes Berg 		return REG_REQ_OK;
2914b2e1b302SLuis R. Rodriguez 	}
2915b2e1b302SLuis R. Rodriguez 
2916d34265a3SJohannes Berg 	return REG_REQ_IGNORE;
2917d34265a3SJohannes Berg }
2918d34265a3SJohannes Berg 
reg_dfs_domain_same(struct wiphy * wiphy1,struct wiphy * wiphy2)291989766727SVasanthakumar Thiagarajan bool reg_dfs_domain_same(struct wiphy *wiphy1, struct wiphy *wiphy2)
292089766727SVasanthakumar Thiagarajan {
292189766727SVasanthakumar Thiagarajan 	const struct ieee80211_regdomain *wiphy1_regd = NULL;
292289766727SVasanthakumar Thiagarajan 	const struct ieee80211_regdomain *wiphy2_regd = NULL;
292389766727SVasanthakumar Thiagarajan 	const struct ieee80211_regdomain *cfg80211_regd = NULL;
292489766727SVasanthakumar Thiagarajan 	bool dfs_domain_same;
292589766727SVasanthakumar Thiagarajan 
292689766727SVasanthakumar Thiagarajan 	rcu_read_lock();
292789766727SVasanthakumar Thiagarajan 
292889766727SVasanthakumar Thiagarajan 	cfg80211_regd = rcu_dereference(cfg80211_regdomain);
292989766727SVasanthakumar Thiagarajan 	wiphy1_regd = rcu_dereference(wiphy1->regd);
293089766727SVasanthakumar Thiagarajan 	if (!wiphy1_regd)
293189766727SVasanthakumar Thiagarajan 		wiphy1_regd = cfg80211_regd;
293289766727SVasanthakumar Thiagarajan 
293389766727SVasanthakumar Thiagarajan 	wiphy2_regd = rcu_dereference(wiphy2->regd);
293489766727SVasanthakumar Thiagarajan 	if (!wiphy2_regd)
293589766727SVasanthakumar Thiagarajan 		wiphy2_regd = cfg80211_regd;
293689766727SVasanthakumar Thiagarajan 
293789766727SVasanthakumar Thiagarajan 	dfs_domain_same = wiphy1_regd->dfs_region == wiphy2_regd->dfs_region;
293889766727SVasanthakumar Thiagarajan 
293989766727SVasanthakumar Thiagarajan 	rcu_read_unlock();
294089766727SVasanthakumar Thiagarajan 
294189766727SVasanthakumar Thiagarajan 	return dfs_domain_same;
294289766727SVasanthakumar Thiagarajan }
294389766727SVasanthakumar Thiagarajan 
reg_copy_dfs_chan_state(struct ieee80211_channel * dst_chan,struct ieee80211_channel * src_chan)294489766727SVasanthakumar Thiagarajan static void reg_copy_dfs_chan_state(struct ieee80211_channel *dst_chan,
294589766727SVasanthakumar Thiagarajan 				    struct ieee80211_channel *src_chan)
294689766727SVasanthakumar Thiagarajan {
294789766727SVasanthakumar Thiagarajan 	if (!(dst_chan->flags & IEEE80211_CHAN_RADAR) ||
294889766727SVasanthakumar Thiagarajan 	    !(src_chan->flags & IEEE80211_CHAN_RADAR))
294989766727SVasanthakumar Thiagarajan 		return;
295089766727SVasanthakumar Thiagarajan 
295189766727SVasanthakumar Thiagarajan 	if (dst_chan->flags & IEEE80211_CHAN_DISABLED ||
295289766727SVasanthakumar Thiagarajan 	    src_chan->flags & IEEE80211_CHAN_DISABLED)
295389766727SVasanthakumar Thiagarajan 		return;
295489766727SVasanthakumar Thiagarajan 
295589766727SVasanthakumar Thiagarajan 	if (src_chan->center_freq == dst_chan->center_freq &&
295689766727SVasanthakumar Thiagarajan 	    dst_chan->dfs_state == NL80211_DFS_USABLE) {
295789766727SVasanthakumar Thiagarajan 		dst_chan->dfs_state = src_chan->dfs_state;
295889766727SVasanthakumar Thiagarajan 		dst_chan->dfs_state_entered = src_chan->dfs_state_entered;
295989766727SVasanthakumar Thiagarajan 	}
296089766727SVasanthakumar Thiagarajan }
296189766727SVasanthakumar Thiagarajan 
wiphy_share_dfs_chan_state(struct wiphy * dst_wiphy,struct wiphy * src_wiphy)296289766727SVasanthakumar Thiagarajan static void wiphy_share_dfs_chan_state(struct wiphy *dst_wiphy,
296389766727SVasanthakumar Thiagarajan 				       struct wiphy *src_wiphy)
296489766727SVasanthakumar Thiagarajan {
296589766727SVasanthakumar Thiagarajan 	struct ieee80211_supported_band *src_sband, *dst_sband;
296689766727SVasanthakumar Thiagarajan 	struct ieee80211_channel *src_chan, *dst_chan;
296789766727SVasanthakumar Thiagarajan 	int i, j, band;
296889766727SVasanthakumar Thiagarajan 
296989766727SVasanthakumar Thiagarajan 	if (!reg_dfs_domain_same(dst_wiphy, src_wiphy))
297089766727SVasanthakumar Thiagarajan 		return;
297189766727SVasanthakumar Thiagarajan 
297289766727SVasanthakumar Thiagarajan 	for (band = 0; band < NUM_NL80211_BANDS; band++) {
297389766727SVasanthakumar Thiagarajan 		dst_sband = dst_wiphy->bands[band];
297489766727SVasanthakumar Thiagarajan 		src_sband = src_wiphy->bands[band];
297589766727SVasanthakumar Thiagarajan 		if (!dst_sband || !src_sband)
297689766727SVasanthakumar Thiagarajan 			continue;
297789766727SVasanthakumar Thiagarajan 
297889766727SVasanthakumar Thiagarajan 		for (i = 0; i < dst_sband->n_channels; i++) {
297989766727SVasanthakumar Thiagarajan 			dst_chan = &dst_sband->channels[i];
298089766727SVasanthakumar Thiagarajan 			for (j = 0; j < src_sband->n_channels; j++) {
298189766727SVasanthakumar Thiagarajan 				src_chan = &src_sband->channels[j];
298289766727SVasanthakumar Thiagarajan 				reg_copy_dfs_chan_state(dst_chan, src_chan);
298389766727SVasanthakumar Thiagarajan 			}
298489766727SVasanthakumar Thiagarajan 		}
298589766727SVasanthakumar Thiagarajan 	}
298689766727SVasanthakumar Thiagarajan }
298789766727SVasanthakumar Thiagarajan 
wiphy_all_share_dfs_chan_state(struct wiphy * wiphy)298889766727SVasanthakumar Thiagarajan static void wiphy_all_share_dfs_chan_state(struct wiphy *wiphy)
298989766727SVasanthakumar Thiagarajan {
299089766727SVasanthakumar Thiagarajan 	struct cfg80211_registered_device *rdev;
299189766727SVasanthakumar Thiagarajan 
299289766727SVasanthakumar Thiagarajan 	ASSERT_RTNL();
299389766727SVasanthakumar Thiagarajan 
299489766727SVasanthakumar Thiagarajan 	list_for_each_entry(rdev, &cfg80211_rdev_list, list) {
299589766727SVasanthakumar Thiagarajan 		if (wiphy == &rdev->wiphy)
299689766727SVasanthakumar Thiagarajan 			continue;
299789766727SVasanthakumar Thiagarajan 		wiphy_share_dfs_chan_state(wiphy, &rdev->wiphy);
299889766727SVasanthakumar Thiagarajan 	}
299989766727SVasanthakumar Thiagarajan }
300089766727SVasanthakumar Thiagarajan 
300130a548c7SLuis R. Rodriguez /* This processes *all* regulatory hints */
reg_process_hint(struct regulatory_request * reg_request)30021daa37c7SLuis R. Rodriguez static void reg_process_hint(struct regulatory_request *reg_request)
3003fe33eb39SLuis R. Rodriguez {
3004fe33eb39SLuis R. Rodriguez 	struct wiphy *wiphy = NULL;
3005b3eb7f3fSLuis R. Rodriguez 	enum reg_request_treatment treatment;
30061db58529SYu Zhao 	enum nl80211_reg_initiator initiator = reg_request->initiator;
3007fe33eb39SLuis R. Rodriguez 
3008f4173766SJohannes Berg 	if (reg_request->wiphy_idx != WIPHY_IDX_INVALID)
3009fe33eb39SLuis R. Rodriguez 		wiphy = wiphy_idx_to_wiphy(reg_request->wiphy_idx);
3010fe33eb39SLuis R. Rodriguez 
30111db58529SYu Zhao 	switch (initiator) {
3012b3eb7f3fSLuis R. Rodriguez 	case NL80211_REGDOM_SET_BY_CORE:
3013d34265a3SJohannes Berg 		treatment = reg_process_hint_core(reg_request);
3014d34265a3SJohannes Berg 		break;
3015b3eb7f3fSLuis R. Rodriguez 	case NL80211_REGDOM_SET_BY_USER:
3016d34265a3SJohannes Berg 		treatment = reg_process_hint_user(reg_request);
3017d34265a3SJohannes Berg 		break;
3018b3eb7f3fSLuis R. Rodriguez 	case NL80211_REGDOM_SET_BY_DRIVER:
3019772f0389SIlan Peer 		if (!wiphy)
3020772f0389SIlan Peer 			goto out_free;
302121636c7fSLuis R. Rodriguez 		treatment = reg_process_hint_driver(wiphy, reg_request);
302221636c7fSLuis R. Rodriguez 		break;
3023b3eb7f3fSLuis R. Rodriguez 	case NL80211_REGDOM_SET_BY_COUNTRY_IE:
3024772f0389SIlan Peer 		if (!wiphy)
3025772f0389SIlan Peer 			goto out_free;
3026b23e7a9eSLuis R. Rodriguez 		treatment = reg_process_hint_country_ie(wiphy, reg_request);
3027b3eb7f3fSLuis R. Rodriguez 		break;
3028b3eb7f3fSLuis R. Rodriguez 	default:
30291db58529SYu Zhao 		WARN(1, "invalid initiator %d\n", initiator);
3030772f0389SIlan Peer 		goto out_free;
3031b3eb7f3fSLuis R. Rodriguez 	}
3032b3eb7f3fSLuis R. Rodriguez 
3033d34265a3SJohannes Berg 	if (treatment == REG_REQ_IGNORE)
3034d34265a3SJohannes Berg 		goto out_free;
3035d34265a3SJohannes Berg 
3036480908a7SJohannes Berg 	WARN(treatment != REG_REQ_OK && treatment != REG_REQ_ALREADY_SET,
3037480908a7SJohannes Berg 	     "unexpected treatment value %d\n", treatment);
3038480908a7SJohannes Berg 
3039841b351cSJohn Linville 	/* This is required so that the orig_* parameters are saved.
3040841b351cSJohn Linville 	 * NOTE: treatment must be set for any case that reaches here!
3041841b351cSJohn Linville 	 */
3042b23e7a9eSLuis R. Rodriguez 	if (treatment == REG_REQ_ALREADY_SET && wiphy &&
3043ad932f04SArik Nemtsov 	    wiphy->regulatory_flags & REGULATORY_STRICT_REG) {
30441db58529SYu Zhao 		wiphy_update_regulatory(wiphy, initiator);
304589766727SVasanthakumar Thiagarajan 		wiphy_all_share_dfs_chan_state(wiphy);
3046ad932f04SArik Nemtsov 		reg_check_channels();
3047ad932f04SArik Nemtsov 	}
3048772f0389SIlan Peer 
3049772f0389SIlan Peer 	return;
3050772f0389SIlan Peer 
3051772f0389SIlan Peer out_free:
3052c888393bSArik Nemtsov 	reg_free_request(reg_request);
3053fe33eb39SLuis R. Rodriguez }
3054fe33eb39SLuis R. Rodriguez 
notify_self_managed_wiphys(struct regulatory_request * request)3055aced43ceSAmar Singhal static void notify_self_managed_wiphys(struct regulatory_request *request)
3056aced43ceSAmar Singhal {
3057aced43ceSAmar Singhal 	struct cfg80211_registered_device *rdev;
3058aced43ceSAmar Singhal 	struct wiphy *wiphy;
3059aced43ceSAmar Singhal 
3060aced43ceSAmar Singhal 	list_for_each_entry(rdev, &cfg80211_rdev_list, list) {
3061aced43ceSAmar Singhal 		wiphy = &rdev->wiphy;
3062aced43ceSAmar Singhal 		if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED &&
3063c82c06ceSSriram R 		    request->initiator == NL80211_REGDOM_SET_BY_USER)
3064aced43ceSAmar Singhal 			reg_call_notifier(wiphy, request);
3065aced43ceSAmar Singhal 	}
3066aced43ceSAmar Singhal }
3067aced43ceSAmar Singhal 
3068b2e253cfSLuis R. Rodriguez /*
3069b2e253cfSLuis R. Rodriguez  * Processes regulatory hints, this is all the NL80211_REGDOM_SET_BY_*
3070b2e253cfSLuis R. Rodriguez  * Regulatory hints come on a first come first serve basis and we
3071b2e253cfSLuis R. Rodriguez  * must process each one atomically.
3072b2e253cfSLuis R. Rodriguez  */
reg_process_pending_hints(void)3073fe33eb39SLuis R. Rodriguez static void reg_process_pending_hints(void)
3074fe33eb39SLuis R. Rodriguez {
3075c492db37SJohannes Berg 	struct regulatory_request *reg_request, *lr;
3076fe33eb39SLuis R. Rodriguez 
3077c492db37SJohannes Berg 	lr = get_last_request();
3078b0e2880bSLuis R. Rodriguez 
3079b2e253cfSLuis R. Rodriguez 	/* When last_request->processed becomes true this will be rescheduled */
3080c492db37SJohannes Berg 	if (lr && !lr->processed) {
30810d31d4dbSHodaszi, Robert 		pr_debug("Pending regulatory request, waiting for it to be processed...\n");
30825fe231e8SJohannes Berg 		return;
3083b2e253cfSLuis R. Rodriguez 	}
3084b2e253cfSLuis R. Rodriguez 
3085fe33eb39SLuis R. Rodriguez 	spin_lock(&reg_requests_lock);
3086b2e253cfSLuis R. Rodriguez 
3087b2e253cfSLuis R. Rodriguez 	if (list_empty(&reg_requests_list)) {
3088b2e253cfSLuis R. Rodriguez 		spin_unlock(&reg_requests_lock);
30895fe231e8SJohannes Berg 		return;
3090b2e253cfSLuis R. Rodriguez 	}
3091b2e253cfSLuis R. Rodriguez 
3092fe33eb39SLuis R. Rodriguez 	reg_request = list_first_entry(&reg_requests_list,
3093fe33eb39SLuis R. Rodriguez 				       struct regulatory_request,
3094fe33eb39SLuis R. Rodriguez 				       list);
3095fe33eb39SLuis R. Rodriguez 	list_del_init(&reg_request->list);
3096fe33eb39SLuis R. Rodriguez 
3097d951c1ddSLuis R. Rodriguez 	spin_unlock(&reg_requests_lock);
3098b0e2880bSLuis R. Rodriguez 
3099aced43ceSAmar Singhal 	notify_self_managed_wiphys(reg_request);
3100ef51fb1dSArik Nemtsov 
31011daa37c7SLuis R. Rodriguez 	reg_process_hint(reg_request);
31022e54a689SBen 
31032e54a689SBen 	lr = get_last_request();
31042e54a689SBen 
31052e54a689SBen 	spin_lock(&reg_requests_lock);
31062e54a689SBen 	if (!list_empty(&reg_requests_list) && lr && lr->processed)
31072e54a689SBen 		schedule_work(&reg_work);
31082e54a689SBen 	spin_unlock(&reg_requests_lock);
3109fe33eb39SLuis R. Rodriguez }
3110fe33eb39SLuis R. Rodriguez 
3111e38f8a7aSLuis R. Rodriguez /* Processes beacon hints -- this has nothing to do with country IEs */
reg_process_pending_beacon_hints(void)3112e38f8a7aSLuis R. Rodriguez static void reg_process_pending_beacon_hints(void)
3113e38f8a7aSLuis R. Rodriguez {
311479c97e97SJohannes Berg 	struct cfg80211_registered_device *rdev;
3115e38f8a7aSLuis R. Rodriguez 	struct reg_beacon *pending_beacon, *tmp;
3116e38f8a7aSLuis R. Rodriguez 
3117e38f8a7aSLuis R. Rodriguez 	/* This goes through the _pending_ beacon list */
3118e38f8a7aSLuis R. Rodriguez 	spin_lock_bh(&reg_pending_beacons_lock);
3119e38f8a7aSLuis R. Rodriguez 
3120e38f8a7aSLuis R. Rodriguez 	list_for_each_entry_safe(pending_beacon, tmp,
3121e38f8a7aSLuis R. Rodriguez 				 &reg_pending_beacons, list) {
3122e38f8a7aSLuis R. Rodriguez 		list_del_init(&pending_beacon->list);
3123e38f8a7aSLuis R. Rodriguez 
3124e38f8a7aSLuis R. Rodriguez 		/* Applies the beacon hint to current wiphys */
312579c97e97SJohannes Berg 		list_for_each_entry(rdev, &cfg80211_rdev_list, list)
312679c97e97SJohannes Berg 			wiphy_update_new_beacon(&rdev->wiphy, pending_beacon);
3127e38f8a7aSLuis R. Rodriguez 
3128e38f8a7aSLuis R. Rodriguez 		/* Remembers the beacon hint for new wiphys or reg changes */
3129e38f8a7aSLuis R. Rodriguez 		list_add_tail(&pending_beacon->list, &reg_beacon_list);
3130e38f8a7aSLuis R. Rodriguez 	}
3131e38f8a7aSLuis R. Rodriguez 
3132e38f8a7aSLuis R. Rodriguez 	spin_unlock_bh(&reg_pending_beacons_lock);
3133e38f8a7aSLuis R. Rodriguez }
3134e38f8a7aSLuis R. Rodriguez 
reg_process_self_managed_hint(struct wiphy * wiphy)3135a05829a7SJohannes Berg static void reg_process_self_managed_hint(struct wiphy *wiphy)
3136b0d7aa59SJonathan Doron {
3137a05829a7SJohannes Berg 	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
3138b0d7aa59SJonathan Doron 	const struct ieee80211_regdomain *tmp;
3139b0d7aa59SJonathan Doron 	const struct ieee80211_regdomain *regd;
314057fbcce3SJohannes Berg 	enum nl80211_band band;
3141b0d7aa59SJonathan Doron 	struct regulatory_request request = {};
3142b0d7aa59SJonathan Doron 
3143a05829a7SJohannes Berg 	ASSERT_RTNL();
3144a05829a7SJohannes Berg 	lockdep_assert_wiphy(wiphy);
3145b0d7aa59SJonathan Doron 
3146b0d7aa59SJonathan Doron 	spin_lock(&reg_requests_lock);
3147b0d7aa59SJonathan Doron 	regd = rdev->requested_regd;
3148b0d7aa59SJonathan Doron 	rdev->requested_regd = NULL;
3149b0d7aa59SJonathan Doron 	spin_unlock(&reg_requests_lock);
3150b0d7aa59SJonathan Doron 
3151a05829a7SJohannes Berg 	if (!regd)
3152a05829a7SJohannes Berg 		return;
3153b0d7aa59SJonathan Doron 
3154b0d7aa59SJonathan Doron 	tmp = get_wiphy_regdom(wiphy);
3155b0d7aa59SJonathan Doron 	rcu_assign_pointer(wiphy->regd, regd);
3156b0d7aa59SJonathan Doron 	rcu_free_regdom(tmp);
3157b0d7aa59SJonathan Doron 
315857fbcce3SJohannes Berg 	for (band = 0; band < NUM_NL80211_BANDS; band++)
3159b0d7aa59SJonathan Doron 		handle_band_custom(wiphy, wiphy->bands[band], regd);
3160b0d7aa59SJonathan Doron 
3161b0d7aa59SJonathan Doron 	reg_process_ht_flags(wiphy);
3162b0d7aa59SJonathan Doron 
3163b0d7aa59SJonathan Doron 	request.wiphy_idx = get_wiphy_idx(wiphy);
3164b0d7aa59SJonathan Doron 	request.alpha2[0] = regd->alpha2[0];
3165b0d7aa59SJonathan Doron 	request.alpha2[1] = regd->alpha2[1];
3166b0d7aa59SJonathan Doron 	request.initiator = NL80211_REGDOM_SET_BY_DRIVER;
3167b0d7aa59SJonathan Doron 
3168d99975c4SWen Gong 	if (wiphy->flags & WIPHY_FLAG_NOTIFY_REGDOM_BY_DRIVER)
3169d99975c4SWen Gong 		reg_call_notifier(wiphy, &request);
3170d99975c4SWen Gong 
3171b0d7aa59SJonathan Doron 	nl80211_send_wiphy_reg_change_event(&request);
3172b0d7aa59SJonathan Doron }
3173b0d7aa59SJonathan Doron 
reg_process_self_managed_hints(void)3174a05829a7SJohannes Berg static void reg_process_self_managed_hints(void)
3175a05829a7SJohannes Berg {
3176a05829a7SJohannes Berg 	struct cfg80211_registered_device *rdev;
3177a05829a7SJohannes Berg 
3178a05829a7SJohannes Berg 	ASSERT_RTNL();
3179a05829a7SJohannes Berg 
3180a05829a7SJohannes Berg 	list_for_each_entry(rdev, &cfg80211_rdev_list, list) {
3181a05829a7SJohannes Berg 		wiphy_lock(&rdev->wiphy);
3182a05829a7SJohannes Berg 		reg_process_self_managed_hint(&rdev->wiphy);
3183a05829a7SJohannes Berg 		wiphy_unlock(&rdev->wiphy);
3184a05829a7SJohannes Berg 	}
3185a05829a7SJohannes Berg 
3186b0d7aa59SJonathan Doron 	reg_check_channels();
3187b0d7aa59SJonathan Doron }
3188b0d7aa59SJonathan Doron 
reg_todo(struct work_struct * work)3189fe33eb39SLuis R. Rodriguez static void reg_todo(struct work_struct *work)
3190fe33eb39SLuis R. Rodriguez {
31915fe231e8SJohannes Berg 	rtnl_lock();
3192fe33eb39SLuis R. Rodriguez 	reg_process_pending_hints();
3193e38f8a7aSLuis R. Rodriguez 	reg_process_pending_beacon_hints();
3194b0d7aa59SJonathan Doron 	reg_process_self_managed_hints();
31955fe231e8SJohannes Berg 	rtnl_unlock();
3196fe33eb39SLuis R. Rodriguez }
3197fe33eb39SLuis R. Rodriguez 
queue_regulatory_request(struct regulatory_request * request)3198fe33eb39SLuis R. Rodriguez static void queue_regulatory_request(struct regulatory_request *request)
3199fe33eb39SLuis R. Rodriguez {
3200c61029c7SJohn W. Linville 	request->alpha2[0] = toupper(request->alpha2[0]);
3201c61029c7SJohn W. Linville 	request->alpha2[1] = toupper(request->alpha2[1]);
3202c61029c7SJohn W. Linville 
3203fe33eb39SLuis R. Rodriguez 	spin_lock(&reg_requests_lock);
3204fe33eb39SLuis R. Rodriguez 	list_add_tail(&request->list, &reg_requests_list);
3205fe33eb39SLuis R. Rodriguez 	spin_unlock(&reg_requests_lock);
3206fe33eb39SLuis R. Rodriguez 
3207fe33eb39SLuis R. Rodriguez 	schedule_work(&reg_work);
3208fe33eb39SLuis R. Rodriguez }
3209fe33eb39SLuis R. Rodriguez 
321009d989d1SLuis R. Rodriguez /*
321109d989d1SLuis R. Rodriguez  * Core regulatory hint -- happens during cfg80211_init()
321209d989d1SLuis R. Rodriguez  * and when we restore regulatory settings.
321309d989d1SLuis R. Rodriguez  */
regulatory_hint_core(const char * alpha2)3214ba25c141SLuis R. Rodriguez static int regulatory_hint_core(const char *alpha2)
3215ba25c141SLuis R. Rodriguez {
3216ba25c141SLuis R. Rodriguez 	struct regulatory_request *request;
3217ba25c141SLuis R. Rodriguez 
32181a919318SJohannes Berg 	request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL);
3219ba25c141SLuis R. Rodriguez 	if (!request)
3220ba25c141SLuis R. Rodriguez 		return -ENOMEM;
3221ba25c141SLuis R. Rodriguez 
3222ba25c141SLuis R. Rodriguez 	request->alpha2[0] = alpha2[0];
3223ba25c141SLuis R. Rodriguez 	request->alpha2[1] = alpha2[1];
32247db90f4aSLuis R. Rodriguez 	request->initiator = NL80211_REGDOM_SET_BY_CORE;
322524f33e64SAndrei Otcheretianski 	request->wiphy_idx = WIPHY_IDX_INVALID;
3226ba25c141SLuis R. Rodriguez 
322731e99729SLuis R. Rodriguez 	queue_regulatory_request(request);
32285078b2e3SLuis R. Rodriguez 
3229fe33eb39SLuis R. Rodriguez 	return 0;
3230ba25c141SLuis R. Rodriguez }
3231ba25c141SLuis R. Rodriguez 
3232fe33eb39SLuis R. Rodriguez /* User hints */
regulatory_hint_user(const char * alpha2,enum nl80211_user_reg_hint_type user_reg_hint_type)323357b5ce07SLuis R. Rodriguez int regulatory_hint_user(const char *alpha2,
323457b5ce07SLuis R. Rodriguez 			 enum nl80211_user_reg_hint_type user_reg_hint_type)
3235b2e1b302SLuis R. Rodriguez {
3236fe33eb39SLuis R. Rodriguez 	struct regulatory_request *request;
3237fe33eb39SLuis R. Rodriguez 
3238fdc9d7b2SJohannes Berg 	if (WARN_ON(!alpha2))
3239fdc9d7b2SJohannes Berg 		return -EINVAL;
3240b2e1b302SLuis R. Rodriguez 
324147caf685SJohannes Berg 	if (!is_world_regdom(alpha2) && !is_an_alpha2(alpha2))
324247caf685SJohannes Berg 		return -EINVAL;
324347caf685SJohannes Berg 
3244fe33eb39SLuis R. Rodriguez 	request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL);
3245fe33eb39SLuis R. Rodriguez 	if (!request)
3246fe33eb39SLuis R. Rodriguez 		return -ENOMEM;
3247fe33eb39SLuis R. Rodriguez 
3248f4173766SJohannes Berg 	request->wiphy_idx = WIPHY_IDX_INVALID;
3249fe33eb39SLuis R. Rodriguez 	request->alpha2[0] = alpha2[0];
3250fe33eb39SLuis R. Rodriguez 	request->alpha2[1] = alpha2[1];
3251e12822e1SLuis R. Rodriguez 	request->initiator = NL80211_REGDOM_SET_BY_USER;
325257b5ce07SLuis R. Rodriguez 	request->user_reg_hint_type = user_reg_hint_type;
3253fe33eb39SLuis R. Rodriguez 
3254c37722bdSIlan peer 	/* Allow calling CRDA again */
3255b6863036SJohannes Berg 	reset_crda_timeouts();
3256c37722bdSIlan peer 
3257fe33eb39SLuis R. Rodriguez 	queue_regulatory_request(request);
3258fe33eb39SLuis R. Rodriguez 
3259fe33eb39SLuis R. Rodriguez 	return 0;
3260fe33eb39SLuis R. Rodriguez }
3261fe33eb39SLuis R. Rodriguez 
regulatory_hint_indoor(bool is_indoor,u32 portid)326205050753SIlan peer int regulatory_hint_indoor(bool is_indoor, u32 portid)
326352616f2bSIlan Peer {
326405050753SIlan peer 	spin_lock(&reg_indoor_lock);
326552616f2bSIlan Peer 
326605050753SIlan peer 	/* It is possible that more than one user space process is trying to
326705050753SIlan peer 	 * configure the indoor setting. To handle such cases, clear the indoor
326805050753SIlan peer 	 * setting in case that some process does not think that the device
326905050753SIlan peer 	 * is operating in an indoor environment. In addition, if a user space
327005050753SIlan peer 	 * process indicates that it is controlling the indoor setting, save its
327105050753SIlan peer 	 * portid, i.e., make it the owner.
327205050753SIlan peer 	 */
327305050753SIlan peer 	reg_is_indoor = is_indoor;
327405050753SIlan peer 	if (reg_is_indoor) {
327505050753SIlan peer 		if (!reg_is_indoor_portid)
327605050753SIlan peer 			reg_is_indoor_portid = portid;
327705050753SIlan peer 	} else {
327805050753SIlan peer 		reg_is_indoor_portid = 0;
327905050753SIlan peer 	}
328052616f2bSIlan Peer 
328105050753SIlan peer 	spin_unlock(&reg_indoor_lock);
328205050753SIlan peer 
328305050753SIlan peer 	if (!is_indoor)
328405050753SIlan peer 		reg_check_channels();
328552616f2bSIlan Peer 
328652616f2bSIlan Peer 	return 0;
328752616f2bSIlan Peer }
328852616f2bSIlan Peer 
regulatory_netlink_notify(u32 portid)328905050753SIlan peer void regulatory_netlink_notify(u32 portid)
329005050753SIlan peer {
329105050753SIlan peer 	spin_lock(&reg_indoor_lock);
329205050753SIlan peer 
329305050753SIlan peer 	if (reg_is_indoor_portid != portid) {
329405050753SIlan peer 		spin_unlock(&reg_indoor_lock);
329505050753SIlan peer 		return;
329605050753SIlan peer 	}
329705050753SIlan peer 
329805050753SIlan peer 	reg_is_indoor = false;
329905050753SIlan peer 	reg_is_indoor_portid = 0;
330005050753SIlan peer 
330105050753SIlan peer 	spin_unlock(&reg_indoor_lock);
330205050753SIlan peer 
330305050753SIlan peer 	reg_check_channels();
330405050753SIlan peer }
330505050753SIlan peer 
3306fe33eb39SLuis R. Rodriguez /* Driver hints */
regulatory_hint(struct wiphy * wiphy,const char * alpha2)3307fe33eb39SLuis R. Rodriguez int regulatory_hint(struct wiphy *wiphy, const char *alpha2)
3308fe33eb39SLuis R. Rodriguez {
3309fe33eb39SLuis R. Rodriguez 	struct regulatory_request *request;
3310fe33eb39SLuis R. Rodriguez 
3311fdc9d7b2SJohannes Berg 	if (WARN_ON(!alpha2 || !wiphy))
3312fdc9d7b2SJohannes Berg 		return -EINVAL;
3313fe33eb39SLuis R. Rodriguez 
33144f7b9140SLuis R. Rodriguez 	wiphy->regulatory_flags &= ~REGULATORY_CUSTOM_REG;
33154f7b9140SLuis R. Rodriguez 
3316fe33eb39SLuis R. Rodriguez 	request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL);
3317fe33eb39SLuis R. Rodriguez 	if (!request)
3318fe33eb39SLuis R. Rodriguez 		return -ENOMEM;
3319fe33eb39SLuis R. Rodriguez 
3320fe33eb39SLuis R. Rodriguez 	request->wiphy_idx = get_wiphy_idx(wiphy);
3321fe33eb39SLuis R. Rodriguez 
3322fe33eb39SLuis R. Rodriguez 	request->alpha2[0] = alpha2[0];
3323fe33eb39SLuis R. Rodriguez 	request->alpha2[1] = alpha2[1];
33247db90f4aSLuis R. Rodriguez 	request->initiator = NL80211_REGDOM_SET_BY_DRIVER;
3325fe33eb39SLuis R. Rodriguez 
3326c37722bdSIlan peer 	/* Allow calling CRDA again */
3327b6863036SJohannes Berg 	reset_crda_timeouts();
3328c37722bdSIlan peer 
3329fe33eb39SLuis R. Rodriguez 	queue_regulatory_request(request);
3330fe33eb39SLuis R. Rodriguez 
3331fe33eb39SLuis R. Rodriguez 	return 0;
3332b2e1b302SLuis R. Rodriguez }
3333b2e1b302SLuis R. Rodriguez EXPORT_SYMBOL(regulatory_hint);
3334b2e1b302SLuis R. Rodriguez 
regulatory_hint_country_ie(struct wiphy * wiphy,enum nl80211_band band,const u8 * country_ie,u8 country_ie_len)333557fbcce3SJohannes Berg void regulatory_hint_country_ie(struct wiphy *wiphy, enum nl80211_band band,
33361a919318SJohannes Berg 				const u8 *country_ie, u8 country_ie_len)
33373f2355cbSLuis R. Rodriguez {
33383f2355cbSLuis R. Rodriguez 	char alpha2[2];
33393f2355cbSLuis R. Rodriguez 	enum environment_cap env = ENVIRON_ANY;
3340db2424c5SJohannes Berg 	struct regulatory_request *request = NULL, *lr;
3341d335fe63SLuis R. Rodriguez 
33423f2355cbSLuis R. Rodriguez 	/* IE len must be evenly divisible by 2 */
33433f2355cbSLuis R. Rodriguez 	if (country_ie_len & 0x01)
3344db2424c5SJohannes Berg 		return;
33453f2355cbSLuis R. Rodriguez 
33463f2355cbSLuis R. Rodriguez 	if (country_ie_len < IEEE80211_COUNTRY_IE_MIN_LEN)
3347db2424c5SJohannes Berg 		return;
3348db2424c5SJohannes Berg 
3349db2424c5SJohannes Berg 	request = kzalloc(sizeof(*request), GFP_KERNEL);
3350db2424c5SJohannes Berg 	if (!request)
3351db2424c5SJohannes Berg 		return;
33523f2355cbSLuis R. Rodriguez 
33533f2355cbSLuis R. Rodriguez 	alpha2[0] = country_ie[0];
33543f2355cbSLuis R. Rodriguez 	alpha2[1] = country_ie[1];
33553f2355cbSLuis R. Rodriguez 
33563f2355cbSLuis R. Rodriguez 	if (country_ie[2] == 'I')
33573f2355cbSLuis R. Rodriguez 		env = ENVIRON_INDOOR;
33583f2355cbSLuis R. Rodriguez 	else if (country_ie[2] == 'O')
33593f2355cbSLuis R. Rodriguez 		env = ENVIRON_OUTDOOR;
33603f2355cbSLuis R. Rodriguez 
3361db2424c5SJohannes Berg 	rcu_read_lock();
3362db2424c5SJohannes Berg 	lr = get_last_request();
3363db2424c5SJohannes Berg 
3364db2424c5SJohannes Berg 	if (unlikely(!lr))
3365db2424c5SJohannes Berg 		goto out;
3366db2424c5SJohannes Berg 
3367fb1fc7adSLuis R. Rodriguez 	/*
33688b19e6caSLuis R. Rodriguez 	 * We will run this only upon a successful connection on cfg80211.
33694b44c8bcSLuis R. Rodriguez 	 * We leave conflict resolution to the workqueue, where can hold
33705fe231e8SJohannes Berg 	 * the RTNL.
3371fb1fc7adSLuis R. Rodriguez 	 */
3372c492db37SJohannes Berg 	if (lr->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE &&
3373c492db37SJohannes Berg 	    lr->wiphy_idx != WIPHY_IDX_INVALID)
33743f2355cbSLuis R. Rodriguez 		goto out;
33753f2355cbSLuis R. Rodriguez 
3376fe33eb39SLuis R. Rodriguez 	request->wiphy_idx = get_wiphy_idx(wiphy);
33774f366c5dSJohn W. Linville 	request->alpha2[0] = alpha2[0];
33784f366c5dSJohn W. Linville 	request->alpha2[1] = alpha2[1];
33797db90f4aSLuis R. Rodriguez 	request->initiator = NL80211_REGDOM_SET_BY_COUNTRY_IE;
3380fe33eb39SLuis R. Rodriguez 	request->country_ie_env = env;
33813f2355cbSLuis R. Rodriguez 
3382c37722bdSIlan peer 	/* Allow calling CRDA again */
3383b6863036SJohannes Berg 	reset_crda_timeouts();
3384c37722bdSIlan peer 
3385fe33eb39SLuis R. Rodriguez 	queue_regulatory_request(request);
3386db2424c5SJohannes Berg 	request = NULL;
33873f2355cbSLuis R. Rodriguez out:
3388db2424c5SJohannes Berg 	kfree(request);
3389db2424c5SJohannes Berg 	rcu_read_unlock();
33903f2355cbSLuis R. Rodriguez }
3391b2e1b302SLuis R. Rodriguez 
restore_alpha2(char * alpha2,bool reset_user)339209d989d1SLuis R. Rodriguez static void restore_alpha2(char *alpha2, bool reset_user)
339309d989d1SLuis R. Rodriguez {
339409d989d1SLuis R. Rodriguez 	/* indicates there is no alpha2 to consider for restoration */
339509d989d1SLuis R. Rodriguez 	alpha2[0] = '9';
339609d989d1SLuis R. Rodriguez 	alpha2[1] = '7';
339709d989d1SLuis R. Rodriguez 
339809d989d1SLuis R. Rodriguez 	/* The user setting has precedence over the module parameter */
339909d989d1SLuis R. Rodriguez 	if (is_user_regdom_saved()) {
340009d989d1SLuis R. Rodriguez 		/* Unless we're asked to ignore it and reset it */
340109d989d1SLuis R. Rodriguez 		if (reset_user) {
3402c799ba6eSJohannes Berg 			pr_debug("Restoring regulatory settings including user preference\n");
340309d989d1SLuis R. Rodriguez 			user_alpha2[0] = '9';
340409d989d1SLuis R. Rodriguez 			user_alpha2[1] = '7';
340509d989d1SLuis R. Rodriguez 
340609d989d1SLuis R. Rodriguez 			/*
340709d989d1SLuis R. Rodriguez 			 * If we're ignoring user settings, we still need to
340809d989d1SLuis R. Rodriguez 			 * check the module parameter to ensure we put things
340909d989d1SLuis R. Rodriguez 			 * back as they were for a full restore.
341009d989d1SLuis R. Rodriguez 			 */
341109d989d1SLuis R. Rodriguez 			if (!is_world_regdom(ieee80211_regdom)) {
3412c799ba6eSJohannes Berg 				pr_debug("Keeping preference on module parameter ieee80211_regdom: %c%c\n",
34131a919318SJohannes Berg 					 ieee80211_regdom[0], ieee80211_regdom[1]);
341409d989d1SLuis R. Rodriguez 				alpha2[0] = ieee80211_regdom[0];
341509d989d1SLuis R. Rodriguez 				alpha2[1] = ieee80211_regdom[1];
341609d989d1SLuis R. Rodriguez 			}
341709d989d1SLuis R. Rodriguez 		} else {
3418c799ba6eSJohannes Berg 			pr_debug("Restoring regulatory settings while preserving user preference for: %c%c\n",
34191a919318SJohannes Berg 				 user_alpha2[0], user_alpha2[1]);
342009d989d1SLuis R. Rodriguez 			alpha2[0] = user_alpha2[0];
342109d989d1SLuis R. Rodriguez 			alpha2[1] = user_alpha2[1];
342209d989d1SLuis R. Rodriguez 		}
342309d989d1SLuis R. Rodriguez 	} else if (!is_world_regdom(ieee80211_regdom)) {
3424c799ba6eSJohannes Berg 		pr_debug("Keeping preference on module parameter ieee80211_regdom: %c%c\n",
34251a919318SJohannes Berg 			 ieee80211_regdom[0], ieee80211_regdom[1]);
342609d989d1SLuis R. Rodriguez 		alpha2[0] = ieee80211_regdom[0];
342709d989d1SLuis R. Rodriguez 		alpha2[1] = ieee80211_regdom[1];
342809d989d1SLuis R. Rodriguez 	} else
3429c799ba6eSJohannes Berg 		pr_debug("Restoring regulatory settings\n");
343009d989d1SLuis R. Rodriguez }
343109d989d1SLuis R. Rodriguez 
restore_custom_reg_settings(struct wiphy * wiphy)34325ce543d1SRajkumar Manoharan static void restore_custom_reg_settings(struct wiphy *wiphy)
34335ce543d1SRajkumar Manoharan {
34345ce543d1SRajkumar Manoharan 	struct ieee80211_supported_band *sband;
343557fbcce3SJohannes Berg 	enum nl80211_band band;
34365ce543d1SRajkumar Manoharan 	struct ieee80211_channel *chan;
34375ce543d1SRajkumar Manoharan 	int i;
34385ce543d1SRajkumar Manoharan 
343957fbcce3SJohannes Berg 	for (band = 0; band < NUM_NL80211_BANDS; band++) {
34405ce543d1SRajkumar Manoharan 		sband = wiphy->bands[band];
34415ce543d1SRajkumar Manoharan 		if (!sband)
34425ce543d1SRajkumar Manoharan 			continue;
34435ce543d1SRajkumar Manoharan 		for (i = 0; i < sband->n_channels; i++) {
34445ce543d1SRajkumar Manoharan 			chan = &sband->channels[i];
34455ce543d1SRajkumar Manoharan 			chan->flags = chan->orig_flags;
34465ce543d1SRajkumar Manoharan 			chan->max_antenna_gain = chan->orig_mag;
34475ce543d1SRajkumar Manoharan 			chan->max_power = chan->orig_mpwr;
3448899852afSPaul Stewart 			chan->beacon_found = false;
34495ce543d1SRajkumar Manoharan 		}
34505ce543d1SRajkumar Manoharan 	}
34515ce543d1SRajkumar Manoharan }
34525ce543d1SRajkumar Manoharan 
345309d989d1SLuis R. Rodriguez /*
3454f2e30931SBhaskar Chowdhury  * Restoring regulatory settings involves ignoring any
345509d989d1SLuis R. Rodriguez  * possibly stale country IE information and user regulatory
345609d989d1SLuis R. Rodriguez  * settings if so desired, this includes any beacon hints
345709d989d1SLuis R. Rodriguez  * learned as we could have traveled outside to another country
345809d989d1SLuis R. Rodriguez  * after disconnection. To restore regulatory settings we do
345909d989d1SLuis R. Rodriguez  * exactly what we did at bootup:
346009d989d1SLuis R. Rodriguez  *
346109d989d1SLuis R. Rodriguez  *   - send a core regulatory hint
346209d989d1SLuis R. Rodriguez  *   - send a user regulatory hint if applicable
346309d989d1SLuis R. Rodriguez  *
346409d989d1SLuis R. Rodriguez  * Device drivers that send a regulatory hint for a specific country
3465cc5a639bSRandy Dunlap  * keep their own regulatory domain on wiphy->regd so that does
346609d989d1SLuis R. Rodriguez  * not need to be remembered.
346709d989d1SLuis R. Rodriguez  */
restore_regulatory_settings(bool reset_user,bool cached)3468e646a025SJohannes Berg static void restore_regulatory_settings(bool reset_user, bool cached)
346909d989d1SLuis R. Rodriguez {
347009d989d1SLuis R. Rodriguez 	char alpha2[2];
3471cee0bec5SDmitry Shmidt 	char world_alpha2[2];
347209d989d1SLuis R. Rodriguez 	struct reg_beacon *reg_beacon, *btmp;
347314609555SLuis R. Rodriguez 	LIST_HEAD(tmp_reg_req_list);
34745ce543d1SRajkumar Manoharan 	struct cfg80211_registered_device *rdev;
347509d989d1SLuis R. Rodriguez 
34765fe231e8SJohannes Berg 	ASSERT_RTNL();
34775fe231e8SJohannes Berg 
347805050753SIlan peer 	/*
347905050753SIlan peer 	 * Clear the indoor setting in case that it is not controlled by user
348005050753SIlan peer 	 * space, as otherwise there is no guarantee that the device is still
348105050753SIlan peer 	 * operating in an indoor environment.
348205050753SIlan peer 	 */
348305050753SIlan peer 	spin_lock(&reg_indoor_lock);
348405050753SIlan peer 	if (reg_is_indoor && !reg_is_indoor_portid) {
348552616f2bSIlan Peer 		reg_is_indoor = false;
348605050753SIlan peer 		reg_check_channels();
348705050753SIlan peer 	}
348805050753SIlan peer 	spin_unlock(&reg_indoor_lock);
348952616f2bSIlan Peer 
34902d319867SJohannes Berg 	reset_regdomains(true, &world_regdom);
349109d989d1SLuis R. Rodriguez 	restore_alpha2(alpha2, reset_user);
349209d989d1SLuis R. Rodriguez 
349314609555SLuis R. Rodriguez 	/*
349414609555SLuis R. Rodriguez 	 * If there's any pending requests we simply
349514609555SLuis R. Rodriguez 	 * stash them to a temporary pending queue and
349614609555SLuis R. Rodriguez 	 * add then after we've restored regulatory
349714609555SLuis R. Rodriguez 	 * settings.
349814609555SLuis R. Rodriguez 	 */
349914609555SLuis R. Rodriguez 	spin_lock(&reg_requests_lock);
3500eeca9fceSIlan peer 	list_splice_tail_init(&reg_requests_list, &tmp_reg_req_list);
350114609555SLuis R. Rodriguez 	spin_unlock(&reg_requests_lock);
350214609555SLuis R. Rodriguez 
350309d989d1SLuis R. Rodriguez 	/* Clear beacon hints */
350409d989d1SLuis R. Rodriguez 	spin_lock_bh(&reg_pending_beacons_lock);
3505fea9bcedSJohannes Berg 	list_for_each_entry_safe(reg_beacon, btmp, &reg_pending_beacons, list) {
350609d989d1SLuis R. Rodriguez 		list_del(&reg_beacon->list);
350709d989d1SLuis R. Rodriguez 		kfree(reg_beacon);
350809d989d1SLuis R. Rodriguez 	}
350909d989d1SLuis R. Rodriguez 	spin_unlock_bh(&reg_pending_beacons_lock);
351009d989d1SLuis R. Rodriguez 
3511fea9bcedSJohannes Berg 	list_for_each_entry_safe(reg_beacon, btmp, &reg_beacon_list, list) {
351209d989d1SLuis R. Rodriguez 		list_del(&reg_beacon->list);
351309d989d1SLuis R. Rodriguez 		kfree(reg_beacon);
351409d989d1SLuis R. Rodriguez 	}
351509d989d1SLuis R. Rodriguez 
351609d989d1SLuis R. Rodriguez 	/* First restore to the basic regulatory settings */
3517379b82f4SJohannes Berg 	world_alpha2[0] = cfg80211_world_regdom->alpha2[0];
3518379b82f4SJohannes Berg 	world_alpha2[1] = cfg80211_world_regdom->alpha2[1];
351909d989d1SLuis R. Rodriguez 
35205ce543d1SRajkumar Manoharan 	list_for_each_entry(rdev, &cfg80211_rdev_list, list) {
3521b0d7aa59SJonathan Doron 		if (rdev->wiphy.regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED)
3522b0d7aa59SJonathan Doron 			continue;
3523a2f73b6cSLuis R. Rodriguez 		if (rdev->wiphy.regulatory_flags & REGULATORY_CUSTOM_REG)
35245ce543d1SRajkumar Manoharan 			restore_custom_reg_settings(&rdev->wiphy);
35255ce543d1SRajkumar Manoharan 	}
35265ce543d1SRajkumar Manoharan 
3527e646a025SJohannes Berg 	if (cached && (!is_an_alpha2(alpha2) ||
3528e646a025SJohannes Berg 		       !IS_ERR_OR_NULL(cfg80211_user_regdom))) {
3529e646a025SJohannes Berg 		reset_regdomains(false, cfg80211_world_regdom);
3530e646a025SJohannes Berg 		update_all_wiphy_regulatory(NL80211_REGDOM_SET_BY_CORE);
3531e646a025SJohannes Berg 		print_regdomain(get_cfg80211_regdom());
3532e646a025SJohannes Berg 		nl80211_send_reg_change_event(&core_request_world);
3533e646a025SJohannes Berg 		reg_set_request_processed();
3534e646a025SJohannes Berg 
3535e646a025SJohannes Berg 		if (is_an_alpha2(alpha2) &&
3536e646a025SJohannes Berg 		    !regulatory_hint_user(alpha2, NL80211_USER_REG_HINT_USER)) {
3537e646a025SJohannes Berg 			struct regulatory_request *ureq;
3538e646a025SJohannes Berg 
3539e646a025SJohannes Berg 			spin_lock(&reg_requests_lock);
3540e646a025SJohannes Berg 			ureq = list_last_entry(&reg_requests_list,
3541e646a025SJohannes Berg 					       struct regulatory_request,
3542e646a025SJohannes Berg 					       list);
3543e646a025SJohannes Berg 			list_del(&ureq->list);
3544e646a025SJohannes Berg 			spin_unlock(&reg_requests_lock);
3545e646a025SJohannes Berg 
3546e646a025SJohannes Berg 			notify_self_managed_wiphys(ureq);
3547e646a025SJohannes Berg 			reg_update_last_request(ureq);
3548e646a025SJohannes Berg 			set_regdom(reg_copy_regd(cfg80211_user_regdom),
3549e646a025SJohannes Berg 				   REGD_SOURCE_CACHED);
3550e646a025SJohannes Berg 		}
3551e646a025SJohannes Berg 	} else {
3552cee0bec5SDmitry Shmidt 		regulatory_hint_core(world_alpha2);
355309d989d1SLuis R. Rodriguez 
355409d989d1SLuis R. Rodriguez 		/*
355509d989d1SLuis R. Rodriguez 		 * This restores the ieee80211_regdom module parameter
355609d989d1SLuis R. Rodriguez 		 * preference or the last user requested regulatory
355709d989d1SLuis R. Rodriguez 		 * settings, user regulatory settings takes precedence.
355809d989d1SLuis R. Rodriguez 		 */
355909d989d1SLuis R. Rodriguez 		if (is_an_alpha2(alpha2))
3560549cc1c5SMaciej S. Szmigiero 			regulatory_hint_user(alpha2, NL80211_USER_REG_HINT_USER);
3561e646a025SJohannes Berg 	}
356209d989d1SLuis R. Rodriguez 
356314609555SLuis R. Rodriguez 	spin_lock(&reg_requests_lock);
356411cff96cSJohannes Berg 	list_splice_tail_init(&tmp_reg_req_list, &reg_requests_list);
356514609555SLuis R. Rodriguez 	spin_unlock(&reg_requests_lock);
356614609555SLuis R. Rodriguez 
3567c799ba6eSJohannes Berg 	pr_debug("Kicking the queue\n");
356814609555SLuis R. Rodriguez 
356914609555SLuis R. Rodriguez 	schedule_work(&reg_work);
357014609555SLuis R. Rodriguez }
357109d989d1SLuis R. Rodriguez 
is_wiphy_all_set_reg_flag(enum ieee80211_regulatory_flags flag)35727417844bSRajeev Kumar Sirasanagandla static bool is_wiphy_all_set_reg_flag(enum ieee80211_regulatory_flags flag)
35737417844bSRajeev Kumar Sirasanagandla {
35747417844bSRajeev Kumar Sirasanagandla 	struct cfg80211_registered_device *rdev;
35757417844bSRajeev Kumar Sirasanagandla 	struct wireless_dev *wdev;
35767417844bSRajeev Kumar Sirasanagandla 
35777417844bSRajeev Kumar Sirasanagandla 	list_for_each_entry(rdev, &cfg80211_rdev_list, list) {
35787417844bSRajeev Kumar Sirasanagandla 		list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
35797417844bSRajeev Kumar Sirasanagandla 			wdev_lock(wdev);
35807417844bSRajeev Kumar Sirasanagandla 			if (!(wdev->wiphy->regulatory_flags & flag)) {
35817417844bSRajeev Kumar Sirasanagandla 				wdev_unlock(wdev);
35827417844bSRajeev Kumar Sirasanagandla 				return false;
35837417844bSRajeev Kumar Sirasanagandla 			}
35847417844bSRajeev Kumar Sirasanagandla 			wdev_unlock(wdev);
35857417844bSRajeev Kumar Sirasanagandla 		}
35867417844bSRajeev Kumar Sirasanagandla 	}
35877417844bSRajeev Kumar Sirasanagandla 
35887417844bSRajeev Kumar Sirasanagandla 	return true;
35897417844bSRajeev Kumar Sirasanagandla }
35907417844bSRajeev Kumar Sirasanagandla 
regulatory_hint_disconnect(void)359109d989d1SLuis R. Rodriguez void regulatory_hint_disconnect(void)
359209d989d1SLuis R. Rodriguez {
35937417844bSRajeev Kumar Sirasanagandla 	/* Restore of regulatory settings is not required when wiphy(s)
35947417844bSRajeev Kumar Sirasanagandla 	 * ignore IE from connected access point but clearance of beacon hints
35957417844bSRajeev Kumar Sirasanagandla 	 * is required when wiphy(s) supports beacon hints.
35967417844bSRajeev Kumar Sirasanagandla 	 */
35977417844bSRajeev Kumar Sirasanagandla 	if (is_wiphy_all_set_reg_flag(REGULATORY_COUNTRY_IE_IGNORE)) {
35987417844bSRajeev Kumar Sirasanagandla 		struct reg_beacon *reg_beacon, *btmp;
35997417844bSRajeev Kumar Sirasanagandla 
36007417844bSRajeev Kumar Sirasanagandla 		if (is_wiphy_all_set_reg_flag(REGULATORY_DISABLE_BEACON_HINTS))
36017417844bSRajeev Kumar Sirasanagandla 			return;
36027417844bSRajeev Kumar Sirasanagandla 
36037417844bSRajeev Kumar Sirasanagandla 		spin_lock_bh(&reg_pending_beacons_lock);
36047417844bSRajeev Kumar Sirasanagandla 		list_for_each_entry_safe(reg_beacon, btmp,
36057417844bSRajeev Kumar Sirasanagandla 					 &reg_pending_beacons, list) {
36067417844bSRajeev Kumar Sirasanagandla 			list_del(&reg_beacon->list);
36077417844bSRajeev Kumar Sirasanagandla 			kfree(reg_beacon);
36087417844bSRajeev Kumar Sirasanagandla 		}
36097417844bSRajeev Kumar Sirasanagandla 		spin_unlock_bh(&reg_pending_beacons_lock);
36107417844bSRajeev Kumar Sirasanagandla 
36117417844bSRajeev Kumar Sirasanagandla 		list_for_each_entry_safe(reg_beacon, btmp,
36127417844bSRajeev Kumar Sirasanagandla 					 &reg_beacon_list, list) {
36137417844bSRajeev Kumar Sirasanagandla 			list_del(&reg_beacon->list);
36147417844bSRajeev Kumar Sirasanagandla 			kfree(reg_beacon);
36157417844bSRajeev Kumar Sirasanagandla 		}
36167417844bSRajeev Kumar Sirasanagandla 
36177417844bSRajeev Kumar Sirasanagandla 		return;
36187417844bSRajeev Kumar Sirasanagandla 	}
36197417844bSRajeev Kumar Sirasanagandla 
3620c799ba6eSJohannes Berg 	pr_debug("All devices are disconnected, going to restore regulatory settings\n");
3621e646a025SJohannes Berg 	restore_regulatory_settings(false, true);
362209d989d1SLuis R. Rodriguez }
362309d989d1SLuis R. Rodriguez 
freq_is_chan_12_13_14(u32 freq)36249cf0a0b4SAlexei Avshalom Lazar static bool freq_is_chan_12_13_14(u32 freq)
3625e38f8a7aSLuis R. Rodriguez {
362657fbcce3SJohannes Berg 	if (freq == ieee80211_channel_to_frequency(12, NL80211_BAND_2GHZ) ||
362757fbcce3SJohannes Berg 	    freq == ieee80211_channel_to_frequency(13, NL80211_BAND_2GHZ) ||
362857fbcce3SJohannes Berg 	    freq == ieee80211_channel_to_frequency(14, NL80211_BAND_2GHZ))
3629e38f8a7aSLuis R. Rodriguez 		return true;
3630e38f8a7aSLuis R. Rodriguez 	return false;
3631e38f8a7aSLuis R. Rodriguez }
3632e38f8a7aSLuis R. Rodriguez 
pending_reg_beacon(struct ieee80211_channel * beacon_chan)36333ebfa6e7SLuis R. Rodriguez static bool pending_reg_beacon(struct ieee80211_channel *beacon_chan)
36343ebfa6e7SLuis R. Rodriguez {
36353ebfa6e7SLuis R. Rodriguez 	struct reg_beacon *pending_beacon;
36363ebfa6e7SLuis R. Rodriguez 
36373ebfa6e7SLuis R. Rodriguez 	list_for_each_entry(pending_beacon, &reg_pending_beacons, list)
3638934f4c7dSThomas Pedersen 		if (ieee80211_channel_equal(beacon_chan,
3639934f4c7dSThomas Pedersen 					    &pending_beacon->chan))
36403ebfa6e7SLuis R. Rodriguez 			return true;
36413ebfa6e7SLuis R. Rodriguez 	return false;
36423ebfa6e7SLuis R. Rodriguez }
36433ebfa6e7SLuis R. Rodriguez 
regulatory_hint_found_beacon(struct wiphy * wiphy,struct ieee80211_channel * beacon_chan,gfp_t gfp)3644e38f8a7aSLuis R. Rodriguez int regulatory_hint_found_beacon(struct wiphy *wiphy,
3645e38f8a7aSLuis R. Rodriguez 				 struct ieee80211_channel *beacon_chan,
3646e38f8a7aSLuis R. Rodriguez 				 gfp_t gfp)
3647e38f8a7aSLuis R. Rodriguez {
3648e38f8a7aSLuis R. Rodriguez 	struct reg_beacon *reg_beacon;
36493ebfa6e7SLuis R. Rodriguez 	bool processing;
3650e38f8a7aSLuis R. Rodriguez 
36511a919318SJohannes Berg 	if (beacon_chan->beacon_found ||
36521a919318SJohannes Berg 	    beacon_chan->flags & IEEE80211_CHAN_RADAR ||
365357fbcce3SJohannes Berg 	    (beacon_chan->band == NL80211_BAND_2GHZ &&
36541a919318SJohannes Berg 	     !freq_is_chan_12_13_14(beacon_chan->center_freq)))
3655e38f8a7aSLuis R. Rodriguez 		return 0;
3656e38f8a7aSLuis R. Rodriguez 
36573ebfa6e7SLuis R. Rodriguez 	spin_lock_bh(&reg_pending_beacons_lock);
36583ebfa6e7SLuis R. Rodriguez 	processing = pending_reg_beacon(beacon_chan);
36593ebfa6e7SLuis R. Rodriguez 	spin_unlock_bh(&reg_pending_beacons_lock);
36603ebfa6e7SLuis R. Rodriguez 
36613ebfa6e7SLuis R. Rodriguez 	if (processing)
3662e38f8a7aSLuis R. Rodriguez 		return 0;
3663e38f8a7aSLuis R. Rodriguez 
3664e38f8a7aSLuis R. Rodriguez 	reg_beacon = kzalloc(sizeof(struct reg_beacon), gfp);
3665e38f8a7aSLuis R. Rodriguez 	if (!reg_beacon)
3666e38f8a7aSLuis R. Rodriguez 		return -ENOMEM;
3667e38f8a7aSLuis R. Rodriguez 
3668934f4c7dSThomas Pedersen 	pr_debug("Found new beacon on frequency: %d.%03d MHz (Ch %d) on %s\n",
3669934f4c7dSThomas Pedersen 		 beacon_chan->center_freq, beacon_chan->freq_offset,
3670934f4c7dSThomas Pedersen 		 ieee80211_freq_khz_to_channel(
3671934f4c7dSThomas Pedersen 			 ieee80211_channel_to_khz(beacon_chan)),
3672e38f8a7aSLuis R. Rodriguez 		 wiphy_name(wiphy));
36734113f751SLuis R. Rodriguez 
3674e38f8a7aSLuis R. Rodriguez 	memcpy(&reg_beacon->chan, beacon_chan,
3675e38f8a7aSLuis R. Rodriguez 	       sizeof(struct ieee80211_channel));
3676e38f8a7aSLuis R. Rodriguez 
3677e38f8a7aSLuis R. Rodriguez 	/*
3678e38f8a7aSLuis R. Rodriguez 	 * Since we can be called from BH or and non-BH context
3679e38f8a7aSLuis R. Rodriguez 	 * we must use spin_lock_bh()
3680e38f8a7aSLuis R. Rodriguez 	 */
3681e38f8a7aSLuis R. Rodriguez 	spin_lock_bh(&reg_pending_beacons_lock);
3682e38f8a7aSLuis R. Rodriguez 	list_add_tail(&reg_beacon->list, &reg_pending_beacons);
3683e38f8a7aSLuis R. Rodriguez 	spin_unlock_bh(&reg_pending_beacons_lock);
3684e38f8a7aSLuis R. Rodriguez 
3685e38f8a7aSLuis R. Rodriguez 	schedule_work(&reg_work);
3686e38f8a7aSLuis R. Rodriguez 
3687e38f8a7aSLuis R. Rodriguez 	return 0;
3688e38f8a7aSLuis R. Rodriguez }
3689e38f8a7aSLuis R. Rodriguez 
print_rd_rules(const struct ieee80211_regdomain * rd)3690a3d2eaf0SJohannes Berg static void print_rd_rules(const struct ieee80211_regdomain *rd)
3691b2e1b302SLuis R. Rodriguez {
3692b2e1b302SLuis R. Rodriguez 	unsigned int i;
3693a3d2eaf0SJohannes Berg 	const struct ieee80211_reg_rule *reg_rule = NULL;
3694a3d2eaf0SJohannes Berg 	const struct ieee80211_freq_range *freq_range = NULL;
3695a3d2eaf0SJohannes Berg 	const struct ieee80211_power_rule *power_rule = NULL;
3696089027e5SJanusz Dziedzic 	char bw[32], cac_time[32];
3697b2e1b302SLuis R. Rodriguez 
369894c4fd64SDave Young 	pr_debug("  (start_freq - end_freq @ bandwidth), (max_antenna_gain, max_eirp), (dfs_cac_time)\n");
3699b2e1b302SLuis R. Rodriguez 
3700b2e1b302SLuis R. Rodriguez 	for (i = 0; i < rd->n_reg_rules; i++) {
3701b2e1b302SLuis R. Rodriguez 		reg_rule = &rd->reg_rules[i];
3702b2e1b302SLuis R. Rodriguez 		freq_range = &reg_rule->freq_range;
3703b2e1b302SLuis R. Rodriguez 		power_rule = &reg_rule->power_rule;
3704b2e1b302SLuis R. Rodriguez 
3705b0dfd2eaSJanusz Dziedzic 		if (reg_rule->flags & NL80211_RRF_AUTO_BW)
3706db18d20dSYe Bin 			snprintf(bw, sizeof(bw), "%d KHz, %u KHz AUTO",
3707b0dfd2eaSJanusz Dziedzic 				 freq_range->max_bandwidth_khz,
370897524820SJanusz Dziedzic 				 reg_get_max_bandwidth(rd, reg_rule));
370997524820SJanusz Dziedzic 		else
3710b0dfd2eaSJanusz Dziedzic 			snprintf(bw, sizeof(bw), "%d KHz",
371197524820SJanusz Dziedzic 				 freq_range->max_bandwidth_khz);
371297524820SJanusz Dziedzic 
3713089027e5SJanusz Dziedzic 		if (reg_rule->flags & NL80211_RRF_DFS)
3714089027e5SJanusz Dziedzic 			scnprintf(cac_time, sizeof(cac_time), "%u s",
3715089027e5SJanusz Dziedzic 				  reg_rule->dfs_cac_ms/1000);
3716089027e5SJanusz Dziedzic 		else
3717089027e5SJanusz Dziedzic 			scnprintf(cac_time, sizeof(cac_time), "N/A");
3718089027e5SJanusz Dziedzic 
3719089027e5SJanusz Dziedzic 
3720fb1fc7adSLuis R. Rodriguez 		/*
3721fb1fc7adSLuis R. Rodriguez 		 * There may not be documentation for max antenna gain
3722fb1fc7adSLuis R. Rodriguez 		 * in certain regions
3723fb1fc7adSLuis R. Rodriguez 		 */
3724b2e1b302SLuis R. Rodriguez 		if (power_rule->max_antenna_gain)
372594c4fd64SDave Young 			pr_debug("  (%d KHz - %d KHz @ %s), (%d mBi, %d mBm), (%s)\n",
3726b2e1b302SLuis R. Rodriguez 				freq_range->start_freq_khz,
3727b2e1b302SLuis R. Rodriguez 				freq_range->end_freq_khz,
372897524820SJanusz Dziedzic 				bw,
3729b2e1b302SLuis R. Rodriguez 				power_rule->max_antenna_gain,
3730089027e5SJanusz Dziedzic 				power_rule->max_eirp,
3731089027e5SJanusz Dziedzic 				cac_time);
3732b2e1b302SLuis R. Rodriguez 		else
373394c4fd64SDave Young 			pr_debug("  (%d KHz - %d KHz @ %s), (N/A, %d mBm), (%s)\n",
3734b2e1b302SLuis R. Rodriguez 				freq_range->start_freq_khz,
3735b2e1b302SLuis R. Rodriguez 				freq_range->end_freq_khz,
373697524820SJanusz Dziedzic 				bw,
3737089027e5SJanusz Dziedzic 				power_rule->max_eirp,
3738089027e5SJanusz Dziedzic 				cac_time);
3739b2e1b302SLuis R. Rodriguez 	}
3740b2e1b302SLuis R. Rodriguez }
3741b2e1b302SLuis R. Rodriguez 
reg_supported_dfs_region(enum nl80211_dfs_regions dfs_region)37424c7d3982SLuis R. Rodriguez bool reg_supported_dfs_region(enum nl80211_dfs_regions dfs_region)
37438b60b078SLuis R. Rodriguez {
37448b60b078SLuis R. Rodriguez 	switch (dfs_region) {
37458b60b078SLuis R. Rodriguez 	case NL80211_DFS_UNSET:
37468b60b078SLuis R. Rodriguez 	case NL80211_DFS_FCC:
37478b60b078SLuis R. Rodriguez 	case NL80211_DFS_ETSI:
37488b60b078SLuis R. Rodriguez 	case NL80211_DFS_JP:
37498b60b078SLuis R. Rodriguez 		return true;
37508b60b078SLuis R. Rodriguez 	default:
37514a22b00bSColin Ian King 		pr_debug("Ignoring unknown DFS master region: %d\n", dfs_region);
37528b60b078SLuis R. Rodriguez 		return false;
37538b60b078SLuis R. Rodriguez 	}
37548b60b078SLuis R. Rodriguez }
37558b60b078SLuis R. Rodriguez 
print_regdomain(const struct ieee80211_regdomain * rd)3756a3d2eaf0SJohannes Berg static void print_regdomain(const struct ieee80211_regdomain *rd)
3757b2e1b302SLuis R. Rodriguez {
3758c492db37SJohannes Berg 	struct regulatory_request *lr = get_last_request();
3759b2e1b302SLuis R. Rodriguez 
37603f2355cbSLuis R. Rodriguez 	if (is_intersected_alpha2(rd->alpha2)) {
3761c492db37SJohannes Berg 		if (lr->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE) {
376279c97e97SJohannes Berg 			struct cfg80211_registered_device *rdev;
3763c492db37SJohannes Berg 			rdev = cfg80211_rdev_by_wiphy_idx(lr->wiphy_idx);
376479c97e97SJohannes Berg 			if (rdev) {
376594c4fd64SDave Young 				pr_debug("Current regulatory domain updated by AP to: %c%c\n",
376679c97e97SJohannes Berg 					rdev->country_ie_alpha2[0],
376779c97e97SJohannes Berg 					rdev->country_ie_alpha2[1]);
37683f2355cbSLuis R. Rodriguez 			} else
376994c4fd64SDave Young 				pr_debug("Current regulatory domain intersected:\n");
37703f2355cbSLuis R. Rodriguez 		} else
377194c4fd64SDave Young 			pr_debug("Current regulatory domain intersected:\n");
37721a919318SJohannes Berg 	} else if (is_world_regdom(rd->alpha2)) {
377394c4fd64SDave Young 		pr_debug("World regulatory domain updated:\n");
37741a919318SJohannes Berg 	} else {
3775b2e1b302SLuis R. Rodriguez 		if (is_unknown_alpha2(rd->alpha2))
377694c4fd64SDave Young 			pr_debug("Regulatory domain changed to driver built-in settings (unknown country)\n");
377757b5ce07SLuis R. Rodriguez 		else {
3778c492db37SJohannes Berg 			if (reg_request_cell_base(lr))
377994c4fd64SDave Young 				pr_debug("Regulatory domain changed to country: %c%c by Cell Station\n",
3780b2e1b302SLuis R. Rodriguez 					rd->alpha2[0], rd->alpha2[1]);
378157b5ce07SLuis R. Rodriguez 			else
378294c4fd64SDave Young 				pr_debug("Regulatory domain changed to country: %c%c\n",
378357b5ce07SLuis R. Rodriguez 					rd->alpha2[0], rd->alpha2[1]);
378457b5ce07SLuis R. Rodriguez 		}
3785b2e1b302SLuis R. Rodriguez 	}
37861a919318SJohannes Berg 
378794c4fd64SDave Young 	pr_debug(" DFS Master region: %s", reg_dfs_region_str(rd->dfs_region));
3788b2e1b302SLuis R. Rodriguez 	print_rd_rules(rd);
3789b2e1b302SLuis R. Rodriguez }
3790b2e1b302SLuis R. Rodriguez 
print_regdomain_info(const struct ieee80211_regdomain * rd)37912df78167SJohannes Berg static void print_regdomain_info(const struct ieee80211_regdomain *rd)
3792b2e1b302SLuis R. Rodriguez {
379394c4fd64SDave Young 	pr_debug("Regulatory domain: %c%c\n", rd->alpha2[0], rd->alpha2[1]);
3794b2e1b302SLuis R. Rodriguez 	print_rd_rules(rd);
3795b2e1b302SLuis R. Rodriguez }
3796b2e1b302SLuis R. Rodriguez 
reg_set_rd_core(const struct ieee80211_regdomain * rd)37973b9e5acaSLuis R. Rodriguez static int reg_set_rd_core(const struct ieee80211_regdomain *rd)
37983b9e5acaSLuis R. Rodriguez {
37993b9e5acaSLuis R. Rodriguez 	if (!is_world_regdom(rd->alpha2))
38003b9e5acaSLuis R. Rodriguez 		return -EINVAL;
38013b9e5acaSLuis R. Rodriguez 	update_world_regdomain(rd);
38023b9e5acaSLuis R. Rodriguez 	return 0;
38033b9e5acaSLuis R. Rodriguez }
38043b9e5acaSLuis R. Rodriguez 
reg_set_rd_user(const struct ieee80211_regdomain * rd,struct regulatory_request * user_request)380584721d44SLuis R. Rodriguez static int reg_set_rd_user(const struct ieee80211_regdomain *rd,
380684721d44SLuis R. Rodriguez 			   struct regulatory_request *user_request)
380784721d44SLuis R. Rodriguez {
380884721d44SLuis R. Rodriguez 	const struct ieee80211_regdomain *intersected_rd = NULL;
380984721d44SLuis R. Rodriguez 
381084721d44SLuis R. Rodriguez 	if (!regdom_changes(rd->alpha2))
381184721d44SLuis R. Rodriguez 		return -EALREADY;
381284721d44SLuis R. Rodriguez 
381384721d44SLuis R. Rodriguez 	if (!is_valid_rd(rd)) {
381494c4fd64SDave Young 		pr_err("Invalid regulatory domain detected: %c%c\n",
381594c4fd64SDave Young 		       rd->alpha2[0], rd->alpha2[1]);
381684721d44SLuis R. Rodriguez 		print_regdomain_info(rd);
381784721d44SLuis R. Rodriguez 		return -EINVAL;
381884721d44SLuis R. Rodriguez 	}
381984721d44SLuis R. Rodriguez 
382084721d44SLuis R. Rodriguez 	if (!user_request->intersect) {
382184721d44SLuis R. Rodriguez 		reset_regdomains(false, rd);
382284721d44SLuis R. Rodriguez 		return 0;
382384721d44SLuis R. Rodriguez 	}
382484721d44SLuis R. Rodriguez 
382584721d44SLuis R. Rodriguez 	intersected_rd = regdom_intersect(rd, get_cfg80211_regdom());
382684721d44SLuis R. Rodriguez 	if (!intersected_rd)
382784721d44SLuis R. Rodriguez 		return -EINVAL;
382884721d44SLuis R. Rodriguez 
382984721d44SLuis R. Rodriguez 	kfree(rd);
383084721d44SLuis R. Rodriguez 	rd = NULL;
383184721d44SLuis R. Rodriguez 	reset_regdomains(false, intersected_rd);
383284721d44SLuis R. Rodriguez 
383384721d44SLuis R. Rodriguez 	return 0;
383484721d44SLuis R. Rodriguez }
383584721d44SLuis R. Rodriguez 
reg_set_rd_driver(const struct ieee80211_regdomain * rd,struct regulatory_request * driver_request)3836f5fe3247SLuis R. Rodriguez static int reg_set_rd_driver(const struct ieee80211_regdomain *rd,
3837f5fe3247SLuis R. Rodriguez 			     struct regulatory_request *driver_request)
3838b2e1b302SLuis R. Rodriguez {
3839e9763c3cSJohannes Berg 	const struct ieee80211_regdomain *regd;
38409c96477dSLuis R. Rodriguez 	const struct ieee80211_regdomain *intersected_rd = NULL;
3841f5fe3247SLuis R. Rodriguez 	const struct ieee80211_regdomain *tmp;
3842806a9e39SLuis R. Rodriguez 	struct wiphy *request_wiphy;
38436913b49aSJohannes Berg 
3844f5fe3247SLuis R. Rodriguez 	if (is_world_regdom(rd->alpha2))
3845b2e1b302SLuis R. Rodriguez 		return -EINVAL;
3846b2e1b302SLuis R. Rodriguez 
3847baeb66feSJohn W. Linville 	if (!regdom_changes(rd->alpha2))
384895908535SKalle Valo 		return -EALREADY;
3849b2e1b302SLuis R. Rodriguez 
3850b2e1b302SLuis R. Rodriguez 	if (!is_valid_rd(rd)) {
385194c4fd64SDave Young 		pr_err("Invalid regulatory domain detected: %c%c\n",
385294c4fd64SDave Young 		       rd->alpha2[0], rd->alpha2[1]);
3853b2e1b302SLuis R. Rodriguez 		print_regdomain_info(rd);
3854b2e1b302SLuis R. Rodriguez 		return -EINVAL;
3855b2e1b302SLuis R. Rodriguez 	}
3856b2e1b302SLuis R. Rodriguez 
3857f5fe3247SLuis R. Rodriguez 	request_wiphy = wiphy_idx_to_wiphy(driver_request->wiphy_idx);
3858922ec58cSJohannes Berg 	if (!request_wiphy)
3859de3584bdSJohannes Berg 		return -ENODEV;
3860806a9e39SLuis R. Rodriguez 
3861f5fe3247SLuis R. Rodriguez 	if (!driver_request->intersect) {
3862a05829a7SJohannes Berg 		ASSERT_RTNL();
3863a05829a7SJohannes Berg 		wiphy_lock(request_wiphy);
3864a05829a7SJohannes Berg 		if (request_wiphy->regd) {
3865a05829a7SJohannes Berg 			wiphy_unlock(request_wiphy);
3866558f6d32SLuis R. Rodriguez 			return -EALREADY;
3867a05829a7SJohannes Berg 		}
38683e0c3ff3SLuis R. Rodriguez 
3869e9763c3cSJohannes Berg 		regd = reg_copy_regd(rd);
3870a05829a7SJohannes Berg 		if (IS_ERR(regd)) {
3871a05829a7SJohannes Berg 			wiphy_unlock(request_wiphy);
3872e9763c3cSJohannes Berg 			return PTR_ERR(regd);
3873a05829a7SJohannes Berg 		}
38743e0c3ff3SLuis R. Rodriguez 
3875458f4f9eSJohannes Berg 		rcu_assign_pointer(request_wiphy->regd, regd);
3876a05829a7SJohannes Berg 		wiphy_unlock(request_wiphy);
3877379b82f4SJohannes Berg 		reset_regdomains(false, rd);
3878b8295acdSLuis R. Rodriguez 		return 0;
3879b8295acdSLuis R. Rodriguez 	}
3880b8295acdSLuis R. Rodriguez 
3881458f4f9eSJohannes Berg 	intersected_rd = regdom_intersect(rd, get_cfg80211_regdom());
38829c96477dSLuis R. Rodriguez 	if (!intersected_rd)
38839c96477dSLuis R. Rodriguez 		return -EINVAL;
3884b8295acdSLuis R. Rodriguez 
3885fb1fc7adSLuis R. Rodriguez 	/*
3886fb1fc7adSLuis R. Rodriguez 	 * We can trash what CRDA provided now.
38873e0c3ff3SLuis R. Rodriguez 	 * However if a driver requested this specific regulatory
3888fb1fc7adSLuis R. Rodriguez 	 * domain we keep it for its private use
3889fb1fc7adSLuis R. Rodriguez 	 */
3890b7566fc3SLarry Finger 	tmp = get_wiphy_regdom(request_wiphy);
3891458f4f9eSJohannes Berg 	rcu_assign_pointer(request_wiphy->regd, rd);
3892b7566fc3SLarry Finger 	rcu_free_regdom(tmp);
38933e0c3ff3SLuis R. Rodriguez 
3894b8295acdSLuis R. Rodriguez 	rd = NULL;
3895b8295acdSLuis R. Rodriguez 
3896379b82f4SJohannes Berg 	reset_regdomains(false, intersected_rd);
3897b8295acdSLuis R. Rodriguez 
3898b8295acdSLuis R. Rodriguez 	return 0;
38999c96477dSLuis R. Rodriguez }
39009c96477dSLuis R. Rodriguez 
reg_set_rd_country_ie(const struct ieee80211_regdomain * rd,struct regulatory_request * country_ie_request)390101992406SLuis R. Rodriguez static int reg_set_rd_country_ie(const struct ieee80211_regdomain *rd,
390201992406SLuis R. Rodriguez 				 struct regulatory_request *country_ie_request)
3903f5fe3247SLuis R. Rodriguez {
3904f5fe3247SLuis R. Rodriguez 	struct wiphy *request_wiphy;
3905f5fe3247SLuis R. Rodriguez 
3906f5fe3247SLuis R. Rodriguez 	if (!is_alpha2_set(rd->alpha2) && !is_an_alpha2(rd->alpha2) &&
3907f5fe3247SLuis R. Rodriguez 	    !is_unknown_alpha2(rd->alpha2))
3908f5fe3247SLuis R. Rodriguez 		return -EINVAL;
3909f5fe3247SLuis R. Rodriguez 
3910f5fe3247SLuis R. Rodriguez 	/*
3911f5fe3247SLuis R. Rodriguez 	 * Lets only bother proceeding on the same alpha2 if the current
3912f5fe3247SLuis R. Rodriguez 	 * rd is non static (it means CRDA was present and was used last)
3913f5fe3247SLuis R. Rodriguez 	 * and the pending request came in from a country IE
3914f5fe3247SLuis R. Rodriguez 	 */
3915f5fe3247SLuis R. Rodriguez 
3916f5fe3247SLuis R. Rodriguez 	if (!is_valid_rd(rd)) {
391794c4fd64SDave Young 		pr_err("Invalid regulatory domain detected: %c%c\n",
391894c4fd64SDave Young 		       rd->alpha2[0], rd->alpha2[1]);
3919f5fe3247SLuis R. Rodriguez 		print_regdomain_info(rd);
39203f2355cbSLuis R. Rodriguez 		return -EINVAL;
3921b2e1b302SLuis R. Rodriguez 	}
3922b2e1b302SLuis R. Rodriguez 
392301992406SLuis R. Rodriguez 	request_wiphy = wiphy_idx_to_wiphy(country_ie_request->wiphy_idx);
3924922ec58cSJohannes Berg 	if (!request_wiphy)
3925f5fe3247SLuis R. Rodriguez 		return -ENODEV;
3926f5fe3247SLuis R. Rodriguez 
392701992406SLuis R. Rodriguez 	if (country_ie_request->intersect)
3928f5fe3247SLuis R. Rodriguez 		return -EINVAL;
3929f5fe3247SLuis R. Rodriguez 
3930f5fe3247SLuis R. Rodriguez 	reset_regdomains(false, rd);
3931f5fe3247SLuis R. Rodriguez 	return 0;
3932f5fe3247SLuis R. Rodriguez }
3933b2e1b302SLuis R. Rodriguez 
3934fb1fc7adSLuis R. Rodriguez /*
3935fb1fc7adSLuis R. Rodriguez  * Use this call to set the current regulatory domain. Conflicts with
3936b2e1b302SLuis R. Rodriguez  * multiple drivers can be ironed out later. Caller must've already
3937458f4f9eSJohannes Berg  * kmalloc'd the rd structure.
3938fb1fc7adSLuis R. Rodriguez  */
set_regdom(const struct ieee80211_regdomain * rd,enum ieee80211_regd_source regd_src)3939c37722bdSIlan peer int set_regdom(const struct ieee80211_regdomain *rd,
3940c37722bdSIlan peer 	       enum ieee80211_regd_source regd_src)
3941b2e1b302SLuis R. Rodriguez {
3942c492db37SJohannes Berg 	struct regulatory_request *lr;
3943092008abSJanusz Dziedzic 	bool user_reset = false;
3944b2e1b302SLuis R. Rodriguez 	int r;
3945b2e1b302SLuis R. Rodriguez 
3946e646a025SJohannes Berg 	if (IS_ERR_OR_NULL(rd))
3947e646a025SJohannes Berg 		return -ENODATA;
3948e646a025SJohannes Berg 
39493b9e5acaSLuis R. Rodriguez 	if (!reg_is_valid_request(rd->alpha2)) {
39503b9e5acaSLuis R. Rodriguez 		kfree(rd);
39513b9e5acaSLuis R. Rodriguez 		return -EINVAL;
39523b9e5acaSLuis R. Rodriguez 	}
39533b9e5acaSLuis R. Rodriguez 
3954c37722bdSIlan peer 	if (regd_src == REGD_SOURCE_CRDA)
3955b6863036SJohannes Berg 		reset_crda_timeouts();
3956c37722bdSIlan peer 
3957c492db37SJohannes Berg 	lr = get_last_request();
3958abc7381bSLuis R. Rodriguez 
3959b2e1b302SLuis R. Rodriguez 	/* Note that this doesn't update the wiphys, this is done below */
39603b9e5acaSLuis R. Rodriguez 	switch (lr->initiator) {
39613b9e5acaSLuis R. Rodriguez 	case NL80211_REGDOM_SET_BY_CORE:
39623b9e5acaSLuis R. Rodriguez 		r = reg_set_rd_core(rd);
39633b9e5acaSLuis R. Rodriguez 		break;
39643b9e5acaSLuis R. Rodriguez 	case NL80211_REGDOM_SET_BY_USER:
3965e646a025SJohannes Berg 		cfg80211_save_user_regdom(rd);
396684721d44SLuis R. Rodriguez 		r = reg_set_rd_user(rd, lr);
3967092008abSJanusz Dziedzic 		user_reset = true;
396884721d44SLuis R. Rodriguez 		break;
39693b9e5acaSLuis R. Rodriguez 	case NL80211_REGDOM_SET_BY_DRIVER:
3970f5fe3247SLuis R. Rodriguez 		r = reg_set_rd_driver(rd, lr);
3971f5fe3247SLuis R. Rodriguez 		break;
39723b9e5acaSLuis R. Rodriguez 	case NL80211_REGDOM_SET_BY_COUNTRY_IE:
397301992406SLuis R. Rodriguez 		r = reg_set_rd_country_ie(rd, lr);
39743b9e5acaSLuis R. Rodriguez 		break;
39753b9e5acaSLuis R. Rodriguez 	default:
39763b9e5acaSLuis R. Rodriguez 		WARN(1, "invalid initiator %d\n", lr->initiator);
397709d11800SOla Olsson 		kfree(rd);
39783b9e5acaSLuis R. Rodriguez 		return -EINVAL;
39793b9e5acaSLuis R. Rodriguez 	}
39803b9e5acaSLuis R. Rodriguez 
3981d2372b31SJohannes Berg 	if (r) {
3982092008abSJanusz Dziedzic 		switch (r) {
3983092008abSJanusz Dziedzic 		case -EALREADY:
398495908535SKalle Valo 			reg_set_request_processed();
3985092008abSJanusz Dziedzic 			break;
3986092008abSJanusz Dziedzic 		default:
3987092008abSJanusz Dziedzic 			/* Back to world regulatory in case of errors */
3988e646a025SJohannes Berg 			restore_regulatory_settings(user_reset, false);
3989092008abSJanusz Dziedzic 		}
399095908535SKalle Valo 
3991d2372b31SJohannes Berg 		kfree(rd);
399238fd2143SJohannes Berg 		return r;
3993d2372b31SJohannes Berg 	}
3994b2e1b302SLuis R. Rodriguez 
3995b2e1b302SLuis R. Rodriguez 	/* This would make this whole thing pointless */
399638fd2143SJohannes Berg 	if (WARN_ON(!lr->intersect && rd != get_cfg80211_regdom()))
399738fd2143SJohannes Berg 		return -EINVAL;
3998b2e1b302SLuis R. Rodriguez 
3999b2e1b302SLuis R. Rodriguez 	/* update all wiphys now with the new established regulatory domain */
4000c492db37SJohannes Berg 	update_all_wiphy_regulatory(lr->initiator);
4001b2e1b302SLuis R. Rodriguez 
4002458f4f9eSJohannes Berg 	print_regdomain(get_cfg80211_regdom());
4003b2e1b302SLuis R. Rodriguez 
4004c492db37SJohannes Berg 	nl80211_send_reg_change_event(lr);
400573d54c9eSLuis R. Rodriguez 
4006b2e253cfSLuis R. Rodriguez 	reg_set_request_processed();
4007b2e253cfSLuis R. Rodriguez 
400838fd2143SJohannes Berg 	return 0;
4009b2e1b302SLuis R. Rodriguez }
4010b2e1b302SLuis R. Rodriguez 
__regulatory_set_wiphy_regd(struct wiphy * wiphy,struct ieee80211_regdomain * rd)40112c3e861cSArik Nemtsov static int __regulatory_set_wiphy_regd(struct wiphy *wiphy,
4012b0d7aa59SJonathan Doron 				       struct ieee80211_regdomain *rd)
4013b0d7aa59SJonathan Doron {
4014b0d7aa59SJonathan Doron 	const struct ieee80211_regdomain *regd;
4015b0d7aa59SJonathan Doron 	const struct ieee80211_regdomain *prev_regd;
4016b0d7aa59SJonathan Doron 	struct cfg80211_registered_device *rdev;
4017b0d7aa59SJonathan Doron 
4018b0d7aa59SJonathan Doron 	if (WARN_ON(!wiphy || !rd))
4019b0d7aa59SJonathan Doron 		return -EINVAL;
4020b0d7aa59SJonathan Doron 
4021b0d7aa59SJonathan Doron 	if (WARN(!(wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED),
4022b0d7aa59SJonathan Doron 		 "wiphy should have REGULATORY_WIPHY_SELF_MANAGED\n"))
4023b0d7aa59SJonathan Doron 		return -EPERM;
4024b0d7aa59SJonathan Doron 
4025b767ecdaSJohannes Berg 	if (WARN(!is_valid_rd(rd),
4026b767ecdaSJohannes Berg 		 "Invalid regulatory domain detected: %c%c\n",
4027b767ecdaSJohannes Berg 		 rd->alpha2[0], rd->alpha2[1])) {
4028b0d7aa59SJonathan Doron 		print_regdomain_info(rd);
4029b0d7aa59SJonathan Doron 		return -EINVAL;
4030b0d7aa59SJonathan Doron 	}
4031b0d7aa59SJonathan Doron 
4032b0d7aa59SJonathan Doron 	regd = reg_copy_regd(rd);
4033b0d7aa59SJonathan Doron 	if (IS_ERR(regd))
4034b0d7aa59SJonathan Doron 		return PTR_ERR(regd);
4035b0d7aa59SJonathan Doron 
4036b0d7aa59SJonathan Doron 	rdev = wiphy_to_rdev(wiphy);
4037b0d7aa59SJonathan Doron 
4038b0d7aa59SJonathan Doron 	spin_lock(&reg_requests_lock);
4039b0d7aa59SJonathan Doron 	prev_regd = rdev->requested_regd;
4040b0d7aa59SJonathan Doron 	rdev->requested_regd = regd;
4041b0d7aa59SJonathan Doron 	spin_unlock(&reg_requests_lock);
4042b0d7aa59SJonathan Doron 
4043b0d7aa59SJonathan Doron 	kfree(prev_regd);
40442c3e861cSArik Nemtsov 	return 0;
40452c3e861cSArik Nemtsov }
40462c3e861cSArik Nemtsov 
regulatory_set_wiphy_regd(struct wiphy * wiphy,struct ieee80211_regdomain * rd)40472c3e861cSArik Nemtsov int regulatory_set_wiphy_regd(struct wiphy *wiphy,
40482c3e861cSArik Nemtsov 			      struct ieee80211_regdomain *rd)
40492c3e861cSArik Nemtsov {
40502c3e861cSArik Nemtsov 	int ret = __regulatory_set_wiphy_regd(wiphy, rd);
40512c3e861cSArik Nemtsov 
40522c3e861cSArik Nemtsov 	if (ret)
40532c3e861cSArik Nemtsov 		return ret;
4054b0d7aa59SJonathan Doron 
4055b0d7aa59SJonathan Doron 	schedule_work(&reg_work);
4056b0d7aa59SJonathan Doron 	return 0;
4057b0d7aa59SJonathan Doron }
4058b0d7aa59SJonathan Doron EXPORT_SYMBOL(regulatory_set_wiphy_regd);
4059b0d7aa59SJonathan Doron 
regulatory_set_wiphy_regd_sync(struct wiphy * wiphy,struct ieee80211_regdomain * rd)4060a05829a7SJohannes Berg int regulatory_set_wiphy_regd_sync(struct wiphy *wiphy,
40612c3e861cSArik Nemtsov 				   struct ieee80211_regdomain *rd)
40622c3e861cSArik Nemtsov {
40632c3e861cSArik Nemtsov 	int ret;
40642c3e861cSArik Nemtsov 
40652c3e861cSArik Nemtsov 	ASSERT_RTNL();
40662c3e861cSArik Nemtsov 
40672c3e861cSArik Nemtsov 	ret = __regulatory_set_wiphy_regd(wiphy, rd);
40682c3e861cSArik Nemtsov 	if (ret)
40692c3e861cSArik Nemtsov 		return ret;
40702c3e861cSArik Nemtsov 
40712c3e861cSArik Nemtsov 	/* process the request immediately */
4072a05829a7SJohannes Berg 	reg_process_self_managed_hint(wiphy);
4073a05829a7SJohannes Berg 	reg_check_channels();
40742c3e861cSArik Nemtsov 	return 0;
40752c3e861cSArik Nemtsov }
4076a05829a7SJohannes Berg EXPORT_SYMBOL(regulatory_set_wiphy_regd_sync);
40772c3e861cSArik Nemtsov 
wiphy_regulatory_register(struct wiphy * wiphy)407857b5ce07SLuis R. Rodriguez void wiphy_regulatory_register(struct wiphy *wiphy)
407957b5ce07SLuis R. Rodriguez {
4080aced43ceSAmar Singhal 	struct regulatory_request *lr = get_last_request();
408123df0b73SArik Nemtsov 
4082aced43ceSAmar Singhal 	/* self-managed devices ignore beacon hints and country IE */
4083aced43ceSAmar Singhal 	if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED) {
4084b0d7aa59SJonathan Doron 		wiphy->regulatory_flags |= REGULATORY_DISABLE_BEACON_HINTS |
4085b0d7aa59SJonathan Doron 					   REGULATORY_COUNTRY_IE_IGNORE;
4086b0d7aa59SJonathan Doron 
4087aced43ceSAmar Singhal 		/*
4088aced43ceSAmar Singhal 		 * The last request may have been received before this
4089aced43ceSAmar Singhal 		 * registration call. Call the driver notifier if
40908772eed9SSriram R 		 * initiator is USER.
4091aced43ceSAmar Singhal 		 */
40928772eed9SSriram R 		if (lr->initiator == NL80211_REGDOM_SET_BY_USER)
4093aced43ceSAmar Singhal 			reg_call_notifier(wiphy, lr);
4094aced43ceSAmar Singhal 	}
4095aced43ceSAmar Singhal 
409657b5ce07SLuis R. Rodriguez 	if (!reg_dev_ignore_cell_hint(wiphy))
409757b5ce07SLuis R. Rodriguez 		reg_num_devs_support_basehint++;
409857b5ce07SLuis R. Rodriguez 
409923df0b73SArik Nemtsov 	wiphy_update_regulatory(wiphy, lr->initiator);
410089766727SVasanthakumar Thiagarajan 	wiphy_all_share_dfs_chan_state(wiphy);
41011b7b3ac8SMiri Korenblit 	reg_process_self_managed_hints();
410257b5ce07SLuis R. Rodriguez }
410357b5ce07SLuis R. Rodriguez 
wiphy_regulatory_deregister(struct wiphy * wiphy)4104bfead080SLuis R. Rodriguez void wiphy_regulatory_deregister(struct wiphy *wiphy)
41053f2355cbSLuis R. Rodriguez {
41060ad8acafSLuis R. Rodriguez 	struct wiphy *request_wiphy = NULL;
4107c492db37SJohannes Berg 	struct regulatory_request *lr;
4108761cf7ecSLuis R. Rodriguez 
4109c492db37SJohannes Berg 	lr = get_last_request();
4110abc7381bSLuis R. Rodriguez 
411157b5ce07SLuis R. Rodriguez 	if (!reg_dev_ignore_cell_hint(wiphy))
411257b5ce07SLuis R. Rodriguez 		reg_num_devs_support_basehint--;
411357b5ce07SLuis R. Rodriguez 
4114458f4f9eSJohannes Berg 	rcu_free_regdom(get_wiphy_regdom(wiphy));
411534dd886cSMonam Agarwal 	RCU_INIT_POINTER(wiphy->regd, NULL);
41160ef9ccddSChris Wright 
4117c492db37SJohannes Berg 	if (lr)
4118c492db37SJohannes Berg 		request_wiphy = wiphy_idx_to_wiphy(lr->wiphy_idx);
4119806a9e39SLuis R. Rodriguez 
41200ef9ccddSChris Wright 	if (!request_wiphy || request_wiphy != wiphy)
412138fd2143SJohannes Berg 		return;
41220ef9ccddSChris Wright 
4123c492db37SJohannes Berg 	lr->wiphy_idx = WIPHY_IDX_INVALID;
4124c492db37SJohannes Berg 	lr->country_ie_env = ENVIRON_ANY;
41253f2355cbSLuis R. Rodriguez }
41263f2355cbSLuis R. Rodriguez 
4127174e0cd2SIlan Peer /*
4128f89769cfSArend van Spriel  * See FCC notices for UNII band definitions
4129f89769cfSArend van Spriel  *  5GHz: https://www.fcc.gov/document/5-ghz-unlicensed-spectrum-unii
4130f89769cfSArend van Spriel  *  6GHz: https://www.fcc.gov/document/fcc-proposes-more-spectrum-unlicensed-use-0
4131174e0cd2SIlan Peer  */
cfg80211_get_unii(int freq)4132174e0cd2SIlan Peer int cfg80211_get_unii(int freq)
4133174e0cd2SIlan Peer {
4134174e0cd2SIlan Peer 	/* UNII-1 */
4135174e0cd2SIlan Peer 	if (freq >= 5150 && freq <= 5250)
4136174e0cd2SIlan Peer 		return 0;
4137174e0cd2SIlan Peer 
4138174e0cd2SIlan Peer 	/* UNII-2A */
4139174e0cd2SIlan Peer 	if (freq > 5250 && freq <= 5350)
4140174e0cd2SIlan Peer 		return 1;
4141174e0cd2SIlan Peer 
4142174e0cd2SIlan Peer 	/* UNII-2B */
4143174e0cd2SIlan Peer 	if (freq > 5350 && freq <= 5470)
4144174e0cd2SIlan Peer 		return 2;
4145174e0cd2SIlan Peer 
4146174e0cd2SIlan Peer 	/* UNII-2C */
4147174e0cd2SIlan Peer 	if (freq > 5470 && freq <= 5725)
4148174e0cd2SIlan Peer 		return 3;
4149174e0cd2SIlan Peer 
4150174e0cd2SIlan Peer 	/* UNII-3 */
4151174e0cd2SIlan Peer 	if (freq > 5725 && freq <= 5825)
4152174e0cd2SIlan Peer 		return 4;
4153174e0cd2SIlan Peer 
4154f89769cfSArend van Spriel 	/* UNII-5 */
4155f89769cfSArend van Spriel 	if (freq > 5925 && freq <= 6425)
4156f89769cfSArend van Spriel 		return 5;
4157f89769cfSArend van Spriel 
4158f89769cfSArend van Spriel 	/* UNII-6 */
4159f89769cfSArend van Spriel 	if (freq > 6425 && freq <= 6525)
4160f89769cfSArend van Spriel 		return 6;
4161f89769cfSArend van Spriel 
4162f89769cfSArend van Spriel 	/* UNII-7 */
4163f89769cfSArend van Spriel 	if (freq > 6525 && freq <= 6875)
4164f89769cfSArend van Spriel 		return 7;
4165f89769cfSArend van Spriel 
4166f89769cfSArend van Spriel 	/* UNII-8 */
4167f89769cfSArend van Spriel 	if (freq > 6875 && freq <= 7125)
4168f89769cfSArend van Spriel 		return 8;
4169f89769cfSArend van Spriel 
4170174e0cd2SIlan Peer 	return -EINVAL;
4171174e0cd2SIlan Peer }
4172174e0cd2SIlan Peer 
regulatory_indoor_allowed(void)4173c8866e55SIlan Peer bool regulatory_indoor_allowed(void)
4174c8866e55SIlan Peer {
4175c8866e55SIlan Peer 	return reg_is_indoor;
4176c8866e55SIlan Peer }
4177c8866e55SIlan Peer 
regulatory_pre_cac_allowed(struct wiphy * wiphy)4178b35a51c7SVasanthakumar Thiagarajan bool regulatory_pre_cac_allowed(struct wiphy *wiphy)
4179b35a51c7SVasanthakumar Thiagarajan {
4180b35a51c7SVasanthakumar Thiagarajan 	const struct ieee80211_regdomain *regd = NULL;
4181b35a51c7SVasanthakumar Thiagarajan 	const struct ieee80211_regdomain *wiphy_regd = NULL;
4182b35a51c7SVasanthakumar Thiagarajan 	bool pre_cac_allowed = false;
4183b35a51c7SVasanthakumar Thiagarajan 
4184b35a51c7SVasanthakumar Thiagarajan 	rcu_read_lock();
4185b35a51c7SVasanthakumar Thiagarajan 
4186b35a51c7SVasanthakumar Thiagarajan 	regd = rcu_dereference(cfg80211_regdomain);
4187b35a51c7SVasanthakumar Thiagarajan 	wiphy_regd = rcu_dereference(wiphy->regd);
4188b35a51c7SVasanthakumar Thiagarajan 	if (!wiphy_regd) {
4189b35a51c7SVasanthakumar Thiagarajan 		if (regd->dfs_region == NL80211_DFS_ETSI)
4190b35a51c7SVasanthakumar Thiagarajan 			pre_cac_allowed = true;
4191b35a51c7SVasanthakumar Thiagarajan 
4192b35a51c7SVasanthakumar Thiagarajan 		rcu_read_unlock();
4193b35a51c7SVasanthakumar Thiagarajan 
4194b35a51c7SVasanthakumar Thiagarajan 		return pre_cac_allowed;
4195b35a51c7SVasanthakumar Thiagarajan 	}
4196b35a51c7SVasanthakumar Thiagarajan 
4197b35a51c7SVasanthakumar Thiagarajan 	if (regd->dfs_region == wiphy_regd->dfs_region &&
4198b35a51c7SVasanthakumar Thiagarajan 	    wiphy_regd->dfs_region == NL80211_DFS_ETSI)
4199b35a51c7SVasanthakumar Thiagarajan 		pre_cac_allowed = true;
4200b35a51c7SVasanthakumar Thiagarajan 
4201b35a51c7SVasanthakumar Thiagarajan 	rcu_read_unlock();
4202b35a51c7SVasanthakumar Thiagarajan 
4203b35a51c7SVasanthakumar Thiagarajan 	return pre_cac_allowed;
4204b35a51c7SVasanthakumar Thiagarajan }
4205dc0c18edSAaron Komisar EXPORT_SYMBOL(regulatory_pre_cac_allowed);
4206b35a51c7SVasanthakumar Thiagarajan 
cfg80211_check_and_end_cac(struct cfg80211_registered_device * rdev)420726ec17a1SOrr Mazor static void cfg80211_check_and_end_cac(struct cfg80211_registered_device *rdev)
420826ec17a1SOrr Mazor {
420926ec17a1SOrr Mazor 	struct wireless_dev *wdev;
421026ec17a1SOrr Mazor 	/* If we finished CAC or received radar, we should end any
421126ec17a1SOrr Mazor 	 * CAC running on the same channels.
421226ec17a1SOrr Mazor 	 * the check !cfg80211_chandef_dfs_usable contain 2 options:
421326ec17a1SOrr Mazor 	 * either all channels are available - those the CAC_FINISHED
421426ec17a1SOrr Mazor 	 * event has effected another wdev state, or there is a channel
421526ec17a1SOrr Mazor 	 * in unavailable state in wdev chandef - those the RADAR_DETECTED
421626ec17a1SOrr Mazor 	 * event has effected another wdev state.
421726ec17a1SOrr Mazor 	 * In both cases we should end the CAC on the wdev.
421826ec17a1SOrr Mazor 	 */
421926ec17a1SOrr Mazor 	list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
42207b0a0e3cSJohannes Berg 		struct cfg80211_chan_def *chandef;
42217b0a0e3cSJohannes Berg 
42227b0a0e3cSJohannes Berg 		if (!wdev->cac_started)
42237b0a0e3cSJohannes Berg 			continue;
42247b0a0e3cSJohannes Berg 
42257b0a0e3cSJohannes Berg 		/* FIXME: radar detection is tied to link 0 for now */
42267b0a0e3cSJohannes Berg 		chandef = wdev_chandef(wdev, 0);
42277b0a0e3cSJohannes Berg 		if (!chandef)
42287b0a0e3cSJohannes Berg 			continue;
42297b0a0e3cSJohannes Berg 
42307b0a0e3cSJohannes Berg 		if (!cfg80211_chandef_dfs_usable(&rdev->wiphy, chandef))
423126ec17a1SOrr Mazor 			rdev_end_cac(rdev, wdev->netdev);
423226ec17a1SOrr Mazor 	}
423326ec17a1SOrr Mazor }
423426ec17a1SOrr Mazor 
regulatory_propagate_dfs_state(struct wiphy * wiphy,struct cfg80211_chan_def * chandef,enum nl80211_dfs_state dfs_state,enum nl80211_radar_event event)423589766727SVasanthakumar Thiagarajan void regulatory_propagate_dfs_state(struct wiphy *wiphy,
423689766727SVasanthakumar Thiagarajan 				    struct cfg80211_chan_def *chandef,
423789766727SVasanthakumar Thiagarajan 				    enum nl80211_dfs_state dfs_state,
423889766727SVasanthakumar Thiagarajan 				    enum nl80211_radar_event event)
423989766727SVasanthakumar Thiagarajan {
424089766727SVasanthakumar Thiagarajan 	struct cfg80211_registered_device *rdev;
424189766727SVasanthakumar Thiagarajan 
424289766727SVasanthakumar Thiagarajan 	ASSERT_RTNL();
424389766727SVasanthakumar Thiagarajan 
424489766727SVasanthakumar Thiagarajan 	if (WARN_ON(!cfg80211_chandef_valid(chandef)))
424589766727SVasanthakumar Thiagarajan 		return;
424689766727SVasanthakumar Thiagarajan 
424789766727SVasanthakumar Thiagarajan 	list_for_each_entry(rdev, &cfg80211_rdev_list, list) {
424889766727SVasanthakumar Thiagarajan 		if (wiphy == &rdev->wiphy)
424989766727SVasanthakumar Thiagarajan 			continue;
425089766727SVasanthakumar Thiagarajan 
425189766727SVasanthakumar Thiagarajan 		if (!reg_dfs_domain_same(wiphy, &rdev->wiphy))
425289766727SVasanthakumar Thiagarajan 			continue;
425389766727SVasanthakumar Thiagarajan 
425489766727SVasanthakumar Thiagarajan 		if (!ieee80211_get_channel(&rdev->wiphy,
425589766727SVasanthakumar Thiagarajan 					   chandef->chan->center_freq))
425689766727SVasanthakumar Thiagarajan 			continue;
425789766727SVasanthakumar Thiagarajan 
425889766727SVasanthakumar Thiagarajan 		cfg80211_set_dfs_state(&rdev->wiphy, chandef, dfs_state);
425989766727SVasanthakumar Thiagarajan 
426089766727SVasanthakumar Thiagarajan 		if (event == NL80211_RADAR_DETECTED ||
426126ec17a1SOrr Mazor 		    event == NL80211_RADAR_CAC_FINISHED) {
426289766727SVasanthakumar Thiagarajan 			cfg80211_sched_dfs_chan_update(rdev);
426326ec17a1SOrr Mazor 			cfg80211_check_and_end_cac(rdev);
426426ec17a1SOrr Mazor 		}
426589766727SVasanthakumar Thiagarajan 
426689766727SVasanthakumar Thiagarajan 		nl80211_radar_notify(rdev, chandef, event, NULL, GFP_KERNEL);
426789766727SVasanthakumar Thiagarajan 	}
426889766727SVasanthakumar Thiagarajan }
426989766727SVasanthakumar Thiagarajan 
regulatory_init_db(void)4270d7be102fSJohannes Berg static int __init regulatory_init_db(void)
4271b2e1b302SLuis R. Rodriguez {
4272d7be102fSJohannes Berg 	int err;
4273734366deSJohannes Berg 
427471e5e886SJohannes Berg 	/*
427571e5e886SJohannes Berg 	 * It's possible that - due to other bugs/issues - cfg80211
427671e5e886SJohannes Berg 	 * never called regulatory_init() below, or that it failed;
427771e5e886SJohannes Berg 	 * in that case, don't try to do any further work here as
427871e5e886SJohannes Berg 	 * it's doomed to lead to crashes.
427971e5e886SJohannes Berg 	 */
428071e5e886SJohannes Berg 	if (IS_ERR_OR_NULL(reg_pdev))
428171e5e886SJohannes Berg 		return -EINVAL;
428271e5e886SJohannes Berg 
428390a53e44SJohannes Berg 	err = load_builtin_regdb_keys();
4284833a9fd2SChen Zhongjin 	if (err) {
4285833a9fd2SChen Zhongjin 		platform_device_unregister(reg_pdev);
428690a53e44SJohannes Berg 		return err;
4287833a9fd2SChen Zhongjin 	}
428890a53e44SJohannes Berg 
4289ae9e4b0dSLuis R. Rodriguez 	/* We always try to get an update for the static regdomain */
4290458f4f9eSJohannes Berg 	err = regulatory_hint_core(cfg80211_world_regdom->alpha2);
4291bcf4f99bSLuis R. Rodriguez 	if (err) {
429209d11800SOla Olsson 		if (err == -ENOMEM) {
429309d11800SOla Olsson 			platform_device_unregister(reg_pdev);
4294bcf4f99bSLuis R. Rodriguez 			return err;
429509d11800SOla Olsson 		}
4296bcf4f99bSLuis R. Rodriguez 		/*
4297bcf4f99bSLuis R. Rodriguez 		 * N.B. kobject_uevent_env() can fail mainly for when we're out
4298bcf4f99bSLuis R. Rodriguez 		 * memory which is handled and propagated appropriately above
4299bcf4f99bSLuis R. Rodriguez 		 * but it can also fail during a netlink_broadcast() or during
4300bcf4f99bSLuis R. Rodriguez 		 * early boot for call_usermodehelper(). For now treat these
4301bcf4f99bSLuis R. Rodriguez 		 * errors as non-fatal.
4302bcf4f99bSLuis R. Rodriguez 		 */
4303e9c0268fSJoe Perches 		pr_err("kobject_uevent_env() was unable to call CRDA during init\n");
4304bcf4f99bSLuis R. Rodriguez 	}
4305734366deSJohannes Berg 
4306ae9e4b0dSLuis R. Rodriguez 	/*
4307ae9e4b0dSLuis R. Rodriguez 	 * Finally, if the user set the module parameter treat it
4308ae9e4b0dSLuis R. Rodriguez 	 * as a user hint.
4309ae9e4b0dSLuis R. Rodriguez 	 */
4310ae9e4b0dSLuis R. Rodriguez 	if (!is_world_regdom(ieee80211_regdom))
431157b5ce07SLuis R. Rodriguez 		regulatory_hint_user(ieee80211_regdom,
431257b5ce07SLuis R. Rodriguez 				     NL80211_USER_REG_HINT_USER);
4313ae9e4b0dSLuis R. Rodriguez 
4314b2e1b302SLuis R. Rodriguez 	return 0;
4315b2e1b302SLuis R. Rodriguez }
4316d7be102fSJohannes Berg #ifndef MODULE
4317d7be102fSJohannes Berg late_initcall(regulatory_init_db);
4318d7be102fSJohannes Berg #endif
4319d7be102fSJohannes Berg 
regulatory_init(void)4320d7be102fSJohannes Berg int __init regulatory_init(void)
4321d7be102fSJohannes Berg {
4322d7be102fSJohannes Berg 	reg_pdev = platform_device_register_simple("regulatory", 0, NULL, 0);
4323d7be102fSJohannes Berg 	if (IS_ERR(reg_pdev))
4324d7be102fSJohannes Berg 		return PTR_ERR(reg_pdev);
4325d7be102fSJohannes Berg 
4326d7be102fSJohannes Berg 	rcu_assign_pointer(cfg80211_regdomain, cfg80211_world_regdom);
4327d7be102fSJohannes Berg 
4328d7be102fSJohannes Berg 	user_alpha2[0] = '9';
4329d7be102fSJohannes Berg 	user_alpha2[1] = '7';
4330d7be102fSJohannes Berg 
4331d7be102fSJohannes Berg #ifdef MODULE
4332d7be102fSJohannes Berg 	return regulatory_init_db();
4333d7be102fSJohannes Berg #else
4334d7be102fSJohannes Berg 	return 0;
4335d7be102fSJohannes Berg #endif
4336d7be102fSJohannes Berg }
4337b2e1b302SLuis R. Rodriguez 
regulatory_exit(void)43381a919318SJohannes Berg void regulatory_exit(void)
4339b2e1b302SLuis R. Rodriguez {
4340fe33eb39SLuis R. Rodriguez 	struct regulatory_request *reg_request, *tmp;
4341e38f8a7aSLuis R. Rodriguez 	struct reg_beacon *reg_beacon, *btmp;
4342fe33eb39SLuis R. Rodriguez 
4343fe33eb39SLuis R. Rodriguez 	cancel_work_sync(&reg_work);
4344b6863036SJohannes Berg 	cancel_crda_timeout_sync();
4345ad932f04SArik Nemtsov 	cancel_delayed_work_sync(&reg_check_chans);
4346fe33eb39SLuis R. Rodriguez 
43479027b149SJohannes Berg 	/* Lock to suppress warnings */
434838fd2143SJohannes Berg 	rtnl_lock();
4349379b82f4SJohannes Berg 	reset_regdomains(true, NULL);
435038fd2143SJohannes Berg 	rtnl_unlock();
4351734366deSJohannes Berg 
435258ebacc6SLuis R. Rodriguez 	dev_set_uevent_suppress(&reg_pdev->dev, true);
4353f6037d09SJohannes Berg 
4354b2e1b302SLuis R. Rodriguez 	platform_device_unregister(reg_pdev);
4355734366deSJohannes Berg 
4356fea9bcedSJohannes Berg 	list_for_each_entry_safe(reg_beacon, btmp, &reg_pending_beacons, list) {
4357e38f8a7aSLuis R. Rodriguez 		list_del(&reg_beacon->list);
4358e38f8a7aSLuis R. Rodriguez 		kfree(reg_beacon);
4359e38f8a7aSLuis R. Rodriguez 	}
4360e38f8a7aSLuis R. Rodriguez 
4361fea9bcedSJohannes Berg 	list_for_each_entry_safe(reg_beacon, btmp, &reg_beacon_list, list) {
4362e38f8a7aSLuis R. Rodriguez 		list_del(&reg_beacon->list);
4363e38f8a7aSLuis R. Rodriguez 		kfree(reg_beacon);
4364e38f8a7aSLuis R. Rodriguez 	}
4365e38f8a7aSLuis R. Rodriguez 
4366fea9bcedSJohannes Berg 	list_for_each_entry_safe(reg_request, tmp, &reg_requests_list, list) {
4367fe33eb39SLuis R. Rodriguez 		list_del(&reg_request->list);
4368fe33eb39SLuis R. Rodriguez 		kfree(reg_request);
4369fe33eb39SLuis R. Rodriguez 	}
4370007f6c5eSJohannes Berg 
4371007f6c5eSJohannes Berg 	if (!IS_ERR_OR_NULL(regdb))
4372007f6c5eSJohannes Berg 		kfree(regdb);
4373e646a025SJohannes Berg 	if (!IS_ERR_OR_NULL(cfg80211_user_regdom))
4374e646a025SJohannes Berg 		kfree(cfg80211_user_regdom);
437590a53e44SJohannes Berg 
437690a53e44SJohannes Berg 	free_regdb_keyring();
4377fe33eb39SLuis R. Rodriguez }
4378