|
@@ -2988,28 +2988,26 @@ cfg80211_beacon_dup(struct cfg80211_beacon_data *beacon)
|
|
|
return new_beacon;
|
|
|
}
|
|
|
|
|
|
-void ieee80211_csa_finalize_work(struct work_struct *work)
|
|
|
+void ieee80211_csa_finish(struct ieee80211_vif *vif)
|
|
|
{
|
|
|
- struct ieee80211_sub_if_data *sdata =
|
|
|
- container_of(work, struct ieee80211_sub_if_data,
|
|
|
- csa_finalize_work);
|
|
|
- struct ieee80211_local *local = sdata->local;
|
|
|
- int err, changed = 0;
|
|
|
+ struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
|
|
|
|
|
|
- sdata_lock(sdata);
|
|
|
- /* AP might have been stopped while waiting for the lock. */
|
|
|
- if (!sdata->vif.csa_active)
|
|
|
- goto unlock;
|
|
|
+ ieee80211_queue_work(&sdata->local->hw,
|
|
|
+ &sdata->csa_finalize_work);
|
|
|
+}
|
|
|
+EXPORT_SYMBOL(ieee80211_csa_finish);
|
|
|
|
|
|
- if (!ieee80211_sdata_running(sdata))
|
|
|
- goto unlock;
|
|
|
+static void ieee80211_csa_finalize(struct ieee80211_sub_if_data *sdata)
|
|
|
+{
|
|
|
+ struct ieee80211_local *local = sdata->local;
|
|
|
+ int err, changed = 0;
|
|
|
|
|
|
sdata->radar_required = sdata->csa_radar_required;
|
|
|
mutex_lock(&local->mtx);
|
|
|
err = ieee80211_vif_change_channel(sdata, &changed);
|
|
|
mutex_unlock(&local->mtx);
|
|
|
if (WARN_ON(err < 0))
|
|
|
- goto unlock;
|
|
|
+ return;
|
|
|
|
|
|
if (!local->use_chanctx) {
|
|
|
local->_oper_chandef = sdata->csa_chandef;
|
|
@@ -3023,7 +3021,7 @@ void ieee80211_csa_finalize_work(struct work_struct *work)
|
|
|
case NL80211_IFTYPE_AP:
|
|
|
err = ieee80211_assign_beacon(sdata, sdata->u.ap.next_beacon);
|
|
|
if (err < 0)
|
|
|
- goto unlock;
|
|
|
+ return;
|
|
|
|
|
|
changed |= err;
|
|
|
kfree(sdata->u.ap.next_beacon);
|
|
@@ -3038,12 +3036,12 @@ void ieee80211_csa_finalize_work(struct work_struct *work)
|
|
|
case NL80211_IFTYPE_MESH_POINT:
|
|
|
err = ieee80211_mesh_finish_csa(sdata);
|
|
|
if (err < 0)
|
|
|
- goto unlock;
|
|
|
+ return;
|
|
|
break;
|
|
|
#endif
|
|
|
default:
|
|
|
WARN_ON(1);
|
|
|
- goto unlock;
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
ieee80211_wake_queues_by_reason(&sdata->local->hw,
|
|
@@ -3051,6 +3049,23 @@ void ieee80211_csa_finalize_work(struct work_struct *work)
|
|
|
IEEE80211_QUEUE_STOP_REASON_CSA);
|
|
|
|
|
|
cfg80211_ch_switch_notify(sdata->dev, &sdata->csa_chandef);
|
|
|
+}
|
|
|
+
|
|
|
+void ieee80211_csa_finalize_work(struct work_struct *work)
|
|
|
+{
|
|
|
+ struct ieee80211_sub_if_data *sdata =
|
|
|
+ container_of(work, struct ieee80211_sub_if_data,
|
|
|
+ csa_finalize_work);
|
|
|
+
|
|
|
+ sdata_lock(sdata);
|
|
|
+ /* AP might have been stopped while waiting for the lock. */
|
|
|
+ if (!sdata->vif.csa_active)
|
|
|
+ goto unlock;
|
|
|
+
|
|
|
+ if (!ieee80211_sdata_running(sdata))
|
|
|
+ goto unlock;
|
|
|
+
|
|
|
+ ieee80211_csa_finalize(sdata);
|
|
|
|
|
|
unlock:
|
|
|
sdata_unlock(sdata);
|
|
@@ -3064,7 +3079,7 @@ int ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
|
|
|
struct ieee80211_chanctx_conf *chanctx_conf;
|
|
|
struct ieee80211_chanctx *chanctx;
|
|
|
struct ieee80211_if_mesh __maybe_unused *ifmsh;
|
|
|
- int err, num_chanctx;
|
|
|
+ int err, num_chanctx, changed = 0;
|
|
|
|
|
|
lockdep_assert_held(&sdata->wdev.mtx);
|
|
|
|
|
@@ -3105,19 +3120,40 @@ int ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
|
|
|
|
|
|
switch (sdata->vif.type) {
|
|
|
case NL80211_IFTYPE_AP:
|
|
|
- sdata->csa_counter_offset_beacon =
|
|
|
- params->counter_offset_beacon;
|
|
|
- sdata->csa_counter_offset_presp = params->counter_offset_presp;
|
|
|
sdata->u.ap.next_beacon =
|
|
|
cfg80211_beacon_dup(¶ms->beacon_after);
|
|
|
if (!sdata->u.ap.next_beacon)
|
|
|
return -ENOMEM;
|
|
|
|
|
|
+ /*
|
|
|
+ * With a count of 0, we don't have to wait for any
|
|
|
+ * TBTT before switching, so complete the CSA
|
|
|
+ * immediately. In theory, with a count == 1 we
|
|
|
+ * should delay the switch until just before the next
|
|
|
+ * TBTT, but that would complicate things so we switch
|
|
|
+ * immediately too. If we would delay the switch
|
|
|
+ * until the next TBTT, we would have to set the probe
|
|
|
+ * response here.
|
|
|
+ *
|
|
|
+ * TODO: A channel switch with count <= 1 without
|
|
|
+ * sending a CSA action frame is kind of useless,
|
|
|
+ * because the clients won't know we're changing
|
|
|
+ * channels. The action frame must be implemented
|
|
|
+ * either here or in the userspace.
|
|
|
+ */
|
|
|
+ if (params->count <= 1)
|
|
|
+ break;
|
|
|
+
|
|
|
+ sdata->csa_counter_offset_beacon =
|
|
|
+ params->counter_offset_beacon;
|
|
|
+ sdata->csa_counter_offset_presp = params->counter_offset_presp;
|
|
|
err = ieee80211_assign_beacon(sdata, ¶ms->beacon_csa);
|
|
|
if (err < 0) {
|
|
|
kfree(sdata->u.ap.next_beacon);
|
|
|
return err;
|
|
|
}
|
|
|
+ changed |= err;
|
|
|
+
|
|
|
break;
|
|
|
case NL80211_IFTYPE_ADHOC:
|
|
|
if (!sdata->vif.bss_conf.ibss_joined)
|
|
@@ -3145,9 +3181,16 @@ int ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
|
|
|
params->chandef.chan->band)
|
|
|
return -EINVAL;
|
|
|
|
|
|
- err = ieee80211_ibss_csa_beacon(sdata, params);
|
|
|
- if (err < 0)
|
|
|
- return err;
|
|
|
+ /* see comments in the NL80211_IFTYPE_AP block */
|
|
|
+ if (params->count > 1) {
|
|
|
+ err = ieee80211_ibss_csa_beacon(sdata, params);
|
|
|
+ if (err < 0)
|
|
|
+ return err;
|
|
|
+ changed |= err;
|
|
|
+ }
|
|
|
+
|
|
|
+ ieee80211_send_action_csa(sdata, params);
|
|
|
+
|
|
|
break;
|
|
|
#ifdef CONFIG_MAC80211_MESH
|
|
|
case NL80211_IFTYPE_MESH_POINT:
|
|
@@ -3172,12 +3215,19 @@ int ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
|
|
|
if (ifmsh->csa_role == IEEE80211_MESH_CSA_ROLE_NONE)
|
|
|
ifmsh->csa_role = IEEE80211_MESH_CSA_ROLE_INIT;
|
|
|
|
|
|
- err = ieee80211_mesh_csa_beacon(sdata, params,
|
|
|
- (ifmsh->csa_role == IEEE80211_MESH_CSA_ROLE_INIT));
|
|
|
- if (err < 0) {
|
|
|
- ifmsh->csa_role = IEEE80211_MESH_CSA_ROLE_NONE;
|
|
|
- return err;
|
|
|
+ /* see comments in the NL80211_IFTYPE_AP block */
|
|
|
+ if (params->count > 1) {
|
|
|
+ err = ieee80211_mesh_csa_beacon(sdata, params);
|
|
|
+ if (err < 0) {
|
|
|
+ ifmsh->csa_role = IEEE80211_MESH_CSA_ROLE_NONE;
|
|
|
+ return err;
|
|
|
+ }
|
|
|
+ changed |= err;
|
|
|
}
|
|
|
+
|
|
|
+ if (ifmsh->csa_role == IEEE80211_MESH_CSA_ROLE_INIT)
|
|
|
+ ieee80211_send_action_csa(sdata, params);
|
|
|
+
|
|
|
break;
|
|
|
#endif
|
|
|
default:
|
|
@@ -3194,8 +3244,13 @@ int ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
|
|
|
sdata->csa_chandef = params->chandef;
|
|
|
sdata->vif.csa_active = true;
|
|
|
|
|
|
- ieee80211_bss_info_change_notify(sdata, err);
|
|
|
- drv_channel_switch_beacon(sdata, ¶ms->chandef);
|
|
|
+ if (changed) {
|
|
|
+ ieee80211_bss_info_change_notify(sdata, changed);
|
|
|
+ drv_channel_switch_beacon(sdata, ¶ms->chandef);
|
|
|
+ } else {
|
|
|
+ /* if the beacon didn't change, we can finalize immediately */
|
|
|
+ ieee80211_csa_finalize(sdata);
|
|
|
+ }
|
|
|
|
|
|
return 0;
|
|
|
}
|