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_legacy = {
  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 const struct wiphy_wowlan_support ath9k_wowlan_support = {
  27        .flags = WIPHY_WOWLAN_MAGIC_PKT | WIPHY_WOWLAN_DISCONNECT,
  28        .n_patterns = MAX_NUM_PATTERN - 2,
  29        .pattern_min_len = 1,
  30        .pattern_max_len = MAX_PATTERN_SIZE,
  31};
  32
  33static u8 ath9k_wow_map_triggers(struct ath_softc *sc,
  34                                 struct cfg80211_wowlan *wowlan)
  35{
  36        u8 wow_triggers = 0;
  37
  38        if (wowlan->disconnect)
  39                wow_triggers |= AH_WOW_LINK_CHANGE |
  40                                AH_WOW_BEACON_MISS;
  41        if (wowlan->magic_pkt)
  42                wow_triggers |= AH_WOW_MAGIC_PATTERN_EN;
  43
  44        if (wowlan->n_patterns)
  45                wow_triggers |= AH_WOW_USER_PATTERN_EN;
  46
  47        return wow_triggers;
  48}
  49
  50static int ath9k_wow_add_disassoc_deauth_pattern(struct ath_softc *sc)
  51{
  52        struct ath_hw *ah = sc->sc_ah;
  53        struct ath_common *common = ath9k_hw_common(ah);
  54        int pattern_count = 0;
  55        int ret, i, byte_cnt = 0;
  56        u8 dis_deauth_pattern[MAX_PATTERN_SIZE];
  57        u8 dis_deauth_mask[MAX_PATTERN_SIZE];
  58
  59        memset(dis_deauth_pattern, 0, MAX_PATTERN_SIZE);
  60        memset(dis_deauth_mask, 0, MAX_PATTERN_SIZE);
  61
  62        /*
  63         * Create Dissassociate / Deauthenticate packet filter
  64         *
  65         *     2 bytes        2 byte    6 bytes   6 bytes  6 bytes
  66         *  +--------------+----------+---------+--------+--------+----
  67         *  + Frame Control+ Duration +   DA    +  SA    +  BSSID +
  68         *  +--------------+----------+---------+--------+--------+----
  69         *
  70         * The above is the management frame format for disassociate/
  71         * deauthenticate pattern, from this we need to match the first byte
  72         * of 'Frame Control' and DA, SA, and BSSID fields
  73         * (skipping 2nd byte of FC and Duration feild.
  74         *
  75         * Disassociate pattern
  76         * --------------------
  77         * Frame control = 00 00 1010
  78         * DA, SA, BSSID = x:x:x:x:x:x
  79         * Pattern will be A0000000 | x:x:x:x:x:x | x:x:x:x:x:x
  80         *                          | x:x:x:x:x:x  -- 22 bytes
  81         *
  82         * Deauthenticate pattern
  83         * ----------------------
  84         * Frame control = 00 00 1100
  85         * DA, SA, BSSID = x:x:x:x:x:x
  86         * Pattern will be C0000000 | x:x:x:x:x:x | x:x:x:x:x:x
  87         *                          | x:x:x:x:x:x  -- 22 bytes
  88         */
  89
  90        /* Fill out the mask with all FF's */
  91        for (i = 0; i < MAX_PATTERN_MASK_SIZE; i++)
  92                dis_deauth_mask[i] = 0xff;
  93
  94        /* copy the first byte of frame control field */
  95        dis_deauth_pattern[byte_cnt] = 0xa0;
  96        byte_cnt++;
  97
  98        /* skip 2nd byte of frame control and Duration field */
  99        byte_cnt += 3;
 100
 101        /*
 102         * need not match the destination mac address, it can be a broadcast
 103         * mac address or an unicast to this station
 104         */
 105        byte_cnt += 6;
 106
 107        /* copy the source mac address */
 108        memcpy((dis_deauth_pattern + byte_cnt), common->curbssid, ETH_ALEN);
 109
 110        byte_cnt += 6;
 111
 112        /* copy the bssid, its same as the source mac address */
 113        memcpy((dis_deauth_pattern + byte_cnt), common->curbssid, ETH_ALEN);
 114
 115        /* Create Disassociate pattern mask */
 116        dis_deauth_mask[0] = 0xfe;
 117        dis_deauth_mask[1] = 0x03;
 118        dis_deauth_mask[2] = 0xc0;
 119
 120        ret = ath9k_hw_wow_apply_pattern(ah, dis_deauth_pattern, dis_deauth_mask,
 121                                         pattern_count, byte_cnt);
 122        if (ret)
 123                goto exit;
 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        ret = ath9k_hw_wow_apply_pattern(ah, dis_deauth_pattern, dis_deauth_mask,
 133                                         pattern_count, byte_cnt);
 134exit:
 135        return ret;
 136}
 137
 138static int ath9k_wow_add_pattern(struct ath_softc *sc,
 139                                 struct cfg80211_wowlan *wowlan)
 140{
 141        struct ath_hw *ah = sc->sc_ah;
 142        struct cfg80211_pkt_pattern *patterns = wowlan->patterns;
 143        u8 wow_pattern[MAX_PATTERN_SIZE];
 144        u8 wow_mask[MAX_PATTERN_SIZE];
 145        int mask_len, ret = 0;
 146        s8 i = 0;
 147
 148        for (i = 0; i < wowlan->n_patterns; i++) {
 149                mask_len = DIV_ROUND_UP(patterns[i].pattern_len, 8);
 150                memset(wow_pattern, 0, MAX_PATTERN_SIZE);
 151                memset(wow_mask, 0, MAX_PATTERN_SIZE);
 152                memcpy(wow_pattern, patterns[i].pattern, patterns[i].pattern_len);
 153                memcpy(wow_mask, patterns[i].mask, mask_len);
 154
 155                ret = ath9k_hw_wow_apply_pattern(ah,
 156                                                 wow_pattern,
 157                                                 wow_mask,
 158                                                 i + 2,
 159                                                 patterns[i].pattern_len);
 160                if (ret)
 161                        break;
 162        }
 163
 164        return ret;
 165}
 166
 167int ath9k_suspend(struct ieee80211_hw *hw,
 168                  struct cfg80211_wowlan *wowlan)
 169{
 170        struct ath_softc *sc = hw->priv;
 171        struct ath_hw *ah = sc->sc_ah;
 172        struct ath_common *common = ath9k_hw_common(ah);
 173        u8 triggers;
 174        int ret = 0;
 175
 176        ath9k_deinit_channel_context(sc);
 177
 178        mutex_lock(&sc->mutex);
 179
 180        if (test_bit(ATH_OP_INVALID, &common->op_flags)) {
 181                ath_err(common, "Device not present\n");
 182                ret = -ENODEV;
 183                goto fail_wow;
 184        }
 185
 186        if (WARN_ON(!wowlan)) {
 187                ath_err(common, "None of the WoW triggers enabled\n");
 188                ret = -EINVAL;
 189                goto fail_wow;
 190        }
 191
 192        if (sc->cur_chan->nvifs > 1) {
 193                ath_dbg(common, WOW, "WoW for multivif is not yet supported\n");
 194                ret = 1;
 195                goto fail_wow;
 196        }
 197
 198        if (ath9k_is_chanctx_enabled()) {
 199                if (test_bit(ATH_OP_MULTI_CHANNEL, &common->op_flags)) {
 200                        ath_dbg(common, WOW,
 201                                "Multi-channel WOW is not supported\n");
 202                        ret = 1;
 203                        goto fail_wow;
 204                }
 205        }
 206
 207        if (!test_bit(ATH_OP_PRIM_STA_VIF, &common->op_flags)) {
 208                ath_dbg(common, WOW, "None of the STA vifs are associated\n");
 209                ret = 1;
 210                goto fail_wow;
 211        }
 212
 213        triggers = ath9k_wow_map_triggers(sc, wowlan);
 214        if (!triggers) {
 215                ath_dbg(common, WOW, "No valid WoW triggers\n");
 216                ret = 1;
 217                goto fail_wow;
 218        }
 219
 220        ath_cancel_work(sc);
 221        ath_stop_ani(sc);
 222
 223        ath9k_ps_wakeup(sc);
 224
 225        ath9k_stop_btcoex(sc);
 226
 227        /*
 228         * Enable wake up on recieving disassoc/deauth
 229         * frame by default.
 230         */
 231        ret = ath9k_wow_add_disassoc_deauth_pattern(sc);
 232        if (ret) {
 233                ath_err(common,
 234                        "Unable to add disassoc/deauth pattern: %d\n", ret);
 235                goto fail_wow;
 236        }
 237
 238        if (triggers & AH_WOW_USER_PATTERN_EN) {
 239                ret = ath9k_wow_add_pattern(sc, wowlan);
 240                if (ret) {
 241                        ath_err(common,
 242                                "Unable to add user pattern: %d\n", ret);
 243                        goto fail_wow;
 244                }
 245        }
 246
 247        spin_lock_bh(&sc->sc_pcu_lock);
 248        /*
 249         * To avoid false wake, we enable beacon miss interrupt only
 250         * when we go to sleep. We save the current interrupt mask
 251         * so we can restore it after the system wakes up
 252         */
 253        sc->wow_intr_before_sleep = ah->imask;
 254        ah->imask &= ~ATH9K_INT_GLOBAL;
 255        ath9k_hw_disable_interrupts(ah);
 256        ah->imask = ATH9K_INT_BMISS | ATH9K_INT_GLOBAL;
 257        ath9k_hw_set_interrupts(ah);
 258        ath9k_hw_enable_interrupts(ah);
 259
 260        spin_unlock_bh(&sc->sc_pcu_lock);
 261
 262        /*
 263         * we can now sync irq and kill any running tasklets, since we already
 264         * disabled interrupts and not holding a spin lock
 265         */
 266        synchronize_irq(sc->irq);
 267        tasklet_kill(&sc->intr_tq);
 268
 269        ath9k_hw_wow_enable(ah, triggers);
 270
 271        ath9k_ps_restore(sc);
 272        ath_dbg(common, WOW, "Suspend with WoW triggers: 0x%x\n", triggers);
 273
 274        set_bit(ATH_OP_WOW_ENABLED, &common->op_flags);
 275fail_wow:
 276        mutex_unlock(&sc->mutex);
 277        return ret;
 278}
 279
 280int ath9k_resume(struct ieee80211_hw *hw)
 281{
 282        struct ath_softc *sc = hw->priv;
 283        struct ath_hw *ah = sc->sc_ah;
 284        struct ath_common *common = ath9k_hw_common(ah);
 285        u8 status;
 286
 287        mutex_lock(&sc->mutex);
 288
 289        ath9k_ps_wakeup(sc);
 290
 291        spin_lock_bh(&sc->sc_pcu_lock);
 292
 293        ath9k_hw_disable_interrupts(ah);
 294        ah->imask = sc->wow_intr_before_sleep;
 295        ath9k_hw_set_interrupts(ah);
 296        ath9k_hw_enable_interrupts(ah);
 297
 298        spin_unlock_bh(&sc->sc_pcu_lock);
 299
 300        status = ath9k_hw_wow_wakeup(ah);
 301        ath_dbg(common, WOW, "Resume with WoW status: 0x%x\n", status);
 302
 303        ath_restart_work(sc);
 304        ath9k_start_btcoex(sc);
 305
 306        clear_bit(ATH_OP_WOW_ENABLED, &common->op_flags);
 307
 308        ath9k_ps_restore(sc);
 309        mutex_unlock(&sc->mutex);
 310
 311        return 0;
 312}
 313
 314void ath9k_set_wakeup(struct ieee80211_hw *hw, bool enabled)
 315{
 316        struct ath_softc *sc = hw->priv;
 317        struct ath_common *common = ath9k_hw_common(sc->sc_ah);
 318
 319        mutex_lock(&sc->mutex);
 320        device_set_wakeup_enable(sc->dev, enabled);
 321        mutex_unlock(&sc->mutex);
 322
 323        ath_dbg(common, WOW, "WoW wakeup source is %s\n",
 324                (enabled) ? "enabled" : "disabled");
 325}
 326
 327void ath9k_init_wow(struct ieee80211_hw *hw)
 328{
 329        struct ath_softc *sc = hw->priv;
 330        struct ath_hw *ah = sc->sc_ah;
 331
 332        if ((sc->driver_data & ATH9K_PCI_WOW) || sc->force_wow) {
 333                if (AR_SREV_9462_20_OR_LATER(ah) || AR_SREV_9565_11_OR_LATER(ah))
 334                        hw->wiphy->wowlan = &ath9k_wowlan_support;
 335                else
 336                        hw->wiphy->wowlan = &ath9k_wowlan_support_legacy;
 337
 338                device_init_wakeup(sc->dev, 1);
 339        }
 340}
 341
 342void ath9k_deinit_wow(struct ieee80211_hw *hw)
 343{
 344        struct ath_softc *sc = hw->priv;
 345
 346        if ((sc->driver_data & ATH9K_PCI_WOW) || sc->force_wow)
 347                device_init_wakeup(sc->dev, 0);
 348}
 349