2014-02-04 12:59:32

by Luca Coelho

[permalink] [raw]
Subject: [RCF/WIP 0/3] multi-vif/multi-channel CSA implementation

From: Luciano Coelho <[email protected]>

Hi,

These are the patches I've been working on. There are still some
rough edges (check the TODOs) and this is work-in-progress.

The concept seems to work fine, with my very limited testing.

Please comment. Now it's your chance to get your revenge for my
annoying comments! :P

--
Luca.

Luciano Coelho (3):
mac80211: split ieee80211_vif_change_channel in two
mac80211: implement channel switch for multiple vifs and multiple
channels
mac80211: add usage of CS channel reservation for STA

net/mac80211/chan.c | 190 ++++++++++++++++++++++++++++++++++++++++-----
net/mac80211/ieee80211_i.h | 8 ++
net/mac80211/mlme.c | 71 +++++++----------
3 files changed, 207 insertions(+), 62 deletions(-)

--
1.8.5.3



2014-02-05 14:32:19

by Luca Coelho

[permalink] [raw]
Subject: Re: [RCF/WIP 2/3] mac80211: implement channel switch for multiple vifs and multiple channels

On Tue, 2014-02-04 at 16:25 +0100, Michal Kazior wrote:
> On 4 February 2014 13:59, Luciano Coelho <[email protected]> wrote:
> > [...]
> > + /* try to find another context with the chandef we want */
> > + new_ctx = ieee80211_find_chanctx(local, chandef,
> > + IEEE80211_CHANCTX_SHARED);
> > + if (new_ctx) {
> > + /* reserve the existing compatible context */
> > + sdata->csa_reserved_chanctx = new_ctx;
> > + new_ctx->refcount++;
> > + } else if (curr_ctx->refcount == 1) {
> > + /* We're the only user of the current context, mark it
> > + * as exclusive, so nobody tries to use it until we
> > + * finish the channel switch. This is an optimization
> > + * to prevent waste of contexts when the number is
> > + * limited.
> > + */
> > +
> > + sdata->csa_reserved_chanctx = curr_ctx;
> > + sdata->csa_chandef = *chandef;
> > +
> > + sdata->csa_chanctx_old_mode = curr_ctx->mode;
> > +
> > + curr_ctx->mode = IEEE80211_CHANCTX_EXCLUSIVE;
>
> The only reason to do this would seem drv_add_chanctx() can fail if
> you have too many chanctx in driver.

This is just an optimization case. I'm not even sure every driver will
be able to change the chanctx on the fly so we probably would need to
add a HWCONF flag for that.

And the idea here is to prevent others from joining out chanctx since we
are going to change it in the near future. Whoever joins now probably
wants the current configuration not our future one.

Maybe we could add a new mode, IEEE80211_CHANCTX_CHANNEL_SWITCH, so
other vifs would only be able to reserve it if their future config
matches our future config?

This starts complicating things too much for a small optimization that
may not even work with every driver. Perhaps is better to just remove
the old context and add a new for this case as well...


> Perhaps we could defer drv_add_chanctx() for reserved chanctx?

I'm not sure I understand. For this situation (ie. we're alone in the
chanctx) we don't do a drv_add_chanctx().


> > [...]
> > + 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;
> > +
> > + /* TODO: what happens if another vif created a context
> > + * that is compatible with our future chandef while
> > + * this one has been marked as exclusive? Merge?
> > + */
> > +
> > + ctx->mode = sdata->csa_chanctx_old_mode;
> > +
> > + *changed = local_changed;
> > + goto out;
> > + }
> > +
> > + ieee80211_stop_queues_by_reason(&local->hw,
> > + IEEE80211_MAX_QUEUE_MAP,
> > + IEEE80211_QUEUE_STOP_REASON_CSA);
> > +
>
> Shouldn't queues be stopped depending on block_tx in CSA IE?

That is the case when we *receive* the CSA. At this point, we're
already making the actual channel switch. The queues are stopped here
because we're going to unassign the current chanctx and assign a new one
and we don't want to send frames meanwhile. It doesn't hurt if they
were already stopped before, it will be a NOP.


> > + 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 != ctx->conf.def.width)
> > + local_changed |= BSS_CHANGED_BANDWIDTH;
>
> I wonder if this check makes any sense. With multi-interface you can
> have varied bss_conf.width and not match the shared width of a channel
> context, can't you?

You're right. I guess I should set this flag according to what happens
when I call ieee80211_recalc_chanctx_chantype(). Maybe the nicest thing
to do would be to change that function so that it returns changed flags
(which will be either 0 or BSS_CHANGED_BANDWIDTH).

I'll look into that.


> > +
> > + sdata->vif.bss_conf.chandef = ctx->conf.def;
> > +
> > + /* unref our reservation before assigning */
> > + ctx->refcount--;
> > + ret = ieee80211_assign_vif_chanctx(sdata, ctx);
>
> This looks wrong. You don't seem to care about old_ctx->refcount here
> at all. If you perform CSA for 2 interfaces on a single-channel hw
> you'll have 2 interfaces on 2 different channels at one point.

I'm unassigning the vif from old_ctx (a few lines above). And now, I'm
reassigning the vif to ctx (the new one).

I think instead of caring about the single-channel stuff here, it should
be handled in the ieee80211_reserve_chanctx() function. It's one of the
reasons for having the reservation in the first place.


> The way I see it should be more like this:
>
> old_ctx->csa_completions++;
> if (old_ctx->csa_completions != old_ctx->csa_requests)
> goto wait_for_more;
>
> disconnect_non_csa_interfaces(old_ctx); // *
> // get list of csa vifs from old_ctx
> for_each_csa_vif_in_ctx(sdata)
> unassign_vif_chanctx(sdata, old_ctx)
> free_chanctx(old_ctx) // **
> drv_add_chanctx(new_ctx)
> for_each_csa_vif_in_ctx(sdata)
> assign_vif_chanctx(sdata, new_ctx)
>
> old_ctx->csa_requests would be increased when you reserve a channel.
>
> * "switch or die" policy; ideally this should go through cfg80211
> somehow so that everything is teared down properly and userspace is
> notified (AP stopped)
>
> ** this might remove the need for the exclusive reserved chanctx?

One of the problems here is that you're checking if the switch can
succeed only at the end of the process. The idea is to fail channel
switch requests as soon as they come in.

I agree that, during the reservation, we need a bit more logic to take
care of the interface combinations and the single-channel cases. But
not here. When I implemented this patch, I was not considering !chanctx
nor single-radio device. ;)

The idea with reservation is more or less like this, as an example:

We have a STA and a P2P-GO (which follows the STA).

1. STA gets CSA from the AP
2. STA makes a reservation for the new context (here the possibility of
creating a new context is checked)
3. the GO controller (ie. wpa_s) gets a notification that there is a
switch going on for the STA
4. the GO wants to follow, so it also reserves the same new context (the
new context is not really used yet, but reserved twice)
5. the STA completes the CS countdown and uses its reservation
6. the GO completes the CS countdown and uses its reservation
7. now the original context doesn't have any user anymore (ie.
refcount=0) and can be freed

5 and 6 can happen in a different order too, it doesn't matter.

Of course this assumes multi-radio. The assumption in this patch is
that if you don't have multi-radio and have 2 interfaces on the same
channel, the channel switch will fail (and we disconnect). Fixing that
comes on top with your patches. ;)

I like your idea of synchronizing things before the switch to help the
single-channel case. But I think we should still have the reservation.
When using the reservation we could check whether the and trying to
free old_ctx) we could check whether the contexts are valid together, if
not, delay the usage of the reserved context until the new combinations
is fine.


>
> > + if (ret) {
> > + /* if assign fails refcount stays the same */
> > + if (ctx->refcount == 0)
> > + ieee80211_free_chanctx(local, ctx);
> > + goto out;
> > + }
> > +
> > + /* TODO: should we wake the queues here? */
>
> Hmm.. you probably should wake queues if this was the last channel
> switch for the hw.

Yes, the queues should be woken here, when the new chanctx is fully
available.

--
Luca.


2014-02-04 12:59:33

by Luca Coelho

[permalink] [raw]
Subject: [RCF/WIP 3/3] mac80211: add usage of CS channel reservation for STA

From: Luciano Coelho <[email protected]>

For simplicity, and as a proof of concept, only the STA case is
covered for now. But the P2P GO and AP cases should also work with
this new concept.

Signed-off-by: Luciano Coelho <[email protected]>
---
net/mac80211/mlme.c | 71 ++++++++++++++++++++---------------------------------
1 file changed, 27 insertions(+), 44 deletions(-)

diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index 6160483..899989c 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -927,18 +927,19 @@ static void ieee80211_chswitch_work(struct work_struct *work)
if (!ifmgd->associated)
goto out;

- mutex_lock(&local->mtx);
- ret = ieee80211_vif_change_channel(sdata, &changed);
- mutex_unlock(&local->mtx);
- if (ret) {
- sdata_info(sdata,
- "vif channel switch failed, disconnecting\n");
- ieee80211_queue_work(&sdata->local->hw,
- &ifmgd->csa_connection_drop_work);
- goto out;
- }
-
+ /* TODO: maybe this if block can be moved to use_reserved()? */
if (!local->use_chanctx) {
+ mutex_lock(&local->mtx);
+ ret = ieee80211_vif_change_channel(sdata, &changed);
+ mutex_unlock(&local->mtx);
+ if (ret) {
+ sdata_info(sdata,
+ "vif channel switch failed, disconnecting\n");
+ ieee80211_queue_work(&sdata->local->hw,
+ &ifmgd->csa_connection_drop_work);
+ goto out;
+ }
+
local->_oper_chandef = sdata->csa_chandef;
/* Call "hw_config" only if doing sw channel switch.
* Otherwise update the channel directly
@@ -947,6 +948,17 @@ static void ieee80211_chswitch_work(struct work_struct *work)
ieee80211_hw_config(local, 0);
else
local->hw.conf.chandef = local->_oper_chandef;
+ } else {
+ mutex_lock(&local->mtx);
+ ret = ieee80211_vif_use_reserved_context(sdata, &changed);
+ mutex_unlock(&local->mtx);
+ if (ret) {
+ sdata_info(sdata,
+ "using reserved context during channel switch failed, disconnecting\n");
+ ieee80211_queue_work(&sdata->local->hw,
+ &ifmgd->csa_connection_drop_work);
+ goto out;
+ }
}

/* XXX: shouldn't really modify cfg80211-owned data! */
@@ -998,7 +1010,6 @@ 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 *chanctx;
enum ieee80211_band current_band;
struct ieee80211_csa_ie csa_ie;
int res;
@@ -1039,44 +1050,16 @@ ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,
return;
}

