* @channel_switch_beacon: Starts a channel switch to a new channel.
  *     Beacons are modified to include CSA or ECSA IEs before calling this
  *     function. The corresponding count fields in these IEs must be
- *     decremented, and when they reach zero the driver must call
+ *     decremented, and when they reach 1 the driver must call
  *     ieee80211_csa_finish(). Drivers which use ieee80211_beacon_get()
  *     get the csa counter decremented by mac80211, but must check if it is
- *     zero using ieee80211_csa_is_complete() after the beacon has been
+ *     1 using ieee80211_csa_is_complete() after the beacon has been
  *     transmitted and then call ieee80211_csa_finish().
+ *     If the CSA count starts as zero or 1, this function will not be called,
+ *     since there won't be any time to beacon before the switch anyway.
  *
  * @join_ibss: Join an IBSS (on an IBSS interface); this is called after all
  *     information in bss_conf is set up and the beacon can be retrieved. A
  * @vif: &struct ieee80211_vif pointer from the add_interface callback.
  *
  * After a channel switch announcement was scheduled and the counter in this
- * announcement hit zero, this function must be called by the driver to
+ * announcement hits 1, this function must be called by the driver to
  * notify mac80211 that the channel can be changed.
  */
 void ieee80211_csa_finish(struct ieee80211_vif *vif);
 
 /**
- * ieee80211_csa_is_complete - find out if counters reached zero
+ * ieee80211_csa_is_complete - find out if counters reached 1
  * @vif: &struct ieee80211_vif pointer from the add_interface callback.
  *
  * This function returns whether the channel switch counters reached zero.
 
        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;
        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);
        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,
                                        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);
        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);
 
 
        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)
                    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:
                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:
        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;
 }
 
        return 0;
 }
 
-void ieee80211_csa_finish(struct ieee80211_vif *vif)
-{
-       struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
-
-       ieee80211_queue_work(&sdata->local->hw,
-                            &sdata->csa_finalize_work);
-}
-EXPORT_SYMBOL(ieee80211_csa_finish);
-
 static void ieee80211_update_csa(struct ieee80211_sub_if_data *sdata,
                                 struct beacon_data *beacon)
 {
        if (WARN_ON(counter_offset_beacon >= beacon_data_len))
                return;
 
-       /* warn if the driver did not check for/react to csa completeness */
-       if (WARN_ON(beacon_data[counter_offset_beacon] == 0))
+       /* Warn if the driver did not check for/react to csa
+        * completeness.  A beacon with CSA counter set to 0 should
+        * never occur, because a counter of 1 means switch just
+        * before the next beacon.
+        */
+       if (WARN_ON(beacon_data[counter_offset_beacon] == 1))
                return;
 
        beacon_data[counter_offset_beacon]--;
        if (WARN_ON(counter_beacon > beacon_data_len))
                goto out;
 
-       if (beacon_data[counter_beacon] == 0)
+       if (beacon_data[counter_beacon] == 1)
                ret = true;
  out:
        rcu_read_unlock();