/*
 * Linux-specific portion of
 * Broadcom 802.11abg Networking Device Driver
 *
 * Copyright (C) 2008, Broadcom Corporation
 * All Rights Reserved.
 * 
 * THIS SOFTWARE IS OFFERED "AS IS", AND BROADCOM GRANTS NO WARRANTIES OF ANY
 * KIND, EXPRESS OR IMPLIED, BY STATUTE, COMMUNICATION OR OTHERWISE. BROADCOM
 * SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A SPECIFIC PURPOSE OR NONINFRINGEMENT CONCERNING THIS SOFTWARE.
 *
 * $Id: wl_linux.c,v 1.388.2.58.4.2 2008/11/03 23:41:20 Exp $
 */

#define LINUX_PORT

#define __UNDEF_NO_VERSION__

#include <typedefs.h>
#include <linuxver.h>
#include <osl.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 14)
#include <linux/module.h>
#endif

#include <linux/types.h>
#include <linux/errno.h>
#include <linux/pci.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>
#include <linux/delay.h>
#include <linux/string.h>
#include <linux/ethtool.h>
#include <linux/completion.h>
#include <linux/sched.h>
#ifdef LINUX_HYBRID
#include <linux/pci_ids.h>
#define WLC_MAXBSSCFG		1	/* single BSS configs */
#else /* LINUX_HYBRID */
#include <bcmdevs.h>
#endif /* LINUX_HYBRID */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 14)
#include <linux/ieee80211.h>
#endif

#include <asm/system.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/pgtable.h>
#include <asm/uaccess.h>
#include <asm/unaligned.h>

#include <proto/802.1d.h>

#include <siutils.h>
#include <epivers.h>
#include <bcmendian.h>
#include <proto/ethernet.h>
#include <bcmutils.h>
#include <pcicfg.h>
#include <wlioctl.h>
#include <wl_linux.h>
#include <wlc_key.h>

#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 4, 5)
#error "No support for Kernel Rev <= 2.4.5, As the older kernel revs doesn't support Tasklets"
#endif

typedef void wlc_info_t;
typedef void wlc_hw_info_t;
#include <wlc_pub.h>
#include <wl_dbg.h>
#ifdef WL_MONITOR
#include <wlc_ethereal.h>
#endif


#ifdef BCMJTAG
#include <bcmjtag.h>
#endif	/* BCMJTAG */


#ifdef CONFIG_SSB
#include <linux/ssb/ssb.h>
#endif

/* Linux wireless extension support */
#ifdef CONFIG_WIRELESS_EXT
#include <wl_iw.h>
struct iw_statistics *wl_get_wireless_stats(struct net_device *dev);
#endif /* CONFIG_WIRELESS_EXT */


#include <wl_export.h>
#ifdef TOE
#include <wl_toe.h>
#endif

#ifdef ARPOE
#include <wl_arpoe.h>
#endif

#ifdef BCMDBUS
#include "dbus.h"
/* BMAC_NOTES: Remove, but just in case your Linux system has this defined */
#undef CONFIG_PCI
#endif


#ifdef WLC_HIGH_ONLY
#include "bcm_rpc_tp.h"
#include "bcm_rpc.h"
#include "bcm_xdr.h"
#include "wlc_rpc.h"
#endif

/* BMAC Note: High-only driver is no longer working in softirq context as it needs to block and
 * sleep so perimeter lock has to be a semaphore instead of spinlock. This requires timers to be
 * submitted to workqueue instead of being on kernel timer
 */
typedef struct wl_timer {
	struct timer_list timer;
	struct wl_info *wl;
	void (*fn)(void *);
	void* arg; /* argument to fn */
	uint ms;
	bool periodic;
	bool set;
	struct wl_timer *next;
#ifdef BCMDBG
	char* name; /* Desription of the timer */
#endif
} wl_timer_t;

/* contortion to call functions at safe time */
/* In 2.6.20 kernels work functions get passed a pointer to the struct work, so things
 * will continue to work as long as the work structure is the first component of the task structure.
 */
typedef struct wl_task {
	struct work_struct work;
	void *context;
} wl_task_t;

#define WL_IFTYPE_BSS	1 /* iftype subunit for BSS */
#define WL_IFTYPE_WDS	2 /* iftype subunit for WDS */
#define WL_IFTYPE_MON	3 /* iftype subunit for MONITOR */

typedef struct wl_if {
#ifdef CONFIG_WIRELESS_EXT
	wl_iw_t		iw;		/* wireless extensions state (must be first) */
#endif /* CONFIG_WIRELESS_EXT */
	struct wl_if *next;
	struct wl_info *wl;		/* back pointer to main wl_info_t */
	struct net_device *dev;		/* virtual netdevice */
	int type;			/* interface type: WDS, BSS */
	struct wlc_if *wlcif;		/* wlc interface handle */
	struct ether_addr remote;	/* remote WDS partner */
	uint subunit;			/* WDS/BSS unit */
	bool dev_registed;		/* netdev registed done */
} wl_if_t;

struct wl_info {
	wlc_pub_t	*pub;		/* pointer to public wlc state */
	void		*wlc;		/* pointer to private common os-independent data */
	osl_t		*osh;		/* pointer to os handler */
	struct net_device *dev;		/* backpoint to device */
#ifdef WLC_HIGH_ONLY
	struct semaphore sem;		/* use semaphore to allow sleep */
#else
	spinlock_t	lock;		/* per-device perimeter lock */
	spinlock_t	isr_lock;	/* per-device ISR synchronization lock */
#endif
	uint		bustype;	/* bus type */
	bool		piomode;	/* set from insmod argument */
	void *regsva;			/* opaque chip registers virtual address */
	struct net_device_stats stats;	/* stat counter reporting structure */
	wl_if_t *if_list;		/* list of all interfaces */
	struct wl_info *next;		/* pointer to next wl_info_t in chain */
	atomic_t callbacks;		/* # outstanding callback functions */
	struct wl_timer *timers;	/* timer cleanup queue */
	struct tasklet_struct tasklet;	/* dpc tasklet */
	struct net_device *monitor;	/* monitor pseudo device */
	bool		resched;	/* dpc needs to be and is rescheduled */
#ifdef TOE
	wl_toe_info_t	*toei;		/* pointer to toe specific information */
#endif
#ifdef ARPOE
	wl_arp_info_t	*arpi;		/* pointer to arp agent offload info */
#endif
#if defined(DSLCPE_DELAY)
	shared_osl_t	oshsh;		/* shared info for osh */
#endif
#ifdef LINUXSTA_PS
	uint32		pci_psstate[16];	/* pci ps-state save/restore */
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 14)
	struct ieee80211_crypto_ops *tkipmodops;	/* external tkip module ops */
	struct ieee80211_tkip_data  *tkip_ucast_data;
	struct ieee80211_tkip_data  *tkip_bcast_data;
#endif
	/* RPC, handle, lock, txq, workitem */
#ifdef WLC_HIGH_ONLY
	rpc_info_t 	*rpc;		/* RPC handle */
	rpc_tp_info_t	*rpc_th;	/* RPC transport handle */
	wlc_rpc_ctx_t	rpc_dispatch_ctx;

	bool	   rpcq_dispatched;	/* Avoid scheduling multiple tasks */
	spinlock_t rpcq_lock;		/* Lock for the queue */
	rpc_buf_t *rpcq_head;		/* RPC Q */
	rpc_buf_t *rpcq_tail;		/* Points to the last buf */

	bool	   txq_dispatched;	/* Avoid scheduling multiple tasks */
	spinlock_t txq_lock;		/* Lock for the queue */
	struct sk_buff *txq_head;	/* TX Q */
	struct sk_buff *txq_tail;	/* Points to the last buf */

	wl_task_t	txq_task;	/* work queue for wl_start() */
	wl_task_t	multicast_task;	/* work queue for wl_set_multicast_list() */
#endif /* WLC_HIGH_ONLY */
	uint	stats_id;		/* the current set of stats */
	/* ping-pong stats counters updated by Linux watchdog */
	struct net_device_stats stats_watchdog[2];
#ifdef CONFIG_WIRELESS_EXT
	struct iw_statistics wstats_watchdog[2];
	struct iw_statistics wstats;
	int		phy_noise;
#endif /* CONFIG_WIRELESS_EXT */

};

static void wl_timer(ulong data);
static void _wl_timer(wl_timer_t *t);

#ifdef WLC_HIGH_ONLY
static void wl_rpc_down(void *wlh);
static void wl_rpcq_free(wl_info_t *wl);
static void wl_rpcq_dispatch(struct wl_task *task);
static void wl_rpc_dispatch_schedule(void *ctx, struct rpc_buf* buf);
#define RPCQ_LOCK(_wl, _flags) spin_lock_irqsave(&(_wl)->rpcq_lock, (_flags))
#define RPCQ_UNLOCK(_wl, _flags)  spin_unlock_irqrestore(&(_wl)->rpcq_lock, (_flags))

static void wl_start_txqwork(struct wl_task *task);
static void wl_txq_free(wl_info_t *wl);
#define TXQ_LOCK(_wl, _flags) spin_lock_irqsave(&(_wl)->txq_lock, (_flags))
#define TXQ_UNLOCK(_wl, _flags)  spin_unlock_irqrestore(&(_wl)->txq_lock, (_flags))

static void wl_set_multicast_list_workitem(struct work_struct *work);

static void wl_timer_task(wl_task_t *task);
#else

#endif /* WLC_HIGH_ONLY */

static int wl_linux_watchdog(void *ctx);

#ifdef LINUX_CRYPTO
struct ieee80211_tkip_data {
#define TKIP_KEY_LEN 32
	u8 key[TKIP_KEY_LEN];
	int key_set;

	u32 tx_iv32;
	u16 tx_iv16;
	u16 tx_ttak[5];
	int tx_phase1_done;

	u32 rx_iv32;
	u16 rx_iv16;
	u16 rx_ttak[5];
	int rx_phase1_done;
	u32 rx_iv32_new;
	u16 rx_iv16_new;

	u32 dot11RSNAStatsTKIPReplays;
	u32 dot11RSNAStatsTKIPICVErrors;
	u32 dot11RSNAStatsTKIPLocalMICFailures;

	int key_idx;

	struct crypto_tfm *tfm_arc4;
	struct crypto_tfm *tfm_michael;

	/* scratch buffers for virt_to_page() (crypto API) */
	u8 rx_hdr[16], tx_hdr[16];
};
#endif /* LINUX_CRYPTO */

static int wl_found = 0;

/* defines */
#define	WL_DEV_IF(dev)		((wl_if_t*)netdev_priv(dev))			/* points to wlif */
#define	WL_INFO(dev)		((wl_info_t*)(WL_DEV_IF(dev)->wl))	/* points to wl */


#ifdef WLC_HIGH_ONLY
#define WL_LOCK(wl)	down(&(wl)->sem)
#define WL_UNLOCK(wl)	up(&(wl)->sem)

#define WL_ISRLOCK(wl)
#define WL_ISRUNLOCK(wl)
#else
/* perimeter lock */
#define WL_LOCK(wl)	spin_lock_bh(&(wl)->lock)
#define WL_UNLOCK(wl)	spin_unlock_bh(&(wl)->lock)

/* locking from inside wl_isr */
#define WL_ISRLOCK(wl, flags) do {spin_lock(&(wl)->isr_lock); (void)(flags);} while (0)
#define WL_ISRUNLOCK(wl, flags) do {spin_unlock(&(wl)->isr_lock); (void)(flags);} while (0)

/* locking under WL_LOCK() to synchronize with wl_isr */
#define INT_LOCK(wl, flags)	spin_lock_irqsave(&(wl)->isr_lock, flags)
#define INT_UNLOCK(wl, flags)	spin_unlock_irqrestore(&(wl)->isr_lock, flags)
#endif	/* WLC_HIGH_ONLY */



#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20)
#define	WL_ISR(i, d, p)		wl_isr((i), (d))
#else
#define	WL_ISR(i, d, p)		wl_isr((i), (d), (p))
#endif	/* < 2.6.20 */

/* local prototypes */
#ifdef BCMDBG_MEM
static void wl_malloc_dump(osl_t *osh);
#endif /* BCMDBG_MEM */
static int wl_open(struct net_device *dev);
static int wl_close(struct net_device *dev);
static int wl_start(struct sk_buff *skb, struct net_device *dev);
static int wl_start_int(wl_if_t *wlif, struct sk_buff *skb);

static struct net_device_stats *wl_get_stats(struct net_device *dev);
static int wl_set_mac_address(struct net_device *dev, void *addr);
static void wl_set_multicast_list(struct net_device *dev);
static void _wl_set_multicast_list(struct net_device *dev);
static int wl_ethtool(wl_info_t *wl, void *uaddr, wl_if_t *wlif);
static void wl_dpc(ulong data);
static void wl_link_up(wl_info_t *wl);
static void wl_link_down(wl_info_t *wl);
static void wl_mic_error(wl_info_t *wl, struct ether_addr *ea, bool group, bool flush_txq);
#if defined(AP) || defined(DSLCPE_DELAY) || defined(WLC_HIGH_ONLY) || \
	defined(WL_MONITOR)
static int wl_schedule_task(wl_info_t *wl, void (*fn)(struct wl_task *), void *context);
#endif
#if defined(CONFIG_PROC_FS)
static int wl_read_proc(char *buffer, char **start, off_t offset, int length, int *eof, void *data);
#endif /* defined(CONFIG_PROC_FS) */
#ifdef BCMDBG
static int wl_dump(wl_info_t *wl, struct bcmstrbuf *b);
#endif /* BCMDBG */
struct wl_if *wl_alloc_if(wl_info_t *wl, int iftype, uint unit, struct wlc_if* wlc_if);
static void wl_free_if(wl_info_t *wl, wl_if_t *wlif);


