]>
Commit | Line | Data |
---|---|---|
0e3d6777 | 1 | // SPDX-License-Identifier: ISC |
aee5b8cf FF |
2 | /* |
3 | * Copyright (C) 2018 Felix Fietkau <nbd@nbd.name> | |
aee5b8cf FF |
4 | */ |
5 | #include "mt76.h" | |
6 | ||
e7ec563e MT |
7 | static unsigned long mt76_aggr_tid_to_timeo(u8 tidno) |
8 | { | |
9 | /* Currently voice traffic (AC_VO) always runs without aggregation, | |
10 | * no special handling is needed. AC_BE/AC_BK use tids 0-3. Just check | |
11 | * for non AC_BK/AC_BE and set smaller timeout for it. */ | |
12 | return HZ / (tidno >= 4 ? 25 : 10); | |
13 | } | |
aee5b8cf FF |
14 | |
15 | static void | |
16 | mt76_aggr_release(struct mt76_rx_tid *tid, struct sk_buff_head *frames, int idx) | |
17 | { | |
18 | struct sk_buff *skb; | |
19 | ||
20 | tid->head = ieee80211_sn_inc(tid->head); | |
21 | ||
22 | skb = tid->reorder_buf[idx]; | |
23 | if (!skb) | |
24 | return; | |
25 | ||
26 | tid->reorder_buf[idx] = NULL; | |
27 | tid->nframes--; | |
28 | __skb_queue_tail(frames, skb); | |
29 | } | |
30 | ||
31 | static void | |
13381dcd RL |
32 | mt76_rx_aggr_release_frames(struct mt76_rx_tid *tid, |
33 | struct sk_buff_head *frames, | |
34 | u16 head) | |
aee5b8cf FF |
35 | { |
36 | int idx; | |
37 | ||
38 | while (ieee80211_sn_less(tid->head, head)) { | |
39 | idx = tid->head % tid->size; | |
40 | mt76_aggr_release(tid, frames, idx); | |
41 | } | |
42 | } | |
43 | ||
44 | static void | |
45 | mt76_rx_aggr_release_head(struct mt76_rx_tid *tid, struct sk_buff_head *frames) | |
46 | { | |
47 | int idx = tid->head % tid->size; | |
48 | ||
49 | while (tid->reorder_buf[idx]) { | |
50 | mt76_aggr_release(tid, frames, idx); | |
51 | idx = tid->head % tid->size; | |
52 | } | |
53 | } | |
54 | ||
55 | static void | |
56 | mt76_rx_aggr_check_release(struct mt76_rx_tid *tid, struct sk_buff_head *frames) | |
57 | { | |
58 | struct mt76_rx_status *status; | |
59 | struct sk_buff *skb; | |
60 | int start, idx, nframes; | |
61 | ||
62 | if (!tid->nframes) | |
63 | return; | |
64 | ||
65 | mt76_rx_aggr_release_head(tid, frames); | |
66 | ||
67 | start = tid->head % tid->size; | |
68 | nframes = tid->nframes; | |
69 | ||
70 | for (idx = (tid->head + 1) % tid->size; | |
71 | idx != start && nframes; | |
72 | idx = (idx + 1) % tid->size) { | |
aee5b8cf FF |
73 | skb = tid->reorder_buf[idx]; |
74 | if (!skb) | |
75 | continue; | |
76 | ||
77 | nframes--; | |
13381dcd | 78 | status = (struct mt76_rx_status *)skb->cb; |
0fda6d7b RL |
79 | if (!time_after32(jiffies, |
80 | status->reorder_time + | |
81 | mt76_aggr_tid_to_timeo(tid->num))) | |
aee5b8cf FF |
82 | continue; |
83 | ||
84 | mt76_rx_aggr_release_frames(tid, frames, status->seqno); | |
85 | } | |
86 | ||
87 | mt76_rx_aggr_release_head(tid, frames); | |
88 | } | |
89 | ||
90 | static void | |
91 | mt76_rx_aggr_reorder_work(struct work_struct *work) | |
92 | { | |
93 | struct mt76_rx_tid *tid = container_of(work, struct mt76_rx_tid, | |
94 | reorder_work.work); | |
95 | struct mt76_dev *dev = tid->dev; | |
aee5b8cf | 96 | struct sk_buff_head frames; |
fb208dc7 | 97 | int nframes; |
aee5b8cf FF |
98 | |
99 | __skb_queue_head_init(&frames); | |
100 | ||
101 | local_bh_disable(); | |
9febfa67 | 102 | rcu_read_lock(); |
aee5b8cf FF |
103 | |
104 | spin_lock(&tid->lock); | |
105 | mt76_rx_aggr_check_release(tid, &frames); | |
fb208dc7 | 106 | nframes = tid->nframes; |
aee5b8cf FF |
107 | spin_unlock(&tid->lock); |
108 | ||
fb208dc7 FF |
109 | if (nframes) |
110 | ieee80211_queue_delayed_work(tid->dev->hw, &tid->reorder_work, | |
e7ec563e | 111 | mt76_aggr_tid_to_timeo(tid->num)); |
81e850ef | 112 | mt76_rx_complete(dev, &frames, NULL); |
aee5b8cf | 113 | |
9febfa67 | 114 | rcu_read_unlock(); |
aee5b8cf FF |
115 | local_bh_enable(); |
116 | } | |
117 | ||
17cf68b7 FF |
118 | static void |
119 | mt76_rx_aggr_check_ctl(struct sk_buff *skb, struct sk_buff_head *frames) | |
120 | { | |
13381dcd | 121 | struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb; |
77ae1d5e | 122 | struct ieee80211_bar *bar = mt76_skb_get_hdr(skb); |
17cf68b7 FF |
123 | struct mt76_wcid *wcid = status->wcid; |
124 | struct mt76_rx_tid *tid; | |
e195dad1 | 125 | u8 tidno = status->qos_ctl & IEEE80211_QOS_CTL_TID_MASK; |
17cf68b7 FF |
126 | u16 seqno; |
127 | ||
128 | if (!ieee80211_is_ctl(bar->frame_control)) | |
129 | return; | |
130 | ||
131 | if (!ieee80211_is_back_req(bar->frame_control)) | |
132 | return; | |
133 | ||
e195dad1 | 134 | status->qos_ctl = tidno = le16_to_cpu(bar->control) >> 12; |
b183878a | 135 | seqno = IEEE80211_SEQ_TO_SN(le16_to_cpu(bar->start_seq_num)); |
e195dad1 | 136 | tid = rcu_dereference(wcid->aggr[tidno]); |
17cf68b7 FF |
137 | if (!tid) |
138 | return; | |
139 | ||
140 | spin_lock_bh(&tid->lock); | |
e7aaa72f FF |
141 | if (!tid->stopped) { |
142 | mt76_rx_aggr_release_frames(tid, frames, seqno); | |
143 | mt76_rx_aggr_release_head(tid, frames); | |
144 | } | |
17cf68b7 FF |
145 | spin_unlock_bh(&tid->lock); |
146 | } | |
147 | ||
aee5b8cf FF |
148 | void mt76_rx_aggr_reorder(struct sk_buff *skb, struct sk_buff_head *frames) |
149 | { | |
13381dcd | 150 | struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb; |
aee5b8cf FF |
151 | struct mt76_wcid *wcid = status->wcid; |
152 | struct ieee80211_sta *sta; | |
153 | struct mt76_rx_tid *tid; | |
154 | bool sn_less; | |
7c4f744d | 155 | u16 seqno, head, size, idx; |
e195dad1 | 156 | u8 tidno = status->qos_ctl & IEEE80211_QOS_CTL_TID_MASK; |
7c4f744d | 157 | u8 ackp; |
aee5b8cf FF |
158 | |
159 | __skb_queue_tail(frames, skb); | |
160 | ||
161 | sta = wcid_to_sta(wcid); | |
17cf68b7 | 162 | if (!sta) |
aee5b8cf FF |
163 | return; |
164 | ||
e195dad1 | 165 | if (!status->aggr && !(status->flag & RX_FLAG_8023)) { |
17cf68b7 FF |
166 | mt76_rx_aggr_check_ctl(skb, frames); |
167 | return; | |
168 | } | |
169 | ||
1af83148 | 170 | /* not part of a BA session */ |
e195dad1 | 171 | ackp = status->qos_ctl & IEEE80211_QOS_CTL_ACK_POLICY_MASK; |
1af83148 FF |
172 | if (ackp != IEEE80211_QOS_CTL_ACK_POLICY_BLOCKACK && |
173 | ackp != IEEE80211_QOS_CTL_ACK_POLICY_NORMAL) | |
174 | return; | |
175 | ||
e195dad1 | 176 | tid = rcu_dereference(wcid->aggr[tidno]); |
aee5b8cf FF |
177 | if (!tid) |
178 | return; | |
179 | ||
18efed59 | 180 | status->flag |= RX_FLAG_DUP_VALIDATED; |
aee5b8cf FF |
181 | spin_lock_bh(&tid->lock); |
182 | ||
183 | if (tid->stopped) | |
184 | goto out; | |
185 | ||
186 | head = tid->head; | |
187 | seqno = status->seqno; | |
188 | size = tid->size; | |
189 | sn_less = ieee80211_sn_less(seqno, head); | |
190 | ||
191 | if (!tid->started) { | |
192 | if (sn_less) | |
193 | goto out; | |
194 | ||
195 | tid->started = true; | |
196 | } | |
197 | ||
198 | if (sn_less) { | |
199 | __skb_unlink(skb, frames); | |
200 | dev_kfree_skb(skb); | |
201 | goto out; | |
202 | } | |
203 | ||
204 | if (seqno == head) { | |
205 | tid->head = ieee80211_sn_inc(head); | |
206 | if (tid->nframes) | |
207 | mt76_rx_aggr_release_head(tid, frames); | |
208 | goto out; | |
209 | } | |
210 | ||
211 | __skb_unlink(skb, frames); | |
212 | ||
213 | /* | |
214 | * Frame sequence number exceeds buffering window, free up some space | |
215 | * by releasing previous frames | |
216 | */ | |
217 | if (!ieee80211_sn_less(seqno, head + size)) { | |
218 | head = ieee80211_sn_inc(ieee80211_sn_sub(seqno, size)); | |
219 | mt76_rx_aggr_release_frames(tid, frames, head); | |
220 | } | |
221 | ||
222 | idx = seqno % size; | |
223 | ||
224 | /* Discard if the current slot is already in use */ | |
225 | if (tid->reorder_buf[idx]) { | |
226 | dev_kfree_skb(skb); | |
227 | goto out; | |
228 | } | |
229 | ||
230 | status->reorder_time = jiffies; | |
231 | tid->reorder_buf[idx] = skb; | |
232 | tid->nframes++; | |
233 | mt76_rx_aggr_release_head(tid, frames); | |
234 | ||
13381dcd | 235 | ieee80211_queue_delayed_work(tid->dev->hw, &tid->reorder_work, |
e7ec563e | 236 | mt76_aggr_tid_to_timeo(tid->num)); |
aee5b8cf FF |
237 | |
238 | out: | |
239 | spin_unlock_bh(&tid->lock); | |
240 | } | |
241 | ||
242 | int mt76_rx_aggr_start(struct mt76_dev *dev, struct mt76_wcid *wcid, u8 tidno, | |
7c4f744d | 243 | u16 ssn, u16 size) |
aee5b8cf FF |
244 | { |
245 | struct mt76_rx_tid *tid; | |
246 | ||
247 | mt76_rx_aggr_stop(dev, wcid, tidno); | |
248 | ||
acafe7e3 | 249 | tid = kzalloc(struct_size(tid, reorder_buf, size), GFP_KERNEL); |
aee5b8cf FF |
250 | if (!tid) |
251 | return -ENOMEM; | |
252 | ||
253 | tid->dev = dev; | |
254 | tid->head = ssn; | |
255 | tid->size = size; | |
e7ec563e | 256 | tid->num = tidno; |
aee5b8cf FF |
257 | INIT_DELAYED_WORK(&tid->reorder_work, mt76_rx_aggr_reorder_work); |
258 | spin_lock_init(&tid->lock); | |
259 | ||
260 | rcu_assign_pointer(wcid->aggr[tidno], tid); | |
261 | ||
262 | return 0; | |
263 | } | |
264 | EXPORT_SYMBOL_GPL(mt76_rx_aggr_start); | |
265 | ||
266 | static void mt76_rx_aggr_shutdown(struct mt76_dev *dev, struct mt76_rx_tid *tid) | |
267 | { | |
7c4f744d | 268 | u16 size = tid->size; |
aee5b8cf FF |
269 | int i; |
270 | ||
271 | spin_lock_bh(&tid->lock); | |
272 | ||
273 | tid->stopped = true; | |
274 | for (i = 0; tid->nframes && i < size; i++) { | |
275 | struct sk_buff *skb = tid->reorder_buf[i]; | |
276 | ||
277 | if (!skb) | |
278 | continue; | |
279 | ||
9379df2f | 280 | tid->reorder_buf[i] = NULL; |
aee5b8cf FF |
281 | tid->nframes--; |
282 | dev_kfree_skb(skb); | |
283 | } | |
284 | ||
285 | spin_unlock_bh(&tid->lock); | |
e7aaa72f FF |
286 | |
287 | cancel_delayed_work_sync(&tid->reorder_work); | |
aee5b8cf FF |
288 | } |
289 | ||
290 | void mt76_rx_aggr_stop(struct mt76_dev *dev, struct mt76_wcid *wcid, u8 tidno) | |
291 | { | |
fb7d95c6 | 292 | struct mt76_rx_tid *tid = NULL; |
aee5b8cf | 293 | |
a191c9e9 PM |
294 | tid = rcu_replace_pointer(wcid->aggr[tidno], tid, |
295 | lockdep_is_held(&dev->mutex)); | |
aee5b8cf | 296 | if (tid) { |
aee5b8cf FF |
297 | mt76_rx_aggr_shutdown(dev, tid); |
298 | kfree_rcu(tid, rcu_head); | |
299 | } | |
aee5b8cf FF |
300 | } |
301 | EXPORT_SYMBOL_GPL(mt76_rx_aggr_stop); |