diff mbox

[v5,3/3] mac80211: allow reservation of a running chanctx

Message ID 1394017904-4012-4-git-send-email-luca@coelho.fi (mailing list archive)
State Not Applicable, archived
Headers show

Commit Message

Luca Coelho March 5, 2014, 11:11 a.m. UTC
From: Luciano Coelho <luciano.coelho@intel.com>

With single-channel drivers, we need to be able to change a running
chanctx if we want to use chanctx reservation.  Not all drivers may be
able to do this, so add a flag that indicates support for it.

Changing a running chanctx can also be used as an optimization in
multi-channel drivers when the context needs to be reserved for future
usage.

Introduce IEEE80211_CHANCTX_RESERVED chanctx mode to mark a channel as
reserved so nobody else can use it (since we know it's going to
change).  In the future, we may allow several vifs to use the same
reservation as long as they plan to use the chanctx on the same
future channel.

Signed-off-by: Luciano Coelho <luciano.coelho@intel.com>
---
In v3:

   * reworded the TODO, slightly;

In v4:

   * remove IEEE80211_CHANCTX_RESERVED and the reserved_mode element;
   * increase refcount also for "in-place" changes;
   * stop queues also before doing an "in-place" change;
   * refactor ieee80211_use_reserved_chanctx() a bit to fit "in-place"
     better;

In v5:

   * fix checkpatch warnings;
---
 include/net/mac80211.h |  7 ++++
 net/mac80211/chan.c    | 97 +++++++++++++++++++++++++++++++++++---------------
 2 files changed, 75 insertions(+), 29 deletions(-)

Comments

Michal Kazior March 5, 2014, 11:32 a.m. UTC | #1
On 5 March 2014 12:11, Luca Coelho <luca@coelho.fi> wrote:
> From: Luciano Coelho <luciano.coelho@intel.com>
>
> With single-channel drivers, we need to be able to change a running
> chanctx if we want to use chanctx reservation.  Not all drivers may be
> able to do this, so add a flag that indicates support for it.
>
> Changing a running chanctx can also be used as an optimization in
> multi-channel drivers when the context needs to be reserved for future
> usage.
>
> Introduce IEEE80211_CHANCTX_RESERVED chanctx mode to mark a channel as
> reserved so nobody else can use it (since we know it's going to
> change).  In the future, we may allow several vifs to use the same
> reservation as long as they plan to use the chanctx on the same
> future channel.
>
> Signed-off-by: Luciano Coelho <luciano.coelho@intel.com>
> ---
> In v3:
>
>    * reworded the TODO, slightly;
>
> In v4:
>
>    * remove IEEE80211_CHANCTX_RESERVED and the reserved_mode element;
>    * increase refcount also for "in-place" changes;
>    * stop queues also before doing an "in-place" change;
>    * refactor ieee80211_use_reserved_chanctx() a bit to fit "in-place"
>      better;
>
> In v5:
>
>    * fix checkpatch warnings;
> ---
>  include/net/mac80211.h |  7 ++++
>  net/mac80211/chan.c    | 97 +++++++++++++++++++++++++++++++++++---------------
>  2 files changed, 75 insertions(+), 29 deletions(-)
>
> diff --git a/include/net/mac80211.h b/include/net/mac80211.h
> index 86faa41..b35c608 100644
> --- a/include/net/mac80211.h
> +++ b/include/net/mac80211.h
> @@ -1553,6 +1553,12 @@ 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,
> @@ -1584,6 +1590,7 @@ 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 9dfdba5..d634f41 100644
> --- a/net/mac80211/chan.c
> +++ b/net/mac80211/chan.c
> @@ -162,6 +162,26 @@ static void ieee80211_change_chanctx(struct ieee80211_local *local,
>         }
>  }
>
> +static bool ieee80211_chanctx_is_reserved(struct ieee80211_local *local,
> +                                    struct ieee80211_chanctx *ctx)
> +{
> +       struct ieee80211_sub_if_data *sdata;
> +       bool ret = false;
> +
> +       lockdep_assert_held(&local->chanctx_mtx);
> +       rcu_read_lock();
> +       list_for_each_entry_rcu(sdata, &local->interfaces, list) {
> +               if (sdata->reserved_chanctx == ctx) {
> +                       ret = true;
> +                       goto out;
> +               }
> +       }
> +
> +out:
> +       rcu_read_unlock();
> +       return false;

`return ret` ;-)

