]> git.karo-electronics.de Git - linux-beck.git/commitdiff
mac80211: support secondary channel offset in CSA
authorJohannes Berg <johannes.berg@intel.com>
Mon, 25 Mar 2013 17:29:27 +0000 (18:29 +0100)
committerJohannes Berg <johannes.berg@intel.com>
Tue, 16 Apr 2013 13:29:44 +0000 (15:29 +0200)
Add support for the secondary channel offset IE in channel
switch announcements. This is necessary for proper handling
of CSA on HT access points.

For this to work it is also necessary to convert everything
here to use chandef structs instead of just channels. The
driver updates aren't really correct though. In particular,
the TI wl18xx driver update can't possibly be right since
it just ignores the new channel width for lack of firmware
API.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
13 files changed:
drivers/net/wireless/iwlegacy/4965-mac.c
drivers/net/wireless/iwlegacy/4965.c
drivers/net/wireless/iwlwifi/dvm/devices.c
drivers/net/wireless/iwlwifi/dvm/mac80211.c
drivers/net/wireless/iwlwifi/dvm/rxon.c
drivers/net/wireless/ti/wl12xx/cmd.c
drivers/net/wireless/ti/wl18xx/cmd.c
include/linux/ieee80211.h
include/net/mac80211.h
net/mac80211/ieee80211_i.h
net/mac80211/mlme.c
net/mac80211/trace.h
net/mac80211/util.c

