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:
parent
ff1b6e69ad
commit
eecc48000a
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user