2013-07-08 13:14:26

by Simon Wunderlich

[permalink] [raw]
Subject: [PATCHv3 0/5] add master channel switch announcement support

This is the 3rd edition of the CSA support patch series. This patchset adds
generic channel switch support for AP. This is required for DFS operation
(e.g. Wi-Fi Alliance requires this for 802.11h certification). This will also
be required for IBSS-DFS later.

Changes from PATCHv2:
* change_channel function for channel context
* announce csa support via wiphy flag/cmd
* put offset into nested csa information for nl80211 command
* various locking, style, documentation and some more minor issues
* rebased on latest mac80211-next master

The rough design is:
* userspace asks kernel to switch a channel using the new NL80211_CMD_CHANNEL_SWITCH
command. It supplies IE information for the time while staying on the old channel and
announcing the switch, and IE information for after the switch to the new channel.
* IE information contains the beacon and optionally probe responses, which should
include (E)CSA IEs for the CSA case. Furthermore an offset is provided (for beacon
and probe response) to point to the counter field within the channel switch IEs.
* The driver gets the new beacons passed and must set them, and decrement the
counter field. When it reaches 0, the channel is changed and userspace notified.

As always, any comments are appreciated.

Cheers,
Simon

Simon Wunderlich (5):
nl80211/cfg80211: add channel switch command
mac80211: add functions to duplicate a cfg80211_beacon
mac80211: allow chanctx to change channels
mac80211: add channel switch command and beacon callbacks
ath9k: enable CSA functionality in ath9k

drivers/net/wireless/ath/ath9k/ath9k.h | 2 +
drivers/net/wireless/ath/ath9k/beacon.c | 21 ++++
drivers/net/wireless/ath/ath9k/init.c | 1 +
drivers/net/wireless/ath/ath9k/main.c | 17 +++
drivers/net/wireless/ath/ath9k/xmit.c | 2 +
include/net/cfg80211.h | 30 ++++++
include/net/mac80211.h | 38 +++++++
include/uapi/linux/nl80211.h | 29 ++++++
net/mac80211/cfg.c | 172 ++++++++++++++++++++++++++++++-
net/mac80211/chan.c | 55 ++++++++++
net/mac80211/driver-ops.h | 13 +++
net/mac80211/ieee80211_i.h | 17 +++
net/mac80211/iface.c | 2 +
net/mac80211/trace.h | 26 +++++
net/mac80211/tx.c | 80 ++++++++++++++
net/wireless/nl80211.c | 126 +++++++++++++++++++++-
16 files changed, 628 insertions(+), 3 deletions(-)

--
1.7.10.4



2013-07-08 13:14:27

by Simon Wunderlich

[permalink] [raw]
Subject: [PATCHv3 3/5] mac80211: allow chanctx to change channels

This adds mac80211/chanctx code to allow to change a channel definition
of an active channel context. This will be used for the channel switch
command added later.

Signed-off-by: Simon Wunderlich <[email protected]>
Signed-off-by: Mathias Kretschmer <[email protected]>
---
include/net/mac80211.h | 4 ++++
net/mac80211/chan.c | 55 ++++++++++++++++++++++++++++++++++++++++++++
net/mac80211/ieee80211_i.h | 4 ++++
3 files changed, 63 insertions(+)

diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 7c5dc78..aadf4eb 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -152,11 +152,13 @@ struct ieee80211_low_level_stats {
* @IEEE80211_CHANCTX_CHANGE_WIDTH: The channel width changed
* @IEEE80211_CHANCTX_CHANGE_RX_CHAINS: The number of RX chains changed
* @IEEE80211_CHANCTX_CHANGE_RADAR: radar detection flag changed
+ * @IEEE80211_CHANCTX_CHANGE_CHANNEL: switched to another operating channel
*/
enum ieee80211_chanctx_change {
IEEE80211_CHANCTX_CHANGE_WIDTH = BIT(0),
IEEE80211_CHANCTX_CHANGE_RX_CHAINS = BIT(1),
IEEE80211_CHANCTX_CHANGE_RADAR = BIT(2),
+ IEEE80211_CHANCTX_CHANGE_CHANNEL = BIT(3)
};

/**
@@ -222,6 +224,7 @@ struct ieee80211_chanctx_conf {
* @BSS_CHANGED_BANDWIDTH: The bandwidth used by this interface changed,
* note that this is only called when it changes after the channel
* context had been assigned.
+ * @BSS_CHANGED_CHANNEL: The operating channel of this interface changed.
*/
enum ieee80211_bss_change {
BSS_CHANGED_ASSOC = 1<<0,
@@ -246,6 +249,7 @@ enum ieee80211_bss_change {
BSS_CHANGED_P2P_PS = 1<<19,
BSS_CHANGED_BEACON_INFO = 1<<20,
BSS_CHANGED_BANDWIDTH = 1<<21,
+ BSS_CHANGED_CHANNEL = 1<<22,

/* when adding here, make sure to change ieee80211_reconfig */
};
diff --git a/net/mac80211/chan.c b/net/mac80211/chan.c
index 03e8d2e..7816703 100644
--- a/net/mac80211/chan.c
+++ b/net/mac80211/chan.c
@@ -410,6 +410,61 @@ int ieee80211_vif_use_channel(struct ieee80211_sub_if_data *sdata,
return ret;
}

+int ieee80211_vif_change_channel(struct ieee80211_sub_if_data *sdata,
+ const struct cfg80211_chan_def *chandef,
+ u32 *changed)
+{
+ struct ieee80211_local *local = sdata->local;
+ struct ieee80211_chanctx_conf *conf;
+ struct ieee80211_chanctx *ctx;
+ int ret;
+ u32 chanctx_changed = 0;
+
+ 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 (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;
+ *changed |= BSS_CHANGED_CHANNEL;
+ drv_change_chanctx(local, ctx, chanctx_changed);
+
+ if (!local->use_chanctx) {
+ local->_oper_chandef = *chandef;
+ ieee80211_hw_config(local, 0);
+ }
+
+ ieee80211_recalc_chanctx_chantype(local, ctx);
+ ieee80211_recalc_smps_chanctx(local, ctx);
+ ieee80211_recalc_radar_chanctx(local, ctx);
+
+ ret = 0;
+ 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)
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index 683751a..e34eb5f 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -1625,6 +1625,10 @@ int __must_check
ieee80211_vif_change_bandwidth(struct ieee80211_sub_if_data *sdata,
const struct cfg80211_chan_def *chandef,
u32 *changed);
+int __must_check
+ieee80211_vif_change_channel(struct ieee80211_sub_if_data *sdata,
+ const struct cfg80211_chan_def *chandef,
+ 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,
--
1.7.10.4


2013-07-08 13:14:28

by Simon Wunderlich

[permalink] [raw]
Subject: [PATCHv3 5/5] ath9k: enable CSA functionality in ath9k

CSA is only enabled for one interface, but the same limitation applies
for mac80211 too. It checks whether the beacon has been sent (different
approaches for non-EDMA-enabled and EDMA-enabled devices), and completes
the channel switch after that.

Signed-off-by: Simon Wunderlich <[email protected]>
Signed-off-by: Mathias Kretschmer <[email protected]>
---
Changes to PATCHv2:
* announce support for channel switch

Changes to PATCHv1:
* complete channel switch after the last beacon has been sent
---
drivers/net/wireless/ath/ath9k/ath9k.h | 2 ++
drivers/net/wireless/ath/ath9k/beacon.c | 21 +++++++++++++++++++++
drivers/net/wireless/ath/ath9k/init.c | 1 +
drivers/net/wireless/ath/ath9k/main.c | 17 +++++++++++++++++
drivers/net/wireless/ath/ath9k/xmit.c | 2 ++
5 files changed, 43 insertions(+)

diff --git a/drivers/net/wireless/ath/ath9k/ath9k.h b/drivers/net/wireless/ath/ath9k/ath9k.h
index 579ed9c..3745487 100644
--- a/drivers/net/wireless/ath/ath9k/ath9k.h
+++ b/drivers/net/wireless/ath/ath9k/ath9k.h
@@ -424,6 +424,7 @@ void ath9k_beacon_assign_slot(struct ath_softc *sc, struct ieee80211_vif *vif);
void ath9k_beacon_remove_slot(struct ath_softc *sc, struct ieee80211_vif *vif);
void ath9k_set_tsfadjust(struct ath_softc *sc, struct ieee80211_vif *vif);
void ath9k_set_beacon(struct ath_softc *sc);
+bool ath9k_csa_is_finished(struct ath_softc *sc);

/*******************/
/* Link Monitoring */
@@ -751,6 +752,7 @@ struct ath_softc {
#endif

struct ath_descdma txsdma;
+ struct ieee80211_vif *csa_vif;

struct ath_ant_comb ant_comb;
u8 ant_tx, ant_rx;
diff --git a/drivers/net/wireless/ath/ath9k/beacon.c b/drivers/net/wireless/ath/ath9k/beacon.c
index fd1eeba..e400151 100644
--- a/drivers/net/wireless/ath/ath9k/beacon.c
+++ b/drivers/net/wireless/ath/ath9k/beacon.c
@@ -310,6 +310,23 @@ void ath9k_set_tsfadjust(struct ath_softc *sc, struct ieee80211_vif *vif)
(unsigned long long)tsfadjust, avp->av_bslot);
}

+bool ath9k_csa_is_finished(struct ath_softc *sc)
+{
+ struct ieee80211_vif *vif;
+
+ vif = sc->csa_vif;
+ if (!vif || !vif->csa_active)
+ return false;
+
+ if (!ieee80211_csa_is_complete(vif))
+ return false;
+
+ ieee80211_csa_finish(vif);
+ vif->csa_active = 0;
+ sc->csa_vif = NULL;
+ return true;
+}
+
void ath9k_beacon_tasklet(unsigned long data)
{
struct ath_softc *sc = (struct ath_softc *)data;
@@ -355,6 +372,10 @@ void ath9k_beacon_tasklet(unsigned long data)
return;
}

+ /* EDMA devices check that in the tx completion function. */
+ if (!edma && ath9k_csa_is_finished(sc))
+ return;
+
slot = ath9k_beacon_choose_slot(sc);
vif = sc->beacon.bslot[slot];

diff --git a/drivers/net/wireless/ath/ath9k/init.c b/drivers/net/wireless/ath/ath9k/init.c
index f359b0d..3ce83c4 100644
--- a/drivers/net/wireless/ath/ath9k/init.c
+++ b/drivers/net/wireless/ath/ath9k/init.c
@@ -807,6 +807,7 @@ void ath9k_set_hw_capab(struct ath_softc *sc, struct ieee80211_hw *hw)
hw->wiphy->flags |= WIPHY_FLAG_IBSS_RSN;
hw->wiphy->flags |= WIPHY_FLAG_SUPPORTS_TDLS;
hw->wiphy->flags |= WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL;
+ hw->wiphy->flags |= WIPHY_FLAG_HAS_CHANNEL_SWITCH;

#ifdef CONFIG_PM_SLEEP
if ((ah->caps.hw_caps & ATH9K_HW_WOW_DEVICE_CAPABLE) &&
diff --git a/drivers/net/wireless/ath/ath9k/main.c b/drivers/net/wireless/ath/ath9k/main.c
index d1ce3da..6844968 100644
--- a/drivers/net/wireless/ath/ath9k/main.c
+++ b/drivers/net/wireless/ath/ath9k/main.c
@@ -1026,6 +1026,9 @@ static void ath9k_remove_interface(struct ieee80211_hw *hw,
if (ath9k_uses_beacons(vif->type))
ath9k_beacon_remove_slot(sc, vif);

+ if (sc->csa_vif == vif)
+ sc->csa_vif = NULL;
+
ath9k_ps_wakeup(sc);
ath9k_calculate_summary_state(hw, NULL);
ath9k_ps_restore(sc);
@@ -2319,6 +2322,19 @@ static void ath9k_sw_scan_complete(struct ieee80211_hw *hw)
clear_bit(SC_OP_SCANNING, &sc->sc_flags);
}

+static void ath9k_channel_switch_beacon(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct cfg80211_chan_def *chandef)
+{
+ struct ath_softc *sc = hw->priv;
+
+ /* mac80211 does not support CSA in multi-if cases (yet) */
+ if (WARN_ON(sc->csa_vif))
+ return;
+
+ sc->csa_vif = vif;
+}
+
struct ieee80211_ops ath9k_ops = {
.tx = ath9k_tx,
.start = ath9k_start,
@@ -2366,4 +2382,5 @@ struct ieee80211_ops ath9k_ops = {
#endif
.sw_scan_start = ath9k_sw_scan_start,
.sw_scan_complete = ath9k_sw_scan_complete,
+ .channel_switch_beacon = ath9k_channel_switch_beacon,
};
diff --git a/drivers/net/wireless/ath/ath9k/xmit.c b/drivers/net/wireless/ath/ath9k/xmit.c
index eab0fcb..cec670d 100644
--- a/drivers/net/wireless/ath/ath9k/xmit.c
+++ b/drivers/net/wireless/ath/ath9k/xmit.c
@@ -2282,6 +2282,8 @@ void ath_tx_edma_tasklet(struct ath_softc *sc)
if (ts.qid == sc->beacon.beaconq) {
sc->beacon.tx_processed = true;
sc->beacon.tx_last = !(ts.ts_status & ATH9K_TXERR_MASK);
+
+ ath9k_csa_is_finished(sc);
continue;
}

--
1.7.10.4


2013-07-09 10:27:56

by Simon Wunderlich

[permalink] [raw]
Subject: Re: [PATCHv3 4/5] mac80211: add channel switch command and beacon callbacks

Hi Michal,

thank you for your feedback!

On Tue, Jul 09, 2013 at 09:17:41AM +0200, Michal Kazior wrote:
> Hi Simon,
>
> On 8 July 2013 15:14, Simon Wunderlich
> <[email protected]> wrote:
> > +static int ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
> > + struct cfg80211_csa_settings *params)
> > +{
> > + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
> > + struct ieee80211_local *local = sdata->local;
> > + struct ieee80211_chanctx_conf *chanctx_conf;
> > + struct ieee80211_chanctx *chanctx;
> > + int err;
> > +
> > + if (!list_empty(&local->roc_list) || local->scanning)
> > + return -EBUSY;
> > +
> > + if (sdata->wdev.cac_started)
> > + return -EBUSY;
> > +
> > + /* don't handle if chanctx is used */
> > + if (local->use_chanctx)
> > + return -EBUSY;
> > +
> > + rcu_read_lock();
> > + chanctx_conf = rcu_dereference(sdata->vif.chanctx_conf);
> > + if (!chanctx_conf) {
> > + rcu_read_unlock();
> > + return -EBUSY;
> > + }
> > +
> > + /* don't handle for multi-VIF cases */
> > + chanctx = container_of(chanctx_conf, struct ieee80211_chanctx, conf);
> > + if (chanctx->refcount > 1) {
> > + rcu_read_unlock();
> > + return -EBUSY;
> > + }
> > + rcu_read_unlock();
>
> I'm wondering if it's a huge hassle to support drivers that depend on
> channel context API? Perhaps local->open_count could be used instead
> of local->use_chantx and chanctx->refcount?

Actually I'm not using any chanctx drivers, and as long as I can't test
I prefer to keep things disabled. :) local->open_count seems doable though,
it should be the same if there is only one channel context. I can change
that ... If you think it is safe I can enable support, nothing will happen
anyway as long as the driver don't set the "CSA supported" wiphy flag ...

>
> I'm also worried this can possibly do silly things if someone starts
> an interface while CSA is under way. Consider the following:
>
> * start AP
> * initiate channel switch
> [ while channel switch is in progress and driver is yet to call
> ieee80211_csa_finish() ]
> * start another STA interface and associate
> [ CSA completes ]
>
> Upon CSA completion the STA will be moved to a different channel
> silently and most likely end up with a beacon loss quickly. I think
> mac80211 should forbid bringing up any new interfaces during CSA.

I'm afraid you are right about that - there should be some check to
prevent other devices coming up during CSA. I'll add something to
prevent that.
>
> It seems there's nothing preventing from multiple calls to channel
> switch callback. Is this expected? Can this work if CSA is invoked
> while other CSA is still in progress?

This should not happen as long as there is only one interface
doing a channel switch. If this is properly checked (need to fix
the point you mentioned above) that should be safe, I think?

Cheers,
Simon

>
>
> Pozdrawiam / Best regards,
> Michał Kazior.


Attachments:
(No filename) (3.12 kB)
signature.asc (198.00 B)
Digital signature
Download all attachments

2013-07-09 14:11:21

by Michal Kazior

[permalink] [raw]
Subject: Re: [PATCHv3 4/5] mac80211: add channel switch command and beacon callbacks

On 9 July 2013 12:27, Simon Wunderlich
<[email protected]> wrote:
> On Tue, Jul 09, 2013 at 09:17:41AM +0200, Michal Kazior wrote:
>> I'm wondering if it's a huge hassle to support drivers that depend on
>> channel context API? Perhaps local->open_count could be used instead
>> of local->use_chantx and chanctx->refcount?
>
> Actually I'm not using any chanctx drivers, and as long as I can't test
> I prefer to keep things disabled. :)

Understandable :) I'm not able to test it now either.

Hmm.. Just as an idea: mac80211_hwsim uses chanctx so it could be
modified to support CSA to test this case.


> local->open_count seems doable though,
> it should be the same if there is only one channel context. I can change
> that ... If you think it is safe I can enable support, nothing will happen
> anyway as long as the driver don't set the "CSA supported" wiphy flag ...

I looked at the code and I think local->open_count won't cut it. It
will fail with AP_VLAN interfaces present (and this isn't a problem
with your current approach). Monitor interfaces are counted in
local->monitors, but AP_VLAN aren't counted at all. mac80211 doesn't
really keep track of the number of interfaces it has reported to a
driver nor the number of software interfaces (that aren't reported to
the driver) unless I missed something.

Another idea is to verify there's exactly 1 chanctx present and it's
refcount is also 1.

CSA + chanctx is just a nice to have. If you're short on resources
it's fine the way it is I think.


>> It seems there's nothing preventing from multiple calls to channel
>> switch callback. Is this expected? Can this work if CSA is invoked
>> while other CSA is still in progress?
>
> This should not happen as long as there is only one interface
> doing a channel switch. If this is properly checked (need to fix
> the point you mentioned above) that should be safe, I think?

Yes it's okay as long as userspace doesn't do something stupid (i.e.
call channel switch more than once quickly). Whether this is a concern
is not for me to decide though.


Pozdrawiam / Best regards,
Michał Kazior.

2013-07-09 07:17:42

by Michal Kazior

[permalink] [raw]
Subject: Re: [PATCHv3 4/5] mac80211: add channel switch command and beacon callbacks

Hi Simon,

On 8 July 2013 15:14, Simon Wunderlich
<[email protected]> wrote:
> +static int ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
> + struct cfg80211_csa_settings *params)
> +{
> + struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
> + struct ieee80211_local *local = sdata->local;
> + struct ieee80211_chanctx_conf *chanctx_conf;
> + struct ieee80211_chanctx *chanctx;
> + int err;
> +
> + if (!list_empty(&local->roc_list) || local->scanning)
> + return -EBUSY;
> +
> + if (sdata->wdev.cac_started)
> + return -EBUSY;
> +
> + /* don't handle if chanctx is used */
> + if (local->use_chanctx)
> + return -EBUSY;
> +
> + rcu_read_lock();
> + chanctx_conf = rcu_dereference(sdata->vif.chanctx_conf);
> + if (!chanctx_conf) {
> + rcu_read_unlock();
> + return -EBUSY;
> + }
> +
> + /* don't handle for multi-VIF cases */
> + chanctx = container_of(chanctx_conf, struct ieee80211_chanctx, conf);
> + if (chanctx->refcount > 1) {
> + rcu_read_unlock();
> + return -EBUSY;
> + }
> + rcu_read_unlock();

I'm wondering if it's a huge hassle to support drivers that depend on
channel context API? Perhaps local->open_count could be used instead
of local->use_chantx and chanctx->refcount?

I'm also worried this can possibly do silly things if someone starts
an interface while CSA is under way. Consider the following:

* start AP
* initiate channel switch
[ while channel switch is in progress and driver is yet to call
ieee80211_csa_finish() ]
* start another STA interface and associate
[ CSA completes ]

Upon CSA completion the STA will be moved to a different channel
silently and most likely end up with a beacon loss quickly. I think
mac80211 should forbid bringing up any new interfaces during CSA.

It seems there's nothing preventing from multiple calls to channel
switch callback. Is this expected? Can this work if CSA is invoked
while other CSA is still in progress?


Pozdrawiam / Best regards,
Micha? Kazior.

2013-07-08 13:14:27

by Simon Wunderlich

[permalink] [raw]
Subject: [PATCHv3 1/5] nl80211/cfg80211: add channel switch command

To allow channel switch announcements within beacons, add
the channel switch command to nl80211/cfg80211. This is
implementation is intended for AP and (later) IBSS mode.

Signed-off-by: Simon Wunderlich <[email protected]>
Signed-off-by: Mathias Kretschmer <[email protected]>
---
Changes to PATCHv2:
* define csa_ies as static to save stack size
* add wiphy flag and cmd for channel_switch
* move NL80211_ATTR_CSA_C_OFF_BEACON into csa_ies

Changes to RFCv1:
* accept and pass CSA IEs and after-change IEs
* use parameter structure instead of individual parameters
---
include/net/cfg80211.h | 30 ++++++++++
include/uapi/linux/nl80211.h | 29 ++++++++++
net/wireless/nl80211.c | 126 +++++++++++++++++++++++++++++++++++++++++-
3 files changed, 184 insertions(+), 1 deletion(-)

diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 4940960..249ac42 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -639,6 +639,30 @@ struct cfg80211_ap_settings {
};

/**
+ * struct cfg80211_csa_settings - channel switch settings
+ *
+ * Used for channel switch
+ *
+ * @chandef: defines the channel to use after the switch
+ * @beacon_csa: beacon data while performing the switch
+ * @counter_offset_beacon: offset for the counter within the beacon (tail)
+ * @counter_offset_presp: offset for the counter within the probe response
+ * @beacon_after: beacon data to be used on the new channel
+ * @radar_required: whether radar detection is required on the new channel
+ * @block_tx: whether transmissions should be blocked while changing
+ * @count: number of beacons until switch
+ */
+struct cfg80211_csa_settings {
+ struct cfg80211_chan_def chandef;
+ struct cfg80211_beacon_data beacon_csa;
+ u16 counter_offset_beacon, counter_offset_presp;
+ struct cfg80211_beacon_data beacon_after;
+ bool radar_required;
+ bool block_tx;
+ u8 count;
+};
+
+/**
* enum station_parameters_apply_mask - station parameter values to apply
* @STATION_PARAM_APPLY_UAPSD: apply new uAPSD parameters (uapsd_queues, max_sp)
* @STATION_PARAM_APPLY_CAPABILITY: apply new capability
@@ -2311,6 +2335,9 @@ struct cfg80211_ops {
u16 duration);
void (*crit_proto_stop)(struct wiphy *wiphy,
struct wireless_dev *wdev);
+ int (*channel_switch)(struct wiphy *wiphy,
+ struct net_device *dev,
+ struct cfg80211_csa_settings *params);
};

/*
@@ -2376,6 +2403,8 @@ struct cfg80211_ops {
* @WIPHY_FLAG_OFFCHAN_TX: Device supports direct off-channel TX.
* @WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL: Device supports remain-on-channel call.
* @WIPHY_FLAG_SUPPORTS_5_10_MHZ: Device supports 5 MHz and 10 MHz channels.
+ * @WIPHY_FLAG_HAS_CHANNEL_SWITCH_BEACON: Device supports channel switch in
+ * beaconing mode (AP, IBSS, Mesh, ...).
*/
enum wiphy_flags {
WIPHY_FLAG_CUSTOM_REGULATORY = BIT(0),
@@ -2400,6 +2429,7 @@ enum wiphy_flags {
WIPHY_FLAG_OFFCHAN_TX = BIT(20),
WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL = BIT(21),
WIPHY_FLAG_SUPPORTS_5_10_MHZ = BIT(22),
+ WIPHY_FLAG_HAS_CHANNEL_SWITCH = BIT(23),
};

/**
diff --git a/include/uapi/linux/nl80211.h b/include/uapi/linux/nl80211.h
index de0ce80..4049cb8 100644
--- a/include/uapi/linux/nl80211.h
+++ b/include/uapi/linux/nl80211.h
@@ -648,6 +648,16 @@
* @NL80211_CMD_CRIT_PROTOCOL_STOP: Indicates the connection reliability can
* return back to normal.
*
+ * @NL80211_CMD_CHANNEL_SWITCH: Perform a channel switch by announcing the
+ * the new channel information (Channel Switch Announcement - CSA)
+ * in the beacon for some time (as defined in the
+ * %NL80211_ATTR_CH_SWITCH_COUNT parameter) and then change to the
+ * new channel. Userspace provides the new channel information (using
+ * %NL80211_ATTR_WIPHY_FREQ and the attributes determining channel
+ * width). %NL80211_ATTR_CH_SWITCH_BLOCK_TX may be supplied to inform
+ * other station that transmission must be blocked until the channel
+ * switch is complete.
+ *
* @NL80211_CMD_MAX: highest used command number
* @__NL80211_CMD_AFTER_LAST: internal use
*/
@@ -810,6 +820,7 @@ enum nl80211_commands {
NL80211_CMD_CRIT_PROTOCOL_START,
NL80211_CMD_CRIT_PROTOCOL_STOP,

+ NL80211_CMD_CHANNEL_SWITCH,
/* add new commands above here */

/* used to define NL80211_CMD_MAX below */
@@ -1436,6 +1447,18 @@ enum nl80211_commands {
* allowed to be used with the first @NL80211_CMD_SET_STATION command to
* update a TDLS peer STA entry.
*
+ * @NL80211_ATTR_CH_SWITCH_COUNT: u32 attribute specifying the number of TBTT's
+ * until the channel switch event.
+ * @NL80211_ATTR_CH_SWITCH_BLOCK_TX: flag attribute specifying that transmission
+ * must be blocked on the current channel (before the channel switch
+ * operation).
+ * @NL80211_ATTR_CSA_IES: Nested set of attributes containing the IE information
+ * for the time while performing a channel switch.
+ * @NL80211_ATTR_CSA_C_OFF_BEACON: Offset of the channel switch counter
+ * field in the beacons tail (%NL80211_ATTR_BEACON_TAIL).
+ * @NL80211_ATTR_CSA_C_OFF_PRESP: Offset of the channel switch counter
+ * field in the probe response (%NL80211_ATTR_PROBE_RESP).
+ *
* @NL80211_ATTR_MAX: highest attribute number currently defined
* @__NL80211_ATTR_AFTER_LAST: internal use
*/
@@ -1736,6 +1759,12 @@ enum nl80211_attrs {

NL80211_ATTR_PEER_AID,

+ NL80211_ATTR_CH_SWITCH_COUNT,
+ NL80211_ATTR_CH_SWITCH_BLOCK_TX,
+ NL80211_ATTR_CSA_IES,
+ NL80211_ATTR_CSA_C_OFF_BEACON,
+ NL80211_ATTR_CSA_C_OFF_PRESP,
+
/* add attributes here, update the policy in nl80211.c */

__NL80211_ATTR_AFTER_LAST,
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index be64cd4..4e86a56 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -349,6 +349,11 @@ static const struct nla_policy nl80211_policy[NL80211_ATTR_MAX+1] = {
[NL80211_ATTR_IE_RIC] = { .type = NLA_BINARY,
.len = IEEE80211_MAX_DATA_LEN },
[NL80211_ATTR_PEER_AID] = { .type = NLA_U16 },
+ [NL80211_ATTR_CH_SWITCH_COUNT] = { .type = NLA_U32 },
+ [NL80211_ATTR_CH_SWITCH_BLOCK_TX] = { .type = NLA_FLAG },
+ [NL80211_ATTR_CSA_IES] = { .type = NLA_NESTED },
+ [NL80211_ATTR_CSA_C_OFF_BEACON] = { .type = NLA_U16 },
+ [NL80211_ATTR_CSA_C_OFF_PRESP] = { .type = NLA_U16 },
};

/* policy for the key attributes */
@@ -1393,6 +1398,8 @@ static int nl80211_send_wiphy(struct cfg80211_registered_device *dev,
if (state->split) {
CMD(crit_proto_start, CRIT_PROTOCOL_START);
CMD(crit_proto_stop, CRIT_PROTOCOL_STOP);
+ if (dev->wiphy.flags & WIPHY_FLAG_HAS_CHANNEL_SWITCH)
+ CMD(channel_switch, CHANNEL_SWITCH);
}

#ifdef CONFIG_NL80211_TESTMODE
@@ -5576,6 +5583,115 @@ static int nl80211_start_radar_detection(struct sk_buff *skb,
return err;
}

+static int nl80211_channel_switch(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct cfg80211_registered_device *rdev = info->user_ptr[0];
+ struct net_device *dev = info->user_ptr[1];
+ struct wireless_dev *wdev = dev->ieee80211_ptr;
+ struct cfg80211_csa_settings params;
+ /* csa_ies is defined static to avoid waste of stack size - this
+ * function is called under RTNL lock, so this should not be a problem.
+ */
+ static struct nlattr *csa_ies[NL80211_ATTR_MAX+1];
+ u8 radar_detect_width = 0;
+ int err;
+
+ if (!rdev->ops->channel_switch ||
+ !(rdev->wiphy.flags & WIPHY_FLAG_HAS_CHANNEL_SWITCH))
+ return -EOPNOTSUPP;
+
+ /* may add IBSS support later */
+ if (dev->ieee80211_ptr->iftype != NL80211_IFTYPE_AP &&
+ dev->ieee80211_ptr->iftype != NL80211_IFTYPE_P2P_GO)
+ return -EOPNOTSUPP;
+
+ memset(&params, 0, sizeof(params));
+
+ if (!info->attrs[NL80211_ATTR_WIPHY_FREQ] ||
+ !info->attrs[NL80211_ATTR_CH_SWITCH_COUNT])
+ return -EINVAL;
+
+ /* only important for AP, IBSS and mesh create IEs internally */
+ if (!info->attrs[NL80211_ATTR_CSA_IES])
+ return -EINVAL;
+
+ /* useless if AP is not running */
+ if (!wdev->beacon_interval)
+ return -EINVAL;
+
+ params.count = nla_get_u32(info->attrs[NL80211_ATTR_CH_SWITCH_COUNT]);
+
+ err = nl80211_parse_beacon(info->attrs, &params.beacon_after);
+ if (err)
+ return err;
+
+ err = nla_parse_nested(csa_ies, NL80211_ATTR_MAX,
+ info->attrs[NL80211_ATTR_CSA_IES],
+ nl80211_policy);
+ if (err)
+ return err;
+
+ err = nl80211_parse_beacon(csa_ies, &params.beacon_csa);
+ if (err)
+ return err;
+
+ if (!csa_ies[NL80211_ATTR_CSA_C_OFF_BEACON])
+ return -EINVAL;
+
+ params.counter_offset_beacon =
+ nla_get_u16(csa_ies[NL80211_ATTR_CSA_C_OFF_BEACON]);
+ if (params.counter_offset_beacon > params.beacon_csa.tail_len)
+ return -EINVAL;
+
+ /* sanity check - counter should be the same this should be the same */
+ if (params.beacon_csa.tail[params.counter_offset_beacon] !=
+ params.count)
+ return -EINVAL;
+
+ if (csa_ies[NL80211_ATTR_CSA_C_OFF_PRESP]) {
+ params.counter_offset_presp =
+ nla_get_u16(csa_ies[NL80211_ATTR_CSA_C_OFF_PRESP]);
+ if (params.counter_offset_presp >
+ params.beacon_csa.probe_resp_len)
+ return -EINVAL;
+
+ if (params.beacon_csa.probe_resp[params.counter_offset_presp] !=
+ params.count)
+ return -EINVAL;
+ }
+
+ err = nl80211_parse_chandef(rdev, info, &params.chandef);
+ if (err)
+ return err;
+
+ if (!cfg80211_reg_can_beacon(&rdev->wiphy, &params.chandef))
+ return -EINVAL;
+
+ err = cfg80211_chandef_dfs_required(wdev->wiphy, &params.chandef);
+ if (err < 0)
+ return err;
+
+ if (err)
+ radar_detect_width = BIT(params.chandef.width);
+ else
+ radar_detect_width = 0;
+
+ 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;
+
+ err = rdev->ops->channel_switch(&rdev->wiphy, dev, &params);
+
+ return err;
+}
+
static int nl80211_send_bss(struct sk_buff *msg, struct netlink_callback *cb,
u32 seq, int flags,
struct cfg80211_registered_device *rdev,
@@ -9048,7 +9164,15 @@ static struct genl_ops nl80211_ops[] = {
.flags = GENL_ADMIN_PERM,
.internal_flags = NL80211_FLAG_NEED_WDEV_UP |
NL80211_FLAG_NEED_RTNL,
- }
+ },
+ {
+ .cmd = NL80211_CMD_CHANNEL_SWITCH,
+ .doit = nl80211_channel_switch,
+ .policy = nl80211_policy,
+ .flags = GENL_ADMIN_PERM,
+ .internal_flags = NL80211_FLAG_NEED_NETDEV_UP |
+ NL80211_FLAG_NEED_RTNL,
+ },
};

static struct genl_multicast_group nl80211_mlme_mcgrp = {
--
1.7.10.4


2013-07-08 13:14:27

by Simon Wunderlich

[permalink] [raw]
Subject: [PATCHv3 4/5] mac80211: add channel switch command and beacon callbacks

The count field in CSA must be decremented with each beacon
transmitted. This patch implements the functionality for drivers
using ieee80211_beacon_get(). Other drivers must call back manually
after reaching count == 0.

This patch also contains the handling and finish worker for the channel
switch command.

Signed-off-by: Simon Wunderlich <[email protected]>
Signed-off-by: Mathias Kretschmer <[email protected]>
---
Changes to PATCHv2:
* fix documentation and style stuff
* update tracing
* change csa_active to bool
* check if csa_active when assigning beacons
* fix locking and access
* use new ieee80211_vif_change_channel to change the channel

Changes to PATCHv1:
* don't switch when CAC is active
* add hw to parameters for driver

Changes to RFCv1:
* use beacons as supplied from nl80211 without generating/parsing them
* update beacons using offsets
* report back by calling the channel switch event in cfg80211
---
include/net/mac80211.h | 34 +++++++++++++
net/mac80211/cfg.c | 116 +++++++++++++++++++++++++++++++++++++++++++-
net/mac80211/driver-ops.h | 13 +++++
net/mac80211/ieee80211_i.h | 13 +++++
net/mac80211/iface.c | 2 +
net/mac80211/trace.h | 26 ++++++++++
net/mac80211/tx.c | 80 ++++++++++++++++++++++++++++++
7 files changed, 282 insertions(+), 2 deletions(-)

diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index aadf4eb..0eb636b 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -1084,6 +1084,7 @@ enum ieee80211_vif_flags {
* @addr: address of this interface
* @p2p: indicates whether this AP or STA interface is a p2p
* interface, i.e. a GO or p2p-sta respectively
+ * @csa_active: marks whether a channel switch is going on
* @driver_flags: flags/capabilities the driver has for this interface,
* these need to be set (or cleared) when the interface is added
* or, if supported by the driver, the interface type is changed
@@ -1106,6 +1107,7 @@ struct ieee80211_vif {
struct ieee80211_bss_conf bss_conf;
u8 addr[ETH_ALEN];
bool p2p;
+ bool csa_active;

u8 cab_queue;
u8 hw_queue[IEEE80211_NUM_ACS];
@@ -2637,6 +2639,16 @@ enum ieee80211_roc_type {
* @ipv6_addr_change: IPv6 address assignment on the given interface changed.
* Currently, this is only called for managed or P2P client interfaces.
* This callback is optional; it must not sleep.
+ *
+ * @channel_switch_beacon: Starts a channel switch to a new channel.
+ * Beacons are modified to include CSA or ECSA IEs before calling this
+ * function. The corresponding count fields in these IEs must be
+ * decremented, and when they reach zero the driver must call
+ * ieee80211_csa_finish(). Drivers which use ieee80211_beacon_get()
+ * get the csa counter decremented by mac80211, but must check if it is
+ * zero using ieee80211_csa_is_complete() after the beacon has been
+ * transmitted and then call ieee80211_csa_finish().
+ *
*/
struct ieee80211_ops {
void (*tx)(struct ieee80211_hw *hw,
@@ -2824,6 +2836,9 @@ struct ieee80211_ops {
struct ieee80211_vif *vif,
struct inet6_dev *idev);
#endif
+ void (*channel_switch_beacon)(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct cfg80211_chan_def *chandef);
};

/**
@@ -3319,6 +3334,25 @@ static inline struct sk_buff *ieee80211_beacon_get(struct ieee80211_hw *hw,
}

/**
+ * ieee80211_csa_finish - notify mac80211 about channel switch
+ * @vif: &struct ieee80211_vif pointer from the add_interface callback.
+ *
+ * After a channel switch announcement was scheduled and the counter in this
+ * announcement hit zero, this function must be called by the driver to
+ * notify mac80211 that the channel can be changed.
+ */
+void ieee80211_csa_finish(struct ieee80211_vif *vif);
+
+/**
+ * ieee80211_csa_is_complete - find out if counters reached zero
+ * @vif: &struct ieee80211_vif pointer from the add_interface callback.
+ *
+ * This function returns whether the channel switch counters reached zero.
+ */
+bool ieee80211_csa_is_complete(struct ieee80211_vif *vif);
+
+
+/**
* ieee80211_proberesp_get - retrieve a Probe Response template
* @hw: pointer obtained from ieee80211_alloc_hw().
* @vif: &struct ieee80211_vif pointer from the add_interface callback.
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index a99c9a8..b32e4db 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -854,8 +854,8 @@ static int ieee80211_set_probe_resp(struct ieee80211_sub_if_data *sdata,
return 0;
}

-static int ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata,
- struct cfg80211_beacon_data *params)
+int ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata,
+ struct cfg80211_beacon_data *params)
{
struct beacon_data *new, *old;
int new_head_len, new_tail_len;
@@ -1018,6 +1018,12 @@ static int ieee80211_change_beacon(struct wiphy *wiphy, struct net_device *dev,

sdata = IEEE80211_DEV_TO_SUB_IF(dev);

+ /* don't allow changing the beacon while CSA is in place - offset
+ * of channel switch counter may change
+ */
+ if (sdata->vif.csa_active)
+ return -EBUSY;
+
old = rtnl_dereference(sdata->u.ap.beacon);
if (!old)
return -ENOENT;
@@ -2840,6 +2846,111 @@ cfg80211_beacon_dup(struct cfg80211_beacon_data *beacon)
return new_beacon;
}

+void ieee80211_csa_finalize_work(struct work_struct *work)
+{
+ struct ieee80211_sub_if_data *sdata =
+ container_of(work, struct ieee80211_sub_if_data,
+ csa_finalize_work);
+ struct ieee80211_local *local = sdata->local;
+ int err, changed;
+
+ if (!ieee80211_sdata_running(sdata))
+ return;
+
+ if (WARN_ON(sdata->vif.type != NL80211_IFTYPE_AP))
+ return;
+
+ sdata->radar_required = sdata->csa_radar_required;
+ err = ieee80211_vif_change_channel(sdata, &local->csa_chandef,
+ &changed);
+ if (WARN_ON(err < 0))
+ return;
+
+ err = ieee80211_assign_beacon(sdata, sdata->u.ap.next_beacon);
+ if (err < 0)
+ return;
+
+ changed |= err;
+ kfree(sdata->u.ap.next_beacon);
+ sdata->u.ap.next_beacon = NULL;
+
+ ieee80211_wake_queues_by_reason(&sdata->local->hw,
+ IEEE80211_MAX_QUEUE_MAP,
+ IEEE80211_QUEUE_STOP_REASON_CSA);
+
+ ieee80211_bss_info_change_notify(sdata, changed);
+
+ cfg80211_ch_switch_notify(sdata->dev, &local->csa_chandef);
+}
+
+static int ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
+ struct cfg80211_csa_settings *params)
+{
+ struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+ struct ieee80211_local *local = sdata->local;
+ struct ieee80211_chanctx_conf *chanctx_conf;
+ struct ieee80211_chanctx *chanctx;
+ int err;
+
+ if (!list_empty(&local->roc_list) || local->scanning)
+ return -EBUSY;
+
+ if (sdata->wdev.cac_started)
+ return -EBUSY;
+
+ /* don't handle if chanctx is used */
+ if (local->use_chanctx)
+ return -EBUSY;
+
+ rcu_read_lock();
+ chanctx_conf = rcu_dereference(sdata->vif.chanctx_conf);
+ if (!chanctx_conf) {
+ rcu_read_unlock();
+ return -EBUSY;
+ }
+
+ /* don't handle for multi-VIF cases */
+ chanctx = container_of(chanctx_conf, struct ieee80211_chanctx, conf);
+ if (chanctx->refcount > 1) {
+ rcu_read_unlock();
+ return -EBUSY;
+ }
+ rcu_read_unlock();
+
+ /* only handle AP for now. */
+ switch (sdata->vif.type) {
+ case NL80211_IFTYPE_AP:
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ sdata->u.ap.next_beacon = cfg80211_beacon_dup(&params->beacon_after);
+ if (!sdata->u.ap.next_beacon)
+ return -ENOMEM;
+
+ sdata->csa_counter_offset_beacon = params->counter_offset_beacon;
+ sdata->csa_counter_offset_presp = params->counter_offset_presp;
+ sdata->csa_radar_required = params->radar_required;
+
+ if (params->block_tx)
+ ieee80211_stop_queues_by_reason(&local->hw,
+ IEEE80211_MAX_QUEUE_MAP,
+ IEEE80211_QUEUE_STOP_REASON_CSA);
+
+ err = ieee80211_assign_beacon(sdata, &params->beacon_csa);
+ if (err < 0)
+ return err;
+
+ local->csa_chandef = params->chandef;
+ sdata->vif.csa_active = true;
+
+ ieee80211_bss_info_change_notify(sdata, err);
+ drv_channel_switch_beacon(sdata, &params->chandef);
+
+ return 0;
+}
+
static int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,
struct ieee80211_channel *chan, bool offchan,
unsigned int wait, const u8 *buf, size_t len,
@@ -3557,4 +3668,5 @@ struct cfg80211_ops mac80211_config_ops = {
.get_et_strings = ieee80211_get_et_strings,
.get_channel = ieee80211_cfg_get_channel,
.start_radar_detection = ieee80211_start_radar_detection,
+ .channel_switch = ieee80211_channel_switch,
};
diff --git a/net/mac80211/driver-ops.h b/net/mac80211/driver-ops.h
index b931c96..b3ea11f 100644
--- a/net/mac80211/driver-ops.h
+++ b/net/mac80211/driver-ops.h
@@ -1072,4 +1072,17 @@ static inline void drv_ipv6_addr_change(struct ieee80211_local *local,
}
#endif

+static inline void
+drv_channel_switch_beacon(struct ieee80211_sub_if_data *sdata,
+ struct cfg80211_chan_def *chandef)
+{
+ struct ieee80211_local *local = sdata->local;
+
+ if (local->ops->channel_switch_beacon) {
+ trace_drv_channel_switch_beacon(local, sdata, chandef);
+ local->ops->channel_switch_beacon(&local->hw, &sdata->vif,
+ chandef);
+ }
+}
+
#endif /* __MAC80211_DRIVER_OPS */
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index e34eb5f..2a22379 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -259,6 +259,8 @@ struct ieee80211_if_ap {
struct beacon_data __rcu *beacon;
struct probe_resp __rcu *probe_resp;

+ /* to be used after channel switch. */
+ struct cfg80211_beacon_data *next_beacon;
struct list_head vlans;

struct ps_data ps;
@@ -716,6 +718,11 @@ struct ieee80211_sub_if_data {

struct ieee80211_tx_queue_params tx_conf[IEEE80211_NUM_ACS];

+ struct work_struct csa_finalize_work;
+ int csa_counter_offset_beacon;
+ int csa_counter_offset_presp;
+ bool csa_radar_required;
+
/* used to reconfigure hardware SM PS */
struct work_struct recalc_smps;

@@ -1344,6 +1351,9 @@ void ieee80211_roc_notify_destroy(struct ieee80211_roc_work *roc, bool free);
void ieee80211_sw_roc_work(struct work_struct *work);
void ieee80211_handle_roc_started(struct ieee80211_roc_work *roc);

+/* channel switch handling */
+void ieee80211_csa_finalize_work(struct work_struct *work);
+
/* interface handling */
int ieee80211_iface_init(void);
void ieee80211_iface_exit(void);
@@ -1365,6 +1375,9 @@ void ieee80211_del_virtual_monitor(struct ieee80211_local *local);

bool __ieee80211_recalc_txpower(struct ieee80211_sub_if_data *sdata);
void ieee80211_recalc_txpower(struct ieee80211_sub_if_data *sdata);
+int ieee80211_assign_beacon(struct ieee80211_sub_if_data *sdata,
+ struct cfg80211_beacon_data *params);
+

static inline bool ieee80211_sdata_running(struct ieee80211_sub_if_data *sdata)
{
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index a2a8250..671fe7c 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -804,6 +804,7 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata,
cancel_work_sync(&local->dynamic_ps_enable_work);

cancel_work_sync(&sdata->recalc_smps);
+ cancel_work_sync(&sdata->csa_finalize_work);

cancel_delayed_work_sync(&sdata->dfs_cac_timer_work);

@@ -1267,6 +1268,7 @@ static void ieee80211_setup_sdata(struct ieee80211_sub_if_data *sdata,
skb_queue_head_init(&sdata->skb_queue);
INIT_WORK(&sdata->work, ieee80211_iface_work);
INIT_WORK(&sdata->recalc_smps, ieee80211_recalc_smps_work);
+ INIT_WORK(&sdata->csa_finalize_work, ieee80211_csa_finalize_work);

switch (type) {
case NL80211_IFTYPE_P2P_GO:
diff --git a/net/mac80211/trace.h b/net/mac80211/trace.h
index c215fafd7..1aba645 100644
--- a/net/mac80211/trace.h
+++ b/net/mac80211/trace.h
@@ -1906,6 +1906,32 @@ TRACE_EVENT(api_radar_detected,
)
);

+TRACE_EVENT(drv_channel_switch_beacon,
+ TP_PROTO(struct ieee80211_local *local,
+ struct ieee80211_sub_if_data *sdata,
+ struct cfg80211_chan_def *chandef),
+
+ TP_ARGS(local, sdata, chandef),
+
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ VIF_ENTRY
+ CHANDEF_ENTRY
+ ),
+
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ VIF_ASSIGN;
+ CHANDEF_ASSIGN(chandef);
+ ),
+
+ TP_printk(
+ LOCAL_PR_FMT VIF_PR_FMT " channel switch to " CHANDEF_PR_FMT,
+ LOCAL_PR_ARG, VIF_PR_ARG, CHANDEF_PR_ARG
+ )
+);
+
+
#ifdef CONFIG_MAC80211_MESSAGE_TRACING
#undef TRACE_SYSTEM
#define TRACE_SYSTEM mac80211_msg
diff --git a/net/mac80211/tx.c b/net/mac80211/tx.c
index 4105d0c..76b2538 100644
--- a/net/mac80211/tx.c
+++ b/net/mac80211/tx.c
@@ -2320,6 +2320,83 @@ static int ieee80211_beacon_add_tim(struct ieee80211_sub_if_data *sdata,
return 0;
}

+void ieee80211_csa_finish(struct ieee80211_vif *vif)
+{
+ struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
+
+ vif->csa_active = false;
+ ieee80211_queue_work(&sdata->local->hw,
+ &sdata->csa_finalize_work);
+}
+EXPORT_SYMBOL(ieee80211_csa_finish);
+
+static void ieee80211_update_csa(struct ieee80211_sub_if_data *sdata,
+ struct beacon_data *beacon)
+{
+ struct probe_resp *resp;
+ int counter_offset_beacon = sdata->csa_counter_offset_beacon;
+ int counter_offset_presp = sdata->csa_counter_offset_presp;
+
+ /* warn if the driver did not check for/react to csa completeness */
+ if (WARN_ON(((u8 *)beacon->tail)[counter_offset_beacon] == 0))
+ return;
+
+ ((u8 *)beacon->tail)[counter_offset_beacon]--;
+
+ if (!counter_offset_presp)
+ return;
+
+ if (sdata->vif.type == NL80211_IFTYPE_AP) {
+ rcu_read_lock();
+ resp = rcu_dereference(sdata->u.ap.probe_resp);
+
+ /* if nl80211 accepted the offset, this should not happen. */
+ if (WARN_ON(!resp)) {
+ rcu_read_unlock();
+ return;
+ }
+ resp->data[counter_offset_presp]--;
+ }
+}
+
+bool ieee80211_csa_is_complete(struct ieee80211_vif *vif)
+{
+ struct ieee80211_sub_if_data *sdata = vif_to_sdata(vif);
+ struct beacon_data *beacon = NULL;
+ u8 *beacon_data;
+ size_t beacon_data_len;
+ int counter_beacon = sdata->csa_counter_offset_beacon;
+ int ret = false;
+
+ if (!ieee80211_sdata_running(sdata))
+ return false;
+
+ rcu_read_lock();
+ if (vif->type == NL80211_IFTYPE_AP) {
+ struct ieee80211_if_ap *ap = &sdata->u.ap;
+
+ beacon = rcu_dereference(ap->beacon);
+ if (WARN_ON(!beacon || !beacon->tail))
+ goto out;
+ beacon_data = beacon->tail;
+ beacon_data_len = beacon->tail_len;
+ } else {
+ WARN_ON(1);
+ goto out;
+ }
+
+ if (WARN_ON(counter_beacon > beacon_data_len))
+ goto out;
+
+ if (beacon_data[counter_beacon] == 0)
+ ret = true;
+ out:
+ rcu_read_unlock();
+
+ return ret;
+}
+EXPORT_SYMBOL(ieee80211_csa_is_complete);
+
struct sk_buff *ieee80211_beacon_get_tim(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
u16 *tim_offset, u16 *tim_length)
@@ -2350,6 +2427,9 @@ struct sk_buff *ieee80211_beacon_get_tim(struct ieee80211_hw *hw,
struct beacon_data *beacon = rcu_dereference(ap->beacon);

if (beacon) {
+ if (sdata->vif.csa_active)
+ ieee80211_update_csa(sdata, beacon);
+
/*
* headroom, head length,
* tail length and maximum TIM length
--
1.7.10.4


2013-07-08 13:14:26

by Simon Wunderlich

[permalink] [raw]
Subject: [PATCHv3 2/5] mac80211: add functions to duplicate a cfg80211_beacon

For the channel switch announcement, it is required to set a new beacon
after the driver arrived on the new channel. The new beacon will be set
by nl80211, but is required to duplicate the beacon as the original
memory within the netlink message will be gone.

Signed-off-by: Simon Wunderlich <[email protected]>
Signed-off-by: Mathias Kretschmer <[email protected]>
---
Changes to PATCHv2:
* allocate only one memory chunk
---
net/mac80211/cfg.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 56 insertions(+)

diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 8184d12..a99c9a8 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -2784,6 +2784,62 @@ static int ieee80211_start_radar_detection(struct wiphy *wiphy,
return 0;
}

+static struct cfg80211_beacon_data *
+cfg80211_beacon_dup(struct cfg80211_beacon_data *beacon)
+{
+ struct cfg80211_beacon_data *new_beacon;
+ u8 *pos;
+ int len;
+
+ len = beacon->head_len + beacon->tail_len + beacon->beacon_ies_len +
+ beacon->proberesp_ies_len + beacon->assocresp_ies_len +
+ beacon->probe_resp_len;
+
+ new_beacon = kzalloc(sizeof(*new_beacon) + len, GFP_KERNEL);
+ if (!new_beacon)
+ return NULL;
+
+ pos = (u8 *)(new_beacon + 1);
+ if (beacon->head_len) {
+ new_beacon->head_len = beacon->head_len;
+ new_beacon->head = pos;
+ memcpy(pos, beacon->head, beacon->head_len);
+ pos += beacon->head_len;
+ }
+ if (beacon->tail_len) {
+ new_beacon->tail_len = beacon->tail_len;
+ new_beacon->tail = pos;
+ memcpy(pos, beacon->tail, beacon->tail_len);
+ pos += beacon->tail_len;
+ }
+ if (beacon->beacon_ies_len) {
+ new_beacon->beacon_ies_len = beacon->beacon_ies_len;
+ new_beacon->beacon_ies = pos;
+ memcpy(pos, beacon->beacon_ies, beacon->beacon_ies_len);
+ pos += beacon->beacon_ies_len;
+ }
+ if (beacon->proberesp_ies_len) {
+ new_beacon->proberesp_ies_len = beacon->proberesp_ies_len;
+ new_beacon->proberesp_ies = pos;
+ memcpy(pos, beacon->proberesp_ies, beacon->proberesp_ies_len);
+ pos += beacon->proberesp_ies_len;
+ }
+ if (beacon->assocresp_ies_len) {
+ new_beacon->assocresp_ies_len = beacon->assocresp_ies_len;
+ new_beacon->assocresp_ies = pos;
+ memcpy(pos, beacon->assocresp_ies, beacon->assocresp_ies_len);
+ pos += beacon->assocresp_ies_len;
+ }
+ if (beacon->probe_resp_len) {
+ new_beacon->probe_resp_len = beacon->probe_resp_len;
+ beacon->probe_resp = pos;
+ memcpy(pos, beacon->probe_resp, beacon->probe_resp_len);
+ pos += beacon->probe_resp_len;
+ }
+
+ return new_beacon;
+}
+
static int ieee80211_mgmt_tx(struct wiphy *wiphy, struct wireless_dev *wdev,
struct ieee80211_channel *chan, bool offchan,
unsigned int wait, const u8 *buf, size_t len,
--
1.7.10.4