From: "Luis R. Rodriguez" In-Reply-To: <20070921204606.GD31768@pogo> References: <20070921204606.GD31768@pogo> To: John Linville Cc: linux-wireless@vger.kernel.org, Michael Wu , Johannes Berg , Daniel Drake , Larry Finger Date: Fri, 21 Sep 2007 15:50:53 -0400 Subject: [PATCH 4/5] Wireless: Add regdomain support to cfg80211 This patch makes cfg80211 provide an interface for defining a central regulatory domain all cfg80211 wireless drivers should adhere to. Now when cfg80211 starts up and you will see this: ieee80211_regdomains: regulatory domain WORLD created Regulatory Domain: WORLD Regulatory Domain ID: 0x03 IEEE 802.11g 2GHz ISM subband max_ir_ptmp: 20 dBm max_eirp_ptmp: 20 dBm max_ir_ptp: 0 dBm max_eirp_ptp: 0 dBm max_antenna_gain: 6 dBi Environment capability: Indoor & Outdoor Channel Freq (MHz) 5 2432 6 2437 7 2442 Signed-off-by: Luis R. Rodriguez --- include/net/cfg80211.h | 72 ++++++++++++++++++++++++++ net/wireless/Kconfig | 1 + net/wireless/core.c | 133 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 206 insertions(+), 0 deletions(-) diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h index 1459d12..34fd74b 100644 --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -5,11 +5,13 @@ #include #include #include +#include /* * 802.11 configuration in-kernel interface * * Copyright 2006 Johannes Berg + * Copyright 2007 Luis R. Rodriguez */ /** @@ -117,6 +119,73 @@ extern int ieee80211_radiotap_iterator_next( struct wiphy; /** + * enum regdom_set_by - the different ways by which the central regulatory + * domain can be set + * + * @REG_SET_BY_INIT: indicates regdomain was set by regdomain_init() + * @REG_SET_BY_DRIVER: indicates regdomain was set by a driver + * through cfg80211_set_regdomain(). This type of settings is ignored if + * the regdomain was already set by userspace (%REG_SET_BY_USER). If + * userspace never set the regdomain and two drivers are found trying to + * set the regdomain to two different regdomains then the world regulatory + * domain will be set by cfg80211_set_regdomain(). + * @REG_SET_BY_USER: indicates regdomain was set by userspace. + * @REG_SET_BY_80211D: indicates an 802.11d frame was received with a valid + * country information element to which the wireless stack is going to + * adhere to. + * @REG_SET_BY_CONFLICT: indicates a conflict was found and regdomain was + * therefore set to the world regulatory domain by + * cfg80211_set_regdomain(). + */ +enum regdom_set_by { + REG_SET_BY_INIT = 1, + REG_SET_BY_DRIVER, + REG_SET_BY_USER, + REG_SET_BY_80211D, + REG_SET_BY_CONFLICT +}; + +/** + * struct cfg80211_regdomain_settings - central regulatory domain settings + * + * @regdomain: central regulatory domain, an &ieee80211_regdomain + * + * @country: country to which this regulatory domain corresponds in the + * last setting. This must be a valid ISO3166-1 country or one of the + * accepted exceptions, such as 00 for the world regulatory domain or + * UK for the United Kingdom. + * + * @set_by: tells us who set last the regualtory domain. This can be any of + * %regdom_set_by + * + * @wiphy: wiphy of driver who last set the regdomain, if set by + * driver. This used to determine conflicts of driver regulatory domains + * settings if more than one wirless card is present and to simply keep + * record if a driver set the regulatory domain which driver set it. + * + * This structure defines the cfg80211 central wireless regulatory domain + * structure which all cfg80211 drivers should adhere to. It is initialized to + * the world regulatory domain by cfg80211 by default (%REG_SET_BY_INIT). If a + * wireless driver present has a built-in regulatory domain set either in the + * EEPROM or firmware it can inform cfg80211 about this and change the + * regulatory domain as such (%REG_SET_BY_DRIVER). If a second wireless card + * is present and if there is a conflict between regulatory domains then the + * world regulatory domain is set (%REG_SET_BY_CONFLICT). The regulatory domain + * can also be changed by receiving an 802.11d frame with a valid country code + * (%REG_SET_BY_80211D). Finally, If the regulatory domain was set by userspace + * (%REG_SET_BY_USER) then driver specific setting of regulatory domains + * (%REG_SET_BY_DRIVER) and 802.11d frames for changing the regulatory domain + * (%REG_SET_BY_80211D) will be ignored. + */ +struct cfg80211_regdomain_settings { + struct ieee80211_regdomain *regdomain; + enum regdom_set_by set_by; + struct wiphy *wiphy; + char country[ISOCOUNTRYSIZ2]; +}; + + +/** * struct cfg80211_ops - backend description for wireless configuration * * This struct is registered by fullmac card drivers and/or wireless stacks @@ -183,6 +252,9 @@ struct cfg80211_ops { struct key_params *params); }; +extern int cfg80211_set_regdomain(char *country, u32, + enum regdom_set_by set_by, + struct wiphy *wiphy_reg_set); /* helper functions specific to nl80211 */ extern void *nl80211hdr_put(struct sk_buff *skb, u32 pid, diff --git a/net/wireless/Kconfig b/net/wireless/Kconfig index c60ee87..0505347 100644 --- a/net/wireless/Kconfig +++ b/net/wireless/Kconfig @@ -1,5 +1,6 @@ config CFG80211 tristate "Improved wireless configuration API" + select IEEE80211_REGDOMAINS config NL80211 bool "nl80211 new netlink interface support" diff --git a/net/wireless/core.c b/net/wireless/core.c index 2f3f7b6..c2f773b 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c @@ -2,6 +2,7 @@ * This is the linux wireless configuration interface. * * Copyright 2006, 2007 Johannes Berg + * Copyright 2007 Luis R. Rodriguez */ #include @@ -34,6 +35,14 @@ LIST_HEAD(cfg80211_drv_list); DEFINE_MUTEX(cfg80211_drv_mutex); static int wiphy_counter; +/* All cfg80211 drivers adhere to this */ +struct cfg80211_regdomain_settings cfg80211_regdomain; +EXPORT_SYMBOL(cfg80211_regdomain); +/* Since the regulatory domain may be set by different events we protect its + * structure */ +DEFINE_MUTEX(cfg80211_regdomain_mutex); +EXPORT_SYMBOL(cfg80211_regdomain_mutex); + /* for debugfs */ static struct dentry *ieee80211_debugfs_dir; @@ -335,12 +344,135 @@ static struct notifier_block cfg80211_netdev_notifier = { .notifier_call = cfg80211_netdev_notifier_call, }; +int cfg80211_set_regdomain(char *country, u32 regdomain_id, + enum regdom_set_by set_by, + struct wiphy *wiphy_reg_set) +{ + struct ieee80211_regdomain *old_reg, *new_reg; + int r = 0; + enum regdom_set_by old_set_by; + int needs_freeing = 0; + + mutex_lock(&cfg80211_regdomain_mutex); + old_reg = cfg80211_regdomain.regdomain; + old_set_by = cfg80211_regdomain.set_by; + + switch (cfg80211_regdomain.set_by) { + case 0: + switch (set_by) { + case REG_SET_BY_INIT: + /* XXX: Test this first */ + cfg80211_regdomain.set_by = REG_SET_BY_INIT; + break; + default: + /* Should not happen unless later we support clearing + * of set_by before calling this routine */ + r = -EOPNOTSUPP; + goto unlock_and_exit; + } + break; + case REG_SET_BY_INIT: + switch (set_by) { + case REG_SET_BY_INIT: + /* Should never happen */ + r = -EOPNOTSUPP; + goto unlock_and_exit; + case REG_SET_BY_DRIVER: + if (wiphy_reg_set == NULL) { + r =-EINVAL; + goto unlock_and_exit; + } + /* XXX: This is not handled yet */ + r = -EOPNOTSUPP; /* Not handled yet */ + goto unlock_and_exit; + case REG_SET_BY_USER: + printk("Userspace changed regulatory domain\n"); + /* XXX don't free until we know for sure we set + * a new regdomain, then free it and arrange pointers */ + needs_freeing = 1; + cfg80211_regdomain.set_by = REG_SET_BY_USER; + break; + default: + BUG(); + } + break; + case REG_SET_BY_USER: + switch (set_by) { + case REG_SET_BY_USER: + printk("Userspace changed regulatory domain\n"); + needs_freeing = 1; + cfg80211_regdomain.set_by = REG_SET_BY_USER; + break; + default: + r = -EOPNOTSUPP; /* Not handled yet */ + goto unlock_and_exit; + } + break; + default: + r = -EOPNOTSUPP; /* Not handled yet */ + goto unlock_and_exit; + break; + } + + /* This does all the work */ + r = regdomain_build(regdomain_id, &new_reg); + if(r) { + cfg80211_regdomain.set_by = old_set_by; + goto unlock_and_exit; + } + + if (needs_freeing) { + free_regdomain(old_reg); + cfg80211_regdomain.regdomain = NULL; + } + + strcpy(cfg80211_regdomain.country, country); + cfg80211_regdomain.country[ISOCOUNTRYSIZ2] = '\0'; + /* Lastly, update pointer */ + cfg80211_regdomain.regdomain = new_reg; + mutex_unlock(&cfg80211_regdomain_mutex); + print_regdomain(cfg80211_regdomain.regdomain); + + return 0; + +unlock_and_exit: + mutex_unlock(&cfg80211_regdomain_mutex); + return r; +} +EXPORT_SYMBOL(cfg80211_set_regdomain); + +static int regdomain_init(void) +{ + int r; + char country[ISOCOUNTRYSIZ2] = DEFAULT_REG_ISO3166_1; + u32 regdomain_id = 0; /* Initialize just to shut up GCC */ + + country[ISOCOUNTRYSIZ2] = '\0'; + + memset(&cfg80211_regdomain, 0, + sizeof(struct cfg80211_regdomain_settings)); + + r = iso3166_to_reg(country, ®domain_id); + if (r) + return r; + r = cfg80211_set_regdomain(country, regdomain_id, + REG_SET_BY_INIT, NULL); + if (r) + return r; + + return 0; +} + static int cfg80211_init(void) { int err = wiphy_sysfs_init(); if (err) goto out_fail_sysfs; + err = regdomain_init(); + if (err) + goto out_fail_sysfs; + err = register_netdevice_notifier(&cfg80211_netdev_notifier); if (err) goto out_fail_notifier; @@ -367,6 +499,7 @@ static void cfg80211_exit(void) debugfs_remove(ieee80211_debugfs_dir); nl80211_exit(); unregister_netdevice_notifier(&cfg80211_netdev_notifier); + free_regdomain(cfg80211_regdomain.regdomain); wiphy_sysfs_exit(); } module_exit(cfg80211_exit); -- 1.5.2.4