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 | ||
babaa80a SM |
19 | static const struct wiphy_wowlan_support ath9k_wowlan_support = { |
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 | ||
e60001e7 SM |
26 | static void ath9k_wow_map_triggers(struct ath_softc *sc, |
27 | struct cfg80211_wowlan *wowlan, | |
28 | u32 *wow_triggers) | |
29 | { | |
30 | if (wowlan->disconnect) | |
31 | *wow_triggers |= AH_WOW_LINK_CHANGE | | |
32 | AH_WOW_BEACON_MISS; | |
33 | if (wowlan->magic_pkt) | |
34 | *wow_triggers |= AH_WOW_MAGIC_PATTERN_EN; | |
35 | ||
36 | if (wowlan->n_patterns) | |
37 | *wow_triggers |= AH_WOW_USER_PATTERN_EN; | |
38 | ||
39 | sc->wow_enabled = *wow_triggers; | |
40 | ||
41 | } | |
42 | ||
43 | static void ath9k_wow_add_disassoc_deauth_pattern(struct ath_softc *sc) | |
44 | { | |
45 | struct ath_hw *ah = sc->sc_ah; | |
46 | struct ath_common *common = ath9k_hw_common(ah); | |
47 | int pattern_count = 0; | |
48 | int i, byte_cnt; | |
49 | u8 dis_deauth_pattern[MAX_PATTERN_SIZE]; | |
50 | u8 dis_deauth_mask[MAX_PATTERN_SIZE]; | |
51 | ||
52 | memset(dis_deauth_pattern, 0, MAX_PATTERN_SIZE); | |
53 | memset(dis_deauth_mask, 0, MAX_PATTERN_SIZE); | |
54 | ||
55 | /* | |
56 | * Create Dissassociate / Deauthenticate packet filter | |
57 | * | |
58 | * 2 bytes 2 byte 6 bytes 6 bytes 6 bytes | |
59 | * +--------------+----------+---------+--------+--------+---- | |
60 | * + Frame Control+ Duration + DA + SA + BSSID + | |
61 | * +--------------+----------+---------+--------+--------+---- | |
62 | * | |
63 | * The above is the management frame format for disassociate/ | |
64 | * deauthenticate pattern, from this we need to match the first byte | |
65 | * of 'Frame Control' and DA, SA, and BSSID fields | |
66 | * (skipping 2nd byte of FC and Duration feild. | |
67 | * | |
68 | * Disassociate pattern | |
69 | * -------------------- | |
70 | * Frame control = 00 00 1010 | |
71 | * DA, SA, BSSID = x:x:x:x:x:x | |
72 | * Pattern will be A0000000 | x:x:x:x:x:x | x:x:x:x:x:x | |
73 | * | x:x:x:x:x:x -- 22 bytes | |
74 | * | |
75 | * Deauthenticate pattern | |
76 | * ---------------------- | |
77 | * Frame control = 00 00 1100 | |
78 | * DA, SA, BSSID = x:x:x:x:x:x | |
79 | * Pattern will be C0000000 | x:x:x:x:x:x | x:x:x:x:x:x | |
80 | * | x:x:x:x:x:x -- 22 bytes | |
81 | */ | |
82 | ||
83 | /* Create Disassociate Pattern first */ | |
84 | ||
85 | byte_cnt = 0; | |
86 | ||
87 | /* Fill out the mask with all FF's */ | |
88 | ||
89 | for (i = 0; i < MAX_PATTERN_MASK_SIZE; i++) | |
90 | dis_deauth_mask[i] = 0xff; | |
91 | ||
92 | /* copy the first byte of frame control field */ | |
93 | dis_deauth_pattern[byte_cnt] = 0xa0; | |
94 | byte_cnt++; | |
95 | ||
96 | /* skip 2nd byte of frame control and Duration field */ | |
97 | byte_cnt += 3; | |
98 | ||
99 | /* | |
100 | * need not match the destination mac address, it can be a broadcast | |
101 | * mac address or an unicast to this station | |
102 | */ | |
103 | byte_cnt += 6; | |
104 | ||
105 | /* copy the source mac address */ | |
106 | memcpy((dis_deauth_pattern + byte_cnt), common->curbssid, ETH_ALEN); | |
107 | ||
108 | byte_cnt += 6; | |
109 | ||
110 | /* copy the bssid, its same as the source mac address */ | |
111 | ||
112 | memcpy((dis_deauth_pattern + byte_cnt), common->curbssid, ETH_ALEN); | |
113 | ||
114 | /* Create Disassociate pattern mask */ | |
115 | ||
116 | dis_deauth_mask[0] = 0xfe; | |
117 | dis_deauth_mask[1] = 0x03; | |
118 | dis_deauth_mask[2] = 0xc0; | |
119 | ||
120 | ath_dbg(common, WOW, "Adding disassoc/deauth patterns for WoW\n"); | |
121 | ||
122 | ath9k_hw_wow_apply_pattern(ah, dis_deauth_pattern, dis_deauth_mask, | |
123 | pattern_count, byte_cnt); | |
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 | ||
132 | ath9k_hw_wow_apply_pattern(ah, dis_deauth_pattern, dis_deauth_mask, | |
133 | pattern_count, byte_cnt); | |
134 | ||
135 | } | |
136 | ||
137 | static void ath9k_wow_add_pattern(struct ath_softc *sc, | |
138 | struct cfg80211_wowlan *wowlan) | |
139 | { | |
140 | struct ath_hw *ah = sc->sc_ah; | |
141 | struct ath9k_wow_pattern *wow_pattern = NULL; | |
142 | struct cfg80211_pkt_pattern *patterns = wowlan->patterns; | |
143 | int mask_len; | |
144 | s8 i = 0; | |
145 | ||
146 | if (!wowlan->n_patterns) | |
147 | return; | |
148 | ||
149 | /* | |
150 | * Add the new user configured patterns | |
151 | */ | |
152 | for (i = 0; i < wowlan->n_patterns; i++) { | |
153 | ||
154 | wow_pattern = kzalloc(sizeof(*wow_pattern), GFP_KERNEL); | |
155 | ||
156 | if (!wow_pattern) | |
157 | return; | |
158 | ||
159 | /* | |
160 | * TODO: convert the generic user space pattern to | |
161 | * appropriate chip specific/802.11 pattern. | |
162 | */ | |
163 | ||
164 | mask_len = DIV_ROUND_UP(wowlan->patterns[i].pattern_len, 8); | |
165 | memset(wow_pattern->pattern_bytes, 0, MAX_PATTERN_SIZE); | |
166 | memset(wow_pattern->mask_bytes, 0, MAX_PATTERN_SIZE); | |
167 | memcpy(wow_pattern->pattern_bytes, patterns[i].pattern, | |
168 | patterns[i].pattern_len); | |
169 | memcpy(wow_pattern->mask_bytes, patterns[i].mask, mask_len); | |
170 | wow_pattern->pattern_len = patterns[i].pattern_len; | |
171 | ||
172 | /* | |
173 | * just need to take care of deauth and disssoc pattern, | |
174 | * make sure we don't overwrite them. | |
175 | */ | |
176 | ||
177 | ath9k_hw_wow_apply_pattern(ah, wow_pattern->pattern_bytes, | |
178 | wow_pattern->mask_bytes, | |
179 | i + 2, | |
180 | wow_pattern->pattern_len); | |
181 | kfree(wow_pattern); | |
182 | ||
183 | } | |
184 | ||
185 | } | |
186 | ||
187 | int ath9k_suspend(struct ieee80211_hw *hw, | |
188 | struct cfg80211_wowlan *wowlan) | |
189 | { | |
190 | struct ath_softc *sc = hw->priv; | |
191 | struct ath_hw *ah = sc->sc_ah; | |
192 | struct ath_common *common = ath9k_hw_common(ah); | |
193 | u32 wow_triggers_enabled = 0; | |
194 | int ret = 0; | |
195 | ||
196 | mutex_lock(&sc->mutex); | |
197 | ||
198 | ath_cancel_work(sc); | |
199 | ath_stop_ani(sc); | |
200 | del_timer_sync(&sc->rx_poll_timer); | |
201 | ||
202 | if (test_bit(SC_OP_INVALID, &sc->sc_flags)) { | |
203 | ath_dbg(common, ANY, "Device not present\n"); | |
204 | ret = -EINVAL; | |
205 | goto fail_wow; | |
206 | } | |
207 | ||
208 | if (WARN_ON(!wowlan)) { | |
209 | ath_dbg(common, WOW, "None of the WoW triggers enabled\n"); | |
210 | ret = -EINVAL; | |
211 | goto fail_wow; | |
212 | } | |
213 | ||
214 | if (!device_can_wakeup(sc->dev)) { | |
215 | ath_dbg(common, WOW, "device_can_wakeup failed, WoW is not enabled\n"); | |
216 | ret = 1; | |
217 | goto fail_wow; | |
218 | } | |
219 | ||
220 | /* | |
221 | * none of the sta vifs are associated | |
222 | * and we are not currently handling multivif | |
223 | * cases, for instance we have to seperately | |
224 | * configure 'keep alive frame' for each | |
225 | * STA. | |
226 | */ | |
227 | ||
228 | if (!test_bit(SC_OP_PRIM_STA_VIF, &sc->sc_flags)) { | |
229 | ath_dbg(common, WOW, "None of the STA vifs are associated\n"); | |
230 | ret = 1; | |
231 | goto fail_wow; | |
232 | } | |
233 | ||
234 | if (sc->nvifs > 1) { | |
235 | ath_dbg(common, WOW, "WoW for multivif is not yet supported\n"); | |
236 | ret = 1; | |
237 | goto fail_wow; | |
238 | } | |
239 | ||
240 | ath9k_wow_map_triggers(sc, wowlan, &wow_triggers_enabled); | |
241 | ||
242 | ath_dbg(common, WOW, "WoW triggers enabled 0x%x\n", | |
243 | wow_triggers_enabled); | |
244 | ||
245 | ath9k_ps_wakeup(sc); | |
246 | ||
247 | ath9k_stop_btcoex(sc); | |
248 | ||
249 | /* | |
250 | * Enable wake up on recieving disassoc/deauth | |
251 | * frame by default. | |
252 | */ | |
253 | ath9k_wow_add_disassoc_deauth_pattern(sc); | |
254 | ||
255 | if (wow_triggers_enabled & AH_WOW_USER_PATTERN_EN) | |
256 | ath9k_wow_add_pattern(sc, wowlan); | |
257 | ||
258 | spin_lock_bh(&sc->sc_pcu_lock); | |
259 | /* | |
260 | * To avoid false wake, we enable beacon miss interrupt only | |
261 | * when we go to sleep. We save the current interrupt mask | |
262 | * so we can restore it after the system wakes up | |
263 | */ | |
264 | sc->wow_intr_before_sleep = ah->imask; | |
265 | ah->imask &= ~ATH9K_INT_GLOBAL; | |
266 | ath9k_hw_disable_interrupts(ah); | |
267 | ah->imask = ATH9K_INT_BMISS | ATH9K_INT_GLOBAL; | |
268 | ath9k_hw_set_interrupts(ah); | |
269 | ath9k_hw_enable_interrupts(ah); | |
270 | ||
271 | spin_unlock_bh(&sc->sc_pcu_lock); | |
272 | ||
273 | /* | |
274 | * we can now sync irq and kill any running tasklets, since we already | |
275 | * disabled interrupts and not holding a spin lock | |
276 | */ | |
277 | synchronize_irq(sc->irq); | |
278 | tasklet_kill(&sc->intr_tq); | |
279 | ||
280 | ath9k_hw_wow_enable(ah, wow_triggers_enabled); | |
281 | ||
282 | ath9k_ps_restore(sc); | |
283 | ath_dbg(common, ANY, "WoW enabled in ath9k\n"); | |
284 | atomic_inc(&sc->wow_sleep_proc_intr); | |
285 | ||
286 | fail_wow: | |
287 | mutex_unlock(&sc->mutex); | |
288 | return ret; | |
289 | } | |
290 | ||
291 | int ath9k_resume(struct ieee80211_hw *hw) | |
292 | { | |
293 | struct ath_softc *sc = hw->priv; | |
294 | struct ath_hw *ah = sc->sc_ah; | |
295 | struct ath_common *common = ath9k_hw_common(ah); | |
296 | u32 wow_status; | |
297 | ||
298 | mutex_lock(&sc->mutex); | |
299 | ||
300 | ath9k_ps_wakeup(sc); | |
301 | ||
302 | spin_lock_bh(&sc->sc_pcu_lock); | |
303 | ||
304 | ath9k_hw_disable_interrupts(ah); | |
305 | ah->imask = sc->wow_intr_before_sleep; | |
306 | ath9k_hw_set_interrupts(ah); | |
307 | ath9k_hw_enable_interrupts(ah); | |
308 | ||
309 | spin_unlock_bh(&sc->sc_pcu_lock); | |
310 | ||
311 | wow_status = ath9k_hw_wow_wakeup(ah); | |
312 | ||
313 | if (atomic_read(&sc->wow_got_bmiss_intr) == 0) { | |
314 | /* | |
315 | * some devices may not pick beacon miss | |
316 | * as the reason they woke up so we add | |
317 | * that here for that shortcoming. | |
318 | */ | |
319 | wow_status |= AH_WOW_BEACON_MISS; | |
320 | atomic_dec(&sc->wow_got_bmiss_intr); | |
321 | ath_dbg(common, ANY, "Beacon miss interrupt picked up during WoW sleep\n"); | |
322 | } | |
323 | ||
324 | atomic_dec(&sc->wow_sleep_proc_intr); | |
325 | ||
326 | if (wow_status) { | |
327 | ath_dbg(common, ANY, "Waking up due to WoW triggers %s with WoW status = %x\n", | |
328 | ath9k_hw_wow_event_to_string(wow_status), wow_status); | |
329 | } | |
330 | ||
331 | ath_restart_work(sc); | |
332 | ath9k_start_btcoex(sc); | |
333 | ||
334 | ath9k_ps_restore(sc); | |
335 | mutex_unlock(&sc->mutex); | |
336 | ||
337 | return 0; | |
338 | } | |
339 | ||
340 | void ath9k_set_wakeup(struct ieee80211_hw *hw, bool enabled) | |
341 | { | |
342 | struct ath_softc *sc = hw->priv; | |
343 | ||
344 | mutex_lock(&sc->mutex); | |
345 | device_init_wakeup(sc->dev, 1); | |
346 | device_set_wakeup_enable(sc->dev, enabled); | |
347 | mutex_unlock(&sc->mutex); | |
348 | } | |
babaa80a SM |
349 | |
350 | void ath9k_init_wow(struct ieee80211_hw *hw) | |
351 | { | |
352 | struct ath_softc *sc = hw->priv; | |
353 | ||
354 | if ((sc->sc_ah->caps.hw_caps & ATH9K_HW_WOW_DEVICE_CAPABLE) && | |
355 | (sc->driver_data & ATH9K_PCI_WOW) && | |
356 | device_can_wakeup(sc->dev)) | |
357 | hw->wiphy->wowlan = &ath9k_wowlan_support; | |
358 | ||
359 | atomic_set(&sc->wow_sleep_proc_intr, -1); | |
360 | atomic_set(&sc->wow_got_bmiss_intr, -1); | |
361 | } |