mac80211: add basic support for WoWLAN

This adds basic support for the new WoWLAN
configuration in mac80211. The behaviour is
completely offloaded to the driver though,
with two new callbacks (suspend/resume).

Options for the driver include a complete
reconfiguration after wakeup, and exposing
all the triggers it wants to support.

Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
This commit is contained in:
Johannes Berg 2011-05-04 15:37:29 +02:00 committed by John W. Linville
parent ff1b6e69ad
commit eecc48000a
9 changed files with 97 additions and 5 deletions

View File

@ -1606,6 +1606,18 @@ enum ieee80211_ampdu_mlme_action {
* you should ensure to cancel it on this callback.
* Must be implemented and can sleep.
*
* @suspend: Suspend the device; mac80211 itself will quiesce before and
* stop transmitting and doing any other configuration, and then
* ask the device to suspend. This is only invoked when WoWLAN is
* configured, otherwise the device is deconfigured completely and
* reconfigured at resume time.
*
* @resume: If WoWLAN was configured, this indicates that mac80211 is
* now resuming its operation, after this the device must be fully
* functional again. If this returns an error, the only way out is
* to also unregister the device. If it returns 1, then mac80211
* will also go through the regular complete restart on resume.
*
* @add_interface: Called when a netdevice attached to the hardware is
* enabled. Because it is not called for monitor mode devices, @start
* and @stop must be implemented.
@ -1831,6 +1843,10 @@ struct ieee80211_ops {
void (*tx)(struct ieee80211_hw *hw, struct sk_buff *skb);
int (*start)(struct ieee80211_hw *hw);
void (*stop)(struct ieee80211_hw *hw);
#ifdef CONFIG_PM
int (*suspend)(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan);
int (*resume)(struct ieee80211_hw *hw);
#endif
int (*add_interface)(struct ieee80211_hw *hw,
struct ieee80211_vif *vif);
int (*change_interface)(struct ieee80211_hw *hw,

View File

@ -1300,7 +1300,7 @@ static int ieee80211_set_channel(struct wiphy *wiphy,
static int ieee80211_suspend(struct wiphy *wiphy,
struct cfg80211_wowlan *wowlan)
{
return __ieee80211_suspend(wiphy_priv(wiphy));
return __ieee80211_suspend(wiphy_priv(wiphy), wowlan);
}
static int ieee80211_resume(struct wiphy *wiphy)

View File

@ -135,7 +135,7 @@ static ssize_t reset_write(struct file *file, const char __user *user_buf,
struct ieee80211_local *local = file->private_data;
rtnl_lock();
__ieee80211_suspend(&local->hw);
__ieee80211_suspend(&local->hw, NULL);
__ieee80211_resume(&local->hw);
rtnl_unlock();

View File

@ -41,6 +41,33 @@ static inline void drv_stop(struct ieee80211_local *local)
local->started = false;
}
#ifdef CONFIG_PM
static inline int drv_suspend(struct ieee80211_local *local,
struct cfg80211_wowlan *wowlan)
{
int ret;
might_sleep();
trace_drv_suspend(local);
ret = local->ops->suspend(&local->hw, wowlan);
trace_drv_return_int(local, ret);
return ret;
}
static inline int drv_resume(struct ieee80211_local *local)
{
int ret;
might_sleep();
trace_drv_resume(local);
ret = local->ops->resume(&local->hw);
trace_drv_return_int(local, ret);
return ret;
}
#endif
static inline int drv_add_interface(struct ieee80211_local *local,
struct ieee80211_vif *vif)
{

View File

@ -108,6 +108,16 @@ DEFINE_EVENT(local_only_evt, drv_start,
TP_ARGS(local)
);
DEFINE_EVENT(local_only_evt, drv_suspend,
TP_PROTO(struct ieee80211_local *local),
TP_ARGS(local)
);
DEFINE_EVENT(local_only_evt, drv_resume,
TP_PROTO(struct ieee80211_local *local),
TP_ARGS(local)
);
DEFINE_EVENT(local_only_evt, drv_stop,
TP_PROTO(struct ieee80211_local *local),
TP_ARGS(local)

View File

@ -764,6 +764,9 @@ struct ieee80211_local {
/* device is started */
bool started;
/* wowlan is enabled -- don't reconfig on resume */
bool wowlan;
int tx_headroom; /* required headroom for hardware/radiotap */
/* count for keys needing tailroom space allocation */
@ -1250,7 +1253,8 @@ int ieee80211_reconfig(struct ieee80211_local *local);
void ieee80211_stop_device(struct ieee80211_local *local);
#ifdef CONFIG_PM
int __ieee80211_suspend(struct ieee80211_hw *hw);
int __ieee80211_suspend(struct ieee80211_hw *hw,
struct cfg80211_wowlan *wowlan);
static inline int __ieee80211_resume(struct ieee80211_hw *hw)
{
@ -1263,7 +1267,8 @@ static inline int __ieee80211_resume(struct ieee80211_hw *hw)
return ieee80211_reconfig(hw_to_local(hw));
}
#else
static inline int __ieee80211_suspend(struct ieee80211_hw *hw)
static inline int __ieee80211_suspend(struct ieee80211_hw *hw,
struct cfg80211_wowlan *wowlan)
{
return 0;
}

View File

@ -696,6 +696,10 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
WLAN_CIPHER_SUITE_AES_CMAC
};
if ((hw->wiphy->wowlan.flags || hw->wiphy->wowlan.n_patterns) &&
(!local->ops->suspend || !local->ops->resume))
return -EINVAL;
if (hw->max_report_rates == 0)
hw->max_report_rates = hw->max_rates;

View File

@ -6,7 +6,7 @@
#include "driver-ops.h"
#include "led.h"
int __ieee80211_suspend(struct ieee80211_hw *hw)
int __ieee80211_suspend(struct ieee80211_hw *hw, struct cfg80211_wowlan *wowlan)
{
struct ieee80211_local *local = hw_to_local(hw);
struct ieee80211_sub_if_data *sdata;
@ -47,6 +47,16 @@ int __ieee80211_suspend(struct ieee80211_hw *hw)
cancel_work_sync(&local->dynamic_ps_enable_work);
del_timer_sync(&local->dynamic_ps_timer);
local->wowlan = wowlan && local->open_count;
if (local->wowlan) {
int err = drv_suspend(local, wowlan);
if (err) {
local->quiescing = false;
return err;
}
goto suspend;
}
/* disable keys */
list_for_each_entry(sdata, &local->interfaces, list)
ieee80211_disable_keys(sdata);
@ -104,6 +114,7 @@ int __ieee80211_suspend(struct ieee80211_hw *hw)
if (local->open_count)
ieee80211_stop_device(local);
suspend:
local->suspended = true;
/* need suspended to be visible before quiescing is false */
barrier();

View File

@ -1125,9 +1125,27 @@ int ieee80211_reconfig(struct ieee80211_local *local)
struct sta_info *sta;
int res;
#ifdef CONFIG_PM
if (local->suspended)
local->resuming = true;
if (local->wowlan) {
local->wowlan = false;
res = drv_resume(local);
if (res < 0) {
local->resuming = false;
return res;
}
if (res == 0)
goto wake_up;
WARN_ON(res > 1);
/*
* res is 1, which means the driver requested
* to go through a regular reset on wakeup.
*/
}
#endif
/* restart hardware */
if (local->open_count) {
/*
@ -1258,6 +1276,7 @@ int ieee80211_reconfig(struct ieee80211_local *local)
if (ieee80211_sdata_running(sdata))
ieee80211_enable_keys(sdata);
wake_up:
ieee80211_wake_queues_by_reason(hw,
IEEE80211_QUEUE_STOP_REASON_SUSPEND);