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