1f0553ca9SKalle Valo // SPDX-License-Identifier: ISC
25fd3ac3cSJanusz Dziedzic /*
38b1083d6SKalle Valo * Copyright (c) 2015-2017 Qualcomm Atheros, Inc.
4fa3440faSWen Gong * Copyright (c) 2018, The Linux Foundation. All rights reserved.
5*358c36eaSJeff Johnson * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
65fd3ac3cSJanusz Dziedzic */
75fd3ac3cSJanusz Dziedzic
85fd3ac3cSJanusz Dziedzic #include "mac.h"
95fd3ac3cSJanusz Dziedzic
105fd3ac3cSJanusz Dziedzic #include <net/mac80211.h>
115fd3ac3cSJanusz Dziedzic #include "hif.h"
125fd3ac3cSJanusz Dziedzic #include "core.h"
135fd3ac3cSJanusz Dziedzic #include "debug.h"
145fd3ac3cSJanusz Dziedzic #include "wmi.h"
155fd3ac3cSJanusz Dziedzic #include "wmi-ops.h"
165fd3ac3cSJanusz Dziedzic
175fd3ac3cSJanusz Dziedzic static const struct wiphy_wowlan_support ath10k_wowlan_support = {
185fd3ac3cSJanusz Dziedzic .flags = WIPHY_WOWLAN_DISCONNECT |
195fd3ac3cSJanusz Dziedzic WIPHY_WOWLAN_MAGIC_PKT,
2025c86619SJanusz Dziedzic .pattern_min_len = WOW_MIN_PATTERN_SIZE,
2125c86619SJanusz Dziedzic .pattern_max_len = WOW_MAX_PATTERN_SIZE,
2225c86619SJanusz Dziedzic .max_pkt_offset = WOW_MAX_PKT_OFFSET,
235fd3ac3cSJanusz Dziedzic };
245fd3ac3cSJanusz Dziedzic
ath10k_wow_vif_cleanup(struct ath10k_vif * arvif)255fd3ac3cSJanusz Dziedzic static int ath10k_wow_vif_cleanup(struct ath10k_vif *arvif)
265fd3ac3cSJanusz Dziedzic {
275fd3ac3cSJanusz Dziedzic struct ath10k *ar = arvif->ar;
285fd3ac3cSJanusz Dziedzic int i, ret;
295fd3ac3cSJanusz Dziedzic
305fd3ac3cSJanusz Dziedzic for (i = 0; i < WOW_EVENT_MAX; i++) {
315fd3ac3cSJanusz Dziedzic ret = ath10k_wmi_wow_add_wakeup_event(ar, arvif->vdev_id, i, 0);
325fd3ac3cSJanusz Dziedzic if (ret) {
335fd3ac3cSJanusz Dziedzic ath10k_warn(ar, "failed to issue wow wakeup for event %s on vdev %i: %d\n",
345fd3ac3cSJanusz Dziedzic wow_wakeup_event(i), arvif->vdev_id, ret);
355fd3ac3cSJanusz Dziedzic return ret;
365fd3ac3cSJanusz Dziedzic }
375fd3ac3cSJanusz Dziedzic }
385fd3ac3cSJanusz Dziedzic
3925c86619SJanusz Dziedzic for (i = 0; i < ar->wow.max_num_patterns; i++) {
4025c86619SJanusz Dziedzic ret = ath10k_wmi_wow_del_pattern(ar, arvif->vdev_id, i);
4125c86619SJanusz Dziedzic if (ret) {
4225c86619SJanusz Dziedzic ath10k_warn(ar, "failed to delete wow pattern %d for vdev %i: %d\n",
4325c86619SJanusz Dziedzic i, arvif->vdev_id, ret);
4425c86619SJanusz Dziedzic return ret;
4525c86619SJanusz Dziedzic }
4625c86619SJanusz Dziedzic }
4725c86619SJanusz Dziedzic
485fd3ac3cSJanusz Dziedzic return 0;
495fd3ac3cSJanusz Dziedzic }
505fd3ac3cSJanusz Dziedzic
ath10k_wow_cleanup(struct ath10k * ar)515fd3ac3cSJanusz Dziedzic static int ath10k_wow_cleanup(struct ath10k *ar)
525fd3ac3cSJanusz Dziedzic {
535fd3ac3cSJanusz Dziedzic struct ath10k_vif *arvif;
545fd3ac3cSJanusz Dziedzic int ret;
555fd3ac3cSJanusz Dziedzic
565fd3ac3cSJanusz Dziedzic lockdep_assert_held(&ar->conf_mutex);
575fd3ac3cSJanusz Dziedzic
585fd3ac3cSJanusz Dziedzic list_for_each_entry(arvif, &ar->arvifs, list) {
595fd3ac3cSJanusz Dziedzic ret = ath10k_wow_vif_cleanup(arvif);
605fd3ac3cSJanusz Dziedzic if (ret) {
615fd3ac3cSJanusz Dziedzic ath10k_warn(ar, "failed to clean wow wakeups on vdev %i: %d\n",
625fd3ac3cSJanusz Dziedzic arvif->vdev_id, ret);
635fd3ac3cSJanusz Dziedzic return ret;
645fd3ac3cSJanusz Dziedzic }
655fd3ac3cSJanusz Dziedzic }
665fd3ac3cSJanusz Dziedzic
675fd3ac3cSJanusz Dziedzic return 0;
685fd3ac3cSJanusz Dziedzic }
695fd3ac3cSJanusz Dziedzic
70bdf2bd9aSKalle Valo /*
71fa3440faSWen Gong * Convert a 802.3 format to a 802.11 format.
72fa3440faSWen Gong * +------------+-----------+--------+----------------+
73fa3440faSWen Gong * 802.3: |dest mac(6B)|src mac(6B)|type(2B)| body... |
74fa3440faSWen Gong * +------------+-----------+--------+----------------+
75fa3440faSWen Gong * |__ |_______ |____________ |________
76fa3440faSWen Gong * | | | |
77fa3440faSWen Gong * +--+------------+----+-----------+---------------+-----------+
78fa3440faSWen Gong * 802.11: |4B|dest mac(6B)| 6B |src mac(6B)| 8B |type(2B)| body... |
79fa3440faSWen Gong * +--+------------+----+-----------+---------------+-----------+
80fa3440faSWen Gong */
ath10k_wow_convert_8023_to_80211(struct cfg80211_pkt_pattern * new,const struct cfg80211_pkt_pattern * old)81bdf2bd9aSKalle Valo static void ath10k_wow_convert_8023_to_80211(struct cfg80211_pkt_pattern *new,
82fa3440faSWen Gong const struct cfg80211_pkt_pattern *old)
83fa3440faSWen Gong {
84fa3440faSWen Gong u8 hdr_8023_pattern[ETH_HLEN] = {};
85fa3440faSWen Gong u8 hdr_8023_bit_mask[ETH_HLEN] = {};
86fa3440faSWen Gong u8 hdr_80211_pattern[WOW_HDR_LEN] = {};
87fa3440faSWen Gong u8 hdr_80211_bit_mask[WOW_HDR_LEN] = {};
88fa3440faSWen Gong
89fa3440faSWen Gong int total_len = old->pkt_offset + old->pattern_len;
90fa3440faSWen Gong int hdr_80211_end_offset;
91fa3440faSWen Gong
92fa3440faSWen Gong struct ieee80211_hdr_3addr *new_hdr_pattern =
93fa3440faSWen Gong (struct ieee80211_hdr_3addr *)hdr_80211_pattern;
94fa3440faSWen Gong struct ieee80211_hdr_3addr *new_hdr_mask =
95fa3440faSWen Gong (struct ieee80211_hdr_3addr *)hdr_80211_bit_mask;
96fa3440faSWen Gong struct ethhdr *old_hdr_pattern = (struct ethhdr *)hdr_8023_pattern;
97fa3440faSWen Gong struct ethhdr *old_hdr_mask = (struct ethhdr *)hdr_8023_bit_mask;
98fa3440faSWen Gong int hdr_len = sizeof(*new_hdr_pattern);
99fa3440faSWen Gong
100fa3440faSWen Gong struct rfc1042_hdr *new_rfc_pattern =
101fa3440faSWen Gong (struct rfc1042_hdr *)(hdr_80211_pattern + hdr_len);
102fa3440faSWen Gong struct rfc1042_hdr *new_rfc_mask =
103fa3440faSWen Gong (struct rfc1042_hdr *)(hdr_80211_bit_mask + hdr_len);
104fa3440faSWen Gong int rfc_len = sizeof(*new_rfc_pattern);
105fa3440faSWen Gong
106fa3440faSWen Gong memcpy(hdr_8023_pattern + old->pkt_offset,
107fa3440faSWen Gong old->pattern, ETH_HLEN - old->pkt_offset);
108fa3440faSWen Gong memcpy(hdr_8023_bit_mask + old->pkt_offset,
109fa3440faSWen Gong old->mask, ETH_HLEN - old->pkt_offset);
110fa3440faSWen Gong
111fa3440faSWen Gong /* Copy destination address */
112fa3440faSWen Gong memcpy(new_hdr_pattern->addr1, old_hdr_pattern->h_dest, ETH_ALEN);
113fa3440faSWen Gong memcpy(new_hdr_mask->addr1, old_hdr_mask->h_dest, ETH_ALEN);
114fa3440faSWen Gong
115fa3440faSWen Gong /* Copy source address */
116fa3440faSWen Gong memcpy(new_hdr_pattern->addr3, old_hdr_pattern->h_source, ETH_ALEN);
117fa3440faSWen Gong memcpy(new_hdr_mask->addr3, old_hdr_mask->h_source, ETH_ALEN);
118fa3440faSWen Gong
119fa3440faSWen Gong /* Copy logic link type */
120fa3440faSWen Gong memcpy(&new_rfc_pattern->snap_type,
121fa3440faSWen Gong &old_hdr_pattern->h_proto,
122fa3440faSWen Gong sizeof(old_hdr_pattern->h_proto));
123fa3440faSWen Gong memcpy(&new_rfc_mask->snap_type,
124fa3440faSWen Gong &old_hdr_mask->h_proto,
125fa3440faSWen Gong sizeof(old_hdr_mask->h_proto));
126fa3440faSWen Gong
127c8cb0964SYangtao Li /* Calculate new pkt_offset */
128fa3440faSWen Gong if (old->pkt_offset < ETH_ALEN)
129fa3440faSWen Gong new->pkt_offset = old->pkt_offset +
130fa3440faSWen Gong offsetof(struct ieee80211_hdr_3addr, addr1);
131fa3440faSWen Gong else if (old->pkt_offset < offsetof(struct ethhdr, h_proto))
132fa3440faSWen Gong new->pkt_offset = old->pkt_offset +
133fa3440faSWen Gong offsetof(struct ieee80211_hdr_3addr, addr3) -
134fa3440faSWen Gong offsetof(struct ethhdr, h_source);
135fa3440faSWen Gong else
136fa3440faSWen Gong new->pkt_offset = old->pkt_offset + hdr_len + rfc_len - ETH_HLEN;
137fa3440faSWen Gong
138c8cb0964SYangtao Li /* Calculate new hdr end offset */
139fa3440faSWen Gong if (total_len > ETH_HLEN)
140fa3440faSWen Gong hdr_80211_end_offset = hdr_len + rfc_len;
141fa3440faSWen Gong else if (total_len > offsetof(struct ethhdr, h_proto))
142fa3440faSWen Gong hdr_80211_end_offset = hdr_len + rfc_len + total_len - ETH_HLEN;
143fa3440faSWen Gong else if (total_len > ETH_ALEN)
144fa3440faSWen Gong hdr_80211_end_offset = total_len - ETH_ALEN +
145fa3440faSWen Gong offsetof(struct ieee80211_hdr_3addr, addr3);
146fa3440faSWen Gong else
147fa3440faSWen Gong hdr_80211_end_offset = total_len +
148fa3440faSWen Gong offsetof(struct ieee80211_hdr_3addr, addr1);
149fa3440faSWen Gong
150fa3440faSWen Gong new->pattern_len = hdr_80211_end_offset - new->pkt_offset;
151fa3440faSWen Gong
152fa3440faSWen Gong memcpy((u8 *)new->pattern,
153fa3440faSWen Gong hdr_80211_pattern + new->pkt_offset,
154fa3440faSWen Gong new->pattern_len);
155fa3440faSWen Gong memcpy((u8 *)new->mask,
156fa3440faSWen Gong hdr_80211_bit_mask + new->pkt_offset,
157fa3440faSWen Gong new->pattern_len);
158fa3440faSWen Gong
159fa3440faSWen Gong if (total_len > ETH_HLEN) {
160fa3440faSWen Gong /* Copy frame body */
161fa3440faSWen Gong memcpy((u8 *)new->pattern + new->pattern_len,
162fa3440faSWen Gong (void *)old->pattern + ETH_HLEN - old->pkt_offset,
163fa3440faSWen Gong total_len - ETH_HLEN);
164fa3440faSWen Gong memcpy((u8 *)new->mask + new->pattern_len,
165fa3440faSWen Gong (void *)old->mask + ETH_HLEN - old->pkt_offset,
166fa3440faSWen Gong total_len - ETH_HLEN);
167fa3440faSWen Gong
168fa3440faSWen Gong new->pattern_len += total_len - ETH_HLEN;
169fa3440faSWen Gong }
170fa3440faSWen Gong }
171fa3440faSWen Gong
ath10k_wmi_pno_check(struct ath10k * ar,u32 vdev_id,struct cfg80211_sched_scan_request * nd_config,struct wmi_pno_scan_req * pno)172ce834e28SWen Gong static int ath10k_wmi_pno_check(struct ath10k *ar, u32 vdev_id,
173ce834e28SWen Gong struct cfg80211_sched_scan_request *nd_config,
174ce834e28SWen Gong struct wmi_pno_scan_req *pno)
175ce834e28SWen Gong {
176ce834e28SWen Gong int i, j, ret = 0;
177ce834e28SWen Gong u8 ssid_len;
178ce834e28SWen Gong
179ce834e28SWen Gong pno->enable = 1;
180ce834e28SWen Gong pno->vdev_id = vdev_id;
181ce834e28SWen Gong pno->uc_networks_count = nd_config->n_match_sets;
182ce834e28SWen Gong
183ce834e28SWen Gong if (!pno->uc_networks_count ||
184ce834e28SWen Gong pno->uc_networks_count > WMI_PNO_MAX_SUPP_NETWORKS)
185ce834e28SWen Gong return -EINVAL;
186ce834e28SWen Gong
187ce834e28SWen Gong if (nd_config->n_channels > WMI_PNO_MAX_NETW_CHANNELS_EX)
188ce834e28SWen Gong return -EINVAL;
189ce834e28SWen Gong
190ce834e28SWen Gong /* Filling per profile params */
191ce834e28SWen Gong for (i = 0; i < pno->uc_networks_count; i++) {
192ce834e28SWen Gong ssid_len = nd_config->match_sets[i].ssid.ssid_len;
193ce834e28SWen Gong
194ce834e28SWen Gong if (ssid_len == 0 || ssid_len > 32)
195ce834e28SWen Gong return -EINVAL;
196ce834e28SWen Gong
197ce834e28SWen Gong pno->a_networks[i].ssid.ssid_len = __cpu_to_le32(ssid_len);
198ce834e28SWen Gong
199ce834e28SWen Gong memcpy(pno->a_networks[i].ssid.ssid,
200ce834e28SWen Gong nd_config->match_sets[i].ssid.ssid,
201ce834e28SWen Gong nd_config->match_sets[i].ssid.ssid_len);
202ce834e28SWen Gong pno->a_networks[i].authentication = 0;
203ce834e28SWen Gong pno->a_networks[i].encryption = 0;
204ce834e28SWen Gong pno->a_networks[i].bcast_nw_type = 0;
205ce834e28SWen Gong
206ce834e28SWen Gong /*Copying list of valid channel into request */
207ce834e28SWen Gong pno->a_networks[i].channel_count = nd_config->n_channels;
208ce834e28SWen Gong pno->a_networks[i].rssi_threshold = nd_config->match_sets[i].rssi_thold;
209ce834e28SWen Gong
210ce834e28SWen Gong for (j = 0; j < nd_config->n_channels; j++) {
211ce834e28SWen Gong pno->a_networks[i].channels[j] =
212ce834e28SWen Gong nd_config->channels[j]->center_freq;
213ce834e28SWen Gong }
214ce834e28SWen Gong }
215ce834e28SWen Gong
216ce834e28SWen Gong /* set scan to passive if no SSIDs are specified in the request */
217ce834e28SWen Gong if (nd_config->n_ssids == 0)
218ce834e28SWen Gong pno->do_passive_scan = true;
219ce834e28SWen Gong else
220ce834e28SWen Gong pno->do_passive_scan = false;
221ce834e28SWen Gong
222ce834e28SWen Gong for (i = 0; i < nd_config->n_ssids; i++) {
223ce834e28SWen Gong j = 0;
224ce834e28SWen Gong while (j < pno->uc_networks_count) {
225ce834e28SWen Gong if (__le32_to_cpu(pno->a_networks[j].ssid.ssid_len) ==
226ce834e28SWen Gong nd_config->ssids[i].ssid_len &&
227ce834e28SWen Gong (memcmp(pno->a_networks[j].ssid.ssid,
228ce834e28SWen Gong nd_config->ssids[i].ssid,
229ce834e28SWen Gong __le32_to_cpu(pno->a_networks[j].ssid.ssid_len)) == 0)) {
230ce834e28SWen Gong pno->a_networks[j].bcast_nw_type = BCAST_HIDDEN;
231ce834e28SWen Gong break;
232ce834e28SWen Gong }
233ce834e28SWen Gong j++;
234ce834e28SWen Gong }
235ce834e28SWen Gong }
236ce834e28SWen Gong
237ce834e28SWen Gong if (nd_config->n_scan_plans == 2) {
238ce834e28SWen Gong pno->fast_scan_period = nd_config->scan_plans[0].interval * MSEC_PER_SEC;
239ce834e28SWen Gong pno->fast_scan_max_cycles = nd_config->scan_plans[0].iterations;
240ce834e28SWen Gong pno->slow_scan_period =
241ce834e28SWen Gong nd_config->scan_plans[1].interval * MSEC_PER_SEC;
242ce834e28SWen Gong } else if (nd_config->n_scan_plans == 1) {
243ce834e28SWen Gong pno->fast_scan_period = nd_config->scan_plans[0].interval * MSEC_PER_SEC;
244ce834e28SWen Gong pno->fast_scan_max_cycles = 1;
245ce834e28SWen Gong pno->slow_scan_period = nd_config->scan_plans[0].interval * MSEC_PER_SEC;
246ce834e28SWen Gong } else {
247ce834e28SWen Gong ath10k_warn(ar, "Invalid number of scan plans %d !!",
248ce834e28SWen Gong nd_config->n_scan_plans);
249ce834e28SWen Gong }
250ce834e28SWen Gong
251ce834e28SWen Gong if (nd_config->flags & NL80211_SCAN_FLAG_RANDOM_ADDR) {
252ce834e28SWen Gong /* enable mac randomization */
253ce834e28SWen Gong pno->enable_pno_scan_randomization = 1;
254ce834e28SWen Gong memcpy(pno->mac_addr, nd_config->mac_addr, ETH_ALEN);
255ce834e28SWen Gong memcpy(pno->mac_addr_mask, nd_config->mac_addr_mask, ETH_ALEN);
256ce834e28SWen Gong }
257ce834e28SWen Gong
258ce834e28SWen Gong pno->delay_start_time = nd_config->delay;
259ce834e28SWen Gong
260ce834e28SWen Gong /* Current FW does not support min-max range for dwell time */
261ce834e28SWen Gong pno->active_max_time = WMI_ACTIVE_MAX_CHANNEL_TIME;
262ce834e28SWen Gong pno->passive_max_time = WMI_PASSIVE_MAX_CHANNEL_TIME;
263ce834e28SWen Gong return ret;
264ce834e28SWen Gong }
265ce834e28SWen Gong
ath10k_vif_wow_set_wakeups(struct ath10k_vif * arvif,struct cfg80211_wowlan * wowlan)2665fd3ac3cSJanusz Dziedzic static int ath10k_vif_wow_set_wakeups(struct ath10k_vif *arvif,
2675fd3ac3cSJanusz Dziedzic struct cfg80211_wowlan *wowlan)
2685fd3ac3cSJanusz Dziedzic {
2695fd3ac3cSJanusz Dziedzic int ret, i;
2705fd3ac3cSJanusz Dziedzic unsigned long wow_mask = 0;
2715fd3ac3cSJanusz Dziedzic struct ath10k *ar = arvif->ar;
27225c86619SJanusz Dziedzic const struct cfg80211_pkt_pattern *patterns = wowlan->patterns;
27325c86619SJanusz Dziedzic int pattern_id = 0;
2745fd3ac3cSJanusz Dziedzic
2755fd3ac3cSJanusz Dziedzic /* Setup requested WOW features */
2765fd3ac3cSJanusz Dziedzic switch (arvif->vdev_type) {
2775fd3ac3cSJanusz Dziedzic case WMI_VDEV_TYPE_IBSS:
2785fd3ac3cSJanusz Dziedzic __set_bit(WOW_BEACON_EVENT, &wow_mask);
2791885c0f7SGustavo A. R. Silva fallthrough;
2805fd3ac3cSJanusz Dziedzic case WMI_VDEV_TYPE_AP:
2815fd3ac3cSJanusz Dziedzic __set_bit(WOW_DEAUTH_RECVD_EVENT, &wow_mask);
2825fd3ac3cSJanusz Dziedzic __set_bit(WOW_DISASSOC_RECVD_EVENT, &wow_mask);
2835fd3ac3cSJanusz Dziedzic __set_bit(WOW_PROBE_REQ_WPS_IE_EVENT, &wow_mask);
2845fd3ac3cSJanusz Dziedzic __set_bit(WOW_AUTH_REQ_EVENT, &wow_mask);
2855fd3ac3cSJanusz Dziedzic __set_bit(WOW_ASSOC_REQ_EVENT, &wow_mask);
2865fd3ac3cSJanusz Dziedzic __set_bit(WOW_HTT_EVENT, &wow_mask);
2875fd3ac3cSJanusz Dziedzic __set_bit(WOW_RA_MATCH_EVENT, &wow_mask);
2885fd3ac3cSJanusz Dziedzic break;
2895fd3ac3cSJanusz Dziedzic case WMI_VDEV_TYPE_STA:
2905fd3ac3cSJanusz Dziedzic if (wowlan->disconnect) {
2915fd3ac3cSJanusz Dziedzic __set_bit(WOW_DEAUTH_RECVD_EVENT, &wow_mask);
2925fd3ac3cSJanusz Dziedzic __set_bit(WOW_DISASSOC_RECVD_EVENT, &wow_mask);
2935fd3ac3cSJanusz Dziedzic __set_bit(WOW_BMISS_EVENT, &wow_mask);
2945fd3ac3cSJanusz Dziedzic __set_bit(WOW_CSA_IE_EVENT, &wow_mask);
2955fd3ac3cSJanusz Dziedzic }
2965fd3ac3cSJanusz Dziedzic
2975fd3ac3cSJanusz Dziedzic if (wowlan->magic_pkt)
2985fd3ac3cSJanusz Dziedzic __set_bit(WOW_MAGIC_PKT_RECVD_EVENT, &wow_mask);
299ce834e28SWen Gong
300ce834e28SWen Gong if (wowlan->nd_config) {
301ce834e28SWen Gong struct wmi_pno_scan_req *pno;
302ce834e28SWen Gong int ret;
303ce834e28SWen Gong
304ce834e28SWen Gong pno = kzalloc(sizeof(*pno), GFP_KERNEL);
305ce834e28SWen Gong if (!pno)
306ce834e28SWen Gong return -ENOMEM;
307ce834e28SWen Gong
308ce834e28SWen Gong ar->nlo_enabled = true;
309ce834e28SWen Gong
310ce834e28SWen Gong ret = ath10k_wmi_pno_check(ar, arvif->vdev_id,
311ce834e28SWen Gong wowlan->nd_config, pno);
312ce834e28SWen Gong if (!ret) {
313ce834e28SWen Gong ath10k_wmi_wow_config_pno(ar, arvif->vdev_id, pno);
314ce834e28SWen Gong __set_bit(WOW_NLO_DETECTED_EVENT, &wow_mask);
315ce834e28SWen Gong }
316ce834e28SWen Gong
317ce834e28SWen Gong kfree(pno);
318ce834e28SWen Gong }
3195fd3ac3cSJanusz Dziedzic break;
3205fd3ac3cSJanusz Dziedzic default:
3215fd3ac3cSJanusz Dziedzic break;
3225fd3ac3cSJanusz Dziedzic }
3235fd3ac3cSJanusz Dziedzic
32425c86619SJanusz Dziedzic for (i = 0; i < wowlan->n_patterns; i++) {
32525c86619SJanusz Dziedzic u8 bitmask[WOW_MAX_PATTERN_SIZE] = {};
326fa3440faSWen Gong u8 ath_pattern[WOW_MAX_PATTERN_SIZE] = {};
327fa3440faSWen Gong u8 ath_bitmask[WOW_MAX_PATTERN_SIZE] = {};
328fa3440faSWen Gong struct cfg80211_pkt_pattern new_pattern = {};
329fa3440faSWen Gong struct cfg80211_pkt_pattern old_pattern = patterns[i];
33025c86619SJanusz Dziedzic int j;
33125c86619SJanusz Dziedzic
332fa3440faSWen Gong new_pattern.pattern = ath_pattern;
333fa3440faSWen Gong new_pattern.mask = ath_bitmask;
33425c86619SJanusz Dziedzic if (patterns[i].pattern_len > WOW_MAX_PATTERN_SIZE)
33525c86619SJanusz Dziedzic continue;
33625c86619SJanusz Dziedzic /* convert bytemask to bitmask */
33725c86619SJanusz Dziedzic for (j = 0; j < patterns[i].pattern_len; j++)
33825c86619SJanusz Dziedzic if (patterns[i].mask[j / 8] & BIT(j % 8))
33925c86619SJanusz Dziedzic bitmask[j] = 0xff;
340fa3440faSWen Gong old_pattern.mask = bitmask;
341fa3440faSWen Gong
342fa3440faSWen Gong if (ar->wmi.rx_decap_mode == ATH10K_HW_TXRX_NATIVE_WIFI) {
343e3fb3d44SWen Gong if (patterns[i].pkt_offset < ETH_HLEN) {
344fa3440faSWen Gong ath10k_wow_convert_8023_to_80211(&new_pattern,
345fa3440faSWen Gong &old_pattern);
346e3fb3d44SWen Gong } else {
347e3fb3d44SWen Gong new_pattern = old_pattern;
348fa3440faSWen Gong new_pattern.pkt_offset += WOW_HDR_LEN - ETH_HLEN;
349fa3440faSWen Gong }
350e3fb3d44SWen Gong }
351fa3440faSWen Gong
352fa3440faSWen Gong if (WARN_ON(new_pattern.pattern_len > WOW_MAX_PATTERN_SIZE))
353fa3440faSWen Gong return -EINVAL;
35425c86619SJanusz Dziedzic
35525c86619SJanusz Dziedzic ret = ath10k_wmi_wow_add_pattern(ar, arvif->vdev_id,
35625c86619SJanusz Dziedzic pattern_id,
357fa3440faSWen Gong new_pattern.pattern,
358fa3440faSWen Gong new_pattern.mask,
359fa3440faSWen Gong new_pattern.pattern_len,
360fa3440faSWen Gong new_pattern.pkt_offset);
36125c86619SJanusz Dziedzic if (ret) {
36225c86619SJanusz Dziedzic ath10k_warn(ar, "failed to add pattern %i to vdev %i: %d\n",
36325c86619SJanusz Dziedzic pattern_id,
36425c86619SJanusz Dziedzic arvif->vdev_id, ret);
36525c86619SJanusz Dziedzic return ret;
36625c86619SJanusz Dziedzic }
36725c86619SJanusz Dziedzic
36825c86619SJanusz Dziedzic pattern_id++;
36925c86619SJanusz Dziedzic __set_bit(WOW_PATTERN_MATCH_EVENT, &wow_mask);
37025c86619SJanusz Dziedzic }
37125c86619SJanusz Dziedzic
3725fd3ac3cSJanusz Dziedzic for (i = 0; i < WOW_EVENT_MAX; i++) {
3735fd3ac3cSJanusz Dziedzic if (!test_bit(i, &wow_mask))
3745fd3ac3cSJanusz Dziedzic continue;
3755fd3ac3cSJanusz Dziedzic ret = ath10k_wmi_wow_add_wakeup_event(ar, arvif->vdev_id, i, 1);
3765fd3ac3cSJanusz Dziedzic if (ret) {
3775fd3ac3cSJanusz Dziedzic ath10k_warn(ar, "failed to enable wakeup event %s on vdev %i: %d\n",
3785fd3ac3cSJanusz Dziedzic wow_wakeup_event(i), arvif->vdev_id, ret);
3795fd3ac3cSJanusz Dziedzic return ret;
3805fd3ac3cSJanusz Dziedzic }
3815fd3ac3cSJanusz Dziedzic }
3825fd3ac3cSJanusz Dziedzic
3835fd3ac3cSJanusz Dziedzic return 0;
3845fd3ac3cSJanusz Dziedzic }
3855fd3ac3cSJanusz Dziedzic
ath10k_wow_set_wakeups(struct ath10k * ar,struct cfg80211_wowlan * wowlan)3865fd3ac3cSJanusz Dziedzic static int ath10k_wow_set_wakeups(struct ath10k *ar,
3875fd3ac3cSJanusz Dziedzic struct cfg80211_wowlan *wowlan)
3885fd3ac3cSJanusz Dziedzic {
3895fd3ac3cSJanusz Dziedzic struct ath10k_vif *arvif;
3905fd3ac3cSJanusz Dziedzic int ret;
3915fd3ac3cSJanusz Dziedzic
3925fd3ac3cSJanusz Dziedzic lockdep_assert_held(&ar->conf_mutex);
3935fd3ac3cSJanusz Dziedzic
3945fd3ac3cSJanusz Dziedzic list_for_each_entry(arvif, &ar->arvifs, list) {
3955fd3ac3cSJanusz Dziedzic ret = ath10k_vif_wow_set_wakeups(arvif, wowlan);
3965fd3ac3cSJanusz Dziedzic if (ret) {
3975fd3ac3cSJanusz Dziedzic ath10k_warn(ar, "failed to set wow wakeups on vdev %i: %d\n",
3985fd3ac3cSJanusz Dziedzic arvif->vdev_id, ret);
3995fd3ac3cSJanusz Dziedzic return ret;
4005fd3ac3cSJanusz Dziedzic }
4015fd3ac3cSJanusz Dziedzic }
4025fd3ac3cSJanusz Dziedzic
4035fd3ac3cSJanusz Dziedzic return 0;
4045fd3ac3cSJanusz Dziedzic }
4055fd3ac3cSJanusz Dziedzic
ath10k_vif_wow_clean_nlo(struct ath10k_vif * arvif)406ce834e28SWen Gong static int ath10k_vif_wow_clean_nlo(struct ath10k_vif *arvif)
407ce834e28SWen Gong {
408ce834e28SWen Gong int ret = 0;
409ce834e28SWen Gong struct ath10k *ar = arvif->ar;
410ce834e28SWen Gong
411ce834e28SWen Gong switch (arvif->vdev_type) {
412ce834e28SWen Gong case WMI_VDEV_TYPE_STA:
413ce834e28SWen Gong if (ar->nlo_enabled) {
414ce834e28SWen Gong struct wmi_pno_scan_req *pno;
415ce834e28SWen Gong
416ce834e28SWen Gong pno = kzalloc(sizeof(*pno), GFP_KERNEL);
417ce834e28SWen Gong if (!pno)
418ce834e28SWen Gong return -ENOMEM;
419ce834e28SWen Gong
420ce834e28SWen Gong pno->enable = 0;
421ce834e28SWen Gong ar->nlo_enabled = false;
422ce834e28SWen Gong ret = ath10k_wmi_wow_config_pno(ar, arvif->vdev_id, pno);
423ce834e28SWen Gong kfree(pno);
424ce834e28SWen Gong }
425ce834e28SWen Gong break;
426ce834e28SWen Gong default:
427ce834e28SWen Gong break;
428ce834e28SWen Gong }
429ce834e28SWen Gong return ret;
430ce834e28SWen Gong }
431ce834e28SWen Gong
ath10k_wow_nlo_cleanup(struct ath10k * ar)432ce834e28SWen Gong static int ath10k_wow_nlo_cleanup(struct ath10k *ar)
433ce834e28SWen Gong {
434ce834e28SWen Gong struct ath10k_vif *arvif;
435ce834e28SWen Gong int ret = 0;
436ce834e28SWen Gong
437ce834e28SWen Gong lockdep_assert_held(&ar->conf_mutex);
438ce834e28SWen Gong
439ce834e28SWen Gong list_for_each_entry(arvif, &ar->arvifs, list) {
440ce834e28SWen Gong ret = ath10k_vif_wow_clean_nlo(arvif);
441ce834e28SWen Gong if (ret) {
442ce834e28SWen Gong ath10k_warn(ar, "failed to clean nlo settings on vdev %i: %d\n",
443ce834e28SWen Gong arvif->vdev_id, ret);
444ce834e28SWen Gong return ret;
445ce834e28SWen Gong }
446ce834e28SWen Gong }
447ce834e28SWen Gong
448ce834e28SWen Gong return 0;
449ce834e28SWen Gong }
450ce834e28SWen Gong
ath10k_wow_enable(struct ath10k * ar)4515fd3ac3cSJanusz Dziedzic static int ath10k_wow_enable(struct ath10k *ar)
4525fd3ac3cSJanusz Dziedzic {
4535fd3ac3cSJanusz Dziedzic int ret;
4545fd3ac3cSJanusz Dziedzic
4555fd3ac3cSJanusz Dziedzic lockdep_assert_held(&ar->conf_mutex);
4565fd3ac3cSJanusz Dziedzic
4575fd3ac3cSJanusz Dziedzic reinit_completion(&ar->target_suspend);
4585fd3ac3cSJanusz Dziedzic
4595fd3ac3cSJanusz Dziedzic ret = ath10k_wmi_wow_enable(ar);
4605fd3ac3cSJanusz Dziedzic if (ret) {
4615fd3ac3cSJanusz Dziedzic ath10k_warn(ar, "failed to issue wow enable: %d\n", ret);
4625fd3ac3cSJanusz Dziedzic return ret;
4635fd3ac3cSJanusz Dziedzic }
4645fd3ac3cSJanusz Dziedzic
4655fd3ac3cSJanusz Dziedzic ret = wait_for_completion_timeout(&ar->target_suspend, 3 * HZ);
4665fd3ac3cSJanusz Dziedzic if (ret == 0) {
4675fd3ac3cSJanusz Dziedzic ath10k_warn(ar, "timed out while waiting for suspend completion\n");
4685fd3ac3cSJanusz Dziedzic return -ETIMEDOUT;
4695fd3ac3cSJanusz Dziedzic }
4705fd3ac3cSJanusz Dziedzic
4715fd3ac3cSJanusz Dziedzic return 0;
4725fd3ac3cSJanusz Dziedzic }
4735fd3ac3cSJanusz Dziedzic
ath10k_wow_wakeup(struct ath10k * ar)4745fd3ac3cSJanusz Dziedzic static int ath10k_wow_wakeup(struct ath10k *ar)
4755fd3ac3cSJanusz Dziedzic {
4765fd3ac3cSJanusz Dziedzic int ret;
4775fd3ac3cSJanusz Dziedzic
4785fd3ac3cSJanusz Dziedzic lockdep_assert_held(&ar->conf_mutex);
4795fd3ac3cSJanusz Dziedzic
4805fd3ac3cSJanusz Dziedzic reinit_completion(&ar->wow.wakeup_completed);
4815fd3ac3cSJanusz Dziedzic
4825fd3ac3cSJanusz Dziedzic ret = ath10k_wmi_wow_host_wakeup_ind(ar);
4835fd3ac3cSJanusz Dziedzic if (ret) {
4845fd3ac3cSJanusz Dziedzic ath10k_warn(ar, "failed to send wow wakeup indication: %d\n",
4855fd3ac3cSJanusz Dziedzic ret);
4865fd3ac3cSJanusz Dziedzic return ret;
4875fd3ac3cSJanusz Dziedzic }
4885fd3ac3cSJanusz Dziedzic
4895fd3ac3cSJanusz Dziedzic ret = wait_for_completion_timeout(&ar->wow.wakeup_completed, 3 * HZ);
4905fd3ac3cSJanusz Dziedzic if (ret == 0) {
4915fd3ac3cSJanusz Dziedzic ath10k_warn(ar, "timed out while waiting for wow wakeup completion\n");
4925fd3ac3cSJanusz Dziedzic return -ETIMEDOUT;
4935fd3ac3cSJanusz Dziedzic }
4945fd3ac3cSJanusz Dziedzic
4955fd3ac3cSJanusz Dziedzic return 0;
4965fd3ac3cSJanusz Dziedzic }
4975fd3ac3cSJanusz Dziedzic
ath10k_wow_op_suspend(struct ieee80211_hw * hw,struct cfg80211_wowlan * wowlan)4985fd3ac3cSJanusz Dziedzic int ath10k_wow_op_suspend(struct ieee80211_hw *hw,
4995fd3ac3cSJanusz Dziedzic struct cfg80211_wowlan *wowlan)
5005fd3ac3cSJanusz Dziedzic {
5015fd3ac3cSJanusz Dziedzic struct ath10k *ar = hw->priv;
5025fd3ac3cSJanusz Dziedzic int ret;
5035fd3ac3cSJanusz Dziedzic
5045fd3ac3cSJanusz Dziedzic mutex_lock(&ar->conf_mutex);
5055fd3ac3cSJanusz Dziedzic
5065fd3ac3cSJanusz Dziedzic if (WARN_ON(!test_bit(ATH10K_FW_FEATURE_WOWLAN_SUPPORT,
507c4cdf753SKalle Valo ar->running_fw->fw_file.fw_features))) {
5085fd3ac3cSJanusz Dziedzic ret = 1;
5095fd3ac3cSJanusz Dziedzic goto exit;
5105fd3ac3cSJanusz Dziedzic }
5115fd3ac3cSJanusz Dziedzic
5125fd3ac3cSJanusz Dziedzic ret = ath10k_wow_cleanup(ar);
5135fd3ac3cSJanusz Dziedzic if (ret) {
5145fd3ac3cSJanusz Dziedzic ath10k_warn(ar, "failed to clear wow wakeup events: %d\n",
5155fd3ac3cSJanusz Dziedzic ret);
5165fd3ac3cSJanusz Dziedzic goto exit;
5175fd3ac3cSJanusz Dziedzic }
5185fd3ac3cSJanusz Dziedzic
5195fd3ac3cSJanusz Dziedzic ret = ath10k_wow_set_wakeups(ar, wowlan);
5205fd3ac3cSJanusz Dziedzic if (ret) {
5215fd3ac3cSJanusz Dziedzic ath10k_warn(ar, "failed to set wow wakeup events: %d\n",
5225fd3ac3cSJanusz Dziedzic ret);
5235fd3ac3cSJanusz Dziedzic goto cleanup;
5245fd3ac3cSJanusz Dziedzic }
5255fd3ac3cSJanusz Dziedzic
526828853acSWen Gong ath10k_mac_wait_tx_complete(ar);
527828853acSWen Gong
5285fd3ac3cSJanusz Dziedzic ret = ath10k_wow_enable(ar);
5295fd3ac3cSJanusz Dziedzic if (ret) {
5305fd3ac3cSJanusz Dziedzic ath10k_warn(ar, "failed to start wow: %d\n", ret);
5315fd3ac3cSJanusz Dziedzic goto cleanup;
5325fd3ac3cSJanusz Dziedzic }
5335fd3ac3cSJanusz Dziedzic
5345fd3ac3cSJanusz Dziedzic ret = ath10k_hif_suspend(ar);
5355fd3ac3cSJanusz Dziedzic if (ret) {
5365fd3ac3cSJanusz Dziedzic ath10k_warn(ar, "failed to suspend hif: %d\n", ret);
5375fd3ac3cSJanusz Dziedzic goto wakeup;
5385fd3ac3cSJanusz Dziedzic }
5395fd3ac3cSJanusz Dziedzic
5405fd3ac3cSJanusz Dziedzic goto exit;
5415fd3ac3cSJanusz Dziedzic
5425fd3ac3cSJanusz Dziedzic wakeup:
5435fd3ac3cSJanusz Dziedzic ath10k_wow_wakeup(ar);
5445fd3ac3cSJanusz Dziedzic
5455fd3ac3cSJanusz Dziedzic cleanup:
5465fd3ac3cSJanusz Dziedzic ath10k_wow_cleanup(ar);
5475fd3ac3cSJanusz Dziedzic
5485fd3ac3cSJanusz Dziedzic exit:
5495fd3ac3cSJanusz Dziedzic mutex_unlock(&ar->conf_mutex);
5505fd3ac3cSJanusz Dziedzic return ret ? 1 : 0;
5515fd3ac3cSJanusz Dziedzic }
5525fd3ac3cSJanusz Dziedzic
ath10k_wow_op_set_wakeup(struct ieee80211_hw * hw,bool enabled)553393b706cSRyan Hsu void ath10k_wow_op_set_wakeup(struct ieee80211_hw *hw, bool enabled)
554393b706cSRyan Hsu {
555393b706cSRyan Hsu struct ath10k *ar = hw->priv;
556393b706cSRyan Hsu
557393b706cSRyan Hsu mutex_lock(&ar->conf_mutex);
558393b706cSRyan Hsu if (test_bit(ATH10K_FW_FEATURE_WOWLAN_SUPPORT,
559393b706cSRyan Hsu ar->running_fw->fw_file.fw_features)) {
560393b706cSRyan Hsu device_set_wakeup_enable(ar->dev, enabled);
561393b706cSRyan Hsu }
562393b706cSRyan Hsu mutex_unlock(&ar->conf_mutex);
563393b706cSRyan Hsu }
564393b706cSRyan Hsu
ath10k_wow_op_resume(struct ieee80211_hw * hw)5655fd3ac3cSJanusz Dziedzic int ath10k_wow_op_resume(struct ieee80211_hw *hw)
5665fd3ac3cSJanusz Dziedzic {
5675fd3ac3cSJanusz Dziedzic struct ath10k *ar = hw->priv;
5685fd3ac3cSJanusz Dziedzic int ret;
5695fd3ac3cSJanusz Dziedzic
5705fd3ac3cSJanusz Dziedzic mutex_lock(&ar->conf_mutex);
5715fd3ac3cSJanusz Dziedzic
5725fd3ac3cSJanusz Dziedzic if (WARN_ON(!test_bit(ATH10K_FW_FEATURE_WOWLAN_SUPPORT,
573c4cdf753SKalle Valo ar->running_fw->fw_file.fw_features))) {
5745fd3ac3cSJanusz Dziedzic ret = 1;
5755fd3ac3cSJanusz Dziedzic goto exit;
5765fd3ac3cSJanusz Dziedzic }
5775fd3ac3cSJanusz Dziedzic
5785fd3ac3cSJanusz Dziedzic ret = ath10k_hif_resume(ar);
5795fd3ac3cSJanusz Dziedzic if (ret) {
5805fd3ac3cSJanusz Dziedzic ath10k_warn(ar, "failed to resume hif: %d\n", ret);
5815fd3ac3cSJanusz Dziedzic goto exit;
5825fd3ac3cSJanusz Dziedzic }
5835fd3ac3cSJanusz Dziedzic
5845fd3ac3cSJanusz Dziedzic ret = ath10k_wow_wakeup(ar);
5855fd3ac3cSJanusz Dziedzic if (ret)
5865fd3ac3cSJanusz Dziedzic ath10k_warn(ar, "failed to wakeup from wow: %d\n", ret);
5875fd3ac3cSJanusz Dziedzic
588ce834e28SWen Gong ret = ath10k_wow_nlo_cleanup(ar);
589ce834e28SWen Gong if (ret)
590ce834e28SWen Gong ath10k_warn(ar, "failed to cleanup nlo: %d\n", ret);
591ce834e28SWen Gong
5925fd3ac3cSJanusz Dziedzic exit:
5936f7429c2SMichal Kazior if (ret) {
5946f7429c2SMichal Kazior switch (ar->state) {
5956f7429c2SMichal Kazior case ATH10K_STATE_ON:
5966f7429c2SMichal Kazior ar->state = ATH10K_STATE_RESTARTING;
5976f7429c2SMichal Kazior ret = 1;
5986f7429c2SMichal Kazior break;
5996f7429c2SMichal Kazior case ATH10K_STATE_OFF:
6006f7429c2SMichal Kazior case ATH10K_STATE_RESTARTING:
6016f7429c2SMichal Kazior case ATH10K_STATE_RESTARTED:
6026f7429c2SMichal Kazior case ATH10K_STATE_UTF:
6036f7429c2SMichal Kazior case ATH10K_STATE_WEDGED:
6046f7429c2SMichal Kazior ath10k_warn(ar, "encountered unexpected device state %d on resume, cannot recover\n",
6056f7429c2SMichal Kazior ar->state);
6066f7429c2SMichal Kazior ret = -EIO;
6076f7429c2SMichal Kazior break;
6086f7429c2SMichal Kazior }
6096f7429c2SMichal Kazior }
6106f7429c2SMichal Kazior
6115fd3ac3cSJanusz Dziedzic mutex_unlock(&ar->conf_mutex);
6126f7429c2SMichal Kazior return ret;
6135fd3ac3cSJanusz Dziedzic }
6145fd3ac3cSJanusz Dziedzic
ath10k_wow_init(struct ath10k * ar)6155fd3ac3cSJanusz Dziedzic int ath10k_wow_init(struct ath10k *ar)
6165fd3ac3cSJanusz Dziedzic {
617c4cdf753SKalle Valo if (!test_bit(ATH10K_FW_FEATURE_WOWLAN_SUPPORT,
618c4cdf753SKalle Valo ar->running_fw->fw_file.fw_features))
6195fd3ac3cSJanusz Dziedzic return 0;
6205fd3ac3cSJanusz Dziedzic
6215fd3ac3cSJanusz Dziedzic if (WARN_ON(!test_bit(WMI_SERVICE_WOW, ar->wmi.svc_map)))
6225fd3ac3cSJanusz Dziedzic return -EINVAL;
6235fd3ac3cSJanusz Dziedzic
62425c86619SJanusz Dziedzic ar->wow.wowlan_support = ath10k_wowlan_support;
625fa3440faSWen Gong
626fa3440faSWen Gong if (ar->wmi.rx_decap_mode == ATH10K_HW_TXRX_NATIVE_WIFI) {
627fa3440faSWen Gong ar->wow.wowlan_support.pattern_max_len -= WOW_MAX_REDUCE;
628fa3440faSWen Gong ar->wow.wowlan_support.max_pkt_offset -= WOW_MAX_REDUCE;
629fa3440faSWen Gong }
630fa3440faSWen Gong
631ce834e28SWen Gong if (test_bit(WMI_SERVICE_NLO, ar->wmi.svc_map)) {
632ce834e28SWen Gong ar->wow.wowlan_support.flags |= WIPHY_WOWLAN_NET_DETECT;
633ce834e28SWen Gong ar->wow.wowlan_support.max_nd_match_sets = WMI_PNO_MAX_SUPP_NETWORKS;
634ce834e28SWen Gong }
635ce834e28SWen Gong
63625c86619SJanusz Dziedzic ar->wow.wowlan_support.n_patterns = ar->wow.max_num_patterns;
63725c86619SJanusz Dziedzic ar->hw->wiphy->wowlan = &ar->wow.wowlan_support;
6385fd3ac3cSJanusz Dziedzic
639393b706cSRyan Hsu device_set_wakeup_capable(ar->dev, true);
640393b706cSRyan Hsu
6415fd3ac3cSJanusz Dziedzic return 0;
6425fd3ac3cSJanusz Dziedzic }
643