summaryrefslogtreecommitdiffstats
path: root/net/mac80211
diff options
context:
space:
mode:
authorJohannes Berg <johannes@sipsolutions.net>2010-05-05 15:28:27 +0200
committerJohn W. Linville <linville@tuxdriver.com>2010-05-07 14:55:51 -0400
commit0aaffa9b9699894aab3266195a529baf9f96ac29 (patch)
tree26fe5f5277ac6d7061ea723f92d4038b0c28b0b8 /net/mac80211
parentf444de05d20e27cdd960c13fcbcfca3099f03143 (diff)
downloadlinux-0aaffa9b9699894aab3266195a529baf9f96ac29.tar.gz
linux-0aaffa9b9699894aab3266195a529baf9f96ac29.tar.bz2
linux-0aaffa9b9699894aab3266195a529baf9f96ac29.zip
mac80211: improve HT channel handling
Currently, when one interface switches HT mode, all others will follow along. This is clearly undesirable, since the new one might switch to no-HT while another one is operating in HT. Address this issue by keeping track of the HT mode per interface, and allowing only changes that are compatible, i.e. switching into HT40+ is not possible when another interface is in HT40-, in that case the second one needs to fall back to HT20. Also, to allow drivers to know what's going on, store the per-interface HT mode (channel type) in the virtual interface's bss_conf. Signed-off-by: Johannes Berg <johannes@sipsolutions.net> Signed-off-by: John W. Linville <linville@tuxdriver.com>
Diffstat (limited to 'net/mac80211')
-rw-r--r--net/mac80211/cfg.c23
-rw-r--r--net/mac80211/chan.c70
-rw-r--r--net/mac80211/ibss.c5
-rw-r--r--net/mac80211/ieee80211_i.h5
-rw-r--r--net/mac80211/main.c2
-rw-r--r--net/mac80211/mlme.c44
6 files changed, 118 insertions, 31 deletions
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 414b7dd7d7fd..ab166c6d9399 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -1166,23 +1166,34 @@ static int ieee80211_set_channel(struct wiphy *wiphy,
enum nl80211_channel_type channel_type)
{
struct ieee80211_local *local = wiphy_priv(wiphy);
+ struct ieee80211_sub_if_data *sdata = NULL;
+
+ if (netdev)
+ sdata = IEEE80211_DEV_TO_SUB_IF(netdev);
switch (ieee80211_get_channel_mode(local, NULL)) {
case CHAN_MODE_HOPPING:
return -EBUSY;
case CHAN_MODE_FIXED:
- if (local->oper_channel == chan &&
- local->oper_channel_type == channel_type)
+ if (local->oper_channel != chan)
+ return -EBUSY;
+ if (!sdata && local->_oper_channel_type == channel_type)
return 0;
- return -EBUSY;
+ break;
case CHAN_MODE_UNDEFINED:
break;
}
local->oper_channel = chan;
- local->oper_channel_type = channel_type;
- return ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_CHANNEL);
+ if (!ieee80211_set_channel_type(local, sdata, channel_type))
+ return -EBUSY;
+
+ ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_CHANNEL);
+ if (sdata && sdata->vif.type != NL80211_IFTYPE_MONITOR)
+ ieee80211_bss_info_change_notify(sdata, BSS_CHANGED_HT);
+
+ return 0;
}
#ifdef CONFIG_PM
@@ -1406,7 +1417,7 @@ int __ieee80211_request_smps(struct ieee80211_sub_if_data *sdata,
* association, there's no need to send an action frame.
*/
if (!sdata->u.mgd.associated ||
- sdata->local->oper_channel_type == NL80211_CHAN_NO_HT) {
+ sdata->vif.bss_conf.channel_type == NL80211_CHAN_NO_HT) {
mutex_lock(&sdata->local->iflist_mtx);
ieee80211_recalc_smps(sdata->local, sdata);
mutex_unlock(&sdata->local->iflist_mtx);
diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
index 08f3832661a5..5d218c530a4e 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -2,6 +2,7 @@
* mac80211 - channel management
*/
+#include <linux/nl80211.h>
#include "ieee80211_i.h"
enum ieee80211_chan_mode
@@ -55,3 +56,72 @@ ieee80211_get_channel_mode(struct ieee80211_local *local,
return mode;
}
+
+bool ieee80211_set_channel_type(struct ieee80211_local *local,
+ struct ieee80211_sub_if_data *sdata,
+ enum nl80211_channel_type chantype)
+{
+ struct ieee80211_sub_if_data *tmp;
+ enum nl80211_channel_type superchan = NL80211_CHAN_NO_HT;
+ bool result;
+
+ mutex_lock(&local->iflist_mtx);
+
+ list_for_each_entry(tmp, &local->interfaces, list) {
+ if (tmp == sdata)
+ continue;
+
+ if (!ieee80211_sdata_running(tmp))
+ continue;
+
+ switch (tmp->vif.bss_conf.channel_type) {
+ case NL80211_CHAN_NO_HT:
+ case NL80211_CHAN_HT20:
+ superchan = tmp->vif.bss_conf.channel_type;
+ break;
+ case NL80211_CHAN_HT40PLUS:
+ WARN_ON(superchan == NL80211_CHAN_HT40MINUS);
+ superchan = NL80211_CHAN_HT40PLUS;
+ break;
+ case NL80211_CHAN_HT40MINUS:
+ WARN_ON(superchan == NL80211_CHAN_HT40PLUS);
+ superchan = NL80211_CHAN_HT40MINUS;
+ break;
+ }
+ }
+
+ switch (superchan) {
+ case NL80211_CHAN_NO_HT:
+ case NL80211_CHAN_HT20:
+ /*
+ * allow any change that doesn't go to no-HT
+ * (if it already is no-HT no change is needed)
+ */
+ if (chantype == NL80211_CHAN_NO_HT)
+ break;
+ superchan = chantype;
+ break;
+ case NL80211_CHAN_HT40PLUS:
+ case NL80211_CHAN_HT40MINUS:
+ /* allow smaller bandwidth and same */
+ if (chantype == NL80211_CHAN_NO_HT)
+ break;
+ if (chantype == NL80211_CHAN_HT20)
+ break;
+ if (superchan == chantype)
+ break;
+ result = false;
+ goto out;
+ }
+
+ local->_oper_channel_type = superchan;
+
+ if (sdata)
+ sdata->vif.bss_conf.channel_type = chantype;
+
+ result = true;
+ out:
+ mutex_unlock(&local->iflist_mtx);
+
+ return result;
+}
diff --git a/net/mac80211/ibss.c b/net/mac80211/ibss.c
index d5855ae387e8..36745f494f63 100644
--- a/net/mac80211/ibss.c
+++ b/net/mac80211/ibss.c
@@ -102,7 +102,7 @@ static void __ieee80211_sta_join_ibss(struct ieee80211_sub_if_data *sdata,
sdata->drop_unencrypted = capability & WLAN_CAPABILITY_PRIVACY ? 1 : 0;
local->oper_channel = chan;
- local->oper_channel_type = NL80211_CHAN_NO_HT;
+ WARN_ON(!ieee80211_set_channel_type(local, sdata, NL80211_CHAN_NO_HT));
ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_CHANNEL);
sband = local->hw.wiphy->bands[chan->band];
@@ -910,7 +910,8 @@ int ieee80211_ibss_join(struct ieee80211_sub_if_data *sdata,
/* fix ourselves to that channel now already */
if (params->channel_fixed) {
sdata->local->oper_channel = params->channel;
- sdata->local->oper_channel_type = NL80211_CHAN_NO_HT;
+ WARN_ON(!ieee80211_set_channel_type(sdata->local, sdata,
+ NL80211_CHAN_NO_HT));
}
if (params->ie) {
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 359edff31471..69e7f4131f46 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -768,7 +768,7 @@ struct ieee80211_local {
enum mac80211_scan_state next_scan_state;
struct delayed_work scan_work;
struct ieee80211_sub_if_data *scan_sdata;
- enum nl80211_channel_type oper_channel_type;
+ enum nl80211_channel_type _oper_channel_type;
struct ieee80211_channel *oper_channel, *csa_channel;
/* Temporary remain-on-channel for off-channel operations */
@@ -1239,6 +1239,9 @@ enum ieee80211_chan_mode {
enum ieee80211_chan_mode
ieee80211_get_channel_mode(struct ieee80211_local *local,
struct ieee80211_sub_if_data *ignore);
+bool ieee80211_set_channel_type(struct ieee80211_local *local,
+ struct ieee80211_sub_if_data *sdata,
+ enum nl80211_channel_type chantype);
#ifdef CONFIG_MAC80211_NOINLINE
#define debug_noinline noinline
diff --git a/net/mac80211/main.c b/net/mac80211/main.c
index 353b6b42d9c5..d763d76e809f 100644
--- a/net/mac80211/main.c
+++ b/net/mac80211/main.c
@@ -111,7 +111,7 @@ int ieee80211_hw_config(struct ieee80211_local *local, u32 changed)
channel_type = local->tmp_channel_type;
} else {
chan = local->oper_channel;
- channel_type = local->oper_channel_type;
+ channel_type = local->_oper_channel_type;
}
if (chan != local->hw.conf.channel ||
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index 17cb8ae912bc..6e149b49d4f0 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -136,11 +136,14 @@ static u32 ieee80211_enable_ht(struct ieee80211_sub_if_data *sdata,
struct sta_info *sta;
u32 changed = 0;
u16 ht_opmode;
- bool enable_ht = true, ht_changed;
+ bool enable_ht = true;
+ enum nl80211_channel_type prev_chantype;
enum nl80211_channel_type channel_type = NL80211_CHAN_NO_HT;
sband = local->hw.wiphy->bands[local->hw.conf.channel->band];
+ prev_chantype = sdata->vif.bss_conf.channel_type;
+
/* HT is not supported */
if (!sband->ht_cap.ht_supported)
enable_ht = false;
@@ -171,38 +174,37 @@ static u32 ieee80211_enable_ht(struct ieee80211_sub_if_data *sdata,
}
}
- ht_changed = conf_is_ht(&local->hw.conf) != enable_ht ||
- channel_type != local->hw.conf.channel_type;
-
if (local->tmp_channel)
local->tmp_channel_type = channel_type;
- local->oper_channel_type = channel_type;
- if (ht_changed) {
- /* channel_type change automatically detected */
- ieee80211_hw_config(local, 0);
+ if (!ieee80211_set_channel_type(local, sdata, channel_type)) {
+ /* can only fail due to HT40+/- mismatch */
+ channel_type = NL80211_CHAN_HT20;
+ WARN_ON(!ieee80211_set_channel_type(local, sdata, channel_type));
+ }
+ /* channel_type change automatically detected */
+ ieee80211_hw_config(local, 0);
+
+ if (prev_chantype != channel_type) {
rcu_read_lock();
sta = sta_info_get(sdata, bssid);
if (sta)
rate_control_rate_update(local, sband, sta,
IEEE80211_RC_HT_CHANGED,
- local->oper_channel_type);
+ channel_type);
rcu_read_unlock();
- }
-
- /* disable HT */
- if (!enable_ht)
- return 0;
+ }
ht_opmode = le16_to_cpu(hti->operation_mode);
/* if bss configuration changed store the new one */
- if (!sdata->ht_opmode_valid ||
- sdata->vif.bss_conf.ht_operation_mode != ht_opmode) {
+ if (sdata->ht_opmode_valid != enable_ht ||
+ sdata->vif.bss_conf.ht_operation_mode != ht_opmode ||
+ prev_chantype != channel_type) {
changed |= BSS_CHANGED_HT;
sdata->vif.bss_conf.ht_operation_mode = ht_opmode;
- sdata->ht_opmode_valid = true;
+ sdata->ht_opmode_valid = enable_ht;
}
return changed;
@@ -865,7 +867,7 @@ static void ieee80211_set_disassoc(struct ieee80211_sub_if_data *sdata,
ieee80211_set_wmm_default(sdata);
/* channel(_type) changes are handled by ieee80211_hw_config */
- local->oper_channel_type = NL80211_CHAN_NO_HT;
+ WARN_ON(!ieee80211_set_channel_type(local, sdata, NL80211_CHAN_NO_HT));
/* on the next assoc, re-program HT parameters */
sdata->ht_opmode_valid = false;
@@ -882,8 +884,8 @@ static void ieee80211_set_disassoc(struct ieee80211_sub_if_data *sdata,
ieee80211_hw_config(local, config_changed);
- /* And the BSSID changed -- not very interesting here */
- changed |= BSS_CHANGED_BSSID;
+ /* The BSSID (not really interesting) and HT changed */
+ changed |= BSS_CHANGED_BSSID | BSS_CHANGED_HT;
ieee80211_bss_info_change_notify(sdata, changed);
if (remove_sta)
@@ -2265,7 +2267,7 @@ int ieee80211_mgd_action(struct ieee80211_sub_if_data *sdata,
if ((chan != local->tmp_channel ||
channel_type != local->tmp_channel_type) &&
(chan != local->oper_channel ||
- channel_type != local->oper_channel_type))
+ channel_type != local->_oper_channel_type))
return -EBUSY;
skb = dev_alloc_skb(local->hw.extra_tx_headroom + len);