#ifdef BCMJTAG
static void *wl_jtag_probe(uint16 venid, uint16 devid, void *regsva, void *param);
static void wl_jtag_detach(void *wl);
static void wl_jtag_poll(void *wl);
#endif


#if	defined(CONFIG_PCI) && !defined(BCMJTAG)
/* recognized PCI IDs */
static struct pci_device_id wl_id_table[] = {
#ifdef PCOEM_LINUXSTA
	{ PCI_VENDOR_ID_BROADCOM, 0x4311, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
	{ PCI_VENDOR_ID_BROADCOM, 0x4312, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
	{ PCI_VENDOR_ID_BROADCOM, 0x4313, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
	{ PCI_VENDOR_ID_BROADCOM, 0x4315, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
	{ PCI_VENDOR_ID_BROADCOM, 0x4328, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
	{ PCI_VENDOR_ID_BROADCOM, 0x4329, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
	{ PCI_VENDOR_ID_BROADCOM, 0x432a, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
	{ PCI_VENDOR_ID_BROADCOM, 0x432b, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
	{ PCI_VENDOR_ID_BROADCOM, 0x432c, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
	{ PCI_VENDOR_ID_BROADCOM, 0x432d, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 },
#else
	{ PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID,
	PCI_CLASS_NETWORK_OTHER << 8, 0xffff00, 0 },
#endif
	{ 0 }
};
MODULE_DEVICE_TABLE(pci, wl_id_table);
#endif	


#ifdef BCMDBG
static int msglevel = 0xdeadbeef;
module_param(msglevel, int, 0);
static int msglevel2 = 0xdeadbeef;
module_param(msglevel2, int, 0);
#endif /* BCMDBG */

static int oneonly = 0;
module_param(oneonly, int, 0);

static int piomode = 0;
module_param(piomode, int, 0);

#if defined(BCMJTAG) || defined(BCMSLTGT)
static int nompc = 1;
#else
static int nompc = 0;
#endif
module_param(nompc, int, 0);

static char name[IFNAMSIZ] = "wl%d";
module_param_string(name, name, IFNAMSIZ, 0);

/* BCMSLTGT: slow target */
#if defined(BCMJTAG) || defined(BCMSLTGT)
/* host and target have different clock speeds */
static uint htclkratio = 2000;
module_param(htclkratio, int, 0);
#endif	/* defined(BCMJTAG) || defined(BCMSLTGT) */

#ifndef	SRCBASE
#define	SRCBASE "."
#endif /* SRCBASE */

#if defined(DSLCPE_DELAY)
typedef struct fn_info {
	wl_info_t *wl;
	void (*fn)(void *);
	void *context;
} fn_info_t;

static void
_wl_schedule_fn(wl_task_t *task)
{
	fn_info_t *fi = (fn_info_t *) task->context;
	wl_info_t *wl = fi->wl;

	atomic_dec(&wl->callbacks);

	WL_LOCK(wl);
	fi->fn(fi->context);
	WL_UNLOCK(wl);
	MFREE(wl->osh, task, sizeof(wl_task_t));
	MFREE(wl->osh, fi, sizeof(fn_info_t));
	schedule();
}

void
wl_schedule_fn(wl_info_t *wl, void (*fn)(void *), void *context)
{
	fn_info_t *fi;

	if ((fi = MALLOC(wl->osh, sizeof(wl_task_t))) != NULL) {

		fi->wl = wl;
		fi->fn = fn;
		fi->context = context;

		if (wl_schedule_task(wl, _wl_schedule_fn, fi))
			MFREE(wl->osh, fi, sizeof(fn_info_t));
	}
}
#endif /* DSLCPE_DELAY */

#define WL_DEFAULT_OPS \
	.ndo_open = wl_open, \
	.ndo_stop = wl_close, \
	.ndo_start_xmit = wl_start, \
	.ndo_get_stats = wl_get_stats, \
	.ndo_set_mac_address = wl_set_mac_address, \
	.ndo_set_multicast_list = wl_set_multicast_list, \
	.ndo_do_ioctl = wl_ioctl

static const struct net_device_ops wl_ops = {
	WL_DEFAULT_OPS,
};

static
void wl_if_setup(struct net_device *dev)
{
	dev->netdev_ops = &wl_ops;
#ifdef CONFIG_WIRELESS_EXT
#if WIRELESS_EXT > 12
	dev->wireless_handlers = (struct iw_handler_def *) &wl_iw_handler_def;
#endif /* WIRELESS_EXT > 12 */
#endif /* CONFIG_WIRELESS_EXT */
}

/** 
 * attach to the WL device.
 *
 * Attach to the WL device identified by vendor and device parameters.
 * regs is a host accessible memory address pointing to WL device registers.
 *
 * wl_attach is not defined as static because in the case where no bus
 * is defined, wl_attach will never be called, and thus, gcc will issue
 * a warning that this function is defined but not used if we declare
 * it as static.
 */
static wl_info_t *
wl_attach(uint16 vendor, uint16 device, ulong regs, uint bustype, void *btparam, uint irq)
{
	struct net_device *dev;
	wl_if_t *wlif;
	wl_info_t *wl;
#if defined(CONFIG_PROC_FS)
	char tmp[128];
#endif
	osl_t *osh;
	int unit;
	uint err;

	unit = wl_found;

	if (oneonly && (unit != 0)) {
		WL_ERROR(("wl%d: wl_attach: oneonly is set, exiting\n", unit));
		return NULL;
	}

	/* Requires pkttag feature */
	osh = osl_attach(btparam, bustype, TRUE);
	ASSERT(osh);

	/* allocate private info */
	if ((wl = (wl_info_t*) MALLOC(osh, sizeof(wl_info_t))) == NULL) {
		WL_ERROR(("wl%d: malloc wl_info_t, out of memory, malloced %d bytes\n", unit,
			MALLOCED(osh)));
		osl_detach(osh);
		return NULL;
	}
	bzero(wl, sizeof(wl_info_t));

	wl->osh = osh;
	atomic_set(&wl->callbacks, 0);

#ifdef WLC_HIGH_ONLY
	wl->rpc_th = bcm_rpc_tp_attach(osh, NULL);
	if (wl->rpc_th == NULL) {
		WL_ERROR(("wl%d: %s: bcm_rpc_tp_attach failed! \n", unit, __FUNCTION__));
		goto fail;
	}

	wl->rpc = bcm_rpc_attach(NULL, osh, wl->rpc_th);
	if (wl->rpc == NULL) {
		WL_ERROR(("wl%d: %s: bcm_rpc_attach failed! \n", unit, __FUNCTION__));
		goto fail;
	}

	/* init tx work queue for wl_start/send pkt; no need to destroy workitem  */
	MY_INIT_WORK(&wl->txq_task.work, (work_func_t)wl_start_txqwork);
	wl->txq_task.context = wl;

	/* init work queue for wl_set_multicast_list(); no need to destroy workitem  */
	MY_INIT_WORK(&wl->multicast_task.work, (work_func_t)wl_set_multicast_list_workitem);
#endif /* WLC_HIGH_ONLY */

	wlif = wl_alloc_if(wl, WL_IFTYPE_BSS, unit, NULL);
	if (!wlif) {
		WL_ERROR(("wl%d: wl_alloc_if failed\n", unit));
		MFREE(osh, wl, sizeof(wl_info_t));
		osl_detach(osh);
		return NULL;
	}

	dev = wlif->dev;
	wl->dev = dev;
	wl_if_setup(dev);

	/* map chip registers (47xx: and sprom) */
	dev->base_addr = regs;

	WL_TRACE(("wl%d: Bus: ", unit));
	if (bustype == PCMCIA_BUS) {
		/* Disregard command overwrite */
		wl->piomode = TRUE;
		WL_TRACE(("PCMCIA\n"));
	} else if (bustype == PCI_BUS) {
		/* piomode can be overwritten by command argument */
		wl->piomode = piomode;
		WL_TRACE(("PCI/%s\n", wl->piomode ? "PIO" : "DMA"));
	}
#ifdef BCMJTAG
	else if (bustype == JTAG_BUS) {
		/* Disregard command option overwrite */
		wl->piomode = TRUE;
		WL_TRACE(("JTAG\n"));
	}
#endif	/* BCMJTAG */
	else if (bustype == RPC_BUS) {
		/* Do nothing */
	} else if (bustype == SI_BUS) {
		/* Do nothing */
	} else {
		bustype = PCI_BUS;
		WL_TRACE(("force to PCI\n"));
	}
	wl->bustype = bustype;

#ifdef BCMJTAG
	if (wl->bustype == JTAG_BUS)
		wl->regsva = (void *)dev->base_addr;
	else
#endif
#ifdef WLC_HIGH_ONLY
	if (wl->bustype == RPC_BUS) {
		wl->regsva = (void *)0;
		btparam = wl->rpc;
	} else
#endif
	if ((wl->regsva = ioremap_nocache(dev->base_addr, PCI_BAR0_WINSZ)) == NULL) {
		WL_ERROR(("wl%d: ioremap() failed\n", unit));
		goto fail;
	}

#ifdef WLC_HIGH_ONLY
	spin_lock_init(&wl->rpcq_lock);
	spin_lock_init(&wl->txq_lock);

	init_MUTEX(&wl->sem);
#else
	spin_lock_init(&wl->lock);
	spin_lock_init(&wl->isr_lock);
#endif

#if defined(DSLCPE_DELAY)
	wl->oshsh.lock = &wl->lock;
	wl->oshsh.wl = wl;
	osl_oshsh_init(wl->osh, &wl->oshsh);
#endif

	/* common load-time initialization */
	if (!(wl->wlc = wlc_attach((void *) wl, vendor, device, unit, wl->piomode,
		osh, wl->regsva, wl->bustype, btparam, &err))) {
		printf("%s: %s driver failed with code %d\n", dev->name, EPI_VERSION_STR, err);
		goto fail;
	}
	wl->pub = (wlc_pub_t *)wl->wlc;

#ifdef WLC_HIGH_ONLY
	REGOPSSET(osh, (osl_rreg_fn_t)wlc_reg_read, (osl_wreg_fn_t)wlc_reg_write, wl->wlc);
	wl->rpc_dispatch_ctx.rpc = wl->rpc;
	wl->rpc_dispatch_ctx.wlc = wl->wlc;
	bcm_rpc_rxcb_init(wl->rpc, wl, wl_rpc_dispatch_schedule, wl, wl_rpc_down, NULL);
#endif
	if (nompc) {
		if (wlc_iovar_setint(wl->wlc, "mpc", 0)) {
			WL_ERROR(("wl%d: Error setting MPC variable to 0\n", unit));
		}
	}
#if defined(CONFIG_PROC_FS)
	/* create /proc/net/wl<unit> */
	sprintf(tmp, "net/wl%d", wl->pub->unit);
	create_proc_read_entry(tmp, 0, 0, wl_read_proc, (void*)wl);
#endif /* defined(CONFIG_PROC_FS) */

	bcopy(&wl->pub->cur_etheraddr, dev->dev_addr, ETHER_ADDR_LEN);

	/* setup the bottom half handler */
	tasklet_init(&wl->tasklet, wl_dpc, (ulong)wl);

#ifdef TOE
	/* allocate the toe info struct */
	if ((wl->toei = wl_toe_attach(wl->wlc)) == NULL) {
		WL_ERROR(("wl%d: wl_toe_attach failed\n", unit));
		goto fail;
	}
#endif

#ifdef ARPOE
	/* allocate the arp info struct */
	if ((wl->arpi = wl_arp_attach(wl->wlc)) == NULL) {
		WL_ERROR(("wl%d: wl_arp_attach failed\n", unit));
		goto fail;
	}
#endif

#ifdef WLC_LOW
	/* register our interrupt handler */
#ifdef BCMJTAG
	if (wl->bustype != JTAG_BUS)
#endif	/* BCMJTAG */
	{
		if (request_irq(irq, wl_isr, IRQF_SHARED|IRQF_SAMPLE_RANDOM, dev->name, wl)) {
			WL_ERROR(("wl%d: request_irq() failed\n", unit));
			goto fail;
		}
		dev->irq = irq;
	}
#endif /* WLC_LOW */

	if (wl->bustype == PCI_BUS) {
		struct pci_dev *pci_dev = (struct pci_dev *)btparam;
		if (pci_dev != NULL)
			SET_NETDEV_DEV(dev, &pci_dev->dev);
	}

	if (register_netdev(dev)) {
		WL_ERROR(("wl%d: register_netdev() failed\n", unit));
		goto fail;
	}
	wlif->dev_registed = TRUE;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 14)
#ifdef LINUX_CRYPTO
	/* load the tkip module */
	wl->tkipmodops = ieee80211_get_crypto_ops("TKIP");
	if (wl->tkipmodops == NULL) {
		request_module("ieee80211_crypt_tkip");
		wl->tkipmodops = ieee80211_get_crypto_ops("TKIP");
	}
#endif /* LINUX_CRYPTO */
#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 14) */
#ifdef CONFIG_WIRELESS_EXT
	wlif->iw.wlinfo = (void *)wl;
#endif

#ifdef PCOEM_LINUXSTA
	/* set led default duty-cycle */
	if (wlc_iovar_setint(wl->wlc, "leddc", 0xa0000)) {
		WL_ERROR(("wl%d: Error setting led duty-cycle\n", unit));
	}
	if (wlc_set(wl->wlc, WLC_SET_PM, PM_FAST)) {
		WL_ERROR(("wl%d: Error setting PM variable to FAST PS\n", unit));
	}
	/* default vlan off */
	if (wlc_iovar_setint(wl->wlc, "vlan_mode", OFF)) {
		WL_ERROR(("wl%d: Error setting vlan mode OFF\n", unit));
	}
	/* default infra_mode is Infrastructure */
	if (wlc_set(wl->wlc, WLC_SET_INFRA, 1)) {
		WL_ERROR(("wl%d: Error setting infra_mode to infrastructure\n", unit));
	}
#endif /* PCOEM_LINUXSTA */

	/* register module */
	wlc_module_register(wl->pub, NULL, "linux", wl, NULL, wl_linux_watchdog, NULL);

#ifdef BCMDBG
	wlc_dump_register(wl->pub, "wl", (dump_fn_t)wl_dump, (void *)wl);
#endif
	if (wl->bustype == SI_BUS)
		device = si_d11_devid(wl->pub->sih);
	/* print hello string */
	printf("%s: Broadcom BCM%04x 802.11 Wireless Controller " EPI_VERSION_STR,
		dev->name, device);

#ifdef BCMDBG
	printf(" (Compiled in " SRCBASE " at " __TIME__ " on " __DATE__ ")");
#endif /* BCMDBG */
	printf("\n");

	wl_found++;
	return wl;

fail:
	wl_free(wl);
	return NULL;
}

#ifdef BCMDBUS
static void *
wl_dbus_probe_cb(void *arg, const char *desc, uint32 bustype, uint32 hdrlen)
{
	wl_info_t *wl;
	WL_ERROR(("%s: \n", __FUNCTION__));

	if (!(wl = wl_attach(BCM_DNGL_VID, BCM_DNGL_BDC_PID,
		(ulong)NULL /* regsva */, RPC_BUS, NULL /* RPC_BT */, 0))) {
		WL_ERROR(("%s: wl_attach failed\n", __FUNCTION__));
	}

	/* This is later passed to wl_dbus_disconnect_cb */
	return wl;
}

static void
wl_dbus_disconnect_cb(void *arg)
{
	wl_info_t *wl = arg;

	WL_ERROR(("%s: \n", __FUNCTION__));

	if (wl) {
#ifdef WLC_HIGH_ONLY
		wlc_device_removed(wl->wlc);
		bcm_rpc_down(wl->rpc);
#endif
		WL_LOCK(wl);
		wl_down(wl);
		WL_UNLOCK(wl);
		wl_free(wl);
	}
}
#endif /* BCMDBUS */

#if defined(CONFIG_PROC_FS)
static int
wl_read_proc(char *buffer, char **start, off_t offset, int length, int *eof, void *data)
{
	wl_info_t *wl;
	int len;
	off_t pos;
	off_t begin;

	len = pos = begin = 0;

	wl = (wl_info_t*) data;

	WL_LOCK(wl);
	/* pass space delimited variables for dumping */
#ifdef BCMDBG
	wlc_iovar_dump(wl->wlc, "all", strlen("all") + 1, buffer, PAGE_SIZE);
	len = strlen(buffer);
#endif /* BCMDBG */
	WL_UNLOCK(wl);
	pos = begin + len;

	if (pos < offset) {
		len = 0;
		begin = pos;
	}

	*eof = 1;

	*start = buffer + (offset - begin);
	len -= (offset - begin);

	if (len > length)
		len = length;

	return (len);
}
#endif /* defined(CONFIG_PROC_FS) */

/* For now, JTAG, SDIO, and PCI are mutually exclusive.  When this changes, remove
 * #if !defined(BCMJTAG) && !defined(BCMSDIO) ... #endif conditionals.
 */
#if !defined(BCMJTAG)
#ifdef CONFIG_PCI
static void __devexit wl_remove(struct pci_dev *pdev);
/** 
 * determines if a device is a WL device, and if so, attaches it.
 *
 * This function determines if a device pointed to by pdev is a WL device,
 * and if so, performs a wl_attach() on it.
 *
 */
int __devinit
wl_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
	int rc;
	wl_info_t *wl;
#ifdef LINUXSTA_PS
	uint32 val;
#endif

	WL_TRACE(("%s: bus %d slot %d func %d irq %d\n", __FUNCTION__,
	          pdev->bus->number, PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn), pdev->irq));

	if ((pdev->vendor != PCI_VENDOR_ID_BROADCOM) ||
	    (((pdev->device & 0xff00) != 0x4300) &&
	     ((pdev->device & 0xff00) != 0x4700)))
		return (-ENODEV);

	rc = pci_enable_device(pdev);
	if (rc) {
		WL_ERROR(("%s: Cannot enable device %d-%d_%d\n", __FUNCTION__,
		          pdev->bus->number, PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn)));
		return (-ENODEV);
	}
	pci_set_master(pdev);

#ifdef LINUXSTA_PS
	/*
	 * Disable the RETRY_TIMEOUT register (0x41) to keep
	 * PCI Tx retries from interfering with C3 CPU state.
	 * Code taken from ipw2100 driver
	 */
	pci_read_config_dword(pdev, 0x40, &val);
	if ((val & 0x0000ff00) != 0)
		pci_write_config_dword(pdev, 0x40, val & 0xffff00ff);
#endif /* LINUXSTA_PS */

	wl = wl_attach(pdev->vendor, pdev->device, pci_resource_start(pdev, 0), PCI_BUS, pdev,
		pdev->irq);
	if (!wl)
		return -ENODEV;

	pci_set_drvdata(pdev, wl);

	return 0;
}

#ifdef LINUXSTA_PS
static int
wl_suspend(struct pci_dev *pdev, DRV_SUSPEND_STATE_TYPE state)
{
	wl_info_t *wl = (wl_info_t *) pci_get_drvdata(pdev);

	WL_TRACE(("wl: wl_suspend\n"));

	wl = (wl_info_t *) pci_get_drvdata(pdev);
	if (!wl) {
		WL_ERROR(("wl: wl_suspend: pci_get_drvdata failed\n"));
		return -ENODEV;
	}

	WL_LOCK(wl);
	WL_APSTA_UPDN(("wl%d (%s): wl_suspend() -> wl_down()\n", wl->pub->unit, wl->dev->name));
	wl_down(wl);
	wl->pub->hw_up = FALSE;
	WL_UNLOCK(wl);
	PCI_SAVE_STATE(pdev, wl->pci_psstate);
	pci_disable_device(pdev);
	return pci_set_power_state(pdev, PCI_D3hot);
}

static int
wl_resume(struct pci_dev *pdev)
{
	wl_info_t *wl = (wl_info_t *) pci_get_drvdata(pdev);
	int err = 0;
	uint32 val;

	WL_TRACE(("wl: wl_resume\n"));

	if (!wl) {
		WL_ERROR(("wl: wl_resume: pci_get_drvdata failed\n"));
	        return -ENODEV;
	}

	err = pci_set_power_state(pdev, PCI_D0);
	if (err)
		return err;

	PCI_RESTORE_STATE(pdev, wl->pci_psstate);

	err = pci_enable_device(pdev);
	if (err)
		return err;

	pci_set_master(pdev);
	/*
	 * Suspend/Resume resets the PCI configuration space, so we have to
	 * re-disable the RETRY_TIMEOUT register (0x41) to keep
	 * PCI Tx retries from interfering with C3 CPU state
	 * Code taken from ipw2100 driver
	 */
	pci_read_config_dword(pdev, 0x40, &val);
	if ((val & 0x0000ff00) != 0)
		pci_write_config_dword(pdev, 0x40, val & 0xffff00ff);

	WL_LOCK(wl);
	WL_APSTA_UPDN(("wl%d: (%s): wl_resume() -> wl_up()\n", wl->pub->unit, wl->dev->name));
	err = wl_up(wl);
	WL_UNLOCK(wl);

	return (err);
}
#endif /* LINUXSTA_PS */

static void __devexit
wl_remove(struct pci_dev *pdev)
{
	wl_info_t *wl = (wl_info_t *) pci_get_drvdata(pdev);

	if (!wl) {
		WL_ERROR(("wl: wl_remove: pci_get_drvdata failed\n"));
		return;
	}

	if (!wlc_chipmatch(pdev->vendor, pdev->device)) {
		WL_ERROR(("wl: wl_remove: wlc_chipmatch failed\n"));
		return;
	}

	WL_LOCK(wl);
	WL_APSTA_UPDN(("wl%d (%s): wl_remove() -> wl_down()\n", wl->pub->unit, wl->dev->name));
	wl_down(wl);
	WL_UNLOCK(wl);
	wl_free(wl);
	pci_disable_device(pdev);
	pci_set_drvdata(pdev, NULL);
}

static struct pci_driver wl_pci_driver = {
	name:		"wl",
	probe:		wl_pci_probe,
#ifdef LINUXSTA_PS
	suspend:	wl_suspend,
	resume:		wl_resume,
#endif /* LINUXSTA_PS */
	remove:		__devexit_p(wl_remove),
	id_table:	wl_id_table,
	};
#endif	/* CONFIG_PCI */
#endif  


static int wl_ssb_probe(struct ssb_device *dev, const struct ssb_device_id *id)
{
	wl_info_t *wl;
	void *mmio;

	if (dev->bus->bustype != SSB_BUSTYPE_SSB) {
		printk("Attaching to SSB behind PCI is not supported. Please remove the b43 ssb bridge\n");
		return -EINVAL;
	}

	mmio = (void *) 0x18000000 + dev->core_index * 0x1000;
	wl = wl_attach(id->vendor, id->coreid, (ulong) mmio, SI_BUS, dev, dev->irq);
	if (!wl) {
		printk("wl_attach failed\n");
		return -ENODEV;
	}

	ssb_set_drvdata(dev, wl);

	return 0;
}

static void wl_ssb_remove(struct ssb_device *dev)
{
	wl_info_t *wl = (wl_info_t *) ssb_get_drvdata(dev);

	WL_LOCK(wl);
	WL_APSTA_UPDN(("wl%d (%s): wl_remove() -> wl_down()\n", wl->pub->unit, wl->dev->name));
	wl_down(wl);
	WL_UNLOCK(wl);
	wl_free(wl);
	ssb_set_drvdata(dev, NULL);
}

static const struct ssb_device_id wl_ssb_tbl[] = {
	SSB_DEVICE(SSB_VENDOR_BROADCOM, SSB_DEV_80211, SSB_ANY_REV),
	SSB_DEVTABLE_END
};

#ifdef CONFIG_SSB
static struct ssb_driver wl_ssb_driver = {
	.name	= KBUILD_MODNAME,
	.id_table = wl_ssb_tbl,
	.probe = wl_ssb_probe,
	.remove = wl_ssb_remove,
};
#endif

#ifdef BCMJTAG
static bcmjtag_driver_t wl_jtag_driver = {
		wl_jtag_probe,
		wl_jtag_detach,
		wl_jtag_poll,
		};
#endif	/* BCMJTAG */


/** 
 * This is the main entry point for the WL driver.
 *
 * This function determines if a device pointed to by pdev is a WL device,
 * and if so, performs a wl_attach() on it.
 *
 */
static int __init
wl_module_init(void)
{
	int error = -ENODEV;

#ifdef CONFIG_SSB
	error = ssb_driver_register(&wl_ssb_driver);
	if (error)
		return error;
#endif	/* CONFIG_SSB */

#ifdef CONFIG_PCI
	error = pci_register_driver(&wl_pci_driver);
	if (error)
		goto error_pci;
#endif	/* CONFIG_PCI */

	return 0;

error_pci:
	ssb_driver_unregister(&wl_ssb_driver);
	return error;
}

/** 
 * This function unloads the WL driver from the system.
 *
 * This function unconditionally unloads the WL driver module from the
 * system.
 *
 */
static void __exit
wl_module_exit(void)
{
#ifdef CONFIG_PCI
	pci_unregister_driver(&wl_pci_driver);
#endif	/* CONFIG_PCI */
#ifdef CONFIG_SSB
	ssb_driver_unregister(&wl_ssb_driver);
#endif	/* CONFIG_SSB */
}

module_init(wl_module_init);
module_exit(wl_module_exit);

/** 
 * This function frees the WL per-device resources.
 *
 * This function frees resources owned by the WL device pointed to
 * by the wl parameter.
 *
 */
void
wl_free(wl_info_t *wl)
{
	wl_timer_t *t, *next;
	osl_t *osh;

	WL_TRACE(("wl: wl_free\n"));
#ifdef BCMJTAG
	if (wl->bustype != JTAG_BUS)
#endif	/* BCMJTAG */
	{
		if (wl->dev && wl->dev->irq)
			free_irq(wl->dev->irq, wl);
	}

	if (wl->dev) {
		wl_free_if(wl, WL_DEV_IF(wl->dev));
		wl->dev = NULL;
	}

#ifdef TOE
	wl_toe_detach(wl->toei);
#endif

#ifdef ARPOE
	wl_arp_detach(wl->arpi);
#endif

	/* kill dpc */
	tasklet_kill(&wl->tasklet);

	if (wl->pub) {
		wlc_module_unregister(wl->pub, "linux", wl);
	}

	/* free common resources */
	if (wl->wlc) {
#if defined(CONFIG_PROC_FS)
		char tmp[128];
		/* remove /proc/net/wl<unit> */
		sprintf(tmp, "net/wl%d", wl->pub->unit);
		remove_proc_entry(tmp, 0);
#endif /* defined(CONFIG_PROC_FS) */
		wlc_detach(wl->wlc);
		wl->wlc = NULL;
		wl->pub = NULL;
	}

	/* virtual interface deletion is deferred so we cannot spinwait */

	/* wait for all pending callbacks to complete */
	while (atomic_read(&wl->callbacks) > 0)
		schedule();

	/* free timers */
	for (t = wl->timers; t; t = next) {
		next = t->next;
#ifdef BCMDBG
		if (t->name)
			MFREE(wl->osh, t->name, strlen(t->name) + 1);
#endif
		MFREE(wl->osh, t, sizeof(wl_timer_t));
	}

	/* free monitor */
	if (wl->monitor) {
		wl_free_if(wl, netdev_priv(wl->monitor));
		wl->monitor = NULL;
	}

	osh = wl->osh;

	/*
	 * unregister_netdev() calls get_stats() which may read chip registers
	 * so we cannot unmap the chip registers until after calling unregister_netdev() .
	 */
	if (wl->regsva && BUSTYPE(wl->bustype) != SDIO_BUS &&
	    BUSTYPE(wl->bustype) != JTAG_BUS) {
		iounmap((void*)wl->regsva);
	}
	wl->regsva = NULL;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 14)
#ifdef LINUX_CRYPTO
	/* un register the TKIP module...if any */
	if (wl->tkipmodops != NULL) {
		if (wl->tkip_ucast_data) {
			wl->tkipmodops->deinit(wl->tkip_ucast_data);
			wl->tkip_ucast_data = NULL;
		}
		if (wl->tkip_bcast_data) {
			wl->tkipmodops->deinit(wl->tkip_bcast_data);
			wl->tkip_bcast_data = NULL;
		}
	}
#endif /* LINUX_CRYPTO */
#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 14) */

#ifdef WLC_HIGH_ONLY

	wl_rpcq_free(wl);
	wl_txq_free(wl);

	if (wl->rpc) {
		bcm_rpc_detach(wl->rpc);
		wl->rpc = NULL;
	}

	if (wl->rpc_th) {
		bcm_rpc_tp_detach(wl->rpc_th);
		wl->rpc_th = NULL;
	}
#endif /* WLC_HIGH_ONLY */

	MFREE(osh, wl, sizeof(wl_info_t));

#ifdef BCMDBG_MEM
	if (MALLOCED(osh)) {
		printf("Memory leak of bytes %d\n", MALLOCED(osh));
		wl_malloc_dump(osh);
	}
#else
	if (MALLOCED(osh)) {
		printf("Memory leak of bytes %d\n", MALLOCED(osh));
		ASSERT(0);
	}
#endif /* BCMDBG_MEM */

	osl_detach(osh);
}

#ifdef BCMDBG_MEM
static void
wl_malloc_dump(osl_t *osh)
{
	char buf[WLC_IOCTL_MAXLEN];
	struct bcmstrbuf b;
	bcm_binit((void *)&b, buf, WLC_IOCTL_MAXLEN);
	MALLOC_DUMP(osh, &b);
	printf("%s", b.origbuf);
}
#endif /* BCMDBG_MEM */

static int
wl_open(struct net_device *dev)
{
	wl_info_t *wl;
	int error;

	if (!dev)
		return -ENETDOWN;

	wl = WL_INFO(dev);

	WL_TRACE(("wl%d: wl_open\n", wl->pub->unit));

	WL_LOCK(wl);
	WL_APSTA_UPDN(("wl%d: (%s): wl_open() -> wl_up()\n",
	               wl->pub->unit, wl->dev->name));
	/* Since this is resume, reset hw to known state */
	error = wl_up(wl);
	if (!error) {
		error = wlc_set(wl->wlc, WLC_SET_PROMISC, (dev->flags & IFF_PROMISC));
	}
	WL_UNLOCK(wl);

	if (!error)
		OLD_MOD_INC_USE_COUNT;

	return (error? -ENODEV: 0);
}

static int
wl_close(struct net_device *dev)
{
	wl_info_t *wl;

	if (!dev)
		return -ENETDOWN;

	wl = WL_INFO(dev);

	WL_TRACE(("wl%d: wl_close\n", wl->pub->unit));

	WL_LOCK(wl);
	WL_APSTA_UPDN(("wl%d (%s): wl_close() -> wl_down()\n",
		wl->pub->unit, wl->dev->name));
	wl_down(wl);
	WL_UNLOCK(wl);

	OLD_MOD_DEC_USE_COUNT;

	return (0);
}

#ifdef WLC_LOW
/* transmit a packet */
static int BCMFASTPATH
wl_start(struct sk_buff *skb, struct net_device *dev)
{
	wl_if_t *wlif;

	if (!dev)
		return -ENETDOWN;

	wlif = WL_DEV_IF(dev);

	return wl_start_int(wlif, skb);
}
#endif	/* WLC_LOW */

static int BCMFASTPATH
wl_start_int(wl_if_t *wlif, struct sk_buff *skb)
{
	wl_info_t *wl;
	void *pkt;

	wl = wlif->wl;

	WL_TRACE(("wl%d: wl_start: len %d summed %d\n", wl->pub->unit, skb->len, skb->ip_summed));

	/* Convert the packet. Mainly attach a pkttag */
	if ((pkt = PKTFRMNATIVE(wl->osh, skb)) == NULL) {
		WL_ERROR(("wl%d: PKTFRMNATIVE failed!\n", wl->pub->unit));
		WLCNTINCR(wl->pub->_cnt.txnobuf);
		dev_kfree_skb_any(skb);
		return 0;
	}

#ifdef ARPOE
	/* Arp agent */
	if (ARPOE_ENAB(wl->pub)) {
		if (wl_arp_send_proc(wl->arpi, pkt) == ARP_REPLY_HOST) {
			PKTFREE(wl->osh, pkt, TRUE);
			return 0;
		}
	}
#endif

	WL_LOCK(wl);

#ifdef TOE
	/* Apply TOE */
	if (TOE_ENAB(wl->pub))
		wl_toe_send_proc(wl->toei, pkt);
#endif

	/* Fix the priority if WME is enabled */
	if (WME_ENAB(wl->pub) && (PKTPRIO(pkt) == 0))
		pktsetprio(pkt, FALSE);
	wlc_sendpkt(wl->wlc, pkt, wlif->wlcif);

	WL_UNLOCK(wl);

	return (0);
}

void
wl_txflowcontrol(wl_info_t *wl, bool state, int prio)
{
	wl_if_t *wlif;

	ASSERT(prio == ALLPRIO);
	for (wlif = wl->if_list; wlif != NULL; wlif = wlif->next) {
		if (state == ON)
			netif_stop_queue(wlif->dev);
		else
			netif_wake_queue(wlif->dev);
	}
}

#if defined(AP) || defined(DSLCPE_DELAY) || defined(WLC_HIGH_ONLY) || \
	defined(WL_MONITOR)
/* Schedule a completion handler to run at safe time */
static int
wl_schedule_task(wl_info_t *wl, void (*fn)(struct wl_task *task), void *context)
{
	wl_task_t *task;

	WL_TRACE(("wl%d: wl_schedule_task\n", wl->pub->unit));

	if (!(task = MALLOC(wl->osh, sizeof(wl_task_t)))) {
		WL_ERROR(("wl%d: wl_schedule_task: out of memory, malloced %d bytes\n",
			wl->pub->unit, MALLOCED(wl->osh)));
		return -ENOMEM;
	}

	MY_INIT_WORK(&task->work, (work_func_t)fn);
	task->context = context;

	if (!schedule_work(&task->work)) {
		WL_ERROR(("wl%d: schedule_work() failed\n", wl->pub->unit));
		MFREE(wl->osh, task, sizeof(wl_task_t));
		return -ENOMEM;
	}

	atomic_inc(&wl->callbacks);

	return 0;
}
#endif /* defined(AP) || defined(DSLCPE_DELAY) || defined(WLC_HIGH_ONLY) || defined(WL_MONITOR) */

struct wl_if *
wl_alloc_if(wl_info_t *wl, int iftype, uint subunit, struct wlc_if* wlcif)
{
	struct net_device *dev;
	wl_if_t *wlif;
	wl_if_t *p;

	dev = alloc_etherdev(sizeof(wl_if_t));
	wlif = netdev_priv(dev);
	bzero(wlif, sizeof(wl_if_t));
	strncpy(dev->name, name, IFNAMSIZ);

	wlif->type = iftype;
	wlif->dev = dev;
	wlif->wl = wl;
	wlif->wlcif = wlcif;
	wlif->subunit = subunit;

	/* match current flow control state */
	if (iftype != WL_IFTYPE_MON && wl->dev && netif_queue_stopped(wl->dev))
		netif_stop_queue(dev);

	/* add the interface to the interface linked list */
	if (wl->if_list == NULL)
		wl->if_list = wlif;
	else {
		p = wl->if_list;
		while (p->next != NULL)
			p = p->next;
		p->next = wlif;
	}
	return wlif;
}

static void
wl_free_if(wl_info_t *wl, wl_if_t *wlif)
{
	wl_if_t *p;

	/* check if register_netdev was successful */
	if (wlif->dev_registed)
		unregister_netdev(wlif->dev);

	/* remove the interface from the interface linked list */
	p = wl->if_list;
	if (p == wlif)
		wl->if_list = p->next;
	else {
		while (p != NULL && p->next != wlif)
			p = p->next;
		if (p != NULL)
			p->next = p->next->next;
	}

	if (wlif->dev) {
#if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24))
		MFREE(wl->osh, wlif->dev, sizeof(struct net_device));
#else
		free_netdev(wlif->dev);
#endif
	}
	MFREE(wl->osh, wlif, sizeof(wl_if_t));
}

#ifdef AP
/* ioctl */
static int
wl_wds_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
{
	wl_if_t *wds;
	wl_ioctl_t ioc;

	if (!dev)
		return -ENETDOWN;

	wds = WL_DEV_IF(dev);

	WL_TRACE(("wds%d: wl_wds_ioctl: cmd 0x%x\n", wds->subunit, cmd));

	if (cmd != SIOCDEVPRIVATE)
		if (cmd != SIOCETHTOOL)
			return -EINVAL;

	if (copy_from_user(&ioc, ifr->ifr_data, sizeof(wl_ioctl_t)))
		return -EFAULT;

	switch (ioc.cmd) {
	case WLC_WDS_GET_REMOTE_HWADDR:
		if (!ioc.buf || ioc.len < ETHER_ADDR_LEN)
			return -EINVAL;
		if (copy_to_user(ioc.buf, wds->remote.octet, ETHER_ADDR_LEN))
			return -EFAULT;
		break;
	default:
		return wl_ioctl(dev, ifr, cmd);
	}
	return 0;
}

/* Create a virtual interface. Call only from safe time! can't call register_netdev with WL_LOCK */
static const struct net_device_ops wl_wds_ops = {
	WL_DEFAULT_OPS,
	.ndo_do_ioctl = wl_wds_ioctl,
};

static void
_wl_add_if(wl_task_t *task)
{
	wl_if_t *wlif = (wl_if_t *) task->context;
	wl_info_t *wl = wlif->wl;
	struct net_device *dev = wlif->dev;

	if (wlif->type == WL_IFTYPE_WDS)
		dev->netdev_ops = &wl_wds_ops;

	bcopy(&wl->pub->cur_etheraddr, dev->dev_addr, ETHER_ADDR_LEN);

	if (register_netdev(dev)) {
		WL_ERROR(("wl%d: wl_add_if: register_netdev() failed for \"%s\"\n",
			wl->pub->unit, dev->name));
		goto done;
	}
	wlif->dev_registed = TRUE;

done:
	MFREE(wl->osh, task, sizeof(wl_task_t));
	atomic_dec(&wl->callbacks);
}

/* Schedule _wl_add_if() to be run at safe time. */
struct wl_if *
wl_add_if(wl_info_t *wl, struct wlc_if* wlcif, uint unit, struct ether_addr *remote)
{
	wl_if_t *wlif;
	char *devname;
	int iftype;

	if (remote) {
		iftype = WL_IFTYPE_WDS;
		devname = "wds";
	} else {
		iftype = WL_IFTYPE_BSS;
		devname = "wl";
	}

	wlif = wl_alloc_if(wl, iftype, unit, wlcif);

	if (!wlif) {
		WL_ERROR(("wl%d: wl_add_if: failed to create %s interface %d\n", wl->pub->unit,
			(remote)?"WDS":"BSS", unit));
		return NULL;
	}

	sprintf(wlif->dev->name, "%s%d.%d", devname, wl->pub->unit, wlif->subunit);
	if (remote)
		bcopy(remote, &wlif->remote, ETHER_ADDR_LEN);

	if (wl_schedule_task(wl, _wl_add_if, wlif)) {
		MFREE(wl->osh, wlif, sizeof(wl_if_t) + sizeof(struct net_device));
		return NULL;
	}

	return wlif;
}

/* Remove a virtual interface. Call only from safe time! */
static void
_wl_del_if(wl_task_t *task)
{
	wl_if_t *wlif = (wl_if_t *) task->context;
	wl_info_t *wl = wlif->wl;

	wl_free_if(wl, wlif);

	MFREE(wl->osh, task, sizeof(wl_task_t));
	atomic_dec(&wl->callbacks);
}

/* Schedule _wl_del_if() to be run at safe time. */
void
wl_del_if(wl_info_t *wl, wl_if_t *wlif)
{
	ASSERT(wlif != NULL);
	ASSERT(wlif->wl == wl);

	wlif->wlcif = NULL;

	if (wl_schedule_task(wl, _wl_del_if, wlif)) {
		WL_ERROR(("wl%d: wl_del_if: schedule_task() failed\n", wl->pub->unit));
		return;
	}
}
#endif /* AP */

/* Return pointer to interface name */
char *
wl_ifname(wl_info_t *wl, wl_if_t *wlif)
{
	if (wlif)
		return wlif->dev->name;
	else
		return wl->dev->name;
}

void
wl_init(wl_info_t *wl)
{
	WL_TRACE(("wl%d: wl_init\n", wl->pub->unit));

	wl_reset(wl);

	wlc_init(wl->wlc);
}

uint
wl_reset(wl_info_t *wl)
{
	WL_TRACE(("wl%d: wl_reset\n", wl->pub->unit));

	wlc_reset(wl->wlc);

	/* dpc will not be rescheduled */
	wl->resched = 0;

	return (0);
}

/*
 * These are interrupt on/off entry points. Disable interrupts
 * during interrupt state transition.
 */
void BCMFASTPATH
wl_intrson(wl_info_t *wl)
{
#ifdef WLC_LOW
	unsigned long flags;

	INT_LOCK(wl, flags);
	wlc_intrson(wl->wlc);
	INT_UNLOCK(wl, flags);
#endif /* WLC_LOW */
}

bool
wl_alloc_dma_resources(wl_info_t *wl, uint addrwidth)
{
	return TRUE;
}

uint32 BCMFASTPATH
wl_intrsoff(wl_info_t *wl)
{
#ifdef WLC_LOW
	unsigned long flags;
	uint32 status;

	INT_LOCK(wl, flags);
	status = wlc_intrsoff(wl->wlc);
	INT_UNLOCK(wl, flags);
	return status;
#else
	return 0;
#endif /* WLC_LOW */
}

void
wl_intrsrestore(wl_info_t *wl, uint32 macintmask)
{
#ifdef WLC_LOW
	unsigned long flags;

	INT_LOCK(wl, flags);
	wlc_intrsrestore(wl->wlc, macintmask);
	INT_UNLOCK(wl, flags);
#endif /* WLC_LOW */
}

int
wl_up(wl_info_t *wl)
{
	int error = 0;

	WL_TRACE(("wl%d: wl_up\n", wl->pub->unit));

	if (wl->pub->up)
		return (0);

	error = wlc_up(wl->wlc);

	/* wake (not just start) all interfaces */
	if (!error)
		wl_txflowcontrol(wl, OFF, ALLPRIO);

	return (error);
}

void
wl_down(wl_info_t *wl)
{
	wl_if_t *wlif;
	uint callbacks, ret_val;

	WL_TRACE(("wl%d: wl_down\n", wl->pub->unit));

	for (wlif = wl->if_list; wlif != NULL; wlif = wlif->next) {
		netif_down(wlif->dev);
		netif_stop_queue(wlif->dev);
	}


	/* call common down function */
	ret_val = wlc_down(wl->wlc);
	callbacks = atomic_read(&wl->callbacks) - ret_val;

	/* wait for down callbacks to complete */
	WL_UNLOCK(wl);

#ifndef WLC_HIGH_ONLY
	/* For HIGH_only driver, it's important to actually schedule other work,
	 * not just spin wait since everything runs at schedule level
	 */
	SPINWAIT((atomic_read(&wl->callbacks) > callbacks), 100 * 1000);
	ASSERT(atomic_read(&wl->callbacks) == callbacks);
#endif

	WL_LOCK(wl);
}

/* Retrieve current toe component enables, which are kept as a bitmap in toe_ol iovar */
static int
wl_toe_get(wl_info_t *wl, uint32 *toe_ol)
{
	if (wlc_iovar_getint(wl->wlc, "toe_ol", toe_ol) != 0)
		return -EOPNOTSUPP;

	return 0;
}

/* Set current toe component enables in toe_ol iovar, and set toe global enable iovar */
static int
wl_toe_set(wl_info_t *wl, uint32 toe_ol)
{
	if (wlc_iovar_setint(wl->wlc, "toe_ol", toe_ol) != 0)
		return -EOPNOTSUPP;

	/* Enable toe globally only if any components are enabled. */

	if (wlc_iovar_setint(wl->wlc, "toe", (toe_ol != 0)) != 0)
		return -EOPNOTSUPP;

	return 0;
}

static int
wl_ethtool(wl_info_t *wl, void *uaddr, wl_if_t *wlif)
{
	struct ethtool_drvinfo info;
	struct ethtool_value edata;
	uint32 cmd;
	uint32 toe_cmpnt, csum_dir;
	int ret;

	if (copy_from_user(&cmd, uaddr, sizeof(uint32)))
		return (-EFAULT);

	switch (cmd) {
	case ETHTOOL_GDRVINFO:
		bzero(&info, sizeof(info));
		info.cmd = cmd;
		sprintf(info.driver, "wl%d", wl->pub->unit);
		strcpy(info.version, EPI_VERSION_STR);
		if (copy_to_user(uaddr, &info, sizeof(info)))
			return (-EFAULT);
		break;

	/* Get toe offload components */
	case ETHTOOL_GRXCSUM:
	case ETHTOOL_GTXCSUM:
		if ((ret = wl_toe_get(wl, &toe_cmpnt)) < 0)
			return ret;

		csum_dir = (cmd == ETHTOOL_GTXCSUM) ? TOE_TX_CSUM_OL : TOE_RX_CSUM_OL;

		edata.cmd = cmd;
		edata.data = (toe_cmpnt & csum_dir) ? 1 : 0;

		if (copy_to_user(uaddr, &edata, sizeof(edata)))
			return (-EFAULT);
		break;

	/* Set toe offload components */
	case ETHTOOL_SRXCSUM:
	case ETHTOOL_STXCSUM:
		if (copy_from_user(&edata, uaddr, sizeof(edata)))
			return (-EFAULT);

		/* Read the current settings, update and write back */
		if ((ret = wl_toe_get(wl, &toe_cmpnt)) < 0)
			return ret;

		csum_dir = (cmd == ETHTOOL_STXCSUM) ? TOE_TX_CSUM_OL : TOE_RX_CSUM_OL;

		if (edata.data != 0)
			toe_cmpnt |= csum_dir;
		else
			toe_cmpnt &= ~csum_dir;

		if ((ret = wl_toe_set(wl, toe_cmpnt)) < 0)
			return ret;

		/* If setting TX checksum mode, tell Linux the new mode */
		if (cmd == ETHTOOL_STXCSUM) {
			if (edata.data)
				wl->dev->features |= NETIF_F_IP_CSUM;
			else
				wl->dev->features &= ~NETIF_F_IP_CSUM;
		}

		break;

	default:
		return (-EOPNOTSUPP);

	}

	return (0);
}

int
wl_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
{
	wl_info_t *wl;
	wl_if_t *wlif;
	void *buf = NULL;
	wl_ioctl_t ioc;
	int bcmerror;

	if (!dev)
		return -ENETDOWN;

	wl = WL_INFO(dev);
	wlif = WL_DEV_IF(dev);
	if (wlif == NULL || wl == NULL)
		return -ENETDOWN;

	bcmerror = 0;

	WL_TRACE(("wl%d: wl_ioctl: cmd 0x%x\n", wl->pub->unit, cmd));

#ifdef CONFIG_PREEMPT
	if (preempt_count())
		WL_ERROR(("wl%d: wl_ioctl: cmd = 0x%x, preempt_count=%d\n",
			wl->pub->unit, cmd, preempt_count()));
#endif

#ifdef CONFIG_WIRELESS_EXT
	/* linux wireless extensions */
	if ((cmd >= SIOCIWFIRST) && (cmd <= SIOCIWLAST)) {
		/* may recurse, do NOT lock */
		return wl_iw_ioctl(dev, ifr, cmd);
	}
#endif /* CONFIG_WIRELESS_EXT */

	if (cmd == SIOCETHTOOL)
		return (wl_ethtool(wl, (void*)ifr->ifr_data, wlif));

	switch (cmd) {
		case SIOCDEVPRIVATE :
			break;
		default:
			bcmerror = BCME_UNSUPPORTED;
			goto done2;
	}

	if (copy_from_user(&ioc, ifr->ifr_data, sizeof(wl_ioctl_t))) {
		bcmerror = BCME_BADADDR;
		goto done2;
	}

	/* optimization for direct ioctl calls from kernel */
	if (segment_eq(get_fs(), KERNEL_DS))
		buf = ioc.buf;

	else if (ioc.buf) {
		if (!(buf = (void *) MALLOC(wl->osh, MAX(ioc.len, WLC_IOCTL_MAXLEN)))) {
			bcmerror = BCME_NORESOURCE;
			goto done2;
		}

		if (copy_from_user(buf, ioc.buf, ioc.len)) {
			bcmerror = BCME_BADADDR;
			goto done1;
		}
	}

	WL_LOCK(wl);
	if (!capable(CAP_NET_ADMIN)) {
		bcmerror = BCME_EPERM;
	} else {
		bcmerror = wlc_ioctl(wl->wlc, ioc.cmd, buf, ioc.len, wlif->wlcif);
	}
	WL_UNLOCK(wl);

done1:
	if (ioc.buf && (ioc.buf != buf)) {
		if (copy_to_user(ioc.buf, buf, ioc.len))
			bcmerror = BCME_BADADDR;
		MFREE(wl->osh, buf, MAX(ioc.len, WLC_IOCTL_MAXLEN));
	}

done2:
	ASSERT(VALID_BCMERROR(bcmerror));
	if (bcmerror != 0)
		wl->pub->bcmerror = bcmerror;
	return (OSL_ERROR(bcmerror));
}

static struct net_device_stats*
wl_get_stats(struct net_device *dev)
{
	struct net_device_stats *stats = NULL, *stats_watchdog = NULL;
	wl_info_t *wl;

	if (!dev)
		return NULL;

	wl = WL_INFO(dev);

	/* At rmmod we will return NULL for calls to wl_get_stats
	 * if pub is already freed. Which will be the case
	 * during module_exit, where pub is deallocated and
	 * marked as NULL.
	 */
	if (!(wl->pub))
		return NULL;

	WL_TRACE(("wl%d: wl_get_stats\n", wl->pub->unit));

	stats = &wl->stats;
	ASSERT(wl->stats_id < 2);
	stats_watchdog = &wl->stats_watchdog[wl->stats_id];

	memcpy(stats, stats_watchdog, sizeof(struct net_device_stats));

	return (stats);
}

#ifdef CONFIG_WIRELESS_EXT
struct iw_statistics *
wl_get_wireless_stats(struct net_device *dev)
{
	int res;
	wl_info_t *wl;
	wl_if_t *wlif;
	struct iw_statistics *wstats = NULL, *wstats_watchdog = NULL;
	int phy_noise, rssi;

	if (!dev)
		return NULL;

	wl = WL_INFO(dev);
	wlif = WL_DEV_IF(dev);

	WL_TRACE(("wl%d: wl_get_wireless_stats\n", wl->pub->unit));

	wstats = &wl->wstats;
	ASSERT(wl->stats_id < 2);
	wstats_watchdog = &wl->wstats_watchdog[wl->stats_id];

	phy_noise = wl->phy_noise;
#if WIRELESS_EXT > 11
	wstats->discard.nwid = 0;
	wstats->discard.code = wstats_watchdog->discard.code;
	wstats->discard.fragment = wstats_watchdog->discard.fragment;
	wstats->discard.retries = wstats_watchdog->discard.retries;
	wstats->discard.misc = wstats_watchdog->discard.misc;

	wstats->miss.beacon = 0;
#endif /* WIRELESS_EXT > 11 */

	/* RSSI measurement is somewhat meaningless for AP in this context */
	if (AP_ENAB(wl->pub))
		rssi = 0;
	else {
		scb_val_t scb;
		if ((res = wlc_ioctl(wl->wlc, WLC_GET_RSSI, &scb, sizeof(scb), wlif->wlcif)))
			return NULL;
		rssi = scb.val;
	}

	if (rssi <= WLC_RSSI_NO_SIGNAL)
		wstats->qual.qual = 0;
	else if (rssi <= WLC_RSSI_VERY_LOW)
		wstats->qual.qual = 1;
	else if (rssi <= WLC_RSSI_LOW)
		wstats->qual.qual = 2;
	else if (rssi <= WLC_RSSI_GOOD)
		wstats->qual.qual = 3;
	else if (rssi <= WLC_RSSI_VERY_GOOD)
		wstats->qual.qual = 4;
	else
		wstats->qual.qual = 5;

	/* Wraps to 0 if RSSI is 0 */
	wstats->qual.level = 0x100 + rssi;
	wstats->qual.noise = 0x100 + phy_noise;
#if WIRELESS_EXT > 18
	wstats->qual.updated |= (IW_QUAL_ALL_UPDATED | IW_QUAL_DBM);
#else
	wstats->qual.updated |= 7;
#endif /* WIRELESS_EXT > 18 */

	return wstats;

}
#endif /* CONFIG_WIRELESS_EXT */

static int
wl_set_mac_address(struct net_device *dev, void *addr)
{
	wl_info_t *wl;
	struct sockaddr *sa = (struct sockaddr *) addr;

	if (!dev)
		return -ENETDOWN;

	wl = WL_INFO(dev);

	WL_TRACE(("wl%d: wl_set_mac_address\n", wl->pub->unit));

	WL_LOCK(wl);

	bcopy(sa->sa_data, dev->dev_addr, ETHER_ADDR_LEN);
	if (wlc_iovar_op(wl->wlc, "cur_etheraddr", NULL, 0, sa->sa_data, ETHER_ADDR_LEN,
		IOV_SET, (WL_DEV_IF(dev))->wlcif))
		WL_ERROR(("wl%d: wl_set_mac_address: error setting MAC addr override\n",
			wl->pub->unit));

	WL_UNLOCK(wl);

	return 0;
}

static void
wl_set_multicast_list(struct net_device *dev)
{
#ifndef WLC_HIGH_ONLY
	_wl_set_multicast_list(dev);
#else
	wl_info_t *wl = WL_INFO(dev);

	wl->multicast_task.context = dev;

	if (schedule_work(&wl->multicast_task.work)) {
		/* work item may already be on the work queue, so only inc callbacks if
		 * we actually schedule a new item
		 */
		atomic_inc(&wl->callbacks);
	}
#endif
}

static void
_wl_set_multicast_list(struct net_device *dev)
{
	wl_info_t *wl;
	struct dev_mc_list *mclist;
	int i;

	if (!dev)
		return;

	wl = WL_INFO(dev);

	WL_TRACE(("wl%d: wl_set_multicast_list\n", wl->pub->unit));

	WL_LOCK(wl);

	if (wl->pub->up) {
		wl->pub->allmulti = (dev->flags & IFF_ALLMULTI)? TRUE: FALSE;

		/* copy the list of multicasts into our private table */
		for (i = 0, mclist = dev->mc_list; mclist && (i < dev->mc_count);
			i++, mclist = mclist->next) {
			if (i >= MAXMULTILIST) {
				wl->pub->allmulti = TRUE;
				i = 0;
				break;
			}
			wl->pub->multicast[i] = *((struct ether_addr*) mclist->dmi_addr);
		}
		wl->pub->nmulticast = i;
		wlc_set(wl->wlc, WLC_SET_PROMISC, (dev->flags & IFF_PROMISC));
	}

	WL_UNLOCK(wl);
}


#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20)
irqreturn_t BCMFASTPATH
wl_isr(int irq, void *dev_id)
#else
irqreturn_t BCMFASTPATH
wl_isr(int irq, void *dev_id, struct pt_regs *ptregs)
#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20) */
{
#ifdef WLC_LOW
	wl_info_t *wl;
	bool ours, wantdpc;
	unsigned long flags;

	wl = (wl_info_t*) dev_id;

	WL_ISRLOCK(wl, flags);

	/* call common first level interrupt handler */
	if ((ours = wlc_isr(wl->wlc, &wantdpc))) {
		/* if more to do... */
		if (wantdpc) {

			/* ...and call the second level interrupt handler */
			/* schedule dpc */
			ASSERT(wl->resched == FALSE);
			tasklet_schedule(&wl->tasklet);
		}
	}

	WL_ISRUNLOCK(wl, flags);

	return IRQ_RETVAL(ours);
#else
	return IRQ_RETVAL(0);
#endif /* WLC_LOW */
}

static void BCMFASTPATH
wl_dpc(ulong data)
{
#ifdef WLC_LOW
	wl_info_t *wl;

	wl = (wl_info_t*) data;

	WL_LOCK(wl);

	/* call the common second level interrupt handler */
	if (wl->pub->up) {
#if defined(DSLCPE_DELAY)
		if (IN_LONG_DELAY(wl->osh)) {
			wl->resched = 1;
			wl_schedule_fn(wl, (void (*) (void *))(wl_dpc), wl);
			goto done;
		}
#endif
		if (wl->resched) {
			unsigned long flags;

			INT_LOCK(wl, flags);
			wlc_intrsupd(wl->wlc);
			INT_UNLOCK(wl, flags);
		}

		wl->resched = wlc_dpc(wl->wlc, TRUE);
	}

	/* wlc_dpc() may bring the driver down */
	if (!wl->pub->up)
		goto done;

	/* re-schedule dpc */
	if (wl->resched)
		tasklet_schedule(&wl->tasklet);
	else {
		/* re-enable interrupts */
		wl_intrson(wl);
	}

done:
	WL_UNLOCK(wl);
#endif /* WLC_LOW */
}

/*
 * The last parameter was added for the build. Caller of
 * this function should pass 1 for now.
 */
void BCMFASTPATH
wl_sendup(wl_info_t *wl, wl_if_t *wlif, void *p, int numpkt)
{
	struct sk_buff *skb;

	WL_TRACE(("wl%d: wl_sendup: %d bytes\n", wl->pub->unit, PKTLEN(wl->osh, p)));

#ifdef ARPOE
	/* Arp agent */
	if (ARPOE_ENAB(wl->pub)) {
		int err = wl_arp_recv_proc(wl->arpi, p);
		/* stop the arp-req pkt going to the host, offload has already handled it. */
		if ((err == ARP_REQ_SINK) || (err ==  ARP_REPLY_PEER)) {
			PKTFREE(wl->pub->osh, p, FALSE);
			return;
		}
	}
#endif

#ifdef TOE
	/* Apply TOE */
	if (TOE_ENAB(wl->pub))
		(void)wl_toe_recv_proc(wl->toei, p);
#endif

	/* route packet to the appropriate interface */
	if (wlif) {
		/* drop if the interface is not up yet */
		if (!netif_device_present(wlif->dev)) {
			WL_ERROR(("wl%d: wl_sendup: interface not ready\n", wl->pub->unit));
			PKTFREE(wl->osh, p, FALSE);
			return;
		}
		/* Convert the packet, mainly detach the pkttag */
		skb = PKTTONATIVE(wl->osh, p);
		skb->dev = wlif->dev;
	} else {
		/* Convert the packet, mainly detach the pkttag */
		skb = PKTTONATIVE(wl->osh, p);
		skb->dev = wl->dev;
	}

	skb->protocol = eth_type_trans(skb, skb->dev);

	if (!ISALIGNED((uintptr)skb->data, 4)) {
		WL_ERROR(("Unaligned assert. skb %p. skb->data %p.\n", skb, skb->data));
		if (wlif) {
			WL_ERROR(("wl_sendup: dev name is %s (wlif) \n", wlif->dev->name));
			WL_ERROR(("wl_sendup: hard header len  %d (wlif) \n",
				wlif->dev->hard_header_len));
		}
		WL_ERROR(("wl_sendup: dev name is %s (wl) \n", wl->dev->name));
		WL_ERROR(("wl_sendup: hard header len %d (wl) \n", wl->dev->hard_header_len));
		ASSERT(ISALIGNED((uintptr)skb->data, 4));
	}

	/* send it up */
	WL_APSTA_RX(("wl%d: wl_sendup(): pkt %p summed %d on interface %p (%s)\n",
		wl->pub->unit, p, skb->ip_summed, wlif, skb->dev->name));

	netif_rx(skb);
}

void
wl_dump_ver(wl_info_t *wl, struct bcmstrbuf *b)
{
	bcm_bprintf(b, "wl%d: %s %s version %s\n", wl->pub->unit,
		__DATE__, __TIME__, EPI_VERSION_STR);
}

#ifdef BCMDBG
static int
wl_dump(wl_info_t *wl, struct bcmstrbuf *b)
{
	wl_if_t *p;
	int i;

	wl_dump_ver(wl, b);

	bcm_bprintf(b, "name %s dev %p tbusy %d callbacks %d malloced %d\n",
	       wl->dev->name, wl->dev, (uint)netif_queue_stopped(wl->dev),
	       atomic_read(&wl->callbacks), MALLOCED(wl->osh));

	/* list all interfaces, skipping the primary one since it is printed above */
	p = wl->if_list;
	if (p)
		p = p->next;
	for (i = 0; p != NULL; p = p->next, i++) {
		if ((i % 4) == 0) {
			if (i != 0)
				bcm_bprintf(b, "\n");
			bcm_bprintf(b, "Interfaces:");
		}
		bcm_bprintf(b, " name %s dev %p", p->dev->name, p->dev);
	}
	if (i)
		bcm_bprintf(b, "\n");

	return 0;
}
#endif	/* BCMDBG */

static void
wl_link_up(wl_info_t *wl)
{
	WL_ERROR(("wl%d: link up\n", wl->pub->unit));
}

static void
wl_link_down(wl_info_t *wl)
{
	WL_ERROR(("wl%d: link down\n", wl->pub->unit));
}

void
wl_event(wl_info_t *wl, char *ifname, wlc_event_t *e)
{
#ifdef CONFIG_WIRELESS_EXT
	wl_iw_event(wl->dev, &(e->event), e->data);
#endif /* CONFIG_WIRELESS_EXT */

	switch (e->event.event_type) {
	case WLC_E_LINK:
	case WLC_E_NDIS_LINK:
		if (e->event.flags&WLC_EVENT_MSG_LINK)
			wl_link_up(wl);
		else
			wl_link_down(wl);
		break;
	case WLC_E_MIC_ERROR:
		wl_mic_error(wl, e->addr,
			e->event.flags&WLC_EVENT_MSG_GROUP,
			e->event.flags&WLC_EVENT_MSG_FLUSHTXQ);
		break;
	}
}

#ifdef WLC_HIGH_ONLY

static void
wl_rpc_down(void *wlh)
{
	wl_info_t *wl = (wl_info_t*)(wlh);

	wlc_device_removed(wl->wlc);

	wl_rpcq_free(wl);
}

/* enqueue pkt to local queue, schedule a task to run, return this context */
static int
wl_start(struct sk_buff *skb, struct net_device *dev)
{
	wl_info_t *wl;
	ulong flags;
	wl_if_t *wlif;

	skb->prev = NULL;
	skb->dev = dev;
	wlif = WL_DEV_IF(skb->dev);
	wl = wlif->wl;
	/* Lock the queue as tasklet could be running at this time */
	TXQ_LOCK(wl, flags);
	if (wl->txq_head == NULL)
		wl->txq_head = skb;
	else {
		wl->txq_tail->prev = skb;
	}
	wl->txq_tail = skb;

	if (wl->txq_dispatched == FALSE) {
		wl->txq_dispatched = TRUE;

		if (schedule_work(&wl->txq_task.work)) {
			atomic_inc(&wl->callbacks);
		} else {
			WL_ERROR(("wl%d: wl_start/schedule_work failed\n", wl->pub->unit));
		}
	}

	TXQ_UNLOCK(wl, flags);

	return (0);
}

static void
wl_start_txqwork(struct wl_task *task)
{
	wl_info_t *wl = (wl_info_t *)task->context;
	struct sk_buff *skb;
	ulong flags;
	uint count = 0;
	wl_if_t *wlif;

	WL_TRACE(("wl%d: wl_start_txqwork\n", wl->pub->unit));

	/* First remove an entry then go for execution */
	TXQ_LOCK(wl, flags);
	while (wl->txq_head) {
		skb = wl->txq_head;
		wl->txq_head = skb->prev;
		skb->prev = NULL;
		if (wl->txq_head == NULL)
			wl->txq_tail = NULL;
		TXQ_UNLOCK(wl, flags);

		wlif = WL_DEV_IF(skb->dev);

		/* it has WL_LOCK/WL_UNLOCK inside */
		wl_start_int(wlif, skb);

		/* bounded our execution, reshedule ourself next */
		if (++count >= 10)
			break;

		TXQ_LOCK(wl, flags);
	}

	if (count >= 10) {
		if (!schedule_work(&wl->txq_task.work)) {
			WL_ERROR(("wl%d: wl_start/schedule_work failed\n", wl->pub->unit));
			atomic_dec(&wl->callbacks);
		}
	} else {
		wl->txq_dispatched = FALSE;
		TXQ_UNLOCK(wl, flags);
		atomic_dec(&wl->callbacks);
	}


	return;
}


static void
wl_txq_free(wl_info_t *wl)
{
	struct sk_buff *skb;

	if (wl->txq_head == NULL) {
		ASSERT(wl->txq_tail == NULL);
		return;
	}

	while (wl->txq_head) {
		skb = wl->txq_head;
		wl->txq_head = skb->prev;
		dev_kfree_skb_any(skb);
	}

	wl->txq_tail = NULL;
}

static void
wl_set_multicast_list_workitem(struct work_struct *work)
{
	wl_task_t *task = (wl_task_t *)work;
	struct net_device *dev = (struct net_device*)task->context;
	wl_info_t *wl;

	wl = WL_INFO(dev);

	atomic_dec(&wl->callbacks);

	_wl_set_multicast_list(dev);
}

static void
wl_timer_task(wl_task_t *task)
{
	wl_timer_t *t = (wl_timer_t *)task->context;
	_wl_timer(t);
	MFREE(t->wl->osh, task, sizeof(wl_task_t));

	/* This dec is for the task_schedule. The timer related
	 * callback is decremented in _wl_timer
	 */
	atomic_dec(&t->wl->callbacks);
}
#endif /* WLC_HIGH_ONLY */

static void
wl_timer(ulong data)
{
#ifndef WLC_HIGH_ONLY
	_wl_timer((wl_timer_t*)data);
#else
	wl_timer_t *t = (wl_timer_t *)data;
	wl_schedule_task(t->wl, wl_timer_task, t);
#endif /* WLC_HIGH_ONLY */
}

static void
_wl_timer(wl_timer_t *t)
{
	WL_LOCK(t->wl);

	if (t->set) {
#if defined(DSLCPE_DELAY)
		bool run_timer = (IN_LONG_DELAY(t->wl->osh) ? FALSE: TRUE);
#endif
		if (t->periodic) {
#if defined(BCMJTAG) || defined(BCMSLTGT)
			t->timer.expires = jiffies + t->ms*HZ/1000*htclkratio;
#else
			t->timer.expires = jiffies + t->ms*HZ/1000;
#endif
			atomic_inc(&t->wl->callbacks);
			add_timer(&t->timer);
			t->set = TRUE;
		} else
			t->set = FALSE;

#if defined(DSLCPE_DELAY)
		if (!run_timer) {
			if (!t->periodic) {
				wl_add_timer(t->wl, t, t->ms, t->periodic);
			}
		}
		else
#endif
		t->fn(t->arg);
	}

	atomic_dec(&t->wl->callbacks);

	WL_UNLOCK(t->wl);
}

wl_timer_t *
wl_init_timer(wl_info_t *wl, void (*fn)(void *arg), void *arg, const char *name)
{
	wl_timer_t *t;

	if (!(t = MALLOC(wl->osh, sizeof(wl_timer_t)))) {
		WL_ERROR(("wl%d: wl_init_timer: out of memory, malloced %d bytes\n", wl->pub->unit,
			MALLOCED(wl->osh)));
		return 0;
	}

	bzero(t, sizeof(wl_timer_t));

	init_timer(&t->timer);
	t->timer.data = (ulong) t;
	t->timer.function = wl_timer;
	t->wl = wl;
	t->fn = fn;
	t->arg = arg;
	t->next = wl->timers;
	wl->timers = t;

#ifdef BCMDBG
	if ((t->name = MALLOC(wl->osh, strlen(name) + 1)))
		strcpy(t->name, name);
#endif

	return t;
}

/* BMAC_NOTE: Add timer adds only the kernel timer since it's going to be more accurate
 * as well as it's easier to make it periodic
 */
void
wl_add_timer(wl_info_t *wl, wl_timer_t *t, uint ms, int periodic)
{
	ASSERT(!t->set);

	t->ms = ms;
	t->periodic = (bool) periodic;
	t->set = TRUE;
#if defined(BCMJTAG) || defined(BCMSLTGT)
	t->timer.expires = jiffies + ms*HZ/1000*htclkratio;
#else
	t->timer.expires = jiffies + ms*HZ/1000;
#endif /* defined(BCMJTAG) || defined(BCMSLTGT) */

	atomic_inc(&wl->callbacks);
	add_timer(&t->timer);
}

/* return TRUE if timer successfully deleted, FALSE if still pending */
bool
wl_del_timer(wl_info_t *wl, wl_timer_t *t)
{
	if (t->set) {
		t->set = FALSE;
		if (!del_timer(&t->timer)) {
#ifdef BCMDBG
			WL_INFORM(("wl%d: Failed to delete timer %s\n", wl->pub->unit, t->name));
#endif
			return FALSE;
		}
		atomic_dec(&wl->callbacks);
	}

	return TRUE;
}

void
wl_free_timer(wl_info_t *wl, wl_timer_t *t)
{
	wl_timer_t *tmp;

	/* delete the timer in case it is active */
	wl_del_timer(wl, t);

	if (wl->timers == t) {
		wl->timers = wl->timers->next;
#ifdef BCMDBG
		if (t->name)
			MFREE(wl->osh, t->name, strlen(t->name) + 1);
#endif
		MFREE(wl->osh, t, sizeof(wl_timer_t));
		return;

	}

	tmp = wl->timers;
	while (tmp) {
		if (tmp->next == t) {
			tmp->next = t->next;
#ifdef BCMDBG
			if (t->name)
				MFREE(wl->osh, t->name, strlen(t->name) + 1);
#endif
			MFREE(wl->osh, t, sizeof(wl_timer_t));
			return;
		}
		tmp = tmp->next;
	}

}

static void
wl_mic_error(wl_info_t *wl, struct ether_addr *ea, bool group, bool flush_txq)
{
	WL_WSEC(("wl%d: mic error using %s key\n", wl->pub->unit,
		(group) ? "group" : "pairwise"));

#if defined(BCMSUP_PSK) && defined(STA)
	if (wlc_sup_mic_error(wl->wlc, group))
		return;
#endif /* defined(BCMSUP_PSK) && defined(STA) */

}

void
wl_monitor(wl_info_t *wl, wl_rxsts_t *rxsts, void *p)
{
#ifdef WL_MONITOR
	struct sk_buff *oskb = (struct sk_buff *)p;
	p80211msg_t *phdr;
	struct sk_buff *skb;
	uint len;
	uchar *pdata;

	WL_TRACE(("wl%d: wl_monitor\n", wl->pub->unit));

	if (!wl->monitor)
		return;

	len = sizeof(p80211msg_t) + oskb->len - D11_PHY_HDR_LEN;
	if ((skb = dev_alloc_skb(len)) == NULL)
		return;

	skb_put(skb, len);
	phdr = (p80211msg_t*)skb->data;
	/* Initialize the message members */
	phdr->msgcode = WL_MON_FRAME;
	phdr->msglen = sizeof(p80211msg_t);
	strcpy(phdr->devname, wl->dev->name);

	phdr->hosttime.did = WL_MON_FRAME_HOSTTIME;
	phdr->hosttime.status = P80211ITEM_OK;
	phdr->hosttime.len = 4;
	phdr->hosttime.data = jiffies;

	phdr->channel.did = WL_MON_FRAME_CHANNEL;
	phdr->channel.status = P80211ITEM_NO_VALUE;
	phdr->channel.len = 4;
	phdr->channel.data = 0;

	phdr->signal.did = WL_MON_FRAME_SIGNAL;
	phdr->signal.status = P80211ITEM_OK;
	phdr->signal.len = 4;
	/* two sets of preamble values are defined in wlc_ethereal and wlc_pub.h
	 *   and this assumet their values are matched. Otherwise,
	 * we have to go through conversion, which requires rspec since datarate is just kbps now
	 */
	phdr->signal.data = rxsts->preamble;

	phdr->noise.did = WL_MON_FRAME_NOISE;
	phdr->noise.status = P80211ITEM_NO_VALUE;
	phdr->noise.len = 4;
	phdr->noise.data = 0;

	phdr->rate.did = WL_MON_FRAME_RATE;
	phdr->rate.status = P80211ITEM_OK;
	phdr->rate.len = 4;
	phdr->rate.data = rxsts->datarate;

	phdr->istx.did = WL_MON_FRAME_ISTX;
	phdr->istx.status = P80211ITEM_NO_VALUE;
	phdr->istx.len = 4;
	phdr->istx.data = 0;

	phdr->mactime.did = WL_MON_FRAME_MACTIME;
	phdr->mactime.status = P80211ITEM_OK;
	phdr->mactime.len = 4;
	phdr->mactime.data = rxsts->mactime;

	phdr->rssi.did = WL_MON_FRAME_RSSI;
	phdr->rssi.status = P80211ITEM_OK;
	phdr->rssi.len = 4;
	phdr->rssi.data = rxsts->signal;		/* to dbm */

	phdr->sq.did = WL_MON_FRAME_SQ;
	phdr->sq.status = P80211ITEM_OK;
	phdr->sq.len = 4;
	phdr->sq.data = rxsts->sq;

	phdr->frmlen.did = WL_MON_FRAME_FRMLEN;
	phdr->frmlen.status = P80211ITEM_OK;
	phdr->frmlen.status = P80211ITEM_OK;
	phdr->frmlen.len = 4;
	phdr->frmlen.data = rxsts->pktlength;

	pdata = skb->data + sizeof(p80211msg_t);
	bcopy(oskb->data + D11_PHY_HDR_LEN, pdata, oskb->len - D11_PHY_HDR_LEN);

	skb->dev = wl->monitor;
	skb->dev->last_rx = jiffies;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 22)
	skb_reset_mac_header(skb);
#else
	skb->mac.raw = skb->data;
#endif
	skb->ip_summed = CHECKSUM_NONE;
	skb->pkt_type = PACKET_OTHERHOST;
	skb->protocol = htons(ETH_P_80211_RAW);

	netif_rx(skb);
#endif /* WL_MONITOR */
}

#ifdef WL_MONITOR
static int
wl_monitor_start(struct sk_buff *skb, struct net_device *dev)
{
	dev_kfree_skb(skb);
	return 0;
}

/* Create a virtual interface. Call only from safe time! can't call register_netdev with WL_LOCK */
static const struct net_device_ops wl_monitor_ops = {
	WL_DEFAULT_OPS,
	.ndo_start_xmit = wl_monitor_start,
};


/* Create a virtual interface. Call only from safe time! can't call register_netdev with WL_LOCK */
static void
wl_add_monitor(wl_task_t *task)
{
	wl_info_t *wl = (wl_info_t *) task->context;
	struct net_device *dev;
	wl_if_t *wlif;

	ASSERT(wl);
	WL_LOCK(wl);
	WL_TRACE(("wl%d: wl_add_monitor\n", wl->pub->unit));

	if (wl->monitor)
		goto done;

	wlif = wl_alloc_if(wl, WL_IFTYPE_MON, wl->pub->unit, NULL);
	if (!wlif) {
		WL_ERROR(("wl%d: wl_add_monitor: alloc wlif failed\n", wl->pub->unit));
		goto done;
	}

	/* link it all up */
	dev = wlif->dev;
	wl->monitor = dev;

	/* override some fields */
	sprintf(dev->name, "prism%d", wl->pub->unit);
	bcopy(wl->dev->dev_addr, dev->dev_addr, ETHER_ADDR_LEN);
	dev->type = ARPHRD_IEEE80211_PRISM;
	dev->flags = wl->dev->flags;

	/* initialize dev fn pointers */
	dev->netdev_ops = &wl_monitor_ops;

	WL_UNLOCK(wl);
	if (register_netdev(dev)) {
		WL_ERROR(("wl%d: wl_add_monitor, register_netdev failed for \"%s\"\n",
			wl->pub->unit, wl->monitor->name));
		wl->monitor = NULL;
		wl_free_if(wl, wlif);
	} else
		wlif->dev_registed = TRUE;
	WL_LOCK(wl);
done:
	MFREE(wl->osh, task, sizeof(wl_task_t));
	atomic_dec(&wl->callbacks);
	WL_UNLOCK(wl);
}

static void
wl_del_monitor(wl_task_t *task)
{
	wl_info_t *wl = (wl_info_t *) task->context;
	struct net_device *dev;
	wl_if_t *wlif = NULL;

	ASSERT(wl);
	WL_LOCK(wl);
	WL_TRACE(("wl%d: wl_del_monitor\n", wl->pub->unit));
	dev = wl->monitor;
	if (!dev)
		goto done;
	wlif = netdev_priv(dev);
	ASSERT(wlif);
	wl->monitor = NULL;
	WL_UNLOCK(wl);
	wl_free_if(wl, wlif);
	WL_LOCK(wl);
done:
	MFREE(wl->osh, task, sizeof(wl_task_t));
	WL_UNLOCK(wl);
	atomic_dec(&wl->callbacks);
}
#endif /* WL_MONITOR */

/*
 * Create a dedicated monitor interface since libpcap caches the
 * packet type when it opens the device. The protocol type in the skb
 * is dropped somewhere in libpcap, and every received frame is tagged
 * with the DLT/ARPHRD type that's read by libpcap when the device is
 * opened.
 *
 * If libpcap was fixed to handle per-packet link types, we might not
 * need to create a pseudo device at all, wl_set_monitor() would be
 * unnecessary, and wlc->monitor could just get set in wlc_ioctl().
 */
void
wl_set_monitor(wl_info_t *wl, int val)
{
#ifdef WL_MONITOR
	WL_TRACE(("wl%d: wl_set_monitor: val %d\n", wl->pub->unit, val));

	if (val && !wl->monitor)
		(void) wl_schedule_task(wl, wl_add_monitor, wl);
	else if (!val && wl->monitor)
		(void) wl_schedule_task(wl, wl_del_monitor, wl);
#endif /* WL_MONITOR */
}

#if LINUX_VERSION_CODE == KERNEL_VERSION(2, 6, 15)
const char *
print_tainted()
{
	return "";
}
#endif	/* LINUX_VERSION_CODE == KERNEL_VERSION(2, 6, 15) */


#ifdef BCMJTAG
/* attach to d11 core thru jtag */
/* venid and devid are pci vendor id and pci device id */
static void *
wl_jtag_probe(uint16 venid, uint16 devid, void *regsva, void *param)
{
	wl_info_t *wl;

	if (!wlc_chipmatch(venid, devid)) {
		WL_ERROR(("wl_jtag_probe: wlc_chipmatch failed\n"));
		return NULL;
	}

	if (!(wl = wl_attach(venid, devid,
		(ulong)regsva, JTAG_BUS, param, 0))) {
		WL_ERROR(("wl_jtag_probe: wl_attach failed\n"));
		return NULL;
	}

	return wl;
}

/* detach from d11 core */
static void
wl_jtag_detach(void *wl)
{
	WL_LOCK((wl_info_t *)wl);
	wl_down((wl_info_t *)wl);
	WL_UNLOCK((wl_info_t *)wl);
	wl_free((wl_info_t *)wl);
}

/* poll d11 core */
static void
wl_jtag_poll(void *wl)
{
	WL_ISR(0, (wl_info_t *)wl, NULL);
}
#endif /* BCMJTAG */



struct net_device *
wl_netdev_get(wl_info_t *wl)
{
	return wl->dev;
}

#ifdef BCM_WL_EMULATOR

/* create an empty wl_info structure to be used by the emulator */
wl_info_t *
wl_wlcreate(osl_t *osh, void *pdev)
{
	wl_info_t *wl;

	/* allocate private info */
	if ((wl = (wl_info_t*) MALLOC(osh, sizeof(wl_info_t))) == NULL) {
		WL_ERROR(("wl%d: malloc wl_info_t, out of memory, malloced %d bytes\n", 0,
		          MALLOCED(osh)));
		osl_detach(osh);
		return NULL;
	}
	bzero(wl, sizeof(wl_info_t));
	wl->dev = pdev;
	wl->osh = osh;
	return wl;
}

void * wl_getdev(void *w)
{
	wl_info_t *wl = (wl_info_t *)w;
	return wl->dev;
}
#endif /* BCM_WL_EMULATOR */

/* Linux: no chaining */
int
wl_set_pktlen(osl_t *osh, void *p, int len)
{
	PKTSETLEN(osh, p, len);
	return len;
}
/* Linux: no chaining */
void *
wl_get_pktbuffer(osl_t *osh, int len)
{
	return (PKTGET(osh, len, FALSE));
}

/* Linux version: no chains */
uint
wl_buf_to_pktcopy(osl_t *osh, void *p, uchar *buf, int len, uint offset)
{
	if (PKTLEN(osh, p) < len + offset)
		return 0;
	bcopy(buf, (char *)PKTDATA(osh, p) + offset, len);
	return len;
}

#ifdef LINUX_CRYPTO
int
wl_tkip_miccheck(wl_info_t *wl, void *p, int hdr_len, bool group_key, int key_index)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 14)
	struct sk_buff *skb = (struct sk_buff *)p;
	skb->dev = wl->dev;

	if (wl->tkipmodops) {
		if (group_key && wl->tkip_bcast_data)
			return (wl->tkipmodops->decrypt_msdu(skb, key_index, hdr_len,
				wl->tkip_bcast_data));
		else if (!group_key && wl->tkip_ucast_data)
			return (wl->tkipmodops->decrypt_msdu(skb, key_index, hdr_len,
				wl->tkip_ucast_data));
	}
#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 14) */
	WL_ERROR(("%s: No tkip mod ops\n", __FUNCTION__));
	return -1;

}

int
wl_tkip_micadd(wl_info_t *wl, void *p, int hdr_len)
{
	int error = -1;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 14)
	struct sk_buff *skb = (struct sk_buff *)p;
	skb->dev = wl->dev;

