18318d78aSJohannes Berg /* 28318d78aSJohannes Berg * Copyright 2002-2005, Instant802 Networks, Inc. 38318d78aSJohannes Berg * Copyright 2005-2006, Devicescape Software, Inc. 48318d78aSJohannes Berg * Copyright 2007 Johannes Berg <johannes@sipsolutions.net> 5b2e1b302SLuis R. Rodriguez * Copyright 2008 Luis R. Rodriguez <lrodriguz@atheros.com> 68318d78aSJohannes Berg * 78318d78aSJohannes Berg * This program is free software; you can redistribute it and/or modify 88318d78aSJohannes Berg * it under the terms of the GNU General Public License version 2 as 98318d78aSJohannes Berg * published by the Free Software Foundation. 108318d78aSJohannes Berg */ 118318d78aSJohannes Berg 12b2e1b302SLuis R. Rodriguez /** 13b2e1b302SLuis R. Rodriguez * DOC: Wireless regulatory infrastructure 148318d78aSJohannes Berg * 158318d78aSJohannes Berg * The usual implementation is for a driver to read a device EEPROM to 168318d78aSJohannes Berg * determine which regulatory domain it should be operating under, then 178318d78aSJohannes Berg * looking up the allowable channels in a driver-local table and finally 188318d78aSJohannes Berg * registering those channels in the wiphy structure. 198318d78aSJohannes Berg * 20b2e1b302SLuis R. Rodriguez * Another set of compliance enforcement is for drivers to use their 21b2e1b302SLuis R. Rodriguez * own compliance limits which can be stored on the EEPROM. The host 22b2e1b302SLuis R. Rodriguez * driver or firmware may ensure these are used. 23b2e1b302SLuis R. Rodriguez * 24b2e1b302SLuis R. Rodriguez * In addition to all this we provide an extra layer of regulatory 25b2e1b302SLuis R. Rodriguez * conformance. For drivers which do not have any regulatory 26b2e1b302SLuis R. Rodriguez * information CRDA provides the complete regulatory solution. 27b2e1b302SLuis R. Rodriguez * For others it provides a community effort on further restrictions 28b2e1b302SLuis R. Rodriguez * to enhance compliance. 29b2e1b302SLuis R. Rodriguez * 30b2e1b302SLuis R. Rodriguez * Note: When number of rules --> infinity we will not be able to 31b2e1b302SLuis R. Rodriguez * index on alpha2 any more, instead we'll probably have to 32b2e1b302SLuis R. Rodriguez * rely on some SHA1 checksum of the regdomain for example. 33b2e1b302SLuis R. Rodriguez * 348318d78aSJohannes Berg */ 35e9c0268fSJoe Perches 36e9c0268fSJoe Perches #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 37e9c0268fSJoe Perches 388318d78aSJohannes Berg #include <linux/kernel.h> 395a0e3ad6STejun Heo #include <linux/slab.h> 40b2e1b302SLuis R. Rodriguez #include <linux/list.h> 41b2e1b302SLuis R. Rodriguez #include <linux/random.h> 42c61029c7SJohn W. Linville #include <linux/ctype.h> 43b2e1b302SLuis R. Rodriguez #include <linux/nl80211.h> 44b2e1b302SLuis R. Rodriguez #include <linux/platform_device.h> 45b2e1b302SLuis R. Rodriguez #include <net/cfg80211.h> 468318d78aSJohannes Berg #include "core.h" 47b2e1b302SLuis R. Rodriguez #include "reg.h" 483b377ea9SJohn W. Linville #include "regdb.h" 4973d54c9eSLuis R. Rodriguez #include "nl80211.h" 508318d78aSJohannes Berg 514113f751SLuis R. Rodriguez #ifdef CONFIG_CFG80211_REG_DEBUG 528271195eSJohn W. Linville #define REG_DBG_PRINT(format, args...) \ 534113f751SLuis R. Rodriguez do { \ 54e9c0268fSJoe Perches printk(KERN_DEBUG pr_fmt(format), ##args); \ 554113f751SLuis R. Rodriguez } while (0) 564113f751SLuis R. Rodriguez #else 578271195eSJohn W. Linville #define REG_DBG_PRINT(args...) 584113f751SLuis R. Rodriguez #endif 594113f751SLuis R. Rodriguez 605166ccd2SLuis R. Rodriguez /* Receipt of information from last regulatory request */ 61f6037d09SJohannes Berg static struct regulatory_request *last_request; 62734366deSJohannes Berg 63b2e1b302SLuis R. Rodriguez /* To trigger userspace events */ 64b2e1b302SLuis R. Rodriguez static struct platform_device *reg_pdev; 658318d78aSJohannes Berg 66fb1fc7adSLuis R. Rodriguez /* 67fb1fc7adSLuis R. Rodriguez * Central wireless core regulatory domains, we only need two, 68734366deSJohannes Berg * the current one and a world regulatory domain in case we have no 69fb1fc7adSLuis R. Rodriguez * information to give us an alpha2 70fb1fc7adSLuis R. Rodriguez */ 71f130347cSLuis R. Rodriguez const struct ieee80211_regdomain *cfg80211_regdomain; 72734366deSJohannes Berg 73fb1fc7adSLuis R. Rodriguez /* 74abc7381bSLuis R. Rodriguez * Protects static reg.c components: 75abc7381bSLuis R. Rodriguez * - cfg80211_world_regdom 76abc7381bSLuis R. Rodriguez * - cfg80211_regdom 77abc7381bSLuis R. Rodriguez * - last_request 78abc7381bSLuis R. Rodriguez */ 79670b7f11SJohn W. Linville static DEFINE_MUTEX(reg_mutex); 8046a5ebafSJohannes Berg 8146a5ebafSJohannes Berg static inline void assert_reg_lock(void) 8246a5ebafSJohannes Berg { 8346a5ebafSJohannes Berg lockdep_assert_held(®_mutex); 8446a5ebafSJohannes Berg } 85abc7381bSLuis R. Rodriguez 86e38f8a7aSLuis R. Rodriguez /* Used to queue up regulatory hints */ 87fe33eb39SLuis R. Rodriguez static LIST_HEAD(reg_requests_list); 88fe33eb39SLuis R. Rodriguez static spinlock_t reg_requests_lock; 89fe33eb39SLuis R. Rodriguez 90e38f8a7aSLuis R. Rodriguez /* Used to queue up beacon hints for review */ 91e38f8a7aSLuis R. Rodriguez static LIST_HEAD(reg_pending_beacons); 92e38f8a7aSLuis R. Rodriguez static spinlock_t reg_pending_beacons_lock; 93e38f8a7aSLuis R. Rodriguez 94e38f8a7aSLuis R. Rodriguez /* Used to keep track of processed beacon hints */ 95e38f8a7aSLuis R. Rodriguez static LIST_HEAD(reg_beacon_list); 96e38f8a7aSLuis R. Rodriguez 97e38f8a7aSLuis R. Rodriguez struct reg_beacon { 98e38f8a7aSLuis R. Rodriguez struct list_head list; 99e38f8a7aSLuis R. Rodriguez struct ieee80211_channel chan; 100e38f8a7aSLuis R. Rodriguez }; 101e38f8a7aSLuis R. Rodriguez 102f333a7a2SLuis R. Rodriguez static void reg_todo(struct work_struct *work); 103f333a7a2SLuis R. Rodriguez static DECLARE_WORK(reg_work, reg_todo); 104f333a7a2SLuis R. Rodriguez 105734366deSJohannes Berg /* We keep a static world regulatory domain in case of the absence of CRDA */ 106734366deSJohannes Berg static const struct ieee80211_regdomain world_regdom = { 107611b6a82SLuis R. Rodriguez .n_reg_rules = 5, 108734366deSJohannes Berg .alpha2 = "00", 109734366deSJohannes Berg .reg_rules = { 11068798a62SLuis R. Rodriguez /* IEEE 802.11b/g, channels 1..11 */ 11168798a62SLuis R. Rodriguez REG_RULE(2412-10, 2462+10, 40, 6, 20, 0), 112611b6a82SLuis R. Rodriguez /* IEEE 802.11b/g, channels 12..13. No HT40 113611b6a82SLuis R. Rodriguez * channel fits here. */ 114611b6a82SLuis R. Rodriguez REG_RULE(2467-10, 2472+10, 20, 6, 20, 115611b6a82SLuis R. Rodriguez NL80211_RRF_PASSIVE_SCAN | 116611b6a82SLuis R. Rodriguez NL80211_RRF_NO_IBSS), 117611b6a82SLuis R. Rodriguez /* IEEE 802.11 channel 14 - Only JP enables 118611b6a82SLuis R. Rodriguez * this and for 802.11b only */ 119611b6a82SLuis R. Rodriguez REG_RULE(2484-10, 2484+10, 20, 6, 20, 120611b6a82SLuis R. Rodriguez NL80211_RRF_PASSIVE_SCAN | 121611b6a82SLuis R. Rodriguez NL80211_RRF_NO_IBSS | 122611b6a82SLuis R. Rodriguez NL80211_RRF_NO_OFDM), 1233fc71f77SLuis R. Rodriguez /* IEEE 802.11a, channel 36..48 */ 124ec329aceSLuis R. Rodriguez REG_RULE(5180-10, 5240+10, 40, 6, 20, 1253fc71f77SLuis R. Rodriguez NL80211_RRF_PASSIVE_SCAN | 1263fc71f77SLuis R. Rodriguez NL80211_RRF_NO_IBSS), 1273fc71f77SLuis R. Rodriguez 1283fc71f77SLuis R. Rodriguez /* NB: 5260 MHz - 5700 MHz requies DFS */ 1293fc71f77SLuis R. Rodriguez 1303fc71f77SLuis R. Rodriguez /* IEEE 802.11a, channel 149..165 */ 131ec329aceSLuis R. Rodriguez REG_RULE(5745-10, 5825+10, 40, 6, 20, 1323fc71f77SLuis R. Rodriguez NL80211_RRF_PASSIVE_SCAN | 1333fc71f77SLuis R. Rodriguez NL80211_RRF_NO_IBSS), 134734366deSJohannes Berg } 135734366deSJohannes Berg }; 136734366deSJohannes Berg 137a3d2eaf0SJohannes Berg static const struct ieee80211_regdomain *cfg80211_world_regdom = 138a3d2eaf0SJohannes Berg &world_regdom; 139734366deSJohannes Berg 1406ee7d330SLuis R. Rodriguez static char *ieee80211_regdom = "00"; 14109d989d1SLuis R. Rodriguez static char user_alpha2[2]; 1426ee7d330SLuis R. Rodriguez 143734366deSJohannes Berg module_param(ieee80211_regdom, charp, 0444); 144734366deSJohannes Berg MODULE_PARM_DESC(ieee80211_regdom, "IEEE 802.11 regulatory domain code"); 145734366deSJohannes Berg 146734366deSJohannes Berg static void reset_regdomains(void) 147734366deSJohannes Berg { 148942b25cfSJohannes Berg /* avoid freeing static information or freeing something twice */ 149942b25cfSJohannes Berg if (cfg80211_regdomain == cfg80211_world_regdom) 150942b25cfSJohannes Berg cfg80211_regdomain = NULL; 151942b25cfSJohannes Berg if (cfg80211_world_regdom == &world_regdom) 152942b25cfSJohannes Berg cfg80211_world_regdom = NULL; 153942b25cfSJohannes Berg if (cfg80211_regdomain == &world_regdom) 154942b25cfSJohannes Berg cfg80211_regdomain = NULL; 155942b25cfSJohannes Berg 156734366deSJohannes Berg kfree(cfg80211_regdomain); 157734366deSJohannes Berg kfree(cfg80211_world_regdom); 158734366deSJohannes Berg 159a3d2eaf0SJohannes Berg cfg80211_world_regdom = &world_regdom; 160734366deSJohannes Berg cfg80211_regdomain = NULL; 161734366deSJohannes Berg } 162734366deSJohannes Berg 163fb1fc7adSLuis R. Rodriguez /* 164fb1fc7adSLuis R. Rodriguez * Dynamic world regulatory domain requested by the wireless 165fb1fc7adSLuis R. Rodriguez * core upon initialization 166fb1fc7adSLuis R. Rodriguez */ 167a3d2eaf0SJohannes Berg static void update_world_regdomain(const struct ieee80211_regdomain *rd) 168734366deSJohannes Berg { 169f6037d09SJohannes Berg BUG_ON(!last_request); 170734366deSJohannes Berg 171734366deSJohannes Berg reset_regdomains(); 172734366deSJohannes Berg 173734366deSJohannes Berg cfg80211_world_regdom = rd; 174734366deSJohannes Berg cfg80211_regdomain = rd; 175734366deSJohannes Berg } 176734366deSJohannes Berg 177a3d2eaf0SJohannes Berg bool is_world_regdom(const char *alpha2) 178b2e1b302SLuis R. Rodriguez { 179b2e1b302SLuis R. Rodriguez if (!alpha2) 180b2e1b302SLuis R. Rodriguez return false; 181b2e1b302SLuis R. Rodriguez if (alpha2[0] == '0' && alpha2[1] == '0') 182b2e1b302SLuis R. Rodriguez return true; 183b2e1b302SLuis R. Rodriguez return false; 184b2e1b302SLuis R. Rodriguez } 185b2e1b302SLuis R. Rodriguez 186a3d2eaf0SJohannes Berg static bool is_alpha2_set(const char *alpha2) 187b2e1b302SLuis R. Rodriguez { 188b2e1b302SLuis R. Rodriguez if (!alpha2) 189b2e1b302SLuis R. Rodriguez return false; 190b2e1b302SLuis R. Rodriguez if (alpha2[0] != 0 && alpha2[1] != 0) 191b2e1b302SLuis R. Rodriguez return true; 192b2e1b302SLuis R. Rodriguez return false; 193b2e1b302SLuis R. Rodriguez } 194b2e1b302SLuis R. Rodriguez 195a3d2eaf0SJohannes Berg static bool is_unknown_alpha2(const char *alpha2) 196b2e1b302SLuis R. Rodriguez { 197b2e1b302SLuis R. Rodriguez if (!alpha2) 198b2e1b302SLuis R. Rodriguez return false; 199fb1fc7adSLuis R. Rodriguez /* 200fb1fc7adSLuis R. Rodriguez * Special case where regulatory domain was built by driver 201fb1fc7adSLuis R. Rodriguez * but a specific alpha2 cannot be determined 202fb1fc7adSLuis R. Rodriguez */ 203b2e1b302SLuis R. Rodriguez if (alpha2[0] == '9' && alpha2[1] == '9') 204b2e1b302SLuis R. Rodriguez return true; 205b2e1b302SLuis R. Rodriguez return false; 206b2e1b302SLuis R. Rodriguez } 207b2e1b302SLuis R. Rodriguez 2083f2355cbSLuis R. Rodriguez static bool is_intersected_alpha2(const char *alpha2) 2093f2355cbSLuis R. Rodriguez { 2103f2355cbSLuis R. Rodriguez if (!alpha2) 2113f2355cbSLuis R. Rodriguez return false; 212fb1fc7adSLuis R. Rodriguez /* 213fb1fc7adSLuis R. Rodriguez * Special case where regulatory domain is the 2143f2355cbSLuis R. Rodriguez * result of an intersection between two regulatory domain 215fb1fc7adSLuis R. Rodriguez * structures 216fb1fc7adSLuis R. Rodriguez */ 2173f2355cbSLuis R. Rodriguez if (alpha2[0] == '9' && alpha2[1] == '8') 2183f2355cbSLuis R. Rodriguez return true; 2193f2355cbSLuis R. Rodriguez return false; 2203f2355cbSLuis R. Rodriguez } 2213f2355cbSLuis R. Rodriguez 222a3d2eaf0SJohannes Berg static bool is_an_alpha2(const char *alpha2) 223b2e1b302SLuis R. Rodriguez { 224b2e1b302SLuis R. Rodriguez if (!alpha2) 225b2e1b302SLuis R. Rodriguez return false; 226c61029c7SJohn W. Linville if (isalpha(alpha2[0]) && isalpha(alpha2[1])) 227b2e1b302SLuis R. Rodriguez return true; 228b2e1b302SLuis R. Rodriguez return false; 229b2e1b302SLuis R. Rodriguez } 230b2e1b302SLuis R. Rodriguez 231a3d2eaf0SJohannes Berg static bool alpha2_equal(const char *alpha2_x, const char *alpha2_y) 232b2e1b302SLuis R. Rodriguez { 233b2e1b302SLuis R. Rodriguez if (!alpha2_x || !alpha2_y) 234b2e1b302SLuis R. Rodriguez return false; 235b2e1b302SLuis R. Rodriguez if (alpha2_x[0] == alpha2_y[0] && 236b2e1b302SLuis R. Rodriguez alpha2_x[1] == alpha2_y[1]) 237b2e1b302SLuis R. Rodriguez return true; 238b2e1b302SLuis R. Rodriguez return false; 239b2e1b302SLuis R. Rodriguez } 240b2e1b302SLuis R. Rodriguez 24169b1572bSLuis R. Rodriguez static bool regdom_changes(const char *alpha2) 242b2e1b302SLuis R. Rodriguez { 243761cf7ecSLuis R. Rodriguez assert_cfg80211_lock(); 244761cf7ecSLuis R. Rodriguez 245b2e1b302SLuis R. Rodriguez if (!cfg80211_regdomain) 246b2e1b302SLuis R. Rodriguez return true; 247b2e1b302SLuis R. Rodriguez if (alpha2_equal(cfg80211_regdomain->alpha2, alpha2)) 248b2e1b302SLuis R. Rodriguez return false; 249b2e1b302SLuis R. Rodriguez return true; 250b2e1b302SLuis R. Rodriguez } 251b2e1b302SLuis R. Rodriguez 25209d989d1SLuis R. Rodriguez /* 25309d989d1SLuis R. Rodriguez * The NL80211_REGDOM_SET_BY_USER regdom alpha2 is cached, this lets 25409d989d1SLuis R. Rodriguez * you know if a valid regulatory hint with NL80211_REGDOM_SET_BY_USER 25509d989d1SLuis R. Rodriguez * has ever been issued. 25609d989d1SLuis R. Rodriguez */ 25709d989d1SLuis R. Rodriguez static bool is_user_regdom_saved(void) 25809d989d1SLuis R. Rodriguez { 25909d989d1SLuis R. Rodriguez if (user_alpha2[0] == '9' && user_alpha2[1] == '7') 26009d989d1SLuis R. Rodriguez return false; 26109d989d1SLuis R. Rodriguez 26209d989d1SLuis R. Rodriguez /* This would indicate a mistake on the design */ 26309d989d1SLuis R. Rodriguez if (WARN((!is_world_regdom(user_alpha2) && 26409d989d1SLuis R. Rodriguez !is_an_alpha2(user_alpha2)), 26509d989d1SLuis R. Rodriguez "Unexpected user alpha2: %c%c\n", 26609d989d1SLuis R. Rodriguez user_alpha2[0], 26709d989d1SLuis R. Rodriguez user_alpha2[1])) 26809d989d1SLuis R. Rodriguez return false; 26909d989d1SLuis R. Rodriguez 27009d989d1SLuis R. Rodriguez return true; 27109d989d1SLuis R. Rodriguez } 27209d989d1SLuis R. Rodriguez 2733b377ea9SJohn W. Linville static int reg_copy_regd(const struct ieee80211_regdomain **dst_regd, 2743b377ea9SJohn W. Linville const struct ieee80211_regdomain *src_regd) 2753b377ea9SJohn W. Linville { 2763b377ea9SJohn W. Linville struct ieee80211_regdomain *regd; 2773b377ea9SJohn W. Linville int size_of_regd = 0; 2783b377ea9SJohn W. Linville unsigned int i; 2793b377ea9SJohn W. Linville 2803b377ea9SJohn W. Linville size_of_regd = sizeof(struct ieee80211_regdomain) + 2813b377ea9SJohn W. Linville ((src_regd->n_reg_rules + 1) * sizeof(struct ieee80211_reg_rule)); 2823b377ea9SJohn W. Linville 2833b377ea9SJohn W. Linville regd = kzalloc(size_of_regd, GFP_KERNEL); 2843b377ea9SJohn W. Linville if (!regd) 2853b377ea9SJohn W. Linville return -ENOMEM; 2863b377ea9SJohn W. Linville 2873b377ea9SJohn W. Linville memcpy(regd, src_regd, sizeof(struct ieee80211_regdomain)); 2883b377ea9SJohn W. Linville 2893b377ea9SJohn W. Linville for (i = 0; i < src_regd->n_reg_rules; i++) 2903b377ea9SJohn W. Linville memcpy(®d->reg_rules[i], &src_regd->reg_rules[i], 2913b377ea9SJohn W. Linville sizeof(struct ieee80211_reg_rule)); 2923b377ea9SJohn W. Linville 2933b377ea9SJohn W. Linville *dst_regd = regd; 2943b377ea9SJohn W. Linville return 0; 2953b377ea9SJohn W. Linville } 2963b377ea9SJohn W. Linville 2973b377ea9SJohn W. Linville #ifdef CONFIG_CFG80211_INTERNAL_REGDB 2983b377ea9SJohn W. Linville struct reg_regdb_search_request { 2993b377ea9SJohn W. Linville char alpha2[2]; 3003b377ea9SJohn W. Linville struct list_head list; 3013b377ea9SJohn W. Linville }; 3023b377ea9SJohn W. Linville 3033b377ea9SJohn W. Linville static LIST_HEAD(reg_regdb_search_list); 304368d06f5SJohn W. Linville static DEFINE_MUTEX(reg_regdb_search_mutex); 3053b377ea9SJohn W. Linville 3063b377ea9SJohn W. Linville static void reg_regdb_search(struct work_struct *work) 3073b377ea9SJohn W. Linville { 3083b377ea9SJohn W. Linville struct reg_regdb_search_request *request; 3093b377ea9SJohn W. Linville const struct ieee80211_regdomain *curdom, *regdom; 3103b377ea9SJohn W. Linville int i, r; 3113b377ea9SJohn W. Linville 312368d06f5SJohn W. Linville mutex_lock(®_regdb_search_mutex); 3133b377ea9SJohn W. Linville while (!list_empty(®_regdb_search_list)) { 3143b377ea9SJohn W. Linville request = list_first_entry(®_regdb_search_list, 3153b377ea9SJohn W. Linville struct reg_regdb_search_request, 3163b377ea9SJohn W. Linville list); 3173b377ea9SJohn W. Linville list_del(&request->list); 3183b377ea9SJohn W. Linville 3193b377ea9SJohn W. Linville for (i=0; i<reg_regdb_size; i++) { 3203b377ea9SJohn W. Linville curdom = reg_regdb[i]; 3213b377ea9SJohn W. Linville 3223b377ea9SJohn W. Linville if (!memcmp(request->alpha2, curdom->alpha2, 2)) { 3233b377ea9SJohn W. Linville r = reg_copy_regd(®dom, curdom); 3243b377ea9SJohn W. Linville if (r) 3253b377ea9SJohn W. Linville break; 3263b377ea9SJohn W. Linville mutex_lock(&cfg80211_mutex); 3273b377ea9SJohn W. Linville set_regdom(regdom); 3283b377ea9SJohn W. Linville mutex_unlock(&cfg80211_mutex); 3293b377ea9SJohn W. Linville break; 3303b377ea9SJohn W. Linville } 3313b377ea9SJohn W. Linville } 3323b377ea9SJohn W. Linville 3333b377ea9SJohn W. Linville kfree(request); 3343b377ea9SJohn W. Linville } 335368d06f5SJohn W. Linville mutex_unlock(®_regdb_search_mutex); 3363b377ea9SJohn W. Linville } 3373b377ea9SJohn W. Linville 3383b377ea9SJohn W. Linville static DECLARE_WORK(reg_regdb_work, reg_regdb_search); 3393b377ea9SJohn W. Linville 3403b377ea9SJohn W. Linville static void reg_regdb_query(const char *alpha2) 3413b377ea9SJohn W. Linville { 3423b377ea9SJohn W. Linville struct reg_regdb_search_request *request; 3433b377ea9SJohn W. Linville 3443b377ea9SJohn W. Linville if (!alpha2) 3453b377ea9SJohn W. Linville return; 3463b377ea9SJohn W. Linville 3473b377ea9SJohn W. Linville request = kzalloc(sizeof(struct reg_regdb_search_request), GFP_KERNEL); 3483b377ea9SJohn W. Linville if (!request) 3493b377ea9SJohn W. Linville return; 3503b377ea9SJohn W. Linville 3513b377ea9SJohn W. Linville memcpy(request->alpha2, alpha2, 2); 3523b377ea9SJohn W. Linville 353368d06f5SJohn W. Linville mutex_lock(®_regdb_search_mutex); 3543b377ea9SJohn W. Linville list_add_tail(&request->list, ®_regdb_search_list); 355368d06f5SJohn W. Linville mutex_unlock(®_regdb_search_mutex); 3563b377ea9SJohn W. Linville 3573b377ea9SJohn W. Linville schedule_work(®_regdb_work); 3583b377ea9SJohn W. Linville } 3593b377ea9SJohn W. Linville #else 3603b377ea9SJohn W. Linville static inline void reg_regdb_query(const char *alpha2) {} 3613b377ea9SJohn W. Linville #endif /* CONFIG_CFG80211_INTERNAL_REGDB */ 3623b377ea9SJohn W. Linville 363fb1fc7adSLuis R. Rodriguez /* 364fb1fc7adSLuis R. Rodriguez * This lets us keep regulatory code which is updated on a regulatory 365fb1fc7adSLuis R. Rodriguez * basis in userspace. 366fb1fc7adSLuis R. Rodriguez */ 367b2e1b302SLuis R. Rodriguez static int call_crda(const char *alpha2) 368b2e1b302SLuis R. Rodriguez { 369b2e1b302SLuis R. Rodriguez char country_env[9 + 2] = "COUNTRY="; 370b2e1b302SLuis R. Rodriguez char *envp[] = { 371b2e1b302SLuis R. Rodriguez country_env, 372b2e1b302SLuis R. Rodriguez NULL 3738318d78aSJohannes Berg }; 3748318d78aSJohannes Berg 375b2e1b302SLuis R. Rodriguez if (!is_world_regdom((char *) alpha2)) 376e9c0268fSJoe Perches pr_info("Calling CRDA for country: %c%c\n", 377b2e1b302SLuis R. Rodriguez alpha2[0], alpha2[1]); 378b2e1b302SLuis R. Rodriguez else 379e9c0268fSJoe Perches pr_info("Calling CRDA to update world regulatory domain\n"); 3808318d78aSJohannes Berg 3813b377ea9SJohn W. Linville /* query internal regulatory database (if it exists) */ 3823b377ea9SJohn W. Linville reg_regdb_query(alpha2); 3833b377ea9SJohn W. Linville 384b2e1b302SLuis R. Rodriguez country_env[8] = alpha2[0]; 385b2e1b302SLuis R. Rodriguez country_env[9] = alpha2[1]; 3868318d78aSJohannes Berg 387b2e1b302SLuis R. Rodriguez return kobject_uevent_env(®_pdev->dev.kobj, KOBJ_CHANGE, envp); 388b2e1b302SLuis R. Rodriguez } 389b2e1b302SLuis R. Rodriguez 390b2e1b302SLuis R. Rodriguez /* Used by nl80211 before kmalloc'ing our regulatory domain */ 391a3d2eaf0SJohannes Berg bool reg_is_valid_request(const char *alpha2) 392b2e1b302SLuis R. Rodriguez { 39361405e97SLuis R. Rodriguez assert_cfg80211_lock(); 39461405e97SLuis R. Rodriguez 395f6037d09SJohannes Berg if (!last_request) 396f6037d09SJohannes Berg return false; 397f6037d09SJohannes Berg 398f6037d09SJohannes Berg return alpha2_equal(last_request->alpha2, alpha2); 399b2e1b302SLuis R. Rodriguez } 400b2e1b302SLuis R. Rodriguez 401b2e1b302SLuis R. Rodriguez /* Sanity check on a regulatory rule */ 402a3d2eaf0SJohannes Berg static bool is_valid_reg_rule(const struct ieee80211_reg_rule *rule) 403b2e1b302SLuis R. Rodriguez { 404a3d2eaf0SJohannes Berg const struct ieee80211_freq_range *freq_range = &rule->freq_range; 405b2e1b302SLuis R. Rodriguez u32 freq_diff; 406b2e1b302SLuis R. Rodriguez 40791e99004SLuis R. Rodriguez if (freq_range->start_freq_khz <= 0 || freq_range->end_freq_khz <= 0) 408b2e1b302SLuis R. Rodriguez return false; 409b2e1b302SLuis R. Rodriguez 410b2e1b302SLuis R. Rodriguez if (freq_range->start_freq_khz > freq_range->end_freq_khz) 411b2e1b302SLuis R. Rodriguez return false; 412b2e1b302SLuis R. Rodriguez 413b2e1b302SLuis R. Rodriguez freq_diff = freq_range->end_freq_khz - freq_range->start_freq_khz; 414b2e1b302SLuis R. Rodriguez 415bd05f28eSRoel Kluin if (freq_range->end_freq_khz <= freq_range->start_freq_khz || 416bd05f28eSRoel Kluin freq_range->max_bandwidth_khz > freq_diff) 417b2e1b302SLuis R. Rodriguez return false; 418b2e1b302SLuis R. Rodriguez 419b2e1b302SLuis R. Rodriguez return true; 420b2e1b302SLuis R. Rodriguez } 421b2e1b302SLuis R. Rodriguez 422a3d2eaf0SJohannes Berg static bool is_valid_rd(const struct ieee80211_regdomain *rd) 423b2e1b302SLuis R. Rodriguez { 424a3d2eaf0SJohannes Berg const struct ieee80211_reg_rule *reg_rule = NULL; 425b2e1b302SLuis R. Rodriguez unsigned int i; 426b2e1b302SLuis R. Rodriguez 427b2e1b302SLuis R. Rodriguez if (!rd->n_reg_rules) 428b2e1b302SLuis R. Rodriguez return false; 429b2e1b302SLuis R. Rodriguez 43088dc1c3fSLuis R. Rodriguez if (WARN_ON(rd->n_reg_rules > NL80211_MAX_SUPP_REG_RULES)) 43188dc1c3fSLuis R. Rodriguez return false; 43288dc1c3fSLuis R. Rodriguez 433b2e1b302SLuis R. Rodriguez for (i = 0; i < rd->n_reg_rules; i++) { 434b2e1b302SLuis R. Rodriguez reg_rule = &rd->reg_rules[i]; 435b2e1b302SLuis R. Rodriguez if (!is_valid_reg_rule(reg_rule)) 436b2e1b302SLuis R. Rodriguez return false; 437b2e1b302SLuis R. Rodriguez } 438b2e1b302SLuis R. Rodriguez 439b2e1b302SLuis R. Rodriguez return true; 440b2e1b302SLuis R. Rodriguez } 441b2e1b302SLuis R. Rodriguez 442038659e7SLuis R. Rodriguez static bool reg_does_bw_fit(const struct ieee80211_freq_range *freq_range, 443038659e7SLuis R. Rodriguez u32 center_freq_khz, 444038659e7SLuis R. Rodriguez u32 bw_khz) 445b2e1b302SLuis R. Rodriguez { 446038659e7SLuis R. Rodriguez u32 start_freq_khz, end_freq_khz; 447038659e7SLuis R. Rodriguez 448038659e7SLuis R. Rodriguez start_freq_khz = center_freq_khz - (bw_khz/2); 449038659e7SLuis R. Rodriguez end_freq_khz = center_freq_khz + (bw_khz/2); 450038659e7SLuis R. Rodriguez 451b2e1b302SLuis R. Rodriguez if (start_freq_khz >= freq_range->start_freq_khz && 452b2e1b302SLuis R. Rodriguez end_freq_khz <= freq_range->end_freq_khz) 453038659e7SLuis R. Rodriguez return true; 454038659e7SLuis R. Rodriguez 455038659e7SLuis R. Rodriguez return false; 456b2e1b302SLuis R. Rodriguez } 457b2e1b302SLuis R. Rodriguez 4580c7dc45dSLuis R. Rodriguez /** 4590c7dc45dSLuis R. Rodriguez * freq_in_rule_band - tells us if a frequency is in a frequency band 4600c7dc45dSLuis R. Rodriguez * @freq_range: frequency rule we want to query 4610c7dc45dSLuis R. Rodriguez * @freq_khz: frequency we are inquiring about 4620c7dc45dSLuis R. Rodriguez * 4630c7dc45dSLuis R. Rodriguez * This lets us know if a specific frequency rule is or is not relevant to 4640c7dc45dSLuis R. Rodriguez * a specific frequency's band. Bands are device specific and artificial 4650c7dc45dSLuis R. Rodriguez * definitions (the "2.4 GHz band" and the "5 GHz band"), however it is 4660c7dc45dSLuis R. Rodriguez * safe for now to assume that a frequency rule should not be part of a 4670c7dc45dSLuis R. Rodriguez * frequency's band if the start freq or end freq are off by more than 2 GHz. 4680c7dc45dSLuis R. Rodriguez * This resolution can be lowered and should be considered as we add 4690c7dc45dSLuis R. Rodriguez * regulatory rule support for other "bands". 4700c7dc45dSLuis R. Rodriguez **/ 4710c7dc45dSLuis R. Rodriguez static bool freq_in_rule_band(const struct ieee80211_freq_range *freq_range, 4720c7dc45dSLuis R. Rodriguez u32 freq_khz) 4730c7dc45dSLuis R. Rodriguez { 4740c7dc45dSLuis R. Rodriguez #define ONE_GHZ_IN_KHZ 1000000 4750c7dc45dSLuis R. Rodriguez if (abs(freq_khz - freq_range->start_freq_khz) <= (2 * ONE_GHZ_IN_KHZ)) 4760c7dc45dSLuis R. Rodriguez return true; 4770c7dc45dSLuis R. Rodriguez if (abs(freq_khz - freq_range->end_freq_khz) <= (2 * ONE_GHZ_IN_KHZ)) 4780c7dc45dSLuis R. Rodriguez return true; 4790c7dc45dSLuis R. Rodriguez return false; 4800c7dc45dSLuis R. Rodriguez #undef ONE_GHZ_IN_KHZ 4810c7dc45dSLuis R. Rodriguez } 4820c7dc45dSLuis R. Rodriguez 483fb1fc7adSLuis R. Rodriguez /* 484fb1fc7adSLuis R. Rodriguez * Helper for regdom_intersect(), this does the real 485fb1fc7adSLuis R. Rodriguez * mathematical intersection fun 486fb1fc7adSLuis R. Rodriguez */ 4879c96477dSLuis R. Rodriguez static int reg_rules_intersect( 4889c96477dSLuis R. Rodriguez const struct ieee80211_reg_rule *rule1, 4899c96477dSLuis R. Rodriguez const struct ieee80211_reg_rule *rule2, 4909c96477dSLuis R. Rodriguez struct ieee80211_reg_rule *intersected_rule) 4919c96477dSLuis R. Rodriguez { 4929c96477dSLuis R. Rodriguez const struct ieee80211_freq_range *freq_range1, *freq_range2; 4939c96477dSLuis R. Rodriguez struct ieee80211_freq_range *freq_range; 4949c96477dSLuis R. Rodriguez const struct ieee80211_power_rule *power_rule1, *power_rule2; 4959c96477dSLuis R. Rodriguez struct ieee80211_power_rule *power_rule; 4969c96477dSLuis R. Rodriguez u32 freq_diff; 4979c96477dSLuis R. Rodriguez 4989c96477dSLuis R. Rodriguez freq_range1 = &rule1->freq_range; 4999c96477dSLuis R. Rodriguez freq_range2 = &rule2->freq_range; 5009c96477dSLuis R. Rodriguez freq_range = &intersected_rule->freq_range; 5019c96477dSLuis R. Rodriguez 5029c96477dSLuis R. Rodriguez power_rule1 = &rule1->power_rule; 5039c96477dSLuis R. Rodriguez power_rule2 = &rule2->power_rule; 5049c96477dSLuis R. Rodriguez power_rule = &intersected_rule->power_rule; 5059c96477dSLuis R. Rodriguez 5069c96477dSLuis R. Rodriguez freq_range->start_freq_khz = max(freq_range1->start_freq_khz, 5079c96477dSLuis R. Rodriguez freq_range2->start_freq_khz); 5089c96477dSLuis R. Rodriguez freq_range->end_freq_khz = min(freq_range1->end_freq_khz, 5099c96477dSLuis R. Rodriguez freq_range2->end_freq_khz); 5109c96477dSLuis R. Rodriguez freq_range->max_bandwidth_khz = min(freq_range1->max_bandwidth_khz, 5119c96477dSLuis R. Rodriguez freq_range2->max_bandwidth_khz); 5129c96477dSLuis R. Rodriguez 5139c96477dSLuis R. Rodriguez freq_diff = freq_range->end_freq_khz - freq_range->start_freq_khz; 5149c96477dSLuis R. Rodriguez if (freq_range->max_bandwidth_khz > freq_diff) 5159c96477dSLuis R. Rodriguez freq_range->max_bandwidth_khz = freq_diff; 5169c96477dSLuis R. Rodriguez 5179c96477dSLuis R. Rodriguez power_rule->max_eirp = min(power_rule1->max_eirp, 5189c96477dSLuis R. Rodriguez power_rule2->max_eirp); 5199c96477dSLuis R. Rodriguez power_rule->max_antenna_gain = min(power_rule1->max_antenna_gain, 5209c96477dSLuis R. Rodriguez power_rule2->max_antenna_gain); 5219c96477dSLuis R. Rodriguez 5229c96477dSLuis R. Rodriguez intersected_rule->flags = (rule1->flags | rule2->flags); 5239c96477dSLuis R. Rodriguez 5249c96477dSLuis R. Rodriguez if (!is_valid_reg_rule(intersected_rule)) 5259c96477dSLuis R. Rodriguez return -EINVAL; 5269c96477dSLuis R. Rodriguez 5279c96477dSLuis R. Rodriguez return 0; 5289c96477dSLuis R. Rodriguez } 5299c96477dSLuis R. Rodriguez 5309c96477dSLuis R. Rodriguez /** 5319c96477dSLuis R. Rodriguez * regdom_intersect - do the intersection between two regulatory domains 5329c96477dSLuis R. Rodriguez * @rd1: first regulatory domain 5339c96477dSLuis R. Rodriguez * @rd2: second regulatory domain 5349c96477dSLuis R. Rodriguez * 5359c96477dSLuis R. Rodriguez * Use this function to get the intersection between two regulatory domains. 5369c96477dSLuis R. Rodriguez * Once completed we will mark the alpha2 for the rd as intersected, "98", 5379c96477dSLuis R. Rodriguez * as no one single alpha2 can represent this regulatory domain. 5389c96477dSLuis R. Rodriguez * 5399c96477dSLuis R. Rodriguez * Returns a pointer to the regulatory domain structure which will hold the 5409c96477dSLuis R. Rodriguez * resulting intersection of rules between rd1 and rd2. We will 5419c96477dSLuis R. Rodriguez * kzalloc() this structure for you. 5429c96477dSLuis R. Rodriguez */ 5439c96477dSLuis R. Rodriguez static struct ieee80211_regdomain *regdom_intersect( 5449c96477dSLuis R. Rodriguez const struct ieee80211_regdomain *rd1, 5459c96477dSLuis R. Rodriguez const struct ieee80211_regdomain *rd2) 5469c96477dSLuis R. Rodriguez { 5479c96477dSLuis R. Rodriguez int r, size_of_regd; 5489c96477dSLuis R. Rodriguez unsigned int x, y; 5499c96477dSLuis R. Rodriguez unsigned int num_rules = 0, rule_idx = 0; 5509c96477dSLuis R. Rodriguez const struct ieee80211_reg_rule *rule1, *rule2; 5519c96477dSLuis R. Rodriguez struct ieee80211_reg_rule *intersected_rule; 5529c96477dSLuis R. Rodriguez struct ieee80211_regdomain *rd; 5539c96477dSLuis R. Rodriguez /* This is just a dummy holder to help us count */ 5549c96477dSLuis R. Rodriguez struct ieee80211_reg_rule irule; 5559c96477dSLuis R. Rodriguez 5569c96477dSLuis R. Rodriguez /* Uses the stack temporarily for counter arithmetic */ 5579c96477dSLuis R. Rodriguez intersected_rule = &irule; 5589c96477dSLuis R. Rodriguez 5599c96477dSLuis R. Rodriguez memset(intersected_rule, 0, sizeof(struct ieee80211_reg_rule)); 5609c96477dSLuis R. Rodriguez 5619c96477dSLuis R. Rodriguez if (!rd1 || !rd2) 5629c96477dSLuis R. Rodriguez return NULL; 5639c96477dSLuis R. Rodriguez 564fb1fc7adSLuis R. Rodriguez /* 565fb1fc7adSLuis R. Rodriguez * First we get a count of the rules we'll need, then we actually 5669c96477dSLuis R. Rodriguez * build them. This is to so we can malloc() and free() a 5679c96477dSLuis R. Rodriguez * regdomain once. The reason we use reg_rules_intersect() here 5689c96477dSLuis R. Rodriguez * is it will return -EINVAL if the rule computed makes no sense. 569fb1fc7adSLuis R. Rodriguez * All rules that do check out OK are valid. 570fb1fc7adSLuis R. Rodriguez */ 5719c96477dSLuis R. Rodriguez 5729c96477dSLuis R. Rodriguez for (x = 0; x < rd1->n_reg_rules; x++) { 5739c96477dSLuis R. Rodriguez rule1 = &rd1->reg_rules[x]; 5749c96477dSLuis R. Rodriguez for (y = 0; y < rd2->n_reg_rules; y++) { 5759c96477dSLuis R. Rodriguez rule2 = &rd2->reg_rules[y]; 5769c96477dSLuis R. Rodriguez if (!reg_rules_intersect(rule1, rule2, 5779c96477dSLuis R. Rodriguez intersected_rule)) 5789c96477dSLuis R. Rodriguez num_rules++; 5799c96477dSLuis R. Rodriguez memset(intersected_rule, 0, 5809c96477dSLuis R. Rodriguez sizeof(struct ieee80211_reg_rule)); 5819c96477dSLuis R. Rodriguez } 5829c96477dSLuis R. Rodriguez } 5839c96477dSLuis R. Rodriguez 5849c96477dSLuis R. Rodriguez if (!num_rules) 5859c96477dSLuis R. Rodriguez return NULL; 5869c96477dSLuis R. Rodriguez 5879c96477dSLuis R. Rodriguez size_of_regd = sizeof(struct ieee80211_regdomain) + 5889c96477dSLuis R. Rodriguez ((num_rules + 1) * sizeof(struct ieee80211_reg_rule)); 5899c96477dSLuis R. Rodriguez 5909c96477dSLuis R. Rodriguez rd = kzalloc(size_of_regd, GFP_KERNEL); 5919c96477dSLuis R. Rodriguez if (!rd) 5929c96477dSLuis R. Rodriguez return NULL; 5939c96477dSLuis R. Rodriguez 5949c96477dSLuis R. Rodriguez for (x = 0; x < rd1->n_reg_rules; x++) { 5959c96477dSLuis R. Rodriguez rule1 = &rd1->reg_rules[x]; 5969c96477dSLuis R. Rodriguez for (y = 0; y < rd2->n_reg_rules; y++) { 5979c96477dSLuis R. Rodriguez rule2 = &rd2->reg_rules[y]; 598fb1fc7adSLuis R. Rodriguez /* 599fb1fc7adSLuis R. Rodriguez * This time around instead of using the stack lets 6009c96477dSLuis R. Rodriguez * write to the target rule directly saving ourselves 601fb1fc7adSLuis R. Rodriguez * a memcpy() 602fb1fc7adSLuis R. Rodriguez */ 6039c96477dSLuis R. Rodriguez intersected_rule = &rd->reg_rules[rule_idx]; 6049c96477dSLuis R. Rodriguez r = reg_rules_intersect(rule1, rule2, 6059c96477dSLuis R. Rodriguez intersected_rule); 606fb1fc7adSLuis R. Rodriguez /* 607fb1fc7adSLuis R. Rodriguez * No need to memset here the intersected rule here as 608fb1fc7adSLuis R. Rodriguez * we're not using the stack anymore 609fb1fc7adSLuis R. Rodriguez */ 6109c96477dSLuis R. Rodriguez if (r) 6119c96477dSLuis R. Rodriguez continue; 6129c96477dSLuis R. Rodriguez rule_idx++; 6139c96477dSLuis R. Rodriguez } 6149c96477dSLuis R. Rodriguez } 6159c96477dSLuis R. Rodriguez 6169c96477dSLuis R. Rodriguez if (rule_idx != num_rules) { 6179c96477dSLuis R. Rodriguez kfree(rd); 6189c96477dSLuis R. Rodriguez return NULL; 6199c96477dSLuis R. Rodriguez } 6209c96477dSLuis R. Rodriguez 6219c96477dSLuis R. Rodriguez rd->n_reg_rules = num_rules; 6229c96477dSLuis R. Rodriguez rd->alpha2[0] = '9'; 6239c96477dSLuis R. Rodriguez rd->alpha2[1] = '8'; 6249c96477dSLuis R. Rodriguez 6259c96477dSLuis R. Rodriguez return rd; 6269c96477dSLuis R. Rodriguez } 6279c96477dSLuis R. Rodriguez 628fb1fc7adSLuis R. Rodriguez /* 629fb1fc7adSLuis R. Rodriguez * XXX: add support for the rest of enum nl80211_reg_rule_flags, we may 630fb1fc7adSLuis R. Rodriguez * want to just have the channel structure use these 631fb1fc7adSLuis R. Rodriguez */ 632b2e1b302SLuis R. Rodriguez static u32 map_regdom_flags(u32 rd_flags) 633b2e1b302SLuis R. Rodriguez { 634b2e1b302SLuis R. Rodriguez u32 channel_flags = 0; 635b2e1b302SLuis R. Rodriguez if (rd_flags & NL80211_RRF_PASSIVE_SCAN) 636b2e1b302SLuis R. Rodriguez channel_flags |= IEEE80211_CHAN_PASSIVE_SCAN; 637b2e1b302SLuis R. Rodriguez if (rd_flags & NL80211_RRF_NO_IBSS) 638b2e1b302SLuis R. Rodriguez channel_flags |= IEEE80211_CHAN_NO_IBSS; 639b2e1b302SLuis R. Rodriguez if (rd_flags & NL80211_RRF_DFS) 640b2e1b302SLuis R. Rodriguez channel_flags |= IEEE80211_CHAN_RADAR; 641b2e1b302SLuis R. Rodriguez return channel_flags; 642b2e1b302SLuis R. Rodriguez } 643b2e1b302SLuis R. Rodriguez 6441fa25e41SLuis R. Rodriguez static int freq_reg_info_regd(struct wiphy *wiphy, 6451fa25e41SLuis R. Rodriguez u32 center_freq, 646038659e7SLuis R. Rodriguez u32 desired_bw_khz, 6471fa25e41SLuis R. Rodriguez const struct ieee80211_reg_rule **reg_rule, 6481fa25e41SLuis R. Rodriguez const struct ieee80211_regdomain *custom_regd) 6498318d78aSJohannes Berg { 6508318d78aSJohannes Berg int i; 6510c7dc45dSLuis R. Rodriguez bool band_rule_found = false; 6523e0c3ff3SLuis R. Rodriguez const struct ieee80211_regdomain *regd; 653038659e7SLuis R. Rodriguez bool bw_fits = false; 654038659e7SLuis R. Rodriguez 655038659e7SLuis R. Rodriguez if (!desired_bw_khz) 656038659e7SLuis R. Rodriguez desired_bw_khz = MHZ_TO_KHZ(20); 6578318d78aSJohannes Berg 6581fa25e41SLuis R. Rodriguez regd = custom_regd ? custom_regd : cfg80211_regdomain; 6593e0c3ff3SLuis R. Rodriguez 660fb1fc7adSLuis R. Rodriguez /* 661fb1fc7adSLuis R. Rodriguez * Follow the driver's regulatory domain, if present, unless a country 662fb1fc7adSLuis R. Rodriguez * IE has been processed or a user wants to help complaince further 663fb1fc7adSLuis R. Rodriguez */ 6642784fe91SLuis R. Rodriguez if (!custom_regd && 6652784fe91SLuis R. Rodriguez last_request->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE && 6667db90f4aSLuis R. Rodriguez last_request->initiator != NL80211_REGDOM_SET_BY_USER && 6673e0c3ff3SLuis R. Rodriguez wiphy->regd) 6683e0c3ff3SLuis R. Rodriguez regd = wiphy->regd; 6693e0c3ff3SLuis R. Rodriguez 6703e0c3ff3SLuis R. Rodriguez if (!regd) 671b2e1b302SLuis R. Rodriguez return -EINVAL; 672b2e1b302SLuis R. Rodriguez 6733e0c3ff3SLuis R. Rodriguez for (i = 0; i < regd->n_reg_rules; i++) { 674b2e1b302SLuis R. Rodriguez const struct ieee80211_reg_rule *rr; 675b2e1b302SLuis R. Rodriguez const struct ieee80211_freq_range *fr = NULL; 676b2e1b302SLuis R. Rodriguez const struct ieee80211_power_rule *pr = NULL; 677b2e1b302SLuis R. Rodriguez 6783e0c3ff3SLuis R. Rodriguez rr = ®d->reg_rules[i]; 679b2e1b302SLuis R. Rodriguez fr = &rr->freq_range; 680b2e1b302SLuis R. Rodriguez pr = &rr->power_rule; 6810c7dc45dSLuis R. Rodriguez 682fb1fc7adSLuis R. Rodriguez /* 683fb1fc7adSLuis R. Rodriguez * We only need to know if one frequency rule was 6840c7dc45dSLuis R. Rodriguez * was in center_freq's band, that's enough, so lets 685fb1fc7adSLuis R. Rodriguez * not overwrite it once found 686fb1fc7adSLuis R. Rodriguez */ 6870c7dc45dSLuis R. Rodriguez if (!band_rule_found) 6880c7dc45dSLuis R. Rodriguez band_rule_found = freq_in_rule_band(fr, center_freq); 6890c7dc45dSLuis R. Rodriguez 690038659e7SLuis R. Rodriguez bw_fits = reg_does_bw_fit(fr, 691038659e7SLuis R. Rodriguez center_freq, 692038659e7SLuis R. Rodriguez desired_bw_khz); 6930c7dc45dSLuis R. Rodriguez 694038659e7SLuis R. Rodriguez if (band_rule_found && bw_fits) { 695b2e1b302SLuis R. Rodriguez *reg_rule = rr; 696038659e7SLuis R. Rodriguez return 0; 6978318d78aSJohannes Berg } 6988318d78aSJohannes Berg } 6998318d78aSJohannes Berg 7000c7dc45dSLuis R. Rodriguez if (!band_rule_found) 7010c7dc45dSLuis R. Rodriguez return -ERANGE; 7020c7dc45dSLuis R. Rodriguez 703038659e7SLuis R. Rodriguez return -EINVAL; 704b2e1b302SLuis R. Rodriguez } 705b2e1b302SLuis R. Rodriguez 706038659e7SLuis R. Rodriguez int freq_reg_info(struct wiphy *wiphy, 707038659e7SLuis R. Rodriguez u32 center_freq, 708038659e7SLuis R. Rodriguez u32 desired_bw_khz, 7091fa25e41SLuis R. Rodriguez const struct ieee80211_reg_rule **reg_rule) 7101fa25e41SLuis R. Rodriguez { 711ac46d48eSLuis R. Rodriguez assert_cfg80211_lock(); 712038659e7SLuis R. Rodriguez return freq_reg_info_regd(wiphy, 713038659e7SLuis R. Rodriguez center_freq, 714038659e7SLuis R. Rodriguez desired_bw_khz, 715038659e7SLuis R. Rodriguez reg_rule, 716038659e7SLuis R. Rodriguez NULL); 7171fa25e41SLuis R. Rodriguez } 7184f366c5dSJohn W. Linville EXPORT_SYMBOL(freq_reg_info); 719b2e1b302SLuis R. Rodriguez 720926a0a09SLuis R. Rodriguez #ifdef CONFIG_CFG80211_REG_DEBUG 721926a0a09SLuis R. Rodriguez static const char *reg_initiator_name(enum nl80211_reg_initiator initiator) 722926a0a09SLuis R. Rodriguez { 723926a0a09SLuis R. Rodriguez switch (initiator) { 724926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_CORE: 725926a0a09SLuis R. Rodriguez return "Set by core"; 726926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_USER: 727926a0a09SLuis R. Rodriguez return "Set by user"; 728926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_DRIVER: 729926a0a09SLuis R. Rodriguez return "Set by driver"; 730926a0a09SLuis R. Rodriguez case NL80211_REGDOM_SET_BY_COUNTRY_IE: 731926a0a09SLuis R. Rodriguez return "Set by country IE"; 732926a0a09SLuis R. Rodriguez default: 733926a0a09SLuis R. Rodriguez WARN_ON(1); 734926a0a09SLuis R. Rodriguez return "Set by bug"; 735926a0a09SLuis R. Rodriguez } 736926a0a09SLuis R. Rodriguez } 737e702d3cfSLuis R. Rodriguez 738e702d3cfSLuis R. Rodriguez static void chan_reg_rule_print_dbg(struct ieee80211_channel *chan, 739e702d3cfSLuis R. Rodriguez u32 desired_bw_khz, 740e702d3cfSLuis R. Rodriguez const struct ieee80211_reg_rule *reg_rule) 741e702d3cfSLuis R. Rodriguez { 742e702d3cfSLuis R. Rodriguez const struct ieee80211_power_rule *power_rule; 743e702d3cfSLuis R. Rodriguez const struct ieee80211_freq_range *freq_range; 744e702d3cfSLuis R. Rodriguez char max_antenna_gain[32]; 745e702d3cfSLuis R. Rodriguez 746e702d3cfSLuis R. Rodriguez power_rule = ®_rule->power_rule; 747e702d3cfSLuis R. Rodriguez freq_range = ®_rule->freq_range; 748e702d3cfSLuis R. Rodriguez 749e702d3cfSLuis R. Rodriguez if (!power_rule->max_antenna_gain) 750e702d3cfSLuis R. Rodriguez snprintf(max_antenna_gain, 32, "N/A"); 751e702d3cfSLuis R. Rodriguez else 752e702d3cfSLuis R. Rodriguez snprintf(max_antenna_gain, 32, "%d", power_rule->max_antenna_gain); 753e702d3cfSLuis R. Rodriguez 754d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Updating information on frequency %d MHz " 755e702d3cfSLuis R. Rodriguez "for %d a MHz width channel with regulatory rule:\n", 756e702d3cfSLuis R. Rodriguez chan->center_freq, 757e702d3cfSLuis R. Rodriguez KHZ_TO_MHZ(desired_bw_khz)); 758e702d3cfSLuis R. Rodriguez 759d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("%d KHz - %d KHz @ KHz), (%s mBi, %d mBm)\n", 760e702d3cfSLuis R. Rodriguez freq_range->start_freq_khz, 761e702d3cfSLuis R. Rodriguez freq_range->end_freq_khz, 762e702d3cfSLuis R. Rodriguez max_antenna_gain, 763e702d3cfSLuis R. Rodriguez power_rule->max_eirp); 764e702d3cfSLuis R. Rodriguez } 765e702d3cfSLuis R. Rodriguez #else 766e702d3cfSLuis R. Rodriguez static void chan_reg_rule_print_dbg(struct ieee80211_channel *chan, 767e702d3cfSLuis R. Rodriguez u32 desired_bw_khz, 768e702d3cfSLuis R. Rodriguez const struct ieee80211_reg_rule *reg_rule) 769e702d3cfSLuis R. Rodriguez { 770e702d3cfSLuis R. Rodriguez return; 771e702d3cfSLuis R. Rodriguez } 772926a0a09SLuis R. Rodriguez #endif 773926a0a09SLuis R. Rodriguez 774038659e7SLuis R. Rodriguez /* 775038659e7SLuis R. Rodriguez * Note that right now we assume the desired channel bandwidth 776038659e7SLuis R. Rodriguez * is always 20 MHz for each individual channel (HT40 uses 20 MHz 777038659e7SLuis R. Rodriguez * per channel, the primary and the extension channel). To support 778038659e7SLuis R. Rodriguez * smaller custom bandwidths such as 5 MHz or 10 MHz we'll need a 779038659e7SLuis R. Rodriguez * new ieee80211_channel.target_bw and re run the regulatory check 780038659e7SLuis R. Rodriguez * on the wiphy with the target_bw specified. Then we can simply use 781038659e7SLuis R. Rodriguez * that below for the desired_bw_khz below. 782038659e7SLuis R. Rodriguez */ 7837ca43d03SLuis R. Rodriguez static void handle_channel(struct wiphy *wiphy, 7847ca43d03SLuis R. Rodriguez enum nl80211_reg_initiator initiator, 7857ca43d03SLuis R. Rodriguez enum ieee80211_band band, 786a92a3ce7SLuis R. Rodriguez unsigned int chan_idx) 787b2e1b302SLuis R. Rodriguez { 788b2e1b302SLuis R. Rodriguez int r; 789038659e7SLuis R. Rodriguez u32 flags, bw_flags = 0; 790038659e7SLuis R. Rodriguez u32 desired_bw_khz = MHZ_TO_KHZ(20); 791b2e1b302SLuis R. Rodriguez const struct ieee80211_reg_rule *reg_rule = NULL; 792b2e1b302SLuis R. Rodriguez const struct ieee80211_power_rule *power_rule = NULL; 793038659e7SLuis R. Rodriguez const struct ieee80211_freq_range *freq_range = NULL; 794a92a3ce7SLuis R. Rodriguez struct ieee80211_supported_band *sband; 795a92a3ce7SLuis R. Rodriguez struct ieee80211_channel *chan; 796fe33eb39SLuis R. Rodriguez struct wiphy *request_wiphy = NULL; 797a92a3ce7SLuis R. Rodriguez 798761cf7ecSLuis R. Rodriguez assert_cfg80211_lock(); 799761cf7ecSLuis R. Rodriguez 800806a9e39SLuis R. Rodriguez request_wiphy = wiphy_idx_to_wiphy(last_request->wiphy_idx); 801806a9e39SLuis R. Rodriguez 802a92a3ce7SLuis R. Rodriguez sband = wiphy->bands[band]; 803a92a3ce7SLuis R. Rodriguez BUG_ON(chan_idx >= sband->n_channels); 804a92a3ce7SLuis R. Rodriguez chan = &sband->channels[chan_idx]; 805a92a3ce7SLuis R. Rodriguez 806a92a3ce7SLuis R. Rodriguez flags = chan->orig_flags; 807b2e1b302SLuis R. Rodriguez 808038659e7SLuis R. Rodriguez r = freq_reg_info(wiphy, 809038659e7SLuis R. Rodriguez MHZ_TO_KHZ(chan->center_freq), 810038659e7SLuis R. Rodriguez desired_bw_khz, 811038659e7SLuis R. Rodriguez ®_rule); 812b2e1b302SLuis R. Rodriguez 813ca4ffe8fSLuis R. Rodriguez if (r) { 814ca4ffe8fSLuis R. Rodriguez /* 815ca4ffe8fSLuis R. Rodriguez * We will disable all channels that do not match our 816ca4ffe8fSLuis R. Rodriguez * recieved regulatory rule unless the hint is coming 817ca4ffe8fSLuis R. Rodriguez * from a Country IE and the Country IE had no information 818ca4ffe8fSLuis R. Rodriguez * about a band. The IEEE 802.11 spec allows for an AP 819ca4ffe8fSLuis R. Rodriguez * to send only a subset of the regulatory rules allowed, 820ca4ffe8fSLuis R. Rodriguez * so an AP in the US that only supports 2.4 GHz may only send 821ca4ffe8fSLuis R. Rodriguez * a country IE with information for the 2.4 GHz band 822ca4ffe8fSLuis R. Rodriguez * while 5 GHz is still supported. 823ca4ffe8fSLuis R. Rodriguez */ 824ca4ffe8fSLuis R. Rodriguez if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE && 825ca4ffe8fSLuis R. Rodriguez r == -ERANGE) 8268318d78aSJohannes Berg return; 8278318d78aSJohannes Berg 828d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Disabling freq %d MHz\n", chan->center_freq); 829ca4ffe8fSLuis R. Rodriguez chan->flags = IEEE80211_CHAN_DISABLED; 830ca4ffe8fSLuis R. Rodriguez return; 831ca4ffe8fSLuis R. Rodriguez } 832ca4ffe8fSLuis R. Rodriguez 833e702d3cfSLuis R. Rodriguez chan_reg_rule_print_dbg(chan, desired_bw_khz, reg_rule); 834e702d3cfSLuis R. Rodriguez 835b2e1b302SLuis R. Rodriguez power_rule = ®_rule->power_rule; 836038659e7SLuis R. Rodriguez freq_range = ®_rule->freq_range; 837038659e7SLuis R. Rodriguez 838038659e7SLuis R. Rodriguez if (freq_range->max_bandwidth_khz < MHZ_TO_KHZ(40)) 839038659e7SLuis R. Rodriguez bw_flags = IEEE80211_CHAN_NO_HT40; 840b2e1b302SLuis R. Rodriguez 8417db90f4aSLuis R. Rodriguez if (last_request->initiator == NL80211_REGDOM_SET_BY_DRIVER && 842806a9e39SLuis R. Rodriguez request_wiphy && request_wiphy == wiphy && 8435be83de5SJohannes Berg request_wiphy->flags & WIPHY_FLAG_STRICT_REGULATORY) { 844fb1fc7adSLuis R. Rodriguez /* 845fb1fc7adSLuis R. Rodriguez * This gaurantees the driver's requested regulatory domain 846f976376dSLuis R. Rodriguez * will always be used as a base for further regulatory 847fb1fc7adSLuis R. Rodriguez * settings 848fb1fc7adSLuis R. Rodriguez */ 849f976376dSLuis R. Rodriguez chan->flags = chan->orig_flags = 850038659e7SLuis R. Rodriguez map_regdom_flags(reg_rule->flags) | bw_flags; 851f976376dSLuis R. Rodriguez chan->max_antenna_gain = chan->orig_mag = 852f976376dSLuis R. Rodriguez (int) MBI_TO_DBI(power_rule->max_antenna_gain); 853f976376dSLuis R. Rodriguez chan->max_power = chan->orig_mpwr = 854f976376dSLuis R. Rodriguez (int) MBM_TO_DBM(power_rule->max_eirp); 855f976376dSLuis R. Rodriguez return; 856f976376dSLuis R. Rodriguez } 857f976376dSLuis R. Rodriguez 858038659e7SLuis R. Rodriguez chan->flags = flags | bw_flags | map_regdom_flags(reg_rule->flags); 8598318d78aSJohannes Berg chan->max_antenna_gain = min(chan->orig_mag, 860b2e1b302SLuis R. Rodriguez (int) MBI_TO_DBI(power_rule->max_antenna_gain)); 861253898c4SJohn W. Linville if (chan->orig_mpwr) 862b2e1b302SLuis R. Rodriguez chan->max_power = min(chan->orig_mpwr, 863b2e1b302SLuis R. Rodriguez (int) MBM_TO_DBM(power_rule->max_eirp)); 864253898c4SJohn W. Linville else 865b2e1b302SLuis R. Rodriguez chan->max_power = (int) MBM_TO_DBM(power_rule->max_eirp); 8668318d78aSJohannes Berg } 8678318d78aSJohannes Berg 8687ca43d03SLuis R. Rodriguez static void handle_band(struct wiphy *wiphy, 8697ca43d03SLuis R. Rodriguez enum ieee80211_band band, 8707ca43d03SLuis R. Rodriguez enum nl80211_reg_initiator initiator) 8718318d78aSJohannes Berg { 872a92a3ce7SLuis R. Rodriguez unsigned int i; 873a92a3ce7SLuis R. Rodriguez struct ieee80211_supported_band *sband; 874a92a3ce7SLuis R. Rodriguez 875a92a3ce7SLuis R. Rodriguez BUG_ON(!wiphy->bands[band]); 876a92a3ce7SLuis R. Rodriguez sband = wiphy->bands[band]; 8778318d78aSJohannes Berg 8788318d78aSJohannes Berg for (i = 0; i < sband->n_channels; i++) 8797ca43d03SLuis R. Rodriguez handle_channel(wiphy, initiator, band, i); 8808318d78aSJohannes Berg } 8818318d78aSJohannes Berg 8827db90f4aSLuis R. Rodriguez static bool ignore_reg_update(struct wiphy *wiphy, 8837db90f4aSLuis R. Rodriguez enum nl80211_reg_initiator initiator) 88414b9815aSLuis R. Rodriguez { 885926a0a09SLuis R. Rodriguez if (!last_request) { 886d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Ignoring regulatory request %s since " 887926a0a09SLuis R. Rodriguez "last_request is not set\n", 888926a0a09SLuis R. Rodriguez reg_initiator_name(initiator)); 88914b9815aSLuis R. Rodriguez return true; 890926a0a09SLuis R. Rodriguez } 891926a0a09SLuis R. Rodriguez 8927db90f4aSLuis R. Rodriguez if (initiator == NL80211_REGDOM_SET_BY_CORE && 893926a0a09SLuis R. Rodriguez wiphy->flags & WIPHY_FLAG_CUSTOM_REGULATORY) { 894d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Ignoring regulatory request %s " 895926a0a09SLuis R. Rodriguez "since the driver uses its own custom " 896926a0a09SLuis R. Rodriguez "regulatory domain ", 897926a0a09SLuis R. Rodriguez reg_initiator_name(initiator)); 89814b9815aSLuis R. Rodriguez return true; 899926a0a09SLuis R. Rodriguez } 900926a0a09SLuis R. Rodriguez 901fb1fc7adSLuis R. Rodriguez /* 902fb1fc7adSLuis R. Rodriguez * wiphy->regd will be set once the device has its own 903fb1fc7adSLuis R. Rodriguez * desired regulatory domain set 904fb1fc7adSLuis R. Rodriguez */ 9055be83de5SJohannes Berg if (wiphy->flags & WIPHY_FLAG_STRICT_REGULATORY && !wiphy->regd && 906749b527bSLuis R. Rodriguez initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE && 907926a0a09SLuis R. Rodriguez !is_world_regdom(last_request->alpha2)) { 908d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Ignoring regulatory request %s " 909926a0a09SLuis R. Rodriguez "since the driver requires its own regulaotry " 910926a0a09SLuis R. Rodriguez "domain to be set first", 911926a0a09SLuis R. Rodriguez reg_initiator_name(initiator)); 91214b9815aSLuis R. Rodriguez return true; 913926a0a09SLuis R. Rodriguez } 914926a0a09SLuis R. Rodriguez 91514b9815aSLuis R. Rodriguez return false; 91614b9815aSLuis R. Rodriguez } 91714b9815aSLuis R. Rodriguez 9187db90f4aSLuis R. Rodriguez static void update_all_wiphy_regulatory(enum nl80211_reg_initiator initiator) 919b2e1b302SLuis R. Rodriguez { 92079c97e97SJohannes Berg struct cfg80211_registered_device *rdev; 921b2e1b302SLuis R. Rodriguez 92279c97e97SJohannes Berg list_for_each_entry(rdev, &cfg80211_rdev_list, list) 92379c97e97SJohannes Berg wiphy_update_regulatory(&rdev->wiphy, initiator); 924b2e1b302SLuis R. Rodriguez } 925b2e1b302SLuis R. Rodriguez 926e38f8a7aSLuis R. Rodriguez static void handle_reg_beacon(struct wiphy *wiphy, 927e38f8a7aSLuis R. Rodriguez unsigned int chan_idx, 928e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon) 929e38f8a7aSLuis R. Rodriguez { 930e38f8a7aSLuis R. Rodriguez struct ieee80211_supported_band *sband; 931e38f8a7aSLuis R. Rodriguez struct ieee80211_channel *chan; 9326bad8766SLuis R. Rodriguez bool channel_changed = false; 9336bad8766SLuis R. Rodriguez struct ieee80211_channel chan_before; 934e38f8a7aSLuis R. Rodriguez 935e38f8a7aSLuis R. Rodriguez assert_cfg80211_lock(); 936e38f8a7aSLuis R. Rodriguez 937e38f8a7aSLuis R. Rodriguez sband = wiphy->bands[reg_beacon->chan.band]; 938e38f8a7aSLuis R. Rodriguez chan = &sband->channels[chan_idx]; 939e38f8a7aSLuis R. Rodriguez 940e38f8a7aSLuis R. Rodriguez if (likely(chan->center_freq != reg_beacon->chan.center_freq)) 941e38f8a7aSLuis R. Rodriguez return; 942e38f8a7aSLuis R. Rodriguez 9436bad8766SLuis R. Rodriguez if (chan->beacon_found) 9446bad8766SLuis R. Rodriguez return; 9456bad8766SLuis R. Rodriguez 9466bad8766SLuis R. Rodriguez chan->beacon_found = true; 9476bad8766SLuis R. Rodriguez 9485be83de5SJohannes Berg if (wiphy->flags & WIPHY_FLAG_DISABLE_BEACON_HINTS) 94937184244SLuis R. Rodriguez return; 95037184244SLuis R. Rodriguez 9516bad8766SLuis R. Rodriguez chan_before.center_freq = chan->center_freq; 9526bad8766SLuis R. Rodriguez chan_before.flags = chan->flags; 9536bad8766SLuis R. Rodriguez 95437184244SLuis R. Rodriguez if (chan->flags & IEEE80211_CHAN_PASSIVE_SCAN) { 955e38f8a7aSLuis R. Rodriguez chan->flags &= ~IEEE80211_CHAN_PASSIVE_SCAN; 9566bad8766SLuis R. Rodriguez channel_changed = true; 957e38f8a7aSLuis R. Rodriguez } 958e38f8a7aSLuis R. Rodriguez 95937184244SLuis R. Rodriguez if (chan->flags & IEEE80211_CHAN_NO_IBSS) { 960e38f8a7aSLuis R. Rodriguez chan->flags &= ~IEEE80211_CHAN_NO_IBSS; 9616bad8766SLuis R. Rodriguez channel_changed = true; 962e38f8a7aSLuis R. Rodriguez } 963e38f8a7aSLuis R. Rodriguez 9646bad8766SLuis R. Rodriguez if (channel_changed) 9656bad8766SLuis R. Rodriguez nl80211_send_beacon_hint_event(wiphy, &chan_before, chan); 966e38f8a7aSLuis R. Rodriguez } 967e38f8a7aSLuis R. Rodriguez 968e38f8a7aSLuis R. Rodriguez /* 969e38f8a7aSLuis R. Rodriguez * Called when a scan on a wiphy finds a beacon on 970e38f8a7aSLuis R. Rodriguez * new channel 971e38f8a7aSLuis R. Rodriguez */ 972e38f8a7aSLuis R. Rodriguez static void wiphy_update_new_beacon(struct wiphy *wiphy, 973e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon) 974e38f8a7aSLuis R. Rodriguez { 975e38f8a7aSLuis R. Rodriguez unsigned int i; 976e38f8a7aSLuis R. Rodriguez struct ieee80211_supported_band *sband; 977e38f8a7aSLuis R. Rodriguez 978e38f8a7aSLuis R. Rodriguez assert_cfg80211_lock(); 979e38f8a7aSLuis R. Rodriguez 980e38f8a7aSLuis R. Rodriguez if (!wiphy->bands[reg_beacon->chan.band]) 981e38f8a7aSLuis R. Rodriguez return; 982e38f8a7aSLuis R. Rodriguez 983e38f8a7aSLuis R. Rodriguez sband = wiphy->bands[reg_beacon->chan.band]; 984e38f8a7aSLuis R. Rodriguez 985e38f8a7aSLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 986e38f8a7aSLuis R. Rodriguez handle_reg_beacon(wiphy, i, reg_beacon); 987e38f8a7aSLuis R. Rodriguez } 988e38f8a7aSLuis R. Rodriguez 989e38f8a7aSLuis R. Rodriguez /* 990e38f8a7aSLuis R. Rodriguez * Called upon reg changes or a new wiphy is added 991e38f8a7aSLuis R. Rodriguez */ 992e38f8a7aSLuis R. Rodriguez static void wiphy_update_beacon_reg(struct wiphy *wiphy) 993e38f8a7aSLuis R. Rodriguez { 994e38f8a7aSLuis R. Rodriguez unsigned int i; 995e38f8a7aSLuis R. Rodriguez struct ieee80211_supported_band *sband; 996e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon; 997e38f8a7aSLuis R. Rodriguez 998e38f8a7aSLuis R. Rodriguez assert_cfg80211_lock(); 999e38f8a7aSLuis R. Rodriguez 1000e38f8a7aSLuis R. Rodriguez if (list_empty(®_beacon_list)) 1001e38f8a7aSLuis R. Rodriguez return; 1002e38f8a7aSLuis R. Rodriguez 1003e38f8a7aSLuis R. Rodriguez list_for_each_entry(reg_beacon, ®_beacon_list, list) { 1004e38f8a7aSLuis R. Rodriguez if (!wiphy->bands[reg_beacon->chan.band]) 1005e38f8a7aSLuis R. Rodriguez continue; 1006e38f8a7aSLuis R. Rodriguez sband = wiphy->bands[reg_beacon->chan.band]; 1007e38f8a7aSLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 1008e38f8a7aSLuis R. Rodriguez handle_reg_beacon(wiphy, i, reg_beacon); 1009e38f8a7aSLuis R. Rodriguez } 1010e38f8a7aSLuis R. Rodriguez } 1011e38f8a7aSLuis R. Rodriguez 1012e38f8a7aSLuis R. Rodriguez static bool reg_is_world_roaming(struct wiphy *wiphy) 1013e38f8a7aSLuis R. Rodriguez { 1014e38f8a7aSLuis R. Rodriguez if (is_world_regdom(cfg80211_regdomain->alpha2) || 1015e38f8a7aSLuis R. Rodriguez (wiphy->regd && is_world_regdom(wiphy->regd->alpha2))) 1016e38f8a7aSLuis R. Rodriguez return true; 1017b1ed8dddSLuis R. Rodriguez if (last_request && 1018b1ed8dddSLuis R. Rodriguez last_request->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE && 10195be83de5SJohannes Berg wiphy->flags & WIPHY_FLAG_CUSTOM_REGULATORY) 1020e38f8a7aSLuis R. Rodriguez return true; 1021e38f8a7aSLuis R. Rodriguez return false; 1022e38f8a7aSLuis R. Rodriguez } 1023e38f8a7aSLuis R. Rodriguez 1024e38f8a7aSLuis R. Rodriguez /* Reap the advantages of previously found beacons */ 1025e38f8a7aSLuis R. Rodriguez static void reg_process_beacons(struct wiphy *wiphy) 1026e38f8a7aSLuis R. Rodriguez { 1027b1ed8dddSLuis R. Rodriguez /* 1028b1ed8dddSLuis R. Rodriguez * Means we are just firing up cfg80211, so no beacons would 1029b1ed8dddSLuis R. Rodriguez * have been processed yet. 1030b1ed8dddSLuis R. Rodriguez */ 1031b1ed8dddSLuis R. Rodriguez if (!last_request) 1032b1ed8dddSLuis R. Rodriguez return; 1033e38f8a7aSLuis R. Rodriguez if (!reg_is_world_roaming(wiphy)) 1034e38f8a7aSLuis R. Rodriguez return; 1035e38f8a7aSLuis R. Rodriguez wiphy_update_beacon_reg(wiphy); 1036e38f8a7aSLuis R. Rodriguez } 1037e38f8a7aSLuis R. Rodriguez 1038038659e7SLuis R. Rodriguez static bool is_ht40_not_allowed(struct ieee80211_channel *chan) 1039038659e7SLuis R. Rodriguez { 1040038659e7SLuis R. Rodriguez if (!chan) 1041038659e7SLuis R. Rodriguez return true; 1042038659e7SLuis R. Rodriguez if (chan->flags & IEEE80211_CHAN_DISABLED) 1043038659e7SLuis R. Rodriguez return true; 1044038659e7SLuis R. Rodriguez /* This would happen when regulatory rules disallow HT40 completely */ 1045038659e7SLuis R. Rodriguez if (IEEE80211_CHAN_NO_HT40 == (chan->flags & (IEEE80211_CHAN_NO_HT40))) 1046038659e7SLuis R. Rodriguez return true; 1047038659e7SLuis R. Rodriguez return false; 1048038659e7SLuis R. Rodriguez } 1049038659e7SLuis R. Rodriguez 1050038659e7SLuis R. Rodriguez static void reg_process_ht_flags_channel(struct wiphy *wiphy, 1051038659e7SLuis R. Rodriguez enum ieee80211_band band, 1052038659e7SLuis R. Rodriguez unsigned int chan_idx) 1053038659e7SLuis R. Rodriguez { 1054038659e7SLuis R. Rodriguez struct ieee80211_supported_band *sband; 1055038659e7SLuis R. Rodriguez struct ieee80211_channel *channel; 1056038659e7SLuis R. Rodriguez struct ieee80211_channel *channel_before = NULL, *channel_after = NULL; 1057038659e7SLuis R. Rodriguez unsigned int i; 1058038659e7SLuis R. Rodriguez 1059038659e7SLuis R. Rodriguez assert_cfg80211_lock(); 1060038659e7SLuis R. Rodriguez 1061038659e7SLuis R. Rodriguez sband = wiphy->bands[band]; 1062038659e7SLuis R. Rodriguez BUG_ON(chan_idx >= sband->n_channels); 1063038659e7SLuis R. Rodriguez channel = &sband->channels[chan_idx]; 1064038659e7SLuis R. Rodriguez 1065038659e7SLuis R. Rodriguez if (is_ht40_not_allowed(channel)) { 1066038659e7SLuis R. Rodriguez channel->flags |= IEEE80211_CHAN_NO_HT40; 1067038659e7SLuis R. Rodriguez return; 1068038659e7SLuis R. Rodriguez } 1069038659e7SLuis R. Rodriguez 1070038659e7SLuis R. Rodriguez /* 1071038659e7SLuis R. Rodriguez * We need to ensure the extension channels exist to 1072038659e7SLuis R. Rodriguez * be able to use HT40- or HT40+, this finds them (or not) 1073038659e7SLuis R. Rodriguez */ 1074038659e7SLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) { 1075038659e7SLuis R. Rodriguez struct ieee80211_channel *c = &sband->channels[i]; 1076038659e7SLuis R. Rodriguez if (c->center_freq == (channel->center_freq - 20)) 1077038659e7SLuis R. Rodriguez channel_before = c; 1078038659e7SLuis R. Rodriguez if (c->center_freq == (channel->center_freq + 20)) 1079038659e7SLuis R. Rodriguez channel_after = c; 1080038659e7SLuis R. Rodriguez } 1081038659e7SLuis R. Rodriguez 1082038659e7SLuis R. Rodriguez /* 1083038659e7SLuis R. Rodriguez * Please note that this assumes target bandwidth is 20 MHz, 1084038659e7SLuis R. Rodriguez * if that ever changes we also need to change the below logic 1085038659e7SLuis R. Rodriguez * to include that as well. 1086038659e7SLuis R. Rodriguez */ 1087038659e7SLuis R. Rodriguez if (is_ht40_not_allowed(channel_before)) 1088689da1b3SLuis R. Rodriguez channel->flags |= IEEE80211_CHAN_NO_HT40MINUS; 1089038659e7SLuis R. Rodriguez else 1090689da1b3SLuis R. Rodriguez channel->flags &= ~IEEE80211_CHAN_NO_HT40MINUS; 1091038659e7SLuis R. Rodriguez 1092038659e7SLuis R. Rodriguez if (is_ht40_not_allowed(channel_after)) 1093689da1b3SLuis R. Rodriguez channel->flags |= IEEE80211_CHAN_NO_HT40PLUS; 1094038659e7SLuis R. Rodriguez else 1095689da1b3SLuis R. Rodriguez channel->flags &= ~IEEE80211_CHAN_NO_HT40PLUS; 1096038659e7SLuis R. Rodriguez } 1097038659e7SLuis R. Rodriguez 1098038659e7SLuis R. Rodriguez static void reg_process_ht_flags_band(struct wiphy *wiphy, 1099038659e7SLuis R. Rodriguez enum ieee80211_band band) 1100038659e7SLuis R. Rodriguez { 1101038659e7SLuis R. Rodriguez unsigned int i; 1102038659e7SLuis R. Rodriguez struct ieee80211_supported_band *sband; 1103038659e7SLuis R. Rodriguez 1104038659e7SLuis R. Rodriguez BUG_ON(!wiphy->bands[band]); 1105038659e7SLuis R. Rodriguez sband = wiphy->bands[band]; 1106038659e7SLuis R. Rodriguez 1107038659e7SLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 1108038659e7SLuis R. Rodriguez reg_process_ht_flags_channel(wiphy, band, i); 1109038659e7SLuis R. Rodriguez } 1110038659e7SLuis R. Rodriguez 1111038659e7SLuis R. Rodriguez static void reg_process_ht_flags(struct wiphy *wiphy) 1112038659e7SLuis R. Rodriguez { 1113038659e7SLuis R. Rodriguez enum ieee80211_band band; 1114038659e7SLuis R. Rodriguez 1115038659e7SLuis R. Rodriguez if (!wiphy) 1116038659e7SLuis R. Rodriguez return; 1117038659e7SLuis R. Rodriguez 1118038659e7SLuis R. Rodriguez for (band = 0; band < IEEE80211_NUM_BANDS; band++) { 1119038659e7SLuis R. Rodriguez if (wiphy->bands[band]) 1120038659e7SLuis R. Rodriguez reg_process_ht_flags_band(wiphy, band); 1121038659e7SLuis R. Rodriguez } 1122038659e7SLuis R. Rodriguez 1123038659e7SLuis R. Rodriguez } 1124038659e7SLuis R. Rodriguez 11257db90f4aSLuis R. Rodriguez void wiphy_update_regulatory(struct wiphy *wiphy, 11267db90f4aSLuis R. Rodriguez enum nl80211_reg_initiator initiator) 11278318d78aSJohannes Berg { 11288318d78aSJohannes Berg enum ieee80211_band band; 1129d46e5b1dSLuis R. Rodriguez 11307db90f4aSLuis R. Rodriguez if (ignore_reg_update(wiphy, initiator)) 1131e38f8a7aSLuis R. Rodriguez goto out; 1132b2e1b302SLuis R. Rodriguez for (band = 0; band < IEEE80211_NUM_BANDS; band++) { 11338318d78aSJohannes Berg if (wiphy->bands[band]) 11347ca43d03SLuis R. Rodriguez handle_band(wiphy, band, initiator); 1135b2e1b302SLuis R. Rodriguez } 1136e38f8a7aSLuis R. Rodriguez out: 1137e38f8a7aSLuis R. Rodriguez reg_process_beacons(wiphy); 1138038659e7SLuis R. Rodriguez reg_process_ht_flags(wiphy); 1139b2e1b302SLuis R. Rodriguez if (wiphy->reg_notifier) 1140716f9392SLuis R. Rodriguez wiphy->reg_notifier(wiphy, last_request); 1141b2e1b302SLuis R. Rodriguez } 1142b2e1b302SLuis R. Rodriguez 11431fa25e41SLuis R. Rodriguez static void handle_channel_custom(struct wiphy *wiphy, 11441fa25e41SLuis R. Rodriguez enum ieee80211_band band, 11451fa25e41SLuis R. Rodriguez unsigned int chan_idx, 11461fa25e41SLuis R. Rodriguez const struct ieee80211_regdomain *regd) 11471fa25e41SLuis R. Rodriguez { 11481fa25e41SLuis R. Rodriguez int r; 1149038659e7SLuis R. Rodriguez u32 desired_bw_khz = MHZ_TO_KHZ(20); 1150038659e7SLuis R. Rodriguez u32 bw_flags = 0; 11511fa25e41SLuis R. Rodriguez const struct ieee80211_reg_rule *reg_rule = NULL; 11521fa25e41SLuis R. Rodriguez const struct ieee80211_power_rule *power_rule = NULL; 1153038659e7SLuis R. Rodriguez const struct ieee80211_freq_range *freq_range = NULL; 11541fa25e41SLuis R. Rodriguez struct ieee80211_supported_band *sband; 11551fa25e41SLuis R. Rodriguez struct ieee80211_channel *chan; 11561fa25e41SLuis R. Rodriguez 1157abc7381bSLuis R. Rodriguez assert_reg_lock(); 1158ac46d48eSLuis R. Rodriguez 11591fa25e41SLuis R. Rodriguez sband = wiphy->bands[band]; 11601fa25e41SLuis R. Rodriguez BUG_ON(chan_idx >= sband->n_channels); 11611fa25e41SLuis R. Rodriguez chan = &sband->channels[chan_idx]; 11621fa25e41SLuis R. Rodriguez 1163038659e7SLuis R. Rodriguez r = freq_reg_info_regd(wiphy, 1164038659e7SLuis R. Rodriguez MHZ_TO_KHZ(chan->center_freq), 1165038659e7SLuis R. Rodriguez desired_bw_khz, 1166038659e7SLuis R. Rodriguez ®_rule, 1167038659e7SLuis R. Rodriguez regd); 11681fa25e41SLuis R. Rodriguez 11691fa25e41SLuis R. Rodriguez if (r) { 1170d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Disabling freq %d MHz as custom " 1171a6518536SLuis R. Rodriguez "regd has no rule that fits a %d MHz " 1172a6518536SLuis R. Rodriguez "wide channel\n", 1173a6518536SLuis R. Rodriguez chan->center_freq, 1174a6518536SLuis R. Rodriguez KHZ_TO_MHZ(desired_bw_khz)); 11751fa25e41SLuis R. Rodriguez chan->flags = IEEE80211_CHAN_DISABLED; 11761fa25e41SLuis R. Rodriguez return; 11771fa25e41SLuis R. Rodriguez } 11781fa25e41SLuis R. Rodriguez 1179e702d3cfSLuis R. Rodriguez chan_reg_rule_print_dbg(chan, desired_bw_khz, reg_rule); 1180e702d3cfSLuis R. Rodriguez 11811fa25e41SLuis R. Rodriguez power_rule = ®_rule->power_rule; 1182038659e7SLuis R. Rodriguez freq_range = ®_rule->freq_range; 11831fa25e41SLuis R. Rodriguez 1184038659e7SLuis R. Rodriguez if (freq_range->max_bandwidth_khz < MHZ_TO_KHZ(40)) 1185038659e7SLuis R. Rodriguez bw_flags = IEEE80211_CHAN_NO_HT40; 1186038659e7SLuis R. Rodriguez 1187038659e7SLuis R. Rodriguez chan->flags |= map_regdom_flags(reg_rule->flags) | bw_flags; 11881fa25e41SLuis R. Rodriguez chan->max_antenna_gain = (int) MBI_TO_DBI(power_rule->max_antenna_gain); 11891fa25e41SLuis R. Rodriguez chan->max_power = (int) MBM_TO_DBM(power_rule->max_eirp); 11901fa25e41SLuis R. Rodriguez } 11911fa25e41SLuis R. Rodriguez 11921fa25e41SLuis R. Rodriguez static void handle_band_custom(struct wiphy *wiphy, enum ieee80211_band band, 11931fa25e41SLuis R. Rodriguez const struct ieee80211_regdomain *regd) 11941fa25e41SLuis R. Rodriguez { 11951fa25e41SLuis R. Rodriguez unsigned int i; 11961fa25e41SLuis R. Rodriguez struct ieee80211_supported_band *sband; 11971fa25e41SLuis R. Rodriguez 11981fa25e41SLuis R. Rodriguez BUG_ON(!wiphy->bands[band]); 11991fa25e41SLuis R. Rodriguez sband = wiphy->bands[band]; 12001fa25e41SLuis R. Rodriguez 12011fa25e41SLuis R. Rodriguez for (i = 0; i < sband->n_channels; i++) 12021fa25e41SLuis R. Rodriguez handle_channel_custom(wiphy, band, i, regd); 12031fa25e41SLuis R. Rodriguez } 12041fa25e41SLuis R. Rodriguez 12051fa25e41SLuis R. Rodriguez /* Used by drivers prior to wiphy registration */ 12061fa25e41SLuis R. Rodriguez void wiphy_apply_custom_regulatory(struct wiphy *wiphy, 12071fa25e41SLuis R. Rodriguez const struct ieee80211_regdomain *regd) 12081fa25e41SLuis R. Rodriguez { 12091fa25e41SLuis R. Rodriguez enum ieee80211_band band; 1210bbcf3f02SLuis R. Rodriguez unsigned int bands_set = 0; 1211ac46d48eSLuis R. Rodriguez 1212abc7381bSLuis R. Rodriguez mutex_lock(®_mutex); 12131fa25e41SLuis R. Rodriguez for (band = 0; band < IEEE80211_NUM_BANDS; band++) { 1214bbcf3f02SLuis R. Rodriguez if (!wiphy->bands[band]) 1215bbcf3f02SLuis R. Rodriguez continue; 12161fa25e41SLuis R. Rodriguez handle_band_custom(wiphy, band, regd); 1217bbcf3f02SLuis R. Rodriguez bands_set++; 12181fa25e41SLuis R. Rodriguez } 1219abc7381bSLuis R. Rodriguez mutex_unlock(®_mutex); 1220bbcf3f02SLuis R. Rodriguez 1221bbcf3f02SLuis R. Rodriguez /* 1222bbcf3f02SLuis R. Rodriguez * no point in calling this if it won't have any effect 1223bbcf3f02SLuis R. Rodriguez * on your device's supportd bands. 1224bbcf3f02SLuis R. Rodriguez */ 1225bbcf3f02SLuis R. Rodriguez WARN_ON(!bands_set); 12261fa25e41SLuis R. Rodriguez } 12271fa25e41SLuis R. Rodriguez EXPORT_SYMBOL(wiphy_apply_custom_regulatory); 12281fa25e41SLuis R. Rodriguez 1229fb1fc7adSLuis R. Rodriguez /* 1230fb1fc7adSLuis R. Rodriguez * Return value which can be used by ignore_request() to indicate 1231fb1fc7adSLuis R. Rodriguez * it has been determined we should intersect two regulatory domains 1232fb1fc7adSLuis R. Rodriguez */ 12339c96477dSLuis R. Rodriguez #define REG_INTERSECT 1 12349c96477dSLuis R. Rodriguez 123584fa4f43SJohannes Berg /* This has the logic which determines when a new request 123684fa4f43SJohannes Berg * should be ignored. */ 12372f92cd2eSLuis R. Rodriguez static int ignore_request(struct wiphy *wiphy, 12382f92cd2eSLuis R. Rodriguez struct regulatory_request *pending_request) 123984fa4f43SJohannes Berg { 1240806a9e39SLuis R. Rodriguez struct wiphy *last_wiphy = NULL; 1241761cf7ecSLuis R. Rodriguez 1242761cf7ecSLuis R. Rodriguez assert_cfg80211_lock(); 1243761cf7ecSLuis R. Rodriguez 124484fa4f43SJohannes Berg /* All initial requests are respected */ 124584fa4f43SJohannes Berg if (!last_request) 124684fa4f43SJohannes Berg return 0; 124784fa4f43SJohannes Berg 12482f92cd2eSLuis R. Rodriguez switch (pending_request->initiator) { 12497db90f4aSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_CORE: 125009d989d1SLuis R. Rodriguez return 0; 12517db90f4aSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_COUNTRY_IE: 1252806a9e39SLuis R. Rodriguez 1253806a9e39SLuis R. Rodriguez last_wiphy = wiphy_idx_to_wiphy(last_request->wiphy_idx); 1254806a9e39SLuis R. Rodriguez 12552f92cd2eSLuis R. Rodriguez if (unlikely(!is_an_alpha2(pending_request->alpha2))) 125684fa4f43SJohannes Berg return -EINVAL; 12577db90f4aSLuis R. Rodriguez if (last_request->initiator == 12587db90f4aSLuis R. Rodriguez NL80211_REGDOM_SET_BY_COUNTRY_IE) { 1259806a9e39SLuis R. Rodriguez if (last_wiphy != wiphy) { 126084fa4f43SJohannes Berg /* 126184fa4f43SJohannes Berg * Two cards with two APs claiming different 12621fe90b03SThadeu Lima de Souza Cascardo * Country IE alpha2s. We could 126384fa4f43SJohannes Berg * intersect them, but that seems unlikely 126484fa4f43SJohannes Berg * to be correct. Reject second one for now. 126584fa4f43SJohannes Berg */ 12662f92cd2eSLuis R. Rodriguez if (regdom_changes(pending_request->alpha2)) 126784fa4f43SJohannes Berg return -EOPNOTSUPP; 126884fa4f43SJohannes Berg return -EALREADY; 126984fa4f43SJohannes Berg } 1270fb1fc7adSLuis R. Rodriguez /* 1271fb1fc7adSLuis R. Rodriguez * Two consecutive Country IE hints on the same wiphy. 1272fb1fc7adSLuis R. Rodriguez * This should be picked up early by the driver/stack 1273fb1fc7adSLuis R. Rodriguez */ 12742f92cd2eSLuis R. Rodriguez if (WARN_ON(regdom_changes(pending_request->alpha2))) 127584fa4f43SJohannes Berg return 0; 127684fa4f43SJohannes Berg return -EALREADY; 127784fa4f43SJohannes Berg } 1278a171fba4SLuis R. Rodriguez return 0; 12797db90f4aSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_DRIVER: 12807db90f4aSLuis R. Rodriguez if (last_request->initiator == NL80211_REGDOM_SET_BY_CORE) { 12812f92cd2eSLuis R. Rodriguez if (regdom_changes(pending_request->alpha2)) 1282e74b1e7fSLuis R. Rodriguez return 0; 1283e74b1e7fSLuis R. Rodriguez return -EALREADY; 1284e74b1e7fSLuis R. Rodriguez } 1285fff32c04SLuis R. Rodriguez 1286fff32c04SLuis R. Rodriguez /* 1287fff32c04SLuis R. Rodriguez * This would happen if you unplug and plug your card 1288fff32c04SLuis R. Rodriguez * back in or if you add a new device for which the previously 1289fff32c04SLuis R. Rodriguez * loaded card also agrees on the regulatory domain. 1290fff32c04SLuis R. Rodriguez */ 12917db90f4aSLuis R. Rodriguez if (last_request->initiator == NL80211_REGDOM_SET_BY_DRIVER && 12922f92cd2eSLuis R. Rodriguez !regdom_changes(pending_request->alpha2)) 1293fff32c04SLuis R. Rodriguez return -EALREADY; 1294fff32c04SLuis R. Rodriguez 12953e0c3ff3SLuis R. Rodriguez return REG_INTERSECT; 12967db90f4aSLuis R. Rodriguez case NL80211_REGDOM_SET_BY_USER: 12977db90f4aSLuis R. Rodriguez if (last_request->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE) 12989c96477dSLuis R. Rodriguez return REG_INTERSECT; 1299fb1fc7adSLuis R. Rodriguez /* 1300fb1fc7adSLuis R. Rodriguez * If the user knows better the user should set the regdom 1301fb1fc7adSLuis R. Rodriguez * to their country before the IE is picked up 1302fb1fc7adSLuis R. Rodriguez */ 13037db90f4aSLuis R. Rodriguez if (last_request->initiator == NL80211_REGDOM_SET_BY_USER && 13043f2355cbSLuis R. Rodriguez last_request->intersect) 13053f2355cbSLuis R. Rodriguez return -EOPNOTSUPP; 1306fb1fc7adSLuis R. Rodriguez /* 1307fb1fc7adSLuis R. Rodriguez * Process user requests only after previous user/driver/core 1308fb1fc7adSLuis R. Rodriguez * requests have been processed 1309fb1fc7adSLuis R. Rodriguez */ 13107db90f4aSLuis R. Rodriguez if (last_request->initiator == NL80211_REGDOM_SET_BY_CORE || 13117db90f4aSLuis R. Rodriguez last_request->initiator == NL80211_REGDOM_SET_BY_DRIVER || 13127db90f4aSLuis R. Rodriguez last_request->initiator == NL80211_REGDOM_SET_BY_USER) { 131369b1572bSLuis R. Rodriguez if (regdom_changes(last_request->alpha2)) 13145eebade6SLuis R. Rodriguez return -EAGAIN; 13155eebade6SLuis R. Rodriguez } 13165eebade6SLuis R. Rodriguez 1317baeb66feSJohn W. Linville if (!regdom_changes(pending_request->alpha2)) 1318e74b1e7fSLuis R. Rodriguez return -EALREADY; 1319e74b1e7fSLuis R. Rodriguez 132084fa4f43SJohannes Berg return 0; 132184fa4f43SJohannes Berg } 132284fa4f43SJohannes Berg 132384fa4f43SJohannes Berg return -EINVAL; 132484fa4f43SJohannes Berg } 132584fa4f43SJohannes Berg 1326b2e253cfSLuis R. Rodriguez static void reg_set_request_processed(void) 1327b2e253cfSLuis R. Rodriguez { 1328b2e253cfSLuis R. Rodriguez bool need_more_processing = false; 1329b2e253cfSLuis R. Rodriguez 1330b2e253cfSLuis R. Rodriguez last_request->processed = true; 1331b2e253cfSLuis R. Rodriguez 1332b2e253cfSLuis R. Rodriguez spin_lock(®_requests_lock); 1333b2e253cfSLuis R. Rodriguez if (!list_empty(®_requests_list)) 1334b2e253cfSLuis R. Rodriguez need_more_processing = true; 1335b2e253cfSLuis R. Rodriguez spin_unlock(®_requests_lock); 1336b2e253cfSLuis R. Rodriguez 1337b2e253cfSLuis R. Rodriguez if (need_more_processing) 1338b2e253cfSLuis R. Rodriguez schedule_work(®_work); 1339b2e253cfSLuis R. Rodriguez } 1340b2e253cfSLuis R. Rodriguez 1341d1c96a9aSLuis R. Rodriguez /** 1342d1c96a9aSLuis R. Rodriguez * __regulatory_hint - hint to the wireless core a regulatory domain 1343d1c96a9aSLuis R. Rodriguez * @wiphy: if the hint comes from country information from an AP, this 1344d1c96a9aSLuis R. Rodriguez * is required to be set to the wiphy that received the information 134528da32d7SLuis R. Rodriguez * @pending_request: the regulatory request currently being processed 1346d1c96a9aSLuis R. Rodriguez * 1347d1c96a9aSLuis R. Rodriguez * The Wireless subsystem can use this function to hint to the wireless core 134828da32d7SLuis R. Rodriguez * what it believes should be the current regulatory domain. 1349d1c96a9aSLuis R. Rodriguez * 1350d1c96a9aSLuis R. Rodriguez * Returns zero if all went fine, %-EALREADY if a regulatory domain had 1351d1c96a9aSLuis R. Rodriguez * already been set or other standard error codes. 1352d1c96a9aSLuis R. Rodriguez * 1353abc7381bSLuis R. Rodriguez * Caller must hold &cfg80211_mutex and ®_mutex 1354d1c96a9aSLuis R. Rodriguez */ 135528da32d7SLuis R. Rodriguez static int __regulatory_hint(struct wiphy *wiphy, 135628da32d7SLuis R. Rodriguez struct regulatory_request *pending_request) 1357b2e1b302SLuis R. Rodriguez { 13589c96477dSLuis R. Rodriguez bool intersect = false; 1359b2e1b302SLuis R. Rodriguez int r = 0; 1360b2e1b302SLuis R. Rodriguez 1361761cf7ecSLuis R. Rodriguez assert_cfg80211_lock(); 1362761cf7ecSLuis R. Rodriguez 13632f92cd2eSLuis R. Rodriguez r = ignore_request(wiphy, pending_request); 13649c96477dSLuis R. Rodriguez 13653e0c3ff3SLuis R. Rodriguez if (r == REG_INTERSECT) { 13667db90f4aSLuis R. Rodriguez if (pending_request->initiator == 13677db90f4aSLuis R. Rodriguez NL80211_REGDOM_SET_BY_DRIVER) { 13683e0c3ff3SLuis R. Rodriguez r = reg_copy_regd(&wiphy->regd, cfg80211_regdomain); 1369d951c1ddSLuis R. Rodriguez if (r) { 1370d951c1ddSLuis R. Rodriguez kfree(pending_request); 1371b2e1b302SLuis R. Rodriguez return r; 13723e0c3ff3SLuis R. Rodriguez } 1373d951c1ddSLuis R. Rodriguez } 13743e0c3ff3SLuis R. Rodriguez intersect = true; 13753e0c3ff3SLuis R. Rodriguez } else if (r) { 1376fb1fc7adSLuis R. Rodriguez /* 1377fb1fc7adSLuis R. Rodriguez * If the regulatory domain being requested by the 13783e0c3ff3SLuis R. Rodriguez * driver has already been set just copy it to the 1379fb1fc7adSLuis R. Rodriguez * wiphy 1380fb1fc7adSLuis R. Rodriguez */ 138128da32d7SLuis R. Rodriguez if (r == -EALREADY && 13827db90f4aSLuis R. Rodriguez pending_request->initiator == 13837db90f4aSLuis R. Rodriguez NL80211_REGDOM_SET_BY_DRIVER) { 13843e0c3ff3SLuis R. Rodriguez r = reg_copy_regd(&wiphy->regd, cfg80211_regdomain); 1385d951c1ddSLuis R. Rodriguez if (r) { 1386d951c1ddSLuis R. Rodriguez kfree(pending_request); 13873e0c3ff3SLuis R. Rodriguez return r; 1388d951c1ddSLuis R. Rodriguez } 13893e0c3ff3SLuis R. Rodriguez r = -EALREADY; 13903e0c3ff3SLuis R. Rodriguez goto new_request; 13913e0c3ff3SLuis R. Rodriguez } 1392d951c1ddSLuis R. Rodriguez kfree(pending_request); 13933e0c3ff3SLuis R. Rodriguez return r; 13943e0c3ff3SLuis R. Rodriguez } 1395b2e1b302SLuis R. Rodriguez 13963e0c3ff3SLuis R. Rodriguez new_request: 1397f6037d09SJohannes Berg kfree(last_request); 1398d951c1ddSLuis R. Rodriguez 1399d951c1ddSLuis R. Rodriguez last_request = pending_request; 1400d951c1ddSLuis R. Rodriguez last_request->intersect = intersect; 1401d951c1ddSLuis R. Rodriguez 1402d951c1ddSLuis R. Rodriguez pending_request = NULL; 14033e0c3ff3SLuis R. Rodriguez 140409d989d1SLuis R. Rodriguez if (last_request->initiator == NL80211_REGDOM_SET_BY_USER) { 140509d989d1SLuis R. Rodriguez user_alpha2[0] = last_request->alpha2[0]; 140609d989d1SLuis R. Rodriguez user_alpha2[1] = last_request->alpha2[1]; 140709d989d1SLuis R. Rodriguez } 140809d989d1SLuis R. Rodriguez 14093e0c3ff3SLuis R. Rodriguez /* When r == REG_INTERSECT we do need to call CRDA */ 141073d54c9eSLuis R. Rodriguez if (r < 0) { 141173d54c9eSLuis R. Rodriguez /* 141273d54c9eSLuis R. Rodriguez * Since CRDA will not be called in this case as we already 141373d54c9eSLuis R. Rodriguez * have applied the requested regulatory domain before we just 141473d54c9eSLuis R. Rodriguez * inform userspace we have processed the request 141573d54c9eSLuis R. Rodriguez */ 1416b2e253cfSLuis R. Rodriguez if (r == -EALREADY) { 141773d54c9eSLuis R. Rodriguez nl80211_send_reg_change_event(last_request); 1418b2e253cfSLuis R. Rodriguez reg_set_request_processed(); 1419b2e253cfSLuis R. Rodriguez } 14203e0c3ff3SLuis R. Rodriguez return r; 142173d54c9eSLuis R. Rodriguez } 14223e0c3ff3SLuis R. Rodriguez 1423d951c1ddSLuis R. Rodriguez return call_crda(last_request->alpha2); 1424b2e1b302SLuis R. Rodriguez } 1425b2e1b302SLuis R. Rodriguez 142630a548c7SLuis R. Rodriguez /* This processes *all* regulatory hints */ 1427d951c1ddSLuis R. Rodriguez static void reg_process_hint(struct regulatory_request *reg_request) 1428fe33eb39SLuis R. Rodriguez { 1429fe33eb39SLuis R. Rodriguez int r = 0; 1430fe33eb39SLuis R. Rodriguez struct wiphy *wiphy = NULL; 1431c4c32294SYuri Ershov enum nl80211_reg_initiator initiator = reg_request->initiator; 1432fe33eb39SLuis R. Rodriguez 1433fe33eb39SLuis R. Rodriguez BUG_ON(!reg_request->alpha2); 1434fe33eb39SLuis R. Rodriguez 1435fe33eb39SLuis R. Rodriguez if (wiphy_idx_valid(reg_request->wiphy_idx)) 1436fe33eb39SLuis R. Rodriguez wiphy = wiphy_idx_to_wiphy(reg_request->wiphy_idx); 1437fe33eb39SLuis R. Rodriguez 14387db90f4aSLuis R. Rodriguez if (reg_request->initiator == NL80211_REGDOM_SET_BY_DRIVER && 1439fe33eb39SLuis R. Rodriguez !wiphy) { 1440d951c1ddSLuis R. Rodriguez kfree(reg_request); 1441b0e2880bSLuis R. Rodriguez return; 1442fe33eb39SLuis R. Rodriguez } 1443fe33eb39SLuis R. Rodriguez 144428da32d7SLuis R. Rodriguez r = __regulatory_hint(wiphy, reg_request); 1445fe33eb39SLuis R. Rodriguez /* This is required so that the orig_* parameters are saved */ 14465be83de5SJohannes Berg if (r == -EALREADY && wiphy && 14475be83de5SJohannes Berg wiphy->flags & WIPHY_FLAG_STRICT_REGULATORY) 1448c4c32294SYuri Ershov wiphy_update_regulatory(wiphy, initiator); 1449fe33eb39SLuis R. Rodriguez } 1450fe33eb39SLuis R. Rodriguez 1451b2e253cfSLuis R. Rodriguez /* 1452b2e253cfSLuis R. Rodriguez * Processes regulatory hints, this is all the NL80211_REGDOM_SET_BY_* 1453b2e253cfSLuis R. Rodriguez * Regulatory hints come on a first come first serve basis and we 1454b2e253cfSLuis R. Rodriguez * must process each one atomically. 1455b2e253cfSLuis R. Rodriguez */ 1456fe33eb39SLuis R. Rodriguez static void reg_process_pending_hints(void) 1457fe33eb39SLuis R. Rodriguez { 1458fe33eb39SLuis R. Rodriguez struct regulatory_request *reg_request; 1459fe33eb39SLuis R. Rodriguez 1460b0e2880bSLuis R. Rodriguez mutex_lock(&cfg80211_mutex); 1461b0e2880bSLuis R. Rodriguez mutex_lock(®_mutex); 1462b0e2880bSLuis R. Rodriguez 1463b2e253cfSLuis R. Rodriguez /* When last_request->processed becomes true this will be rescheduled */ 1464b2e253cfSLuis R. Rodriguez if (last_request && !last_request->processed) { 1465b2e253cfSLuis R. Rodriguez REG_DBG_PRINT("Pending regulatory request, waiting " 1466b2e253cfSLuis R. Rodriguez "for it to be processed..."); 1467b2e253cfSLuis R. Rodriguez goto out; 1468b2e253cfSLuis R. Rodriguez } 1469b2e253cfSLuis R. Rodriguez 1470fe33eb39SLuis R. Rodriguez spin_lock(®_requests_lock); 1471b2e253cfSLuis R. Rodriguez 1472b2e253cfSLuis R. Rodriguez if (list_empty(®_requests_list)) { 1473b2e253cfSLuis R. Rodriguez spin_unlock(®_requests_lock); 1474b2e253cfSLuis R. Rodriguez goto out; 1475b2e253cfSLuis R. Rodriguez } 1476b2e253cfSLuis R. Rodriguez 1477fe33eb39SLuis R. Rodriguez reg_request = list_first_entry(®_requests_list, 1478fe33eb39SLuis R. Rodriguez struct regulatory_request, 1479fe33eb39SLuis R. Rodriguez list); 1480fe33eb39SLuis R. Rodriguez list_del_init(®_request->list); 1481fe33eb39SLuis R. Rodriguez 1482d951c1ddSLuis R. Rodriguez spin_unlock(®_requests_lock); 1483b0e2880bSLuis R. Rodriguez 1484b2e253cfSLuis R. Rodriguez reg_process_hint(reg_request); 1485b2e253cfSLuis R. Rodriguez 1486b2e253cfSLuis R. Rodriguez out: 1487b0e2880bSLuis R. Rodriguez mutex_unlock(®_mutex); 1488b0e2880bSLuis R. Rodriguez mutex_unlock(&cfg80211_mutex); 1489fe33eb39SLuis R. Rodriguez } 1490fe33eb39SLuis R. Rodriguez 1491e38f8a7aSLuis R. Rodriguez /* Processes beacon hints -- this has nothing to do with country IEs */ 1492e38f8a7aSLuis R. Rodriguez static void reg_process_pending_beacon_hints(void) 1493e38f8a7aSLuis R. Rodriguez { 149479c97e97SJohannes Berg struct cfg80211_registered_device *rdev; 1495e38f8a7aSLuis R. Rodriguez struct reg_beacon *pending_beacon, *tmp; 1496e38f8a7aSLuis R. Rodriguez 1497abc7381bSLuis R. Rodriguez /* 1498abc7381bSLuis R. Rodriguez * No need to hold the reg_mutex here as we just touch wiphys 1499abc7381bSLuis R. Rodriguez * and do not read or access regulatory variables. 1500abc7381bSLuis R. Rodriguez */ 1501e38f8a7aSLuis R. Rodriguez mutex_lock(&cfg80211_mutex); 1502e38f8a7aSLuis R. Rodriguez 1503e38f8a7aSLuis R. Rodriguez /* This goes through the _pending_ beacon list */ 1504e38f8a7aSLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 1505e38f8a7aSLuis R. Rodriguez 1506e38f8a7aSLuis R. Rodriguez if (list_empty(®_pending_beacons)) { 1507e38f8a7aSLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 1508e38f8a7aSLuis R. Rodriguez goto out; 1509e38f8a7aSLuis R. Rodriguez } 1510e38f8a7aSLuis R. Rodriguez 1511e38f8a7aSLuis R. Rodriguez list_for_each_entry_safe(pending_beacon, tmp, 1512e38f8a7aSLuis R. Rodriguez ®_pending_beacons, list) { 1513e38f8a7aSLuis R. Rodriguez 1514e38f8a7aSLuis R. Rodriguez list_del_init(&pending_beacon->list); 1515e38f8a7aSLuis R. Rodriguez 1516e38f8a7aSLuis R. Rodriguez /* Applies the beacon hint to current wiphys */ 151779c97e97SJohannes Berg list_for_each_entry(rdev, &cfg80211_rdev_list, list) 151879c97e97SJohannes Berg wiphy_update_new_beacon(&rdev->wiphy, pending_beacon); 1519e38f8a7aSLuis R. Rodriguez 1520e38f8a7aSLuis R. Rodriguez /* Remembers the beacon hint for new wiphys or reg changes */ 1521e38f8a7aSLuis R. Rodriguez list_add_tail(&pending_beacon->list, ®_beacon_list); 1522e38f8a7aSLuis R. Rodriguez } 1523e38f8a7aSLuis R. Rodriguez 1524e38f8a7aSLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 1525e38f8a7aSLuis R. Rodriguez out: 1526e38f8a7aSLuis R. Rodriguez mutex_unlock(&cfg80211_mutex); 1527e38f8a7aSLuis R. Rodriguez } 1528e38f8a7aSLuis R. Rodriguez 1529fe33eb39SLuis R. Rodriguez static void reg_todo(struct work_struct *work) 1530fe33eb39SLuis R. Rodriguez { 1531fe33eb39SLuis R. Rodriguez reg_process_pending_hints(); 1532e38f8a7aSLuis R. Rodriguez reg_process_pending_beacon_hints(); 1533fe33eb39SLuis R. Rodriguez } 1534fe33eb39SLuis R. Rodriguez 1535fe33eb39SLuis R. Rodriguez static void queue_regulatory_request(struct regulatory_request *request) 1536fe33eb39SLuis R. Rodriguez { 1537c61029c7SJohn W. Linville if (isalpha(request->alpha2[0])) 1538c61029c7SJohn W. Linville request->alpha2[0] = toupper(request->alpha2[0]); 1539c61029c7SJohn W. Linville if (isalpha(request->alpha2[1])) 1540c61029c7SJohn W. Linville request->alpha2[1] = toupper(request->alpha2[1]); 1541c61029c7SJohn W. Linville 1542fe33eb39SLuis R. Rodriguez spin_lock(®_requests_lock); 1543fe33eb39SLuis R. Rodriguez list_add_tail(&request->list, ®_requests_list); 1544fe33eb39SLuis R. Rodriguez spin_unlock(®_requests_lock); 1545fe33eb39SLuis R. Rodriguez 1546fe33eb39SLuis R. Rodriguez schedule_work(®_work); 1547fe33eb39SLuis R. Rodriguez } 1548fe33eb39SLuis R. Rodriguez 154909d989d1SLuis R. Rodriguez /* 155009d989d1SLuis R. Rodriguez * Core regulatory hint -- happens during cfg80211_init() 155109d989d1SLuis R. Rodriguez * and when we restore regulatory settings. 155209d989d1SLuis R. Rodriguez */ 1553ba25c141SLuis R. Rodriguez static int regulatory_hint_core(const char *alpha2) 1554ba25c141SLuis R. Rodriguez { 1555ba25c141SLuis R. Rodriguez struct regulatory_request *request; 1556ba25c141SLuis R. Rodriguez 155709d989d1SLuis R. Rodriguez kfree(last_request); 155809d989d1SLuis R. Rodriguez last_request = NULL; 1559ba25c141SLuis R. Rodriguez 1560ba25c141SLuis R. Rodriguez request = kzalloc(sizeof(struct regulatory_request), 1561ba25c141SLuis R. Rodriguez GFP_KERNEL); 1562ba25c141SLuis R. Rodriguez if (!request) 1563ba25c141SLuis R. Rodriguez return -ENOMEM; 1564ba25c141SLuis R. Rodriguez 1565ba25c141SLuis R. Rodriguez request->alpha2[0] = alpha2[0]; 1566ba25c141SLuis R. Rodriguez request->alpha2[1] = alpha2[1]; 15677db90f4aSLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_CORE; 1568ba25c141SLuis R. Rodriguez 156931e99729SLuis R. Rodriguez queue_regulatory_request(request); 15705078b2e3SLuis R. Rodriguez 1571fe33eb39SLuis R. Rodriguez return 0; 1572ba25c141SLuis R. Rodriguez } 1573ba25c141SLuis R. Rodriguez 1574fe33eb39SLuis R. Rodriguez /* User hints */ 1575fe33eb39SLuis R. Rodriguez int regulatory_hint_user(const char *alpha2) 1576b2e1b302SLuis R. Rodriguez { 1577fe33eb39SLuis R. Rodriguez struct regulatory_request *request; 1578fe33eb39SLuis R. Rodriguez 1579be3d4810SJohannes Berg BUG_ON(!alpha2); 1580b2e1b302SLuis R. Rodriguez 1581fe33eb39SLuis R. Rodriguez request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL); 1582fe33eb39SLuis R. Rodriguez if (!request) 1583fe33eb39SLuis R. Rodriguez return -ENOMEM; 1584fe33eb39SLuis R. Rodriguez 1585fe33eb39SLuis R. Rodriguez request->wiphy_idx = WIPHY_IDX_STALE; 1586fe33eb39SLuis R. Rodriguez request->alpha2[0] = alpha2[0]; 1587fe33eb39SLuis R. Rodriguez request->alpha2[1] = alpha2[1]; 1588e12822e1SLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_USER; 1589fe33eb39SLuis R. Rodriguez 1590fe33eb39SLuis R. Rodriguez queue_regulatory_request(request); 1591fe33eb39SLuis R. Rodriguez 1592fe33eb39SLuis R. Rodriguez return 0; 1593fe33eb39SLuis R. Rodriguez } 1594fe33eb39SLuis R. Rodriguez 1595fe33eb39SLuis R. Rodriguez /* Driver hints */ 1596fe33eb39SLuis R. Rodriguez int regulatory_hint(struct wiphy *wiphy, const char *alpha2) 1597fe33eb39SLuis R. Rodriguez { 1598fe33eb39SLuis R. Rodriguez struct regulatory_request *request; 1599fe33eb39SLuis R. Rodriguez 1600fe33eb39SLuis R. Rodriguez BUG_ON(!alpha2); 1601fe33eb39SLuis R. Rodriguez BUG_ON(!wiphy); 1602fe33eb39SLuis R. Rodriguez 1603fe33eb39SLuis R. Rodriguez request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL); 1604fe33eb39SLuis R. Rodriguez if (!request) 1605fe33eb39SLuis R. Rodriguez return -ENOMEM; 1606fe33eb39SLuis R. Rodriguez 1607fe33eb39SLuis R. Rodriguez request->wiphy_idx = get_wiphy_idx(wiphy); 1608fe33eb39SLuis R. Rodriguez 1609fe33eb39SLuis R. Rodriguez /* Must have registered wiphy first */ 1610fe33eb39SLuis R. Rodriguez BUG_ON(!wiphy_idx_valid(request->wiphy_idx)); 1611fe33eb39SLuis R. Rodriguez 1612fe33eb39SLuis R. Rodriguez request->alpha2[0] = alpha2[0]; 1613fe33eb39SLuis R. Rodriguez request->alpha2[1] = alpha2[1]; 16147db90f4aSLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_DRIVER; 1615fe33eb39SLuis R. Rodriguez 1616fe33eb39SLuis R. Rodriguez queue_regulatory_request(request); 1617fe33eb39SLuis R. Rodriguez 1618fe33eb39SLuis R. Rodriguez return 0; 1619b2e1b302SLuis R. Rodriguez } 1620b2e1b302SLuis R. Rodriguez EXPORT_SYMBOL(regulatory_hint); 1621b2e1b302SLuis R. Rodriguez 16224b44c8bcSLuis R. Rodriguez /* 16234b44c8bcSLuis R. Rodriguez * We hold wdev_lock() here so we cannot hold cfg80211_mutex() and 16244b44c8bcSLuis R. Rodriguez * therefore cannot iterate over the rdev list here. 16254b44c8bcSLuis R. Rodriguez */ 16263f2355cbSLuis R. Rodriguez void regulatory_hint_11d(struct wiphy *wiphy, 162784920e3eSLuis R. Rodriguez enum ieee80211_band band, 16283f2355cbSLuis R. Rodriguez u8 *country_ie, 16293f2355cbSLuis R. Rodriguez u8 country_ie_len) 16303f2355cbSLuis R. Rodriguez { 16313f2355cbSLuis R. Rodriguez char alpha2[2]; 16323f2355cbSLuis R. Rodriguez enum environment_cap env = ENVIRON_ANY; 1633fe33eb39SLuis R. Rodriguez struct regulatory_request *request; 16343f2355cbSLuis R. Rodriguez 1635abc7381bSLuis R. Rodriguez mutex_lock(®_mutex); 16363f2355cbSLuis R. Rodriguez 16379828b017SLuis R. Rodriguez if (unlikely(!last_request)) 16389828b017SLuis R. Rodriguez goto out; 1639d335fe63SLuis R. Rodriguez 16403f2355cbSLuis R. Rodriguez /* IE len must be evenly divisible by 2 */ 16413f2355cbSLuis R. Rodriguez if (country_ie_len & 0x01) 16423f2355cbSLuis R. Rodriguez goto out; 16433f2355cbSLuis R. Rodriguez 16443f2355cbSLuis R. Rodriguez if (country_ie_len < IEEE80211_COUNTRY_IE_MIN_LEN) 16453f2355cbSLuis R. Rodriguez goto out; 16463f2355cbSLuis R. Rodriguez 16473f2355cbSLuis R. Rodriguez alpha2[0] = country_ie[0]; 16483f2355cbSLuis R. Rodriguez alpha2[1] = country_ie[1]; 16493f2355cbSLuis R. Rodriguez 16503f2355cbSLuis R. Rodriguez if (country_ie[2] == 'I') 16513f2355cbSLuis R. Rodriguez env = ENVIRON_INDOOR; 16523f2355cbSLuis R. Rodriguez else if (country_ie[2] == 'O') 16533f2355cbSLuis R. Rodriguez env = ENVIRON_OUTDOOR; 16543f2355cbSLuis R. Rodriguez 1655fb1fc7adSLuis R. Rodriguez /* 16568b19e6caSLuis R. Rodriguez * We will run this only upon a successful connection on cfg80211. 16574b44c8bcSLuis R. Rodriguez * We leave conflict resolution to the workqueue, where can hold 16584b44c8bcSLuis R. Rodriguez * cfg80211_mutex. 1659fb1fc7adSLuis R. Rodriguez */ 1660cc0b6fe8SLuis R. Rodriguez if (likely(last_request->initiator == 1661cc0b6fe8SLuis R. Rodriguez NL80211_REGDOM_SET_BY_COUNTRY_IE && 16624b44c8bcSLuis R. Rodriguez wiphy_idx_valid(last_request->wiphy_idx))) 16633f2355cbSLuis R. Rodriguez goto out; 16643f2355cbSLuis R. Rodriguez 1665fe33eb39SLuis R. Rodriguez request = kzalloc(sizeof(struct regulatory_request), GFP_KERNEL); 1666fe33eb39SLuis R. Rodriguez if (!request) 1667f9f9b6e3SDan Carpenter goto out; 1668fe33eb39SLuis R. Rodriguez 1669fe33eb39SLuis R. Rodriguez request->wiphy_idx = get_wiphy_idx(wiphy); 16704f366c5dSJohn W. Linville request->alpha2[0] = alpha2[0]; 16714f366c5dSJohn W. Linville request->alpha2[1] = alpha2[1]; 16727db90f4aSLuis R. Rodriguez request->initiator = NL80211_REGDOM_SET_BY_COUNTRY_IE; 1673fe33eb39SLuis R. Rodriguez request->country_ie_env = env; 16743f2355cbSLuis R. Rodriguez 1675abc7381bSLuis R. Rodriguez mutex_unlock(®_mutex); 1676fe33eb39SLuis R. Rodriguez 1677fe33eb39SLuis R. Rodriguez queue_regulatory_request(request); 1678fe33eb39SLuis R. Rodriguez 1679fe33eb39SLuis R. Rodriguez return; 16800441d6ffSLuis R. Rodriguez 16813f2355cbSLuis R. Rodriguez out: 1682abc7381bSLuis R. Rodriguez mutex_unlock(®_mutex); 16833f2355cbSLuis R. Rodriguez } 1684b2e1b302SLuis R. Rodriguez 168509d989d1SLuis R. Rodriguez static void restore_alpha2(char *alpha2, bool reset_user) 168609d989d1SLuis R. Rodriguez { 168709d989d1SLuis R. Rodriguez /* indicates there is no alpha2 to consider for restoration */ 168809d989d1SLuis R. Rodriguez alpha2[0] = '9'; 168909d989d1SLuis R. Rodriguez alpha2[1] = '7'; 169009d989d1SLuis R. Rodriguez 169109d989d1SLuis R. Rodriguez /* The user setting has precedence over the module parameter */ 169209d989d1SLuis R. Rodriguez if (is_user_regdom_saved()) { 169309d989d1SLuis R. Rodriguez /* Unless we're asked to ignore it and reset it */ 169409d989d1SLuis R. Rodriguez if (reset_user) { 1695d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Restoring regulatory settings " 169609d989d1SLuis R. Rodriguez "including user preference\n"); 169709d989d1SLuis R. Rodriguez user_alpha2[0] = '9'; 169809d989d1SLuis R. Rodriguez user_alpha2[1] = '7'; 169909d989d1SLuis R. Rodriguez 170009d989d1SLuis R. Rodriguez /* 170109d989d1SLuis R. Rodriguez * If we're ignoring user settings, we still need to 170209d989d1SLuis R. Rodriguez * check the module parameter to ensure we put things 170309d989d1SLuis R. Rodriguez * back as they were for a full restore. 170409d989d1SLuis R. Rodriguez */ 170509d989d1SLuis R. Rodriguez if (!is_world_regdom(ieee80211_regdom)) { 1706d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Keeping preference on " 170709d989d1SLuis R. Rodriguez "module parameter ieee80211_regdom: %c%c\n", 170809d989d1SLuis R. Rodriguez ieee80211_regdom[0], 170909d989d1SLuis R. Rodriguez ieee80211_regdom[1]); 171009d989d1SLuis R. Rodriguez alpha2[0] = ieee80211_regdom[0]; 171109d989d1SLuis R. Rodriguez alpha2[1] = ieee80211_regdom[1]; 171209d989d1SLuis R. Rodriguez } 171309d989d1SLuis R. Rodriguez } else { 1714d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Restoring regulatory settings " 171509d989d1SLuis R. Rodriguez "while preserving user preference for: %c%c\n", 171609d989d1SLuis R. Rodriguez user_alpha2[0], 171709d989d1SLuis R. Rodriguez user_alpha2[1]); 171809d989d1SLuis R. Rodriguez alpha2[0] = user_alpha2[0]; 171909d989d1SLuis R. Rodriguez alpha2[1] = user_alpha2[1]; 172009d989d1SLuis R. Rodriguez } 172109d989d1SLuis R. Rodriguez } else if (!is_world_regdom(ieee80211_regdom)) { 1722d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Keeping preference on " 172309d989d1SLuis R. Rodriguez "module parameter ieee80211_regdom: %c%c\n", 172409d989d1SLuis R. Rodriguez ieee80211_regdom[0], 172509d989d1SLuis R. Rodriguez ieee80211_regdom[1]); 172609d989d1SLuis R. Rodriguez alpha2[0] = ieee80211_regdom[0]; 172709d989d1SLuis R. Rodriguez alpha2[1] = ieee80211_regdom[1]; 172809d989d1SLuis R. Rodriguez } else 1729d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Restoring regulatory settings\n"); 173009d989d1SLuis R. Rodriguez } 173109d989d1SLuis R. Rodriguez 173209d989d1SLuis R. Rodriguez /* 173309d989d1SLuis R. Rodriguez * Restoring regulatory settings involves ingoring any 173409d989d1SLuis R. Rodriguez * possibly stale country IE information and user regulatory 173509d989d1SLuis R. Rodriguez * settings if so desired, this includes any beacon hints 173609d989d1SLuis R. Rodriguez * learned as we could have traveled outside to another country 173709d989d1SLuis R. Rodriguez * after disconnection. To restore regulatory settings we do 173809d989d1SLuis R. Rodriguez * exactly what we did at bootup: 173909d989d1SLuis R. Rodriguez * 174009d989d1SLuis R. Rodriguez * - send a core regulatory hint 174109d989d1SLuis R. Rodriguez * - send a user regulatory hint if applicable 174209d989d1SLuis R. Rodriguez * 174309d989d1SLuis R. Rodriguez * Device drivers that send a regulatory hint for a specific country 174409d989d1SLuis R. Rodriguez * keep their own regulatory domain on wiphy->regd so that does does 174509d989d1SLuis R. Rodriguez * not need to be remembered. 174609d989d1SLuis R. Rodriguez */ 174709d989d1SLuis R. Rodriguez static void restore_regulatory_settings(bool reset_user) 174809d989d1SLuis R. Rodriguez { 174909d989d1SLuis R. Rodriguez char alpha2[2]; 175009d989d1SLuis R. Rodriguez struct reg_beacon *reg_beacon, *btmp; 175109d989d1SLuis R. Rodriguez 175209d989d1SLuis R. Rodriguez mutex_lock(&cfg80211_mutex); 175309d989d1SLuis R. Rodriguez mutex_lock(®_mutex); 175409d989d1SLuis R. Rodriguez 175509d989d1SLuis R. Rodriguez reset_regdomains(); 175609d989d1SLuis R. Rodriguez restore_alpha2(alpha2, reset_user); 175709d989d1SLuis R. Rodriguez 175809d989d1SLuis R. Rodriguez /* Clear beacon hints */ 175909d989d1SLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 176009d989d1SLuis R. Rodriguez if (!list_empty(®_pending_beacons)) { 176109d989d1SLuis R. Rodriguez list_for_each_entry_safe(reg_beacon, btmp, 176209d989d1SLuis R. Rodriguez ®_pending_beacons, list) { 176309d989d1SLuis R. Rodriguez list_del(®_beacon->list); 176409d989d1SLuis R. Rodriguez kfree(reg_beacon); 176509d989d1SLuis R. Rodriguez } 176609d989d1SLuis R. Rodriguez } 176709d989d1SLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 176809d989d1SLuis R. Rodriguez 176909d989d1SLuis R. Rodriguez if (!list_empty(®_beacon_list)) { 177009d989d1SLuis R. Rodriguez list_for_each_entry_safe(reg_beacon, btmp, 177109d989d1SLuis R. Rodriguez ®_beacon_list, list) { 177209d989d1SLuis R. Rodriguez list_del(®_beacon->list); 177309d989d1SLuis R. Rodriguez kfree(reg_beacon); 177409d989d1SLuis R. Rodriguez } 177509d989d1SLuis R. Rodriguez } 177609d989d1SLuis R. Rodriguez 177709d989d1SLuis R. Rodriguez /* First restore to the basic regulatory settings */ 177809d989d1SLuis R. Rodriguez cfg80211_regdomain = cfg80211_world_regdom; 177909d989d1SLuis R. Rodriguez 178009d989d1SLuis R. Rodriguez mutex_unlock(®_mutex); 178109d989d1SLuis R. Rodriguez mutex_unlock(&cfg80211_mutex); 178209d989d1SLuis R. Rodriguez 178309d989d1SLuis R. Rodriguez regulatory_hint_core(cfg80211_regdomain->alpha2); 178409d989d1SLuis R. Rodriguez 178509d989d1SLuis R. Rodriguez /* 178609d989d1SLuis R. Rodriguez * This restores the ieee80211_regdom module parameter 178709d989d1SLuis R. Rodriguez * preference or the last user requested regulatory 178809d989d1SLuis R. Rodriguez * settings, user regulatory settings takes precedence. 178909d989d1SLuis R. Rodriguez */ 179009d989d1SLuis R. Rodriguez if (is_an_alpha2(alpha2)) 179109d989d1SLuis R. Rodriguez regulatory_hint_user(user_alpha2); 179209d989d1SLuis R. Rodriguez } 179309d989d1SLuis R. Rodriguez 179409d989d1SLuis R. Rodriguez 179509d989d1SLuis R. Rodriguez void regulatory_hint_disconnect(void) 179609d989d1SLuis R. Rodriguez { 1797d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("All devices are disconnected, going to " 179809d989d1SLuis R. Rodriguez "restore regulatory settings\n"); 179909d989d1SLuis R. Rodriguez restore_regulatory_settings(false); 180009d989d1SLuis R. Rodriguez } 180109d989d1SLuis R. Rodriguez 1802e38f8a7aSLuis R. Rodriguez static bool freq_is_chan_12_13_14(u16 freq) 1803e38f8a7aSLuis R. Rodriguez { 1804e38f8a7aSLuis R. Rodriguez if (freq == ieee80211_channel_to_frequency(12) || 1805e38f8a7aSLuis R. Rodriguez freq == ieee80211_channel_to_frequency(13) || 1806e38f8a7aSLuis R. Rodriguez freq == ieee80211_channel_to_frequency(14)) 1807e38f8a7aSLuis R. Rodriguez return true; 1808e38f8a7aSLuis R. Rodriguez return false; 1809e38f8a7aSLuis R. Rodriguez } 1810e38f8a7aSLuis R. Rodriguez 1811e38f8a7aSLuis R. Rodriguez int regulatory_hint_found_beacon(struct wiphy *wiphy, 1812e38f8a7aSLuis R. Rodriguez struct ieee80211_channel *beacon_chan, 1813e38f8a7aSLuis R. Rodriguez gfp_t gfp) 1814e38f8a7aSLuis R. Rodriguez { 1815e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon; 1816e38f8a7aSLuis R. Rodriguez 1817e38f8a7aSLuis R. Rodriguez if (likely((beacon_chan->beacon_found || 1818e38f8a7aSLuis R. Rodriguez (beacon_chan->flags & IEEE80211_CHAN_RADAR) || 1819e38f8a7aSLuis R. Rodriguez (beacon_chan->band == IEEE80211_BAND_2GHZ && 1820e38f8a7aSLuis R. Rodriguez !freq_is_chan_12_13_14(beacon_chan->center_freq))))) 1821e38f8a7aSLuis R. Rodriguez return 0; 1822e38f8a7aSLuis R. Rodriguez 1823e38f8a7aSLuis R. Rodriguez reg_beacon = kzalloc(sizeof(struct reg_beacon), gfp); 1824e38f8a7aSLuis R. Rodriguez if (!reg_beacon) 1825e38f8a7aSLuis R. Rodriguez return -ENOMEM; 1826e38f8a7aSLuis R. Rodriguez 1827d91e41b6SLuis R. Rodriguez REG_DBG_PRINT("Found new beacon on " 1828e38f8a7aSLuis R. Rodriguez "frequency: %d MHz (Ch %d) on %s\n", 1829e38f8a7aSLuis R. Rodriguez beacon_chan->center_freq, 1830e38f8a7aSLuis R. Rodriguez ieee80211_frequency_to_channel(beacon_chan->center_freq), 1831e38f8a7aSLuis R. Rodriguez wiphy_name(wiphy)); 18324113f751SLuis R. Rodriguez 1833e38f8a7aSLuis R. Rodriguez memcpy(®_beacon->chan, beacon_chan, 1834e38f8a7aSLuis R. Rodriguez sizeof(struct ieee80211_channel)); 1835e38f8a7aSLuis R. Rodriguez 1836e38f8a7aSLuis R. Rodriguez 1837e38f8a7aSLuis R. Rodriguez /* 1838e38f8a7aSLuis R. Rodriguez * Since we can be called from BH or and non-BH context 1839e38f8a7aSLuis R. Rodriguez * we must use spin_lock_bh() 1840e38f8a7aSLuis R. Rodriguez */ 1841e38f8a7aSLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 1842e38f8a7aSLuis R. Rodriguez list_add_tail(®_beacon->list, ®_pending_beacons); 1843e38f8a7aSLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 1844e38f8a7aSLuis R. Rodriguez 1845e38f8a7aSLuis R. Rodriguez schedule_work(®_work); 1846e38f8a7aSLuis R. Rodriguez 1847e38f8a7aSLuis R. Rodriguez return 0; 1848e38f8a7aSLuis R. Rodriguez } 1849e38f8a7aSLuis R. Rodriguez 1850a3d2eaf0SJohannes Berg static void print_rd_rules(const struct ieee80211_regdomain *rd) 1851b2e1b302SLuis R. Rodriguez { 1852b2e1b302SLuis R. Rodriguez unsigned int i; 1853a3d2eaf0SJohannes Berg const struct ieee80211_reg_rule *reg_rule = NULL; 1854a3d2eaf0SJohannes Berg const struct ieee80211_freq_range *freq_range = NULL; 1855a3d2eaf0SJohannes Berg const struct ieee80211_power_rule *power_rule = NULL; 1856b2e1b302SLuis R. Rodriguez 1857e9c0268fSJoe Perches pr_info(" (start_freq - end_freq @ bandwidth), (max_antenna_gain, max_eirp)\n"); 1858b2e1b302SLuis R. Rodriguez 1859b2e1b302SLuis R. Rodriguez for (i = 0; i < rd->n_reg_rules; i++) { 1860b2e1b302SLuis R. Rodriguez reg_rule = &rd->reg_rules[i]; 1861b2e1b302SLuis R. Rodriguez freq_range = ®_rule->freq_range; 1862b2e1b302SLuis R. Rodriguez power_rule = ®_rule->power_rule; 1863b2e1b302SLuis R. Rodriguez 1864fb1fc7adSLuis R. Rodriguez /* 1865fb1fc7adSLuis R. Rodriguez * There may not be documentation for max antenna gain 1866fb1fc7adSLuis R. Rodriguez * in certain regions 1867fb1fc7adSLuis R. Rodriguez */ 1868b2e1b302SLuis R. Rodriguez if (power_rule->max_antenna_gain) 1869e9c0268fSJoe Perches pr_info(" (%d KHz - %d KHz @ %d KHz), (%d mBi, %d mBm)\n", 1870b2e1b302SLuis R. Rodriguez freq_range->start_freq_khz, 1871b2e1b302SLuis R. Rodriguez freq_range->end_freq_khz, 1872b2e1b302SLuis R. Rodriguez freq_range->max_bandwidth_khz, 1873b2e1b302SLuis R. Rodriguez power_rule->max_antenna_gain, 1874b2e1b302SLuis R. Rodriguez power_rule->max_eirp); 1875b2e1b302SLuis R. Rodriguez else 1876e9c0268fSJoe Perches pr_info(" (%d KHz - %d KHz @ %d KHz), (N/A, %d mBm)\n", 1877b2e1b302SLuis R. Rodriguez freq_range->start_freq_khz, 1878b2e1b302SLuis R. Rodriguez freq_range->end_freq_khz, 1879b2e1b302SLuis R. Rodriguez freq_range->max_bandwidth_khz, 1880b2e1b302SLuis R. Rodriguez power_rule->max_eirp); 1881b2e1b302SLuis R. Rodriguez } 1882b2e1b302SLuis R. Rodriguez } 1883b2e1b302SLuis R. Rodriguez 1884a3d2eaf0SJohannes Berg static void print_regdomain(const struct ieee80211_regdomain *rd) 1885b2e1b302SLuis R. Rodriguez { 1886b2e1b302SLuis R. Rodriguez 18873f2355cbSLuis R. Rodriguez if (is_intersected_alpha2(rd->alpha2)) { 18883f2355cbSLuis R. Rodriguez 18897db90f4aSLuis R. Rodriguez if (last_request->initiator == 18907db90f4aSLuis R. Rodriguez NL80211_REGDOM_SET_BY_COUNTRY_IE) { 189179c97e97SJohannes Berg struct cfg80211_registered_device *rdev; 189279c97e97SJohannes Berg rdev = cfg80211_rdev_by_wiphy_idx( 1893806a9e39SLuis R. Rodriguez last_request->wiphy_idx); 189479c97e97SJohannes Berg if (rdev) { 1895e9c0268fSJoe Perches pr_info("Current regulatory domain updated by AP to: %c%c\n", 189679c97e97SJohannes Berg rdev->country_ie_alpha2[0], 189779c97e97SJohannes Berg rdev->country_ie_alpha2[1]); 18983f2355cbSLuis R. Rodriguez } else 1899e9c0268fSJoe Perches pr_info("Current regulatory domain intersected:\n"); 19003f2355cbSLuis R. Rodriguez } else 1901e9c0268fSJoe Perches pr_info("Current regulatory domain intersected:\n"); 19023f2355cbSLuis R. Rodriguez } else if (is_world_regdom(rd->alpha2)) 1903e9c0268fSJoe Perches pr_info("World regulatory domain updated:\n"); 1904b2e1b302SLuis R. Rodriguez else { 1905b2e1b302SLuis R. Rodriguez if (is_unknown_alpha2(rd->alpha2)) 1906e9c0268fSJoe Perches pr_info("Regulatory domain changed to driver built-in settings (unknown country)\n"); 1907b2e1b302SLuis R. Rodriguez else 1908e9c0268fSJoe Perches pr_info("Regulatory domain changed to country: %c%c\n", 1909b2e1b302SLuis R. Rodriguez rd->alpha2[0], rd->alpha2[1]); 1910b2e1b302SLuis R. Rodriguez } 1911b2e1b302SLuis R. Rodriguez print_rd_rules(rd); 1912b2e1b302SLuis R. Rodriguez } 1913b2e1b302SLuis R. Rodriguez 19142df78167SJohannes Berg static void print_regdomain_info(const struct ieee80211_regdomain *rd) 1915b2e1b302SLuis R. Rodriguez { 1916e9c0268fSJoe Perches pr_info("Regulatory domain: %c%c\n", rd->alpha2[0], rd->alpha2[1]); 1917b2e1b302SLuis R. Rodriguez print_rd_rules(rd); 1918b2e1b302SLuis R. Rodriguez } 1919b2e1b302SLuis R. Rodriguez 1920d2372b31SJohannes Berg /* Takes ownership of rd only if it doesn't fail */ 1921a3d2eaf0SJohannes Berg static int __set_regdom(const struct ieee80211_regdomain *rd) 1922b2e1b302SLuis R. Rodriguez { 19239c96477dSLuis R. Rodriguez const struct ieee80211_regdomain *intersected_rd = NULL; 192479c97e97SJohannes Berg struct cfg80211_registered_device *rdev = NULL; 1925806a9e39SLuis R. Rodriguez struct wiphy *request_wiphy; 1926b2e1b302SLuis R. Rodriguez /* Some basic sanity checks first */ 1927b2e1b302SLuis R. Rodriguez 1928b2e1b302SLuis R. Rodriguez if (is_world_regdom(rd->alpha2)) { 1929f6037d09SJohannes Berg if (WARN_ON(!reg_is_valid_request(rd->alpha2))) 1930b2e1b302SLuis R. Rodriguez return -EINVAL; 1931b2e1b302SLuis R. Rodriguez update_world_regdomain(rd); 1932b2e1b302SLuis R. Rodriguez return 0; 1933b2e1b302SLuis R. Rodriguez } 1934b2e1b302SLuis R. Rodriguez 1935b2e1b302SLuis R. Rodriguez if (!is_alpha2_set(rd->alpha2) && !is_an_alpha2(rd->alpha2) && 1936b2e1b302SLuis R. Rodriguez !is_unknown_alpha2(rd->alpha2)) 1937b2e1b302SLuis R. Rodriguez return -EINVAL; 1938b2e1b302SLuis R. Rodriguez 1939f6037d09SJohannes Berg if (!last_request) 1940b2e1b302SLuis R. Rodriguez return -EINVAL; 1941b2e1b302SLuis R. Rodriguez 1942fb1fc7adSLuis R. Rodriguez /* 1943fb1fc7adSLuis R. Rodriguez * Lets only bother proceeding on the same alpha2 if the current 19443f2355cbSLuis R. Rodriguez * rd is non static (it means CRDA was present and was used last) 1945fb1fc7adSLuis R. Rodriguez * and the pending request came in from a country IE 1946fb1fc7adSLuis R. Rodriguez */ 19477db90f4aSLuis R. Rodriguez if (last_request->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE) { 1948fb1fc7adSLuis R. Rodriguez /* 1949fb1fc7adSLuis R. Rodriguez * If someone else asked us to change the rd lets only bother 1950fb1fc7adSLuis R. Rodriguez * checking if the alpha2 changes if CRDA was already called 1951fb1fc7adSLuis R. Rodriguez */ 1952baeb66feSJohn W. Linville if (!regdom_changes(rd->alpha2)) 1953b2e1b302SLuis R. Rodriguez return -EINVAL; 19543f2355cbSLuis R. Rodriguez } 19553f2355cbSLuis R. Rodriguez 1956fb1fc7adSLuis R. Rodriguez /* 1957fb1fc7adSLuis R. Rodriguez * Now lets set the regulatory domain, update all driver channels 1958b2e1b302SLuis R. Rodriguez * and finally inform them of what we have done, in case they want 1959b2e1b302SLuis R. Rodriguez * to review or adjust their own settings based on their own 1960fb1fc7adSLuis R. Rodriguez * internal EEPROM data 1961fb1fc7adSLuis R. Rodriguez */ 1962b2e1b302SLuis R. Rodriguez 1963f6037d09SJohannes Berg if (WARN_ON(!reg_is_valid_request(rd->alpha2))) 1964b2e1b302SLuis R. Rodriguez return -EINVAL; 1965b2e1b302SLuis R. Rodriguez 1966b2e1b302SLuis R. Rodriguez if (!is_valid_rd(rd)) { 1967e9c0268fSJoe Perches pr_err("Invalid regulatory domain detected:\n"); 1968b2e1b302SLuis R. Rodriguez print_regdomain_info(rd); 1969b2e1b302SLuis R. Rodriguez return -EINVAL; 1970b2e1b302SLuis R. Rodriguez } 1971b2e1b302SLuis R. Rodriguez 1972806a9e39SLuis R. Rodriguez request_wiphy = wiphy_idx_to_wiphy(last_request->wiphy_idx); 1973806a9e39SLuis R. Rodriguez 1974b8295acdSLuis R. Rodriguez if (!last_request->intersect) { 19753e0c3ff3SLuis R. Rodriguez int r; 19763e0c3ff3SLuis R. Rodriguez 19777db90f4aSLuis R. Rodriguez if (last_request->initiator != NL80211_REGDOM_SET_BY_DRIVER) { 19783e0c3ff3SLuis R. Rodriguez reset_regdomains(); 19793e0c3ff3SLuis R. Rodriguez cfg80211_regdomain = rd; 19803e0c3ff3SLuis R. Rodriguez return 0; 19813e0c3ff3SLuis R. Rodriguez } 19823e0c3ff3SLuis R. Rodriguez 1983fb1fc7adSLuis R. Rodriguez /* 1984fb1fc7adSLuis R. Rodriguez * For a driver hint, lets copy the regulatory domain the 1985fb1fc7adSLuis R. Rodriguez * driver wanted to the wiphy to deal with conflicts 1986fb1fc7adSLuis R. Rodriguez */ 19873e0c3ff3SLuis R. Rodriguez 1988558f6d32SLuis R. Rodriguez /* 1989558f6d32SLuis R. Rodriguez * Userspace could have sent two replies with only 1990558f6d32SLuis R. Rodriguez * one kernel request. 1991558f6d32SLuis R. Rodriguez */ 1992558f6d32SLuis R. Rodriguez if (request_wiphy->regd) 1993558f6d32SLuis R. Rodriguez return -EALREADY; 19943e0c3ff3SLuis R. Rodriguez 1995806a9e39SLuis R. Rodriguez r = reg_copy_regd(&request_wiphy->regd, rd); 19963e0c3ff3SLuis R. Rodriguez if (r) 19973e0c3ff3SLuis R. Rodriguez return r; 19983e0c3ff3SLuis R. Rodriguez 1999b8295acdSLuis R. Rodriguez reset_regdomains(); 2000b8295acdSLuis R. Rodriguez cfg80211_regdomain = rd; 2001b8295acdSLuis R. Rodriguez return 0; 2002b8295acdSLuis R. Rodriguez } 2003b8295acdSLuis R. Rodriguez 2004b8295acdSLuis R. Rodriguez /* Intersection requires a bit more work */ 2005b8295acdSLuis R. Rodriguez 20067db90f4aSLuis R. Rodriguez if (last_request->initiator != NL80211_REGDOM_SET_BY_COUNTRY_IE) { 2007b8295acdSLuis R. Rodriguez 20089c96477dSLuis R. Rodriguez intersected_rd = regdom_intersect(rd, cfg80211_regdomain); 20099c96477dSLuis R. Rodriguez if (!intersected_rd) 20109c96477dSLuis R. Rodriguez return -EINVAL; 2011b8295acdSLuis R. Rodriguez 2012fb1fc7adSLuis R. Rodriguez /* 2013fb1fc7adSLuis R. Rodriguez * We can trash what CRDA provided now. 20143e0c3ff3SLuis R. Rodriguez * However if a driver requested this specific regulatory 2015fb1fc7adSLuis R. Rodriguez * domain we keep it for its private use 2016fb1fc7adSLuis R. Rodriguez */ 20177db90f4aSLuis R. Rodriguez if (last_request->initiator == NL80211_REGDOM_SET_BY_DRIVER) 2018806a9e39SLuis R. Rodriguez request_wiphy->regd = rd; 20193e0c3ff3SLuis R. Rodriguez else 20209c96477dSLuis R. Rodriguez kfree(rd); 20213e0c3ff3SLuis R. Rodriguez 2022b8295acdSLuis R. Rodriguez rd = NULL; 2023b8295acdSLuis R. Rodriguez 2024b8295acdSLuis R. Rodriguez reset_regdomains(); 2025b8295acdSLuis R. Rodriguez cfg80211_regdomain = intersected_rd; 2026b8295acdSLuis R. Rodriguez 2027b8295acdSLuis R. Rodriguez return 0; 20289c96477dSLuis R. Rodriguez } 20299c96477dSLuis R. Rodriguez 20303f2355cbSLuis R. Rodriguez if (!intersected_rd) 20313f2355cbSLuis R. Rodriguez return -EINVAL; 20323f2355cbSLuis R. Rodriguez 203379c97e97SJohannes Berg rdev = wiphy_to_dev(request_wiphy); 20343f2355cbSLuis R. Rodriguez 203579c97e97SJohannes Berg rdev->country_ie_alpha2[0] = rd->alpha2[0]; 203679c97e97SJohannes Berg rdev->country_ie_alpha2[1] = rd->alpha2[1]; 203779c97e97SJohannes Berg rdev->env = last_request->country_ie_env; 20383f2355cbSLuis R. Rodriguez 20393f2355cbSLuis R. Rodriguez BUG_ON(intersected_rd == rd); 20403f2355cbSLuis R. Rodriguez 20413f2355cbSLuis R. Rodriguez kfree(rd); 20423f2355cbSLuis R. Rodriguez rd = NULL; 20433f2355cbSLuis R. Rodriguez 2044b8295acdSLuis R. Rodriguez reset_regdomains(); 20453f2355cbSLuis R. Rodriguez cfg80211_regdomain = intersected_rd; 2046b2e1b302SLuis R. Rodriguez 2047b2e1b302SLuis R. Rodriguez return 0; 2048b2e1b302SLuis R. Rodriguez } 2049b2e1b302SLuis R. Rodriguez 2050b2e1b302SLuis R. Rodriguez 2051fb1fc7adSLuis R. Rodriguez /* 2052fb1fc7adSLuis R. Rodriguez * Use this call to set the current regulatory domain. Conflicts with 2053b2e1b302SLuis R. Rodriguez * multiple drivers can be ironed out later. Caller must've already 2054fb1fc7adSLuis R. Rodriguez * kmalloc'd the rd structure. Caller must hold cfg80211_mutex 2055fb1fc7adSLuis R. Rodriguez */ 2056a3d2eaf0SJohannes Berg int set_regdom(const struct ieee80211_regdomain *rd) 2057b2e1b302SLuis R. Rodriguez { 2058b2e1b302SLuis R. Rodriguez int r; 2059b2e1b302SLuis R. Rodriguez 2060761cf7ecSLuis R. Rodriguez assert_cfg80211_lock(); 2061761cf7ecSLuis R. Rodriguez 2062abc7381bSLuis R. Rodriguez mutex_lock(®_mutex); 2063abc7381bSLuis R. Rodriguez 2064b2e1b302SLuis R. Rodriguez /* Note that this doesn't update the wiphys, this is done below */ 2065b2e1b302SLuis R. Rodriguez r = __set_regdom(rd); 2066d2372b31SJohannes Berg if (r) { 2067d2372b31SJohannes Berg kfree(rd); 2068abc7381bSLuis R. Rodriguez mutex_unlock(®_mutex); 2069b2e1b302SLuis R. Rodriguez return r; 2070d2372b31SJohannes Berg } 2071b2e1b302SLuis R. Rodriguez 2072b2e1b302SLuis R. Rodriguez /* This would make this whole thing pointless */ 2073a01ddafdSLuis R. Rodriguez if (!last_request->intersect) 2074b2e1b302SLuis R. Rodriguez BUG_ON(rd != cfg80211_regdomain); 2075b2e1b302SLuis R. Rodriguez 2076b2e1b302SLuis R. Rodriguez /* update all wiphys now with the new established regulatory domain */ 2077f6037d09SJohannes Berg update_all_wiphy_regulatory(last_request->initiator); 2078b2e1b302SLuis R. Rodriguez 2079a01ddafdSLuis R. Rodriguez print_regdomain(cfg80211_regdomain); 2080b2e1b302SLuis R. Rodriguez 208173d54c9eSLuis R. Rodriguez nl80211_send_reg_change_event(last_request); 208273d54c9eSLuis R. Rodriguez 2083b2e253cfSLuis R. Rodriguez reg_set_request_processed(); 2084b2e253cfSLuis R. Rodriguez 2085abc7381bSLuis R. Rodriguez mutex_unlock(®_mutex); 2086abc7381bSLuis R. Rodriguez 2087b2e1b302SLuis R. Rodriguez return r; 2088b2e1b302SLuis R. Rodriguez } 2089b2e1b302SLuis R. Rodriguez 2090a1794390SLuis R. Rodriguez /* Caller must hold cfg80211_mutex */ 20913f2355cbSLuis R. Rodriguez void reg_device_remove(struct wiphy *wiphy) 20923f2355cbSLuis R. Rodriguez { 20930ad8acafSLuis R. Rodriguez struct wiphy *request_wiphy = NULL; 2094806a9e39SLuis R. Rodriguez 2095761cf7ecSLuis R. Rodriguez assert_cfg80211_lock(); 2096761cf7ecSLuis R. Rodriguez 2097abc7381bSLuis R. Rodriguez mutex_lock(®_mutex); 2098abc7381bSLuis R. Rodriguez 20990ef9ccddSChris Wright kfree(wiphy->regd); 21000ef9ccddSChris Wright 21010ad8acafSLuis R. Rodriguez if (last_request) 2102806a9e39SLuis R. Rodriguez request_wiphy = wiphy_idx_to_wiphy(last_request->wiphy_idx); 2103806a9e39SLuis R. Rodriguez 21040ef9ccddSChris Wright if (!request_wiphy || request_wiphy != wiphy) 2105abc7381bSLuis R. Rodriguez goto out; 21060ef9ccddSChris Wright 2107806a9e39SLuis R. Rodriguez last_request->wiphy_idx = WIPHY_IDX_STALE; 21083f2355cbSLuis R. Rodriguez last_request->country_ie_env = ENVIRON_ANY; 2109abc7381bSLuis R. Rodriguez out: 2110abc7381bSLuis R. Rodriguez mutex_unlock(®_mutex); 21113f2355cbSLuis R. Rodriguez } 21123f2355cbSLuis R. Rodriguez 21132fcc9f73SUwe Kleine-König int __init regulatory_init(void) 2114b2e1b302SLuis R. Rodriguez { 2115bcf4f99bSLuis R. Rodriguez int err = 0; 2116734366deSJohannes Berg 2117b2e1b302SLuis R. Rodriguez reg_pdev = platform_device_register_simple("regulatory", 0, NULL, 0); 2118b2e1b302SLuis R. Rodriguez if (IS_ERR(reg_pdev)) 2119b2e1b302SLuis R. Rodriguez return PTR_ERR(reg_pdev); 2120734366deSJohannes Berg 2121fe33eb39SLuis R. Rodriguez spin_lock_init(®_requests_lock); 2122e38f8a7aSLuis R. Rodriguez spin_lock_init(®_pending_beacons_lock); 2123fe33eb39SLuis R. Rodriguez 2124a3d2eaf0SJohannes Berg cfg80211_regdomain = cfg80211_world_regdom; 2125734366deSJohannes Berg 212609d989d1SLuis R. Rodriguez user_alpha2[0] = '9'; 212709d989d1SLuis R. Rodriguez user_alpha2[1] = '7'; 212809d989d1SLuis R. Rodriguez 2129ae9e4b0dSLuis R. Rodriguez /* We always try to get an update for the static regdomain */ 2130ae9e4b0dSLuis R. Rodriguez err = regulatory_hint_core(cfg80211_regdomain->alpha2); 2131bcf4f99bSLuis R. Rodriguez if (err) { 2132bcf4f99bSLuis R. Rodriguez if (err == -ENOMEM) 2133bcf4f99bSLuis R. Rodriguez return err; 2134bcf4f99bSLuis R. Rodriguez /* 2135bcf4f99bSLuis R. Rodriguez * N.B. kobject_uevent_env() can fail mainly for when we're out 2136bcf4f99bSLuis R. Rodriguez * memory which is handled and propagated appropriately above 2137bcf4f99bSLuis R. Rodriguez * but it can also fail during a netlink_broadcast() or during 2138bcf4f99bSLuis R. Rodriguez * early boot for call_usermodehelper(). For now treat these 2139bcf4f99bSLuis R. Rodriguez * errors as non-fatal. 2140bcf4f99bSLuis R. Rodriguez */ 2141e9c0268fSJoe Perches pr_err("kobject_uevent_env() was unable to call CRDA during init\n"); 2142bcf4f99bSLuis R. Rodriguez #ifdef CONFIG_CFG80211_REG_DEBUG 2143bcf4f99bSLuis R. Rodriguez /* We want to find out exactly why when debugging */ 2144bcf4f99bSLuis R. Rodriguez WARN_ON(err); 2145bcf4f99bSLuis R. Rodriguez #endif 2146bcf4f99bSLuis R. Rodriguez } 2147734366deSJohannes Berg 2148ae9e4b0dSLuis R. Rodriguez /* 2149ae9e4b0dSLuis R. Rodriguez * Finally, if the user set the module parameter treat it 2150ae9e4b0dSLuis R. Rodriguez * as a user hint. 2151ae9e4b0dSLuis R. Rodriguez */ 2152ae9e4b0dSLuis R. Rodriguez if (!is_world_regdom(ieee80211_regdom)) 2153ae9e4b0dSLuis R. Rodriguez regulatory_hint_user(ieee80211_regdom); 2154ae9e4b0dSLuis R. Rodriguez 2155b2e1b302SLuis R. Rodriguez return 0; 2156b2e1b302SLuis R. Rodriguez } 2157b2e1b302SLuis R. Rodriguez 21582fcc9f73SUwe Kleine-König void /* __init_or_exit */ regulatory_exit(void) 2159b2e1b302SLuis R. Rodriguez { 2160fe33eb39SLuis R. Rodriguez struct regulatory_request *reg_request, *tmp; 2161e38f8a7aSLuis R. Rodriguez struct reg_beacon *reg_beacon, *btmp; 2162fe33eb39SLuis R. Rodriguez 2163fe33eb39SLuis R. Rodriguez cancel_work_sync(®_work); 2164fe33eb39SLuis R. Rodriguez 2165a1794390SLuis R. Rodriguez mutex_lock(&cfg80211_mutex); 2166abc7381bSLuis R. Rodriguez mutex_lock(®_mutex); 2167734366deSJohannes Berg 2168b2e1b302SLuis R. Rodriguez reset_regdomains(); 2169734366deSJohannes Berg 2170f6037d09SJohannes Berg kfree(last_request); 2171f6037d09SJohannes Berg 2172b2e1b302SLuis R. Rodriguez platform_device_unregister(reg_pdev); 2173734366deSJohannes Berg 2174e38f8a7aSLuis R. Rodriguez spin_lock_bh(®_pending_beacons_lock); 2175e38f8a7aSLuis R. Rodriguez if (!list_empty(®_pending_beacons)) { 2176e38f8a7aSLuis R. Rodriguez list_for_each_entry_safe(reg_beacon, btmp, 2177e38f8a7aSLuis R. Rodriguez ®_pending_beacons, list) { 2178e38f8a7aSLuis R. Rodriguez list_del(®_beacon->list); 2179e38f8a7aSLuis R. Rodriguez kfree(reg_beacon); 2180e38f8a7aSLuis R. Rodriguez } 2181e38f8a7aSLuis R. Rodriguez } 2182e38f8a7aSLuis R. Rodriguez spin_unlock_bh(®_pending_beacons_lock); 2183e38f8a7aSLuis R. Rodriguez 2184e38f8a7aSLuis R. Rodriguez if (!list_empty(®_beacon_list)) { 2185e38f8a7aSLuis R. Rodriguez list_for_each_entry_safe(reg_beacon, btmp, 2186e38f8a7aSLuis R. Rodriguez ®_beacon_list, list) { 2187e38f8a7aSLuis R. Rodriguez list_del(®_beacon->list); 2188e38f8a7aSLuis R. Rodriguez kfree(reg_beacon); 2189e38f8a7aSLuis R. Rodriguez } 2190e38f8a7aSLuis R. Rodriguez } 2191e38f8a7aSLuis R. Rodriguez 2192fe33eb39SLuis R. Rodriguez spin_lock(®_requests_lock); 2193fe33eb39SLuis R. Rodriguez if (!list_empty(®_requests_list)) { 2194fe33eb39SLuis R. Rodriguez list_for_each_entry_safe(reg_request, tmp, 2195fe33eb39SLuis R. Rodriguez ®_requests_list, list) { 2196fe33eb39SLuis R. Rodriguez list_del(®_request->list); 2197fe33eb39SLuis R. Rodriguez kfree(reg_request); 2198fe33eb39SLuis R. Rodriguez } 2199fe33eb39SLuis R. Rodriguez } 2200fe33eb39SLuis R. Rodriguez spin_unlock(®_requests_lock); 2201fe33eb39SLuis R. Rodriguez 2202abc7381bSLuis R. Rodriguez mutex_unlock(®_mutex); 2203a1794390SLuis R. Rodriguez mutex_unlock(&cfg80211_mutex); 22048318d78aSJohannes Berg } 2205