提交 b2e1b302 编写于 作者: L Luis R. Rodriguez 提交者: John W. Linville

cfg80211: Add new wireless regulatory infrastructure

This adds the new wireless regulatory infrastructure. The
main motiviation behind this was to centralize regulatory
code as each driver was implementing their own regulatory solution,
and to replace the initial centralized code we have where:

* only 3 regulatory domains are supported: US, JP and EU
* regulatory domains can only be changed through module parameter
* all rules were built statically in the kernel

We now have support for regulatory domains for many countries
and regulatory domains are now queried through a userspace agent
through udev allowing distributions to update regulatory rules
without updating the kernel.

Each driver can regulatory_hint() a regulatory domain
based on either their EEPROM mapped regulatory domain value to a
respective ISO/IEC 3166-1 country code or pass an internally built
regulatory domain. We also add support to let the user set the
regulatory domain through userspace in case of faulty EEPROMs to
further help compliance.

Support for world roaming will be added soon for cards capable of
this.

For more information see:

http://wireless.kernel.org/en/developers/Regulatory/CRDA

For now we leave an option to enable the old module parameter,
ieee80211_regdom, and to build the 3 old regdomains statically
(US, JP and EU). This option is CONFIG_WIRELESS_OLD_REGULATORY.
These old static definitions and the module parameter is being
scheduled for removal for 2.6.29. Note that if you use this
you won't make use of a world regulatory domain as its pointless.
If you leave this option enabled and if CRDA is present and you
use US or JP we will try to ask CRDA to update us a regulatory
domain for us.
Signed-off-by: NLuis R. Rodriguez <lrodriguez@atheros.com>
Signed-off-by: NJohn W. Linville <linville@tuxdriver.com>
上级 63f2c046
...@@ -6,6 +6,24 @@ be removed from this file. ...@@ -6,6 +6,24 @@ be removed from this file.
--------------------------- ---------------------------
What: old static regulatory information and ieee80211_regdom module parameter
When: 2.6.29
Why: The old regulatory infrastructure has been replaced with a new one
which does not require statically defined regulatory domains. We do
not want to keep static regulatory domains in the kernel due to the
the dynamic nature of regulatory law and localization. We kept around
the old static definitions for the regulatory domains of:
* US
* JP
* EU
and used by default the US when CONFIG_WIRELESS_OLD_REGULATORY was
set. We also kept around the ieee80211_regdom module parameter in case
some applications were relying on it. Changing regulatory domains
can now be done instead by using nl80211, as is done with iw.
Who: Luis R. Rodriguez <lrodriguez@atheros.com>
---------------------------
What: dev->power.power_state What: dev->power.power_state
When: July 2007 When: July 2007
Why: Broken design for runtime control over driver power states, confusing Why: Broken design for runtime control over driver power states, confusing
......
Linux wireless regulatory documentation
---------------------------------------
This document gives a brief review over how the Linux wireless
regulatory infrastructure works.
More up to date information can be obtained at the project's web page:
http://wireless.kernel.org/en/developers/Regulatory
Keeping regulatory domains in userspace
---------------------------------------
Due to the dynamic nature of regulatory domains we keep them
in userspace and provide a framework for userspace to upload
to the kernel one regulatory domain to be used as the central
core regulatory domain all wireless devices should adhere to.
How to get regulatory domains to the kernel
-------------------------------------------
Userspace gets a regulatory domain in the kernel by having
a userspace agent build it and send it via nl80211. Only
expected regulatory domains will be respected by the kernel.
A currently available userspace agent which can accomplish this
is CRDA - central regulatory domain agent. Its documented here:
http://wireless.kernel.org/en/developers/Regulatory/CRDA
Essentially the kernel will send a udev event when it knows
it needs a new regulatory domain. A udev rule can be put in place
to trigger crda to send the respective regulatory domain for a
specific ISO/IEC 3166 alpha2.
Below is an example udev rule which can be used:
# Example file, should be put in /etc/udev/rules.d/regulatory.rules
KERNEL=="regulatory*", ACTION=="change", SUBSYSTEM=="platform", RUN+="/sbin/crda"
The alpha2 is passed as an environment variable under the variable COUNTRY.
Who asks for regulatory domains?
--------------------------------
* Users
Users can use iw:
http://wireless.kernel.org/en/users/Documentation/iw
An example:
# set regulatory domain to "Costa Rica"
iw reg set CR
This will request the kernel to set the regulatory domain to
the specificied alpha2. The kernel in turn will then ask userspace
to provide a regulatory domain for the alpha2 specified by the user
by sending a uevent.
* Wireless subsystems for Country Information elements
The kernel will send a uevent to inform userspace a new
regulatory domain is required. More on this to be added
as its integration is added.
* Drivers
If drivers determine they need a specific regulatory domain
set they can inform the wireless core using regulatory_hint().
They have two options -- they either provide an alpha2 so that
crda can provide back a regulatory domain for that country or
they can build their own regulatory domain based on internal
custom knowledge so the wireless core can respect it.
*Most* drivers will rely on the first mechanism of providing a
regulatory hint with an alpha2. For these drivers there is an additional
check that can be used to ensure compliance based on custom EEPROM
regulatory data. This additional check can be used by drivers by
registering on its struct wiphy a reg_notifier() callback. This notifier
is called when the core's regulatory domain has been changed. The driver
can use this to review the changes made and also review who made them
(driver, user, country IE) and determine what to allow based on its
internal EEPROM data. Devices drivers wishing to be capable of world
roaming should use this callback. More on world roaming will be
added to this document when its support is enabled.
Device drivers who provide their own built regulatory domain
do not need a callback as the channels registered by them are
the only ones that will be allowed and therefore *additional*
cannels cannot be enabled.
Example code - drivers hinting an alpha2:
------------------------------------------
This example comes from the zd1211rw device driver. You can start
by having a mapping of your device's EEPROM country/regulatory
domain value to to a specific alpha2 as follows:
static struct zd_reg_alpha2_map reg_alpha2_map[] = {
{ ZD_REGDOMAIN_FCC, "US" },
{ ZD_REGDOMAIN_IC, "CA" },
{ ZD_REGDOMAIN_ETSI, "DE" }, /* Generic ETSI, use most restrictive */
{ ZD_REGDOMAIN_JAPAN, "JP" },
{ ZD_REGDOMAIN_JAPAN_ADD, "JP" },
{ ZD_REGDOMAIN_SPAIN, "ES" },
{ ZD_REGDOMAIN_FRANCE, "FR" },
Then you can define a routine to map your read EEPROM value to an alpha2,
as follows:
static int zd_reg2alpha2(u8 regdomain, char *alpha2)
{
unsigned int i;
struct zd_reg_alpha2_map *reg_map;
for (i = 0; i < ARRAY_SIZE(reg_alpha2_map); i++) {
reg_map = &reg_alpha2_map[i];
if (regdomain == reg_map->reg) {
alpha2[0] = reg_map->alpha2[0];
alpha2[1] = reg_map->alpha2[1];
return 0;
}
}
return 1;
}
Lastly, you can then hint to the core of your discovered alpha2, if a match
was found. You need to do this after you have registered your wiphy. You
are expected to do this during initialization.
r = zd_reg2alpha2(mac->regdomain, alpha2);
if (!r)
regulatory_hint(hw->wiphy, alpha2, NULL);
Example code - drivers providing a built in regulatory domain:
--------------------------------------------------------------
If you have regulatory information you can obtain from your
driver and you *need* to use this we let you build a regulatory domain
structure and pass it to the wireless core. To do this you should
kmalloc() a structure big enough to hold your regulatory domain
structure and you should then fill it with your data. Finally you simply
call regulatory_hint() with the regulatory domain structure in it.
Bellow is a simple example, with a regulatory domain cached using the stack.
Your implementation may vary (read EEPROM cache instead, for example).
Example cache of some regulatory domain
struct ieee80211_regdomain mydriver_jp_regdom = {
.n_reg_rules = 3,
.alpha2 = "JP",
//.alpha2 = "99", /* If I have no alpha2 to map it to */
.reg_rules = {
/* IEEE 802.11b/g, channels 1..14 */
REG_RULE(2412-20, 2484+20, 40, 6, 20, 0),
/* IEEE 802.11a, channels 34..48 */
REG_RULE(5170-20, 5240+20, 40, 6, 20,
NL80211_RRF_PASSIVE_SCAN),
/* IEEE 802.11a, channels 52..64 */
REG_RULE(5260-20, 5320+20, 40, 6, 20,
NL80211_RRF_NO_IBSS |
NL80211_RRF_DFS),
}
};
Then in some part of your code after your wiphy has been registered:
int r;
struct ieee80211_regdomain *rd;
int size_of_regd;
int num_rules = mydriver_jp_regdom.n_reg_rules;
unsigned int i;
size_of_regd = sizeof(struct ieee80211_regdomain) +
(num_rules * sizeof(struct ieee80211_reg_rule));
rd = kzalloc(size_of_regd, GFP_KERNEL);
if (!rd)
return -ENOMEM;
memcpy(rd, &mydriver_jp_regdom, sizeof(struct ieee80211_regdomain));
for (i=0; i < num_rules; i++) {
memcpy(&rd->reg_rules[i], &mydriver_jp_regdom.reg_rules[i],
sizeof(struct ieee80211_reg_rule));
}
r = regulatory_hint(hw->wiphy, NULL, rd);
if (r) {
kfree(rd);
return r;
}
...@@ -92,6 +92,20 @@ ...@@ -92,6 +92,20 @@
* @NL80211_CMD_SET_BSS: Set BSS attributes for BSS identified by * @NL80211_CMD_SET_BSS: Set BSS attributes for BSS identified by
* %NL80211_ATTR_IFINDEX. * %NL80211_ATTR_IFINDEX.
* *
* @NL80211_CMD_SET_REG: Set current regulatory domain. CRDA sends this command
* after being queried by the kernel. CRDA replies by sending a regulatory
* domain structure which consists of %NL80211_ATTR_REG_ALPHA set to our
* current alpha2 if it found a match. It also provides
* NL80211_ATTR_REG_RULE_FLAGS, and a set of regulatory rules. Each
* regulatory rule is a nested set of attributes given by
* %NL80211_ATTR_REG_RULE_FREQ_[START|END] and
* %NL80211_ATTR_FREQ_RANGE_MAX_BW with an attached power rule given by
* %NL80211_ATTR_REG_RULE_POWER_MAX_ANT_GAIN and
* %NL80211_ATTR_REG_RULE_POWER_MAX_EIRP.
* @NL80211_CMD_REQ_SET_REG: ask the wireless core to set the regulatory domain
* to the the specified ISO/IEC 3166-1 alpha2 country code. The core will
* store this as a valid request and then query userspace for it.
*
* @NL80211_CMD_MAX: highest used command number * @NL80211_CMD_MAX: highest used command number
* @__NL80211_CMD_AFTER_LAST: internal use * @__NL80211_CMD_AFTER_LAST: internal use
*/ */
...@@ -131,7 +145,10 @@ enum nl80211_commands { ...@@ -131,7 +145,10 @@ enum nl80211_commands {
NL80211_CMD_SET_BSS, NL80211_CMD_SET_BSS,
/* add commands here */ NL80211_CMD_SET_REG,
NL80211_CMD_REQ_SET_REG,
/* add new commands above here */
/* used to define NL80211_CMD_MAX below */ /* used to define NL80211_CMD_MAX below */
__NL80211_CMD_AFTER_LAST, __NL80211_CMD_AFTER_LAST,
...@@ -197,10 +214,21 @@ enum nl80211_commands { ...@@ -197,10 +214,21 @@ enum nl80211_commands {
* info given for %NL80211_CMD_GET_MPATH, nested attribute described at * info given for %NL80211_CMD_GET_MPATH, nested attribute described at
* &enum nl80211_mpath_info. * &enum nl80211_mpath_info.
* *
*
* @NL80211_ATTR_MNTR_FLAGS: flags, nested element with NLA_FLAG attributes of * @NL80211_ATTR_MNTR_FLAGS: flags, nested element with NLA_FLAG attributes of
* &enum nl80211_mntr_flags. * &enum nl80211_mntr_flags.
* *
* @NL80211_ATTR_REG_ALPHA2: an ISO-3166-alpha2 country code for which the
* current regulatory domain should be set to or is already set to.
* For example, 'CR', for Costa Rica. This attribute is used by the kernel
* to query the CRDA to retrieve one regulatory domain. This attribute can
* also be used by userspace to query the kernel for the currently set
* regulatory domain. We chose an alpha2 as that is also used by the
* IEEE-802.11d country information element to identify a country.
* Users can also simply ask the wireless core to set regulatory domain
* to a specific alpha2.
* @NL80211_ATTR_REG_RULES: a nested array of regulatory domain regulatory
* rules.
*
* @NL80211_ATTR_BSS_CTS_PROT: whether CTS protection is enabled (u8, 0 or 1) * @NL80211_ATTR_BSS_CTS_PROT: whether CTS protection is enabled (u8, 0 or 1)
* @NL80211_ATTR_BSS_SHORT_PREAMBLE: whether short preamble is enabled * @NL80211_ATTR_BSS_SHORT_PREAMBLE: whether short preamble is enabled
* (u8, 0 or 1) * (u8, 0 or 1)
...@@ -265,6 +293,9 @@ enum nl80211_attrs { ...@@ -265,6 +293,9 @@ enum nl80211_attrs {
NL80211_ATTR_SUPPORTED_IFTYPES, NL80211_ATTR_SUPPORTED_IFTYPES,
NL80211_ATTR_REG_ALPHA2,
NL80211_ATTR_REG_RULES,
/* add attributes here, update the policy in nl80211.c */ /* add attributes here, update the policy in nl80211.c */
__NL80211_ATTR_AFTER_LAST, __NL80211_ATTR_AFTER_LAST,
...@@ -278,6 +309,7 @@ enum nl80211_attrs { ...@@ -278,6 +309,7 @@ enum nl80211_attrs {
#define NL80211_ATTR_HT_CAPABILITY NL80211_ATTR_HT_CAPABILITY #define NL80211_ATTR_HT_CAPABILITY NL80211_ATTR_HT_CAPABILITY
#define NL80211_MAX_SUPP_RATES 32 #define NL80211_MAX_SUPP_RATES 32
#define NL80211_MAX_SUPP_REG_RULES 32
#define NL80211_TKIP_DATA_OFFSET_ENCR_KEY 0 #define NL80211_TKIP_DATA_OFFSET_ENCR_KEY 0
#define NL80211_TKIP_DATA_OFFSET_TX_MIC_KEY 16 #define NL80211_TKIP_DATA_OFFSET_TX_MIC_KEY 16
#define NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY 24 #define NL80211_TKIP_DATA_OFFSET_RX_MIC_KEY 24
...@@ -472,6 +504,66 @@ enum nl80211_bitrate_attr { ...@@ -472,6 +504,66 @@ enum nl80211_bitrate_attr {
NL80211_BITRATE_ATTR_MAX = __NL80211_BITRATE_ATTR_AFTER_LAST - 1 NL80211_BITRATE_ATTR_MAX = __NL80211_BITRATE_ATTR_AFTER_LAST - 1
}; };
/**
* enum nl80211_reg_rule_attr - regulatory rule attributes
* @NL80211_ATTR_REG_RULE_FLAGS: a set of flags which specify additional
* considerations for a given frequency range. These are the
* &enum nl80211_reg_rule_flags.
* @NL80211_ATTR_FREQ_RANGE_START: starting frequencry for the regulatory
* rule in KHz. This is not a center of frequency but an actual regulatory
* band edge.
* @NL80211_ATTR_FREQ_RANGE_END: ending frequency for the regulatory rule
* in KHz. This is not a center a frequency but an actual regulatory
* band edge.
* @NL80211_ATTR_FREQ_RANGE_MAX_BW: maximum allowed bandwidth for this
* frequency range, in KHz.
* @NL80211_ATTR_POWER_RULE_MAX_ANT_GAIN: the maximum allowed antenna gain
* for a given frequency range. The value is in mBi (100 * dBi).
* If you don't have one then don't send this.
* @NL80211_ATTR_POWER_RULE_MAX_EIRP: the maximum allowed EIRP for
* a given frequency range. The value is in mBm (100 * dBm).
*/
enum nl80211_reg_rule_attr {
__NL80211_REG_RULE_ATTR_INVALID,
NL80211_ATTR_REG_RULE_FLAGS,
NL80211_ATTR_FREQ_RANGE_START,
NL80211_ATTR_FREQ_RANGE_END,
NL80211_ATTR_FREQ_RANGE_MAX_BW,
NL80211_ATTR_POWER_RULE_MAX_ANT_GAIN,
NL80211_ATTR_POWER_RULE_MAX_EIRP,
/* keep last */
__NL80211_REG_RULE_ATTR_AFTER_LAST,
NL80211_REG_RULE_ATTR_MAX = __NL80211_REG_RULE_ATTR_AFTER_LAST - 1
};
/**
* enum nl80211_reg_rule_flags - regulatory rule flags
*
* @NL80211_RRF_NO_OFDM: OFDM modulation not allowed
* @NL80211_RRF_NO_CCK: CCK modulation not allowed
* @NL80211_RRF_NO_INDOOR: indoor operation not allowed
* @NL80211_RRF_NO_OUTDOOR: outdoor operation not allowed
* @NL80211_RRF_DFS: DFS support is required to be used
* @NL80211_RRF_PTP_ONLY: this is only for Point To Point links
* @NL80211_RRF_PTMP_ONLY: this is only for Point To Multi Point links
* @NL80211_RRF_PASSIVE_SCAN: passive scan is required
* @NL80211_RRF_NO_IBSS: no IBSS is allowed
*/
enum nl80211_reg_rule_flags {
NL80211_RRF_NO_OFDM = 1<<0,
NL80211_RRF_NO_CCK = 1<<1,
NL80211_RRF_NO_INDOOR = 1<<2,
NL80211_RRF_NO_OUTDOOR = 1<<3,
NL80211_RRF_DFS = 1<<4,
NL80211_RRF_PTP_ONLY = 1<<5,
NL80211_RRF_PTMP_ONLY = 1<<6,
NL80211_RRF_PASSIVE_SCAN = 1<<7,
NL80211_RRF_NO_IBSS = 1<<8,
};
/** /**
* enum nl80211_mntr_flags - monitor configuration flags * enum nl80211_mntr_flags - monitor configuration flags
* *
......
...@@ -287,6 +287,66 @@ struct bss_parameters { ...@@ -287,6 +287,66 @@ struct bss_parameters {
int use_short_slot_time; int use_short_slot_time;
}; };
/**
* enum reg_set_by - Indicates who is trying to set the regulatory domain
* @REGDOM_SET_BY_INIT: regulatory domain was set by initialization. We will be
* using a static world regulatory domain by default.
* @REGDOM_SET_BY_CORE: Core queried CRDA for a dynamic world regulatory domain.
* @REGDOM_SET_BY_USER: User asked the wireless core to set the
* regulatory domain.
* @REGDOM_SET_BY_DRIVER: a wireless drivers has hinted to the wireless core
* it thinks its knows the regulatory domain we should be in.
* @REGDOM_SET_BY_COUNTRY_IE: the wireless core has received an 802.11 country
* information element with regulatory information it thinks we
* should consider.
*/
enum reg_set_by {
REGDOM_SET_BY_INIT,
REGDOM_SET_BY_CORE,
REGDOM_SET_BY_USER,
REGDOM_SET_BY_DRIVER,
REGDOM_SET_BY_COUNTRY_IE,
};
struct ieee80211_freq_range {
u32 start_freq_khz;
u32 end_freq_khz;
u32 max_bandwidth_khz;
};
struct ieee80211_power_rule {
u32 max_antenna_gain;
u32 max_eirp;
};
struct ieee80211_reg_rule {
struct ieee80211_freq_range freq_range;
struct ieee80211_power_rule power_rule;
u32 flags;
};
struct ieee80211_regdomain {
u32 n_reg_rules;
char alpha2[2];
struct ieee80211_reg_rule reg_rules[];
};
#define MHZ_TO_KHZ(freq) (freq * 1000)
#define KHZ_TO_MHZ(freq) (freq / 1000)
#define DBI_TO_MBI(gain) (gain * 100)
#define MBI_TO_DBI(gain) (gain / 100)
#define DBM_TO_MBM(gain) (gain * 100)
#define MBM_TO_DBM(gain) (gain / 100)
#define REG_RULE(start, end, bw, gain, eirp, reg_flags) { \
.freq_range.start_freq_khz = (start) * 1000, \
.freq_range.end_freq_khz = (end) * 1000, \
.freq_range.max_bandwidth_khz = (bw) * 1000, \
.power_rule.max_antenna_gain = (gain) * 100, \
.power_rule.max_eirp = (eirp) * 100, \
.flags = reg_flags, \
}
/* from net/wireless.h */ /* from net/wireless.h */
struct wiphy; struct wiphy;
......
...@@ -833,6 +833,8 @@ struct ieee80211_hw { ...@@ -833,6 +833,8 @@ struct ieee80211_hw {
s8 max_signal; s8 max_signal;
}; };
struct ieee80211_hw *wiphy_to_hw(struct wiphy *wiphy);
/** /**
* SET_IEEE80211_DEV - set device for 802.11 hardware * SET_IEEE80211_DEV - set device for 802.11 hardware
* *
......
...@@ -60,6 +60,7 @@ enum ieee80211_channel_flags { ...@@ -60,6 +60,7 @@ enum ieee80211_channel_flags {
* with cfg80211. * with cfg80211.
* *
* @center_freq: center frequency in MHz * @center_freq: center frequency in MHz
* @max_bandwidth: maximum allowed bandwidth for this channel, in MHz
* @hw_value: hardware-specific value for the channel * @hw_value: hardware-specific value for the channel
* @flags: channel flags from &enum ieee80211_channel_flags. * @flags: channel flags from &enum ieee80211_channel_flags.
* @orig_flags: channel flags at registration time, used by regulatory * @orig_flags: channel flags at registration time, used by regulatory
...@@ -73,6 +74,7 @@ enum ieee80211_channel_flags { ...@@ -73,6 +74,7 @@ enum ieee80211_channel_flags {
struct ieee80211_channel { struct ieee80211_channel {
enum ieee80211_band band; enum ieee80211_band band;
u16 center_freq; u16 center_freq;
u8 max_bandwidth;
u16 hw_value; u16 hw_value;
u32 flags; u32 flags;
int max_antenna_gain; int max_antenna_gain;
...@@ -178,6 +180,7 @@ struct ieee80211_supported_band { ...@@ -178,6 +180,7 @@ struct ieee80211_supported_band {
* struct wiphy - wireless hardware description * struct wiphy - wireless hardware description
* @idx: the wiphy index assigned to this item * @idx: the wiphy index assigned to this item
* @class_dev: the class device representing /sys/class/ieee80211/<wiphy-name> * @class_dev: the class device representing /sys/class/ieee80211/<wiphy-name>
* @reg_notifier: the driver's regulatory notification callback
*/ */
struct wiphy { struct wiphy {
/* assign these fields before you register the wiphy */ /* assign these fields before you register the wiphy */
...@@ -197,6 +200,9 @@ struct wiphy { ...@@ -197,6 +200,9 @@ struct wiphy {
struct ieee80211_supported_band *bands[IEEE80211_NUM_BANDS]; struct ieee80211_supported_band *bands[IEEE80211_NUM_BANDS];
/* Lets us get back the wiphy on the callback */
int (*reg_notifier)(struct wiphy *wiphy, enum reg_set_by setby);
/* fields below are read-only, assigned by cfg80211 */ /* fields below are read-only, assigned by cfg80211 */
/* the item in /sys/class/ieee80211/ points to this, /* the item in /sys/class/ieee80211/ points to this,
...@@ -322,6 +328,58 @@ extern int ieee80211_frequency_to_channel(int freq); ...@@ -322,6 +328,58 @@ extern int ieee80211_frequency_to_channel(int freq);
*/ */
extern struct ieee80211_channel *__ieee80211_get_channel(struct wiphy *wiphy, extern struct ieee80211_channel *__ieee80211_get_channel(struct wiphy *wiphy,
int freq); int freq);
/**
* __regulatory_hint - hint to the wireless core a regulatory domain
* @wiphy: if a driver is providing the hint this is the driver's very
* own &struct wiphy
* @alpha2: the ISO/IEC 3166 alpha2 being claimed the regulatory domain
* should be in. If @rd is set this should be NULL
* @rd: a complete regulatory domain, if passed the caller need not worry
* about freeing it
*
* The Wireless subsystem can use this function to hint to the wireless core
* what it believes should be the current regulatory domain by
* giving it an ISO/IEC 3166 alpha2 country code it knows its regulatory
* domain should be in or by providing a completely build regulatory domain.
*
* Returns -EALREADY if *a regulatory domain* has already been set. Note that
* this could be by another driver. It is safe for drivers to continue if
* -EALREADY is returned, if drivers are not capable of world roaming they
* should not register more channels than they support. Right now we only
* support listening to the first driver hint. If the driver is capable
* of world roaming but wants to respect its own EEPROM mappings for
* specific regulatory domains it should register the @reg_notifier callback
* on the &struct wiphy. Returns 0 if the hint went through fine or through an
* intersection operation. Otherwise a standard error code is returned.
*
*/
extern int __regulatory_hint(struct wiphy *wiphy, enum reg_set_by set_by,
const char *alpha2, struct ieee80211_regdomain *rd);
/**
* regulatory_hint - driver hint to the wireless core a regulatory domain
* @wiphy: the driver's very own &struct wiphy
* @alpha2: the ISO/IEC 3166 alpha2 the driver claims its regulatory domain
* should be in. If @rd is set this should be NULL. Note that if you
* set this to NULL you should still set rd->alpha2 to some accepted
* alpha2.
* @rd: a complete regulatory domain provided by the driver. If passed
* the driver does not need to worry about freeing it.
*
* Wireless drivers can use this function to hint to the wireless core
* what it believes should be the current regulatory domain by
* giving it an ISO/IEC 3166 alpha2 country code it knows its regulatory
* domain should be in or by providing a completely build regulatory domain.
* If the driver provides an ISO/IEC 3166 alpha2 userspace will be queried
* for a regulatory domain structure for the respective country. If
* a regulatory domain is build and passed you should set the alpha2
* if possible, otherwise set it to the special value of "99" which tells
* the wireless core it is unknown. If you pass a built regulatory domain
* and we return non zero you are in charge of kfree()'ing the structure.
*
* See __regulatory_hint() documentation for possible return values.
*/
extern int regulatory_hint(struct wiphy *wiphy,
const char *alpha2, struct ieee80211_regdomain *rd);
/** /**
* ieee80211_get_channel - get channel struct from wiphy for specified frequency * ieee80211_get_channel - get channel struct from wiphy for specified frequency
......
...@@ -17,6 +17,13 @@ ...@@ -17,6 +17,13 @@
#include "rate.h" #include "rate.h"
#include "mesh.h" #include "mesh.h"
struct ieee80211_hw *wiphy_to_hw(struct wiphy *wiphy)
{
struct ieee80211_local *local = wiphy_priv(wiphy);
return &local->hw;
}
EXPORT_SYMBOL(wiphy_to_hw);
static enum ieee80211_if_types static enum ieee80211_if_types
nl80211_type_to_mac80211_type(enum nl80211_iftype type) nl80211_type_to_mac80211_type(enum nl80211_iftype type)
{ {
......
...@@ -14,6 +14,38 @@ config NL80211 ...@@ -14,6 +14,38 @@ config NL80211
If unsure, say Y. If unsure, say Y.
config WIRELESS_OLD_REGULATORY
bool "Old wireless static regulatory defintions"
default n
---help---
This option enables the old static regulatory information
and uses it within the new framework. This is available
temporarily as an option to help prevent immediate issues
due to the switch to the new regulatory framework which
does require a new userspace application which has the
database of regulatory information (CRDA) and another for
setting regulatory domains (iw).
For more information see:
http://wireless.kernel.org/en/developers/Regulatory/CRDA
http://wireless.kernel.org/en/users/Documentation/iw
It is important to note though that if you *do* have CRDA present
and if this option is enabled CRDA *will* be called to update the
regulatory domain (for US and JP only). Support for letting the user
set the regulatory domain through iw is also supported. This option
mainly exists to leave around for a kernel release some old static
regulatory domains that were defined and to keep around the old
ieee80211_regdom module parameter. This is being phased out and you
should stop using them ASAP.
Say N unless you cannot install a new userspace application
or have one currently depending on the ieee80211_regdom module
parameter and cannot port it to use the new userspace interfaces.
This is scheduled for removal for 2.6.29.
config WIRELESS_EXT config WIRELESS_EXT
bool "Wireless extensions" bool "Wireless extensions"
default n default n
......
...@@ -13,12 +13,14 @@ ...@@ -13,12 +13,14 @@
#include <linux/debugfs.h> #include <linux/debugfs.h>
#include <linux/notifier.h> #include <linux/notifier.h>
#include <linux/device.h> #include <linux/device.h>
#include <linux/list.h>
#include <net/genetlink.h> #include <net/genetlink.h>
#include <net/cfg80211.h> #include <net/cfg80211.h>
#include <net/wireless.h> #include <net/wireless.h>
#include "nl80211.h" #include "nl80211.h"
#include "core.h" #include "core.h"
#include "sysfs.h" #include "sysfs.h"
#include "reg.h"
/* name for sysfs, %d is appended */ /* name for sysfs, %d is appended */
#define PHY_NAME "phy" #define PHY_NAME "phy"
...@@ -27,6 +29,107 @@ MODULE_AUTHOR("Johannes Berg"); ...@@ -27,6 +29,107 @@ MODULE_AUTHOR("Johannes Berg");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("wireless configuration support"); MODULE_DESCRIPTION("wireless configuration support");
struct list_head regulatory_requests;
/* Central wireless core regulatory domains, we only need two,
* the current one and a world regulatory domain in case we have no
* information to give us an alpha2 */
struct ieee80211_regdomain *cfg80211_regdomain;
/* We keep a static world regulatory domain in case of the absence of CRDA */
const struct ieee80211_regdomain world_regdom = {
.n_reg_rules = 1,
.alpha2 = "00",
.reg_rules = {
REG_RULE(2402, 2472, 40, 6, 20,
NL80211_RRF_PASSIVE_SCAN |
NL80211_RRF_NO_IBSS),
}
};
#ifdef CONFIG_WIRELESS_OLD_REGULATORY
/* All this fucking static junk will be removed soon, so
* don't fucking count on it !@#$ */
static char *ieee80211_regdom = "US";
module_param(ieee80211_regdom, charp, 0444);
MODULE_PARM_DESC(ieee80211_regdom, "IEEE 802.11 regulatory domain code");
/* We assume 40 MHz bandwidth for the old regulatory work.
* We make emphasis we are using the exact same frequencies
* as before */
const struct ieee80211_regdomain us_regdom = {
.n_reg_rules = 6,
.alpha2 = "US",
.reg_rules = {
/* IEEE 802.11b/g, channels 1..11 */
REG_RULE(2412-20, 2462+20, 40, 6, 27, 0),
/* IEEE 802.11a, channel 36 */
REG_RULE(5180-20, 5180+20, 40, 6, 23, 0),
/* IEEE 802.11a, channel 40 */
REG_RULE(5200-20, 5200+20, 40, 6, 23, 0),
/* IEEE 802.11a, channel 44 */
REG_RULE(5220-20, 5220+20, 40, 6, 23, 0),
/* IEEE 802.11a, channels 48..64 */
REG_RULE(5240-20, 5320+20, 40, 6, 23, 0),
/* IEEE 802.11a, channels 149..165, outdoor */
REG_RULE(5745-20, 5825+20, 40, 6, 30, 0),
}
};
const struct ieee80211_regdomain jp_regdom = {
.n_reg_rules = 3,
.alpha2 = "JP",
.reg_rules = {
/* IEEE 802.11b/g, channels 1..14 */
REG_RULE(2412-20, 2484+20, 40, 6, 20, 0),
/* IEEE 802.11a, channels 34..48 */
REG_RULE(5170-20, 5240+20, 40, 6, 20,
NL80211_RRF_PASSIVE_SCAN),
/* IEEE 802.11a, channels 52..64 */
REG_RULE(5260-20, 5320+20, 40, 6, 20,
NL80211_RRF_NO_IBSS |
NL80211_RRF_DFS),
}
};
const struct ieee80211_regdomain eu_regdom = {
.n_reg_rules = 6,
/* This alpha2 is bogus, we leave it here just for stupid
* backward compatibility */
.alpha2 = "EU",
.reg_rules = {
/* IEEE 802.11b/g, channels 1..13 */
REG_RULE(2412-20, 2472+20, 40, 6, 20, 0),
/* IEEE 802.11a, channel 36 */
REG_RULE(5180-20, 5180+20, 40, 6, 23,
NL80211_RRF_PASSIVE_SCAN),
/* IEEE 802.11a, channel 40 */
REG_RULE(5200-20, 5200+20, 40, 6, 23,
NL80211_RRF_PASSIVE_SCAN),
/* IEEE 802.11a, channel 44 */
REG_RULE(5220-20, 5220+20, 40, 6, 23,
NL80211_RRF_PASSIVE_SCAN),
/* IEEE 802.11a, channels 48..64 */
REG_RULE(5240-20, 5320+20, 40, 6, 20,
NL80211_RRF_NO_IBSS |
NL80211_RRF_DFS),
/* IEEE 802.11a, channels 100..140 */
REG_RULE(5500-20, 5700+20, 40, 6, 30,
NL80211_RRF_NO_IBSS |
NL80211_RRF_DFS),
}
};
#endif
struct ieee80211_regdomain *cfg80211_world_regdom =
(struct ieee80211_regdomain *) &world_regdom;
LIST_HEAD(regulatory_requests);
DEFINE_MUTEX(cfg80211_reg_mutex);
/* RCU might be appropriate here since we usually /* RCU might be appropriate here since we usually
* only read the list, and that can happen quite * only read the list, and that can happen quite
* often because we need to do it for each command */ * often because we need to do it for each command */
...@@ -302,7 +405,9 @@ int wiphy_register(struct wiphy *wiphy) ...@@ -302,7 +405,9 @@ int wiphy_register(struct wiphy *wiphy)
ieee80211_set_bitrate_flags(wiphy); ieee80211_set_bitrate_flags(wiphy);
/* set up regulatory info */ /* set up regulatory info */
wiphy_update_regulatory(wiphy); mutex_lock(&cfg80211_reg_mutex);
wiphy_update_regulatory(wiphy, REGDOM_SET_BY_CORE);
mutex_unlock(&cfg80211_reg_mutex);
mutex_lock(&cfg80211_drv_mutex); mutex_lock(&cfg80211_drv_mutex);
...@@ -409,9 +514,35 @@ static struct notifier_block cfg80211_netdev_notifier = { ...@@ -409,9 +514,35 @@ static struct notifier_block cfg80211_netdev_notifier = {
.notifier_call = cfg80211_netdev_notifier_call, .notifier_call = cfg80211_netdev_notifier_call,
}; };
#ifdef CONFIG_WIRELESS_OLD_REGULATORY
const struct ieee80211_regdomain *static_regdom(char *alpha2)
{
if (alpha2[0] == 'U' && alpha2[1] == 'S')
return &us_regdom;
if (alpha2[0] == 'J' && alpha2[1] == 'P')
return &jp_regdom;
if (alpha2[0] == 'E' && alpha2[1] == 'U')
return &eu_regdom;
/* Default, as per the old rules */
return &us_regdom;
}
#endif
static int cfg80211_init(void) static int cfg80211_init(void)
{ {
int err = wiphy_sysfs_init(); int err;
#ifdef CONFIG_WIRELESS_OLD_REGULATORY
cfg80211_regdomain =
(struct ieee80211_regdomain *) static_regdom(ieee80211_regdom);
/* Used during reset_regdomains_static() */
cfg80211_world_regdom = cfg80211_regdomain;
#else
cfg80211_regdomain =
(struct ieee80211_regdomain *) cfg80211_world_regdom;
#endif
err = wiphy_sysfs_init();
if (err) if (err)
goto out_fail_sysfs; goto out_fail_sysfs;
...@@ -425,8 +556,33 @@ static int cfg80211_init(void) ...@@ -425,8 +556,33 @@ static int cfg80211_init(void)
ieee80211_debugfs_dir = debugfs_create_dir("ieee80211", NULL); ieee80211_debugfs_dir = debugfs_create_dir("ieee80211", NULL);
err = regulatory_init();
if (err)
goto out_fail_reg;
#ifdef CONFIG_WIRELESS_OLD_REGULATORY
printk(KERN_INFO "cfg80211: Using old static regulatory domain:\n");
print_regdomain_info(cfg80211_regdomain);
/* The old code still requests for a new regdomain and if
* you have CRDA you get it updated, otherwise you get
* stuck with the static values. We ignore "EU" code as
* that is not a valid ISO / IEC 3166 alpha2 */
if (ieee80211_regdom[0] != 'E' &&
ieee80211_regdom[1] != 'U')
err = __regulatory_hint(NULL, REGDOM_SET_BY_CORE,
ieee80211_regdom, NULL);
#else
err = __regulatory_hint(NULL, REGDOM_SET_BY_CORE, "00", NULL);
if (err)
printk(KERN_ERR "cfg80211: calling CRDA failed - "
"unable to update world regulatory domain, "
"using static definition\n");
#endif
return 0; return 0;
out_fail_reg:
debugfs_remove(ieee80211_debugfs_dir);
out_fail_nl80211: out_fail_nl80211:
unregister_netdevice_notifier(&cfg80211_netdev_notifier); unregister_netdevice_notifier(&cfg80211_netdev_notifier);
out_fail_notifier: out_fail_notifier:
...@@ -434,6 +590,7 @@ static int cfg80211_init(void) ...@@ -434,6 +590,7 @@ static int cfg80211_init(void)
out_fail_sysfs: out_fail_sysfs:
return err; return err;
} }
subsys_initcall(cfg80211_init); subsys_initcall(cfg80211_init);
static void cfg80211_exit(void) static void cfg80211_exit(void)
...@@ -442,5 +599,6 @@ static void cfg80211_exit(void) ...@@ -442,5 +599,6 @@ static void cfg80211_exit(void)
nl80211_exit(); nl80211_exit();
unregister_netdevice_notifier(&cfg80211_netdev_notifier); unregister_netdevice_notifier(&cfg80211_netdev_notifier);
wiphy_sysfs_exit(); wiphy_sysfs_exit();
regulatory_exit();
} }
module_exit(cfg80211_exit); module_exit(cfg80211_exit);
...@@ -79,6 +79,6 @@ extern int cfg80211_dev_rename(struct cfg80211_registered_device *drv, ...@@ -79,6 +79,6 @@ extern int cfg80211_dev_rename(struct cfg80211_registered_device *drv,
char *newname); char *newname);
void ieee80211_set_bitrate_flags(struct wiphy *wiphy); void ieee80211_set_bitrate_flags(struct wiphy *wiphy);
void wiphy_update_regulatory(struct wiphy *wiphy); void wiphy_update_regulatory(struct wiphy *wiphy, enum reg_set_by setby);
#endif /* __NET_WIRELESS_CORE_H */ #endif /* __NET_WIRELESS_CORE_H */
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
#include <net/cfg80211.h> #include <net/cfg80211.h>
#include "core.h" #include "core.h"
#include "nl80211.h" #include "nl80211.h"
#include "reg.h"
/* the netlink family */ /* the netlink family */
static struct genl_family nl80211_fam = { static struct genl_family nl80211_fam = {
...@@ -88,6 +89,9 @@ static struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] __read_mostly = { ...@@ -88,6 +89,9 @@ static struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] __read_mostly = {
.len = IEEE80211_MAX_MESH_ID_LEN }, .len = IEEE80211_MAX_MESH_ID_LEN },
[NL80211_ATTR_MPATH_NEXT_HOP] = { .type = NLA_U32 }, [NL80211_ATTR_MPATH_NEXT_HOP] = { .type = NLA_U32 },
[NL80211_ATTR_REG_ALPHA2] = { .type = NLA_STRING, .len = 2 },
[NL80211_ATTR_REG_RULES] = { .type = NLA_NESTED },
[NL80211_ATTR_BSS_CTS_PROT] = { .type = NLA_U8 }, [NL80211_ATTR_BSS_CTS_PROT] = { .type = NLA_U8 },
[NL80211_ATTR_BSS_SHORT_PREAMBLE] = { .type = NLA_U8 }, [NL80211_ATTR_BSS_SHORT_PREAMBLE] = { .type = NLA_U8 },
[NL80211_ATTR_BSS_SHORT_SLOT_TIME] = { .type = NLA_U8 }, [NL80211_ATTR_BSS_SHORT_SLOT_TIME] = { .type = NLA_U8 },
...@@ -1599,6 +1603,141 @@ static int nl80211_set_bss(struct sk_buff *skb, struct genl_info *info) ...@@ -1599,6 +1603,141 @@ static int nl80211_set_bss(struct sk_buff *skb, struct genl_info *info)
return err; return err;
} }
static const struct nla_policy
reg_rule_policy[NL80211_REG_RULE_ATTR_MAX + 1] = {
[NL80211_ATTR_REG_RULE_FLAGS] = { .type = NLA_U32 },
[NL80211_ATTR_FREQ_RANGE_START] = { .type = NLA_U32 },
[NL80211_ATTR_FREQ_RANGE_END] = { .type = NLA_U32 },
[NL80211_ATTR_FREQ_RANGE_MAX_BW] = { .type = NLA_U32 },
[NL80211_ATTR_POWER_RULE_MAX_ANT_GAIN] = { .type = NLA_U32 },
[NL80211_ATTR_POWER_RULE_MAX_EIRP] = { .type = NLA_U32 },
};
static int parse_reg_rule(struct nlattr *tb[],
struct ieee80211_reg_rule *reg_rule)
{
struct ieee80211_freq_range *freq_range = &reg_rule->freq_range;
struct ieee80211_power_rule *power_rule = &reg_rule->power_rule;
if (!tb[NL80211_ATTR_REG_RULE_FLAGS])
return -EINVAL;
if (!tb[NL80211_ATTR_FREQ_RANGE_START])
return -EINVAL;
if (!tb[NL80211_ATTR_FREQ_RANGE_END])
return -EINVAL;
if (!tb[NL80211_ATTR_FREQ_RANGE_MAX_BW])
return -EINVAL;
if (!tb[NL80211_ATTR_POWER_RULE_MAX_EIRP])
return -EINVAL;
reg_rule->flags = nla_get_u32(tb[NL80211_ATTR_REG_RULE_FLAGS]);
freq_range->start_freq_khz =
nla_get_u32(tb[NL80211_ATTR_FREQ_RANGE_START]);
freq_range->end_freq_khz =
nla_get_u32(tb[NL80211_ATTR_FREQ_RANGE_END]);
freq_range->max_bandwidth_khz =
nla_get_u32(tb[NL80211_ATTR_FREQ_RANGE_MAX_BW]);
power_rule->max_eirp =
nla_get_u32(tb[NL80211_ATTR_POWER_RULE_MAX_EIRP]);
if (tb[NL80211_ATTR_POWER_RULE_MAX_ANT_GAIN])
power_rule->max_antenna_gain =
nla_get_u32(tb[NL80211_ATTR_POWER_RULE_MAX_ANT_GAIN]);
return 0;
}
static int nl80211_req_set_reg(struct sk_buff *skb, struct genl_info *info)
{
int r;
char *data = NULL;
if (!info->attrs[NL80211_ATTR_REG_ALPHA2])
return -EINVAL;
data = nla_data(info->attrs[NL80211_ATTR_REG_ALPHA2]);
#ifdef CONFIG_WIRELESS_OLD_REGULATORY
/* We ignore world regdom requests with the old regdom setup */
if (is_world_regdom(data))
return -EINVAL;
#endif
mutex_lock(&cfg80211_drv_mutex);
r = __regulatory_hint(NULL, REGDOM_SET_BY_USER, data, NULL);
mutex_unlock(&cfg80211_drv_mutex);
return r;
}
static int nl80211_set_reg(struct sk_buff *skb, struct genl_info *info)
{
struct nlattr *tb[NL80211_REG_RULE_ATTR_MAX + 1];
struct nlattr *nl_reg_rule;
char *alpha2 = NULL;
int rem_reg_rules = 0, r = 0;
u32 num_rules = 0, rule_idx = 0, size_of_regd;
struct ieee80211_regdomain *rd = NULL;
if (!info->attrs[NL80211_ATTR_REG_ALPHA2])
return -EINVAL;
if (!info->attrs[NL80211_ATTR_REG_RULES])
return -EINVAL;
alpha2 = nla_data(info->attrs[NL80211_ATTR_REG_ALPHA2]);
nla_for_each_nested(nl_reg_rule, info->attrs[NL80211_ATTR_REG_RULES],
rem_reg_rules) {
num_rules++;
if (num_rules > NL80211_MAX_SUPP_REG_RULES)
goto bad_reg;
}
if (!reg_is_valid_request(alpha2))
return -EINVAL;
size_of_regd = sizeof(struct ieee80211_regdomain) +
(num_rules * sizeof(struct ieee80211_reg_rule));
rd = kzalloc(size_of_regd, GFP_KERNEL);
if (!rd)
return -ENOMEM;
rd->n_reg_rules = num_rules;
rd->alpha2[0] = alpha2[0];
rd->alpha2[1] = alpha2[1];
nla_for_each_nested(nl_reg_rule, info->attrs[NL80211_ATTR_REG_RULES],
rem_reg_rules) {
nla_parse(tb, NL80211_REG_RULE_ATTR_MAX,
nla_data(nl_reg_rule), nla_len(nl_reg_rule),
reg_rule_policy);
r = parse_reg_rule(tb, &rd->reg_rules[rule_idx]);
if (r)
goto bad_reg;
rule_idx++;
if (rule_idx > NL80211_MAX_SUPP_REG_RULES)
goto bad_reg;
}
BUG_ON(rule_idx != num_rules);
mutex_lock(&cfg80211_drv_mutex);
r = set_regdom(rd);
mutex_unlock(&cfg80211_drv_mutex);
if (r)
goto bad_reg;
return r;
bad_reg:
kfree(rd);
return -EINVAL;
}
static struct genl_ops nl80211_ops[] = { static struct genl_ops nl80211_ops[] = {
{ {
.cmd = NL80211_CMD_GET_WIPHY, .cmd = NL80211_CMD_GET_WIPHY,
...@@ -1736,6 +1875,18 @@ static struct genl_ops nl80211_ops[] = { ...@@ -1736,6 +1875,18 @@ static struct genl_ops nl80211_ops[] = {
.policy = nl80211_policy, .policy = nl80211_policy,
.flags = GENL_ADMIN_PERM, .flags = GENL_ADMIN_PERM,
}, },
{
.cmd = NL80211_CMD_SET_REG,
.doit = nl80211_set_reg,
.policy = nl80211_policy,
.flags = GENL_ADMIN_PERM,
},
{
.cmd = NL80211_CMD_REQ_SET_REG,
.doit = nl80211_req_set_reg,
.policy = nl80211_policy,
.flags = GENL_ADMIN_PERM,
},
}; };
/* multicast groups */ /* multicast groups */
......
...@@ -2,179 +2,758 @@ ...@@ -2,179 +2,758 @@
* Copyright 2002-2005, Instant802 Networks, Inc. * Copyright 2002-2005, Instant802 Networks, Inc.
* Copyright 2005-2006, Devicescape Software, Inc. * Copyright 2005-2006, Devicescape Software, Inc.
* Copyright 2007 Johannes Berg <johannes@sipsolutions.net> * Copyright 2007 Johannes Berg <johannes@sipsolutions.net>
* Copyright 2008 Luis R. Rodriguez <lrodriguz@atheros.com>
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as * it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation. * published by the Free Software Foundation.
*/ */
/* /**
* This regulatory domain control implementation is highly incomplete, it * DOC: Wireless regulatory infrastructure
* only exists for the purpose of not regressing mac80211.
*
* For now, drivers can restrict the set of allowed channels by either
* not registering those channels or setting the IEEE80211_CHAN_DISABLED
* flag; that flag will only be *set* by this code, never *cleared.
* *
* The usual implementation is for a driver to read a device EEPROM to * The usual implementation is for a driver to read a device EEPROM to
* determine which regulatory domain it should be operating under, then * determine which regulatory domain it should be operating under, then
* looking up the allowable channels in a driver-local table and finally * looking up the allowable channels in a driver-local table and finally
* registering those channels in the wiphy structure. * registering those channels in the wiphy structure.
* *
* Alternatively, drivers that trust the regulatory domain control here * Another set of compliance enforcement is for drivers to use their
* will register a complete set of capabilities and the control code * own compliance limits which can be stored on the EEPROM. The host
* will restrict the set by setting the IEEE80211_CHAN_* flags. * driver or firmware may ensure these are used.
*
* In addition to all this we provide an extra layer of regulatory
* conformance. For drivers which do not have any regulatory
* information CRDA provides the complete regulatory solution.
* For others it provides a community effort on further restrictions
* to enhance compliance.
*
* Note: When number of rules --> infinity we will not be able to
* index on alpha2 any more, instead we'll probably have to
* rely on some SHA1 checksum of the regdomain for example.
*
*/ */
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/list.h>
#include <linux/random.h>
#include <linux/nl80211.h>
#include <linux/platform_device.h>
#include <net/wireless.h> #include <net/wireless.h>
#include <net/cfg80211.h>
#include "core.h" #include "core.h"
#include "reg.h"
static char *ieee80211_regdom = "US"; /* To trigger userspace events */
module_param(ieee80211_regdom, charp, 0444); static struct platform_device *reg_pdev;
MODULE_PARM_DESC(ieee80211_regdom, "IEEE 802.11 regulatory domain code");
struct ieee80211_channel_range { /* Keep the ordering from large to small */
short start_freq; static u32 supported_bandwidths[] = {
short end_freq; MHZ_TO_KHZ(40),
int max_power; MHZ_TO_KHZ(20),
int max_antenna_gain;
u32 flags;
}; };
struct ieee80211_regdomain { bool is_world_regdom(char *alpha2)
const char *code; {
const struct ieee80211_channel_range *ranges; if (!alpha2)
int n_ranges; return false;
}; if (alpha2[0] == '0' && alpha2[1] == '0')
return true;
return false;
}
#define RANGE_PWR(_start, _end, _pwr, _ag, _flags) \ static bool is_alpha2_set(char *alpha2)
{ _start, _end, _pwr, _ag, _flags } {
if (!alpha2)
return false;
if (alpha2[0] != 0 && alpha2[1] != 0)
return true;
return false;
}
static bool is_alpha_upper(char letter)
{
/* ASCII A - Z */
if (letter >= 65 && letter <= 90)
return true;
return false;
}
/* static bool is_unknown_alpha2(char *alpha2)
* Ideally, in the future, these definitions will be loaded from a {
* userspace table via some daemon. if (!alpha2)
*/ return false;
static const struct ieee80211_channel_range ieee80211_US_channels[] = { /* Special case where regulatory domain was built by driver
/* IEEE 802.11b/g, channels 1..11 */ * but a specific alpha2 cannot be determined */
RANGE_PWR(2412, 2462, 27, 6, 0), if (alpha2[0] == '9' && alpha2[1] == '9')
/* IEEE 802.11a, channel 36*/ return true;
RANGE_PWR(5180, 5180, 23, 6, 0), return false;
/* IEEE 802.11a, channel 40*/ }
RANGE_PWR(5200, 5200, 23, 6, 0),
/* IEEE 802.11a, channel 44*/
RANGE_PWR(5220, 5220, 23, 6, 0),
/* IEEE 802.11a, channels 48..64 */
RANGE_PWR(5240, 5320, 23, 6, 0),
/* IEEE 802.11a, channels 149..165, outdoor */
RANGE_PWR(5745, 5825, 30, 6, 0),
};
static const struct ieee80211_channel_range ieee80211_JP_channels[] = { static bool is_an_alpha2(char *alpha2)
/* IEEE 802.11b/g, channels 1..14 */ {
RANGE_PWR(2412, 2484, 20, 6, 0), if (!alpha2)
/* IEEE 802.11a, channels 34..48 */ return false;
RANGE_PWR(5170, 5240, 20, 6, IEEE80211_CHAN_PASSIVE_SCAN), if (is_alpha_upper(alpha2[0]) && is_alpha_upper(alpha2[1]))
/* IEEE 802.11a, channels 52..64 */ return true;
RANGE_PWR(5260, 5320, 20, 6, IEEE80211_CHAN_NO_IBSS | return false;
IEEE80211_CHAN_RADAR), }
};
static const struct ieee80211_channel_range ieee80211_EU_channels[] = { static bool alpha2_equal(char *alpha2_x, char *alpha2_y)
/* IEEE 802.11b/g, channels 1..13 */ {
RANGE_PWR(2412, 2472, 20, 6, 0), if (!alpha2_x || !alpha2_y)
/* IEEE 802.11a, channel 36*/ return false;
RANGE_PWR(5180, 5180, 23, 6, IEEE80211_CHAN_PASSIVE_SCAN), if (alpha2_x[0] == alpha2_y[0] &&
/* IEEE 802.11a, channel 40*/ alpha2_x[1] == alpha2_y[1])
RANGE_PWR(5200, 5200, 23, 6, IEEE80211_CHAN_PASSIVE_SCAN), return true;
/* IEEE 802.11a, channel 44*/ return false;
RANGE_PWR(5220, 5220, 23, 6, IEEE80211_CHAN_PASSIVE_SCAN), }
/* IEEE 802.11a, channels 48..64 */
RANGE_PWR(5240, 5320, 23, 6, IEEE80211_CHAN_NO_IBSS |
IEEE80211_CHAN_RADAR),
/* IEEE 802.11a, channels 100..140 */
RANGE_PWR(5500, 5700, 30, 6, IEEE80211_CHAN_NO_IBSS |
IEEE80211_CHAN_RADAR),
};
#define REGDOM(_code) \ static bool regdom_changed(char *alpha2)
{ \ {
.code = __stringify(_code), \ if (!cfg80211_regdomain)
.ranges = ieee80211_ ##_code## _channels, \ return true;
.n_ranges = ARRAY_SIZE(ieee80211_ ##_code## _channels), \ if (alpha2_equal(cfg80211_regdomain->alpha2, alpha2))
return false;
return true;
}
/* This lets us keep regulatory code which is updated on a regulatory
* basis in userspace. */
static int call_crda(const char *alpha2)
{
char country_env[9 + 2] = "COUNTRY=";
char *envp[] = {
country_env,
NULL
};
if (!is_world_regdom((char *) alpha2))
printk(KERN_INFO "cfg80211: Calling CRDA for country: %c%c\n",
alpha2[0], alpha2[1]);
else
#ifdef CONFIG_WIRELESS_OLD_REGULATORY
return -EINVAL;
#else
printk(KERN_INFO "cfg80211: Calling CRDA to update world "
"regulatory domain\n");
#endif
country_env[8] = alpha2[0];
country_env[9] = alpha2[1];
return kobject_uevent_env(&reg_pdev->dev.kobj, KOBJ_CHANGE, envp);
}
/* This has the logic which determines when a new request
* should be ignored. */
static int ignore_request(struct wiphy *wiphy, enum reg_set_by set_by,
char *alpha2, struct ieee80211_regdomain *rd)
{
struct regulatory_request *last_request = NULL;
/* All initial requests are respected */
if (list_empty(&regulatory_requests))
return 0;
last_request = list_first_entry(&regulatory_requests,
struct regulatory_request, list);
switch (set_by) {
case REGDOM_SET_BY_INIT:
return -EINVAL;
case REGDOM_SET_BY_CORE:
/* Always respect new wireless core hints, should only
* come in for updating the world regulatory domain at init
* anyway */
return 0;
case REGDOM_SET_BY_COUNTRY_IE:
if (last_request->initiator == set_by) {
if (last_request->wiphy != wiphy) {
/* Two cards with two APs claiming different
* different Country IE alpha2s!
* You're special!! */
if (!alpha2_equal(last_request->alpha2,
cfg80211_regdomain->alpha2)) {
/* XXX: Deal with conflict, consider
* building a new one out of the
* intersection */
WARN_ON(1);
return -EOPNOTSUPP;
}
return -EALREADY;
}
/* Two consecutive Country IE hints on the same wiphy */
if (!alpha2_equal(cfg80211_regdomain->alpha2, alpha2))
return 0;
return -EALREADY;
}
if (WARN_ON(!is_alpha2_set(alpha2) || !is_an_alpha2(alpha2)),
"Invalid Country IE regulatory hint passed "
"to the wireless core\n")
return -EINVAL;
/* We ignore Country IE hints for now, as we haven't yet
* added the dot11MultiDomainCapabilityEnabled flag
* for wiphys */
return 1;
case REGDOM_SET_BY_DRIVER:
BUG_ON(!wiphy);
if (last_request->initiator == set_by) {
/* Two separate drivers hinting different things,
* this is possible if you have two devices present
* on a system with different EEPROM regulatory
* readings. XXX: Do intersection, we support only
* the first regulatory hint for now */
if (last_request->wiphy != wiphy)
return -EALREADY;
if (rd)
return -EALREADY;
/* Driver should not be trying to hint different
* regulatory domains! */
BUG_ON(!alpha2_equal(alpha2,
cfg80211_regdomain->alpha2));
return -EALREADY;
} }
if (last_request->initiator == REGDOM_SET_BY_CORE)
return 0;
/* XXX: Handle intersection, and add the
* dot11MultiDomainCapabilityEnabled flag to wiphy. For now
* we assume the driver has this set to false, following the
* 802.11d dot11MultiDomainCapabilityEnabled documentation */
if (last_request->initiator == REGDOM_SET_BY_COUNTRY_IE)
return 0;
return 0;
case REGDOM_SET_BY_USER:
if (last_request->initiator == set_by ||
last_request->initiator == REGDOM_SET_BY_CORE)
return 0;
/* Drivers can use their wiphy's reg_notifier()
* to override any information */
if (last_request->initiator == REGDOM_SET_BY_DRIVER)
return 0;
/* XXX: Handle intersection */
if (last_request->initiator == REGDOM_SET_BY_COUNTRY_IE)
return -EOPNOTSUPP;
return 0;
default:
return -EINVAL;
}
}
static const struct ieee80211_regdomain ieee80211_regdoms[] = { static bool __reg_is_valid_request(char *alpha2,
REGDOM(US), struct regulatory_request **request)
REGDOM(JP), {
REGDOM(EU), struct regulatory_request *req;
}; if (list_empty(&regulatory_requests))
return false;
list_for_each_entry(req, &regulatory_requests, list) {
if (alpha2_equal(req->alpha2, alpha2)) {
*request = req;
return true;
}
}
return false;
}
/* Used by nl80211 before kmalloc'ing our regulatory domain */
bool reg_is_valid_request(char *alpha2)
{
struct regulatory_request *request = NULL;
return __reg_is_valid_request(alpha2, &request);
}
static const struct ieee80211_regdomain *get_regdom(void) /* Sanity check on a regulatory rule */
static bool is_valid_reg_rule(struct ieee80211_reg_rule *rule)
{ {
static const struct ieee80211_channel_range struct ieee80211_freq_range *freq_range = &rule->freq_range;
ieee80211_world_channels[] = { u32 freq_diff;
/* IEEE 802.11b/g, channels 1..11 */
RANGE_PWR(2412, 2462, 27, 6, 0),
};
static const struct ieee80211_regdomain regdom_world = REGDOM(world);
int i;
for (i = 0; i < ARRAY_SIZE(ieee80211_regdoms); i++) if (freq_range->start_freq_khz == 0 || freq_range->end_freq_khz == 0)
if (strcmp(ieee80211_regdom, ieee80211_regdoms[i].code) == 0) return false;
return &ieee80211_regdoms[i];
return &regdom_world; if (freq_range->start_freq_khz > freq_range->end_freq_khz)
return false;
freq_diff = freq_range->end_freq_khz - freq_range->start_freq_khz;
if (freq_range->max_bandwidth_khz > freq_diff)
return false;
return true;
} }
static bool is_valid_rd(struct ieee80211_regdomain *rd)
{
struct ieee80211_reg_rule *reg_rule = NULL;
unsigned int i;
if (!rd->n_reg_rules)
return false;
static void handle_channel(struct ieee80211_channel *chan, for (i = 0; i < rd->n_reg_rules; i++) {
const struct ieee80211_regdomain *rd) reg_rule = &rd->reg_rules[i];
if (!is_valid_reg_rule(reg_rule))
return false;
}
return true;
}
/* Returns value in KHz */
static u32 freq_max_bandwidth(const struct ieee80211_freq_range *freq_range,
u32 freq)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(supported_bandwidths); i++) {
u32 start_freq_khz = freq - supported_bandwidths[i]/2;
u32 end_freq_khz = freq + supported_bandwidths[i]/2;
if (start_freq_khz >= freq_range->start_freq_khz &&
end_freq_khz <= freq_range->end_freq_khz)
return supported_bandwidths[i];
}
return 0;
}
/* XXX: add support for the rest of enum nl80211_reg_rule_flags, we may
* want to just have the channel structure use these */
static u32 map_regdom_flags(u32 rd_flags)
{
u32 channel_flags = 0;
if (rd_flags & NL80211_RRF_PASSIVE_SCAN)
channel_flags |= IEEE80211_CHAN_PASSIVE_SCAN;
if (rd_flags & NL80211_RRF_NO_IBSS)
channel_flags |= IEEE80211_CHAN_NO_IBSS;
if (rd_flags & NL80211_RRF_DFS)
channel_flags |= IEEE80211_CHAN_RADAR;
return channel_flags;
}
/**
* freq_reg_info - get regulatory information for the given frequency
* @center_freq: Frequency in KHz for which we want regulatory information for
* @bandwidth: the bandwidth requirement you have in KHz, if you do not have one
* you can set this to 0. If this frequency is allowed we then set
* this value to the maximum allowed bandwidth.
* @reg_rule: the regulatory rule which we have for this frequency
*
* Use this function to get the regulatory rule for a specific frequency.
*/
static int freq_reg_info(u32 center_freq, u32 *bandwidth,
const struct ieee80211_reg_rule **reg_rule)
{ {
int i; int i;
u32 flags = chan->orig_flags; u32 max_bandwidth = 0;
const struct ieee80211_channel_range *rg = NULL;
if (!cfg80211_regdomain)
return -EINVAL;
for (i = 0; i < rd->n_ranges; i++) { for (i = 0; i < cfg80211_regdomain->n_reg_rules; i++) {
if (rd->ranges[i].start_freq <= chan->center_freq && const struct ieee80211_reg_rule *rr;
chan->center_freq <= rd->ranges[i].end_freq) { const struct ieee80211_freq_range *fr = NULL;
rg = &rd->ranges[i]; const struct ieee80211_power_rule *pr = NULL;
rr = &cfg80211_regdomain->reg_rules[i];
fr = &rr->freq_range;
pr = &rr->power_rule;
max_bandwidth = freq_max_bandwidth(fr, center_freq);
if (max_bandwidth && *bandwidth <= max_bandwidth) {
*reg_rule = rr;
*bandwidth = max_bandwidth;
break; break;
} }
} }
if (!rg) { return !max_bandwidth;
/* not found */ }
static void handle_channel(struct ieee80211_channel *chan)
{
int r;
u32 flags = chan->orig_flags;
u32 max_bandwidth = 0;
const struct ieee80211_reg_rule *reg_rule = NULL;
const struct ieee80211_power_rule *power_rule = NULL;
r = freq_reg_info(MHZ_TO_KHZ(chan->center_freq),
&max_bandwidth, &reg_rule);
if (r) {
flags |= IEEE80211_CHAN_DISABLED; flags |= IEEE80211_CHAN_DISABLED;
chan->flags = flags; chan->flags = flags;
return; return;
} }
chan->flags = flags; power_rule = &reg_rule->power_rule;
chan->flags = flags | map_regdom_flags(reg_rule->flags);
chan->max_antenna_gain = min(chan->orig_mag, chan->max_antenna_gain = min(chan->orig_mag,
rg->max_antenna_gain); (int) MBI_TO_DBI(power_rule->max_antenna_gain));
chan->max_bandwidth = KHZ_TO_MHZ(max_bandwidth);
if (chan->orig_mpwr) if (chan->orig_mpwr)
chan->max_power = min(chan->orig_mpwr, rg->max_power); chan->max_power = min(chan->orig_mpwr,
(int) MBM_TO_DBM(power_rule->max_eirp));
else else
chan->max_power = rg->max_power; chan->max_power = (int) MBM_TO_DBM(power_rule->max_eirp);
} }
static void handle_band(struct ieee80211_supported_band *sband, static void handle_band(struct ieee80211_supported_band *sband)
const struct ieee80211_regdomain *rd)
{ {
int i; int i;
for (i = 0; i < sband->n_channels; i++) for (i = 0; i < sband->n_channels; i++)
handle_channel(&sband->channels[i], rd); handle_channel(&sband->channels[i]);
} }
void wiphy_update_regulatory(struct wiphy *wiphy) static void update_all_wiphy_regulatory(enum reg_set_by setby)
{ {
enum ieee80211_band band; struct cfg80211_registered_device *drv;
const struct ieee80211_regdomain *rd = get_regdom();
list_for_each_entry(drv, &cfg80211_drv_list, list)
wiphy_update_regulatory(&drv->wiphy, setby);
}
for (band = 0; band < IEEE80211_NUM_BANDS; band++) void wiphy_update_regulatory(struct wiphy *wiphy, enum reg_set_by setby)
{
enum ieee80211_band band;
for (band = 0; band < IEEE80211_NUM_BANDS; band++) {
if (wiphy->bands[band]) if (wiphy->bands[band])
handle_band(wiphy->bands[band], rd); handle_band(wiphy->bands[band]);
if (wiphy->reg_notifier)
wiphy->reg_notifier(wiphy, setby);
}
}
/* Caller must hold &cfg80211_drv_mutex */
int __regulatory_hint(struct wiphy *wiphy, enum reg_set_by set_by,
const char *alpha2, struct ieee80211_regdomain *rd)
{
struct regulatory_request *request;
char *rd_alpha2;
int r = 0;
r = ignore_request(wiphy, set_by, (char *) alpha2, rd);
if (r)
return r;
if (rd)
rd_alpha2 = rd->alpha2;
else
rd_alpha2 = (char *) alpha2;
switch (set_by) {
case REGDOM_SET_BY_CORE:
case REGDOM_SET_BY_COUNTRY_IE:
case REGDOM_SET_BY_DRIVER:
case REGDOM_SET_BY_USER:
request = kzalloc(sizeof(struct regulatory_request),
GFP_KERNEL);
if (!request)
return -ENOMEM;
request->alpha2[0] = rd_alpha2[0];
request->alpha2[1] = rd_alpha2[1];
request->initiator = set_by;
request->wiphy = wiphy;
list_add_tail(&request->list, &regulatory_requests);
if (rd)
break;
r = call_crda(alpha2);
#ifndef CONFIG_WIRELESS_OLD_REGULATORY
if (r)
printk(KERN_ERR "cfg80211: Failed calling CRDA\n");
#endif
break;
default:
r = -ENOTSUPP;
break;
}
return r;
}
/* If rd is not NULL and if this call fails the caller must free it */
int regulatory_hint(struct wiphy *wiphy, const char *alpha2,
struct ieee80211_regdomain *rd)
{
int r;
BUG_ON(!rd && !alpha2);
mutex_lock(&cfg80211_drv_mutex);
r = __regulatory_hint(wiphy, REGDOM_SET_BY_DRIVER, alpha2, rd);
if (r || !rd)
goto unlock_and_exit;
/* If the driver passed a regulatory domain we skipped asking
* userspace for one so we can now go ahead and set it */
r = set_regdom(rd);
unlock_and_exit:
mutex_unlock(&cfg80211_drv_mutex);
return r;
}
EXPORT_SYMBOL(regulatory_hint);
static void print_rd_rules(struct ieee80211_regdomain *rd)
{
unsigned int i;
struct ieee80211_reg_rule *reg_rule = NULL;
struct ieee80211_freq_range *freq_range = NULL;
struct ieee80211_power_rule *power_rule = NULL;
printk(KERN_INFO "\t(start_freq - end_freq @ bandwidth), "
"(max_antenna_gain, max_eirp)\n");
for (i = 0; i < rd->n_reg_rules; i++) {
reg_rule = &rd->reg_rules[i];
freq_range = &reg_rule->freq_range;
power_rule = &reg_rule->power_rule;
/* There may not be documentation for max antenna gain
* in certain regions */
if (power_rule->max_antenna_gain)
printk(KERN_INFO "\t(%d KHz - %d KHz @ %d KHz), "
"(%d mBi, %d mBm)\n",
freq_range->start_freq_khz,
freq_range->end_freq_khz,
freq_range->max_bandwidth_khz,
power_rule->max_antenna_gain,
power_rule->max_eirp);
else
printk(KERN_INFO "\t(%d KHz - %d KHz @ %d KHz), "
"(N/A, %d mBm)\n",
freq_range->start_freq_khz,
freq_range->end_freq_khz,
freq_range->max_bandwidth_khz,
power_rule->max_eirp);
}
}
static void print_regdomain(struct ieee80211_regdomain *rd)
{
if (is_world_regdom(rd->alpha2))
printk(KERN_INFO "cfg80211: World regulatory "
"domain updated:\n");
else {
if (is_unknown_alpha2(rd->alpha2))
printk(KERN_INFO "cfg80211: Regulatory domain "
"changed to driver built-in settings "
"(unknown country)\n");
else
printk(KERN_INFO "cfg80211: Regulatory domain "
"changed to country: %c%c\n",
rd->alpha2[0], rd->alpha2[1]);
}
print_rd_rules(rd);
}
void print_regdomain_info(struct ieee80211_regdomain *rd)
{
printk(KERN_INFO "cfg80211: Regulatory domain: %c%c\n",
rd->alpha2[0], rd->alpha2[1]);
print_rd_rules(rd);
}
#ifdef CONFIG_WIRELESS_OLD_REGULATORY
static bool is_old_static_regdom(struct ieee80211_regdomain *rd)
{
if (rd == &us_regdom || rd == &jp_regdom || rd == &eu_regdom)
return true;
return false;
}
/* The old crap never deals with a world regulatory domain, it only
* deals with the static regulatory domain passed and if possible
* an updated "US" or "JP" regulatory domain. We do however store the
* old static regulatory domain in cfg80211_world_regdom for convenience
* of use here */
static void reset_regdomains_static(void)
{
if (!is_old_static_regdom(cfg80211_regdomain))
kfree(cfg80211_regdomain);
/* This is setting the regdom to the old static regdom */
cfg80211_regdomain =
(struct ieee80211_regdomain *) cfg80211_world_regdom;
}
#else
static void reset_regdomains(void)
{
if (cfg80211_world_regdom && cfg80211_world_regdom != &world_regdom) {
if (cfg80211_world_regdom == cfg80211_regdomain) {
kfree(cfg80211_regdomain);
} else {
kfree(cfg80211_world_regdom);
kfree(cfg80211_regdomain);
}
} else if (cfg80211_regdomain && cfg80211_regdomain != &world_regdom)
kfree(cfg80211_regdomain);
cfg80211_world_regdom = (struct ieee80211_regdomain *) &world_regdom;
cfg80211_regdomain = NULL;
}
/* Dynamic world regulatory domain requested by the wireless
* core upon initialization */
static void update_world_regdomain(struct ieee80211_regdomain *rd)
{
BUG_ON(list_empty(&regulatory_requests));
reset_regdomains();
cfg80211_world_regdom = rd;
cfg80211_regdomain = rd;
}
#endif
static int __set_regdom(struct ieee80211_regdomain *rd)
{
struct regulatory_request *request = NULL;
/* Some basic sanity checks first */
#ifdef CONFIG_WIRELESS_OLD_REGULATORY
/* We ignore the world regdom with the old static regdomains setup
* as there is no point to it with satic regulatory definitions :(
* Don't worry this shit will be removed soon... */
if (is_world_regdom(rd->alpha2))
return -EINVAL;
#else
if (is_world_regdom(rd->alpha2)) {
if (WARN_ON(!__reg_is_valid_request(rd->alpha2, &request)))
return -EINVAL;
update_world_regdomain(rd);
return 0;
}
#endif
if (!is_alpha2_set(rd->alpha2) && !is_an_alpha2(rd->alpha2) &&
!is_unknown_alpha2(rd->alpha2))
return -EINVAL;
if (list_empty(&regulatory_requests))
return -EINVAL;
#ifdef CONFIG_WIRELESS_OLD_REGULATORY
/* Static "US" and "JP" will be overridden, but just once */
if (!is_old_static_regdom(cfg80211_regdomain) &&
!regdom_changed(rd->alpha2))
return -EINVAL;
#else
if (!regdom_changed(rd->alpha2))
return -EINVAL;
#endif
/* Now lets set the regulatory domain, update all driver channels
* and finally inform them of what we have done, in case they want
* to review or adjust their own settings based on their own
* internal EEPROM data */
if (WARN_ON(!__reg_is_valid_request(rd->alpha2, &request)))
return -EINVAL;
#ifdef CONFIG_WIRELESS_OLD_REGULATORY
reset_regdomains_static();
#else
reset_regdomains();
#endif
/* Country IE parsing coming soon */
switch (request->initiator) {
case REGDOM_SET_BY_CORE:
case REGDOM_SET_BY_DRIVER:
case REGDOM_SET_BY_USER:
if (!is_valid_rd(rd)) {
printk(KERN_ERR "cfg80211: Invalid "
"regulatory domain detected:\n");
print_regdomain_info(rd);
return -EINVAL;
}
break;
case REGDOM_SET_BY_COUNTRY_IE: /* Not yet */
WARN_ON(1);
default:
return -EOPNOTSUPP;
}
/* Tada! */
cfg80211_regdomain = rd;
request->granted = 1;
return 0;
}
/* Use this call to set the current regulatory domain. Conflicts with
* multiple drivers can be ironed out later. Caller must've already
* kmalloc'd the rd structure. If this calls fails you should kfree()
* the passed rd. Caller must hold cfg80211_drv_mutex */
int set_regdom(struct ieee80211_regdomain *rd)
{
struct regulatory_request *this_request = NULL, *prev_request = NULL;
int r;
if (!list_empty(&regulatory_requests))
prev_request = list_first_entry(&regulatory_requests,
struct regulatory_request, list);
/* Note that this doesn't update the wiphys, this is done below */
r = __set_regdom(rd);
if (r)
return r;
BUG_ON((!__reg_is_valid_request(rd->alpha2, &this_request)));
/* The initial standard core update of the world regulatory domain, no
* need to keep that request info around if it didn't fail. */
if (is_world_regdom(rd->alpha2) &&
this_request->initiator == REGDOM_SET_BY_CORE &&
this_request->granted) {
list_del(&this_request->list);
kfree(this_request);
this_request = NULL;
}
/* Remove old requests, we only leave behind the last one */
if (prev_request) {
list_del(&prev_request->list);
kfree(prev_request);
prev_request = NULL;
}
/* This would make this whole thing pointless */
BUG_ON(rd != cfg80211_regdomain);
/* update all wiphys now with the new established regulatory domain */
update_all_wiphy_regulatory(this_request->initiator);
print_regdomain(rd);
return r;
}
int regulatory_init(void)
{
reg_pdev = platform_device_register_simple("regulatory", 0, NULL, 0);
if (IS_ERR(reg_pdev))
return PTR_ERR(reg_pdev);
return 0;
}
void regulatory_exit(void)
{
struct regulatory_request *req, *req_tmp;
mutex_lock(&cfg80211_drv_mutex);
#ifdef CONFIG_WIRELESS_OLD_REGULATORY
reset_regdomains_static();
#else
reset_regdomains();
#endif
list_for_each_entry_safe(req, req_tmp, &regulatory_requests, list) {
list_del(&req->list);
kfree(req);
}
platform_device_unregister(reg_pdev);
mutex_unlock(&cfg80211_drv_mutex);
} }
#ifndef __NET_WIRELESS_REG_H
#define __NET_WIRELESS_REG_H
extern const struct ieee80211_regdomain world_regdom;
#ifdef CONFIG_WIRELESS_OLD_REGULATORY
extern const struct ieee80211_regdomain us_regdom;
extern const struct ieee80211_regdomain jp_regdom;
extern const struct ieee80211_regdomain eu_regdom;
#endif
extern struct ieee80211_regdomain *cfg80211_regdomain;
extern struct ieee80211_regdomain *cfg80211_world_regdom;
extern struct list_head regulatory_requests;
struct regdom_last_setby {
struct wiphy *wiphy;
u8 initiator;
};
/* wiphy is set if this request's initiator is REGDOM_SET_BY_DRIVER */
struct regulatory_request {
struct list_head list;
struct wiphy *wiphy;
int granted;
enum reg_set_by initiator;
char alpha2[2];
};
bool is_world_regdom(char *alpha2);
bool reg_is_valid_request(char *alpha2);
int set_regdom(struct ieee80211_regdomain *rd);
int __regulatory_hint_alpha2(struct wiphy *wiphy, enum reg_set_by set_by,
const char *alpha2);
int regulatory_init(void);
void regulatory_exit(void);
void print_regdomain_info(struct ieee80211_regdomain *);
/* If a char is A-Z */
#define IS_ALPHA(letter) (letter >= 65 && letter <= 90)
#endif /* __NET_WIRELESS_REG_H */
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册