xref: /openbmc/linux/net/wireless/reg.c (revision 50c11eb9)
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>
68318d78aSJohannes Berg  *
73b77d5ecSLuis R. Rodriguez  * Permission to use, copy, modify, and/or distribute this software for any
83b77d5ecSLuis R. Rodriguez  * purpose with or without fee is hereby granted, provided that the above
93b77d5ecSLuis R. Rodriguez  * copyright notice and this permission notice appear in all copies.
103b77d5ecSLuis R. Rodriguez  *
113b77d5ecSLuis R. Rodriguez  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
123b77d5ecSLuis R. Rodriguez  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
133b77d5ecSLuis R. Rodriguez  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
143b77d5ecSLuis R. Rodriguez  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
153b77d5ecSLuis R. Rodriguez  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
163b77d5ecSLuis R. Rodriguez  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
173b77d5ecSLuis R. Rodriguez  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
188318d78aSJohannes Berg  */
198318d78aSJohannes Berg 
203b77d5ecSLuis R. Rodriguez 
21b2e1b302SLuis R. Rodriguez /**
22b2e1b302SLuis R. Rodriguez  * DOC: Wireless regulatory infrastructure
238318d78aSJohannes Berg  *
248318d78aSJohannes Berg  * The usual implementation is for a driver to read a device EEPROM to
258318d78aSJohannes Berg  * determine which regulatory domain it should be operating under, then
268318d78aSJohannes Berg  * looking up the allowable channels in a driver-local table and finally
278318d78aSJohannes Berg  * registering those channels in the wiphy structure.
288318d78aSJohannes Berg  *
29b2e1b302SLuis R. Rodriguez  * Another set of compliance enforcement is for drivers to use their
30b2e1b302SLuis R. Rodriguez  * own compliance limits which can be stored on the EEPROM. The host
31b2e1b302SLuis R. Rodriguez  * driver or firmware may ensure these are used.
32b2e1b302SLuis R. Rodriguez  *
33b2e1b302SLuis R. Rodriguez  * In addition to all this we provide an extra layer of regulatory
34b2e1b302SLuis R. Rodriguez  * conformance. For drivers which do not have any regulatory
35b2e1b302SLuis R. Rodriguez  * information CRDA provides the complete regulatory solution.
36b2e1b302SLuis R. Rodriguez  * For others it provides a community effort on further restrictions
37b2e1b302SLuis R. Rodriguez  * to enhance compliance.
38b2e1b302SLuis R. Rodriguez  *
39b2e1b302SLuis R. Rodriguez  * Note: When number of rules --> infinity we will not be able to
40b2e1b302SLuis R. Rodriguez  * index on alpha2 any more, instead we'll probably have to
41b2e1b302SLuis R. Rodriguez  * rely on some SHA1 checksum of the regdomain for example.
42b2e1b302SLuis R. Rodriguez  *
438318d78aSJohannes Berg  */
44e9c0268fSJoe Perches 
45e9c0268fSJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
46e9c0268fSJoe Perches 
478318d78aSJohannes Berg #include <linux/kernel.h>
48bc3b2d7fSPaul Gortmaker #include <linux/export.h>
495a0e3ad6STejun Heo #include <linux/slab.h>
50b2e1b302SLuis R. Rodriguez #include <linux/list.h>
51c61029c7SJohn W. Linville #include <linux/ctype.h>
52b2e1b302SLuis R. Rodriguez #include <linux/nl80211.h>
53b2e1b302SLuis R. Rodriguez #include <linux/platform_device.h>
54d9b93842SPaul Gortmaker #include <linux/moduleparam.h>
55b2e1b302SLuis R. Rodriguez #include <net/cfg80211.h>
568318d78aSJohannes Berg #include "core.h"
57b2e1b302SLuis R. Rodriguez #include "reg.h"
583b377ea9SJohn W. Linville #include "regdb.h"
5973d54c9eSLuis R. Rodriguez #include "nl80211.h"
608318d78aSJohannes Berg 
614113f751SLuis R. Rodriguez #ifdef CONFIG_CFG80211_REG_DEBUG
628271195eSJohn W. Linville #define REG_DBG_PRINT(format, args...)			\
6312c5ffb5SJoe Perches 	printk(KERN_DEBUG pr_fmt(format), ##args)
644113f751SLuis R. Rodriguez #else
658271195eSJohn W. Linville #define REG_DBG_PRINT(args...)
664113f751SLuis R. Rodriguez #endif
674113f751SLuis R. Rodriguez 
682f92212bSJohannes Berg enum reg_request_treatment {
692f92212bSJohannes Berg 	REG_REQ_OK,
702f92212bSJohannes Berg 	REG_REQ_IGNORE,
712f92212bSJohannes Berg 	REG_REQ_INTERSECT,
722f92212bSJohannes Berg 	REG_REQ_ALREADY_SET,
732f92212bSJohannes Berg };
742f92212bSJohannes Berg 
75a042994dSLuis R. Rodriguez static struct regulatory_request core_request_world = {
76a042994dSLuis R. Rodriguez 	.initiator = NL80211_REGDOM_SET_BY_CORE,
77a042994dSLuis R. Rodriguez 	.alpha2[0] = '0',
78a042994dSLuis R. Rodriguez 	.alpha2[1] = '0',
79a042994dSLuis R. Rodriguez 	.intersect = false,
80a042994dSLuis R. Rodriguez 	.processed = true,
81a042994dSLuis R. Rodriguez 	.country_ie_env = ENVIRON_ANY,
82a042994dSLuis R. Rodriguez };
83a042994dSLuis R. Rodriguez 
8438fd2143SJohannes Berg /*
8538fd2143SJohannes Berg  * Receipt of information from last regulatory request,
8638fd2143SJohannes Berg  * protected by RTNL (and can be accessed with RCU protection)
8738fd2143SJohannes Berg  */
88c492db37SJohannes Berg static struct regulatory_request __rcu *last_request =
89c492db37SJohannes Berg 	(void __rcu *)&core_request_world;
90734366deSJohannes Berg 
91b2e1b302SLuis R. Rodriguez /* To trigger userspace events */
92b2e1b302SLuis R. Rodriguez static struct platform_device *reg_pdev;
938318d78aSJohannes Berg 
944d9d88d1SScott James Remnant static struct device_type reg_device_type = {
954d9d88d1SScott James Remnant 	.uevent = reg_device_uevent,
964d9d88d1SScott James Remnant };
974d9d88d1SScott James Remnant 
98fb1fc7adSLuis R. Rodriguez /*
99fb1fc7adSLuis R. Rodriguez  * Central wireless core regulatory domains, we only need two,
100734366deSJohannes Berg  * the current one and a world regulatory domain in case we have no
101e8da2bb4SJohannes Berg  * information to give us an alpha2.
10238fd2143SJohannes Berg  * (protected by RTNL, can be read under RCU)
103fb1fc7adSLuis R. Rodriguez  */
104458f4f9eSJohannes Berg const struct ieee80211_regdomain __rcu *cfg80211_regdomain;
105734366deSJohannes Berg 
106fb1fc7adSLuis R. Rodriguez /*
10757b5ce07SLuis R. Rodriguez  * Number of devices that registered to the core
10857b5ce07SLuis R. Rodriguez  * that support cellular base station regulatory hints
10938fd2143SJohannes Berg  * (protected by RTNL)
11057b5ce07SLuis R. Rodriguez  */
11157b5ce07SLuis R. Rodriguez static int reg_num_devs_support_basehint;
11257b5ce07SLuis R. Rodriguez 
113458f4f9eSJohannes Berg static const struct ieee80211_regdomain *get_cfg80211_regdom(void)
114458f4f9eSJohannes Berg {
11538fd2143SJohannes Berg 	return rtnl_dereference(cfg80211_regdomain);
116458f4f9eSJohannes Berg }
117458f4f9eSJohannes Berg 
118458f4f9eSJohannes Berg static const struct ieee80211_regdomain *get_wiphy_regdom(struct wiphy *wiphy)
119458f4f9eSJohannes Berg {
12038fd2143SJohannes Berg 	return rtnl_dereference(wiphy->regd);
121458f4f9eSJohannes Berg }
122458f4f9eSJohannes Berg 
1233ef121b5SLuis R. Rodriguez static const char *reg_dfs_region_str(enum nl80211_dfs_regions dfs_region)
1243ef121b5SLuis R. Rodriguez {
1253ef121b5SLuis R. Rodriguez 	switch (dfs_region) {
1263ef121b5SLuis R. Rodriguez 	case NL80211_DFS_UNSET:
1273ef121b5SLuis R. Rodriguez 		return "unset";
1283ef121b5SLuis R. Rodriguez 	case NL80211_DFS_FCC:
1293ef121b5SLuis R. Rodriguez 		return "FCC";
1303ef121b5SLuis R. Rodriguez 	case NL80211_DFS_ETSI:
1313ef121b5SLuis R. Rodriguez 		return "ETSI";
1323ef121b5SLuis R. Rodriguez 	case NL80211_DFS_JP:
1333ef121b5SLuis R. Rodriguez 		return "JP";
1343ef121b5SLuis R. Rodriguez 	}
1353ef121b5SLuis R. Rodriguez 	return "Unknown";
1363ef121b5SLuis R. Rodriguez }
1373ef121b5SLuis R. Rodriguez 
1386c474799SLuis R. Rodriguez enum nl80211_dfs_regions reg_get_dfs_region(struct wiphy *wiphy)
1396c474799SLuis R. Rodriguez {
1406c474799SLuis R. Rodriguez 	const struct ieee80211_regdomain *regd = NULL;
1416c474799SLuis R. Rodriguez 	const struct ieee80211_regdomain *wiphy_regd = NULL;
1426c474799SLuis R. Rodriguez 
1436c474799SLuis R. Rodriguez 	regd = get_cfg80211_regdom();
1446c474799SLuis R. Rodriguez 	if (!wiphy)
1456c474799SLuis R. Rodriguez 		goto out;
1466c474799SLuis R. Rodriguez 
1476c474799SLuis R. Rodriguez 	wiphy_regd = get_wiphy_regdom(wiphy);
1486c474799SLuis R. Rodriguez 	if (!wiphy_regd)
1496c474799SLuis R. Rodriguez 		goto out;
1506c474799SLuis R. Rodriguez 
1516c474799SLuis R. Rodriguez 	if (wiphy_regd->dfs_region == regd->dfs_region)
1526c474799SLuis R. Rodriguez 		goto out;
1536c474799SLuis R. Rodriguez 
1546c474799SLuis R. Rodriguez 	REG_DBG_PRINT("%s: device specific dfs_region "
1556c474799SLuis R. Rodriguez 		      "(%s) disagrees with cfg80211's "
1566c474799SLuis R. Rodriguez 		      "central dfs_region (%s)\n",
1576c474799SLuis R. Rodriguez 		      dev_name(&wiphy->dev),
1586c474799SLuis R. Rodriguez 		      reg_dfs_region_str(wiphy_regd->dfs_region),
1596c474799SLuis R. Rodriguez 		      reg_dfs_region_str(regd->dfs_region));
1606c474799SLuis R. Rodriguez 
1616c474799SLuis R. Rodriguez out:
1626c474799SLuis R. Rodriguez 	return regd->dfs_region;
1636c474799SLuis R. Rodriguez }
1646c474799SLuis R. Rodriguez 
165458f4f9eSJohannes Berg static void rcu_free_regdom(const struct ieee80211_regdomain *r)
166458f4f9eSJohannes Berg {
167458f4f9eSJohannes Berg 	if (!r)
168458f4f9eSJohannes Berg 		return;
169458f4f9eSJohannes Berg 	kfree_rcu((struct ieee80211_regdomain *)r, rcu_head);
170458f4f9eSJohannes Berg }
171458f4f9eSJohannes Berg 
172c492db37SJohannes Berg static struct regulatory_request *get_last_request(void)
173c492db37SJohannes Berg {
17438fd2143SJohannes Berg 	return rcu_dereference_rtnl(last_request);
175c492db37SJohannes Berg }
176c492db37SJohannes Berg 
177e38f8a7aSLuis R. Rodriguez /* Used to queue up regulatory hints */
178fe33eb39SLuis R. Rodriguez static LIST_HEAD(reg_requests_list);
179fe33eb39SLuis R. Rodriguez static spinlock_t reg_requests_lock;
180fe33eb39SLuis R. Rodriguez 
181e38f8a7aSLuis R. Rodriguez /* Used to queue up beacon hints for review */
182e38f8a7aSLuis R. Rodriguez static LIST_HEAD(reg_pending_beacons);
183e38f8a7aSLuis R. Rodriguez static spinlock_t reg_pending_beacons_lock;
184e38f8a7aSLuis R. Rodriguez 
185e38f8a7aSLuis R. Rodriguez /* Used to keep track of processed beacon hints */
186e38f8a7aSLuis R. Rodriguez static LIST_HEAD(reg_beacon_list);
187e38f8a7aSLuis R. Rodriguez 
188e38f8a7aSLuis R. Rodriguez struct reg_beacon {
189e38f8a7aSLuis R. Rodriguez 	struct list_head list;
190e38f8a7aSLuis R. Rodriguez 	struct ieee80211_channel chan;
191e38f8a7aSLuis R. Rodriguez };
192e38f8a7aSLuis R. Rodriguez 
193f333a7a2SLuis R. Rodriguez static void reg_todo(struct work_struct *work);
194f333a7a2SLuis R. Rodriguez static DECLARE_WORK(reg_work, reg_todo);
195f333a7a2SLuis R. Rodriguez 
196a90c7a31SLuis R. Rodriguez static void reg_timeout_work(struct work_struct *work);
197a90c7a31SLuis R. Rodriguez static DECLARE_DELAYED_WORK(reg_timeout, reg_timeout_work);
198a90c7a31SLuis R. Rodriguez 
199734366deSJohannes Berg /* We keep a static world regulatory domain in case of the absence of CRDA */
200734366deSJohannes Berg static const struct ieee80211_regdomain world_regdom = {
20190cdc6dfSVladimir Kondratiev 	.n_reg_rules = 6,
202734366deSJohannes Berg 	.alpha2 =  "00",
203734366deSJohannes Berg 	.reg_rules = {
20468798a62SLuis R. Rodriguez 		/* IEEE 802.11b/g, channels 1..11 */
20568798a62SLuis R. Rodriguez 		REG_RULE(2412-10, 2462+10, 40, 6, 20, 0),
20643c771a1SJohannes Berg 		/* IEEE 802.11b/g, channels 12..13. */
20743c771a1SJohannes Berg 		REG_RULE(2467-10, 2472+10, 40, 6, 20,
2088fe02e16SLuis R. Rodriguez 			NL80211_RRF_NO_IR),
209611b6a82SLuis R. Rodriguez 		/* IEEE 802.11 channel 14 - Only JP enables
210611b6a82SLuis R. Rodriguez 		 * this and for 802.11b only */
211611b6a82SLuis R. Rodriguez 		REG_RULE(2484-10, 2484+10, 20, 6, 20,
2128fe02e16SLuis R. Rodriguez 			NL80211_RRF_NO_IR |
213611b6a82SLuis R. Rodriguez 			NL80211_RRF_NO_OFDM),
2143fc71f77SLuis R. Rodriguez 		/* IEEE 802.11a, channel 36..48 */
215131a19bcSJohannes Berg 		REG_RULE(5180-10, 5240+10, 160, 6, 20,
2168fe02e16SLuis R. Rodriguez                         NL80211_RRF_NO_IR),
2173fc71f77SLuis R. Rodriguez 
218131a19bcSJohannes Berg 		/* IEEE 802.11a, channel 52..64 - DFS required */
219131a19bcSJohannes Berg 		REG_RULE(5260-10, 5320+10, 160, 6, 20,
2208fe02e16SLuis R. Rodriguez 			NL80211_RRF_NO_IR |
221131a19bcSJohannes Berg 			NL80211_RRF_DFS),
222131a19bcSJohannes Berg 
223131a19bcSJohannes Berg 		/* IEEE 802.11a, channel 100..144 - DFS required */
224131a19bcSJohannes Berg 		REG_RULE(5500-10, 5720+10, 160, 6, 20,
2258fe02e16SLuis R. Rodriguez 			NL80211_RRF_NO_IR |
226131a19bcSJohannes Berg 			NL80211_RRF_DFS),
2273fc71f77SLuis R. Rodriguez 
2283fc71f77SLuis R. Rodriguez 		/* IEEE 802.11a, channel 149..165 */
2298ab9d85cSJohannes Berg 		REG_RULE(5745-10, 5825+10, 80, 6, 20,
2308fe02e16SLuis R. Rodriguez 			NL80211_RRF_NO_IR),
23190cdc6dfSVladimir Kondratiev 
23290cdc6dfSVladimir Kondratiev 		/* IEEE 802.11ad (60gHz), channels 1..3 */
23390cdc6dfSVladimir Kondratiev 		REG_RULE(56160+2160*1-1080, 56160+2160*3+1080, 2160, 0, 0, 0),
234734366deSJohannes Berg 	}
235734366deSJohannes Berg };
236734366deSJohannes Berg 
23738fd2143SJohannes Berg /* protected by RTNL */
238a3d2eaf0SJohannes Berg static const struct ieee80211_regdomain *cfg80211_world_regdom =
239a3d2eaf0SJohannes Berg 	&world_regdom;
240734366deSJohannes Berg 
2416ee7d330SLuis R. Rodriguez static char *ieee80211_regdom = "00";
24209d989d1SLuis R. Rodriguez static char user_alpha2[2];
2436ee7d330SLuis R. Rodriguez 
244734366deSJohannes Berg module_param(ieee80211_regdom, charp, 0444);
245734366deSJohannes Berg MODULE_PARM_DESC(ieee80211_regdom, "IEEE 802.11 regulatory domain code");
246734366deSJohannes Berg 
2475ad6ef5eSLuis R. Rodriguez static void reg_kfree_last_request(void)
2485ad6ef5eSLuis R. Rodriguez {
2495ad6ef5eSLuis R. Rodriguez 	struct regulatory_request *lr;
2505ad6ef5eSLuis R. Rodriguez 
2515ad6ef5eSLuis R. Rodriguez 	lr = get_last_request();
2525ad6ef5eSLuis R. Rodriguez 
2535ad6ef5eSLuis R. Rodriguez 	if (lr != &core_request_world && lr)
2545ad6ef5eSLuis R. Rodriguez 		kfree_rcu(lr, rcu_head);
2555ad6ef5eSLuis R. Rodriguez }
2565ad6ef5eSLuis R. Rodriguez 
25705f1a3eaSLuis R. Rodriguez static void reg_update_last_request(struct regulatory_request *request)
25805f1a3eaSLuis R. Rodriguez {
25905f1a3eaSLuis R. Rodriguez 	reg_kfree_last_request();
26005f1a3eaSLuis R. Rodriguez 	rcu_assign_pointer(last_request, request);
26105f1a3eaSLuis R. Rodriguez }
26205f1a3eaSLuis R. Rodriguez 
263379b82f4SJohannes Berg static void reset_regdomains(bool full_reset,
264379b82f4SJohannes Berg 			     const struct ieee80211_regdomain *new_regdom)
265734366deSJohannes Berg {
266458f4f9eSJohannes Berg 	const struct ieee80211_regdomain *r;
267458f4f9eSJohannes Berg 
26838fd2143SJohannes Berg 	ASSERT_RTNL();
269e8da2bb4SJohannes Berg 
270458f4f9eSJohannes Berg 	r = get_cfg80211_regdom();
271458f4f9eSJohannes Berg 
272942b25cfSJohannes Berg 	/* avoid freeing static information or freeing something twice */
273458f4f9eSJohannes Berg 	if (r == cfg80211_world_regdom)
274458f4f9eSJohannes Berg 		r = NULL;
275942b25cfSJohannes Berg 	if (cfg80211_world_regdom == &world_regdom)
276942b25cfSJohannes Berg 		cfg80211_world_regdom = NULL;
277458f4f9eSJohannes Berg 	if (r == &world_regdom)
278458f4f9eSJohannes Berg 		r = NULL;
279942b25cfSJohannes Berg 
280458f4f9eSJohannes Berg 	rcu_free_regdom(r);
281458f4f9eSJohannes Berg 	rcu_free_regdom(cfg80211_world_regdom);
282734366deSJohannes Berg 
283a3d2eaf0SJohannes Berg 	cfg80211_world_regdom = &world_regdom;
284458f4f9eSJohannes Berg 	rcu_assign_pointer(cfg80211_regdomain, new_regdom);
285a042994dSLuis R. Rodriguez 
286a042994dSLuis R. Rodriguez 	if (!full_reset)
287a042994dSLuis R. Rodriguez 		return;
288a042994dSLuis R. Rodriguez 
28905f1a3eaSLuis R. Rodriguez 	reg_update_last_request(&core_request_world);
290734366deSJohannes Berg }
291734366deSJohannes Berg 
292fb1fc7adSLuis R. Rodriguez /*
293fb1fc7adSLuis R. Rodriguez  * Dynamic world regulatory domain requested by the wireless
294fb1fc7adSLuis R. Rodriguez  * core upon initialization
295fb1fc7adSLuis R. Rodriguez  */
296a3d2eaf0SJohannes Berg static void update_world_regdomain(const struct ieee80211_regdomain *rd)
297734366deSJohannes Berg {
298c492db37SJohannes Berg 	struct regulatory_request *lr;
299734366deSJohannes Berg 
300c492db37SJohannes Berg 	lr = get_last_request();
301c492db37SJohannes Berg 
302c492db37SJohannes Berg 	WARN_ON(!lr);
303e8da2bb4SJohannes Berg 
304379b82f4SJohannes Berg 	reset_regdomains(false, rd);
305734366deSJohannes Berg 
306734366deSJohannes Berg 	cfg80211_world_regdom = rd;
307734366deSJohannes Berg }
308734366deSJohannes Berg 
309a3d2eaf0SJohannes Berg bool is_world_regdom(const char *alpha2)
310b2e1b302SLuis R. Rodriguez {
311b2e1b302SLuis R. Rodriguez 	if (!alpha2)
312b2e1b302SLuis R. Rodriguez 		return false;
3131a919318SJohannes Berg 	return alpha2[0] == '0' && alpha2[1] == '0';
314b2e1b302SLuis R. Rodriguez }
315b2e1b302SLuis R. Rodriguez 
316a3d2eaf0SJohannes Berg static bool is_alpha2_set(const char *alpha2)
317b2e1b302SLuis R. Rodriguez {
318b2e1b302SLuis R. Rodriguez 	if (!alpha2)
319b2e1b302SLuis R. Rodriguez 		return false;
3201a919318SJohannes Berg 	return alpha2[0] && alpha2[1];
321b2e1b302SLuis R. Rodriguez }
322b2e1b302SLuis R. Rodriguez 
323a3d2eaf0SJohannes Berg static bool is_unknown_alpha2(const char *alpha2)
324b2e1b302SLuis R. Rodriguez {
325b2e1b302SLuis R. Rodriguez 	if (!alpha2)
326b2e1b302SLuis R. Rodriguez 		return false;
327fb1fc7adSLuis R. Rodriguez 	/*
328fb1fc7adSLuis R. Rodriguez 	 * Special case where regulatory domain was built by driver
329fb1fc7adSLuis R. Rodriguez 	 * but a specific alpha2 cannot be determined
330fb1fc7adSLuis R. Rodriguez 	 */
3311a919318SJohannes Berg 	return alpha2[0] == '9' && alpha2[1] == '9';
332b2e1b302SLuis R. Rodriguez }
333b2e1b302SLuis R. Rodriguez 
3343f2355cbSLuis R. Rodriguez static bool is_intersected_alpha2(const char *alpha2)
3353f2355cbSLuis R. Rodriguez {
3363f2355cbSLuis R. Rodriguez 	if (!alpha2)
3373f2355cbSLuis R. Rodriguez 		return false;
338fb1fc7adSLuis R. Rodriguez 	/*
339fb1fc7adSLuis R. Rodriguez 	 * Special case where regulatory domain is the
3403f2355cbSLuis R. Rodriguez 	 * result of an intersection between two regulatory domain
341fb1fc7adSLuis R. Rodriguez 	 * structures
342fb1fc7adSLuis R. Rodriguez 	 */
3431a919318SJohannes Berg 	return alpha2[0] == '9' && alpha2[1] == '8';
3443f2355cbSLuis R. Rodriguez }
3453f2355cbSLuis R. Rodriguez 
346a3d2eaf0SJohannes Berg static bool is_an_alpha2(const char *alpha2)
347b2e1b302SLuis R. Rodriguez {
348b2e1b302SLuis R. Rodriguez 	if (!alpha2)
349b2e1b302SLuis R. Rodriguez 		return false;
3501a919318SJohannes Berg 	return isalpha(alpha2[0]) && isalpha(alpha2[1]);
351b2e1b302SLuis R. Rodriguez }
352b2e1b302SLuis R. Rodriguez 
353a3d2eaf0SJohannes Berg static bool alpha2_equal(const char *alpha2_x, const char *alpha2_y)
354b2e1b302SLuis R. Rodriguez {
355b2e1b302SLuis R. Rodriguez 	if (!alpha2_x || !alpha2_y)
356b2e1b302SLuis R. Rodriguez 		return false;
3571a919318SJohannes Berg 	return alpha2_x[0] == alpha2_y[0] && alpha2_x[1] == alpha2_y[1];
358b2e1b302SLuis R. Rodriguez }
359b2e1b302SLuis R. Rodriguez 
36069b1572bSLuis R. Rodriguez static bool regdom_changes(const char *alpha2)
361b2e1b302SLuis R. Rodriguez {
362458f4f9eSJohannes Berg 	const struct ieee80211_regdomain *r = get_cfg80211_regdom();
363761cf7ecSLuis R. Rodriguez 
364458f4f9eSJohannes Berg 	if (!r)
365b2e1b302SLuis R. Rodriguez 		return true;
366458f4f9eSJohannes Berg 	return !alpha2_equal(r->alpha2, alpha2);
367b2e1b302SLuis R. Rodriguez }
368b2e1b302SLuis R. Rodriguez 
36909d989d1SLuis R. Rodriguez /*
37009d989d1SLuis R. Rodriguez  * The NL80211_REGDOM_SET_BY_USER regdom alpha2 is cached, this lets
37109d989d1SLuis R. Rodriguez  * you know if a valid regulatory hint with NL80211_REGDOM_SET_BY_USER
37209d989d1SLuis R. Rodriguez  * has ever been issued.
37309d989d1SLuis R. Rodriguez  */
37409d989d1SLuis R. Rodriguez static bool is_user_regdom_saved(void)
37509d989d1SLuis R. Rodriguez {
37609d989d1SLuis R. Rodriguez 	if (user_alpha2[0] == '9' && user_alpha2[1] == '7')
37709d989d1SLuis R. Rodriguez 		return false;
37809d989d1SLuis R. Rodriguez 
37909d989d1SLuis R. Rodriguez 	/* This would indicate a mistake on the design */
3801a919318SJohannes Berg 	if (WARN(!is_world_regdom(user_alpha2) && !is_an_alpha2(user_alpha2),
38109d989d1SLuis R. Rodriguez 		 "Unexpected user alpha2: %c%c\n",
3821a919318SJohannes Berg 		 user_alpha2[0], user_alpha2[1]))
38309d989d1SLuis R. Rodriguez 		return false;
38409d989d1SLuis R. Rodriguez 
38509d989d1SLuis R. Rodriguez 	return true;
38609d989d1SLuis R. Rodriguez }
38709d989d1SLuis R. Rodriguez 
388e9763c3cSJohannes Berg static const struct ieee80211_regdomain *
389e9763c3cSJohannes Berg reg_copy_regd(const struct ieee80211_regdomain *src_regd)
3903b377ea9SJohn W. Linville {
3913b377ea9SJohn W. Linville 	struct ieee80211_regdomain *regd;
392e9763c3cSJohannes Berg 	int size_of_regd;
3933b377ea9SJohn W. Linville 	unsigned int i;
3943b377ea9SJohn W. Linville 
39582f20856SJohannes Berg 	size_of_regd =
39682f20856SJohannes Berg 		sizeof(struct ieee80211_regdomain) +
39782f20856SJohannes Berg 		src_regd->n_reg_rules * sizeof(struct ieee80211_reg_rule);
3983b377ea9SJohn W. Linville 
3993b377ea9SJohn W. Linville 	regd = kzalloc(size_of_regd, GFP_KERNEL);
4003b377ea9SJohn W. Linville 	if (!regd)
401e9763c3cSJohannes Berg 		return ERR_PTR(-ENOMEM);
4023b377ea9SJohn W. Linville 
4033b377ea9SJohn W. Linville 	memcpy(regd, src_regd, sizeof(struct ieee80211_regdomain));
4043b377ea9SJohn W. Linville 
4053b377ea9SJohn W. Linville 	for (i = 0; i < src_regd->n_reg_rules; i++)
4063b377ea9SJohn W. Linville 		memcpy(&regd->reg_rules[i], &src_regd->reg_rules[i],
4073b377ea9SJohn W. Linville 		       sizeof(struct ieee80211_reg_rule));
4083b377ea9SJohn W. Linville 
409e9763c3cSJohannes Berg 	return regd;
4103b377ea9SJohn W. Linville }
4113b377ea9SJohn W. Linville 
4123b377ea9SJohn W. Linville #ifdef CONFIG_CFG80211_INTERNAL_REGDB
4133b377ea9SJohn W. Linville struct reg_regdb_search_request {
4143b377ea9SJohn W. Linville 	char alpha2[2];
4153b377ea9SJohn W. Linville 	struct list_head list;
4163b377ea9SJohn W. Linville };
4173b377ea9SJohn W. Linville 
4183b377ea9SJohn W. Linville static LIST_HEAD(reg_regdb_search_list);
419368d06f5SJohn W. Linville static DEFINE_MUTEX(reg_regdb_search_mutex);
4203b377ea9SJohn W. Linville 
4213b377ea9SJohn W. Linville static void reg_regdb_search(struct work_struct *work)
4223b377ea9SJohn W. Linville {
4233b377ea9SJohn W. Linville 	struct reg_regdb_search_request *request;
424e9763c3cSJohannes Berg 	const struct ieee80211_regdomain *curdom, *regdom = NULL;
425e9763c3cSJohannes Berg 	int i;
426a85d0d7fSLuis R. Rodriguez 
4275fe231e8SJohannes Berg 	rtnl_lock();
4283b377ea9SJohn W. Linville 
429368d06f5SJohn W. Linville 	mutex_lock(&reg_regdb_search_mutex);
4303b377ea9SJohn W. Linville 	while (!list_empty(&reg_regdb_search_list)) {
4313b377ea9SJohn W. Linville 		request = list_first_entry(&reg_regdb_search_list,
4323b377ea9SJohn W. Linville 					   struct reg_regdb_search_request,
4333b377ea9SJohn W. Linville 					   list);
4343b377ea9SJohn W. Linville 		list_del(&request->list);
4353b377ea9SJohn W. Linville 
4363b377ea9SJohn W. Linville 		for (i = 0; i < reg_regdb_size; i++) {
4373b377ea9SJohn W. Linville 			curdom = reg_regdb[i];
4383b377ea9SJohn W. Linville 
4391a919318SJohannes Berg 			if (alpha2_equal(request->alpha2, curdom->alpha2)) {
440e9763c3cSJohannes Berg 				regdom = reg_copy_regd(curdom);
4413b377ea9SJohn W. Linville 				break;
4423b377ea9SJohn W. Linville 			}
4433b377ea9SJohn W. Linville 		}
4443b377ea9SJohn W. Linville 
4453b377ea9SJohn W. Linville 		kfree(request);
4463b377ea9SJohn W. Linville 	}
447368d06f5SJohn W. Linville 	mutex_unlock(&reg_regdb_search_mutex);
448a85d0d7fSLuis R. Rodriguez 
449e9763c3cSJohannes Berg 	if (!IS_ERR_OR_NULL(regdom))
450a85d0d7fSLuis R. Rodriguez 		set_regdom(regdom);
451a85d0d7fSLuis R. Rodriguez 
4525fe231e8SJohannes Berg 	rtnl_unlock();
4533b377ea9SJohn W. Linville }
4543b377ea9SJohn W. Linville 
4553b377ea9SJohn W. Linville static DECLARE_WORK(reg_regdb_work, reg_regdb_search);
4563b377ea9SJohn W. Linville 
4573b377ea9SJohn W. Linville static void reg_regdb_query(const char *alpha2)
4583b377ea9SJohn W. Linville {
4593b377ea9SJohn W. Linville 	struct reg_regdb_search_request *request;
4603b377ea9SJohn W. Linville 
4613b377ea9SJohn W. Linville 	if (!alpha2)
4623b377ea9SJohn W. Linville 		return;
4633b377ea9SJohn W. Linville 
4643b377ea9SJohn W. Linville 	request = kzalloc(sizeof(struct reg_regdb_search_request), GFP_KERNEL);
4653b377ea9SJohn W. Linville 	if (!request)
4663b377ea9SJohn W. Linville 		return;
4673b377ea9SJohn W. Linville 
4683b377ea9SJohn W. Linville 	memcpy(request->alpha2, alpha2, 2);
4693b377ea9SJohn W. Linville 
470368d06f5SJohn W. Linville 	mutex_lock(&reg_regdb_search_mutex);
4713b377ea9SJohn W. Linville 	list_add_tail(&request->list, &reg_regdb_search_list);
472368d06f5SJohn W. Linville 	mutex_unlock(&reg_regdb_search_mutex);
4733b377ea9SJohn W. Linville 
4743b377ea9SJohn W. Linville 	schedule_work(&reg_regdb_work);
4753b377ea9SJohn W. Linville }
47680007efeSLuis R. Rodriguez 
47780007efeSLuis R. Rodriguez /* Feel free to add any other sanity checks here */
47880007efeSLuis R. Rodriguez static void reg_regdb_size_check(void)
47980007efeSLuis R. Rodriguez {
48080007efeSLuis R. Rodriguez 	/* We should ideally BUILD_BUG_ON() but then random builds would fail */
48180007efeSLuis R. Rodriguez 	WARN_ONCE(!reg_regdb_size, "db.txt is empty, you should update it...");
48280007efeSLuis R. Rodriguez }
4833b377ea9SJohn W. Linville #else
48480007efeSLuis R. Rodriguez static inline void reg_regdb_size_check(void) {}
4853b377ea9SJohn W. Linville static inline void reg_regdb_query(const char *alpha2) {}
4863b377ea9SJohn W. Linville #endif /* CONFIG_CFG80211_INTERNAL_REGDB */
4873b377ea9SJohn W. Linville 
488fb1fc7adSLuis R. Rodriguez /*
489fb1fc7adSLuis R. Rodriguez  * This lets us keep regulatory code which is updated on a regulatory
4904d9d88d1SScott James Remnant  * basis in userspace. Country information is filled in by
4914d9d88d1SScott James Remnant  * reg_device_uevent
492fb1fc7adSLuis R. Rodriguez  */
493b2e1b302SLuis R. Rodriguez static int call_crda(const char *alpha2)
494b2e1b302SLuis R. Rodriguez {
495b2e1b302SLuis R. Rodriguez 	if (!is_world_regdom((char *) alpha2))
496e9c0268fSJoe Perches 		pr_info("Calling CRDA for country: %c%c\n",
497b2e1b302SLuis R. Rodriguez 			alpha2[0], alpha2[1]);
498b2e1b302SLuis R. Rodriguez 	else
499e9c0268fSJoe Perches 		pr_info("Calling CRDA to update world regulatory domain\n");
5008318d78aSJohannes Berg 
5013b377ea9SJohn W. Linville 	/* query internal regulatory database (if it exists) */
5023b377ea9SJohn W. Linville 	reg_regdb_query(alpha2);
5033b377ea9SJohn W. Linville 
5044d9d88d1SScott James Remnant 	return kobject_uevent(&reg_pdev->dev.kobj, KOBJ_CHANGE);
505b2e1b302SLuis R. Rodriguez }
506b2e1b302SLuis R. Rodriguez 
507fe6631ffSLuis R. Rodriguez static enum reg_request_treatment
508fe6631ffSLuis R. Rodriguez reg_call_crda(struct regulatory_request *request)
509fe6631ffSLuis R. Rodriguez {
510fe6631ffSLuis R. Rodriguez 	if (call_crda(request->alpha2))
511fe6631ffSLuis R. Rodriguez 		return REG_REQ_IGNORE;
512fe6631ffSLuis R. Rodriguez 	return REG_REQ_OK;
513fe6631ffSLuis R. Rodriguez }
514fe6631ffSLuis R. Rodriguez 
515e438768fSLuis R. Rodriguez bool reg_is_valid_request(const char *alpha2)
516b2e1b302SLuis R. Rodriguez {
517c492db37SJohannes Berg 	struct regulatory_request *lr = get_last_request();
51861405e97SLuis R. Rodriguez 
519c492db37SJohannes Berg 	if (!lr || lr->processed)
520f6037d09SJohannes Berg 		return false;
521f6037d09SJohannes Berg 
522c492db37SJohannes Berg 	return alpha2_equal(lr->alpha2, alpha2);
523b2e1b302SLuis R. Rodriguez }
524b2e1b302SLuis R. Rodriguez 
525b2e1b302SLuis R. Rodriguez /* Sanity check on a regulatory rule */
526a3d2eaf0SJohannes Berg static bool is_valid_reg_rule(const struct ieee80211_reg_rule *rule)
527b2e1b302SLuis R. Rodriguez {
528a3d2eaf0SJohannes Berg 	const struct ieee80211_freq_range *freq_range = &rule->freq_range;
529b2e1b302SLuis R. Rodriguez 	u32 freq_diff;
530b2e1b302SLuis R. Rodriguez 
53191e99004SLuis R. Rodriguez 	if (freq_range->start_freq_khz <= 0 || freq_range->end_freq_khz <= 0)
532b2e1b302SLuis R. Rodriguez 		return false;
533b2e1b302SLuis R. Rodriguez 
534b2e1b302SLuis R. Rodriguez 	if (freq_range->start_freq_khz > freq_range->end_freq_khz)
535b2e1b302SLuis R. Rodriguez 		return false;
536b2e1b302SLuis R. Rodriguez 
537b2e1b302SLuis R. Rodriguez 	freq_diff = freq_range->end_freq_khz - freq_range->start_freq_khz;
538b2e1b302SLuis R. Rodriguez 
539bd05f28eSRoel Kluin 	if (freq_range->end_freq_khz <= freq_range->start_freq_khz ||
540bd05f28eSRoel Kluin 	    freq_range->max_bandwidth_khz > freq_diff)
541b2e1b302SLuis R. Rodriguez 		return false;
542b2e1b302SLuis R. Rodriguez 
543b2e1b302SLuis R. Rodriguez 	return true;
544b2e1b302SLuis R. Rodriguez }
545b2e1b302SLuis R. Rodriguez 
546a3d2eaf0SJohannes Berg static bool is_valid_rd(const struct ieee80211_regdomain *rd)
547b2e1b302SLuis R. Rodriguez {
548a3d2eaf0SJohannes Berg 	const struct ieee80211_reg_rule *reg_rule = NULL;
549b2e1b302SLuis R. Rodriguez 	unsigned int i;
550b2e1b302SLuis R. Rodriguez 
551b2e1b302SLuis R. Rodriguez 	if (!rd->n_reg_rules)
552b2e1b302SLuis R. Rodriguez 		return false;
553b2e1b302SLuis R. Rodriguez 
55488dc1c3fSLuis R. Rodriguez 	if (WARN_ON(rd->n_reg_rules > NL80211_MAX_SUPP_REG_RULES))
55588dc1c3fSLuis R. Rodriguez 		return false;
55688dc1c3fSLuis R. Rodriguez 
557b2e1b302SLuis R. Rodriguez 	for (i = 0; i < rd->n_reg_rules; i++) {
558b2e1b302SLuis R. Rodriguez 		reg_rule = &rd->reg_rules[i];
559b2e1b302SLuis R. Rodriguez 		if (!is_valid_reg_rule(reg_rule))
560b2e1b302SLuis R. Rodriguez 			return false;
561b2e1b302SLuis R. Rodriguez 	}
562b2e1b302SLuis R. Rodriguez 
563b2e1b302SLuis R. Rodriguez 	return true;
564b2e1b302SLuis R. Rodriguez }
565b2e1b302SLuis R. Rodriguez 
566038659e7SLuis R. Rodriguez static bool reg_does_bw_fit(const struct ieee80211_freq_range *freq_range,
567fe7ef5e9SJohannes Berg 			    u32 center_freq_khz, u32 bw_khz)
568b2e1b302SLuis R. Rodriguez {
569038659e7SLuis R. Rodriguez 	u32 start_freq_khz, end_freq_khz;
570038659e7SLuis R. Rodriguez 
571038659e7SLuis R. Rodriguez 	start_freq_khz = center_freq_khz - (bw_khz/2);
572038659e7SLuis R. Rodriguez 	end_freq_khz = center_freq_khz + (bw_khz/2);
573038659e7SLuis R. Rodriguez 
574b2e1b302SLuis R. Rodriguez 	if (start_freq_khz >= freq_range->start_freq_khz &&
575b2e1b302SLuis R. Rodriguez 	    end_freq_khz <= freq_range->end_freq_khz)
576038659e7SLuis R. Rodriguez 		return true;
577038659e7SLuis R. Rodriguez 
578038659e7SLuis R. Rodriguez 	return false;
579b2e1b302SLuis R. Rodriguez }
580b2e1b302SLuis R. Rodriguez 
5810c7dc45dSLuis R. Rodriguez /**
5820c7dc45dSLuis R. Rodriguez  * freq_in_rule_band - tells us if a frequency is in a frequency band
5830c7dc45dSLuis R. Rodriguez  * @freq_range: frequency rule we want to query
5840c7dc45dSLuis R. Rodriguez  * @freq_khz: frequency we are inquiring about
5850c7dc45dSLuis R. Rodriguez  *
5860c7dc45dSLuis R. Rodriguez  * This lets us know if a specific frequency rule is or is not relevant to
5870c7dc45dSLuis R. Rodriguez  * a specific frequency's band. Bands are device specific and artificial
58864629b9dSVladimir Kondratiev  * definitions (the "2.4 GHz band", the "5 GHz band" and the "60GHz band"),
58964629b9dSVladimir Kondratiev  * however it is safe for now to assume that a frequency rule should not be
59064629b9dSVladimir Kondratiev  * part of a frequency's band if the start freq or end freq are off by more
59164629b9dSVladimir Kondratiev  * than 2 GHz for the 2.4 and 5 GHz bands, and by more than 10 GHz for the
59264629b9dSVladimir Kondratiev  * 60 GHz band.
5930c7dc45dSLuis R. Rodriguez  * This resolution can be lowered and should be considered as we add
5940c7dc45dSLuis R. Rodriguez  * regulatory rule support for other "bands".
5950c7dc45dSLuis R. Rodriguez  **/
5960c7dc45dSLuis R. Rodriguez static bool freq_in_rule_band(const struct ieee80211_freq_range *freq_range,
5970c7dc45dSLuis R. Rodriguez 			      u32 freq_khz)
5980c7dc45dSLuis R. Rodriguez {
5990c7dc45dSLuis R. Rodriguez #define ONE_GHZ_IN_KHZ	1000000
60064629b9dSVladimir Kondratiev 	/*
60164629b9dSVladimir Kondratiev 	 * From 802.11ad: directional multi-gigabit (DMG):
60264629b9dSVladimir Kondratiev 	 * Pertaining to operation in a frequency band containing a channel
60364629b9dSVladimir Kondratiev 	 * with the Channel starting frequency above 45 GHz.
60464629b9dSVladimir Kondratiev 	 */
60564629b9dSVladimir Kondratiev 	u32 limit = freq_khz > 45 * ONE_GHZ_IN_KHZ ?
60664629b9dSVladimir Kondratiev 			10 * ONE_GHZ_IN_KHZ : 2 * ONE_GHZ_IN_KHZ;
60764629b9dSVladimir Kondratiev 	if (abs(freq_khz - freq_range->start_freq_khz) <= limit)
6080c7dc45dSLuis R. Rodriguez 		return true;
60964629b9dSVladimir Kondratiev 	if (abs(freq_khz - freq_range->end_freq_khz) <= limit)
6100c7dc45dSLuis R. Rodriguez 		return true;
6110c7dc45dSLuis R. Rodriguez 	return false;
6120c7dc45dSLuis R. Rodriguez #undef ONE_GHZ_IN_KHZ
6130c7dc45dSLuis R. Rodriguez }
6140c7dc45dSLuis R. Rodriguez 
615fb1fc7adSLuis R. Rodriguez /*
616adbfb058SLuis R. Rodriguez  * Later on we can perhaps use the more restrictive DFS
617adbfb058SLuis R. Rodriguez  * region but we don't have information for that yet so
618adbfb058SLuis R. Rodriguez  * for now simply disallow conflicts.
619adbfb058SLuis R. Rodriguez  */
620adbfb058SLuis R. Rodriguez static enum nl80211_dfs_regions
621adbfb058SLuis R. Rodriguez reg_intersect_dfs_region(const enum nl80211_dfs_regions dfs_region1,
622adbfb058SLuis R. Rodriguez 			 const enum nl80211_dfs_regions dfs_region2)
623adbfb058SLuis R. Rodriguez {
624adbfb058SLuis R. Rodriguez 	if (dfs_region1 != dfs_region2)
625adbfb058SLuis R. Rodriguez 		return NL80211_DFS_UNSET;
626adbfb058SLuis R. Rodriguez 	return dfs_region1;
627adbfb058SLuis R. Rodriguez }
628adbfb058SLuis R. Rodriguez 
629adbfb058SLuis R. Rodriguez /*
630fb1fc7adSLuis R. Rodriguez  * Helper for regdom_intersect(), this does the real
631fb1fc7adSLuis R. Rodriguez  * mathematical intersection fun
632fb1fc7adSLuis R. Rodriguez  */
6331a919318SJohannes Berg static int reg_rules_intersect(const struct ieee80211_reg_rule *rule1,
6349c96477dSLuis R. Rodriguez 			       const struct ieee80211_reg_rule *rule2,
6359c96477dSLuis R. Rodriguez 			       struct ieee80211_reg_rule *intersected_rule)
6369c96477dSLuis R. Rodriguez {
6379c96477dSLuis R. Rodriguez 	const struct ieee80211_freq_range *freq_range1, *freq_range2;
6389c96477dSLuis R. Rodriguez 	struct ieee80211_freq_range *freq_range;
6399c96477dSLuis R. Rodriguez 	const struct ieee80211_power_rule *power_rule1, *power_rule2;
6409c96477dSLuis R. Rodriguez 	struct ieee80211_power_rule *power_rule;
6419c96477dSLuis R. Rodriguez 	u32 freq_diff;
6429c96477dSLuis R. Rodriguez 
6439c96477dSLuis R. Rodriguez 	freq_range1 = &rule1->freq_range;
6449c96477dSLuis R. Rodriguez 	freq_range2 = &rule2->freq_range;
6459c96477dSLuis R. Rodriguez 	freq_range = &intersected_rule->freq_range;
6469c96477dSLuis R. Rodriguez 
6479c96477dSLuis R. Rodriguez 	power_rule1 = &rule1->power_rule;
6489c96477dSLuis R. Rodriguez 	power_rule2 = &rule2->power_rule;
6499c96477dSLuis R. Rodriguez 	power_rule = &intersected_rule->power_rule;
6509c96477dSLuis R. Rodriguez 
6519c96477dSLuis R. Rodriguez 	freq_range->start_freq_khz = max(freq_range1->start_freq_khz,
6529c96477dSLuis R. Rodriguez 					 freq_range2->start_freq_khz);
6539c96477dSLuis R. Rodriguez 	freq_range->end_freq_khz = min(freq_range1->end_freq_khz,
6549c96477dSLuis R. Rodriguez 				       freq_range2->end_freq_khz);
6559c96477dSLuis R. Rodriguez 	freq_range->max_bandwidth_khz = min(freq_range1->max_bandwidth_khz,
6569c96477dSLuis R. Rodriguez 					    freq_range2->max_bandwidth_khz);
6579c96477dSLuis R. Rodriguez 
6589c96477dSLuis R. Rodriguez 	freq_diff = freq_range->end_freq_khz - freq_range->start_freq_khz;
6599c96477dSLuis R. Rodriguez 	if (freq_range->max_bandwidth_khz > freq_diff)
6609c96477dSLuis R. Rodriguez 		freq_range->max_bandwidth_khz = freq_diff;
6619c96477dSLuis R. Rodriguez 
6629c96477dSLuis R. Rodriguez 	power_rule->max_eirp = min(power_rule1->max_eirp,
6639c96477dSLuis R. Rodriguez 		power_rule2->max_eirp);
6649c96477dSLuis R. Rodriguez 	power_rule->max_antenna_gain = min(power_rule1->max_antenna_gain,
6659c96477dSLuis R. Rodriguez 		power_rule2->max_antenna_gain);
6669c96477dSLuis R. Rodriguez 
6671a919318SJohannes Berg 	intersected_rule->flags = rule1->flags | rule2->flags;
6689c96477dSLuis R. Rodriguez 
6699c96477dSLuis R. Rodriguez 	if (!is_valid_reg_rule(intersected_rule))
6709c96477dSLuis R. Rodriguez 		return -EINVAL;
6719c96477dSLuis R. Rodriguez 
6729c96477dSLuis R. Rodriguez 	return 0;
6739c96477dSLuis R. Rodriguez }
6749c96477dSLuis R. Rodriguez 
6759c96477dSLuis R. Rodriguez /**
6769c96477dSLuis R. Rodriguez  * regdom_intersect - do the intersection between two regulatory domains
6779c96477dSLuis R. Rodriguez  * @rd1: first regulatory domain
6789c96477dSLuis R. Rodriguez  * @rd2: second regulatory domain
6799c96477dSLuis R. Rodriguez  *
6809c96477dSLuis R. Rodriguez  * Use this function to get the intersection between two regulatory domains.
6819c96477dSLuis R. Rodriguez  * Once completed we will mark the alpha2 for the rd as intersected, "98",
6829c96477dSLuis R. Rodriguez  * as no one single alpha2 can represent this regulatory domain.
6839c96477dSLuis R. Rodriguez  *
6849c96477dSLuis R. Rodriguez  * Returns a pointer to the regulatory domain structure which will hold the
6859c96477dSLuis R. Rodriguez  * resulting intersection of rules between rd1 and rd2. We will
6869c96477dSLuis R. Rodriguez  * kzalloc() this structure for you.
6879c96477dSLuis R. Rodriguez  */
6881a919318SJohannes Berg static struct ieee80211_regdomain *
6891a919318SJohannes Berg regdom_intersect(const struct ieee80211_regdomain *rd1,
6909c96477dSLuis R. Rodriguez 		 const struct ieee80211_regdomain *rd2)
6919c96477dSLuis R. Rodriguez {
6929c96477dSLuis R. Rodriguez 	int r, size_of_regd;
6939c96477dSLuis R. Rodriguez 	unsigned int x, y;
6949c96477dSLuis R. Rodriguez 	unsigned int num_rules = 0, rule_idx = 0;
6959c96477dSLuis R. Rodriguez 	const struct ieee80211_reg_rule *rule1, *rule2;
6969c96477dSLuis R. Rodriguez 	struct ieee80211_reg_rule *intersected_rule;
6979c96477dSLuis R. Rodriguez 	struct ieee80211_regdomain *rd;
6989c96477dSLuis R. Rodriguez 	/* This is just a dummy holder to help us count */
69974f53cd8SJohannes Berg 	struct ieee80211_reg_rule dummy_rule;
7009c96477dSLuis R. Rodriguez 
7019c96477dSLuis R. Rodriguez 	if (!rd1 || !rd2)
7029c96477dSLuis R. Rodriguez 		return NULL;
7039c96477dSLuis R. Rodriguez 
704fb1fc7adSLuis R. Rodriguez 	/*
705fb1fc7adSLuis R. Rodriguez 	 * First we get a count of the rules we'll need, then we actually
7069c96477dSLuis R. Rodriguez 	 * build them. This is to so we can malloc() and free() a
7079c96477dSLuis R. Rodriguez 	 * regdomain once. The reason we use reg_rules_intersect() here
7089c96477dSLuis R. Rodriguez 	 * is it will return -EINVAL if the rule computed makes no sense.
709fb1fc7adSLuis R. Rodriguez 	 * All rules that do check out OK are valid.
710fb1fc7adSLuis R. Rodriguez 	 */
7119c96477dSLuis R. Rodriguez 
7129c96477dSLuis R. Rodriguez 	for (x = 0; x < rd1->n_reg_rules; x++) {
7139c96477dSLuis R. Rodriguez 		rule1 = &rd1->reg_rules[x];
7149c96477dSLuis R. Rodriguez 		for (y = 0; y < rd2->n_reg_rules; y++) {
7159c96477dSLuis R. Rodriguez 			rule2 = &rd2->reg_rules[y];
71674f53cd8SJohannes Berg 			if (!reg_rules_intersect(rule1, rule2, &dummy_rule))
7179c96477dSLuis R. Rodriguez 				num_rules++;
7189c96477dSLuis R. Rodriguez 		}
7199c96477dSLuis R. Rodriguez 	}
7209c96477dSLuis R. Rodriguez 
7219c96477dSLuis R. Rodriguez 	if (!num_rules)
7229c96477dSLuis R. Rodriguez 		return NULL;
7239c96477dSLuis R. Rodriguez 
7249c96477dSLuis R. Rodriguez 	size_of_regd = sizeof(struct ieee80211_regdomain) +
72582f20856SJohannes Berg 		       num_rules * sizeof(struct ieee80211_reg_rule);
7269c96477dSLuis R. Rodriguez 
7279c96477dSLuis R. Rodriguez 	rd = kzalloc(size_of_regd, GFP_KERNEL);
7289c96477dSLuis R. Rodriguez 	if (!rd)
7299c96477dSLuis R. Rodriguez 		return NULL;
7309c96477dSLuis R. Rodriguez 
7318a57fff0SJohannes Berg 	for (x = 0; x < rd1->n_reg_rules && rule_idx < num_rules; x++) {
7329c96477dSLuis R. Rodriguez 		rule1 = &rd1->reg_rules[x];
7338a57fff0SJohannes Berg 		for (y = 0; y < rd2->n_reg_rules && rule_idx < num_rules; y++) {
7349c96477dSLuis R. Rodriguez 			rule2 = &rd2->reg_rules[y];
735fb1fc7adSLuis R. Rodriguez 			/*
736fb1fc7adSLuis R. Rodriguez 			 * This time around instead of using the stack lets
7379c96477dSLuis R. Rodriguez 			 * write to the target rule directly saving ourselves
738fb1fc7adSLuis R. Rodriguez 			 * a memcpy()
739fb1fc7adSLuis R. Rodriguez 			 */
7409c96477dSLuis R. Rodriguez 			intersected_rule = &rd->reg_rules[rule_idx];
7411a919318SJohannes Berg 			r = reg_rules_intersect(rule1, rule2, intersected_rule);
742fb1fc7adSLuis R. Rodriguez 			/*
743fb1fc7adSLuis R. Rodriguez 			 * No need to memset here the intersected rule here as
744fb1fc7adSLuis R. Rodriguez 			 * we're not using the stack anymore
745fb1fc7adSLuis R. Rodriguez 			 */
7469c96477dSLuis R. Rodriguez 			if (r)
7479c96477dSLuis R. Rodriguez 				continue;
7489c96477dSLuis R. Rodriguez 			rule_idx++;
7499c96477dSLuis R. Rodriguez 		}
7509c96477dSLuis R. Rodriguez 	}
7519c96477dSLuis R. Rodriguez 
7529c96477dSLuis R. Rodriguez 	if (rule_idx != num_rules) {
7539c96477dSLuis R. Rodriguez 		kfree(rd);
7549c96477dSLuis R. Rodriguez 		return NULL;
7559c96477dSLuis R. Rodriguez 	}
7569c96477dSLuis R. Rodriguez 
7579c96477dSLuis R. Rodriguez 	rd->n_reg_rules = num_rules;
7589c96477dSLuis R. Rodriguez 	rd->alpha2[0] = '9';
7599c96477dSLuis R. Rodriguez 	rd->alpha2[1] = '8';
760adbfb058SLuis R. Rodriguez 	rd->dfs_region = reg_intersect_dfs_region(rd1->dfs_region,
761adbfb058SLuis R. Rodriguez 						  rd2->dfs_region);
7629c96477dSLuis R. Rodriguez 
7639c96477dSLuis R. Rodriguez 	return rd;
7649c96477dSLuis R. Rodriguez }
7659c96477dSLuis R. Rodriguez 
766fb1fc7adSLuis R. Rodriguez /*
767fb1fc7adSLuis R. Rodriguez  * XXX: add support for the rest of enum nl80211_reg_rule_flags, we may
768fb1fc7adSLuis R. Rodriguez  * want to just have the channel structure use these
769fb1fc7adSLuis R. Rodriguez  */
770b2e1b302SLuis R. Rodriguez static u32 map_regdom_flags(u32 rd_flags)
771b2e1b302SLuis R. Rodriguez {
772b2e1b302SLuis R. Rodriguez 	u32 channel_flags = 0;
7738fe02e16SLuis R. Rodriguez 	if (rd_flags & NL80211_RRF_NO_IR_ALL)
7748fe02e16SLuis R. Rodriguez 		channel_flags |= IEEE80211_CHAN_NO_IR;
775b2e1b302SLuis R. Rodriguez 	if (rd_flags & NL80211_RRF_DFS)
776b2e1b302SLuis R. Rodriguez 		channel_flags |= IEEE80211_CHAN_RADAR;
77703f6b084SSeth Forshee 	if (rd_flags & NL80211_RRF_NO_OFDM)
77803f6b084SSeth Forshee 		channel_flags |= IEEE80211_CHAN_NO_OFDM;
779b2e1b302SLuis R. Rodriguez 	return channel_flags;
780b2e1b302SLuis R. Rodriguez }
781b2e1b302SLuis R. Rodriguez 
782361c9c8bSJohannes Berg static const struct ieee80211_reg_rule *
783361c9c8bSJohannes Berg freq_reg_info_regd(struct wiphy *wiphy, u32 center_freq,
7845d885b99SJohannes Berg 		   const struct ieee80211_regdomain *regd)
7858318d78aSJohannes Berg {
7868318d78aSJohannes Berg 	int i;
7870c7dc45dSLuis R. Rodriguez 	bool band_rule_found = false;
788038659e7SLuis R. Rodriguez 	bool bw_fits = false;
789038659e7SLuis R. Rodriguez 
7903e0c3ff3SLuis R. Rodriguez 	if (!regd)
791361c9c8bSJohannes Berg 		return ERR_PTR(-EINVAL);
792b2e1b302SLuis R. Rodriguez 
7933e0c3ff3SLuis R. Rodriguez 	for (i = 0; i < regd->n_reg_rules; i++) {
794b2e1b302SLuis R. Rodriguez 		const struct ieee80211_reg_rule *rr;
795b2e1b302SLuis R. Rodriguez 		const struct ieee80211_freq_range *fr = NULL;
796b2e1b302SLuis R. Rodriguez 
7973e0c3ff3SLuis R. Rodriguez 		rr = &regd->reg_rules[i];
798b2e1b302SLuis R. Rodriguez 		fr = &rr->freq_range;
7990c7dc45dSLuis R. Rodriguez 
800fb1fc7adSLuis R. Rodriguez 		/*
801fb1fc7adSLuis R. Rodriguez 		 * We only need to know if one frequency rule was
8020c7dc45dSLuis R. Rodriguez 		 * was in center_freq's band, that's enough, so lets
803fb1fc7adSLuis R. Rodriguez 		 * not overwrite it once found
804fb1fc7adSLuis R. Rodriguez 		 */
8050c7dc45dSLuis R. Rodriguez 		if (!band_rule_found)
8060c7dc45dSLuis R. Rodriguez 			band_rule_found = freq_in_rule_band(fr, center_freq);
8070c7dc45dSLuis R. Rodriguez 
808fe7ef5e9SJohannes Berg 		bw_fits = reg_does_bw_fit(fr, center_freq, MHZ_TO_KHZ(20));
8090c7dc45dSLuis R. Rodriguez 
810361c9c8bSJohannes Berg 		if (band_rule_found && bw_fits)
811361c9c8bSJohannes Berg 			return rr;
8128318d78aSJohannes Berg 	}
8138318d78aSJohannes Berg 
8140c7dc45dSLuis R. Rodriguez 	if (!band_rule_found)
815361c9c8bSJohannes Berg 		return ERR_PTR(-ERANGE);
8160c7dc45dSLuis R. Rodriguez 
817361c9c8bSJohannes Berg 	return ERR_PTR(-EINVAL);
818b2e1b302SLuis R. Rodriguez }
819b2e1b302SLuis R. Rodriguez 
820361c9c8bSJohannes Berg const struct ieee80211_reg_rule *freq_reg_info(struct wiphy *wiphy,
821361c9c8bSJohannes Berg 					       u32 center_freq)
8221fa25e41SLuis R. Rodriguez {
8235d885b99SJohannes Berg 	const struct ieee80211_regdomain *regd;
824c492db37SJohannes Berg 	struct regulatory_request *lr = get_last_request();
8251a919318SJohannes Berg 
8265d885b99SJohannes Berg 	/*
8275d885b99SJohannes Berg 	 * Follow the driver's regulatory domain, if present, unless a country
8285d885b99SJohannes Berg 	 * IE has been processed or a user wants to help complaince further
8295d885b99SJohannes Berg 	 */
830c492db37SJohannes Berg 	if (lr->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE &&
831c492db37SJohannes Berg 	    lr->initiator != NL80211_REGDOM_SET_BY_USER &&
8325d885b99SJohannes Berg 	    wiphy->regd)
833458f4f9eSJohannes Berg 		regd = get_wiphy_regdom(wiphy);
8345d885b99SJohannes Berg 	else
835458f4f9eSJohannes Berg 		regd = get_cfg80211_regdom();
8365d885b99SJohannes Berg 
837361c9c8bSJohannes Berg 	return freq_reg_info_regd(wiphy, center_freq, regd);
8381fa25e41SLuis R. Rodriguez }
8394f366c5dSJohn W. Linville EXPORT_SYMBOL(freq_reg_info);
840b2e1b302SLuis R. Rodriguez 
841034c6d6eSLuis R. Rodriguez const char *reg_initiator_name(enum nl80211_reg_initiator initiator)
842926a0a09SLuis R. Rodriguez {
843926a0a09SLuis R. Rodriguez 	switch (initiator) {
844926a0a09SLuis R. Rodriguez 	case NL80211_REGDOM_SET_BY_CORE:
845034c6d6eSLuis R. Rodriguez 		return "core";
846926a0a09SLuis R. Rodriguez 	case NL80211_REGDOM_SET_BY_USER:
847034c6d6eSLuis R. Rodriguez 		return "user";
848926a0a09SLuis R. Rodriguez 	case NL80211_REGDOM_SET_BY_DRIVER:
849034c6d6eSLuis R. Rodriguez 		return "driver";
850926a0a09SLuis R. Rodriguez 	case NL80211_REGDOM_SET_BY_COUNTRY_IE:
851034c6d6eSLuis R. Rodriguez 		return "country IE";
852926a0a09SLuis R. Rodriguez 	default:
853926a0a09SLuis R. Rodriguez 		WARN_ON(1);
854034c6d6eSLuis R. Rodriguez 		return "bug";
855926a0a09SLuis R. Rodriguez 	}
856926a0a09SLuis R. Rodriguez }
857034c6d6eSLuis R. Rodriguez EXPORT_SYMBOL(reg_initiator_name);
858e702d3cfSLuis R. Rodriguez 
859034c6d6eSLuis R. Rodriguez #ifdef CONFIG_CFG80211_REG_DEBUG
860e702d3cfSLuis R. Rodriguez static void chan_reg_rule_print_dbg(struct ieee80211_channel *chan,
861e702d3cfSLuis R. Rodriguez 				    const struct ieee80211_reg_rule *reg_rule)
862e702d3cfSLuis R. Rodriguez {
863e702d3cfSLuis R. Rodriguez 	const struct ieee80211_power_rule *power_rule;
864e702d3cfSLuis R. Rodriguez 	const struct ieee80211_freq_range *freq_range;
865e702d3cfSLuis R. Rodriguez 	char max_antenna_gain[32];
866e702d3cfSLuis R. Rodriguez 
867e702d3cfSLuis R. Rodriguez 	power_rule = &reg_rule->power_rule;
868e702d3cfSLuis R. Rodriguez 	freq_range = &reg_rule->freq_range;
869e702d3cfSLuis R. Rodriguez 
870e702d3cfSLuis R. Rodriguez 	if (!power_rule->max_antenna_gain)
871e702d3cfSLuis R. Rodriguez 		snprintf(max_antenna_gain, 32, "N/A");
872e702d3cfSLuis R. Rodriguez 	else
873e702d3cfSLuis R. Rodriguez 		snprintf(max_antenna_gain, 32, "%d", power_rule->max_antenna_gain);
874e702d3cfSLuis R. Rodriguez 
875fe7ef5e9SJohannes Berg 	REG_DBG_PRINT("Updating information on frequency %d MHz with regulatory rule:\n",
876fe7ef5e9SJohannes Berg 		      chan->center_freq);
877e702d3cfSLuis R. Rodriguez 
87856e6786eSPavel Roskin 	REG_DBG_PRINT("%d KHz - %d KHz @ %d KHz), (%s mBi, %d mBm)\n",
8791a919318SJohannes Berg 		      freq_range->start_freq_khz, freq_range->end_freq_khz,
8801a919318SJohannes Berg 		      freq_range->max_bandwidth_khz, max_antenna_gain,
881e702d3cfSLuis R. Rodriguez 		      power_rule->max_eirp);
882e702d3cfSLuis R. Rodriguez }
883e702d3cfSLuis R. Rodriguez #else
884e702d3cfSLuis R. Rodriguez static void chan_reg_rule_print_dbg(struct ieee80211_channel *chan,
885e702d3cfSLuis R. Rodriguez 				    const struct ieee80211_reg_rule *reg_rule)
886e702d3cfSLuis R. Rodriguez {
887e702d3cfSLuis R. Rodriguez 	return;
888e702d3cfSLuis R. Rodriguez }
889926a0a09SLuis R. Rodriguez #endif
890926a0a09SLuis R. Rodriguez 
891038659e7SLuis R. Rodriguez /*
892038659e7SLuis R. Rodriguez  * Note that right now we assume the desired channel bandwidth
893038659e7SLuis R. Rodriguez  * is always 20 MHz for each individual channel (HT40 uses 20 MHz
894fe7ef5e9SJohannes Berg  * per channel, the primary and the extension channel).
895038659e7SLuis R. Rodriguez  */
8967ca43d03SLuis R. Rodriguez static void handle_channel(struct wiphy *wiphy,
8977ca43d03SLuis R. Rodriguez 			   enum nl80211_reg_initiator initiator,
898fdc9d7b2SJohannes Berg 			   struct ieee80211_channel *chan)
899b2e1b302SLuis R. Rodriguez {
900038659e7SLuis R. Rodriguez 	u32 flags, bw_flags = 0;
901b2e1b302SLuis R. Rodriguez 	const struct ieee80211_reg_rule *reg_rule = NULL;
902b2e1b302SLuis R. Rodriguez 	const struct ieee80211_power_rule *power_rule = NULL;
903038659e7SLuis R. Rodriguez 	const struct ieee80211_freq_range *freq_range = NULL;
904fe33eb39SLuis R. Rodriguez 	struct wiphy *request_wiphy = NULL;
905c492db37SJohannes Berg 	struct regulatory_request *lr = get_last_request();
906a92a3ce7SLuis R. Rodriguez 
907c492db37SJohannes Berg 	request_wiphy = wiphy_idx_to_wiphy(lr->wiphy_idx);
908a92a3ce7SLuis R. Rodriguez 
909a92a3ce7SLuis R. Rodriguez 	flags = chan->orig_flags;
910b2e1b302SLuis R. Rodriguez 
911361c9c8bSJohannes Berg 	reg_rule = freq_reg_info(wiphy, MHZ_TO_KHZ(chan->center_freq));
912361c9c8bSJohannes Berg 	if (IS_ERR(reg_rule)) {
913ca4ffe8fSLuis R. Rodriguez 		/*
914ca4ffe8fSLuis R. Rodriguez 		 * We will disable all channels that do not match our
91525985edcSLucas De Marchi 		 * received regulatory rule unless the hint is coming
916ca4ffe8fSLuis R. Rodriguez 		 * from a Country IE and the Country IE had no information
917ca4ffe8fSLuis R. Rodriguez 		 * about a band. The IEEE 802.11 spec allows for an AP
918ca4ffe8fSLuis R. Rodriguez 		 * to send only a subset of the regulatory rules allowed,
919ca4ffe8fSLuis R. Rodriguez 		 * so an AP in the US that only supports 2.4 GHz may only send
920ca4ffe8fSLuis R. Rodriguez 		 * a country IE with information for the 2.4 GHz band
921ca4ffe8fSLuis R. Rodriguez 		 * while 5 GHz is still supported.
922ca4ffe8fSLuis R. Rodriguez 		 */
923ca4ffe8fSLuis R. Rodriguez 		if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE &&
924361c9c8bSJohannes Berg 		    PTR_ERR(reg_rule) == -ERANGE)
9258318d78aSJohannes Berg 			return;
9268318d78aSJohannes Berg 
927cc493e4fSLuis R. Rodriguez 		if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER &&
928cc493e4fSLuis R. Rodriguez 		    request_wiphy && request_wiphy == wiphy &&
929a2f73b6cSLuis R. Rodriguez 		    request_wiphy->regulatory_flags & REGULATORY_STRICT_REG) {
930cc493e4fSLuis R. Rodriguez 			REG_DBG_PRINT("Disabling freq %d MHz for good\n",
931cc493e4fSLuis R. Rodriguez 				      chan->center_freq);
932cc493e4fSLuis R. Rodriguez 			chan->orig_flags |= IEEE80211_CHAN_DISABLED;
933cc493e4fSLuis R. Rodriguez 			chan->flags = chan->orig_flags;
934cc493e4fSLuis R. Rodriguez 		} else {
935cc493e4fSLuis R. Rodriguez 			REG_DBG_PRINT("Disabling freq %d MHz\n",
936cc493e4fSLuis R. Rodriguez 				      chan->center_freq);
937990de49fSJohannes Berg 			chan->flags |= IEEE80211_CHAN_DISABLED;
938cc493e4fSLuis R. Rodriguez 		}
939ca4ffe8fSLuis R. Rodriguez 		return;
940ca4ffe8fSLuis R. Rodriguez 	}
941ca4ffe8fSLuis R. Rodriguez 
942fe7ef5e9SJohannes Berg 	chan_reg_rule_print_dbg(chan, reg_rule);
943e702d3cfSLuis R. Rodriguez 
944b2e1b302SLuis R. Rodriguez 	power_rule = &reg_rule->power_rule;
945038659e7SLuis R. Rodriguez 	freq_range = &reg_rule->freq_range;
946038659e7SLuis R. Rodriguez 
947038659e7SLuis R. Rodriguez 	if (freq_range->max_bandwidth_khz < MHZ_TO_KHZ(40))
948038659e7SLuis R. Rodriguez 		bw_flags = IEEE80211_CHAN_NO_HT40;
949c7a6ee27SJohannes Berg 	if (freq_range->max_bandwidth_khz < MHZ_TO_KHZ(80))
950c7a6ee27SJohannes Berg 		bw_flags |= IEEE80211_CHAN_NO_80MHZ;
951c7a6ee27SJohannes Berg 	if (freq_range->max_bandwidth_khz < MHZ_TO_KHZ(160))
952c7a6ee27SJohannes Berg 		bw_flags |= IEEE80211_CHAN_NO_160MHZ;
953b2e1b302SLuis R. Rodriguez 
954c492db37SJohannes Berg 	if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER &&
955806a9e39SLuis R. Rodriguez 	    request_wiphy && request_wiphy == wiphy &&
956a2f73b6cSLuis R. Rodriguez 	    request_wiphy->regulatory_flags & REGULATORY_STRICT_REG) {
957fb1fc7adSLuis R. Rodriguez 		/*
95825985edcSLucas De Marchi 		 * This guarantees the driver's requested regulatory domain
959f976376dSLuis R. Rodriguez 		 * will always be used as a base for further regulatory
960fb1fc7adSLuis R. Rodriguez 		 * settings
961fb1fc7adSLuis R. Rodriguez 		 */
962f976376dSLuis R. Rodriguez 		chan->flags = chan->orig_flags =
963038659e7SLuis R. Rodriguez 			map_regdom_flags(reg_rule->flags) | bw_flags;
964f976376dSLuis R. Rodriguez 		chan->max_antenna_gain = chan->orig_mag =
965f976376dSLuis R. Rodriguez 			(int) MBI_TO_DBI(power_rule->max_antenna_gain);
966279f0f55SFelix Fietkau 		chan->max_reg_power = chan->max_power = chan->orig_mpwr =
967f976376dSLuis R. Rodriguez 			(int) MBM_TO_DBM(power_rule->max_eirp);
968f976376dSLuis R. Rodriguez 		return;
969f976376dSLuis R. Rodriguez 	}
970f976376dSLuis R. Rodriguez 
97104f39047SSimon Wunderlich 	chan->dfs_state = NL80211_DFS_USABLE;
97204f39047SSimon Wunderlich 	chan->dfs_state_entered = jiffies;
97304f39047SSimon Wunderlich 
974aa3d7eefSRajkumar Manoharan 	chan->beacon_found = false;
975038659e7SLuis R. Rodriguez 	chan->flags = flags | bw_flags | map_regdom_flags(reg_rule->flags);
9761a919318SJohannes Berg 	chan->max_antenna_gain =
9771a919318SJohannes Berg 		min_t(int, chan->orig_mag,
9781a919318SJohannes Berg 		      MBI_TO_DBI(power_rule->max_antenna_gain));
979eccc068eSHong Wu 	chan->max_reg_power = (int) MBM_TO_DBM(power_rule->max_eirp);
9805e31fc08SStanislaw Gruszka 	if (chan->orig_mpwr) {
9815e31fc08SStanislaw Gruszka 		/*
982a09a85a0SLuis R. Rodriguez 		 * Devices that use REGULATORY_COUNTRY_IE_FOLLOW_POWER
983a09a85a0SLuis R. Rodriguez 		 * will always follow the passed country IE power settings.
9845e31fc08SStanislaw Gruszka 		 */
9855e31fc08SStanislaw Gruszka 		if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE &&
986a09a85a0SLuis R. Rodriguez 		    wiphy->regulatory_flags & REGULATORY_COUNTRY_IE_FOLLOW_POWER)
9875e31fc08SStanislaw Gruszka 			chan->max_power = chan->max_reg_power;
9885e31fc08SStanislaw Gruszka 		else
9895e31fc08SStanislaw Gruszka 			chan->max_power = min(chan->orig_mpwr,
9905e31fc08SStanislaw Gruszka 					      chan->max_reg_power);
9915e31fc08SStanislaw Gruszka 	} else
9925e31fc08SStanislaw Gruszka 		chan->max_power = chan->max_reg_power;
9938318d78aSJohannes Berg }
9948318d78aSJohannes Berg 
9957ca43d03SLuis R. Rodriguez static void handle_band(struct wiphy *wiphy,
996fdc9d7b2SJohannes Berg 			enum nl80211_reg_initiator initiator,
997fdc9d7b2SJohannes Berg 			struct ieee80211_supported_band *sband)
9988318d78aSJohannes Berg {
999a92a3ce7SLuis R. Rodriguez 	unsigned int i;
1000a92a3ce7SLuis R. Rodriguez 
1001fdc9d7b2SJohannes Berg 	if (!sband)
1002fdc9d7b2SJohannes Berg 		return;
10038318d78aSJohannes Berg 
10048318d78aSJohannes Berg 	for (i = 0; i < sband->n_channels; i++)
1005fdc9d7b2SJohannes Berg 		handle_channel(wiphy, initiator, &sband->channels[i]);
10068318d78aSJohannes Berg }
10078318d78aSJohannes Berg 
100857b5ce07SLuis R. Rodriguez static bool reg_request_cell_base(struct regulatory_request *request)
100957b5ce07SLuis R. Rodriguez {
101057b5ce07SLuis R. Rodriguez 	if (request->initiator != NL80211_REGDOM_SET_BY_USER)
101157b5ce07SLuis R. Rodriguez 		return false;
10121a919318SJohannes Berg 	return request->user_reg_hint_type == NL80211_USER_REG_HINT_CELL_BASE;
101357b5ce07SLuis R. Rodriguez }
101457b5ce07SLuis R. Rodriguez 
101557b5ce07SLuis R. Rodriguez bool reg_last_request_cell_base(void)
101657b5ce07SLuis R. Rodriguez {
101738fd2143SJohannes Berg 	return reg_request_cell_base(get_last_request());
101857b5ce07SLuis R. Rodriguez }
101957b5ce07SLuis R. Rodriguez 
102057b5ce07SLuis R. Rodriguez #ifdef CONFIG_CFG80211_CERTIFICATION_ONUS
102157b5ce07SLuis R. Rodriguez /* Core specific check */
10222f92212bSJohannes Berg static enum reg_request_treatment
10232f92212bSJohannes Berg reg_ignore_cell_hint(struct regulatory_request *pending_request)
102457b5ce07SLuis R. Rodriguez {
1025c492db37SJohannes Berg 	struct regulatory_request *lr = get_last_request();
102657b5ce07SLuis R. Rodriguez 
102757b5ce07SLuis R. Rodriguez 	if (!reg_num_devs_support_basehint)
10282f92212bSJohannes Berg 		return REG_REQ_IGNORE;
102957b5ce07SLuis R. Rodriguez 
1030c492db37SJohannes Berg 	if (reg_request_cell_base(lr) &&
10311a919318SJohannes Berg 	    !regdom_changes(pending_request->alpha2))
10322f92212bSJohannes Berg 		return REG_REQ_ALREADY_SET;
10331a919318SJohannes Berg 
10342f92212bSJohannes Berg 	return REG_REQ_OK;
103557b5ce07SLuis R. Rodriguez }
103657b5ce07SLuis R. Rodriguez 
103757b5ce07SLuis R. Rodriguez /* Device specific check */
103857b5ce07SLuis R. Rodriguez static bool reg_dev_ignore_cell_hint(struct wiphy *wiphy)
103957b5ce07SLuis R. Rodriguez {
10401a919318SJohannes Berg 	return !(wiphy->features & NL80211_FEATURE_CELL_BASE_REG_HINTS);
104157b5ce07SLuis R. Rodriguez }
104257b5ce07SLuis R. Rodriguez #else
104357b5ce07SLuis R. Rodriguez static int reg_ignore_cell_hint(struct regulatory_request *pending_request)
104457b5ce07SLuis R. Rodriguez {
10452f92212bSJohannes Berg 	return REG_REQ_IGNORE;
104657b5ce07SLuis R. Rodriguez }
10471a919318SJohannes Berg 
10481a919318SJohannes Berg static bool reg_dev_ignore_cell_hint(struct wiphy *wiphy)
104957b5ce07SLuis R. Rodriguez {
105057b5ce07SLuis R. Rodriguez 	return true;
105157b5ce07SLuis R. Rodriguez }
105257b5ce07SLuis R. Rodriguez #endif
105357b5ce07SLuis R. Rodriguez 
1054fa1fb9cbSLuis R. Rodriguez static bool wiphy_strict_alpha2_regd(struct wiphy *wiphy)
1055fa1fb9cbSLuis R. Rodriguez {
1056a2f73b6cSLuis R. Rodriguez 	if (wiphy->regulatory_flags & REGULATORY_STRICT_REG &&
1057a2f73b6cSLuis R. Rodriguez 	    !(wiphy->regulatory_flags & REGULATORY_CUSTOM_REG))
1058fa1fb9cbSLuis R. Rodriguez 		return true;
1059fa1fb9cbSLuis R. Rodriguez 	return false;
1060fa1fb9cbSLuis R. Rodriguez }
106157b5ce07SLuis R. Rodriguez 
10627db90f4aSLuis R. Rodriguez static bool ignore_reg_update(struct wiphy *wiphy,
10637db90f4aSLuis R. Rodriguez 			      enum nl80211_reg_initiator initiator)
106414b9815aSLuis R. Rodriguez {
1065c492db37SJohannes Berg 	struct regulatory_request *lr = get_last_request();
1066c492db37SJohannes Berg 
1067c492db37SJohannes Berg 	if (!lr) {
1068034c6d6eSLuis R. Rodriguez 		REG_DBG_PRINT("Ignoring regulatory request set by %s "
1069034c6d6eSLuis R. Rodriguez 			      "since last_request is not set\n",
1070926a0a09SLuis R. Rodriguez 			      reg_initiator_name(initiator));
107114b9815aSLuis R. Rodriguez 		return true;
1072926a0a09SLuis R. Rodriguez 	}
1073926a0a09SLuis R. Rodriguez 
10747db90f4aSLuis R. Rodriguez 	if (initiator == NL80211_REGDOM_SET_BY_CORE &&
1075a2f73b6cSLuis R. Rodriguez 	    wiphy->regulatory_flags & REGULATORY_CUSTOM_REG) {
1076034c6d6eSLuis R. Rodriguez 		REG_DBG_PRINT("Ignoring regulatory request set by %s "
1077034c6d6eSLuis R. Rodriguez 			      "since the driver uses its own custom "
1078034c6d6eSLuis R. Rodriguez 			      "regulatory domain\n",
1079926a0a09SLuis R. Rodriguez 			      reg_initiator_name(initiator));
108014b9815aSLuis R. Rodriguez 		return true;
1081926a0a09SLuis R. Rodriguez 	}
1082926a0a09SLuis R. Rodriguez 
1083fb1fc7adSLuis R. Rodriguez 	/*
1084fb1fc7adSLuis R. Rodriguez 	 * wiphy->regd will be set once the device has its own
1085fb1fc7adSLuis R. Rodriguez 	 * desired regulatory domain set
1086fb1fc7adSLuis R. Rodriguez 	 */
1087fa1fb9cbSLuis R. Rodriguez 	if (wiphy_strict_alpha2_regd(wiphy) && !wiphy->regd &&
1088749b527bSLuis R. Rodriguez 	    initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE &&
1089c492db37SJohannes Berg 	    !is_world_regdom(lr->alpha2)) {
1090034c6d6eSLuis R. Rodriguez 		REG_DBG_PRINT("Ignoring regulatory request set by %s "
1091034c6d6eSLuis R. Rodriguez 			      "since the driver requires its own regulatory "
1092034c6d6eSLuis R. Rodriguez 			      "domain to be set first\n",
1093926a0a09SLuis R. Rodriguez 			      reg_initiator_name(initiator));
109414b9815aSLuis R. Rodriguez 		return true;
1095926a0a09SLuis R. Rodriguez 	}
1096926a0a09SLuis R. Rodriguez 
1097c492db37SJohannes Berg 	if (reg_request_cell_base(lr))
109857b5ce07SLuis R. Rodriguez 		return reg_dev_ignore_cell_hint(wiphy);
109957b5ce07SLuis R. Rodriguez 
110014b9815aSLuis R. Rodriguez 	return false;
110114b9815aSLuis R. Rodriguez }
110214b9815aSLuis R. Rodriguez 
11033195e489SLuis R. Rodriguez static bool reg_is_world_roaming(struct wiphy *wiphy)
11043195e489SLuis R. Rodriguez {
11053195e489SLuis R. Rodriguez 	const struct ieee80211_regdomain *cr = get_cfg80211_regdom();
11063195e489SLuis R. Rodriguez 	const struct ieee80211_regdomain *wr = get_wiphy_regdom(wiphy);
11073195e489SLuis R. Rodriguez 	struct regulatory_request *lr = get_last_request();
11083195e489SLuis R. Rodriguez 
11093195e489SLuis R. Rodriguez 	if (is_world_regdom(cr->alpha2) || (wr && is_world_regdom(wr->alpha2)))
11103195e489SLuis R. Rodriguez 		return true;
11113195e489SLuis R. Rodriguez 
11123195e489SLuis R. Rodriguez 	if (lr && lr->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE &&
1113a2f73b6cSLuis R. Rodriguez 	    wiphy->regulatory_flags & REGULATORY_CUSTOM_REG)
11143195e489SLuis R. Rodriguez 		return true;
11153195e489SLuis R. Rodriguez 
11163195e489SLuis R. Rodriguez 	return false;
11173195e489SLuis R. Rodriguez }
11183195e489SLuis R. Rodriguez 
11191a919318SJohannes Berg static void handle_reg_beacon(struct wiphy *wiphy, unsigned int chan_idx,
1120e38f8a7aSLuis R. Rodriguez 			      struct reg_beacon *reg_beacon)
1121e38f8a7aSLuis R. Rodriguez {
1122e38f8a7aSLuis R. Rodriguez 	struct ieee80211_supported_band *sband;
1123e38f8a7aSLuis R. Rodriguez 	struct ieee80211_channel *chan;
11246bad8766SLuis R. Rodriguez 	bool channel_changed = false;
11256bad8766SLuis R. Rodriguez 	struct ieee80211_channel chan_before;
1126e38f8a7aSLuis R. Rodriguez 
1127e38f8a7aSLuis R. Rodriguez 	sband = wiphy->bands[reg_beacon->chan.band];
1128e38f8a7aSLuis R. Rodriguez 	chan = &sband->channels[chan_idx];
1129e38f8a7aSLuis R. Rodriguez 
1130e38f8a7aSLuis R. Rodriguez 	if (likely(chan->center_freq != reg_beacon->chan.center_freq))
1131e38f8a7aSLuis R. Rodriguez 		return;
1132e38f8a7aSLuis R. Rodriguez 
11336bad8766SLuis R. Rodriguez 	if (chan->beacon_found)
11346bad8766SLuis R. Rodriguez 		return;
11356bad8766SLuis R. Rodriguez 
11366bad8766SLuis R. Rodriguez 	chan->beacon_found = true;
11376bad8766SLuis R. Rodriguez 
11380f500a5fSLuis R. Rodriguez 	if (!reg_is_world_roaming(wiphy))
11390f500a5fSLuis R. Rodriguez 		return;
11400f500a5fSLuis R. Rodriguez 
1141a2f73b6cSLuis R. Rodriguez 	if (wiphy->regulatory_flags & REGULATORY_DISABLE_BEACON_HINTS)
114237184244SLuis R. Rodriguez 		return;
114337184244SLuis R. Rodriguez 
11446bad8766SLuis R. Rodriguez 	chan_before.center_freq = chan->center_freq;
11456bad8766SLuis R. Rodriguez 	chan_before.flags = chan->flags;
11466bad8766SLuis R. Rodriguez 
11478fe02e16SLuis R. Rodriguez 	if (chan->flags & IEEE80211_CHAN_NO_IR) {
11488fe02e16SLuis R. Rodriguez 		chan->flags &= ~IEEE80211_CHAN_NO_IR;
11496bad8766SLuis R. Rodriguez 		channel_changed = true;
1150e38f8a7aSLuis R. Rodriguez 	}
1151e38f8a7aSLuis R. Rodriguez 
11526bad8766SLuis R. Rodriguez 	if (channel_changed)
11536bad8766SLuis R. Rodriguez 		nl80211_send_beacon_hint_event(wiphy, &chan_before, chan);
1154e38f8a7aSLuis R. Rodriguez }
1155e38f8a7aSLuis R. Rodriguez 
1156e38f8a7aSLuis R. Rodriguez /*
1157e38f8a7aSLuis R. Rodriguez  * Called when a scan on a wiphy finds a beacon on
1158e38f8a7aSLuis R. Rodriguez  * new channel
1159e38f8a7aSLuis R. Rodriguez  */
1160e38f8a7aSLuis R. Rodriguez static void wiphy_update_new_beacon(struct wiphy *wiphy,
1161e38f8a7aSLuis R. Rodriguez 				    struct reg_beacon *reg_beacon)
1162e38f8a7aSLuis R. Rodriguez {
1163e38f8a7aSLuis R. Rodriguez 	unsigned int i;
1164e38f8a7aSLuis R. Rodriguez 	struct ieee80211_supported_band *sband;
1165e38f8a7aSLuis R. Rodriguez 
1166e38f8a7aSLuis R. Rodriguez 	if (!wiphy->bands[reg_beacon->chan.band])
1167e38f8a7aSLuis R. Rodriguez 		return;
1168e38f8a7aSLuis R. Rodriguez 
1169e38f8a7aSLuis R. Rodriguez 	sband = wiphy->bands[reg_beacon->chan.band];
1170e38f8a7aSLuis R. Rodriguez 
1171e38f8a7aSLuis R. Rodriguez 	for (i = 0; i < sband->n_channels; i++)
1172e38f8a7aSLuis R. Rodriguez 		handle_reg_beacon(wiphy, i, reg_beacon);
1173e38f8a7aSLuis R. Rodriguez }
1174e38f8a7aSLuis R. Rodriguez 
1175e38f8a7aSLuis R. Rodriguez /*
1176e38f8a7aSLuis R. Rodriguez  * Called upon reg changes or a new wiphy is added
1177e38f8a7aSLuis R. Rodriguez  */
1178e38f8a7aSLuis R. Rodriguez static void wiphy_update_beacon_reg(struct wiphy *wiphy)
1179e38f8a7aSLuis R. Rodriguez {
1180e38f8a7aSLuis R. Rodriguez 	unsigned int i;
1181e38f8a7aSLuis R. Rodriguez 	struct ieee80211_supported_band *sband;
1182e38f8a7aSLuis R. Rodriguez 	struct reg_beacon *reg_beacon;
1183e38f8a7aSLuis R. Rodriguez 
1184e38f8a7aSLuis R. Rodriguez 	list_for_each_entry(reg_beacon, &reg_beacon_list, list) {
1185e38f8a7aSLuis R. Rodriguez 		if (!wiphy->bands[reg_beacon->chan.band])
1186e38f8a7aSLuis R. Rodriguez 			continue;
1187e38f8a7aSLuis R. Rodriguez 		sband = wiphy->bands[reg_beacon->chan.band];
1188e38f8a7aSLuis R. Rodriguez 		for (i = 0; i < sband->n_channels; i++)
1189e38f8a7aSLuis R. Rodriguez 			handle_reg_beacon(wiphy, i, reg_beacon);
1190e38f8a7aSLuis R. Rodriguez 	}
1191e38f8a7aSLuis R. Rodriguez }
1192e38f8a7aSLuis R. Rodriguez 
1193e38f8a7aSLuis R. Rodriguez /* Reap the advantages of previously found beacons */
1194e38f8a7aSLuis R. Rodriguez static void reg_process_beacons(struct wiphy *wiphy)
1195e38f8a7aSLuis R. Rodriguez {
1196b1ed8dddSLuis R. Rodriguez 	/*
1197b1ed8dddSLuis R. Rodriguez 	 * Means we are just firing up cfg80211, so no beacons would
1198b1ed8dddSLuis R. Rodriguez 	 * have been processed yet.
1199b1ed8dddSLuis R. Rodriguez 	 */
1200b1ed8dddSLuis R. Rodriguez 	if (!last_request)
1201b1ed8dddSLuis R. Rodriguez 		return;
1202e38f8a7aSLuis R. Rodriguez 	wiphy_update_beacon_reg(wiphy);
1203e38f8a7aSLuis R. Rodriguez }
1204e38f8a7aSLuis R. Rodriguez 
12051a919318SJohannes Berg static bool is_ht40_allowed(struct ieee80211_channel *chan)
1206038659e7SLuis R. Rodriguez {
1207038659e7SLuis R. Rodriguez 	if (!chan)
1208038659e7SLuis R. Rodriguez 		return false;
12091a919318SJohannes Berg 	if (chan->flags & IEEE80211_CHAN_DISABLED)
12101a919318SJohannes Berg 		return false;
12111a919318SJohannes Berg 	/* This would happen when regulatory rules disallow HT40 completely */
121255b183adSFelix Fietkau 	if ((chan->flags & IEEE80211_CHAN_NO_HT40) == IEEE80211_CHAN_NO_HT40)
121355b183adSFelix Fietkau 		return false;
121455b183adSFelix Fietkau 	return true;
1215038659e7SLuis R. Rodriguez }
1216038659e7SLuis R. Rodriguez 
1217038659e7SLuis R. Rodriguez static void reg_process_ht_flags_channel(struct wiphy *wiphy,
1218fdc9d7b2SJohannes Berg 					 struct ieee80211_channel *channel)
1219038659e7SLuis R. Rodriguez {
1220fdc9d7b2SJohannes Berg 	struct ieee80211_supported_band *sband = wiphy->bands[channel->band];
1221038659e7SLuis R. Rodriguez 	struct ieee80211_channel *channel_before = NULL, *channel_after = NULL;
1222038659e7SLuis R. Rodriguez 	unsigned int i;
1223038659e7SLuis R. Rodriguez 
12241a919318SJohannes Berg 	if (!is_ht40_allowed(channel)) {
1225038659e7SLuis R. Rodriguez 		channel->flags |= IEEE80211_CHAN_NO_HT40;
1226038659e7SLuis R. Rodriguez 		return;
1227038659e7SLuis R. Rodriguez 	}
1228038659e7SLuis R. Rodriguez 
1229038659e7SLuis R. Rodriguez 	/*
1230038659e7SLuis R. Rodriguez 	 * We need to ensure the extension channels exist to
1231038659e7SLuis R. Rodriguez 	 * be able to use HT40- or HT40+, this finds them (or not)
1232038659e7SLuis R. Rodriguez 	 */
1233038659e7SLuis R. Rodriguez 	for (i = 0; i < sband->n_channels; i++) {
1234038659e7SLuis R. Rodriguez 		struct ieee80211_channel *c = &sband->channels[i];
12351a919318SJohannes Berg 
1236038659e7SLuis R. Rodriguez 		if (c->center_freq == (channel->center_freq - 20))
1237038659e7SLuis R. Rodriguez 			channel_before = c;
1238038659e7SLuis R. Rodriguez 		if (c->center_freq == (channel->center_freq + 20))
1239038659e7SLuis R. Rodriguez 			channel_after = c;
1240038659e7SLuis R. Rodriguez 	}
1241038659e7SLuis R. Rodriguez 
1242038659e7SLuis R. Rodriguez 	/*
1243038659e7SLuis R. Rodriguez 	 * Please note that this assumes target bandwidth is 20 MHz,
1244038659e7SLuis R. Rodriguez 	 * if that ever changes we also need to change the below logic
1245038659e7SLuis R. Rodriguez 	 * to include that as well.
1246038659e7SLuis R. Rodriguez 	 */
12471a919318SJohannes Berg 	if (!is_ht40_allowed(channel_before))
1248689da1b3SLuis R. Rodriguez 		channel->flags |= IEEE80211_CHAN_NO_HT40MINUS;
1249038659e7SLuis R. Rodriguez 	else
1250689da1b3SLuis R. Rodriguez 		channel->flags &= ~IEEE80211_CHAN_NO_HT40MINUS;
1251038659e7SLuis R. Rodriguez 
12521a919318SJohannes Berg 	if (!is_ht40_allowed(channel_after))
1253689da1b3SLuis R. Rodriguez 		channel->flags |= IEEE80211_CHAN_NO_HT40PLUS;
1254038659e7SLuis R. Rodriguez 	else
1255689da1b3SLuis R. Rodriguez 		channel->flags &= ~IEEE80211_CHAN_NO_HT40PLUS;
1256038659e7SLuis R. Rodriguez }
1257038659e7SLuis R. Rodriguez 
1258038659e7SLuis R. Rodriguez static void reg_process_ht_flags_band(struct wiphy *wiphy,
1259fdc9d7b2SJohannes Berg 				      struct ieee80211_supported_band *sband)
1260038659e7SLuis R. Rodriguez {
1261038659e7SLuis R. Rodriguez 	unsigned int i;
1262038659e7SLuis R. Rodriguez 
1263fdc9d7b2SJohannes Berg 	if (!sband)
1264fdc9d7b2SJohannes Berg 		return;
1265038659e7SLuis R. Rodriguez 
1266038659e7SLuis R. Rodriguez 	for (i = 0; i < sband->n_channels; i++)
1267fdc9d7b2SJohannes Berg 		reg_process_ht_flags_channel(wiphy, &sband->channels[i]);
1268038659e7SLuis R. Rodriguez }
1269038659e7SLuis R. Rodriguez 
1270038659e7SLuis R. Rodriguez static void reg_process_ht_flags(struct wiphy *wiphy)
1271038659e7SLuis R. Rodriguez {
1272038659e7SLuis R. Rodriguez 	enum ieee80211_band band;
1273038659e7SLuis R. Rodriguez 
1274038659e7SLuis R. Rodriguez 	if (!wiphy)
1275038659e7SLuis R. Rodriguez 		return;
1276038659e7SLuis R. Rodriguez 
1277fdc9d7b2SJohannes Berg 	for (band = 0; band < IEEE80211_NUM_BANDS; band++)
1278fdc9d7b2SJohannes Berg 		reg_process_ht_flags_band(wiphy, wiphy->bands[band]);
1279038659e7SLuis R. Rodriguez }
1280038659e7SLuis R. Rodriguez 
12810e3802dbSLuis R. Rodriguez static void reg_call_notifier(struct wiphy *wiphy,
12820e3802dbSLuis R. Rodriguez 			      struct regulatory_request *request)
12830e3802dbSLuis R. Rodriguez {
12840e3802dbSLuis R. Rodriguez 	if (wiphy->reg_notifier)
12850e3802dbSLuis R. Rodriguez 		wiphy->reg_notifier(wiphy, request);
12860e3802dbSLuis R. Rodriguez }
12870e3802dbSLuis R. Rodriguez 
1288eac03e38SSven Neumann static void wiphy_update_regulatory(struct wiphy *wiphy,
12897db90f4aSLuis R. Rodriguez 				    enum nl80211_reg_initiator initiator)
12908318d78aSJohannes Berg {
12918318d78aSJohannes Berg 	enum ieee80211_band band;
1292c492db37SJohannes Berg 	struct regulatory_request *lr = get_last_request();
1293eac03e38SSven Neumann 
12940e3802dbSLuis R. Rodriguez 	if (ignore_reg_update(wiphy, initiator)) {
12950e3802dbSLuis R. Rodriguez 		/*
12960e3802dbSLuis R. Rodriguez 		 * Regulatory updates set by CORE are ignored for custom
12970e3802dbSLuis R. Rodriguez 		 * regulatory cards. Let us notify the changes to the driver,
12980e3802dbSLuis R. Rodriguez 		 * as some drivers used this to restore its orig_* reg domain.
12990e3802dbSLuis R. Rodriguez 		 */
13000e3802dbSLuis R. Rodriguez 		if (initiator == NL80211_REGDOM_SET_BY_CORE &&
1301a2f73b6cSLuis R. Rodriguez 		    wiphy->regulatory_flags & REGULATORY_CUSTOM_REG)
13020e3802dbSLuis R. Rodriguez 			reg_call_notifier(wiphy, lr);
1303a203c2aaSSven Neumann 		return;
13040e3802dbSLuis R. Rodriguez 	}
1305a203c2aaSSven Neumann 
1306c492db37SJohannes Berg 	lr->dfs_region = get_cfg80211_regdom()->dfs_region;
1307b68e6b3bSLuis R. Rodriguez 
1308fdc9d7b2SJohannes Berg 	for (band = 0; band < IEEE80211_NUM_BANDS; band++)
1309fdc9d7b2SJohannes Berg 		handle_band(wiphy, initiator, wiphy->bands[band]);
1310a203c2aaSSven Neumann 
1311e38f8a7aSLuis R. Rodriguez 	reg_process_beacons(wiphy);
1312038659e7SLuis R. Rodriguez 	reg_process_ht_flags(wiphy);
13130e3802dbSLuis R. Rodriguez 	reg_call_notifier(wiphy, lr);
1314b2e1b302SLuis R. Rodriguez }
1315b2e1b302SLuis R. Rodriguez 
1316d7549cbbSSven Neumann static void update_all_wiphy_regulatory(enum nl80211_reg_initiator initiator)
1317d7549cbbSSven Neumann {
1318d7549cbbSSven Neumann 	struct cfg80211_registered_device *rdev;
13194a38994fSRajkumar Manoharan 	struct wiphy *wiphy;
1320d7549cbbSSven Neumann 
13215fe231e8SJohannes Berg 	ASSERT_RTNL();
1322458f4f9eSJohannes Berg 
13234a38994fSRajkumar Manoharan 	list_for_each_entry(rdev, &cfg80211_rdev_list, list) {
13244a38994fSRajkumar Manoharan 		wiphy = &rdev->wiphy;
13254a38994fSRajkumar Manoharan 		wiphy_update_regulatory(wiphy, initiator);
13264a38994fSRajkumar Manoharan 	}
1327d7549cbbSSven Neumann }
1328d7549cbbSSven Neumann 
13291fa25e41SLuis R. Rodriguez static void handle_channel_custom(struct wiphy *wiphy,
1330fdc9d7b2SJohannes Berg 				  struct ieee80211_channel *chan,
13311fa25e41SLuis R. Rodriguez 				  const struct ieee80211_regdomain *regd)
13321fa25e41SLuis R. Rodriguez {
1333038659e7SLuis R. Rodriguez 	u32 bw_flags = 0;
13341fa25e41SLuis R. Rodriguez 	const struct ieee80211_reg_rule *reg_rule = NULL;
13351fa25e41SLuis R. Rodriguez 	const struct ieee80211_power_rule *power_rule = NULL;
1336038659e7SLuis R. Rodriguez 	const struct ieee80211_freq_range *freq_range = NULL;
13371fa25e41SLuis R. Rodriguez 
1338361c9c8bSJohannes Berg 	reg_rule = freq_reg_info_regd(wiphy, MHZ_TO_KHZ(chan->center_freq),
1339038659e7SLuis R. Rodriguez 				      regd);
13401fa25e41SLuis R. Rodriguez 
1341361c9c8bSJohannes Berg 	if (IS_ERR(reg_rule)) {
1342fe7ef5e9SJohannes Berg 		REG_DBG_PRINT("Disabling freq %d MHz as custom regd has no rule that fits it\n",
1343fe7ef5e9SJohannes Berg 			      chan->center_freq);
1344cc493e4fSLuis R. Rodriguez 		chan->orig_flags |= IEEE80211_CHAN_DISABLED;
1345cc493e4fSLuis R. Rodriguez 		chan->flags = chan->orig_flags;
13461fa25e41SLuis R. Rodriguez 		return;
13471fa25e41SLuis R. Rodriguez 	}
13481fa25e41SLuis R. Rodriguez 
1349fe7ef5e9SJohannes Berg 	chan_reg_rule_print_dbg(chan, reg_rule);
1350e702d3cfSLuis R. Rodriguez 
13511fa25e41SLuis R. Rodriguez 	power_rule = &reg_rule->power_rule;
1352038659e7SLuis R. Rodriguez 	freq_range = &reg_rule->freq_range;
13531fa25e41SLuis R. Rodriguez 
1354038659e7SLuis R. Rodriguez 	if (freq_range->max_bandwidth_khz < MHZ_TO_KHZ(40))
1355038659e7SLuis R. Rodriguez 		bw_flags = IEEE80211_CHAN_NO_HT40;
1356c7a6ee27SJohannes Berg 	if (freq_range->max_bandwidth_khz < MHZ_TO_KHZ(80))
1357c7a6ee27SJohannes Berg 		bw_flags |= IEEE80211_CHAN_NO_80MHZ;
1358c7a6ee27SJohannes Berg 	if (freq_range->max_bandwidth_khz < MHZ_TO_KHZ(160))
1359c7a6ee27SJohannes Berg 		bw_flags |= IEEE80211_CHAN_NO_160MHZ;
1360038659e7SLuis R. Rodriguez 
1361038659e7SLuis R. Rodriguez 	chan->flags |= map_regdom_flags(reg_rule->flags) | bw_flags;
13621fa25e41SLuis R. Rodriguez 	chan->max_antenna_gain = (int) MBI_TO_DBI(power_rule->max_antenna_gain);
1363279f0f55SFelix Fietkau 	chan->max_reg_power = chan->max_power =
1364279f0f55SFelix Fietkau 		(int) MBM_TO_DBM(power_rule->max_eirp);
13651fa25e41SLuis R. Rodriguez }
13661fa25e41SLuis R. Rodriguez 
1367fdc9d7b2SJohannes Berg static void handle_band_custom(struct wiphy *wiphy,
1368fdc9d7b2SJohannes Berg 			       struct ieee80211_supported_band *sband,
13691fa25e41SLuis R. Rodriguez 			       const struct ieee80211_regdomain *regd)
13701fa25e41SLuis R. Rodriguez {
13711fa25e41SLuis R. Rodriguez 	unsigned int i;
13721fa25e41SLuis R. Rodriguez 
1373fdc9d7b2SJohannes Berg 	if (!sband)
1374fdc9d7b2SJohannes Berg 		return;
13751fa25e41SLuis R. Rodriguez 
13761fa25e41SLuis R. Rodriguez 	for (i = 0; i < sband->n_channels; i++)
1377fdc9d7b2SJohannes Berg 		handle_channel_custom(wiphy, &sband->channels[i], regd);
13781fa25e41SLuis R. Rodriguez }
13791fa25e41SLuis R. Rodriguez 
13801fa25e41SLuis R. Rodriguez /* Used by drivers prior to wiphy registration */
13811fa25e41SLuis R. Rodriguez void wiphy_apply_custom_regulatory(struct wiphy *wiphy,
13821fa25e41SLuis R. Rodriguez 				   const struct ieee80211_regdomain *regd)
13831fa25e41SLuis R. Rodriguez {
13841fa25e41SLuis R. Rodriguez 	enum ieee80211_band band;
1385bbcf3f02SLuis R. Rodriguez 	unsigned int bands_set = 0;
1386ac46d48eSLuis R. Rodriguez 
1387a2f73b6cSLuis R. Rodriguez 	WARN(!(wiphy->regulatory_flags & REGULATORY_CUSTOM_REG),
1388a2f73b6cSLuis R. Rodriguez 	     "wiphy should have REGULATORY_CUSTOM_REG\n");
1389a2f73b6cSLuis R. Rodriguez 	wiphy->regulatory_flags |= REGULATORY_CUSTOM_REG;
1390222ea581SLuis R. Rodriguez 
13911fa25e41SLuis R. Rodriguez 	for (band = 0; band < IEEE80211_NUM_BANDS; band++) {
1392bbcf3f02SLuis R. Rodriguez 		if (!wiphy->bands[band])
1393bbcf3f02SLuis R. Rodriguez 			continue;
1394fdc9d7b2SJohannes Berg 		handle_band_custom(wiphy, wiphy->bands[band], regd);
1395bbcf3f02SLuis R. Rodriguez 		bands_set++;
13961fa25e41SLuis R. Rodriguez 	}
1397bbcf3f02SLuis R. Rodriguez 
1398bbcf3f02SLuis R. Rodriguez 	/*
1399bbcf3f02SLuis R. Rodriguez 	 * no point in calling this if it won't have any effect
14001a919318SJohannes Berg 	 * on your device's supported bands.
1401bbcf3f02SLuis R. Rodriguez 	 */
1402bbcf3f02SLuis R. Rodriguez 	WARN_ON(!bands_set);
14031fa25e41SLuis R. Rodriguez }
14041fa25e41SLuis R. Rodriguez EXPORT_SYMBOL(wiphy_apply_custom_regulatory);
14051fa25e41SLuis R. Rodriguez 
1406b2e253cfSLuis R. Rodriguez static void reg_set_request_processed(void)
1407b2e253cfSLuis R. Rodriguez {
1408b2e253cfSLuis R. Rodriguez 	bool need_more_processing = false;
1409c492db37SJohannes Berg 	struct regulatory_request *lr = get_last_request();
1410b2e253cfSLuis R. Rodriguez 
1411c492db37SJohannes Berg 	lr->processed = true;
1412b2e253cfSLuis R. Rodriguez 
1413b2e253cfSLuis R. Rodriguez 	spin_lock(&reg_requests_lock);
1414b2e253cfSLuis R. Rodriguez 	if (!list_empty(&reg_requests_list))
1415b2e253cfSLuis R. Rodriguez 		need_more_processing = true;
1416b2e253cfSLuis R. Rodriguez 	spin_unlock(&reg_requests_lock);
1417b2e253cfSLuis R. Rodriguez 
1418c492db37SJohannes Berg 	if (lr->initiator == NL80211_REGDOM_SET_BY_USER)
1419fe20b39eSEliad Peller 		cancel_delayed_work(&reg_timeout);
1420a90c7a31SLuis R. Rodriguez 
1421b2e253cfSLuis R. Rodriguez 	if (need_more_processing)
1422b2e253cfSLuis R. Rodriguez 		schedule_work(&reg_work);
1423b2e253cfSLuis R. Rodriguez }
1424b2e253cfSLuis R. Rodriguez 
1425d1c96a9aSLuis R. Rodriguez /**
1426b3eb7f3fSLuis R. Rodriguez  * reg_process_hint_core - process core regulatory requests
1427b3eb7f3fSLuis R. Rodriguez  * @pending_request: a pending core regulatory request
1428b3eb7f3fSLuis R. Rodriguez  *
1429b3eb7f3fSLuis R. Rodriguez  * The wireless subsystem can use this function to process
1430b3eb7f3fSLuis R. Rodriguez  * a regulatory request issued by the regulatory core.
1431b3eb7f3fSLuis R. Rodriguez  *
1432b3eb7f3fSLuis R. Rodriguez  * Returns one of the different reg request treatment values.
1433b3eb7f3fSLuis R. Rodriguez  */
1434b3eb7f3fSLuis R. Rodriguez static enum reg_request_treatment
1435b3eb7f3fSLuis R. Rodriguez reg_process_hint_core(struct regulatory_request *core_request)
1436b3eb7f3fSLuis R. Rodriguez {
1437b3eb7f3fSLuis R. Rodriguez 
1438b3eb7f3fSLuis R. Rodriguez 	core_request->intersect = false;
1439b3eb7f3fSLuis R. Rodriguez 	core_request->processed = false;
14405ad6ef5eSLuis R. Rodriguez 
144105f1a3eaSLuis R. Rodriguez 	reg_update_last_request(core_request);
1442b3eb7f3fSLuis R. Rodriguez 
1443fe6631ffSLuis R. Rodriguez 	return reg_call_crda(core_request);
1444b3eb7f3fSLuis R. Rodriguez }
1445b3eb7f3fSLuis R. Rodriguez 
14460d97a619SLuis R. Rodriguez static enum reg_request_treatment
14470d97a619SLuis R. Rodriguez __reg_process_hint_user(struct regulatory_request *user_request)
14480d97a619SLuis R. Rodriguez {
14490d97a619SLuis R. Rodriguez 	struct regulatory_request *lr = get_last_request();
14500d97a619SLuis R. Rodriguez 
14510d97a619SLuis R. Rodriguez 	if (reg_request_cell_base(user_request))
14520d97a619SLuis R. Rodriguez 		return reg_ignore_cell_hint(user_request);
14530d97a619SLuis R. Rodriguez 
14540d97a619SLuis R. Rodriguez 	if (reg_request_cell_base(lr))
14550d97a619SLuis R. Rodriguez 		return REG_REQ_IGNORE;
14560d97a619SLuis R. Rodriguez 
14570d97a619SLuis R. Rodriguez 	if (lr->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE)
14580d97a619SLuis R. Rodriguez 		return REG_REQ_INTERSECT;
14590d97a619SLuis R. Rodriguez 	/*
14600d97a619SLuis R. Rodriguez 	 * If the user knows better the user should set the regdom
14610d97a619SLuis R. Rodriguez 	 * to their country before the IE is picked up
14620d97a619SLuis R. Rodriguez 	 */
14630d97a619SLuis R. Rodriguez 	if (lr->initiator == NL80211_REGDOM_SET_BY_USER &&
14640d97a619SLuis R. Rodriguez 	    lr->intersect)
14650d97a619SLuis R. Rodriguez 		return REG_REQ_IGNORE;
14660d97a619SLuis R. Rodriguez 	/*
14670d97a619SLuis R. Rodriguez 	 * Process user requests only after previous user/driver/core
14680d97a619SLuis R. Rodriguez 	 * requests have been processed
14690d97a619SLuis R. Rodriguez 	 */
14700d97a619SLuis R. Rodriguez 	if ((lr->initiator == NL80211_REGDOM_SET_BY_CORE ||
14710d97a619SLuis R. Rodriguez 	     lr->initiator == NL80211_REGDOM_SET_BY_DRIVER ||
14720d97a619SLuis R. Rodriguez 	     lr->initiator == NL80211_REGDOM_SET_BY_USER) &&
14730d97a619SLuis R. Rodriguez 	    regdom_changes(lr->alpha2))
14740d97a619SLuis R. Rodriguez 		return REG_REQ_IGNORE;
14750d97a619SLuis R. Rodriguez 
14760d97a619SLuis R. Rodriguez 	if (!regdom_changes(user_request->alpha2))
14770d97a619SLuis R. Rodriguez 		return REG_REQ_ALREADY_SET;
14780d97a619SLuis R. Rodriguez 
14790d97a619SLuis R. Rodriguez 	return REG_REQ_OK;
14800d97a619SLuis R. Rodriguez }
14810d97a619SLuis R. Rodriguez 
14820d97a619SLuis R. Rodriguez /**
14830d97a619SLuis R. Rodriguez  * reg_process_hint_user - process user regulatory requests
14840d97a619SLuis R. Rodriguez  * @user_request: a pending user regulatory request
14850d97a619SLuis R. Rodriguez  *
14860d97a619SLuis R. Rodriguez  * The wireless subsystem can use this function to process
14870d97a619SLuis R. Rodriguez  * a regulatory request initiated by userspace.
14880d97a619SLuis R. Rodriguez  *
14890d97a619SLuis R. Rodriguez  * Returns one of the different reg request treatment values.
14900d97a619SLuis R. Rodriguez  */
14910d97a619SLuis R. Rodriguez static enum reg_request_treatment
14920d97a619SLuis R. Rodriguez reg_process_hint_user(struct regulatory_request *user_request)
14930d97a619SLuis R. Rodriguez {
14940d97a619SLuis R. Rodriguez 	enum reg_request_treatment treatment;
14950d97a619SLuis R. Rodriguez 
14960d97a619SLuis R. Rodriguez 	treatment = __reg_process_hint_user(user_request);
14970d97a619SLuis R. Rodriguez 	if (treatment == REG_REQ_IGNORE ||
14980d97a619SLuis R. Rodriguez 	    treatment == REG_REQ_ALREADY_SET) {
14990d97a619SLuis R. Rodriguez 		kfree(user_request);
15000d97a619SLuis R. Rodriguez 		return treatment;
15010d97a619SLuis R. Rodriguez 	}
15020d97a619SLuis R. Rodriguez 
15030d97a619SLuis R. Rodriguez 	user_request->intersect = treatment == REG_REQ_INTERSECT;
15040d97a619SLuis R. Rodriguez 	user_request->processed = false;
15055ad6ef5eSLuis R. Rodriguez 
150605f1a3eaSLuis R. Rodriguez 	reg_update_last_request(user_request);
15070d97a619SLuis R. Rodriguez 
15080d97a619SLuis R. Rodriguez 	user_alpha2[0] = user_request->alpha2[0];
15090d97a619SLuis R. Rodriguez 	user_alpha2[1] = user_request->alpha2[1];
15100d97a619SLuis R. Rodriguez 
1511fe6631ffSLuis R. Rodriguez 	return reg_call_crda(user_request);
15120d97a619SLuis R. Rodriguez }
15130d97a619SLuis R. Rodriguez 
151421636c7fSLuis R. Rodriguez static enum reg_request_treatment
151521636c7fSLuis R. Rodriguez __reg_process_hint_driver(struct regulatory_request *driver_request)
151621636c7fSLuis R. Rodriguez {
151721636c7fSLuis R. Rodriguez 	struct regulatory_request *lr = get_last_request();
151821636c7fSLuis R. Rodriguez 
151921636c7fSLuis R. Rodriguez 	if (lr->initiator == NL80211_REGDOM_SET_BY_CORE) {
152021636c7fSLuis R. Rodriguez 		if (regdom_changes(driver_request->alpha2))
152121636c7fSLuis R. Rodriguez 			return REG_REQ_OK;
152221636c7fSLuis R. Rodriguez 		return REG_REQ_ALREADY_SET;
152321636c7fSLuis R. Rodriguez 	}
152421636c7fSLuis R. Rodriguez 
152521636c7fSLuis R. Rodriguez 	/*
152621636c7fSLuis R. Rodriguez 	 * This would happen if you unplug and plug your card
152721636c7fSLuis R. Rodriguez 	 * back in or if you add a new device for which the previously
152821636c7fSLuis R. Rodriguez 	 * loaded card also agrees on the regulatory domain.
152921636c7fSLuis R. Rodriguez 	 */
153021636c7fSLuis R. Rodriguez 	if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER &&
153121636c7fSLuis R. Rodriguez 	    !regdom_changes(driver_request->alpha2))
153221636c7fSLuis R. Rodriguez 		return REG_REQ_ALREADY_SET;
153321636c7fSLuis R. Rodriguez 
153421636c7fSLuis R. Rodriguez 	return REG_REQ_INTERSECT;
153521636c7fSLuis R. Rodriguez }
153621636c7fSLuis R. Rodriguez 
153721636c7fSLuis R. Rodriguez /**
153821636c7fSLuis R. Rodriguez  * reg_process_hint_driver - process driver regulatory requests
153921636c7fSLuis R. Rodriguez  * @driver_request: a pending driver regulatory request
154021636c7fSLuis R. Rodriguez  *
154121636c7fSLuis R. Rodriguez  * The wireless subsystem can use this function to process
154221636c7fSLuis R. Rodriguez  * a regulatory request issued by an 802.11 driver.
154321636c7fSLuis R. Rodriguez  *
154421636c7fSLuis R. Rodriguez  * Returns one of the different reg request treatment values.
154521636c7fSLuis R. Rodriguez  */
154621636c7fSLuis R. Rodriguez static enum reg_request_treatment
154721636c7fSLuis R. Rodriguez reg_process_hint_driver(struct wiphy *wiphy,
154821636c7fSLuis R. Rodriguez 			struct regulatory_request *driver_request)
154921636c7fSLuis R. Rodriguez {
155021636c7fSLuis R. Rodriguez 	const struct ieee80211_regdomain *regd;
155121636c7fSLuis R. Rodriguez 	enum reg_request_treatment treatment;
155221636c7fSLuis R. Rodriguez 
155321636c7fSLuis R. Rodriguez 	treatment = __reg_process_hint_driver(driver_request);
155421636c7fSLuis R. Rodriguez 
155521636c7fSLuis R. Rodriguez 	switch (treatment) {
155621636c7fSLuis R. Rodriguez 	case REG_REQ_OK:
155721636c7fSLuis R. Rodriguez 		break;
155821636c7fSLuis R. Rodriguez 	case REG_REQ_IGNORE:
155921636c7fSLuis R. Rodriguez 		kfree(driver_request);
156021636c7fSLuis R. Rodriguez 		return treatment;
156121636c7fSLuis R. Rodriguez 	case REG_REQ_INTERSECT:
156221636c7fSLuis R. Rodriguez 		/* fall through */
156321636c7fSLuis R. Rodriguez 	case REG_REQ_ALREADY_SET:
156421636c7fSLuis R. Rodriguez 		regd = reg_copy_regd(get_cfg80211_regdom());
156521636c7fSLuis R. Rodriguez 		if (IS_ERR(regd)) {
156621636c7fSLuis R. Rodriguez 			kfree(driver_request);
156721636c7fSLuis R. Rodriguez 			return REG_REQ_IGNORE;
156821636c7fSLuis R. Rodriguez 		}
156921636c7fSLuis R. Rodriguez 		rcu_assign_pointer(wiphy->regd, regd);
157021636c7fSLuis R. Rodriguez 	}
157121636c7fSLuis R. Rodriguez 
157221636c7fSLuis R. Rodriguez 
157321636c7fSLuis R. Rodriguez 	driver_request->intersect = treatment == REG_REQ_INTERSECT;
157421636c7fSLuis R. Rodriguez 	driver_request->processed = false;
15755ad6ef5eSLuis R. Rodriguez 
157605f1a3eaSLuis R. Rodriguez 	reg_update_last_request(driver_request);
157721636c7fSLuis R. Rodriguez 
157821636c7fSLuis R. Rodriguez 	/*
157921636c7fSLuis R. Rodriguez 	 * Since CRDA will not be called in this case as we already
158021636c7fSLuis R. Rodriguez 	 * have applied the requested regulatory domain before we just
158121636c7fSLuis R. Rodriguez 	 * inform userspace we have processed the request
158221636c7fSLuis R. Rodriguez 	 */
158321636c7fSLuis R. Rodriguez 	if (treatment == REG_REQ_ALREADY_SET) {
158421636c7fSLuis R. Rodriguez 		nl80211_send_reg_change_event(driver_request);
158521636c7fSLuis R. Rodriguez 		reg_set_request_processed();
158621636c7fSLuis R. Rodriguez 		return treatment;
158721636c7fSLuis R. Rodriguez 	}
158821636c7fSLuis R. Rodriguez 
1589fe6631ffSLuis R. Rodriguez 	return reg_call_crda(driver_request);
159021636c7fSLuis R. Rodriguez }
159121636c7fSLuis R. Rodriguez 
1592b23e7a9eSLuis R. Rodriguez static enum reg_request_treatment
1593b23e7a9eSLuis R. Rodriguez __reg_process_hint_country_ie(struct wiphy *wiphy,
1594b23e7a9eSLuis R. Rodriguez 			      struct regulatory_request *country_ie_request)
1595b23e7a9eSLuis R. Rodriguez {
1596b23e7a9eSLuis R. Rodriguez 	struct wiphy *last_wiphy = NULL;
1597b23e7a9eSLuis R. Rodriguez 	struct regulatory_request *lr = get_last_request();
1598b23e7a9eSLuis R. Rodriguez 
1599b23e7a9eSLuis R. Rodriguez 	if (reg_request_cell_base(lr)) {
1600b23e7a9eSLuis R. Rodriguez 		/* Trust a Cell base station over the AP's country IE */
1601b23e7a9eSLuis R. Rodriguez 		if (regdom_changes(country_ie_request->alpha2))
1602b23e7a9eSLuis R. Rodriguez 			return REG_REQ_IGNORE;
1603b23e7a9eSLuis R. Rodriguez 		return REG_REQ_ALREADY_SET;
16042a901468SLuis R. Rodriguez 	} else {
16052a901468SLuis R. Rodriguez 		if (wiphy->regulatory_flags & REGULATORY_COUNTRY_IE_IGNORE)
16062a901468SLuis R. Rodriguez 			return REG_REQ_IGNORE;
1607b23e7a9eSLuis R. Rodriguez 	}
1608b23e7a9eSLuis R. Rodriguez 
1609b23e7a9eSLuis R. Rodriguez 	if (unlikely(!is_an_alpha2(country_ie_request->alpha2)))
1610b23e7a9eSLuis R. Rodriguez 		return -EINVAL;
16112f1c6c57SLuis R. Rodriguez 
16122f1c6c57SLuis R. Rodriguez 	if (lr->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE)
16132f1c6c57SLuis R. Rodriguez 		return REG_REQ_OK;
16142f1c6c57SLuis R. Rodriguez 
16152f1c6c57SLuis R. Rodriguez 	last_wiphy = wiphy_idx_to_wiphy(lr->wiphy_idx);
16162f1c6c57SLuis R. Rodriguez 
1617b23e7a9eSLuis R. Rodriguez 	if (last_wiphy != wiphy) {
1618b23e7a9eSLuis R. Rodriguez 		/*
1619b23e7a9eSLuis R. Rodriguez 		 * Two cards with two APs claiming different
1620b23e7a9eSLuis R. Rodriguez 		 * Country IE alpha2s. We could
1621b23e7a9eSLuis R. Rodriguez 		 * intersect them, but that seems unlikely
1622b23e7a9eSLuis R. Rodriguez 		 * to be correct. Reject second one for now.
1623b23e7a9eSLuis R. Rodriguez 		 */
1624b23e7a9eSLuis R. Rodriguez 		if (regdom_changes(country_ie_request->alpha2))
1625b23e7a9eSLuis R. Rodriguez 			return REG_REQ_IGNORE;
1626b23e7a9eSLuis R. Rodriguez 		return REG_REQ_ALREADY_SET;
1627b23e7a9eSLuis R. Rodriguez 	}
1628b23e7a9eSLuis R. Rodriguez 	/*
1629b23e7a9eSLuis R. Rodriguez 	 * Two consecutive Country IE hints on the same wiphy.
1630b23e7a9eSLuis R. Rodriguez 	 * This should be picked up early by the driver/stack
1631b23e7a9eSLuis R. Rodriguez 	 */
1632b23e7a9eSLuis R. Rodriguez 	if (WARN_ON(regdom_changes(country_ie_request->alpha2)))
1633b23e7a9eSLuis R. Rodriguez 		return REG_REQ_OK;
1634b23e7a9eSLuis R. Rodriguez 	return REG_REQ_ALREADY_SET;
1635b23e7a9eSLuis R. Rodriguez }
1636b23e7a9eSLuis R. Rodriguez 
1637b3eb7f3fSLuis R. Rodriguez /**
1638b23e7a9eSLuis R. Rodriguez  * reg_process_hint_country_ie - process regulatory requests from country IEs
1639b23e7a9eSLuis R. Rodriguez  * @country_ie_request: a regulatory request from a country IE
1640d1c96a9aSLuis R. Rodriguez  *
1641b23e7a9eSLuis R. Rodriguez  * The wireless subsystem can use this function to process
1642b23e7a9eSLuis R. Rodriguez  * a regulatory request issued by a country Information Element.
1643d1c96a9aSLuis R. Rodriguez  *
16442f92212bSJohannes Berg  * Returns one of the different reg request treatment values.
1645d1c96a9aSLuis R. Rodriguez  */
16462f92212bSJohannes Berg static enum reg_request_treatment
1647b23e7a9eSLuis R. Rodriguez reg_process_hint_country_ie(struct wiphy *wiphy,
1648b23e7a9eSLuis R. Rodriguez 			    struct regulatory_request *country_ie_request)
1649b2e1b302SLuis R. Rodriguez {
16502f92212bSJohannes Berg 	enum reg_request_treatment treatment;
1651b2e1b302SLuis R. Rodriguez 
1652b23e7a9eSLuis R. Rodriguez 	treatment = __reg_process_hint_country_ie(wiphy, country_ie_request);
1653761cf7ecSLuis R. Rodriguez 
16542f92212bSJohannes Berg 	switch (treatment) {
16552f92212bSJohannes Berg 	case REG_REQ_OK:
16562f92212bSJohannes Berg 		break;
1657b23e7a9eSLuis R. Rodriguez 	case REG_REQ_IGNORE:
1658b23e7a9eSLuis R. Rodriguez 		/* fall through */
1659b23e7a9eSLuis R. Rodriguez 	case REG_REQ_ALREADY_SET:
1660b23e7a9eSLuis R. Rodriguez 		kfree(country_ie_request);
1661b23e7a9eSLuis R. Rodriguez 		return treatment;
1662b23e7a9eSLuis R. Rodriguez 	case REG_REQ_INTERSECT:
1663b23e7a9eSLuis R. Rodriguez 		kfree(country_ie_request);
1664fb1fc7adSLuis R. Rodriguez 		/*
1665b23e7a9eSLuis R. Rodriguez 		 * This doesn't happen yet, not sure we
1666b23e7a9eSLuis R. Rodriguez 		 * ever want to support it for this case.
1667fb1fc7adSLuis R. Rodriguez 		 */
1668b23e7a9eSLuis R. Rodriguez 		WARN_ONCE(1, "Unexpected intersection for country IEs");
16692f92212bSJohannes Berg 		return REG_REQ_IGNORE;
1670d951c1ddSLuis R. Rodriguez 	}
1671b2e1b302SLuis R. Rodriguez 
1672b23e7a9eSLuis R. Rodriguez 	country_ie_request->intersect = false;
1673b23e7a9eSLuis R. Rodriguez 	country_ie_request->processed = false;
16745ad6ef5eSLuis R. Rodriguez 
167505f1a3eaSLuis R. Rodriguez 	reg_update_last_request(country_ie_request);
1676d951c1ddSLuis R. Rodriguez 
1677fe6631ffSLuis R. Rodriguez 	return reg_call_crda(country_ie_request);
1678b2e1b302SLuis R. Rodriguez }
1679b2e1b302SLuis R. Rodriguez 
168030a548c7SLuis R. Rodriguez /* This processes *all* regulatory hints */
16811daa37c7SLuis R. Rodriguez static void reg_process_hint(struct regulatory_request *reg_request)
1682fe33eb39SLuis R. Rodriguez {
1683fe33eb39SLuis R. Rodriguez 	struct wiphy *wiphy = NULL;
1684b3eb7f3fSLuis R. Rodriguez 	enum reg_request_treatment treatment;
1685fe33eb39SLuis R. Rodriguez 
1686fdc9d7b2SJohannes Berg 	if (WARN_ON(!reg_request->alpha2))
1687fdc9d7b2SJohannes Berg 		return;
1688fe33eb39SLuis R. Rodriguez 
1689f4173766SJohannes Berg 	if (reg_request->wiphy_idx != WIPHY_IDX_INVALID)
1690fe33eb39SLuis R. Rodriguez 		wiphy = wiphy_idx_to_wiphy(reg_request->wiphy_idx);
1691fe33eb39SLuis R. Rodriguez 
16921daa37c7SLuis R. Rodriguez 	if (reg_request->initiator == NL80211_REGDOM_SET_BY_DRIVER && !wiphy) {
1693d951c1ddSLuis R. Rodriguez 		kfree(reg_request);
1694b0e2880bSLuis R. Rodriguez 		return;
1695fe33eb39SLuis R. Rodriguez 	}
1696fe33eb39SLuis R. Rodriguez 
1697b3eb7f3fSLuis R. Rodriguez 	switch (reg_request->initiator) {
1698b3eb7f3fSLuis R. Rodriguez 	case NL80211_REGDOM_SET_BY_CORE:
1699b3eb7f3fSLuis R. Rodriguez 		reg_process_hint_core(reg_request);
1700b3eb7f3fSLuis R. Rodriguez 		return;
1701b3eb7f3fSLuis R. Rodriguez 	case NL80211_REGDOM_SET_BY_USER:
17020d97a619SLuis R. Rodriguez 		treatment = reg_process_hint_user(reg_request);
170350c11eb9SInbal Hacohen 		if (treatment == REG_REQ_IGNORE ||
17040d97a619SLuis R. Rodriguez 		    treatment == REG_REQ_ALREADY_SET)
17050d97a619SLuis R. Rodriguez 			return;
17060d97a619SLuis R. Rodriguez 		schedule_delayed_work(&reg_timeout, msecs_to_jiffies(3142));
17070d97a619SLuis R. Rodriguez 		return;
1708b3eb7f3fSLuis R. Rodriguez 	case NL80211_REGDOM_SET_BY_DRIVER:
170921636c7fSLuis R. Rodriguez 		treatment = reg_process_hint_driver(wiphy, reg_request);
171021636c7fSLuis R. Rodriguez 		break;
1711b3eb7f3fSLuis R. Rodriguez 	case NL80211_REGDOM_SET_BY_COUNTRY_IE:
1712b23e7a9eSLuis R. Rodriguez 		treatment = reg_process_hint_country_ie(wiphy, reg_request);
1713b3eb7f3fSLuis R. Rodriguez 		break;
1714b3eb7f3fSLuis R. Rodriguez 	default:
1715b3eb7f3fSLuis R. Rodriguez 		WARN(1, "invalid initiator %d\n", reg_request->initiator);
1716b3eb7f3fSLuis R. Rodriguez 		return;
1717b3eb7f3fSLuis R. Rodriguez 	}
1718b3eb7f3fSLuis R. Rodriguez 
1719fe33eb39SLuis R. Rodriguez 	/* This is required so that the orig_* parameters are saved */
1720b23e7a9eSLuis R. Rodriguez 	if (treatment == REG_REQ_ALREADY_SET && wiphy &&
1721a2f73b6cSLuis R. Rodriguez 	    wiphy->regulatory_flags & REGULATORY_STRICT_REG)
17221daa37c7SLuis R. Rodriguez 		wiphy_update_regulatory(wiphy, reg_request->initiator);
1723fe33eb39SLuis R. Rodriguez }
1724fe33eb39SLuis R. Rodriguez 
1725b2e253cfSLuis R. Rodriguez /*
1726b2e253cfSLuis R. Rodriguez  * Processes regulatory hints, this is all the NL80211_REGDOM_SET_BY_*
1727b2e253cfSLuis R. Rodriguez  * Regulatory hints come on a first come first serve basis and we
1728b2e253cfSLuis R. Rodriguez  * must process each one atomically.
1729b2e253cfSLuis R. Rodriguez  */
1730fe33eb39SLuis R. Rodriguez static void reg_process_pending_hints(void)
1731fe33eb39SLuis R. Rodriguez {
1732c492db37SJohannes Berg 	struct regulatory_request *reg_request, *lr;
1733fe33eb39SLuis R. Rodriguez 
1734c492db37SJohannes Berg 	lr = get_last_request();
1735b0e2880bSLuis R. Rodriguez 
1736b2e253cfSLuis R. Rodriguez 	/* When last_request->processed becomes true this will be rescheduled */
1737c492db37SJohannes Berg 	if (lr && !lr->processed) {
17381a919318SJohannes Berg 		REG_DBG_PRINT("Pending regulatory request, waiting for it to be processed...\n");
17395fe231e8SJohannes Berg 		return;
1740b2e253cfSLuis R. Rodriguez 	}
1741b2e253cfSLuis R. Rodriguez 
1742fe33eb39SLuis R. Rodriguez 	spin_lock(&reg_requests_lock);
1743b2e253cfSLuis R. Rodriguez 
1744b2e253cfSLuis R. Rodriguez 	if (list_empty(&reg_requests_list)) {
1745b2e253cfSLuis R. Rodriguez 		spin_unlock(&reg_requests_lock);
17465fe231e8SJohannes Berg 		return;
1747b2e253cfSLuis R. Rodriguez 	}
1748b2e253cfSLuis R. Rodriguez 
1749fe33eb39SLuis R. Rodriguez 	reg_request = list_first_entry(&reg_requests_list,
1750fe33eb39SLuis R. Rodriguez 				       struct regulatory_request,
1751fe33eb39SLuis R. Rodriguez 				       list);
1752fe33eb39SLuis R. Rodriguez 	list_del_init(&reg_request->list);
1753fe33eb39SLuis R. Rodriguez 
1754d951c1ddSLuis R. Rodriguez 	spin_unlock(&reg_requests_lock);
1755b0e2880bSLuis R. Rodriguez 
17561daa37c7SLuis R. Rodriguez 	reg_process_hint(reg_request);
1757fe33eb39SLuis R. Rodriguez }
1758fe33eb39SLuis R. Rodriguez 
1759e38f8a7aSLuis R. Rodriguez /* Processes beacon hints -- this has nothing to do with country IEs */
1760e38f8a7aSLuis R. Rodriguez static void reg_process_pending_beacon_hints(void)
1761e38f8a7aSLuis R. Rodriguez {
176279c97e97SJohannes Berg 	struct cfg80211_registered_device *rdev;
1763e38f8a7aSLuis R. Rodriguez 	struct reg_beacon *pending_beacon, *tmp;
1764e38f8a7aSLuis R. Rodriguez 
1765e38f8a7aSLuis R. Rodriguez 	/* This goes through the _pending_ beacon list */
1766e38f8a7aSLuis R. Rodriguez 	spin_lock_bh(&reg_pending_beacons_lock);
1767e38f8a7aSLuis R. Rodriguez 
1768e38f8a7aSLuis R. Rodriguez 	list_for_each_entry_safe(pending_beacon, tmp,
1769e38f8a7aSLuis R. Rodriguez 				 &reg_pending_beacons, list) {
1770e38f8a7aSLuis R. Rodriguez 		list_del_init(&pending_beacon->list);
1771e38f8a7aSLuis R. Rodriguez 
1772e38f8a7aSLuis R. Rodriguez 		/* Applies the beacon hint to current wiphys */
177379c97e97SJohannes Berg 		list_for_each_entry(rdev, &cfg80211_rdev_list, list)
177479c97e97SJohannes Berg 			wiphy_update_new_beacon(&rdev->wiphy, pending_beacon);
1775e38f8a7aSLuis R. Rodriguez 
1776e38f8a7aSLuis R. Rodriguez 		/* Remembers the beacon hint for new wiphys or reg changes */
1777e38f8a7aSLuis R. Rodriguez 		list_add_tail(&pending_beacon->list, &reg_beacon_list);
1778e38f8a7aSLuis R. Rodriguez 	}
1779e38f8a7aSLuis R. Rodriguez 
1780e38f8a7aSLuis R. Rodriguez 	spin_unlock_bh(&reg_pending_beacons_lock);
1781e38f8a7aSLuis R. Rodriguez }
1782e38f8a7aSLuis R. Rodriguez 
1783fe33eb39SLuis R. Rodriguez static void reg_todo(struct work_struct *work)
1784fe33eb39SLuis R. Rodriguez {
17855fe231e8SJohannes Berg 	rtnl_lock();
1786fe33eb39SLuis R. Rodriguez 	reg_process_pending_hints();
1787e38f8a7aSLuis R. Rodriguez 	reg_process_pending_beacon_hints();
17885fe231e8SJohannes Berg 	rtnl_unlock();
1789fe33eb39SLuis R. Rodriguez }
1790fe33eb39SLuis R. Rodriguez 
1791fe33eb39SLuis R. Rodriguez static void queue_regulatory_request(struct regulatory_request *request)
1792fe33eb39SLuis R. Rodriguez {
1793c61029c7SJohn W. Linville 	request->alpha2[0] = toupper(request->alpha2[0]);
1794c61029c7SJohn W. Linville 	request->alpha2[1] = toupper(request->alpha2[1]);
1795c61029c7SJohn W. Linville 
1796fe33eb39SLuis R. Rodriguez 	spin_lock(&reg_requests_lock);
1797fe33eb39SLuis R. Rodriguez 	list_add_tail(&request->list, &reg_requests_list);
1798fe33eb39SLuis R. Rodriguez 	spin_unlock(&reg_requests_lock);
1799fe33eb39SLuis R. Rodriguez 
1800fe33eb39SLuis R. Rodriguez 	schedule_work(&reg_work);
1801fe33eb39SLuis R. Rodriguez }
1802fe33eb39SLuis R. Rodriguez 
180309d989d1SLuis R. Rodriguez /*
180409d989d1SLuis R. Rodriguez  * Core regulatory hint -- happens during cfg80211_init()
180509d989d1SLuis R. Rodriguez  * and when we restore regulatory settings.
180609d989d1SLuis R. Rodriguez  */
1807ba25c141SLuis R. Rodriguez static int regulatory_hint_core(const char *alpha2)
1808ba25c141SLuis R. Rodriguez {
1809ba25c141SLuis R. Rodriguez 	struct regulatory_request *request;
1810ba25c141SLuis R. Rodriguez 
18111a919318SJohannes Berg 	request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL);
1812ba25c141SLuis R. Rodriguez 	if (!request)
1813ba25c141SLuis R. Rodriguez 		return -ENOMEM;
1814ba25c141SLuis R. Rodriguez 
1815ba25c141SLuis R. Rodriguez 	request->alpha2[0] = alpha2[0];
1816ba25c141SLuis R. Rodriguez 	request->alpha2[1] = alpha2[1];
18177db90f4aSLuis R. Rodriguez 	request->initiator = NL80211_REGDOM_SET_BY_CORE;
1818ba25c141SLuis R. Rodriguez 
181931e99729SLuis R. Rodriguez 	queue_regulatory_request(request);
18205078b2e3SLuis R. Rodriguez 
1821fe33eb39SLuis R. Rodriguez 	return 0;
1822ba25c141SLuis R. Rodriguez }
1823ba25c141SLuis R. Rodriguez 
1824fe33eb39SLuis R. Rodriguez /* User hints */
182557b5ce07SLuis R. Rodriguez int regulatory_hint_user(const char *alpha2,
182657b5ce07SLuis R. Rodriguez 			 enum nl80211_user_reg_hint_type user_reg_hint_type)
1827b2e1b302SLuis R. Rodriguez {
1828fe33eb39SLuis R. Rodriguez 	struct regulatory_request *request;
1829fe33eb39SLuis R. Rodriguez 
1830fdc9d7b2SJohannes Berg 	if (WARN_ON(!alpha2))
1831fdc9d7b2SJohannes Berg 		return -EINVAL;
1832b2e1b302SLuis R. Rodriguez 
1833fe33eb39SLuis R. Rodriguez 	request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL);
1834fe33eb39SLuis R. Rodriguez 	if (!request)
1835fe33eb39SLuis R. Rodriguez 		return -ENOMEM;
1836fe33eb39SLuis R. Rodriguez 
1837f4173766SJohannes Berg 	request->wiphy_idx = WIPHY_IDX_INVALID;
1838fe33eb39SLuis R. Rodriguez 	request->alpha2[0] = alpha2[0];
1839fe33eb39SLuis R. Rodriguez 	request->alpha2[1] = alpha2[1];
1840e12822e1SLuis R. Rodriguez 	request->initiator = NL80211_REGDOM_SET_BY_USER;
184157b5ce07SLuis R. Rodriguez 	request->user_reg_hint_type = user_reg_hint_type;
1842fe33eb39SLuis R. Rodriguez 
1843fe33eb39SLuis R. Rodriguez 	queue_regulatory_request(request);
1844fe33eb39SLuis R. Rodriguez 
1845fe33eb39SLuis R. Rodriguez 	return 0;
1846fe33eb39SLuis R. Rodriguez }
1847fe33eb39SLuis R. Rodriguez 
1848fe33eb39SLuis R. Rodriguez /* Driver hints */
1849fe33eb39SLuis R. Rodriguez int regulatory_hint(struct wiphy *wiphy, const char *alpha2)
1850fe33eb39SLuis R. Rodriguez {
1851fe33eb39SLuis R. Rodriguez 	struct regulatory_request *request;
1852fe33eb39SLuis R. Rodriguez 
1853fdc9d7b2SJohannes Berg 	if (WARN_ON(!alpha2 || !wiphy))
1854fdc9d7b2SJohannes Berg 		return -EINVAL;
1855fe33eb39SLuis R. Rodriguez 
18564f7b9140SLuis R. Rodriguez 	wiphy->regulatory_flags &= ~REGULATORY_CUSTOM_REG;
18574f7b9140SLuis R. Rodriguez 
1858fe33eb39SLuis R. Rodriguez 	request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL);
1859fe33eb39SLuis R. Rodriguez 	if (!request)
1860fe33eb39SLuis R. Rodriguez 		return -ENOMEM;
1861fe33eb39SLuis R. Rodriguez 
1862fe33eb39SLuis R. Rodriguez 	request->wiphy_idx = get_wiphy_idx(wiphy);
1863fe33eb39SLuis R. Rodriguez 
1864fe33eb39SLuis R. Rodriguez 	request->alpha2[0] = alpha2[0];
1865fe33eb39SLuis R. Rodriguez 	request->alpha2[1] = alpha2[1];
18667db90f4aSLuis R. Rodriguez 	request->initiator = NL80211_REGDOM_SET_BY_DRIVER;
1867fe33eb39SLuis R. Rodriguez 
1868fe33eb39SLuis R. Rodriguez 	queue_regulatory_request(request);
1869fe33eb39SLuis R. Rodriguez 
1870fe33eb39SLuis R. Rodriguez 	return 0;
1871b2e1b302SLuis R. Rodriguez }
1872b2e1b302SLuis R. Rodriguez EXPORT_SYMBOL(regulatory_hint);
1873b2e1b302SLuis R. Rodriguez 
1874789fd033SLuis R. Rodriguez void regulatory_hint_country_ie(struct wiphy *wiphy, enum ieee80211_band band,
18751a919318SJohannes Berg 				const u8 *country_ie, u8 country_ie_len)
18763f2355cbSLuis R. Rodriguez {
18773f2355cbSLuis R. Rodriguez 	char alpha2[2];
18783f2355cbSLuis R. Rodriguez 	enum environment_cap env = ENVIRON_ANY;
1879db2424c5SJohannes Berg 	struct regulatory_request *request = NULL, *lr;
1880d335fe63SLuis R. Rodriguez 
18813f2355cbSLuis R. Rodriguez 	/* IE len must be evenly divisible by 2 */
18823f2355cbSLuis R. Rodriguez 	if (country_ie_len & 0x01)
1883db2424c5SJohannes Berg 		return;
18843f2355cbSLuis R. Rodriguez 
18853f2355cbSLuis R. Rodriguez 	if (country_ie_len < IEEE80211_COUNTRY_IE_MIN_LEN)
1886db2424c5SJohannes Berg 		return;
1887db2424c5SJohannes Berg 
1888db2424c5SJohannes Berg 	request = kzalloc(sizeof(*request), GFP_KERNEL);
1889db2424c5SJohannes Berg 	if (!request)
1890db2424c5SJohannes Berg 		return;
18913f2355cbSLuis R. Rodriguez 
18923f2355cbSLuis R. Rodriguez 	alpha2[0] = country_ie[0];
18933f2355cbSLuis R. Rodriguez 	alpha2[1] = country_ie[1];
18943f2355cbSLuis R. Rodriguez 
18953f2355cbSLuis R. Rodriguez 	if (country_ie[2] == 'I')
18963f2355cbSLuis R. Rodriguez 		env = ENVIRON_INDOOR;
18973f2355cbSLuis R. Rodriguez 	else if (country_ie[2] == 'O')
18983f2355cbSLuis R. Rodriguez 		env = ENVIRON_OUTDOOR;
18993f2355cbSLuis R. Rodriguez 
1900db2424c5SJohannes Berg 	rcu_read_lock();
1901db2424c5SJohannes Berg 	lr = get_last_request();
1902db2424c5SJohannes Berg 
1903db2424c5SJohannes Berg 	if (unlikely(!lr))
1904db2424c5SJohannes Berg 		goto out;
1905db2424c5SJohannes Berg 
1906fb1fc7adSLuis R. Rodriguez 	/*
19078b19e6caSLuis R. Rodriguez 	 * We will run this only upon a successful connection on cfg80211.
19084b44c8bcSLuis R. Rodriguez 	 * We leave conflict resolution to the workqueue, where can hold
19095fe231e8SJohannes Berg 	 * the RTNL.
1910fb1fc7adSLuis R. Rodriguez 	 */
1911c492db37SJohannes Berg 	if (lr->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE &&
1912c492db37SJohannes Berg 	    lr->wiphy_idx != WIPHY_IDX_INVALID)
19133f2355cbSLuis R. Rodriguez 		goto out;
19143f2355cbSLuis R. Rodriguez 
1915fe33eb39SLuis R. Rodriguez 	request->wiphy_idx = get_wiphy_idx(wiphy);
19164f366c5dSJohn W. Linville 	request->alpha2[0] = alpha2[0];
19174f366c5dSJohn W. Linville 	request->alpha2[1] = alpha2[1];
19187db90f4aSLuis R. Rodriguez 	request->initiator = NL80211_REGDOM_SET_BY_COUNTRY_IE;
1919fe33eb39SLuis R. Rodriguez 	request->country_ie_env = env;
19203f2355cbSLuis R. Rodriguez 
1921fe33eb39SLuis R. Rodriguez 	queue_regulatory_request(request);
1922db2424c5SJohannes Berg 	request = NULL;
19233f2355cbSLuis R. Rodriguez out:
1924db2424c5SJohannes Berg 	kfree(request);
1925db2424c5SJohannes Berg 	rcu_read_unlock();
19263f2355cbSLuis R. Rodriguez }
1927b2e1b302SLuis R. Rodriguez 
192809d989d1SLuis R. Rodriguez static void restore_alpha2(char *alpha2, bool reset_user)
192909d989d1SLuis R. Rodriguez {
193009d989d1SLuis R. Rodriguez 	/* indicates there is no alpha2 to consider for restoration */
193109d989d1SLuis R. Rodriguez 	alpha2[0] = '9';
193209d989d1SLuis R. Rodriguez 	alpha2[1] = '7';
193309d989d1SLuis R. Rodriguez 
193409d989d1SLuis R. Rodriguez 	/* The user setting has precedence over the module parameter */
193509d989d1SLuis R. Rodriguez 	if (is_user_regdom_saved()) {
193609d989d1SLuis R. Rodriguez 		/* Unless we're asked to ignore it and reset it */
193709d989d1SLuis R. Rodriguez 		if (reset_user) {
19381a919318SJohannes Berg 			REG_DBG_PRINT("Restoring regulatory settings including user preference\n");
193909d989d1SLuis R. Rodriguez 			user_alpha2[0] = '9';
194009d989d1SLuis R. Rodriguez 			user_alpha2[1] = '7';
194109d989d1SLuis R. Rodriguez 
194209d989d1SLuis R. Rodriguez 			/*
194309d989d1SLuis R. Rodriguez 			 * If we're ignoring user settings, we still need to
194409d989d1SLuis R. Rodriguez 			 * check the module parameter to ensure we put things
194509d989d1SLuis R. Rodriguez 			 * back as they were for a full restore.
194609d989d1SLuis R. Rodriguez 			 */
194709d989d1SLuis R. Rodriguez 			if (!is_world_regdom(ieee80211_regdom)) {
19481a919318SJohannes Berg 				REG_DBG_PRINT("Keeping preference on module parameter ieee80211_regdom: %c%c\n",
19491a919318SJohannes Berg 					      ieee80211_regdom[0], ieee80211_regdom[1]);
195009d989d1SLuis R. Rodriguez 				alpha2[0] = ieee80211_regdom[0];
195109d989d1SLuis R. Rodriguez 				alpha2[1] = ieee80211_regdom[1];
195209d989d1SLuis R. Rodriguez 			}
195309d989d1SLuis R. Rodriguez 		} else {
19541a919318SJohannes Berg 			REG_DBG_PRINT("Restoring regulatory settings while preserving user preference for: %c%c\n",
19551a919318SJohannes Berg 				      user_alpha2[0], user_alpha2[1]);
195609d989d1SLuis R. Rodriguez 			alpha2[0] = user_alpha2[0];
195709d989d1SLuis R. Rodriguez 			alpha2[1] = user_alpha2[1];
195809d989d1SLuis R. Rodriguez 		}
195909d989d1SLuis R. Rodriguez 	} else if (!is_world_regdom(ieee80211_regdom)) {
19601a919318SJohannes Berg 		REG_DBG_PRINT("Keeping preference on module parameter ieee80211_regdom: %c%c\n",
19611a919318SJohannes Berg 			      ieee80211_regdom[0], ieee80211_regdom[1]);
196209d989d1SLuis R. Rodriguez 		alpha2[0] = ieee80211_regdom[0];
196309d989d1SLuis R. Rodriguez 		alpha2[1] = ieee80211_regdom[1];
196409d989d1SLuis R. Rodriguez 	} else
1965d91e41b6SLuis R. Rodriguez 		REG_DBG_PRINT("Restoring regulatory settings\n");
196609d989d1SLuis R. Rodriguez }
196709d989d1SLuis R. Rodriguez 
19685ce543d1SRajkumar Manoharan static void restore_custom_reg_settings(struct wiphy *wiphy)
19695ce543d1SRajkumar Manoharan {
19705ce543d1SRajkumar Manoharan 	struct ieee80211_supported_band *sband;
19715ce543d1SRajkumar Manoharan 	enum ieee80211_band band;
19725ce543d1SRajkumar Manoharan 	struct ieee80211_channel *chan;
19735ce543d1SRajkumar Manoharan 	int i;
19745ce543d1SRajkumar Manoharan 
19755ce543d1SRajkumar Manoharan 	for (band = 0; band < IEEE80211_NUM_BANDS; band++) {
19765ce543d1SRajkumar Manoharan 		sband = wiphy->bands[band];
19775ce543d1SRajkumar Manoharan 		if (!sband)
19785ce543d1SRajkumar Manoharan 			continue;
19795ce543d1SRajkumar Manoharan 		for (i = 0; i < sband->n_channels; i++) {
19805ce543d1SRajkumar Manoharan 			chan = &sband->channels[i];
19815ce543d1SRajkumar Manoharan 			chan->flags = chan->orig_flags;
19825ce543d1SRajkumar Manoharan 			chan->max_antenna_gain = chan->orig_mag;
19835ce543d1SRajkumar Manoharan 			chan->max_power = chan->orig_mpwr;
1984899852afSPaul Stewart 			chan->beacon_found = false;
19855ce543d1SRajkumar Manoharan 		}
19865ce543d1SRajkumar Manoharan 	}
19875ce543d1SRajkumar Manoharan }
19885ce543d1SRajkumar Manoharan 
198909d989d1SLuis R. Rodriguez /*
199009d989d1SLuis R. Rodriguez  * Restoring regulatory settings involves ingoring any
199109d989d1SLuis R. Rodriguez  * possibly stale country IE information and user regulatory
199209d989d1SLuis R. Rodriguez  * settings if so desired, this includes any beacon hints
199309d989d1SLuis R. Rodriguez  * learned as we could have traveled outside to another country
199409d989d1SLuis R. Rodriguez  * after disconnection. To restore regulatory settings we do
199509d989d1SLuis R. Rodriguez  * exactly what we did at bootup:
199609d989d1SLuis R. Rodriguez  *
199709d989d1SLuis R. Rodriguez  *   - send a core regulatory hint
199809d989d1SLuis R. Rodriguez  *   - send a user regulatory hint if applicable
199909d989d1SLuis R. Rodriguez  *
200009d989d1SLuis R. Rodriguez  * Device drivers that send a regulatory hint for a specific country
200109d989d1SLuis R. Rodriguez  * keep their own regulatory domain on wiphy->regd so that does does
200209d989d1SLuis R. Rodriguez  * not need to be remembered.
200309d989d1SLuis R. Rodriguez  */
200409d989d1SLuis R. Rodriguez static void restore_regulatory_settings(bool reset_user)
200509d989d1SLuis R. Rodriguez {
200609d989d1SLuis R. Rodriguez 	char alpha2[2];
2007cee0bec5SDmitry Shmidt 	char world_alpha2[2];
200809d989d1SLuis R. Rodriguez 	struct reg_beacon *reg_beacon, *btmp;
200914609555SLuis R. Rodriguez 	struct regulatory_request *reg_request, *tmp;
201014609555SLuis R. Rodriguez 	LIST_HEAD(tmp_reg_req_list);
20115ce543d1SRajkumar Manoharan 	struct cfg80211_registered_device *rdev;
201209d989d1SLuis R. Rodriguez 
20135fe231e8SJohannes Berg 	ASSERT_RTNL();
20145fe231e8SJohannes Berg 
20152d319867SJohannes Berg 	reset_regdomains(true, &world_regdom);
201609d989d1SLuis R. Rodriguez 	restore_alpha2(alpha2, reset_user);
201709d989d1SLuis R. Rodriguez 
201814609555SLuis R. Rodriguez 	/*
201914609555SLuis R. Rodriguez 	 * If there's any pending requests we simply
202014609555SLuis R. Rodriguez 	 * stash them to a temporary pending queue and
202114609555SLuis R. Rodriguez 	 * add then after we've restored regulatory
202214609555SLuis R. Rodriguez 	 * settings.
202314609555SLuis R. Rodriguez 	 */
202414609555SLuis R. Rodriguez 	spin_lock(&reg_requests_lock);
2025fea9bcedSJohannes Berg 	list_for_each_entry_safe(reg_request, tmp, &reg_requests_list, list) {
2026fea9bcedSJohannes Berg 		if (reg_request->initiator != NL80211_REGDOM_SET_BY_USER)
202714609555SLuis R. Rodriguez 			continue;
202800a9ac4cSWei Yongjun 		list_move_tail(&reg_request->list, &tmp_reg_req_list);
202914609555SLuis R. Rodriguez 	}
203014609555SLuis R. Rodriguez 	spin_unlock(&reg_requests_lock);
203114609555SLuis R. Rodriguez 
203209d989d1SLuis R. Rodriguez 	/* Clear beacon hints */
203309d989d1SLuis R. Rodriguez 	spin_lock_bh(&reg_pending_beacons_lock);
2034fea9bcedSJohannes Berg 	list_for_each_entry_safe(reg_beacon, btmp, &reg_pending_beacons, list) {
203509d989d1SLuis R. Rodriguez 		list_del(&reg_beacon->list);
203609d989d1SLuis R. Rodriguez 		kfree(reg_beacon);
203709d989d1SLuis R. Rodriguez 	}
203809d989d1SLuis R. Rodriguez 	spin_unlock_bh(&reg_pending_beacons_lock);
203909d989d1SLuis R. Rodriguez 
2040fea9bcedSJohannes Berg 	list_for_each_entry_safe(reg_beacon, btmp, &reg_beacon_list, list) {
204109d989d1SLuis R. Rodriguez 		list_del(&reg_beacon->list);
204209d989d1SLuis R. Rodriguez 		kfree(reg_beacon);
204309d989d1SLuis R. Rodriguez 	}
204409d989d1SLuis R. Rodriguez 
204509d989d1SLuis R. Rodriguez 	/* First restore to the basic regulatory settings */
2046379b82f4SJohannes Berg 	world_alpha2[0] = cfg80211_world_regdom->alpha2[0];
2047379b82f4SJohannes Berg 	world_alpha2[1] = cfg80211_world_regdom->alpha2[1];
204809d989d1SLuis R. Rodriguez 
20495ce543d1SRajkumar Manoharan 	list_for_each_entry(rdev, &cfg80211_rdev_list, list) {
2050a2f73b6cSLuis R. Rodriguez 		if (rdev->wiphy.regulatory_flags & REGULATORY_CUSTOM_REG)
20515ce543d1SRajkumar Manoharan 			restore_custom_reg_settings(&rdev->wiphy);
20525ce543d1SRajkumar Manoharan 	}
20535ce543d1SRajkumar Manoharan 
2054cee0bec5SDmitry Shmidt 	regulatory_hint_core(world_alpha2);
205509d989d1SLuis R. Rodriguez 
205609d989d1SLuis R. Rodriguez 	/*
205709d989d1SLuis R. Rodriguez 	 * This restores the ieee80211_regdom module parameter
205809d989d1SLuis R. Rodriguez 	 * preference or the last user requested regulatory
205909d989d1SLuis R. Rodriguez 	 * settings, user regulatory settings takes precedence.
206009d989d1SLuis R. Rodriguez 	 */
206109d989d1SLuis R. Rodriguez 	if (is_an_alpha2(alpha2))
206257b5ce07SLuis R. Rodriguez 		regulatory_hint_user(user_alpha2, NL80211_USER_REG_HINT_USER);
206309d989d1SLuis R. Rodriguez 
206414609555SLuis R. Rodriguez 	spin_lock(&reg_requests_lock);
206511cff96cSJohannes Berg 	list_splice_tail_init(&tmp_reg_req_list, &reg_requests_list);
206614609555SLuis R. Rodriguez 	spin_unlock(&reg_requests_lock);
206714609555SLuis R. Rodriguez 
206814609555SLuis R. Rodriguez 	REG_DBG_PRINT("Kicking the queue\n");
206914609555SLuis R. Rodriguez 
207014609555SLuis R. Rodriguez 	schedule_work(&reg_work);
207114609555SLuis R. Rodriguez }
207209d989d1SLuis R. Rodriguez 
207309d989d1SLuis R. Rodriguez void regulatory_hint_disconnect(void)
207409d989d1SLuis R. Rodriguez {
20751a919318SJohannes Berg 	REG_DBG_PRINT("All devices are disconnected, going to restore regulatory settings\n");
207609d989d1SLuis R. Rodriguez 	restore_regulatory_settings(false);
207709d989d1SLuis R. Rodriguez }
207809d989d1SLuis R. Rodriguez 
2079e38f8a7aSLuis R. Rodriguez static bool freq_is_chan_12_13_14(u16 freq)
2080e38f8a7aSLuis R. Rodriguez {
208159eb21a6SBruno Randolf 	if (freq == ieee80211_channel_to_frequency(12, IEEE80211_BAND_2GHZ) ||
208259eb21a6SBruno Randolf 	    freq == ieee80211_channel_to_frequency(13, IEEE80211_BAND_2GHZ) ||
208359eb21a6SBruno Randolf 	    freq == ieee80211_channel_to_frequency(14, IEEE80211_BAND_2GHZ))
2084e38f8a7aSLuis R. Rodriguez 		return true;
2085e38f8a7aSLuis R. Rodriguez 	return false;
2086e38f8a7aSLuis R. Rodriguez }
2087e38f8a7aSLuis R. Rodriguez 
20883ebfa6e7SLuis R. Rodriguez static bool pending_reg_beacon(struct ieee80211_channel *beacon_chan)
20893ebfa6e7SLuis R. Rodriguez {
20903ebfa6e7SLuis R. Rodriguez 	struct reg_beacon *pending_beacon;
20913ebfa6e7SLuis R. Rodriguez 
20923ebfa6e7SLuis R. Rodriguez 	list_for_each_entry(pending_beacon, &reg_pending_beacons, list)
20933ebfa6e7SLuis R. Rodriguez 		if (beacon_chan->center_freq ==
20943ebfa6e7SLuis R. Rodriguez 		    pending_beacon->chan.center_freq)
20953ebfa6e7SLuis R. Rodriguez 			return true;
20963ebfa6e7SLuis R. Rodriguez 	return false;
20973ebfa6e7SLuis R. Rodriguez }
20983ebfa6e7SLuis R. Rodriguez 
2099e38f8a7aSLuis R. Rodriguez int regulatory_hint_found_beacon(struct wiphy *wiphy,
2100e38f8a7aSLuis R. Rodriguez 				 struct ieee80211_channel *beacon_chan,
2101e38f8a7aSLuis R. Rodriguez 				 gfp_t gfp)
2102e38f8a7aSLuis R. Rodriguez {
2103e38f8a7aSLuis R. Rodriguez 	struct reg_beacon *reg_beacon;
21043ebfa6e7SLuis R. Rodriguez 	bool processing;
2105e38f8a7aSLuis R. Rodriguez 
21061a919318SJohannes Berg 	if (beacon_chan->beacon_found ||
21071a919318SJohannes Berg 	    beacon_chan->flags & IEEE80211_CHAN_RADAR ||
2108e38f8a7aSLuis R. Rodriguez 	    (beacon_chan->band == IEEE80211_BAND_2GHZ &&
21091a919318SJohannes Berg 	     !freq_is_chan_12_13_14(beacon_chan->center_freq)))
2110e38f8a7aSLuis R. Rodriguez 		return 0;
2111e38f8a7aSLuis R. Rodriguez 
21123ebfa6e7SLuis R. Rodriguez 	spin_lock_bh(&reg_pending_beacons_lock);
21133ebfa6e7SLuis R. Rodriguez 	processing = pending_reg_beacon(beacon_chan);
21143ebfa6e7SLuis R. Rodriguez 	spin_unlock_bh(&reg_pending_beacons_lock);
21153ebfa6e7SLuis R. Rodriguez 
21163ebfa6e7SLuis R. Rodriguez 	if (processing)
2117e38f8a7aSLuis R. Rodriguez 		return 0;
2118e38f8a7aSLuis R. Rodriguez 
2119e38f8a7aSLuis R. Rodriguez 	reg_beacon = kzalloc(sizeof(struct reg_beacon), gfp);
2120e38f8a7aSLuis R. Rodriguez 	if (!reg_beacon)
2121e38f8a7aSLuis R. Rodriguez 		return -ENOMEM;
2122e38f8a7aSLuis R. Rodriguez 
21231a919318SJohannes Berg 	REG_DBG_PRINT("Found new beacon on frequency: %d MHz (Ch %d) on %s\n",
2124e38f8a7aSLuis R. Rodriguez 		      beacon_chan->center_freq,
2125e38f8a7aSLuis R. Rodriguez 		      ieee80211_frequency_to_channel(beacon_chan->center_freq),
2126e38f8a7aSLuis R. Rodriguez 		      wiphy_name(wiphy));
21274113f751SLuis R. Rodriguez 
2128e38f8a7aSLuis R. Rodriguez 	memcpy(&reg_beacon->chan, beacon_chan,
2129e38f8a7aSLuis R. Rodriguez 	       sizeof(struct ieee80211_channel));
2130e38f8a7aSLuis R. Rodriguez 
2131e38f8a7aSLuis R. Rodriguez 	/*
2132e38f8a7aSLuis R. Rodriguez 	 * Since we can be called from BH or and non-BH context
2133e38f8a7aSLuis R. Rodriguez 	 * we must use spin_lock_bh()
2134e38f8a7aSLuis R. Rodriguez 	 */
2135e38f8a7aSLuis R. Rodriguez 	spin_lock_bh(&reg_pending_beacons_lock);
2136e38f8a7aSLuis R. Rodriguez 	list_add_tail(&reg_beacon->list, &reg_pending_beacons);
2137e38f8a7aSLuis R. Rodriguez 	spin_unlock_bh(&reg_pending_beacons_lock);
2138e38f8a7aSLuis R. Rodriguez 
2139e38f8a7aSLuis R. Rodriguez 	schedule_work(&reg_work);
2140e38f8a7aSLuis R. Rodriguez 
2141e38f8a7aSLuis R. Rodriguez 	return 0;
2142e38f8a7aSLuis R. Rodriguez }
2143e38f8a7aSLuis R. Rodriguez 
2144a3d2eaf0SJohannes Berg static void print_rd_rules(const struct ieee80211_regdomain *rd)
2145b2e1b302SLuis R. Rodriguez {
2146b2e1b302SLuis R. Rodriguez 	unsigned int i;
2147a3d2eaf0SJohannes Berg 	const struct ieee80211_reg_rule *reg_rule = NULL;
2148a3d2eaf0SJohannes Berg 	const struct ieee80211_freq_range *freq_range = NULL;
2149a3d2eaf0SJohannes Berg 	const struct ieee80211_power_rule *power_rule = NULL;
2150b2e1b302SLuis R. Rodriguez 
2151e9c0268fSJoe Perches 	pr_info("  (start_freq - end_freq @ bandwidth), (max_antenna_gain, max_eirp)\n");
2152b2e1b302SLuis R. Rodriguez 
2153b2e1b302SLuis R. Rodriguez 	for (i = 0; i < rd->n_reg_rules; i++) {
2154b2e1b302SLuis R. Rodriguez 		reg_rule = &rd->reg_rules[i];
2155b2e1b302SLuis R. Rodriguez 		freq_range = &reg_rule->freq_range;
2156b2e1b302SLuis R. Rodriguez 		power_rule = &reg_rule->power_rule;
2157b2e1b302SLuis R. Rodriguez 
2158fb1fc7adSLuis R. Rodriguez 		/*
2159fb1fc7adSLuis R. Rodriguez 		 * There may not be documentation for max antenna gain
2160fb1fc7adSLuis R. Rodriguez 		 * in certain regions
2161fb1fc7adSLuis R. Rodriguez 		 */
2162b2e1b302SLuis R. Rodriguez 		if (power_rule->max_antenna_gain)
2163e9c0268fSJoe Perches 			pr_info("  (%d KHz - %d KHz @ %d KHz), (%d mBi, %d mBm)\n",
2164b2e1b302SLuis R. Rodriguez 				freq_range->start_freq_khz,
2165b2e1b302SLuis R. Rodriguez 				freq_range->end_freq_khz,
2166b2e1b302SLuis R. Rodriguez 				freq_range->max_bandwidth_khz,
2167b2e1b302SLuis R. Rodriguez 				power_rule->max_antenna_gain,
2168b2e1b302SLuis R. Rodriguez 				power_rule->max_eirp);
2169b2e1b302SLuis R. Rodriguez 		else
2170e9c0268fSJoe Perches 			pr_info("  (%d KHz - %d KHz @ %d KHz), (N/A, %d mBm)\n",
2171b2e1b302SLuis R. Rodriguez 				freq_range->start_freq_khz,
2172b2e1b302SLuis R. Rodriguez 				freq_range->end_freq_khz,
2173b2e1b302SLuis R. Rodriguez 				freq_range->max_bandwidth_khz,
2174b2e1b302SLuis R. Rodriguez 				power_rule->max_eirp);
2175b2e1b302SLuis R. Rodriguez 	}
2176b2e1b302SLuis R. Rodriguez }
2177b2e1b302SLuis R. Rodriguez 
21784c7d3982SLuis R. Rodriguez bool reg_supported_dfs_region(enum nl80211_dfs_regions dfs_region)
21798b60b078SLuis R. Rodriguez {
21808b60b078SLuis R. Rodriguez 	switch (dfs_region) {
21818b60b078SLuis R. Rodriguez 	case NL80211_DFS_UNSET:
21828b60b078SLuis R. Rodriguez 	case NL80211_DFS_FCC:
21838b60b078SLuis R. Rodriguez 	case NL80211_DFS_ETSI:
21848b60b078SLuis R. Rodriguez 	case NL80211_DFS_JP:
21858b60b078SLuis R. Rodriguez 		return true;
21868b60b078SLuis R. Rodriguez 	default:
21878b60b078SLuis R. Rodriguez 		REG_DBG_PRINT("Ignoring uknown DFS master region: %d\n",
21888b60b078SLuis R. Rodriguez 			      dfs_region);
21898b60b078SLuis R. Rodriguez 		return false;
21908b60b078SLuis R. Rodriguez 	}
21918b60b078SLuis R. Rodriguez }
21928b60b078SLuis R. Rodriguez 
2193a3d2eaf0SJohannes Berg static void print_regdomain(const struct ieee80211_regdomain *rd)
2194b2e1b302SLuis R. Rodriguez {
2195c492db37SJohannes Berg 	struct regulatory_request *lr = get_last_request();
2196b2e1b302SLuis R. Rodriguez 
21973f2355cbSLuis R. Rodriguez 	if (is_intersected_alpha2(rd->alpha2)) {
2198c492db37SJohannes Berg 		if (lr->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE) {
219979c97e97SJohannes Berg 			struct cfg80211_registered_device *rdev;
2200c492db37SJohannes Berg 			rdev = cfg80211_rdev_by_wiphy_idx(lr->wiphy_idx);
220179c97e97SJohannes Berg 			if (rdev) {
2202e9c0268fSJoe Perches 				pr_info("Current regulatory domain updated by AP to: %c%c\n",
220379c97e97SJohannes Berg 					rdev->country_ie_alpha2[0],
220479c97e97SJohannes Berg 					rdev->country_ie_alpha2[1]);
22053f2355cbSLuis R. Rodriguez 			} else
2206e9c0268fSJoe Perches 				pr_info("Current regulatory domain intersected:\n");
22073f2355cbSLuis R. Rodriguez 		} else
2208e9c0268fSJoe Perches 			pr_info("Current regulatory domain intersected:\n");
22091a919318SJohannes Berg 	} else if (is_world_regdom(rd->alpha2)) {
2210e9c0268fSJoe Perches 		pr_info("World regulatory domain updated:\n");
22111a919318SJohannes Berg 	} else {
2212b2e1b302SLuis R. Rodriguez 		if (is_unknown_alpha2(rd->alpha2))
2213e9c0268fSJoe Perches 			pr_info("Regulatory domain changed to driver built-in settings (unknown country)\n");
221457b5ce07SLuis R. Rodriguez 		else {
2215c492db37SJohannes Berg 			if (reg_request_cell_base(lr))
22161a919318SJohannes Berg 				pr_info("Regulatory domain changed to country: %c%c by Cell Station\n",
2217b2e1b302SLuis R. Rodriguez 					rd->alpha2[0], rd->alpha2[1]);
221857b5ce07SLuis R. Rodriguez 			else
22191a919318SJohannes Berg 				pr_info("Regulatory domain changed to country: %c%c\n",
222057b5ce07SLuis R. Rodriguez 					rd->alpha2[0], rd->alpha2[1]);
222157b5ce07SLuis R. Rodriguez 		}
2222b2e1b302SLuis R. Rodriguez 	}
22231a919318SJohannes Berg 
22243ef121b5SLuis R. Rodriguez 	pr_info(" DFS Master region: %s", reg_dfs_region_str(rd->dfs_region));
2225b2e1b302SLuis R. Rodriguez 	print_rd_rules(rd);
2226b2e1b302SLuis R. Rodriguez }
2227b2e1b302SLuis R. Rodriguez 
22282df78167SJohannes Berg static void print_regdomain_info(const struct ieee80211_regdomain *rd)
2229b2e1b302SLuis R. Rodriguez {
2230e9c0268fSJoe Perches 	pr_info("Regulatory domain: %c%c\n", rd->alpha2[0], rd->alpha2[1]);
2231b2e1b302SLuis R. Rodriguez 	print_rd_rules(rd);
2232b2e1b302SLuis R. Rodriguez }
2233b2e1b302SLuis R. Rodriguez 
22343b9e5acaSLuis R. Rodriguez static int reg_set_rd_core(const struct ieee80211_regdomain *rd)
22353b9e5acaSLuis R. Rodriguez {
22363b9e5acaSLuis R. Rodriguez 	if (!is_world_regdom(rd->alpha2))
22373b9e5acaSLuis R. Rodriguez 		return -EINVAL;
22383b9e5acaSLuis R. Rodriguez 	update_world_regdomain(rd);
22393b9e5acaSLuis R. Rodriguez 	return 0;
22403b9e5acaSLuis R. Rodriguez }
22413b9e5acaSLuis R. Rodriguez 
224284721d44SLuis R. Rodriguez static int reg_set_rd_user(const struct ieee80211_regdomain *rd,
224384721d44SLuis R. Rodriguez 			   struct regulatory_request *user_request)
224484721d44SLuis R. Rodriguez {
224584721d44SLuis R. Rodriguez 	const struct ieee80211_regdomain *intersected_rd = NULL;
224684721d44SLuis R. Rodriguez 
224784721d44SLuis R. Rodriguez 	if (is_world_regdom(rd->alpha2))
224884721d44SLuis R. Rodriguez 		return -EINVAL;
224984721d44SLuis R. Rodriguez 
225084721d44SLuis R. Rodriguez 	if (!regdom_changes(rd->alpha2))
225184721d44SLuis R. Rodriguez 		return -EALREADY;
225284721d44SLuis R. Rodriguez 
225384721d44SLuis R. Rodriguez 	if (!is_valid_rd(rd)) {
225484721d44SLuis R. Rodriguez 		pr_err("Invalid regulatory domain detected:\n");
225584721d44SLuis R. Rodriguez 		print_regdomain_info(rd);
225684721d44SLuis R. Rodriguez 		return -EINVAL;
225784721d44SLuis R. Rodriguez 	}
225884721d44SLuis R. Rodriguez 
225984721d44SLuis R. Rodriguez 	if (!user_request->intersect) {
226084721d44SLuis R. Rodriguez 		reset_regdomains(false, rd);
226184721d44SLuis R. Rodriguez 		return 0;
226284721d44SLuis R. Rodriguez 	}
226384721d44SLuis R. Rodriguez 
226484721d44SLuis R. Rodriguez 	intersected_rd = regdom_intersect(rd, get_cfg80211_regdom());
226584721d44SLuis R. Rodriguez 	if (!intersected_rd)
226684721d44SLuis R. Rodriguez 		return -EINVAL;
226784721d44SLuis R. Rodriguez 
226884721d44SLuis R. Rodriguez 	kfree(rd);
226984721d44SLuis R. Rodriguez 	rd = NULL;
227084721d44SLuis R. Rodriguez 	reset_regdomains(false, intersected_rd);
227184721d44SLuis R. Rodriguez 
227284721d44SLuis R. Rodriguez 	return 0;
227384721d44SLuis R. Rodriguez }
227484721d44SLuis R. Rodriguez 
2275f5fe3247SLuis R. Rodriguez static int reg_set_rd_driver(const struct ieee80211_regdomain *rd,
2276f5fe3247SLuis R. Rodriguez 			     struct regulatory_request *driver_request)
2277b2e1b302SLuis R. Rodriguez {
2278e9763c3cSJohannes Berg 	const struct ieee80211_regdomain *regd;
22799c96477dSLuis R. Rodriguez 	const struct ieee80211_regdomain *intersected_rd = NULL;
2280f5fe3247SLuis R. Rodriguez 	const struct ieee80211_regdomain *tmp;
2281806a9e39SLuis R. Rodriguez 	struct wiphy *request_wiphy;
22826913b49aSJohannes Berg 
2283f5fe3247SLuis R. Rodriguez 	if (is_world_regdom(rd->alpha2))
2284b2e1b302SLuis R. Rodriguez 		return -EINVAL;
2285b2e1b302SLuis R. Rodriguez 
2286baeb66feSJohn W. Linville 	if (!regdom_changes(rd->alpha2))
228795908535SKalle Valo 		return -EALREADY;
2288b2e1b302SLuis R. Rodriguez 
2289b2e1b302SLuis R. Rodriguez 	if (!is_valid_rd(rd)) {
2290e9c0268fSJoe Perches 		pr_err("Invalid regulatory domain detected:\n");
2291b2e1b302SLuis R. Rodriguez 		print_regdomain_info(rd);
2292b2e1b302SLuis R. Rodriguez 		return -EINVAL;
2293b2e1b302SLuis R. Rodriguez 	}
2294b2e1b302SLuis R. Rodriguez 
2295f5fe3247SLuis R. Rodriguez 	request_wiphy = wiphy_idx_to_wiphy(driver_request->wiphy_idx);
2296f5fe3247SLuis R. Rodriguez 	if (!request_wiphy) {
22970bac71afSLuis R. Rodriguez 		schedule_delayed_work(&reg_timeout, 0);
2298de3584bdSJohannes Berg 		return -ENODEV;
2299de3584bdSJohannes Berg 	}
2300806a9e39SLuis R. Rodriguez 
2301f5fe3247SLuis R. Rodriguez 	if (!driver_request->intersect) {
2302558f6d32SLuis R. Rodriguez 		if (request_wiphy->regd)
2303558f6d32SLuis R. Rodriguez 			return -EALREADY;
23043e0c3ff3SLuis R. Rodriguez 
2305e9763c3cSJohannes Berg 		regd = reg_copy_regd(rd);
2306e9763c3cSJohannes Berg 		if (IS_ERR(regd))
2307e9763c3cSJohannes Berg 			return PTR_ERR(regd);
23083e0c3ff3SLuis R. Rodriguez 
2309458f4f9eSJohannes Berg 		rcu_assign_pointer(request_wiphy->regd, regd);
2310379b82f4SJohannes Berg 		reset_regdomains(false, rd);
2311b8295acdSLuis R. Rodriguez 		return 0;
2312b8295acdSLuis R. Rodriguez 	}
2313b8295acdSLuis R. Rodriguez 
2314458f4f9eSJohannes Berg 	intersected_rd = regdom_intersect(rd, get_cfg80211_regdom());
23159c96477dSLuis R. Rodriguez 	if (!intersected_rd)
23169c96477dSLuis R. Rodriguez 		return -EINVAL;
2317b8295acdSLuis R. Rodriguez 
2318fb1fc7adSLuis R. Rodriguez 	/*
2319fb1fc7adSLuis R. Rodriguez 	 * We can trash what CRDA provided now.
23203e0c3ff3SLuis R. Rodriguez 	 * However if a driver requested this specific regulatory
2321fb1fc7adSLuis R. Rodriguez 	 * domain we keep it for its private use
2322fb1fc7adSLuis R. Rodriguez 	 */
2323b7566fc3SLarry Finger 	tmp = get_wiphy_regdom(request_wiphy);
2324458f4f9eSJohannes Berg 	rcu_assign_pointer(request_wiphy->regd, rd);
2325b7566fc3SLarry Finger 	rcu_free_regdom(tmp);
23263e0c3ff3SLuis R. Rodriguez 
2327b8295acdSLuis R. Rodriguez 	rd = NULL;
2328b8295acdSLuis R. Rodriguez 
2329379b82f4SJohannes Berg 	reset_regdomains(false, intersected_rd);
2330b8295acdSLuis R. Rodriguez 
2331b8295acdSLuis R. Rodriguez 	return 0;
23329c96477dSLuis R. Rodriguez }
23339c96477dSLuis R. Rodriguez 
233401992406SLuis R. Rodriguez static int reg_set_rd_country_ie(const struct ieee80211_regdomain *rd,
233501992406SLuis R. Rodriguez 				 struct regulatory_request *country_ie_request)
2336f5fe3247SLuis R. Rodriguez {
2337f5fe3247SLuis R. Rodriguez 	struct wiphy *request_wiphy;
2338f5fe3247SLuis R. Rodriguez 
2339f5fe3247SLuis R. Rodriguez 	if (!is_alpha2_set(rd->alpha2) && !is_an_alpha2(rd->alpha2) &&
2340f5fe3247SLuis R. Rodriguez 	    !is_unknown_alpha2(rd->alpha2))
2341f5fe3247SLuis R. Rodriguez 		return -EINVAL;
2342f5fe3247SLuis R. Rodriguez 
2343f5fe3247SLuis R. Rodriguez 	/*
2344f5fe3247SLuis R. Rodriguez 	 * Lets only bother proceeding on the same alpha2 if the current
2345f5fe3247SLuis R. Rodriguez 	 * rd is non static (it means CRDA was present and was used last)
2346f5fe3247SLuis R. Rodriguez 	 * and the pending request came in from a country IE
2347f5fe3247SLuis R. Rodriguez 	 */
2348f5fe3247SLuis R. Rodriguez 
2349f5fe3247SLuis R. Rodriguez 	if (!is_valid_rd(rd)) {
2350f5fe3247SLuis R. Rodriguez 		pr_err("Invalid regulatory domain detected:\n");
2351f5fe3247SLuis R. Rodriguez 		print_regdomain_info(rd);
23523f2355cbSLuis R. Rodriguez 		return -EINVAL;
2353b2e1b302SLuis R. Rodriguez 	}
2354b2e1b302SLuis R. Rodriguez 
235501992406SLuis R. Rodriguez 	request_wiphy = wiphy_idx_to_wiphy(country_ie_request->wiphy_idx);
2356f5fe3247SLuis R. Rodriguez 	if (!request_wiphy) {
2357f5fe3247SLuis R. Rodriguez 		schedule_delayed_work(&reg_timeout, 0);
2358f5fe3247SLuis R. Rodriguez 		return -ENODEV;
2359f5fe3247SLuis R. Rodriguez 	}
2360f5fe3247SLuis R. Rodriguez 
236101992406SLuis R. Rodriguez 	if (country_ie_request->intersect)
2362f5fe3247SLuis R. Rodriguez 		return -EINVAL;
2363f5fe3247SLuis R. Rodriguez 
2364f5fe3247SLuis R. Rodriguez 	reset_regdomains(false, rd);
2365f5fe3247SLuis R. Rodriguez 	return 0;
2366f5fe3247SLuis R. Rodriguez }
2367b2e1b302SLuis R. Rodriguez 
2368fb1fc7adSLuis R. Rodriguez /*
2369fb1fc7adSLuis R. Rodriguez  * Use this call to set the current regulatory domain. Conflicts with
2370b2e1b302SLuis R. Rodriguez  * multiple drivers can be ironed out later. Caller must've already
2371458f4f9eSJohannes Berg  * kmalloc'd the rd structure.
2372fb1fc7adSLuis R. Rodriguez  */
2373a3d2eaf0SJohannes Berg int set_regdom(const struct ieee80211_regdomain *rd)
2374b2e1b302SLuis R. Rodriguez {
2375c492db37SJohannes Berg 	struct regulatory_request *lr;
2376b2e1b302SLuis R. Rodriguez 	int r;
2377b2e1b302SLuis R. Rodriguez 
23783b9e5acaSLuis R. Rodriguez 	if (!reg_is_valid_request(rd->alpha2)) {
23793b9e5acaSLuis R. Rodriguez 		kfree(rd);
23803b9e5acaSLuis R. Rodriguez 		return -EINVAL;
23813b9e5acaSLuis R. Rodriguez 	}
23823b9e5acaSLuis R. Rodriguez 
2383c492db37SJohannes Berg 	lr = get_last_request();
2384abc7381bSLuis R. Rodriguez 
2385b2e1b302SLuis R. Rodriguez 	/* Note that this doesn't update the wiphys, this is done below */
23863b9e5acaSLuis R. Rodriguez 	switch (lr->initiator) {
23873b9e5acaSLuis R. Rodriguez 	case NL80211_REGDOM_SET_BY_CORE:
23883b9e5acaSLuis R. Rodriguez 		r = reg_set_rd_core(rd);
23893b9e5acaSLuis R. Rodriguez 		break;
23903b9e5acaSLuis R. Rodriguez 	case NL80211_REGDOM_SET_BY_USER:
239184721d44SLuis R. Rodriguez 		r = reg_set_rd_user(rd, lr);
239284721d44SLuis R. Rodriguez 		break;
23933b9e5acaSLuis R. Rodriguez 	case NL80211_REGDOM_SET_BY_DRIVER:
2394f5fe3247SLuis R. Rodriguez 		r = reg_set_rd_driver(rd, lr);
2395f5fe3247SLuis R. Rodriguez 		break;
23963b9e5acaSLuis R. Rodriguez 	case NL80211_REGDOM_SET_BY_COUNTRY_IE:
239701992406SLuis R. Rodriguez 		r = reg_set_rd_country_ie(rd, lr);
23983b9e5acaSLuis R. Rodriguez 		break;
23993b9e5acaSLuis R. Rodriguez 	default:
24003b9e5acaSLuis R. Rodriguez 		WARN(1, "invalid initiator %d\n", lr->initiator);
24013b9e5acaSLuis R. Rodriguez 		return -EINVAL;
24023b9e5acaSLuis R. Rodriguez 	}
24033b9e5acaSLuis R. Rodriguez 
2404d2372b31SJohannes Berg 	if (r) {
240595908535SKalle Valo 		if (r == -EALREADY)
240695908535SKalle Valo 			reg_set_request_processed();
240795908535SKalle Valo 
2408d2372b31SJohannes Berg 		kfree(rd);
240938fd2143SJohannes Berg 		return r;
2410d2372b31SJohannes Berg 	}
2411b2e1b302SLuis R. Rodriguez 
2412b2e1b302SLuis R. Rodriguez 	/* This would make this whole thing pointless */
241338fd2143SJohannes Berg 	if (WARN_ON(!lr->intersect && rd != get_cfg80211_regdom()))
241438fd2143SJohannes Berg 		return -EINVAL;
2415b2e1b302SLuis R. Rodriguez 
2416b2e1b302SLuis R. Rodriguez 	/* update all wiphys now with the new established regulatory domain */
2417c492db37SJohannes Berg 	update_all_wiphy_regulatory(lr->initiator);
2418b2e1b302SLuis R. Rodriguez 
2419458f4f9eSJohannes Berg 	print_regdomain(get_cfg80211_regdom());
2420b2e1b302SLuis R. Rodriguez 
2421c492db37SJohannes Berg 	nl80211_send_reg_change_event(lr);
242273d54c9eSLuis R. Rodriguez 
2423b2e253cfSLuis R. Rodriguez 	reg_set_request_processed();
2424b2e253cfSLuis R. Rodriguez 
242538fd2143SJohannes Berg 	return 0;
2426b2e1b302SLuis R. Rodriguez }
2427b2e1b302SLuis R. Rodriguez 
24284d9d88d1SScott James Remnant int reg_device_uevent(struct device *dev, struct kobj_uevent_env *env)
24294d9d88d1SScott James Remnant {
24304a484cffSJohannes Berg 	struct regulatory_request *lr;
24314a484cffSJohannes Berg 	u8 alpha2[2];
24324a484cffSJohannes Berg 	bool add = false;
24334d9d88d1SScott James Remnant 
24344a484cffSJohannes Berg 	rcu_read_lock();
24354a484cffSJohannes Berg 	lr = get_last_request();
2436c492db37SJohannes Berg 	if (lr && !lr->processed) {
24374a484cffSJohannes Berg 		memcpy(alpha2, lr->alpha2, 2);
24384a484cffSJohannes Berg 		add = true;
24394d9d88d1SScott James Remnant 	}
24404a484cffSJohannes Berg 	rcu_read_unlock();
24414d9d88d1SScott James Remnant 
24424a484cffSJohannes Berg 	if (add)
24434a484cffSJohannes Berg 		return add_uevent_var(env, "COUNTRY=%c%c",
24444a484cffSJohannes Berg 				      alpha2[0], alpha2[1]);
24454d9d88d1SScott James Remnant 	return 0;
24464d9d88d1SScott James Remnant }
24474d9d88d1SScott James Remnant 
244857b5ce07SLuis R. Rodriguez void wiphy_regulatory_register(struct wiphy *wiphy)
244957b5ce07SLuis R. Rodriguez {
245023df0b73SArik Nemtsov 	struct regulatory_request *lr;
245123df0b73SArik Nemtsov 
245257b5ce07SLuis R. Rodriguez 	if (!reg_dev_ignore_cell_hint(wiphy))
245357b5ce07SLuis R. Rodriguez 		reg_num_devs_support_basehint++;
245457b5ce07SLuis R. Rodriguez 
245523df0b73SArik Nemtsov 	lr = get_last_request();
245623df0b73SArik Nemtsov 	wiphy_update_regulatory(wiphy, lr->initiator);
245757b5ce07SLuis R. Rodriguez }
245857b5ce07SLuis R. Rodriguez 
2459bfead080SLuis R. Rodriguez void wiphy_regulatory_deregister(struct wiphy *wiphy)
24603f2355cbSLuis R. Rodriguez {
24610ad8acafSLuis R. Rodriguez 	struct wiphy *request_wiphy = NULL;
2462c492db37SJohannes Berg 	struct regulatory_request *lr;
2463761cf7ecSLuis R. Rodriguez 
2464c492db37SJohannes Berg 	lr = get_last_request();
2465abc7381bSLuis R. Rodriguez 
246657b5ce07SLuis R. Rodriguez 	if (!reg_dev_ignore_cell_hint(wiphy))
246757b5ce07SLuis R. Rodriguez 		reg_num_devs_support_basehint--;
246857b5ce07SLuis R. Rodriguez 
2469458f4f9eSJohannes Berg 	rcu_free_regdom(get_wiphy_regdom(wiphy));
2470458f4f9eSJohannes Berg 	rcu_assign_pointer(wiphy->regd, NULL);
24710ef9ccddSChris Wright 
2472c492db37SJohannes Berg 	if (lr)
2473c492db37SJohannes Berg 		request_wiphy = wiphy_idx_to_wiphy(lr->wiphy_idx);
2474806a9e39SLuis R. Rodriguez 
24750ef9ccddSChris Wright 	if (!request_wiphy || request_wiphy != wiphy)
247638fd2143SJohannes Berg 		return;
24770ef9ccddSChris Wright 
2478c492db37SJohannes Berg 	lr->wiphy_idx = WIPHY_IDX_INVALID;
2479c492db37SJohannes Berg 	lr->country_ie_env = ENVIRON_ANY;
24803f2355cbSLuis R. Rodriguez }
24813f2355cbSLuis R. Rodriguez 
2482a90c7a31SLuis R. Rodriguez static void reg_timeout_work(struct work_struct *work)
2483a90c7a31SLuis R. Rodriguez {
24841a919318SJohannes Berg 	REG_DBG_PRINT("Timeout while waiting for CRDA to reply, restoring regulatory settings\n");
2485f77b86d7SJohannes Berg 	rtnl_lock();
2486a90c7a31SLuis R. Rodriguez 	restore_regulatory_settings(true);
2487f77b86d7SJohannes Berg 	rtnl_unlock();
2488a90c7a31SLuis R. Rodriguez }
2489a90c7a31SLuis R. Rodriguez 
24902fcc9f73SUwe Kleine-König int __init regulatory_init(void)
2491b2e1b302SLuis R. Rodriguez {
2492bcf4f99bSLuis R. Rodriguez 	int err = 0;
2493734366deSJohannes Berg 
2494b2e1b302SLuis R. Rodriguez 	reg_pdev = platform_device_register_simple("regulatory", 0, NULL, 0);
2495b2e1b302SLuis R. Rodriguez 	if (IS_ERR(reg_pdev))
2496b2e1b302SLuis R. Rodriguez 		return PTR_ERR(reg_pdev);
2497734366deSJohannes Berg 
24984d9d88d1SScott James Remnant 	reg_pdev->dev.type = &reg_device_type;
24994d9d88d1SScott James Remnant 
2500fe33eb39SLuis R. Rodriguez 	spin_lock_init(&reg_requests_lock);
2501e38f8a7aSLuis R. Rodriguez 	spin_lock_init(&reg_pending_beacons_lock);
2502fe33eb39SLuis R. Rodriguez 
250380007efeSLuis R. Rodriguez 	reg_regdb_size_check();
250480007efeSLuis R. Rodriguez 
2505458f4f9eSJohannes Berg 	rcu_assign_pointer(cfg80211_regdomain, cfg80211_world_regdom);
2506734366deSJohannes Berg 
250709d989d1SLuis R. Rodriguez 	user_alpha2[0] = '9';
250809d989d1SLuis R. Rodriguez 	user_alpha2[1] = '7';
250909d989d1SLuis R. Rodriguez 
2510ae9e4b0dSLuis R. Rodriguez 	/* We always try to get an update for the static regdomain */
2511458f4f9eSJohannes Berg 	err = regulatory_hint_core(cfg80211_world_regdom->alpha2);
2512bcf4f99bSLuis R. Rodriguez 	if (err) {
2513bcf4f99bSLuis R. Rodriguez 		if (err == -ENOMEM)
2514bcf4f99bSLuis R. Rodriguez 			return err;
2515bcf4f99bSLuis R. Rodriguez 		/*
2516bcf4f99bSLuis R. Rodriguez 		 * N.B. kobject_uevent_env() can fail mainly for when we're out
2517bcf4f99bSLuis R. Rodriguez 		 * memory which is handled and propagated appropriately above
2518bcf4f99bSLuis R. Rodriguez 		 * but it can also fail during a netlink_broadcast() or during
2519bcf4f99bSLuis R. Rodriguez 		 * early boot for call_usermodehelper(). For now treat these
2520bcf4f99bSLuis R. Rodriguez 		 * errors as non-fatal.
2521bcf4f99bSLuis R. Rodriguez 		 */
2522e9c0268fSJoe Perches 		pr_err("kobject_uevent_env() was unable to call CRDA during init\n");
2523bcf4f99bSLuis R. Rodriguez 	}
2524734366deSJohannes Berg 
2525ae9e4b0dSLuis R. Rodriguez 	/*
2526ae9e4b0dSLuis R. Rodriguez 	 * Finally, if the user set the module parameter treat it
2527ae9e4b0dSLuis R. Rodriguez 	 * as a user hint.
2528ae9e4b0dSLuis R. Rodriguez 	 */
2529ae9e4b0dSLuis R. Rodriguez 	if (!is_world_regdom(ieee80211_regdom))
253057b5ce07SLuis R. Rodriguez 		regulatory_hint_user(ieee80211_regdom,
253157b5ce07SLuis R. Rodriguez 				     NL80211_USER_REG_HINT_USER);
2532ae9e4b0dSLuis R. Rodriguez 
2533b2e1b302SLuis R. Rodriguez 	return 0;
2534b2e1b302SLuis R. Rodriguez }
2535b2e1b302SLuis R. Rodriguez 
25361a919318SJohannes Berg void regulatory_exit(void)
2537b2e1b302SLuis R. Rodriguez {
2538fe33eb39SLuis R. Rodriguez 	struct regulatory_request *reg_request, *tmp;
2539e38f8a7aSLuis R. Rodriguez 	struct reg_beacon *reg_beacon, *btmp;
2540fe33eb39SLuis R. Rodriguez 
2541fe33eb39SLuis R. Rodriguez 	cancel_work_sync(&reg_work);
2542a90c7a31SLuis R. Rodriguez 	cancel_delayed_work_sync(&reg_timeout);
2543fe33eb39SLuis R. Rodriguez 
25449027b149SJohannes Berg 	/* Lock to suppress warnings */
254538fd2143SJohannes Berg 	rtnl_lock();
2546379b82f4SJohannes Berg 	reset_regdomains(true, NULL);
254738fd2143SJohannes Berg 	rtnl_unlock();
2548734366deSJohannes Berg 
254958ebacc6SLuis R. Rodriguez 	dev_set_uevent_suppress(&reg_pdev->dev, true);
2550f6037d09SJohannes Berg 
2551b2e1b302SLuis R. Rodriguez 	platform_device_unregister(reg_pdev);
2552734366deSJohannes Berg 
2553fea9bcedSJohannes Berg 	list_for_each_entry_safe(reg_beacon, btmp, &reg_pending_beacons, list) {
2554e38f8a7aSLuis R. Rodriguez 		list_del(&reg_beacon->list);
2555e38f8a7aSLuis R. Rodriguez 		kfree(reg_beacon);
2556e38f8a7aSLuis R. Rodriguez 	}
2557e38f8a7aSLuis R. Rodriguez 
2558fea9bcedSJohannes Berg 	list_for_each_entry_safe(reg_beacon, btmp, &reg_beacon_list, list) {
2559e38f8a7aSLuis R. Rodriguez 		list_del(&reg_beacon->list);
2560e38f8a7aSLuis R. Rodriguez 		kfree(reg_beacon);
2561e38f8a7aSLuis R. Rodriguez 	}
2562e38f8a7aSLuis R. Rodriguez 
2563fea9bcedSJohannes Berg 	list_for_each_entry_safe(reg_request, tmp, &reg_requests_list, list) {
2564fe33eb39SLuis R. Rodriguez 		list_del(&reg_request->list);
2565fe33eb39SLuis R. Rodriguez 		kfree(reg_request);
2566fe33eb39SLuis R. Rodriguez 	}
2567fe33eb39SLuis R. Rodriguez }
2568