It's probably a good idea to check ieee80211_sdata_running() before
even considering reserved_chanctx?


> +}
> +
>  static struct ieee80211_chanctx *
>  ieee80211_find_chanctx(struct ieee80211_local *local,
>                        const struct cfg80211_chan_def *chandef,
> @@ -177,7 +197,11 @@ ieee80211_find_chanctx(struct ieee80211_local *local,
>         list_for_each_entry(ctx, &local->chanctx_list, list) {
>                 const struct cfg80211_chan_def *compat;
>
> -               if (ctx->mode == IEEE80211_CHANCTX_EXCLUSIVE)
> +               /* We don't support chanctx reservation for multiple
> +                * vifs yet, so don't allow reserved chanctxs to be
> +                * reused.
> +                */
> +               if (ieee80211_chanctx_is_reserved(local, ctx))
>                         continue;

Are you really sure you want to drop the ctx->mode == EXCLUSIVE check here?


> -       /* reserve the new or existing context */
>         sdata->reserved_chanctx = new_ctx;
>         new_ctx->refcount++;
> -
>         sdata->reserved_chandef = *chandef;

Shouldn't this be in the [2/3]?


>  out:
>         mutex_unlock(&local->chanctx_mtx);
> @@ -703,37 +734,45 @@ int ieee80211_vif_use_reserved_context(struct ieee80211_sub_if_data *sdata,
>         ieee80211_stop_queues_by_reason(&local->hw,
>                                         IEEE80211_MAX_QUEUE_MAP,
>                                         IEEE80211_QUEUE_STOP_REASON_CHANCTX);
> +       /* unref our reservation */
> +       ctx->refcount--;
> +       sdata->reserved_chanctx = NULL;
>
> -       ieee80211_unassign_vif_chanctx(sdata, old_ctx);
> -       if (old_ctx->refcount == 0)
> -               ieee80211_free_chanctx(local, old_ctx);
> +       if (old_ctx == ctx) {
> +               /* This is our own context, just change it */
> +               ret = __ieee80211_vif_change_channel(sdata, old_ctx,
> +                                                    &local_changed);
> +               if (ret)
> +                       goto out;
> +       } else {
> +               ieee80211_unassign_vif_chanctx(sdata, old_ctx);
> +               if (old_ctx->refcount == 0)
> +                       ieee80211_free_chanctx(local, old_ctx);
>
> -       if (sdata->vif.bss_conf.chandef.width != sdata->reserved_chandef.width)
> -               local_changed |= BSS_CHANGED_BANDWIDTH;
> +               if (sdata->vif.bss_conf.chandef.width !=
> +                   sdata->reserved_chandef.width)
> +                       local_changed |= BSS_CHANGED_BANDWIDTH;
>
> -       sdata->vif.bss_conf.chandef = sdata->reserved_chandef;
> +               sdata->vif.bss_conf.chandef = sdata->reserved_chandef;
>
> -       /* unref our reservation before assigning */
> -       ctx->refcount--;
> -       sdata->reserved_chanctx = NULL;
> -       ret = ieee80211_assign_vif_chanctx(sdata, ctx);
> -       if (ret) {
> -               /* if assign fails refcount stays the same */
> -               if (ctx->refcount == 0)
> -                       ieee80211_free_chanctx(local, ctx);
> -               goto out_wake;
> +               ret = ieee80211_assign_vif_chanctx(sdata, ctx);
> +               if (ret) {
> +                       /* if assign fails refcount stays the same */
> +                       if (ctx->refcount == 0)
> +                               ieee80211_free_chanctx(local, ctx);
> +                       goto out;
> +               }
> +
> +               ieee80211_recalc_chanctx_chantype(local, ctx);
> +               ieee80211_recalc_smps_chanctx(local, ctx);
> +               ieee80211_recalc_radar_chanctx(local, ctx);
>         }

Not really sure if you need to `else` and re-indent the whole thing
because you already do a `goto` in the `if`..


>         *changed = local_changed;
> -
> -       ieee80211_recalc_chanctx_chantype(local, ctx);
> -       ieee80211_recalc_smps_chanctx(local, ctx);
> -       ieee80211_recalc_radar_chanctx(local, ctx);
> -out_wake:
> +out:
>         ieee80211_wake_queues_by_reason(&sdata->local->hw,
>                                         IEEE80211_MAX_QUEUE_MAP,
>                                         IEEE80211_QUEUE_STOP_REASON_CHANCTX);
> -out:
>         mutex_unlock(&local->chanctx_mtx);
>         return ret;
>  }

Are you sure you want to remove the `out_wake` from here? Why not in the [2/3]?


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
Luca Coelho March 7, 2014, 6:43 a.m. UTC | #2
On Wed, 2014-03-05 at 12:32 +0100, Michal Kazior wrote:
> On 5 March 2014 12:11, Luca Coelho <luca@coelho.fi> wrote:
> > From: Luciano Coelho <luciano.coelho@intel.com>
> >
> > With single-channel drivers, we need to be able to change a running
> > chanctx if we want to use chanctx reservation.  Not all drivers may be
> > able to do this, so add a flag that indicates support for it.
> >
> > Changing a running chanctx can also be used as an optimization in
> > multi-channel drivers when the context needs to be reserved for future
> > usage.
> >
> > Introduce IEEE80211_CHANCTX_RESERVED chanctx mode to mark a channel as
> > reserved so nobody else can use it (since we know it's going to
> > change).  In the future, we may allow several vifs to use the same
> > reservation as long as they plan to use the chanctx on the same
> > future channel.
> >
> > Signed-off-by: Luciano Coelho <luciano.coelho@intel.com>
> > ---
> > In v3:
> >
> >    * reworded the TODO, slightly;
> >
> > In v4:
> >
> >    * remove IEEE80211_CHANCTX_RESERVED and the reserved_mode element;
> >    * increase refcount also for "in-place" changes;
> >    * stop queues also before doing an "in-place" change;
> >    * refactor ieee80211_use_reserved_chanctx() a bit to fit "in-place"
> >      better;
> >
> > In v5:
> >
> >    * fix checkpatch warnings;
> > ---
> >  include/net/mac80211.h |  7 ++++
> >  net/mac80211/chan.c    | 97 +++++++++++++++++++++++++++++++++++---------------
> >  2 files changed, 75 insertions(+), 29 deletions(-)
> >
> > diff --git a/include/net/mac80211.h b/include/net/mac80211.h
> > index 86faa41..b35c608 100644
> > --- a/include/net/mac80211.h
> > +++ b/include/net/mac80211.h
> > @@ -1553,6 +1553,12 @@ 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,
> > @@ -1584,6 +1590,7 @@ 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 9dfdba5..d634f41 100644
> > --- a/net/mac80211/chan.c
> > +++ b/net/mac80211/chan.c
> > @@ -162,6 +162,26 @@ static void ieee80211_change_chanctx(struct ieee80211_local *local,
> >         }
> >  }
> >
> > +static bool ieee80211_chanctx_is_reserved(struct ieee80211_local *local,
> > +                                    struct ieee80211_chanctx *ctx)
> > +{
> > +       struct ieee80211_sub_if_data *sdata;
> > +       bool ret = false;
> > +
> > +       lockdep_assert_held(&local->chanctx_mtx);
> > +       rcu_read_lock();
> > +       list_for_each_entry_rcu(sdata, &local->interfaces, list) {
> > +               if (sdata->reserved_chanctx == ctx) {
> > +                       ret = true;
> > +                       goto out;
> > +               }
> > +       }
> > +
> > +out:
> > +       rcu_read_unlock();
> > +       return false;
> 
> `return ret` ;-)

Gack! I'll fix.


> It's probably a good idea to check ieee80211_sdata_running() before
> even considering reserved_chanctx?

Yes, good point.  reserved->chanctx should not be set if the sdata is
not running, but for consistency, if nothing else, I'll add the check
here.


> 
> > +}
> > +
> >  static struct ieee80211_chanctx *
> >  ieee80211_find_chanctx(struct ieee80211_local *local,
> >                        const struct cfg80211_chan_def *chandef,
> > @@ -177,7 +197,11 @@ ieee80211_find_chanctx(struct ieee80211_local *local,
> >         list_for_each_entry(ctx, &local->chanctx_list, list) {
> >                 const struct cfg80211_chan_def *compat;
> >
> > -               if (ctx->mode == IEEE80211_CHANCTX_EXCLUSIVE)
> > +               /* We don't support chanctx reservation for multiple
> > +                * vifs yet, so don't allow reserved chanctxs to be
> > +                * reused.
> > +                */
> > +               if (ieee80211_chanctx_is_reserved(local, ctx))
> >                         continue;
> 
> Are you really sure you want to drop the ctx->mode == EXCLUSIVE check here?

Nope, I don't.  I should teach myself to be more careful when I'm trying
to multitask. :\


> 
> > -       /* reserve the new or existing context */
> >         sdata->reserved_chanctx = new_ctx;
> >         new_ctx->refcount++;
> > -
> >         sdata->reserved_chandef = *chandef;
> 
> Shouldn't this be in the [2/3]?

Yeah.


> 
> >  out:
> >         mutex_unlock(&local->chanctx_mtx);
> > @@ -703,37 +734,45 @@ int ieee80211_vif_use_reserved_context(struct ieee80211_sub_if_data *sdata,
> >         ieee80211_stop_queues_by_reason(&local->hw,
> >                                         IEEE80211_MAX_QUEUE_MAP,
> >                                         IEEE80211_QUEUE_STOP_REASON_CHANCTX);
> > +       /* unref our reservation */
> > +       ctx->refcount--;
> > +       sdata->reserved_chanctx = NULL;
> >
> > -       ieee80211_unassign_vif_chanctx(sdata, old_ctx);
> > -       if (old_ctx->refcount == 0)
> > -               ieee80211_free_chanctx(local, old_ctx);
> > +       if (old_ctx == ctx) {
> > +               /* This is our own context, just change it */
> > +               ret = __ieee80211_vif_change_channel(sdata, old_ctx,
> > +                                                    &local_changed);
> > +               if (ret)
> > +                       goto out;
> > +       } else {
> > +               ieee80211_unassign_vif_chanctx(sdata, old_ctx);
> > +               if (old_ctx->refcount == 0)
> > +                       ieee80211_free_chanctx(local, old_ctx);
> >
> > -       if (sdata->vif.bss_conf.chandef.width != sdata->reserved_chandef.width)
> > -               local_changed |= BSS_CHANGED_BANDWIDTH;
> > +               if (sdata->vif.bss_conf.chandef.width !=
> > +                   sdata->reserved_chandef.width)
> > +                       local_changed |= BSS_CHANGED_BANDWIDTH;
> >
> > -       sdata->vif.bss_conf.chandef = sdata->reserved_chandef;
> > +               sdata->vif.bss_conf.chandef = sdata->reserved_chandef;
> >
> > -       /* unref our reservation before assigning */
> > -       ctx->refcount--;
> > -       sdata->reserved_chanctx = NULL;
> > -       ret = ieee80211_assign_vif_chanctx(sdata, ctx);
> > -       if (ret) {
> > -               /* if assign fails refcount stays the same */
> > -               if (ctx->refcount == 0)
> > -                       ieee80211_free_chanctx(local, ctx);
> > -               goto out_wake;
> > +               ret = ieee80211_assign_vif_chanctx(sdata, ctx);
> > +               if (ret) {
> > +                       /* if assign fails refcount stays the same */
> > +                       if (ctx->refcount == 0)
> > +                               ieee80211_free_chanctx(local, ctx);
> > +                       goto out;
> > +               }
> > +
> > +               ieee80211_recalc_chanctx_chantype(local, ctx);
> > +               ieee80211_recalc_smps_chanctx(local, ctx);
> > +               ieee80211_recalc_radar_chanctx(local, ctx);
> >         }
> 
> Not really sure if you need to `else` and re-indent the whole thing
> because you already do a `goto` in the `if`..

No, I don't do a goto in the 'if', unless
__ieee80211_vif_change_channel() fails.  I just reckoned this would be a
bit cleaner like this.


> >         *changed = local_changed;
> > -
> > -       ieee80211_recalc_chanctx_chantype(local, ctx);
> > -       ieee80211_recalc_smps_chanctx(local, ctx);
> > -       ieee80211_recalc_radar_chanctx(local, ctx);
> > -out_wake:
> > +out:
> >         ieee80211_wake_queues_by_reason(&sdata->local->hw,
> >                                         IEEE80211_MAX_QUEUE_MAP,
> >                                         IEEE80211_QUEUE_STOP_REASON_CHANCTX);
> > -out:
> >         mutex_unlock(&local->chanctx_mtx);
> >         return ret;
> >  }
> 
> Are you sure you want to remove the `out_wake` from here? Why not in the [2/3]?

Well, this patch is somewhat refactoring this function, so I think it's
okay to have this here as well.

--
Luca.

--
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
Joe Perches March 7, 2014, 8:19 p.m. UTC | #3
On Fri, 2014-03-07 at 08:43 +0200, Luca Coelho wrote:
> On Wed, 2014-03-05 at 12:32 +0100, Michal Kazior wrote:
> > On 5 March 2014 12:11, Luca Coelho <luca@coelho.fi> wrote:
> > > With single-channel drivers, we need to be able to change a running
> > > chanctx if we want to use chanctx reservation.  Not all drivers may be
> > > able to do this, so add a flag that indicates support for it.
[]
> > > diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
[]
> > > @@ -162,6 +162,26 @@ static void ieee80211_change_chanctx(struct ieee80211_local *local,
> > >         }
> > >  }
> > >
> > > +static bool ieee80211_chanctx_is_reserved(struct ieee80211_local *local,
> > > +                                    struct ieee80211_chanctx *ctx)
> > > +{
> > > +       struct ieee80211_sub_if_data *sdata;
> > > +       bool ret = false;
> > > +
> > > +       lockdep_assert_held(&local->chanctx_mtx);
> > > +       rcu_read_lock();
> > > +       list_for_each_entry_rcu(sdata, &local->interfaces, list) {
> > > +               if (sdata->reserved_chanctx == ctx) {
> > > +                       ret = true;
> > > +                       goto out;
> > > +               }
> > > +       }
> > > +
> > > +out:
> > > +       rcu_read_unlock();
> > > +       return false;
> > 
> > `return ret` ;-)
> 
> Gack! I'll fix.

trivia: using break is more traditional

static bool ieee80211_chanctx_is_reserved(struct ieee80211_local *local,
					  struct ieee80211_chanctx *ctx)
{
	struct ieee80211_sub_if_data *sdata;
	bool reserved = false;

	lockdep_assert_held(&local->chanctx_mtx);

	rcu_read_lock();

	list_for_each_entry_rcu(sdata, &local->interfaces, list) {
		if (sdata->reserved_chanctx == ctx) {
			reserved = true;
			break;
		}
	}

	rcu_read_unlock();

	return reserved;
}


--
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
Luca Coelho March 9, 2014, 1:38 p.m. UTC | #4
On March 7, 2014 10:19:49 PM GMT+02:00, Joe Perches <joe@perches.com> wrote:
>On Fri, 2014-03-07 at 08:43 +0200, Luca Coelho wrote:
>> On Wed, 2014-03-05 at 12:32 +0100, Michal Kazior wrote:
>> > On 5 March 2014 12:11, Luca Coelho <luca@coelho.fi> wrote:
>> > > With single-channel drivers, we need to be able to change a
>running
>> > > chanctx if we want to use chanctx reservation.  Not all drivers
>may be
>> > > able to do this, so add a flag that indicates support for it.
>[]
>> > > diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
>[]
>> > > @@ -162,6 +162,26 @@ static void ieee80211_change_chanctx(struct
>ieee80211_local *local,
>> > >         }
>> > >  }
>> > >
>> > > +static bool ieee80211_chanctx_is_reserved(struct ieee80211_local
>*local,
>> > > +                                    struct ieee80211_chanctx
>*ctx)
>> > > +{
>> > > +       struct ieee80211_sub_if_data *sdata;
>> > > +       bool ret = false;
>> > > +
>> > > +       lockdep_assert_held(&local->chanctx_mtx);
>> > > +       rcu_read_lock();
>> > > +       list_for_each_entry_rcu(sdata, &local->interfaces, list)
>{
>> > > +               if (sdata->reserved_chanctx == ctx) {
>> > > +                       ret = true;
>> > > +                       goto out;
>> > > +               }
>> > > +       }
>> > > +
>> > > +out:
>> > > +       rcu_read_unlock();
>> > > +       return false;
>> > 
>> > `return ret` ;-)
>> 
>> Gack! I'll fix.
>
>trivia: using break is more traditional

Yeah, good point. I'll change to break.

--
Luca.


--
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 86faa41..b35c608 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -1553,6 +1553,12 @@  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,
@@ -1584,6 +1590,7 @@  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 9dfdba5..d634f41 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -162,6 +162,26 @@  static void ieee80211_change_chanctx(struct ieee80211_local *local,
 	}
 }
 
+static bool ieee80211_chanctx_is_reserved(struct ieee80211_local *local,
+				     struct ieee80211_chanctx *ctx)
+{
+	struct ieee80211_sub_if_data *sdata;
+	bool ret = false;
+
+	lockdep_assert_held(&local->chanctx_mtx);
+	rcu_read_lock();
+	list_for_each_entry_rcu(sdata, &local->interfaces, list) {
+		if (sdata->reserved_chanctx == ctx) {
+			ret = true;
+			goto out;
+		}
+	}
+
+out:
+	rcu_read_unlock();
+	return false;
+}
+
 static struct ieee80211_chanctx *
 ieee80211_find_chanctx(struct ieee80211_local *local,
 		       const struct cfg80211_chan_def *chandef,
@@ -177,7 +197,11 @@  ieee80211_find_chanctx(struct ieee80211_local *local,
 	list_for_each_entry(ctx, &local->chanctx_list, list) {
 		const struct cfg80211_chan_def *compat;
 
-		if (ctx->mode == IEEE80211_CHANCTX_EXCLUSIVE)
+		/* We don't support chanctx reservation for multiple
+		 * vifs yet, so don't allow reserved chanctxs to be
+		 * reused.
+		 */
+		if (ieee80211_chanctx_is_reserved(local, ctx))
 			continue;
 
 		compat = cfg80211_chandef_compatible(&ctx->conf.def, chandef);
@@ -652,18 +676,25 @@  int ieee80211_vif_reserve_chanctx(struct ieee80211_sub_if_data *sdata,
 	/* try to find another context with the chandef we want */
 	new_ctx = ieee80211_find_chanctx(local, chandef, mode);
 	if (!new_ctx) {
-		/* create a new context */
-		new_ctx = ieee80211_new_chanctx(local, chandef, mode);
-		if (IS_ERR(new_ctx)) {
-			ret = PTR_ERR(new_ctx);
-			goto out;
+		if (curr_ctx->refcount == 1 &&
+		    (local->hw.flags & IEEE80211_HW_CHANGE_RUNNING_CHANCTX)) {
+			/* if we're the only users of the chanctx and
+			 * the driver supports changing a running
+			 * context, reserve our current context
+			 */
+			new_ctx = curr_ctx;
+		} else {
+			/* create a new context and reserve it */
+			new_ctx = ieee80211_new_chanctx(local, chandef, mode);
+			if (IS_ERR(new_ctx)) {
+				ret = PTR_ERR(new_ctx);
+				goto out;
+			}
 		}
 	}
 
-	/* reserve the new or existing context */
 	sdata->reserved_chanctx = new_ctx;
 	new_ctx->refcount++;
-
 	sdata->reserved_chandef = *chandef;
 out:
 	mutex_unlock(&local->chanctx_mtx);
@@ -703,37 +734,45 @@  int ieee80211_vif_use_reserved_context(struct ieee80211_sub_if_data *sdata,
 	ieee80211_stop_queues_by_reason(&local->hw,
 					IEEE80211_MAX_QUEUE_MAP,
 					IEEE80211_QUEUE_STOP_REASON_CHANCTX);
+	/* unref our reservation */
+	ctx->refcount--;
+	sdata->reserved_chanctx = NULL;
 
-	ieee80211_unassign_vif_chanctx(sdata, old_ctx);
-	if (old_ctx->refcount == 0)
-		ieee80211_free_chanctx(local, old_ctx);
+	if (old_ctx == ctx) {
+		/* This is our own context, just change it */
+		ret = __ieee80211_vif_change_channel(sdata, old_ctx,
+						     &local_changed);
+		if (ret)
+			goto out;
+	} else {
+		ieee80211_unassign_vif_chanctx(sdata, old_ctx);
+		if (old_ctx->refcount == 0)
+			ieee80211_free_chanctx(local, old_ctx);
 
-	if (sdata->vif.bss_conf.chandef.width != sdata->reserved_chandef.width)
-		local_changed |= BSS_CHANGED_BANDWIDTH;
+		if (sdata->vif.bss_conf.chandef.width !=
+		    sdata->reserved_chandef.width)
+			local_changed |= BSS_CHANGED_BANDWIDTH;
 
-	sdata->vif.bss_conf.chandef = sdata->reserved_chandef;
+		sdata->vif.bss_conf.chandef = sdata->reserved_chandef;
 
-	/* unref our reservation before assigning */
-	ctx->refcount--;
-	sdata->reserved_chanctx = NULL;
-	ret = ieee80211_assign_vif_chanctx(sdata, ctx);
-	if (ret) {
-		/* if assign fails refcount stays the same */
-		if (ctx->refcount == 0)
-			ieee80211_free_chanctx(local, ctx);
-		goto out_wake;
+		ret = ieee80211_assign_vif_chanctx(sdata, ctx);
+		if (ret) {
+			/* if assign fails refcount stays the same */
+			if (ctx->refcount == 0)
+				ieee80211_free_chanctx(local, ctx);
+			goto out;
+		}
+
+		ieee80211_recalc_chanctx_chantype(local, ctx);
+		ieee80211_recalc_smps_chanctx(local, ctx);
+		ieee80211_recalc_radar_chanctx(local, ctx);
 	}
 
 	*changed = local_changed;
-
-	ieee80211_recalc_chanctx_chantype(local, ctx);
-	ieee80211_recalc_smps_chanctx(local, ctx);
-	ieee80211_recalc_radar_chanctx(local, ctx);
-out_wake:
+out:
 	ieee80211_wake_queues_by_reason(&sdata->local->hw,
 					IEEE80211_MAX_QUEUE_MAP,
 					IEEE80211_QUEUE_STOP_REASON_CHANCTX);
-out:
 	mutex_unlock(&local->chanctx_mtx);
 	return ret;
 }