2014-09-26 19:35:29

by Luca Coelho

[permalink] [raw]
Subject: [PATCH 0/7] mac80211: some more channel switch work

From: Luciano Coelho <[email protected]>

Hi,

These patches contain some more channel switch work. They have been
in use in our internal tree for some time now and are mostly adding
small details needed by the iwlmvm driver. There's also a nice
improvement to a long-time TODO, where we wait for a beacon on the new
channel before we start transmitting again (in BSS station/P2P client
mode).

One slightly controversial change is the one that allows channel
switches with multiple channel contexts. The problem is not with
mult-context itself, but the handling of drivers that offload the
channel switch and have, so far, not received any information about
the vif that is switching (so the assumption was that all vifs would
have to switch).

Please review.

--
Cheers,
Luca.


Luciano Coelho (7):
nl80211: sanity check the channel switch counter value
mac80211: add device_timestamp to the ieee80211_channel_switch struct
mac80211: add extended channel switching capability if the driver
supports CSA
mac80211: add pre_channel_switch driver operation
mac80211: add post_channel_switch driver operation
mac80211: wait for the first beacon on the new channel after CSA
mac80211: allow channel switch with multiple channel contexts

drivers/net/wireless/iwlegacy/4965-mac.c | 2 +-
drivers/net/wireless/iwlegacy/4965.h | 5 +-
drivers/net/wireless/iwlwifi/dvm/mac80211.c | 1 +
drivers/net/wireless/ti/wlcore/main.c | 23 ++----
include/linux/ieee80211.h | 5 ++
include/net/mac80211.h | 17 +++++
net/mac80211/cfg.c | 18 ++++-
net/mac80211/driver-ops.h | 41 ++++++++++-
net/mac80211/ieee80211_i.h | 5 ++
net/mac80211/iface.c | 2 +
net/mac80211/main.c | 20 ++---
net/mac80211/mlme.c | 109 ++++++++++++++++++----------
net/mac80211/trace.h | 50 ++++++++++++-
net/wireless/nl80211.c | 10 ++-
14 files changed, 236 insertions(+), 72 deletions(-)

--
2.1.0



2014-09-26 19:35:30

by Luca Coelho

[permalink] [raw]
Subject: [PATCH 1/7] nl80211: sanity check the channel switch counter value

From: Luciano Coelho <[email protected]>

The nl80211 channel switch count attribute
(NL80211_ATTR_CH_SWITCH_COUNT) is specified as u32, but the
specification uses u8 for the counter. To make sure strange things
don't happen without informing the user, sanity check the value and
return -EINVAL if it doesn't fit in u8.

Signed-off-by: Luciano Coelho <[email protected]>
---
net/wireless/nl80211.c | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c
index 4cce3e1..9e29053 100644
--- a/net/wireless/nl80211.c
+++ b/net/wireless/nl80211.c
@@ -5927,6 +5927,7 @@ static int nl80211_channel_switch(struct sk_buff *skb, struct genl_info *info)
int err;
bool need_new_beacon = false;
int len, i;
+ u32 cs_count;

if (!rdev->ops->channel_switch ||
!(rdev->wiphy.flags & WIPHY_FLAG_HAS_CHANNEL_SWITCH))
@@ -5963,7 +5964,14 @@ static int nl80211_channel_switch(struct sk_buff *skb, struct genl_info *info)
if (need_new_beacon && !info->attrs[NL80211_ATTR_CSA_IES])
return -EINVAL;

- params.count = nla_get_u32(info->attrs[NL80211_ATTR_CH_SWITCH_COUNT]);
+ /* Even though the attribute is u32, the specification says
+ * u8, so let's make sure we don't overflow.
+ */
+ cs_count = nla_get_u32(info->attrs[NL80211_ATTR_CH_SWITCH_COUNT]);
+ if (cs_count > 255)
+ return -EINVAL;
+
+ params.count = cs_count;

if (!need_new_beacon)
goto skip_beacons;
--
2.1.0


2014-09-26 19:35:29

by Luca Coelho

[permalink] [raw]
Subject: [PATCH 6/7] mac80211: wait for the first beacon on the new channel after CSA

From: Luciano Coelho <[email protected]>

Instead of immediately reopening the queues (in case of block_tx),
calling the post_channel_switch operation and sending the
notification, wait for the first beacon on the new channel. This
makes sure that we don't lose packets if the AP/GO is not on the new
channel yet.

Signed-off-by: Luciano Coelho <[email protected]>
---
net/mac80211/ieee80211_i.h | 2 ++
net/mac80211/iface.c | 2 ++
net/mac80211/mlme.c | 42 ++++++++++++++++++++++++++++++------------
3 files changed, 34 insertions(+), 12 deletions(-)

diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index a9cc491..78d6121 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -434,6 +434,8 @@ struct ieee80211_if_managed {

unsigned int flags;

+ bool csa_waiting_bcn;
+
bool beacon_crc_valid;
u32 beacon_crc;

diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index af23722..e469b33 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -842,6 +842,8 @@ static void ieee80211_do_stop(struct ieee80211_sub_if_data *sdata,
sdata_lock(sdata);
mutex_lock(&local->mtx);
sdata->vif.csa_active = false;
+ if (sdata->vif.type == NL80211_IFTYPE_STATION)
+ sdata->u.mgd.csa_waiting_bcn = false;
if (sdata->csa_block_tx) {
ieee80211_wake_vif_queues(local, sdata,
IEEE80211_QUEUE_STOP_REASON_CSA);
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index 2e29cc3..d996126 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -1001,31 +1001,43 @@ static void ieee80211_chswitch_work(struct work_struct *work)
/* XXX: shouldn't really modify cfg80211-owned data! */
ifmgd->associated->channel = sdata->csa_chandef.chan;

- sdata->vif.csa_active = false;
+ ifmgd->csa_waiting_bcn = true;
+
+ ieee80211_sta_reset_beacon_monitor(sdata);
+ ieee80211_sta_reset_conn_monitor(sdata);
+
+out:
+ mutex_unlock(&local->chanctx_mtx);
+ mutex_unlock(&local->mtx);
+ sdata_unlock(sdata);
+}
+
+static void ieee80211_chswitch_post_beacon(struct ieee80211_sub_if_data *sdata)
+{
+ struct ieee80211_local *local = sdata->local;
+ struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
+ int ret;
+
+ sdata_assert_lock(sdata);

- /* XXX: wait for a beacon first? */
if (sdata->csa_block_tx) {
ieee80211_wake_vif_queues(local, sdata,
IEEE80211_QUEUE_STOP_REASON_CSA);
sdata->csa_block_tx = false;
}

+ sdata->vif.csa_active = false;
+ ifmgd->csa_waiting_bcn = false;
+
ret = drv_post_channel_switch(sdata);
if (ret) {
sdata_info(sdata,
"driver post channel switch failed, disconnecting\n");
ieee80211_queue_work(&local->hw,
&ifmgd->csa_connection_drop_work);
- goto out;
+ return;
}

- ieee80211_sta_reset_beacon_monitor(sdata);
- ieee80211_sta_reset_conn_monitor(sdata);
-
-out:
- mutex_unlock(&local->chanctx_mtx);
- mutex_unlock(&local->mtx);
- sdata_unlock(sdata);
}

void ieee80211_chswitch_done(struct ieee80211_vif *vif, bool success)
@@ -1943,6 +1955,7 @@ static void ieee80211_set_disassoc(struct ieee80211_sub_if_data *sdata,
ieee80211_vif_release_channel(sdata);

sdata->vif.csa_active = false;
+ ifmgd->csa_waiting_bcn = false;
if (sdata->csa_block_tx) {
ieee80211_wake_vif_queues(local, sdata,
IEEE80211_QUEUE_STOP_REASON_CSA);
@@ -2191,6 +2204,7 @@ static void __ieee80211_disconnect(struct ieee80211_sub_if_data *sdata)
true, frame_buf);
mutex_lock(&local->mtx);
sdata->vif.csa_active = false;
+ ifmgd->csa_waiting_bcn = false;
if (sdata->csa_block_tx) {
ieee80211_wake_vif_queues(local, sdata,
IEEE80211_QUEUE_STOP_REASON_CSA);
@@ -3215,6 +3229,9 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_sub_if_data *sdata,
}
}

+ if (ifmgd->csa_waiting_bcn)
+ ieee80211_chswitch_post_beacon(sdata);
+
if (ncrc == ifmgd->beacon_crc && ifmgd->beacon_crc_valid)
return;
ifmgd->beacon_crc = ncrc;
@@ -3687,11 +3704,12 @@ static void ieee80211_sta_bcn_mon_timer(unsigned long data)
struct ieee80211_sub_if_data *sdata =
(struct ieee80211_sub_if_data *) data;
struct ieee80211_local *local = sdata->local;
+ struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;

if (local->quiescing)
return;

- if (sdata->vif.csa_active)
+ if (sdata->vif.csa_active && !ifmgd->csa_waiting_bcn)
return;

sdata->u.mgd.connection_loss = false;
@@ -3709,7 +3727,7 @@ static void ieee80211_sta_conn_mon_timer(unsigned long data)
if (local->quiescing)
return;

- if (sdata->vif.csa_active)
+ if (sdata->vif.csa_active && !ifmgd->csa_waiting_bcn)
return;

ieee80211_queue_work(&local->hw, &ifmgd->monitor_work);
--
2.1.0


2014-09-26 19:35:30

by Luca Coelho

[permalink] [raw]
Subject: [PATCH 4/7] mac80211: add pre_channel_switch driver operation

From: Luciano Coelho <[email protected]>

Some drivers may need to prepare for a channel switch also when it is
initiated from the remote side (eg. station, P2P client). To make
this possible, add a generic callback that can be called for all
interface types.

Signed-off-by: Luciano Coelho <[email protected]>
---
include/net/mac80211.h | 7 +++++++
net/mac80211/cfg.c | 11 +++++++++++
net/mac80211/driver-ops.h | 18 ++++++++++++++++++
net/mac80211/mlme.c | 25 +++++++++++++++++--------
net/mac80211/trace.h | 33 +++++++++++++++++++++++++++++++++
5 files changed, 86 insertions(+), 8 deletions(-)

diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index ec0a5b0..19e4159 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -2832,6 +2832,10 @@ enum ieee80211_roc_type {
* transmitted and then call ieee80211_csa_finish().
* If the CSA count starts as zero or 1, this function will not be called,
* since there won't be any time to beacon before the switch anyway.
+ * @pre_channel_switch: This is an optional callback that is called
+ * before a channel switch procedure is started (ie. when a STA
+ * gets a CSA or an userspace initiated channel-switch), allowing
+ * the driver to prepare for the channel switch.
*
* @join_ibss: Join an IBSS (on an IBSS interface); this is called after all
* information in bss_conf is set up and the beacon can be retrieved. A
@@ -3038,6 +3042,9 @@ struct ieee80211_ops {
void (*channel_switch_beacon)(struct ieee80211_hw *hw,
struct ieee80211_vif *vif,
struct cfg80211_chan_def *chandef);
+ int (*pre_channel_switch)(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
+ struct ieee80211_channel_switch *ch_switch);

int (*join_ibss)(struct ieee80211_hw *hw, struct ieee80211_vif *vif);
void (*leave_ibss)(struct ieee80211_hw *hw, struct ieee80211_vif *vif);
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index fb6a150..2caa7f0 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -3053,6 +3053,7 @@ __ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
{
struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
struct ieee80211_local *local = sdata->local;
+ struct ieee80211_channel_switch ch_switch;
struct ieee80211_chanctx_conf *conf;
struct ieee80211_chanctx *chanctx;
int err, changed = 0;
@@ -3088,6 +3089,10 @@ __ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
goto out;
}

+ err = drv_pre_channel_switch(sdata, &ch_switch);
+ if (err)
+ goto out;
+
err = ieee80211_vif_reserve_chanctx(sdata, &params->chandef,
chanctx->mode,
params->radar_required);
@@ -3101,6 +3106,12 @@ __ieee80211_channel_switch(struct wiphy *wiphy, struct net_device *dev,
goto out;
}

+ ch_switch.timestamp = 0;
+ ch_switch.device_timestamp = 0;
+ ch_switch.block_tx = params->block_tx;
+ ch_switch.chandef = params->chandef;
+ ch_switch.count = params->count;
+
err = ieee80211_set_csa_beacon(sdata, params, &changed);
if (err) {
ieee80211_vif_unreserve_chanctx(sdata);
diff --git a/net/mac80211/driver-ops.h b/net/mac80211/driver-ops.h
index 196d48c..5522672 100644
--- a/net/mac80211/driver-ops.h
+++ b/net/mac80211/driver-ops.h
@@ -1196,6 +1196,24 @@ drv_channel_switch_beacon(struct ieee80211_sub_if_data *sdata,
}
}

+static inline int
+drv_pre_channel_switch(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_channel_switch *ch_switch)
+{
+ struct ieee80211_local *local = sdata->local;
+ int ret = 0;
+
+ if (!check_sdata_in_driver(sdata))
+ return -EIO;
+
+ trace_drv_pre_channel_switch(local, sdata, ch_switch);
+ if (local->ops->pre_channel_switch)
+ ret = local->ops->pre_channel_switch(&local->hw, &sdata->vif,
+ ch_switch);
+ trace_drv_return_int(local, ret);
+ return ret;
+}
+
static inline int drv_join_ibss(struct ieee80211_local *local,
struct ieee80211_sub_if_data *sdata)
{
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index cb4d074..c5fa20f 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -1057,6 +1057,7 @@ ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,
struct ieee80211_chanctx *chanctx;
enum ieee80211_band current_band;
struct ieee80211_csa_ie csa_ie;
+ struct ieee80211_channel_switch ch_switch;
int res;

sdata_assert_lock(sdata);
@@ -1128,6 +1129,22 @@ ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,
}
}

+ ch_switch.timestamp = timestamp;
+ ch_switch.device_timestamp = device_timestamp;
+ ch_switch.block_tx = csa_ie.mode;
+ ch_switch.chandef = csa_ie.chandef;
+ ch_switch.count = csa_ie.count;
+
+ if (drv_pre_channel_switch(sdata, &ch_switch)) {
+ sdata_info(sdata,
+ "preparing for channel switch failed, disconnecting\n");
+ ieee80211_queue_work(&local->hw,
+ &ifmgd->csa_connection_drop_work);
+ mutex_unlock(&local->chanctx_mtx);
+ mutex_unlock(&local->mtx);
+ return;
+ }
+
res = ieee80211_vif_reserve_chanctx(sdata, &csa_ie.chandef,
chanctx->mode, false);
if (res) {
@@ -1153,14 +1170,6 @@ ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,

if (local->ops->channel_switch) {
/* use driver's channel switch callback */
- struct ieee80211_channel_switch ch_switch = {
- .timestamp = timestamp,
- .device_timestamp = device_timestamp,
- .block_tx = csa_ie.mode,
- .chandef = csa_ie.chandef,
- .count = csa_ie.count,
- };
-
drv_channel_switch(local, &ch_switch);
return;
}
diff --git a/net/mac80211/trace.h b/net/mac80211/trace.h
index 853c440..30476d2 100644
--- a/net/mac80211/trace.h
+++ b/net/mac80211/trace.h
@@ -2108,6 +2108,39 @@ TRACE_EVENT(drv_channel_switch_beacon,
)
);

+TRACE_EVENT(drv_pre_channel_switch,
+ TP_PROTO(struct ieee80211_local *local,
+ struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_channel_switch *ch_switch),
+
+ TP_ARGS(local, sdata, ch_switch),
+
+ TP_STRUCT__entry(
+ LOCAL_ENTRY
+ VIF_ENTRY
+ CHANDEF_ENTRY
+ __field(u64, timestamp)
+ __field(bool, block_tx)
+ __field(u8, count)
+ ),
+
+ TP_fast_assign(
+ LOCAL_ASSIGN;
+ VIF_ASSIGN;
+ CHANDEF_ASSIGN(&ch_switch->chandef)
+ __entry->timestamp = ch_switch->timestamp;
+ __entry->block_tx = ch_switch->block_tx;
+ __entry->count = ch_switch->count;
+ ),
+
+ TP_printk(
+ LOCAL_PR_FMT VIF_PR_FMT " prepare channel switch to "
+ CHANDEF_PR_FMT " count:%d block_tx:%d timestamp:%llu",
+ LOCAL_PR_ARG, VIF_PR_ARG, CHANDEF_PR_ARG, __entry->count,
+ __entry->block_tx, __entry->timestamp
+ )
+);
+

#ifdef CONFIG_MAC80211_MESSAGE_TRACING
#undef TRACE_SYSTEM
--
2.1.0


2014-09-26 19:35:29

by Luca Coelho

[permalink] [raw]
Subject: [PATCH 2/7] mac80211: add device_timestamp to the ieee80211_channel_switch struct

From: Luciano Coelho <[email protected]>

Some devices may need the device timestamp in order to synchronize the
channel switch. To pass this value back to the driver, add it to the
channel switch structure and copy the device_timestamp value received
in the rx info structure into it.

Signed-off-by: Luciano Coelho <[email protected]>
---
include/net/mac80211.h | 3 +++
net/mac80211/mlme.c | 15 ++++++++++-----
net/mac80211/trace.h | 2 ++
3 files changed, 15 insertions(+), 5 deletions(-)

diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 0ad1f47..ec0a5b0 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -1117,6 +1117,8 @@ struct ieee80211_conf {
* Function (TSF) timer when the frame containing the channel switch
* announcement was received. This is simply the rx.mactime parameter
* the driver passed into mac80211.
+ * @device_timestamp: arbitrary timestamp for the device, this is the
+ * rx.device_timestamp parameter the driver passed to mac80211.
* @block_tx: Indicates whether transmission must be blocked before the
* scheduled channel switch, as indicated by the AP.
* @chandef: the new channel to switch to
@@ -1124,6 +1126,7 @@ struct ieee80211_conf {
*/
struct ieee80211_channel_switch {
u64 timestamp;
+ u32 device_timestamp;
bool block_tx;
struct cfg80211_chan_def chandef;
u8 count;
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index 9d4ccb2..cb4d074 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -1046,7 +1046,8 @@ static void ieee80211_chswitch_timer(unsigned long data)

static void
ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,
- u64 timestamp, struct ieee802_11_elems *elems,
+ u64 timestamp, u32 device_timestamp,
+ struct ieee802_11_elems *elems,
bool beacon)
{
struct ieee80211_local *local = sdata->local;
@@ -1154,6 +1155,7 @@ ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,
/* use driver's channel switch callback */
struct ieee80211_channel_switch ch_switch = {
.timestamp = timestamp,
+ .device_timestamp = device_timestamp,
.block_tx = csa_ie.mode,
.chandef = csa_ie.chandef,
.count = csa_ie.count,
@@ -3203,6 +3205,7 @@ static void ieee80211_rx_mgmt_beacon(struct ieee80211_sub_if_data *sdata,
ieee80211_rx_bss_info(sdata, mgmt, len, rx_status, &elems);

ieee80211_sta_process_chanswitch(sdata, rx_status->mactime,
+ rx_status->device_timestamp,
&elems, true);

if (!(ifmgd->flags & IEEE80211_STA_DISABLE_WMM) &&
@@ -3334,8 +3337,9 @@ void ieee80211_sta_rx_queued_mgmt(struct ieee80211_sub_if_data *sdata,
break;

ieee80211_sta_process_chanswitch(sdata,
- rx_status->mactime,
- &elems, false);
+ rx_status->mactime,
+ rx_status->device_timestamp,
+ &elems, false);
} else if (mgmt->u.action.category == WLAN_CATEGORY_PUBLIC) {
ies_len = skb->len -
offsetof(struct ieee80211_mgmt,
@@ -3356,8 +3360,9 @@ void ieee80211_sta_rx_queued_mgmt(struct ieee80211_sub_if_data *sdata,
&mgmt->u.action.u.ext_chan_switch.data;

ieee80211_sta_process_chanswitch(sdata,
- rx_status->mactime,
- &elems, false);
+ rx_status->mactime,
+ rx_status->device_timestamp,
+ &elems, false);
}
break;
}
diff --git a/net/mac80211/trace.h b/net/mac80211/trace.h
index 38fae7e..853c440 100644
--- a/net/mac80211/trace.h
+++ b/net/mac80211/trace.h
@@ -995,6 +995,7 @@ TRACE_EVENT(drv_channel_switch,
LOCAL_ENTRY
CHANDEF_ENTRY
__field(u64, timestamp)
+ __field(u32, device_timestamp)
__field(bool, block_tx)
__field(u8, count)
),
@@ -1003,6 +1004,7 @@ TRACE_EVENT(drv_channel_switch,
LOCAL_ASSIGN;
CHANDEF_ASSIGN(&ch_switch->chandef)
__entry->timestamp = ch_switch->timestamp;
+ __entry->device_timestamp = ch_switch->device_timestamp;
__entry->block_tx = ch_switch->block_tx;
__entry->count = ch_switch->count;
),
--
2.1.0


2014-09-26 19:35:29

by Luca Coelho

[permalink] [raw]
Subject: [PATCH 5/7] mac80211: add post_channel_switch driver operation

From: Luciano Coelho <[email protected]>

As a counterpart to the pre_channel_switch operation, add a
post_channel_switch operation. This allows the drivers to go back to
a normal configuration after the channel switch is completed.

Signed-off-by: Luciano Coelho <[email protected]>
---
include/net/mac80211.h | 6 ++++++
net/mac80211/cfg.c | 7 ++++++-
net/mac80211/driver-ops.h | 16 ++++++++++++++++
net/mac80211/mlme.c | 9 +++++++++
net/mac80211/trace.h | 6 ++++++
5 files changed, 43 insertions(+), 1 deletion(-)

diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 19e4159..7861ed8 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -2836,6 +2836,9 @@ enum ieee80211_roc_type {
* before a channel switch procedure is started (ie. when a STA
* gets a CSA or an userspace initiated channel-switch), allowing
* the driver to prepare for the channel switch.
+ * @post_channel_switch: This is an optional callback that is called
+ * after a channel switch procedure is completed, allowing the
+ * driver to go back to a normal configuration.
*
* @join_ibss: Join an IBSS (on an IBSS interface); this is called after all
* information in bss_conf is set up and the beacon can be retrieved. A
@@ -3046,6 +3049,9 @@ struct ieee80211_ops {
struct ieee80211_vif *vif,
struct ieee80211_channel_switch *ch_switch);

+ int (*post_channel_switch)(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif);
+
int (*join_ibss)(struct ieee80211_hw *hw, struct ieee80211_vif *vif);
void (*leave_ibss)(struct ieee80211_hw *hw, struct ieee80211_vif *vif);
u32 (*get_expected_throughput)(struct ieee80211_sta *sta);
diff --git a/net/mac80211/cfg.c b/net/mac80211/cfg.c
index 2caa7f0..ce81ff2 100644
--- a/net/mac80211/cfg.c
+++ b/net/mac80211/cfg.c
@@ -2868,7 +2868,6 @@ static int __ieee80211_csa_finalize(struct ieee80211_sub_if_data *sdata)
return err;

ieee80211_bss_info_change_notify(sdata, changed);
- cfg80211_ch_switch_notify(sdata->dev, &sdata->csa_chandef);

if (sdata->csa_block_tx) {
ieee80211_wake_vif_queues(local, sdata,
@@ -2876,6 +2875,12 @@ static int __ieee80211_csa_finalize(struct ieee80211_sub_if_data *sdata)
sdata->csa_block_tx = false;
}

+ err = drv_post_channel_switch(sdata);
+ if (err)
+ return err;
+
+ cfg80211_ch_switch_notify(sdata->dev, &sdata->csa_chandef);
+
return 0;
}

diff --git a/net/mac80211/driver-ops.h b/net/mac80211/driver-ops.h
index 5522672..0a60906 100644
--- a/net/mac80211/driver-ops.h
+++ b/net/mac80211/driver-ops.h
@@ -1214,6 +1214,22 @@ drv_pre_channel_switch(struct ieee80211_sub_if_data *sdata,
return ret;
}

+static inline int
+drv_post_channel_switch(struct ieee80211_sub_if_data *sdata)
+{
+ struct ieee80211_local *local = sdata->local;
+ int ret = 0;
+
+ if (!check_sdata_in_driver(sdata))
+ return -EIO;
+
+ trace_drv_post_channel_switch(local, sdata);
+ if (local->ops->post_channel_switch)
+ ret = local->ops->post_channel_switch(&local->hw, &sdata->vif);
+ trace_drv_return_int(local, ret);
+ return ret;
+}
+
static inline int drv_join_ibss(struct ieee80211_local *local,
struct ieee80211_sub_if_data *sdata)
{
diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index c5fa20f..2e29cc3 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -1010,6 +1010,15 @@ static void ieee80211_chswitch_work(struct work_struct *work)
sdata->csa_block_tx = false;
}

+ ret = drv_post_channel_switch(sdata);
+ if (ret) {
+ sdata_info(sdata,
+ "driver post channel switch failed, disconnecting\n");
+ ieee80211_queue_work(&local->hw,
+ &ifmgd->csa_connection_drop_work);
+ goto out;
+ }
+
ieee80211_sta_reset_beacon_monitor(sdata);
ieee80211_sta_reset_conn_monitor(sdata);

diff --git a/net/mac80211/trace.h b/net/mac80211/trace.h
index 30476d2..ca0e12d 100644
--- a/net/mac80211/trace.h
+++ b/net/mac80211/trace.h
@@ -2141,6 +2141,12 @@ TRACE_EVENT(drv_pre_channel_switch,
)
);

+DEFINE_EVENT(local_sdata_evt, drv_post_channel_switch,
+ TP_PROTO(struct ieee80211_local *local,
+ struct ieee80211_sub_if_data *sdata),
+ TP_ARGS(local, sdata)
+);
+

#ifdef CONFIG_MAC80211_MESSAGE_TRACING
#undef TRACE_SYSTEM
--
2.1.0


2014-09-29 09:02:15

by Michal Kazior

[permalink] [raw]
Subject: Re: [PATCH 6/7] mac80211: wait for the first beacon on the new channel after CSA

On 26 September 2014 21:35, Luca Coelho <[email protected]> wrote:
> From: Luciano Coelho <[email protected]>
>
> Instead of immediately reopening the queues (in case of block_tx),
> calling the post_channel_switch operation and sending the
> notification, wait for the first beacon on the new channel. This
> makes sure that we don't lose packets if the AP/GO is not on the new
> channel yet.
>
> Signed-off-by: Luciano Coelho <[email protected]>
[...]

Just a few nitpicks from me:

> +static void ieee80211_chswitch_post_beacon(struct ieee80211_sub_if_data *sdata)
> +{
> + struct ieee80211_local *local = sdata->local;
> + struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
> + int ret;
> +
> + sdata_assert_lock(sdata);
>

I was thinking about WARN_ON(!sdata->vif.csa_active) here. csa_active
should be set in all cases if csa_waiting_bcn is set, no?


> - /* XXX: wait for a beacon first? */
> if (sdata->csa_block_tx) {
> ieee80211_wake_vif_queues(local, sdata,
> IEEE80211_QUEUE_STOP_REASON_CSA);
> sdata->csa_block_tx = false;
> }
>
> + sdata->vif.csa_active = false;
> + ifmgd->csa_waiting_bcn = false;
> +
> ret = drv_post_channel_switch(sdata);
> if (ret) {
> sdata_info(sdata,
> "driver post channel switch failed, disconnecting\n");
> ieee80211_queue_work(&local->hw,
> &ifmgd->csa_connection_drop_work);
> - goto out;
> + return;
> }
> <---- here
> - ieee80211_sta_reset_beacon_monitor(sdata);
> - ieee80211_sta_reset_conn_monitor(sdata);
> -
> -out:
> - mutex_unlock(&local->chanctx_mtx);
> - mutex_unlock(&local->mtx);
> - sdata_unlock(sdata);
> }

Is that an empty line I before final `}`?


MichaƂ

2014-09-26 19:35:30

by Luca Coelho

[permalink] [raw]
Subject: [PATCH 7/7] mac80211: allow channel switch with multiple channel contexts

From: Luciano Coelho <[email protected]>

Channel switch with multiple channel contexts should now work fine.
Remove check that disallows switches when multiple contexts are in
use.

Signed-off-by: Luciano Coelho <[email protected]>
---
Note: the driver changes are only compile-tested.
---
drivers/net/wireless/iwlegacy/4965-mac.c | 2 +-
drivers/net/wireless/iwlegacy/4965.h | 5 +++--
drivers/net/wireless/iwlwifi/dvm/mac80211.c | 1 +
drivers/net/wireless/ti/wlcore/main.c | 23 ++++++++---------------
include/net/mac80211.h | 1 +
net/mac80211/driver-ops.h | 7 ++++---
net/mac80211/mlme.c | 26 ++++++++++----------------
net/mac80211/trace.h | 9 ++++++---
8 files changed, 34 insertions(+), 40 deletions(-)

diff --git a/drivers/net/wireless/iwlegacy/4965-mac.c b/drivers/net/wireless/iwlegacy/4965-mac.c
index 9f930a0..42c4e39 100644
--- a/drivers/net/wireless/iwlegacy/4965-mac.c
+++ b/drivers/net/wireless/iwlegacy/4965-mac.c
@@ -6063,7 +6063,7 @@ il4965_mac_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
}

void
-il4965_mac_channel_switch(struct ieee80211_hw *hw,
+il4965_mac_channel_switch(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_channel_switch *ch_switch)
{
struct il_priv *il = hw->priv;
diff --git a/drivers/net/wireless/iwlegacy/4965.h b/drivers/net/wireless/iwlegacy/4965.h
index 337dfcf..3a57f71 100644
--- a/drivers/net/wireless/iwlegacy/4965.h
+++ b/drivers/net/wireless/iwlegacy/4965.h
@@ -187,8 +187,9 @@ int il4965_mac_ampdu_action(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
u8 buf_size);
int il4965_mac_sta_add(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_sta *sta);
-void il4965_mac_channel_switch(struct ieee80211_hw *hw,
- struct ieee80211_channel_switch *ch_switch);
+void
+il4965_mac_channel_switch(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
+ struct ieee80211_channel_switch *ch_switch);

void il4965_led_enable(struct il_priv *il);

diff --git a/drivers/net/wireless/iwlwifi/dvm/mac80211.c b/drivers/net/wireless/iwlwifi/dvm/mac80211.c
index 2364a3c..a967bf8 100644
--- a/drivers/net/wireless/iwlwifi/dvm/mac80211.c
+++ b/drivers/net/wireless/iwlwifi/dvm/mac80211.c
@@ -941,6 +941,7 @@ static int iwlagn_mac_sta_state(struct ieee80211_hw *hw,
}

static void iwlagn_mac_channel_switch(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
struct ieee80211_channel_switch *ch_switch)
{
struct iwl_priv *priv = IWL_MAC80211_GET_DVM(hw);
diff --git a/drivers/net/wireless/ti/wlcore/main.c b/drivers/net/wireless/ti/wlcore/main.c
index 575c8f6..6ad3fce 100644
--- a/drivers/net/wireless/ti/wlcore/main.c
+++ b/drivers/net/wireless/ti/wlcore/main.c
@@ -5177,10 +5177,11 @@ out:
}

static void wl12xx_op_channel_switch(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vif,
struct ieee80211_channel_switch *ch_switch)
{
struct wl1271 *wl = hw->priv;
- struct wl12xx_vif *wlvif;
+ struct wl12xx_vif *wlvif = wl12xx_vif_to_data(vif);
int ret;

wl1271_debug(DEBUG_MAC80211, "mac80211 channel switch");
@@ -5190,14 +5191,8 @@ static void wl12xx_op_channel_switch(struct ieee80211_hw *hw,
mutex_lock(&wl->mutex);

if (unlikely(wl->state == WLCORE_STATE_OFF)) {
- wl12xx_for_each_wlvif_sta(wl, wlvif) {
- struct ieee80211_vif *vif = wl12xx_wlvif_to_vif(wlvif);
-
- if (!test_bit(WLVIF_FLAG_STA_ASSOCIATED, &wlvif->flags))
- continue;
-
+ if (test_bit(WLVIF_FLAG_STA_ASSOCIATED, &wlvif->flags))
ieee80211_chswitch_done(vif, false);
- }
goto out;
} else if (unlikely(wl->state != WLCORE_STATE_ON)) {
goto out;
@@ -5208,11 +5203,9 @@ static void wl12xx_op_channel_switch(struct ieee80211_hw *hw,
goto out;

/* TODO: change mac80211 to pass vif as param */
- wl12xx_for_each_wlvif_sta(wl, wlvif) {
- unsigned long delay_usec;

- if (!test_bit(WLVIF_FLAG_STA_ASSOCIATED, &wlvif->flags))
- continue;
+ if (test_bit(WLVIF_FLAG_STA_ASSOCIATED, &wlvif->flags)) {
+ unsigned long delay_usec;

ret = wl->ops->channel_switch(wl, wlvif, ch_switch);
if (ret)
@@ -5222,10 +5215,10 @@ static void wl12xx_op_channel_switch(struct ieee80211_hw *hw,

/* indicate failure 5 seconds after channel switch time */
delay_usec = ieee80211_tu_to_usec(wlvif->beacon_int) *
- ch_switch->count;
+ ch_switch->count;
ieee80211_queue_delayed_work(hw, &wlvif->channel_switch_work,
- usecs_to_jiffies(delay_usec) +
- msecs_to_jiffies(5000));
+ usecs_to_jiffies(delay_usec) +
+ msecs_to_jiffies(5000));
}

out_sleep:
diff --git a/include/net/mac80211.h b/include/net/mac80211.h
index 7861ed8..1756dbf 100644
--- a/include/net/mac80211.h
+++ b/include/net/mac80211.h
@@ -2969,6 +2969,7 @@ struct ieee80211_ops {
void (*flush)(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
u32 queues, bool drop);
void (*channel_switch)(struct ieee80211_hw *hw,
+ struct ieee80211_vif *vifos,
struct ieee80211_channel_switch *ch_switch);
int (*set_antenna)(struct ieee80211_hw *hw, u32 tx_ant, u32 rx_ant);
int (*get_antenna)(struct ieee80211_hw *hw, u32 *tx_ant, u32 *rx_ant);
diff --git a/net/mac80211/driver-ops.h b/net/mac80211/driver-ops.h
index 0a60906..1bbb079 100644
--- a/net/mac80211/driver-ops.h
+++ b/net/mac80211/driver-ops.h
@@ -764,12 +764,13 @@ static inline void drv_flush(struct ieee80211_local *local,
}

static inline void drv_channel_switch(struct ieee80211_local *local,
- struct ieee80211_channel_switch *ch_switch)
+ struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_channel_switch *ch_switch)
{
might_sleep();

- trace_drv_channel_switch(local, ch_switch);
- local->ops->channel_switch(&local->hw, ch_switch);
+ trace_drv_channel_switch(local, sdata, ch_switch);
+ local->ops->channel_switch(&local->hw, &sdata->vif, ch_switch);
trace_drv_return_void(local);
}

diff --git a/net/mac80211/mlme.c b/net/mac80211/mlme.c
index d996126..c4fef13 100644
--- a/net/mac80211/mlme.c
+++ b/net/mac80211/mlme.c
@@ -1133,21 +1133,15 @@ ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,

chanctx = container_of(conf, struct ieee80211_chanctx, conf);

- if (local->use_chanctx) {
- u32 num_chanctx = 0;
- list_for_each_entry(chanctx, &local->chanctx_list, list)
- 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);
- mutex_unlock(&local->mtx);
- return;
- }
+ if (local->use_chanctx &&
+ !(local->hw.flags & IEEE80211_HW_CHANCTX_STA_CSA)) {
+ sdata_info(sdata,
+ "driver doesn't support chan-switch with channel contexts\n");
+ ieee80211_queue_work(&local->hw,
+ &ifmgd->csa_connection_drop_work);
+ mutex_unlock(&local->chanctx_mtx);
+ mutex_unlock(&local->mtx);
+ return;
}

ch_switch.timestamp = timestamp;
@@ -1191,7 +1185,7 @@ ieee80211_sta_process_chanswitch(struct ieee80211_sub_if_data *sdata,

if (local->ops->channel_switch) {
/* use driver's channel switch callback */
- drv_channel_switch(local, &ch_switch);
+ drv_channel_switch(local, sdata, &ch_switch);
return;
}

diff --git a/net/mac80211/trace.h b/net/mac80211/trace.h
index ca0e12d..976606a 100644
--- a/net/mac80211/trace.h
+++ b/net/mac80211/trace.h
@@ -987,12 +987,14 @@ TRACE_EVENT(drv_flush,

TRACE_EVENT(drv_channel_switch,
TP_PROTO(struct ieee80211_local *local,
+ struct ieee80211_sub_if_data *sdata,
struct ieee80211_channel_switch *ch_switch),

- TP_ARGS(local, ch_switch),
+ TP_ARGS(local, sdata, ch_switch),

TP_STRUCT__entry(
LOCAL_ENTRY
+ VIF_ENTRY
CHANDEF_ENTRY
__field(u64, timestamp)
__field(u32, device_timestamp)
@@ -1002,6 +1004,7 @@ TRACE_EVENT(drv_channel_switch,

TP_fast_assign(
LOCAL_ASSIGN;
+ VIF_ASSIGN;
CHANDEF_ASSIGN(&ch_switch->chandef)
__entry->timestamp = ch_switch->timestamp;
__entry->device_timestamp = ch_switch->device_timestamp;
@@ -1010,8 +1013,8 @@ TRACE_EVENT(drv_channel_switch,
),

TP_printk(
- LOCAL_PR_FMT " new " CHANDEF_PR_FMT " count:%d",
- LOCAL_PR_ARG, CHANDEF_PR_ARG, __entry->count
+ LOCAL_PR_FMT VIF_PR_FMT " new " CHANDEF_PR_FMT " count:%d",
+ LOCAL_PR_ARG, VIF_PR_ARG, CHANDEF_PR_ARG, __entry->count
)
);

--
2.1.0


2014-09-26 19:35:50

by Luca Coelho

[permalink] [raw]
Subject: [PATCH 3/7] mac80211: add extended channel switching capability if the driver supports CSA

From: Luciano Coelho <[email protected]>

The Extended Channel Switching capability bit in the extended
capabilities element must be set if the driver supports CSA on
non-beaconing interfaces.

Since this capability needs to be set during driver registration, the
extended_capabiliities global variable needs to be moved to the local
structure so that it can be modified.

Signed-off-by: Luciano Coelho <[email protected]>
---
include/linux/ieee80211.h | 5 +++++
net/mac80211/ieee80211_i.h | 3 +++
net/mac80211/main.c | 20 +++++++++++---------
3 files changed, 19 insertions(+), 9 deletions(-)

diff --git a/include/linux/ieee80211.h b/include/linux/ieee80211.h
index b1be39c..5fab17b 100644
--- a/include/linux/ieee80211.h
+++ b/include/linux/ieee80211.h
@@ -1998,6 +1998,11 @@ enum ieee80211_tdls_actioncode {
WLAN_TDLS_DISCOVERY_REQUEST = 10,
};

+/* Extended Channel Switching capability to be set in the 1st byte of
+ * the @WLAN_EID_EXT_CAPABILITY information element
+ */
+#define WLAN_EXT_CAPA1_EXT_CHANNEL_SWITCHING BIT(2)
+
/* Interworking capabilities are set in 7th bit of 4th byte of the
* @WLAN_EID_EXT_CAPABILITY information element
*/
diff --git a/net/mac80211/ieee80211_i.h b/net/mac80211/ieee80211_i.h
index c2aaec4..a9cc491 100644
--- a/net/mac80211/ieee80211_i.h
+++ b/net/mac80211/ieee80211_i.h
@@ -1307,6 +1307,9 @@ struct ieee80211_local {
/* virtual monitor interface */
struct ieee80211_sub_if_data __rcu *monitor_sdata;
struct cfg80211_chan_def monitor_chandef;
+
+ /* extended capabilities provided by mac80211 */
+ u8 ext_capa[8];
};

static inline struct ieee80211_sub_if_data *
diff --git a/net/mac80211/main.c b/net/mac80211/main.c
index 0de7c93..107d1c8 100644
--- a/net/mac80211/main.c
+++ b/net/mac80211/main.c
@@ -478,11 +478,6 @@ static const struct ieee80211_vht_cap mac80211_vht_capa_mod_mask = {
},
};

-static const u8 extended_capabilities[] = {
- 0, 0, 0, 0, 0, 0, 0,
- WLAN_EXT_CAPA8_OPMODE_NOTIF,
-};
-
struct ieee80211_hw *ieee80211_alloc_hw(size_t priv_data_len,
const struct ieee80211_ops *ops)
{
@@ -539,10 +534,6 @@ struct ieee80211_hw *ieee80211_alloc_hw(size_t priv_data_len,
WIPHY_FLAG_REPORTS_OBSS |
WIPHY_FLAG_OFFCHAN_TX;

- wiphy->extended_capabilities = extended_capabilities;
- wiphy->extended_capabilities_mask = extended_capabilities;
- wiphy->extended_capabilities_len = ARRAY_SIZE(extended_capabilities);
-
if (ops->remain_on_channel)
wiphy->flags |= WIPHY_FLAG_HAS_REMAIN_ON_CHANNEL;

@@ -591,6 +582,13 @@ struct ieee80211_hw *ieee80211_alloc_hw(size_t priv_data_len,
wiphy->ht_capa_mod_mask = &mac80211_ht_capa_mod_mask;
wiphy->vht_capa_mod_mask = &mac80211_vht_capa_mod_mask;

+ local->ext_capa[7] = WLAN_EXT_CAPA8_OPMODE_NOTIF;
+
+ wiphy->extended_capabilities = local->ext_capa;
+ wiphy->extended_capabilities_mask = local->ext_capa;
+ wiphy->extended_capabilities_len =
+ ARRAY_SIZE(local->ext_capa);
+
INIT_LIST_HEAD(&local->interfaces);

__hw_addr_init(&local->mc_list);
@@ -958,6 +956,10 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
if (local->hw.wiphy->flags & WIPHY_FLAG_SUPPORTS_TDLS)
local->hw.wiphy->flags |= WIPHY_FLAG_TDLS_EXTERNAL_SETUP;

+ /* mac80211 supports eCSA, if the driver supports STA CSA at all */
+ if (local->hw.flags & IEEE80211_HW_CHANCTX_STA_CSA)
+ local->ext_capa[0] |= WLAN_EXT_CAPA1_EXT_CHANNEL_SWITCHING;
+
local->hw.wiphy->max_num_csa_counters = IEEE80211_MAX_CSA_COUNTERS_NUM;

result = wiphy_register(local->hw.wiphy);
--
2.1.0


2014-10-08 06:41:00

by Luca Coelho

[permalink] [raw]
Subject: Re: [PATCH 6/7] mac80211: wait for the first beacon on the new channel after CSA

Sorry for the delay in replying.


On Mon, 2014-09-29 at 11:02 +0200, Michal Kazior wrote:
> On 26 September 2014 21:35, Luca Coelho <[email protected]> wrote:
> > From: Luciano Coelho <[email protected]>
> >
> > Instead of immediately reopening the queues (in case of block_tx),
> > calling the post_channel_switch operation and sending the
> > notification, wait for the first beacon on the new channel. This
> > makes sure that we don't lose packets if the AP/GO is not on the new
> > channel yet.
> >
> > Signed-off-by: Luciano Coelho <[email protected]>
> [...]
>
> Just a few nitpicks from me:
>
> > +static void ieee80211_chswitch_post_beacon(struct ieee80211_sub_if_data *sdata)
> > +{
> > + struct ieee80211_local *local = sdata->local;
> > + struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
> > + int ret;
> > +
> > + sdata_assert_lock(sdata);
> >
>
> I was thinking about WARN_ON(!sdata->vif.csa_active) here. csa_active
> should be set in all cases if csa_waiting_bcn is set, no?

Good idea, csa_active must still be true otherwise we may get into
trouble. I'll add the WARN_ON.


>
> > - /* XXX: wait for a beacon first? */
> > if (sdata->csa_block_tx) {
> > ieee80211_wake_vif_queues(local, sdata,
> > IEEE80211_QUEUE_STOP_REASON_CSA);
> > sdata->csa_block_tx = false;
> > }
> >
> > + sdata->vif.csa_active = false;
> > + ifmgd->csa_waiting_bcn = false;
> > +
> > ret = drv_post_channel_switch(sdata);
> > if (ret) {
> > sdata_info(sdata,
> > "driver post channel switch failed, disconnecting\n");
> > ieee80211_queue_work(&local->hw,
> > &ifmgd->csa_connection_drop_work);
> > - goto out;
> > + return;
> > }
> > <---- here
> > - ieee80211_sta_reset_beacon_monitor(sdata);
> > - ieee80211_sta_reset_conn_monitor(sdata);
> > -
> > -out:
> > - mutex_unlock(&local->chanctx_mtx);
> > - mutex_unlock(&local->mtx);
> > - sdata_unlock(sdata);
> > }
>
> Is that an empty line I before final `}`?

Yep, good catch, I'll fix it.

--
Luca.