	if (wl->tkipmodops) {
		if (wl->tkip_ucast_data)
			error = wl->tkipmodops->encrypt_msdu(skb, hdr_len, wl->tkip_ucast_data);
		if (error)
			WL_ERROR(("Error encrypting MSDU %d\n", error));
	}
	else
#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 14) */
		WL_ERROR(("%s: No tkip mod ops\n", __FUNCTION__));
	return error;
}

int
wl_tkip_encrypt(wl_info_t *wl, void *p, int hdr_len)
{
	int error = -1;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 14)
	struct sk_buff *skb = (struct sk_buff *)p;
	skb->dev = wl->dev;

	if (wl->tkipmodops) {
		if (wl->tkip_ucast_data)
			error = wl->tkipmodops->encrypt_mpdu(skb, hdr_len, wl->tkip_ucast_data);
		if (error) {
			WL_ERROR(("Error encrypting MPDU %d\n", error));
		}
	}
	else
#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 14) */
		WL_ERROR(("%s: No tkip mod ops\n", __FUNCTION__));
	return error;

}

int
wl_tkip_decrypt(wl_info_t *wl, void *p, int hdr_len, bool group_key)
{
	int err = -1;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 14)
	struct sk_buff *skb = (struct sk_buff *)p;
	skb->dev = wl->dev;

	if (wl->tkipmodops) {
		if (group_key && wl->tkip_bcast_data)
			err = wl->tkipmodops->decrypt_mpdu(skb, hdr_len, wl->tkip_bcast_data);
		else if (!group_key && wl->tkip_ucast_data)
			err = wl->tkipmodops->decrypt_mpdu(skb, hdr_len, wl->tkip_ucast_data);
	}
	else
		WL_ERROR(("%s: No tkip mod ops\n", __FUNCTION__));

#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 14) */

	/* Error */
	return err;
}


int
wl_tkip_keyset(wl_info_t *wl, wsec_key_t *key)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 14)
	bool group_key = FALSE;
	uchar	rxseq[IW_ENCODE_SEQ_MAX_SIZE];

	if (key->len != 0) {
		WL_WSEC(("%s: Key Length is Not zero\n", __FUNCTION__));
		if (key->algo != CRYPTO_ALGO_TKIP) {
			WL_WSEC(("%s: Algo is Not TKIP %d\n", __FUNCTION__, key->algo));
			return 0;
		}
		WL_WSEC(("%s: Trying to set a key in TKIP Mod\n", __FUNCTION__));
	}
	else
		WL_WSEC(("%s: Trying to Remove a Key from TKIP Mod\n", __FUNCTION__));

	if (ETHER_ISNULLADDR(&key->ea) || ETHER_ISBCAST(&key->ea)) {
		group_key = TRUE;
		WL_WSEC(("Group Key index %d\n", key->id));
	}
	else
		WL_WSEC(("Unicast Key index %d\n", key->id));

	if (wl->tkipmodops) {
		uint8 keybuf[8];
		if (group_key) {
			if (key->len) {
				if (!wl->tkip_bcast_data) {
					WL_WSEC(("Init TKIP Bcast Module\n"));
					WL_UNLOCK(wl);
					wl->tkip_bcast_data = wl->tkipmodops->init(key->id);
					WL_LOCK(wl);
				}
				if (wl->tkip_bcast_data) {
					WL_WSEC(("TKIP SET BROADCAST KEY******************\n"));
					bzero(rxseq, IW_ENCODE_SEQ_MAX_SIZE);
					bcopy(&key->rxiv, rxseq, 6);
					bcopy(&key->data[24], keybuf, sizeof(keybuf));
					bcopy(&key->data[16], &key->data[24], sizeof(keybuf));
					bcopy(keybuf, &key->data[16], sizeof(keybuf));
					wl->tkipmodops->set_key(&key->data, key->len,
						(uint8 *)&key->rxiv, wl->tkip_bcast_data);
				}
			}
			else {
				if (wl->tkip_bcast_data) {
					WL_WSEC(("Deinit TKIP Bcast Module\n"));
					wl->tkipmodops->deinit(wl->tkip_bcast_data);
					wl->tkip_bcast_data = NULL;
				}
			}
		}
		else {
			if (key->len) {
				if (!wl->tkip_ucast_data) {
					WL_WSEC(("Init TKIP Ucast Module\n"));
					WL_UNLOCK(wl);
					wl->tkip_ucast_data = wl->tkipmodops->init(key->id);
					WL_LOCK(wl);
				}
				if (wl->tkip_ucast_data) {
					WL_WSEC(("TKIP SET UNICAST KEY******************\n"));
					bzero(rxseq, IW_ENCODE_SEQ_MAX_SIZE);
					bcopy(&key->rxiv, rxseq, 6);
					bcopy(&key->data[24], keybuf, sizeof(keybuf));
					bcopy(&key->data[16], &key->data[24], sizeof(keybuf));
					bcopy(keybuf, &key->data[16], sizeof(keybuf));
					wl->tkipmodops->set_key(&key->data, key->len,
						(uint8 *)&key->rxiv, wl->tkip_ucast_data);
				}
			}
			else {
				if (wl->tkip_ucast_data) {
					WL_WSEC(("Deinit TKIP Ucast Module\n"));
					wl->tkipmodops->deinit(wl->tkip_ucast_data);
					wl->tkip_ucast_data = NULL;
				}
			}
		}
	}
	else
