/** @file dbus.c * * Hides details of USB / SDIO / SPI interfaces and OS details. It is intended to shield details and * provide the caller with one common bus interface for all dongle devices. In practice, it is only * used for USB interfaces. DBUS is not a protocol, but an abstraction layer. * * Copyright (C) 1999-2016, 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: dbus.c 553311 2015-04-29 10:23:08Z $ */ #include "osl.h" #include "dbus.h" #include #include #include #include #ifdef PROP_TXSTATUS /* a form of flow control between host and dongle */ #include #endif #include #if defined(BCM_REQUEST_FW) #include #include #include #include #include #include #include #endif #if defined(BCM_REQUEST_FW) #ifndef VARS_MAX #define VARS_MAX 8192 #endif #endif #ifdef DBUS_USB_LOOPBACK extern bool is_loopback_pkt(void *buf); extern int matches_loopback_pkt(void *buf); #endif /** General info for all BUS types */ typedef struct dbus_irbq { dbus_irb_t *head; dbus_irb_t *tail; int cnt; } dbus_irbq_t; /** * This private structure dhd_bus_t is also declared in dbus_usb_linux.c. * All the fields must be consistent in both declarations. */ typedef struct dhd_bus { dbus_pub_t pub; /* MUST BE FIRST */ dhd_pub_t *dhd; void *cbarg; dbus_callbacks_t *cbs; /* callbacks to higher level, e.g. dhd_linux.c */ void *bus_info; dbus_intf_t *drvintf; /* callbacks to lower level, e.g. dbus_usb.c or dbus_usb_linux.c */ uint8 *fw; int fwlen; uint32 errmask; int rx_low_watermark; /* avoid rx overflow by filling rx with free IRBs */ int tx_low_watermark; bool txoff; bool txoverride; /* flow control related */ bool rxoff; bool tx_timer_ticking; dbus_irbq_t *rx_q; dbus_irbq_t *tx_q; uint8 *nvram; int nvram_len; uint8 *image; /* buffer for combine fw and nvram */ int image_len; uint8 *orig_fw; int origfw_len; int decomp_memsize; dbus_extdl_t extdl; int nvram_nontxt; #if defined(BCM_REQUEST_FW) void *firmware; void *nvfile; #endif char *fw_path; /* module_param: path to firmware image */ char *nv_path; /* module_param: path to nvram vars file */ } dhd_bus_t; struct exec_parms { union { /* Can consolidate same params, if need be, but this shows * group of parameters per function */ struct { dbus_irbq_t *q; dbus_irb_t *b; } qenq; struct { dbus_irbq_t *q; } qdeq; }; }; #define EXEC_RXLOCK(info, fn, a) \ info->drvintf->exec_rxlock(dhd_bus->bus_info, ((exec_cb_t)fn), ((struct exec_parms *) a)) #define EXEC_TXLOCK(info, fn, a) \ info->drvintf->exec_txlock(dhd_bus->bus_info, ((exec_cb_t)fn), ((struct exec_parms *) a)) /* * Callbacks common for all BUS */ static void dbus_if_send_irb_timeout(void *handle, dbus_irb_tx_t *txirb); static void dbus_if_send_irb_complete(void *handle, dbus_irb_tx_t *txirb, int status); static void dbus_if_recv_irb_complete(void *handle, dbus_irb_rx_t *rxirb, int status); static void dbus_if_errhandler(void *handle, int err); static void dbus_if_ctl_complete(void *handle, int type, int status); static void dbus_if_state_change(void *handle, int state); static void *dbus_if_pktget(void *handle, uint len, bool send); static void dbus_if_pktfree(void *handle, void *p, bool send); static struct dbus_irb *dbus_if_getirb(void *cbarg, bool send); static void dbus_if_rxerr_indicate(void *handle, bool on); void * dhd_dbus_probe_cb(void *arg, const char *desc, uint32 bustype, uint32 hdrlen); void dhd_dbus_disconnect_cb(void *arg); void dbus_detach(dhd_bus_t *pub); /** functions in this file that are called by lower DBUS levels, e.g. dbus_usb.c */ static dbus_intf_callbacks_t dbus_intf_cbs = { dbus_if_send_irb_timeout, dbus_if_send_irb_complete, dbus_if_recv_irb_complete, dbus_if_errhandler, dbus_if_ctl_complete, dbus_if_state_change, NULL, /* isr */ NULL, /* dpc */ NULL, /* watchdog */ dbus_if_pktget, dbus_if_pktfree, dbus_if_getirb, dbus_if_rxerr_indicate }; /* * Need global for probe() and disconnect() since * attach() is not called at probe and detach() * can be called inside disconnect() */ static dbus_intf_t *g_busintf = NULL; static probe_cb_t probe_cb = NULL; static disconnect_cb_t disconnect_cb = NULL; static void *probe_arg = NULL; static void *disc_arg = NULL; #if defined(BCM_REQUEST_FW) int8 *nonfwnvram = NULL; /* stand-alone multi-nvram given with driver load */ int nonfwnvramlen = 0; #endif /* #if defined(BCM_REQUEST_FW) */ static void* q_enq(dbus_irbq_t *q, dbus_irb_t *b); static void* q_enq_exec(struct exec_parms *args); static dbus_irb_t*q_deq(dbus_irbq_t *q); static void* q_deq_exec(struct exec_parms *args); static int dbus_tx_timer_init(dhd_bus_t *dhd_bus); static int dbus_tx_timer_start(dhd_bus_t *dhd_bus, uint timeout); static int dbus_tx_timer_stop(dhd_bus_t *dhd_bus); static int dbus_irbq_init(dhd_bus_t *dhd_bus, dbus_irbq_t *q, int nq, int size_irb); static int dbus_irbq_deinit(dhd_bus_t *dhd_bus, dbus_irbq_t *q, int size_irb); static int dbus_rxirbs_fill(dhd_bus_t *dhd_bus); static int dbus_send_irb(dbus_pub_t *pub, uint8 *buf, int len, void *pkt, void *info); static void dbus_disconnect(void *handle); static void *dbus_probe(void *arg, const char *desc, uint32 bustype, uint32 hdrlen); #if defined(BCM_REQUEST_FW) extern char * dngl_firmware; extern unsigned int dngl_fwlen; #ifndef EXTERNAL_FW_PATH static int dbus_get_nvram(dhd_bus_t *dhd_bus); static int dbus_jumbo_nvram(dhd_bus_t *dhd_bus); static int dbus_otp(dhd_bus_t *dhd_bus, uint16 *boardtype, uint16 *boardrev); static int dbus_select_nvram(dhd_bus_t *dhd_bus, int8 *jumbonvram, int jumbolen, uint16 boardtype, uint16 boardrev, int8 **nvram, int *nvram_len); #endif /* !EXTERNAL_FW_PATH */ extern int dbus_zlib_decomp(dhd_bus_t *dhd_bus); extern void *dbus_zlib_calloc(int num, int size); extern void dbus_zlib_free(void *ptr); #endif /* function */ void dbus_flowctrl_tx(void *dbi, bool on) { dhd_bus_t *dhd_bus = dbi; if (dhd_bus == NULL) return; DBUSTRACE(("%s on %d\n", __FUNCTION__, on)); if (dhd_bus->txoff == on) return; dhd_bus->txoff = on; if (dhd_bus->cbs && dhd_bus->cbs->txflowcontrol) dhd_bus->cbs->txflowcontrol(dhd_bus->cbarg, on); } /** * if lower level DBUS signaled a rx error, more free rx IRBs should be allocated or flow control * should kick in to make more free rx IRBs available. */ static void dbus_if_rxerr_indicate(void *handle, bool on) { dhd_bus_t *dhd_bus = (dhd_bus_t *) handle; DBUSTRACE(("%s, on %d\n", __FUNCTION__, on)); if (dhd_bus == NULL) return; if (dhd_bus->txoverride == on) return; dhd_bus->txoverride = on; /* flow control */ if (!on) dbus_rxirbs_fill(dhd_bus); } /** q_enq()/q_deq() are executed with protection via exec_rxlock()/exec_txlock() */ static void* q_enq(dbus_irbq_t *q, dbus_irb_t *b) { ASSERT(q->tail != b); ASSERT(b->next == NULL); b->next = NULL; if (q->tail) { q->tail->next = b; q->tail = b; } else q->head = q->tail = b; q->cnt++; return b; } static void* q_enq_exec(struct exec_parms *args) { return q_enq(args->qenq.q, args->qenq.b); } static dbus_irb_t* q_deq(dbus_irbq_t *q) { dbus_irb_t *b; b = q->head; if (b) { q->head = q->head->next; b->next = NULL; if (q->head == NULL) q->tail = q->head; q->cnt--; } return b; } static void* q_deq_exec(struct exec_parms *args) { return q_deq(args->qdeq.q); } /** * called during attach phase. Status @ Dec 2012: this function does nothing since for all of the * lower DBUS levels dhd_bus->drvintf->tx_timer_init is NULL. */ static int dbus_tx_timer_init(dhd_bus_t *dhd_bus) { if (dhd_bus && dhd_bus->drvintf && dhd_bus->drvintf->tx_timer_init) return dhd_bus->drvintf->tx_timer_init(dhd_bus->bus_info); else return DBUS_ERR; } static int dbus_tx_timer_start(dhd_bus_t *dhd_bus, uint timeout) { if (dhd_bus == NULL) return DBUS_ERR; if (dhd_bus->tx_timer_ticking) return DBUS_OK; if (dhd_bus->drvintf && dhd_bus->drvintf->tx_timer_start) { if (dhd_bus->drvintf->tx_timer_start(dhd_bus->bus_info, timeout) == DBUS_OK) { dhd_bus->tx_timer_ticking = TRUE; return DBUS_OK; } } return DBUS_ERR; } static int dbus_tx_timer_stop(dhd_bus_t *dhd_bus) { if (dhd_bus == NULL) return DBUS_ERR; if (!dhd_bus->tx_timer_ticking) return DBUS_OK; if (dhd_bus->drvintf && dhd_bus->drvintf->tx_timer_stop) { if (dhd_bus->drvintf->tx_timer_stop(dhd_bus->bus_info) == DBUS_OK) { dhd_bus->tx_timer_ticking = FALSE; return DBUS_OK; } } return DBUS_ERR; } /** called during attach phase. */ static int dbus_irbq_init(dhd_bus_t *dhd_bus, dbus_irbq_t *q, int nq, int size_irb) { int i; dbus_irb_t *irb; ASSERT(q); ASSERT(dhd_bus); for (i = 0; i < nq; i++) { /* MALLOC dbus_irb_tx or dbus_irb_rx, but cast to simple dbus_irb_t linkedlist */ irb = (dbus_irb_t *) MALLOC(dhd_bus->pub.osh, size_irb); if (irb == NULL) { ASSERT(irb); return DBUS_ERR; } bzero(irb, size_irb); /* q_enq() does not need to go through EXEC_xxLOCK() during init() */ q_enq(q, irb); } return DBUS_OK; } /** called during detach phase or when attach failed */ static int dbus_irbq_deinit(dhd_bus_t *dhd_bus, dbus_irbq_t *q, int size_irb) { dbus_irb_t *irb; ASSERT(q); ASSERT(dhd_bus); /* q_deq() does not need to go through EXEC_xxLOCK() * during deinit(); all callbacks are stopped by this time */ while ((irb = q_deq(q)) != NULL) { MFREE(dhd_bus->pub.osh, irb, size_irb); } if (q->cnt) DBUSERR(("deinit: q->cnt=%d > 0\n", q->cnt)); return DBUS_OK; } /** multiple code paths require the rx queue to be filled with more free IRBs */ static int dbus_rxirbs_fill(dhd_bus_t *dhd_bus) { int err = DBUS_OK; dbus_irb_rx_t *rxirb; struct exec_parms args; ASSERT(dhd_bus); if (dhd_bus->pub.busstate != DBUS_STATE_UP) { DBUSERR(("dbus_rxirbs_fill: DBUS not up \n")); return DBUS_ERR; } else if (!dhd_bus->drvintf || (dhd_bus->drvintf->recv_irb == NULL)) { /* Lower edge bus interface does not support recv_irb(). * No need to pre-submit IRBs in this case. */ return DBUS_ERR; } /* The dongle recv callback is freerunning without lock. So multiple callbacks(and this * refill) can run in parallel. While the rxoff condition is triggered outside, * below while loop has to check and abort posting more to avoid RPC rxq overflow. */ args.qdeq.q = dhd_bus->rx_q; while ((!dhd_bus->rxoff) && (rxirb = (EXEC_RXLOCK(dhd_bus, q_deq_exec, &args))) != NULL) { err = dhd_bus->drvintf->recv_irb(dhd_bus->bus_info, rxirb); if (err == DBUS_ERR_RXDROP || err == DBUS_ERR_RXFAIL) { /* Add the the free rxirb back to the queue * and wait till later */ bzero(rxirb, sizeof(dbus_irb_rx_t)); args.qenq.q = dhd_bus->rx_q; args.qenq.b = (dbus_irb_t *) rxirb; EXEC_RXLOCK(dhd_bus, q_enq_exec, &args); break; } else if (err != DBUS_OK) { int i = 0; while (i++ < 100) { DBUSERR(("%s :: memory leak for rxirb note?\n", __FUNCTION__)); } } } return err; } /* dbus_rxirbs_fill */ /** called when the DBUS interface state changed. */ void dbus_flowctrl_rx(dbus_pub_t *pub, bool on) { dhd_bus_t *dhd_bus = (dhd_bus_t *) pub; if (dhd_bus == NULL) return; DBUSTRACE(("%s\n", __FUNCTION__)); if (dhd_bus->rxoff == on) return; dhd_bus->rxoff = on; if (dhd_bus->pub.busstate == DBUS_STATE_UP) { if (!on) { /* post more irbs, resume rx if necessary */ dbus_rxirbs_fill(dhd_bus); if (dhd_bus && dhd_bus->drvintf->recv_resume) { dhd_bus->drvintf->recv_resume(dhd_bus->bus_info); } } else { /* ??? cancell posted irbs first */ if (dhd_bus && dhd_bus->drvintf->recv_stop) { dhd_bus->drvintf->recv_stop(dhd_bus->bus_info); } } } } /** * Several code paths in this file want to send a buffer to the dongle. This function handles both * sending of a buffer or a pkt. */ static int dbus_send_irb(dbus_pub_t *pub, uint8 *buf, int len, void *pkt, void *info) { dhd_bus_t *dhd_bus = (dhd_bus_t *) pub; int err = DBUS_OK; dbus_irb_tx_t *txirb = NULL; int txirb_pending; struct exec_parms args; if (dhd_bus == NULL) return DBUS_ERR; DBUSTRACE(("%s\n", __FUNCTION__)); if (dhd_bus->pub.busstate == DBUS_STATE_UP || dhd_bus->pub.busstate == DBUS_STATE_SLEEP) { args.qdeq.q = dhd_bus->tx_q; if (dhd_bus->drvintf) txirb = EXEC_TXLOCK(dhd_bus, q_deq_exec, &args); if (txirb == NULL) { DBUSERR(("Out of tx dbus_bufs\n")); return DBUS_ERR; } if (pkt != NULL) { txirb->pkt = pkt; txirb->buf = NULL; txirb->len = 0; } else if (buf != NULL) { txirb->pkt = NULL; txirb->buf = buf; txirb->len = len; } else { ASSERT(0); /* Should not happen */ } txirb->info = info; txirb->arg = NULL; txirb->retry_count = 0; if (dhd_bus->drvintf && dhd_bus->drvintf->send_irb) { /* call lower DBUS level send_irb function */ err = dhd_bus->drvintf->send_irb(dhd_bus->bus_info, txirb); if (err == DBUS_ERR_TXDROP) { /* tx fail and no completion routine to clean up, reclaim irb NOW */ DBUSERR(("%s: send_irb failed, status = %d\n", __FUNCTION__, err)); bzero(txirb, sizeof(dbus_irb_tx_t)); args.qenq.q = dhd_bus->tx_q; args.qenq.b = (dbus_irb_t *) txirb; EXEC_TXLOCK(dhd_bus, q_enq_exec, &args); } else { dbus_tx_timer_start(dhd_bus, DBUS_TX_TIMEOUT_INTERVAL); txirb_pending = dhd_bus->pub.ntxq - dhd_bus->tx_q->cnt; if (txirb_pending > (dhd_bus->tx_low_watermark * 3)) { dbus_flowctrl_tx(dhd_bus, TRUE); } } } } else { err = DBUS_ERR_TXFAIL; DBUSTRACE(("%s: bus down, send_irb failed\n", __FUNCTION__)); } return err; } /* dbus_send_irb */ #if defined(BCM_REQUEST_FW) /** * Before downloading a firmware image into the dongle, the validity of the image must be checked. */ static int check_file(osl_t *osh, unsigned char *headers) { struct trx_header *trx; int actual_len = -1; /* Extract trx header */ trx = (struct trx_header *)headers; if (ltoh32(trx->magic) != TRX_MAGIC) { printf("Error: trx bad hdr %x\n", ltoh32(trx->magic)); return -1; } headers += SIZEOF_TRX(trx); /* TRX V1: get firmware len */ /* TRX V2: get firmware len and DSG/CFG lengths */ if (ltoh32(trx->flag_version) & TRX_UNCOMP_IMAGE) { actual_len = ltoh32(trx->offsets[TRX_OFFSETS_DLFWLEN_IDX]) + SIZEOF_TRX(trx); #ifdef BCMTRXV2 if (ISTRX_V2(trx)) { actual_len += ltoh32(trx->offsets[TRX_OFFSETS_DSG_LEN_IDX]) + ltoh32(trx->offsets[TRX_OFFSETS_CFG_LEN_IDX]); } #endif return actual_len; } else { printf("compressed image\n"); } return -1; } #ifdef EXTERNAL_FW_PATH static int dbus_get_fw_nvram(dhd_bus_t *dhd_bus, char *pfw_path, char *pnv_path) { int bcmerror = -1, i; uint len, total_len; void *nv_image = NULL, *fw_image = NULL; char *nv_memblock = NULL, *fw_memblock = NULL; char *bufp; bool file_exists; uint8 nvram_words_pad = 0; uint memblock_size = 2048; uint8 *memptr; int actual_fwlen; struct trx_header *hdr; uint32 img_offset = 0; int offset = 0; /* For Get nvram */ file_exists = ((pnv_path != NULL) && (pnv_path[0] != '\0')); if (file_exists) { nv_image = dhd_os_open_image(pnv_path); if (nv_image == NULL) { printf("%s: Open nvram file failed %s\n", __FUNCTION__, pnv_path); goto err; } } nv_memblock = MALLOC(dhd_bus->pub.osh, MAX_NVRAMBUF_SIZE); if (nv_memblock == NULL) { DBUSERR(("%s: Failed to allocate memory %d bytes\n", __FUNCTION__, MAX_NVRAMBUF_SIZE)); goto err; } len = dhd_os_get_image_block(nv_memblock, MAX_NVRAMBUF_SIZE, nv_image); if (len > 0 && len < MAX_NVRAMBUF_SIZE) { bufp = (char *)nv_memblock; bufp[len] = 0; dhd_bus->nvram_len = process_nvram_vars(bufp, len); if (dhd_bus->nvram_len % 4) nvram_words_pad = 4 - dhd_bus->nvram_len % 4; } else { DBUSERR(("%s: error reading nvram file: %d\n", __FUNCTION__, len)); bcmerror = DBUS_ERR_NVRAM; goto err; } if (nv_image) dhd_os_close_image(nv_image); /* For Get first block of fw to calculate total_len */ file_exists = ((pfw_path != NULL) && (pfw_path[0] != '\0')); if (file_exists) { fw_image = dhd_os_open_image(pfw_path); if (fw_image == NULL) { printf("%s: Open fw file failed %s\n", __FUNCTION__, pfw_path); goto err; } } memptr = fw_memblock = MALLOC(dhd_bus->pub.osh, memblock_size); if (fw_memblock == NULL) { DBUSERR(("%s: Failed to allocate memory %d bytes\n", __FUNCTION__, memblock_size)); goto err; } len = dhd_os_get_image_block((char*)memptr, memblock_size, fw_image); if ((actual_fwlen = check_file(dhd_bus->pub.osh, memptr)) <= 0) { DBUSERR(("%s: bad firmware format!\n", __FUNCTION__)); goto err; } total_len = actual_fwlen + dhd_bus->nvram_len + nvram_words_pad; dhd_bus->image = MALLOC(dhd_bus->pub.osh, total_len); dhd_bus->image_len = total_len; if (dhd_bus->image == NULL) { DBUSERR(("%s: malloc failed!\n", __FUNCTION__)); goto err; } /* Step1: Copy trx header + firmwre */ memptr = fw_memblock; do { if (len < 0) { DBUSERR(("%s: dhd_os_get_image_block failed (%d)\n", __FUNCTION__, len)); bcmerror = BCME_ERROR; goto err; } bcopy(memptr, dhd_bus->image+offset, len); offset += len; } while ((len = dhd_os_get_image_block((char*)memptr, memblock_size, fw_image))); /* Step2: Copy NVRAM + pad */ hdr = (struct trx_header *)dhd_bus->image; img_offset = SIZEOF_TRX(hdr) + hdr->offsets[TRX_OFFSETS_DLFWLEN_IDX]; bcopy(nv_memblock, (uint8 *)(dhd_bus->image + img_offset), dhd_bus->nvram_len); img_offset += dhd_bus->nvram_len; if (nvram_words_pad) { bzero(&dhd_bus->image[img_offset], nvram_words_pad); img_offset += nvram_words_pad; } #ifdef BCMTRXV2 /* Step3: Copy DSG/CFG for V2 */ if (ISTRX_V2(hdr) && (hdr->offsets[TRX_OFFSETS_DSG_LEN_IDX] || hdr->offsets[TRX_OFFSETS_CFG_LEN_IDX])) { DBUSERR(("%s: fix me\n", __FUNCTION__)); } #endif /* BCMTRXV2 */ /* Step4: update TRX header for nvram size */ hdr = (struct trx_header *)dhd_bus->image; hdr->len = htol32(total_len); /* Pass the actual fw len */ hdr->offsets[TRX_OFFSETS_NVM_LEN_IDX] = htol32(dhd_bus->nvram_len + nvram_words_pad); /* Calculate CRC over header */ hdr->crc32 = hndcrc32((uint8 *)&hdr->flag_version, SIZEOF_TRX(hdr) - OFFSETOF(struct trx_header, flag_version), CRC32_INIT_VALUE); /* Calculate CRC over data */ for (i = SIZEOF_TRX(hdr); i < total_len; ++i) hdr->crc32 = hndcrc32((uint8 *)&dhd_bus->image[i], 1, hdr->crc32); hdr->crc32 = htol32(hdr->crc32); bcmerror = DBUS_OK; err: if (fw_memblock) MFREE(dhd_bus->pub.osh, fw_memblock, MAX_NVRAMBUF_SIZE); if (fw_image) dhd_os_close_image(fw_image); if (nv_memblock) MFREE(dhd_bus->pub.osh, nv_memblock, MAX_NVRAMBUF_SIZE); if (nv_image) dhd_os_close_image(nv_image); return bcmerror; } /** * during driver initialization ('attach') or after PnP 'resume', firmware needs to be loaded into * the dongle */ static int dbus_do_download(dhd_bus_t *dhd_bus, char *pfw_path, char *pnv_path) { int err = DBUS_OK; err = dbus_get_fw_nvram(dhd_bus, pfw_path, pnv_path); if (err) { DBUSERR(("dbus_do_download: fail to get nvram %d\n", err)); return err; } if (dhd_bus->drvintf->dlstart && dhd_bus->drvintf->dlrun) { err = dhd_bus->drvintf->dlstart(dhd_bus->bus_info, dhd_bus->image, dhd_bus->image_len); if (err == DBUS_OK) { err = dhd_bus->drvintf->dlrun(dhd_bus->bus_info); } } else err = DBUS_ERR; if (dhd_bus->image) { MFREE(dhd_bus->pub.osh, dhd_bus->image, dhd_bus->image_len); dhd_bus->image = NULL; dhd_bus->image_len = 0; } return err; } /* dbus_do_download */ #else /** * It is easy for the user to pass one jumbo nvram file to the driver than a set of smaller files. * The 'jumbo nvram' file format is essentially a set of nvram files. Before commencing firmware * download, the dongle needs to be probed so that the correct nvram contents within the jumbo nvram * file is selected. */ static int dbus_jumbo_nvram(dhd_bus_t *dhd_bus) { int8 *nvram = NULL; int nvram_len = 0; int ret = DBUS_OK; uint16 boardrev = 0xFFFF; uint16 boardtype = 0xFFFF; /* read the otp for boardrev & boardtype * if boardtype/rev are present in otp * select nvram data for that boardtype/rev */ dbus_otp(dhd_bus, &boardtype, &boardrev); ret = dbus_select_nvram(dhd_bus, dhd_bus->extdl.vars, dhd_bus->extdl.varslen, boardtype, boardrev, &nvram, &nvram_len); if (ret == DBUS_JUMBO_BAD_FORMAT) return DBUS_ERR_NVRAM; else if (ret == DBUS_JUMBO_NOMATCH && (boardtype != 0xFFFF || boardrev != 0xFFFF)) { DBUSERR(("No matching NVRAM for boardtype 0x%02x boardrev 0x%02x\n", boardtype, boardrev)); return DBUS_ERR_NVRAM; } dhd_bus->nvram = nvram; dhd_bus->nvram_len = nvram_len; return DBUS_OK; } /** before commencing fw download, the correct NVRAM image to download has to be picked */ static int dbus_get_nvram(dhd_bus_t *dhd_bus) { int len, i; struct trx_header *hdr; int actual_fwlen; uint32 img_offset = 0; dhd_bus->nvram_len = 0; if (dhd_bus->extdl.varslen) { if (DBUS_OK != dbus_jumbo_nvram(dhd_bus)) return DBUS_ERR_NVRAM; DBUSERR(("NVRAM %d bytes downloaded\n", dhd_bus->nvram_len)); } #if defined(BCM_REQUEST_FW) else if (nonfwnvram) { dhd_bus->nvram = nonfwnvram; dhd_bus->nvram_len = nonfwnvramlen; DBUSERR(("NVRAM %d bytes downloaded\n", dhd_bus->nvram_len)); } #endif if (dhd_bus->nvram) { uint8 nvram_words_pad = 0; /* Validate the format/length etc of the file */ if ((actual_fwlen = check_file(dhd_bus->pub.osh, dhd_bus->fw)) <= 0) { DBUSERR(("%s: bad firmware format!\n", __FUNCTION__)); return DBUS_ERR_NVRAM; } if (!dhd_bus->nvram_nontxt) { /* host supplied nvram could be in .txt format * with all the comments etc... */ dhd_bus->nvram_len = process_nvram_vars(dhd_bus->nvram, dhd_bus->nvram_len); } if (dhd_bus->nvram_len % 4) nvram_words_pad = 4 - dhd_bus->nvram_len % 4; len = actual_fwlen + dhd_bus->nvram_len + nvram_words_pad; dhd_bus->image = MALLOC(dhd_bus->pub.osh, len); dhd_bus->image_len = len; if (dhd_bus->image == NULL) { DBUSERR(("%s: malloc failed!\n", __FUNCTION__)); return DBUS_ERR_NVRAM; } hdr = (struct trx_header *)dhd_bus->fw; /* Step1: Copy trx header + firmwre */ img_offset = SIZEOF_TRX(hdr) + hdr->offsets[TRX_OFFSETS_DLFWLEN_IDX]; bcopy(dhd_bus->fw, dhd_bus->image, img_offset); /* Step2: Copy NVRAM + pad */ bcopy(dhd_bus->nvram, (uint8 *)(dhd_bus->image + img_offset), dhd_bus->nvram_len); img_offset += dhd_bus->nvram_len; if (nvram_words_pad) { bzero(&dhd_bus->image[img_offset], nvram_words_pad); img_offset += nvram_words_pad; } #ifdef BCMTRXV2 /* Step3: Copy DSG/CFG for V2 */ if (ISTRX_V2(hdr) && (hdr->offsets[TRX_OFFSETS_DSG_LEN_IDX] || hdr->offsets[TRX_OFFSETS_CFG_LEN_IDX])) { bcopy(dhd_bus->fw + SIZEOF_TRX(hdr) + hdr->offsets[TRX_OFFSETS_DLFWLEN_IDX] + hdr->offsets[TRX_OFFSETS_NVM_LEN_IDX], dhd_bus->image + img_offset, hdr->offsets[TRX_OFFSETS_DSG_LEN_IDX] + hdr->offsets[TRX_OFFSETS_CFG_LEN_IDX]); img_offset += hdr->offsets[TRX_OFFSETS_DSG_LEN_IDX] + hdr->offsets[TRX_OFFSETS_CFG_LEN_IDX]; } #endif /* BCMTRXV2 */ /* Step4: update TRX header for nvram size */ hdr = (struct trx_header *)dhd_bus->image; hdr->len = htol32(len); /* Pass the actual fw len */ hdr->offsets[TRX_OFFSETS_NVM_LEN_IDX] = htol32(dhd_bus->nvram_len + nvram_words_pad); /* Calculate CRC over header */ hdr->crc32 = hndcrc32((uint8 *)&hdr->flag_version, SIZEOF_TRX(hdr) - OFFSETOF(struct trx_header, flag_version), CRC32_INIT_VALUE); /* Calculate CRC over data */ for (i = SIZEOF_TRX(hdr); i < len; ++i) hdr->crc32 = hndcrc32((uint8 *)&dhd_bus->image[i], 1, hdr->crc32); hdr->crc32 = htol32(hdr->crc32); } else { dhd_bus->image = dhd_bus->fw; dhd_bus->image_len = (uint32)dhd_bus->fwlen; } return DBUS_OK; } /* dbus_get_nvram */ /** * during driver initialization ('attach') or after PnP 'resume', firmware needs to be loaded into * the dongle */ static int dbus_do_download(dhd_bus_t *dhd_bus) { int err = DBUS_OK; #ifndef BCM_REQUEST_FW int decomp_override = 0; #endif #ifdef BCM_REQUEST_FW uint16 boardrev = 0xFFFF, boardtype = 0xFFFF; int8 *temp_nvram; int temp_len; #endif #if defined(BCM_REQUEST_FW) dhd_bus->firmware = dbus_get_fw_nvfile(dhd_bus->pub.attrib.devid, dhd_bus->pub.attrib.chiprev, &dhd_bus->fw, &dhd_bus->fwlen, DBUS_FIRMWARE, 0, 0); if (!dhd_bus->firmware) return DBUS_ERR; #endif dhd_bus->image = dhd_bus->fw; dhd_bus->image_len = (uint32)dhd_bus->fwlen; #ifndef BCM_REQUEST_FW if (UNZIP_ENAB(dhd_bus) && !decomp_override) { err = dbus_zlib_decomp(dhd_bus); if (err) { DBUSERR(("dbus_attach: fw decompress fail %d\n", err)); return err; } } #endif #if defined(BCM_REQUEST_FW) /* check if firmware is appended with nvram file */ err = dbus_otp(dhd_bus, &boardtype, &boardrev); /* check if nvram is provided as separte file */ nonfwnvram = NULL; nonfwnvramlen = 0; dhd_bus->nvfile = dbus_get_fw_nvfile(dhd_bus->pub.attrib.devid, dhd_bus->pub.attrib.chiprev, (void *)&temp_nvram, &temp_len, DBUS_NVFILE, boardtype, boardrev); if (dhd_bus->nvfile) { int8 *tmp = MALLOC(dhd_bus->pub.osh, temp_len); if (tmp) { bcopy(temp_nvram, tmp, temp_len); nonfwnvram = tmp; nonfwnvramlen = temp_len; } else { err = DBUS_ERR; goto fail; } } #endif /* defined(BCM_REQUEST_FW) */ err = dbus_get_nvram(dhd_bus); if (err) { DBUSERR(("dbus_do_download: fail to get nvram %d\n", err)); return err; } if (dhd_bus->drvintf->dlstart && dhd_bus->drvintf->dlrun) { err = dhd_bus->drvintf->dlstart(dhd_bus->bus_info, dhd_bus->image, dhd_bus->image_len); if (err == DBUS_OK) err = dhd_bus->drvintf->dlrun(dhd_bus->bus_info); } else err = DBUS_ERR; if (dhd_bus->nvram) { MFREE(dhd_bus->pub.osh, dhd_bus->image, dhd_bus->image_len); dhd_bus->image = dhd_bus->fw; dhd_bus->image_len = (uint32)dhd_bus->fwlen; } #ifndef BCM_REQUEST_FW if (UNZIP_ENAB(dhd_bus) && (!decomp_override) && dhd_bus->orig_fw) { MFREE(dhd_bus->pub.osh, dhd_bus->fw, dhd_bus->decomp_memsize); dhd_bus->image = dhd_bus->fw = dhd_bus->orig_fw; dhd_bus->image_len = dhd_bus->fwlen = dhd_bus->origfw_len; } #endif #if defined(BCM_REQUEST_FW) fail: if (dhd_bus->firmware) { dbus_release_fw_nvfile(dhd_bus->firmware); dhd_bus->firmware = NULL; } if (dhd_bus->nvfile) { dbus_release_fw_nvfile(dhd_bus->nvfile); dhd_bus->nvfile = NULL; } if (nonfwnvram) { MFREE(dhd_bus->pub.osh, nonfwnvram, nonfwnvramlen); nonfwnvram = NULL; nonfwnvramlen = 0; } #endif return err; } /* dbus_do_download */ #endif /* EXTERNAL_FW_PATH */ #endif /** required for DBUS deregistration */ static void dbus_disconnect(void *handle) { DBUSTRACE(("%s\n", __FUNCTION__)); if (disconnect_cb) disconnect_cb(disc_arg); } /** * This function is called when the sent irb times out without a tx response status. * DBUS adds reliability by resending timed out IRBs DBUS_TX_RETRY_LIMIT times. */ static void dbus_if_send_irb_timeout(void *handle, dbus_irb_tx_t *txirb) { dhd_bus_t *dhd_bus = (dhd_bus_t *) handle; if ((dhd_bus == NULL) || (dhd_bus->drvintf == NULL) || (txirb == NULL)) { return; } DBUSTRACE(("%s\n", __FUNCTION__)); return; } /* dbus_if_send_irb_timeout */ /** * When lower DBUS level signals that a send IRB completed, either successful or not, the higher * level (e.g. dhd_linux.c) has to be notified, and transmit flow control has to be evaluated. */ static void BCMFASTPATH dbus_if_send_irb_complete(void *handle, dbus_irb_tx_t *txirb, int status) { dhd_bus_t *dhd_bus = (dhd_bus_t *) handle; int txirb_pending; struct exec_parms args; void *pktinfo; if ((dhd_bus == NULL) || (txirb == NULL)) { return; } DBUSTRACE(("%s: status = %d\n", __FUNCTION__, status)); dbus_tx_timer_stop(dhd_bus); /* re-queue BEFORE calling send_complete which will assume that this irb is now available. */ pktinfo = txirb->info; bzero(txirb, sizeof(dbus_irb_tx_t)); args.qenq.q = dhd_bus->tx_q; args.qenq.b = (dbus_irb_t *) txirb; EXEC_TXLOCK(dhd_bus, q_enq_exec, &args); if (dhd_bus->pub.busstate != DBUS_STATE_DOWN) { if ((status == DBUS_OK) || (status == DBUS_ERR_NODEVICE)) { if (dhd_bus->cbs && dhd_bus->cbs->send_complete) dhd_bus->cbs->send_complete(dhd_bus->cbarg, pktinfo, status); if (status == DBUS_OK) { txirb_pending = dhd_bus->pub.ntxq - dhd_bus->tx_q->cnt; if (txirb_pending) dbus_tx_timer_start(dhd_bus, DBUS_TX_TIMEOUT_INTERVAL); if ((txirb_pending < dhd_bus->tx_low_watermark) && dhd_bus->txoff && !dhd_bus->txoverride) { dbus_flowctrl_tx(dhd_bus, OFF); } } } else { DBUSERR(("%s: %d WARNING freeing orphan pkt %p\n", __FUNCTION__, __LINE__, pktinfo)); #if defined(BCM_RPC_NOCOPY) || defined(BCM_RPC_TXNOCOPY) || defined(BCM_RPC_TOC) if (pktinfo) if (dhd_bus->cbs && dhd_bus->cbs->send_complete) dhd_bus->cbs->send_complete(dhd_bus->cbarg, pktinfo, status); #else dbus_if_pktfree(dhd_bus, (void*)pktinfo, TRUE); #endif /* defined(BCM_RPC_NOCOPY) || defined(BCM_RPC_TXNOCOPY) || defined(BCM_RPC_TOC) */ } } else { DBUSERR(("%s: %d WARNING freeing orphan pkt %p\n", __FUNCTION__, __LINE__, pktinfo)); #if defined(BCM_RPC_NOCOPY) || defined(BCM_RPC_TXNOCOPY) || defined(BCM_RPC_TOC) if (pktinfo) if (dhd_bus->cbs && dhd_bus->cbs->send_complete) dhd_bus->cbs->send_complete(dhd_bus->cbarg, pktinfo, status); #else dbus_if_pktfree(dhd_bus, (void*)pktinfo, TRUE); #endif /* defined(BCM_RPC_NOCOPY) || defined(BCM_RPC_TXNOCOPY) defined(BCM_RPC_TOC) */ } } /* dbus_if_send_irb_complete */ /** * When lower DBUS level signals that a receive IRB completed, either successful or not, the higher * level (e.g. dhd_linux.c) has to be notified, and fresh free receive IRBs may have to be given * to lower levels. */ static void BCMFASTPATH dbus_if_recv_irb_complete(void *handle, dbus_irb_rx_t *rxirb, int status) { dhd_bus_t *dhd_bus = (dhd_bus_t *) handle; int rxirb_pending; struct exec_parms args; if ((dhd_bus == NULL) || (rxirb == NULL)) { return; } DBUSTRACE(("%s\n", __FUNCTION__)); if (dhd_bus->pub.busstate != DBUS_STATE_DOWN && dhd_bus->pub.busstate != DBUS_STATE_SLEEP) { if (status == DBUS_OK) { if ((rxirb->buf != NULL) && (rxirb->actual_len > 0)) { #ifdef DBUS_USB_LOOPBACK if (is_loopback_pkt(rxirb->buf)) { matches_loopback_pkt(rxirb->buf); } else #endif if (dhd_bus->cbs && dhd_bus->cbs->recv_buf) { dhd_bus->cbs->recv_buf(dhd_bus->cbarg, rxirb->buf, rxirb->actual_len); } } else if (rxirb->pkt != NULL) { if (dhd_bus->cbs && dhd_bus->cbs->recv_pkt) dhd_bus->cbs->recv_pkt(dhd_bus->cbarg, rxirb->pkt); } else { ASSERT(0); /* Should not happen */ } rxirb_pending = dhd_bus->pub.nrxq - dhd_bus->rx_q->cnt - 1; if ((rxirb_pending <= dhd_bus->rx_low_watermark) && !dhd_bus->rxoff) { DBUSTRACE(("Low watermark so submit more %d <= %d \n", dhd_bus->rx_low_watermark, rxirb_pending)); dbus_rxirbs_fill(dhd_bus); } else if (dhd_bus->rxoff) DBUSTRACE(("rx flow controlled. not filling more. cut_rxq=%d\n", dhd_bus->rx_q->cnt)); } else if (status == DBUS_ERR_NODEVICE) { DBUSERR(("%s: %d status = %d, buf %p\n", __FUNCTION__, __LINE__, status, rxirb->buf)); #if defined(BCM_RPC_NOCOPY) || defined(BCM_RPC_RXNOCOPY) if (rxirb->buf) { PKTFRMNATIVE(dhd_bus->pub.osh, rxirb->buf); PKTFREE(dhd_bus->pub.osh, rxirb->buf, FALSE); } #endif /* BCM_RPC_NOCOPY || BCM_RPC_TXNOCOPY || BCM_RPC_TOC */ } else { if (status != DBUS_ERR_RXZLP) DBUSERR(("%s: %d status = %d, buf %p\n", __FUNCTION__, __LINE__, status, rxirb->buf)); #if defined(BCM_RPC_NOCOPY) || defined(BCM_RPC_RXNOCOPY) if (rxirb->buf) { PKTFRMNATIVE(dhd_bus->pub.osh, rxirb->buf); PKTFREE(dhd_bus->pub.osh, rxirb->buf, FALSE); } #endif /* BCM_RPC_NOCOPY || BCM_RPC_TXNOCOPY || BCM_RPC_TOC */ } } else { DBUSTRACE(("%s: DBUS down, ignoring recv callback. buf %p\n", __FUNCTION__, rxirb->buf)); #if defined(BCM_RPC_NOCOPY) || defined(BCM_RPC_RXNOCOPY) if (rxirb->buf) { PKTFRMNATIVE(dhd_bus->pub.osh, rxirb->buf); PKTFREE(dhd_bus->pub.osh, rxirb->buf, FALSE); } #endif /* BCM_RPC_NOCOPY || BCM_RPC_TXNOCOPY || BCM_RPC_TOC */ } if (dhd_bus->rx_q != NULL) { bzero(rxirb, sizeof(dbus_irb_rx_t)); args.qenq.q = dhd_bus->rx_q; args.qenq.b = (dbus_irb_t *) rxirb; EXEC_RXLOCK(dhd_bus, q_enq_exec, &args); } else MFREE(dhd_bus->pub.osh, rxirb, sizeof(dbus_irb_tx_t)); } /* dbus_if_recv_irb_complete */ /** * Accumulate errors signaled by lower DBUS levels and signal them to higher (e.g. dhd_linux.c) * level. */ static void dbus_if_errhandler(void *handle, int err) { dhd_bus_t *dhd_bus = handle; uint32 mask = 0; if (dhd_bus == NULL) return; switch (err) { case DBUS_ERR_TXFAIL: dhd_bus->pub.stats.tx_errors++; mask |= ERR_CBMASK_TXFAIL; break; case DBUS_ERR_TXDROP: dhd_bus->pub.stats.tx_dropped++; mask |= ERR_CBMASK_TXFAIL; break; case DBUS_ERR_RXFAIL: dhd_bus->pub.stats.rx_errors++; mask |= ERR_CBMASK_RXFAIL; break; case DBUS_ERR_RXDROP: dhd_bus->pub.stats.rx_dropped++; mask |= ERR_CBMASK_RXFAIL; break; default: break; } if (dhd_bus->cbs && dhd_bus->cbs->errhandler && (dhd_bus->errmask & mask)) dhd_bus->cbs->errhandler(dhd_bus->cbarg, err); } /** * When lower DBUS level signals control IRB completed, higher level (e.g. dhd_linux.c) has to be * notified. */ static void dbus_if_ctl_complete(void *handle, int type, int status) { dhd_bus_t *dhd_bus = (dhd_bus_t *) handle; DBUSTRACE(("%s\n", __FUNCTION__)); if (dhd_bus == NULL) { DBUSERR(("%s: dhd_bus is NULL\n", __FUNCTION__)); return; } if (dhd_bus->pub.busstate != DBUS_STATE_DOWN) { if (dhd_bus->cbs && dhd_bus->cbs->ctl_complete) dhd_bus->cbs->ctl_complete(dhd_bus->cbarg, type, status); } } /** * Rx related functionality (flow control, posting of free IRBs to rx queue) is dependent upon the * bus state. When lower DBUS level signals a change in the interface state, take appropriate action * and forward the signaling to the higher (e.g. dhd_linux.c) level. */ static void dbus_if_state_change(void *handle, int state) { dhd_bus_t *dhd_bus = (dhd_bus_t *) handle; int old_state; if (dhd_bus == NULL) return; if (dhd_bus->pub.busstate == state) return; old_state = dhd_bus->pub.busstate; if (state == DBUS_STATE_DISCONNECT) { DBUSERR(("DBUS disconnected\n")); } /* Ignore USB SUSPEND while not up yet */ if (state == DBUS_STATE_SLEEP && old_state != DBUS_STATE_UP) return; DBUSTRACE(("dbus state change from %d to to %d\n", old_state, state)); /* Don't update state if it's PnP firmware re-download */ if (state != DBUS_STATE_PNP_FWDL) dhd_bus->pub.busstate = state; else dbus_flowctrl_rx(handle, FALSE); if (state == DBUS_STATE_SLEEP) dbus_flowctrl_rx(handle, TRUE); if (state == DBUS_STATE_UP) { dbus_rxirbs_fill(dhd_bus); dbus_flowctrl_rx(handle, FALSE); } if (dhd_bus->cbs && dhd_bus->cbs->state_change) dhd_bus->cbs->state_change(dhd_bus->cbarg, state); } /** Forward request for packet from lower DBUS layer to higher layer (e.g. dhd_linux.c) */ static void * dbus_if_pktget(void *handle, uint len, bool send) { dhd_bus_t *dhd_bus = (dhd_bus_t *) handle; void *p = NULL; if (dhd_bus == NULL) return NULL; if (dhd_bus->cbs && dhd_bus->cbs->pktget) p = dhd_bus->cbs->pktget(dhd_bus->cbarg, len, send); else ASSERT(0); return p; } /** Forward request to free packet from lower DBUS layer to higher layer (e.g. dhd_linux.c) */ static void dbus_if_pktfree(void *handle, void *p, bool send) { dhd_bus_t *dhd_bus = (dhd_bus_t *) handle; if (dhd_bus == NULL) return; if (dhd_bus->cbs && dhd_bus->cbs->pktfree) dhd_bus->cbs->pktfree(dhd_bus->cbarg, p, send); else ASSERT(0); } /** Lower DBUS level requests either a send or receive IRB */ static struct dbus_irb* dbus_if_getirb(void *cbarg, bool send) { dhd_bus_t *dhd_bus = (dhd_bus_t *) cbarg; struct exec_parms args; struct dbus_irb *irb; if ((dhd_bus == NULL) || (dhd_bus->pub.busstate != DBUS_STATE_UP)) return NULL; if (send == TRUE) { args.qdeq.q = dhd_bus->tx_q; irb = EXEC_TXLOCK(dhd_bus, q_deq_exec, &args); } else { args.qdeq.q = dhd_bus->rx_q; irb = EXEC_RXLOCK(dhd_bus, q_deq_exec, &args); } return irb; } /** * Called as part of DBUS bus registration. Calls back into higher level (e.g. dhd_linux.c) probe * function. */ static void * dbus_probe(void *arg, const char *desc, uint32 bustype, uint32 hdrlen) { DBUSTRACE(("%s\n", __FUNCTION__)); if (probe_cb) { disc_arg = probe_cb(probe_arg, desc, bustype, hdrlen); return disc_arg; } return (void *)DBUS_ERR; } /** * As part of initialization, higher level (e.g. dhd_linux.c) requests DBUS to prepare for * action. */ int dhd_bus_register(void) { int err; DBUSTRACE(("%s: Enter\n", __FUNCTION__)); probe_cb = dhd_dbus_probe_cb; disconnect_cb = dhd_dbus_disconnect_cb; probe_arg = NULL; err = dbus_bus_register(0xa5c, 0x48f, dbus_probe, /* call lower DBUS level register function */ dbus_disconnect, NULL, &g_busintf, NULL, NULL); /* Device not detected */ if (err == DBUS_ERR_NODEVICE) err = DBUS_OK; return err; } dhd_pub_t *g_pub = NULL; void dhd_bus_unregister(void) { int ret; DBUSTRACE(("%s\n", __FUNCTION__)); DHD_MUTEX_LOCK(); if (g_pub) { g_pub->dhd_remove = TRUE; if (!g_pub->bus) { dhd_dbus_disconnect_cb(g_pub->bus); } } probe_cb = NULL; DHD_MUTEX_UNLOCK(); ret = dbus_bus_deregister(); disconnect_cb = NULL; probe_arg = NULL; } /** As part of initialization, data structures have to be allocated and initialized */ dhd_bus_t * dbus_attach(osl_t *osh, int rxsize, int nrxq, int ntxq, dhd_pub_t *pub, dbus_callbacks_t *cbs, dbus_extdl_t *extdl, struct shared_info *sh) { dhd_bus_t *dhd_bus; int err; if ((g_busintf == NULL) || (g_busintf->attach == NULL) || (cbs == NULL)) return NULL; DBUSTRACE(("%s\n", __FUNCTION__)); if ((nrxq <= 0) || (ntxq <= 0)) return NULL; dhd_bus = MALLOC(osh, sizeof(dhd_bus_t)); if (dhd_bus == NULL) { DBUSERR(("%s: malloc failed %d\n", __FUNCTION__, sizeof(dhd_bus_t))); return NULL; } bzero(dhd_bus, sizeof(dhd_bus_t)); /* BUS-specific driver interface (at a lower DBUS level) */ dhd_bus->drvintf = g_busintf; dhd_bus->cbarg = pub; dhd_bus->cbs = cbs; dhd_bus->pub.sh = sh; dhd_bus->pub.osh = osh; dhd_bus->pub.rxsize = rxsize; dhd_bus->pub.nrxq = nrxq; dhd_bus->rx_low_watermark = nrxq / 2; /* keep enough posted rx urbs */ dhd_bus->pub.ntxq = ntxq; dhd_bus->tx_low_watermark = ntxq / 4; /* flow control when too many tx urbs posted */ dhd_bus->tx_q = MALLOC(osh, sizeof(dbus_irbq_t)); if (dhd_bus->tx_q == NULL) goto error; else { bzero(dhd_bus->tx_q, sizeof(dbus_irbq_t)); err = dbus_irbq_init(dhd_bus, dhd_bus->tx_q, ntxq, sizeof(dbus_irb_tx_t)); if (err != DBUS_OK) goto error; } dhd_bus->rx_q = MALLOC(osh, sizeof(dbus_irbq_t)); if (dhd_bus->rx_q == NULL) goto error; else { bzero(dhd_bus->rx_q, sizeof(dbus_irbq_t)); err = dbus_irbq_init(dhd_bus, dhd_bus->rx_q, nrxq, sizeof(dbus_irb_rx_t)); if (err != DBUS_OK) goto error; } dhd_bus->bus_info = (void *)g_busintf->attach(&dhd_bus->pub, dhd_bus, &dbus_intf_cbs); if (dhd_bus->bus_info == NULL) goto error; dbus_tx_timer_init(dhd_bus); #if defined(BCM_REQUEST_FW) /* Need to copy external image for re-download */ if (extdl && extdl->fw && (extdl->fwlen > 0)) { dhd_bus->extdl.fw = MALLOC(osh, extdl->fwlen); if (dhd_bus->extdl.fw) { bcopy(extdl->fw, dhd_bus->extdl.fw, extdl->fwlen); dhd_bus->extdl.fwlen = extdl->fwlen; } } if (extdl && extdl->vars && (extdl->varslen > 0)) { dhd_bus->extdl.vars = MALLOC(osh, extdl->varslen); if (dhd_bus->extdl.vars) { bcopy(extdl->vars, dhd_bus->extdl.vars, extdl->varslen); dhd_bus->extdl.varslen = extdl->varslen; } } #endif return (dhd_bus_t *)dhd_bus; error: DBUSERR(("%s: Failed\n", __FUNCTION__)); dbus_detach(dhd_bus); return NULL; } /* dbus_attach */ void dbus_detach(dhd_bus_t *pub) { dhd_bus_t *dhd_bus = (dhd_bus_t *) pub; osl_t *osh; DBUSTRACE(("%s\n", __FUNCTION__)); if (dhd_bus == NULL) return; dbus_tx_timer_stop(dhd_bus); osh = pub->pub.osh; if (dhd_bus->drvintf && dhd_bus->drvintf->detach) dhd_bus->drvintf->detach((dbus_pub_t *)dhd_bus, dhd_bus->bus_info); if (dhd_bus->tx_q) { dbus_irbq_deinit(dhd_bus, dhd_bus->tx_q, sizeof(dbus_irb_tx_t)); MFREE(osh, dhd_bus->tx_q, sizeof(dbus_irbq_t)); dhd_bus->tx_q = NULL; } if (dhd_bus->rx_q) { dbus_irbq_deinit(dhd_bus, dhd_bus->rx_q, sizeof(dbus_irb_rx_t)); MFREE(osh, dhd_bus->rx_q, sizeof(dbus_irbq_t)); dhd_bus->rx_q = NULL; } if (dhd_bus->extdl.fw && (dhd_bus->extdl.fwlen > 0)) { MFREE(osh, dhd_bus->extdl.fw, dhd_bus->extdl.fwlen); dhd_bus->extdl.fw = NULL; dhd_bus->extdl.fwlen = 0; } if (dhd_bus->extdl.vars && (dhd_bus->extdl.varslen > 0)) { MFREE(osh, dhd_bus->extdl.vars, dhd_bus->extdl.varslen); dhd_bus->extdl.vars = NULL; dhd_bus->extdl.varslen = 0; } MFREE(osh, dhd_bus, sizeof(dhd_bus_t)); } /* dbus_detach */ int dbus_dlneeded(dhd_bus_t *pub) { dhd_bus_t *dhd_bus = (dhd_bus_t *) pub; int dlneeded = DBUS_ERR; if (!dhd_bus) { DBUSERR(("%s: dhd_bus is NULL\n", __FUNCTION__)); return DBUS_ERR; } DBUSTRACE(("%s: state %d\n", __FUNCTION__, dhd_bus->pub.busstate)); if (dhd_bus->drvintf->dlneeded) { dlneeded = dhd_bus->drvintf->dlneeded(dhd_bus->bus_info); } printf("%s: dlneeded=%d\n", __FUNCTION__, dlneeded); /* dlneeded > 0: need to download * dlneeded = 0: downloaded * dlneeded < 0: bus error*/ return dlneeded; } #if defined(BCM_REQUEST_FW) int dbus_download_firmware(dhd_bus_t *pub, char *pfw_path, char *pnv_path) { dhd_bus_t *dhd_bus = (dhd_bus_t *) pub; int err = DBUS_OK; if (!dhd_bus) { DBUSERR(("%s: dhd_bus is NULL\n", __FUNCTION__)); return DBUS_ERR; } DBUSTRACE(("%s: state %d\n", __FUNCTION__, dhd_bus->pub.busstate)); dhd_bus->pub.busstate = DBUS_STATE_DL_PENDING; #ifdef EXTERNAL_FW_PATH err = dbus_do_download(dhd_bus, pfw_path, pnv_path); #else err = dbus_do_download(dhd_bus); #endif /* EXTERNAL_FW_PATH */ if (err == DBUS_OK) { dhd_bus->pub.busstate = DBUS_STATE_DL_DONE; } else { DBUSERR(("%s: download failed (%d)\n", __FUNCTION__, err)); } return err; } #endif /** * higher layer requests us to 'up' the interface to the dongle. Prerequisite is that firmware (not * bootloader) must be active in the dongle. */ int dbus_up(struct dhd_bus *pub) { dhd_bus_t *dhd_bus = (dhd_bus_t *) pub; int err = DBUS_OK; DBUSTRACE(("%s\n", __FUNCTION__)); if (dhd_bus == NULL) { DBUSERR(("%s: dhd_bus is NULL\n", __FUNCTION__)); return DBUS_ERR; } if ((dhd_bus->pub.busstate == DBUS_STATE_DL_DONE) || (dhd_bus->pub.busstate == DBUS_STATE_DOWN) || (dhd_bus->pub.busstate == DBUS_STATE_SLEEP)) { if (dhd_bus->drvintf && dhd_bus->drvintf->up) { err = dhd_bus->drvintf->up(dhd_bus->bus_info); if (err == DBUS_OK) { dbus_rxirbs_fill(dhd_bus); } } } else err = DBUS_ERR; return err; } /** higher layer requests us to 'down' the interface to the dongle. */ int dbus_down(dbus_pub_t *pub) { dhd_bus_t *dhd_bus = (dhd_bus_t *) pub; DBUSTRACE(("%s\n", __FUNCTION__)); if (dhd_bus == NULL) return DBUS_ERR; dbus_tx_timer_stop(dhd_bus); if (dhd_bus->pub.busstate == DBUS_STATE_UP || dhd_bus->pub.busstate == DBUS_STATE_SLEEP) { if (dhd_bus->drvintf && dhd_bus->drvintf->down) return dhd_bus->drvintf->down(dhd_bus->bus_info); } return DBUS_ERR; } int dbus_shutdown(dbus_pub_t *pub) { dhd_bus_t *dhd_bus = (dhd_bus_t *) pub; DBUSTRACE(("%s\n", __FUNCTION__)); if (dhd_bus == NULL) return DBUS_ERR; if (dhd_bus->drvintf && dhd_bus->drvintf->shutdown) return dhd_bus->drvintf->shutdown(dhd_bus->bus_info); return DBUS_OK; } int dbus_stop(struct dhd_bus *pub) { dhd_bus_t *dhd_bus = (dhd_bus_t *) pub; DBUSTRACE(("%s\n", __FUNCTION__)); if (dhd_bus == NULL) return DBUS_ERR; if (dhd_bus->pub.busstate == DBUS_STATE_UP || dhd_bus->pub.busstate == DBUS_STATE_SLEEP) { if (dhd_bus->drvintf && dhd_bus->drvintf->stop) return dhd_bus->drvintf->stop(dhd_bus->bus_info); } return DBUS_ERR; } int dbus_send_txdata(dbus_pub_t *dbus, void *pktbuf) { return dbus_send_pkt(dbus, pktbuf, pktbuf /* pktinfo */); } int dbus_send_buf(dbus_pub_t *pub, uint8 *buf, int len, void *info) { return dbus_send_irb(pub, buf, len, NULL, info); } int dbus_send_pkt(dbus_pub_t *pub, void *pkt, void *info) { return dbus_send_irb(pub, NULL, 0, pkt, info); } int dbus_send_ctl(struct dhd_bus *pub, uint8 *buf, int len) { dhd_bus_t *dhd_bus = (dhd_bus_t *) pub; if (dhd_bus == NULL) { DBUSERR(("%s: dhd_bus is NULL\n", __FUNCTION__)); return DBUS_ERR; } if (dhd_bus->pub.busstate == DBUS_STATE_UP || dhd_bus->pub.busstate == DBUS_STATE_SLEEP) { if (dhd_bus->drvintf && dhd_bus->drvintf->send_ctl) return dhd_bus->drvintf->send_ctl(dhd_bus->bus_info, buf, len); } else { DBUSERR(("%s: bustate=%d\n", __FUNCTION__, dhd_bus->pub.busstate)); } return DBUS_ERR; } int dbus_recv_ctl(struct dhd_bus *pub, uint8 *buf, int len) { dhd_bus_t *dhd_bus = (dhd_bus_t *) pub; if ((dhd_bus == NULL) || (buf == NULL)) return DBUS_ERR; if (dhd_bus->pub.busstate == DBUS_STATE_UP || dhd_bus->pub.busstate == DBUS_STATE_SLEEP) { if (dhd_bus->drvintf && dhd_bus->drvintf->recv_ctl) return dhd_bus->drvintf->recv_ctl(dhd_bus->bus_info, buf, len); } return DBUS_ERR; } /** Only called via RPC (Dec 2012) */ int dbus_recv_bulk(dbus_pub_t *pub, uint32 ep_idx) { dhd_bus_t *dhd_bus = (dhd_bus_t *) pub; dbus_irb_rx_t *rxirb; struct exec_parms args; int status; if (dhd_bus == NULL) return DBUS_ERR; args.qdeq.q = dhd_bus->rx_q; if (dhd_bus->pub.busstate == DBUS_STATE_UP) { if (dhd_bus->drvintf && dhd_bus->drvintf->recv_irb_from_ep) { if ((rxirb = (EXEC_RXLOCK(dhd_bus, q_deq_exec, &args))) != NULL) { status = dhd_bus->drvintf->recv_irb_from_ep(dhd_bus->bus_info, rxirb, ep_idx); if (status == DBUS_ERR_RXDROP) { bzero(rxirb, sizeof(dbus_irb_rx_t)); args.qenq.q = dhd_bus->rx_q; args.qenq.b = (dbus_irb_t *) rxirb; EXEC_RXLOCK(dhd_bus, q_enq_exec, &args); } } } } return DBUS_ERR; } /** only called by dhd_cdc.c (Dec 2012) */ int dbus_poll_intr(dbus_pub_t *pub) { dhd_bus_t *dhd_bus = (dhd_bus_t *) pub; int status = DBUS_ERR; if (dhd_bus == NULL) return DBUS_ERR; if (dhd_bus->pub.busstate == DBUS_STATE_UP) { if (dhd_bus->drvintf && dhd_bus->drvintf->recv_irb_from_ep) { status = dhd_bus->drvintf->recv_irb_from_ep(dhd_bus->bus_info, NULL, 0xff); } } return status; } /** called by nobody (Dec 2012) */ void * dbus_pktget(dbus_pub_t *pub, int len) { dhd_bus_t *dhd_bus = (dhd_bus_t *) pub; if ((dhd_bus == NULL) || (len < 0)) return NULL; return PKTGET(dhd_bus->pub.osh, len, TRUE); } /** called by nobody (Dec 2012) */ void dbus_pktfree(dbus_pub_t *pub, void* pkt) { dhd_bus_t *dhd_bus = (dhd_bus_t *) pub; if ((dhd_bus == NULL) || (pkt == NULL)) return; PKTFREE(dhd_bus->pub.osh, pkt, TRUE); } /** called by nobody (Dec 2012) */ int dbus_get_stats(dbus_pub_t *pub, dbus_stats_t *stats) { dhd_bus_t *dhd_bus = (dhd_bus_t *) pub; if ((dhd_bus == NULL) || (stats == NULL)) return DBUS_ERR; bcopy(&dhd_bus->pub.stats, stats, sizeof(dbus_stats_t)); return DBUS_OK; } int dbus_get_attrib(dhd_bus_t *pub, dbus_attrib_t *attrib) { dhd_bus_t *dhd_bus = (dhd_bus_t *) pub; int err = DBUS_ERR; if ((dhd_bus == NULL) || (attrib == NULL)) return DBUS_ERR; if (dhd_bus->drvintf && dhd_bus->drvintf->get_attrib) { err = dhd_bus->drvintf->get_attrib(dhd_bus->bus_info, &dhd_bus->pub.attrib); } bcopy(&dhd_bus->pub.attrib, attrib, sizeof(dbus_attrib_t)); return err; } int dbus_get_device_speed(dbus_pub_t *pub) { dhd_bus_t *dhd_bus = (dhd_bus_t *) pub; if (dhd_bus == NULL) return INVALID_SPEED; return (dhd_bus->pub.device_speed); } int dbus_set_config(dbus_pub_t *pub, dbus_config_t *config) { dhd_bus_t *dhd_bus = (dhd_bus_t *) pub; int err = DBUS_ERR; if ((dhd_bus == NULL) || (config == NULL)) return DBUS_ERR; if (dhd_bus->drvintf && dhd_bus->drvintf->set_config) { err = dhd_bus->drvintf->set_config(dhd_bus->bus_info, config); if ((config->config_id == DBUS_CONFIG_ID_AGGR_LIMIT) && (!err) && (dhd_bus->pub.busstate == DBUS_STATE_UP)) { dbus_rxirbs_fill(dhd_bus); } } return err; } int dbus_get_config(dbus_pub_t *pub, dbus_config_t *config) { dhd_bus_t *dhd_bus = (dhd_bus_t *) pub; int err = DBUS_ERR; if ((dhd_bus == NULL) || (config == NULL)) return DBUS_ERR; if (dhd_bus->drvintf && dhd_bus->drvintf->get_config) { err = dhd_bus->drvintf->get_config(dhd_bus->bus_info, config); } return err; } int dbus_set_errmask(dbus_pub_t *pub, uint32 mask) { dhd_bus_t *dhd_bus = (dhd_bus_t *) pub; int err = DBUS_OK; if (dhd_bus == NULL) return DBUS_ERR; dhd_bus->errmask = mask; return err; } int dbus_pnp_resume(dbus_pub_t *pub, int *fw_reload) { dhd_bus_t *dhd_bus = (dhd_bus_t *) pub; int err = DBUS_ERR; bool fwdl = FALSE; DBUSTRACE(("%s\n", __FUNCTION__)); if (dhd_bus == NULL) return DBUS_ERR; if (dhd_bus->pub.busstate == DBUS_STATE_UP) { return DBUS_OK; } if (dhd_bus->drvintf->pnp) { err = dhd_bus->drvintf->pnp(dhd_bus->bus_info, DBUS_PNP_RESUME); } if (dhd_bus->drvintf->recv_needed) { if (dhd_bus->drvintf->recv_needed(dhd_bus->bus_info)) { /* Refill after sleep/hibernate */ dbus_rxirbs_fill(dhd_bus); } } if (fw_reload) *fw_reload = fwdl; return err; } /* dbus_pnp_resume */ int dbus_pnp_sleep(dbus_pub_t *pub) { dhd_bus_t *dhd_bus = (dhd_bus_t *) pub; int err = DBUS_ERR; DBUSTRACE(("%s\n", __FUNCTION__)); if (dhd_bus == NULL) return DBUS_ERR; dbus_tx_timer_stop(dhd_bus); if (dhd_bus->drvintf && dhd_bus->drvintf->pnp) { err = dhd_bus->drvintf->pnp(dhd_bus->bus_info, DBUS_PNP_SLEEP); } return err; } int dbus_pnp_disconnect(dbus_pub_t *pub) { dhd_bus_t *dhd_bus = (dhd_bus_t *) pub; int err = DBUS_ERR; DBUSTRACE(("%s\n", __FUNCTION__)); if (dhd_bus == NULL) return DBUS_ERR; dbus_tx_timer_stop(dhd_bus); if (dhd_bus->drvintf && dhd_bus->drvintf->pnp) { err = dhd_bus->drvintf->pnp(dhd_bus->bus_info, DBUS_PNP_DISCONNECT); } return err; } int dhd_bus_iovar_op(dhd_pub_t *dhdp, const char *name, void *params, int plen, void *arg, int len, bool set) { dhd_bus_t *dhd_bus = (dhd_bus_t *) dhdp->bus; int err = DBUS_ERR; DBUSTRACE(("%s\n", __FUNCTION__)); if (dhd_bus == NULL) return DBUS_ERR; if (dhd_bus->drvintf && dhd_bus->drvintf->iovar_op) { err = dhd_bus->drvintf->iovar_op(dhd_bus->bus_info, name, params, plen, arg, len, set); } return err; } void * dhd_dbus_txq(const dbus_pub_t *pub) { return NULL; } uint dhd_dbus_hdrlen(const dbus_pub_t *pub) { return 0; } void * dbus_get_devinfo(dbus_pub_t *pub) { return pub->dev_info; } #if defined(BCM_REQUEST_FW) && !defined(EXTERNAL_FW_PATH) static int dbus_otp(dhd_bus_t *dhd_bus, uint16 *boardtype, uint16 *boardrev) { uint32 value = 0; uint8 *cis; uint16 *otpinfo; uint32 i; bool standard_cis = TRUE; uint8 tup, tlen; bool btype_present = FALSE; bool brev_present = FALSE; int ret; int devid; uint16 btype = 0; uint16 brev = 0; uint32 otp_size = 0, otp_addr = 0, otp_sw_rgn = 0; if (dhd_bus == NULL || dhd_bus->drvintf == NULL || dhd_bus->drvintf->readreg == NULL) return DBUS_ERR; devid = dhd_bus->pub.attrib.devid; if ((devid == BCM43234_CHIP_ID) || (devid == BCM43235_CHIP_ID) || (devid == BCM43236_CHIP_ID)) { otp_size = BCM_OTP_SIZE_43236; otp_sw_rgn = BCM_OTP_SW_RGN_43236; otp_addr = BCM_OTP_ADDR_43236; } else { return DBUS_ERR_NVRAM; } cis = MALLOC(dhd_bus->pub.osh, otp_size * 2); if (cis == NULL) return DBUS_ERR; otpinfo = (uint16 *) cis; for (i = 0; i < otp_size; i++) { ret = dhd_bus->drvintf->readreg(dhd_bus->bus_info, otp_addr + ((otp_sw_rgn + i) << 1), 2, &value); if (ret != DBUS_OK) { MFREE(dhd_bus->pub.osh, cis, otp_size * 2); return ret; } otpinfo[i] = (uint16) value; } for (i = 0; i < (otp_size << 1); ) { if (standard_cis) { tup = cis[i++]; if (tup == CISTPL_NULL || tup == CISTPL_END) tlen = 0; else tlen = cis[i++]; } else { if (cis[i] == CISTPL_NULL || cis[i] == CISTPL_END) { tlen = 0; tup = cis[i]; } else { tlen = cis[i]; tup = CISTPL_BRCM_HNBU; } ++i; } if (tup == CISTPL_END || (i + tlen) >= (otp_size << 1)) { break; } switch (tup) { case CISTPL_BRCM_HNBU: switch (cis[i]) { case HNBU_BOARDTYPE: btype = (uint16) ((cis[i + 2] << 8) + cis[i + 1]); btype_present = TRUE; DBUSTRACE(("%s: HNBU_BOARDTYPE = 0x%2x\n", __FUNCTION__, (uint32)btype)); break; case HNBU_BOARDREV: if (tlen == 2) brev = (uint16) cis[i + 1]; else brev = (uint16) ((cis[i + 2] << 8) + cis[i + 1]); brev_present = TRUE; DBUSTRACE(("%s: HNBU_BOARDREV = 0x%2x\n", __FUNCTION__, (uint32)*boardrev)); break; case HNBU_HNBUCIS: DBUSTRACE(("%s: HNBU_HNBUCIS\n", __FUNCTION__)); tlen++; standard_cis = FALSE; break; } break; } i += tlen; } MFREE(dhd_bus->pub.osh, cis, otp_size * 2); if (btype_present == TRUE && brev_present == TRUE) { *boardtype = btype; *boardrev = brev; DBUSERR(("otp boardtype = 0x%2x boardrev = 0x%2x\n", *boardtype, *boardrev)); return DBUS_OK; } else return DBUS_ERR; } /* dbus_otp */ static int dbus_select_nvram(dhd_bus_t *dhd_bus, int8 *jumbonvram, int jumbolen, uint16 boardtype, uint16 boardrev, int8 **nvram, int *nvram_len) { /* Multi board nvram file format is contenation of nvram info with \r * The file format for two contatenated set is * \nBroadcom Jumbo Nvram file\nfirst_set\nsecond_set\nthird_set\n */ uint8 *nvram_start = NULL, *nvram_end = NULL; uint8 *nvram_start_prev = NULL, *nvram_end_prev = NULL; uint16 btype = 0, brev = 0; int len = 0; char *field; *nvram = NULL; *nvram_len = 0; if (strncmp(BCM_JUMBO_START, jumbonvram, strlen(BCM_JUMBO_START))) { /* single nvram file in the native format */ DBUSTRACE(("%s: Non-Jumbo NVRAM File \n", __FUNCTION__)); *nvram = jumbonvram; *nvram_len = jumbolen; return DBUS_OK; } else { DBUSTRACE(("%s: Jumbo NVRAM File \n", __FUNCTION__)); } /* sanity test the end of the config sets for proper ending */ if (jumbonvram[jumbolen - 1] != BCM_JUMBO_NVRAM_DELIMIT || jumbonvram[jumbolen - 2] != '\0') { DBUSERR(("%s: Bad Jumbo NVRAM file format\n", __FUNCTION__)); return DBUS_JUMBO_BAD_FORMAT; } dhd_bus->nvram_nontxt = DBUS_NVRAM_NONTXT; nvram_start = jumbonvram; while (*nvram_start != BCM_JUMBO_NVRAM_DELIMIT && len < jumbolen) { /* consume the first file info line * \nBroadcom Jumbo Nvram file\nfile1\n ... */ len ++; nvram_start ++; } nvram_end = nvram_start; /* search for "boardrev=0xabcd" and "boardtype=0x1234" information in * the concatenated nvram config files /sets */ while (len < jumbolen) { if (*nvram_end == '\0') { /* end of a config set is marked by multiple null characters */ len ++; nvram_end ++; DBUSTRACE(("%s: NULL chr len = %d char = 0x%x\n", __FUNCTION__, len, *nvram_end)); continue; } else if (*nvram_end == BCM_JUMBO_NVRAM_DELIMIT) { /* config set delimiter is reached */ /* check if next config set is present or not * return if next config is not present */ /* start search the next config set */ nvram_start_prev = nvram_start; nvram_end_prev = nvram_end; nvram_end ++; nvram_start = nvram_end; btype = brev = 0; DBUSTRACE(("%s: going to next record len = %d " "char = 0x%x \n", __FUNCTION__, len, *nvram_end)); len ++; if (len >= jumbolen) { *nvram = nvram_start_prev; *nvram_len = (int)(nvram_end_prev - nvram_start_prev); DBUSTRACE(("%s: no more len = %d nvram_end = 0x%p", __FUNCTION__, len, nvram_end)); return DBUS_JUMBO_NOMATCH; } else { continue; } } else { DBUSTRACE(("%s: config str = %s\n", __FUNCTION__, nvram_end)); if (bcmp(nvram_end, "boardtype", strlen("boardtype")) == 0) { field = strchr(nvram_end, '='); field++; btype = (uint16)bcm_strtoul(field, NULL, 0); DBUSTRACE(("%s: btype = 0x%x boardtype = 0x%x \n", __FUNCTION__, btype, boardtype)); } if (bcmp(nvram_end, "boardrev", strlen("boardrev")) == 0) { field = strchr(nvram_end, '='); field++; brev = (uint16)bcm_strtoul(field, NULL, 0); DBUSTRACE(("%s: brev = 0x%x boardrev = 0x%x \n", __FUNCTION__, brev, boardrev)); } if (btype == boardtype && brev == boardrev) { /* locate nvram config set end - ie.find '\r' char */ while (*nvram_end != BCM_JUMBO_NVRAM_DELIMIT) nvram_end ++; *nvram = nvram_start; *nvram_len = (int) (nvram_end - nvram_start); DBUSTRACE(("found len = %d nvram_start = 0x%p " "nvram_end = 0x%p\n", *nvram_len, nvram_start, nvram_end)); return DBUS_OK; } len += (strlen(nvram_end) + 1); nvram_end += (strlen(nvram_end) + 1); } } return DBUS_JUMBO_NOMATCH; } /* dbus_select_nvram */ #endif #define DBUS_NRXQ 50 #define DBUS_NTXQ 100 static void dhd_dbus_send_complete(void *handle, void *info, int status) { dhd_pub_t *dhd = (dhd_pub_t *)handle; void *pkt = info; if ((dhd == NULL) || (pkt == NULL)) { DBUSERR(("dhd or pkt is NULL\n")); return; } if (status == DBUS_OK) { dhd->dstats.tx_packets++; } else { DBUSERR(("TX error=%d\n", status)); dhd->dstats.tx_errors++; } #ifdef PROP_TXSTATUS if (DHD_PKTTAG_WLFCPKT(PKTTAG(pkt)) && (dhd_wlfc_txcomplete(dhd, pkt, status == 0) != WLFC_UNSUPPORTED)) { return; } #endif /* PROP_TXSTATUS */ PKTFREE(dhd->osh, pkt, TRUE); } static void dhd_dbus_recv_pkt(void *handle, void *pkt) { uchar reorder_info_buf[WLHOST_REORDERDATA_TOTLEN]; uint reorder_info_len; uint pkt_count; dhd_pub_t *dhd = (dhd_pub_t *)handle; int ifidx = 0; if (dhd == NULL) { DBUSERR(("%s: dhd is NULL\n", __FUNCTION__)); return; } /* If the protocol uses a data header, check and remove it */ if (dhd_prot_hdrpull(dhd, &ifidx, pkt, reorder_info_buf, &reorder_info_len) != 0) { DBUSERR(("rx protocol error\n")); PKTFREE(dhd->osh, pkt, FALSE); dhd->rx_errors++; return; } if (reorder_info_len) { /* Reordering info from the firmware */ dhd_process_pkt_reorder_info(dhd, reorder_info_buf, reorder_info_len, &pkt, &pkt_count); if (pkt_count == 0) return; } else { pkt_count = 1; } dhd_rx_frame(dhd, ifidx, pkt, pkt_count, 0); } static void dhd_dbus_recv_buf(void *handle, uint8 *buf, int len) { dhd_pub_t *dhd = (dhd_pub_t *)handle; void *pkt; if (dhd == NULL) { DBUSERR(("%s: dhd is NULL\n", __FUNCTION__)); return; } if ((pkt = PKTGET(dhd->osh, len, FALSE)) == NULL) { DBUSERR(("PKTGET (rx) failed=%d\n", len)); return; } bcopy(buf, PKTDATA(dhd->osh, pkt), len); dhd_dbus_recv_pkt(dhd, pkt); } static void dhd_dbus_txflowcontrol(void *handle, bool onoff) { dhd_pub_t *dhd = (dhd_pub_t *)handle; bool wlfc_enabled = FALSE; if (dhd == NULL) { DBUSERR(("%s: dhd is NULL\n", __FUNCTION__)); return; } #ifdef PROP_TXSTATUS wlfc_enabled = (dhd_wlfc_flowcontrol(dhd, onoff, !onoff) != WLFC_UNSUPPORTED); #endif if (!wlfc_enabled) { dhd_txflowcontrol(dhd, ALL_INTERFACES, onoff); } } static void dhd_dbus_errhandler(void *handle, int err) { } static void dhd_dbus_ctl_complete(void *handle, int type, int status) { dhd_pub_t *dhd = (dhd_pub_t *)handle; if (dhd == NULL) { DBUSERR(("%s: dhd is NULL\n", __FUNCTION__)); return; } if (type == DBUS_CBCTL_READ) { if (status == DBUS_OK) dhd->rx_ctlpkts++; else dhd->rx_ctlerrs++; } else if (type == DBUS_CBCTL_WRITE) { if (status == DBUS_OK) dhd->tx_ctlpkts++; else dhd->tx_ctlerrs++; } dhd_prot_ctl_complete(dhd); } static void dhd_dbus_state_change(void *handle, int state) { dhd_pub_t *dhd = (dhd_pub_t *)handle; if (dhd == NULL) { DBUSERR(("%s: dhd is NULL\n", __FUNCTION__)); return; } switch (state) { case DBUS_STATE_DL_NEEDED: DBUSERR(("%s: firmware request cannot be handled\n", __FUNCTION__)); break; case DBUS_STATE_DOWN: DBUSTRACE(("%s: DBUS is down\n", __FUNCTION__)); dhd->busstate = DHD_BUS_DOWN; break; case DBUS_STATE_UP: DBUSTRACE(("%s: DBUS is up\n", __FUNCTION__)); dhd->busstate = DHD_BUS_DATA; break; default: break; } DBUSERR(("%s: DBUS current state=%d\n", __FUNCTION__, state)); } static void * dhd_dbus_pktget(void *handle, uint len, bool send) { dhd_pub_t *dhd = (dhd_pub_t *)handle; void *p = NULL; if (dhd == NULL) { DBUSERR(("%s: dhd is NULL\n", __FUNCTION__)); return NULL; } if (send == TRUE) { dhd_os_sdlock_txq(dhd); p = PKTGET(dhd->osh, len, TRUE); dhd_os_sdunlock_txq(dhd); } else { dhd_os_sdlock_rxq(dhd); p = PKTGET(dhd->osh, len, FALSE); dhd_os_sdunlock_rxq(dhd); } return p; } static void dhd_dbus_pktfree(void *handle, void *p, bool send) { dhd_pub_t *dhd = (dhd_pub_t *)handle; if (dhd == NULL) { DBUSERR(("%s: dhd is NULL\n", __FUNCTION__)); return; } if (send == TRUE) { #ifdef PROP_TXSTATUS if (DHD_PKTTAG_WLFCPKT(PKTTAG(p)) && (dhd_wlfc_txcomplete(dhd, p, FALSE) != WLFC_UNSUPPORTED)) { return; } #endif /* PROP_TXSTATUS */ dhd_os_sdlock_txq(dhd); PKTFREE(dhd->osh, p, TRUE); dhd_os_sdunlock_txq(dhd); } else { dhd_os_sdlock_rxq(dhd); PKTFREE(dhd->osh, p, FALSE); dhd_os_sdunlock_rxq(dhd); } } static dbus_callbacks_t dhd_dbus_cbs = { dhd_dbus_send_complete, dhd_dbus_recv_buf, dhd_dbus_recv_pkt, dhd_dbus_txflowcontrol, dhd_dbus_errhandler, dhd_dbus_ctl_complete, dhd_dbus_state_change, dhd_dbus_pktget, dhd_dbus_pktfree }; uint dhd_bus_chip(struct dhd_bus *bus) { ASSERT(bus != NULL); return bus->pub.attrib.devid; } uint dhd_bus_chiprev(struct dhd_bus *bus) { ASSERT(bus); ASSERT(bus != NULL); return bus->pub.attrib.chiprev; } void dhd_bus_dump(dhd_pub_t *dhdp, struct bcmstrbuf *strbuf) { bcm_bprintf(strbuf, "Bus USB\n"); } void dhd_bus_clearcounts(dhd_pub_t *dhdp) { } int dhd_bus_txdata(struct dhd_bus *bus, void *pktbuf) { DBUSTRACE(("%s\n", __FUNCTION__)); if (bus->txoff) { DBUSTRACE(("txoff\n")); return BCME_EPERM; } return dbus_send_txdata(&bus->pub, pktbuf); } static void dhd_dbus_advertise_bus_cleanup(dhd_pub_t *dhdp) { unsigned long flags; int timeleft; DHD_LINUX_GENERAL_LOCK(dhdp, flags); dhdp->busstate = DHD_BUS_DOWN_IN_PROGRESS; DHD_LINUX_GENERAL_UNLOCK(dhdp, flags); timeleft = dhd_os_busbusy_wait_negation(dhdp, &dhdp->dhd_bus_busy_state); if ((timeleft == 0) || (timeleft == 1)) { DBUSERR(("%s : Timeout due to dhd_bus_busy_state=0x%x\n", __FUNCTION__, dhdp->dhd_bus_busy_state)); ASSERT(0); } return; } static void dhd_dbus_advertise_bus_remove(dhd_pub_t *dhdp) { unsigned long flags; int timeleft; DHD_LINUX_GENERAL_LOCK(dhdp, flags); dhdp->busstate = DHD_BUS_REMOVE; DHD_LINUX_GENERAL_UNLOCK(dhdp, flags); timeleft = dhd_os_busbusy_wait_negation(dhdp, &dhdp->dhd_bus_busy_state); if ((timeleft == 0) || (timeleft == 1)) { DBUSERR(("%s : Timeout due to dhd_bus_busy_state=0x%x\n", __FUNCTION__, dhdp->dhd_bus_busy_state)); ASSERT(0); } return; } int dhd_bus_devreset(dhd_pub_t *dhdp, uint8 flag) { int bcmerror = 0; unsigned long flags; if (flag == TRUE) { if (!dhdp->dongle_reset) { DBUSERR(("%s: == Power OFF ==\n", __FUNCTION__)); dhd_dbus_advertise_bus_cleanup(dhdp); dhd_os_wd_timer(dhdp, 0); #if !defined(IGNORE_ETH0_DOWN) /* Force flow control as protection when stop come before ifconfig_down */ dhd_txflowcontrol(dhdp, ALL_INTERFACES, ON); #endif /* !defined(IGNORE_ETH0_DOWN) */ dbus_stop(dhdp->bus); dhdp->dongle_reset = TRUE; dhdp->up = FALSE; DHD_LINUX_GENERAL_LOCK(dhdp, flags); dhdp->busstate = DHD_BUS_DOWN; DHD_LINUX_GENERAL_UNLOCK(dhdp, flags); dhdp->fw_ready = FALSE; printf("%s: WLAN OFF DONE\n", __FUNCTION__); /* App can now remove power from device */ } else bcmerror = BCME_ERROR; } else { /* App must have restored power to device before calling */ printf("\n\n%s: == WLAN ON ==\n", __FUNCTION__); if (dhdp->dongle_reset) { /* Turn on WLAN */ DHD_MUTEX_UNLOCK(); wait_event_interruptible_timeout(dhdp->fw_ready_event, dhdp->fw_ready, msecs_to_jiffies(5000)); DHD_MUTEX_LOCK(); bcmerror = dbus_up(dhdp->bus); if (bcmerror == BCME_OK) { dhdp->dongle_reset = FALSE; dhdp->up = TRUE; #if !defined(IGNORE_ETH0_DOWN) /* Restore flow control */ dhd_txflowcontrol(dhdp, ALL_INTERFACES, OFF); #endif dhd_os_wd_timer(dhdp, dhd_watchdog_ms); DBUSTRACE(("%s: WLAN ON DONE\n", __FUNCTION__)); } else { DBUSERR(("%s: failed to dbus_up with code %d\n", __FUNCTION__, bcmerror)); } } } #ifdef PKT_STATICS memset((uint8*) &tx_statics, 0, sizeof(pkt_statics_t)); #endif return bcmerror; } void dhd_set_path_params(struct dhd_bus *bus) { /* External conf takes precedence if specified */ dhd_conf_preinit(bus->dhd); if (bus->dhd->conf_path[0] == '\0') { dhd_conf_set_path(bus->dhd, "config.txt", bus->dhd->conf_path, bus->nv_path); } if (bus->dhd->clm_path[0] == '\0') { dhd_conf_set_path(bus->dhd, "clm.blob", bus->dhd->clm_path, bus->fw_path); } #ifdef CONFIG_PATH_AUTO_SELECT dhd_conf_set_conf_name_by_chip(bus->dhd, bus->dhd->conf_path); #endif dhd_conf_read_config(bus->dhd, bus->dhd->conf_path); dhd_conf_set_fw_name_by_chip(bus->dhd, bus->fw_path); dhd_conf_set_nv_name_by_chip(bus->dhd, bus->nv_path); dhd_conf_set_clm_name_by_chip(bus->dhd, bus->dhd->clm_path); printf("Final fw_path=%s\n", bus->fw_path); printf("Final nv_path=%s\n", bus->nv_path); printf("Final clm_path=%s\n", bus->dhd->clm_path); printf("Final conf_path=%s\n", bus->dhd->conf_path); } void dhd_bus_update_fw_nv_path(struct dhd_bus *bus, char *pfw_path, char *pnv_path, char *pclm_path, char *pconf_path) { DBUSTRACE(("%s\n", __FUNCTION__)); if (bus == NULL) { DBUSERR(("%s: bus is NULL\n", __FUNCTION__)); return; } bus->fw_path = pfw_path; bus->nv_path = pnv_path; bus->dhd->clm_path = pclm_path; bus->dhd->conf_path = pconf_path; dhd_set_path_params(bus); } #if defined(MULTIPLE_SUPPLICANT) extern void wl_android_post_init(void); // terence 20120530: fix critical section in dhd_open and dhdsdio_probe #endif /* * hdrlen is space to reserve in pkt headroom for DBUS */ void * dhd_dbus_probe_cb(void *arg, const char *desc, uint32 bustype, uint32 hdrlen) { osl_t *osh = NULL; dhd_bus_t *bus = NULL; dhd_pub_t *pub = NULL; uint rxsz; int dlneeded = 0; DBUSTRACE(("%s: Enter\n", __FUNCTION__)); if (!g_pub) { /* Ask the OS interface part for an OSL handle */ if (!(osh = osl_attach(NULL, bustype, TRUE))) { DBUSERR(("%s: OSL attach failed\n", __FUNCTION__)); goto fail; } /* Attach to the dhd/OS interface */ if (!(pub = dhd_attach(osh, bus, hdrlen))) { DBUSERR(("%s: dhd_attach failed\n", __FUNCTION__)); goto fail; } } else { pub = g_pub; } if (pub->bus) { DBUSERR(("%s: wrong probe\n", __FUNCTION__)); goto fail; } rxsz = dhd_get_rxsz(pub); bus = dbus_attach(osh, rxsz, DBUS_NRXQ, DBUS_NTXQ, pub, &dhd_dbus_cbs, NULL, NULL); if (bus) { pub->bus = bus; bus->dhd = pub; dlneeded = dbus_dlneeded(bus); if (dlneeded >= 0) { if (!g_pub) { dhd_conf_reset(pub); dhd_conf_set_chiprev(pub, bus->pub.attrib.devid, bus->pub.attrib.chiprev); dhd_conf_preinit(pub); } } if (g_pub || dhd_download_fw_on_driverload) { if (dlneeded == 0) { pub->fw_ready = TRUE; wake_up_interruptible(&pub->fw_ready_event); #ifdef BCM_REQUEST_FW } else if (dlneeded > 0) { dhd_set_path(bus->dhd); if (dbus_download_firmware(bus, bus->fw_path, bus->nv_path) != DBUS_OK) goto fail; #endif } } } else { DBUSERR(("%s: dbus_attach failed\n", __FUNCTION__)); } if (!g_pub) { /* Ok, finish the attach to the OS network interface */ if (dhd_register_if(pub, 0, TRUE) != 0) { DBUSERR(("%s: dhd_register_if failed\n", __FUNCTION__)); goto fail; } pub->hang_report = TRUE; #if defined(MULTIPLE_SUPPLICANT) wl_android_post_init(); // terence 20120530: fix critical section in dhd_open and dhdsdio_probe #endif g_pub = pub; } DBUSTRACE(("%s: Exit\n", __FUNCTION__)); /* This is passed to dhd_dbus_disconnect_cb */ return bus; fail: if (pub && pub->bus) { dbus_detach(pub->bus); pub->bus = NULL; } /* Release resources in reverse order */ if (!g_pub) { if (pub) { dhd_detach(pub); dhd_free(pub); } if (osh) { osl_detach(osh); } } printf("%s: Failed\n", __FUNCTION__); return NULL; } void dhd_dbus_disconnect_cb(void *arg) { dhd_bus_t *bus = (dhd_bus_t *)arg; dhd_pub_t *pub = g_pub; osl_t *osh; if (pub && !pub->dhd_remove && bus == NULL) { DBUSERR(("%s: bus is NULL\n", __FUNCTION__)); return; } printf("%s: Enter dhd_remove=%d\n", __FUNCTION__, pub->dhd_remove); if (!pub->dhd_remove) { /* Advertise bus remove during rmmod */ dhd_dbus_advertise_bus_remove(bus->dhd); dbus_detach(pub->bus); pub->bus = NULL; } else { osh = pub->osh; dhd_detach(pub); if (pub->bus) { dbus_detach(pub->bus); pub->bus = NULL; } dhd_free(pub); if (MALLOCED(osh)) { DBUSERR(("%s: MEMORY LEAK %d bytes\n", __FUNCTION__, MALLOCED(osh))); } osl_detach(osh); } DBUSTRACE(("%s: Exit\n", __FUNCTION__)); } #ifdef BCM_REQUEST_FW /* Register a dummy USB client driver in order to be notified of new USB device */ int dbus_reg_notify(void* semaphore) { return dbus_usb_reg_notify(semaphore); } void dbus_unreg_notify(void) { dbus_usb_unreg_notify(); } #endif #ifdef LINUX_EXTERNAL_MODULE_DBUS static int __init bcm_dbus_module_init(void) { printf("Inserting bcm_dbus module \n"); return 0; } static void __exit bcm_dbus_module_exit(void) { printf("Removing bcm_dbus module \n"); return; } EXPORT_SYMBOL(dbus_pnp_sleep); EXPORT_SYMBOL(dbus_get_devinfo); EXPORT_SYMBOL(dbus_detach); EXPORT_SYMBOL(dbus_get_attrib); EXPORT_SYMBOL(dbus_down); EXPORT_SYMBOL(dbus_pnp_resume); EXPORT_SYMBOL(dbus_set_config); EXPORT_SYMBOL(dbus_flowctrl_rx); EXPORT_SYMBOL(dbus_up); EXPORT_SYMBOL(dbus_get_device_speed); EXPORT_SYMBOL(dbus_send_pkt); EXPORT_SYMBOL(dbus_recv_ctl); EXPORT_SYMBOL(dbus_attach); MODULE_LICENSE("GPL"); module_init(bcm_dbus_module_init); module_exit(bcm_dbus_module_exit); #endif /* #ifdef LINUX_EXTERNAL_MODULE_DBUS */