* [PATCH 1/4] mac80211: Generalize off-channel operation helpers from scan code
2009-12-04 14:35 [PATCH 0/4] off-channel work Johannes Berg
@ 2009-12-04 14:35 ` Johannes Berg
2009-12-12 15:28 ` [PATCH 1/4 v2] " Johannes Berg
2009-12-04 14:35 ` [PATCH 2/4] cfg80211: add remain-on-channel command Johannes Berg
` (2 subsequent siblings)
3 siblings, 1 reply; 6+ messages in thread
From: Johannes Berg @ 2009-12-04 14:35 UTC (permalink / raw)
To: John Linville; +Cc: linux-wireless, Jouni Malinen, Jouni Malinen
From: Jouni Malinen <jouni.malinen@atheros.com>
The off-channel operations for going into power save mode (station
mode) or stop beaconing (AP/IBSS) are not limited to scanning. Move
these into a separate file and allow them to be used for other
purposes, too.
Signed-off-by: Jouni Malinen <jouni.malinen@atheros.com>
Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
---
net/mac80211/Makefile | 2
net/mac80211/ieee80211_i.h | 6 +
net/mac80211/offchannel.c | 152 +++++++++++++++++++++++++++++++++++++++++++++
net/mac80211/scan.c | 144 +-----------------------------------------
4 files changed, 165 insertions(+), 139 deletions(-)
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ wireless-testing/net/mac80211/offchannel.c 2009-12-04 13:01:32.000000000 +0100
@@ -0,0 +1,152 @@
+/*
+ * Off-channel operation helpers
+ *
+ * Copyright 2003, Jouni Malinen <jkmaline@cc.hut.fi>
+ * Copyright 2004, Instant802 Networks, Inc.
+ * Copyright 2005, Devicescape Software, Inc.
+ * Copyright 2006-2007 Jiri Benc <jbenc@suse.cz>
+ * Copyright 2007, Michael Wu <flamingice@sourmilk.net>
+ * Copyright 2009 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <net/mac80211.h>
+#include "ieee80211_i.h"
+
+/*
+ * inform AP that we will go to sleep so that it will buffer the frames
+ * while we scan
+ */
+static void ieee80211_offchannel_ps_enable(struct ieee80211_sub_if_data *sdata)
+{
+ struct ieee80211_local *local = sdata->local;
+ bool ps = false;
+
+ /* FIXME: what to do when local->pspolling is true? */
+
+ del_timer_sync(&local->dynamic_ps_timer);
+ cancel_work_sync(&local->dynamic_ps_enable_work);
+
+ if (local->hw.conf.flags & IEEE80211_CONF_PS) {
+ ps = true;
+ local->hw.conf.flags &= ~IEEE80211_CONF_PS;
+ ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
+ }
+
+ if (!ps || !(local->hw.flags & IEEE80211_HW_PS_NULLFUNC_STACK))
+ /*
+ * If power save was enabled, no need to send a nullfunc
+ * frame because AP knows that we are sleeping. But if the
+ * hardware is creating the nullfunc frame for power save
+ * status (ie. IEEE80211_HW_PS_NULLFUNC_STACK is not
+ * enabled) and power save was enabled, the firmware just
+ * sent a null frame with power save disabled. So we need
+ * to send a new nullfunc frame to inform the AP that we
+ * are again sleeping.
+ */
+ ieee80211_send_nullfunc(local, sdata, 1);
+}
+
+/* inform AP that we are awake again, unless power save is enabled */
+static void ieee80211_offchannel_ps_disable(struct ieee80211_sub_if_data *sdata)
+{
+ struct ieee80211_local *local = sdata->local;
+
+ if (!local->ps_sdata)
+ ieee80211_send_nullfunc(local, sdata, 0);
+ else {
+ /*
+ * In !IEEE80211_HW_PS_NULLFUNC_STACK case the hardware
+ * will send a nullfunc frame with the powersave bit set
+ * even though the AP already knows that we are sleeping.
+ * This could be avoided by sending a null frame with power
+ * save bit disabled before enabling the power save, but
+ * this doesn't gain anything.
+ *
+ * When IEEE80211_HW_PS_NULLFUNC_STACK is enabled, no need
+ * to send a nullfunc frame because AP already knows that
+ * we are sleeping, let's just enable power save mode in
+ * hardware.
+ */
+ local->hw.conf.flags |= IEEE80211_CONF_PS;
+ ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
+ }
+}
+
+void ieee80211_offchannel_stop_beaconing(struct ieee80211_local *local)
+{
+ struct ieee80211_sub_if_data *sdata;
+
+ mutex_lock(&local->iflist_mtx);
+ list_for_each_entry(sdata, &local->interfaces, list) {
+ if (!ieee80211_sdata_running(sdata))
+ continue;
+
+ /* disable beaconing */
+ if (sdata->vif.type == NL80211_IFTYPE_AP ||
+ sdata->vif.type == NL80211_IFTYPE_ADHOC ||
+ sdata->vif.type == NL80211_IFTYPE_MESH_POINT)
+ ieee80211_bss_info_change_notify(
+ sdata, BSS_CHANGED_BEACON_ENABLED);
+
+ /*
+ * only handle non-STA interfaces here, STA interfaces
+ * are handled in ieee80211_offchannel_stop_station(),
+ * e.g., from the background scan state machine
+ */
+ if (sdata->vif.type != NL80211_IFTYPE_STATION)
+ netif_stop_queue(sdata->dev);
+ }
+ mutex_unlock(&local->iflist_mtx);
+}
+
+void ieee80211_offchannel_stop_station(struct ieee80211_local *local)
+{
+ struct ieee80211_sub_if_data *sdata;
+
+ /*
+ * notify the AP about us leaving the channel and stop all STA interfaces
+ */
+ mutex_lock(&local->iflist_mtx);
+ list_for_each_entry(sdata, &local->interfaces, list) {
+ if (!ieee80211_sdata_running(sdata))
+ continue;
+
+ if (sdata->vif.type == NL80211_IFTYPE_STATION) {
+ netif_stop_queue(sdata->dev);
+ if (sdata->u.mgd.associated)
+ ieee80211_offchannel_ps_enable(sdata);
+ }
+ }
+ mutex_unlock(&local->iflist_mtx);
+}
+
+void ieee80211_offchannel_return(struct ieee80211_local *local,
+ bool enable_beaconing)
+{
+ struct ieee80211_sub_if_data *sdata;
+
+ mutex_lock(&local->iflist_mtx);
+ list_for_each_entry(sdata, &local->interfaces, list) {
+ if (!ieee80211_sdata_running(sdata))
+ continue;
+
+ /* Tell AP we're back */
+ if (sdata->vif.type == NL80211_IFTYPE_STATION) {
+ if (sdata->u.mgd.associated)
+ ieee80211_offchannel_ps_disable(sdata);
+ netif_wake_queue(sdata->dev);
+ }
+
+ /* re-enable beaconing */
+ if (enable_beaconing &&
+ (sdata->vif.type == NL80211_IFTYPE_AP ||
+ sdata->vif.type == NL80211_IFTYPE_ADHOC ||
+ sdata->vif.type == NL80211_IFTYPE_MESH_POINT))
+ ieee80211_bss_info_change_notify(
+ sdata, BSS_CHANGED_BEACON_ENABLED);
+ }
+ mutex_unlock(&local->iflist_mtx);
+}
--- wireless-testing.orig/net/mac80211/ieee80211_i.h 2009-12-04 12:44:37.000000000 +0100
+++ wireless-testing/net/mac80211/ieee80211_i.h 2009-12-04 13:01:32.000000000 +0100
@@ -980,6 +980,12 @@ ieee80211_rx_bss_get(struct ieee80211_lo
void ieee80211_rx_bss_put(struct ieee80211_local *local,
struct ieee80211_bss *bss);
+/* off-channel helpers */
+void ieee80211_offchannel_stop_beaconing(struct ieee80211_local *local);
+void ieee80211_offchannel_stop_station(struct ieee80211_local *local);
+void ieee80211_offchannel_return(struct ieee80211_local *local,
+ bool enable_beaconing);
+
/* interface handling */
int ieee80211_iface_init(void);
void ieee80211_iface_exit(void);
--- wireless-testing.orig/net/mac80211/scan.c 2009-12-04 12:44:37.000000000 +0100
+++ wireless-testing/net/mac80211/scan.c 2009-12-04 13:01:24.000000000 +0100
@@ -227,70 +227,9 @@ static bool ieee80211_prep_hw_scan(struc
return true;
}
-/*
- * inform AP that we will go to sleep so that it will buffer the frames
- * while we scan
- */
-static void ieee80211_scan_ps_enable(struct ieee80211_sub_if_data *sdata)
-{
- struct ieee80211_local *local = sdata->local;
- bool ps = false;
-
- /* FIXME: what to do when local->pspolling is true? */
-
- del_timer_sync(&local->dynamic_ps_timer);
- cancel_work_sync(&local->dynamic_ps_enable_work);
-
- if (local->hw.conf.flags & IEEE80211_CONF_PS) {
- ps = true;
- local->hw.conf.flags &= ~IEEE80211_CONF_PS;
- ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
- }
-
- if (!ps || !(local->hw.flags & IEEE80211_HW_PS_NULLFUNC_STACK))
- /*
- * If power save was enabled, no need to send a nullfunc
- * frame because AP knows that we are sleeping. But if the
- * hardware is creating the nullfunc frame for power save
- * status (ie. IEEE80211_HW_PS_NULLFUNC_STACK is not
- * enabled) and power save was enabled, the firmware just
- * sent a null frame with power save disabled. So we need
- * to send a new nullfunc frame to inform the AP that we
- * are again sleeping.
- */
- ieee80211_send_nullfunc(local, sdata, 1);
-}
-
-/* inform AP that we are awake again, unless power save is enabled */
-static void ieee80211_scan_ps_disable(struct ieee80211_sub_if_data *sdata)
-{
- struct ieee80211_local *local = sdata->local;
-
- if (!local->ps_sdata)
- ieee80211_send_nullfunc(local, sdata, 0);
- else {
- /*
- * In !IEEE80211_HW_PS_NULLFUNC_STACK case the hardware
- * will send a nullfunc frame with the powersave bit set
- * even though the AP already knows that we are sleeping.
- * This could be avoided by sending a null frame with power
- * save bit disabled before enabling the power save, but
- * this doesn't gain anything.
- *
- * When IEEE80211_HW_PS_NULLFUNC_STACK is enabled, no need
- * to send a nullfunc frame because AP already knows that
- * we are sleeping, let's just enable power save mode in
- * hardware.
- */
- local->hw.conf.flags |= IEEE80211_CONF_PS;
- ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
- }
-}
-
void ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted)
{
struct ieee80211_local *local = hw_to_local(hw);
- struct ieee80211_sub_if_data *sdata;
bool was_hw_scan;
mutex_lock(&local->scan_mtx);
@@ -339,28 +278,7 @@ void ieee80211_scan_completed(struct iee
drv_sw_scan_complete(local);
- mutex_lock(&local->iflist_mtx);
- list_for_each_entry(sdata, &local->interfaces, list) {
- if (!ieee80211_sdata_running(sdata))
- continue;
-
- /* Tell AP we're back */
- if (sdata->vif.type == NL80211_IFTYPE_STATION) {
- if (sdata->u.mgd.associated) {
- ieee80211_scan_ps_disable(sdata);
- netif_wake_queue(sdata->dev);
- }
- } else
- netif_wake_queue(sdata->dev);
-
- /* re-enable beaconing */
- if (sdata->vif.type == NL80211_IFTYPE_AP ||
- sdata->vif.type == NL80211_IFTYPE_ADHOC ||
- sdata->vif.type == NL80211_IFTYPE_MESH_POINT)
- ieee80211_bss_info_change_notify(
- sdata, BSS_CHANGED_BEACON_ENABLED);
- }
- mutex_unlock(&local->iflist_mtx);
+ ieee80211_offchannel_return(local, true);
done:
ieee80211_recalc_idle(local);
@@ -372,8 +290,6 @@ EXPORT_SYMBOL(ieee80211_scan_completed);
static int ieee80211_start_sw_scan(struct ieee80211_local *local)
{
- struct ieee80211_sub_if_data *sdata;
-
/*
* Hardware/driver doesn't support hw_scan, so use software
* scanning instead. First send a nullfunc frame with power save
@@ -389,26 +305,7 @@ static int ieee80211_start_sw_scan(struc
*/
drv_sw_scan_start(local);
- mutex_lock(&local->iflist_mtx);
- list_for_each_entry(sdata, &local->interfaces, list) {
- if (!ieee80211_sdata_running(sdata))
- continue;
-
- /* disable beaconing */
- if (sdata->vif.type == NL80211_IFTYPE_AP ||
- sdata->vif.type == NL80211_IFTYPE_ADHOC ||
- sdata->vif.type == NL80211_IFTYPE_MESH_POINT)
- ieee80211_bss_info_change_notify(
- sdata, BSS_CHANGED_BEACON_ENABLED);
-
- /*
- * only handle non-STA interfaces here, STA interfaces
- * are handled in the scan state machine
- */
- if (sdata->vif.type != NL80211_IFTYPE_STATION)
- netif_stop_queue(sdata->dev);
- }
- mutex_unlock(&local->iflist_mtx);
+ ieee80211_offchannel_stop_beaconing(local);
local->next_scan_state = SCAN_DECISION;
local->scan_channel_idx = 0;
@@ -556,23 +453,7 @@ static int ieee80211_scan_state_decision
static void ieee80211_scan_state_leave_oper_channel(struct ieee80211_local *local,
unsigned long *next_delay)
{
- struct ieee80211_sub_if_data *sdata;
-
- /*
- * notify the AP about us leaving the channel and stop all STA interfaces
- */
- mutex_lock(&local->iflist_mtx);
- list_for_each_entry(sdata, &local->interfaces, list) {
- if (!ieee80211_sdata_running(sdata))
- continue;
-
- if (sdata->vif.type == NL80211_IFTYPE_STATION) {
- netif_stop_queue(sdata->dev);
- if (sdata->u.mgd.associated)
- ieee80211_scan_ps_enable(sdata);
- }
- }
- mutex_unlock(&local->iflist_mtx);
+ ieee80211_offchannel_stop_station(local);
__set_bit(SCAN_OFF_CHANNEL, &local->scanning);
@@ -592,28 +473,15 @@ static void ieee80211_scan_state_leave_o
static void ieee80211_scan_state_enter_oper_channel(struct ieee80211_local *local,
unsigned long *next_delay)
{
- struct ieee80211_sub_if_data *sdata = local->scan_sdata;
-
/* switch back to the operating channel */
local->scan_channel = NULL;
ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_CHANNEL);
/*
- * notify the AP about us being back and restart all STA interfaces
+ * Only re-enable station mode interface now; beaconing will be
+ * re-enabled once the full scan has been completed.
*/
- mutex_lock(&local->iflist_mtx);
- list_for_each_entry(sdata, &local->interfaces, list) {
- if (!ieee80211_sdata_running(sdata))
- continue;
-
- /* Tell AP we're back */
- if (sdata->vif.type == NL80211_IFTYPE_STATION) {
- if (sdata->u.mgd.associated)
- ieee80211_scan_ps_disable(sdata);
- netif_wake_queue(sdata->dev);
- }
- }
- mutex_unlock(&local->iflist_mtx);
+ ieee80211_offchannel_return(local, false);
__clear_bit(SCAN_OFF_CHANNEL, &local->scanning);
--- wireless-testing.orig/net/mac80211/Makefile 2009-12-04 12:44:35.000000000 +0100
+++ wireless-testing/net/mac80211/Makefile 2009-12-04 13:01:24.000000000 +0100
@@ -6,7 +6,7 @@ mac80211-y := \
sta_info.o \
wep.o \
wpa.o \
- scan.o \
+ scan.o offchannel.o \
ht.o agg-tx.o agg-rx.o \
ibss.o \
mlme.o work.o \
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH 1/4 v2] mac80211: Generalize off-channel operation helpers from scan code
2009-12-04 14:35 ` [PATCH 1/4] mac80211: Generalize off-channel operation helpers from scan code Johannes Berg
@ 2009-12-12 15:28 ` Johannes Berg
0 siblings, 0 replies; 6+ messages in thread
From: Johannes Berg @ 2009-12-12 15:28 UTC (permalink / raw)
To: John Linville; +Cc: linux-wireless, Jouni Malinen, Jouni Malinen
From: Jouni Malinen <jouni.malinen@atheros.com>
The off-channel operations for going into power save mode (station
mode) or stop beaconing (AP/IBSS) are not limited to scanning. Move
these into a separate file and allow them to be used for other
purposes, too.
Signed-off-by: Jouni Malinen <jouni.malinen@atheros.com>
Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
---
v2: rebase over Vivek's PS fix
net/mac80211/Makefile | 2
net/mac80211/ieee80211_i.h | 8 +-
net/mac80211/offchannel.c | 164 +++++++++++++++++++++++++++++++++++++++++++++
net/mac80211/scan.c | 156 +-----------------------------------------
4 files changed, 178 insertions(+), 152 deletions(-)
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ wireless-testing/net/mac80211/offchannel.c 2009-12-12 16:25:04.000000000 +0100
@@ -0,0 +1,164 @@
+/*
+ * Off-channel operation helpers
+ *
+ * Copyright 2003, Jouni Malinen <jkmaline@cc.hut.fi>
+ * Copyright 2004, Instant802 Networks, Inc.
+ * Copyright 2005, Devicescape Software, Inc.
+ * Copyright 2006-2007 Jiri Benc <jbenc@suse.cz>
+ * Copyright 2007, Michael Wu <flamingice@sourmilk.net>
+ * Copyright 2009 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <net/mac80211.h>
+#include "ieee80211_i.h"
+
+/*
+ * inform AP that we will go to sleep so that it will buffer the frames
+ * while we scan
+ */
+static void ieee80211_offchannel_ps_enable(struct ieee80211_sub_if_data *sdata)
+{
+ struct ieee80211_local *local = sdata->local;
+
+ local->offchannel_ps_enabled = false;
+
+ /* FIXME: what to do when local->pspolling is true? */
+
+ del_timer_sync(&local->dynamic_ps_timer);
+ cancel_work_sync(&local->dynamic_ps_enable_work);
+
+ if (local->hw.conf.flags & IEEE80211_CONF_PS) {
+ local->offchannel_ps_enabled = true;
+ local->hw.conf.flags &= ~IEEE80211_CONF_PS;
+ ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
+ }
+
+ if (!(local->offchannel_ps_enabled) ||
+ !(local->hw.flags & IEEE80211_HW_PS_NULLFUNC_STACK))
+ /*
+ * If power save was enabled, no need to send a nullfunc
+ * frame because AP knows that we are sleeping. But if the
+ * hardware is creating the nullfunc frame for power save
+ * status (ie. IEEE80211_HW_PS_NULLFUNC_STACK is not
+ * enabled) and power save was enabled, the firmware just
+ * sent a null frame with power save disabled. So we need
+ * to send a new nullfunc frame to inform the AP that we
+ * are again sleeping.
+ */
+ ieee80211_send_nullfunc(local, sdata, 1);
+}
+
+/* inform AP that we are awake again, unless power save is enabled */
+static void ieee80211_offchannel_ps_disable(struct ieee80211_sub_if_data *sdata)
+{
+ struct ieee80211_local *local = sdata->local;
+
+ if (!local->ps_sdata)
+ ieee80211_send_nullfunc(local, sdata, 0);
+ else if (local->offchannel_ps_enabled) {
+ /*
+ * In !IEEE80211_HW_PS_NULLFUNC_STACK case the hardware
+ * will send a nullfunc frame with the powersave bit set
+ * even though the AP already knows that we are sleeping.
+ * This could be avoided by sending a null frame with power
+ * save bit disabled before enabling the power save, but
+ * this doesn't gain anything.
+ *
+ * When IEEE80211_HW_PS_NULLFUNC_STACK is enabled, no need
+ * to send a nullfunc frame because AP already knows that
+ * we are sleeping, let's just enable power save mode in
+ * hardware.
+ */
+ local->hw.conf.flags |= IEEE80211_CONF_PS;
+ ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
+ } else if (local->hw.conf.dynamic_ps_timeout > 0) {
+ /*
+ * If IEEE80211_CONF_PS was not set and the dynamic_ps_timer
+ * had been running before leaving the operating channel,
+ * restart the timer now and send a nullfunc frame to inform
+ * the AP that we are awake.
+ */
+ ieee80211_send_nullfunc(local, sdata, 0);
+ mod_timer(&local->dynamic_ps_timer, jiffies +
+ msecs_to_jiffies(local->hw.conf.dynamic_ps_timeout));
+ }
+}
+
+void ieee80211_offchannel_stop_beaconing(struct ieee80211_local *local)
+{
+ struct ieee80211_sub_if_data *sdata;
+
+ mutex_lock(&local->iflist_mtx);
+ list_for_each_entry(sdata, &local->interfaces, list) {
+ if (!ieee80211_sdata_running(sdata))
+ continue;
+
+ /* disable beaconing */
+ if (sdata->vif.type == NL80211_IFTYPE_AP ||
+ sdata->vif.type == NL80211_IFTYPE_ADHOC ||
+ sdata->vif.type == NL80211_IFTYPE_MESH_POINT)
+ ieee80211_bss_info_change_notify(
+ sdata, BSS_CHANGED_BEACON_ENABLED);
+
+ /*
+ * only handle non-STA interfaces here, STA interfaces
+ * are handled in ieee80211_offchannel_stop_station(),
+ * e.g., from the background scan state machine
+ */
+ if (sdata->vif.type != NL80211_IFTYPE_STATION)
+ netif_stop_queue(sdata->dev);
+ }
+ mutex_unlock(&local->iflist_mtx);
+}
+
+void ieee80211_offchannel_stop_station(struct ieee80211_local *local)
+{
+ struct ieee80211_sub_if_data *sdata;
+
+ /*
+ * notify the AP about us leaving the channel and stop all STA interfaces
+ */
+ mutex_lock(&local->iflist_mtx);
+ list_for_each_entry(sdata, &local->interfaces, list) {
+ if (!ieee80211_sdata_running(sdata))
+ continue;
+
+ if (sdata->vif.type == NL80211_IFTYPE_STATION) {
+ netif_stop_queue(sdata->dev);
+ if (sdata->u.mgd.associated)
+ ieee80211_offchannel_ps_enable(sdata);
+ }
+ }
+ mutex_unlock(&local->iflist_mtx);
+}
+
+void ieee80211_offchannel_return(struct ieee80211_local *local,
+ bool enable_beaconing)
+{
+ struct ieee80211_sub_if_data *sdata;
+
+ mutex_lock(&local->iflist_mtx);
+ list_for_each_entry(sdata, &local->interfaces, list) {
+ if (!ieee80211_sdata_running(sdata))
+ continue;
+
+ /* Tell AP we're back */
+ if (sdata->vif.type == NL80211_IFTYPE_STATION) {
+ if (sdata->u.mgd.associated)
+ ieee80211_offchannel_ps_disable(sdata);
+ netif_wake_queue(sdata->dev);
+ }
+
+ /* re-enable beaconing */
+ if (enable_beaconing &&
+ (sdata->vif.type == NL80211_IFTYPE_AP ||
+ sdata->vif.type == NL80211_IFTYPE_ADHOC ||
+ sdata->vif.type == NL80211_IFTYPE_MESH_POINT))
+ ieee80211_bss_info_change_notify(
+ sdata, BSS_CHANGED_BEACON_ENABLED);
+ }
+ mutex_unlock(&local->iflist_mtx);
+}
--- wireless-testing.orig/net/mac80211/ieee80211_i.h 2009-12-12 16:22:03.000000000 +0100
+++ wireless-testing/net/mac80211/ieee80211_i.h 2009-12-12 16:25:10.000000000 +0100
@@ -786,7 +786,7 @@ struct ieee80211_local {
unsigned int wmm_acm; /* bit field of ACM bits (BIT(802.1D tag)) */
bool pspolling;
- bool scan_ps_enabled;
+ bool offchannel_ps_enabled;
/*
* PS can only be enabled when we have exactly one managed
* interface (and monitors) in PS, this then points there.
@@ -981,6 +981,12 @@ ieee80211_rx_bss_get(struct ieee80211_lo
void ieee80211_rx_bss_put(struct ieee80211_local *local,
struct ieee80211_bss *bss);
+/* off-channel helpers */
+void ieee80211_offchannel_stop_beaconing(struct ieee80211_local *local);
+void ieee80211_offchannel_stop_station(struct ieee80211_local *local);
+void ieee80211_offchannel_return(struct ieee80211_local *local,
+ bool enable_beaconing);
+
/* interface handling */
int ieee80211_iface_init(void);
void ieee80211_iface_exit(void);
--- wireless-testing.orig/net/mac80211/scan.c 2009-12-12 16:22:03.000000000 +0100
+++ wireless-testing/net/mac80211/scan.c 2009-12-12 16:26:07.000000000 +0100
@@ -227,82 +227,9 @@ static bool ieee80211_prep_hw_scan(struc
return true;
}
-/*
- * inform AP that we will go to sleep so that it will buffer the frames
- * while we scan
- */
-static void ieee80211_scan_ps_enable(struct ieee80211_sub_if_data *sdata)
-{
- struct ieee80211_local *local = sdata->local;
-
- local->scan_ps_enabled = false;
-
- /* FIXME: what to do when local->pspolling is true? */
-
- del_timer_sync(&local->dynamic_ps_timer);
- cancel_work_sync(&local->dynamic_ps_enable_work);
-
- if (local->hw.conf.flags & IEEE80211_CONF_PS) {
- local->scan_ps_enabled = true;
- local->hw.conf.flags &= ~IEEE80211_CONF_PS;
- ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
- }
-
- if (!(local->scan_ps_enabled) ||
- !(local->hw.flags & IEEE80211_HW_PS_NULLFUNC_STACK))
- /*
- * If power save was enabled, no need to send a nullfunc
- * frame because AP knows that we are sleeping. But if the
- * hardware is creating the nullfunc frame for power save
- * status (ie. IEEE80211_HW_PS_NULLFUNC_STACK is not
- * enabled) and power save was enabled, the firmware just
- * sent a null frame with power save disabled. So we need
- * to send a new nullfunc frame to inform the AP that we
- * are again sleeping.
- */
- ieee80211_send_nullfunc(local, sdata, 1);
-}
-
-/* inform AP that we are awake again, unless power save is enabled */
-static void ieee80211_scan_ps_disable(struct ieee80211_sub_if_data *sdata)
-{
- struct ieee80211_local *local = sdata->local;
-
- if (!local->ps_sdata)
- ieee80211_send_nullfunc(local, sdata, 0);
- else if (local->scan_ps_enabled) {
- /*
- * In !IEEE80211_HW_PS_NULLFUNC_STACK case the hardware
- * will send a nullfunc frame with the powersave bit set
- * even though the AP already knows that we are sleeping.
- * This could be avoided by sending a null frame with power
- * save bit disabled before enabling the power save, but
- * this doesn't gain anything.
- *
- * When IEEE80211_HW_PS_NULLFUNC_STACK is enabled, no need
- * to send a nullfunc frame because AP already knows that
- * we are sleeping, let's just enable power save mode in
- * hardware.
- */
- local->hw.conf.flags |= IEEE80211_CONF_PS;
- ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_PS);
- } else if (local->hw.conf.dynamic_ps_timeout > 0) {
- /*
- * If IEEE80211_CONF_PS was not set and the dynamic_ps_timer
- * had been running before leaving the operating channel,
- * restart the timer now and send a nullfunc frame to inform
- * the AP that we are awake.
- */
- ieee80211_send_nullfunc(local, sdata, 0);
- mod_timer(&local->dynamic_ps_timer, jiffies +
- msecs_to_jiffies(local->hw.conf.dynamic_ps_timeout));
- }
-}
-
void ieee80211_scan_completed(struct ieee80211_hw *hw, bool aborted)
{
struct ieee80211_local *local = hw_to_local(hw);
- struct ieee80211_sub_if_data *sdata;
bool was_hw_scan;
mutex_lock(&local->scan_mtx);
@@ -351,28 +278,7 @@ void ieee80211_scan_completed(struct iee
drv_sw_scan_complete(local);
- mutex_lock(&local->iflist_mtx);
- list_for_each_entry(sdata, &local->interfaces, list) {
- if (!ieee80211_sdata_running(sdata))
- continue;
-
- /* Tell AP we're back */
- if (sdata->vif.type == NL80211_IFTYPE_STATION) {
- if (sdata->u.mgd.associated) {
- ieee80211_scan_ps_disable(sdata);
- netif_wake_queue(sdata->dev);
- }
- } else
- netif_wake_queue(sdata->dev);
-
- /* re-enable beaconing */
- if (sdata->vif.type == NL80211_IFTYPE_AP ||
- sdata->vif.type == NL80211_IFTYPE_ADHOC ||
- sdata->vif.type == NL80211_IFTYPE_MESH_POINT)
- ieee80211_bss_info_change_notify(
- sdata, BSS_CHANGED_BEACON_ENABLED);
- }
- mutex_unlock(&local->iflist_mtx);
+ ieee80211_offchannel_return(local, true);
done:
ieee80211_recalc_idle(local);
@@ -384,8 +290,6 @@ EXPORT_SYMBOL(ieee80211_scan_completed);
static int ieee80211_start_sw_scan(struct ieee80211_local *local)
{
- struct ieee80211_sub_if_data *sdata;
-
/*
* Hardware/driver doesn't support hw_scan, so use software
* scanning instead. First send a nullfunc frame with power save
@@ -401,26 +305,7 @@ static int ieee80211_start_sw_scan(struc
*/
drv_sw_scan_start(local);
- mutex_lock(&local->iflist_mtx);
- list_for_each_entry(sdata, &local->interfaces, list) {
- if (!ieee80211_sdata_running(sdata))
- continue;
-
- /* disable beaconing */
- if (sdata->vif.type == NL80211_IFTYPE_AP ||
- sdata->vif.type == NL80211_IFTYPE_ADHOC ||
- sdata->vif.type == NL80211_IFTYPE_MESH_POINT)
- ieee80211_bss_info_change_notify(
- sdata, BSS_CHANGED_BEACON_ENABLED);
-
- /*
- * only handle non-STA interfaces here, STA interfaces
- * are handled in the scan state machine
- */
- if (sdata->vif.type != NL80211_IFTYPE_STATION)
- netif_stop_queue(sdata->dev);
- }
- mutex_unlock(&local->iflist_mtx);
+ ieee80211_offchannel_stop_beaconing(local);
local->next_scan_state = SCAN_DECISION;
local->scan_channel_idx = 0;
@@ -568,23 +453,7 @@ static int ieee80211_scan_state_decision
static void ieee80211_scan_state_leave_oper_channel(struct ieee80211_local *local,
unsigned long *next_delay)
{
- struct ieee80211_sub_if_data *sdata;
-
- /*
- * notify the AP about us leaving the channel and stop all STA interfaces
- */
- mutex_lock(&local->iflist_mtx);
- list_for_each_entry(sdata, &local->interfaces, list) {
- if (!ieee80211_sdata_running(sdata))
- continue;
-
- if (sdata->vif.type == NL80211_IFTYPE_STATION) {
- netif_stop_queue(sdata->dev);
- if (sdata->u.mgd.associated)
- ieee80211_scan_ps_enable(sdata);
- }
- }
- mutex_unlock(&local->iflist_mtx);
+ ieee80211_offchannel_stop_station(local);
__set_bit(SCAN_OFF_CHANNEL, &local->scanning);
@@ -604,28 +473,15 @@ static void ieee80211_scan_state_leave_o
static void ieee80211_scan_state_enter_oper_channel(struct ieee80211_local *local,
unsigned long *next_delay)
{
- struct ieee80211_sub_if_data *sdata = local->scan_sdata;
-
/* switch back to the operating channel */
local->scan_channel = NULL;
ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_CHANNEL);
/*
- * notify the AP about us being back and restart all STA interfaces
+ * Only re-enable station mode interface now; beaconing will be
+ * re-enabled once the full scan has been completed.
*/
- mutex_lock(&local->iflist_mtx);
- list_for_each_entry(sdata, &local->interfaces, list) {
- if (!ieee80211_sdata_running(sdata))
- continue;
-
- /* Tell AP we're back */
- if (sdata->vif.type == NL80211_IFTYPE_STATION) {
- if (sdata->u.mgd.associated)
- ieee80211_scan_ps_disable(sdata);
- netif_wake_queue(sdata->dev);
- }
- }
- mutex_unlock(&local->iflist_mtx);
+ ieee80211_offchannel_return(local, false);
__clear_bit(SCAN_OFF_CHANNEL, &local->scanning);
--- wireless-testing.orig/net/mac80211/Makefile 2009-12-12 16:21:58.000000000 +0100
+++ wireless-testing/net/mac80211/Makefile 2009-12-12 16:23:57.000000000 +0100
@@ -6,7 +6,7 @@ mac80211-y := \
sta_info.o \
wep.o \
wpa.o \
- scan.o \
+ scan.o offchannel.o \
ht.o agg-tx.o agg-rx.o \
ibss.o \
mlme.o work.o \
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH 2/4] cfg80211: add remain-on-channel command
2009-12-04 14:35 [PATCH 0/4] off-channel work Johannes Berg
2009-12-04 14:35 ` [PATCH 1/4] mac80211: Generalize off-channel operation helpers from scan code Johannes Berg
@ 2009-12-04 14:35 ` Johannes Berg
2009-12-04 14:35 ` [PATCH 3/4] mac80211: support " Johannes Berg
2009-12-04 14:35 ` [PATCH 4/4] mac80211: make off-channel work generic Johannes Berg
3 siblings, 0 replies; 6+ messages in thread
From: Johannes Berg @ 2009-12-04 14:35 UTC (permalink / raw)
To: John Linville; +Cc: linux-wireless, Jouni Malinen, Jouni Malinen
From: Jouni Malinen <jouni.malinen@atheros.com>
Add new commands for requesting the driver to remain awake
on a specified channel for the specified amount of time
(and another command to cancel such an operation). This
can be used to implement userspace-controlled off-channel
operations, like Public Action frame exchange on another
channel than the operation channel.
The off-channel operation should behave similarly to scan,
i.e. the local station (if associated) moves into power
save mode to request the AP to buffer frames for it and
then moves to the other channel to allow the off-channel
operation to be completed. The duration parameter can be
used to request enough time to receive a response from
the target station.
Signed-off-by: Jouni Malinen <jouni.malinen@atheros.com>
Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
---
include/linux/nl80211.h | 36 +++++++
include/net/cfg80211.h | 47 ++++++++++
net/wireless/chan.c | 41 +++++----
net/wireless/core.h | 3
net/wireless/mlme.c | 27 +++++
net/wireless/nl80211.c | 218 +++++++++++++++++++++++++++++++++++++++++++++++-
net/wireless/nl80211.h | 11 ++
7 files changed, 368 insertions(+), 15 deletions(-)
--- wireless-testing.orig/include/linux/nl80211.h 2009-12-04 09:57:31.000000000 +0100
+++ wireless-testing/include/linux/nl80211.h 2009-12-04 10:06:13.000000000 +0100
@@ -270,6 +270,31 @@
* @NL80211_CMD_SET_WIPHY_NETNS: Set a wiphy's netns. Note that all devices
* associated with this wiphy must be down and will follow.
*
+ * @NL80211_CMD_REMAIN_ON_CHANNEL: Request to remain awake on the specified
+ * channel for the specified amount of time. This can be used to do
+ * off-channel operations like transmit a Public Action frame and wait for
+ * a response while being associated to an AP on another channel.
+ * %NL80211_ATTR_WIPHY or %NL80211_ATTR_IFINDEX is used to specify which
+ * radio is used. %NL80211_ATTR_WIPHY_FREQ is used to specify the
+ * frequency for the operation and %NL80211_ATTR_WIPHY_CHANNEL_TYPE may be
+ * optionally used to specify additional channel parameters.
+ * %NL80211_ATTR_DURATION is used to specify the duration in milliseconds
+ * to remain on the channel. This command is also used as an event to
+ * notify when the requested duration starts (it may take a while for the
+ * driver to schedule this time due to other concurrent needs for the
+ * radio).
+ * When called, this operation returns a cookie (%NL80211_ATTR_COOKIE)
+ * that will be included with any events pertaining to this request;
+ * the cookie is also used to cancel the request.
+ * @NL80211_CMD_CANCEL_REMAIN_ON_CHANNEL: This command can be used to cancel a
+ * pending remain-on-channel duration if the desired operation has been
+ * completed prior to expiration of the originally requested duration.
+ * %NL80211_ATTR_WIPHY or %NL80211_ATTR_IFINDEX is used to specify the
+ * radio. The %NL80211_ATTR_COOKIE attribute must be given as well to
+ * uniquely identify the request.
+ * This command is also used as an event to notify when a requested
+ * remain-on-channel duration has expired.
+ *
* @NL80211_CMD_MAX: highest used command number
* @__NL80211_CMD_AFTER_LAST: internal use
*/
@@ -353,6 +378,9 @@ enum nl80211_commands {
NL80211_CMD_DEL_PMKSA,
NL80211_CMD_FLUSH_PMKSA,
+ NL80211_CMD_REMAIN_ON_CHANNEL,
+ NL80211_CMD_CANCEL_REMAIN_ON_CHANNEL,
+
/* add new commands above here */
/* used to define NL80211_CMD_MAX below */
@@ -606,6 +634,10 @@ enum nl80211_commands {
* @NL80211_ATTR_MAX_NUM_PMKIDS: maximum number of PMKIDs a firmware can
* cache, a wiphy attribute.
*
+ * @NL80211_ATTR_DURATION: Duration of an operation in milliseconds, u32.
+ *
+ * @NL80211_ATTR_COOKIE: Generic 64-bit cookie to identify objects.
+ *
* @NL80211_ATTR_MAX: highest attribute number currently defined
* @__NL80211_ATTR_AFTER_LAST: internal use
*/
@@ -743,6 +775,10 @@ enum nl80211_attrs {
NL80211_ATTR_PMKID,
NL80211_ATTR_MAX_NUM_PMKIDS,
+ NL80211_ATTR_DURATION,
+
+ NL80211_ATTR_COOKIE,
+
/* add attributes here, update the policy in nl80211.c */
__NL80211_ATTR_AFTER_LAST,
--- wireless-testing.orig/net/wireless/core.h 2009-12-04 09:57:31.000000000 +0100
+++ wireless-testing/net/wireless/core.h 2009-12-04 09:57:35.000000000 +0100
@@ -374,6 +374,9 @@ void cfg80211_process_rdev_events(struct
struct ieee80211_channel *
rdev_fixed_channel(struct cfg80211_registered_device *rdev,
struct wireless_dev *for_wdev);
+struct ieee80211_channel *
+rdev_freq_to_chan(struct cfg80211_registered_device *rdev,
+ int freq, enum nl80211_channel_type channel_type);
int rdev_set_freq(struct cfg80211_registered_device *rdev,
struct wireless_dev *for_wdev,
int freq, enum nl80211_channel_type channel_type);
--- wireless-testing.orig/net/wireless/chan.c 2009-12-04 09:57:31.000000000 +0100
+++ wireless-testing/net/wireless/chan.c 2009-12-04 09:57:35.000000000 +0100
@@ -41,44 +41,57 @@ rdev_fixed_channel(struct cfg80211_regis
return result;
}
-int rdev_set_freq(struct cfg80211_registered_device *rdev,
- struct wireless_dev *for_wdev,
+struct ieee80211_channel *
+rdev_freq_to_chan(struct cfg80211_registered_device *rdev,
int freq, enum nl80211_channel_type channel_type)
{
struct ieee80211_channel *chan;
struct ieee80211_sta_ht_cap *ht_cap;
- int result;
-
- if (rdev_fixed_channel(rdev, for_wdev))
- return -EBUSY;
-
- if (!rdev->ops->set_channel)
- return -EOPNOTSUPP;
chan = ieee80211_get_channel(&rdev->wiphy, freq);
/* Primary channel not allowed */
if (!chan || chan->flags & IEEE80211_CHAN_DISABLED)
- return -EINVAL;
+ return NULL;
if (channel_type == NL80211_CHAN_HT40MINUS &&
chan->flags & IEEE80211_CHAN_NO_HT40MINUS)
- return -EINVAL;
+ return NULL;
else if (channel_type == NL80211_CHAN_HT40PLUS &&
chan->flags & IEEE80211_CHAN_NO_HT40PLUS)
- return -EINVAL;
+ return NULL;
ht_cap = &rdev->wiphy.bands[chan->band]->ht_cap;
if (channel_type != NL80211_CHAN_NO_HT) {
if (!ht_cap->ht_supported)
- return -EINVAL;
+ return NULL;
if (!(ht_cap->cap & IEEE80211_HT_CAP_SUP_WIDTH_20_40) ||
ht_cap->cap & IEEE80211_HT_CAP_40MHZ_INTOLERANT)
- return -EINVAL;
+ return NULL;
}
+ return chan;
+}
+
+int rdev_set_freq(struct cfg80211_registered_device *rdev,
+ struct wireless_dev *for_wdev,
+ int freq, enum nl80211_channel_type channel_type)
+{
+ struct ieee80211_channel *chan;
+ int result;
+
+ if (rdev_fixed_channel(rdev, for_wdev))
+ return -EBUSY;
+
+ if (!rdev->ops->set_channel)
+ return -EOPNOTSUPP;
+
+ chan = rdev_freq_to_chan(rdev, freq, channel_type);
+ if (!chan)
+ return -EINVAL;
+
result = rdev->ops->set_channel(&rdev->wiphy, chan, channel_type);
if (result)
return result;
--- wireless-testing.orig/include/net/cfg80211.h 2009-12-04 09:57:31.000000000 +0100
+++ wireless-testing/include/net/cfg80211.h 2009-12-04 10:21:37.000000000 +0100
@@ -988,6 +988,15 @@ struct cfg80211_pmksa {
*
* @dump_survey: get site survey information.
*
+ * @remain_on_channel: Request the driver to remain awake on the specified
+ * channel for the specified duration to complete an off-channel
+ * operation (e.g., public action frame exchange). When the driver is
+ * ready on the requested channel, it must indicate this with an event
+ * notification by calling cfg80211_ready_on_channel().
+ * @cancel_remain_on_channel: Cancel an on-going remain-on-channel operation.
+ * This allows the operation to be terminated prior to timeout based on
+ * the duration value.
+ *
* @testmode_cmd: run a test mode command
*
* @set_pmksa: Cache a PMKID for a BSSID. This is mostly useful for fullmac
@@ -1123,6 +1132,16 @@ struct cfg80211_ops {
struct cfg80211_pmksa *pmksa);
int (*flush_pmksa)(struct wiphy *wiphy, struct net_device *netdev);
+ int (*remain_on_channel)(struct wiphy *wiphy,
+ struct net_device *dev,
+ struct ieee80211_channel *chan,
+ enum nl80211_channel_type channel_type,
+ unsigned int duration,
+ u64 *cookie);
+ int (*cancel_remain_on_channel)(struct wiphy *wiphy,
+ struct net_device *dev,
+ u64 cookie);
+
/* some temporary stuff to finish wext */
int (*set_power_mgmt)(struct wiphy *wiphy, struct net_device *dev,
bool enabled, int timeout);
@@ -2129,5 +2148,33 @@ void cfg80211_roamed(struct net_device *
void cfg80211_disconnected(struct net_device *dev, u16 reason,
u8 *ie, size_t ie_len, gfp_t gfp);
+/**
+ * cfg80211_ready_on_channel - notification of remain_on_channel start
+ * @dev: network device
+ * @cookie: the request cookie
+ * @chan: The current channel (from remain_on_channel request)
+ * @channel_type: Channel type
+ * @duration: Duration in milliseconds that the driver intents to remain on the
+ * channel
+ * @gfp: allocation flags
+ */
+void cfg80211_ready_on_channel(struct net_device *dev, u64 cookie,
+ struct ieee80211_channel *chan,
+ enum nl80211_channel_type channel_type,
+ unsigned int duration, gfp_t gfp);
+
+/**
+ * cfg80211_remain_on_channel_expired - remain_on_channel duration expired
+ * @dev: network device
+ * @cookie: the request cookie
+ * @chan: The current channel (from remain_on_channel request)
+ * @channel_type: Channel type
+ * @gfp: allocation flags
+ */
+void cfg80211_remain_on_channel_expired(struct net_device *dev,
+ u64 cookie,
+ struct ieee80211_channel *chan,
+ enum nl80211_channel_type channel_type,
+ gfp_t gfp);
#endif /* __NET_CFG80211_H */
--- wireless-testing.orig/net/wireless/mlme.c 2009-12-04 09:57:31.000000000 +0100
+++ wireless-testing/net/wireless/mlme.c 2009-12-04 10:19:23.000000000 +0100
@@ -680,3 +680,30 @@ void cfg80211_mlme_down(struct cfg80211_
}
}
}
+
+void cfg80211_ready_on_channel(struct net_device *dev, u64 cookie,
+ struct ieee80211_channel *chan,
+ enum nl80211_channel_type channel_type,
+ unsigned int duration, gfp_t gfp)
+{
+ struct wiphy *wiphy = dev->ieee80211_ptr->wiphy;
+ struct cfg80211_registered_device *rdev = wiphy_to_dev(wiphy);
+
+ nl80211_send_remain_on_channel(rdev, dev, cookie, chan, channel_type,
+ duration, gfp);
+}
+EXPORT_SYMBOL(cfg80211_ready_on_channel);
+
+void cfg80211_remain_on_channel_expired(struct net_device *dev,
+ u64 cookie,
+ struct ieee80211_channel *chan,
+ enum nl80211_channel_type channel_type,
+ gfp_t gfp)
+{
+ struct wiphy *wiphy = dev->ieee80211_ptr->wiphy;
+ struct cfg80211_registered_device *rdev = wiphy_to_dev(wiphy);
+
+ nl80211_send_remain_on_channel_cancel(rdev, dev, cookie, chan,
+ channel_type, gfp);
+}
+EXPORT_SYMBOL(cfg80211_remain_on_channel_expired);
--- wireless-testing.orig/net/wireless/nl80211.c 2009-12-04 09:57:31.000000000 +0100
+++ wireless-testing/net/wireless/nl80211.c 2009-12-04 10:37:25.000000000 +0100
@@ -141,6 +141,8 @@ static struct nla_policy nl80211_policy[
[NL80211_ATTR_4ADDR] = { .type = NLA_U8 },
[NL80211_ATTR_PMKID] = { .type = NLA_BINARY,
.len = WLAN_PMKID_LEN },
+ [NL80211_ATTR_DURATION] = { .type = NLA_U32 },
+ [NL80211_ATTR_COOKIE] = { .type = NLA_U64 },
};
/* policy for the attributes */
@@ -569,6 +571,7 @@ static int nl80211_send_wiphy(struct sk_
CMD(set_pmksa, SET_PMKSA);
CMD(del_pmksa, DEL_PMKSA);
CMD(flush_pmksa, FLUSH_PMKSA);
+ CMD(remain_on_channel, REMAIN_ON_CHANNEL);
if (dev->wiphy.flags & WIPHY_FLAG_NETNS_OK) {
i++;
NLA_PUT_U32(msg, i, NL80211_CMD_SET_WIPHY_NETNS);
@@ -4322,6 +4325,143 @@ static int nl80211_flush_pmksa(struct sk
}
+static int nl80211_remain_on_channel(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct cfg80211_registered_device *rdev;
+ struct net_device *dev;
+ struct ieee80211_channel *chan;
+ struct sk_buff *msg;
+ void *hdr;
+ u64 cookie;
+ enum nl80211_channel_type channel_type = NL80211_CHAN_NO_HT;
+ u32 freq, duration;
+ int err;
+
+ if (!info->attrs[NL80211_ATTR_WIPHY_FREQ] ||
+ !info->attrs[NL80211_ATTR_DURATION])
+ return -EINVAL;
+
+ duration = nla_get_u32(info->attrs[NL80211_ATTR_DURATION]);
+
+ /*
+ * We should be on that channel for at least one jiffie,
+ * and more than 5 seconds seems excessive.
+ */
+ if (!duration || !msecs_to_jiffies(duration) || duration > 5000)
+ return -EINVAL;
+
+ rtnl_lock();
+
+ err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev);
+ if (err)
+ goto unlock_rtnl;
+
+ if (!rdev->ops->remain_on_channel) {
+ err = -EOPNOTSUPP;
+ goto out;
+ }
+
+ if (!netif_running(dev)) {
+ err = -ENETDOWN;
+ goto out;
+ }
+
+ if (info->attrs[NL80211_ATTR_WIPHY_CHANNEL_TYPE]) {
+ channel_type = nla_get_u32(
+ info->attrs[NL80211_ATTR_WIPHY_CHANNEL_TYPE]);
+ if (channel_type != NL80211_CHAN_NO_HT &&
+ channel_type != NL80211_CHAN_HT20 &&
+ channel_type != NL80211_CHAN_HT40PLUS &&
+ channel_type != NL80211_CHAN_HT40MINUS)
+ err = -EINVAL;
+ goto out;
+ }
+
+ freq = nla_get_u32(info->attrs[NL80211_ATTR_WIPHY_FREQ]);
+ chan = rdev_freq_to_chan(rdev, freq, channel_type);
+ if (chan == NULL) {
+ err = -EINVAL;
+ goto out;
+ }
+
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!msg) {
+ err = -ENOMEM;
+ goto out;
+ }
+
+ hdr = nl80211hdr_put(msg, info->snd_pid, info->snd_seq, 0,
+ NL80211_CMD_REMAIN_ON_CHANNEL);
+
+ if (IS_ERR(hdr)) {
+ err = PTR_ERR(hdr);
+ goto free_msg;
+ }
+
+ err = rdev->ops->remain_on_channel(&rdev->wiphy, dev, chan,
+ channel_type, duration, &cookie);
+
+ if (err)
+ goto free_msg;
+
+ NLA_PUT_U64(msg, NL80211_ATTR_COOKIE, cookie);
+
+ genlmsg_end(msg, hdr);
+ err = genlmsg_reply(msg, info);
+ goto out;
+
+ nla_put_failure:
+ err = -ENOBUFS;
+ free_msg:
+ nlmsg_free(msg);
+ out:
+ cfg80211_unlock_rdev(rdev);
+ dev_put(dev);
+ unlock_rtnl:
+ rtnl_unlock();
+ return err;
+}
+
+static int nl80211_cancel_remain_on_channel(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct cfg80211_registered_device *rdev;
+ struct net_device *dev;
+ u64 cookie;
+ int err;
+
+ if (!info->attrs[NL80211_ATTR_COOKIE])
+ return -EINVAL;
+
+ rtnl_lock();
+
+ err = get_rdev_dev_by_info_ifindex(info, &rdev, &dev);
+ if (err)
+ goto unlock_rtnl;
+
+ if (!rdev->ops->cancel_remain_on_channel) {
+ err = -EOPNOTSUPP;
+ goto out;
+ }
+
+ if (!netif_running(dev)) {
+ err = -ENETDOWN;
+ goto out;
+ }
+
+ cookie = nla_get_u64(info->attrs[NL80211_ATTR_COOKIE]);
+
+ err = rdev->ops->cancel_remain_on_channel(&rdev->wiphy, dev, cookie);
+
+ out:
+ cfg80211_unlock_rdev(rdev);
+ dev_put(dev);
+ unlock_rtnl:
+ rtnl_unlock();
+ return err;
+}
+
static struct genl_ops nl80211_ops[] = {
{
.cmd = NL80211_CMD_GET_WIPHY,
@@ -4584,8 +4724,20 @@ static struct genl_ops nl80211_ops[] = {
.policy = nl80211_policy,
.flags = GENL_ADMIN_PERM,
},
-
+ {
+ .cmd = NL80211_CMD_REMAIN_ON_CHANNEL,
+ .doit = nl80211_remain_on_channel,
+ .policy = nl80211_policy,
+ .flags = GENL_ADMIN_PERM,
+ },
+ {
+ .cmd = NL80211_CMD_CANCEL_REMAIN_ON_CHANNEL,
+ .doit = nl80211_cancel_remain_on_channel,
+ .policy = nl80211_policy,
+ .flags = GENL_ADMIN_PERM,
+ },
};
+
static struct genl_multicast_group nl80211_mlme_mcgrp = {
.name = "mlme",
};
@@ -5173,6 +5325,70 @@ nla_put_failure:
nlmsg_free(msg);
}
+static void nl80211_send_remain_on_chan_event(
+ int cmd, struct cfg80211_registered_device *rdev,
+ struct net_device *netdev, u64 cookie,
+ struct ieee80211_channel *chan,
+ enum nl80211_channel_type channel_type,
+ unsigned int duration, gfp_t gfp)
+{
+ struct sk_buff *msg;
+ void *hdr;
+
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, gfp);
+ if (!msg)
+ return;
+
+ hdr = nl80211hdr_put(msg, 0, 0, 0, cmd);
+ if (!hdr) {
+ nlmsg_free(msg);
+ return;
+ }
+
+ NLA_PUT_U32(msg, NL80211_ATTR_WIPHY, rdev->wiphy_idx);
+ NLA_PUT_U32(msg, NL80211_ATTR_IFINDEX, netdev->ifindex);
+ NLA_PUT_U32(msg, NL80211_ATTR_WIPHY_FREQ, chan->center_freq);
+ NLA_PUT_U32(msg, NL80211_ATTR_WIPHY_CHANNEL_TYPE, channel_type);
+ NLA_PUT_U64(msg, NL80211_ATTR_COOKIE, cookie);
+
+ if (cmd == NL80211_CMD_REMAIN_ON_CHANNEL)
+ NLA_PUT_U32(msg, NL80211_ATTR_DURATION, duration);
+
+ if (genlmsg_end(msg, hdr) < 0) {
+ nlmsg_free(msg);
+ return;
+ }
+
+ genlmsg_multicast_netns(wiphy_net(&rdev->wiphy), msg, 0,
+ nl80211_mlme_mcgrp.id, gfp);
+ return;
+
+ nla_put_failure:
+ genlmsg_cancel(msg, hdr);
+ nlmsg_free(msg);
+}
+
+void nl80211_send_remain_on_channel(struct cfg80211_registered_device *rdev,
+ struct net_device *netdev, u64 cookie,
+ struct ieee80211_channel *chan,
+ enum nl80211_channel_type channel_type,
+ unsigned int duration, gfp_t gfp)
+{
+ nl80211_send_remain_on_chan_event(NL80211_CMD_REMAIN_ON_CHANNEL,
+ rdev, netdev, cookie, chan,
+ channel_type, duration, gfp);
+}
+
+void nl80211_send_remain_on_channel_cancel(
+ struct cfg80211_registered_device *rdev, struct net_device *netdev,
+ u64 cookie, struct ieee80211_channel *chan,
+ enum nl80211_channel_type channel_type, gfp_t gfp)
+{
+ nl80211_send_remain_on_chan_event(NL80211_CMD_CANCEL_REMAIN_ON_CHANNEL,
+ rdev, netdev, cookie, chan,
+ channel_type, 0, gfp);
+}
+
/* initialisation/exit functions */
int nl80211_init(void)
--- wireless-testing.orig/net/wireless/nl80211.h 2009-12-04 09:57:31.000000000 +0100
+++ wireless-testing/net/wireless/nl80211.h 2009-12-04 10:19:49.000000000 +0100
@@ -59,4 +59,15 @@ void nl80211_send_ibss_bssid(struct cfg8
struct net_device *netdev, const u8 *bssid,
gfp_t gfp);
+void nl80211_send_remain_on_channel(struct cfg80211_registered_device *rdev,
+ struct net_device *netdev,
+ u64 cookie,
+ struct ieee80211_channel *chan,
+ enum nl80211_channel_type channel_type,
+ unsigned int duration, gfp_t gfp);
+void nl80211_send_remain_on_channel_cancel(
+ struct cfg80211_registered_device *rdev, struct net_device *netdev,
+ u64 cookie, struct ieee80211_channel *chan,
+ enum nl80211_channel_type channel_type, gfp_t gfp);
+
#endif /* __NET_WIRELESS_NL80211_H */
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH 3/4] mac80211: support remain-on-channel command
2009-12-04 14:35 [PATCH 0/4] off-channel work Johannes Berg
2009-12-04 14:35 ` [PATCH 1/4] mac80211: Generalize off-channel operation helpers from scan code Johannes Berg
2009-12-04 14:35 ` [PATCH 2/4] cfg80211: add remain-on-channel command Johannes Berg
@ 2009-12-04 14:35 ` Johannes Berg
2009-12-04 14:35 ` [PATCH 4/4] mac80211: make off-channel work generic Johannes Berg
3 siblings, 0 replies; 6+ messages in thread
From: Johannes Berg @ 2009-12-04 14:35 UTC (permalink / raw)
To: John Linville; +Cc: linux-wireless, Jouni Malinen, Jouni Malinen
This implements the new remain-on-channel cfg80211
command in mac80211, extending the work interface.
Also change the work purge code to be able to clean
up events properly (pretending they timed out.)
Signed-off-by: Jouni Malinen <jouni.malinen@atheros.com>
Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
---
net/mac80211/cfg.c | 24 ++++++++
net/mac80211/ieee80211_i.h | 15 +++++
net/mac80211/main.c | 3 +
net/mac80211/mlme.c | 3 +
net/mac80211/offchannel.c | 8 ++
net/mac80211/work.c | 134 +++++++++++++++++++++++++++++++++++++++++++--
6 files changed, 181 insertions(+), 6 deletions(-)
--- wireless-testing.orig/net/mac80211/ieee80211_i.h 2009-12-04 15:00:40.000000000 +0100
+++ wireless-testing/net/mac80211/ieee80211_i.h 2009-12-04 15:00:41.000000000 +0100
@@ -225,9 +225,11 @@ struct mesh_preq_queue {
};
enum ieee80211_work_type {
+ IEEE80211_WORK_ABORT,
IEEE80211_WORK_DIRECT_PROBE,
IEEE80211_WORK_AUTH,
IEEE80211_WORK_ASSOC,
+ IEEE80211_WORK_REMAIN_ON_CHANNEL,
};
/**
@@ -283,6 +285,9 @@ struct ieee80211_work {
u8 supp_rates_len;
bool wmm_used, use_11n;
} assoc;
+ struct {
+ unsigned long timeout;
+ } remain;
};
int ie_len;
@@ -729,6 +734,10 @@ struct ieee80211_local {
enum nl80211_channel_type oper_channel_type;
struct ieee80211_channel *oper_channel, *csa_channel;
+ /* Temporary remain-on-channel for off-channel operations */
+ struct ieee80211_channel *tmp_channel;
+ enum nl80211_channel_type tmp_channel_type;
+
/* SNMP counters */
/* dot11CountersTable */
u32 dot11TransmittedFragmentCount;
@@ -1160,6 +1169,12 @@ void ieee80211_add_work(struct ieee80211
void ieee80211_work_purge(struct ieee80211_sub_if_data *sdata);
ieee80211_rx_result ieee80211_work_rx_mgmt(struct ieee80211_sub_if_data *sdata,
struct sk_buff *skb);
+int ieee80211_wk_remain_on_channel(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_channel *chan,
+ enum nl80211_channel_type channel_type,
+ unsigned int duration, u64 *cookie);
+int ieee80211_wk_cancel_remain_on_channel(
+ struct ieee80211_sub_if_data *sdata, u64 cookie);
#ifdef CONFIG_MAC80211_NOINLINE
#define debug_noinline noinline
--- wireless-testing.orig/net/mac80211/mlme.c 2009-12-04 15:00:40.000000000 +0100
+++ wireless-testing/net/mac80211/mlme.c 2009-12-04 15:00:41.000000000 +0100
@@ -849,6 +849,9 @@ static void ieee80211_mgd_probe_ap(struc
if (sdata->local->scanning)
return;
+ if (sdata->local->tmp_channel)
+ return;
+
mutex_lock(&ifmgd->mtx);
if (!ifmgd->associated)
--- wireless-testing.orig/net/mac80211/main.c 2009-12-04 15:00:40.000000000 +0100
+++ wireless-testing/net/mac80211/main.c 2009-12-04 15:00:41.000000000 +0100
@@ -107,6 +107,9 @@ int ieee80211_hw_config(struct ieee80211
if (scan_chan) {
chan = scan_chan;
channel_type = NL80211_CHAN_NO_HT;
+ } else if (local->tmp_channel) {
+ chan = scan_chan = local->tmp_channel;
+ channel_type = local->tmp_channel_type;
} else {
chan = local->oper_channel;
channel_type = local->oper_channel_type;
--- wireless-testing.orig/net/mac80211/cfg.c 2009-12-04 15:00:40.000000000 +0100
+++ wireless-testing/net/mac80211/cfg.c 2009-12-04 15:00:41.000000000 +0100
@@ -1440,6 +1440,28 @@ static int ieee80211_set_bitrate_mask(st
return -EINVAL;
}
+static int ieee80211_remain_on_channel(struct wiphy *wiphy,
+ struct net_device *dev,
+ struct ieee80211_channel *chan,
+ enum nl80211_channel_type channel_type,
+ unsigned int duration,
+ u64 *cookie)
+{
+ struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+
+ return ieee80211_wk_remain_on_channel(sdata, chan, channel_type,
+ duration, cookie);
+}
+
+static int ieee80211_cancel_remain_on_channel(struct wiphy *wiphy,
+ struct net_device *dev,
+ u64 cookie)
+{
+ struct ieee80211_sub_if_data *sdata = IEEE80211_DEV_TO_SUB_IF(dev);
+
+ return ieee80211_wk_cancel_remain_on_channel(sdata, cookie);
+}
+
struct cfg80211_ops mac80211_config_ops = {
.add_virtual_intf = ieee80211_add_iface,
.del_virtual_intf = ieee80211_del_iface,
@@ -1486,4 +1508,6 @@ struct cfg80211_ops mac80211_config_ops
CFG80211_TESTMODE_CMD(ieee80211_testmode_cmd)
.set_power_mgmt = ieee80211_set_power_mgmt,
.set_bitrate_mask = ieee80211_set_bitrate_mask,
+ .remain_on_channel = ieee80211_remain_on_channel,
+ .cancel_remain_on_channel = ieee80211_cancel_remain_on_channel,
};
--- wireless-testing.orig/net/mac80211/offchannel.c 2009-12-04 15:00:40.000000000 +0100
+++ wireless-testing/net/mac80211/offchannel.c 2009-12-04 15:00:41.000000000 +0100
@@ -94,9 +94,13 @@ void ieee80211_offchannel_stop_beaconing
/*
* only handle non-STA interfaces here, STA interfaces
* are handled in ieee80211_offchannel_stop_station(),
- * e.g., from the background scan state machine
+ * e.g., from the background scan state machine.
+ *
+ * In addition, do not stop monitor interface to allow it to be
+ * used from user space controlled off-channel operations.
*/
- if (sdata->vif.type != NL80211_IFTYPE_STATION)
+ if (sdata->vif.type != NL80211_IFTYPE_STATION &&
+ sdata->vif.type != NL80211_IFTYPE_MONITOR)
netif_stop_queue(sdata->dev);
}
mutex_unlock(&local->iflist_mtx);
--- wireless-testing.orig/net/mac80211/work.c 2009-12-04 15:00:41.000000000 +0100
+++ wireless-testing/net/mac80211/work.c 2009-12-04 15:00:41.000000000 +0100
@@ -538,6 +538,44 @@ ieee80211_associate(struct ieee80211_wor
return WORK_ACT_NONE;
}
+static enum work_action __must_check
+ieee80211_remain_on_channel_timeout(struct ieee80211_work *wk)
+{
+ struct ieee80211_sub_if_data *sdata = wk->sdata;
+ struct ieee80211_local *local = sdata->local;
+
+ /*
+ * First time we run, do nothing -- the generic code will
+ * have switched to the right channel etc.
+ */
+ if (wk->timeout != wk->remain.timeout) {
+ wk->timeout = wk->remain.timeout;
+ return WORK_ACT_NONE;
+ }
+
+ /*
+ * We are done serving the remain-on-channel command; kill the work
+ * item to allow idle state to be entered again. In addition, clear the
+ * temporary channel information to allow operational channel to be
+ * used.
+ */
+ list_del(&wk->list);
+ free_work(wk);
+
+ if (local->tmp_channel) {
+ cfg80211_remain_on_channel_expired(sdata->dev, (u64)wk,
+ local->tmp_channel,
+ local->tmp_channel_type,
+ GFP_KERNEL);
+
+ local->tmp_channel = NULL;
+ ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_CHANNEL);
+ ieee80211_offchannel_return(local, true);
+ }
+
+ return WORK_ACT_NONE;
+}
+
static void ieee80211_auth_challenge(struct ieee80211_work *wk,
struct ieee80211_mgmt *mgmt,
size_t len)
@@ -825,6 +863,8 @@ static void ieee80211_work_work(struct w
/* nothing */
rma = WORK_ACT_NONE;
break;
+ case IEEE80211_WORK_ABORT:
+ rma = WORK_ACT_TIMEOUT;
case IEEE80211_WORK_DIRECT_PROBE:
rma = ieee80211_direct_probe(wk);
break;
@@ -834,6 +874,9 @@ static void ieee80211_work_work(struct w
case IEEE80211_WORK_ASSOC:
rma = ieee80211_associate(wk);
break;
+ case IEEE80211_WORK_REMAIN_ON_CHANNEL:
+ rma = ieee80211_remain_on_channel_timeout(wk);
+ break;
}
switch (rma) {
@@ -900,14 +943,25 @@ void ieee80211_work_init(struct ieee8021
void ieee80211_work_purge(struct ieee80211_sub_if_data *sdata)
{
struct ieee80211_local *local = sdata->local;
- struct ieee80211_work *wk, *tmp;
+ struct ieee80211_work *wk;
mutex_lock(&local->work_mtx);
- list_for_each_entry_safe(wk, tmp, &local->work_list, list) {
+ list_for_each_entry(wk, &local->work_list, list) {
if (wk->sdata != sdata)
continue;
- list_del(&wk->list);
- free_work(wk);
+ wk->type = IEEE80211_WORK_ABORT;
+ }
+ mutex_unlock(&local->work_mtx);
+
+ /* run cleanups etc. */
+ ieee80211_work_work(&local->work_work);
+
+ mutex_lock(&local->work_mtx);
+ list_for_each_entry(wk, &local->work_list, list) {
+ if (wk->sdata != sdata)
+ continue;
+ WARN_ON(1);
+ break;
}
mutex_unlock(&local->work_mtx);
}
@@ -949,3 +1003,75 @@ ieee80211_rx_result ieee80211_work_rx_mg
return RX_CONTINUE;
}
+
+int ieee80211_wk_remain_on_channel(struct ieee80211_sub_if_data *sdata,
+ struct ieee80211_channel *chan,
+ enum nl80211_channel_type channel_type,
+ unsigned int duration, u64 *cookie)
+{
+ struct ieee80211_local *local = sdata->local;
+ struct ieee80211_work *wk;
+
+ wk = kzalloc(sizeof(*wk), GFP_KERNEL);
+ if (!wk)
+ return -ENOMEM;
+
+ wk->type = IEEE80211_WORK_REMAIN_ON_CHANNEL;
+ wk->chan = chan;
+ wk->sdata = sdata;
+
+ wk->remain.timeout = jiffies + msecs_to_jiffies(duration);
+
+ *cookie = (u64)wk;
+
+ ieee80211_add_work(wk);
+
+ /*
+ * TODO: could optimize this by leaving the station vifs in awake mode
+ * if they happen to be on the same channel as the requested channel
+ */
+ ieee80211_offchannel_stop_beaconing(local);
+ ieee80211_offchannel_stop_station(local);
+
+ sdata->local->tmp_channel = chan;
+ sdata->local->tmp_channel_type = channel_type;
+ ieee80211_hw_config(sdata->local, IEEE80211_CONF_CHANGE_CHANNEL);
+
+ cfg80211_ready_on_channel(sdata->dev, (u64)wk, chan, channel_type,
+ duration, GFP_KERNEL);
+
+ return 0;
+}
+
+int ieee80211_wk_cancel_remain_on_channel(struct ieee80211_sub_if_data *sdata,
+ u64 cookie)
+{
+ struct ieee80211_local *local = sdata->local;
+ struct ieee80211_work *wk, *tmp;
+ bool found = false;
+
+ mutex_lock(&local->work_mtx);
+ list_for_each_entry_safe(wk, tmp, &local->work_list, list) {
+ if ((u64)wk == cookie) {
+ found = true;
+ list_del(&wk->list);
+ free_work(wk);
+ break;
+ }
+ }
+ mutex_unlock(&local->work_mtx);
+
+ if (!found)
+ return -ENOENT;
+
+ if (sdata->local->tmp_channel) {
+ sdata->local->tmp_channel = NULL;
+ ieee80211_hw_config(sdata->local,
+ IEEE80211_CONF_CHANGE_CHANNEL);
+ ieee80211_offchannel_return(sdata->local, true);
+ }
+
+ ieee80211_recalc_idle(local);
+
+ return 0;
+}
^ permalink raw reply [flat|nested] 6+ messages in thread* [PATCH 4/4] mac80211: make off-channel work generic
2009-12-04 14:35 [PATCH 0/4] off-channel work Johannes Berg
` (2 preceding siblings ...)
2009-12-04 14:35 ` [PATCH 3/4] mac80211: support " Johannes Berg
@ 2009-12-04 14:35 ` Johannes Berg
3 siblings, 0 replies; 6+ messages in thread
From: Johannes Berg @ 2009-12-04 14:35 UTC (permalink / raw)
To: John Linville; +Cc: linux-wireless, Jouni Malinen
This changes mac80211 to allow being off-channel for
any type of work, not just the 'remain-on-channel'
work. This also helps fast transition to a BSS on a
different channel.
Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
---
net/mac80211/ieee80211_i.h | 7 +-
net/mac80211/mlme.c | 14 ----
net/mac80211/work.c | 137 ++++++++++++++++++++++++++-------------------
3 files changed, 89 insertions(+), 69 deletions(-)
--- wireless-testing.orig/net/mac80211/ieee80211_i.h 2009-12-04 14:44:51.000000000 +0100
+++ wireless-testing/net/mac80211/ieee80211_i.h 2009-12-04 14:45:02.000000000 +0100
@@ -255,13 +255,15 @@ struct ieee80211_work {
struct sk_buff *skb);
struct ieee80211_channel *chan;
- /* XXX: chan type? -- right now not really needed */
+ enum nl80211_channel_type chan_type;
unsigned long timeout;
enum ieee80211_work_type type;
u8 filter_ta[ETH_ALEN];
+ bool started;
+
union {
struct {
int tries;
@@ -286,7 +288,8 @@ struct ieee80211_work {
bool wmm_used, use_11n;
} assoc;
struct {
- unsigned long timeout;
+ u32 duration;
+ bool started;
} remain;
};
--- wireless-testing.orig/net/mac80211/work.c 2009-12-04 14:44:51.000000000 +0100
+++ wireless-testing/net/mac80211/work.c 2009-12-04 14:47:57.000000000 +0100
@@ -541,39 +541,22 @@ ieee80211_associate(struct ieee80211_wor
static enum work_action __must_check
ieee80211_remain_on_channel_timeout(struct ieee80211_work *wk)
{
- struct ieee80211_sub_if_data *sdata = wk->sdata;
- struct ieee80211_local *local = sdata->local;
-
/*
* First time we run, do nothing -- the generic code will
* have switched to the right channel etc.
*/
- if (wk->timeout != wk->remain.timeout) {
- wk->timeout = wk->remain.timeout;
- return WORK_ACT_NONE;
- }
+ if (!wk->remain.started) {
+ wk->remain.started = true;
+ wk->timeout = jiffies + msecs_to_jiffies(wk->remain.duration);
+
+ cfg80211_ready_on_channel(wk->sdata->dev, (u64)wk, wk->chan,
+ wk->chan_type, wk->remain.duration,
+ GFP_KERNEL);
- /*
- * We are done serving the remain-on-channel command; kill the work
- * item to allow idle state to be entered again. In addition, clear the
- * temporary channel information to allow operational channel to be
- * used.
- */
- list_del(&wk->list);
- free_work(wk);
-
- if (local->tmp_channel) {
- cfg80211_remain_on_channel_expired(sdata->dev, (u64)wk,
- local->tmp_channel,
- local->tmp_channel_type,
- GFP_KERNEL);
-
- local->tmp_channel = NULL;
- ieee80211_hw_config(local, IEEE80211_CONF_CHANGE_CHANNEL);
- ieee80211_offchannel_return(local, true);
+ return WORK_ACT_NONE;
}
- return WORK_ACT_NONE;
+ return WORK_ACT_TIMEOUT;
}
static void ieee80211_auth_challenge(struct ieee80211_work *wk,
@@ -799,7 +782,7 @@ static void ieee80211_work_rx_queued_mgm
break;
case WORK_DONE_REQUEUE:
synchronize_rcu();
- wk->timeout = jiffies; /* run again directly */
+ wk->started = false; /* restart */
mutex_lock(&local->work_mtx);
list_add_tail(&wk->list, &local->work_list);
mutex_unlock(&local->work_mtx);
@@ -827,6 +810,7 @@ static void ieee80211_work_work(struct w
struct ieee80211_work *wk, *tmp;
LIST_HEAD(free_work);
enum work_action rma;
+ bool remain_off_channel = false;
if (local->scanning)
return;
@@ -847,6 +831,34 @@ static void ieee80211_work_work(struct w
mutex_lock(&local->work_mtx);
list_for_each_entry_safe(wk, tmp, &local->work_list, list) {
+ /* mark work as started if it's on the current off-channel */
+ if (!wk->started && local->tmp_channel &&
+ wk->chan == local->tmp_channel &&
+ wk->chan_type == local->tmp_channel_type) {
+ wk->started = true;
+ }
+
+ if (!wk->started && !local->tmp_channel) {
+ /*
+ * TODO: could optimize this by leaving the
+ * station vifs in awake mode if they
+ * happen to be on the same channel as
+ * the requested channel
+ */
+ ieee80211_offchannel_stop_beaconing(local);
+ ieee80211_offchannel_stop_station(local);
+
+ local->tmp_channel = wk->chan;
+ local->tmp_channel_type = wk->chan_type;
+ ieee80211_hw_config(local, 0);
+ wk->started = true;
+ wk->timeout = jiffies;
+ }
+
+ /* don't try to work with items that aren't started */
+ if (!wk->started)
+ continue;
+
if (time_is_after_jiffies(wk->timeout)) {
/*
* This work item isn't supposed to be worked on
@@ -881,7 +893,8 @@ static void ieee80211_work_work(struct w
switch (rma) {
case WORK_ACT_NONE:
- /* no action required */
+ /* might have changed the timeout */
+ run_again(local, wk->timeout);
break;
case WORK_ACT_TIMEOUT:
list_del_rcu(&wk->list);
@@ -893,6 +906,24 @@ static void ieee80211_work_work(struct w
}
}
+ list_for_each_entry(wk, &local->work_list, list) {
+ if (!wk->started)
+ continue;
+ if (wk->chan != local->tmp_channel)
+ continue;
+ if (wk->chan_type != local->tmp_channel_type)
+ continue;
+ remain_off_channel = true;
+ }
+
+ if (!remain_off_channel && local->tmp_channel) {
+ local->tmp_channel = NULL;
+ ieee80211_hw_config(local, 0);
+ ieee80211_offchannel_return(local, true);
+ /* give connection some time to breathe */
+ run_again(local, jiffies + HZ/2);
+ }
+
if (list_empty(&local->work_list) && local->scan_req)
ieee80211_queue_delayed_work(&local->hw,
&local->scan_work,
@@ -900,6 +931,8 @@ static void ieee80211_work_work(struct w
mutex_unlock(&local->work_mtx);
+ ieee80211_recalc_idle(local);
+
list_for_each_entry_safe(wk, tmp, &free_work, list) {
wk->done(wk, NULL);
list_del(&wk->list);
@@ -920,7 +953,7 @@ void ieee80211_add_work(struct ieee80211
if (WARN_ON(!wk->done))
return;
- wk->timeout = jiffies;
+ wk->started = false;
local = wk->sdata->local;
mutex_lock(&local->work_mtx);
@@ -950,6 +983,8 @@ void ieee80211_work_purge(struct ieee802
if (wk->sdata != sdata)
continue;
wk->type = IEEE80211_WORK_ABORT;
+ wk->started = true;
+ wk->timeout = jiffies;
}
mutex_unlock(&local->work_mtx);
@@ -1004,12 +1039,24 @@ ieee80211_rx_result ieee80211_work_rx_mg
return RX_CONTINUE;
}
+static enum work_done_result ieee80211_remain_done(struct ieee80211_work *wk,
+ struct sk_buff *skb)
+{
+ /*
+ * We are done serving the remain-on-channel command.
+ */
+ cfg80211_remain_on_channel_expired(wk->sdata->dev, (u64)wk,
+ wk->chan, wk->chan_type,
+ GFP_KERNEL);
+
+ return WORK_DONE_DESTROY;
+}
+
int ieee80211_wk_remain_on_channel(struct ieee80211_sub_if_data *sdata,
struct ieee80211_channel *chan,
enum nl80211_channel_type channel_type,
unsigned int duration, u64 *cookie)
{
- struct ieee80211_local *local = sdata->local;
struct ieee80211_work *wk;
wk = kzalloc(sizeof(*wk), GFP_KERNEL);
@@ -1018,28 +1065,16 @@ int ieee80211_wk_remain_on_channel(struc
wk->type = IEEE80211_WORK_REMAIN_ON_CHANNEL;
wk->chan = chan;
+ wk->chan_type = channel_type;
wk->sdata = sdata;
+ wk->done = ieee80211_remain_done;
- wk->remain.timeout = jiffies + msecs_to_jiffies(duration);
+ wk->remain.duration = duration;
*cookie = (u64)wk;
ieee80211_add_work(wk);
- /*
- * TODO: could optimize this by leaving the station vifs in awake mode
- * if they happen to be on the same channel as the requested channel
- */
- ieee80211_offchannel_stop_beaconing(local);
- ieee80211_offchannel_stop_station(local);
-
- sdata->local->tmp_channel = chan;
- sdata->local->tmp_channel_type = channel_type;
- ieee80211_hw_config(sdata->local, IEEE80211_CONF_CHANGE_CHANNEL);
-
- cfg80211_ready_on_channel(sdata->dev, (u64)wk, chan, channel_type,
- duration, GFP_KERNEL);
-
return 0;
}
@@ -1053,9 +1088,8 @@ int ieee80211_wk_cancel_remain_on_channe
mutex_lock(&local->work_mtx);
list_for_each_entry_safe(wk, tmp, &local->work_list, list) {
if ((u64)wk == cookie) {
+ wk->timeout = jiffies;
found = true;
- list_del(&wk->list);
- free_work(wk);
break;
}
}
@@ -1064,14 +1098,7 @@ int ieee80211_wk_cancel_remain_on_channe
if (!found)
return -ENOENT;
- if (sdata->local->tmp_channel) {
- sdata->local->tmp_channel = NULL;
- ieee80211_hw_config(sdata->local,
- IEEE80211_CONF_CHANGE_CHANNEL);
- ieee80211_offchannel_return(sdata->local, true);
- }
-
- ieee80211_recalc_idle(local);
+ ieee80211_queue_work(&local->hw, &local->work_work);
return 0;
}
--- wireless-testing.orig/net/mac80211/mlme.c 2009-12-04 14:44:51.000000000 +0100
+++ wireless-testing/net/mac80211/mlme.c 2009-12-04 14:45:02.000000000 +0100
@@ -1097,6 +1097,8 @@ static bool ieee80211_assoc_success(stru
else
ieee80211_set_wmm_default(sdata);
+ local->oper_channel = wk->chan;
+
if (elems.ht_info_elem && elems.wmm_param &&
(sdata->local->hw.queues >= 4) &&
!(ifmgd->flags & IEEE80211_STA_DISABLE_11N))
@@ -1789,15 +1791,6 @@ int ieee80211_mgd_auth(struct ieee80211_
wk->sdata = sdata;
wk->done = ieee80211_probe_auth_done;
- /*
- * XXX: if still associated need to tell AP that we're going
- * to sleep and then change channel etc.
- * For now switch channel here, later will be handled
- * by submitting this as an off-channel work item.
- */
- sdata->local->oper_channel = req->bss->channel;
- ieee80211_hw_config(sdata->local, 0);
-
ieee80211_add_work(wk);
return 0;
}
@@ -1921,9 +1914,6 @@ int ieee80211_mgd_assoc(struct ieee80211
else
ifmgd->flags &= ~IEEE80211_STA_CONTROL_PORT;
- sdata->local->oper_channel = req->bss->channel;
- ieee80211_hw_config(sdata->local, 0);
-
ieee80211_add_work(wk);
return 0;
}
^ permalink raw reply [flat|nested] 6+ messages in thread