wl12xx: support wowlan wakeup patterns
authorEyal Shapira <eyal@wizery.com>
Wed, 14 Mar 2012 04:32:10 +0000 (06:32 +0200)
committerJohn W. Linville <linville@tuxdriver.com>
Tue, 15 May 2012 21:53:23 +0000 (17:53 -0400)
Use FW RX data filters to support cfg80211 wowlan wakeup patterns.
This enables to wake up the host from suspend following detection
of certain configurable patterns within an incoming packet.
Up to 5 patterns are supported. Once the host is resumed
any configured RX data filter is cleared.
A single pattern can match several bytes sequences with different
offsets within a packet.

Signed-off-by: Eyal Shapira <eyal@wizery.com>
Signed-off-by: Luciano Coelho <coelho@ti.com>
drivers/net/wireless/ti/wlcore/main.c
drivers/net/wireless/ti/wlcore/wl12xx.h

index 425a91c..4347de6 100644 (file)
@@ -1261,6 +1261,67 @@ static struct sk_buff *wl12xx_alloc_dummy_packet(struct wl1271 *wl)
 
 
 #ifdef CONFIG_PM
+int wl1271_validate_wowlan_pattern(struct cfg80211_wowlan_trig_pkt_pattern *p)
+{
+       int num_fields = 0, in_field = 0, fields_size = 0;
+       int i, pattern_len = 0;
+
+       if (!p->mask) {
+               wl1271_warning("No mask in WoWLAN pattern");
+               return -EINVAL;
+       }
+
+       /*
+        * The pattern is broken up into segments of bytes at different offsets
+        * that need to be checked by the FW filter. Each segment is called
+        * a field in the FW API. We verify that the total number of fields
+        * required for this pattern won't exceed FW limits (8)
+        * as well as the total fields buffer won't exceed the FW limit.
+        * Note that if there's a pattern which crosses Ethernet/IP header
+        * boundary a new field is required.
+        */
+       for (i = 0; i < p->pattern_len; i++) {
+               if (test_bit(i, (unsigned long *)p->mask)) {
+                       if (!in_field) {
+                               in_field = 1;
+                               pattern_len = 1;
+                       } else {
+                               if (i == WL1271_RX_FILTER_ETH_HEADER_SIZE) {
+                                       num_fields++;
+                                       fields_size += pattern_len +
+                                               RX_FILTER_FIELD_OVERHEAD;
+                                       pattern_len = 1;
+                               } else
+                                       pattern_len++;
+                       }
+               } else {
+                       if (in_field) {
+                               in_field = 0;
+                               fields_size += pattern_len +
+                                       RX_FILTER_FIELD_OVERHEAD;
+                               num_fields++;
+                       }
+               }
+       }
+
+       if (in_field) {
+               fields_size += pattern_len + RX_FILTER_FIELD_OVERHEAD;
+               num_fields++;
+       }
+
+       if (num_fields > WL1271_RX_FILTER_MAX_FIELDS) {
+               wl1271_warning("RX Filter too complex. Too many segments");
+               return -EINVAL;
+       }
+
+       if (fields_size > WL1271_RX_FILTER_MAX_FIELDS_SIZE) {
+               wl1271_warning("RX filter pattern is too big");
+               return -E2BIG;
+       }
+
+       return 0;
+}
+
 struct wl12xx_rx_filter *wl1271_rx_filter_alloc(void)
 {
        return kzalloc(sizeof(struct wl12xx_rx_filter), GFP_KERNEL);
@@ -1339,8 +1400,130 @@ void wl1271_rx_filter_flatten_fields(struct wl12xx_rx_filter *filter,
        }
 }
 
