xref: /openbmc/linux/net/wireless/reg.c (revision 26ec17a1)
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
8e646a025SJohannes Berg  * Copyright (C) 2018 - 2019 Intel Corporation
98318d78aSJohannes Berg  *
103b77d5ecSLuis R. Rodriguez  * Permission to use, copy, modify, and/or distribute this software for any
113b77d5ecSLuis R. Rodriguez  * purpose with or without fee is hereby granted, provided that the above
123b77d5ecSLuis R. Rodriguez  * copyright notice and this permission notice appear in all copies.
133b77d5ecSLuis R. Rodriguez  *
143b77d5ecSLuis R. Rodriguez  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
153b77d5ecSLuis R. Rodriguez  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
163b77d5ecSLuis R. Rodriguez  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
173b77d5ecSLuis R. Rodriguez  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
183b77d5ecSLuis R. Rodriguez  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
193b77d5ecSLuis R. Rodriguez  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
203b77d5ecSLuis R. Rodriguez  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
218318d78aSJohannes Berg  */
228318d78aSJohannes Berg 
233b77d5ecSLuis R. Rodriguez 
24b2e1b302SLuis R. Rodriguez /**
25b2e1b302SLuis R. Rodriguez  * DOC: Wireless regulatory infrastructure
268318d78aSJohannes Berg  *
278318d78aSJohannes Berg  * The usual implementation is for a driver to read a device EEPROM to
288318d78aSJohannes Berg  * determine which regulatory domain it should be operating under, then
298318d78aSJohannes Berg  * looking up the allowable channels in a driver-local table and finally
308318d78aSJohannes Berg  * registering those channels in the wiphy structure.
318318d78aSJohannes Berg  *
32b2e1b302SLuis R. Rodriguez  * Another set of compliance enforcement is for drivers to use their
33b2e1b302SLuis R. Rodriguez  * own compliance limits which can be stored on the EEPROM. The host
34b2e1b302SLuis R. Rodriguez  * driver or firmware may ensure these are used.
35b2e1b302SLuis R. Rodriguez  *
36b2e1b302SLuis R. Rodriguez  * In addition to all this we provide an extra layer of regulatory
37b2e1b302SLuis R. Rodriguez  * conformance. For drivers which do not have any regulatory
38b2e1b302SLuis R. Rodriguez  * information CRDA provides the complete regulatory solution.
39b2e1b302SLuis R. Rodriguez  * For others it provides a community effort on further restrictions
40b2e1b302SLuis R. Rodriguez  * to enhance compliance.
41b2e1b302SLuis R. Rodriguez  *
42b2e1b302SLuis R. Rodriguez  * Note: When number of rules --> infinity we will not be able to
43b2e1b302SLuis R. Rodriguez  * index on alpha2 any more, instead we'll probably have to
44b2e1b302SLuis R. Rodriguez  * rely on some SHA1 checksum of the regdomain for example.
45b2e1b302SLuis R. Rodriguez  *
468318d78aSJohannes Berg  */
47e9c0268fSJoe Perches 
48e9c0268fSJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
49e9c0268fSJoe Perches 
508318d78aSJohannes Berg #include <linux/kernel.h>
51bc3b2d7fSPaul Gortmaker #include <linux/export.h>
525a0e3ad6STejun Heo #include <linux/slab.h>
53b2e1b302SLuis R. Rodriguez #include <linux/list.h>
54c61029c7SJohn W. Linville #include <linux/ctype.h>
55b2e1b302SLuis R. Rodriguez #include <linux/nl80211.h>
56b2e1b302SLuis R. Rodriguez #include <linux/platform_device.h>
5790a53e44SJohannes Berg #include <linux/verification.h>
58d9b93842SPaul Gortmaker #include <linux/moduleparam.h>
59007f6c5eSJohannes Berg #include <linux/firmware.h>
60b2e1b302SLuis R. Rodriguez #include <net/cfg80211.h>
618318d78aSJohannes Berg #include "core.h"
62b2e1b302SLuis R. Rodriguez #include "reg.h"
63ad932f04SArik Nemtsov #include "rdev-ops.h"
6473d54c9eSLuis R. Rodriguez #include "nl80211.h"
658318d78aSJohannes Berg 
66ad932f04SArik Nemtsov /*
67ad932f04SArik Nemtsov  * Grace period we give before making sure all current interfaces reside on
68ad932f04SArik Nemtsov  * channels allowed by the current regulatory domain.
69ad932f04SArik Nemtsov  */
70ad932f04SArik Nemtsov #define REG_ENFORCE_GRACE_MS 60000
71ad932f04SArik Nemtsov 
7252616f2bSIlan Peer /**
7352616f2bSIlan Peer  * enum reg_request_treatment - regulatory request treatment
7452616f2bSIlan Peer  *
7552616f2bSIlan Peer  * @REG_REQ_OK: continue processing the regulatory request
7652616f2bSIlan Peer  * @REG_REQ_IGNORE: ignore the regulatory request
7752616f2bSIlan Peer  * @REG_REQ_INTERSECT: the regulatory domain resulting from this request should
7852616f2bSIlan Peer  *	be intersected with the current one.
7952616f2bSIlan Peer  * @REG_REQ_ALREADY_SET: the regulatory request will not change the current
8052616f2bSIlan Peer  *	regulatory settings, and no further processing is required.
8152616f2bSIlan Peer  */
822f92212bSJohannes Berg enum reg_request_treatment {
832f92212bSJohannes Berg 	REG_REQ_OK,
842f92212bSJohannes Berg 	REG_REQ_IGNORE,
852f92212bSJohannes Berg 	REG_REQ_INTERSECT,
862f92212bSJohannes Berg 	REG_REQ_ALREADY_SET,
872f92212bSJohannes Berg };
882f92212bSJohannes Berg 
89a042994dSLuis R. Rodriguez static struct regulatory_request core_request_world = {
90a042994dSLuis R. Rodriguez 	.initiator = NL80211_REGDOM_SET_BY_CORE,
91a042994dSLuis R. Rodriguez 	.alpha2[0] = '0',
92a042994dSLuis R. Rodriguez 	.alpha2[1] = '0',
93a042994dSLuis R. Rodriguez 	.intersect = false,
94a042994dSLuis R. Rodriguez 	.processed = true,
95a042994dSLuis R. Rodriguez 	.country_ie_env = ENVIRON_ANY,
96a042994dSLuis R. Rodriguez };
97a042994dSLuis R. Rodriguez 
9838fd2143SJohannes Berg /*
9938fd2143SJohannes Berg  * Receipt of information from last regulatory request,
10038fd2143SJohannes Berg  * protected by RTNL (and can be accessed with RCU protection)
10138fd2143SJohannes Berg  */
102c492db37SJohannes Berg static struct regulatory_request __rcu *last_request =
103cec3f0edSJohannes Berg 	(void __force __rcu *)&core_request_world;
104734366deSJohannes Berg 
105007f6c5eSJohannes Berg /* To trigger userspace events and load firmware */
106b2e1b302SLuis R. Rodriguez static struct platform_device *reg_pdev;
1078318d78aSJohannes Berg 
108fb1fc7adSLuis R. Rodriguez /*
109fb1fc7adSLuis R. Rodriguez  * Central wireless core regulatory domains, we only need two,
110734366deSJohannes Berg  * the current one and a world regulatory domain in case we have no
111e8da2bb4SJohannes Berg  * information to give us an alpha2.
11238fd2143SJohannes Berg  * (protected by RTNL, can be read under RCU)
113fb1fc7adSLuis R. Rodriguez  */
114458f4f9eSJohannes Berg const struct ieee80211_regdomain __rcu *cfg80211_regdomain;
115734366deSJohannes Berg 
116fb1fc7adSLuis R. Rodriguez /*
11757b5ce07SLuis R. Rodriguez  * Number of devices that registered to the core
11857b5ce07SLuis R. Rodriguez  * that support cellular base station regulatory hints
11938fd2143SJohannes Berg  * (protected by RTNL)
12057b5ce07SLuis R. Rodriguez  */
12157b5ce07SLuis R. Rodriguez static int reg_num_devs_support_basehint;
12257b5ce07SLuis R. Rodriguez 
12352616f2bSIlan Peer /*
12452616f2bSIlan Peer  * State variable indicating if the platform on which the devices
12552616f2bSIlan Peer  * are attached is operating in an indoor environment. The state variable
12652616f2bSIlan Peer  * is relevant for all registered devices.
12752616f2bSIlan Peer  */
12852616f2bSIlan Peer static bool reg_is_indoor;
12905050753SIlan peer static spinlock_t reg_indoor_lock;
13005050753SIlan peer 
13105050753SIlan peer /* Used to track the userspace process controlling the indoor setting */
13205050753SIlan peer static u32 reg_is_indoor_portid;
13352616f2bSIlan Peer 
134e646a025SJohannes Berg static void restore_regulatory_settings(bool reset_user, bool cached);
135e646a025SJohannes Berg static void print_regdomain(const struct ieee80211_regdomain *rd);
136c37722bdSIlan peer 
137458f4f9eSJohannes Berg static const struct ieee80211_regdomain *get_cfg80211_regdom(void)
138458f4f9eSJohannes Berg {
1395bf16a11SJohannes Berg 	return rcu_dereference_rtnl(cfg80211_regdomain);
140458f4f9eSJohannes Berg }
141458f4f9eSJohannes Berg 
142ad30ca2cSArik Nemtsov const struct ieee80211_regdomain *get_wiphy_regdom(struct wiphy *wiphy)
143458f4f9eSJohannes Berg {
1445bf16a11SJohannes Berg 	return rcu_dereference_rtnl(wiphy->regd);
145458f4f9eSJohannes Berg }
146458f4f9eSJohannes Berg 
1473ef121b5SLuis R. Rodriguez static const char *reg_dfs_region_str(enum nl80211_dfs_regions dfs_region)
1483ef121b5SLuis R. Rodriguez {
1493ef121b5SLuis R. Rodriguez 	switch (dfs_region) {
1503ef121b5SLuis R. Rodriguez 	case NL80211_DFS_UNSET:
1513ef121b5SLuis R. Rodriguez 		return "unset";
1523ef121b5SLuis R. Rodriguez 	case NL80211_DFS_FCC:
1533ef121b5SLuis R. Rodriguez 		return "FCC";
1543ef121b5SLuis R. Rodriguez 	case NL80211_DFS_ETSI:
1553ef121b5SLuis R. Rodriguez 		return "ETSI";
1563ef121b5SLuis R. Rodriguez 	case NL80211_DFS_JP:
1573ef121b5SLuis R. Rodriguez 		return "JP";
1583ef121b5SLuis R. Rodriguez 	}
1593ef121b5SLuis R. Rodriguez 	return "Unknown";
1603ef121b5SLuis R. Rodriguez }
1613ef121b5SLuis R. Rodriguez 
1626c474799SLuis R. Rodriguez enum nl80211_dfs_regions reg_get_dfs_region(struct wiphy *wiphy)
1636c474799SLuis R. Rodriguez {
1646c474799SLuis R. Rodriguez 	const struct ieee80211_regdomain *regd = NULL;
1656c474799SLuis R. Rodriguez 	const struct ieee80211_regdomain *wiphy_regd = NULL;
1666c474799SLuis R. Rodriguez 
1676c474799SLuis R. Rodriguez 	regd = get_cfg80211_regdom();
1686c474799SLuis R. Rodriguez 	if (!wiphy)
1696c474799SLuis R. Rodriguez 		goto out;
1706c474799SLuis R. Rodriguez 
1716c474799SLuis R. Rodriguez 	wiphy_regd = get_wiphy_regdom(wiphy);
1726c474799SLuis R. Rodriguez 	if (!wiphy_regd)
1736c474799SLuis R. Rodriguez 		goto out;
1746c474799SLuis R. Rodriguez 
1756c474799SLuis R. Rodriguez 	if (wiphy_regd->dfs_region == regd->dfs_region)
1766c474799SLuis R. Rodriguez 		goto out;
1776c474799SLuis R. Rodriguez 
178c799ba6eSJohannes Berg 	pr_debug("%s: device specific dfs_region (%s) disagrees with cfg80211's central dfs_region (%s)\n",
1796c474799SLuis R. Rodriguez 		 dev_name(&wiphy->dev),
1806c474799SLuis R. Rodriguez 		 reg_dfs_region_str(wiphy_regd->dfs_region),
1816c474799SLuis R. Rodriguez 		 reg_dfs_region_str(regd->dfs_region));
1826c474799SLuis R. Rodriguez 
1836c474799SLuis R. Rodriguez out:
1846c474799SLuis R. Rodriguez 	return regd->dfs_region;
1856c474799SLuis R. Rodriguez }
1866c474799SLuis R. Rodriguez 
187458f4f9eSJohannes Berg static void rcu_free_regdom(const struct ieee80211_regdomain *r)
188458f4f9eSJohannes Berg {
189458f4f9eSJohannes Berg 	if (!r)
190458f4f9eSJohannes Berg 		return;
191458f4f9eSJohannes Berg 	kfree_rcu((struct ieee80211_regdomain *)r, rcu_head);
192458f4f9eSJohannes Berg }
193458f4f9eSJohannes Berg 
194c492db37SJohannes Berg static struct regulatory_request *get_last_request(void)
195c492db37SJohannes Berg {
19638fd2143SJohannes Berg 	return rcu_dereference_rtnl(last_request);
197c492db37SJohannes Berg }
198c492db37SJohannes Berg 
199e38f8a7aSLuis R. Rodriguez /* Used to queue up regulatory hints */
200fe33eb39SLuis R. Rodriguez static LIST_HEAD(reg_requests_list);
201fe33eb39SLuis R. Rodriguez static spinlock_t reg_requests_lock;
202fe33eb39SLuis R. Rodriguez 
203e38f8a7aSLuis R. Rodriguez /* Used to queue up beacon hints for review */
204e38f8a7aSLuis R. Rodriguez static LIST_HEAD(reg_pending_beacons);
205e38f8a7aSLuis R. Rodriguez static spinlock_t reg_pending_beacons_lock;
206e38f8a7aSLuis R. Rodriguez 
207e38f8a7aSLuis R. Rodriguez /* Used to keep track of processed beacon hints */
208e38f8a7aSLuis R. Rodriguez static LIST_HEAD(reg_beacon_list);
209e38f8a7aSLuis R. Rodriguez 
210e38f8a7aSLuis R. Rodriguez struct reg_beacon {
211e38f8a7aSLuis R. Rodriguez 	struct list_head list;
212e38f8a7aSLuis R. Rodriguez 	struct ieee80211_channel chan;
213e38f8a7aSLuis R. Rodriguez };
214e38f8a7aSLuis R. Rodriguez 
215ad932f04SArik Nemtsov static void reg_check_chans_work(struct work_struct *work);
216ad932f04SArik Nemtsov static DECLARE_DELAYED_WORK(reg_check_chans, reg_check_chans_work);
217ad932f04SArik Nemtsov 
218f333a7a2SLuis R. Rodriguez static void reg_todo(struct work_struct *work);
219f333a7a2SLuis R. Rodriguez static DECLARE_WORK(reg_work, reg_todo);
220f333a7a2SLuis R. Rodriguez 
221734366deSJohannes Berg /* We keep a static world regulatory domain in case of the absence of CRDA */
222734366deSJohannes Berg static const struct ieee80211_regdomain world_regdom = {
22328981e5eSJason Abele 	.n_reg_rules = 8,
224734366deSJohannes Berg 	.alpha2 =  "00",
225734366deSJohannes Berg 	.reg_rules = {
22668798a62SLuis R. Rodriguez 		/* IEEE 802.11b/g, channels 1..11 */
22768798a62SLuis R. Rodriguez 		REG_RULE(2412-10, 2462+10, 40, 6, 20, 0),
22843c771a1SJohannes Berg 		/* IEEE 802.11b/g, channels 12..13. */
229c3826807SJohannes Berg 		REG_RULE(2467-10, 2472+10, 20, 6, 20,
230c3826807SJohannes Berg 			NL80211_RRF_NO_IR | NL80211_RRF_AUTO_BW),
231611b6a82SLuis R. Rodriguez 		/* IEEE 802.11 channel 14 - Only JP enables
232611b6a82SLuis R. Rodriguez 		 * this and for 802.11b only */
233611b6a82SLuis R. Rodriguez 		REG_RULE(2484-10, 2484+10, 20, 6, 20,
2348fe02e16SLuis R. Rodriguez 			NL80211_RRF_NO_IR |
235611b6a82SLuis R. Rodriguez 			NL80211_RRF_NO_OFDM),
2363fc71f77SLuis R. Rodriguez 		/* IEEE 802.11a, channel 36..48 */
237c3826807SJohannes Berg 		REG_RULE(5180-10, 5240+10, 80, 6, 20,
238c3826807SJohannes Berg                         NL80211_RRF_NO_IR |
239c3826807SJohannes Berg                         NL80211_RRF_AUTO_BW),
2403fc71f77SLuis R. Rodriguez 
241131a19bcSJohannes Berg 		/* IEEE 802.11a, channel 52..64 - DFS required */
242c3826807SJohannes Berg 		REG_RULE(5260-10, 5320+10, 80, 6, 20,
2438fe02e16SLuis R. Rodriguez 			NL80211_RRF_NO_IR |
244c3826807SJohannes Berg 			NL80211_RRF_AUTO_BW |
245131a19bcSJohannes Berg 			NL80211_RRF_DFS),
246131a19bcSJohannes Berg 
247131a19bcSJohannes Berg 		/* IEEE 802.11a, channel 100..144 - DFS required */
248131a19bcSJohannes Berg 		REG_RULE(5500-10, 5720+10, 160, 6, 20,
2498fe02e16SLuis R. Rodriguez 			NL80211_RRF_NO_IR |
250131a19bcSJohannes Berg 			NL80211_RRF_DFS),
2513fc71f77SLuis R. Rodriguez 
2523fc71f77SLuis R. Rodriguez 		/* IEEE 802.11a, channel 149..165 */
2538ab9d85cSJohannes Berg 		REG_RULE(5745-10, 5825+10, 80, 6, 20,
2548fe02e16SLuis R. Rodriguez 			NL80211_RRF_NO_IR),
25590cdc6dfSVladimir Kondratiev 
2568047d261SJohannes Berg 		/* IEEE 802.11ad (60GHz), channels 1..3 */
25790cdc6dfSVladimir Kondratiev 		REG_RULE(56160+2160*1-1080, 56160+2160*3+1080, 2160, 0, 0, 0),
258734366deSJohannes Berg 	}
259734366deSJohannes Berg };
260734366deSJohannes Berg 
26138fd2143SJohannes Berg /* protected by RTNL */
262a3d2eaf0SJohannes Berg static const struct ieee80211_regdomain *cfg80211_world_regdom =
263a3d2eaf0SJohannes Berg 	&world_regdom;
264734366deSJohannes Berg 
2656ee7d330SLuis R. Rodriguez static char *ieee80211_regdom = "00";
26609d989d1SLuis R. Rodriguez static char user_alpha2[2];
267e646a025SJohannes Berg static const struct ieee80211_regdomain *cfg80211_user_regdom;
2686ee7d330SLuis R. Rodriguez 
269734366deSJohannes Berg module_param(ieee80211_regdom, charp, 0444);
270734366deSJohannes Berg MODULE_PARM_DESC(ieee80211_regdom, "IEEE 802.11 regulatory domain code");
271734366deSJohannes Berg 
272c888393bSArik Nemtsov static void reg_free_request(struct regulatory_request *request)
2735ad6ef5eSLuis R. Rodriguez {
274d34265a3SJohannes Berg 	if (request == &core_request_world)
275d34265a3SJohannes Berg 		return;
276d34265a3SJohannes Berg 
277c888393bSArik Nemtsov 	if (request != get_last_request())
278c888393bSArik Nemtsov 		kfree(request);
279c888393bSArik Nemtsov }
280c888393bSArik Nemtsov 
281c888393bSArik Nemtsov static void reg_free_last_request(void)
282c888393bSArik Nemtsov {
283c888393bSArik Nemtsov 	struct regulatory_request *lr = get_last_request();
284c888393bSArik Nemtsov 
2855ad6ef5eSLuis R. Rodriguez 	if (lr != &core_request_world && lr)
2865ad6ef5eSLuis R. Rodriguez 		kfree_rcu(lr, rcu_head);
2875ad6ef5eSLuis R. Rodriguez }
2885ad6ef5eSLuis R. Rodriguez 
28905f1a3eaSLuis R. Rodriguez static void reg_update_last_request(struct regulatory_request *request)
29005f1a3eaSLuis R. Rodriguez {
291255e25b0SLuis R. Rodriguez 	struct regulatory_request *lr;
292255e25b0SLuis R. Rodriguez 
293255e25b0SLuis R. Rodriguez 	lr = get_last_request();
294255e25b0SLuis R. Rodriguez 	if (lr == request)
295255e25b0SLuis R. Rodriguez 		return;
296255e25b0SLuis R. Rodriguez 
297c888393bSArik Nemtsov 	reg_free_last_request();
29805f1a3eaSLuis R. Rodriguez 	rcu_assign_pointer(last_request, request);
29905f1a3eaSLuis R. Rodriguez }
30005f1a3eaSLuis R. Rodriguez 
301379b82f4SJohannes Berg static void reset_regdomains(bool full_reset,
302379b82f4SJohannes Berg 			     const struct ieee80211_regdomain *new_regdom)
303734366deSJohannes Berg {
304458f4f9eSJohannes Berg 	const struct ieee80211_regdomain *r;
305458f4f9eSJohannes Berg 
30638fd2143SJohannes Berg 	ASSERT_RTNL();
307e8da2bb4SJohannes Berg 
308458f4f9eSJohannes Berg 	r = get_cfg80211_regdom();
309458f4f9eSJohannes Berg 
310942b25cfSJohannes Berg 	/* avoid freeing static information or freeing something twice */
311458f4f9eSJohannes Berg 	if (r == cfg80211_world_regdom)
312458f4f9eSJohannes Berg 		r = NULL;
313942b25cfSJohannes Berg 	if (cfg80211_world_regdom == &world_regdom)
314942b25cfSJohannes Berg 		cfg80211_world_regdom = NULL;
315458f4f9eSJohannes Berg 	if (r == &world_regdom)
316458f4f9eSJohannes Berg 		r = NULL;
317942b25cfSJohannes Berg 
318458f4f9eSJohannes Berg 	rcu_free_regdom(r);
319458f4f9eSJohannes Berg 	rcu_free_regdom(cfg80211_world_regdom);
320734366deSJohannes Berg 
321a3d2eaf0SJohannes Berg 	cfg80211_world_regdom = &world_regdom;
322458f4f9eSJohannes Berg 	rcu_assign_pointer(cfg80211_regdomain, new_regdom);
323a042994dSLuis R. Rodriguez 
324a042994dSLuis R. Rodriguez 	if (!full_reset)
325a042994dSLuis R. Rodriguez 		return;
326a042994dSLuis R. Rodriguez 
32705f1a3eaSLuis R. Rodriguez 	reg_update_last_request(&core_request_world);
328734366deSJohannes Berg }
329734366deSJohannes Berg 
330fb1fc7adSLuis R. Rodriguez /*
331fb1fc7adSLuis R. Rodriguez  * Dynamic world regulatory domain requested by the wireless
332fb1fc7adSLuis R. Rodriguez  * core upon initialization
333fb1fc7adSLuis R. Rodriguez  */
334a3d2eaf0SJohannes Berg static void update_world_regdomain(const struct ieee80211_regdomain *rd)
335734366deSJohannes Berg {
336c492db37SJohannes Berg 	struct regulatory_request *lr;
337734366deSJohannes Berg 
338c492db37SJohannes Berg 	lr = get_last_request();
339c492db37SJohannes Berg 
340c492db37SJohannes Berg 	WARN_ON(!lr);
341e8da2bb4SJohannes Berg 
342379b82f4SJohannes Berg 	reset_regdomains(false, rd);
343734366deSJohannes Berg 
344734366deSJohannes Berg 	cfg80211_world_regdom = rd;
345734366deSJohannes Berg }
346734366deSJohannes Berg 
347a3d2eaf0SJohannes Berg bool is_world_regdom(const char *alpha2)
348b2e1b302SLuis R. Rodriguez {
349b2e1b302SLuis R. Rodriguez 	if (!alpha2)
350b2e1b302SLuis R. Rodriguez 		return false;
3511a919318SJohannes Berg 	return alpha2[0] == '0' && alpha2[1] == '0';
352b2e1b302SLuis R. Rodriguez }
353b2e1b302SLuis R. Rodriguez 
354a3d2eaf0SJohannes Berg static bool is_alpha2_set(const char *alpha2)
355b2e1b302SLuis R. Rodriguez {
356b2e1b302SLuis R. Rodriguez 	if (!alpha2)
357b2e1b302SLuis R. Rodriguez 		return false;
3581a919318SJohannes Berg 	return alpha2[0] && alpha2[1];
359b2e1b302SLuis R. Rodriguez }
360b2e1b302SLuis R. Rodriguez 
361a3d2eaf0SJohannes Berg static bool is_unknown_alpha2(const char *alpha2)
362b2e1b302SLuis R. Rodriguez {
363b2e1b302SLuis R. Rodriguez 	if (!alpha2)
364b2e1b302SLuis R. Rodriguez 		return false;
365fb1fc7adSLuis R. Rodriguez 	/*
366fb1fc7adSLuis R. Rodriguez 	 * Special case where regulatory domain was built by driver
367fb1fc7adSLuis R. Rodriguez 	 * but a specific alpha2 cannot be determined
368fb1fc7adSLuis R. Rodriguez 	 */
3691a919318SJohannes Berg 	return alpha2[0] == '9' && alpha2[1] == '9';
370b2e1b302SLuis R. Rodriguez }
371b2e1b302SLuis R. Rodriguez 
3723f2355cbSLuis R. Rodriguez static bool is_intersected_alpha2(const char *alpha2)
3733f2355cbSLuis R. Rodriguez {
3743f2355cbSLuis R. Rodriguez 	if (!alpha2)
3753f2355cbSLuis R. Rodriguez 		return false;
376fb1fc7adSLuis R. Rodriguez 	/*
377fb1fc7adSLuis R. Rodriguez 	 * Special case where regulatory domain is the
3783f2355cbSLuis R. Rodriguez 	 * result of an intersection between two regulatory domain
379fb1fc7adSLuis R. Rodriguez 	 * structures
380fb1fc7adSLuis R. Rodriguez 	 */
3811a919318SJohannes Berg 	return alpha2[0] == '9' && alpha2[1] == '8';
3823f2355cbSLuis R. Rodriguez }
3833f2355cbSLuis R. Rodriguez 
384a3d2eaf0SJohannes Berg static bool is_an_alpha2(const char *alpha2)
385b2e1b302SLuis R. Rodriguez {
386b2e1b302SLuis R. Rodriguez 	if (!alpha2)
387b2e1b302SLuis R. Rodriguez 		return false;
3881a919318SJohannes Berg 	return isalpha(alpha2[0]) && isalpha(alpha2[1]);
389b2e1b302SLuis R. Rodriguez }
390b2e1b302SLuis R. Rodriguez 
391a3d2eaf0SJohannes Berg static bool alpha2_equal(const char *alpha2_x, const char *alpha2_y)
392b2e1b302SLuis R. Rodriguez {
393b2e1b302SLuis R. Rodriguez 	if (!alpha2_x || !alpha2_y)
394b2e1b302SLuis R. Rodriguez 		return false;
3951a919318SJohannes Berg 	return alpha2_x[0] == alpha2_y[0] && alpha2_x[1] == alpha2_y[1];
396b2e1b302SLuis R. Rodriguez }
397b2e1b302SLuis R. Rodriguez 
39869b1572bSLuis R. Rodriguez static bool regdom_changes(const char *alpha2)
399b2e1b302SLuis R. Rodriguez {
400458f4f9eSJohannes Berg 	const struct ieee80211_regdomain *r = get_cfg80211_regdom();
401761cf7ecSLuis R. Rodriguez 
402458f4f9eSJohannes Berg 	if (!r)
403b2e1b302SLuis R. Rodriguez 		return true;
404458f4f9eSJohannes Berg 	return !alpha2_equal(r->alpha2, alpha2);
405b2e1b302SLuis R. Rodriguez }
406b2e1b302SLuis R. Rodriguez 
40709d989d1SLuis R. Rodriguez /*
40809d989d1SLuis R. Rodriguez  * The NL80211_REGDOM_SET_BY_USER regdom alpha2 is cached, this lets
40909d989d1SLuis R. Rodriguez  * you know if a valid regulatory hint with NL80211_REGDOM_SET_BY_USER
41009d989d1SLuis R. Rodriguez  * has ever been issued.
41109d989d1SLuis R. Rodriguez  */
41209d989d1SLuis R. Rodriguez static bool is_user_regdom_saved(void)
41309d989d1SLuis R. Rodriguez {
41409d989d1SLuis R. Rodriguez 	if (user_alpha2[0] == '9' && user_alpha2[1] == '7')
41509d989d1SLuis R. Rodriguez 		return false;
41609d989d1SLuis R. Rodriguez 
41709d989d1SLuis R. Rodriguez 	/* This would indicate a mistake on the design */
4181a919318SJohannes Berg 	if (WARN(!is_world_regdom(user_alpha2) && !is_an_alpha2(user_alpha2),
41909d989d1SLuis R. Rodriguez 		 "Unexpected user alpha2: %c%c\n",
4201a919318SJohannes Berg 		 user_alpha2[0], user_alpha2[1]))
42109d989d1SLuis R. Rodriguez 		return false;
42209d989d1SLuis R. Rodriguez 
42309d989d1SLuis R. Rodriguez 	return true;
42409d989d1SLuis R. Rodriguez }
42509d989d1SLuis R. Rodriguez 
426e9763c3cSJohannes Berg static const struct ieee80211_regdomain *
427e9763c3cSJohannes Berg reg_copy_regd(const struct ieee80211_regdomain *src_regd)
4283b377ea9SJohn W. Linville {
4293b377ea9SJohn W. Linville 	struct ieee80211_regdomain *regd;
4303b377ea9SJohn W. Linville 	unsigned int i;
4313b377ea9SJohn W. Linville 
4329f8c7136SGustavo A. R. Silva 	regd = kzalloc(struct_size(regd, reg_rules, src_regd->n_reg_rules),
4339f8c7136SGustavo A. R. Silva 		       GFP_KERNEL);
4343b377ea9SJohn W. Linville 	if (!regd)
435e9763c3cSJohannes Berg 		return ERR_PTR(-ENOMEM);
4363b377ea9SJohn W. Linville 
4373b377ea9SJohn W. Linville 	memcpy(regd, src_regd, sizeof(struct ieee80211_regdomain));
4383b377ea9SJohn W. Linville 
43938cb87eeSStanislaw Gruszka 	for (i = 0; i < src_regd->n_reg_rules; i++)
4403b377ea9SJohn W. Linville 		memcpy(&regd->reg_rules[i], &src_regd->reg_rules[i],
4413b377ea9SJohn W. Linville 		       sizeof(struct ieee80211_reg_rule));
4423b377ea9SJohn W. Linville 
443e9763c3cSJohannes Berg 	return regd;
4443b377ea9SJohn W. Linville }
4453b377ea9SJohn W. Linville 
446e646a025SJohannes Berg static void cfg80211_save_user_regdom(const struct ieee80211_regdomain *rd)
447e646a025SJohannes Berg {
448e646a025SJohannes Berg 	ASSERT_RTNL();
449e646a025SJohannes Berg 
450e646a025SJohannes Berg 	if (!IS_ERR(cfg80211_user_regdom))
451e646a025SJohannes Berg 		kfree(cfg80211_user_regdom);
452e646a025SJohannes Berg 	cfg80211_user_regdom = reg_copy_regd(rd);
453e646a025SJohannes Berg }
454e646a025SJohannes Berg 
455c7d319e5SJohannes Berg struct reg_regdb_apply_request {
4563b377ea9SJohn W. Linville 	struct list_head list;
457c7d319e5SJohannes Berg 	const struct ieee80211_regdomain *regdom;
4583b377ea9SJohn W. Linville };
4593b377ea9SJohn W. Linville 
460c7d319e5SJohannes Berg static LIST_HEAD(reg_regdb_apply_list);
461c7d319e5SJohannes Berg static DEFINE_MUTEX(reg_regdb_apply_mutex);
4623b377ea9SJohn W. Linville 
463c7d319e5SJohannes Berg static void reg_regdb_apply(struct work_struct *work)
4643b377ea9SJohn W. Linville {
465c7d319e5SJohannes Berg 	struct reg_regdb_apply_request *request;
466a85d0d7fSLuis R. Rodriguez 
4675fe231e8SJohannes Berg 	rtnl_lock();
4683b377ea9SJohn W. Linville 
469c7d319e5SJohannes Berg 	mutex_lock(&reg_regdb_apply_mutex);
470c7d319e5SJohannes Berg 	while (!list_empty(&reg_regdb_apply_list)) {
471c7d319e5SJohannes Berg 		request = list_first_entry(&reg_regdb_apply_list,
472c7d319e5SJohannes Berg 					   struct reg_regdb_apply_request,
4733b377ea9SJohn W. Linville 					   list);
4743b377ea9SJohn W. Linville 		list_del(&request->list);
4753b377ea9SJohn W. Linville 
476c7d319e5SJohannes Berg 		set_regdom(request->regdom, REGD_SOURCE_INTERNAL_DB);
4773b377ea9SJohn W. Linville 		kfree(request);
4783b377ea9SJohn W. Linville 	}
479c7d319e5SJohannes Berg 	mutex_unlock(&reg_regdb_apply_mutex);
480a85d0d7fSLuis R. Rodriguez 
4815fe231e8SJohannes Berg 	rtnl_unlock();
4823b377ea9SJohn W. Linville }
4833b377ea9SJohn W. Linville 
484c7d319e5SJohannes Berg static DECLARE_WORK(reg_regdb_work, reg_regdb_apply);
4853b377ea9SJohn W. Linville 
486007f6c5eSJohannes Berg static int reg_schedule_apply(const struct ieee80211_regdomain *regdom)
4873b377ea9SJohn W. Linville {
488c7d319e5SJohannes Berg 	struct reg_regdb_apply_request *request;
489c7d319e5SJohannes Berg 
490c7d319e5SJohannes Berg 	request = kzalloc(sizeof(struct reg_regdb_apply_request), GFP_KERNEL);
491007f6c5eSJohannes Berg 	if (!request) {
492007f6c5eSJohannes Berg 		kfree(regdom);
493c7d319e5SJohannes Berg 		return -ENOMEM;
494c7d319e5SJohannes Berg 	}
4953b377ea9SJohn W. Linville 
496007f6c5eSJohannes Berg 	request->regdom = regdom;
497007f6c5eSJohannes Berg 
498c7d319e5SJohannes Berg 	mutex_lock(&reg_regdb_apply_mutex);
499c7d319e5SJohannes Berg 	list_add_tail(&request->list, &reg_regdb_apply_list);
500c7d319e5SJohannes Berg 	mutex_unlock(&reg_regdb_apply_mutex);
5013b377ea9SJohn W. Linville 
5023b377ea9SJohn W. Linville 	schedule_work(&reg_regdb_work);
503c7d319e5SJohannes Berg 	return 0;
5043b377ea9SJohn W. Linville }
50580007efeSLuis R. Rodriguez 
506b6863036SJohannes Berg #ifdef CONFIG_CFG80211_CRDA_SUPPORT
507b6863036SJohannes Berg /* Max number of consecutive attempts to communicate with CRDA  */
508b6863036SJohannes Berg #define REG_MAX_CRDA_TIMEOUTS 10
509b6863036SJohannes Berg 
510b6863036SJohannes Berg static u32 reg_crda_timeouts;
511b6863036SJohannes Berg 
512b6863036SJohannes Berg static void crda_timeout_work(struct work_struct *work);
513b6863036SJohannes Berg static DECLARE_DELAYED_WORK(crda_timeout, crda_timeout_work);
514b6863036SJohannes Berg 
515b6863036SJohannes Berg static void crda_timeout_work(struct work_struct *work)
516b6863036SJohannes Berg {
517c799ba6eSJohannes Berg 	pr_debug("Timeout while waiting for CRDA to reply, restoring regulatory settings\n");
518b6863036SJohannes Berg 	rtnl_lock();
519b6863036SJohannes Berg 	reg_crda_timeouts++;
520e646a025SJohannes Berg 	restore_regulatory_settings(true, false);
521b6863036SJohannes Berg 	rtnl_unlock();
522b6863036SJohannes Berg }
523b6863036SJohannes Berg 
524b6863036SJohannes Berg static void cancel_crda_timeout(void)
525b6863036SJohannes Berg {
526b6863036SJohannes Berg 	cancel_delayed_work(&crda_timeout);
527b6863036SJohannes Berg }
528b6863036SJohannes Berg 
529b6863036SJohannes Berg static void cancel_crda_timeout_sync(void)
530b6863036SJohannes Berg {
531b6863036SJohannes Berg 	cancel_delayed_work_sync(&crda_timeout);
532b6863036SJohannes Berg }
533b6863036SJohannes Berg 
534b6863036SJohannes Berg static void reset_crda_timeouts(void)
535b6863036SJohannes Berg {
536b6863036SJohannes Berg 	reg_crda_timeouts = 0;
537b6863036SJohannes Berg }
538b6863036SJohannes Berg 
539fb1fc7adSLuis R. Rodriguez /*
540fb1fc7adSLuis R. Rodriguez  * This lets us keep regulatory code which is updated on a regulatory
5411226d258SJohannes Berg  * basis in userspace.
542fb1fc7adSLuis R. Rodriguez  */
543b2e1b302SLuis R. Rodriguez static int call_crda(const char *alpha2)
544b2e1b302SLuis R. Rodriguez {
5451226d258SJohannes Berg 	char country[12];
5461226d258SJohannes Berg 	char *env[] = { country, NULL };
547c7d319e5SJohannes Berg 	int ret;
5481226d258SJohannes Berg 
5491226d258SJohannes Berg 	snprintf(country, sizeof(country), "COUNTRY=%c%c",
5501226d258SJohannes Berg 		 alpha2[0], alpha2[1]);
5511226d258SJohannes Berg 
552c37722bdSIlan peer 	if (reg_crda_timeouts > REG_MAX_CRDA_TIMEOUTS) {
553042ab5fcSThomas Petazzoni 		pr_debug("Exceeded CRDA call max attempts. Not calling CRDA\n");
554c37722bdSIlan peer 		return -EINVAL;
555c37722bdSIlan peer 	}
556c37722bdSIlan peer 
557b2e1b302SLuis R. Rodriguez 	if (!is_world_regdom((char *) alpha2))
558042ab5fcSThomas Petazzoni 		pr_debug("Calling CRDA for country: %c%c\n",
559b2e1b302SLuis R. Rodriguez 			 alpha2[0], alpha2[1]);
560b2e1b302SLuis R. Rodriguez 	else
561042ab5fcSThomas Petazzoni 		pr_debug("Calling CRDA to update world regulatory domain\n");
5628318d78aSJohannes Berg 
563c7d319e5SJohannes Berg 	ret = kobject_uevent_env(&reg_pdev->dev.kobj, KOBJ_CHANGE, env);
564c7d319e5SJohannes Berg 	if (ret)
565c7d319e5SJohannes Berg 		return ret;
566c7d319e5SJohannes Berg 
567c7d319e5SJohannes Berg 	queue_delayed_work(system_power_efficient_wq,
568b6863036SJohannes Berg 			   &crda_timeout, msecs_to_jiffies(3142));
569c7d319e5SJohannes Berg 	return 0;
570b2e1b302SLuis R. Rodriguez }
571b6863036SJohannes Berg #else
572b6863036SJohannes Berg static inline void cancel_crda_timeout(void) {}
573b6863036SJohannes Berg static inline void cancel_crda_timeout_sync(void) {}
574b6863036SJohannes Berg static inline void reset_crda_timeouts(void) {}
575b6863036SJohannes Berg static inline int call_crda(const char *alpha2)
576b6863036SJohannes Berg {
577b6863036SJohannes Berg 	return -ENODATA;
578b6863036SJohannes Berg }
579b6863036SJohannes Berg #endif /* CONFIG_CFG80211_CRDA_SUPPORT */
580b2e1b302SLuis R. Rodriguez 
581007f6c5eSJohannes Berg /* code to directly load a firmware database through request_firmware */
582007f6c5eSJohannes Berg static const struct fwdb_header *regdb;
583007f6c5eSJohannes Berg 
584007f6c5eSJohannes Berg struct fwdb_country {
585007f6c5eSJohannes Berg 	u8 alpha2[2];
586007f6c5eSJohannes Berg 	__be16 coll_ptr;
587007f6c5eSJohannes Berg 	/* this struct cannot be extended */
588007f6c5eSJohannes Berg } __packed __aligned(4);
589007f6c5eSJohannes Berg 
590007f6c5eSJohannes Berg struct fwdb_collection {
591007f6c5eSJohannes Berg 	u8 len;
592007f6c5eSJohannes Berg 	u8 n_rules;
593007f6c5eSJohannes Berg 	u8 dfs_region;
594007f6c5eSJohannes Berg 	/* no optional data yet */
595007f6c5eSJohannes Berg 	/* aligned to 2, then followed by __be16 array of rule pointers */
596007f6c5eSJohannes Berg } __packed __aligned(4);
597007f6c5eSJohannes Berg 
598007f6c5eSJohannes Berg enum fwdb_flags {
599007f6c5eSJohannes Berg 	FWDB_FLAG_NO_OFDM	= BIT(0),
600007f6c5eSJohannes Berg 	FWDB_FLAG_NO_OUTDOOR	= BIT(1),
601007f6c5eSJohannes Berg 	FWDB_FLAG_DFS		= BIT(2),
602007f6c5eSJohannes Berg 	FWDB_FLAG_NO_IR		= BIT(3),
603007f6c5eSJohannes Berg 	FWDB_FLAG_AUTO_BW	= BIT(4),
604007f6c5eSJohannes Berg };
605007f6c5eSJohannes Berg 
606230ebaa1SHaim Dreyfuss struct fwdb_wmm_ac {
607230ebaa1SHaim Dreyfuss 	u8 ecw;
608230ebaa1SHaim Dreyfuss 	u8 aifsn;
609230ebaa1SHaim Dreyfuss 	__be16 cot;
610230ebaa1SHaim Dreyfuss } __packed;
611230ebaa1SHaim Dreyfuss 
612230ebaa1SHaim Dreyfuss struct fwdb_wmm_rule {
613230ebaa1SHaim Dreyfuss 	struct fwdb_wmm_ac client[IEEE80211_NUM_ACS];
614230ebaa1SHaim Dreyfuss 	struct fwdb_wmm_ac ap[IEEE80211_NUM_ACS];
615230ebaa1SHaim Dreyfuss } __packed;
616230ebaa1SHaim Dreyfuss 
617007f6c5eSJohannes Berg struct fwdb_rule {
618007f6c5eSJohannes Berg 	u8 len;
619007f6c5eSJohannes Berg 	u8 flags;
620007f6c5eSJohannes Berg 	__be16 max_eirp;
621007f6c5eSJohannes Berg 	__be32 start, end, max_bw;
622007f6c5eSJohannes Berg 	/* start of optional data */
623007f6c5eSJohannes Berg 	__be16 cac_timeout;
624230ebaa1SHaim Dreyfuss 	__be16 wmm_ptr;
625007f6c5eSJohannes Berg } __packed __aligned(4);
626007f6c5eSJohannes Berg 
627007f6c5eSJohannes Berg #define FWDB_MAGIC 0x52474442
628007f6c5eSJohannes Berg #define FWDB_VERSION 20
629007f6c5eSJohannes Berg 
630007f6c5eSJohannes Berg struct fwdb_header {
631007f6c5eSJohannes Berg 	__be32 magic;
632007f6c5eSJohannes Berg 	__be32 version;
633007f6c5eSJohannes Berg 	struct fwdb_country country[];
634007f6c5eSJohannes Berg } __packed __aligned(4);
635007f6c5eSJohannes Berg 
636230ebaa1SHaim Dreyfuss static int ecw2cw(int ecw)
637230ebaa1SHaim Dreyfuss {
638230ebaa1SHaim Dreyfuss 	return (1 << ecw) - 1;
639230ebaa1SHaim Dreyfuss }
640230ebaa1SHaim Dreyfuss 
641230ebaa1SHaim Dreyfuss static bool valid_wmm(struct fwdb_wmm_rule *rule)
642230ebaa1SHaim Dreyfuss {
643230ebaa1SHaim Dreyfuss 	struct fwdb_wmm_ac *ac = (struct fwdb_wmm_ac *)rule;
644230ebaa1SHaim Dreyfuss 	int i;
645230ebaa1SHaim Dreyfuss 
646230ebaa1SHaim Dreyfuss 	for (i = 0; i < IEEE80211_NUM_ACS * 2; i++) {
647230ebaa1SHaim Dreyfuss 		u16 cw_min = ecw2cw((ac[i].ecw & 0xf0) >> 4);
648230ebaa1SHaim Dreyfuss 		u16 cw_max = ecw2cw(ac[i].ecw & 0x0f);
649230ebaa1SHaim Dreyfuss 		u8 aifsn = ac[i].aifsn;
650230ebaa1SHaim Dreyfuss 
651230ebaa1SHaim Dreyfuss 		if (cw_min >= cw_max)
652230ebaa1SHaim Dreyfuss 			return false;
653230ebaa1SHaim Dreyfuss 
654230ebaa1SHaim Dreyfuss 		if (aifsn < 1)
655230ebaa1SHaim Dreyfuss 			return false;
656230ebaa1SHaim Dreyfuss 	}
657230ebaa1SHaim Dreyfuss 
658230ebaa1SHaim Dreyfuss 	return true;
659230ebaa1SHaim Dreyfuss }
660230ebaa1SHaim Dreyfuss 
661007f6c5eSJohannes Berg static bool valid_rule(const u8 *data, unsigned int size, u16 rule_ptr)
662007f6c5eSJohannes Berg {
663007f6c5eSJohannes Berg 	struct fwdb_rule *rule = (void *)(data + (rule_ptr << 2));
664007f6c5eSJohannes Berg 
665007f6c5eSJohannes Berg 	if ((u8 *)rule + sizeof(rule->len) > data + size)
666007f6c5eSJohannes Berg 		return false;
667007f6c5eSJohannes Berg 
668007f6c5eSJohannes Berg 	/* mandatory fields */
669007f6c5eSJohannes Berg 	if (rule->len < offsetofend(struct fwdb_rule, max_bw))
670007f6c5eSJohannes Berg 		return false;
671230ebaa1SHaim Dreyfuss 	if (rule->len >= offsetofend(struct fwdb_rule, wmm_ptr)) {
672230ebaa1SHaim Dreyfuss 		u32 wmm_ptr = be16_to_cpu(rule->wmm_ptr) << 2;
673230ebaa1SHaim Dreyfuss 		struct fwdb_wmm_rule *wmm;
674007f6c5eSJohannes Berg 
675230ebaa1SHaim Dreyfuss 		if (wmm_ptr + sizeof(struct fwdb_wmm_rule) > size)
676230ebaa1SHaim Dreyfuss 			return false;
677230ebaa1SHaim Dreyfuss 
678230ebaa1SHaim Dreyfuss 		wmm = (void *)(data + wmm_ptr);
679230ebaa1SHaim Dreyfuss 
680230ebaa1SHaim Dreyfuss 		if (!valid_wmm(wmm))
681230ebaa1SHaim Dreyfuss 			return false;
682230ebaa1SHaim Dreyfuss 	}
683007f6c5eSJohannes Berg 	return true;
684007f6c5eSJohannes Berg }
685007f6c5eSJohannes Berg 
686007f6c5eSJohannes Berg static bool valid_country(const u8 *data, unsigned int size,
687007f6c5eSJohannes Berg 			  const struct fwdb_country *country)
688007f6c5eSJohannes Berg {
689007f6c5eSJohannes Berg 	unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2;
690007f6c5eSJohannes Berg 	struct fwdb_collection *coll = (void *)(data + ptr);
691007f6c5eSJohannes Berg 	__be16 *rules_ptr;
692007f6c5eSJohannes Berg 	unsigned int i;
693007f6c5eSJohannes Berg 
694007f6c5eSJohannes Berg 	/* make sure we can read len/n_rules */
695007f6c5eSJohannes Berg 	if ((u8 *)coll + offsetofend(typeof(*coll), n_rules) > data + size)
696007f6c5eSJohannes Berg 		return false;
697007f6c5eSJohannes Berg 
698007f6c5eSJohannes Berg 	/* make sure base struct and all rules fit */
699007f6c5eSJohannes Berg 	if ((u8 *)coll + ALIGN(coll->len, 2) +
700007f6c5eSJohannes Berg 	    (coll->n_rules * 2) > data + size)
701007f6c5eSJohannes Berg 		return false;
702007f6c5eSJohannes Berg 
703007f6c5eSJohannes Berg 	/* mandatory fields must exist */
704007f6c5eSJohannes Berg 	if (coll->len < offsetofend(struct fwdb_collection, dfs_region))
705007f6c5eSJohannes Berg 		return false;
706007f6c5eSJohannes Berg 
707007f6c5eSJohannes Berg 	rules_ptr = (void *)((u8 *)coll + ALIGN(coll->len, 2));
708007f6c5eSJohannes Berg 
709007f6c5eSJohannes Berg 	for (i = 0; i < coll->n_rules; i++) {
710007f6c5eSJohannes Berg 		u16 rule_ptr = be16_to_cpu(rules_ptr[i]);
711007f6c5eSJohannes Berg 
712007f6c5eSJohannes Berg 		if (!valid_rule(data, size, rule_ptr))
713007f6c5eSJohannes Berg 			return false;
714007f6c5eSJohannes Berg 	}
715007f6c5eSJohannes Berg 
716007f6c5eSJohannes Berg 	return true;
717007f6c5eSJohannes Berg }
718007f6c5eSJohannes Berg 
71990a53e44SJohannes Berg #ifdef CONFIG_CFG80211_REQUIRE_SIGNED_REGDB
72090a53e44SJohannes Berg static struct key *builtin_regdb_keys;
72190a53e44SJohannes Berg 
72290a53e44SJohannes Berg static void __init load_keys_from_buffer(const u8 *p, unsigned int buflen)
72390a53e44SJohannes Berg {
72490a53e44SJohannes Berg 	const u8 *end = p + buflen;
72590a53e44SJohannes Berg 	size_t plen;
72690a53e44SJohannes Berg 	key_ref_t key;
72790a53e44SJohannes Berg 
72890a53e44SJohannes Berg 	while (p < end) {
72990a53e44SJohannes Berg 		/* Each cert begins with an ASN.1 SEQUENCE tag and must be more
73090a53e44SJohannes Berg 		 * than 256 bytes in size.
73190a53e44SJohannes Berg 		 */
73290a53e44SJohannes Berg 		if (end - p < 4)
73390a53e44SJohannes Berg 			goto dodgy_cert;
73490a53e44SJohannes Berg 		if (p[0] != 0x30 &&
73590a53e44SJohannes Berg 		    p[1] != 0x82)
73690a53e44SJohannes Berg 			goto dodgy_cert;
73790a53e44SJohannes Berg 		plen = (p[2] << 8) | p[3];
73890a53e44SJohannes Berg 		plen += 4;
73990a53e44SJohannes Berg 		if (plen > end - p)
74090a53e44SJohannes Berg 			goto dodgy_cert;
74190a53e44SJohannes Berg 
74290a53e44SJohannes Berg 		key = key_create_or_update(make_key_ref(builtin_regdb_keys, 1),
74390a53e44SJohannes Berg 					   "asymmetric", NULL, p, plen,
744028db3e2SLinus Torvalds 					   ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
745028db3e2SLinus Torvalds 					    KEY_USR_VIEW | KEY_USR_READ),
74690a53e44SJohannes Berg 					   KEY_ALLOC_NOT_IN_QUOTA |
74790a53e44SJohannes Berg 					   KEY_ALLOC_BUILT_IN |
74890a53e44SJohannes Berg 					   KEY_ALLOC_BYPASS_RESTRICTION);
74990a53e44SJohannes Berg 		if (IS_ERR(key)) {
75090a53e44SJohannes Berg 			pr_err("Problem loading in-kernel X.509 certificate (%ld)\n",
75190a53e44SJohannes Berg 			       PTR_ERR(key));
75290a53e44SJohannes Berg 		} else {
75390a53e44SJohannes Berg 			pr_notice("Loaded X.509 cert '%s'\n",
75490a53e44SJohannes Berg 				  key_ref_to_ptr(key)->description);
75590a53e44SJohannes Berg 			key_ref_put(key);
75690a53e44SJohannes Berg 		}
75790a53e44SJohannes Berg 		p += plen;
75890a53e44SJohannes Berg 	}
75990a53e44SJohannes Berg 
76090a53e44SJohannes Berg 	return;
76190a53e44SJohannes Berg 
76290a53e44SJohannes Berg dodgy_cert:
76390a53e44SJohannes Berg 	pr_err("Problem parsing in-kernel X.509 certificate list\n");
76490a53e44SJohannes Berg }
76590a53e44SJohannes Berg 
76690a53e44SJohannes Berg static int __init load_builtin_regdb_keys(void)
76790a53e44SJohannes Berg {
76890a53e44SJohannes Berg 	builtin_regdb_keys =
76990a53e44SJohannes Berg 		keyring_alloc(".builtin_regdb_keys",
77090a53e44SJohannes Berg 			      KUIDT_INIT(0), KGIDT_INIT(0), current_cred(),
771028db3e2SLinus Torvalds 			      ((KEY_POS_ALL & ~KEY_POS_SETATTR) |
772028db3e2SLinus Torvalds 			      KEY_USR_VIEW | KEY_USR_READ | KEY_USR_SEARCH),
77390a53e44SJohannes Berg 			      KEY_ALLOC_NOT_IN_QUOTA, NULL, NULL);
77490a53e44SJohannes Berg 	if (IS_ERR(builtin_regdb_keys))
77590a53e44SJohannes Berg 		return PTR_ERR(builtin_regdb_keys);
77690a53e44SJohannes Berg 
77790a53e44SJohannes Berg 	pr_notice("Loading compiled-in X.509 certificates for regulatory database\n");
77890a53e44SJohannes Berg 
77990a53e44SJohannes Berg #ifdef CONFIG_CFG80211_USE_KERNEL_REGDB_KEYS
78090a53e44SJohannes Berg 	load_keys_from_buffer(shipped_regdb_certs, shipped_regdb_certs_len);
78190a53e44SJohannes Berg #endif
78288230ef1SArnd Bergmann #ifdef CONFIG_CFG80211_EXTRA_REGDB_KEYDIR
78390a53e44SJohannes Berg 	if (CONFIG_CFG80211_EXTRA_REGDB_KEYDIR[0] != '\0')
78490a53e44SJohannes Berg 		load_keys_from_buffer(extra_regdb_certs, extra_regdb_certs_len);
78590a53e44SJohannes Berg #endif
78690a53e44SJohannes Berg 
78790a53e44SJohannes Berg 	return 0;
78890a53e44SJohannes Berg }
78990a53e44SJohannes Berg 
79090a53e44SJohannes Berg static bool regdb_has_valid_signature(const u8 *data, unsigned int size)
79190a53e44SJohannes Berg {
79290a53e44SJohannes Berg 	const struct firmware *sig;
79390a53e44SJohannes Berg 	bool result;
79490a53e44SJohannes Berg 
79590a53e44SJohannes Berg 	if (request_firmware(&sig, "regulatory.db.p7s", &reg_pdev->dev))
79690a53e44SJohannes Berg 		return false;
79790a53e44SJohannes Berg 
79890a53e44SJohannes Berg 	result = verify_pkcs7_signature(data, size, sig->data, sig->size,
79990a53e44SJohannes Berg 					builtin_regdb_keys,
80090a53e44SJohannes Berg 					VERIFYING_UNSPECIFIED_SIGNATURE,
80190a53e44SJohannes Berg 					NULL, NULL) == 0;
80290a53e44SJohannes Berg 
80390a53e44SJohannes Berg 	release_firmware(sig);
80490a53e44SJohannes Berg 
80590a53e44SJohannes Berg 	return result;
80690a53e44SJohannes Berg }
80790a53e44SJohannes Berg 
80890a53e44SJohannes Berg static void free_regdb_keyring(void)
80990a53e44SJohannes Berg {
81090a53e44SJohannes Berg 	key_put(builtin_regdb_keys);
81190a53e44SJohannes Berg }
81290a53e44SJohannes Berg #else
81390a53e44SJohannes Berg static int load_builtin_regdb_keys(void)
81490a53e44SJohannes Berg {
81590a53e44SJohannes Berg 	return 0;
81690a53e44SJohannes Berg }
81790a53e44SJohannes Berg 
81890a53e44SJohannes Berg static bool regdb_has_valid_signature(const u8 *data, unsigned int size)
81990a53e44SJohannes Berg {
82090a53e44SJohannes Berg 	return true;
82190a53e44SJohannes Berg }
82290a53e44SJohannes Berg 
82390a53e44SJohannes Berg static void free_regdb_keyring(void)
82490a53e44SJohannes Berg {
82590a53e44SJohannes Berg }
82690a53e44SJohannes Berg #endif /* CONFIG_CFG80211_REQUIRE_SIGNED_REGDB */
82790a53e44SJohannes Berg 
828007f6c5eSJohannes Berg static bool valid_regdb(const u8 *data, unsigned int size)
829007f6c5eSJohannes Berg {
830007f6c5eSJohannes Berg 	const struct fwdb_header *hdr = (void *)data;
831007f6c5eSJohannes Berg 	const struct fwdb_country *country;
832007f6c5eSJohannes Berg 
833007f6c5eSJohannes Berg 	if (size < sizeof(*hdr))
834007f6c5eSJohannes Berg 		return false;
835007f6c5eSJohannes Berg 
836007f6c5eSJohannes Berg 	if (hdr->magic != cpu_to_be32(FWDB_MAGIC))
837007f6c5eSJohannes Berg 		return false;
838007f6c5eSJohannes Berg 
839007f6c5eSJohannes Berg 	if (hdr->version != cpu_to_be32(FWDB_VERSION))
840007f6c5eSJohannes Berg 		return false;
841007f6c5eSJohannes Berg 
84290a53e44SJohannes Berg 	if (!regdb_has_valid_signature(data, size))
84390a53e44SJohannes Berg 		return false;
84490a53e44SJohannes Berg 
845007f6c5eSJohannes Berg 	country = &hdr->country[0];
846007f6c5eSJohannes Berg 	while ((u8 *)(country + 1) <= data + size) {
847007f6c5eSJohannes Berg 		if (!country->coll_ptr)
848007f6c5eSJohannes Berg 			break;
849007f6c5eSJohannes Berg 		if (!valid_country(data, size, country))
850007f6c5eSJohannes Berg 			return false;
851007f6c5eSJohannes Berg 		country++;
852007f6c5eSJohannes Berg 	}
853007f6c5eSJohannes Berg 
854007f6c5eSJohannes Berg 	return true;
855007f6c5eSJohannes Berg }
856007f6c5eSJohannes Berg 
857014f5a25SStanislaw Gruszka static void set_wmm_rule(const struct fwdb_header *db,
858014f5a25SStanislaw Gruszka 			 const struct fwdb_country *country,
859014f5a25SStanislaw Gruszka 			 const struct fwdb_rule *rule,
860014f5a25SStanislaw Gruszka 			 struct ieee80211_reg_rule *rrule)
861230ebaa1SHaim Dreyfuss {
862014f5a25SStanislaw Gruszka 	struct ieee80211_wmm_rule *wmm_rule = &rrule->wmm_rule;
863014f5a25SStanislaw Gruszka 	struct fwdb_wmm_rule *wmm;
864014f5a25SStanislaw Gruszka 	unsigned int i, wmm_ptr;
865014f5a25SStanislaw Gruszka 
866014f5a25SStanislaw Gruszka 	wmm_ptr = be16_to_cpu(rule->wmm_ptr) << 2;
867014f5a25SStanislaw Gruszka 	wmm = (void *)((u8 *)db + wmm_ptr);
868014f5a25SStanislaw Gruszka 
869014f5a25SStanislaw Gruszka 	if (!valid_wmm(wmm)) {
870014f5a25SStanislaw Gruszka 		pr_err("Invalid regulatory WMM rule %u-%u in domain %c%c\n",
871014f5a25SStanislaw Gruszka 		       be32_to_cpu(rule->start), be32_to_cpu(rule->end),
872014f5a25SStanislaw Gruszka 		       country->alpha2[0], country->alpha2[1]);
873014f5a25SStanislaw Gruszka 		return;
874014f5a25SStanislaw Gruszka 	}
875230ebaa1SHaim Dreyfuss 
876230ebaa1SHaim Dreyfuss 	for (i = 0; i < IEEE80211_NUM_ACS; i++) {
877014f5a25SStanislaw Gruszka 		wmm_rule->client[i].cw_min =
878230ebaa1SHaim Dreyfuss 			ecw2cw((wmm->client[i].ecw & 0xf0) >> 4);
879014f5a25SStanislaw Gruszka 		wmm_rule->client[i].cw_max = ecw2cw(wmm->client[i].ecw & 0x0f);
880014f5a25SStanislaw Gruszka 		wmm_rule->client[i].aifsn =  wmm->client[i].aifsn;
881014f5a25SStanislaw Gruszka 		wmm_rule->client[i].cot =
882014f5a25SStanislaw Gruszka 			1000 * be16_to_cpu(wmm->client[i].cot);
883014f5a25SStanislaw Gruszka 		wmm_rule->ap[i].cw_min = ecw2cw((wmm->ap[i].ecw & 0xf0) >> 4);
884014f5a25SStanislaw Gruszka 		wmm_rule->ap[i].cw_max = ecw2cw(wmm->ap[i].ecw & 0x0f);
885014f5a25SStanislaw Gruszka 		wmm_rule->ap[i].aifsn = wmm->ap[i].aifsn;
886014f5a25SStanislaw Gruszka 		wmm_rule->ap[i].cot = 1000 * be16_to_cpu(wmm->ap[i].cot);
887230ebaa1SHaim Dreyfuss 	}
88838cb87eeSStanislaw Gruszka 
88938cb87eeSStanislaw Gruszka 	rrule->has_wmm = true;
890230ebaa1SHaim Dreyfuss }
891230ebaa1SHaim Dreyfuss 
89219d3577eSHaim Dreyfuss static int __regdb_query_wmm(const struct fwdb_header *db,
89319d3577eSHaim Dreyfuss 			     const struct fwdb_country *country, int freq,
894014f5a25SStanislaw Gruszka 			     struct ieee80211_reg_rule *rrule)
89519d3577eSHaim Dreyfuss {
89619d3577eSHaim Dreyfuss 	unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2;
89719d3577eSHaim Dreyfuss 	struct fwdb_collection *coll = (void *)((u8 *)db + ptr);
89819d3577eSHaim Dreyfuss 	int i;
89919d3577eSHaim Dreyfuss 
90019d3577eSHaim Dreyfuss 	for (i = 0; i < coll->n_rules; i++) {
90119d3577eSHaim Dreyfuss 		__be16 *rules_ptr = (void *)((u8 *)coll + ALIGN(coll->len, 2));
90219d3577eSHaim Dreyfuss 		unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2;
903014f5a25SStanislaw Gruszka 		struct fwdb_rule *rule = (void *)((u8 *)db + rule_ptr);
90419d3577eSHaim Dreyfuss 
905014f5a25SStanislaw Gruszka 		if (rule->len < offsetofend(struct fwdb_rule, wmm_ptr))
90619d3577eSHaim Dreyfuss 			continue;
90719d3577eSHaim Dreyfuss 
908014f5a25SStanislaw Gruszka 		if (freq >= KHZ_TO_MHZ(be32_to_cpu(rule->start)) &&
909014f5a25SStanislaw Gruszka 		    freq <= KHZ_TO_MHZ(be32_to_cpu(rule->end))) {
910014f5a25SStanislaw Gruszka 			set_wmm_rule(db, country, rule, rrule);
91119d3577eSHaim Dreyfuss 			return 0;
91219d3577eSHaim Dreyfuss 		}
91319d3577eSHaim Dreyfuss 	}
91419d3577eSHaim Dreyfuss 
91519d3577eSHaim Dreyfuss 	return -ENODATA;
91619d3577eSHaim Dreyfuss }
91719d3577eSHaim Dreyfuss 
91838cb87eeSStanislaw Gruszka int reg_query_regdb_wmm(char *alpha2, int freq, struct ieee80211_reg_rule *rule)
91919d3577eSHaim Dreyfuss {
92019d3577eSHaim Dreyfuss 	const struct fwdb_header *hdr = regdb;
92119d3577eSHaim Dreyfuss 	const struct fwdb_country *country;
92219d3577eSHaim Dreyfuss 
9235247a77cSHaim Dreyfuss 	if (!regdb)
9245247a77cSHaim Dreyfuss 		return -ENODATA;
9255247a77cSHaim Dreyfuss 
92619d3577eSHaim Dreyfuss 	if (IS_ERR(regdb))
92719d3577eSHaim Dreyfuss 		return PTR_ERR(regdb);
92819d3577eSHaim Dreyfuss 
92919d3577eSHaim Dreyfuss 	country = &hdr->country[0];
93019d3577eSHaim Dreyfuss 	while (country->coll_ptr) {
93119d3577eSHaim Dreyfuss 		if (alpha2_equal(alpha2, country->alpha2))
93238cb87eeSStanislaw Gruszka 			return __regdb_query_wmm(regdb, country, freq, rule);
93319d3577eSHaim Dreyfuss 
93419d3577eSHaim Dreyfuss 		country++;
93519d3577eSHaim Dreyfuss 	}
93619d3577eSHaim Dreyfuss 
93719d3577eSHaim Dreyfuss 	return -ENODATA;
93819d3577eSHaim Dreyfuss }
93919d3577eSHaim Dreyfuss EXPORT_SYMBOL(reg_query_regdb_wmm);
94019d3577eSHaim Dreyfuss 
941007f6c5eSJohannes Berg static int regdb_query_country(const struct fwdb_header *db,
942007f6c5eSJohannes Berg 			       const struct fwdb_country *country)
943007f6c5eSJohannes Berg {
944007f6c5eSJohannes Berg 	unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2;
945007f6c5eSJohannes Berg 	struct fwdb_collection *coll = (void *)((u8 *)db + ptr);
946007f6c5eSJohannes Berg 	struct ieee80211_regdomain *regdom;
9479f8c7136SGustavo A. R. Silva 	unsigned int i;
948007f6c5eSJohannes Berg 
9499f8c7136SGustavo A. R. Silva 	regdom = kzalloc(struct_size(regdom, reg_rules, coll->n_rules),
9509f8c7136SGustavo A. R. Silva 			 GFP_KERNEL);
951007f6c5eSJohannes Berg 	if (!regdom)
952007f6c5eSJohannes Berg 		return -ENOMEM;
953007f6c5eSJohannes Berg 
954007f6c5eSJohannes Berg 	regdom->n_reg_rules = coll->n_rules;
955007f6c5eSJohannes Berg 	regdom->alpha2[0] = country->alpha2[0];
956007f6c5eSJohannes Berg 	regdom->alpha2[1] = country->alpha2[1];
957007f6c5eSJohannes Berg 	regdom->dfs_region = coll->dfs_region;
958007f6c5eSJohannes Berg 
959007f6c5eSJohannes Berg 	for (i = 0; i < regdom->n_reg_rules; i++) {
960007f6c5eSJohannes Berg 		__be16 *rules_ptr = (void *)((u8 *)coll + ALIGN(coll->len, 2));
961007f6c5eSJohannes Berg 		unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2;
962007f6c5eSJohannes Berg 		struct fwdb_rule *rule = (void *)((u8 *)db + rule_ptr);
963007f6c5eSJohannes Berg 		struct ieee80211_reg_rule *rrule = &regdom->reg_rules[i];
964007f6c5eSJohannes Berg 
965007f6c5eSJohannes Berg 		rrule->freq_range.start_freq_khz = be32_to_cpu(rule->start);
966007f6c5eSJohannes Berg 		rrule->freq_range.end_freq_khz = be32_to_cpu(rule->end);
967007f6c5eSJohannes Berg 		rrule->freq_range.max_bandwidth_khz = be32_to_cpu(rule->max_bw);
968007f6c5eSJohannes Berg 
969007f6c5eSJohannes Berg 		rrule->power_rule.max_antenna_gain = 0;
970007f6c5eSJohannes Berg 		rrule->power_rule.max_eirp = be16_to_cpu(rule->max_eirp);
971007f6c5eSJohannes Berg 
972007f6c5eSJohannes Berg 		rrule->flags = 0;
973007f6c5eSJohannes Berg 		if (rule->flags & FWDB_FLAG_NO_OFDM)
974007f6c5eSJohannes Berg 			rrule->flags |= NL80211_RRF_NO_OFDM;
975007f6c5eSJohannes Berg 		if (rule->flags & FWDB_FLAG_NO_OUTDOOR)
976007f6c5eSJohannes Berg 			rrule->flags |= NL80211_RRF_NO_OUTDOOR;
977007f6c5eSJohannes Berg 		if (rule->flags & FWDB_FLAG_DFS)
978007f6c5eSJohannes Berg 			rrule->flags |= NL80211_RRF_DFS;
979007f6c5eSJohannes Berg 		if (rule->flags & FWDB_FLAG_NO_IR)
980007f6c5eSJohannes Berg 			rrule->flags |= NL80211_RRF_NO_IR;
981007f6c5eSJohannes Berg 		if (rule->flags & FWDB_FLAG_AUTO_BW)
982007f6c5eSJohannes Berg 			rrule->flags |= NL80211_RRF_AUTO_BW;
983007f6c5eSJohannes Berg 
984007f6c5eSJohannes Berg 		rrule->dfs_cac_ms = 0;
985007f6c5eSJohannes Berg 
986007f6c5eSJohannes Berg 		/* handle optional data */
987007f6c5eSJohannes Berg 		if (rule->len >= offsetofend(struct fwdb_rule, cac_timeout))
988007f6c5eSJohannes Berg 			rrule->dfs_cac_ms =
989007f6c5eSJohannes Berg 				1000 * be16_to_cpu(rule->cac_timeout);
990014f5a25SStanislaw Gruszka 		if (rule->len >= offsetofend(struct fwdb_rule, wmm_ptr))
991014f5a25SStanislaw Gruszka 			set_wmm_rule(db, country, rule, rrule);
992230ebaa1SHaim Dreyfuss 	}
993007f6c5eSJohannes Berg 
994007f6c5eSJohannes Berg 	return reg_schedule_apply(regdom);
995007f6c5eSJohannes Berg }
996007f6c5eSJohannes Berg 
997007f6c5eSJohannes Berg static int query_regdb(const char *alpha2)
998007f6c5eSJohannes Berg {
999007f6c5eSJohannes Berg 	const struct fwdb_header *hdr = regdb;
1000007f6c5eSJohannes Berg 	const struct fwdb_country *country;
1001007f6c5eSJohannes Berg 
10021ea4ff3eSJohannes Berg 	ASSERT_RTNL();
10031ea4ff3eSJohannes Berg 
1004007f6c5eSJohannes Berg 	if (IS_ERR(regdb))
1005007f6c5eSJohannes Berg 		return PTR_ERR(regdb);
1006007f6c5eSJohannes Berg 
1007007f6c5eSJohannes Berg 	country = &hdr->country[0];
1008007f6c5eSJohannes Berg 	while (country->coll_ptr) {
1009007f6c5eSJohannes Berg 		if (alpha2_equal(alpha2, country->alpha2))
1010007f6c5eSJohannes Berg 			return regdb_query_country(regdb, country);
1011007f6c5eSJohannes Berg 		country++;
1012007f6c5eSJohannes Berg 	}
1013007f6c5eSJohannes Berg 
1014007f6c5eSJohannes Berg 	return -ENODATA;
1015007f6c5eSJohannes Berg }
1016007f6c5eSJohannes Berg 
1017007f6c5eSJohannes Berg static void regdb_fw_cb(const struct firmware *fw, void *context)
1018007f6c5eSJohannes Berg {
10191ea4ff3eSJohannes Berg 	int set_error = 0;
10201ea4ff3eSJohannes Berg 	bool restore = true;
1021007f6c5eSJohannes Berg 	void *db;
1022007f6c5eSJohannes Berg 
1023007f6c5eSJohannes Berg 	if (!fw) {
1024007f6c5eSJohannes Berg 		pr_info("failed to load regulatory.db\n");
10251ea4ff3eSJohannes Berg 		set_error = -ENODATA;
10261ea4ff3eSJohannes Berg 	} else if (!valid_regdb(fw->data, fw->size)) {
102790a53e44SJohannes Berg 		pr_info("loaded regulatory.db is malformed or signature is missing/invalid\n");
10281ea4ff3eSJohannes Berg 		set_error = -EINVAL;
1029007f6c5eSJohannes Berg 	}
1030007f6c5eSJohannes Berg 
1031007f6c5eSJohannes Berg 	rtnl_lock();
1032faae54adSChaitanya Tata 	if (regdb && !IS_ERR(regdb)) {
1033faae54adSChaitanya Tata 		/* negative case - a bug
1034faae54adSChaitanya Tata 		 * positive case - can happen due to race in case of multiple cb's in
1035faae54adSChaitanya Tata 		 * queue, due to usage of asynchronous callback
1036faae54adSChaitanya Tata 		 *
1037faae54adSChaitanya Tata 		 * Either case, just restore and free new db.
1038faae54adSChaitanya Tata 		 */
10391ea4ff3eSJohannes Berg 	} else if (set_error) {
10401ea4ff3eSJohannes Berg 		regdb = ERR_PTR(set_error);
10411ea4ff3eSJohannes Berg 	} else if (fw) {
10421ea4ff3eSJohannes Berg 		db = kmemdup(fw->data, fw->size, GFP_KERNEL);
10431ea4ff3eSJohannes Berg 		if (db) {
10441ea4ff3eSJohannes Berg 			regdb = db;
10451ea4ff3eSJohannes Berg 			restore = context && query_regdb(context);
10461ea4ff3eSJohannes Berg 		} else {
10471ea4ff3eSJohannes Berg 			restore = true;
10481ea4ff3eSJohannes Berg 		}
10491ea4ff3eSJohannes Berg 	}
10501ea4ff3eSJohannes Berg 
10511ea4ff3eSJohannes Berg 	if (restore)
1052e646a025SJohannes Berg 		restore_regulatory_settings(true, false);
10531ea4ff3eSJohannes Berg 
1054007f6c5eSJohannes Berg 	rtnl_unlock();
10551ea4ff3eSJohannes Berg 
1056007f6c5eSJohannes Berg 	kfree(context);
10571ea4ff3eSJohannes Berg 
10581ea4ff3eSJohannes Berg 	release_firmware(fw);
1059007f6c5eSJohannes Berg }
1060007f6c5eSJohannes Berg 
1061007f6c5eSJohannes Berg static int query_regdb_file(const char *alpha2)
1062007f6c5eSJohannes Berg {
10631ea4ff3eSJohannes Berg 	ASSERT_RTNL();
10641ea4ff3eSJohannes Berg 
1065007f6c5eSJohannes Berg 	if (regdb)
1066007f6c5eSJohannes Berg 		return query_regdb(alpha2);
1067007f6c5eSJohannes Berg 
1068007f6c5eSJohannes Berg 	alpha2 = kmemdup(alpha2, 2, GFP_KERNEL);
1069007f6c5eSJohannes Berg 	if (!alpha2)
1070007f6c5eSJohannes Berg 		return -ENOMEM;
1071007f6c5eSJohannes Berg 
1072007f6c5eSJohannes Berg 	return request_firmware_nowait(THIS_MODULE, true, "regulatory.db",
1073007f6c5eSJohannes Berg 				       &reg_pdev->dev, GFP_KERNEL,
1074007f6c5eSJohannes Berg 				       (void *)alpha2, regdb_fw_cb);
1075007f6c5eSJohannes Berg }
1076007f6c5eSJohannes Berg 
10771ea4ff3eSJohannes Berg int reg_reload_regdb(void)
10781ea4ff3eSJohannes Berg {
10791ea4ff3eSJohannes Berg 	const struct firmware *fw;
10801ea4ff3eSJohannes Berg 	void *db;
10811ea4ff3eSJohannes Berg 	int err;
10821ea4ff3eSJohannes Berg 
10831ea4ff3eSJohannes Berg 	err = request_firmware(&fw, "regulatory.db", &reg_pdev->dev);
10841ea4ff3eSJohannes Berg 	if (err)
10851ea4ff3eSJohannes Berg 		return err;
10861ea4ff3eSJohannes Berg 
10871ea4ff3eSJohannes Berg 	if (!valid_regdb(fw->data, fw->size)) {
10881ea4ff3eSJohannes Berg 		err = -ENODATA;
10891ea4ff3eSJohannes Berg 		goto out;
10901ea4ff3eSJohannes Berg 	}
10911ea4ff3eSJohannes Berg 
10921ea4ff3eSJohannes Berg 	db = kmemdup(fw->data, fw->size, GFP_KERNEL);
10931ea4ff3eSJohannes Berg 	if (!db) {
10941ea4ff3eSJohannes Berg 		err = -ENOMEM;
10951ea4ff3eSJohannes Berg 		goto out;
10961ea4ff3eSJohannes Berg 	}
10971ea4ff3eSJohannes Berg 
10981ea4ff3eSJohannes Berg 	rtnl_lock();
10991ea4ff3eSJohannes Berg 	if (!IS_ERR_OR_NULL(regdb))
11001ea4ff3eSJohannes Berg 		kfree(regdb);
11011ea4ff3eSJohannes Berg 	regdb = db;
11021ea4ff3eSJohannes Berg 	rtnl_unlock();
11031ea4ff3eSJohannes Berg 
11041ea4ff3eSJohannes Berg  out:
11051ea4ff3eSJohannes Berg 	release_firmware(fw);
11061ea4ff3eSJohannes Berg 	return err;
11071ea4ff3eSJohannes Berg }
11081ea4ff3eSJohannes Berg 
1109cecbb069SJohannes Berg static bool reg_query_database(struct regulatory_request *request)
1110fe6631ffSLuis R. Rodriguez {
1111007f6c5eSJohannes Berg 	if (query_regdb_file(request->alpha2) == 0)
1112007f6c5eSJohannes Berg 		return true;
1113007f6c5eSJohannes Berg 
1114c7d319e5SJohannes Berg 	if (call_crda(request->alpha2) == 0)
1115c7d319e5SJohannes Berg 		return true;
1116c7d319e5SJohannes Berg 
1117c7d319e5SJohannes Berg 	return false;
1118fe6631ffSLuis R. Rodriguez }
1119fe6631ffSLuis R. Rodriguez 
1120e438768fSLuis R. Rodriguez bool reg_is_valid_request(const char *alpha2)
1121b2e1b302SLuis R. Rodriguez {
1122c492db37SJohannes Berg 	struct regulatory_request *lr = get_last_request();
112361405e97SLuis R. Rodriguez 
1124c492db37SJohannes Berg 	if (!lr || lr->processed)
1125f6037d09SJohannes Berg 		return false;
1126f6037d09SJohannes Berg 
1127c492db37SJohannes Berg 	return alpha2_equal(lr->alpha2, alpha2);
1128b2e1b302SLuis R. Rodriguez }
1129b2e1b302SLuis R. Rodriguez 
1130e3961af1SJanusz Dziedzic static const struct ieee80211_regdomain *reg_get_regdomain(struct wiphy *wiphy)
1131e3961af1SJanusz Dziedzic {
1132e3961af1SJanusz Dziedzic 	struct regulatory_request *lr = get_last_request();
1133e3961af1SJanusz Dziedzic 
1134e3961af1SJanusz Dziedzic 	/*
1135e3961af1SJanusz Dziedzic 	 * Follow the driver's regulatory domain, if present, unless a country
1136e3961af1SJanusz Dziedzic 	 * IE has been processed or a user wants to help complaince further
1137e3961af1SJanusz Dziedzic 	 */
1138e3961af1SJanusz Dziedzic 	if (lr->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE &&
1139e3961af1SJanusz Dziedzic 	    lr->initiator != NL80211_REGDOM_SET_BY_USER &&
1140e3961af1SJanusz Dziedzic 	    wiphy->regd)
1141e3961af1SJanusz Dziedzic 		return get_wiphy_regdom(wiphy);
1142e3961af1SJanusz Dziedzic 
1143e3961af1SJanusz Dziedzic 	return get_cfg80211_regdom();
1144e3961af1SJanusz Dziedzic }
1145e3961af1SJanusz Dziedzic 
1146a6d4a534SArik Nemtsov static unsigned int
1147a6d4a534SArik Nemtsov reg_get_max_bandwidth_from_range(const struct ieee80211_regdomain *rd,
114897524820SJanusz Dziedzic 				 const struct ieee80211_reg_rule *rule)
114997524820SJanusz Dziedzic {
115097524820SJanusz Dziedzic 	const struct ieee80211_freq_range *freq_range = &rule->freq_range;
115197524820SJanusz Dziedzic 	const struct ieee80211_freq_range *freq_range_tmp;
115297524820SJanusz Dziedzic 	const struct ieee80211_reg_rule *tmp;
115397524820SJanusz Dziedzic 	u32 start_freq, end_freq, idx, no;
115497524820SJanusz Dziedzic 
115597524820SJanusz Dziedzic 	for (idx = 0; idx < rd->n_reg_rules; idx++)
115697524820SJanusz Dziedzic 		if (rule == &rd->reg_rules[idx])
115797524820SJanusz Dziedzic 			break;
115897524820SJanusz Dziedzic 
115997524820SJanusz Dziedzic 	if (idx == rd->n_reg_rules)
116097524820SJanusz Dziedzic 		return 0;
116197524820SJanusz Dziedzic 
116297524820SJanusz Dziedzic 	/* get start_freq */
116397524820SJanusz Dziedzic 	no = idx;
116497524820SJanusz Dziedzic 
116597524820SJanusz Dziedzic 	while (no) {
116697524820SJanusz Dziedzic 		tmp = &rd->reg_rules[--no];
116797524820SJanusz Dziedzic 		freq_range_tmp = &tmp->freq_range;
116897524820SJanusz Dziedzic 
116997524820SJanusz Dziedzic 		if (freq_range_tmp->end_freq_khz < freq_range->start_freq_khz)
117097524820SJanusz Dziedzic 			break;
117197524820SJanusz Dziedzic 
117297524820SJanusz Dziedzic 		freq_range = freq_range_tmp;
117397524820SJanusz Dziedzic 	}
117497524820SJanusz Dziedzic 
117597524820SJanusz Dziedzic 	start_freq = freq_range->start_freq_khz;
117697524820SJanusz Dziedzic 
117797524820SJanusz Dziedzic 	/* get end_freq */
117897524820SJanusz Dziedzic 	freq_range = &rule->freq_range;
117997524820SJanusz Dziedzic 	no = idx;
118097524820SJanusz Dziedzic 
118197524820SJanusz Dziedzic 	while (no < rd->n_reg_rules - 1) {
118297524820SJanusz Dziedzic 		tmp = &rd->reg_rules[++no];
118397524820SJanusz Dziedzic 		freq_range_tmp = &tmp->freq_range;
118497524820SJanusz Dziedzic 
118597524820SJanusz Dziedzic 		if (freq_range_tmp->start_freq_khz > freq_range->end_freq_khz)
118697524820SJanusz Dziedzic 			break;
118797524820SJanusz Dziedzic 
118897524820SJanusz Dziedzic 		freq_range = freq_range_tmp;
118997524820SJanusz Dziedzic 	}
119097524820SJanusz Dziedzic 
119197524820SJanusz Dziedzic 	end_freq = freq_range->end_freq_khz;
119297524820SJanusz Dziedzic 
119397524820SJanusz Dziedzic 	return end_freq - start_freq;
119497524820SJanusz Dziedzic }
119597524820SJanusz Dziedzic 
1196a6d4a534SArik Nemtsov unsigned int reg_get_max_bandwidth(const struct ieee80211_regdomain *rd,
1197a6d4a534SArik Nemtsov 				   const struct ieee80211_reg_rule *rule)
1198a6d4a534SArik Nemtsov {
1199a6d4a534SArik Nemtsov 	unsigned int bw = reg_get_max_bandwidth_from_range(rd, rule);
1200a6d4a534SArik Nemtsov 
1201a6d4a534SArik Nemtsov 	if (rule->flags & NL80211_RRF_NO_160MHZ)
1202a6d4a534SArik Nemtsov 		bw = min_t(unsigned int, bw, MHZ_TO_KHZ(80));
1203a6d4a534SArik Nemtsov 	if (rule->flags & NL80211_RRF_NO_80MHZ)
1204a6d4a534SArik Nemtsov 		bw = min_t(unsigned int, bw, MHZ_TO_KHZ(40));
1205a6d4a534SArik Nemtsov 
1206a6d4a534SArik Nemtsov 	/*
1207a6d4a534SArik Nemtsov 	 * HT40+/HT40- limits are handled per-channel. Only limit BW if both
1208a6d4a534SArik Nemtsov 	 * are not allowed.
1209a6d4a534SArik Nemtsov 	 */
1210a6d4a534SArik Nemtsov 	if (rule->flags & NL80211_RRF_NO_HT40MINUS &&
1211a6d4a534SArik Nemtsov 	    rule->flags & NL80211_RRF_NO_HT40PLUS)
1212a6d4a534SArik Nemtsov 		bw = min_t(unsigned int, bw, MHZ_TO_KHZ(20));
1213a6d4a534SArik Nemtsov 
1214a6d4a534SArik Nemtsov 	return bw;
1215a6d4a534SArik Nemtsov }
1216a6d4a534SArik Nemtsov 
1217b2e1b302SLuis R. Rodriguez /* Sanity check on a regulatory rule */
1218a3d2eaf0SJohannes Berg static bool is_valid_reg_rule(const struct ieee80211_reg_rule *rule)
1219b2e1b302SLuis R. Rodriguez {
1220a3d2eaf0SJohannes Berg 	const struct ieee80211_freq_range *freq_range = &rule->freq_range;
1221b2e1b302SLuis R. Rodriguez 	u32 freq_diff;
1222b2e1b302SLuis R. Rodriguez 
122391e99004SLuis R. Rodriguez 	if (freq_range->start_freq_khz <= 0 || freq_range->end_freq_khz <= 0)
1224b2e1b302SLuis R. Rodriguez 		return false;
1225b2e1b302SLuis R. Rodriguez 
1226b2e1b302SLuis R. Rodriguez 	if (freq_range->start_freq_khz > freq_range->end_freq_khz)
1227b2e1b302SLuis R. Rodriguez 		return false;
1228b2e1b302SLuis R. Rodriguez 
1229b2e1b302SLuis R. Rodriguez 	freq_diff = freq_range->end_freq_khz - freq_range->start_freq_khz;
1230b2e1b302SLuis R. Rodriguez 
1231bd05f28eSRoel Kluin 	if (freq_range->end_freq_khz <= freq_range->start_freq_khz ||
1232bd05f28eSRoel Kluin 	    freq_range->max_bandwidth_khz > freq_diff)
1233b2e1b302SLuis R. Rodriguez 		return false;
1234b2e1b302SLuis R. Rodriguez 
1235b2e1b302SLuis R. Rodriguez 	return true;
1236b2e1b302SLuis R. Rodriguez }
1237b2e1b302SLuis R. Rodriguez 
1238a3d2eaf0SJohannes Berg static bool is_valid_rd(const struct ieee80211_regdomain *rd)
1239b2e1b302SLuis R. Rodriguez {
1240a3d2eaf0SJohannes Berg 	const struct ieee80211_reg_rule *reg_rule = NULL;
1241b2e1b302SLuis R. Rodriguez 	unsigned int i;
1242b2e1b302SLuis R. Rodriguez 
1243b2e1b302SLuis R. Rodriguez 	if (!rd->n_reg_rules)
1244b2e1b302SLuis R. Rodriguez 		return false;
1245b2e1b302SLuis R. Rodriguez 
124688dc1c3fSLuis R. Rodriguez 	if (WARN_ON(rd->n_reg_rules > NL80211_MAX_SUPP_REG_RULES))
124788dc1c3fSLuis R. Rodriguez 		return false;
124888dc1c3fSLuis R. Rodriguez 
1249b2e1b302SLuis R. Rodriguez 	for (i = 0; i < rd->n_reg_rules; i++) {
1250b2e1b302SLuis R. Rodriguez 		reg_rule = &rd->reg_rules[i];
1251b2e1b302SLuis R. Rodriguez 		if (!is_valid_reg_rule(reg_rule))
1252b2e1b302SLuis R. Rodriguez 			return false;
1253b2e1b302SLuis R. Rodriguez 	}
1254b2e1b302SLuis R. Rodriguez 
1255b2e1b302SLuis R. Rodriguez 	return true;
1256b2e1b302SLuis R. Rodriguez }
1257b2e1b302SLuis R. Rodriguez 
12580c7dc45dSLuis R. Rodriguez /**
12590c7dc45dSLuis R. Rodriguez  * freq_in_rule_band - tells us if a frequency is in a frequency band
12600c7dc45dSLuis R. Rodriguez  * @freq_range: frequency rule we want to query
12610c7dc45dSLuis R. Rodriguez  * @freq_khz: frequency we are inquiring about
12620c7dc45dSLuis R. Rodriguez  *
12630c7dc45dSLuis R. Rodriguez  * This lets us know if a specific frequency rule is or is not relevant to
12640c7dc45dSLuis R. Rodriguez  * a specific frequency's band. Bands are device specific and artificial
126564629b9dSVladimir Kondratiev  * definitions (the "2.4 GHz band", the "5 GHz band" and the "60GHz band"),
126664629b9dSVladimir Kondratiev  * however it is safe for now to assume that a frequency rule should not be
126764629b9dSVladimir Kondratiev  * part of a frequency's band if the start freq or end freq are off by more
126893183bdbSChaitanya Tata  * than 2 GHz for the 2.4 and 5 GHz bands, and by more than 20 GHz for the
126964629b9dSVladimir Kondratiev  * 60 GHz band.
12700c7dc45dSLuis R. Rodriguez  * This resolution can be lowered and should be considered as we add
12710c7dc45dSLuis R. Rodriguez  * regulatory rule support for other "bands".
12720c7dc45dSLuis R. Rodriguez  **/
12730c7dc45dSLuis R. Rodriguez static bool freq_in_rule_band(const struct ieee80211_freq_range *freq_range,
12740c7dc45dSLuis R. Rodriguez 			      u32 freq_khz)
12750c7dc45dSLuis R. Rodriguez {
12760c7dc45dSLuis R. Rodriguez #define ONE_GHZ_IN_KHZ	1000000
127764629b9dSVladimir Kondratiev 	/*
127864629b9dSVladimir Kondratiev 	 * From 802.11ad: directional multi-gigabit (DMG):
127964629b9dSVladimir Kondratiev 	 * Pertaining to operation in a frequency band containing a channel
128064629b9dSVladimir Kondratiev 	 * with the Channel starting frequency above 45 GHz.
128164629b9dSVladimir Kondratiev 	 */
128264629b9dSVladimir Kondratiev 	u32 limit = freq_khz > 45 * ONE_GHZ_IN_KHZ ?
128393183bdbSChaitanya Tata 			20 * ONE_GHZ_IN_KHZ : 2 * ONE_GHZ_IN_KHZ;
128464629b9dSVladimir Kondratiev 	if (abs(freq_khz - freq_range->start_freq_khz) <= limit)
12850c7dc45dSLuis R. Rodriguez 		return true;
128664629b9dSVladimir Kondratiev 	if (abs(freq_khz - freq_range->end_freq_khz) <= limit)
12870c7dc45dSLuis R. Rodriguez 		return true;
12880c7dc45dSLuis R. Rodriguez 	return false;
12890c7dc45dSLuis R. Rodriguez #undef ONE_GHZ_IN_KHZ
12900c7dc45dSLuis R. Rodriguez }
12910c7dc45dSLuis R. Rodriguez 
1292fb1fc7adSLuis R. Rodriguez /*
1293adbfb058SLuis R. Rodriguez  * Later on we can perhaps use the more restrictive DFS
1294adbfb058SLuis R. Rodriguez  * region but we don't have information for that yet so
1295adbfb058SLuis R. Rodriguez  * for now simply disallow conflicts.
1296adbfb058SLuis R. Rodriguez  */
1297adbfb058SLuis R. Rodriguez static enum nl80211_dfs_regions
1298adbfb058SLuis R. Rodriguez reg_intersect_dfs_region(const enum nl80211_dfs_regions dfs_region1,
1299adbfb058SLuis R. Rodriguez 			 const enum nl80211_dfs_regions dfs_region2)
1300adbfb058SLuis R. Rodriguez {
1301adbfb058SLuis R. Rodriguez 	if (dfs_region1 != dfs_region2)
1302adbfb058SLuis R. Rodriguez 		return NL80211_DFS_UNSET;
1303adbfb058SLuis R. Rodriguez 	return dfs_region1;
1304adbfb058SLuis R. Rodriguez }
1305adbfb058SLuis R. Rodriguez 
130608a75a88SIlan Peer static void reg_wmm_rules_intersect(const struct ieee80211_wmm_ac *wmm_ac1,
130708a75a88SIlan Peer 				    const struct ieee80211_wmm_ac *wmm_ac2,
130808a75a88SIlan Peer 				    struct ieee80211_wmm_ac *intersect)
130908a75a88SIlan Peer {
131008a75a88SIlan Peer 	intersect->cw_min = max_t(u16, wmm_ac1->cw_min, wmm_ac2->cw_min);
131108a75a88SIlan Peer 	intersect->cw_max = max_t(u16, wmm_ac1->cw_max, wmm_ac2->cw_max);
131208a75a88SIlan Peer 	intersect->cot = min_t(u16, wmm_ac1->cot, wmm_ac2->cot);
131308a75a88SIlan Peer 	intersect->aifsn = max_t(u8, wmm_ac1->aifsn, wmm_ac2->aifsn);
131408a75a88SIlan Peer }
131508a75a88SIlan Peer 
1316adbfb058SLuis R. Rodriguez /*
1317fb1fc7adSLuis R. Rodriguez  * Helper for regdom_intersect(), this does the real
1318fb1fc7adSLuis R. Rodriguez  * mathematical intersection fun
1319fb1fc7adSLuis R. Rodriguez  */
132097524820SJanusz Dziedzic static int reg_rules_intersect(const struct ieee80211_regdomain *rd1,
132197524820SJanusz Dziedzic 			       const struct ieee80211_regdomain *rd2,
132297524820SJanusz Dziedzic 			       const struct ieee80211_reg_rule *rule1,
13239c96477dSLuis R. Rodriguez 			       const struct ieee80211_reg_rule *rule2,
13249c96477dSLuis R. Rodriguez 			       struct ieee80211_reg_rule *intersected_rule)
13259c96477dSLuis R. Rodriguez {
13269c96477dSLuis R. Rodriguez 	const struct ieee80211_freq_range *freq_range1, *freq_range2;
13279c96477dSLuis R. Rodriguez 	struct ieee80211_freq_range *freq_range;
13289c96477dSLuis R. Rodriguez 	const struct ieee80211_power_rule *power_rule1, *power_rule2;
13299c96477dSLuis R. Rodriguez 	struct ieee80211_power_rule *power_rule;
133008a75a88SIlan Peer 	const struct ieee80211_wmm_rule *wmm_rule1, *wmm_rule2;
133108a75a88SIlan Peer 	struct ieee80211_wmm_rule *wmm_rule;
133297524820SJanusz Dziedzic 	u32 freq_diff, max_bandwidth1, max_bandwidth2;
13339c96477dSLuis R. Rodriguez 
13349c96477dSLuis R. Rodriguez 	freq_range1 = &rule1->freq_range;
13359c96477dSLuis R. Rodriguez 	freq_range2 = &rule2->freq_range;
13369c96477dSLuis R. Rodriguez 	freq_range = &intersected_rule->freq_range;
13379c96477dSLuis R. Rodriguez 
13389c96477dSLuis R. Rodriguez 	power_rule1 = &rule1->power_rule;
13399c96477dSLuis R. Rodriguez 	power_rule2 = &rule2->power_rule;
13409c96477dSLuis R. Rodriguez 	power_rule = &intersected_rule->power_rule;
13419c96477dSLuis R. Rodriguez 
134208a75a88SIlan Peer 	wmm_rule1 = &rule1->wmm_rule;
134308a75a88SIlan Peer 	wmm_rule2 = &rule2->wmm_rule;
134408a75a88SIlan Peer 	wmm_rule = &intersected_rule->wmm_rule;
134508a75a88SIlan Peer 
13469c96477dSLuis R. Rodriguez 	freq_range->start_freq_khz = max(freq_range1->start_freq_khz,
13479c96477dSLuis R. Rodriguez 					 freq_range2->start_freq_khz);
13489c96477dSLuis R. Rodriguez 	freq_range->end_freq_khz = min(freq_range1->end_freq_khz,
13499c96477dSLuis R. Rodriguez 				       freq_range2->end_freq_khz);
135097524820SJanusz Dziedzic 
135197524820SJanusz Dziedzic 	max_bandwidth1 = freq_range1->max_bandwidth_khz;
135297524820SJanusz Dziedzic 	max_bandwidth2 = freq_range2->max_bandwidth_khz;
135397524820SJanusz Dziedzic 
1354b0dfd2eaSJanusz Dziedzic 	if (rule1->flags & NL80211_RRF_AUTO_BW)
135597524820SJanusz Dziedzic 		max_bandwidth1 = reg_get_max_bandwidth(rd1, rule1);
1356b0dfd2eaSJanusz Dziedzic 	if (rule2->flags & NL80211_RRF_AUTO_BW)
135797524820SJanusz Dziedzic 		max_bandwidth2 = reg_get_max_bandwidth(rd2, rule2);
135897524820SJanusz Dziedzic 
135997524820SJanusz Dziedzic 	freq_range->max_bandwidth_khz = min(max_bandwidth1, max_bandwidth2);
13609c96477dSLuis R. Rodriguez 
1361b0dfd2eaSJanusz Dziedzic 	intersected_rule->flags = rule1->flags | rule2->flags;
1362b0dfd2eaSJanusz Dziedzic 
1363b0dfd2eaSJanusz Dziedzic 	/*
1364b0dfd2eaSJanusz Dziedzic 	 * In case NL80211_RRF_AUTO_BW requested for both rules
1365b0dfd2eaSJanusz Dziedzic 	 * set AUTO_BW in intersected rule also. Next we will
1366b0dfd2eaSJanusz Dziedzic 	 * calculate BW correctly in handle_channel function.
1367b0dfd2eaSJanusz Dziedzic 	 * In other case remove AUTO_BW flag while we calculate
1368b0dfd2eaSJanusz Dziedzic 	 * maximum bandwidth correctly and auto calculation is
1369b0dfd2eaSJanusz Dziedzic 	 * not required.
1370b0dfd2eaSJanusz Dziedzic 	 */
1371b0dfd2eaSJanusz Dziedzic 	if ((rule1->flags & NL80211_RRF_AUTO_BW) &&
1372b0dfd2eaSJanusz Dziedzic 	    (rule2->flags & NL80211_RRF_AUTO_BW))
1373b0dfd2eaSJanusz Dziedzic 		intersected_rule->flags |= NL80211_RRF_AUTO_BW;
1374b0dfd2eaSJanusz Dziedzic 	else
1375b0dfd2eaSJanusz Dziedzic 		intersected_rule->flags &= ~NL80211_RRF_AUTO_BW;
1376b0dfd2eaSJanusz Dziedzic 
13779c96477dSLuis R. Rodriguez 	freq_diff = freq_range->end_freq_khz - freq_range->start_freq_khz;
13789c96477dSLuis R. Rodriguez 	if (freq_range->max_bandwidth_khz > freq_diff)
13799c96477dSLuis R. Rodriguez 		freq_range->max_bandwidth_khz = freq_diff;
13809c96477dSLuis R. Rodriguez 
13819c96477dSLuis R. Rodriguez 	power_rule->max_eirp = min(power_rule1->max_eirp,
13829c96477dSLuis R. Rodriguez 		power_rule2->max_eirp);
13839c96477dSLuis R. Rodriguez 	power_rule->max_antenna_gain = min(power_rule1->max_antenna_gain,
13849c96477dSLuis R. Rodriguez 		power_rule2->max_antenna_gain);
13859c96477dSLuis R. Rodriguez 
1386089027e5SJanusz Dziedzic 	intersected_rule->dfs_cac_ms = max(rule1->dfs_cac_ms,
1387089027e5SJanusz Dziedzic 					   rule2->dfs_cac_ms);
1388089027e5SJanusz Dziedzic 
138908a75a88SIlan Peer 	if (rule1->has_wmm && rule2->has_wmm) {
139008a75a88SIlan Peer 		u8 ac;
139108a75a88SIlan Peer 
139208a75a88SIlan Peer 		for (ac = 0; ac < IEEE80211_NUM_ACS; ac++) {
139308a75a88SIlan Peer 			reg_wmm_rules_intersect(&wmm_rule1->client[ac],
139408a75a88SIlan Peer 						&wmm_rule2->client[ac],
139508a75a88SIlan Peer 						&wmm_rule->client[ac]);
139608a75a88SIlan Peer 			reg_wmm_rules_intersect(&wmm_rule1->ap[ac],
139708a75a88SIlan Peer 						&wmm_rule2->ap[ac],
139808a75a88SIlan Peer 						&wmm_rule->ap[ac]);
139908a75a88SIlan Peer 		}
140008a75a88SIlan Peer 
140108a75a88SIlan Peer 		intersected_rule->has_wmm = true;
140208a75a88SIlan Peer 	} else if (rule1->has_wmm) {
140308a75a88SIlan Peer 		*wmm_rule = *wmm_rule1;
140408a75a88SIlan Peer 		intersected_rule->has_wmm = true;
140508a75a88SIlan Peer 	} else if (rule2->has_wmm) {
140608a75a88SIlan Peer 		*wmm_rule = *wmm_rule2;
140708a75a88SIlan Peer 		intersected_rule->has_wmm = true;
140808a75a88SIlan Peer 	} else {
140908a75a88SIlan Peer 		intersected_rule->has_wmm = false;
141008a75a88SIlan Peer 	}
141108a75a88SIlan Peer 
14129c96477dSLuis R. Rodriguez 	if (!is_valid_reg_rule(intersected_rule))
14139c96477dSLuis R. Rodriguez 		return -EINVAL;
14149c96477dSLuis R. Rodriguez 
14159c96477dSLuis R. Rodriguez 	return 0;
14169c96477dSLuis R. Rodriguez }
14179c96477dSLuis R. Rodriguez 
1418a62a1aedSEliad Peller /* check whether old rule contains new rule */
1419a62a1aedSEliad Peller static bool rule_contains(struct ieee80211_reg_rule *r1,
1420a62a1aedSEliad Peller 			  struct ieee80211_reg_rule *r2)
1421a62a1aedSEliad Peller {
1422a62a1aedSEliad Peller 	/* for simplicity, currently consider only same flags */
1423a62a1aedSEliad Peller 	if (r1->flags != r2->flags)
1424a62a1aedSEliad Peller 		return false;
1425a62a1aedSEliad Peller 
1426a62a1aedSEliad Peller 	/* verify r1 is more restrictive */
1427a62a1aedSEliad Peller 	if ((r1->power_rule.max_antenna_gain >
1428a62a1aedSEliad Peller 	     r2->power_rule.max_antenna_gain) ||
1429a62a1aedSEliad Peller 	    r1->power_rule.max_eirp > r2->power_rule.max_eirp)
1430a62a1aedSEliad Peller 		return false;
1431a62a1aedSEliad Peller 
1432a62a1aedSEliad Peller 	/* make sure r2's range is contained within r1 */
1433a62a1aedSEliad Peller 	if (r1->freq_range.start_freq_khz > r2->freq_range.start_freq_khz ||
1434a62a1aedSEliad Peller 	    r1->freq_range.end_freq_khz < r2->freq_range.end_freq_khz)
1435a62a1aedSEliad Peller 		return false;
1436a62a1aedSEliad Peller 
1437a62a1aedSEliad Peller 	/* and finally verify that r1.max_bw >= r2.max_bw */
1438a62a1aedSEliad Peller 	if (r1->freq_range.max_bandwidth_khz <
1439a62a1aedSEliad Peller 	    r2->freq_range.max_bandwidth_khz)
1440a62a1aedSEliad Peller 		return false;
1441a62a1aedSEliad Peller 
1442a62a1aedSEliad Peller 	return true;
1443a62a1aedSEliad Peller }
1444a62a1aedSEliad Peller 
1445a62a1aedSEliad Peller /* add or extend current rules. do nothing if rule is already contained */
1446a62a1aedSEliad Peller static void add_rule(struct ieee80211_reg_rule *rule,
1447a62a1aedSEliad Peller 		     struct ieee80211_reg_rule *reg_rules, u32 *n_rules)
1448a62a1aedSEliad Peller {
1449a62a1aedSEliad Peller 	struct ieee80211_reg_rule *tmp_rule;
1450a62a1aedSEliad Peller 	int i;
1451a62a1aedSEliad Peller 
1452a62a1aedSEliad Peller 	for (i = 0; i < *n_rules; i++) {
1453a62a1aedSEliad Peller 		tmp_rule = &reg_rules[i];
1454a62a1aedSEliad Peller 		/* rule is already contained - do nothing */
1455a62a1aedSEliad Peller 		if (rule_contains(tmp_rule, rule))
1456a62a1aedSEliad Peller 			return;
1457a62a1aedSEliad Peller 
1458a62a1aedSEliad Peller 		/* extend rule if possible */
1459a62a1aedSEliad Peller 		if (rule_contains(rule, tmp_rule)) {
1460a62a1aedSEliad Peller 			memcpy(tmp_rule, rule, sizeof(*rule));
1461a62a1aedSEliad Peller 			return;
1462a62a1aedSEliad Peller 		}
1463a62a1aedSEliad Peller 	}
1464a62a1aedSEliad Peller 
1465a62a1aedSEliad Peller 	memcpy(&reg_rules[*n_rules], rule, sizeof(*rule));
1466a62a1aedSEliad Peller 	(*n_rules)++;
1467a62a1aedSEliad Peller }
1468a62a1aedSEliad Peller 
14699c96477dSLuis R. Rodriguez /**
14709c96477dSLuis R. Rodriguez  * regdom_intersect - do the intersection between two regulatory domains
14719c96477dSLuis R. Rodriguez  * @rd1: first regulatory domain
14729c96477dSLuis R. Rodriguez  * @rd2: second regulatory domain
14739c96477dSLuis R. Rodriguez  *
14749c96477dSLuis R. Rodriguez  * Use this function to get the intersection between two regulatory domains.
14759c96477dSLuis R. Rodriguez  * Once completed we will mark the alpha2 for the rd as intersected, "98",
14769c96477dSLuis R. Rodriguez  * as no one single alpha2 can represent this regulatory domain.
14779c96477dSLuis R. Rodriguez  *
14789c96477dSLuis R. Rodriguez  * Returns a pointer to the regulatory domain structure which will hold the
14799c96477dSLuis R. Rodriguez  * resulting intersection of rules between rd1 and rd2. We will
14809c96477dSLuis R. Rodriguez  * kzalloc() this structure for you.
14819c96477dSLuis R. Rodriguez  */
14821a919318SJohannes Berg static struct ieee80211_regdomain *
14831a919318SJohannes Berg regdom_intersect(const struct ieee80211_regdomain *rd1,
14849c96477dSLuis R. Rodriguez 		 const struct ieee80211_regdomain *rd2)
14859c96477dSLuis R. Rodriguez {
14869f8c7136SGustavo A. R. Silva 	int r;
14879c96477dSLuis R. Rodriguez 	unsigned int x, y;
1488a62a1aedSEliad Peller 	unsigned int num_rules = 0;
14899c96477dSLuis R. Rodriguez 	const struct ieee80211_reg_rule *rule1, *rule2;
1490a62a1aedSEliad Peller 	struct ieee80211_reg_rule intersected_rule;
14919c96477dSLuis R. Rodriguez 	struct ieee80211_regdomain *rd;
14929c96477dSLuis R. Rodriguez 
14939c96477dSLuis R. Rodriguez 	if (!rd1 || !rd2)
14949c96477dSLuis R. Rodriguez 		return NULL;
14959c96477dSLuis R. Rodriguez 
1496fb1fc7adSLuis R. Rodriguez 	/*
1497fb1fc7adSLuis R. Rodriguez 	 * First we get a count of the rules we'll need, then we actually
14989c96477dSLuis R. Rodriguez 	 * build them. This is to so we can malloc() and free() a
14999c96477dSLuis R. Rodriguez 	 * regdomain once. The reason we use reg_rules_intersect() here
15009c96477dSLuis R. Rodriguez 	 * is it will return -EINVAL if the rule computed makes no sense.
1501fb1fc7adSLuis R. Rodriguez 	 * All rules that do check out OK are valid.
1502fb1fc7adSLuis R. Rodriguez 	 */
15039c96477dSLuis R. Rodriguez 
15049c96477dSLuis R. Rodriguez 	for (x = 0; x < rd1->n_reg_rules; x++) {
15059c96477dSLuis R. Rodriguez 		rule1 = &rd1->reg_rules[x];
15069c96477dSLuis R. Rodriguez 		for (y = 0; y < rd2->n_reg_rules; y++) {
15079c96477dSLuis R. Rodriguez 			rule2 = &rd2->reg_rules[y];
150897524820SJanusz Dziedzic 			if (!reg_rules_intersect(rd1, rd2, rule1, rule2,
1509a62a1aedSEliad Peller 						 &intersected_rule))
15109c96477dSLuis R. Rodriguez 				num_rules++;
15119c96477dSLuis R. Rodriguez 		}
15129c96477dSLuis R. Rodriguez 	}
15139c96477dSLuis R. Rodriguez 
15149c96477dSLuis R. Rodriguez 	if (!num_rules)
15159c96477dSLuis R. Rodriguez 		return NULL;
15169c96477dSLuis R. Rodriguez 
15179f8c7136SGustavo A. R. Silva 	rd = kzalloc(struct_size(rd, reg_rules, num_rules), GFP_KERNEL);
15189c96477dSLuis R. Rodriguez 	if (!rd)
15199c96477dSLuis R. Rodriguez 		return NULL;
15209c96477dSLuis R. Rodriguez 
1521a62a1aedSEliad Peller 	for (x = 0; x < rd1->n_reg_rules; x++) {
15229c96477dSLuis R. Rodriguez 		rule1 = &rd1->reg_rules[x];
1523a62a1aedSEliad Peller 		for (y = 0; y < rd2->n_reg_rules; y++) {
15249c96477dSLuis R. Rodriguez 			rule2 = &rd2->reg_rules[y];
152597524820SJanusz Dziedzic 			r = reg_rules_intersect(rd1, rd2, rule1, rule2,
1526a62a1aedSEliad Peller 						&intersected_rule);
1527fb1fc7adSLuis R. Rodriguez 			/*
1528fb1fc7adSLuis R. Rodriguez 			 * No need to memset here the intersected rule here as
1529fb1fc7adSLuis R. Rodriguez 			 * we're not using the stack anymore
1530fb1fc7adSLuis R. Rodriguez 			 */
15319c96477dSLuis R. Rodriguez 			if (r)
15329c96477dSLuis R. Rodriguez 				continue;
1533a62a1aedSEliad Peller 
1534a62a1aedSEliad Peller 			add_rule(&intersected_rule, rd->reg_rules,
1535a62a1aedSEliad Peller 				 &rd->n_reg_rules);
15369c96477dSLuis R. Rodriguez 		}
15379c96477dSLuis R. Rodriguez 	}
15389c96477dSLuis R. Rodriguez 
15399c96477dSLuis R. Rodriguez 	rd->alpha2[0] = '9';
15409c96477dSLuis R. Rodriguez 	rd->alpha2[1] = '8';
1541adbfb058SLuis R. Rodriguez 	rd->dfs_region = reg_intersect_dfs_region(rd1->dfs_region,
1542adbfb058SLuis R. Rodriguez 						  rd2->dfs_region);
15439c96477dSLuis R. Rodriguez 
15449c96477dSLuis R. Rodriguez 	return rd;
15459c96477dSLuis R. Rodriguez }
15469c96477dSLuis R. Rodriguez 
1547fb1fc7adSLuis R. Rodriguez /*
1548fb1fc7adSLuis R. Rodriguez  * XXX: add support for the rest of enum nl80211_reg_rule_flags, we may
1549fb1fc7adSLuis R. Rodriguez  * want to just have the channel structure use these
1550fb1fc7adSLuis R. Rodriguez  */
1551b2e1b302SLuis R. Rodriguez static u32 map_regdom_flags(u32 rd_flags)
1552b2e1b302SLuis R. Rodriguez {
1553b2e1b302SLuis R. Rodriguez 	u32 channel_flags = 0;
15548fe02e16SLuis R. Rodriguez 	if (rd_flags & NL80211_RRF_NO_IR_ALL)
15558fe02e16SLuis R. Rodriguez 		channel_flags |= IEEE80211_CHAN_NO_IR;
1556b2e1b302SLuis R. Rodriguez 	if (rd_flags & NL80211_RRF_DFS)
1557b2e1b302SLuis R. Rodriguez 		channel_flags |= IEEE80211_CHAN_RADAR;
155803f6b084SSeth Forshee 	if (rd_flags & NL80211_RRF_NO_OFDM)
155903f6b084SSeth Forshee 		channel_flags |= IEEE80211_CHAN_NO_OFDM;
1560570dbde1SDavid Spinadel 	if (rd_flags & NL80211_RRF_NO_OUTDOOR)
1561570dbde1SDavid Spinadel 		channel_flags |= IEEE80211_CHAN_INDOOR_ONLY;
156206f207fcSArik Nemtsov 	if (rd_flags & NL80211_RRF_IR_CONCURRENT)
156306f207fcSArik Nemtsov 		channel_flags |= IEEE80211_CHAN_IR_CONCURRENT;
1564a6d4a534SArik Nemtsov 	if (rd_flags & NL80211_RRF_NO_HT40MINUS)
1565a6d4a534SArik Nemtsov 		channel_flags |= IEEE80211_CHAN_NO_HT40MINUS;
1566a6d4a534SArik Nemtsov 	if (rd_flags & NL80211_RRF_NO_HT40PLUS)
1567a6d4a534SArik Nemtsov 		channel_flags |= IEEE80211_CHAN_NO_HT40PLUS;
1568a6d4a534SArik Nemtsov 	if (rd_flags & NL80211_RRF_NO_80MHZ)
1569a6d4a534SArik Nemtsov 		channel_flags |= IEEE80211_CHAN_NO_80MHZ;
1570a6d4a534SArik Nemtsov 	if (rd_flags & NL80211_RRF_NO_160MHZ)
1571a6d4a534SArik Nemtsov 		channel_flags |= IEEE80211_CHAN_NO_160MHZ;
1572b2e1b302SLuis R. Rodriguez 	return channel_flags;
1573b2e1b302SLuis R. Rodriguez }
1574b2e1b302SLuis R. Rodriguez 
1575361c9c8bSJohannes Berg static const struct ieee80211_reg_rule *
157649172874SMichal Sojka freq_reg_info_regd(u32 center_freq,
15774edd5698SMatthias May 		   const struct ieee80211_regdomain *regd, u32 bw)
15788318d78aSJohannes Berg {
15798318d78aSJohannes Berg 	int i;
15800c7dc45dSLuis R. Rodriguez 	bool band_rule_found = false;
1581038659e7SLuis R. Rodriguez 	bool bw_fits = false;
1582038659e7SLuis R. Rodriguez 
15833e0c3ff3SLuis R. Rodriguez 	if (!regd)
1584361c9c8bSJohannes Berg 		return ERR_PTR(-EINVAL);
1585b2e1b302SLuis R. Rodriguez 
15863e0c3ff3SLuis R. Rodriguez 	for (i = 0; i < regd->n_reg_rules; i++) {
1587b2e1b302SLuis R. Rodriguez 		const struct ieee80211_reg_rule *rr;
1588b2e1b302SLuis R. Rodriguez 		const struct ieee80211_freq_range *fr = NULL;
1589b2e1b302SLuis R. Rodriguez 
15903e0c3ff3SLuis R. Rodriguez 		rr = &regd->reg_rules[i];
1591b2e1b302SLuis R. Rodriguez 		fr = &rr->freq_range;
15920c7dc45dSLuis R. Rodriguez 
1593fb1fc7adSLuis R. Rodriguez 		/*
1594fb1fc7adSLuis R. Rodriguez 		 * We only need to know if one frequency rule was
15950c7dc45dSLuis R. Rodriguez 		 * was in center_freq's band, that's enough, so lets
1596fb1fc7adSLuis R. Rodriguez 		 * not overwrite it once found
1597fb1fc7adSLuis R. Rodriguez 		 */
15980c7dc45dSLuis R. Rodriguez 		if (!band_rule_found)
15990c7dc45dSLuis R. Rodriguez 			band_rule_found = freq_in_rule_band(fr, center_freq);
16000c7dc45dSLuis R. Rodriguez 
16014787cfa0SRafał Miłecki 		bw_fits = cfg80211_does_bw_fit_range(fr, center_freq, bw);
16020c7dc45dSLuis R. Rodriguez 
1603361c9c8bSJohannes Berg 		if (band_rule_found && bw_fits)
1604361c9c8bSJohannes Berg 			return rr;
16058318d78aSJohannes Berg 	}
16068318d78aSJohannes Berg 
16070c7dc45dSLuis R. Rodriguez 	if (!band_rule_found)
1608361c9c8bSJohannes Berg 		return ERR_PTR(-ERANGE);
16090c7dc45dSLuis R. Rodriguez 
1610361c9c8bSJohannes Berg 	return ERR_PTR(-EINVAL);
1611b2e1b302SLuis R. Rodriguez }
1612b2e1b302SLuis R. Rodriguez 
16138de1c63bSJohannes Berg static const struct ieee80211_reg_rule *
16148de1c63bSJohannes Berg __freq_reg_info(struct wiphy *wiphy, u32 center_freq, u32 min_bw)
16154edd5698SMatthias May {
16164edd5698SMatthias May 	const struct ieee80211_regdomain *regd = reg_get_regdomain(wiphy);
16174edd5698SMatthias May 	const struct ieee80211_reg_rule *reg_rule = NULL;
16184edd5698SMatthias May 	u32 bw;
16194edd5698SMatthias May 
16204edd5698SMatthias May 	for (bw = MHZ_TO_KHZ(20); bw >= min_bw; bw = bw / 2) {
162149172874SMichal Sojka 		reg_rule = freq_reg_info_regd(center_freq, regd, bw);
16224edd5698SMatthias May 		if (!IS_ERR(reg_rule))
16234edd5698SMatthias May 			return reg_rule;
16244edd5698SMatthias May 	}
16254edd5698SMatthias May 
16264edd5698SMatthias May 	return reg_rule;
16274edd5698SMatthias May }
16284edd5698SMatthias May 
1629361c9c8bSJohannes Berg const struct ieee80211_reg_rule *freq_reg_info(struct wiphy *wiphy,
1630361c9c8bSJohannes Berg 					       u32 center_freq)
16311fa25e41SLuis R. Rodriguez {
16324edd5698SMatthias May 	return __freq_reg_info(wiphy, center_freq, MHZ_TO_KHZ(20));
16331fa25e41SLuis R. Rodriguez }
16344f366c5dSJohn W. Linville EXPORT_SYMBOL(freq_reg_info);
1635b2e1b302SLuis R. Rodriguez 
1636034c6d6eSLuis R. Rodriguez const char *reg_initiator_name(enum nl80211_reg_initiator initiator)
1637926a0a09SLuis R. Rodriguez {
1638926a0a09SLuis R. Rodriguez 	switch (initiator) {
1639926a0a09SLuis R. Rodriguez 	case NL80211_REGDOM_SET_BY_CORE:
1640034c6d6eSLuis R. Rodriguez 		return "core";
1641926a0a09SLuis R. Rodriguez 	case NL80211_REGDOM_SET_BY_USER:
1642034c6d6eSLuis R. Rodriguez 		return "user";
1643926a0a09SLuis R. Rodriguez 	case NL80211_REGDOM_SET_BY_DRIVER:
1644034c6d6eSLuis R. Rodriguez 		return "driver";
1645926a0a09SLuis R. Rodriguez 	case NL80211_REGDOM_SET_BY_COUNTRY_IE:
16468db0c433SToke Høiland-Jørgensen 		return "country element";
1647926a0a09SLuis R. Rodriguez 	default:
1648926a0a09SLuis R. Rodriguez 		WARN_ON(1);
1649034c6d6eSLuis R. Rodriguez 		return "bug";
1650926a0a09SLuis R. Rodriguez 	}
1651926a0a09SLuis R. Rodriguez }
1652034c6d6eSLuis R. Rodriguez EXPORT_SYMBOL(reg_initiator_name);
1653e702d3cfSLuis R. Rodriguez 
16541aeb135fSMichal Sojka static uint32_t reg_rule_to_chan_bw_flags(const struct ieee80211_regdomain *regd,
16551aeb135fSMichal Sojka 					  const struct ieee80211_reg_rule *reg_rule,
16561aeb135fSMichal Sojka 					  const struct ieee80211_channel *chan)
16571aeb135fSMichal Sojka {
16581aeb135fSMichal Sojka 	const struct ieee80211_freq_range *freq_range = NULL;
16591aeb135fSMichal Sojka 	u32 max_bandwidth_khz, bw_flags = 0;
16601aeb135fSMichal Sojka 
16611aeb135fSMichal Sojka 	freq_range = &reg_rule->freq_range;
16621aeb135fSMichal Sojka 
16631aeb135fSMichal Sojka 	max_bandwidth_khz = freq_range->max_bandwidth_khz;
16641aeb135fSMichal Sojka 	/* Check if auto calculation requested */
16651aeb135fSMichal Sojka 	if (reg_rule->flags & NL80211_RRF_AUTO_BW)
16661aeb135fSMichal Sojka 		max_bandwidth_khz = reg_get_max_bandwidth(regd, reg_rule);
16671aeb135fSMichal Sojka 
16681aeb135fSMichal Sojka 	/* If we get a reg_rule we can assume that at least 5Mhz fit */
16694787cfa0SRafał Miłecki 	if (!cfg80211_does_bw_fit_range(freq_range,
16704787cfa0SRafał Miłecki 					MHZ_TO_KHZ(chan->center_freq),
16711aeb135fSMichal Sojka 					MHZ_TO_KHZ(10)))
16721aeb135fSMichal Sojka 		bw_flags |= IEEE80211_CHAN_NO_10MHZ;
16734787cfa0SRafał Miłecki 	if (!cfg80211_does_bw_fit_range(freq_range,
16744787cfa0SRafał Miłecki 					MHZ_TO_KHZ(chan->center_freq),
16751aeb135fSMichal Sojka 					MHZ_TO_KHZ(20)))
16761aeb135fSMichal Sojka 		bw_flags |= IEEE80211_CHAN_NO_20MHZ;
16771aeb135fSMichal Sojka 
16781aeb135fSMichal Sojka 	if (max_bandwidth_khz < MHZ_TO_KHZ(10))
16791aeb135fSMichal Sojka 		bw_flags |= IEEE80211_CHAN_NO_10MHZ;
16801aeb135fSMichal Sojka 	if (max_bandwidth_khz < MHZ_TO_KHZ(20))
16811aeb135fSMichal Sojka 		bw_flags |= IEEE80211_CHAN_NO_20MHZ;
16821aeb135fSMichal Sojka 	if (max_bandwidth_khz < MHZ_TO_KHZ(40))
16831aeb135fSMichal Sojka 		bw_flags |= IEEE80211_CHAN_NO_HT40;
16841aeb135fSMichal Sojka 	if (max_bandwidth_khz < MHZ_TO_KHZ(80))
16851aeb135fSMichal Sojka 		bw_flags |= IEEE80211_CHAN_NO_80MHZ;
16861aeb135fSMichal Sojka 	if (max_bandwidth_khz < MHZ_TO_KHZ(160))
16871aeb135fSMichal Sojka 		bw_flags |= IEEE80211_CHAN_NO_160MHZ;
16881aeb135fSMichal Sojka 	return bw_flags;
16891aeb135fSMichal Sojka }
16901aeb135fSMichal Sojka 
1691e33e2241SJohannes Berg /*
1692e33e2241SJohannes Berg  * Note that right now we assume the desired channel bandwidth
1693e33e2241SJohannes Berg  * is always 20 MHz for each individual channel (HT40 uses 20 MHz
1694e33e2241SJohannes Berg  * per channel, the primary and the extension channel).
1695038659e7SLuis R. Rodriguez  */
16967ca43d03SLuis R. Rodriguez static void handle_channel(struct wiphy *wiphy,
16977ca43d03SLuis R. Rodriguez 			   enum nl80211_reg_initiator initiator,
1698fdc9d7b2SJohannes Berg 			   struct ieee80211_channel *chan)
1699b2e1b302SLuis R. Rodriguez {
1700038659e7SLuis R. Rodriguez 	u32 flags, bw_flags = 0;
1701b2e1b302SLuis R. Rodriguez 	const struct ieee80211_reg_rule *reg_rule = NULL;
1702b2e1b302SLuis R. Rodriguez 	const struct ieee80211_power_rule *power_rule = NULL;
1703fe33eb39SLuis R. Rodriguez 	struct wiphy *request_wiphy = NULL;
1704c492db37SJohannes Berg 	struct regulatory_request *lr = get_last_request();
170597524820SJanusz Dziedzic 	const struct ieee80211_regdomain *regd;
1706a92a3ce7SLuis R. Rodriguez 
1707c492db37SJohannes Berg 	request_wiphy = wiphy_idx_to_wiphy(lr->wiphy_idx);
1708a92a3ce7SLuis R. Rodriguez 
1709a92a3ce7SLuis R. Rodriguez 	flags = chan->orig_flags;
1710b2e1b302SLuis R. Rodriguez 
1711361c9c8bSJohannes Berg 	reg_rule = freq_reg_info(wiphy, MHZ_TO_KHZ(chan->center_freq));
1712361c9c8bSJohannes Berg 	if (IS_ERR(reg_rule)) {
1713ca4ffe8fSLuis R. Rodriguez 		/*
1714ca4ffe8fSLuis R. Rodriguez 		 * We will disable all channels that do not match our
171525985edcSLucas De Marchi 		 * received regulatory rule unless the hint is coming
1716ca4ffe8fSLuis R. Rodriguez 		 * from a Country IE and the Country IE had no information
1717ca4ffe8fSLuis R. Rodriguez 		 * about a band. The IEEE 802.11 spec allows for an AP
1718ca4ffe8fSLuis R. Rodriguez 		 * to send only a subset of the regulatory rules allowed,
1719ca4ffe8fSLuis R. Rodriguez 		 * so an AP in the US that only supports 2.4 GHz may only send
1720ca4ffe8fSLuis R. Rodriguez 		 * a country IE with information for the 2.4 GHz band
1721ca4ffe8fSLuis R. Rodriguez 		 * while 5 GHz is still supported.
1722ca4ffe8fSLuis R. Rodriguez 		 */
1723ca4ffe8fSLuis R. Rodriguez 		if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE &&
1724361c9c8bSJohannes Berg 		    PTR_ERR(reg_rule) == -ERANGE)
17258318d78aSJohannes Berg 			return;
17268318d78aSJohannes Berg 
1727cc493e4fSLuis R. Rodriguez 		if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER &&
1728cc493e4fSLuis R. Rodriguez 		    request_wiphy && request_wiphy == wiphy &&
1729a2f73b6cSLuis R. Rodriguez 		    request_wiphy->regulatory_flags & REGULATORY_STRICT_REG) {
1730c799ba6eSJohannes Berg 			pr_debug("Disabling freq %d MHz for good\n",
1731cc493e4fSLuis R. Rodriguez 				 chan->center_freq);
1732cc493e4fSLuis R. Rodriguez 			chan->orig_flags |= IEEE80211_CHAN_DISABLED;
1733cc493e4fSLuis R. Rodriguez 			chan->flags = chan->orig_flags;
1734cc493e4fSLuis R. Rodriguez 		} else {
1735c799ba6eSJohannes Berg 			pr_debug("Disabling freq %d MHz\n",
1736cc493e4fSLuis R. Rodriguez 				 chan->center_freq);
1737990de49fSJohannes Berg 			chan->flags |= IEEE80211_CHAN_DISABLED;
1738cc493e4fSLuis R. Rodriguez 		}
1739ca4ffe8fSLuis R. Rodriguez 		return;
1740ca4ffe8fSLuis R. Rodriguez 	}
1741ca4ffe8fSLuis R. Rodriguez 
1742b0dfd2eaSJanusz Dziedzic 	regd = reg_get_regdomain(wiphy);
1743e702d3cfSLuis R. Rodriguez 
1744b2e1b302SLuis R. Rodriguez 	power_rule = &reg_rule->power_rule;
17451aeb135fSMichal Sojka 	bw_flags = reg_rule_to_chan_bw_flags(regd, reg_rule, chan);
1746b2e1b302SLuis R. Rodriguez 
1747c492db37SJohannes Berg 	if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER &&
1748806a9e39SLuis R. Rodriguez 	    request_wiphy && request_wiphy == wiphy &&
1749a2f73b6cSLuis R. Rodriguez 	    request_wiphy->regulatory_flags & REGULATORY_STRICT_REG) {
1750fb1fc7adSLuis R. Rodriguez 		/*
175125985edcSLucas De Marchi 		 * This guarantees the driver's requested regulatory domain
1752f976376dSLuis R. Rodriguez 		 * will always be used as a base for further regulatory
1753fb1fc7adSLuis R. Rodriguez 		 * settings
1754fb1fc7adSLuis R. Rodriguez 		 */
1755f976376dSLuis R. Rodriguez 		chan->flags = chan->orig_flags =
1756038659e7SLuis R. Rodriguez 			map_regdom_flags(reg_rule->flags) | bw_flags;
1757f976376dSLuis R. Rodriguez 		chan->max_antenna_gain = chan->orig_mag =
1758f976376dSLuis R. Rodriguez 			(int) MBI_TO_DBI(power_rule->max_antenna_gain);
1759279f0f55SFelix Fietkau 		chan->max_reg_power = chan->max_power = chan->orig_mpwr =
1760f976376dSLuis R. Rodriguez 			(int) MBM_TO_DBM(power_rule->max_eirp);
17614f267c11SJanusz Dziedzic 
17624f267c11SJanusz Dziedzic 		if (chan->flags & IEEE80211_CHAN_RADAR) {
17634f267c11SJanusz Dziedzic 			chan->dfs_cac_ms = IEEE80211_DFS_MIN_CAC_TIME_MS;
17644f267c11SJanusz Dziedzic 			if (reg_rule->dfs_cac_ms)
17654f267c11SJanusz Dziedzic 				chan->dfs_cac_ms = reg_rule->dfs_cac_ms;
17664f267c11SJanusz Dziedzic 		}
17674f267c11SJanusz Dziedzic 
1768f976376dSLuis R. Rodriguez 		return;
1769f976376dSLuis R. Rodriguez 	}
1770f976376dSLuis R. Rodriguez 
177104f39047SSimon Wunderlich 	chan->dfs_state = NL80211_DFS_USABLE;
177204f39047SSimon Wunderlich 	chan->dfs_state_entered = jiffies;
177304f39047SSimon Wunderlich 
1774aa3d7eefSRajkumar Manoharan 	chan->beacon_found = false;
1775038659e7SLuis R. Rodriguez 	chan->flags = flags | bw_flags | map_regdom_flags(reg_rule->flags);
17761a919318SJohannes Berg 	chan->max_antenna_gain =
17771a919318SJohannes Berg 		min_t(int, chan->orig_mag,
17781a919318SJohannes Berg 		      MBI_TO_DBI(power_rule->max_antenna_gain));
1779eccc068eSHong Wu 	chan->max_reg_power = (int) MBM_TO_DBM(power_rule->max_eirp);
1780089027e5SJanusz Dziedzic 
1781089027e5SJanusz Dziedzic 	if (chan->flags & IEEE80211_CHAN_RADAR) {
1782089027e5SJanusz Dziedzic 		if (reg_rule->dfs_cac_ms)
1783089027e5SJanusz Dziedzic 			chan->dfs_cac_ms = reg_rule->dfs_cac_ms;
1784089027e5SJanusz Dziedzic 		else
1785089027e5SJanusz Dziedzic 			chan->dfs_cac_ms = IEEE80211_DFS_MIN_CAC_TIME_MS;
1786089027e5SJanusz Dziedzic 	}
1787089027e5SJanusz Dziedzic 
17885e31fc08SStanislaw Gruszka 	if (chan->orig_mpwr) {
17895e31fc08SStanislaw Gruszka 		/*
1790a09a85a0SLuis R. Rodriguez 		 * Devices that use REGULATORY_COUNTRY_IE_FOLLOW_POWER
1791a09a85a0SLuis R. Rodriguez 		 * will always follow the passed country IE power settings.
17925e31fc08SStanislaw Gruszka 		 */
17935e31fc08SStanislaw Gruszka 		if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE &&
1794a09a85a0SLuis R. Rodriguez 		    wiphy->regulatory_flags & REGULATORY_COUNTRY_IE_FOLLOW_POWER)
17955e31fc08SStanislaw Gruszka 			chan->max_power = chan->max_reg_power;
17965e31fc08SStanislaw Gruszka 		else
17975e31fc08SStanislaw Gruszka 			chan->max_power = min(chan->orig_mpwr,
17985e31fc08SStanislaw Gruszka 					      chan->max_reg_power);
17995e31fc08SStanislaw Gruszka 	} else
18005e31fc08SStanislaw Gruszka 		chan->max_power = chan->max_reg_power;
18018318d78aSJohannes Berg }
18028318d78aSJohannes Berg 
18037ca43d03SLuis R. Rodriguez static void handle_band(struct wiphy *wiphy,
1804fdc9d7b2SJohannes Berg 			enum nl80211_reg_initiator initiator,
1805fdc9d7b2SJohannes Berg 			struct ieee80211_supported_band *sband)
18068318d78aSJohannes Berg {
1807a92a3ce7SLuis R. Rodriguez 	unsigned int i;
1808a92a3ce7SLuis R. Rodriguez 
1809fdc9d7b2SJohannes Berg 	if (!sband)
1810fdc9d7b2SJohannes Berg 		return;
18118318d78aSJohannes Berg 
18128318d78aSJohannes Berg 	for (i = 0; i < sband->n_channels; i++)
1813fdc9d7b2SJohannes Berg 		handle_channel(wiphy, initiator, &sband->channels[i]);
18148318d78aSJohannes Berg }
18158318d78aSJohannes Berg 
181657b5ce07SLuis R. Rodriguez static bool reg_request_cell_base(struct regulatory_request *request)
181757b5ce07SLuis R. Rodriguez {
181857b5ce07SLuis R. Rodriguez 	if (request->initiator != NL80211_REGDOM_SET_BY_USER)
181957b5ce07SLuis R. Rodriguez 		return false;
18201a919318SJohannes Berg 	return request->user_reg_hint_type == NL80211_USER_REG_HINT_CELL_BASE;
182157b5ce07SLuis R. Rodriguez }
182257b5ce07SLuis R. Rodriguez 
182357b5ce07SLuis R. Rodriguez bool reg_last_request_cell_base(void)
182457b5ce07SLuis R. Rodriguez {
182538fd2143SJohannes Berg 	return reg_request_cell_base(get_last_request());
182657b5ce07SLuis R. Rodriguez }
182757b5ce07SLuis R. Rodriguez 
182894fc661fSIlan Peer #ifdef CONFIG_CFG80211_REG_CELLULAR_HINTS
182957b5ce07SLuis R. Rodriguez /* Core specific check */
18302f92212bSJohannes Berg static enum reg_request_treatment
18312f92212bSJohannes Berg reg_ignore_cell_hint(struct regulatory_request *pending_request)
183257b5ce07SLuis R. Rodriguez {
1833c492db37SJohannes Berg 	struct regulatory_request *lr = get_last_request();
183457b5ce07SLuis R. Rodriguez 
183557b5ce07SLuis R. Rodriguez 	if (!reg_num_devs_support_basehint)
18362f92212bSJohannes Berg 		return REG_REQ_IGNORE;
183757b5ce07SLuis R. Rodriguez 
1838c492db37SJohannes Berg 	if (reg_request_cell_base(lr) &&
18391a919318SJohannes Berg 	    !regdom_changes(pending_request->alpha2))
18402f92212bSJohannes Berg 		return REG_REQ_ALREADY_SET;
18411a919318SJohannes Berg 
18422f92212bSJohannes Berg 	return REG_REQ_OK;
184357b5ce07SLuis R. Rodriguez }
184457b5ce07SLuis R. Rodriguez 
184557b5ce07SLuis R. Rodriguez /* Device specific check */
184657b5ce07SLuis R. Rodriguez static bool reg_dev_ignore_cell_hint(struct wiphy *wiphy)
184757b5ce07SLuis R. Rodriguez {
18481a919318SJohannes Berg 	return !(wiphy->features & NL80211_FEATURE_CELL_BASE_REG_HINTS);
184957b5ce07SLuis R. Rodriguez }
185057b5ce07SLuis R. Rodriguez #else
1851a515de66SJohannes Berg static enum reg_request_treatment
1852a515de66SJohannes Berg reg_ignore_cell_hint(struct regulatory_request *pending_request)
185357b5ce07SLuis R. Rodriguez {
18542f92212bSJohannes Berg 	return REG_REQ_IGNORE;
185557b5ce07SLuis R. Rodriguez }
18561a919318SJohannes Berg 
18571a919318SJohannes Berg static bool reg_dev_ignore_cell_hint(struct wiphy *wiphy)
185857b5ce07SLuis R. Rodriguez {
185957b5ce07SLuis R. Rodriguez 	return true;
186057b5ce07SLuis R. Rodriguez }
186157b5ce07SLuis R. Rodriguez #endif
186257b5ce07SLuis R. Rodriguez 
1863fa1fb9cbSLuis R. Rodriguez static bool wiphy_strict_alpha2_regd(struct wiphy *wiphy)
1864fa1fb9cbSLuis R. Rodriguez {
1865a2f73b6cSLuis R. Rodriguez 	if (wiphy->regulatory_flags & REGULATORY_STRICT_REG &&
1866a2f73b6cSLuis R. Rodriguez 	    !(wiphy->regulatory_flags & REGULATORY_CUSTOM_REG))
1867fa1fb9cbSLuis R. Rodriguez 		return true;
1868fa1fb9cbSLuis R. Rodriguez 	return false;
1869fa1fb9cbSLuis R. Rodriguez }
187057b5ce07SLuis R. Rodriguez 
18717db90f4aSLuis R. Rodriguez static bool ignore_reg_update(struct wiphy *wiphy,
18727db90f4aSLuis R. Rodriguez 			      enum nl80211_reg_initiator initiator)
187314b9815aSLuis R. Rodriguez {
1874c492db37SJohannes Berg 	struct regulatory_request *lr = get_last_request();
1875c492db37SJohannes Berg 
1876b0d7aa59SJonathan Doron 	if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED)
1877b0d7aa59SJonathan Doron 		return true;
1878b0d7aa59SJonathan Doron 
1879c492db37SJohannes Berg 	if (!lr) {
1880c799ba6eSJohannes Berg 		pr_debug("Ignoring regulatory request set by %s since last_request is not set\n",
1881926a0a09SLuis R. Rodriguez 			 reg_initiator_name(initiator));
188214b9815aSLuis R. Rodriguez 		return true;
1883926a0a09SLuis R. Rodriguez 	}
1884926a0a09SLuis R. Rodriguez 
18857db90f4aSLuis R. Rodriguez 	if (initiator == NL80211_REGDOM_SET_BY_CORE &&
1886a2f73b6cSLuis R. Rodriguez 	    wiphy->regulatory_flags & REGULATORY_CUSTOM_REG) {
1887c799ba6eSJohannes Berg 		pr_debug("Ignoring regulatory request set by %s since the driver uses its own custom regulatory domain\n",
1888926a0a09SLuis R. Rodriguez 			 reg_initiator_name(initiator));
188914b9815aSLuis R. Rodriguez 		return true;
1890926a0a09SLuis R. Rodriguez 	}
1891926a0a09SLuis R. Rodriguez 
1892fb1fc7adSLuis R. Rodriguez 	/*
1893fb1fc7adSLuis R. Rodriguez 	 * wiphy->regd will be set once the device has its own
1894fb1fc7adSLuis R. Rodriguez 	 * desired regulatory domain set
1895fb1fc7adSLuis R. Rodriguez 	 */
1896fa1fb9cbSLuis R. Rodriguez 	if (wiphy_strict_alpha2_regd(wiphy) && !wiphy->regd &&
1897749b527bSLuis R. Rodriguez 	    initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE &&
1898c492db37SJohannes Berg 	    !is_world_regdom(lr->alpha2)) {
1899c799ba6eSJohannes Berg 		pr_debug("Ignoring regulatory request set by %s since the driver requires its own regulatory domain to be set first\n",
1900926a0a09SLuis R. Rodriguez 			 reg_initiator_name(initiator));
190114b9815aSLuis R. Rodriguez 		return true;
1902926a0a09SLuis R. Rodriguez 	}
1903926a0a09SLuis R. Rodriguez 
1904c492db37SJohannes Berg 	if (reg_request_cell_base(lr))
190557b5ce07SLuis R. Rodriguez 		return reg_dev_ignore_cell_hint(wiphy);
190657b5ce07SLuis R. Rodriguez 
190714b9815aSLuis R. Rodriguez 	return false;
190814b9815aSLuis R. Rodriguez }
190914b9815aSLuis R. Rodriguez 
19103195e489SLuis R. Rodriguez static bool reg_is_world_roaming(struct wiphy *wiphy)
19113195e489SLuis R. Rodriguez {
19123195e489SLuis R. Rodriguez 	const struct ieee80211_regdomain *cr = get_cfg80211_regdom();
19133195e489SLuis R. Rodriguez 	const struct ieee80211_regdomain *wr = get_wiphy_regdom(wiphy);
19143195e489SLuis R. Rodriguez 	struct regulatory_request *lr = get_last_request();
19153195e489SLuis R. Rodriguez 
19163195e489SLuis R. Rodriguez 	if (is_world_regdom(cr->alpha2) || (wr && is_world_regdom(wr->alpha2)))
19173195e489SLuis R. Rodriguez 		return true;
19183195e489SLuis R. Rodriguez 
19193195e489SLuis R. Rodriguez 	if (lr && lr->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE &&
1920a2f73b6cSLuis R. Rodriguez 	    wiphy->regulatory_flags & REGULATORY_CUSTOM_REG)
19213195e489SLuis R. Rodriguez 		return true;
19223195e489SLuis R. Rodriguez 
19233195e489SLuis R. Rodriguez 	return false;
19243195e489SLuis R. Rodriguez }
19253195e489SLuis R. Rodriguez 
19261a919318SJohannes Berg static void handle_reg_beacon(struct wiphy *wiphy, unsigned int chan_idx,
1927e38f8a7aSLuis R. Rodriguez 			      struct reg_beacon *reg_beacon)
1928e38f8a7aSLuis R. Rodriguez {
1929e38f8a7aSLuis R. Rodriguez 	struct ieee80211_supported_band *sband;
1930e38f8a7aSLuis R. Rodriguez 	struct ieee80211_channel *chan;
19316bad8766SLuis R. Rodriguez 	bool channel_changed = false;
19326bad8766SLuis R. Rodriguez 	struct ieee80211_channel chan_before;
1933e38f8a7aSLuis R. Rodriguez 
1934e38f8a7aSLuis R. Rodriguez 	sband = wiphy->bands[reg_beacon->chan.band];
1935e38f8a7aSLuis R. Rodriguez 	chan = &sband->channels[chan_idx];
1936e38f8a7aSLuis R. Rodriguez 
1937e38f8a7aSLuis R. Rodriguez 	if (likely(chan->center_freq != reg_beacon->chan.center_freq))
1938e38f8a7aSLuis R. Rodriguez 		return;
1939e38f8a7aSLuis R. Rodriguez 
19406bad8766SLuis R. Rodriguez 	if (chan->beacon_found)
19416bad8766SLuis R. Rodriguez 		return;
19426bad8766SLuis R. Rodriguez 
19436bad8766SLuis R. Rodriguez 	chan->beacon_found = true;
19446bad8766SLuis R. Rodriguez 
19450f500a5fSLuis R. Rodriguez 	if (!reg_is_world_roaming(wiphy))
19460f500a5fSLuis R. Rodriguez 		return;
19470f500a5fSLuis R. Rodriguez 
1948a2f73b6cSLuis R. Rodriguez 	if (wiphy->regulatory_flags & REGULATORY_DISABLE_BEACON_HINTS)
194937184244SLuis R. Rodriguez 		return;
195037184244SLuis R. Rodriguez 
1951a48a52b7SJohannes Berg 	chan_before = *chan;
19526bad8766SLuis R. Rodriguez 
19538fe02e16SLuis R. Rodriguez 	if (chan->flags & IEEE80211_CHAN_NO_IR) {
19548fe02e16SLuis R. Rodriguez 		chan->flags &= ~IEEE80211_CHAN_NO_IR;
19556bad8766SLuis R. Rodriguez 		channel_changed = true;
1956e38f8a7aSLuis R. Rodriguez 	}
1957e38f8a7aSLuis R. Rodriguez 
19586bad8766SLuis R. Rodriguez 	if (channel_changed)
19596bad8766SLuis R. Rodriguez 		nl80211_send_beacon_hint_event(wiphy, &chan_before, chan);
1960e38f8a7aSLuis R. Rodriguez }
1961e38f8a7aSLuis R. Rodriguez 
1962e38f8a7aSLuis R. Rodriguez /*
1963e38f8a7aSLuis R. Rodriguez  * Called when a scan on a wiphy finds a beacon on
1964e38f8a7aSLuis R. Rodriguez  * new channel
1965e38f8a7aSLuis R. Rodriguez  */
1966e38f8a7aSLuis R. Rodriguez static void wiphy_update_new_beacon(struct wiphy *wiphy,
1967e38f8a7aSLuis R. Rodriguez 				    struct reg_beacon *reg_beacon)
1968e38f8a7aSLuis R. Rodriguez {
1969e38f8a7aSLuis R. Rodriguez 	unsigned int i;
1970e38f8a7aSLuis R. Rodriguez 	struct ieee80211_supported_band *sband;
1971e38f8a7aSLuis R. Rodriguez 
1972e38f8a7aSLuis R. Rodriguez 	if (!wiphy->bands[reg_beacon->chan.band])
1973e38f8a7aSLuis R. Rodriguez 		return;
1974e38f8a7aSLuis R. Rodriguez 
1975e38f8a7aSLuis R. Rodriguez 	sband = wiphy->bands[reg_beacon->chan.band];
1976e38f8a7aSLuis R. Rodriguez 
1977e38f8a7aSLuis R. Rodriguez 	for (i = 0; i < sband->n_channels; i++)
1978e38f8a7aSLuis R. Rodriguez 		handle_reg_beacon(wiphy, i, reg_beacon);
1979e38f8a7aSLuis R. Rodriguez }
1980e38f8a7aSLuis R. Rodriguez 
1981e38f8a7aSLuis R. Rodriguez /*
1982e38f8a7aSLuis R. Rodriguez  * Called upon reg changes or a new wiphy is added
1983e38f8a7aSLuis R. Rodriguez  */
1984e38f8a7aSLuis R. Rodriguez static void wiphy_update_beacon_reg(struct wiphy *wiphy)
1985e38f8a7aSLuis R. Rodriguez {
1986e38f8a7aSLuis R. Rodriguez 	unsigned int i;
1987e38f8a7aSLuis R. Rodriguez 	struct ieee80211_supported_band *sband;
1988e38f8a7aSLuis R. Rodriguez 	struct reg_beacon *reg_beacon;
1989e38f8a7aSLuis R. Rodriguez 
1990e38f8a7aSLuis R. Rodriguez 	list_for_each_entry(reg_beacon, &reg_beacon_list, list) {
1991e38f8a7aSLuis R. Rodriguez 		if (!wiphy->bands[reg_beacon->chan.band])
1992e38f8a7aSLuis R. Rodriguez 			continue;
1993e38f8a7aSLuis R. Rodriguez 		sband = wiphy->bands[reg_beacon->chan.band];
1994e38f8a7aSLuis R. Rodriguez 		for (i = 0; i < sband->n_channels; i++)
1995e38f8a7aSLuis R. Rodriguez 			handle_reg_beacon(wiphy, i, reg_beacon);
1996e38f8a7aSLuis R. Rodriguez 	}
1997e38f8a7aSLuis R. Rodriguez }
1998e38f8a7aSLuis R. Rodriguez 
1999e38f8a7aSLuis R. Rodriguez /* Reap the advantages of previously found beacons */
2000e38f8a7aSLuis R. Rodriguez static void reg_process_beacons(struct wiphy *wiphy)
2001e38f8a7aSLuis R. Rodriguez {
2002b1ed8dddSLuis R. Rodriguez 	/*
2003b1ed8dddSLuis R. Rodriguez 	 * Means we are just firing up cfg80211, so no beacons would
2004b1ed8dddSLuis R. Rodriguez 	 * have been processed yet.
2005b1ed8dddSLuis R. Rodriguez 	 */
2006b1ed8dddSLuis R. Rodriguez 	if (!last_request)
2007b1ed8dddSLuis R. Rodriguez 		return;
2008e38f8a7aSLuis R. Rodriguez 	wiphy_update_beacon_reg(wiphy);
2009e38f8a7aSLuis R. Rodriguez }
2010e38f8a7aSLuis R. Rodriguez 
20111a919318SJohannes Berg static bool is_ht40_allowed(struct ieee80211_channel *chan)
2012038659e7SLuis R. Rodriguez {
2013038659e7SLuis R. Rodriguez 	if (!chan)
2014038659e7SLuis R. Rodriguez 		return false;
20151a919318SJohannes Berg 	if (chan->flags & IEEE80211_CHAN_DISABLED)
20161a919318SJohannes Berg 		return false;
20171a919318SJohannes Berg 	/* This would happen when regulatory rules disallow HT40 completely */
201855b183adSFelix Fietkau 	if ((chan->flags & IEEE80211_CHAN_NO_HT40) == IEEE80211_CHAN_NO_HT40)
201955b183adSFelix Fietkau 		return false;
202055b183adSFelix Fietkau 	return true;
2021038659e7SLuis R. Rodriguez }
2022038659e7SLuis R. Rodriguez 
2023038659e7SLuis R. Rodriguez static void reg_process_ht_flags_channel(struct wiphy *wiphy,
2024fdc9d7b2SJohannes Berg 					 struct ieee80211_channel *channel)
2025038659e7SLuis R. Rodriguez {
2026fdc9d7b2SJohannes Berg 	struct ieee80211_supported_band *sband = wiphy->bands[channel->band];
2027038659e7SLuis R. Rodriguez 	struct ieee80211_channel *channel_before = NULL, *channel_after = NULL;
20284e0854a7SEmmanuel Grumbach 	const struct ieee80211_regdomain *regd;
2029038659e7SLuis R. Rodriguez 	unsigned int i;
20304e0854a7SEmmanuel Grumbach 	u32 flags;
2031038659e7SLuis R. Rodriguez 
20321a919318SJohannes Berg 	if (!is_ht40_allowed(channel)) {
2033038659e7SLuis R. Rodriguez 		channel->flags |= IEEE80211_CHAN_NO_HT40;
2034038659e7SLuis R. Rodriguez 		return;
2035038659e7SLuis R. Rodriguez 	}
2036038659e7SLuis R. Rodriguez 
2037038659e7SLuis R. Rodriguez 	/*
2038038659e7SLuis R. Rodriguez 	 * We need to ensure the extension channels exist to
2039038659e7SLuis R. Rodriguez 	 * be able to use HT40- or HT40+, this finds them (or not)
2040038659e7SLuis R. Rodriguez 	 */
2041038659e7SLuis R. Rodriguez 	for (i = 0; i < sband->n_channels; i++) {
2042038659e7SLuis R. Rodriguez 		struct ieee80211_channel *c = &sband->channels[i];
20431a919318SJohannes Berg 
2044038659e7SLuis R. Rodriguez 		if (c->center_freq == (channel->center_freq - 20))
2045038659e7SLuis R. Rodriguez 			channel_before = c;
2046038659e7SLuis R. Rodriguez 		if (c->center_freq == (channel->center_freq + 20))
2047038659e7SLuis R. Rodriguez 			channel_after = c;
2048038659e7SLuis R. Rodriguez 	}
2049038659e7SLuis R. Rodriguez 
20504e0854a7SEmmanuel Grumbach 	flags = 0;
20514e0854a7SEmmanuel Grumbach 	regd = get_wiphy_regdom(wiphy);
20524e0854a7SEmmanuel Grumbach 	if (regd) {
20534e0854a7SEmmanuel Grumbach 		const struct ieee80211_reg_rule *reg_rule =
20544e0854a7SEmmanuel Grumbach 			freq_reg_info_regd(MHZ_TO_KHZ(channel->center_freq),
20554e0854a7SEmmanuel Grumbach 					   regd, MHZ_TO_KHZ(20));
20564e0854a7SEmmanuel Grumbach 
20574e0854a7SEmmanuel Grumbach 		if (!IS_ERR(reg_rule))
20584e0854a7SEmmanuel Grumbach 			flags = reg_rule->flags;
20594e0854a7SEmmanuel Grumbach 	}
20604e0854a7SEmmanuel Grumbach 
2061038659e7SLuis R. Rodriguez 	/*
2062038659e7SLuis R. Rodriguez 	 * Please note that this assumes target bandwidth is 20 MHz,
2063038659e7SLuis R. Rodriguez 	 * if that ever changes we also need to change the below logic
2064038659e7SLuis R. Rodriguez 	 * to include that as well.
2065038659e7SLuis R. Rodriguez 	 */
20664e0854a7SEmmanuel Grumbach 	if (!is_ht40_allowed(channel_before) ||
20674e0854a7SEmmanuel Grumbach 	    flags & NL80211_RRF_NO_HT40MINUS)
2068689da1b3SLuis R. Rodriguez 		channel->flags |= IEEE80211_CHAN_NO_HT40MINUS;
2069038659e7SLuis R. Rodriguez 	else
2070689da1b3SLuis R. Rodriguez 		channel->flags &= ~IEEE80211_CHAN_NO_HT40MINUS;
2071038659e7SLuis R. Rodriguez 
20724e0854a7SEmmanuel Grumbach 	if (!is_ht40_allowed(channel_after) ||
20734e0854a7SEmmanuel Grumbach 	    flags & NL80211_RRF_NO_HT40PLUS)
2074689da1b3SLuis R. Rodriguez 		channel->flags |= IEEE80211_CHAN_NO_HT40PLUS;
2075038659e7SLuis R. Rodriguez 	else
2076689da1b3SLuis R. Rodriguez 		channel->flags &= ~IEEE80211_CHAN_NO_HT40PLUS;
2077038659e7SLuis R. Rodriguez }
2078038659e7SLuis R. Rodriguez 
2079038659e7SLuis R. Rodriguez static void reg_process_ht_flags_band(struct wiphy *wiphy,
2080fdc9d7b2SJohannes Berg 				      struct ieee80211_supported_band *sband)
2081038659e7SLuis R. Rodriguez {
2082038659e7SLuis R. Rodriguez 	unsigned int i;
2083038659e7SLuis R. Rodriguez 
2084fdc9d7b2SJohannes Berg 	if (!sband)
2085fdc9d7b2SJohannes Berg 		return;
2086038659e7SLuis R. Rodriguez 
2087038659e7SLuis R. Rodriguez 	for (i = 0; i < sband->n_channels; i++)
2088fdc9d7b2SJohannes Berg 		reg_process_ht_flags_channel(wiphy, &sband->channels[i]);
2089038659e7SLuis R. Rodriguez }
2090038659e7SLuis R. Rodriguez 
2091038659e7SLuis R. Rodriguez static void reg_process_ht_flags(struct wiphy *wiphy)
2092038659e7SLuis R. Rodriguez {
209357fbcce3SJohannes Berg 	enum nl80211_band band;
2094038659e7SLuis R. Rodriguez 
2095038659e7SLuis R. Rodriguez 	if (!wiphy)
2096038659e7SLuis R. Rodriguez 		return;
2097038659e7SLuis R. Rodriguez 
209857fbcce3SJohannes Berg 	for (band = 0; band < NUM_NL80211_BANDS; band++)
2099fdc9d7b2SJohannes Berg 		reg_process_ht_flags_band(wiphy, wiphy->bands[band]);
2100038659e7SLuis R. Rodriguez }
2101038659e7SLuis R. Rodriguez 
21020e3802dbSLuis R. Rodriguez static void reg_call_notifier(struct wiphy *wiphy,
21030e3802dbSLuis R. Rodriguez 			      struct regulatory_request *request)
21040e3802dbSLuis R. Rodriguez {
21050e3802dbSLuis R. Rodriguez 	if (wiphy->reg_notifier)
21060e3802dbSLuis R. Rodriguez 		wiphy->reg_notifier(wiphy, request);
21070e3802dbSLuis R. Rodriguez }
21080e3802dbSLuis R. Rodriguez 
2109ad932f04SArik Nemtsov static bool reg_wdev_chan_valid(struct wiphy *wiphy, struct wireless_dev *wdev)
2110ad932f04SArik Nemtsov {
2111f43e5210SJohannes Berg 	struct cfg80211_chan_def chandef = {};
2112ad932f04SArik Nemtsov 	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
211320658702SArik Nemtsov 	enum nl80211_iftype iftype;
2114ad932f04SArik Nemtsov 
2115ad932f04SArik Nemtsov 	wdev_lock(wdev);
211620658702SArik Nemtsov 	iftype = wdev->iftype;
2117ad932f04SArik Nemtsov 
211820658702SArik Nemtsov 	/* make sure the interface is active */
2119ad932f04SArik Nemtsov 	if (!wdev->netdev || !netif_running(wdev->netdev))
212020658702SArik Nemtsov 		goto wdev_inactive_unlock;
2121ad932f04SArik Nemtsov 
212220658702SArik Nemtsov 	switch (iftype) {
2123ad932f04SArik Nemtsov 	case NL80211_IFTYPE_AP:
2124ad932f04SArik Nemtsov 	case NL80211_IFTYPE_P2P_GO:
2125ad932f04SArik Nemtsov 		if (!wdev->beacon_interval)
212620658702SArik Nemtsov 			goto wdev_inactive_unlock;
212720658702SArik Nemtsov 		chandef = wdev->chandef;
2128ad932f04SArik Nemtsov 		break;
2129185076d6SArik Nemtsov 	case NL80211_IFTYPE_ADHOC:
2130185076d6SArik Nemtsov 		if (!wdev->ssid_len)
213120658702SArik Nemtsov 			goto wdev_inactive_unlock;
213220658702SArik Nemtsov 		chandef = wdev->chandef;
2133185076d6SArik Nemtsov 		break;
2134ad932f04SArik Nemtsov 	case NL80211_IFTYPE_STATION:
2135ad932f04SArik Nemtsov 	case NL80211_IFTYPE_P2P_CLIENT:
2136ad932f04SArik Nemtsov 		if (!wdev->current_bss ||
2137ad932f04SArik Nemtsov 		    !wdev->current_bss->pub.channel)
213820658702SArik Nemtsov 			goto wdev_inactive_unlock;
2139ad932f04SArik Nemtsov 
214020658702SArik Nemtsov 		if (!rdev->ops->get_channel ||
214120658702SArik Nemtsov 		    rdev_get_channel(rdev, wdev, &chandef))
214220658702SArik Nemtsov 			cfg80211_chandef_create(&chandef,
214320658702SArik Nemtsov 						wdev->current_bss->pub.channel,
214420658702SArik Nemtsov 						NL80211_CHAN_NO_HT);
2145ad932f04SArik Nemtsov 		break;
2146ad932f04SArik Nemtsov 	case NL80211_IFTYPE_MONITOR:
2147ad932f04SArik Nemtsov 	case NL80211_IFTYPE_AP_VLAN:
2148ad932f04SArik Nemtsov 	case NL80211_IFTYPE_P2P_DEVICE:
2149ad932f04SArik Nemtsov 		/* no enforcement required */
2150ad932f04SArik Nemtsov 		break;
2151ad932f04SArik Nemtsov 	default:
2152ad932f04SArik Nemtsov 		/* others not implemented for now */
2153ad932f04SArik Nemtsov 		WARN_ON(1);
2154ad932f04SArik Nemtsov 		break;
2155ad932f04SArik Nemtsov 	}
2156ad932f04SArik Nemtsov 
2157ad932f04SArik Nemtsov 	wdev_unlock(wdev);
215820658702SArik Nemtsov 
215920658702SArik Nemtsov 	switch (iftype) {
216020658702SArik Nemtsov 	case NL80211_IFTYPE_AP:
216120658702SArik Nemtsov 	case NL80211_IFTYPE_P2P_GO:
216220658702SArik Nemtsov 	case NL80211_IFTYPE_ADHOC:
2163923b352fSArik Nemtsov 		return cfg80211_reg_can_beacon_relax(wiphy, &chandef, iftype);
216420658702SArik Nemtsov 	case NL80211_IFTYPE_STATION:
216520658702SArik Nemtsov 	case NL80211_IFTYPE_P2P_CLIENT:
216620658702SArik Nemtsov 		return cfg80211_chandef_usable(wiphy, &chandef,
216720658702SArik Nemtsov 					       IEEE80211_CHAN_DISABLED);
216820658702SArik Nemtsov 	default:
216920658702SArik Nemtsov 		break;
217020658702SArik Nemtsov 	}
217120658702SArik Nemtsov 
217220658702SArik Nemtsov 	return true;
217320658702SArik Nemtsov 
217420658702SArik Nemtsov wdev_inactive_unlock:
217520658702SArik Nemtsov 	wdev_unlock(wdev);
217620658702SArik Nemtsov 	return true;
2177ad932f04SArik Nemtsov }
2178ad932f04SArik Nemtsov 
2179ad932f04SArik Nemtsov static void reg_leave_invalid_chans(struct wiphy *wiphy)
2180ad932f04SArik Nemtsov {
2181ad932f04SArik Nemtsov 	struct wireless_dev *wdev;
2182ad932f04SArik Nemtsov 	struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
2183ad932f04SArik Nemtsov 
2184ad932f04SArik Nemtsov 	ASSERT_RTNL();
2185ad932f04SArik Nemtsov 
218653873f13SJohannes Berg 	list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list)
2187ad932f04SArik Nemtsov 		if (!reg_wdev_chan_valid(wiphy, wdev))
2188ad932f04SArik Nemtsov 			cfg80211_leave(rdev, wdev);
2189ad932f04SArik Nemtsov }
2190ad932f04SArik Nemtsov 
2191ad932f04SArik Nemtsov static void reg_check_chans_work(struct work_struct *work)
2192ad932f04SArik Nemtsov {
2193ad932f04SArik Nemtsov 	struct cfg80211_registered_device *rdev;
2194ad932f04SArik Nemtsov 
2195c799ba6eSJohannes Berg 	pr_debug("Verifying active interfaces after reg change\n");
2196ad932f04SArik Nemtsov 	rtnl_lock();
2197ad932f04SArik Nemtsov 
2198ad932f04SArik Nemtsov 	list_for_each_entry(rdev, &cfg80211_rdev_list, list)
2199ad932f04SArik Nemtsov 		if (!(rdev->wiphy.regulatory_flags &
2200ad932f04SArik Nemtsov 		      REGULATORY_IGNORE_STALE_KICKOFF))
2201ad932f04SArik Nemtsov 			reg_leave_invalid_chans(&rdev->wiphy);
2202ad932f04SArik Nemtsov 
2203ad932f04SArik Nemtsov 	rtnl_unlock();
2204ad932f04SArik Nemtsov }
2205ad932f04SArik Nemtsov 
2206ad932f04SArik Nemtsov static void reg_check_channels(void)
2207ad932f04SArik Nemtsov {
2208ad932f04SArik Nemtsov 	/*
2209ad932f04SArik Nemtsov 	 * Give usermode a chance to do something nicer (move to another
2210ad932f04SArik Nemtsov 	 * channel, orderly disconnection), before forcing a disconnection.
2211ad932f04SArik Nemtsov 	 */
2212ad932f04SArik Nemtsov 	mod_delayed_work(system_power_efficient_wq,
2213ad932f04SArik Nemtsov 			 &reg_check_chans,
2214ad932f04SArik Nemtsov 			 msecs_to_jiffies(REG_ENFORCE_GRACE_MS));
2215ad932f04SArik Nemtsov }
2216ad932f04SArik Nemtsov 
2217eac03e38SSven Neumann static void wiphy_update_regulatory(struct wiphy *wiphy,
22187db90f4aSLuis R. Rodriguez 				    enum nl80211_reg_initiator initiator)
22198318d78aSJohannes Berg {
222057fbcce3SJohannes Berg 	enum nl80211_band band;
2221c492db37SJohannes Berg 	struct regulatory_request *lr = get_last_request();
2222eac03e38SSven Neumann 
22230e3802dbSLuis R. Rodriguez 	if (ignore_reg_update(wiphy, initiator)) {
22240e3802dbSLuis R. Rodriguez 		/*
22250e3802dbSLuis R. Rodriguez 		 * Regulatory updates set by CORE are ignored for custom
22260e3802dbSLuis R. Rodriguez 		 * regulatory cards. Let us notify the changes to the driver,
22270e3802dbSLuis R. Rodriguez 		 * as some drivers used this to restore its orig_* reg domain.
22280e3802dbSLuis R. Rodriguez 		 */
22290e3802dbSLuis R. Rodriguez 		if (initiator == NL80211_REGDOM_SET_BY_CORE &&
2230e31f6456SAmar Singhal 		    wiphy->regulatory_flags & REGULATORY_CUSTOM_REG &&
2231e31f6456SAmar Singhal 		    !(wiphy->regulatory_flags &
2232e31f6456SAmar Singhal 		      REGULATORY_WIPHY_SELF_MANAGED))
22330e3802dbSLuis R. Rodriguez 			reg_call_notifier(wiphy, lr);
2234a203c2aaSSven Neumann 		return;
22350e3802dbSLuis R. Rodriguez 	}
2236a203c2aaSSven Neumann 
2237c492db37SJohannes Berg 	lr->dfs_region = get_cfg80211_regdom()->dfs_region;
2238b68e6b3bSLuis R. Rodriguez 
223957fbcce3SJohannes Berg 	for (band = 0; band < NUM_NL80211_BANDS; band++)
2240fdc9d7b2SJohannes Berg 		handle_band(wiphy, initiator, wiphy->bands[band]);
2241a203c2aaSSven Neumann 
2242e38f8a7aSLuis R. Rodriguez 	reg_process_beacons(wiphy);
2243038659e7SLuis R. Rodriguez 	reg_process_ht_flags(wiphy);
22440e3802dbSLuis R. Rodriguez 	reg_call_notifier(wiphy, lr);
2245b2e1b302SLuis R. Rodriguez }
2246b2e1b302SLuis R. Rodriguez 
2247d7549cbbSSven Neumann static void update_all_wiphy_regulatory(enum nl80211_reg_initiator initiator)
2248d7549cbbSSven Neumann {
2249d7549cbbSSven Neumann 	struct cfg80211_registered_device *rdev;
22504a38994fSRajkumar Manoharan 	struct wiphy *wiphy;
2251d7549cbbSSven Neumann 
22525fe231e8SJohannes Berg 	ASSERT_RTNL();
2253458f4f9eSJohannes Berg 
22544a38994fSRajkumar Manoharan 	list_for_each_entry(rdev, &cfg80211_rdev_list, list) {
22554a38994fSRajkumar Manoharan 		wiphy = &rdev->wiphy;
22564a38994fSRajkumar Manoharan 		wiphy_update_regulatory(wiphy, initiator);
22574a38994fSRajkumar Manoharan 	}
2258ad932f04SArik Nemtsov 
2259ad932f04SArik Nemtsov 	reg_check_channels();
2260d7549cbbSSven Neumann }
2261d7549cbbSSven Neumann 
22621fa25e41SLuis R. Rodriguez static void handle_channel_custom(struct wiphy *wiphy,
2263fdc9d7b2SJohannes Berg 				  struct ieee80211_channel *chan,
2264c4b9d655SGanapathi Bhat 				  const struct ieee80211_regdomain *regd,
2265c4b9d655SGanapathi Bhat 				  u32 min_bw)
22661fa25e41SLuis R. Rodriguez {
2267038659e7SLuis R. Rodriguez 	u32 bw_flags = 0;
22681fa25e41SLuis R. Rodriguez 	const struct ieee80211_reg_rule *reg_rule = NULL;
22691fa25e41SLuis R. Rodriguez 	const struct ieee80211_power_rule *power_rule = NULL;
22704edd5698SMatthias May 	u32 bw;
22711fa25e41SLuis R. Rodriguez 
2272c4b9d655SGanapathi Bhat 	for (bw = MHZ_TO_KHZ(20); bw >= min_bw; bw = bw / 2) {
227349172874SMichal Sojka 		reg_rule = freq_reg_info_regd(MHZ_TO_KHZ(chan->center_freq),
22744edd5698SMatthias May 					      regd, bw);
22754edd5698SMatthias May 		if (!IS_ERR(reg_rule))
22764edd5698SMatthias May 			break;
22774edd5698SMatthias May 	}
22781fa25e41SLuis R. Rodriguez 
2279361c9c8bSJohannes Berg 	if (IS_ERR(reg_rule)) {
2280c799ba6eSJohannes Berg 		pr_debug("Disabling freq %d MHz as custom regd has no rule that fits it\n",
2281fe7ef5e9SJohannes Berg 			 chan->center_freq);
2282db8dfee5SArik Nemtsov 		if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED) {
2283db8dfee5SArik Nemtsov 			chan->flags |= IEEE80211_CHAN_DISABLED;
2284db8dfee5SArik Nemtsov 		} else {
2285cc493e4fSLuis R. Rodriguez 			chan->orig_flags |= IEEE80211_CHAN_DISABLED;
2286cc493e4fSLuis R. Rodriguez 			chan->flags = chan->orig_flags;
2287db8dfee5SArik Nemtsov 		}
22881fa25e41SLuis R. Rodriguez 		return;
22891fa25e41SLuis R. Rodriguez 	}
22901fa25e41SLuis R. Rodriguez 
22911fa25e41SLuis R. Rodriguez 	power_rule = &reg_rule->power_rule;
22921aeb135fSMichal Sojka 	bw_flags = reg_rule_to_chan_bw_flags(regd, reg_rule, chan);
2293038659e7SLuis R. Rodriguez 
22942e18b38fSArik Nemtsov 	chan->dfs_state_entered = jiffies;
2295c7ab5081SArik Nemtsov 	chan->dfs_state = NL80211_DFS_USABLE;
2296c7ab5081SArik Nemtsov 
2297c7ab5081SArik Nemtsov 	chan->beacon_found = false;
2298db8dfee5SArik Nemtsov 
2299db8dfee5SArik Nemtsov 	if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED)
2300db8dfee5SArik Nemtsov 		chan->flags = chan->orig_flags | bw_flags |
2301db8dfee5SArik Nemtsov 			      map_regdom_flags(reg_rule->flags);
2302db8dfee5SArik Nemtsov 	else
2303038659e7SLuis R. Rodriguez 		chan->flags |= map_regdom_flags(reg_rule->flags) | bw_flags;
2304db8dfee5SArik Nemtsov 
23051fa25e41SLuis R. Rodriguez 	chan->max_antenna_gain = (int) MBI_TO_DBI(power_rule->max_antenna_gain);
2306279f0f55SFelix Fietkau 	chan->max_reg_power = chan->max_power =
2307279f0f55SFelix Fietkau 		(int) MBM_TO_DBM(power_rule->max_eirp);
23082e18b38fSArik Nemtsov 
23092e18b38fSArik Nemtsov 	if (chan->flags & IEEE80211_CHAN_RADAR) {
23102e18b38fSArik Nemtsov 		if (reg_rule->dfs_cac_ms)
23112e18b38fSArik Nemtsov 			chan->dfs_cac_ms = reg_rule->dfs_cac_ms;
23122e18b38fSArik Nemtsov 		else
23132e18b38fSArik Nemtsov 			chan->dfs_cac_ms = IEEE80211_DFS_MIN_CAC_TIME_MS;
23142e18b38fSArik Nemtsov 	}
23152e18b38fSArik Nemtsov 
23162e18b38fSArik Nemtsov 	chan->max_power = chan->max_reg_power;
23171fa25e41SLuis R. Rodriguez }
23181fa25e41SLuis R. Rodriguez 
2319fdc9d7b2SJohannes Berg static void handle_band_custom(struct wiphy *wiphy,
2320fdc9d7b2SJohannes Berg 			       struct ieee80211_supported_band *sband,
23211fa25e41SLuis R. Rodriguez 			       const struct ieee80211_regdomain *regd)
23221fa25e41SLuis R. Rodriguez {
23231fa25e41SLuis R. Rodriguez 	unsigned int i;
23241fa25e41SLuis R. Rodriguez 
2325fdc9d7b2SJohannes Berg 	if (!sband)
2326fdc9d7b2SJohannes Berg 		return;
23271fa25e41SLuis R. Rodriguez 
2328c4b9d655SGanapathi Bhat 	/*
2329c4b9d655SGanapathi Bhat 	 * We currently assume that you always want at least 20 MHz,
2330c4b9d655SGanapathi Bhat 	 * otherwise channel 12 might get enabled if this rule is
2331c4b9d655SGanapathi Bhat 	 * compatible to US, which permits 2402 - 2472 MHz.
2332c4b9d655SGanapathi Bhat 	 */
23331fa25e41SLuis R. Rodriguez 	for (i = 0; i < sband->n_channels; i++)
2334c4b9d655SGanapathi Bhat 		handle_channel_custom(wiphy, &sband->channels[i], regd,
2335c4b9d655SGanapathi Bhat 				      MHZ_TO_KHZ(20));
23361fa25e41SLuis R. Rodriguez }
23371fa25e41SLuis R. Rodriguez 
23381fa25e41SLuis R. Rodriguez /* Used by drivers prior to wiphy registration */
23391fa25e41SLuis R. Rodriguez void wiphy_apply_custom_regulatory(struct wiphy *wiphy,
23401fa25e41SLuis R. Rodriguez 				   const struct ieee80211_regdomain *regd)
23411fa25e41SLuis R. Rodriguez {
234257fbcce3SJohannes Berg 	enum nl80211_band band;
2343bbcf3f02SLuis R. Rodriguez 	unsigned int bands_set = 0;
2344ac46d48eSLuis R. Rodriguez 
2345a2f73b6cSLuis R. Rodriguez 	WARN(!(wiphy->regulatory_flags & REGULATORY_CUSTOM_REG),
2346a2f73b6cSLuis R. Rodriguez 	     "wiphy should have REGULATORY_CUSTOM_REG\n");
2347a2f73b6cSLuis R. Rodriguez 	wiphy->regulatory_flags |= REGULATORY_CUSTOM_REG;
2348222ea581SLuis R. Rodriguez 
234957fbcce3SJohannes Berg 	for (band = 0; band < NUM_NL80211_BANDS; band++) {
2350bbcf3f02SLuis R. Rodriguez 		if (!wiphy->bands[band])
2351bbcf3f02SLuis R. Rodriguez 			continue;
2352fdc9d7b2SJohannes Berg 		handle_band_custom(wiphy, wiphy->bands[band], regd);
2353bbcf3f02SLuis R. Rodriguez 		bands_set++;
23541fa25e41SLuis R. Rodriguez 	}
2355bbcf3f02SLuis R. Rodriguez 
2356bbcf3f02SLuis R. Rodriguez 	/*
2357bbcf3f02SLuis R. Rodriguez 	 * no point in calling this if it won't have any effect
23581a919318SJohannes Berg 	 * on your device's supported bands.
2359bbcf3f02SLuis R. Rodriguez 	 */
2360bbcf3f02SLuis R. Rodriguez 	WARN_ON(!bands_set);
23611fa25e41SLuis R. Rodriguez }
23621fa25e41SLuis R. Rodriguez EXPORT_SYMBOL(wiphy_apply_custom_regulatory);
23631fa25e41SLuis R. Rodriguez 
2364b2e253cfSLuis R. Rodriguez static void reg_set_request_processed(void)
2365b2e253cfSLuis R. Rodriguez {
2366b2e253cfSLuis R. Rodriguez 	bool need_more_processing = false;
2367c492db37SJohannes Berg 	struct regulatory_request *lr = get_last_request();
2368b2e253cfSLuis R. Rodriguez 
2369c492db37SJohannes Berg 	lr->processed = true;
2370b2e253cfSLuis R. Rodriguez 
2371b2e253cfSLuis R. Rodriguez 	spin_lock(&reg_requests_lock);
2372b2e253cfSLuis R. Rodriguez 	if (!list_empty(&reg_requests_list))
2373b2e253cfSLuis R. Rodriguez 		need_more_processing = true;
2374b2e253cfSLuis R. Rodriguez 	spin_unlock(&reg_requests_lock);
2375b2e253cfSLuis R. Rodriguez 
2376b6863036SJohannes Berg 	cancel_crda_timeout();
2377a90c7a31SLuis R. Rodriguez 
2378b2e253cfSLuis R. Rodriguez 	if (need_more_processing)
2379b2e253cfSLuis R. Rodriguez 		schedule_work(&reg_work);
2380b2e253cfSLuis R. Rodriguez }
2381b2e253cfSLuis R. Rodriguez 
2382d1c96a9aSLuis R. Rodriguez /**
2383b3eb7f3fSLuis R. Rodriguez  * reg_process_hint_core - process core regulatory requests
2384b3eb7f3fSLuis R. Rodriguez  * @pending_request: a pending core regulatory request
2385b3eb7f3fSLuis R. Rodriguez  *
2386b3eb7f3fSLuis R. Rodriguez  * The wireless subsystem can use this function to process
2387b3eb7f3fSLuis R. Rodriguez  * a regulatory request issued by the regulatory core.
2388b3eb7f3fSLuis R. Rodriguez  */
2389d34265a3SJohannes Berg static enum reg_request_treatment
2390d34265a3SJohannes Berg reg_process_hint_core(struct regulatory_request *core_request)
2391b3eb7f3fSLuis R. Rodriguez {
2392cecbb069SJohannes Berg 	if (reg_query_database(core_request)) {
2393b3eb7f3fSLuis R. Rodriguez 		core_request->intersect = false;
2394b3eb7f3fSLuis R. Rodriguez 		core_request->processed = false;
239505f1a3eaSLuis R. Rodriguez 		reg_update_last_request(core_request);
2396d34265a3SJohannes Berg 		return REG_REQ_OK;
239725b20dbdSJohannes Berg 	}
2398d34265a3SJohannes Berg 
2399d34265a3SJohannes Berg 	return REG_REQ_IGNORE;
2400b3eb7f3fSLuis R. Rodriguez }
2401b3eb7f3fSLuis R. Rodriguez 
24020d97a619SLuis R. Rodriguez static enum reg_request_treatment
24030d97a619SLuis R. Rodriguez __reg_process_hint_user(struct regulatory_request *user_request)
24040d97a619SLuis R. Rodriguez {
24050d97a619SLuis R. Rodriguez 	struct regulatory_request *lr = get_last_request();
24060d97a619SLuis R. Rodriguez 
24070d97a619SLuis R. Rodriguez 	if (reg_request_cell_base(user_request))
24080d97a619SLuis R. Rodriguez 		return reg_ignore_cell_hint(user_request);
24090d97a619SLuis R. Rodriguez 
24100d97a619SLuis R. Rodriguez 	if (reg_request_cell_base(lr))
24110d97a619SLuis R. Rodriguez 		return REG_REQ_IGNORE;
24120d97a619SLuis R. Rodriguez 
24130d97a619SLuis R. Rodriguez 	if (lr->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE)
24140d97a619SLuis R. Rodriguez 		return REG_REQ_INTERSECT;
24150d97a619SLuis R. Rodriguez 	/*
24160d97a619SLuis R. Rodriguez 	 * If the user knows better the user should set the regdom
24170d97a619SLuis R. Rodriguez 	 * to their country before the IE is picked up
24180d97a619SLuis R. Rodriguez 	 */
24190d97a619SLuis R. Rodriguez 	if (lr->initiator == NL80211_REGDOM_SET_BY_USER &&
24200d97a619SLuis R. Rodriguez 	    lr->intersect)
24210d97a619SLuis R. Rodriguez 		return REG_REQ_IGNORE;
24220d97a619SLuis R. Rodriguez 	/*
24230d97a619SLuis R. Rodriguez 	 * Process user requests only after previous user/driver/core
24240d97a619SLuis R. Rodriguez 	 * requests have been processed
24250d97a619SLuis R. Rodriguez 	 */
24260d97a619SLuis R. Rodriguez 	if ((lr->initiator == NL80211_REGDOM_SET_BY_CORE ||
24270d97a619SLuis R. Rodriguez 	     lr->initiator == NL80211_REGDOM_SET_BY_DRIVER ||
24280d97a619SLuis R. Rodriguez 	     lr->initiator == NL80211_REGDOM_SET_BY_USER) &&
24290d97a619SLuis R. Rodriguez 	    regdom_changes(lr->alpha2))
24300d97a619SLuis R. Rodriguez 		return REG_REQ_IGNORE;
24310d97a619SLuis R. Rodriguez 
24320d97a619SLuis R. Rodriguez 	if (!regdom_changes(user_request->alpha2))
24330d97a619SLuis R. Rodriguez 		return REG_REQ_ALREADY_SET;
24340d97a619SLuis R. Rodriguez 
24350d97a619SLuis R. Rodriguez 	return REG_REQ_OK;
24360d97a619SLuis R. Rodriguez }
24370d97a619SLuis R. Rodriguez 
24380d97a619SLuis R. Rodriguez /**
24390d97a619SLuis R. Rodriguez  * reg_process_hint_user - process user regulatory requests
24400d97a619SLuis R. Rodriguez  * @user_request: a pending user regulatory request
24410d97a619SLuis R. Rodriguez  *
24420d97a619SLuis R. Rodriguez  * The wireless subsystem can use this function to process
24430d97a619SLuis R. Rodriguez  * a regulatory request initiated by userspace.
24440d97a619SLuis R. Rodriguez  */
2445d34265a3SJohannes Berg static enum reg_request_treatment
2446d34265a3SJohannes Berg reg_process_hint_user(struct regulatory_request *user_request)
24470d97a619SLuis R. Rodriguez {
24480d97a619SLuis R. Rodriguez 	enum reg_request_treatment treatment;
24490d97a619SLuis R. Rodriguez 
24500d97a619SLuis R. Rodriguez 	treatment = __reg_process_hint_user(user_request);
24510d97a619SLuis R. Rodriguez 	if (treatment == REG_REQ_IGNORE ||
2452d34265a3SJohannes Berg 	    treatment == REG_REQ_ALREADY_SET)
2453d34265a3SJohannes Berg 		return REG_REQ_IGNORE;
24540d97a619SLuis R. Rodriguez 
24550d97a619SLuis R. Rodriguez 	user_request->intersect = treatment == REG_REQ_INTERSECT;
24560d97a619SLuis R. Rodriguez 	user_request->processed = false;
24575ad6ef5eSLuis R. Rodriguez 
2458cecbb069SJohannes Berg 	if (reg_query_database(user_request)) {
245905f1a3eaSLuis R. Rodriguez 		reg_update_last_request(user_request);
24600d97a619SLuis R. Rodriguez 		user_alpha2[0] = user_request->alpha2[0];
24610d97a619SLuis R. Rodriguez 		user_alpha2[1] = user_request->alpha2[1];
2462d34265a3SJohannes Berg 		return REG_REQ_OK;
246325b20dbdSJohannes Berg 	}
2464d34265a3SJohannes Berg 
2465d34265a3SJohannes Berg 	return REG_REQ_IGNORE;
24660d97a619SLuis R. Rodriguez }
24670d97a619SLuis R. Rodriguez 
246821636c7fSLuis R. Rodriguez static enum reg_request_treatment
246921636c7fSLuis R. Rodriguez __reg_process_hint_driver(struct regulatory_request *driver_request)
247021636c7fSLuis R. Rodriguez {
247121636c7fSLuis R. Rodriguez 	struct regulatory_request *lr = get_last_request();
247221636c7fSLuis R. Rodriguez 
247321636c7fSLuis R. Rodriguez 	if (lr->initiator == NL80211_REGDOM_SET_BY_CORE) {
247421636c7fSLuis R. Rodriguez 		if (regdom_changes(driver_request->alpha2))
247521636c7fSLuis R. Rodriguez 			return REG_REQ_OK;
247621636c7fSLuis R. Rodriguez 		return REG_REQ_ALREADY_SET;
247721636c7fSLuis R. Rodriguez 	}
247821636c7fSLuis R. Rodriguez 
247921636c7fSLuis R. Rodriguez 	/*
248021636c7fSLuis R. Rodriguez 	 * This would happen if you unplug and plug your card
248121636c7fSLuis R. Rodriguez 	 * back in or if you add a new device for which the previously
248221636c7fSLuis R. Rodriguez 	 * loaded card also agrees on the regulatory domain.
248321636c7fSLuis R. Rodriguez 	 */
248421636c7fSLuis R. Rodriguez 	if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER &&
248521636c7fSLuis R. Rodriguez 	    !regdom_changes(driver_request->alpha2))
248621636c7fSLuis R. Rodriguez 		return REG_REQ_ALREADY_SET;
248721636c7fSLuis R. Rodriguez 
248821636c7fSLuis R. Rodriguez 	return REG_REQ_INTERSECT;
248921636c7fSLuis R. Rodriguez }
249021636c7fSLuis R. Rodriguez 
249121636c7fSLuis R. Rodriguez /**
249221636c7fSLuis R. Rodriguez  * reg_process_hint_driver - process driver regulatory requests
249321636c7fSLuis R. Rodriguez  * @driver_request: a pending driver regulatory request
249421636c7fSLuis R. Rodriguez  *
249521636c7fSLuis R. Rodriguez  * The wireless subsystem can use this function to process
249621636c7fSLuis R. Rodriguez  * a regulatory request issued by an 802.11 driver.
249721636c7fSLuis R. Rodriguez  *
249821636c7fSLuis R. Rodriguez  * Returns one of the different reg request treatment values.
249921636c7fSLuis R. Rodriguez  */
250021636c7fSLuis R. Rodriguez static enum reg_request_treatment
250121636c7fSLuis R. Rodriguez reg_process_hint_driver(struct wiphy *wiphy,
250221636c7fSLuis R. Rodriguez 			struct regulatory_request *driver_request)
250321636c7fSLuis R. Rodriguez {
250434f05f54SArik Nemtsov 	const struct ieee80211_regdomain *regd, *tmp;
250521636c7fSLuis R. Rodriguez 	enum reg_request_treatment treatment;
250621636c7fSLuis R. Rodriguez 
250721636c7fSLuis R. Rodriguez 	treatment = __reg_process_hint_driver(driver_request);
250821636c7fSLuis R. Rodriguez 
250921636c7fSLuis R. Rodriguez 	switch (treatment) {
251021636c7fSLuis R. Rodriguez 	case REG_REQ_OK:
251121636c7fSLuis R. Rodriguez 		break;
251221636c7fSLuis R. Rodriguez 	case REG_REQ_IGNORE:
2513d34265a3SJohannes Berg 		return REG_REQ_IGNORE;
251421636c7fSLuis R. Rodriguez 	case REG_REQ_INTERSECT:
251521636c7fSLuis R. Rodriguez 	case REG_REQ_ALREADY_SET:
251621636c7fSLuis R. Rodriguez 		regd = reg_copy_regd(get_cfg80211_regdom());
2517d34265a3SJohannes Berg 		if (IS_ERR(regd))
2518d34265a3SJohannes Berg 			return REG_REQ_IGNORE;
251934f05f54SArik Nemtsov 
252034f05f54SArik Nemtsov 		tmp = get_wiphy_regdom(wiphy);
252121636c7fSLuis R. Rodriguez 		rcu_assign_pointer(wiphy->regd, regd);
252234f05f54SArik Nemtsov 		rcu_free_regdom(tmp);
252321636c7fSLuis R. Rodriguez 	}
252421636c7fSLuis R. Rodriguez 
252521636c7fSLuis R. Rodriguez 
252621636c7fSLuis R. Rodriguez 	driver_request->intersect = treatment == REG_REQ_INTERSECT;
252721636c7fSLuis R. Rodriguez 	driver_request->processed = false;
25285ad6ef5eSLuis R. Rodriguez 
252921636c7fSLuis R. Rodriguez 	/*
253021636c7fSLuis R. Rodriguez 	 * Since CRDA will not be called in this case as we already
253121636c7fSLuis R. Rodriguez 	 * have applied the requested regulatory domain before we just
253221636c7fSLuis R. Rodriguez 	 * inform userspace we have processed the request
253321636c7fSLuis R. Rodriguez 	 */
253421636c7fSLuis R. Rodriguez 	if (treatment == REG_REQ_ALREADY_SET) {
253521636c7fSLuis R. Rodriguez 		nl80211_send_reg_change_event(driver_request);
253625b20dbdSJohannes Berg 		reg_update_last_request(driver_request);
253721636c7fSLuis R. Rodriguez 		reg_set_request_processed();
2538480908a7SJohannes Berg 		return REG_REQ_ALREADY_SET;
253921636c7fSLuis R. Rodriguez 	}
254021636c7fSLuis R. Rodriguez 
2541d34265a3SJohannes Berg 	if (reg_query_database(driver_request)) {
254225b20dbdSJohannes Berg 		reg_update_last_request(driver_request);
254325b20dbdSJohannes Berg 		return REG_REQ_OK;
254421636c7fSLuis R. Rodriguez 	}
254521636c7fSLuis R. Rodriguez 
2546d34265a3SJohannes Berg 	return REG_REQ_IGNORE;
2547d34265a3SJohannes Berg }
2548d34265a3SJohannes Berg 
2549b23e7a9eSLuis R. Rodriguez static enum reg_request_treatment
2550b23e7a9eSLuis R. Rodriguez __reg_process_hint_country_ie(struct wiphy *wiphy,
2551b23e7a9eSLuis R. Rodriguez 			      struct regulatory_request *country_ie_request)
2552b23e7a9eSLuis R. Rodriguez {
2553b23e7a9eSLuis R. Rodriguez 	struct wiphy *last_wiphy = NULL;
2554b23e7a9eSLuis R. Rodriguez 	struct regulatory_request *lr = get_last_request();
2555b23e7a9eSLuis R. Rodriguez 
2556b23e7a9eSLuis R. Rodriguez 	if (reg_request_cell_base(lr)) {
2557b23e7a9eSLuis R. Rodriguez 		/* Trust a Cell base station over the AP's country IE */
2558b23e7a9eSLuis R. Rodriguez 		if (regdom_changes(country_ie_request->alpha2))
2559b23e7a9eSLuis R. Rodriguez 			return REG_REQ_IGNORE;
2560b23e7a9eSLuis R. Rodriguez 		return REG_REQ_ALREADY_SET;
25612a901468SLuis R. Rodriguez 	} else {
25622a901468SLuis R. Rodriguez 		if (wiphy->regulatory_flags & REGULATORY_COUNTRY_IE_IGNORE)
25632a901468SLuis R. Rodriguez 			return REG_REQ_IGNORE;
2564b23e7a9eSLuis R. Rodriguez 	}
2565b23e7a9eSLuis R. Rodriguez 
2566b23e7a9eSLuis R. Rodriguez 	if (unlikely(!is_an_alpha2(country_ie_request->alpha2)))
2567b23e7a9eSLuis R. Rodriguez 		return -EINVAL;
25682f1c6c57SLuis R. Rodriguez 
25692f1c6c57SLuis R. Rodriguez 	if (lr->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE)
25702f1c6c57SLuis R. Rodriguez 		return REG_REQ_OK;
25712f1c6c57SLuis R. Rodriguez 
25722f1c6c57SLuis R. Rodriguez 	last_wiphy = wiphy_idx_to_wiphy(lr->wiphy_idx);
25732f1c6c57SLuis R. Rodriguez 
2574b23e7a9eSLuis R. Rodriguez 	if (last_wiphy != wiphy) {
2575b23e7a9eSLuis R. Rodriguez 		/*
2576b23e7a9eSLuis R. Rodriguez 		 * Two cards with two APs claiming different
2577b23e7a9eSLuis R. Rodriguez 		 * Country IE alpha2s. We could
2578b23e7a9eSLuis R. Rodriguez 		 * intersect them, but that seems unlikely
2579b23e7a9eSLuis R. Rodriguez 		 * to be correct. Reject second one for now.
2580b23e7a9eSLuis R. Rodriguez 		 */
2581b23e7a9eSLuis R. Rodriguez 		if (regdom_changes(country_ie_request->alpha2))
2582b23e7a9eSLuis R. Rodriguez 			return REG_REQ_IGNORE;
2583b23e7a9eSLuis R. Rodriguez 		return REG_REQ_ALREADY_SET;
2584b23e7a9eSLuis R. Rodriguez 	}
258570dcec5aSEmmanuel Grumbach 
258670dcec5aSEmmanuel Grumbach 	if (regdom_changes(country_ie_request->alpha2))
2587b23e7a9eSLuis R. Rodriguez 		return REG_REQ_OK;
2588b23e7a9eSLuis R. Rodriguez 	return REG_REQ_ALREADY_SET;
2589b23e7a9eSLuis R. Rodriguez }
2590b23e7a9eSLuis R. Rodriguez 
2591b3eb7f3fSLuis R. Rodriguez /**
2592b23e7a9eSLuis R. Rodriguez  * reg_process_hint_country_ie - process regulatory requests from country IEs
2593b23e7a9eSLuis R. Rodriguez  * @country_ie_request: a regulatory request from a country IE
2594d1c96a9aSLuis R. Rodriguez  *
2595b23e7a9eSLuis R. Rodriguez  * The wireless subsystem can use this function to process
2596b23e7a9eSLuis R. Rodriguez  * a regulatory request issued by a country Information Element.
2597d1c96a9aSLuis R. Rodriguez  *
25982f92212bSJohannes Berg  * Returns one of the different reg request treatment values.
2599d1c96a9aSLuis R. Rodriguez  */
26002f92212bSJohannes Berg static enum reg_request_treatment
2601b23e7a9eSLuis R. Rodriguez reg_process_hint_country_ie(struct wiphy *wiphy,
2602b23e7a9eSLuis R. Rodriguez 			    struct regulatory_request *country_ie_request)
2603b2e1b302SLuis R. Rodriguez {
26042f92212bSJohannes Berg 	enum reg_request_treatment treatment;
2605b2e1b302SLuis R. Rodriguez 
2606b23e7a9eSLuis R. Rodriguez 	treatment = __reg_process_hint_country_ie(wiphy, country_ie_request);
2607761cf7ecSLuis R. Rodriguez 
26082f92212bSJohannes Berg 	switch (treatment) {
26092f92212bSJohannes Berg 	case REG_REQ_OK:
26102f92212bSJohannes Berg 		break;
2611b23e7a9eSLuis R. Rodriguez 	case REG_REQ_IGNORE:
2612d34265a3SJohannes Berg 		return REG_REQ_IGNORE;
2613b23e7a9eSLuis R. Rodriguez 	case REG_REQ_ALREADY_SET:
2614c888393bSArik Nemtsov 		reg_free_request(country_ie_request);
2615480908a7SJohannes Berg 		return REG_REQ_ALREADY_SET;
2616b23e7a9eSLuis R. Rodriguez 	case REG_REQ_INTERSECT:
2617fb1fc7adSLuis R. Rodriguez 		/*
2618b23e7a9eSLuis R. Rodriguez 		 * This doesn't happen yet, not sure we
2619b23e7a9eSLuis R. Rodriguez 		 * ever want to support it for this case.
2620fb1fc7adSLuis R. Rodriguez 		 */
26218db0c433SToke Høiland-Jørgensen 		WARN_ONCE(1, "Unexpected intersection for country elements");
2622d34265a3SJohannes Berg 		return REG_REQ_IGNORE;
2623d951c1ddSLuis R. Rodriguez 	}
2624b2e1b302SLuis R. Rodriguez 
2625b23e7a9eSLuis R. Rodriguez 	country_ie_request->intersect = false;
2626b23e7a9eSLuis R. Rodriguez 	country_ie_request->processed = false;
26275ad6ef5eSLuis R. Rodriguez 
2628d34265a3SJohannes Berg 	if (reg_query_database(country_ie_request)) {
262905f1a3eaSLuis R. Rodriguez 		reg_update_last_request(country_ie_request);
263025b20dbdSJohannes Berg 		return REG_REQ_OK;
2631b2e1b302SLuis R. Rodriguez 	}
2632b2e1b302SLuis R. Rodriguez 
2633d34265a3SJohannes Berg 	return REG_REQ_IGNORE;
2634d34265a3SJohannes Berg }
2635d34265a3SJohannes Berg 
263689766727SVasanthakumar Thiagarajan bool reg_dfs_domain_same(struct wiphy *wiphy1, struct wiphy *wiphy2)
263789766727SVasanthakumar Thiagarajan {
263889766727SVasanthakumar Thiagarajan 	const struct ieee80211_regdomain *wiphy1_regd = NULL;
263989766727SVasanthakumar Thiagarajan 	const struct ieee80211_regdomain *wiphy2_regd = NULL;
264089766727SVasanthakumar Thiagarajan 	const struct ieee80211_regdomain *cfg80211_regd = NULL;
264189766727SVasanthakumar Thiagarajan 	bool dfs_domain_same;
264289766727SVasanthakumar Thiagarajan 
264389766727SVasanthakumar Thiagarajan 	rcu_read_lock();
264489766727SVasanthakumar Thiagarajan 
264589766727SVasanthakumar Thiagarajan 	cfg80211_regd = rcu_dereference(cfg80211_regdomain);
264689766727SVasanthakumar Thiagarajan 	wiphy1_regd = rcu_dereference(wiphy1->regd);
264789766727SVasanthakumar Thiagarajan 	if (!wiphy1_regd)
264889766727SVasanthakumar Thiagarajan 		wiphy1_regd = cfg80211_regd;
264989766727SVasanthakumar Thiagarajan 
265089766727SVasanthakumar Thiagarajan 	wiphy2_regd = rcu_dereference(wiphy2->regd);
265189766727SVasanthakumar Thiagarajan 	if (!wiphy2_regd)
265289766727SVasanthakumar Thiagarajan 		wiphy2_regd = cfg80211_regd;
265389766727SVasanthakumar Thiagarajan 
265489766727SVasanthakumar Thiagarajan 	dfs_domain_same = wiphy1_regd->dfs_region == wiphy2_regd->dfs_region;
265589766727SVasanthakumar Thiagarajan 
265689766727SVasanthakumar Thiagarajan 	rcu_read_unlock();
265789766727SVasanthakumar Thiagarajan 
265889766727SVasanthakumar Thiagarajan 	return dfs_domain_same;
265989766727SVasanthakumar Thiagarajan }
266089766727SVasanthakumar Thiagarajan 
266189766727SVasanthakumar Thiagarajan static void reg_copy_dfs_chan_state(struct ieee80211_channel *dst_chan,
266289766727SVasanthakumar Thiagarajan 				    struct ieee80211_channel *src_chan)
266389766727SVasanthakumar Thiagarajan {
266489766727SVasanthakumar Thiagarajan 	if (!(dst_chan->flags & IEEE80211_CHAN_RADAR) ||
266589766727SVasanthakumar Thiagarajan 	    !(src_chan->flags & IEEE80211_CHAN_RADAR))
266689766727SVasanthakumar Thiagarajan 		return;
266789766727SVasanthakumar Thiagarajan 
266889766727SVasanthakumar Thiagarajan 	if (dst_chan->flags & IEEE80211_CHAN_DISABLED ||
266989766727SVasanthakumar Thiagarajan 	    src_chan->flags & IEEE80211_CHAN_DISABLED)
267089766727SVasanthakumar Thiagarajan 		return;
267189766727SVasanthakumar Thiagarajan 
267289766727SVasanthakumar Thiagarajan 	if (src_chan->center_freq == dst_chan->center_freq &&
267389766727SVasanthakumar Thiagarajan 	    dst_chan->dfs_state == NL80211_DFS_USABLE) {
267489766727SVasanthakumar Thiagarajan 		dst_chan->dfs_state = src_chan->dfs_state;
267589766727SVasanthakumar Thiagarajan 		dst_chan->dfs_state_entered = src_chan->dfs_state_entered;
267689766727SVasanthakumar Thiagarajan 	}
267789766727SVasanthakumar Thiagarajan }
267889766727SVasanthakumar Thiagarajan 
267989766727SVasanthakumar Thiagarajan static void wiphy_share_dfs_chan_state(struct wiphy *dst_wiphy,
268089766727SVasanthakumar Thiagarajan 				       struct wiphy *src_wiphy)
268189766727SVasanthakumar Thiagarajan {
268289766727SVasanthakumar Thiagarajan 	struct ieee80211_supported_band *src_sband, *dst_sband;
268389766727SVasanthakumar Thiagarajan 	struct ieee80211_channel *src_chan, *dst_chan;
268489766727SVasanthakumar Thiagarajan 	int i, j, band;
268589766727SVasanthakumar Thiagarajan 
268689766727SVasanthakumar Thiagarajan 	if (!reg_dfs_domain_same(dst_wiphy, src_wiphy))
268789766727SVasanthakumar Thiagarajan 		return;
268889766727SVasanthakumar Thiagarajan 
268989766727SVasanthakumar Thiagarajan 	for (band = 0; band < NUM_NL80211_BANDS; band++) {
269089766727SVasanthakumar Thiagarajan 		dst_sband = dst_wiphy->bands[band];
269189766727SVasanthakumar Thiagarajan 		src_sband = src_wiphy->bands[band];
269289766727SVasanthakumar Thiagarajan 		if (!dst_sband || !src_sband)
269389766727SVasanthakumar Thiagarajan 			continue;
269489766727SVasanthakumar Thiagarajan 
269589766727SVasanthakumar Thiagarajan 		for (i = 0; i < dst_sband->n_channels; i++) {
269689766727SVasanthakumar Thiagarajan 			dst_chan = &dst_sband->channels[i];
269789766727SVasanthakumar Thiagarajan 			for (j = 0; j < src_sband->n_channels; j++) {
269889766727SVasanthakumar Thiagarajan 				src_chan = &src_sband->channels[j];
269989766727SVasanthakumar Thiagarajan 				reg_copy_dfs_chan_state(dst_chan, src_chan);
270089766727SVasanthakumar Thiagarajan 			}
270189766727SVasanthakumar Thiagarajan 		}
270289766727SVasanthakumar Thiagarajan 	}
270389766727SVasanthakumar Thiagarajan }
270489766727SVasanthakumar Thiagarajan 
270589766727SVasanthakumar Thiagarajan static void wiphy_all_share_dfs_chan_state(struct wiphy *wiphy)
270689766727SVasanthakumar Thiagarajan {
270789766727SVasanthakumar Thiagarajan 	struct cfg80211_registered_device *rdev;
270889766727SVasanthakumar Thiagarajan 
270989766727SVasanthakumar Thiagarajan 	ASSERT_RTNL();
271089766727SVasanthakumar Thiagarajan 
271189766727SVasanthakumar Thiagarajan 	list_for_each_entry(rdev, &cfg80211_rdev_list, list) {
271289766727SVasanthakumar Thiagarajan 		if (wiphy == &rdev->wiphy)
271389766727SVasanthakumar Thiagarajan 			continue;
271489766727SVasanthakumar Thiagarajan 		wiphy_share_dfs_chan_state(wiphy, &rdev->wiphy);
271589766727SVasanthakumar Thiagarajan 	}
271689766727SVasanthakumar Thiagarajan }
271789766727SVasanthakumar Thiagarajan 
271830a548c7SLuis R. Rodriguez /* This processes *all* regulatory hints */
27191daa37c7SLuis R. Rodriguez static void reg_process_hint(struct regulatory_request *reg_request)
2720fe33eb39SLuis R. Rodriguez {
2721fe33eb39SLuis R. Rodriguez 	struct wiphy *wiphy = NULL;
2722b3eb7f3fSLuis R. Rodriguez 	enum reg_request_treatment treatment;
27231db58529SYu Zhao 	enum nl80211_reg_initiator initiator = reg_request->initiator;
2724fe33eb39SLuis R. Rodriguez 
2725f4173766SJohannes Berg 	if (reg_request->wiphy_idx != WIPHY_IDX_INVALID)
2726fe33eb39SLuis R. Rodriguez 		wiphy = wiphy_idx_to_wiphy(reg_request->wiphy_idx);
2727fe33eb39SLuis R. Rodriguez 
27281db58529SYu Zhao 	switch (initiator) {
2729b3eb7f3fSLuis R. Rodriguez 	case NL80211_REGDOM_SET_BY_CORE:
2730d34265a3SJohannes Berg 		treatment = reg_process_hint_core(reg_request);
2731d34265a3SJohannes Berg 		break;
2732b3eb7f3fSLuis R. Rodriguez 	case NL80211_REGDOM_SET_BY_USER:
2733d34265a3SJohannes Berg 		treatment = reg_process_hint_user(reg_request);
2734d34265a3SJohannes Berg 		break;
2735b3eb7f3fSLuis R. Rodriguez 	case NL80211_REGDOM_SET_BY_DRIVER:
2736772f0389SIlan Peer 		if (!wiphy)
2737772f0389SIlan Peer 			goto out_free;
273821636c7fSLuis R. Rodriguez 		treatment = reg_process_hint_driver(wiphy, reg_request);
273921636c7fSLuis R. Rodriguez 		break;
2740b3eb7f3fSLuis R. Rodriguez 	case NL80211_REGDOM_SET_BY_COUNTRY_IE:
2741772f0389SIlan Peer 		if (!wiphy)
2742772f0389SIlan Peer 			goto out_free;
2743b23e7a9eSLuis R. Rodriguez 		treatment = reg_process_hint_country_ie(wiphy, reg_request);
2744b3eb7f3fSLuis R. Rodriguez 		break;
2745b3eb7f3fSLuis R. Rodriguez 	default:
27461db58529SYu Zhao 		WARN(1, "invalid initiator %d\n", initiator);
2747772f0389SIlan Peer 		goto out_free;
2748b3eb7f3fSLuis R. Rodriguez 	}
2749b3eb7f3fSLuis R. Rodriguez 
2750d34265a3SJohannes Berg 	if (treatment == REG_REQ_IGNORE)
2751d34265a3SJohannes Berg 		goto out_free;
2752d34265a3SJohannes Berg 
2753480908a7SJohannes Berg 	WARN(treatment != REG_REQ_OK && treatment != REG_REQ_ALREADY_SET,
2754480908a7SJohannes Berg 	     "unexpected treatment value %d\n", treatment);
2755480908a7SJohannes Berg 
2756841b351cSJohn Linville 	/* This is required so that the orig_* parameters are saved.
2757841b351cSJohn Linville 	 * NOTE: treatment must be set for any case that reaches here!
2758841b351cSJohn Linville 	 */
2759b23e7a9eSLuis R. Rodriguez 	if (treatment == REG_REQ_ALREADY_SET && wiphy &&
2760ad932f04SArik Nemtsov 	    wiphy->regulatory_flags & REGULATORY_STRICT_REG) {
27611db58529SYu Zhao 		wiphy_update_regulatory(wiphy, initiator);
276289766727SVasanthakumar Thiagarajan 		wiphy_all_share_dfs_chan_state(wiphy);
2763ad932f04SArik Nemtsov 		reg_check_channels();
2764ad932f04SArik Nemtsov 	}
2765772f0389SIlan Peer 
2766772f0389SIlan Peer 	return;
2767772f0389SIlan Peer 
2768772f0389SIlan Peer out_free:
2769c888393bSArik Nemtsov 	reg_free_request(reg_request);
2770fe33eb39SLuis R. Rodriguez }
2771fe33eb39SLuis R. Rodriguez 
2772aced43ceSAmar Singhal static void notify_self_managed_wiphys(struct regulatory_request *request)
2773aced43ceSAmar Singhal {
2774aced43ceSAmar Singhal 	struct cfg80211_registered_device *rdev;
2775aced43ceSAmar Singhal 	struct wiphy *wiphy;
2776aced43ceSAmar Singhal 
2777aced43ceSAmar Singhal 	list_for_each_entry(rdev, &cfg80211_rdev_list, list) {
2778aced43ceSAmar Singhal 		wiphy = &rdev->wiphy;
2779aced43ceSAmar Singhal 		if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED &&
2780c82c06ceSSriram R 		    request->initiator == NL80211_REGDOM_SET_BY_USER)
2781aced43ceSAmar Singhal 			reg_call_notifier(wiphy, request);
2782aced43ceSAmar Singhal 	}
2783aced43ceSAmar Singhal }
2784aced43ceSAmar Singhal 
2785b2e253cfSLuis R. Rodriguez /*
2786b2e253cfSLuis R. Rodriguez  * Processes regulatory hints, this is all the NL80211_REGDOM_SET_BY_*
2787b2e253cfSLuis R. Rodriguez  * Regulatory hints come on a first come first serve basis and we
2788b2e253cfSLuis R. Rodriguez  * must process each one atomically.
2789b2e253cfSLuis R. Rodriguez  */
2790fe33eb39SLuis R. Rodriguez static void reg_process_pending_hints(void)
2791fe33eb39SLuis R. Rodriguez {
2792c492db37SJohannes Berg 	struct regulatory_request *reg_request, *lr;
2793fe33eb39SLuis R. Rodriguez 
2794c492db37SJohannes Berg 	lr = get_last_request();
2795b0e2880bSLuis R. Rodriguez 
2796b2e253cfSLuis R. Rodriguez 	/* When last_request->processed becomes true this will be rescheduled */
2797c492db37SJohannes Berg 	if (lr && !lr->processed) {
27980d31d4dbSHodaszi, Robert 		pr_debug("Pending regulatory request, waiting for it to be processed...\n");
27995fe231e8SJohannes Berg 		return;
2800b2e253cfSLuis R. Rodriguez 	}
2801b2e253cfSLuis R. Rodriguez 
2802fe33eb39SLuis R. Rodriguez 	spin_lock(&reg_requests_lock);
2803b2e253cfSLuis R. Rodriguez 
2804b2e253cfSLuis R. Rodriguez 	if (list_empty(&reg_requests_list)) {
2805b2e253cfSLuis R. Rodriguez 		spin_unlock(&reg_requests_lock);
28065fe231e8SJohannes Berg 		return;
2807b2e253cfSLuis R. Rodriguez 	}
2808b2e253cfSLuis R. Rodriguez 
2809fe33eb39SLuis R. Rodriguez 	reg_request = list_first_entry(&reg_requests_list,
2810fe33eb39SLuis R. Rodriguez 				       struct regulatory_request,
2811fe33eb39SLuis R. Rodriguez 				       list);
2812fe33eb39SLuis R. Rodriguez 	list_del_init(&reg_request->list);
2813fe33eb39SLuis R. Rodriguez 
2814d951c1ddSLuis R. Rodriguez 	spin_unlock(&reg_requests_lock);
2815b0e2880bSLuis R. Rodriguez 
2816aced43ceSAmar Singhal 	notify_self_managed_wiphys(reg_request);
2817ef51fb1dSArik Nemtsov 
28181daa37c7SLuis R. Rodriguez 	reg_process_hint(reg_request);
28192e54a689SBen 
28202e54a689SBen 	lr = get_last_request();
28212e54a689SBen 
28222e54a689SBen 	spin_lock(&reg_requests_lock);
28232e54a689SBen 	if (!list_empty(&reg_requests_list) && lr && lr->processed)
28242e54a689SBen 		schedule_work(&reg_work);
28252e54a689SBen 	spin_unlock(&reg_requests_lock);
2826fe33eb39SLuis R. Rodriguez }
2827fe33eb39SLuis R. Rodriguez 
2828e38f8a7aSLuis R. Rodriguez /* Processes beacon hints -- this has nothing to do with country IEs */
2829e38f8a7aSLuis R. Rodriguez static void reg_process_pending_beacon_hints(void)
2830e38f8a7aSLuis R. Rodriguez {
283179c97e97SJohannes Berg 	struct cfg80211_registered_device *rdev;
2832e38f8a7aSLuis R. Rodriguez 	struct reg_beacon *pending_beacon, *tmp;
2833e38f8a7aSLuis R. Rodriguez 
2834e38f8a7aSLuis R. Rodriguez 	/* This goes through the _pending_ beacon list */
2835e38f8a7aSLuis R. Rodriguez 	spin_lock_bh(&reg_pending_beacons_lock);
2836e38f8a7aSLuis R. Rodriguez 
2837e38f8a7aSLuis R. Rodriguez 	list_for_each_entry_safe(pending_beacon, tmp,
2838e38f8a7aSLuis R. Rodriguez 				 &reg_pending_beacons, list) {
2839e38f8a7aSLuis R. Rodriguez 		list_del_init(&pending_beacon->list);
2840e38f8a7aSLuis R. Rodriguez 
2841e38f8a7aSLuis R. Rodriguez 		/* Applies the beacon hint to current wiphys */
284279c97e97SJohannes Berg 		list_for_each_entry(rdev, &cfg80211_rdev_list, list)
284379c97e97SJohannes Berg 			wiphy_update_new_beacon(&rdev->wiphy, pending_beacon);
2844e38f8a7aSLuis R. Rodriguez 
2845e38f8a7aSLuis R. Rodriguez 		/* Remembers the beacon hint for new wiphys or reg changes */
2846e38f8a7aSLuis R. Rodriguez 		list_add_tail(&pending_beacon->list, &reg_beacon_list);
2847e38f8a7aSLuis R. Rodriguez 	}
2848e38f8a7aSLuis R. Rodriguez 
2849e38f8a7aSLuis R. Rodriguez 	spin_unlock_bh(&reg_pending_beacons_lock);
2850e38f8a7aSLuis R. Rodriguez }
2851e38f8a7aSLuis R. Rodriguez 
2852b0d7aa59SJonathan Doron static void reg_process_self_managed_hints(void)
2853b0d7aa59SJonathan Doron {
2854b0d7aa59SJonathan Doron 	struct cfg80211_registered_device *rdev;
2855b0d7aa59SJonathan Doron 	struct wiphy *wiphy;
2856b0d7aa59SJonathan Doron 	const struct ieee80211_regdomain *tmp;
2857b0d7aa59SJonathan Doron 	const struct ieee80211_regdomain *regd;
285857fbcce3SJohannes Berg 	enum nl80211_band band;
2859b0d7aa59SJonathan Doron 	struct regulatory_request request = {};
2860b0d7aa59SJonathan Doron 
2861b0d7aa59SJonathan Doron 	list_for_each_entry(rdev, &cfg80211_rdev_list, list) {
2862b0d7aa59SJonathan Doron 		wiphy = &rdev->wiphy;
2863b0d7aa59SJonathan Doron 
2864b0d7aa59SJonathan Doron 		spin_lock(&reg_requests_lock);
2865b0d7aa59SJonathan Doron 		regd = rdev->requested_regd;
2866b0d7aa59SJonathan Doron 		rdev->requested_regd = NULL;
2867b0d7aa59SJonathan Doron 		spin_unlock(&reg_requests_lock);
2868b0d7aa59SJonathan Doron 
2869b0d7aa59SJonathan Doron 		if (regd == NULL)
2870b0d7aa59SJonathan Doron 			continue;
2871b0d7aa59SJonathan Doron 
2872b0d7aa59SJonathan Doron 		tmp = get_wiphy_regdom(wiphy);
2873b0d7aa59SJonathan Doron 		rcu_assign_pointer(wiphy->regd, regd);
2874b0d7aa59SJonathan Doron 		rcu_free_regdom(tmp);
2875b0d7aa59SJonathan Doron 
287657fbcce3SJohannes Berg 		for (band = 0; band < NUM_NL80211_BANDS; band++)
2877b0d7aa59SJonathan Doron 			handle_band_custom(wiphy, wiphy->bands[band], regd);
2878b0d7aa59SJonathan Doron 
2879b0d7aa59SJonathan Doron 		reg_process_ht_flags(wiphy);
2880b0d7aa59SJonathan Doron 
2881b0d7aa59SJonathan Doron 		request.wiphy_idx = get_wiphy_idx(wiphy);
2882b0d7aa59SJonathan Doron 		request.alpha2[0] = regd->alpha2[0];
2883b0d7aa59SJonathan Doron 		request.alpha2[1] = regd->alpha2[1];
2884b0d7aa59SJonathan Doron 		request.initiator = NL80211_REGDOM_SET_BY_DRIVER;
2885b0d7aa59SJonathan Doron 
2886b0d7aa59SJonathan Doron 		nl80211_send_wiphy_reg_change_event(&request);
2887b0d7aa59SJonathan Doron 	}
2888b0d7aa59SJonathan Doron 
2889b0d7aa59SJonathan Doron 	reg_check_channels();
2890b0d7aa59SJonathan Doron }
2891b0d7aa59SJonathan Doron 
2892fe33eb39SLuis R. Rodriguez static void reg_todo(struct work_struct *work)
2893fe33eb39SLuis R. Rodriguez {
28945fe231e8SJohannes Berg 	rtnl_lock();
2895fe33eb39SLuis R. Rodriguez 	reg_process_pending_hints();
2896e38f8a7aSLuis R. Rodriguez 	reg_process_pending_beacon_hints();
2897b0d7aa59SJonathan Doron 	reg_process_self_managed_hints();
28985fe231e8SJohannes Berg 	rtnl_unlock();
2899fe33eb39SLuis R. Rodriguez }
2900fe33eb39SLuis R. Rodriguez 
2901fe33eb39SLuis R. Rodriguez static void queue_regulatory_request(struct regulatory_request *request)
2902fe33eb39SLuis R. Rodriguez {
2903c61029c7SJohn W. Linville 	request->alpha2[0] = toupper(request->alpha2[0]);
2904c61029c7SJohn W. Linville 	request->alpha2[1] = toupper(request->alpha2[1]);
2905c61029c7SJohn W. Linville 
2906fe33eb39SLuis R. Rodriguez 	spin_lock(&reg_requests_lock);
2907fe33eb39SLuis R. Rodriguez 	list_add_tail(&request->list, &reg_requests_list);
2908fe33eb39SLuis R. Rodriguez 	spin_unlock(&reg_requests_lock);
2909fe33eb39SLuis R. Rodriguez 
2910fe33eb39SLuis R. Rodriguez 	schedule_work(&reg_work);
2911fe33eb39SLuis R. Rodriguez }
2912fe33eb39SLuis R. Rodriguez 
291309d989d1SLuis R. Rodriguez /*
291409d989d1SLuis R. Rodriguez  * Core regulatory hint -- happens during cfg80211_init()
291509d989d1SLuis R. Rodriguez  * and when we restore regulatory settings.
291609d989d1SLuis R. Rodriguez  */
2917ba25c141SLuis R. Rodriguez static int regulatory_hint_core(const char *alpha2)
2918ba25c141SLuis R. Rodriguez {
2919ba25c141SLuis R. Rodriguez 	struct regulatory_request *request;
2920ba25c141SLuis R. Rodriguez 
29211a919318SJohannes Berg 	request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL);
2922ba25c141SLuis R. Rodriguez 	if (!request)
2923ba25c141SLuis R. Rodriguez 		return -ENOMEM;
2924ba25c141SLuis R. Rodriguez 
2925ba25c141SLuis R. Rodriguez 	request->alpha2[0] = alpha2[0];
2926ba25c141SLuis R. Rodriguez 	request->alpha2[1] = alpha2[1];
29277db90f4aSLuis R. Rodriguez 	request->initiator = NL80211_REGDOM_SET_BY_CORE;
292824f33e64SAndrei Otcheretianski 	request->wiphy_idx = WIPHY_IDX_INVALID;
2929ba25c141SLuis R. Rodriguez 
293031e99729SLuis R. Rodriguez 	queue_regulatory_request(request);
29315078b2e3SLuis R. Rodriguez 
2932fe33eb39SLuis R. Rodriguez 	return 0;
2933ba25c141SLuis R. Rodriguez }
2934ba25c141SLuis R. Rodriguez 
2935fe33eb39SLuis R. Rodriguez /* User hints */
293657b5ce07SLuis R. Rodriguez int regulatory_hint_user(const char *alpha2,
293757b5ce07SLuis R. Rodriguez 			 enum nl80211_user_reg_hint_type user_reg_hint_type)
2938b2e1b302SLuis R. Rodriguez {
2939fe33eb39SLuis R. Rodriguez 	struct regulatory_request *request;
2940fe33eb39SLuis R. Rodriguez 
2941fdc9d7b2SJohannes Berg 	if (WARN_ON(!alpha2))
2942fdc9d7b2SJohannes Berg 		return -EINVAL;
2943b2e1b302SLuis R. Rodriguez 
2944fe33eb39SLuis R. Rodriguez 	request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL);
2945fe33eb39SLuis R. Rodriguez 	if (!request)
2946fe33eb39SLuis R. Rodriguez 		return -ENOMEM;
2947fe33eb39SLuis R. Rodriguez 
2948f4173766SJohannes Berg 	request->wiphy_idx = WIPHY_IDX_INVALID;
2949fe33eb39SLuis R. Rodriguez 	request->alpha2[0] = alpha2[0];
2950fe33eb39SLuis R. Rodriguez 	request->alpha2[1] = alpha2[1];
2951e12822e1SLuis R. Rodriguez 	request->initiator = NL80211_REGDOM_SET_BY_USER;
295257b5ce07SLuis R. Rodriguez 	request->user_reg_hint_type = user_reg_hint_type;
2953fe33eb39SLuis R. Rodriguez 
2954c37722bdSIlan peer 	/* Allow calling CRDA again */
2955b6863036SJohannes Berg 	reset_crda_timeouts();
2956c37722bdSIlan peer 
2957fe33eb39SLuis R. Rodriguez 	queue_regulatory_request(request);
2958fe33eb39SLuis R. Rodriguez 
2959fe33eb39SLuis R. Rodriguez 	return 0;
2960fe33eb39SLuis R. Rodriguez }
2961fe33eb39SLuis R. Rodriguez 
296205050753SIlan peer int regulatory_hint_indoor(bool is_indoor, u32 portid)
296352616f2bSIlan Peer {
296405050753SIlan peer 	spin_lock(&reg_indoor_lock);
296552616f2bSIlan Peer 
296605050753SIlan peer 	/* It is possible that more than one user space process is trying to
296705050753SIlan peer 	 * configure the indoor setting. To handle such cases, clear the indoor
296805050753SIlan peer 	 * setting in case that some process does not think that the device
296905050753SIlan peer 	 * is operating in an indoor environment. In addition, if a user space
297005050753SIlan peer 	 * process indicates that it is controlling the indoor setting, save its
297105050753SIlan peer 	 * portid, i.e., make it the owner.
297205050753SIlan peer 	 */
297305050753SIlan peer 	reg_is_indoor = is_indoor;
297405050753SIlan peer 	if (reg_is_indoor) {
297505050753SIlan peer 		if (!reg_is_indoor_portid)
297605050753SIlan peer 			reg_is_indoor_portid = portid;
297705050753SIlan peer 	} else {
297805050753SIlan peer 		reg_is_indoor_portid = 0;
297905050753SIlan peer 	}
298052616f2bSIlan Peer 
298105050753SIlan peer 	spin_unlock(&reg_indoor_lock);
298205050753SIlan peer 
298305050753SIlan peer 	if (!is_indoor)
298405050753SIlan peer 		reg_check_channels();
298552616f2bSIlan Peer 
298652616f2bSIlan Peer 	return 0;
298752616f2bSIlan Peer }
298852616f2bSIlan Peer 
298905050753SIlan peer void regulatory_netlink_notify(u32 portid)
299005050753SIlan peer {
299105050753SIlan peer 	spin_lock(&reg_indoor_lock);
299205050753SIlan peer 
299305050753SIlan peer 	if (reg_is_indoor_portid != portid) {
299405050753SIlan peer 		spin_unlock(&reg_indoor_lock);
299505050753SIlan peer 		return;
299605050753SIlan peer 	}
299705050753SIlan peer 
299805050753SIlan peer 	reg_is_indoor = false;
299905050753SIlan peer 	reg_is_indoor_portid = 0;
300005050753SIlan peer 
300105050753SIlan peer 	spin_unlock(&reg_indoor_lock);
300205050753SIlan peer 
300305050753SIlan peer 	reg_check_channels();
300405050753SIlan peer }
300505050753SIlan peer 
3006fe33eb39SLuis R. Rodriguez /* Driver hints */
3007fe33eb39SLuis R. Rodriguez int regulatory_hint(struct wiphy *wiphy, const char *alpha2)
3008fe33eb39SLuis R. Rodriguez {
3009fe33eb39SLuis R. Rodriguez 	struct regulatory_request *request;
3010fe33eb39SLuis R. Rodriguez 
3011fdc9d7b2SJohannes Berg 	if (WARN_ON(!alpha2 || !wiphy))
3012fdc9d7b2SJohannes Berg 		return -EINVAL;
3013fe33eb39SLuis R. Rodriguez 
30144f7b9140SLuis R. Rodriguez 	wiphy->regulatory_flags &= ~REGULATORY_CUSTOM_REG;
30154f7b9140SLuis R. Rodriguez 
3016fe33eb39SLuis R. Rodriguez 	request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL);
3017fe33eb39SLuis R. Rodriguez 	if (!request)
3018fe33eb39SLuis R. Rodriguez 		return -ENOMEM;
3019fe33eb39SLuis R. Rodriguez 
3020fe33eb39SLuis R. Rodriguez 	request->wiphy_idx = get_wiphy_idx(wiphy);
3021fe33eb39SLuis R. Rodriguez 
3022fe33eb39SLuis R. Rodriguez 	request->alpha2[0] = alpha2[0];
3023fe33eb39SLuis R. Rodriguez 	request->alpha2[1] = alpha2[1];
30247db90f4aSLuis R. Rodriguez 	request->initiator = NL80211_REGDOM_SET_BY_DRIVER;
3025fe33eb39SLuis R. Rodriguez 
3026c37722bdSIlan peer 	/* Allow calling CRDA again */
3027b6863036SJohannes Berg 	reset_crda_timeouts();
3028c37722bdSIlan peer 
3029fe33eb39SLuis R. Rodriguez 	queue_regulatory_request(request);
3030fe33eb39SLuis R. Rodriguez 
3031fe33eb39SLuis R. Rodriguez 	return 0;
3032b2e1b302SLuis R. Rodriguez }
3033b2e1b302SLuis R. Rodriguez EXPORT_SYMBOL(regulatory_hint);
3034b2e1b302SLuis R. Rodriguez 
303557fbcce3SJohannes Berg void regulatory_hint_country_ie(struct wiphy *wiphy, enum nl80211_band band,
30361a919318SJohannes Berg 				const u8 *country_ie, u8 country_ie_len)
30373f2355cbSLuis R. Rodriguez {
30383f2355cbSLuis R. Rodriguez 	char alpha2[2];
30393f2355cbSLuis R. Rodriguez 	enum environment_cap env = ENVIRON_ANY;
3040db2424c5SJohannes Berg 	struct regulatory_request *request = NULL, *lr;
3041d335fe63SLuis R. Rodriguez 
30423f2355cbSLuis R. Rodriguez 	/* IE len must be evenly divisible by 2 */
30433f2355cbSLuis R. Rodriguez 	if (country_ie_len & 0x01)
3044db2424c5SJohannes Berg 		return;
30453f2355cbSLuis R. Rodriguez 
30463f2355cbSLuis R. Rodriguez 	if (country_ie_len < IEEE80211_COUNTRY_IE_MIN_LEN)
3047db2424c5SJohannes Berg 		return;
3048db2424c5SJohannes Berg 
3049db2424c5SJohannes Berg 	request = kzalloc(sizeof(*request), GFP_KERNEL);
3050db2424c5SJohannes Berg 	if (!request)
3051db2424c5SJohannes Berg 		return;
30523f2355cbSLuis R. Rodriguez 
30533f2355cbSLuis R. Rodriguez 	alpha2[0] = country_ie[0];
30543f2355cbSLuis R. Rodriguez 	alpha2[1] = country_ie[1];
30553f2355cbSLuis R. Rodriguez 
30563f2355cbSLuis R. Rodriguez 	if (country_ie[2] == 'I')
30573f2355cbSLuis R. Rodriguez 		env = ENVIRON_INDOOR;
30583f2355cbSLuis R. Rodriguez 	else if (country_ie[2] == 'O')
30593f2355cbSLuis R. Rodriguez 		env = ENVIRON_OUTDOOR;
30603f2355cbSLuis R. Rodriguez 
3061db2424c5SJohannes Berg 	rcu_read_lock();
3062db2424c5SJohannes Berg 	lr = get_last_request();
3063db2424c5SJohannes Berg 
3064db2424c5SJohannes Berg 	if (unlikely(!lr))
3065db2424c5SJohannes Berg 		goto out;
3066db2424c5SJohannes Berg 
3067fb1fc7adSLuis R. Rodriguez 	/*
30688b19e6caSLuis R. Rodriguez 	 * We will run this only upon a successful connection on cfg80211.
30694b44c8bcSLuis R. Rodriguez 	 * We leave conflict resolution to the workqueue, where can hold
30705fe231e8SJohannes Berg 	 * the RTNL.
3071fb1fc7adSLuis R. Rodriguez 	 */
3072c492db37SJohannes Berg 	if (lr->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE &&
3073c492db37SJohannes Berg 	    lr->wiphy_idx != WIPHY_IDX_INVALID)
30743f2355cbSLuis R. Rodriguez 		goto out;
30753f2355cbSLuis R. Rodriguez 
3076fe33eb39SLuis R. Rodriguez 	request->wiphy_idx = get_wiphy_idx(wiphy);
30774f366c5dSJohn W. Linville 	request->alpha2[0] = alpha2[0];
30784f366c5dSJohn W. Linville 	request->alpha2[1] = alpha2[1];
30797db90f4aSLuis R. Rodriguez 	request->initiator = NL80211_REGDOM_SET_BY_COUNTRY_IE;
3080fe33eb39SLuis R. Rodriguez 	request->country_ie_env = env;
30813f2355cbSLuis R. Rodriguez 
3082c37722bdSIlan peer 	/* Allow calling CRDA again */
3083b6863036SJohannes Berg 	reset_crda_timeouts();
3084c37722bdSIlan peer 
3085fe33eb39SLuis R. Rodriguez 	queue_regulatory_request(request);
3086db2424c5SJohannes Berg 	request = NULL;
30873f2355cbSLuis R. Rodriguez out:
3088db2424c5SJohannes Berg 	kfree(request);
3089db2424c5SJohannes Berg 	rcu_read_unlock();
30903f2355cbSLuis R. Rodriguez }
3091b2e1b302SLuis R. Rodriguez 
309209d989d1SLuis R. Rodriguez static void restore_alpha2(char *alpha2, bool reset_user)
309309d989d1SLuis R. Rodriguez {
309409d989d1SLuis R. Rodriguez 	/* indicates there is no alpha2 to consider for restoration */
309509d989d1SLuis R. Rodriguez 	alpha2[0] = '9';
309609d989d1SLuis R. Rodriguez 	alpha2[1] = '7';
309709d989d1SLuis R. Rodriguez 
309809d989d1SLuis R. Rodriguez 	/* The user setting has precedence over the module parameter */
309909d989d1SLuis R. Rodriguez 	if (is_user_regdom_saved()) {
310009d989d1SLuis R. Rodriguez 		/* Unless we're asked to ignore it and reset it */
310109d989d1SLuis R. Rodriguez 		if (reset_user) {
3102c799ba6eSJohannes Berg 			pr_debug("Restoring regulatory settings including user preference\n");
310309d989d1SLuis R. Rodriguez 			user_alpha2[0] = '9';
310409d989d1SLuis R. Rodriguez 			user_alpha2[1] = '7';
310509d989d1SLuis R. Rodriguez 
310609d989d1SLuis R. Rodriguez 			/*
310709d989d1SLuis R. Rodriguez 			 * If we're ignoring user settings, we still need to
310809d989d1SLuis R. Rodriguez 			 * check the module parameter to ensure we put things
310909d989d1SLuis R. Rodriguez 			 * back as they were for a full restore.
311009d989d1SLuis R. Rodriguez 			 */
311109d989d1SLuis R. Rodriguez 			if (!is_world_regdom(ieee80211_regdom)) {
3112c799ba6eSJohannes Berg 				pr_debug("Keeping preference on module parameter ieee80211_regdom: %c%c\n",
31131a919318SJohannes Berg 					 ieee80211_regdom[0], ieee80211_regdom[1]);
311409d989d1SLuis R. Rodriguez 				alpha2[0] = ieee80211_regdom[0];
311509d989d1SLuis R. Rodriguez 				alpha2[1] = ieee80211_regdom[1];
311609d989d1SLuis R. Rodriguez 			}
311709d989d1SLuis R. Rodriguez 		} else {
3118c799ba6eSJohannes Berg 			pr_debug("Restoring regulatory settings while preserving user preference for: %c%c\n",
31191a919318SJohannes Berg 				 user_alpha2[0], user_alpha2[1]);
312009d989d1SLuis R. Rodriguez 			alpha2[0] = user_alpha2[0];
312109d989d1SLuis R. Rodriguez 			alpha2[1] = user_alpha2[1];
312209d989d1SLuis R. Rodriguez 		}
312309d989d1SLuis R. Rodriguez 	} else if (!is_world_regdom(ieee80211_regdom)) {
3124c799ba6eSJohannes Berg 		pr_debug("Keeping preference on module parameter ieee80211_regdom: %c%c\n",
31251a919318SJohannes Berg 			 ieee80211_regdom[0], ieee80211_regdom[1]);
312609d989d1SLuis R. Rodriguez 		alpha2[0] = ieee80211_regdom[0];
312709d989d1SLuis R. Rodriguez 		alpha2[1] = ieee80211_regdom[1];
312809d989d1SLuis R. Rodriguez 	} else
3129c799ba6eSJohannes Berg 		pr_debug("Restoring regulatory settings\n");
313009d989d1SLuis R. Rodriguez }
313109d989d1SLuis R. Rodriguez 
31325ce543d1SRajkumar Manoharan static void restore_custom_reg_settings(struct wiphy *wiphy)
31335ce543d1SRajkumar Manoharan {
31345ce543d1SRajkumar Manoharan 	struct ieee80211_supported_band *sband;
313557fbcce3SJohannes Berg 	enum nl80211_band band;
31365ce543d1SRajkumar Manoharan 	struct ieee80211_channel *chan;
31375ce543d1SRajkumar Manoharan 	int i;
31385ce543d1SRajkumar Manoharan 
313957fbcce3SJohannes Berg 	for (band = 0; band < NUM_NL80211_BANDS; band++) {
31405ce543d1SRajkumar Manoharan 		sband = wiphy->bands[band];
31415ce543d1SRajkumar Manoharan 		if (!sband)
31425ce543d1SRajkumar Manoharan 			continue;
31435ce543d1SRajkumar Manoharan 		for (i = 0; i < sband->n_channels; i++) {
31445ce543d1SRajkumar Manoharan 			chan = &sband->channels[i];
31455ce543d1SRajkumar Manoharan 			chan->flags = chan->orig_flags;
31465ce543d1SRajkumar Manoharan 			chan->max_antenna_gain = chan->orig_mag;
31475ce543d1SRajkumar Manoharan 			chan->max_power = chan->orig_mpwr;
3148899852afSPaul Stewart 			chan->beacon_found = false;
31495ce543d1SRajkumar Manoharan 		}
31505ce543d1SRajkumar Manoharan 	}
31515ce543d1SRajkumar Manoharan }
31525ce543d1SRajkumar Manoharan 
315309d989d1SLuis R. Rodriguez /*
315409d989d1SLuis R. Rodriguez  * Restoring regulatory settings involves ingoring any
315509d989d1SLuis R. Rodriguez  * possibly stale country IE information and user regulatory
315609d989d1SLuis R. Rodriguez  * settings if so desired, this includes any beacon hints
315709d989d1SLuis R. Rodriguez  * learned as we could have traveled outside to another country
315809d989d1SLuis R. Rodriguez  * after disconnection. To restore regulatory settings we do
315909d989d1SLuis R. Rodriguez  * exactly what we did at bootup:
316009d989d1SLuis R. Rodriguez  *
316109d989d1SLuis R. Rodriguez  *   - send a core regulatory hint
316209d989d1SLuis R. Rodriguez  *   - send a user regulatory hint if applicable
316309d989d1SLuis R. Rodriguez  *
316409d989d1SLuis R. Rodriguez  * Device drivers that send a regulatory hint for a specific country
316509d989d1SLuis R. Rodriguez  * keep their own regulatory domain on wiphy->regd so that does does
316609d989d1SLuis R. Rodriguez  * not need to be remembered.
316709d989d1SLuis R. Rodriguez  */
3168e646a025SJohannes Berg static void restore_regulatory_settings(bool reset_user, bool cached)
316909d989d1SLuis R. Rodriguez {
317009d989d1SLuis R. Rodriguez 	char alpha2[2];
3171cee0bec5SDmitry Shmidt 	char world_alpha2[2];
317209d989d1SLuis R. Rodriguez 	struct reg_beacon *reg_beacon, *btmp;
317314609555SLuis R. Rodriguez 	LIST_HEAD(tmp_reg_req_list);
31745ce543d1SRajkumar Manoharan 	struct cfg80211_registered_device *rdev;
317509d989d1SLuis R. Rodriguez 
31765fe231e8SJohannes Berg 	ASSERT_RTNL();
31775fe231e8SJohannes Berg 
317805050753SIlan peer 	/*
317905050753SIlan peer 	 * Clear the indoor setting in case that it is not controlled by user
318005050753SIlan peer 	 * space, as otherwise there is no guarantee that the device is still
318105050753SIlan peer 	 * operating in an indoor environment.
318205050753SIlan peer 	 */
318305050753SIlan peer 	spin_lock(&reg_indoor_lock);
318405050753SIlan peer 	if (reg_is_indoor && !reg_is_indoor_portid) {
318552616f2bSIlan Peer 		reg_is_indoor = false;
318605050753SIlan peer 		reg_check_channels();
318705050753SIlan peer 	}
318805050753SIlan peer 	spin_unlock(&reg_indoor_lock);
318952616f2bSIlan Peer 
31902d319867SJohannes Berg 	reset_regdomains(true, &world_regdom);
319109d989d1SLuis R. Rodriguez 	restore_alpha2(alpha2, reset_user);
319209d989d1SLuis R. Rodriguez 
319314609555SLuis R. Rodriguez 	/*
319414609555SLuis R. Rodriguez 	 * If there's any pending requests we simply
319514609555SLuis R. Rodriguez 	 * stash them to a temporary pending queue and
319614609555SLuis R. Rodriguez 	 * add then after we've restored regulatory
319714609555SLuis R. Rodriguez 	 * settings.
319814609555SLuis R. Rodriguez 	 */
319914609555SLuis R. Rodriguez 	spin_lock(&reg_requests_lock);
3200eeca9fceSIlan peer 	list_splice_tail_init(&reg_requests_list, &tmp_reg_req_list);
320114609555SLuis R. Rodriguez 	spin_unlock(&reg_requests_lock);
320214609555SLuis R. Rodriguez 
320309d989d1SLuis R. Rodriguez 	/* Clear beacon hints */
320409d989d1SLuis R. Rodriguez 	spin_lock_bh(&reg_pending_beacons_lock);
3205fea9bcedSJohannes Berg 	list_for_each_entry_safe(reg_beacon, btmp, &reg_pending_beacons, list) {
320609d989d1SLuis R. Rodriguez 		list_del(&reg_beacon->list);
320709d989d1SLuis R. Rodriguez 		kfree(reg_beacon);
320809d989d1SLuis R. Rodriguez 	}
320909d989d1SLuis R. Rodriguez 	spin_unlock_bh(&reg_pending_beacons_lock);
321009d989d1SLuis R. Rodriguez 
3211fea9bcedSJohannes Berg 	list_for_each_entry_safe(reg_beacon, btmp, &reg_beacon_list, list) {
321209d989d1SLuis R. Rodriguez 		list_del(&reg_beacon->list);
321309d989d1SLuis R. Rodriguez 		kfree(reg_beacon);
321409d989d1SLuis R. Rodriguez 	}
321509d989d1SLuis R. Rodriguez 
321609d989d1SLuis R. Rodriguez 	/* First restore to the basic regulatory settings */
3217379b82f4SJohannes Berg 	world_alpha2[0] = cfg80211_world_regdom->alpha2[0];
3218379b82f4SJohannes Berg 	world_alpha2[1] = cfg80211_world_regdom->alpha2[1];
321909d989d1SLuis R. Rodriguez 
32205ce543d1SRajkumar Manoharan 	list_for_each_entry(rdev, &cfg80211_rdev_list, list) {
3221b0d7aa59SJonathan Doron 		if (rdev->wiphy.regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED)
3222b0d7aa59SJonathan Doron 			continue;
3223a2f73b6cSLuis R. Rodriguez 		if (rdev->wiphy.regulatory_flags & REGULATORY_CUSTOM_REG)
32245ce543d1SRajkumar Manoharan 			restore_custom_reg_settings(&rdev->wiphy);
32255ce543d1SRajkumar Manoharan 	}
32265ce543d1SRajkumar Manoharan 
3227e646a025SJohannes Berg 	if (cached && (!is_an_alpha2(alpha2) ||
3228e646a025SJohannes Berg 		       !IS_ERR_OR_NULL(cfg80211_user_regdom))) {
3229e646a025SJohannes Berg 		reset_regdomains(false, cfg80211_world_regdom);
3230e646a025SJohannes Berg 		update_all_wiphy_regulatory(NL80211_REGDOM_SET_BY_CORE);
3231e646a025SJohannes Berg 		print_regdomain(get_cfg80211_regdom());
3232e646a025SJohannes Berg 		nl80211_send_reg_change_event(&core_request_world);
3233e646a025SJohannes Berg 		reg_set_request_processed();
3234e646a025SJohannes Berg 
3235e646a025SJohannes Berg 		if (is_an_alpha2(alpha2) &&
3236e646a025SJohannes Berg 		    !regulatory_hint_user(alpha2, NL80211_USER_REG_HINT_USER)) {
3237e646a025SJohannes Berg 			struct regulatory_request *ureq;
3238e646a025SJohannes Berg 
3239e646a025SJohannes Berg 			spin_lock(&reg_requests_lock);
3240e646a025SJohannes Berg 			ureq = list_last_entry(&reg_requests_list,
3241e646a025SJohannes Berg 					       struct regulatory_request,
3242e646a025SJohannes Berg 					       list);
3243e646a025SJohannes Berg 			list_del(&ureq->list);
3244e646a025SJohannes Berg 			spin_unlock(&reg_requests_lock);
3245e646a025SJohannes Berg 
3246e646a025SJohannes Berg 			notify_self_managed_wiphys(ureq);
3247e646a025SJohannes Berg 			reg_update_last_request(ureq);
3248e646a025SJohannes Berg 			set_regdom(reg_copy_regd(cfg80211_user_regdom),
3249e646a025SJohannes Berg 				   REGD_SOURCE_CACHED);
3250e646a025SJohannes Berg 		}
3251e646a025SJohannes Berg 	} else {
3252cee0bec5SDmitry Shmidt 		regulatory_hint_core(world_alpha2);
325309d989d1SLuis R. Rodriguez 
325409d989d1SLuis R. Rodriguez 		/*
325509d989d1SLuis R. Rodriguez 		 * This restores the ieee80211_regdom module parameter
325609d989d1SLuis R. Rodriguez 		 * preference or the last user requested regulatory
325709d989d1SLuis R. Rodriguez 		 * settings, user regulatory settings takes precedence.
325809d989d1SLuis R. Rodriguez 		 */
325909d989d1SLuis R. Rodriguez 		if (is_an_alpha2(alpha2))
3260549cc1c5SMaciej S. Szmigiero 			regulatory_hint_user(alpha2, NL80211_USER_REG_HINT_USER);
3261e646a025SJohannes Berg 	}
326209d989d1SLuis R. Rodriguez 
326314609555SLuis R. Rodriguez 	spin_lock(&reg_requests_lock);
326411cff96cSJohannes Berg 	list_splice_tail_init(&tmp_reg_req_list, &reg_requests_list);
326514609555SLuis R. Rodriguez 	spin_unlock(&reg_requests_lock);
326614609555SLuis R. Rodriguez 
3267c799ba6eSJohannes Berg 	pr_debug("Kicking the queue\n");
326814609555SLuis R. Rodriguez 
326914609555SLuis R. Rodriguez 	schedule_work(&reg_work);
327014609555SLuis R. Rodriguez }
327109d989d1SLuis R. Rodriguez 
32727417844bSRajeev Kumar Sirasanagandla static bool is_wiphy_all_set_reg_flag(enum ieee80211_regulatory_flags flag)
32737417844bSRajeev Kumar Sirasanagandla {
32747417844bSRajeev Kumar Sirasanagandla 	struct cfg80211_registered_device *rdev;
32757417844bSRajeev Kumar Sirasanagandla 	struct wireless_dev *wdev;
32767417844bSRajeev Kumar Sirasanagandla 
32777417844bSRajeev Kumar Sirasanagandla 	list_for_each_entry(rdev, &cfg80211_rdev_list, list) {
32787417844bSRajeev Kumar Sirasanagandla 		list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
32797417844bSRajeev Kumar Sirasanagandla 			wdev_lock(wdev);
32807417844bSRajeev Kumar Sirasanagandla 			if (!(wdev->wiphy->regulatory_flags & flag)) {
32817417844bSRajeev Kumar Sirasanagandla 				wdev_unlock(wdev);
32827417844bSRajeev Kumar Sirasanagandla 				return false;
32837417844bSRajeev Kumar Sirasanagandla 			}
32847417844bSRajeev Kumar Sirasanagandla 			wdev_unlock(wdev);
32857417844bSRajeev Kumar Sirasanagandla 		}
32867417844bSRajeev Kumar Sirasanagandla 	}
32877417844bSRajeev Kumar Sirasanagandla 
32887417844bSRajeev Kumar Sirasanagandla 	return true;
32897417844bSRajeev Kumar Sirasanagandla }
32907417844bSRajeev Kumar Sirasanagandla 
329109d989d1SLuis R. Rodriguez void regulatory_hint_disconnect(void)
329209d989d1SLuis R. Rodriguez {
32937417844bSRajeev Kumar Sirasanagandla 	/* Restore of regulatory settings is not required when wiphy(s)
32947417844bSRajeev Kumar Sirasanagandla 	 * ignore IE from connected access point but clearance of beacon hints
32957417844bSRajeev Kumar Sirasanagandla 	 * is required when wiphy(s) supports beacon hints.
32967417844bSRajeev Kumar Sirasanagandla 	 */
32977417844bSRajeev Kumar Sirasanagandla 	if (is_wiphy_all_set_reg_flag(REGULATORY_COUNTRY_IE_IGNORE)) {
32987417844bSRajeev Kumar Sirasanagandla 		struct reg_beacon *reg_beacon, *btmp;
32997417844bSRajeev Kumar Sirasanagandla 
33007417844bSRajeev Kumar Sirasanagandla 		if (is_wiphy_all_set_reg_flag(REGULATORY_DISABLE_BEACON_HINTS))
33017417844bSRajeev Kumar Sirasanagandla 			return;
33027417844bSRajeev Kumar Sirasanagandla 
33037417844bSRajeev Kumar Sirasanagandla 		spin_lock_bh(&reg_pending_beacons_lock);
33047417844bSRajeev Kumar Sirasanagandla 		list_for_each_entry_safe(reg_beacon, btmp,
33057417844bSRajeev Kumar Sirasanagandla 					 &reg_pending_beacons, list) {
33067417844bSRajeev Kumar Sirasanagandla 			list_del(&reg_beacon->list);
33077417844bSRajeev Kumar Sirasanagandla 			kfree(reg_beacon);
33087417844bSRajeev Kumar Sirasanagandla 		}
33097417844bSRajeev Kumar Sirasanagandla 		spin_unlock_bh(&reg_pending_beacons_lock);
33107417844bSRajeev Kumar Sirasanagandla 
33117417844bSRajeev Kumar Sirasanagandla 		list_for_each_entry_safe(reg_beacon, btmp,
33127417844bSRajeev Kumar Sirasanagandla 					 &reg_beacon_list, list) {
33137417844bSRajeev Kumar Sirasanagandla 			list_del(&reg_beacon->list);
33147417844bSRajeev Kumar Sirasanagandla 			kfree(reg_beacon);
33157417844bSRajeev Kumar Sirasanagandla 		}
33167417844bSRajeev Kumar Sirasanagandla 
33177417844bSRajeev Kumar Sirasanagandla 		return;
33187417844bSRajeev Kumar Sirasanagandla 	}
33197417844bSRajeev Kumar Sirasanagandla 
3320c799ba6eSJohannes Berg 	pr_debug("All devices are disconnected, going to restore regulatory settings\n");
3321e646a025SJohannes Berg 	restore_regulatory_settings(false, true);
332209d989d1SLuis R. Rodriguez }
332309d989d1SLuis R. Rodriguez 
33249cf0a0b4SAlexei Avshalom Lazar static bool freq_is_chan_12_13_14(u32 freq)
3325e38f8a7aSLuis R. Rodriguez {
332657fbcce3SJohannes Berg 	if (freq == ieee80211_channel_to_frequency(12, NL80211_BAND_2GHZ) ||
332757fbcce3SJohannes Berg 	    freq == ieee80211_channel_to_frequency(13, NL80211_BAND_2GHZ) ||
332857fbcce3SJohannes Berg 	    freq == ieee80211_channel_to_frequency(14, NL80211_BAND_2GHZ))
3329e38f8a7aSLuis R. Rodriguez 		return true;
3330e38f8a7aSLuis R. Rodriguez 	return false;
3331e38f8a7aSLuis R. Rodriguez }
3332e38f8a7aSLuis R. Rodriguez 
33333ebfa6e7SLuis R. Rodriguez static bool pending_reg_beacon(struct ieee80211_channel *beacon_chan)
33343ebfa6e7SLuis R. Rodriguez {
33353ebfa6e7SLuis R. Rodriguez 	struct reg_beacon *pending_beacon;
33363ebfa6e7SLuis R. Rodriguez 
33373ebfa6e7SLuis R. Rodriguez 	list_for_each_entry(pending_beacon, &reg_pending_beacons, list)
33383ebfa6e7SLuis R. Rodriguez 		if (beacon_chan->center_freq ==
33393ebfa6e7SLuis R. Rodriguez 		    pending_beacon->chan.center_freq)
33403ebfa6e7SLuis R. Rodriguez 			return true;
33413ebfa6e7SLuis R. Rodriguez 	return false;
33423ebfa6e7SLuis R. Rodriguez }
33433ebfa6e7SLuis R. Rodriguez 
3344e38f8a7aSLuis R. Rodriguez int regulatory_hint_found_beacon(struct wiphy *wiphy,
3345e38f8a7aSLuis R. Rodriguez 				 struct ieee80211_channel *beacon_chan,
3346e38f8a7aSLuis R. Rodriguez 				 gfp_t gfp)
3347e38f8a7aSLuis R. Rodriguez {
3348e38f8a7aSLuis R. Rodriguez 	struct reg_beacon *reg_beacon;
33493ebfa6e7SLuis R. Rodriguez 	bool processing;
3350e38f8a7aSLuis R. Rodriguez 
33511a919318SJohannes Berg 	if (beacon_chan->beacon_found ||
33521a919318SJohannes Berg 	    beacon_chan->flags & IEEE80211_CHAN_RADAR ||
335357fbcce3SJohannes Berg 	    (beacon_chan->band == NL80211_BAND_2GHZ &&
33541a919318SJohannes Berg 	     !freq_is_chan_12_13_14(beacon_chan->center_freq)))
3355e38f8a7aSLuis R. Rodriguez 		return 0;
3356e38f8a7aSLuis R. Rodriguez 
33573ebfa6e7SLuis R. Rodriguez 	spin_lock_bh(&reg_pending_beacons_lock);
33583ebfa6e7SLuis R. Rodriguez 	processing = pending_reg_beacon(beacon_chan);
33593ebfa6e7SLuis R. Rodriguez 	spin_unlock_bh(&reg_pending_beacons_lock);
33603ebfa6e7SLuis R. Rodriguez 
33613ebfa6e7SLuis R. Rodriguez 	if (processing)
3362e38f8a7aSLuis R. Rodriguez 		return 0;
3363e38f8a7aSLuis R. Rodriguez 
3364e38f8a7aSLuis R. Rodriguez 	reg_beacon = kzalloc(sizeof(struct reg_beacon), gfp);
3365e38f8a7aSLuis R. Rodriguez 	if (!reg_beacon)
3366e38f8a7aSLuis R. Rodriguez 		return -ENOMEM;
3367e38f8a7aSLuis R. Rodriguez 
3368c799ba6eSJohannes Berg 	pr_debug("Found new beacon on frequency: %d MHz (Ch %d) on %s\n",
3369e38f8a7aSLuis R. Rodriguez 		 beacon_chan->center_freq,
3370e38f8a7aSLuis R. Rodriguez 		 ieee80211_frequency_to_channel(beacon_chan->center_freq),
3371e38f8a7aSLuis R. Rodriguez 		 wiphy_name(wiphy));
33724113f751SLuis R. Rodriguez 
3373e38f8a7aSLuis R. Rodriguez 	memcpy(&reg_beacon->chan, beacon_chan,
3374e38f8a7aSLuis R. Rodriguez 	       sizeof(struct ieee80211_channel));
3375e38f8a7aSLuis R. Rodriguez 
3376e38f8a7aSLuis R. Rodriguez 	/*
3377e38f8a7aSLuis R. Rodriguez 	 * Since we can be called from BH or and non-BH context
3378e38f8a7aSLuis R. Rodriguez 	 * we must use spin_lock_bh()
3379e38f8a7aSLuis R. Rodriguez 	 */
3380e38f8a7aSLuis R. Rodriguez 	spin_lock_bh(&reg_pending_beacons_lock);
3381e38f8a7aSLuis R. Rodriguez 	list_add_tail(&reg_beacon->list, &reg_pending_beacons);
3382e38f8a7aSLuis R. Rodriguez 	spin_unlock_bh(&reg_pending_beacons_lock);
3383e38f8a7aSLuis R. Rodriguez 
3384e38f8a7aSLuis R. Rodriguez 	schedule_work(&reg_work);
3385e38f8a7aSLuis R. Rodriguez 
3386e38f8a7aSLuis R. Rodriguez 	return 0;
3387e38f8a7aSLuis R. Rodriguez }
3388e38f8a7aSLuis R. Rodriguez 
3389a3d2eaf0SJohannes Berg static void print_rd_rules(const struct ieee80211_regdomain *rd)
3390b2e1b302SLuis R. Rodriguez {
3391b2e1b302SLuis R. Rodriguez 	unsigned int i;
3392a3d2eaf0SJohannes Berg 	const struct ieee80211_reg_rule *reg_rule = NULL;
3393a3d2eaf0SJohannes Berg 	const struct ieee80211_freq_range *freq_range = NULL;
3394a3d2eaf0SJohannes Berg 	const struct ieee80211_power_rule *power_rule = NULL;
3395089027e5SJanusz Dziedzic 	char bw[32], cac_time[32];
3396b2e1b302SLuis R. Rodriguez 
339794c4fd64SDave Young 	pr_debug("  (start_freq - end_freq @ bandwidth), (max_antenna_gain, max_eirp), (dfs_cac_time)\n");
3398b2e1b302SLuis R. Rodriguez 
3399b2e1b302SLuis R. Rodriguez 	for (i = 0; i < rd->n_reg_rules; i++) {
3400b2e1b302SLuis R. Rodriguez 		reg_rule = &rd->reg_rules[i];
3401b2e1b302SLuis R. Rodriguez 		freq_range = &reg_rule->freq_range;
3402b2e1b302SLuis R. Rodriguez 		power_rule = &reg_rule->power_rule;
3403b2e1b302SLuis R. Rodriguez 
3404b0dfd2eaSJanusz Dziedzic 		if (reg_rule->flags & NL80211_RRF_AUTO_BW)
3405b0dfd2eaSJanusz Dziedzic 			snprintf(bw, sizeof(bw), "%d KHz, %d KHz AUTO",
3406b0dfd2eaSJanusz Dziedzic 				 freq_range->max_bandwidth_khz,
340797524820SJanusz Dziedzic 				 reg_get_max_bandwidth(rd, reg_rule));
340897524820SJanusz Dziedzic 		else
3409b0dfd2eaSJanusz Dziedzic 			snprintf(bw, sizeof(bw), "%d KHz",
341097524820SJanusz Dziedzic 				 freq_range->max_bandwidth_khz);
341197524820SJanusz Dziedzic 
3412089027e5SJanusz Dziedzic 		if (reg_rule->flags & NL80211_RRF_DFS)
3413089027e5SJanusz Dziedzic 			scnprintf(cac_time, sizeof(cac_time), "%u s",
3414089027e5SJanusz Dziedzic 				  reg_rule->dfs_cac_ms/1000);
3415089027e5SJanusz Dziedzic 		else
3416089027e5SJanusz Dziedzic 			scnprintf(cac_time, sizeof(cac_time), "N/A");
3417089027e5SJanusz Dziedzic 
3418089027e5SJanusz Dziedzic 
3419fb1fc7adSLuis R. Rodriguez 		/*
3420fb1fc7adSLuis R. Rodriguez 		 * There may not be documentation for max antenna gain
3421fb1fc7adSLuis R. Rodriguez 		 * in certain regions
3422fb1fc7adSLuis R. Rodriguez 		 */
3423b2e1b302SLuis R. Rodriguez 		if (power_rule->max_antenna_gain)
342494c4fd64SDave Young 			pr_debug("  (%d KHz - %d KHz @ %s), (%d mBi, %d mBm), (%s)\n",
3425b2e1b302SLuis R. Rodriguez 				freq_range->start_freq_khz,
3426b2e1b302SLuis R. Rodriguez 				freq_range->end_freq_khz,
342797524820SJanusz Dziedzic 				bw,
3428b2e1b302SLuis R. Rodriguez 				power_rule->max_antenna_gain,
3429089027e5SJanusz Dziedzic 				power_rule->max_eirp,
3430089027e5SJanusz Dziedzic 				cac_time);
3431b2e1b302SLuis R. Rodriguez 		else
343294c4fd64SDave Young 			pr_debug("  (%d KHz - %d KHz @ %s), (N/A, %d mBm), (%s)\n",
3433b2e1b302SLuis R. Rodriguez 				freq_range->start_freq_khz,
3434b2e1b302SLuis R. Rodriguez 				freq_range->end_freq_khz,
343597524820SJanusz Dziedzic 				bw,
3436089027e5SJanusz Dziedzic 				power_rule->max_eirp,
3437089027e5SJanusz Dziedzic 				cac_time);
3438b2e1b302SLuis R. Rodriguez 	}
3439b2e1b302SLuis R. Rodriguez }
3440b2e1b302SLuis R. Rodriguez 
34414c7d3982SLuis R. Rodriguez bool reg_supported_dfs_region(enum nl80211_dfs_regions dfs_region)
34428b60b078SLuis R. Rodriguez {
34438b60b078SLuis R. Rodriguez 	switch (dfs_region) {
34448b60b078SLuis R. Rodriguez 	case NL80211_DFS_UNSET:
34458b60b078SLuis R. Rodriguez 	case NL80211_DFS_FCC:
34468b60b078SLuis R. Rodriguez 	case NL80211_DFS_ETSI:
34478b60b078SLuis R. Rodriguez 	case NL80211_DFS_JP:
34488b60b078SLuis R. Rodriguez 		return true;
34498b60b078SLuis R. Rodriguez 	default:
34504a22b00bSColin Ian King 		pr_debug("Ignoring unknown DFS master region: %d\n", dfs_region);
34518b60b078SLuis R. Rodriguez 		return false;
34528b60b078SLuis R. Rodriguez 	}
34538b60b078SLuis R. Rodriguez }
34548b60b078SLuis R. Rodriguez 
3455a3d2eaf0SJohannes Berg static void print_regdomain(const struct ieee80211_regdomain *rd)
3456b2e1b302SLuis R. Rodriguez {
3457c492db37SJohannes Berg 	struct regulatory_request *lr = get_last_request();
3458b2e1b302SLuis R. Rodriguez 
34593f2355cbSLuis R. Rodriguez 	if (is_intersected_alpha2(rd->alpha2)) {
3460c492db37SJohannes Berg 		if (lr->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE) {
346179c97e97SJohannes Berg 			struct cfg80211_registered_device *rdev;
3462c492db37SJohannes Berg 			rdev = cfg80211_rdev_by_wiphy_idx(lr->wiphy_idx);
346379c97e97SJohannes Berg 			if (rdev) {
346494c4fd64SDave Young 				pr_debug("Current regulatory domain updated by AP to: %c%c\n",
346579c97e97SJohannes Berg 					rdev->country_ie_alpha2[0],
346679c97e97SJohannes Berg 					rdev->country_ie_alpha2[1]);
34673f2355cbSLuis R. Rodriguez 			} else
346894c4fd64SDave Young 				pr_debug("Current regulatory domain intersected:\n");
34693f2355cbSLuis R. Rodriguez 		} else
347094c4fd64SDave Young 			pr_debug("Current regulatory domain intersected:\n");
34711a919318SJohannes Berg 	} else if (is_world_regdom(rd->alpha2)) {
347294c4fd64SDave Young 		pr_debug("World regulatory domain updated:\n");
34731a919318SJohannes Berg 	} else {
3474b2e1b302SLuis R. Rodriguez 		if (is_unknown_alpha2(rd->alpha2))
347594c4fd64SDave Young 			pr_debug("Regulatory domain changed to driver built-in settings (unknown country)\n");
347657b5ce07SLuis R. Rodriguez 		else {
3477c492db37SJohannes Berg 			if (reg_request_cell_base(lr))
347894c4fd64SDave Young 				pr_debug("Regulatory domain changed to country: %c%c by Cell Station\n",
3479b2e1b302SLuis R. Rodriguez 					rd->alpha2[0], rd->alpha2[1]);
348057b5ce07SLuis R. Rodriguez 			else
348194c4fd64SDave Young 				pr_debug("Regulatory domain changed to country: %c%c\n",
348257b5ce07SLuis R. Rodriguez 					rd->alpha2[0], rd->alpha2[1]);
348357b5ce07SLuis R. Rodriguez 		}
3484b2e1b302SLuis R. Rodriguez 	}
34851a919318SJohannes Berg 
348694c4fd64SDave Young 	pr_debug(" DFS Master region: %s", reg_dfs_region_str(rd->dfs_region));
3487b2e1b302SLuis R. Rodriguez 	print_rd_rules(rd);
3488b2e1b302SLuis R. Rodriguez }
3489b2e1b302SLuis R. Rodriguez 
34902df78167SJohannes Berg static void print_regdomain_info(const struct ieee80211_regdomain *rd)
3491b2e1b302SLuis R. Rodriguez {
349294c4fd64SDave Young 	pr_debug("Regulatory domain: %c%c\n", rd->alpha2[0], rd->alpha2[1]);
3493b2e1b302SLuis R. Rodriguez 	print_rd_rules(rd);
3494b2e1b302SLuis R. Rodriguez }
3495b2e1b302SLuis R. Rodriguez 
34963b9e5acaSLuis R. Rodriguez static int reg_set_rd_core(const struct ieee80211_regdomain *rd)
34973b9e5acaSLuis R. Rodriguez {
34983b9e5acaSLuis R. Rodriguez 	if (!is_world_regdom(rd->alpha2))
34993b9e5acaSLuis R. Rodriguez 		return -EINVAL;
35003b9e5acaSLuis R. Rodriguez 	update_world_regdomain(rd);
35013b9e5acaSLuis R. Rodriguez 	return 0;
35023b9e5acaSLuis R. Rodriguez }
35033b9e5acaSLuis R. Rodriguez 
350484721d44SLuis R. Rodriguez static int reg_set_rd_user(const struct ieee80211_regdomain *rd,
350584721d44SLuis R. Rodriguez 			   struct regulatory_request *user_request)
350684721d44SLuis R. Rodriguez {
350784721d44SLuis R. Rodriguez 	const struct ieee80211_regdomain *intersected_rd = NULL;
350884721d44SLuis R. Rodriguez 
350984721d44SLuis R. Rodriguez 	if (!regdom_changes(rd->alpha2))
351084721d44SLuis R. Rodriguez 		return -EALREADY;
351184721d44SLuis R. Rodriguez 
351284721d44SLuis R. Rodriguez 	if (!is_valid_rd(rd)) {
351394c4fd64SDave Young 		pr_err("Invalid regulatory domain detected: %c%c\n",
351494c4fd64SDave Young 		       rd->alpha2[0], rd->alpha2[1]);
351584721d44SLuis R. Rodriguez 		print_regdomain_info(rd);
351684721d44SLuis R. Rodriguez 		return -EINVAL;
351784721d44SLuis R. Rodriguez 	}
351884721d44SLuis R. Rodriguez 
351984721d44SLuis R. Rodriguez 	if (!user_request->intersect) {
352084721d44SLuis R. Rodriguez 		reset_regdomains(false, rd);
352184721d44SLuis R. Rodriguez 		return 0;
352284721d44SLuis R. Rodriguez 	}
352384721d44SLuis R. Rodriguez 
352484721d44SLuis R. Rodriguez 	intersected_rd = regdom_intersect(rd, get_cfg80211_regdom());
352584721d44SLuis R. Rodriguez 	if (!intersected_rd)
352684721d44SLuis R. Rodriguez 		return -EINVAL;
352784721d44SLuis R. Rodriguez 
352884721d44SLuis R. Rodriguez 	kfree(rd);
352984721d44SLuis R. Rodriguez 	rd = NULL;
353084721d44SLuis R. Rodriguez 	reset_regdomains(false, intersected_rd);
353184721d44SLuis R. Rodriguez 
353284721d44SLuis R. Rodriguez 	return 0;
353384721d44SLuis R. Rodriguez }
353484721d44SLuis R. Rodriguez 
3535f5fe3247SLuis R. Rodriguez static int reg_set_rd_driver(const struct ieee80211_regdomain *rd,
3536f5fe3247SLuis R. Rodriguez 			     struct regulatory_request *driver_request)
3537b2e1b302SLuis R. Rodriguez {
3538e9763c3cSJohannes Berg 	const struct ieee80211_regdomain *regd;
35399c96477dSLuis R. Rodriguez 	const struct ieee80211_regdomain *intersected_rd = NULL;
3540f5fe3247SLuis R. Rodriguez 	const struct ieee80211_regdomain *tmp;
3541806a9e39SLuis R. Rodriguez 	struct wiphy *request_wiphy;
35426913b49aSJohannes Berg 
3543f5fe3247SLuis R. Rodriguez 	if (is_world_regdom(rd->alpha2))
3544b2e1b302SLuis R. Rodriguez 		return -EINVAL;
3545b2e1b302SLuis R. Rodriguez 
3546baeb66feSJohn W. Linville 	if (!regdom_changes(rd->alpha2))
354795908535SKalle Valo 		return -EALREADY;
3548b2e1b302SLuis R. Rodriguez 
3549b2e1b302SLuis R. Rodriguez 	if (!is_valid_rd(rd)) {
355094c4fd64SDave Young 		pr_err("Invalid regulatory domain detected: %c%c\n",
355194c4fd64SDave Young 		       rd->alpha2[0], rd->alpha2[1]);
3552b2e1b302SLuis R. Rodriguez 		print_regdomain_info(rd);
3553b2e1b302SLuis R. Rodriguez 		return -EINVAL;
3554b2e1b302SLuis R. Rodriguez 	}
3555b2e1b302SLuis R. Rodriguez 
3556f5fe3247SLuis R. Rodriguez 	request_wiphy = wiphy_idx_to_wiphy(driver_request->wiphy_idx);
3557922ec58cSJohannes Berg 	if (!request_wiphy)
3558de3584bdSJohannes Berg 		return -ENODEV;
3559806a9e39SLuis R. Rodriguez 
3560f5fe3247SLuis R. Rodriguez 	if (!driver_request->intersect) {
3561558f6d32SLuis R. Rodriguez 		if (request_wiphy->regd)
3562558f6d32SLuis R. Rodriguez 			return -EALREADY;
35633e0c3ff3SLuis R. Rodriguez 
3564e9763c3cSJohannes Berg 		regd = reg_copy_regd(rd);
3565e9763c3cSJohannes Berg 		if (IS_ERR(regd))
3566e9763c3cSJohannes Berg 			return PTR_ERR(regd);
35673e0c3ff3SLuis R. Rodriguez 
3568458f4f9eSJohannes Berg 		rcu_assign_pointer(request_wiphy->regd, regd);
3569379b82f4SJohannes Berg 		reset_regdomains(false, rd);
3570b8295acdSLuis R. Rodriguez 		return 0;
3571b8295acdSLuis R. Rodriguez 	}
3572b8295acdSLuis R. Rodriguez 
3573458f4f9eSJohannes Berg 	intersected_rd = regdom_intersect(rd, get_cfg80211_regdom());
35749c96477dSLuis R. Rodriguez 	if (!intersected_rd)
35759c96477dSLuis R. Rodriguez 		return -EINVAL;
3576b8295acdSLuis R. Rodriguez 
3577fb1fc7adSLuis R. Rodriguez 	/*
3578fb1fc7adSLuis R. Rodriguez 	 * We can trash what CRDA provided now.
35793e0c3ff3SLuis R. Rodriguez 	 * However if a driver requested this specific regulatory
3580fb1fc7adSLuis R. Rodriguez 	 * domain we keep it for its private use
3581fb1fc7adSLuis R. Rodriguez 	 */
3582b7566fc3SLarry Finger 	tmp = get_wiphy_regdom(request_wiphy);
3583458f4f9eSJohannes Berg 	rcu_assign_pointer(request_wiphy->regd, rd);
3584b7566fc3SLarry Finger 	rcu_free_regdom(tmp);
35853e0c3ff3SLuis R. Rodriguez 
3586b8295acdSLuis R. Rodriguez 	rd = NULL;
3587b8295acdSLuis R. Rodriguez 
3588379b82f4SJohannes Berg 	reset_regdomains(false, intersected_rd);
3589b8295acdSLuis R. Rodriguez 
3590b8295acdSLuis R. Rodriguez 	return 0;
35919c96477dSLuis R. Rodriguez }
35929c96477dSLuis R. Rodriguez 
359301992406SLuis R. Rodriguez static int reg_set_rd_country_ie(const struct ieee80211_regdomain *rd,
359401992406SLuis R. Rodriguez 				 struct regulatory_request *country_ie_request)
3595f5fe3247SLuis R. Rodriguez {
3596f5fe3247SLuis R. Rodriguez 	struct wiphy *request_wiphy;
3597f5fe3247SLuis R. Rodriguez 
3598f5fe3247SLuis R. Rodriguez 	if (!is_alpha2_set(rd->alpha2) && !is_an_alpha2(rd->alpha2) &&
3599f5fe3247SLuis R. Rodriguez 	    !is_unknown_alpha2(rd->alpha2))
3600f5fe3247SLuis R. Rodriguez 		return -EINVAL;
3601f5fe3247SLuis R. Rodriguez 
3602f5fe3247SLuis R. Rodriguez 	/*
3603f5fe3247SLuis R. Rodriguez 	 * Lets only bother proceeding on the same alpha2 if the current
3604f5fe3247SLuis R. Rodriguez 	 * rd is non static (it means CRDA was present and was used last)
3605f5fe3247SLuis R. Rodriguez 	 * and the pending request came in from a country IE
3606f5fe3247SLuis R. Rodriguez 	 */
3607f5fe3247SLuis R. Rodriguez 
3608f5fe3247SLuis R. Rodriguez 	if (!is_valid_rd(rd)) {
360994c4fd64SDave Young 		pr_err("Invalid regulatory domain detected: %c%c\n",
361094c4fd64SDave Young 		       rd->alpha2[0], rd->alpha2[1]);
3611f5fe3247SLuis R. Rodriguez 		print_regdomain_info(rd);
36123f2355cbSLuis R. Rodriguez 		return -EINVAL;
3613b2e1b302SLuis R. Rodriguez 	}
3614b2e1b302SLuis R. Rodriguez 
361501992406SLuis R. Rodriguez 	request_wiphy = wiphy_idx_to_wiphy(country_ie_request->wiphy_idx);
3616922ec58cSJohannes Berg 	if (!request_wiphy)
3617f5fe3247SLuis R. Rodriguez 		return -ENODEV;
3618f5fe3247SLuis R. Rodriguez 
361901992406SLuis R. Rodriguez 	if (country_ie_request->intersect)
3620f5fe3247SLuis R. Rodriguez 		return -EINVAL;
3621f5fe3247SLuis R. Rodriguez 
3622f5fe3247SLuis R. Rodriguez 	reset_regdomains(false, rd);
3623f5fe3247SLuis R. Rodriguez 	return 0;
3624f5fe3247SLuis R. Rodriguez }
3625b2e1b302SLuis R. Rodriguez 
3626fb1fc7adSLuis R. Rodriguez /*
3627fb1fc7adSLuis R. Rodriguez  * Use this call to set the current regulatory domain. Conflicts with
3628b2e1b302SLuis R. Rodriguez  * multiple drivers can be ironed out later. Caller must've already
3629458f4f9eSJohannes Berg  * kmalloc'd the rd structure.
3630fb1fc7adSLuis R. Rodriguez  */
3631c37722bdSIlan peer int set_regdom(const struct ieee80211_regdomain *rd,
3632c37722bdSIlan peer 	       enum ieee80211_regd_source regd_src)
3633b2e1b302SLuis R. Rodriguez {
3634c492db37SJohannes Berg 	struct regulatory_request *lr;
3635092008abSJanusz Dziedzic 	bool user_reset = false;
3636b2e1b302SLuis R. Rodriguez 	int r;
3637b2e1b302SLuis R. Rodriguez 
3638e646a025SJohannes Berg 	if (IS_ERR_OR_NULL(rd))
3639e646a025SJohannes Berg 		return -ENODATA;
3640e646a025SJohannes Berg 
36413b9e5acaSLuis R. Rodriguez 	if (!reg_is_valid_request(rd->alpha2)) {
36423b9e5acaSLuis R. Rodriguez 		kfree(rd);
36433b9e5acaSLuis R. Rodriguez 		return -EINVAL;
36443b9e5acaSLuis R. Rodriguez 	}
36453b9e5acaSLuis R. Rodriguez 
3646c37722bdSIlan peer 	if (regd_src == REGD_SOURCE_CRDA)
3647b6863036SJohannes Berg 		reset_crda_timeouts();
3648c37722bdSIlan peer 
3649c492db37SJohannes Berg 	lr = get_last_request();
3650abc7381bSLuis R. Rodriguez 
3651b2e1b302SLuis R. Rodriguez 	/* Note that this doesn't update the wiphys, this is done below */
36523b9e5acaSLuis R. Rodriguez 	switch (lr->initiator) {
36533b9e5acaSLuis R. Rodriguez 	case NL80211_REGDOM_SET_BY_CORE:
36543b9e5acaSLuis R. Rodriguez 		r = reg_set_rd_core(rd);
36553b9e5acaSLuis R. Rodriguez 		break;
36563b9e5acaSLuis R. Rodriguez 	case NL80211_REGDOM_SET_BY_USER:
3657e646a025SJohannes Berg 		cfg80211_save_user_regdom(rd);
365884721d44SLuis R. Rodriguez 		r = reg_set_rd_user(rd, lr);
3659092008abSJanusz Dziedzic 		user_reset = true;
366084721d44SLuis R. Rodriguez 		break;
36613b9e5acaSLuis R. Rodriguez 	case NL80211_REGDOM_SET_BY_DRIVER:
3662f5fe3247SLuis R. Rodriguez 		r = reg_set_rd_driver(rd, lr);
3663f5fe3247SLuis R. Rodriguez 		break;
36643b9e5acaSLuis R. Rodriguez 	case NL80211_REGDOM_SET_BY_COUNTRY_IE:
366501992406SLuis R. Rodriguez 		r = reg_set_rd_country_ie(rd, lr);
36663b9e5acaSLuis R. Rodriguez 		break;
36673b9e5acaSLuis R. Rodriguez 	default:
36683b9e5acaSLuis R. Rodriguez 		WARN(1, "invalid initiator %d\n", lr->initiator);
366909d11800SOla Olsson 		kfree(rd);
36703b9e5acaSLuis R. Rodriguez 		return -EINVAL;
36713b9e5acaSLuis R. Rodriguez 	}
36723b9e5acaSLuis R. Rodriguez 
3673d2372b31SJohannes Berg 	if (r) {
3674092008abSJanusz Dziedzic 		switch (r) {
3675092008abSJanusz Dziedzic 		case -EALREADY:
367695908535SKalle Valo 			reg_set_request_processed();
3677092008abSJanusz Dziedzic 			break;
3678092008abSJanusz Dziedzic 		default:
3679092008abSJanusz Dziedzic 			/* Back to world regulatory in case of errors */
3680e646a025SJohannes Berg 			restore_regulatory_settings(user_reset, false);
3681092008abSJanusz Dziedzic 		}
368295908535SKalle Valo 
3683d2372b31SJohannes Berg 		kfree(rd);
368438fd2143SJohannes Berg 		return r;
3685d2372b31SJohannes Berg 	}
3686b2e1b302SLuis R. Rodriguez 
3687b2e1b302SLuis R. Rodriguez 	/* This would make this whole thing pointless */
368838fd2143SJohannes Berg 	if (WARN_ON(!lr->intersect && rd != get_cfg80211_regdom()))
368938fd2143SJohannes Berg 		return -EINVAL;
3690b2e1b302SLuis R. Rodriguez 
3691b2e1b302SLuis R. Rodriguez 	/* update all wiphys now with the new established regulatory domain */
3692c492db37SJohannes Berg 	update_all_wiphy_regulatory(lr->initiator);
3693b2e1b302SLuis R. Rodriguez 
3694458f4f9eSJohannes Berg 	print_regdomain(get_cfg80211_regdom());
3695b2e1b302SLuis R. Rodriguez 
3696c492db37SJohannes Berg 	nl80211_send_reg_change_event(lr);
369773d54c9eSLuis R. Rodriguez 
3698b2e253cfSLuis R. Rodriguez 	reg_set_request_processed();
3699b2e253cfSLuis R. Rodriguez 
370038fd2143SJohannes Berg 	return 0;
3701b2e1b302SLuis R. Rodriguez }
3702b2e1b302SLuis R. Rodriguez 
37032c3e861cSArik Nemtsov static int __regulatory_set_wiphy_regd(struct wiphy *wiphy,
3704b0d7aa59SJonathan Doron 				       struct ieee80211_regdomain *rd)
3705b0d7aa59SJonathan Doron {
3706b0d7aa59SJonathan Doron 	const struct ieee80211_regdomain *regd;
3707b0d7aa59SJonathan Doron 	const struct ieee80211_regdomain *prev_regd;
3708b0d7aa59SJonathan Doron 	struct cfg80211_registered_device *rdev;
3709b0d7aa59SJonathan Doron 
3710b0d7aa59SJonathan Doron 	if (WARN_ON(!wiphy || !rd))
3711b0d7aa59SJonathan Doron 		return -EINVAL;
3712b0d7aa59SJonathan Doron 
3713b0d7aa59SJonathan Doron 	if (WARN(!(wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED),
3714b0d7aa59SJonathan Doron 		 "wiphy should have REGULATORY_WIPHY_SELF_MANAGED\n"))
3715b0d7aa59SJonathan Doron 		return -EPERM;
3716b0d7aa59SJonathan Doron 
3717b0d7aa59SJonathan Doron 	if (WARN(!is_valid_rd(rd), "Invalid regulatory domain detected\n")) {
3718b0d7aa59SJonathan Doron 		print_regdomain_info(rd);
3719b0d7aa59SJonathan Doron 		return -EINVAL;
3720b0d7aa59SJonathan Doron 	}
3721b0d7aa59SJonathan Doron 
3722b0d7aa59SJonathan Doron 	regd = reg_copy_regd(rd);
3723b0d7aa59SJonathan Doron 	if (IS_ERR(regd))
3724b0d7aa59SJonathan Doron 		return PTR_ERR(regd);
3725b0d7aa59SJonathan Doron 
3726b0d7aa59SJonathan Doron 	rdev = wiphy_to_rdev(wiphy);
3727b0d7aa59SJonathan Doron 
3728b0d7aa59SJonathan Doron 	spin_lock(&reg_requests_lock);
3729b0d7aa59SJonathan Doron 	prev_regd = rdev->requested_regd;
3730b0d7aa59SJonathan Doron 	rdev->requested_regd = regd;
3731b0d7aa59SJonathan Doron 	spin_unlock(&reg_requests_lock);
3732b0d7aa59SJonathan Doron 
3733b0d7aa59SJonathan Doron 	kfree(prev_regd);
37342c3e861cSArik Nemtsov 	return 0;
37352c3e861cSArik Nemtsov }
37362c3e861cSArik Nemtsov 
37372c3e861cSArik Nemtsov int regulatory_set_wiphy_regd(struct wiphy *wiphy,
37382c3e861cSArik Nemtsov 			      struct ieee80211_regdomain *rd)
37392c3e861cSArik Nemtsov {
37402c3e861cSArik Nemtsov 	int ret = __regulatory_set_wiphy_regd(wiphy, rd);
37412c3e861cSArik Nemtsov 
37422c3e861cSArik Nemtsov 	if (ret)
37432c3e861cSArik Nemtsov 		return ret;
3744b0d7aa59SJonathan Doron 
3745b0d7aa59SJonathan Doron 	schedule_work(&reg_work);
3746b0d7aa59SJonathan Doron 	return 0;
3747b0d7aa59SJonathan Doron }
3748b0d7aa59SJonathan Doron EXPORT_SYMBOL(regulatory_set_wiphy_regd);
3749b0d7aa59SJonathan Doron 
37502c3e861cSArik Nemtsov int regulatory_set_wiphy_regd_sync_rtnl(struct wiphy *wiphy,
37512c3e861cSArik Nemtsov 					struct ieee80211_regdomain *rd)
37522c3e861cSArik Nemtsov {
37532c3e861cSArik Nemtsov 	int ret;
37542c3e861cSArik Nemtsov 
37552c3e861cSArik Nemtsov 	ASSERT_RTNL();
37562c3e861cSArik Nemtsov 
37572c3e861cSArik Nemtsov 	ret = __regulatory_set_wiphy_regd(wiphy, rd);
37582c3e861cSArik Nemtsov 	if (ret)
37592c3e861cSArik Nemtsov 		return ret;
37602c3e861cSArik Nemtsov 
37612c3e861cSArik Nemtsov 	/* process the request immediately */
37622c3e861cSArik Nemtsov 	reg_process_self_managed_hints();
37632c3e861cSArik Nemtsov 	return 0;
37642c3e861cSArik Nemtsov }
37652c3e861cSArik Nemtsov EXPORT_SYMBOL(regulatory_set_wiphy_regd_sync_rtnl);
37662c3e861cSArik Nemtsov 
376757b5ce07SLuis R. Rodriguez void wiphy_regulatory_register(struct wiphy *wiphy)
376857b5ce07SLuis R. Rodriguez {
3769aced43ceSAmar Singhal 	struct regulatory_request *lr = get_last_request();
377023df0b73SArik Nemtsov 
3771aced43ceSAmar Singhal 	/* self-managed devices ignore beacon hints and country IE */
3772aced43ceSAmar Singhal 	if (wiphy->regulatory_flags & REGULATORY_WIPHY_SELF_MANAGED) {
3773b0d7aa59SJonathan Doron 		wiphy->regulatory_flags |= REGULATORY_DISABLE_BEACON_HINTS |
3774b0d7aa59SJonathan Doron 					   REGULATORY_COUNTRY_IE_IGNORE;
3775b0d7aa59SJonathan Doron 
3776aced43ceSAmar Singhal 		/*
3777aced43ceSAmar Singhal 		 * The last request may have been received before this
3778aced43ceSAmar Singhal 		 * registration call. Call the driver notifier if
37798772eed9SSriram R 		 * initiator is USER.
3780aced43ceSAmar Singhal 		 */
37818772eed9SSriram R 		if (lr->initiator == NL80211_REGDOM_SET_BY_USER)
3782aced43ceSAmar Singhal 			reg_call_notifier(wiphy, lr);
3783aced43ceSAmar Singhal 	}
3784aced43ceSAmar Singhal 
378557b5ce07SLuis R. Rodriguez 	if (!reg_dev_ignore_cell_hint(wiphy))
378657b5ce07SLuis R. Rodriguez 		reg_num_devs_support_basehint++;
378757b5ce07SLuis R. Rodriguez 
378823df0b73SArik Nemtsov 	wiphy_update_regulatory(wiphy, lr->initiator);
378989766727SVasanthakumar Thiagarajan 	wiphy_all_share_dfs_chan_state(wiphy);
379057b5ce07SLuis R. Rodriguez }
379157b5ce07SLuis R. Rodriguez 
3792bfead080SLuis R. Rodriguez void wiphy_regulatory_deregister(struct wiphy *wiphy)
37933f2355cbSLuis R. Rodriguez {
37940ad8acafSLuis R. Rodriguez 	struct wiphy *request_wiphy = NULL;
3795c492db37SJohannes Berg 	struct regulatory_request *lr;
3796761cf7ecSLuis R. Rodriguez 
3797c492db37SJohannes Berg 	lr = get_last_request();
3798abc7381bSLuis R. Rodriguez 
379957b5ce07SLuis R. Rodriguez 	if (!reg_dev_ignore_cell_hint(wiphy))
380057b5ce07SLuis R. Rodriguez 		reg_num_devs_support_basehint--;
380157b5ce07SLuis R. Rodriguez 
3802458f4f9eSJohannes Berg 	rcu_free_regdom(get_wiphy_regdom(wiphy));
380334dd886cSMonam Agarwal 	RCU_INIT_POINTER(wiphy->regd, NULL);
38040ef9ccddSChris Wright 
3805c492db37SJohannes Berg 	if (lr)
3806c492db37SJohannes Berg 		request_wiphy = wiphy_idx_to_wiphy(lr->wiphy_idx);
3807806a9e39SLuis R. Rodriguez 
38080ef9ccddSChris Wright 	if (!request_wiphy || request_wiphy != wiphy)
380938fd2143SJohannes Berg 		return;
38100ef9ccddSChris Wright 
3811c492db37SJohannes Berg 	lr->wiphy_idx = WIPHY_IDX_INVALID;
3812c492db37SJohannes Berg 	lr->country_ie_env = ENVIRON_ANY;
38133f2355cbSLuis R. Rodriguez }
38143f2355cbSLuis R. Rodriguez 
3815174e0cd2SIlan Peer /*
3816f89769cfSArend van Spriel  * See FCC notices for UNII band definitions
3817f89769cfSArend van Spriel  *  5GHz: https://www.fcc.gov/document/5-ghz-unlicensed-spectrum-unii
3818f89769cfSArend van Spriel  *  6GHz: https://www.fcc.gov/document/fcc-proposes-more-spectrum-unlicensed-use-0
3819174e0cd2SIlan Peer  */
3820174e0cd2SIlan Peer int cfg80211_get_unii(int freq)
3821174e0cd2SIlan Peer {
3822174e0cd2SIlan Peer 	/* UNII-1 */
3823174e0cd2SIlan Peer 	if (freq >= 5150 && freq <= 5250)
3824174e0cd2SIlan Peer 		return 0;
3825174e0cd2SIlan Peer 
3826174e0cd2SIlan Peer 	/* UNII-2A */
3827174e0cd2SIlan Peer 	if (freq > 5250 && freq <= 5350)
3828174e0cd2SIlan Peer 		return 1;
3829174e0cd2SIlan Peer 
3830174e0cd2SIlan Peer 	/* UNII-2B */
3831174e0cd2SIlan Peer 	if (freq > 5350 && freq <= 5470)
3832174e0cd2SIlan Peer 		return 2;
3833174e0cd2SIlan Peer 
3834174e0cd2SIlan Peer 	/* UNII-2C */
3835174e0cd2SIlan Peer 	if (freq > 5470 && freq <= 5725)
3836174e0cd2SIlan Peer 		return 3;
3837174e0cd2SIlan Peer 
3838174e0cd2SIlan Peer 	/* UNII-3 */
3839174e0cd2SIlan Peer 	if (freq > 5725 && freq <= 5825)
3840174e0cd2SIlan Peer 		return 4;
3841174e0cd2SIlan Peer 
3842f89769cfSArend van Spriel 	/* UNII-5 */
3843f89769cfSArend van Spriel 	if (freq > 5925 && freq <= 6425)
3844f89769cfSArend van Spriel 		return 5;
3845f89769cfSArend van Spriel 
3846f89769cfSArend van Spriel 	/* UNII-6 */
3847f89769cfSArend van Spriel 	if (freq > 6425 && freq <= 6525)
3848f89769cfSArend van Spriel 		return 6;
3849f89769cfSArend van Spriel 
3850f89769cfSArend van Spriel 	/* UNII-7 */
3851f89769cfSArend van Spriel 	if (freq > 6525 && freq <= 6875)
3852f89769cfSArend van Spriel 		return 7;
3853f89769cfSArend van Spriel 
3854f89769cfSArend van Spriel 	/* UNII-8 */
3855f89769cfSArend van Spriel 	if (freq > 6875 && freq <= 7125)
3856f89769cfSArend van Spriel 		return 8;
3857f89769cfSArend van Spriel 
3858174e0cd2SIlan Peer 	return -EINVAL;
3859174e0cd2SIlan Peer }
3860174e0cd2SIlan Peer 
3861c8866e55SIlan Peer bool regulatory_indoor_allowed(void)
3862c8866e55SIlan Peer {
3863c8866e55SIlan Peer 	return reg_is_indoor;
3864c8866e55SIlan Peer }
3865c8866e55SIlan Peer 
3866b35a51c7SVasanthakumar Thiagarajan bool regulatory_pre_cac_allowed(struct wiphy *wiphy)
3867b35a51c7SVasanthakumar Thiagarajan {
3868b35a51c7SVasanthakumar Thiagarajan 	const struct ieee80211_regdomain *regd = NULL;
3869b35a51c7SVasanthakumar Thiagarajan 	const struct ieee80211_regdomain *wiphy_regd = NULL;
3870b35a51c7SVasanthakumar Thiagarajan 	bool pre_cac_allowed = false;
3871b35a51c7SVasanthakumar Thiagarajan 
3872b35a51c7SVasanthakumar Thiagarajan 	rcu_read_lock();
3873b35a51c7SVasanthakumar Thiagarajan 
3874b35a51c7SVasanthakumar Thiagarajan 	regd = rcu_dereference(cfg80211_regdomain);
3875b35a51c7SVasanthakumar Thiagarajan 	wiphy_regd = rcu_dereference(wiphy->regd);
3876b35a51c7SVasanthakumar Thiagarajan 	if (!wiphy_regd) {
3877b35a51c7SVasanthakumar Thiagarajan 		if (regd->dfs_region == NL80211_DFS_ETSI)
3878b35a51c7SVasanthakumar Thiagarajan 			pre_cac_allowed = true;
3879b35a51c7SVasanthakumar Thiagarajan 
3880b35a51c7SVasanthakumar Thiagarajan 		rcu_read_unlock();
3881b35a51c7SVasanthakumar Thiagarajan 
3882b35a51c7SVasanthakumar Thiagarajan 		return pre_cac_allowed;
3883b35a51c7SVasanthakumar Thiagarajan 	}
3884b35a51c7SVasanthakumar Thiagarajan 
3885b35a51c7SVasanthakumar Thiagarajan 	if (regd->dfs_region == wiphy_regd->dfs_region &&
3886b35a51c7SVasanthakumar Thiagarajan 	    wiphy_regd->dfs_region == NL80211_DFS_ETSI)
3887b35a51c7SVasanthakumar Thiagarajan 		pre_cac_allowed = true;
3888b35a51c7SVasanthakumar Thiagarajan 
3889b35a51c7SVasanthakumar Thiagarajan 	rcu_read_unlock();
3890b35a51c7SVasanthakumar Thiagarajan 
3891b35a51c7SVasanthakumar Thiagarajan 	return pre_cac_allowed;
3892b35a51c7SVasanthakumar Thiagarajan }
3893dc0c18edSAaron Komisar EXPORT_SYMBOL(regulatory_pre_cac_allowed);
3894b35a51c7SVasanthakumar Thiagarajan 
389526ec17a1SOrr Mazor static void cfg80211_check_and_end_cac(struct cfg80211_registered_device *rdev)
389626ec17a1SOrr Mazor {
389726ec17a1SOrr Mazor 	struct wireless_dev *wdev;
389826ec17a1SOrr Mazor 	/* If we finished CAC or received radar, we should end any
389926ec17a1SOrr Mazor 	 * CAC running on the same channels.
390026ec17a1SOrr Mazor 	 * the check !cfg80211_chandef_dfs_usable contain 2 options:
390126ec17a1SOrr Mazor 	 * either all channels are available - those the CAC_FINISHED
390226ec17a1SOrr Mazor 	 * event has effected another wdev state, or there is a channel
390326ec17a1SOrr Mazor 	 * in unavailable state in wdev chandef - those the RADAR_DETECTED
390426ec17a1SOrr Mazor 	 * event has effected another wdev state.
390526ec17a1SOrr Mazor 	 * In both cases we should end the CAC on the wdev.
390626ec17a1SOrr Mazor 	 */
390726ec17a1SOrr Mazor 	list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
390826ec17a1SOrr Mazor 		if (wdev->cac_started &&
390926ec17a1SOrr Mazor 		    !cfg80211_chandef_dfs_usable(&rdev->wiphy, &wdev->chandef))
391026ec17a1SOrr Mazor 			rdev_end_cac(rdev, wdev->netdev);
391126ec17a1SOrr Mazor 	}
391226ec17a1SOrr Mazor }
391326ec17a1SOrr Mazor 
391489766727SVasanthakumar Thiagarajan void regulatory_propagate_dfs_state(struct wiphy *wiphy,
391589766727SVasanthakumar Thiagarajan 				    struct cfg80211_chan_def *chandef,
391689766727SVasanthakumar Thiagarajan 				    enum nl80211_dfs_state dfs_state,
391789766727SVasanthakumar Thiagarajan 				    enum nl80211_radar_event event)
391889766727SVasanthakumar Thiagarajan {
391989766727SVasanthakumar Thiagarajan 	struct cfg80211_registered_device *rdev;
392089766727SVasanthakumar Thiagarajan 
392189766727SVasanthakumar Thiagarajan 	ASSERT_RTNL();
392289766727SVasanthakumar Thiagarajan 
392389766727SVasanthakumar Thiagarajan 	if (WARN_ON(!cfg80211_chandef_valid(chandef)))
392489766727SVasanthakumar Thiagarajan 		return;
392589766727SVasanthakumar Thiagarajan 
392689766727SVasanthakumar Thiagarajan 	list_for_each_entry(rdev, &cfg80211_rdev_list, list) {
392789766727SVasanthakumar Thiagarajan 		if (wiphy == &rdev->wiphy)
392889766727SVasanthakumar Thiagarajan 			continue;
392989766727SVasanthakumar Thiagarajan 
393089766727SVasanthakumar Thiagarajan 		if (!reg_dfs_domain_same(wiphy, &rdev->wiphy))
393189766727SVasanthakumar Thiagarajan 			continue;
393289766727SVasanthakumar Thiagarajan 
393389766727SVasanthakumar Thiagarajan 		if (!ieee80211_get_channel(&rdev->wiphy,
393489766727SVasanthakumar Thiagarajan 					   chandef->chan->center_freq))
393589766727SVasanthakumar Thiagarajan 			continue;
393689766727SVasanthakumar Thiagarajan 
393789766727SVasanthakumar Thiagarajan 		cfg80211_set_dfs_state(&rdev->wiphy, chandef, dfs_state);
393889766727SVasanthakumar Thiagarajan 
393989766727SVasanthakumar Thiagarajan 		if (event == NL80211_RADAR_DETECTED ||
394026ec17a1SOrr Mazor 		    event == NL80211_RADAR_CAC_FINISHED) {
394189766727SVasanthakumar Thiagarajan 			cfg80211_sched_dfs_chan_update(rdev);
394226ec17a1SOrr Mazor 			cfg80211_check_and_end_cac(rdev);
394326ec17a1SOrr Mazor 		}
394489766727SVasanthakumar Thiagarajan 
394589766727SVasanthakumar Thiagarajan 		nl80211_radar_notify(rdev, chandef, event, NULL, GFP_KERNEL);
394689766727SVasanthakumar Thiagarajan 	}
394789766727SVasanthakumar Thiagarajan }
394889766727SVasanthakumar Thiagarajan 
3949d7be102fSJohannes Berg static int __init regulatory_init_db(void)
3950b2e1b302SLuis R. Rodriguez {
3951d7be102fSJohannes Berg 	int err;
3952734366deSJohannes Berg 
395371e5e886SJohannes Berg 	/*
395471e5e886SJohannes Berg 	 * It's possible that - due to other bugs/issues - cfg80211
395571e5e886SJohannes Berg 	 * never called regulatory_init() below, or that it failed;
395671e5e886SJohannes Berg 	 * in that case, don't try to do any further work here as
395771e5e886SJohannes Berg 	 * it's doomed to lead to crashes.
395871e5e886SJohannes Berg 	 */
395971e5e886SJohannes Berg 	if (IS_ERR_OR_NULL(reg_pdev))
396071e5e886SJohannes Berg 		return -EINVAL;
396171e5e886SJohannes Berg 
396290a53e44SJohannes Berg 	err = load_builtin_regdb_keys();
396390a53e44SJohannes Berg 	if (err)
396490a53e44SJohannes Berg 		return err;
396590a53e44SJohannes Berg 
3966ae9e4b0dSLuis R. Rodriguez 	/* We always try to get an update for the static regdomain */
3967458f4f9eSJohannes Berg 	err = regulatory_hint_core(cfg80211_world_regdom->alpha2);
3968bcf4f99bSLuis R. Rodriguez 	if (err) {
396909d11800SOla Olsson 		if (err == -ENOMEM) {
397009d11800SOla Olsson 			platform_device_unregister(reg_pdev);
3971bcf4f99bSLuis R. Rodriguez 			return err;
397209d11800SOla Olsson 		}
3973bcf4f99bSLuis R. Rodriguez 		/*
3974bcf4f99bSLuis R. Rodriguez 		 * N.B. kobject_uevent_env() can fail mainly for when we're out
3975bcf4f99bSLuis R. Rodriguez 		 * memory which is handled and propagated appropriately above
3976bcf4f99bSLuis R. Rodriguez 		 * but it can also fail during a netlink_broadcast() or during
3977bcf4f99bSLuis R. Rodriguez 		 * early boot for call_usermodehelper(). For now treat these
3978bcf4f99bSLuis R. Rodriguez 		 * errors as non-fatal.
3979bcf4f99bSLuis R. Rodriguez 		 */
3980e9c0268fSJoe Perches 		pr_err("kobject_uevent_env() was unable to call CRDA during init\n");
3981bcf4f99bSLuis R. Rodriguez 	}
3982734366deSJohannes Berg 
3983ae9e4b0dSLuis R. Rodriguez 	/*
3984ae9e4b0dSLuis R. Rodriguez 	 * Finally, if the user set the module parameter treat it
3985ae9e4b0dSLuis R. Rodriguez 	 * as a user hint.
3986ae9e4b0dSLuis R. Rodriguez 	 */
3987ae9e4b0dSLuis R. Rodriguez 	if (!is_world_regdom(ieee80211_regdom))
398857b5ce07SLuis R. Rodriguez 		regulatory_hint_user(ieee80211_regdom,
398957b5ce07SLuis R. Rodriguez 				     NL80211_USER_REG_HINT_USER);
3990ae9e4b0dSLuis R. Rodriguez 
3991b2e1b302SLuis R. Rodriguez 	return 0;
3992b2e1b302SLuis R. Rodriguez }
3993d7be102fSJohannes Berg #ifndef MODULE
3994d7be102fSJohannes Berg late_initcall(regulatory_init_db);
3995d7be102fSJohannes Berg #endif
3996d7be102fSJohannes Berg 
3997d7be102fSJohannes Berg int __init regulatory_init(void)
3998d7be102fSJohannes Berg {
3999d7be102fSJohannes Berg 	reg_pdev = platform_device_register_simple("regulatory", 0, NULL, 0);
4000d7be102fSJohannes Berg 	if (IS_ERR(reg_pdev))
4001d7be102fSJohannes Berg 		return PTR_ERR(reg_pdev);
4002d7be102fSJohannes Berg 
4003d7be102fSJohannes Berg 	spin_lock_init(&reg_requests_lock);
4004d7be102fSJohannes Berg 	spin_lock_init(&reg_pending_beacons_lock);
4005d7be102fSJohannes Berg 	spin_lock_init(&reg_indoor_lock);
4006d7be102fSJohannes Berg 
4007d7be102fSJohannes Berg 	rcu_assign_pointer(cfg80211_regdomain, cfg80211_world_regdom);
4008d7be102fSJohannes Berg 
4009d7be102fSJohannes Berg 	user_alpha2[0] = '9';
4010d7be102fSJohannes Berg 	user_alpha2[1] = '7';
4011d7be102fSJohannes Berg 
4012d7be102fSJohannes Berg #ifdef MODULE
4013d7be102fSJohannes Berg 	return regulatory_init_db();
4014d7be102fSJohannes Berg #else
4015d7be102fSJohannes Berg 	return 0;
4016d7be102fSJohannes Berg #endif
4017d7be102fSJohannes Berg }
4018b2e1b302SLuis R. Rodriguez 
40191a919318SJohannes Berg void regulatory_exit(void)
4020b2e1b302SLuis R. Rodriguez {
4021fe33eb39SLuis R. Rodriguez 	struct regulatory_request *reg_request, *tmp;
4022e38f8a7aSLuis R. Rodriguez 	struct reg_beacon *reg_beacon, *btmp;
4023fe33eb39SLuis R. Rodriguez 
4024fe33eb39SLuis R. Rodriguez 	cancel_work_sync(&reg_work);
4025b6863036SJohannes Berg 	cancel_crda_timeout_sync();
4026ad932f04SArik Nemtsov 	cancel_delayed_work_sync(&reg_check_chans);
4027fe33eb39SLuis R. Rodriguez 
40289027b149SJohannes Berg 	/* Lock to suppress warnings */
402938fd2143SJohannes Berg 	rtnl_lock();
4030379b82f4SJohannes Berg 	reset_regdomains(true, NULL);
403138fd2143SJohannes Berg 	rtnl_unlock();
4032734366deSJohannes Berg 
403358ebacc6SLuis R. Rodriguez 	dev_set_uevent_suppress(&reg_pdev->dev, true);
4034f6037d09SJohannes Berg 
4035b2e1b302SLuis R. Rodriguez 	platform_device_unregister(reg_pdev);
4036734366deSJohannes Berg 
4037fea9bcedSJohannes Berg 	list_for_each_entry_safe(reg_beacon, btmp, &reg_pending_beacons, list) {
4038e38f8a7aSLuis R. Rodriguez 		list_del(&reg_beacon->list);
4039e38f8a7aSLuis R. Rodriguez 		kfree(reg_beacon);
4040e38f8a7aSLuis R. Rodriguez 	}
4041e38f8a7aSLuis R. Rodriguez 
4042fea9bcedSJohannes Berg 	list_for_each_entry_safe(reg_beacon, btmp, &reg_beacon_list, list) {
4043e38f8a7aSLuis R. Rodriguez 		list_del(&reg_beacon->list);
4044e38f8a7aSLuis R. Rodriguez 		kfree(reg_beacon);
4045e38f8a7aSLuis R. Rodriguez 	}
4046e38f8a7aSLuis R. Rodriguez 
4047fea9bcedSJohannes Berg 	list_for_each_entry_safe(reg_request, tmp, &reg_requests_list, list) {
4048fe33eb39SLuis R. Rodriguez 		list_del(&reg_request->list);
4049fe33eb39SLuis R. Rodriguez 		kfree(reg_request);
4050fe33eb39SLuis R. Rodriguez 	}
4051007f6c5eSJohannes Berg 
4052007f6c5eSJohannes Berg 	if (!IS_ERR_OR_NULL(regdb))
4053007f6c5eSJohannes Berg 		kfree(regdb);
4054e646a025SJohannes Berg 	if (!IS_ERR_OR_NULL(cfg80211_user_regdom))
4055e646a025SJohannes Berg 		kfree(cfg80211_user_regdom);
405690a53e44SJohannes Berg 
405790a53e44SJohannes Berg 	free_regdb_keyring();
4058fe33eb39SLuis R. Rodriguez }
4059