3404 lines
		
	
	
		
			94 KiB
		
	
	
	
		
			C
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			3404 lines
		
	
	
		
			94 KiB
		
	
	
	
		
			C
		
	
	
		
			Executable File
		
	
	
	
	
| /*
 | |
|  * Dongle BUS interface
 | |
|  * USB Linux Implementation
 | |
|  *
 | |
|  * 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_usb_linux.c 564663 2015-06-18 02:34:42Z $
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * @file @brief
 | |
|  * This file contains DBUS code that is USB *and* OS (Linux) specific. DBUS is a Broadcom
 | |
|  * proprietary host specific abstraction layer.
 | |
|  */
 | |
| 
 | |
| #include <typedefs.h>
 | |
| #include <osl.h>
 | |
| 
 | |
| /**
 | |
|  * DBUS_LINUX_RXDPC is created for router platform performance tuning. A separate thread is created
 | |
|  * to handle USB RX and avoid the call chain getting too long and enhance cache hit rate.
 | |
|  *
 | |
|  * DBUS_LINUX_RXDPC setting is in wlconfig file.
 | |
|  */
 | |
| 
 | |
| /*
 | |
|  * If DBUS_LINUX_RXDPC is off, spin_lock_bh() for CTFPOOL in
 | |
|  * linux_osl.c has to be changed to spin_lock_irqsave() because
 | |
|  * PKTGET/PKTFREE are no longer in bottom half.
 | |
|  *
 | |
|  * Right now we have another queue rpcq in wl_linux.c. Maybe we
 | |
|  * can eliminate that one to reduce the overhead.
 | |
|  *
 | |
|  * Enabling 2nd EP and DBUS_LINUX_RXDPC causing traffic from
 | |
|  * both EP's to be queued in the same rx queue. If we want
 | |
|  * RXDPC to work with 2nd EP. The EP for RPC call return
 | |
|  * should bypass the dpc and go directly up.
 | |
|  */
 | |
| 
 | |
| /* #define DBUS_LINUX_RXDPC */
 | |
| 
 | |
| /* Dbus histogram for ntxq, nrxq, dpc parameter tuning */
 | |
| /* #define DBUS_LINUX_HIST */
 | |
| 
 | |
| #include <usbrdl.h>
 | |
| #include <bcmendian.h>
 | |
| #include <linux/init.h>
 | |
| #include <linux/kernel.h>
 | |
| #include <linux/slab.h>
 | |
| #include <linux/usb.h>
 | |
| #include <linux/skbuff.h>
 | |
| #include <linux/netdevice.h>
 | |
| #include <linux/random.h>
 | |
| #include <linux/spinlock.h>
 | |
| #include <linux/list.h>
 | |
| #include <asm/uaccess.h>
 | |
| #include <asm/unaligned.h>
 | |
| #include <dbus.h>
 | |
| #include <bcmutils.h>
 | |
| #include <bcmdevs.h>
 | |
| #include <linux/usb.h>
 | |
| #include <usbrdl.h>
 | |
| #include <linux/firmware.h>
 | |
| #include <dngl_stats.h>
 | |
| #include <dhd.h>
 | |
| 
 | |
| #if defined(USBOS_THREAD) || defined(USBOS_TX_THREAD)
 | |
| 
 | |
| /**
 | |
|  * The usb-thread is designed to provide currency on multiprocessors and SMP linux kernels. On the
 | |
|  * dual cores platform, the WLAN driver, without threads, executed only on CPU0. The driver consumed
 | |
|  * almost of 100% on CPU0, while CPU1 remained idle. The behavior was observed on Broadcom's STB.
 | |
|  *
 | |
|  * The WLAN driver consumed most of CPU0 and not CPU1 because tasklets/queues, software irq, and
 | |
|  * hardware irq are executing from CPU0, only. CPU0 became the system's bottle-neck. TPUT is lower
 | |
|  * and system's responsiveness is slower.
 | |
|  *
 | |
|  * To improve system responsiveness and TPUT usb-thread was implemented. The system's threads could
 | |
|  * be scheduled to run on any core. One core could be processing data in the usb-layer and the other
 | |
|  * core could be processing data in the wl-layer.
 | |
|  *
 | |
|  * For further info see [WlThreadAndUsbThread] Twiki.
 | |
|  */
 | |
| 
 | |
| #include <linux/kthread.h>
 | |
| #include <linux/interrupt.h>
 | |
| #include <linux/irq.h>
 | |
| #include <asm/hardirq.h>
 | |
| #include <linux/list.h>
 | |
| #include <linux_osl.h>
 | |
| #endif /* USBOS_THREAD || USBOS_TX_THREAD */
 | |
| 
 | |
| 
 | |
| 
 | |
| #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0))
 | |
| #define KERNEL26
 | |
| #endif
 | |
| 
 | |
| /**
 | |
|  * Starting with the 3.10 kernel release, dynamic PM support for USB is present whenever
 | |
|  * the kernel was built with CONFIG_PM_RUNTIME enabled. The CONFIG_USB_SUSPEND option has
 | |
|  * been eliminated.
 | |
|  */
 | |
| #if ((LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 21)) && defined(CONFIG_USB_SUSPEND)) \
 | |
| 	|| ((LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0)) && defined(CONFIG_PM_RUNTIME))
 | |
| /* For USB power management support, see Linux kernel: Documentation/usb/power-management.txt */
 | |
| #define USB_SUSPEND_AVAILABLE
 | |
| #endif
 | |
| 
 | |
| /* Define alternate fw/nvram paths used in Android */
 | |
| #ifdef OEM_ANDROID
 | |
| #define CONFIG_ANDROID_BCMDHD_FW_PATH "broadcom/dhd/firmware/fw.bin.trx"
 | |
| #define CONFIG_ANDROID_BCMDHD_NVRAM_PATH "broadcom/dhd/nvrams/nvm.txt"
 | |
| #endif /* OEM_ANDROID */
 | |
| 
 | |
| static inline int usb_submit_urb_linux(struct urb *urb)
 | |
| {
 | |
| 
 | |
| #ifdef BCM_MAX_URB_LEN
 | |
| 	if (urb && (urb->transfer_buffer_length > BCM_MAX_URB_LEN)) {
 | |
| 		DBUSERR(("URB transfer length=%d exceeded %d ra=%p\n", urb->transfer_buffer_length,
 | |
| 		BCM_MAX_URB_LEN, __builtin_return_address(0)));
 | |
| 		return DBUS_ERR;
 | |
| 	}
 | |
| #endif
 | |
| 
 | |
| #ifdef KERNEL26
 | |
| 	return usb_submit_urb(urb, GFP_ATOMIC);
 | |
| #else
 | |
| 	return usb_submit_urb(urb);
 | |
| #endif
 | |
| 
 | |
| }
 | |
| 
 | |
| #define USB_SUBMIT_URB(urb) usb_submit_urb_linux(urb)
 | |
| 
 | |
| #ifdef KERNEL26
 | |
| 
 | |
| #define USB_ALLOC_URB()				usb_alloc_urb(0, GFP_ATOMIC)
 | |
| #define USB_UNLINK_URB(urb)			(usb_kill_urb(urb))
 | |
| #define USB_FREE_URB(urb)			(usb_free_urb(urb))
 | |
| #define USB_REGISTER()				usb_register(&dbus_usbdev)
 | |
| #define USB_DEREGISTER()			usb_deregister(&dbus_usbdev)
 | |
| 
 | |
| #ifdef USB_SUSPEND_AVAILABLE
 | |
| 
 | |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 33))
 | |
| #define USB_AUTOPM_SET_INTERFACE(intf)		usb_autopm_set_interface(intf)
 | |
| #else
 | |
| #define USB_ENABLE_AUTOSUSPEND(udev)		usb_enable_autosuspend(udev)
 | |
| #define USB_DISABLE_AUTOSUSPEND(udev)       usb_disable_autosuspend(udev)
 | |
| #endif  /* LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 33))  */
 | |
| 
 | |
| #define USB_AUTOPM_GET_INTERFACE(intf)		usb_autopm_get_interface(intf)
 | |
| #define USB_AUTOPM_PUT_INTERFACE(intf)		usb_autopm_put_interface(intf)
 | |
| #define USB_AUTOPM_GET_INTERFACE_ASYNC(intf)	usb_autopm_get_interface_async(intf)
 | |
| #define USB_AUTOPM_PUT_INTERFACE_ASYNC(intf)	usb_autopm_put_interface_async(intf)
 | |
| #define USB_MARK_LAST_BUSY(dev)			usb_mark_last_busy(dev)
 | |
| 
 | |
| #else /* USB_SUSPEND_AVAILABLE */
 | |
| 
 | |
| #define USB_AUTOPM_GET_INTERFACE(intf)		do {} while (0)
 | |
| #define USB_AUTOPM_PUT_INTERFACE(intf)		do {} while (0)
 | |
| #define USB_AUTOPM_GET_INTERFACE_ASYNC(intf)	do {} while (0)
 | |
| #define USB_AUTOPM_PUT_INTERFACE_ASYNC(intf)	do {} while (0)
 | |
| #define USB_MARK_LAST_BUSY(dev)			do {} while (0)
 | |
| #endif /* USB_SUSPEND_AVAILABLE */
 | |
| 
 | |
| #define USB_CONTROL_MSG(dev, pipe, request, requesttype, value, index, data, size, timeout) \
 | |
| 	usb_control_msg((dev), (pipe), (request), (requesttype), (value), (index), \
 | |
| 	(data), (size), (timeout))
 | |
| #define USB_BULK_MSG(dev, pipe, data, len, actual_length, timeout) \
 | |
| 	usb_bulk_msg((dev), (pipe), (data), (len), (actual_length), (timeout))
 | |
| #define USB_BUFFER_ALLOC(dev, size, mem, dma)	usb_buffer_alloc(dev, size, mem, dma)
 | |
| #define USB_BUFFER_FREE(dev, size, data, dma)	usb_buffer_free(dev, size, data, dma)
 | |
| 
 | |
| #ifdef WL_URB_ZPKT
 | |
| #define URB_QUEUE_BULK   URB_ZERO_PACKET
 | |
| #else
 | |
| #define URB_QUEUE_BULK   0
 | |
| #endif /* WL_URB_ZPKT */
 | |
| 
 | |
| #define CALLBACK_ARGS		struct urb *urb, struct pt_regs *regs
 | |
| #define CALLBACK_ARGS_DATA	urb, regs
 | |
| #define CONFIGDESC(usb)		(&((usb)->actconfig)->desc)
 | |
| #define IFPTR(usb, idx)		((usb)->actconfig->interface[idx])
 | |
| #define IFALTS(usb, idx)	(IFPTR((usb), (idx))->altsetting[0])
 | |
| #define IFDESC(usb, idx)	IFALTS((usb), (idx)).desc
 | |
| #define IFEPDESC(usb, idx, ep)	(IFALTS((usb), (idx)).endpoint[ep]).desc
 | |
| 
 | |
| #else /* KERNEL26 */
 | |
| 
 | |
| #define USB_ALLOC_URB()				usb_alloc_urb(0)
 | |
| #define USB_UNLINK_URB(urb)			usb_unlink_urb(urb)
 | |
| #define USB_FREE_URB(urb)			(usb_free_urb(urb))
 | |
| #define USB_REGISTER()				usb_register(&dbus_usbdev)
 | |
| #define USB_DEREGISTER()			usb_deregister(&dbus_usbdev)
 | |
| #define USB_AUTOPM_GET_INTERFACE(intf)		do {} while (0)
 | |
| #define USB_AUTOPM_GET_INTERFACE_ASYNC(intf)	do {} while (0)
 | |
| #define USB_AUTOPM_PUT_INTERFACE_ASYNC(intf)	do {} while (0)
 | |
| #define USB_MARK_LAST_BUSY(dev)			do {} while (0)
 | |
| 
 | |
| #define USB_CONTROL_MSG(dev, pipe, request, requesttype, value, index, data, size, timeout) \
 | |
| 	usb_control_msg((dev), (pipe), (request), (requesttype), (value), (index), \
 | |
| 	(data), (size), (timeout))
 | |
| #define USB_BUFFER_ALLOC(dev, size, mem, dma)  kmalloc(size, mem)
 | |
| #define USB_BUFFER_FREE(dev, size, data, dma)  kfree(data)
 | |
| 
 | |
| #ifdef WL_URB_ZPKT
 | |
| #define URB_QUEUE_BULK   USB_QUEUE_BULK|URB_ZERO_PACKET
 | |
| #else
 | |
| #define URB_QUEUE_BULK   0
 | |
| #endif /*  WL_URB_ZPKT */
 | |
| 
 | |
| #define CALLBACK_ARGS		struct urb *urb
 | |
| #define CALLBACK_ARGS_DATA	urb
 | |
| #define CONFIGDESC(usb)		((usb)->actconfig)
 | |
| #define IFPTR(usb, idx)		(&(usb)->actconfig->interface[idx])
 | |
| #define IFALTS(usb, idx)	((usb)->actconfig->interface[idx].altsetting[0])
 | |
| #define IFDESC(usb, idx)	IFALTS((usb), (idx))
 | |
| #define IFEPDESC(usb, idx, ep)	(IFALTS((usb), (idx)).endpoint[ep])
 | |
| 
 | |
| 
 | |
| #endif /* KERNEL26 */
 | |
| 
 | |
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 31))
 | |
| #define USB_SPEED_SUPER		5
 | |
| #endif  /* #if (LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 31)) */
 | |
| 
 | |
| #define CONTROL_IF   0
 | |
| #define BULK_IF      0
 | |
| 
 | |
| #ifdef BCMUSBDEV_COMPOSITE
 | |
| #define USB_COMPIF_MAX       4
 | |
| 
 | |
| #define USB_CLASS_WIRELESS	0xe0
 | |
| #define USB_CLASS_MISC		0xef
 | |
| #define USB_SUBCLASS_COMMON	0x02
 | |
| #define USB_PROTO_IAD		0x01
 | |
| #define USB_PROTO_VENDOR	0xff
 | |
| 
 | |
| #define USB_QUIRK_NO_SET_INTF   0x04 /* device does not support set_interface */
 | |
| #endif /* BCMUSBDEV_COMPOSITE */
 | |
| 
 | |
| #define USB_SYNC_WAIT_TIMEOUT  300  /* ms */
 | |
| 
 | |
| /* Private data kept in skb */
 | |
| #define SKB_PRIV(skb, idx)  (&((void **)skb->cb)[idx])
 | |
| #define SKB_PRIV_URB(skb)   (*(struct urb **)SKB_PRIV(skb, 0))
 | |
| 
 | |
| #ifndef DBUS_USB_RXQUEUE_BATCH_ADD
 | |
| /* items to add each time within limit */
 | |
| #define DBUS_USB_RXQUEUE_BATCH_ADD            8
 | |
| #endif
 | |
| 
 | |
| #ifndef DBUS_USB_RXQUEUE_LOWER_WATERMARK
 | |
| /* add a new batch req to rx queue when waiting item count reduce to this number */
 | |
| #define DBUS_USB_RXQUEUE_LOWER_WATERMARK      4
 | |
| #endif
 | |
| 
 | |
| enum usbos_suspend_state {
 | |
| 	USBOS_SUSPEND_STATE_DEVICE_ACTIVE = 0, /* Device is busy, won't allow suspend */
 | |
| 	USBOS_SUSPEND_STATE_SUSPEND_PENDING,   /* Device is idle, can be suspended */
 | |
| 	                                       /* Wating PM to suspend */
 | |
| 	USBOS_SUSPEND_STATE_SUSPENDED          /* Device suspended */
 | |
| };
 | |
| 
 | |
| enum usbos_request_state {
 | |
| 	USBOS_REQUEST_STATE_UNSCHEDULED = 0,	/* USB TX request not scheduled */
 | |
| 	USBOS_REQUEST_STATE_SCHEDULED,		/* USB TX request given to TX thread */
 | |
| 	USBOS_REQUEST_STATE_SUBMITTED		/* USB TX request submitted */
 | |
| };
 | |
| 
 | |
| typedef struct {
 | |
| 	uint32 notification;
 | |
| 	uint32 reserved;
 | |
| } intr_t;
 | |
| 
 | |
| typedef struct {
 | |
| 	dbus_pub_t *pub;
 | |
| 
 | |
| 	void *cbarg;
 | |
| 	dbus_intf_callbacks_t *cbs;
 | |
| 
 | |
| 	/* Imported */
 | |
| 	struct usb_device *usb;	/* USB device pointer from OS */
 | |
| 	struct urb *intr_urb; /* URB for interrupt endpoint */
 | |
| 	struct list_head req_rxfreeq;
 | |
| 	struct list_head req_txfreeq;
 | |
| 	struct list_head req_rxpostedq;	/* Posted down to USB driver for RX */
 | |
| 	struct list_head req_txpostedq;	/* Posted down to USB driver for TX */
 | |
| 	spinlock_t rxfree_lock; /* Lock for rx free list */
 | |
| 	spinlock_t txfree_lock; /* Lock for tx free list */
 | |
| 	spinlock_t rxposted_lock; /* Lock for rx posted list */
 | |
| 	spinlock_t txposted_lock; /* Lock for tx posted list */
 | |
| 	uint rx_pipe, tx_pipe, intr_pipe, rx_pipe2; /* Pipe numbers for USB I/O */
 | |
| 	uint rxbuf_len;
 | |
| 
 | |
| 	struct list_head req_rxpendingq; /* RXDPC: Pending for dpc to send up */
 | |
| 	spinlock_t rxpending_lock;	/* RXDPC: Lock for rx pending list */
 | |
| 	long dpc_pid;
 | |
| 	struct semaphore dpc_sem;
 | |
| 	struct completion dpc_exited;
 | |
| 	int rxpending;
 | |
| 
 | |
| 	struct urb               *ctl_urb;
 | |
| 	int                      ctl_in_pipe, ctl_out_pipe;
 | |
| 	struct usb_ctrlrequest   ctl_write;
 | |
| 	struct usb_ctrlrequest   ctl_read;
 | |
| 	struct semaphore         ctl_lock;     /* Lock for CTRL transfers via tx_thread */
 | |
| #ifdef USBOS_TX_THREAD
 | |
| 	enum usbos_request_state ctl_state;
 | |
| #endif /* USBOS_TX_THREAD */
 | |
| 
 | |
| 	spinlock_t rxlock;      /* Lock for rxq management */
 | |
| 	spinlock_t txlock;      /* Lock for txq management */
 | |
| 
 | |
| 	int intr_size;          /* Size of interrupt message */
 | |
| 	int interval;           /* Interrupt polling interval */
 | |
| 	intr_t intr;            /* Data buffer for interrupt endpoint */
 | |
| 
 | |
| 	int maxps;
 | |
| 	atomic_t txposted;
 | |
| 	atomic_t rxposted;
 | |
| 	atomic_t txallocated;
 | |
| 	atomic_t rxallocated;
 | |
| 	bool rxctl_deferrespok;	/* Get a response for setup from dongle */
 | |
| 
 | |
| 	wait_queue_head_t wait;
 | |
| 	bool waitdone;
 | |
| 	int sync_urb_status;
 | |
| 
 | |
| 	struct urb *blk_urb; /* Used for downloading embedded image */
 | |
| 
 | |
| #ifdef USBOS_THREAD
 | |
| 	spinlock_t              ctrl_lock;
 | |
| 	spinlock_t              usbos_list_lock;
 | |
| 	struct list_head        usbos_list;
 | |
| 	struct list_head        usbos_free_list;
 | |
| 	atomic_t                usbos_list_cnt;
 | |
| 	wait_queue_head_t       usbos_queue_head;
 | |
| 	struct task_struct      *usbos_kt;
 | |
| #endif /* USBOS_THREAD */
 | |
| 
 | |
| #ifdef USBOS_TX_THREAD
 | |
| 	spinlock_t              usbos_tx_list_lock;
 | |
| 	struct list_head	usbos_tx_list;
 | |
| 	wait_queue_head_t	usbos_tx_queue_head;
 | |
| 	struct task_struct      *usbos_tx_kt;
 | |
| #endif /* USBOS_TX_THREAD */
 | |
| 
 | |
| 	struct dma_pool *qtd_pool; /* QTD pool for USB optimization only */
 | |
| 	int tx_ep, rx_ep, rx2_ep;  /* EPs for USB optimization */
 | |
| 	struct usb_device *usb_device; /* USB device for optimization */
 | |
| } usbos_info_t;
 | |
| 
 | |
| typedef struct urb_req {
 | |
| 	void         *pkt;
 | |
| 	int          buf_len;
 | |
| 	struct urb   *urb;
 | |
| 	void         *arg;
 | |
| 	usbos_info_t *usbinfo;
 | |
| 	struct list_head urb_list;
 | |
| } urb_req_t;
 | |
| 
 | |
| #ifdef USBOS_THREAD
 | |
| typedef struct usbos_list_entry {
 | |
| 	struct list_head    list;   /* must be first */
 | |
| 	void               *urb_context;
 | |
| 	int                 urb_length;
 | |
| 	int                 urb_status;
 | |
| } usbos_list_entry_t;
 | |
| 
 | |
| static void* dbus_usbos_thread_init(usbos_info_t *usbos_info);
 | |
| static void  dbus_usbos_thread_deinit(usbos_info_t *usbos_info);
 | |
| static void  dbus_usbos_dispatch_schedule(CALLBACK_ARGS);
 | |
| static int   dbus_usbos_thread_func(void *data);
 | |
| #endif /* USBOS_THREAD */
 | |
| 
 | |
| #ifdef USBOS_TX_THREAD
 | |
