Commit eecc4800 authored by Johannes Berg's avatar Johannes Berg Committed by John W. Linville

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: default avatarJohannes Berg <johannes.berg@intel.com>
Signed-off-by: default avatarJohn W. Linville <linville@tuxdriver.com>
parent ff1b6e69
...@@ -1606,6 +1606,18 @@ enum ieee80211_ampdu_mlme_action { ...@@ -1606,6 +1606,18 @@ enum ieee80211_ampdu_mlme_action {
* you should ensure to cancel it on this callback. * you should ensure to cancel it on this callback.
* Must be implemented and can sleep. * 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 * @add_interface: Called when a netdevice attached to the hardware is
* enabled. Because it is not called for monitor mode devices, @start * enabled. Because it is not called for monitor mode devices, @start
* and @stop must be implemented. * and @stop must be implemented.
...@@ -1831,6 +1843,10 @@ struct ieee80211_ops { ...@@ -1831,6 +1843,10 @@ struct ieee80211_ops {
void (*tx)(struct ieee80211_hw *hw, struct sk_buff *skb); void (*tx)(struct ieee80211_hw *hw, struct sk_buff *skb);
int (*start)(struct ieee80211_hw *hw); int (*start)(struct ieee80211_hw *hw);
void (*stop)(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, int (*add_interface)(struct ieee80211_hw *hw,
struct ieee80211_vif *vif); struct ieee80211_vif *vif);
int (*change_interface)(struct ieee80211_hw *hw, int (*change_interface)(struct ieee80211_hw *hw,
......
...@@ -1300,7 +1300,7 @@ static int ieee80211_set_channel(struct wiphy *wiphy, ...@@ -1300,7 +1300,7 @@ static int ieee80211_set_channel(struct wiphy *wiphy,
static int ieee80211_suspend(struct wiphy *wiphy, static int ieee80211_suspend(struct wiphy *wiphy,
struct cfg80211_wowlan *wowlan) struct cfg80211_wowlan *wowlan)
{ {
return __ieee80211_suspend(wiphy_priv(wiphy)); return __ieee80211_suspend(wiphy_priv(wiphy), wowlan);
} }
static int ieee80211_resume(struct wiphy *wiphy) static int ieee80211_resume(struct wiphy *wiphy)
......
...@@ -135,7 +135,7 @@ static ssize_t reset_write(struct file *file, const char __user *user_buf, ...@@ -135,7 +135,7 @@ static ssize_t reset_write(struct file *file, const char __user *user_buf,
struct ieee80211_local *local = file->private_data; struct ieee80211_local *local = file->private_data;
rtnl_lock(); rtnl_lock();
__ieee80211_suspend(&local->hw); __ieee80211_suspend(&local->hw, NULL);
__ieee80211_resume(&local->hw); __ieee80211_resume(&local->hw);
rtnl_unlock(); rtnl_unlock();
......
...@@ -41,6 +41,33 @@ static inline void drv_stop(struct ieee80211_local *local) ...@@ -41,6 +41,33 @@ static inline void drv_stop(struct ieee80211_local *local)
local->started = false; 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, static inline int drv_add_interface(struct ieee80211_local *local,
struct ieee80211_vif *vif) struct ieee80211_vif *vif)
{ {
......
...@@ -108,6 +108,16 @@ DEFINE_EVENT(local_only_evt, drv_start, ...@@ -108,6 +108,16 @@ DEFINE_EVENT(local_only_evt, drv_start,
TP_ARGS(local) 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, DEFINE_EVENT(local_only_evt, drv_stop,
TP_PROTO(struct ieee80211_local *local), TP_PROTO(struct ieee80211_local *local),
TP_ARGS(local) TP_ARGS(local)
......
...@@ -764,6 +764,9 @@ struct ieee80211_local { ...@@ -764,6 +764,9 @@ struct ieee80211_local {
/* device is started */ /* device is started */
bool started; bool started;
/* wowlan is enabled -- don't reconfig on resume */
bool wowlan;
int tx_headroom; /* required headroom for hardware/radiotap */ int tx_headroom; /* required headroom for hardware/radiotap */
/* count for keys needing tailroom space allocation */ /* count for keys needing tailroom space allocation */
...@@ -1250,7 +1253,8 @@ int ieee80211_reconfig(struct ieee80211_local *local); ...@@ -1250,7 +1253,8 @@ int ieee80211_reconfig(struct ieee80211_local *local);
void ieee80211_stop_device(struct ieee80211_local *local); void ieee80211_stop_device(struct ieee80211_local *local);
#ifdef CONFIG_PM #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) static inline int __ieee80211_resume(struct ieee80211_hw *hw)
{ {
...@@ -1263,7 +1267,8 @@ 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)); return ieee80211_reconfig(hw_to_local(hw));
} }
#else #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; return 0;
} }
......
...@@ -696,6 +696,10 @@ int ieee80211_register_hw(struct ieee80211_hw *hw) ...@@ -696,6 +696,10 @@ int ieee80211_register_hw(struct ieee80211_hw *hw)
WLAN_CIPHER_SUITE_AES_CMAC 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) if (hw->max_report_rates == 0)
hw->max_report_rates = hw->max_rates; hw->max_report_rates = hw->max_rates;
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
#include "driver-ops.h" #include "driver-ops.h"
#include "led.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_local *local = hw_to_local(hw);
struct ieee80211_sub_if_data *sdata; struct ieee80211_sub_if_data *sdata;
...@@ -47,6 +47,16 @@ int __ieee80211_suspend(struct ieee80211_hw *hw) ...@@ -47,6 +47,16 @@ int __ieee80211_suspend(struct ieee80211_hw *hw)
cancel_work_sync(&local->dynamic_ps_enable_work); cancel_work_sync(&local->dynamic_ps_enable_work);
del_timer_sync(&local->dynamic_ps_timer); 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 */ /* disable keys */
list_for_each_entry(sdata, &local->interfaces, list) list_for_each_entry(sdata, &local->interfaces, list)
ieee80211_disable_keys(sdata); ieee80211_disable_keys(sdata);
...@@ -104,6 +114,7 @@ int __ieee80211_suspend(struct ieee80211_hw *hw) ...@@ -104,6 +114,7 @@ int __ieee80211_suspend(struct ieee80211_hw *hw)
if (local->open_count) if (local->open_count)
ieee80211_stop_device(local); ieee80211_stop_device(local);
suspend:
local->suspended = true; local->suspended = true;
/* need suspended to be visible before quiescing is false */ /* need suspended to be visible before quiescing is false */
barrier(); barrier();
......
...@@ -1125,9 +1125,27 @@ int ieee80211_reconfig(struct ieee80211_local *local) ...@@ -1125,9 +1125,27 @@ int ieee80211_reconfig(struct ieee80211_local *local)
struct sta_info *sta; struct sta_info *sta;
int res; int res;
#ifdef CONFIG_PM
if (local->suspended) if (local->suspended)
local->resuming = true; 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 */ /* restart hardware */
if (local->open_count) { if (local->open_count) {
/* /*
...@@ -1258,6 +1276,7 @@ int ieee80211_reconfig(struct ieee80211_local *local) ...@@ -1258,6 +1276,7 @@ int ieee80211_reconfig(struct ieee80211_local *local)
if (ieee80211_sdata_running(sdata)) if (ieee80211_sdata_running(sdata))
ieee80211_enable_keys(sdata); ieee80211_enable_keys(sdata);
wake_up:
ieee80211_wake_queues_by_reason(hw, ieee80211_wake_queues_by_reason(hw,
IEEE80211_QUEUE_STOP_REASON_SUSPEND); IEEE80211_QUEUE_STOP_REASON_SUSPEND);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment