linux/drivers/net/wireless/ath/ath10k/wow.c
<<
>>
Prefs
   1/*
   2 * Copyright (c) 2015 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 "mac.h"
  18
  19#include <net/mac80211.h>
  20#include "hif.h"
  21#include "core.h"
  22#include "debug.h"
  23#include "wmi.h"
  24#include "wmi-ops.h"
  25
  26static const struct wiphy_wowlan_support ath10k_wowlan_support = {
  27        .flags = WIPHY_WOWLAN_DISCONNECT |
  28                 WIPHY_WOWLAN_MAGIC_PKT,
  29        .pattern_min_len = WOW_MIN_PATTERN_SIZE,
  30        .pattern_max_len = WOW_MAX_PATTERN_SIZE,
  31        .max_pkt_offset = WOW_MAX_PKT_OFFSET,
  32};
  33
  34static int ath10k_wow_vif_cleanup(struct ath10k_vif *arvif)
  35{
  36        struct ath10k *ar = arvif->ar;
  37        int i, ret;
  38
  39        for (i = 0; i < WOW_EVENT_MAX; i++) {
  40                ret = ath10k_wmi_wow_add_wakeup_event(ar, arvif->vdev_id, i, 0);
  41                if (ret) {
  42                        ath10k_warn(ar, "failed to issue wow wakeup for event %s on vdev %i: %d\n",
  43                                    wow_wakeup_event(i), arvif->vdev_id, ret);
  44                        return ret;
  45                }
  46        }
  47
  48        for (i = 0; i < ar->wow.max_num_patterns; i++) {
  49                ret = ath10k_wmi_wow_del_pattern(ar, arvif->vdev_id, i);
  50                if (ret) {
  51                        ath10k_warn(ar, "failed to delete wow pattern %d for vdev %i: %d\n",
  52                                    i, arvif->vdev_id, ret);
  53                        return ret;
  54                }
  55        }
  56
  57        return 0;
  58}
  59
  60static int ath10k_wow_cleanup(struct ath10k *ar)
  61{
  62        struct ath10k_vif *arvif;
  63        int ret;
  64
  65        lockdep_assert_held(&ar->conf_mutex);
  66
  67        list_for_each_entry(arvif, &ar->arvifs, list) {
  68                ret = ath10k_wow_vif_cleanup(arvif);
  69                if (ret) {
  70                        ath10k_warn(ar, "failed to clean wow wakeups on vdev %i: %d\n",
  71                                    arvif->vdev_id, ret);
  72                        return ret;
  73                }
  74        }
  75
  76        return 0;
  77}
  78
  79static int ath10k_vif_wow_set_wakeups(struct ath10k_vif *arvif,
  80                                      struct cfg80211_wowlan *wowlan)
  81{
  82        int ret, i;
  83        unsigned long wow_mask = 0;
  84        struct ath10k *ar = arvif->ar;
  85        const struct cfg80211_pkt_pattern *patterns = wowlan->patterns;
  86        int pattern_id = 0;
  87
  88        /* Setup requested WOW features */
  89        switch (arvif->vdev_type) {
  90        case WMI_VDEV_TYPE_IBSS:
  91                __set_bit(WOW_BEACON_EVENT, &wow_mask);
  92                 /* fall through */
  93        case WMI_VDEV_TYPE_AP:
  94                __set_bit(WOW_DEAUTH_RECVD_EVENT, &wow_mask);
  95                __set_bit(WOW_DISASSOC_RECVD_EVENT, &wow_mask);
  96                __set_bit(WOW_PROBE_REQ_WPS_IE_EVENT, &wow_mask);
  97                __set_bit(WOW_AUTH_REQ_EVENT, &wow_mask);
  98                __set_bit(WOW_ASSOC_REQ_EVENT, &wow_mask);
  99                __set_bit(WOW_HTT_EVENT, &wow_mask);
 100                __set_bit(WOW_RA_MATCH_EVENT, &wow_mask);
 101                break;
 102        case WMI_VDEV_TYPE_STA:
 103                if (wowlan->disconnect) {
 104                        __set_bit(WOW_DEAUTH_RECVD_EVENT, &wow_mask);
 105                        __set_bit(WOW_DISASSOC_RECVD_EVENT, &wow_mask);
 106                        __set_bit(WOW_BMISS_EVENT, &wow_mask);
 107                        __set_bit(WOW_CSA_IE_EVENT, &wow_mask);
 108                }
 109
 110                if (wowlan->magic_pkt)
 111                        __set_bit(WOW_MAGIC_PKT_RECVD_EVENT, &wow_mask);
 112                break;
 113        default:
 114                break;
 115        }
 116
 117        for (i = 0; i < wowlan->n_patterns; i++) {
 118                u8 bitmask[WOW_MAX_PATTERN_SIZE] = {};
 119                int j;
 120
 121                if (patterns[i].pattern_len > WOW_MAX_PATTERN_SIZE)
 122                        continue;
 123
 124                /* convert bytemask to bitmask */
 125                for (j = 0; j < patterns[i].pattern_len; j++)
 126                        if (patterns[i].mask[j / 8] & BIT(j % 8))
 127                                bitmask[j] = 0xff;
 128
 129                ret = ath10k_wmi_wow_add_pattern(ar, arvif->vdev_id,
 130                                                 pattern_id,
 131                                                 patterns[i].pattern,
 132                                                 bitmask,
 133                                                 patterns[i].pattern_len,
 134                                                 patterns[i].pkt_offset);
 135                if (ret) {
 136                        ath10k_warn(ar, "failed to add pattern %i to vdev %i: %d\n",
 137                                    pattern_id,
 138                                    arvif->vdev_id, ret);
 139                        return ret;
 140                }
 141
 142                pattern_id++;
 143                __set_bit(WOW_PATTERN_MATCH_EVENT, &wow_mask);
 144        }
 145
 146        for (i = 0; i < WOW_EVENT_MAX; i++) {
 147                if (!test_bit(i, &wow_mask))
 148                        continue;
 149                ret = ath10k_wmi_wow_add_wakeup_event(ar, arvif->vdev_id, i, 1);
 150                if (ret) {
 151                        ath10k_warn(ar, "failed to enable wakeup event %s on vdev %i: %d\n",
 152                                    wow_wakeup_event(i), arvif->vdev_id, ret);
 153                        return ret;
 154                }
 155        }
 156
 157        return 0;
 158}
 159
 160static int ath10k_wow_set_wakeups(struct ath10k *ar,
 161                                  struct cfg80211_wowlan *wowlan)
 162{
 163        struct ath10k_vif *arvif;
 164        int ret;
 165
 166        lockdep_assert_held(&ar->conf_mutex);
 167
 168        list_for_each_entry(arvif, &ar->arvifs, list) {
 169                ret = ath10k_vif_wow_set_wakeups(arvif, wowlan);
 170                if (ret) {
 171                        ath10k_warn(ar, "failed to set wow wakeups on vdev %i: %d\n",
 172                                    arvif->vdev_id, ret);
 173                        return ret;
 174                }
 175        }
 176
 177        return 0;
 178}
 179
 180static int ath10k_wow_enable(struct ath10k *ar)
 181{
 182        int ret;
 183
 184        lockdep_assert_held(&ar->conf_mutex);
 185
 186        reinit_completion(&ar->target_suspend);
 187
 188        ret = ath10k_wmi_wow_enable(ar);
 189        if (ret) {
 190                ath10k_warn(ar, "failed to issue wow enable: %d\n", ret);
 191                return ret;
 192        }
 193
 194        ret = wait_for_completion_timeout(&ar->target_suspend, 3 * HZ);
 195        if (ret == 0) {
 196                ath10k_warn(ar, "timed out while waiting for suspend completion\n");
 197                return -ETIMEDOUT;
 198        }
 199
 200        return 0;
 201}
 202
 203static int ath10k_wow_wakeup(struct ath10k *ar)
 204{
 205        int ret;
 206
 207        lockdep_assert_held(&ar->conf_mutex);
 208
 209        reinit_completion(&ar->wow.wakeup_completed);
 210
 211        ret = ath10k_wmi_wow_host_wakeup_ind(ar);
 212        if (ret) {
 213                ath10k_warn(ar, "failed to send wow wakeup indication: %d\n",
 214                            ret);
 215                return ret;
 216        }
 217
 218        ret = wait_for_completion_timeout(&ar->wow.wakeup_completed, 3 * HZ);
 219        if (ret == 0) {
 220                ath10k_warn(ar, "timed out while waiting for wow wakeup completion\n");
 221                return -ETIMEDOUT;
 222        }
 223
 224        return 0;
 225}
 226
 227int ath10k_wow_op_suspend(struct ieee80211_hw *hw,
 228                          struct cfg80211_wowlan *wowlan)
 229{
 230        struct ath10k *ar = hw->priv;
 231        int ret;
 232
 233        mutex_lock(&ar->conf_mutex);
 234
 235        if (WARN_ON(!test_bit(ATH10K_FW_FEATURE_WOWLAN_SUPPORT,
 236                              ar->running_fw->fw_file.fw_features))) {
 237                ret = 1;
 238                goto exit;
 239        }
 240
 241        ret =  ath10k_wow_cleanup(ar);
 242        if (ret) {
 243                ath10k_warn(ar, "failed to clear wow wakeup events: %d\n",
 244                            ret);
 245                goto exit;
 246        }
 247
 248        ret = ath10k_wow_set_wakeups(ar, wowlan);
 249        if (ret) {
 250                ath10k_warn(ar, "failed to set wow wakeup events: %d\n",
 251                            ret);
 252                goto cleanup;
 253        }
 254
 255        ret = ath10k_wow_enable(ar);
 256        if (ret) {
 257                ath10k_warn(ar, "failed to start wow: %d\n", ret);
 258                goto cleanup;
 259        }
 260
 261        ret = ath10k_hif_suspend(ar);
 262        if (ret) {
 263                ath10k_warn(ar, "failed to suspend hif: %d\n", ret);
 264                goto wakeup;
 265        }
 266
 267        goto exit;
 268
 269wakeup:
 270        ath10k_wow_wakeup(ar);
 271
 272cleanup:
 273        ath10k_wow_cleanup(ar);
 274
 275exit:
 276        mutex_unlock(&ar->conf_mutex);
 277        return ret ? 1 : 0;
 278}
 279
 280int ath10k_wow_op_resume(struct ieee80211_hw *hw)
 281{
 282        struct ath10k *ar = hw->priv;
 283        int ret;
 284
 285        mutex_lock(&ar->conf_mutex);
 286
 287        if (WARN_ON(!test_bit(ATH10K_FW_FEATURE_WOWLAN_SUPPORT,
 288                              ar->running_fw->fw_file.fw_features))) {
 289                ret = 1;
 290                goto exit;
 291        }
 292
 293        ret = ath10k_hif_resume(ar);
 294        if (ret) {
 295                ath10k_warn(ar, "failed to resume hif: %d\n", ret);
 296                goto exit;
 297        }
 298
 299        ret = ath10k_wow_wakeup(ar);
 300        if (ret)
 301                ath10k_warn(ar, "failed to wakeup from wow: %d\n", ret);
 302
 303exit:
 304        if (ret) {
 305                switch (ar->state) {
 306                case ATH10K_STATE_ON:
 307                        ar->state = ATH10K_STATE_RESTARTING;
 308                        ret = 1;
 309                        break;
 310                case ATH10K_STATE_OFF:
 311                case ATH10K_STATE_RESTARTING:
 312                case ATH10K_STATE_RESTARTED:
 313                case ATH10K_STATE_UTF:
 314                case ATH10K_STATE_WEDGED:
 315                        ath10k_warn(ar, "encountered unexpected device state %d on resume, cannot recover\n",
 316                                    ar->state);
 317                        ret = -EIO;
 318                        break;
 319                }
 320        }
 321
 322        mutex_unlock(&ar->conf_mutex);
 323        return ret;
 324}
 325
 326int ath10k_wow_init(struct ath10k *ar)
 327{
 328        if (!test_bit(ATH10K_FW_FEATURE_WOWLAN_SUPPORT,
 329                      ar->running_fw->fw_file.fw_features))
 330                return 0;
 331
 332        if (WARN_ON(!test_bit(WMI_SERVICE_WOW, ar->wmi.svc_map)))
 333                return -EINVAL;
 334
 335        ar->wow.wowlan_support = ath10k_wowlan_support;
 336        ar->wow.wowlan_support.n_patterns = ar->wow.max_num_patterns;
 337        ar->hw->wiphy->wowlan = &ar->wow.wowlan_support;
 338
 339        return 0;
 340}
 341