index c092fcbbe965260b3a03eaf40e80068977d3b015..cb5882ea5f3afd92e081f27386d84fbfc2b5a0ca 100644 (file)
@@ -6057,7 +6057,7 @@ il4965_mac_channel_switch(struct ieee80211_hw *hw,
        struct il_priv *il = hw->priv;
        const struct il_channel_info *ch_info;
        struct ieee80211_conf *conf = &hw->conf;
-       struct ieee80211_channel *channel = ch_switch->channel;
+       struct ieee80211_channel *channel = ch_switch->chandef.chan;
        struct il_ht_config *ht_conf = &il->current_ht_config;
        u16 ch;
 
@@ -6094,23 +6094,21 @@ il4965_mac_channel_switch(struct ieee80211_hw *hw,
        il->current_ht_config.smps = conf->smps_mode;
 
        /* Configure HT40 channels */
-       il->ht.enabled = conf_is_ht(conf);
-       if (il->ht.enabled) {
-               if (conf_is_ht40_minus(conf)) {
-                       il->ht.extension_chan_offset =
-                           IEEE80211_HT_PARAM_CHA_SEC_BELOW;
-                       il->ht.is_40mhz = true;
-               } else if (conf_is_ht40_plus(conf)) {
-                       il->ht.extension_chan_offset =
-                           IEEE80211_HT_PARAM_CHA_SEC_ABOVE;
-                       il->ht.is_40mhz = true;
-               } else {
-                       il->ht.extension_chan_offset =
-                           IEEE80211_HT_PARAM_CHA_SEC_NONE;
-                       il->ht.is_40mhz = false;
-               }
-       } else
+       switch (cfg80211_get_chandef_type(&ch_switch->chandef)) {
+       case NL80211_CHAN_NO_HT:
+       case NL80211_CHAN_HT20:
                il->ht.is_40mhz = false;
+               il->ht.extension_chan_offset = IEEE80211_HT_PARAM_CHA_SEC_NONE;
+               break;
+       case NL80211_CHAN_HT40MINUS:
+               il->ht.extension_chan_offset = IEEE80211_HT_PARAM_CHA_SEC_BELOW;
+               il->ht.is_40mhz = true;
+               break;
+       case NL80211_CHAN_HT40PLUS:
+               il->ht.extension_chan_offset = IEEE80211_HT_PARAM_CHA_SEC_ABOVE;
+               il->ht.is_40mhz = true;
+               break;
+       }
 
        if ((le16_to_cpu(il->staging.channel) != ch))
                il->staging.flags = 0;
index 91eb2d07fdb82aff24b490dc106c10b60f6be6ee..777a578294bdc3ff35712b1a83261de7c46c24ce 100644 (file)
@@ -1493,7 +1493,7 @@ il4965_hw_channel_switch(struct il_priv *il,
 
        cmd.band = band;
        cmd.expect_beacon = 0;
-       ch = ch_switch->channel->hw_value;
+       ch = ch_switch->chandef.chan->hw_value;
        cmd.channel = cpu_to_le16(ch);
        cmd.rxon_flags = il->staging.flags;
        cmd.rxon_filter_flags = il->staging.filter_flags;
index 15cca2ef9294f6c87f3249df485a90ad97f16aa7..c48907c8ab4336db00b1719e86b40d5051f33425 100644 (file)
@@ -379,7 +379,7 @@ static int iwl5000_hw_channel_switch(struct iwl_priv *priv,
        };
 
        cmd.band = priv->band == IEEE80211_BAND_2GHZ;
-       ch = ch_switch->channel->hw_value;
+       ch = ch_switch->chandef.chan->hw_value;
        IWL_DEBUG_11H(priv, "channel switch from %d to %d\n",
                      ctx->active.channel, ch);
        cmd.channel = cpu_to_le16(ch);
@@ -414,7 +414,8 @@ static int iwl5000_hw_channel_switch(struct iwl_priv *priv,
        }
        IWL_DEBUG_11H(priv, "uCode time for the switch is 0x%x\n",
                      cmd.switch_time);
-       cmd.expect_beacon = ch_switch->channel->flags & IEEE80211_CHAN_RADAR;
+       cmd.expect_beacon =
+               ch_switch->chandef.chan->flags & IEEE80211_CHAN_RADAR;
 
        return iwl_dvm_send_cmd(priv, &hcmd);
 }
@@ -540,7 +541,7 @@ static int iwl6000_hw_channel_switch(struct iwl_priv *priv,
        hcmd.data[0] = cmd;
 
        cmd->band = priv->band == IEEE80211_BAND_2GHZ;
-       ch = ch_switch->channel->hw_value;
+       ch = ch_switch->chandef.chan->hw_value;
        IWL_DEBUG_11H(priv, "channel switch from %u to %u\n",
                      ctx->active.channel, ch);
        cmd->channel = cpu_to_le16(ch);
@@ -575,7 +576,8 @@ static int iwl6000_hw_channel_switch(struct iwl_priv *priv,
        }
        IWL_DEBUG_11H(priv, "uCode time for the switch is 0x%x\n",
                      cmd->switch_time);
-       cmd->expect_beacon = ch_switch->channel->flags & IEEE80211_CHAN_RADAR;
+       cmd->expect_beacon =
+               ch_switch->chandef.chan->flags & IEEE80211_CHAN_RADAR;
 
        err = iwl_dvm_send_cmd(priv, &hcmd);
        kfree(cmd);
index a7294fa4d7e54851566c6560ffd7d825927b8f25..2dc101fe0d24f48123037e683313b4ee8f452de0 100644 (file)
@@ -967,7 +967,7 @@ static void iwlagn_mac_channel_switch(struct ieee80211_hw *hw,
 {
        struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw);
        struct ieee80211_conf *conf = &hw->conf;
-       struct ieee80211_channel *channel = ch_switch->channel;
+       struct ieee80211_channel *channel = ch_switch->chandef.chan;
        struct iwl_ht_config *ht_conf = &priv->current_ht_config;
        /*
         * MULTI-FIXME
@@ -1005,11 +1005,21 @@ static void iwlagn_mac_channel_switch(struct ieee80211_hw *hw,
        priv->current_ht_config.smps = conf->smps_mode;
 
        /* Configure HT40 channels */
-       ctx->ht.enabled = conf_is_ht(conf);
-       if (ctx->ht.enabled)
-               iwlagn_config_ht40(conf, ctx);
-       else
+       switch (cfg80211_get_chandef_type(&ch_switch->chandef)) {
+       case NL80211_CHAN_NO_HT:
+       case NL80211_CHAN_HT20:
                ctx->ht.is_40mhz = false;
+               ctx->ht.extension_chan_offset = IEEE80211_HT_PARAM_CHA_SEC_NONE;
+               break;
+       case NL80211_CHAN_HT40MINUS:
+               ctx->ht.extension_chan_offset = IEEE80211_HT_PARAM_CHA_SEC_BELOW;
+               ctx->ht.is_40mhz = true;
+               break;
+       case NL80211_CHAN_HT40PLUS:
+               ctx->ht.extension_chan_offset = IEEE80211_HT_PARAM_CHA_SEC_ABOVE;
+               ctx->ht.is_40mhz = true;
+               break;
+       }
 
        if ((le16_to_cpu(ctx->staging.channel) != ch))
                ctx->staging.flags = 0;
index 085c589e71499bbd137e9aeec14a7230e03f2f3c..acbb50b5f1e8c36202aea3ebf327101b301f2980 100644 (file)
@@ -1160,7 +1160,7 @@ int iwlagn_commit_rxon(struct iwl_priv *priv, struct iwl_rxon_context *ctx)
 }
 
 void iwlagn_config_ht40(struct ieee80211_conf *conf,
-       struct iwl_rxon_context *ctx)
+                       struct iwl_rxon_context *ctx)
 {
        if (conf_is_ht40_minus(conf)) {
                ctx->ht.extension_chan_offset =
index 7dc9f965037d4189467c053f0c81f35b71a33166..7485dbae8c4bf5448c2489fd72ecb69664404be5 100644 (file)
@@ -301,7 +301,7 @@ int wl12xx_cmd_channel_switch(struct wl1271 *wl,
        }
 
        cmd->role_id = wlvif->role_id;
-       cmd->channel = ch_switch->channel->hw_value;
+       cmd->channel = ch_switch->chandef.chan->hw_value;
        cmd->switch_time = ch_switch->count;
        cmd->stop_tx = ch_switch->block_tx;
 
index 1d1f6cc7a50aa7a298546058ff3efffc87e301a8..7649c75cd68dfd9d189fc724a57522fbad2b9bce 100644 (file)
@@ -42,11 +42,11 @@ int wl18xx_cmd_channel_switch(struct wl1271 *wl,
        }
 
        cmd->role_id = wlvif->role_id;
-       cmd->channel = ch_switch->channel->hw_value;
+       cmd->channel = ch_switch->chandef.chan->hw_value;
        cmd->switch_time = ch_switch->count;
        cmd->stop_tx = ch_switch->block_tx;
 
-       switch (ch_switch->channel->band) {
+       switch (ch_switch->chandef.chan->band) {
        case IEEE80211_BAND_2GHZ:
                cmd->band = WLCORE_BAND_2_4GHZ;
                break;
@@ -55,7 +55,7 @@ int wl18xx_cmd_channel_switch(struct wl1271 *wl,
                break;
        default:
                wl1271_error("invalid channel switch band: %d",
-                            ch_switch->channel->band);
+                            ch_switch->chandef.chan->band);
                ret = -EINVAL;
                goto out_free;
        }
index 2a10acc65a54c7227917558ca2522fa691489756..95621528436c4a3ce766f6652ae9f1160238a41f 100644 (file)
@@ -684,6 +684,16 @@ struct ieee80211_ext_chansw_ie {
        u8 count;
 } __packed;
 
+/**
+ * struct ieee80211_sec_chan_offs_ie - secondary channel offset IE
+ * @sec_chan_offs: secondary channel offset, uses IEEE80211_HT_PARAM_CHA_SEC_*
+ *     values here
+ * This structure represents the "Secondary Channel Offset element"
+ */
+struct ieee80211_sec_chan_offs_ie {
+       u8 sec_chan_offs;
+} __packed;
+
 /**
  * struct ieee80211_tim
  *
@@ -1648,6 +1658,7 @@ enum ieee80211_eid {
 
        WLAN_EID_HT_CAPABILITY = 45,
        WLAN_EID_HT_OPERATION = 61,
+       WLAN_EID_SECONDARY_CHANNEL_OFFSET = 62,
 
        WLAN_EID_RSN = 48,
        WLAN_EID_MMIE = 76,
index 0dde213dd3b6c7208a5331897f2a463a8d9a0c9a..9ff10b33b71142b7587db30f182ee9dabe6286e0 100644 (file)
@@ -1017,13 +1017,13 @@ struct ieee80211_conf {
  *     the driver passed into mac80211.
  * @block_tx: Indicates whether transmission must be blocked before the
  *     scheduled channel switch, as indicated by the AP.
- * @channel: the new channel to switch to
+ * @chandef: the new channel to switch to
  * @count: the number of TBTT's until the channel switch event
  */
 struct ieee80211_channel_switch {
        u64 timestamp;
        bool block_tx;
-       struct ieee80211_channel *channel;
+       struct cfg80211_chan_def chandef;
        u8 count;
 };
 
index 10c3180b165e070c7b8786ccfe597f6a8aad0788..8f240c0ec304b3d4a3b30272bc6bfc85100c932b 100644 (file)
@@ -1019,7 +1019,7 @@ struct ieee80211_local {
        enum mac80211_scan_state next_scan_state;
        struct delayed_work scan_work;
        struct ieee80211_sub_if_data __rcu *scan_sdata;
-       struct ieee80211_channel *csa_channel;
+       struct cfg80211_chan_def csa_chandef;
        /* For backward compatibility only -- do not use */
        struct cfg80211_chan_def _oper_chandef;
 
@@ -1183,6 +1183,7 @@ struct ieee802_11_elems {
        const u8 *pwr_constr_elem;
        const struct ieee80211_timeout_interval_ie *timeout_int;
        const u8 *opmode_notif;
+       const struct ieee80211_sec_chan_offs_ie *sec_chan_offs;
 
        /* length of them, respectively */
        u8 ssid_len;
index bc6f87edc624b2cb30e2167f0abe1d0c068f7cea..bd581a80e4b7919e3ad7dde1dbb8d1a92cfc60ec 100644 (file)
@@ -289,6 +289,8 @@ ieee80211_determine_chantype(struct ieee80211_sub_if_data *sdata,
        } else {
                /* 40 MHz (and 80 MHz) must be supported for VHT */
                ret = IEEE80211_STA_DISABLE_VHT;
+               /* also mark 40 MHz disabled */
+               ret |= IEEE80211_STA_DISABLE_40MHZ;
                goto out;
        }
 
@@ -964,16 +966,7 @@ static void ieee80211_chswitch_work(struct work_struct *work)
        if (!ifmgd->associated)
                goto out;
 
-       /*
-        * FIXME: Here we are downgrading to NL80211_CHAN_WIDTH_20_NOHT
-        * and don't adjust our ht/vht settings
-        * This is wrong - we should behave according to the CSA params
-        */
-       local->_oper_chandef.chan = local->csa_channel;
-       local->_oper_chandef.width = NL80211_CHAN_WIDTH_20_NOHT;
-       local->_oper_chandef.center_freq1 =
-               local->_oper_chandef.chan->center_freq;
-       local->_oper_chandef.center_freq2 = 0;
+       local->_oper_chandef = local->csa_chandef;
 
        if (!local->ops->channel_switch) {
                /* call "hw_config" only if doing sw channel switch */
@@ -1028,13 +1021,14 @@ ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,
        struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
        struct cfg80211_bss *cbss = ifmgd->associated;
        struct ieee80211_bss *bss;
-       struct ieee80211_channel *new_ch;
        struct ieee80211_chanctx *chanctx;
        enum ieee80211_band new_band;
        int new_freq;
        u8 new_chan_no;
        u8 count;
        u8 mode;
+       struct cfg80211_chan_def new_chandef = {};
+       int secondary_channel_offset = -1;
 
        ASSERT_MGD_MTX(ifmgd);
 
@@ -1048,6 +1042,19 @@ ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,
        if (ifmgd->flags & IEEE80211_STA_CSA_RECEIVED)
                return;
 
+       if (!(ifmgd->flags & IEEE80211_STA_DISABLE_HT)) {
+               /* if HT is enabled and the IE not present, it's still HT */
+               secondary_channel_offset = IEEE80211_HT_PARAM_CHA_SEC_NONE;
+               if (elems->sec_chan_offs)
+                       secondary_channel_offset =
+                               elems->sec_chan_offs->sec_chan_offs;
+       }
+
+       if (ifmgd->flags & IEEE80211_STA_DISABLE_40MHZ &&
+           (secondary_channel_offset == IEEE80211_HT_PARAM_CHA_SEC_ABOVE ||
+            secondary_channel_offset == IEEE80211_HT_PARAM_CHA_SEC_BELOW))
+               secondary_channel_offset = IEEE80211_HT_PARAM_CHA_SEC_NONE;
+
        if (elems->ext_chansw_ie) {
                if (!ieee80211_operating_class_to_band(
                                elems->ext_chansw_ie->new_operating_class,
@@ -1074,8 +1081,9 @@ ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,
        bss = (void *)cbss->priv;
 
        new_freq = ieee80211_channel_to_frequency(new_chan_no, new_band);
-       new_ch = ieee80211_get_channel(local->hw.wiphy, new_freq);
-       if (!new_ch || new_ch->flags & IEEE80211_CHAN_DISABLED) {
+       new_chandef.chan = ieee80211_get_channel(sdata->local->hw.wiphy, new_freq);
+       if (!new_chandef.chan ||
+           new_chandef.chan->flags & IEEE80211_CHAN_DISABLED) {
                sdata_info(sdata,
                           "AP %pM switches to unsupported channel (%d MHz), disconnecting\n",
                           ifmgd->associated->bssid, new_freq);
@@ -1084,6 +1092,39 @@ ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,
                return;
        }
 
+       switch (secondary_channel_offset) {
+       default:
+               /* secondary_channel_offset was present but is invalid */
+       case IEEE80211_HT_PARAM_CHA_SEC_NONE:
+               cfg80211_chandef_create(&new_chandef, new_chandef.chan,
+                                       NL80211_CHAN_HT20);
+               break;
+       case IEEE80211_HT_PARAM_CHA_SEC_ABOVE:
+               cfg80211_chandef_create(&new_chandef, new_chandef.chan,
+                                       NL80211_CHAN_HT40PLUS);
+               break;
+       case IEEE80211_HT_PARAM_CHA_SEC_BELOW:
+               cfg80211_chandef_create(&new_chandef, new_chandef.chan,
+                                       NL80211_CHAN_HT40MINUS);
+               break;
+       case -1:
+               cfg80211_chandef_create(&new_chandef, new_chandef.chan,
+                                       NL80211_CHAN_NO_HT);
+               break;
+       }
+
+       if (!cfg80211_chandef_usable(local->hw.wiphy, &new_chandef,
+                                    IEEE80211_CHAN_DISABLED)) {
+               sdata_info(sdata,
+                          "AP %pM switches to unsupported channel (%d MHz, width:%d, CF1/2: %d/%d MHz), disconnecting\n",
+                          ifmgd->associated->bssid, new_freq,
+                          new_chandef.width, new_chandef.center_freq1,
+                          new_chandef.center_freq2);
+               ieee80211_queue_work(&local->hw,
+                                    &ifmgd->csa_connection_drop_work);
+               return;
+       }
+
        ifmgd->flags |= IEEE80211_STA_CSA_RECEIVED;
 
        if (local->use_chanctx) {
@@ -1111,7 +1152,7 @@ ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,
        }
        mutex_unlock(&local->chanctx_mtx);
 
-       local->csa_channel = new_ch;
+       local->csa_chandef = new_chandef;
 
        if (mode)
                ieee80211_stop_queues_by_reason(&local->hw,
@@ -1123,7 +1164,7 @@ ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,
                struct ieee80211_channel_switch ch_switch = {
                        .timestamp = timestamp,
                        .block_tx = mode,
-                       .channel = new_ch,
+                       .chandef = new_chandef,
                        .count = count,
                };
 
index 8286dcef228b492fd4491b7ea416701d243459ae..c215fafd7a2fc1ac3e206b4551c60de8d656ab24 100644 (file)
@@ -990,23 +990,23 @@ TRACE_EVENT(drv_channel_switch,
 
        TP_STRUCT__entry(
                LOCAL_ENTRY
+               CHANDEF_ENTRY
                __field(u64, timestamp)
                __field(bool, block_tx)
-               __field(u16, freq)
                __field(u8, count)
        ),
 
        TP_fast_assign(
                LOCAL_ASSIGN;
+               CHANDEF_ASSIGN(&ch_switch->chandef)
                __entry->timestamp = ch_switch->timestamp;
                __entry->block_tx = ch_switch->block_tx;
-               __entry->freq = ch_switch->channel->center_freq;
                __entry->count = ch_switch->count;
        ),
 
        TP_printk(
-               LOCAL_PR_FMT " new freq:%u count:%d",
-               LOCAL_PR_ARG, __entry->freq, __entry->count
+               LOCAL_PR_FMT " new " CHANDEF_PR_FMT " count:%d",
+               LOCAL_PR_ARG, CHANDEF_PR_ARG, __entry->count
        )
 );
 
index e4a6d559372df4d437beba25a46584955ff9b1f3..155056c90edf6d892deb1af596204d2d77aa46ae 100644 (file)
@@ -716,6 +716,7 @@ u32 ieee802_11_parse_elems_crc(u8 *start, size_t len,
                case WLAN_EID_COUNTRY:
                case WLAN_EID_PWR_CONSTRAINT:
                case WLAN_EID_TIMEOUT_INTERVAL:
+               case WLAN_EID_SECONDARY_CHANNEL_OFFSET:
                        if (test_bit(id, seen_elems)) {
                                elems->parse_error = true;
                                left -= elen;
@@ -870,6 +871,13 @@ u32 ieee802_11_parse_elems_crc(u8 *start, size_t len,
                        }
                        elems->ext_chansw_ie = (void *)pos;
                        break;
+               case WLAN_EID_SECONDARY_CHANNEL_OFFSET:
+                       if (elen != sizeof(struct ieee80211_sec_chan_offs_ie)) {
+                               elem_parse_failed = true;
+                               break;
+                       }
+                       elems->sec_chan_offs = (void *)pos;
+                       break;
                case WLAN_EID_COUNTRY:
                        elems->country_elem = pos;
                        elems->country_elem_len = elen;