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>
This commit is contained in:
parent
f444de05d2
commit
0aaffa9b96
|
@ -651,17 +651,17 @@ static void mac80211_hwsim_beacon(unsigned long arg)
|
||||||
add_timer(&data->beacon_timer);
|
add_timer(&data->beacon_timer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char *hwsim_chantypes[] = {
|
||||||
|
[NL80211_CHAN_NO_HT] = "noht",
|
||||||
|
[NL80211_CHAN_HT20] = "ht20",
|
||||||
|
[NL80211_CHAN_HT40MINUS] = "ht40-",
|
||||||
|
[NL80211_CHAN_HT40PLUS] = "ht40+",
|
||||||
|
};
|
||||||
|
|
||||||
static int mac80211_hwsim_config(struct ieee80211_hw *hw, u32 changed)
|
static int mac80211_hwsim_config(struct ieee80211_hw *hw, u32 changed)
|
||||||
{
|
{
|
||||||
struct mac80211_hwsim_data *data = hw->priv;
|
struct mac80211_hwsim_data *data = hw->priv;
|
||||||
struct ieee80211_conf *conf = &hw->conf;
|
struct ieee80211_conf *conf = &hw->conf;
|
||||||
static const char *chantypes[4] = {
|
|
||||||
[NL80211_CHAN_NO_HT] = "noht",
|
|
||||||
[NL80211_CHAN_HT20] = "ht20",
|
|
||||||
[NL80211_CHAN_HT40MINUS] = "ht40-",
|
|
||||||
[NL80211_CHAN_HT40PLUS] = "ht40+",
|
|
||||||
};
|
|
||||||
static const char *smps_modes[IEEE80211_SMPS_NUM_MODES] = {
|
static const char *smps_modes[IEEE80211_SMPS_NUM_MODES] = {
|
||||||
[IEEE80211_SMPS_AUTOMATIC] = "auto",
|
[IEEE80211_SMPS_AUTOMATIC] = "auto",
|
||||||
[IEEE80211_SMPS_OFF] = "off",
|
[IEEE80211_SMPS_OFF] = "off",
|
||||||
|
@ -672,7 +672,7 @@ static int mac80211_hwsim_config(struct ieee80211_hw *hw, u32 changed)
|
||||||
printk(KERN_DEBUG "%s:%s (freq=%d/%s idle=%d ps=%d smps=%s)\n",
|
printk(KERN_DEBUG "%s:%s (freq=%d/%s idle=%d ps=%d smps=%s)\n",
|
||||||
wiphy_name(hw->wiphy), __func__,
|
wiphy_name(hw->wiphy), __func__,
|
||||||
conf->channel->center_freq,
|
conf->channel->center_freq,
|
||||||
chantypes[conf->channel_type],
|
hwsim_chantypes[conf->channel_type],
|
||||||
!!(conf->flags & IEEE80211_CONF_IDLE),
|
!!(conf->flags & IEEE80211_CONF_IDLE),
|
||||||
!!(conf->flags & IEEE80211_CONF_PS),
|
!!(conf->flags & IEEE80211_CONF_PS),
|
||||||
smps_modes[conf->smps_mode]);
|
smps_modes[conf->smps_mode]);
|
||||||
|
@ -760,9 +760,10 @@ static void mac80211_hwsim_bss_info_changed(struct ieee80211_hw *hw,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changed & BSS_CHANGED_HT) {
|
if (changed & BSS_CHANGED_HT) {
|
||||||
printk(KERN_DEBUG " %s: HT: op_mode=0x%x\n",
|
printk(KERN_DEBUG " %s: HT: op_mode=0x%x, chantype=%s\n",
|
||||||
wiphy_name(hw->wiphy),
|
wiphy_name(hw->wiphy),
|
||||||
info->ht_operation_mode);
|
info->ht_operation_mode,
|
||||||
|
hwsim_chantypes[info->channel_type]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changed & BSS_CHANGED_BASIC_RATES) {
|
if (changed & BSS_CHANGED_BASIC_RATES) {
|
||||||
|
|
|
@ -191,6 +191,9 @@ enum ieee80211_bss_change {
|
||||||
* the current band.
|
* the current band.
|
||||||
* @bssid: The BSSID for this BSS
|
* @bssid: The BSSID for this BSS
|
||||||
* @enable_beacon: whether beaconing should be enabled or not
|
* @enable_beacon: whether beaconing should be enabled or not
|
||||||
|
* @channel_type: Channel type for this BSS -- the hardware might be
|
||||||
|
* configured for HT40+ while this BSS only uses no-HT, for
|
||||||
|
* example.
|
||||||
* @ht_operation_mode: HT operation mode (like in &struct ieee80211_ht_info).
|
* @ht_operation_mode: HT operation mode (like in &struct ieee80211_ht_info).
|
||||||
* This field is only valid when the channel type is one of the HT types.
|
* This field is only valid when the channel type is one of the HT types.
|
||||||
* @cqm_rssi_thold: Connection quality monitor RSSI threshold, a zero value
|
* @cqm_rssi_thold: Connection quality monitor RSSI threshold, a zero value
|
||||||
|
@ -215,6 +218,7 @@ struct ieee80211_bss_conf {
|
||||||
u16 ht_operation_mode;
|
u16 ht_operation_mode;
|
||||||
s32 cqm_rssi_thold;
|
s32 cqm_rssi_thold;
|
||||||
u32 cqm_rssi_hyst;
|
u32 cqm_rssi_hyst;
|
||||||
|
enum nl80211_channel_type channel_type;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1166,23 +1166,34 @@ static int ieee80211_set_channel(struct wiphy *wiphy,
|
||||||
enum nl80211_channel_type channel_type)
|
enum nl80211_channel_type channel_type)
|
||||||
{
|
{
|
||||||
struct ieee80211_local *local = wiphy_priv(wiphy);
|
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)) {
|
switch (ieee80211_get_channel_mode(local, NULL)) {
|
||||||
case CHAN_MODE_HOPPING:
|
case CHAN_MODE_HOPPING:
|
||||||
return -EBUSY;
|
return -EBUSY;
|
||||||
case CHAN_MODE_FIXED:
|
case CHAN_MODE_FIXED:
|
||||||
if (local->oper_channel == chan &&
|
if (local->oper_channel != chan)
|
||||||
local->oper_channel_type == channel_type)
|
return -EBUSY;
|
||||||
|
if (!sdata && local->_oper_channel_type == channel_type)
|
||||||
return 0;
|
return 0;
|
||||||
return -EBUSY;
|
break;
|
||||||
case CHAN_MODE_UNDEFINED:
|
case CHAN_MODE_UNDEFINED:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
local->oper_channel = chan;
|
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
|
#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.
|
* association, there's no need to send an action frame.
|
||||||
*/
|
*/
|
||||||
if (!sdata->u.mgd.associated ||
|
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);
|
mutex_lock(&sdata->local->iflist_mtx);
|
||||||
ieee80211_recalc_smps(sdata->local, sdata);
|
ieee80211_recalc_smps(sdata->local, sdata);
|
||||||
mutex_unlock(&sdata->local->iflist_mtx);
|
mutex_unlock(&sdata->local->iflist_mtx);
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
* mac80211 - channel management
|
* mac80211 - channel management
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <linux/nl80211.h>
|
||||||
#include "ieee80211_i.h"
|
#include "ieee80211_i.h"
|
||||||
|
|
||||||
enum ieee80211_chan_mode
|
enum ieee80211_chan_mode
|
||||||
|
@ -55,3 +56,72 @@ ieee80211_get_channel_mode(struct ieee80211_local *local,
|
||||||
|
|
||||||
return mode;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
sdata->drop_unencrypted = capability & WLAN_CAPABILITY_PRIVACY ? 1 : 0;
|
||||||
|
|
||||||
local->oper_channel = chan;
|
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);
|
ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_CHANNEL);
|
||||||
|
|
||||||
sband = local->hw.wiphy->bands[chan->band];
|
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 */
|
/* fix ourselves to that channel now already */
|
||||||
if (params->channel_fixed) {
|
if (params->channel_fixed) {
|
||||||
sdata->local->oper_channel = params->channel;
|
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) {
|
if (params->ie) {
|
||||||
|
|
|
@ -768,7 +768,7 @@ struct ieee80211_local {
|
||||||
enum mac80211_scan_state next_scan_state;
|
enum mac80211_scan_state next_scan_state;
|
||||||
struct delayed_work scan_work;
|
struct delayed_work scan_work;
|
||||||
struct ieee80211_sub_if_data *scan_sdata;
|
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;
|
struct ieee80211_channel *oper_channel, *csa_channel;
|
||||||
|
|
||||||
/* Temporary remain-on-channel for off-channel operations */
|
/* Temporary remain-on-channel for off-channel operations */
|
||||||
|
@ -1239,6 +1239,9 @@ enum ieee80211_chan_mode {
|
||||||
enum ieee80211_chan_mode
|
enum ieee80211_chan_mode
|
||||||
ieee80211_get_channel_mode(struct ieee80211_local *local,
|
ieee80211_get_channel_mode(struct ieee80211_local *local,
|
||||||
struct ieee80211_sub_if_data *ignore);
|
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
|
#ifdef CONFIG_MAC80211_NOINLINE
|
||||||
#define debug_noinline noinline
|
#define debug_noinline noinline
|
||||||
|
|
|
@ -111,7 +111,7 @@ int ieee80211_hw_config(struct ieee80211_local *local, u32 changed)
|
||||||
channel_type = local->tmp_channel_type;
|
channel_type = local->tmp_channel_type;
|
||||||
} else {
|
} else {
|
||||||
chan = local->oper_channel;
|
chan = local->oper_channel;
|
||||||
channel_type = local->oper_channel_type;
|
channel_type = local->_oper_channel_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chan != local->hw.conf.channel ||
|
if (chan != local->hw.conf.channel ||
|
||||||
|
|
|
@ -136,11 +136,14 @@ static u32 ieee80211_enable_ht(struct ieee80211_sub_if_data *sdata,
|
||||||
struct sta_info *sta;
|
struct sta_info *sta;
|
||||||
u32 changed = 0;
|
u32 changed = 0;
|
||||||
u16 ht_opmode;
|
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;
|
enum nl80211_channel_type channel_type = NL80211_CHAN_NO_HT;
|
||||||
|
|
||||||
sband = local->hw.wiphy->bands[local->hw.conf.channel->band];
|
sband = local->hw.wiphy->bands[local->hw.conf.channel->band];
|
||||||
|
|
||||||
|
prev_chantype = sdata->vif.bss_conf.channel_type;
|
||||||
|
|
||||||
/* HT is not supported */
|
/* HT is not supported */
|
||||||
if (!sband->ht_cap.ht_supported)
|
if (!sband->ht_cap.ht_supported)
|
||||||
enable_ht = false;
|
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)
|
if (local->tmp_channel)
|
||||||
local->tmp_channel_type = channel_type;
|
local->tmp_channel_type = channel_type;
|
||||||
local->oper_channel_type = channel_type;
|
|
||||||
|
|
||||||
if (ht_changed) {
|
if (!ieee80211_set_channel_type(local, sdata, channel_type)) {
|
||||||
/* channel_type change automatically detected */
|
/* can only fail due to HT40+/- mismatch */
|
||||||
ieee80211_hw_config(local, 0);
|
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();
|
rcu_read_lock();
|
||||||
sta = sta_info_get(sdata, bssid);
|
sta = sta_info_get(sdata, bssid);
|
||||||
if (sta)
|
if (sta)
|
||||||
rate_control_rate_update(local, sband, sta,
|
rate_control_rate_update(local, sband, sta,
|
||||||
IEEE80211_RC_HT_CHANGED,
|
IEEE80211_RC_HT_CHANGED,
|
||||||
local->oper_channel_type);
|
channel_type);
|
||||||
rcu_read_unlock();
|
rcu_read_unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* disable HT */
|
|
||||||
if (!enable_ht)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
ht_opmode = le16_to_cpu(hti->operation_mode);
|
ht_opmode = le16_to_cpu(hti->operation_mode);
|
||||||
|
|
||||||
/* if bss configuration changed store the new one */
|
/* if bss configuration changed store the new one */
|
||||||
if (!sdata->ht_opmode_valid ||
|
if (sdata->ht_opmode_valid != enable_ht ||
|
||||||
sdata->vif.bss_conf.ht_operation_mode != ht_opmode) {
|
sdata->vif.bss_conf.ht_operation_mode != ht_opmode ||
|
||||||
|
prev_chantype != channel_type) {
|
||||||
changed |= BSS_CHANGED_HT;
|
changed |= BSS_CHANGED_HT;
|
||||||
sdata->vif.bss_conf.ht_operation_mode = ht_opmode;
|
sdata->vif.bss_conf.ht_operation_mode = ht_opmode;
|
||||||
sdata->ht_opmode_valid = true;
|
sdata->ht_opmode_valid = enable_ht;
|
||||||
}
|
}
|
||||||
|
|
||||||
return changed;
|
return changed;
|
||||||
|
@ -865,7 +867,7 @@ static void ieee80211_set_disassoc(struct ieee80211_sub_if_data *sdata,
|
||||||
ieee80211_set_wmm_default(sdata);
|
ieee80211_set_wmm_default(sdata);
|
||||||
|
|
||||||
/* channel(_type) changes are handled by ieee80211_hw_config */
|
/* 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 */
|
/* on the next assoc, re-program HT parameters */
|
||||||
sdata->ht_opmode_valid = false;
|
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);
|
ieee80211_hw_config(local, config_changed);
|
||||||
|
|
||||||
/* And the BSSID changed -- not very interesting here */
|
/* The BSSID (not really interesting) and HT changed */
|
||||||
changed |= BSS_CHANGED_BSSID;
|
changed |= BSS_CHANGED_BSSID | BSS_CHANGED_HT;
|
||||||
ieee80211_bss_info_change_notify(sdata, changed);
|
ieee80211_bss_info_change_notify(sdata, changed);
|
||||||
|
|
||||||
if (remove_sta)
|
if (remove_sta)
|
||||||
|
@ -2265,7 +2267,7 @@ int ieee80211_mgd_action(struct ieee80211_sub_if_data *sdata,
|
||||||
if ((chan != local->tmp_channel ||
|
if ((chan != local->tmp_channel ||
|
||||||
channel_type != local->tmp_channel_type) &&
|
channel_type != local->tmp_channel_type) &&
|
||||||
(chan != local->oper_channel ||
|
(chan != local->oper_channel ||
|
||||||
channel_type != local->oper_channel_type))
|
channel_type != local->_oper_channel_type))
|
||||||
return -EBUSY;
|
return -EBUSY;
|
||||||
|
|
||||||
skb = dev_alloc_skb(local->hw.extra_tx_headroom + len);
|
skb = dev_alloc_skb(local->hw.extra_tx_headroom + len);
|
||||||
|
|
Loading…
Reference in New Issue