179802b13SCarl Huang // SPDX-License-Identifier: BSD-3-Clause-Clear 279802b13SCarl Huang /* 379802b13SCarl Huang * Copyright (c) 2020 The Linux Foundation. All rights reserved. 479802b13SCarl Huang */ 579802b13SCarl Huang 679802b13SCarl Huang #include <linux/delay.h> 779802b13SCarl Huang 879802b13SCarl Huang #include "mac.h" 9ba9177fcSCarl Huang 10ba9177fcSCarl Huang #include <net/mac80211.h> 1179802b13SCarl Huang #include "core.h" 1279802b13SCarl Huang #include "hif.h" 1379802b13SCarl Huang #include "debug.h" 1479802b13SCarl Huang #include "wmi.h" 1579802b13SCarl Huang #include "wow.h" 1679802b13SCarl Huang 17ba9177fcSCarl Huang static const struct wiphy_wowlan_support ath11k_wowlan_support = { 18ba9177fcSCarl Huang .flags = WIPHY_WOWLAN_DISCONNECT | 19ba9177fcSCarl Huang WIPHY_WOWLAN_MAGIC_PKT, 20ba9177fcSCarl Huang .pattern_min_len = WOW_MIN_PATTERN_SIZE, 21ba9177fcSCarl Huang .pattern_max_len = WOW_MAX_PATTERN_SIZE, 22ba9177fcSCarl Huang .max_pkt_offset = WOW_MAX_PKT_OFFSET, 23ba9177fcSCarl Huang }; 24ba9177fcSCarl Huang 2579802b13SCarl Huang int ath11k_wow_enable(struct ath11k_base *ab) 2679802b13SCarl Huang { 2779802b13SCarl Huang struct ath11k *ar = ath11k_ab_to_ar(ab, 0); 2879802b13SCarl Huang int i, ret; 2979802b13SCarl Huang 3079802b13SCarl Huang clear_bit(ATH11K_FLAG_HTC_SUSPEND_COMPLETE, &ab->dev_flags); 3179802b13SCarl Huang 3279802b13SCarl Huang for (i = 0; i < ATH11K_WOW_RETRY_NUM; i++) { 3379802b13SCarl Huang reinit_completion(&ab->htc_suspend); 3479802b13SCarl Huang 3579802b13SCarl Huang ret = ath11k_wmi_wow_enable(ar); 3679802b13SCarl Huang if (ret) { 3779802b13SCarl Huang ath11k_warn(ab, "failed to issue wow enable: %d\n", ret); 3879802b13SCarl Huang return ret; 3979802b13SCarl Huang } 4079802b13SCarl Huang 4179802b13SCarl Huang ret = wait_for_completion_timeout(&ab->htc_suspend, 3 * HZ); 4279802b13SCarl Huang if (ret == 0) { 4379802b13SCarl Huang ath11k_warn(ab, 4479802b13SCarl Huang "timed out while waiting for htc suspend completion\n"); 4579802b13SCarl Huang return -ETIMEDOUT; 4679802b13SCarl Huang } 4779802b13SCarl Huang 4879802b13SCarl Huang if (test_bit(ATH11K_FLAG_HTC_SUSPEND_COMPLETE, &ab->dev_flags)) 4979802b13SCarl Huang /* success, suspend complete received */ 5079802b13SCarl Huang return 0; 5179802b13SCarl Huang 5279802b13SCarl Huang ath11k_warn(ab, "htc suspend not complete, retrying (try %d)\n", 5379802b13SCarl Huang i); 5479802b13SCarl Huang msleep(ATH11K_WOW_RETRY_WAIT_MS); 5579802b13SCarl Huang } 5679802b13SCarl Huang 5779802b13SCarl Huang ath11k_warn(ab, "htc suspend not complete, failing after %d tries\n", i); 5879802b13SCarl Huang 5979802b13SCarl Huang return -ETIMEDOUT; 6079802b13SCarl Huang } 6179802b13SCarl Huang 6279802b13SCarl Huang int ath11k_wow_wakeup(struct ath11k_base *ab) 6379802b13SCarl Huang { 6479802b13SCarl Huang struct ath11k *ar = ath11k_ab_to_ar(ab, 0); 6579802b13SCarl Huang int ret; 6679802b13SCarl Huang 6779802b13SCarl Huang reinit_completion(&ab->wow.wakeup_completed); 6879802b13SCarl Huang 6979802b13SCarl Huang ret = ath11k_wmi_wow_host_wakeup_ind(ar); 7079802b13SCarl Huang if (ret) { 7179802b13SCarl Huang ath11k_warn(ab, "failed to send wow wakeup indication: %d\n", 7279802b13SCarl Huang ret); 7379802b13SCarl Huang return ret; 7479802b13SCarl Huang } 7579802b13SCarl Huang 7679802b13SCarl Huang ret = wait_for_completion_timeout(&ab->wow.wakeup_completed, 3 * HZ); 7779802b13SCarl Huang if (ret == 0) { 7879802b13SCarl Huang ath11k_warn(ab, "timed out while waiting for wow wakeup completion\n"); 7979802b13SCarl Huang return -ETIMEDOUT; 8079802b13SCarl Huang } 8179802b13SCarl Huang 8279802b13SCarl Huang return 0; 8379802b13SCarl Huang } 84ba9177fcSCarl Huang 85ba9177fcSCarl Huang static int ath11k_wow_vif_cleanup(struct ath11k_vif *arvif) 86ba9177fcSCarl Huang { 87ba9177fcSCarl Huang struct ath11k *ar = arvif->ar; 88ba9177fcSCarl Huang int i, ret; 89ba9177fcSCarl Huang 90ba9177fcSCarl Huang for (i = 0; i < WOW_EVENT_MAX; i++) { 91ba9177fcSCarl Huang ret = ath11k_wmi_wow_add_wakeup_event(ar, arvif->vdev_id, i, 0); 92ba9177fcSCarl Huang if (ret) { 93ba9177fcSCarl Huang ath11k_warn(ar->ab, "failed to issue wow wakeup for event %s on vdev %i: %d\n", 94ba9177fcSCarl Huang wow_wakeup_event(i), arvif->vdev_id, ret); 95ba9177fcSCarl Huang return ret; 96ba9177fcSCarl Huang } 97ba9177fcSCarl Huang } 98ba9177fcSCarl Huang 99ba9177fcSCarl Huang for (i = 0; i < ar->wow.max_num_patterns; i++) { 100ba9177fcSCarl Huang ret = ath11k_wmi_wow_del_pattern(ar, arvif->vdev_id, i); 101ba9177fcSCarl Huang if (ret) { 102ba9177fcSCarl Huang ath11k_warn(ar->ab, "failed to delete wow pattern %d for vdev %i: %d\n", 103ba9177fcSCarl Huang i, arvif->vdev_id, ret); 104ba9177fcSCarl Huang return ret; 105ba9177fcSCarl Huang } 106ba9177fcSCarl Huang } 107ba9177fcSCarl Huang 108ba9177fcSCarl Huang return 0; 109ba9177fcSCarl Huang } 110ba9177fcSCarl Huang 111ba9177fcSCarl Huang static int ath11k_wow_cleanup(struct ath11k *ar) 112ba9177fcSCarl Huang { 113ba9177fcSCarl Huang struct ath11k_vif *arvif; 114ba9177fcSCarl Huang int ret; 115ba9177fcSCarl Huang 116ba9177fcSCarl Huang lockdep_assert_held(&ar->conf_mutex); 117ba9177fcSCarl Huang 118ba9177fcSCarl Huang list_for_each_entry(arvif, &ar->arvifs, list) { 119ba9177fcSCarl Huang ret = ath11k_wow_vif_cleanup(arvif); 120ba9177fcSCarl Huang if (ret) { 121ba9177fcSCarl Huang ath11k_warn(ar->ab, "failed to clean wow wakeups on vdev %i: %d\n", 122ba9177fcSCarl Huang arvif->vdev_id, ret); 123ba9177fcSCarl Huang return ret; 124ba9177fcSCarl Huang } 125ba9177fcSCarl Huang } 126ba9177fcSCarl Huang 127ba9177fcSCarl Huang return 0; 128ba9177fcSCarl Huang } 129ba9177fcSCarl Huang 130ba9177fcSCarl Huang /* Convert a 802.3 format to a 802.11 format. 131ba9177fcSCarl Huang * +------------+-----------+--------+----------------+ 132ba9177fcSCarl Huang * 802.3: |dest mac(6B)|src mac(6B)|type(2B)| body... | 133ba9177fcSCarl Huang * +------------+-----------+--------+----------------+ 134ba9177fcSCarl Huang * |__ |_______ |____________ |________ 135ba9177fcSCarl Huang * | | | | 136ba9177fcSCarl Huang * +--+------------+----+-----------+---------------+-----------+ 137ba9177fcSCarl Huang * 802.11: |4B|dest mac(6B)| 6B |src mac(6B)| 8B |type(2B)| body... | 138ba9177fcSCarl Huang * +--+------------+----+-----------+---------------+-----------+ 139ba9177fcSCarl Huang */ 140ba9177fcSCarl Huang static void ath11k_wow_convert_8023_to_80211(struct cfg80211_pkt_pattern *new, 141ba9177fcSCarl Huang const struct cfg80211_pkt_pattern *old) 142ba9177fcSCarl Huang { 143ba9177fcSCarl Huang u8 hdr_8023_pattern[ETH_HLEN] = {}; 144ba9177fcSCarl Huang u8 hdr_8023_bit_mask[ETH_HLEN] = {}; 145ba9177fcSCarl Huang u8 hdr_80211_pattern[WOW_HDR_LEN] = {}; 146ba9177fcSCarl Huang u8 hdr_80211_bit_mask[WOW_HDR_LEN] = {}; 147ba9177fcSCarl Huang 148ba9177fcSCarl Huang int total_len = old->pkt_offset + old->pattern_len; 149ba9177fcSCarl Huang int hdr_80211_end_offset; 150ba9177fcSCarl Huang 151ba9177fcSCarl Huang struct ieee80211_hdr_3addr *new_hdr_pattern = 152ba9177fcSCarl Huang (struct ieee80211_hdr_3addr *)hdr_80211_pattern; 153ba9177fcSCarl Huang struct ieee80211_hdr_3addr *new_hdr_mask = 154ba9177fcSCarl Huang (struct ieee80211_hdr_3addr *)hdr_80211_bit_mask; 155ba9177fcSCarl Huang struct ethhdr *old_hdr_pattern = (struct ethhdr *)hdr_8023_pattern; 156ba9177fcSCarl Huang struct ethhdr *old_hdr_mask = (struct ethhdr *)hdr_8023_bit_mask; 157ba9177fcSCarl Huang int hdr_len = sizeof(*new_hdr_pattern); 158ba9177fcSCarl Huang 159ba9177fcSCarl Huang struct rfc1042_hdr *new_rfc_pattern = 160ba9177fcSCarl Huang (struct rfc1042_hdr *)(hdr_80211_pattern + hdr_len); 161ba9177fcSCarl Huang struct rfc1042_hdr *new_rfc_mask = 162ba9177fcSCarl Huang (struct rfc1042_hdr *)(hdr_80211_bit_mask + hdr_len); 163ba9177fcSCarl Huang int rfc_len = sizeof(*new_rfc_pattern); 164ba9177fcSCarl Huang 165ba9177fcSCarl Huang memcpy(hdr_8023_pattern + old->pkt_offset, 166ba9177fcSCarl Huang old->pattern, ETH_HLEN - old->pkt_offset); 167ba9177fcSCarl Huang memcpy(hdr_8023_bit_mask + old->pkt_offset, 168ba9177fcSCarl Huang old->mask, ETH_HLEN - old->pkt_offset); 169ba9177fcSCarl Huang 170ba9177fcSCarl Huang /* Copy destination address */ 171ba9177fcSCarl Huang memcpy(new_hdr_pattern->addr1, old_hdr_pattern->h_dest, ETH_ALEN); 172ba9177fcSCarl Huang memcpy(new_hdr_mask->addr1, old_hdr_mask->h_dest, ETH_ALEN); 173ba9177fcSCarl Huang 174ba9177fcSCarl Huang /* Copy source address */ 175ba9177fcSCarl Huang memcpy(new_hdr_pattern->addr3, old_hdr_pattern->h_source, ETH_ALEN); 176ba9177fcSCarl Huang memcpy(new_hdr_mask->addr3, old_hdr_mask->h_source, ETH_ALEN); 177ba9177fcSCarl Huang 178ba9177fcSCarl Huang /* Copy logic link type */ 179ba9177fcSCarl Huang memcpy(&new_rfc_pattern->snap_type, 180ba9177fcSCarl Huang &old_hdr_pattern->h_proto, 181ba9177fcSCarl Huang sizeof(old_hdr_pattern->h_proto)); 182ba9177fcSCarl Huang memcpy(&new_rfc_mask->snap_type, 183ba9177fcSCarl Huang &old_hdr_mask->h_proto, 184ba9177fcSCarl Huang sizeof(old_hdr_mask->h_proto)); 185ba9177fcSCarl Huang 186ba9177fcSCarl Huang /* Compute new pkt_offset */ 187ba9177fcSCarl Huang if (old->pkt_offset < ETH_ALEN) 188ba9177fcSCarl Huang new->pkt_offset = old->pkt_offset + 189ba9177fcSCarl Huang offsetof(struct ieee80211_hdr_3addr, addr1); 190ba9177fcSCarl Huang else if (old->pkt_offset < offsetof(struct ethhdr, h_proto)) 191ba9177fcSCarl Huang new->pkt_offset = old->pkt_offset + 192ba9177fcSCarl Huang offsetof(struct ieee80211_hdr_3addr, addr3) - 193ba9177fcSCarl Huang offsetof(struct ethhdr, h_source); 194ba9177fcSCarl Huang else 195ba9177fcSCarl Huang new->pkt_offset = old->pkt_offset + hdr_len + rfc_len - ETH_HLEN; 196ba9177fcSCarl Huang 197ba9177fcSCarl Huang /* Compute new hdr end offset */ 198ba9177fcSCarl Huang if (total_len > ETH_HLEN) 199ba9177fcSCarl Huang hdr_80211_end_offset = hdr_len + rfc_len; 200ba9177fcSCarl Huang else if (total_len > offsetof(struct ethhdr, h_proto)) 201ba9177fcSCarl Huang hdr_80211_end_offset = hdr_len + rfc_len + total_len - ETH_HLEN; 202ba9177fcSCarl Huang else if (total_len > ETH_ALEN) 203ba9177fcSCarl Huang hdr_80211_end_offset = total_len - ETH_ALEN + 204ba9177fcSCarl Huang offsetof(struct ieee80211_hdr_3addr, addr3); 205ba9177fcSCarl Huang else 206ba9177fcSCarl Huang hdr_80211_end_offset = total_len + 207ba9177fcSCarl Huang offsetof(struct ieee80211_hdr_3addr, addr1); 208ba9177fcSCarl Huang 209ba9177fcSCarl Huang new->pattern_len = hdr_80211_end_offset - new->pkt_offset; 210ba9177fcSCarl Huang 211ba9177fcSCarl Huang memcpy((u8 *)new->pattern, 212ba9177fcSCarl Huang hdr_80211_pattern + new->pkt_offset, 213ba9177fcSCarl Huang new->pattern_len); 214ba9177fcSCarl Huang memcpy((u8 *)new->mask, 215ba9177fcSCarl Huang hdr_80211_bit_mask + new->pkt_offset, 216ba9177fcSCarl Huang new->pattern_len); 217ba9177fcSCarl Huang 218ba9177fcSCarl Huang if (total_len > ETH_HLEN) { 219ba9177fcSCarl Huang /* Copy frame body */ 220ba9177fcSCarl Huang memcpy((u8 *)new->pattern + new->pattern_len, 221ba9177fcSCarl Huang (void *)old->pattern + ETH_HLEN - old->pkt_offset, 222ba9177fcSCarl Huang total_len - ETH_HLEN); 223ba9177fcSCarl Huang memcpy((u8 *)new->mask + new->pattern_len, 224ba9177fcSCarl Huang (void *)old->mask + ETH_HLEN - old->pkt_offset, 225ba9177fcSCarl Huang total_len - ETH_HLEN); 226ba9177fcSCarl Huang 227ba9177fcSCarl Huang new->pattern_len += total_len - ETH_HLEN; 228ba9177fcSCarl Huang } 229ba9177fcSCarl Huang } 230ba9177fcSCarl Huang 231*fec4b898SCarl Huang static int ath11k_wmi_pno_check_and_convert(struct ath11k *ar, u32 vdev_id, 232*fec4b898SCarl Huang struct cfg80211_sched_scan_request *nd_config, 233*fec4b898SCarl Huang struct wmi_pno_scan_req *pno) 234*fec4b898SCarl Huang { 235*fec4b898SCarl Huang int i, j; 236*fec4b898SCarl Huang u8 ssid_len; 237*fec4b898SCarl Huang 238*fec4b898SCarl Huang pno->enable = 1; 239*fec4b898SCarl Huang pno->vdev_id = vdev_id; 240*fec4b898SCarl Huang pno->uc_networks_count = nd_config->n_match_sets; 241*fec4b898SCarl Huang 242*fec4b898SCarl Huang if (!pno->uc_networks_count || 243*fec4b898SCarl Huang pno->uc_networks_count > WMI_PNO_MAX_SUPP_NETWORKS) 244*fec4b898SCarl Huang return -EINVAL; 245*fec4b898SCarl Huang 246*fec4b898SCarl Huang if (nd_config->n_channels > WMI_PNO_MAX_NETW_CHANNELS_EX) 247*fec4b898SCarl Huang return -EINVAL; 248*fec4b898SCarl Huang 249*fec4b898SCarl Huang /* Filling per profile params */ 250*fec4b898SCarl Huang for (i = 0; i < pno->uc_networks_count; i++) { 251*fec4b898SCarl Huang ssid_len = nd_config->match_sets[i].ssid.ssid_len; 252*fec4b898SCarl Huang 253*fec4b898SCarl Huang if (ssid_len == 0 || ssid_len > 32) 254*fec4b898SCarl Huang return -EINVAL; 255*fec4b898SCarl Huang 256*fec4b898SCarl Huang pno->a_networks[i].ssid.ssid_len = ssid_len; 257*fec4b898SCarl Huang 258*fec4b898SCarl Huang memcpy(pno->a_networks[i].ssid.ssid, 259*fec4b898SCarl Huang nd_config->match_sets[i].ssid.ssid, 260*fec4b898SCarl Huang nd_config->match_sets[i].ssid.ssid_len); 261*fec4b898SCarl Huang pno->a_networks[i].authentication = 0; 262*fec4b898SCarl Huang pno->a_networks[i].encryption = 0; 263*fec4b898SCarl Huang pno->a_networks[i].bcast_nw_type = 0; 264*fec4b898SCarl Huang 265*fec4b898SCarl Huang /* Copying list of valid channel into request */ 266*fec4b898SCarl Huang pno->a_networks[i].channel_count = nd_config->n_channels; 267*fec4b898SCarl Huang pno->a_networks[i].rssi_threshold = nd_config->match_sets[i].rssi_thold; 268*fec4b898SCarl Huang 269*fec4b898SCarl Huang for (j = 0; j < nd_config->n_channels; j++) { 270*fec4b898SCarl Huang pno->a_networks[i].channels[j] = 271*fec4b898SCarl Huang nd_config->channels[j]->center_freq; 272*fec4b898SCarl Huang } 273*fec4b898SCarl Huang } 274*fec4b898SCarl Huang 275*fec4b898SCarl Huang /* set scan to passive if no SSIDs are specified in the request */ 276*fec4b898SCarl Huang if (nd_config->n_ssids == 0) 277*fec4b898SCarl Huang pno->do_passive_scan = true; 278*fec4b898SCarl Huang else 279*fec4b898SCarl Huang pno->do_passive_scan = false; 280*fec4b898SCarl Huang 281*fec4b898SCarl Huang for (i = 0; i < nd_config->n_ssids; i++) { 282*fec4b898SCarl Huang j = 0; 283*fec4b898SCarl Huang while (j < pno->uc_networks_count) { 284*fec4b898SCarl Huang if (pno->a_networks[j].ssid.ssid_len == 285*fec4b898SCarl Huang nd_config->ssids[i].ssid_len && 286*fec4b898SCarl Huang (memcmp(pno->a_networks[j].ssid.ssid, 287*fec4b898SCarl Huang nd_config->ssids[i].ssid, 288*fec4b898SCarl Huang pno->a_networks[j].ssid.ssid_len) == 0)) { 289*fec4b898SCarl Huang pno->a_networks[j].bcast_nw_type = BCAST_HIDDEN; 290*fec4b898SCarl Huang break; 291*fec4b898SCarl Huang } 292*fec4b898SCarl Huang j++; 293*fec4b898SCarl Huang } 294*fec4b898SCarl Huang } 295*fec4b898SCarl Huang 296*fec4b898SCarl Huang if (nd_config->n_scan_plans == 2) { 297*fec4b898SCarl Huang pno->fast_scan_period = nd_config->scan_plans[0].interval * MSEC_PER_SEC; 298*fec4b898SCarl Huang pno->fast_scan_max_cycles = nd_config->scan_plans[0].iterations; 299*fec4b898SCarl Huang pno->slow_scan_period = 300*fec4b898SCarl Huang nd_config->scan_plans[1].interval * MSEC_PER_SEC; 301*fec4b898SCarl Huang } else if (nd_config->n_scan_plans == 1) { 302*fec4b898SCarl Huang pno->fast_scan_period = nd_config->scan_plans[0].interval * MSEC_PER_SEC; 303*fec4b898SCarl Huang pno->fast_scan_max_cycles = 1; 304*fec4b898SCarl Huang pno->slow_scan_period = nd_config->scan_plans[0].interval * MSEC_PER_SEC; 305*fec4b898SCarl Huang } else { 306*fec4b898SCarl Huang ath11k_warn(ar->ab, "Invalid number of scan plans %d !!", 307*fec4b898SCarl Huang nd_config->n_scan_plans); 308*fec4b898SCarl Huang } 309*fec4b898SCarl Huang 310*fec4b898SCarl Huang if (nd_config->flags & NL80211_SCAN_FLAG_RANDOM_ADDR) { 311*fec4b898SCarl Huang /* enable mac randomization */ 312*fec4b898SCarl Huang pno->enable_pno_scan_randomization = 1; 313*fec4b898SCarl Huang memcpy(pno->mac_addr, nd_config->mac_addr, ETH_ALEN); 314*fec4b898SCarl Huang memcpy(pno->mac_addr_mask, nd_config->mac_addr_mask, ETH_ALEN); 315*fec4b898SCarl Huang } 316*fec4b898SCarl Huang 317*fec4b898SCarl Huang pno->delay_start_time = nd_config->delay; 318*fec4b898SCarl Huang 319*fec4b898SCarl Huang /* Current FW does not support min-max range for dwell time */ 320*fec4b898SCarl Huang pno->active_max_time = WMI_ACTIVE_MAX_CHANNEL_TIME; 321*fec4b898SCarl Huang pno->passive_max_time = WMI_PASSIVE_MAX_CHANNEL_TIME; 322*fec4b898SCarl Huang 323*fec4b898SCarl Huang return 0; 324*fec4b898SCarl Huang } 325*fec4b898SCarl Huang 326ba9177fcSCarl Huang static int ath11k_vif_wow_set_wakeups(struct ath11k_vif *arvif, 327ba9177fcSCarl Huang struct cfg80211_wowlan *wowlan) 328ba9177fcSCarl Huang { 329ba9177fcSCarl Huang int ret, i; 330ba9177fcSCarl Huang unsigned long wow_mask = 0; 331ba9177fcSCarl Huang struct ath11k *ar = arvif->ar; 332ba9177fcSCarl Huang const struct cfg80211_pkt_pattern *patterns = wowlan->patterns; 333ba9177fcSCarl Huang int pattern_id = 0; 334ba9177fcSCarl Huang 335ba9177fcSCarl Huang /* Setup requested WOW features */ 336ba9177fcSCarl Huang switch (arvif->vdev_type) { 337ba9177fcSCarl Huang case WMI_VDEV_TYPE_IBSS: 338ba9177fcSCarl Huang __set_bit(WOW_BEACON_EVENT, &wow_mask); 339ba9177fcSCarl Huang fallthrough; 340ba9177fcSCarl Huang case WMI_VDEV_TYPE_AP: 341ba9177fcSCarl Huang __set_bit(WOW_DEAUTH_RECVD_EVENT, &wow_mask); 342ba9177fcSCarl Huang __set_bit(WOW_DISASSOC_RECVD_EVENT, &wow_mask); 343ba9177fcSCarl Huang __set_bit(WOW_PROBE_REQ_WPS_IE_EVENT, &wow_mask); 344ba9177fcSCarl Huang __set_bit(WOW_AUTH_REQ_EVENT, &wow_mask); 345ba9177fcSCarl Huang __set_bit(WOW_ASSOC_REQ_EVENT, &wow_mask); 346ba9177fcSCarl Huang __set_bit(WOW_HTT_EVENT, &wow_mask); 347ba9177fcSCarl Huang __set_bit(WOW_RA_MATCH_EVENT, &wow_mask); 348ba9177fcSCarl Huang break; 349ba9177fcSCarl Huang case WMI_VDEV_TYPE_STA: 350ba9177fcSCarl Huang if (wowlan->disconnect) { 351ba9177fcSCarl Huang __set_bit(WOW_DEAUTH_RECVD_EVENT, &wow_mask); 352ba9177fcSCarl Huang __set_bit(WOW_DISASSOC_RECVD_EVENT, &wow_mask); 353ba9177fcSCarl Huang __set_bit(WOW_BMISS_EVENT, &wow_mask); 354ba9177fcSCarl Huang __set_bit(WOW_CSA_IE_EVENT, &wow_mask); 355ba9177fcSCarl Huang } 356ba9177fcSCarl Huang 357ba9177fcSCarl Huang if (wowlan->magic_pkt) 358ba9177fcSCarl Huang __set_bit(WOW_MAGIC_PKT_RECVD_EVENT, &wow_mask); 359*fec4b898SCarl Huang 360*fec4b898SCarl Huang if (wowlan->nd_config) { 361*fec4b898SCarl Huang struct wmi_pno_scan_req *pno; 362*fec4b898SCarl Huang int ret; 363*fec4b898SCarl Huang 364*fec4b898SCarl Huang pno = kzalloc(sizeof(*pno), GFP_KERNEL); 365*fec4b898SCarl Huang if (!pno) 366*fec4b898SCarl Huang return -ENOMEM; 367*fec4b898SCarl Huang 368*fec4b898SCarl Huang ar->nlo_enabled = true; 369*fec4b898SCarl Huang 370*fec4b898SCarl Huang ret = ath11k_wmi_pno_check_and_convert(ar, arvif->vdev_id, 371*fec4b898SCarl Huang wowlan->nd_config, pno); 372*fec4b898SCarl Huang if (!ret) { 373*fec4b898SCarl Huang ath11k_wmi_wow_config_pno(ar, arvif->vdev_id, pno); 374*fec4b898SCarl Huang __set_bit(WOW_NLO_DETECTED_EVENT, &wow_mask); 375*fec4b898SCarl Huang } 376*fec4b898SCarl Huang 377*fec4b898SCarl Huang kfree(pno); 378*fec4b898SCarl Huang } 379ba9177fcSCarl Huang break; 380ba9177fcSCarl Huang default: 381ba9177fcSCarl Huang break; 382ba9177fcSCarl Huang } 383ba9177fcSCarl Huang 384ba9177fcSCarl Huang for (i = 0; i < wowlan->n_patterns; i++) { 385ba9177fcSCarl Huang u8 bitmask[WOW_MAX_PATTERN_SIZE] = {}; 386ba9177fcSCarl Huang u8 ath_pattern[WOW_MAX_PATTERN_SIZE] = {}; 387ba9177fcSCarl Huang u8 ath_bitmask[WOW_MAX_PATTERN_SIZE] = {}; 388ba9177fcSCarl Huang struct cfg80211_pkt_pattern new_pattern = {}; 389ba9177fcSCarl Huang struct cfg80211_pkt_pattern old_pattern = patterns[i]; 390ba9177fcSCarl Huang int j; 391ba9177fcSCarl Huang 392ba9177fcSCarl Huang new_pattern.pattern = ath_pattern; 393ba9177fcSCarl Huang new_pattern.mask = ath_bitmask; 394ba9177fcSCarl Huang if (patterns[i].pattern_len > WOW_MAX_PATTERN_SIZE) 395ba9177fcSCarl Huang continue; 396ba9177fcSCarl Huang /* convert bytemask to bitmask */ 397ba9177fcSCarl Huang for (j = 0; j < patterns[i].pattern_len; j++) 398ba9177fcSCarl Huang if (patterns[i].mask[j / 8] & BIT(j % 8)) 399ba9177fcSCarl Huang bitmask[j] = 0xff; 400ba9177fcSCarl Huang old_pattern.mask = bitmask; 401ba9177fcSCarl Huang 402ba9177fcSCarl Huang if (ar->wmi->wmi_ab->wlan_resource_config.rx_decap_mode == 403ba9177fcSCarl Huang ATH11K_HW_TXRX_NATIVE_WIFI) { 404ba9177fcSCarl Huang if (patterns[i].pkt_offset < ETH_HLEN) { 405ba9177fcSCarl Huang u8 pattern_ext[WOW_MAX_PATTERN_SIZE] = {}; 406ba9177fcSCarl Huang 407ba9177fcSCarl Huang memcpy(pattern_ext, old_pattern.pattern, 408ba9177fcSCarl Huang old_pattern.pattern_len); 409ba9177fcSCarl Huang old_pattern.pattern = pattern_ext; 410ba9177fcSCarl Huang ath11k_wow_convert_8023_to_80211(&new_pattern, 411ba9177fcSCarl Huang &old_pattern); 412ba9177fcSCarl Huang } else { 413ba9177fcSCarl Huang new_pattern = old_pattern; 414ba9177fcSCarl Huang new_pattern.pkt_offset += WOW_HDR_LEN - ETH_HLEN; 415ba9177fcSCarl Huang } 416ba9177fcSCarl Huang } 417ba9177fcSCarl Huang 418ba9177fcSCarl Huang if (WARN_ON(new_pattern.pattern_len > WOW_MAX_PATTERN_SIZE)) 419ba9177fcSCarl Huang return -EINVAL; 420ba9177fcSCarl Huang 421ba9177fcSCarl Huang ret = ath11k_wmi_wow_add_pattern(ar, arvif->vdev_id, 422ba9177fcSCarl Huang pattern_id, 423ba9177fcSCarl Huang new_pattern.pattern, 424ba9177fcSCarl Huang new_pattern.mask, 425ba9177fcSCarl Huang new_pattern.pattern_len, 426ba9177fcSCarl Huang new_pattern.pkt_offset); 427ba9177fcSCarl Huang if (ret) { 428ba9177fcSCarl Huang ath11k_warn(ar->ab, "failed to add pattern %i to vdev %i: %d\n", 429ba9177fcSCarl Huang pattern_id, 430ba9177fcSCarl Huang arvif->vdev_id, ret); 431ba9177fcSCarl Huang return ret; 432ba9177fcSCarl Huang } 433ba9177fcSCarl Huang 434ba9177fcSCarl Huang pattern_id++; 435ba9177fcSCarl Huang __set_bit(WOW_PATTERN_MATCH_EVENT, &wow_mask); 436ba9177fcSCarl Huang } 437ba9177fcSCarl Huang 438ba9177fcSCarl Huang for (i = 0; i < WOW_EVENT_MAX; i++) { 439ba9177fcSCarl Huang if (!test_bit(i, &wow_mask)) 440ba9177fcSCarl Huang continue; 441ba9177fcSCarl Huang ret = ath11k_wmi_wow_add_wakeup_event(ar, arvif->vdev_id, i, 1); 442ba9177fcSCarl Huang if (ret) { 443ba9177fcSCarl Huang ath11k_warn(ar->ab, "failed to enable wakeup event %s on vdev %i: %d\n", 444ba9177fcSCarl Huang wow_wakeup_event(i), arvif->vdev_id, ret); 445ba9177fcSCarl Huang return ret; 446ba9177fcSCarl Huang } 447ba9177fcSCarl Huang } 448ba9177fcSCarl Huang 449ba9177fcSCarl Huang return 0; 450ba9177fcSCarl Huang } 451ba9177fcSCarl Huang 452ba9177fcSCarl Huang static int ath11k_wow_set_wakeups(struct ath11k *ar, 453ba9177fcSCarl Huang struct cfg80211_wowlan *wowlan) 454ba9177fcSCarl Huang { 455ba9177fcSCarl Huang struct ath11k_vif *arvif; 456ba9177fcSCarl Huang int ret; 457ba9177fcSCarl Huang 458ba9177fcSCarl Huang lockdep_assert_held(&ar->conf_mutex); 459ba9177fcSCarl Huang 460ba9177fcSCarl Huang list_for_each_entry(arvif, &ar->arvifs, list) { 461ba9177fcSCarl Huang ret = ath11k_vif_wow_set_wakeups(arvif, wowlan); 462ba9177fcSCarl Huang if (ret) { 463ba9177fcSCarl Huang ath11k_warn(ar->ab, "failed to set wow wakeups on vdev %i: %d\n", 464ba9177fcSCarl Huang arvif->vdev_id, ret); 465ba9177fcSCarl Huang return ret; 466ba9177fcSCarl Huang } 467ba9177fcSCarl Huang } 468ba9177fcSCarl Huang 469ba9177fcSCarl Huang return 0; 470ba9177fcSCarl Huang } 471ba9177fcSCarl Huang 472*fec4b898SCarl Huang static int ath11k_vif_wow_clean_nlo(struct ath11k_vif *arvif) 473*fec4b898SCarl Huang { 474*fec4b898SCarl Huang int ret = 0; 475*fec4b898SCarl Huang struct ath11k *ar = arvif->ar; 476*fec4b898SCarl Huang 477*fec4b898SCarl Huang switch (arvif->vdev_type) { 478*fec4b898SCarl Huang case WMI_VDEV_TYPE_STA: 479*fec4b898SCarl Huang if (ar->nlo_enabled) { 480*fec4b898SCarl Huang struct wmi_pno_scan_req *pno; 481*fec4b898SCarl Huang 482*fec4b898SCarl Huang pno = kzalloc(sizeof(*pno), GFP_KERNEL); 483*fec4b898SCarl Huang if (!pno) 484*fec4b898SCarl Huang return -ENOMEM; 485*fec4b898SCarl Huang 486*fec4b898SCarl Huang pno->enable = 0; 487*fec4b898SCarl Huang ar->nlo_enabled = false; 488*fec4b898SCarl Huang ret = ath11k_wmi_wow_config_pno(ar, arvif->vdev_id, pno); 489*fec4b898SCarl Huang kfree(pno); 490*fec4b898SCarl Huang } 491*fec4b898SCarl Huang break; 492*fec4b898SCarl Huang default: 493*fec4b898SCarl Huang break; 494*fec4b898SCarl Huang } 495*fec4b898SCarl Huang return ret; 496*fec4b898SCarl Huang } 497*fec4b898SCarl Huang 498*fec4b898SCarl Huang static int ath11k_wow_nlo_cleanup(struct ath11k *ar) 499*fec4b898SCarl Huang { 500*fec4b898SCarl Huang struct ath11k_vif *arvif; 501*fec4b898SCarl Huang int ret; 502*fec4b898SCarl Huang 503*fec4b898SCarl Huang lockdep_assert_held(&ar->conf_mutex); 504*fec4b898SCarl Huang 505*fec4b898SCarl Huang list_for_each_entry(arvif, &ar->arvifs, list) { 506*fec4b898SCarl Huang ret = ath11k_vif_wow_clean_nlo(arvif); 507*fec4b898SCarl Huang if (ret) { 508*fec4b898SCarl Huang ath11k_warn(ar->ab, "failed to clean nlo settings on vdev %i: %d\n", 509*fec4b898SCarl Huang arvif->vdev_id, ret); 510*fec4b898SCarl Huang return ret; 511*fec4b898SCarl Huang } 512*fec4b898SCarl Huang } 513*fec4b898SCarl Huang 514*fec4b898SCarl Huang return 0; 515*fec4b898SCarl Huang } 516*fec4b898SCarl Huang 517ba9177fcSCarl Huang int ath11k_wow_op_suspend(struct ieee80211_hw *hw, 518ba9177fcSCarl Huang struct cfg80211_wowlan *wowlan) 519ba9177fcSCarl Huang { 520ba9177fcSCarl Huang struct ath11k *ar = hw->priv; 521ba9177fcSCarl Huang int ret; 522ba9177fcSCarl Huang 523ba9177fcSCarl Huang mutex_lock(&ar->conf_mutex); 524ba9177fcSCarl Huang 525ba9177fcSCarl Huang ret = ath11k_wow_cleanup(ar); 526ba9177fcSCarl Huang if (ret) { 527ba9177fcSCarl Huang ath11k_warn(ar->ab, "failed to clear wow wakeup events: %d\n", 528ba9177fcSCarl Huang ret); 529ba9177fcSCarl Huang goto exit; 530ba9177fcSCarl Huang } 531ba9177fcSCarl Huang 532ba9177fcSCarl Huang ret = ath11k_wow_set_wakeups(ar, wowlan); 533ba9177fcSCarl Huang if (ret) { 534ba9177fcSCarl Huang ath11k_warn(ar->ab, "failed to set wow wakeup events: %d\n", 535ba9177fcSCarl Huang ret); 536ba9177fcSCarl Huang goto cleanup; 537ba9177fcSCarl Huang } 538ba9177fcSCarl Huang 539ba9177fcSCarl Huang ret = ath11k_mac_wait_tx_complete(ar); 540ba9177fcSCarl Huang if (ret) { 541ba9177fcSCarl Huang ath11k_warn(ar->ab, "failed to wait tx complete: %d\n", ret); 542ba9177fcSCarl Huang goto cleanup; 543ba9177fcSCarl Huang } 544ba9177fcSCarl Huang 545ba9177fcSCarl Huang ret = ath11k_wow_enable(ar->ab); 546ba9177fcSCarl Huang if (ret) { 547ba9177fcSCarl Huang ath11k_warn(ar->ab, "failed to start wow: %d\n", ret); 548ba9177fcSCarl Huang goto cleanup; 549ba9177fcSCarl Huang } 550ba9177fcSCarl Huang 551ba9177fcSCarl Huang ath11k_ce_stop_shadow_timers(ar->ab); 552ba9177fcSCarl Huang ath11k_dp_stop_shadow_timers(ar->ab); 553ba9177fcSCarl Huang 554ba9177fcSCarl Huang ath11k_hif_irq_disable(ar->ab); 555ba9177fcSCarl Huang ath11k_hif_ce_irq_disable(ar->ab); 556ba9177fcSCarl Huang 557ba9177fcSCarl Huang ret = ath11k_hif_suspend(ar->ab); 558ba9177fcSCarl Huang if (ret) { 559ba9177fcSCarl Huang ath11k_warn(ar->ab, "failed to suspend hif: %d\n", ret); 560ba9177fcSCarl Huang goto wakeup; 561ba9177fcSCarl Huang } 562ba9177fcSCarl Huang 563ba9177fcSCarl Huang goto exit; 564ba9177fcSCarl Huang 565ba9177fcSCarl Huang wakeup: 566ba9177fcSCarl Huang ath11k_wow_wakeup(ar->ab); 567ba9177fcSCarl Huang 568ba9177fcSCarl Huang cleanup: 569ba9177fcSCarl Huang ath11k_wow_cleanup(ar); 570ba9177fcSCarl Huang 571ba9177fcSCarl Huang exit: 572ba9177fcSCarl Huang mutex_unlock(&ar->conf_mutex); 573ba9177fcSCarl Huang return ret ? 1 : 0; 574ba9177fcSCarl Huang } 575ba9177fcSCarl Huang 576ba9177fcSCarl Huang void ath11k_wow_op_set_wakeup(struct ieee80211_hw *hw, bool enabled) 577ba9177fcSCarl Huang { 578ba9177fcSCarl Huang struct ath11k *ar = hw->priv; 579ba9177fcSCarl Huang 580ba9177fcSCarl Huang mutex_lock(&ar->conf_mutex); 581ba9177fcSCarl Huang device_set_wakeup_enable(ar->ab->dev, enabled); 582ba9177fcSCarl Huang mutex_unlock(&ar->conf_mutex); 583ba9177fcSCarl Huang } 584ba9177fcSCarl Huang 585ba9177fcSCarl Huang int ath11k_wow_op_resume(struct ieee80211_hw *hw) 586ba9177fcSCarl Huang { 587ba9177fcSCarl Huang struct ath11k *ar = hw->priv; 588ba9177fcSCarl Huang int ret; 589ba9177fcSCarl Huang 590ba9177fcSCarl Huang mutex_lock(&ar->conf_mutex); 591ba9177fcSCarl Huang 592ba9177fcSCarl Huang ret = ath11k_hif_resume(ar->ab); 593ba9177fcSCarl Huang if (ret) { 594ba9177fcSCarl Huang ath11k_warn(ar->ab, "failed to resume hif: %d\n", ret); 595ba9177fcSCarl Huang goto exit; 596ba9177fcSCarl Huang } 597ba9177fcSCarl Huang 598ba9177fcSCarl Huang ath11k_hif_ce_irq_enable(ar->ab); 599ba9177fcSCarl Huang ath11k_hif_irq_enable(ar->ab); 600ba9177fcSCarl Huang 601ba9177fcSCarl Huang ret = ath11k_wow_wakeup(ar->ab); 602*fec4b898SCarl Huang if (ret) { 603ba9177fcSCarl Huang ath11k_warn(ar->ab, "failed to wakeup from wow: %d\n", ret); 604*fec4b898SCarl Huang goto exit; 605*fec4b898SCarl Huang } 606*fec4b898SCarl Huang 607*fec4b898SCarl Huang ret = ath11k_wow_nlo_cleanup(ar); 608*fec4b898SCarl Huang if (ret) { 609*fec4b898SCarl Huang ath11k_warn(ar->ab, "failed to cleanup nlo: %d\n", ret); 610*fec4b898SCarl Huang goto exit; 611*fec4b898SCarl Huang } 612ba9177fcSCarl Huang 613ba9177fcSCarl Huang exit: 614ba9177fcSCarl Huang if (ret) { 615ba9177fcSCarl Huang switch (ar->state) { 616ba9177fcSCarl Huang case ATH11K_STATE_ON: 617ba9177fcSCarl Huang ar->state = ATH11K_STATE_RESTARTING; 618ba9177fcSCarl Huang ret = 1; 619ba9177fcSCarl Huang break; 620ba9177fcSCarl Huang case ATH11K_STATE_OFF: 621ba9177fcSCarl Huang case ATH11K_STATE_RESTARTING: 622ba9177fcSCarl Huang case ATH11K_STATE_RESTARTED: 623ba9177fcSCarl Huang case ATH11K_STATE_WEDGED: 624ba9177fcSCarl Huang ath11k_warn(ar->ab, "encountered unexpected device state %d on resume, cannot recover\n", 625ba9177fcSCarl Huang ar->state); 626ba9177fcSCarl Huang ret = -EIO; 627ba9177fcSCarl Huang break; 628ba9177fcSCarl Huang } 629ba9177fcSCarl Huang } 630ba9177fcSCarl Huang 631ba9177fcSCarl Huang mutex_unlock(&ar->conf_mutex); 632ba9177fcSCarl Huang return ret; 633ba9177fcSCarl Huang } 634ba9177fcSCarl Huang 635ba9177fcSCarl Huang int ath11k_wow_init(struct ath11k *ar) 636ba9177fcSCarl Huang { 637ba9177fcSCarl Huang if (WARN_ON(!test_bit(WMI_TLV_SERVICE_WOW, ar->wmi->wmi_ab->svc_map))) 638ba9177fcSCarl Huang return -EINVAL; 639ba9177fcSCarl Huang 640ba9177fcSCarl Huang ar->wow.wowlan_support = ath11k_wowlan_support; 641ba9177fcSCarl Huang 642ba9177fcSCarl Huang if (ar->wmi->wmi_ab->wlan_resource_config.rx_decap_mode == 643ba9177fcSCarl Huang ATH11K_HW_TXRX_NATIVE_WIFI) { 644ba9177fcSCarl Huang ar->wow.wowlan_support.pattern_max_len -= WOW_MAX_REDUCE; 645ba9177fcSCarl Huang ar->wow.wowlan_support.max_pkt_offset -= WOW_MAX_REDUCE; 646ba9177fcSCarl Huang } 647ba9177fcSCarl Huang 648*fec4b898SCarl Huang if (test_bit(WMI_TLV_SERVICE_NLO, ar->wmi->wmi_ab->svc_map)) { 649*fec4b898SCarl Huang ar->wow.wowlan_support.flags |= WIPHY_WOWLAN_NET_DETECT; 650*fec4b898SCarl Huang ar->wow.wowlan_support.max_nd_match_sets = WMI_PNO_MAX_SUPP_NETWORKS; 651*fec4b898SCarl Huang } 652*fec4b898SCarl Huang 653ba9177fcSCarl Huang ar->wow.max_num_patterns = ATH11K_WOW_PATTERNS; 654ba9177fcSCarl Huang ar->wow.wowlan_support.n_patterns = ar->wow.max_num_patterns; 655ba9177fcSCarl Huang ar->hw->wiphy->wowlan = &ar->wow.wowlan_support; 656ba9177fcSCarl Huang 657ba9177fcSCarl Huang device_set_wakeup_capable(ar->ab->dev, true); 658ba9177fcSCarl Huang 659ba9177fcSCarl Huang return 0; 660ba9177fcSCarl Huang } 661