xref: /OK3568_Linux_fs/kernel/drivers/net/wireless/rockchip_wlan/rkwifi/bcmdhd_indep_power/dhd_wlfc.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1 /* SPDX-License-Identifier: GPL-2.0 */
2 /*
3  * DHD PROP_TXSTATUS Module.
4  *
5  * Copyright (C) 1999-2017, Broadcom Corporation
6  *
7  *      Unless you and Broadcom execute a separate written software license
8  * agreement governing use of this software, this software is licensed to you
9  * under the terms of the GNU General Public License version 2 (the "GPL"),
10  * available at http://www.broadcom.com/licenses/GPLv2.php, with the
11  * following added to such license:
12  *
13  *      As a special exception, the copyright holders of this software give you
14  * permission to link this software with independent modules, and to copy and
15  * distribute the resulting executable under terms of your choice, provided that
16  * you also meet, for each linked independent module, the terms and conditions of
17  * the license of that module.  An independent module is a module which is not
18  * derived from this software.  The special exception does not apply to any
19  * modifications of the software.
20  *
21  *      Notwithstanding the above, under no circumstances may you combine this
22  * software in any way with any other Broadcom software provided under a license
23  * other than the GPL, without Broadcom's express prior written consent.
24  *
25  *
26  * <<Broadcom-WL-IPTag/Open:>>
27  *
28  * $Id: dhd_wlfc.c 679733 2017-01-17 06:40:39Z $
29  *
30  */
31 
32 
33 #include <typedefs.h>
34 #include <osl.h>
35 
36 #include <bcmutils.h>
37 #include <bcmendian.h>
38 
39 #include <dngl_stats.h>
40 #include <dhd.h>
41 
42 #include <dhd_bus.h>
43 
44 #include <dhd_dbg.h>
45 #include <dhd_config.h>
46 #include <wl_android.h>
47 
48 #ifdef PROP_TXSTATUS /* a form of flow control between host and dongle */
49 #include <wlfc_proto.h>
50 #include <dhd_wlfc.h>
51 #endif
52 
53 #ifdef DHDTCPACK_SUPPRESS
54 #include <dhd_ip.h>
55 #endif /* DHDTCPACK_SUPPRESS */
56 
57 
58 /*
59  * wlfc naming and lock rules:
60  *
61  * 1. Private functions name like _dhd_wlfc_XXX, declared as static and avoid wlfc lock operation.
62  * 2. Public functions name like dhd_wlfc_XXX, use wlfc lock if needed.
63  * 3. Non-Proptxstatus module call public functions only and avoid wlfc lock operation.
64  *
65  */
66 
67 #if defined(DHD_WLFC_THREAD)
68 #define WLFC_THREAD_QUICK_RETRY_WAIT_MS    10      /* 10 msec */
69 #define WLFC_THREAD_RETRY_WAIT_MS          10000   /* 10 sec */
70 #endif /* defined (DHD_WLFC_THREAD) */
71 
72 
73 #ifdef PROP_TXSTATUS
74 
75 #define DHD_WLFC_QMON_COMPLETE(entry)
76 
77 
78 /** reordering related */
79 
80 #if defined(DHD_WLFC_THREAD)
81 static void
_dhd_wlfc_thread_wakeup(dhd_pub_t * dhdp)82 _dhd_wlfc_thread_wakeup(dhd_pub_t *dhdp)
83 {
84 	dhdp->wlfc_thread_go = TRUE;
85 	wake_up_interruptible(&dhdp->wlfc_wqhead);
86 }
87 #endif /* DHD_WLFC_THREAD */
88 
89 static uint16
_dhd_wlfc_adjusted_seq(void * p,uint8 current_seq)90 _dhd_wlfc_adjusted_seq(void* p, uint8 current_seq)
91 {
92 	uint16 seq;
93 
94 	if (!p) {
95 		return 0xffff;
96 	}
97 
98 	seq = WL_TXSTATUS_GET_FREERUNCTR(DHD_PKTTAG_H2DTAG(PKTTAG(p)));
99 	if (seq < current_seq) {
100 		/* wrap around */
101 		seq += 256;
102 	}
103 
104 	return seq;
105 }
106 
107 /**
108  * Enqueue a caller supplied packet on a caller supplied precedence queue, optionally reorder
109  * suppressed packets.
110  *    @param[in] pq       caller supplied packet queue to enqueue the packet on
111  *    @param[in] prec     precedence of the to-be-queued packet
112  *    @param[in] p        transmit packet to enqueue
113  *    @param[in] qHead    if TRUE, enqueue to head instead of tail. Used to maintain d11 seq order.
114  *    @param[in] current_seq
115  *    @param[in] reOrder  reOrder on odd precedence (=suppress queue)
116  */
117 static void
_dhd_wlfc_prec_enque(struct pktq * pq,int prec,void * p,bool qHead,uint8 current_seq,bool reOrder)118 _dhd_wlfc_prec_enque(struct pktq *pq, int prec, void* p, bool qHead,
119 	uint8 current_seq, bool reOrder)
120 {
121 	struct pktq_prec *q;
122 	uint16 seq, seq2;
123 	void *p2, *p2_prev;
124 
125 	if (!p)
126 		return;
127 
128 	ASSERT(prec >= 0 && prec < pq->num_prec);
129 	ASSERT(PKTLINK(p) == NULL);		/* queueing chains not allowed */
130 
131 	ASSERT(!pktq_full(pq));
132 	ASSERT(!pktq_pfull(pq, prec));
133 
134 	q = &pq->q[prec];
135 
136 	PKTSETLINK(p, NULL);
137 	if (q->head == NULL) {
138 		/* empty queue */
139 		q->head = p;
140 		q->tail = p;
141 	} else {
142 		if (reOrder && (prec & 1)) {
143 			seq = _dhd_wlfc_adjusted_seq(p, current_seq);
144 			p2 = qHead ? q->head : q->tail;
145 			seq2 = _dhd_wlfc_adjusted_seq(p2, current_seq);
146 
147 			if ((qHead &&((seq+1) > seq2)) || (!qHead && ((seq2+1) > seq))) {
148 				/* need reorder */
149 				p2 = q->head;
150 				p2_prev = NULL;
151 				seq2 = _dhd_wlfc_adjusted_seq(p2, current_seq);
152 
153 				while (seq > seq2) {
154 					p2_prev = p2;
155 					p2 = PKTLINK(p2);
156 					if (!p2) {
157 						break;
158 					}
159 					seq2 = _dhd_wlfc_adjusted_seq(p2, current_seq);
160 				}
161 
162 				if (p2_prev == NULL) {
163 					/* insert head */
164 					PKTSETLINK(p, q->head);
165 					q->head = p;
166 				} else if (p2 == NULL) {
167 					/* insert tail */
168 					PKTSETLINK(p2_prev, p);
169 					q->tail = p;
170 				} else {
171 					/* insert after p2_prev */
172 					PKTSETLINK(p, PKTLINK(p2_prev));
173 					PKTSETLINK(p2_prev, p);
174 				}
175 				goto exit;
176 			}
177 		}
178 
179 		if (qHead) {
180 			PKTSETLINK(p, q->head);
181 			q->head = p;
182 		} else {
183 			PKTSETLINK(q->tail, p);
184 			q->tail = p;
185 		}
186 	}
187 
188 exit:
189 
190 	q->len++;
191 	pq->len++;
192 
193 	if (pq->hi_prec < prec)
194 		pq->hi_prec = (uint8)prec;
195 } /* _dhd_wlfc_prec_enque */
196 
197 /**
198  * Create a place to store all packet pointers submitted to the firmware until a status comes back,
199  * suppress or otherwise.
200  *
201  * hang-er: noun, a contrivance on which things are hung, as a hook.
202  */
203 /** @deprecated soon */
204 static void*
_dhd_wlfc_hanger_create(dhd_pub_t * dhd,int max_items)205 _dhd_wlfc_hanger_create(dhd_pub_t *dhd, int max_items)
206 {
207 	int i;
208 	wlfc_hanger_t* hanger;
209 
210 	/* allow only up to a specific size for now */
211 	ASSERT(max_items == WLFC_HANGER_MAXITEMS);
212 
213 	if ((hanger = (wlfc_hanger_t*)DHD_OS_PREALLOC(dhd, DHD_PREALLOC_DHD_WLFC_HANGER,
214 		WLFC_HANGER_SIZE(max_items))) == NULL) {
215 		return NULL;
216 	}
217 	memset(hanger, 0, WLFC_HANGER_SIZE(max_items));
218 	hanger->max_items = max_items;
219 
220 	for (i = 0; i < hanger->max_items; i++) {
221 		hanger->items[i].state = WLFC_HANGER_ITEM_STATE_FREE;
222 	}
223 	return hanger;
224 }
225 
226 /** @deprecated soon */
227 static int
_dhd_wlfc_hanger_delete(dhd_pub_t * dhd,void * hanger)228 _dhd_wlfc_hanger_delete(dhd_pub_t *dhd, void* hanger)
229 {
230 	wlfc_hanger_t* h = (wlfc_hanger_t*)hanger;
231 
232 	if (h) {
233 		DHD_OS_PREFREE(dhd, h, WLFC_HANGER_SIZE(h->max_items));
234 		return BCME_OK;
235 	}
236 	return BCME_BADARG;
237 }
238 
239 /** @deprecated soon */
240 static uint16
_dhd_wlfc_hanger_get_free_slot(void * hanger)241 _dhd_wlfc_hanger_get_free_slot(void* hanger)
242 {
243 	uint32 i;
244 	wlfc_hanger_t* h = (wlfc_hanger_t*)hanger;
245 
246 	if (h) {
247 		i = h->slot_pos + 1;
248 		if (i == h->max_items) {
249 			i = 0;
250 		}
251 		while (i != h->slot_pos) {
252 			if (h->items[i].state == WLFC_HANGER_ITEM_STATE_FREE) {
253 				h->slot_pos = i;
254 				return (uint16)i;
255 			}
256 			i++;
257 			if (i == h->max_items)
258 				i = 0;
259 		}
260 		h->failed_slotfind++;
261 	}
262 	return WLFC_HANGER_MAXITEMS;
263 }
264 
265 /** @deprecated soon */
266 static int
_dhd_wlfc_hanger_get_genbit(void * hanger,void * pkt,uint32 slot_id,int * gen)267 _dhd_wlfc_hanger_get_genbit(void* hanger, void* pkt, uint32 slot_id, int* gen)
268 {
269 	int rc = BCME_OK;
270 	wlfc_hanger_t* h = (wlfc_hanger_t*)hanger;
271 
272 	*gen = 0xff;
273 
274 	/* this packet was not pushed at the time it went to the firmware */
275 	if (slot_id == WLFC_HANGER_MAXITEMS)
276 		return BCME_NOTFOUND;
277 
278 	if (h) {
279 		if (h->items[slot_id].state != WLFC_HANGER_ITEM_STATE_FREE) {
280 			*gen = h->items[slot_id].gen;
281 		}
282 		else {
283 			DHD_ERROR(("Error: %s():%d item not used\n",
284 				__FUNCTION__, __LINE__));
285 			rc = BCME_NOTFOUND;
286 		}
287 
288 	} else {
289 		rc = BCME_BADARG;
290 	}
291 
292 	return rc;
293 }
294 
295 /** @deprecated soon */
296 static int
_dhd_wlfc_hanger_pushpkt(void * hanger,void * pkt,uint32 slot_id)297 _dhd_wlfc_hanger_pushpkt(void* hanger, void* pkt, uint32 slot_id)
298 {
299 	int rc = BCME_OK;
300 	wlfc_hanger_t* h = (wlfc_hanger_t*)hanger;
301 
302 	if (h && (slot_id < WLFC_HANGER_MAXITEMS)) {
303 		if (h->items[slot_id].state == WLFC_HANGER_ITEM_STATE_FREE) {
304 			h->items[slot_id].state = WLFC_HANGER_ITEM_STATE_INUSE;
305 			h->items[slot_id].pkt = pkt;
306 			h->items[slot_id].pkt_state = 0;
307 			h->items[slot_id].pkt_txstatus = 0;
308 			h->pushed++;
309 		} else {
310 			h->failed_to_push++;
311 			rc = BCME_NOTFOUND;
312 		}
313 	} else {
314 		rc = BCME_BADARG;
315 	}
316 
317 	return rc;
318 }
319 
320 /** @deprecated soon */
321 static int
_dhd_wlfc_hanger_poppkt(void * hanger,uint32 slot_id,void ** pktout,bool remove_from_hanger)322 _dhd_wlfc_hanger_poppkt(void* hanger, uint32 slot_id, void** pktout, bool remove_from_hanger)
323 {
324 	int rc = BCME_OK;
325 	wlfc_hanger_t* h = (wlfc_hanger_t*)hanger;
326 
327 	*pktout = NULL;
328 
329 	/* this packet was not pushed at the time it went to the firmware */
330 	if (slot_id == WLFC_HANGER_MAXITEMS)
331 		return BCME_NOTFOUND;
332 
333 	if (h) {
334 		if (h->items[slot_id].state != WLFC_HANGER_ITEM_STATE_FREE) {
335 			*pktout = h->items[slot_id].pkt;
336 			if (remove_from_hanger) {
337 				h->items[slot_id].state =
338 					WLFC_HANGER_ITEM_STATE_FREE;
339 				h->items[slot_id].pkt = NULL;
340 				h->items[slot_id].gen = 0xff;
341 				h->items[slot_id].identifier = 0;
342 				h->popped++;
343 			}
344 		} else {
345 			h->failed_to_pop++;
346 			rc = BCME_NOTFOUND;
347 		}
348 	} else {
349 		rc = BCME_BADARG;
350 	}
351 
352 	return rc;
353 }
354 
355 /** @deprecated soon */
356 static int
_dhd_wlfc_hanger_mark_suppressed(void * hanger,uint32 slot_id,uint8 gen)357 _dhd_wlfc_hanger_mark_suppressed(void* hanger, uint32 slot_id, uint8 gen)
358 {
359 	int rc = BCME_OK;
360 	wlfc_hanger_t* h = (wlfc_hanger_t*)hanger;
361 
362 	/* this packet was not pushed at the time it went to the firmware */
363 	if (slot_id == WLFC_HANGER_MAXITEMS)
364 		return BCME_NOTFOUND;
365 	if (h) {
366 		h->items[slot_id].gen = gen;
367 		if (h->items[slot_id].state == WLFC_HANGER_ITEM_STATE_INUSE) {
368 			h->items[slot_id].state = WLFC_HANGER_ITEM_STATE_INUSE_SUPPRESSED;
369 		} else {
370 			rc = BCME_BADARG;
371 		}
372 	} else {
373 		rc = BCME_BADARG;
374 	}
375 
376 	return rc;
377 }
378 
379 /** remove reference of specific packet in hanger */
380 /** @deprecated soon */
381 static bool
_dhd_wlfc_hanger_remove_reference(wlfc_hanger_t * h,void * pkt)382 _dhd_wlfc_hanger_remove_reference(wlfc_hanger_t* h, void* pkt)
383 {
384 	int i;
385 
386 	if (!h || !pkt) {
387 		return FALSE;
388 	}
389 
390 	i = WL_TXSTATUS_GET_HSLOT(DHD_PKTTAG_H2DTAG(PKTTAG(pkt)));
391 
392 	if ((i < h->max_items) && (pkt == h->items[i].pkt)) {
393 		if (h->items[i].state == WLFC_HANGER_ITEM_STATE_INUSE_SUPPRESSED) {
394 			h->items[i].state = WLFC_HANGER_ITEM_STATE_FREE;
395 			h->items[i].pkt = NULL;
396 			h->items[i].gen = 0xff;
397 			h->items[i].identifier = 0;
398 			return TRUE;
399 		} else {
400 			DHD_ERROR(("Error: %s():%d item not suppressed\n",
401 				__FUNCTION__, __LINE__));
402 		}
403 	}
404 
405 	return FALSE;
406 }
407 
408 /** afq = At Firmware Queue, queue containing packets pending in the dongle */
409 static int
_dhd_wlfc_enque_afq(athost_wl_status_info_t * ctx,void * p)410 _dhd_wlfc_enque_afq(athost_wl_status_info_t* ctx, void *p)
411 {
412 	wlfc_mac_descriptor_t* entry;
413 	uint16 entry_idx = WL_TXSTATUS_GET_HSLOT(DHD_PKTTAG_H2DTAG(PKTTAG(p)));
414 	uint8 prec = DHD_PKTTAG_FIFO(PKTTAG(p));
415 
416 	if (entry_idx < WLFC_MAC_DESC_TABLE_SIZE)
417 		entry  = &ctx->destination_entries.nodes[entry_idx];
418 	else if (entry_idx < (WLFC_MAC_DESC_TABLE_SIZE + WLFC_MAX_IFNUM))
419 		entry = &ctx->destination_entries.interfaces[entry_idx - WLFC_MAC_DESC_TABLE_SIZE];
420 	else
421 		entry = &ctx->destination_entries.other;
422 
423 	pktq_penq(&entry->afq, prec, p);
424 
425 	return BCME_OK;
426 }
427 
428 /** afq = At Firmware Queue, queue containing packets pending in the dongle */
429 static int
_dhd_wlfc_deque_afq(athost_wl_status_info_t * ctx,uint16 hslot,uint8 hcnt,uint8 prec,void ** pktout)430 _dhd_wlfc_deque_afq(athost_wl_status_info_t* ctx, uint16 hslot, uint8 hcnt, uint8 prec,
431 	void **pktout)
432 {
433 	wlfc_mac_descriptor_t *entry;
434 	struct pktq *pq;
435 	struct pktq_prec *q;
436 	void *p, *b;
437 
438 	if (!ctx) {
439 		DHD_ERROR(("%s: ctx(%p), pktout(%p)\n", __FUNCTION__, ctx, pktout));
440 		return BCME_BADARG;
441 	}
442 
443 	if (pktout) {
444 		*pktout = NULL;
445 	}
446 
447 	ASSERT(hslot < (WLFC_MAC_DESC_TABLE_SIZE + WLFC_MAX_IFNUM + 1));
448 
449 	if (hslot < WLFC_MAC_DESC_TABLE_SIZE)
450 		entry  = &ctx->destination_entries.nodes[hslot];
451 	else if (hslot < (WLFC_MAC_DESC_TABLE_SIZE + WLFC_MAX_IFNUM))
452 		entry = &ctx->destination_entries.interfaces[hslot - WLFC_MAC_DESC_TABLE_SIZE];
453 	else
454 		entry = &ctx->destination_entries.other;
455 
456 	pq = &entry->afq;
457 
458 	ASSERT(prec < pq->num_prec);
459 
460 	q = &pq->q[prec];
461 
462 	b = NULL;
463 	p = q->head;
464 
465 	while (p && (hcnt != WL_TXSTATUS_GET_FREERUNCTR(DHD_PKTTAG_H2DTAG(PKTTAG(p)))))
466 	{
467 		b = p;
468 		p = PKTLINK(p);
469 	}
470 
471 	if (p == NULL) {
472 		/* none is matched */
473 		if (b) {
474 			DHD_ERROR(("%s: can't find matching seq(%d)\n", __FUNCTION__, hcnt));
475 		} else {
476 			DHD_ERROR(("%s: queue is empty\n", __FUNCTION__));
477 		}
478 
479 		return BCME_ERROR;
480 	}
481 
482 	bcm_pkt_validate_chk(p);
483 
484 	if (!b) {
485 		/* head packet is matched */
486 		if ((q->head = PKTLINK(p)) == NULL) {
487 			q->tail = NULL;
488 		}
489 	} else {
490 		/* middle packet is matched */
491 		DHD_INFO(("%s: out of order, seq(%d), head_seq(%d)\n", __FUNCTION__, hcnt,
492 			WL_TXSTATUS_GET_FREERUNCTR(DHD_PKTTAG_H2DTAG(PKTTAG(q->head)))));
493 		ctx->stats.ooo_pkts[prec]++;
494 		PKTSETLINK(b, PKTLINK(p));
495 		if (PKTLINK(p) == NULL) {
496 			q->tail = b;
497 		}
498 	}
499 
500 	q->len--;
501 	pq->len--;
502 
503 	PKTSETLINK(p, NULL);
504 
505 	if (pktout) {
506 		*pktout = p;
507 	}
508 
509 	return BCME_OK;
510 } /* _dhd_wlfc_deque_afq */
511 
512 /**
513  * Flow control information piggy backs on packets, in the form of one or more TLVs. This function
514  * pushes one or more TLVs onto a packet that is going to be sent towards the dongle.
515  *
516  *     @param[in]     ctx
517  *     @param[in/out] packet
518  *     @param[in]     tim_signal TRUE if parameter 'tim_bmp' is valid
519  *     @param[in]     tim_bmp
520  *     @param[in]     mac_handle
521  *     @param[in]     htodtag
522  *     @param[in]     htodseq d11 seqno for seqno reuse, only used if 'seq reuse' was agreed upon
523  *                    earlier between host and firmware.
524  *     @param[in]     skip_wlfc_hdr
525  */
526 static int
_dhd_wlfc_pushheader(athost_wl_status_info_t * ctx,void ** packet,bool tim_signal,uint8 tim_bmp,uint8 mac_handle,uint32 htodtag,uint16 htodseq,bool skip_wlfc_hdr)527 _dhd_wlfc_pushheader(athost_wl_status_info_t* ctx, void** packet, bool tim_signal,
528 	uint8 tim_bmp, uint8 mac_handle, uint32 htodtag, uint16 htodseq, bool skip_wlfc_hdr)
529 {
530 	uint32 wl_pktinfo = 0;
531 	uint8* wlh;
532 	uint8 dataOffset = 0;
533 	uint8 fillers;
534 	uint8 tim_signal_len = 0;
535 	dhd_pub_t *dhdp = (dhd_pub_t *)ctx->dhdp;
536 
537 	struct bdc_header *h;
538 	void *p = *packet;
539 
540 	if (skip_wlfc_hdr)
541 		goto push_bdc_hdr;
542 
543 	if (tim_signal) {
544 		tim_signal_len = TLV_HDR_LEN + WLFC_CTL_VALUE_LEN_PENDING_TRAFFIC_BMP;
545 	}
546 
547 	/* +2 is for Type[1] and Len[1] in TLV, plus TIM signal */
548 	dataOffset = WLFC_CTL_VALUE_LEN_PKTTAG + TLV_HDR_LEN + tim_signal_len;
549 	if (WLFC_GET_REUSESEQ(dhdp->wlfc_mode)) {
550 		dataOffset += WLFC_CTL_VALUE_LEN_SEQ;
551 	}
552 
553 	fillers = ROUNDUP(dataOffset, 4) - dataOffset;
554 	dataOffset += fillers;
555 
556 	PKTPUSH(ctx->osh, p, dataOffset);
557 	wlh = (uint8*) PKTDATA(ctx->osh, p);
558 
559 	wl_pktinfo = htol32(htodtag);
560 
561 	wlh[TLV_TAG_OFF] = WLFC_CTL_TYPE_PKTTAG;
562 	wlh[TLV_LEN_OFF] = WLFC_CTL_VALUE_LEN_PKTTAG;
563 	memcpy(&wlh[TLV_HDR_LEN] /* dst */, &wl_pktinfo, sizeof(uint32));
564 
565 	if (WLFC_GET_REUSESEQ(dhdp->wlfc_mode)) {
566 		uint16 wl_seqinfo = htol16(htodseq);
567 		wlh[TLV_LEN_OFF] += WLFC_CTL_VALUE_LEN_SEQ;
568 		memcpy(&wlh[TLV_HDR_LEN + WLFC_CTL_VALUE_LEN_PKTTAG], &wl_seqinfo,
569 			WLFC_CTL_VALUE_LEN_SEQ);
570 	}
571 
572 	if (tim_signal_len) {
573 		wlh[dataOffset - fillers - tim_signal_len ] =
574 			WLFC_CTL_TYPE_PENDING_TRAFFIC_BMP;
575 		wlh[dataOffset - fillers - tim_signal_len + 1] =
576 			WLFC_CTL_VALUE_LEN_PENDING_TRAFFIC_BMP;
577 		wlh[dataOffset - fillers - tim_signal_len + 2] = mac_handle;
578 		wlh[dataOffset - fillers - tim_signal_len + 3] = tim_bmp;
579 	}
580 	if (fillers)
581 		memset(&wlh[dataOffset - fillers], WLFC_CTL_TYPE_FILLER, fillers);
582 
583 push_bdc_hdr:
584 	PKTPUSH(ctx->osh, p, BDC_HEADER_LEN);
585 	h = (struct bdc_header *)PKTDATA(ctx->osh, p);
586 	h->flags = (BDC_PROTO_VER << BDC_FLAG_VER_SHIFT);
587 	if (PKTSUMNEEDED(p))
588 		h->flags |= BDC_FLAG_SUM_NEEDED;
589 
590 
591 	h->priority = (PKTPRIO(p) & BDC_PRIORITY_MASK);
592 	h->flags2 = 0;
593 	h->dataOffset = dataOffset >> 2;
594 	BDC_SET_IF_IDX(h, DHD_PKTTAG_IF(PKTTAG(p)));
595 	*packet = p;
596 	return BCME_OK;
597 } /* _dhd_wlfc_pushheader */
598 
599 /**
600  * Removes (PULLs) flow control related headers from the caller supplied packet, is invoked eg
601  * when a packet is about to be freed.
602  */
603 static int
_dhd_wlfc_pullheader(athost_wl_status_info_t * ctx,void * pktbuf)604 _dhd_wlfc_pullheader(athost_wl_status_info_t* ctx, void* pktbuf)
605 {
606 	struct bdc_header *h;
607 
608 	if (PKTLEN(ctx->osh, pktbuf) < BDC_HEADER_LEN) {
609 		DHD_ERROR(("%s: rx data too short (%d < %d)\n", __FUNCTION__,
610 		           PKTLEN(ctx->osh, pktbuf), BDC_HEADER_LEN));
611 		return BCME_ERROR;
612 	}
613 	h = (struct bdc_header *)PKTDATA(ctx->osh, pktbuf);
614 
615 	/* pull BDC header */
616 	PKTPULL(ctx->osh, pktbuf, BDC_HEADER_LEN);
617 
618 	if (PKTLEN(ctx->osh, pktbuf) < (uint)(h->dataOffset << 2)) {
619 		DHD_ERROR(("%s: rx data too short (%d < %d)\n", __FUNCTION__,
620 		           PKTLEN(ctx->osh, pktbuf), (h->dataOffset << 2)));
621 		return BCME_ERROR;
622 	}
623 
624 	/* pull wl-header */
625 	PKTPULL(ctx->osh, pktbuf, (h->dataOffset << 2));
626 	return BCME_OK;
627 }
628 
629 /**
630  * @param[in/out] p packet
631  */
632 static wlfc_mac_descriptor_t*
_dhd_wlfc_find_table_entry(athost_wl_status_info_t * ctx,void * p)633 _dhd_wlfc_find_table_entry(athost_wl_status_info_t* ctx, void* p)
634 {
635 	int i;
636 	wlfc_mac_descriptor_t* table = ctx->destination_entries.nodes;
637 	uint8 ifid = DHD_PKTTAG_IF(PKTTAG(p));
638 	uint8* dstn = DHD_PKTTAG_DSTN(PKTTAG(p));
639 	wlfc_mac_descriptor_t* entry = DHD_PKTTAG_ENTRY(PKTTAG(p));
640 	int iftype = ctx->destination_entries.interfaces[ifid].iftype;
641 
642 	/* saved one exists, return it */
643 	if (entry)
644 		return entry;
645 
646 	/* Multicast destination, STA and P2P clients get the interface entry.
647 	 * STA/GC gets the Mac Entry for TDLS destinations, TDLS destinations
648 	 * have their own entry.
649 	 */
650 	if ((iftype == WLC_E_IF_ROLE_STA || ETHER_ISMULTI(dstn) ||
651 		iftype == WLC_E_IF_ROLE_P2P_CLIENT) &&
652 		(ctx->destination_entries.interfaces[ifid].occupied)) {
653 			entry = &ctx->destination_entries.interfaces[ifid];
654 	}
655 
656 	if (entry && ETHER_ISMULTI(dstn)) {
657 		DHD_PKTTAG_SET_ENTRY(PKTTAG(p), entry);
658 		return entry;
659 	}
660 
661 	for (i = 0; i < WLFC_MAC_DESC_TABLE_SIZE; i++) {
662 		if (table[i].occupied) {
663 			if (table[i].interface_id == ifid) {
664 				if (!memcmp(table[i].ea, dstn, ETHER_ADDR_LEN)) {
665 					entry = &table[i];
666 					break;
667 				}
668 			}
669 		}
670 	}
671 
672 	if (entry == NULL)
673 		entry = &ctx->destination_entries.other;
674 
675 	DHD_PKTTAG_SET_ENTRY(PKTTAG(p), entry);
676 
677 	return entry;
678 } /* _dhd_wlfc_find_table_entry */
679 
680 /**
681  * In case a packet must be dropped (because eg the queues are full), various tallies have to be
682  * be updated. Called from several other functions.
683  *     @param[in] dhdp pointer to public DHD structure
684  *     @param[in] prec precedence of the packet
685  *     @param[in] p    the packet to be dropped
686  *     @param[in] bPktInQ TRUE if packet is part of a queue
687  */
688 static int
_dhd_wlfc_prec_drop(dhd_pub_t * dhdp,int prec,void * p,bool bPktInQ)689 _dhd_wlfc_prec_drop(dhd_pub_t *dhdp, int prec, void* p, bool bPktInQ)
690 {
691 	athost_wl_status_info_t* ctx;
692 	void *pout = NULL;
693 
694 	ASSERT(dhdp && p);
695 	ASSERT(prec >= 0 && prec <= WLFC_PSQ_PREC_COUNT);
696 
697 	ctx = (athost_wl_status_info_t*)dhdp->wlfc_state;
698 
699 	if (!WLFC_GET_AFQ(dhdp->wlfc_mode) && (prec & 1)) {
700 		/* suppressed queue, need pop from hanger */
701 		_dhd_wlfc_hanger_poppkt(ctx->hanger, WL_TXSTATUS_GET_HSLOT(DHD_PKTTAG_H2DTAG
702 					(PKTTAG(p))), &pout, TRUE);
703 		ASSERT(p == pout);
704 	}
705 
706 	if (!(prec & 1)) {
707 #ifdef DHDTCPACK_SUPPRESS
708 		/* pkt in delayed q, so fake push BDC header for
709 		 * dhd_tcpack_check_xmit() and dhd_txcomplete().
710 		 */
711 		_dhd_wlfc_pushheader(ctx, &p, FALSE, 0, 0, 0, 0, TRUE);
712 
713 		/* This packet is about to be freed, so remove it from tcp_ack_info_tbl
714 		 * This must be one of...
715 		 * 1. A pkt already in delayQ is evicted by another pkt with higher precedence
716 		 * in _dhd_wlfc_prec_enq_with_drop()
717 		 * 2. A pkt could not be enqueued to delayQ because it is full,
718 		 * in _dhd_wlfc_enque_delayq().
719 		 * 3. A pkt could not be enqueued to delayQ because it is full,
720 		 * in _dhd_wlfc_rollback_packet_toq().
721 		 */
722 		if (dhd_tcpack_check_xmit(dhdp, p) == BCME_ERROR) {
723 			DHD_ERROR(("%s %d: tcpack_suppress ERROR!!!"
724 				" Stop using it\n",
725 				__FUNCTION__, __LINE__));
726 			dhd_tcpack_suppress_set(dhdp, TCPACK_SUP_OFF);
727 		}
728 #endif /* DHDTCPACK_SUPPRESS */
729 	}
730 
731 	if (bPktInQ) {
732 		ctx->pkt_cnt_in_q[DHD_PKTTAG_IF(PKTTAG(p))][prec>>1]--;
733 		ctx->pkt_cnt_per_ac[prec>>1]--;
734 		ctx->pkt_cnt_in_psq--;
735 	}
736 
737 	ctx->pkt_cnt_in_drv[DHD_PKTTAG_IF(PKTTAG(p))][DHD_PKTTAG_FIFO(PKTTAG(p))]--;
738 	ctx->stats.pktout++;
739 	ctx->stats.drop_pkts[prec]++;
740 
741 	dhd_txcomplete(dhdp, p, FALSE);
742 	PKTFREE(ctx->osh, p, TRUE);
743 
744 	return 0;
745 } /* _dhd_wlfc_prec_drop */
746 
747 /**
748  * Called when eg the host handed a new packet over to the driver, or when the dongle reported
749  * that a packet could currently not be transmitted (=suppressed). This function enqueues a transmit
750  * packet in the host driver to be (re)transmitted at a later opportunity.
751  *     @param[in] dhdp pointer to public DHD structure
752  *     @param[in] qHead When TRUE, queue packet at head instead of tail, to preserve d11 sequence
753  */
754 static bool
_dhd_wlfc_prec_enq_with_drop(dhd_pub_t * dhdp,struct pktq * pq,void * pkt,int prec,bool qHead,uint8 current_seq)755 _dhd_wlfc_prec_enq_with_drop(dhd_pub_t *dhdp, struct pktq *pq, void *pkt, int prec, bool qHead,
756 	uint8 current_seq)
757 {
758 	void *p = NULL;
759 	int eprec = -1;		/* precedence to evict from */
760 	athost_wl_status_info_t* ctx;
761 
762 	ASSERT(dhdp && pq && pkt);
763 	ASSERT(prec >= 0 && prec < pq->num_prec);
764 
765 	ctx = (athost_wl_status_info_t*)dhdp->wlfc_state;
766 
767 	/* Fast case, precedence queue is not full and we are also not
768 	 * exceeding total queue length
769 	 */
770 	if (!pktq_pfull(pq, prec) && !pktq_full(pq)) {
771 		goto exit;
772 	}
773 
774 	/* Determine precedence from which to evict packet, if any */
775 	if (pktq_pfull(pq, prec)) {
776 		eprec = prec;
777 	} else if (pktq_full(pq)) {
778 		p = pktq_peek_tail(pq, &eprec);
779 		if (!p) {
780 			DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
781 			return FALSE;
782 		}
783 		if ((eprec > prec) || (eprec < 0)) {
784 			if (!pktq_pempty(pq, prec)) {
785 				eprec = prec;
786 			} else {
787 				return FALSE;
788 			}
789 		}
790 	}
791 
792 	/* Evict if needed */
793 	if (eprec >= 0) {
794 		/* Detect queueing to unconfigured precedence */
795 		ASSERT(!pktq_pempty(pq, eprec));
796 		/* Evict all fragmented frames */
797 		dhd_prec_drop_pkts(dhdp, pq, eprec, _dhd_wlfc_prec_drop);
798 	}
799 
800 exit:
801 	/* Enqueue */
802 	_dhd_wlfc_prec_enque(pq, prec, pkt, qHead, current_seq,
803 		WLFC_GET_REORDERSUPP(dhdp->wlfc_mode));
804 	ctx->pkt_cnt_in_q[DHD_PKTTAG_IF(PKTTAG(pkt))][prec>>1]++;
805 	ctx->pkt_cnt_per_ac[prec>>1]++;
806 	ctx->pkt_cnt_in_psq++;
807 
808 	return TRUE;
809 } /* _dhd_wlfc_prec_enq_with_drop */
810 
811 /**
812  * Called during eg the 'committing' of a transmit packet from the OS layer to a lower layer, in
813  * the event that this 'commit' failed.
814  */
815 static int
_dhd_wlfc_rollback_packet_toq(athost_wl_status_info_t * ctx,void * p,ewlfc_packet_state_t pkt_type,uint32 hslot)816 _dhd_wlfc_rollback_packet_toq(athost_wl_status_info_t* ctx,
817 	void* p, ewlfc_packet_state_t pkt_type, uint32 hslot)
818 {
819 	/*
820 	 * put the packet back to the head of queue
821 	 * - suppressed packet goes back to suppress sub-queue
822 	 * - pull out the header, if new or delayed packet
823 	 *
824 	 * Note: hslot is used only when header removal is done.
825 	 */
826 	wlfc_mac_descriptor_t* entry;
827 	int rc = BCME_OK;
828 	int prec, fifo_id;
829 
830 	entry = _dhd_wlfc_find_table_entry(ctx, p);
831 	prec = DHD_PKTTAG_FIFO(PKTTAG(p));
832 	fifo_id = prec << 1;
833 	if (pkt_type == eWLFC_PKTTYPE_SUPPRESSED)
834 		fifo_id += 1;
835 	if (entry != NULL) {
836 		/*
837 		if this packet did not count against FIFO credit, it must have
838 		taken a requested_credit from the firmware (for pspoll etc.)
839 		*/
840 		if ((prec != AC_COUNT) && !DHD_PKTTAG_CREDITCHECK(PKTTAG(p)))
841 			entry->requested_credit++;
842 
843 		if (pkt_type == eWLFC_PKTTYPE_DELAYED) {
844 			/* decrement sequence count */
845 			WLFC_DECR_SEQCOUNT(entry, prec);
846 			/* remove header first */
847 			rc = _dhd_wlfc_pullheader(ctx, p);
848 			if (rc != BCME_OK) {
849 				DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
850 				goto exit;
851 			}
852 		}
853 
854 		if (_dhd_wlfc_prec_enq_with_drop(ctx->dhdp, &entry->psq, p, fifo_id, TRUE,
855 			WLFC_SEQCOUNT(entry, fifo_id>>1))
856 			== FALSE) {
857 			/* enque failed */
858 			DHD_ERROR(("Error: %s():%d, fifo_id(%d)\n",
859 				__FUNCTION__, __LINE__, fifo_id));
860 			rc = BCME_ERROR;
861 		}
862 	} else {
863 		DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
864 		rc = BCME_ERROR;
865 	}
866 
867 exit:
868 	if (rc != BCME_OK) {
869 		ctx->stats.rollback_failed++;
870 		_dhd_wlfc_prec_drop(ctx->dhdp, fifo_id, p, FALSE);
871 	} else {
872 		ctx->stats.rollback++;
873 	}
874 
875 	return rc;
876 } /* _dhd_wlfc_rollback_packet_toq */
877 
878 /** Returns TRUE if host OS -> DHD flow control is allowed on the caller supplied interface */
879 static bool
_dhd_wlfc_allow_fc(athost_wl_status_info_t * ctx,uint8 ifid)880 _dhd_wlfc_allow_fc(athost_wl_status_info_t* ctx, uint8 ifid)
881 {
882 	int prec, ac_traffic = WLFC_NO_TRAFFIC;
883 
884 	for (prec = 0; prec < AC_COUNT; prec++) {
885 		if (ctx->pkt_cnt_in_drv[ifid][prec] > 0) {
886 			if (ac_traffic == WLFC_NO_TRAFFIC)
887 				ac_traffic = prec + 1;
888 			else if (ac_traffic != (prec + 1))
889 				ac_traffic = WLFC_MULTI_TRAFFIC;
890 		}
891 	}
892 
893 	if (ac_traffic >= 1 && ac_traffic <= AC_COUNT) {
894 		/* single AC (BE/BK/VI/VO) in queue */
895 		if (ctx->allow_fc) {
896 			return TRUE;
897 		} else {
898 			uint32 delta;
899 			uint32 curr_t = OSL_SYSUPTIME();
900 
901 			if (ctx->fc_defer_timestamp == 0) {
902 				/* first single ac scenario */
903 				ctx->fc_defer_timestamp = curr_t;
904 				return FALSE;
905 			}
906 
907 			/* single AC duration, this handles wrap around, e.g. 1 - ~0 = 2. */
908 			delta = curr_t - ctx->fc_defer_timestamp;
909 			if (delta >= WLFC_FC_DEFER_PERIOD_MS) {
910 				ctx->allow_fc = TRUE;
911 			}
912 		}
913 	} else {
914 		/* multiple ACs or BCMC in queue */
915 		ctx->allow_fc = FALSE;
916 		ctx->fc_defer_timestamp = 0;
917 	}
918 
919 	return ctx->allow_fc;
920 } /* _dhd_wlfc_allow_fc */
921 
922 /**
923  * Starts or stops the flow of transmit packets from the host OS towards the DHD, depending on
924  * low/high watermarks.
925  */
926 static void
_dhd_wlfc_flow_control_check(athost_wl_status_info_t * ctx,struct pktq * pq,uint8 if_id)927 _dhd_wlfc_flow_control_check(athost_wl_status_info_t* ctx, struct pktq* pq, uint8 if_id)
928 {
929 	dhd_pub_t *dhdp;
930 
931 	ASSERT(ctx);
932 
933 	dhdp = (dhd_pub_t *)ctx->dhdp;
934 	ASSERT(dhdp);
935 
936 	if (dhdp->skip_fc && dhdp->skip_fc((void *)dhdp, if_id))
937 		return;
938 
939 	if ((ctx->hostif_flow_state[if_id] == OFF) && !_dhd_wlfc_allow_fc(ctx, if_id))
940 		return;
941 
942 	if ((pq->len <= WLFC_FLOWCONTROL_LOWATER) && (ctx->hostif_flow_state[if_id] == ON)) {
943 		/* start traffic */
944 		ctx->hostif_flow_state[if_id] = OFF;
945 		/*
946 		WLFC_DBGMESG(("qlen:%02d, if:%02d, ->OFF, start traffic %s()\n",
947 		pq->len, if_id, __FUNCTION__));
948 		*/
949 		WLFC_DBGMESG(("F"));
950 
951 		dhd_txflowcontrol(dhdp, if_id, OFF);
952 
953 		ctx->toggle_host_if = 0;
954 	}
955 
956 	if ((pq->len >= WLFC_FLOWCONTROL_HIWATER) && (ctx->hostif_flow_state[if_id] == OFF)) {
957 		/* stop traffic */
958 		ctx->hostif_flow_state[if_id] = ON;
959 		/*
960 		WLFC_DBGMESG(("qlen:%02d, if:%02d, ->ON, stop traffic   %s()\n",
961 		pq->len, if_id, __FUNCTION__));
962 		*/
963 		WLFC_DBGMESG(("N"));
964 
965 		dhd_txflowcontrol(dhdp, if_id, ON);
966 
967 		ctx->host_ifidx = if_id;
968 		ctx->toggle_host_if = 1;
969 	}
970 
971 	return;
972 } /* _dhd_wlfc_flow_control_check */
973 
974 static int
_dhd_wlfc_send_signalonly_packet(athost_wl_status_info_t * ctx,wlfc_mac_descriptor_t * entry,uint8 ta_bmp)975 _dhd_wlfc_send_signalonly_packet(athost_wl_status_info_t* ctx, wlfc_mac_descriptor_t* entry,
976 	uint8 ta_bmp)
977 {
978 	int rc = BCME_OK;
979 	void* p = NULL;
980 	int dummylen = ((dhd_pub_t *)ctx->dhdp)->hdrlen+ 16;
981 	dhd_pub_t *dhdp = (dhd_pub_t *)ctx->dhdp;
982 
983 	if (dhdp->proptxstatus_txoff) {
984 		rc = BCME_NORESOURCE;
985 		return rc;
986 	}
987 
988 	/* allocate a dummy packet */
989 	p = PKTGET(ctx->osh, dummylen, TRUE);
990 	if (p) {
991 		PKTPULL(ctx->osh, p, dummylen);
992 		DHD_PKTTAG_SET_H2DTAG(PKTTAG(p), 0);
993 		_dhd_wlfc_pushheader(ctx, &p, TRUE, ta_bmp, entry->mac_handle, 0, 0, FALSE);
994 		DHD_PKTTAG_SETSIGNALONLY(PKTTAG(p), 1);
995 		DHD_PKTTAG_WLFCPKT_SET(PKTTAG(p), 1);
996 #ifdef PROP_TXSTATUS_DEBUG
997 		ctx->stats.signal_only_pkts_sent++;
998 #endif
999 
1000 #if defined(BCMPCIE)
1001 		rc = dhd_bus_txdata(dhdp->bus, p, ctx->host_ifidx);
1002 #else
1003 		rc = dhd_bus_txdata(dhdp->bus, p);
1004 #endif
1005 		if (rc != BCME_OK) {
1006 			_dhd_wlfc_pullheader(ctx, p);
1007 			PKTFREE(ctx->osh, p, TRUE);
1008 		}
1009 	} else {
1010 		DHD_ERROR(("%s: couldn't allocate new %d-byte packet\n",
1011 		           __FUNCTION__, dummylen));
1012 		rc = BCME_NOMEM;
1013 		dhdp->tx_pktgetfail++;
1014 	}
1015 
1016 	return rc;
1017 } /* _dhd_wlfc_send_signalonly_packet */
1018 
1019 /**
1020  * Called on eg receiving 'mac close' indication from dongle. Updates the per-MAC administration
1021  * maintained in caller supplied parameter 'entry'.
1022  *
1023  *    @param[in/out] entry  administration about a remote MAC entity
1024  *    @param[in]     prec   precedence queue for this remote MAC entitity
1025  *
1026  * Return value: TRUE if traffic availability changed
1027  */
1028 static bool
_dhd_wlfc_traffic_pending_check(athost_wl_status_info_t * ctx,wlfc_mac_descriptor_t * entry,int prec)1029 _dhd_wlfc_traffic_pending_check(athost_wl_status_info_t* ctx, wlfc_mac_descriptor_t* entry,
1030 	int prec)
1031 {
1032 	bool rc = FALSE;
1033 
1034 	if (entry->state == WLFC_STATE_CLOSE) {
1035 		if ((pktq_plen(&entry->psq, (prec << 1)) == 0) &&
1036 			(pktq_plen(&entry->psq, ((prec << 1) + 1)) == 0)) {
1037 			/* no packets in both 'normal' and 'suspended' queues */
1038 			if (entry->traffic_pending_bmp & NBITVAL(prec)) {
1039 				rc = TRUE;
1040 				entry->traffic_pending_bmp =
1041 					entry->traffic_pending_bmp & ~ NBITVAL(prec);
1042 			}
1043 		} else {
1044 			/* packets are queued in host for transmission to dongle */
1045 			if (!(entry->traffic_pending_bmp & NBITVAL(prec))) {
1046 				rc = TRUE;
1047 				entry->traffic_pending_bmp =
1048 					entry->traffic_pending_bmp | NBITVAL(prec);
1049 			}
1050 		}
1051 	}
1052 
1053 	if (rc) {
1054 		/* request a TIM update to firmware at the next piggyback opportunity */
1055 		if (entry->traffic_lastreported_bmp != entry->traffic_pending_bmp) {
1056 			entry->send_tim_signal = 1;
1057 			_dhd_wlfc_send_signalonly_packet(ctx, entry, entry->traffic_pending_bmp);
1058 			entry->traffic_lastreported_bmp = entry->traffic_pending_bmp;
1059 			entry->send_tim_signal = 0;
1060 		} else {
1061 			rc = FALSE;
1062 		}
1063 	}
1064 
1065 	return rc;
1066 } /* _dhd_wlfc_traffic_pending_check */
1067 
1068 /**
1069  * Called on receiving a 'd11 suppressed' or 'wl suppressed' tx status from the firmware. Enqueues
1070  * the packet to transmit to firmware again at a later opportunity.
1071  */
1072 static int
_dhd_wlfc_enque_suppressed(athost_wl_status_info_t * ctx,int prec,void * p)1073 _dhd_wlfc_enque_suppressed(athost_wl_status_info_t* ctx, int prec, void* p)
1074 {
1075 	wlfc_mac_descriptor_t* entry;
1076 
1077 	entry = _dhd_wlfc_find_table_entry(ctx, p);
1078 	if (entry == NULL) {
1079 		DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
1080 		return BCME_NOTFOUND;
1081 	}
1082 	/*
1083 	- suppressed packets go to sub_queue[2*prec + 1] AND
1084 	- delayed packets go to sub_queue[2*prec + 0] to ensure
1085 	order of delivery.
1086 	*/
1087 	if (_dhd_wlfc_prec_enq_with_drop(ctx->dhdp, &entry->psq, p, ((prec << 1) + 1), FALSE,
1088 		WLFC_SEQCOUNT(entry, prec))
1089 		== FALSE) {
1090 		ctx->stats.delayq_full_error++;
1091 		/* WLFC_DBGMESG(("Error: %s():%d\n", __FUNCTION__, __LINE__)); */
1092 		WLFC_DBGMESG(("s"));
1093 		return BCME_ERROR;
1094 	}
1095 
1096 	/* A packet has been pushed, update traffic availability bitmap, if applicable */
1097 	_dhd_wlfc_traffic_pending_check(ctx, entry, prec);
1098 	_dhd_wlfc_flow_control_check(ctx, &entry->psq, DHD_PKTTAG_IF(PKTTAG(p)));
1099 	return BCME_OK;
1100 }
1101 
1102 /**
1103  * Called when a transmit packet is about to be 'committed' from the OS layer to a lower layer
1104  * towards the dongle (eg the DBUS layer). Updates wlfc administration. May modify packet.
1105  *
1106  *     @param[in/out] ctx    driver specific flow control administration
1107  *     @param[in/out] entry  The remote MAC entity for which the packet is destined.
1108  *     @param[in/out] packet Packet to send. This function optionally adds TLVs to the packet.
1109  *     @param[in] header_needed True if packet is 'new' to flow control
1110  *     @param[out] slot Handle to container in which the packet was 'parked'
1111  */
1112 static int
_dhd_wlfc_pretx_pktprocess(athost_wl_status_info_t * ctx,wlfc_mac_descriptor_t * entry,void ** packet,int header_needed,uint32 * slot)1113 _dhd_wlfc_pretx_pktprocess(athost_wl_status_info_t* ctx,
1114 	wlfc_mac_descriptor_t* entry, void** packet, int header_needed, uint32* slot)
1115 {
1116 	int rc = BCME_OK;
1117 	int hslot = WLFC_HANGER_MAXITEMS;
1118 	bool send_tim_update = FALSE;
1119 	uint32 htod = 0;
1120 	uint16 htodseq = 0;
1121 	uint8 free_ctr;
1122 	int gen = 0xff;
1123 	dhd_pub_t *dhdp = (dhd_pub_t *)ctx->dhdp;
1124 	void * p = *packet;
1125 
1126 	*slot = hslot;
1127 
1128 	if (entry == NULL) {
1129 		entry = _dhd_wlfc_find_table_entry(ctx, p);
1130 	}
1131 
1132 	if (entry == NULL) {
1133 		DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
1134 		return BCME_ERROR;
1135 	}
1136 
1137 	if (entry->send_tim_signal) {
1138 		/* sends a traffic indication bitmap to the dongle */
1139 		send_tim_update = TRUE;
1140 		entry->send_tim_signal = 0;
1141 		entry->traffic_lastreported_bmp = entry->traffic_pending_bmp;
1142 	}
1143 
1144 	if (header_needed) {
1145 		if (WLFC_GET_AFQ(dhdp->wlfc_mode)) {
1146 			hslot = (uint)(entry - &ctx->destination_entries.nodes[0]);
1147 		} else {
1148 			hslot = _dhd_wlfc_hanger_get_free_slot(ctx->hanger);
1149 		}
1150 		gen = entry->generation;
1151 		free_ctr = WLFC_SEQCOUNT(entry, DHD_PKTTAG_FIFO(PKTTAG(p)));
1152 	} else {
1153 		if (WLFC_GET_REUSESEQ(dhdp->wlfc_mode)) {
1154 			htodseq = DHD_PKTTAG_H2DSEQ(PKTTAG(p));
1155 		}
1156 
1157 		hslot = WL_TXSTATUS_GET_HSLOT(DHD_PKTTAG_H2DTAG(PKTTAG(p)));
1158 
1159 		if (WLFC_GET_REORDERSUPP(dhdp->wlfc_mode)) {
1160 			gen = entry->generation;
1161 		} else if (WLFC_GET_AFQ(dhdp->wlfc_mode)) {
1162 			gen = WL_TXSTATUS_GET_GENERATION(DHD_PKTTAG_H2DTAG(PKTTAG(p)));
1163 		} else {
1164 			_dhd_wlfc_hanger_get_genbit(ctx->hanger, p, hslot, &gen);
1165 		}
1166 
1167 		free_ctr = WL_TXSTATUS_GET_FREERUNCTR(DHD_PKTTAG_H2DTAG(PKTTAG(p)));
1168 		/* remove old header */
1169 		_dhd_wlfc_pullheader(ctx, p);
1170 	}
1171 
1172 	if (hslot >= WLFC_HANGER_MAXITEMS) {
1173 		DHD_ERROR(("Error: %s():no hanger slot available\n", __FUNCTION__));
1174 		return BCME_ERROR;
1175 	}
1176 
1177 	WL_TXSTATUS_SET_FREERUNCTR(htod, free_ctr);
1178 	WL_TXSTATUS_SET_HSLOT(htod, hslot);
1179 	WL_TXSTATUS_SET_FIFO(htod, DHD_PKTTAG_FIFO(PKTTAG(p)));
1180 	WL_TXSTATUS_SET_FLAGS(htod, WLFC_PKTFLAG_PKTFROMHOST);
1181 	WL_TXSTATUS_SET_GENERATION(htod, gen);
1182 	DHD_PKTTAG_SETPKTDIR(PKTTAG(p), 1);
1183 
1184 	if (!DHD_PKTTAG_CREDITCHECK(PKTTAG(p))) {
1185 		/*
1186 		Indicate that this packet is being sent in response to an
1187 		explicit request from the firmware side.
1188 		*/
1189 		WLFC_PKTFLAG_SET_PKTREQUESTED(htod);
1190 	} else {
1191 		WLFC_PKTFLAG_CLR_PKTREQUESTED(htod);
1192 	}
1193 
1194 	rc = _dhd_wlfc_pushheader(ctx, &p, send_tim_update,
1195 		entry->traffic_lastreported_bmp, entry->mac_handle, htod, htodseq, FALSE);
1196 	if (rc == BCME_OK) {
1197 		DHD_PKTTAG_SET_H2DTAG(PKTTAG(p), htod);
1198 
1199 		if (!WLFC_GET_AFQ(dhdp->wlfc_mode)) {
1200 			wlfc_hanger_t *h = (wlfc_hanger_t*)(ctx->hanger);
1201 			if (header_needed) {
1202 				/*
1203 				a new header was created for this packet.
1204 				push to hanger slot and scrub q. Since bus
1205 				send succeeded, increment seq number as well.
1206 				*/
1207 				rc = _dhd_wlfc_hanger_pushpkt(ctx->hanger, p, hslot);
1208 				if (rc == BCME_OK) {
1209 #ifdef PROP_TXSTATUS_DEBUG
1210 					h->items[hslot].push_time =
1211 						OSL_SYSUPTIME();
1212 #endif
1213 				} else {
1214 					DHD_ERROR(("%s() hanger_pushpkt() failed, rc: %d\n",
1215 						__FUNCTION__, rc));
1216 				}
1217 			} else {
1218 				/* clear hanger state */
1219 				if (((wlfc_hanger_t*)(ctx->hanger))->items[hslot].pkt != p)
1220 					DHD_ERROR(("%s() pkt not match: cur %p, hanger pkt %p\n",
1221 						__FUNCTION__, p, h->items[hslot].pkt));
1222 				ASSERT(h->items[hslot].pkt == p);
1223 				bcm_object_feature_set(h->items[hslot].pkt,
1224 					BCM_OBJECT_FEATURE_PKT_STATE, 0);
1225 				h->items[hslot].pkt_state = 0;
1226 				h->items[hslot].pkt_txstatus = 0;
1227 				h->items[hslot].state = WLFC_HANGER_ITEM_STATE_INUSE;
1228 			}
1229 		}
1230 
1231 		if ((rc == BCME_OK) && header_needed) {
1232 			/* increment free running sequence count */
1233 			WLFC_INCR_SEQCOUNT(entry, DHD_PKTTAG_FIFO(PKTTAG(p)));
1234 		}
1235 	}
1236 	*slot = hslot;
1237 	*packet = p;
1238 	return rc;
1239 } /* _dhd_wlfc_pretx_pktprocess */
1240 
1241 /**
1242  * A remote wireless mac may be temporarily 'closed' due to power management. Returns '1' if remote
1243  * mac is in the 'open' state, otherwise '0'.
1244  */
1245 static int
_dhd_wlfc_is_destination_open(athost_wl_status_info_t * ctx,wlfc_mac_descriptor_t * entry,int prec)1246 _dhd_wlfc_is_destination_open(athost_wl_status_info_t* ctx,
1247 	wlfc_mac_descriptor_t* entry, int prec)
1248 {
1249 	wlfc_mac_descriptor_t* interfaces = ctx->destination_entries.interfaces;
1250 
1251 	if (entry->interface_id >= WLFC_MAX_IFNUM) {
1252 		ASSERT(&ctx->destination_entries.other == entry);
1253 		return 1;
1254 	}
1255 
1256 	if (interfaces[entry->interface_id].iftype ==
1257 		WLC_E_IF_ROLE_P2P_GO) {
1258 		/* - destination interface is of type p2p GO.
1259 		For a p2pGO interface, if the destination is OPEN but the interface is
1260 		CLOSEd, do not send traffic. But if the dstn is CLOSEd while there is
1261 		destination-specific-credit left send packets. This is because the
1262 		firmware storing the destination-specific-requested packet in queue.
1263 		*/
1264 		if ((entry->state == WLFC_STATE_CLOSE) && (entry->requested_credit == 0) &&
1265 			(entry->requested_packet == 0)) {
1266 			return 0;
1267 		}
1268 	}
1269 
1270 	/* AP, p2p_go -> unicast desc entry, STA/p2p_cl -> interface desc. entry */
1271 	if ((((entry->state == WLFC_STATE_CLOSE) ||
1272 		(interfaces[entry->interface_id].state == WLFC_STATE_CLOSE)) &&
1273 		(entry->requested_credit == 0) &&
1274 		(entry->requested_packet == 0)) ||
1275 		(!(entry->ac_bitmap & (1 << prec)))) {
1276 		return 0;
1277 	}
1278 
1279 	return 1;
1280 } /* _dhd_wlfc_is_destination_open */
1281 
1282 /**
1283  * Dequeues a suppressed or delayed packet from a queue
1284  *    @param[in/out] ctx          Driver specific flow control administration
1285  *    @param[in]  prec            Precedence of queue to dequeue from
1286  *    @param[out] ac_credit_spent Boolean, returns 0 or 1
1287  *    @param[out] needs_hdr       Boolean, returns 0 or 1
1288  *    @param[out] entry_out       The remote MAC for which the packet is destined
1289  *    @param[in]  only_no_credit  If TRUE, searches all entries instead of just the active ones
1290  *
1291  * Return value: the dequeued packet
1292  */
1293 static void*
_dhd_wlfc_deque_delayedq(athost_wl_status_info_t * ctx,int prec,uint8 * ac_credit_spent,uint8 * needs_hdr,wlfc_mac_descriptor_t ** entry_out,bool only_no_credit)1294 _dhd_wlfc_deque_delayedq(athost_wl_status_info_t* ctx, int prec,
1295 	uint8* ac_credit_spent, uint8* needs_hdr, wlfc_mac_descriptor_t** entry_out,
1296 	bool only_no_credit)
1297 {
1298 	wlfc_mac_descriptor_t* entry;
1299 	int total_entries;
1300 	void* p = NULL;
1301 	int i;
1302 	uint8 credit_spent = ((prec == AC_COUNT) && !ctx->bcmc_credit_supported) ? 0 : 1;
1303 
1304 	*entry_out = NULL;
1305 	/* most cases a packet will count against FIFO credit */
1306 	*ac_credit_spent = credit_spent;
1307 
1308 	/* search all entries, include nodes as well as interfaces */
1309 	if (only_no_credit) {
1310 		total_entries = ctx->requested_entry_count;
1311 	} else {
1312 		total_entries = ctx->active_entry_count;
1313 	}
1314 
1315 	for (i = 0; i < total_entries; i++) {
1316 		if (only_no_credit) {
1317 			entry = ctx->requested_entry[i];
1318 		} else {
1319 			entry = ctx->active_entry_head;
1320 			/* move head to ensure fair round-robin */
1321 			ctx->active_entry_head = ctx->active_entry_head->next;
1322 		}
1323 		ASSERT(entry);
1324 
1325 		if (entry->occupied && _dhd_wlfc_is_destination_open(ctx, entry, prec) &&
1326 #ifdef PROPTX_MAXCOUNT
1327 			(entry->transit_count < entry->transit_maxcount) &&
1328 #endif /* PROPTX_MAXCOUNT */
1329 			(entry->transit_count < WL_TXSTATUS_FREERUNCTR_MASK) &&
1330 			(!entry->suppressed)) {
1331 			*ac_credit_spent = credit_spent;
1332 			if (entry->state == WLFC_STATE_CLOSE) {
1333 				*ac_credit_spent = 0;
1334 			}
1335 
1336 			/* higher precedence will be picked up first,
1337 			 * i.e. suppressed packets before delayed ones
1338 			 */
1339 			p = pktq_pdeq(&entry->psq, PSQ_SUP_IDX(prec));
1340 			*needs_hdr = 0;
1341 			if (p == NULL) {
1342 				/* De-Q from delay Q */
1343 				p = pktq_pdeq(&entry->psq, PSQ_DLY_IDX(prec));
1344 				*needs_hdr = 1;
1345 			}
1346 
1347 			if (p != NULL) {
1348 				bcm_pkt_validate_chk(p);
1349 				/* did the packet come from suppress sub-queue? */
1350 				if (entry->requested_credit > 0) {
1351 					entry->requested_credit--;
1352 #ifdef PROP_TXSTATUS_DEBUG
1353 					entry->dstncredit_sent_packets++;
1354 #endif
1355 				} else if (entry->requested_packet > 0) {
1356 					entry->requested_packet--;
1357 					DHD_PKTTAG_SETONETIMEPKTRQST(PKTTAG(p));
1358 				}
1359 
1360 				*entry_out = entry;
1361 				ctx->pkt_cnt_in_q[DHD_PKTTAG_IF(PKTTAG(p))][prec]--;
1362 				ctx->pkt_cnt_per_ac[prec]--;
1363 				ctx->pkt_cnt_in_psq--;
1364 				_dhd_wlfc_flow_control_check(ctx, &entry->psq,
1365 					DHD_PKTTAG_IF(PKTTAG(p)));
1366 				/*
1367 				 * A packet has been picked up, update traffic availability bitmap,
1368 				 * if applicable.
1369 				 */
1370 				_dhd_wlfc_traffic_pending_check(ctx, entry, prec);
1371 				return p;
1372 			}
1373 		}
1374 	}
1375 	return NULL;
1376 } /* _dhd_wlfc_deque_delayedq */
1377 
1378 /** Enqueues caller supplied packet on either a 'suppressed' or 'delayed' queue */
1379 static int
_dhd_wlfc_enque_delayq(athost_wl_status_info_t * ctx,void * pktbuf,int prec)1380 _dhd_wlfc_enque_delayq(athost_wl_status_info_t* ctx, void* pktbuf, int prec)
1381 {
1382 	wlfc_mac_descriptor_t* entry;
1383 
1384 	if (pktbuf != NULL) {
1385 		entry = _dhd_wlfc_find_table_entry(ctx, pktbuf);
1386 		if (entry == NULL) {
1387 			DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
1388 			return BCME_ERROR;
1389 		}
1390 
1391 		/*
1392 		- suppressed packets go to sub_queue[2*prec + 1] AND
1393 		- delayed packets go to sub_queue[2*prec + 0] to ensure
1394 		order of delivery.
1395 		*/
1396 		if (_dhd_wlfc_prec_enq_with_drop(ctx->dhdp, &entry->psq, pktbuf, (prec << 1),
1397 			FALSE, WLFC_SEQCOUNT(entry, prec))
1398 			== FALSE) {
1399 			WLFC_DBGMESG(("D"));
1400 			ctx->stats.delayq_full_error++;
1401 			return BCME_ERROR;
1402 		}
1403 
1404 
1405 		/* A packet has been pushed, update traffic availability bitmap, if applicable */
1406 		_dhd_wlfc_traffic_pending_check(ctx, entry, prec);
1407 	}
1408 
1409 	return BCME_OK;
1410 } /* _dhd_wlfc_enque_delayq */
1411 
1412 /** Returns TRUE if caller supplied packet is destined for caller supplied interface */
_dhd_wlfc_ifpkt_fn(void * p,void * p_ifid)1413 static bool _dhd_wlfc_ifpkt_fn(void* p, void *p_ifid)
1414 {
1415 	if (!p || !p_ifid)
1416 		return FALSE;
1417 
1418 	return (DHD_PKTTAG_WLFCPKT(PKTTAG(p))&& (*((uint8 *)p_ifid) == DHD_PKTTAG_IF(PKTTAG(p))));
1419 }
1420 
1421 /** Returns TRUE if caller supplied packet is destined for caller supplied remote MAC */
_dhd_wlfc_entrypkt_fn(void * p,void * entry)1422 static bool _dhd_wlfc_entrypkt_fn(void* p, void *entry)
1423 {
1424 	if (!p || !entry)
1425 		return FALSE;
1426 
1427 	return (DHD_PKTTAG_WLFCPKT(PKTTAG(p))&& (entry == DHD_PKTTAG_ENTRY(PKTTAG(p))));
1428 }
1429 
1430 static void
_dhd_wlfc_return_implied_credit(athost_wl_status_info_t * wlfc,void * pkt)1431 _dhd_wlfc_return_implied_credit(athost_wl_status_info_t* wlfc, void* pkt)
1432 {
1433 	dhd_pub_t *dhdp;
1434 	bool credit_return = FALSE;
1435 
1436 	if (!wlfc || !pkt) {
1437 		return;
1438 	}
1439 
1440 	dhdp = (dhd_pub_t *)(wlfc->dhdp);
1441 	if (dhdp && (dhdp->proptxstatus_mode == WLFC_FCMODE_IMPLIED_CREDIT) &&
1442 		DHD_PKTTAG_CREDITCHECK(PKTTAG(pkt))) {
1443 		int lender, credit_returned = 0;
1444 		uint8 fifo_id = DHD_PKTTAG_FIFO(PKTTAG(pkt));
1445 
1446 		credit_return = TRUE;
1447 
1448 		/* Note that borrower is fifo_id */
1449 		/* Return credits to highest priority lender first */
1450 		for (lender = AC_COUNT; lender >= 0; lender--) {
1451 			if (wlfc->credits_borrowed[fifo_id][lender] > 0) {
1452 				wlfc->FIFO_credit[lender]++;
1453 				wlfc->credits_borrowed[fifo_id][lender]--;
1454 				credit_returned = 1;
1455 				break;
1456 			}
1457 		}
1458 
1459 		if (!credit_returned) {
1460 			wlfc->FIFO_credit[fifo_id]++;
1461 		}
1462 	}
1463 
1464 	BCM_REFERENCE(credit_return);
1465 #if defined(DHD_WLFC_THREAD)
1466 	if (credit_return) {
1467 		_dhd_wlfc_thread_wakeup(dhdp);
1468 	}
1469 #endif /* defined(DHD_WLFC_THREAD) */
1470 }
1471 
1472 /** Removes and frees a packet from the hanger. Called during eg tx complete. */
1473 static void
_dhd_wlfc_hanger_free_pkt(athost_wl_status_info_t * wlfc,uint32 slot_id,uint8 pkt_state,int pkt_txstatus)1474 _dhd_wlfc_hanger_free_pkt(athost_wl_status_info_t* wlfc, uint32 slot_id, uint8 pkt_state,
1475 	int pkt_txstatus)
1476 {
1477 	wlfc_hanger_t* hanger;
1478 	wlfc_hanger_item_t* item;
1479 
1480 	if (!wlfc)
1481 		return;
1482 
1483 	hanger = (wlfc_hanger_t*)wlfc->hanger;
1484 	if (!hanger)
1485 		return;
1486 
1487 	if (slot_id == WLFC_HANGER_MAXITEMS)
1488 		return;
1489 
1490 	item = &hanger->items[slot_id];
1491 
1492 	if (item->pkt) {
1493 		item->pkt_state |= pkt_state;
1494 		if (pkt_txstatus != -1)
1495 			item->pkt_txstatus = (uint8)pkt_txstatus;
1496 		bcm_object_feature_set(item->pkt, BCM_OBJECT_FEATURE_PKT_STATE, item->pkt_state);
1497 		if (item->pkt_state == WLFC_HANGER_PKT_STATE_COMPLETE) {
1498 			void *p = NULL;
1499 			void *pkt = item->pkt;
1500 			uint8 old_state = item->state;
1501 			int ret = _dhd_wlfc_hanger_poppkt(wlfc->hanger, slot_id, &p, TRUE);
1502 			BCM_REFERENCE(ret);
1503 			BCM_REFERENCE(pkt);
1504 			ASSERT((ret == BCME_OK) && p && (pkt == p));
1505 			if (old_state == WLFC_HANGER_ITEM_STATE_INUSE_SUPPRESSED) {
1506 				printf("ERROR: free a suppressed pkt %p state %d pkt_state %d\n",
1507 					pkt, old_state, item->pkt_state);
1508 			}
1509 			ASSERT(old_state != WLFC_HANGER_ITEM_STATE_INUSE_SUPPRESSED);
1510 
1511 			/* free packet */
1512 			wlfc->pkt_cnt_in_drv[DHD_PKTTAG_IF(PKTTAG(p))]
1513 				[DHD_PKTTAG_FIFO(PKTTAG(p))]--;
1514 			wlfc->stats.pktout++;
1515 			dhd_txcomplete((dhd_pub_t *)wlfc->dhdp, p, item->pkt_txstatus);
1516 			PKTFREE(wlfc->osh, p, TRUE);
1517 		}
1518 	} else {
1519 		/* free slot */
1520 		if (item->state == WLFC_HANGER_ITEM_STATE_FREE)
1521 			DHD_ERROR(("Error: %s():%d Multiple TXSTATUS or BUSRETURNED: %d (%d)\n",
1522 			    __FUNCTION__, __LINE__, item->pkt_state, pkt_state));
1523 		item->state = WLFC_HANGER_ITEM_STATE_FREE;
1524 	}
1525 } /* _dhd_wlfc_hanger_free_pkt */
1526 
1527 /** Called during eg detach() */
1528 static void
_dhd_wlfc_pktq_flush(athost_wl_status_info_t * ctx,struct pktq * pq,bool dir,f_processpkt_t fn,void * arg,q_type_t q_type)1529 _dhd_wlfc_pktq_flush(athost_wl_status_info_t* ctx, struct pktq *pq,
1530 	bool dir, f_processpkt_t fn, void *arg, q_type_t q_type)
1531 {
1532 	int prec;
1533 	dhd_pub_t *dhdp = (dhd_pub_t *)ctx->dhdp;
1534 
1535 	ASSERT(dhdp);
1536 
1537 	/* Optimize flush, if pktq len = 0, just return.
1538 	 * pktq len of 0 means pktq's prec q's are all empty.
1539 	 */
1540 	if (pq->len == 0) {
1541 		return;
1542 	}
1543 
1544 	for (prec = 0; prec < pq->num_prec; prec++) {
1545 		struct pktq_prec *q;
1546 		void *p, *prev = NULL;
1547 
1548 		q = &pq->q[prec];
1549 		p = q->head;
1550 		while (p) {
1551 			bcm_pkt_validate_chk(p);
1552 			if (fn == NULL || (*fn)(p, arg)) {
1553 				bool head = (p == q->head);
1554 				if (head)
1555 					q->head = PKTLINK(p);
1556 				else
1557 					PKTSETLINK(prev, PKTLINK(p));
1558 				if (q_type == Q_TYPE_PSQ) {
1559 					if (!WLFC_GET_AFQ(dhdp->wlfc_mode) && (prec & 1)) {
1560 						_dhd_wlfc_hanger_remove_reference(ctx->hanger, p);
1561 					}
1562 					ctx->pkt_cnt_in_q[DHD_PKTTAG_IF(PKTTAG(p))][prec>>1]--;
1563 					ctx->pkt_cnt_per_ac[prec>>1]--;
1564 					ctx->pkt_cnt_in_psq--;
1565 					ctx->stats.cleanup_psq_cnt++;
1566 					if (!(prec & 1)) {
1567 						/* pkt in delayed q, so fake push BDC header for
1568 						 * dhd_tcpack_check_xmit() and dhd_txcomplete().
1569 						 */
1570 						_dhd_wlfc_pushheader(ctx, &p, FALSE, 0, 0,
1571 							0, 0, TRUE);
1572 #ifdef DHDTCPACK_SUPPRESS
1573 						if (dhd_tcpack_check_xmit(dhdp, p) == BCME_ERROR) {
1574 							DHD_ERROR(("%s %d: tcpack_suppress ERROR!!!"
1575 								" Stop using it\n",
1576 								__FUNCTION__, __LINE__));
1577 							dhd_tcpack_suppress_set(dhdp,
1578 								TCPACK_SUP_OFF);
1579 						}
1580 #endif /* DHDTCPACK_SUPPRESS */
1581 					}
1582 				} else if (q_type == Q_TYPE_AFQ) {
1583 					wlfc_mac_descriptor_t* entry =
1584 						_dhd_wlfc_find_table_entry(ctx, p);
1585 					if (entry->transit_count)
1586 						entry->transit_count--;
1587 					if (entry->suppr_transit_count) {
1588 						entry->suppr_transit_count--;
1589 						if (entry->suppressed &&
1590 							(!entry->onbus_pkts_count) &&
1591 							(!entry->suppr_transit_count))
1592 							entry->suppressed = FALSE;
1593 					}
1594 					_dhd_wlfc_return_implied_credit(ctx, p);
1595 					ctx->stats.cleanup_fw_cnt++;
1596 				}
1597 				PKTSETLINK(p, NULL);
1598 				if (dir) {
1599 					ctx->pkt_cnt_in_drv[DHD_PKTTAG_IF(PKTTAG(p))][prec>>1]--;
1600 					ctx->stats.pktout++;
1601 					dhd_txcomplete(dhdp, p, FALSE);
1602 				}
1603 				PKTFREE(ctx->osh, p, dir);
1604 
1605 				q->len--;
1606 				pq->len--;
1607 				p = (head ? q->head : PKTLINK(prev));
1608 			} else {
1609 				prev = p;
1610 				p = PKTLINK(p);
1611 			}
1612 		}
1613 
1614 		if (q->head == NULL) {
1615 			ASSERT(q->len == 0);
1616 			q->tail = NULL;
1617 		}
1618 
1619 	}
1620 
1621 	if (fn == NULL)
1622 		ASSERT(pq->len == 0);
1623 } /* _dhd_wlfc_pktq_flush */
1624 
1625 #ifndef BCMDBUS
1626 /** !BCMDBUS specific function. Dequeues a packet from the caller supplied queue. */
1627 static void*
_dhd_wlfc_pktq_pdeq_with_fn(struct pktq * pq,int prec,f_processpkt_t fn,void * arg)1628 _dhd_wlfc_pktq_pdeq_with_fn(struct pktq *pq, int prec, f_processpkt_t fn, void *arg)
1629 {
1630 	struct pktq_prec *q;
1631 	void *p, *prev = NULL;
1632 
1633 	ASSERT(prec >= 0 && prec < pq->num_prec);
1634 
1635 	q = &pq->q[prec];
1636 	p = q->head;
1637 
1638 	while (p) {
1639 		if (fn == NULL || (*fn)(p, arg)) {
1640 			break;
1641 		} else {
1642 			prev = p;
1643 			p = PKTLINK(p);
1644 		}
1645 	}
1646 	if (p == NULL)
1647 		return NULL;
1648 
1649 	bcm_pkt_validate_chk(p);
1650 
1651 	if (prev == NULL) {
1652 		if ((q->head = PKTLINK(p)) == NULL) {
1653 			q->tail = NULL;
1654 		}
1655 	} else {
1656 		PKTSETLINK(prev, PKTLINK(p));
1657 		if (q->tail == p) {
1658 			q->tail = prev;
1659 		}
1660 	}
1661 
1662 	q->len--;
1663 
1664 	pq->len--;
1665 
1666 	PKTSETLINK(p, NULL);
1667 
1668 	return p;
1669 }
1670 
1671 /** !BCMDBUS specific function */
1672 static void
_dhd_wlfc_cleanup_txq(dhd_pub_t * dhd,f_processpkt_t fn,void * arg)1673 _dhd_wlfc_cleanup_txq(dhd_pub_t *dhd, f_processpkt_t fn, void *arg)
1674 {
1675 	int prec;
1676 	void *pkt = NULL, *head = NULL, *tail = NULL;
1677 	struct pktq *txq = (struct pktq *)dhd_bus_txq(dhd->bus);
1678 	athost_wl_status_info_t* wlfc = (athost_wl_status_info_t*)dhd->wlfc_state;
1679 	wlfc_hanger_t* h = (wlfc_hanger_t*)wlfc->hanger;
1680 	wlfc_mac_descriptor_t* entry;
1681 
1682 	dhd_os_sdlock_txq(dhd);
1683 	for (prec = 0; prec < txq->num_prec; prec++) {
1684 		while ((pkt = _dhd_wlfc_pktq_pdeq_with_fn(txq, prec, fn, arg))) {
1685 #ifdef DHDTCPACK_SUPPRESS
1686 			if (dhd_tcpack_check_xmit(dhd, pkt) == BCME_ERROR) {
1687 				DHD_ERROR(("%s %d: tcpack_suppress ERROR!!! Stop using it\n",
1688 					__FUNCTION__, __LINE__));
1689 				dhd_tcpack_suppress_set(dhd, TCPACK_SUP_OFF);
1690 			}
1691 #endif /* DHDTCPACK_SUPPRESS */
1692 			if (!head) {
1693 				head = pkt;
1694 			}
1695 			if (tail) {
1696 				PKTSETLINK(tail, pkt);
1697 			}
1698 			tail = pkt;
1699 		}
1700 	}
1701 	dhd_os_sdunlock_txq(dhd);
1702 
1703 
1704 	while ((pkt = head)) {
1705 		head = PKTLINK(pkt);
1706 		PKTSETLINK(pkt, NULL);
1707 		entry = _dhd_wlfc_find_table_entry(wlfc, pkt);
1708 
1709 		if (!WLFC_GET_AFQ(dhd->wlfc_mode) &&
1710 			!_dhd_wlfc_hanger_remove_reference(h, pkt)) {
1711 			DHD_ERROR(("%s: can't find pkt(%p) in hanger, free it anyway\n",
1712 				__FUNCTION__, pkt));
1713 		}
1714 		if (entry->transit_count)
1715 			entry->transit_count--;
1716 		if (entry->suppr_transit_count) {
1717 			entry->suppr_transit_count--;
1718 			if (entry->suppressed &&
1719 				(!entry->onbus_pkts_count) &&
1720 				(!entry->suppr_transit_count))
1721 				entry->suppressed = FALSE;
1722 		}
1723 		_dhd_wlfc_return_implied_credit(wlfc, pkt);
1724 		wlfc->pkt_cnt_in_drv[DHD_PKTTAG_IF(PKTTAG(pkt))][DHD_PKTTAG_FIFO(PKTTAG(pkt))]--;
1725 		wlfc->stats.pktout++;
1726 		wlfc->stats.cleanup_txq_cnt++;
1727 		dhd_txcomplete(dhd, pkt, FALSE);
1728 		PKTFREE(wlfc->osh, pkt, TRUE);
1729 	}
1730 } /* _dhd_wlfc_cleanup_txq */
1731 #endif /* !BCMDBUS */
1732 
1733 /** called during eg detach */
1734 void
_dhd_wlfc_cleanup(dhd_pub_t * dhd,f_processpkt_t fn,void * arg)1735 _dhd_wlfc_cleanup(dhd_pub_t *dhd, f_processpkt_t fn, void *arg)
1736 {
1737 	int i;
1738 	int total_entries;
1739 	athost_wl_status_info_t* wlfc = (athost_wl_status_info_t*)dhd->wlfc_state;
1740 	wlfc_mac_descriptor_t* table;
1741 	wlfc_hanger_t* h = (wlfc_hanger_t*)wlfc->hanger;
1742 
1743 	wlfc->stats.cleanup_txq_cnt = 0;
1744 	wlfc->stats.cleanup_psq_cnt = 0;
1745 	wlfc->stats.cleanup_fw_cnt = 0;
1746 
1747 	/*
1748 	*  flush sequence should be txq -> psq -> hanger/afq, hanger has to be last one
1749 	*/
1750 #ifndef BCMDBUS
1751 	/* flush bus->txq */
1752 	_dhd_wlfc_cleanup_txq(dhd, fn, arg);
1753 #endif /* !BCMDBUS */
1754 
1755 	/* flush psq, search all entries, include nodes as well as interfaces */
1756 	total_entries = sizeof(wlfc->destination_entries)/sizeof(wlfc_mac_descriptor_t);
1757 	table = (wlfc_mac_descriptor_t*)&wlfc->destination_entries;
1758 
1759 	for (i = 0; i < total_entries; i++) {
1760 		if (table[i].occupied) {
1761 			/* release packets held in PSQ (both delayed and suppressed) */
1762 			if (table[i].psq.len) {
1763 				WLFC_DBGMESG(("%s(): PSQ[%d].len = %d\n",
1764 					__FUNCTION__, i, table[i].psq.len));
1765 				_dhd_wlfc_pktq_flush(wlfc, &table[i].psq, TRUE,
1766 					fn, arg, Q_TYPE_PSQ);
1767 			}
1768 
1769 			/* free packets held in AFQ */
1770 			if (WLFC_GET_AFQ(dhd->wlfc_mode) && (table[i].afq.len)) {
1771 				_dhd_wlfc_pktq_flush(wlfc, &table[i].afq, TRUE,
1772 					fn, arg, Q_TYPE_AFQ);
1773 			}
1774 
1775 			if ((fn == NULL) && (&table[i] != &wlfc->destination_entries.other)) {
1776 				table[i].occupied = 0;
1777 				if (table[i].transit_count || table[i].suppr_transit_count) {
1778 					DHD_ERROR(("%s: table[%d] transit(%d), suppr_tansit(%d)\n",
1779 						__FUNCTION__, i,
1780 						table[i].transit_count,
1781 						table[i].suppr_transit_count));
1782 				}
1783 			}
1784 		}
1785 	}
1786 
1787 	/*
1788 		. flush remained pkt in hanger queue, not in bus->txq nor psq.
1789 		. the remained pkt was successfully downloaded to dongle already.
1790 		. hanger slot state cannot be set to free until receive txstatus update.
1791 	*/
1792 	if (!WLFC_GET_AFQ(dhd->wlfc_mode)) {
1793 		for (i = 0; i < h->max_items; i++) {
1794 			if ((h->items[i].state == WLFC_HANGER_ITEM_STATE_INUSE) ||
1795 				(h->items[i].state == WLFC_HANGER_ITEM_STATE_INUSE_SUPPRESSED)) {
1796 				if (fn == NULL || (*fn)(h->items[i].pkt, arg)) {
1797 					h->items[i].state = WLFC_HANGER_ITEM_STATE_FLUSHED;
1798 				}
1799 			}
1800 		}
1801 	}
1802 
1803 	return;
1804 } /* _dhd_wlfc_cleanup */
1805 
1806 /** Called after eg the dongle signalled a new remote MAC that it connected with to the DHD */
1807 static int
_dhd_wlfc_mac_entry_update(athost_wl_status_info_t * ctx,wlfc_mac_descriptor_t * entry,uint8 action,uint8 ifid,uint8 iftype,uint8 * ea,f_processpkt_t fn,void * arg)1808 _dhd_wlfc_mac_entry_update(athost_wl_status_info_t* ctx, wlfc_mac_descriptor_t* entry,
1809 	uint8 action, uint8 ifid, uint8 iftype, uint8* ea,
1810 	f_processpkt_t fn, void *arg)
1811 {
1812 	int rc = BCME_OK;
1813 
1814 
1815 	if ((action == eWLFC_MAC_ENTRY_ACTION_ADD) || (action == eWLFC_MAC_ENTRY_ACTION_UPDATE)) {
1816 		entry->occupied = 1;
1817 		entry->state = WLFC_STATE_OPEN;
1818 		entry->requested_credit = 0;
1819 		entry->interface_id = ifid;
1820 		entry->iftype = iftype;
1821 		entry->ac_bitmap = 0xff; /* update this when handling APSD */
1822 
1823 		/* for an interface entry we may not care about the MAC address */
1824 		if (ea != NULL)
1825 			memcpy(&entry->ea[0], ea, ETHER_ADDR_LEN);
1826 
1827 		if (action == eWLFC_MAC_ENTRY_ACTION_ADD) {
1828 			entry->suppressed = FALSE;
1829 			entry->transit_count = 0;
1830 #ifdef PROPTX_MAXCOUNT
1831 			entry->transit_maxcount = wl_ext_get_wlfc_maxcount(ctx->dhdp, ifid);
1832 #endif /* PROPTX_MAXCOUNT */
1833 			entry->suppr_transit_count = 0;
1834 			entry->onbus_pkts_count = 0;
1835 		}
1836 
1837 		if (action == eWLFC_MAC_ENTRY_ACTION_ADD) {
1838 			dhd_pub_t *dhdp = (dhd_pub_t *)(ctx->dhdp);
1839 
1840 			pktq_init(&entry->psq, WLFC_PSQ_PREC_COUNT, WLFC_PSQ_LEN);
1841 			_dhd_wlfc_flow_control_check(ctx, &entry->psq, ifid);
1842 
1843 			if (WLFC_GET_AFQ(dhdp->wlfc_mode)) {
1844 				pktq_init(&entry->afq, WLFC_AFQ_PREC_COUNT, WLFC_PSQ_LEN);
1845 			}
1846 
1847 			if (entry->next == NULL) {
1848 				/* not linked to anywhere, add to tail */
1849 				if (ctx->active_entry_head) {
1850 					entry->prev = ctx->active_entry_head->prev;
1851 					ctx->active_entry_head->prev->next = entry;
1852 					ctx->active_entry_head->prev = entry;
1853 					entry->next = ctx->active_entry_head;
1854 				} else {
1855 					ASSERT(ctx->active_entry_count == 0);
1856 					entry->prev = entry->next = entry;
1857 					ctx->active_entry_head = entry;
1858 				}
1859 				ctx->active_entry_count++;
1860 			} else {
1861 				DHD_ERROR(("%s():%d, entry(%d)\n", __FUNCTION__, __LINE__,
1862 					(int)(entry - &ctx->destination_entries.nodes[0])));
1863 			}
1864 		}
1865 	} else if (action == eWLFC_MAC_ENTRY_ACTION_DEL) {
1866 		/* When the entry is deleted, the packets that are queued in the entry must be
1867 		   cleanup. The cleanup action should be before the occupied is set as 0.
1868 		*/
1869 		_dhd_wlfc_cleanup(ctx->dhdp, fn, arg);
1870 		_dhd_wlfc_flow_control_check(ctx, &entry->psq, ifid);
1871 
1872 		entry->occupied = 0;
1873 		entry->state = WLFC_STATE_CLOSE;
1874 		memset(&entry->ea[0], 0, ETHER_ADDR_LEN);
1875 
1876 		if (entry->next) {
1877 			/* not floating, remove from Q */
1878 			if (ctx->active_entry_count <= 1) {
1879 				/* last item */
1880 				ctx->active_entry_head = NULL;
1881 				ctx->active_entry_count = 0;
1882 			} else {
1883 				entry->prev->next = entry->next;
1884 				entry->next->prev = entry->prev;
1885 				if (entry == ctx->active_entry_head) {
1886 					ctx->active_entry_head = entry->next;
1887 				}
1888 				ctx->active_entry_count--;
1889 			}
1890 			entry->next = entry->prev = NULL;
1891 		} else {
1892 			DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
1893 		}
1894 	}
1895 	return rc;
1896 } /* _dhd_wlfc_mac_entry_update */
1897 
1898 
1899 #ifdef LIMIT_BORROW
1900 
1901 /** LIMIT_BORROW specific function */
1902 static int
_dhd_wlfc_borrow_credit(athost_wl_status_info_t * ctx,int highest_lender_ac,int borrower_ac,bool bBorrowAll)1903 _dhd_wlfc_borrow_credit(athost_wl_status_info_t* ctx, int highest_lender_ac, int borrower_ac,
1904 	bool bBorrowAll)
1905 {
1906 	int lender_ac, borrow_limit = 0;
1907 	int rc = -1;
1908 
1909 	if (ctx == NULL) {
1910 		DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
1911 		return -1;
1912 	}
1913 
1914 	/* Borrow from lowest priority available AC (including BC/MC credits) */
1915 	for (lender_ac = 0; lender_ac <= highest_lender_ac; lender_ac++) {
1916 		if (!bBorrowAll) {
1917 			borrow_limit = ctx->Init_FIFO_credit[lender_ac]/WLFC_BORROW_LIMIT_RATIO;
1918 		} else {
1919 			borrow_limit = 0;
1920 		}
1921 
1922 		if (ctx->FIFO_credit[lender_ac] > borrow_limit) {
1923 			ctx->credits_borrowed[borrower_ac][lender_ac]++;
1924 			ctx->FIFO_credit[lender_ac]--;
1925 			rc = lender_ac;
1926 			break;
1927 		}
1928 	}
1929 
1930 	return rc;
1931 }
1932 
1933 /** LIMIT_BORROW specific function */
_dhd_wlfc_return_credit(athost_wl_status_info_t * ctx,int lender_ac,int borrower_ac)1934 static int _dhd_wlfc_return_credit(athost_wl_status_info_t* ctx, int lender_ac, int borrower_ac)
1935 {
1936 	if ((ctx == NULL) || (lender_ac < 0) || (lender_ac > AC_COUNT) ||
1937 		(borrower_ac < 0) || (borrower_ac > AC_COUNT)) {
1938 		DHD_ERROR(("Error: %s():%d, ctx(%p), lender_ac(%d), borrower_ac(%d)\n",
1939 			__FUNCTION__, __LINE__, ctx, lender_ac, borrower_ac));
1940 
1941 		return BCME_BADARG;
1942 	}
1943 
1944 	ctx->credits_borrowed[borrower_ac][lender_ac]--;
1945 	ctx->FIFO_credit[lender_ac]++;
1946 
1947 	return BCME_OK;
1948 }
1949 
1950 #endif /* LIMIT_BORROW */
1951 
1952 /**
1953  * Called on an interface event (WLC_E_IF) indicated by firmware.
1954  *     @param action : eg eWLFC_MAC_ENTRY_ACTION_UPDATE or eWLFC_MAC_ENTRY_ACTION_ADD
1955  */
1956 static int
_dhd_wlfc_interface_entry_update(void * state,uint8 action,uint8 ifid,uint8 iftype,uint8 * ea)1957 _dhd_wlfc_interface_entry_update(void* state,
1958 	uint8 action, uint8 ifid, uint8 iftype, uint8* ea)
1959 {
1960 	athost_wl_status_info_t* ctx = (athost_wl_status_info_t*)state;
1961 	wlfc_mac_descriptor_t* entry;
1962 
1963 	if (ifid >= WLFC_MAX_IFNUM)
1964 		return BCME_BADARG;
1965 
1966 	entry = &ctx->destination_entries.interfaces[ifid];
1967 
1968 	return _dhd_wlfc_mac_entry_update(ctx, entry, action, ifid, iftype, ea,
1969 		_dhd_wlfc_ifpkt_fn, &ifid);
1970 }
1971 
1972 /**
1973  * Called eg on receiving a WLC_E_BCMC_CREDIT_SUPPORT event from the dongle (broadcast/multicast
1974  * specific)
1975  */
1976 static int
_dhd_wlfc_BCMCCredit_support_update(void * state)1977 _dhd_wlfc_BCMCCredit_support_update(void* state)
1978 {
1979 	athost_wl_status_info_t* ctx = (athost_wl_status_info_t*)state;
1980 
1981 	ctx->bcmc_credit_supported = TRUE;
1982 	return BCME_OK;
1983 }
1984 
1985 /** Called eg on receiving a WLC_E_FIFO_CREDIT_MAP event from the dongle */
1986 static int
_dhd_wlfc_FIFOcreditmap_update(void * state,uint8 * credits)1987 _dhd_wlfc_FIFOcreditmap_update(void* state, uint8* credits)
1988 {
1989 	athost_wl_status_info_t* ctx = (athost_wl_status_info_t*)state;
1990 	int i;
1991 
1992 	for (i = 0; i <= 4; i++) {
1993 		if (ctx->Init_FIFO_credit[i] != ctx->FIFO_credit[i]) {
1994 			DHD_ERROR(("%s: credit[i] is not returned, (%d %d)\n",
1995 				__FUNCTION__, ctx->Init_FIFO_credit[i], ctx->FIFO_credit[i]));
1996 		}
1997 	}
1998 
1999 	/* update the AC FIFO credit map */
2000 	ctx->FIFO_credit[0] += (credits[0] - ctx->Init_FIFO_credit[0]);
2001 	ctx->FIFO_credit[1] += (credits[1] - ctx->Init_FIFO_credit[1]);
2002 	ctx->FIFO_credit[2] += (credits[2] - ctx->Init_FIFO_credit[2]);
2003 	ctx->FIFO_credit[3] += (credits[3] - ctx->Init_FIFO_credit[3]);
2004 	ctx->FIFO_credit[4] += (credits[4] - ctx->Init_FIFO_credit[4]);
2005 
2006 	ctx->Init_FIFO_credit[0] = credits[0];
2007 	ctx->Init_FIFO_credit[1] = credits[1];
2008 	ctx->Init_FIFO_credit[2] = credits[2];
2009 	ctx->Init_FIFO_credit[3] = credits[3];
2010 	ctx->Init_FIFO_credit[4] = credits[4];
2011 
2012 	/* credit for ATIM FIFO is not used yet. */
2013 	ctx->Init_FIFO_credit[5] = ctx->FIFO_credit[5] = 0;
2014 
2015 	return BCME_OK;
2016 }
2017 
2018 /**
2019  * Called during committing of a transmit packet from the OS DHD layer to the next layer towards
2020  * the dongle (eg the DBUS layer). All transmit packets flow via this function to the next layer.
2021  *
2022  *     @param[in/out] ctx      Driver specific flow control administration
2023  *     @param[in] ac           Access Category (QoS) of called supplied packet
2024  *     @param[in] commit_info  Contains eg the packet to send
2025  *     @param[in] fcommit      Function pointer to transmit function of next software layer
2026  *     @param[in] commit_ctx   Opaque context used when calling next layer
2027  */
2028 static int
_dhd_wlfc_handle_packet_commit(athost_wl_status_info_t * ctx,int ac,dhd_wlfc_commit_info_t * commit_info,f_commitpkt_t fcommit,void * commit_ctx)2029 _dhd_wlfc_handle_packet_commit(athost_wl_status_info_t* ctx, int ac,
2030     dhd_wlfc_commit_info_t *commit_info, f_commitpkt_t fcommit, void* commit_ctx)
2031 {
2032 	uint32 hslot;
2033 	int	rc;
2034 	dhd_pub_t *dhdp = (dhd_pub_t *)(ctx->dhdp);
2035 
2036 	/*
2037 		if ac_fifo_credit_spent = 0
2038 
2039 		This packet will not count against the FIFO credit.
2040 		To ensure the txstatus corresponding to this packet
2041 		does not provide an implied credit (default behavior)
2042 		mark the packet accordingly.
2043 
2044 		if ac_fifo_credit_spent = 1
2045 
2046 		This is a normal packet and it counts against the FIFO
2047 		credit count.
2048 	*/
2049 	DHD_PKTTAG_SETCREDITCHECK(PKTTAG(commit_info->p), commit_info->ac_fifo_credit_spent);
2050 	rc = _dhd_wlfc_pretx_pktprocess(ctx, commit_info->mac_entry, &commit_info->p,
2051 	     commit_info->needs_hdr, &hslot);
2052 
2053 	if (rc == BCME_OK) {
2054 		rc = fcommit(commit_ctx, commit_info->p);
2055 		if (rc == BCME_OK) {
2056 			uint8 gen = WL_TXSTATUS_GET_GENERATION(
2057 				DHD_PKTTAG_H2DTAG(PKTTAG(commit_info->p)));
2058 			ctx->stats.pkt2bus++;
2059 			if (commit_info->ac_fifo_credit_spent || (ac == AC_COUNT)) {
2060 				ctx->stats.send_pkts[ac]++;
2061 				WLFC_HOST_FIFO_CREDIT_INC_SENTCTRS(ctx, ac);
2062 			}
2063 
2064 			if (gen != commit_info->mac_entry->generation) {
2065 				/* will be suppressed back by design */
2066 				if (!commit_info->mac_entry->suppressed) {
2067 					commit_info->mac_entry->suppressed = TRUE;
2068 				}
2069 				commit_info->mac_entry->suppr_transit_count++;
2070 			}
2071 			commit_info->mac_entry->transit_count++;
2072 			commit_info->mac_entry->onbus_pkts_count++;
2073 		} else if (commit_info->needs_hdr) {
2074 			if (!WLFC_GET_AFQ(dhdp->wlfc_mode)) {
2075 				void *pout = NULL;
2076 				/* pop hanger for delayed packet */
2077 				_dhd_wlfc_hanger_poppkt(ctx->hanger, WL_TXSTATUS_GET_HSLOT(
2078 					DHD_PKTTAG_H2DTAG(PKTTAG(commit_info->p))), &pout, TRUE);
2079 				ASSERT(commit_info->p == pout);
2080 			}
2081 		}
2082 	} else {
2083 		ctx->stats.generic_error++;
2084 	}
2085 
2086 	if (rc != BCME_OK) {
2087 		/*
2088 		   pretx pkt process or bus commit has failed, rollback.
2089 		   - remove wl-header for a delayed packet
2090 		   - save wl-header header for suppressed packets
2091 		   - reset credit check flag
2092 		*/
2093 		_dhd_wlfc_rollback_packet_toq(ctx, commit_info->p, commit_info->pkt_type, hslot);
2094 		DHD_PKTTAG_SETCREDITCHECK(PKTTAG(commit_info->p), 0);
2095 	}
2096 
2097 	return rc;
2098 } /* _dhd_wlfc_handle_packet_commit */
2099 
2100 /** Returns remote MAC descriptor for caller supplied MAC address */
2101 static uint8
_dhd_wlfc_find_mac_desc_id_from_mac(dhd_pub_t * dhdp,uint8 * ea)2102 _dhd_wlfc_find_mac_desc_id_from_mac(dhd_pub_t *dhdp, uint8 *ea)
2103 {
2104 	wlfc_mac_descriptor_t* table =
2105 		((athost_wl_status_info_t*)dhdp->wlfc_state)->destination_entries.nodes;
2106 	uint8 table_index;
2107 
2108 	if (ea != NULL) {
2109 		for (table_index = 0; table_index < WLFC_MAC_DESC_TABLE_SIZE; table_index++) {
2110 			if ((memcmp(ea, &table[table_index].ea[0], ETHER_ADDR_LEN) == 0) &&
2111 				table[table_index].occupied)
2112 				return table_index;
2113 		}
2114 	}
2115 	return WLFC_MAC_DESC_ID_INVALID;
2116 }
2117 
2118 /**
2119  * Called when the host receives a WLFC_CTL_TYPE_TXSTATUS event from the dongle, indicating the
2120  * status of a frame that the dongle attempted to transmit over the wireless medium.
2121  */
2122 static int
dhd_wlfc_suppressed_acked_update(dhd_pub_t * dhd,uint16 hslot,uint8 prec,uint8 hcnt)2123 dhd_wlfc_suppressed_acked_update(dhd_pub_t *dhd, uint16 hslot, uint8 prec, uint8 hcnt)
2124 {
2125 	athost_wl_status_info_t* ctx;
2126 	wlfc_mac_descriptor_t* entry = NULL;
2127 	struct pktq *pq;
2128 	struct pktq_prec *q;
2129 	void *p, *b;
2130 
2131 	if (!dhd) {
2132 		DHD_ERROR(("%s: dhd(%p)\n", __FUNCTION__, dhd));
2133 		return BCME_BADARG;
2134 	}
2135 	ctx = (athost_wl_status_info_t*)dhd->wlfc_state;
2136 	if (!ctx) {
2137 		DHD_ERROR(("%s: ctx(%p)\n", __FUNCTION__, ctx));
2138 		return BCME_ERROR;
2139 	}
2140 
2141 	ASSERT(hslot < (WLFC_MAC_DESC_TABLE_SIZE + WLFC_MAX_IFNUM + 1));
2142 
2143 	if (hslot < WLFC_MAC_DESC_TABLE_SIZE)
2144 		entry  = &ctx->destination_entries.nodes[hslot];
2145 	else if (hslot < (WLFC_MAC_DESC_TABLE_SIZE + WLFC_MAX_IFNUM))
2146 		entry = &ctx->destination_entries.interfaces[hslot - WLFC_MAC_DESC_TABLE_SIZE];
2147 	else
2148 		entry = &ctx->destination_entries.other;
2149 
2150 	pq = &entry->psq;
2151 
2152 	ASSERT(((prec << 1) + 1) < pq->num_prec);
2153 
2154 	q = &pq->q[((prec << 1) + 1)];
2155 
2156 	b = NULL;
2157 	p = q->head;
2158 
2159 	while (p && (hcnt != WL_TXSTATUS_GET_FREERUNCTR(DHD_PKTTAG_H2DTAG(PKTTAG(p))))) {
2160 		b = p;
2161 		p = PKTLINK(p);
2162 	}
2163 
2164 	if (p == NULL) {
2165 		/* none is matched */
2166 		if (b) {
2167 			DHD_ERROR(("%s: can't find matching seq(%d)\n", __FUNCTION__, hcnt));
2168 		} else {
2169 			DHD_ERROR(("%s: queue is empty\n", __FUNCTION__));
2170 		}
2171 
2172 		return BCME_ERROR;
2173 	}
2174 
2175 	if (!b) {
2176 		/* head packet is matched */
2177 		if ((q->head = PKTLINK(p)) == NULL) {
2178 			q->tail = NULL;
2179 		}
2180 	} else {
2181 		/* middle packet is matched */
2182 		PKTSETLINK(b, PKTLINK(p));
2183 		if (PKTLINK(p) == NULL) {
2184 			q->tail = b;
2185 		}
2186 	}
2187 
2188 	q->len--;
2189 	pq->len--;
2190 	ctx->pkt_cnt_in_q[DHD_PKTTAG_IF(PKTTAG(p))][prec]--;
2191 	ctx->pkt_cnt_per_ac[prec]--;
2192 
2193 	PKTSETLINK(p, NULL);
2194 
2195 	if (WLFC_GET_AFQ(dhd->wlfc_mode)) {
2196 		_dhd_wlfc_enque_afq(ctx, p);
2197 	} else {
2198 		_dhd_wlfc_hanger_pushpkt(ctx->hanger, p, hslot);
2199 	}
2200 
2201 	entry->transit_count++;
2202 
2203 	return BCME_OK;
2204 }
2205 
2206 static int
_dhd_wlfc_compressed_txstatus_update(dhd_pub_t * dhd,uint8 * pkt_info,uint8 len,void ** p_mac)2207 _dhd_wlfc_compressed_txstatus_update(dhd_pub_t *dhd, uint8* pkt_info, uint8 len, void** p_mac)
2208 {
2209 	uint8 status_flag_ori, status_flag;
2210 	uint32 status;
2211 	int ret = BCME_OK;
2212 	int remove_from_hanger_ori, remove_from_hanger = 1;
2213 	void* pktbuf = NULL;
2214 	uint8 fifo_id = 0, gen = 0, count = 0, hcnt;
2215 	uint16 hslot;
2216 	wlfc_mac_descriptor_t* entry = NULL;
2217 	athost_wl_status_info_t* wlfc = (athost_wl_status_info_t*)dhd->wlfc_state;
2218 	uint16 seq = 0, seq_fromfw = 0, seq_num = 0;
2219 
2220 	memcpy(&status, pkt_info, sizeof(uint32));
2221 	status = ltoh32(status);
2222 	status_flag = WL_TXSTATUS_GET_FLAGS(status);
2223 	hcnt = WL_TXSTATUS_GET_FREERUNCTR(status);
2224 	hslot = WL_TXSTATUS_GET_HSLOT(status);
2225 	fifo_id = WL_TXSTATUS_GET_FIFO(status);
2226 	gen = WL_TXSTATUS_GET_GENERATION(status);
2227 
2228 	if (WLFC_GET_REUSESEQ(dhd->wlfc_mode)) {
2229 		memcpy(&seq, pkt_info + WLFC_CTL_VALUE_LEN_TXSTATUS, WLFC_CTL_VALUE_LEN_SEQ);
2230 		seq = ltoh16(seq);
2231 		seq_fromfw = GET_WL_HAS_ASSIGNED_SEQ(seq);
2232 		seq_num = WL_SEQ_GET_NUM(seq);
2233 	}
2234 
2235 	wlfc->stats.txstatus_in += len;
2236 
2237 	if (status_flag == WLFC_CTL_PKTFLAG_DISCARD) {
2238 		wlfc->stats.pkt_freed += len;
2239 	} else if (status_flag == WLFC_CTL_PKTFLAG_DISCARD_NOACK) {
2240 		wlfc->stats.pkt_freed += len;
2241 	} else if (status_flag == WLFC_CTL_PKTFLAG_D11SUPPRESS) {
2242 		wlfc->stats.d11_suppress += len;
2243 		remove_from_hanger = 0;
2244 	} else if (status_flag == WLFC_CTL_PKTFLAG_WLSUPPRESS) {
2245 		wlfc->stats.wl_suppress += len;
2246 		remove_from_hanger = 0;
2247 	} else if (status_flag == WLFC_CTL_PKTFLAG_TOSSED_BYWLC) {
2248 		wlfc->stats.wlc_tossed_pkts += len;
2249 	} else if (status_flag == WLFC_CTL_PKTFLAG_SUPPRESS_ACKED) {
2250 		wlfc->stats.pkt_freed += len;
2251 	}
2252 
2253 	if (dhd->proptxstatus_txstatus_ignore) {
2254 		if (!remove_from_hanger) {
2255 			DHD_ERROR(("suppress txstatus: %d\n", status_flag));
2256 		}
2257 		return BCME_OK;
2258 	}
2259 
2260 	status_flag_ori = status_flag;
2261 	remove_from_hanger_ori = remove_from_hanger;
2262 
2263 	while (count < len) {
2264 		if (status_flag == WLFC_CTL_PKTFLAG_SUPPRESS_ACKED) {
2265 			dhd_wlfc_suppressed_acked_update(dhd, hslot, fifo_id, hcnt);
2266 		}
2267 		if (WLFC_GET_AFQ(dhd->wlfc_mode)) {
2268 			ret = _dhd_wlfc_deque_afq(wlfc, hslot, hcnt, fifo_id, &pktbuf);
2269 		} else {
2270 			status_flag = status_flag_ori;
2271 			remove_from_hanger = remove_from_hanger_ori;
2272 			ret = _dhd_wlfc_hanger_poppkt(wlfc->hanger, hslot, &pktbuf, FALSE);
2273 			if (!pktbuf) {
2274 				_dhd_wlfc_hanger_free_pkt(wlfc, hslot,
2275 					WLFC_HANGER_PKT_STATE_TXSTATUS, -1);
2276 				goto cont;
2277 			} else {
2278 				wlfc_hanger_t* h = (wlfc_hanger_t*)wlfc->hanger;
2279 				if (h->items[hslot].state == WLFC_HANGER_ITEM_STATE_FLUSHED) {
2280 					status_flag = WLFC_CTL_PKTFLAG_DISCARD;
2281 					remove_from_hanger = 1;
2282 				}
2283 			}
2284 		}
2285 
2286 		if ((ret != BCME_OK) || !pktbuf) {
2287 			goto cont;
2288 		}
2289 
2290 		bcm_pkt_validate_chk(pktbuf);
2291 
2292 		/* set fifo_id to correct value because not all FW does that */
2293 		fifo_id = DHD_PKTTAG_FIFO(PKTTAG(pktbuf));
2294 
2295 		entry = _dhd_wlfc_find_table_entry(wlfc, pktbuf);
2296 
2297 		if (!remove_from_hanger) {
2298 			/* this packet was suppressed */
2299 			if (!entry->suppressed || (entry->generation != gen)) {
2300 				if (!entry->suppressed) {
2301 					entry->suppr_transit_count = entry->transit_count;
2302 					if (p_mac) {
2303 						*p_mac = entry;
2304 					}
2305 				} else {
2306 					DHD_ERROR(("gen(%d), entry->generation(%d)\n",
2307 						gen, entry->generation));
2308 				}
2309 				entry->suppressed = TRUE;
2310 
2311 			}
2312 			entry->generation = gen;
2313 		}
2314 
2315 #ifdef PROP_TXSTATUS_DEBUG
2316 		if (!WLFC_GET_AFQ(dhd->wlfc_mode))
2317 		{
2318 			uint32 new_t = OSL_SYSUPTIME();
2319 			uint32 old_t;
2320 			uint32 delta;
2321 			old_t = ((wlfc_hanger_t*)(wlfc->hanger))->items[hslot].push_time;
2322 
2323 
2324 			wlfc->stats.latency_sample_count++;
2325 			if (new_t > old_t)
2326 				delta = new_t - old_t;
2327 			else
2328 				delta = 0xffffffff + new_t - old_t;
2329 			wlfc->stats.total_status_latency += delta;
2330 			wlfc->stats.latency_most_recent = delta;
2331 
2332 			wlfc->stats.deltas[wlfc->stats.idx_delta++] = delta;
2333 			if (wlfc->stats.idx_delta == sizeof(wlfc->stats.deltas)/sizeof(uint32))
2334 				wlfc->stats.idx_delta = 0;
2335 		}
2336 #endif /* PROP_TXSTATUS_DEBUG */
2337 
2338 		/* pick up the implicit credit from this packet */
2339 		if (DHD_PKTTAG_CREDITCHECK(PKTTAG(pktbuf))) {
2340 			_dhd_wlfc_return_implied_credit(wlfc, pktbuf);
2341 		} else {
2342 			/*
2343 			if this packet did not count against FIFO credit, it must have
2344 			taken a requested_credit from the destination entry (for pspoll etc.)
2345 			*/
2346 			if (!DHD_PKTTAG_ONETIMEPKTRQST(PKTTAG(pktbuf))) {
2347 				entry->requested_credit++;
2348 #if defined(DHD_WLFC_THREAD)
2349 				_dhd_wlfc_thread_wakeup(dhd);
2350 #endif /* DHD_WLFC_THREAD */
2351 			}
2352 #ifdef PROP_TXSTATUS_DEBUG
2353 			entry->dstncredit_acks++;
2354 #endif
2355 		}
2356 
2357 		if ((status_flag == WLFC_CTL_PKTFLAG_D11SUPPRESS) ||
2358 			(status_flag == WLFC_CTL_PKTFLAG_WLSUPPRESS)) {
2359 			/* save generation bit inside packet */
2360 			WL_TXSTATUS_SET_GENERATION(DHD_PKTTAG_H2DTAG(PKTTAG(pktbuf)), gen);
2361 
2362 			if (WLFC_GET_REUSESEQ(dhd->wlfc_mode)) {
2363 				WL_SEQ_SET_REUSE(DHD_PKTTAG_H2DSEQ(PKTTAG(pktbuf)), seq_fromfw);
2364 				WL_SEQ_SET_NUM(DHD_PKTTAG_H2DSEQ(PKTTAG(pktbuf)), seq_num);
2365 			}
2366 
2367 			ret = _dhd_wlfc_enque_suppressed(wlfc, fifo_id, pktbuf);
2368 			if (ret != BCME_OK) {
2369 				/* delay q is full, drop this packet */
2370 				DHD_WLFC_QMON_COMPLETE(entry);
2371 				_dhd_wlfc_prec_drop(dhd, (fifo_id << 1) + 1, pktbuf, FALSE);
2372 			} else {
2373 				if (!WLFC_GET_AFQ(dhd->wlfc_mode)) {
2374 					/* Mark suppressed to avoid a double free
2375 					during wlfc cleanup
2376 					*/
2377 					_dhd_wlfc_hanger_mark_suppressed(wlfc->hanger, hslot, gen);
2378 				}
2379 			}
2380 		} else {
2381 
2382 			DHD_WLFC_QMON_COMPLETE(entry);
2383 
2384 			if (!WLFC_GET_AFQ(dhd->wlfc_mode)) {
2385 				_dhd_wlfc_hanger_free_pkt(wlfc, hslot,
2386 					WLFC_HANGER_PKT_STATE_TXSTATUS, TRUE);
2387 			} else {
2388 				dhd_txcomplete(dhd, pktbuf, TRUE);
2389 				wlfc->pkt_cnt_in_drv[DHD_PKTTAG_IF(PKTTAG(pktbuf))]
2390 					[DHD_PKTTAG_FIFO(PKTTAG(pktbuf))]--;
2391 				wlfc->stats.pktout++;
2392 				/* free the packet */
2393 				PKTFREE(wlfc->osh, pktbuf, TRUE);
2394 			}
2395 		}
2396 		/* pkt back from firmware side */
2397 		if (entry->transit_count)
2398 			entry->transit_count--;
2399 		if (entry->suppr_transit_count) {
2400 			entry->suppr_transit_count--;
2401 			if (entry->suppressed &&
2402 				(!entry->onbus_pkts_count) &&
2403 				(!entry->suppr_transit_count))
2404 				entry->suppressed = FALSE;
2405 		}
2406 
2407 cont:
2408 		hcnt = (hcnt + 1) & WL_TXSTATUS_FREERUNCTR_MASK;
2409 		if (!WLFC_GET_AFQ(dhd->wlfc_mode)) {
2410 			hslot = (hslot + 1) & WL_TXSTATUS_HSLOT_MASK;
2411 		}
2412 
2413 		if (WLFC_GET_REUSESEQ(dhd->wlfc_mode) && seq_fromfw) {
2414 			seq_num = (seq_num + 1) & WL_SEQ_NUM_MASK;
2415 		}
2416 
2417 		count++;
2418 	}
2419 
2420 	return BCME_OK;
2421 } /* _dhd_wlfc_compressed_txstatus_update */
2422 
2423 /**
2424  * Called when eg host receives a 'WLFC_CTL_TYPE_FIFO_CREDITBACK' event from the dongle.
2425  *    @param[in] credits caller supplied credit that will be added to the host credit.
2426  */
2427 static int
_dhd_wlfc_fifocreditback_indicate(dhd_pub_t * dhd,uint8 * credits)2428 _dhd_wlfc_fifocreditback_indicate(dhd_pub_t *dhd, uint8* credits)
2429 {
2430 	int i;
2431 	athost_wl_status_info_t* wlfc = (athost_wl_status_info_t*)dhd->wlfc_state;
2432 	for (i = 0; i < WLFC_CTL_VALUE_LEN_FIFO_CREDITBACK; i++) {
2433 #ifdef PROP_TXSTATUS_DEBUG
2434 		wlfc->stats.fifo_credits_back[i] += credits[i];
2435 #endif
2436 
2437 		/* update FIFO credits */
2438 		if (dhd->proptxstatus_mode == WLFC_FCMODE_EXPLICIT_CREDIT)
2439 		{
2440 			int lender; /* Note that borrower is i */
2441 
2442 			/* Return credits to highest priority lender first */
2443 			for (lender = AC_COUNT; (lender >= 0) && (credits[i] > 0); lender--) {
2444 				if (wlfc->credits_borrowed[i][lender] > 0) {
2445 					if (credits[i] >= wlfc->credits_borrowed[i][lender]) {
2446 						credits[i] -=
2447 							(uint8)wlfc->credits_borrowed[i][lender];
2448 						wlfc->FIFO_credit[lender] +=
2449 						    wlfc->credits_borrowed[i][lender];
2450 						wlfc->credits_borrowed[i][lender] = 0;
2451 					} else {
2452 						wlfc->credits_borrowed[i][lender] -= credits[i];
2453 						wlfc->FIFO_credit[lender] += credits[i];
2454 						credits[i] = 0;
2455 					}
2456 				}
2457 			}
2458 
2459 			/* If we have more credits left over, these must belong to the AC */
2460 			if (credits[i] > 0) {
2461 				wlfc->FIFO_credit[i] += credits[i];
2462 			}
2463 
2464 			if (wlfc->FIFO_credit[i] > wlfc->Init_FIFO_credit[i]) {
2465 				wlfc->FIFO_credit[i] = wlfc->Init_FIFO_credit[i];
2466 			}
2467 		}
2468 	}
2469 
2470 #if defined(DHD_WLFC_THREAD)
2471 	_dhd_wlfc_thread_wakeup(dhd);
2472 #endif /* defined(DHD_WLFC_THREAD) */
2473 
2474 	return BCME_OK;
2475 } /* _dhd_wlfc_fifocreditback_indicate */
2476 
2477 #ifndef BCMDBUS
2478 /** !BCMDBUS specific function */
2479 static void
_dhd_wlfc_suppress_txq(dhd_pub_t * dhd,f_processpkt_t fn,void * arg)2480 _dhd_wlfc_suppress_txq(dhd_pub_t *dhd, f_processpkt_t fn, void *arg)
2481 {
2482 	athost_wl_status_info_t* wlfc = (athost_wl_status_info_t*)dhd->wlfc_state;
2483 	wlfc_mac_descriptor_t* entry;
2484 	int prec;
2485 	void *pkt = NULL, *head = NULL, *tail = NULL;
2486 	struct pktq *txq = (struct pktq *)dhd_bus_txq(dhd->bus);
2487 	uint8	results[WLFC_CTL_VALUE_LEN_TXSTATUS+WLFC_CTL_VALUE_LEN_SEQ];
2488 	uint8 credits[WLFC_CTL_VALUE_LEN_FIFO_CREDITBACK] = {0};
2489 	uint32 htod = 0;
2490 	uint16 htodseq = 0;
2491 	bool bCreditUpdate = FALSE;
2492 
2493 	dhd_os_sdlock_txq(dhd);
2494 	for (prec = 0; prec < txq->num_prec; prec++) {
2495 		while ((pkt = _dhd_wlfc_pktq_pdeq_with_fn(txq, prec, fn, arg))) {
2496 			if (!head) {
2497 				head = pkt;
2498 			}
2499 			if (tail) {
2500 				PKTSETLINK(tail, pkt);
2501 			}
2502 			tail = pkt;
2503 		}
2504 	}
2505 	dhd_os_sdunlock_txq(dhd);
2506 
2507 	while ((pkt = head)) {
2508 		head = PKTLINK(pkt);
2509 		PKTSETLINK(pkt, NULL);
2510 
2511 		entry = _dhd_wlfc_find_table_entry(wlfc, pkt);
2512 		if (!entry) {
2513 			PKTFREE(dhd->osh, pkt, TRUE);
2514 			continue;
2515 		}
2516 		if (entry->onbus_pkts_count > 0) {
2517 			entry->onbus_pkts_count--;
2518 		}
2519 		if (entry->suppressed &&
2520 				(!entry->onbus_pkts_count) &&
2521 				(!entry->suppr_transit_count)) {
2522 			entry->suppressed = FALSE;
2523 		}
2524 		/* fake a suppression txstatus */
2525 		htod = DHD_PKTTAG_H2DTAG(PKTTAG(pkt));
2526 		WL_TXSTATUS_SET_FLAGS(htod, WLFC_CTL_PKTFLAG_WLSUPPRESS);
2527 		WL_TXSTATUS_SET_GENERATION(htod, entry->generation);
2528 		htod = htol32(htod);
2529 		memcpy(results, &htod, WLFC_CTL_VALUE_LEN_TXSTATUS);
2530 		if (WLFC_GET_REUSESEQ(dhd->wlfc_mode)) {
2531 			htodseq = DHD_PKTTAG_H2DSEQ(PKTTAG(pkt));
2532 			if (IS_WL_TO_REUSE_SEQ(htodseq)) {
2533 				SET_WL_HAS_ASSIGNED_SEQ(htodseq);
2534 				RESET_WL_TO_REUSE_SEQ(htodseq);
2535 			}
2536 			htodseq = htol16(htodseq);
2537 			memcpy(results + WLFC_CTL_VALUE_LEN_TXSTATUS, &htodseq,
2538 				WLFC_CTL_VALUE_LEN_SEQ);
2539 		}
2540 		if (WLFC_GET_AFQ(dhd->wlfc_mode)) {
2541 			_dhd_wlfc_enque_afq(wlfc, pkt);
2542 		}
2543 		_dhd_wlfc_compressed_txstatus_update(dhd, results, 1, NULL);
2544 
2545 		/* fake a fifo credit back */
2546 		if (DHD_PKTTAG_CREDITCHECK(PKTTAG(pkt))) {
2547 			credits[DHD_PKTTAG_FIFO(PKTTAG(pkt))]++;
2548 			bCreditUpdate = TRUE;
2549 		}
2550 	}
2551 
2552 	if (bCreditUpdate) {
2553 		_dhd_wlfc_fifocreditback_indicate(dhd, credits);
2554 	}
2555 } /* _dhd_wlfc_suppress_txq */
2556 #endif /* !BCMDBUS */
2557 
2558 static int
_dhd_wlfc_dbg_senum_check(dhd_pub_t * dhd,uint8 * value)2559 _dhd_wlfc_dbg_senum_check(dhd_pub_t *dhd, uint8 *value)
2560 {
2561 	uint32 timestamp;
2562 
2563 	(void)dhd;
2564 
2565 	bcopy(&value[2], &timestamp, sizeof(uint32));
2566 	timestamp = ltoh32(timestamp);
2567 	DHD_INFO(("RXPKT: SEQ: %d, timestamp %d\n", value[1], timestamp));
2568 	return BCME_OK;
2569 }
2570 
2571 static int
_dhd_wlfc_rssi_indicate(dhd_pub_t * dhd,uint8 * rssi)2572 _dhd_wlfc_rssi_indicate(dhd_pub_t *dhd, uint8* rssi)
2573 {
2574 	(void)dhd;
2575 	(void)rssi;
2576 	return BCME_OK;
2577 }
2578 
2579 static void
_dhd_wlfc_add_requested_entry(athost_wl_status_info_t * wlfc,wlfc_mac_descriptor_t * entry)2580 _dhd_wlfc_add_requested_entry(athost_wl_status_info_t* wlfc, wlfc_mac_descriptor_t* entry)
2581 {
2582 	int i;
2583 
2584 	if (!wlfc || !entry) {
2585 		return;
2586 	}
2587 
2588 	for (i = 0; i < wlfc->requested_entry_count; i++) {
2589 		if (entry == wlfc->requested_entry[i]) {
2590 			break;
2591 		}
2592 	}
2593 
2594 	if (i == wlfc->requested_entry_count) {
2595 		/* no match entry found */
2596 		ASSERT(wlfc->requested_entry_count <= (WLFC_MAC_DESC_TABLE_SIZE-1));
2597 		wlfc->requested_entry[wlfc->requested_entry_count++] = entry;
2598 	}
2599 }
2600 
2601 /** called on eg receiving 'mac open' event from the dongle. */
2602 static void
_dhd_wlfc_remove_requested_entry(athost_wl_status_info_t * wlfc,wlfc_mac_descriptor_t * entry)2603 _dhd_wlfc_remove_requested_entry(athost_wl_status_info_t* wlfc, wlfc_mac_descriptor_t* entry)
2604 {
2605 	int i;
2606 
2607 	if (!wlfc || !entry) {
2608 		return;
2609 	}
2610 
2611 	for (i = 0; i < wlfc->requested_entry_count; i++) {
2612 		if (entry == wlfc->requested_entry[i]) {
2613 			break;
2614 		}
2615 	}
2616 
2617 	if (i < wlfc->requested_entry_count) {
2618 		/* found */
2619 		ASSERT(wlfc->requested_entry_count > 0);
2620 		wlfc->requested_entry_count--;
2621 		if (i != wlfc->requested_entry_count) {
2622 			wlfc->requested_entry[i] =
2623 				wlfc->requested_entry[wlfc->requested_entry_count];
2624 		}
2625 		wlfc->requested_entry[wlfc->requested_entry_count] = NULL;
2626 	}
2627 }
2628 
2629 /** called on eg receiving a WLFC_CTL_TYPE_MACDESC_ADD TLV from the dongle */
2630 static int
_dhd_wlfc_mac_table_update(dhd_pub_t * dhd,uint8 * value,uint8 type)2631 _dhd_wlfc_mac_table_update(dhd_pub_t *dhd, uint8* value, uint8 type)
2632 {
2633 	int rc;
2634 	athost_wl_status_info_t* wlfc = (athost_wl_status_info_t*)dhd->wlfc_state;
2635 	wlfc_mac_descriptor_t* table;
2636 	uint8 existing_index;
2637 	uint8 table_index;
2638 	uint8 ifid;
2639 	uint8* ea;
2640 
2641 	WLFC_DBGMESG(("%s(), mac [%02x:%02x:%02x:%02x:%02x:%02x],%s,idx:%d,id:0x%02x\n",
2642 		__FUNCTION__, value[2], value[3], value[4], value[5], value[6], value[7],
2643 		((type == WLFC_CTL_TYPE_MACDESC_ADD) ? "ADD":"DEL"),
2644 		WLFC_MAC_DESC_GET_LOOKUP_INDEX(value[0]), value[0]));
2645 
2646 	table = wlfc->destination_entries.nodes;
2647 	table_index = WLFC_MAC_DESC_GET_LOOKUP_INDEX(value[0]);
2648 	ifid = value[1];
2649 	ea = &value[2];
2650 
2651 	_dhd_wlfc_remove_requested_entry(wlfc, &table[table_index]);
2652 	if (type == WLFC_CTL_TYPE_MACDESC_ADD) {
2653 		existing_index = _dhd_wlfc_find_mac_desc_id_from_mac(dhd, &value[2]);
2654 		if ((existing_index != WLFC_MAC_DESC_ID_INVALID) &&
2655 			(existing_index != table_index) && table[existing_index].occupied) {
2656 			/*
2657 			there is an existing different entry, free the old one
2658 			and move it to new index if necessary.
2659 			*/
2660 			rc = _dhd_wlfc_mac_entry_update(wlfc, &table[existing_index],
2661 				eWLFC_MAC_ENTRY_ACTION_DEL, table[existing_index].interface_id,
2662 				table[existing_index].iftype, NULL, _dhd_wlfc_entrypkt_fn,
2663 				&table[existing_index]);
2664 		}
2665 
2666 		if (!table[table_index].occupied) {
2667 			/* this new MAC entry does not exist, create one */
2668 			table[table_index].mac_handle = value[0];
2669 			rc = _dhd_wlfc_mac_entry_update(wlfc, &table[table_index],
2670 				eWLFC_MAC_ENTRY_ACTION_ADD, ifid,
2671 				wlfc->destination_entries.interfaces[ifid].iftype,
2672 				ea, NULL, NULL);
2673 		} else {
2674 			/* the space should have been empty, but it's not */
2675 			wlfc->stats.mac_update_failed++;
2676 		}
2677 	}
2678 
2679 	if (type == WLFC_CTL_TYPE_MACDESC_DEL) {
2680 		if (table[table_index].occupied) {
2681 				rc = _dhd_wlfc_mac_entry_update(wlfc, &table[table_index],
2682 					eWLFC_MAC_ENTRY_ACTION_DEL, ifid,
2683 					wlfc->destination_entries.interfaces[ifid].iftype,
2684 					ea, _dhd_wlfc_entrypkt_fn, &table[table_index]);
2685 		} else {
2686 			/* the space should have been occupied, but it's not */
2687 			wlfc->stats.mac_update_failed++;
2688 		}
2689 	}
2690 	BCM_REFERENCE(rc);
2691 	return BCME_OK;
2692 } /* _dhd_wlfc_mac_table_update */
2693 
2694 /** Called on a 'mac open' or 'mac close' event indicated by the dongle */
2695 static int
_dhd_wlfc_psmode_update(dhd_pub_t * dhd,uint8 * value,uint8 type)2696 _dhd_wlfc_psmode_update(dhd_pub_t *dhd, uint8* value, uint8 type)
2697 {
2698 	/* Handle PS on/off indication */
2699 	athost_wl_status_info_t* wlfc = (athost_wl_status_info_t*)dhd->wlfc_state;
2700 	wlfc_mac_descriptor_t* table;
2701 	wlfc_mac_descriptor_t* desc; /* a table maps from mac handle to mac descriptor */
2702 	uint8 mac_handle = value[0];
2703 	int i;
2704 
2705 	table = wlfc->destination_entries.nodes;
2706 	desc = &table[WLFC_MAC_DESC_GET_LOOKUP_INDEX(mac_handle)];
2707 	if (desc->occupied) {
2708 		if (type == WLFC_CTL_TYPE_MAC_OPEN) {
2709 			desc->state = WLFC_STATE_OPEN;
2710 			desc->ac_bitmap = 0xff;
2711 			DHD_WLFC_CTRINC_MAC_OPEN(desc);
2712 			desc->requested_credit = 0;
2713 			desc->requested_packet = 0;
2714 			_dhd_wlfc_remove_requested_entry(wlfc, desc);
2715 		} else {
2716 			desc->state = WLFC_STATE_CLOSE;
2717 			DHD_WLFC_CTRINC_MAC_CLOSE(desc);
2718 			/* Indicate to firmware if there is any traffic pending. */
2719 			for (i = 0; i < AC_COUNT; i++) {
2720 				_dhd_wlfc_traffic_pending_check(wlfc, desc, i);
2721 			}
2722 		}
2723 	} else {
2724 		wlfc->stats.psmode_update_failed++;
2725 	}
2726 
2727 	return BCME_OK;
2728 } /* _dhd_wlfc_psmode_update */
2729 
2730 /** called upon receiving 'interface open' or 'interface close' event from the dongle */
2731 static int
_dhd_wlfc_interface_update(dhd_pub_t * dhd,uint8 * value,uint8 type)2732 _dhd_wlfc_interface_update(dhd_pub_t *dhd, uint8* value, uint8 type)
2733 {
2734 	/* Handle PS on/off indication */
2735 	athost_wl_status_info_t* wlfc = (athost_wl_status_info_t*)dhd->wlfc_state;
2736 	wlfc_mac_descriptor_t* table;
2737 	uint8 if_id = value[0];
2738 
2739 	if (if_id < WLFC_MAX_IFNUM) {
2740 		table = wlfc->destination_entries.interfaces;
2741 		if (table[if_id].occupied) {
2742 			if (type == WLFC_CTL_TYPE_INTERFACE_OPEN) {
2743 				table[if_id].state = WLFC_STATE_OPEN;
2744 				/* WLFC_DBGMESG(("INTERFACE[%d] OPEN\n", if_id)); */
2745 			} else {
2746 				table[if_id].state = WLFC_STATE_CLOSE;
2747 				/* WLFC_DBGMESG(("INTERFACE[%d] CLOSE\n", if_id)); */
2748 			}
2749 			return BCME_OK;
2750 		}
2751 	}
2752 	wlfc->stats.interface_update_failed++;
2753 
2754 	return BCME_OK;
2755 }
2756 
2757 /** Called on receiving a WLFC_CTL_TYPE_MAC_REQUEST_CREDIT TLV from the dongle */
2758 static int
_dhd_wlfc_credit_request(dhd_pub_t * dhd,uint8 * value)2759 _dhd_wlfc_credit_request(dhd_pub_t *dhd, uint8* value)
2760 {
2761 	athost_wl_status_info_t* wlfc = (athost_wl_status_info_t*)dhd->wlfc_state;
2762 	wlfc_mac_descriptor_t* table;
2763 	wlfc_mac_descriptor_t* desc;
2764 	uint8 mac_handle;
2765 	uint8 credit;
2766 
2767 	table = wlfc->destination_entries.nodes;
2768 	mac_handle = value[1];
2769 	credit = value[0];
2770 
2771 	desc = &table[WLFC_MAC_DESC_GET_LOOKUP_INDEX(mac_handle)];
2772 	if (desc->occupied) {
2773 		desc->requested_credit = credit;
2774 
2775 		desc->ac_bitmap = value[2] & (~(1<<AC_COUNT));
2776 		_dhd_wlfc_add_requested_entry(wlfc, desc);
2777 #if defined(DHD_WLFC_THREAD)
2778 		if (credit) {
2779 			_dhd_wlfc_thread_wakeup(dhd);
2780 		}
2781 #endif /* DHD_WLFC_THREAD */
2782 	} else {
2783 		wlfc->stats.credit_request_failed++;
2784 	}
2785 
2786 	return BCME_OK;
2787 }
2788 
2789 /** Called on receiving a WLFC_CTL_TYPE_MAC_REQUEST_PACKET TLV from the dongle */
2790 static int
_dhd_wlfc_packet_request(dhd_pub_t * dhd,uint8 * value)2791 _dhd_wlfc_packet_request(dhd_pub_t *dhd, uint8* value)
2792 {
2793 	athost_wl_status_info_t* wlfc = (athost_wl_status_info_t*)dhd->wlfc_state;
2794 	wlfc_mac_descriptor_t* table;
2795 	wlfc_mac_descriptor_t* desc;
2796 	uint8 mac_handle;
2797 	uint8 packet_count;
2798 
2799 	table = wlfc->destination_entries.nodes;
2800 	mac_handle = value[1];
2801 	packet_count = value[0];
2802 
2803 	desc = &table[WLFC_MAC_DESC_GET_LOOKUP_INDEX(mac_handle)];
2804 	if (desc->occupied) {
2805 		desc->requested_packet = packet_count;
2806 
2807 		desc->ac_bitmap = value[2] & (~(1<<AC_COUNT));
2808 		_dhd_wlfc_add_requested_entry(wlfc, desc);
2809 #if defined(DHD_WLFC_THREAD)
2810 		if (packet_count) {
2811 			_dhd_wlfc_thread_wakeup(dhd);
2812 		}
2813 #endif /* DHD_WLFC_THREAD */
2814 	} else {
2815 		wlfc->stats.packet_request_failed++;
2816 	}
2817 
2818 	return BCME_OK;
2819 }
2820 
2821 /** Called when host receives a WLFC_CTL_TYPE_HOST_REORDER_RXPKTS TLV from the dongle */
2822 static void
_dhd_wlfc_reorderinfo_indicate(uint8 * val,uint8 len,uchar * info_buf,uint * info_len)2823 _dhd_wlfc_reorderinfo_indicate(uint8 *val, uint8 len, uchar *info_buf, uint *info_len)
2824 {
2825 	if (info_len) {
2826 		/* Check copy length to avoid buffer overrun. In case of length exceeding
2827 		*  WLHOST_REORDERDATA_TOTLEN, return failure instead sending incomplete result
2828 		*  of length WLHOST_REORDERDATA_TOTLEN
2829 		*/
2830 		if ((info_buf) && (len <= WLHOST_REORDERDATA_TOTLEN)) {
2831 			bcopy(val, info_buf, len);
2832 			*info_len = len;
2833 		} else {
2834 			*info_len = 0;
2835 		}
2836 	}
2837 }
2838 
2839 /*
2840  * public functions
2841  */
2842 
dhd_wlfc_is_supported(dhd_pub_t * dhd)2843 bool dhd_wlfc_is_supported(dhd_pub_t *dhd)
2844 {
2845 	bool rc = TRUE;
2846 
2847 	if (dhd == NULL) {
2848 		DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
2849 		return FALSE;
2850 	}
2851 
2852 	dhd_os_wlfc_block(dhd);
2853 
2854 	if (!dhd->wlfc_state || (dhd->proptxstatus_mode == WLFC_FCMODE_NONE)) {
2855 		rc =  FALSE;
2856 	}
2857 
2858 	dhd_os_wlfc_unblock(dhd);
2859 
2860 	return rc;
2861 }
2862 
dhd_wlfc_enable(dhd_pub_t * dhd)2863 int dhd_wlfc_enable(dhd_pub_t *dhd)
2864 {
2865 	int i, rc = BCME_OK;
2866 	athost_wl_status_info_t* wlfc;
2867 
2868 	if (dhd == NULL) {
2869 		DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
2870 		return BCME_BADARG;
2871 	}
2872 
2873 	dhd_os_wlfc_block(dhd);
2874 
2875 	if (!dhd->wlfc_enabled || dhd->wlfc_state) {
2876 		rc = BCME_OK;
2877 		goto exit;
2878 	}
2879 
2880 	/* allocate space to track txstatus propagated from firmware */
2881 	dhd->wlfc_state = DHD_OS_PREALLOC(dhd, DHD_PREALLOC_DHD_WLFC_INFO,
2882 		sizeof(athost_wl_status_info_t));
2883 	if (dhd->wlfc_state == NULL) {
2884 		rc = BCME_NOMEM;
2885 		goto exit;
2886 	}
2887 
2888 	/* initialize state space */
2889 	wlfc = (athost_wl_status_info_t*)dhd->wlfc_state;
2890 	memset(wlfc, 0, sizeof(athost_wl_status_info_t));
2891 
2892 	/* remember osh & dhdp */
2893 	wlfc->osh = dhd->osh;
2894 	wlfc->dhdp = dhd;
2895 
2896 	if (!WLFC_GET_AFQ(dhd->wlfc_mode)) {
2897 		wlfc->hanger = _dhd_wlfc_hanger_create(dhd, WLFC_HANGER_MAXITEMS);
2898 		if (wlfc->hanger == NULL) {
2899 			DHD_OS_PREFREE(dhd, dhd->wlfc_state,
2900 				sizeof(athost_wl_status_info_t));
2901 			dhd->wlfc_state = NULL;
2902 			rc = BCME_NOMEM;
2903 			goto exit;
2904 		}
2905 	}
2906 
2907 	dhd->proptxstatus_mode = WLFC_FCMODE_EXPLICIT_CREDIT;
2908 	/* default to check rx pkt */
2909 	dhd->wlfc_rxpkt_chk = TRUE;
2910 	if (dhd->op_mode & DHD_FLAG_IBSS_MODE) {
2911 		dhd->wlfc_rxpkt_chk = FALSE;
2912 	}
2913 
2914 	/* initialize all interfaces to accept traffic */
2915 	for (i = 0; i < WLFC_MAX_IFNUM; i++) {
2916 		wlfc->hostif_flow_state[i] = OFF;
2917 	}
2918 
2919 	_dhd_wlfc_mac_entry_update(wlfc, &wlfc->destination_entries.other,
2920 		eWLFC_MAC_ENTRY_ACTION_ADD, 0xff, 0, NULL, NULL, NULL);
2921 
2922 	wlfc->allow_credit_borrow = 0;
2923 	wlfc->single_ac = 0;
2924 	wlfc->single_ac_timestamp = 0;
2925 
2926 
2927 exit:
2928 	DHD_ERROR(("%s: ret=%d\n", __FUNCTION__, rc));
2929 	dhd_os_wlfc_unblock(dhd);
2930 
2931 	return rc;
2932 } /* dhd_wlfc_enable */
2933 
2934 #ifdef SUPPORT_P2P_GO_PS
2935 
2936 /**
2937  * Called when the host platform enters a lower power mode, eg right before a system hibernate.
2938  * SUPPORT_P2P_GO_PS specific function.
2939  */
2940 int
dhd_wlfc_suspend(dhd_pub_t * dhd)2941 dhd_wlfc_suspend(dhd_pub_t *dhd)
2942 {
2943 	uint32 tlv = 0;
2944 
2945 	DHD_TRACE(("%s: masking wlfc events\n", __FUNCTION__));
2946 	if (!dhd->wlfc_enabled)
2947 		return -1;
2948 
2949 	if (!dhd_wl_ioctl_get_intiovar(dhd, "tlv", &tlv, WLC_GET_VAR, FALSE, 0))
2950 		return -1;
2951 	if ((tlv & (WLFC_FLAGS_RSSI_SIGNALS | WLFC_FLAGS_XONXOFF_SIGNALS)) == 0)
2952 		return 0;
2953 	tlv &= ~(WLFC_FLAGS_RSSI_SIGNALS | WLFC_FLAGS_XONXOFF_SIGNALS);
2954 	if (!dhd_wl_ioctl_set_intiovar(dhd, "tlv", tlv, WLC_SET_VAR, TRUE, 0))
2955 		return -1;
2956 
2957 	return 0;
2958 }
2959 
2960 /**
2961  * Called when the host platform resumes from a power management operation, eg resume after a
2962  * system hibernate. SUPPORT_P2P_GO_PS specific function.
2963  */
2964 int
dhd_wlfc_resume(dhd_pub_t * dhd)2965 dhd_wlfc_resume(dhd_pub_t *dhd)
2966 {
2967 	uint32 tlv = 0;
2968 
2969 	DHD_TRACE(("%s: unmasking wlfc events\n", __FUNCTION__));
2970 	if (!dhd->wlfc_enabled)
2971 		return -1;
2972 
2973 	if (!dhd_wl_ioctl_get_intiovar(dhd, "tlv", &tlv, WLC_GET_VAR, FALSE, 0))
2974 		return -1;
2975 	if ((tlv & (WLFC_FLAGS_RSSI_SIGNALS | WLFC_FLAGS_XONXOFF_SIGNALS)) ==
2976 		(WLFC_FLAGS_RSSI_SIGNALS | WLFC_FLAGS_XONXOFF_SIGNALS))
2977 		return 0;
2978 	tlv |= (WLFC_FLAGS_RSSI_SIGNALS | WLFC_FLAGS_XONXOFF_SIGNALS);
2979 	if (!dhd_wl_ioctl_set_intiovar(dhd, "tlv", tlv, WLC_SET_VAR, TRUE, 0))
2980 		return -1;
2981 
2982 	return 0;
2983 }
2984 
2985 #endif /* SUPPORT_P2P_GO_PS */
2986 
2987 /** A flow control header was received from firmware, containing one or more TLVs */
2988 int
dhd_wlfc_parse_header_info(dhd_pub_t * dhd,void * pktbuf,int tlv_hdr_len,uchar * reorder_info_buf,uint * reorder_info_len)2989 dhd_wlfc_parse_header_info(dhd_pub_t *dhd, void* pktbuf, int tlv_hdr_len, uchar *reorder_info_buf,
2990 	uint *reorder_info_len)
2991 {
2992 	uint8 type, len;
2993 	uint8* value;
2994 	uint8* tmpbuf;
2995 	uint16 remainder = (uint16)tlv_hdr_len;
2996 	uint16 processed = 0;
2997 	athost_wl_status_info_t* wlfc = NULL;
2998 	void* entry;
2999 
3000 	if ((dhd == NULL) || (pktbuf == NULL)) {
3001 		DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
3002 		return BCME_BADARG;
3003 	}
3004 
3005 	dhd_os_wlfc_block(dhd);
3006 
3007 	if (dhd->proptxstatus_mode != WLFC_ONLY_AMPDU_HOSTREORDER) {
3008 		if (!dhd->wlfc_state || (dhd->proptxstatus_mode == WLFC_FCMODE_NONE)) {
3009 			dhd_os_wlfc_unblock(dhd);
3010 			return WLFC_UNSUPPORTED;
3011 		}
3012 		wlfc = (athost_wl_status_info_t*)dhd->wlfc_state;
3013 	}
3014 
3015 	tmpbuf = (uint8*)PKTDATA(dhd->osh, pktbuf);
3016 
3017 	if (remainder) {
3018 		while ((processed < (WLFC_MAX_PENDING_DATALEN * 2)) && (remainder > 0)) {
3019 			type = tmpbuf[processed];
3020 			if (type == WLFC_CTL_TYPE_FILLER) {
3021 				remainder -= 1;
3022 				processed += 1;
3023 				continue;
3024 			}
3025 
3026 			len  = tmpbuf[processed + 1];
3027 			value = &tmpbuf[processed + 2];
3028 
3029 			if (remainder < (2 + len))
3030 				break;
3031 
3032 			remainder -= 2 + len;
3033 			processed += 2 + len;
3034 			entry = NULL;
3035 
3036 			DHD_INFO(("%s():%d type %d remainder %d processed %d\n",
3037 				__FUNCTION__, __LINE__, type, remainder, processed));
3038 
3039 			if (type == WLFC_CTL_TYPE_HOST_REORDER_RXPKTS)
3040 				_dhd_wlfc_reorderinfo_indicate(value, len, reorder_info_buf,
3041 					reorder_info_len);
3042 
3043 			if (wlfc == NULL) {
3044 				ASSERT(dhd->proptxstatus_mode == WLFC_ONLY_AMPDU_HOSTREORDER);
3045 
3046 				if (type != WLFC_CTL_TYPE_HOST_REORDER_RXPKTS &&
3047 					type != WLFC_CTL_TYPE_TRANS_ID)
3048 					DHD_INFO(("%s():%d dhd->wlfc_state is NULL yet!"
3049 					" type %d remainder %d processed %d\n",
3050 					__FUNCTION__, __LINE__, type, remainder, processed));
3051 				continue;
3052 			}
3053 
3054 			if (type == WLFC_CTL_TYPE_TXSTATUS) {
3055 				_dhd_wlfc_compressed_txstatus_update(dhd, value, 1, &entry);
3056 			} else if (type == WLFC_CTL_TYPE_COMP_TXSTATUS) {
3057 				uint8 compcnt_offset = WLFC_CTL_VALUE_LEN_TXSTATUS;
3058 
3059 				if (WLFC_GET_REUSESEQ(dhd->wlfc_mode)) {
3060 					compcnt_offset += WLFC_CTL_VALUE_LEN_SEQ;
3061 				}
3062 				_dhd_wlfc_compressed_txstatus_update(dhd, value,
3063 					value[compcnt_offset], &entry);
3064 			} else if (type == WLFC_CTL_TYPE_FIFO_CREDITBACK) {
3065 				_dhd_wlfc_fifocreditback_indicate(dhd, value);
3066 			} else if (type == WLFC_CTL_TYPE_RSSI) {
3067 				_dhd_wlfc_rssi_indicate(dhd, value);
3068 			} else if (type == WLFC_CTL_TYPE_MAC_REQUEST_CREDIT) {
3069 				_dhd_wlfc_credit_request(dhd, value);
3070 			} else if (type == WLFC_CTL_TYPE_MAC_REQUEST_PACKET) {
3071 				_dhd_wlfc_packet_request(dhd, value);
3072 			} else if ((type == WLFC_CTL_TYPE_MAC_OPEN) ||
3073 				(type == WLFC_CTL_TYPE_MAC_CLOSE)) {
3074 				_dhd_wlfc_psmode_update(dhd, value, type);
3075 			} else if ((type == WLFC_CTL_TYPE_MACDESC_ADD) ||
3076 				(type == WLFC_CTL_TYPE_MACDESC_DEL)) {
3077 				_dhd_wlfc_mac_table_update(dhd, value, type);
3078 			} else if (type == WLFC_CTL_TYPE_TRANS_ID) {
3079 				_dhd_wlfc_dbg_senum_check(dhd, value);
3080 			} else if ((type == WLFC_CTL_TYPE_INTERFACE_OPEN) ||
3081 				(type == WLFC_CTL_TYPE_INTERFACE_CLOSE)) {
3082 				_dhd_wlfc_interface_update(dhd, value, type);
3083 			}
3084 
3085 #ifndef BCMDBUS
3086 			if (entry && WLFC_GET_REORDERSUPP(dhd->wlfc_mode)) {
3087 				/* suppress all packets for this mac entry from bus->txq */
3088 				_dhd_wlfc_suppress_txq(dhd, _dhd_wlfc_entrypkt_fn, entry);
3089 			}
3090 #endif /* !BCMDBUS */
3091 		} /* while */
3092 
3093 		if (remainder != 0 && wlfc) {
3094 			/* trouble..., something is not right */
3095 			wlfc->stats.tlv_parse_failed++;
3096 		}
3097 	} /* if */
3098 
3099 	if (wlfc)
3100 		wlfc->stats.dhd_hdrpulls++;
3101 
3102 	dhd_os_wlfc_unblock(dhd);
3103 	return BCME_OK;
3104 }
3105 
3106 KERNEL_THREAD_RETURN_TYPE
dhd_wlfc_transfer_packets(void * data)3107 dhd_wlfc_transfer_packets(void *data)
3108 {
3109 	dhd_pub_t *dhdp = (dhd_pub_t *)data;
3110 	int ac, single_ac = 0, rc = BCME_OK;
3111 	dhd_wlfc_commit_info_t  commit_info;
3112 	athost_wl_status_info_t* ctx;
3113 	int bus_retry_count = 0;
3114 	int pkt_send = 0;
3115 	int pkt_send_per_ac = 0;
3116 
3117 	uint8 tx_map = 0; /* packets (send + in queue), Bitmask for 4 ACs + BC/MC */
3118 	uint8 rx_map = 0; /* received packets, Bitmask for 4 ACs + BC/MC */
3119 	uint8 packets_map = 0; /* packets in queue, Bitmask for 4 ACs + BC/MC */
3120 	bool no_credit = FALSE;
3121 
3122 	int lender;
3123 
3124 #if defined(DHD_WLFC_THREAD)
3125 	/* wait till someone wakeup me up, will change it at running time */
3126 	int wait_msec = msecs_to_jiffies(0xFFFFFFFF);
3127 #endif /* defined(DHD_WLFC_THREAD) */
3128 
3129 #if defined(DHD_WLFC_THREAD)
3130 	while (1) {
3131 		bus_retry_count = 0;
3132 		pkt_send = 0;
3133 		tx_map = 0;
3134 		rx_map = 0;
3135 		packets_map = 0;
3136 		wait_msec = wait_event_interruptible_timeout(dhdp->wlfc_wqhead,
3137 			dhdp->wlfc_thread_go, wait_msec);
3138 		if (kthread_should_stop()) {
3139 			break;
3140 		}
3141 		dhdp->wlfc_thread_go = FALSE;
3142 
3143 		dhd_os_wlfc_block(dhdp);
3144 #endif /* defined(DHD_WLFC_THREAD) */
3145 		ctx = (athost_wl_status_info_t*)dhdp->wlfc_state;
3146 #if defined(DHD_WLFC_THREAD)
3147 		if (!ctx)
3148 			goto exit;
3149 #endif /* defined(DHD_WLFC_THREAD) */
3150 
3151 	memset(&commit_info, 0, sizeof(commit_info));
3152 
3153 	/*
3154 	Commit packets for regular AC traffic. Higher priority first.
3155 	First, use up FIFO credits available to each AC. Based on distribution
3156 	and credits left, borrow from other ACs as applicable
3157 
3158 	-NOTE:
3159 	If the bus between the host and firmware is overwhelmed by the
3160 	traffic from host, it is possible that higher priority traffic
3161 	starves the lower priority queue. If that occurs often, we may
3162 	have to employ weighted round-robin or ucode scheme to avoid
3163 	low priority packet starvation.
3164 	*/
3165 
3166 	for (ac = AC_COUNT; ac >= 0; ac--) {
3167 		if (dhdp->wlfc_rxpkt_chk) {
3168 			/* check rx packet */
3169 			uint32 curr_t = OSL_SYSUPTIME(), delta;
3170 
3171 			delta = curr_t - ctx->rx_timestamp[ac];
3172 			if (delta < WLFC_RX_DETECTION_THRESHOLD_MS) {
3173 				rx_map |= (1 << ac);
3174 			}
3175 		}
3176 
3177 		if (ctx->pkt_cnt_per_ac[ac] == 0) {
3178 			continue;
3179 		}
3180 
3181 		tx_map |= (1 << ac);
3182 		single_ac = ac + 1;
3183 		pkt_send_per_ac = 0;
3184 		while ((FALSE == dhdp->proptxstatus_txoff) &&
3185 				(pkt_send_per_ac < WLFC_PACKET_BOUND)) {
3186 			/* packets from delayQ with less priority are fresh and
3187 			 * they'd need header and have no MAC entry
3188 			 */
3189 			no_credit = (ctx->FIFO_credit[ac] < 1);
3190 			if (dhdp->proptxstatus_credit_ignore ||
3191 				((ac == AC_COUNT) && !ctx->bcmc_credit_supported)) {
3192 				no_credit = FALSE;
3193 			}
3194 
3195 			lender = -1;
3196 #ifdef LIMIT_BORROW
3197 			if (no_credit && (ac < AC_COUNT) && (tx_map >= rx_map) &&
3198 				dhdp->wlfc_borrow_allowed) {
3199 				/* try borrow from lower priority */
3200 				lender = _dhd_wlfc_borrow_credit(ctx, ac - 1, ac, FALSE);
3201 				if (lender != -1) {
3202 					no_credit = FALSE;
3203 				}
3204 			}
3205 #endif
3206 			commit_info.needs_hdr = 1;
3207 			commit_info.mac_entry = NULL;
3208 			commit_info.p = _dhd_wlfc_deque_delayedq(ctx, ac,
3209 				&(commit_info.ac_fifo_credit_spent),
3210 				&(commit_info.needs_hdr),
3211 				&(commit_info.mac_entry),
3212 				no_credit);
3213 			commit_info.pkt_type = (commit_info.needs_hdr) ? eWLFC_PKTTYPE_DELAYED :
3214 				eWLFC_PKTTYPE_SUPPRESSED;
3215 
3216 			if (commit_info.p == NULL) {
3217 #ifdef LIMIT_BORROW
3218 				if (lender != -1 && dhdp->wlfc_borrow_allowed) {
3219 					_dhd_wlfc_return_credit(ctx, lender, ac);
3220 				}
3221 #endif
3222 				break;
3223 			}
3224 
3225 			if (!dhdp->proptxstatus_credit_ignore && (lender == -1)) {
3226 				ASSERT(ctx->FIFO_credit[ac] >= commit_info.ac_fifo_credit_spent);
3227 			}
3228 			/* here we can ensure have credit or no credit needed */
3229 			rc = _dhd_wlfc_handle_packet_commit(ctx, ac, &commit_info,
3230 				ctx->fcommit, ctx->commit_ctx);
3231 
3232 			/* Bus commits may fail (e.g. flow control); abort after retries */
3233 			if (rc == BCME_OK) {
3234 				pkt_send++;
3235 				pkt_send_per_ac++;
3236 				if (commit_info.ac_fifo_credit_spent && (lender == -1)) {
3237 					ctx->FIFO_credit[ac]--;
3238 				}
3239 #ifdef LIMIT_BORROW
3240 				else if (!commit_info.ac_fifo_credit_spent && (lender != -1) &&
3241 					dhdp->wlfc_borrow_allowed) {
3242 					_dhd_wlfc_return_credit(ctx, lender, ac);
3243 				}
3244 #endif
3245 			} else {
3246 #ifdef LIMIT_BORROW
3247 				if (lender != -1 && dhdp->wlfc_borrow_allowed) {
3248 					_dhd_wlfc_return_credit(ctx, lender, ac);
3249 				}
3250 #endif
3251 				bus_retry_count++;
3252 				if (bus_retry_count >= BUS_RETRIES) {
3253 					DHD_ERROR(("%s: bus error %d\n", __FUNCTION__, rc));
3254 					goto exit;
3255 				}
3256 			}
3257 		}
3258 
3259 		if (ctx->pkt_cnt_per_ac[ac]) {
3260 			packets_map |= (1 << ac);
3261 		}
3262 	}
3263 
3264 	if ((tx_map == 0) || dhdp->proptxstatus_credit_ignore) {
3265 		/* nothing send out or remain in queue */
3266 		rc = BCME_OK;
3267 		goto exit;
3268 	}
3269 
3270 	if (((tx_map & (tx_map - 1)) == 0) && (tx_map >= rx_map)) {
3271 		/* only one tx ac exist and no higher rx ac */
3272 		if ((single_ac == ctx->single_ac) && ctx->allow_credit_borrow) {
3273 			ac = single_ac - 1;
3274 		} else {
3275 			uint32 delta;
3276 			uint32 curr_t = OSL_SYSUPTIME();
3277 
3278 			if (single_ac != ctx->single_ac) {
3279 				/* new single ac traffic (first single ac or different single ac) */
3280 				ctx->allow_credit_borrow = 0;
3281 				ctx->single_ac_timestamp = curr_t;
3282 				ctx->single_ac = (uint8)single_ac;
3283 				rc = BCME_OK;
3284 				goto exit;
3285 			}
3286 			/* same ac traffic, check if it lasts enough time */
3287 			delta = curr_t - ctx->single_ac_timestamp;
3288 
3289 			if (delta >= WLFC_BORROW_DEFER_PERIOD_MS) {
3290 				/* wait enough time, can borrow now */
3291 				ctx->allow_credit_borrow = 1;
3292 				ac = single_ac - 1;
3293 			} else {
3294 				rc = BCME_OK;
3295 				goto exit;
3296 			}
3297 		}
3298 	} else {
3299 		/* If we have multiple AC traffic, turn off borrowing, mark time and bail out */
3300 		ctx->allow_credit_borrow = 0;
3301 		ctx->single_ac_timestamp = 0;
3302 		ctx->single_ac = 0;
3303 		rc = BCME_OK;
3304 		goto exit;
3305 	}
3306 
3307 	if (packets_map == 0) {
3308 		/* nothing to send, skip borrow */
3309 		rc = BCME_OK;
3310 		goto exit;
3311 	}
3312 
3313 	/* At this point, borrow all credits only for ac */
3314 	while (FALSE == dhdp->proptxstatus_txoff) {
3315 #ifdef LIMIT_BORROW
3316 		if (dhdp->wlfc_borrow_allowed) {
3317 			if ((lender = _dhd_wlfc_borrow_credit(ctx, AC_COUNT, ac, TRUE)) == -1) {
3318 				break;
3319 			}
3320 		}
3321 		else
3322 			break;
3323 #endif
3324 		commit_info.p = _dhd_wlfc_deque_delayedq(ctx, ac,
3325 			&(commit_info.ac_fifo_credit_spent),
3326 			&(commit_info.needs_hdr),
3327 			&(commit_info.mac_entry),
3328 			FALSE);
3329 		if (commit_info.p == NULL) {
3330 			/* before borrow only one ac exists and now this only ac is empty */
3331 #ifdef LIMIT_BORROW
3332 			_dhd_wlfc_return_credit(ctx, lender, ac);
3333 #endif
3334 			break;
3335 		}
3336 
3337 		commit_info.pkt_type = (commit_info.needs_hdr) ? eWLFC_PKTTYPE_DELAYED :
3338 			eWLFC_PKTTYPE_SUPPRESSED;
3339 
3340 		rc = _dhd_wlfc_handle_packet_commit(ctx, ac, &commit_info,
3341 		     ctx->fcommit, ctx->commit_ctx);
3342 
3343 		/* Bus commits may fail (e.g. flow control); abort after retries */
3344 		if (rc == BCME_OK) {
3345 			pkt_send++;
3346 			if (commit_info.ac_fifo_credit_spent) {
3347 #ifndef LIMIT_BORROW
3348 				ctx->FIFO_credit[ac]--;
3349 #endif
3350 			} else {
3351 #ifdef LIMIT_BORROW
3352 				_dhd_wlfc_return_credit(ctx, lender, ac);
3353 #endif
3354 			}
3355 		} else {
3356 #ifdef LIMIT_BORROW
3357 			_dhd_wlfc_return_credit(ctx, lender, ac);
3358 #endif
3359 			bus_retry_count++;
3360 			if (bus_retry_count >= BUS_RETRIES) {
3361 				DHD_ERROR(("%s: bus error %d\n", __FUNCTION__, rc));
3362 				goto exit;
3363 			}
3364 		}
3365 	}
3366 
3367 	BCM_REFERENCE(pkt_send);
3368 
3369 exit:
3370 #if defined(DHD_WLFC_THREAD)
3371 		dhd_os_wlfc_unblock(dhdp);
3372 		if (ctx && ctx->pkt_cnt_in_psq && pkt_send) {
3373 			wait_msec = msecs_to_jiffies(WLFC_THREAD_QUICK_RETRY_WAIT_MS);
3374 		} else {
3375 			wait_msec = msecs_to_jiffies(WLFC_THREAD_RETRY_WAIT_MS);
3376 		}
3377 	}
3378 	return 0;
3379 #else
3380 	return rc;
3381 #endif /* defined(DHD_WLFC_THREAD) */
3382 }
3383 
3384 /**
3385  * Enqueues a transmit packet in the next layer towards the dongle, eg the DBUS layer. Called by
3386  * eg dhd_sendpkt().
3387  *     @param[in] dhdp                  Pointer to public DHD structure
3388  *     @param[in] fcommit               Pointer to transmit function of next layer
3389  *     @param[in] commit_ctx            Opaque context used when calling next layer
3390  *     @param[in] pktbuf                Packet to send
3391  *     @param[in] need_toggle_host_if   If TRUE, resets flag ctx->toggle_host_if
3392  */
3393 int
dhd_wlfc_commit_packets(dhd_pub_t * dhdp,f_commitpkt_t fcommit,void * commit_ctx,void * pktbuf,bool need_toggle_host_if)3394 dhd_wlfc_commit_packets(dhd_pub_t *dhdp, f_commitpkt_t fcommit, void* commit_ctx, void *pktbuf,
3395 	bool need_toggle_host_if)
3396 {
3397 	int rc = BCME_OK;
3398 	athost_wl_status_info_t* ctx;
3399 
3400 #if defined(DHD_WLFC_THREAD)
3401 	if (!pktbuf)
3402 		return BCME_OK;
3403 #endif /* defined(DHD_WLFC_THREAD) */
3404 
3405 	if ((dhdp == NULL) || (fcommit == NULL)) {
3406 		DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
3407 		return BCME_BADARG;
3408 	}
3409 
3410 	dhd_os_wlfc_block(dhdp);
3411 
3412 	if (!dhdp->wlfc_state || (dhdp->proptxstatus_mode == WLFC_FCMODE_NONE)) {
3413 		if (pktbuf) {
3414 			DHD_PKTTAG_WLFCPKT_SET(PKTTAG(pktbuf), 0);
3415 		}
3416 		rc =  WLFC_UNSUPPORTED;
3417 		goto exit;
3418 	}
3419 
3420 	ctx = (athost_wl_status_info_t*)dhdp->wlfc_state;
3421 
3422 #ifdef BCMDBUS
3423 	if (!dhdp->up || (dhdp->busstate == DHD_BUS_DOWN)) {
3424 		if (pktbuf) {
3425 			PKTFREE(ctx->osh, pktbuf, TRUE);
3426 			rc = BCME_OK;
3427 		}
3428 		goto exit;
3429 	}
3430 #endif /* BCMDBUS */
3431 
3432 	if (dhdp->proptxstatus_module_ignore) {
3433 		if (pktbuf) {
3434 			uint32 htod = 0;
3435 			WL_TXSTATUS_SET_FLAGS(htod, WLFC_PKTFLAG_PKTFROMHOST);
3436 			_dhd_wlfc_pushheader(ctx, &pktbuf, FALSE, 0, 0, htod, 0, FALSE);
3437 			if (fcommit(commit_ctx, pktbuf)) {
3438 				/* free it if failed, otherwise do it in tx complete cb */
3439 				PKTFREE(ctx->osh, pktbuf, TRUE);
3440 			}
3441 			rc = BCME_OK;
3442 		}
3443 		goto exit;
3444 	}
3445 
3446 	if (pktbuf) {
3447 		int ac = DHD_PKTTAG_FIFO(PKTTAG(pktbuf));
3448 		ASSERT(ac <= AC_COUNT);
3449 		DHD_PKTTAG_WLFCPKT_SET(PKTTAG(pktbuf), 1);
3450 		/* en-queue the packets to respective queue. */
3451 		rc = _dhd_wlfc_enque_delayq(ctx, pktbuf, ac);
3452 		if (rc) {
3453 			_dhd_wlfc_prec_drop(ctx->dhdp, (ac << 1), pktbuf, FALSE);
3454 		} else {
3455 			ctx->stats.pktin++;
3456 			ctx->pkt_cnt_in_drv[DHD_PKTTAG_IF(PKTTAG(pktbuf))][ac]++;
3457 		}
3458 	}
3459 
3460 	if (!ctx->fcommit) {
3461 		ctx->fcommit = fcommit;
3462 	} else {
3463 		ASSERT(ctx->fcommit == fcommit);
3464 	}
3465 	if (!ctx->commit_ctx) {
3466 		ctx->commit_ctx = commit_ctx;
3467 	} else {
3468 		ASSERT(ctx->commit_ctx == commit_ctx);
3469 	}
3470 
3471 #if defined(DHD_WLFC_THREAD)
3472 	_dhd_wlfc_thread_wakeup(dhdp);
3473 #else
3474 	dhd_wlfc_transfer_packets(dhdp);
3475 #endif /* defined(DHD_WLFC_THREAD) */
3476 
3477 exit:
3478 	dhd_os_wlfc_unblock(dhdp);
3479 	return rc;
3480 } /* dhd_wlfc_commit_packets */
3481 
3482 /**
3483  * Called when the (lower) DBUS layer indicates completion (succesfull or not) of a transmit packet
3484  */
3485 int
dhd_wlfc_txcomplete(dhd_pub_t * dhd,void * txp,bool success)3486 dhd_wlfc_txcomplete(dhd_pub_t *dhd, void *txp, bool success)
3487 {
3488 	athost_wl_status_info_t* wlfc;
3489 	wlfc_mac_descriptor_t *entry;
3490 	void* pout = NULL;
3491 	int rtn = BCME_OK;
3492 	if ((dhd == NULL) || (txp == NULL)) {
3493 		DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
3494 		return BCME_BADARG;
3495 	}
3496 
3497 	bcm_pkt_validate_chk(txp);
3498 
3499 	dhd_os_wlfc_block(dhd);
3500 
3501 	if (!dhd->wlfc_state || (dhd->proptxstatus_mode == WLFC_FCMODE_NONE)) {
3502 		rtn = WLFC_UNSUPPORTED;
3503 		goto EXIT;
3504 	}
3505 
3506 	wlfc = (athost_wl_status_info_t*)dhd->wlfc_state;
3507 	if (DHD_PKTTAG_SIGNALONLY(PKTTAG(txp))) {
3508 #ifdef PROP_TXSTATUS_DEBUG
3509 		wlfc->stats.signal_only_pkts_freed++;
3510 #endif
3511 		/* is this a signal-only packet? */
3512 		_dhd_wlfc_pullheader(wlfc, txp);
3513 		PKTFREE(wlfc->osh, txp, TRUE);
3514 		goto EXIT;
3515 	}
3516 
3517 	entry = _dhd_wlfc_find_table_entry(wlfc, txp);
3518 	ASSERT(entry);
3519 
3520 	if (!success || dhd->proptxstatus_txstatus_ignore) {
3521 		WLFC_DBGMESG(("At: %s():%d, bus_complete() failure for %p, htod_tag:0x%08x\n",
3522 			__FUNCTION__, __LINE__, txp, DHD_PKTTAG_H2DTAG(PKTTAG(txp))));
3523 		if (!WLFC_GET_AFQ(dhd->wlfc_mode)) {
3524 			_dhd_wlfc_hanger_poppkt(wlfc->hanger, WL_TXSTATUS_GET_HSLOT(
3525 				DHD_PKTTAG_H2DTAG(PKTTAG(txp))), &pout, TRUE);
3526 			ASSERT(txp == pout);
3527 		}
3528 
3529 		/* indicate failure and free the packet */
3530 		dhd_txcomplete(dhd, txp, success);
3531 
3532 		/* return the credit, if necessary */
3533 		_dhd_wlfc_return_implied_credit(wlfc, txp);
3534 
3535 		if (entry->transit_count)
3536 			entry->transit_count--;
3537 		if (entry->suppr_transit_count)
3538 			entry->suppr_transit_count--;
3539 		wlfc->pkt_cnt_in_drv[DHD_PKTTAG_IF(PKTTAG(txp))][DHD_PKTTAG_FIFO(PKTTAG(txp))]--;
3540 		wlfc->stats.pktout++;
3541 		PKTFREE(wlfc->osh, txp, TRUE);
3542 	} else {
3543 		/* bus confirmed pkt went to firmware side */
3544 		if (WLFC_GET_AFQ(dhd->wlfc_mode)) {
3545 			_dhd_wlfc_enque_afq(wlfc, txp);
3546 		} else {
3547 			int hslot = WL_TXSTATUS_GET_HSLOT(DHD_PKTTAG_H2DTAG(PKTTAG(txp)));
3548 			_dhd_wlfc_hanger_free_pkt(wlfc, hslot,
3549 				WLFC_HANGER_PKT_STATE_BUSRETURNED, -1);
3550 		}
3551 	}
3552 
3553 	ASSERT(entry->onbus_pkts_count > 0);
3554 	if (entry->onbus_pkts_count > 0)
3555 		entry->onbus_pkts_count--;
3556 	if (entry->suppressed &&
3557 		(!entry->onbus_pkts_count) &&
3558 		(!entry->suppr_transit_count))
3559 		entry->suppressed = FALSE;
3560 EXIT:
3561 	dhd_os_wlfc_unblock(dhd);
3562 	return rtn;
3563 } /* dhd_wlfc_txcomplete */
3564 
3565 int
dhd_wlfc_init(dhd_pub_t * dhd)3566 dhd_wlfc_init(dhd_pub_t *dhd)
3567 {
3568 	/* enable all signals & indicate host proptxstatus logic is active */
3569 	uint32 tlv, mode, fw_caps;
3570 	int ret = 0;
3571 
3572 	if (dhd == NULL) {
3573 		DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
3574 		return BCME_BADARG;
3575 	}
3576 
3577 	dhd_os_wlfc_block(dhd);
3578 	if (dhd->wlfc_enabled) {
3579 		DHD_ERROR(("%s():%d, Already enabled!\n", __FUNCTION__, __LINE__));
3580 		dhd_os_wlfc_unblock(dhd);
3581 		return BCME_OK;
3582 	}
3583 	dhd->wlfc_enabled = TRUE;
3584 	dhd_os_wlfc_unblock(dhd);
3585 
3586 	tlv = WLFC_FLAGS_RSSI_SIGNALS |
3587 		WLFC_FLAGS_XONXOFF_SIGNALS |
3588 		WLFC_FLAGS_CREDIT_STATUS_SIGNALS |
3589 		WLFC_FLAGS_HOST_PROPTXSTATUS_ACTIVE |
3590 		WLFC_FLAGS_HOST_RXRERODER_ACTIVE;
3591 
3592 
3593 	/*
3594 	try to enable/disable signaling by sending "tlv" iovar. if that fails,
3595 	fallback to no flow control? Print a message for now.
3596 	*/
3597 
3598 	/* enable proptxtstatus signaling by default */
3599 	if (!dhd_wl_ioctl_set_intiovar(dhd, "tlv", tlv, WLC_SET_VAR, TRUE, 0)) {
3600 		/*
3601 		Leaving the message for now, it should be removed after a while; once
3602 		the tlv situation is stable.
3603 		*/
3604 		DHD_PRINT("dhd_wlfc_init(): successfully %s bdcv2 tlv signaling, %d\n",
3605 			dhd->wlfc_enabled?"enabled":"disabled", tlv);
3606 	}
3607 
3608 	mode = 0;
3609 
3610 	/* query caps */
3611 	ret = dhd_wl_ioctl_get_intiovar(dhd, "wlfc_mode", &fw_caps, WLC_GET_VAR, FALSE, 0);
3612 
3613 	if (!ret) {
3614 		DHD_PRINT("%s: query wlfc_mode succeed, fw_caps=0x%x\n", __FUNCTION__, fw_caps);
3615 
3616 		if (WLFC_IS_OLD_DEF(fw_caps)) {
3617 #ifdef BCMDBUS
3618 			mode = WLFC_MODE_HANGER;
3619 #else
3620 			/* enable proptxtstatus v2 by default */
3621 			mode = WLFC_MODE_AFQ;
3622 #endif /* BCMDBUS */
3623 		} else {
3624 			WLFC_SET_AFQ(mode, WLFC_GET_AFQ(fw_caps));
3625 #ifdef BCMDBUS
3626 			WLFC_SET_AFQ(mode, 0);
3627 #endif /* BCMDBUS */
3628 			WLFC_SET_REUSESEQ(mode, WLFC_GET_REUSESEQ(fw_caps));
3629 			WLFC_SET_REORDERSUPP(mode, WLFC_GET_REORDERSUPP(fw_caps));
3630 		}
3631 		ret = dhd_wl_ioctl_set_intiovar(dhd, "wlfc_mode", mode, WLC_SET_VAR, TRUE, 0);
3632 	}
3633 
3634 	dhd_os_wlfc_block(dhd);
3635 
3636 	dhd->wlfc_mode = 0;
3637 	if (ret >= 0) {
3638 		if (WLFC_IS_OLD_DEF(mode)) {
3639 			WLFC_SET_AFQ(dhd->wlfc_mode, (mode == WLFC_MODE_AFQ));
3640 		} else {
3641 			dhd->wlfc_mode = mode;
3642 		}
3643 	}
3644 
3645 	DHD_PRINT("dhd_wlfc_init(): wlfc_mode=0x%x, ret=%d\n", dhd->wlfc_mode, ret);
3646 #ifdef LIMIT_BORROW
3647 	dhd->wlfc_borrow_allowed = TRUE;
3648 #endif
3649 	dhd_os_wlfc_unblock(dhd);
3650 
3651 	if (dhd->plat_init)
3652 		dhd->plat_init((void *)dhd);
3653 
3654 	return BCME_OK;
3655 } /* dhd_wlfc_init */
3656 
3657 /** AMPDU host reorder specific function */
3658 int
dhd_wlfc_hostreorder_init(dhd_pub_t * dhd)3659 dhd_wlfc_hostreorder_init(dhd_pub_t *dhd)
3660 {
3661 	/* enable only ampdu hostreorder here */
3662 	uint32 tlv;
3663 
3664 	if (dhd == NULL) {
3665 		DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
3666 		return BCME_BADARG;
3667 	}
3668 
3669 	DHD_TRACE(("%s():%d Enter\n", __FUNCTION__, __LINE__));
3670 
3671 	tlv = WLFC_FLAGS_HOST_RXRERODER_ACTIVE;
3672 
3673 	/* enable proptxtstatus signaling by default */
3674 	if (dhd_wl_ioctl_set_intiovar(dhd, "tlv", tlv, WLC_SET_VAR, TRUE, 0)) {
3675 		DHD_ERROR(("%s(): failed to enable/disable bdcv2 tlv signaling\n",
3676 			__FUNCTION__));
3677 	} else {
3678 		/*
3679 		Leaving the message for now, it should be removed after a while; once
3680 		the tlv situation is stable.
3681 		*/
3682 		DHD_PRINT("%s(): successful bdcv2 tlv signaling, %d\n",
3683 			__FUNCTION__, tlv);
3684 	}
3685 
3686 	dhd_os_wlfc_block(dhd);
3687 	dhd->proptxstatus_mode = WLFC_ONLY_AMPDU_HOSTREORDER;
3688 	dhd_os_wlfc_unblock(dhd);
3689 	/* terence 20161229: enable ampdu_hostreorder if tlv enable hostreorder */
3690 	dhd_conf_set_intiovar(dhd, WLC_SET_VAR, "ampdu_hostreorder", 1, 0, TRUE);
3691 
3692 	return BCME_OK;
3693 }
3694 
3695 #ifdef DHD_LOAD_CHIPALIVE
3696 int
dhd_chipalive_wlfc_init(dhd_pub_t * dhd)3697 dhd_chipalive_wlfc_init(dhd_pub_t *dhd)
3698 {
3699 	char iovbuf[14]; /* Room for "tlv" + '\0' + parameter */
3700 	/* enable all signals & indicate host proptxstatus logic is active */
3701 	uint32 tlv, mode, fw_caps;
3702 	int ret = 0;
3703 
3704 	/* This function should do query fw tlv only to sync for dhd host, don't change fw state */
3705 
3706 	ret= dhd_conf_get_iovar(dhd, 0, WLC_GET_VAR, "tlv", (char *)&tlv, sizeof(tlv));
3707 	if (ret || tlv==0) {
3708 		DHD_PRINT("%s: proptx is not enabled in fw, ret=%d\n", __FUNCTION__, ret);
3709 		return -1;
3710 	} else {
3711 		dhd_os_wlfc_block(dhd);
3712 		dhd->wlfc_enabled = TRUE;
3713 		dhd_os_wlfc_unblock(dhd);
3714 		DHD_PRINT("%s: tlv = %d\n", __FUNCTION__, tlv);
3715 	}
3716 
3717 	/* query caps */
3718 	ret = bcm_mkiovar("wlfc_mode", (char *)&mode, 4, iovbuf, sizeof(iovbuf));
3719 	if (ret > 0) {
3720 		ret = dhd_wl_ioctl_cmd(dhd, WLC_GET_VAR, iovbuf, sizeof(iovbuf), FALSE, 0);
3721 	}
3722 
3723 	if (ret >= 0) {
3724 		fw_caps = *((uint32 *)iovbuf);
3725 		mode = 0;
3726 		DHD_PRINT("%s: query wlfc_mode succeed, fw_caps=0x%x\n", __FUNCTION__, fw_caps);
3727 
3728 		if (WLFC_IS_OLD_DEF(fw_caps)) {
3729 			/* enable proptxtstatus v2 by default */
3730 			mode = WLFC_MODE_AFQ;
3731 		} else {
3732 			WLFC_SET_AFQ(mode, WLFC_GET_AFQ(fw_caps));
3733 			WLFC_SET_REUSESEQ(mode, WLFC_GET_REUSESEQ(fw_caps));
3734 			WLFC_SET_REORDERSUPP(mode, WLFC_GET_REORDERSUPP(fw_caps));
3735 		}
3736 	}
3737 
3738 	dhd_os_wlfc_block(dhd);
3739 
3740 	dhd->wlfc_mode = 0;
3741 	if (ret >= 0) { // this ret value is from wlfc_mode supported in fw or not
3742 		if (WLFC_IS_OLD_DEF(mode)) {
3743 			WLFC_SET_AFQ(dhd->wlfc_mode, (mode == WLFC_MODE_AFQ));
3744 		} else {
3745 			dhd->wlfc_mode = mode;
3746 		}
3747 	}
3748 	DHD_PRINT("%s: wlfc_mode=0x%x, ret=%d\n", __FUNCTION__, dhd->wlfc_mode, ret);
3749 
3750 	dhd_os_wlfc_unblock(dhd);
3751 
3752 	if (dhd->plat_init)
3753 		dhd->plat_init((void *)dhd);
3754 
3755 	return BCME_OK;
3756 }
3757 #endif
3758 
3759 int
dhd_wlfc_cleanup_txq(dhd_pub_t * dhd,f_processpkt_t fn,void * arg)3760 dhd_wlfc_cleanup_txq(dhd_pub_t *dhd, f_processpkt_t fn, void *arg)
3761 {
3762 	if (dhd == NULL) {
3763 		DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
3764 		return BCME_BADARG;
3765 	}
3766 
3767 	dhd_os_wlfc_block(dhd);
3768 
3769 	if (!dhd->wlfc_state || (dhd->proptxstatus_mode == WLFC_FCMODE_NONE)) {
3770 		dhd_os_wlfc_unblock(dhd);
3771 		return WLFC_UNSUPPORTED;
3772 	}
3773 
3774 #ifndef BCMDBUS
3775 	_dhd_wlfc_cleanup_txq(dhd, fn, arg);
3776 #endif /* !BCMDBUS */
3777 
3778 	dhd_os_wlfc_unblock(dhd);
3779 
3780 	return BCME_OK;
3781 }
3782 
3783 /** release all packet resources */
3784 int
dhd_wlfc_cleanup(dhd_pub_t * dhd,f_processpkt_t fn,void * arg)3785 dhd_wlfc_cleanup(dhd_pub_t *dhd, f_processpkt_t fn, void *arg)
3786 {
3787 	if (dhd == NULL) {
3788 		DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
3789 		return BCME_BADARG;
3790 	}
3791 
3792 	dhd_os_wlfc_block(dhd);
3793 
3794 	if (!dhd->wlfc_state || (dhd->proptxstatus_mode == WLFC_FCMODE_NONE)) {
3795 		dhd_os_wlfc_unblock(dhd);
3796 		return WLFC_UNSUPPORTED;
3797 	}
3798 
3799 	_dhd_wlfc_cleanup(dhd, fn, arg);
3800 
3801 	dhd_os_wlfc_unblock(dhd);
3802 
3803 	return BCME_OK;
3804 }
3805 
3806 int
dhd_wlfc_deinit(dhd_pub_t * dhd)3807 dhd_wlfc_deinit(dhd_pub_t *dhd)
3808 {
3809 	/* cleanup all psq related resources */
3810 	athost_wl_status_info_t* wlfc;
3811 	uint32 tlv = 0;
3812 	uint32 hostreorder = 0;
3813 
3814 	if (dhd == NULL) {
3815 		DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
3816 		return BCME_BADARG;
3817 	}
3818 
3819 	dhd_os_wlfc_block(dhd);
3820 	if (!dhd->wlfc_enabled) {
3821 		DHD_ERROR(("%s():%d, Already disabled!\n", __FUNCTION__, __LINE__));
3822 		dhd_os_wlfc_unblock(dhd);
3823 		return BCME_OK;
3824 	}
3825 
3826 	dhd->wlfc_enabled = FALSE;
3827 	dhd_os_wlfc_unblock(dhd);
3828 
3829 	/* query ampdu hostreorder */
3830 	(void) dhd_wl_ioctl_get_intiovar(dhd, "ampdu_hostreorder",
3831 		&hostreorder, WLC_GET_VAR, FALSE, 0);
3832 
3833 	if (hostreorder) {
3834 		tlv = WLFC_FLAGS_HOST_RXRERODER_ACTIVE;
3835 		DHD_ERROR(("%s():%d, maintain HOST RXRERODER flag in tvl\n",
3836 			__FUNCTION__, __LINE__));
3837 	}
3838 
3839 	/* Disable proptxtstatus signaling for deinit */
3840 	(void) dhd_wl_ioctl_set_intiovar(dhd, "tlv", tlv, WLC_SET_VAR, TRUE, 0);
3841 
3842 	dhd_os_wlfc_block(dhd);
3843 
3844 	if (!dhd->wlfc_state || (dhd->proptxstatus_mode == WLFC_FCMODE_NONE)) {
3845 		dhd_os_wlfc_unblock(dhd);
3846 		return WLFC_UNSUPPORTED;
3847 	}
3848 
3849 	wlfc = (athost_wl_status_info_t*)dhd->wlfc_state;
3850 
3851 	_dhd_wlfc_cleanup(dhd, NULL, NULL);
3852 
3853 	if (!WLFC_GET_AFQ(dhd->wlfc_mode)) {
3854 		int i;
3855 		wlfc_hanger_t* h = (wlfc_hanger_t*)wlfc->hanger;
3856 		for (i = 0; i < h->max_items; i++) {
3857 			if (h->items[i].state != WLFC_HANGER_ITEM_STATE_FREE) {
3858 				_dhd_wlfc_hanger_free_pkt(wlfc, i,
3859 					WLFC_HANGER_PKT_STATE_COMPLETE, TRUE);
3860 			}
3861 		}
3862 
3863 		/* delete hanger */
3864 		_dhd_wlfc_hanger_delete(dhd, h);
3865 	}
3866 
3867 
3868 	/* free top structure */
3869 	DHD_OS_PREFREE(dhd, dhd->wlfc_state,
3870 		sizeof(athost_wl_status_info_t));
3871 	dhd->wlfc_state = NULL;
3872 	dhd->proptxstatus_mode = hostreorder ?
3873 		WLFC_ONLY_AMPDU_HOSTREORDER : WLFC_FCMODE_NONE;
3874 
3875 	DHD_ERROR(("%s: wlfc_mode=0x%x, tlv=%d\n", __FUNCTION__, dhd->wlfc_mode, tlv));
3876 
3877 	dhd_os_wlfc_unblock(dhd);
3878 
3879 	if (dhd->plat_deinit)
3880 		dhd->plat_deinit((void *)dhd);
3881 	return BCME_OK;
3882 } /* dhd_wlfc_init */
3883 
3884 /**
3885  * Called on an interface event (WLC_E_IF) indicated by firmware
3886  *     @param[in] dhdp   Pointer to public DHD structure
3887  *     @param[in] action eg eWLFC_MAC_ENTRY_ACTION_UPDATE or eWLFC_MAC_ENTRY_ACTION_ADD
3888  */
dhd_wlfc_interface_event(dhd_pub_t * dhdp,uint8 action,uint8 ifid,uint8 iftype,uint8 * ea)3889 int dhd_wlfc_interface_event(dhd_pub_t *dhdp, uint8 action, uint8 ifid, uint8 iftype, uint8* ea)
3890 {
3891 	int rc;
3892 
3893 	if (dhdp == NULL) {
3894 		DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
3895 		return BCME_BADARG;
3896 	}
3897 
3898 	dhd_os_wlfc_block(dhdp);
3899 
3900 	if (!dhdp->wlfc_state || (dhdp->proptxstatus_mode == WLFC_FCMODE_NONE)) {
3901 		dhd_os_wlfc_unblock(dhdp);
3902 		return WLFC_UNSUPPORTED;
3903 	}
3904 
3905 	rc = _dhd_wlfc_interface_entry_update(dhdp->wlfc_state, action, ifid, iftype, ea);
3906 
3907 	dhd_os_wlfc_unblock(dhdp);
3908 	return rc;
3909 }
3910 
3911 /** Called eg on receiving a WLC_E_FIFO_CREDIT_MAP event from the dongle */
dhd_wlfc_FIFOcreditmap_event(dhd_pub_t * dhdp,uint8 * event_data)3912 int dhd_wlfc_FIFOcreditmap_event(dhd_pub_t *dhdp, uint8* event_data)
3913 {
3914 	int rc;
3915 
3916 	if (dhdp == NULL) {
3917 		DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
3918 		return BCME_BADARG;
3919 	}
3920 
3921 	dhd_os_wlfc_block(dhdp);
3922 
3923 	if (!dhdp->wlfc_state || (dhdp->proptxstatus_mode == WLFC_FCMODE_NONE)) {
3924 		dhd_os_wlfc_unblock(dhdp);
3925 		return WLFC_UNSUPPORTED;
3926 	}
3927 
3928 	rc = _dhd_wlfc_FIFOcreditmap_update(dhdp->wlfc_state, event_data);
3929 
3930 	dhd_os_wlfc_unblock(dhdp);
3931 
3932 	return rc;
3933 }
3934 #ifdef LIMIT_BORROW
dhd_wlfc_disable_credit_borrow_event(dhd_pub_t * dhdp,uint8 * event_data)3935 int dhd_wlfc_disable_credit_borrow_event(dhd_pub_t *dhdp, uint8* event_data)
3936 {
3937 	if (dhdp == NULL || event_data == NULL) {
3938 		DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
3939 		return BCME_BADARG;
3940 	}
3941 	dhd_os_wlfc_block(dhdp);
3942 	dhdp->wlfc_borrow_allowed = (bool)(*(uint32 *)event_data);
3943 	dhd_os_wlfc_unblock(dhdp);
3944 
3945 	return BCME_OK;
3946 }
3947 #endif /* LIMIT_BORROW */
3948 
3949 /**
3950  * Called eg on receiving a WLC_E_BCMC_CREDIT_SUPPORT event from the dongle (broadcast/multicast
3951  * specific)
3952  */
dhd_wlfc_BCMCCredit_support_event(dhd_pub_t * dhdp)3953 int dhd_wlfc_BCMCCredit_support_event(dhd_pub_t *dhdp)
3954 {
3955 	int rc;
3956 
3957 	if (dhdp == NULL) {
3958 		DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
3959 		return BCME_BADARG;
3960 	}
3961 
3962 	dhd_os_wlfc_block(dhdp);
3963 
3964 	if (!dhdp->wlfc_state || (dhdp->proptxstatus_mode == WLFC_FCMODE_NONE)) {
3965 		dhd_os_wlfc_unblock(dhdp);
3966 		return WLFC_UNSUPPORTED;
3967 	}
3968 
3969 	rc = _dhd_wlfc_BCMCCredit_support_update(dhdp->wlfc_state);
3970 
3971 	dhd_os_wlfc_unblock(dhdp);
3972 	return rc;
3973 }
3974 
3975 /** debug specific function */
3976 int
dhd_wlfc_dump(dhd_pub_t * dhdp,struct bcmstrbuf * strbuf)3977 dhd_wlfc_dump(dhd_pub_t *dhdp, struct bcmstrbuf *strbuf)
3978 {
3979 	int i;
3980 	uint8* ea;
3981 	athost_wl_status_info_t* wlfc;
3982 	wlfc_hanger_t* h;
3983 	wlfc_mac_descriptor_t* mac_table;
3984 	wlfc_mac_descriptor_t* interfaces;
3985 	char* iftypes[] = {"STA", "AP", "WDS", "p2pGO", "p2pCL"};
3986 
3987 	if (!dhdp || !strbuf) {
3988 		DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
3989 		return BCME_BADARG;
3990 	}
3991 
3992 	dhd_os_wlfc_block(dhdp);
3993 
3994 	if (!dhdp->wlfc_state || (dhdp->proptxstatus_mode == WLFC_FCMODE_NONE)) {
3995 		dhd_os_wlfc_unblock(dhdp);
3996 		return WLFC_UNSUPPORTED;
3997 	}
3998 
3999 	wlfc = (athost_wl_status_info_t*)dhdp->wlfc_state;
4000 
4001 	h = (wlfc_hanger_t*)wlfc->hanger;
4002 	if (h == NULL) {
4003 		bcm_bprintf(strbuf, "wlfc-hanger not initialized yet\n");
4004 	}
4005 
4006 	mac_table = wlfc->destination_entries.nodes;
4007 	interfaces = wlfc->destination_entries.interfaces;
4008 	bcm_bprintf(strbuf, "---- wlfc stats ----\n");
4009 
4010 	if (!WLFC_GET_AFQ(dhdp->wlfc_mode)) {
4011 		h = (wlfc_hanger_t*)wlfc->hanger;
4012 		if (h == NULL) {
4013 			bcm_bprintf(strbuf, "wlfc-hanger not initialized yet\n");
4014 		} else {
4015 			bcm_bprintf(strbuf, "wlfc hanger (pushed,popped,f_push,"
4016 				"f_pop,f_slot, pending) = (%d,%d,%d,%d,%d,%d)\n",
4017 				h->pushed,
4018 				h->popped,
4019 				h->failed_to_push,
4020 				h->failed_to_pop,
4021 				h->failed_slotfind,
4022 				(h->pushed - h->popped));
4023 		}
4024 	}
4025 
4026 	bcm_bprintf(strbuf, "wlfc fail(tlv,credit_rqst,mac_update,psmode_update), "
4027 		"(dq_full,rollback_fail) = (%d,%d,%d,%d), (%d,%d)\n",
4028 		wlfc->stats.tlv_parse_failed,
4029 		wlfc->stats.credit_request_failed,
4030 		wlfc->stats.mac_update_failed,
4031 		wlfc->stats.psmode_update_failed,
4032 		wlfc->stats.delayq_full_error,
4033 		wlfc->stats.rollback_failed);
4034 
4035 	bcm_bprintf(strbuf, "PKTS (init_credit,credit,sent,drop_d,drop_s,outoforder) "
4036 		"(AC0[%d,%d,%d,%d,%d,%d],AC1[%d,%d,%d,%d,%d,%d],AC2[%d,%d,%d,%d,%d,%d],"
4037 		"AC3[%d,%d,%d,%d,%d,%d],BC_MC[%d,%d,%d,%d,%d,%d])\n",
4038 		wlfc->Init_FIFO_credit[0], wlfc->FIFO_credit[0], wlfc->stats.send_pkts[0],
4039 		wlfc->stats.drop_pkts[0], wlfc->stats.drop_pkts[1], wlfc->stats.ooo_pkts[0],
4040 		wlfc->Init_FIFO_credit[1], wlfc->FIFO_credit[1], wlfc->stats.send_pkts[1],
4041 		wlfc->stats.drop_pkts[2], wlfc->stats.drop_pkts[3], wlfc->stats.ooo_pkts[1],
4042 		wlfc->Init_FIFO_credit[2], wlfc->FIFO_credit[2], wlfc->stats.send_pkts[2],
4043 		wlfc->stats.drop_pkts[4], wlfc->stats.drop_pkts[5], wlfc->stats.ooo_pkts[2],
4044 		wlfc->Init_FIFO_credit[3], wlfc->FIFO_credit[3], wlfc->stats.send_pkts[3],
4045 		wlfc->stats.drop_pkts[6], wlfc->stats.drop_pkts[7], wlfc->stats.ooo_pkts[3],
4046 		wlfc->Init_FIFO_credit[4], wlfc->FIFO_credit[4], wlfc->stats.send_pkts[4],
4047 		wlfc->stats.drop_pkts[8], wlfc->stats.drop_pkts[9], wlfc->stats.ooo_pkts[4]);
4048 
4049 	bcm_bprintf(strbuf, "\n");
4050 	for (i = 0; i < WLFC_MAX_IFNUM; i++) {
4051 		if (interfaces[i].occupied) {
4052 			char* iftype_desc;
4053 
4054 			if (interfaces[i].iftype > WLC_E_IF_ROLE_P2P_CLIENT)
4055 				iftype_desc = "<Unknown";
4056 			else
4057 				iftype_desc = iftypes[interfaces[i].iftype];
4058 
4059 			ea = interfaces[i].ea;
4060 			bcm_bprintf(strbuf, "INTERFACE[%d].ea = "
4061 				"[%02x:%02x:%02x:%02x:%02x:%02x], if:%d, type: %s "
4062 				"netif_flow_control:%s\n", i,
4063 				ea[0], ea[1], ea[2], ea[3], ea[4], ea[5],
4064 				interfaces[i].interface_id,
4065 				iftype_desc, ((wlfc->hostif_flow_state[i] == OFF)
4066 				? " OFF":" ON"));
4067 
4068 			bcm_bprintf(strbuf, "INTERFACE[%d].PSQ(len,state,credit),"
4069 				"(trans,supp_trans,onbus)"
4070 				"= (%d,%s,%d),(%d,%d,%d)\n",
4071 				i,
4072 				interfaces[i].psq.len,
4073 				((interfaces[i].state ==
4074 				WLFC_STATE_OPEN) ? "OPEN":"CLOSE"),
4075 				interfaces[i].requested_credit,
4076 				interfaces[i].transit_count,
4077 				interfaces[i].suppr_transit_count,
4078 				interfaces[i].onbus_pkts_count);
4079 
4080 			bcm_bprintf(strbuf, "INTERFACE[%d].PSQ"
4081 				"(delay0,sup0,afq0),(delay1,sup1,afq1),(delay2,sup2,afq2),"
4082 				"(delay3,sup3,afq3),(delay4,sup4,afq4) = (%d,%d,%d),"
4083 				"(%d,%d,%d),(%d,%d,%d),(%d,%d,%d),(%d,%d,%d)\n",
4084 				i,
4085 				interfaces[i].psq.q[0].len,
4086 				interfaces[i].psq.q[1].len,
4087 				interfaces[i].afq.q[0].len,
4088 				interfaces[i].psq.q[2].len,
4089 				interfaces[i].psq.q[3].len,
4090 				interfaces[i].afq.q[1].len,
4091 				interfaces[i].psq.q[4].len,
4092 				interfaces[i].psq.q[5].len,
4093 				interfaces[i].afq.q[2].len,
4094 				interfaces[i].psq.q[6].len,
4095 				interfaces[i].psq.q[7].len,
4096 				interfaces[i].afq.q[3].len,
4097 				interfaces[i].psq.q[8].len,
4098 				interfaces[i].psq.q[9].len,
4099 				interfaces[i].afq.q[4].len);
4100 		}
4101 	}
4102 
4103 	bcm_bprintf(strbuf, "\n");
4104 	for (i = 0; i < WLFC_MAC_DESC_TABLE_SIZE; i++) {
4105 		if (mac_table[i].occupied) {
4106 			ea = mac_table[i].ea;
4107 			bcm_bprintf(strbuf, "MAC_table[%d].ea = "
4108 				"[%02x:%02x:%02x:%02x:%02x:%02x], if:%d \n", i,
4109 				ea[0], ea[1], ea[2], ea[3], ea[4], ea[5],
4110 				mac_table[i].interface_id);
4111 
4112 			bcm_bprintf(strbuf, "MAC_table[%d].PSQ(len,state,credit),"
4113 				"(trans,supp_trans,onbus)"
4114 				"= (%d,%s,%d),(%d,%d,%d)\n",
4115 				i,
4116 				mac_table[i].psq.len,
4117 				((mac_table[i].state ==
4118 				WLFC_STATE_OPEN) ? " OPEN":"CLOSE"),
4119 				mac_table[i].requested_credit,
4120 				mac_table[i].transit_count,
4121 				mac_table[i].suppr_transit_count,
4122 				mac_table[i].onbus_pkts_count);
4123 #ifdef PROP_TXSTATUS_DEBUG
4124 			bcm_bprintf(strbuf, "MAC_table[%d]: (opened, closed) = (%d, %d)\n",
4125 				i, mac_table[i].opened_ct, mac_table[i].closed_ct);
4126 #endif
4127 			bcm_bprintf(strbuf, "MAC_table[%d].PSQ"
4128 				"(delay0,sup0,afq0),(delay1,sup1,afq1),(delay2,sup2,afq2),"
4129 				"(delay3,sup3,afq3),(delay4,sup4,afq4) =(%d,%d,%d),"
4130 				"(%d,%d,%d),(%d,%d,%d),(%d,%d,%d),(%d,%d,%d)\n",
4131 				i,
4132 				mac_table[i].psq.q[0].len,
4133 				mac_table[i].psq.q[1].len,
4134 				mac_table[i].afq.q[0].len,
4135 				mac_table[i].psq.q[2].len,
4136 				mac_table[i].psq.q[3].len,
4137 				mac_table[i].afq.q[1].len,
4138 				mac_table[i].psq.q[4].len,
4139 				mac_table[i].psq.q[5].len,
4140 				mac_table[i].afq.q[2].len,
4141 				mac_table[i].psq.q[6].len,
4142 				mac_table[i].psq.q[7].len,
4143 				mac_table[i].afq.q[3].len,
4144 				mac_table[i].psq.q[8].len,
4145 				mac_table[i].psq.q[9].len,
4146 				mac_table[i].afq.q[4].len);
4147 
4148 		}
4149 	}
4150 
4151 #ifdef PROP_TXSTATUS_DEBUG
4152 	{
4153 		int avg;
4154 		int moving_avg = 0;
4155 		int moving_samples;
4156 
4157 		if (wlfc->stats.latency_sample_count) {
4158 			moving_samples = sizeof(wlfc->stats.deltas)/sizeof(uint32);
4159 
4160 			for (i = 0; i < moving_samples; i++)
4161 				moving_avg += wlfc->stats.deltas[i];
4162 			moving_avg /= moving_samples;
4163 
4164 			avg = (100 * wlfc->stats.total_status_latency) /
4165 				wlfc->stats.latency_sample_count;
4166 			bcm_bprintf(strbuf, "txstatus latency (average, last, moving[%d]) = "
4167 				"(%d.%d, %03d, %03d)\n",
4168 				moving_samples, avg/100, (avg - (avg/100)*100),
4169 				wlfc->stats.latency_most_recent,
4170 				moving_avg);
4171 		}
4172 	}
4173 
4174 	bcm_bprintf(strbuf, "wlfc- fifo[0-5] credit stats: sent = (%d,%d,%d,%d,%d,%d), "
4175 		"back = (%d,%d,%d,%d,%d,%d)\n",
4176 		wlfc->stats.fifo_credits_sent[0],
4177 		wlfc->stats.fifo_credits_sent[1],
4178 		wlfc->stats.fifo_credits_sent[2],
4179 		wlfc->stats.fifo_credits_sent[3],
4180 		wlfc->stats.fifo_credits_sent[4],
4181 		wlfc->stats.fifo_credits_sent[5],
4182 
4183 		wlfc->stats.fifo_credits_back[0],
4184 		wlfc->stats.fifo_credits_back[1],
4185 		wlfc->stats.fifo_credits_back[2],
4186 		wlfc->stats.fifo_credits_back[3],
4187 		wlfc->stats.fifo_credits_back[4],
4188 		wlfc->stats.fifo_credits_back[5]);
4189 	{
4190 		uint32 fifo_cr_sent = 0;
4191 		uint32 fifo_cr_acked = 0;
4192 		uint32 request_cr_sent = 0;
4193 		uint32 request_cr_ack = 0;
4194 		uint32 bc_mc_cr_ack = 0;
4195 
4196 		for (i = 0; i < sizeof(wlfc->stats.fifo_credits_sent)/sizeof(uint32); i++) {
4197 			fifo_cr_sent += wlfc->stats.fifo_credits_sent[i];
4198 		}
4199 
4200 		for (i = 0; i < sizeof(wlfc->stats.fifo_credits_back)/sizeof(uint32); i++) {
4201 			fifo_cr_acked += wlfc->stats.fifo_credits_back[i];
4202 		}
4203 
4204 		for (i = 0; i < WLFC_MAC_DESC_TABLE_SIZE; i++) {
4205 			if (wlfc->destination_entries.nodes[i].occupied) {
4206 				request_cr_sent +=
4207 					wlfc->destination_entries.nodes[i].dstncredit_sent_packets;
4208 			}
4209 		}
4210 		for (i = 0; i < WLFC_MAX_IFNUM; i++) {
4211 			if (wlfc->destination_entries.interfaces[i].occupied) {
4212 				request_cr_sent +=
4213 				wlfc->destination_entries.interfaces[i].dstncredit_sent_packets;
4214 			}
4215 		}
4216 		for (i = 0; i < WLFC_MAC_DESC_TABLE_SIZE; i++) {
4217 			if (wlfc->destination_entries.nodes[i].occupied) {
4218 				request_cr_ack +=
4219 					wlfc->destination_entries.nodes[i].dstncredit_acks;
4220 			}
4221 		}
4222 		for (i = 0; i < WLFC_MAX_IFNUM; i++) {
4223 			if (wlfc->destination_entries.interfaces[i].occupied) {
4224 				request_cr_ack +=
4225 					wlfc->destination_entries.interfaces[i].dstncredit_acks;
4226 			}
4227 		}
4228 		bcm_bprintf(strbuf, "wlfc- (sent, status) => pq(%d,%d), vq(%d,%d),"
4229 			"other:%d, bc_mc:%d, signal-only, (sent,freed): (%d,%d)",
4230 			fifo_cr_sent, fifo_cr_acked,
4231 			request_cr_sent, request_cr_ack,
4232 			wlfc->destination_entries.other.dstncredit_acks,
4233 			bc_mc_cr_ack,
4234 			wlfc->stats.signal_only_pkts_sent, wlfc->stats.signal_only_pkts_freed);
4235 	}
4236 #endif /* PROP_TXSTATUS_DEBUG */
4237 	bcm_bprintf(strbuf, "\n");
4238 	bcm_bprintf(strbuf, "wlfc- pkt((in,2bus,txstats,hdrpull,out),(dropped,hdr_only,wlc_tossed)"
4239 		"(freed,free_err,rollback)) = "
4240 		"((%d,%d,%d,%d,%d),(%d,%d,%d),(%d,%d,%d))\n",
4241 		wlfc->stats.pktin,
4242 		wlfc->stats.pkt2bus,
4243 		wlfc->stats.txstatus_in,
4244 		wlfc->stats.dhd_hdrpulls,
4245 		wlfc->stats.pktout,
4246 
4247 		wlfc->stats.pktdropped,
4248 		wlfc->stats.wlfc_header_only_pkt,
4249 		wlfc->stats.wlc_tossed_pkts,
4250 
4251 		wlfc->stats.pkt_freed,
4252 		wlfc->stats.pkt_free_err, wlfc->stats.rollback);
4253 
4254 	bcm_bprintf(strbuf, "wlfc- suppress((d11,wlc,err),enq(d11,wl,hq,mac?),retx(d11,wlc,hq)) = "
4255 		"((%d,%d,%d),(%d,%d,%d,%d),(%d,%d,%d))\n",
4256 		wlfc->stats.d11_suppress,
4257 		wlfc->stats.wl_suppress,
4258 		wlfc->stats.bad_suppress,
4259 
4260 		wlfc->stats.psq_d11sup_enq,
4261 		wlfc->stats.psq_wlsup_enq,
4262 		wlfc->stats.psq_hostq_enq,
4263 		wlfc->stats.mac_handle_notfound,
4264 
4265 		wlfc->stats.psq_d11sup_retx,
4266 		wlfc->stats.psq_wlsup_retx,
4267 		wlfc->stats.psq_hostq_retx);
4268 
4269 	bcm_bprintf(strbuf, "wlfc- cleanup(txq,psq,fw) = (%d,%d,%d)\n",
4270 		wlfc->stats.cleanup_txq_cnt,
4271 		wlfc->stats.cleanup_psq_cnt,
4272 		wlfc->stats.cleanup_fw_cnt);
4273 
4274 	bcm_bprintf(strbuf, "wlfc- generic error: %d\n", wlfc->stats.generic_error);
4275 
4276 	for (i = 0; i < WLFC_MAX_IFNUM; i++) {
4277 		bcm_bprintf(strbuf, "wlfc- if[%d], pkt_cnt_in_q/AC[0-4] = (%d,%d,%d,%d,%d)\n", i,
4278 			wlfc->pkt_cnt_in_q[i][0],
4279 			wlfc->pkt_cnt_in_q[i][1],
4280 			wlfc->pkt_cnt_in_q[i][2],
4281 			wlfc->pkt_cnt_in_q[i][3],
4282 			wlfc->pkt_cnt_in_q[i][4]);
4283 	}
4284 	bcm_bprintf(strbuf, "\n");
4285 
4286 	dhd_os_wlfc_unblock(dhdp);
4287 	return BCME_OK;
4288 } /* dhd_wlfc_dump */
4289 
dhd_wlfc_clear_counts(dhd_pub_t * dhd)4290 int dhd_wlfc_clear_counts(dhd_pub_t *dhd)
4291 {
4292 	athost_wl_status_info_t* wlfc;
4293 	wlfc_hanger_t* hanger;
4294 
4295 	if (dhd == NULL) {
4296 		DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
4297 		return BCME_BADARG;
4298 	}
4299 
4300 	dhd_os_wlfc_block(dhd);
4301 
4302 	if (!dhd->wlfc_state || (dhd->proptxstatus_mode == WLFC_FCMODE_NONE)) {
4303 		dhd_os_wlfc_unblock(dhd);
4304 		return WLFC_UNSUPPORTED;
4305 	}
4306 
4307 	wlfc = (athost_wl_status_info_t*)dhd->wlfc_state;
4308 
4309 	memset(&wlfc->stats, 0, sizeof(athost_wl_stat_counters_t));
4310 
4311 	if (!WLFC_GET_AFQ(dhd->wlfc_mode)) {
4312 		hanger = (wlfc_hanger_t*)wlfc->hanger;
4313 
4314 		hanger->pushed = 0;
4315 		hanger->popped = 0;
4316 		hanger->failed_slotfind = 0;
4317 		hanger->failed_to_pop = 0;
4318 		hanger->failed_to_push = 0;
4319 	}
4320 
4321 	dhd_os_wlfc_unblock(dhd);
4322 
4323 	return BCME_OK;
4324 }
4325 
4326 /** returns TRUE if flow control is enabled */
dhd_wlfc_get_enable(dhd_pub_t * dhd,bool * val)4327 int dhd_wlfc_get_enable(dhd_pub_t *dhd, bool *val)
4328 {
4329 	if (!dhd || !val) {
4330 		DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
4331 		return BCME_BADARG;
4332 	}
4333 
4334 	dhd_os_wlfc_block(dhd);
4335 
4336 	*val = dhd->wlfc_enabled;
4337 
4338 	dhd_os_wlfc_unblock(dhd);
4339 
4340 	return BCME_OK;
4341 }
4342 
4343 /** Called via an IOVAR */
dhd_wlfc_get_mode(dhd_pub_t * dhd,int * val)4344 int dhd_wlfc_get_mode(dhd_pub_t *dhd, int *val)
4345 {
4346 	if (!dhd || !val) {
4347 		DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
4348 		return BCME_BADARG;
4349 	}
4350 
4351 	dhd_os_wlfc_block(dhd);
4352 
4353 	*val = dhd->wlfc_state ? dhd->proptxstatus_mode : 0;
4354 
4355 	dhd_os_wlfc_unblock(dhd);
4356 
4357 	return BCME_OK;
4358 }
4359 
4360 /** Called via an IOVAR */
dhd_wlfc_set_mode(dhd_pub_t * dhd,int val)4361 int dhd_wlfc_set_mode(dhd_pub_t *dhd, int val)
4362 {
4363 	if (!dhd) {
4364 		DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
4365 		return BCME_BADARG;
4366 	}
4367 
4368 	dhd_os_wlfc_block(dhd);
4369 
4370 	if (dhd->wlfc_state) {
4371 		dhd->proptxstatus_mode = val & 0xff;
4372 	}
4373 
4374 	dhd_os_wlfc_unblock(dhd);
4375 
4376 	return BCME_OK;
4377 }
4378 
4379 /** Called when rx frame is received from the dongle */
dhd_wlfc_is_header_only_pkt(dhd_pub_t * dhd,void * pktbuf)4380 bool dhd_wlfc_is_header_only_pkt(dhd_pub_t * dhd, void *pktbuf)
4381 {
4382 	athost_wl_status_info_t* wlfc;
4383 	bool rc = FALSE;
4384 
4385 	if (dhd == NULL) {
4386 		DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
4387 		return FALSE;
4388 	}
4389 
4390 	dhd_os_wlfc_block(dhd);
4391 
4392 	if (!dhd->wlfc_state || (dhd->proptxstatus_mode == WLFC_FCMODE_NONE)) {
4393 		dhd_os_wlfc_unblock(dhd);
4394 		return FALSE;
4395 	}
4396 
4397 	wlfc = (athost_wl_status_info_t*)dhd->wlfc_state;
4398 
4399 	if (PKTLEN(wlfc->osh, pktbuf) == 0) {
4400 		wlfc->stats.wlfc_header_only_pkt++;
4401 		rc = TRUE;
4402 	}
4403 
4404 	dhd_os_wlfc_unblock(dhd);
4405 
4406 	return rc;
4407 }
4408 
dhd_wlfc_flowcontrol(dhd_pub_t * dhdp,bool state,bool bAcquireLock)4409 int dhd_wlfc_flowcontrol(dhd_pub_t *dhdp, bool state, bool bAcquireLock)
4410 {
4411 	if (dhdp == NULL) {
4412 		DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
4413 		return BCME_BADARG;
4414 	}
4415 
4416 	if (bAcquireLock) {
4417 		dhd_os_wlfc_block(dhdp);
4418 	}
4419 
4420 	if (!dhdp->wlfc_state || (dhdp->proptxstatus_mode == WLFC_FCMODE_NONE) ||
4421 		dhdp->proptxstatus_module_ignore) {
4422 		if (bAcquireLock) {
4423 			dhd_os_wlfc_unblock(dhdp);
4424 		}
4425 		return WLFC_UNSUPPORTED;
4426 	}
4427 
4428 	if (state != dhdp->proptxstatus_txoff) {
4429 		dhdp->proptxstatus_txoff = state;
4430 	}
4431 
4432 	if (bAcquireLock) {
4433 		dhd_os_wlfc_unblock(dhdp);
4434 	}
4435 
4436 	return BCME_OK;
4437 }
4438 
4439 /** Called when eg an rx frame is received from the dongle */
dhd_wlfc_save_rxpath_ac_time(dhd_pub_t * dhd,uint8 prio)4440 int dhd_wlfc_save_rxpath_ac_time(dhd_pub_t * dhd, uint8 prio)
4441 {
4442 	athost_wl_status_info_t* wlfc;
4443 	int rx_path_ac = -1;
4444 
4445 	if ((dhd == NULL) || (prio >= NUMPRIO)) {
4446 		DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
4447 		return BCME_BADARG;
4448 	}
4449 
4450 	dhd_os_wlfc_block(dhd);
4451 
4452 	if (!dhd->wlfc_rxpkt_chk) {
4453 		dhd_os_wlfc_unblock(dhd);
4454 		return BCME_OK;
4455 	}
4456 
4457 	if (!dhd->wlfc_state || (dhd->proptxstatus_mode == WLFC_FCMODE_NONE)) {
4458 		dhd_os_wlfc_unblock(dhd);
4459 		return WLFC_UNSUPPORTED;
4460 	}
4461 
4462 	wlfc = (athost_wl_status_info_t*)dhd->wlfc_state;
4463 
4464 	rx_path_ac = prio2fifo[prio];
4465 	wlfc->rx_timestamp[rx_path_ac] = OSL_SYSUPTIME();
4466 
4467 	dhd_os_wlfc_unblock(dhd);
4468 
4469 	return BCME_OK;
4470 }
4471 
4472 /** called via an IOVAR */
dhd_wlfc_get_module_ignore(dhd_pub_t * dhd,int * val)4473 int dhd_wlfc_get_module_ignore(dhd_pub_t *dhd, int *val)
4474 {
4475 	if (!dhd || !val) {
4476 		DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
4477 		return BCME_BADARG;
4478 	}
4479 
4480 	dhd_os_wlfc_block(dhd);
4481 
4482 	*val = dhd->proptxstatus_module_ignore;
4483 
4484 	dhd_os_wlfc_unblock(dhd);
4485 
4486 	return BCME_OK;
4487 }
4488 
4489 /** called via an IOVAR */
dhd_wlfc_set_module_ignore(dhd_pub_t * dhd,int val)4490 int dhd_wlfc_set_module_ignore(dhd_pub_t *dhd, int val)
4491 {
4492 	uint32 tlv = 0;
4493 	bool bChanged = FALSE;
4494 
4495 	if (!dhd) {
4496 		DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
4497 		return BCME_BADARG;
4498 	}
4499 
4500 	dhd_os_wlfc_block(dhd);
4501 
4502 	if ((bool)val != dhd->proptxstatus_module_ignore) {
4503 		dhd->proptxstatus_module_ignore = (val != 0);
4504 		/* force txstatus_ignore sync with proptxstatus_module_ignore */
4505 		dhd->proptxstatus_txstatus_ignore = dhd->proptxstatus_module_ignore;
4506 		if (FALSE == dhd->proptxstatus_module_ignore) {
4507 			tlv = WLFC_FLAGS_RSSI_SIGNALS |
4508 				WLFC_FLAGS_XONXOFF_SIGNALS |
4509 				WLFC_FLAGS_CREDIT_STATUS_SIGNALS |
4510 				WLFC_FLAGS_HOST_PROPTXSTATUS_ACTIVE;
4511 		}
4512 		/* always enable host reorder */
4513 		tlv |= WLFC_FLAGS_HOST_RXRERODER_ACTIVE;
4514 		bChanged = TRUE;
4515 	}
4516 
4517 	dhd_os_wlfc_unblock(dhd);
4518 
4519 	if (bChanged) {
4520 		/* select enable proptxtstatus signaling */
4521 		if (dhd_wl_ioctl_set_intiovar(dhd, "tlv", tlv, WLC_SET_VAR, TRUE, 0)) {
4522 			DHD_ERROR(("%s: failed to set bdcv2 tlv signaling to 0x%x\n",
4523 				__FUNCTION__, tlv));
4524 		} else {
4525 			DHD_ERROR(("%s: successfully set bdcv2 tlv signaling to 0x%x\n",
4526 				__FUNCTION__, tlv));
4527 		}
4528 	}
4529 
4530 #if defined(DHD_WLFC_THREAD)
4531 	_dhd_wlfc_thread_wakeup(dhd);
4532 #endif /* defined(DHD_WLFC_THREAD) */
4533 
4534 	return BCME_OK;
4535 }
4536 
4537 /** called via an IOVAR */
dhd_wlfc_get_credit_ignore(dhd_pub_t * dhd,int * val)4538 int dhd_wlfc_get_credit_ignore(dhd_pub_t *dhd, int *val)
4539 {
4540 	if (!dhd || !val) {
4541 		DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
4542 		return BCME_BADARG;
4543 	}
4544 
4545 	dhd_os_wlfc_block(dhd);
4546 
4547 	*val = dhd->proptxstatus_credit_ignore;
4548 
4549 	dhd_os_wlfc_unblock(dhd);
4550 
4551 	return BCME_OK;
4552 }
4553 
4554 /** called via an IOVAR */
dhd_wlfc_set_credit_ignore(dhd_pub_t * dhd,int val)4555 int dhd_wlfc_set_credit_ignore(dhd_pub_t *dhd, int val)
4556 {
4557 	if (!dhd) {
4558 		DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
4559 		return BCME_BADARG;
4560 	}
4561 
4562 	dhd_os_wlfc_block(dhd);
4563 
4564 	dhd->proptxstatus_credit_ignore = (val != 0);
4565 
4566 	dhd_os_wlfc_unblock(dhd);
4567 
4568 	return BCME_OK;
4569 }
4570 
4571 /** called via an IOVAR */
dhd_wlfc_get_txstatus_ignore(dhd_pub_t * dhd,int * val)4572 int dhd_wlfc_get_txstatus_ignore(dhd_pub_t *dhd, int *val)
4573 {
4574 	if (!dhd || !val) {
4575 		DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
4576 		return BCME_BADARG;
4577 	}
4578 
4579 	dhd_os_wlfc_block(dhd);
4580 
4581 	*val = dhd->proptxstatus_txstatus_ignore;
4582 
4583 	dhd_os_wlfc_unblock(dhd);
4584 
4585 	return BCME_OK;
4586 }
4587 
4588 /** called via an IOVAR */
dhd_wlfc_set_txstatus_ignore(dhd_pub_t * dhd,int val)4589 int dhd_wlfc_set_txstatus_ignore(dhd_pub_t *dhd, int val)
4590 {
4591 	if (!dhd) {
4592 		DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
4593 		return BCME_BADARG;
4594 	}
4595 
4596 	dhd_os_wlfc_block(dhd);
4597 
4598 	dhd->proptxstatus_txstatus_ignore = (val != 0);
4599 
4600 	dhd_os_wlfc_unblock(dhd);
4601 
4602 	return BCME_OK;
4603 }
4604 
4605 /** called via an IOVAR */
dhd_wlfc_get_rxpkt_chk(dhd_pub_t * dhd,int * val)4606 int dhd_wlfc_get_rxpkt_chk(dhd_pub_t *dhd, int *val)
4607 {
4608 	if (!dhd || !val) {
4609 		DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
4610 		return BCME_BADARG;
4611 	}
4612 
4613 	dhd_os_wlfc_block(dhd);
4614 
4615 	*val = dhd->wlfc_rxpkt_chk;
4616 
4617 	dhd_os_wlfc_unblock(dhd);
4618 
4619 	return BCME_OK;
4620 }
4621 
4622 /** called via an IOVAR */
dhd_wlfc_set_rxpkt_chk(dhd_pub_t * dhd,int val)4623 int dhd_wlfc_set_rxpkt_chk(dhd_pub_t *dhd, int val)
4624 {
4625 	if (!dhd) {
4626 		DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
4627 		return BCME_BADARG;
4628 	}
4629 
4630 	dhd_os_wlfc_block(dhd);
4631 
4632 	dhd->wlfc_rxpkt_chk = (val != 0);
4633 
4634 	dhd_os_wlfc_unblock(dhd);
4635 
4636 	return BCME_OK;
4637 }
4638 
4639 #ifdef PROPTX_MAXCOUNT
dhd_wlfc_update_maxcount(dhd_pub_t * dhdp,uint8 ifid,int maxcount)4640 int dhd_wlfc_update_maxcount(dhd_pub_t *dhdp, uint8 ifid, int maxcount)
4641 {
4642 	athost_wl_status_info_t* ctx;
4643 	int rc = 0;
4644 
4645 	if (dhdp == NULL) {
4646 		DHD_ERROR(("%s: dhdp is NULL\n", __FUNCTION__));
4647 		return BCME_BADARG;
4648 	}
4649 
4650 	dhd_os_wlfc_block(dhdp);
4651 
4652 	if (!dhdp->wlfc_state || (dhdp->proptxstatus_mode == WLFC_FCMODE_NONE)) {
4653 		rc = WLFC_UNSUPPORTED;
4654 		goto exit;
4655 	}
4656 
4657 	if (ifid >= WLFC_MAX_IFNUM) {
4658 		DHD_ERROR(("%s: bad ifid\n", __FUNCTION__));
4659 		rc = BCME_BADARG;
4660 		goto exit;
4661 	}
4662 
4663 	ctx = (athost_wl_status_info_t*)dhdp->wlfc_state;
4664 	ctx->destination_entries.interfaces[ifid].transit_maxcount = maxcount;
4665 exit:
4666 	dhd_os_wlfc_unblock(dhdp);
4667 	return rc;
4668 }
4669 #endif /* PROPTX_MAXCOUNT */
4670 
4671 #endif /* PROP_TXSTATUS */
4672