Commit | Line | Data |
---|---|---|
e60001e7 SM |
1 | /* |
2 | * Copyright (c) 2013 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 "ath9k.h" | |
18 | ||
e68e9c10 | 19 | static const struct wiphy_wowlan_support ath9k_wowlan_support_legacy = { |
babaa80a SM |
20 | .flags = WIPHY_WOWLAN_MAGIC_PKT | WIPHY_WOWLAN_DISCONNECT, |
21 | .n_patterns = MAX_NUM_USER_PATTERN, | |
22 | .pattern_min_len = 1, | |
23 | .pattern_max_len = MAX_PATTERN_SIZE, | |
24 | }; | |
25 | ||
e68e9c10 SM |
26 | static const struct wiphy_wowlan_support ath9k_wowlan_support = { |
27 | .flags = WIPHY_WOWLAN_MAGIC_PKT | WIPHY_WOWLAN_DISCONNECT, | |
28 | .n_patterns = MAX_NUM_PATTERN - 2, | |
29 | .pattern_min_len = 1, | |
30 | .pattern_max_len = MAX_PATTERN_SIZE, | |
31 | }; | |
32 | ||
249943a2 SM |
33 | static u8 ath9k_wow_map_triggers(struct ath_softc *sc, |
34 | struct cfg80211_wowlan *wowlan) | |
e60001e7 | 35 | { |
249943a2 SM |
36 | u8 wow_triggers = 0; |
37 | ||
e60001e7 | 38 | if (wowlan->disconnect) |
249943a2 SM |
39 | wow_triggers |= AH_WOW_LINK_CHANGE | |
40 | AH_WOW_BEACON_MISS; | |
e60001e7 | 41 | if (wowlan->magic_pkt) |
249943a2 | 42 | wow_triggers |= AH_WOW_MAGIC_PATTERN_EN; |
e60001e7 SM |
43 | |
44 | if (wowlan->n_patterns) | |
249943a2 | 45 | wow_triggers |= AH_WOW_USER_PATTERN_EN; |
e60001e7 | 46 | |
249943a2 | 47 | return wow_triggers; |
e60001e7 SM |
48 | } |
49 | ||
6af75e4d | 50 | static int ath9k_wow_add_disassoc_deauth_pattern(struct ath_softc *sc) |
e60001e7 SM |
51 | { |
52 | struct ath_hw *ah = sc->sc_ah; | |
53 | struct ath_common *common = ath9k_hw_common(ah); | |
54 | int pattern_count = 0; | |
6af75e4d | 55 | int ret, i, byte_cnt = 0; |
e60001e7 SM |
56 | u8 dis_deauth_pattern[MAX_PATTERN_SIZE]; |
57 | u8 dis_deauth_mask[MAX_PATTERN_SIZE]; | |
58 | ||
59 | memset(dis_deauth_pattern, 0, MAX_PATTERN_SIZE); | |
60 | memset(dis_deauth_mask, 0, MAX_PATTERN_SIZE); | |
61 | ||
62 | /* | |
63 | * Create Dissassociate / Deauthenticate packet filter | |
64 | * | |
65 | * 2 bytes 2 byte 6 bytes 6 bytes 6 bytes | |
66 | * +--------------+----------+---------+--------+--------+---- | |
67 | * + Frame Control+ Duration + DA + SA + BSSID + | |
68 | * +--------------+----------+---------+--------+--------+---- | |
69 | * | |
70 | * The above is the management frame format for disassociate/ | |
71 | * deauthenticate pattern, from this we need to match the first byte | |
72 | * of 'Frame Control' and DA, SA, and BSSID fields | |
73 | * (skipping 2nd byte of FC and Duration feild. | |
74 | * | |
75 | * Disassociate pattern | |
76 | * -------------------- | |
77 | * Frame control = 00 00 1010 | |
78 | * DA, SA, BSSID = x:x:x:x:x:x | |
79 | * Pattern will be A0000000 | x:x:x:x:x:x | x:x:x:x:x:x | |
80 | * | x:x:x:x:x:x -- 22 bytes | |
81 | * | |
82 | * Deauthenticate pattern | |
83 | * ---------------------- | |
84 | * Frame control = 00 00 1100 | |
85 | * DA, SA, BSSID = x:x:x:x:x:x | |
86 | * Pattern will be C0000000 | x:x:x:x:x:x | x:x:x:x:x:x | |
87 | * | x:x:x:x:x:x -- 22 bytes | |
88 | */ | |
89 | ||
e60001e7 | 90 | /* Fill out the mask with all FF's */ |
e60001e7 SM |
91 | for (i = 0; i < MAX_PATTERN_MASK_SIZE; i++) |
92 | dis_deauth_mask[i] = 0xff; | |
93 | ||
94 | /* copy the first byte of frame control field */ | |
95 | dis_deauth_pattern[byte_cnt] = 0xa0; | |
96 | byte_cnt++; | |
97 | ||
98 | /* skip 2nd byte of frame control and Duration field */ | |
99 | byte_cnt += 3; | |
100 | ||
101 | /* | |
102 | * need not match the destination mac address, it can be a broadcast | |
103 | * mac address or an unicast to this station | |
104 | */ | |
105 | byte_cnt += 6; | |
106 | ||
107 | /* copy the source mac address */ | |
108 | memcpy((dis_deauth_pattern + byte_cnt), common->curbssid, ETH_ALEN); | |
109 | ||
110 | byte_cnt += 6; | |
111 | ||
112 | /* copy the bssid, its same as the source mac address */ | |
e60001e7 SM |
113 | memcpy((dis_deauth_pattern + byte_cnt), common->curbssid, ETH_ALEN); |
114 | ||
115 | /* Create Disassociate pattern mask */ | |
e60001e7 SM |
116 | dis_deauth_mask[0] = 0xfe; |
117 | dis_deauth_mask[1] = 0x03; | |
118 | dis_deauth_mask[2] = 0xc0; | |
119 | ||
6af75e4d SM |
120 | ret = ath9k_hw_wow_apply_pattern(ah, dis_deauth_pattern, dis_deauth_mask, |
121 | pattern_count, byte_cnt); | |
122 | if (ret) | |
123 | goto exit; | |
e60001e7 SM |
124 | |
125 | pattern_count++; | |
126 | /* | |
127 | * for de-authenticate pattern, only the first byte of the frame | |
128 | * control field gets changed from 0xA0 to 0xC0 | |
129 | */ | |
130 | dis_deauth_pattern[0] = 0xC0; | |
131 | ||
6af75e4d SM |
132 | ret = ath9k_hw_wow_apply_pattern(ah, dis_deauth_pattern, dis_deauth_mask, |
133 | pattern_count, byte_cnt); | |
134 | exit: | |
135 | return ret; | |
e60001e7 SM |
136 | } |
137 | ||
6af75e4d SM |
138 | static int ath9k_wow_add_pattern(struct ath_softc *sc, |
139 | struct cfg80211_wowlan *wowlan) | |
e60001e7 SM |
140 | { |
141 | struct ath_hw *ah = sc->sc_ah; | |
e60001e7 | 142 | struct cfg80211_pkt_pattern *patterns = wowlan->patterns; |
34d102c9 SM |
143 | u8 wow_pattern[MAX_PATTERN_SIZE]; |
144 | u8 wow_mask[MAX_PATTERN_SIZE]; | |
6af75e4d | 145 | int mask_len, ret = 0; |
e60001e7 SM |
146 | s8 i = 0; |
147 | ||
e60001e7 | 148 | for (i = 0; i < wowlan->n_patterns; i++) { |
34d102c9 SM |
149 | mask_len = DIV_ROUND_UP(patterns[i].pattern_len, 8); |
150 | memset(wow_pattern, 0, MAX_PATTERN_SIZE); | |
151 | memset(wow_mask, 0, MAX_PATTERN_SIZE); | |
152 | memcpy(wow_pattern, patterns[i].pattern, patterns[i].pattern_len); | |
153 | memcpy(wow_mask, patterns[i].mask, mask_len); | |
154 | ||
6af75e4d SM |
155 | ret = ath9k_hw_wow_apply_pattern(ah, |
156 | wow_pattern, | |
157 | wow_mask, | |
158 | i + 2, | |
159 | patterns[i].pattern_len); | |
160 | if (ret) | |
161 | break; | |
e60001e7 | 162 | } |
6af75e4d SM |
163 | |
164 | return ret; | |
e60001e7 SM |
165 | } |
166 | ||
167 | int ath9k_suspend(struct ieee80211_hw *hw, | |
168 | struct cfg80211_wowlan *wowlan) | |
169 | { | |
170 | struct ath_softc *sc = hw->priv; | |
171 | struct ath_hw *ah = sc->sc_ah; | |
172 | struct ath_common *common = ath9k_hw_common(ah); | |
249943a2 | 173 | u8 triggers; |
e60001e7 SM |
174 | int ret = 0; |
175 | ||
ea22df29 SM |
176 | ath9k_deinit_channel_context(sc); |
177 | ||
e60001e7 SM |
178 | mutex_lock(&sc->mutex); |
179 | ||
eefa01dd | 180 | if (test_bit(ATH_OP_INVALID, &common->op_flags)) { |
13084c2d SM |
181 | ath_err(common, "Device not present\n"); |
182 | ret = -ENODEV; | |
e60001e7 SM |
183 | goto fail_wow; |
184 | } | |
185 | ||
186 | if (WARN_ON(!wowlan)) { | |
13084c2d | 187 | ath_err(common, "None of the WoW triggers enabled\n"); |
e60001e7 SM |
188 | ret = -EINVAL; |
189 | goto fail_wow; | |
190 | } | |
191 | ||
dc4b277d SM |
192 | if (sc->cur_chan->nvifs > 1) { |
193 | ath_dbg(common, WOW, "WoW for multivif is not yet supported\n"); | |
e60001e7 SM |
194 | ret = 1; |
195 | goto fail_wow; | |
196 | } | |
197 | ||
1331f5a7 SM |
198 | if (ath9k_is_chanctx_enabled()) { |
199 | if (test_bit(ATH_OP_MULTI_CHANNEL, &common->op_flags)) { | |
200 | ath_dbg(common, WOW, | |
201 | "Multi-channel WOW is not supported\n"); | |
202 | ret = 1; | |
203 | goto fail_wow; | |
204 | } | |
205 | } | |
206 | ||
dc4b277d SM |
207 | if (!test_bit(ATH_OP_PRIM_STA_VIF, &common->op_flags)) { |
208 | ath_dbg(common, WOW, "None of the STA vifs are associated\n"); | |
e60001e7 SM |
209 | ret = 1; |
210 | goto fail_wow; | |
211 | } | |
212 | ||
249943a2 SM |
213 | triggers = ath9k_wow_map_triggers(sc, wowlan); |
214 | if (!triggers) { | |
215 | ath_dbg(common, WOW, "No valid WoW triggers\n"); | |
216 | ret = 1; | |
217 | goto fail_wow; | |
218 | } | |
219 | ||
dc4b277d SM |
220 | ath_cancel_work(sc); |
221 | ath_stop_ani(sc); | |
222 | ||
e60001e7 SM |
223 | ath9k_ps_wakeup(sc); |
224 | ||
225 | ath9k_stop_btcoex(sc); | |
226 | ||
227 | /* | |
228 | * Enable wake up on recieving disassoc/deauth | |
229 | * frame by default. | |
230 | */ | |
6af75e4d SM |
231 | ret = ath9k_wow_add_disassoc_deauth_pattern(sc); |
232 | if (ret) { | |
233 | ath_err(common, | |
234 | "Unable to add disassoc/deauth pattern: %d\n", ret); | |
235 | goto fail_wow; | |
236 | } | |
e60001e7 | 237 | |
6af75e4d SM |
238 | if (triggers & AH_WOW_USER_PATTERN_EN) { |
239 | ret = ath9k_wow_add_pattern(sc, wowlan); | |
240 | if (ret) { | |
241 | ath_err(common, | |
242 | "Unable to add user pattern: %d\n", ret); | |
243 | goto fail_wow; | |
244 | } | |
245 | } | |
e60001e7 SM |
246 | |
247 | spin_lock_bh(&sc->sc_pcu_lock); | |
248 | /* | |
249 | * To avoid false wake, we enable beacon miss interrupt only | |
250 | * when we go to sleep. We save the current interrupt mask | |
251 | * so we can restore it after the system wakes up | |
252 | */ | |
253 | sc->wow_intr_before_sleep = ah->imask; | |
254 | ah->imask &= ~ATH9K_INT_GLOBAL; | |
255 | ath9k_hw_disable_interrupts(ah); | |
256 | ah->imask = ATH9K_INT_BMISS | ATH9K_INT_GLOBAL; | |
257 | ath9k_hw_set_interrupts(ah); | |
258 | ath9k_hw_enable_interrupts(ah); | |
259 | ||
260 | spin_unlock_bh(&sc->sc_pcu_lock); | |
261 | ||
262 | /* | |
263 | * we can now sync irq and kill any running tasklets, since we already | |
264 | * disabled interrupts and not holding a spin lock | |
265 | */ | |
266 | synchronize_irq(sc->irq); | |
267 | tasklet_kill(&sc->intr_tq); | |
268 | ||
249943a2 | 269 | ath9k_hw_wow_enable(ah, triggers); |
e60001e7 SM |
270 | |
271 | ath9k_ps_restore(sc); | |
249943a2 | 272 | ath_dbg(common, WOW, "Suspend with WoW triggers: 0x%x\n", triggers); |
e60001e7 | 273 | |
249943a2 | 274 | set_bit(ATH_OP_WOW_ENABLED, &common->op_flags); |
e60001e7 SM |
275 | fail_wow: |
276 | mutex_unlock(&sc->mutex); | |
277 | return ret; | |
278 | } | |
279 | ||
280 | int ath9k_resume(struct ieee80211_hw *hw) | |
281 | { | |
282 | struct ath_softc *sc = hw->priv; | |
283 | struct ath_hw *ah = sc->sc_ah; | |
284 | struct ath_common *common = ath9k_hw_common(ah); | |
e094c337 | 285 | u8 status; |
e60001e7 SM |
286 | |
287 | mutex_lock(&sc->mutex); | |
288 | ||
289 | ath9k_ps_wakeup(sc); | |
290 | ||
291 | spin_lock_bh(&sc->sc_pcu_lock); | |
292 | ||
293 | ath9k_hw_disable_interrupts(ah); | |
294 | ah->imask = sc->wow_intr_before_sleep; | |
295 | ath9k_hw_set_interrupts(ah); | |
296 | ath9k_hw_enable_interrupts(ah); | |
297 | ||
298 | spin_unlock_bh(&sc->sc_pcu_lock); | |
299 | ||
e094c337 SM |
300 | status = ath9k_hw_wow_wakeup(ah); |
301 | ath_dbg(common, WOW, "Resume with WoW status: 0x%x\n", status); | |
e60001e7 SM |
302 | |
303 | ath_restart_work(sc); | |
304 | ath9k_start_btcoex(sc); | |
305 | ||
249943a2 SM |
306 | clear_bit(ATH_OP_WOW_ENABLED, &common->op_flags); |
307 | ||
e60001e7 SM |
308 | ath9k_ps_restore(sc); |
309 | mutex_unlock(&sc->mutex); | |
310 | ||
311 | return 0; | |
312 | } | |
313 | ||
314 | void ath9k_set_wakeup(struct ieee80211_hw *hw, bool enabled) | |
315 | { | |
316 | struct ath_softc *sc = hw->priv; | |
661d2581 | 317 | struct ath_common *common = ath9k_hw_common(sc->sc_ah); |
e60001e7 SM |
318 | |
319 | mutex_lock(&sc->mutex); | |
e60001e7 SM |
320 | device_set_wakeup_enable(sc->dev, enabled); |
321 | mutex_unlock(&sc->mutex); | |
661d2581 SM |
322 | |
323 | ath_dbg(common, WOW, "WoW wakeup source is %s\n", | |
324 | (enabled) ? "enabled" : "disabled"); | |
e60001e7 | 325 | } |
babaa80a SM |
326 | |
327 | void ath9k_init_wow(struct ieee80211_hw *hw) | |
328 | { | |
329 | struct ath_softc *sc = hw->priv; | |
e68e9c10 | 330 | struct ath_hw *ah = sc->sc_ah; |
babaa80a | 331 | |
8b861715 | 332 | if ((sc->driver_data & ATH9K_PCI_WOW) || sc->force_wow) { |
e68e9c10 SM |
333 | if (AR_SREV_9462_20_OR_LATER(ah) || AR_SREV_9565_11_OR_LATER(ah)) |
334 | hw->wiphy->wowlan = &ath9k_wowlan_support; | |
335 | else | |
336 | hw->wiphy->wowlan = &ath9k_wowlan_support_legacy; | |
337 | ||
661d2581 SM |
338 | device_init_wakeup(sc->dev, 1); |
339 | } | |
340 | } | |
341 | ||
342 | void ath9k_deinit_wow(struct ieee80211_hw *hw) | |
343 | { | |
344 | struct ath_softc *sc = hw->priv; | |
345 | ||
8b861715 | 346 | if ((sc->driver_data & ATH9K_PCI_WOW) || sc->force_wow) |
661d2581 | 347 | device_init_wakeup(sc->dev, 0); |
babaa80a | 348 | } |