All the mail mirrored from lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v7 0/5] cfg/mac80211: implement multi-vif csa
@ 2014-05-29  7:34 Michal Kazior
  2014-05-29  7:34 ` [PATCH v7 1/5] mac80211: implement multi-vif in-place reservations Michal Kazior
                   ` (6 more replies)
  0 siblings, 7 replies; 40+ messages in thread
From: Michal Kazior @ 2014-05-29  7:34 UTC (permalink / raw
  To: linux-wireless; +Cc: luca, johannes, Michal Kazior

Hi,

Here's yet again a re-spin of my multi-vif csa
patches. Hopefully I got everything right this
time..

I've delibarately not included documentation
fixes. I think we can handle this with follow up
patches.


v7:
 * drop drv_switch_vif_chanctx patch in favor of
   Luca's implementation
 * rework in-place reservations to be more robust
 * other fixes (see per patch notes)

Michal Kazior (5):
  mac80211: implement multi-vif in-place reservations
  mac80211: make check_combinations() aware of chanctx reservation
  mac80211: use chanctx reservation for AP CSA
  mac80211: use chanctx reservation for STA CSA
  cfg80211: remove channel_switch combination check

 include/net/cfg80211.h     |   7 +-
 include/net/mac80211.h     |   7 -
 net/mac80211/cfg.c         |  82 ++++---
 net/mac80211/chan.c        | 593 ++++++++++++++++++++++++++++++++++-----------
 net/mac80211/ieee80211_i.h |  12 +-
 net/mac80211/mlme.c        |  99 +++++---
 net/mac80211/util.c        |  42 +++-
 net/wireless/nl80211.c     |  11 -
 8 files changed, 621 insertions(+), 232 deletions(-)

-- 
1.8.5.3


^ permalink raw reply	[flat|nested] 40+ messages in thread

* [PATCH v7 1/5] mac80211: implement multi-vif in-place reservations
  2014-05-29  7:34 [PATCH v7 0/5] cfg/mac80211: implement multi-vif csa Michal Kazior
@ 2014-05-29  7:34 ` Michal Kazior
  2014-06-02 11:33   ` Luca Coelho
  2014-05-29  7:34 ` [PATCH v7 2/5] mac80211: make check_combinations() aware of chanctx reservation Michal Kazior
                   ` (5 subsequent siblings)
  6 siblings, 1 reply; 40+ messages in thread
From: Michal Kazior @ 2014-05-29  7:34 UTC (permalink / raw
  To: linux-wireless; +Cc: luca, johannes, Michal Kazior

Multi-vif in-place reservations happen when
it is impossible to allocate more channel contexts
as indicated by interface combinations.

Such reservations are not finalized until all
assigned interfaces are ready.

This still doesn't handle all possible cases
(i.e. degradation of number of channels) properly.

Signed-off-by: Michal Kazior <michal.kazior@tieto.com>
---

Notes:
    v2:
     * use new_ctx instead of ctx [Eliad]
     * move recalcs after bss_conf is updated [Eliad]
    
    v4:
     * move recalc-radar before vif-chanctx-assign [Eliad]
     * move radar_required swapping before initial add_chanctx() [Eliad]
    
    v5:
     * move kfree_rcu() [Zhao Gang]
    
    v6:
     * use switch_vif_chanctx for incompat case with chanctx drivers [Johannes]
     * fix vlan chanctx copying
    
    v7:
     * major rework of the algorithm to support cross-context vif switches (e.g.
       when 2 contexts depend on each others reserved chandefs and both need
       in-place switch)
     * create dummy chanctx structure for in-place reservations instead of storing
       everything in a single chanctx (new chanctx structure needs to be allocated
       sooner or later for drv_switch_vif_chanctx)
     * remove the ieee80211_vif_chanctx_reservation_complete() stub; it is now
       simply implemented by AP CSA patch [Johannes]
     * fix lines with over 80 chars

 include/net/mac80211.h     |   7 -
 net/mac80211/chan.c        | 498 ++++++++++++++++++++++++++++++++++++++-------
 net/mac80211/ieee80211_i.h |   7 +-
 net/mac80211/util.c        |   8 +-
 4 files changed, 433 insertions(+), 87 deletions(-)

diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 236c5c3..6568cd4 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -1600,12 +1600,6 @@ struct ieee80211_tx_control {
  *	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,
@@ -1637,7 +1631,6 @@ enum ieee80211_hw_flags {
 	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,
 };
 
 /**
diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
index 3702d64..01379a17 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -63,6 +63,19 @@ static bool ieee80211_can_create_new_chanctx(struct ieee80211_local *local)
 	return ieee80211_num_chanctx(local) < ieee80211_max_num_channels(local);
 }
 
+static struct ieee80211_chanctx *
+ieee80211_vif_get_chanctx(struct ieee80211_sub_if_data *sdata)
+{
+	struct ieee80211_chanctx_conf *conf;
+
+	conf = rcu_dereference_protected(sdata->vif.chanctx_conf,
+					 lockdep_is_held(&local->chanctx_mtx));
+	if (!conf)
+		return NULL;
+
+	return container_of(conf, struct ieee80211_chanctx, conf);
+}
+
 static const struct cfg80211_chan_def *
 ieee80211_chanctx_reserved_chandef(struct ieee80211_local *local,
 				   struct ieee80211_chanctx *ctx,
@@ -160,6 +173,9 @@ ieee80211_find_reservation_chanctx(struct ieee80211_local *local,
 		return NULL;
 
 	list_for_each_entry(ctx, &local->chanctx_list, list) {
+		if (ctx->replaced_by)
+			continue;
+
 		if (ctx->mode == IEEE80211_CHANCTX_EXCLUSIVE)
 			continue;
 
@@ -347,6 +363,12 @@ ieee80211_find_chanctx(struct ieee80211_local *local,
 	list_for_each_entry(ctx, &local->chanctx_list, list) {
 		const struct cfg80211_chan_def *compat;
 
+		if (ctx->replaces)
+			continue;
+
+		if (ctx->replaced_by)
+			continue;
+
 		if (ctx->mode == IEEE80211_CHANCTX_EXCLUSIVE)
 			continue;
 
@@ -898,8 +920,16 @@ int ieee80211_vif_unreserve_chanctx(struct ieee80211_sub_if_data *sdata)
 	list_del(&sdata->reserved_chanctx_list);
 	sdata->reserved_chanctx = NULL;
 
-	if (ieee80211_chanctx_refcount(sdata->local, ctx) == 0)
-		ieee80211_free_chanctx(sdata->local, ctx);
+	if (ieee80211_chanctx_refcount(sdata->local, ctx) == 0) {
+		if (ctx->replaces) {
+			WARN_ON(ctx->replaces->replaced_by != ctx);
+			ctx->replaces->replaced_by = NULL;
+			list_del_rcu(&ctx->list);
+			kfree_rcu(ctx, rcu_head);
+		} else {
+			ieee80211_free_chanctx(sdata->local, ctx);
+		}
+	}
 
 	return 0;
 }
@@ -911,39 +941,71 @@ int ieee80211_vif_reserve_chanctx(struct ieee80211_sub_if_data *sdata,
 {
 	struct ieee80211_local *local = sdata->local;
 	struct ieee80211_chanctx_conf *conf;
-	struct ieee80211_chanctx *new_ctx, *curr_ctx;
-	int ret = 0;
+	struct ieee80211_chanctx *new_ctx, *curr_ctx, *ctx;
 
-	mutex_lock(&local->chanctx_mtx);
+	lockdep_assert_held(&local->chanctx_mtx);
+
+	if (local->use_chanctx && !local->ops->switch_vif_chanctx)
+		return -ENOTSUPP;
 
 	conf = rcu_dereference_protected(sdata->vif.chanctx_conf,
 					 lockdep_is_held(&local->chanctx_mtx));
-	if (!conf) {
-		ret = -EINVAL;
-		goto out;
-	}
+	if (!conf)
+		return -EINVAL;
 
 	curr_ctx = container_of(conf, struct ieee80211_chanctx, conf);
 
 	new_ctx = ieee80211_find_reservation_chanctx(local, chandef, mode);
 	if (!new_ctx) {
-		if (ieee80211_chanctx_refcount(local, curr_ctx) == 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 if (ieee80211_can_create_new_chanctx(local)) {
-			/* create a new context and reserve it */
+		if (ieee80211_can_create_new_chanctx(local)) {
 			new_ctx = ieee80211_new_chanctx(local, chandef, mode);
-			if (IS_ERR(new_ctx)) {
-				ret = PTR_ERR(new_ctx);
-				goto out;
-			}
+			if (IS_ERR(new_ctx))
+				return PTR_ERR(new_ctx);
 		} else {
-			ret = -EBUSY;
-			goto out;
+			if (curr_ctx->replaced_by ||
+			    !list_empty(&curr_ctx->reserved_vifs)) {
+				/*
+				 * Another vif already requested this context
+				 * for an in-place reservation. Find another
+				 * one hoping all vifs assigned to it will also
+				 * switch soon enough.
+				 *
+				 * TODO: This needs a little more work as some
+				 * cases may fail which could otherwise succeed
+				 * provided some channel context juggling was
+				 * performed.
+				 */
+				list_for_each_entry(ctx, &local->chanctx_list,
+						    list) {
+					if (ctx->replaced_by)
+						continue;
+
+					if (ctx->replaces)
+						continue;
+
+					if (!list_empty(&ctx->reserved_vifs))
+						continue;
+
+					curr_ctx = ctx;
+					break;
+				}
+			}
+
+			/*
+			 * If that's true then all available contexts are all
+			 * in-place reserved already.
+			 */
+			if (curr_ctx->replaced_by)
+				return -EBUSY;
+
+			new_ctx = ieee80211_alloc_chanctx(local, chandef, mode);
+			if (!new_ctx)
+				return -ENOMEM;
+
+			new_ctx->replaces = curr_ctx;
+			curr_ctx->replaced_by = new_ctx;
+
+			list_add_rcu(&new_ctx->list, &local->chanctx_list);
 		}
 	}
 
@@ -951,82 +1013,364 @@ int ieee80211_vif_reserve_chanctx(struct ieee80211_sub_if_data *sdata,
 	sdata->reserved_chanctx = new_ctx;
 	sdata->reserved_chandef = *chandef;
 	sdata->reserved_radar_required = radar_required;
-out:
-	mutex_unlock(&local->chanctx_mtx);
-	return ret;
+	sdata->reserved_ready = false;
+
+	return 0;
 }
 
-int ieee80211_vif_use_reserved_context(struct ieee80211_sub_if_data *sdata,
-				       u32 *changed)
+static int
+ieee80211_vif_use_reserved_assign(struct ieee80211_sub_if_data *sdata)
 {
 	struct ieee80211_local *local = sdata->local;
-	struct ieee80211_chanctx *ctx;
-	struct ieee80211_chanctx *old_ctx;
-	struct ieee80211_chanctx_conf *conf;
-	int ret;
-	u32 tmp_changed = *changed;
-
-	/* TODO: need to recheck if the chandef is usable etc.? */
+	struct ieee80211_vif_chanctx_switch vif_chsw[1] = {};
+	struct ieee80211_chanctx *old_ctx, *new_ctx;
+	const struct cfg80211_chan_def *chandef;
+	u32 changed = 0;
+	int err;
 
 	lockdep_assert_held(&local->mtx);
+	lockdep_assert_held(&local->chanctx_mtx);
 
-	mutex_lock(&local->chanctx_mtx);
+	new_ctx = sdata->reserved_chanctx;
+	old_ctx = ieee80211_vif_get_chanctx(sdata);
 
-	ctx = sdata->reserved_chanctx;
-	if (WARN_ON(!ctx)) {
-		ret = -EINVAL;
-		goto out;
-	}
+	if (WARN_ON(!sdata->reserved_ready))
+		return -EBUSY;
 
-	conf = rcu_dereference_protected(sdata->vif.chanctx_conf,
-					 lockdep_is_held(&local->chanctx_mtx));
-	if (!conf) {
-		ret = -EINVAL;
-		goto out;
+	if (WARN_ON(!new_ctx))
+		return -EINVAL;
+
+	if (WARN_ON(!old_ctx))
+		return -EINVAL;
+
+	if (WARN_ON(new_ctx->replaced_by))
+		return -EINVAL;
+
+	if (WARN_ON(old_ctx->replaced_by && new_ctx->replaces))
+		return -EINVAL;
+
+	/*
+	 * If target context is in progress of an in-place switching then defer
+	 * the work. This function will be called later again when the target
+	 * context is ready.
+	 */
+	if (new_ctx->replaces)
+		return -EAGAIN;
+
+	chandef = ieee80211_chanctx_non_reserved_chandef(local, new_ctx,
+				&sdata->reserved_chandef);
+	if (WARN_ON(!chandef))
+		return -EINVAL;
+
+	vif_chsw[0].vif = &sdata->vif;
+	vif_chsw[0].old_ctx = &old_ctx->conf;
+	vif_chsw[0].new_ctx = &new_ctx->conf;
+
+	list_del(&sdata->reserved_chanctx_list);
+	sdata->reserved_chanctx = NULL;
+
+	err = drv_switch_vif_chanctx(local, vif_chsw, 1,
+				     CHANCTX_SWMODE_REASSIGN_VIF);
+	if (err) {
+		if (ieee80211_chanctx_refcount(local, new_ctx) == 0)
+			ieee80211_free_chanctx(local, new_ctx);
+
+		return err;
 	}
 
-	old_ctx = container_of(conf, struct ieee80211_chanctx, conf);
+	list_move(&sdata->assigned_chanctx_list, &new_ctx->assigned_vifs);
+
+	if (sdata->vif.type == NL80211_IFTYPE_AP)
+		__ieee80211_vif_copy_chanctx_to_vlans(sdata, false);
+
+	if (ieee80211_chanctx_refcount(local, old_ctx) == 0)
+		ieee80211_free_chanctx(local, old_ctx);
 
 	if (sdata->vif.bss_conf.chandef.width != sdata->reserved_chandef.width)
-		tmp_changed |= BSS_CHANGED_BANDWIDTH;
+		changed = BSS_CHANGED_BANDWIDTH;
 
 	sdata->vif.bss_conf.chandef = sdata->reserved_chandef;
 
-	/* unref our reservation */
-	sdata->reserved_chanctx = NULL;
-	sdata->radar_required = sdata->reserved_radar_required;
-	list_del(&sdata->reserved_chanctx_list);
+	if (changed)
+		ieee80211_bss_info_change_notify(sdata, changed);
 
-	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;
+	return err;
+}
+
+static bool
+ieee80211_vif_has_in_place_reservation(struct ieee80211_sub_if_data *sdata)
+{
+	struct ieee80211_chanctx *old_ctx, *new_ctx;
+
+	lockdep_assert_held(&sdata->local->chanctx_mtx);
+
+	new_ctx = sdata->reserved_chanctx;
+	old_ctx = ieee80211_vif_get_chanctx(sdata);
+
+	if (WARN_ON(!old_ctx))
+		return false;
+
+	if (WARN_ON(!new_ctx))
+		return false;
+
+	if (!old_ctx->replaced_by)
+		return false;
+
+	if (!new_ctx->replaces)
+		return false;
+
+	return true;
+}
+
+static int
+ieee80211_vif_use_reserved_switch(struct ieee80211_local *local)
+{
+	struct ieee80211_sub_if_data *sdata, *sdata_tmp;
+	struct ieee80211_chanctx *new_ctx = NULL, *ctx, *ctx_tmp;
+	struct ieee80211_vif_chanctx_switch *vif_chsw = NULL;
+	const struct cfg80211_chan_def *chandef;
+	int i, err, n_ctx = 0, n_vifs = 0;
+
+	lockdep_assert_held(&local->mtx);
+	lockdep_assert_held(&local->chanctx_mtx);
+
+	/*
+	 * If there are 2 independant pairs of channel contexts performing
+	 * cross-switch of their vifs this code will still wait until both are
+	 * ready even though it could be possible to switch one before the
+	 * other is ready.
+	 *
+	 * For practical reasons and code simplicity just do a single huge
+	 * switch.
+	 */
+
+	list_for_each_entry(ctx, &local->chanctx_list, list) {
+		if (!(ctx->replaces && !ctx->replaced_by))
+			continue;
+
+		if (!local->use_chanctx)
+			new_ctx = ctx;
+
+		n_ctx++;
+
+		list_for_each_entry(sdata, &ctx->replaces->assigned_vifs,
+				    assigned_chanctx_list) {
+			if (!sdata->reserved_chanctx) {
+				wiphy_info(local->hw.wiphy,
+					   "channel context reservation cannot be finalized because some interfaces aren't switching\n");
+				err = -EBUSY;
+				goto err;
+			}
+
+			if (!sdata->reserved_ready)
+				return -EAGAIN;
+		}
+
+		list_for_each_entry(sdata, &ctx->reserved_vifs,
+				    reserved_chanctx_list) {
+			if (!ieee80211_vif_has_in_place_reservation(sdata))
+				continue;
+
+			if (!sdata->reserved_ready)
+				return -EAGAIN;
+
+			n_vifs++;
+
+			if (sdata->reserved_radar_required)
+				ctx->conf.radar_enabled = true;
+		}
+	}
+
+	if (WARN_ON(n_ctx == 0) ||
+	    WARN_ON(n_vifs == 0) ||
+	    WARN_ON(n_ctx > 1 && !local->use_chanctx) ||
+	    WARN_ON(!new_ctx && !local->use_chanctx)) {
+		err = -EINVAL;
+		goto err;
+	}
+
+	if (local->use_chanctx) {
+		vif_chsw = kzalloc(sizeof(*vif_chsw) * n_vifs, GFP_KERNEL);
+		if (vif_chsw) {
+			err = -ENOMEM;
+			goto err;
+		}
+
+		i = 0;
+		list_for_each_entry(ctx, &local->chanctx_list, list) {
+			if (!(ctx->replaces && !ctx->replaced_by))
+				continue;
+
+			list_for_each_entry(sdata, &ctx->reserved_vifs,
+					    reserved_chanctx_list) {
+				if (!ieee80211_vif_has_in_place_reservation(
+						sdata))
+					continue;
+
+				vif_chsw[i].vif = &sdata->vif;
+				vif_chsw[i].old_ctx = &ctx->replaces->conf;
+				vif_chsw[i].new_ctx = &ctx->conf;
+
+				i++;
+			}
+		}
+
+		err = drv_switch_vif_chanctx(local, vif_chsw, n_vifs,
+					     CHANCTX_SWMODE_SWAP_CONTEXTS);
+		kfree(vif_chsw);
+
+		if (err)
+			goto err;
 	} else {
-		ret = ieee80211_assign_vif_chanctx(sdata, ctx);
-		if (ieee80211_chanctx_refcount(local, old_ctx) == 0)
-			ieee80211_free_chanctx(local, old_ctx);
-		if (ret) {
-			/* if assign fails refcount stays the same */
-			if (ieee80211_chanctx_refcount(local, ctx) == 0)
-				ieee80211_free_chanctx(local, ctx);
-			goto out;
+		chandef = ieee80211_chanctx_reserved_chandef(local, new_ctx,
+							     NULL);
+		if (WARN_ON(!chandef)) {
+			err = -EINVAL;
+			goto err;
 		}
 
-		if (sdata->vif.type == NL80211_IFTYPE_AP)
-			__ieee80211_vif_copy_chanctx_to_vlans(sdata, false);
+		local->hw.conf.radar_enabled = new_ctx->conf.radar_enabled;
+		local->_oper_chandef = *chandef;
+		ieee80211_hw_config(local, 0);
 	}
 
-	*changed = tmp_changed;
+	i = 0;
+	list_for_each_entry_safe(ctx, ctx_tmp, &local->chanctx_list, list) {
+		if (!(ctx->replaces && !ctx->replaced_by))
+			continue;
 
-	ieee80211_recalc_chanctx_chantype(local, ctx);
-	ieee80211_recalc_smps_chanctx(local, ctx);
-	ieee80211_recalc_radar_chanctx(local, ctx);
-	ieee80211_recalc_chanctx_min_def(local, ctx);
-out:
-	mutex_unlock(&local->chanctx_mtx);
-	return ret;
+		list_for_each_entry(sdata, &ctx->reserved_vifs,
+				    reserved_chanctx_list) {
+			u32 changed = 0;
+
+			if (!ieee80211_vif_has_in_place_reservation(sdata))
+				continue;
+
+			rcu_assign_pointer(sdata->vif.chanctx_conf, &ctx->conf);
+
+			if (sdata->vif.type == NL80211_IFTYPE_AP)
+				__ieee80211_vif_copy_chanctx_to_vlans(sdata,
+								      false);
+
+			sdata->radar_required = sdata->reserved_radar_required;
+
+			if (sdata->vif.bss_conf.chandef.width !=
+			    sdata->reserved_chandef.width)
+				changed = BSS_CHANGED_BANDWIDTH;
+
+			sdata->vif.bss_conf.chandef = sdata->reserved_chandef;
+			if (changed)
+				ieee80211_bss_info_change_notify(sdata,
+								 changed);
+
+			ieee80211_recalc_txpower(sdata);
+		}
+
+		ieee80211_recalc_chanctx_chantype(local, ctx);
+		ieee80211_recalc_smps_chanctx(local, ctx);
+		ieee80211_recalc_radar_chanctx(local, ctx);
+		ieee80211_recalc_chanctx_min_def(local, ctx);
+
+		list_for_each_entry_safe(sdata, sdata_tmp, &ctx->reserved_vifs,
+					 reserved_chanctx_list) {
+			list_del(&sdata->reserved_chanctx_list);
+			list_move(&sdata->assigned_chanctx_list,
+				  &new_ctx->assigned_vifs);
+			sdata->reserved_chanctx = NULL;
+		}
+
+		list_del_rcu(&ctx->replaces->list);
+		kfree_rcu(ctx->replaces, rcu_head);
+		ctx->replaces = NULL;
+
+		/*
+		 * Some simple reservations depending on the in-place switch
+		 * might've been ready before and were deferred. Retry them now
+		 * but don't propagate the error up the call stack as the
+		 * directly requested reservation has been already handled
+		 * successfully at this point.
+		 */
+		list_for_each_entry(sdata, &ctx->reserved_vifs,
+				    reserved_chanctx_list) {
+			if (WARN_ON(ieee80211_vif_has_in_place_reservation(
+					sdata)))
+				continue;
+
+			if (!sdata->reserved_ready)
+				continue;
+
+			err = ieee80211_vif_use_reserved_assign(sdata);
+			if (WARN_ON(err && err != -EAGAIN)) {
+				sdata_info(sdata,
+					   "failed to finalize reservation (err=%d)\n",
+					   err);
+				ieee80211_vif_unreserve_chanctx(sdata);
+				cfg80211_stop_iface(local->hw.wiphy,
+						    &sdata->wdev,
+						    GFP_KERNEL);
+			}
+		}
+	}
+
+	return 0;
+
+err:
+	list_for_each_entry(ctx, &local->chanctx_list, list) {
+		if (!(ctx->replaces && !ctx->replaced_by))
+			continue;
+
+		list_for_each_entry_safe(sdata, sdata_tmp, &ctx->reserved_vifs,
+					 reserved_chanctx_list)
+			ieee80211_vif_unreserve_chanctx(sdata);
+	}
+
+	return err;
+}
+
+int ieee80211_vif_use_reserved_context(struct ieee80211_sub_if_data *sdata)
+{
+	struct ieee80211_local *local = sdata->local;
+	struct ieee80211_chanctx *new_ctx;
+	struct ieee80211_chanctx *old_ctx;
+	int err;
+
+	lockdep_assert_held(&local->mtx);
+	lockdep_assert_held(&local->chanctx_mtx);
+
+	new_ctx = sdata->reserved_chanctx;
+	old_ctx = ieee80211_vif_get_chanctx(sdata);
+
+	if (WARN_ON(!new_ctx))
+		return -EINVAL;
+
+	if (WARN_ON(!old_ctx))
+		return -EINVAL;
+
+	if (WARN_ON(sdata->reserved_ready))
+		return -EINVAL;
+
+	sdata->reserved_ready = true;
+
+	if (!(old_ctx->replaced_by && new_ctx->replaces)) {
+		err = ieee80211_vif_use_reserved_assign(sdata);
+		if (err && err != -EAGAIN)
+			return err;
+	}
+
+	/*
+	 * In-place reservation may need to be finalized now either if:
+	 *  - sdata is taking part in the swapping itself and is the last one
+	 *  - sdata has switched with a simple reservation to an existing
+	 *    context readying the in-place switching old_ctx
+	 */
+
+	if (old_ctx->replaced_by || new_ctx->replaces) {
+		err = ieee80211_vif_use_reserved_switch(local);
+		if (err && err != -EAGAIN)
+			return err;
+	}
+
+	return 0;
 }
 
 int ieee80211_vif_change_bandwidth(struct ieee80211_sub_if_data *sdata,
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 998c18c..1acb879 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -707,6 +707,9 @@ struct ieee80211_chanctx {
 	struct list_head assigned_vifs;
 	struct list_head reserved_vifs;
 
+	struct ieee80211_chanctx *replaces;
+	struct ieee80211_chanctx *replaced_by;
+
 	enum ieee80211_chanctx_mode mode;
 	bool driver_present;
 
@@ -777,6 +780,7 @@ struct ieee80211_sub_if_data {
 	struct ieee80211_chanctx *reserved_chanctx;
 	struct cfg80211_chan_def reserved_chandef;
 	bool reserved_radar_required;
+	bool reserved_ready;
 
 	/* used to reconfigure hardware SM PS */
 	struct work_struct recalc_smps;
@@ -1800,8 +1804,7 @@ ieee80211_vif_reserve_chanctx(struct ieee80211_sub_if_data *sdata,
 			      enum ieee80211_chanctx_mode mode,
 			      bool radar_required);
 int __must_check
-ieee80211_vif_use_reserved_context(struct ieee80211_sub_if_data *sdata,
-				   u32 *changed);
+ieee80211_vif_use_reserved_context(struct ieee80211_sub_if_data *sdata);
 int ieee80211_vif_unreserve_chanctx(struct ieee80211_sub_if_data *sdata);
 
 int __must_check
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index 7e0dd4b..6615044 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -1603,7 +1603,8 @@ int ieee80211_reconfig(struct ieee80211_local *local)
 	if (local->use_chanctx) {
 		mutex_lock(&local->chanctx_mtx);
 		list_for_each_entry(ctx, &local->chanctx_list, list)
-			WARN_ON(drv_add_chanctx(local, ctx));
+			if (!ctx->replaces)
+				WARN_ON(drv_add_chanctx(local, ctx));
 		mutex_unlock(&local->chanctx_mtx);
 
 		list_for_each_entry(sdata, &local->interfaces, list) {
@@ -2876,6 +2877,8 @@ int ieee80211_check_combinations(struct ieee80211_sub_if_data *sdata,
 		num[iftype] = 1;
 
 	list_for_each_entry(ctx, &local->chanctx_list, list) {
+		if (ctx->replaced_by)
+			continue;
 		if (ctx->conf.radar_enabled)
 			radar_detect |= BIT(ctx->conf.def.width);
 		if (ctx->mode == IEEE80211_CHANCTX_EXCLUSIVE) {
@@ -2934,6 +2937,9 @@ int ieee80211_max_num_channels(struct ieee80211_local *local)
 	lockdep_assert_held(&local->chanctx_mtx);
 
 	list_for_each_entry(ctx, &local->chanctx_list, list) {
+		if (ctx->replaced_by)
+			continue;
+
 		num_different_channels++;
 
 		if (ctx->conf.radar_enabled)
-- 
1.8.5.3


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [PATCH v7 2/5] mac80211: make check_combinations() aware of chanctx reservation
  2014-05-29  7:34 [PATCH v7 0/5] cfg/mac80211: implement multi-vif csa Michal Kazior
  2014-05-29  7:34 ` [PATCH v7 1/5] mac80211: implement multi-vif in-place reservations Michal Kazior
@ 2014-05-29  7:34 ` Michal Kazior
  2014-05-29  7:34 ` [PATCH v7 3/5] mac80211: use chanctx reservation for AP CSA Michal Kazior
                   ` (4 subsequent siblings)
  6 siblings, 0 replies; 40+ messages in thread
From: Michal Kazior @ 2014-05-29  7:34 UTC (permalink / raw
  To: linux-wireless; +Cc: luca, johannes, Michal Kazior

The ieee80211_check_combinations() computes
radar_detect accordingly depending on chanctx
reservation status.

This makes it possible to use the function for
channel_switch validation.

Signed-off-by: Michal Kazior <michal.kazior@tieto.com>
---

Notes:
    v3:
     * fix typo in comment [Johannes]
     * fix comment style [Johannes]
    
    v7:
     * use replaces/replaced_by pointers
     * replace radar_detect calculation in ieee80211_max_num_channels() too

 net/mac80211/util.c | 34 ++++++++++++++++++++++++++++++----
 1 file changed, 30 insertions(+), 4 deletions(-)

diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index 6615044..b2f7bf9 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -2836,6 +2836,34 @@ void ieee80211_recalc_dtim(struct ieee80211_local *local,
 	ps->dtim_count = dtim_count;
 }
 
+static u8 ieee80211_chanctx_radar_detect(struct ieee80211_local *local,
+					 struct ieee80211_chanctx *ctx)
+{
+	struct ieee80211_sub_if_data *sdata;
+	u8 radar_detect = 0;
+
+	lockdep_assert_held(&local->chanctx_mtx);
+
+	if (WARN_ON(ctx->replaced_by))
+		return 0;
+
+	list_for_each_entry(sdata, &ctx->reserved_vifs, reserved_chanctx_list)
+		if (sdata->reserved_radar_required)
+			radar_detect |= BIT(sdata->reserved_chandef.width);
+
+	/*
+	 * An in-place reservation context should not have any assigned vifs
+	 * until it replaces the other context.
+	 */
+	WARN_ON(ctx->replaces && !list_empty(&ctx->assigned_vifs));
+
+	list_for_each_entry(sdata, &ctx->assigned_vifs, assigned_chanctx_list)
+		if (sdata->radar_required)
+			radar_detect |= BIT(sdata->vif.bss_conf.chandef.width);
+
+	return radar_detect;
+}
+
 int ieee80211_check_combinations(struct ieee80211_sub_if_data *sdata,
 				 const struct cfg80211_chan_def *chandef,
 				 enum ieee80211_chanctx_mode chanmode,
@@ -2879,8 +2907,7 @@ int ieee80211_check_combinations(struct ieee80211_sub_if_data *sdata,
 	list_for_each_entry(ctx, &local->chanctx_list, list) {
 		if (ctx->replaced_by)
 			continue;
-		if (ctx->conf.radar_enabled)
-			radar_detect |= BIT(ctx->conf.def.width);
+		radar_detect |= ieee80211_chanctx_radar_detect(local, ctx);
 		if (ctx->mode == IEEE80211_CHANCTX_EXCLUSIVE) {
 			num_different_channels++;
 			continue;
@@ -2942,8 +2969,7 @@ int ieee80211_max_num_channels(struct ieee80211_local *local)
 
 		num_different_channels++;
 
-		if (ctx->conf.radar_enabled)
-			radar_detect |= BIT(ctx->conf.def.width);
+		radar_detect |= ieee80211_chanctx_radar_detect(local, ctx);
 	}
 
 	list_for_each_entry_rcu(sdata, &local->interfaces, list)
-- 
1.8.5.3


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [PATCH v7 3/5] mac80211: use chanctx reservation for AP CSA
  2014-05-29  7:34 [PATCH v7 0/5] cfg/mac80211: implement multi-vif csa Michal Kazior
  2014-05-29  7:34 ` [PATCH v7 1/5] mac80211: implement multi-vif in-place reservations Michal Kazior
  2014-05-29  7:34 ` [PATCH v7 2/5] mac80211: make check_combinations() aware of chanctx reservation Michal Kazior
@ 2014-05-29  7:34 ` Michal Kazior
  2014-06-03 19:49   ` Johannes Berg
  2014-05-29  7:34 ` [PATCH v7 4/5] mac80211: use chanctx reservation for STA CSA Michal Kazior
                   ` (3 subsequent siblings)
  6 siblings, 1 reply; 40+ messages in thread
From: Michal Kazior @ 2014-05-29  7:34 UTC (permalink / raw
  To: linux-wireless; +Cc: luca, johannes, Michal Kazior

Channel switch finalization is now 2-step. First
step is when driver calls csa_finish(), the other
is when reservation is actually finalized (which
can be deferred for in-place reservation).

It is now safe to call ieee80211_csa_finish() more
then once.

Signed-off-by: Michal Kazior <michal.kazior@tieto.com>
---

Notes:
    v3:
     * fix lockdep typo s/mtx/chanctx_mtx/ [Johannes]
     * fix comment style [Johannes]
     * use goto for cleaner unlocking/returning [Johannes]
     * squash with ieee80211_vif_change_channel() removal patch [Johannes]
     * fix commit message [Johannes]
     * add resilience for multiple ieee80211_csa_finish() calls
    
    v4:
     * split removal of ieee80211_vif_change_channel()
    
    v7:
     * tune commit message
     * put the initial ieee80211_vif_chanctx_reservation_complete()
       implementation here [Johannes]
     * remove 'default' case and use all iftypes explicitly and WARN_ON

 net/mac80211/cfg.c  | 82 +++++++++++++++++++++++++++++++++++------------------
 net/mac80211/chan.c | 34 ++++++++++++++++++++--
 2 files changed, 87 insertions(+), 29 deletions(-)

diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 0fa37d8..53401d5 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -3126,17 +3126,35 @@ static int __ieee80211_csa_finalize(struct ieee80211_sub_if_data *sdata)
 
 	sdata_assert_lock(sdata);
 	lockdep_assert_held(&local->mtx);
+	lockdep_assert_held(&local->chanctx_mtx);
 
-	sdata->radar_required = sdata->csa_radar_required;
-	err = ieee80211_vif_change_channel(sdata, &changed);
-	if (err < 0)
-		return err;
+	/*
+	 * using reservation isn't immediate as it may be deferred until later
+	 * with multi-vif. once reservation is complete it will re-schedule the
+	 * work with no reserved_chanctx so verify chandef to check if it
+	 * completed successfully
+	 */
 
-	if (!local->use_chanctx) {
-		local->_oper_chandef = sdata->csa_chandef;
-		ieee80211_hw_config(local, 0);
+	if (sdata->reserved_chanctx) {
+		/*
+		 * with multi-vif csa driver may call ieee80211_csa_finish()
+		 * many times while waiting for other interfaces to use their
+		 * reservations
+		 */
+		if (sdata->reserved_ready)
+			return 0;
+
+		err = ieee80211_vif_use_reserved_context(sdata);
+		if (err)
+			return err;
+
+		return 0;
 	}
 
+	if (!cfg80211_chandef_identical(&sdata->vif.bss_conf.chandef,
+					&sdata->csa_chandef))
+		return -EINVAL;
+
 	sdata->vif.csa_active = false;
 
 	err = ieee80211_set_after_csa_beacon(sdata, &changed);
@@ -3172,6 +3190,7 @@ void ieee80211_csa_finalize_work(struct work_struct *work)
 
 	sdata_lock(sdata);
 	mutex_lock(&local->mtx);
+	mutex_lock(&local->chanctx_mtx);
 
 	/* AP might have been stopped while waiting for the lock. */
 	if (!sdata->vif.csa_active)
@@ -3183,6 +3202,7 @@ void ieee80211_csa_finalize_work(struct work_struct *work)
 	ieee80211_csa_finalize(sdata);
 
 unlock:
+	mutex_unlock(&local->chanctx_mtx);
 	mutex_unlock(&local->mtx);
 	sdata_unlock(sdata);
 }
@@ -3328,7 +3348,7 @@ __ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
 	struct ieee80211_local *local = sdata->local;
 	struct ieee80211_chanctx_conf *conf;
 	struct ieee80211_chanctx *chanctx;
-	int err, num_chanctx, changed = 0;
+	int err, changed = 0;
 
 	sdata_assert_lock(sdata);
 	lockdep_assert_held(&local->mtx);
@@ -3343,37 +3363,43 @@ __ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
 				       &sdata->vif.bss_conf.chandef))
 		return -EINVAL;
 
+	/* don't allow another channel switch if one is already active. */
+	if (sdata->vif.csa_active)
+		return -EBUSY;
+
 	mutex_lock(&local->chanctx_mtx);
 	conf = rcu_dereference_protected(sdata->vif.chanctx_conf,
 					 lockdep_is_held(&local->chanctx_mtx));
 	if (!conf) {
-		mutex_unlock(&local->chanctx_mtx);
-		return -EBUSY;
+		err = -EBUSY;
+		goto out;
 	}
 
-	/* don't handle for multi-VIF cases */
 	chanctx = container_of(conf, struct ieee80211_chanctx, conf);
-	if (ieee80211_chanctx_refcount(local, chanctx) > 1) {
-		mutex_unlock(&local->chanctx_mtx);
-		return -EBUSY;
+	if (!chanctx) {
+		err = -EBUSY;
+		goto out;
 	}
-	num_chanctx = 0;
-	list_for_each_entry_rcu(chanctx, &local->chanctx_list, list)
-		num_chanctx++;
-	mutex_unlock(&local->chanctx_mtx);
 
-	if (num_chanctx > 1)
-		return -EBUSY;
+	err = ieee80211_vif_reserve_chanctx(sdata, &params->chandef,
+					    chanctx->mode,
+					    params->radar_required);
+	if (err)
+		goto out;
 
-	/* don't allow another channel switch if one is already active. */
-	if (sdata->vif.csa_active)
-		return -EBUSY;
+	/* if reservation is invalid then this will fail */
+	err = ieee80211_check_combinations(sdata, NULL, chanctx->mode, 0);
+	if (err) {
+		ieee80211_vif_unreserve_chanctx(sdata);
+		goto out;
+	}
 
 	err = ieee80211_set_csa_beacon(sdata, params, &changed);
-	if (err)
-		return err;
+	if (err) {
+		ieee80211_vif_unreserve_chanctx(sdata);
+		goto out;
+	}
 
-	sdata->csa_radar_required = params->radar_required;
 	sdata->csa_chandef = params->chandef;
 	sdata->csa_block_tx = params->block_tx;
 	sdata->vif.csa_active = true;
@@ -3391,7 +3417,9 @@ __ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
 		ieee80211_csa_finalize(sdata);
 	}
 
-	return 0;
+out:
+	mutex_unlock(&local->chanctx_mtx);
+	return err;
 }
 
 int ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
index 01379a17..2ab0c90 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -1018,6 +1018,30 @@ int ieee80211_vif_reserve_chanctx(struct ieee80211_sub_if_data *sdata,
 	return 0;
 }
 
+static void
+ieee80211_vif_chanctx_reservation_complete(struct ieee80211_sub_if_data *sdata)
+{
+	switch (sdata->vif.type) {
+	case NL80211_IFTYPE_ADHOC:
+	case NL80211_IFTYPE_AP:
+	case NL80211_IFTYPE_MESH_POINT:
+		ieee80211_queue_work(&sdata->local->hw,
+				     &sdata->csa_finalize_work);
+		break;
+	case NL80211_IFTYPE_UNSPECIFIED:
+	case NL80211_IFTYPE_STATION:
+	case NL80211_IFTYPE_AP_VLAN:
+	case NL80211_IFTYPE_WDS:
+	case NL80211_IFTYPE_MONITOR:
+	case NL80211_IFTYPE_P2P_CLIENT:
+	case NL80211_IFTYPE_P2P_GO:
+	case NL80211_IFTYPE_P2P_DEVICE:
+	case NUM_NL80211_IFTYPES:
+		WARN_ON(1);
+		break;
+	}
+}
+
 static int
 ieee80211_vif_use_reserved_assign(struct ieee80211_sub_if_data *sdata)
 {
@@ -1075,7 +1099,7 @@ ieee80211_vif_use_reserved_assign(struct ieee80211_sub_if_data *sdata)
 		if (ieee80211_chanctx_refcount(local, new_ctx) == 0)
 			ieee80211_free_chanctx(local, new_ctx);
 
-		return err;
+		goto out;
 	}
 
 	list_move(&sdata->assigned_chanctx_list, &new_ctx->assigned_vifs);
@@ -1094,6 +1118,8 @@ ieee80211_vif_use_reserved_assign(struct ieee80211_sub_if_data *sdata)
 	if (changed)
 		ieee80211_bss_info_change_notify(sdata, changed);
 
+out:
+	ieee80211_vif_chanctx_reservation_complete(sdata);
 	return err;
 }
 
@@ -1277,6 +1303,8 @@ ieee80211_vif_use_reserved_switch(struct ieee80211_local *local)
 			list_move(&sdata->assigned_chanctx_list,
 				  &new_ctx->assigned_vifs);
 			sdata->reserved_chanctx = NULL;
+
+			ieee80211_vif_chanctx_reservation_complete(sdata);
 		}
 
 		list_del_rcu(&ctx->replaces->list);
@@ -1320,8 +1348,10 @@ err:
 			continue;
 
 		list_for_each_entry_safe(sdata, sdata_tmp, &ctx->reserved_vifs,
-					 reserved_chanctx_list)
+					 reserved_chanctx_list) {
 			ieee80211_vif_unreserve_chanctx(sdata);
+			ieee80211_vif_chanctx_reservation_complete(sdata);
+		}
 	}
 
 	return err;
-- 
1.8.5.3


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [PATCH v7 4/5] mac80211: use chanctx reservation for STA CSA
  2014-05-29  7:34 [PATCH v7 0/5] cfg/mac80211: implement multi-vif csa Michal Kazior
                   ` (2 preceding siblings ...)
  2014-05-29  7:34 ` [PATCH v7 3/5] mac80211: use chanctx reservation for AP CSA Michal Kazior
@ 2014-05-29  7:34 ` Michal Kazior
  2014-05-29  7:34 ` [PATCH v7 5/5] cfg80211: remove channel_switch combination check Michal Kazior
                   ` (2 subsequent siblings)
  6 siblings, 0 replies; 40+ messages in thread
From: Michal Kazior @ 2014-05-29  7:34 UTC (permalink / raw
  To: linux-wireless; +Cc: luca, johannes, Michal Kazior

Channel switch finalization is now 2-step. First
step is when driver calls chswitch_done(), the
other is when reservation is actually finalized
(which be defered for in-place reservation).

It is now safe to call ieee80211_chswitch_done()
more than once.

Also remove the ieee80211_vif_change_channel()
because it is no longer used.

Signed-off-by: Michal Kazior <michal.kazior@tieto.com>
---

Notes:
    v3:
     * fix comment style [Johannes]
     * add resilience for multiple ieee80211_chswitch_done() calls
    
    v3:
     * fix lockdep typo s/mtx/chanctx_mtx/ [Johannes]
     * fix comment style [Johannes]
     * use goto for cleaner unlocking/returning [Johannes]
     * squash with ieee80211_vif_change_channel() removal patch [Johannes]
     * fix commit message [Johannes]
     * add resilience for multiple ieee80211_csa_finish() calls
    
    v4:
     * squash with removal of ieee80211_vif_change_channel()
    
    v7:
     * add local->mtx locking when reserving (for ieee80211_chanctx_alloc)

 net/mac80211/chan.c        | 69 ++------------------------------
 net/mac80211/ieee80211_i.h |  5 ---
 net/mac80211/mlme.c        | 99 ++++++++++++++++++++++++++++++----------------
 3 files changed, 69 insertions(+), 104 deletions(-)

diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
index 2ab0c90..4d6b8a0 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -802,70 +802,6 @@ int ieee80211_vif_use_channel(struct ieee80211_sub_if_data *sdata,
 	return ret;
 }
 
-static int __ieee80211_vif_change_channel(struct ieee80211_sub_if_data *sdata,
-					  struct ieee80211_chanctx *ctx,
-					  u32 *changed)
-{
-	struct ieee80211_local *local = sdata->local;
-	const struct cfg80211_chan_def *chandef = &sdata->csa_chandef;
-	u32 chanctx_changed = 0;
-
-	if (!cfg80211_chandef_usable(sdata->local->hw.wiphy, chandef,
-				     IEEE80211_CHAN_DISABLED))
-		return -EINVAL;
-
-	if (ieee80211_chanctx_refcount(local, ctx) != 1)
-		return -EINVAL;
-
-	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);
-
-	ieee80211_recalc_chanctx_chantype(local, ctx);
-	ieee80211_recalc_smps_chanctx(local, ctx);
-	ieee80211_recalc_radar_chanctx(local, ctx);
-	ieee80211_recalc_chanctx_min_def(local, ctx);
-
-	return 0;
-}
-
-int ieee80211_vif_change_channel(struct ieee80211_sub_if_data *sdata,
-				 u32 *changed)
-{
-	struct ieee80211_local *local = sdata->local;
-	struct ieee80211_chanctx_conf *conf;
-	struct ieee80211_chanctx *ctx;
-	int ret;
-
-	lockdep_assert_held(&local->mtx);
-
-	/* should never be called if not performing a channel switch. */
-	if (WARN_ON(!sdata->vif.csa_active))
-		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);
-
-	ret = __ieee80211_vif_change_channel(sdata, ctx, changed);
- out:
-	mutex_unlock(&local->chanctx_mtx);
-	return ret;
-}
-
 static void
 __ieee80211_vif_copy_chanctx_to_vlans(struct ieee80211_sub_if_data *sdata,
 				      bool clear)
@@ -1028,8 +964,11 @@ ieee80211_vif_chanctx_reservation_complete(struct ieee80211_sub_if_data *sdata)
 		ieee80211_queue_work(&sdata->local->hw,
 				     &sdata->csa_finalize_work);
 		break;
-	case NL80211_IFTYPE_UNSPECIFIED:
 	case NL80211_IFTYPE_STATION:
+		ieee80211_queue_work(&sdata->local->hw,
+				     &sdata->u.mgd.chswitch_work);
+		break;
+	case NL80211_IFTYPE_UNSPECIFIED:
 	case NL80211_IFTYPE_AP_VLAN:
 	case NL80211_IFTYPE_WDS:
 	case NL80211_IFTYPE_MONITOR:
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 1acb879..3c75b1f 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -769,7 +769,6 @@ struct ieee80211_sub_if_data {
 	struct mac80211_qos_map __rcu *qos_map;
 
 	struct work_struct csa_finalize_work;
-	bool csa_radar_required;
 	bool csa_block_tx; /* write-protected by sdata_lock and local->mtx */
 	struct cfg80211_chan_def csa_chandef;
 
@@ -1811,10 +1810,6 @@ 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,
-			     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,
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index 7f073ef..baea348 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -940,51 +940,69 @@ static void ieee80211_chswitch_work(struct work_struct *work)
 		container_of(work, struct ieee80211_sub_if_data, u.mgd.chswitch_work);
 	struct ieee80211_local *local = sdata->local;
 	struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
-	u32 changed = 0;
 	int ret;
 
 	if (!ieee80211_sdata_running(sdata))
 		return;
 
 	sdata_lock(sdata);
+	mutex_lock(&local->mtx);
+	mutex_lock(&local->chanctx_mtx);
+
 	if (!ifmgd->associated)
 		goto out;
 
-	mutex_lock(&local->mtx);
-	ret = ieee80211_vif_change_channel(sdata, &changed);
-	mutex_unlock(&local->mtx);
-	if (ret) {
+	if (!sdata->vif.csa_active)
+		goto out;
+
+	/*
+	 * using reservation isn't immediate as it may be deferred until later
+	 * with multi-vif. once reservation is complete it will re-schedule the
+	 * work with no reserved_chanctx so verify chandef to check if it
+	 * completed successfully
+	 */
+
+	if (sdata->reserved_chanctx) {
+		/*
+		 * with multi-vif csa driver may call ieee80211_csa_finish()
+		 * many times while waiting for other interfaces to use their
+		 * reservations
+		 */
+		if (sdata->reserved_ready)
+			goto out;
+
+		ret = ieee80211_vif_use_reserved_context(sdata);
+		if (ret) {
+			sdata_info(sdata,
+				   "failed to use reserved channel context, disconnecting (err=%d)\n",
+				   ret);
+			ieee80211_queue_work(&sdata->local->hw,
+					     &ifmgd->csa_connection_drop_work);
+			goto out;
+		}
+
+		goto out;
+	}
+
+	if (!cfg80211_chandef_identical(&sdata->vif.bss_conf.chandef,
+					&sdata->csa_chandef)) {
 		sdata_info(sdata,
-			   "vif channel switch failed, disconnecting\n");
+			   "failed to finalize channel switch, disconnecting\n");
 		ieee80211_queue_work(&sdata->local->hw,
 				     &ifmgd->csa_connection_drop_work);
 		goto out;
 	}
 
-	if (!local->use_chanctx) {
-		local->_oper_chandef = sdata->csa_chandef;
-		/* Call "hw_config" only if doing sw channel switch.
-		 * Otherwise update the channel directly
-		 */
-		if (!local->ops->channel_switch)
-			ieee80211_hw_config(local, 0);
-		else
-			local->hw.conf.chandef = local->_oper_chandef;
-	}
-
 	/* XXX: shouldn't really modify cfg80211-owned data! */
 	ifmgd->associated->channel = sdata->csa_chandef.chan;
 
-	ieee80211_bss_info_change_notify(sdata, changed);
-
-	mutex_lock(&local->mtx);
 	sdata->vif.csa_active = false;
+
 	/* XXX: wait for a beacon first? */
 	if (!ieee80211_csa_needs_block_tx(local))
 		ieee80211_wake_queues_by_reason(&local->hw,
 					IEEE80211_MAX_QUEUE_MAP,
 					IEEE80211_QUEUE_STOP_REASON_CSA);
-	mutex_unlock(&local->mtx);
 
 	ifmgd->flags &= ~IEEE80211_STA_CSA_RECEIVED;
 
@@ -992,6 +1010,8 @@ static void ieee80211_chswitch_work(struct work_struct *work)
 	ieee80211_sta_reset_conn_monitor(sdata);
 
 out:
+	mutex_unlock(&local->chanctx_mtx);
+	mutex_unlock(&local->mtx);
 	sdata_unlock(sdata);
 }
 
@@ -1028,6 +1048,7 @@ ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,
 	struct ieee80211_local *local = sdata->local;
 	struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
 	struct cfg80211_bss *cbss = ifmgd->associated;
+	struct ieee80211_chanctx_conf *conf;
 	struct ieee80211_chanctx *chanctx;
 	enum ieee80211_band current_band;
 	struct ieee80211_csa_ie csa_ie;
@@ -1071,7 +1092,22 @@ ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,
 
 	ifmgd->flags |= IEEE80211_STA_CSA_RECEIVED;
 
+	mutex_lock(&local->mtx);
 	mutex_lock(&local->chanctx_mtx);
+	conf = rcu_dereference_protected(sdata->vif.chanctx_conf,
+					 lockdep_is_held(&local->chanctx_mtx));
+	if (!conf) {
+		sdata_info(sdata,
+			   "no channel context assigned to vif?, disconnecting\n");
+		ieee80211_queue_work(&local->hw,
+				     &ifmgd->csa_connection_drop_work);
+		mutex_unlock(&local->chanctx_mtx);
+		mutex_unlock(&local->mtx);
+		return;
+	}
+
+	chanctx = container_of(conf, struct ieee80211_chanctx, conf);
+
 	if (local->use_chanctx) {
 		u32 num_chanctx = 0;
 		list_for_each_entry(chanctx, &local->chanctx_list, list)
@@ -1084,32 +1120,27 @@ ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,
 			ieee80211_queue_work(&local->hw,
 					     &ifmgd->csa_connection_drop_work);
 			mutex_unlock(&local->chanctx_mtx);
+			mutex_unlock(&local->mtx);
 			return;
 		}
 	}
 
-	if (WARN_ON(!rcu_access_pointer(sdata->vif.chanctx_conf))) {
-		ieee80211_queue_work(&local->hw,
-				     &ifmgd->csa_connection_drop_work);
-		mutex_unlock(&local->chanctx_mtx);
-		return;
-	}
-	chanctx = container_of(rcu_access_pointer(sdata->vif.chanctx_conf),
-			       struct ieee80211_chanctx, conf);
-	if (ieee80211_chanctx_refcount(local, chanctx) > 1) {
+	res = ieee80211_vif_reserve_chanctx(sdata, &csa_ie.chandef,
+					    chanctx->mode, false);
+	if (res) {
 		sdata_info(sdata,
-			   "channel switch with multiple interfaces on the same channel, disconnecting\n");
+			   "failed to reserve channel context for channel switch, disconnecting (err=%d)\n",
+			   res);
 		ieee80211_queue_work(&local->hw,
 				     &ifmgd->csa_connection_drop_work);
 		mutex_unlock(&local->chanctx_mtx);
+		mutex_unlock(&local->mtx);
 		return;
 	}
 	mutex_unlock(&local->chanctx_mtx);
 
-	sdata->csa_chandef = csa_ie.chandef;
-
-	mutex_lock(&local->mtx);
 	sdata->vif.csa_active = true;
+	sdata->csa_chandef = csa_ie.chandef;
 	sdata->csa_block_tx = csa_ie.mode;
 
 	if (sdata->csa_block_tx)
-- 
1.8.5.3


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [PATCH v7 5/5] cfg80211: remove channel_switch combination check
  2014-05-29  7:34 [PATCH v7 0/5] cfg/mac80211: implement multi-vif csa Michal Kazior
                   ` (3 preceding siblings ...)
  2014-05-29  7:34 ` [PATCH v7 4/5] mac80211: use chanctx reservation for STA CSA Michal Kazior
@ 2014-05-29  7:34 ` Michal Kazior
  2014-06-03 19:51 ` [PATCH v7 0/5] cfg/mac80211: implement multi-vif csa Johannes Berg
  2014-06-05 12:56 ` [PATCH v8 " Michal Kazior
  6 siblings, 0 replies; 40+ messages in thread
From: Michal Kazior @ 2014-05-29  7:34 UTC (permalink / raw
  To: linux-wireless; +Cc: luca, johannes, Michal Kazior

Driver is now responsible for veryfing if the
switch is possible.

Since this is inherently tricky driver may decide
to disconnect an interface later with
cfg80211_stop_iface().

This doesn't mean driver can accept everything. It
should do it's best to verify requests and reject
them as soon as possible.

Signed-off-by: Michal Kazior <michal.kazior@tieto.com>
---

Notes:
    v7:
     * emphasize channel switching verification should not be
       taken lightly [Johannes]
     * update channel_switch description in cfg80211.h

 include/net/cfg80211.h |  7 ++++++-
 net/wireless/nl80211.c | 11 -----------
 2 files changed, 6 insertions(+), 12 deletions(-)

diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 920ec8c..c62d48e 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -2315,7 +2315,12 @@ struct cfg80211_qos_map {
  *	reliability. This operation can not fail.
  * @set_coalesce: Set coalesce parameters.
  *
- * @channel_switch: initiate channel-switch procedure (with CSA)
+ * @channel_switch: initiate channel-switch procedure (with CSA). Driver is
+ *	responsible for veryfing if the switch is possible. Since this is
+ *	inherently tricky driver may decide to disconnect an interface later
+ *	with cfg80211_stop_iface(). This doesn't mean driver can accept
+ *	everything. It should do it's best to verify requests and reject them
+ *	as soon as possible.
  *
  * @set_qos_map: Set QoS mapping information to the driver
  *
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 62bdb1a..9c9318e 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -6005,17 +6005,6 @@ skip_beacons:
 		params.radar_required = true;
 	}
 
-	/* TODO: I left this here for now.  With channel switch, the
-	 * verification is a bit more complicated, because we only do
-	 * it later when the channel switch really happens.
-	 */
-	err = cfg80211_can_use_iftype_chan(rdev, wdev, wdev->iftype,
-					   params.chandef.chan,
-					   CHAN_MODE_SHARED,
-					   radar_detect_width);
-	if (err)
-		return err;
-
 	if (info->attrs[NL80211_ATTR_CH_SWITCH_BLOCK_TX])
 		params.block_tx = true;
 
-- 
1.8.5.3


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* Re: [PATCH v7 1/5] mac80211: implement multi-vif in-place reservations
  2014-05-29  7:34 ` [PATCH v7 1/5] mac80211: implement multi-vif in-place reservations Michal Kazior
@ 2014-06-02 11:33   ` Luca Coelho
  2014-06-03  6:15     ` Michal Kazior
  0 siblings, 1 reply; 40+ messages in thread
From: Luca Coelho @ 2014-06-02 11:33 UTC (permalink / raw
  To: Michal Kazior; +Cc: linux-wireless, johannes

On Thu, 2014-05-29 at 09:34 +0200, Michal Kazior wrote:
> Multi-vif in-place reservations happen when
> it is impossible to allocate more channel contexts
> as indicated by interface combinations.
> 
> Such reservations are not finalized until all
> assigned interfaces are ready.
> 
> This still doesn't handle all possible cases
> (i.e. degradation of number of channels) properly.
> 
> Signed-off-by: Michal Kazior <michal.kazior@tieto.com>
> ---


> diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
> index 3702d64..01379a17 100644
> --- a/net/mac80211/chan.c
> +++ b/net/mac80211/chan.c
[...]
> @@ -898,8 +920,16 @@ int ieee80211_vif_unreserve_chanctx(struct ieee80211_sub_if_data *sdata)
>  	list_del(&sdata->reserved_chanctx_list);
>  	sdata->reserved_chanctx = NULL;
>  
> -	if (ieee80211_chanctx_refcount(sdata->local, ctx) == 0)
> -		ieee80211_free_chanctx(sdata->local, ctx);
> +	if (ieee80211_chanctx_refcount(sdata->local, ctx) == 0) {
> +		if (ctx->replaces) {
> +			WARN_ON(ctx->replaces->replaced_by != ctx);
> +			ctx->replaces->replaced_by = NULL;
> +			list_del_rcu(&ctx->list);
> +			kfree_rcu(ctx, rcu_head);

Couldn't this go into the ieee80211_free_chanctx()?

[...]
> @@ -911,39 +941,71 @@ int ieee80211_vif_reserve_chanctx(struct ieee80211_sub_if_data *sdata,
>  {
>  	struct ieee80211_local *local = sdata->local;
>  	struct ieee80211_chanctx_conf *conf;
> -	struct ieee80211_chanctx *new_ctx, *curr_ctx;
> -	int ret = 0;
> +	struct ieee80211_chanctx *new_ctx, *curr_ctx, *ctx;
>  
> -	mutex_lock(&local->chanctx_mtx);
> +	lockdep_assert_held(&local->chanctx_mtx);
> +
> +	if (local->use_chanctx && !local->ops->switch_vif_chanctx)
> +		return -ENOTSUPP;

Do we really need to fail here for all reservations (ie. even when it's
not an in-place reservation)?

[...]
>  		} else {
> -			ret = -EBUSY;
> -			goto out;

> +			if (curr_ctx->replaced_by ||
> +			    !list_empty(&curr_ctx->reserved_vifs)) {
> +				/*
> +				 * Another vif already requested this context
> +				 * for an in-place reservation. Find another

If !curr_ctx->replaced_by, then this is not an in-place reservation,
right? the ->reserved_vifs will switch from another context to this one.
The comment is a bit misleading.


> +				 * one hoping all vifs assigned to it will also
> +				 * switch soon enough.
> +				 *
> +				 * TODO: This needs a little more work as some
> +				 * cases may fail which could otherwise succeed
> +				 * provided some channel context juggling was
> +				 * performed.
> +				 */

This TODO seems fair enough, but could you provide at least one example
of such case?


> +				list_for_each_entry(ctx, &local->chanctx_list,
> +						    list) {
> +					if (ctx->replaced_by)
> +						continue;
> +
> +					if (ctx->replaces)
> +						continue;
> +
> +					if (!list_empty(&ctx->reserved_vifs))
> +						continue;
> +
> +					curr_ctx = ctx;
> +					break;
> +				}

I'm probably missing something, because I don't get this.  Do you just
try to find *any* other existing context and "steal" it? Even if the
other context you find has a completely unrelated chandef?


> +			}
> +
> +			/*
> +			 * If that's true then all available contexts are all
> +			 * in-place reserved already.
> +			 */
> +			if (curr_ctx->replaced_by)
> +				return -EBUSY;

What about the reserved_vifs case? You may also get here if
curr_ctx->replaced_by is not true, but curr_ctx->reserved_vifs is not
empty.

[...]
> -int ieee80211_vif_use_reserved_context(struct ieee80211_sub_if_data *sdata,
> -				       u32 *changed)
> +static int
> +ieee80211_vif_use_reserved_assign(struct ieee80211_sub_if_data *sdata)
>  {
>  	struct ieee80211_local *local = sdata->local;
> -	struct ieee80211_chanctx *ctx;
> -	struct ieee80211_chanctx *old_ctx;
> -	struct ieee80211_chanctx_conf *conf;
> -	int ret;
> -	u32 tmp_changed = *changed;
> -
> -	/* TODO: need to recheck if the chandef is usable etc.? */
> +	struct ieee80211_vif_chanctx_switch vif_chsw[1] = {};
> +	struct ieee80211_chanctx *old_ctx, *new_ctx;
> +	const struct cfg80211_chan_def *chandef;
> +	u32 changed = 0;
> +	int err;
>  
>  	lockdep_assert_held(&local->mtx);
> +	lockdep_assert_held(&local->chanctx_mtx);
>  
> -	mutex_lock(&local->chanctx_mtx);
> +	new_ctx = sdata->reserved_chanctx;
> +	old_ctx = ieee80211_vif_get_chanctx(sdata);
>  
> -	ctx = sdata->reserved_chanctx;
> -	if (WARN_ON(!ctx)) {
> -		ret = -EINVAL;
> -		goto out;
> -	}
> +	if (WARN_ON(!sdata->reserved_ready))
> +		return -EBUSY;
>  
> -	conf = rcu_dereference_protected(sdata->vif.chanctx_conf,
> -					 lockdep_is_held(&local->chanctx_mtx));
> -	if (!conf) {
> -		ret = -EINVAL;
> -		goto out;
> +	if (WARN_ON(!new_ctx))
> +		return -EINVAL;
> +
> +	if (WARN_ON(!old_ctx))
> +		return -EINVAL;

You already WARN_ON !new_ctx and !old_ctx in
ieee80211_vif_use_reserved_context().  I know it doesn't hurt, but do
you have to double-check here?


> +
> +	if (WARN_ON(new_ctx->replaced_by))
> +		return -EINVAL;
> +
> +	if (WARN_ON(old_ctx->replaced_by && new_ctx->replaces))
> +		return -EINVAL;

What if new_ctx is going to replace something else?

[...]
> +static int
> +ieee80211_vif_use_reserved_switch(struct ieee80211_local *local)
> +{
> +	struct ieee80211_sub_if_data *sdata, *sdata_tmp;
> +	struct ieee80211_chanctx *new_ctx = NULL, *ctx, *ctx_tmp;

I prefer if declarations with assignments are in lines of their own, but
maybe it's just me.


> +	struct ieee80211_vif_chanctx_switch *vif_chsw = NULL;
> +	const struct cfg80211_chan_def *chandef;
> +	int i, err, n_ctx = 0, n_vifs = 0;
> +
> +	lockdep_assert_held(&local->mtx);
> +	lockdep_assert_held(&local->chanctx_mtx);
> +
> +	/*
> +	 * If there are 2 independant pairs of channel contexts performing

Typo, independent.

> +	 * cross-switch of their vifs this code will still wait until both are
> +	 * ready even though it could be possible to switch one before the
> +	 * other is ready.
> +	 *
> +	 * For practical reasons and code simplicity just do a single huge
> +	 * switch.
> +	 */
> +
> +	list_for_each_entry(ctx, &local->chanctx_list, list) {
> +		if (!(ctx->replaces && !ctx->replaced_by))
> +			continue;

if (!ctx->replaces || ctx->replaced_by) looks more natural to me. (Same
thing for some other cases in this patch).


> +
> +		if (!local->use_chanctx)
> +			new_ctx = ctx;
> +
> +		n_ctx++;
> +
> +		list_for_each_entry(sdata, &ctx->replaces->assigned_vifs,
> +				    assigned_chanctx_list) {
> +			if (!sdata->reserved_chanctx) {
> +				wiphy_info(local->hw.wiphy,
> +					   "channel context reservation cannot be finalized because some interfaces aren't switching\n");
> +				err = -EBUSY;
> +				goto err;
> +			}

Hadn't we decided to disconnect the vifs that didn't follow? Can't
remember anymore. :) But now you're just canceling the whole switch?


> +
> +			if (!sdata->reserved_ready)
> +				return -EAGAIN;
> +		}

Shouldn't this be above the previous if? If not everyone switched yet,
can't we give more time for others to join the switch?


> +
> +		list_for_each_entry(sdata, &ctx->reserved_vifs,
> +				    reserved_chanctx_list) {
> +			if (!ieee80211_vif_has_in_place_reservation(sdata))
> +				continue;
> +
> +			if (!sdata->reserved_ready)
> +				return -EAGAIN;
> +
> +			n_vifs++;
> +
> +			if (sdata->reserved_radar_required)
> +				ctx->conf.radar_enabled = true;
> +		}
> +	}
> +
> +	if (WARN_ON(n_ctx == 0) ||
> +	    WARN_ON(n_vifs == 0) ||
> +	    WARN_ON(n_ctx > 1 && !local->use_chanctx) ||
> +	    WARN_ON(!new_ctx && !local->use_chanctx)) {

Can this last WARN ever happen? Only if there's no chanctx at all in the
chanctx_list (which should never happen)?


> +		err = -EINVAL;
> +		goto err;
> +	}
> +
> +	if (local->use_chanctx) {
> +		vif_chsw = kzalloc(sizeof(*vif_chsw) * n_vifs, GFP_KERNEL);
> +		if (vif_chsw) {
> +			err = -ENOMEM;
> +			goto err;
> +		}
> +
> +		i = 0;
> +		list_for_each_entry(ctx, &local->chanctx_list, list) {
> +			if (!(ctx->replaces && !ctx->replaced_by))
> +				continue;
> +
> +			list_for_each_entry(sdata, &ctx->reserved_vifs,
> +					    reserved_chanctx_list) {
> +				if (!ieee80211_vif_has_in_place_reservation(
> +						sdata))
> +					continue;
> +
> +				vif_chsw[i].vif = &sdata->vif;
> +				vif_chsw[i].old_ctx = &ctx->replaces->conf;
> +				vif_chsw[i].new_ctx = &ctx->conf;
> +
> +				i++;
> +			}
> +		}
> +
> +		err = drv_switch_vif_chanctx(local, vif_chsw, n_vifs,
> +					     CHANCTX_SWMODE_SWAP_CONTEXTS);
> +		kfree(vif_chsw);
> +
> +		if (err)
> +			goto err;
>  	} else {
> -		ret = ieee80211_assign_vif_chanctx(sdata, ctx);
> -		if (ieee80211_chanctx_refcount(local, old_ctx) == 0)
> -			ieee80211_free_chanctx(local, old_ctx);
> -		if (ret) {
> -			/* if assign fails refcount stays the same */
> -			if (ieee80211_chanctx_refcount(local, ctx) == 0)
> -				ieee80211_free_chanctx(local, ctx);
> -			goto out;
> +		chandef = ieee80211_chanctx_reserved_chandef(local, new_ctx,
> +							     NULL);
> +		if (WARN_ON(!chandef)) {
> +			err = -EINVAL;
> +			goto err;
>  		}
>  
> -		if (sdata->vif.type == NL80211_IFTYPE_AP)
> -			__ieee80211_vif_copy_chanctx_to_vlans(sdata, false);
> +		local->hw.conf.radar_enabled = new_ctx->conf.radar_enabled;
> +		local->_oper_chandef = *chandef;
> +		ieee80211_hw_config(local, 0);
>  	}
>  
> -	*changed = tmp_changed;
> +	i = 0;
> +	list_for_each_entry_safe(ctx, ctx_tmp, &local->chanctx_list, list) {
> +		if (!(ctx->replaces && !ctx->replaced_by))
> +			continue;
>  
> -	ieee80211_recalc_chanctx_chantype(local, ctx);
> -	ieee80211_recalc_smps_chanctx(local, ctx);
> -	ieee80211_recalc_radar_chanctx(local, ctx);
> -	ieee80211_recalc_chanctx_min_def(local, ctx);
> -out:
> -	mutex_unlock(&local->chanctx_mtx);
> -	return ret;
> +		list_for_each_entry(sdata, &ctx->reserved_vifs,
> +				    reserved_chanctx_list) {
> +			u32 changed = 0;
> +
> +			if (!ieee80211_vif_has_in_place_reservation(sdata))
> +				continue;
> +
> +			rcu_assign_pointer(sdata->vif.chanctx_conf, &ctx->conf);
> +
> +			if (sdata->vif.type == NL80211_IFTYPE_AP)
> +				__ieee80211_vif_copy_chanctx_to_vlans(sdata,
> +								      false);
> +
> +			sdata->radar_required = sdata->reserved_radar_required;
> +
> +			if (sdata->vif.bss_conf.chandef.width !=
> +			    sdata->reserved_chandef.width)
> +				changed = BSS_CHANGED_BANDWIDTH;
> +
> +			sdata->vif.bss_conf.chandef = sdata->reserved_chandef;
> +			if (changed)
> +				ieee80211_bss_info_change_notify(sdata,
> +								 changed);
> +
> +			ieee80211_recalc_txpower(sdata);
> +		}
> +
> +		ieee80211_recalc_chanctx_chantype(local, ctx);
> +		ieee80211_recalc_smps_chanctx(local, ctx);
> +		ieee80211_recalc_radar_chanctx(local, ctx);
> +		ieee80211_recalc_chanctx_min_def(local, ctx);
> +
> +		list_for_each_entry_safe(sdata, sdata_tmp, &ctx->reserved_vifs,
> +					 reserved_chanctx_list) {
> +			list_del(&sdata->reserved_chanctx_list);
> +			list_move(&sdata->assigned_chanctx_list,
> +				  &new_ctx->assigned_vifs);
> +			sdata->reserved_chanctx = NULL;
> +		}
> +
> +		list_del_rcu(&ctx->replaces->list);
> +		kfree_rcu(ctx->replaces, rcu_head);
> +		ctx->replaces = NULL;
> +
> +		/*
> +		 * Some simple reservations depending on the in-place switch
> +		 * might've been ready before and were deferred. Retry them now
> +		 * but don't propagate the error up the call stack as the
> +		 * directly requested reservation has been already handled
> +		 * successfully at this point.
> +		 */
> +		list_for_each_entry(sdata, &ctx->reserved_vifs,
> +				    reserved_chanctx_list) {
> +			if (WARN_ON(ieee80211_vif_has_in_place_reservation(
> +					sdata)))
> +				continue;
> +
> +			if (!sdata->reserved_ready)
> +				continue;
> +
> +			err = ieee80211_vif_use_reserved_assign(sdata);
> +			if (WARN_ON(err && err != -EAGAIN)) {
 
Do we really want to WARN on other error cases here? It could be some
kind of -EBUSY or so and just disconnecting would be okay?


> +				sdata_info(sdata,
> +					   "failed to finalize reservation (err=%d)\n",
> +					   err);
> +				ieee80211_vif_unreserve_chanctx(sdata);
> +				cfg80211_stop_iface(local->hw.wiphy,
> +						    &sdata->wdev,
> +						    GFP_KERNEL);
> +			}
> +		}
> +	}
> +
> +	return 0;
> +
> +err:
> +	list_for_each_entry(ctx, &local->chanctx_list, list) {
> +		if (!(ctx->replaces && !ctx->replaced_by))
> +			continue;
> +
> +		list_for_each_entry_safe(sdata, sdata_tmp, &ctx->reserved_vifs,
> +					 reserved_chanctx_list)
> +			ieee80211_vif_unreserve_chanctx(sdata);
> +	}
> +
> +	return err;
> +}

This function is huge, any way to break it down? Or at least add
comments before each block explaining what they do...


> +int ieee80211_vif_use_reserved_context(struct ieee80211_sub_if_data *sdata)
> +{
> +	struct ieee80211_local *local = sdata->local;
> +	struct ieee80211_chanctx *new_ctx;
> +	struct ieee80211_chanctx *old_ctx;
> +	int err;
> +
> +	lockdep_assert_held(&local->mtx);
> +	lockdep_assert_held(&local->chanctx_mtx);
> +
> +	new_ctx = sdata->reserved_chanctx;
> +	old_ctx = ieee80211_vif_get_chanctx(sdata);
> +
> +	if (WARN_ON(!new_ctx))
> +		return -EINVAL;
> +
> +	if (WARN_ON(!old_ctx))
> +		return -EINVAL;
> +
> +	if (WARN_ON(sdata->reserved_ready))
> +		return -EINVAL;
> +
> +	sdata->reserved_ready = true;
> +	if (!(old_ctx->replaced_by && new_ctx->replaces)) {

Isn't !old->ctx->replaced_by enough here? Do you really care if the
new_ctx will replace something else at this point?


> +		err = ieee80211_vif_use_reserved_assign(sdata);
> +		if (err && err != -EAGAIN)
> +			return err;
> +	}
> +
> +	/*
> +	 * In-place reservation may need to be finalized now either if:
> +	 *  - sdata is taking part in the swapping itself and is the last one
> +	 *  - sdata has switched with a simple reservation to an existing
> +	 *    context readying the in-place switching old_ctx
> +	 */
> +
> +	if (old_ctx->replaced_by || new_ctx->replaces) {

Same thing here... if new_ctx replaces something *else* shouldn't we use
_assign() instead?


> +		err = ieee80211_vif_use_reserved_switch(local);
> +		if (err && err != -EAGAIN)
> +			return err;
> +	}
> +
> +	return 0;
>  }
[...]

--
Luca.


^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v7 1/5] mac80211: implement multi-vif in-place reservations
  2014-06-02 11:33   ` Luca Coelho
@ 2014-06-03  6:15     ` Michal Kazior
  2014-06-03 17:10       ` Johannes Berg
  0 siblings, 1 reply; 40+ messages in thread
From: Michal Kazior @ 2014-06-03  6:15 UTC (permalink / raw
  To: Luca Coelho; +Cc: linux-wireless, Johannes Berg

On 2 June 2014 13:33, Luca Coelho <luca@coelho.fi> wrote:
> On Thu, 2014-05-29 at 09:34 +0200, Michal Kazior wrote:
>> Multi-vif in-place reservations happen when
>> it is impossible to allocate more channel contexts
>> as indicated by interface combinations.
>>
>> Such reservations are not finalized until all
>> assigned interfaces are ready.
>>
>> This still doesn't handle all possible cases
>> (i.e. degradation of number of channels) properly.
>>
>> Signed-off-by: Michal Kazior <michal.kazior@tieto.com>
>> ---
>
>
>> diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
>> index 3702d64..01379a17 100644
>> --- a/net/mac80211/chan.c
>> +++ b/net/mac80211/chan.c
> [...]
>> @@ -898,8 +920,16 @@ int ieee80211_vif_unreserve_chanctx(struct ieee80211_sub_if_data *sdata)
>>       list_del(&sdata->reserved_chanctx_list);
>>       sdata->reserved_chanctx = NULL;
>>
>> -     if (ieee80211_chanctx_refcount(sdata->local, ctx) == 0)
>> -             ieee80211_free_chanctx(sdata->local, ctx);
>> +     if (ieee80211_chanctx_refcount(sdata->local, ctx) == 0) {
>> +             if (ctx->replaces) {
>> +                     WARN_ON(ctx->replaces->replaced_by != ctx);
>> +                     ctx->replaces->replaced_by = NULL;
>> +                     list_del_rcu(&ctx->list);
>> +                     kfree_rcu(ctx, rcu_head);
>
> Couldn't this go into the ieee80211_free_chanctx()?

I'm afraid not. At least not as it is now. ieee80211_free_chanctx()
calls ieee80211_del_chanctx() (and thus drv_remove_chanctx) which must
not be called when doing drv_switch_vif_chanctx(..., SWAP).


> [...]
>> @@ -911,39 +941,71 @@ int ieee80211_vif_reserve_chanctx(struct ieee80211_sub_if_data *sdata,
>>  {
>>       struct ieee80211_local *local = sdata->local;
>>       struct ieee80211_chanctx_conf *conf;
>> -     struct ieee80211_chanctx *new_ctx, *curr_ctx;
>> -     int ret = 0;
>> +     struct ieee80211_chanctx *new_ctx, *curr_ctx, *ctx;
>>
>> -     mutex_lock(&local->chanctx_mtx);
>> +     lockdep_assert_held(&local->chanctx_mtx);
>> +
>> +     if (local->use_chanctx && !local->ops->switch_vif_chanctx)
>> +             return -ENOTSUPP;
>
> Do we really need to fail here for all reservations (ie. even when it's
> not an in-place reservation)?

Good point. I guess this should be enforced only if given sdata has a
chanctx already bound. If there's no chanctx bound at all then it
should be a simple matter of assign_vif_chanctx. This isn't covered
yet.

I should either add:

 if (!ieee80211_vif_chanctx(sdata))
   return -ENOTSUPP;

.. or handle the vif_assign case (and update the condition you pointed
out to consider current chanctx).


> [...]
>>               } else {
>> -                     ret = -EBUSY;
>> -                     goto out;
>
>> +                     if (curr_ctx->replaced_by ||
>> +                         !list_empty(&curr_ctx->reserved_vifs)) {
>> +                             /*
>> +                              * Another vif already requested this context
>> +                              * for an in-place reservation. Find another
>
> If !curr_ctx->replaced_by, then this is not an in-place reservation,
> right? the ->reserved_vifs will switch from another context to this one.
> The comment is a bit misleading.

Ah, you're right. It should just state "another vif already requested
this context for a reservation".


>> +                              * one hoping all vifs assigned to it will also
>> +                              * switch soon enough.
>> +                              *
>> +                              * TODO: This needs a little more work as some
>> +                              * cases may fail which could otherwise succeed
>> +                              * provided some channel context juggling was
>> +                              * performed.
>> +                              */
>
> This TODO seems fair enough, but could you provide at least one example
> of such case?

Hmm... (my brain..)

Consider the following: 3 ctx, 6 vifs. cannot create more ctx.
ctx1=vif1+vif2, ctx2=vif3+vif4, ctx3=vif5+vif6. ctx1..3 have
chandef1..3.

vif5 wants chandef4 so it allocates ctx4 and sets `replaces` to ctx3.
vif6 wants chandef5 so it allocates ctx5 and sets `replaces` to ctx1.
[ note ctx1 with its assigned vif1 and vif2 ]
vif3 wants chandef5 so it reserves ctx5.
vif4 wants chandef5 so it reserves ctx5.
[ ctx2 could be considered now as "free" and could be set as
`replaces` for ctx5 instead of ctx1 ]

It's pretty far fetched for now I'd say - are there any devices that
support more than 2 channels now?

For max_chans=2 devices this turns into a single in-place and doesn't
require extra the trick. With the optimization present it would split
the single in-place switch into in-place + re-assign + in-place.


>> +                             list_for_each_entry(ctx, &local->chanctx_list,
>> +                                                 list) {
>> +                                     if (ctx->replaced_by)
>> +                                             continue;
>> +
>> +                                     if (ctx->replaces)
>> +                                             continue;
>> +
>> +                                     if (!list_empty(&ctx->reserved_vifs))
>> +                                             continue;
>> +
>> +                                     curr_ctx = ctx;
>> +                                     break;
>> +                             }
>
> I'm probably missing something, because I don't get this.  Do you just
> try to find *any* other existing context and "steal" it? Even if the
> other context you find has a completely unrelated chandef?

Correct. There's a chance it will align with other switches and
there's a chance to swap the replaces/replaced_by between in-place
future chanctx. I guess it's better to give it a chance.


>> +                     }
>> +
>> +                     /*
>> +                      * If that's true then all available contexts are all
>> +                      * in-place reserved already.
>> +                      */
>> +                     if (curr_ctx->replaced_by)
>> +                             return -EBUSY;
>
> What about the reserved_vifs case? You may also get here if
> curr_ctx->replaced_by is not true, but curr_ctx->reserved_vifs is not
> empty.

Good catch! I didn't update this condition properly.


> [...]
>> -int ieee80211_vif_use_reserved_context(struct ieee80211_sub_if_data *sdata,
>> -                                    u32 *changed)
>> +static int
>> +ieee80211_vif_use_reserved_assign(struct ieee80211_sub_if_data *sdata)
>>  {
>>       struct ieee80211_local *local = sdata->local;
>> -     struct ieee80211_chanctx *ctx;
>> -     struct ieee80211_chanctx *old_ctx;
>> -     struct ieee80211_chanctx_conf *conf;
>> -     int ret;
>> -     u32 tmp_changed = *changed;
>> -
>> -     /* TODO: need to recheck if the chandef is usable etc.? */
>> +     struct ieee80211_vif_chanctx_switch vif_chsw[1] = {};
>> +     struct ieee80211_chanctx *old_ctx, *new_ctx;
>> +     const struct cfg80211_chan_def *chandef;
>> +     u32 changed = 0;
>> +     int err;
>>
>>       lockdep_assert_held(&local->mtx);
>> +     lockdep_assert_held(&local->chanctx_mtx);
>>
>> -     mutex_lock(&local->chanctx_mtx);
>> +     new_ctx = sdata->reserved_chanctx;
>> +     old_ctx = ieee80211_vif_get_chanctx(sdata);
>>
>> -     ctx = sdata->reserved_chanctx;
>> -     if (WARN_ON(!ctx)) {
>> -             ret = -EINVAL;
>> -             goto out;
>> -     }
>> +     if (WARN_ON(!sdata->reserved_ready))
>> +             return -EBUSY;
>>
>> -     conf = rcu_dereference_protected(sdata->vif.chanctx_conf,
>> -                                      lockdep_is_held(&local->chanctx_mtx));
>> -     if (!conf) {
>> -             ret = -EINVAL;
>> -             goto out;
>> +     if (WARN_ON(!new_ctx))
>> +             return -EINVAL;
>> +
>> +     if (WARN_ON(!old_ctx))
>> +             return -EINVAL;
>
> You already WARN_ON !new_ctx and !old_ctx in
> ieee80211_vif_use_reserved_context().  I know it doesn't hurt, but do
> you have to double-check here?

Just staying on the safe side of "better print twice than nothing" (in
case this ends up being called by other functions in the future). I
actually think it's a good idea to state function requirements like
this.


>> +
>> +     if (WARN_ON(new_ctx->replaced_by))
>> +             return -EINVAL;
>> +
>> +     if (WARN_ON(old_ctx->replaced_by && new_ctx->replaces))
>> +             return -EINVAL;
>
> What if new_ctx is going to replace something else?

Shouldn't happen - that's why WARN_ON.


> [...]
>> +static int
>> +ieee80211_vif_use_reserved_switch(struct ieee80211_local *local)
>> +{
>> +     struct ieee80211_sub_if_data *sdata, *sdata_tmp;
>> +     struct ieee80211_chanctx *new_ctx = NULL, *ctx, *ctx_tmp;
>
> I prefer if declarations with assignments are in lines of their own, but
> maybe it's just me.

Actually fair point. I'm declaring multiple vars here..


>> +     struct ieee80211_vif_chanctx_switch *vif_chsw = NULL;
>> +     const struct cfg80211_chan_def *chandef;
>> +     int i, err, n_ctx = 0, n_vifs = 0;
>> +
>> +     lockdep_assert_held(&local->mtx);
>> +     lockdep_assert_held(&local->chanctx_mtx);
>> +
>> +     /*
>> +      * If there are 2 independant pairs of channel contexts performing
>
> Typo, independent.

Roger.


>> +      * cross-switch of their vifs this code will still wait until both are
>> +      * ready even though it could be possible to switch one before the
>> +      * other is ready.
>> +      *
>> +      * For practical reasons and code simplicity just do a single huge
>> +      * switch.
>> +      */
>> +
>> +     list_for_each_entry(ctx, &local->chanctx_list, list) {
>> +             if (!(ctx->replaces && !ctx->replaced_by))
>> +                     continue;
>
> if (!ctx->replaces || ctx->replaced_by) looks more natural to me. (Same
> thing for some other cases in this patch).

I find it easier to reason with my condition. But yeah. Popular vote? :-)


>> +
>> +             if (!local->use_chanctx)
>> +                     new_ctx = ctx;
>> +
>> +             n_ctx++;
>> +
>> +             list_for_each_entry(sdata, &ctx->replaces->assigned_vifs,
>> +                                 assigned_chanctx_list) {
>> +                     if (!sdata->reserved_chanctx) {
>> +                             wiphy_info(local->hw.wiphy,
>> +                                        "channel context reservation cannot be finalized because some interfaces aren't switching\n");
>> +                             err = -EBUSY;
>> +                             goto err;
>> +                     }
>
> Hadn't we decided to disconnect the vifs that didn't follow? Can't
> remember anymore. :) But now you're just canceling the whole switch?

I'm not sure myself anymore either. This keeps the current behaviour
(i.e. disconnect interfaces that are switching on failure). Johannes?


>> +
>> +                     if (!sdata->reserved_ready)
>> +                             return -EAGAIN;
>> +             }
>
> Shouldn't this be above the previous if? If not everyone switched yet,
> can't we give more time for others to join the switch?

This is okay as it is. reserved_chanctx means there is a reservation
ongoing. reserved_ready means it is ready for the switch itself.

If you reach this place you already have at least one vif ready for
the switch but it needs in-place to be done. If there are still any
assigned interfaces not complying then this fails the whole switch.

I guess you could relax this to fail after assigned interfaces that
have reservations are all ready and there are still some assigned
interfaces left without reservation at all. I guess it might increase
a chance of a racy channel switch to succeed. Just a little tiny bit.


>> +
>> +             list_for_each_entry(sdata, &ctx->reserved_vifs,
>> +                                 reserved_chanctx_list) {
>> +                     if (!ieee80211_vif_has_in_place_reservation(sdata))
>> +                             continue;
>> +
>> +                     if (!sdata->reserved_ready)
>> +                             return -EAGAIN;
>> +
>> +                     n_vifs++;
>> +
>> +                     if (sdata->reserved_radar_required)
>> +                             ctx->conf.radar_enabled = true;
>> +             }
>> +     }
>> +
>> +     if (WARN_ON(n_ctx == 0) ||
>> +         WARN_ON(n_vifs == 0) ||
>> +         WARN_ON(n_ctx > 1 && !local->use_chanctx) ||
>> +         WARN_ON(!new_ctx && !local->use_chanctx)) {
>
> Can this last WARN ever happen? Only if there's no chanctx at all in the
> chanctx_list (which should never happen)?

True, but this explicitly tells you "new_ctx cannot be NULL beyond
this point without use_chanctx". I'd like to keep it.


>> +             err = -EINVAL;
>> +             goto err;
>> +     }
>> +
>> +     if (local->use_chanctx) {
>> +             vif_chsw = kzalloc(sizeof(*vif_chsw) * n_vifs, GFP_KERNEL);
>> +             if (vif_chsw) {
>> +                     err = -ENOMEM;
>> +                     goto err;
>> +             }
>> +
>> +             i = 0;
>> +             list_for_each_entry(ctx, &local->chanctx_list, list) {
>> +                     if (!(ctx->replaces && !ctx->replaced_by))
>> +                             continue;
>> +
>> +                     list_for_each_entry(sdata, &ctx->reserved_vifs,
>> +                                         reserved_chanctx_list) {
>> +                             if (!ieee80211_vif_has_in_place_reservation(
>> +                                             sdata))
>> +                                     continue;
>> +
>> +                             vif_chsw[i].vif = &sdata->vif;
>> +                             vif_chsw[i].old_ctx = &ctx->replaces->conf;
>> +                             vif_chsw[i].new_ctx = &ctx->conf;
>> +
>> +                             i++;
>> +                     }
>> +             }
>> +
>> +             err = drv_switch_vif_chanctx(local, vif_chsw, n_vifs,
>> +                                          CHANCTX_SWMODE_SWAP_CONTEXTS);
>> +             kfree(vif_chsw);
>> +
>> +             if (err)
>> +                     goto err;
>>       } else {
>> -             ret = ieee80211_assign_vif_chanctx(sdata, ctx);
>> -             if (ieee80211_chanctx_refcount(local, old_ctx) == 0)
>> -                     ieee80211_free_chanctx(local, old_ctx);
>> -             if (ret) {
>> -                     /* if assign fails refcount stays the same */
>> -                     if (ieee80211_chanctx_refcount(local, ctx) == 0)
>> -                             ieee80211_free_chanctx(local, ctx);
>> -                     goto out;
>> +             chandef = ieee80211_chanctx_reserved_chandef(local, new_ctx,
>> +                                                          NULL);
>> +             if (WARN_ON(!chandef)) {
>> +                     err = -EINVAL;
>> +                     goto err;
>>               }
>>
>> -             if (sdata->vif.type == NL80211_IFTYPE_AP)
>> -                     __ieee80211_vif_copy_chanctx_to_vlans(sdata, false);
>> +             local->hw.conf.radar_enabled = new_ctx->conf.radar_enabled;
>> +             local->_oper_chandef = *chandef;
>> +             ieee80211_hw_config(local, 0);
>>       }
>>
>> -     *changed = tmp_changed;
>> +     i = 0;
>> +     list_for_each_entry_safe(ctx, ctx_tmp, &local->chanctx_list, list) {
>> +             if (!(ctx->replaces && !ctx->replaced_by))
>> +                     continue;
>>
>> -     ieee80211_recalc_chanctx_chantype(local, ctx);
>> -     ieee80211_recalc_smps_chanctx(local, ctx);
>> -     ieee80211_recalc_radar_chanctx(local, ctx);
>> -     ieee80211_recalc_chanctx_min_def(local, ctx);
>> -out:
>> -     mutex_unlock(&local->chanctx_mtx);
>> -     return ret;
>> +             list_for_each_entry(sdata, &ctx->reserved_vifs,
>> +                                 reserved_chanctx_list) {
>> +                     u32 changed = 0;
>> +
>> +                     if (!ieee80211_vif_has_in_place_reservation(sdata))
>> +                             continue;
>> +
>> +                     rcu_assign_pointer(sdata->vif.chanctx_conf, &ctx->conf);
>> +
>> +                     if (sdata->vif.type == NL80211_IFTYPE_AP)
>> +                             __ieee80211_vif_copy_chanctx_to_vlans(sdata,
>> +                                                                   false);
>> +
>> +                     sdata->radar_required = sdata->reserved_radar_required;
>> +
>> +                     if (sdata->vif.bss_conf.chandef.width !=
>> +                         sdata->reserved_chandef.width)
>> +                             changed = BSS_CHANGED_BANDWIDTH;
>> +
>> +                     sdata->vif.bss_conf.chandef = sdata->reserved_chandef;
>> +                     if (changed)
>> +                             ieee80211_bss_info_change_notify(sdata,
>> +                                                              changed);
>> +
>> +                     ieee80211_recalc_txpower(sdata);
>> +             }
>> +
>> +             ieee80211_recalc_chanctx_chantype(local, ctx);
>> +             ieee80211_recalc_smps_chanctx(local, ctx);
>> +             ieee80211_recalc_radar_chanctx(local, ctx);
>> +             ieee80211_recalc_chanctx_min_def(local, ctx);
>> +
>> +             list_for_each_entry_safe(sdata, sdata_tmp, &ctx->reserved_vifs,
>> +                                      reserved_chanctx_list) {
>> +                     list_del(&sdata->reserved_chanctx_list);
>> +                     list_move(&sdata->assigned_chanctx_list,
>> +                               &new_ctx->assigned_vifs);
>> +                     sdata->reserved_chanctx = NULL;
>> +             }
>> +
>> +             list_del_rcu(&ctx->replaces->list);
>> +             kfree_rcu(ctx->replaces, rcu_head);
>> +             ctx->replaces = NULL;
>> +
>> +             /*
>> +              * Some simple reservations depending on the in-place switch
>> +              * might've been ready before and were deferred. Retry them now
>> +              * but don't propagate the error up the call stack as the
>> +              * directly requested reservation has been already handled
>> +              * successfully at this point.
>> +              */
>> +             list_for_each_entry(sdata, &ctx->reserved_vifs,
>> +                                 reserved_chanctx_list) {
>> +                     if (WARN_ON(ieee80211_vif_has_in_place_reservation(
>> +                                     sdata)))
>> +                             continue;
>> +
>> +                     if (!sdata->reserved_ready)
>> +                             continue;
>> +
>> +                     err = ieee80211_vif_use_reserved_assign(sdata);
>> +                     if (WARN_ON(err && err != -EAGAIN)) {
>
> Do we really want to WARN on other error cases here? It could be some
> kind of -EBUSY or so and just disconnecting would be okay?

See the comment above.

This takes care of related reservations. These reservations aren't
strictly the main subject for this function call so I don't really
agree to propagate the error up. The in-place reservation that was the
purpose of this call was a success at this point.

This failure shouldn't happen in most cases unless thing go really bad
so I guess it's fine like this.

Actually the caller should also be aware of this, because in-place may
happen as a side-effect to re-assign (due to dependencies).


>> +                             sdata_info(sdata,
>> +                                        "failed to finalize reservation (err=%d)\n",
>> +                                        err);
>> +                             ieee80211_vif_unreserve_chanctx(sdata);
>> +                             cfg80211_stop_iface(local->hw.wiphy,
>> +                                                 &sdata->wdev,
>> +                                                 GFP_KERNEL);
>> +                     }
>> +             }
>> +     }
>> +
>> +     return 0;
>> +
>> +err:
>> +     list_for_each_entry(ctx, &local->chanctx_list, list) {
>> +             if (!(ctx->replaces && !ctx->replaced_by))
>> +                     continue;
>> +
>> +             list_for_each_entry_safe(sdata, sdata_tmp, &ctx->reserved_vifs,
>> +                                      reserved_chanctx_list)
>> +                     ieee80211_vif_unreserve_chanctx(sdata);
>> +     }
>> +
>> +     return err;
>> +}
>
> This function is huge, any way to break it down? Or at least add
> comments before each block explaining what they do...

I'm aware of this. I've thought about splitting it but I'm afraid
it'll end up with a very artificial break up and code duplication (or
silly function arguments). Comment blocks might be the way to go here.


>> +int ieee80211_vif_use_reserved_context(struct ieee80211_sub_if_data *sdata)
>> +{
>> +     struct ieee80211_local *local = sdata->local;
>> +     struct ieee80211_chanctx *new_ctx;
>> +     struct ieee80211_chanctx *old_ctx;
>> +     int err;
>> +
>> +     lockdep_assert_held(&local->mtx);
>> +     lockdep_assert_held(&local->chanctx_mtx);
>> +
>> +     new_ctx = sdata->reserved_chanctx;
>> +     old_ctx = ieee80211_vif_get_chanctx(sdata);
>> +
>> +     if (WARN_ON(!new_ctx))
>> +             return -EINVAL;
>> +
>> +     if (WARN_ON(!old_ctx))
>> +             return -EINVAL;
>> +
>> +     if (WARN_ON(sdata->reserved_ready))
>> +             return -EINVAL;
>> +
>> +     sdata->reserved_ready = true;
>> +     if (!(old_ctx->replaced_by && new_ctx->replaces)) {
>
> Isn't !old->ctx->replaced_by enough here? Do you really care if the
> new_ctx will replace something else at this point?

Hmm..

Now that I think this should actually be just:

 if (!new_ctx->replaces) { ... }

You can only perform the re-assign here if the destination chanctx is
already operational.

If new_ctx->replaces is not NULL then the chanctx isn't there yet
(drv_switch_vif_chanctx wasn't called yet).

To better understand this you have to consider 3 cases of reservations:
 a) reassign vif from a chanctx that is going away to a chanctx that
already exists
 b) reassign vif from a chanctx that isn't going away to a chanctx
that doesn't exist yet
 c) reassign vif from a chanctx that is going away to a chanctx that
doesn't exist yet

The above code handles (a). The code below that handles (c). The
reservation with WARN_ON you pointed out earlier handles (b).

IOW you can have reservations depend on each other: (b) depends on (c)
which depends on (a).

(ad. b. The old chanctx might go away if it's the last vif switching)


>> +             err = ieee80211_vif_use_reserved_assign(sdata);
>> +             if (err && err != -EAGAIN)
>> +                     return err;
>> +     }
>> +
>> +     /*
>> +      * In-place reservation may need to be finalized now either if:
>> +      *  - sdata is taking part in the swapping itself and is the last one
>> +      *  - sdata has switched with a simple reservation to an existing
>> +      *    context readying the in-place switching old_ctx
>> +      */
>> +
>> +     if (old_ctx->replaced_by || new_ctx->replaces) {
>
> Same thing here... if new_ctx replaces something *else* shouldn't we use
> _assign() instead?

It doesn't matter what it replaces. If it replaces anything you can't
(re)assign to it just yet because it's a part of an incomplete
in-place reservation. Think about a circular in-place reservations
between chanctxs and vifs.

The condition should be this way because there are 2 cases you want to
call this (as stated in the comment):
 1) the _assign() above was the last dependency on old_ctx that is
taking part in an in-place reservation
 2) the _assign() wasn't even called because the sdata is taking part
in an in-place reservation itself


If you think my logic is flawed at any point or still have any doubts
do not hesitate to point it out. I'm not guaranteed to be correct :-)


Michał

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v7 1/5] mac80211: implement multi-vif in-place reservations
  2014-06-03  6:15     ` Michal Kazior
@ 2014-06-03 17:10       ` Johannes Berg
  2014-06-04 12:12         ` Michal Kazior
  0 siblings, 1 reply; 40+ messages in thread
From: Johannes Berg @ 2014-06-03 17:10 UTC (permalink / raw
  To: Michal Kazior; +Cc: Luca Coelho, linux-wireless

On Tue, 2014-06-03 at 08:15 +0200, Michal Kazior wrote:

> Hmm... (my brain..)
> 
> Consider the following: 3 ctx, 6 vifs. cannot create more ctx.
> ctx1=vif1+vif2, ctx2=vif3+vif4, ctx3=vif5+vif6. ctx1..3 have
> chandef1..3.
> 
> vif5 wants chandef4 so it allocates ctx4 and sets `replaces` to ctx3.
> vif6 wants chandef5 so it allocates ctx5 and sets `replaces` to ctx1.
> [ note ctx1 with its assigned vif1 and vif2 ]
> vif3 wants chandef5 so it reserves ctx5.
> vif4 wants chandef5 so it reserves ctx5.
> [ ctx2 could be considered now as "free" and could be set as
> `replaces` for ctx5 instead of ctx1 ]
> 
> It's pretty far fetched for now I'd say - are there any devices that
> support more than 2 channels now?

Not right now, afaik, but in theory I think our device could have such
cases. But it wouldn't have enough vifs ;-)

Somebody is working on ath9k multi-channel though I believe - will be
interesting to see what happens there.

> >> +     struct ieee80211_sub_if_data *sdata, *sdata_tmp;
> >> +     struct ieee80211_chanctx *new_ctx = NULL, *ctx, *ctx_tmp;
> >
> > I prefer if declarations with assignments are in lines of their own, but
> > maybe it's just me.
> 
> Actually fair point. I'm declaring multiple vars here..

I think the above is fine, but if you want to declare on multiple lines
that's OK too. Maybe just separate the one with from the ones without
initialization?

> >> +     list_for_each_entry(ctx, &local->chanctx_list, list) {
> >> +             if (!(ctx->replaces && !ctx->replaced_by))
> >> +                     continue;
> >
> > if (!ctx->replaces || ctx->replaced_by) looks more natural to me. (Same
> > thing for some other cases in this patch).
> 
> I find it easier to reason with my condition. But yeah. Popular vote? :-)

Uh, I find them almost equally unreadable, but then again I have no idea
what "replaces" and "replaced_by" means :-)

*looking*...

Oh, those are pointers. I thought they were bool, reviewing without
paying attention is the one case where using bools as pointers is no
good I guess ;-)

I'm assuming that "ctx->replaces && ctx->replaced_by" is always false,
right? Or are some sort of concurrent replacement chains possible? In
this case though I don't understand the logic at all - shouldn't it be
equivalent to just checking ctx->replaces then?

Maybe for such logic it would be easier to understand to have

enum {
	NOT_AFFECTED,
	WILL_BE_REPLACED,
	IS_REPLACEMENT,
} replacement_state;
struct chanctx *replace_ctx;

then you can replace (pun intended) this check with

	if (ctx->replacement_state != IS_REPLACEMENT)
		continue;

But maybe I'm misunderstanding something entirely.

> > Hadn't we decided to disconnect the vifs that didn't follow? Can't
> > remember anymore. :) But now you're just canceling the whole switch?
> 
> I'm not sure myself anymore either. This keeps the current behaviour
> (i.e. disconnect interfaces that are switching on failure). Johannes?

I think I'd prefer to disconnect the failing switch ones, rather than
the other ones - seems simpler (since we know which ones are affected)
and easier to understand as it's a more localized operation. Any
arguments for the other way?


> >> +     if (!(old_ctx->replaced_by && new_ctx->replaces)) {
> >
> > Isn't !old->ctx->replaced_by enough here? Do you really care if the
> > new_ctx will replace something else at this point?
> 
> Hmm..
> 
> Now that I think this should actually be just:
> 
>  if (!new_ctx->replaces) { ... }

Oh ... I think that's what I asked above. Nah. This check actually means
that it *is* possible that replaced_by and replaces are true at the same
time? *confused*

johannes


^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v7 3/5] mac80211: use chanctx reservation for AP CSA
  2014-05-29  7:34 ` [PATCH v7 3/5] mac80211: use chanctx reservation for AP CSA Michal Kazior
@ 2014-06-03 19:49   ` Johannes Berg
  0 siblings, 0 replies; 40+ messages in thread
From: Johannes Berg @ 2014-06-03 19:49 UTC (permalink / raw
  To: Michal Kazior; +Cc: linux-wireless, luca

On Thu, 2014-05-29 at 09:34 +0200, Michal Kazior wrote:
> Channel switch finalization is now 2-step. First
> step is when driver calls csa_finish(), the other
> is when reservation is actually finalized (which
> can be deferred for in-place reservation).
> 
> It is now safe to call ieee80211_csa_finish() more
> then once.

"more than once" :-)

johannes


^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v7 0/5] cfg/mac80211: implement multi-vif csa
  2014-05-29  7:34 [PATCH v7 0/5] cfg/mac80211: implement multi-vif csa Michal Kazior
                   ` (4 preceding siblings ...)
  2014-05-29  7:34 ` [PATCH v7 5/5] cfg80211: remove channel_switch combination check Michal Kazior
@ 2014-06-03 19:51 ` Johannes Berg
  2014-06-05 12:56 ` [PATCH v8 " Michal Kazior
  6 siblings, 0 replies; 40+ messages in thread
From: Johannes Berg @ 2014-06-03 19:51 UTC (permalink / raw
  To: Michal Kazior; +Cc: linux-wireless, luca

On Thu, 2014-05-29 at 09:34 +0200, Michal Kazior wrote:
> Hi,
> 
> Here's yet again a re-spin of my multi-vif csa
> patches. Hopefully I got everything right this
> time..

So there seems to be some discussion on patch 1, the rest looks fine (to
me, I'll let Luca chime in if he wants)

> I've delibarately not included documentation
> fixes. I think we can handle this with follow up
> patches.

Fair enough.

johannes


^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v7 1/5] mac80211: implement multi-vif in-place reservations
  2014-06-03 17:10       ` Johannes Berg
@ 2014-06-04 12:12         ` Michal Kazior
  2014-06-04 12:38           ` Johannes Berg
  0 siblings, 1 reply; 40+ messages in thread
From: Michal Kazior @ 2014-06-04 12:12 UTC (permalink / raw
  To: Johannes Berg; +Cc: Luca Coelho, linux-wireless

On 3 June 2014 19:10, Johannes Berg <johannes@sipsolutions.net> wrote:
> On Tue, 2014-06-03 at 08:15 +0200, Michal Kazior wrote:
>
>> Hmm... (my brain..)
>>
>> Consider the following: 3 ctx, 6 vifs. cannot create more ctx.
>> ctx1=vif1+vif2, ctx2=vif3+vif4, ctx3=vif5+vif6. ctx1..3 have
>> chandef1..3.
>>
>> vif5 wants chandef4 so it allocates ctx4 and sets `replaces` to ctx3.
>> vif6 wants chandef5 so it allocates ctx5 and sets `replaces` to ctx1.
>> [ note ctx1 with its assigned vif1 and vif2 ]
>> vif3 wants chandef5 so it reserves ctx5.
>> vif4 wants chandef5 so it reserves ctx5.
>> [ ctx2 could be considered now as "free" and could be set as
>> `replaces` for ctx5 instead of ctx1 ]
>>
>> It's pretty far fetched for now I'd say - are there any devices that
>> support more than 2 channels now?
>
> Not right now, afaik, but in theory I think our device could have such
> cases. But it wouldn't have enough vifs ;-)

Sounds "fun" ;-)


>> >> +     struct ieee80211_sub_if_data *sdata, *sdata_tmp;
>> >> +     struct ieee80211_chanctx *new_ctx = NULL, *ctx, *ctx_tmp;
>> >
>> > I prefer if declarations with assignments are in lines of their own, but
>> > maybe it's just me.
>>
>> Actually fair point. I'm declaring multiple vars here..
>
> I think the above is fine, but if you want to declare on multiple lines
> that's OK too. Maybe just separate the one with from the ones without
> initialization?

Sounds good as well.


>> >> +     list_for_each_entry(ctx, &local->chanctx_list, list) {
>> >> +             if (!(ctx->replaces && !ctx->replaced_by))
>> >> +                     continue;
>> >
>> > if (!ctx->replaces || ctx->replaced_by) looks more natural to me. (Same
>> > thing for some other cases in this patch).
>>
>> I find it easier to reason with my condition. But yeah. Popular vote? :-)
>
> Uh, I find them almost equally unreadable, but then again I have no idea
> what "replaces" and "replaced_by" means :-)
>
> *looking*...
>
> Oh, those are pointers. I thought they were bool, reviewing without
> paying attention is the one case where using bools as pointers is no
> good I guess ;-)
>
> I'm assuming that "ctx->replaces && ctx->replaced_by" is always false,
> right? Or are some sort of concurrent replacement chains possible? In
> this case though I don't understand the logic at all - shouldn't it be
> equivalent to just checking ctx->replaces then?
>
> Maybe for such logic it would be easier to understand to have
>
> enum {
>         NOT_AFFECTED,
>         WILL_BE_REPLACED,
>         IS_REPLACEMENT,
> } replacement_state;
> struct chanctx *replace_ctx;
>
> then you can replace (pun intended) this check with
>
>         if (ctx->replacement_state != IS_REPLACEMENT)
>                 continue;
>
> But maybe I'm misunderstanding something entirely.

Hmm.. Right now `replaces` and `replaced_by` shouldn't be both set at
the same time. It wouldn't even work with how
drv_switch_vif_chanctx(..., SWAP) is designed. It might make sense to
use the enum+pointer instead of 2 pointers, but I'm worried it might
complicate some conditions/access. I'll look into it.


>> > Hadn't we decided to disconnect the vifs that didn't follow? Can't
>> > remember anymore. :) But now you're just canceling the whole switch?
>>
>> I'm not sure myself anymore either. This keeps the current behaviour
>> (i.e. disconnect interfaces that are switching on failure). Johannes?
>
> I think I'd prefer to disconnect the failing switch ones, rather than
> the other ones - seems simpler (since we know which ones are affected)
> and easier to understand as it's a more localized operation. Any
> arguments for the other way?

I'm okay with this. One less thing for me to worry about fixing :-)


>> >> +     if (!(old_ctx->replaced_by && new_ctx->replaces)) {
>> >
>> > Isn't !old->ctx->replaced_by enough here? Do you really care if the
>> > new_ctx will replace something else at this point?
>>
>> Hmm..
>>
>> Now that I think this should actually be just:
>>
>>  if (!new_ctx->replaces) { ... }
>
> Oh ... I think that's what I asked above. Nah. This check actually means
> that it *is* possible that replaced_by and replaces are true at the same
> time? *confused*

If you refer to the original condition do note the *old_ctx* and *new_ctx*.

As I stated above for a single channel context both `replaces` and
`replaced_by` shouldn't be set at the same time.


Michał

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v7 1/5] mac80211: implement multi-vif in-place reservations
  2014-06-04 12:12         ` Michal Kazior
@ 2014-06-04 12:38           ` Johannes Berg
  0 siblings, 0 replies; 40+ messages in thread
From: Johannes Berg @ 2014-06-04 12:38 UTC (permalink / raw
  To: Michal Kazior; +Cc: Luca Coelho, linux-wireless

On Wed, 2014-06-04 at 14:12 +0200, Michal Kazior wrote:

> >> >> +     if (!(old_ctx->replaced_by && new_ctx->replaces)) {
> >> >
> >> > Isn't !old->ctx->replaced_by enough here? Do you really care if the
> >> > new_ctx will replace something else at this point?
> >>
> >> Hmm..
> >>
> >> Now that I think this should actually be just:
> >>
> >>  if (!new_ctx->replaces) { ... }
> >
> > Oh ... I think that's what I asked above. Nah. This check actually means
> > that it *is* possible that replaced_by and replaces are true at the same
> > time? *confused*
> 
> If you refer to the original condition do note the *old_ctx* and *new_ctx*.
> 
> As I stated above for a single channel context both `replaces` and
> `replaced_by` shouldn't be set at the same time.

Ah, right, I did mix that up, thanks for the clarification.

johannes


^ permalink raw reply	[flat|nested] 40+ messages in thread

* [PATCH v8 0/5] cfg/mac80211: implement multi-vif csa
  2014-05-29  7:34 [PATCH v7 0/5] cfg/mac80211: implement multi-vif csa Michal Kazior
                   ` (5 preceding siblings ...)
  2014-06-03 19:51 ` [PATCH v7 0/5] cfg/mac80211: implement multi-vif csa Johannes Berg
@ 2014-06-05 12:56 ` Michal Kazior
  2014-06-05 12:56   ` [PATCH v8 1/5] mac80211: implement multi-vif in-place reservations Michal Kazior
                     ` (5 more replies)
  6 siblings, 6 replies; 40+ messages in thread
From: Michal Kazior @ 2014-06-05 12:56 UTC (permalink / raw
  To: linux-wireless; +Cc: luca, johannes, Michal Kazior

Hi,

I am sooo getting used to re-spinning :-)

Hopefully I won't need to respin 42 times.


v8:
 * a lot of updates, see patch notes

Michal Kazior (5):
  mac80211: implement multi-vif in-place reservations
  mac80211: make check_combinations() aware of chanctx reservation
  mac80211: use chanctx reservation for AP CSA
  mac80211: use chanctx reservation for STA CSA
  cfg80211: remove channel_switch combination check

 include/net/cfg80211.h     |   7 +-
 include/net/mac80211.h     |   7 -
 net/mac80211/cfg.c         |  82 +++--
 net/mac80211/chan.c        | 727 ++++++++++++++++++++++++++++++++++++---------
 net/mac80211/ieee80211_i.h |  30 +-
 net/mac80211/mlme.c        |  99 +++---
 net/mac80211/util.c        |  44 ++-
 net/wireless/nl80211.c     |  11 -
 8 files changed, 771 insertions(+), 236 deletions(-)

-- 
1.8.5.3


^ permalink raw reply	[flat|nested] 40+ messages in thread

* [PATCH v8 1/5] mac80211: implement multi-vif in-place reservations
  2014-06-05 12:56 ` [PATCH v8 " Michal Kazior
@ 2014-06-05 12:56   ` Michal Kazior
  2014-06-09 16:27     ` Eliad Peller
  2014-06-05 12:56   ` [PATCH v8 2/5] mac80211: make check_combinations() aware of chanctx reservation Michal Kazior
                     ` (4 subsequent siblings)
  5 siblings, 1 reply; 40+ messages in thread
From: Michal Kazior @ 2014-06-05 12:56 UTC (permalink / raw
  To: linux-wireless; +Cc: luca, johannes, Michal Kazior

Multi-vif in-place reservations happen when
it is impossible to allocate more channel contexts
as indicated by interface combinations.

Such reservations are not finalized until all
assigned interfaces are ready.

This still doesn't handle all possible cases
(i.e. degradation of number of channels) properly.

Signed-off-by: Michal Kazior <michal.kazior@tieto.com>
---

Notes:
    v2:
     * use new_ctx instead of ctx [Eliad]
     * move recalcs after bss_conf is updated [Eliad]
    
    v4:
     * move recalc-radar before vif-chanctx-assign [Eliad]
     * move radar_required swapping before initial add_chanctx() [Eliad]
    
    v5:
     * move kfree_rcu() [Zhao Gang]
    
    v6:
     * use switch_vif_chanctx for incompat case with chanctx drivers [Johannes]
     * fix vlan chanctx copying
    
    v7:
     * major rework of the algorithm to support cross-context vif switches (e.g.
       when 2 contexts depend on each others reserved chandefs and both need
       in-place switch)
     * create dummy chanctx structure for in-place reservations instead of storing
       everything in a single chanctx (new chanctx structure needs to be allocated
       sooner or later for drv_switch_vif_chanctx)
     * remove the ieee80211_vif_chanctx_reservation_complete() stub; it is now
       simply implemented by AP CSA patch [Johannes]
     * fix lines with over 80 chars
    
    v8:
     * implement assign reservation (i.e. when old_ctx is NULL) [Luca]
     * expand the TODO description for context shuffling [Luca]
     * add missing !list_empty() when reserving in-place [Luca]
     * fix independ[ae]nt typo [Luca]
     * add comment-labels in the reserved_switch() [Luca]
     * relax in-place verification to allow racy switches to succeed [Luca]
     * refine some comments [Luca]
     * remove WARN_ON for re-assign reservations after in-place reservation [Luca]
     * simplify re-assign reservation condition [Luca]
     * do not propagate error for in-place reservation failure if it was a result
       of a depending old_ctx being ready by re-assign of another vif [Luca]
     * fix in-place reservation context/vif list clearing
     * replace replaces/replaced_by with replace_state(enum)/replace_ctx(pointer) [Johannes]
     * var declare/init cleanup [Luca/Johannes]
     * some condition fixups

 include/net/mac80211.h     |   7 -
 net/mac80211/chan.c        | 631 +++++++++++++++++++++++++++++++++++++++------
 net/mac80211/ieee80211_i.h |  25 +-
 net/mac80211/util.c        |   9 +-
 4 files changed, 581 insertions(+), 91 deletions(-)

diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 421b6ec..66040af 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -1600,12 +1600,6 @@ struct ieee80211_tx_control {
  *	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,
@@ -1637,7 +1631,6 @@ enum ieee80211_hw_flags {
 	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,
 };
 
 /**
diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
index 3702d64..2bac54a 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -63,6 +63,19 @@ static bool ieee80211_can_create_new_chanctx(struct ieee80211_local *local)
 	return ieee80211_num_chanctx(local) < ieee80211_max_num_channels(local);
 }
 
+static struct ieee80211_chanctx *
+ieee80211_vif_get_chanctx(struct ieee80211_sub_if_data *sdata)
+{
+	struct ieee80211_chanctx_conf *conf;
+
+	conf = rcu_dereference_protected(sdata->vif.chanctx_conf,
+					 lockdep_is_held(&local->chanctx_mtx));
+	if (!conf)
+		return NULL;
+
+	return container_of(conf, struct ieee80211_chanctx, conf);
+}
+
 static const struct cfg80211_chan_def *
 ieee80211_chanctx_reserved_chandef(struct ieee80211_local *local,
 				   struct ieee80211_chanctx *ctx,
@@ -160,6 +173,9 @@ ieee80211_find_reservation_chanctx(struct ieee80211_local *local,
 		return NULL;
 
 	list_for_each_entry(ctx, &local->chanctx_list, list) {
+		if (ctx->replace_state == IEEE80211_CHANCTX_WILL_BE_REPLACED)
+			continue;
+
 		if (ctx->mode == IEEE80211_CHANCTX_EXCLUSIVE)
 			continue;
 
@@ -347,6 +363,9 @@ ieee80211_find_chanctx(struct ieee80211_local *local,
 	list_for_each_entry(ctx, &local->chanctx_list, list) {
 		const struct cfg80211_chan_def *compat;
 
+		if (ctx->replace_state != IEEE80211_CHANCTX_REPLACE_NONE)
+			continue;
+
 		if (ctx->mode == IEEE80211_CHANCTX_EXCLUSIVE)
 			continue;
 
@@ -898,8 +917,25 @@ int ieee80211_vif_unreserve_chanctx(struct ieee80211_sub_if_data *sdata)
 	list_del(&sdata->reserved_chanctx_list);
 	sdata->reserved_chanctx = NULL;
 
-	if (ieee80211_chanctx_refcount(sdata->local, ctx) == 0)
-		ieee80211_free_chanctx(sdata->local, ctx);
+	if (ieee80211_chanctx_refcount(sdata->local, ctx) == 0) {
+		if (ctx->replace_state == IEEE80211_CHANCTX_REPLACES_OTHER) {
+			if (WARN_ON(!ctx->replace_ctx))
+				return -EINVAL;
+
+			WARN_ON(ctx->replace_ctx->replace_state !=
+			        IEEE80211_CHANCTX_WILL_BE_REPLACED);
+			WARN_ON(ctx->replace_ctx->replace_ctx != ctx);
+
+			ctx->replace_ctx->replace_ctx = NULL;
+			ctx->replace_ctx->replace_state =
+					IEEE80211_CHANCTX_REPLACE_NONE;
+
+			list_del_rcu(&ctx->list);
+			kfree_rcu(ctx, rcu_head);
+		} else {
+			ieee80211_free_chanctx(sdata->local, ctx);
+		}
+	}
 
 	return 0;
 }
@@ -910,40 +946,82 @@ int ieee80211_vif_reserve_chanctx(struct ieee80211_sub_if_data *sdata,
 				  bool radar_required)
 {
 	struct ieee80211_local *local = sdata->local;
-	struct ieee80211_chanctx_conf *conf;
-	struct ieee80211_chanctx *new_ctx, *curr_ctx;
-	int ret = 0;
-
-	mutex_lock(&local->chanctx_mtx);
+	struct ieee80211_chanctx *new_ctx, *curr_ctx, *ctx;
 
-	conf = rcu_dereference_protected(sdata->vif.chanctx_conf,
-					 lockdep_is_held(&local->chanctx_mtx));
-	if (!conf) {
-		ret = -EINVAL;
-		goto out;
-	}
+	lockdep_assert_held(&local->chanctx_mtx);
 
-	curr_ctx = container_of(conf, struct ieee80211_chanctx, conf);
+	curr_ctx = ieee80211_vif_get_chanctx(sdata);
+	if (curr_ctx && local->use_chanctx && !local->ops->switch_vif_chanctx)
+		return -ENOTSUPP;
 
 	new_ctx = ieee80211_find_reservation_chanctx(local, chandef, mode);
 	if (!new_ctx) {
-		if (ieee80211_chanctx_refcount(local, curr_ctx) == 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 if (ieee80211_can_create_new_chanctx(local)) {
-			/* create a new context and reserve it */
+		if (ieee80211_can_create_new_chanctx(local)) {
 			new_ctx = ieee80211_new_chanctx(local, chandef, mode);
-			if (IS_ERR(new_ctx)) {
-				ret = PTR_ERR(new_ctx);
-				goto out;
-			}
+			if (IS_ERR(new_ctx))
+				return PTR_ERR(new_ctx);
 		} else {
-			ret = -EBUSY;
-			goto out;
+			if ((curr_ctx->replace_state ==
+			     IEEE80211_CHANCTX_WILL_BE_REPLACED) ||
+			    !list_empty(&curr_ctx->reserved_vifs)) {
+				/*
+				 * Another vif already requested this context
+				 * for a reservation. Find another one hoping
+				 * all vifs assigned to it will also switch
+				 * soon enough.
+				 *
+				 * TODO: This needs a little more work as some
+				 * cases (more than 2 chanctx capable devices)
+				 * may fail which could otherwise succeed
+				 * provided some channel context juggling was
+				 * performed.
+				 *
+				 * Consider ctx1..3, vif1..6, each ctx has 2
+				 * vifs. vif1 and vif2 from ctx1 request new
+				 * different chandefs starting 2 in-place
+				 * reserations with ctx4 and ctx5 replacing
+				 * ctx1 and ctx2 respectively. Next vif5 and
+				 * vif6 from ctx3 reserve ctx4. If vif3 and
+				 * vif4 remain on ctx2 as they are then this
+				 * fails unless `replace_ctx` from ctx5 is
+				 * replaced with ctx3.
+				 */
+				list_for_each_entry(ctx, &local->chanctx_list,
+						    list) {
+					if (ctx->replace_state !=
+					    IEEE80211_CHANCTX_REPLACE_NONE)
+						continue;
+
+					if (!list_empty(&ctx->reserved_vifs))
+						continue;
+
+					curr_ctx = ctx;
+					break;
+				}
+			}
+
+			/*
+			 * If that's true then all available contexts already
+			 * have reservations and cannot be used.
+			 */
+			if ((curr_ctx->replace_state ==
+			     IEEE80211_CHANCTX_WILL_BE_REPLACED) ||
+			    !list_empty(&curr_ctx->reserved_vifs))
+				return -EBUSY;
+
+			new_ctx = ieee80211_alloc_chanctx(local, chandef, mode);
+			if (!new_ctx)
+				return -ENOMEM;
+
+			new_ctx->replace_ctx = curr_ctx;
+			new_ctx->replace_state =
+					IEEE80211_CHANCTX_REPLACES_OTHER;
+
+			curr_ctx->replace_ctx = new_ctx;
+			curr_ctx->replace_state =
+					IEEE80211_CHANCTX_WILL_BE_REPLACED;
+
+			list_add_rcu(&new_ctx->list, &local->chanctx_list);
 		}
 	}
 
@@ -951,82 +1029,473 @@ int ieee80211_vif_reserve_chanctx(struct ieee80211_sub_if_data *sdata,
 	sdata->reserved_chanctx = new_ctx;
 	sdata->reserved_chandef = *chandef;
 	sdata->reserved_radar_required = radar_required;
-out:
-	mutex_unlock(&local->chanctx_mtx);
-	return ret;
+	sdata->reserved_ready = false;
+
+	return 0;
 }
 
-int ieee80211_vif_use_reserved_context(struct ieee80211_sub_if_data *sdata,
-				       u32 *changed)
+static int
+ieee80211_vif_use_reserved_reassign(struct ieee80211_sub_if_data *sdata)
 {
 	struct ieee80211_local *local = sdata->local;
-	struct ieee80211_chanctx *ctx;
-	struct ieee80211_chanctx *old_ctx;
-	struct ieee80211_chanctx_conf *conf;
-	int ret;
-	u32 tmp_changed = *changed;
-
-	/* TODO: need to recheck if the chandef is usable etc.? */
+	struct ieee80211_vif_chanctx_switch vif_chsw[1] = {};
+	struct ieee80211_chanctx *old_ctx, *new_ctx;
+	const struct cfg80211_chan_def *chandef;
+	u32 changed = 0;
+	int err;
 
 	lockdep_assert_held(&local->mtx);
+	lockdep_assert_held(&local->chanctx_mtx);
 
-	mutex_lock(&local->chanctx_mtx);
+	new_ctx = sdata->reserved_chanctx;
+	old_ctx = ieee80211_vif_get_chanctx(sdata);
 
-	ctx = sdata->reserved_chanctx;
-	if (WARN_ON(!ctx)) {
-		ret = -EINVAL;
-		goto out;
-	}
+	if (WARN_ON(!sdata->reserved_ready))
+		return -EBUSY;
 
-	conf = rcu_dereference_protected(sdata->vif.chanctx_conf,
-					 lockdep_is_held(&local->chanctx_mtx));
-	if (!conf) {
-		ret = -EINVAL;
-		goto out;
+	if (WARN_ON(!new_ctx))
+		return -EINVAL;
+
+	if (WARN_ON(!old_ctx))
+		return -EINVAL;
+
+	if (WARN_ON(new_ctx->replace_state ==
+		    IEEE80211_CHANCTX_REPLACES_OTHER))
+		return -EINVAL;
+
+	chandef = ieee80211_chanctx_non_reserved_chandef(local, new_ctx,
+				&sdata->reserved_chandef);
+	if (WARN_ON(!chandef))
+		return -EINVAL;
+
+	vif_chsw[0].vif = &sdata->vif;
+	vif_chsw[0].old_ctx = &old_ctx->conf;
+	vif_chsw[0].new_ctx = &new_ctx->conf;
+
+	list_del(&sdata->reserved_chanctx_list);
+	sdata->reserved_chanctx = NULL;
+
+	err = drv_switch_vif_chanctx(local, vif_chsw, 1,
+				     CHANCTX_SWMODE_REASSIGN_VIF);
+	if (err) {
+		if (ieee80211_chanctx_refcount(local, new_ctx) == 0)
+			ieee80211_free_chanctx(local, new_ctx);
+
+		return err;
 	}
 
-	old_ctx = container_of(conf, struct ieee80211_chanctx, conf);
+	list_move(&sdata->assigned_chanctx_list, &new_ctx->assigned_vifs);
+
+	if (sdata->vif.type == NL80211_IFTYPE_AP)
+		__ieee80211_vif_copy_chanctx_to_vlans(sdata, false);
+
+	if (ieee80211_chanctx_refcount(local, old_ctx) == 0)
+		ieee80211_free_chanctx(local, old_ctx);
 
 	if (sdata->vif.bss_conf.chandef.width != sdata->reserved_chandef.width)
-		tmp_changed |= BSS_CHANGED_BANDWIDTH;
+		changed = BSS_CHANGED_BANDWIDTH;
 
 	sdata->vif.bss_conf.chandef = sdata->reserved_chandef;
 
-	/* unref our reservation */
-	sdata->reserved_chanctx = NULL;
-	sdata->radar_required = sdata->reserved_radar_required;
+	if (changed)
+		ieee80211_bss_info_change_notify(sdata, changed);
+
+	return err;
+}
+
+static int
+ieee80211_vif_use_reserved_assign(struct ieee80211_sub_if_data *sdata)
+{
+	struct ieee80211_local *local = sdata->local;
+	struct ieee80211_chanctx *old_ctx, *new_ctx;
+	const struct cfg80211_chan_def *chandef;
+	int err;
+
+	old_ctx = ieee80211_vif_get_chanctx(sdata);
+	new_ctx = sdata->reserved_chanctx;
+
+	if (WARN_ON(!sdata->reserved_ready))
+		return -EINVAL;
+
+	if (WARN_ON(old_ctx))
+		return -EINVAL;
+
+	if (WARN_ON(!new_ctx))
+		return -EINVAL;
+
+	if (WARN_ON(new_ctx->replace_state ==
+		    IEEE80211_CHANCTX_REPLACES_OTHER))
+		return -EINVAL;
+
+	chandef = ieee80211_chanctx_non_reserved_chandef(local, new_ctx,
+				&sdata->reserved_chandef);
+	if (WARN_ON(!chandef))
+		return -EINVAL;
+
 	list_del(&sdata->reserved_chanctx_list);
+	sdata->reserved_chanctx = NULL;
 
-	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;
+	err = ieee80211_assign_vif_chanctx(sdata, new_ctx);
+	if (err) {
+		if (ieee80211_chanctx_refcount(local, new_ctx) == 0)
+			ieee80211_free_chanctx(local, new_ctx);
+
+		goto out;
+	}
+
+out:
+	return err;
+}
+
+static bool
+ieee80211_vif_has_in_place_reservation(struct ieee80211_sub_if_data *sdata)
+{
+	struct ieee80211_chanctx *old_ctx, *new_ctx;
+
+	lockdep_assert_held(&sdata->local->chanctx_mtx);
+
+	new_ctx = sdata->reserved_chanctx;
+	old_ctx = ieee80211_vif_get_chanctx(sdata);
+
+	if (!old_ctx)
+		return false;
+
+	if (WARN_ON(!new_ctx))
+		return false;
+
+	if (old_ctx->replace_state != IEEE80211_CHANCTX_WILL_BE_REPLACED)
+		return false;
+
+	if (new_ctx->replace_state != IEEE80211_CHANCTX_REPLACES_OTHER)
+		return false;
+
+	return true;
+}
+
+static int
+ieee80211_vif_use_reserved_switch(struct ieee80211_local *local)
+{
+	struct ieee80211_sub_if_data *sdata, *sdata_tmp;
+	struct ieee80211_chanctx *ctx, *ctx_tmp;
+	struct ieee80211_chanctx *new_ctx = NULL;
+	struct ieee80211_vif_chanctx_switch *vif_chsw = NULL;
+	const struct cfg80211_chan_def *chandef;
+	int i, err;
+	int n_ctx = 0, n_vifs = 0, n_assigned = 0, n_reserved = 0, n_ready = 0;
+
+	lockdep_assert_held(&local->mtx);
+	lockdep_assert_held(&local->chanctx_mtx);
+
+	/*
+	 * If there are 2 independent pairs of channel contexts performing
+	 * cross-switch of their vifs this code will still wait until both are
+	 * ready even though it could be possible to switch one before the
+	 * other is ready.
+	 *
+	 * For practical reasons and code simplicity just do a single huge
+	 * switch.
+	 */
+
+	/*
+	 * Verify if the reservation is still feasible.
+	 *  - if it's not then disconnect
+	 *  - if it is but not all vifs necessary are ready then defer
+	 */
+
+	list_for_each_entry(ctx, &local->chanctx_list, list) {
+		if (ctx->replace_state != IEEE80211_CHANCTX_REPLACES_OTHER)
+			continue;
+
+		if (WARN_ON(!ctx->replace_ctx))
+			return -EINVAL;
+
+		if (!local->use_chanctx)
+			new_ctx = ctx;
+
+		n_ctx++;
+
+		list_for_each_entry(sdata, &ctx->replace_ctx->assigned_vifs,
+				    assigned_chanctx_list) {
+			n_assigned++;
+			if (sdata->reserved_chanctx) {
+				n_reserved++;
+				if (sdata->reserved_ready)
+					n_ready++;
+			}
+		}
+
+		if (n_assigned != n_reserved) {
+			if (n_ready == n_reserved) {
+				wiphy_info(local->hw.wiphy,
+					   "channel context reservation cannot be finalized because some interfaces aren't switching\n");
+				err = -EBUSY;
+				goto err;
+			}
+
+			return -EAGAIN;
+		}
+
+		list_for_each_entry(sdata, &ctx->reserved_vifs,
+				    reserved_chanctx_list) {
+			if (!ieee80211_vif_has_in_place_reservation(sdata))
+				continue;
+
+			if (!sdata->reserved_ready)
+				return -EAGAIN;
+
+			n_vifs++;
+
+			if (sdata->reserved_radar_required)
+				ctx->conf.radar_enabled = true;
+		}
+	}
+
+	if (WARN_ON(n_ctx == 0) ||
+	    WARN_ON(n_vifs == 0) ||
+	    WARN_ON(n_ctx > 1 && !local->use_chanctx) ||
+	    WARN_ON(!new_ctx && !local->use_chanctx)) {
+		err = -EINVAL;
+		goto err;
+	}
+
+	/*
+	 * All necessary vifs are ready. Perform the switch now.
+	 */
+
+	if (local->use_chanctx) {
+		vif_chsw = kzalloc(sizeof(*vif_chsw) * n_vifs, GFP_KERNEL);
+		if (vif_chsw) {
+			err = -ENOMEM;
+			goto err;
+		}
+
+		i = 0;
+		list_for_each_entry(ctx, &local->chanctx_list, list) {
+			if (ctx->replace_state != IEEE80211_CHANCTX_REPLACES_OTHER)
+				continue;
+
+			if (WARN_ON(!ctx->replace_ctx))
+				return -EINVAL;
+
+			list_for_each_entry(sdata, &ctx->reserved_vifs,
+					    reserved_chanctx_list) {
+				if (!ieee80211_vif_has_in_place_reservation(
+						sdata))
+					continue;
+
+				vif_chsw[i].vif = &sdata->vif;
+				vif_chsw[i].old_ctx = &ctx->replace_ctx->conf;
+				vif_chsw[i].new_ctx = &ctx->conf;
+
+				i++;
+			}
+		}
+
+		err = drv_switch_vif_chanctx(local, vif_chsw, n_vifs,
+					     CHANCTX_SWMODE_SWAP_CONTEXTS);
+		kfree(vif_chsw);
+
+		if (err)
+			goto err;
 	} else {
-		ret = ieee80211_assign_vif_chanctx(sdata, ctx);
-		if (ieee80211_chanctx_refcount(local, old_ctx) == 0)
-			ieee80211_free_chanctx(local, old_ctx);
-		if (ret) {
-			/* if assign fails refcount stays the same */
-			if (ieee80211_chanctx_refcount(local, ctx) == 0)
-				ieee80211_free_chanctx(local, ctx);
-			goto out;
+		chandef = ieee80211_chanctx_reserved_chandef(local, new_ctx,
+							     NULL);
+		if (WARN_ON(!chandef)) {
+			err = -EINVAL;
+			goto err;
 		}
 
-		if (sdata->vif.type == NL80211_IFTYPE_AP)
-			__ieee80211_vif_copy_chanctx_to_vlans(sdata, false);
+		local->hw.conf.radar_enabled = new_ctx->conf.radar_enabled;
+		local->_oper_chandef = *chandef;
+		ieee80211_hw_config(local, 0);
 	}
 
-	*changed = tmp_changed;
+	/*
+	 * Update all structures, values and pointers to point to new channel
+	 * context(s).
+	 */
 
-	ieee80211_recalc_chanctx_chantype(local, ctx);
-	ieee80211_recalc_smps_chanctx(local, ctx);
-	ieee80211_recalc_radar_chanctx(local, ctx);
-	ieee80211_recalc_chanctx_min_def(local, ctx);
-out:
-	mutex_unlock(&local->chanctx_mtx);
-	return ret;
+	i = 0;
+	list_for_each_entry(ctx, &local->chanctx_list, list) {
+		if (ctx->replace_state != IEEE80211_CHANCTX_REPLACES_OTHER)
+			continue;
+
+		if (WARN_ON(!ctx->replace_ctx))
+			return -EINVAL;
+
+		list_for_each_entry(sdata, &ctx->reserved_vifs,
+				    reserved_chanctx_list) {
+			u32 changed = 0;
+
+			if (!ieee80211_vif_has_in_place_reservation(sdata))
+				continue;
+
+			rcu_assign_pointer(sdata->vif.chanctx_conf, &ctx->conf);
+
+			if (sdata->vif.type == NL80211_IFTYPE_AP)
+				__ieee80211_vif_copy_chanctx_to_vlans(sdata,
+								      false);
+
+			sdata->radar_required = sdata->reserved_radar_required;
+
+			if (sdata->vif.bss_conf.chandef.width !=
+			    sdata->reserved_chandef.width)
+				changed = BSS_CHANGED_BANDWIDTH;
+
+			sdata->vif.bss_conf.chandef = sdata->reserved_chandef;
+			if (changed)
+				ieee80211_bss_info_change_notify(sdata,
+								 changed);
+
+			ieee80211_recalc_txpower(sdata);
+		}
+
+		ieee80211_recalc_chanctx_chantype(local, ctx);
+		ieee80211_recalc_smps_chanctx(local, ctx);
+		ieee80211_recalc_radar_chanctx(local, ctx);
+		ieee80211_recalc_chanctx_min_def(local, ctx);
+
+		list_for_each_entry_safe(sdata, sdata_tmp, &ctx->reserved_vifs,
+					 reserved_chanctx_list) {
+			if (ieee80211_vif_get_chanctx(sdata) != ctx)
+				continue;
+
+			list_del(&sdata->reserved_chanctx_list);
+			list_move(&sdata->assigned_chanctx_list,
+				  &new_ctx->assigned_vifs);
+			sdata->reserved_chanctx = NULL;
+		}
+
+		/*
+		 * This context might have been a dependency for an already
+		 * ready re-assign reservation interface that was deferred. Do
+		 * not propagate error to the caller though. The in-place
+		 * reservation for originally requested interface has already
+		 * succeeded at this point.
+		 */
+		list_for_each_entry(sdata, &ctx->reserved_vifs,
+				    reserved_chanctx_list) {
+			if (WARN_ON(ieee80211_vif_has_in_place_reservation(
+					sdata)))
+				continue;
+
+			if (WARN_ON(sdata->reserved_chanctx != ctx))
+				continue;
+
+			if (!sdata->reserved_ready)
+				continue;
+
+			if (ieee80211_vif_get_chanctx(sdata))
+				err = ieee80211_vif_use_reserved_reassign(
+						sdata);
+			else
+				err = ieee80211_vif_use_reserved_assign(sdata);
+
+			if (err) {
+				sdata_info(sdata,
+					   "failed to finalize (re-)assign reservation (err=%d)\n",
+					   err);
+				ieee80211_vif_unreserve_chanctx(sdata);
+				cfg80211_stop_iface(local->hw.wiphy,
+						    &sdata->wdev,
+						    GFP_KERNEL);
+			}
+		}
+	}
+
+	/*
+	 * Finally free old contexts
+	 */
+
+	list_for_each_entry_safe(ctx, ctx_tmp, &local->chanctx_list, list) {
+		if (ctx->replace_state != IEEE80211_CHANCTX_WILL_BE_REPLACED)
+			continue;
+
+		ctx->replace_ctx->replace_ctx = NULL;
+		ctx->replace_ctx->replace_state =
+				IEEE80211_CHANCTX_REPLACE_NONE;
+
+		list_del_rcu(&ctx->list);
+		kfree_rcu(ctx, rcu_head);
+	}
+
+	return 0;
+
+err:
+	list_for_each_entry(ctx, &local->chanctx_list, list) {
+		if (ctx->replace_state != IEEE80211_CHANCTX_REPLACES_OTHER)
+			continue;
+
+		list_for_each_entry_safe(sdata, sdata_tmp, &ctx->reserved_vifs,
+					 reserved_chanctx_list)
+			ieee80211_vif_unreserve_chanctx(sdata);
+	}
+
+	return err;
+}
+
+int ieee80211_vif_use_reserved_context(struct ieee80211_sub_if_data *sdata)
+{
+	struct ieee80211_local *local = sdata->local;
+	struct ieee80211_chanctx *new_ctx;
+	struct ieee80211_chanctx *old_ctx;
+	int err;
+
+	lockdep_assert_held(&local->mtx);
+	lockdep_assert_held(&local->chanctx_mtx);
+
+	new_ctx = sdata->reserved_chanctx;
+	old_ctx = ieee80211_vif_get_chanctx(sdata);
+
+	if (WARN_ON(!new_ctx))
+		return -EINVAL;
+
+	if (WARN_ON(new_ctx->replace_state ==
+		    IEEE80211_CHANCTX_WILL_BE_REPLACED))
+		return -EINVAL;
+
+	if (WARN_ON(sdata->reserved_ready))
+		return -EINVAL;
+
+	sdata->reserved_ready = true;
+
+	if (new_ctx->replace_state == IEEE80211_CHANCTX_REPLACE_NONE) {
+		if (old_ctx)
+			err = ieee80211_vif_use_reserved_reassign(sdata);
+		else
+			err = ieee80211_vif_use_reserved_assign(sdata);
+
+		if (err)
+			return err;
+	}
+
+	/*
+	 * In-place reservation may need to be finalized now either if:
+	 *  a) sdata is taking part in the swapping itself and is the last one
+	 *  b) sdata has switched with a re-assign reservation to an existing
+	 *     context readying in-place switching of old_ctx
+	 *
+	 * In case of (b) do not propagate the error up because the requested
+	 * sdata already switched successfully. Just spill an extra warning.
+	 * The ieee80211_vif_use_reserved_switch() already stops all necessary
+	 * interfaces upon failure.
+	 */
+	if ((old_ctx &&
+	     old_ctx->replace_state == IEEE80211_CHANCTX_WILL_BE_REPLACED) ||
+	    new_ctx->replace_state == IEEE80211_CHANCTX_REPLACES_OTHER) {
+		err = ieee80211_vif_use_reserved_switch(local);
+		if (err && err != -EAGAIN) {
+			if (new_ctx->replace_state ==
+			    IEEE80211_CHANCTX_REPLACES_OTHER)
+				return err;
+
+			wiphy_info(local->hw.wiphy,
+				   "depending in-place reservation failed (err=%d)\n",
+				   err);
+		}
+	}
+
+	return 0;
 }
 
 int ieee80211_vif_change_bandwidth(struct ieee80211_sub_if_data *sdata,
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 998c18c..aa23193 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -700,6 +700,24 @@ enum ieee80211_chanctx_mode {
 	IEEE80211_CHANCTX_EXCLUSIVE
 };
 
+/**
+ * enum ieee80211_chanctx_replace_state - channel context replacement state
+ *
+ * This is used for channel context in-place reservations that require channel
+ * context switch/swap.
+ *
+ * @IEEE80211_CHANCTX_REPLACE_NONE: no replacement is taking place
+ * @IEEE80211_CHANCTX_WILL_BE_REPLACED: this channel context will be replaced
+ *	by a (not yet registered) channel context pointed by %replace_ctx.
+ * @IEEE80211_CHANCTX_REPLACES_OTHER: this (not yet registered) channel context
+ *	replaces an existing channel context pointed to by %replace_ctx.
+ */
+enum ieee80211_chanctx_replace_state {
+	IEEE80211_CHANCTX_REPLACE_NONE,
+	IEEE80211_CHANCTX_WILL_BE_REPLACED,
+	IEEE80211_CHANCTX_REPLACES_OTHER,
+};
+
 struct ieee80211_chanctx {
 	struct list_head list;
 	struct rcu_head rcu_head;
@@ -707,6 +725,9 @@ struct ieee80211_chanctx {
 	struct list_head assigned_vifs;
 	struct list_head reserved_vifs;
 
+	enum ieee80211_chanctx_replace_state replace_state;
+	struct ieee80211_chanctx *replace_ctx;
+
 	enum ieee80211_chanctx_mode mode;
 	bool driver_present;
 
@@ -777,6 +798,7 @@ struct ieee80211_sub_if_data {
 	struct ieee80211_chanctx *reserved_chanctx;
 	struct cfg80211_chan_def reserved_chandef;
 	bool reserved_radar_required;
+	bool reserved_ready;
 
 	/* used to reconfigure hardware SM PS */
 	struct work_struct recalc_smps;
@@ -1800,8 +1822,7 @@ ieee80211_vif_reserve_chanctx(struct ieee80211_sub_if_data *sdata,
 			      enum ieee80211_chanctx_mode mode,
 			      bool radar_required);
 int __must_check
-ieee80211_vif_use_reserved_context(struct ieee80211_sub_if_data *sdata,
-				   u32 *changed);
+ieee80211_vif_use_reserved_context(struct ieee80211_sub_if_data *sdata);
 int ieee80211_vif_unreserve_chanctx(struct ieee80211_sub_if_data *sdata);
 
 int __must_check
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index 7e0dd4b..8e661ea 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -1603,7 +1603,9 @@ int ieee80211_reconfig(struct ieee80211_local *local)
 	if (local->use_chanctx) {
 		mutex_lock(&local->chanctx_mtx);
 		list_for_each_entry(ctx, &local->chanctx_list, list)
-			WARN_ON(drv_add_chanctx(local, ctx));
+			if (ctx->replace_state !=
+			    IEEE80211_CHANCTX_REPLACES_OTHER)
+				WARN_ON(drv_add_chanctx(local, ctx));
 		mutex_unlock(&local->chanctx_mtx);
 
 		list_for_each_entry(sdata, &local->interfaces, list) {
@@ -2876,6 +2878,8 @@ int ieee80211_check_combinations(struct ieee80211_sub_if_data *sdata,
 		num[iftype] = 1;
 
 	list_for_each_entry(ctx, &local->chanctx_list, list) {
+		if (ctx->replace_state == IEEE80211_CHANCTX_WILL_BE_REPLACED)
+			continue;
 		if (ctx->conf.radar_enabled)
 			radar_detect |= BIT(ctx->conf.def.width);
 		if (ctx->mode == IEEE80211_CHANCTX_EXCLUSIVE) {
@@ -2934,6 +2938,9 @@ int ieee80211_max_num_channels(struct ieee80211_local *local)
 	lockdep_assert_held(&local->chanctx_mtx);
 
 	list_for_each_entry(ctx, &local->chanctx_list, list) {
+		if (ctx->replace_state == IEEE80211_CHANCTX_WILL_BE_REPLACED)
+			continue;
+
 		num_different_channels++;
 
 		if (ctx->conf.radar_enabled)
-- 
1.8.5.3


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [PATCH v8 2/5] mac80211: make check_combinations() aware of chanctx reservation
  2014-06-05 12:56 ` [PATCH v8 " Michal Kazior
  2014-06-05 12:56   ` [PATCH v8 1/5] mac80211: implement multi-vif in-place reservations Michal Kazior
@ 2014-06-05 12:56   ` Michal Kazior
  2014-06-05 12:56   ` [PATCH v8 3/5] mac80211: use chanctx reservation for AP CSA Michal Kazior
                     ` (3 subsequent siblings)
  5 siblings, 0 replies; 40+ messages in thread
From: Michal Kazior @ 2014-06-05 12:56 UTC (permalink / raw
  To: linux-wireless; +Cc: luca, johannes, Michal Kazior

The ieee80211_check_combinations() computes
radar_detect accordingly depending on chanctx
reservation status.

This makes it possible to use the function for
channel_switch validation.

Signed-off-by: Michal Kazior <michal.kazior@tieto.com>
---

Notes:
    v3:
     * fix typo in comment [Johannes]
     * fix comment style [Johannes]
    
    v7:
     * use replaces/replaced_by pointers
     * replace radar_detect calculation in ieee80211_max_num_channels() too
    
    v8:
     * use replace_state/replace_ctx instead of replaces/replaced_by (rebased)

 net/mac80211/util.c | 35 +++++++++++++++++++++++++++++++----
 1 file changed, 31 insertions(+), 4 deletions(-)

diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index 8e661ea..16dc720 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -2837,6 +2837,35 @@ void ieee80211_recalc_dtim(struct ieee80211_local *local,
 	ps->dtim_count = dtim_count;
 }
 
+static u8 ieee80211_chanctx_radar_detect(struct ieee80211_local *local,
+					 struct ieee80211_chanctx *ctx)
+{
+	struct ieee80211_sub_if_data *sdata;
+	u8 radar_detect = 0;
+
+	lockdep_assert_held(&local->chanctx_mtx);
+
+	if (WARN_ON(ctx->replace_state == IEEE80211_CHANCTX_WILL_BE_REPLACED))
+		return 0;
+
+	list_for_each_entry(sdata, &ctx->reserved_vifs, reserved_chanctx_list)
+		if (sdata->reserved_radar_required)
+			radar_detect |= BIT(sdata->reserved_chandef.width);
+
+	/*
+	 * An in-place reservation context should not have any assigned vifs
+	 * until it replaces the other context.
+	 */
+	WARN_ON(ctx->replace_state == IEEE80211_CHANCTX_REPLACES_OTHER &&
+		!list_empty(&ctx->assigned_vifs));
+
+	list_for_each_entry(sdata, &ctx->assigned_vifs, assigned_chanctx_list)
+		if (sdata->radar_required)
+			radar_detect |= BIT(sdata->vif.bss_conf.chandef.width);
+
+	return radar_detect;
+}
+
 int ieee80211_check_combinations(struct ieee80211_sub_if_data *sdata,
 				 const struct cfg80211_chan_def *chandef,
 				 enum ieee80211_chanctx_mode chanmode,
@@ -2880,8 +2909,7 @@ int ieee80211_check_combinations(struct ieee80211_sub_if_data *sdata,
 	list_for_each_entry(ctx, &local->chanctx_list, list) {
 		if (ctx->replace_state == IEEE80211_CHANCTX_WILL_BE_REPLACED)
 			continue;
-		if (ctx->conf.radar_enabled)
-			radar_detect |= BIT(ctx->conf.def.width);
+		radar_detect |= ieee80211_chanctx_radar_detect(local, ctx);
 		if (ctx->mode == IEEE80211_CHANCTX_EXCLUSIVE) {
 			num_different_channels++;
 			continue;
@@ -2943,8 +2971,7 @@ int ieee80211_max_num_channels(struct ieee80211_local *local)
 
 		num_different_channels++;
 
-		if (ctx->conf.radar_enabled)
-			radar_detect |= BIT(ctx->conf.def.width);
+		radar_detect |= ieee80211_chanctx_radar_detect(local, ctx);
 	}
 
 	list_for_each_entry_rcu(sdata, &local->interfaces, list)
-- 
1.8.5.3


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [PATCH v8 3/5] mac80211: use chanctx reservation for AP CSA
  2014-06-05 12:56 ` [PATCH v8 " Michal Kazior
  2014-06-05 12:56   ` [PATCH v8 1/5] mac80211: implement multi-vif in-place reservations Michal Kazior
  2014-06-05 12:56   ` [PATCH v8 2/5] mac80211: make check_combinations() aware of chanctx reservation Michal Kazior
@ 2014-06-05 12:56   ` Michal Kazior
  2014-06-05 12:56   ` [PATCH v8 4/5] mac80211: use chanctx reservation for STA CSA Michal Kazior
                     ` (2 subsequent siblings)
  5 siblings, 0 replies; 40+ messages in thread
From: Michal Kazior @ 2014-06-05 12:56 UTC (permalink / raw
  To: linux-wireless; +Cc: luca, johannes, Michal Kazior

Channel switch finalization is now 2-step. First
step is when driver calls csa_finish(), the other
is when reservation is actually finalized (which
can be deferred for in-place reservation).

It is now safe to call ieee80211_csa_finish() more
than once.

Signed-off-by: Michal Kazior <michal.kazior@tieto.com>
---

Notes:
    v3:
     * fix lockdep typo s/mtx/chanctx_mtx/ [Johannes]
     * fix comment style [Johannes]
     * use goto for cleaner unlocking/returning [Johannes]
     * squash with ieee80211_vif_change_channel() removal patch [Johannes]
     * fix commit message [Johannes]
     * add resilience for multiple ieee80211_csa_finish() calls
    
    v4:
     * split removal of ieee80211_vif_change_channel()
    
    v7:
     * tune commit message
     * put the initial ieee80211_vif_chanctx_reservation_complete()
       implementation here [Johannes]
     * remove 'default' case and use all iftypes explicitly and WARN_ON
    
    v8:
     * fix typo in commit message [Johannes]

 net/mac80211/cfg.c  | 82 +++++++++++++++++++++++++++++++++++------------------
 net/mac80211/chan.c | 35 +++++++++++++++++++++--
 2 files changed, 88 insertions(+), 29 deletions(-)

diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index c17e624..787254e 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -3126,17 +3126,35 @@ static int __ieee80211_csa_finalize(struct ieee80211_sub_if_data *sdata)
 
 	sdata_assert_lock(sdata);
 	lockdep_assert_held(&local->mtx);
+	lockdep_assert_held(&local->chanctx_mtx);
 
-	sdata->radar_required = sdata->csa_radar_required;
-	err = ieee80211_vif_change_channel(sdata, &changed);
-	if (err < 0)
-		return err;
+	/*
+	 * using reservation isn't immediate as it may be deferred until later
+	 * with multi-vif. once reservation is complete it will re-schedule the
+	 * work with no reserved_chanctx so verify chandef to check if it
+	 * completed successfully
+	 */
 
-	if (!local->use_chanctx) {
-		local->_oper_chandef = sdata->csa_chandef;
-		ieee80211_hw_config(local, 0);
+	if (sdata->reserved_chanctx) {
+		/*
+		 * with multi-vif csa driver may call ieee80211_csa_finish()
+		 * many times while waiting for other interfaces to use their
+		 * reservations
+		 */
+		if (sdata->reserved_ready)
+			return 0;
+
+		err = ieee80211_vif_use_reserved_context(sdata);
+		if (err)
+			return err;
+
+		return 0;
 	}
 
+	if (!cfg80211_chandef_identical(&sdata->vif.bss_conf.chandef,
+					&sdata->csa_chandef))
+		return -EINVAL;
+
 	sdata->vif.csa_active = false;
 
 	err = ieee80211_set_after_csa_beacon(sdata, &changed);
@@ -3172,6 +3190,7 @@ void ieee80211_csa_finalize_work(struct work_struct *work)
 
 	sdata_lock(sdata);
 	mutex_lock(&local->mtx);
+	mutex_lock(&local->chanctx_mtx);
 
 	/* AP might have been stopped while waiting for the lock. */
 	if (!sdata->vif.csa_active)
@@ -3183,6 +3202,7 @@ void ieee80211_csa_finalize_work(struct work_struct *work)
 	ieee80211_csa_finalize(sdata);
 
 unlock:
+	mutex_unlock(&local->chanctx_mtx);
 	mutex_unlock(&local->mtx);
 	sdata_unlock(sdata);
 }
@@ -3328,7 +3348,7 @@ __ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
 	struct ieee80211_local *local = sdata->local;
 	struct ieee80211_chanctx_conf *conf;
 	struct ieee80211_chanctx *chanctx;
-	int err, num_chanctx, changed = 0;
+	int err, changed = 0;
 
 	sdata_assert_lock(sdata);
 	lockdep_assert_held(&local->mtx);
@@ -3343,37 +3363,43 @@ __ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
 				       &sdata->vif.bss_conf.chandef))
 		return -EINVAL;
 
+	/* don't allow another channel switch if one is already active. */
+	if (sdata->vif.csa_active)
+		return -EBUSY;
+
 	mutex_lock(&local->chanctx_mtx);
 	conf = rcu_dereference_protected(sdata->vif.chanctx_conf,
 					 lockdep_is_held(&local->chanctx_mtx));
 	if (!conf) {
-		mutex_unlock(&local->chanctx_mtx);
-		return -EBUSY;
+		err = -EBUSY;
+		goto out;
 	}
 
-	/* don't handle for multi-VIF cases */
 	chanctx = container_of(conf, struct ieee80211_chanctx, conf);
-	if (ieee80211_chanctx_refcount(local, chanctx) > 1) {
-		mutex_unlock(&local->chanctx_mtx);
-		return -EBUSY;
+	if (!chanctx) {
+		err = -EBUSY;
+		goto out;
 	}
-	num_chanctx = 0;
-	list_for_each_entry_rcu(chanctx, &local->chanctx_list, list)
-		num_chanctx++;
-	mutex_unlock(&local->chanctx_mtx);
 
-	if (num_chanctx > 1)
-		return -EBUSY;
+	err = ieee80211_vif_reserve_chanctx(sdata, &params->chandef,
+					    chanctx->mode,
+					    params->radar_required);
+	if (err)
+		goto out;
 
-	/* don't allow another channel switch if one is already active. */
-	if (sdata->vif.csa_active)
-		return -EBUSY;
+	/* if reservation is invalid then this will fail */
+	err = ieee80211_check_combinations(sdata, NULL, chanctx->mode, 0);
+	if (err) {
+		ieee80211_vif_unreserve_chanctx(sdata);
+		goto out;
+	}
 
 	err = ieee80211_set_csa_beacon(sdata, params, &changed);
-	if (err)
-		return err;
+	if (err) {
+		ieee80211_vif_unreserve_chanctx(sdata);
+		goto out;
+	}
 
-	sdata->csa_radar_required = params->radar_required;
 	sdata->csa_chandef = params->chandef;
 	sdata->csa_block_tx = params->block_tx;
 	sdata->vif.csa_active = true;
@@ -3391,7 +3417,9 @@ __ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
 		ieee80211_csa_finalize(sdata);
 	}
 
-	return 0;
+out:
+	mutex_unlock(&local->chanctx_mtx);
+	return err;
 }
 
 int ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
index 2bac54a..fcbe9b9 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -1034,6 +1034,30 @@ int ieee80211_vif_reserve_chanctx(struct ieee80211_sub_if_data *sdata,
 	return 0;
 }
 
+static void
+ieee80211_vif_chanctx_reservation_complete(struct ieee80211_sub_if_data *sdata)
+{
+	switch (sdata->vif.type) {
+	case NL80211_IFTYPE_ADHOC:
+	case NL80211_IFTYPE_AP:
+	case NL80211_IFTYPE_MESH_POINT:
+		ieee80211_queue_work(&sdata->local->hw,
+				     &sdata->csa_finalize_work);
+		break;
+	case NL80211_IFTYPE_UNSPECIFIED:
+	case NL80211_IFTYPE_STATION:
+	case NL80211_IFTYPE_AP_VLAN:
+	case NL80211_IFTYPE_WDS:
+	case NL80211_IFTYPE_MONITOR:
+	case NL80211_IFTYPE_P2P_CLIENT:
+	case NL80211_IFTYPE_P2P_GO:
+	case NL80211_IFTYPE_P2P_DEVICE:
+	case NUM_NL80211_IFTYPES:
+		WARN_ON(1);
+		break;
+	}
+}
+
 static int
 ieee80211_vif_use_reserved_reassign(struct ieee80211_sub_if_data *sdata)
 {
@@ -1081,7 +1105,7 @@ ieee80211_vif_use_reserved_reassign(struct ieee80211_sub_if_data *sdata)
 		if (ieee80211_chanctx_refcount(local, new_ctx) == 0)
 			ieee80211_free_chanctx(local, new_ctx);
 
-		return err;
+		goto out;
 	}
 
 	list_move(&sdata->assigned_chanctx_list, &new_ctx->assigned_vifs);
@@ -1100,6 +1124,8 @@ ieee80211_vif_use_reserved_reassign(struct ieee80211_sub_if_data *sdata)
 	if (changed)
 		ieee80211_bss_info_change_notify(sdata, changed);
 
+out:
+	ieee80211_vif_chanctx_reservation_complete(sdata);
 	return err;
 }
 
@@ -1144,6 +1170,7 @@ ieee80211_vif_use_reserved_assign(struct ieee80211_sub_if_data *sdata)
 	}
 
 out:
+	ieee80211_vif_chanctx_reservation_complete(sdata);
 	return err;
 }
 
@@ -1364,6 +1391,8 @@ ieee80211_vif_use_reserved_switch(struct ieee80211_local *local)
 			list_move(&sdata->assigned_chanctx_list,
 				  &new_ctx->assigned_vifs);
 			sdata->reserved_chanctx = NULL;
+
+			ieee80211_vif_chanctx_reservation_complete(sdata);
 		}
 
 		/*
@@ -1427,8 +1456,10 @@ err:
 			continue;
 
 		list_for_each_entry_safe(sdata, sdata_tmp, &ctx->reserved_vifs,
-					 reserved_chanctx_list)
+					 reserved_chanctx_list) {
 			ieee80211_vif_unreserve_chanctx(sdata);
+			ieee80211_vif_chanctx_reservation_complete(sdata);
+		}
 	}
 
 	return err;
-- 
1.8.5.3


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [PATCH v8 4/5] mac80211: use chanctx reservation for STA CSA
  2014-06-05 12:56 ` [PATCH v8 " Michal Kazior
                     ` (2 preceding siblings ...)
  2014-06-05 12:56   ` [PATCH v8 3/5] mac80211: use chanctx reservation for AP CSA Michal Kazior
@ 2014-06-05 12:56   ` Michal Kazior
  2014-06-05 12:56   ` [PATCH v8 5/5] cfg80211: remove channel_switch combination check Michal Kazior
  2014-06-12 12:54   ` [PATCH v9 0/5] cfg/mac80211: implement multi-vif csa Michal Kazior
  5 siblings, 0 replies; 40+ messages in thread
From: Michal Kazior @ 2014-06-05 12:56 UTC (permalink / raw
  To: linux-wireless; +Cc: luca, johannes, Michal Kazior

Channel switch finalization is now 2-step. First
step is when driver calls chswitch_done(), the
other is when reservation is actually finalized
(which be defered for in-place reservation).

It is now safe to call ieee80211_chswitch_done()
more than once.

Also remove the ieee80211_vif_change_channel()
because it is no longer used.

Signed-off-by: Michal Kazior <michal.kazior@tieto.com>
---

Notes:
    v3:
     * fix comment style [Johannes]
     * add resilience for multiple ieee80211_chswitch_done() calls
    
    v3:
     * fix lockdep typo s/mtx/chanctx_mtx/ [Johannes]
     * fix comment style [Johannes]
     * use goto for cleaner unlocking/returning [Johannes]
     * squash with ieee80211_vif_change_channel() removal patch [Johannes]
     * fix commit message [Johannes]
     * add resilience for multiple ieee80211_csa_finish() calls
    
    v4:
     * squash with removal of ieee80211_vif_change_channel()
    
    v7:
     * add local->mtx locking when reserving (for ieee80211_chanctx_alloc)

 net/mac80211/chan.c        | 69 ++------------------------------
 net/mac80211/ieee80211_i.h |  5 ---
 net/mac80211/mlme.c        | 99 ++++++++++++++++++++++++++++++----------------
 3 files changed, 69 insertions(+), 104 deletions(-)

diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
index fcbe9b9..0b202b2 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -799,70 +799,6 @@ int ieee80211_vif_use_channel(struct ieee80211_sub_if_data *sdata,
 	return ret;
 }
 
-static int __ieee80211_vif_change_channel(struct ieee80211_sub_if_data *sdata,
-					  struct ieee80211_chanctx *ctx,
-					  u32 *changed)
-{
-	struct ieee80211_local *local = sdata->local;
-	const struct cfg80211_chan_def *chandef = &sdata->csa_chandef;
-	u32 chanctx_changed = 0;
-
-	if (!cfg80211_chandef_usable(sdata->local->hw.wiphy, chandef,
-				     IEEE80211_CHAN_DISABLED))
-		return -EINVAL;
-
-	if (ieee80211_chanctx_refcount(local, ctx) != 1)
-		return -EINVAL;
-
-	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);
-
-	ieee80211_recalc_chanctx_chantype(local, ctx);
-	ieee80211_recalc_smps_chanctx(local, ctx);
-	ieee80211_recalc_radar_chanctx(local, ctx);
-	ieee80211_recalc_chanctx_min_def(local, ctx);
-
-	return 0;
-}
-
-int ieee80211_vif_change_channel(struct ieee80211_sub_if_data *sdata,
-				 u32 *changed)
-{
-	struct ieee80211_local *local = sdata->local;
-	struct ieee80211_chanctx_conf *conf;
-	struct ieee80211_chanctx *ctx;
-	int ret;
-
-	lockdep_assert_held(&local->mtx);
-
-	/* should never be called if not performing a channel switch. */
-	if (WARN_ON(!sdata->vif.csa_active))
-		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);
-
-	ret = __ieee80211_vif_change_channel(sdata, ctx, changed);
- out:
-	mutex_unlock(&local->chanctx_mtx);
-	return ret;
-}
-
 static void
 __ieee80211_vif_copy_chanctx_to_vlans(struct ieee80211_sub_if_data *sdata,
 				      bool clear)
@@ -1044,8 +980,11 @@ ieee80211_vif_chanctx_reservation_complete(struct ieee80211_sub_if_data *sdata)
 		ieee80211_queue_work(&sdata->local->hw,
 				     &sdata->csa_finalize_work);
 		break;
-	case NL80211_IFTYPE_UNSPECIFIED:
 	case NL80211_IFTYPE_STATION:
+		ieee80211_queue_work(&sdata->local->hw,
+				     &sdata->u.mgd.chswitch_work);
+		break;
+	case NL80211_IFTYPE_UNSPECIFIED:
 	case NL80211_IFTYPE_AP_VLAN:
 	case NL80211_IFTYPE_WDS:
 	case NL80211_IFTYPE_MONITOR:
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index aa23193..34892a1 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -787,7 +787,6 @@ struct ieee80211_sub_if_data {
 	struct mac80211_qos_map __rcu *qos_map;
 
 	struct work_struct csa_finalize_work;
-	bool csa_radar_required;
 	bool csa_block_tx; /* write-protected by sdata_lock and local->mtx */
 	struct cfg80211_chan_def csa_chandef;
 
@@ -1829,10 +1828,6 @@ 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,
-			     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,
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index 7f073ef..baea348 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -940,51 +940,69 @@ static void ieee80211_chswitch_work(struct work_struct *work)
 		container_of(work, struct ieee80211_sub_if_data, u.mgd.chswitch_work);
 	struct ieee80211_local *local = sdata->local;
 	struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
-	u32 changed = 0;
 	int ret;
 
 	if (!ieee80211_sdata_running(sdata))
 		return;
 
 	sdata_lock(sdata);
+	mutex_lock(&local->mtx);
+	mutex_lock(&local->chanctx_mtx);
+
 	if (!ifmgd->associated)
 		goto out;
 
-	mutex_lock(&local->mtx);
-	ret = ieee80211_vif_change_channel(sdata, &changed);
-	mutex_unlock(&local->mtx);
-	if (ret) {
+	if (!sdata->vif.csa_active)
+		goto out;
+
+	/*
+	 * using reservation isn't immediate as it may be deferred until later
+	 * with multi-vif. once reservation is complete it will re-schedule the
+	 * work with no reserved_chanctx so verify chandef to check if it
+	 * completed successfully
+	 */
+
+	if (sdata->reserved_chanctx) {
+		/*
+		 * with multi-vif csa driver may call ieee80211_csa_finish()
+		 * many times while waiting for other interfaces to use their
+		 * reservations
+		 */
+		if (sdata->reserved_ready)
+			goto out;
+
+		ret = ieee80211_vif_use_reserved_context(sdata);
+		if (ret) {
+			sdata_info(sdata,
+				   "failed to use reserved channel context, disconnecting (err=%d)\n",
+				   ret);
+			ieee80211_queue_work(&sdata->local->hw,
+					     &ifmgd->csa_connection_drop_work);
+			goto out;
+		}
+
+		goto out;
+	}
+
+	if (!cfg80211_chandef_identical(&sdata->vif.bss_conf.chandef,
+					&sdata->csa_chandef)) {
 		sdata_info(sdata,
-			   "vif channel switch failed, disconnecting\n");
+			   "failed to finalize channel switch, disconnecting\n");
 		ieee80211_queue_work(&sdata->local->hw,
 				     &ifmgd->csa_connection_drop_work);
 		goto out;
 	}
 
-	if (!local->use_chanctx) {
-		local->_oper_chandef = sdata->csa_chandef;
-		/* Call "hw_config" only if doing sw channel switch.
-		 * Otherwise update the channel directly
-		 */
-		if (!local->ops->channel_switch)
-			ieee80211_hw_config(local, 0);
-		else
-			local->hw.conf.chandef = local->_oper_chandef;
-	}
-
 	/* XXX: shouldn't really modify cfg80211-owned data! */
 	ifmgd->associated->channel = sdata->csa_chandef.chan;
 
-	ieee80211_bss_info_change_notify(sdata, changed);
-
-	mutex_lock(&local->mtx);
 	sdata->vif.csa_active = false;
+
 	/* XXX: wait for a beacon first? */
 	if (!ieee80211_csa_needs_block_tx(local))
 		ieee80211_wake_queues_by_reason(&local->hw,
 					IEEE80211_MAX_QUEUE_MAP,
 					IEEE80211_QUEUE_STOP_REASON_CSA);
-	mutex_unlock(&local->mtx);
 
 	ifmgd->flags &= ~IEEE80211_STA_CSA_RECEIVED;
 
@@ -992,6 +1010,8 @@ static void ieee80211_chswitch_work(struct work_struct *work)
 	ieee80211_sta_reset_conn_monitor(sdata);
 
 out:
+	mutex_unlock(&local->chanctx_mtx);
+	mutex_unlock(&local->mtx);
 	sdata_unlock(sdata);
 }
 
@@ -1028,6 +1048,7 @@ ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,
 	struct ieee80211_local *local = sdata->local;
 	struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
 	struct cfg80211_bss *cbss = ifmgd->associated;
+	struct ieee80211_chanctx_conf *conf;
 	struct ieee80211_chanctx *chanctx;
 	enum ieee80211_band current_band;
 	struct ieee80211_csa_ie csa_ie;
@@ -1071,7 +1092,22 @@ ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,
 
 	ifmgd->flags |= IEEE80211_STA_CSA_RECEIVED;
 
+	mutex_lock(&local->mtx);
 	mutex_lock(&local->chanctx_mtx);
+	conf = rcu_dereference_protected(sdata->vif.chanctx_conf,
+					 lockdep_is_held(&local->chanctx_mtx));
+	if (!conf) {
+		sdata_info(sdata,
+			   "no channel context assigned to vif?, disconnecting\n");
+		ieee80211_queue_work(&local->hw,
+				     &ifmgd->csa_connection_drop_work);
+		mutex_unlock(&local->chanctx_mtx);
+		mutex_unlock(&local->mtx);
+		return;
+	}
+
+	chanctx = container_of(conf, struct ieee80211_chanctx, conf);
+
 	if (local->use_chanctx) {
 		u32 num_chanctx = 0;
 		list_for_each_entry(chanctx, &local->chanctx_list, list)
@@ -1084,32 +1120,27 @@ ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,
 			ieee80211_queue_work(&local->hw,
 					     &ifmgd->csa_connection_drop_work);
 			mutex_unlock(&local->chanctx_mtx);
+			mutex_unlock(&local->mtx);
 			return;
 		}
 	}
 
-	if (WARN_ON(!rcu_access_pointer(sdata->vif.chanctx_conf))) {
-		ieee80211_queue_work(&local->hw,
-				     &ifmgd->csa_connection_drop_work);
-		mutex_unlock(&local->chanctx_mtx);
-		return;
-	}
-	chanctx = container_of(rcu_access_pointer(sdata->vif.chanctx_conf),
-			       struct ieee80211_chanctx, conf);
-	if (ieee80211_chanctx_refcount(local, chanctx) > 1) {
+	res = ieee80211_vif_reserve_chanctx(sdata, &csa_ie.chandef,
+					    chanctx->mode, false);
+	if (res) {
 		sdata_info(sdata,
-			   "channel switch with multiple interfaces on the same channel, disconnecting\n");
+			   "failed to reserve channel context for channel switch, disconnecting (err=%d)\n",
+			   res);
 		ieee80211_queue_work(&local->hw,
 				     &ifmgd->csa_connection_drop_work);
 		mutex_unlock(&local->chanctx_mtx);
+		mutex_unlock(&local->mtx);
 		return;
 	}
 	mutex_unlock(&local->chanctx_mtx);
 
-	sdata->csa_chandef = csa_ie.chandef;
-
-	mutex_lock(&local->mtx);
 	sdata->vif.csa_active = true;
+	sdata->csa_chandef = csa_ie.chandef;
 	sdata->csa_block_tx = csa_ie.mode;
 
 	if (sdata->csa_block_tx)
-- 
1.8.5.3


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [PATCH v8 5/5] cfg80211: remove channel_switch combination check
  2014-06-05 12:56 ` [PATCH v8 " Michal Kazior
                     ` (3 preceding siblings ...)
  2014-06-05 12:56   ` [PATCH v8 4/5] mac80211: use chanctx reservation for STA CSA Michal Kazior
@ 2014-06-05 12:56   ` Michal Kazior
  2014-06-12 12:54   ` [PATCH v9 0/5] cfg/mac80211: implement multi-vif csa Michal Kazior
  5 siblings, 0 replies; 40+ messages in thread
From: Michal Kazior @ 2014-06-05 12:56 UTC (permalink / raw
  To: linux-wireless; +Cc: luca, johannes, Michal Kazior

Driver is now responsible for veryfing if the
switch is possible.

Since this is inherently tricky driver may decide
to disconnect an interface later with
cfg80211_stop_iface().

This doesn't mean driver can accept everything. It
should do it's best to verify requests and reject
them as soon as possible.

Signed-off-by: Michal Kazior <michal.kazior@tieto.com>
---

Notes:
    v7:
     * emphasize channel switching verification should not be
       taken lightly [Johannes]
     * update channel_switch description in cfg80211.h

 include/net/cfg80211.h |  7 ++++++-
 net/wireless/nl80211.c | 11 -----------
 2 files changed, 6 insertions(+), 12 deletions(-)

diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 920ec8c..c62d48e 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -2315,7 +2315,12 @@ struct cfg80211_qos_map {
  *	reliability. This operation can not fail.
  * @set_coalesce: Set coalesce parameters.
  *
- * @channel_switch: initiate channel-switch procedure (with CSA)
+ * @channel_switch: initiate channel-switch procedure (with CSA). Driver is
+ *	responsible for veryfing if the switch is possible. Since this is
+ *	inherently tricky driver may decide to disconnect an interface later
+ *	with cfg80211_stop_iface(). This doesn't mean driver can accept
+ *	everything. It should do it's best to verify requests and reject them
+ *	as soon as possible.
  *
  * @set_qos_map: Set QoS mapping information to the driver
  *
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index ba4f172..d70f542 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -6012,17 +6012,6 @@ skip_beacons:
 		params.radar_required = true;
 	}
 
-	/* TODO: I left this here for now.  With channel switch, the
-	 * verification is a bit more complicated, because we only do
-	 * it later when the channel switch really happens.
-	 */
-	err = cfg80211_can_use_iftype_chan(rdev, wdev, wdev->iftype,
-					   params.chandef.chan,
-					   CHAN_MODE_SHARED,
-					   radar_detect_width);
-	if (err)
-		return err;
-
 	if (info->attrs[NL80211_ATTR_CH_SWITCH_BLOCK_TX])
 		params.block_tx = true;
 
-- 
1.8.5.3


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* Re: [PATCH v8 1/5] mac80211: implement multi-vif in-place reservations
  2014-06-05 12:56   ` [PATCH v8 1/5] mac80211: implement multi-vif in-place reservations Michal Kazior
@ 2014-06-09 16:27     ` Eliad Peller
  2014-06-10  5:16       ` Michal Kazior
  0 siblings, 1 reply; 40+ messages in thread
From: Eliad Peller @ 2014-06-09 16:27 UTC (permalink / raw
  To: Michal Kazior
  Cc: linux-wireless@vger.kernel.org, Luciano Coelho, Johannes Berg

hi Michal,

On Thu, Jun 5, 2014 at 3:56 PM, Michal Kazior <michal.kazior@tieto.com> wrote:
> Multi-vif in-place reservations happen when
> it is impossible to allocate more channel contexts
> as indicated by interface combinations.
>
> Such reservations are not finalized until all
> assigned interfaces are ready.
>
> This still doesn't handle all possible cases
> (i.e. degradation of number of channels) properly.
>
> Signed-off-by: Michal Kazior <michal.kazior@tieto.com>
> ---
[...]

> +static int
> +ieee80211_vif_use_reserved_reassign(struct ieee80211_sub_if_data *sdata)
>  {
[...]
> +
> +       vif_chsw[0].vif = &sdata->vif;
> +       vif_chsw[0].old_ctx = &old_ctx->conf;
> +       vif_chsw[0].new_ctx = &new_ctx->conf;
> +
> +       list_del(&sdata->reserved_chanctx_list);
> +       sdata->reserved_chanctx = NULL;
> +
> +       err = drv_switch_vif_chanctx(local, vif_chsw, 1,
> +                                    CHANCTX_SWMODE_REASSIGN_VIF);
> +       if (err) {
> +               if (ieee80211_chanctx_refcount(local, new_ctx) == 0)
> +                       ieee80211_free_chanctx(local, new_ctx);
> +
> +               return err;
>         }
>
> -       old_ctx = container_of(conf, struct ieee80211_chanctx, conf);
> +       list_move(&sdata->assigned_chanctx_list, &new_ctx->assigned_vifs);
> +
> +       if (sdata->vif.type == NL80211_IFTYPE_AP)
> +               __ieee80211_vif_copy_chanctx_to_vlans(sdata, false);
> +
> +       if (ieee80211_chanctx_refcount(local, old_ctx) == 0)
> +               ieee80211_free_chanctx(local, old_ctx);

i gave it a quick run, and it crashed quickly due to use-after-free.
adding:
rcu_assign_pointer(sdata->vif.chanctx_conf, &new_ctx->conf);

after the list_move (before freeing it...) seemed to solve it.
other than that, it seems to work well so far :)

Eliad.

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v8 1/5] mac80211: implement multi-vif in-place reservations
  2014-06-09 16:27     ` Eliad Peller
@ 2014-06-10  5:16       ` Michal Kazior
  0 siblings, 0 replies; 40+ messages in thread
From: Michal Kazior @ 2014-06-10  5:16 UTC (permalink / raw
  To: Eliad Peller
  Cc: linux-wireless@vger.kernel.org, Luciano Coelho, Johannes Berg

On 9 June 2014 18:27, Eliad Peller <eliad@wizery.com> wrote:
> hi Michal,
>
> On Thu, Jun 5, 2014 at 3:56 PM, Michal Kazior <michal.kazior@tieto.com> wrote:
>> Multi-vif in-place reservations happen when
>> it is impossible to allocate more channel contexts
>> as indicated by interface combinations.
>>
>> Such reservations are not finalized until all
>> assigned interfaces are ready.
>>
>> This still doesn't handle all possible cases
>> (i.e. degradation of number of channels) properly.
>>
>> Signed-off-by: Michal Kazior <michal.kazior@tieto.com>
>> ---
> [...]
>
>> +static int
>> +ieee80211_vif_use_reserved_reassign(struct ieee80211_sub_if_data *sdata)
>>  {
> [...]
>> +
>> +       vif_chsw[0].vif = &sdata->vif;
>> +       vif_chsw[0].old_ctx = &old_ctx->conf;
>> +       vif_chsw[0].new_ctx = &new_ctx->conf;
>> +
>> +       list_del(&sdata->reserved_chanctx_list);
>> +       sdata->reserved_chanctx = NULL;
>> +
>> +       err = drv_switch_vif_chanctx(local, vif_chsw, 1,
>> +                                    CHANCTX_SWMODE_REASSIGN_VIF);
>> +       if (err) {
>> +               if (ieee80211_chanctx_refcount(local, new_ctx) == 0)
>> +                       ieee80211_free_chanctx(local, new_ctx);
>> +
>> +               return err;
>>         }
>>
>> -       old_ctx = container_of(conf, struct ieee80211_chanctx, conf);
>> +       list_move(&sdata->assigned_chanctx_list, &new_ctx->assigned_vifs);
>> +
>> +       if (sdata->vif.type == NL80211_IFTYPE_AP)
>> +               __ieee80211_vif_copy_chanctx_to_vlans(sdata, false);
>> +
>> +       if (ieee80211_chanctx_refcount(local, old_ctx) == 0)
>> +               ieee80211_free_chanctx(local, old_ctx);
>
> i gave it a quick run, and it crashed quickly due to use-after-free.
> adding:
> rcu_assign_pointer(sdata->vif.chanctx_conf, &new_ctx->conf);
>
> after the list_move (before freeing it...) seemed to solve it.
> other than that, it seems to work well so far :)

Good catch! Thanks!


Michał

^ permalink raw reply	[flat|nested] 40+ messages in thread

* [PATCH v9 0/5] cfg/mac80211: implement multi-vif csa
  2014-06-05 12:56 ` [PATCH v8 " Michal Kazior
                     ` (4 preceding siblings ...)
  2014-06-05 12:56   ` [PATCH v8 5/5] cfg80211: remove channel_switch combination check Michal Kazior
@ 2014-06-12 12:54   ` Michal Kazior
  2014-06-12 12:54     ` [PATCH v9 1/5] mac80211: implement multi-vif in-place reservations Michal Kazior
                       ` (5 more replies)
  5 siblings, 6 replies; 40+ messages in thread
From: Michal Kazior @ 2014-06-12 12:54 UTC (permalink / raw
  To: linux-wireless; +Cc: luca, johannes, Michal Kazior

Hi,

The patchset implements multi-interface
reservations and multi-interface channel
switching.

There are still some cases that aren't handled
well (at least degradation of number of channels
due to interface combinations that I'm aware of).


v9:
 * free use-after-free [Eliad]

Michal Kazior (5):
  mac80211: implement multi-vif in-place reservations
  mac80211: make check_combinations() aware of chanctx reservation
  mac80211: use chanctx reservation for AP CSA
  mac80211: use chanctx reservation for STA CSA
  cfg80211: remove channel_switch combination check

 include/net/cfg80211.h     |   7 +-
 include/net/mac80211.h     |   7 -
 net/mac80211/cfg.c         |  82 +++--
 net/mac80211/chan.c        | 728 ++++++++++++++++++++++++++++++++++++---------
 net/mac80211/ieee80211_i.h |  30 +-
 net/mac80211/mlme.c        |  99 +++---
 net/mac80211/util.c        |  44 ++-
 net/wireless/nl80211.c     |  11 -
 8 files changed, 772 insertions(+), 236 deletions(-)

-- 
1.8.5.3


^ permalink raw reply	[flat|nested] 40+ messages in thread

* [PATCH v9 1/5] mac80211: implement multi-vif in-place reservations
  2014-06-12 12:54   ` [PATCH v9 0/5] cfg/mac80211: implement multi-vif csa Michal Kazior
@ 2014-06-12 12:54     ` Michal Kazior
  2014-06-16 14:35       ` Luca Coelho
  2014-06-12 12:54     ` [PATCH v9 2/5] mac80211: make check_combinations() aware of chanctx reservation Michal Kazior
                       ` (4 subsequent siblings)
  5 siblings, 1 reply; 40+ messages in thread
From: Michal Kazior @ 2014-06-12 12:54 UTC (permalink / raw
  To: linux-wireless; +Cc: luca, johannes, Michal Kazior

Multi-vif in-place reservations happen when
it is impossible to allocate more channel contexts
as indicated by interface combinations.

Such reservations are not finalized until all
assigned interfaces are ready.

This still doesn't handle all possible cases
(i.e. degradation of number of channels) properly.

Signed-off-by: Michal Kazior <michal.kazior@tieto.com>
---

Notes:
    v2:
     * use new_ctx instead of ctx [Eliad]
     * move recalcs after bss_conf is updated [Eliad]
    
    v4:
     * move recalc-radar before vif-chanctx-assign [Eliad]
     * move radar_required swapping before initial add_chanctx() [Eliad]
    
    v5:
     * move kfree_rcu() [Zhao Gang]
    
    v6:
     * use switch_vif_chanctx for incompat case with chanctx drivers [Johannes]
     * fix vlan chanctx copying
    
    v7:
     * major rework of the algorithm to support cross-context vif switches (e.g.
       when 2 contexts depend on each others reserved chandefs and both need
       in-place switch)
     * create dummy chanctx structure for in-place reservations instead of storing
       everything in a single chanctx (new chanctx structure needs to be allocated
       sooner or later for drv_switch_vif_chanctx)
     * remove the ieee80211_vif_chanctx_reservation_complete() stub; it is now
       simply implemented by AP CSA patch [Johannes]
     * fix lines with over 80 chars
    
    v8:
     * implement assign reservation (i.e. when old_ctx is NULL) [Luca]
     * expand the TODO description for context shuffling [Luca]
     * add missing !list_empty() when reserving in-place [Luca]
     * fix independ[ae]nt typo [Luca]
     * add comment-labels in the reserved_switch() [Luca]
     * relax in-place verification to allow racy switches to succeed [Luca]
     * refine some comments [Luca]
     * remove WARN_ON for re-assign reservations after in-place reservation [Luca]
     * simplify re-assign reservation condition [Luca]
     * do not propagate error for in-place reservation failure if it was a result
       of a depending old_ctx being ready by re-assign of another vif [Luca]
     * fix in-place reservation context/vif list clearing
     * replace replaces/replaced_by with replace_state(enum)/replace_ctx(pointer) [Johannes]
     * var declare/init cleanup [Luca/Johannes]
     * some condition fixups
    
    v9:
     * fix use-after-free (missing rcu_assign_pointer in _reassign reservation) [Eliad]

 include/net/mac80211.h     |   7 -
 net/mac80211/chan.c        | 632 +++++++++++++++++++++++++++++++++++++++------
 net/mac80211/ieee80211_i.h |  25 +-
 net/mac80211/util.c        |   9 +-
 4 files changed, 582 insertions(+), 91 deletions(-)

diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 421b6ec..66040af 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -1600,12 +1600,6 @@ struct ieee80211_tx_control {
  *	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,
@@ -1637,7 +1631,6 @@ enum ieee80211_hw_flags {
 	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,
 };
 
 /**
diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
index 3702d64..159f4c3 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -63,6 +63,19 @@ static bool ieee80211_can_create_new_chanctx(struct ieee80211_local *local)
 	return ieee80211_num_chanctx(local) < ieee80211_max_num_channels(local);
 }
 
+static struct ieee80211_chanctx *
+ieee80211_vif_get_chanctx(struct ieee80211_sub_if_data *sdata)
+{
+	struct ieee80211_chanctx_conf *conf;
+
+	conf = rcu_dereference_protected(sdata->vif.chanctx_conf,
+					 lockdep_is_held(&local->chanctx_mtx));
+	if (!conf)
+		return NULL;
+
+	return container_of(conf, struct ieee80211_chanctx, conf);
+}
+
 static const struct cfg80211_chan_def *
 ieee80211_chanctx_reserved_chandef(struct ieee80211_local *local,
 				   struct ieee80211_chanctx *ctx,
@@ -160,6 +173,9 @@ ieee80211_find_reservation_chanctx(struct ieee80211_local *local,
 		return NULL;
 
 	list_for_each_entry(ctx, &local->chanctx_list, list) {
+		if (ctx->replace_state == IEEE80211_CHANCTX_WILL_BE_REPLACED)
+			continue;
+
 		if (ctx->mode == IEEE80211_CHANCTX_EXCLUSIVE)
 			continue;
 
@@ -347,6 +363,9 @@ ieee80211_find_chanctx(struct ieee80211_local *local,
 	list_for_each_entry(ctx, &local->chanctx_list, list) {
 		const struct cfg80211_chan_def *compat;
 
+		if (ctx->replace_state != IEEE80211_CHANCTX_REPLACE_NONE)
+			continue;
+
 		if (ctx->mode == IEEE80211_CHANCTX_EXCLUSIVE)
 			continue;
 
@@ -898,8 +917,25 @@ int ieee80211_vif_unreserve_chanctx(struct ieee80211_sub_if_data *sdata)
 	list_del(&sdata->reserved_chanctx_list);
 	sdata->reserved_chanctx = NULL;
 
-	if (ieee80211_chanctx_refcount(sdata->local, ctx) == 0)
-		ieee80211_free_chanctx(sdata->local, ctx);
+	if (ieee80211_chanctx_refcount(sdata->local, ctx) == 0) {
+		if (ctx->replace_state == IEEE80211_CHANCTX_REPLACES_OTHER) {
+			if (WARN_ON(!ctx->replace_ctx))
+				return -EINVAL;
+
+			WARN_ON(ctx->replace_ctx->replace_state !=
+			        IEEE80211_CHANCTX_WILL_BE_REPLACED);
+			WARN_ON(ctx->replace_ctx->replace_ctx != ctx);
+
+			ctx->replace_ctx->replace_ctx = NULL;
+			ctx->replace_ctx->replace_state =
+					IEEE80211_CHANCTX_REPLACE_NONE;
+
+			list_del_rcu(&ctx->list);
+			kfree_rcu(ctx, rcu_head);
+		} else {
+			ieee80211_free_chanctx(sdata->local, ctx);
+		}
+	}
 
 	return 0;
 }
@@ -910,40 +946,82 @@ int ieee80211_vif_reserve_chanctx(struct ieee80211_sub_if_data *sdata,
 				  bool radar_required)
 {
 	struct ieee80211_local *local = sdata->local;
-	struct ieee80211_chanctx_conf *conf;
-	struct ieee80211_chanctx *new_ctx, *curr_ctx;
-	int ret = 0;
-
-	mutex_lock(&local->chanctx_mtx);
+	struct ieee80211_chanctx *new_ctx, *curr_ctx, *ctx;
 
-	conf = rcu_dereference_protected(sdata->vif.chanctx_conf,
-					 lockdep_is_held(&local->chanctx_mtx));
-	if (!conf) {
-		ret = -EINVAL;
-		goto out;
-	}
+	lockdep_assert_held(&local->chanctx_mtx);
 
-	curr_ctx = container_of(conf, struct ieee80211_chanctx, conf);
+	curr_ctx = ieee80211_vif_get_chanctx(sdata);
+	if (curr_ctx && local->use_chanctx && !local->ops->switch_vif_chanctx)
+		return -ENOTSUPP;
 
 	new_ctx = ieee80211_find_reservation_chanctx(local, chandef, mode);
 	if (!new_ctx) {
-		if (ieee80211_chanctx_refcount(local, curr_ctx) == 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 if (ieee80211_can_create_new_chanctx(local)) {
-			/* create a new context and reserve it */
+		if (ieee80211_can_create_new_chanctx(local)) {
 			new_ctx = ieee80211_new_chanctx(local, chandef, mode);
-			if (IS_ERR(new_ctx)) {
-				ret = PTR_ERR(new_ctx);
-				goto out;
-			}
+			if (IS_ERR(new_ctx))
+				return PTR_ERR(new_ctx);
 		} else {
-			ret = -EBUSY;
-			goto out;
+			if ((curr_ctx->replace_state ==
+			     IEEE80211_CHANCTX_WILL_BE_REPLACED) ||
+			    !list_empty(&curr_ctx->reserved_vifs)) {
+				/*
+				 * Another vif already requested this context
+				 * for a reservation. Find another one hoping
+				 * all vifs assigned to it will also switch
+				 * soon enough.
+				 *
+				 * TODO: This needs a little more work as some
+				 * cases (more than 2 chanctx capable devices)
+				 * may fail which could otherwise succeed
+				 * provided some channel context juggling was
+				 * performed.
+				 *
+				 * Consider ctx1..3, vif1..6, each ctx has 2
+				 * vifs. vif1 and vif2 from ctx1 request new
+				 * different chandefs starting 2 in-place
+				 * reserations with ctx4 and ctx5 replacing
+				 * ctx1 and ctx2 respectively. Next vif5 and
+				 * vif6 from ctx3 reserve ctx4. If vif3 and
+				 * vif4 remain on ctx2 as they are then this
+				 * fails unless `replace_ctx` from ctx5 is
+				 * replaced with ctx3.
+				 */
+				list_for_each_entry(ctx, &local->chanctx_list,
+						    list) {
+					if (ctx->replace_state !=
+					    IEEE80211_CHANCTX_REPLACE_NONE)
+						continue;
+
+					if (!list_empty(&ctx->reserved_vifs))
+						continue;
+
+					curr_ctx = ctx;
+					break;
+				}
+			}
+
+			/*
+			 * If that's true then all available contexts already
+			 * have reservations and cannot be used.
+			 */
+			if ((curr_ctx->replace_state ==
+			     IEEE80211_CHANCTX_WILL_BE_REPLACED) ||
+			    !list_empty(&curr_ctx->reserved_vifs))
+				return -EBUSY;
+
+			new_ctx = ieee80211_alloc_chanctx(local, chandef, mode);
+			if (!new_ctx)
+				return -ENOMEM;
+
+			new_ctx->replace_ctx = curr_ctx;
+			new_ctx->replace_state =
+					IEEE80211_CHANCTX_REPLACES_OTHER;
+
+			curr_ctx->replace_ctx = new_ctx;
+			curr_ctx->replace_state =
+					IEEE80211_CHANCTX_WILL_BE_REPLACED;
+
+			list_add_rcu(&new_ctx->list, &local->chanctx_list);
 		}
 	}
 
@@ -951,82 +1029,474 @@ int ieee80211_vif_reserve_chanctx(struct ieee80211_sub_if_data *sdata,
 	sdata->reserved_chanctx = new_ctx;
 	sdata->reserved_chandef = *chandef;
 	sdata->reserved_radar_required = radar_required;
-out:
-	mutex_unlock(&local->chanctx_mtx);
-	return ret;
+	sdata->reserved_ready = false;
+
+	return 0;
 }
 
-int ieee80211_vif_use_reserved_context(struct ieee80211_sub_if_data *sdata,
-				       u32 *changed)
+static int
+ieee80211_vif_use_reserved_reassign(struct ieee80211_sub_if_data *sdata)
 {
 	struct ieee80211_local *local = sdata->local;
-	struct ieee80211_chanctx *ctx;
-	struct ieee80211_chanctx *old_ctx;
-	struct ieee80211_chanctx_conf *conf;
-	int ret;
-	u32 tmp_changed = *changed;
-
-	/* TODO: need to recheck if the chandef is usable etc.? */
+	struct ieee80211_vif_chanctx_switch vif_chsw[1] = {};
+	struct ieee80211_chanctx *old_ctx, *new_ctx;
+	const struct cfg80211_chan_def *chandef;
+	u32 changed = 0;
+	int err;
 
 	lockdep_assert_held(&local->mtx);
+	lockdep_assert_held(&local->chanctx_mtx);
 
-	mutex_lock(&local->chanctx_mtx);
+	new_ctx = sdata->reserved_chanctx;
+	old_ctx = ieee80211_vif_get_chanctx(sdata);
 
-	ctx = sdata->reserved_chanctx;
-	if (WARN_ON(!ctx)) {
-		ret = -EINVAL;
-		goto out;
-	}
+	if (WARN_ON(!sdata->reserved_ready))
+		return -EBUSY;
 
-	conf = rcu_dereference_protected(sdata->vif.chanctx_conf,
-					 lockdep_is_held(&local->chanctx_mtx));
-	if (!conf) {
-		ret = -EINVAL;
-		goto out;
+	if (WARN_ON(!new_ctx))
+		return -EINVAL;
+
+	if (WARN_ON(!old_ctx))
+		return -EINVAL;
+
+	if (WARN_ON(new_ctx->replace_state ==
+		    IEEE80211_CHANCTX_REPLACES_OTHER))
+		return -EINVAL;
+
+	chandef = ieee80211_chanctx_non_reserved_chandef(local, new_ctx,
+				&sdata->reserved_chandef);
+	if (WARN_ON(!chandef))
+		return -EINVAL;
+
+	vif_chsw[0].vif = &sdata->vif;
+	vif_chsw[0].old_ctx = &old_ctx->conf;
+	vif_chsw[0].new_ctx = &new_ctx->conf;
+
+	list_del(&sdata->reserved_chanctx_list);
+	sdata->reserved_chanctx = NULL;
+
+	err = drv_switch_vif_chanctx(local, vif_chsw, 1,
+				     CHANCTX_SWMODE_REASSIGN_VIF);
+	if (err) {
+		if (ieee80211_chanctx_refcount(local, new_ctx) == 0)
+			ieee80211_free_chanctx(local, new_ctx);
+
+		return err;
 	}
 
-	old_ctx = container_of(conf, struct ieee80211_chanctx, conf);
+	list_move(&sdata->assigned_chanctx_list, &new_ctx->assigned_vifs);
+	rcu_assign_pointer(sdata->vif.chanctx_conf, &new_ctx->conf);
+
+	if (sdata->vif.type == NL80211_IFTYPE_AP)
+		__ieee80211_vif_copy_chanctx_to_vlans(sdata, false);
+
+	if (ieee80211_chanctx_refcount(local, old_ctx) == 0)
+		ieee80211_free_chanctx(local, old_ctx);
 
 	if (sdata->vif.bss_conf.chandef.width != sdata->reserved_chandef.width)
-		tmp_changed |= BSS_CHANGED_BANDWIDTH;
+		changed = BSS_CHANGED_BANDWIDTH;
 
 	sdata->vif.bss_conf.chandef = sdata->reserved_chandef;
 
-	/* unref our reservation */
-	sdata->reserved_chanctx = NULL;
-	sdata->radar_required = sdata->reserved_radar_required;
+	if (changed)
+		ieee80211_bss_info_change_notify(sdata, changed);
+
+	return err;
+}
+
+static int
+ieee80211_vif_use_reserved_assign(struct ieee80211_sub_if_data *sdata)
+{
+	struct ieee80211_local *local = sdata->local;
+	struct ieee80211_chanctx *old_ctx, *new_ctx;
+	const struct cfg80211_chan_def *chandef;
+	int err;
+
+	old_ctx = ieee80211_vif_get_chanctx(sdata);
+	new_ctx = sdata->reserved_chanctx;
+
+	if (WARN_ON(!sdata->reserved_ready))
+		return -EINVAL;
+
+	if (WARN_ON(old_ctx))
+		return -EINVAL;
+
+	if (WARN_ON(!new_ctx))
+		return -EINVAL;
+
+	if (WARN_ON(new_ctx->replace_state ==
+		    IEEE80211_CHANCTX_REPLACES_OTHER))
+		return -EINVAL;
+
+	chandef = ieee80211_chanctx_non_reserved_chandef(local, new_ctx,
+				&sdata->reserved_chandef);
+	if (WARN_ON(!chandef))
+		return -EINVAL;
+
 	list_del(&sdata->reserved_chanctx_list);
+	sdata->reserved_chanctx = NULL;
 
-	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;
+	err = ieee80211_assign_vif_chanctx(sdata, new_ctx);
+	if (err) {
+		if (ieee80211_chanctx_refcount(local, new_ctx) == 0)
+			ieee80211_free_chanctx(local, new_ctx);
+
+		goto out;
+	}
+
+out:
+	return err;
+}
+
+static bool
+ieee80211_vif_has_in_place_reservation(struct ieee80211_sub_if_data *sdata)
+{
+	struct ieee80211_chanctx *old_ctx, *new_ctx;
+
+	lockdep_assert_held(&sdata->local->chanctx_mtx);
+
+	new_ctx = sdata->reserved_chanctx;
+	old_ctx = ieee80211_vif_get_chanctx(sdata);
+
+	if (!old_ctx)
+		return false;
+
+	if (WARN_ON(!new_ctx))
+		return false;
+
+	if (old_ctx->replace_state != IEEE80211_CHANCTX_WILL_BE_REPLACED)
+		return false;
+
+	if (new_ctx->replace_state != IEEE80211_CHANCTX_REPLACES_OTHER)
+		return false;
+
+	return true;
+}
+
+static int
+ieee80211_vif_use_reserved_switch(struct ieee80211_local *local)
+{
+	struct ieee80211_sub_if_data *sdata, *sdata_tmp;
+	struct ieee80211_chanctx *ctx, *ctx_tmp;
+	struct ieee80211_chanctx *new_ctx = NULL;
+	struct ieee80211_vif_chanctx_switch *vif_chsw = NULL;
+	const struct cfg80211_chan_def *chandef;
+	int i, err;
+	int n_ctx = 0, n_vifs = 0, n_assigned = 0, n_reserved = 0, n_ready = 0;
+
+	lockdep_assert_held(&local->mtx);
+	lockdep_assert_held(&local->chanctx_mtx);
+
+	/*
+	 * If there are 2 independent pairs of channel contexts performing
+	 * cross-switch of their vifs this code will still wait until both are
+	 * ready even though it could be possible to switch one before the
+	 * other is ready.
+	 *
+	 * For practical reasons and code simplicity just do a single huge
+	 * switch.
+	 */
+
+	/*
+	 * Verify if the reservation is still feasible.
+	 *  - if it's not then disconnect
+	 *  - if it is but not all vifs necessary are ready then defer
+	 */
+
+	list_for_each_entry(ctx, &local->chanctx_list, list) {
+		if (ctx->replace_state != IEEE80211_CHANCTX_REPLACES_OTHER)
+			continue;
+
+		if (WARN_ON(!ctx->replace_ctx))
+			return -EINVAL;
+
+		if (!local->use_chanctx)
+			new_ctx = ctx;
+
+		n_ctx++;
+
+		list_for_each_entry(sdata, &ctx->replace_ctx->assigned_vifs,
+				    assigned_chanctx_list) {
+			n_assigned++;
+			if (sdata->reserved_chanctx) {
+				n_reserved++;
+				if (sdata->reserved_ready)
+					n_ready++;
+			}
+		}
+
+		if (n_assigned != n_reserved) {
+			if (n_ready == n_reserved) {
+				wiphy_info(local->hw.wiphy,
+					   "channel context reservation cannot be finalized because some interfaces aren't switching\n");
+				err = -EBUSY;
+				goto err;
+			}
+
+			return -EAGAIN;
+		}
+
+		list_for_each_entry(sdata, &ctx->reserved_vifs,
+				    reserved_chanctx_list) {
+			if (!ieee80211_vif_has_in_place_reservation(sdata))
+				continue;
+
+			if (!sdata->reserved_ready)
+				return -EAGAIN;
+
+			n_vifs++;
+
+			if (sdata->reserved_radar_required)
+				ctx->conf.radar_enabled = true;
+		}
+	}
+
+	if (WARN_ON(n_ctx == 0) ||
+	    WARN_ON(n_vifs == 0) ||
+	    WARN_ON(n_ctx > 1 && !local->use_chanctx) ||
+	    WARN_ON(!new_ctx && !local->use_chanctx)) {
+		err = -EINVAL;
+		goto err;
+	}
+
+	/*
+	 * All necessary vifs are ready. Perform the switch now.
+	 */
+
+	if (local->use_chanctx) {
+		vif_chsw = kzalloc(sizeof(*vif_chsw) * n_vifs, GFP_KERNEL);
+		if (vif_chsw) {
+			err = -ENOMEM;
+			goto err;
+		}
+
+		i = 0;
+		list_for_each_entry(ctx, &local->chanctx_list, list) {
+			if (ctx->replace_state != IEEE80211_CHANCTX_REPLACES_OTHER)
+				continue;
+
+			if (WARN_ON(!ctx->replace_ctx))
+				return -EINVAL;
+
+			list_for_each_entry(sdata, &ctx->reserved_vifs,
+					    reserved_chanctx_list) {
+				if (!ieee80211_vif_has_in_place_reservation(
+						sdata))
+					continue;
+
+				vif_chsw[i].vif = &sdata->vif;
+				vif_chsw[i].old_ctx = &ctx->replace_ctx->conf;
+				vif_chsw[i].new_ctx = &ctx->conf;
+
+				i++;
+			}
+		}
+
+		err = drv_switch_vif_chanctx(local, vif_chsw, n_vifs,
+					     CHANCTX_SWMODE_SWAP_CONTEXTS);
+		kfree(vif_chsw);
+
+		if (err)
+			goto err;
 	} else {
-		ret = ieee80211_assign_vif_chanctx(sdata, ctx);
-		if (ieee80211_chanctx_refcount(local, old_ctx) == 0)
-			ieee80211_free_chanctx(local, old_ctx);
-		if (ret) {
-			/* if assign fails refcount stays the same */
-			if (ieee80211_chanctx_refcount(local, ctx) == 0)
-				ieee80211_free_chanctx(local, ctx);
-			goto out;
+		chandef = ieee80211_chanctx_reserved_chandef(local, new_ctx,
+							     NULL);
+		if (WARN_ON(!chandef)) {
+			err = -EINVAL;
+			goto err;
 		}
 
-		if (sdata->vif.type == NL80211_IFTYPE_AP)
-			__ieee80211_vif_copy_chanctx_to_vlans(sdata, false);
+		local->hw.conf.radar_enabled = new_ctx->conf.radar_enabled;
+		local->_oper_chandef = *chandef;
+		ieee80211_hw_config(local, 0);
 	}
 
-	*changed = tmp_changed;
+	/*
+	 * Update all structures, values and pointers to point to new channel
+	 * context(s).
+	 */
 
-	ieee80211_recalc_chanctx_chantype(local, ctx);
-	ieee80211_recalc_smps_chanctx(local, ctx);
-	ieee80211_recalc_radar_chanctx(local, ctx);
-	ieee80211_recalc_chanctx_min_def(local, ctx);
-out:
-	mutex_unlock(&local->chanctx_mtx);
-	return ret;
+	i = 0;
+	list_for_each_entry(ctx, &local->chanctx_list, list) {
+		if (ctx->replace_state != IEEE80211_CHANCTX_REPLACES_OTHER)
+			continue;
+
+		if (WARN_ON(!ctx->replace_ctx))
+			return -EINVAL;
+
+		list_for_each_entry(sdata, &ctx->reserved_vifs,
+				    reserved_chanctx_list) {
+			u32 changed = 0;
+
+			if (!ieee80211_vif_has_in_place_reservation(sdata))
+				continue;
+
+			rcu_assign_pointer(sdata->vif.chanctx_conf, &ctx->conf);
+
+			if (sdata->vif.type == NL80211_IFTYPE_AP)
+				__ieee80211_vif_copy_chanctx_to_vlans(sdata,
+								      false);
+
+			sdata->radar_required = sdata->reserved_radar_required;
+
+			if (sdata->vif.bss_conf.chandef.width !=
+			    sdata->reserved_chandef.width)
+				changed = BSS_CHANGED_BANDWIDTH;
+
+			sdata->vif.bss_conf.chandef = sdata->reserved_chandef;
+			if (changed)
+				ieee80211_bss_info_change_notify(sdata,
+								 changed);
+
+			ieee80211_recalc_txpower(sdata);
+		}
+
+		ieee80211_recalc_chanctx_chantype(local, ctx);
+		ieee80211_recalc_smps_chanctx(local, ctx);
+		ieee80211_recalc_radar_chanctx(local, ctx);
+		ieee80211_recalc_chanctx_min_def(local, ctx);
+
+		list_for_each_entry_safe(sdata, sdata_tmp, &ctx->reserved_vifs,
+					 reserved_chanctx_list) {
+			if (ieee80211_vif_get_chanctx(sdata) != ctx)
+				continue;
+
+			list_del(&sdata->reserved_chanctx_list);
+			list_move(&sdata->assigned_chanctx_list,
+				  &new_ctx->assigned_vifs);
+			sdata->reserved_chanctx = NULL;
+		}
+
+		/*
+		 * This context might have been a dependency for an already
+		 * ready re-assign reservation interface that was deferred. Do
+		 * not propagate error to the caller though. The in-place
+		 * reservation for originally requested interface has already
+		 * succeeded at this point.
+		 */
+		list_for_each_entry(sdata, &ctx->reserved_vifs,
+				    reserved_chanctx_list) {
+			if (WARN_ON(ieee80211_vif_has_in_place_reservation(
+					sdata)))
+				continue;
+
+			if (WARN_ON(sdata->reserved_chanctx != ctx))
+				continue;
+
+			if (!sdata->reserved_ready)
+				continue;
+
+			if (ieee80211_vif_get_chanctx(sdata))
+				err = ieee80211_vif_use_reserved_reassign(
+						sdata);
+			else
+				err = ieee80211_vif_use_reserved_assign(sdata);
+
+			if (err) {
+				sdata_info(sdata,
+					   "failed to finalize (re-)assign reservation (err=%d)\n",
+					   err);
+				ieee80211_vif_unreserve_chanctx(sdata);
+				cfg80211_stop_iface(local->hw.wiphy,
+						    &sdata->wdev,
+						    GFP_KERNEL);
+			}
+		}
+	}
+
+	/*
+	 * Finally free old contexts
+	 */
+
+	list_for_each_entry_safe(ctx, ctx_tmp, &local->chanctx_list, list) {
+		if (ctx->replace_state != IEEE80211_CHANCTX_WILL_BE_REPLACED)
+			continue;
+
+		ctx->replace_ctx->replace_ctx = NULL;
+		ctx->replace_ctx->replace_state =
+				IEEE80211_CHANCTX_REPLACE_NONE;
+
+		list_del_rcu(&ctx->list);
+		kfree_rcu(ctx, rcu_head);
+	}
+
+	return 0;
+
+err:
+	list_for_each_entry(ctx, &local->chanctx_list, list) {
+		if (ctx->replace_state != IEEE80211_CHANCTX_REPLACES_OTHER)
+			continue;
+
+		list_for_each_entry_safe(sdata, sdata_tmp, &ctx->reserved_vifs,
+					 reserved_chanctx_list)
+			ieee80211_vif_unreserve_chanctx(sdata);
+	}
+
+	return err;
+}
+
+int ieee80211_vif_use_reserved_context(struct ieee80211_sub_if_data *sdata)
+{
+	struct ieee80211_local *local = sdata->local;
+	struct ieee80211_chanctx *new_ctx;
+	struct ieee80211_chanctx *old_ctx;
+	int err;
+
+	lockdep_assert_held(&local->mtx);
+	lockdep_assert_held(&local->chanctx_mtx);
+
+	new_ctx = sdata->reserved_chanctx;
+	old_ctx = ieee80211_vif_get_chanctx(sdata);
+
+	if (WARN_ON(!new_ctx))
+		return -EINVAL;
+
+	if (WARN_ON(new_ctx->replace_state ==
+		    IEEE80211_CHANCTX_WILL_BE_REPLACED))
+		return -EINVAL;
+
+	if (WARN_ON(sdata->reserved_ready))
+		return -EINVAL;
+
+	sdata->reserved_ready = true;
+
+	if (new_ctx->replace_state == IEEE80211_CHANCTX_REPLACE_NONE) {
+		if (old_ctx)
+			err = ieee80211_vif_use_reserved_reassign(sdata);
+		else
+			err = ieee80211_vif_use_reserved_assign(sdata);
+
+		if (err)
+			return err;
+	}
+
+	/*
+	 * In-place reservation may need to be finalized now either if:
+	 *  a) sdata is taking part in the swapping itself and is the last one
+	 *  b) sdata has switched with a re-assign reservation to an existing
+	 *     context readying in-place switching of old_ctx
+	 *
+	 * In case of (b) do not propagate the error up because the requested
+	 * sdata already switched successfully. Just spill an extra warning.
+	 * The ieee80211_vif_use_reserved_switch() already stops all necessary
+	 * interfaces upon failure.
+	 */
+	if ((old_ctx &&
+	     old_ctx->replace_state == IEEE80211_CHANCTX_WILL_BE_REPLACED) ||
+	    new_ctx->replace_state == IEEE80211_CHANCTX_REPLACES_OTHER) {
+		err = ieee80211_vif_use_reserved_switch(local);
+		if (err && err != -EAGAIN) {
+			if (new_ctx->replace_state ==
+			    IEEE80211_CHANCTX_REPLACES_OTHER)
+				return err;
+
+			wiphy_info(local->hw.wiphy,
+				   "depending in-place reservation failed (err=%d)\n",
+				   err);
+		}
+	}
+
+	return 0;
 }
 
 int ieee80211_vif_change_bandwidth(struct ieee80211_sub_if_data *sdata,
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 677e9c8..4c6dd32 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -700,6 +700,24 @@ enum ieee80211_chanctx_mode {
 	IEEE80211_CHANCTX_EXCLUSIVE
 };
 
+/**
+ * enum ieee80211_chanctx_replace_state - channel context replacement state
+ *
+ * This is used for channel context in-place reservations that require channel
+ * context switch/swap.
+ *
+ * @IEEE80211_CHANCTX_REPLACE_NONE: no replacement is taking place
+ * @IEEE80211_CHANCTX_WILL_BE_REPLACED: this channel context will be replaced
+ *	by a (not yet registered) channel context pointed by %replace_ctx.
+ * @IEEE80211_CHANCTX_REPLACES_OTHER: this (not yet registered) channel context
+ *	replaces an existing channel context pointed to by %replace_ctx.
+ */
+enum ieee80211_chanctx_replace_state {
+	IEEE80211_CHANCTX_REPLACE_NONE,
+	IEEE80211_CHANCTX_WILL_BE_REPLACED,
+	IEEE80211_CHANCTX_REPLACES_OTHER,
+};
+
 struct ieee80211_chanctx {
 	struct list_head list;
 	struct rcu_head rcu_head;
@@ -707,6 +725,9 @@ struct ieee80211_chanctx {
 	struct list_head assigned_vifs;
 	struct list_head reserved_vifs;
 
+	enum ieee80211_chanctx_replace_state replace_state;
+	struct ieee80211_chanctx *replace_ctx;
+
 	enum ieee80211_chanctx_mode mode;
 	bool driver_present;
 
@@ -777,6 +798,7 @@ struct ieee80211_sub_if_data {
 	struct ieee80211_chanctx *reserved_chanctx;
 	struct cfg80211_chan_def reserved_chandef;
 	bool reserved_radar_required;
+	bool reserved_ready;
 
 	/* used to reconfigure hardware SM PS */
 	struct work_struct recalc_smps;
@@ -1800,8 +1822,7 @@ ieee80211_vif_reserve_chanctx(struct ieee80211_sub_if_data *sdata,
 			      enum ieee80211_chanctx_mode mode,
 			      bool radar_required);
 int __must_check
-ieee80211_vif_use_reserved_context(struct ieee80211_sub_if_data *sdata,
-				   u32 *changed);
+ieee80211_vif_use_reserved_context(struct ieee80211_sub_if_data *sdata);
 int ieee80211_vif_unreserve_chanctx(struct ieee80211_sub_if_data *sdata);
 
 int __must_check
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index 7e0dd4b..8e661ea 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -1603,7 +1603,9 @@ int ieee80211_reconfig(struct ieee80211_local *local)
 	if (local->use_chanctx) {
 		mutex_lock(&local->chanctx_mtx);
 		list_for_each_entry(ctx, &local->chanctx_list, list)
-			WARN_ON(drv_add_chanctx(local, ctx));
+			if (ctx->replace_state !=
+			    IEEE80211_CHANCTX_REPLACES_OTHER)
+				WARN_ON(drv_add_chanctx(local, ctx));
 		mutex_unlock(&local->chanctx_mtx);
 
 		list_for_each_entry(sdata, &local->interfaces, list) {
@@ -2876,6 +2878,8 @@ int ieee80211_check_combinations(struct ieee80211_sub_if_data *sdata,
 		num[iftype] = 1;
 
 	list_for_each_entry(ctx, &local->chanctx_list, list) {
+		if (ctx->replace_state == IEEE80211_CHANCTX_WILL_BE_REPLACED)
+			continue;
 		if (ctx->conf.radar_enabled)
 			radar_detect |= BIT(ctx->conf.def.width);
 		if (ctx->mode == IEEE80211_CHANCTX_EXCLUSIVE) {
@@ -2934,6 +2938,9 @@ int ieee80211_max_num_channels(struct ieee80211_local *local)
 	lockdep_assert_held(&local->chanctx_mtx);
 
 	list_for_each_entry(ctx, &local->chanctx_list, list) {
+		if (ctx->replace_state == IEEE80211_CHANCTX_WILL_BE_REPLACED)
+			continue;
+
 		num_different_channels++;
 
 		if (ctx->conf.radar_enabled)
-- 
1.8.5.3


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [PATCH v9 2/5] mac80211: make check_combinations() aware of chanctx reservation
  2014-06-12 12:54   ` [PATCH v9 0/5] cfg/mac80211: implement multi-vif csa Michal Kazior
  2014-06-12 12:54     ` [PATCH v9 1/5] mac80211: implement multi-vif in-place reservations Michal Kazior
@ 2014-06-12 12:54     ` Michal Kazior
  2014-06-12 12:54     ` [PATCH v9 3/5] mac80211: use chanctx reservation for AP CSA Michal Kazior
                       ` (3 subsequent siblings)
  5 siblings, 0 replies; 40+ messages in thread
From: Michal Kazior @ 2014-06-12 12:54 UTC (permalink / raw
  To: linux-wireless; +Cc: luca, johannes, Michal Kazior

The ieee80211_check_combinations() computes
radar_detect accordingly depending on chanctx
reservation status.

This makes it possible to use the function for
channel_switch validation.

Signed-off-by: Michal Kazior <michal.kazior@tieto.com>
---

Notes:
    v3:
     * fix typo in comment [Johannes]
     * fix comment style [Johannes]
    
    v7:
     * use replaces/replaced_by pointers
     * replace radar_detect calculation in ieee80211_max_num_channels() too
    
    v8:
     * use replace_state/replace_ctx instead of replaces/replaced_by (rebased)

 net/mac80211/util.c | 35 +++++++++++++++++++++++++++++++----
 1 file changed, 31 insertions(+), 4 deletions(-)

diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index 8e661ea..16dc720 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -2837,6 +2837,35 @@ void ieee80211_recalc_dtim(struct ieee80211_local *local,
 	ps->dtim_count = dtim_count;
 }
 
+static u8 ieee80211_chanctx_radar_detect(struct ieee80211_local *local,
+					 struct ieee80211_chanctx *ctx)
+{
+	struct ieee80211_sub_if_data *sdata;
+	u8 radar_detect = 0;
+
+	lockdep_assert_held(&local->chanctx_mtx);
+
+	if (WARN_ON(ctx->replace_state == IEEE80211_CHANCTX_WILL_BE_REPLACED))
+		return 0;
+
+	list_for_each_entry(sdata, &ctx->reserved_vifs, reserved_chanctx_list)
+		if (sdata->reserved_radar_required)
+			radar_detect |= BIT(sdata->reserved_chandef.width);
+
+	/*
+	 * An in-place reservation context should not have any assigned vifs
+	 * until it replaces the other context.
+	 */
+	WARN_ON(ctx->replace_state == IEEE80211_CHANCTX_REPLACES_OTHER &&
+		!list_empty(&ctx->assigned_vifs));
+
+	list_for_each_entry(sdata, &ctx->assigned_vifs, assigned_chanctx_list)
+		if (sdata->radar_required)
+			radar_detect |= BIT(sdata->vif.bss_conf.chandef.width);
+
+	return radar_detect;
+}
+
 int ieee80211_check_combinations(struct ieee80211_sub_if_data *sdata,
 				 const struct cfg80211_chan_def *chandef,
 				 enum ieee80211_chanctx_mode chanmode,
@@ -2880,8 +2909,7 @@ int ieee80211_check_combinations(struct ieee80211_sub_if_data *sdata,
 	list_for_each_entry(ctx, &local->chanctx_list, list) {
 		if (ctx->replace_state == IEEE80211_CHANCTX_WILL_BE_REPLACED)
 			continue;
-		if (ctx->conf.radar_enabled)
-			radar_detect |= BIT(ctx->conf.def.width);
+		radar_detect |= ieee80211_chanctx_radar_detect(local, ctx);
 		if (ctx->mode == IEEE80211_CHANCTX_EXCLUSIVE) {
 			num_different_channels++;
 			continue;
@@ -2943,8 +2971,7 @@ int ieee80211_max_num_channels(struct ieee80211_local *local)
 
 		num_different_channels++;
 
-		if (ctx->conf.radar_enabled)
-			radar_detect |= BIT(ctx->conf.def.width);
+		radar_detect |= ieee80211_chanctx_radar_detect(local, ctx);
 	}
 
 	list_for_each_entry_rcu(sdata, &local->interfaces, list)
-- 
1.8.5.3


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [PATCH v9 3/5] mac80211: use chanctx reservation for AP CSA
  2014-06-12 12:54   ` [PATCH v9 0/5] cfg/mac80211: implement multi-vif csa Michal Kazior
  2014-06-12 12:54     ` [PATCH v9 1/5] mac80211: implement multi-vif in-place reservations Michal Kazior
  2014-06-12 12:54     ` [PATCH v9 2/5] mac80211: make check_combinations() aware of chanctx reservation Michal Kazior
@ 2014-06-12 12:54     ` Michal Kazior
  2014-06-12 12:54     ` [PATCH v9 4/5] mac80211: use chanctx reservation for STA CSA Michal Kazior
                       ` (2 subsequent siblings)
  5 siblings, 0 replies; 40+ messages in thread
From: Michal Kazior @ 2014-06-12 12:54 UTC (permalink / raw
  To: linux-wireless; +Cc: luca, johannes, Michal Kazior

Channel switch finalization is now 2-step. First
step is when driver calls csa_finish(), the other
is when reservation is actually finalized (which
can be deferred for in-place reservation).

It is now safe to call ieee80211_csa_finish() more
than once.

Signed-off-by: Michal Kazior <michal.kazior@tieto.com>
---

Notes:
    v3:
     * fix lockdep typo s/mtx/chanctx_mtx/ [Johannes]
     * fix comment style [Johannes]
     * use goto for cleaner unlocking/returning [Johannes]
     * squash with ieee80211_vif_change_channel() removal patch [Johannes]
     * fix commit message [Johannes]
     * add resilience for multiple ieee80211_csa_finish() calls
    
    v4:
     * split removal of ieee80211_vif_change_channel()
    
    v7:
     * tune commit message
     * put the initial ieee80211_vif_chanctx_reservation_complete()
       implementation here [Johannes]
     * remove 'default' case and use all iftypes explicitly and WARN_ON
    
    v8:
     * fix typo in commit message [Johannes]

 net/mac80211/cfg.c  | 82 +++++++++++++++++++++++++++++++++++------------------
 net/mac80211/chan.c | 35 +++++++++++++++++++++--
 2 files changed, 88 insertions(+), 29 deletions(-)

diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index af3eac4..32c3cf8 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -2805,17 +2805,35 @@ static int __ieee80211_csa_finalize(struct ieee80211_sub_if_data *sdata)
 
 	sdata_assert_lock(sdata);
 	lockdep_assert_held(&local->mtx);
+	lockdep_assert_held(&local->chanctx_mtx);
 
-	sdata->radar_required = sdata->csa_radar_required;
-	err = ieee80211_vif_change_channel(sdata, &changed);
-	if (err < 0)
-		return err;
+	/*
+	 * using reservation isn't immediate as it may be deferred until later
+	 * with multi-vif. once reservation is complete it will re-schedule the
+	 * work with no reserved_chanctx so verify chandef to check if it
+	 * completed successfully
+	 */
 
-	if (!local->use_chanctx) {
-		local->_oper_chandef = sdata->csa_chandef;
-		ieee80211_hw_config(local, 0);
+	if (sdata->reserved_chanctx) {
+		/*
+		 * with multi-vif csa driver may call ieee80211_csa_finish()
+		 * many times while waiting for other interfaces to use their
+		 * reservations
+		 */
+		if (sdata->reserved_ready)
+			return 0;
+
+		err = ieee80211_vif_use_reserved_context(sdata);
+		if (err)
+			return err;
+
+		return 0;
 	}
 
+	if (!cfg80211_chandef_identical(&sdata->vif.bss_conf.chandef,
+					&sdata->csa_chandef))
+		return -EINVAL;
+
 	sdata->vif.csa_active = false;
 
 	err = ieee80211_set_after_csa_beacon(sdata, &changed);
@@ -2851,6 +2869,7 @@ void ieee80211_csa_finalize_work(struct work_struct *work)
 
 	sdata_lock(sdata);
 	mutex_lock(&local->mtx);
+	mutex_lock(&local->chanctx_mtx);
 
 	/* AP might have been stopped while waiting for the lock. */
 	if (!sdata->vif.csa_active)
@@ -2862,6 +2881,7 @@ void ieee80211_csa_finalize_work(struct work_struct *work)
 	ieee80211_csa_finalize(sdata);
 
 unlock:
+	mutex_unlock(&local->chanctx_mtx);
 	mutex_unlock(&local->mtx);
 	sdata_unlock(sdata);
 }
@@ -3007,7 +3027,7 @@ __ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
 	struct ieee80211_local *local = sdata->local;
 	struct ieee80211_chanctx_conf *conf;
 	struct ieee80211_chanctx *chanctx;
-	int err, num_chanctx, changed = 0;
+	int err, changed = 0;
 
 	sdata_assert_lock(sdata);
 	lockdep_assert_held(&local->mtx);
@@ -3022,37 +3042,43 @@ __ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
 				       &sdata->vif.bss_conf.chandef))
 		return -EINVAL;
 
+	/* don't allow another channel switch if one is already active. */
+	if (sdata->vif.csa_active)
+		return -EBUSY;
+
 	mutex_lock(&local->chanctx_mtx);
 	conf = rcu_dereference_protected(sdata->vif.chanctx_conf,
 					 lockdep_is_held(&local->chanctx_mtx));
 	if (!conf) {
-		mutex_unlock(&local->chanctx_mtx);
-		return -EBUSY;
+		err = -EBUSY;
+		goto out;
 	}
 
-	/* don't handle for multi-VIF cases */
 	chanctx = container_of(conf, struct ieee80211_chanctx, conf);
-	if (ieee80211_chanctx_refcount(local, chanctx) > 1) {
-		mutex_unlock(&local->chanctx_mtx);
-		return -EBUSY;
+	if (!chanctx) {
+		err = -EBUSY;
+		goto out;
 	}
-	num_chanctx = 0;
-	list_for_each_entry_rcu(chanctx, &local->chanctx_list, list)
-		num_chanctx++;
-	mutex_unlock(&local->chanctx_mtx);
 
-	if (num_chanctx > 1)
-		return -EBUSY;
+	err = ieee80211_vif_reserve_chanctx(sdata, &params->chandef,
+					    chanctx->mode,
+					    params->radar_required);
+	if (err)
+		goto out;
 
-	/* don't allow another channel switch if one is already active. */
-	if (sdata->vif.csa_active)
-		return -EBUSY;
+	/* if reservation is invalid then this will fail */
+	err = ieee80211_check_combinations(sdata, NULL, chanctx->mode, 0);
+	if (err) {
+		ieee80211_vif_unreserve_chanctx(sdata);
+		goto out;
+	}
 
 	err = ieee80211_set_csa_beacon(sdata, params, &changed);
-	if (err)
-		return err;
+	if (err) {
+		ieee80211_vif_unreserve_chanctx(sdata);
+		goto out;
+	}
 
-	sdata->csa_radar_required = params->radar_required;
 	sdata->csa_chandef = params->chandef;
 	sdata->csa_block_tx = params->block_tx;
 	sdata->vif.csa_active = true;
@@ -3070,7 +3096,9 @@ __ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
 		ieee80211_csa_finalize(sdata);
 	}
 
-	return 0;
+out:
+	mutex_unlock(&local->chanctx_mtx);
+	return err;
 }
 
 int ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
index 159f4c3..e50c263 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -1034,6 +1034,30 @@ int ieee80211_vif_reserve_chanctx(struct ieee80211_sub_if_data *sdata,
 	return 0;
 }
 
+static void
+ieee80211_vif_chanctx_reservation_complete(struct ieee80211_sub_if_data *sdata)
+{
+	switch (sdata->vif.type) {
+	case NL80211_IFTYPE_ADHOC:
+	case NL80211_IFTYPE_AP:
+	case NL80211_IFTYPE_MESH_POINT:
+		ieee80211_queue_work(&sdata->local->hw,
+				     &sdata->csa_finalize_work);
+		break;
+	case NL80211_IFTYPE_UNSPECIFIED:
+	case NL80211_IFTYPE_STATION:
+	case NL80211_IFTYPE_AP_VLAN:
+	case NL80211_IFTYPE_WDS:
+	case NL80211_IFTYPE_MONITOR:
+	case NL80211_IFTYPE_P2P_CLIENT:
+	case NL80211_IFTYPE_P2P_GO:
+	case NL80211_IFTYPE_P2P_DEVICE:
+	case NUM_NL80211_IFTYPES:
+		WARN_ON(1);
+		break;
+	}
+}
+
 static int
 ieee80211_vif_use_reserved_reassign(struct ieee80211_sub_if_data *sdata)
 {
@@ -1081,7 +1105,7 @@ ieee80211_vif_use_reserved_reassign(struct ieee80211_sub_if_data *sdata)
 		if (ieee80211_chanctx_refcount(local, new_ctx) == 0)
 			ieee80211_free_chanctx(local, new_ctx);
 
-		return err;
+		goto out;
 	}
 
 	list_move(&sdata->assigned_chanctx_list, &new_ctx->assigned_vifs);
@@ -1101,6 +1125,8 @@ ieee80211_vif_use_reserved_reassign(struct ieee80211_sub_if_data *sdata)
 	if (changed)
 		ieee80211_bss_info_change_notify(sdata, changed);
 
+out:
+	ieee80211_vif_chanctx_reservation_complete(sdata);
 	return err;
 }
 
@@ -1145,6 +1171,7 @@ ieee80211_vif_use_reserved_assign(struct ieee80211_sub_if_data *sdata)
 	}
 
 out:
+	ieee80211_vif_chanctx_reservation_complete(sdata);
 	return err;
 }
 
@@ -1365,6 +1392,8 @@ ieee80211_vif_use_reserved_switch(struct ieee80211_local *local)
 			list_move(&sdata->assigned_chanctx_list,
 				  &new_ctx->assigned_vifs);
 			sdata->reserved_chanctx = NULL;
+
+			ieee80211_vif_chanctx_reservation_complete(sdata);
 		}
 
 		/*
@@ -1428,8 +1457,10 @@ err:
 			continue;
 
 		list_for_each_entry_safe(sdata, sdata_tmp, &ctx->reserved_vifs,
-					 reserved_chanctx_list)
+					 reserved_chanctx_list) {
 			ieee80211_vif_unreserve_chanctx(sdata);
+			ieee80211_vif_chanctx_reservation_complete(sdata);
+		}
 	}
 
 	return err;
-- 
1.8.5.3


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [PATCH v9 4/5] mac80211: use chanctx reservation for STA CSA
  2014-06-12 12:54   ` [PATCH v9 0/5] cfg/mac80211: implement multi-vif csa Michal Kazior
                       ` (2 preceding siblings ...)
  2014-06-12 12:54     ` [PATCH v9 3/5] mac80211: use chanctx reservation for AP CSA Michal Kazior
@ 2014-06-12 12:54     ` Michal Kazior
  2014-06-12 12:54     ` [PATCH v9 5/5] cfg80211: remove channel_switch combination check Michal Kazior
  2014-06-17 12:58     ` [PATCH v10 0/5] cfg/mac80211: implement multi-vif csa Michal Kazior
  5 siblings, 0 replies; 40+ messages in thread
From: Michal Kazior @ 2014-06-12 12:54 UTC (permalink / raw
  To: linux-wireless; +Cc: luca, johannes, Michal Kazior

Channel switch finalization is now 2-step. First
step is when driver calls chswitch_done(), the
other is when reservation is actually finalized
(which be defered for in-place reservation).

It is now safe to call ieee80211_chswitch_done()
more than once.

Also remove the ieee80211_vif_change_channel()
because it is no longer used.

Signed-off-by: Michal Kazior <michal.kazior@tieto.com>
---

Notes:
    v3:
     * fix comment style [Johannes]
     * add resilience for multiple ieee80211_chswitch_done() calls
    
    v3:
     * fix lockdep typo s/mtx/chanctx_mtx/ [Johannes]
     * fix comment style [Johannes]
     * use goto for cleaner unlocking/returning [Johannes]
     * squash with ieee80211_vif_change_channel() removal patch [Johannes]
     * fix commit message [Johannes]
     * add resilience for multiple ieee80211_csa_finish() calls
    
    v4:
     * squash with removal of ieee80211_vif_change_channel()
    
    v7:
     * add local->mtx locking when reserving (for ieee80211_chanctx_alloc)

 net/mac80211/chan.c        | 69 ++------------------------------
 net/mac80211/ieee80211_i.h |  5 ---
 net/mac80211/mlme.c        | 99 ++++++++++++++++++++++++++++++----------------
 3 files changed, 69 insertions(+), 104 deletions(-)

diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
index e50c263..4cf4cac 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -799,70 +799,6 @@ int ieee80211_vif_use_channel(struct ieee80211_sub_if_data *sdata,
 	return ret;
 }
 
-static int __ieee80211_vif_change_channel(struct ieee80211_sub_if_data *sdata,
-					  struct ieee80211_chanctx *ctx,
-					  u32 *changed)
-{
-	struct ieee80211_local *local = sdata->local;
-	const struct cfg80211_chan_def *chandef = &sdata->csa_chandef;
-	u32 chanctx_changed = 0;
-
-	if (!cfg80211_chandef_usable(sdata->local->hw.wiphy, chandef,
-				     IEEE80211_CHAN_DISABLED))
-		return -EINVAL;
-
-	if (ieee80211_chanctx_refcount(local, ctx) != 1)
-		return -EINVAL;
-
-	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);
-
-	ieee80211_recalc_chanctx_chantype(local, ctx);
-	ieee80211_recalc_smps_chanctx(local, ctx);
-	ieee80211_recalc_radar_chanctx(local, ctx);
-	ieee80211_recalc_chanctx_min_def(local, ctx);
-
-	return 0;
-}
-
-int ieee80211_vif_change_channel(struct ieee80211_sub_if_data *sdata,
-				 u32 *changed)
-{
-	struct ieee80211_local *local = sdata->local;
-	struct ieee80211_chanctx_conf *conf;
-	struct ieee80211_chanctx *ctx;
-	int ret;
-
-	lockdep_assert_held(&local->mtx);
-
-	/* should never be called if not performing a channel switch. */
-	if (WARN_ON(!sdata->vif.csa_active))
-		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);
-
-	ret = __ieee80211_vif_change_channel(sdata, ctx, changed);
- out:
-	mutex_unlock(&local->chanctx_mtx);
-	return ret;
-}
-
 static void
 __ieee80211_vif_copy_chanctx_to_vlans(struct ieee80211_sub_if_data *sdata,
 				      bool clear)
@@ -1044,8 +980,11 @@ ieee80211_vif_chanctx_reservation_complete(struct ieee80211_sub_if_data *sdata)
 		ieee80211_queue_work(&sdata->local->hw,
 				     &sdata->csa_finalize_work);
 		break;
-	case NL80211_IFTYPE_UNSPECIFIED:
 	case NL80211_IFTYPE_STATION:
+		ieee80211_queue_work(&sdata->local->hw,
+				     &sdata->u.mgd.chswitch_work);
+		break;
+	case NL80211_IFTYPE_UNSPECIFIED:
 	case NL80211_IFTYPE_AP_VLAN:
 	case NL80211_IFTYPE_WDS:
 	case NL80211_IFTYPE_MONITOR:
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 4c6dd32..341bc6c 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -787,7 +787,6 @@ struct ieee80211_sub_if_data {
 	struct mac80211_qos_map __rcu *qos_map;
 
 	struct work_struct csa_finalize_work;
-	bool csa_radar_required;
 	bool csa_block_tx; /* write-protected by sdata_lock and local->mtx */
 	struct cfg80211_chan_def csa_chandef;
 
@@ -1829,10 +1828,6 @@ 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,
-			     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,
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index 7f073ef..baea348 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -940,51 +940,69 @@ static void ieee80211_chswitch_work(struct work_struct *work)
 		container_of(work, struct ieee80211_sub_if_data, u.mgd.chswitch_work);
 	struct ieee80211_local *local = sdata->local;
 	struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
-	u32 changed = 0;
 	int ret;
 
 	if (!ieee80211_sdata_running(sdata))
 		return;
 
 	sdata_lock(sdata);
+	mutex_lock(&local->mtx);
+	mutex_lock(&local->chanctx_mtx);
+
 	if (!ifmgd->associated)
 		goto out;
 
-	mutex_lock(&local->mtx);
-	ret = ieee80211_vif_change_channel(sdata, &changed);
-	mutex_unlock(&local->mtx);
-	if (ret) {
+	if (!sdata->vif.csa_active)
+		goto out;
+
+	/*
+	 * using reservation isn't immediate as it may be deferred until later
+	 * with multi-vif. once reservation is complete it will re-schedule the
+	 * work with no reserved_chanctx so verify chandef to check if it
+	 * completed successfully
+	 */
+
+	if (sdata->reserved_chanctx) {
+		/*
+		 * with multi-vif csa driver may call ieee80211_csa_finish()
+		 * many times while waiting for other interfaces to use their
+		 * reservations
+		 */
+		if (sdata->reserved_ready)
+			goto out;
+
+		ret = ieee80211_vif_use_reserved_context(sdata);
+		if (ret) {
+			sdata_info(sdata,
+				   "failed to use reserved channel context, disconnecting (err=%d)\n",
+				   ret);
+			ieee80211_queue_work(&sdata->local->hw,
+					     &ifmgd->csa_connection_drop_work);
+			goto out;
+		}
+
+		goto out;
+	}
+
+	if (!cfg80211_chandef_identical(&sdata->vif.bss_conf.chandef,
+					&sdata->csa_chandef)) {
 		sdata_info(sdata,
-			   "vif channel switch failed, disconnecting\n");
+			   "failed to finalize channel switch, disconnecting\n");
 		ieee80211_queue_work(&sdata->local->hw,
 				     &ifmgd->csa_connection_drop_work);
 		goto out;
 	}
 
-	if (!local->use_chanctx) {
-		local->_oper_chandef = sdata->csa_chandef;
-		/* Call "hw_config" only if doing sw channel switch.
-		 * Otherwise update the channel directly
-		 */
-		if (!local->ops->channel_switch)
-			ieee80211_hw_config(local, 0);
-		else
-			local->hw.conf.chandef = local->_oper_chandef;
-	}
-
 	/* XXX: shouldn't really modify cfg80211-owned data! */
 	ifmgd->associated->channel = sdata->csa_chandef.chan;
 
-	ieee80211_bss_info_change_notify(sdata, changed);
-
-	mutex_lock(&local->mtx);
 	sdata->vif.csa_active = false;
+
 	/* XXX: wait for a beacon first? */
 	if (!ieee80211_csa_needs_block_tx(local))
 		ieee80211_wake_queues_by_reason(&local->hw,
 					IEEE80211_MAX_QUEUE_MAP,
 					IEEE80211_QUEUE_STOP_REASON_CSA);
-	mutex_unlock(&local->mtx);
 
 	ifmgd->flags &= ~IEEE80211_STA_CSA_RECEIVED;
 
@@ -992,6 +1010,8 @@ static void ieee80211_chswitch_work(struct work_struct *work)
 	ieee80211_sta_reset_conn_monitor(sdata);
 
 out:
+	mutex_unlock(&local->chanctx_mtx);
+	mutex_unlock(&local->mtx);
 	sdata_unlock(sdata);
 }
 
@@ -1028,6 +1048,7 @@ ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,
 	struct ieee80211_local *local = sdata->local;
 	struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
 	struct cfg80211_bss *cbss = ifmgd->associated;
+	struct ieee80211_chanctx_conf *conf;
 	struct ieee80211_chanctx *chanctx;
 	enum ieee80211_band current_band;
 	struct ieee80211_csa_ie csa_ie;
@@ -1071,7 +1092,22 @@ ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,
 
 	ifmgd->flags |= IEEE80211_STA_CSA_RECEIVED;
 
+	mutex_lock(&local->mtx);
 	mutex_lock(&local->chanctx_mtx);
+	conf = rcu_dereference_protected(sdata->vif.chanctx_conf,
+					 lockdep_is_held(&local->chanctx_mtx));
+	if (!conf) {
+		sdata_info(sdata,
+			   "no channel context assigned to vif?, disconnecting\n");
+		ieee80211_queue_work(&local->hw,
+				     &ifmgd->csa_connection_drop_work);
+		mutex_unlock(&local->chanctx_mtx);
+		mutex_unlock(&local->mtx);
+		return;
+	}
+
+	chanctx = container_of(conf, struct ieee80211_chanctx, conf);
+
 	if (local->use_chanctx) {
 		u32 num_chanctx = 0;
 		list_for_each_entry(chanctx, &local->chanctx_list, list)
@@ -1084,32 +1120,27 @@ ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,
 			ieee80211_queue_work(&local->hw,
 					     &ifmgd->csa_connection_drop_work);
 			mutex_unlock(&local->chanctx_mtx);
+			mutex_unlock(&local->mtx);
 			return;
 		}
 	}
 
-	if (WARN_ON(!rcu_access_pointer(sdata->vif.chanctx_conf))) {
-		ieee80211_queue_work(&local->hw,
-				     &ifmgd->csa_connection_drop_work);
-		mutex_unlock(&local->chanctx_mtx);
-		return;
-	}
-	chanctx = container_of(rcu_access_pointer(sdata->vif.chanctx_conf),
-			       struct ieee80211_chanctx, conf);
-	if (ieee80211_chanctx_refcount(local, chanctx) > 1) {
+	res = ieee80211_vif_reserve_chanctx(sdata, &csa_ie.chandef,
+					    chanctx->mode, false);
+	if (res) {
 		sdata_info(sdata,
-			   "channel switch with multiple interfaces on the same channel, disconnecting\n");
+			   "failed to reserve channel context for channel switch, disconnecting (err=%d)\n",
+			   res);
 		ieee80211_queue_work(&local->hw,
 				     &ifmgd->csa_connection_drop_work);
 		mutex_unlock(&local->chanctx_mtx);
+		mutex_unlock(&local->mtx);
 		return;
 	}
 	mutex_unlock(&local->chanctx_mtx);
 
-	sdata->csa_chandef = csa_ie.chandef;
-
-	mutex_lock(&local->mtx);
 	sdata->vif.csa_active = true;
+	sdata->csa_chandef = csa_ie.chandef;
 	sdata->csa_block_tx = csa_ie.mode;
 
 	if (sdata->csa_block_tx)
-- 
1.8.5.3


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [PATCH v9 5/5] cfg80211: remove channel_switch combination check
  2014-06-12 12:54   ` [PATCH v9 0/5] cfg/mac80211: implement multi-vif csa Michal Kazior
                       ` (3 preceding siblings ...)
  2014-06-12 12:54     ` [PATCH v9 4/5] mac80211: use chanctx reservation for STA CSA Michal Kazior
@ 2014-06-12 12:54     ` Michal Kazior
  2014-06-17 12:58     ` [PATCH v10 0/5] cfg/mac80211: implement multi-vif csa Michal Kazior
  5 siblings, 0 replies; 40+ messages in thread
From: Michal Kazior @ 2014-06-12 12:54 UTC (permalink / raw
  To: linux-wireless; +Cc: luca, johannes, Michal Kazior

Driver is now responsible for veryfing if the
switch is possible.

Since this is inherently tricky driver may decide
to disconnect an interface later with
cfg80211_stop_iface().

This doesn't mean driver can accept everything. It
should do it's best to verify requests and reject
them as soon as possible.

Signed-off-by: Michal Kazior <michal.kazior@tieto.com>
---

Notes:
    v7:
     * emphasize channel switching verification should not be
       taken lightly [Johannes]
     * update channel_switch description in cfg80211.h

 include/net/cfg80211.h |  7 ++++++-
 net/wireless/nl80211.c | 11 -----------
 2 files changed, 6 insertions(+), 12 deletions(-)

diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 6b02dd6..3d829d4 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -2301,7 +2301,12 @@ struct cfg80211_qos_map {
  *	reliability. This operation can not fail.
  * @set_coalesce: Set coalesce parameters.
  *
- * @channel_switch: initiate channel-switch procedure (with CSA)
+ * @channel_switch: initiate channel-switch procedure (with CSA). Driver is
+ *	responsible for veryfing if the switch is possible. Since this is
+ *	inherently tricky driver may decide to disconnect an interface later
+ *	with cfg80211_stop_iface(). This doesn't mean driver can accept
+ *	everything. It should do it's best to verify requests and reject them
+ *	as soon as possible.
  *
  * @set_qos_map: Set QoS mapping information to the driver
  *
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index ba4f172..d70f542 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -6012,17 +6012,6 @@ skip_beacons:
 		params.radar_required = true;
 	}
 
-	/* TODO: I left this here for now.  With channel switch, the
-	 * verification is a bit more complicated, because we only do
-	 * it later when the channel switch really happens.
-	 */
-	err = cfg80211_can_use_iftype_chan(rdev, wdev, wdev->iftype,
-					   params.chandef.chan,
-					   CHAN_MODE_SHARED,
-					   radar_detect_width);
-	if (err)
-		return err;
-
 	if (info->attrs[NL80211_ATTR_CH_SWITCH_BLOCK_TX])
 		params.block_tx = true;
 
-- 
1.8.5.3


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* Re: [PATCH v9 1/5] mac80211: implement multi-vif in-place reservations
  2014-06-12 12:54     ` [PATCH v9 1/5] mac80211: implement multi-vif in-place reservations Michal Kazior
@ 2014-06-16 14:35       ` Luca Coelho
  2014-06-17  5:52         ` Michal Kazior
  0 siblings, 1 reply; 40+ messages in thread
From: Luca Coelho @ 2014-06-16 14:35 UTC (permalink / raw
  To: Michal Kazior; +Cc: linux-wireless, johannes

On Thu, 2014-06-12 at 14:54 +0200, Michal Kazior wrote:
> Multi-vif in-place reservations happen when
> it is impossible to allocate more channel contexts
> as indicated by interface combinations.
> 
> Such reservations are not finalized until all
> assigned interfaces are ready.
> 
> This still doesn't handle all possible cases
> (i.e. degradation of number of channels) properly.
> 
> Signed-off-by: Michal Kazior <michal.kazior@tieto.com>
> ---
> 

[...]

> diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
> index 3702d64..159f4c3 100644
> --- a/net/mac80211/chan.c
> +++ b/net/mac80211/chan.c
> @@ -63,6 +63,19 @@ static bool ieee80211_can_create_new_chanctx(struct ieee80211_local *local)
>  	return ieee80211_num_chanctx(local) < ieee80211_max_num_channels(local);
>  }
>  
> +static struct ieee80211_chanctx *
> +ieee80211_vif_get_chanctx(struct ieee80211_sub_if_data *sdata)
> +{
> +	struct ieee80211_chanctx_conf *conf;
> +
> +	conf = rcu_dereference_protected(sdata->vif.chanctx_conf,
> +					 lockdep_is_held(&local->chanctx_mtx));

This doesn't compile.  local is undefined here. :(

It's a simple fix though.  s/local/sdata->local/

--
Luca.


^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v9 1/5] mac80211: implement multi-vif in-place reservations
  2014-06-16 14:35       ` Luca Coelho
@ 2014-06-17  5:52         ` Michal Kazior
  0 siblings, 0 replies; 40+ messages in thread
From: Michal Kazior @ 2014-06-17  5:52 UTC (permalink / raw
  To: Luca Coelho; +Cc: linux-wireless, Johannes Berg

On 16 June 2014 16:35, Luca Coelho <luca@coelho.fi> wrote:
> On Thu, 2014-06-12 at 14:54 +0200, Michal Kazior wrote:
>> Multi-vif in-place reservations happen when
>> it is impossible to allocate more channel contexts
>> as indicated by interface combinations.
>>
>> Such reservations are not finalized until all
>> assigned interfaces are ready.
>>
>> This still doesn't handle all possible cases
>> (i.e. degradation of number of channels) properly.
>>
>> Signed-off-by: Michal Kazior <michal.kazior@tieto.com>
>> ---
>>
>
> [...]
>
>> diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
>> index 3702d64..159f4c3 100644
>> --- a/net/mac80211/chan.c
>> +++ b/net/mac80211/chan.c
>> @@ -63,6 +63,19 @@ static bool ieee80211_can_create_new_chanctx(struct ieee80211_local *local)
>>       return ieee80211_num_chanctx(local) < ieee80211_max_num_channels(local);
>>  }
>>
>> +static struct ieee80211_chanctx *
>> +ieee80211_vif_get_chanctx(struct ieee80211_sub_if_data *sdata)
>> +{
>> +     struct ieee80211_chanctx_conf *conf;
>> +
>> +     conf = rcu_dereference_protected(sdata->vif.chanctx_conf,
>> +                                      lockdep_is_held(&local->chanctx_mtx));
>
> This doesn't compile.  local is undefined here. :(
>
> It's a simple fix though.  s/local/sdata->local/

Oh.. thanks! How did I not catch this -_-?


Michał

^ permalink raw reply	[flat|nested] 40+ messages in thread

* [PATCH v10 0/5] cfg/mac80211: implement multi-vif csa
  2014-06-12 12:54   ` [PATCH v9 0/5] cfg/mac80211: implement multi-vif csa Michal Kazior
                       ` (4 preceding siblings ...)
  2014-06-12 12:54     ` [PATCH v9 5/5] cfg80211: remove channel_switch combination check Michal Kazior
@ 2014-06-17 12:58     ` Michal Kazior
  2014-06-17 12:58       ` [PATCH v10 1/5] mac80211: implement multi-vif in-place reservations Michal Kazior
                         ` (6 more replies)
  5 siblings, 7 replies; 40+ messages in thread
From: Michal Kazior @ 2014-06-17 12:58 UTC (permalink / raw
  To: linux-wireless; +Cc: luca, johannes, Michal Kazior

Hi,

The patchset implements multi-interface
reservations and multi-interface channel
switching.

There are still some cases that aren't handled
well (at least degradation of number of channels
due to interface combinations that I'm aware of).


v10:
 * rebase to latest mac80211-next/master
   (90c18a8b34add196da9b07fd37d0f3b4b451c6b3)
 * fix compilation [Luca]

Michal Kazior (5):
  mac80211: implement multi-vif in-place reservations
  mac80211: make check_combinations() aware of chanctx reservation
  mac80211: use chanctx reservation for AP CSA
  mac80211: use chanctx reservation for STA CSA
  cfg80211: remove channel_switch combination check

 include/net/cfg80211.h     |   7 +-
 include/net/mac80211.h     |   7 -
 net/mac80211/cfg.c         |  82 +++--
 net/mac80211/chan.c        | 729 ++++++++++++++++++++++++++++++++++++---------
 net/mac80211/ieee80211_i.h |  30 +-
 net/mac80211/mlme.c        |  99 +++---
 net/mac80211/util.c        |  44 ++-
 net/wireless/nl80211.c     |  11 -
 8 files changed, 773 insertions(+), 236 deletions(-)

-- 
1.8.5.3


^ permalink raw reply	[flat|nested] 40+ messages in thread

* [PATCH v10 1/5] mac80211: implement multi-vif in-place reservations
  2014-06-17 12:58     ` [PATCH v10 0/5] cfg/mac80211: implement multi-vif csa Michal Kazior
@ 2014-06-17 12:58       ` Michal Kazior
  2014-06-23 13:03         ` Johannes Berg
  2014-06-17 12:58       ` [PATCH v10 2/5] mac80211: make check_combinations() aware of chanctx reservation Michal Kazior
                         ` (5 subsequent siblings)
  6 siblings, 1 reply; 40+ messages in thread
From: Michal Kazior @ 2014-06-17 12:58 UTC (permalink / raw
  To: linux-wireless; +Cc: luca, johannes, Michal Kazior

Multi-vif in-place reservations happen when
it is impossible to allocate more channel contexts
as indicated by interface combinations.

Such reservations are not finalized until all
assigned interfaces are ready.

This still doesn't handle all possible cases
(i.e. degradation of number of channels) properly.

Signed-off-by: Michal Kazior <michal.kazior@tieto.com>
---

Notes:
    v2:
     * use new_ctx instead of ctx [Eliad]
     * move recalcs after bss_conf is updated [Eliad]
    
    v4:
     * move recalc-radar before vif-chanctx-assign [Eliad]
     * move radar_required swapping before initial add_chanctx() [Eliad]
    
    v5:
     * move kfree_rcu() [Zhao Gang]
    
    v6:
     * use switch_vif_chanctx for incompat case with chanctx drivers [Johannes]
     * fix vlan chanctx copying
    
    v7:
     * major rework of the algorithm to support cross-context vif switches (e.g.
       when 2 contexts depend on each others reserved chandefs and both need
       in-place switch)
     * create dummy chanctx structure for in-place reservations instead of storing
       everything in a single chanctx (new chanctx structure needs to be allocated
       sooner or later for drv_switch_vif_chanctx)
     * remove the ieee80211_vif_chanctx_reservation_complete() stub; it is now
       simply implemented by AP CSA patch [Johannes]
     * fix lines with over 80 chars
    
    v8:
     * implement assign reservation (i.e. when old_ctx is NULL) [Luca]
     * expand the TODO description for context shuffling [Luca]
     * add missing !list_empty() when reserving in-place [Luca]
     * fix independ[ae]nt typo [Luca]
     * add comment-labels in the reserved_switch() [Luca]
     * relax in-place verification to allow racy switches to succeed [Luca]
     * refine some comments [Luca]
     * remove WARN_ON for re-assign reservations after in-place reservation [Luca]
     * simplify re-assign reservation condition [Luca]
     * do not propagate error for in-place reservation failure if it was a result
       of a depending old_ctx being ready by re-assign of another vif [Luca]
     * fix in-place reservation context/vif list clearing
     * replace replaces/replaced_by with replace_state(enum)/replace_ctx(pointer) [Johannes]
     * var declare/init cleanup [Luca/Johannes]
     * some condition fixups
    
    v9:
     * fix use-after-free (missing rcu_assign_pointer in _reassign reservation) [Eliad]
    
    v10:
     * fix compilation issue when RCU debugging is enabled [Luca]

 include/net/mac80211.h     |   7 -
 net/mac80211/chan.c        | 633 +++++++++++++++++++++++++++++++++++++++------
 net/mac80211/ieee80211_i.h |  25 +-
 net/mac80211/util.c        |   9 +-
 4 files changed, 583 insertions(+), 91 deletions(-)

diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 421b6ec..66040af 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -1600,12 +1600,6 @@ struct ieee80211_tx_control {
  *	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,
@@ -1637,7 +1631,6 @@ enum ieee80211_hw_flags {
 	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,
 };
 
 /**
diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
index 3702d64..2560fbe 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -63,6 +63,20 @@ static bool ieee80211_can_create_new_chanctx(struct ieee80211_local *local)
 	return ieee80211_num_chanctx(local) < ieee80211_max_num_channels(local);
 }
 
+static struct ieee80211_chanctx *
+ieee80211_vif_get_chanctx(struct ieee80211_sub_if_data *sdata)
+{
+	struct ieee80211_local *local = sdata->local;
+	struct ieee80211_chanctx_conf *conf;
+
+	conf = rcu_dereference_protected(sdata->vif.chanctx_conf,
+					 lockdep_is_held(&local->chanctx_mtx));
+	if (!conf)
+		return NULL;
+
+	return container_of(conf, struct ieee80211_chanctx, conf);
+}
+
 static const struct cfg80211_chan_def *
 ieee80211_chanctx_reserved_chandef(struct ieee80211_local *local,
 				   struct ieee80211_chanctx *ctx,
@@ -160,6 +174,9 @@ ieee80211_find_reservation_chanctx(struct ieee80211_local *local,
 		return NULL;
 
 	list_for_each_entry(ctx, &local->chanctx_list, list) {
+		if (ctx->replace_state == IEEE80211_CHANCTX_WILL_BE_REPLACED)
+			continue;
+
 		if (ctx->mode == IEEE80211_CHANCTX_EXCLUSIVE)
 			continue;
 
@@ -347,6 +364,9 @@ ieee80211_find_chanctx(struct ieee80211_local *local,
 	list_for_each_entry(ctx, &local->chanctx_list, list) {
 		const struct cfg80211_chan_def *compat;
 
+		if (ctx->replace_state != IEEE80211_CHANCTX_REPLACE_NONE)
+			continue;
+
 		if (ctx->mode == IEEE80211_CHANCTX_EXCLUSIVE)
 			continue;
 
@@ -898,8 +918,25 @@ int ieee80211_vif_unreserve_chanctx(struct ieee80211_sub_if_data *sdata)
 	list_del(&sdata->reserved_chanctx_list);
 	sdata->reserved_chanctx = NULL;
 
-	if (ieee80211_chanctx_refcount(sdata->local, ctx) == 0)
-		ieee80211_free_chanctx(sdata->local, ctx);
+	if (ieee80211_chanctx_refcount(sdata->local, ctx) == 0) {
+		if (ctx->replace_state == IEEE80211_CHANCTX_REPLACES_OTHER) {
+			if (WARN_ON(!ctx->replace_ctx))
+				return -EINVAL;
+
+			WARN_ON(ctx->replace_ctx->replace_state !=
+			        IEEE80211_CHANCTX_WILL_BE_REPLACED);
+			WARN_ON(ctx->replace_ctx->replace_ctx != ctx);
+
+			ctx->replace_ctx->replace_ctx = NULL;
+			ctx->replace_ctx->replace_state =
+					IEEE80211_CHANCTX_REPLACE_NONE;
+
+			list_del_rcu(&ctx->list);
+			kfree_rcu(ctx, rcu_head);
+		} else {
+			ieee80211_free_chanctx(sdata->local, ctx);
+		}
+	}
 
 	return 0;
 }
@@ -910,40 +947,82 @@ int ieee80211_vif_reserve_chanctx(struct ieee80211_sub_if_data *sdata,
 				  bool radar_required)
 {
 	struct ieee80211_local *local = sdata->local;
-	struct ieee80211_chanctx_conf *conf;
-	struct ieee80211_chanctx *new_ctx, *curr_ctx;
-	int ret = 0;
+	struct ieee80211_chanctx *new_ctx, *curr_ctx, *ctx;
 
-	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;
-	}
+	lockdep_assert_held(&local->chanctx_mtx);
 
-	curr_ctx = container_of(conf, struct ieee80211_chanctx, conf);
+	curr_ctx = ieee80211_vif_get_chanctx(sdata);
+	if (curr_ctx && local->use_chanctx && !local->ops->switch_vif_chanctx)
+		return -ENOTSUPP;
 
 	new_ctx = ieee80211_find_reservation_chanctx(local, chandef, mode);
 	if (!new_ctx) {
-		if (ieee80211_chanctx_refcount(local, curr_ctx) == 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 if (ieee80211_can_create_new_chanctx(local)) {
-			/* create a new context and reserve it */
+		if (ieee80211_can_create_new_chanctx(local)) {
 			new_ctx = ieee80211_new_chanctx(local, chandef, mode);
-			if (IS_ERR(new_ctx)) {
-				ret = PTR_ERR(new_ctx);
-				goto out;
-			}
+			if (IS_ERR(new_ctx))
+				return PTR_ERR(new_ctx);
 		} else {
-			ret = -EBUSY;
-			goto out;
+			if ((curr_ctx->replace_state ==
+			     IEEE80211_CHANCTX_WILL_BE_REPLACED) ||
+			    !list_empty(&curr_ctx->reserved_vifs)) {
+				/*
+				 * Another vif already requested this context
+				 * for a reservation. Find another one hoping
+				 * all vifs assigned to it will also switch
+				 * soon enough.
+				 *
+				 * TODO: This needs a little more work as some
+				 * cases (more than 2 chanctx capable devices)
+				 * may fail which could otherwise succeed
+				 * provided some channel context juggling was
+				 * performed.
+				 *
+				 * Consider ctx1..3, vif1..6, each ctx has 2
+				 * vifs. vif1 and vif2 from ctx1 request new
+				 * different chandefs starting 2 in-place
+				 * reserations with ctx4 and ctx5 replacing
+				 * ctx1 and ctx2 respectively. Next vif5 and
+				 * vif6 from ctx3 reserve ctx4. If vif3 and
+				 * vif4 remain on ctx2 as they are then this
+				 * fails unless `replace_ctx` from ctx5 is
+				 * replaced with ctx3.
+				 */
+				list_for_each_entry(ctx, &local->chanctx_list,
+						    list) {
+					if (ctx->replace_state !=
+					    IEEE80211_CHANCTX_REPLACE_NONE)
+						continue;
+
+					if (!list_empty(&ctx->reserved_vifs))
+						continue;
+
+					curr_ctx = ctx;
+					break;
+				}
+			}
+
+			/*
+			 * If that's true then all available contexts already
+			 * have reservations and cannot be used.
+			 */
+			if ((curr_ctx->replace_state ==
+			     IEEE80211_CHANCTX_WILL_BE_REPLACED) ||
+			    !list_empty(&curr_ctx->reserved_vifs))
+				return -EBUSY;
+
+			new_ctx = ieee80211_alloc_chanctx(local, chandef, mode);
+			if (!new_ctx)
+				return -ENOMEM;
+
+			new_ctx->replace_ctx = curr_ctx;
+			new_ctx->replace_state =
+					IEEE80211_CHANCTX_REPLACES_OTHER;
+
+			curr_ctx->replace_ctx = new_ctx;
+			curr_ctx->replace_state =
+					IEEE80211_CHANCTX_WILL_BE_REPLACED;
+
+			list_add_rcu(&new_ctx->list, &local->chanctx_list);
 		}
 	}
 
@@ -951,82 +1030,474 @@ int ieee80211_vif_reserve_chanctx(struct ieee80211_sub_if_data *sdata,
 	sdata->reserved_chanctx = new_ctx;
 	sdata->reserved_chandef = *chandef;
 	sdata->reserved_radar_required = radar_required;
-out:
-	mutex_unlock(&local->chanctx_mtx);
-	return ret;
+	sdata->reserved_ready = false;
+
+	return 0;
 }
 
-int ieee80211_vif_use_reserved_context(struct ieee80211_sub_if_data *sdata,
-				       u32 *changed)
+static int
+ieee80211_vif_use_reserved_reassign(struct ieee80211_sub_if_data *sdata)
 {
 	struct ieee80211_local *local = sdata->local;
-	struct ieee80211_chanctx *ctx;
-	struct ieee80211_chanctx *old_ctx;
-	struct ieee80211_chanctx_conf *conf;
-	int ret;
-	u32 tmp_changed = *changed;
-
-	/* TODO: need to recheck if the chandef is usable etc.? */
+	struct ieee80211_vif_chanctx_switch vif_chsw[1] = {};
+	struct ieee80211_chanctx *old_ctx, *new_ctx;
+	const struct cfg80211_chan_def *chandef;
+	u32 changed = 0;
+	int err;
 
 	lockdep_assert_held(&local->mtx);
+	lockdep_assert_held(&local->chanctx_mtx);
 
-	mutex_lock(&local->chanctx_mtx);
+	new_ctx = sdata->reserved_chanctx;
+	old_ctx = ieee80211_vif_get_chanctx(sdata);
 
-	ctx = sdata->reserved_chanctx;
-	if (WARN_ON(!ctx)) {
-		ret = -EINVAL;
-		goto out;
-	}
+	if (WARN_ON(!sdata->reserved_ready))
+		return -EBUSY;
 
-	conf = rcu_dereference_protected(sdata->vif.chanctx_conf,
-					 lockdep_is_held(&local->chanctx_mtx));
-	if (!conf) {
-		ret = -EINVAL;
-		goto out;
+	if (WARN_ON(!new_ctx))
+		return -EINVAL;
+
+	if (WARN_ON(!old_ctx))
+		return -EINVAL;
+
+	if (WARN_ON(new_ctx->replace_state ==
+		    IEEE80211_CHANCTX_REPLACES_OTHER))
+		return -EINVAL;
+
+	chandef = ieee80211_chanctx_non_reserved_chandef(local, new_ctx,
+				&sdata->reserved_chandef);
+	if (WARN_ON(!chandef))
+		return -EINVAL;
+
+	vif_chsw[0].vif = &sdata->vif;
+	vif_chsw[0].old_ctx = &old_ctx->conf;
+	vif_chsw[0].new_ctx = &new_ctx->conf;
+
+	list_del(&sdata->reserved_chanctx_list);
+	sdata->reserved_chanctx = NULL;
+
+	err = drv_switch_vif_chanctx(local, vif_chsw, 1,
+				     CHANCTX_SWMODE_REASSIGN_VIF);
+	if (err) {
+		if (ieee80211_chanctx_refcount(local, new_ctx) == 0)
+			ieee80211_free_chanctx(local, new_ctx);
+
+		return err;
 	}
 
-	old_ctx = container_of(conf, struct ieee80211_chanctx, conf);
+	list_move(&sdata->assigned_chanctx_list, &new_ctx->assigned_vifs);
+	rcu_assign_pointer(sdata->vif.chanctx_conf, &new_ctx->conf);
+
+	if (sdata->vif.type == NL80211_IFTYPE_AP)
+		__ieee80211_vif_copy_chanctx_to_vlans(sdata, false);
+
+	if (ieee80211_chanctx_refcount(local, old_ctx) == 0)
+		ieee80211_free_chanctx(local, old_ctx);
 
 	if (sdata->vif.bss_conf.chandef.width != sdata->reserved_chandef.width)
-		tmp_changed |= BSS_CHANGED_BANDWIDTH;
+		changed = BSS_CHANGED_BANDWIDTH;
 
 	sdata->vif.bss_conf.chandef = sdata->reserved_chandef;
 
-	/* unref our reservation */
-	sdata->reserved_chanctx = NULL;
-	sdata->radar_required = sdata->reserved_radar_required;
+	if (changed)
+		ieee80211_bss_info_change_notify(sdata, changed);
+
+	return err;
+}
+
+static int
+ieee80211_vif_use_reserved_assign(struct ieee80211_sub_if_data *sdata)
+{
+	struct ieee80211_local *local = sdata->local;
+	struct ieee80211_chanctx *old_ctx, *new_ctx;
+	const struct cfg80211_chan_def *chandef;
+	int err;
+
+	old_ctx = ieee80211_vif_get_chanctx(sdata);
+	new_ctx = sdata->reserved_chanctx;
+
+	if (WARN_ON(!sdata->reserved_ready))
+		return -EINVAL;
+
+	if (WARN_ON(old_ctx))
+		return -EINVAL;
+
+	if (WARN_ON(!new_ctx))
+		return -EINVAL;
+
+	if (WARN_ON(new_ctx->replace_state ==
+		    IEEE80211_CHANCTX_REPLACES_OTHER))
+		return -EINVAL;
+
+	chandef = ieee80211_chanctx_non_reserved_chandef(local, new_ctx,
+				&sdata->reserved_chandef);
+	if (WARN_ON(!chandef))
+		return -EINVAL;
+
 	list_del(&sdata->reserved_chanctx_list);
+	sdata->reserved_chanctx = NULL;
 
-	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;
+	err = ieee80211_assign_vif_chanctx(sdata, new_ctx);
+	if (err) {
+		if (ieee80211_chanctx_refcount(local, new_ctx) == 0)
+			ieee80211_free_chanctx(local, new_ctx);
+
+		goto out;
+	}
+
+out:
+	return err;
+}
+
+static bool
+ieee80211_vif_has_in_place_reservation(struct ieee80211_sub_if_data *sdata)
+{
+	struct ieee80211_chanctx *old_ctx, *new_ctx;
+
+	lockdep_assert_held(&sdata->local->chanctx_mtx);
+
+	new_ctx = sdata->reserved_chanctx;
+	old_ctx = ieee80211_vif_get_chanctx(sdata);
+
+	if (!old_ctx)
+		return false;
+
+	if (WARN_ON(!new_ctx))
+		return false;
+
+	if (old_ctx->replace_state != IEEE80211_CHANCTX_WILL_BE_REPLACED)
+		return false;
+
+	if (new_ctx->replace_state != IEEE80211_CHANCTX_REPLACES_OTHER)
+		return false;
+
+	return true;
+}
+
+static int
+ieee80211_vif_use_reserved_switch(struct ieee80211_local *local)
+{
+	struct ieee80211_sub_if_data *sdata, *sdata_tmp;
+	struct ieee80211_chanctx *ctx, *ctx_tmp;
+	struct ieee80211_chanctx *new_ctx = NULL;
+	struct ieee80211_vif_chanctx_switch *vif_chsw = NULL;
+	const struct cfg80211_chan_def *chandef;
+	int i, err;
+	int n_ctx = 0, n_vifs = 0, n_assigned = 0, n_reserved = 0, n_ready = 0;
+
+	lockdep_assert_held(&local->mtx);
+	lockdep_assert_held(&local->chanctx_mtx);
+
+	/*
+	 * If there are 2 independent pairs of channel contexts performing
+	 * cross-switch of their vifs this code will still wait until both are
+	 * ready even though it could be possible to switch one before the
+	 * other is ready.
+	 *
+	 * For practical reasons and code simplicity just do a single huge
+	 * switch.
+	 */
+
+	/*
+	 * Verify if the reservation is still feasible.
+	 *  - if it's not then disconnect
+	 *  - if it is but not all vifs necessary are ready then defer
+	 */
+
+	list_for_each_entry(ctx, &local->chanctx_list, list) {
+		if (ctx->replace_state != IEEE80211_CHANCTX_REPLACES_OTHER)
+			continue;
+
+		if (WARN_ON(!ctx->replace_ctx))
+			return -EINVAL;
+
+		if (!local->use_chanctx)
+			new_ctx = ctx;
+
+		n_ctx++;
+
+		list_for_each_entry(sdata, &ctx->replace_ctx->assigned_vifs,
+				    assigned_chanctx_list) {
+			n_assigned++;
+			if (sdata->reserved_chanctx) {
+				n_reserved++;
+				if (sdata->reserved_ready)
+					n_ready++;
+			}
+		}
+
+		if (n_assigned != n_reserved) {
+			if (n_ready == n_reserved) {
+				wiphy_info(local->hw.wiphy,
+					   "channel context reservation cannot be finalized because some interfaces aren't switching\n");
+				err = -EBUSY;
+				goto err;
+			}
+
+			return -EAGAIN;
+		}
+
+		list_for_each_entry(sdata, &ctx->reserved_vifs,
+				    reserved_chanctx_list) {
+			if (!ieee80211_vif_has_in_place_reservation(sdata))
+				continue;
+
+			if (!sdata->reserved_ready)
+				return -EAGAIN;
+
+			n_vifs++;
+
+			if (sdata->reserved_radar_required)
+				ctx->conf.radar_enabled = true;
+		}
+	}
+
+	if (WARN_ON(n_ctx == 0) ||
+	    WARN_ON(n_vifs == 0) ||
+	    WARN_ON(n_ctx > 1 && !local->use_chanctx) ||
+	    WARN_ON(!new_ctx && !local->use_chanctx)) {
+		err = -EINVAL;
+		goto err;
+	}
+
+	/*
+	 * All necessary vifs are ready. Perform the switch now.
+	 */
+
+	if (local->use_chanctx) {
+		vif_chsw = kzalloc(sizeof(*vif_chsw) * n_vifs, GFP_KERNEL);
+		if (vif_chsw) {
+			err = -ENOMEM;
+			goto err;
+		}
+
+		i = 0;
+		list_for_each_entry(ctx, &local->chanctx_list, list) {
+			if (ctx->replace_state != IEEE80211_CHANCTX_REPLACES_OTHER)
+				continue;
+
+			if (WARN_ON(!ctx->replace_ctx))
+				return -EINVAL;
+
+			list_for_each_entry(sdata, &ctx->reserved_vifs,
+					    reserved_chanctx_list) {
+				if (!ieee80211_vif_has_in_place_reservation(
+						sdata))
+					continue;
+
+				vif_chsw[i].vif = &sdata->vif;
+				vif_chsw[i].old_ctx = &ctx->replace_ctx->conf;
+				vif_chsw[i].new_ctx = &ctx->conf;
+
+				i++;
+			}
+		}
+
+		err = drv_switch_vif_chanctx(local, vif_chsw, n_vifs,
+					     CHANCTX_SWMODE_SWAP_CONTEXTS);
+		kfree(vif_chsw);
+
+		if (err)
+			goto err;
 	} else {
-		ret = ieee80211_assign_vif_chanctx(sdata, ctx);
-		if (ieee80211_chanctx_refcount(local, old_ctx) == 0)
-			ieee80211_free_chanctx(local, old_ctx);
-		if (ret) {
-			/* if assign fails refcount stays the same */
-			if (ieee80211_chanctx_refcount(local, ctx) == 0)
-				ieee80211_free_chanctx(local, ctx);
-			goto out;
+		chandef = ieee80211_chanctx_reserved_chandef(local, new_ctx,
+							     NULL);
+		if (WARN_ON(!chandef)) {
+			err = -EINVAL;
+			goto err;
 		}
 
-		if (sdata->vif.type == NL80211_IFTYPE_AP)
-			__ieee80211_vif_copy_chanctx_to_vlans(sdata, false);
+		local->hw.conf.radar_enabled = new_ctx->conf.radar_enabled;
+		local->_oper_chandef = *chandef;
+		ieee80211_hw_config(local, 0);
 	}
 
-	*changed = tmp_changed;
+	/*
+	 * Update all structures, values and pointers to point to new channel
+	 * context(s).
+	 */
 
-	ieee80211_recalc_chanctx_chantype(local, ctx);
-	ieee80211_recalc_smps_chanctx(local, ctx);
-	ieee80211_recalc_radar_chanctx(local, ctx);
-	ieee80211_recalc_chanctx_min_def(local, ctx);
-out:
-	mutex_unlock(&local->chanctx_mtx);
-	return ret;
+	i = 0;
+	list_for_each_entry(ctx, &local->chanctx_list, list) {
+		if (ctx->replace_state != IEEE80211_CHANCTX_REPLACES_OTHER)
+			continue;
+
+		if (WARN_ON(!ctx->replace_ctx))
+			return -EINVAL;
+
+		list_for_each_entry(sdata, &ctx->reserved_vifs,
+				    reserved_chanctx_list) {
+			u32 changed = 0;
+
+			if (!ieee80211_vif_has_in_place_reservation(sdata))
+				continue;
+
+			rcu_assign_pointer(sdata->vif.chanctx_conf, &ctx->conf);
+
+			if (sdata->vif.type == NL80211_IFTYPE_AP)
+				__ieee80211_vif_copy_chanctx_to_vlans(sdata,
+								      false);
+
+			sdata->radar_required = sdata->reserved_radar_required;
+
+			if (sdata->vif.bss_conf.chandef.width !=
+			    sdata->reserved_chandef.width)
+				changed = BSS_CHANGED_BANDWIDTH;
+
+			sdata->vif.bss_conf.chandef = sdata->reserved_chandef;
+			if (changed)
+				ieee80211_bss_info_change_notify(sdata,
+								 changed);
+
+			ieee80211_recalc_txpower(sdata);
+		}
+
+		ieee80211_recalc_chanctx_chantype(local, ctx);
+		ieee80211_recalc_smps_chanctx(local, ctx);
+		ieee80211_recalc_radar_chanctx(local, ctx);
+		ieee80211_recalc_chanctx_min_def(local, ctx);
+
+		list_for_each_entry_safe(sdata, sdata_tmp, &ctx->reserved_vifs,
+					 reserved_chanctx_list) {
+			if (ieee80211_vif_get_chanctx(sdata) != ctx)
+				continue;
+
+			list_del(&sdata->reserved_chanctx_list);
+			list_move(&sdata->assigned_chanctx_list,
+				  &new_ctx->assigned_vifs);
+			sdata->reserved_chanctx = NULL;
+		}
+
+		/*
+		 * This context might have been a dependency for an already
+		 * ready re-assign reservation interface that was deferred. Do
+		 * not propagate error to the caller though. The in-place
+		 * reservation for originally requested interface has already
+		 * succeeded at this point.
+		 */
+		list_for_each_entry(sdata, &ctx->reserved_vifs,
+				    reserved_chanctx_list) {
+			if (WARN_ON(ieee80211_vif_has_in_place_reservation(
+					sdata)))
+				continue;
+
+			if (WARN_ON(sdata->reserved_chanctx != ctx))
+				continue;
+
+			if (!sdata->reserved_ready)
+				continue;
+
+			if (ieee80211_vif_get_chanctx(sdata))
+				err = ieee80211_vif_use_reserved_reassign(
+						sdata);
+			else
+				err = ieee80211_vif_use_reserved_assign(sdata);
+
+			if (err) {
+				sdata_info(sdata,
+					   "failed to finalize (re-)assign reservation (err=%d)\n",
+					   err);
+				ieee80211_vif_unreserve_chanctx(sdata);
+				cfg80211_stop_iface(local->hw.wiphy,
+						    &sdata->wdev,
+						    GFP_KERNEL);
+			}
+		}
+	}
+
+	/*
+	 * Finally free old contexts
+	 */
+
+	list_for_each_entry_safe(ctx, ctx_tmp, &local->chanctx_list, list) {
+		if (ctx->replace_state != IEEE80211_CHANCTX_WILL_BE_REPLACED)
+			continue;
+
+		ctx->replace_ctx->replace_ctx = NULL;
+		ctx->replace_ctx->replace_state =
+				IEEE80211_CHANCTX_REPLACE_NONE;
+
+		list_del_rcu(&ctx->list);
+		kfree_rcu(ctx, rcu_head);
+	}
+
+	return 0;
+
+err:
+	list_for_each_entry(ctx, &local->chanctx_list, list) {
+		if (ctx->replace_state != IEEE80211_CHANCTX_REPLACES_OTHER)
+			continue;
+
+		list_for_each_entry_safe(sdata, sdata_tmp, &ctx->reserved_vifs,
+					 reserved_chanctx_list)
+			ieee80211_vif_unreserve_chanctx(sdata);
+	}
+
+	return err;
+}
+
+int ieee80211_vif_use_reserved_context(struct ieee80211_sub_if_data *sdata)
+{
+	struct ieee80211_local *local = sdata->local;
+	struct ieee80211_chanctx *new_ctx;
+	struct ieee80211_chanctx *old_ctx;
+	int err;
+
+	lockdep_assert_held(&local->mtx);
+	lockdep_assert_held(&local->chanctx_mtx);
+
+	new_ctx = sdata->reserved_chanctx;
+	old_ctx = ieee80211_vif_get_chanctx(sdata);
+
+	if (WARN_ON(!new_ctx))
+		return -EINVAL;
+
+	if (WARN_ON(new_ctx->replace_state ==
+		    IEEE80211_CHANCTX_WILL_BE_REPLACED))
+		return -EINVAL;
+
+	if (WARN_ON(sdata->reserved_ready))
+		return -EINVAL;
+
+	sdata->reserved_ready = true;
+
+	if (new_ctx->replace_state == IEEE80211_CHANCTX_REPLACE_NONE) {
+		if (old_ctx)
+			err = ieee80211_vif_use_reserved_reassign(sdata);
+		else
+			err = ieee80211_vif_use_reserved_assign(sdata);
+
+		if (err)
+			return err;
+	}
+
+	/*
+	 * In-place reservation may need to be finalized now either if:
+	 *  a) sdata is taking part in the swapping itself and is the last one
+	 *  b) sdata has switched with a re-assign reservation to an existing
+	 *     context readying in-place switching of old_ctx
+	 *
+	 * In case of (b) do not propagate the error up because the requested
+	 * sdata already switched successfully. Just spill an extra warning.
+	 * The ieee80211_vif_use_reserved_switch() already stops all necessary
+	 * interfaces upon failure.
+	 */
+	if ((old_ctx &&
+	     old_ctx->replace_state == IEEE80211_CHANCTX_WILL_BE_REPLACED) ||
+	    new_ctx->replace_state == IEEE80211_CHANCTX_REPLACES_OTHER) {
+		err = ieee80211_vif_use_reserved_switch(local);
+		if (err && err != -EAGAIN) {
+			if (new_ctx->replace_state ==
+			    IEEE80211_CHANCTX_REPLACES_OTHER)
+				return err;
+
+			wiphy_info(local->hw.wiphy,
+				   "depending in-place reservation failed (err=%d)\n",
+				   err);
+		}
+	}
+
+	return 0;
 }
 
 int ieee80211_vif_change_bandwidth(struct ieee80211_sub_if_data *sdata,
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 677e9c8..4c6dd32 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -700,6 +700,24 @@ enum ieee80211_chanctx_mode {
 	IEEE80211_CHANCTX_EXCLUSIVE
 };
 
+/**
+ * enum ieee80211_chanctx_replace_state - channel context replacement state
+ *
+ * This is used for channel context in-place reservations that require channel
+ * context switch/swap.
+ *
+ * @IEEE80211_CHANCTX_REPLACE_NONE: no replacement is taking place
+ * @IEEE80211_CHANCTX_WILL_BE_REPLACED: this channel context will be replaced
+ *	by a (not yet registered) channel context pointed by %replace_ctx.
+ * @IEEE80211_CHANCTX_REPLACES_OTHER: this (not yet registered) channel context
+ *	replaces an existing channel context pointed to by %replace_ctx.
+ */
+enum ieee80211_chanctx_replace_state {
+	IEEE80211_CHANCTX_REPLACE_NONE,
+	IEEE80211_CHANCTX_WILL_BE_REPLACED,
+	IEEE80211_CHANCTX_REPLACES_OTHER,
+};
+
 struct ieee80211_chanctx {
 	struct list_head list;
 	struct rcu_head rcu_head;
@@ -707,6 +725,9 @@ struct ieee80211_chanctx {
 	struct list_head assigned_vifs;
 	struct list_head reserved_vifs;
 
+	enum ieee80211_chanctx_replace_state replace_state;
+	struct ieee80211_chanctx *replace_ctx;
+
 	enum ieee80211_chanctx_mode mode;
 	bool driver_present;
 
@@ -777,6 +798,7 @@ struct ieee80211_sub_if_data {
 	struct ieee80211_chanctx *reserved_chanctx;
 	struct cfg80211_chan_def reserved_chandef;
 	bool reserved_radar_required;
+	bool reserved_ready;
 
 	/* used to reconfigure hardware SM PS */
 	struct work_struct recalc_smps;
@@ -1800,8 +1822,7 @@ ieee80211_vif_reserve_chanctx(struct ieee80211_sub_if_data *sdata,
 			      enum ieee80211_chanctx_mode mode,
 			      bool radar_required);
 int __must_check
-ieee80211_vif_use_reserved_context(struct ieee80211_sub_if_data *sdata,
-				   u32 *changed);
+ieee80211_vif_use_reserved_context(struct ieee80211_sub_if_data *sdata);
 int ieee80211_vif_unreserve_chanctx(struct ieee80211_sub_if_data *sdata);
 
 int __must_check
diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index 7e0dd4b..8e661ea 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -1603,7 +1603,9 @@ int ieee80211_reconfig(struct ieee80211_local *local)
 	if (local->use_chanctx) {
 		mutex_lock(&local->chanctx_mtx);
 		list_for_each_entry(ctx, &local->chanctx_list, list)
-			WARN_ON(drv_add_chanctx(local, ctx));
+			if (ctx->replace_state !=
+			    IEEE80211_CHANCTX_REPLACES_OTHER)
+				WARN_ON(drv_add_chanctx(local, ctx));
 		mutex_unlock(&local->chanctx_mtx);
 
 		list_for_each_entry(sdata, &local->interfaces, list) {
@@ -2876,6 +2878,8 @@ int ieee80211_check_combinations(struct ieee80211_sub_if_data *sdata,
 		num[iftype] = 1;
 
 	list_for_each_entry(ctx, &local->chanctx_list, list) {
+		if (ctx->replace_state == IEEE80211_CHANCTX_WILL_BE_REPLACED)
+			continue;
 		if (ctx->conf.radar_enabled)
 			radar_detect |= BIT(ctx->conf.def.width);
 		if (ctx->mode == IEEE80211_CHANCTX_EXCLUSIVE) {
@@ -2934,6 +2938,9 @@ int ieee80211_max_num_channels(struct ieee80211_local *local)
 	lockdep_assert_held(&local->chanctx_mtx);
 
 	list_for_each_entry(ctx, &local->chanctx_list, list) {
+		if (ctx->replace_state == IEEE80211_CHANCTX_WILL_BE_REPLACED)
+			continue;
+
 		num_different_channels++;
 
 		if (ctx->conf.radar_enabled)
-- 
1.8.5.3


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [PATCH v10 2/5] mac80211: make check_combinations() aware of chanctx reservation
  2014-06-17 12:58     ` [PATCH v10 0/5] cfg/mac80211: implement multi-vif csa Michal Kazior
  2014-06-17 12:58       ` [PATCH v10 1/5] mac80211: implement multi-vif in-place reservations Michal Kazior
@ 2014-06-17 12:58       ` Michal Kazior
  2014-06-17 12:58       ` [PATCH v10 3/5] mac80211: use chanctx reservation for AP CSA Michal Kazior
                         ` (4 subsequent siblings)
  6 siblings, 0 replies; 40+ messages in thread
From: Michal Kazior @ 2014-06-17 12:58 UTC (permalink / raw
  To: linux-wireless; +Cc: luca, johannes, Michal Kazior

The ieee80211_check_combinations() computes
radar_detect accordingly depending on chanctx
reservation status.

This makes it possible to use the function for
channel_switch validation.

Signed-off-by: Michal Kazior <michal.kazior@tieto.com>
---

Notes:
    v3:
     * fix typo in comment [Johannes]
     * fix comment style [Johannes]
    
    v7:
     * use replaces/replaced_by pointers
     * replace radar_detect calculation in ieee80211_max_num_channels() too
    
    v8:
     * use replace_state/replace_ctx instead of replaces/replaced_by (rebased)

 net/mac80211/util.c | 35 +++++++++++++++++++++++++++++++----
 1 file changed, 31 insertions(+), 4 deletions(-)

diff --git a/net/mac80211/util.c b/net/mac80211/util.c
index 8e661ea..16dc720 100644
--- a/net/mac80211/util.c
+++ b/net/mac80211/util.c
@@ -2837,6 +2837,35 @@ void ieee80211_recalc_dtim(struct ieee80211_local *local,
 	ps->dtim_count = dtim_count;
 }
 
+static u8 ieee80211_chanctx_radar_detect(struct ieee80211_local *local,
+					 struct ieee80211_chanctx *ctx)
+{
+	struct ieee80211_sub_if_data *sdata;
+	u8 radar_detect = 0;
+
+	lockdep_assert_held(&local->chanctx_mtx);
+
+	if (WARN_ON(ctx->replace_state == IEEE80211_CHANCTX_WILL_BE_REPLACED))
+		return 0;
+
+	list_for_each_entry(sdata, &ctx->reserved_vifs, reserved_chanctx_list)
+		if (sdata->reserved_radar_required)
+			radar_detect |= BIT(sdata->reserved_chandef.width);
+
+	/*
+	 * An in-place reservation context should not have any assigned vifs
+	 * until it replaces the other context.
+	 */
+	WARN_ON(ctx->replace_state == IEEE80211_CHANCTX_REPLACES_OTHER &&
+		!list_empty(&ctx->assigned_vifs));
+
+	list_for_each_entry(sdata, &ctx->assigned_vifs, assigned_chanctx_list)
+		if (sdata->radar_required)
+			radar_detect |= BIT(sdata->vif.bss_conf.chandef.width);
+
+	return radar_detect;
+}
+
 int ieee80211_check_combinations(struct ieee80211_sub_if_data *sdata,
 				 const struct cfg80211_chan_def *chandef,
 				 enum ieee80211_chanctx_mode chanmode,
@@ -2880,8 +2909,7 @@ int ieee80211_check_combinations(struct ieee80211_sub_if_data *sdata,
 	list_for_each_entry(ctx, &local->chanctx_list, list) {
 		if (ctx->replace_state == IEEE80211_CHANCTX_WILL_BE_REPLACED)
 			continue;
-		if (ctx->conf.radar_enabled)
-			radar_detect |= BIT(ctx->conf.def.width);
+		radar_detect |= ieee80211_chanctx_radar_detect(local, ctx);
 		if (ctx->mode == IEEE80211_CHANCTX_EXCLUSIVE) {
 			num_different_channels++;
 			continue;
@@ -2943,8 +2971,7 @@ int ieee80211_max_num_channels(struct ieee80211_local *local)
 
 		num_different_channels++;
 
-		if (ctx->conf.radar_enabled)
-			radar_detect |= BIT(ctx->conf.def.width);
+		radar_detect |= ieee80211_chanctx_radar_detect(local, ctx);
 	}
 
 	list_for_each_entry_rcu(sdata, &local->interfaces, list)
-- 
1.8.5.3


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [PATCH v10 3/5] mac80211: use chanctx reservation for AP CSA
  2014-06-17 12:58     ` [PATCH v10 0/5] cfg/mac80211: implement multi-vif csa Michal Kazior
  2014-06-17 12:58       ` [PATCH v10 1/5] mac80211: implement multi-vif in-place reservations Michal Kazior
  2014-06-17 12:58       ` [PATCH v10 2/5] mac80211: make check_combinations() aware of chanctx reservation Michal Kazior
@ 2014-06-17 12:58       ` Michal Kazior
  2014-06-17 12:58       ` [PATCH v10 4/5] mac80211: use chanctx reservation for STA CSA Michal Kazior
                         ` (3 subsequent siblings)
  6 siblings, 0 replies; 40+ messages in thread
From: Michal Kazior @ 2014-06-17 12:58 UTC (permalink / raw
  To: linux-wireless; +Cc: luca, johannes, Michal Kazior

Channel switch finalization is now 2-step. First
step is when driver calls csa_finish(), the other
is when reservation is actually finalized (which
can be deferred for in-place reservation).

It is now safe to call ieee80211_csa_finish() more
than once.

Signed-off-by: Michal Kazior <michal.kazior@tieto.com>
---

Notes:
    v3:
     * fix lockdep typo s/mtx/chanctx_mtx/ [Johannes]
     * fix comment style [Johannes]
     * use goto for cleaner unlocking/returning [Johannes]
     * squash with ieee80211_vif_change_channel() removal patch [Johannes]
     * fix commit message [Johannes]
     * add resilience for multiple ieee80211_csa_finish() calls
    
    v4:
     * split removal of ieee80211_vif_change_channel()
    
    v7:
     * tune commit message
     * put the initial ieee80211_vif_chanctx_reservation_complete()
       implementation here [Johannes]
     * remove 'default' case and use all iftypes explicitly and WARN_ON
    
    v8:
     * fix typo in commit message [Johannes]

 net/mac80211/cfg.c  | 82 +++++++++++++++++++++++++++++++++++------------------
 net/mac80211/chan.c | 35 +++++++++++++++++++++--
 2 files changed, 88 insertions(+), 29 deletions(-)

diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index af3eac4..32c3cf8 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -2805,17 +2805,35 @@ static int __ieee80211_csa_finalize(struct ieee80211_sub_if_data *sdata)
 
 	sdata_assert_lock(sdata);
 	lockdep_assert_held(&local->mtx);
+	lockdep_assert_held(&local->chanctx_mtx);
 
-	sdata->radar_required = sdata->csa_radar_required;
-	err = ieee80211_vif_change_channel(sdata, &changed);
-	if (err < 0)
-		return err;
+	/*
+	 * using reservation isn't immediate as it may be deferred until later
+	 * with multi-vif. once reservation is complete it will re-schedule the
+	 * work with no reserved_chanctx so verify chandef to check if it
+	 * completed successfully
+	 */
 
-	if (!local->use_chanctx) {
-		local->_oper_chandef = sdata->csa_chandef;
-		ieee80211_hw_config(local, 0);
+	if (sdata->reserved_chanctx) {
+		/*
+		 * with multi-vif csa driver may call ieee80211_csa_finish()
+		 * many times while waiting for other interfaces to use their
+		 * reservations
+		 */
+		if (sdata->reserved_ready)
+			return 0;
+
+		err = ieee80211_vif_use_reserved_context(sdata);
+		if (err)
+			return err;
+
+		return 0;
 	}
 
+	if (!cfg80211_chandef_identical(&sdata->vif.bss_conf.chandef,
+					&sdata->csa_chandef))
+		return -EINVAL;
+
 	sdata->vif.csa_active = false;
 
 	err = ieee80211_set_after_csa_beacon(sdata, &changed);
@@ -2851,6 +2869,7 @@ void ieee80211_csa_finalize_work(struct work_struct *work)
 
 	sdata_lock(sdata);
 	mutex_lock(&local->mtx);
+	mutex_lock(&local->chanctx_mtx);
 
 	/* AP might have been stopped while waiting for the lock. */
 	if (!sdata->vif.csa_active)
@@ -2862,6 +2881,7 @@ void ieee80211_csa_finalize_work(struct work_struct *work)
 	ieee80211_csa_finalize(sdata);
 
 unlock:
+	mutex_unlock(&local->chanctx_mtx);
 	mutex_unlock(&local->mtx);
 	sdata_unlock(sdata);
 }
@@ -3007,7 +3027,7 @@ __ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
 	struct ieee80211_local *local = sdata->local;
 	struct ieee80211_chanctx_conf *conf;
 	struct ieee80211_chanctx *chanctx;
-	int err, num_chanctx, changed = 0;
+	int err, changed = 0;
 
 	sdata_assert_lock(sdata);
 	lockdep_assert_held(&local->mtx);
@@ -3022,37 +3042,43 @@ __ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
 				       &sdata->vif.bss_conf.chandef))
 		return -EINVAL;
 
+	/* don't allow another channel switch if one is already active. */
+	if (sdata->vif.csa_active)
+		return -EBUSY;
+
 	mutex_lock(&local->chanctx_mtx);
 	conf = rcu_dereference_protected(sdata->vif.chanctx_conf,
 					 lockdep_is_held(&local->chanctx_mtx));
 	if (!conf) {
-		mutex_unlock(&local->chanctx_mtx);
-		return -EBUSY;
+		err = -EBUSY;
+		goto out;
 	}
 
-	/* don't handle for multi-VIF cases */
 	chanctx = container_of(conf, struct ieee80211_chanctx, conf);
-	if (ieee80211_chanctx_refcount(local, chanctx) > 1) {
-		mutex_unlock(&local->chanctx_mtx);
-		return -EBUSY;
+	if (!chanctx) {
+		err = -EBUSY;
+		goto out;
 	}
-	num_chanctx = 0;
-	list_for_each_entry_rcu(chanctx, &local->chanctx_list, list)
-		num_chanctx++;
-	mutex_unlock(&local->chanctx_mtx);
 
-	if (num_chanctx > 1)
-		return -EBUSY;
+	err = ieee80211_vif_reserve_chanctx(sdata, &params->chandef,
+					    chanctx->mode,
+					    params->radar_required);
+	if (err)
+		goto out;
 
-	/* don't allow another channel switch if one is already active. */
-	if (sdata->vif.csa_active)
-		return -EBUSY;
+	/* if reservation is invalid then this will fail */
+	err = ieee80211_check_combinations(sdata, NULL, chanctx->mode, 0);
+	if (err) {
+		ieee80211_vif_unreserve_chanctx(sdata);
+		goto out;
+	}
 
 	err = ieee80211_set_csa_beacon(sdata, params, &changed);
-	if (err)
-		return err;
+	if (err) {
+		ieee80211_vif_unreserve_chanctx(sdata);
+		goto out;
+	}
 
-	sdata->csa_radar_required = params->radar_required;
 	sdata->csa_chandef = params->chandef;
 	sdata->csa_block_tx = params->block_tx;
 	sdata->vif.csa_active = true;
@@ -3070,7 +3096,9 @@ __ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
 		ieee80211_csa_finalize(sdata);
 	}
 
-	return 0;
+out:
+	mutex_unlock(&local->chanctx_mtx);
+	return err;
 }
 
 int ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
index 2560fbe..292e1a3 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -1035,6 +1035,30 @@ int ieee80211_vif_reserve_chanctx(struct ieee80211_sub_if_data *sdata,
 	return 0;
 }
 
+static void
+ieee80211_vif_chanctx_reservation_complete(struct ieee80211_sub_if_data *sdata)
+{
+	switch (sdata->vif.type) {
+	case NL80211_IFTYPE_ADHOC:
+	case NL80211_IFTYPE_AP:
+	case NL80211_IFTYPE_MESH_POINT:
+		ieee80211_queue_work(&sdata->local->hw,
+				     &sdata->csa_finalize_work);
+		break;
+	case NL80211_IFTYPE_UNSPECIFIED:
+	case NL80211_IFTYPE_STATION:
+	case NL80211_IFTYPE_AP_VLAN:
+	case NL80211_IFTYPE_WDS:
+	case NL80211_IFTYPE_MONITOR:
+	case NL80211_IFTYPE_P2P_CLIENT:
+	case NL80211_IFTYPE_P2P_GO:
+	case NL80211_IFTYPE_P2P_DEVICE:
+	case NUM_NL80211_IFTYPES:
+		WARN_ON(1);
+		break;
+	}
+}
+
 static int
 ieee80211_vif_use_reserved_reassign(struct ieee80211_sub_if_data *sdata)
 {
@@ -1082,7 +1106,7 @@ ieee80211_vif_use_reserved_reassign(struct ieee80211_sub_if_data *sdata)
 		if (ieee80211_chanctx_refcount(local, new_ctx) == 0)
 			ieee80211_free_chanctx(local, new_ctx);
 
-		return err;
+		goto out;
 	}
 
 	list_move(&sdata->assigned_chanctx_list, &new_ctx->assigned_vifs);
@@ -1102,6 +1126,8 @@ ieee80211_vif_use_reserved_reassign(struct ieee80211_sub_if_data *sdata)
 	if (changed)
 		ieee80211_bss_info_change_notify(sdata, changed);
 
+out:
+	ieee80211_vif_chanctx_reservation_complete(sdata);
 	return err;
 }
 
@@ -1146,6 +1172,7 @@ ieee80211_vif_use_reserved_assign(struct ieee80211_sub_if_data *sdata)
 	}
 
 out:
+	ieee80211_vif_chanctx_reservation_complete(sdata);
 	return err;
 }
 
@@ -1366,6 +1393,8 @@ ieee80211_vif_use_reserved_switch(struct ieee80211_local *local)
 			list_move(&sdata->assigned_chanctx_list,
 				  &new_ctx->assigned_vifs);
 			sdata->reserved_chanctx = NULL;
+
+			ieee80211_vif_chanctx_reservation_complete(sdata);
 		}
 
 		/*
@@ -1429,8 +1458,10 @@ err:
 			continue;
 
 		list_for_each_entry_safe(sdata, sdata_tmp, &ctx->reserved_vifs,
-					 reserved_chanctx_list)
+					 reserved_chanctx_list) {
 			ieee80211_vif_unreserve_chanctx(sdata);
+			ieee80211_vif_chanctx_reservation_complete(sdata);
+		}
 	}
 
 	return err;
-- 
1.8.5.3


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [PATCH v10 4/5] mac80211: use chanctx reservation for STA CSA
  2014-06-17 12:58     ` [PATCH v10 0/5] cfg/mac80211: implement multi-vif csa Michal Kazior
                         ` (2 preceding siblings ...)
  2014-06-17 12:58       ` [PATCH v10 3/5] mac80211: use chanctx reservation for AP CSA Michal Kazior
@ 2014-06-17 12:58       ` Michal Kazior
  2014-06-17 12:58       ` [PATCH v10 5/5] cfg80211: remove channel_switch combination check Michal Kazior
                         ` (2 subsequent siblings)
  6 siblings, 0 replies; 40+ messages in thread
From: Michal Kazior @ 2014-06-17 12:58 UTC (permalink / raw
  To: linux-wireless; +Cc: luca, johannes, Michal Kazior

Channel switch finalization is now 2-step. First
step is when driver calls chswitch_done(), the
other is when reservation is actually finalized
(which be defered for in-place reservation).

It is now safe to call ieee80211_chswitch_done()
more than once.

Also remove the ieee80211_vif_change_channel()
because it is no longer used.

Signed-off-by: Michal Kazior <michal.kazior@tieto.com>
---

Notes:
    v3:
     * fix comment style [Johannes]
     * add resilience for multiple ieee80211_chswitch_done() calls
    
    v3:
     * fix lockdep typo s/mtx/chanctx_mtx/ [Johannes]
     * fix comment style [Johannes]
     * use goto for cleaner unlocking/returning [Johannes]
     * squash with ieee80211_vif_change_channel() removal patch [Johannes]
     * fix commit message [Johannes]
     * add resilience for multiple ieee80211_csa_finish() calls
    
    v4:
     * squash with removal of ieee80211_vif_change_channel()
    
    v7:
     * add local->mtx locking when reserving (for ieee80211_chanctx_alloc)

 net/mac80211/chan.c        | 69 ++------------------------------
 net/mac80211/ieee80211_i.h |  5 ---
 net/mac80211/mlme.c        | 99 ++++++++++++++++++++++++++++++----------------
 3 files changed, 69 insertions(+), 104 deletions(-)

diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
index 292e1a3..2f3f6a6 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -800,70 +800,6 @@ int ieee80211_vif_use_channel(struct ieee80211_sub_if_data *sdata,
 	return ret;
 }
 
-static int __ieee80211_vif_change_channel(struct ieee80211_sub_if_data *sdata,
-					  struct ieee80211_chanctx *ctx,
-					  u32 *changed)
-{
-	struct ieee80211_local *local = sdata->local;
-	const struct cfg80211_chan_def *chandef = &sdata->csa_chandef;
-	u32 chanctx_changed = 0;
-
-	if (!cfg80211_chandef_usable(sdata->local->hw.wiphy, chandef,
-				     IEEE80211_CHAN_DISABLED))
-		return -EINVAL;
-
-	if (ieee80211_chanctx_refcount(local, ctx) != 1)
-		return -EINVAL;
-
-	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);
-
-	ieee80211_recalc_chanctx_chantype(local, ctx);
-	ieee80211_recalc_smps_chanctx(local, ctx);
-	ieee80211_recalc_radar_chanctx(local, ctx);
-	ieee80211_recalc_chanctx_min_def(local, ctx);
-
-	return 0;
-}
-
-int ieee80211_vif_change_channel(struct ieee80211_sub_if_data *sdata,
-				 u32 *changed)
-{
-	struct ieee80211_local *local = sdata->local;
-	struct ieee80211_chanctx_conf *conf;
-	struct ieee80211_chanctx *ctx;
-	int ret;
-
-	lockdep_assert_held(&local->mtx);
-
-	/* should never be called if not performing a channel switch. */
-	if (WARN_ON(!sdata->vif.csa_active))
-		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);
-
-	ret = __ieee80211_vif_change_channel(sdata, ctx, changed);
- out:
-	mutex_unlock(&local->chanctx_mtx);
-	return ret;
-}
-
 static void
 __ieee80211_vif_copy_chanctx_to_vlans(struct ieee80211_sub_if_data *sdata,
 				      bool clear)
@@ -1045,8 +981,11 @@ ieee80211_vif_chanctx_reservation_complete(struct ieee80211_sub_if_data *sdata)
 		ieee80211_queue_work(&sdata->local->hw,
 				     &sdata->csa_finalize_work);
 		break;
-	case NL80211_IFTYPE_UNSPECIFIED:
 	case NL80211_IFTYPE_STATION:
+		ieee80211_queue_work(&sdata->local->hw,
+				     &sdata->u.mgd.chswitch_work);
+		break;
+	case NL80211_IFTYPE_UNSPECIFIED:
 	case NL80211_IFTYPE_AP_VLAN:
 	case NL80211_IFTYPE_WDS:
 	case NL80211_IFTYPE_MONITOR:
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 4c6dd32..341bc6c 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -787,7 +787,6 @@ struct ieee80211_sub_if_data {
 	struct mac80211_qos_map __rcu *qos_map;
 
 	struct work_struct csa_finalize_work;
-	bool csa_radar_required;
 	bool csa_block_tx; /* write-protected by sdata_lock and local->mtx */
 	struct cfg80211_chan_def csa_chandef;
 
@@ -1829,10 +1828,6 @@ 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,
-			     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,
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index 7f073ef..baea348 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -940,51 +940,69 @@ static void ieee80211_chswitch_work(struct work_struct *work)
 		container_of(work, struct ieee80211_sub_if_data, u.mgd.chswitch_work);
 	struct ieee80211_local *local = sdata->local;
 	struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
-	u32 changed = 0;
 	int ret;
 
 	if (!ieee80211_sdata_running(sdata))
 		return;
 
 	sdata_lock(sdata);
+	mutex_lock(&local->mtx);
+	mutex_lock(&local->chanctx_mtx);
+
 	if (!ifmgd->associated)
 		goto out;
 
-	mutex_lock(&local->mtx);
-	ret = ieee80211_vif_change_channel(sdata, &changed);
-	mutex_unlock(&local->mtx);
-	if (ret) {
+	if (!sdata->vif.csa_active)
+		goto out;
+
+	/*
+	 * using reservation isn't immediate as it may be deferred until later
+	 * with multi-vif. once reservation is complete it will re-schedule the
+	 * work with no reserved_chanctx so verify chandef to check if it
+	 * completed successfully
+	 */
+
+	if (sdata->reserved_chanctx) {
+		/*
+		 * with multi-vif csa driver may call ieee80211_csa_finish()
+		 * many times while waiting for other interfaces to use their
+		 * reservations
+		 */
+		if (sdata->reserved_ready)
+			goto out;
+
+		ret = ieee80211_vif_use_reserved_context(sdata);
+		if (ret) {
+			sdata_info(sdata,
+				   "failed to use reserved channel context, disconnecting (err=%d)\n",
+				   ret);
+			ieee80211_queue_work(&sdata->local->hw,
+					     &ifmgd->csa_connection_drop_work);
+			goto out;
+		}
+
+		goto out;
+	}
+
+	if (!cfg80211_chandef_identical(&sdata->vif.bss_conf.chandef,
+					&sdata->csa_chandef)) {
 		sdata_info(sdata,
-			   "vif channel switch failed, disconnecting\n");
+			   "failed to finalize channel switch, disconnecting\n");
 		ieee80211_queue_work(&sdata->local->hw,
 				     &ifmgd->csa_connection_drop_work);
 		goto out;
 	}
 
-	if (!local->use_chanctx) {
-		local->_oper_chandef = sdata->csa_chandef;
-		/* Call "hw_config" only if doing sw channel switch.
-		 * Otherwise update the channel directly
-		 */
-		if (!local->ops->channel_switch)
-			ieee80211_hw_config(local, 0);
-		else
-			local->hw.conf.chandef = local->_oper_chandef;
-	}
-
 	/* XXX: shouldn't really modify cfg80211-owned data! */
 	ifmgd->associated->channel = sdata->csa_chandef.chan;
 
-	ieee80211_bss_info_change_notify(sdata, changed);
-
-	mutex_lock(&local->mtx);
 	sdata->vif.csa_active = false;
+
 	/* XXX: wait for a beacon first? */
 	if (!ieee80211_csa_needs_block_tx(local))
 		ieee80211_wake_queues_by_reason(&local->hw,
 					IEEE80211_MAX_QUEUE_MAP,
 					IEEE80211_QUEUE_STOP_REASON_CSA);
-	mutex_unlock(&local->mtx);
 
 	ifmgd->flags &= ~IEEE80211_STA_CSA_RECEIVED;
 
@@ -992,6 +1010,8 @@ static void ieee80211_chswitch_work(struct work_struct *work)
 	ieee80211_sta_reset_conn_monitor(sdata);
 
 out:
+	mutex_unlock(&local->chanctx_mtx);
+	mutex_unlock(&local->mtx);
 	sdata_unlock(sdata);
 }
 
@@ -1028,6 +1048,7 @@ ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,
 	struct ieee80211_local *local = sdata->local;
 	struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
 	struct cfg80211_bss *cbss = ifmgd->associated;
+	struct ieee80211_chanctx_conf *conf;
 	struct ieee80211_chanctx *chanctx;
 	enum ieee80211_band current_band;
 	struct ieee80211_csa_ie csa_ie;
@@ -1071,7 +1092,22 @@ ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,
 
 	ifmgd->flags |= IEEE80211_STA_CSA_RECEIVED;
 
+	mutex_lock(&local->mtx);
 	mutex_lock(&local->chanctx_mtx);
+	conf = rcu_dereference_protected(sdata->vif.chanctx_conf,
+					 lockdep_is_held(&local->chanctx_mtx));
+	if (!conf) {
+		sdata_info(sdata,
+			   "no channel context assigned to vif?, disconnecting\n");
+		ieee80211_queue_work(&local->hw,
+				     &ifmgd->csa_connection_drop_work);
+		mutex_unlock(&local->chanctx_mtx);
+		mutex_unlock(&local->mtx);
+		return;
+	}
+
+	chanctx = container_of(conf, struct ieee80211_chanctx, conf);
+
 	if (local->use_chanctx) {
 		u32 num_chanctx = 0;
 		list_for_each_entry(chanctx, &local->chanctx_list, list)
@@ -1084,32 +1120,27 @@ ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,
 			ieee80211_queue_work(&local->hw,
 					     &ifmgd->csa_connection_drop_work);
 			mutex_unlock(&local->chanctx_mtx);
+			mutex_unlock(&local->mtx);
 			return;
 		}
 	}
 
-	if (WARN_ON(!rcu_access_pointer(sdata->vif.chanctx_conf))) {
-		ieee80211_queue_work(&local->hw,
-				     &ifmgd->csa_connection_drop_work);
-		mutex_unlock(&local->chanctx_mtx);
-		return;
-	}
-	chanctx = container_of(rcu_access_pointer(sdata->vif.chanctx_conf),
-			       struct ieee80211_chanctx, conf);
-	if (ieee80211_chanctx_refcount(local, chanctx) > 1) {
+	res = ieee80211_vif_reserve_chanctx(sdata, &csa_ie.chandef,
+					    chanctx->mode, false);
+	if (res) {
 		sdata_info(sdata,
-			   "channel switch with multiple interfaces on the same channel, disconnecting\n");
+			   "failed to reserve channel context for channel switch, disconnecting (err=%d)\n",
+			   res);
 		ieee80211_queue_work(&local->hw,
 				     &ifmgd->csa_connection_drop_work);
 		mutex_unlock(&local->chanctx_mtx);
+		mutex_unlock(&local->mtx);
 		return;
 	}
 	mutex_unlock(&local->chanctx_mtx);
 
-	sdata->csa_chandef = csa_ie.chandef;
-
-	mutex_lock(&local->mtx);
 	sdata->vif.csa_active = true;
+	sdata->csa_chandef = csa_ie.chandef;
 	sdata->csa_block_tx = csa_ie.mode;
 
 	if (sdata->csa_block_tx)
-- 
1.8.5.3


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [PATCH v10 5/5] cfg80211: remove channel_switch combination check
  2014-06-17 12:58     ` [PATCH v10 0/5] cfg/mac80211: implement multi-vif csa Michal Kazior
                         ` (3 preceding siblings ...)
  2014-06-17 12:58       ` [PATCH v10 4/5] mac80211: use chanctx reservation for STA CSA Michal Kazior
@ 2014-06-17 12:58       ` Michal Kazior
  2014-06-23 10:46       ` [PATCH v10 0/5] cfg/mac80211: implement multi-vif csa Luca Coelho
  2014-06-23 13:04       ` Johannes Berg
  6 siblings, 0 replies; 40+ messages in thread
From: Michal Kazior @ 2014-06-17 12:58 UTC (permalink / raw
  To: linux-wireless; +Cc: luca, johannes, Michal Kazior

Driver is now responsible for veryfing if the
switch is possible.

Since this is inherently tricky driver may decide
to disconnect an interface later with
cfg80211_stop_iface().

This doesn't mean driver can accept everything. It
should do it's best to verify requests and reject
them as soon as possible.

Signed-off-by: Michal Kazior <michal.kazior@tieto.com>
---

Notes:
    v7:
     * emphasize channel switching verification should not be
       taken lightly [Johannes]
     * update channel_switch description in cfg80211.h

 include/net/cfg80211.h |  7 ++++++-
 net/wireless/nl80211.c | 11 -----------
 2 files changed, 6 insertions(+), 12 deletions(-)

diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 6b02dd6..3d829d4 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -2301,7 +2301,12 @@ struct cfg80211_qos_map {
  *	reliability. This operation can not fail.
  * @set_coalesce: Set coalesce parameters.
  *
- * @channel_switch: initiate channel-switch procedure (with CSA)
+ * @channel_switch: initiate channel-switch procedure (with CSA). Driver is
+ *	responsible for veryfing if the switch is possible. Since this is
+ *	inherently tricky driver may decide to disconnect an interface later
+ *	with cfg80211_stop_iface(). This doesn't mean driver can accept
+ *	everything. It should do it's best to verify requests and reject them
+ *	as soon as possible.
  *
  * @set_qos_map: Set QoS mapping information to the driver
  *
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index ba4f172..d70f542 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -6012,17 +6012,6 @@ skip_beacons:
 		params.radar_required = true;
 	}
 
-	/* TODO: I left this here for now.  With channel switch, the
-	 * verification is a bit more complicated, because we only do
-	 * it later when the channel switch really happens.
-	 */
-	err = cfg80211_can_use_iftype_chan(rdev, wdev, wdev->iftype,
-					   params.chandef.chan,
-					   CHAN_MODE_SHARED,
-					   radar_detect_width);
-	if (err)
-		return err;
-
 	if (info->attrs[NL80211_ATTR_CH_SWITCH_BLOCK_TX])
 		params.block_tx = true;
 
-- 
1.8.5.3


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* Re: [PATCH v10 0/5] cfg/mac80211: implement multi-vif csa
  2014-06-17 12:58     ` [PATCH v10 0/5] cfg/mac80211: implement multi-vif csa Michal Kazior
                         ` (4 preceding siblings ...)
  2014-06-17 12:58       ` [PATCH v10 5/5] cfg80211: remove channel_switch combination check Michal Kazior
@ 2014-06-23 10:46       ` Luca Coelho
  2014-06-23 13:04       ` Johannes Berg
  6 siblings, 0 replies; 40+ messages in thread
From: Luca Coelho @ 2014-06-23 10:46 UTC (permalink / raw
  To: Michal Kazior; +Cc: linux-wireless, johannes

Hi,

On Tue, 2014-06-17 at 14:58 +0200, Michal Kazior wrote:
> Hi,
> 
> The patchset implements multi-interface
> reservations and multi-interface channel
> switching.
> 
> There are still some cases that aren't handled
> well (at least degradation of number of channels
> due to interface combinations that I'm aware of).
> 
> 
> v10:
>  * rebase to latest mac80211-next/master
>    (90c18a8b34add196da9b07fd37d0f3b4b451c6b3)
>  * fix compilation [Luca]
> 
> Michal Kazior (5):
>   mac80211: implement multi-vif in-place reservations
>   mac80211: make check_combinations() aware of chanctx reservation
>   mac80211: use chanctx reservation for AP CSA
>   mac80211: use chanctx reservation for STA CSA
>   cfg80211: remove channel_switch combination check

This series looks good to me.  I couldn't spot anything wrong (though I
admit I have not reviewed the latest series as deeply as some of the
previous series), so I think we could apply this now and test, test,
test.  Let's make sure things are working before we officially re-enable
channel switch in general in mac80211.

I have tried to apply these patches in our internal tree and got some
crashed, but there were so many conflicts that I'm almost sure the
troubles were because of a bad merge and not with the original patches
themselves.  I'll do a merge again once this gets applied to mac80211
and, if I still see problems, let's fix them.

In short, I think it's time to merge this to get it tested in real life
instead of just hammering it ad-infinitum with reviews.  The risk is low
anyway, because CSA is still disabled by force in mac80211.

What do others think?

--
Cheers,
Luca.


^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v10 1/5] mac80211: implement multi-vif in-place reservations
  2014-06-17 12:58       ` [PATCH v10 1/5] mac80211: implement multi-vif in-place reservations Michal Kazior
@ 2014-06-23 13:03         ` Johannes Berg
  2014-06-24 11:55           ` Michal Kazior
  0 siblings, 1 reply; 40+ messages in thread
From: Johannes Berg @ 2014-06-23 13:03 UTC (permalink / raw
  To: Michal Kazior; +Cc: linux-wireless, luca

So, I was going to apply this, but then I ran sparse & smatch :-)

On Tue, 2014-06-17 at 14:58 +0200, Michal Kazior wrote:

> -	curr_ctx = container_of(conf, struct ieee80211_chanctx, conf);
> +	curr_ctx = ieee80211_vif_get_chanctx(sdata);
> +	if (curr_ctx && local->use_chanctx && !local->ops->switch_vif_chanctx)
> +		return -ENOTSUPP;

curr_ctx can be NULL?

>  	new_ctx = ieee80211_find_reservation_chanctx(local, chandef, mode);
>  	if (!new_ctx) {
> +		if (ieee80211_can_create_new_chanctx(local)) {
>  			new_ctx = ieee80211_new_chanctx(local, chandef, mode);
> +			if (IS_ERR(new_ctx))
> +				return PTR_ERR(new_ctx);
>  		} else {
> +			if ((curr_ctx->replace_state ==
> +			     IEEE80211_CHANCTX_WILL_BE_REPLACED) ||

then this crashes?

> +	if (local->use_chanctx) {
> +		vif_chsw = kzalloc(sizeof(*vif_chsw) * n_vifs, GFP_KERNEL);
> +		if (vif_chsw) {

clearly you didn't test this? :)

> +			err = -ENOMEM;
> +			goto err;
> +		}
> +
> +		i = 0;
> +		list_for_each_entry(ctx, &local->chanctx_list, list) {
> +			if (ctx->replace_state != IEEE80211_CHANCTX_REPLACES_OTHER)
> +				continue;
> +
> +			if (WARN_ON(!ctx->replace_ctx))
> +				return -EINVAL;

This also leaks "vif_chsw".

johannes



^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v10 0/5] cfg/mac80211: implement multi-vif csa
  2014-06-17 12:58     ` [PATCH v10 0/5] cfg/mac80211: implement multi-vif csa Michal Kazior
                         ` (5 preceding siblings ...)
  2014-06-23 10:46       ` [PATCH v10 0/5] cfg/mac80211: implement multi-vif csa Luca Coelho
@ 2014-06-23 13:04       ` Johannes Berg
  6 siblings, 0 replies; 40+ messages in thread
From: Johannes Berg @ 2014-06-23 13:04 UTC (permalink / raw
  To: Michal Kazior; +Cc: linux-wireless, luca

There's also some merge conflict now, that was just in the context
though and fairly easy to resolve.

johannes


^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v10 1/5] mac80211: implement multi-vif in-place reservations
  2014-06-23 13:03         ` Johannes Berg
@ 2014-06-24 11:55           ` Michal Kazior
  2014-06-24 11:59             ` Johannes Berg
  0 siblings, 1 reply; 40+ messages in thread
From: Michal Kazior @ 2014-06-24 11:55 UTC (permalink / raw
  To: Johannes Berg; +Cc: linux-wireless, Luca Coelho

On 23 June 2014 15:03, Johannes Berg <johannes@sipsolutions.net> wrote:
> So, I was going to apply this, but then I ran sparse & smatch :-)
>
> On Tue, 2014-06-17 at 14:58 +0200, Michal Kazior wrote:
>
>> -     curr_ctx = container_of(conf, struct ieee80211_chanctx, conf);
>> +     curr_ctx = ieee80211_vif_get_chanctx(sdata);
>> +     if (curr_ctx && local->use_chanctx && !local->ops->switch_vif_chanctx)
>> +             return -ENOTSUPP;
>
> curr_ctx can be NULL?
>
>>       new_ctx = ieee80211_find_reservation_chanctx(local, chandef, mode);
>>       if (!new_ctx) {
>> +             if (ieee80211_can_create_new_chanctx(local)) {
>>                       new_ctx = ieee80211_new_chanctx(local, chandef, mode);
>> +                     if (IS_ERR(new_ctx))
>> +                             return PTR_ERR(new_ctx);
>>               } else {
>> +                     if ((curr_ctx->replace_state ==
>> +                          IEEE80211_CHANCTX_WILL_BE_REPLACED) ||
>
> then this crashes?

Right. Thanks to this I've just found another issue with unreserving
(if in-place reservation vifs are unreserved+unassigned then
(re)assign reservations need special treatment).


>> +     if (local->use_chanctx) {
>> +             vif_chsw = kzalloc(sizeof(*vif_chsw) * n_vifs, GFP_KERNEL);
>> +             if (vif_chsw) {
>
> clearly you didn't test this? :)

:(


>> +                     err = -ENOMEM;
>> +                     goto err;
>> +             }
>> +
>> +             i = 0;
>> +             list_for_each_entry(ctx, &local->chanctx_list, list) {
>> +                     if (ctx->replace_state != IEEE80211_CHANCTX_REPLACES_OTHER)
>> +                             continue;
>> +
>> +                     if (WARN_ON(!ctx->replace_ctx))
>> +                             return -EINVAL;
>
> This also leaks "vif_chsw".

This should be a `goto` too.

I'll re-spin once I fix all the problems that I'm aware of now.


Michał

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v10 1/5] mac80211: implement multi-vif in-place reservations
  2014-06-24 11:55           ` Michal Kazior
@ 2014-06-24 11:59             ` Johannes Berg
  0 siblings, 0 replies; 40+ messages in thread
From: Johannes Berg @ 2014-06-24 11:59 UTC (permalink / raw
  To: Michal Kazior; +Cc: linux-wireless, Luca Coelho

On Tue, 2014-06-24 at 13:55 +0200, Michal Kazior wrote:

> >> +                     if (WARN_ON(!ctx->replace_ctx))
> >> +                             return -EINVAL;
> >
> > This also leaks "vif_chsw".
> 
> This should be a `goto` too.

This was the first thing I'd actually looked at, and there's currently
no label that frees vif_chsw :)

> I'll re-spin once I fix all the problems that I'm aware of now.

Thanks.

johannes


^ permalink raw reply	[flat|nested] 40+ messages in thread

end of thread, other threads:[~2014-06-24 11:59 UTC | newest]

Thread overview: 40+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2014-05-29  7:34 [PATCH v7 0/5] cfg/mac80211: implement multi-vif csa Michal Kazior
2014-05-29  7:34 ` [PATCH v7 1/5] mac80211: implement multi-vif in-place reservations Michal Kazior
2014-06-02 11:33   ` Luca Coelho
2014-06-03  6:15     ` Michal Kazior
2014-06-03 17:10       ` Johannes Berg
2014-06-04 12:12         ` Michal Kazior
2014-06-04 12:38           ` Johannes Berg
2014-05-29  7:34 ` [PATCH v7 2/5] mac80211: make check_combinations() aware of chanctx reservation Michal Kazior
2014-05-29  7:34 ` [PATCH v7 3/5] mac80211: use chanctx reservation for AP CSA Michal Kazior
2014-06-03 19:49   ` Johannes Berg
2014-05-29  7:34 ` [PATCH v7 4/5] mac80211: use chanctx reservation for STA CSA Michal Kazior
2014-05-29  7:34 ` [PATCH v7 5/5] cfg80211: remove channel_switch combination check Michal Kazior
2014-06-03 19:51 ` [PATCH v7 0/5] cfg/mac80211: implement multi-vif csa Johannes Berg
2014-06-05 12:56 ` [PATCH v8 " Michal Kazior
2014-06-05 12:56   ` [PATCH v8 1/5] mac80211: implement multi-vif in-place reservations Michal Kazior
2014-06-09 16:27     ` Eliad Peller
2014-06-10  5:16       ` Michal Kazior
2014-06-05 12:56   ` [PATCH v8 2/5] mac80211: make check_combinations() aware of chanctx reservation Michal Kazior
2014-06-05 12:56   ` [PATCH v8 3/5] mac80211: use chanctx reservation for AP CSA Michal Kazior
2014-06-05 12:56   ` [PATCH v8 4/5] mac80211: use chanctx reservation for STA CSA Michal Kazior
2014-06-05 12:56   ` [PATCH v8 5/5] cfg80211: remove channel_switch combination check Michal Kazior
2014-06-12 12:54   ` [PATCH v9 0/5] cfg/mac80211: implement multi-vif csa Michal Kazior
2014-06-12 12:54     ` [PATCH v9 1/5] mac80211: implement multi-vif in-place reservations Michal Kazior
2014-06-16 14:35       ` Luca Coelho
2014-06-17  5:52         ` Michal Kazior
2014-06-12 12:54     ` [PATCH v9 2/5] mac80211: make check_combinations() aware of chanctx reservation Michal Kazior
2014-06-12 12:54     ` [PATCH v9 3/5] mac80211: use chanctx reservation for AP CSA Michal Kazior
2014-06-12 12:54     ` [PATCH v9 4/5] mac80211: use chanctx reservation for STA CSA Michal Kazior
2014-06-12 12:54     ` [PATCH v9 5/5] cfg80211: remove channel_switch combination check Michal Kazior
2014-06-17 12:58     ` [PATCH v10 0/5] cfg/mac80211: implement multi-vif csa Michal Kazior
2014-06-17 12:58       ` [PATCH v10 1/5] mac80211: implement multi-vif in-place reservations Michal Kazior
2014-06-23 13:03         ` Johannes Berg
2014-06-24 11:55           ` Michal Kazior
2014-06-24 11:59             ` Johannes Berg
2014-06-17 12:58       ` [PATCH v10 2/5] mac80211: make check_combinations() aware of chanctx reservation Michal Kazior
2014-06-17 12:58       ` [PATCH v10 3/5] mac80211: use chanctx reservation for AP CSA Michal Kazior
2014-06-17 12:58       ` [PATCH v10 4/5] mac80211: use chanctx reservation for STA CSA Michal Kazior
2014-06-17 12:58       ` [PATCH v10 5/5] cfg80211: remove channel_switch combination check Michal Kazior
2014-06-23 10:46       ` [PATCH v10 0/5] cfg/mac80211: implement multi-vif csa Luca Coelho
2014-06-23 13:04       ` Johannes Berg

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.