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