linux/drivers/net/wireless/ath/ath9k/wow.c
<<
>>
Prefs
   1/*
   2 * Copyright (c) 2013 Qualcomm Atheros, Inc.
   3 *
   4 * Permission to use, copy, modify, and/or distribute this software for any
   5 * purpose with or without fee is hereby granted, provided that the above
   6 * copyright notice and this permission notice appear in all copies.
   7 *
   8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
   9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  15 */
  16
  17#include "ath9k.h"
  18
  19static const struct wiphy_wowlan_support ath9k_wowlan_support = {
  20        .flags = WIPHY_WOWLAN_MAGIC_PKT | WIPHY_WOWLAN_DISCONNECT,
  21        .n_patterns = MAX_NUM_USER_PATTERN,
  22        .pattern_min_len = 1,
  23        .pattern_max_len = MAX_PATTERN_SIZE,
  24};
  25
  26static void ath9k_wow_map_triggers(struct ath_softc *sc,
  27                                   struct cfg80211_wowlan *wowlan,
  28                                   u32 *wow_triggers)
  29{
  30        if (wowlan->disconnect)
  31                *wow_triggers |= AH_WOW_LINK_CHANGE |
  32                                 AH_WOW_BEACON_MISS;
  33        if (wowlan->magic_pkt)
  34                *wow_triggers |= AH_WOW_MAGIC_PATTERN_EN;
  35
  36        if (wowlan->n_patterns)
  37                *wow_triggers |= AH_WOW_USER_PATTERN_EN;
  38
  39        sc->wow_enabled = *wow_triggers;
  40
  41}
  42
  43static void ath9k_wow_add_disassoc_deauth_pattern(struct ath_softc *sc)
  44{
  45        struct ath_hw *ah = sc->sc_ah;
  46        struct ath_common *common = ath9k_hw_common(ah);
  47        int pattern_count = 0;
  48        int i, byte_cnt;
  49        u8 dis_deauth_pattern[MAX_PATTERN_SIZE];
  50        u8 dis_deauth_mask[MAX_PATTERN_SIZE];
  51
  52        memset(dis_deauth_pattern, 0, MAX_PATTERN_SIZE);
  53        memset(dis_deauth_mask, 0, MAX_PATTERN_SIZE);
  54
  55        /*
  56         * Create Dissassociate / Deauthenticate packet filter
  57         *
  58         *     2 bytes        2 byte    6 bytes   6 bytes  6 bytes
  59         *  +--------------+----------+---------+--------+--------+----
  60         *  + Frame Control+ Duration +   DA    +  SA    +  BSSID +
  61         *  +--------------+----------+---------+--------+--------+----
  62         *
  63         * The above is the management frame format for disassociate/
  64         * deauthenticate pattern, from this we need to match the first byte
  65         * of 'Frame Control' and DA, SA, and BSSID fields
  66         * (skipping 2nd byte of FC and Duration feild.
  67         *
  68         * Disassociate pattern
  69         * --------------------
  70         * Frame control = 00 00 1010
  71         * DA, SA, BSSID = x:x:x:x:x:x
  72         * Pattern will be A0000000 | x:x:x:x:x:x | x:x:x:x:x:x
  73         *                          | x:x:x:x:x:x  -- 22 bytes
  74         *
  75         * Deauthenticate pattern
  76         * ----------------------
  77         * Frame control = 00 00 1100
  78         * DA, SA, BSSID = x:x:x:x:x:x
  79         * Pattern will be C0000000 | x:x:x:x:x:x | x:x:x:x:x:x
  80         *                          | x:x:x:x:x:x  -- 22 bytes
  81         */
  82
  83        /* Create Disassociate Pattern first */
  84
  85        byte_cnt = 0;
  86
  87        /* Fill out the mask with all FF's */
  88
  89        for (i = 0; i < MAX_PATTERN_MASK_SIZE; i++)
  90                dis_deauth_mask[i] = 0xff;
  91
  92        /* copy the first byte of frame control field */
  93        dis_deauth_pattern[byte_cnt] = 0xa0;
  94        byte_cnt++;
  95
  96        /* skip 2nd byte of frame control and Duration field */
  97        byte_cnt += 3;
  98
  99        /*
 100         * need not match the destination mac address, it can be a broadcast
 101         * mac address or an unicast to this station
 102         */
 103        byte_cnt += 6;
 104
 105        /* copy the source mac address */
 106        memcpy((dis_deauth_pattern + byte_cnt), common->curbssid, ETH_ALEN);
 107
 108        byte_cnt += 6;
 109
 110        /* copy the bssid, its same as the source mac address */
 111
 112        memcpy((dis_deauth_pattern + byte_cnt), common->curbssid, ETH_ALEN);
 113
 114        /* Create Disassociate pattern mask */
 115
 116        dis_deauth_mask[0] = 0xfe;
 117        dis_deauth_mask[1] = 0x03;
 118        dis_deauth_mask[2] = 0xc0;
 119
 120        ath_dbg(common, WOW, "Adding disassoc/deauth patterns for WoW\n");
 121
 122        ath9k_hw_wow_apply_pattern(ah, dis_deauth_pattern, dis_deauth_mask,
 123                                   pattern_count, byte_cnt);
 124
 125        pattern_count++;
 126        /*
 127         * for de-authenticate pattern, only the first byte of the frame
 128         * control field gets changed from 0xA0 to 0xC0
 129         */
 130        dis_deauth_pattern[0] = 0xC0;
 131
 132        ath9k_hw_wow_apply_pattern(ah, dis_deauth_pattern, dis_deauth_mask,
 133                                   pattern_count, byte_cnt);
 134
 135}
 136
 137static void ath9k_wow_add_pattern(struct ath_softc *sc,
 138                                  struct cfg80211_wowlan *wowlan)
 139{
 140        struct ath_hw *ah = sc->sc_ah;
 141        struct ath9k_wow_pattern *wow_pattern = NULL;
 142        struct cfg80211_pkt_pattern *patterns = wowlan->patterns;
 143        int mask_len;
 144        s8 i = 0;
 145
 146        if (!wowlan->n_patterns)
 147                return;
 148
 149        /*
 150         * Add the new user configured patterns
 151         */
 152        for (i = 0; i < wowlan->n_patterns; i++) {
 153
 154                wow_pattern = kzalloc(sizeof(*wow_pattern), GFP_KERNEL);
 155
 156                if (!wow_pattern)
 157                        return;
 158
 159                /*
 160                 * TODO: convert the generic user space pattern to
 161                 * appropriate chip specific/802.11 pattern.
 162                 */
 163
 164                mask_len = DIV_ROUND_UP(wowlan->patterns[i].pattern_len, 8);
 165                memset(wow_pattern->pattern_bytes, 0, MAX_PATTERN_SIZE);
 166                memset(wow_pattern->mask_bytes, 0, MAX_PATTERN_SIZE);
 167                memcpy(wow_pattern->pattern_bytes, patterns[i].pattern,
 168                       patterns[i].pattern_len);
 169                memcpy(wow_pattern->mask_bytes, patterns[i].mask, mask_len);
 170                wow_pattern->pattern_len = patterns[i].pattern_len;
 171
 172                /*
 173                 * just need to take care of deauth and disssoc pattern,
 174                 * make sure we don't overwrite them.
 175                 */
 176
 177                ath9k_hw_wow_apply_pattern(ah, wow_pattern->pattern_bytes,
 178                                           wow_pattern->mask_bytes,
 179                                           i + 2,
 180                                           wow_pattern->pattern_len);
 181                kfree(wow_pattern);
 182
 183        }
 184
 185}
 186
 187int ath9k_suspend(struct ieee80211_hw *hw,
 188                  struct cfg80211_wowlan *wowlan)
 189{
 190        struct ath_softc *sc = hw->priv;
 191        struct ath_hw *ah = sc->sc_ah;
 192        struct ath_common *common = ath9k_hw_common(ah);
 193        u32 wow_triggers_enabled = 0;
 194        int ret = 0;
 195
 196        mutex_lock(&sc->mutex);
 197
 198        ath_cancel_work(sc);
 199        ath_stop_ani(sc);
 200
 201        if (test_bit(ATH_OP_INVALID, &common->op_flags)) {
 202                ath_dbg(common, ANY, "Device not present\n");
 203                ret = -EINVAL;
 204                goto fail_wow;
 205        }
 206
 207        if (WARN_ON(!wowlan)) {
 208                ath_dbg(common, WOW, "None of the WoW triggers enabled\n");
 209                ret = -EINVAL;
 210                goto fail_wow;
 211        }
 212
 213        if (!device_can_wakeup(sc->dev)) {
 214                ath_dbg(common, WOW, "device_can_wakeup failed, WoW is not enabled\n");
 215                ret = 1;
 216                goto fail_wow;
 217        }
 218
 219        /*
 220         * none of the sta vifs are associated
 221         * and we are not currently handling multivif
 222         * cases, for instance we have to seperately
 223         * configure 'keep alive frame' for each
 224         * STA.
 225         */
 226
 227        if (!test_bit(ATH_OP_PRIM_STA_VIF, &common->op_flags)) {
 228                ath_dbg(common, WOW, "None of the STA vifs are associated\n");
 229                ret = 1;
 230                goto fail_wow;
 231        }
 232
 233        if (sc->nvifs > 1) {
 234                ath_dbg(common, WOW, "WoW for multivif is not yet supported\n");
 235                ret = 1;
 236                goto fail_wow;
 237        }
 238
 239        ath9k_wow_map_triggers(sc, wowlan, &wow_triggers_enabled);
 240
 241        ath_dbg(common, WOW, "WoW triggers enabled 0x%x\n",
 242                wow_triggers_enabled);
 243
 244        ath9k_ps_wakeup(sc);
 245
 246        ath9k_stop_btcoex(sc);
 247
 248        /*
 249         * Enable wake up on recieving disassoc/deauth
 250         * frame by default.
 251         */
 252        ath9k_wow_add_disassoc_deauth_pattern(sc);
 253
 254        if (wow_triggers_enabled & AH_WOW_USER_PATTERN_EN)
 255                ath9k_wow_add_pattern(sc, wowlan);
 256
 257        spin_lock_bh(&sc->sc_pcu_lock);
 258        /*
 259         * To avoid false wake, we enable beacon miss interrupt only
 260         * when we go to sleep. We save the current interrupt mask
 261         * so we can restore it after the system wakes up
 262         */
 263        sc->wow_intr_before_sleep = ah->imask;
 264        ah->imask &= ~ATH9K_INT_GLOBAL;
 265        ath9k_hw_disable_interrupts(ah);
 266        ah->imask = ATH9K_INT_BMISS | ATH9K_INT_GLOBAL;
 267        ath9k_hw_set_interrupts(ah);
 268        ath9k_hw_enable_interrupts(ah);
 269
 270        spin_unlock_bh(&sc->sc_pcu_lock);
 271
 272        /*
 273         * we can now sync irq and kill any running tasklets, since we already
 274         * disabled interrupts and not holding a spin lock
 275         */
 276        synchronize_irq(sc->irq);
 277        tasklet_kill(&sc->intr_tq);
 278
 279        ath9k_hw_wow_enable(ah, wow_triggers_enabled);
 280
 281        ath9k_ps_restore(sc);
 282        ath_dbg(common, ANY, "WoW enabled in ath9k\n");
 283        atomic_inc(&sc->wow_sleep_proc_intr);
 284
 285fail_wow:
 286        mutex_unlock(&sc->mutex);
 287        return ret;
 288}
 289
 290int ath9k_resume(struct ieee80211_hw *hw)
 291{
 292        struct ath_softc *sc = hw->priv;
 293        struct ath_hw *ah = sc->sc_ah;
 294        struct ath_common *common = ath9k_hw_common(ah);
 295        u32 wow_status;
 296
 297        mutex_lock(&sc->mutex);
 298
 299        ath9k_ps_wakeup(sc);
 300
 301        spin_lock_bh(&sc->sc_pcu_lock);
 302
 303        ath9k_hw_disable_interrupts(ah);
 304        ah->imask = sc->wow_intr_before_sleep;
 305        ath9k_hw_set_interrupts(ah);
 306        ath9k_hw_enable_interrupts(ah);
 307
 308        spin_unlock_bh(&sc->sc_pcu_lock);
 309
 310        wow_status = ath9k_hw_wow_wakeup(ah);
 311
 312        if (atomic_read(&sc->wow_got_bmiss_intr) == 0) {
 313                /*
 314                 * some devices may not pick beacon miss
 315                 * as the reason they woke up so we add
 316                 * that here for that shortcoming.
 317                 */
 318                wow_status |= AH_WOW_BEACON_MISS;
 319                atomic_dec(&sc->wow_got_bmiss_intr);
 320                ath_dbg(common, ANY, "Beacon miss interrupt picked up during WoW sleep\n");
 321        }
 322
 323        atomic_dec(&sc->wow_sleep_proc_intr);
 324
 325        if (wow_status) {
 326                ath_dbg(common, ANY, "Waking up due to WoW triggers %s with WoW status = %x\n",
 327                        ath9k_hw_wow_event_to_string(wow_status), wow_status);
 328        }
 329
 330        ath_restart_work(sc);
 331        ath9k_start_btcoex(sc);
 332
 333        ath9k_ps_restore(sc);
 334        mutex_unlock(&sc->mutex);
 335
 336        return 0;
 337}
 338
 339void ath9k_set_wakeup(struct ieee80211_hw *hw, bool enabled)
 340{
 341        struct ath_softc *sc = hw->priv;
 342
 343        mutex_lock(&sc->mutex);
 344        device_init_wakeup(sc->dev, 1);
 345        device_set_wakeup_enable(sc->dev, enabled);
 346        mutex_unlock(&sc->mutex);
 347}
 348
 349void ath9k_init_wow(struct ieee80211_hw *hw)
 350{
 351        struct ath_softc *sc = hw->priv;
 352
 353        if ((sc->sc_ah->caps.hw_caps & ATH9K_HW_WOW_DEVICE_CAPABLE) &&
 354            (sc->driver_data & ATH9K_PCI_WOW) &&
 355            device_can_wakeup(sc->dev))
 356                hw->wiphy->wowlan = &ath9k_wowlan_support;
 357
 358        atomic_set(&sc->wow_sleep_proc_intr, -1);
 359        atomic_set(&sc->wow_got_bmiss_intr, -1);
 360}
 361