#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);
}
}
+/*
+ * 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;
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);
}
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;
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);
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;
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);