diff mbox

[v3,13/13] mac80211: implement multi-vif in-place reservations

Message ID 1396262371-6466-14-git-send-email-michal.kazior@tieto.com (mailing list archive)
State Not Applicable, archived
Headers show

Commit Message

Michal Kazior March 31, 2014, 10:39 a.m. UTC
Multi-vif in-place reservations happen when
it's impossible to allocate more chanctx as per
driver combinations.

Such reservations aren't finalized until last
reservation interface calls in to use the
reservation.

This introduces a special hook
ieee80211_vif_chanctx_reservation_complete(). This
is currently an empty stub and will be filled in
by AP/STA CSA code. This is required to implement
2-step CSA finalization.

This also gets rid of driver requirement to be
able to re-program channel of a chanctx.

Signed-off-by: Michal Kazior <michal.kazior@tieto.com>
---
v2:
 * use new_ctx instead of ctx [Eliad]
 * move recalcs after bss_conf is updated [Eliad]

 include/net/mac80211.h     |   7 --
 net/mac80211/chan.c        | 281 +++++++++++++++++++++++++++++++++------------
 net/mac80211/ieee80211_i.h |   4 +-
 3 files changed, 208 insertions(+), 84 deletions(-)

Comments

Eliad Peller March 31, 2014, 4:15 p.m. UTC | #1
hi Michal,

On Mon, Mar 31, 2014 at 1:39 PM, Michal Kazior <michal.kazior@tieto.com> wrote:
> Multi-vif in-place reservations happen when
> it's impossible to allocate more chanctx as per
> driver combinations.
>
> Such reservations aren't finalized until last
> reservation interface calls in to use the
> reservation.
>
> This introduces a special hook
> ieee80211_vif_chanctx_reservation_complete(). This
> is currently an empty stub and will be filled in
> by AP/STA CSA code. This is required to implement
> 2-step CSA finalization.
>
> This also gets rid of driver requirement to be
> able to re-program channel of a chanctx.
>
> Signed-off-by: Michal Kazior <michal.kazior@tieto.com>
> ---
>
[...]

