xref: /openbmc/linux/drivers/net/wireless/ath/ath9k/wow.c (revision f1288bdb)
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 
19 static const struct wiphy_wowlan_support ath9k_wowlan_support_legacy = {
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 
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 
33 static u8 ath9k_wow_map_triggers(struct ath_softc *sc,
34 				 struct cfg80211_wowlan *wowlan)
35 {
36 	u8 wow_triggers = 0;
37 
38 	if (wowlan->disconnect)
39 		wow_triggers |= AH_WOW_LINK_CHANGE |
40 				AH_WOW_BEACON_MISS;
41 	if (wowlan->magic_pkt)
42 		wow_triggers |= AH_WOW_MAGIC_PATTERN_EN;
43 
44 	if (wowlan->n_patterns)
45 		wow_triggers |= AH_WOW_USER_PATTERN_EN;
46 
47 	return wow_triggers;
48 }
49 
50 static int ath9k_wow_add_disassoc_deauth_pattern(struct ath_softc *sc)
51 {
52 	struct ath_hw *ah = sc->sc_ah;
53 	struct ath_common *common = ath9k_hw_common(ah);
54 	int pattern_count = 0;
55 	int ret, i, byte_cnt = 0;
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 
90 	/* Fill out the mask with all FF's */
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 */
113 	memcpy((dis_deauth_pattern + byte_cnt), common->curbssid, ETH_ALEN);
114 
115 	/* Create Disassociate pattern mask */
116 	dis_deauth_mask[0] = 0xfe;
117 	dis_deauth_mask[1] = 0x03;
118 	dis_deauth_mask[2] = 0xc0;
119 
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;
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 	ret = ath9k_hw_wow_apply_pattern(ah, dis_deauth_pattern, dis_deauth_mask,
133 					 pattern_count, byte_cnt);
134 exit:
135 	return ret;
136 }
137 
138 static int ath9k_wow_add_pattern(struct ath_softc *sc,
139 				 struct cfg80211_wowlan *wowlan)
140 {
141 	struct ath_hw *ah = sc->sc_ah;
142 	struct cfg80211_pkt_pattern *patterns = wowlan->patterns;
143 	u8 wow_pattern[MAX_PATTERN_SIZE];
144 	u8 wow_mask[MAX_PATTERN_SIZE];
145 	int mask_len, ret = 0;
146 	s8 i = 0;
147 
148 	for (i = 0; i < wowlan->n_patterns; i++) {
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 
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;
162 	}
163 
164 	return ret;
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);
173 	u8 triggers;
174 	int ret = 0;
175 
176 	ath9k_deinit_channel_context(sc);
177 
178 	mutex_lock(&sc->mutex);
179 
180 	if (test_bit(ATH_OP_INVALID, &common->op_flags)) {
181 		ath_err(common, "Device not present\n");
182 		ret = -ENODEV;
183 		goto fail_wow;
184 	}
185 
186 	if (WARN_ON(!wowlan)) {
187 		ath_err(common, "None of the WoW triggers enabled\n");
188 		ret = -EINVAL;
189 		goto fail_wow;
190 	}
191 
192 	if (sc->cur_chan->nvifs > 1) {
193 		ath_dbg(common, WOW, "WoW for multivif is not yet supported\n");
194 		ret = 1;
195 		goto fail_wow;
196 	}
197 
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 
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");
209 		ret = 1;
210 		goto fail_wow;
211 	}
212 
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 
220 	ath_cancel_work(sc);
221 	ath_stop_ani(sc);
222 
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 	 */
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 	}
237 
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 	}
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 
269 	ath9k_hw_wow_enable(ah, triggers);
270 
271 	ath9k_ps_restore(sc);
272 	ath_dbg(common, WOW, "Suspend with WoW triggers: 0x%x\n", triggers);
273 
274 	set_bit(ATH_OP_WOW_ENABLED, &common->op_flags);
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);
285 	u8 status;
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 
300 	status = ath9k_hw_wow_wakeup(ah);
301 	ath_dbg(common, WOW, "Resume with WoW status: 0x%x\n", status);
302 
303 	ath_restart_work(sc);
304 	ath9k_start_btcoex(sc);
305 
306 	clear_bit(ATH_OP_WOW_ENABLED, &common->op_flags);
307 
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;
317 	struct ath_common *common = ath9k_hw_common(sc->sc_ah);
318 
319 	mutex_lock(&sc->mutex);
320 	device_set_wakeup_enable(sc->dev, enabled);
321 	mutex_unlock(&sc->mutex);
322 
323 	ath_dbg(common, WOW, "WoW wakeup source is %s\n",
324 		(enabled) ? "enabled" : "disabled");
325 }
326 
327 void ath9k_init_wow(struct ieee80211_hw *hw)
328 {
329 	struct ath_softc *sc = hw->priv;
330 	struct ath_hw *ah = sc->sc_ah;
331 
332 	if ((sc->driver_data & ATH9K_PCI_WOW) || sc->force_wow) {
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 
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 
346 	if ((sc->driver_data & ATH9K_PCI_WOW) || sc->force_wow)
347 		device_init_wakeup(sc->dev, 0);
348 }
349