| void* dbus_usbos_tx_thread_init(usbos_info_t *usbos_info);
 | |
| void  dbus_usbos_tx_thread_deinit(usbos_info_t *usbos_info);
 | |
| int   dbus_usbos_tx_thread_func(void *data);
 | |
| #endif /* USBOS_TX_THREAD */
 | |
| 
 | |
| /* Shared Function prototypes */
 | |
| bool dbus_usbos_dl_cmd(usbos_info_t *usbinfo, uint8 cmd, void *buffer, int buflen);
 | |
| int dbus_usbos_wait(usbos_info_t *usbinfo, uint16 ms);
 | |
| bool dbus_usbos_dl_send_bulk(usbos_info_t *usbinfo, void *buffer, int len);
 | |
| int dbus_write_membytes(usbos_info_t *usbinfo, bool set, uint32 address, uint8 *data, uint size);
 | |
| 
 | |
| /* Local function prototypes */
 | |
| static void dbus_usbos_send_complete(CALLBACK_ARGS);
 | |
| static void dbus_usbos_recv_complete(CALLBACK_ARGS);
 | |
| static int  dbus_usbos_errhandler(void *bus, int err);
 | |
| static int  dbus_usbos_state_change(void *bus, int state);
 | |
| static void dbusos_stop(usbos_info_t *usbos_info);
 | |
| 
 | |
| #ifdef KERNEL26
 | |
| static int dbus_usbos_probe(struct usb_interface *intf, const struct usb_device_id *id);
 | |
| static void dbus_usbos_disconnect(struct usb_interface *intf);
 | |
| #if defined(USB_SUSPEND_AVAILABLE)
 | |
| static int dbus_usbos_resume(struct usb_interface *intf);
 | |
| static int dbus_usbos_suspend(struct usb_interface *intf, pm_message_t message);
 | |
| /* at the moment, used for full dongle host driver only */
 | |
| static int dbus_usbos_reset_resume(struct usb_interface *intf);
 | |
| #endif /* USB_SUSPEND_AVAILABLE */
 | |
| #else /* KERNEL26 */
 | |
| static void *dbus_usbos_probe(struct usb_device *usb, unsigned int ifnum,
 | |
| 	const struct usb_device_id *id);
 | |
| static void dbus_usbos_disconnect(struct usb_device *usb, void *ptr);
 | |
| #endif /* KERNEL26 */
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * have to disable missing-field-initializers warning as last element {} triggers it
 | |
|  * and different versions of kernel have different number of members so it is impossible
 | |
|  * to specify the initializer. BTW issuing the warning here is bug og GCC as  universal
 | |
|  * zero {0} specified in C99 standard as correct way of initialization of struct to all zeros
 | |
|  */
 | |
| #if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == \
 | |
| 	4 && __GNUC_MINOR__ >= 6))
 | |
| #pragma GCC diagnostic push
 | |
| #pragma GCC diagnostic ignored "-Wmissing-field-initializers"
 | |
| #endif
 | |
| 
 | |
| static struct usb_device_id devid_table[] = {
 | |
| 	{ USB_DEVICE(BCM_DNGL_VID, 0x0000) }, /* Configurable via register() */
 | |
| #if defined(BCM_REQUEST_FW)
 | |
| 	{ USB_DEVICE(BCM_DNGL_VID, BCM_DNGL_BL_PID_4328) },
 | |
| 	{ USB_DEVICE(BCM_DNGL_VID, BCM_DNGL_BL_PID_4322) },
 | |
| 	{ USB_DEVICE(BCM_DNGL_VID, BCM_DNGL_BL_PID_4319) },
 | |
| 	{ USB_DEVICE(BCM_DNGL_VID, BCM_DNGL_BL_PID_43236) },
 | |
| 	{ USB_DEVICE(BCM_DNGL_VID, BCM_DNGL_BL_PID_43143) },
 | |
| 	{ USB_DEVICE(BCM_DNGL_VID, BCM_DNGL_BL_PID_43242) },
 | |
| 	{ USB_DEVICE(BCM_DNGL_VID, BCM_DNGL_BL_PID_4360) },
 | |
| 	{ USB_DEVICE(BCM_DNGL_VID, BCM_DNGL_BL_PID_4350) },
 | |
| 	{ USB_DEVICE(BCM_DNGL_VID, BCM_DNGL_BL_PID_43569) },
 | |
| #endif
 | |
| #ifdef EXTENDED_VID_PID
 | |
| 	EXTENDED_VID_PID,
 | |
| #endif /* EXTENDED_VID_PID */
 | |
| 	{ USB_DEVICE(BCM_DNGL_VID, BCM_DNGL_BDC_PID) }, /* Default BDC */
 | |
| 	{ }
 | |
| };
 | |
| 
 | |
| #if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == \
 | |
| 	4 && __GNUC_MINOR__ >= 6))
 | |
| #pragma GCC diagnostic pop
 | |
| #endif
 | |
| 
 | |
| MODULE_DEVICE_TABLE(usb, devid_table);
 | |
| 
 | |
| /** functions called by the Linux kernel USB subsystem */
 | |
