2 * Copyright (c) 2013 Qualcomm Atheros, Inc.
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.
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.
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
,
23 .pattern_max_len
= MAX_PATTERN_SIZE
,
26 static void ath9k_wow_map_triggers(struct ath_softc
*sc
,
27 struct cfg80211_wowlan
*wowlan
,
30 if (wowlan
->disconnect
)
31 *wow_triggers
|= AH_WOW_LINK_CHANGE
|
33 if (wowlan
->magic_pkt
)
34 *wow_triggers
|= AH_WOW_MAGIC_PATTERN_EN
;
36 if (wowlan
->n_patterns
)
37 *wow_triggers
|= AH_WOW_USER_PATTERN_EN
;
39 sc
->wow_enabled
= *wow_triggers
;
43 static void ath9k_wow_add_disassoc_deauth_pattern(struct ath_softc
*sc
)
45 struct ath_hw
*ah
= sc
->sc_ah
;
46 struct ath_common
*common
= ath9k_hw_common(ah
);
47 int pattern_count
= 0;
49 u8 dis_deauth_pattern
[MAX_PATTERN_SIZE
];
50 u8 dis_deauth_mask
[MAX_PATTERN_SIZE
];
52 memset(dis_deauth_pattern
, 0, MAX_PATTERN_SIZE
);
53 memset(dis_deauth_mask
, 0, MAX_PATTERN_SIZE
);
56 * Create Dissassociate / Deauthenticate packet filter
58 * 2 bytes 2 byte 6 bytes 6 bytes 6 bytes
59 * +--------------+----------+---------+--------+--------+----
60 * + Frame Control+ Duration + DA + SA + BSSID +
61 * +--------------+----------+---------+--------+--------+----
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.
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
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
83 /* Create Disassociate Pattern first */
87 /* Fill out the mask with all FF's */
89 for (i
= 0; i
< MAX_PATTERN_MASK_SIZE
; i
++)
90 dis_deauth_mask
[i
] = 0xff;
92 /* copy the first byte of frame control field */
93 dis_deauth_pattern
[byte_cnt
] = 0xa0;
96 /* skip 2nd byte of frame control and Duration field */
100 * need not match the destination mac address, it can be a broadcast
101 * mac address or an unicast to this station
105 /* copy the source mac address */
106 memcpy((dis_deauth_pattern
+ byte_cnt
), common
->curbssid
, ETH_ALEN
);
110 /* copy the bssid, its same as the source mac address */
112 memcpy((dis_deauth_pattern
+ byte_cnt
), common
->curbssid
, ETH_ALEN
);
114 /* Create Disassociate pattern mask */
116 dis_deauth_mask
[0] = 0xfe;
117 dis_deauth_mask
[1] = 0x03;
118 dis_deauth_mask
[2] = 0xc0;
120 ath_dbg(common
, WOW
, "Adding disassoc/deauth patterns for WoW\n");
122 ath9k_hw_wow_apply_pattern(ah
, dis_deauth_pattern
, dis_deauth_mask
,
123 pattern_count
, byte_cnt
);
127 * for de-authenticate pattern, only the first byte of the frame
128 * control field gets changed from 0xA0 to 0xC0
130 dis_deauth_pattern
[0] = 0xC0;
132 ath9k_hw_wow_apply_pattern(ah
, dis_deauth_pattern
, dis_deauth_mask
,
133 pattern_count
, byte_cnt
);
137 static void ath9k_wow_add_pattern(struct ath_softc
*sc
,
138 struct cfg80211_wowlan
*wowlan
)
140 struct ath_hw
*ah
= sc
->sc_ah
;
141 struct ath9k_wow_pattern
*wow_pattern
= NULL
;
142 struct cfg80211_pkt_pattern
*patterns
= wowlan
->patterns
;
146 if (!wowlan
->n_patterns
)
150 * Add the new user configured patterns
152 for (i
= 0; i
< wowlan
->n_patterns
; i
++) {
154 wow_pattern
= kzalloc(sizeof(*wow_pattern
), GFP_KERNEL
);
160 * TODO: convert the generic user space pattern to
161 * appropriate chip specific/802.11 pattern.
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
;
173 * just need to take care of deauth and disssoc pattern,
174 * make sure we don't overwrite them.
177 ath9k_hw_wow_apply_pattern(ah
, wow_pattern
->pattern_bytes
,
178 wow_pattern
->mask_bytes
,
180 wow_pattern
->pattern_len
);
187 int ath9k_suspend(struct ieee80211_hw
*hw
,
188 struct cfg80211_wowlan
*wowlan
)
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;
196 mutex_lock(&sc
->mutex
);
201 if (test_bit(SC_OP_INVALID
, &sc
->sc_flags
)) {
202 ath_dbg(common
, ANY
, "Device not present\n");
207 if (WARN_ON(!wowlan
)) {
208 ath_dbg(common
, WOW
, "None of the WoW triggers enabled\n");
213 if (!device_can_wakeup(sc
->dev
)) {
214 ath_dbg(common
, WOW
, "device_can_wakeup failed, WoW is not enabled\n");
220 * none of the sta vifs are associated
221 * and we are not currently handling multivif
222 * cases, for instance we have to seperately
223 * configure 'keep alive frame' for each
227 if (!test_bit(SC_OP_PRIM_STA_VIF
, &sc
->sc_flags
)) {
228 ath_dbg(common
, WOW
, "None of the STA vifs are associated\n");
234 ath_dbg(common
, WOW
, "WoW for multivif is not yet supported\n");
239 ath9k_wow_map_triggers(sc
, wowlan
, &wow_triggers_enabled
);
241 ath_dbg(common
, WOW
, "WoW triggers enabled 0x%x\n",
242 wow_triggers_enabled
);
246 ath9k_stop_btcoex(sc
);
249 * Enable wake up on recieving disassoc/deauth
252 ath9k_wow_add_disassoc_deauth_pattern(sc
);
254 if (wow_triggers_enabled
& AH_WOW_USER_PATTERN_EN
)
255 ath9k_wow_add_pattern(sc
, wowlan
);
257 spin_lock_bh(&sc
->sc_pcu_lock
);
259 * To avoid false wake, we enable beacon miss interrupt only
260 * when we go to sleep. We save the current interrupt mask
261 * so we can restore it after the system wakes up
263 sc
->wow_intr_before_sleep
= ah
->imask
;
264 ah
->imask
&= ~ATH9K_INT_GLOBAL
;
265 ath9k_hw_disable_interrupts(ah
);
266 ah
->imask
= ATH9K_INT_BMISS
| ATH9K_INT_GLOBAL
;
267 ath9k_hw_set_interrupts(ah
);
268 ath9k_hw_enable_interrupts(ah
);
270 spin_unlock_bh(&sc
->sc_pcu_lock
);
273 * we can now sync irq and kill any running tasklets, since we already
274 * disabled interrupts and not holding a spin lock
276 synchronize_irq(sc
->irq
);
277 tasklet_kill(&sc
->intr_tq
);
279 ath9k_hw_wow_enable(ah
, wow_triggers_enabled
);
281 ath9k_ps_restore(sc
);
282 ath_dbg(common
, ANY
, "WoW enabled in ath9k\n");
283 atomic_inc(&sc
->wow_sleep_proc_intr
);
286 mutex_unlock(&sc
->mutex
);
290 int ath9k_resume(struct ieee80211_hw
*hw
)
292 struct ath_softc
*sc
= hw
->priv
;
293 struct ath_hw
*ah
= sc
->sc_ah
;
294 struct ath_common
*common
= ath9k_hw_common(ah
);
297 mutex_lock(&sc
->mutex
);
301 spin_lock_bh(&sc
->sc_pcu_lock
);
303 ath9k_hw_disable_interrupts(ah
);
304 ah
->imask
= sc
->wow_intr_before_sleep
;
305 ath9k_hw_set_interrupts(ah
);
306 ath9k_hw_enable_interrupts(ah
);
308 spin_unlock_bh(&sc
->sc_pcu_lock
);
310 wow_status
= ath9k_hw_wow_wakeup(ah
);
312 if (atomic_read(&sc
->wow_got_bmiss_intr
) == 0) {
314 * some devices may not pick beacon miss
315 * as the reason they woke up so we add
316 * that here for that shortcoming.
318 wow_status
|= AH_WOW_BEACON_MISS
;
319 atomic_dec(&sc
->wow_got_bmiss_intr
);
320 ath_dbg(common
, ANY
, "Beacon miss interrupt picked up during WoW sleep\n");
323 atomic_dec(&sc
->wow_sleep_proc_intr
);
326 ath_dbg(common
, ANY
, "Waking up due to WoW triggers %s with WoW status = %x\n",
327 ath9k_hw_wow_event_to_string(wow_status
), wow_status
);
330 ath_restart_work(sc
);
331 ath9k_start_btcoex(sc
);
333 ath9k_ps_restore(sc
);
334 mutex_unlock(&sc
->mutex
);
339 void ath9k_set_wakeup(struct ieee80211_hw
*hw
, bool enabled
)
341 struct ath_softc
*sc
= hw
->priv
;
343 mutex_lock(&sc
->mutex
);
344 device_init_wakeup(sc
->dev
, 1);
345 device_set_wakeup_enable(sc
->dev
, enabled
);
346 mutex_unlock(&sc
->mutex
);
349 void ath9k_init_wow(struct ieee80211_hw
*hw
)
351 struct ath_softc
*sc
= hw
->priv
;
353 if ((sc
->sc_ah
->caps
.hw_caps
& ATH9K_HW_WOW_DEVICE_CAPABLE
) &&
354 (sc
->driver_data
& ATH9K_PCI_WOW
) &&
355 device_can_wakeup(sc
->dev
))
356 hw
->wiphy
->wowlan
= &ath9k_wowlan_support
;
358 atomic_set(&sc
->wow_sleep_proc_intr
, -1);
359 atomic_set(&sc
->wow_got_bmiss_intr
, -1);