mac80211: add basic support for WoWLAN
authorJohannes Berg <johannes.berg@intel.com>
Wed, 4 May 2011 13:37:29 +0000 (15:37 +0200)
committerJohn W. Linville <linville@tuxdriver.com>
Thu, 5 May 2011 18:59:20 +0000 (14:59 -0400)
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>
include/net/mac80211.h
net/mac80211/cfg.c
net/mac80211/debugfs.c
net/mac80211/driver-ops.h
net/mac80211/driver-trace.h
net/mac80211/ieee80211_i.h
net/mac80211/main.c
net/mac80211/pm.c
net/mac80211/util.c

index 63f75a0..9e55427 100644 (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,
index 321d598..1ebc133 100644 (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)
index 0a602db..186e02f 100644 (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();
 
index 2ddb56e..aa16bd8 100644 (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)
 {
index 191e834..11e1ea5 100644 (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)
index 9e3b4f0..e89bc27 100644 (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;
 }
index d8be198..cb326d3 100644 (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;
 
index 0424617..730778a 100644 (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();
index ef0560a..d3fe2d2 100644 (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);