diff options
-rw-r--r-- | drivers/net/wireless/ti/wlcore/main.c | 206 | ||||
-rw-r--r-- | 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 @@ 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); 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, |