- ifmgd->flags |= IEEE80211_STA_CSA_RECEIVED;
-
- mutex_lock(&local->chanctx_mtx);
- if (local->use_chanctx) {
- u32 num_chanctx = 0;
- list_for_each_entry(chanctx, &local->chanctx_list, list)
- num_chanctx++;
-
- if (num_chanctx > 1 ||
- !(local->hw.flags & IEEE80211_HW_CHANCTX_STA_CSA)) {
- sdata_info(sdata,
- "not handling chan-switch with channel contexts\n");
- ieee80211_queue_work(&local->hw,
- &ifmgd->csa_connection_drop_work);
- mutex_unlock(&local->chanctx_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 (chanctx->refcount > 1) {
+ res = ieee80211_vif_reserve_chanctx(sdata, &csa_ie.chandef);
+ if (res) {
sdata_info(sdata,
- "channel switch with multiple interfaces on the same channel, disconnecting\n");
+ "reserving context for channel switch failed, disconnecting\n");
ieee80211_queue_work(&local->hw,
&ifmgd->csa_connection_drop_work);
- mutex_unlock(&local->chanctx_mtx);
return;
}
- mutex_unlock(&local->chanctx_mtx);

- sdata->csa_chandef = csa_ie.chandef;
+ ifmgd->flags |= IEEE80211_STA_CSA_RECEIVED;
sdata->vif.csa_active = true;

if (csa_ie.mode)
--
1.8.5.3


2014-02-04 12:59:32

by Luca Coelho

[permalink] [raw]
Subject: [RCF/WIP 1/3] mac80211: split ieee80211_vif_change_channel in two

From: Luciano Coelho <[email protected]>

ieee80211_vif_change_channel() locks chanctx_mtx. When implementing
channel reservation for CS, we will need to call the function to
change channel when the lock is already held, so split the part that
requires the lock out and leave the locking in the original function.

Signed-off-by: Luciano Coelho <[email protected]>
---
net/mac80211/chan.c | 58 ++++++++++++++++++++++++++++++-----------------------
1 file changed, 33 insertions(+), 25 deletions(-)

diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
index 42c6592..a27c6ec 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -547,39 +547,20 @@ int ieee80211_vif_use_channel(struct ieee80211_sub_if_data *sdata,
return ret;
}

-int ieee80211_vif_change_channel(struct ieee80211_sub_if_data *sdata,
- u32 *changed)
+static int __ieee80211_vif_change_channel(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_chanctx *ctx,
+ u32 *changed)
{
struct ieee80211_local *local = sdata->local;
- struct ieee80211_chanctx_conf *conf;
- struct ieee80211_chanctx *ctx;
const struct cfg80211_chan_def *chandef = &sdata->csa_chandef;
- int ret;
u32 chanctx_changed = 0;

- lockdep_assert_held(&local->mtx);
-
- /* should never be called if not performing a channel switch. */
- if (WARN_ON(!sdata->vif.csa_active))
- return -EINVAL;
-
if (!cfg80211_chandef_usable(sdata->local->hw.wiphy, chandef,
IEEE80211_CHAN_DISABLED))
return -EINVAL;

- mutex_lock(&local->chanctx_mtx);
- conf = rcu_dereference_protected(sdata->vif.chanctx_conf,
- lockdep_is_held(&local->chanctx_mtx));
- if (!conf) {
- ret = -EINVAL;
- goto out;
- }
-
- ctx = container_of(conf, struct ieee80211_chanctx, conf);
- if (ctx->refcount != 1) {
- ret = -EINVAL;
- goto out;
- }
+ if (ctx->refcount != 1)
+ return -EINVAL;

if (sdata->vif.bss_conf.chandef.width != chandef->width) {
chanctx_changed = IEEE80211_CHANCTX_CHANGE_WIDTH;
@@ -597,7 +578,34 @@ int ieee80211_vif_change_channel(struct ieee80211_sub_if_data *sdata,
ieee80211_recalc_radar_chanctx(local, ctx);
ieee80211_recalc_chanctx_min_def(local, ctx);

- ret = 0;
+ 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;
--
1.8.5.3


2014-02-04 15:26:00

by Michal Kazior

[permalink] [raw]
Subject: Re: [RCF/WIP 2/3] mac80211: implement channel switch for multiple vifs and multiple channels

On 4 February 2014 13:59, Luciano Coelho <[email protected]> wrote:
> [...]
> + /* try to find another context with the chandef we want */
> + new_ctx = ieee80211_find_chanctx(local, chandef,
> + IEEE80211_CHANCTX_SHARED);
> + if (new_ctx) {
> + /* reserve the existing compatible context */
> + sdata->csa_reserved_chanctx = new_ctx;
> + new_ctx->refcount++;
> + } else if (curr_ctx->refcount == 1) {
> + /* We're the only user of the current context, mark it
> + * as exclusive, so nobody tries to use it until we
> + * finish the channel switch. This is an optimization
> + * to prevent waste of contexts when the number is
> + * limited.
> + */
> +
> + sdata->csa_reserved_chanctx = curr_ctx;
> + sdata->csa_chandef = *chandef;
> +
> + sdata->csa_chanctx_old_mode = curr_ctx->mode;
> +
> + curr_ctx->mode = IEEE80211_CHANCTX_EXCLUSIVE;

The only reason to do this would seem drv_add_chanctx() can fail if
you have too many chanctx in driver.

Perhaps we could defer drv_add_chanctx() for reserved chanctx?


> [...]
> + 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;
> +
> + /* TODO: what happens if another vif created a context
> + * that is compatible with our future chandef while
> + * this one has been marked as exclusive? Merge?
> + */
> +
> + ctx->mode = sdata->csa_chanctx_old_mode;
> +
> + *changed = local_changed;
> + goto out;
> + }
> +
> + ieee80211_stop_queues_by_reason(&local->hw,
> + IEEE80211_MAX_QUEUE_MAP,
> + IEEE80211_QUEUE_STOP_REASON_CSA);
> +

Shouldn't queues be stopped depending on block_tx in CSA IE?


> + 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 != ctx->conf.def.width)
> + local_changed |= BSS_CHANGED_BANDWIDTH;

I wonder if this check makes any sense. With multi-interface you can
have varied bss_conf.width and not match the shared width of a channel
context, can't you?


> +
> + sdata->vif.bss_conf.chandef = ctx->conf.def;
> +
> + /* unref our reservation before assigning */
> + ctx->refcount--;
> + ret = ieee80211_assign_vif_chanctx(sdata, ctx);

This looks wrong. You don't seem to care about old_ctx->refcount here
at all. If you perform CSA for 2 interfaces on a single-channel hw
you'll have 2 interfaces on 2 different channels at one point.

The way I see it should be more like this:

old_ctx->csa_completions++;
if (old_ctx->csa_completions != old_ctx->csa_requests)
goto wait_for_more;

disconnect_non_csa_interfaces(old_ctx); // *
// get list of csa vifs from old_ctx
for_each_csa_vif_in_ctx(sdata)
unassign_vif_chanctx(sdata, old_ctx)
free_chanctx(old_ctx) // **
drv_add_chanctx(new_ctx)
for_each_csa_vif_in_ctx(sdata)
assign_vif_chanctx(sdata, new_ctx)

old_ctx->csa_requests would be increased when you reserve a channel.

* "switch or die" policy; ideally this should go through cfg80211
somehow so that everything is teared down properly and userspace is
notified (AP stopped)

** this might remove the need for the exclusive reserved chanctx?


> + if (ret) {
> + /* if assign fails refcount stays the same */
> + if (ctx->refcount == 0)
> + ieee80211_free_chanctx(local, ctx);
> + goto out;
> + }
> +
> + /* TODO: should we wake the queues here? */

Hmm.. you probably should wake queues if this was the last channel
switch for the hw.


MichaƂ

2014-02-04 12:59:32

by Luca Coelho

[permalink] [raw]
Subject: [RCF/WIP 2/3] mac80211: implement channel switch for multiple vifs and multiple channels

From: Luciano Coelho <[email protected]>

In order to support CSA handling with multiple vifs and multiple
contexts, we implement a concept of reserving a context when the CSA
process is started. This allows us to know whether the CSA can be
done before we try to do it (which is useful in AP mode, for
instance). When the actual channel switch happens, this reserved
context is used.

When reserving the context, the following algorithm is used:

1) try to find an existing context that matches our future chandef and
reserve it if it exists;
2) otherwise, check if we're the only vif in the current context, in
which case we can just change our current context. To prevent
other vifs from joining this context in the meantime, we mark it as
exclusive. This is an optimization to avoid using extra contexts
when not necessary;
3) if we're not the only vif in the current context, create a new
context and reserve it;

Signed-off-by: Luciano Coelho <[email protected]>
---
net/mac80211/chan.c | 146 +++++++++++++++++++++++++++++++++++++++++++++
net/mac80211/ieee80211_i.h | 8 +++
2 files changed, 154 insertions(+)

diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
index a27c6ec..4289c22 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -611,6 +611,150 @@ int ieee80211_vif_change_channel(struct ieee80211_sub_if_data *sdata,
return ret;
}

+int ieee80211_vif_reserve_chanctx(struct ieee80211_sub_if_data *sdata,
+ const struct cfg80211_chan_def *chandef)
+{
+ struct ieee80211_local *local = sdata->local;
+ struct ieee80211_chanctx_conf *conf;
+ struct ieee80211_chanctx *new_ctx, *curr_ctx;
+ int ret;
+
+ /* TODO: check !use_ctxt stuff */
+
+ 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;
+ }
+
+ curr_ctx = container_of(conf, struct ieee80211_chanctx, conf);
+
+ /* try to find another context with the chandef we want */
+ new_ctx = ieee80211_find_chanctx(local, chandef,
+ IEEE80211_CHANCTX_SHARED);
+ if (new_ctx) {
+ /* reserve the existing compatible context */
+ sdata->csa_reserved_chanctx = new_ctx;
+ new_ctx->refcount++;
+ } else if (curr_ctx->refcount == 1) {
+ /* We're the only user of the current context, mark it
+ * as exclusive, so nobody tries to use it until we
+ * finish the channel switch. This is an optimization
+ * to prevent waste of contexts when the number is
+ * limited.
+ */
+
+ sdata->csa_reserved_chanctx = curr_ctx;
+ sdata->csa_chandef = *chandef;
+
+ sdata->csa_chanctx_old_mode = curr_ctx->mode;
+
+ curr_ctx->mode = IEEE80211_CHANCTX_EXCLUSIVE;
+ } else {
+ /* create a new context and reserve it */
+ new_ctx = ieee80211_new_chanctx(local, chandef,
+ IEEE80211_CHANCTX_SHARED);
+ if (IS_ERR(new_ctx)) {
+ ret = PTR_ERR(new_ctx);
+ goto out;
+ }
+ sdata->csa_reserved_chanctx = new_ctx;
+
+ new_ctx->refcount++;
+ }
+
+ ret = 0;
+out:
+ mutex_unlock(&local->chanctx_mtx);
+ return ret;
+}
+
+int ieee80211_vif_use_reserved_context(struct ieee80211_sub_if_data *sdata,
+ u32 *changed)
+{
+ struct ieee80211_local *local = sdata->local;
+ struct ieee80211_chanctx *ctx;
+ struct ieee80211_chanctx *old_ctx;
+ struct ieee80211_chanctx_conf *conf;
+ int ret, local_changed = *changed;
+
+ /* TODO: need to recheck if the chandef is usable etc.? */
+
+ lockdep_assert_held(&local->mtx);
+
+ mutex_lock(&local->chanctx_mtx);
+
+ ctx = sdata->csa_reserved_chanctx;
+ if (WARN_ON(!ctx)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ conf = rcu_dereference_protected(sdata->vif.chanctx_conf,
+ lockdep_is_held(&local->chanctx_mtx));
+ if (!conf) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ old_ctx = container_of(conf, struct ieee80211_chanctx, conf);
+
+ 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;
+
+ /* TODO: what happens if another vif created a context
+ * that is compatible with our future chandef while
+ * this one has been marked as exclusive? Merge?
+ */
+
+ ctx->mode = sdata->csa_chanctx_old_mode;
+
+ *changed = local_changed;
+ goto out;
+ }
+
+ ieee80211_stop_queues_by_reason(&local->hw,
+ IEEE80211_MAX_QUEUE_MAP,
+ IEEE80211_QUEUE_STOP_REASON_CSA);
+
+ 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 != ctx->conf.def.width)
+ local_changed |= BSS_CHANGED_BANDWIDTH;
+
+ sdata->vif.bss_conf.chandef = ctx->conf.def;
+
+ /* unref our reservation before assigning */
+ ctx->refcount--;
+ 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;
+ }
+
+ /* TODO: should we wake the queues here? */
+
+ *changed = local_changed;
+
+ ieee80211_recalc_chanctx_chantype(local, ctx);
+ ieee80211_recalc_smps_chanctx(local, ctx);
+ ieee80211_recalc_radar_chanctx(local, ctx);
+out:
+ mutex_unlock(&local->chanctx_mtx);
+ return ret;
+}
+
int ieee80211_vif_change_bandwidth(struct ieee80211_sub_if_data *sdata,
const struct cfg80211_chan_def *chandef,
u32 *changed)
@@ -664,6 +808,8 @@ void ieee80211_vif_release_channel(struct ieee80211_sub_if_data *sdata)
{
WARN_ON(sdata->dev && netif_carrier_ok(sdata->dev));

+ /* TODO: remember to remove the reservation in here! */
+
lockdep_assert_held(&sdata->local->mtx);

mutex_lock(&sdata->local->chanctx_mtx);
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 0014b53..df85562 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -755,6 +755,8 @@ struct ieee80211_sub_if_data {
int csa_counter_offset_presp;
bool csa_radar_required;
struct cfg80211_chan_def csa_chandef;
+ struct ieee80211_chanctx *csa_reserved_chanctx;
+ enum ieee80211_chanctx_mode csa_chanctx_old_mode;

/* used to reconfigure hardware SM PS */
struct work_struct recalc_smps;
@@ -1774,6 +1776,12 @@ ieee80211_vif_use_channel(struct ieee80211_sub_if_data *sdata,
const struct cfg80211_chan_def *chandef,
enum ieee80211_chanctx_mode mode);
int __must_check
+ieee80211_vif_reserve_chanctx(struct ieee80211_sub_if_data *sdata,
+ const struct cfg80211_chan_def *chandef);
+int __must_check
+ieee80211_vif_use_reserved_context(struct ieee80211_sub_if_data *sdata,
+ u32 *changed);
+int __must_check
ieee80211_vif_change_bandwidth(struct ieee80211_sub_if_data *sdata,
const struct cfg80211_chan_def *chandef,
u32 *changed);
--
1.8.5.3