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