*     for a single active channel while using channel contexts. When support
  *     is not enabled the default action is to disconnect when getting the
  *     CSA frame.
+ *
+ * @IEEE80211_HW_CHANGE_RUNNING_CHANCTX: The hardware can change a
+ *     channel context on-the-fly.  This is needed for channel switch
+ *     on single-channel hardware.  It can also be used as an
+ *     optimization in certain channel switch cases with
+ *     multi-channel.
  */
 enum ieee80211_hw_flags {
        IEEE80211_HW_HAS_RATE_CONTROL                   = 1<<0,
        IEEE80211_HW_TIMING_BEACON_ONLY                 = 1<<26,
        IEEE80211_HW_SUPPORTS_HT_CCK_RATES              = 1<<27,
        IEEE80211_HW_CHANCTX_STA_CSA                    = 1<<28,
+       IEEE80211_HW_CHANGE_RUNNING_CHANCTX             = 1<<29,
 };
 
 /**
 
        }
 }
 
+static bool ieee80211_chanctx_is_reserved(struct ieee80211_local *local,
+                                         struct ieee80211_chanctx *ctx)
+{
+       struct ieee80211_sub_if_data *sdata;
+       bool ret = false;
+
+       lockdep_assert_held(&local->chanctx_mtx);
+       rcu_read_lock();
+       list_for_each_entry_rcu(sdata, &local->interfaces, list) {
+               if (!ieee80211_sdata_running(sdata))
+                       continue;
+               if (sdata->reserved_chanctx == ctx) {
+                       ret = true;
+                       break;
+               }
+       }
+
+       rcu_read_unlock();
+       return ret;
+}
+
 static struct ieee80211_chanctx *
 ieee80211_find_chanctx(struct ieee80211_local *local,
                       const struct cfg80211_chan_def *chandef,
        list_for_each_entry(ctx, &local->chanctx_list, list) {
                const struct cfg80211_chan_def *compat;
 
-               if (ctx->mode == IEEE80211_CHANCTX_EXCLUSIVE)
+               /* We don't support chanctx reservation for multiple
+                * vifs yet, so don't allow reserved chanctxs to be
+                * reused.
+                */
+               if ((ctx->mode == IEEE80211_CHANCTX_EXCLUSIVE) ||
+                   ieee80211_chanctx_is_reserved(local, ctx))
                        continue;
 
                compat = cfg80211_chandef_compatible(&ctx->conf.def, chandef);
        /* try to find another context with the chandef we want */
        new_ctx = ieee80211_find_chanctx(local, chandef, mode);
        if (!new_ctx) {
-               /* create a new context */
-               new_ctx = ieee80211_new_chanctx(local, chandef, mode);
-               if (IS_ERR(new_ctx)) {
-                       ret = PTR_ERR(new_ctx);
-                       goto out;
+               if (curr_ctx->refcount == 1 &&
+                   (local->hw.flags & IEEE80211_HW_CHANGE_RUNNING_CHANCTX)) {
+                       /* if we're the only users of the chanctx and
+                        * the driver supports changing a running
+                        * context, reserve our current context
+                        */
+                       new_ctx = curr_ctx;
+               } else {
+                       /* create a new context and reserve it */
+                       new_ctx = ieee80211_new_chanctx(local, chandef, mode);
+                       if (IS_ERR(new_ctx)) {
+                               ret = PTR_ERR(new_ctx);
+                               goto out;
+                       }
                }
        }
 
 
        sdata->vif.bss_conf.chandef = sdata->reserved_chandef;
 
-       /* unref our reservation before assigning */
+       /* unref our reservation */
        ctx->refcount--;
        sdata->reserved_chanctx = NULL;
 
-       ret = ieee80211_assign_vif_chanctx(sdata, ctx);
-       if (old_ctx->refcount == 0)
-               ieee80211_free_chanctx(local, old_ctx);
-       if (ret) {
-               /* if assign fails refcount stays the same */
-               if (ctx->refcount == 0)
-                       ieee80211_free_chanctx(local, ctx);
-               goto out;
-       }
+       if (old_ctx == ctx) {
+               /* This is our own context, just change it */
+               ret = __ieee80211_vif_change_channel(sdata, old_ctx,
+                                                    &tmp_changed);
+               if (ret)
+                       goto out;
+       } else {
+               ret = ieee80211_assign_vif_chanctx(sdata, ctx);
+               if (old_ctx->refcount == 0)
+                       ieee80211_free_chanctx(local, old_ctx);
+               if (ret) {
+                       /* if assign fails refcount stays the same */
+                       if (ctx->refcount == 0)
+                               ieee80211_free_chanctx(local, ctx);
+                       goto out;
+               }
 
-       if (sdata->vif.type == NL80211_IFTYPE_AP)
-               __ieee80211_vif_copy_chanctx_to_vlans(sdata, false);
+               if (sdata->vif.type == NL80211_IFTYPE_AP)
+                       __ieee80211_vif_copy_chanctx_to_vlans(sdata, false);
+       }
 
        *changed = tmp_changed;