blob: cc75cc6743cd8a69b734f39321dd8fcbcc0fec62 [file] [log] [blame]
/*
* DHD PROP_TXSTATUS Module.
*
* Copyright (C) 1999-2014, Broadcom Corporation
*
* Unless you and Broadcom execute a separate written software license
* agreement governing use of this software, this software is licensed to you
* under the terms of the GNU General Public License version 2 (the "GPL"),
* available at http://www.broadcom.com/licenses/GPLv2.php, with the
* following added to such license:
*
* As a special exception, the copyright holders of this software give you
* permission to link this software with independent modules, and to copy and
* distribute the resulting executable under terms of your choice, provided that
* you also meet, for each linked independent module, the terms and conditions of
* the license of that module. An independent module is a module which is not
* derived from this software. The special exception does not apply to any
* modifications of the software.
*
* Notwithstanding the above, under no circumstances may you combine this
* software in any way with any other Broadcom software provided under a license
* other than the GPL, without Broadcom's express prior written consent.
*
* $Id: dhd_wlfc.c 490028 2014-07-09 05:58:25Z $
*
*/
#include <typedefs.h>
#include <osl.h>
#include <bcmutils.h>
#include <bcmendian.h>
#include <dngl_stats.h>
#include <dhd.h>
#include <dhd_bus.h>
#include <dhd_dbg.h>
#ifdef PROP_TXSTATUS
#include <wlfc_proto.h>
#include <dhd_wlfc.h>
#endif
#include <dhd_ip.h>
/*
* wlfc naming and lock rules:
*
* 1. Private functions name like _dhd_wlfc_XXX, declared as static and avoid wlfc lock operation.
* 2. Public functions name like dhd_wlfc_XXX, use wlfc lock if needed.
* 3. Non-Proptxstatus module call public functions only and avoid wlfc lock operation.
*
*/
#ifdef PROP_TXSTATUS
#define DHD_WLFC_QMON_COMPLETE(entry)
#define LIMIT_BORROW
static uint16
_dhd_wlfc_adjusted_seq(void* p, uint8 current_seq)
{
uint16 seq;
if (!p) {
return 0xffff;
}
seq = WL_TXSTATUS_GET_FREERUNCTR(DHD_PKTTAG_H2DTAG(PKTTAG(p)));
if (seq < current_seq) {
/* wrap around */
seq += 256;
}
return seq;
}
static void
_dhd_wlfc_prec_enque(struct pktq *pq, int prec, void* p, bool qHead,
uint8 current_seq, bool reOrder)
{
struct pktq_prec *q;
uint16 seq, seq2;
void *p2, *p2_prev;
if (!p)
return;
ASSERT(prec >= 0 && prec < pq->num_prec);
/* queueing chains not allowed */
ASSERT(!((PKTLINK(p) != NULL) && (PKTLINK(p) != p)));
ASSERT(!pktq_full(pq));
ASSERT(!pktq_pfull(pq, prec));
q = &pq->q[prec];
PKTSETLINK(p, NULL);
if (q->head == NULL) {
/* empty queue */
q->head = p;
q->tail = p;
} else {
if (reOrder && (prec & 1)) {
seq = _dhd_wlfc_adjusted_seq(p, current_seq);
p2 = qHead ? q->head : q->tail;
seq2 = _dhd_wlfc_adjusted_seq(p2, current_seq);
if ((qHead &&((seq+1) > seq2)) || (!qHead && ((seq2+1) > seq))) {
/* need reorder */
p2 = q->head;
p2_prev = NULL;
seq2 = _dhd_wlfc_adjusted_seq(p2, current_seq);
while (seq > seq2) {
p2_prev = p2;
p2 = PKTLINK(p2);
if (!p2) {
break;
}
seq2 = _dhd_wlfc_adjusted_seq(p2, current_seq);
}
if (p2_prev == NULL) {
/* insert head */
PKTSETLINK(p, q->head);
q->head = p;
} else if (p2 == NULL) {
/* insert tail */
PKTSETLINK(p2_prev, p);
q->tail = p;
} else {
/* insert after p2_prev */
PKTSETLINK(p, PKTLINK(p2_prev));
PKTSETLINK(p2_prev, p);
}
goto exit;
}
}
if (qHead) {
PKTSETLINK(p, q->head);
q->head = p;
} else {
PKTSETLINK(q->tail, p);
q->tail = p;
}
}
exit:
q->len++;
pq->len++;
if (pq->hi_prec < prec)
pq->hi_prec = (uint8)prec;
}
/* Create a place to store all packet pointers submitted to the firmware until
a status comes back, suppress or otherwise.
hang-er: noun, a contrivance on which things are hung, as a hook.
*/
static void*
_dhd_wlfc_hanger_create(osl_t *osh, int max_items)
{
int i;
wlfc_hanger_t* hanger;
/* allow only up to a specific size for now */
ASSERT(max_items == WLFC_HANGER_MAXITEMS);
if ((hanger = (wlfc_hanger_t*)MALLOC(osh, WLFC_HANGER_SIZE(max_items))) == NULL)
return NULL;
memset(hanger, 0, WLFC_HANGER_SIZE(max_items));
hanger->max_items = max_items;
for (i = 0; i < hanger->max_items; i++) {
hanger->items[i].state = WLFC_HANGER_ITEM_STATE_FREE;
}
return hanger;
}
static int
_dhd_wlfc_hanger_delete(osl_t *osh, void* hanger)
{
wlfc_hanger_t* h = (wlfc_hanger_t*)hanger;
if (h) {
MFREE(osh, h, WLFC_HANGER_SIZE(h->max_items));
return BCME_OK;
}
return BCME_BADARG;
}
static uint16
_dhd_wlfc_hanger_get_free_slot(void* hanger)
{
uint32 i;
wlfc_hanger_t* h = (wlfc_hanger_t*)hanger;
if (h) {
i = h->slot_pos + 1;
if (i == h->max_items) {
i = 0;
}
while (i != h->slot_pos) {
if (h->items[i].state == WLFC_HANGER_ITEM_STATE_FREE) {
h->slot_pos = i;
return (uint16)i;
}
i++;
if (i == h->max_items)
i = 0;
}
h->failed_slotfind++;
}
return WLFC_HANGER_MAXITEMS;
}
static int
_dhd_wlfc_hanger_get_genbit(void* hanger, void* pkt, uint32 slot_id, int* gen)
{
int rc = BCME_OK;
wlfc_hanger_t* h = (wlfc_hanger_t*)hanger;
*gen = 0xff;
/* this packet was not pushed at the time it went to the firmware */
if (slot_id == WLFC_HANGER_MAXITEMS)
return BCME_NOTFOUND;
if (h) {
if ((h->items[slot_id].state == WLFC_HANGER_ITEM_STATE_INUSE) ||
(h->items[slot_id].state == WLFC_HANGER_ITEM_STATE_INUSE_SUPPRESSED)) {
*gen = h->items[slot_id].gen;
}
else {
rc = BCME_NOTFOUND;
}
}
else
rc = BCME_BADARG;
return rc;
}
static int
_dhd_wlfc_hanger_pushpkt(void* hanger, void* pkt, uint32 slot_id)
{
int rc = BCME_OK;
wlfc_hanger_t* h = (wlfc_hanger_t*)hanger;
if (h && (slot_id < WLFC_HANGER_MAXITEMS)) {
if (h->items[slot_id].state == WLFC_HANGER_ITEM_STATE_FREE) {
h->items[slot_id].state = WLFC_HANGER_ITEM_STATE_INUSE;
h->items[slot_id].pkt = pkt;
h->items[slot_id].pkt_state = 0;
h->items[slot_id].pkt_txstatus = 0;
h->pushed++;
}
else {
h->failed_to_push++;
rc = BCME_NOTFOUND;
}
}
else
rc = BCME_BADARG;
return rc;
}
static int
_dhd_wlfc_hanger_poppkt(void* hanger, uint32 slot_id, void** pktout, bool remove_from_hanger)
{
int rc = BCME_OK;
wlfc_hanger_t* h = (wlfc_hanger_t*)hanger;
/* this packet was not pushed at the time it went to the firmware */
if (slot_id == WLFC_HANGER_MAXITEMS)
return BCME_NOTFOUND;
if (h) {
if (h->items[slot_id].state != WLFC_HANGER_ITEM_STATE_FREE) {
*pktout = h->items[slot_id].pkt;
if (remove_from_hanger) {
h->items[slot_id].state =
WLFC_HANGER_ITEM_STATE_FREE;
h->items[slot_id].pkt = NULL;
h->items[slot_id].gen = 0xff;
h->items[slot_id].identifier = 0;
h->popped++;
}
}
else {
h->failed_to_pop++;
rc = BCME_NOTFOUND;
}
}
else
rc = BCME_BADARG;
return rc;
}
static int
_dhd_wlfc_hanger_mark_suppressed(void* hanger, uint32 slot_id, uint8 gen)
{
int rc = BCME_OK;
wlfc_hanger_t* h = (wlfc_hanger_t*)hanger;
/* this packet was not pushed at the time it went to the firmware */
if (slot_id == WLFC_HANGER_MAXITEMS)
return BCME_NOTFOUND;
if (h) {
h->items[slot_id].gen = gen;
if (h->items[slot_id].state == WLFC_HANGER_ITEM_STATE_INUSE) {
h->items[slot_id].state = WLFC_HANGER_ITEM_STATE_INUSE_SUPPRESSED;
}
else
rc = BCME_BADARG;
}
else
rc = BCME_BADARG;
return rc;
}
/* remove reference of specific packet in hanger */
static bool
_dhd_wlfc_hanger_remove_reference(wlfc_hanger_t* h, void* pkt)
{
int i;
if (!h || !pkt) {
return FALSE;
}
for (i = 0; i < h->max_items; i++) {
if (pkt == h->items[i].pkt) {
if ((h->items[i].state == WLFC_HANGER_ITEM_STATE_INUSE) ||
(h->items[i].state == WLFC_HANGER_ITEM_STATE_INUSE_SUPPRESSED)) {
h->items[i].state = WLFC_HANGER_ITEM_STATE_FREE;
h->items[i].pkt = NULL;
h->items[i].gen = 0xff;
h->items[i].identifier = 0;
}
return TRUE;
}
}
return FALSE;
}
static int
_dhd_wlfc_enque_afq(athost_wl_status_info_t* ctx, void *p)
{
wlfc_mac_descriptor_t* entry;
uint16 entry_idx = WL_TXSTATUS_GET_HSLOT(DHD_PKTTAG_H2DTAG(PKTTAG(p)));
uint8 prec = DHD_PKTTAG_FIFO(PKTTAG(p));
if (entry_idx < WLFC_MAC_DESC_TABLE_SIZE)
entry = &ctx->destination_entries.nodes[entry_idx];
else if (entry_idx < (WLFC_MAC_DESC_TABLE_SIZE + WLFC_MAX_IFNUM))
entry = &ctx->destination_entries.interfaces[entry_idx - WLFC_MAC_DESC_TABLE_SIZE];
else
entry = &ctx->destination_entries.other;
pktq_penq(&entry->afq, prec, p);
return BCME_OK;
}
static int
_dhd_wlfc_deque_afq(athost_wl_status_info_t* ctx, uint16 hslot, uint8 hcnt, uint8 prec,
void **pktout)
{
wlfc_mac_descriptor_t *entry;
struct pktq *pq;
struct pktq_prec *q;
void *p, *b;
if (!ctx) {
DHD_ERROR(("%s: ctx(%p), pktout(%p)\n", __FUNCTION__, ctx, pktout));
return BCME_BADARG;
}
if (pktout) {
*pktout = NULL;
}
ASSERT(hslot < (WLFC_MAC_DESC_TABLE_SIZE + WLFC_MAX_IFNUM + 1));
if (hslot < WLFC_MAC_DESC_TABLE_SIZE)
entry = &ctx->destination_entries.nodes[hslot];
else if (hslot < (WLFC_MAC_DESC_TABLE_SIZE + WLFC_MAX_IFNUM))
entry = &ctx->destination_entries.interfaces[hslot - WLFC_MAC_DESC_TABLE_SIZE];
else
entry = &ctx->destination_entries.other;
pq = &entry->afq;
ASSERT(prec < pq->num_prec);
q = &pq->q[prec];
b = NULL;
p = q->head;
while (p && (hcnt != WL_TXSTATUS_GET_FREERUNCTR(DHD_PKTTAG_H2DTAG(PKTTAG(p)))))
{
b = p;
p = PKTLINK(p);
}
if (p == NULL) {
/* none is matched */
if (b) {
DHD_ERROR(("%s: can't find matching seq(%d)\n", __FUNCTION__, hcnt));
} else {
DHD_ERROR(("%s: queue is empty\n", __FUNCTION__));
}
return BCME_ERROR;
}
if (!b) {
/* head packet is matched */
if ((q->head = PKTLINK(p)) == NULL) {
q->tail = NULL;
}
} else {
/* middle packet is matched */
DHD_INFO(("%s: out of order, seq(%d), head_seq(%d)\n", __FUNCTION__, hcnt,
WL_TXSTATUS_GET_FREERUNCTR(DHD_PKTTAG_H2DTAG(PKTTAG(q->head)))));
ctx->stats.ooo_pkts[prec]++;
PKTSETLINK(b, PKTLINK(p));
if (PKTLINK(p) == NULL) {
q->tail = b;
}
}
q->len--;
pq->len--;
PKTSETLINK(p, NULL);
if (pktout) {
*pktout = p;
}
return BCME_OK;
}
static int
_dhd_wlfc_pushheader(athost_wl_status_info_t* ctx, void* p, bool tim_signal,
uint8 tim_bmp, uint8 mac_handle, uint32 htodtag, uint16 htodseq, bool skip_wlfc_hdr)
{
uint32 wl_pktinfo = 0;
uint8* wlh;
uint8 dataOffset = 0;
uint8 fillers;
uint8 tim_signal_len = 0;
dhd_pub_t *dhdp = (dhd_pub_t *)ctx->dhdp;
struct bdc_header *h;
if (skip_wlfc_hdr)
goto push_bdc_hdr;
if (tim_signal) {
tim_signal_len = TLV_HDR_LEN + WLFC_CTL_VALUE_LEN_PENDING_TRAFFIC_BMP;
}
/* +2 is for Type[1] and Len[1] in TLV, plus TIM signal */
dataOffset = WLFC_CTL_VALUE_LEN_PKTTAG + TLV_HDR_LEN + tim_signal_len;
if (WLFC_GET_REUSESEQ(dhdp->wlfc_mode)) {
dataOffset += WLFC_CTL_VALUE_LEN_SEQ;
}
fillers = ROUNDUP(dataOffset, 4) - dataOffset;
dataOffset += fillers;
PKTPUSH(ctx->osh, p, dataOffset);
wlh = (uint8*) PKTDATA(ctx->osh, p);
wl_pktinfo = htol32(htodtag);
wlh[TLV_TAG_OFF] = WLFC_CTL_TYPE_PKTTAG;
wlh[TLV_LEN_OFF] = WLFC_CTL_VALUE_LEN_PKTTAG;
memcpy(&wlh[TLV_HDR_LEN], &wl_pktinfo, sizeof(uint32));
if (WLFC_GET_REUSESEQ(dhdp->wlfc_mode)) {
uint16 wl_seqinfo = htol16(htodseq);
wlh[TLV_LEN_OFF] += WLFC_CTL_VALUE_LEN_SEQ;
memcpy(&wlh[TLV_HDR_LEN + WLFC_CTL_VALUE_LEN_PKTTAG], &wl_seqinfo,
WLFC_CTL_VALUE_LEN_SEQ);
}
if (tim_signal_len) {
wlh[dataOffset - fillers - tim_signal_len ] =
WLFC_CTL_TYPE_PENDING_TRAFFIC_BMP;
wlh[dataOffset - fillers - tim_signal_len + 1] =
WLFC_CTL_VALUE_LEN_PENDING_TRAFFIC_BMP;
wlh[dataOffset - fillers - tim_signal_len + 2] = mac_handle;
wlh[dataOffset - fillers - tim_signal_len + 3] = tim_bmp;
}
if (fillers)
memset(&wlh[dataOffset - fillers], WLFC_CTL_TYPE_FILLER, fillers);
push_bdc_hdr:
PKTPUSH(ctx->osh, p, BDC_HEADER_LEN);
h = (struct bdc_header *)PKTDATA(ctx->osh, p);
h->flags = (BDC_PROTO_VER << BDC_FLAG_VER_SHIFT);
if (PKTSUMNEEDED(p))
h->flags |= BDC_FLAG_SUM_NEEDED;
h->priority = (PKTPRIO(p) & BDC_PRIORITY_MASK);
h->flags2 = 0;
h->dataOffset = dataOffset >> 2;
BDC_SET_IF_IDX(h, DHD_PKTTAG_IF(PKTTAG(p)));
return BCME_OK;
}
static int
_dhd_wlfc_pullheader(athost_wl_status_info_t* ctx, void* pktbuf)
{
struct bdc_header *h;
if (PKTLEN(ctx->osh, pktbuf) < BDC_HEADER_LEN) {
DHD_ERROR(("%s: rx data too short (%d < %d)\n", __FUNCTION__,
PKTLEN(ctx->osh, pktbuf), BDC_HEADER_LEN));
return BCME_ERROR;
}
h = (struct bdc_header *)PKTDATA(ctx->osh, pktbuf);
/* pull BDC header */
PKTPULL(ctx->osh, pktbuf, BDC_HEADER_LEN);
if (PKTLEN(ctx->osh, pktbuf) < (uint)(h->dataOffset << 2)) {
DHD_ERROR(("%s: rx data too short (%d < %d)\n", __FUNCTION__,
PKTLEN(ctx->osh, pktbuf), (h->dataOffset << 2)));
return BCME_ERROR;
}
/* pull wl-header */
PKTPULL(ctx->osh, pktbuf, (h->dataOffset << 2));
return BCME_OK;
}
static wlfc_mac_descriptor_t*
_dhd_wlfc_find_table_entry(athost_wl_status_info_t* ctx, void* p)
{
int i;
wlfc_mac_descriptor_t* table = ctx->destination_entries.nodes;
uint8 ifid = DHD_PKTTAG_IF(PKTTAG(p));
uint8* dstn = DHD_PKTTAG_DSTN(PKTTAG(p));
wlfc_mac_descriptor_t* entry = DHD_PKTTAG_ENTRY(PKTTAG(p));
int iftype = ctx->destination_entries.interfaces[ifid].iftype;
/* saved one exists, return it */
if (entry)
return entry;
/* Multicast destination, STA and P2P clients get the interface entry.
* STA/GC gets the Mac Entry for TDLS destinations, TDLS destinations
* have their own entry.
*/
if ((iftype == WLC_E_IF_ROLE_STA || ETHER_ISMULTI(dstn) ||
iftype == WLC_E_IF_ROLE_P2P_CLIENT) &&
(ctx->destination_entries.interfaces[ifid].occupied)) {
entry = &ctx->destination_entries.interfaces[ifid];
}
if (entry && ETHER_ISMULTI(dstn)) {
DHD_PKTTAG_SET_ENTRY(PKTTAG(p), entry);
return entry;
}
for (i = 0; i < WLFC_MAC_DESC_TABLE_SIZE; i++) {
if (table[i].occupied) {
if (table[i].interface_id == ifid) {
if (!memcmp(table[i].ea, dstn, ETHER_ADDR_LEN)) {
entry = &table[i];
break;
}
}
}
}
if (entry == NULL)
entry = &ctx->destination_entries.other;
DHD_PKTTAG_SET_ENTRY(PKTTAG(p), entry);
return entry;
}
static int
_dhd_wlfc_prec_drop(dhd_pub_t *dhdp, int prec, void* p, bool bPktInQ)
{
athost_wl_status_info_t* ctx;
void *pout = NULL;
ASSERT(dhdp && p);
ASSERT(prec >= 0 && prec < WLFC_PSQ_PREC_COUNT);
ctx = (athost_wl_status_info_t*)dhdp->wlfc_state;
if (!WLFC_GET_AFQ(dhdp->wlfc_mode) && (prec & 1)) {
/* suppressed queue, need pop from hanger */
_dhd_wlfc_hanger_poppkt(ctx->hanger, WL_TXSTATUS_GET_HSLOT(DHD_PKTTAG_H2DTAG
(PKTTAG(p))), &pout, TRUE);
ASSERT(p == pout);
}
if (!(prec & 1)) {
#ifdef DHDTCPACK_SUPPRESS
/* pkt in delayed q, so fake push BDC header for
* dhd_tcpack_check_xmit() and dhd_txcomplete().
*/
_dhd_wlfc_pushheader(ctx, p, FALSE, 0, 0, 0, 0, TRUE);
/* This packet is about to be freed, so remove it from tcp_ack_info_tbl
* This must be one of...
* 1. A pkt already in delayQ is evicted by another pkt with higher precedence
* in _dhd_wlfc_prec_enq_with_drop()
* 2. A pkt could not be enqueued to delayQ because it is full,
* in _dhd_wlfc_enque_delayq().
* 3. A pkt could not be enqueued to delayQ because it is full,
* in _dhd_wlfc_rollback_packet_toq().
*/
if (dhd_tcpack_check_xmit(dhdp, p) == BCME_ERROR) {
DHD_ERROR(("%s %d: tcpack_suppress ERROR!!!"
" Stop using it\n",
__FUNCTION__, __LINE__));
dhd_tcpack_suppress_set(dhdp, TCPACK_SUP_OFF);
}
#endif /* DHDTCPACK_SUPPRESS */
}
if (bPktInQ) {
ctx->pkt_cnt_in_q[DHD_PKTTAG_IF(PKTTAG(p))][prec>>1]--;
ctx->pkt_cnt_per_ac[prec>>1]--;
}
ctx->pkt_cnt_in_drv[DHD_PKTTAG_IF(PKTTAG(p))][DHD_PKTTAG_FIFO(PKTTAG(p))]--;
ctx->stats.pktout++;
ctx->stats.drop_pkts[prec]++;
dhd_txcomplete(dhdp, p, FALSE);
PKTFREE(ctx->osh, p, TRUE);
return 0;
}
static bool
_dhd_wlfc_prec_enq_with_drop(dhd_pub_t *dhdp, struct pktq *pq, void *pkt, int prec, bool qHead,
uint8 current_seq)
{
void *p = NULL;
int eprec = -1; /* precedence to evict from */
athost_wl_status_info_t* ctx;
ASSERT(dhdp && pq && pkt);
ASSERT(prec >= 0 && prec < pq->num_prec);
ctx = (athost_wl_status_info_t*)dhdp->wlfc_state;
/* Fast case, precedence queue is not full and we are also not
* exceeding total queue length
*/
if (!pktq_pfull(pq, prec) && !pktq_full(pq)) {
goto exit;
}
/* Determine precedence from which to evict packet, if any */
if (pktq_pfull(pq, prec))
eprec = prec;
else if (pktq_full(pq)) {
p = pktq_peek_tail(pq, &eprec);
if (!p) {
DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
return FALSE;
}
if ((eprec > prec) || (eprec < 0)) {
if (!pktq_pempty(pq, prec)) {
eprec = prec;
} else {
return FALSE;
}
}
}
/* Evict if needed */
if (eprec >= 0) {
/* Detect queueing to unconfigured precedence */
ASSERT(!pktq_pempty(pq, eprec));
/* Evict all fragmented frames */
dhd_prec_drop_pkts(dhdp, pq, eprec, _dhd_wlfc_prec_drop);
}
exit:
/* Enqueue */
_dhd_wlfc_prec_enque(pq, prec, pkt, qHead, current_seq,
WLFC_GET_REORDERSUPP(dhdp->wlfc_mode));
ctx->pkt_cnt_in_q[DHD_PKTTAG_IF(PKTTAG(pkt))][prec>>1]++;
ctx->pkt_cnt_per_ac[prec>>1]++;
return TRUE;
}
static int
_dhd_wlfc_rollback_packet_toq(athost_wl_status_info_t* ctx,
void* p, ewlfc_packet_state_t pkt_type, uint32 hslot)
{
/*
put the packet back to the head of queue
- suppressed packet goes back to suppress sub-queue
- pull out the header, if new or delayed packet
Note: hslot is used only when header removal is done.
*/
wlfc_mac_descriptor_t* entry;
int rc = BCME_OK;
int prec, fifo_id;
entry = _dhd_wlfc_find_table_entry(ctx, p);
prec = DHD_PKTTAG_FIFO(PKTTAG(p));
fifo_id = prec << 1;
if (pkt_type == eWLFC_PKTTYPE_SUPPRESSED)
fifo_id += 1;
if (entry != NULL) {
/*
if this packet did not count against FIFO credit, it must have
taken a requested_credit from the firmware (for pspoll etc.)
*/
if ((prec != AC_COUNT) && !DHD_PKTTAG_CREDITCHECK(PKTTAG(p)))
entry->requested_credit++;
if (pkt_type == eWLFC_PKTTYPE_DELAYED) {
/* decrement sequence count */
WLFC_DECR_SEQCOUNT(entry, prec);
/* remove header first */
rc = _dhd_wlfc_pullheader(ctx, p);
if (rc != BCME_OK) {
DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
goto exit;
}
}
if (_dhd_wlfc_prec_enq_with_drop(ctx->dhdp, &entry->psq, p, fifo_id, TRUE,
WLFC_SEQCOUNT(entry, fifo_id>>1))
== FALSE) {
/* enque failed */
DHD_ERROR(("Error: %s():%d, fifo_id(%d)\n",
__FUNCTION__, __LINE__, fifo_id));
rc = BCME_ERROR;
}
} else {
DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
rc = BCME_ERROR;
}
exit:
if (rc != BCME_OK) {
ctx->stats.rollback_failed++;
_dhd_wlfc_prec_drop(ctx->dhdp, fifo_id, p, FALSE);
}
else
ctx->stats.rollback++;
return rc;
}
static bool
_dhd_wlfc_allow_fc(athost_wl_status_info_t* ctx, uint8 ifid)
{
int prec, ac_traffic = WLFC_NO_TRAFFIC;
for (prec = 0; prec < AC_COUNT; prec++) {
if (ctx->pkt_cnt_in_drv[ifid][prec] > 0) {
if (ac_traffic == WLFC_NO_TRAFFIC)
ac_traffic = prec + 1;
else if (ac_traffic != (prec + 1))
ac_traffic = WLFC_MULTI_TRAFFIC;
}
}
if (ac_traffic >= 1 && ac_traffic <= AC_COUNT) {
/* single AC (BE/BK/VI/VO) in queue */
if (ctx->allow_fc) {
return TRUE;
} else {
uint32 delta;
uint32 curr_t = OSL_SYSUPTIME();
if (ctx->fc_defer_timestamp == 0) {
/* first signle ac scenario */
ctx->fc_defer_timestamp = curr_t;
return FALSE;
}
/* single AC duration, this handles wrap around, e.g. 1 - ~0 = 2. */
delta = curr_t - ctx->fc_defer_timestamp;
if (delta >= WLFC_FC_DEFER_PERIOD_MS) {
ctx->allow_fc = TRUE;
}
}
} else {
/* multiple ACs or BCMC in queue */
ctx->allow_fc = FALSE;
ctx->fc_defer_timestamp = 0;
}
return ctx->allow_fc;
}
static void
_dhd_wlfc_flow_control_check(athost_wl_status_info_t* ctx, struct pktq* pq, uint8 if_id)
{
dhd_pub_t *dhdp;
ASSERT(ctx);
dhdp = (dhd_pub_t *)ctx->dhdp;
ASSERT(dhdp);
if (dhdp->skip_fc && dhdp->skip_fc())
return;
if ((ctx->hostif_flow_state[if_id] == OFF) && !_dhd_wlfc_allow_fc(ctx, if_id))
return;
if ((pq->len <= WLFC_FLOWCONTROL_LOWATER) && (ctx->hostif_flow_state[if_id] == ON)) {
/* start traffic */
ctx->hostif_flow_state[if_id] = OFF;
/*
WLFC_DBGMESG(("qlen:%02d, if:%02d, ->OFF, start traffic %s()\n",
pq->len, if_id, __FUNCTION__));
*/
WLFC_DBGMESG(("F"));
dhd_txflowcontrol(dhdp, if_id, OFF);
ctx->toggle_host_if = 0;
}
if ((pq->len >= WLFC_FLOWCONTROL_HIWATER) && (ctx->hostif_flow_state[if_id] == OFF)) {
/* stop traffic */
ctx->hostif_flow_state[if_id] = ON;
/*
WLFC_DBGMESG(("qlen:%02d, if:%02d, ->ON, stop traffic %s()\n",
pq->len, if_id, __FUNCTION__));
*/
WLFC_DBGMESG(("N"));
dhd_txflowcontrol(dhdp, if_id, ON);
ctx->host_ifidx = if_id;
ctx->toggle_host_if = 1;
}
return;
}
static int
_dhd_wlfc_send_signalonly_packet(athost_wl_status_info_t* ctx, wlfc_mac_descriptor_t* entry,
uint8 ta_bmp)
{
int rc = BCME_OK;
void* p = NULL;
int dummylen = ((dhd_pub_t *)ctx->dhdp)->hdrlen+ 16;
dhd_pub_t *dhdp = (dhd_pub_t *)ctx->dhdp;
if (dhdp->proptxstatus_txoff) {
rc = BCME_NORESOURCE;
return rc;
}
/* allocate a dummy packet */
p = PKTGET(ctx->osh, dummylen, TRUE);
if (p) {
PKTPULL(ctx->osh, p, dummylen);
DHD_PKTTAG_SET_H2DTAG(PKTTAG(p), 0);
_dhd_wlfc_pushheader(ctx, p, TRUE, ta_bmp, entry->mac_handle, 0, 0, FALSE);
DHD_PKTTAG_SETSIGNALONLY(PKTTAG(p), 1);
DHD_PKTTAG_WLFCPKT_SET(PKTTAG(p), 1);
#ifdef PROP_TXSTATUS_DEBUG
ctx->stats.signal_only_pkts_sent++;
#endif
#if defined(BCMPCIE)
rc = dhd_bus_txdata(dhdp->bus, p, ctx->host_ifidx);
#else
rc = dhd_bus_txdata(dhdp->bus, p);
#endif
if (rc != BCME_OK) {
_dhd_wlfc_pullheader(ctx, p);
PKTFREE(ctx->osh, p, TRUE);
}
}
else {
DHD_ERROR(("%s: couldn't allocate new %d-byte packet\n",
__FUNCTION__, dummylen));
rc = BCME_NOMEM;
}
return rc;
}
/* Return TRUE if traffic availability changed */
static bool
_dhd_wlfc_traffic_pending_check(athost_wl_status_info_t* ctx, wlfc_mac_descriptor_t* entry,
int prec)
{
bool rc = FALSE;
if (entry->state == WLFC_STATE_CLOSE) {
if ((pktq_plen(&entry->psq, (prec << 1)) == 0) &&
(pktq_plen(&entry->psq, ((prec << 1) + 1)) == 0)) {
if (entry->traffic_pending_bmp & NBITVAL(prec)) {
rc = TRUE;
entry->traffic_pending_bmp =
entry->traffic_pending_bmp & ~ NBITVAL(prec);
}
}
else {
if (!(entry->traffic_pending_bmp & NBITVAL(prec))) {
rc = TRUE;
entry->traffic_pending_bmp =
entry->traffic_pending_bmp | NBITVAL(prec);
}
}
}
if (rc) {
/* request a TIM update to firmware at the next piggyback opportunity */
if (entry->traffic_lastreported_bmp != entry->traffic_pending_bmp) {
entry->send_tim_signal = 1;
_dhd_wlfc_send_signalonly_packet(ctx, entry, entry->traffic_pending_bmp);
entry->traffic_lastreported_bmp = entry->traffic_pending_bmp;
entry->send_tim_signal = 0;
}
else {
rc = FALSE;
}
}
return rc;
}
static int
_dhd_wlfc_enque_suppressed(athost_wl_status_info_t* ctx, int prec, void* p)
{
wlfc_mac_descriptor_t* entry;
entry = _dhd_wlfc_find_table_entry(ctx, p);
if (entry == NULL) {
DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
return BCME_NOTFOUND;
}
/*
- suppressed packets go to sub_queue[2*prec + 1] AND
- delayed packets go to sub_queue[2*prec + 0] to ensure
order of delivery.
*/
if (_dhd_wlfc_prec_enq_with_drop(ctx->dhdp, &entry->psq, p, ((prec << 1) + 1), FALSE,
WLFC_SEQCOUNT(entry, prec))
== FALSE) {
ctx->stats.delayq_full_error++;
/* WLFC_DBGMESG(("Error: %s():%d\n", __FUNCTION__, __LINE__)); */
WLFC_DBGMESG(("s"));
return BCME_ERROR;
}
/* A packet has been pushed, update traffic availability bitmap, if applicable */
_dhd_wlfc_traffic_pending_check(ctx, entry, prec);
_dhd_wlfc_flow_control_check(ctx, &entry->psq, DHD_PKTTAG_IF(PKTTAG(p)));
return BCME_OK;
}
static int
_dhd_wlfc_pretx_pktprocess(athost_wl_status_info_t* ctx,
wlfc_mac_descriptor_t* entry, void* p, int header_needed, uint32* slot)
{
int rc = BCME_OK;
int hslot = WLFC_HANGER_MAXITEMS;
bool send_tim_update = FALSE;
uint32 htod = 0;
uint16 htodseq = 0;
uint8 free_ctr, flags = 0;
int gen = 0xff;
dhd_pub_t *dhdp = (dhd_pub_t *)ctx->dhdp;
*slot = hslot;
if (entry == NULL) {
entry = _dhd_wlfc_find_table_entry(ctx, p);
}
if (entry == NULL) {
DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
return BCME_ERROR;
}
if (entry->send_tim_signal) {
send_tim_update = TRUE;
entry->send_tim_signal = 0;
entry->traffic_lastreported_bmp = entry->traffic_pending_bmp;
}
if (header_needed) {
if (WLFC_GET_AFQ(dhdp->wlfc_mode)) {
hslot = (uint)(entry - &ctx->destination_entries.nodes[0]);
} else {
hslot = _dhd_wlfc_hanger_get_free_slot(ctx->hanger);
}
gen = entry->generation;
free_ctr = WLFC_SEQCOUNT(entry, DHD_PKTTAG_FIFO(PKTTAG(p)));
} else {
if (WLFC_GET_REUSESEQ(dhdp->wlfc_mode)) {
htodseq = DHD_PKTTAG_H2DSEQ(PKTTAG(p));
}
hslot = WL_TXSTATUS_GET_HSLOT(DHD_PKTTAG_H2DTAG(PKTTAG(p)));
if (WLFC_GET_REORDERSUPP(dhdp->wlfc_mode)) {
gen = entry->generation;
} else if (WLFC_GET_AFQ(dhdp->wlfc_mode)) {
gen = WL_TXSTATUS_GET_GENERATION(DHD_PKTTAG_H2DTAG(PKTTAG(p)));
} else {
_dhd_wlfc_hanger_get_genbit(ctx->hanger, p, hslot, &gen);
}
free_ctr = WL_TXSTATUS_GET_FREERUNCTR(DHD_PKTTAG_H2DTAG(PKTTAG(p)));
/* remove old header */
_dhd_wlfc_pullheader(ctx, p);
}
if (hslot >= WLFC_HANGER_MAXITEMS) {
DHD_ERROR(("Error: %s():no hanger slot available\n", __FUNCTION__));
return BCME_ERROR;
}
flags = WLFC_PKTFLAG_PKTFROMHOST;
if (!DHD_PKTTAG_CREDITCHECK(PKTTAG(p))) {
/*
Indicate that this packet is being sent in response to an
explicit request from the firmware side.
*/
flags |= WLFC_PKTFLAG_PKT_REQUESTED;
}
if (pkt_is_dhcp(ctx->osh, p)) {
flags |= WLFC_PKTFLAG_PKT_FORCELOWRATE;
}
WL_TXSTATUS_SET_FREERUNCTR(htod, free_ctr);
WL_TXSTATUS_SET_HSLOT(htod, hslot);
WL_TXSTATUS_SET_FIFO(htod, DHD_PKTTAG_FIFO(PKTTAG(p)));
WL_TXSTATUS_SET_FLAGS(htod, flags);
WL_TXSTATUS_SET_GENERATION(htod, gen);
DHD_PKTTAG_SETPKTDIR(PKTTAG(p), 1);
rc = _dhd_wlfc_pushheader(ctx, p, send_tim_update,
entry->traffic_lastreported_bmp, entry->mac_handle, htod, htodseq, FALSE);
if (rc == BCME_OK) {
DHD_PKTTAG_SET_H2DTAG(PKTTAG(p), htod);
if (!WLFC_GET_AFQ(dhdp->wlfc_mode) && header_needed) {
/*
a new header was created for this packet.
push to hanger slot and scrub q. Since bus
send succeeded, increment seq number as well.
*/
rc = _dhd_wlfc_hanger_pushpkt(ctx->hanger, p, hslot);
if (rc == BCME_OK) {
#ifdef PROP_TXSTATUS_DEBUG
((wlfc_hanger_t*)(ctx->hanger))->items[hslot].push_time =
OSL_SYSUPTIME();
#endif
} else {
DHD_ERROR(("%s() hanger_pushpkt() failed, rc: %d\n",
__FUNCTION__, rc));
}
}
if ((rc == BCME_OK) && header_needed) {
/* increment free running sequence count */
WLFC_INCR_SEQCOUNT(entry, DHD_PKTTAG_FIFO(PKTTAG(p)));
}
}
*slot = hslot;
return rc;
}
static int
_dhd_wlfc_is_destination_open(athost_wl_status_info_t* ctx,
wlfc_mac_descriptor_t* entry, int prec)
{
if (entry->interface_id >= WLFC_MAX_IFNUM) {
ASSERT(&ctx->destination_entries.other == entry);
return 1;
}
if (ctx->destination_entries.interfaces[entry->interface_id].iftype ==
WLC_E_IF_ROLE_P2P_GO) {
/* - destination interface is of type p2p GO.
For a p2pGO interface, if the destination is OPEN but the interface is
CLOSEd, do not send traffic. But if the dstn is CLOSEd while there is
destination-specific-credit left send packets. This is because the
firmware storing the destination-specific-requested packet in queue.
*/
if ((entry->state == WLFC_STATE_CLOSE) && (entry->requested_credit == 0) &&
(entry->requested_packet == 0)) {
return 0;
}
}
/* AP, p2p_go -> unicast desc entry, STA/p2p_cl -> interface desc. entry */
if (((entry->state == WLFC_STATE_CLOSE) && (entry->requested_credit == 0) &&
(entry->requested_packet == 0)) ||
(!(entry->ac_bitmap & (1 << prec)))) {
return 0;
}
return 1;
}
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)
{
dhd_pub_t *dhdp = (dhd_pub_t *)ctx->dhdp;
wlfc_mac_descriptor_t* entry;
int total_entries;
void* p = NULL;
int i;
*entry_out = NULL;
/* most cases a packet will count against FIFO credit */
*ac_credit_spent = ((prec == AC_COUNT) && !ctx->bcmc_credit_supported) ? 0 : 1;
/* search all entries, include nodes as well as interfaces */
if (only_no_credit) {
total_entries = ctx->requested_entry_count;
} else {
total_entries = ctx->active_entry_count;
}
for (i = 0; i < total_entries; i++) {
if (only_no_credit) {
entry = ctx->requested_entry[i];
} else {
entry = ctx->active_entry_head;
/* move head to ensure fair round-robin */
ctx->active_entry_head = ctx->active_entry_head->next;
}
ASSERT(entry);
if (entry->occupied && _dhd_wlfc_is_destination_open(ctx, entry, prec) &&
(entry->transit_count < WL_TXSTATUS_FREERUNCTR_MASK) &&
!(WLFC_GET_REORDERSUPP(dhdp->wlfc_mode) && entry->suppressed)) {
if (entry->state == WLFC_STATE_CLOSE) {
*ac_credit_spent = 0;
}
/* higher precedence will be picked up first,
* i.e. suppressed packets before delayed ones
*/
p = pktq_pdeq(&entry->psq, PSQ_SUP_IDX(prec));
*needs_hdr = 0;
if (p == NULL) {
if (entry->suppressed == TRUE) {
/* skip this entry */
continue;
}
/* De-Q from delay Q */
p = pktq_pdeq(&entry->psq, PSQ_DLY_IDX(prec));
*needs_hdr = 1;
}
if (p != NULL) {
/* did the packet come from suppress sub-queue? */
if (entry->requested_credit > 0) {
entry->requested_credit--;
#ifdef PROP_TXSTATUS_DEBUG
entry->dstncredit_sent_packets++;
#endif
} else if (entry->requested_packet > 0) {
entry->requested_packet--;
DHD_PKTTAG_SETONETIMEPKTRQST(PKTTAG(p));
}
*entry_out = entry;
ctx->pkt_cnt_in_q[DHD_PKTTAG_IF(PKTTAG(p))][prec]--;
ctx->pkt_cnt_per_ac[prec]--;
_dhd_wlfc_flow_control_check(ctx, &entry->psq,
DHD_PKTTAG_IF(PKTTAG(p)));
/*
A packet has been picked up, update traffic
availability bitmap, if applicable
*/
_dhd_wlfc_traffic_pending_check(ctx, entry, prec);
return p;
}
}
}
return NULL;
}
static int
_dhd_wlfc_enque_delayq(athost_wl_status_info_t* ctx, void* pktbuf, int prec)
{
wlfc_mac_descriptor_t* entry;
if (pktbuf != NULL) {
entry = _dhd_wlfc_find_table_entry(ctx, pktbuf);
if (entry == NULL) {
DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
return BCME_ERROR;
}
/*
- suppressed packets go to sub_queue[2*prec + 1] AND
- delayed packets go to sub_queue[2*prec + 0] to ensure
order of delivery.
*/
if (_dhd_wlfc_prec_enq_with_drop(ctx->dhdp, &entry->psq, pktbuf, (prec << 1),
FALSE, WLFC_SEQCOUNT(entry, prec))
== FALSE) {
WLFC_DBGMESG(("D"));
ctx->stats.delayq_full_error++;
return BCME_ERROR;
}
/*
A packet has been pushed, update traffic availability bitmap,
if applicable
*/
_dhd_wlfc_traffic_pending_check(ctx, entry, prec);
}
return BCME_OK;
}
static bool _dhd_wlfc_ifpkt_fn(void* p, void *p_ifid)
{
if (!p || !p_ifid)
return FALSE;
return (DHD_PKTTAG_WLFCPKT(PKTTAG(p))&& (*((uint8 *)p_ifid) == DHD_PKTTAG_IF(PKTTAG(p))));
}
static bool _dhd_wlfc_entrypkt_fn(void* p, void *entry)
{
if (!p || !entry)
return FALSE;
return (DHD_PKTTAG_WLFCPKT(PKTTAG(p))&& (entry == DHD_PKTTAG_ENTRY(PKTTAG(p))));
}
static void
_dhd_wlfc_return_implied_credit(athost_wl_status_info_t* wlfc, void* pkt)
{
dhd_pub_t *dhdp;
if (!wlfc || !pkt) {
return;
}
dhdp = (dhd_pub_t *)(wlfc->dhdp);
if (dhdp && (dhdp->proptxstatus_mode == WLFC_FCMODE_IMPLIED_CREDIT) &&
DHD_PKTTAG_CREDITCHECK(PKTTAG(pkt))) {
int lender, credit_returned = 0;
uint8 fifo_id = DHD_PKTTAG_FIFO(PKTTAG(pkt));
/* Note that borrower is fifo_id */
/* Return credits to highest priority lender first */
for (lender = AC_COUNT; lender >= 0; lender--) {
if (wlfc->credits_borrowed[fifo_id][lender] > 0) {
wlfc->FIFO_credit[lender]++;
wlfc->credits_borrowed[fifo_id][lender]--;
credit_returned = 1;
break;
}
}
if (!credit_returned) {
wlfc->FIFO_credit[fifo_id]++;
}
}
}
static void
_dhd_wlfc_hanger_free_pkt(athost_wl_status_info_t* wlfc, uint32 slot_id, uint8 pkt_state,
int pkt_txstatus)
{
wlfc_hanger_t* hanger;
wlfc_hanger_item_t* item;
if (!wlfc)
return;
hanger = (wlfc_hanger_t*)wlfc->hanger;
if (!hanger)
return;
if (slot_id == WLFC_HANGER_MAXITEMS)
return;
item = &hanger->items[slot_id];
item->pkt_state |= pkt_state;
if (pkt_txstatus != -1) {
item->pkt_txstatus = pkt_txstatus;
}
if (item->pkt) {
if ((item->pkt_state & WLFC_HANGER_PKT_STATE_TXCOMPLETE) &&
(item->pkt_state & (WLFC_HANGER_PKT_STATE_TXSTATUS |
WLFC_HANGER_PKT_STATE_CLEANUP))) {
void *p = NULL;
void *pkt = item->pkt;
uint8 old_state = item->state;
int ret = _dhd_wlfc_hanger_poppkt(wlfc->hanger, slot_id, &p, TRUE);
BCM_REFERENCE(ret);
BCM_REFERENCE(pkt);
ASSERT((ret == BCME_OK) && p && (pkt == p));
/* free packet */
if (!(item->pkt_state & WLFC_HANGER_PKT_STATE_TXSTATUS)) {
/* cleanup case */
wlfc_mac_descriptor_t *entry = _dhd_wlfc_find_table_entry(wlfc, p);
ASSERT(entry);
entry->transit_count--;
if (entry->suppressed &&
(--entry->suppr_transit_count == 0)) {
entry->suppressed = FALSE;
}
_dhd_wlfc_return_implied_credit(wlfc, p);
wlfc->stats.cleanup_fw_cnt++;
/* slot not freeable yet */
item->state = old_state;
}
wlfc->pkt_cnt_in_drv[DHD_PKTTAG_IF(PKTTAG(p))]
[DHD_PKTTAG_FIFO(PKTTAG(p))]--;
wlfc->stats.pktout++;
dhd_txcomplete((dhd_pub_t *)wlfc->dhdp, p, item->pkt_txstatus);
PKTFREE(wlfc->osh, p, TRUE);
}
} else {
if (item->pkt_state & WLFC_HANGER_PKT_STATE_TXSTATUS) {
/* free slot */
ASSERT(item->state != WLFC_HANGER_ITEM_STATE_FREE);
item->state = WLFC_HANGER_ITEM_STATE_FREE;
}
}
}
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)
{
int prec;
dhd_pub_t *dhdp = (dhd_pub_t *)ctx->dhdp;
ASSERT(dhdp);
/* Optimize flush, if pktq len = 0, just return.
* pktq len of 0 means pktq's prec q's are all empty.
*/
if (pq->len == 0) {
return;
}
for (prec = 0; prec < pq->num_prec; prec++) {
struct pktq_prec *q;
void *p, *prev = NULL;
q = &pq->q[prec];
p = q->head;
while (p) {
if (fn == NULL || (*fn)(p, arg)) {
bool head = (p == q->head);
if (head)
q->head = PKTLINK(p);
else
PKTSETLINK(prev, PKTLINK(p));
if (q_type == Q_TYPE_PSQ) {
if (!WLFC_GET_AFQ(dhdp->wlfc_mode) && (prec & 1)) {
_dhd_wlfc_hanger_remove_reference(ctx->hanger, p);
}
ctx->pkt_cnt_in_q[DHD_PKTTAG_IF(PKTTAG(p))][prec>>1]--;
ctx->pkt_cnt_per_ac[prec>>1]--;
ctx->stats.cleanup_psq_cnt++;
if (!(prec & 1)) {
/* pkt in delayed q, so fake push BDC header for
* dhd_tcpack_check_xmit() and dhd_txcomplete().
*/
_dhd_wlfc_pushheader(ctx, p, FALSE, 0, 0,
0, 0, TRUE);
#ifdef DHDTCPACK_SUPPRESS
if (dhd_tcpack_check_xmit(dhdp, p) == BCME_ERROR) {
DHD_ERROR(("%s %d: tcpack_suppress ERROR!!!"
" Stop using it\n",
__FUNCTION__, __LINE__));
dhd_tcpack_suppress_set(dhdp,
TCPACK_SUP_OFF);
}
#endif /* DHDTCPACK_SUPPRESS */
}
} else if (q_type == Q_TYPE_AFQ) {
wlfc_mac_descriptor_t* entry =
_dhd_wlfc_find_table_entry(ctx, p);
entry->transit_count--;
if (entry->suppressed &&
(--entry->suppr_transit_count == 0)) {
entry->suppressed = FALSE;
}
_dhd_wlfc_return_implied_credit(ctx, p);
ctx->stats.cleanup_fw_cnt++;
}
PKTSETLINK(p, NULL);
if (dir) {
ctx->pkt_cnt_in_drv[DHD_PKTTAG_IF(PKTTAG(p))][prec>>1]--;
ctx->stats.pktout++;
dhd_txcomplete(dhdp, p, FALSE);
}
PKTFREE(ctx->osh, p, dir);
q->len--;
pq->len--;
p = (head ? q->head : PKTLINK(prev));
} else {
prev = p;
p = PKTLINK(p);
}
}
if (q->head == NULL) {
ASSERT(q->len == 0);
q->tail = NULL;
}
}
if (fn == NULL)
ASSERT(pq->len == 0);
}
static void*
_dhd_wlfc_pktq_pdeq_with_fn(struct pktq *pq, int prec, f_processpkt_t fn, void *arg)
{
struct pktq_prec *q;
void *p, *prev = NULL;
ASSERT(prec >= 0 && prec < pq->num_prec);
q = &pq->q[prec];
p = q->head;
while (p) {
if (fn == NULL || (*fn)(p, arg)) {
break;
} else {
prev = p;
p = PKTLINK(p);
}
}
if (p == NULL)
return NULL;
if (prev == NULL) {
if ((q->head = PKTLINK(p)) == NULL) {
q->tail = NULL;
}
} else {
PKTSETLINK(prev, PKTLINK(p));
if (q->tail == p) {
q->tail = prev;
}
}
q->len--;
pq->len--;
PKTSETLINK(p, NULL);
return p;
}
static void
_dhd_wlfc_cleanup_txq(dhd_pub_t *dhd, f_processpkt_t fn, void *arg)
{
int prec;
void *pkt = NULL, *head = NULL, *tail = NULL;
struct pktq *txq = (struct pktq *)dhd_bus_txq(dhd->bus);
athost_wl_status_info_t* wlfc = (athost_wl_status_info_t*)dhd->wlfc_state;
wlfc_hanger_t* h = (wlfc_hanger_t*)wlfc->hanger;
wlfc_mac_descriptor_t* entry;
dhd_os_sdlock_txq(dhd);
for (prec = 0; prec < txq->num_prec; prec++) {
while ((pkt = _dhd_wlfc_pktq_pdeq_with_fn(txq, prec, fn, arg))) {
#ifdef DHDTCPACK_SUPPRESS
if (dhd_tcpack_check_xmit(dhd, pkt) == BCME_ERROR) {
DHD_ERROR(("%s %d: tcpack_suppress ERROR!!! Stop using it\n",
__FUNCTION__, __LINE__));
dhd_tcpack_suppress_set(dhd, TCPACK_SUP_OFF);
}
#endif /* DHDTCPACK_SUPPRESS */
if (!head) {
head = pkt;
}
if (tail) {
PKTSETLINK(tail, pkt);
}
tail = pkt;
}
}
dhd_os_sdunlock_txq(dhd);
while ((pkt = head)) {
head = PKTLINK(pkt);
PKTSETLINK(pkt, NULL);
entry = _dhd_wlfc_find_table_entry(wlfc, pkt);
if (!WLFC_GET_AFQ(dhd->wlfc_mode) &&
!_dhd_wlfc_hanger_remove_reference(h, pkt)) {
DHD_ERROR(("%s: can't find pkt(%p) in hanger, free it anyway\n",
__FUNCTION__, pkt));
}
entry->transit_count--;
if (entry->suppressed &&
(--entry->suppr_transit_count == 0)) {
entry->suppressed = FALSE;
}
_dhd_wlfc_return_implied_credit(wlfc, pkt);
wlfc->pkt_cnt_in_drv[DHD_PKTTAG_IF(PKTTAG(pkt))][DHD_PKTTAG_FIFO(PKTTAG(pkt))]--;
wlfc->stats.pktout++;
wlfc->stats.cleanup_txq_cnt++;
dhd_txcomplete(dhd, pkt, FALSE);
PKTFREE(wlfc->osh, pkt, TRUE);
}
}
void
_dhd_wlfc_cleanup(dhd_pub_t *dhd, f_processpkt_t fn, void *arg)
{
int i;
int total_entries;
athost_wl_status_info_t* wlfc = (athost_wl_status_info_t*)dhd->wlfc_state;
wlfc_mac_descriptor_t* table;
wlfc_hanger_t* h = (wlfc_hanger_t*)wlfc->hanger;
wlfc->stats.cleanup_txq_cnt = 0;
wlfc->stats.cleanup_psq_cnt = 0;
wlfc->stats.cleanup_fw_cnt = 0;
/*
* flush sequence shoulde be txq -> psq -> hanger/afq, hanger has to be last one
*/
/* flush bus->txq */
_dhd_wlfc_cleanup_txq(dhd, fn, arg);
/* flush psq, search all entries, include nodes as well as interfaces */
total_entries = sizeof(wlfc->destination_entries)/sizeof(wlfc_mac_descriptor_t);
table = (wlfc_mac_descriptor_t*)&wlfc->destination_entries;
for (i = 0; i < total_entries; i++) {
if (table[i].occupied) {
/* release packets held in PSQ (both delayed and suppressed) */
if (table[i].psq.len) {
WLFC_DBGMESG(("%s(): PSQ[%d].len = %d\n",
__FUNCTION__, i, table[i].psq.len));
_dhd_wlfc_pktq_flush(wlfc, &table[i].psq, TRUE,
fn, arg, Q_TYPE_PSQ);
}
/* free packets held in AFQ */
if (WLFC_GET_AFQ(dhd->wlfc_mode) && (table[i].afq.len)) {
_dhd_wlfc_pktq_flush(wlfc, &table[i].afq, TRUE,
fn, arg, Q_TYPE_AFQ);
}
if ((fn == NULL) && (&table[i] != &wlfc->destination_entries.other)) {
table[i].occupied = 0;
if (table[i].transit_count || table[i].suppr_transit_count) {
DHD_ERROR(("%s: table[%d] transit(%d), suppr_tansit(%d)\n",
__FUNCTION__, i,
table[i].transit_count,
table[i].suppr_transit_count));
}
}
}
}
/*
. flush remained pkt in hanger queue, not in bus->txq nor psq.
. the remained pkt was successfully downloaded to dongle already.
. hanger slot state cannot be set to free until receive txstatus update.
*/
if (!WLFC_GET_AFQ(dhd->wlfc_mode)) {
for (i = 0; i < h->max_items; i++) {
if ((h->items[i].state == WLFC_HANGER_ITEM_STATE_INUSE) ||
(h->items[i].state == WLFC_HANGER_ITEM_STATE_INUSE_SUPPRESSED)) {
if (fn == NULL || (*fn)(h->items[i].pkt, arg)) {
_dhd_wlfc_hanger_free_pkt(wlfc, i,
WLFC_HANGER_PKT_STATE_CLEANUP, FALSE);
}
}
}
}
return;
}
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)
{
int rc = BCME_OK;
if ((action == eWLFC_MAC_ENTRY_ACTION_ADD) || (action == eWLFC_MAC_ENTRY_ACTION_UPDATE)) {
entry->occupied = 1;
entry->state = WLFC_STATE_OPEN;
entry->requested_credit = 0;
entry->interface_id = ifid;
entry->iftype = iftype;
entry->ac_bitmap = 0xff; /* update this when handling APSD */
/* for an interface entry we may not care about the MAC address */
if (ea != NULL)
memcpy(&entry->ea[0], ea, ETHER_ADDR_LEN);
if (action == eWLFC_MAC_ENTRY_ACTION_ADD) {
dhd_pub_t *dhdp = (dhd_pub_t *)(ctx->dhdp);
pktq_init(&entry->psq, WLFC_PSQ_PREC_COUNT, WLFC_PSQ_LEN);
if (WLFC_GET_AFQ(dhdp->wlfc_mode)) {
pktq_init(&entry->afq, WLFC_AFQ_PREC_COUNT, WLFC_PSQ_LEN);
}
if (entry->next == NULL) {
/* not linked to anywhere, add to tail */
if (ctx->active_entry_head) {
entry->prev = ctx->active_entry_head->prev;
ctx->active_entry_head->prev->next = entry;
ctx->active_entry_head->prev = entry;
entry->next = ctx->active_entry_head;
} else {
ASSERT(ctx->active_entry_count == 0);
entry->prev = entry->next = entry;
ctx->active_entry_head = entry;
}
ctx->active_entry_count++;
} else {
DHD_ERROR(("%s():%d, entry(%d)\n", __FUNCTION__, __LINE__,
(int)(entry - &ctx->destination_entries.nodes[0])));
}
}
} else if (action == eWLFC_MAC_ENTRY_ACTION_DEL) {
/* When the entry is deleted, the packets that are queued in the entry must be
cleanup. The cleanup action should be before the occupied is set as 0.
*/
_dhd_wlfc_cleanup(ctx->dhdp, fn, arg);
_dhd_wlfc_flow_control_check(ctx, &entry->psq, ifid);
entry->occupied = 0;
entry->suppressed = 0;
entry->state = WLFC_STATE_CLOSE;
entry->requested_credit = 0;
entry->transit_count = 0;
entry->suppr_transit_count = 0;
memset(&entry->ea[0], 0, ETHER_ADDR_LEN);
if (entry->next) {
/* not floating, remove from Q */
if (ctx->active_entry_count <= 1) {
/* last item */
ctx->active_entry_head = NULL;
ctx->active_entry_count = 0;
} else {
entry->prev->next = entry->next;
entry->next->prev = entry->prev;
if (entry == ctx->active_entry_head) {
ctx->active_entry_head = entry->next;
}
ctx->active_entry_count--;
}
entry->next = entry->prev = NULL;
} else {
DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
}
}
return rc;
}
#ifdef LIMIT_BORROW
static int
_dhd_wlfc_borrow_credit(athost_wl_status_info_t* ctx, int highest_lender_ac, int borrower_ac,
bool bBorrowAll)
{
int lender_ac, borrow_limit = 0;
int rc = -1;
if (ctx == NULL) {
DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
return -1;
}
/* Borrow from lowest priority available AC (including BC/MC credits) */
for (lender_ac = 0; lender_ac <= highest_lender_ac; lender_ac++) {
if (!bBorrowAll) {
borrow_limit = ctx->Init_FIFO_credit[lender_ac]/WLFC_BORROW_LIMIT_RATIO;
} else {
borrow_limit = 0;
}
if (ctx->FIFO_credit[lender_ac] > borrow_limit) {
ctx->credits_borrowed[borrower_ac][lender_ac]++;
ctx->FIFO_credit[lender_ac]--;
rc = lender_ac;
break;
}
}
return rc;
}
static int _dhd_wlfc_return_credit(athost_wl_status_info_t* ctx, int lender_ac, int borrower_ac)
{
if ((ctx == NULL) || (lender_ac < 0) || (lender_ac > AC_COUNT) ||
(borrower_ac < 0) || (borrower_ac > AC_COUNT)) {
DHD_ERROR(("Error: %s():%d, ctx(%p), lender_ac(%d), borrower_ac(%d)\n",
__FUNCTION__, __LINE__, ctx, lender_ac, borrower_ac));
return BCME_BADARG;
}
ctx->credits_borrowed[borrower_ac][lender_ac]--;
ctx->FIFO_credit[lender_ac]++;
return BCME_OK;
}
#endif /* LIMIT_BORROW */
static int
_dhd_wlfc_interface_entry_update(void* state,
uint8 action, uint8 ifid, uint8 iftype, uint8* ea)
{
athost_wl_status_info_t* ctx = (athost_wl_status_info_t*)state;
wlfc_mac_descriptor_t* entry;
if (ifid >= WLFC_MAX_IFNUM)
return BCME_BADARG;
entry = &ctx->destination_entries.interfaces[ifid];
return _dhd_wlfc_mac_entry_update(ctx, entry, action, ifid, iftype, ea,
_dhd_wlfc_ifpkt_fn, &ifid);
}
static int
_dhd_wlfc_BCMCCredit_support_update(void* state)
{
athost_wl_status_info_t* ctx = (athost_wl_status_info_t*)state;
ctx->bcmc_credit_supported = TRUE;
return BCME_OK;
}
static int
_dhd_wlfc_FIFOcreditmap_update(void* state, uint8* credits)
{
athost_wl_status_info_t* ctx = (athost_wl_status_info_t*)state;
int i;
for (i = 0; i <= 4; i++) {
if (ctx->Init_FIFO_credit[i] != ctx->FIFO_credit[i]) {
DHD_ERROR(("%s: credit[i] is not returned, (%d %d)\n",
__FUNCTION__, ctx->Init_FIFO_credit[i], ctx->FIFO_credit[i]));
}
}
/* update the AC FIFO credit map */
ctx->FIFO_credit[0] += (credits[0] - ctx->Init_FIFO_credit[0]);
ctx->FIFO_credit[1] += (credits[1] - ctx->Init_FIFO_credit[1]);
ctx->FIFO_credit[2] += (credits[2] - ctx->Init_FIFO_credit[2]);
ctx->FIFO_credit[3] += (credits[3] - ctx->Init_FIFO_credit[3]);
ctx->FIFO_credit[4] += (credits[4] - ctx->Init_FIFO_credit[4]);
ctx->Init_FIFO_credit[0] = credits[0];
ctx->Init_FIFO_credit[1] = credits[1];
ctx->Init_FIFO_credit[2] = credits[2];
ctx->Init_FIFO_credit[3] = credits[3];
ctx->Init_FIFO_credit[4] = credits[4];
/* credit for ATIM FIFO is not used yet. */
ctx->Init_FIFO_credit[5] = ctx->FIFO_credit[5] = 0;
return BCME_OK;
}
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)
{
uint32 hslot;
int rc;
dhd_pub_t *dhdp = (dhd_pub_t *)(ctx->dhdp);
/*
if ac_fifo_credit_spent = 0
This packet will not count against the FIFO credit.
To ensure the txstatus corresponding to this packet
does not provide an implied credit (default behavior)
mark the packet accordingly.
if ac_fifo_credit_spent = 1
This is a normal packet and it counts against the FIFO
credit count.
*/
DHD_PKTTAG_SETCREDITCHECK(PKTTAG(commit_info->p), commit_info->ac_fifo_credit_spent);
rc = _dhd_wlfc_pretx_pktprocess(ctx, commit_info->mac_entry, commit_info->p,
commit_info->needs_hdr, &hslot);
if (rc == BCME_OK) {
rc = fcommit(commit_ctx, commit_info->p);
if (rc == BCME_OK) {
uint8 gen = WL_TXSTATUS_GET_GENERATION(
DHD_PKTTAG_H2DTAG(PKTTAG(commit_info->p)));
ctx->stats.pkt2bus++;
if (commit_info->ac_fifo_credit_spent || (ac == AC_COUNT)) {
ctx->stats.send_pkts[ac]++;
WLFC_HOST_FIFO_CREDIT_INC_SENTCTRS(ctx, ac);
}
if (gen != commit_info->mac_entry->generation) {
/* will be suppressed back by design */
if (!commit_info->mac_entry->suppressed) {
commit_info->mac_entry->suppressed = TRUE;
}
commit_info->mac_entry->suppr_transit_count++;
}
commit_info->mac_entry->transit_count++;
} else if (commit_info->needs_hdr) {
if (!WLFC_GET_AFQ(dhdp->wlfc_mode)) {
void *pout = NULL;
/* pop hanger for delayed packet */
_dhd_wlfc_hanger_poppkt(ctx->hanger, WL_TXSTATUS_GET_HSLOT(
DHD_PKTTAG_H2DTAG(PKTTAG(commit_info->p))), &pout, TRUE);
ASSERT(commit_info->p == pout);
}
}
} else {
ctx->stats.generic_error++;
}
if (rc != BCME_OK) {
/*
pretx pkt process or bus commit has failed, rollback.
- remove wl-header for a delayed packet
- save wl-header header for suppressed packets
- reset credit check flag
*/
_dhd_wlfc_rollback_packet_toq(ctx, commit_info->p, commit_info->pkt_type, hslot);
DHD_PKTTAG_SETCREDITCHECK(PKTTAG(commit_info->p), 0);
}
return rc;
}
static uint8
_dhd_wlfc_find_mac_desc_id_from_mac(dhd_pub_t *dhdp, uint8* ea)
{
wlfc_mac_descriptor_t* table =
((athost_wl_status_info_t*)dhdp->wlfc_state)->destination_entries.nodes;
uint8 table_index;
if (ea != NULL) {
for (table_index = 0; table_index < WLFC_MAC_DESC_TABLE_SIZE; table_index++) {
if ((memcmp(ea, &table[table_index].ea[0], ETHER_ADDR_LEN) == 0) &&
table[table_index].occupied)
return table_index;
}
}
return WLFC_MAC_DESC_ID_INVALID;
}
static int
_dhd_wlfc_compressed_txstatus_update(dhd_pub_t *dhd, uint8* pkt_info, uint8 len, void** p_mac)
{
uint8 status_flag;
uint32 status;
int ret = BCME_OK;
int remove_from_hanger = 1;
void* pktbuf = NULL;
uint8 fifo_id = 0, gen = 0, count = 0, hcnt;
uint16 hslot;
wlfc_mac_descriptor_t* entry = NULL;
athost_wl_status_info_t* wlfc = (athost_wl_status_info_t*)dhd->wlfc_state;
uint16 seq = 0, seq_fromfw = 0, seq_num = 0;
memcpy(&status, pkt_info, sizeof(uint32));
status_flag = WL_TXSTATUS_GET_FLAGS(status);
hcnt = WL_TXSTATUS_GET_FREERUNCTR(status);
hslot = WL_TXSTATUS_GET_HSLOT(status);
fifo_id = WL_TXSTATUS_GET_FIFO(status);
gen = WL_TXSTATUS_GET_GENERATION(status);
if (WLFC_GET_REUSESEQ(dhd->wlfc_mode)) {
memcpy(&seq, pkt_info + WLFC_CTL_VALUE_LEN_TXSTATUS, WLFC_CTL_VALUE_LEN_SEQ);
seq_fromfw = WL_SEQ_GET_FROMFW(seq);
seq_num = WL_SEQ_GET_NUM(seq);
}
wlfc->stats.txstatus_in += len;
if (status_flag == WLFC_CTL_PKTFLAG_DISCARD) {
wlfc->stats.pkt_freed += len;
}
else if (status_flag == WLFC_CTL_PKTFLAG_DISCARD_NOACK) {
wlfc->stats.pkt_freed += len;
}
else if (status_flag == WLFC_CTL_PKTFLAG_D11SUPPRESS) {
wlfc->stats.d11_suppress += len;
remove_from_hanger = 0;
}
else if (status_flag == WLFC_CTL_PKTFLAG_WLSUPPRESS) {
wlfc->stats.wl_suppress += len;
remove_from_hanger = 0;
}
else if (status_flag == WLFC_CTL_PKTFLAG_TOSSED_BYWLC) {
wlfc->stats.wlc_tossed_pkts += len;
}
if (dhd->proptxstatus_txstatus_ignore) {
if (!remove_from_hanger) {
DHD_ERROR(("suppress txstatus: %d\n", status_flag));
}
return BCME_OK;
}
while (count < len) {
if (WLFC_GET_AFQ(dhd->wlfc_mode)) {
ret = _dhd_wlfc_deque_afq(wlfc, hslot, hcnt, fifo_id, &pktbuf);
} else {
ret = _dhd_wlfc_hanger_poppkt(wlfc->hanger, hslot, &pktbuf, FALSE);
if (!pktbuf) {
_dhd_wlfc_hanger_free_pkt(wlfc, hslot,
WLFC_HANGER_PKT_STATE_TXSTATUS, -1);
goto cont;
}
}
if ((ret != BCME_OK) || !pktbuf) {
goto cont;
}
/* set fifo_id to correct value because not all FW does that */
fifo_id = DHD_PKTTAG_FIFO(PKTTAG(pktbuf));
entry = _dhd_wlfc_find_table_entry(wlfc, pktbuf);
if (!remove_from_hanger) {
/* this packet was suppressed */
if (!entry->suppressed || (entry->generation != gen)) {
if (!entry->suppressed) {
entry->suppr_transit_count = entry->transit_count;
if (p_mac) {
*p_mac = entry;
}
} else {
DHD_ERROR(("gen(%d), entry->generation(%d)\n",
gen, entry->generation));
}
entry->suppressed = TRUE;
}
entry->generation = gen;
}
#ifdef PROP_TXSTATUS_DEBUG
if (!WLFC_GET_AFQ(dhd->wlfc_mode))
{
uint32 new_t = OSL_SYSUPTIME();
uint32 old_t;
uint32 delta;
old_t = ((wlfc_hanger_t*)(wlfc->hanger))->items[hslot].push_time;
wlfc->stats.latency_sample_count++;
if (new_t > old_t)
delta = new_t - old_t;
else
delta = 0xffffffff + new_t - old_t;
wlfc->stats.total_status_latency += delta;
wlfc->stats.latency_most_recent = delta;
wlfc->stats.deltas[wlfc->stats.idx_delta++] = delta;
if (wlfc->stats.idx_delta == sizeof(wlfc->stats.deltas)/sizeof(uint32))
wlfc->stats.idx_delta = 0;
}
#endif /* PROP_TXSTATUS_DEBUG */
/* pick up the implicit credit from this packet */
if (DHD_PKTTAG_CREDITCHECK(PKTTAG(pktbuf))) {
_dhd_wlfc_return_implied_credit(wlfc, pktbuf);
} else {
/*
if this packet did not count against FIFO credit, it must have
taken a requested_credit from the destination entry (for pspoll etc.)
*/
if (!DHD_PKTTAG_ONETIMEPKTRQST(PKTTAG(pktbuf)))
entry->requested_credit++;
#ifdef PROP_TXSTATUS_DEBUG
entry->dstncredit_acks++;
#endif
}
if ((status_flag == WLFC_CTL_PKTFLAG_D11SUPPRESS) ||
(status_flag == WLFC_CTL_PKTFLAG_WLSUPPRESS)) {
/* save generation bit inside packet */
WL_TXSTATUS_SET_GENERATION(DHD_PKTTAG_H2DTAG(PKTTAG(pktbuf)), gen);
if (WLFC_GET_REUSESEQ(dhd->wlfc_mode)) {
WL_SEQ_SET_FROMDRV(DHD_PKTTAG_H2DSEQ(PKTTAG(pktbuf)), seq_fromfw);
WL_SEQ_SET_NUM(DHD_PKTTAG_H2DSEQ(PKTTAG(pktbuf)), seq_num);
}
ret = _dhd_wlfc_enque_suppressed(wlfc, fifo_id, pktbuf);
if (ret != BCME_OK) {
/* delay q is full, drop this packet */
DHD_WLFC_QMON_COMPLETE(entry);
_dhd_wlfc_prec_drop(dhd, (fifo_id << 1) + 1, pktbuf, FALSE);
} else {
if (!WLFC_GET_AFQ(dhd->wlfc_mode)) {
/* Mark suppressed to avoid a double free
during wlfc cleanup
*/
_dhd_wlfc_hanger_mark_suppressed(wlfc->hanger, hslot, gen);
}
}
} else {
DHD_WLFC_QMON_COMPLETE(entry);
if (!WLFC_GET_AFQ(dhd->wlfc_mode)) {
_dhd_wlfc_hanger_free_pkt(wlfc, hslot,
WLFC_HANGER_PKT_STATE_TXSTATUS, TRUE);
} else {
dhd_txcomplete(dhd, pktbuf, TRUE);
wlfc->pkt_cnt_in_drv[DHD_PKTTAG_IF(PKTTAG(pktbuf))]
[DHD_PKTTAG_FIFO(PKTTAG(pktbuf))]--;
wlfc->stats.pktout++;
/* free the packet */
PKTFREE(wlfc->osh, pktbuf, TRUE);
}
}
/* pkt back from firmware side */
entry->transit_count--;
if (entry->suppressed && (--entry->suppr_transit_count == 0)) {
entry->suppressed = FALSE;
}
cont:
hcnt = (hcnt + 1) & WL_TXSTATUS_FREERUNCTR_MASK;
if (!WLFC_GET_AFQ(dhd->wlfc_mode)) {
hslot = (hslot + 1) & WL_TXSTATUS_HSLOT_MASK;
}
if (WLFC_GET_REUSESEQ(dhd->wlfc_mode) && seq_fromfw) {
seq_num = (seq_num + 1) & WL_SEQ_NUM_MASK;
}
count++;
}
return BCME_OK;
}
static int
_dhd_wlfc_fifocreditback_indicate(dhd_pub_t *dhd, uint8* credits)
{
int i;
athost_wl_status_info_t* wlfc = (athost_wl_status_info_t*)dhd->wlfc_state;
for (i = 0; i < WLFC_CTL_VALUE_LEN_FIFO_CREDITBACK; i++) {
#ifdef PROP_TXSTATUS_DEBUG
wlfc->stats.fifo_credits_back[i] += credits[i];
#endif
/* update FIFO credits */
if (dhd->proptxstatus_mode == WLFC_FCMODE_EXPLICIT_CREDIT)
{
int lender; /* Note that borrower is i */
/* Return credits to highest priority lender first */
for (lender = AC_COUNT; (lender >= 0) && (credits[i] > 0); lender--) {
if (wlfc->credits_borrowed[i][lender] > 0) {
if (credits[i] >= wlfc->credits_borrowed[i][lender]) {
credits[i] -=
(uint8)wlfc->credits_borrowed[i][lender];
wlfc->FIFO_credit[lender] +=
wlfc->credits_borrowed[i][lender];
wlfc->credits_borrowed[i][lender] = 0;
}
else {
wlfc->credits_borrowed[i][lender] -= credits[i];
wlfc->FIFO_credit[lender] += credits[i];
credits[i] = 0;
}
}
}
/* If we have more credits left over, these must belong to the AC */
if (credits[i] > 0) {
wlfc->FIFO_credit[i] += credits[i];
}
if (wlfc->FIFO_credit[i] > wlfc->Init_FIFO_credit[i]) {
wlfc->FIFO_credit[i] = wlfc->Init_FIFO_credit[i];
}
}
}
return BCME_OK;
}
static void
_dhd_wlfc_suppress_txq(dhd_pub_t *dhd, f_processpkt_t fn, void *arg)
{
athost_wl_status_info_t* wlfc = (athost_wl_status_info_t*)dhd->wlfc_state;
wlfc_mac_descriptor_t* entry;
int prec;
void *pkt = NULL, *head = NULL, *tail = NULL;
struct pktq *txq = (struct pktq *)dhd_bus_txq(dhd->bus);
uint8 results[WLFC_CTL_VALUE_LEN_TXSTATUS+WLFC_CTL_VALUE_LEN_SEQ];
uint8 credits[WLFC_CTL_VALUE_LEN_FIFO_CREDITBACK] = {0};
uint32 htod = 0;
uint16 htodseq = 0;
bool bCreditUpdate = FALSE;
dhd_os_sdlock_txq(dhd);
for (prec = 0; prec < txq->num_prec; prec++) {
while ((pkt = _dhd_wlfc_pktq_pdeq_with_fn(txq, prec, fn, arg))) {
if (!head) {
head = pkt;
}
if (tail) {
PKTSETLINK(tail, pkt);
}
tail = pkt;
}
}
dhd_os_sdunlock_txq(dhd);
while ((pkt = head)) {
head = PKTLINK(pkt);
PKTSETLINK(pkt, NULL);
entry = _dhd_wlfc_find_table_entry(wlfc, pkt);
/* fake a suppression txstatus */
htod = DHD_PKTTAG_H2DTAG(PKTTAG(pkt));
WL_TXSTATUS_SET_FLAGS(htod, WLFC_CTL_PKTFLAG_WLSUPPRESS);
WL_TXSTATUS_SET_GENERATION(htod, entry->generation);
memcpy(results, &htod, WLFC_CTL_VALUE_LEN_TXSTATUS);
if (WLFC_GET_REUSESEQ(dhd->wlfc_mode)) {
htodseq = DHD_PKTTAG_H2DSEQ(PKTTAG(pkt));
if (WL_SEQ_GET_FROMDRV(htodseq)) {
WL_SEQ_SET_FROMFW(htodseq, 1);
WL_SEQ_SET_FROMDRV(htodseq, 0);
}
memcpy(results + WLFC_CTL_VALUE_LEN_TXSTATUS, &htodseq,
WLFC_CTL_VALUE_LEN_SEQ);
}
if (WLFC_GET_AFQ(dhd->wlfc_mode)) {
_dhd_wlfc_enque_afq(wlfc, pkt);
}
_dhd_wlfc_compressed_txstatus_update(dhd, results, 1, NULL);
/* fake a fifo credit back */
if (DHD_PKTTAG_CREDITCHECK(PKTTAG(pkt))) {
credits[DHD_PKTTAG_FIFO(PKTTAG(pkt))]++;
bCreditUpdate = TRUE;
}
}
if (bCreditUpdate) {
_dhd_wlfc_fifocreditback_indicate(dhd, credits);
}
}
static int
_dhd_wlfc_dbg_senum_check(dhd_pub_t *dhd, uint8 *value)
{
uint32 timestamp;
(void)dhd;
bcopy(&value[2], &timestamp, sizeof(uint32));
DHD_INFO(("RXPKT: SEQ: %d, timestamp %d\n", value[1], timestamp));
return BCME_OK;
}
static int
_dhd_wlfc_rssi_indicate(dhd_pub_t *dhd, uint8* rssi)
{
(void)dhd;
(void)rssi;
return BCME_OK;
}
static void
_dhd_wlfc_add_requested_entry(athost_wl_status_info_t* wlfc, wlfc_mac_descriptor_t* entry)
{
int i;
if (!wlfc || !entry) {
return;
}
for (i = 0; i < wlfc->requested_entry_count; i++) {
if (entry == wlfc->requested_entry[i]) {
break;
}
}
if (i == wlfc->requested_entry_count) {
/* no match entry found */
ASSERT(wlfc->requested_entry_count <= (WLFC_MAC_DESC_TABLE_SIZE-1));
wlfc->requested_entry[wlfc->requested_entry_count++] = entry;
}
}
static void
_dhd_wlfc_remove_requested_entry(athost_wl_status_info_t* wlfc, wlfc_mac_descriptor_t* entry)
{
int i;
if (!wlfc || !entry) {
return;
}
for (i = 0; i < wlfc->requested_entry_count; i++) {
if (entry == wlfc->requested_entry[i]) {
break;
}
}
if (i < wlfc->requested_entry_count) {
/* found */
ASSERT(wlfc->requested_entry_count > 0);
wlfc->requested_entry_count--;
if (i != wlfc->requested_entry_count) {
wlfc->requested_entry[i] =
wlfc->requested_entry[wlfc->requested_entry_count];
}
wlfc->requested_entry[wlfc->requested_entry_count] = NULL;
}
}
static int
_dhd_wlfc_mac_table_update(dhd_pub_t *dhd, uint8* value, uint8 type)
{
int rc;
athost_wl_status_info_t* wlfc = (athost_wl_status_info_t*)dhd->wlfc_state;
wlfc_mac_descriptor_t* table;
uint8 existing_index;
uint8 table_index;
uint8 ifid;
uint8* ea;
WLFC_DBGMESG(("%s(), mac [%02x:%02x:%02x:%02x:%02x:%02x],%s,idx:%d,id:0x%02x\n",
__FUNCTION__, value[2], value[3], value[4], value[5], value[6], value[7],
((type == WLFC_CTL_TYPE_MACDESC_ADD) ? "ADD":"DEL"),
WLFC_MAC_DESC_GET_LOOKUP_INDEX(value[0]), value[0]));
table = wlfc->destination_entries.nodes;
table_index = WLFC_MAC_DESC_GET_LOOKUP_INDEX(value[0]);
ifid = value[1];
ea = &value[2];
_dhd_wlfc_remove_requested_entry(wlfc, &table[table_index]);
if (type == WLFC_CTL_TYPE_MACDESC_ADD) {
existing_index = _dhd_wlfc_find_mac_desc_id_from_mac(dhd, &value[2]);
if ((existing_index != WLFC_MAC_DESC_ID_INVALID) &&
(existing_index != table_index) && table[existing_index].occupied) {
/*
there is an existing different entry, free the old one
and move it to new index if necessary.
*/
rc = _dhd_wlfc_mac_entry_update(wlfc, &table[existing_index],
eWLFC_MAC_ENTRY_ACTION_DEL, table[existing_index].interface_id,
table[existing_index].iftype, NULL, _dhd_wlfc_entrypkt_fn,
&table[existing_index]);
}
if (!table[table_index].occupied) {
/* this new MAC entry does not exist, create one */
table[table_index].mac_handle = value[0];
rc = _dhd_wlfc_mac_entry_update(wlfc, &table[table_index],
eWLFC_MAC_ENTRY_ACTION_ADD, ifid,
wlfc->destination_entries.interfaces[ifid].iftype,
ea, NULL, NULL);
} else {
/* the space should have been empty, but it's not */
wlfc->stats.mac_update_failed++;
}
}
if (type == WLFC_CTL_TYPE_MACDESC_DEL) {
if (table[table_index].occupied) {
rc = _dhd_wlfc_mac_entry_update(wlfc, &table[table_index],
eWLFC_MAC_ENTRY_ACTION_DEL, ifid,
wlfc->destination_entries.interfaces[ifid].iftype,
ea, _dhd_wlfc_entrypkt_fn, &table[table_index]);
} else {
/* the space should have been occupied, but it's not */
wlfc->stats.mac_update_failed++;
}
}
BCM_REFERENCE(rc);
return BCME_OK;
}
static int
_dhd_wlfc_psmode_update(dhd_pub_t *dhd, uint8* value, uint8 type)
{
/* Handle PS on/off indication */
athost_wl_status_info_t* wlfc = (athost_wl_status_info_t*)dhd->wlfc_state;
wlfc_mac_descriptor_t* table;
wlfc_mac_descriptor_t* desc;
uint8 mac_handle = value[0];
int i;
table = wlfc->destination_entries.nodes;
desc = &table[WLFC_MAC_DESC_GET_LOOKUP_INDEX(mac_handle)];
if (desc->occupied) {
if (type == WLFC_CTL_TYPE_MAC_OPEN) {
desc->state = WLFC_STATE_OPEN;
desc->ac_bitmap = 0xff;
DHD_WLFC_CTRINC_MAC_OPEN(desc);
desc->requested_credit = 0;
desc->requested_packet = 0;
_dhd_wlfc_remove_requested_entry(wlfc, desc);
}
else {
desc->state = WLFC_STATE_CLOSE;
DHD_WLFC_CTRINC_MAC_CLOSE(desc);
/*
Indicate to firmware if there is any traffic pending.
*/
for (i = 0; i < AC_COUNT; i++) {
_dhd_wlfc_traffic_pending_check(wlfc, desc, i);
}
}
}
else {
wlfc->stats.psmode_update_failed++;
}
return BCME_OK;
}
static int
_dhd_wlfc_interface_update(dhd_pub_t *dhd, uint8* value, uint8 type)
{
/* Handle PS on/off indication */
athost_wl_status_info_t* wlfc = (athost_wl_status_info_t*)dhd->wlfc_state;
wlfc_mac_descriptor_t* table;
uint8 if_id = value[0];
if (if_id < WLFC_MAX_IFNUM) {
table = wlfc->destination_entries.interfaces;
if (table[if_id].occupied) {
if (type == WLFC_CTL_TYPE_INTERFACE_OPEN) {
table[if_id].state = WLFC_STATE_OPEN;
/* WLFC_DBGMESG(("INTERFACE[%d] OPEN\n", if_id)); */
}
else {
table[if_id].state = WLFC_STATE_CLOSE;
/* WLFC_DBGMESG(("INTERFACE[%d] CLOSE\n", if_id)); */
}
return BCME_OK;
}
}
wlfc->stats.interface_update_failed++;
return BCME_OK;
}
static int
_dhd_wlfc_credit_request(dhd_pub_t *dhd, uint8* value)
{
athost_wl_status_info_t* wlfc = (athost_wl_status_info_t*)dhd->wlfc_state;
wlfc_mac_descriptor_t* table;
wlfc_mac_descriptor_t* desc;
uint8 mac_handle;
uint8 credit;
table = wlfc->destination_entries.nodes;
mac_handle = value[1];
credit = value[0];
desc = &table[WLFC_MAC_DESC_GET_LOOKUP_INDEX(mac_handle)];
if (desc->occupied) {
desc->requested_credit = credit;
desc->ac_bitmap = value[2] & (~(1<<AC_COUNT));
_dhd_wlfc_add_requested_entry(wlfc, desc);
}
else {
wlfc->stats.credit_request_failed++;
}
return BCME_OK;
}
static int
_dhd_wlfc_packet_request(dhd_pub_t *dhd, uint8* value)
{
athost_wl_status_info_t* wlfc = (athost_wl_status_info_t*)dhd->wlfc_state;
wlfc_mac_descriptor_t* table;
wlfc_mac_descriptor_t* desc;
uint8 mac_handle;
uint8 packet_count;
table = wlfc->destination_entries.nodes;
mac_handle = value[1];
packet_count = value[0];
desc = &table[WLFC_MAC_DESC_GET_LOOKUP_INDEX(mac_handle)];
if (desc->occupied) {
desc->requested_packet = packet_count;
desc->ac_bitmap = value[2] & (~(1<<AC_COUNT));
_dhd_wlfc_add_requested_entry(wlfc, desc);
}
else {
wlfc->stats.packet_request_failed++;
}
return BCME_OK;
}
static void
_dhd_wlfc_reorderinfo_indicate(uint8 *val, uint8 len, uchar *info_buf, uint *info_len)
{
if (info_len) {
if (info_buf) {
bcopy(val, info_buf, len);
*info_len = len;
}
else
*info_len = 0;
}
}
/*
* public functions
*/
bool dhd_wlfc_is_supported(dhd_pub_t *dhd)
{
bool rc = TRUE;
if (dhd == NULL) {
DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
return FALSE;
}
dhd_os_wlfc_block(dhd);
if (!dhd->wlfc_state || (dhd->proptxstatus_mode == WLFC_FCMODE_NONE)) {
rc = FALSE;
}
dhd_os_wlfc_unblock(dhd);
return rc;
}
int dhd_wlfc_enable(dhd_pub_t *dhd)
{
int i, rc = BCME_OK;
athost_wl_status_info_t* wlfc;
if (dhd == NULL) {
DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
return BCME_BADARG;
}
dhd_os_wlfc_block(dhd);
if (!dhd->wlfc_enabled || dhd->wlfc_state) {
rc = BCME_OK;
goto exit;
}
/* allocate space to track txstatus propagated from firmware */
#ifdef WLFC_STATE_PREALLOC
if (!dhd->wlfc_state)
#endif
dhd->wlfc_state = MALLOC(dhd->osh, sizeof(athost_wl_status_info_t));
if (dhd->wlfc_state == NULL) {
rc = BCME_NOMEM;
goto exit;
}
/* initialize state space */
wlfc = (athost_wl_status_info_t*)dhd->wlfc_state;
memset(wlfc, 0, sizeof(athost_wl_status_info_t));
/* remember osh & dhdp */
wlfc->osh = dhd->osh;
wlfc->dhdp = dhd;
if (!WLFC_GET_AFQ(dhd->wlfc_mode)) {
wlfc->hanger = _dhd_wlfc_hanger_create(dhd->osh, WLFC_HANGER_MAXITEMS);
if (wlfc->hanger == NULL) {
#ifndef WLFC_STATE_PREALLOC
MFREE(dhd->osh, dhd->wlfc_state, sizeof(athost_wl_status_info_t));
dhd->wlfc_state = NULL;
#endif
rc = BCME_NOMEM;
goto exit;
}
}
dhd->proptxstatus_mode = WLFC_FCMODE_EXPLICIT_CREDIT;
/* default to check rx pkt */
if (dhd->op_mode & DHD_FLAG_IBSS_MODE) {
dhd->wlfc_rxpkt_chk = FALSE;
} else {
dhd->wlfc_rxpkt_chk = TRUE;
}
/* initialize all interfaces to accept traffic */
for (i = 0; i < WLFC_MAX_IFNUM; i++) {
wlfc->hostif_flow_state[i] = OFF;
}
_dhd_wlfc_mac_entry_update(wlfc, &wlfc->destination_entries.other,
eWLFC_MAC_ENTRY_ACTION_ADD, 0xff, 0, NULL, NULL, NULL);
wlfc->allow_credit_borrow = 0;
wlfc->single_ac = 0;
wlfc->single_ac_timestamp = 0;
exit:
dhd_os_wlfc_unblock(dhd);
return rc;
}
#ifdef SUPPORT_P2P_GO_PS
int
dhd_wlfc_suspend(dhd_pub_t *dhd)
{
uint32 iovbuf[4]; /* Room for "tlv" + '\0' + parameter */
uint32 tlv = 0;
DHD_TRACE(("%s: masking wlfc events\n", __FUNCTION__));
if (!dhd->wlfc_enabled)
return -1;
bcm_mkiovar("tlv", NULL, 0, (char*)iovbuf, sizeof(iovbuf));
if (dhd_wl_ioctl_cmd(dhd, WLC_GET_VAR, iovbuf, sizeof(iovbuf), FALSE, 0) < 0) {
DHD_ERROR(("%s: failed to get bdcv2 tlv signaling\n", __FUNCTION__));
return -1;
}
tlv = iovbuf[0];
if ((tlv & (WLFC_FLAGS_RSSI_SIGNALS | WLFC_FLAGS_XONXOFF_SIGNALS)) == 0)
return 0;
tlv &= ~(WLFC_FLAGS_RSSI_SIGNALS | WLFC_FLAGS_XONXOFF_SIGNALS);
bcm_mkiovar("tlv", (char *)&tlv, 4, (char*)iovbuf, sizeof(iovbuf));
if (dhd_wl_ioctl_cmd(dhd, WLC_SET_VAR, iovbuf, sizeof(iovbuf), TRUE, 0) < 0) {
DHD_ERROR(("%s: failed to set bdcv2 tlv signaling to 0x%x\n",
__FUNCTION__, tlv));
return -1;
}
return 0;
}
int
dhd_wlfc_resume(dhd_pub_t *dhd)
{
uint32 iovbuf[4]; /* Room for "tlv" + '\0' + parameter */
uint32 tlv = 0;
DHD_TRACE(("%s: unmasking wlfc events\n", __FUNCTION__));
if (!dhd->wlfc_enabled)
return -1;
bcm_mkiovar("tlv", NULL, 0, (char*)iovbuf, sizeof(iovbuf));
if (dhd_wl_ioctl_cmd(dhd, WLC_GET_VAR, iovbuf, sizeof(iovbuf), FALSE, 0) < 0) {
DHD_ERROR(("%s: failed to get bdcv2 tlv signaling\n", __FUNCTION__));
return -1;
}
tlv = iovbuf[0];
if ((tlv & (WLFC_FLAGS_RSSI_SIGNALS | WLFC_FLAGS_XONXOFF_SIGNALS)) ==
(WLFC_FLAGS_RSSI_SIGNALS | WLFC_FLAGS_XONXOFF_SIGNALS))
return 0;
tlv |= (WLFC_FLAGS_RSSI_SIGNALS | WLFC_FLAGS_XONXOFF_SIGNALS);
bcm_mkiovar("tlv", (char *)&tlv, 4, (char*)iovbuf, sizeof(iovbuf));
if (dhd_wl_ioctl_cmd(dhd, WLC_SET_VAR, (char*)iovbuf, sizeof(iovbuf), TRUE, 0) < 0) {
DHD_ERROR(("%s: failed to set bdcv2 tlv signaling to 0x%x\n",
__FUNCTION__, tlv));
return -1;
}
return 0;
}
#endif /* SUPPORT_P2P_GO_PS */
int
dhd_wlfc_parse_header_info(dhd_pub_t *dhd, void* pktbuf, int tlv_hdr_len, uchar *reorder_info_buf,
uint *reorder_info_len)
{
uint8 type, len;
uint8* value;
uint8* tmpbuf;
uint16 remainder = (uint16)tlv_hdr_len;
uint16 processed = 0;
athost_wl_status_info_t* wlfc = NULL;
void* entry;
if ((dhd == NULL) || (pktbuf == NULL)) {
DHD_ERROR(("Error: %s():%d\n", __FUNCTION__, __LINE__));
return BCME_BADARG;
}
dhd_os_wlfc_block(dhd);
if (dhd->proptxstatus_mode != WLFC_ONLY_AMPDU_HOSTREORDER) {
if (!dhd->wlfc_state || (dhd->proptxstatus_mode == WLFC_FCMODE_NONE)) {
dhd_os_wlfc_unblock(dhd);
return WLFC_UNSUPPORTED;
}
wlfc = (athost_wl_status_info_t*)dhd->wlfc_state;
}
tmpbuf = (uint8*)PKTDATA(dhd->osh, pktbuf);
if (remainder) {