1 | // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause |
2 | /* Copyright(c) 2019-2022 Realtek Corporation |
3 | */ |
4 | #include "cam.h" |
5 | #include "core.h" |
6 | #include "debug.h" |
7 | #include "fw.h" |
8 | #include "mac.h" |
9 | #include "phy.h" |
10 | #include "ps.h" |
11 | #include "reg.h" |
12 | #include "util.h" |
13 | #include "wow.h" |
14 | |
15 | static void rtw89_wow_leave_deep_ps(struct rtw89_dev *rtwdev) |
16 | { |
17 | __rtw89_leave_ps_mode(rtwdev); |
18 | } |
19 | |
20 | static void rtw89_wow_enter_deep_ps(struct rtw89_dev *rtwdev) |
21 | { |
22 | struct ieee80211_vif *wow_vif = rtwdev->wow.wow_vif; |
23 | struct rtw89_vif *rtwvif = (struct rtw89_vif *)wow_vif->drv_priv; |
24 | |
25 | __rtw89_enter_ps_mode(rtwdev, rtwvif); |
26 | } |
27 | |
28 | static void rtw89_wow_enter_lps(struct rtw89_dev *rtwdev) |
29 | { |
30 | struct ieee80211_vif *wow_vif = rtwdev->wow.wow_vif; |
31 | struct rtw89_vif *rtwvif = (struct rtw89_vif *)wow_vif->drv_priv; |
32 | |
33 | rtw89_enter_lps(rtwdev, rtwvif, ps_mode: false); |
34 | } |
35 | |
36 | static void rtw89_wow_leave_lps(struct rtw89_dev *rtwdev) |
37 | { |
38 | rtw89_leave_lps(rtwdev); |
39 | } |
40 | |
41 | static int rtw89_wow_config_mac(struct rtw89_dev *rtwdev, bool enable_wow) |
42 | { |
43 | const struct rtw89_mac_gen_def *mac = rtwdev->chip->mac_def; |
44 | |
45 | return mac->wow_config_mac(rtwdev, enable_wow); |
46 | } |
47 | |
48 | static void rtw89_wow_set_rx_filter(struct rtw89_dev *rtwdev, bool enable) |
49 | { |
50 | const struct rtw89_mac_gen_def *mac = rtwdev->chip->mac_def; |
51 | enum rtw89_mac_fwd_target fwd_target = enable ? |
52 | RTW89_FWD_DONT_CARE : |
53 | RTW89_FWD_TO_HOST; |
54 | |
55 | mac->typ_fltr_opt(rtwdev, RTW89_MGNT, fwd_target, RTW89_MAC_0); |
56 | mac->typ_fltr_opt(rtwdev, RTW89_CTRL, fwd_target, RTW89_MAC_0); |
57 | mac->typ_fltr_opt(rtwdev, RTW89_DATA, fwd_target, RTW89_MAC_0); |
58 | } |
59 | |
60 | static void rtw89_wow_show_wakeup_reason(struct rtw89_dev *rtwdev) |
61 | { |
62 | u32 wow_reason_reg = rtwdev->chip->wow_reason_reg; |
63 | struct cfg80211_wowlan_nd_info nd_info; |
64 | struct cfg80211_wowlan_wakeup wakeup = { |
65 | .pattern_idx = -1, |
66 | }; |
67 | u8 reason; |
68 | |
69 | reason = rtw89_read8(rtwdev, addr: wow_reason_reg); |
70 | switch (reason) { |
71 | case RTW89_WOW_RSN_RX_DEAUTH: |
72 | wakeup.disconnect = true; |
73 | rtw89_debug(rtwdev, mask: RTW89_DBG_WOW, fmt: "WOW: Rx deauth\n" ); |
74 | break; |
75 | case RTW89_WOW_RSN_DISCONNECT: |
76 | wakeup.disconnect = true; |
77 | rtw89_debug(rtwdev, mask: RTW89_DBG_WOW, fmt: "WOW: AP is off\n" ); |
78 | break; |
79 | case RTW89_WOW_RSN_RX_MAGIC_PKT: |
80 | wakeup.magic_pkt = true; |
81 | rtw89_debug(rtwdev, mask: RTW89_DBG_WOW, fmt: "WOW: Rx magic packet\n" ); |
82 | break; |
83 | case RTW89_WOW_RSN_RX_GTK_REKEY: |
84 | wakeup.gtk_rekey_failure = true; |
85 | rtw89_debug(rtwdev, mask: RTW89_DBG_WOW, fmt: "WOW: Rx gtk rekey\n" ); |
86 | break; |
87 | case RTW89_WOW_RSN_RX_PATTERN_MATCH: |
88 | /* Current firmware and driver don't report pattern index |
89 | * Use pattern_idx to 0 defaultly. |
90 | */ |
91 | wakeup.pattern_idx = 0; |
92 | rtw89_debug(rtwdev, mask: RTW89_DBG_WOW, fmt: "WOW: Rx pattern match packet\n" ); |
93 | break; |
94 | case RTW89_WOW_RSN_RX_NLO: |
95 | /* Current firmware and driver don't report ssid index. |
96 | * Use 0 for n_matches based on its comment. |
97 | */ |
98 | nd_info.n_matches = 0; |
99 | wakeup.net_detect = &nd_info; |
100 | rtw89_debug(rtwdev, mask: RTW89_DBG_WOW, fmt: "Rx NLO\n" ); |
101 | break; |
102 | default: |
103 | rtw89_warn(rtwdev, "Unknown wakeup reason %x\n" , reason); |
104 | ieee80211_report_wowlan_wakeup(vif: rtwdev->wow.wow_vif, NULL, |
105 | GFP_KERNEL); |
106 | return; |
107 | } |
108 | |
109 | ieee80211_report_wowlan_wakeup(vif: rtwdev->wow.wow_vif, wakeup: &wakeup, |
110 | GFP_KERNEL); |
111 | } |
112 | |
113 | static void rtw89_wow_vif_iter(struct rtw89_dev *rtwdev, struct rtw89_vif *rtwvif) |
114 | { |
115 | struct rtw89_wow_param *rtw_wow = &rtwdev->wow; |
116 | struct ieee80211_vif *vif = rtwvif_to_vif(rtwvif); |
117 | |
118 | /* Current wowlan function support setting of only one STATION vif. |
119 | * So when one suitable vif is found, stop the iteration. |
120 | */ |
121 | if (rtw_wow->wow_vif || vif->type != NL80211_IFTYPE_STATION) |
122 | return; |
123 | |
124 | switch (rtwvif->net_type) { |
125 | case RTW89_NET_TYPE_INFRA: |
126 | rtw_wow->wow_vif = vif; |
127 | break; |
128 | case RTW89_NET_TYPE_NO_LINK: |
129 | default: |
130 | break; |
131 | } |
132 | } |
133 | |
134 | static u16 __rtw89_cal_crc16(u8 data, u16 crc) |
135 | { |
136 | u8 shift_in, data_bit; |
137 | u8 crc_bit4, crc_bit11, crc_bit15; |
138 | u16 crc_result; |
139 | int index; |
140 | |
141 | for (index = 0; index < 8; index++) { |
142 | crc_bit15 = crc & BIT(15) ? 1 : 0; |
143 | data_bit = data & BIT(index) ? 1 : 0; |
144 | shift_in = crc_bit15 ^ data_bit; |
145 | |
146 | crc_result = crc << 1; |
147 | |
148 | if (shift_in == 0) |
149 | crc_result &= ~BIT(0); |
150 | else |
151 | crc_result |= BIT(0); |
152 | |
153 | crc_bit11 = (crc & BIT(11) ? 1 : 0) ^ shift_in; |
154 | |
155 | if (crc_bit11 == 0) |
156 | crc_result &= ~BIT(12); |
157 | else |
158 | crc_result |= BIT(12); |
159 | |
160 | crc_bit4 = (crc & BIT(4) ? 1 : 0) ^ shift_in; |
161 | |
162 | if (crc_bit4 == 0) |
163 | crc_result &= ~BIT(5); |
164 | else |
165 | crc_result |= BIT(5); |
166 | |
167 | crc = crc_result; |
168 | } |
169 | return crc; |
170 | } |
171 | |
172 | static u16 rtw89_calc_crc(u8 *pdata, int length) |
173 | { |
174 | u16 crc = 0xffff; |
175 | int i; |
176 | |
177 | for (i = 0; i < length; i++) |
178 | crc = __rtw89_cal_crc16(data: pdata[i], crc); |
179 | |
180 | /* get 1' complement */ |
181 | return ~crc; |
182 | } |
183 | |
184 | static int rtw89_wow_pattern_get_type(struct rtw89_vif *rtwvif, |
185 | struct rtw89_wow_cam_info *rtw_pattern, |
186 | const u8 *pattern, u8 da_mask) |
187 | { |
188 | u8 da[ETH_ALEN]; |
189 | |
190 | ether_addr_copy_mask(dst: da, src: pattern, mask: da_mask); |
191 | |
192 | /* Each pattern is divided into different kinds by DA address |
193 | * a. DA is broadcast address: set bc = 0; |
194 | * b. DA is multicast address: set mc = 0 |
195 | * c. DA is unicast address same as dev's mac address: set uc = 0 |
196 | * d. DA is unmasked. Also called wildcard type: set uc = bc = mc = 0 |
197 | * e. Others is invalid type. |
198 | */ |
199 | |
200 | if (is_broadcast_ether_addr(addr: da)) |
201 | rtw_pattern->bc = true; |
202 | else if (is_multicast_ether_addr(addr: da)) |
203 | rtw_pattern->mc = true; |
204 | else if (ether_addr_equal(addr1: da, addr2: rtwvif->mac_addr) && |
205 | da_mask == GENMASK(5, 0)) |
206 | rtw_pattern->uc = true; |
207 | else if (!da_mask) /*da_mask == 0 mean wildcard*/ |
208 | return 0; |
209 | else |
210 | return -EPERM; |
211 | |
212 | return 0; |
213 | } |
214 | |
215 | static int rtw89_wow_pattern_generate(struct rtw89_dev *rtwdev, |
216 | struct rtw89_vif *rtwvif, |
217 | const struct cfg80211_pkt_pattern *pkt_pattern, |
218 | struct rtw89_wow_cam_info *rtw_pattern) |
219 | { |
220 | u8 mask_hw[RTW89_MAX_PATTERN_MASK_SIZE * 4] = {0}; |
221 | u8 content[RTW89_MAX_PATTERN_SIZE] = {0}; |
222 | const u8 *mask; |
223 | const u8 *pattern; |
224 | u8 mask_len; |
225 | u16 count; |
226 | u32 len; |
227 | int i, ret; |
228 | |
229 | pattern = pkt_pattern->pattern; |
230 | len = pkt_pattern->pattern_len; |
231 | mask = pkt_pattern->mask; |
232 | mask_len = DIV_ROUND_UP(len, 8); |
233 | memset(rtw_pattern, 0, sizeof(*rtw_pattern)); |
234 | |
235 | ret = rtw89_wow_pattern_get_type(rtwvif, rtw_pattern, pattern, |
236 | da_mask: mask[0] & GENMASK(5, 0)); |
237 | if (ret) |
238 | return ret; |
239 | |
240 | /* translate mask from os to mask for hw |
241 | * pattern from OS uses 'ethenet frame', like this: |
242 | * | 6 | 6 | 2 | 20 | Variable | 4 | |
243 | * |--------+--------+------+-----------+------------+-----| |
244 | * | 802.3 Mac Header | IP Header | TCP Packet | FCS | |
245 | * | DA | SA | Type | |
246 | * |
247 | * BUT, packet catched by our HW is in '802.11 frame', begin from LLC |
248 | * | 24 or 30 | 6 | 2 | 20 | Variable | 4 | |
249 | * |-------------------+--------+------+-----------+------------+-----| |
250 | * | 802.11 MAC Header | LLC | IP Header | TCP Packet | FCS | |
251 | * | Others | Tpye | |
252 | * |
253 | * Therefore, we need translate mask_from_OS to mask_to_hw. |
254 | * We should left-shift mask by 6 bits, then set the new bit[0~5] = 0, |
255 | * because new mask[0~5] means 'SA', but our HW packet begins from LLC, |
256 | * bit[0~5] corresponds to first 6 Bytes in LLC, they just don't match. |
257 | */ |
258 | |
259 | /* Shift 6 bits */ |
260 | for (i = 0; i < mask_len - 1; i++) { |
261 | mask_hw[i] = u8_get_bits(v: mask[i], GENMASK(7, 6)) | |
262 | u8_get_bits(v: mask[i + 1], GENMASK(5, 0)) << 2; |
263 | } |
264 | mask_hw[i] = u8_get_bits(v: mask[i], GENMASK(7, 6)); |
265 | |
266 | /* Set bit 0-5 to zero */ |
267 | mask_hw[0] &= ~GENMASK(5, 0); |
268 | |
269 | memcpy(rtw_pattern->mask, mask_hw, sizeof(rtw_pattern->mask)); |
270 | |
271 | /* To get the wake up pattern from the mask. |
272 | * We do not count first 12 bits which means |
273 | * DA[6] and SA[6] in the pattern to match HW design. |
274 | */ |
275 | count = 0; |
276 | for (i = 12; i < len; i++) { |
277 | if ((mask[i / 8] >> (i % 8)) & 0x01) { |
278 | content[count] = pattern[i]; |
279 | count++; |
280 | } |
281 | } |
282 | |
283 | rtw_pattern->crc = rtw89_calc_crc(pdata: content, length: count); |
284 | |
285 | return 0; |
286 | } |
287 | |
288 | static int rtw89_wow_parse_patterns(struct rtw89_dev *rtwdev, |
289 | struct rtw89_vif *rtwvif, |
290 | struct cfg80211_wowlan *wowlan) |
291 | { |
292 | struct rtw89_wow_param *rtw_wow = &rtwdev->wow; |
293 | struct rtw89_wow_cam_info *rtw_pattern = rtw_wow->patterns; |
294 | int i; |
295 | int ret; |
296 | |
297 | if (!wowlan->n_patterns || !wowlan->patterns) |
298 | return 0; |
299 | |
300 | for (i = 0; i < wowlan->n_patterns; i++) { |
301 | rtw_pattern = &rtw_wow->patterns[i]; |
302 | ret = rtw89_wow_pattern_generate(rtwdev, rtwvif, |
303 | pkt_pattern: &wowlan->patterns[i], |
304 | rtw_pattern); |
305 | if (ret) { |
306 | rtw89_err(rtwdev, "failed to generate pattern(%d)\n" , i); |
307 | rtw_wow->pattern_cnt = 0; |
308 | return ret; |
309 | } |
310 | |
311 | rtw_pattern->r_w = true; |
312 | rtw_pattern->idx = i; |
313 | rtw_pattern->negative_pattern_match = false; |
314 | rtw_pattern->skip_mac_hdr = true; |
315 | rtw_pattern->valid = true; |
316 | } |
317 | rtw_wow->pattern_cnt = wowlan->n_patterns; |
318 | |
319 | return 0; |
320 | } |
321 | |
322 | static void rtw89_wow_pattern_clear_cam(struct rtw89_dev *rtwdev) |
323 | { |
324 | struct rtw89_wow_param *rtw_wow = &rtwdev->wow; |
325 | struct rtw89_wow_cam_info *rtw_pattern = rtw_wow->patterns; |
326 | int i = 0; |
327 | |
328 | for (i = 0; i < rtw_wow->pattern_cnt; i++) { |
329 | rtw_pattern = &rtw_wow->patterns[i]; |
330 | rtw_pattern->valid = false; |
331 | rtw89_fw_wow_cam_update(rtwdev, cam_info: rtw_pattern); |
332 | } |
333 | } |
334 | |
335 | static void rtw89_wow_pattern_write(struct rtw89_dev *rtwdev) |
336 | { |
337 | struct rtw89_wow_param *rtw_wow = &rtwdev->wow; |
338 | struct rtw89_wow_cam_info *rtw_pattern = rtw_wow->patterns; |
339 | int i; |
340 | |
341 | for (i = 0; i < rtw_wow->pattern_cnt; i++) |
342 | rtw89_fw_wow_cam_update(rtwdev, cam_info: rtw_pattern + i); |
343 | } |
344 | |
345 | static void rtw89_wow_pattern_clear(struct rtw89_dev *rtwdev) |
346 | { |
347 | struct rtw89_wow_param *rtw_wow = &rtwdev->wow; |
348 | |
349 | rtw89_wow_pattern_clear_cam(rtwdev); |
350 | |
351 | rtw_wow->pattern_cnt = 0; |
352 | memset(rtw_wow->patterns, 0, sizeof(rtw_wow->patterns)); |
353 | } |
354 | |
355 | static void rtw89_wow_clear_wakeups(struct rtw89_dev *rtwdev) |
356 | { |
357 | struct rtw89_wow_param *rtw_wow = &rtwdev->wow; |
358 | |
359 | rtw_wow->wow_vif = NULL; |
360 | rtw89_core_release_all_bits_map(addr: rtw_wow->flags, nbits: RTW89_WOW_FLAG_NUM); |
361 | rtw_wow->pattern_cnt = 0; |
362 | } |
363 | |
364 | static int rtw89_wow_set_wakeups(struct rtw89_dev *rtwdev, |
365 | struct cfg80211_wowlan *wowlan) |
366 | { |
367 | struct rtw89_wow_param *rtw_wow = &rtwdev->wow; |
368 | struct rtw89_vif *rtwvif; |
369 | |
370 | if (wowlan->disconnect) |
371 | set_bit(nr: RTW89_WOW_FLAG_EN_DISCONNECT, addr: rtw_wow->flags); |
372 | if (wowlan->magic_pkt) |
373 | set_bit(nr: RTW89_WOW_FLAG_EN_MAGIC_PKT, addr: rtw_wow->flags); |
374 | |
375 | rtw89_for_each_rtwvif(rtwdev, rtwvif) |
376 | rtw89_wow_vif_iter(rtwdev, rtwvif); |
377 | |
378 | if (!rtw_wow->wow_vif) |
379 | return -EPERM; |
380 | |
381 | rtwvif = (struct rtw89_vif *)rtw_wow->wow_vif->drv_priv; |
382 | return rtw89_wow_parse_patterns(rtwdev, rtwvif, wowlan); |
383 | } |
384 | |
385 | static int rtw89_wow_cfg_wake(struct rtw89_dev *rtwdev, bool wow) |
386 | { |
387 | struct rtw89_wow_param *rtw_wow = &rtwdev->wow; |
388 | struct ieee80211_vif *wow_vif = rtw_wow->wow_vif; |
389 | struct rtw89_vif *rtwvif = (struct rtw89_vif *)wow_vif->drv_priv; |
390 | struct ieee80211_sta *wow_sta; |
391 | struct rtw89_sta *rtwsta = NULL; |
392 | int ret; |
393 | |
394 | wow_sta = ieee80211_find_sta(vif: wow_vif, addr: rtwvif->bssid); |
395 | if (wow_sta) |
396 | rtwsta = (struct rtw89_sta *)wow_sta->drv_priv; |
397 | |
398 | if (wow) { |
399 | if (rtw_wow->pattern_cnt) |
400 | rtwvif->wowlan_pattern = true; |
401 | if (test_bit(RTW89_WOW_FLAG_EN_MAGIC_PKT, rtw_wow->flags)) |
402 | rtwvif->wowlan_magic = true; |
403 | } else { |
404 | rtwvif->wowlan_pattern = false; |
405 | rtwvif->wowlan_magic = false; |
406 | } |
407 | |
408 | ret = rtw89_fw_h2c_wow_wakeup_ctrl(rtwdev, rtwvif, enable: wow); |
409 | if (ret) { |
410 | rtw89_err(rtwdev, "failed to fw wow wakeup ctrl\n" ); |
411 | return ret; |
412 | } |
413 | |
414 | if (wow) { |
415 | ret = rtw89_chip_h2c_dctl_sec_cam(rtwdev, rtwvif, rtwsta); |
416 | if (ret) { |
417 | rtw89_err(rtwdev, "failed to update dctl cam sec entry: %d\n" , |
418 | ret); |
419 | return ret; |
420 | } |
421 | } |
422 | |
423 | ret = rtw89_fw_h2c_cam(rtwdev, vif: rtwvif, rtwsta, NULL); |
424 | if (ret) { |
425 | rtw89_warn(rtwdev, "failed to send h2c cam\n" ); |
426 | return ret; |
427 | } |
428 | |
429 | ret = rtw89_fw_h2c_wow_global(rtwdev, rtwvif, enable: wow); |
430 | if (ret) { |
431 | rtw89_err(rtwdev, "failed to fw wow global\n" ); |
432 | return ret; |
433 | } |
434 | |
435 | return 0; |
436 | } |
437 | |
438 | static int rtw89_wow_check_fw_status(struct rtw89_dev *rtwdev, bool wow_enable) |
439 | { |
440 | const struct rtw89_mac_gen_def *mac = rtwdev->chip->mac_def; |
441 | u8 polling; |
442 | int ret; |
443 | |
444 | ret = read_poll_timeout_atomic(rtw89_read8_mask, polling, |
445 | wow_enable == !!polling, |
446 | 50, 50000, false, rtwdev, |
447 | mac->wow_ctrl.addr, mac->wow_ctrl.mask); |
448 | if (ret) |
449 | rtw89_err(rtwdev, "failed to check wow status %s\n" , |
450 | wow_enable ? "enabled" : "disabled" ); |
451 | return ret; |
452 | } |
453 | |
454 | static int rtw89_wow_swap_fw(struct rtw89_dev *rtwdev, bool wow) |
455 | { |
456 | enum rtw89_fw_type fw_type = wow ? RTW89_FW_WOWLAN : RTW89_FW_NORMAL; |
457 | struct rtw89_wow_param *rtw_wow = &rtwdev->wow; |
458 | struct ieee80211_vif *wow_vif = rtw_wow->wow_vif; |
459 | struct rtw89_vif *rtwvif = (struct rtw89_vif *)wow_vif->drv_priv; |
460 | const struct rtw89_chip_info *chip = rtwdev->chip; |
461 | bool include_bb = !!chip->bbmcu_nr; |
462 | struct ieee80211_sta *wow_sta; |
463 | struct rtw89_sta *rtwsta = NULL; |
464 | bool is_conn = true; |
465 | int ret; |
466 | |
467 | rtw89_hci_disable_intr(rtwdev); |
468 | |
469 | wow_sta = ieee80211_find_sta(vif: wow_vif, addr: rtwvif->bssid); |
470 | if (wow_sta) |
471 | rtwsta = (struct rtw89_sta *)wow_sta->drv_priv; |
472 | else |
473 | is_conn = false; |
474 | |
475 | ret = rtw89_fw_download(rtwdev, type: fw_type, include_bb); |
476 | if (ret) { |
477 | rtw89_warn(rtwdev, "download fw failed\n" ); |
478 | return ret; |
479 | } |
480 | |
481 | rtw89_phy_init_rf_reg(rtwdev, noio: true); |
482 | |
483 | ret = rtw89_fw_h2c_role_maintain(rtwdev, rtwvif, rtwsta, |
484 | upd_mode: RTW89_ROLE_FW_RESTORE); |
485 | if (ret) { |
486 | rtw89_warn(rtwdev, "failed to send h2c role maintain\n" ); |
487 | return ret; |
488 | } |
489 | |
490 | ret = rtw89_chip_h2c_assoc_cmac_tbl(rtwdev, vif: wow_vif, sta: wow_sta); |
491 | if (ret) { |
492 | rtw89_warn(rtwdev, "failed to send h2c assoc cmac tbl\n" ); |
493 | return ret; |
494 | } |
495 | |
496 | if (!is_conn) |
497 | rtw89_cam_reset_keys(rtwdev); |
498 | |
499 | ret = rtw89_fw_h2c_join_info(rtwdev, rtwvif, rtwsta, dis_conn: !is_conn); |
500 | if (ret) { |
501 | rtw89_warn(rtwdev, "failed to send h2c join info\n" ); |
502 | return ret; |
503 | } |
504 | |
505 | ret = rtw89_fw_h2c_cam(rtwdev, vif: rtwvif, rtwsta, NULL); |
506 | if (ret) { |
507 | rtw89_warn(rtwdev, "failed to send h2c cam\n" ); |
508 | return ret; |
509 | } |
510 | |
511 | if (is_conn) { |
512 | ret = rtw89_fw_h2c_general_pkt(rtwdev, rtwvif, macid: rtwsta->mac_id); |
513 | if (ret) { |
514 | rtw89_warn(rtwdev, "failed to send h2c general packet\n" ); |
515 | return ret; |
516 | } |
517 | rtw89_phy_ra_assoc(rtwdev, sta: wow_sta); |
518 | rtw89_phy_set_bss_color(rtwdev, vif: wow_vif); |
519 | rtw89_chip_cfg_txpwr_ul_tb_offset(rtwdev, vif: wow_vif); |
520 | } |
521 | |
522 | rtw89_mac_hw_mgnt_sec(rtwdev, wow); |
523 | rtw89_hci_enable_intr(rtwdev); |
524 | |
525 | return 0; |
526 | } |
527 | |
528 | static int rtw89_wow_enable_trx_pre(struct rtw89_dev *rtwdev) |
529 | { |
530 | int ret; |
531 | |
532 | rtw89_hci_ctrl_txdma_ch(rtwdev, enable: false); |
533 | rtw89_hci_ctrl_txdma_fw_ch(rtwdev, enable: true); |
534 | |
535 | rtw89_mac_ptk_drop_by_band_and_wait(rtwdev, band: RTW89_MAC_0); |
536 | |
537 | ret = rtw89_hci_poll_txdma_ch_idle(rtwdev); |
538 | if (ret) { |
539 | rtw89_err(rtwdev, "txdma ch busy\n" ); |
540 | return ret; |
541 | } |
542 | rtw89_wow_set_rx_filter(rtwdev, enable: true); |
543 | |
544 | ret = rtw89_mac_cfg_ppdu_status(rtwdev, mac_idx: RTW89_MAC_0, enable: false); |
545 | if (ret) { |
546 | rtw89_err(rtwdev, "cfg ppdu status\n" ); |
547 | return ret; |
548 | } |
549 | |
550 | return 0; |
551 | } |
552 | |
553 | static int rtw89_wow_enable_trx_post(struct rtw89_dev *rtwdev) |
554 | { |
555 | int ret; |
556 | |
557 | rtw89_hci_disable_intr(rtwdev); |
558 | rtw89_hci_ctrl_trxhci(rtwdev, enable: false); |
559 | |
560 | ret = rtw89_hci_poll_txdma_ch_idle(rtwdev); |
561 | if (ret) { |
562 | rtw89_err(rtwdev, "failed to poll txdma ch idle pcie\n" ); |
563 | return ret; |
564 | } |
565 | |
566 | ret = rtw89_wow_config_mac(rtwdev, enable_wow: true); |
567 | if (ret) { |
568 | rtw89_err(rtwdev, "failed to config mac\n" ); |
569 | return ret; |
570 | } |
571 | |
572 | rtw89_wow_set_rx_filter(rtwdev, enable: false); |
573 | rtw89_hci_reset(rtwdev); |
574 | |
575 | return 0; |
576 | } |
577 | |
578 | static int rtw89_wow_disable_trx_pre(struct rtw89_dev *rtwdev) |
579 | { |
580 | int ret; |
581 | |
582 | rtw89_hci_clr_idx_all(rtwdev); |
583 | |
584 | ret = rtw89_hci_rst_bdram(rtwdev); |
585 | if (ret) { |
586 | rtw89_warn(rtwdev, "reset bdram busy\n" ); |
587 | return ret; |
588 | } |
589 | |
590 | rtw89_hci_ctrl_trxhci(rtwdev, enable: true); |
591 | rtw89_hci_ctrl_txdma_ch(rtwdev, enable: true); |
592 | |
593 | ret = rtw89_wow_config_mac(rtwdev, enable_wow: false); |
594 | if (ret) { |
595 | rtw89_err(rtwdev, "failed to config mac\n" ); |
596 | return ret; |
597 | } |
598 | rtw89_hci_enable_intr(rtwdev); |
599 | |
600 | return 0; |
601 | } |
602 | |
603 | static int rtw89_wow_disable_trx_post(struct rtw89_dev *rtwdev) |
604 | { |
605 | int ret; |
606 | |
607 | ret = rtw89_mac_cfg_ppdu_status(rtwdev, mac_idx: RTW89_MAC_0, enable: true); |
608 | if (ret) |
609 | rtw89_err(rtwdev, "cfg ppdu status\n" ); |
610 | |
611 | return ret; |
612 | } |
613 | |
614 | static int rtw89_wow_fw_start(struct rtw89_dev *rtwdev) |
615 | { |
616 | struct rtw89_wow_param *rtw_wow = &rtwdev->wow; |
617 | struct rtw89_vif *rtwvif = (struct rtw89_vif *)rtw_wow->wow_vif->drv_priv; |
618 | int ret; |
619 | |
620 | rtw89_wow_pattern_write(rtwdev); |
621 | |
622 | ret = rtw89_fw_h2c_keep_alive(rtwdev, rtwvif, enable: true); |
623 | if (ret) { |
624 | rtw89_err(rtwdev, "wow: failed to enable keep alive\n" ); |
625 | return ret; |
626 | } |
627 | |
628 | ret = rtw89_fw_h2c_disconnect_detect(rtwdev, rtwvif, enable: true); |
629 | if (ret) { |
630 | rtw89_err(rtwdev, "wow: failed to enable disconnect detect\n" ); |
631 | goto out; |
632 | } |
633 | |
634 | ret = rtw89_wow_cfg_wake(rtwdev, wow: true); |
635 | if (ret) { |
636 | rtw89_err(rtwdev, "wow: failed to config wake\n" ); |
637 | goto out; |
638 | } |
639 | |
640 | ret = rtw89_wow_check_fw_status(rtwdev, wow_enable: true); |
641 | if (ret) { |
642 | rtw89_err(rtwdev, "wow: failed to check enable fw ready\n" ); |
643 | goto out; |
644 | } |
645 | |
646 | out: |
647 | return ret; |
648 | } |
649 | |
650 | static int rtw89_wow_fw_stop(struct rtw89_dev *rtwdev) |
651 | { |
652 | struct rtw89_wow_param *rtw_wow = &rtwdev->wow; |
653 | struct rtw89_vif *rtwvif = (struct rtw89_vif *)rtw_wow->wow_vif->drv_priv; |
654 | int ret; |
655 | |
656 | rtw89_wow_pattern_clear(rtwdev); |
657 | |
658 | ret = rtw89_fw_h2c_keep_alive(rtwdev, rtwvif, enable: false); |
659 | if (ret) { |
660 | rtw89_err(rtwdev, "wow: failed to disable keep alive\n" ); |
661 | goto out; |
662 | } |
663 | |
664 | ret = rtw89_fw_h2c_disconnect_detect(rtwdev, rtwvif, enable: false); |
665 | if (ret) { |
666 | rtw89_err(rtwdev, "wow: failed to disable disconnect detect\n" ); |
667 | goto out; |
668 | } |
669 | |
670 | rtw89_fw_release_general_pkt_list(rtwdev, notify_fw: true); |
671 | |
672 | ret = rtw89_wow_cfg_wake(rtwdev, wow: false); |
673 | if (ret) { |
674 | rtw89_err(rtwdev, "wow: failed to disable config wake\n" ); |
675 | goto out; |
676 | } |
677 | |
678 | ret = rtw89_wow_check_fw_status(rtwdev, wow_enable: false); |
679 | if (ret) { |
680 | rtw89_err(rtwdev, "wow: failed to check disable fw ready\n" ); |
681 | goto out; |
682 | } |
683 | |
684 | out: |
685 | return ret; |
686 | } |
687 | |
688 | static int rtw89_wow_enable(struct rtw89_dev *rtwdev) |
689 | { |
690 | int ret; |
691 | |
692 | set_bit(nr: RTW89_FLAG_WOWLAN, addr: rtwdev->flags); |
693 | |
694 | ret = rtw89_wow_enable_trx_pre(rtwdev); |
695 | if (ret) { |
696 | rtw89_err(rtwdev, "wow: failed to enable trx_pre\n" ); |
697 | goto out; |
698 | } |
699 | |
700 | rtw89_fw_release_general_pkt_list(rtwdev, notify_fw: true); |
701 | |
702 | ret = rtw89_wow_swap_fw(rtwdev, wow: true); |
703 | if (ret) { |
704 | rtw89_err(rtwdev, "wow: failed to swap to wow fw\n" ); |
705 | goto out; |
706 | } |
707 | |
708 | ret = rtw89_wow_fw_start(rtwdev); |
709 | if (ret) { |
710 | rtw89_err(rtwdev, "wow: failed to let wow fw start\n" ); |
711 | goto out; |
712 | } |
713 | |
714 | rtw89_wow_enter_lps(rtwdev); |
715 | |
716 | ret = rtw89_wow_enable_trx_post(rtwdev); |
717 | if (ret) { |
718 | rtw89_err(rtwdev, "wow: failed to enable trx_post\n" ); |
719 | goto out; |
720 | } |
721 | |
722 | return 0; |
723 | |
724 | out: |
725 | clear_bit(nr: RTW89_FLAG_WOWLAN, addr: rtwdev->flags); |
726 | return ret; |
727 | } |
728 | |
729 | static int rtw89_wow_disable(struct rtw89_dev *rtwdev) |
730 | { |
731 | int ret; |
732 | |
733 | ret = rtw89_wow_disable_trx_pre(rtwdev); |
734 | if (ret) { |
735 | rtw89_err(rtwdev, "wow: failed to disable trx_pre\n" ); |
736 | goto out; |
737 | } |
738 | |
739 | rtw89_wow_leave_lps(rtwdev); |
740 | |
741 | ret = rtw89_wow_fw_stop(rtwdev); |
742 | if (ret) { |
743 | rtw89_err(rtwdev, "wow: failed to swap to normal fw\n" ); |
744 | goto out; |
745 | } |
746 | |
747 | ret = rtw89_wow_swap_fw(rtwdev, wow: false); |
748 | if (ret) { |
749 | rtw89_err(rtwdev, "wow: failed to disable trx_post\n" ); |
750 | goto out; |
751 | } |
752 | |
753 | ret = rtw89_wow_disable_trx_post(rtwdev); |
754 | if (ret) { |
755 | rtw89_err(rtwdev, "wow: failed to disable trx_pre\n" ); |
756 | goto out; |
757 | } |
758 | |
759 | out: |
760 | clear_bit(nr: RTW89_FLAG_WOWLAN, addr: rtwdev->flags); |
761 | return ret; |
762 | } |
763 | |
764 | int rtw89_wow_resume(struct rtw89_dev *rtwdev) |
765 | { |
766 | int ret; |
767 | |
768 | if (!test_bit(RTW89_FLAG_WOWLAN, rtwdev->flags)) { |
769 | rtw89_err(rtwdev, "wow is not enabled\n" ); |
770 | ret = -EPERM; |
771 | goto out; |
772 | } |
773 | |
774 | if (!rtw89_mac_get_power_state(rtwdev)) { |
775 | rtw89_err(rtwdev, "chip is no power when resume\n" ); |
776 | ret = -EPERM; |
777 | goto out; |
778 | } |
779 | |
780 | rtw89_wow_leave_deep_ps(rtwdev); |
781 | |
782 | rtw89_wow_show_wakeup_reason(rtwdev); |
783 | |
784 | ret = rtw89_wow_disable(rtwdev); |
785 | if (ret) |
786 | rtw89_err(rtwdev, "failed to disable wow\n" ); |
787 | |
788 | out: |
789 | rtw89_wow_clear_wakeups(rtwdev); |
790 | return ret; |
791 | } |
792 | |
793 | int rtw89_wow_suspend(struct rtw89_dev *rtwdev, struct cfg80211_wowlan *wowlan) |
794 | { |
795 | int ret; |
796 | |
797 | ret = rtw89_wow_set_wakeups(rtwdev, wowlan); |
798 | if (ret) { |
799 | rtw89_err(rtwdev, "failed to set wakeup event\n" ); |
800 | return ret; |
801 | } |
802 | |
803 | rtw89_wow_leave_lps(rtwdev); |
804 | |
805 | ret = rtw89_wow_enable(rtwdev); |
806 | if (ret) { |
807 | rtw89_err(rtwdev, "failed to enable wow\n" ); |
808 | return ret; |
809 | } |
810 | |
811 | rtw89_wow_enter_deep_ps(rtwdev); |
812 | |
813 | return 0; |
814 | } |
815 | |