mac80211: add channel switch command and beacon callbacks
authorSimon Wunderlich <simon.wunderlich@s2003.tu-chemnitz.de>
Thu, 11 Jul 2013 14:09:06 +0000 (16:09 +0200)
committerJohannes Berg <johannes.berg@intel.com>
Thu, 1 Aug 2013 16:30:33 +0000 (18:30 +0200)
The count field in CSA must be decremented with each beacon
transmitted. This patch implements the functionality for drivers
using ieee80211_beacon_get(). Other drivers must call back manually
after reaching count == 0.

This patch also contains the handling and finish worker for the channel
switch command, and mac80211/chanctx code to allow to change a channel
definition of an active channel context.

Signed-off-by: Simon Wunderlich <siwu@hrz.tu-chemnitz.de>
Signed-off-by: Mathias Kretschmer <mathias.kretschmer@fokus.fraunhofer.de>
[small cleanups, catch identical chandef]
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
include/net/mac80211.h
net/mac80211/cfg.c
net/mac80211/chan.c
net/mac80211/driver-ops.h
net/mac80211/ieee80211_i.h
net/mac80211/iface.c
net/mac80211/trace.h
net/mac80211/tx.c

index 3124036285ebe6b048c2731dc8d51e4e58e7b7ad..9cda3728c2cbfc78d77efc765ff298718469108e 100644 (file)
@@ -152,11 +152,14 @@ struct ieee80211_low_level_stats {
  * @IEEE80211_CHANCTX_CHANGE_WIDTH: The channel width changed
  * @IEEE80211_CHANCTX_CHANGE_RX_CHAINS: The number of RX chains changed
  * @IEEE80211_CHANCTX_CHANGE_RADAR: radar detection flag changed
+ * @IEEE80211_CHANCTX_CHANGE_CHANNEL: switched to another operating channel,
+ *     this is used only with channel switching with CSA
  */
 enum ieee80211_chanctx_change {
        IEEE80211_CHANCTX_CHANGE_WIDTH          = BIT(0),
        IEEE80211_CHANCTX_CHANGE_RX_CHAINS      = BIT(1),
        IEEE80211_CHANCTX_CHANGE_RADAR          = BIT(2),
+       IEEE80211_CHANCTX_CHANGE_CHANNEL        = BIT(3),
 };
 
 /**
@@ -1084,6 +1087,7 @@ enum ieee80211_vif_flags {
  * @addr: address of this interface
  * @p2p: indicates whether this AP or STA interface is a p2p
  *     interface, i.e. a GO or p2p-sta respectively
+ * @csa_active: marks whether a channel switch is going on
  * @driver_flags: flags/capabilities the driver has for this interface,
  *     these need to be set (or cleared) when the interface is added
  *     or, if supported by the driver, the interface type is changed
@@ -1106,6 +1110,7 @@ struct ieee80211_vif {
        struct ieee80211_bss_conf bss_conf;
        u8 addr[ETH_ALEN];
        bool p2p;
+       bool csa_active;
 
        u8 cab_queue;
        u8 hw_queue[IEEE80211_NUM_ACS];
@@ -2637,6 +2642,16 @@ enum ieee80211_roc_type {
  * @ipv6_addr_change: IPv6 address assignment on the given interface changed.
  *     Currently, this is only called for managed or P2P client interfaces.
  *     This callback is optional; it must not sleep.
+ *
+ * @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
+ *     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
+ *     transmitted and then call ieee80211_csa_finish().
+ *
  */
 struct ieee80211_ops {
        void (*tx)(struct ieee80211_hw *hw,
@@ -2824,6 +2839,9 @@ struct ieee80211_ops {
                                 struct ieee80211_vif *vif,
                                 struct inet6_dev *idev);
 #endif
+       void (*channel_switch_beacon)(struct ieee80211_hw *hw,
+                                     struct ieee80211_vif *vif,
+                                     struct cfg80211_chan_def *chandef);
 };
 
 /**
@@ -3318,6 +3336,25 @@ static inline struct sk_buff *ieee80211_beacon_get(struct ieee80211_hw *hw,
        return ieee80211_beacon_get_tim(hw, vif, NULL, NULL);
 }
 
+/**
+ * ieee80211_csa_finish - notify mac80211 about channel switch
+ * @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
+ * 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
+ * @vif: &struct ieee80211_vif pointer from the add_interface callback.
+ *
+ * This function returns whether the channel switch counters reached zero.
+ */
+bool ieee80211_csa_is_complete(struct ieee80211_vif *vif);
+
+
 /**
  * ieee80211_proberesp_get - retrieve a Probe Response template
  * @hw: pointer obtained from ieee80211_alloc_hw().
index b82fff6c0b306e3e11b4dbcdb434f7a7efb72e7d..44449ceb79664026037113126e2f13d0ed0ca16a 100644 (file)
@@ -860,8 +860,8 @@ static int ieee80211_set_probe_resp(struct ieee80211_sub_if_data *sdata,
        return 0;
 }
 
-static int ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata,
-                                  struct cfg80211_beacon_data *params)
+int ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata,
+                           struct cfg80211_beacon_data *params)
 {
        struct beacon_data *new, *old;
        int new_head_len, new_tail_len;
@@ -1024,6 +1024,12 @@ static int ieee80211_change_beacon(struct wiphy *wiphy, struct net_device *dev,
 
        sdata = IEEE80211_DEV_TO_SUB_IF(dev);
 
+       /* don't allow changing the beacon while CSA is in place - offset
+        * of channel switch counter may change
+        */
+       if (sdata->vif.csa_active)
+               return -EBUSY;
+
        old = rtnl_dereference(sdata->u.ap.beacon);
        if (!old)
                return -ENOENT;
@@ -1048,6 +1054,10 @@ static int ieee80211_stop_ap(struct wiphy *wiphy, struct net_device *dev)
                return -ENOENT;
        old_probe_resp = rtnl_dereference(sdata->u.ap.probe_resp);
 
+       /* abort any running channel switch */
+       sdata->vif.csa_active = false;
+       cancel_work_sync(&sdata->csa_finalize_work);
+
        /* turn off carrier for this interface and dependent VLANs */
        list_for_each_entry(vlan, &sdata->u.ap.vlans, u.vlan.list)
                netif_carrier_off(vlan->dev);
@@ -2775,6 +2785,178 @@ static int ieee80211_start_radar_detection(struct wiphy *wiphy,
        return 0;
 }
 
+static struct cfg80211_beacon_data *
+cfg80211_beacon_dup(struct cfg80211_beacon_data *beacon)
+{
+       struct cfg80211_beacon_data *new_beacon;
+       u8 *pos;
+       int len;
+
+       len = beacon->head_len + beacon->tail_len + beacon->beacon_ies_len +
+             beacon->proberesp_ies_len + beacon->assocresp_ies_len +
+             beacon->probe_resp_len;
+
+       new_beacon = kzalloc(sizeof(*new_beacon) + len, GFP_KERNEL);
+       if (!new_beacon)
+               return NULL;
+
+       pos = (u8 *)(new_beacon + 1);
+       if (beacon->head_len) {
+               new_beacon->head_len = beacon->head_len;
+               new_beacon->head = pos;
+               memcpy(pos, beacon->head, beacon->head_len);
+               pos += beacon->head_len;
+       }
+       if (beacon->tail_len) {
+               new_beacon->tail_len = beacon->tail_len;
+               new_beacon->tail = pos;
+               memcpy(pos, beacon->tail, beacon->tail_len);
+               pos += beacon->tail_len;
+       }
+       if (beacon->beacon_ies_len) {
+               new_beacon->beacon_ies_len = beacon->beacon_ies_len;
+               new_beacon->beacon_ies = pos;
+               memcpy(pos, beacon->beacon_ies, beacon->beacon_ies_len);
+               pos += beacon->beacon_ies_len;
+       }
+       if (beacon->proberesp_ies_len) {
+               new_beacon->proberesp_ies_len = beacon->proberesp_ies_len;
+               new_beacon->proberesp_ies = pos;
+               memcpy(pos, beacon->proberesp_ies, beacon->proberesp_ies_len);
+               pos += beacon->proberesp_ies_len;
+       }
+       if (beacon->assocresp_ies_len) {
+               new_beacon->assocresp_ies_len = beacon->assocresp_ies_len;
+               new_beacon->assocresp_ies = pos;
+               memcpy(pos, beacon->assocresp_ies, beacon->assocresp_ies_len);
+               pos += beacon->assocresp_ies_len;
+       }
+       if (beacon->probe_resp_len) {
+               new_beacon->probe_resp_len = beacon->probe_resp_len;
+               beacon->probe_resp = pos;
+               memcpy(pos, beacon->probe_resp, beacon->probe_resp_len);
+               pos += beacon->probe_resp_len;
+       }
+
+       return new_beacon;
+}
+
+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);
+       struct ieee80211_local *local = sdata->local;
+       int err, changed;
+
+       if (!ieee80211_sdata_running(sdata))
+               return;
+
+       if (WARN_ON(sdata->vif.type != NL80211_IFTYPE_AP))
+               return;
+
+       sdata->radar_required = sdata->csa_radar_required;
+       err = ieee80211_vif_change_channel(sdata, &local->csa_chandef,
+                                          &changed);
+       if (WARN_ON(err < 0))
+               return;
+
+       err = ieee80211_assign_beacon(sdata, sdata->u.ap.next_beacon);
+       if (err < 0)
+               return;
+
+       changed |= err;
+       kfree(sdata->u.ap.next_beacon);
+       sdata->u.ap.next_beacon = NULL;
+       sdata->vif.csa_active = false;
+
+       ieee80211_wake_queues_by_reason(&sdata->local->hw,
+                                       IEEE80211_MAX_QUEUE_MAP,
+                                       IEEE80211_QUEUE_STOP_REASON_CSA);
+
+       ieee80211_bss_info_change_notify(sdata, changed);
+
+       cfg80211_ch_switch_notify(sdata->dev, &local->csa_chandef);
+}
+
+static int ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
+                                   struct cfg80211_csa_settings *params)
+{
+       struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+       struct ieee80211_local *local = sdata->local;
+       struct ieee80211_chanctx_conf *chanctx_conf;
+       struct ieee80211_chanctx *chanctx;
+       int err, num_chanctx;
+
+       if (!list_empty(&local->roc_list) || local->scanning)
+               return -EBUSY;
+
+       if (sdata->wdev.cac_started)
+               return -EBUSY;
+
+       if (cfg80211_chandef_identical(&params->chandef,
+                                      &sdata->vif.bss_conf.chandef))
+               return -EINVAL;
+
+       rcu_read_lock();
+       chanctx_conf = rcu_dereference(sdata->vif.chanctx_conf);
+       if (!chanctx_conf) {
+               rcu_read_unlock();
+               return -EBUSY;
+       }
+
+       /* don't handle for multi-VIF cases */
+       chanctx = container_of(chanctx_conf, struct ieee80211_chanctx, conf);
+       if (chanctx->refcount > 1) {
+               rcu_read_unlock();
+               return -EBUSY;
+       }
+       num_chanctx = 0;
+       list_for_each_entry_rcu(chanctx, &local->chanctx_list, list)
+               num_chanctx++;
+       rcu_read_unlock();
+
+       if (num_chanctx > 1)
+               return -EBUSY;
+
+       /* don't allow another channel switch if one is already active. */
+       if (sdata->vif.csa_active)
+               return -EBUSY;
+
+       /* only handle AP for now. */
+       switch (sdata->vif.type) {
+       case NL80211_IFTYPE_AP:
+               break;
+       default:
+               return -EOPNOTSUPP;
+       }
+
+       sdata->u.ap.next_beacon = cfg80211_beacon_dup(&params->beacon_after);
+       if (!sdata->u.ap.next_beacon)
+               return -ENOMEM;
+
+       sdata->csa_counter_offset_beacon = params->counter_offset_beacon;
+       sdata->csa_counter_offset_presp = params->counter_offset_presp;
+       sdata->csa_radar_required = params->radar_required;
+
+       if (params->block_tx)
+               ieee80211_stop_queues_by_reason(&local->hw,
+                               IEEE80211_MAX_QUEUE_MAP,
+                               IEEE80211_QUEUE_STOP_REASON_CSA);
+
+       err = ieee80211_assign_beacon(sdata, &params->beacon_csa);
+       if (err < 0)
+               return err;
+
+       local->csa_chandef = params->chandef;
+       sdata->vif.csa_active = true;
+
+       ieee80211_bss_info_change_notify(sdata, err);
+       drv_channel_switch_beacon(sdata, &params->chandef);
+
+       return 0;
+}
+
 static int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,
                             struct ieee80211_channel *chan, bool offchan,
                             unsigned int wait, const u8 *buf, size_t len,
