Skip to content

Commit

Permalink
cfg80211: add cellular base station regulatory hint support
Browse files Browse the repository at this point in the history
Cellular base stations can provide hints to cfg80211 about
where they think we are. This can be done for example on
a cell phone. To enable these hints we simply allow them
through as user regulatory hints but we allow userspace
to clasify the hint as either coming directly from the
user or coming from a cellular base station. This option
is only available when you enable
CONFIG_CFG80211_CERTIFICATION_ONUS.

The base station hints themselves will not be processed
by the core unless at least one device on the system
supports this feature.

Signed-off-by: Luis R. Rodriguez <mcgrof@qca.qualcomm.com>
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
  • Loading branch information
Luis R. Rodriguez authored and Johannes Berg committed Jul 17, 2012
1 parent b594bab commit 57b5ce0
Show file tree
Hide file tree
Showing 6 changed files with 171 additions and 8 deletions.
32 changes: 32 additions & 0 deletions include/linux/nl80211.h
Original file line number Diff line number Diff line change
Expand Up @@ -1245,6 +1245,12 @@ enum nl80211_commands {
* @NL80211_ATTR_BG_SCAN_PERIOD: Background scan period in seconds
* or 0 to disable background scan.
*
* @NL80211_ATTR_USER_REG_HINT_TYPE: type of regulatory hint passed from
* userspace. If unset it is assumed the hint comes directly from
* a user. If set code could specify exactly what type of source
* was used to provide the hint. For the different types of
* allowed user regulatory hints see nl80211_user_reg_hint_type.
*
* @NL80211_ATTR_MAX: highest attribute number currently defined
* @__NL80211_ATTR_AFTER_LAST: internal use
*/
Expand Down Expand Up @@ -1498,6 +1504,8 @@ enum nl80211_attrs {

NL80211_ATTR_WDEV,

NL80211_ATTR_USER_REG_HINT_TYPE,

/* add attributes here, update the policy in nl80211.c */

__NL80211_ATTR_AFTER_LAST,
Expand Down Expand Up @@ -2060,6 +2068,26 @@ enum nl80211_dfs_regions {
NL80211_DFS_JP = 3,
};

/**
* enum nl80211_user_reg_hint_type - type of user regulatory hint
*
* @NL80211_USER_REG_HINT_USER: a user sent the hint. This is always
* assumed if the attribute is not set.
* @NL80211_USER_REG_HINT_CELL_BASE: the hint comes from a cellular
* base station. Device drivers that have been tested to work
* properly to support this type of hint can enable these hints
* by setting the NL80211_FEATURE_CELL_BASE_REG_HINTS feature
* capability on the struct wiphy. The wireless core will
* ignore all cell base station hints until at least one device
* present has been registered with the wireless core that
* has listed NL80211_FEATURE_CELL_BASE_REG_HINTS as a
* supported feature.
*/
enum nl80211_user_reg_hint_type {
NL80211_USER_REG_HINT_USER = 0,
NL80211_USER_REG_HINT_CELL_BASE = 1,
};

/**
* enum nl80211_survey_info - survey information
*
Expand Down Expand Up @@ -2963,11 +2991,15 @@ enum nl80211_ap_sme_features {
* @NL80211_FEATURE_HT_IBSS: This driver supports IBSS with HT datarates.
* @NL80211_FEATURE_INACTIVITY_TIMER: This driver takes care of freeing up
* the connected inactive stations in AP mode.
* @NL80211_FEATURE_CELL_BASE_REG_HINTS: This driver has been tested
* to work properly to suppport receiving regulatory hints from
* cellular base stations.
*/
enum nl80211_feature_flags {
NL80211_FEATURE_SK_TX_STATUS = 1 << 0,
NL80211_FEATURE_HT_IBSS = 1 << 1,
NL80211_FEATURE_INACTIVITY_TIMER = 1 << 2,
NL80211_FEATURE_CELL_BASE_REG_HINTS = 1 << 3,
};

/**
Expand Down
5 changes: 5 additions & 0 deletions include/net/regulatory.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ enum environment_cap {
* DFS master operation on a known DFS region (NL80211_DFS_*),
* dfs_region represents that region. Drivers can use this and the
* @alpha2 to adjust their device's DFS parameters as required.
* @user_reg_hint_type: if the @initiator was of type
* %NL80211_REGDOM_SET_BY_USER, this classifies the type
* of hint passed. This could be any of the %NL80211_USER_REG_HINT_*
* types.
* @intersect: indicates whether the wireless core should intersect
* the requested regulatory domain with the presently set regulatory
* domain.
Expand All @@ -70,6 +74,7 @@ enum environment_cap {
struct regulatory_request {
int wiphy_idx;
enum nl80211_reg_initiator initiator;
enum nl80211_user_reg_hint_type user_reg_hint_type;
char alpha2[2];
u8 dfs_region;
bool intersect;
Expand Down
1 change: 1 addition & 0 deletions net/wireless/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,7 @@ int wiphy_register(struct wiphy *wiphy)
}

/* set up regulatory info */
wiphy_regulatory_register(wiphy);
regulatory_update(wiphy, NL80211_REGDOM_SET_BY_CORE);

list_add_rcu(&rdev->list, &cfg80211_rdev_list);
Expand Down
23 changes: 22 additions & 1 deletion net/wireless/nl80211.c
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,7 @@ static const struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] = {
[NL80211_ATTR_INACTIVITY_TIMEOUT] = { .type = NLA_U16 },
[NL80211_ATTR_BG_SCAN_PERIOD] = { .type = NLA_U16 },
[NL80211_ATTR_WDEV] = { .type = NLA_U64 },
[NL80211_ATTR_USER_REG_HINT_TYPE] = { .type = NLA_U32 },
};

/* policy for the key attributes */
Expand Down Expand Up @@ -3582,6 +3583,7 @@ static int nl80211_req_set_reg(struct sk_buff *skb, struct genl_info *info)
{
int r;
char *data = NULL;
enum nl80211_user_reg_hint_type user_reg_hint_type;

/*
* You should only get this when cfg80211 hasn't yet initialized
Expand All @@ -3601,7 +3603,21 @@ static int nl80211_req_set_reg(struct sk_buff *skb, struct genl_info *info)

data = nla_data(info->attrs[NL80211_ATTR_REG_ALPHA2]);

r = regulatory_hint_user(data);
if (info->attrs[NL80211_ATTR_USER_REG_HINT_TYPE])
user_reg_hint_type =
nla_get_u32(info->attrs[NL80211_ATTR_USER_REG_HINT_TYPE]);
else
user_reg_hint_type = NL80211_USER_REG_HINT_USER;

switch (user_reg_hint_type) {
case NL80211_USER_REG_HINT_USER:
case NL80211_USER_REG_HINT_CELL_BASE:
break;
default:
return -EINVAL;
}

r = regulatory_hint_user(data, user_reg_hint_type);

return r;
}
Expand Down Expand Up @@ -3971,6 +3987,11 @@ static int nl80211_get_reg(struct sk_buff *skb, struct genl_info *info)
cfg80211_regdomain->dfs_region)))
goto nla_put_failure;

if (reg_last_request_cell_base() &&
nla_put_u32(msg, NL80211_ATTR_USER_REG_HINT_TYPE,
NL80211_USER_REG_HINT_CELL_BASE))
goto nla_put_failure;

nl_reg_rules = nla_nest_start(msg, NL80211_ATTR_REG_RULES);
if (!nl_reg_rules)
goto nla_put_failure;
Expand Down
113 changes: 107 additions & 6 deletions net/wireless/reg.c
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,16 @@ const struct ieee80211_regdomain *cfg80211_regdomain;
* - cfg80211_world_regdom
* - cfg80211_regdom
* - last_request
* - reg_num_devs_support_basehint
*/
static DEFINE_MUTEX(reg_mutex);

/*
* Number of devices that registered to the core
* that support cellular base station regulatory hints
*/
static int reg_num_devs_support_basehint;

static inline void assert_reg_lock(void)
{
lockdep_assert_held(&reg_mutex);
Expand Down Expand Up @@ -911,6 +918,59 @@ static void handle_band(struct wiphy *wiphy,
handle_channel(wiphy, initiator, band, i);
}

static bool reg_request_cell_base(struct regulatory_request *request)
{
if (request->initiator != NL80211_REGDOM_SET_BY_USER)
return false;
if (request->user_reg_hint_type != NL80211_USER_REG_HINT_CELL_BASE)
return false;
return true;
}

bool reg_last_request_cell_base(void)
{
assert_cfg80211_lock();

mutex_lock(&reg_mutex);
return reg_request_cell_base(last_request);
mutex_unlock(&reg_mutex);
}

#ifdef CONFIG_CFG80211_CERTIFICATION_ONUS

/* Core specific check */
static int reg_ignore_cell_hint(struct regulatory_request *pending_request)
{
if (!reg_num_devs_support_basehint)
return -EOPNOTSUPP;

if (reg_request_cell_base(last_request)) {
if (!regdom_changes(pending_request->alpha2))
return -EALREADY;
return 0;
}
return 0;
}

/* Device specific check */
static bool reg_dev_ignore_cell_hint(struct wiphy *wiphy)
{
if (!(wiphy->features & NL80211_FEATURE_CELL_BASE_REG_HINTS))
return true;
return false;
}
#else
static int reg_ignore_cell_hint(struct regulatory_request *pending_request)
{
return -EOPNOTSUPP;
}
static int reg_dev_ignore_cell_hint(struct wiphy *wiphy)
{
return true;
}
#endif


static bool ignore_reg_update(struct wiphy *wiphy,
enum nl80211_reg_initiator initiator)
{
Expand Down Expand Up @@ -944,6 +1004,9 @@ static bool ignore_reg_update(struct wiphy *wiphy,
return true;
}

if (reg_request_cell_base(last_request))
return reg_dev_ignore_cell_hint(wiphy);

return false;
}

Expand Down Expand Up @@ -1307,6 +1370,13 @@ static int ignore_request(struct wiphy *wiphy,
return 0;
case NL80211_REGDOM_SET_BY_COUNTRY_IE:

if (reg_request_cell_base(last_request)) {
/* Trust a Cell base station over the AP's country IE */
if (regdom_changes(pending_request->alpha2))
return -EOPNOTSUPP;
return -EALREADY;
}

last_wiphy = wiphy_idx_to_wiphy(last_request->wiphy_idx);

if (unlikely(!is_an_alpha2(pending_request->alpha2)))
Expand Down Expand Up @@ -1351,6 +1421,12 @@ static int ignore_request(struct wiphy *wiphy,

return REG_INTERSECT;
case NL80211_REGDOM_SET_BY_USER:
if (reg_request_cell_base(pending_request))
return reg_ignore_cell_hint(pending_request);

if (reg_request_cell_base(last_request))
return -EOPNOTSUPP;

if (last_request->initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE)
return REG_INTERSECT;
/*
Expand Down Expand Up @@ -1640,7 +1716,8 @@ static int regulatory_hint_core(const char *alpha2)
}

/* User hints */
int regulatory_hint_user(const char *alpha2)
int regulatory_hint_user(const char *alpha2,
enum nl80211_user_reg_hint_type user_reg_hint_type)
{
struct regulatory_request *request;

Expand All @@ -1654,6 +1731,7 @@ int regulatory_hint_user(const char *alpha2)
request->alpha2[0] = alpha2[0];
request->alpha2[1] = alpha2[1];
request->initiator = NL80211_REGDOM_SET_BY_USER;
request->user_reg_hint_type = user_reg_hint_type;

queue_regulatory_request(request);

Expand Down Expand Up @@ -1906,7 +1984,7 @@ static void restore_regulatory_settings(bool reset_user)
* settings, user regulatory settings takes precedence.
*/
if (is_an_alpha2(alpha2))
regulatory_hint_user(user_alpha2);
regulatory_hint_user(user_alpha2, NL80211_USER_REG_HINT_USER);

if (list_empty(&tmp_reg_req_list))
return;
Expand Down Expand Up @@ -2081,9 +2159,16 @@ static void print_regdomain(const struct ieee80211_regdomain *rd)
else {
if (is_unknown_alpha2(rd->alpha2))
pr_info("Regulatory domain changed to driver built-in settings (unknown country)\n");
else
pr_info("Regulatory domain changed to country: %c%c\n",
rd->alpha2[0], rd->alpha2[1]);
else {
if (reg_request_cell_base(last_request))
pr_info("Regulatory domain changed "
"to country: %c%c by Cell Station\n",
rd->alpha2[0], rd->alpha2[1]);
else
pr_info("Regulatory domain changed "
"to country: %c%c\n",
rd->alpha2[0], rd->alpha2[1]);
}
}
print_dfs_region(rd->dfs_region);
print_rd_rules(rd);
Expand Down Expand Up @@ -2293,6 +2378,18 @@ int reg_device_uevent(struct device *dev, struct kobj_uevent_env *env)
}
#endif /* CONFIG_HOTPLUG */

void wiphy_regulatory_register(struct wiphy *wiphy)
{
assert_cfg80211_lock();

mutex_lock(&reg_mutex);

if (!reg_dev_ignore_cell_hint(wiphy))
reg_num_devs_support_basehint++;

mutex_unlock(&reg_mutex);
}

/* Caller must hold cfg80211_mutex */
void reg_device_remove(struct wiphy *wiphy)
{
Expand All @@ -2302,6 +2399,9 @@ void reg_device_remove(struct wiphy *wiphy)

mutex_lock(&reg_mutex);

if (!reg_dev_ignore_cell_hint(wiphy))
reg_num_devs_support_basehint--;

kfree(wiphy->regd);

if (last_request)
Expand Down Expand Up @@ -2367,7 +2467,8 @@ int __init regulatory_init(void)
* as a user hint.
*/
if (!is_world_regdom(ieee80211_regdom))
regulatory_hint_user(ieee80211_regdom);
regulatory_hint_user(ieee80211_regdom,
NL80211_USER_REG_HINT_USER);

return 0;
}
Expand Down
5 changes: 4 additions & 1 deletion net/wireless/reg.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@ bool is_world_regdom(const char *alpha2);
bool reg_is_valid_request(const char *alpha2);
bool reg_supported_dfs_region(u8 dfs_region);

int regulatory_hint_user(const char *alpha2);
int regulatory_hint_user(const char *alpha2,
enum nl80211_user_reg_hint_type user_reg_hint_type);

int reg_device_uevent(struct device *dev, struct kobj_uevent_env *env);
void wiphy_regulatory_register(struct wiphy *wiphy);
void reg_device_remove(struct wiphy *wiphy);

int __init regulatory_init(void);
Expand All @@ -33,6 +35,7 @@ void regulatory_exit(void);
int set_regdom(const struct ieee80211_regdomain *rd);

void regulatory_update(struct wiphy *wiphy, enum nl80211_reg_initiator setby);
bool reg_last_request_cell_base(void);

/**
* regulatory_hint_found_beacon - hints a beacon was found on a channel
Expand Down

0 comments on commit 57b5ce0

Please sign in to comment.