1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * HT handling |
4 | * |
5 | * Copyright 2003, Jouni Malinen <jkmaline@cc.hut.fi> |
6 | * Copyright 2002-2005, Instant802 Networks, Inc. |
7 | * Copyright 2005-2006, Devicescape Software, Inc. |
8 | * Copyright 2006-2007 Jiri Benc <jbenc@suse.cz> |
9 | * Copyright 2007, Michael Wu <flamingice@sourmilk.net> |
10 | * Copyright 2007-2010, Intel Corporation |
11 | * Copyright(c) 2015-2017 Intel Deutschland GmbH |
12 | * Copyright (C) 2018-2023 Intel Corporation |
13 | */ |
14 | |
15 | /** |
16 | * DOC: RX A-MPDU aggregation |
17 | * |
18 | * Aggregation on the RX side requires only implementing the |
19 | * @ampdu_action callback that is invoked to start/stop any |
20 | * block-ack sessions for RX aggregation. |
21 | * |
22 | * When RX aggregation is started by the peer, the driver is |
23 | * notified via @ampdu_action function, with the |
24 | * %IEEE80211_AMPDU_RX_START action, and may reject the request |
25 | * in which case a negative response is sent to the peer, if it |
26 | * accepts it a positive response is sent. |
27 | * |
28 | * While the session is active, the device/driver are required |
29 | * to de-aggregate frames and pass them up one by one to mac80211, |
30 | * which will handle the reorder buffer. |
31 | * |
32 | * When the aggregation session is stopped again by the peer or |
33 | * ourselves, the driver's @ampdu_action function will be called |
34 | * with the action %IEEE80211_AMPDU_RX_STOP. In this case, the |
35 | * call must not fail. |
36 | */ |
37 | |
38 | #include <linux/ieee80211.h> |
39 | #include <linux/slab.h> |
40 | #include <linux/export.h> |
41 | #include <net/mac80211.h> |
42 | #include "ieee80211_i.h" |
43 | #include "driver-ops.h" |
44 | |
45 | static void ieee80211_free_tid_rx(struct rcu_head *h) |
46 | { |
47 | struct tid_ampdu_rx *tid_rx = |
48 | container_of(h, struct tid_ampdu_rx, rcu_head); |
49 | int i; |
50 | |
51 | for (i = 0; i < tid_rx->buf_size; i++) |
52 | __skb_queue_purge(list: &tid_rx->reorder_buf[i]); |
53 | kfree(objp: tid_rx->reorder_buf); |
54 | kfree(objp: tid_rx->reorder_time); |
55 | kfree(objp: tid_rx); |
56 | } |
57 | |
58 | void __ieee80211_stop_rx_ba_session(struct sta_info *sta, u16 tid, |
59 | u16 initiator, u16 reason, bool tx) |
60 | { |
61 | struct ieee80211_local *local = sta->local; |
62 | struct tid_ampdu_rx *tid_rx; |
63 | struct ieee80211_ampdu_params params = { |
64 | .sta = &sta->sta, |
65 | .action = IEEE80211_AMPDU_RX_STOP, |
66 | .tid = tid, |
67 | .amsdu = false, |
68 | .timeout = 0, |
69 | .ssn = 0, |
70 | }; |
71 | |
72 | lockdep_assert_wiphy(sta->local->hw.wiphy); |
73 | |
74 | tid_rx = rcu_dereference_protected(sta->ampdu_mlme.tid_rx[tid], |
75 | lockdep_is_held(&sta->local->hw.wiphy->mtx)); |
76 | |
77 | if (!test_bit(tid, sta->ampdu_mlme.agg_session_valid)) |
78 | return; |
79 | |
80 | RCU_INIT_POINTER(sta->ampdu_mlme.tid_rx[tid], NULL); |
81 | __clear_bit(tid, sta->ampdu_mlme.agg_session_valid); |
82 | |
83 | ht_dbg(sta->sdata, |
84 | "Rx BA session stop requested for %pM tid %u %s reason: %d\n" , |
85 | sta->sta.addr, tid, |
86 | initiator == WLAN_BACK_RECIPIENT ? "recipient" : "initiator" , |
87 | (int)reason); |
88 | |
89 | if (drv_ampdu_action(local, sdata: sta->sdata, params: ¶ms)) |
90 | sdata_info(sta->sdata, |
91 | "HW problem - can not stop rx aggregation for %pM tid %d\n" , |
92 | sta->sta.addr, tid); |
93 | |
94 | /* check if this is a self generated aggregation halt */ |
95 | if (initiator == WLAN_BACK_RECIPIENT && tx) |
96 | ieee80211_send_delba(sdata: sta->sdata, da: sta->sta.addr, |
97 | tid, initiator: WLAN_BACK_RECIPIENT, reason_code: reason); |
98 | |
99 | /* |
100 | * return here in case tid_rx is not assigned - which will happen if |
101 | * IEEE80211_HW_SUPPORTS_REORDERING_BUFFER is set. |
102 | */ |
103 | if (!tid_rx) |
104 | return; |
105 | |
106 | del_timer_sync(timer: &tid_rx->session_timer); |
107 | |
108 | /* make sure ieee80211_sta_reorder_release() doesn't re-arm the timer */ |
109 | spin_lock_bh(lock: &tid_rx->reorder_lock); |
110 | tid_rx->removed = true; |
111 | spin_unlock_bh(lock: &tid_rx->reorder_lock); |
112 | del_timer_sync(timer: &tid_rx->reorder_timer); |
113 | |
114 | call_rcu(head: &tid_rx->rcu_head, func: ieee80211_free_tid_rx); |
115 | } |
116 | |
117 | void ieee80211_stop_rx_ba_session(struct ieee80211_vif *vif, u16 ba_rx_bitmap, |
118 | const u8 *addr) |
119 | { |
120 | struct ieee80211_sub_if_data *sdata = vif_to_sdata(p: vif); |
121 | struct sta_info *sta; |
122 | int i; |
123 | |
124 | rcu_read_lock(); |
125 | sta = sta_info_get_bss(sdata, addr); |
126 | if (!sta) { |
127 | rcu_read_unlock(); |
128 | return; |
129 | } |
130 | |
131 | for (i = 0; i < IEEE80211_NUM_TIDS; i++) |
132 | if (ba_rx_bitmap & BIT(i)) |
133 | set_bit(nr: i, addr: sta->ampdu_mlme.tid_rx_stop_requested); |
134 | |
135 | wiphy_work_queue(wiphy: sta->local->hw.wiphy, work: &sta->ampdu_mlme.work); |
136 | rcu_read_unlock(); |
137 | } |
138 | EXPORT_SYMBOL(ieee80211_stop_rx_ba_session); |
139 | |
140 | /* |
141 | * After accepting the AddBA Request we activated a timer, |
142 | * resetting it after each frame that arrives from the originator. |
143 | */ |
144 | static void sta_rx_agg_session_timer_expired(struct timer_list *t) |
145 | { |
146 | struct tid_ampdu_rx *tid_rx = from_timer(tid_rx, t, session_timer); |
147 | struct sta_info *sta = tid_rx->sta; |
148 | u8 tid = tid_rx->tid; |
149 | unsigned long timeout; |
150 | |
151 | timeout = tid_rx->last_rx + TU_TO_JIFFIES(tid_rx->timeout); |
152 | if (time_is_after_jiffies(timeout)) { |
153 | mod_timer(timer: &tid_rx->session_timer, expires: timeout); |
154 | return; |
155 | } |
156 | |
157 | ht_dbg(sta->sdata, "RX session timer expired on %pM tid %d\n" , |
158 | sta->sta.addr, tid); |
159 | |
160 | set_bit(nr: tid, addr: sta->ampdu_mlme.tid_rx_timer_expired); |
161 | wiphy_work_queue(wiphy: sta->local->hw.wiphy, work: &sta->ampdu_mlme.work); |
162 | } |
163 | |
164 | static void sta_rx_agg_reorder_timer_expired(struct timer_list *t) |
165 | { |
166 | struct tid_ampdu_rx *tid_rx = from_timer(tid_rx, t, reorder_timer); |
167 | |
168 | rcu_read_lock(); |
169 | ieee80211_release_reorder_timeout(sta: tid_rx->sta, tid: tid_rx->tid); |
170 | rcu_read_unlock(); |
171 | } |
172 | |
173 | static void ieee80211_add_addbaext(struct ieee80211_sub_if_data *sdata, |
174 | struct sk_buff *skb, |
175 | const struct ieee80211_addba_ext_ie *req, |
176 | u16 buf_size) |
177 | { |
178 | struct ieee80211_addba_ext_ie *resp; |
179 | u8 *pos; |
180 | |
181 | pos = skb_put_zero(skb, len: 2 + sizeof(struct ieee80211_addba_ext_ie)); |
182 | *pos++ = WLAN_EID_ADDBA_EXT; |
183 | *pos++ = sizeof(struct ieee80211_addba_ext_ie); |
184 | resp = (struct ieee80211_addba_ext_ie *)pos; |
185 | resp->data = req->data & IEEE80211_ADDBA_EXT_NO_FRAG; |
186 | |
187 | resp->data |= u8_encode_bits(v: buf_size >> IEEE80211_ADDBA_EXT_BUF_SIZE_SHIFT, |
188 | IEEE80211_ADDBA_EXT_BUF_SIZE_MASK); |
189 | } |
190 | |
191 | static void ieee80211_send_addba_resp(struct sta_info *sta, u8 *da, u16 tid, |
192 | u8 dialog_token, u16 status, u16 policy, |
193 | u16 buf_size, u16 timeout, |
194 | const struct ieee80211_addba_ext_ie *addbaext) |
195 | { |
196 | struct ieee80211_sub_if_data *sdata = sta->sdata; |
197 | struct ieee80211_local *local = sdata->local; |
198 | struct sk_buff *skb; |
199 | struct ieee80211_mgmt *mgmt; |
200 | bool amsdu = ieee80211_hw_check(&local->hw, SUPPORTS_AMSDU_IN_AMPDU); |
201 | u16 capab; |
202 | |
203 | skb = dev_alloc_skb(length: sizeof(*mgmt) + |
204 | 2 + sizeof(struct ieee80211_addba_ext_ie) + |
205 | local->hw.extra_tx_headroom); |
206 | if (!skb) |
207 | return; |
208 | |
209 | skb_reserve(skb, len: local->hw.extra_tx_headroom); |
210 | mgmt = skb_put_zero(skb, len: 24); |
211 | memcpy(mgmt->da, da, ETH_ALEN); |
212 | memcpy(mgmt->sa, sdata->vif.addr, ETH_ALEN); |
213 | if (sdata->vif.type == NL80211_IFTYPE_AP || |
214 | sdata->vif.type == NL80211_IFTYPE_AP_VLAN || |
215 | sdata->vif.type == NL80211_IFTYPE_MESH_POINT) |
216 | memcpy(mgmt->bssid, sdata->vif.addr, ETH_ALEN); |
217 | else if (sdata->vif.type == NL80211_IFTYPE_STATION) |
218 | memcpy(mgmt->bssid, sdata->vif.cfg.ap_addr, ETH_ALEN); |
219 | else if (sdata->vif.type == NL80211_IFTYPE_ADHOC) |
220 | memcpy(mgmt->bssid, sdata->u.ibss.bssid, ETH_ALEN); |
221 | |
222 | mgmt->frame_control = cpu_to_le16(IEEE80211_FTYPE_MGMT | |
223 | IEEE80211_STYPE_ACTION); |
224 | |
225 | skb_put(skb, len: 1 + sizeof(mgmt->u.action.u.addba_resp)); |
226 | mgmt->u.action.category = WLAN_CATEGORY_BACK; |
227 | mgmt->u.action.u.addba_resp.action_code = WLAN_ACTION_ADDBA_RESP; |
228 | mgmt->u.action.u.addba_resp.dialog_token = dialog_token; |
229 | |
230 | capab = u16_encode_bits(v: amsdu, IEEE80211_ADDBA_PARAM_AMSDU_MASK); |
231 | capab |= u16_encode_bits(v: policy, IEEE80211_ADDBA_PARAM_POLICY_MASK); |
232 | capab |= u16_encode_bits(v: tid, IEEE80211_ADDBA_PARAM_TID_MASK); |
233 | capab |= u16_encode_bits(v: buf_size, IEEE80211_ADDBA_PARAM_BUF_SIZE_MASK); |
234 | |
235 | mgmt->u.action.u.addba_resp.capab = cpu_to_le16(capab); |
236 | mgmt->u.action.u.addba_resp.timeout = cpu_to_le16(timeout); |
237 | mgmt->u.action.u.addba_resp.status = cpu_to_le16(status); |
238 | |
239 | if (sta->sta.deflink.he_cap.has_he && addbaext) |
240 | ieee80211_add_addbaext(sdata, skb, req: addbaext, buf_size); |
241 | |
242 | ieee80211_tx_skb(sdata, skb); |
243 | } |
244 | |
245 | void __ieee80211_start_rx_ba_session(struct sta_info *sta, |
246 | u8 dialog_token, u16 timeout, |
247 | u16 start_seq_num, u16 ba_policy, u16 tid, |
248 | u16 buf_size, bool tx, bool auto_seq, |
249 | const struct ieee80211_addba_ext_ie *addbaext) |
250 | { |
251 | struct ieee80211_local *local = sta->sdata->local; |
252 | struct tid_ampdu_rx *tid_agg_rx; |
253 | struct ieee80211_ampdu_params params = { |
254 | .sta = &sta->sta, |
255 | .action = IEEE80211_AMPDU_RX_START, |
256 | .tid = tid, |
257 | .amsdu = false, |
258 | .timeout = timeout, |
259 | .ssn = start_seq_num, |
260 | }; |
261 | int i, ret = -EOPNOTSUPP; |
262 | u16 status = WLAN_STATUS_REQUEST_DECLINED; |
263 | u16 max_buf_size; |
264 | |
265 | lockdep_assert_wiphy(sta->local->hw.wiphy); |
266 | |
267 | if (tid >= IEEE80211_FIRST_TSPEC_TSID) { |
268 | ht_dbg(sta->sdata, |
269 | "STA %pM requests BA session on unsupported tid %d\n" , |
270 | sta->sta.addr, tid); |
271 | goto end; |
272 | } |
273 | |
274 | if (!sta->sta.deflink.ht_cap.ht_supported && |
275 | !sta->sta.deflink.he_cap.has_he) { |
276 | ht_dbg(sta->sdata, |
277 | "STA %pM erroneously requests BA session on tid %d w/o HT\n" , |
278 | sta->sta.addr, tid); |
279 | /* send a response anyway, it's an error case if we get here */ |
280 | goto end; |
281 | } |
282 | |
283 | if (test_sta_flag(sta, flag: WLAN_STA_BLOCK_BA)) { |
284 | ht_dbg(sta->sdata, |
285 | "Suspend in progress - Denying ADDBA request (%pM tid %d)\n" , |
286 | sta->sta.addr, tid); |
287 | goto end; |
288 | } |
289 | |
290 | if (sta->sta.deflink.eht_cap.has_eht) |
291 | max_buf_size = IEEE80211_MAX_AMPDU_BUF_EHT; |
292 | else if (sta->sta.deflink.he_cap.has_he) |
293 | max_buf_size = IEEE80211_MAX_AMPDU_BUF_HE; |
294 | else |
295 | max_buf_size = IEEE80211_MAX_AMPDU_BUF_HT; |
296 | |
297 | /* sanity check for incoming parameters: |
298 | * check if configuration can support the BA policy |
299 | * and if buffer size does not exceeds max value */ |
300 | /* XXX: check own ht delayed BA capability?? */ |
301 | if (((ba_policy != 1) && |
302 | (!(sta->sta.deflink.ht_cap.cap & IEEE80211_HT_CAP_DELAY_BA))) || |
303 | (buf_size > max_buf_size)) { |
304 | status = WLAN_STATUS_INVALID_QOS_PARAM; |
305 | ht_dbg_ratelimited(sta->sdata, |
306 | "AddBA Req with bad params from %pM on tid %u. policy %d, buffer size %d\n" , |
307 | sta->sta.addr, tid, ba_policy, buf_size); |
308 | goto end; |
309 | } |
310 | /* determine default buffer size */ |
311 | if (buf_size == 0) |
312 | buf_size = max_buf_size; |
313 | |
314 | /* make sure the size doesn't exceed the maximum supported by the hw */ |
315 | if (buf_size > sta->sta.max_rx_aggregation_subframes) |
316 | buf_size = sta->sta.max_rx_aggregation_subframes; |
317 | params.buf_size = buf_size; |
318 | |
319 | ht_dbg(sta->sdata, "AddBA Req buf_size=%d for %pM\n" , |
320 | buf_size, sta->sta.addr); |
321 | |
322 | if (test_bit(tid, sta->ampdu_mlme.agg_session_valid)) { |
323 | if (sta->ampdu_mlme.tid_rx_token[tid] == dialog_token) { |
324 | struct tid_ampdu_rx *tid_rx; |
325 | |
326 | ht_dbg_ratelimited(sta->sdata, |
327 | "updated AddBA Req from %pM on tid %u\n" , |
328 | sta->sta.addr, tid); |
329 | /* We have no API to update the timeout value in the |
330 | * driver so reject the timeout update if the timeout |
331 | * changed. If it did not change, i.e., no real update, |
332 | * just reply with success. |
333 | */ |
334 | rcu_read_lock(); |
335 | tid_rx = rcu_dereference(sta->ampdu_mlme.tid_rx[tid]); |
336 | if (tid_rx && tid_rx->timeout == timeout) |
337 | status = WLAN_STATUS_SUCCESS; |
338 | else |
339 | status = WLAN_STATUS_REQUEST_DECLINED; |
340 | rcu_read_unlock(); |
341 | goto end; |
342 | } |
343 | |
344 | ht_dbg_ratelimited(sta->sdata, |
345 | "unexpected AddBA Req from %pM on tid %u\n" , |
346 | sta->sta.addr, tid); |
347 | |
348 | /* delete existing Rx BA session on the same tid */ |
349 | __ieee80211_stop_rx_ba_session(sta, tid, initiator: WLAN_BACK_RECIPIENT, |
350 | reason: WLAN_STATUS_UNSPECIFIED_QOS, |
351 | tx: false); |
352 | } |
353 | |
354 | if (ieee80211_hw_check(&local->hw, SUPPORTS_REORDERING_BUFFER)) { |
355 | ret = drv_ampdu_action(local, sdata: sta->sdata, params: ¶ms); |
356 | ht_dbg(sta->sdata, |
357 | "Rx A-MPDU request on %pM tid %d result %d\n" , |
358 | sta->sta.addr, tid, ret); |
359 | if (!ret) |
360 | status = WLAN_STATUS_SUCCESS; |
361 | goto end; |
362 | } |
363 | |
364 | /* prepare A-MPDU MLME for Rx aggregation */ |
365 | tid_agg_rx = kzalloc(size: sizeof(*tid_agg_rx), GFP_KERNEL); |
366 | if (!tid_agg_rx) |
367 | goto end; |
368 | |
369 | spin_lock_init(&tid_agg_rx->reorder_lock); |
370 | |
371 | /* rx timer */ |
372 | timer_setup(&tid_agg_rx->session_timer, |
373 | sta_rx_agg_session_timer_expired, TIMER_DEFERRABLE); |
374 | |
375 | /* rx reorder timer */ |
376 | timer_setup(&tid_agg_rx->reorder_timer, |
377 | sta_rx_agg_reorder_timer_expired, 0); |
378 | |
379 | /* prepare reordering buffer */ |
380 | tid_agg_rx->reorder_buf = |
381 | kcalloc(n: buf_size, size: sizeof(struct sk_buff_head), GFP_KERNEL); |
382 | tid_agg_rx->reorder_time = |
383 | kcalloc(n: buf_size, size: sizeof(unsigned long), GFP_KERNEL); |
384 | if (!tid_agg_rx->reorder_buf || !tid_agg_rx->reorder_time) { |
385 | kfree(objp: tid_agg_rx->reorder_buf); |
386 | kfree(objp: tid_agg_rx->reorder_time); |
387 | kfree(objp: tid_agg_rx); |
388 | goto end; |
389 | } |
390 | |
391 | for (i = 0; i < buf_size; i++) |
392 | __skb_queue_head_init(list: &tid_agg_rx->reorder_buf[i]); |
393 | |
394 | ret = drv_ampdu_action(local, sdata: sta->sdata, params: ¶ms); |
395 | ht_dbg(sta->sdata, "Rx A-MPDU request on %pM tid %d result %d\n" , |
396 | sta->sta.addr, tid, ret); |
397 | if (ret) { |
398 | kfree(objp: tid_agg_rx->reorder_buf); |
399 | kfree(objp: tid_agg_rx->reorder_time); |
400 | kfree(objp: tid_agg_rx); |
401 | goto end; |
402 | } |
403 | |
404 | /* update data */ |
405 | tid_agg_rx->ssn = start_seq_num; |
406 | tid_agg_rx->head_seq_num = start_seq_num; |
407 | tid_agg_rx->buf_size = buf_size; |
408 | tid_agg_rx->timeout = timeout; |
409 | tid_agg_rx->stored_mpdu_num = 0; |
410 | tid_agg_rx->auto_seq = auto_seq; |
411 | tid_agg_rx->started = false; |
412 | tid_agg_rx->reorder_buf_filtered = 0; |
413 | tid_agg_rx->tid = tid; |
414 | tid_agg_rx->sta = sta; |
415 | status = WLAN_STATUS_SUCCESS; |
416 | |
417 | /* activate it for RX */ |
418 | rcu_assign_pointer(sta->ampdu_mlme.tid_rx[tid], tid_agg_rx); |
419 | |
420 | if (timeout) { |
421 | mod_timer(timer: &tid_agg_rx->session_timer, TU_TO_EXP_TIME(timeout)); |
422 | tid_agg_rx->last_rx = jiffies; |
423 | } |
424 | |
425 | end: |
426 | if (status == WLAN_STATUS_SUCCESS) { |
427 | __set_bit(tid, sta->ampdu_mlme.agg_session_valid); |
428 | __clear_bit(tid, sta->ampdu_mlme.unexpected_agg); |
429 | sta->ampdu_mlme.tid_rx_token[tid] = dialog_token; |
430 | } |
431 | |
432 | if (tx) |
433 | ieee80211_send_addba_resp(sta, da: sta->sta.addr, tid, |
434 | dialog_token, status, policy: 1, buf_size, |
435 | timeout, addbaext); |
436 | } |
437 | |
438 | void ieee80211_process_addba_request(struct ieee80211_local *local, |
439 | struct sta_info *sta, |
440 | struct ieee80211_mgmt *mgmt, |
441 | size_t len) |
442 | { |
443 | u16 capab, tid, timeout, ba_policy, buf_size, start_seq_num; |
444 | struct ieee802_11_elems *elems = NULL; |
445 | u8 dialog_token; |
446 | int ies_len; |
447 | |
448 | /* extract session parameters from addba request frame */ |
449 | dialog_token = mgmt->u.action.u.addba_req.dialog_token; |
450 | timeout = le16_to_cpu(mgmt->u.action.u.addba_req.timeout); |
451 | start_seq_num = |
452 | le16_to_cpu(mgmt->u.action.u.addba_req.start_seq_num) >> 4; |
453 | |
454 | capab = le16_to_cpu(mgmt->u.action.u.addba_req.capab); |
455 | ba_policy = (capab & IEEE80211_ADDBA_PARAM_POLICY_MASK) >> 1; |
456 | tid = (capab & IEEE80211_ADDBA_PARAM_TID_MASK) >> 2; |
457 | buf_size = (capab & IEEE80211_ADDBA_PARAM_BUF_SIZE_MASK) >> 6; |
458 | |
459 | ies_len = len - offsetof(struct ieee80211_mgmt, |
460 | u.action.u.addba_req.variable); |
461 | if (ies_len) { |
462 | elems = ieee802_11_parse_elems(start: mgmt->u.action.u.addba_req.variable, |
463 | len: ies_len, action: true, NULL); |
464 | if (!elems || elems->parse_error) |
465 | goto free; |
466 | } |
467 | |
468 | if (sta->sta.deflink.eht_cap.has_eht && elems && elems->addba_ext_ie) { |
469 | u8 buf_size_1k = u8_get_bits(v: elems->addba_ext_ie->data, |
470 | IEEE80211_ADDBA_EXT_BUF_SIZE_MASK); |
471 | |
472 | buf_size |= buf_size_1k << IEEE80211_ADDBA_EXT_BUF_SIZE_SHIFT; |
473 | } |
474 | |
475 | __ieee80211_start_rx_ba_session(sta, dialog_token, timeout, |
476 | start_seq_num, ba_policy, tid, |
477 | buf_size, tx: true, auto_seq: false, |
478 | addbaext: elems ? elems->addba_ext_ie : NULL); |
479 | free: |
480 | kfree(objp: elems); |
481 | } |
482 | |
483 | void ieee80211_manage_rx_ba_offl(struct ieee80211_vif *vif, |
484 | const u8 *addr, unsigned int tid) |
485 | { |
486 | struct ieee80211_sub_if_data *sdata = vif_to_sdata(p: vif); |
487 | struct sta_info *sta; |
488 | |
489 | rcu_read_lock(); |
490 | sta = sta_info_get_bss(sdata, addr); |
491 | if (!sta) |
492 | goto unlock; |
493 | |
494 | set_bit(nr: tid, addr: sta->ampdu_mlme.tid_rx_manage_offl); |
495 | wiphy_work_queue(wiphy: sta->local->hw.wiphy, work: &sta->ampdu_mlme.work); |
496 | unlock: |
497 | rcu_read_unlock(); |
498 | } |
499 | EXPORT_SYMBOL(ieee80211_manage_rx_ba_offl); |
500 | |
501 | void ieee80211_rx_ba_timer_expired(struct ieee80211_vif *vif, |
502 | const u8 *addr, unsigned int tid) |
503 | { |
504 | struct ieee80211_sub_if_data *sdata = vif_to_sdata(p: vif); |
505 | struct sta_info *sta; |
506 | |
507 | rcu_read_lock(); |
508 | sta = sta_info_get_bss(sdata, addr); |
509 | if (!sta) |
510 | goto unlock; |
511 | |
512 | set_bit(nr: tid, addr: sta->ampdu_mlme.tid_rx_timer_expired); |
513 | wiphy_work_queue(wiphy: sta->local->hw.wiphy, work: &sta->ampdu_mlme.work); |
514 | |
515 | unlock: |
516 | rcu_read_unlock(); |
517 | } |
518 | EXPORT_SYMBOL(ieee80211_rx_ba_timer_expired); |
519 | |