/** @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.
 *
 *
 * <<Broadcom-WL-IPTag/Open:>>
 *
 * $Id: dbus.c 553311 2015-04-29 10:23:08Z $
 */


#include <linux/usb.h>
#include "osl.h"
#include "dbus.h"
#include <bcmutils.h>
#include <dngl_stats.h>
#include <dhd.h>
#include <dhd_proto.h>
#ifdef PROP_TXSTATUS /* a form of flow control between host and dongle */
#include <dhd_wlfc.h>
#endif
#include <dhd_config.h>
#ifdef WL_CFG80211
#include <wl_cfg80211.h>
#include <wl_cfgp2p.h>
#endif

#include <bcmdevs_legacy.h>
#if defined(BCM_REQUEST_FW)
#include <bcmsrom_fmt.h>
#include <trxhdr.h>
#include <usbrdl.h>
#include <bcmendian.h>
#include <sbpcmcia.h>
#include <bcmnvram.h>
#include <bcmdevs.h>
#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;
	uint ctl_completed;

	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 */
	uint64 last_suspend_end_time;
} 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);

static int dbus_suspend(void *context);
static int dbus_resume(void *context);
static void * dhd_dbus_probe_cb(uint16 bus_no, uint16 slot, uint32 hdrlen);
static void dhd_dbus_disconnect_cb(void *arg);
static 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;

#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);

#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)
{
	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;
	char *pfw_path = dhd_bus->fw_path;
	char *pnv_path = dhd_bus->nv_path;

	/* For Get nvram */
	file_exists = ((pnv_path != NULL) && (pnv_path[0] != '\0'));
	if (file_exists) {
		nv_image = dhd_os_open_image1(dhd_bus->dhd, 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_image1(dhd_bus->dhd, nv_image);
		nv_image = NULL;
	}

	/* 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_image1(dhd_bus->dhd, 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;
#if defined(CONFIG_DHD_USE_STATIC_BUF)
	dhd_bus->image = (uint8*)DHD_OS_PREALLOC(dhd_bus->dhd,
		DHD_PREALLOC_MEMDUMP_RAM, total_len);
#else
	dhd_bus->image = MALLOC(dhd_bus->pub.osh, total_len);
#endif /* CONFIG_DHD_USE_STATIC_BUF */
	dhd_bus->image_len = total_len;
	if (dhd_bus->image == NULL) {
		DBUSERR(("%s: malloc failed! size=%d\n", __FUNCTION__, total_len));
		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_image1(dhd_bus->dhd, fw_image);
	if (nv_memblock)
		MFREE(dhd_bus->pub.osh, nv_memblock, MAX_NVRAMBUF_SIZE);
	if (nv_image)
		dhd_os_close_image1(dhd_bus->dhd, 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)
{
	int err = DBUS_OK;

	err = dbus_get_fw_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->image) {
#if defined(CONFIG_DHD_USE_STATIC_BUF)
		DHD_OS_PREFREE(dhd_bus->dhd, dhd_bus->image, dhd_bus->image_len);
#else
		MFREE(dhd_bus->pub.osh, dhd_bus->image, dhd_bus->image_len);
#endif /* CONFIG_DHD_USE_STATIC_BUF */
		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;
#if defined(CONFIG_DHD_USE_STATIC_BUF)
		dhd_bus->image = (uint8*)DHD_OS_PREALLOC(dhd_bus->dhd,
			DHD_PREALLOC_MEMDUMP_RAM, len);
#else
		dhd_bus->image = MALLOC(dhd_bus->pub.osh, len);
#endif /* CONFIG_DHD_USE_STATIC_BUF */
		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, dhd_bus->fw_path);
	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, dhd_bus->nv_path);
	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) {
#if defined(CONFIG_DHD_USE_STATIC_BUF)
		DHD_OS_PREFREE(dhd_bus->dhd, dhd_bus->image, dhd_bus->image_len);
#else
		MFREE(dhd_bus->pub.osh, dhd_bus->image, dhd_bus->image_len);
#endif /* CONFIG_DHD_USE_STATIC_BUF */
		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

/**
 * 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
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
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;
}

/* Register/Unregister functions are called by the main DHD entry
 * point (e.g. module insertion) to link with the bus driver, in
 * order to look for or await the device.
 */

static dbus_driver_t dhd_dbus = {
	dhd_dbus_probe_cb,
	dhd_dbus_disconnect_cb,
	dbus_suspend,
	dbus_resume
};

/**
 * 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__));

	err = dbus_bus_register(&dhd_dbus, &g_busintf);

	/* Device not detected */
	if (err == DBUS_ERR_NODEVICE)
		err = DBUS_OK;

	return err;
}

dhd_pub_t *g_pub = NULL;
bool net_attached = FALSE;
void
dhd_bus_unregister(void)
{
	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);
		}
	}
	DHD_MUTEX_UNLOCK();
	dbus_bus_deregister();
}

