linux/net/mac80211/pm.c
Luis R. Rodriguez 42935ecaf4 mac80211: redefine usage of the mac80211 workqueue
The mac80211 workqueue exists to enable mac80211 and drivers
to queue their own work on a single threaded workqueue. mac80211
takes care to flush the workqueue during suspend but we never
really had requirements on drivers for how they should use
the workqueue in consideration for suspend.

We extend mac80211 to document how the mac80211 workqueue should
be used, how it should not be used and finally move raw access to
the workqueue to mac80211 only. Drivers and mac80211 use helpers
to queue work onto the mac80211 workqueue:

  * ieee80211_queue_work()
  * ieee80211_queue_delayed_work()

These helpers will now warn if mac80211 already completed its
suspend cycle and someone is trying to queue work. mac80211
flushes the mac80211 workqueue prior to suspend a few times,
but we haven't taken the care to ensure drivers won't add more
work after suspend. To help with this we add a warning when
someone tries to add work and mac80211 already completed the
suspend cycle.

Drivers should ensure they cancel any work or delayed work
in the mac80211 stop() callback.

Signed-off-by: Luis R. Rodriguez <lrodriguez@atheros.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
2009-08-04 16:44:14 -04:00

135 lines
3.1 KiB
C

#include <net/mac80211.h>
#include <net/rtnetlink.h>
#include "ieee80211_i.h"
#include "mesh.h"
#include "driver-ops.h"
#include "led.h"
int __ieee80211_suspend(struct ieee80211_hw *hw)
{
struct ieee80211_local *local = hw_to_local(hw);
struct ieee80211_sub_if_data *sdata;
struct ieee80211_if_init_conf conf;
struct sta_info *sta;
unsigned long flags;
ieee80211_scan_cancel(local);
ieee80211_stop_queues_by_reason(hw,
IEEE80211_QUEUE_STOP_REASON_SUSPEND);
/* flush out all packets */
synchronize_net();
local->quiescing = true;
/* make quiescing visible to timers everywhere */
mb();
flush_workqueue(local->workqueue);
/* Don't try to run timers while suspended. */
del_timer_sync(&local->sta_cleanup);
/*
* Note that this particular timer doesn't need to be
* restarted at resume.
*/
cancel_work_sync(&local->dynamic_ps_enable_work);
del_timer_sync(&local->dynamic_ps_timer);
/* disable keys */
list_for_each_entry(sdata, &local->interfaces, list)
ieee80211_disable_keys(sdata);
/* Tear down aggregation sessions */
rcu_read_lock();
if (hw->flags & IEEE80211_HW_AMPDU_AGGREGATION) {
list_for_each_entry_rcu(sta, &local->sta_list, list) {
set_sta_flags(sta, WLAN_STA_SUSPEND);
ieee80211_sta_tear_down_BA_sessions(sta);
}
}
rcu_read_unlock();
/* remove STAs */
spin_lock_irqsave(&local->sta_lock, flags);
list_for_each_entry(sta, &local->sta_list, list) {
if (local->ops->sta_notify) {
sdata = sta->sdata;
if (sdata->vif.type == NL80211_IFTYPE_AP_VLAN)
sdata = container_of(sdata->bss,
struct ieee80211_sub_if_data,
u.ap);
drv_sta_notify(local, &sdata->vif, STA_NOTIFY_REMOVE,
&sta->sta);
}
mesh_plink_quiesce(sta);
}
spin_unlock_irqrestore(&local->sta_lock, flags);
/* remove all interfaces */
list_for_each_entry(sdata, &local->interfaces, list) {
switch(sdata->vif.type) {
case NL80211_IFTYPE_STATION:
ieee80211_sta_quiesce(sdata);
break;
case NL80211_IFTYPE_ADHOC:
ieee80211_ibss_quiesce(sdata);
break;
case NL80211_IFTYPE_MESH_POINT:
ieee80211_mesh_quiesce(sdata);
break;
case NL80211_IFTYPE_AP_VLAN:
case NL80211_IFTYPE_MONITOR:
/* don't tell driver about this */
continue;
default:
break;
}
if (!netif_running(sdata->dev))
continue;
/* disable beaconing */
ieee80211_bss_info_change_notify(sdata,
BSS_CHANGED_BEACON_ENABLED);
conf.vif = &sdata->vif;
conf.type = sdata->vif.type;
conf.mac_addr = sdata->dev->dev_addr;
drv_remove_interface(local, &conf);
}
/* stop hardware - this must stop RX */
if (local->open_count) {
ieee80211_led_radio(local, false);
drv_stop(local);
}
/*
* flush again, in case driver queued work -- it
* shouldn't be doing (or cancel everything in the
* stop callback) that but better safe than sorry.
*/
flush_workqueue(local->workqueue);
local->suspended = true;
/* need suspended to be visible before quiescing is false */
barrier();
local->quiescing = false;
return 0;
}
/*
* __ieee80211_resume() is a static inline which just calls
* ieee80211_reconfig(), which is also needed for hardware
* hang/firmware failure/etc. recovery.
*/