From b95d7cef067e12926b6b69a9164cdea301dd6fc0 Mon Sep 17 00:00:00 2001 From: Eyal Shapira Date: Wed, 14 Mar 2012 06:32:10 +0200 Subject: [PATCH] wl12xx: support wowlan wakeup patterns 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 Signed-off-by: Luciano Coelho --- drivers/net/wireless/ti/wlcore/main.c | 206 +++++++++++++++++++++++- drivers/net/wireless/ti/wlcore/wl12xx.h | 12 ++ 2 files changed, 211 insertions(+), 7 deletions(-) diff --git a/drivers/net/wireless/ti/wlcore/main.c b/drivers/net/wireless/ti/wlcore/main.c index 425a91c48202..4347de6784d8 100644 --- a/drivers/net/wireless/ti/wlcore/main.c +++ b/drivers/net/wireless/ti/wlcore/main.c @@ -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 @@ static int wl1271_configure_suspend_ap(struct wl1271 *wl, } 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); diff --git a/drivers/net/wireless/ti/wlcore/wl12xx.h b/drivers/net/wireless/ti/wlcore/wl12xx.h index 12558cf3437b..f12bdf745180 100644 --- a/drivers/net/wireless/ti/wlcore/wl12xx.h +++ b/drivers/net/wireless/ti/wlcore/wl12xx.h @@ -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, -- GitLab