/** 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 %zu\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)
{
	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;
	err = dbus_do_download(dhd_bus);
	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_buf(dbus_pub_t *pub, uint8 *buf, int len, void *info)
{
	return dbus_send_irb(pub, buf, len, NULL, info);
}

static int
dbus_send_pkt(dbus_pub_t *pub, void *pkt, void *info)
{
	return dbus_send_irb(pub, NULL, 0, pkt, info);
}

static 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;
}

static 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);
	} else {
		DBUSERR(("%s: bustate=%d\n", __FUNCTION__, dhd_bus->pub.busstate));
	}

	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;
}

#ifdef INTR_EP_ENABLE
/** only called by dhd_cdc.c (Dec 2012) */
static 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;
}
#endif /* INTR_EP_ENABLE */

/** 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;
	} else
#endif /* PROP_TXSTATUS */
	dhd_txcomplete(dhd, pkt, status == 0);
	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->bus->ctl_completed = TRUE;
	dhd_os_ioctl_resp_wake(dhd);
}

static void
dhd_dbus_state_change(void *handle, int state)
{
	dhd_pub_t *dhd = (dhd_pub_t *)handle;
	unsigned long flags;
	wifi_adapter_info_t *adapter;

	if (dhd == NULL) {
		DBUSERR(("%s: dhd is NULL\n", __FUNCTION__));
		return;
	}
	adapter = (wifi_adapter_info_t *)dhd->adapter;

	if (dhd->busstate == DHD_BUS_SUSPEND && state == DBUS_STATE_DOWN) {
		DBUSERR(("%s: switch state %d to %d\n", __FUNCTION__, state, DBUS_STATE_SLEEP));
		state = DBUS_STATE_SLEEP;
	}
	switch (state) {
		case DBUS_STATE_DL_NEEDED:
			DBUSERR(("%s: firmware request cannot be handled\n", __FUNCTION__));
			break;
		case DBUS_STATE_DOWN:
			DHD_LINUX_GENERAL_LOCK(dhd, flags);
			dhd_txflowcontrol(dhd, ALL_INTERFACES, ON);
			DBUSTRACE(("%s: DBUS is down\n", __FUNCTION__));
			dhd->busstate = DHD_BUS_DOWN;
			DHD_LINUX_GENERAL_UNLOCK(dhd, flags);
			break;
		case DBUS_STATE_UP:
			DBUSTRACE(("%s: DBUS is up\n", __FUNCTION__));
			DHD_LINUX_GENERAL_LOCK(dhd, flags);
			dhd_txflowcontrol(dhd, ALL_INTERFACES, OFF);
			dhd->busstate = DHD_BUS_DATA;
			DHD_LINUX_GENERAL_UNLOCK(dhd, flags);
			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;
}

struct device *
dhd_bus_to_dev(struct dhd_bus *bus)
{
	return dbus_get_dev();
}

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_pkt(&bus->pub, pktbuf, pktbuf);
}

int
dhd_bus_txctl(struct dhd_bus *bus, uchar *msg, uint msglen)
{
	int timeleft = 0;
	int ret = -1;

	DBUSTRACE(("%s: Enter\n", __FUNCTION__));

	if (bus->dhd->dongle_reset)
		return -EIO;

	bus->ctl_completed = FALSE;
	ret = dbus_send_ctl(bus, (void *)msg, msglen);
	if (ret) {
		DBUSERR(("%s: dbus_send_ctl error %d\n", __FUNCTION__, ret));
		return ret;
	}

	timeleft = dhd_os_ioctl_resp_wait(bus->dhd, &bus->ctl_completed);
	if ((!timeleft) || (!bus->ctl_completed)) {
		DBUSERR(("%s: Txctl timeleft %d ctl_completed %d\n",
			__FUNCTION__, timeleft, bus->ctl_completed));
		ret = -1;
	}

#ifdef INTR_EP_ENABLE
	/* If the ctl write is successfully completed, wait for an acknowledgement
	* that indicates that it is now ok to do ctl read from the dongle
	*/
	if (ret != -1) {
		bus->ctl_completed = FALSE;
		if (dbus_poll_intr(bus->pub)) {
			DBUSERR(("%s: dbus_poll_intr not submitted\n", __FUNCTION__));
		} else {
			/* interrupt polling is sucessfully submitted. Wait for dongle to send
			* interrupt
			*/
			timeleft = dhd_os_ioctl_resp_wait(bus->dhd, &bus->ctl_completed);
			if (!timeleft) {
				DBUSERR(("%s: intr poll wait timed out\n", __FUNCTION__));
			}
		}
	}
#endif /* INTR_EP_ENABLE */

	return ret;
}