#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 14) */
		WL_WSEC(("%s: No tkip mod ops\n", __FUNCTION__));
	return 0;
}

void
wl_tkip_printstats(wl_info_t *wl, bool group_key)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 14)
	char debug_buf[512];
	if (wl->tkipmodops) {
		if (group_key && wl->tkip_bcast_data)
			wl->tkipmodops->print_stats(debug_buf, wl->tkip_bcast_data);
		else if (!group_key && wl->tkip_ucast_data)
			wl->tkipmodops->print_stats(debug_buf, wl->tkip_ucast_data);
		else
			return;
		printk("%s: TKIP stats from module: %s\n", debug_buf, group_key?"Bcast":"Ucast");
	}
#endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 14) */
}

#endif /* LINUX_CRYPTO */


/* High stub dispatch functions */
#ifdef WLC_HIGH_ONLY

static void
wl_rpcq_free(wl_info_t *wl)
{
	rpc_buf_t *buf;

	if (wl->rpcq_head == NULL) {
		ASSERT(wl->rpcq_tail == NULL);
		return;
	}

	while (wl->rpcq_head) {
		buf = wl->rpcq_head;
		wl->rpcq_head = bcm_rpc_buf_next_get(wl->rpc_th, buf);
		bcm_rpc_buf_free(wl->rpc_dispatch_ctx.rpc, buf);
	}

	wl->rpcq_tail = NULL;
}

