2014-07-10 13:28:53

by Rostislav Lisovy

[permalink] [raw]
Subject: [PATCH] cfg80211: Try multiple bandwidths when checking usable channels.

Current code checks if at least 20MHz bandwidth is allowed for
particular channel -- if it is not, the channel is disabled.
This disables usage of 5/10 MHz channels.
Another issue with the current code is that it may allow a channel
with bandwidth which is although less or the same as the "maximum
bandwidth allowed" but overlaps the border of the band.

The new approach is that there are multiple checks for one channel --
one for each bandwidth: 5, 10, 20, 40, 80, 160 MHz (when we hit a
bandwidth that is not allowed, greater bandwidths are automaticly
disabled as well). This prevents the following scenario to happen:
The 5 MHz bandwidth channel at the very end of the band is
successfully checked to fit which is followed by setting flags
IEEE80211_CHAN_NO_* according to the maximum bandwidth allowed by the
particular regulatory rule (which may be greater than the 5 MHz).
When someone will try to use that particular channel with the maximum
bandwidth allowed (e.g. 20 MHz), the resulting channel will not be in
the range of the band anymore.

Signed-off-by: Rostislav Lisovy <[email protected]>
---
include/net/cfg80211.h | 8 +-
net/wireless/reg.c | 202 +++++++++++++++++++++++++++++++------------------
2 files changed, 137 insertions(+), 73 deletions(-)

diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index e46c437..919f759 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -3679,6 +3679,8 @@ void wiphy_apply_custom_regulatory(struct wiphy *wiphy,
* freq_reg_info - get regulatory information for the given frequency
* @wiphy: the wiphy for which we want to process this rule for
* @center_freq: Frequency in KHz for which we want regulatory information for
+ * @desired_bw_khz: The bandwidth of the channel in KHz we want regulatory
+ * information for
*
* Use this function to get the regulatory rule for a specific frequency on
* a given wireless device. If the device has a specific regulatory domain
@@ -3692,9 +3694,13 @@ void wiphy_apply_custom_regulatory(struct wiphy *wiphy,
* have a regulatory rule for a frequency range in the center_freq's band.
* See freq_in_rule_band() for our current definition of a band -- this is
* purely subjective and right now it's 802.11 specific.
+ * -EINVAL either if the channel does not fit to any of the belonging
+ * regulatory rules OR when it would possibly fit but the requested channel
+ * bandwidth is greater than the one allowed by the regulatory rule.
*/
const struct ieee80211_reg_rule *freq_reg_info(struct wiphy *wiphy,
- u32 center_freq);
+ u32 center_freq,
+ u32 desired_bw_khz);

/**
* reg_initiator_name - map regulatory request initiator enum to name
diff --git a/net/wireless/reg.c b/net/wireless/reg.c
index 1afdf45..7a14b94 100644
--- a/net/wireless/reg.c
+++ b/net/wireless/reg.c
@@ -910,7 +910,7 @@ static u32 map_regdom_flags(u32 rd_flags)
}

static const struct ieee80211_reg_rule *
-freq_reg_info_regd(struct wiphy *wiphy, u32 center_freq,
+freq_reg_info_regd(struct wiphy *wiphy, u32 center_freq, u32 desired_bw_khz,
const struct ieee80211_regdomain *regd)
{
int i;
@@ -920,6 +920,9 @@ freq_reg_info_regd(struct wiphy *wiphy, u32 center_freq,
if (!regd)
return ERR_PTR(-EINVAL);

+ if (!desired_bw_khz)
+ desired_bw_khz = MHZ_TO_KHZ(20);
+
for (i = 0; i < regd->n_reg_rules; i++) {
const struct ieee80211_reg_rule *rr;
const struct ieee80211_freq_range *fr = NULL;
@@ -935,10 +938,28 @@ freq_reg_info_regd(struct wiphy *wiphy, u32 center_freq,
if (!band_rule_found)
band_rule_found = freq_in_rule_band(fr, center_freq);

- bw_fits = reg_does_bw_fit(fr, center_freq, MHZ_TO_KHZ(20));
+ bw_fits = reg_does_bw_fit(fr, center_freq, desired_bw_khz);
+
+ /*
+ * Even if the 'center_freq' and 'bw' do fit
+ * we need to check if the required bandwidth makes
+ * sense according to the maximum allowed bandwidth
+ */
+ if (band_rule_found && bw_fits) {
+ u32 max_bandwidth_khz;

- if (band_rule_found && bw_fits)
- return rr;
+ /* Check if auto calculation requested */
+ if (rr->flags & NL80211_RRF_AUTO_BW)
+ max_bandwidth_khz =
+ reg_get_max_bandwidth(regd, rr);
+ else
+ max_bandwidth_khz = fr->max_bandwidth_khz;
+
+ if (max_bandwidth_khz > desired_bw_khz)
+ return rr;
+ else
+ return ERR_PTR(-EINVAL);
+ }
}

