diff options
Diffstat (limited to 'drivers/net/wireless/ath/ath10k/wow.c')
-rw-r--r-- | drivers/net/wireless/ath/ath10k/wow.c | 321 |
1 files changed, 321 insertions, 0 deletions
diff --git a/drivers/net/wireless/ath/ath10k/wow.c b/drivers/net/wireless/ath/ath10k/wow.c new file mode 100644 index 000000000000..a68d8fd853a3 --- /dev/null +++ b/drivers/net/wireless/ath/ath10k/wow.c | |||
@@ -0,0 +1,321 @@ | |||
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->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->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 | mutex_unlock(&ar->conf_mutex); | ||
305 | return ret ? 1 : 0; | ||
306 | } | ||
307 | |||
308 | int ath10k_wow_init(struct ath10k *ar) | ||
309 | { | ||
310 | if (!test_bit(ATH10K_FW_FEATURE_WOWLAN_SUPPORT, ar->fw_features)) | ||
311 | return 0; | ||
312 | |||
313 | if (WARN_ON(!test_bit(WMI_SERVICE_WOW, ar->wmi.svc_map))) | ||
314 | return -EINVAL; | ||
315 | |||
316 | ar->wow.wowlan_support = ath10k_wowlan_support; | ||
317 | ar->wow.wowlan_support.n_patterns = ar->wow.max_num_patterns; | ||
318 | ar->hw->wiphy->wowlan = &ar->wow.wowlan_support; | ||
319 | |||
320 | return 0; | ||
321 | } | ||