> -       /* TODO: need to recheck if the chandef is usable etc.? */
> +static int
> +ieee80211_vif_use_reserved_incompat(struct ieee80211_local *local,
> +                                   struct ieee80211_chanctx *ctx,
> +                                   const struct cfg80211_chan_def *chandef)
> +{
[...]

> +       new_ctx = ieee80211_alloc_chanctx(local, chandef, ctx->mode);
> +       if (!new_ctx) {
> +               err = -ENOMEM;
> +               goto err;
> +       }
> +
> +       list_for_each_entry(sdata, &ctx->reserved_vifs, reserved_chanctx_list) {
> +               drv_unassign_vif_chanctx(local, sdata, ctx);
> +               rcu_assign_pointer(sdata->vif.chanctx_conf, &new_ctx->conf);
> +       }
> +
> +       list_del_rcu(&ctx->list);
> +       ieee80211_del_chanctx(local, ctx);
> +
> +       err = ieee80211_add_chanctx(local, new_ctx);
> +       if (err)
> +               goto err_revert;
> +
> +       /* don't simply overwrite radar_required in case of failure */
> +       list_for_each_entry(sdata, &ctx->reserved_vifs, reserved_chanctx_list) {
> +               bool tmp = sdata->radar_required;
> +               sdata->radar_required = sdata->reserved_radar_required;
> +               sdata->reserved_radar_required = tmp;
> +       }
> +
> +       list_for_each_entry(sdata, &ctx->reserved_vifs, reserved_chanctx_list) {
> +               err = drv_assign_vif_chanctx(local, sdata, new_ctx);
> +               if (err)
> +                       goto err_unassign;
> +       }
> +
better recalc radar before assigning the chanctx. otherwise, you end
up with radar_enabled configuration for non-radar channels -
ieee80211_alloc_chanctx sets radar_enabled if any
sdata->radar_required is true (which is true before the channel
switch).

[...]

> +
> +err_unassign:
> +       list_for_each_entry_continue_reverse(sdata, &ctx->reserved_vifs,
> +                                            reserved_chanctx_list)
> +               drv_unassign_vif_chanctx(local, sdata, ctx);
> +       ieee80211_del_chanctx(local, new_ctx);
> +err_revert:
> +       kfree_rcu(new_ctx, rcu_head);
> +       WARN_ON(ieee80211_add_chanctx(local, ctx));
> +       list_add_rcu(&ctx->list, &local->chanctx_list);
> +       list_for_each_entry(sdata, &ctx->reserved_vifs, reserved_chanctx_list) {
> +               sdata->radar_required = sdata->reserved_radar_required;
> +               rcu_assign_pointer(sdata->vif.chanctx_conf, &ctx->conf);
> +               WARN_ON(drv_assign_vif_chanctx(local, sdata, ctx));
> +       }
seems like the list_for_each_entry should actually be under err_unassign?

Eliad.
--
To unsubscribe from this list: send the line "unsubscribe linux-wireless" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Michal Kazior April 1, 2014, 5:10 a.m. UTC | #2
On 31 March 2014 18:15, Eliad Peller <eliad@wizery.com> wrote:
> hi Michal,
>
> On Mon, Mar 31, 2014 at 1:39 PM, Michal Kazior <michal.kazior@tieto.com> wrote:
>> Multi-vif in-place reservations happen when
>> it's impossible to allocate more chanctx as per
>> driver combinations.
>>
>> Such reservations aren't finalized until last
>> reservation interface calls in to use the
>> reservation.
>>
>> This introduces a special hook
>> ieee80211_vif_chanctx_reservation_complete(). This
>> is currently an empty stub and will be filled in
>> by AP/STA CSA code. This is required to implement
>> 2-step CSA finalization.
>>
>> This also gets rid of driver requirement to be
>> able to re-program channel of a chanctx.
>>
>> Signed-off-by: Michal Kazior <michal.kazior@tieto.com>
>> ---
>>
> [...]
>
>> -       /* TODO: need to recheck if the chandef is usable etc.? */
>> +static int
>> +ieee80211_vif_use_reserved_incompat(struct ieee80211_local *local,
>> +                                   struct ieee80211_chanctx *ctx,
>> +                                   const struct cfg80211_chan_def *chandef)
>> +{
> [...]
>
>> +       new_ctx = ieee80211_alloc_chanctx(local, chandef, ctx->mode);
>> +       if (!new_ctx) {
>> +               err = -ENOMEM;
>> +               goto err;
>> +       }
>> +
>> +       list_for_each_entry(sdata, &ctx->reserved_vifs, reserved_chanctx_list) {
>> +               drv_unassign_vif_chanctx(local, sdata, ctx);
>> +               rcu_assign_pointer(sdata->vif.chanctx_conf, &new_ctx->conf);
>> +       }
>> +
>> +       list_del_rcu(&ctx->list);
>> +       ieee80211_del_chanctx(local, ctx);
>> +
>> +       err = ieee80211_add_chanctx(local, new_ctx);
>> +       if (err)
>> +               goto err_revert;
>> +
>> +       /* don't simply overwrite radar_required in case of failure */
>> +       list_for_each_entry(sdata, &ctx->reserved_vifs, reserved_chanctx_list) {
>> +               bool tmp = sdata->radar_required;
>> +               sdata->radar_required = sdata->reserved_radar_required;
>> +               sdata->reserved_radar_required = tmp;
>> +       }
>> +
>> +       list_for_each_entry(sdata, &ctx->reserved_vifs, reserved_chanctx_list) {
>> +               err = drv_assign_vif_chanctx(local, sdata, new_ctx);
>> +               if (err)
>> +                       goto err_unassign;
>> +       }
>> +
> better recalc radar before assigning the chanctx. otherwise, you end
> up with radar_enabled configuration for non-radar channels -
> ieee80211_alloc_chanctx sets radar_enabled if any
> sdata->radar_required is true (which is true before the channel
> switch).

Good point, thanks!


>
> [...]
>
>> +
>> +err_unassign:
>> +       list_for_each_entry_continue_reverse(sdata, &ctx->reserved_vifs,
>> +                                            reserved_chanctx_list)
>> +               drv_unassign_vif_chanctx(local, sdata, ctx);
>> +       ieee80211_del_chanctx(local, new_ctx);
>> +err_revert:
>> +       kfree_rcu(new_ctx, rcu_head);
>> +       WARN_ON(ieee80211_add_chanctx(local, ctx));
>> +       list_add_rcu(&ctx->list, &local->chanctx_list);
>> +       list_for_each_entry(sdata, &ctx->reserved_vifs, reserved_chanctx_list) {
>> +               sdata->radar_required = sdata->reserved_radar_required;
>> +               rcu_assign_pointer(sdata->vif.chanctx_conf, &ctx->conf);
>> +               WARN_ON(drv_assign_vif_chanctx(local, sdata, ctx));
>> +       }
> seems like the list_for_each_entry should actually be under err_unassign?

No. This is correct. The err_unassign is used to bail out if any
drv_assign_vif_chanctx fails mid-way. We want to unassign only those
vif-chanctx we only managed to assign.


Micha?
--
To unsubscribe from this list: send the line "unsubscribe linux-wireless" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Eliad Peller April 1, 2014, 7:46 a.m. UTC | #3
On Tue, Apr 1, 2014 at 8:10 AM, Michal Kazior <michal.kazior@tieto.com> wrote:
> On 31 March 2014 18:15, Eliad Peller <eliad@wizery.com> wrote:
>> hi Michal,
>>
>> On Mon, Mar 31, 2014 at 1:39 PM, Michal Kazior <michal.kazior@tieto.com> wrote:
>>> Multi-vif in-place reservations happen when
>>> it's impossible to allocate more chanctx as per
>>> driver combinations.
>>>
>>> Such reservations aren't finalized until last
>>> reservation interface calls in to use the
>>> reservation.
>>>
>>> This introduces a special hook
>>> ieee80211_vif_chanctx_reservation_complete(). This
>>> is currently an empty stub and will be filled in
>>> by AP/STA CSA code. This is required to implement
>>> 2-step CSA finalization.
>>>
>>> This also gets rid of driver requirement to be
>>> able to re-program channel of a chanctx.
>>>
>>> Signed-off-by: Michal Kazior <michal.kazior@tieto.com>
>>> ---
>>>
>> [...]
>>
>>> -       /* TODO: need to recheck if the chandef is usable etc.? */
>>> +static int
>>> +ieee80211_vif_use_reserved_incompat(struct ieee80211_local *local,
>>> +                                   struct ieee80211_chanctx *ctx,
>>> +                                   const struct cfg80211_chan_def *chandef)
>>> +{
>> [...]
>>
>>> +       new_ctx = ieee80211_alloc_chanctx(local, chandef, ctx->mode);
>>> +       if (!new_ctx) {
>>> +               err = -ENOMEM;
>>> +               goto err;
>>> +       }
>>> +
>>> +       list_for_each_entry(sdata, &ctx->reserved_vifs, reserved_chanctx_list) {
>>> +               drv_unassign_vif_chanctx(local, sdata, ctx);
>>> +               rcu_assign_pointer(sdata->vif.chanctx_conf, &new_ctx->conf);
>>> +       }
>>> +
>>> +       list_del_rcu(&ctx->list);
>>> +       ieee80211_del_chanctx(local, ctx);
>>> +
>>> +       err = ieee80211_add_chanctx(local, new_ctx);
>>> +       if (err)
>>> +               goto err_revert;
>>> +
>>> +       /* don't simply overwrite radar_required in case of failure */
>>> +       list_for_each_entry(sdata, &ctx->reserved_vifs, reserved_chanctx_list) {
>>> +               bool tmp = sdata->radar_required;
>>> +               sdata->radar_required = sdata->reserved_radar_required;
>>> +               sdata->reserved_radar_required = tmp;
>>> +       }
>>> +
>>> +       list_for_each_entry(sdata, &ctx->reserved_vifs, reserved_chanctx_list) {
>>> +               err = drv_assign_vif_chanctx(local, sdata, new_ctx);
>>> +               if (err)
>>> +                       goto err_unassign;
>>> +       }
>>> +
>> better recalc radar before assigning the chanctx. otherwise, you end
>> up with radar_enabled configuration for non-radar channels -
>> ieee80211_alloc_chanctx sets radar_enabled if any
>> sdata->radar_required is true (which is true before the channel
>> switch).
>
> Good point, thanks!
>
>
>>
>> [...]
>>
>>> +
>>> +err_unassign:
>>> +       list_for_each_entry_continue_reverse(sdata, &ctx->reserved_vifs,
>>> +                                            reserved_chanctx_list)
>>> +               drv_unassign_vif_chanctx(local, sdata, ctx);
>>> +       ieee80211_del_chanctx(local, new_ctx);
>>> +err_revert:
>>> +       kfree_rcu(new_ctx, rcu_head);
>>> +       WARN_ON(ieee80211_add_chanctx(local, ctx));
>>> +       list_add_rcu(&ctx->list, &local->chanctx_list);
>>> +       list_for_each_entry(sdata, &ctx->reserved_vifs, reserved_chanctx_list) {
>>> +               sdata->radar_required = sdata->reserved_radar_required;
>>> +               rcu_assign_pointer(sdata->vif.chanctx_conf, &ctx->conf);
>>> +               WARN_ON(drv_assign_vif_chanctx(local, sdata, ctx));
>>> +       }
>> seems like the list_for_each_entry should actually be under err_unassign?
>
> No. This is correct. The err_unassign is used to bail out if any
> drv_assign_vif_chanctx fails mid-way. We want to unassign only those
> vif-chanctx we only managed to assign.
>
sure. i failed to explain - i'm referring only to the radar_required
swapping - it seems to happen only later.

Eliad.
--
To unsubscribe from this list: send the line "unsubscribe linux-wireless" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Michal Kazior April 1, 2014, 7:54 a.m. UTC | #4
On 1 April 2014 09:46, Eliad Peller <eliad@wizery.com> wrote:
> On Tue, Apr 1, 2014 at 8:10 AM, Michal Kazior <michal.kazior@tieto.com> wrote:
>> On 31 March 2014 18:15, Eliad Peller <eliad@wizery.com> wrote:
>>> On Mon, Mar 31, 2014 at 1:39 PM, Michal Kazior <michal.kazior@tieto.com> wrote:

[...]

>>>> +err_unassign:
>>>> +       list_for_each_entry_continue_reverse(sdata, &ctx->reserved_vifs,
>>>> +                                            reserved_chanctx_list)
>>>> +               drv_unassign_vif_chanctx(local, sdata, ctx);
>>>> +       ieee80211_del_chanctx(local, new_ctx);
>>>> +err_revert:
>>>> +       kfree_rcu(new_ctx, rcu_head);
>>>> +       WARN_ON(ieee80211_add_chanctx(local, ctx));
>>>> +       list_add_rcu(&ctx->list, &local->chanctx_list);
>>>> +       list_for_each_entry(sdata, &ctx->reserved_vifs, reserved_chanctx_list) {
>>>> +               sdata->radar_required = sdata->reserved_radar_required;
>>>> +               rcu_assign_pointer(sdata->vif.chanctx_conf, &ctx->conf);
>>>> +               WARN_ON(drv_assign_vif_chanctx(local, sdata, ctx));
>>>> +       }
>>> seems like the list_for_each_entry should actually be under err_unassign?
>>
>> No. This is correct. The err_unassign is used to bail out if any
>> drv_assign_vif_chanctx fails mid-way. We want to unassign only those
>> vif-chanctx we only managed to assign.
>>
> sure. i failed to explain - i'm referring only to the radar_required
> swapping - it seems to happen only later.

You mean to split the list_for_each_entry from err_revert into 2
parts: one that reverts the radar_required swapping and the other one
re-assigning vif-chanctx? I'm not really sure if that's necessary? The
`ctx` (old chanctx) should still be configured as it was wrt radar
detection. Or is there something that I'm missing?


Micha?
--
To unsubscribe from this list: send the line "unsubscribe linux-wireless" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Eliad Peller April 1, 2014, 8:10 a.m. UTC | #5
On Tue, Apr 1, 2014 at 10:54 AM, Michal Kazior <michal.kazior@tieto.com> wrote:
> On 1 April 2014 09:46, Eliad Peller <eliad@wizery.com> wrote:
>> On Tue, Apr 1, 2014 at 8:10 AM, Michal Kazior <michal.kazior@tieto.com> wrote:
>>> On 31 March 2014 18:15, Eliad Peller <eliad@wizery.com> wrote:
>>>> On Mon, Mar 31, 2014 at 1:39 PM, Michal Kazior <michal.kazior@tieto.com> wrote:
>
> [...]
>
>>>>> +err_unassign:
>>>>> +       list_for_each_entry_continue_reverse(sdata, &ctx->reserved_vifs,
>>>>> +                                            reserved_chanctx_list)
>>>>> +               drv_unassign_vif_chanctx(local, sdata, ctx);
>>>>> +       ieee80211_del_chanctx(local, new_ctx);
>>>>> +err_revert:
>>>>> +       kfree_rcu(new_ctx, rcu_head);
>>>>> +       WARN_ON(ieee80211_add_chanctx(local, ctx));
>>>>> +       list_add_rcu(&ctx->list, &local->chanctx_list);
>>>>> +       list_for_each_entry(sdata, &ctx->reserved_vifs, reserved_chanctx_list) {
>>>>> +               sdata->radar_required = sdata->reserved_radar_required;
>>>>> +               rcu_assign_pointer(sdata->vif.chanctx_conf, &ctx->conf);
>>>>> +               WARN_ON(drv_assign_vif_chanctx(local, sdata, ctx));
>>>>> +       }
>>>> seems like the list_for_each_entry should actually be under err_unassign?
>>>
>>> No. This is correct. The err_unassign is used to bail out if any
>>> drv_assign_vif_chanctx fails mid-way. We want to unassign only those
>>> vif-chanctx we only managed to assign.
>>>
>> sure. i failed to explain - i'm referring only to the radar_required
>> swapping - it seems to happen only later.
>
> You mean to split the list_for_each_entry from err_revert into 2
> parts: one that reverts the radar_required swapping and the other one
> re-assigning vif-chanctx? I'm not really sure if that's necessary? The
> `ctx` (old chanctx) should still be configured as it was wrt radar
> detection. Or is there something that I'm missing?
>
ctx is correct, but sdata->radar_required doesn't seem to derive its
value from it.
i mean that you should either split the list_for_each_entry as you
suggested, or move the radar_enabled swapping loop before the
ieee80211_add_chanctx() call.

Eliad.
--
To unsubscribe from this list: send the line "unsubscribe linux-wireless" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Michal Kazior April 1, 2014, 8:26 a.m. UTC | #6
On 1 April 2014 10:10, Eliad Peller <eliad@wizery.com> wrote:
> On Tue, Apr 1, 2014 at 10:54 AM, Michal Kazior <michal.kazior@tieto.com> wrote:
>> On 1 April 2014 09:46, Eliad Peller <eliad@wizery.com> wrote:
>>> On Tue, Apr 1, 2014 at 8:10 AM, Michal Kazior <michal.kazior@tieto.com> wrote:
>>>> On 31 March 2014 18:15, Eliad Peller <eliad@wizery.com> wrote:
>>>>> On Mon, Mar 31, 2014 at 1:39 PM, Michal Kazior <michal.kazior@tieto.com> wrote:
>>
>> [...]
>>
>>>>>> +err_unassign:
>>>>>> +       list_for_each_entry_continue_reverse(sdata, &ctx->reserved_vifs,
>>>>>> +                                            reserved_chanctx_list)
>>>>>> +               drv_unassign_vif_chanctx(local, sdata, ctx);
>>>>>> +       ieee80211_del_chanctx(local, new_ctx);
>>>>>> +err_revert:
>>>>>> +       kfree_rcu(new_ctx, rcu_head);
>>>>>> +       WARN_ON(ieee80211_add_chanctx(local, ctx));
>>>>>> +       list_add_rcu(&ctx->list, &local->chanctx_list);
>>>>>> +       list_for_each_entry(sdata, &ctx->reserved_vifs, reserved_chanctx_list) {
>>>>>> +               sdata->radar_required = sdata->reserved_radar_required;
>>>>>> +               rcu_assign_pointer(sdata->vif.chanctx_conf, &ctx->conf);
>>>>>> +               WARN_ON(drv_assign_vif_chanctx(local, sdata, ctx));
>>>>>> +       }
>>>>> seems like the list_for_each_entry should actually be under err_unassign?
>>>>
>>>> No. This is correct. The err_unassign is used to bail out if any
>>>> drv_assign_vif_chanctx fails mid-way. We want to unassign only those
>>>> vif-chanctx we only managed to assign.
>>>>
>>> sure. i failed to explain - i'm referring only to the radar_required
>>> swapping - it seems to happen only later.
>>
>> You mean to split the list_for_each_entry from err_revert into 2
>> parts: one that reverts the radar_required swapping and the other one
>> re-assigning vif-chanctx? I'm not really sure if that's necessary? The
>> `ctx` (old chanctx) should still be configured as it was wrt radar
>> detection. Or is there something that I'm missing?
>>
> ctx is correct, but sdata->radar_required doesn't seem to derive its
> value from it.
> i mean that you should either split the list_for_each_entry as you
> suggested, or move the radar_enabled swapping loop before the
> ieee80211_add_chanctx() call.

Yeah. I'll do that. It seems like a good idea regardless if this makes
no difference with current codebase.


Micha?
--
To unsubscribe from this list: send the line "unsubscribe linux-wireless" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index ea69961..7e3fe90 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -1557,12 +1557,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,
@@ -1594,7 +1588,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 9d4c04d..6870c3c 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -173,6 +173,24 @@  ieee80211_find_reservation_chanctx(struct ieee80211_local *local,
 	return NULL;
 }
 
+static bool
+ieee80211_chanctx_all_reserved_vifs_ready(struct ieee80211_local *local,
+					  struct ieee80211_chanctx *ctx)
+{
+	struct ieee80211_sub_if_data *sdata;
+
+	lockdep_assert_held(&local->chanctx_mtx);
+
+	list_for_each_entry(sdata, &ctx->reserved_vifs, reserved_chanctx_list) {
+		if (!sdata->reserved_chanctx)
+			continue;
+		if (!sdata->reserved_ready)
+			return false;
+	}
+
+	return true;
+}
+
 static enum nl80211_chan_width ieee80211_get_sta_bw(struct ieee80211_sta *sta)
 {
 	switch (sta->bandwidth) {
@@ -913,38 +931,24 @@  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;
 
-	mutex_lock(&local->chanctx_mtx);
+	lockdep_assert_held(&local->chanctx_mtx);
 
 	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;
+			new_ctx = curr_ctx;
 		}
 	}
 
@@ -952,82 +956,209 @@  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 void
+ieee80211_vif_chanctx_reservation_complete(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;
+	/* stub */
+}
 
-	/* TODO: need to recheck if the chandef is usable etc.? */
+static int
+ieee80211_vif_use_reserved_incompat(struct ieee80211_local *local,
+				    struct ieee80211_chanctx *ctx,
+				    const struct cfg80211_chan_def *chandef)
+{
+	struct ieee80211_sub_if_data *sdata, *tmp;
+	struct ieee80211_chanctx *new_ctx;
+	u32 changed = 0;
+	int err;
 
 	lockdep_assert_held(&local->mtx);
+	lockdep_assert_held(&local->chanctx_mtx);
 
-	mutex_lock(&local->chanctx_mtx);
+	if (!ieee80211_chanctx_all_reserved_vifs_ready(local, ctx))
+		return 0;
 
-	ctx = sdata->reserved_chanctx;
-	if (WARN_ON(!ctx)) {
-		ret = -EINVAL;
-		goto out;
+	if (ieee80211_chanctx_num_assigned(local, ctx) !=
+	    ieee80211_chanctx_num_reserved(local, ctx)) {
+		wiphy_info(local->hw.wiphy,
+			   "channel context reservation cannot be finalized because some interfaces aren't switching\n");
+		err = -EBUSY;
+		goto err;
 	}
 
-	conf = rcu_dereference_protected(sdata->vif.chanctx_conf,
-					 lockdep_is_held(&local->chanctx_mtx));
-	if (!conf) {
-		ret = -EINVAL;
+	new_ctx = ieee80211_alloc_chanctx(local, chandef, ctx->mode);
+	if (!new_ctx) {
+		err = -ENOMEM;
+		goto err;
+	}
+
+	list_for_each_entry(sdata, &ctx->reserved_vifs, reserved_chanctx_list) {
+		drv_unassign_vif_chanctx(local, sdata, ctx);
+		rcu_assign_pointer(sdata->vif.chanctx_conf, &new_ctx->conf);
+	}
+
+	list_del_rcu(&ctx->list);
+	ieee80211_del_chanctx(local, ctx);
+
+	err = ieee80211_add_chanctx(local, new_ctx);
+	if (err)
+		goto err_revert;
+
+	/* don't simply overwrite radar_required in case of failure */
+	list_for_each_entry(sdata, &ctx->reserved_vifs, reserved_chanctx_list) {
+		bool tmp = sdata->radar_required;
+		sdata->radar_required = sdata->reserved_radar_required;
+		sdata->reserved_radar_required = tmp;
+	}
+
+	list_for_each_entry(sdata, &ctx->reserved_vifs, reserved_chanctx_list) {
+		err = drv_assign_vif_chanctx(local, sdata, new_ctx);
+		if (err)
+			goto err_unassign;
+	}
+
+	if (sdata->vif.type == NL80211_IFTYPE_AP)
+		__ieee80211_vif_copy_chanctx_to_vlans(sdata, false);
+
+	list_add_rcu(&new_ctx->list, &local->chanctx_list);
+	kfree_rcu(ctx, rcu_head);
+
+	list_for_each_entry(sdata, &ctx->reserved_vifs,
+			    reserved_chanctx_list) {
+		changed = 0;
+		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, new_ctx);
+	ieee80211_recalc_smps_chanctx(local, new_ctx);
+	ieee80211_recalc_radar_chanctx(local, new_ctx);
+	ieee80211_recalc_chanctx_min_def(local, new_ctx);
+
+	list_for_each_entry_safe(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;
+
+		ieee80211_vif_chanctx_reservation_complete(sdata);
+	}
+
+	return 0;
+
+err_unassign:
+	list_for_each_entry_continue_reverse(sdata, &ctx->reserved_vifs,
+					     reserved_chanctx_list)
+		drv_unassign_vif_chanctx(local, sdata, ctx);
+	ieee80211_del_chanctx(local, new_ctx);
+err_revert:
+	kfree_rcu(new_ctx, rcu_head);
+	WARN_ON(ieee80211_add_chanctx(local, ctx));
+	list_add_rcu(&ctx->list, &local->chanctx_list);
+	list_for_each_entry(sdata, &ctx->reserved_vifs, reserved_chanctx_list) {
+		sdata->radar_required = sdata->reserved_radar_required;
+		rcu_assign_pointer(sdata->vif.chanctx_conf, &ctx->conf);
+		WARN_ON(drv_assign_vif_chanctx(local, sdata, ctx));
+	}
+err:
+	list_for_each_entry_safe(sdata, tmp, &ctx->reserved_vifs,
+				 reserved_chanctx_list) {
+		list_del(&sdata->reserved_chanctx_list);
+		sdata->reserved_chanctx = NULL;
+		ieee80211_vif_chanctx_reservation_complete(sdata);
+	}
+	return err;
+}
+
+static int
+ieee80211_vif_use_reserved_compat(struct ieee80211_sub_if_data *sdata,
+				  struct ieee80211_chanctx *old_ctx,
+				  struct ieee80211_chanctx *new_ctx)
+{
+	struct ieee80211_local *local = sdata->local;
+	u32 changed = 0;
+	int err;
+
+	lockdep_assert_held(&local->mtx);
+	lockdep_assert_held(&local->chanctx_mtx);
+
+	list_del(&sdata->reserved_chanctx_list);
+	sdata->reserved_chanctx = NULL;
+
+	err = ieee80211_assign_vif_chanctx(sdata, new_ctx);
+	if (ieee80211_chanctx_refcount(local, old_ctx) == 0)
+		ieee80211_free_chanctx(local, old_ctx);
+	if (err) {
+		/* if assign fails refcount stays the same */
+		if (ieee80211_chanctx_refcount(local, new_ctx) == 0)
+			ieee80211_free_chanctx(local, new_ctx);
 		goto out;
 	}
 
-	old_ctx = container_of(conf, struct ieee80211_chanctx, conf);
+	if (sdata->vif.type == NL80211_IFTYPE_AP)
+		__ieee80211_vif_copy_chanctx_to_vlans(sdata, false);
 
 	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;
-	} 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;
-		}
+out:
+	ieee80211_vif_chanctx_reservation_complete(sdata);
+	return err;
+}
 
-		if (sdata->vif.type == NL80211_IFTYPE_AP)
-			__ieee80211_vif_copy_chanctx_to_vlans(sdata, false);
-	}
+int ieee80211_vif_use_reserved_context(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;
+	const struct cfg80211_chan_def *chandef;
 
-	*changed = tmp_changed;
+	lockdep_assert_held(&local->mtx);
+	lockdep_assert_held(&local->chanctx_mtx);
 
-	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;
+	ctx = sdata->reserved_chanctx;
+	if (WARN_ON(!ctx))
+		return -EINVAL;
+
+	conf = rcu_dereference_protected(sdata->vif.chanctx_conf,
+					 lockdep_is_held(&local->chanctx_mtx));
+	if (!conf)
+		return -EINVAL;
+
+	old_ctx = container_of(conf, struct ieee80211_chanctx, conf);
+
+	if (WARN_ON(sdata->reserved_ready))
+		return -EINVAL;
+
+	chandef = ieee80211_chanctx_reserved_chandef(local, ctx, NULL);
+	if (WARN_ON(!chandef))
+		return -EINVAL;
+
+	sdata->reserved_ready = true;
+
+	if (cfg80211_chandef_compatible(&ctx->conf.def, chandef))
+		return ieee80211_vif_use_reserved_compat(sdata, old_ctx, ctx);
+	else
+		return ieee80211_vif_use_reserved_incompat(local, ctx, chandef);
 }
 
 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 3dffdb2..a941260 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -766,6 +766,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;
@@ -1794,8 +1795,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