if (!band_rule_found)
@@ -948,13 +969,14 @@ freq_reg_info_regd(struct wiphy *wiphy, u32 center_freq,
}

const struct ieee80211_reg_rule *freq_reg_info(struct wiphy *wiphy,
- u32 center_freq)
+ u32 center_freq,
+ u32 desired_bw_khz)
{
const struct ieee80211_regdomain *regd;

regd = reg_get_regdomain(wiphy);

- return freq_reg_info_regd(wiphy, center_freq, regd);
+ return freq_reg_info_regd(wiphy, center_freq, desired_bw_khz, regd);
}
EXPORT_SYMBOL(freq_reg_info);

@@ -1019,11 +1041,6 @@ static void chan_reg_rule_print_dbg(const struct ieee80211_regdomain *regd,
}
#endif

-/*
- * Note that right now we assume the desired channel bandwidth
- * is always 20 MHz for each individual channel (HT40 uses 20 MHz
- * per channel, the primary and the extension channel).
- */
static void handle_channel(struct wiphy *wiphy,
enum nl80211_reg_initiator initiator,
struct ieee80211_channel *chan)
@@ -1035,41 +1052,75 @@ static void handle_channel(struct wiphy *wiphy,
struct wiphy *request_wiphy = NULL;
struct regulatory_request *lr = get_last_request();
const struct ieee80211_regdomain *regd;
- u32 max_bandwidth_khz;
+ bool check_greater_bw = 1;
+ int bw;

request_wiphy = wiphy_idx_to_wiphy(lr->wiphy_idx);

flags = chan->orig_flags;

- reg_rule = freq_reg_info(wiphy, MHZ_TO_KHZ(chan->center_freq));
- if (IS_ERR(reg_rule)) {
- /*
- * We will disable all channels that do not match our
- * received regulatory rule unless the hint is coming
- * from a Country IE and the Country IE had no information
- * about a band. The IEEE 802.11 spec allows for an AP
- * to send only a subset of the regulatory rules allowed,
- * so an AP in the US that only supports 2.4 GHz may only send
- * a country IE with information for the 2.4 GHz band
- * while 5 GHz is still supported.
- */
- if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE &&
- PTR_ERR(reg_rule) == -ERANGE)
- return;
+ /* Check for 5, 10, 20, 40, 80, 160 bandwidths */
+ for (bw = 5; bw < 160 && check_greater_bw; bw *= 2) {
+ reg_rule = freq_reg_info(wiphy, MHZ_TO_KHZ(chan->center_freq),
+ MHZ_TO_KHZ(bw));

- if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER &&
- request_wiphy && request_wiphy == wiphy &&
- request_wiphy->regulatory_flags & REGULATORY_STRICT_REG) {
- REG_DBG_PRINT("Disabling freq %d MHz for good\n",
- chan->center_freq);
- chan->orig_flags |= IEEE80211_CHAN_DISABLED;
- chan->flags = chan->orig_flags;
- } else {
- REG_DBG_PRINT("Disabling freq %d MHz\n",
- chan->center_freq);
- chan->flags |= IEEE80211_CHAN_DISABLED;
+ if (IS_ERR(reg_rule) && (PTR_ERR(reg_rule) == -EINVAL)) {
+ /*
+ * Set BW limiting flags for any channel but the
+ * 5MHz one -- if the 5MHz BW does not fit, the whole
+ * channel is disabled
+ */
+ switch (bw) {
+ case 5:
+ break;
+ case 10:
+ bw_flags |= IEEE80211_CHAN_NO_10MHZ;
+ case 20:
+ bw_flags |= IEEE80211_CHAN_NO_20MHZ;
+ case 40:
+ bw_flags |= IEEE80211_CHAN_NO_HT40;
+ case 80:
+ bw_flags |= IEEE80211_CHAN_NO_80MHZ;
+ case 160:
+ bw_flags |= IEEE80211_CHAN_NO_160MHZ;
+ check_greater_bw = 0;
+ }
+
+ if (!check_greater_bw)
+ break;
+ }
+
+ if (IS_ERR(reg_rule)) {
+
+ /*
+ * We will disable all channels that do not match our
+ * received regulatory rule unless the hint is coming
+ * from a Country IE and the Country IE had no information
+ * about a band. The IEEE 802.11 spec allows for an AP
+ * to send only a subset of the regulatory rules allowed,
+ * so an AP in the US that only supports 2.4 GHz may only send
+ * a country IE with information for the 2.4 GHz band
+ * while 5 GHz is still supported.
+ */
+ if (initiator == NL80211_REGDOM_SET_BY_COUNTRY_IE &&
+ PTR_ERR(reg_rule) == -ERANGE)
+ return;
+
+ if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER &&
+ request_wiphy && request_wiphy == wiphy &&
+ request_wiphy->regulatory_flags & REGULATORY_STRICT_REG) {
+ REG_DBG_PRINT("Disabling freq %d MHz for good\n",
+ chan->center_freq);
+ chan->orig_flags |= IEEE80211_CHAN_DISABLED;
+ chan->flags = chan->orig_flags;
+ } else {
+ REG_DBG_PRINT("Disabling freq %d MHz\n",
+ chan->center_freq);
+ chan->flags |= IEEE80211_CHAN_DISABLED;
+ }
+
+ return;
}
- return;
}

