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