cfg80211: support loading regulatory database as firmware file
As the current regulatory database is only about 4k big, and already difficult to extend, we decided that overall it would be better to get rid of the complications with CRDA and load the database into the kernel directly, but in a new format that is extensible. The new file format can be extended since it carries a length field on all the structs that need to be extensible. In order to be able to request firmware when the module initializes, move cfg80211 from subsys_initcall() to the later fs_initcall(); the firmware loader is at the same level but linked earlier, so it can be called from there. Otherwise, when both the firmware loader and cfg80211 are built-in, the request will crash the kernel. We also need to be before device_initcall() so that cfg80211 is available for devices when they initialize. Signed-off-by: Johannes Berg <johannes.berg@intel.com>
This commit is contained in:
parent
2a9e25796b
commit
007f6c5e6e
|
@ -19,6 +19,14 @@ core regulatory domain all wireless devices should adhere to.
|
||||||
How to get regulatory domains to the kernel
|
How to get regulatory domains to the kernel
|
||||||
-------------------------------------------
|
-------------------------------------------
|
||||||
|
|
||||||
|
When the regulatory domain is first set up, the kernel will request a
|
||||||
|
database file (regulatory.db) containing all the regulatory rules. It
|
||||||
|
will then use that database when it needs to look up the rules for a
|
||||||
|
given country.
|
||||||
|
|
||||||
|
How to get regulatory domains to the kernel (old CRDA solution)
|
||||||
|
---------------------------------------------------------------
|
||||||
|
|
||||||
Userspace gets a regulatory domain in the kernel by having
|
Userspace gets a regulatory domain in the kernel by having
|
||||||
a userspace agent build it and send it via nl80211. Only
|
a userspace agent build it and send it via nl80211. Only
|
||||||
expected regulatory domains will be respected by the kernel.
|
expected regulatory domains will be respected by the kernel.
|
||||||
|
|
|
@ -19,6 +19,7 @@ config WEXT_PRIV
|
||||||
config CFG80211
|
config CFG80211
|
||||||
tristate "cfg80211 - wireless configuration API"
|
tristate "cfg80211 - wireless configuration API"
|
||||||
depends on RFKILL || !RFKILL
|
depends on RFKILL || !RFKILL
|
||||||
|
select FW_LOADER
|
||||||
---help---
|
---help---
|
||||||
cfg80211 is the Linux wireless LAN (802.11) configuration API.
|
cfg80211 is the Linux wireless LAN (802.11) configuration API.
|
||||||
Enable this if you have a wireless device.
|
Enable this if you have a wireless device.
|
||||||
|
@ -167,7 +168,8 @@ config CFG80211_CRDA_SUPPORT
|
||||||
depends on CFG80211
|
depends on CFG80211
|
||||||
help
|
help
|
||||||
You should enable this option unless you know for sure you have no
|
You should enable this option unless you know for sure you have no
|
||||||
need for it, for example when using internal regdb (above.)
|
need for it, for example when using internal regdb (above) or the
|
||||||
|
database loaded as a firmware file.
|
||||||
|
|
||||||
If unsure, say Y.
|
If unsure, say Y.
|
||||||
|
|
||||||
|
|
|
@ -1384,7 +1384,7 @@ out_fail_sysfs:
|
||||||
out_fail_pernet:
|
out_fail_pernet:
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
subsys_initcall(cfg80211_init);
|
fs_initcall(cfg80211_init);
|
||||||
|
|
||||||
static void __exit cfg80211_exit(void)
|
static void __exit cfg80211_exit(void)
|
||||||
{
|
{
|
||||||
|
|
|
@ -54,6 +54,7 @@
|
||||||
#include <linux/nl80211.h>
|
#include <linux/nl80211.h>
|
||||||
#include <linux/platform_device.h>
|
#include <linux/platform_device.h>
|
||||||
#include <linux/moduleparam.h>
|
#include <linux/moduleparam.h>
|
||||||
|
#include <linux/firmware.h>
|
||||||
#include <net/cfg80211.h>
|
#include <net/cfg80211.h>
|
||||||
#include "core.h"
|
#include "core.h"
|
||||||
#include "reg.h"
|
#include "reg.h"
|
||||||
|
@ -100,7 +101,7 @@ static struct regulatory_request core_request_world = {
|
||||||
static struct regulatory_request __rcu *last_request =
|
static struct regulatory_request __rcu *last_request =
|
||||||
(void __force __rcu *)&core_request_world;
|
(void __force __rcu *)&core_request_world;
|
||||||
|
|
||||||
/* To trigger userspace events */
|
/* To trigger userspace events and load firmware */
|
||||||
static struct platform_device *reg_pdev;
|
static struct platform_device *reg_pdev;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -443,7 +444,6 @@ reg_copy_regd(const struct ieee80211_regdomain *src_regd)
|
||||||
return regd;
|
return regd;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_CFG80211_INTERNAL_REGDB
|
|
||||||
struct reg_regdb_apply_request {
|
struct reg_regdb_apply_request {
|
||||||
struct list_head list;
|
struct list_head list;
|
||||||
const struct ieee80211_regdomain *regdom;
|
const struct ieee80211_regdomain *regdom;
|
||||||
|
@ -475,41 +475,44 @@ static void reg_regdb_apply(struct work_struct *work)
|
||||||
|
|
||||||
static DECLARE_WORK(reg_regdb_work, reg_regdb_apply);
|
static DECLARE_WORK(reg_regdb_work, reg_regdb_apply);
|
||||||
|
|
||||||
static int reg_query_builtin(const char *alpha2)
|
static int reg_schedule_apply(const struct ieee80211_regdomain *regdom)
|
||||||
{
|
{
|
||||||
const struct ieee80211_regdomain *regdom = NULL;
|
|
||||||
struct reg_regdb_apply_request *request;
|
struct reg_regdb_apply_request *request;
|
||||||
unsigned int i;
|
|
||||||
|
|
||||||
for (i = 0; i < reg_regdb_size; i++) {
|
|
||||||
if (alpha2_equal(alpha2, reg_regdb[i]->alpha2)) {
|
|
||||||
regdom = reg_regdb[i];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!regdom)
|
|
||||||
return -ENODATA;
|
|
||||||
|
|
||||||
request = kzalloc(sizeof(struct reg_regdb_apply_request), GFP_KERNEL);
|
request = kzalloc(sizeof(struct reg_regdb_apply_request), GFP_KERNEL);
|
||||||
if (!request)
|
if (!request) {
|
||||||
return -ENOMEM;
|
kfree(regdom);
|
||||||
|
|
||||||
request->regdom = reg_copy_regd(regdom);
|
|
||||||
if (IS_ERR_OR_NULL(request->regdom)) {
|
|
||||||
kfree(request);
|
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
request->regdom = regdom;
|
||||||
|
|
||||||
mutex_lock(®_regdb_apply_mutex);
|
mutex_lock(®_regdb_apply_mutex);
|
||||||
list_add_tail(&request->list, ®_regdb_apply_list);
|
list_add_tail(&request->list, ®_regdb_apply_list);
|
||||||
mutex_unlock(®_regdb_apply_mutex);
|
mutex_unlock(®_regdb_apply_mutex);
|
||||||
|
|
||||||
schedule_work(®_regdb_work);
|
schedule_work(®_regdb_work);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_CFG80211_INTERNAL_REGDB
|
||||||
|
static int reg_query_builtin(const char *alpha2)
|
||||||
|
{
|
||||||
|
const struct ieee80211_regdomain *regdom = NULL;
|
||||||
|
unsigned int i;
|
||||||
|
|
||||||
|
for (i = 0; i < reg_regdb_size; i++) {
|
||||||
|
if (alpha2_equal(alpha2, reg_regdb[i]->alpha2)) {
|
||||||
|
regdom = reg_copy_regd(reg_regdb[i]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!regdom)
|
||||||
|
return -ENODATA;
|
||||||
|
|
||||||
|
return reg_schedule_apply(regdom);
|
||||||
|
}
|
||||||
|
|
||||||
/* Feel free to add any other sanity checks here */
|
/* Feel free to add any other sanity checks here */
|
||||||
static void reg_regdb_size_check(void)
|
static void reg_regdb_size_check(void)
|
||||||
{
|
{
|
||||||
|
@ -599,12 +602,256 @@ static inline int call_crda(const char *alpha2)
|
||||||
}
|
}
|
||||||
#endif /* CONFIG_CFG80211_CRDA_SUPPORT */
|
#endif /* CONFIG_CFG80211_CRDA_SUPPORT */
|
||||||
|
|
||||||
|
/* code to directly load a firmware database through request_firmware */
|
||||||
|
static const struct fwdb_header *regdb;
|
||||||
|
|
||||||
|
struct fwdb_country {
|
||||||
|
u8 alpha2[2];
|
||||||
|
__be16 coll_ptr;
|
||||||
|
/* this struct cannot be extended */
|
||||||
|
} __packed __aligned(4);
|
||||||
|
|
||||||
|
struct fwdb_collection {
|
||||||
|
u8 len;
|
||||||
|
u8 n_rules;
|
||||||
|
u8 dfs_region;
|
||||||
|
/* no optional data yet */
|
||||||
|
/* aligned to 2, then followed by __be16 array of rule pointers */
|
||||||
|
} __packed __aligned(4);
|
||||||
|
|
||||||
|
enum fwdb_flags {
|
||||||
|
FWDB_FLAG_NO_OFDM = BIT(0),
|
||||||
|
FWDB_FLAG_NO_OUTDOOR = BIT(1),
|
||||||
|
FWDB_FLAG_DFS = BIT(2),
|
||||||
|
FWDB_FLAG_NO_IR = BIT(3),
|
||||||
|
FWDB_FLAG_AUTO_BW = BIT(4),
|
||||||
|
};
|
||||||
|
|
||||||
|
struct fwdb_rule {
|
||||||
|
u8 len;
|
||||||
|
u8 flags;
|
||||||
|
__be16 max_eirp;
|
||||||
|
__be32 start, end, max_bw;
|
||||||
|
/* start of optional data */
|
||||||
|
__be16 cac_timeout;
|
||||||
|
} __packed __aligned(4);
|
||||||
|
|
||||||
|
#define FWDB_MAGIC 0x52474442
|
||||||
|
#define FWDB_VERSION 20
|
||||||
|
|
||||||
|
struct fwdb_header {
|
||||||
|
__be32 magic;
|
||||||
|
__be32 version;
|
||||||
|
struct fwdb_country country[];
|
||||||
|
} __packed __aligned(4);
|
||||||
|
|
||||||
|
static bool valid_rule(const u8 *data, unsigned int size, u16 rule_ptr)
|
||||||
|
{
|
||||||
|
struct fwdb_rule *rule = (void *)(data + (rule_ptr << 2));
|
||||||
|
|
||||||
|
if ((u8 *)rule + sizeof(rule->len) > data + size)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* mandatory fields */
|
||||||
|
if (rule->len < offsetofend(struct fwdb_rule, max_bw))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool valid_country(const u8 *data, unsigned int size,
|
||||||
|
const struct fwdb_country *country)
|
||||||
|
{
|
||||||
|
unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2;
|
||||||
|
struct fwdb_collection *coll = (void *)(data + ptr);
|
||||||
|
__be16 *rules_ptr;
|
||||||
|
unsigned int i;
|
||||||
|
|
||||||
|
/* make sure we can read len/n_rules */
|
||||||
|
if ((u8 *)coll + offsetofend(typeof(*coll), n_rules) > data + size)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* make sure base struct and all rules fit */
|
||||||
|
if ((u8 *)coll + ALIGN(coll->len, 2) +
|
||||||
|
(coll->n_rules * 2) > data + size)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/* mandatory fields must exist */
|
||||||
|
if (coll->len < offsetofend(struct fwdb_collection, dfs_region))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
rules_ptr = (void *)((u8 *)coll + ALIGN(coll->len, 2));
|
||||||
|
|
||||||
|
for (i = 0; i < coll->n_rules; i++) {
|
||||||
|
u16 rule_ptr = be16_to_cpu(rules_ptr[i]);
|
||||||
|
|
||||||
|
if (!valid_rule(data, size, rule_ptr))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool valid_regdb(const u8 *data, unsigned int size)
|
||||||
|
{
|
||||||
|
const struct fwdb_header *hdr = (void *)data;
|
||||||
|
const struct fwdb_country *country;
|
||||||
|
|
||||||
|
if (size < sizeof(*hdr))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (hdr->magic != cpu_to_be32(FWDB_MAGIC))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (hdr->version != cpu_to_be32(FWDB_VERSION))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
country = &hdr->country[0];
|
||||||
|
while ((u8 *)(country + 1) <= data + size) {
|
||||||
|
if (!country->coll_ptr)
|
||||||
|
break;
|
||||||
|
if (!valid_country(data, size, country))
|
||||||
|
return false;
|
||||||
|
country++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int regdb_query_country(const struct fwdb_header *db,
|
||||||
|
const struct fwdb_country *country)
|
||||||
|
{
|
||||||
|
unsigned int ptr = be16_to_cpu(country->coll_ptr) << 2;
|
||||||
|
struct fwdb_collection *coll = (void *)((u8 *)db + ptr);
|
||||||
|
struct ieee80211_regdomain *regdom;
|
||||||
|
unsigned int size_of_regd;
|
||||||
|
unsigned int i;
|
||||||
|
|
||||||
|
size_of_regd =
|
||||||
|
sizeof(struct ieee80211_regdomain) +
|
||||||
|
coll->n_rules * sizeof(struct ieee80211_reg_rule);
|
||||||
|
|
||||||
|
regdom = kzalloc(size_of_regd, GFP_KERNEL);
|
||||||
|
if (!regdom)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
regdom->n_reg_rules = coll->n_rules;
|
||||||
|
regdom->alpha2[0] = country->alpha2[0];
|
||||||
|
regdom->alpha2[1] = country->alpha2[1];
|
||||||
|
regdom->dfs_region = coll->dfs_region;
|
||||||
|
|
||||||
|
for (i = 0; i < regdom->n_reg_rules; i++) {
|
||||||
|
__be16 *rules_ptr = (void *)((u8 *)coll + ALIGN(coll->len, 2));
|
||||||
|
unsigned int rule_ptr = be16_to_cpu(rules_ptr[i]) << 2;
|
||||||
|
struct fwdb_rule *rule = (void *)((u8 *)db + rule_ptr);
|
||||||
|
struct ieee80211_reg_rule *rrule = ®dom->reg_rules[i];
|
||||||
|
|
||||||
|
rrule->freq_range.start_freq_khz = be32_to_cpu(rule->start);
|
||||||
|
rrule->freq_range.end_freq_khz = be32_to_cpu(rule->end);
|
||||||
|
rrule->freq_range.max_bandwidth_khz = be32_to_cpu(rule->max_bw);
|
||||||
|
|
||||||
|
rrule->power_rule.max_antenna_gain = 0;
|
||||||
|
rrule->power_rule.max_eirp = be16_to_cpu(rule->max_eirp);
|
||||||
|
|
||||||
|
rrule->flags = 0;
|
||||||
|
if (rule->flags & FWDB_FLAG_NO_OFDM)
|
||||||
|
rrule->flags |= NL80211_RRF_NO_OFDM;
|
||||||
|
if (rule->flags & FWDB_FLAG_NO_OUTDOOR)
|
||||||
|
rrule->flags |= NL80211_RRF_NO_OUTDOOR;
|
||||||
|
if (rule->flags & FWDB_FLAG_DFS)
|
||||||
|
rrule->flags |= NL80211_RRF_DFS;
|
||||||
|
if (rule->flags & FWDB_FLAG_NO_IR)
|
||||||
|
rrule->flags |= NL80211_RRF_NO_IR;
|
||||||
|
if (rule->flags & FWDB_FLAG_AUTO_BW)
|
||||||
|
rrule->flags |= NL80211_RRF_AUTO_BW;
|
||||||
|
|
||||||
|
rrule->dfs_cac_ms = 0;
|
||||||
|
|
||||||
|
/* handle optional data */
|
||||||
|
if (rule->len >= offsetofend(struct fwdb_rule, cac_timeout))
|
||||||
|
rrule->dfs_cac_ms =
|
||||||
|
1000 * be16_to_cpu(rule->cac_timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
return reg_schedule_apply(regdom);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int query_regdb(const char *alpha2)
|
||||||
|
{
|
||||||
|
const struct fwdb_header *hdr = regdb;
|
||||||
|
const struct fwdb_country *country;
|
||||||
|
|
||||||
|
if (IS_ERR(regdb))
|
||||||
|
return PTR_ERR(regdb);
|
||||||
|
|
||||||
|
country = &hdr->country[0];
|
||||||
|
while (country->coll_ptr) {
|
||||||
|
if (alpha2_equal(alpha2, country->alpha2))
|
||||||
|
return regdb_query_country(regdb, country);
|
||||||
|
country++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -ENODATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void regdb_fw_cb(const struct firmware *fw, void *context)
|
||||||
|
{
|
||||||
|
void *db;
|
||||||
|
|
||||||
|
if (!fw) {
|
||||||
|
pr_info("failed to load regulatory.db\n");
|
||||||
|
regdb = ERR_PTR(-ENODATA);
|
||||||
|
goto restore;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!valid_regdb(fw->data, fw->size)) {
|
||||||
|
pr_info("loaded regulatory.db is malformed\n");
|
||||||
|
release_firmware(fw);
|
||||||
|
regdb = ERR_PTR(-EINVAL);
|
||||||
|
goto restore;
|
||||||
|
}
|
||||||
|
|
||||||
|
db = kmemdup(fw->data, fw->size, GFP_KERNEL);
|
||||||
|
release_firmware(fw);
|
||||||
|
|
||||||
|
if (!db)
|
||||||
|
goto restore;
|
||||||
|
regdb = db;
|
||||||
|
|
||||||
|
if (query_regdb(context))
|
||||||
|
goto restore;
|
||||||
|
goto free;
|
||||||
|
restore:
|
||||||
|
rtnl_lock();
|
||||||
|
restore_regulatory_settings(true);
|
||||||
|
rtnl_unlock();
|
||||||
|
free:
|
||||||
|
kfree(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int query_regdb_file(const char *alpha2)
|
||||||
|
{
|
||||||
|
if (regdb)
|
||||||
|
return query_regdb(alpha2);
|
||||||
|
|
||||||
|
alpha2 = kmemdup(alpha2, 2, GFP_KERNEL);
|
||||||
|
if (!alpha2)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
return request_firmware_nowait(THIS_MODULE, true, "regulatory.db",
|
||||||
|
®_pdev->dev, GFP_KERNEL,
|
||||||
|
(void *)alpha2, regdb_fw_cb);
|
||||||
|
}
|
||||||
|
|
||||||
static bool reg_query_database(struct regulatory_request *request)
|
static bool reg_query_database(struct regulatory_request *request)
|
||||||
{
|
{
|
||||||
/* query internal regulatory database (if it exists) */
|
/* query internal regulatory database (if it exists) */
|
||||||
if (reg_query_builtin(request->alpha2) == 0)
|
if (reg_query_builtin(request->alpha2) == 0)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
if (query_regdb_file(request->alpha2) == 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
if (call_crda(request->alpha2) == 0)
|
if (call_crda(request->alpha2) == 0)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
@ -3360,4 +3607,7 @@ void regulatory_exit(void)
|
||||||
list_del(®_request->list);
|
list_del(®_request->list);
|
||||||
kfree(reg_request);
|
kfree(reg_request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!IS_ERR_OR_NULL(regdb))
|
||||||
|
kfree(regdb);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue