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 63f75a06043b914c7b1237b9bea05a19552597ea..9e5542794b95d174c1ff19a9699ffd29c1f42f21 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 321d598eb8cb03802482dda0e84dc784865d1047..1ebc13383ae790321b3c10e59ebd71314a0efccb 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 0a602dbfdb2bd988e60b28b4c4ca37e55d44f91a..186e02f7cc32dd4bd30a9176778261dd333c4437 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 2ddb56e5b51f240093d05347dae78629afe36e84..aa16bd8ef7891a925a3edcc1e86a358f75fefd04 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 191e834ec46b11efdc21689ad45f9cf9bc92ceef..11e1ea5111ea505456a76c6e52394bf8b0bc11f5 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 9e3b4f0f31bd6886a7fa073f29c1cbab19976a8b..e89bc27f8dc3432b85fce17ed96b03073f17b416 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 d8be1986a10852750d96214a966164d848bba206..cb326d36be9ca933c516c9ad75b66ef434e4febd 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 042461710880768821dd3c36c6bbdd7bd2728099..730778a2c90c05d3c2e76d6f1f183f004e27ed9e 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 ef0560a2346a1b7869f4518baa7941e272707bed..d3fe2d2374859566d0c968a949aff2536dadf60d 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);