static void
wl_rpcq_dispatch(struct wl_task *task)
{
	wl_info_t * wl = (wl_info_t *)task->context;
	rpc_buf_t *buf;
	ulong flags;

	/* First remove an entry then go for execution */
	RPCQ_LOCK(wl, flags);
	while (wl->rpcq_head) {
		buf = wl->rpcq_head;
		wl->rpcq_head = bcm_rpc_buf_next_get(wl->rpc_th, buf);

		if (wl->rpcq_head == NULL)
			wl->rpcq_tail = NULL;
		RPCQ_UNLOCK(wl, flags);

		WL_LOCK(wl);
		wlc_rpc_high_dispatch(&wl->rpc_dispatch_ctx, buf);
		WL_UNLOCK(wl);

		RPCQ_LOCK(wl, flags);
	}

	wl->rpcq_dispatched = FALSE;

	RPCQ_UNLOCK(wl, flags);

	MFREE(wl->osh, task, sizeof(wl_task_t));
	atomic_dec(&wl->callbacks);
}

static void
wl_rpcq_add(wl_info_t *wl, rpc_buf_t *buf)
{
	ulong flags;

	bcm_rpc_buf_next_set(wl->rpc_th, buf, NULL);

	/* Lock the queue as tasklet could be running at this time */
	RPCQ_LOCK(wl, flags);
	if (wl->rpcq_head == NULL)
		wl->rpcq_head = buf;
	else
		bcm_rpc_buf_next_set(wl->rpc_th, wl->rpcq_tail, buf);

	wl->rpcq_tail = buf;

	if (wl->rpcq_dispatched == FALSE) {
		wl->rpcq_dispatched = TRUE;
		wl_schedule_task(wl, wl_rpcq_dispatch, wl);
	}

	RPCQ_UNLOCK(wl, flags);
}

