/* * DHD Protocol Module for CDC and BDC. * * Copyright (C) 1999-2012, 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_cdc.c 368762 2012-11-14 21:59:17Z $ * * BDC is like CDC, except it includes a header for data packets to convey * packet priority over the bus, and flags (e.g. to indicate checksum status * for dongle offload.) */ #include #include #include #include #include #include #include #include #include #include #ifdef PROP_TXSTATUS #include #include #endif #define RETRIES 2 /* # of retries to retrieve matching ioctl response */ #define BUS_HEADER_LEN (24+DHD_SDALIGN) /* Must be at least SDPCM_RESERVE * defined in dhd_sdio.c (amount of header tha might be added) * plus any space that might be needed for alignment padding. */ #define ROUND_UP_MARGIN 2048 /* Biggest SDIO block size possible for * round off at the end of buffer */ #define BUS_RETRIES 1 /* # of retries before aborting a bus tx operation */ #ifdef PROP_TXSTATUS typedef struct dhd_wlfc_commit_info { uint8 needs_hdr; uint8 ac_fifo_credit_spent; ewlfc_packet_state_t pkt_type; wlfc_mac_descriptor_t* mac_entry; void* p; } dhd_wlfc_commit_info_t; #endif /* PROP_TXSTATUS */ typedef struct dhd_prot { uint16 reqid; uint8 pending; uint32 lastcmd; uint8 bus_header[BUS_HEADER_LEN]; cdc_ioctl_t msg; unsigned char buf[WLC_IOCTL_MAXLEN + ROUND_UP_MARGIN]; } dhd_prot_t; static int dhdcdc_msg(dhd_pub_t *dhd) { int err = 0; dhd_prot_t *prot = dhd->prot; int len = ltoh32(prot->msg.len) + sizeof(cdc_ioctl_t); DHD_TRACE(("%s: Enter\n", __FUNCTION__)); DHD_OS_WAKE_LOCK(dhd); /* NOTE : cdc->msg.len holds the desired length of the buffer to be * returned. Only up to CDC_MAX_MSG_SIZE of this buffer area * is actually sent to the dongle */ if (len > CDC_MAX_MSG_SIZE) len = CDC_MAX_MSG_SIZE; /* Send request */ err = dhd_bus_txctl(dhd->bus, (uchar*)&prot->msg, len); DHD_OS_WAKE_UNLOCK(dhd); return err; } static int dhdcdc_cmplt(dhd_pub_t *dhd, uint32 id, uint32 len) { int ret; int cdc_len = len + sizeof(cdc_ioctl_t); dhd_prot_t *prot = dhd->prot; DHD_TRACE(("%s: Enter\n", __FUNCTION__)); do { ret = dhd_bus_rxctl(dhd->bus, (uchar*)&prot->msg, cdc_len); if (ret < 0) break; } while (CDC_IOC_ID(ltoh32(prot->msg.flags)) != id); return ret; } static int dhdcdc_query_ioctl(dhd_pub_t *dhd, int ifidx, uint cmd, void *buf, uint len, uint8 action) { dhd_prot_t *prot = dhd->prot; cdc_ioctl_t *msg = &prot->msg; void *info; int ret = 0, retries = 0; uint32 id, flags = 0; DHD_TRACE(("%s: Enter\n", __FUNCTION__)); DHD_CTL(("%s: cmd %d len %d\n", __FUNCTION__, cmd, len)); /* Respond "bcmerror" and "bcmerrorstr" with local cache */ if (cmd == WLC_GET_VAR && buf) { if (!strcmp((char *)buf, "bcmerrorstr")) { strncpy((char *)buf, bcmerrorstr(dhd->dongle_error), BCME_STRLEN); goto done; } else if (!strcmp((char *)buf, "bcmerror")) { *(int *)buf = dhd->dongle_error; goto done; } } memset(msg, 0, sizeof(cdc_ioctl_t)); msg->cmd = htol32(cmd); msg->len = htol32(len); msg->flags = (++prot->reqid << CDCF_IOC_ID_SHIFT); CDC_SET_IF_IDX(msg, ifidx); /* add additional action bits */ action &= WL_IOCTL_ACTION_MASK; msg->flags |= (action << CDCF_IOC_ACTION_SHIFT); msg->flags = htol32(msg->flags); if (buf) memcpy(prot->buf, buf, len); if ((ret = dhdcdc_msg(dhd)) < 0) { if (!dhd->hang_was_sent) DHD_ERROR(("dhdcdc_query_ioctl: dhdcdc_msg failed w/status %d\n", ret)); goto done; } retry: /* wait for interrupt and get first fragment */ if ((ret = dhdcdc_cmplt(dhd, prot->reqid, len)) < 0) goto done; flags = ltoh32(msg->flags); id = (flags & CDCF_IOC_ID_MASK) >> CDCF_IOC_ID_SHIFT; if ((id < prot->reqid) && (++retries < RETRIES)) goto retry; if (id != prot->reqid) { DHD_ERROR(("%s: %s: unexpected request id %d (expected %d)\n", dhd_ifname(dhd, ifidx), __FUNCTION__, id, prot->reqid)); ret = -EINVAL; goto done; } /* Check info buffer */ info = (void*)&msg[1]; /* Copy info buffer */ if (buf) { if (ret < (int)len) len = ret; memcpy(buf, info, len); } /* Check the ERROR flag */ if (flags & CDCF_IOC_ERROR) { ret = ltoh32(msg->status); /* Cache error from dongle */ dhd->dongle_error = ret; } done: return ret; } static int dhdcdc_set_ioctl(dhd_pub_t *dhd, int ifidx, uint cmd, void *buf, uint len, uint8 action) { dhd_prot_t *prot = dhd->prot; cdc_ioctl_t *msg = &prot->msg; int ret = 0; uint32 flags, id; DHD_TRACE(("%s: Enter\n", __FUNCTION__)); DHD_CTL(("%s: cmd %d len %d\n", __FUNCTION__, cmd, len)); if (dhd->busstate == DHD_BUS_DOWN) { DHD_ERROR(("%s : bus is down. we have nothing to do\n", __FUNCTION__)); return -EIO; } /* don't talk to the dongle if fw is about to be reloaded */ if (dhd->hang_was_sent) { DHD_ERROR(("%s: HANG was sent up earlier. Not talking to the chip\n", __FUNCTION__)); return -EIO; } memset(msg, 0, sizeof(cdc_ioctl_t)); msg->cmd = htol32(cmd); msg->len = htol32(len); msg->flags = (++prot->reqid << CDCF_IOC_ID_SHIFT); CDC_SET_IF_IDX(msg, ifidx); /* add additional action bits */ action &= WL_IOCTL_ACTION_MASK; msg->flags |= (action << CDCF_IOC_ACTION_SHIFT) | CDCF_IOC_SET; msg->flags = htol32(msg->flags); if (buf) memcpy(prot->buf, buf, len); if ((ret = dhdcdc_msg(dhd)) < 0) { DHD_ERROR(("%s: dhdcdc_msg failed w/status %d\n", __FUNCTION__, ret)); goto done; } if ((ret = dhdcdc_cmplt(dhd, prot->reqid, len)) < 0) goto done; flags = ltoh32(msg->flags); id = (flags & CDCF_IOC_ID_MASK) >> CDCF_IOC_ID_SHIFT; if (id != prot->reqid) { DHD_ERROR(("%s: %s: unexpected request id %d (expected %d)\n", dhd_ifname(dhd, ifidx), __FUNCTION__, id, prot->reqid)); ret = -EINVAL; goto done; } /* Check the ERROR flag */ if (flags & CDCF_IOC_ERROR) { ret = ltoh32(msg->status); /* Cache error from dongle */ dhd->dongle_error = ret; } done: return ret; } int dhd_prot_ioctl(dhd_pub_t *dhd, int ifidx, wl_ioctl_t * ioc, void * buf, int len) { dhd_prot_t *prot = dhd->prot; int ret = -1; uint8 action; #if defined(NDIS630) bool acquired = FALSE; #endif static int error_cnt = 0; if ((dhd->busstate == DHD_BUS_DOWN) || dhd->hang_was_sent) { DHD_ERROR(("%s : bus is down. we have nothing to do\n", __FUNCTION__)); goto done; } #if defined(NDIS630) if (dhd_os_proto_block(dhd)) { acquired = TRUE; } else { /* attempt to acquire protocol mutex timed out. */ ret = -1; return ret; } #endif /* NDIS630 */ DHD_TRACE(("%s: Enter\n", __FUNCTION__)); ASSERT(len <= WLC_IOCTL_MAXLEN); if (len > WLC_IOCTL_MAXLEN) goto done; if (prot->pending == TRUE) { DHD_ERROR(("CDC packet is pending!!!! cmd=0x%x (%lu) lastcmd=0x%x (%lu)\n", ioc->cmd, (unsigned long)ioc->cmd, prot->lastcmd, (unsigned long)prot->lastcmd)); if ((ioc->cmd == WLC_SET_VAR) || (ioc->cmd == WLC_GET_VAR)) { DHD_TRACE(("iovar cmd=%s\n", (char*)buf)); } goto done; } prot->pending = TRUE; prot->lastcmd = ioc->cmd; action = ioc->set; if (action & WL_IOCTL_ACTION_SET) ret = dhdcdc_set_ioctl(dhd, ifidx, ioc->cmd, buf, len, action); else { ret = dhdcdc_query_ioctl(dhd, ifidx, ioc->cmd, buf, len, action); if (ret > 0) ioc->used = ret - sizeof(cdc_ioctl_t); } // terence 20130805: send hang event to wpa_supplicant if (ret == -EIO) { error_cnt++; if (error_cnt > 2) ret = -ETIMEDOUT; } else error_cnt = 0; /* Too many programs assume ioctl() returns 0 on success */ if (ret >= 0) ret = 0; else { cdc_ioctl_t *msg = &prot->msg; ioc->needed = ltoh32(msg->len); /* len == needed when set/query fails from dongle */ } /* Intercept the wme_dp ioctl here */ if ((!ret) && (ioc->cmd == WLC_SET_VAR) && (!strcmp(buf, "wme_dp"))) { int slen, val = 0; slen = strlen("wme_dp") + 1; if (len >= (int)(slen + sizeof(int))) bcopy(((char *)buf + slen), &val, sizeof(int)); dhd->wme_dp = (uint8) ltoh32(val); } prot->pending = FALSE; done: #if defined(NDIS630) if (acquired) dhd_os_proto_unblock(dhd); #endif return ret; } int dhd_prot_iovar_op(dhd_pub_t *dhdp, const char *name, void *params, int plen, void *arg, int len, bool set) { return BCME_UNSUPPORTED; } #ifdef PROP_TXSTATUS void dhd_wlfc_dump(dhd_pub_t *dhdp, struct bcmstrbuf *strbuf) { int i; uint8* ea; athost_wl_status_info_t* wlfc = (athost_wl_status_info_t*) dhdp->wlfc_state; wlfc_hanger_t* h; wlfc_mac_descriptor_t* mac_table; wlfc_mac_descriptor_t* interfaces; char* iftypes[] = {"STA", "AP", "WDS", "p2pGO", "p2pCL"}; if (wlfc == NULL) { bcm_bprintf(strbuf, "wlfc not initialized yet\n"); return; } h = (wlfc_hanger_t*)wlfc->hanger; if (h == NULL) { bcm_bprintf(strbuf, "wlfc-hanger not initialized yet\n"); } mac_table = wlfc->destination_entries.nodes; interfaces = wlfc->destination_entries.interfaces; bcm_bprintf(strbuf, "---- wlfc stats ----\n"); if (h) { bcm_bprintf(strbuf, "wlfc hanger (pushed,popped,f_push," "f_pop,f_slot, pending) = (%d,%d,%d,%d,%d,%d)\n", h->pushed, h->popped, h->failed_to_push, h->failed_to_pop, h->failed_slotfind, (h->pushed - h->popped)); } bcm_bprintf(strbuf, "wlfc fail(tlv,credit_rqst,mac_update,psmode_update), " "(dq_full,sendq_full, rollback_fail) = (%d,%d,%d,%d), (%d,%d,%d)\n", wlfc->stats.tlv_parse_failed, wlfc->stats.credit_request_failed, wlfc->stats.mac_update_failed, wlfc->stats.psmode_update_failed, wlfc->stats.delayq_full_error, wlfc->stats.sendq_full_error, wlfc->stats.rollback_failed); bcm_bprintf(strbuf, "SENDQ (len,credit,sent) " "(AC0[%d,%d,%d],AC1[%d,%d,%d],AC2[%d,%d,%d],AC3[%d,%d,%d],BC_MC[%d,%d,%d])\n", wlfc->SENDQ.q[0].len, wlfc->FIFO_credit[0], wlfc->stats.sendq_pkts[0], wlfc->SENDQ.q[1].len, wlfc->FIFO_credit[1], wlfc->stats.sendq_pkts[1], wlfc->SENDQ.q[2].len, wlfc->FIFO_credit[2], wlfc->stats.sendq_pkts[2], wlfc->SENDQ.q[3].len, wlfc->FIFO_credit[3], wlfc->stats.sendq_pkts[3], wlfc->SENDQ.q[4].len, wlfc->FIFO_credit[4], wlfc->stats.sendq_pkts[4]); #ifdef PROP_TXSTATUS_DEBUG bcm_bprintf(strbuf, "SENDQ dropped: AC[0-3]:(%d,%d,%d,%d), (bcmc,atim):(%d,%d)\n", wlfc->stats.dropped_qfull[0], wlfc->stats.dropped_qfull[1], wlfc->stats.dropped_qfull[2], wlfc->stats.dropped_qfull[3], wlfc->stats.dropped_qfull[4], wlfc->stats.dropped_qfull[5]); #endif bcm_bprintf(strbuf, "\n"); for (i = 0; i < WLFC_MAX_IFNUM; i++) { if (interfaces[i].occupied) { char* iftype_desc; if (interfaces[i].iftype > WLC_E_IF_ROLE_P2P_CLIENT) iftype_desc = "hostif_flow_state[i] == OFF) ? " OFF":" ON")); bcm_bprintf(strbuf, "INTERFACE[%d].DELAYQ(len,state,credit)" "= (%d,%s,%d)\n", i, interfaces[i].psq.len, ((interfaces[i].state == WLFC_STATE_OPEN) ? " OPEN":"CLOSE"), interfaces[i].requested_credit); bcm_bprintf(strbuf, "INTERFACE[%d].DELAYQ" "(sup,ac0),(sup,ac1),(sup,ac2),(sup,ac3) = " "(%d,%d),(%d,%d),(%d,%d),(%d,%d)\n", i, interfaces[i].psq.q[0].len, interfaces[i].psq.q[1].len, interfaces[i].psq.q[2].len, interfaces[i].psq.q[3].len, interfaces[i].psq.q[4].len, interfaces[i].psq.q[5].len, interfaces[i].psq.q[6].len, interfaces[i].psq.q[7].len); } } bcm_bprintf(strbuf, "\n"); for (i = 0; i < WLFC_MAC_DESC_TABLE_SIZE; i++) { if (mac_table[i].occupied) { ea = mac_table[i].ea; bcm_bprintf(strbuf, "MAC_table[%d].ea = " "[%02x:%02x:%02x:%02x:%02x:%02x], if:%d \n", i, ea[0], ea[1], ea[2], ea[3], ea[4], ea[5], mac_table[i].interface_id); bcm_bprintf(strbuf, "MAC_table[%d].DELAYQ(len,state,credit)" "= (%d,%s,%d)\n", i, mac_table[i].psq.len, ((mac_table[i].state == WLFC_STATE_OPEN) ? " OPEN":"CLOSE"), mac_table[i].requested_credit); #ifdef PROP_TXSTATUS_DEBUG bcm_bprintf(strbuf, "MAC_table[%d]: (opened, closed) = (%d, %d)\n", i, mac_table[i].opened_ct, mac_table[i].closed_ct); #endif bcm_bprintf(strbuf, "MAC_table[%d].DELAYQ" "(sup,ac0),(sup,ac1),(sup,ac2),(sup,ac3) = " "(%d,%d),(%d,%d),(%d,%d),(%d,%d)\n", i, mac_table[i].psq.q[0].len, mac_table[i].psq.q[1].len, mac_table[i].psq.q[2].len, mac_table[i].psq.q[3].len, mac_table[i].psq.q[4].len, mac_table[i].psq.q[5].len, mac_table[i].psq.q[6].len, mac_table[i].psq.q[7].len); } } #ifdef PROP_TXSTATUS_DEBUG { int avg; int moving_avg = 0; int moving_samples; if (wlfc->stats.latency_sample_count) { moving_samples = sizeof(wlfc->stats.deltas)/sizeof(uint32); for (i = 0; i < moving_samples; i++) moving_avg += wlfc->stats.deltas[i]; moving_avg /= moving_samples; avg = (100 * wlfc->stats.total_status_latency) / wlfc->stats.latency_sample_count; bcm_bprintf(strbuf, "txstatus latency (average, last, moving[%d]) = " "(%d.%d, %03d, %03d)\n", moving_samples, avg/100, (avg - (avg/100)*100), wlfc->stats.latency_most_recent, moving_avg); } } bcm_bprintf(strbuf, "wlfc- fifo[0-5] credit stats: sent = (%d,%d,%d,%d,%d,%d), " "back = (%d,%d,%d,%d,%d,%d)\n", wlfc->stats.fifo_credits_sent[0], wlfc->stats.fifo_credits_sent[1], wlfc->stats.fifo_credits_sent[2], wlfc->stats.fifo_credits_sent[3], wlfc->stats.fifo_credits_sent[4], wlfc->stats.fifo_credits_sent[5], wlfc->stats.fifo_credits_back[0], wlfc->stats.fifo_credits_back[1], wlfc->stats.fifo_credits_back[2], wlfc->stats.fifo_credits_back[3], wlfc->stats.fifo_credits_back[4], wlfc->stats.fifo_credits_back[5]); { uint32 fifo_cr_sent = 0; uint32 fifo_cr_acked = 0; uint32 request_cr_sent = 0; uint32 request_cr_ack = 0; uint32 bc_mc_cr_ack = 0; for (i = 0; i < sizeof(wlfc->stats.fifo_credits_sent)/sizeof(uint32); i++) { fifo_cr_sent += wlfc->stats.fifo_credits_sent[i]; } for (i = 0; i < sizeof(wlfc->stats.fifo_credits_back)/sizeof(uint32); i++) { fifo_cr_acked += wlfc->stats.fifo_credits_back[i]; } for (i = 0; i < WLFC_MAC_DESC_TABLE_SIZE; i++) { if (wlfc->destination_entries.nodes[i].occupied) { request_cr_sent += wlfc->destination_entries.nodes[i].dstncredit_sent_packets; } } for (i = 0; i < WLFC_MAX_IFNUM; i++) { if (wlfc->destination_entries.interfaces[i].occupied) { request_cr_sent += wlfc->destination_entries.interfaces[i].dstncredit_sent_packets; } } for (i = 0; i < WLFC_MAC_DESC_TABLE_SIZE; i++) { if (wlfc->destination_entries.nodes[i].occupied) { request_cr_ack += wlfc->destination_entries.nodes[i].dstncredit_acks; } } for (i = 0; i < WLFC_MAX_IFNUM; i++) { if (wlfc->destination_entries.interfaces[i].occupied) { request_cr_ack += wlfc->destination_entries.interfaces[i].dstncredit_acks; } } bcm_bprintf(strbuf, "wlfc- (sent, status) => pq(%d,%d), vq(%d,%d)," "other:%d, bc_mc:%d, signal-only, (sent,freed): (%d,%d)", fifo_cr_sent, fifo_cr_acked, request_cr_sent, request_cr_ack, wlfc->destination_entries.other.dstncredit_acks, bc_mc_cr_ack, wlfc->stats.signal_only_pkts_sent, wlfc->stats.signal_only_pkts_freed); } #endif /* PROP_TXSTATUS_DEBUG */ bcm_bprintf(strbuf, "\n"); bcm_bprintf(strbuf, "wlfc- pkt((in,2bus,txstats,hdrpull),(dropped,hdr_only,wlc_tossed)" "(freed,free_err,rollback)) = " "((%d,%d,%d,%d),(%d,%d,%d),(%d,%d,%d))\n", wlfc->stats.pktin, wlfc->stats.pkt2bus, wlfc->stats.txstatus_in, wlfc->stats.dhd_hdrpulls, wlfc->stats.pktdropped, wlfc->stats.wlfc_header_only_pkt, wlfc->stats.wlc_tossed_pkts, wlfc->stats.pkt_freed, wlfc->stats.pkt_free_err, wlfc->stats.rollback); bcm_bprintf(strbuf, "wlfc- suppress((d11,wlc,err),enq(d11,wl,hq,mac?),retx(d11,wlc,hq)) = " "((%d,%d,%d),(%d,%d,%d,%d),(%d,%d,%d))\n", wlfc->stats.d11_suppress, wlfc->stats.wl_suppress, wlfc->stats.bad_suppress, wlfc->stats.psq_d11sup_enq, wlfc->stats.psq_wlsup_enq, wlfc->stats.psq_hostq_enq, wlfc->stats.mac_handle_notfound, wlfc->stats.psq_d11sup_retx, wlfc->stats.psq_wlsup_retx, wlfc->stats.psq_hostq_retx); return; } /* 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) { for (i = (h->slot_pos + 1); i != h->slot_pos;) { if (h->items[i].state == WLFC_HANGER_ITEM_STATE_FREE) { h->slot_pos = i; return (uint16)i; } (i == h->max_items)? i = 0 : i++; } 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].identifier = slot_id; 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, int 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].identifier = 0; h->items[slot_id].gen = 0xff; 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; } static int _dhd_wlfc_pushheader(athost_wl_status_info_t* ctx, void* p, bool tim_signal, uint8 tim_bmp, uint8 mac_handle, uint32 htodtag) { uint32 wl_pktinfo = 0; uint8* wlh; uint8 dataOffset; uint8 fillers; uint8 tim_signal_len = 0; struct bdc_header *h; if (tim_signal) { tim_signal_len = 1 + 1 + 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 + 2 + tim_signal_len; fillers = ROUNDUP(dataOffset, 4) - dataOffset; dataOffset += fillers; PKTPUSH(ctx->osh, p, dataOffset); wlh = (uint8*) PKTDATA(ctx->osh, p); wl_pktinfo = htol32(htodtag); wlh[0] = WLFC_CTL_TYPE_PKTTAG; wlh[1] = WLFC_CTL_VALUE_LEN_PKTTAG; memcpy(&wlh[2], &wl_pktinfo, sizeof(uint32)); 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); 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) { WLFC_DBGMESG(("%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) < (h->dataOffset << 2)) { WLFC_DBGMESG(("%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)); if (((ctx->destination_entries.interfaces[ifid].iftype == WLC_E_IF_ROLE_STA) || ETHER_ISMULTI(dstn) || (ctx->destination_entries.interfaces[ifid].iftype == WLC_E_IF_ROLE_P2P_CLIENT)) && (ctx->destination_entries.interfaces[ifid].occupied)) { return &ctx->destination_entries.interfaces[ifid]; } 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)) return &table[i]; } } } return &ctx->destination_entries.other; } 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 - a packet from send-q will need to go back to send-q and not delay-q since that will change the order of packets. - 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; void* pktout; int rc = BCME_OK; int prec; entry = _dhd_wlfc_find_table_entry(ctx, p); prec = DHD_PKTTAG_FIFO(PKTTAG(p)); if (entry != NULL) { if (pkt_type == eWLFC_PKTTYPE_SUPPRESSED) { /* wl-header is saved for suppressed packets */ if (WLFC_PKTQ_PENQ_HEAD(&entry->psq, ((prec << 1) + 1), p) == NULL) { WLFC_DBGMESG(("Error: %s():%d\n", __FUNCTION__, __LINE__)); rc = BCME_ERROR; } } else { /* remove header first */ rc = _dhd_wlfc_pullheader(ctx, p); if (rc != BCME_OK) { WLFC_DBGMESG(("Error: %s():%d\n", __FUNCTION__, __LINE__)); /* free the hanger slot */ dhd_wlfc_hanger_poppkt(ctx->hanger, hslot, &pktout, 1); PKTFREE(ctx->osh, p, TRUE); rc = BCME_ERROR; return rc; } if (pkt_type == eWLFC_PKTTYPE_DELAYED) { /* delay-q packets are going to delay-q */ if (WLFC_PKTQ_PENQ_HEAD(&entry->psq, (prec << 1), p) == NULL) { WLFC_DBGMESG(("Error: %s():%d\n", __FUNCTION__, __LINE__)); rc = BCME_ERROR; } } else { /* these are going to SENDQ */ if (WLFC_PKTQ_PENQ_HEAD(&ctx->SENDQ, prec, p) == NULL) { WLFC_DBGMESG(("Error: %s():%d\n", __FUNCTION__, __LINE__)); rc = BCME_ERROR; } } /* free the hanger slot */ dhd_wlfc_hanger_poppkt(ctx->hanger, hslot, &pktout, 1); /* decrement sequence count */ WLFC_DECR_SEQCOUNT(entry, prec); } /* if this packet did not count against FIFO credit, it must have taken a requested_credit from the firmware (for pspoll etc.) */ if (!DHD_PKTTAG_CREDITCHECK(PKTTAG(p))) { entry->requested_credit++; } } else { WLFC_DBGMESG(("Error: %s():%d\n", __FUNCTION__, __LINE__)); rc = BCME_ERROR; } if (rc != BCME_OK) ctx->stats.rollback_failed++; else ctx->stats.rollback++; return rc; } static void _dhd_wlfc_flow_control_check(athost_wl_status_info_t* ctx, struct pktq* pq, uint8 if_id) { 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(ctx->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(ctx->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+ 12; /* 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); DHD_PKTTAG_SETSIGNALONLY(PKTTAG(p), 1); #ifdef PROP_TXSTATUS_DEBUG ctx->stats.signal_only_pkts_sent++; #endif rc = dhd_bus_txdata(((dhd_pub_t *)ctx->dhdp)->bus, p, FALSE); if (rc != BCME_OK) { 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) { WLFC_DBGMESG(("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 (WLFC_PKTQ_PENQ(&entry->psq, ((prec << 1) + 1), p) == NULL) { 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; uint8 free_ctr; *slot = hslot; if (entry == NULL) { entry = _dhd_wlfc_find_table_entry(ctx, p); } if (entry == NULL) { WLFC_DBGMESG(("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) { hslot = dhd_wlfc_hanger_get_free_slot(ctx->hanger); free_ctr = WLFC_SEQCOUNT(entry, DHD_PKTTAG_FIFO(PKTTAG(p))); DHD_PKTTAG_SET_H2DTAG(PKTTAG(p), htod); WLFC_PKTFLAG_SET_GENERATION(htod, entry->generation); entry->transit_count++; } else { hslot = WLFC_PKTID_HSLOT_GET(DHD_PKTTAG_H2DTAG(PKTTAG(p))); free_ctr = WLFC_PKTID_FREERUNCTR_GET(DHD_PKTTAG_H2DTAG(PKTTAG(p))); } WLFC_PKTID_HSLOT_SET(htod, hslot); WLFC_PKTID_FREERUNCTR_SET(htod, free_ctr); DHD_PKTTAG_SETPKTDIR(PKTTAG(p), 1); WL_TXSTATUS_SET_FLAGS(htod, WLFC_PKTFLAG_PKTFROMHOST); WL_TXSTATUS_SET_FIFO(htod, DHD_PKTTAG_FIFO(PKTTAG(p))); if (!DHD_PKTTAG_CREDITCHECK(PKTTAG(p))) { /* Indicate that this packet is being sent in response to an explicit request from the firmware side. */ WLFC_PKTFLAG_SET_PKTREQUESTED(htod); } else { WLFC_PKTFLAG_CLR_PKTREQUESTED(htod); } if (header_needed) { rc = _dhd_wlfc_pushheader(ctx, p, send_tim_update, entry->traffic_lastreported_bmp, entry->mac_handle, htod); if (rc == BCME_OK) { DHD_PKTTAG_SET_H2DTAG(PKTTAG(p), htod); /* 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) { /* increment free running sequence count */ WLFC_INCR_SEQCOUNT(entry, DHD_PKTTAG_FIFO(PKTTAG(p))); #ifdef PROP_TXSTATUS_DEBUG ((wlfc_hanger_t*)(ctx->hanger))->items[hslot].push_time = OSL_SYSUPTIME(); #endif } else { WLFC_DBGMESG(("%s() hanger_pushpkt() failed, rc: %d\n", __FUNCTION__, rc)); } } } else { int gen; /* remove old header */ rc = _dhd_wlfc_pullheader(ctx, p); if (rc == BCME_OK) { hslot = WLFC_PKTID_HSLOT_GET(DHD_PKTTAG_H2DTAG(PKTTAG(p))); dhd_wlfc_hanger_get_genbit(ctx->hanger, p, hslot, &gen); WLFC_PKTFLAG_SET_GENERATION(htod, gen); free_ctr = WLFC_PKTID_FREERUNCTR_GET(DHD_PKTTAG_H2DTAG(PKTTAG(p))); /* push new header */ _dhd_wlfc_pushheader(ctx, p, send_tim_update, entry->traffic_lastreported_bmp, entry->mac_handle, htod); } } *slot = hslot; return rc; } static int _dhd_wlfc_is_destination_closed(athost_wl_status_info_t* ctx, wlfc_mac_descriptor_t* entry, int prec) { 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 1; } /* 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 1; return 0; } 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) { wlfc_mac_descriptor_t* entry; wlfc_mac_descriptor_t* table; uint8 token_pos; int total_entries; void* p = NULL; int pout; int i; *entry_out = NULL; token_pos = ctx->token_pos[prec]; /* most cases a packet will count against FIFO credit */ *ac_credit_spent = 1; *needs_hdr = 1; /* search all entries, include nodes as well as interfaces */ table = (wlfc_mac_descriptor_t*)&ctx->destination_entries; total_entries = sizeof(ctx->destination_entries)/sizeof(wlfc_mac_descriptor_t); for (i = 0; i < total_entries; i++) { entry = &table[(token_pos + i) % total_entries]; if (entry->occupied) { if (!_dhd_wlfc_is_destination_closed(ctx, entry, prec)) { p = pktq_mdeq(&entry->psq, /* higher precedence will be picked up first, * i.e. suppressed packets before delayed ones */ NBITVAL((prec << 1) + 1), &pout); *needs_hdr = 0; if (p == NULL) { if (entry->suppressed == TRUE) { if ((entry->suppr_transit_count <= entry->suppress_count)) { entry->suppressed = FALSE; } else { return NULL; } } /* De-Q from delay Q */ p = pktq_mdeq(&entry->psq, NBITVAL((prec << 1)), &pout); *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 /* if the packet was pulled out while destination is in closed state but had a non-zero packets requested, then this should not count against the FIFO credit. That is due to the fact that the firmware will most likely hold onto this packet until a suitable time later to push it to the appropriate AC FIFO. */ if (entry->state == WLFC_STATE_CLOSE) *ac_credit_spent = 0; } else if (entry->requested_packet > 0) { entry->requested_packet--; DHD_PKTTAG_SETONETIMEPKTRQST(PKTTAG(p)); if (entry->state == WLFC_STATE_CLOSE) *ac_credit_spent = 0; } /* move token to ensure fair round-robin */ ctx->token_pos[prec] = (token_pos + i + 1) % total_entries; *entry_out = entry; _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 void* _dhd_wlfc_deque_sendq(athost_wl_status_info_t* ctx, int prec) { wlfc_mac_descriptor_t* entry; void* p; p = pktq_pdeq(&ctx->SENDQ, prec); if (p != NULL) { if (ETHER_ISMULTI(DHD_PKTTAG_DSTN(PKTTAG(p)))) /* bc/mc packets do not have a delay queue */ return p; entry = _dhd_wlfc_find_table_entry(ctx, p); if (entry == NULL) { WLFC_DBGMESG(("Error: %s():%d\n", __FUNCTION__, __LINE__)); return p; } while ((p != NULL)) { /* - 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 (WLFC_PKTQ_PENQ(&entry->psq, (prec << 1), p) == NULL) { WLFC_DBGMESG(("D")); /* dhd_txcomplete(ctx->dhdp, p, FALSE); */ PKTFREE(ctx->osh, p, TRUE); ctx->stats.delayq_full_error++; } /* A packet has been pushed, update traffic availability bitmap, if applicable */ _dhd_wlfc_traffic_pending_check(ctx, entry, prec); p = pktq_pdeq(&ctx->SENDQ, prec); if (p == NULL) break; entry = _dhd_wlfc_find_table_entry(ctx, p); if ((entry == NULL) || (ETHER_ISMULTI(DHD_PKTTAG_DSTN(PKTTAG(p))))) { return p; } } } return p; } static int _dhd_wlfc_mac_entry_update(athost_wl_status_info_t* ctx, wlfc_mac_descriptor_t* entry, ewlfc_mac_entry_action_t action, uint8 ifid, uint8 iftype, uint8* ea) { int rc = BCME_OK; if (action == eWLFC_MAC_ENTRY_ACTION_ADD) { 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); pktq_init(&entry->psq, WLFC_PSQ_PREC_COUNT, WLFC_PSQ_LEN); } else if (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); } else if (action == eWLFC_MAC_ENTRY_ACTION_DEL) { entry->occupied = 0; entry->state = WLFC_STATE_CLOSE; entry->requested_credit = 0; /* enable after packets are queued-deqeued properly. pktq_flush(dhd->osh, &entry->psq, FALSE, NULL, 0); */ } return rc; } int _dhd_wlfc_borrow_credit(athost_wl_status_info_t* ctx, uint8 available_credit_map, int borrower_ac) { int lender_ac; int rc = BCME_ERROR; if (ctx == NULL || available_credit_map == 0) { WLFC_DBGMESG(("Error: %s():%d\n", __FUNCTION__, __LINE__)); return BCME_BADARG; } /* Borrow from lowest priority available AC (including BC/MC credits) */ for (lender_ac = 0; lender_ac <= AC_COUNT; lender_ac++) { if ((available_credit_map && (1 << lender_ac)) && (ctx->FIFO_credit[lender_ac] > 0)) { ctx->credits_borrowed[borrower_ac][lender_ac]++; ctx->FIFO_credit[lender_ac]--; rc = BCME_OK; break; } } return rc; } int dhd_wlfc_interface_entry_update(void* state, ewlfc_mac_entry_action_t 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); } int dhd_wlfc_FIFOcreditmap_update(void* state, uint8* credits) { athost_wl_status_info_t* ctx = (athost_wl_status_info_t*)state; /* update the AC FIFO credit map */ ctx->FIFO_credit[0] = credits[0]; ctx->FIFO_credit[1] = credits[1]; ctx->FIFO_credit[2] = credits[2]; ctx->FIFO_credit[3] = credits[3]; /* credit for bc/mc packets */ ctx->FIFO_credit[4] = credits[4]; /* credit for ATIM FIFO is not used yet. */ ctx->FIFO_credit[5] = 0; return BCME_OK; } int dhd_wlfc_enque_sendq(void* state, int prec, void* p) { athost_wl_status_info_t* ctx = (athost_wl_status_info_t*)state; if ((state == NULL) || /* prec = AC_COUNT is used for bc/mc queue */ (prec > AC_COUNT) || (p == NULL)) { WLFC_DBGMESG(("Error: %s():%d\n", __FUNCTION__, __LINE__)); return BCME_BADARG; } if (FALSE == dhd_prec_enq(ctx->dhdp, &ctx->SENDQ, p, prec)) { ctx->stats.sendq_full_error++; /* WLFC_DBGMESG(("Error: %s():%d, qlen:%d\n", __FUNCTION__, __LINE__, ctx->SENDQ.len)); */ WLFC_HOST_FIFO_DROPPEDCTR_INC(ctx, prec); WLFC_DBGMESG(("Q")); PKTFREE(ctx->osh, p, TRUE); return BCME_ERROR; } ctx->stats.pktin++; /* _dhd_wlfc_flow_control_check(ctx, &ctx->SENDQ, DHD_PKTTAG_IF(PKTTAG(p))); */ return BCME_OK; } 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; /* 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, TRUE); else ctx->stats.generic_error++; if (rc == BCME_OK) { ctx->stats.pkt2bus++; if (commit_info->ac_fifo_credit_spent) { ctx->stats.sendq_pkts[ac]++; WLFC_HOST_FIFO_CREDIT_INC_SENTCTRS(ctx, ac); } } else if (rc == BCME_NORESOURCE) rc = BCME_ERROR; else { /* bus commit has failed, rollback. - remove wl-header for a delayed packet - save wl-header header for suppressed packets */ rc = _dhd_wlfc_rollback_packet_toq(ctx, commit_info->p, (commit_info->pkt_type), hslot); if (rc != BCME_OK) ctx->stats.rollback_failed++; rc = BCME_ERROR; } return rc; } int dhd_wlfc_commit_packets(void* state, f_commitpkt_t fcommit, void* commit_ctx) { int ac; int credit; int rc; dhd_wlfc_commit_info_t commit_info; athost_wl_status_info_t* ctx = (athost_wl_status_info_t*)state; int credit_count = 0; int bus_retry_count = 0; uint8 ac_available = 0; /* Bitmask for 4 ACs + BC/MC */ if ((state == NULL) || (fcommit == NULL)) { WLFC_DBGMESG(("Error: %s():%d\n", __FUNCTION__, __LINE__)); return BCME_BADARG; } memset(&commit_info, 0, sizeof(commit_info)); /* Commit packets for regular AC traffic. Higher priority first. First, use up FIFO credits available to each AC. Based on distribution and credits left, borrow from other ACs as applicable -NOTE: If the bus between the host and firmware is overwhelmed by the traffic from host, it is possible that higher priority traffic starves the lower priority queue. If that occurs often, we may have to employ weighted round-robin or ucode scheme to avoid low priority packet starvation. */ for (ac = AC_COUNT; ac >= 0; ac--) { int initial_credit_count = ctx->FIFO_credit[ac]; /* packets from SENDQ are fresh and they'd need header and have no MAC entry */ commit_info.needs_hdr = 1; commit_info.mac_entry = NULL; commit_info.pkt_type = eWLFC_PKTTYPE_NEW; do { commit_info.p = _dhd_wlfc_deque_sendq(ctx, ac); if (commit_info.p == NULL) break; else if (ETHER_ISMULTI(DHD_PKTTAG_DSTN(PKTTAG(commit_info.p)))) { ASSERT(ac == AC_COUNT); if (ctx->FIFO_credit[ac]) { rc = _dhd_wlfc_handle_packet_commit(ctx, ac, &commit_info, fcommit, commit_ctx); /* Bus commits may fail (e.g. flow control); abort after retries */ if (rc == BCME_OK) { if (commit_info.ac_fifo_credit_spent) { (void) _dhd_wlfc_borrow_credit(ctx, ac_available, ac); credit_count--; } } else { bus_retry_count++; if (bus_retry_count >= BUS_RETRIES) { DHD_ERROR((" %s: bus error\n", __FUNCTION__)); return rc; } } } } } while (commit_info.p); for (credit = 0; credit < ctx->FIFO_credit[ac];) { commit_info.p = _dhd_wlfc_deque_delayedq(ctx, ac, &(commit_info.ac_fifo_credit_spent), &(commit_info.needs_hdr), &(commit_info.mac_entry)); if (commit_info.p == NULL) break; commit_info.pkt_type = (commit_info.needs_hdr) ? eWLFC_PKTTYPE_DELAYED : eWLFC_PKTTYPE_SUPPRESSED; rc = _dhd_wlfc_handle_packet_commit(ctx, ac, &commit_info, fcommit, commit_ctx); /* Bus commits may fail (e.g. flow control); abort after retries */ if (rc == BCME_OK) { if (commit_info.ac_fifo_credit_spent) { credit++; } } else { bus_retry_count++; if (bus_retry_count >= BUS_RETRIES) { DHD_ERROR(("dhd_wlfc_commit_packets(): bus error\n")); ctx->FIFO_credit[ac] -= credit; return rc; } } } ctx->FIFO_credit[ac] -= credit; /* If no credits were used, the queue is idle and can be re-used Note that resv credits cannot be borrowed */ if (initial_credit_count == ctx->FIFO_credit[ac]) { ac_available |= (1 << ac); credit_count += ctx->FIFO_credit[ac]; } } /* We borrow only for AC_BE and only if no other traffic seen for DEFER_PERIOD Note that (ac_available & WLFC_AC_BE_TRAFFIC_ONLY) is done to: a) ignore BC/MC for deferring borrow b) ignore AC_BE being available along with other ACs (this should happen only for pure BC/MC traffic) i.e. AC_VI, AC_VO, AC_BK all MUST be available (i.e. no traffic) and we do not care if AC_BE and BC/MC are available or not */ if ((ac_available & WLFC_AC_BE_TRAFFIC_ONLY) == WLFC_AC_BE_TRAFFIC_ONLY) { if (ctx->allow_credit_borrow) { ac = 1; /* Set ac to AC_BE and borrow credits */ } else { int delta; int curr_t = OSL_SYSUPTIME(); if (curr_t > ctx->borrow_defer_timestamp) delta = curr_t - ctx->borrow_defer_timestamp; else delta = 0xffffffff + curr_t - ctx->borrow_defer_timestamp; if (delta >= WLFC_BORROW_DEFER_PERIOD_MS) { /* Reset borrow but defer to next iteration (defensive borrowing) */ ctx->allow_credit_borrow = TRUE; ctx->borrow_defer_timestamp = 0; } return BCME_OK; } } else { /* If we have multiple AC traffic, turn off borrowing, mark time and bail out */ ctx->allow_credit_borrow = FALSE; ctx->borrow_defer_timestamp = OSL_SYSUPTIME(); return BCME_OK; } /* At this point, borrow all credits only for "ac" (which should be set above to AC_BE) Generically use "ac" only in case we extend to all ACs in future */ for (; (credit_count > 0);) { commit_info.p = _dhd_wlfc_deque_delayedq(ctx, ac, &(commit_info.ac_fifo_credit_spent), &(commit_info.needs_hdr), &(commit_info.mac_entry)); if (commit_info.p == NULL) break; commit_info.pkt_type = (commit_info.needs_hdr) ? eWLFC_PKTTYPE_DELAYED : eWLFC_PKTTYPE_SUPPRESSED; rc = _dhd_wlfc_handle_packet_commit(ctx, ac, &commit_info, fcommit, commit_ctx); /* Bus commits may fail (e.g. flow control); abort after retries */ if (rc == BCME_OK) { if (commit_info.ac_fifo_credit_spent) { (void) _dhd_wlfc_borrow_credit(ctx, ac_available, ac); credit_count--; } } else { bus_retry_count++; if (bus_retry_count >= BUS_RETRIES) { DHD_ERROR(("dhd_wlfc_commit_packets(): bus error\n")); return rc; } } } return BCME_OK; } 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; } void dhd_wlfc_txcomplete(dhd_pub_t *dhd, void *txp, bool success, bool wake_locked) { athost_wl_status_info_t* wlfc = (athost_wl_status_info_t*) dhd->wlfc_state; void* p; int fifo_id; if (!wake_locked) dhd_os_wlfc_block(dhd); if (DHD_PKTTAG_SIGNALONLY(PKTTAG(txp))) { #ifdef PROP_TXSTATUS_DEBUG wlfc->stats.signal_only_pkts_freed++; #endif if (success) /* is this a signal-only packet? */ PKTFREE(wlfc->osh, txp, TRUE); if (!wake_locked) dhd_os_wlfc_unblock(dhd); return; } if (!success) { WLFC_DBGMESG(("At: %s():%d, bus_complete() failure for %p, htod_tag:0x%08x\n", __FUNCTION__, __LINE__, txp, DHD_PKTTAG_H2DTAG(PKTTAG(txp)))); dhd_wlfc_hanger_poppkt(wlfc->hanger, WLFC_PKTID_HSLOT_GET(DHD_PKTTAG_H2DTAG (PKTTAG(txp))), &p, 1); /* indicate failure and free the packet */ dhd_txcomplete(dhd, txp, FALSE); /* return the credit, if necessary */ if (DHD_PKTTAG_CREDITCHECK(PKTTAG(txp))) { int lender, credit_returned = 0; /* Note that borrower is fifo_id */ fifo_id = DHD_PKTTAG_FIFO(PKTTAG(txp)); /* 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]++; } } PKTFREE(wlfc->osh, txp, TRUE); } if (!wake_locked) dhd_os_wlfc_unblock(dhd); return; } static int dhd_wlfc_compressed_txstatus_update(dhd_pub_t *dhd, uint8* pkt_info, uint8 len) { uint8 status_flag; uint32 status; int ret; int remove_from_hanger = 1; void* pktbuf; uint8 fifo_id; uint8 count = 0; uint32 status_g; uint32 hslot, hcnt; wlfc_mac_descriptor_t* entry = NULL; athost_wl_status_info_t* wlfc = (athost_wl_status_info_t*) dhd->wlfc_state; memcpy(&status, pkt_info, sizeof(uint32)); status_flag = WL_TXSTATUS_GET_FLAGS(status); status_g = status & 0xff000000; hslot = (status & 0x00ffff00) >> 8; hcnt = status & 0xff; len = pkt_info[4]; wlfc->stats.txstatus_in++; if (status_flag == WLFC_CTL_PKTFLAG_DISCARD) { wlfc->stats.pkt_freed++; } else if (status_flag == WLFC_CTL_PKTFLAG_D11SUPPRESS) { wlfc->stats.d11_suppress++; remove_from_hanger = 0; } else if (status_flag == WLFC_CTL_PKTFLAG_WLSUPPRESS) { wlfc->stats.wl_suppress++; remove_from_hanger = 0; } else if (status_flag == WLFC_CTL_PKTFLAG_TOSSED_BYWLC) { wlfc->stats.wlc_tossed_pkts++; } while (count < len) { status = (status_g << 24) | (hslot << 8) | (hcnt); count++; hslot++; hcnt++; ret = dhd_wlfc_hanger_poppkt(wlfc->hanger, WLFC_PKTID_HSLOT_GET(status), &pktbuf, remove_from_hanger); if (ret != BCME_OK) { /* do something */ continue; } entry = _dhd_wlfc_find_table_entry(wlfc, pktbuf); if (!remove_from_hanger) { /* this packet was suppressed */ if (!entry->suppressed || entry->generation != WLFC_PKTID_GEN(status)) { entry->suppressed = TRUE; entry->suppress_count = pktq_mlen(&entry->psq, NBITVAL((WL_TXSTATUS_GET_FIFO(status) << 1) + 1)); entry->suppr_transit_count = entry->transit_count; } entry->generation = WLFC_PKTID_GEN(status); } #ifdef PROP_TXSTATUS_DEBUG { uint32 new_t = OSL_SYSUPTIME(); uint32 old_t; uint32 delta; old_t = ((wlfc_hanger_t*)(wlfc->hanger))->items[ WLFC_PKTID_HSLOT_GET(status)].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 */ fifo_id = DHD_PKTTAG_FIFO(PKTTAG(pktbuf)); /* pick up the implicit credit from this packet */ if (DHD_PKTTAG_CREDITCHECK(PKTTAG(pktbuf))) { if (wlfc->proptxstatus_mode == WLFC_FCMODE_IMPLIED_CREDIT) { int lender, credit_returned = 0; /* 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]++; } } } 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 (!entry) { entry = _dhd_wlfc_find_table_entry(wlfc, pktbuf); } 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)) { ret = _dhd_wlfc_enque_suppressed(wlfc, fifo_id, pktbuf); if (ret != BCME_OK) { /* delay q is full, drop this packet */ dhd_wlfc_hanger_poppkt(wlfc->hanger, WLFC_PKTID_HSLOT_GET(status), &pktbuf, 1); /* indicate failure and free the packet */ dhd_txcomplete(dhd, pktbuf, FALSE); entry->transit_count--; /* packet is transmitted Successfully by dongle * after first suppress. */ if (entry->suppressed) { entry->suppr_transit_count--; } PKTFREE(wlfc->osh, pktbuf, TRUE); } else { /* Mark suppressed to avoid a double free during wlfc cleanup */ dhd_wlfc_hanger_mark_suppressed(wlfc->hanger, WLFC_PKTID_HSLOT_GET(status), WLFC_PKTID_GEN(status)); entry->suppress_count++; } } else { dhd_txcomplete(dhd, pktbuf, TRUE); entry->transit_count--; /* This packet is transmitted Successfully by dongle * even after first suppress. */ if (entry->suppressed) { entry->suppr_transit_count--; } /* free the packet */ PKTFREE(wlfc->osh, pktbuf, TRUE); } } return BCME_OK; } /* Handle discard or suppress indication */ static int dhd_wlfc_txstatus_update(dhd_pub_t *dhd, uint8* pkt_info) { uint8 status_flag; uint32 status; int ret; int remove_from_hanger = 1; void* pktbuf; uint8 fifo_id; wlfc_mac_descriptor_t* entry = NULL; athost_wl_status_info_t* wlfc = (athost_wl_status_info_t*) dhd->wlfc_state; memcpy(&status, pkt_info, sizeof(uint32)); status_flag = WL_TXSTATUS_GET_FLAGS(status); wlfc->stats.txstatus_in++; if (status_flag == WLFC_CTL_PKTFLAG_DISCARD) { wlfc->stats.pkt_freed++; } else if (status_flag == WLFC_CTL_PKTFLAG_D11SUPPRESS) { wlfc->stats.d11_suppress++; remove_from_hanger = 0; } else if (status_flag == WLFC_CTL_PKTFLAG_WLSUPPRESS) { wlfc->stats.wl_suppress++; remove_from_hanger = 0; } else if (status_flag == WLFC_CTL_PKTFLAG_TOSSED_BYWLC) { wlfc->stats.wlc_tossed_pkts++; } ret = dhd_wlfc_hanger_poppkt(wlfc->hanger, WLFC_PKTID_HSLOT_GET(status), &pktbuf, remove_from_hanger); if (ret != BCME_OK) { /* do something */ return ret; } entry = _dhd_wlfc_find_table_entry(wlfc, pktbuf); if (!remove_from_hanger) { /* this packet was suppressed */ if (!entry->suppressed || entry->generation != WLFC_PKTID_GEN(status)) { entry->suppressed = TRUE; entry->suppress_count = pktq_mlen(&entry->psq, NBITVAL((WL_TXSTATUS_GET_FIFO(status) << 1) + 1)); entry->suppr_transit_count = entry->transit_count; } entry->generation = WLFC_PKTID_GEN(status); } #ifdef PROP_TXSTATUS_DEBUG { uint32 new_t = OSL_SYSUPTIME(); uint32 old_t; uint32 delta; old_t = ((wlfc_hanger_t*)(wlfc->hanger))->items[ WLFC_PKTID_HSLOT_GET(status)].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 */ fifo_id = DHD_PKTTAG_FIFO(PKTTAG(pktbuf)); /* pick up the implicit credit from this packet */ if (DHD_PKTTAG_CREDITCHECK(PKTTAG(pktbuf))) { if (wlfc->proptxstatus_mode == WLFC_FCMODE_IMPLIED_CREDIT) { int lender, credit_returned = 0; /* 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]++; } } } 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 (!entry) { entry = _dhd_wlfc_find_table_entry(wlfc, pktbuf); } 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)) { ret = _dhd_wlfc_enque_suppressed(wlfc, fifo_id, pktbuf); if (ret != BCME_OK) { /* delay q is full, drop this packet */ dhd_wlfc_hanger_poppkt(wlfc->hanger, WLFC_PKTID_HSLOT_GET(status), &pktbuf, 1); /* indicate failure and free the packet */ dhd_txcomplete(dhd, pktbuf, FALSE); entry->transit_count--; /* This packet is transmitted Successfully by * dongle even after first suppress. */ if (entry->suppressed) { entry->suppr_transit_count--; } PKTFREE(wlfc->osh, pktbuf, TRUE); } else { /* Mark suppressed to avoid a double free during wlfc cleanup */ dhd_wlfc_hanger_mark_suppressed(wlfc->hanger, WLFC_PKTID_HSLOT_GET(status), WLFC_PKTID_GEN(status)); entry->suppress_count++; } } else { dhd_txcomplete(dhd, pktbuf, TRUE); entry->transit_count--; /* This packet is transmitted Successfully by dongle even after first suppress. */ if (entry->suppressed) { entry->suppr_transit_count--; } /* free the packet */ PKTFREE(wlfc->osh, pktbuf, TRUE); } 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 (wlfc->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] -= 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]; } } } return BCME_OK; } static int dhd_wlfc_dbg_senum_check(dhd_pub_t *dhd, uint8 *value) { uint32 timestamp; (void)dhd; bcopy(&value[2], ×tamp, 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 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]; 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) { /* this MAC entry does not exist, create one */ if (!table[table_index].occupied) { 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); } else { /* the space should have been empty, but it's not */ wlfc->stats.mac_update_failed++; } } else { /* there is an existing entry, move it to new index if necessary. */ if (existing_index != table_index) { /* if we already have an entry, free the old one */ table[existing_index].occupied = 0; table[existing_index].state = WLFC_STATE_CLOSE; table[existing_index].requested_credit = 0; table[existing_index].interface_id = 0; /* enable after packets are queued-deqeued properly. pktq_flush(dhd->osh, &table[existing_index].psq, FALSE, NULL, 0); */ } } } 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); } 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) { /* a fresh PS mode should wipe old ps credits? */ desc->requested_credit = 0; if (type == WLFC_CTL_TYPE_MAC_OPEN) { desc->state = WLFC_STATE_OPEN; DHD_WLFC_CTRINC_MAC_OPEN(desc); } else { desc->state = WLFC_STATE_CLOSE; DHD_WLFC_CTRINC_MAC_CLOSE(desc); /* Indicate to firmware if there is any traffic pending. */ for (i = AC_BE; 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]; } 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]; } 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; } } static 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 = tlv_hdr_len; uint16 processed = 0; athost_wl_status_info_t* wlfc = (athost_wl_status_info_t*) dhd->wlfc_state; tmpbuf = (uint8*)PKTDATA(dhd->osh, pktbuf); if (remainder) { while ((processed < (WLFC_MAX_PENDING_DATALEN * 2)) && (remainder > 0)) { type = tmpbuf[processed]; if (type == WLFC_CTL_TYPE_FILLER) { remainder -= 1; processed += 1; continue; } len = tmpbuf[processed + 1]; value = &tmpbuf[processed + 2]; if (remainder < (2 + len)) break; remainder -= 2 + len; processed += 2 + len; if (type == WLFC_CTL_TYPE_TXSTATUS) dhd_wlfc_txstatus_update(dhd, value); if (type == WLFC_CTL_TYPE_COMP_TXSTATUS) dhd_wlfc_compressed_txstatus_update(dhd, value, len); else if (type == WLFC_CTL_TYPE_HOST_REORDER_RXPKTS) dhd_wlfc_reorderinfo_indicate(value, len, reorder_info_buf, reorder_info_len); else if (type == WLFC_CTL_TYPE_FIFO_CREDITBACK) dhd_wlfc_fifocreditback_indicate(dhd, value); else if (type == WLFC_CTL_TYPE_RSSI) dhd_wlfc_rssi_indicate(dhd, value); else if (type == WLFC_CTL_TYPE_MAC_REQUEST_CREDIT) dhd_wlfc_credit_request(dhd, value); else if (type == WLFC_CTL_TYPE_MAC_REQUEST_PACKET) dhd_wlfc_packet_request(dhd, value); else if ((type == WLFC_CTL_TYPE_MAC_OPEN) || (type == WLFC_CTL_TYPE_MAC_CLOSE)) dhd_wlfc_psmode_update(dhd, value, type); else if ((type == WLFC_CTL_TYPE_MACDESC_ADD) || (type == WLFC_CTL_TYPE_MACDESC_DEL)) dhd_wlfc_mac_table_update(dhd, value, type); else if (type == WLFC_CTL_TYPE_TRANS_ID) dhd_wlfc_dbg_senum_check(dhd, value); else if ((type == WLFC_CTL_TYPE_INTERFACE_OPEN) || (type == WLFC_CTL_TYPE_INTERFACE_CLOSE)) { dhd_wlfc_interface_update(dhd, value, type); } } if (remainder != 0) { /* trouble..., something is not right */ wlfc->stats.tlv_parse_failed++; } } return BCME_OK; } int dhd_wlfc_init(dhd_pub_t *dhd) { char iovbuf[12]; /* Room for "tlv" + '\0' + parameter */ /* enable all signals & indicate host proptxstatus logic is active */ uint32 tlv = dhd->wlfc_enabled? WLFC_FLAGS_RSSI_SIGNALS | WLFC_FLAGS_XONXOFF_SIGNALS | WLFC_FLAGS_CREDIT_STATUS_SIGNALS | WLFC_FLAGS_HOST_PROPTXSTATUS_ACTIVE | WLFC_FLAGS_HOST_RXRERODER_ACTIVE : 0; /* WLFC_FLAGS_HOST_PROPTXSTATUS_ACTIVE | WLFC_FLAGS_HOST_RXRERODER_ACTIVE : 0; */ /* try to enable/disable signaling by sending "tlv" iovar. if that fails, fallback to no flow control? Print a message for now. */ /* enable proptxtstatus signaling by default */ bcm_mkiovar("tlv", (char *)&tlv, 4, iovbuf, sizeof(iovbuf)); if (dhd_wl_ioctl_cmd(dhd, WLC_SET_VAR, iovbuf, sizeof(iovbuf), TRUE, 0) < 0) { DHD_ERROR(("dhd_wlfc_init(): failed to enable/disable bdcv2 tlv signaling\n")); } else { /* Leaving the message for now, it should be removed after a while; once the tlv situation is stable. */ DHD_ERROR(("dhd_wlfc_init(): successfully %s bdcv2 tlv signaling, %d\n", dhd->wlfc_enabled?"enabled":"disabled", tlv)); } return BCME_OK; } int dhd_wlfc_enable(dhd_pub_t *dhd) { int i; athost_wl_status_info_t* wlfc; DHD_TRACE(("Enter %s\n", __FUNCTION__)); if (!dhd->wlfc_enabled || dhd->wlfc_state) return BCME_OK; /* allocate space to track txstatus propagated from firmware */ dhd->wlfc_state = MALLOC(dhd->osh, sizeof(athost_wl_status_info_t)); if (dhd->wlfc_state == NULL) return BCME_NOMEM; /* 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; wlfc->hanger = dhd_wlfc_hanger_create(dhd->osh, WLFC_HANGER_MAXITEMS); if (wlfc->hanger == NULL) { MFREE(dhd->osh, dhd->wlfc_state, sizeof(athost_wl_status_info_t)); dhd->wlfc_state = NULL; return BCME_NOMEM; } /* initialize all interfaces to accept traffic */ for (i = 0; i < WLFC_MAX_IFNUM; i++) { wlfc->hostif_flow_state[i] = OFF; } /* create the SENDQ containing sub-queues for all AC precedences + 1 for bc/mc traffic */ pktq_init(&wlfc->SENDQ, (AC_COUNT + 1), WLFC_SENDQ_LEN); wlfc->destination_entries.other.state = WLFC_STATE_OPEN; /* bc/mc FIFO is always open [credit aside], i.e. b[5] */ wlfc->destination_entries.other.ac_bitmap = 0x1f; wlfc->destination_entries.other.interface_id = 0; wlfc->proptxstatus_mode = WLFC_FCMODE_EXPLICIT_CREDIT; wlfc->allow_credit_borrow = TRUE; wlfc->borrow_defer_timestamp = 0; return BCME_OK; } /* release all packet resources */ void dhd_wlfc_cleanup(dhd_pub_t *dhd) { 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; int prec; void *pkt = NULL; struct pktq *txq = NULL; DHD_TRACE(("Enter %s\n", __FUNCTION__)); if (dhd->wlfc_state == NULL) return; /* flush bus->txq */ txq = dhd_bus_txq(dhd->bus); /* any in the hanger? */ h = (wlfc_hanger_t*)wlfc->hanger; total_entries = sizeof(wlfc->destination_entries)/sizeof(wlfc_mac_descriptor_t); /* search all entries, include nodes as well as interfaces */ table = (wlfc_mac_descriptor_t*)&wlfc->destination_entries; for (i = 0; i < total_entries; i++) { if (table[i].occupied) { if (table[i].psq.len) { WLFC_DBGMESG(("%s(): DELAYQ[%d].len = %d\n", __FUNCTION__, i, table[i].psq.len)); /* release packets held in DELAYQ */ pktq_flush(wlfc->osh, &table[i].psq, TRUE, NULL, 0); } table[i].occupied = 0; } } /* release packets held in SENDQ */ if (wlfc->SENDQ.len) pktq_flush(wlfc->osh, &wlfc->SENDQ, TRUE, NULL, 0); for (prec = 0; prec < txq->num_prec; prec++) { pkt = pktq_pdeq(txq, prec); while (pkt) { for (i = 0; i < h->max_items; i++) { if (pkt == h->items[i].pkt) { if (h->items[i].state == WLFC_HANGER_ITEM_STATE_INUSE) { PKTFREE(wlfc->osh, h->items[i].pkt, TRUE); h->items[i].state = WLFC_HANGER_ITEM_STATE_FREE; h->items[i].pkt = NULL; h->items[i].identifier = 0; } else if (h->items[i].state == WLFC_HANGER_ITEM_STATE_INUSE_SUPPRESSED) { /* These are already freed from the psq */ h->items[i].state = WLFC_HANGER_ITEM_STATE_FREE; } break; } } pkt = pktq_pdeq(txq, prec); } } /* flush remained pkt in hanger queue, not in bus->txq */ for (i = 0; i < h->max_items; i++) { if (h->items[i].state == WLFC_HANGER_ITEM_STATE_INUSE) { PKTFREE(wlfc->osh, h->items[i].pkt, TRUE); h->items[i].state = WLFC_HANGER_ITEM_STATE_FREE; } else if (h->items[i].state == WLFC_HANGER_ITEM_STATE_INUSE_SUPPRESSED) { /* These are freed from the psq so no need to free again */ h->items[i].state = WLFC_HANGER_ITEM_STATE_FREE; } } return; } void dhd_wlfc_deinit(dhd_pub_t *dhd) { /* cleanup all psq related resources */ athost_wl_status_info_t* wlfc = (athost_wl_status_info_t*) dhd->wlfc_state; DHD_TRACE(("Enter %s\n", __FUNCTION__)); dhd_os_wlfc_block(dhd); if (dhd->wlfc_state == NULL) { dhd_os_wlfc_unblock(dhd); return; } #ifdef PROP_TXSTATUS_DEBUG { int i; wlfc_hanger_t* h = (wlfc_hanger_t*)wlfc->hanger; for (i = 0; i < h->max_items; i++) { if (h->items[i].state != WLFC_HANGER_ITEM_STATE_FREE) { WLFC_DBGMESG(("%s() pkt[%d] = 0x%p, FIFO_credit_used:%d\n", __FUNCTION__, i, h->items[i].pkt, DHD_PKTTAG_CREDITCHECK(PKTTAG(h->items[i].pkt)))); } } } #endif /* delete hanger */ dhd_wlfc_hanger_delete(dhd->osh, wlfc->hanger); /* free top structure */ MFREE(dhd->osh, dhd->wlfc_state, sizeof(athost_wl_status_info_t)); dhd->wlfc_state = NULL; dhd_os_wlfc_unblock(dhd); return; } #endif /* PROP_TXSTATUS */ void dhd_prot_dump(dhd_pub_t *dhdp, struct bcmstrbuf *strbuf) { bcm_bprintf(strbuf, "Protocol CDC: reqid %d\n", dhdp->prot->reqid); #ifdef PROP_TXSTATUS dhd_os_wlfc_block(dhdp); if (dhdp->wlfc_state) dhd_wlfc_dump(dhdp, strbuf); dhd_os_wlfc_unblock(dhdp); #endif } void dhd_prot_hdrpush(dhd_pub_t *dhd, int ifidx, void *pktbuf) { #ifdef BDC struct bdc_header *h; #endif /* BDC */ DHD_TRACE(("%s: Enter\n", __FUNCTION__)); #ifdef BDC /* Push BDC header used to convey priority for buses that don't */ PKTPUSH(dhd->osh, pktbuf, BDC_HEADER_LEN); h = (struct bdc_header *)PKTDATA(dhd->osh, pktbuf); h->flags = (BDC_PROTO_VER << BDC_FLAG_VER_SHIFT); if (PKTSUMNEEDED(pktbuf)) h->flags |= BDC_FLAG_SUM_NEEDED; h->priority = (PKTPRIO(pktbuf) & BDC_PRIORITY_MASK); h->flags2 = 0; h->dataOffset = 0; #endif /* BDC */ BDC_SET_IF_IDX(h, ifidx); } int dhd_prot_hdrpull(dhd_pub_t *dhd, int *ifidx, void *pktbuf, uchar *reorder_buf_info, uint *reorder_info_len) { #ifdef BDC struct bdc_header *h; #endif uint8 data_offset = 0; DHD_TRACE(("%s: Enter\n", __FUNCTION__)); #ifdef BDC if (reorder_info_len) *reorder_info_len = 0; /* Pop BDC header used to convey priority for buses that don't */ if (PKTLEN(dhd->osh, pktbuf) < BDC_HEADER_LEN) { DHD_ERROR(("%s: rx data too short (%d < %d)\n", __FUNCTION__, PKTLEN(dhd->osh, pktbuf), BDC_HEADER_LEN)); return BCME_ERROR; } h = (struct bdc_header *)PKTDATA(dhd->osh, pktbuf); #if defined(NDIS630) h->dataOffset = 0; #endif if (!ifidx) { /* for tx packet, skip the analysis */ data_offset = h->dataOffset; PKTPULL(dhd->osh, pktbuf, BDC_HEADER_LEN); goto exit; } if ((*ifidx = BDC_GET_IF_IDX(h)) >= DHD_MAX_IFS) { DHD_ERROR(("%s: rx data ifnum out of range (%d)\n", __FUNCTION__, *ifidx)); return BCME_ERROR; } if (((h->flags & BDC_FLAG_VER_MASK) >> BDC_FLAG_VER_SHIFT) != BDC_PROTO_VER) { DHD_ERROR(("%s: non-BDC packet received, flags = 0x%x\n", dhd_ifname(dhd, *ifidx), h->flags)); if (((h->flags & BDC_FLAG_VER_MASK) >> BDC_FLAG_VER_SHIFT) == BDC_PROTO_VER_1) h->dataOffset = 0; else return BCME_ERROR; } if (h->flags & BDC_FLAG_SUM_GOOD) { DHD_INFO(("%s: BDC packet received with good rx-csum, flags 0x%x\n", dhd_ifname(dhd, *ifidx), h->flags)); PKTSETSUMGOOD(pktbuf, TRUE); } PKTSETPRIO(pktbuf, (h->priority & BDC_PRIORITY_MASK)); data_offset = h->dataOffset; PKTPULL(dhd->osh, pktbuf, BDC_HEADER_LEN); #endif /* BDC */ #if !defined(NDIS630) if (PKTLEN(dhd->osh, pktbuf) < (uint32) (data_offset << 2)) { DHD_ERROR(("%s: rx data too short (%d < %d)\n", __FUNCTION__, PKTLEN(dhd->osh, pktbuf), (data_offset * 4))); return BCME_ERROR; } #endif #ifdef PROP_TXSTATUS if (dhd->wlfc_state && ((athost_wl_status_info_t*)dhd->wlfc_state)->proptxstatus_mode != WLFC_FCMODE_NONE && (!DHD_PKTTAG_PKTDIR(PKTTAG(pktbuf)))) { /* - parse txstatus only for packets that came from the firmware */ dhd_os_wlfc_block(dhd); dhd_wlfc_parse_header_info(dhd, pktbuf, (data_offset << 2), reorder_buf_info, reorder_info_len); ((athost_wl_status_info_t*)dhd->wlfc_state)->stats.dhd_hdrpulls++; dhd_os_wlfc_unblock(dhd); } #endif /* PROP_TXSTATUS */ exit: #if !defined(NDIS630) PKTPULL(dhd->osh, pktbuf, (data_offset << 2)); #endif return 0; } #if defined(PROP_TXSTATUS) void dhd_wlfc_trigger_pktcommit(dhd_pub_t *dhd) { if (dhd->wlfc_state && (((athost_wl_status_info_t*)dhd->wlfc_state)->proptxstatus_mode != WLFC_FCMODE_NONE)) { dhd_os_wlfc_block(dhd); dhd_wlfc_commit_packets(dhd->wlfc_state, (f_commitpkt_t)dhd_bus_txdata, (void *)dhd->bus); dhd_os_wlfc_unblock(dhd); } } #endif int dhd_prot_attach(dhd_pub_t *dhd) { dhd_prot_t *cdc; if (!(cdc = (dhd_prot_t *)DHD_OS_PREALLOC(dhd->osh, DHD_PREALLOC_PROT, sizeof(dhd_prot_t)))) { DHD_ERROR(("%s: kmalloc failed\n", __FUNCTION__)); goto fail; } memset(cdc, 0, sizeof(dhd_prot_t)); /* ensure that the msg buf directly follows the cdc msg struct */ if ((uintptr)(&cdc->msg + 1) != (uintptr)cdc->buf) { DHD_ERROR(("dhd_prot_t is not correctly defined\n")); goto fail; } dhd->prot = cdc; #ifdef BDC dhd->hdrlen += BDC_HEADER_LEN; #endif dhd->maxctl = WLC_IOCTL_MAXLEN + sizeof(cdc_ioctl_t) + ROUND_UP_MARGIN; return 0; fail: #ifndef CONFIG_DHD_USE_STATIC_BUF if (cdc != NULL) MFREE(dhd->osh, cdc, sizeof(dhd_prot_t)); #endif /* CONFIG_DHD_USE_STATIC_BUF */ return BCME_NOMEM; } /* ~NOTE~ What if another thread is waiting on the semaphore? Holding it? */ void dhd_prot_detach(dhd_pub_t *dhd) { #ifdef PROP_TXSTATUS dhd_wlfc_deinit(dhd); #endif #ifndef CONFIG_DHD_USE_STATIC_BUF MFREE(dhd->osh, dhd->prot, sizeof(dhd_prot_t)); #endif /* CONFIG_DHD_USE_STATIC_BUF */ dhd->prot = NULL; } void dhd_prot_dstats(dhd_pub_t *dhd) { /* No stats from dongle added yet, copy bus stats */ dhd->dstats.tx_packets = dhd->tx_packets; dhd->dstats.tx_errors = dhd->tx_errors; dhd->dstats.rx_packets = dhd->rx_packets; dhd->dstats.rx_errors = dhd->rx_errors; dhd->dstats.rx_dropped = dhd->rx_dropped; dhd->dstats.multicast = dhd->rx_multicast; return; } int dhd_prot_init(dhd_pub_t *dhd) { int ret = 0; wlc_rev_info_t revinfo; DHD_TRACE(("%s: Enter\n", __FUNCTION__)); /* Get the device rev info */ memset(&revinfo, 0, sizeof(revinfo)); ret = dhd_wl_ioctl_cmd(dhd, WLC_GET_REVINFO, &revinfo, sizeof(revinfo), FALSE, 0); if (ret < 0) goto done; #if defined(WL_CFG80211) if (dhd_download_fw_on_driverload) #endif /* defined(WL_CFG80211) */ ret = dhd_preinit_ioctls(dhd); #ifdef PROP_TXSTATUS ret = dhd_wlfc_init(dhd); #endif /* Always assumes wl for now */ dhd->iswl = TRUE; done: return ret; } void dhd_prot_stop(dhd_pub_t *dhd) { /* Nothing to do for CDC */ } static void dhd_get_hostreorder_pkts(void *osh, struct reorder_info *ptr, void **pkt, uint32 *pkt_count, void **pplast, uint8 start, uint8 end) { uint i; void *plast = NULL, *p; uint32 pkt_cnt = 0; if (ptr->pend_pkts == 0) { DHD_REORDER(("%s: no packets in reorder queue \n", __FUNCTION__)); *pplast = NULL; *pkt_count = 0; *pkt = NULL; return; } if (start == end) i = ptr->max_idx + 1; else { if (start > end) i = ((ptr->max_idx + 1) - start) + end; else i = end - start; } while (i) { p = (void *)(ptr->p[start]); ptr->p[start] = NULL; if (p != NULL) { if (plast == NULL) *pkt = p; else PKTSETNEXT(osh, plast, p); plast = p; pkt_cnt++; } i--; if (start++ == ptr->max_idx) start = 0; } *pplast = plast; *pkt_count = (uint32)pkt_cnt; } int dhd_process_pkt_reorder_info(dhd_pub_t *dhd, uchar *reorder_info_buf, uint reorder_info_len, void **pkt, uint32 *pkt_count) { uint8 flow_id, max_idx, cur_idx, exp_idx; struct reorder_info *ptr; uint8 flags; void *cur_pkt, *plast = NULL; uint32 cnt = 0; if (pkt == NULL) { if (pkt_count != NULL) *pkt_count = 0; return 0; } flow_id = reorder_info_buf[WLHOST_REORDERDATA_FLOWID_OFFSET]; flags = reorder_info_buf[WLHOST_REORDERDATA_FLAGS_OFFSET]; DHD_REORDER(("flow_id %d, flags 0x%02x, idx(%d, %d, %d)\n", flow_id, flags, reorder_info_buf[WLHOST_REORDERDATA_CURIDX_OFFSET], reorder_info_buf[WLHOST_REORDERDATA_EXPIDX_OFFSET], reorder_info_buf[WLHOST_REORDERDATA_MAXIDX_OFFSET])); /* validate flags and flow id */ if (flags == 0xFF) { DHD_ERROR(("%s: invalid flags...so ignore this packet\n", __FUNCTION__)); *pkt_count = 1; return 0; } cur_pkt = *pkt; *pkt = NULL; ptr = dhd->reorder_bufs[flow_id]; if (flags & WLHOST_REORDERDATA_DEL_FLOW) { uint32 buf_size = sizeof(struct reorder_info); DHD_REORDER(("%s: Flags indicating to delete a flow id %d\n", __FUNCTION__, flow_id)); if (ptr == NULL) { DHD_ERROR(("%s: received flags to cleanup, but no flow (%d) yet\n", __FUNCTION__, flow_id)); *pkt_count = 1; *pkt = cur_pkt; return 0; } dhd_get_hostreorder_pkts(dhd->osh, ptr, pkt, &cnt, &plast, ptr->exp_idx, ptr->exp_idx); /* set it to the last packet */ if (plast) { PKTSETNEXT(dhd->osh, plast, cur_pkt); cnt++; } else { if (cnt != 0) { DHD_ERROR(("%s: del flow: something fishy, pending packets %d\n", __FUNCTION__, cnt)); } *pkt = cur_pkt; cnt = 1; } buf_size += ((ptr->max_idx + 1) * sizeof(void *)); MFREE(dhd->osh, ptr, buf_size); dhd->reorder_bufs[flow_id] = NULL; *pkt_count = cnt; return 0; } /* all the other cases depend on the existance of the reorder struct for that flow id */ if (ptr == NULL) { uint32 buf_size_alloc = sizeof(reorder_info_t); max_idx = reorder_info_buf[WLHOST_REORDERDATA_MAXIDX_OFFSET]; buf_size_alloc += ((max_idx + 1) * sizeof(void*)); /* allocate space to hold the buffers, index etc */ DHD_REORDER(("%s: alloc buffer of size %d size, reorder info id %d, maxidx %d\n", __FUNCTION__, buf_size_alloc, flow_id, max_idx)); ptr = (struct reorder_info *)MALLOC(dhd->osh, buf_size_alloc); if (ptr == NULL) { DHD_ERROR(("%s: Malloc failed to alloc buffer\n", __FUNCTION__)); *pkt_count = 1; return 0; } bzero(ptr, buf_size_alloc); dhd->reorder_bufs[flow_id] = ptr; ptr->p = (void *)(ptr+1); ptr->max_idx = max_idx; } if (flags & WLHOST_REORDERDATA_NEW_HOLE) { DHD_REORDER(("%s: new hole, so cleanup pending buffers\n", __FUNCTION__)); if (ptr->pend_pkts) { dhd_get_hostreorder_pkts(dhd->osh, ptr, pkt, &cnt, &plast, ptr->exp_idx, ptr->exp_idx); ptr->pend_pkts = 0; } ptr->cur_idx = reorder_info_buf[WLHOST_REORDERDATA_CURIDX_OFFSET]; ptr->exp_idx = reorder_info_buf[WLHOST_REORDERDATA_EXPIDX_OFFSET]; ptr->max_idx = reorder_info_buf[WLHOST_REORDERDATA_MAXIDX_OFFSET]; ptr->p[ptr->cur_idx] = cur_pkt; ptr->pend_pkts++; *pkt_count = cnt; } else if (flags & WLHOST_REORDERDATA_CURIDX_VALID) { cur_idx = reorder_info_buf[WLHOST_REORDERDATA_CURIDX_OFFSET]; exp_idx = reorder_info_buf[WLHOST_REORDERDATA_EXPIDX_OFFSET]; if ((exp_idx == ptr->exp_idx) && (cur_idx != ptr->exp_idx)) { /* still in the current hole */ /* enqueue the current on the buffer chain */ if (ptr->p[cur_idx] != NULL) { DHD_REORDER(("%s: HOLE: ERROR buffer pending..free it\n", __FUNCTION__)); PKTFREE(dhd->osh, ptr->p[cur_idx], TRUE); ptr->p[cur_idx] = NULL; } ptr->p[cur_idx] = cur_pkt; ptr->pend_pkts++; ptr->cur_idx = cur_idx; DHD_REORDER(("%s: fill up a hole..pending packets is %d\n", __FUNCTION__, ptr->pend_pkts)); *pkt_count = 0; *pkt = NULL; } else if (ptr->exp_idx == cur_idx) { /* got the right one ..flush from cur to exp and update exp */ DHD_REORDER(("%s: got the right one now, cur_idx is %d\n", __FUNCTION__, cur_idx)); if (ptr->p[cur_idx] != NULL) { DHD_REORDER(("%s: Error buffer pending..free it\n", __FUNCTION__)); PKTFREE(dhd->osh, ptr->p[cur_idx], TRUE); ptr->p[cur_idx] = NULL; } ptr->p[cur_idx] = cur_pkt; ptr->pend_pkts++; ptr->cur_idx = cur_idx; ptr->exp_idx = exp_idx; dhd_get_hostreorder_pkts(dhd->osh, ptr, pkt, &cnt, &plast, cur_idx, exp_idx); ptr->pend_pkts -= (uint8)cnt; *pkt_count = cnt; DHD_REORDER(("%s: freeing up buffers %d, still pending %d\n", __FUNCTION__, cnt, ptr->pend_pkts)); } else { uint8 end_idx; bool flush_current = FALSE; /* both cur and exp are moved now .. */ DHD_REORDER(("%s:, flow %d, both moved, cur %d(%d), exp %d(%d)\n", __FUNCTION__, flow_id, ptr->cur_idx, cur_idx, ptr->exp_idx, exp_idx)); if (flags & WLHOST_REORDERDATA_FLUSH_ALL) end_idx = ptr->exp_idx; else end_idx = exp_idx; /* flush pkts first */ dhd_get_hostreorder_pkts(dhd->osh, ptr, pkt, &cnt, &plast, ptr->exp_idx, end_idx); if (cur_idx == ptr->max_idx) { if (exp_idx == 0) flush_current = TRUE; } else { if (exp_idx == cur_idx + 1) flush_current = TRUE; } if (flush_current) { if (plast) PKTSETNEXT(dhd->osh, plast, cur_pkt); else *pkt = cur_pkt; cnt++; } else { ptr->p[cur_idx] = cur_pkt; ptr->pend_pkts++; } ptr->exp_idx = exp_idx; ptr->cur_idx = cur_idx; *pkt_count = cnt; } } else { uint8 end_idx; /* no real packet but update to exp_seq...that means explicit window move */ exp_idx = reorder_info_buf[WLHOST_REORDERDATA_EXPIDX_OFFSET]; DHD_REORDER(("%s: move the window, cur_idx is %d, exp is %d, new exp is %d\n", __FUNCTION__, ptr->cur_idx, ptr->exp_idx, exp_idx)); if (flags & WLHOST_REORDERDATA_FLUSH_ALL) end_idx = ptr->exp_idx; else end_idx = exp_idx; dhd_get_hostreorder_pkts(dhd->osh, ptr, pkt, &cnt, &plast, ptr->exp_idx, end_idx); ptr->pend_pkts -= (uint8)cnt; if (plast) PKTSETNEXT(dhd->osh, plast, cur_pkt); else *pkt = cur_pkt; cnt++; *pkt_count = cnt; /* set the new expected idx */ ptr->exp_idx = exp_idx; } return 0; }