xref: /linux/drivers/net/wireless/ath/ath11k/wow.c (revision fec4b898f369a9b9d516f7bfc459eb4a8c5ceb2c)
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