cfg80211: allow userspace to control supported rates in scan
Some P2P scans are not allowed to advertise
11b rates, but that is a rather special case
so instead of having that, allow userspace
to request the rate sets (per band) that are
advertised in scan probe request frames.
Since it's needed in two places now, factor
out some common code parsing a rate array.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
diff --git a/include/linux/nl80211.h b/include/linux/nl80211.h
index 7ba71e4..e4da76c 100644
--- a/include/linux/nl80211.h
+++ b/include/linux/nl80211.h
@@ -1014,6 +1014,11 @@
* @%NL80211_ATTR_REKEY_DATA: nested attribute containing the information
* necessary for GTK rekeying in the device, see &enum nl80211_rekey_data.
*
+ * @NL80211_ATTR_SCAN_SUPP_RATES: rates per to be advertised as supported in scan,
+ * nested array attribute containing an entry for each band, with the entry
+ * being a list of supported rates as defined by IEEE 802.11 7.3.2.2 but
+ * without the length restriction (at most %NL80211_MAX_SUPP_RATES).
+ *
* @NL80211_ATTR_MAX: highest attribute number currently defined
* @__NL80211_ATTR_AFTER_LAST: internal use
*/
@@ -1217,6 +1222,8 @@
NL80211_ATTR_MAX_NUM_SCHED_SCAN_SSIDS,
NL80211_ATTR_MAX_SCHED_SCAN_IE_LEN,
+ NL80211_ATTR_SCAN_SUPP_RATES,
+
/* add attributes here, update the policy in nl80211.c */
__NL80211_ATTR_AFTER_LAST,
diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 930c783..d17f47f 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -777,6 +777,7 @@
* @n_channels: total number of channels to scan
* @ie: optional information element(s) to add into Probe Request or %NULL
* @ie_len: length of ie in octets
+ * @rates: bitmap of rates to advertise for each band
* @wiphy: the wiphy this was for
* @dev: the interface
* @aborted: (internal) scan request was notified as aborted
@@ -788,6 +789,8 @@
const u8 *ie;
size_t ie_len;
+ u32 rates[IEEE80211_NUM_BANDS];
+
/* internal */
struct wiphy *wiphy;
struct net_device *dev;
diff --git a/net/wireless/core.h b/net/wireless/core.h
index a570ff9..8672e02 100644
--- a/net/wireless/core.h
+++ b/net/wireless/core.h
@@ -447,6 +447,10 @@
u16 cfg80211_calculate_bitrate(struct rate_info *rate);
+int ieee80211_get_ratemask(struct ieee80211_supported_band *sband,
+ const u8 *rates, unsigned int n_rates,
+ u32 *mask);
+
int cfg80211_validate_beacon_int(struct cfg80211_registered_device *rdev,
u32 beacon_int);
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 44a3fc2..20aa390 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -177,6 +177,7 @@
[NL80211_ATTR_STA_PLINK_STATE] = { .type = NLA_U8 },
[NL80211_ATTR_SCHED_SCAN_INTERVAL] = { .type = NLA_U32 },
[NL80211_ATTR_REKEY_DATA] = { .type = NLA_NESTED },
+ [NL80211_ATTR_SCAN_SUPP_RATES] = { .type = NLA_NESTED },
};
/* policy for the key attributes */
@@ -3324,7 +3325,6 @@
struct nlattr *attr;
struct wiphy *wiphy;
int err, tmp, n_ssids = 0, n_channels, i;
- enum ieee80211_band band;
size_t ie_len;
if (!is_valid_ie_attr(info->attrs[NL80211_ATTR_IE]))
@@ -3344,6 +3344,7 @@
if (!n_channels)
return -EINVAL;
} else {
+ enum ieee80211_band band;
n_channels = 0;
for (band = 0; band < IEEE80211_NUM_BANDS; band++)
@@ -3404,6 +3405,8 @@
i++;
}
} else {
+ enum ieee80211_band band;
+
/* all channels */
for (band = 0; band < IEEE80211_NUM_BANDS; band++) {
int j;
@@ -3450,6 +3453,28 @@
request->ie_len);
}
+ for (i = 0; i < IEEE80211_NUM_BANDS; i++)
+ request->rates[i] = (1 << wiphy->bands[i]->n_bitrates) - 1;
+
+ if (info->attrs[NL80211_ATTR_SCAN_SUPP_RATES]) {
+ nla_for_each_nested(attr,
+ info->attrs[NL80211_ATTR_SCAN_SUPP_RATES],
+ tmp) {
+ enum ieee80211_band band = nla_type(attr);
+
+ if (band < 0 || band > IEEE80211_NUM_BANDS) {
+ err = -EINVAL;
+ goto out_free;
+ }
+ err = ieee80211_get_ratemask(wiphy->bands[band],
+ nla_data(attr),
+ nla_len(attr),
+ &request->rates[band]);
+ if (err)
+ goto out_free;
+ }
+ }
+
request->dev = dev;
request->wiphy = &rdev->wiphy;
@@ -4336,25 +4361,12 @@
nla_len(info->attrs[NL80211_ATTR_BSS_BASIC_RATES]);
struct ieee80211_supported_band *sband =
wiphy->bands[ibss.channel->band];
- int i, j;
+ int err;
- if (n_rates == 0)
- return -EINVAL;
-
- for (i = 0; i < n_rates; i++) {
- int rate = (rates[i] & 0x7f) * 5;
- bool found = false;
-
- for (j = 0; j < sband->n_bitrates; j++) {
- if (sband->bitrates[j].bitrate == rate) {
- found = true;
- ibss.basic_rates |= BIT(j);
- break;
- }
- }
- if (!found)
- return -EINVAL;
- }
+ err = ieee80211_get_ratemask(sband, rates, n_rates,
+ &ibss.basic_rates);
+ if (err)
+ return err;
}
if (info->attrs[NL80211_ATTR_MCAST_RATE] &&
diff --git a/net/wireless/scan.c b/net/wireless/scan.c
index 1c4672e..1e7ff94 100644
--- a/net/wireless/scan.c
+++ b/net/wireless/scan.c
@@ -862,6 +862,9 @@
creq->n_ssids = 0;
}
+ for (i = 0; i < IEEE80211_NUM_BANDS; i++)
+ creq->rates[i] = (1 << wiphy->bands[i]->n_bitrates) - 1;
+
rdev->scan_req = creq;
err = rdev->ops->scan(wiphy, dev, creq);
if (err) {
diff --git a/net/wireless/util.c b/net/wireless/util.c
index 4d7b83f..a329429 100644
--- a/net/wireless/util.c
+++ b/net/wireless/util.c
@@ -1006,3 +1006,38 @@
return -EBUSY;
}
+
+int ieee80211_get_ratemask(struct ieee80211_supported_band *sband,
+ const u8 *rates, unsigned int n_rates,
+ u32 *mask)
+{
+ int i, j;
+
+ if (n_rates == 0 || n_rates > NL80211_MAX_SUPP_RATES)
+ return -EINVAL;
+
+ *mask = 0;
+
+ for (i = 0; i < n_rates; i++) {
+ int rate = (rates[i] & 0x7f) * 5;
+ bool found = false;
+
+ for (j = 0; j < sband->n_bitrates; j++) {
+ if (sband->bitrates[j].bitrate == rate) {
+ found = true;
+ *mask |= BIT(j);
+ break;
+ }
+ }
+ if (!found)
+ return -EINVAL;
+ }
+
+ /*
+ * mask must have at least one bit set here since we
+ * didn't accept a 0-length rates array nor allowed
+ * entries in the array that didn't exist
+ */
+
+ return 0;
+}