Commit | Line | Data |
---|---|---|
9a1bb602 HM |
1 | /* Copyright (c) 2014 Broadcom Corporation |
2 | * | |
3 | * Permission to use, copy, modify, and/or distribute this software for any | |
4 | * purpose with or without fee is hereby granted, provided that the above | |
5 | * copyright notice and this permission notice appear in all copies. | |
6 | * | |
7 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
8 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
9 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY | |
10 | * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
11 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION | |
12 | * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | |
13 | * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
14 | */ | |
15 | ||
16 | ||
17 | #include <linux/types.h> | |
18 | #include <linux/netdevice.h> | |
19 | #include <linux/etherdevice.h> | |
20 | #include <brcmu_utils.h> | |
21 | ||
122d3d04 | 22 | #include "core.h" |
a8e8ed34 | 23 | #include "debug.h" |
d14f78b9 | 24 | #include "bus.h" |
9a1bb602 HM |
25 | #include "proto.h" |
26 | #include "flowring.h" | |
27 | #include "msgbuf.h" | |
6b89dcb3 | 28 | #include "common.h" |
9a1bb602 HM |
29 | |
30 | ||
31 | #define BRCMF_FLOWRING_HIGH 1024 | |
32 | #define BRCMF_FLOWRING_LOW (BRCMF_FLOWRING_HIGH - 256) | |
33 | #define BRCMF_FLOWRING_INVALID_IFIDX 0xff | |
34 | ||
35 | #define BRCMF_FLOWRING_HASH_AP(da, fifo, ifidx) (da[5] + fifo + ifidx * 16) | |
36 | #define BRCMF_FLOWRING_HASH_STA(fifo, ifidx) (fifo + ifidx * 16) | |
37 | ||
9a1bb602 HM |
38 | static const u8 brcmf_flowring_prio2fifo[] = { |
39 | 1, | |
40 | 0, | |
41 | 0, | |
42 | 1, | |
43 | 2, | |
44 | 2, | |
45 | 3, | |
46 | 3 | |
47 | }; | |
48 | ||
49 | ||
70b7d94b HM |
50 | static bool |
51 | brcmf_flowring_is_tdls_mac(struct brcmf_flowring *flow, u8 mac[ETH_ALEN]) | |
52 | { | |
53 | struct brcmf_flowring_tdls_entry *search; | |
54 | ||
55 | search = flow->tdls_entry; | |
56 | ||
57 | while (search) { | |
58 | if (memcmp(search->mac, mac, ETH_ALEN) == 0) | |
59 | return true; | |
60 | search = search->next; | |
61 | } | |
62 | ||
63 | return false; | |
64 | } | |
65 | ||
66 | ||
9a1bb602 HM |
67 | u32 brcmf_flowring_lookup(struct brcmf_flowring *flow, u8 da[ETH_ALEN], |
68 | u8 prio, u8 ifidx) | |
69 | { | |
70 | struct brcmf_flowring_hash *hash; | |
71 | u8 hash_idx; | |
72 | u32 i; | |
73 | bool found; | |
74 | bool sta; | |
75 | u8 fifo; | |
76 | u8 *mac; | |
77 | ||
78 | fifo = brcmf_flowring_prio2fifo[prio]; | |
79 | sta = (flow->addr_mode[ifidx] == ADDR_INDIRECT); | |
80 | mac = da; | |
81 | if ((!sta) && (is_multicast_ether_addr(da))) { | |
82 | mac = (u8 *)ALLFFMAC; | |
83 | fifo = 0; | |
84 | } | |
70b7d94b HM |
85 | if ((sta) && (flow->tdls_active) && |
86 | (brcmf_flowring_is_tdls_mac(flow, da))) { | |
87 | sta = false; | |
88 | } | |
9a1bb602 HM |
89 | hash_idx = sta ? BRCMF_FLOWRING_HASH_STA(fifo, ifidx) : |
90 | BRCMF_FLOWRING_HASH_AP(mac, fifo, ifidx); | |
91 | found = false; | |
92 | hash = flow->hash; | |
93 | for (i = 0; i < BRCMF_FLOWRING_HASHSIZE; i++) { | |
94 | if ((sta || (memcmp(hash[hash_idx].mac, mac, ETH_ALEN) == 0)) && | |
95 | (hash[hash_idx].fifo == fifo) && | |
96 | (hash[hash_idx].ifidx == ifidx)) { | |
97 | found = true; | |
98 | break; | |
99 | } | |
100 | hash_idx++; | |
101 | } | |
102 | if (found) | |
103 | return hash[hash_idx].flowid; | |
104 | ||
105 | return BRCMF_FLOWRING_INVALID_ID; | |
106 | } | |
107 | ||
108 | ||
109 | u32 brcmf_flowring_create(struct brcmf_flowring *flow, u8 da[ETH_ALEN], | |
110 | u8 prio, u8 ifidx) | |
111 | { | |
112 | struct brcmf_flowring_ring *ring; | |
113 | struct brcmf_flowring_hash *hash; | |
114 | u8 hash_idx; | |
115 | u32 i; | |
116 | bool found; | |
117 | u8 fifo; | |
118 | bool sta; | |
119 | u8 *mac; | |
120 | ||
121 | fifo = brcmf_flowring_prio2fifo[prio]; | |
122 | sta = (flow->addr_mode[ifidx] == ADDR_INDIRECT); | |
123 | mac = da; | |
124 | if ((!sta) && (is_multicast_ether_addr(da))) { | |
125 | mac = (u8 *)ALLFFMAC; | |
126 | fifo = 0; | |
127 | } | |
70b7d94b HM |
128 | if ((sta) && (flow->tdls_active) && |
129 | (brcmf_flowring_is_tdls_mac(flow, da))) { | |
130 | sta = false; | |
131 | } | |
9a1bb602 HM |
132 | hash_idx = sta ? BRCMF_FLOWRING_HASH_STA(fifo, ifidx) : |
133 | BRCMF_FLOWRING_HASH_AP(mac, fifo, ifidx); | |
134 | found = false; | |
135 | hash = flow->hash; | |
136 | for (i = 0; i < BRCMF_FLOWRING_HASHSIZE; i++) { | |
70b7d94b | 137 | if ((hash[hash_idx].ifidx == BRCMF_FLOWRING_INVALID_IFIDX) && |
6b89dcb3 | 138 | (is_zero_ether_addr(hash[hash_idx].mac))) { |
9a1bb602 HM |
139 | found = true; |
140 | break; | |
141 | } | |
142 | hash_idx++; | |
143 | } | |
144 | if (found) { | |
145 | for (i = 0; i < flow->nrofrings; i++) { | |
146 | if (flow->rings[i] == NULL) | |
147 | break; | |
148 | } | |
149 | if (i == flow->nrofrings) | |
150 | return -ENOMEM; | |
151 | ||
152 | ring = kzalloc(sizeof(*ring), GFP_ATOMIC); | |
153 | if (!ring) | |
154 | return -ENOMEM; | |
155 | ||
156 | memcpy(hash[hash_idx].mac, mac, ETH_ALEN); | |
157 | hash[hash_idx].fifo = fifo; | |
158 | hash[hash_idx].ifidx = ifidx; | |
159 | hash[hash_idx].flowid = i; | |
160 | ||
161 | ring->hash_id = hash_idx; | |
162 | ring->status = RING_CLOSED; | |
163 | skb_queue_head_init(&ring->skblist); | |
164 | flow->rings[i] = ring; | |
165 | ||
166 | return i; | |
167 | } | |
168 | return BRCMF_FLOWRING_INVALID_ID; | |
169 | } | |
170 | ||
171 | ||
172 | u8 brcmf_flowring_tid(struct brcmf_flowring *flow, u8 flowid) | |
173 | { | |
174 | struct brcmf_flowring_ring *ring; | |
175 | ||
176 | ring = flow->rings[flowid]; | |
177 | ||
178 | return flow->hash[ring->hash_id].fifo; | |
179 | } | |
180 | ||
181 | ||
17ca5c71 HM |
182 | static void brcmf_flowring_block(struct brcmf_flowring *flow, u8 flowid, |
183 | bool blocked) | |
184 | { | |
185 | struct brcmf_flowring_ring *ring; | |
186 | struct brcmf_bus *bus_if; | |
187 | struct brcmf_pub *drvr; | |
188 | struct brcmf_if *ifp; | |
189 | bool currently_blocked; | |
190 | int i; | |
191 | u8 ifidx; | |
192 | unsigned long flags; | |
193 | ||
194 | spin_lock_irqsave(&flow->block_lock, flags); | |
195 | ||
196 | ring = flow->rings[flowid]; | |
197 | ifidx = brcmf_flowring_ifidx_get(flow, flowid); | |
198 | ||
199 | currently_blocked = false; | |
200 | for (i = 0; i < flow->nrofrings; i++) { | |
201 | if (flow->rings[i]) { | |
202 | ring = flow->rings[i]; | |
203 | if ((ring->status == RING_OPEN) && | |
204 | (brcmf_flowring_ifidx_get(flow, i) == ifidx)) { | |
205 | if (ring->blocked) { | |
206 | currently_blocked = true; | |
207 | break; | |
208 | } | |
209 | } | |
210 | } | |
211 | } | |
212 | ring->blocked = blocked; | |
213 | if (currently_blocked == blocked) { | |
214 | spin_unlock_irqrestore(&flow->block_lock, flags); | |
215 | return; | |
216 | } | |
217 | ||
218 | bus_if = dev_get_drvdata(flow->dev); | |
219 | drvr = bus_if->drvr; | |
220 | ifp = drvr->iflist[ifidx]; | |
221 | brcmf_txflowblock_if(ifp, BRCMF_NETIF_STOP_REASON_FLOW, blocked); | |
222 | ||
223 | spin_unlock_irqrestore(&flow->block_lock, flags); | |
224 | } | |
225 | ||
226 | ||
9a1bb602 HM |
227 | void brcmf_flowring_delete(struct brcmf_flowring *flow, u8 flowid) |
228 | { | |
229 | struct brcmf_flowring_ring *ring; | |
230 | u8 hash_idx; | |
231 | struct sk_buff *skb; | |
232 | ||
233 | ring = flow->rings[flowid]; | |
234 | if (!ring) | |
235 | return; | |
17ca5c71 | 236 | brcmf_flowring_block(flow, flowid, false); |
9a1bb602 HM |
237 | hash_idx = ring->hash_id; |
238 | flow->hash[hash_idx].ifidx = BRCMF_FLOWRING_INVALID_IFIDX; | |
93803b33 | 239 | eth_zero_addr(flow->hash[hash_idx].mac); |
9a1bb602 HM |
240 | flow->rings[flowid] = NULL; |
241 | ||
242 | skb = skb_dequeue(&ring->skblist); | |
243 | while (skb) { | |
244 | brcmu_pkt_buf_free_skb(skb); | |
245 | skb = skb_dequeue(&ring->skblist); | |
246 | } | |
247 | ||
248 | kfree(ring); | |
249 | } | |
250 | ||
251 | ||
252 | void brcmf_flowring_enqueue(struct brcmf_flowring *flow, u8 flowid, | |
253 | struct sk_buff *skb) | |
254 | { | |
255 | struct brcmf_flowring_ring *ring; | |
256 | ||
257 | ring = flow->rings[flowid]; | |
258 | ||
259 | skb_queue_tail(&ring->skblist, skb); | |
260 | ||
261 | if (!ring->blocked && | |
262 | (skb_queue_len(&ring->skblist) > BRCMF_FLOWRING_HIGH)) { | |
17ca5c71 | 263 | brcmf_flowring_block(flow, flowid, true); |
9a1bb602 | 264 | brcmf_dbg(MSGBUF, "Flowcontrol: BLOCK for ring %d\n", flowid); |
17ca5c71 HM |
265 | /* To prevent (work around) possible race condition, check |
266 | * queue len again. It is also possible to use locking to | |
267 | * protect, but that is undesirable for every enqueue and | |
268 | * dequeue. This simple check will solve a possible race | |
269 | * condition if it occurs. | |
270 | */ | |
271 | if (skb_queue_len(&ring->skblist) < BRCMF_FLOWRING_LOW) | |
272 | brcmf_flowring_block(flow, flowid, false); | |
9a1bb602 HM |
273 | } |
274 | } | |
275 | ||
276 | ||
277 | struct sk_buff *brcmf_flowring_dequeue(struct brcmf_flowring *flow, u8 flowid) | |
278 | { | |
279 | struct brcmf_flowring_ring *ring; | |
280 | struct sk_buff *skb; | |
281 | ||
282 | ring = flow->rings[flowid]; | |
283 | if (ring->status != RING_OPEN) | |
284 | return NULL; | |
285 | ||
286 | skb = skb_dequeue(&ring->skblist); | |
287 | ||
288 | if (ring->blocked && | |
289 | (skb_queue_len(&ring->skblist) < BRCMF_FLOWRING_LOW)) { | |
17ca5c71 | 290 | brcmf_flowring_block(flow, flowid, false); |
9a1bb602 | 291 | brcmf_dbg(MSGBUF, "Flowcontrol: OPEN for ring %d\n", flowid); |
9a1bb602 HM |
292 | } |
293 | ||
294 | return skb; | |
295 | } | |
296 | ||
297 | ||
298 | void brcmf_flowring_reinsert(struct brcmf_flowring *flow, u8 flowid, | |
299 | struct sk_buff *skb) | |
300 | { | |
301 | struct brcmf_flowring_ring *ring; | |
302 | ||
303 | ring = flow->rings[flowid]; | |
304 | ||
305 | skb_queue_head(&ring->skblist, skb); | |
306 | } | |
307 | ||
308 | ||
309 | u32 brcmf_flowring_qlen(struct brcmf_flowring *flow, u8 flowid) | |
310 | { | |
311 | struct brcmf_flowring_ring *ring; | |
312 | ||
313 | ring = flow->rings[flowid]; | |
314 | if (!ring) | |
315 | return 0; | |
316 | ||
317 | if (ring->status != RING_OPEN) | |
318 | return 0; | |
319 | ||
320 | return skb_queue_len(&ring->skblist); | |
321 | } | |
322 | ||
323 | ||
324 | void brcmf_flowring_open(struct brcmf_flowring *flow, u8 flowid) | |
325 | { | |
326 | struct brcmf_flowring_ring *ring; | |
327 | ||
328 | ring = flow->rings[flowid]; | |
329 | if (!ring) { | |
330 | brcmf_err("Ring NULL, for flowid %d\n", flowid); | |
331 | return; | |
332 | } | |
333 | ||
334 | ring->status = RING_OPEN; | |
335 | } | |
336 | ||
337 | ||
338 | u8 brcmf_flowring_ifidx_get(struct brcmf_flowring *flow, u8 flowid) | |
339 | { | |
340 | struct brcmf_flowring_ring *ring; | |
341 | u8 hash_idx; | |
342 | ||
343 | ring = flow->rings[flowid]; | |
344 | hash_idx = ring->hash_id; | |
345 | ||
346 | return flow->hash[hash_idx].ifidx; | |
347 | } | |
348 | ||
349 | ||
350 | struct brcmf_flowring *brcmf_flowring_attach(struct device *dev, u16 nrofrings) | |
351 | { | |
352 | struct brcmf_flowring *flow; | |
353 | u32 i; | |
354 | ||
3ba06610 | 355 | flow = kzalloc(sizeof(*flow), GFP_KERNEL); |
9a1bb602 HM |
356 | if (flow) { |
357 | flow->dev = dev; | |
358 | flow->nrofrings = nrofrings; | |
17ca5c71 | 359 | spin_lock_init(&flow->block_lock); |
9a1bb602 HM |
360 | for (i = 0; i < ARRAY_SIZE(flow->addr_mode); i++) |
361 | flow->addr_mode[i] = ADDR_INDIRECT; | |
362 | for (i = 0; i < ARRAY_SIZE(flow->hash); i++) | |
363 | flow->hash[i].ifidx = BRCMF_FLOWRING_INVALID_IFIDX; | |
364 | flow->rings = kcalloc(nrofrings, sizeof(*flow->rings), | |
3ba06610 | 365 | GFP_KERNEL); |
9a1bb602 HM |
366 | if (!flow->rings) { |
367 | kfree(flow); | |
368 | flow = NULL; | |
369 | } | |
370 | } | |
371 | ||
372 | return flow; | |
373 | } | |
374 | ||
375 | ||
376 | void brcmf_flowring_detach(struct brcmf_flowring *flow) | |
377 | { | |
378 | struct brcmf_bus *bus_if = dev_get_drvdata(flow->dev); | |
379 | struct brcmf_pub *drvr = bus_if->drvr; | |
70b7d94b HM |
380 | struct brcmf_flowring_tdls_entry *search; |
381 | struct brcmf_flowring_tdls_entry *remove; | |
9a1bb602 HM |
382 | u8 flowid; |
383 | ||
384 | for (flowid = 0; flowid < flow->nrofrings; flowid++) { | |
385 | if (flow->rings[flowid]) | |
386 | brcmf_msgbuf_delete_flowring(drvr, flowid); | |
387 | } | |
70b7d94b HM |
388 | |
389 | search = flow->tdls_entry; | |
390 | while (search) { | |
391 | remove = search; | |
392 | search = search->next; | |
393 | kfree(remove); | |
394 | } | |
9a1bb602 HM |
395 | kfree(flow->rings); |
396 | kfree(flow); | |
397 | } | |
398 | ||
399 | ||
400 | void brcmf_flowring_configure_addr_mode(struct brcmf_flowring *flow, int ifidx, | |
401 | enum proto_addr_mode addr_mode) | |
402 | { | |
403 | struct brcmf_bus *bus_if = dev_get_drvdata(flow->dev); | |
404 | struct brcmf_pub *drvr = bus_if->drvr; | |
405 | u32 i; | |
406 | u8 flowid; | |
407 | ||
408 | if (flow->addr_mode[ifidx] != addr_mode) { | |
409 | for (i = 0; i < ARRAY_SIZE(flow->hash); i++) { | |
410 | if (flow->hash[i].ifidx == ifidx) { | |
411 | flowid = flow->hash[i].flowid; | |
412 | if (flow->rings[flowid]->status != RING_OPEN) | |
413 | continue; | |
414 | flow->rings[flowid]->status = RING_CLOSING; | |
415 | brcmf_msgbuf_delete_flowring(drvr, flowid); | |
416 | } | |
417 | } | |
418 | flow->addr_mode[ifidx] = addr_mode; | |
419 | } | |
420 | } | |
421 | ||
422 | ||
423 | void brcmf_flowring_delete_peer(struct brcmf_flowring *flow, int ifidx, | |
424 | u8 peer[ETH_ALEN]) | |
425 | { | |
426 | struct brcmf_bus *bus_if = dev_get_drvdata(flow->dev); | |
427 | struct brcmf_pub *drvr = bus_if->drvr; | |
428 | struct brcmf_flowring_hash *hash; | |
70b7d94b HM |
429 | struct brcmf_flowring_tdls_entry *prev; |
430 | struct brcmf_flowring_tdls_entry *search; | |
9a1bb602 HM |
431 | u32 i; |
432 | u8 flowid; | |
433 | bool sta; | |
434 | ||
435 | sta = (flow->addr_mode[ifidx] == ADDR_INDIRECT); | |
70b7d94b HM |
436 | |
437 | search = flow->tdls_entry; | |
438 | prev = NULL; | |
439 | while (search) { | |
440 | if (memcmp(search->mac, peer, ETH_ALEN) == 0) { | |
441 | sta = false; | |
442 | break; | |
443 | } | |
444 | prev = search; | |
445 | search = search->next; | |
446 | } | |
447 | ||
9a1bb602 HM |
448 | hash = flow->hash; |
449 | for (i = 0; i < BRCMF_FLOWRING_HASHSIZE; i++) { | |
450 | if ((sta || (memcmp(hash[i].mac, peer, ETH_ALEN) == 0)) && | |
451 | (hash[i].ifidx == ifidx)) { | |
452 | flowid = flow->hash[i].flowid; | |
453 | if (flow->rings[flowid]->status == RING_OPEN) { | |
454 | flow->rings[flowid]->status = RING_CLOSING; | |
455 | brcmf_msgbuf_delete_flowring(drvr, flowid); | |
456 | } | |
457 | } | |
458 | } | |
70b7d94b HM |
459 | |
460 | if (search) { | |
461 | if (prev) | |
462 | prev->next = search->next; | |
463 | else | |
464 | flow->tdls_entry = search->next; | |
465 | kfree(search); | |
466 | if (flow->tdls_entry == NULL) | |
467 | flow->tdls_active = false; | |
468 | } | |
469 | } | |
470 | ||
471 | ||
472 | void brcmf_flowring_add_tdls_peer(struct brcmf_flowring *flow, int ifidx, | |
473 | u8 peer[ETH_ALEN]) | |
474 | { | |
475 | struct brcmf_flowring_tdls_entry *tdls_entry; | |
476 | struct brcmf_flowring_tdls_entry *search; | |
477 | ||
478 | tdls_entry = kzalloc(sizeof(*tdls_entry), GFP_ATOMIC); | |
479 | if (tdls_entry == NULL) | |
480 | return; | |
481 | ||
482 | memcpy(tdls_entry->mac, peer, ETH_ALEN); | |
483 | tdls_entry->next = NULL; | |
484 | if (flow->tdls_entry == NULL) { | |
485 | flow->tdls_entry = tdls_entry; | |
486 | } else { | |
487 | search = flow->tdls_entry; | |
488 | if (memcmp(search->mac, peer, ETH_ALEN) == 0) | |
489 | return; | |
490 | while (search->next) { | |
491 | search = search->next; | |
492 | if (memcmp(search->mac, peer, ETH_ALEN) == 0) | |
493 | return; | |
494 | } | |
495 | search->next = tdls_entry; | |
496 | } | |
497 | ||
498 | flow->tdls_active = true; | |
9a1bb602 | 499 | } |