| static struct usb_driver dbus_usbdev = {
 | |
| 	name:           "dbus_usbdev",
 | |
| 	probe:          dbus_usbos_probe,
 | |
| 	disconnect:     dbus_usbos_disconnect,
 | |
| 	id_table:       devid_table,
 | |
| #if defined(USB_SUSPEND_AVAILABLE)
 | |
| 	suspend:        dbus_usbos_suspend,
 | |
| 	resume:         dbus_usbos_resume,
 | |
| 	reset_resume:	dbus_usbos_reset_resume,
 | |
| 	/* Linux USB core will allow autosuspend for devices bound to this driver */
 | |
| 	supports_autosuspend: 1
 | |
| #endif /* USB_SUSPEND_AVAILABLE */
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * This stores USB info during Linux probe callback since attach() is not called yet at this point
 | |
|  */
 | |
| typedef struct {
 | |
| 	void    *usbos_info;
 | |
| 	struct usb_device *usb; /* USB device pointer from OS */
 | |
| 	uint    rx_pipe;   /* Pipe numbers for USB I/O */
 | |
| 	uint    tx_pipe;   /* Pipe numbers for USB I/O */
 | |
| 	uint    intr_pipe; /* Pipe numbers for USB I/O */
 | |
| 	uint    rx_pipe2;  /* Pipe numbers for USB I/O */
 | |
| 	int     intr_size; /* Size of interrupt message */
 | |
| 	int     interval;  /* Interrupt polling interval */
 | |
| 	bool    dldone;
 | |
| 	int     vid;
 | |
| 	int     pid;
 | |
| 	bool    dereged;
 | |
| 	bool    disc_cb_done;
 | |
| 	DEVICE_SPEED    device_speed;
 | |
| 	enum usbos_suspend_state suspend_state;
 | |
| 	struct usb_interface     *intf;
 | |
| } probe_info_t;
 | |
| 
 | |
| /*
 | |
|  * USB Linux dbus_intf_t
 | |
|  */
 | |
| static void *dbus_usbos_intf_attach(dbus_pub_t *pub, void *cbarg, dbus_intf_callbacks_t *cbs);
 | |
| static void dbus_usbos_intf_detach(dbus_pub_t *pub, void *info);
 | |
| static int  dbus_usbos_intf_send_irb(void *bus, dbus_irb_tx_t *txirb);
 | |
| static int  dbus_usbos_intf_recv_irb(void *bus, dbus_irb_rx_t *rxirb);
 | |
| static int  dbus_usbos_intf_recv_irb_from_ep(void *bus, dbus_irb_rx_t *rxirb, uint32 ep_idx);
 | |
| static int  dbus_usbos_intf_cancel_irb(void *bus, dbus_irb_tx_t *txirb);
 | |
| static int  dbus_usbos_intf_send_ctl(void *bus, uint8 *buf, int len);
 | |
| static int  dbus_usbos_intf_recv_ctl(void *bus, uint8 *buf, int len);
 | |
| static int  dbus_usbos_intf_get_attrib(void *bus, dbus_attrib_t *attrib);
 | |
| static int  dbus_usbos_intf_up(void *bus);
 | |
| static int  dbus_usbos_intf_down(void *bus);
 | |
| static int  dbus_usbos_intf_stop(void *bus);
 | |
| static int  dbus_usbos_readreg(void *bus, uint32 regaddr, int datalen, uint32 *value);
 | |
| extern int dbus_usbos_loopback_tx(void *usbos_info_ptr, int cnt, int size);
 | |
| int dbus_usbos_writereg(void *bus, uint32 regaddr, int datalen, uint32 data);
 | |
| static int  dbus_usbos_intf_set_config(void *bus, dbus_config_t *config);
 | |
| static bool dbus_usbos_intf_recv_needed(void *bus);
 | |
| static void *dbus_usbos_intf_exec_rxlock(void *bus, exec_cb_t cb, struct exec_parms *args);
 | |
| static void *dbus_usbos_intf_exec_txlock(void *bus, exec_cb_t cb, struct exec_parms *args);
 | |
| #ifdef BCMUSBDEV_COMPOSITE
 | |
| static int dbus_usbos_intf_wlan(struct usb_device *usb);
 | |
| #endif /* BCMUSBDEV_COMPOSITE */
 | |
| 
 | |
| /** functions called by dbus_usb.c */
 | |
| static dbus_intf_t dbus_usbos_intf = {
 | |
| 	.attach = dbus_usbos_intf_attach,
 | |
| 	.detach = dbus_usbos_intf_detach,
 | |
| 	.up = dbus_usbos_intf_up,
 | |
| 	.down = dbus_usbos_intf_down,
 | |
| 	.send_irb = dbus_usbos_intf_send_irb,
 | |
| 	.recv_irb = dbus_usbos_intf_recv_irb,
 | |
| 	.cancel_irb = dbus_usbos_intf_cancel_irb,
 | |
| 	.send_ctl = dbus_usbos_intf_send_ctl,
 | |
| 	.recv_ctl = dbus_usbos_intf_recv_ctl,
 | |
| 	.get_stats = NULL,
 | |
| 	.get_attrib = dbus_usbos_intf_get_attrib,
 | |
| 	.remove = NULL,
 | |
| 	.resume = NULL,
 | |
| 	.suspend = NULL,
 | |
| 	.stop = dbus_usbos_intf_stop,
 | |
| 	.reset = NULL,
 | |
| 	.pktget = NULL,
 | |
| 	.pktfree = NULL,
 | |
| 	.iovar_op = NULL,
 | |
| 	.dump = NULL,
 | |
| 	.set_config = dbus_usbos_intf_set_config,
 | |
| 	.get_config = NULL,
 | |
| 	.device_exists = NULL,
 | |
| 	.dlneeded = NULL,
 | |
| 	.dlstart = NULL,
 | |
| 	.dlrun = NULL,
 | |
| 	.recv_needed = dbus_usbos_intf_recv_needed,
 | |
| 	.exec_rxlock = dbus_usbos_intf_exec_rxlock,
 | |
| 	.exec_txlock = dbus_usbos_intf_exec_txlock,
 | |
| 
 | |
| 	.tx_timer_init = NULL,
 | |
| 	.tx_timer_start = NULL,
 | |
| 	.tx_timer_stop = NULL,
 | |
| 
 | |
| 	.sched_dpc = NULL,
 | |
| 	.lock = NULL,
 | |
| 	.unlock = NULL,
 | |
| 	.sched_probe_cb = NULL,
 | |
| 
 | |
| 	.shutdown = NULL,
 | |
| 
 | |
| 	.recv_stop = NULL,
 | |
| 	.recv_resume = NULL,
 | |
| 
 | |
| 	.recv_irb_from_ep = dbus_usbos_intf_recv_irb_from_ep,
 | |
| 	.readreg = dbus_usbos_readreg
 | |
| };
 | |
| 
 | |
| static probe_info_t    g_probe_info;
 | |
| static probe_cb_t      probe_cb = NULL;
 | |
| static disconnect_cb_t disconnect_cb = NULL;
 | |
| static void            *probe_arg = NULL;
 | |
| static void            *disc_arg = NULL;
 | |
| 
 | |
| 
 | |
| 
 | |
| static volatile int loopback_rx_cnt, loopback_tx_cnt;
 | |
| int loopback_size;
 | |
| bool is_loopback_pkt(void *buf);
 | |
| int matches_loopback_pkt(void *buf);
 | |
| 
 | |
| /**
 | |
|  * multiple code paths in this file dequeue a URB request, this function makes sure that it happens
 | |
|  * in a concurrency save manner. Don't call this from a sleepable process context.
 | |
|  */
 | |
| static urb_req_t * BCMFASTPATH
 | |
| dbus_usbos_qdeq(struct list_head *urbreq_q, spinlock_t *lock)
 | |
| {
 | |
| 	unsigned long flags;
 | |
| 	urb_req_t *req;
 | |
| 
 | |
| 	ASSERT(urbreq_q != NULL);
 | |
| 
 | |
| 	spin_lock_irqsave(lock, flags);
 | |
| 
 | |
| 	if (list_empty(urbreq_q)) {
 | |
| 		req = NULL;
 | |
| 	} else {
 | |
| 		ASSERT(urbreq_q->next != NULL);
 | |
| 		ASSERT(urbreq_q->next != urbreq_q);
 | |
| #if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
 | |
| #pragma GCC diagnostic push
 | |
| #pragma GCC diagnostic ignored "-Wcast-qual"
 | |
| #endif
 | |
| 		req = list_entry(urbreq_q->next, urb_req_t, urb_list);
 | |
| #if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
 | |
| #pragma GCC diagnostic pop
 | |
| #endif
 | |
| 		list_del_init(&req->urb_list);
 | |
| 	}
 | |
| 
 | |
| 	spin_unlock_irqrestore(lock, flags);
 | |
| 
 | |
| 	return req;
 | |
| }
 | |
| 
 | |
| static void BCMFASTPATH
 | |
| dbus_usbos_qenq(struct list_head *urbreq_q, urb_req_t *req, spinlock_t *lock)
 | |
| {
 | |
| 	unsigned long flags;
 | |
| 
 | |
| 	spin_lock_irqsave(lock, flags);
 | |
| 
 | |
| 	list_add_tail(&req->urb_list, urbreq_q);
 | |
| 
 | |
| 	spin_unlock_irqrestore(lock, flags);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * multiple code paths in this file remove a URB request from a list, this function makes sure that
 | |
|  * it happens in a concurrency save manner. Don't call this from a sleepable process context.
 | |
|  * Is quite similar to dbus_usbos_qdeq(), I wonder why this function is needed.
 | |
|  */
 | |
| static void
 | |
| dbus_usbos_req_del(urb_req_t *req, spinlock_t *lock)
 | |
| {
 | |
| 	unsigned long flags;
 | |
| 
 | |
| 	spin_lock_irqsave(lock, flags);
 | |
| 
 | |
| 	list_del_init(&req->urb_list);
 | |
| 
 | |
| 	spin_unlock_irqrestore(lock, flags);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Driver requires a pool of URBs to operate. This function is called during
 | |
|  * initialization (attach phase), allocates a number of URBs, and puts them
 | |
|  * on the free (req_rxfreeq and req_txfreeq) queue
 | |
|  */
 | |
| static int
 | |
| dbus_usbos_urbreqs_alloc(usbos_info_t *usbos_info, uint32 count, bool is_rx)
 | |
| {
 | |
| 	int i;
 | |
| 	int allocated = 0;
 | |
| 	int err = DBUS_OK;
 | |
| 
 | |
| 	for (i = 0; i < count; i++) {
 | |
| 		urb_req_t *req;
 | |
| 
 | |
| 		req = MALLOC(usbos_info->pub->osh, sizeof(urb_req_t));
 | |
| 		if (req == NULL) {
 | |
| 			DBUSERR(("%s: MALLOC req failed\n", __FUNCTION__));
 | |
| 			err = DBUS_ERR_NOMEM;
 | |
| 			goto fail;
 | |
| 		}
 | |
| 		bzero(req, sizeof(urb_req_t));
 | |
| 
 | |
| 		req->urb = USB_ALLOC_URB();
 | |
| 		if (req->urb == NULL) {
 | |
| 			DBUSERR(("%s: USB_ALLOC_URB req->urb failed\n", __FUNCTION__));
 | |
| 			err = DBUS_ERR_NOMEM;
 | |
| 			goto fail;
 | |
| 		}
 | |
| 
 | |
| 		INIT_LIST_HEAD(&req->urb_list);
 | |
| 
 | |
| 		if (is_rx) {
 | |
| #if defined(BCM_RPC_NOCOPY) || defined(BCM_RPC_RXNOCOPY)
 | |
| 			/* don't allocate now. Do it on demand */
 | |
| 			req->pkt = NULL;
 | |
| #else
 | |
| 			/* pre-allocate  buffers never to be released */
 | |
| 			req->pkt = MALLOC(usbos_info->pub->osh, usbos_info->rxbuf_len);
 | |
| 			if (req->pkt == NULL) {
 | |
| 				DBUSERR(("%s: MALLOC req->pkt failed\n", __FUNCTION__));
 | |
| 				err = DBUS_ERR_NOMEM;
 | |
| 				goto fail;
 | |
| 			}
 | |
| #endif
 | |
| 			req->buf_len = usbos_info->rxbuf_len;
 | |
| 			dbus_usbos_qenq(&usbos_info->req_rxfreeq, req, &usbos_info->rxfree_lock);
 | |
| 		} else {
 | |
| 			req->buf_len = 0;
 | |
| 			dbus_usbos_qenq(&usbos_info->req_txfreeq, req, &usbos_info->txfree_lock);
 | |
| 		}
 | |
| 		allocated++;
 | |
| 		continue;
 | |
| 
 | |
| fail:
 | |
| 		if (req) {
 | |
| 			if (is_rx && req->pkt) {
 | |
| #if defined(BCM_RPC_NOCOPY) || defined(BCM_RPC_RXNOCOPY)
 | |
| 				/* req->pkt is NULL in "NOCOPY" mode */
 | |
| #else
 | |
| 				MFREE(usbos_info->pub->osh, req->pkt, req->buf_len);
 | |
| #endif
 | |
| 			}
 | |
| 			if (req->urb) {
 | |
| 				USB_FREE_URB(req->urb);
 | |
| 			}
 | |
| 			MFREE(usbos_info->pub->osh, req, sizeof(urb_req_t));
 | |
| 		}
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	atomic_add(allocated, is_rx ? &usbos_info->rxallocated : &usbos_info->txallocated);
 | |
| 
 | |
| 	if (is_rx) {
 | |
| 		DBUSTRACE(("%s: add %d (total %d) rx buf, each has %d bytes\n", __FUNCTION__,
 | |
| 			allocated, atomic_read(&usbos_info->rxallocated), usbos_info->rxbuf_len));
 | |
| 	} else {
 | |
| 		DBUSTRACE(("%s: add %d (total %d) tx req\n", __FUNCTION__,
 | |
| 			allocated, atomic_read(&usbos_info->txallocated)));
 | |
| 	}
 | |
| 
 | |
| 	return err;
 | |
| } /* dbus_usbos_urbreqs_alloc */
 | |
| 
 | |
| /** Typically called during detach or when attach failed. Don't call until all URBs unlinked */
 | |
| static int
 | |
| dbus_usbos_urbreqs_free(usbos_info_t *usbos_info, bool is_rx)
 | |
| {
 | |
| 	int rtn = 0;
 | |
| 	urb_req_t *req;
 | |
| 	struct list_head *req_q;
 | |
| 	spinlock_t *lock;
 | |
| 
 | |
| 	if (is_rx) {
 | |
| 		req_q = &usbos_info->req_rxfreeq;
 | |
| 		lock = &usbos_info->rxfree_lock;
 | |
| 	} else {
 | |
| 		req_q = &usbos_info->req_txfreeq;
 | |
| 		lock = &usbos_info->txfree_lock;
 | |
| 	}
 | |
| 	while ((req = dbus_usbos_qdeq(req_q, lock)) != NULL) {
 | |
| 
 | |
| 		if (is_rx) {
 | |
| 			if (req->pkt) {
 | |
| 				/* We do MFREE instead of PKTFREE because the pkt has been
 | |
| 				 * converted to native already
 | |
| 				 */
 | |
| 				MFREE(usbos_info->pub->osh, req->pkt, req->buf_len);
 | |
| 				req->pkt = NULL;
 | |
| 				req->buf_len = 0;
 | |
| 			}
 | |
| 		} else {
 | |
| 			/* sending req should not be assigned pkt buffer */
 | |
| 			ASSERT(req->pkt == NULL);
 | |
| 		}
 | |
| 
 | |
| 		if (req->urb) {
 | |
| 			USB_FREE_URB(req->urb);
 | |
| 			req->urb = NULL;
 | |
| 		}
 | |
| 		MFREE(usbos_info->pub->osh, req, sizeof(urb_req_t));
 | |
| 
 | |
| 		rtn++;
 | |
| 	}
 | |
| 	return rtn;
 | |
| } /* dbus_usbos_urbreqs_free */
 | |
| 
 | |
| /**
 | |
|  * called by Linux kernel on URB completion. Upper DBUS layer (dbus_usb.c) has to be notified of
 | |
|  * send completion.
 | |
|  */
 | |
| void
 | |
| dbus_usbos_send_complete(CALLBACK_ARGS)
 | |
| {
 | |
| 	urb_req_t *req = urb->context;
 | |
| 	dbus_irb_tx_t *txirb = req->arg;
 | |
| 	usbos_info_t *usbos_info = req->usbinfo;
 | |
| 	unsigned long flags;
 | |
| 	int status = DBUS_OK;
 | |
| 	int txposted;
 | |
| 
 | |
| 	USB_AUTOPM_PUT_INTERFACE_ASYNC(g_probe_info.intf);
 | |
| 
 | |
| 	spin_lock_irqsave(&usbos_info->txlock, flags);
 | |
| 
 | |
| 	dbus_usbos_req_del(req, &usbos_info->txposted_lock);
 | |
| 	txposted = atomic_dec_return(&usbos_info->txposted);
 | |
| 	if (unlikely (txposted < 0)) {
 | |
| 		DBUSERR(("%s ERROR: txposted is negative (%d)!!\n", __FUNCTION__, txposted));
 | |
| 	}
 | |
| 	spin_unlock_irqrestore(&usbos_info->txlock, flags);
 | |
| 
 | |
| 	if (unlikely (urb->status)) {
 | |
| 		status = DBUS_ERR_TXFAIL;
 | |
| 		DBUSTRACE(("txfail status %d\n", urb->status));
 | |
| 	}
 | |
| 
 | |
| #if defined(BCM_RPC_NOCOPY) || defined(BCM_RPC_RXNOCOPY)
 | |
| 	/* sending req should not be assigned pkt buffer */
 | |
| 	ASSERT(req->pkt == NULL);
 | |
| #endif
 | |
| 	/*  txirb should always be set, except for ZLP. ZLP is reusing this callback function. */
 | |
| 	if (txirb != NULL) {
 | |
| 		if (txirb->send_buf != NULL) {
 | |
| 			MFREE(usbos_info->pub->osh, txirb->send_buf, req->buf_len);
 | |
| 			txirb->send_buf = NULL;
 | |
| 			req->buf_len = 0;
 | |
| 		}
 | |
| 		if (likely (usbos_info->cbarg && usbos_info->cbs)) {
 | |
| 			if (likely (usbos_info->cbs->send_irb_complete != NULL))
 | |
| 			    usbos_info->cbs->send_irb_complete(usbos_info->cbarg, txirb, status);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	dbus_usbos_qenq(&usbos_info->req_txfreeq, req, &usbos_info->txfree_lock);
 | |
| } /* dbus_usbos_send_complete */
 | |
| 
 | |
| /**
 | |
|  * In order to receive USB traffic from the dongle, we need to supply the Linux kernel with a free
 | |
|  * URB that is going to contain received data.
 | |
|  */
 | |
| static int BCMFASTPATH
 | |
| dbus_usbos_recv_urb_submit(usbos_info_t *usbos_info, dbus_irb_rx_t *rxirb, uint32 ep_idx)
 | |
| {
 | |
| 	urb_req_t *req;
 | |
| 	int ret = DBUS_OK;
 | |
| 	unsigned long flags;
 | |
| 	void *p;
 | |
| 	uint rx_pipe;
 | |
| 	int rxposted;
 | |
| 
 | |
| 	BCM_REFERENCE(rxposted);
 | |
| 
 | |
| 	if (!(req = dbus_usbos_qdeq(&usbos_info->req_rxfreeq, &usbos_info->rxfree_lock))) {
 | |
| 		DBUSTRACE(("%s No free URB!\n", __FUNCTION__));
 | |
| 		return DBUS_ERR_RXDROP;
 | |
| 	}
 | |
| 
 | |
| 	spin_lock_irqsave(&usbos_info->rxlock, flags);
 | |
| 
 | |
| #if defined(BCM_RPC_NOCOPY) || defined(BCM_RPC_RXNOCOPY)
 | |
| 	req->pkt = rxirb->pkt = PKTGET(usbos_info->pub->osh, req->buf_len, FALSE);
 | |
| 	if (!rxirb->pkt) {
 | |
| 		DBUSERR(("%s: PKTGET failed\n", __FUNCTION__));
 | |
| 		dbus_usbos_qenq(&usbos_info->req_rxfreeq, req, &usbos_info->rxfree_lock);
 | |
| 		ret = DBUS_ERR_RXDROP;
 | |
| 		goto fail;
 | |
| 	}
 | |
| 	/* consider the packet "native" so we don't count it as MALLOCED in the osl */
 | |
| 	PKTTONATIVE(usbos_info->pub->osh, req->pkt);
 | |
| 	rxirb->buf = NULL;
 | |
| 	p = PKTDATA(usbos_info->pub->osh, req->pkt);
 | |
| #else
 | |
| 	if (req->buf_len != usbos_info->rxbuf_len) {
 | |
| 		ASSERT(req->pkt);
 | |
| 		MFREE(usbos_info->pub->osh, req->pkt, req->buf_len);
 | |
| 		DBUSTRACE(("%s: replace rx buff: old len %d, new len %d\n", __FUNCTION__,
 | |
| 			req->buf_len, usbos_info->rxbuf_len));
 | |
| 		req->buf_len = 0;
 | |
| 		req->pkt = MALLOC(usbos_info->pub->osh, usbos_info->rxbuf_len);
 | |
| 		if (req->pkt == NULL) {
 | |
| 			DBUSERR(("%s: MALLOC req->pkt failed\n", __FUNCTION__));
 | |
| 			ret = DBUS_ERR_NOMEM;
 | |
| 			goto fail;
 | |
| 		}
 | |
| 		req->buf_len = usbos_info->rxbuf_len;
 | |
| 	}
 | |
| 	rxirb->buf = req->pkt;
 | |
| 	p = rxirb->buf;
 | |
| #endif /* defined(BCM_RPC_NOCOPY) */
 | |
| 	rxirb->buf_len = req->buf_len;
 | |
| 	req->usbinfo = usbos_info;
 | |
| 	req->arg = rxirb;
 | |
| 	if (ep_idx == 0) {
 | |
| 		rx_pipe = usbos_info->rx_pipe;
 | |
| 	} else {
 | |
| 		rx_pipe = usbos_info->rx_pipe2;
 | |
| 		ASSERT(usbos_info->rx_pipe2);
 | |
| 	}
 | |
| 	/* Prepare the URB */
 | |
| 	usb_fill_bulk_urb(req->urb, usbos_info->usb, rx_pipe,
 | |
| 		p,
 | |
| 		rxirb->buf_len,
 | |
| 		(usb_complete_t)dbus_usbos_recv_complete, req);
 | |
| 		req->urb->transfer_flags |= URB_QUEUE_BULK;
 | |
| 
 | |
| 	if ((ret = USB_SUBMIT_URB(req->urb))) {
 | |
| 		DBUSERR(("%s USB_SUBMIT_URB failed. status %d\n", __FUNCTION__, ret));
 | |
| 		dbus_usbos_qenq(&usbos_info->req_rxfreeq, req, &usbos_info->rxfree_lock);
 | |
| 		ret = DBUS_ERR_RXFAIL;
 | |
| 		goto fail;
 | |
| 	}
 | |
| 	rxposted = atomic_inc_return(&usbos_info->rxposted);
 | |
| 
 | |
| 	dbus_usbos_qenq(&usbos_info->req_rxpostedq, req, &usbos_info->rxposted_lock);
 | |
| fail:
 | |
| 	spin_unlock_irqrestore(&usbos_info->rxlock, flags);
 | |
| 	return ret;
 | |
| } /* dbus_usbos_recv_urb_submit */
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * Called by worked thread when a 'receive URB' completed or Linux kernel when it returns a URB to
 | |
|  * this driver.
 | |
|  */
 | |
| static void BCMFASTPATH
 | |
| dbus_usbos_recv_complete_handle(urb_req_t *req, int len, int status)
 | |
| {
 | |
| 	dbus_irb_rx_t *rxirb = req->arg;
 | |
| 	usbos_info_t *usbos_info = req->usbinfo;
 | |
| 	unsigned long flags;
 | |
| 	int rxallocated, rxposted;
 | |
| 	int dbus_status = DBUS_OK;
 | |
| 	bool killed = (g_probe_info.suspend_state == USBOS_SUSPEND_STATE_SUSPEND_PENDING) ? 1 : 0;
 | |
| 
 | |
| 	spin_lock_irqsave(&usbos_info->rxlock, flags);
 | |
| 	dbus_usbos_req_del(req, &usbos_info->rxposted_lock);
 | |
| 	rxposted = atomic_dec_return(&usbos_info->rxposted);
 | |
| 	rxallocated = atomic_read(&usbos_info->rxallocated);
 | |
| 	spin_unlock_irqrestore(&usbos_info->rxlock, flags);
 | |
| 
 | |
| 	if ((rxallocated < usbos_info->pub->nrxq) && (!status) &&
 | |
| 		(rxposted == DBUS_USB_RXQUEUE_LOWER_WATERMARK)) {
 | |
| 			DBUSTRACE(("%s: need more rx buf: rxallocated %d rxposted %d!\n",
 | |
| 				__FUNCTION__, rxallocated, rxposted));
 | |
| 			dbus_usbos_urbreqs_alloc(usbos_info,
 | |
| 				MIN(DBUS_USB_RXQUEUE_BATCH_ADD,
 | |
| 				usbos_info->pub->nrxq - rxallocated), TRUE);
 | |
| 	}
 | |
| 
 | |
| 	/* Handle errors */
 | |
| 	if (status) {
 | |
| 		/*
 | |
| 		 * Linux 2.4 disconnect: -ENOENT or -EILSEQ for CRC error; rmmod: -ENOENT
 | |
| 		 * Linux 2.6 disconnect: -EPROTO, rmmod: -ESHUTDOWN
 | |
| 		 */
 | |
| 		if ((status == -ENOENT && (!killed))|| status == -ESHUTDOWN) {
 | |
| 			/* NOTE: unlink() can not be called from URB callback().
 | |
| 			 * Do not call dbusos_stop() here.
 | |
| 			 */
 | |
| 			DBUSTRACE(("%s rx error %d\n", __FUNCTION__, status));
 | |
| 			dbus_usbos_state_change(usbos_info, DBUS_STATE_DOWN);
 | |
| 		} else if (status == -EPROTO) {
 | |
| 			DBUSTRACE(("%s rx error %d\n", __FUNCTION__, status));
 | |
| 		} else if (killed && (status == -EHOSTUNREACH || status == -ENOENT)) {
 | |
| 			/* Device is suspended */
 | |
| 		} else {
 | |
| 			DBUSTRACE(("%s rx error %d\n", __FUNCTION__, status));
 | |
| 			dbus_usbos_errhandler(usbos_info, DBUS_ERR_RXFAIL);
 | |
| 		}
 | |
| 
 | |
| 		/* On error, don't submit more URBs yet */
 | |
| 		rxirb->buf = NULL;
 | |
| 		rxirb->actual_len = 0;
 | |
| 		dbus_status = DBUS_ERR_RXFAIL;
 | |
| 		goto fail;
 | |
| 	}
 | |
| 
 | |
| 	/* Make the skb represent the received urb */
 | |
| 	rxirb->actual_len = len;
 | |
| 
 | |
| 	if (rxirb->actual_len < sizeof(uint32)) {
 | |
| 		DBUSTRACE(("small pkt len %d, process as ZLP\n", rxirb->actual_len));
 | |
| 		dbus_status = DBUS_ERR_RXZLP;
 | |
| 	}
 | |
| 
 | |
| fail:
 | |
| #if defined(BCM_RPC_NOCOPY) || defined(BCM_RPC_RXNOCOPY)
 | |
| 	/* detach the packet from the queue */
 | |
| 	req->pkt = NULL;
 | |
| #endif /* BCM_RPC_NOCOPY || BCM_RPC_RXNOCOPY */
 | |
| 
 | |
| 	if (usbos_info->cbarg && usbos_info->cbs) {
 | |
| 		if (usbos_info->cbs->recv_irb_complete) {
 | |
| 			usbos_info->cbs->recv_irb_complete(usbos_info->cbarg, rxirb, dbus_status);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	dbus_usbos_qenq(&usbos_info->req_rxfreeq, req, &usbos_info->rxfree_lock);
 | |
| 
 | |
| 	/* Mark the interface as busy to reset USB autosuspend timer */
 | |
| 	USB_MARK_LAST_BUSY(usbos_info->usb);
 | |
| } /* dbus_usbos_recv_complete_handle */
 | |
| 
 | |
| /** called by Linux kernel when it returns a URB to this driver */
 | |
| static void
 | |
| dbus_usbos_recv_complete(CALLBACK_ARGS)
 | |
| {
 | |
| #ifdef USBOS_THREAD
 | |
| 	dbus_usbos_dispatch_schedule(CALLBACK_ARGS_DATA);
 | |
| #else /*  !USBOS_THREAD */
 | |
| 	dbus_usbos_recv_complete_handle(urb->context, urb->actual_length, urb->status);
 | |
| #endif /*  USBOS_THREAD */
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|  * If Linux notifies our driver that a control read or write URB has completed, we should notify
 | |
|  * the DBUS layer above us (dbus_usb.c in this case).
 | |
|  */
 | |
| static void
 | |
| dbus_usbos_ctl_complete(usbos_info_t *usbos_info, int type, int urbstatus)
 | |
| {
 | |
| 	int status = DBUS_ERR;
 | |
| 
 | |
| 	if (usbos_info == NULL)
 | |
| 		return;
 | |
| 
 | |
| 	switch (urbstatus) {
 | |
| 		case 0:
 | |
| 			status = DBUS_OK;
 | |
| 		break;
 | |
| 		case -EINPROGRESS:
 | |
| 		case -ENOENT:
 | |
| 		default:
 | |
| #ifdef INTR_EP_ENABLE
 | |
| 			DBUSERR(("%s:%d fail status %d bus:%d susp:%d intr:%d ctli:%d ctlo:%d\n",
 | |
| 				__FUNCTION__, type, urbstatus,
 | |
| 				usbos_info->pub->busstate, g_probe_info.suspend_state,
 | |
| 				usbos_info->intr_urb_submitted, usbos_info->ctlin_urb_submitted,
 | |
| 				usbos_info->ctlout_urb_submitted));
 | |
| #else
 | |
| 			DBUSERR(("%s: failed with status %d\n", __FUNCTION__, urbstatus));
 | |
| 			status = DBUS_ERR;
 | |
| 		break;
 | |
| #endif /* INTR_EP_ENABLE */
 | |
| 	}
 | |
| 
 | |
| 	if (usbos_info->cbarg && usbos_info->cbs) {
 | |
| 		if (usbos_info->cbs->ctl_complete)
 | |
| 			usbos_info->cbs->ctl_complete(usbos_info->cbarg, type, status);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /** called by Linux */
 | |
| static void
 | |
| dbus_usbos_ctlread_complete(CALLBACK_ARGS)
 | |
| {
 | |
| 	usbos_info_t *usbos_info = (usbos_info_t *)urb->context;
 | |
| 
 | |
| 	ASSERT(urb);
 | |
| 	usbos_info = (usbos_info_t *)urb->context;
 | |
| 
 | |
| 	dbus_usbos_ctl_complete(usbos_info, DBUS_CBCTL_READ, urb->status);
 | |
| 
 | |
| #ifdef USBOS_THREAD
 | |
| 	if (usbos_info->rxctl_deferrespok) {
 | |
| 		usbos_info->ctl_read.bRequestType = USB_DIR_IN | USB_TYPE_CLASS |
 | |
| 		USB_RECIP_INTERFACE;
 | |
| 		usbos_info->ctl_read.bRequest = 1;
 | |
| 	}
 | |
| #endif
 | |
| 
 | |
| 	up(&usbos_info->ctl_lock);
 | |
| 
 | |
| 	USB_AUTOPM_PUT_INTERFACE_ASYNC(g_probe_info.intf);
 | |
| }
 | |
| 
 | |
| /** called by Linux */
 | |
| static void
 | |
| dbus_usbos_ctlwrite_complete(CALLBACK_ARGS)
 | |
| {
 | |
| 	usbos_info_t *usbos_info = (usbos_info_t *)urb->context;
 | |
| 
 | |
| 	ASSERT(urb);
 | |
| 	usbos_info = (usbos_info_t *)urb->context;
 | |
| 
 | |
| 	dbus_usbos_ctl_complete(usbos_info, DBUS_CBCTL_WRITE, urb->status);
 | |
| 
 | |
| #ifdef USBOS_TX_THREAD
 | |
| 	usbos_info->ctl_state = USBOS_REQUEST_STATE_UNSCHEDULED;
 | |
| #endif /* USBOS_TX_THREAD */
 | |
| 
 | |
| 	up(&usbos_info->ctl_lock);
 | |
| 
 | |
| 	USB_AUTOPM_PUT_INTERFACE_ASYNC(g_probe_info.intf);
 | |
| }
 | |
| 
 | |
| #ifdef INTR_EP_ENABLE
 | |
| /** called by Linux */
 | |
| static void
 | |
| dbus_usbos_intr_complete(CALLBACK_ARGS)
 | |
| {
 | |
| 	usbos_info_t *usbos_info = (usbos_info_t *)urb->context;
 | |
| 	bool killed = (g_probe_info.suspend_state == USBOS_SUSPEND_STATE_SUSPEND_PENDING) ? 1 : 0;
 | |
| 
 | |
| 	if (usbos_info == NULL || usbos_info->pub == NULL)
 | |
| 		return;
 | |
| 	if ((urb->status == -ENOENT && (!killed)) || urb->status == -ESHUTDOWN ||
 | |
| 		urb->status == -ENODEV) {
 | |
| 		dbus_usbos_state_change(usbos_info, DBUS_STATE_DOWN);
 | |
| 	}
 | |
| 
 | |
| 	if (usbos_info->pub->busstate == DBUS_STATE_DOWN) {
 | |
| 		DBUSERR(("%s: intr cb when DBUS down, ignoring\n", __FUNCTION__));
 | |
| 		return;
 | |
| 	}
 | |
| 	dbus_usbos_ctl_complete(usbos_info, DBUS_CBINTR_POLL, urb->status);
 | |
| }
 | |
| #endif	/* INTR_EP_ENABLE */
 | |
| 
 | |
| /**
 | |
|  * when the bus is going to sleep or halt, the Linux kernel requires us to take ownership of our
 | |
|  * URBs again. Multiple code paths in this file require a list of URBs to be cancelled in a
 | |
|  * concurrency save manner.
 | |
|  */
 | |
| static void
 | |
| dbus_usbos_unlink(struct list_head *urbreq_q, spinlock_t *lock)
 | |
| {
 | |
| 	urb_req_t *req;
 | |
| 
 | |
| 	/* dbus_usbos_recv_complete() adds req back to req_freeq */
 | |
| 	while ((req = dbus_usbos_qdeq(urbreq_q, lock)) != NULL) {
 | |
| 		ASSERT(req->urb != NULL);
 | |
| 		USB_UNLINK_URB(req->urb);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /** multiple code paths in this file require the bus to stop */
 | |
| static void
 | |
| dbus_usbos_cancel_all_urbs(usbos_info_t *usbos_info)
 | |
| {
 | |
| 	int rxposted, txposted;
 | |
| 
 | |
| 	DBUSTRACE(("%s: unlink all URBs\n", __FUNCTION__));
 | |
| 
 | |
| #ifdef USBOS_TX_THREAD
 | |
| 	usbos_info->ctl_state = USBOS_REQUEST_STATE_UNSCHEDULED;
 | |
| 
 | |
| 	/* Yield the CPU to TX thread so all pending requests are submitted */
 | |
| 	while (!list_empty(&usbos_info->usbos_tx_list)) {
 | |
| 		wake_up_interruptible(&usbos_info->usbos_tx_queue_head);
 | |
| 		OSL_SLEEP(10);
 | |
| 	}
 | |
| #endif /* USBOS_TX_THREAD */
 | |
| 
 | |
| 	/* tell Linux kernel to cancel a single intr, ctl and blk URB */
 | |
| 	if (usbos_info->intr_urb)
 | |
| 		USB_UNLINK_URB(usbos_info->intr_urb);
 | |
| 	if (usbos_info->ctl_urb)
 | |
| 		USB_UNLINK_URB(usbos_info->ctl_urb);
 | |
| 	if (usbos_info->blk_urb)
 | |
| 		USB_UNLINK_URB(usbos_info->blk_urb);
 | |
| 
 | |
| 	dbus_usbos_unlink(&usbos_info->req_txpostedq, &usbos_info->txposted_lock);
 | |
| 	dbus_usbos_unlink(&usbos_info->req_rxpostedq, &usbos_info->rxposted_lock);
 | |
| 
 | |
| 	/* Wait until the callbacks for all submitted URBs have been called, because the
 | |
| 	 * handler needs to know is an USB suspend is in progress.
 | |
| 	 */
 | |
| 	SPINWAIT((atomic_read(&usbos_info->txposted) != 0 ||
 | |
| 		atomic_read(&usbos_info->rxposted) != 0), 10000);
 | |
| 
 | |
| 	txposted = atomic_read(&usbos_info->txposted);
 | |
| 	rxposted = atomic_read(&usbos_info->rxposted);
 | |
| 	if (txposted != 0 || rxposted != 0) {
 | |
| 		DBUSERR(("%s ERROR: REQs posted, rx=%d tx=%d!\n",
 | |
| 			__FUNCTION__, rxposted, txposted));
 | |
| 	}
 | |
| } /* dbus_usbos_cancel_all_urbs */
 | |
| 
 | |
| /** multiple code paths require the bus to stop */
 | |
| static void
 | |
| dbusos_stop(usbos_info_t *usbos_info)
 | |
| {
 | |
| 	urb_req_t *req;
 | |
| 	int rxposted;
 | |
| 	req = NULL;
 | |
| 	BCM_REFERENCE(req);
 | |
| 
 | |
| 	ASSERT(usbos_info);
 | |
| 
 | |
| 	dbus_usbos_state_change(usbos_info, DBUS_STATE_DOWN);
 | |
| 
 | |
| 	dbus_usbos_cancel_all_urbs(usbos_info);
 | |
| 
 | |
| #ifdef USBOS_THREAD
 | |
| 	/* yield the CPU to rx packet thread */
 | |
| 	while (1) {
 | |
| 		if (atomic_read(&usbos_info->usbos_list_cnt) <= 0)	break;
 | |
| 		wake_up_interruptible(&usbos_info->usbos_queue_head);
 | |
| 		OSL_SLEEP(3);
 | |
| 	}
 | |
| #endif /* USBOS_THREAD */
 | |
| 
 | |
| 	rxposted = atomic_read(&usbos_info->rxposted);
 | |
| 	if (rxposted > 0) {
 | |
| 		DBUSERR(("%s ERROR: rx REQs posted=%d in stop!\n", __FUNCTION__,
 | |
| 			rxposted));
 | |
| 	}
 | |
| 
 | |
| 	ASSERT(atomic_read(&usbos_info->txposted) == 0 && rxposted == 0);
 | |
| 
 | |
| } /* dbusos_stop */
 | |
| 
 | |
| #if defined(USB_SUSPEND_AVAILABLE)
 | |
| 
 | |
| /**
 | |
|  * Linux kernel sports a 'USB auto suspend' feature. See: http://lwn.net/Articles/373550/
 | |
|  * The suspend method is called by the Linux kernel to warn the driver that the device is going to
 | |
|  * be suspended.  If the driver returns a negative error code, the suspend will be aborted. If the
 | |
|  * driver returns 0, it must cancel all outstanding URBs (usb_kill_urb()) and not submit any more.
 | |
|  */
 | |
| static int
 | |
| dbus_usbos_suspend(struct usb_interface *intf,
 | |
|             pm_message_t message)
 | |
| {
 | |
| 	DBUSERR(("%s suspend state: %d\n", __FUNCTION__, g_probe_info.suspend_state));
 | |
| 	/* DHD for full dongle model */
 | |
| 	g_probe_info.suspend_state = USBOS_SUSPEND_STATE_SUSPEND_PENDING;
 | |
| 	dbus_usbos_state_change((usbos_info_t*)g_probe_info.usbos_info, DBUS_STATE_SLEEP);
 | |
| 	dbus_usbos_cancel_all_urbs((usbos_info_t*)g_probe_info.usbos_info);
 | |
| 	g_probe_info.suspend_state = USBOS_SUSPEND_STATE_SUSPENDED;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * The resume method is called to tell the driver that the device has been resumed and the driver
 | |
|  * can return to normal operation.  URBs may once more be submitted.
 | |
|  */
 | |
| static int dbus_usbos_resume(struct usb_interface *intf)
 | |
| {
 | |
| 	DBUSERR(("%s Device resumed\n", __FUNCTION__));
 | |
| 
 | |
| 	dbus_usbos_state_change((usbos_info_t*)g_probe_info.usbos_info, DBUS_STATE_UP);
 | |
| 	g_probe_info.suspend_state = USBOS_SUSPEND_STATE_DEVICE_ACTIVE;
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
| * This function is directly called by the Linux kernel, when the suspended device has been reset
 | |
| * instead of being resumed
 | |
| */
 | |
| static int dbus_usbos_reset_resume(struct usb_interface *intf)
 | |
| {
 | |
| 	DBUSERR(("%s Device reset resumed\n", __FUNCTION__));
 | |
| 
 | |
| 	/* The device may have lost power, so a firmware download may be required */
 | |
| 	dbus_usbos_state_change((usbos_info_t*)g_probe_info.usbos_info, DBUS_STATE_DL_NEEDED);
 | |
| 	g_probe_info.suspend_state = USBOS_SUSPEND_STATE_DEVICE_ACTIVE;
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| #endif /* USB_SUSPEND_AVAILABLE */
 | |
| 
 | |
| /**
 | |
|  * Called by Linux kernel at initialization time, kernel wants to know if our driver will accept the
 | |
|  * caller supplied USB interface. Note that USB drivers are bound to interfaces, and not to USB
 | |
|  * devices.
 | |
|  */
 | |
| #ifdef KERNEL26
 | |
| #define DBUS_USBOS_PROBE() static int dbus_usbos_probe(struct usb_interface *intf, const struct usb_device_id *id)
 | |
| #define DBUS_USBOS_DISCONNECT() static void dbus_usbos_disconnect(struct usb_interface *intf)
 | |
| #else
 | |
| #define DBUS_USBOS_PROBE() static void * dbus_usbos_probe(struct usb_device *usb, unsigned int ifnum, const struct usb_device_id *id)
 | |
| #define DBUS_USBOS_DISCONNECT() static void dbus_usbos_disconnect(struct usb_device *usb, void *ptr)
 | |
| #endif /* KERNEL26 */
 | |
| 
 | |
| DBUS_USBOS_PROBE()
 | |
| {
 | |
| 	int ep;
 | |
| 	struct usb_endpoint_descriptor *endpoint;
 | |
| 	int ret = 0;
 | |
| #ifdef KERNEL26
 | |
| 	struct usb_device *usb = interface_to_usbdev(intf);
 | |
| #else
 | |
| 	int claimed = 0;
 | |
| #endif
 | |
| 	int num_of_eps;
 | |
| #ifdef BCMUSBDEV_COMPOSITE
 | |
| 	int wlan_if = -1;
 | |
| 	bool intr_ep = FALSE;
 | |
| #endif /* BCMUSBDEV_COMPOSITE */
 | |
| 	wifi_adapter_info_t *adapter;
 | |
| 
 | |
| 	DHD_MUTEX_LOCK();
 | |
| 
 | |
| 	DBUSERR(("%s: bus num(busnum)=%d, slot num (portnum)=%d\n", __FUNCTION__,
 | |
| 		usb->bus->busnum, usb->portnum));
 | |
| 	adapter = dhd_wifi_platform_attach_adapter(USB_BUS, usb->bus->busnum,
 | |
| 		usb->portnum, WIFI_STATUS_POWER_ON);
 | |
| 	if (adapter == NULL) {
 | |
| 		DBUSERR(("%s: can't find adapter info for this chip\n", __FUNCTION__));
 | |
| 		goto fail;
 | |
| 	}
 | |
| 
 | |
| #ifdef BCMUSBDEV_COMPOSITE
 | |
| 	wlan_if = dbus_usbos_intf_wlan(usb);
 | |
| #ifdef KERNEL26
 | |
| 	if ((wlan_if >= 0) && (IFPTR(usb, wlan_if) == intf))
 | |
| #else
 | |
| 	if (wlan_if == ifnum)
 | |
| #endif /* KERNEL26 */
 | |
| 	{
 | |
| #endif /* BCMUSBDEV_COMPOSITE */
 | |
| 		g_probe_info.usb = usb;
 | |
| 		g_probe_info.dldone = TRUE;
 | |
| #ifdef BCMUSBDEV_COMPOSITE
 | |
| 	} else {
 | |
| 		DBUSTRACE(("dbus_usbos_probe: skip probe for non WLAN interface\n"));
 | |
| 		ret = BCME_UNSUPPORTED;
 | |
| 		goto fail;
 | |
| 	}
 | |
| #endif /* BCMUSBDEV_COMPOSITE */
 | |
| 
 | |
| #ifdef KERNEL26
 | |
| 	g_probe_info.intf = intf;
 | |
| #endif /* KERNEL26 */
 | |
| 
 | |
| #ifdef BCMUSBDEV_COMPOSITE
 | |
| 	if (IFDESC(usb, wlan_if).bInterfaceNumber > USB_COMPIF_MAX)
 | |
| #else
 | |
| 	if (IFDESC(usb, CONTROL_IF).bInterfaceNumber)
 | |
| #endif /* BCMUSBDEV_COMPOSITE */
 | |
| 	{
 | |
| 		ret = -1;
 | |
| 		goto fail;
 | |
| 	}
 | |
| 	if (id != NULL) {
 | |
| 		g_probe_info.vid = id->idVendor;
 | |
| 		g_probe_info.pid = id->idProduct;
 | |
| 	}
 | |
| 
 | |
| #ifdef KERNEL26
 | |
| 	usb_set_intfdata(intf, &g_probe_info);
 | |
| #endif
 | |
| 
 | |
| 	/* Check that the device supports only one configuration */
 | |
| 	if (usb->descriptor.bNumConfigurations != 1) {
 | |
| 		ret = -1;
 | |
| 		goto fail;
 | |
| 	}
 | |
| 
 | |
| 	if (usb->descriptor.bDeviceClass != USB_CLASS_VENDOR_SPEC) {
 | |
| #ifdef BCMUSBDEV_COMPOSITE
 | |
| 		if ((usb->descriptor.bDeviceClass != USB_CLASS_MISC) &&
 | |
| 			(usb->descriptor.bDeviceClass != USB_CLASS_WIRELESS)) {
 | |
| #endif /* BCMUSBDEV_COMPOSITE */
 | |
| 			ret = -1;
 | |
| 			goto fail;
 | |
| #ifdef BCMUSBDEV_COMPOSITE
 | |
| 		}
 | |
| #endif /* BCMUSBDEV_COMPOSITE */
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * Only the BDC interface configuration is supported:
 | |
| 	 *	Device class: USB_CLASS_VENDOR_SPEC
 | |
| 	 *	if0 class: USB_CLASS_VENDOR_SPEC
 | |
| 	 *	if0/ep0: control
 | |
| 	 *	if0/ep1: bulk in
 | |
| 	 *	if0/ep2: bulk out (ok if swapped with bulk in)
 | |
| 	 */
 | |
| 	if (CONFIGDESC(usb)->bNumInterfaces != 1) {
 | |
| #ifdef BCMUSBDEV_COMPOSITE
 | |
| 		if (CONFIGDESC(usb)->bNumInterfaces > USB_COMPIF_MAX) {
 | |
| #endif /* BCMUSBDEV_COMPOSITE */
 | |
| 			ret = -1;
 | |
| 			goto fail;
 | |
| #ifdef BCMUSBDEV_COMPOSITE
 | |
| 		}
 | |
| #endif /* BCMUSBDEV_COMPOSITE */
 | |
| 	}
 | |
| 
 | |
| 	/* Check interface */
 | |
| #ifndef KERNEL26
 | |
| #ifdef BCMUSBDEV_COMPOSITE
 | |
| 	if (usb_interface_claimed(IFPTR(usb, wlan_if)))
 | |
| #else
 | |
| 	if (usb_interface_claimed(IFPTR(usb, CONTROL_IF)))
 | |
| #endif /* BCMUSBDEV_COMPOSITE */
 | |
| 	{
 | |
| 		ret = -1;
 | |
| 		goto fail;
 | |
| 	}
 | |
| #endif /* !KERNEL26 */
 | |
| 
 | |
| #ifdef BCMUSBDEV_COMPOSITE
 | |
| 	if ((IFDESC(usb, wlan_if).bInterfaceClass != USB_CLASS_VENDOR_SPEC ||
 | |
| 		IFDESC(usb, wlan_if).bInterfaceSubClass != 2 ||
 | |
| 		IFDESC(usb, wlan_if).bInterfaceProtocol != 0xff) &&
 | |
| 		(IFDESC(usb, wlan_if).bInterfaceClass != USB_CLASS_MISC ||
 | |
| 		IFDESC(usb, wlan_if).bInterfaceSubClass != USB_SUBCLASS_COMMON ||
 | |
| 		IFDESC(usb, wlan_if).bInterfaceProtocol != USB_PROTO_IAD))
 | |
| #else
 | |
| 	if (IFDESC(usb, CONTROL_IF).bInterfaceClass != USB_CLASS_VENDOR_SPEC ||
 | |
| 		IFDESC(usb, CONTROL_IF).bInterfaceSubClass != 2 ||
 | |
| 		IFDESC(usb, CONTROL_IF).bInterfaceProtocol != 0xff)
 | |
| #endif /* BCMUSBDEV_COMPOSITE */
 | |
| 	{
 | |
| #ifdef BCMUSBDEV_COMPOSITE
 | |
| 			DBUSERR(("%s: invalid control interface: class %d, subclass %d, proto %d\n",
 | |
| 				__FUNCTION__,
 | |
| 				IFDESC(usb, wlan_if).bInterfaceClass,
 | |
| 				IFDESC(usb, wlan_if).bInterfaceSubClass,
 | |
| 				IFDESC(usb, wlan_if).bInterfaceProtocol));
 | |
| #else
 | |
| 			DBUSERR(("%s: invalid control interface: class %d, subclass %d, proto %d\n",
 | |
| 				__FUNCTION__,
 | |
| 				IFDESC(usb, CONTROL_IF).bInterfaceClass,
 | |
| 				IFDESC(usb, CONTROL_IF).bInterfaceSubClass,
 | |
| 				IFDESC(usb, CONTROL_IF).bInterfaceProtocol));
 | |
| #endif /* BCMUSBDEV_COMPOSITE */
 | |
| 			ret = -1;
 | |
| 			goto fail;
 | |
| 	}
 | |
| 
 | |
| 	/* Check control endpoint */
 | |
| #ifdef BCMUSBDEV_COMPOSITE
 | |
| 	endpoint = &IFEPDESC(usb, wlan_if, 0);
 | |
| #else
 | |
| 	endpoint = &IFEPDESC(usb, CONTROL_IF, 0);
 | |
| #endif /* BCMUSBDEV_COMPOSITE */
 | |
| 	if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_INT) {
 | |
| #ifdef BCMUSBDEV_COMPOSITE
 | |
| 		if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) !=
 | |
| 			USB_ENDPOINT_XFER_BULK) {
 | |
| #endif /* BCMUSBDEV_COMPOSITE */
 | |
| 			DBUSERR(("%s: invalid control endpoint %d\n",
 | |
| 				__FUNCTION__, endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK));
 | |
| 			ret = -1;
 | |
| 			goto fail;
 | |
| #ifdef BCMUSBDEV_COMPOSITE
 | |
| 		}
 | |
| #endif /* BCMUSBDEV_COMPOSITE */
 | |
| 	}
 | |
| 
 | |
| #ifdef BCMUSBDEV_COMPOSITE
 | |
| 	if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT) {
 | |
| #endif /* BCMUSBDEV_COMPOSITE */
 | |
| 		g_probe_info.intr_pipe =
 | |
| 			usb_rcvintpipe(usb, endpoint->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);
 | |
| #ifdef BCMUSBDEV_COMPOSITE
 | |
| 		intr_ep = TRUE;
 | |
| 	}
 | |
| #endif /* BCMUSBDEV_COMPOSITE */
 | |
| 
 | |
| #ifndef KERNEL26
 | |
| 	/* Claim interface */
 | |
| #ifdef BCMUSBDEV_COMPOSITE
 | |
| 	usb_driver_claim_interface(&dbus_usbdev, IFPTR(usb, wlan_if), &g_probe_info);
 | |
| #else
 | |
| 	usb_driver_claim_interface(&dbus_usbdev, IFPTR(usb, CONTROL_IF), &g_probe_info);
 | |
| #endif /* BCMUSBDEV_COMPOSITE */
 | |
| 	claimed = 1;
 | |
| #endif /* !KERNEL26 */
 | |
| 	g_probe_info.rx_pipe = 0;
 | |
| 	g_probe_info.rx_pipe2 = 0;
 | |
| 	g_probe_info.tx_pipe = 0;
 | |
| #ifdef BCMUSBDEV_COMPOSITE
 | |
| 	if (intr_ep)
 | |
| 		ep = 1;
 | |
| 	else
 | |
| 		ep = 0;
 | |
| 	num_of_eps = IFDESC(usb, wlan_if).bNumEndpoints - 1;
 | |
| #else
 | |
| 	num_of_eps = IFDESC(usb, BULK_IF).bNumEndpoints - 1;
 | |
| #endif /* BCMUSBDEV_COMPOSITE */
 | |
| 
 | |
| 	if ((num_of_eps != 2) && (num_of_eps != 3)) {
 | |
| #ifdef BCMUSBDEV_COMPOSITE
 | |
| 		if (num_of_eps > 7)
 | |
| #endif /* BCMUSBDEV_COMPOSITE */
 | |
| 			ASSERT(0);
 | |
| 	}
 | |
| 	/* Check data endpoints and get pipes */
 | |
| #ifdef BCMUSBDEV_COMPOSITE
 | |
| 	for (; ep <= num_of_eps; ep++)
 | |
| #else
 | |
| 	for (ep = 1; ep <= num_of_eps; ep++)
 | |
| #endif /* BCMUSBDEV_COMPOSITE */
 | |
| 	{
 | |
| #ifdef BCMUSBDEV_COMPOSITE
 | |
| 		endpoint = &IFEPDESC(usb, wlan_if, ep);
 | |
| #else
 | |
| 		endpoint = &IFEPDESC(usb, BULK_IF, ep);
 | |
| #endif /* BCMUSBDEV_COMPOSITE */
 | |
| 		if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) !=
 | |
| 		    USB_ENDPOINT_XFER_BULK) {
 | |
| 			DBUSERR(("%s: invalid data endpoint %d\n",
 | |
| 			           __FUNCTION__, ep));
 | |
| 			ret = -1;
 | |
| 			goto fail;
 | |
| 		}
 | |
| 
 | |
| 		if ((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN) {
 | |
| 			/* direction: dongle->host */
 | |
| 			if (!g_probe_info.rx_pipe) {
 | |
| 				g_probe_info.rx_pipe = usb_rcvbulkpipe(usb,
 | |
| 					(endpoint->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK));
 | |
| 			} else {
 | |
| 				g_probe_info.rx_pipe2 = usb_rcvbulkpipe(usb,
 | |
| 					(endpoint->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK));
 | |
| 			}
 | |
| 
 | |
| 		} else
 | |
| 			g_probe_info.tx_pipe = usb_sndbulkpipe(usb, (endpoint->bEndpointAddress &
 | |
| 			     USB_ENDPOINT_NUMBER_MASK));
 | |
| 	}
 | |
| 
 | |
| 	/* Allocate interrupt URB and data buffer */
 | |
| 	/* RNDIS says 8-byte intr, our old drivers used 4-byte */
 | |
| #ifdef BCMUSBDEV_COMPOSITE
 | |
| 	g_probe_info.intr_size = (IFEPDESC(usb, wlan_if, 0).wMaxPacketSize == 16) ? 8 : 4;
 | |
| 	g_probe_info.interval = IFEPDESC(usb, wlan_if, 0).bInterval;
 | |
| #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 21))
 | |
| 	usb->quirks |= USB_QUIRK_NO_SET_INTF;
 | |
| #endif
 | |
| #else
 | |
| 	g_probe_info.intr_size = (IFEPDESC(usb, CONTROL_IF, 0).wMaxPacketSize == 16) ? 8 : 4;
 | |
| 	g_probe_info.interval = IFEPDESC(usb, CONTROL_IF, 0).bInterval;
 | |
| #endif /* BCMUSBDEV_COMPOSITE */
 | |
| 
 | |
| #ifndef KERNEL26
 | |
| 	/* usb_fill_int_urb does the interval decoding in 2.6 */
 | |
| 	if (usb->speed == USB_SPEED_HIGH)
 | |
| 		g_probe_info.interval = 1 << (g_probe_info.interval - 1);
 | |
| #endif
 | |
| 	if (usb->speed == USB_SPEED_SUPER) {
 | |
| 		g_probe_info.device_speed = SUPER_SPEED;
 | |
| 		DBUSERR(("super speed device detected\n"));
 | |
| 	} else if (usb->speed == USB_SPEED_HIGH) {
 | |
| 		g_probe_info.device_speed = HIGH_SPEED;
 | |
| 		DBUSERR(("high speed device detected\n"));
 | |
| 	} else {
 | |
| 		g_probe_info.device_speed = FULL_SPEED;
 | |
| 		DBUSERR(("full speed device detected\n"));
 | |
| 	}
 | |
| 	if (g_probe_info.dereged == FALSE && probe_cb) {
 | |
| 		disc_arg = probe_cb(probe_arg, "", USB_BUS, usb->bus->busnum, usb->portnum, 0);
 | |
| 	}
 | |
| 
 | |
| 	g_probe_info.disc_cb_done = FALSE;
 | |
| 
 | |
| #ifdef KERNEL26
 | |
| 	intf->needs_remote_wakeup = 1;
 | |
| #endif /* KERNEL26 */
 | |
| 	DHD_MUTEX_UNLOCK();
 | |
| 
 | |
| 	/* Success */
 | |
| #ifdef KERNEL26
 | |
| 	return DBUS_OK;
 | |
| #else
 | |
| 	usb_inc_dev_use(usb);
 | |
| 	return &g_probe_info;
 | |
| #endif
 | |
| 
 | |
| fail:
 | |
| 	printf("%s: Exit ret=%d\n", __FUNCTION__, ret);
 | |
| #ifdef BCMUSBDEV_COMPOSITE
 | |
| 	if (ret != BCME_UNSUPPORTED)
 | |
| #endif /* BCMUSBDEV_COMPOSITE */
 | |
| 		DBUSERR(("%s: failed with errno %d\n", __FUNCTION__, ret));
 | |
| #ifndef KERNEL26
 | |
| 	if (claimed)
 | |
| #ifdef BCMUSBDEV_COMPOSITE
 | |
| 		usb_driver_release_interface(&dbus_usbdev, IFPTR(usb, wlan_if));
 | |
| #else
 | |
| 		usb_driver_release_interface(&dbus_usbdev, IFPTR(usb, CONTROL_IF));
 | |
| #endif /* BCMUSBDEV_COMPOSITE */
 | |
| #endif /* !KERNEL26 */
 | |
| 
 | |
| 	DHD_MUTEX_UNLOCK();
 | |
| #ifdef KERNEL26
 | |
| 	usb_set_intfdata(intf, NULL);
 | |
| 	return ret;
 | |
| #else
 | |
| 	return NULL;
 | |
| #endif
 | |
| } /* dbus_usbos_probe */
 | |
| 
 | |
| /** Called by Linux kernel, is the counter part of dbus_usbos_probe() */
 | |
| DBUS_USBOS_DISCONNECT()
 | |
| {
 | |
| #ifdef KERNEL26
 | |
| 	struct usb_device *usb = interface_to_usbdev(intf);
 | |
| 	probe_info_t *probe_usb_init_data = usb_get_intfdata(intf);
 | |
| #else
 | |
| 	probe_info_t *probe_usb_init_data = (probe_info_t *) ptr;
 | |
| #endif
 | |
| 	usbos_info_t *usbos_info;
 | |
| 
 | |
| 	DHD_MUTEX_LOCK();
 | |
| 
 | |
| 	DBUSERR(("%s: bus num(busnum)=%d, slot num (portnum)=%d\n", __FUNCTION__,
 | |
| 		usb->bus->busnum, usb->portnum));
 | |
| 
 | |
| 	if (probe_usb_init_data) {
 | |
| 		usbos_info = (usbos_info_t *) probe_usb_init_data->usbos_info;
 | |
| 		if (usbos_info) {
 | |
| 			if ((probe_usb_init_data->dereged == FALSE) && disconnect_cb && disc_arg) {
 | |
| 				disconnect_cb(disc_arg);
 | |
| 				disc_arg = NULL;
 | |
| 				probe_usb_init_data->disc_cb_done = TRUE;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (usb) {
 | |
| #ifndef KERNEL26
 | |
| #ifdef BCMUSBDEV_COMPOSITE
 | |
| 		usb_driver_release_interface(&dbus_usbdev, IFPTR(usb, wlan_if));
 | |
| #else
 | |
| 		usb_driver_release_interface(&dbus_usbdev, IFPTR(usb, CONTROL_IF));
 | |
| #endif /* BCMUSBDEV_COMPOSITE */
 | |
| 		usb_dec_dev_use(usb);
 | |
| #endif /* !KERNEL26 */
 | |
| 	}
 | |
| 	DHD_MUTEX_UNLOCK();
 | |
| } /* dbus_usbos_disconnect */
 | |
| 
 | |
| #define LOOPBACK_PKT_START 0xBABE1234
 | |
| 
 | |
| bool is_loopback_pkt(void *buf)
 | |
| {
 | |
| 
 | |
| 	uint32 *buf_ptr = (uint32 *) buf;
 | |
| 
 | |
| 	if (*buf_ptr == LOOPBACK_PKT_START)
 | |
| 		return TRUE;
 | |
| 	return FALSE;
 | |
| 
 | |
| }
 | |
| 
 | |
| int matches_loopback_pkt(void *buf)
 | |
| {
 | |
| 	int i, j;
 | |
| 	unsigned char *cbuf = (unsigned char *) buf;
 | |
| 
 | |
| 	for (i = 4; i < loopback_size; i++) {
 | |
| 		if (cbuf[i] != (i % 256)) {
 | |
| 			printf("%s: mismatch at i=%d %d : ", __FUNCTION__, i, cbuf[i]);
 | |
| 			for (j = i; ((j < i+ 16) && (j < loopback_size)); j++) {
 | |
| 				printf("%d ", cbuf[j]);
 | |
| 			}
 | |
| 			printf("\n");
 | |
| 			return 0;
 | |
| 		}
 | |
| 	}
 | |
| 	loopback_rx_cnt++;
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| int dbus_usbos_loopback_tx(void *usbos_info_ptr, int cnt, int size)
 | |
| {
 | |
| 	usbos_info_t *usbos_info = (usbos_info_t *) usbos_info_ptr;
 | |
| 	unsigned char *buf;
 | |
| 	int j;
 | |
| 	void* p = NULL;
 | |
| 	int rc, last_rx_cnt;
 | |
| 	int tx_failed_cnt;
 | |
| 	int max_size = 1650;
 | |
| 	int usb_packet_size = 512;
 | |
| 	int min_packet_size = 10;
 | |
| 
 | |
| 	if (size % usb_packet_size == 0) {
 | |
| 		size = size - 1;
 | |
| 		DBUSERR(("%s: overriding size=%d \n", __FUNCTION__, size));
 | |
| 	}
 | |
| 
 | |
| 	if (size < min_packet_size) {
 | |
| 		size = min_packet_size;
 | |
| 		DBUSERR(("%s: overriding size=%d\n", __FUNCTION__, min_packet_size));
 | |
| 	}
 | |
| 	if (size > max_size) {
 | |
| 		size = max_size;
 | |
| 		DBUSERR(("%s: overriding size=%d\n", __FUNCTION__, max_size));
 | |
| 	}
 | |
| 
 | |
| 	loopback_tx_cnt = 0;
 | |
| 	loopback_rx_cnt = 0;
 | |
| 	tx_failed_cnt = 0;
 | |
| 	loopback_size   = size;
 | |
| 
 | |
| 	while (loopback_tx_cnt < cnt) {
 | |
| 		uint32 *x;
 | |
| 		int pkt_size = loopback_size;
 | |
| 
 | |
| 		p = PKTGET(usbos_info->pub->osh, pkt_size, TRUE);
 | |
| 		if (p == NULL) {
 | |
| 			DBUSERR(("%s:%d Failed to allocate packet sz=%d\n",
 | |
| 			       __FUNCTION__, __LINE__, pkt_size));
 | |
| 			return BCME_ERROR;
 | |
| 		}
 | |
| 		x = (uint32*) PKTDATA(usbos_info->pub->osh, p);
 | |
| 		*x = LOOPBACK_PKT_START;
 | |
| 		buf = (unsigned char*) x;
 | |
| 		for (j = 4; j < pkt_size; j++) {
 | |
| 			buf[j] = j % 256;
 | |
| 		}
 | |
| 		rc = dbus_send_buf(usbos_info->pub, buf, pkt_size, p);
 | |
| 		if (rc != BCME_OK) {
 | |
| 			DBUSERR(("%s:%d Freeing packet \n", __FUNCTION__, __LINE__));
 | |
| 			PKTFREE(usbos_info->pub->osh, p, TRUE);
 | |
| 			dbus_usbos_wait(usbos_info, 1);
 | |
| 			tx_failed_cnt++;
 | |
| 		} else {
 | |
| 			loopback_tx_cnt++;
 | |
| 			tx_failed_cnt = 0;
 | |
| 		}
 | |
| 		if (tx_failed_cnt == 5) {
 | |
| 			DBUSERR(("%s : Failed to send loopback packets cnt=%d loopback_tx_cnt=%d\n",
 | |
| 			 __FUNCTION__, cnt, loopback_tx_cnt));
 | |
| 			break;
 | |
| 		}
 | |
| 	}
 | |
| 	printf("Transmitted %d loopback packets of size %d\n", loopback_tx_cnt, loopback_size);
 | |
| 
 | |
| 	last_rx_cnt = loopback_rx_cnt;
 | |
| 	while (loopback_rx_cnt < loopback_tx_cnt) {
 | |
| 		dbus_usbos_wait(usbos_info, 1);
 | |
| 		if (loopback_rx_cnt <= last_rx_cnt) {
 | |
| 			DBUSERR(("%s: Matched rx cnt stuck at %d \n", __FUNCTION__, last_rx_cnt));
 | |
| 			return BCME_ERROR;
 | |
| 		}
 | |
| 		last_rx_cnt = loopback_rx_cnt;
 | |
| 	}
 | |
| 	printf("Received %d loopback packets of size %d\n", loopback_tx_cnt, loopback_size);
 | |
| 
 | |
| 	return BCME_OK;
 | |
| } /* dbus_usbos_loopback_tx */
 | |
| 
 | |
| /**
 | |
|  * Higher layer (dbus_usb.c) wants to transmit an I/O Request Block
 | |
|  *     @param[in] txirb txirb->pkt, if non-zero, contains a single or a chain of packets
 | |
|  */
 | |
| static int
 | |
| dbus_usbos_intf_send_irb(void *bus, dbus_irb_tx_t *txirb)
 | |
| {
 | |
| 	usbos_info_t *usbos_info = (usbos_info_t *) bus;
 | |
| 	urb_req_t *req, *req_zlp = NULL;
 | |
| 	int ret = DBUS_OK;
 | |
| 	unsigned long flags;
 | |
| 	void *pkt;
 | |
| 	uint32 buffer_length;
 | |
| 	uint8 *buf;
 | |
| 
 | |
| 	if ((usbos_info == NULL) || !usbos_info->tx_pipe) {
 | |
| 		return DBUS_ERR;
 | |
| 	}
 | |
| 
 | |
| 	if (txirb->pkt != NULL) {
 | |
| 		buffer_length = pkttotlen(usbos_info->pub->osh, txirb->pkt);
 | |
| 		/* In case of multiple packets the values below may be overwritten */
 | |
| 		txirb->send_buf = NULL;
 | |
| 		buf = PKTDATA(usbos_info->pub->osh, txirb->pkt);
 | |
| 	} else { /* txirb->buf != NULL */
 | |
| 		ASSERT(txirb->buf != NULL);
 | |
| 		ASSERT(txirb->send_buf == NULL);
 | |
| 		buffer_length = txirb->len;
 | |
| 		buf = txirb->buf;
 | |
| 	}
 | |
| 
 | |
| 	if (!(req = dbus_usbos_qdeq(&usbos_info->req_txfreeq, &usbos_info->txfree_lock))) {
 | |
| 		DBUSERR(("%s No free URB!\n", __FUNCTION__));
 | |
| 		return DBUS_ERR_TXDROP;
 | |
| 	}
 | |
| 
 | |
| 	/* If not using standard Linux kernel functionality for handling Zero Length Packet(ZLP),
 | |
| 	 * the dbus needs to generate ZLP when length is multiple of MaxPacketSize.
 | |
| 	 */
 | |
| #ifndef WL_URB_ZPKT
 | |
| 	if (!(buffer_length % usbos_info->maxps)) {
 | |
| 		if (!(req_zlp =
 | |
| 			dbus_usbos_qdeq(&usbos_info->req_txfreeq, &usbos_info->txfree_lock))) {
 | |
| 			DBUSERR(("%s No free URB for ZLP!\n", __FUNCTION__));
 | |
| 			dbus_usbos_qenq(&usbos_info->req_txfreeq, req, &usbos_info->txfree_lock);
 | |
| 			return DBUS_ERR_TXDROP;
 | |
| 		}
 | |
| 
 | |
| 		/* No txirb, so that dbus_usbos_send_complete can differentiate between
 | |
| 		 * DATA and ZLP.
 | |
| 		 */
 | |
| 		req_zlp->arg = NULL;
 | |
| 		req_zlp->usbinfo = usbos_info;
 | |
| 		req_zlp->buf_len = 0;
 | |
| 
 | |
| 		usb_fill_bulk_urb(req_zlp->urb, usbos_info->usb, usbos_info->tx_pipe, NULL,
 | |
| 			0, (usb_complete_t)dbus_usbos_send_complete, req_zlp);
 | |
| 
 | |
| 		req_zlp->urb->transfer_flags |= URB_QUEUE_BULK;
 | |
| 	}
 | |
| #endif /* !WL_URB_ZPKT */
 | |
| 
 | |
| #ifndef USBOS_TX_THREAD
 | |
| 	/* Disable USB autosuspend until this request completes, request USB resume if needed.
 | |
| 	 * Because this call runs asynchronously, there is no guarantee the bus is resumed before
 | |
| 	 * the URB is submitted, and the URB might be dropped. Use USBOS_TX_THREAD to avoid
 | |
| 	 * this.
 | |
| 	 */
 | |
| 	USB_AUTOPM_GET_INTERFACE_ASYNC(g_probe_info.intf);
 | |
| #endif /* !USBOS_TX_THREAD */
 | |
| 
 | |
| 	spin_lock_irqsave(&usbos_info->txlock, flags);
 | |
| 
 | |
| 	req->arg = txirb;
 | |
| 	req->usbinfo = usbos_info;
 | |
| 	req->buf_len = 0;
 | |
| 
 | |
| 	/* Prepare the URB */
 | |
| 	if (txirb->pkt != NULL) {
 | |
| 		uint32 pktlen;
 | |
| 		uint8 *transfer_buf;
 | |
| 
 | |
| 		/* For multiple packets, allocate contiguous buffer and copy packet data to it */
 | |
| 		if (PKTNEXT(usbos_info->pub->osh, txirb->pkt)) {
 | |
| 			transfer_buf = MALLOC(usbos_info->pub->osh, buffer_length);
 | |
| 			if (!transfer_buf) {
 | |
| 				ret = DBUS_ERR_TXDROP;
 | |
| 				DBUSERR(("fail to alloc to usb buffer\n"));
 | |
| 				goto fail;
 | |
| 			}
 | |
| 
 | |
| 			pkt = txirb->pkt;
 | |
| 			txirb->send_buf = transfer_buf;
 | |
| 			req->buf_len = buffer_length;
 | |
| 
 | |
| 			while (pkt) {
 | |
| 				pktlen = PKTLEN(usbos_info->pub->osh, pkt);
 | |
| 				bcopy(PKTDATA(usbos_info->pub->osh, pkt), transfer_buf, pktlen);
 | |
| 				transfer_buf += pktlen;
 | |
| 				pkt = PKTNEXT(usbos_info->pub->osh, pkt);
 | |
| 			}
 | |
| 
 | |
| 			ASSERT(((uint8 *) txirb->send_buf + buffer_length) == transfer_buf);
 | |
| 
 | |
| 			/* Overwrite buf pointer with pointer to allocated contiguous transfer_buf
 | |
| 			 */
 | |
| 			buf = txirb->send_buf;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	usb_fill_bulk_urb(req->urb, usbos_info->usb, usbos_info->tx_pipe, buf,
 | |
| 		buffer_length, (usb_complete_t)dbus_usbos_send_complete, req);
 | |
| 
 | |
| 	req->urb->transfer_flags |= URB_QUEUE_BULK;
 | |
| 
 | |
| #ifdef USBOS_TX_THREAD
 | |
| 	/* Enqueue TX request, the TX thread will resume the bus if needed and submit
 | |
| 	 * it asynchronously
 | |
| 	 */
 | |
| 	dbus_usbos_qenq(&usbos_info->usbos_tx_list, req, &usbos_info->usbos_tx_list_lock);
 | |
| 	if (req_zlp != NULL) {
 | |
| 		dbus_usbos_qenq(&usbos_info->usbos_tx_list, req_zlp,
 | |
| 			&usbos_info->usbos_tx_list_lock);
 | |
| 	}
 | |
| 	spin_unlock_irqrestore(&usbos_info->txlock, flags);
 | |
| 
 | |
| 	wake_up_interruptible(&usbos_info->usbos_tx_queue_head);
 | |
| 	return DBUS_OK;
 | |
| #else
 | |
| 	if ((ret = USB_SUBMIT_URB(req->urb))) {
 | |
| 		ret = DBUS_ERR_TXDROP;
 | |
| 		goto fail;
 | |
| 	}
 | |
| 
 | |
| 	dbus_usbos_qenq(&usbos_info->req_txpostedq, req, &usbos_info->txposted_lock);
 | |
| 	atomic_inc(&usbos_info->txposted);
 | |
| 
 | |
| 	if (req_zlp != NULL) {
 | |
| 		if ((ret = USB_SUBMIT_URB(req_zlp->urb))) {
 | |
| 			DBUSERR(("failed to submit ZLP URB!\n"));
 | |
| 			ASSERT(0);
 | |
| 			ret = DBUS_ERR_TXDROP;
 | |
| 			goto fail2;
 | |
| 		}
 | |
| 
 | |
| 		dbus_usbos_qenq(&usbos_info->req_txpostedq, req_zlp, &usbos_info->txposted_lock);
 | |
| 		/* Also increment txposted for zlp packet, as it will be decremented in
 | |
| 		 * dbus_usbos_send_complete()
 | |
| 		 */
 | |
| 		atomic_inc(&usbos_info->txposted);
 | |
| 	}
 | |
| 
 | |
| 	spin_unlock_irqrestore(&usbos_info->txlock, flags);
 | |
| 	return DBUS_OK;
 | |
| #endif /* USBOS_TX_THREAD */
 | |
| 
 | |
| fail:
 | |
| 	if (txirb->send_buf != NULL) {
 | |
| 		MFREE(usbos_info->pub->osh, txirb->send_buf, req->buf_len);
 | |
| 		txirb->send_buf = NULL;
 | |
| 		req->buf_len = 0;
 | |
| 	}
 | |
| 	dbus_usbos_qenq(&usbos_info->req_txfreeq, req, &usbos_info->txfree_lock);
 | |
| #ifndef USBOS_TX_THREAD
 | |
| fail2:
 | |
| #endif
 | |
| 	if (req_zlp != NULL) {
 | |
| 		dbus_usbos_qenq(&usbos_info->req_txfreeq, req_zlp, &usbos_info->txfree_lock);
 | |
| 	}
 | |
| 
 | |
| 	spin_unlock_irqrestore(&usbos_info->txlock, flags);
 | |
| 
 | |
| #ifndef USBOS_TX_THREAD
 | |
| 	USB_AUTOPM_PUT_INTERFACE_ASYNC(g_probe_info.intf);
 | |
| #endif /* !USBOS_TX_THREAD */
 | |
| 
 | |
| 	return ret;
 | |
| } /* dbus_usbos_intf_send_irb */
 | |
| 
 | |
| /** Higher layer (dbus_usb.c) recycles a received (and used) packet. */
 | |
| static int
 | |
| dbus_usbos_intf_recv_irb(void *bus, dbus_irb_rx_t *rxirb)
 | |
| {
 | |
| 	usbos_info_t *usbos_info = (usbos_info_t *) bus;
 | |
| 	int ret = DBUS_OK;
 | |
| 
 | |
| 	if (usbos_info == NULL)
 | |
| 		return DBUS_ERR;
 | |
| 
 | |
| 	ret = dbus_usbos_recv_urb_submit(usbos_info, rxirb, 0);
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static int
 | |
| dbus_usbos_intf_recv_irb_from_ep(void *bus, dbus_irb_rx_t *rxirb, uint32 ep_idx)
 | |
| {
 | |
| 	usbos_info_t *usbos_info = (usbos_info_t *) bus;
 | |
| 	int ret = DBUS_OK;
 | |
| 
 | |
| 	if (usbos_info == NULL)
 | |
| 		return DBUS_ERR;
 | |
| 
 | |
| #ifdef INTR_EP_ENABLE
 | |
| 		/* By specifying the ep_idx value of 0xff, the cdc layer is asking to
 | |
| 		* submit an interrupt URB
 | |
| 		*/
 | |
| 		if (rxirb == NULL && ep_idx == 0xff) {
 | |
| 			/* submit intr URB */
 | |
| 			if ((ret = USB_SUBMIT_URB(usbos_info->intr_urb)) < 0) {
 | |
| 				DBUSERR(("%s intr USB_SUBMIT_URB failed, status %d\n",
 | |
| 					__FUNCTION__, ret));
 | |
| 			}
 | |
| 			return ret;
 | |
| 		}
 | |
| #else
 | |
| 		if (rxirb == NULL) {
 | |
| 			return DBUS_ERR;
 | |
| 		}
 | |
| #endif /* INTR_EP_ENABLE */
 | |
| 
 | |
| 	ret = dbus_usbos_recv_urb_submit(usbos_info, rxirb, ep_idx);
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| /** Higher layer (dbus_usb.c) want to cancel an IRB */
 | |
| static int
 | |
| dbus_usbos_intf_cancel_irb(void *bus, dbus_irb_tx_t *txirb)
 | |
| {
 | |
| 	usbos_info_t *usbos_info = (usbos_info_t *) bus;
 | |
| 
 | |
| 	if (usbos_info == NULL)
 | |
| 		return DBUS_ERR;
 | |
| 
 | |
| 	return DBUS_ERR;
 | |
| }
 | |
| 
 | |
| /** Only one CTL transfer can be pending at any time. This function may block. */
 | |
| static int
 | |
| dbus_usbos_intf_send_ctl(void *bus, uint8 *buf, int len)
 | |
| {
 | |
| 	usbos_info_t *usbos_info = (usbos_info_t *) bus;
 | |
| 	uint16 size;
 | |
| #ifndef USBOS_TX_THREAD
 | |
| 	int status;
 | |
| #endif /* USBOS_TX_THREAD */
 | |
| 
 | |
| 	if ((usbos_info == NULL) || (buf == NULL) || (len == 0))
 | |
| 		return DBUS_ERR;
 | |
| 
 | |
| 	if (usbos_info->ctl_urb == NULL)
 | |
| 		return DBUS_ERR;
 | |
| 
 | |
| 	/* Block until a pending CTL transfer has completed */
 | |
| 	if (down_interruptible(&usbos_info->ctl_lock) != 0) {
 | |
| 		return DBUS_ERR_TXCTLFAIL;
 | |
| 	}
 | |
| 
 | |
| #ifdef USBOS_TX_THREAD
 | |
| 	ASSERT(usbos_info->ctl_state == USBOS_REQUEST_STATE_UNSCHEDULED);
 | |
| #else
 | |
| 	/* Disable USB autosuspend until this request completes, request USB resume if needed.
 | |
| 	 * Because this call runs asynchronously, there is no guarantee the bus is resumed before
 | |
| 	 * the URB is submitted, and the URB might be dropped. Use USBOS_TX_THREAD to avoid
 | |
| 	 * this.
 | |
| 	 */
 | |
| 	USB_AUTOPM_GET_INTERFACE_ASYNC(g_probe_info.intf);
 | |
| #endif /* USBOS_TX_THREAD */
 | |
| 
 | |
| 	size = len;
 | |
| 	usbos_info->ctl_write.wLength = cpu_to_le16p(&size);
 | |
| 	usbos_info->ctl_urb->transfer_buffer_length = size;
 | |
| 
 | |
| 	usb_fill_control_urb(usbos_info->ctl_urb,
 | |
| 		usbos_info->usb,
 | |
| 		usb_sndctrlpipe(usbos_info->usb, 0),
 | |
| 		(unsigned char *) &usbos_info->ctl_write,
 | |
| 		buf, size, (usb_complete_t)dbus_usbos_ctlwrite_complete, usbos_info);
 | |
| 
 | |
| #ifdef USBOS_TX_THREAD
 | |
| 	/* Enqueue CTRL request for transmission by the TX thread. The
 | |
| 	 * USB bus will first be resumed if needed.
 | |
| 	 */
 | |
| 	usbos_info->ctl_state = USBOS_REQUEST_STATE_SCHEDULED;
 | |
| 	wake_up_interruptible(&usbos_info->usbos_tx_queue_head);
 | |
| #else
 | |
| 	status = USB_SUBMIT_URB(usbos_info->ctl_urb);
 | |
| 	if (status < 0) {
 | |
| 		DBUSERR(("%s: usb_submit_urb failed %d\n", __FUNCTION__, status));
 | |
| 		up(&usbos_info->ctl_lock);
 | |
| 
 | |
| 		USB_AUTOPM_PUT_INTERFACE_ASYNC(g_probe_info.intf);
 | |
| 
 | |
| 		return DBUS_ERR_TXCTLFAIL;
 | |
| 	}
 | |
| #endif /* USBOS_TX_THREAD */
 | |
| 
 | |
| 	return DBUS_OK;
 | |
| } /* dbus_usbos_intf_send_ctl */
 | |
| 
 | |
| /** This function does not seem to be called by anyone, including dbus_usb.c */
 | |
| static int
 | |
| dbus_usbos_intf_recv_ctl(void *bus, uint8 *buf, int len)
 | |
| {
 | |
| 	usbos_info_t *usbos_info = (usbos_info_t *) bus;
 | |
| 	int status;
 | |
| 	uint16 size;
 | |
| 
 | |
| 	if ((usbos_info == NULL) || (buf == NULL) || (len == 0))
 | |
| 		return DBUS_ERR;
 | |
| 
 | |
| 	if (usbos_info->ctl_urb == NULL)
 | |
| 		return DBUS_ERR;
 | |
| 
 | |
| 	/* Block until a pending CTRL transfer has completed */
 | |
| 	if (down_interruptible(&usbos_info->ctl_lock) != 0) {
 | |
| 		return DBUS_ERR_TXCTLFAIL;
 | |
| 	}
 | |
| 
 | |
| 	/* Disable USB autosuspend until this request completes, request USB resume if needed. */
 | |
| 	USB_AUTOPM_GET_INTERFACE_ASYNC(g_probe_info.intf);
 | |
| 
 | |
| 	size = len;
 | |
| 	usbos_info->ctl_read.wLength = cpu_to_le16p(&size);
 | |
| 	usbos_info->ctl_urb->transfer_buffer_length = size;
 | |
| 
 | |
| 	if (usbos_info->rxctl_deferrespok) {
 | |
| 		/* BMAC model */
 | |
| 		usbos_info->ctl_read.bRequestType = USB_DIR_IN | USB_TYPE_VENDOR |
 | |
| 			USB_RECIP_INTERFACE;
 | |
| 		usbos_info->ctl_read.bRequest = DL_DEFER_RESP_OK;
 | |
| 	} else {
 | |
| 		/* full dongle model */
 | |
| 		usbos_info->ctl_read.bRequestType = USB_DIR_IN | USB_TYPE_CLASS |
 | |
| 			USB_RECIP_INTERFACE;
 | |
| 		usbos_info->ctl_read.bRequest = 1;
 | |
| 	}
 | |
| 
 | |
| 	usb_fill_control_urb(usbos_info->ctl_urb,
 | |
| 		usbos_info->usb,
 | |
| 		usb_rcvctrlpipe(usbos_info->usb, 0),
 | |
| 		(unsigned char *) &usbos_info->ctl_read,
 | |
| 		buf, size, (usb_complete_t)dbus_usbos_ctlread_complete, usbos_info);
 | |
| 
 | |
| 	status = USB_SUBMIT_URB(usbos_info->ctl_urb);
 | |
| 	if (status < 0) {
 | |
| 		DBUSERR(("%s: usb_submit_urb failed %d\n", __FUNCTION__, status));
 | |
| 		up(&usbos_info->ctl_lock);
 | |
| 
 | |
| 		USB_AUTOPM_PUT_INTERFACE_ASYNC(g_probe_info.intf);
 | |
| 
 | |
| 		return DBUS_ERR_RXCTLFAIL;
 | |
| 	}
 | |
| 
 | |
| 	return DBUS_OK;
 | |
| }
 | |
| 
 | |
| static int
 | |
| dbus_usbos_intf_get_attrib(void *bus, dbus_attrib_t *attrib)
 | |
| {
 | |
| 	usbos_info_t *usbos_info = (usbos_info_t *) bus;
 | |
| 
 | |
| 	if ((usbos_info == NULL) || (attrib == NULL))
 | |
| 		return DBUS_ERR;
 | |
| 
 | |
| 	attrib->bustype = DBUS_USB;
 | |
| 	attrib->vid = g_probe_info.vid;
 | |
| 	attrib->pid = g_probe_info.pid;
 | |
| 	attrib->devid = 0x4322;
 | |
| 
 | |
| 	attrib->nchan = 1;
 | |
| 
 | |
| 	/* MaxPacketSize for USB hi-speed bulk out is 512 bytes
 | |
| 	 * and 64-bytes for full-speed.
 | |
| 	 * When sending pkt > MaxPacketSize, Host SW breaks it
 | |
| 	 * up into multiple packets.
 | |
| 	 */
 | |
| 	attrib->mtu = usbos_info->maxps;
 | |
| 
 | |
| 	return DBUS_OK;
 | |
| }
 | |
| 
 | |
| /** Called by higher layer (dbus_usb.c) when it wants to 'up' the USB interface to the dongle */
 | |
| static int
 | |
| dbus_usbos_intf_up(void *bus)
 | |
| {
 | |
| 	usbos_info_t *usbos_info = (usbos_info_t *) bus;
 | |
| 	uint16 ifnum;
 | |
| #ifdef BCMUSBDEV_COMPOSITE
 | |
| 	int wlan_if = 0;
 | |
| #endif
 | |
| 	if (usbos_info == NULL)
 | |
| 		return DBUS_ERR;
 | |
| 
 | |
| 	if (usbos_info->usb == NULL)
 | |
| 		return DBUS_ERR;
 | |
| 
 | |
| #if defined(INTR_EP_ENABLE)
 | |
| 	/* full dongle use intr EP, bmac doesn't use it */
 | |
| 	if (usbos_info->intr_urb) {
 | |
| 		int ret;
 | |
| 
 | |
| 		usb_fill_int_urb(usbos_info->intr_urb, usbos_info->usb,
 | |
| 			usbos_info->intr_pipe, &usbos_info->intr,
 | |
| 			usbos_info->intr_size, (usb_complete_t)dbus_usbos_intr_complete,
 | |
| 			usbos_info, usbos_info->interval);
 | |
| 
 | |
| 		if ((ret = USB_SUBMIT_URB(usbos_info->intr_urb))) {
 | |
| 			DBUSERR(("%s USB_SUBMIT_URB failed with status %d\n", __FUNCTION__, ret));
 | |
| 			return DBUS_ERR;
 | |
| 		}
 | |
| 	}
 | |
| #endif	
 | |
| 
 | |
| 	if (usbos_info->ctl_urb) {
 | |
| 		usbos_info->ctl_in_pipe = usb_rcvctrlpipe(usbos_info->usb, 0);
 | |
| 		usbos_info->ctl_out_pipe = usb_sndctrlpipe(usbos_info->usb, 0);
 | |
| 
 | |
| #ifdef BCMUSBDEV_COMPOSITE
 | |
| 		wlan_if = dbus_usbos_intf_wlan(usbos_info->usb);
 | |
| 		ifnum = cpu_to_le16(IFDESC(usbos_info->usb, wlan_if).bInterfaceNumber);
 | |
| #else
 | |
| 		ifnum = cpu_to_le16(IFDESC(usbos_info->usb, CONTROL_IF).bInterfaceNumber);
 | |
| #endif /* BCMUSBDEV_COMPOSITE */
 | |
| 		/* CTL Write */
 | |
| 		usbos_info->ctl_write.bRequestType =
 | |
| 			USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE;
 | |
| 		usbos_info->ctl_write.bRequest = 0;
 | |
| 		usbos_info->ctl_write.wValue = cpu_to_le16(0);
 | |
| 		usbos_info->ctl_write.wIndex = cpu_to_le16p(&ifnum);
 | |
| 
 | |
| 		/* CTL Read */
 | |
| 		usbos_info->ctl_read.bRequestType =
 | |
| 			USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE;
 | |
| 		usbos_info->ctl_read.bRequest = 1;
 | |
| 		usbos_info->ctl_read.wValue = cpu_to_le16(0);
 | |
| 		usbos_info->ctl_read.wIndex = cpu_to_le16p(&ifnum);
 | |
| 	}
 | |
| 
 | |
| 	/* Success, indicate usbos_info is fully up */
 | |
| 	dbus_usbos_state_change(usbos_info, DBUS_STATE_UP);
 | |
| 
 | |
| 	return DBUS_OK;
 | |
| } /* dbus_usbos_intf_up */
 | |
| 
 | |
| static int
 | |
| dbus_usbos_intf_down(void *bus)
 | |
| {
 | |
| 	usbos_info_t *usbos_info = (usbos_info_t *) bus;
 | |
| 
 | |
| 	if (usbos_info == NULL)
 | |
| 		return DBUS_ERR;
 | |
| 
 | |
| 	dbusos_stop(usbos_info);
 | |
| 	return DBUS_OK;
 | |
| }
 | |
| 
 | |
| static int
 | |
| dbus_usbos_intf_stop(void *bus)
 | |
| {
 | |
| 	usbos_info_t *usbos_info = (usbos_info_t *) bus;
 | |
| 
 | |
| 	if (usbos_info == NULL)
 | |
| 		return DBUS_ERR;
 | |
| 
 | |
| 	dbusos_stop(usbos_info);
 | |
| 	return DBUS_OK;
 | |
| }
 | |
| 
 | |
| 
 | |
| /** Called by higher layer (dbus_usb.c) */
 | |
| static int
 | |
| dbus_usbos_intf_set_config(void *bus, dbus_config_t *config)
 | |
| {
 | |
| 	int err = DBUS_ERR;
 | |
| 	usbos_info_t* usbos_info = bus;
 | |
| 
 | |
| 	if (config->config_id == DBUS_CONFIG_ID_RXCTL_DEFERRES) {
 | |
| 		usbos_info->rxctl_deferrespok = config->rxctl_deferrespok;
 | |
| 		err = DBUS_OK;
 | |
| 	} else if (config->config_id == DBUS_CONFIG_ID_AGGR_LIMIT) {
 | |
| 		/* DBUS_CONFIG_ID_AGGR_LIMIT shouldn't be called after probe stage */
 | |
| 		ASSERT(disc_arg == NULL);
 | |
| 		ASSERT(config->aggr_param.maxrxsf > 0);
 | |
| 		ASSERT(config->aggr_param.maxrxsize > 0);
 | |
| 		if (config->aggr_param.maxrxsize > usbos_info->rxbuf_len) {
 | |
| 			int state = usbos_info->pub->busstate;
 | |
| 			dbus_usbos_unlink(&usbos_info->req_rxpostedq, &usbos_info->rxposted_lock);
 | |
| 			while (atomic_read(&usbos_info->rxposted)) {
 | |
| 				DBUSTRACE(("%s rxposted is %d, delay 1 ms\n", __FUNCTION__,
 | |
| 					atomic_read(&usbos_info->rxposted)));
 | |
| 				dbus_usbos_wait(usbos_info, 1);
 | |
| 			}
 | |
| 			usbos_info->rxbuf_len = config->aggr_param.maxrxsize;
 | |
| 			dbus_usbos_state_change(usbos_info, state);
 | |
| 		}
 | |
| 		err = DBUS_OK;
 | |
| 	}
 | |
| 
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| 
 | |
| /** Called by dbus_usb.c when it wants to download firmware into the dongle */
 | |
| bool
 | |
| dbus_usbos_dl_cmd(usbos_info_t *usbinfo, uint8 cmd, void *buffer, int buflen)
 | |
| {
 | |
| 	int transferred;
 | |
| 	int index = 0;
 | |
| 	char *tmpbuf;
 | |
| 
 | |
| 	if ((usbinfo == NULL) || (buffer == NULL) || (buflen == 0))
 | |
| 		return FALSE;
 | |
| 
 | |
| 	tmpbuf = (char *) MALLOC(usbinfo->pub->osh, buflen);
 | |
| 	if (!tmpbuf) {
 | |
| 		DBUSERR(("%s: Unable to allocate memory \n", __FUNCTION__));
 | |
| 		return FALSE;
 | |
| 	}
 | |
| 
 | |
| #ifdef BCM_REQUEST_FW
 | |
| 	if (cmd == DL_GO) {
 | |
| 		index = 1;
 | |
| 	}
 | |
| #endif
 | |
| 
 | |
| 	/* Disable USB autosuspend until this request completes, request USB resume if needed. */
 | |
| 	USB_AUTOPM_GET_INTERFACE(g_probe_info.intf);
 | |
| 
 | |
| 	transferred = USB_CONTROL_MSG(usbinfo->usb, usb_rcvctrlpipe(usbinfo->usb, 0),
 | |
| 		cmd, (USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_INTERFACE),
 | |
| 		0, index,
 | |
| 		(void*) tmpbuf, buflen, USB_CTRL_EP_TIMEOUT);
 | |
| 	if (transferred == buflen) {
 | |
| 		memcpy(buffer, tmpbuf, buflen);
 | |
| 	} else {
 | |
| 		DBUSERR(("%s: usb_control_msg failed %d\n", __FUNCTION__, transferred));
 | |
| 	}
 | |
| 
 | |
| 	USB_AUTOPM_PUT_INTERFACE(g_probe_info.intf);
 | |
| 
 | |
| 	MFREE(usbinfo->pub->osh, tmpbuf, buflen);
 | |
| 	return (transferred == buflen);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Called by dbus_usb.c when it wants to download a buffer into the dongle (e.g. as part of the
 | |
|  * download process, when writing nvram variables).
 | |
|  */
 | |
| int
 | |
| dbus_write_membytes(usbos_info_t* usbinfo, bool set, uint32 address, uint8 *data, uint size)
 | |
| {
 | |
| 	hwacc_t hwacc;
 | |
| 	int write_bytes = 4;
 | |
| 	int status;
 | |
| 	int retval = 0;
 | |
| 
 | |
| 	DBUSTRACE(("Enter:%s\n", __FUNCTION__));
 | |
| 
 | |
| 	/* Read is not supported */
 | |
| 	if (set == 0) {
 | |
| 		DBUSERR(("Currently read is not supported!!\n"));
 | |
| 		return -1;
 | |
| 	}
 | |
| 
 | |
| 	USB_AUTOPM_GET_INTERFACE(g_probe_info.intf);
 | |
| 
 | |
| 	hwacc.cmd = DL_CMD_WRHW;
 | |
| 	hwacc.addr = address;
 | |
| 
 | |
| 	DBUSTRACE(("Address:%x size:%d", hwacc.addr, size));
 | |
| 	do {
 | |
| 		if (size >= 4) {
 | |
| 			write_bytes = 4;
 | |
| 		} else if (size >= 2) {
 | |
| 			write_bytes = 2;
 | |
| 		} else {
 | |
| 			write_bytes = 1;
 | |
| 		}
 | |
| 
 | |
| 		hwacc.len = write_bytes;
 | |
| 
 | |
| 		while (size >= write_bytes) {
 | |
| 			hwacc.data = *((unsigned int*)data);
 | |
| 
 | |
| 			status = USB_CONTROL_MSG(usbinfo->usb, usb_sndctrlpipe(usbinfo->usb, 0),
 | |
| 				DL_WRHW, (USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_INTERFACE),
 | |
| 				1, 0, (char *)&hwacc, sizeof(hwacc_t), USB_CTRL_EP_TIMEOUT);
 | |
| 
 | |
| 			if (status < 0) {
 | |
| 				retval = -1;
 | |
| 				DBUSERR((" Ctrl write hwacc failed w/status %d @ address:%x \n",
 | |
| 					status, hwacc.addr));
 | |
| 				goto err;
 | |
| 			}
 | |
| 
 | |
| 			hwacc.addr += write_bytes;
 | |
| 			data += write_bytes;
 | |
| 			size -= write_bytes;
 | |
| 		}
 | |
| 	} while (size > 0);
 | |
| 
 | |
| err:
 | |
| 	USB_AUTOPM_PUT_INTERFACE(g_probe_info.intf);
 | |
| 
 | |
| 	return retval;
 | |
| }
 | |
| 
 | |
| int
 | |
| dbus_usbos_readreg(void *bus, uint32 regaddr, int datalen, uint32 *value)
 | |
| {
 | |
| 	usbos_info_t *usbinfo = (usbos_info_t *) bus;
 | |
| 	int ret = DBUS_OK;
 | |
| 	int transferred;
 | |
| 	uint32 cmd;
 | |
| 	hwacc_t	hwacc;
 | |
| 
 | |
| 	if (usbinfo == NULL)
 | |
| 		return DBUS_ERR;
 | |
| 
 | |
| 	if (datalen == 1)
 | |
| 		cmd = DL_RDHW8;
 | |
| 	else if (datalen == 2)
 | |
| 		cmd = DL_RDHW16;
 | |
| 	else
 | |
| 		cmd = DL_RDHW32;
 | |
| 
 | |
| 	USB_AUTOPM_GET_INTERFACE(g_probe_info.intf);
 | |
| 
 | |
| 	transferred = USB_CONTROL_MSG(usbinfo->usb, usb_rcvctrlpipe(usbinfo->usb, 0),
 | |
| 		cmd, (USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_INTERFACE),
 | |
| 		(uint16)(regaddr), (uint16)(regaddr >> 16),
 | |
| 		(void *) &hwacc, sizeof(hwacc_t), USB_CTRL_EP_TIMEOUT);
 | |
| 
 | |
| 	if (transferred >= sizeof(hwacc_t)) {
 | |
| 		*value = hwacc.data;
 | |
| 	} else {
 | |
| 		DBUSERR(("%s: usb_control_msg failed %d\n", __FUNCTION__, transferred));
 | |
| 		ret = DBUS_ERR;
 | |
| 	}
 | |
| 
 | |
| 	USB_AUTOPM_PUT_INTERFACE(g_probe_info.intf);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| int
 | |
| dbus_usbos_writereg(void *bus, uint32 regaddr, int datalen, uint32 data)
 | |
| {
 | |
| 	usbos_info_t *usbinfo = (usbos_info_t *) bus;
 | |
| 	int ret = DBUS_OK;
 | |
| 	int transferred;
 | |
| 	uint32 cmd = DL_WRHW;
 | |
| 	hwacc_t	hwacc;
 | |
| 
 | |
| 	if (usbinfo == NULL)
 | |
| 		return DBUS_ERR;
 | |
| 
 | |
| 	USB_AUTOPM_GET_INTERFACE(g_probe_info.intf);
 | |
| 
 | |
| 	hwacc.cmd = DL_WRHW;
 | |
| 	hwacc.addr = regaddr;
 | |
| 	hwacc.data = data;
 | |
| 	hwacc.len = datalen;
 | |
| 
 | |
| 	transferred = USB_CONTROL_MSG(usbinfo->usb, usb_sndctrlpipe(usbinfo->usb, 0),
 | |
| 		cmd, (USB_DIR_OUT| USB_TYPE_VENDOR | USB_RECIP_INTERFACE),
 | |
| 		1, 0,
 | |
| 		(void *) &hwacc, sizeof(hwacc_t), USB_CTRL_EP_TIMEOUT);
 | |
| 
 | |
| 	if (transferred != sizeof(hwacc_t)) {
 | |
| 		DBUSERR(("%s: usb_control_msg failed %d\n", __FUNCTION__, transferred));
 | |
| 		ret = DBUS_ERR;
 | |
| 	}
 | |
| 
 | |
| 	USB_AUTOPM_PUT_INTERFACE(g_probe_info.intf);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| int
 | |
| dbus_usbos_wait(usbos_info_t *usbinfo, uint16 ms)
 | |
| {
 | |
| #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0))
 | |
| 	if (in_interrupt())
 | |
| 		mdelay(ms);
 | |
| 	else
 | |
| 		msleep_interruptible(ms);
 | |
| #else
 | |
| 	wait_ms(ms);
 | |
| #endif
 | |
| 	return DBUS_OK;
 | |
| }
 | |
| 
 | |
| /** Called by dbus_usb.c as part of the firmware download process */
 | |
| bool
 | |
| dbus_usbos_dl_send_bulk(usbos_info_t *usbinfo, void *buffer, int len)
 | |
| {
 | |
| 	bool ret = TRUE;
 | |
| 	int status;
 | |
| 	int transferred = 0;
 | |
| 
 | |
| 	if (usbinfo == NULL)
 | |
| 		return DBUS_ERR;
 | |
| 
 | |
| 	USB_AUTOPM_GET_INTERFACE(g_probe_info.intf);
 | |
| 
 | |
| 	status = USB_BULK_MSG(usbinfo->usb, usbinfo->tx_pipe,
 | |
| 		buffer, len,
 | |
| 		&transferred, USB_BULK_EP_TIMEOUT);
 | |
| 
 | |
| 	if (status < 0) {
 | |
| 		DBUSERR(("%s: usb_bulk_msg failed %d\n", __FUNCTION__, status));
 | |
| 		ret = FALSE;
 | |
| 	}
 | |
| 
 | |
| 	USB_AUTOPM_PUT_INTERFACE(g_probe_info.intf);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static bool
 | |
| dbus_usbos_intf_recv_needed(void *bus)
 | |
| {
 | |
| 	return FALSE;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Higher layer (dbus_usb.c) wants to execute a function on the condition that the rx spin lock has
 | |
|  * been acquired.
 | |
|  */
 | |
| static void*
 | |
| dbus_usbos_intf_exec_rxlock(void *bus, exec_cb_t cb, struct exec_parms *args)
 | |
| {
 | |
| 	usbos_info_t *usbos_info = (usbos_info_t *) bus;
 | |
| 	void *ret;
 | |
| 	unsigned long flags;
 | |
| 
 | |
| 	if (usbos_info == NULL)
 | |
| 		return NULL;
 | |
| 
 | |
| 	spin_lock_irqsave(&usbos_info->rxlock, flags);
 | |
| 	ret = cb(args);
 | |
| 	spin_unlock_irqrestore(&usbos_info->rxlock, flags);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static void*
 | |
| dbus_usbos_intf_exec_txlock(void *bus, exec_cb_t cb, struct exec_parms *args)
 | |
| {
 | |
| 	usbos_info_t *usbos_info = (usbos_info_t *) bus;
 | |
| 	void *ret;
 | |
| 	unsigned long flags;
 | |
| 
 | |
| 	if (usbos_info == NULL)
 | |
| 		return NULL;
 | |
| 
 | |
| 	spin_lock_irqsave(&usbos_info->txlock, flags);
 | |
| 	ret = cb(args);
 | |
| 	spin_unlock_irqrestore(&usbos_info->txlock, flags);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * if an error condition was detected in this module, the higher DBUS layer (dbus_usb.c) has to
 | |
|  * be notified.
 | |
|  */
 | |
| int
 | |
| dbus_usbos_errhandler(void *bus, int err)
 | |
| {
 | |
| 	usbos_info_t *usbos_info = (usbos_info_t *) bus;
 | |
| 
 | |
| 	if (usbos_info == NULL)
 | |
| 		return DBUS_ERR;
 | |
| 
 | |
| 	if (usbos_info->cbarg && usbos_info->cbs) {
 | |
| 		if (usbos_info->cbs->errhandler)
 | |
| 			usbos_info->cbs->errhandler(usbos_info->cbarg, err);
 | |
| 	}
 | |
| 
 | |
| 	return DBUS_OK;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * if a change in bus state was detected in this module, the higher DBUS layer (dbus_usb.c) has to
 | |
|  * be notified.
 | |
|  */
 | |
| int
 | |
| dbus_usbos_state_change(void *bus, int state)
 | |
| {
 | |
| 	usbos_info_t *usbos_info = (usbos_info_t *) bus;
 | |
| 
 | |
| 	if (usbos_info == NULL)
 | |
| 		return DBUS_ERR;
 | |
| 
 | |
| 	if (usbos_info->cbarg && usbos_info->cbs) {
 | |
| 		if (usbos_info->cbs->state_change)
 | |
| 			usbos_info->cbs->state_change(usbos_info->cbarg, state);
 | |
| 	}
 | |
| 
 | |
| 	usbos_info->pub->busstate = state;
 | |
| 	return DBUS_OK;
 | |
| }
 | |
| 
 | |
| int
 | |
| dbus_bus_osl_register(int vid, int pid, probe_cb_t prcb,
 | |
| 	disconnect_cb_t discb, void *prarg, dbus_intf_t **intf, void *param1, void *param2)
 | |
| {
 | |
| 	bzero(&g_probe_info, sizeof(probe_info_t));
 | |
| 
 | |
| 	probe_cb = prcb;
 | |
| 	disconnect_cb = discb;
 | |
| 	probe_arg = prarg;
 | |
| 
 | |
| 	devid_table[0].idVendor = vid;
 | |
| 	devid_table[0].idProduct = pid;
 | |
| 
 | |
| 	*intf = &dbus_usbos_intf;
 | |
| 
 | |
| 	USB_REGISTER();
 | |
| 
 | |
| 	return DBUS_ERR_NODEVICE;
 | |
| }
 | |
| 
 | |
| int
 | |
| dbus_bus_osl_deregister()
 | |
| {
 | |
| 	g_probe_info.dereged = TRUE;
 | |
| 
 | |
| 	DHD_MUTEX_LOCK();
 | |
| 	if (disconnect_cb && disc_arg && (g_probe_info.disc_cb_done == FALSE)) {
 | |
| 		disconnect_cb(disc_arg);
 | |
| 		disc_arg = NULL;
 | |
| 	}
 | |
| 	DHD_MUTEX_UNLOCK();
 | |
| 
 | |
| 	USB_DEREGISTER();
 | |
| 
 | |
| 	return DBUS_OK;
 | |
| }
 | |
| 
 | |
| void *
 | |
| dbus_usbos_intf_attach(dbus_pub_t *pub, void *cbarg, dbus_intf_callbacks_t *cbs)
 | |
| {
 | |
| 	usbos_info_t *usbos_info;
 | |
| 
 | |
| 	if (g_probe_info.dldone == FALSE) {
 | |
| 		DBUSERR(("%s: err device not downloaded!\n", __FUNCTION__));
 | |
| 		return NULL;
 | |
| 	}
 | |
| 
 | |
| 	/* Sanity check for BUS_INFO() */
 | |
| 	ASSERT(OFFSETOF(usbos_info_t, pub) == 0);
 | |
| 
 | |
| 	usbos_info = MALLOC(pub->osh, sizeof(usbos_info_t));
 | |
| 	if (usbos_info == NULL)
 | |
| 		return NULL;
 | |
| 
 | |
| 	bzero(usbos_info, sizeof(usbos_info_t));
 | |
| 
 | |
| 	usbos_info->pub = pub;
 | |
| 	usbos_info->cbarg = cbarg;
 | |
| 	usbos_info->cbs = cbs;
 | |
| 
 | |
| 	/* Needed for disconnect() */
 | |
| 	g_probe_info.usbos_info = usbos_info;
 | |
| 
 | |
| 	/* Update USB Info */
 | |
| 	usbos_info->usb = g_probe_info.usb;
 | |
| 	usbos_info->rx_pipe = g_probe_info.rx_pipe;
 | |
| 	usbos_info->rx_pipe2 = g_probe_info.rx_pipe2;
 | |
| 	usbos_info->tx_pipe = g_probe_info.tx_pipe;
 | |
| 	usbos_info->intr_pipe = g_probe_info.intr_pipe;
 | |
| 	usbos_info->intr_size = g_probe_info.intr_size;
 | |
| 	usbos_info->interval = g_probe_info.interval;
 | |
| 	usbos_info->pub->device_speed = g_probe_info.device_speed;
 | |
| 	if (usbos_info->rx_pipe2) {
 | |
| 		usbos_info->pub->attrib.has_2nd_bulk_in_ep = 1;
 | |
| 	} else {
 | |
| 		usbos_info->pub->attrib.has_2nd_bulk_in_ep = 0;
 | |
| 	}
 | |
| 
 | |
| 	if (usbos_info->tx_pipe)
 | |
| 		usbos_info->maxps = usb_maxpacket(usbos_info->usb,
 | |
| 			usbos_info->tx_pipe, usb_pipeout(usbos_info->tx_pipe));
 | |
| 
 | |
| 	INIT_LIST_HEAD(&usbos_info->req_rxfreeq);
 | |
| 	INIT_LIST_HEAD(&usbos_info->req_txfreeq);
 | |
| 	INIT_LIST_HEAD(&usbos_info->req_rxpostedq);
 | |
| 	INIT_LIST_HEAD(&usbos_info->req_txpostedq);
 | |
| 	spin_lock_init(&usbos_info->rxfree_lock);
 | |
| 	spin_lock_init(&usbos_info->txfree_lock);
 | |
| 	spin_lock_init(&usbos_info->rxposted_lock);
 | |
| 	spin_lock_init(&usbos_info->txposted_lock);
 | |
| 	spin_lock_init(&usbos_info->rxlock);
 | |
| 	spin_lock_init(&usbos_info->txlock);
 | |
| 
 | |
| 	atomic_set(&usbos_info->rxposted, 0);
 | |
| 	atomic_set(&usbos_info->txposted, 0);
 | |
| 
 | |
| 
 | |
| #ifdef USB_DISABLE_INT_EP
 | |
| 	usbos_info->intr_urb = NULL;
 | |
| #else
 | |
| 	if (!(usbos_info->intr_urb = USB_ALLOC_URB())) {
 | |
| 		DBUSERR(("%s: usb_alloc_urb (tx) failed\n", __FUNCTION__));
 | |
| 		goto fail;
 | |
| 	}
 | |
| #endif
 | |
| 
 | |
| 	if (!(usbos_info->ctl_urb = USB_ALLOC_URB())) {
 | |
| 		DBUSERR(("%s: usb_alloc_urb (tx) failed\n", __FUNCTION__));
 | |
| 		goto fail;
 | |
| 	}
 | |
| 
 | |
| 	init_waitqueue_head(&usbos_info->wait);
 | |
| 
 | |
| 	if (!(usbos_info->blk_urb = USB_ALLOC_URB())) {	/* for embedded image downloading */
 | |
| 		DBUSERR(("%s: usb_alloc_urb (tx) failed\n", __FUNCTION__));
 | |
| 		goto fail;
 | |
| 	}
 | |
| 
 | |
| 	usbos_info->rxbuf_len = (uint)usbos_info->pub->rxsize;
 | |
| 
 | |
| 
 | |
| 
 | |
| 	atomic_set(&usbos_info->txallocated, 0);
 | |
| 	if (DBUS_OK != dbus_usbos_urbreqs_alloc(usbos_info,
 | |
| 		usbos_info->pub->ntxq, FALSE)) {
 | |
| 		goto fail;
 | |
| 	}
 | |
| 
 | |
| 	atomic_set(&usbos_info->rxallocated, 0);
 | |
| 	if (DBUS_OK != dbus_usbos_urbreqs_alloc(usbos_info,
 | |
| 		MIN(DBUS_USB_RXQUEUE_BATCH_ADD, usbos_info->pub->nrxq),
 | |
| 		TRUE)) {
 | |
| 		goto fail;
 | |
| 	}
 | |
| 
 | |
| 	sema_init(&usbos_info->ctl_lock, 1);
 | |
| 
 | |
| #ifdef USBOS_THREAD
 | |
| 	if (dbus_usbos_thread_init(usbos_info) == NULL)
 | |
| 		goto fail;
 | |
| #endif /* USBOS_THREAD */
 | |
| 
 | |
| #ifdef USBOS_TX_THREAD
 | |
| 	if (dbus_usbos_tx_thread_init(usbos_info) == NULL)
 | |
| 		goto fail;
 | |
| #endif /* USBOS_TX_THREAD */
 | |
| 
 | |
| 	pub->dev_info = g_probe_info.usb;
 | |
| 
 | |
| 
 | |
| 	return (void *) usbos_info;
 | |
| fail:
 | |
| 	if (usbos_info->intr_urb) {
 | |
| 		USB_FREE_URB(usbos_info->intr_urb);
 | |
| 		usbos_info->intr_urb = NULL;
 | |
| 	}
 | |
| 
 | |
| 	if (usbos_info->ctl_urb) {
 | |
| 		USB_FREE_URB(usbos_info->ctl_urb);
 | |
| 		usbos_info->ctl_urb = NULL;
 | |
| 	}
 | |
| 
 | |
| #if defined(BCM_REQUEST_FW)
 | |
| 	if (usbos_info->blk_urb) {
 | |
| 		USB_FREE_URB(usbos_info->blk_urb);
 | |
| 		usbos_info->blk_urb = NULL;
 | |
| 	}
 | |
| #endif
 | |
| 
 | |
| 	dbus_usbos_urbreqs_free(usbos_info, TRUE);
 | |
| 	atomic_set(&usbos_info->rxallocated, 0);
 | |
| 	dbus_usbos_urbreqs_free(usbos_info, FALSE);
 | |
| 	atomic_set(&usbos_info->txallocated, 0);
 | |
| 
 | |
| 	g_probe_info.usbos_info = NULL;
 | |
| 
 | |
| 	MFREE(pub->osh, usbos_info, sizeof(usbos_info_t));
 | |
| 	return NULL;
 | |
| } /* dbus_usbos_intf_attach */
 | |
| 
 | |
| void
 | |
| dbus_usbos_intf_detach(dbus_pub_t *pub, void *info)
 | |
| {
 | |
| 	usbos_info_t *usbos_info = (usbos_info_t *) info;
 | |
| 	osl_t *osh = pub->osh;
 | |
| 
 | |
| 	if (usbos_info == NULL) {
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| #ifdef USBOS_TX_THREAD
 | |
| 	dbus_usbos_tx_thread_deinit(usbos_info);
 | |
| #endif /* USBOS_TX_THREAD */
 | |
| 
 | |
| 	/* Must unlink all URBs prior to driver unload;
 | |
| 	 * otherwise an URB callback can occur after driver
 | |
| 	 * has been de-allocated and rmmod'd
 | |
| 	 */
 | |
| 	dbusos_stop(usbos_info);
 | |
| 
 | |
| 	if (usbos_info->intr_urb) {
 | |
| 		USB_FREE_URB(usbos_info->intr_urb);
 | |
| 		usbos_info->intr_urb = NULL;
 | |
| 	}
 | |
| 
 | |
| 	if (usbos_info->ctl_urb) {
 | |
| 		USB_FREE_URB(usbos_info->ctl_urb);
 | |
| 		usbos_info->ctl_urb = NULL;
 | |
| 	}
 | |
| 
 | |
| 	if (usbos_info->blk_urb) {
 | |
| 		USB_FREE_URB(usbos_info->blk_urb);
 | |
| 		usbos_info->blk_urb = NULL;
 | |
| 	}
 | |
| 
 | |
| 	dbus_usbos_urbreqs_free(usbos_info, TRUE);
 | |
| 	atomic_set(&usbos_info->rxallocated, 0);
 | |
| 	dbus_usbos_urbreqs_free(usbos_info, FALSE);
 | |
| 	atomic_set(&usbos_info->txallocated, 0);
 | |
| 
 | |
| #ifdef USBOS_THREAD
 | |
| 	dbus_usbos_thread_deinit(usbos_info);
 | |
| #endif /* USBOS_THREAD */
 | |
| 
 | |
| 	g_probe_info.usbos_info = NULL;
 | |
| 	MFREE(osh, usbos_info, sizeof(usbos_info_t));
 | |
| } /* dbus_usbos_intf_detach */
 | |
| 
 | |
| 
 | |
| #ifdef USBOS_TX_THREAD
 | |
| 
 | |
| void*
 | |
| dbus_usbos_tx_thread_init(usbos_info_t *usbos_info)
 | |
| {
 | |
| 	spin_lock_init(&usbos_info->usbos_tx_list_lock);
 | |
| 	INIT_LIST_HEAD(&usbos_info->usbos_tx_list);
 | |
| 	init_waitqueue_head(&usbos_info->usbos_tx_queue_head);
 | |
| 
 | |
| 	usbos_info->usbos_tx_kt = kthread_create(dbus_usbos_tx_thread_func,
 | |
| 		usbos_info, "usb-tx-thread");
 | |
| 
 | |
| 	if (IS_ERR(usbos_info->usbos_tx_kt)) {
 | |
| 		DBUSERR(("Thread Creation failed\n"));
 | |
| 		return (NULL);
 | |
| 	}
 | |
| 
 | |
| 	usbos_info->ctl_state = USBOS_REQUEST_STATE_UNSCHEDULED;
 | |
| 	wake_up_process(usbos_info->usbos_tx_kt);
 | |
| 
 | |
| 	return (usbos_info->usbos_tx_kt);
 | |
| }
 | |
| 
 | |
| void
 | |
| dbus_usbos_tx_thread_deinit(usbos_info_t *usbos_info)
 | |
| {
 | |
| 	urb_req_t *req;
 | |
| 
 | |
| 	if (usbos_info->usbos_tx_kt) {
 | |
| 		wake_up_interruptible(&usbos_info->usbos_tx_queue_head);
 | |
| 		kthread_stop(usbos_info->usbos_tx_kt);
 | |
| 	}
 | |
| 
 | |
| 	/* Move pending requests to free queue so they can be freed */
 | |
| 	while ((req = dbus_usbos_qdeq(
 | |
| 		&usbos_info->usbos_tx_list, &usbos_info->usbos_tx_list_lock)) != NULL) {
 | |
| 		dbus_usbos_qenq(&usbos_info->req_txfreeq, req, &usbos_info->txfree_lock);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Allow USB in-band resume to block by submitting CTRL and DATA URBs on a separate thread.
 | |
|  */
 | |
| int
 | |
| dbus_usbos_tx_thread_func(void *data)
 | |
| {
 | |
| 	usbos_info_t  *usbos_info = (usbos_info_t *)data;
 | |
| 	urb_req_t     *req;
 | |
| 	dbus_irb_tx_t *txirb;
 | |
| 	int           ret;
 | |
| 	unsigned long flags;
 | |
| 
 | |
| #ifdef WL_THREADNICE
 | |
| 	set_user_nice(current, WL_THREADNICE);
 | |
| #endif
 | |
| 
 | |
| 	while (1) {
 | |
| 		/* Wait until there are URBs to submit */
 | |
| 		wait_event_interruptible_timeout(
 | |
| 			usbos_info->usbos_tx_queue_head,
 | |
| 			!list_empty(&usbos_info->usbos_tx_list) ||
 | |
| 			usbos_info->ctl_state == USBOS_REQUEST_STATE_SCHEDULED,
 | |
| 			100);
 | |
| 
 | |
| 		if (kthread_should_stop())
 | |
| 			break;
 | |
| 
 | |
| 		/* Submit CTRL URB if needed */
 | |
| 		if (usbos_info->ctl_state == USBOS_REQUEST_STATE_SCHEDULED) {
 | |
| 
 | |
| 			/* Disable USB autosuspend until this request completes. If the
 | |
| 			 * interface was suspended, this call blocks until it has been resumed.
 | |
| 			 */
 | |
| 			USB_AUTOPM_GET_INTERFACE(g_probe_info.intf);
 | |
| 
 | |
| 			usbos_info->ctl_state = USBOS_REQUEST_STATE_SUBMITTED;
 | |
| 
 | |
| 			ret = USB_SUBMIT_URB(usbos_info->ctl_urb);
 | |
| 			if (ret != 0) {
 | |
| 				DBUSERR(("%s CTRL USB_SUBMIT_URB failed, status %d\n",
 | |
| 					__FUNCTION__, ret));
 | |
| 
 | |
| 				usbos_info->ctl_state = USBOS_REQUEST_STATE_UNSCHEDULED;
 | |
| 				up(&usbos_info->ctl_lock);
 | |
| 
 | |
| 				USB_AUTOPM_PUT_INTERFACE_ASYNC(g_probe_info.intf);
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		/* Submit all available TX URBs */
 | |
| 		while ((req = dbus_usbos_qdeq(&usbos_info->usbos_tx_list,
 | |
| 			&usbos_info->usbos_tx_list_lock)) != NULL) {
 | |
| 
 | |
| 			/* Disable USB autosuspend until this request completes. If the
 | |
| 			 * interface was suspended, this call blocks until it has been resumed.
 | |
| 			 */
 | |
| 			USB_AUTOPM_GET_INTERFACE(g_probe_info.intf);
 | |
| 
 | |
| 			spin_lock_irqsave(&usbos_info->txlock, flags);
 | |
| 
 | |
| 			ret = USB_SUBMIT_URB(req->urb);
 | |
| 			if (ret == 0) {
 | |
| 				/* URB submitted successfully */
 | |
| 				dbus_usbos_qenq(&usbos_info->req_txpostedq, req,
 | |
| 					&usbos_info->txposted_lock);
 | |
| 				atomic_inc(&usbos_info->txposted);
 | |
| 			} else {
 | |
| 				/* Submitting the URB failed. */
 | |
| 				DBUSERR(("%s TX USB_SUBMIT_URB failed, status %d\n",
 | |
| 					__FUNCTION__, ret));
 | |
| 
 | |
| 				USB_AUTOPM_PUT_INTERFACE_ASYNC(g_probe_info.intf);
 | |
| 			}
 | |
| 
 | |
| 			spin_unlock_irqrestore(&usbos_info->txlock, flags);
 | |
| 
 | |
| 			if (ret != 0) {
 | |
| 				/* Cleanup and notify higher layers */
 | |
| 				dbus_usbos_qenq(&usbos_info->req_txfreeq, req,
 | |
| 					&usbos_info->txfree_lock);
 | |
| 
 | |
| 				txirb = req->arg;
 | |
| 				if (txirb->send_buf) {
 | |
| 					MFREE(usbos_info->pub->osh, txirb->send_buf, req->buf_len);
 | |
| 					txirb->send_buf = NULL;
 | |
| 					req->buf_len = 0;
 | |
| 				}
 | |
| 
 | |
| 				if (likely (usbos_info->cbarg && usbos_info->cbs)) {
 | |
| 					if (likely (usbos_info->cbs->send_irb_complete != NULL))
 | |
| 						usbos_info->cbs->send_irb_complete(
 | |
| 							usbos_info->cbarg, txirb, DBUS_ERR_TXDROP);
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| } /* dbus_usbos_tx_thread_func */
 | |
| 
 | |
| #endif /* USBOS_TX_THREAD */
 | |
| 
 | |
| #ifdef USBOS_THREAD
 | |
| 
 | |
| /**
 | |
|  * Increase system performance by creating a USB thread that runs parallel to other system
 | |
|  * activity.
 | |
|  */
 | |
| static void*
 | |
| dbus_usbos_thread_init(usbos_info_t *usbos_info)
 | |
| {
 | |
| 	usbos_list_entry_t  *entry;
 | |
| 	unsigned long       flags, ii;
 | |
| 
 | |
| 	spin_lock_init(&usbos_info->usbos_list_lock);
 | |
| 	spin_lock_init(&usbos_info->ctrl_lock);
 | |
| 	INIT_LIST_HEAD(&usbos_info->usbos_list);
 | |
| 	INIT_LIST_HEAD(&usbos_info->usbos_free_list);
 | |
| 	init_waitqueue_head(&usbos_info->usbos_queue_head);
 | |
| 	atomic_set(&usbos_info->usbos_list_cnt, 0);
 | |
| 
 | |
| 
 | |
| 	for (ii = 0; ii < (usbos_info->pub->nrxq + usbos_info->pub->ntxq); ii++) {
 | |
| 		entry = MALLOC(usbos_info->pub->osh, sizeof(usbos_list_entry_t));
 | |
| 		if (entry) {
 | |
| 			spin_lock_irqsave(&usbos_info->usbos_list_lock, flags);
 | |
| 			list_add_tail((struct list_head*) entry, &usbos_info->usbos_free_list);
 | |
| 			spin_unlock_irqrestore(&usbos_info->usbos_list_lock, flags);
 | |
| 		} else {
 | |
| 			DBUSERR(("Failed to create list\n"));
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	usbos_info->usbos_kt = kthread_create(dbus_usbos_thread_func,
 | |
| 		usbos_info, "usb-thread");
 | |
| 
 | |
| 	if (IS_ERR(usbos_info->usbos_kt)) {
 | |
| 		DBUSERR(("Thread Creation failed\n"));
 | |
| 		return (NULL);
 | |
| 	}
 | |
| 
 | |
| 	wake_up_process(usbos_info->usbos_kt);
 | |
| 
 | |
| 	return (usbos_info->usbos_kt);
 | |
| }
 | |
| 
 | |
| static void
 | |
| dbus_usbos_thread_deinit(usbos_info_t *usbos_info)
 | |
| {
 | |
| 	struct list_head    *cur, *next;
 | |
| 	usbos_list_entry_t  *entry;
 | |
| 	unsigned long       flags;
 | |
| 
 | |
| 	if (usbos_info->usbos_kt) {
 | |
| 		wake_up_interruptible(&usbos_info->usbos_queue_head);
 | |
| 		kthread_stop(usbos_info->usbos_kt);
 | |
| 	}
 | |
| #if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
 | |
| #pragma GCC diagnostic push
 | |
| #pragma GCC diagnostic ignored "-Wcast-qual"
 | |
| #endif
 | |
| 	list_for_each_safe(cur, next, &usbos_info->usbos_list)
 | |
| 	{
 | |
| 		entry = list_entry(cur, struct usbos_list_entry, list);
 | |
| 		/* detach this entry from the list and then free the entry */
 | |
| 		spin_lock_irqsave(&usbos_info->usbos_list_lock, flags);
 | |
| 		list_del(cur);
 | |
| 		MFREE(usbos_info->pub->osh, entry, sizeof(usbos_list_entry_t));
 | |
| 		spin_unlock_irqrestore(&usbos_info->usbos_list_lock, flags);
 | |
| 	}
 | |
| 
 | |
| 	list_for_each_safe(cur, next, &usbos_info->usbos_free_list)
 | |
| 	{
 | |
| 		entry = list_entry(cur, struct usbos_list_entry, list);
 | |
| 		/* detach this entry from the list and then free the entry */
 | |
| 		spin_lock_irqsave(&usbos_info->usbos_list_lock, flags);
 | |
| 		list_del(cur);
 | |
| 		MFREE(usbos_info->pub->osh, entry, sizeof(usbos_list_entry_t));
 | |
| 		spin_unlock_irqrestore(&usbos_info->usbos_list_lock, flags);
 | |
| 	}
 | |
| #if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
 | |
| #pragma GCC diagnostic pop
 | |
| #endif
 | |
| }
 | |
| 
 | |
| /** Process completed URBs in a worker thread */
 | |
| static int
 | |
| dbus_usbos_thread_func(void *data)
 | |
| {
 | |
| 	usbos_info_t        *usbos_info = (usbos_info_t *)data;
 | |
| 	usbos_list_entry_t  *entry;
 | |
| 	struct list_head    *cur, *next;
 | |
| 	unsigned long       flags;
 | |
| 
 | |
| #ifdef WL_THREADNICE
 | |
| 	set_user_nice(current, WL_THREADNICE);
 | |
| #endif
 | |
| 
 | |
| 	while (1) {
 | |
| 		/* If the list is empty, then go to sleep */
 | |
| 		wait_event_interruptible_timeout
 | |
| 		(usbos_info->usbos_queue_head,
 | |
| 			atomic_read(&usbos_info->usbos_list_cnt) > 0,
 | |
| 			100);
 | |
| 
 | |
| 		if (kthread_should_stop())
 | |
| 			break;
 | |
| 
 | |
| 		spin_lock_irqsave(&usbos_info->usbos_list_lock, flags);
 | |
| 
 | |
| 		/* For each entry on the list, process it.  Remove the entry from
 | |
| 		* the list when done.
 | |
| 		*/
 | |
| #if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
 | |
| #pragma GCC diagnostic push
 | |
| #pragma GCC diagnostic ignored "-Wcast-qual"
 | |
| #endif
 | |
| 		list_for_each_safe(cur, next, &usbos_info->usbos_list)
 | |
| 		{
 | |
| 			urb_req_t           *req;
 | |
| 			int                 len;
 | |
| 			int                 stat;
 | |
| 			usbos_info_t        *usbos_info_local;
 | |
| 
 | |
| 			entry = list_entry(cur, struct usbos_list_entry, list);
 | |
| 			if (entry == NULL)
 | |
| 				break;
 | |
| 
 | |
| 			req = entry->urb_context;
 | |
| 			len = entry->urb_length;
 | |
| 			stat = entry->urb_status;
 | |
| 			usbos_info_local = req->usbinfo;
 | |
| 
 | |
| 			/* detach this entry from the list and attach it to the free list */
 | |
| 			list_del_init(cur);
 | |
| 			spin_unlock_irqrestore(&usbos_info_local->usbos_list_lock, flags);
 | |
| 
 | |
| 			dbus_usbos_recv_complete_handle(req, len, stat);
 | |
| 
 | |
| 			spin_lock_irqsave(&usbos_info_local->usbos_list_lock, flags);
 | |
| 
 | |
| 			list_add_tail(cur, &usbos_info_local->usbos_free_list);
 | |
| 
 | |
| 			atomic_dec(&usbos_info_local->usbos_list_cnt);
 | |
| 		}
 | |
| 
 | |
| 		spin_unlock_irqrestore(&usbos_info->usbos_list_lock, flags);
 | |
| 
 | |
| 	}
 | |
| #if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
 | |
| #pragma GCC diagnostic pop
 | |
| #endif
 | |
| 
 | |
| 	return 0;
 | |
| } /* dbus_usbos_thread_func */
 | |
| 
 | |
| /** Called on Linux calling URB callback, see dbus_usbos_recv_complete() */
 | |
| static void
 | |
| dbus_usbos_dispatch_schedule(CALLBACK_ARGS)
 | |
| {
 | |
| 	urb_req_t           *req = urb->context;
 | |
| 	usbos_info_t        *usbos_info = req->usbinfo;
 | |
| 	usbos_list_entry_t  *entry;
 | |
| 	unsigned long       flags;
 | |
| 	struct list_head    *cur;
 | |
| 
 | |
| 	spin_lock_irqsave(&usbos_info->usbos_list_lock, flags);
 | |
| 
 | |
| 	cur   = usbos_info->usbos_free_list.next;
 | |
| #if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
 | |
| #pragma GCC diagnostic push
 | |
| #pragma GCC diagnostic ignored "-Wcast-qual"
 | |
| #endif
 | |
| 	entry = list_entry(cur, struct usbos_list_entry, list);
 | |
| #if defined(STRICT_GCC_WARNINGS) && defined(__GNUC__)
 | |
| #pragma GCC diagnostic pop
 | |
| #endif
 | |
| 
 | |
| 	/* detach this entry from the free list and prepare it insert it to use list */
 | |
| 	list_del_init(cur);
 | |
| 
 | |
| 	if (entry) {
 | |
| 		entry->urb_context = urb->context;
 | |
| 		entry->urb_length  = urb->actual_length;
 | |
| 		entry->urb_status  = urb->status;
 | |
| 
 | |
| 		atomic_inc(&usbos_info->usbos_list_cnt);
 | |
| 		list_add_tail(cur, &usbos_info->usbos_list);
 | |
| 	} else {
 | |
| 		DBUSERR(("!!!!!!OUT OF MEMORY!!!!!!!\n"));
 | |
| 	}
 | |
| 
 | |
| 	spin_unlock_irqrestore(&usbos_info->usbos_list_lock, flags);
 | |
| 
 | |
| 	/* thread */
 | |
| 	wake_up_interruptible(&usbos_info->usbos_queue_head);
 | |
| } /* dbus_usbos_dispatch_schedule */
 | |
| 
 | |
| #endif /* USBOS_THREAD */
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| #ifdef BCM_REQUEST_FW
 | |
| 
 | |
| struct request_fw_context {
 | |
| 	const struct firmware *firmware;
 | |
| 	struct semaphore lock;
 | |
| };
 | |
| 
 | |
| /*
 | |
|  * Callback for dbus_request_firmware().
 | |
|  */
 | |
| static void
 | |
| dbus_request_firmware_done(const struct firmware *firmware, void *ctx)
 | |
| {
 | |
| 	struct request_fw_context *context = (struct request_fw_context*)ctx;
 | |
| 
 | |
| 	/* Store the received firmware handle in the context and wake requester */
 | |
| 	context->firmware = firmware;
 | |
| 	up(&context->lock);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Send a firmware request and wait for completion.
 | |
|  *
 | |
|  * The use of the asynchronous version of request_firmware() is needed to avoid
 | |
|  * kernel oopses when we just come out of system hibernate.
 | |
|  */
 | |
| static int
 | |
| dbus_request_firmware(const char *name, const struct firmware **firmware)
 | |
| {
 | |
| 	struct request_fw_context *context;
 | |
| 	int ret;
 | |
| 
 | |
| 	context = kzalloc(sizeof(*context), GFP_KERNEL);
 | |
| 	if (!context)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	sema_init(&context->lock, 0);
 | |
| 
 | |
| 	ret = request_firmware_nowait(THIS_MODULE, true, name, &g_probe_info.usb->dev,
 | |
| 	                              GFP_KERNEL, context, dbus_request_firmware_done);
 | |
| 	if (ret) {
 | |
| 		kfree(context);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	/* Wait for completion */
 | |
| 	if (down_interruptible(&context->lock) != 0) {
 | |
| 		kfree(context);
 | |
| 		return -ERESTARTSYS;
 | |
| 	}
 | |
| 
 | |
| 	*firmware = context->firmware;
 | |
| 	kfree(context);
 | |
| 
 | |
| 	return *firmware != NULL ? 0 : -ENOENT;
 | |
| }
 | |
| 
 | |
| static void *
 | |
| dbus_get_fwfile(int devid, int chiprev, uint8 **fw, int *fwlen, uint16 boardtype, uint16 boardrev)
 | |
| {
 | |
| 	const struct firmware *firmware = NULL;
 | |
| #ifndef OEM_ANDROID
 | |
| 	s8 *device_id = NULL;
 | |
| 	s8 *chip_rev = "";
 | |
| #endif /* OEM_ANDROID */
 | |
| 	s8 file_name[64];
 | |
| 	int ret;
 | |
| 
 | |
| #ifndef OEM_ANDROID
 | |
| 	switch (devid) {
 | |
| 		case BCM4350_CHIP_ID:
 | |
| 		case BCM4354_CHIP_ID:
 | |
| 		case BCM43556_CHIP_ID:
 | |
| 		case BCM43558_CHIP_ID:
 | |
| 		case BCM43566_CHIP_ID:
 | |
| 		case BCM43568_CHIP_ID:
 | |
| 		case BCM43570_CHIP_ID:
 | |
| 		case BCM4358_CHIP_ID:
 | |
| 			device_id = "4350";
 | |
| 			break;
 | |
| 		case BCM43143_CHIP_ID:
 | |
| 			device_id = "43143";
 | |
| 			break;
 | |
| 		case BCM43234_CHIP_ID:
 | |
| 		case BCM43235_CHIP_ID:
 | |
| 		case BCM43236_CHIP_ID:
 | |
| 			device_id = "43236";
 | |
| 			break;
 | |
| 		case BCM43242_CHIP_ID:
 | |
| 			device_id = "43242";
 | |
| 			break;
 | |
| 		case BCM43238_CHIP_ID:
 | |
| 			device_id = "43238";
 | |
| 			break;
 | |
| 		case BCM43526_CHIP_ID:
 | |
| 			device_id = "43526";
 | |
| 			break;
 | |
| 		case BCM43569_CHIP_ID:
 | |
| 			device_id = "43569";
 | |
| 			switch (chiprev) {
 | |
| 				case 0:
 | |
| 					chip_rev = "a0";
 | |
| 					break;
 | |
| 				case 2:
 | |
| 					chip_rev = "a2";
 | |
| 					break;
 | |
| 				default:
 | |
| 					break;
 | |
| 			}
 | |
| 			break;
 | |
| 		default:
 | |
| 			DBUSERR(("unsupported device %x\n", devid));
 | |
| 			return NULL;
 | |
| 	}
 | |
| 
 | |
| 	/* Load firmware */
 | |
| 	snprintf(file_name, sizeof(file_name), "brcm/bcm%s%s-firmware.bin", device_id, chip_rev);
 | |
| #else
 | |
| 	snprintf(file_name, sizeof(file_name), "%s", CONFIG_ANDROID_BCMDHD_FW_PATH);
 | |
| #endif /* OEM_ANDROID */
 | |
| 
 | |
| 	ret = dbus_request_firmware(file_name, &firmware);
 | |
| 	if (ret) {
 | |
| 		DBUSERR(("fail to request firmware %s\n", file_name));
 | |
| 		return NULL;
 | |
| 	}
 | |
| 
 | |
| 	*fwlen = firmware->size;
 | |
| 	*fw = (uint8 *)firmware->data;
 | |
| 	return (void *)firmware;
 | |
| 
 | |
| }
 | |
| 
 | |
| static void *
 | |
| dbus_get_nvfile(int devid, int chiprev, uint8 **fw, int *fwlen, uint16 boardtype, uint16 boardrev)
 | |
| {
 | |
| 	const struct firmware *firmware = NULL;
 | |
| #ifndef OEM_ANDROID
 | |
| 	s8 *device_id = NULL;
 | |
| 	s8 *chip_rev = "";
 | |
| #endif /* OEM_ANDROID */
 | |
| 	s8 file_name[64];
 | |
| 	int ret;
 | |
| 
 | |
| #ifndef OEM_ANDROID
 | |
| 	switch (devid) {
 | |
| 		case BCM4350_CHIP_ID:
 | |
| 		case BCM4354_CHIP_ID:
 | |
| 		case BCM43556_CHIP_ID:
 | |
| 		case BCM43558_CHIP_ID:
 | |
| 		case BCM43566_CHIP_ID:
 | |
| 		case BCM43568_CHIP_ID:
 | |
| 		case BCM43570_CHIP_ID:
 | |
| 		case BCM4358_CHIP_ID:
 | |
| 			device_id = "4350";
 | |
| 			break;
 | |
| 		case BCM43143_CHIP_ID:
 | |
| 			device_id = "43143";
 | |
| 			break;
 | |
| 		case BCM43234_CHIP_ID:
 | |
| 			device_id = "43234";
 | |
| 			break;
 | |
| 		case BCM43235_CHIP_ID:
 | |
| 			device_id = "43235";
 | |
| 			break;
 | |
| 		case BCM43236_CHIP_ID:
 | |
| 			device_id = "43236";
 | |
| 			break;
 | |
| 		case BCM43238_CHIP_ID:
 | |
| 			device_id = "43238";
 | |
| 			break;
 | |
| 		case BCM43242_CHIP_ID:
 | |
| 			device_id = "43242";
 | |
| 			break;
 | |
| 		case BCM43526_CHIP_ID:
 | |
| 			device_id = "43526";
 | |
| 			break;
 | |
| 		case BCM43569_CHIP_ID:
 | |
| 			device_id = "43569";
 | |
| 			switch (chiprev) {
 | |
| 				case 0:
 | |
| 					chip_rev = "a0";
 | |
| 					break;
 | |
| 				case 2:
 | |
| 					chip_rev = "a2";
 | |
| 					break;
 | |
| 				default:
 | |
| 					break;
 | |
| 			}
 | |
| 			break;
 | |
| 		default:
 | |
| 			DBUSERR(("unsupported device %x\n", devid));
 | |
| 			return NULL;
 | |
| 	}
 | |
| 
 | |
| 	/* Load board specific nvram file */
 | |
| 	snprintf(file_name, sizeof(file_name), "brcm/bcm%s%s-%2x-%2x.nvm",
 | |
| 	         device_id, chip_rev, boardtype, boardrev);
 | |
| #else
 | |
| 	snprintf(file_name, sizeof(file_name), "%s", CONFIG_ANDROID_BCMDHD_NVRAM_PATH);
 | |
| #endif /* OEM_ANDROID */
 | |
| 
 | |
| 	ret = dbus_request_firmware(file_name, &firmware);
 | |
| 	if (ret) {
 | |
| 		DBUSERR(("fail to request nvram %s\n", file_name));
 | |
| 
 | |
| #ifndef OEM_ANDROID
 | |
| 		/* Load generic nvram file */
 | |
| 		snprintf(file_name, sizeof(file_name), "brcm/bcm%s%s.nvm",
 | |
| 		         device_id, chip_rev);
 | |
| 
 | |
| 		ret = dbus_request_firmware(file_name, &firmware);
 | |
| #endif /* OEM_ANDROID */
 | |
| 
 | |
| 		if (ret) {
 | |
| 			DBUSERR(("fail to request nvram %s\n", file_name));
 | |
| 			return NULL;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	*fwlen = firmware->size;
 | |
| 	*fw = (uint8 *)firmware->data;
 | |
| 	return (void *)firmware;
 | |
| }
 | |
| 
 | |
| void *
 | |
| dbus_get_fw_nvfile(int devid, int chiprev, uint8 **fw, int *fwlen, int type, uint16 boardtype,
 | |
| 	uint16 boardrev)
 | |
| {
 | |
| 	switch (type) {
 | |
| 		case DBUS_FIRMWARE:
 | |
| 			return dbus_get_fwfile(devid, chiprev, fw, fwlen, boardtype, boardrev);
 | |
| 		case DBUS_NVFILE:
 | |
| 			return dbus_get_nvfile(devid, chiprev, fw, fwlen, boardtype, boardrev);
 | |
| 		default:
 | |
| 			return NULL;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void
 | |
| dbus_release_fw_nvfile(void *firmware)
 | |
| {
 | |
| 	release_firmware((struct firmware *)firmware);
 | |
| }
 | |
| #endif /* BCM_REQUEST_FW */
 | |
| 
 | |
| #ifdef BCMUSBDEV_COMPOSITE
 | |
| /**
 | |
|  * For a composite device the interface order is not guaranteed, scan the device struct for the WLAN
 | |
|  * interface.
 | |
|  */
 | |
| static int
 | |
| dbus_usbos_intf_wlan(struct usb_device *usb)
 | |
| {
 | |
| 	int i, num_of_eps, ep, intf_wlan = -1;
 | |
| 	int num_intf = CONFIGDESC(usb)->bNumInterfaces;
 | |
| 	struct usb_endpoint_descriptor *endpoint;
 | |
| 
 | |
| 	for (i = 0; i < num_intf; i++) {
 | |
| 		if (IFDESC(usb, i).bInterfaceClass != USB_CLASS_VENDOR_SPEC)
 | |
| 			continue;
 | |
| 		num_of_eps = IFDESC(usb, i).bNumEndpoints;
 | |
| 
 | |
| 		for (ep = 0; ep < num_of_eps; ep++) {
 | |
| 			endpoint = &IFEPDESC(usb, i, ep);
 | |
| 			if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) ==
 | |
| 				USB_ENDPOINT_XFER_BULK) {
 | |
| 				intf_wlan = i;
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 		if (ep < num_of_eps)
 | |
| 			break;
 | |
| 	}
 | |
| 
 | |
| 	return intf_wlan;
 | |
| }
 | |
| #endif /* BCMUSBDEV_COMPOSITE */
 | 