int
dhd_bus_rxctl(struct dhd_bus *bus, uchar *msg, uint msglen)
{
	int timeleft;
	int ret = -1;

	DBUSTRACE(("%s: Enter\n", __FUNCTION__));

	if (bus->dhd->dongle_reset)
		return -EIO;

	bus->ctl_completed = FALSE;
	ret = dbus_recv_ctl(bus, (uchar*)msg, msglen);
	if (ret) {
		DBUSERR(("%s: dbus_recv_ctl error %d\n", __FUNCTION__, ret));
		goto done;
	}

	timeleft = dhd_os_ioctl_resp_wait(bus->dhd, &bus->ctl_completed);
	if ((!timeleft) || (!bus->ctl_completed)) {
		DBUSERR(("%s: Rxctl timeleft %d ctl_completed %d\n", __FUNCTION__,
			timeleft, bus->ctl_completed));
		ret = -ETIMEDOUT;
		goto done;
	}

	/* XXX FIX: Must return cdc_len, not len, because after query_ioctl()
	 * it subtracts sizeof(cdc_ioctl_t);  The other approach is
	 * to have dbus_recv_ctl() return actual len.
	 */
	ret = msglen;

done:
	return ret;
}

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);
	if (dhdp->busstate != DHD_BUS_SUSPEND)
		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;
	wifi_adapter_info_t *adapter = (wifi_adapter_info_t *)dhdp->adapter;

	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);
			wifi_clr_adapter_status(adapter, WIFI_STATUS_FW_READY);

			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(adapter->status_event,
				wifi_get_adapter_status(adapter, WIFI_STATUS_FW_READY),
				msecs_to_jiffies(DHD_FW_READY_TIMEOUT));
			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));
			}
		}
	}

	return bcmerror;
}

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_conf_set_path_params(bus->dhd, bus->fw_path, bus->nv_path);

}

static int
dhd_dbus_sync_dongle(dhd_pub_t *pub, int dlneeded)
{
	int ret = 0;

	if (dlneeded == 0) {
		ret = dbus_up(pub->bus);
		if (ret) {
			DBUSERR(("%s: dbus_up failed!!\n", __FUNCTION__));
			goto exit;
		}
		ret = dhd_sync_with_dongle(pub);
		if (ret < 0) {
			DBUSERR(("%s: failed with code ret=%d\n", __FUNCTION__, ret));
			goto exit;
		}
	}

exit:
	return ret;
}

static int
dbus_suspend(void *context)
{
	int ret = 0;

#if defined(LINUX)
	dhd_bus_t *bus = (dhd_bus_t*)context;
	unsigned long flags;

	DBUSERR(("%s Enter\n", __FUNCTION__));
	if (bus->dhd == NULL) {
		DBUSERR(("bus not inited\n"));
		return BCME_ERROR;
	}
	if (bus->dhd->prot == NULL) {
		DBUSERR(("prot is not inited\n"));
		return BCME_ERROR;
	}

	if (bus->dhd->up == FALSE) {
		return BCME_OK;
	}

	DHD_LINUX_GENERAL_LOCK(bus->dhd, flags);
	if (bus->dhd->busstate != DHD_BUS_DATA && bus->dhd->busstate != DHD_BUS_SUSPEND) {
		DBUSERR(("not in a readystate to LPBK  is not inited\n"));
		DHD_LINUX_GENERAL_UNLOCK(bus->dhd, flags);
		return BCME_ERROR;
	}
	DHD_LINUX_GENERAL_UNLOCK(bus->dhd, flags);
	if (bus->dhd->dongle_reset) {
		DBUSERR(("Dongle is in reset state.\n"));
		return -EIO;
	}

	DHD_LINUX_GENERAL_LOCK(bus->dhd, flags);
	/* stop all interface network queue. */
	dhd_txflowcontrol(bus->dhd, ALL_INTERFACES, ON);
	bus->dhd->busstate = DHD_BUS_SUSPEND;
#if defined(LINUX) || defined(linux)
	if (DHD_BUS_BUSY_CHECK_IN_TX(bus->dhd)) {
		DBUSERR(("Tx Request is not ended\n"));
		bus->dhd->busstate = DHD_BUS_DATA;
		/* resume all interface network queue. */
		dhd_txflowcontrol(bus->dhd, ALL_INTERFACES, OFF);
		DHD_LINUX_GENERAL_UNLOCK(bus->dhd, flags);
		return -EBUSY;
	}
#endif /* LINUX || linux */
	DHD_BUS_BUSY_SET_SUSPEND_IN_PROGRESS(bus->dhd);
	DHD_LINUX_GENERAL_UNLOCK(bus->dhd, flags);

	ret = dhd_os_check_wakelock_all(bus->dhd);

	DHD_LINUX_GENERAL_LOCK(bus->dhd, flags);
	if (ret) {
		bus->dhd->busstate = DHD_BUS_DATA;
		/* resume all interface network queue. */
		dhd_txflowcontrol(bus->dhd, ALL_INTERFACES, OFF);
	} else {
		bus->last_suspend_end_time = OSL_LOCALTIME_NS();
	}
	bus->dhd->hostsleep = 2;
	DHD_BUS_BUSY_CLEAR_SUSPEND_IN_PROGRESS(bus->dhd);
	dhd_os_busbusy_wake(bus->dhd);
	DHD_LINUX_GENERAL_UNLOCK(bus->dhd, flags);

#endif /* LINUX */
	DBUSERR(("%s Exit ret=%d\n", __FUNCTION__, ret));
	return ret;
}