#if defined(BCMDBG) || defined(BCMDBG_ERR)
static const struct name_entry rpc_name_tbl[] = RPC_ID_TABLE;
#endif /* BCMDBG */

/* dongle-side rpc dispatch routine */
static void
wl_rpc_dispatch_schedule(void *ctx, struct rpc_buf* buf)
{
	bcm_xdr_buf_t b;
	wl_info_t *wl = (wl_info_t *)ctx;
	wlc_rpc_id_t rpc_id;
	int err;

	bcm_xdr_buf_init(&b, bcm_rpc_buf_data(wl->rpc_th, buf),
	                 bcm_rpc_buf_len_get(wl->rpc_th, buf));

	err = bcm_xdr_unpack_uint32(&b, &rpc_id);
	ASSERT(!err);
	WL_TRACE(("%s: Dispatch id %s\n", __FUNCTION__, WLC_RPC_ID_LOOKUP(rpc_name_tbl, rpc_id)));

	/* Handle few emergency ones */
	switch (rpc_id) {
	default:
		wl_rpcq_add(wl, buf);
		break;
	}
}
#endif /* WLC_LOW */

static int
wl_linux_watchdog(void *ctx)
{
	wl_info_t *wl = (wl_info_t *) ctx;
	struct net_device_stats *stats = NULL;
	uint id;
#ifdef CONFIG_WIRELESS_EXT
	struct iw_statistics *wstats = NULL;
	int phy_noise;
#endif
	/* refresh stats */
	if (wl->pub->up) {
		ASSERT(wl->stats_id < 2);

		id = 1 - wl->stats_id;

		stats = &wl->stats_watchdog[id];
#ifdef CONFIG_WIRELESS_EXT
		wstats = &wl->wstats_watchdog[id];
#endif
		stats->rx_packets = WLCNTVAL(wl->pub->_cnt.rxframe);
		stats->tx_packets = WLCNTVAL(wl->pub->_cnt.txframe);
		stats->rx_bytes = WLCNTVAL(wl->pub->_cnt.rxbyte);
		stats->tx_bytes = WLCNTVAL(wl->pub->_cnt.txbyte);
		stats->rx_errors = WLCNTVAL(wl->pub->_cnt.rxerror);
		stats->tx_errors = WLCNTVAL(wl->pub->_cnt.txerror);
		stats->collisions = 0;

		stats->rx_length_errors = 0;
		stats->rx_over_errors = WLCNTVAL(wl->pub->_cnt.rxoflo);
		stats->rx_crc_errors = WLCNTVAL(wl->pub->_cnt.rxcrc);
		stats->rx_frame_errors = 0;
		stats->rx_fifo_errors = WLCNTVAL(wl->pub->_cnt.rxoflo);
		stats->rx_missed_errors = 0;

		stats->tx_fifo_errors = WLCNTVAL(wl->pub->_cnt.txuflo);
#ifdef CONFIG_WIRELESS_EXT
#if WIRELESS_EXT > 11
		wstats->discard.nwid = 0;
		wstats->discard.code = WLCNTVAL(wl->pub->_cnt.rxundec);
		wstats->discard.fragment = WLCNTVAL(wl->pub->_cnt.rxfragerr);
		wstats->discard.retries = WLCNTVAL(wl->pub->_cnt.txfail);
		wstats->discard.misc = WLCNTVAL(wl->pub->_cnt.rxrunt) +
			WLCNTVAL(wl->pub->_cnt.rxgiant);

		wstats->miss.beacon = 0;
#endif /* WIRELESS_EXT > 11 */
#endif /* CONFIG_WIRELESS_EXT */

		wl->stats_id = id;

#ifdef CONFIG_WIRELESS_EXT
		if (!wlc_get(wl->wlc, WLC_GET_PHY_NOISE, &phy_noise))
			wl->phy_noise = phy_noise;
#endif /* CONFIG_WIRELESS_EXT */
	}

	return 0;
}