@@ -3492,4 +3674,5 @@ struct cfg80211_ops mac80211_config_ops = {
        .get_et_strings = ieee80211_get_et_strings,
        .get_channel = ieee80211_cfg_get_channel,
        .start_radar_detection = ieee80211_start_radar_detection,
+       .channel_switch = ieee80211_channel_switch,
 };
index 03e8d2e3270e23f0e97a58595513b33fcb048cb3..3a4764b2869efffdbcc3f90a363cf3f8b095496c 100644 (file)
@@ -410,6 +410,64 @@ int ieee80211_vif_use_channel(struct ieee80211_sub_if_data *sdata,
        return ret;
 }
 
+int ieee80211_vif_change_channel(struct ieee80211_sub_if_data *sdata,
+                                const struct cfg80211_chan_def *chandef,
+                                u32 *changed)
+{
+       struct ieee80211_local *local = sdata->local;
+       struct ieee80211_chanctx_conf *conf;
+       struct ieee80211_chanctx *ctx;
+       int ret;
+       u32 chanctx_changed = 0;
+
+       /* should never be called if not performing a channel switch. */
+       if (WARN_ON(!sdata->vif.csa_active))
+               return -EINVAL;
+
+       if (!cfg80211_chandef_usable(sdata->local->hw.wiphy, chandef,
+                                    IEEE80211_CHAN_DISABLED))
+               return -EINVAL;
+
+       mutex_lock(&local->chanctx_mtx);
+       conf = rcu_dereference_protected(sdata->vif.chanctx_conf,
+                                        lockdep_is_held(&local->chanctx_mtx));
+       if (!conf) {
+               ret = -EINVAL;
+               goto out;
+       }
+
+       ctx = container_of(conf, struct ieee80211_chanctx, conf);
+       if (ctx->refcount != 1) {
+               ret = -EINVAL;
+               goto out;
+       }
+
+       if (sdata->vif.bss_conf.chandef.width != chandef->width) {
+               chanctx_changed = IEEE80211_CHANCTX_CHANGE_WIDTH;
+               *changed |= BSS_CHANGED_BANDWIDTH;
+       }
+
+       sdata->vif.bss_conf.chandef = *chandef;
+       ctx->conf.def = *chandef;
+
+       chanctx_changed |= IEEE80211_CHANCTX_CHANGE_CHANNEL;
+       drv_change_chanctx(local, ctx, chanctx_changed);
+
+       if (!local->use_chanctx) {
+               local->_oper_chandef = *chandef;
+               ieee80211_hw_config(local, 0);
+       }
+
+       ieee80211_recalc_chanctx_chantype(local, ctx);
+       ieee80211_recalc_smps_chanctx(local, ctx);
+       ieee80211_recalc_radar_chanctx(local, ctx);
+
+       ret = 0;
+ out:
+       mutex_unlock(&local->chanctx_mtx);
+       return ret;
+}
+
 int ieee80211_vif_change_bandwidth(struct ieee80211_sub_if_data *sdata,
                                   const struct cfg80211_chan_def *chandef,
                                   u32 *changed)