+/*
+ * Allocates an RX filter returned through f
+ * which needs to be freed using rx_filter_free()
+ */
+int wl1271_convert_wowlan_pattern_to_rx_filter(
+       struct cfg80211_wowlan_trig_pkt_pattern *p,
+       struct wl12xx_rx_filter **f)
+{
+       int i, j, ret = 0;
+       struct wl12xx_rx_filter *filter;
+       u16 offset;
+       u8 flags, len;
+
+       filter = wl1271_rx_filter_alloc();
+       if (!filter) {
+               wl1271_warning("Failed to alloc rx filter");
+               ret = -ENOMEM;
+               goto err;
+       }
+
+       i = 0;
+       while (i < p->pattern_len) {
+               if (!test_bit(i, (unsigned long *)p->mask)) {
+                       i++;
+                       continue;
+               }
+
+               for (j = i; j < p->pattern_len; j++) {
+                       if (!test_bit(j, (unsigned long *)p->mask))
+                               break;
+
+                       if (i < WL1271_RX_FILTER_ETH_HEADER_SIZE &&
+                           j >= WL1271_RX_FILTER_ETH_HEADER_SIZE)
+                               break;
+               }
+
+               if (i < WL1271_RX_FILTER_ETH_HEADER_SIZE) {
+                       offset = i;
+                       flags = WL1271_RX_FILTER_FLAG_ETHERNET_HEADER;
+               } else {
+                       offset = i - WL1271_RX_FILTER_ETH_HEADER_SIZE;
+                       flags = WL1271_RX_FILTER_FLAG_IP_HEADER;
+               }
+
+               len = j - i;
+
+               ret = wl1271_rx_filter_alloc_field(filter,
+                                                  offset,
+                                                  flags,
+                                                  &p->pattern[i], len);
+               if (ret)
+                       goto err;
+
+               i = j;
+       }
+
+       filter->action = FILTER_SIGNAL;
+
+       *f = filter;
+       return 0;
+
+err:
+       wl1271_rx_filter_free(filter);
+       *f = NULL;
+
+       return ret;
+}
+
+static int wl1271_configure_wowlan(struct wl1271 *wl,
+                                  struct cfg80211_wowlan *wow)
+{
+       int i, ret;
+
+       if (!wow || wow->any || !wow->n_patterns) {
+               wl1271_acx_default_rx_filter_enable(wl, 0, FILTER_SIGNAL);
+               wl1271_rx_filter_clear_all(wl);
+               return 0;
+       }
+
+       if (WARN_ON(wow->n_patterns > WL1271_MAX_RX_FILTERS))
+               return -EINVAL;
+
+       /* Validate all incoming patterns before clearing current FW state */
+       for (i = 0; i < wow->n_patterns; i++) {
+               ret = wl1271_validate_wowlan_pattern(&wow->patterns[i]);
+               if (ret) {
+                       wl1271_warning("Bad wowlan pattern %d", i);
+                       return ret;
+               }
+       }
+
+       wl1271_acx_default_rx_filter_enable(wl, 0, FILTER_SIGNAL);
+       wl1271_rx_filter_clear_all(wl);
+
+       /* Translate WoWLAN patterns into filters */
+       for (i = 0; i < wow->n_patterns; i++) {
+               struct cfg80211_wowlan_trig_pkt_pattern *p;
+               struct wl12xx_rx_filter *filter = NULL;
+
+               p = &wow->patterns[i];
+
+               ret = wl1271_convert_wowlan_pattern_to_rx_filter(p, &filter);
+               if (ret) {
+                       wl1271_warning("Failed to create an RX filter from "
+                                      "wowlan pattern %d", i);
+                       goto out;
+               }
+
+               ret = wl1271_rx_filter_enable(wl, i, 1, filter);
+
+               wl1271_rx_filter_free(filter);
+               if (ret)
+                       goto out;
+       }
+
+       ret = wl1271_acx_default_rx_filter_enable(wl, 1, FILTER_DROP);
+
+out:
+       return ret;
+}
+
 static int wl1271_configure_suspend_sta(struct wl1271 *wl,
-                                       struct wl12xx_vif *wlvif)
+                                       struct wl12xx_vif *wlvif,
+                                       struct cfg80211_wowlan *wow)
 {
        int ret = 0;
 
@@ -1351,6 +1534,7 @@ static int wl1271_configure_suspend_sta(struct wl1271 *wl,
        if (ret < 0)
                goto out;
 
+       wl1271_configure_wowlan(wl, wow);
        ret = wl1271_acx_wake_up_conditions(wl, wlvif,
                                    wl->conf.conn.suspend_wake_up_event,
                                    wl->conf.conn.suspend_listen_interval);
@@ -1386,10 +1570,11 @@ out:
 }
 
 static int wl1271_configure_suspend(struct wl1271 *wl,
-                                   struct wl12xx_vif *wlvif)
+                                   struct wl12xx_vif *wlvif,
+                                   struct cfg80211_wowlan *wow)
 {
        if (wlvif->bss_type == BSS_TYPE_STA_BSS)
-               return wl1271_configure_suspend_sta(wl, wlvif);
+               return wl1271_configure_suspend_sta(wl, wlvif, wow);
        if (wlvif->bss_type == BSS_TYPE_AP_BSS)
                return wl1271_configure_suspend_ap(wl, wlvif);
        return 0;
@@ -1410,6 +1595,8 @@ static void wl1271_configure_resume(struct wl1271 *wl,
                return;
 
        if (is_sta) {
+               wl1271_configure_wowlan(wl, NULL);
+
                ret = wl1271_acx_wake_up_conditions(wl, wlvif,
                                    wl->conf.conn.wake_up_event,
                                    wl->conf.conn.listen_interval);
@@ -1433,14 +1620,14 @@ static int wl1271_op_suspend(struct ieee80211_hw *hw,
        int ret;
 
        wl1271_debug(DEBUG_MAC80211, "mac80211 suspend wow=%d", !!wow);
-       WARN_ON(!wow || !wow->any);
+       WARN_ON(!wow);
 
        wl1271_tx_flush(wl);
 
        mutex_lock(&wl->mutex);
        wl->wow_enabled = true;
        wl12xx_for_each_wlvif(wl, wlvif) {
-               ret = wl1271_configure_suspend(wl, wlvif);
+               ret = wl1271_configure_suspend(wl, wlvif, wow);
                if (ret < 0) {
                        wl1271_warning("couldn't prepare device to suspend");
                        return ret;
@@ -5081,9 +5268,14 @@ int __devinit wlcore_probe(struct wl1271 *wl, struct platform_device *pdev)
        if (!ret) {
                wl->irq_wake_enabled = true;
                device_init_wakeup(wl->dev, 1);
-               if (pdata->pwr_in_suspend)
+               if (pdata->pwr_in_suspend) {
                        wl->hw->wiphy->wowlan.flags = WIPHY_WOWLAN_ANY;
-
+                       wl->hw->wiphy->wowlan.n_patterns =
+                               WL1271_MAX_RX_FILTERS;
+                       wl->hw->wiphy->wowlan.pattern_min_len = 1;
+                       wl->hw->wiphy->wowlan.pattern_max_len =
+                               WL1271_RX_FILTER_MAX_PATTERN_SIZE;
+               }
        }
        disable_irq(wl->irq);
 
index 12558cf..f12bdf7 100644 (file)
@@ -281,6 +281,18 @@ struct wl1271_link {
 
 #define WL1271_MAX_RX_FILTERS 5
 #define WL1271_RX_FILTER_MAX_FIELDS 8
+
+#define WL1271_RX_FILTER_ETH_HEADER_SIZE 14
+#define WL1271_RX_FILTER_MAX_FIELDS_SIZE 95
+#define RX_FILTER_FIELD_OVERHEAD                               \
+       (sizeof(struct wl12xx_rx_filter_field) - sizeof(u8 *))
+#define WL1271_RX_FILTER_MAX_PATTERN_SIZE                      \
+       (WL1271_RX_FILTER_MAX_FIELDS_SIZE - RX_FILTER_FIELD_OVERHEAD)
+
+#define WL1271_RX_FILTER_FLAG_MASK                BIT(0)
+#define WL1271_RX_FILTER_FLAG_IP_HEADER           0
+#define WL1271_RX_FILTER_FLAG_ETHERNET_HEADER     BIT(1)
+
 enum rx_filter_action {
        FILTER_DROP = 0,
        FILTER_SIGNAL = 1,