mac80211: rewrite HT handling
The HT handling has the following deficiencies, which I've
(partially) fixed:
* it always uses the AP info even if there is no AP,
hence has no chance of working as an AP
* it pretends to be HW config, but really is per-BSS
* channel sanity checking is left to the drivers
* it generally lets the driver control too much
HT enabling is still wrong with this patch if you have more than
one virtual STA mode interface, but that never happens currently.
Once WDS, IBSS or AP/VLAN gets HT capabilities, it will also be
wrong, see the comment in ieee80211_enable_ht().
Additionally, this fixes a number of bugs:
* mac80211: ieee80211_set_disassoc doesn't notify the driver any
more since the refactoring
* iwl-agn-rs: always uses the HT capabilities from the wrong stuff
mac80211 gives it rather than the actual peer STA
* ath9k: a number of bugs resulting from the broken HT API
I'm not entirely happy with putting the HT capabilities into
struct ieee80211_sta as restricted to our own HT TX capabilities,
but I see no cleaner solution for now.
Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 28382b5..55e3a265 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -582,6 +582,8 @@
struct ieee80211_supported_band *sband;
struct ieee80211_sub_if_data *sdata = sta->sdata;
+ sband = local->hw.wiphy->bands[local->oper_channel->band];
+
/*
* FIXME: updating the flags is racy when this function is
* called from ieee80211_change_station(), this will
@@ -622,7 +624,6 @@
if (params->supported_rates) {
rates = 0;
- sband = local->hw.wiphy->bands[local->oper_channel->band];
for (i = 0; i < params->supported_rates_len; i++) {
int rate = (params->supported_rates[i] & 0x7f) * 5;
@@ -635,7 +636,8 @@
}
if (params->ht_capa)
- ieee80211_ht_cap_ie_to_sta_ht_cap(params->ht_capa,
+ ieee80211_ht_cap_ie_to_sta_ht_cap(sband,
+ params->ht_capa,
&sta->sta.ht_cap);
if (ieee80211_vif_is_mesh(&sdata->vif) && params->plink_action) {
diff --git a/net/mac80211/ht.c b/net/mac80211/ht.c
index e2d121b..42c3e59 100644
--- a/net/mac80211/ht.c
+++ b/net/mac80211/ht.c
@@ -20,114 +20,38 @@
#include "sta_info.h"
#include "wme.h"
-void ieee80211_ht_cap_ie_to_sta_ht_cap(struct ieee80211_ht_cap *ht_cap_ie,
+void ieee80211_ht_cap_ie_to_sta_ht_cap(struct ieee80211_supported_band *sband,
+ struct ieee80211_ht_cap *ht_cap_ie,
struct ieee80211_sta_ht_cap *ht_cap)
{
+ u8 ampdu_info, tx_mcs_set_cap;
+ int i, max_tx_streams;
BUG_ON(!ht_cap);
memset(ht_cap, 0, sizeof(*ht_cap));
- if (ht_cap_ie) {
- u8 ampdu_info = ht_cap_ie->ampdu_params_info;
+ if (!ht_cap_ie)
+ return;
- ht_cap->ht_supported = true;
- ht_cap->cap = le16_to_cpu(ht_cap_ie->cap_info);
- ht_cap->ampdu_factor =
- ampdu_info & IEEE80211_HT_AMPDU_PARM_FACTOR;
- ht_cap->ampdu_density =
- (ampdu_info & IEEE80211_HT_AMPDU_PARM_DENSITY) >> 2;
- memcpy(&ht_cap->mcs, &ht_cap_ie->mcs, sizeof(ht_cap->mcs));
- } else
- ht_cap->ht_supported = false;
-}
+ ht_cap->ht_supported = true;
-void ieee80211_ht_info_ie_to_ht_bss_info(
- struct ieee80211_ht_info *ht_add_info_ie,
- struct ieee80211_ht_bss_info *bss_info)
-{
- BUG_ON(!bss_info);
+ ht_cap->cap = ht_cap->cap & sband->ht_cap.cap;
+ ht_cap->cap &= ~IEEE80211_HT_CAP_SM_PS;
+ ht_cap->cap |= sband->ht_cap.cap & IEEE80211_HT_CAP_SM_PS;
- memset(bss_info, 0, sizeof(*bss_info));
-
- if (ht_add_info_ie) {
- u16 op_mode;
- op_mode = le16_to_cpu(ht_add_info_ie->operation_mode);
-
- bss_info->primary_channel = ht_add_info_ie->control_chan;
- bss_info->bss_cap = ht_add_info_ie->ht_param;
- bss_info->bss_op_mode = (u8)(op_mode & 0xff);
- }
-}
-
-/*
- * ieee80211_handle_ht should be called only after the operating band
- * has been determined as ht configuration depends on the hw's
- * HT abilities for a specific band.
- */
-u32 ieee80211_handle_ht(struct ieee80211_local *local,
- struct ieee80211_sta_ht_cap *req_ht_cap,
- struct ieee80211_ht_bss_info *req_bss_cap)
-{
- struct ieee80211_conf *conf = &local->hw.conf;
- struct ieee80211_supported_band *sband;
- struct ieee80211_sta_ht_cap ht_cap;
- struct ieee80211_ht_bss_info ht_bss_conf;
- u32 changed = 0;
- int i;
- u8 max_tx_streams;
- u8 tx_mcs_set_cap;
- bool enable_ht = true;
-
- sband = local->hw.wiphy->bands[conf->channel->band];
-
- memset(&ht_cap, 0, sizeof(ht_cap));
- memset(&ht_bss_conf, 0, sizeof(struct ieee80211_ht_bss_info));
-
- /* HT is not supported */
- if (!sband->ht_cap.ht_supported)
- enable_ht = false;
-
- /* disable HT */
- if (!enable_ht) {
- if (conf->flags & IEEE80211_CONF_SUPPORT_HT_MODE)
- changed |= BSS_CHANGED_HT;
- conf->flags &= ~IEEE80211_CONF_SUPPORT_HT_MODE;
- conf->ht_cap.ht_supported = false;
- return changed;
- }
-
-
- if (!(conf->flags & IEEE80211_CONF_SUPPORT_HT_MODE))
- changed |= BSS_CHANGED_HT;
-
- conf->flags |= IEEE80211_CONF_SUPPORT_HT_MODE;
- ht_cap.ht_supported = true;
-
- ht_cap.cap = req_ht_cap->cap & sband->ht_cap.cap;
- ht_cap.cap &= ~IEEE80211_HT_CAP_SM_PS;
- ht_cap.cap |= sband->ht_cap.cap & IEEE80211_HT_CAP_SM_PS;
-
- ht_bss_conf.primary_channel = req_bss_cap->primary_channel;
- ht_bss_conf.bss_cap = req_bss_cap->bss_cap;
- ht_bss_conf.bss_op_mode = req_bss_cap->bss_op_mode;
-
- ht_cap.ampdu_factor = req_ht_cap->ampdu_factor;
- ht_cap.ampdu_density = req_ht_cap->ampdu_density;
+ ampdu_info = ht_cap_ie->ampdu_params_info;
+ ht_cap->ampdu_factor =
+ ampdu_info & IEEE80211_HT_AMPDU_PARM_FACTOR;
+ ht_cap->ampdu_density =
+ (ampdu_info & IEEE80211_HT_AMPDU_PARM_DENSITY) >> 2;
/* own MCS TX capabilities */
tx_mcs_set_cap = sband->ht_cap.mcs.tx_params;
- /*
- * configure supported Tx MCS according to requested MCS
- * (based in most cases on Rx capabilities of peer) and self
- * Tx MCS capabilities (as defined by low level driver HW
- * Tx capabilities)
- */
-
/* can we TX with MCS rates? */
if (!(tx_mcs_set_cap & IEEE80211_HT_MCS_TX_DEFINED))
- goto check_changed;
+ return;
/* Counting from 0, therefore +1 */
if (tx_mcs_set_cap & IEEE80211_HT_MCS_TX_RX_DIFF)
@@ -145,29 +69,73 @@
* - remainder are multiple spatial streams using unequal modulation
*/
for (i = 0; i < max_tx_streams; i++)
- ht_cap.mcs.rx_mask[i] =
- sband->ht_cap.mcs.rx_mask[i] &
- req_ht_cap->mcs.rx_mask[i];
+ ht_cap->mcs.rx_mask[i] =
+ sband->ht_cap.mcs.rx_mask[i] & ht_cap_ie->mcs.rx_mask[i];
if (tx_mcs_set_cap & IEEE80211_HT_MCS_TX_UNEQUAL_MODULATION)
for (i = IEEE80211_HT_MCS_UNEQUAL_MODULATION_START_BYTE;
i < IEEE80211_HT_MCS_MASK_LEN; i++)
- ht_cap.mcs.rx_mask[i] =
+ ht_cap->mcs.rx_mask[i] =
sband->ht_cap.mcs.rx_mask[i] &
- req_ht_cap->mcs.rx_mask[i];
+ ht_cap_ie->mcs.rx_mask[i];
/* handle MCS rate 32 too */
- if (sband->ht_cap.mcs.rx_mask[32/8] &
- req_ht_cap->mcs.rx_mask[32/8] & 1)
- ht_cap.mcs.rx_mask[32/8] |= 1;
+ if (sband->ht_cap.mcs.rx_mask[32/8] & ht_cap_ie->mcs.rx_mask[32/8] & 1)
+ ht_cap->mcs.rx_mask[32/8] |= 1;
+}
- check_changed:
+/*
+ * ieee80211_enable_ht should be called only after the operating band
+ * has been determined as ht configuration depends on the hw's
+ * HT abilities for a specific band.
+ */
+u32 ieee80211_enable_ht(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_ht_info *hti,
+ u16 ap_ht_cap_flags)
+{
+ struct ieee80211_local *local = sdata->local;
+ struct ieee80211_supported_band *sband;
+ struct ieee80211_bss_ht_conf ht;
+ u32 changed = 0;
+ bool enable_ht = true, ht_changed;
+
+ sband = local->hw.wiphy->bands[local->hw.conf.channel->band];
+
+ memset(&ht, 0, sizeof(ht));
+
+ /* HT is not supported */
+ if (!sband->ht_cap.ht_supported)
+ enable_ht = false;
+
+ /* check that channel matches the right operating channel */
+ if (local->hw.conf.channel->center_freq !=
+ ieee80211_channel_to_frequency(hti->control_chan))
+ enable_ht = false;
+
+ /*
+ * XXX: This is totally incorrect when there are multiple virtual
+ * interfaces, needs to be fixed later.
+ */
+ ht_changed = local->hw.conf.ht.enabled != enable_ht;
+ local->hw.conf.ht.enabled = enable_ht;
+ if (ht_changed)
+ ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_HT);
+
+ /* disable HT */
+ if (!enable_ht)
+ return 0;
+ ht.secondary_channel_offset =
+ hti->ht_param & IEEE80211_HT_PARAM_CHA_SEC_OFFSET;
+ ht.width_40_ok =
+ !(ap_ht_cap_flags & IEEE80211_HT_CAP_40MHZ_INTOLERANT) &&
+ (sband->ht_cap.cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40) &&
+ (hti->ht_param & IEEE80211_HT_PARAM_CHAN_WIDTH_ANY);
+ ht.operation_mode = le16_to_cpu(hti->operation_mode);
+
/* if bss configuration changed store the new one */
- if (memcmp(&conf->ht_cap, &ht_cap, sizeof(ht_cap)) ||
- memcmp(&conf->ht_bss_conf, &ht_bss_conf, sizeof(ht_bss_conf))) {
+ if (memcmp(&sdata->vif.bss_conf.ht, &ht, sizeof(ht))) {
changed |= BSS_CHANGED_HT;
- memcpy(&conf->ht_cap, &ht_cap, sizeof(ht_cap));
- memcpy(&conf->ht_bss_conf, &ht_bss_conf, sizeof(ht_bss_conf));
+ sdata->vif.bss_conf.ht = ht;
}
return changed;
@@ -900,8 +868,9 @@
/* sanity check for incoming parameters:
* check if configuration can support the BA policy
* and if buffer size does not exceeds max value */
+ /* XXX: check own ht delayed BA capability?? */
if (((ba_policy != 1)
- && (!(conf->ht_cap.cap & IEEE80211_HT_CAP_DELAY_BA)))
+ && (!(sta->sta.ht_cap.cap & IEEE80211_HT_CAP_DELAY_BA)))
|| (buf_size > IEEE80211_MAX_AMPDU_BUF)) {
status = WLAN_STATUS_INVALID_QOS_PARAM;
#ifdef CONFIG_MAC80211_HT_DEBUG
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 859b5b0..6f8756d 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -955,14 +955,12 @@
int ieee80211_subif_start_xmit(struct sk_buff *skb, struct net_device *dev);
/* HT */
-void ieee80211_ht_cap_ie_to_sta_ht_cap(struct ieee80211_ht_cap *ht_cap_ie,
+void ieee80211_ht_cap_ie_to_sta_ht_cap(struct ieee80211_supported_band *sband,
+ struct ieee80211_ht_cap *ht_cap_ie,
struct ieee80211_sta_ht_cap *ht_cap);
-void ieee80211_ht_info_ie_to_ht_bss_info(
- struct ieee80211_ht_info *ht_add_info_ie,
- struct ieee80211_ht_bss_info *bss_info);
-u32 ieee80211_handle_ht(struct ieee80211_local *local,
- struct ieee80211_sta_ht_cap *req_ht_cap,
- struct ieee80211_ht_bss_info *req_bss_cap);
+u32 ieee80211_enable_ht(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_ht_info *hti,
+ u16 ap_ht_cap_flags);
void ieee80211_send_bar(struct ieee80211_sub_if_data *sdata, u8 *ra, u16 tid, u16 ssn);
void ieee80211_sta_stop_rx_ba_session(struct ieee80211_sub_if_data *sdata, u8 *da,
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index 9c5f5c3..39bc9c6 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -700,14 +700,15 @@
static void ieee80211_set_associated(struct ieee80211_sub_if_data *sdata,
- struct ieee80211_if_sta *ifsta)
+ struct ieee80211_if_sta *ifsta,
+ u32 bss_info_changed)
{
struct ieee80211_local *local = sdata->local;
struct ieee80211_conf *conf = &local_to_hw(local)->conf;
- u32 changed = BSS_CHANGED_ASSOC;
struct ieee80211_bss *bss;
+ bss_info_changed |= BSS_CHANGED_ASSOC;
ifsta->flags |= IEEE80211_STA_ASSOCIATED;
if (sdata->vif.type != NL80211_IFTYPE_STATION)
@@ -722,19 +723,12 @@
sdata->vif.bss_conf.timestamp = bss->timestamp;
sdata->vif.bss_conf.dtim_period = bss->dtim_period;
- changed |= ieee80211_handle_bss_capability(sdata,
+ bss_info_changed |= ieee80211_handle_bss_capability(sdata,
bss->capability, bss->has_erp_value, bss->erp_value);
ieee80211_rx_bss_put(local, bss);
}
- if (conf->flags & IEEE80211_CONF_SUPPORT_HT_MODE) {
- changed |= BSS_CHANGED_HT;
- sdata->vif.bss_conf.assoc_ht = 1;
- sdata->vif.bss_conf.ht_cap = &conf->ht_cap;
- sdata->vif.bss_conf.ht_bss_conf = &conf->ht_bss_conf;
- }
-
ifsta->flags |= IEEE80211_STA_PREV_BSSID_SET;
memcpy(ifsta->prev_bssid, sdata->u.sta.bssid, ETH_ALEN);
ieee80211_sta_send_associnfo(sdata, ifsta);
@@ -748,8 +742,8 @@
* when we have associated, we aren't checking whether it actually
* changed or not.
*/
- changed |= BSS_CHANGED_BASIC_RATES;
- ieee80211_bss_info_change_notify(sdata, changed);
+ bss_info_changed |= BSS_CHANGED_BASIC_RATES;
+ ieee80211_bss_info_change_notify(sdata, bss_info_changed);
netif_tx_start_all_queues(sdata->dev);
netif_carrier_on(sdata->dev);
@@ -813,7 +807,7 @@
{
struct ieee80211_local *local = sdata->local;
struct sta_info *sta;
- u32 changed = BSS_CHANGED_ASSOC;
+ u32 changed = 0;
rcu_read_lock();
@@ -847,15 +841,9 @@
ifsta->flags &= ~IEEE80211_STA_ASSOCIATED;
changed |= ieee80211_reset_erp_info(sdata);
- if (sdata->vif.bss_conf.assoc_ht)
- changed |= BSS_CHANGED_HT;
-
- sdata->vif.bss_conf.assoc_ht = 0;
- sdata->vif.bss_conf.ht_cap = NULL;
- sdata->vif.bss_conf.ht_bss_conf = NULL;
-
ieee80211_led_assoc(local, 0);
- sdata->vif.bss_conf.assoc = 0;
+ changed |= BSS_CHANGED_ASSOC;
+ sdata->vif.bss_conf.assoc = false;
ieee80211_sta_send_apinfo(sdata, ifsta);
@@ -867,6 +855,11 @@
rcu_read_unlock();
sta_info_destroy(sta);
+
+ local->hw.conf.ht.enabled = false;
+ ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_HT);
+
+ ieee80211_bss_info_change_notify(sdata, changed);
}
static int ieee80211_sta_wep_configured(struct ieee80211_sub_if_data *sdata)
@@ -1184,8 +1177,10 @@
struct ieee802_11_elems elems;
struct ieee80211_bss_conf *bss_conf = &sdata->vif.bss_conf;
u8 *pos;
+ u32 changed = 0;
int i, j;
bool have_higher_than_11mbit = false;
+ u16 ap_ht_cap_flags;
/* AssocResp and ReassocResp have identical structure, so process both
* of them in this function. */
@@ -1333,15 +1328,11 @@
else
sdata->flags &= ~IEEE80211_SDATA_OPERATING_GMODE;
- if (elems.ht_cap_elem && elems.ht_info_elem && elems.wmm_param &&
- (ifsta->flags & IEEE80211_STA_WMM_ENABLED)) {
- struct ieee80211_ht_bss_info bss_info;
- ieee80211_ht_cap_ie_to_sta_ht_cap(
+ if (elems.ht_cap_elem)
+ ieee80211_ht_cap_ie_to_sta_ht_cap(sband,
elems.ht_cap_elem, &sta->sta.ht_cap);
- ieee80211_ht_info_ie_to_ht_bss_info(
- elems.ht_info_elem, &bss_info);
- ieee80211_handle_ht(local, &sta->sta.ht_cap, &bss_info);
- }
+
+ ap_ht_cap_flags = sta->sta.ht_cap.cap;
rate_control_rate_init(sta);
@@ -1353,11 +1344,16 @@
} else
rcu_read_unlock();
+ if (elems.ht_info_elem && elems.wmm_param &&
+ (ifsta->flags & IEEE80211_STA_WMM_ENABLED))
+ changed |= ieee80211_enable_ht(sdata, elems.ht_info_elem,
+ ap_ht_cap_flags);
+
/* set AID and assoc capability,
* ieee80211_set_associated() will tell the driver */
bss_conf->aid = aid;
bss_conf->assoc_capability = capab_info;
- ieee80211_set_associated(sdata, ifsta);
+ ieee80211_set_associated(sdata, ifsta, changed);
ieee80211_associated(sdata, ifsta);
}
@@ -1657,7 +1653,6 @@
size_t baselen;
struct ieee802_11_elems elems;
struct ieee80211_local *local = sdata->local;
- struct ieee80211_conf *conf = &local->hw.conf;
u32 changed = 0;
bool erp_valid;
u8 erp_value = 0;
@@ -1693,14 +1688,31 @@
le16_to_cpu(mgmt->u.beacon.capab_info),
erp_valid, erp_value);
- if (elems.ht_cap_elem && elems.ht_info_elem &&
- elems.wmm_param && conf->flags & IEEE80211_CONF_SUPPORT_HT_MODE) {
- struct ieee80211_ht_bss_info bss_info;
- ieee80211_ht_info_ie_to_ht_bss_info(
- elems.ht_info_elem, &bss_info);
- changed |= ieee80211_handle_ht(local, &conf->ht_cap,
- &bss_info);
+ if (elems.ht_cap_elem && elems.ht_info_elem && elems.wmm_param) {
+ struct sta_info *sta;
+ struct ieee80211_supported_band *sband;
+ u16 ap_ht_cap_flags;
+
+ rcu_read_lock();
+
+ sta = sta_info_get(local, ifsta->bssid);
+ if (!sta) {
+ rcu_read_unlock();
+ return;
+ }
+
+ sband = local->hw.wiphy->bands[local->hw.conf.channel->band];
+
+ ieee80211_ht_cap_ie_to_sta_ht_cap(sband,
+ elems.ht_cap_elem, &sta->sta.ht_cap);
+
+ ap_ht_cap_flags = sta->sta.ht_cap.cap;
+
+ rcu_read_unlock();
+
+ changed |= ieee80211_enable_ht(sdata, elems.ht_info_elem,
+ ap_ht_cap_flags);
}
ieee80211_bss_info_change_notify(sdata, changed);