blob: 65f86264f7bbdbf30dd1c0e80c9e2af9c5893c95 [file] [log] [blame]
Johannes Berg8318d782008-01-24 19:38:38 +01001/*
2 * Copyright 2002-2005, Instant802 Networks, Inc.
3 * Copyright 2005-2006, Devicescape Software, Inc.
4 * Copyright 2007 Johannes Berg <johannes@sipsolutions.net>
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07005 * Copyright 2008 Luis R. Rodriguez <lrodriguz@atheros.com>
Johannes Berg8318d782008-01-24 19:38:38 +01006 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
10 */
11
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -070012/**
13 * DOC: Wireless regulatory infrastructure
Johannes Berg8318d782008-01-24 19:38:38 +010014 *
15 * The usual implementation is for a driver to read a device EEPROM to
16 * determine which regulatory domain it should be operating under, then
17 * looking up the allowable channels in a driver-local table and finally
18 * registering those channels in the wiphy structure.
19 *
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -070020 * Another set of compliance enforcement is for drivers to use their
21 * own compliance limits which can be stored on the EEPROM. The host
22 * driver or firmware may ensure these are used.
23 *
24 * In addition to all this we provide an extra layer of regulatory
25 * conformance. For drivers which do not have any regulatory
26 * information CRDA provides the complete regulatory solution.
27 * For others it provides a community effort on further restrictions
28 * to enhance compliance.
29 *
30 * Note: When number of rules --> infinity we will not be able to
31 * index on alpha2 any more, instead we'll probably have to
32 * rely on some SHA1 checksum of the regdomain for example.
33 *
Johannes Berg8318d782008-01-24 19:38:38 +010034 */
35#include <linux/kernel.h>
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -070036#include <linux/list.h>
37#include <linux/random.h>
38#include <linux/nl80211.h>
39#include <linux/platform_device.h>
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -070040#include <net/cfg80211.h>
Johannes Berg8318d782008-01-24 19:38:38 +010041#include "core.h"
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -070042#include "reg.h"
John W. Linville3b377ea2009-12-18 17:59:01 -050043#include "regdb.h"
Luis R. Rodriguez73d54c92009-03-09 22:07:42 -040044#include "nl80211.h"
Johannes Berg8318d782008-01-24 19:38:38 +010045
Luis R. Rodriguez5166ccd2008-10-30 13:33:56 -070046/* Receipt of information from last regulatory request */
Johannes Bergf6037d02008-10-21 11:01:33 +020047static struct regulatory_request *last_request;
Johannes Berg734366d2008-09-15 10:56:48 +020048
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -070049/* To trigger userspace events */
50static struct platform_device *reg_pdev;
Johannes Berg8318d782008-01-24 19:38:38 +010051
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -050052/*
53 * Central wireless core regulatory domains, we only need two,
Johannes Berg734366d2008-09-15 10:56:48 +020054 * the current one and a world regulatory domain in case we have no
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -050055 * information to give us an alpha2
56 */
Luis R. Rodriguezf1303472009-01-30 09:26:42 -080057const struct ieee80211_regdomain *cfg80211_regdomain;
Johannes Berg734366d2008-09-15 10:56:48 +020058
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -050059/*
60 * We use this as a place for the rd structure built from the
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -080061 * last parsed country IE to rest until CRDA gets back to us with
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -050062 * what it thinks should apply for the same country
63 */
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -080064static const struct ieee80211_regdomain *country_ie_regdomain;
65
Luis R. Rodriguezabc73812009-07-30 17:38:08 -070066/*
67 * Protects static reg.c components:
68 * - cfg80211_world_regdom
69 * - cfg80211_regdom
70 * - country_ie_regdomain
71 * - last_request
72 */
73DEFINE_MUTEX(reg_mutex);
74#define assert_reg_lock() WARN_ON(!mutex_is_locked(&reg_mutex))
75
Luis R. Rodrigueze38f8a72009-02-21 00:20:39 -050076/* Used to queue up regulatory hints */
Luis R. Rodriguezfe33eb32009-02-21 00:04:30 -050077static LIST_HEAD(reg_requests_list);
78static spinlock_t reg_requests_lock;
79
Luis R. Rodrigueze38f8a72009-02-21 00:20:39 -050080/* Used to queue up beacon hints for review */
81static LIST_HEAD(reg_pending_beacons);
82static spinlock_t reg_pending_beacons_lock;
83
84/* Used to keep track of processed beacon hints */
85static LIST_HEAD(reg_beacon_list);
86
87struct reg_beacon {
88 struct list_head list;
89 struct ieee80211_channel chan;
90};
91
Johannes Berg734366d2008-09-15 10:56:48 +020092/* We keep a static world regulatory domain in case of the absence of CRDA */
93static const struct ieee80211_regdomain world_regdom = {
Luis R. Rodriguez611b6a82009-03-05 21:19:21 -080094 .n_reg_rules = 5,
Johannes Berg734366d2008-09-15 10:56:48 +020095 .alpha2 = "00",
96 .reg_rules = {
Luis R. Rodriguez68798a62009-02-21 00:20:37 -050097 /* IEEE 802.11b/g, channels 1..11 */
98 REG_RULE(2412-10, 2462+10, 40, 6, 20, 0),
Luis R. Rodriguez611b6a82009-03-05 21:19:21 -080099 /* IEEE 802.11b/g, channels 12..13. No HT40
100 * channel fits here. */
101 REG_RULE(2467-10, 2472+10, 20, 6, 20,
Luis R. Rodriguez3fc71f72009-02-21 00:20:38 -0500102 NL80211_RRF_PASSIVE_SCAN |
103 NL80211_RRF_NO_IBSS),
Luis R. Rodriguez611b6a82009-03-05 21:19:21 -0800104 /* IEEE 802.11 channel 14 - Only JP enables
105 * this and for 802.11b only */
106 REG_RULE(2484-10, 2484+10, 20, 6, 20,
107 NL80211_RRF_PASSIVE_SCAN |
108 NL80211_RRF_NO_IBSS |
109 NL80211_RRF_NO_OFDM),
110 /* IEEE 802.11a, channel 36..48 */
Luis R. Rodriguezec329ac2009-03-05 21:19:22 -0800111 REG_RULE(5180-10, 5240+10, 40, 6, 20,
Luis R. Rodriguez611b6a82009-03-05 21:19:21 -0800112 NL80211_RRF_PASSIVE_SCAN |
113 NL80211_RRF_NO_IBSS),
Luis R. Rodriguez3fc71f72009-02-21 00:20:38 -0500114
115 /* NB: 5260 MHz - 5700 MHz requies DFS */
116
117 /* IEEE 802.11a, channel 149..165 */
Luis R. Rodriguezec329ac2009-03-05 21:19:22 -0800118 REG_RULE(5745-10, 5825+10, 40, 6, 20,
Luis R. Rodriguez3fc71f72009-02-21 00:20:38 -0500119 NL80211_RRF_PASSIVE_SCAN |
120 NL80211_RRF_NO_IBSS),
Johannes Berg734366d2008-09-15 10:56:48 +0200121 }
122};
123
Johannes Berga3d2eaf2008-09-15 11:10:52 +0200124static const struct ieee80211_regdomain *cfg80211_world_regdom =
125 &world_regdom;
Johannes Berg734366d2008-09-15 10:56:48 +0200126
Luis R. Rodriguez6ee7d332009-03-20 23:53:06 -0400127static char *ieee80211_regdom = "00";
Luis R. Rodriguez6ee7d332009-03-20 23:53:06 -0400128
Johannes Berg734366d2008-09-15 10:56:48 +0200129module_param(ieee80211_regdom, charp, 0444);
130MODULE_PARM_DESC(ieee80211_regdom, "IEEE 802.11 regulatory domain code");
131
Luis R. Rodriguez6ee7d332009-03-20 23:53:06 -0400132#ifdef CONFIG_WIRELESS_OLD_REGULATORY
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -0500133/*
134 * We assume 40 MHz bandwidth for the old regulatory work.
Johannes Berg734366d2008-09-15 10:56:48 +0200135 * We make emphasis we are using the exact same frequencies
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -0500136 * as before
137 */
Johannes Berg734366d2008-09-15 10:56:48 +0200138
139static const struct ieee80211_regdomain us_regdom = {
140 .n_reg_rules = 6,
141 .alpha2 = "US",
142 .reg_rules = {
143 /* IEEE 802.11b/g, channels 1..11 */
144 REG_RULE(2412-10, 2462+10, 40, 6, 27, 0),
145 /* IEEE 802.11a, channel 36 */
146 REG_RULE(5180-10, 5180+10, 40, 6, 23, 0),
147 /* IEEE 802.11a, channel 40 */
148 REG_RULE(5200-10, 5200+10, 40, 6, 23, 0),
149 /* IEEE 802.11a, channel 44 */
150 REG_RULE(5220-10, 5220+10, 40, 6, 23, 0),
151 /* IEEE 802.11a, channels 48..64 */
152 REG_RULE(5240-10, 5320+10, 40, 6, 23, 0),
153 /* IEEE 802.11a, channels 149..165, outdoor */
154 REG_RULE(5745-10, 5825+10, 40, 6, 30, 0),
155 }
156};
157
158static const struct ieee80211_regdomain jp_regdom = {
159 .n_reg_rules = 3,
160 .alpha2 = "JP",
161 .reg_rules = {
162 /* IEEE 802.11b/g, channels 1..14 */
163 REG_RULE(2412-10, 2484+10, 40, 6, 20, 0),
164 /* IEEE 802.11a, channels 34..48 */
165 REG_RULE(5170-10, 5240+10, 40, 6, 20,
166 NL80211_RRF_PASSIVE_SCAN),
167 /* IEEE 802.11a, channels 52..64 */
168 REG_RULE(5260-10, 5320+10, 40, 6, 20,
169 NL80211_RRF_NO_IBSS |
170 NL80211_RRF_DFS),
171 }
172};
173
174static const struct ieee80211_regdomain eu_regdom = {
175 .n_reg_rules = 6,
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -0500176 /*
177 * This alpha2 is bogus, we leave it here just for stupid
178 * backward compatibility
179 */
Johannes Berg734366d2008-09-15 10:56:48 +0200180 .alpha2 = "EU",
181 .reg_rules = {
182 /* IEEE 802.11b/g, channels 1..13 */
183 REG_RULE(2412-10, 2472+10, 40, 6, 20, 0),
184 /* IEEE 802.11a, channel 36 */
185 REG_RULE(5180-10, 5180+10, 40, 6, 23,
186 NL80211_RRF_PASSIVE_SCAN),
187 /* IEEE 802.11a, channel 40 */
188 REG_RULE(5200-10, 5200+10, 40, 6, 23,
189 NL80211_RRF_PASSIVE_SCAN),
190 /* IEEE 802.11a, channel 44 */
191 REG_RULE(5220-10, 5220+10, 40, 6, 23,
192 NL80211_RRF_PASSIVE_SCAN),
193 /* IEEE 802.11a, channels 48..64 */
194 REG_RULE(5240-10, 5320+10, 40, 6, 20,
195 NL80211_RRF_NO_IBSS |
196 NL80211_RRF_DFS),
197 /* IEEE 802.11a, channels 100..140 */
198 REG_RULE(5500-10, 5700+10, 40, 6, 30,
199 NL80211_RRF_NO_IBSS |
200 NL80211_RRF_DFS),
201 }
202};
203
204static const struct ieee80211_regdomain *static_regdom(char *alpha2)
205{
206 if (alpha2[0] == 'U' && alpha2[1] == 'S')
207 return &us_regdom;
208 if (alpha2[0] == 'J' && alpha2[1] == 'P')
209 return &jp_regdom;
210 if (alpha2[0] == 'E' && alpha2[1] == 'U')
211 return &eu_regdom;
212 /* Default, as per the old rules */
213 return &us_regdom;
214}
215
Johannes Berga3d2eaf2008-09-15 11:10:52 +0200216static bool is_old_static_regdom(const struct ieee80211_regdomain *rd)
Johannes Berg734366d2008-09-15 10:56:48 +0200217{
218 if (rd == &us_regdom || rd == &jp_regdom || rd == &eu_regdom)
219 return true;
220 return false;
221}
Johannes Berg734366d2008-09-15 10:56:48 +0200222#else
Johannes Berg942b25c2008-09-15 11:26:47 +0200223static inline bool is_old_static_regdom(const struct ieee80211_regdomain *rd)
224{
225 return false;
226}
227#endif
228
Johannes Berg734366d2008-09-15 10:56:48 +0200229static void reset_regdomains(void)
230{
Johannes Berg942b25c2008-09-15 11:26:47 +0200231 /* avoid freeing static information or freeing something twice */
232 if (cfg80211_regdomain == cfg80211_world_regdom)
233 cfg80211_regdomain = NULL;
234 if (cfg80211_world_regdom == &world_regdom)
235 cfg80211_world_regdom = NULL;
236 if (cfg80211_regdomain == &world_regdom)
237 cfg80211_regdomain = NULL;
238 if (is_old_static_regdom(cfg80211_regdomain))
239 cfg80211_regdomain = NULL;
240
241 kfree(cfg80211_regdomain);
242 kfree(cfg80211_world_regdom);
Johannes Berg734366d2008-09-15 10:56:48 +0200243
Johannes Berga3d2eaf2008-09-15 11:10:52 +0200244 cfg80211_world_regdom = &world_regdom;
Johannes Berg734366d2008-09-15 10:56:48 +0200245 cfg80211_regdomain = NULL;
246}
247
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -0500248/*
249 * Dynamic world regulatory domain requested by the wireless
250 * core upon initialization
251 */
Johannes Berga3d2eaf2008-09-15 11:10:52 +0200252static void update_world_regdomain(const struct ieee80211_regdomain *rd)
Johannes Berg734366d2008-09-15 10:56:48 +0200253{
Johannes Bergf6037d02008-10-21 11:01:33 +0200254 BUG_ON(!last_request);
Johannes Berg734366d2008-09-15 10:56:48 +0200255
256 reset_regdomains();
257
258 cfg80211_world_regdom = rd;
259 cfg80211_regdomain = rd;
260}
Johannes Berg734366d2008-09-15 10:56:48 +0200261
Johannes Berga3d2eaf2008-09-15 11:10:52 +0200262bool is_world_regdom(const char *alpha2)
Johannes Berg8318d782008-01-24 19:38:38 +0100263{
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -0700264 if (!alpha2)
265 return false;
266 if (alpha2[0] == '0' && alpha2[1] == '0')
267 return true;
268 return false;
Johannes Berg8318d782008-01-24 19:38:38 +0100269}
270
Johannes Berga3d2eaf2008-09-15 11:10:52 +0200271static bool is_alpha2_set(const char *alpha2)
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -0700272{
273 if (!alpha2)
274 return false;
275 if (alpha2[0] != 0 && alpha2[1] != 0)
276 return true;
277 return false;
278}
Johannes Berg8318d782008-01-24 19:38:38 +0100279
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -0700280static bool is_alpha_upper(char letter)
281{
282 /* ASCII A - Z */
283 if (letter >= 65 && letter <= 90)
284 return true;
285 return false;
286}
287
Johannes Berga3d2eaf2008-09-15 11:10:52 +0200288static bool is_unknown_alpha2(const char *alpha2)
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -0700289{
290 if (!alpha2)
291 return false;
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -0500292 /*
293 * Special case where regulatory domain was built by driver
294 * but a specific alpha2 cannot be determined
295 */
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -0700296 if (alpha2[0] == '9' && alpha2[1] == '9')
297 return true;
298 return false;
299}
300
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -0800301static bool is_intersected_alpha2(const char *alpha2)
302{
303 if (!alpha2)
304 return false;
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -0500305 /*
306 * Special case where regulatory domain is the
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -0800307 * result of an intersection between two regulatory domain
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -0500308 * structures
309 */
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -0800310 if (alpha2[0] == '9' && alpha2[1] == '8')
311 return true;
312 return false;
313}
314
Johannes Berga3d2eaf2008-09-15 11:10:52 +0200315static bool is_an_alpha2(const char *alpha2)
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -0700316{
317 if (!alpha2)
318 return false;
319 if (is_alpha_upper(alpha2[0]) && is_alpha_upper(alpha2[1]))
320 return true;
321 return false;
322}
323
Johannes Berga3d2eaf2008-09-15 11:10:52 +0200324static bool alpha2_equal(const char *alpha2_x, const char *alpha2_y)
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -0700325{
326 if (!alpha2_x || !alpha2_y)
327 return false;
328 if (alpha2_x[0] == alpha2_y[0] &&
329 alpha2_x[1] == alpha2_y[1])
330 return true;
331 return false;
332}
333
Luis R. Rodriguez69b15722009-02-21 00:04:33 -0500334static bool regdom_changes(const char *alpha2)
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -0700335{
Luis R. Rodriguez761cf7e2009-02-21 00:04:25 -0500336 assert_cfg80211_lock();
337
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -0700338 if (!cfg80211_regdomain)
339 return true;
340 if (alpha2_equal(cfg80211_regdomain->alpha2, alpha2))
341 return false;
342 return true;
343}
344
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -0800345/**
346 * country_ie_integrity_changes - tells us if the country IE has changed
347 * @checksum: checksum of country IE of fields we are interested in
348 *
349 * If the country IE has not changed you can ignore it safely. This is
350 * useful to determine if two devices are seeing two different country IEs
351 * even on the same alpha2. Note that this will return false if no IE has
352 * been set on the wireless core yet.
353 */
354static bool country_ie_integrity_changes(u32 checksum)
355{
356 /* If no IE has been set then the checksum doesn't change */
357 if (unlikely(!last_request->country_ie_checksum))
358 return false;
359 if (unlikely(last_request->country_ie_checksum != checksum))
360 return true;
361 return false;
362}
363
John W. Linville3b377ea2009-12-18 17:59:01 -0500364static int reg_copy_regd(const struct ieee80211_regdomain **dst_regd,
365 const struct ieee80211_regdomain *src_regd)
366{
367 struct ieee80211_regdomain *regd;
368 int size_of_regd = 0;
369 unsigned int i;
370
371 size_of_regd = sizeof(struct ieee80211_regdomain) +
372 ((src_regd->n_reg_rules + 1) * sizeof(struct ieee80211_reg_rule));
373
374 regd = kzalloc(size_of_regd, GFP_KERNEL);
375 if (!regd)
376 return -ENOMEM;
377
378 memcpy(regd, src_regd, sizeof(struct ieee80211_regdomain));
379
380 for (i = 0; i < src_regd->n_reg_rules; i++)
381 memcpy(&regd->reg_rules[i], &src_regd->reg_rules[i],
382 sizeof(struct ieee80211_reg_rule));
383
384 *dst_regd = regd;
385 return 0;
386}
387
388#ifdef CONFIG_CFG80211_INTERNAL_REGDB
389struct reg_regdb_search_request {
390 char alpha2[2];
391 struct list_head list;
392};
393
394static LIST_HEAD(reg_regdb_search_list);
395static DEFINE_SPINLOCK(reg_regdb_search_lock);
396
397static void reg_regdb_search(struct work_struct *work)
398{
399 struct reg_regdb_search_request *request;
400 const struct ieee80211_regdomain *curdom, *regdom;
401 int i, r;
402
403 spin_lock(&reg_regdb_search_lock);
404 while (!list_empty(&reg_regdb_search_list)) {
405 request = list_first_entry(&reg_regdb_search_list,
406 struct reg_regdb_search_request,
407 list);
408 list_del(&request->list);
409
410 for (i=0; i<reg_regdb_size; i++) {
411 curdom = reg_regdb[i];
412
413 if (!memcmp(request->alpha2, curdom->alpha2, 2)) {
414 r = reg_copy_regd(&regdom, curdom);
415 if (r)
416 break;
417 spin_unlock(&reg_regdb_search_lock);
418 mutex_lock(&cfg80211_mutex);
419 set_regdom(regdom);
420 mutex_unlock(&cfg80211_mutex);
421 spin_lock(&reg_regdb_search_lock);
422 break;
423 }
424 }
425
426 kfree(request);
427 }
428 spin_unlock(&reg_regdb_search_lock);
429}
430
431static DECLARE_WORK(reg_regdb_work, reg_regdb_search);
432
433static void reg_regdb_query(const char *alpha2)
434{
435 struct reg_regdb_search_request *request;
436
437 if (!alpha2)
438 return;
439
440 request = kzalloc(sizeof(struct reg_regdb_search_request), GFP_KERNEL);
441 if (!request)
442 return;
443
444 memcpy(request->alpha2, alpha2, 2);
445
446 spin_lock(&reg_regdb_search_lock);
447 list_add_tail(&request->list, &reg_regdb_search_list);
448 spin_unlock(&reg_regdb_search_lock);
449
450 schedule_work(&reg_regdb_work);
451}
452#else
453static inline void reg_regdb_query(const char *alpha2) {}
454#endif /* CONFIG_CFG80211_INTERNAL_REGDB */
455
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -0500456/*
457 * This lets us keep regulatory code which is updated on a regulatory
458 * basis in userspace.
459 */
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -0700460static int call_crda(const char *alpha2)
461{
462 char country_env[9 + 2] = "COUNTRY=";
463 char *envp[] = {
464 country_env,
465 NULL
466 };
467
468 if (!is_world_regdom((char *) alpha2))
469 printk(KERN_INFO "cfg80211: Calling CRDA for country: %c%c\n",
470 alpha2[0], alpha2[1]);
471 else
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -0700472 printk(KERN_INFO "cfg80211: Calling CRDA to update world "
473 "regulatory domain\n");
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -0700474
John W. Linville3b377ea2009-12-18 17:59:01 -0500475 /* query internal regulatory database (if it exists) */
476 reg_regdb_query(alpha2);
477
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -0700478 country_env[8] = alpha2[0];
479 country_env[9] = alpha2[1];
480
481 return kobject_uevent_env(&reg_pdev->dev.kobj, KOBJ_CHANGE, envp);
482}
483
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -0700484/* Used by nl80211 before kmalloc'ing our regulatory domain */
Johannes Berga3d2eaf2008-09-15 11:10:52 +0200485bool reg_is_valid_request(const char *alpha2)
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -0700486{
Luis R. Rodriguez61405e92009-05-13 17:04:41 -0400487 assert_cfg80211_lock();
488
Johannes Bergf6037d02008-10-21 11:01:33 +0200489 if (!last_request)
490 return false;
491
492 return alpha2_equal(last_request->alpha2, alpha2);
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -0700493}
494
495/* Sanity check on a regulatory rule */
Johannes Berga3d2eaf2008-09-15 11:10:52 +0200496static bool is_valid_reg_rule(const struct ieee80211_reg_rule *rule)
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -0700497{
Johannes Berga3d2eaf2008-09-15 11:10:52 +0200498 const struct ieee80211_freq_range *freq_range = &rule->freq_range;
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -0700499 u32 freq_diff;
500
Luis R. Rodriguez91e99002008-11-12 14:21:55 -0800501 if (freq_range->start_freq_khz <= 0 || freq_range->end_freq_khz <= 0)
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -0700502 return false;
503
504 if (freq_range->start_freq_khz > freq_range->end_freq_khz)
505 return false;
506
507 freq_diff = freq_range->end_freq_khz - freq_range->start_freq_khz;
508
Roel Kluinbd05f282009-03-03 22:55:21 +0100509 if (freq_range->end_freq_khz <= freq_range->start_freq_khz ||
510 freq_range->max_bandwidth_khz > freq_diff)
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -0700511 return false;
512
513 return true;
514}
515
Johannes Berga3d2eaf2008-09-15 11:10:52 +0200516static bool is_valid_rd(const struct ieee80211_regdomain *rd)
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -0700517{
Johannes Berga3d2eaf2008-09-15 11:10:52 +0200518 const struct ieee80211_reg_rule *reg_rule = NULL;
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -0700519 unsigned int i;
520
521 if (!rd->n_reg_rules)
522 return false;
523
Luis R. Rodriguez88dc1c32008-11-12 14:22:01 -0800524 if (WARN_ON(rd->n_reg_rules > NL80211_MAX_SUPP_REG_RULES))
525 return false;
526
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -0700527 for (i = 0; i < rd->n_reg_rules; i++) {
528 reg_rule = &rd->reg_rules[i];
529 if (!is_valid_reg_rule(reg_rule))
530 return false;
531 }
532
533 return true;
534}
535
Luis R. Rodriguez038659e2009-05-02 00:37:17 -0400536static bool reg_does_bw_fit(const struct ieee80211_freq_range *freq_range,
537 u32 center_freq_khz,
538 u32 bw_khz)
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -0700539{
Luis R. Rodriguez038659e2009-05-02 00:37:17 -0400540 u32 start_freq_khz, end_freq_khz;
541
542 start_freq_khz = center_freq_khz - (bw_khz/2);
543 end_freq_khz = center_freq_khz + (bw_khz/2);
544
545 if (start_freq_khz >= freq_range->start_freq_khz &&
546 end_freq_khz <= freq_range->end_freq_khz)
547 return true;
548
549 return false;
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -0700550}
551
Luis R. Rodriguez0c7dc452009-01-07 17:43:36 -0800552/**
553 * freq_in_rule_band - tells us if a frequency is in a frequency band
554 * @freq_range: frequency rule we want to query
555 * @freq_khz: frequency we are inquiring about
556 *
557 * This lets us know if a specific frequency rule is or is not relevant to
558 * a specific frequency's band. Bands are device specific and artificial
559 * definitions (the "2.4 GHz band" and the "5 GHz band"), however it is
560 * safe for now to assume that a frequency rule should not be part of a
561 * frequency's band if the start freq or end freq are off by more than 2 GHz.
562 * This resolution can be lowered and should be considered as we add
563 * regulatory rule support for other "bands".
564 **/
565static bool freq_in_rule_band(const struct ieee80211_freq_range *freq_range,
566 u32 freq_khz)
567{
568#define ONE_GHZ_IN_KHZ 1000000
569 if (abs(freq_khz - freq_range->start_freq_khz) <= (2 * ONE_GHZ_IN_KHZ))
570 return true;
571 if (abs(freq_khz - freq_range->end_freq_khz) <= (2 * ONE_GHZ_IN_KHZ))
572 return true;
573 return false;
574#undef ONE_GHZ_IN_KHZ
575}
576
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -0500577/*
578 * Converts a country IE to a regulatory domain. A regulatory domain
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -0800579 * structure has a lot of information which the IE doesn't yet have,
580 * so for the other values we use upper max values as we will intersect
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -0500581 * with our userspace regulatory agent to get lower bounds.
582 */
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -0800583static struct ieee80211_regdomain *country_ie_2_rd(
584 u8 *country_ie,
585 u8 country_ie_len,
586 u32 *checksum)
587{
588 struct ieee80211_regdomain *rd = NULL;
589 unsigned int i = 0;
590 char alpha2[2];
591 u32 flags = 0;
592 u32 num_rules = 0, size_of_regd = 0;
593 u8 *triplets_start = NULL;
594 u8 len_at_triplet = 0;
595 /* the last channel we have registered in a subband (triplet) */
596 int last_sub_max_channel = 0;
597
598 *checksum = 0xDEADBEEF;
599
600 /* Country IE requirements */
601 BUG_ON(country_ie_len < IEEE80211_COUNTRY_IE_MIN_LEN ||
602 country_ie_len & 0x01);
603
604 alpha2[0] = country_ie[0];
605 alpha2[1] = country_ie[1];
606
607 /*
608 * Third octet can be:
609 * 'I' - Indoor
610 * 'O' - Outdoor
611 *
612 * anything else we assume is no restrictions
613 */
614 if (country_ie[2] == 'I')
615 flags = NL80211_RRF_NO_OUTDOOR;
616 else if (country_ie[2] == 'O')
617 flags = NL80211_RRF_NO_INDOOR;
618
619 country_ie += 3;
620 country_ie_len -= 3;
621
622 triplets_start = country_ie;
623 len_at_triplet = country_ie_len;
624
625 *checksum ^= ((flags ^ alpha2[0] ^ alpha2[1]) << 8);
626
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -0500627 /*
628 * We need to build a reg rule for each triplet, but first we must
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -0800629 * calculate the number of reg rules we will need. We will need one
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -0500630 * for each channel subband
631 */
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -0800632 while (country_ie_len >= 3) {
Luis R. Rodriguez615aab42009-01-22 15:05:46 -0800633 int end_channel = 0;
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -0800634 struct ieee80211_country_ie_triplet *triplet =
635 (struct ieee80211_country_ie_triplet *) country_ie;
636 int cur_sub_max_channel = 0, cur_channel = 0;
637
638 if (triplet->ext.reg_extension_id >=
639 IEEE80211_COUNTRY_EXTENSION_ID) {
640 country_ie += 3;
641 country_ie_len -= 3;
642 continue;
643 }
644
Luis R. Rodriguez615aab42009-01-22 15:05:46 -0800645 /* 2 GHz */
646 if (triplet->chans.first_channel <= 14)
647 end_channel = triplet->chans.first_channel +
648 triplet->chans.num_channels;
649 else
650 /*
651 * 5 GHz -- For example in country IEs if the first
652 * channel given is 36 and the number of channels is 4
653 * then the individual channel numbers defined for the
654 * 5 GHz PHY by these parameters are: 36, 40, 44, and 48
655 * and not 36, 37, 38, 39.
656 *
657 * See: http://tinyurl.com/11d-clarification
658 */
659 end_channel = triplet->chans.first_channel +
660 (4 * (triplet->chans.num_channels - 1));
661
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -0800662 cur_channel = triplet->chans.first_channel;
Luis R. Rodriguez615aab42009-01-22 15:05:46 -0800663 cur_sub_max_channel = end_channel;
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -0800664
665 /* Basic sanity check */
666 if (cur_sub_max_channel < cur_channel)
667 return NULL;
668
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -0500669 /*
670 * Do not allow overlapping channels. Also channels
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -0800671 * passed in each subband must be monotonically
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -0500672 * increasing
673 */
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -0800674 if (last_sub_max_channel) {
675 if (cur_channel <= last_sub_max_channel)
676 return NULL;
677 if (cur_sub_max_channel <= last_sub_max_channel)
678 return NULL;
679 }
680
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -0500681 /*
682 * When dot11RegulatoryClassesRequired is supported
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -0800683 * we can throw ext triplets as part of this soup,
684 * for now we don't care when those change as we
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -0500685 * don't support them
686 */
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -0800687 *checksum ^= ((cur_channel ^ cur_sub_max_channel) << 8) |
688 ((cur_sub_max_channel ^ cur_sub_max_channel) << 16) |
689 ((triplet->chans.max_power ^ cur_sub_max_channel) << 24);
690
691 last_sub_max_channel = cur_sub_max_channel;
692
693 country_ie += 3;
694 country_ie_len -= 3;
695 num_rules++;
696
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -0500697 /*
698 * Note: this is not a IEEE requirement but
699 * simply a memory requirement
700 */
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -0800701 if (num_rules > NL80211_MAX_SUPP_REG_RULES)
702 return NULL;
703 }
704
705 country_ie = triplets_start;
706 country_ie_len = len_at_triplet;
707
708 size_of_regd = sizeof(struct ieee80211_regdomain) +
709 (num_rules * sizeof(struct ieee80211_reg_rule));
710
711 rd = kzalloc(size_of_regd, GFP_KERNEL);
712 if (!rd)
713 return NULL;
714
715 rd->n_reg_rules = num_rules;
716 rd->alpha2[0] = alpha2[0];
717 rd->alpha2[1] = alpha2[1];
718
719 /* This time around we fill in the rd */
720 while (country_ie_len >= 3) {
Luis R. Rodriguez02e68a32009-01-07 17:43:37 -0800721 int end_channel = 0;
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -0800722 struct ieee80211_country_ie_triplet *triplet =
723 (struct ieee80211_country_ie_triplet *) country_ie;
724 struct ieee80211_reg_rule *reg_rule = NULL;
725 struct ieee80211_freq_range *freq_range = NULL;
726 struct ieee80211_power_rule *power_rule = NULL;
727
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -0500728 /*
729 * Must parse if dot11RegulatoryClassesRequired is true,
730 * we don't support this yet
731 */
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -0800732 if (triplet->ext.reg_extension_id >=
733 IEEE80211_COUNTRY_EXTENSION_ID) {
734 country_ie += 3;
735 country_ie_len -= 3;
736 continue;
737 }
738
739 reg_rule = &rd->reg_rules[i];
740 freq_range = &reg_rule->freq_range;
741 power_rule = &reg_rule->power_rule;
742
743 reg_rule->flags = flags;
744
Luis R. Rodriguez02e68a32009-01-07 17:43:37 -0800745 /* 2 GHz */
746 if (triplet->chans.first_channel <= 14)
747 end_channel = triplet->chans.first_channel +
748 triplet->chans.num_channels;
749 else
Luis R. Rodriguez02e68a32009-01-07 17:43:37 -0800750 end_channel = triplet->chans.first_channel +
751 (4 * (triplet->chans.num_channels - 1));
752
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -0500753 /*
754 * The +10 is since the regulatory domain expects
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -0800755 * the actual band edge, not the center of freq for
756 * its start and end freqs, assuming 20 MHz bandwidth on
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -0500757 * the channels passed
758 */
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -0800759 freq_range->start_freq_khz =
760 MHZ_TO_KHZ(ieee80211_channel_to_frequency(
761 triplet->chans.first_channel) - 10);
762 freq_range->end_freq_khz =
763 MHZ_TO_KHZ(ieee80211_channel_to_frequency(
Luis R. Rodriguez02e68a32009-01-07 17:43:37 -0800764 end_channel) + 10);
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -0800765
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -0500766 /*
767 * These are large arbitrary values we use to intersect later.
768 * Increment this if we ever support >= 40 MHz channels
769 * in IEEE 802.11
770 */
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -0800771 freq_range->max_bandwidth_khz = MHZ_TO_KHZ(40);
772 power_rule->max_antenna_gain = DBI_TO_MBI(100);
773 power_rule->max_eirp = DBM_TO_MBM(100);
774
775 country_ie += 3;
776 country_ie_len -= 3;
777 i++;
778
779 BUG_ON(i > NL80211_MAX_SUPP_REG_RULES);
780 }
781
782 return rd;
783}
784
785
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -0500786/*
787 * Helper for regdom_intersect(), this does the real
788 * mathematical intersection fun
789 */
Luis R. Rodriguez9c964772008-10-30 13:33:53 -0700790static int reg_rules_intersect(
791 const struct ieee80211_reg_rule *rule1,
792 const struct ieee80211_reg_rule *rule2,
793 struct ieee80211_reg_rule *intersected_rule)
794{
795 const struct ieee80211_freq_range *freq_range1, *freq_range2;
796 struct ieee80211_freq_range *freq_range;
797 const struct ieee80211_power_rule *power_rule1, *power_rule2;
798 struct ieee80211_power_rule *power_rule;
799 u32 freq_diff;
800
801 freq_range1 = &rule1->freq_range;
802 freq_range2 = &rule2->freq_range;
803 freq_range = &intersected_rule->freq_range;
804
805 power_rule1 = &rule1->power_rule;
806 power_rule2 = &rule2->power_rule;
807 power_rule = &intersected_rule->power_rule;
808
809 freq_range->start_freq_khz = max(freq_range1->start_freq_khz,
810 freq_range2->start_freq_khz);
811 freq_range->end_freq_khz = min(freq_range1->end_freq_khz,
812 freq_range2->end_freq_khz);
813 freq_range->max_bandwidth_khz = min(freq_range1->max_bandwidth_khz,
814 freq_range2->max_bandwidth_khz);
815
816 freq_diff = freq_range->end_freq_khz - freq_range->start_freq_khz;
817 if (freq_range->max_bandwidth_khz > freq_diff)
818 freq_range->max_bandwidth_khz = freq_diff;
819
820 power_rule->max_eirp = min(power_rule1->max_eirp,
821 power_rule2->max_eirp);
822 power_rule->max_antenna_gain = min(power_rule1->max_antenna_gain,
823 power_rule2->max_antenna_gain);
824
825 intersected_rule->flags = (rule1->flags | rule2->flags);
826
827 if (!is_valid_reg_rule(intersected_rule))
828 return -EINVAL;
829
830 return 0;
831}
832
833/**
834 * regdom_intersect - do the intersection between two regulatory domains
835 * @rd1: first regulatory domain
836 * @rd2: second regulatory domain
837 *
838 * Use this function to get the intersection between two regulatory domains.
839 * Once completed we will mark the alpha2 for the rd as intersected, "98",
840 * as no one single alpha2 can represent this regulatory domain.
841 *
842 * Returns a pointer to the regulatory domain structure which will hold the
843 * resulting intersection of rules between rd1 and rd2. We will
844 * kzalloc() this structure for you.
845 */
846static struct ieee80211_regdomain *regdom_intersect(
847 const struct ieee80211_regdomain *rd1,
848 const struct ieee80211_regdomain *rd2)
849{
850 int r, size_of_regd;
851 unsigned int x, y;
852 unsigned int num_rules = 0, rule_idx = 0;
853 const struct ieee80211_reg_rule *rule1, *rule2;
854 struct ieee80211_reg_rule *intersected_rule;
855 struct ieee80211_regdomain *rd;
856 /* This is just a dummy holder to help us count */
857 struct ieee80211_reg_rule irule;
858
859 /* Uses the stack temporarily for counter arithmetic */
860 intersected_rule = &irule;
861
862 memset(intersected_rule, 0, sizeof(struct ieee80211_reg_rule));
863
864 if (!rd1 || !rd2)
865 return NULL;
866
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -0500867 /*
868 * First we get a count of the rules we'll need, then we actually
Luis R. Rodriguez9c964772008-10-30 13:33:53 -0700869 * build them. This is to so we can malloc() and free() a
870 * regdomain once. The reason we use reg_rules_intersect() here
871 * is it will return -EINVAL if the rule computed makes no sense.
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -0500872 * All rules that do check out OK are valid.
873 */
Luis R. Rodriguez9c964772008-10-30 13:33:53 -0700874
875 for (x = 0; x < rd1->n_reg_rules; x++) {
876 rule1 = &rd1->reg_rules[x];
877 for (y = 0; y < rd2->n_reg_rules; y++) {
878 rule2 = &rd2->reg_rules[y];
879 if (!reg_rules_intersect(rule1, rule2,
880 intersected_rule))
881 num_rules++;
882 memset(intersected_rule, 0,
883 sizeof(struct ieee80211_reg_rule));
884 }
885 }
886
887 if (!num_rules)
888 return NULL;
889
890 size_of_regd = sizeof(struct ieee80211_regdomain) +
891 ((num_rules + 1) * sizeof(struct ieee80211_reg_rule));
892
893 rd = kzalloc(size_of_regd, GFP_KERNEL);
894 if (!rd)
895 return NULL;
896
897 for (x = 0; x < rd1->n_reg_rules; x++) {
898 rule1 = &rd1->reg_rules[x];
899 for (y = 0; y < rd2->n_reg_rules; y++) {
900 rule2 = &rd2->reg_rules[y];
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -0500901 /*
902 * This time around instead of using the stack lets
Luis R. Rodriguez9c964772008-10-30 13:33:53 -0700903 * write to the target rule directly saving ourselves
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -0500904 * a memcpy()
905 */
Luis R. Rodriguez9c964772008-10-30 13:33:53 -0700906 intersected_rule = &rd->reg_rules[rule_idx];
907 r = reg_rules_intersect(rule1, rule2,
908 intersected_rule);
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -0500909 /*
910 * No need to memset here the intersected rule here as
911 * we're not using the stack anymore
912 */
Luis R. Rodriguez9c964772008-10-30 13:33:53 -0700913 if (r)
914 continue;
915 rule_idx++;
916 }
917 }
918
919 if (rule_idx != num_rules) {
920 kfree(rd);
921 return NULL;
922 }
923
924 rd->n_reg_rules = num_rules;
925 rd->alpha2[0] = '9';
926 rd->alpha2[1] = '8';
927
928 return rd;
929}
930
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -0500931/*
932 * XXX: add support for the rest of enum nl80211_reg_rule_flags, we may
933 * want to just have the channel structure use these
934 */
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -0700935static u32 map_regdom_flags(u32 rd_flags)
936{
937 u32 channel_flags = 0;
938 if (rd_flags & NL80211_RRF_PASSIVE_SCAN)
939 channel_flags |= IEEE80211_CHAN_PASSIVE_SCAN;
940 if (rd_flags & NL80211_RRF_NO_IBSS)
941 channel_flags |= IEEE80211_CHAN_NO_IBSS;
942 if (rd_flags & NL80211_RRF_DFS)
943 channel_flags |= IEEE80211_CHAN_RADAR;
944 return channel_flags;
945}
946
Luis R. Rodriguez1fa25e42009-01-22 15:05:44 -0800947static int freq_reg_info_regd(struct wiphy *wiphy,
948 u32 center_freq,
Luis R. Rodriguez038659e2009-05-02 00:37:17 -0400949 u32 desired_bw_khz,
Luis R. Rodriguez1fa25e42009-01-22 15:05:44 -0800950 const struct ieee80211_reg_rule **reg_rule,
951 const struct ieee80211_regdomain *custom_regd)
Johannes Berg8318d782008-01-24 19:38:38 +0100952{
953 int i;
Luis R. Rodriguez0c7dc452009-01-07 17:43:36 -0800954 bool band_rule_found = false;
Luis R. Rodriguez3e0c3ff2009-01-07 17:43:34 -0800955 const struct ieee80211_regdomain *regd;
Luis R. Rodriguez038659e2009-05-02 00:37:17 -0400956 bool bw_fits = false;
957
958 if (!desired_bw_khz)
959 desired_bw_khz = MHZ_TO_KHZ(20);
Johannes Berg8318d782008-01-24 19:38:38 +0100960
Luis R. Rodriguez1fa25e42009-01-22 15:05:44 -0800961 regd = custom_regd ? custom_regd : cfg80211_regdomain;
Luis R. Rodriguez3e0c3ff2009-01-07 17:43:34 -0800962
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -0500963 /*
964 * Follow the driver's regulatory domain, if present, unless a country
965 * IE has been processed or a user wants to help complaince further
966 */
Luis R. Rodriguez7db90f42009-03-09 22:07:41 -0400967 if (last_request->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE &&
968 last_request->initiator != NL80211_REGDOM_SET_BY_USER &&
Luis R. Rodriguez3e0c3ff2009-01-07 17:43:34 -0800969 wiphy->regd)
970 regd = wiphy->regd;
971
972 if (!regd)
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -0700973 return -EINVAL;
974
Luis R. Rodriguez3e0c3ff2009-01-07 17:43:34 -0800975 for (i = 0; i < regd->n_reg_rules; i++) {
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -0700976 const struct ieee80211_reg_rule *rr;
977 const struct ieee80211_freq_range *fr = NULL;
978 const struct ieee80211_power_rule *pr = NULL;
979
Luis R. Rodriguez3e0c3ff2009-01-07 17:43:34 -0800980 rr = &regd->reg_rules[i];
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -0700981 fr = &rr->freq_range;
982 pr = &rr->power_rule;
Luis R. Rodriguez0c7dc452009-01-07 17:43:36 -0800983
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -0500984 /*
985 * We only need to know if one frequency rule was
Luis R. Rodriguez0c7dc452009-01-07 17:43:36 -0800986 * was in center_freq's band, that's enough, so lets
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -0500987 * not overwrite it once found
988 */
Luis R. Rodriguez0c7dc452009-01-07 17:43:36 -0800989 if (!band_rule_found)
990 band_rule_found = freq_in_rule_band(fr, center_freq);
991
Luis R. Rodriguez038659e2009-05-02 00:37:17 -0400992 bw_fits = reg_does_bw_fit(fr,
993 center_freq,
994 desired_bw_khz);
Luis R. Rodriguez0c7dc452009-01-07 17:43:36 -0800995
Luis R. Rodriguez038659e2009-05-02 00:37:17 -0400996 if (band_rule_found && bw_fits) {
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -0700997 *reg_rule = rr;
Luis R. Rodriguez038659e2009-05-02 00:37:17 -0400998 return 0;
Johannes Berg8318d782008-01-24 19:38:38 +0100999 }
1000 }
1001
Luis R. Rodriguez0c7dc452009-01-07 17:43:36 -08001002 if (!band_rule_found)
1003 return -ERANGE;
1004
Luis R. Rodriguez038659e2009-05-02 00:37:17 -04001005 return -EINVAL;
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07001006}
Luis R. Rodriguez34f57342009-01-22 15:05:45 -08001007EXPORT_SYMBOL(freq_reg_info);
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07001008
Luis R. Rodriguez038659e2009-05-02 00:37:17 -04001009int freq_reg_info(struct wiphy *wiphy,
1010 u32 center_freq,
1011 u32 desired_bw_khz,
1012 const struct ieee80211_reg_rule **reg_rule)
Luis R. Rodriguez1fa25e42009-01-22 15:05:44 -08001013{
Luis R. Rodriguezac46d482009-05-01 18:44:50 -04001014 assert_cfg80211_lock();
Luis R. Rodriguez038659e2009-05-02 00:37:17 -04001015 return freq_reg_info_regd(wiphy,
1016 center_freq,
1017 desired_bw_khz,
1018 reg_rule,
1019 NULL);
Luis R. Rodriguez1fa25e42009-01-22 15:05:44 -08001020}
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07001021
Luis R. Rodriguez038659e2009-05-02 00:37:17 -04001022/*
1023 * Note that right now we assume the desired channel bandwidth
1024 * is always 20 MHz for each individual channel (HT40 uses 20 MHz
1025 * per channel, the primary and the extension channel). To support
1026 * smaller custom bandwidths such as 5 MHz or 10 MHz we'll need a
1027 * new ieee80211_channel.target_bw and re run the regulatory check
1028 * on the wiphy with the target_bw specified. Then we can simply use
1029 * that below for the desired_bw_khz below.
1030 */
Luis R. Rodrigueza92a3ce2009-01-07 17:43:33 -08001031static void handle_channel(struct wiphy *wiphy, enum ieee80211_band band,
1032 unsigned int chan_idx)
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07001033{
1034 int r;
Luis R. Rodriguez038659e2009-05-02 00:37:17 -04001035 u32 flags, bw_flags = 0;
1036 u32 desired_bw_khz = MHZ_TO_KHZ(20);
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07001037 const struct ieee80211_reg_rule *reg_rule = NULL;
1038 const struct ieee80211_power_rule *power_rule = NULL;
Luis R. Rodriguez038659e2009-05-02 00:37:17 -04001039 const struct ieee80211_freq_range *freq_range = NULL;
Luis R. Rodrigueza92a3ce2009-01-07 17:43:33 -08001040 struct ieee80211_supported_band *sband;
1041 struct ieee80211_channel *chan;
Luis R. Rodriguezfe33eb32009-02-21 00:04:30 -05001042 struct wiphy *request_wiphy = NULL;
Luis R. Rodrigueza92a3ce2009-01-07 17:43:33 -08001043
Luis R. Rodriguez761cf7e2009-02-21 00:04:25 -05001044 assert_cfg80211_lock();
1045
Luis R. Rodriguez806a9e32009-02-21 00:04:26 -05001046 request_wiphy = wiphy_idx_to_wiphy(last_request->wiphy_idx);
1047
Luis R. Rodrigueza92a3ce2009-01-07 17:43:33 -08001048 sband = wiphy->bands[band];
1049 BUG_ON(chan_idx >= sband->n_channels);
1050 chan = &sband->channels[chan_idx];
1051
1052 flags = chan->orig_flags;
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07001053
Luis R. Rodriguez038659e2009-05-02 00:37:17 -04001054 r = freq_reg_info(wiphy,
1055 MHZ_TO_KHZ(chan->center_freq),
1056 desired_bw_khz,
1057 &reg_rule);
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07001058
1059 if (r) {
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -05001060 /*
1061 * This means no regulatory rule was found in the country IE
Luis R. Rodriguez0c7dc452009-01-07 17:43:36 -08001062 * with a frequency range on the center_freq's band, since
1063 * IEEE-802.11 allows for a country IE to have a subset of the
1064 * regulatory information provided in a country we ignore
1065 * disabling the channel unless at least one reg rule was
1066 * found on the center_freq's band. For details see this
1067 * clarification:
1068 *
1069 * http://tinyurl.com/11d-clarification
1070 */
1071 if (r == -ERANGE &&
Luis R. Rodriguez7db90f42009-03-09 22:07:41 -04001072 last_request->initiator ==
1073 NL80211_REGDOM_SET_BY_COUNTRY_IE) {
Luis R. Rodriguez0c7dc452009-01-07 17:43:36 -08001074#ifdef CONFIG_CFG80211_REG_DEBUG
1075 printk(KERN_DEBUG "cfg80211: Leaving channel %d MHz "
1076 "intact on %s - no rule found in band on "
1077 "Country IE\n",
1078 chan->center_freq, wiphy_name(wiphy));
1079#endif
1080 } else {
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -05001081 /*
1082 * In this case we know the country IE has at least one reg rule
1083 * for the band so we respect its band definitions
1084 */
Luis R. Rodriguez0c7dc452009-01-07 17:43:36 -08001085#ifdef CONFIG_CFG80211_REG_DEBUG
Luis R. Rodriguez7db90f42009-03-09 22:07:41 -04001086 if (last_request->initiator ==
1087 NL80211_REGDOM_SET_BY_COUNTRY_IE)
Luis R. Rodriguez0c7dc452009-01-07 17:43:36 -08001088 printk(KERN_DEBUG "cfg80211: Disabling "
1089 "channel %d MHz on %s due to "
1090 "Country IE\n",
1091 chan->center_freq, wiphy_name(wiphy));
1092#endif
1093 flags |= IEEE80211_CHAN_DISABLED;
1094 chan->flags = flags;
1095 }
Johannes Berg8318d782008-01-24 19:38:38 +01001096 return;
1097 }
1098
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07001099 power_rule = &reg_rule->power_rule;
Luis R. Rodriguez038659e2009-05-02 00:37:17 -04001100 freq_range = &reg_rule->freq_range;
1101
1102 if (freq_range->max_bandwidth_khz < MHZ_TO_KHZ(40))
1103 bw_flags = IEEE80211_CHAN_NO_HT40;
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07001104
Luis R. Rodriguez7db90f42009-03-09 22:07:41 -04001105 if (last_request->initiator == NL80211_REGDOM_SET_BY_DRIVER &&
Luis R. Rodriguez806a9e32009-02-21 00:04:26 -05001106 request_wiphy && request_wiphy == wiphy &&
Johannes Berg5be83de2009-11-19 00:56:28 +01001107 request_wiphy->flags & WIPHY_FLAG_STRICT_REGULATORY) {
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -05001108 /*
1109 * This gaurantees the driver's requested regulatory domain
Luis R. Rodriguezf9763762009-01-22 15:05:52 -08001110 * will always be used as a base for further regulatory
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -05001111 * settings
1112 */
Luis R. Rodriguezf9763762009-01-22 15:05:52 -08001113 chan->flags = chan->orig_flags =
Luis R. Rodriguez038659e2009-05-02 00:37:17 -04001114 map_regdom_flags(reg_rule->flags) | bw_flags;
Luis R. Rodriguezf9763762009-01-22 15:05:52 -08001115 chan->max_antenna_gain = chan->orig_mag =
1116 (int) MBI_TO_DBI(power_rule->max_antenna_gain);
Luis R. Rodriguezf9763762009-01-22 15:05:52 -08001117 chan->max_power = chan->orig_mpwr =
1118 (int) MBM_TO_DBM(power_rule->max_eirp);
1119 return;
1120 }
1121
Luis R. Rodriguez038659e2009-05-02 00:37:17 -04001122 chan->flags = flags | bw_flags | map_regdom_flags(reg_rule->flags);
Johannes Berg8318d782008-01-24 19:38:38 +01001123 chan->max_antenna_gain = min(chan->orig_mag,
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07001124 (int) MBI_TO_DBI(power_rule->max_antenna_gain));
John W. Linville253898c2008-04-03 15:32:54 -04001125 if (chan->orig_mpwr)
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07001126 chan->max_power = min(chan->orig_mpwr,
1127 (int) MBM_TO_DBM(power_rule->max_eirp));
John W. Linville253898c2008-04-03 15:32:54 -04001128 else
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07001129 chan->max_power = (int) MBM_TO_DBM(power_rule->max_eirp);
Johannes Berg8318d782008-01-24 19:38:38 +01001130}
1131
Luis R. Rodrigueza92a3ce2009-01-07 17:43:33 -08001132static void handle_band(struct wiphy *wiphy, enum ieee80211_band band)
Johannes Berg8318d782008-01-24 19:38:38 +01001133{
Luis R. Rodrigueza92a3ce2009-01-07 17:43:33 -08001134 unsigned int i;
1135 struct ieee80211_supported_band *sband;
1136
1137 BUG_ON(!wiphy->bands[band]);
1138 sband = wiphy->bands[band];
Johannes Berg8318d782008-01-24 19:38:38 +01001139
1140 for (i = 0; i < sband->n_channels; i++)
Luis R. Rodrigueza92a3ce2009-01-07 17:43:33 -08001141 handle_channel(wiphy, band, i);
Johannes Berg8318d782008-01-24 19:38:38 +01001142}
1143
Luis R. Rodriguez7db90f42009-03-09 22:07:41 -04001144static bool ignore_reg_update(struct wiphy *wiphy,
1145 enum nl80211_reg_initiator initiator)
Luis R. Rodriguez14b98152008-11-12 14:22:03 -08001146{
1147 if (!last_request)
1148 return true;
Luis R. Rodriguez7db90f42009-03-09 22:07:41 -04001149 if (initiator == NL80211_REGDOM_SET_BY_CORE &&
Johannes Berg5be83de2009-11-19 00:56:28 +01001150 wiphy->flags & WIPHY_FLAG_CUSTOM_REGULATORY)
Luis R. Rodriguez14b98152008-11-12 14:22:03 -08001151 return true;
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -05001152 /*
1153 * wiphy->regd will be set once the device has its own
1154 * desired regulatory domain set
1155 */
Johannes Berg5be83de2009-11-19 00:56:28 +01001156 if (wiphy->flags & WIPHY_FLAG_STRICT_REGULATORY && !wiphy->regd &&
Luis R. Rodriguezf9763762009-01-22 15:05:52 -08001157 !is_world_regdom(last_request->alpha2))
Luis R. Rodriguez14b98152008-11-12 14:22:03 -08001158 return true;
1159 return false;
1160}
1161
Luis R. Rodriguez7db90f42009-03-09 22:07:41 -04001162static void update_all_wiphy_regulatory(enum nl80211_reg_initiator initiator)
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07001163{
Johannes Berg79c97e92009-07-07 03:56:12 +02001164 struct cfg80211_registered_device *rdev;
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07001165
Johannes Berg79c97e92009-07-07 03:56:12 +02001166 list_for_each_entry(rdev, &cfg80211_rdev_list, list)
1167 wiphy_update_regulatory(&rdev->wiphy, initiator);
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07001168}
1169
Luis R. Rodrigueze38f8a72009-02-21 00:20:39 -05001170static void handle_reg_beacon(struct wiphy *wiphy,
1171 unsigned int chan_idx,
1172 struct reg_beacon *reg_beacon)
1173{
Luis R. Rodrigueze38f8a72009-02-21 00:20:39 -05001174 struct ieee80211_supported_band *sband;
1175 struct ieee80211_channel *chan;
Luis R. Rodriguez6bad8762009-04-02 14:08:09 -04001176 bool channel_changed = false;
1177 struct ieee80211_channel chan_before;
Luis R. Rodrigueze38f8a72009-02-21 00:20:39 -05001178
1179 assert_cfg80211_lock();
1180
1181 sband = wiphy->bands[reg_beacon->chan.band];
1182 chan = &sband->channels[chan_idx];
1183
1184 if (likely(chan->center_freq != reg_beacon->chan.center_freq))
1185 return;
1186
Luis R. Rodriguez6bad8762009-04-02 14:08:09 -04001187 if (chan->beacon_found)
1188 return;
1189
1190 chan->beacon_found = true;
1191
Johannes Berg5be83de2009-11-19 00:56:28 +01001192 if (wiphy->flags & WIPHY_FLAG_DISABLE_BEACON_HINTS)
Luis R. Rodriguez37184242009-07-30 17:43:48 -07001193 return;
1194
Luis R. Rodriguez6bad8762009-04-02 14:08:09 -04001195 chan_before.center_freq = chan->center_freq;
1196 chan_before.flags = chan->flags;
1197
Luis R. Rodriguez37184242009-07-30 17:43:48 -07001198 if (chan->flags & IEEE80211_CHAN_PASSIVE_SCAN) {
Luis R. Rodrigueze38f8a72009-02-21 00:20:39 -05001199 chan->flags &= ~IEEE80211_CHAN_PASSIVE_SCAN;
Luis R. Rodriguez6bad8762009-04-02 14:08:09 -04001200 channel_changed = true;
Luis R. Rodrigueze38f8a72009-02-21 00:20:39 -05001201 }
1202
Luis R. Rodriguez37184242009-07-30 17:43:48 -07001203 if (chan->flags & IEEE80211_CHAN_NO_IBSS) {
Luis R. Rodrigueze38f8a72009-02-21 00:20:39 -05001204 chan->flags &= ~IEEE80211_CHAN_NO_IBSS;
Luis R. Rodriguez6bad8762009-04-02 14:08:09 -04001205 channel_changed = true;
Luis R. Rodrigueze38f8a72009-02-21 00:20:39 -05001206 }
1207
Luis R. Rodriguez6bad8762009-04-02 14:08:09 -04001208 if (channel_changed)
1209 nl80211_send_beacon_hint_event(wiphy, &chan_before, chan);
Luis R. Rodrigueze38f8a72009-02-21 00:20:39 -05001210}
1211
1212/*
1213 * Called when a scan on a wiphy finds a beacon on
1214 * new channel
1215 */
1216static void wiphy_update_new_beacon(struct wiphy *wiphy,
1217 struct reg_beacon *reg_beacon)
1218{
1219 unsigned int i;
1220 struct ieee80211_supported_band *sband;
1221
1222 assert_cfg80211_lock();
1223
1224 if (!wiphy->bands[reg_beacon->chan.band])
1225 return;
1226
1227 sband = wiphy->bands[reg_beacon->chan.band];
1228
1229 for (i = 0; i < sband->n_channels; i++)
1230 handle_reg_beacon(wiphy, i, reg_beacon);
1231}
1232
1233/*
1234 * Called upon reg changes or a new wiphy is added
1235 */
1236static void wiphy_update_beacon_reg(struct wiphy *wiphy)
1237{
1238 unsigned int i;
1239 struct ieee80211_supported_band *sband;
1240 struct reg_beacon *reg_beacon;
1241
1242 assert_cfg80211_lock();
1243
1244 if (list_empty(&reg_beacon_list))
1245 return;
1246
1247 list_for_each_entry(reg_beacon, &reg_beacon_list, list) {
1248 if (!wiphy->bands[reg_beacon->chan.band])
1249 continue;
1250 sband = wiphy->bands[reg_beacon->chan.band];
1251 for (i = 0; i < sband->n_channels; i++)
1252 handle_reg_beacon(wiphy, i, reg_beacon);
1253 }
1254}
1255
1256static bool reg_is_world_roaming(struct wiphy *wiphy)
1257{
1258 if (is_world_regdom(cfg80211_regdomain->alpha2) ||
1259 (wiphy->regd && is_world_regdom(wiphy->regd->alpha2)))
1260 return true;
Luis R. Rodriguezb1ed8dd2009-05-02 00:34:15 -04001261 if (last_request &&
1262 last_request->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE &&
Johannes Berg5be83de2009-11-19 00:56:28 +01001263 wiphy->flags & WIPHY_FLAG_CUSTOM_REGULATORY)
Luis R. Rodrigueze38f8a72009-02-21 00:20:39 -05001264 return true;
1265 return false;
1266}
1267
1268/* Reap the advantages of previously found beacons */
1269static void reg_process_beacons(struct wiphy *wiphy)
1270{
Luis R. Rodriguezb1ed8dd2009-05-02 00:34:15 -04001271 /*
1272 * Means we are just firing up cfg80211, so no beacons would
1273 * have been processed yet.
1274 */
1275 if (!last_request)
1276 return;
Luis R. Rodrigueze38f8a72009-02-21 00:20:39 -05001277 if (!reg_is_world_roaming(wiphy))
1278 return;
1279 wiphy_update_beacon_reg(wiphy);
1280}
1281
Luis R. Rodriguez038659e2009-05-02 00:37:17 -04001282static bool is_ht40_not_allowed(struct ieee80211_channel *chan)
1283{
1284 if (!chan)
1285 return true;
1286 if (chan->flags & IEEE80211_CHAN_DISABLED)
1287 return true;
1288 /* This would happen when regulatory rules disallow HT40 completely */
1289 if (IEEE80211_CHAN_NO_HT40 == (chan->flags & (IEEE80211_CHAN_NO_HT40)))
1290 return true;
1291 return false;
1292}
1293
1294static void reg_process_ht_flags_channel(struct wiphy *wiphy,
1295 enum ieee80211_band band,
1296 unsigned int chan_idx)
1297{
1298 struct ieee80211_supported_band *sband;
1299 struct ieee80211_channel *channel;
1300 struct ieee80211_channel *channel_before = NULL, *channel_after = NULL;
1301 unsigned int i;
1302
1303 assert_cfg80211_lock();
1304
1305 sband = wiphy->bands[band];
1306 BUG_ON(chan_idx >= sband->n_channels);
1307 channel = &sband->channels[chan_idx];
1308
1309 if (is_ht40_not_allowed(channel)) {
1310 channel->flags |= IEEE80211_CHAN_NO_HT40;
1311 return;
1312 }
1313
1314 /*
1315 * We need to ensure the extension channels exist to
1316 * be able to use HT40- or HT40+, this finds them (or not)
1317 */
1318 for (i = 0; i < sband->n_channels; i++) {
1319 struct ieee80211_channel *c = &sband->channels[i];
1320 if (c->center_freq == (channel->center_freq - 20))
1321 channel_before = c;
1322 if (c->center_freq == (channel->center_freq + 20))
1323 channel_after = c;
1324 }
1325
1326 /*
1327 * Please note that this assumes target bandwidth is 20 MHz,
1328 * if that ever changes we also need to change the below logic
1329 * to include that as well.
1330 */
1331 if (is_ht40_not_allowed(channel_before))
Luis R. Rodriguez689da1b2009-05-02 00:37:18 -04001332 channel->flags |= IEEE80211_CHAN_NO_HT40MINUS;
Luis R. Rodriguez038659e2009-05-02 00:37:17 -04001333 else
Luis R. Rodriguez689da1b2009-05-02 00:37:18 -04001334 channel->flags &= ~IEEE80211_CHAN_NO_HT40MINUS;
Luis R. Rodriguez038659e2009-05-02 00:37:17 -04001335
1336 if (is_ht40_not_allowed(channel_after))
Luis R. Rodriguez689da1b2009-05-02 00:37:18 -04001337 channel->flags |= IEEE80211_CHAN_NO_HT40PLUS;
Luis R. Rodriguez038659e2009-05-02 00:37:17 -04001338 else
Luis R. Rodriguez689da1b2009-05-02 00:37:18 -04001339 channel->flags &= ~IEEE80211_CHAN_NO_HT40PLUS;
Luis R. Rodriguez038659e2009-05-02 00:37:17 -04001340}
1341
1342static void reg_process_ht_flags_band(struct wiphy *wiphy,
1343 enum ieee80211_band band)
1344{
1345 unsigned int i;
1346 struct ieee80211_supported_band *sband;
1347
1348 BUG_ON(!wiphy->bands[band]);
1349 sband = wiphy->bands[band];
1350
1351 for (i = 0; i < sband->n_channels; i++)
1352 reg_process_ht_flags_channel(wiphy, band, i);
1353}
1354
1355static void reg_process_ht_flags(struct wiphy *wiphy)
1356{
1357 enum ieee80211_band band;
1358
1359 if (!wiphy)
1360 return;
1361
1362 for (band = 0; band < IEEE80211_NUM_BANDS; band++) {
1363 if (wiphy->bands[band])
1364 reg_process_ht_flags_band(wiphy, band);
1365 }
1366
1367}
1368
Luis R. Rodriguez7db90f42009-03-09 22:07:41 -04001369void wiphy_update_regulatory(struct wiphy *wiphy,
1370 enum nl80211_reg_initiator initiator)
Johannes Berg8318d782008-01-24 19:38:38 +01001371{
1372 enum ieee80211_band band;
Luis R. Rodriguezd46e5b12009-01-22 15:05:50 -08001373
Luis R. Rodriguez7db90f42009-03-09 22:07:41 -04001374 if (ignore_reg_update(wiphy, initiator))
Luis R. Rodrigueze38f8a72009-02-21 00:20:39 -05001375 goto out;
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07001376 for (band = 0; band < IEEE80211_NUM_BANDS; band++) {
Johannes Berg8318d782008-01-24 19:38:38 +01001377 if (wiphy->bands[band])
Luis R. Rodrigueza92a3ce2009-01-07 17:43:33 -08001378 handle_band(wiphy, band);
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07001379 }
Luis R. Rodrigueze38f8a72009-02-21 00:20:39 -05001380out:
1381 reg_process_beacons(wiphy);
Luis R. Rodriguez038659e2009-05-02 00:37:17 -04001382 reg_process_ht_flags(wiphy);
Luis R. Rodriguez560e28e2009-01-07 17:43:32 -08001383 if (wiphy->reg_notifier)
Luis R. Rodriguez716f9392009-01-22 15:05:51 -08001384 wiphy->reg_notifier(wiphy, last_request);
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07001385}
1386
Luis R. Rodriguez1fa25e42009-01-22 15:05:44 -08001387static void handle_channel_custom(struct wiphy *wiphy,
1388 enum ieee80211_band band,
1389 unsigned int chan_idx,
1390 const struct ieee80211_regdomain *regd)
1391{
1392 int r;
Luis R. Rodriguez038659e2009-05-02 00:37:17 -04001393 u32 desired_bw_khz = MHZ_TO_KHZ(20);
1394 u32 bw_flags = 0;
Luis R. Rodriguez1fa25e42009-01-22 15:05:44 -08001395 const struct ieee80211_reg_rule *reg_rule = NULL;
1396 const struct ieee80211_power_rule *power_rule = NULL;
Luis R. Rodriguez038659e2009-05-02 00:37:17 -04001397 const struct ieee80211_freq_range *freq_range = NULL;
Luis R. Rodriguez1fa25e42009-01-22 15:05:44 -08001398 struct ieee80211_supported_band *sband;
1399 struct ieee80211_channel *chan;
1400
Luis R. Rodriguezabc73812009-07-30 17:38:08 -07001401 assert_reg_lock();
Luis R. Rodriguezac46d482009-05-01 18:44:50 -04001402
Luis R. Rodriguez1fa25e42009-01-22 15:05:44 -08001403 sband = wiphy->bands[band];
1404 BUG_ON(chan_idx >= sband->n_channels);
1405 chan = &sband->channels[chan_idx];
1406
Luis R. Rodriguez038659e2009-05-02 00:37:17 -04001407 r = freq_reg_info_regd(wiphy,
1408 MHZ_TO_KHZ(chan->center_freq),
1409 desired_bw_khz,
1410 &reg_rule,
1411 regd);
Luis R. Rodriguez1fa25e42009-01-22 15:05:44 -08001412
1413 if (r) {
1414 chan->flags = IEEE80211_CHAN_DISABLED;
1415 return;
1416 }
1417
1418 power_rule = &reg_rule->power_rule;
Luis R. Rodriguez038659e2009-05-02 00:37:17 -04001419 freq_range = &reg_rule->freq_range;
Luis R. Rodriguez1fa25e42009-01-22 15:05:44 -08001420
Luis R. Rodriguez038659e2009-05-02 00:37:17 -04001421 if (freq_range->max_bandwidth_khz < MHZ_TO_KHZ(40))
1422 bw_flags = IEEE80211_CHAN_NO_HT40;
1423
1424 chan->flags |= map_regdom_flags(reg_rule->flags) | bw_flags;
Luis R. Rodriguez1fa25e42009-01-22 15:05:44 -08001425 chan->max_antenna_gain = (int) MBI_TO_DBI(power_rule->max_antenna_gain);
Luis R. Rodriguez1fa25e42009-01-22 15:05:44 -08001426 chan->max_power = (int) MBM_TO_DBM(power_rule->max_eirp);
1427}
1428
1429static void handle_band_custom(struct wiphy *wiphy, enum ieee80211_band band,
1430 const struct ieee80211_regdomain *regd)
1431{
1432 unsigned int i;
1433 struct ieee80211_supported_band *sband;
1434
1435 BUG_ON(!wiphy->bands[band]);
1436 sband = wiphy->bands[band];
1437
1438 for (i = 0; i < sband->n_channels; i++)
1439 handle_channel_custom(wiphy, band, i, regd);
1440}
1441
1442/* Used by drivers prior to wiphy registration */
1443void wiphy_apply_custom_regulatory(struct wiphy *wiphy,
1444 const struct ieee80211_regdomain *regd)
1445{
1446 enum ieee80211_band band;
Luis R. Rodriguezbbcf3f02009-05-19 17:49:47 -04001447 unsigned int bands_set = 0;
Luis R. Rodriguezac46d482009-05-01 18:44:50 -04001448
Luis R. Rodriguezabc73812009-07-30 17:38:08 -07001449 mutex_lock(&reg_mutex);
Luis R. Rodriguez1fa25e42009-01-22 15:05:44 -08001450 for (band = 0; band < IEEE80211_NUM_BANDS; band++) {
Luis R. Rodriguezbbcf3f02009-05-19 17:49:47 -04001451 if (!wiphy->bands[band])
1452 continue;
1453 handle_band_custom(wiphy, band, regd);
1454 bands_set++;
Luis R. Rodriguez1fa25e42009-01-22 15:05:44 -08001455 }
Luis R. Rodriguezabc73812009-07-30 17:38:08 -07001456 mutex_unlock(&reg_mutex);
Luis R. Rodriguezbbcf3f02009-05-19 17:49:47 -04001457
1458 /*
1459 * no point in calling this if it won't have any effect
1460 * on your device's supportd bands.
1461 */
1462 WARN_ON(!bands_set);
Luis R. Rodriguez1fa25e42009-01-22 15:05:44 -08001463}
1464EXPORT_SYMBOL(wiphy_apply_custom_regulatory);
1465
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -05001466/*
1467 * Return value which can be used by ignore_request() to indicate
1468 * it has been determined we should intersect two regulatory domains
1469 */
Luis R. Rodriguez9c964772008-10-30 13:33:53 -07001470#define REG_INTERSECT 1
1471
Johannes Berg84fa4f42008-10-24 20:32:23 +02001472/* This has the logic which determines when a new request
1473 * should be ignored. */
Luis R. Rodriguez2f92cd22009-02-21 00:24:16 -05001474static int ignore_request(struct wiphy *wiphy,
1475 struct regulatory_request *pending_request)
Johannes Berg84fa4f42008-10-24 20:32:23 +02001476{
Luis R. Rodriguez806a9e32009-02-21 00:04:26 -05001477 struct wiphy *last_wiphy = NULL;
Luis R. Rodriguez761cf7e2009-02-21 00:04:25 -05001478
1479 assert_cfg80211_lock();
1480
Johannes Berg84fa4f42008-10-24 20:32:23 +02001481 /* All initial requests are respected */
1482 if (!last_request)
1483 return 0;
1484
Luis R. Rodriguez2f92cd22009-02-21 00:24:16 -05001485 switch (pending_request->initiator) {
Luis R. Rodriguez7db90f42009-03-09 22:07:41 -04001486 case NL80211_REGDOM_SET_BY_CORE:
Luis R. Rodriguezba25c142009-02-21 00:04:23 -05001487 return -EINVAL;
Luis R. Rodriguez7db90f42009-03-09 22:07:41 -04001488 case NL80211_REGDOM_SET_BY_COUNTRY_IE:
Luis R. Rodriguez806a9e32009-02-21 00:04:26 -05001489
1490 last_wiphy = wiphy_idx_to_wiphy(last_request->wiphy_idx);
1491
Luis R. Rodriguez2f92cd22009-02-21 00:24:16 -05001492 if (unlikely(!is_an_alpha2(pending_request->alpha2)))
Johannes Berg84fa4f42008-10-24 20:32:23 +02001493 return -EINVAL;
Luis R. Rodriguez7db90f42009-03-09 22:07:41 -04001494 if (last_request->initiator ==
1495 NL80211_REGDOM_SET_BY_COUNTRY_IE) {
Luis R. Rodriguez806a9e32009-02-21 00:04:26 -05001496 if (last_wiphy != wiphy) {
Johannes Berg84fa4f42008-10-24 20:32:23 +02001497 /*
1498 * Two cards with two APs claiming different
Thadeu Lima de Souza Cascardo1fe90b02009-08-11 11:18:42 -03001499 * Country IE alpha2s. We could
Johannes Berg84fa4f42008-10-24 20:32:23 +02001500 * intersect them, but that seems unlikely
1501 * to be correct. Reject second one for now.
1502 */
Luis R. Rodriguez2f92cd22009-02-21 00:24:16 -05001503 if (regdom_changes(pending_request->alpha2))
Johannes Berg84fa4f42008-10-24 20:32:23 +02001504 return -EOPNOTSUPP;
1505 return -EALREADY;
1506 }
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -05001507 /*
1508 * Two consecutive Country IE hints on the same wiphy.
1509 * This should be picked up early by the driver/stack
1510 */
Luis R. Rodriguez2f92cd22009-02-21 00:24:16 -05001511 if (WARN_ON(regdom_changes(pending_request->alpha2)))
Johannes Berg84fa4f42008-10-24 20:32:23 +02001512 return 0;
1513 return -EALREADY;
1514 }
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08001515 return REG_INTERSECT;
Luis R. Rodriguez7db90f42009-03-09 22:07:41 -04001516 case NL80211_REGDOM_SET_BY_DRIVER:
1517 if (last_request->initiator == NL80211_REGDOM_SET_BY_CORE) {
Luis R. Rodrigueze74b1e72009-01-22 15:05:48 -08001518 if (is_old_static_regdom(cfg80211_regdomain))
1519 return 0;
Luis R. Rodriguez2f92cd22009-02-21 00:24:16 -05001520 if (regdom_changes(pending_request->alpha2))
Luis R. Rodrigueze74b1e72009-01-22 15:05:48 -08001521 return 0;
Johannes Berg84fa4f42008-10-24 20:32:23 +02001522 return -EALREADY;
Luis R. Rodrigueze74b1e72009-01-22 15:05:48 -08001523 }
Luis R. Rodriguezfff32c02009-02-21 00:04:32 -05001524
1525 /*
1526 * This would happen if you unplug and plug your card
1527 * back in or if you add a new device for which the previously
1528 * loaded card also agrees on the regulatory domain.
1529 */
Luis R. Rodriguez7db90f42009-03-09 22:07:41 -04001530 if (last_request->initiator == NL80211_REGDOM_SET_BY_DRIVER &&
Luis R. Rodriguez2f92cd22009-02-21 00:24:16 -05001531 !regdom_changes(pending_request->alpha2))
Luis R. Rodriguezfff32c02009-02-21 00:04:32 -05001532 return -EALREADY;
1533
Luis R. Rodriguez3e0c3ff2009-01-07 17:43:34 -08001534 return REG_INTERSECT;
Luis R. Rodriguez7db90f42009-03-09 22:07:41 -04001535 case NL80211_REGDOM_SET_BY_USER:
1536 if (last_request->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE)
Luis R. Rodriguez9c964772008-10-30 13:33:53 -07001537 return REG_INTERSECT;
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -05001538 /*
1539 * If the user knows better the user should set the regdom
1540 * to their country before the IE is picked up
1541 */
Luis R. Rodriguez7db90f42009-03-09 22:07:41 -04001542 if (last_request->initiator == NL80211_REGDOM_SET_BY_USER &&
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08001543 last_request->intersect)
1544 return -EOPNOTSUPP;
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -05001545 /*
1546 * Process user requests only after previous user/driver/core
1547 * requests have been processed
1548 */
Luis R. Rodriguez7db90f42009-03-09 22:07:41 -04001549 if (last_request->initiator == NL80211_REGDOM_SET_BY_CORE ||
1550 last_request->initiator == NL80211_REGDOM_SET_BY_DRIVER ||
1551 last_request->initiator == NL80211_REGDOM_SET_BY_USER) {
Luis R. Rodriguez69b15722009-02-21 00:04:33 -05001552 if (regdom_changes(last_request->alpha2))
Luis R. Rodriguez5eebade2009-01-22 15:05:47 -08001553 return -EAGAIN;
1554 }
1555
Luis R. Rodrigueze74b1e72009-01-22 15:05:48 -08001556 if (!is_old_static_regdom(cfg80211_regdomain) &&
Luis R. Rodriguez2f92cd22009-02-21 00:24:16 -05001557 !regdom_changes(pending_request->alpha2))
Luis R. Rodrigueze74b1e72009-01-22 15:05:48 -08001558 return -EALREADY;
1559
Johannes Berg84fa4f42008-10-24 20:32:23 +02001560 return 0;
1561 }
1562
1563 return -EINVAL;
1564}
1565
Luis R. Rodriguezd1c96a92009-02-21 00:24:13 -05001566/**
1567 * __regulatory_hint - hint to the wireless core a regulatory domain
1568 * @wiphy: if the hint comes from country information from an AP, this
1569 * is required to be set to the wiphy that received the information
Luis R. Rodriguez28da32d2009-02-21 00:24:14 -05001570 * @pending_request: the regulatory request currently being processed
Luis R. Rodriguezd1c96a92009-02-21 00:24:13 -05001571 *
1572 * The Wireless subsystem can use this function to hint to the wireless core
Luis R. Rodriguez28da32d2009-02-21 00:24:14 -05001573 * what it believes should be the current regulatory domain.
Luis R. Rodriguezd1c96a92009-02-21 00:24:13 -05001574 *
1575 * Returns zero if all went fine, %-EALREADY if a regulatory domain had
1576 * already been set or other standard error codes.
1577 *
Luis R. Rodriguezabc73812009-07-30 17:38:08 -07001578 * Caller must hold &cfg80211_mutex and &reg_mutex
Luis R. Rodriguezd1c96a92009-02-21 00:24:13 -05001579 */
Luis R. Rodriguez28da32d2009-02-21 00:24:14 -05001580static int __regulatory_hint(struct wiphy *wiphy,
1581 struct regulatory_request *pending_request)
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07001582{
Luis R. Rodriguez9c964772008-10-30 13:33:53 -07001583 bool intersect = false;
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07001584 int r = 0;
1585
Luis R. Rodriguez761cf7e2009-02-21 00:04:25 -05001586 assert_cfg80211_lock();
1587
Luis R. Rodriguez2f92cd22009-02-21 00:24:16 -05001588 r = ignore_request(wiphy, pending_request);
Luis R. Rodriguez9c964772008-10-30 13:33:53 -07001589
Luis R. Rodriguez3e0c3ff2009-01-07 17:43:34 -08001590 if (r == REG_INTERSECT) {
Luis R. Rodriguez7db90f42009-03-09 22:07:41 -04001591 if (pending_request->initiator ==
1592 NL80211_REGDOM_SET_BY_DRIVER) {
Luis R. Rodriguez3e0c3ff2009-01-07 17:43:34 -08001593 r = reg_copy_regd(&wiphy->regd, cfg80211_regdomain);
Luis R. Rodriguezd951c1d2009-02-21 00:24:15 -05001594 if (r) {
1595 kfree(pending_request);
Luis R. Rodriguez3e0c3ff2009-01-07 17:43:34 -08001596 return r;
Luis R. Rodriguezd951c1d2009-02-21 00:24:15 -05001597 }
Luis R. Rodriguez3e0c3ff2009-01-07 17:43:34 -08001598 }
Luis R. Rodriguez9c964772008-10-30 13:33:53 -07001599 intersect = true;
Luis R. Rodriguez3e0c3ff2009-01-07 17:43:34 -08001600 } else if (r) {
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -05001601 /*
1602 * If the regulatory domain being requested by the
Luis R. Rodriguez3e0c3ff2009-01-07 17:43:34 -08001603 * driver has already been set just copy it to the
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -05001604 * wiphy
1605 */
Luis R. Rodriguez28da32d2009-02-21 00:24:14 -05001606 if (r == -EALREADY &&
Luis R. Rodriguez7db90f42009-03-09 22:07:41 -04001607 pending_request->initiator ==
1608 NL80211_REGDOM_SET_BY_DRIVER) {
Luis R. Rodriguez3e0c3ff2009-01-07 17:43:34 -08001609 r = reg_copy_regd(&wiphy->regd, cfg80211_regdomain);
Luis R. Rodriguezd951c1d2009-02-21 00:24:15 -05001610 if (r) {
1611 kfree(pending_request);
Luis R. Rodriguez3e0c3ff2009-01-07 17:43:34 -08001612 return r;
Luis R. Rodriguezd951c1d2009-02-21 00:24:15 -05001613 }
Luis R. Rodriguez3e0c3ff2009-01-07 17:43:34 -08001614 r = -EALREADY;
1615 goto new_request;
1616 }
Luis R. Rodriguezd951c1d2009-02-21 00:24:15 -05001617 kfree(pending_request);
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07001618 return r;
Luis R. Rodriguez3e0c3ff2009-01-07 17:43:34 -08001619 }
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07001620
Luis R. Rodriguez3e0c3ff2009-01-07 17:43:34 -08001621new_request:
Luis R. Rodriguez5203cdb2008-11-12 14:21:56 -08001622 kfree(last_request);
Luis R. Rodriguezd951c1d2009-02-21 00:24:15 -05001623
1624 last_request = pending_request;
1625 last_request->intersect = intersect;
1626
1627 pending_request = NULL;
Luis R. Rodriguez3e0c3ff2009-01-07 17:43:34 -08001628
1629 /* When r == REG_INTERSECT we do need to call CRDA */
Luis R. Rodriguez73d54c92009-03-09 22:07:42 -04001630 if (r < 0) {
1631 /*
1632 * Since CRDA will not be called in this case as we already
1633 * have applied the requested regulatory domain before we just
1634 * inform userspace we have processed the request
1635 */
1636 if (r == -EALREADY)
1637 nl80211_send_reg_change_event(last_request);
Luis R. Rodriguez3e0c3ff2009-01-07 17:43:34 -08001638 return r;
Luis R. Rodriguez73d54c92009-03-09 22:07:42 -04001639 }
Luis R. Rodriguez3e0c3ff2009-01-07 17:43:34 -08001640
Luis R. Rodriguezd951c1d2009-02-21 00:24:15 -05001641 return call_crda(last_request->alpha2);
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07001642}
1643
Luis R. Rodriguez30a548c2009-05-02 01:17:27 -04001644/* This processes *all* regulatory hints */
Luis R. Rodriguezd951c1d2009-02-21 00:24:15 -05001645static void reg_process_hint(struct regulatory_request *reg_request)
Luis R. Rodriguezfe33eb32009-02-21 00:04:30 -05001646{
1647 int r = 0;
1648 struct wiphy *wiphy = NULL;
1649
1650 BUG_ON(!reg_request->alpha2);
1651
1652 mutex_lock(&cfg80211_mutex);
Luis R. Rodriguezabc73812009-07-30 17:38:08 -07001653 mutex_lock(&reg_mutex);
Luis R. Rodriguezfe33eb32009-02-21 00:04:30 -05001654
1655 if (wiphy_idx_valid(reg_request->wiphy_idx))
1656 wiphy = wiphy_idx_to_wiphy(reg_request->wiphy_idx);
1657
Luis R. Rodriguez7db90f42009-03-09 22:07:41 -04001658 if (reg_request->initiator == NL80211_REGDOM_SET_BY_DRIVER &&
Luis R. Rodriguezfe33eb32009-02-21 00:04:30 -05001659 !wiphy) {
Luis R. Rodriguezd951c1d2009-02-21 00:24:15 -05001660 kfree(reg_request);
Luis R. Rodriguezfe33eb32009-02-21 00:04:30 -05001661 goto out;
1662 }
1663
Luis R. Rodriguez28da32d2009-02-21 00:24:14 -05001664 r = __regulatory_hint(wiphy, reg_request);
Luis R. Rodriguezfe33eb32009-02-21 00:04:30 -05001665 /* This is required so that the orig_* parameters are saved */
Johannes Berg5be83de2009-11-19 00:56:28 +01001666 if (r == -EALREADY && wiphy &&
1667 wiphy->flags & WIPHY_FLAG_STRICT_REGULATORY)
Luis R. Rodriguezfe33eb32009-02-21 00:04:30 -05001668 wiphy_update_regulatory(wiphy, reg_request->initiator);
1669out:
Luis R. Rodriguezabc73812009-07-30 17:38:08 -07001670 mutex_unlock(&reg_mutex);
Luis R. Rodriguezfe33eb32009-02-21 00:04:30 -05001671 mutex_unlock(&cfg80211_mutex);
Luis R. Rodriguezfe33eb32009-02-21 00:04:30 -05001672}
1673
Luis R. Rodriguez7db90f42009-03-09 22:07:41 -04001674/* Processes regulatory hints, this is all the NL80211_REGDOM_SET_BY_* */
Luis R. Rodriguezfe33eb32009-02-21 00:04:30 -05001675static void reg_process_pending_hints(void)
1676 {
1677 struct regulatory_request *reg_request;
Luis R. Rodriguezfe33eb32009-02-21 00:04:30 -05001678
1679 spin_lock(&reg_requests_lock);
1680 while (!list_empty(&reg_requests_list)) {
1681 reg_request = list_first_entry(&reg_requests_list,
1682 struct regulatory_request,
1683 list);
1684 list_del_init(&reg_request->list);
Luis R. Rodriguezfe33eb32009-02-21 00:04:30 -05001685
Luis R. Rodriguezd951c1d2009-02-21 00:24:15 -05001686 spin_unlock(&reg_requests_lock);
1687 reg_process_hint(reg_request);
Luis R. Rodriguezfe33eb32009-02-21 00:04:30 -05001688 spin_lock(&reg_requests_lock);
1689 }
1690 spin_unlock(&reg_requests_lock);
1691}
1692
Luis R. Rodrigueze38f8a72009-02-21 00:20:39 -05001693/* Processes beacon hints -- this has nothing to do with country IEs */
1694static void reg_process_pending_beacon_hints(void)
1695{
Johannes Berg79c97e92009-07-07 03:56:12 +02001696 struct cfg80211_registered_device *rdev;
Luis R. Rodrigueze38f8a72009-02-21 00:20:39 -05001697 struct reg_beacon *pending_beacon, *tmp;
1698
Luis R. Rodriguezabc73812009-07-30 17:38:08 -07001699 /*
1700 * No need to hold the reg_mutex here as we just touch wiphys
1701 * and do not read or access regulatory variables.
1702 */
Luis R. Rodrigueze38f8a72009-02-21 00:20:39 -05001703 mutex_lock(&cfg80211_mutex);
1704
1705 /* This goes through the _pending_ beacon list */
1706 spin_lock_bh(&reg_pending_beacons_lock);
1707
1708 if (list_empty(&reg_pending_beacons)) {
1709 spin_unlock_bh(&reg_pending_beacons_lock);
1710 goto out;
1711 }
1712
1713 list_for_each_entry_safe(pending_beacon, tmp,
1714 &reg_pending_beacons, list) {
1715
1716 list_del_init(&pending_beacon->list);
1717
1718 /* Applies the beacon hint to current wiphys */
Johannes Berg79c97e92009-07-07 03:56:12 +02001719 list_for_each_entry(rdev, &cfg80211_rdev_list, list)
1720 wiphy_update_new_beacon(&rdev->wiphy, pending_beacon);
Luis R. Rodrigueze38f8a72009-02-21 00:20:39 -05001721
1722 /* Remembers the beacon hint for new wiphys or reg changes */
1723 list_add_tail(&pending_beacon->list, &reg_beacon_list);
1724 }
1725
1726 spin_unlock_bh(&reg_pending_beacons_lock);
1727out:
1728 mutex_unlock(&cfg80211_mutex);
1729}
1730
Luis R. Rodriguezfe33eb32009-02-21 00:04:30 -05001731static void reg_todo(struct work_struct *work)
1732{
1733 reg_process_pending_hints();
Luis R. Rodrigueze38f8a72009-02-21 00:20:39 -05001734 reg_process_pending_beacon_hints();
Luis R. Rodriguezfe33eb32009-02-21 00:04:30 -05001735}
1736
1737static DECLARE_WORK(reg_work, reg_todo);
1738
1739static void queue_regulatory_request(struct regulatory_request *request)
1740{
1741 spin_lock(&reg_requests_lock);
1742 list_add_tail(&request->list, &reg_requests_list);
1743 spin_unlock(&reg_requests_lock);
1744
1745 schedule_work(&reg_work);
1746}
1747
1748/* Core regulatory hint -- happens once during cfg80211_init() */
Luis R. Rodriguezba25c142009-02-21 00:04:23 -05001749static int regulatory_hint_core(const char *alpha2)
1750{
1751 struct regulatory_request *request;
1752
1753 BUG_ON(last_request);
1754
1755 request = kzalloc(sizeof(struct regulatory_request),
1756 GFP_KERNEL);
1757 if (!request)
1758 return -ENOMEM;
1759
1760 request->alpha2[0] = alpha2[0];
1761 request->alpha2[1] = alpha2[1];
Luis R. Rodriguez7db90f42009-03-09 22:07:41 -04001762 request->initiator = NL80211_REGDOM_SET_BY_CORE;
Luis R. Rodriguezba25c142009-02-21 00:04:23 -05001763
Luis R. Rodriguezfe33eb32009-02-21 00:04:30 -05001764 queue_regulatory_request(request);
Luis R. Rodriguezba25c142009-02-21 00:04:23 -05001765
Luis R. Rodriguez5078b2e2009-05-13 17:04:42 -04001766 /*
1767 * This ensures last_request is populated once modules
1768 * come swinging in and calling regulatory hints and
1769 * wiphy_apply_custom_regulatory().
1770 */
1771 flush_scheduled_work();
1772
Luis R. Rodriguezfe33eb32009-02-21 00:04:30 -05001773 return 0;
Luis R. Rodriguezba25c142009-02-21 00:04:23 -05001774}
1775
Luis R. Rodriguezfe33eb32009-02-21 00:04:30 -05001776/* User hints */
1777int regulatory_hint_user(const char *alpha2)
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07001778{
Luis R. Rodriguezfe33eb32009-02-21 00:04:30 -05001779 struct regulatory_request *request;
1780
Johannes Bergbe3d4812008-10-24 20:32:21 +02001781 BUG_ON(!alpha2);
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07001782
Luis R. Rodriguezfe33eb32009-02-21 00:04:30 -05001783 request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL);
1784 if (!request)
1785 return -ENOMEM;
1786
1787 request->wiphy_idx = WIPHY_IDX_STALE;
1788 request->alpha2[0] = alpha2[0];
1789 request->alpha2[1] = alpha2[1];
Luis R. Rodriguez7db90f42009-03-09 22:07:41 -04001790 request->initiator = NL80211_REGDOM_SET_BY_USER,
Luis R. Rodriguezfe33eb32009-02-21 00:04:30 -05001791
1792 queue_regulatory_request(request);
1793
1794 return 0;
1795}
1796
1797/* Driver hints */
1798int regulatory_hint(struct wiphy *wiphy, const char *alpha2)
1799{
1800 struct regulatory_request *request;
1801
1802 BUG_ON(!alpha2);
1803 BUG_ON(!wiphy);
1804
1805 request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL);
1806 if (!request)
1807 return -ENOMEM;
1808
1809 request->wiphy_idx = get_wiphy_idx(wiphy);
1810
1811 /* Must have registered wiphy first */
1812 BUG_ON(!wiphy_idx_valid(request->wiphy_idx));
1813
1814 request->alpha2[0] = alpha2[0];
1815 request->alpha2[1] = alpha2[1];
Luis R. Rodriguez7db90f42009-03-09 22:07:41 -04001816 request->initiator = NL80211_REGDOM_SET_BY_DRIVER;
Luis R. Rodriguezfe33eb32009-02-21 00:04:30 -05001817
1818 queue_regulatory_request(request);
1819
1820 return 0;
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07001821}
1822EXPORT_SYMBOL(regulatory_hint);
1823
Luis R. Rodriguezabc73812009-07-30 17:38:08 -07001824/* Caller must hold reg_mutex */
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08001825static bool reg_same_country_ie_hint(struct wiphy *wiphy,
1826 u32 country_ie_checksum)
1827{
Luis R. Rodriguez806a9e32009-02-21 00:04:26 -05001828 struct wiphy *request_wiphy;
1829
Luis R. Rodriguezabc73812009-07-30 17:38:08 -07001830 assert_reg_lock();
Luis R. Rodriguez761cf7e2009-02-21 00:04:25 -05001831
Luis R. Rodriguezcc0b6fe2009-03-20 23:53:05 -04001832 if (unlikely(last_request->initiator !=
1833 NL80211_REGDOM_SET_BY_COUNTRY_IE))
1834 return false;
1835
Luis R. Rodriguez806a9e32009-02-21 00:04:26 -05001836 request_wiphy = wiphy_idx_to_wiphy(last_request->wiphy_idx);
1837
1838 if (!request_wiphy)
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08001839 return false;
Luis R. Rodriguez806a9e32009-02-21 00:04:26 -05001840
1841 if (likely(request_wiphy != wiphy))
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08001842 return !country_ie_integrity_changes(country_ie_checksum);
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -05001843 /*
1844 * We should not have let these through at this point, they
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08001845 * should have been picked up earlier by the first alpha2 check
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -05001846 * on the device
1847 */
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08001848 if (WARN_ON(!country_ie_integrity_changes(country_ie_checksum)))
1849 return true;
1850 return false;
1851}
1852
Luis R. Rodriguez4b44c8b2009-07-30 17:38:07 -07001853/*
1854 * We hold wdev_lock() here so we cannot hold cfg80211_mutex() and
1855 * therefore cannot iterate over the rdev list here.
1856 */
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08001857void regulatory_hint_11d(struct wiphy *wiphy,
1858 u8 *country_ie,
1859 u8 country_ie_len)
1860{
1861 struct ieee80211_regdomain *rd = NULL;
1862 char alpha2[2];
1863 u32 checksum = 0;
1864 enum environment_cap env = ENVIRON_ANY;
Luis R. Rodriguezfe33eb32009-02-21 00:04:30 -05001865 struct regulatory_request *request;
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08001866
Luis R. Rodriguezabc73812009-07-30 17:38:08 -07001867 mutex_lock(&reg_mutex);
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08001868
Luis R. Rodriguez9828b012009-07-30 17:38:06 -07001869 if (unlikely(!last_request))
1870 goto out;
Luis R. Rodriguezd335fe62009-02-21 00:04:27 -05001871
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08001872 /* IE len must be evenly divisible by 2 */
1873 if (country_ie_len & 0x01)
1874 goto out;
1875
1876 if (country_ie_len < IEEE80211_COUNTRY_IE_MIN_LEN)
1877 goto out;
1878
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -05001879 /*
1880 * Pending country IE processing, this can happen after we
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08001881 * call CRDA and wait for a response if a beacon was received before
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -05001882 * we were able to process the last regulatory_hint_11d() call
1883 */
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08001884 if (country_ie_regdomain)
1885 goto out;
1886
1887 alpha2[0] = country_ie[0];
1888 alpha2[1] = country_ie[1];
1889
1890 if (country_ie[2] == 'I')
1891 env = ENVIRON_INDOOR;
1892 else if (country_ie[2] == 'O')
1893 env = ENVIRON_OUTDOOR;
1894
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -05001895 /*
Luis R. Rodriguez8b19e6c2009-07-30 17:38:09 -07001896 * We will run this only upon a successful connection on cfg80211.
Luis R. Rodriguez4b44c8b2009-07-30 17:38:07 -07001897 * We leave conflict resolution to the workqueue, where can hold
1898 * cfg80211_mutex.
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -05001899 */
Luis R. Rodriguezcc0b6fe2009-03-20 23:53:05 -04001900 if (likely(last_request->initiator ==
1901 NL80211_REGDOM_SET_BY_COUNTRY_IE &&
Luis R. Rodriguez4b44c8b2009-07-30 17:38:07 -07001902 wiphy_idx_valid(last_request->wiphy_idx)))
1903 goto out;
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08001904
1905 rd = country_ie_2_rd(country_ie, country_ie_len, &checksum);
1906 if (!rd)
1907 goto out;
1908
Luis R. Rodriguez915278e2009-02-21 00:04:28 -05001909 /*
1910 * This will not happen right now but we leave it here for the
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08001911 * the future when we want to add suspend/resume support and having
1912 * the user move to another country after doing so, or having the user
Luis R. Rodriguez915278e2009-02-21 00:04:28 -05001913 * move to another AP. Right now we just trust the first AP.
1914 *
1915 * If we hit this before we add this support we want to be informed of
1916 * it as it would indicate a mistake in the current design
1917 */
1918 if (WARN_ON(reg_same_country_ie_hint(wiphy, checksum)))
Luis R. Rodriguez0441d6f2009-02-21 00:04:29 -05001919 goto free_rd_out;
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08001920
Luis R. Rodriguezfe33eb32009-02-21 00:04:30 -05001921 request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL);
1922 if (!request)
1923 goto free_rd_out;
1924
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -05001925 /*
1926 * We keep this around for when CRDA comes back with a response so
1927 * we can intersect with that
1928 */
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08001929 country_ie_regdomain = rd;
1930
Luis R. Rodriguezfe33eb32009-02-21 00:04:30 -05001931 request->wiphy_idx = get_wiphy_idx(wiphy);
1932 request->alpha2[0] = rd->alpha2[0];
1933 request->alpha2[1] = rd->alpha2[1];
Luis R. Rodriguez7db90f42009-03-09 22:07:41 -04001934 request->initiator = NL80211_REGDOM_SET_BY_COUNTRY_IE;
Luis R. Rodriguezfe33eb32009-02-21 00:04:30 -05001935 request->country_ie_checksum = checksum;
1936 request->country_ie_env = env;
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08001937
Luis R. Rodriguezabc73812009-07-30 17:38:08 -07001938 mutex_unlock(&reg_mutex);
Luis R. Rodriguezfe33eb32009-02-21 00:04:30 -05001939
1940 queue_regulatory_request(request);
1941
1942 return;
Luis R. Rodriguez0441d6f2009-02-21 00:04:29 -05001943
1944free_rd_out:
1945 kfree(rd);
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08001946out:
Luis R. Rodriguezabc73812009-07-30 17:38:08 -07001947 mutex_unlock(&reg_mutex);
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08001948}
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07001949
Luis R. Rodrigueze38f8a72009-02-21 00:20:39 -05001950static bool freq_is_chan_12_13_14(u16 freq)
1951{
1952 if (freq == ieee80211_channel_to_frequency(12) ||
1953 freq == ieee80211_channel_to_frequency(13) ||
1954 freq == ieee80211_channel_to_frequency(14))
1955 return true;
1956 return false;
1957}
1958
1959int regulatory_hint_found_beacon(struct wiphy *wiphy,
1960 struct ieee80211_channel *beacon_chan,
1961 gfp_t gfp)
1962{
1963 struct reg_beacon *reg_beacon;
1964
1965 if (likely((beacon_chan->beacon_found ||
1966 (beacon_chan->flags & IEEE80211_CHAN_RADAR) ||
1967 (beacon_chan->band == IEEE80211_BAND_2GHZ &&
1968 !freq_is_chan_12_13_14(beacon_chan->center_freq)))))
1969 return 0;
1970
1971 reg_beacon = kzalloc(sizeof(struct reg_beacon), gfp);
1972 if (!reg_beacon)
1973 return -ENOMEM;
1974
1975#ifdef CONFIG_CFG80211_REG_DEBUG
1976 printk(KERN_DEBUG "cfg80211: Found new beacon on "
1977 "frequency: %d MHz (Ch %d) on %s\n",
1978 beacon_chan->center_freq,
1979 ieee80211_frequency_to_channel(beacon_chan->center_freq),
1980 wiphy_name(wiphy));
1981#endif
1982 memcpy(&reg_beacon->chan, beacon_chan,
1983 sizeof(struct ieee80211_channel));
1984
1985
1986 /*
1987 * Since we can be called from BH or and non-BH context
1988 * we must use spin_lock_bh()
1989 */
1990 spin_lock_bh(&reg_pending_beacons_lock);
1991 list_add_tail(&reg_beacon->list, &reg_pending_beacons);
1992 spin_unlock_bh(&reg_pending_beacons_lock);
1993
1994 schedule_work(&reg_work);
1995
1996 return 0;
1997}
1998
Johannes Berga3d2eaf2008-09-15 11:10:52 +02001999static void print_rd_rules(const struct ieee80211_regdomain *rd)
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07002000{
2001 unsigned int i;
Johannes Berga3d2eaf2008-09-15 11:10:52 +02002002 const struct ieee80211_reg_rule *reg_rule = NULL;
2003 const struct ieee80211_freq_range *freq_range = NULL;
2004 const struct ieee80211_power_rule *power_rule = NULL;
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07002005
Kalle Valo269ac5f2009-12-01 10:47:15 +02002006 printk(KERN_INFO " (start_freq - end_freq @ bandwidth), "
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07002007 "(max_antenna_gain, max_eirp)\n");
2008
2009 for (i = 0; i < rd->n_reg_rules; i++) {
2010 reg_rule = &rd->reg_rules[i];
2011 freq_range = &reg_rule->freq_range;
2012 power_rule = &reg_rule->power_rule;
2013
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -05002014 /*
2015 * There may not be documentation for max antenna gain
2016 * in certain regions
2017 */
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07002018 if (power_rule->max_antenna_gain)
Kalle Valo269ac5f2009-12-01 10:47:15 +02002019 printk(KERN_INFO " (%d KHz - %d KHz @ %d KHz), "
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07002020 "(%d mBi, %d mBm)\n",
2021 freq_range->start_freq_khz,
2022 freq_range->end_freq_khz,
2023 freq_range->max_bandwidth_khz,
2024 power_rule->max_antenna_gain,
2025 power_rule->max_eirp);
2026 else
Kalle Valo269ac5f2009-12-01 10:47:15 +02002027 printk(KERN_INFO " (%d KHz - %d KHz @ %d KHz), "
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07002028 "(N/A, %d mBm)\n",
2029 freq_range->start_freq_khz,
2030 freq_range->end_freq_khz,
2031 freq_range->max_bandwidth_khz,
2032 power_rule->max_eirp);
2033 }
2034}
2035
Johannes Berga3d2eaf2008-09-15 11:10:52 +02002036static void print_regdomain(const struct ieee80211_regdomain *rd)
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07002037{
2038
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08002039 if (is_intersected_alpha2(rd->alpha2)) {
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08002040
Luis R. Rodriguez7db90f42009-03-09 22:07:41 -04002041 if (last_request->initiator ==
2042 NL80211_REGDOM_SET_BY_COUNTRY_IE) {
Johannes Berg79c97e92009-07-07 03:56:12 +02002043 struct cfg80211_registered_device *rdev;
2044 rdev = cfg80211_rdev_by_wiphy_idx(
Luis R. Rodriguez806a9e32009-02-21 00:04:26 -05002045 last_request->wiphy_idx);
Johannes Berg79c97e92009-07-07 03:56:12 +02002046 if (rdev) {
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08002047 printk(KERN_INFO "cfg80211: Current regulatory "
2048 "domain updated by AP to: %c%c\n",
Johannes Berg79c97e92009-07-07 03:56:12 +02002049 rdev->country_ie_alpha2[0],
2050 rdev->country_ie_alpha2[1]);
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08002051 } else
2052 printk(KERN_INFO "cfg80211: Current regulatory "
2053 "domain intersected: \n");
2054 } else
2055 printk(KERN_INFO "cfg80211: Current regulatory "
Luis R. Rodriguez039498c2009-01-07 17:43:35 -08002056 "domain intersected: \n");
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08002057 } else if (is_world_regdom(rd->alpha2))
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07002058 printk(KERN_INFO "cfg80211: World regulatory "
2059 "domain updated:\n");
2060 else {
2061 if (is_unknown_alpha2(rd->alpha2))
2062 printk(KERN_INFO "cfg80211: Regulatory domain "
2063 "changed to driver built-in settings "
2064 "(unknown country)\n");
2065 else
2066 printk(KERN_INFO "cfg80211: Regulatory domain "
2067 "changed to country: %c%c\n",
2068 rd->alpha2[0], rd->alpha2[1]);
2069 }
2070 print_rd_rules(rd);
2071}
2072
Johannes Berg2df78162008-10-28 16:49:41 +01002073static void print_regdomain_info(const struct ieee80211_regdomain *rd)
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07002074{
2075 printk(KERN_INFO "cfg80211: Regulatory domain: %c%c\n",
2076 rd->alpha2[0], rd->alpha2[1]);
2077 print_rd_rules(rd);
2078}
2079
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08002080#ifdef CONFIG_CFG80211_REG_DEBUG
2081static void reg_country_ie_process_debug(
2082 const struct ieee80211_regdomain *rd,
2083 const struct ieee80211_regdomain *country_ie_regdomain,
2084 const struct ieee80211_regdomain *intersected_rd)
2085{
2086 printk(KERN_DEBUG "cfg80211: Received country IE:\n");
2087 print_regdomain_info(country_ie_regdomain);
2088 printk(KERN_DEBUG "cfg80211: CRDA thinks this should applied:\n");
2089 print_regdomain_info(rd);
2090 if (intersected_rd) {
2091 printk(KERN_DEBUG "cfg80211: We intersect both of these "
2092 "and get:\n");
Luis R. Rodriguez667ecd02009-01-22 15:05:43 -08002093 print_regdomain_info(intersected_rd);
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08002094 return;
2095 }
2096 printk(KERN_DEBUG "cfg80211: Intersection between both failed\n");
2097}
2098#else
2099static inline void reg_country_ie_process_debug(
2100 const struct ieee80211_regdomain *rd,
2101 const struct ieee80211_regdomain *country_ie_regdomain,
2102 const struct ieee80211_regdomain *intersected_rd)
2103{
2104}
2105#endif
2106
Johannes Bergd2372b32008-10-24 20:32:20 +02002107/* Takes ownership of rd only if it doesn't fail */
Johannes Berga3d2eaf2008-09-15 11:10:52 +02002108static int __set_regdom(const struct ieee80211_regdomain *rd)
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07002109{
Luis R. Rodriguez9c964772008-10-30 13:33:53 -07002110 const struct ieee80211_regdomain *intersected_rd = NULL;
Johannes Berg79c97e92009-07-07 03:56:12 +02002111 struct cfg80211_registered_device *rdev = NULL;
Luis R. Rodriguez806a9e32009-02-21 00:04:26 -05002112 struct wiphy *request_wiphy;
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07002113 /* Some basic sanity checks first */
2114
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07002115 if (is_world_regdom(rd->alpha2)) {
Johannes Bergf6037d02008-10-21 11:01:33 +02002116 if (WARN_ON(!reg_is_valid_request(rd->alpha2)))
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07002117 return -EINVAL;
2118 update_world_regdomain(rd);
2119 return 0;
2120 }
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07002121
2122 if (!is_alpha2_set(rd->alpha2) && !is_an_alpha2(rd->alpha2) &&
2123 !is_unknown_alpha2(rd->alpha2))
2124 return -EINVAL;
2125
Johannes Bergf6037d02008-10-21 11:01:33 +02002126 if (!last_request)
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07002127 return -EINVAL;
2128
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -05002129 /*
2130 * Lets only bother proceeding on the same alpha2 if the current
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08002131 * rd is non static (it means CRDA was present and was used last)
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -05002132 * and the pending request came in from a country IE
2133 */
Luis R. Rodriguez7db90f42009-03-09 22:07:41 -04002134 if (last_request->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE) {
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -05002135 /*
2136 * If someone else asked us to change the rd lets only bother
2137 * checking if the alpha2 changes if CRDA was already called
2138 */
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08002139 if (!is_old_static_regdom(cfg80211_regdomain) &&
Luis R. Rodriguez69b15722009-02-21 00:04:33 -05002140 !regdom_changes(rd->alpha2))
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08002141 return -EINVAL;
2142 }
2143
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -05002144 /*
2145 * Now lets set the regulatory domain, update all driver channels
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07002146 * and finally inform them of what we have done, in case they want
2147 * to review or adjust their own settings based on their own
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -05002148 * internal EEPROM data
2149 */
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07002150
Johannes Bergf6037d02008-10-21 11:01:33 +02002151 if (WARN_ON(!reg_is_valid_request(rd->alpha2)))
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07002152 return -EINVAL;
2153
Luis R. Rodriguez8375af32008-11-12 14:21:57 -08002154 if (!is_valid_rd(rd)) {
2155 printk(KERN_ERR "cfg80211: Invalid "
2156 "regulatory domain detected:\n");
2157 print_regdomain_info(rd);
2158 return -EINVAL;
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07002159 }
2160
Luis R. Rodriguez806a9e32009-02-21 00:04:26 -05002161 request_wiphy = wiphy_idx_to_wiphy(last_request->wiphy_idx);
2162
Luis R. Rodriguezb8295ac2008-11-12 14:21:58 -08002163 if (!last_request->intersect) {
Luis R. Rodriguez3e0c3ff2009-01-07 17:43:34 -08002164 int r;
2165
Luis R. Rodriguez7db90f42009-03-09 22:07:41 -04002166 if (last_request->initiator != NL80211_REGDOM_SET_BY_DRIVER) {
Luis R. Rodriguez3e0c3ff2009-01-07 17:43:34 -08002167 reset_regdomains();
2168 cfg80211_regdomain = rd;
2169 return 0;
2170 }
2171
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -05002172 /*
2173 * For a driver hint, lets copy the regulatory domain the
2174 * driver wanted to the wiphy to deal with conflicts
2175 */
Luis R. Rodriguez3e0c3ff2009-01-07 17:43:34 -08002176
Luis R. Rodriguez558f6d32009-06-08 18:54:37 -07002177 /*
2178 * Userspace could have sent two replies with only
2179 * one kernel request.
2180 */
2181 if (request_wiphy->regd)
2182 return -EALREADY;
Luis R. Rodriguez3e0c3ff2009-01-07 17:43:34 -08002183
Luis R. Rodriguez806a9e32009-02-21 00:04:26 -05002184 r = reg_copy_regd(&request_wiphy->regd, rd);
Luis R. Rodriguez3e0c3ff2009-01-07 17:43:34 -08002185 if (r)
2186 return r;
2187
Luis R. Rodriguezb8295ac2008-11-12 14:21:58 -08002188 reset_regdomains();
2189 cfg80211_regdomain = rd;
2190 return 0;
2191 }
2192
2193 /* Intersection requires a bit more work */
2194
Luis R. Rodriguez7db90f42009-03-09 22:07:41 -04002195 if (last_request->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE) {
Luis R. Rodriguezb8295ac2008-11-12 14:21:58 -08002196
Luis R. Rodriguez9c964772008-10-30 13:33:53 -07002197 intersected_rd = regdom_intersect(rd, cfg80211_regdomain);
2198 if (!intersected_rd)
2199 return -EINVAL;
Luis R. Rodriguezb8295ac2008-11-12 14:21:58 -08002200
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -05002201 /*
2202 * We can trash what CRDA provided now.
Luis R. Rodriguez3e0c3ff2009-01-07 17:43:34 -08002203 * However if a driver requested this specific regulatory
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -05002204 * domain we keep it for its private use
2205 */
Luis R. Rodriguez7db90f42009-03-09 22:07:41 -04002206 if (last_request->initiator == NL80211_REGDOM_SET_BY_DRIVER)
Luis R. Rodriguez806a9e32009-02-21 00:04:26 -05002207 request_wiphy->regd = rd;
Luis R. Rodriguez3e0c3ff2009-01-07 17:43:34 -08002208 else
2209 kfree(rd);
2210
Luis R. Rodriguezb8295ac2008-11-12 14:21:58 -08002211 rd = NULL;
2212
2213 reset_regdomains();
2214 cfg80211_regdomain = intersected_rd;
2215
2216 return 0;
Luis R. Rodriguez9c964772008-10-30 13:33:53 -07002217 }
2218
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08002219 /*
2220 * Country IE requests are handled a bit differently, we intersect
2221 * the country IE rd with what CRDA believes that country should have
2222 */
2223
Luis R. Rodriguez729e9c72009-05-31 18:24:34 -04002224 /*
2225 * Userspace could have sent two replies with only
2226 * one kernel request. By the second reply we would have
2227 * already processed and consumed the country_ie_regdomain.
2228 */
2229 if (!country_ie_regdomain)
2230 return -EALREADY;
Luis R. Rodriguez86f04682009-03-20 23:53:07 -04002231 BUG_ON(rd == country_ie_regdomain);
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08002232
Luis R. Rodriguez86f04682009-03-20 23:53:07 -04002233 /*
2234 * Intersect what CRDA returned and our what we
2235 * had built from the Country IE received
2236 */
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08002237
Luis R. Rodriguez86f04682009-03-20 23:53:07 -04002238 intersected_rd = regdom_intersect(rd, country_ie_regdomain);
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08002239
Luis R. Rodriguez86f04682009-03-20 23:53:07 -04002240 reg_country_ie_process_debug(rd,
2241 country_ie_regdomain,
2242 intersected_rd);
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08002243
Luis R. Rodriguez86f04682009-03-20 23:53:07 -04002244 kfree(country_ie_regdomain);
2245 country_ie_regdomain = NULL;
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08002246
2247 if (!intersected_rd)
2248 return -EINVAL;
2249
Johannes Berg79c97e92009-07-07 03:56:12 +02002250 rdev = wiphy_to_dev(request_wiphy);
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08002251
Johannes Berg79c97e92009-07-07 03:56:12 +02002252 rdev->country_ie_alpha2[0] = rd->alpha2[0];
2253 rdev->country_ie_alpha2[1] = rd->alpha2[1];
2254 rdev->env = last_request->country_ie_env;
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08002255
2256 BUG_ON(intersected_rd == rd);
2257
2258 kfree(rd);
2259 rd = NULL;
2260
Luis R. Rodriguezb8295ac2008-11-12 14:21:58 -08002261 reset_regdomains();
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08002262 cfg80211_regdomain = intersected_rd;
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07002263
2264 return 0;
2265}
2266
2267
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -05002268/*
2269 * Use this call to set the current regulatory domain. Conflicts with
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07002270 * multiple drivers can be ironed out later. Caller must've already
Luis R. Rodriguezfb1fc7a2009-02-21 00:04:31 -05002271 * kmalloc'd the rd structure. Caller must hold cfg80211_mutex
2272 */
Johannes Berga3d2eaf2008-09-15 11:10:52 +02002273int set_regdom(const struct ieee80211_regdomain *rd)
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07002274{
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07002275 int r;
2276
Luis R. Rodriguez761cf7e2009-02-21 00:04:25 -05002277 assert_cfg80211_lock();
2278
Luis R. Rodriguezabc73812009-07-30 17:38:08 -07002279 mutex_lock(&reg_mutex);
2280
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07002281 /* Note that this doesn't update the wiphys, this is done below */
2282 r = __set_regdom(rd);
Johannes Bergd2372b32008-10-24 20:32:20 +02002283 if (r) {
2284 kfree(rd);
Luis R. Rodriguezabc73812009-07-30 17:38:08 -07002285 mutex_unlock(&reg_mutex);
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07002286 return r;
Johannes Bergd2372b32008-10-24 20:32:20 +02002287 }
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07002288
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07002289 /* This would make this whole thing pointless */
Luis R. Rodrigueza01ddaf2008-11-12 14:21:59 -08002290 if (!last_request->intersect)
2291 BUG_ON(rd != cfg80211_regdomain);
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07002292
2293 /* update all wiphys now with the new established regulatory domain */
Johannes Bergf6037d02008-10-21 11:01:33 +02002294 update_all_wiphy_regulatory(last_request->initiator);
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07002295
Luis R. Rodrigueza01ddaf2008-11-12 14:21:59 -08002296 print_regdomain(cfg80211_regdomain);
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07002297
Luis R. Rodriguez73d54c92009-03-09 22:07:42 -04002298 nl80211_send_reg_change_event(last_request);
2299
Luis R. Rodriguezabc73812009-07-30 17:38:08 -07002300 mutex_unlock(&reg_mutex);
2301
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07002302 return r;
2303}
2304
Luis R. Rodrigueza1794392009-02-21 00:04:21 -05002305/* Caller must hold cfg80211_mutex */
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08002306void reg_device_remove(struct wiphy *wiphy)
2307{
Luis R. Rodriguez0ad8aca2009-03-24 21:21:08 -04002308 struct wiphy *request_wiphy = NULL;
Luis R. Rodriguez806a9e32009-02-21 00:04:26 -05002309
Luis R. Rodriguez761cf7e2009-02-21 00:04:25 -05002310 assert_cfg80211_lock();
2311
Luis R. Rodriguezabc73812009-07-30 17:38:08 -07002312 mutex_lock(&reg_mutex);
2313
Chris Wright0ef9ccd2009-04-24 14:09:31 -07002314 kfree(wiphy->regd);
2315
Luis R. Rodriguez0ad8aca2009-03-24 21:21:08 -04002316 if (last_request)
2317 request_wiphy = wiphy_idx_to_wiphy(last_request->wiphy_idx);
Luis R. Rodriguez806a9e32009-02-21 00:04:26 -05002318
Chris Wright0ef9ccd2009-04-24 14:09:31 -07002319 if (!request_wiphy || request_wiphy != wiphy)
Luis R. Rodriguezabc73812009-07-30 17:38:08 -07002320 goto out;
Chris Wright0ef9ccd2009-04-24 14:09:31 -07002321
Luis R. Rodriguez806a9e32009-02-21 00:04:26 -05002322 last_request->wiphy_idx = WIPHY_IDX_STALE;
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08002323 last_request->country_ie_env = ENVIRON_ANY;
Luis R. Rodriguezabc73812009-07-30 17:38:08 -07002324out:
2325 mutex_unlock(&reg_mutex);
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08002326}
2327
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07002328int regulatory_init(void)
2329{
Luis R. Rodriguezbcf4f992009-02-21 00:04:24 -05002330 int err = 0;
Johannes Berg734366d2008-09-15 10:56:48 +02002331
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07002332 reg_pdev = platform_device_register_simple("regulatory", 0, NULL, 0);
2333 if (IS_ERR(reg_pdev))
2334 return PTR_ERR(reg_pdev);
Johannes Berg734366d2008-09-15 10:56:48 +02002335
Luis R. Rodriguezfe33eb32009-02-21 00:04:30 -05002336 spin_lock_init(&reg_requests_lock);
Luis R. Rodrigueze38f8a72009-02-21 00:20:39 -05002337 spin_lock_init(&reg_pending_beacons_lock);
Luis R. Rodriguezfe33eb32009-02-21 00:04:30 -05002338
Johannes Berg734366d2008-09-15 10:56:48 +02002339#ifdef CONFIG_WIRELESS_OLD_REGULATORY
Johannes Berga3d2eaf2008-09-15 11:10:52 +02002340 cfg80211_regdomain = static_regdom(ieee80211_regdom);
Johannes Berg734366d2008-09-15 10:56:48 +02002341
Johannes Berg942b25c2008-09-15 11:26:47 +02002342 printk(KERN_INFO "cfg80211: Using static regulatory domain info\n");
Johannes Berg734366d2008-09-15 10:56:48 +02002343 print_regdomain_info(cfg80211_regdomain);
Johannes Berg734366d2008-09-15 10:56:48 +02002344#else
Johannes Berga3d2eaf2008-09-15 11:10:52 +02002345 cfg80211_regdomain = cfg80211_world_regdom;
Johannes Berg734366d2008-09-15 10:56:48 +02002346
Johannes Berg734366d2008-09-15 10:56:48 +02002347#endif
Luis R. Rodriguezae9e4b02009-07-14 20:23:15 -04002348 /* We always try to get an update for the static regdomain */
2349 err = regulatory_hint_core(cfg80211_regdomain->alpha2);
Luis R. Rodriguezbcf4f992009-02-21 00:04:24 -05002350 if (err) {
2351 if (err == -ENOMEM)
2352 return err;
2353 /*
2354 * N.B. kobject_uevent_env() can fail mainly for when we're out
2355 * memory which is handled and propagated appropriately above
2356 * but it can also fail during a netlink_broadcast() or during
2357 * early boot for call_usermodehelper(). For now treat these
2358 * errors as non-fatal.
2359 */
2360 printk(KERN_ERR "cfg80211: kobject_uevent_env() was unable "
2361 "to call CRDA during init");
2362#ifdef CONFIG_CFG80211_REG_DEBUG
2363 /* We want to find out exactly why when debugging */
2364 WARN_ON(err);
2365#endif
2366 }
Johannes Berg734366d2008-09-15 10:56:48 +02002367
Luis R. Rodriguezae9e4b02009-07-14 20:23:15 -04002368 /*
2369 * Finally, if the user set the module parameter treat it
2370 * as a user hint.
2371 */
2372 if (!is_world_regdom(ieee80211_regdom))
2373 regulatory_hint_user(ieee80211_regdom);
2374
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07002375 return 0;
2376}
2377
2378void regulatory_exit(void)
2379{
Luis R. Rodriguezfe33eb32009-02-21 00:04:30 -05002380 struct regulatory_request *reg_request, *tmp;
Luis R. Rodrigueze38f8a72009-02-21 00:20:39 -05002381 struct reg_beacon *reg_beacon, *btmp;
Luis R. Rodriguezfe33eb32009-02-21 00:04:30 -05002382
2383 cancel_work_sync(&reg_work);
2384
Luis R. Rodrigueza1794392009-02-21 00:04:21 -05002385 mutex_lock(&cfg80211_mutex);
Luis R. Rodriguezabc73812009-07-30 17:38:08 -07002386 mutex_lock(&reg_mutex);
Johannes Berg734366d2008-09-15 10:56:48 +02002387
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07002388 reset_regdomains();
Johannes Berg734366d2008-09-15 10:56:48 +02002389
Luis R. Rodriguez3f2355c2008-11-12 14:22:02 -08002390 kfree(country_ie_regdomain);
2391 country_ie_regdomain = NULL;
2392
Johannes Bergf6037d02008-10-21 11:01:33 +02002393 kfree(last_request);
2394
Luis R. Rodriguezb2e1b302008-09-09 23:19:48 -07002395 platform_device_unregister(reg_pdev);
Johannes Berg734366d2008-09-15 10:56:48 +02002396
Luis R. Rodrigueze38f8a72009-02-21 00:20:39 -05002397 spin_lock_bh(&reg_pending_beacons_lock);
2398 if (!list_empty(&reg_pending_beacons)) {
2399 list_for_each_entry_safe(reg_beacon, btmp,
2400 &reg_pending_beacons, list) {
2401 list_del(&reg_beacon->list);
2402 kfree(reg_beacon);
2403 }
2404 }
2405 spin_unlock_bh(&reg_pending_beacons_lock);
2406
2407 if (!list_empty(&reg_beacon_list)) {
2408 list_for_each_entry_safe(reg_beacon, btmp,
2409 &reg_beacon_list, list) {
2410 list_del(&reg_beacon->list);
2411 kfree(reg_beacon);
2412 }
2413 }
2414
Luis R. Rodriguezfe33eb32009-02-21 00:04:30 -05002415 spin_lock(&reg_requests_lock);
2416 if (!list_empty(&reg_requests_list)) {
2417 list_for_each_entry_safe(reg_request, tmp,
2418 &reg_requests_list, list) {
2419 list_del(&reg_request->list);
2420 kfree(reg_request);
2421 }
2422 }
2423 spin_unlock(&reg_requests_lock);
2424
Luis R. Rodriguezabc73812009-07-30 17:38:08 -07002425 mutex_unlock(&reg_mutex);
Luis R. Rodrigueza1794392009-02-21 00:04:21 -05002426 mutex_unlock(&cfg80211_mutex);
Johannes Berg8318d782008-01-24 19:38:38 +01002427}