index b931c96a596fb3e92e9effa845ab10f74144dfd1..b3ea11f3d526962ddd8190587f82d8d84134067c 100644 (file)
@@ -1072,4 +1072,17 @@ static inline void drv_ipv6_addr_change(struct ieee80211_local *local,
 }
 #endif
 
+static inline void
+drv_channel_switch_beacon(struct ieee80211_sub_if_data *sdata,
+                         struct cfg80211_chan_def *chandef)
+{
+       struct ieee80211_local *local = sdata->local;
+
+       if (local->ops->channel_switch_beacon) {
+               trace_drv_channel_switch_beacon(local, sdata, chandef);
+               local->ops->channel_switch_beacon(&local->hw, &sdata->vif,
+                                                 chandef);
+       }
+}
+
 #endif /* __MAC80211_DRIVER_OPS */
index d779383c52d2baeadb8437b56f4f74d6e0c0f07a..e94c84050e9c214c3959088423f75ef444824dcd 100644 (file)
@@ -259,6 +259,8 @@ struct ieee80211_if_ap {
        struct beacon_data __rcu *beacon;
        struct probe_resp __rcu *probe_resp;
 
+       /* to be used after channel switch. */
+       struct cfg80211_beacon_data *next_beacon;
        struct list_head vlans;
 
        struct ps_data ps;
@@ -716,6 +718,11 @@ struct ieee80211_sub_if_data {
 
        struct ieee80211_tx_queue_params tx_conf[IEEE80211_NUM_ACS];
 
+       struct work_struct csa_finalize_work;
+       int csa_counter_offset_beacon;
+       int csa_counter_offset_presp;
+       bool csa_radar_required;
+
        /* used to reconfigure hardware SM PS */
        struct work_struct recalc_smps;
 
@@ -1372,6 +1379,9 @@ void ieee80211_roc_notify_destroy(struct ieee80211_roc_work *roc, bool free);
 void ieee80211_sw_roc_work(struct work_struct *work);
 void ieee80211_handle_roc_started(struct ieee80211_roc_work *roc);
 
+/* channel switch handling */
+void ieee80211_csa_finalize_work(struct work_struct *work);
+
 /* interface handling */
 int ieee80211_iface_init(void);
 void ieee80211_iface_exit(void);
@@ -1393,6 +1403,8 @@ void ieee80211_del_virtual_monitor(struct ieee80211_local *local);
 
 bool __ieee80211_recalc_txpower(struct ieee80211_sub_if_data *sdata);
 void ieee80211_recalc_txpower(struct ieee80211_sub_if_data *sdata);
+int ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata,
+                           struct cfg80211_beacon_data *params);
 
 static inline bool ieee80211_sdata_running(struct ieee80211_sub_if_data *sdata)
 {
@@ -1654,6 +1666,11 @@ int __must_check
 ieee80211_vif_change_bandwidth(struct ieee80211_sub_if_data *sdata,
                               const struct cfg80211_chan_def *chandef,
                               u32 *changed);
+/* NOTE: only use ieee80211_vif_change_channel() for channel switch */
+int __must_check
+ieee80211_vif_change_channel(struct ieee80211_sub_if_data *sdata,
+                            const struct cfg80211_chan_def *chandef,
+                            u32 *changed);
 void ieee80211_vif_release_channel(struct ieee80211_sub_if_data *sdata);
 void ieee80211_vif_vlan_copy_chanctx(struct ieee80211_sub_if_data *sdata);
 void ieee80211_vif_copy_chanctx_to_vlans(struct ieee80211_sub_if_data *sdata,
index 4c41c11958c8a9de28fcae05465f44bc6712ca45..7ca534bf4ceaef6d3125a6204d66bf29ced020ab 100644 (file)
@@ -274,6 +274,12 @@ static int ieee80211_check_concurrent_iface(struct ieee80211_sub_if_data *sdata,
                        if (iftype == NL80211_IFTYPE_ADHOC &&
                            nsdata->vif.type == NL80211_IFTYPE_ADHOC)
                                return -EBUSY;
+                       /*
+                        * will not add another interface while any channel
+                        * switch is active.
+                        */
+                       if (nsdata->vif.csa_active)
+                               return -EBUSY;
 
                        /*
                         * The remaining checks are only performed for interfaces
@@ -804,6 +810,8 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata,
        cancel_work_sync(&local->dynamic_ps_enable_work);
 
        cancel_work_sync(&sdata->recalc_smps);
+       sdata->vif.csa_active = false;
+       cancel_work_sync(&sdata->csa_finalize_work);
 
        cancel_delayed_work_sync(&sdata->dfs_cac_timer_work);
 
@@ -1267,6 +1275,7 @@ static void ieee80211_setup_sdata(struct ieee80211_sub_if_data *sdata,
        skb_queue_head_init(&sdata->skb_queue);
        INIT_WORK(&sdata->work, ieee80211_iface_work);
        INIT_WORK(&sdata->recalc_smps, ieee80211_recalc_smps_work);
+       INIT_WORK(&sdata->csa_finalize_work, ieee80211_csa_finalize_work);
 
        switch (type) {
        case NL80211_IFTYPE_P2P_GO:
index c215fafd7a2fc1ac3e206b4551c60de8d656ab24..1aba645882bd92abbf9e5a4cdd90f7544a173c4a 100644 (file)
@@ -1906,6 +1906,32 @@ TRACE_EVENT(api_radar_detected,
        )
 );
 
+TRACE_EVENT(drv_channel_switch_beacon,
+       TP_PROTO(struct ieee80211_local *local,
+                struct ieee80211_sub_if_data *sdata,
+                struct cfg80211_chan_def *chandef),
+
+       TP_ARGS(local, sdata, chandef),
+
+       TP_STRUCT__entry(
+               LOCAL_ENTRY
+               VIF_ENTRY
+               CHANDEF_ENTRY
+       ),
+
+       TP_fast_assign(
+               LOCAL_ASSIGN;
+               VIF_ASSIGN;
+               CHANDEF_ASSIGN(chandef);
+       ),
+
+       TP_printk(
+               LOCAL_PR_FMT VIF_PR_FMT " channel switch to " CHANDEF_PR_FMT,
+               LOCAL_PR_ARG, VIF_PR_ARG, CHANDEF_PR_ARG
+       )
+);
+
+
 #ifdef CONFIG_MAC80211_MESSAGE_TRACING
 #undef TRACE_SYSTEM
 #define TRACE_SYSTEM mac80211_msg
index f65873f0c89f07ee33e41e28c80bc3b02295e85b..0e42322aa6b157e13d7ea8ee57d3ad6f03e08ac4 100644 (file)
@@ -2338,6 +2338,81 @@ static int ieee80211_beacon_add_tim(struct ieee80211_sub_if_data *sdata,
        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)
+{
+       struct probe_resp *resp;
+       int counter_offset_beacon = sdata->csa_counter_offset_beacon;
+       int counter_offset_presp = sdata->csa_counter_offset_presp;
+
+       /* warn if the driver did not check for/react to csa completeness */
+       if (WARN_ON(((u8 *)beacon->tail)[counter_offset_beacon] == 0))
+               return;
+
+       ((u8 *)beacon->tail)[counter_offset_beacon]--;
+
+       if (sdata->vif.type == NL80211_IFTYPE_AP &&
+           counter_offset_presp) {
+               rcu_read_lock();
+               resp = rcu_dereference(sdata->u.ap.probe_resp);
+
+               /* if nl80211 accepted the offset, this should not happen. */
+               if (WARN_ON(!resp)) {
+                       rcu_read_unlock();
+                       return;
+               }
+               resp->data[counter_offset_presp]--;
+               rcu_read_unlock();
+       }
+}
+
+bool ieee80211_csa_is_complete(struct ieee80211_vif *vif)
+{
+       struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
+       struct beacon_data *beacon = NULL;
+       u8 *beacon_data;
+       size_t beacon_data_len;
+       int counter_beacon = sdata->csa_counter_offset_beacon;
+       int ret = false;
+
+       if (!ieee80211_sdata_running(sdata))
+               return false;
+
+       rcu_read_lock();
+       if (vif->type == NL80211_IFTYPE_AP) {
+               struct ieee80211_if_ap *ap = &sdata->u.ap;
+
+               beacon = rcu_dereference(ap->beacon);
+               if (WARN_ON(!beacon || !beacon->tail))
+                       goto out;
+               beacon_data = beacon->tail;
+               beacon_data_len = beacon->tail_len;
+       } else {
+               WARN_ON(1);
+               goto out;
+       }
+
+       if (WARN_ON(counter_beacon > beacon_data_len))
+               goto out;
+
+       if (beacon_data[counter_beacon] == 0)
+               ret = true;
+ out:
+       rcu_read_unlock();
+
+       return ret;
+}
+EXPORT_SYMBOL(ieee80211_csa_is_complete);
+
 struct sk_buff *ieee80211_beacon_get_tim(struct ieee80211_hw *hw,
                                         struct ieee80211_vif *vif,
                                         u16 *tim_offset, u16 *tim_length)
@@ -2368,6 +2443,9 @@ struct sk_buff *ieee80211_beacon_get_tim(struct ieee80211_hw *hw,
                struct beacon_data *beacon = rcu_dereference(ap->beacon);
 
                if (beacon) {
+                       if (sdata->vif.csa_active)
+                               ieee80211_update_csa(sdata, beacon);
+
                        /*
                         * headroom, head length,
                         * tail length and maximum TIM length