Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

mac80211: support secondary channel offset in CSA

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>

+125 -54
+15 -17
drivers/net/wireless/iwlegacy/4965-mac.c
··· 6057 6057 struct il_priv *il = hw->priv; 6058 6058 const struct il_channel_info *ch_info; 6059 6059 struct ieee80211_conf *conf = &hw->conf; 6060 - struct ieee80211_channel *channel = ch_switch->channel; 6060 + struct ieee80211_channel *channel = ch_switch->chandef.chan; 6061 6061 struct il_ht_config *ht_conf = &il->current_ht_config; 6062 6062 u16 ch; 6063 6063 ··· 6094 6094 il->current_ht_config.smps = conf->smps_mode; 6095 6095 6096 6096 /* Configure HT40 channels */ 6097 - il->ht.enabled = conf_is_ht(conf); 6098 - if (il->ht.enabled) { 6099 - if (conf_is_ht40_minus(conf)) { 6100 - il->ht.extension_chan_offset = 6101 - IEEE80211_HT_PARAM_CHA_SEC_BELOW; 6102 - il->ht.is_40mhz = true; 6103 - } else if (conf_is_ht40_plus(conf)) { 6104 - il->ht.extension_chan_offset = 6105 - IEEE80211_HT_PARAM_CHA_SEC_ABOVE; 6106 - il->ht.is_40mhz = true; 6107 - } else { 6108 - il->ht.extension_chan_offset = 6109 - IEEE80211_HT_PARAM_CHA_SEC_NONE; 6110 - il->ht.is_40mhz = false; 6111 - } 6112 - } else 6097 + switch (cfg80211_get_chandef_type(&ch_switch->chandef)) { 6098 + case NL80211_CHAN_NO_HT: 6099 + case NL80211_CHAN_HT20: 6113 6100 il->ht.is_40mhz = false; 6101 + il->ht.extension_chan_offset = IEEE80211_HT_PARAM_CHA_SEC_NONE; 6102 + break; 6103 + case NL80211_CHAN_HT40MINUS: 6104 + il->ht.extension_chan_offset = IEEE80211_HT_PARAM_CHA_SEC_BELOW; 6105 + il->ht.is_40mhz = true; 6106 + break; 6107 + case NL80211_CHAN_HT40PLUS: 6108 + il->ht.extension_chan_offset = IEEE80211_HT_PARAM_CHA_SEC_ABOVE; 6109 + il->ht.is_40mhz = true; 6110 + break; 6111 + } 6114 6112 6115 6113 if ((le16_to_cpu(il->staging.channel) != ch)) 6116 6114 il->staging.flags = 0;
+1 -1
drivers/net/wireless/iwlegacy/4965.c
··· 1493 1493 1494 1494 cmd.band = band; 1495 1495 cmd.expect_beacon = 0; 1496 - ch = ch_switch->channel->hw_value; 1496 + ch = ch_switch->chandef.chan->hw_value; 1497 1497 cmd.channel = cpu_to_le16(ch); 1498 1498 cmd.rxon_flags = il->staging.flags; 1499 1499 cmd.rxon_filter_flags = il->staging.filter_flags;
+6 -4
drivers/net/wireless/iwlwifi/dvm/devices.c
··· 379 379 }; 380 380 381 381 cmd.band = priv->band == IEEE80211_BAND_2GHZ; 382 - ch = ch_switch->channel->hw_value; 382 + ch = ch_switch->chandef.chan->hw_value; 383 383 IWL_DEBUG_11H(priv, "channel switch from %d to %d\n", 384 384 ctx->active.channel, ch); 385 385 cmd.channel = cpu_to_le16(ch); ··· 414 414 } 415 415 IWL_DEBUG_11H(priv, "uCode time for the switch is 0x%x\n", 416 416 cmd.switch_time); 417 - cmd.expect_beacon = ch_switch->channel->flags & IEEE80211_CHAN_RADAR; 417 + cmd.expect_beacon = 418 + ch_switch->chandef.chan->flags & IEEE80211_CHAN_RADAR; 418 419 419 420 return iwl_dvm_send_cmd(priv, &hcmd); 420 421 } ··· 541 540 hcmd.data[0] = cmd; 542 541 543 542 cmd->band = priv->band == IEEE80211_BAND_2GHZ; 544 - ch = ch_switch->channel->hw_value; 543 + ch = ch_switch->chandef.chan->hw_value; 545 544 IWL_DEBUG_11H(priv, "channel switch from %u to %u\n", 546 545 ctx->active.channel, ch); 547 546 cmd->channel = cpu_to_le16(ch); ··· 576 575 } 577 576 IWL_DEBUG_11H(priv, "uCode time for the switch is 0x%x\n", 578 577 cmd->switch_time); 579 - cmd->expect_beacon = ch_switch->channel->flags & IEEE80211_CHAN_RADAR; 578 + cmd->expect_beacon = 579 + ch_switch->chandef.chan->flags & IEEE80211_CHAN_RADAR; 580 580 581 581 err = iwl_dvm_send_cmd(priv, &hcmd); 582 582 kfree(cmd);
+15 -5
drivers/net/wireless/iwlwifi/dvm/mac80211.c
··· 967 967 { 968 968 struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw); 969 969 struct ieee80211_conf *conf = &hw->conf; 970 - struct ieee80211_channel *channel = ch_switch->channel; 970 + struct ieee80211_channel *channel = ch_switch->chandef.chan; 971 971 struct iwl_ht_config *ht_conf = &priv->current_ht_config; 972 972 /* 973 973 * MULTI-FIXME ··· 1005 1005 priv->current_ht_config.smps = conf->smps_mode; 1006 1006 1007 1007 /* Configure HT40 channels */ 1008 - ctx->ht.enabled = conf_is_ht(conf); 1009 - if (ctx->ht.enabled) 1010 - iwlagn_config_ht40(conf, ctx); 1011 - else 1008 + switch (cfg80211_get_chandef_type(&ch_switch->chandef)) { 1009 + case NL80211_CHAN_NO_HT: 1010 + case NL80211_CHAN_HT20: 1012 1011 ctx->ht.is_40mhz = false; 1012 + ctx->ht.extension_chan_offset = IEEE80211_HT_PARAM_CHA_SEC_NONE; 1013 + break; 1014 + case NL80211_CHAN_HT40MINUS: 1015 + ctx->ht.extension_chan_offset = IEEE80211_HT_PARAM_CHA_SEC_BELOW; 1016 + ctx->ht.is_40mhz = true; 1017 + break; 1018 + case NL80211_CHAN_HT40PLUS: 1019 + ctx->ht.extension_chan_offset = IEEE80211_HT_PARAM_CHA_SEC_ABOVE; 1020 + ctx->ht.is_40mhz = true; 1021 + break; 1022 + } 1013 1023 1014 1024 if ((le16_to_cpu(ctx->staging.channel) != ch)) 1015 1025 ctx->staging.flags = 0;
+1 -1
drivers/net/wireless/iwlwifi/dvm/rxon.c
··· 1160 1160 } 1161 1161 1162 1162 void iwlagn_config_ht40(struct ieee80211_conf *conf, 1163 - struct iwl_rxon_context *ctx) 1163 + struct iwl_rxon_context *ctx) 1164 1164 { 1165 1165 if (conf_is_ht40_minus(conf)) { 1166 1166 ctx->ht.extension_chan_offset =
+1 -1
drivers/net/wireless/ti/wl12xx/cmd.c
··· 301 301 } 302 302 303 303 cmd->role_id = wlvif->role_id; 304 - cmd->channel = ch_switch->channel->hw_value; 304 + cmd->channel = ch_switch->chandef.chan->hw_value; 305 305 cmd->switch_time = ch_switch->count; 306 306 cmd->stop_tx = ch_switch->block_tx; 307 307
+3 -3
drivers/net/wireless/ti/wl18xx/cmd.c
··· 42 42 } 43 43 44 44 cmd->role_id = wlvif->role_id; 45 - cmd->channel = ch_switch->channel->hw_value; 45 + cmd->channel = ch_switch->chandef.chan->hw_value; 46 46 cmd->switch_time = ch_switch->count; 47 47 cmd->stop_tx = ch_switch->block_tx; 48 48 49 - switch (ch_switch->channel->band) { 49 + switch (ch_switch->chandef.chan->band) { 50 50 case IEEE80211_BAND_2GHZ: 51 51 cmd->band = WLCORE_BAND_2_4GHZ; 52 52 break; ··· 55 55 break; 56 56 default: 57 57 wl1271_error("invalid channel switch band: %d", 58 - ch_switch->channel->band); 58 + ch_switch->chandef.chan->band); 59 59 ret = -EINVAL; 60 60 goto out_free; 61 61 }
+11
include/linux/ieee80211.h
··· 685 685 } __packed; 686 686 687 687 /** 688 + * struct ieee80211_sec_chan_offs_ie - secondary channel offset IE 689 + * @sec_chan_offs: secondary channel offset, uses IEEE80211_HT_PARAM_CHA_SEC_* 690 + * values here 691 + * This structure represents the "Secondary Channel Offset element" 692 + */ 693 + struct ieee80211_sec_chan_offs_ie { 694 + u8 sec_chan_offs; 695 + } __packed; 696 + 697 + /** 688 698 * struct ieee80211_tim 689 699 * 690 700 * This structure refers to "Traffic Indication Map information element" ··· 1658 1648 1659 1649 WLAN_EID_HT_CAPABILITY = 45, 1660 1650 WLAN_EID_HT_OPERATION = 61, 1651 + WLAN_EID_SECONDARY_CHANNEL_OFFSET = 62, 1661 1652 1662 1653 WLAN_EID_RSN = 48, 1663 1654 WLAN_EID_MMIE = 76,
+2 -2
include/net/mac80211.h
··· 1017 1017 * the driver passed into mac80211. 1018 1018 * @block_tx: Indicates whether transmission must be blocked before the 1019 1019 * scheduled channel switch, as indicated by the AP. 1020 - * @channel: the new channel to switch to 1020 + * @chandef: the new channel to switch to 1021 1021 * @count: the number of TBTT's until the channel switch event 1022 1022 */ 1023 1023 struct ieee80211_channel_switch { 1024 1024 u64 timestamp; 1025 1025 bool block_tx; 1026 - struct ieee80211_channel *channel; 1026 + struct cfg80211_chan_def chandef; 1027 1027 u8 count; 1028 1028 }; 1029 1029
+2 -1
net/mac80211/ieee80211_i.h
··· 1019 1019 enum mac80211_scan_state next_scan_state; 1020 1020 struct delayed_work scan_work; 1021 1021 struct ieee80211_sub_if_data __rcu *scan_sdata; 1022 - struct ieee80211_channel *csa_channel; 1022 + struct cfg80211_chan_def csa_chandef; 1023 1023 /* For backward compatibility only -- do not use */ 1024 1024 struct cfg80211_chan_def _oper_chandef; 1025 1025 ··· 1183 1183 const u8 *pwr_constr_elem; 1184 1184 const struct ieee80211_timeout_interval_ie *timeout_int; 1185 1185 const u8 *opmode_notif; 1186 + const struct ieee80211_sec_chan_offs_ie *sec_chan_offs; 1186 1187 1187 1188 /* length of them, respectively */ 1188 1189 u8 ssid_len;
+56 -15
net/mac80211/mlme.c
··· 289 289 } else { 290 290 /* 40 MHz (and 80 MHz) must be supported for VHT */ 291 291 ret = IEEE80211_STA_DISABLE_VHT; 292 + /* also mark 40 MHz disabled */ 293 + ret |= IEEE80211_STA_DISABLE_40MHZ; 292 294 goto out; 293 295 } 294 296 ··· 966 964 if (!ifmgd->associated) 967 965 goto out; 968 966 969 - /* 970 - * FIXME: Here we are downgrading to NL80211_CHAN_WIDTH_20_NOHT 971 - * and don't adjust our ht/vht settings 972 - * This is wrong - we should behave according to the CSA params 973 - */ 974 - local->_oper_chandef.chan = local->csa_channel; 975 - local->_oper_chandef.width = NL80211_CHAN_WIDTH_20_NOHT; 976 - local->_oper_chandef.center_freq1 = 977 - local->_oper_chandef.chan->center_freq; 978 - local->_oper_chandef.center_freq2 = 0; 967 + local->_oper_chandef = local->csa_chandef; 979 968 980 969 if (!local->ops->channel_switch) { 981 970 /* call "hw_config" only if doing sw channel switch */ ··· 1021 1028 struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; 1022 1029 struct cfg80211_bss *cbss = ifmgd->associated; 1023 1030 struct ieee80211_bss *bss; 1024 - struct ieee80211_channel *new_ch; 1025 1031 struct ieee80211_chanctx *chanctx; 1026 1032 enum ieee80211_band new_band; 1027 1033 int new_freq; 1028 1034 u8 new_chan_no; 1029 1035 u8 count; 1030 1036 u8 mode; 1037 + struct cfg80211_chan_def new_chandef = {}; 1038 + int secondary_channel_offset = -1; 1031 1039 1032 1040 ASSERT_MGD_MTX(ifmgd); 1033 1041 ··· 1041 1047 /* disregard subsequent announcements if we are already processing */ 1042 1048 if (ifmgd->flags & IEEE80211_STA_CSA_RECEIVED) 1043 1049 return; 1050 + 1051 + if (!(ifmgd->flags & IEEE80211_STA_DISABLE_HT)) { 1052 + /* if HT is enabled and the IE not present, it's still HT */ 1053 + secondary_channel_offset = IEEE80211_HT_PARAM_CHA_SEC_NONE; 1054 + if (elems->sec_chan_offs) 1055 + secondary_channel_offset = 1056 + elems->sec_chan_offs->sec_chan_offs; 1057 + } 1058 + 1059 + if (ifmgd->flags & IEEE80211_STA_DISABLE_40MHZ && 1060 + (secondary_channel_offset == IEEE80211_HT_PARAM_CHA_SEC_ABOVE || 1061 + secondary_channel_offset == IEEE80211_HT_PARAM_CHA_SEC_BELOW)) 1062 + secondary_channel_offset = IEEE80211_HT_PARAM_CHA_SEC_NONE; 1044 1063 1045 1064 if (elems->ext_chansw_ie) { 1046 1065 if (!ieee80211_operating_class_to_band( ··· 1081 1074 bss = (void *)cbss->priv; 1082 1075 1083 1076 new_freq = ieee80211_channel_to_frequency(new_chan_no, new_band); 1084 - new_ch = ieee80211_get_channel(local->hw.wiphy, new_freq); 1085 - if (!new_ch || new_ch->flags & IEEE80211_CHAN_DISABLED) { 1077 + new_chandef.chan = ieee80211_get_channel(sdata->local->hw.wiphy, new_freq); 1078 + if (!new_chandef.chan || 1079 + new_chandef.chan->flags & IEEE80211_CHAN_DISABLED) { 1086 1080 sdata_info(sdata, 1087 1081 "AP %pM switches to unsupported channel (%d MHz), disconnecting\n", 1088 1082 ifmgd->associated->bssid, new_freq); 1083 + ieee80211_queue_work(&local->hw, 1084 + &ifmgd->csa_connection_drop_work); 1085 + return; 1086 + } 1087 + 1088 + switch (secondary_channel_offset) { 1089 + default: 1090 + /* secondary_channel_offset was present but is invalid */ 1091 + case IEEE80211_HT_PARAM_CHA_SEC_NONE: 1092 + cfg80211_chandef_create(&new_chandef, new_chandef.chan, 1093 + NL80211_CHAN_HT20); 1094 + break; 1095 + case IEEE80211_HT_PARAM_CHA_SEC_ABOVE: 1096 + cfg80211_chandef_create(&new_chandef, new_chandef.chan, 1097 + NL80211_CHAN_HT40PLUS); 1098 + break; 1099 + case IEEE80211_HT_PARAM_CHA_SEC_BELOW: 1100 + cfg80211_chandef_create(&new_chandef, new_chandef.chan, 1101 + NL80211_CHAN_HT40MINUS); 1102 + break; 1103 + case -1: 1104 + cfg80211_chandef_create(&new_chandef, new_chandef.chan, 1105 + NL80211_CHAN_NO_HT); 1106 + break; 1107 + } 1108 + 1109 + if (!cfg80211_chandef_usable(local->hw.wiphy, &new_chandef, 1110 + IEEE80211_CHAN_DISABLED)) { 1111 + sdata_info(sdata, 1112 + "AP %pM switches to unsupported channel (%d MHz, width:%d, CF1/2: %d/%d MHz), disconnecting\n", 1113 + ifmgd->associated->bssid, new_freq, 1114 + new_chandef.width, new_chandef.center_freq1, 1115 + new_chandef.center_freq2); 1089 1116 ieee80211_queue_work(&local->hw, 1090 1117 &ifmgd->csa_connection_drop_work); 1091 1118 return; ··· 1152 1111 } 1153 1112 mutex_unlock(&local->chanctx_mtx); 1154 1113 1155 - local->csa_channel = new_ch; 1114 + local->csa_chandef = new_chandef; 1156 1115 1157 1116 if (mode) 1158 1117 ieee80211_stop_queues_by_reason(&local->hw, ··· 1164 1123 struct ieee80211_channel_switch ch_switch = { 1165 1124 .timestamp = timestamp, 1166 1125 .block_tx = mode, 1167 - .channel = new_ch, 1126 + .chandef = new_chandef, 1168 1127 .count = count, 1169 1128 }; 1170 1129
+4 -4
net/mac80211/trace.h
··· 990 990 991 991 TP_STRUCT__entry( 992 992 LOCAL_ENTRY 993 + CHANDEF_ENTRY 993 994 __field(u64, timestamp) 994 995 __field(bool, block_tx) 995 - __field(u16, freq) 996 996 __field(u8, count) 997 997 ), 998 998 999 999 TP_fast_assign( 1000 1000 LOCAL_ASSIGN; 1001 + CHANDEF_ASSIGN(&ch_switch->chandef) 1001 1002 __entry->timestamp = ch_switch->timestamp; 1002 1003 __entry->block_tx = ch_switch->block_tx; 1003 - __entry->freq = ch_switch->channel->center_freq; 1004 1004 __entry->count = ch_switch->count; 1005 1005 ), 1006 1006 1007 1007 TP_printk( 1008 - LOCAL_PR_FMT " new freq:%u count:%d", 1009 - LOCAL_PR_ARG, __entry->freq, __entry->count 1008 + LOCAL_PR_FMT " new " CHANDEF_PR_FMT " count:%d", 1009 + LOCAL_PR_ARG, CHANDEF_PR_ARG, __entry->count 1010 1010 ) 1011 1011 ); 1012 1012
+8
net/mac80211/util.c
··· 716 716 case WLAN_EID_COUNTRY: 717 717 case WLAN_EID_PWR_CONSTRAINT: 718 718 case WLAN_EID_TIMEOUT_INTERVAL: 719 + case WLAN_EID_SECONDARY_CHANNEL_OFFSET: 719 720 if (test_bit(id, seen_elems)) { 720 721 elems->parse_error = true; 721 722 left -= elen; ··· 870 869 break; 871 870 } 872 871 elems->ext_chansw_ie = (void *)pos; 872 + break; 873 + case WLAN_EID_SECONDARY_CHANNEL_OFFSET: 874 + if (elen != sizeof(struct ieee80211_sec_chan_offs_ie)) { 875 + elem_parse_failed = true; 876 + break; 877 + } 878 + elems->sec_chan_offs = (void *)pos; 873 879 break; 874 880 case WLAN_EID_COUNTRY: 875 881 elems->country_elem = pos;