regd = reg_get_regdomain(wiphy);
@@ -1078,18 +1129,6 @@ static void handle_channel(struct wiphy *wiphy,
power_rule = &reg_rule->power_rule;
freq_range = &reg_rule->freq_range;

- max_bandwidth_khz = freq_range->max_bandwidth_khz;
- /* Check if auto calculation requested */
- if (reg_rule->flags & NL80211_RRF_AUTO_BW)
- max_bandwidth_khz = reg_get_max_bandwidth(regd, reg_rule);
-
- if (max_bandwidth_khz < MHZ_TO_KHZ(40))
- bw_flags = IEEE80211_CHAN_NO_HT40;
- if (max_bandwidth_khz < MHZ_TO_KHZ(80))
- bw_flags |= IEEE80211_CHAN_NO_80MHZ;
- if (max_bandwidth_khz < MHZ_TO_KHZ(160))
- bw_flags |= IEEE80211_CHAN_NO_160MHZ;
-
if (lr->initiator == NL80211_REGDOM_SET_BY_DRIVER &&
request_wiphy && request_wiphy == wiphy &&
request_wiphy->regulatory_flags & REGULATORY_STRICT_REG) {
@@ -1495,17 +1534,48 @@ static void handle_channel_custom(struct wiphy *wiphy,
const struct ieee80211_reg_rule *reg_rule = NULL;
const struct ieee80211_power_rule *power_rule = NULL;
const struct ieee80211_freq_range *freq_range = NULL;
- u32 max_bandwidth_khz;
+ bool check_greater_bw = 1;
+ int bw;

- reg_rule = freq_reg_info_regd(wiphy, MHZ_TO_KHZ(chan->center_freq),
- regd);
+ /* Check for 5, 10, 20, 40, 80, 160 bandwidths */
+ for (bw = 5; bw < 160 && check_greater_bw; bw *= 2) {
+ reg_rule = freq_reg_info_regd(wiphy,
+ MHZ_TO_KHZ(chan->center_freq),
+ MHZ_TO_KHZ(bw), regd);

- if (IS_ERR(reg_rule)) {
- REG_DBG_PRINT("Disabling freq %d MHz as custom regd has no rule that fits it\n",
- chan->center_freq);
- chan->orig_flags |= IEEE80211_CHAN_DISABLED;
- chan->flags = chan->orig_flags;
- return;
+ if (IS_ERR(reg_rule) && (PTR_ERR(reg_rule) == -EINVAL)) {
+ /*
+ * Set BW limiting flags for any channel but the
+ * 5MHz one -- if the 5MHz BW does not fit, the whole
+ * channel is disabled
+ */
+ switch (bw) {
+ case 5:
+ break;
+ case 10:
+ bw_flags |= IEEE80211_CHAN_NO_10MHZ;
+ case 20:
+ bw_flags |= IEEE80211_CHAN_NO_20MHZ;
+ case 40:
+ bw_flags |= IEEE80211_CHAN_NO_HT40;
+ case 80:
+ bw_flags |= IEEE80211_CHAN_NO_80MHZ;
+ case 160:
+ bw_flags |= IEEE80211_CHAN_NO_160MHZ;
+ check_greater_bw = 0;
+ }
+
+ if (!check_greater_bw)
+ break;
+ }
+
+ if (IS_ERR(reg_rule)) {
+ REG_DBG_PRINT("Disabling freq %d MHz as custom regd has no rule that fits it\n",
+ chan->center_freq);
+ chan->orig_flags |= IEEE80211_CHAN_DISABLED;
+ chan->flags = chan->orig_flags;
+ return;
+ }
}

chan_reg_rule_print_dbg(regd, chan, reg_rule);
@@ -1513,18 +1583,6 @@ static void handle_channel_custom(struct wiphy *wiphy,
power_rule = &reg_rule->power_rule;
freq_range = &reg_rule->freq_range;

- max_bandwidth_khz = freq_range->max_bandwidth_khz;
- /* Check if auto calculation requested */
- if (reg_rule->flags & NL80211_RRF_AUTO_BW)
- max_bandwidth_khz = reg_get_max_bandwidth(regd, reg_rule);
-
- if (max_bandwidth_khz < MHZ_TO_KHZ(40))
- bw_flags = IEEE80211_CHAN_NO_HT40;
- if (max_bandwidth_khz < MHZ_TO_KHZ(80))
- bw_flags |= IEEE80211_CHAN_NO_80MHZ;
- if (max_bandwidth_khz < MHZ_TO_KHZ(160))
- bw_flags |= IEEE80211_CHAN_NO_160MHZ;
-
chan->flags |= map_regdom_flags(reg_rule->flags) | bw_flags;
chan->max_antenna_gain = (int) MBI_TO_DBI(power_rule->max_antenna_gain);
chan->max_reg_power = chan->max_power =
--
2.0.0.rc4


2014-07-24 11:08:04

by Johannes Berg

[permalink] [raw]
Subject: Re: [PATCH] cfg80211: Try multiple bandwidths when checking usable channels.

On Thu, 2014-07-10 at 15:25 +0200, Rostislav Lisovy wrote:

> const struct ieee80211_reg_rule *freq_reg_info(struct wiphy *wiphy,
> - u32 center_freq);
> + u32 center_freq,
> + u32 desired_bw_khz);

You can't just change a function prototype without changing all the
callers...

johannes