/* * 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. * * * <> * * $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 #include /** * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 #include #include #include #include #include #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 */ DHD_MUTEX_LOCK(); #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, 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(); 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 */ #ifdef BCM_REQUEST_FW #ifdef KERNEL26 static int dummy_usbos_probe(struct usb_interface *intf, const struct usb_device_id *id); static void dummy_usbos_disconnect(struct usb_interface *intf); #else /* KERNEL26 */ static void *dummy_usbos_probe(struct usb_device *usb, unsigned int ifnum, const struct usb_device_id *id); static void dummy_usbos_disconnect(struct usb_device *usb, void *ptr); #endif /* KERNEL26 */ #ifdef KERNEL26 #define DUMMY_DBUS_USBOS_PROBE() static int dummy_usbos_probe(struct usb_interface *intf, const struct usb_device_id *id) #define DUMMY_DBUS_USBOS_DISCONNECT() static void dummy_usbos_disconnect(struct usb_interface *intf) #else #define DUMMY_DBUS_USBOS_PROBE() static void * dummy_usbos_probe(struct usb_device *usb, unsigned int ifnum, const struct usb_device_id *id) #define DUMMY_DBUS_USBOS_DISCONNECT() static void dummy_usbos_disconnect(struct usb_device *usb, void *ptr) #endif /* KERNEL26 */ static struct semaphore *notify_semaphore = NULL; DUMMY_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 */ DHD_MUTEX_LOCK(); #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 && notify_semaphore) { up(notify_semaphore); } 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 return 0; } DUMMY_DBUS_USBOS_DISCONNECT() { #ifdef KERNEL26 struct usb_device *usb = interface_to_usbdev(intf); #endif DHD_MUTEX_LOCK(); disc_arg = NULL; 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(); } /** functions called by the Linux kernel USB subsystem */ static struct usb_driver dummy_dbus_usbdev = { name: "dummy_dbus_usbdev", probe: dummy_usbos_probe, disconnect: dummy_usbos_disconnect, id_table: devid_table, }; /* Register a dummy USB client driver in order to be notified of new USB device */ int dbus_usbos_reg_notify(void* semaphore) { notify_semaphore = semaphore; usb_register(&dummy_dbus_usbdev); return DBUS_OK; } void dbus_usbos_unreg_notify(void) { usb_deregister(&dummy_dbus_usbdev); } #endif