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