static int
dbus_resume(void *context)
{
	dhd_bus_t *bus = (dhd_bus_t*)context;
	ulong flags;
	int dlneeded = 0;
	int ret = 0;

	DBUSERR(("%s Enter\n", __FUNCTION__));

	if (bus->dhd->up == FALSE) {
		return BCME_OK;
	}
	
	dlneeded = dbus_dlneeded(bus);
	if (dlneeded == 0) {
		ret = dbus_up(bus);
		if (ret) {
			DBUSERR(("%s: dbus_up failed!!\n", __FUNCTION__));
		}
	}

	DHD_LINUX_GENERAL_LOCK(bus->dhd, flags);
	DHD_BUS_BUSY_SET_RESUME_IN_PROGRESS(bus->dhd);
	DHD_LINUX_GENERAL_UNLOCK(bus->dhd, flags);

	DHD_LINUX_GENERAL_LOCK(bus->dhd, flags);
	DHD_BUS_BUSY_CLEAR_RESUME_IN_PROGRESS(bus->dhd);
	bus->dhd->hostsleep = 0;
	bus->dhd->busstate = DHD_BUS_DATA;
	dhd_os_busbusy_wake(bus->dhd);
	/* resume all interface network queue. */
	dhd_txflowcontrol(bus->dhd, ALL_INTERFACES, OFF);
	DHD_LINUX_GENERAL_UNLOCK(bus->dhd, flags);
//	dhd_conf_set_suspend_resume(bus->dhd, 0);

	return 0;
}

/*
 * hdrlen is space to reserve in pkt headroom for DBUS
 */
static void *
dhd_dbus_probe_cb(uint16 bus_no, uint16 slot, uint32 hdrlen)
{
	osl_t *osh = NULL;
	dhd_bus_t *bus = NULL;
	dhd_pub_t *pub = NULL;
	uint rxsz;
	int dlneeded = 0, ret = DBUS_OK;
	wifi_adapter_info_t *adapter = NULL;
	bool net_attach_now = TRUE;

	DBUSTRACE(("%s: Enter\n", __FUNCTION__));

	adapter = dhd_wifi_platform_get_adapter(USB_BUS, bus_no, slot);

	if (!g_pub) {
		/* Ask the OS interface part for an OSL handle */
		if (!(osh = osl_attach(NULL, USB_BUS, TRUE))) {
			DBUSERR(("%s: OSL attach failed\n", __FUNCTION__));
			goto fail;
		}

		/* Attach to the dhd/OS interface */
		if (!(pub = dhd_attach(osh, bus, hdrlen, adapter))) {
			DBUSERR(("%s: dhd_attach failed\n", __FUNCTION__));
			goto fail;
		}
	} else {
		pub = g_pub;
		osh = pub->osh;
	}

	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 && !g_pub) {
			dhd_conf_reset(pub);
			dhd_conf_set_chiprev(pub, bus->pub.attrib.devid, bus->pub.attrib.chiprev);
			dhd_conf_preinit(pub);
		}

