Commit b4f286a1 authored by Johannes Berg's avatar Johannes Berg

mac80211: support extended channel switch

Support extended channel switch when the operating
class is one of the global operating classes as
defined in Annex E of 802.11-2012. If it isn't,
disconnect from the AP instead.
Signed-off-by: default avatarJohannes Berg <johannes.berg@intel.com>
parent 1ce3e82b
...@@ -672,6 +672,18 @@ struct ieee80211_channel_sw_ie { ...@@ -672,6 +672,18 @@ struct ieee80211_channel_sw_ie {
u8 count; u8 count;
} __packed; } __packed;
/**
* struct ieee80211_ext_chansw_ie
*
* This structure represents the "Extended Channel Switch Announcement element"
*/
struct ieee80211_ext_chansw_ie {
u8 mode;
u8 new_operating_class;
u8 new_ch_num;
u8 count;
} __packed;
/** /**
* struct ieee80211_tim * struct ieee80211_tim
* *
......
...@@ -1178,6 +1178,7 @@ struct ieee802_11_elems { ...@@ -1178,6 +1178,7 @@ struct ieee802_11_elems {
const u8 *perr; const u8 *perr;
const struct ieee80211_rann_ie *rann; const struct ieee80211_rann_ie *rann;
const struct ieee80211_channel_sw_ie *ch_switch_ie; const struct ieee80211_channel_sw_ie *ch_switch_ie;
const struct ieee80211_ext_chansw_ie *ext_chansw_ie;
const u8 *country_elem; const u8 *country_elem;
const u8 *pwr_constr_elem; const u8 *pwr_constr_elem;
const struct ieee80211_timeout_interval_ie *timeout_int; const struct ieee80211_timeout_interval_ie *timeout_int;
......
...@@ -1024,56 +1024,79 @@ static void ...@@ -1024,56 +1024,79 @@ static void
ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata, ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,
u64 timestamp, struct ieee802_11_elems *elems) u64 timestamp, struct ieee802_11_elems *elems)
{ {
struct ieee80211_local *local = sdata->local;
struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
struct cfg80211_bss *cbss = ifmgd->associated; struct cfg80211_bss *cbss = ifmgd->associated;
struct ieee80211_bss *bss; struct ieee80211_bss *bss;
struct ieee80211_channel *new_ch; struct ieee80211_channel *new_ch;
int new_freq;
struct ieee80211_chanctx *chanctx; struct ieee80211_chanctx *chanctx;
enum ieee80211_band new_band;
int new_freq;
u8 new_chan_no;
u8 count;
u8 mode;
ASSERT_MGD_MTX(ifmgd); ASSERT_MGD_MTX(ifmgd);
if (!cbss) if (!cbss)
return; return;
if (sdata->local->scanning) if (local->scanning)
return; return;
/* disregard subsequent announcements if we are already processing */ /* disregard subsequent announcements if we are already processing */
if (ifmgd->flags & IEEE80211_STA_CSA_RECEIVED) if (ifmgd->flags & IEEE80211_STA_CSA_RECEIVED)
return; return;
if (!elems->ch_switch_ie) if (elems->ext_chansw_ie) {
if (!ieee80211_operating_class_to_band(
elems->ext_chansw_ie->new_operating_class,
&new_band)) {
sdata_info(sdata,
"cannot understand ECSA IE operating class %d, disconnecting\n",
elems->ext_chansw_ie->new_operating_class);
ieee80211_queue_work(&local->hw,
&ifmgd->csa_connection_drop_work);
}
new_chan_no = elems->ext_chansw_ie->new_ch_num;
count = elems->ext_chansw_ie->count;
mode = elems->ext_chansw_ie->mode;
} else if (elems->ch_switch_ie) {
new_band = cbss->channel->band;
new_chan_no = elems->ch_switch_ie->new_ch_num;
count = elems->ch_switch_ie->count;
mode = elems->ch_switch_ie->mode;
} else {
/* nothing here we understand */
return; return;
}
bss = (void *)cbss->priv; bss = (void *)cbss->priv;
new_freq = ieee80211_channel_to_frequency( new_freq = ieee80211_channel_to_frequency(new_chan_no, new_band);
elems->ch_switch_ie->new_ch_num, new_ch = ieee80211_get_channel(local->hw.wiphy, new_freq);
cbss->channel->band);
new_ch = ieee80211_get_channel(sdata->local->hw.wiphy, new_freq);
if (!new_ch || new_ch->flags & IEEE80211_CHAN_DISABLED) { if (!new_ch || new_ch->flags & IEEE80211_CHAN_DISABLED) {
sdata_info(sdata, sdata_info(sdata,
"AP %pM switches to unsupported channel (%d MHz), disconnecting\n", "AP %pM switches to unsupported channel (%d MHz), disconnecting\n",
ifmgd->associated->bssid, new_freq); ifmgd->associated->bssid, new_freq);
ieee80211_queue_work(&sdata->local->hw, ieee80211_queue_work(&local->hw,
&ifmgd->csa_connection_drop_work); &ifmgd->csa_connection_drop_work);
return; return;
} }
ifmgd->flags |= IEEE80211_STA_CSA_RECEIVED; ifmgd->flags |= IEEE80211_STA_CSA_RECEIVED;
if (sdata->local->use_chanctx) { if (local->use_chanctx) {
sdata_info(sdata, sdata_info(sdata,
"not handling channel switch with channel contexts\n"); "not handling channel switch with channel contexts\n");
ieee80211_queue_work(&sdata->local->hw, ieee80211_queue_work(&local->hw,
&ifmgd->csa_connection_drop_work); &ifmgd->csa_connection_drop_work);
return; return;
} }
mutex_lock(&sdata->local->chanctx_mtx); mutex_lock(&local->chanctx_mtx);
if (WARN_ON(!rcu_access_pointer(sdata->vif.chanctx_conf))) { if (WARN_ON(!rcu_access_pointer(sdata->vif.chanctx_conf))) {
mutex_unlock(&sdata->local->chanctx_mtx); mutex_unlock(&local->chanctx_mtx);
return; return;
} }
chanctx = container_of(rcu_access_pointer(sdata->vif.chanctx_conf), chanctx = container_of(rcu_access_pointer(sdata->vif.chanctx_conf),
...@@ -1081,40 +1104,39 @@ ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata, ...@@ -1081,40 +1104,39 @@ ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,
if (chanctx->refcount > 1) { if (chanctx->refcount > 1) {
sdata_info(sdata, sdata_info(sdata,
"channel switch with multiple interfaces on the same channel, disconnecting\n"); "channel switch with multiple interfaces on the same channel, disconnecting\n");
ieee80211_queue_work(&sdata->local->hw, ieee80211_queue_work(&local->hw,
&ifmgd->csa_connection_drop_work); &ifmgd->csa_connection_drop_work);
mutex_unlock(&sdata->local->chanctx_mtx); mutex_unlock(&local->chanctx_mtx);
return; return;
} }
mutex_unlock(&sdata->local->chanctx_mtx); mutex_unlock(&local->chanctx_mtx);
sdata->local->csa_channel = new_ch; local->csa_channel = new_ch;
if (elems->ch_switch_ie->mode) if (mode)
ieee80211_stop_queues_by_reason(&sdata->local->hw, ieee80211_stop_queues_by_reason(&local->hw,
IEEE80211_MAX_QUEUE_MAP, IEEE80211_MAX_QUEUE_MAP,
IEEE80211_QUEUE_STOP_REASON_CSA); IEEE80211_QUEUE_STOP_REASON_CSA);
if (sdata->local->ops->channel_switch) { if (local->ops->channel_switch) {
/* use driver's channel switch callback */ /* use driver's channel switch callback */
struct ieee80211_channel_switch ch_switch = { struct ieee80211_channel_switch ch_switch = {
.timestamp = timestamp, .timestamp = timestamp,
.block_tx = elems->ch_switch_ie->mode, .block_tx = mode,
.channel = new_ch, .channel = new_ch,
.count = elems->ch_switch_ie->count, .count = count,
}; };
drv_channel_switch(sdata->local, &ch_switch); drv_channel_switch(local, &ch_switch);
return; return;
} }
/* channel switch handled in software */ /* channel switch handled in software */
if (elems->ch_switch_ie->count <= 1) if (count <= 1)
ieee80211_queue_work(&sdata->local->hw, &ifmgd->chswitch_work); ieee80211_queue_work(&local->hw, &ifmgd->chswitch_work);
else else
mod_timer(&ifmgd->chswitch_timer, mod_timer(&ifmgd->chswitch_timer,
TU_TO_EXP_TIME(elems->ch_switch_ie->count * TU_TO_EXP_TIME(count * cbss->beacon_interval));
cbss->beacon_interval));
} }
static u32 ieee80211_handle_pwr_constr(struct ieee80211_sub_if_data *sdata, static u32 ieee80211_handle_pwr_constr(struct ieee80211_sub_if_data *sdata,
...@@ -2629,6 +2651,8 @@ static void ieee80211_rx_bss_info(struct ieee80211_sub_if_data *sdata, ...@@ -2629,6 +2651,8 @@ static void ieee80211_rx_bss_info(struct ieee80211_sub_if_data *sdata,
struct ieee80211_channel *channel; struct ieee80211_channel *channel;
bool need_ps = false; bool need_ps = false;
lockdep_assert_held(&sdata->u.mgd.mtx);
if ((sdata->u.mgd.associated && if ((sdata->u.mgd.associated &&
ether_addr_equal(mgmt->bssid, sdata->u.mgd.associated->bssid)) || ether_addr_equal(mgmt->bssid, sdata->u.mgd.associated->bssid)) ||
(sdata->u.mgd.assoc_data && (sdata->u.mgd.assoc_data &&
...@@ -2670,6 +2694,7 @@ static void ieee80211_rx_bss_info(struct ieee80211_sub_if_data *sdata, ...@@ -2670,6 +2694,7 @@ static void ieee80211_rx_bss_info(struct ieee80211_sub_if_data *sdata,
} }
ieee80211_sta_process_chanswitch(sdata, rx_status->mactime, elems); ieee80211_sta_process_chanswitch(sdata, rx_status->mactime, elems);
} }
......
...@@ -863,6 +863,13 @@ u32 ieee802_11_parse_elems_crc(u8 *start, size_t len, ...@@ -863,6 +863,13 @@ u32 ieee802_11_parse_elems_crc(u8 *start, size_t len,
} }
elems->ch_switch_ie = (void *)pos; elems->ch_switch_ie = (void *)pos;
break; break;
case WLAN_EID_EXT_CHANSWITCH_ANN:
if (elen != sizeof(struct ieee80211_ext_chansw_ie)) {
elem_parse_failed = true;
break;
}
elems->ext_chansw_ie = (void *)pos;
break;
case WLAN_EID_COUNTRY: case WLAN_EID_COUNTRY:
elems->country_elem = pos; elems->country_elem = pos;
elems->country_elem_len = elen; elems->country_elem_len = elen;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment