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:
committed by
John W. Linville
parent
ff1b6e69ad
commit
eecc48000a
@@ -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 {
|
|||||||
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,
|
|||||||
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,
|
|||||||
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)
|
|||||||
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,
|
|||||||
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 {
|
|||||||
/* 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);
|
|||||||
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)
|
|||||||
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)
|
|||||||
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 @@
|
|||||||
#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)
|
|||||||
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)
|
|||||||
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)
|
|||||||
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)
|
|||||||
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);
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user