#if defined(BCMDHD_MODULAR) && defined(INSMOD_FW_LOAD)
		if (1)
#else
		if (g_pub || dhd_download_fw_on_driverload)
#endif
		{
			if (dlneeded == 0)
				wifi_set_adapter_status(adapter, WIFI_STATUS_FW_READY);
#ifdef BCM_REQUEST_FW
			else if (dlneeded > 0) {
				struct dhd_conf *conf = pub->conf;
				unsigned long flags;
				bool suspended;
				wifi_clr_adapter_status(adapter, WIFI_STATUS_FW_READY);
				suspended = conf->suspended;
				dhd_set_path(bus->dhd);
				conf->suspended = suspended;
				if (dbus_download_firmware(bus) != DBUS_OK)
					goto fail;
				DHD_LINUX_GENERAL_LOCK(pub, flags);
				if (bus->dhd->busstate != DHD_BUS_SUSPEND)
					bus->dhd->busstate = DHD_BUS_LOAD;
				DHD_LINUX_GENERAL_UNLOCK(pub, flags);
			}
#endif
			else {
				goto fail;
			}
		}
	}
	else {
		DBUSERR(("%s: dbus_attach failed\n", __FUNCTION__));
		goto fail;
	}

#if defined(BCMDHD_MODULAR) && defined(INSMOD_FW_LOAD)
	if (dlneeded > 0)
		net_attach_now = FALSE;
#endif

	if (!net_attached && (net_attach_now || (dlneeded == 0))) {
		if (dhd_dbus_sync_dongle(pub, dlneeded)) {
			goto fail;
		}
		if (dhd_attach_net(bus->dhd, TRUE) != 0) {
			DBUSERR(("%s: Net attach 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
		net_attached = TRUE;
	}
	else if (net_attached && (pub->up == 1) && (dlneeded == 0)) {
		// kernel resume case
		pub->hostsleep = 0;
		ret = dhd_dbus_sync_dongle(pub, dlneeded);
#ifdef WL_CFG80211
		__wl_cfg80211_up_resume(pub);
		wl_cfgp2p_start_p2p_device_resume(pub);
#endif
		dhd_conf_set_suspend_resume(pub, 0);
		if (ret != DBUS_OK)
			goto fail;
	}

	if (!g_pub) {
		g_pub = pub;
	}

	DBUSTRACE(("%s: Exit\n", __FUNCTION__));
	if (net_attached) {
		wifi_set_adapter_status(adapter, WIFI_STATUS_NET_ATTACHED);
		wake_up_interruptible(&adapter->status_event);
		/* 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;
}

static void
dhd_dbus_disconnect_cb(void *arg)
{
	dhd_bus_t *bus = (dhd_bus_t *)arg;
	dhd_pub_t *pub = g_pub;
	osl_t *osh;
	wifi_adapter_info_t *adapter = NULL;

	adapter = (wifi_adapter_info_t *)pub->adapter;

	if (pub && !pub->dhd_remove && bus == NULL) {
		DBUSERR(("%s: bus is NULL\n", __FUNCTION__));
		return;
	}
	if (!adapter) {
		DBUSERR(("%s: adapter is NULL\n", __FUNCTION__));
		return;
	}

	printf("%s: Enter dhd_remove=%d on %s\n", __FUNCTION__,
		pub->dhd_remove, adapter->name);
	if (!pub->dhd_remove) {
		/* Advertise bus remove during rmmod */
		dhd_dbus_advertise_bus_remove(bus->dhd);
		dbus_detach(pub->bus);
		pub->bus = NULL;
		wake_up_interruptible(&adapter->status_event);
	} else {
		osh = pub->osh;
		dhd_detach(pub);
		if (pub->bus) {
			dbus_detach(pub->bus);
			pub->bus = NULL;
		}
		dhd_free(pub);
		g_pub = NULL;
		net_attached = FALSE;
		wifi_clr_adapter_status(adapter, WIFI_STATUS_NET_ATTACHED);
		if (MALLOCED(osh)) {
			DBUSERR(("%s: MEMORY LEAK %d bytes\n", __FUNCTION__, MALLOCED(osh)));
		}
		osl_detach(osh);
	}

	DBUSTRACE(("%s: Exit\n", __FUNCTION__));
}

#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(dhd_bus_register);
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);
EXPORT_SYMBOL(dhd_bus_unregister);

MODULE_LICENSE("GPL");

module_init(bcm_dbus_module_init);
module_exit(bcm_dbus_module_exit);

#endif  /* #ifdef LINUX_EXTERNAL_MODULE_DBUS */
