/* * Dongle BUS interface * USB Linux Implementation * * Copyright (C) 2022, Broadcom. * * 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. * * * <> */ /** * @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 #ifdef DBUS_LINUX_RXDPC #include #endif #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. */ #include #include #include #include #include #include #endif /* USBOS_THREAD || USBOS_TX_THREAD */ #ifdef DBUS_LINUX_RXDPC #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 25)) #define RESCHED() _cond_resched() #elif (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 0)) #define RESCHED() cond_resched() #else #define RESCHED() __cond_resched() #endif /* LINUX_VERSION_CODE */ #endif /* DBUS_LINUX_RXDPC */ #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)) ||\ (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 19, 0)) /* For USB power management support, see Linux kernel: Documentation/usb/power-management.txt */ #define USB_SUSPEND_AVAILABLE #endif 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, CALL_SITE)); 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 #ifdef DBUS_LINUX_RXDPC #define DAEMONIZE(a) daemonize(a); allow_signal(SIGKILL); allow_signal(SIGTERM); #define SET_NICE(n) set_user_nice(current, n) #endif #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]) #ifdef DBUS_LINUX_RXDPC #define DAEMONIZE(a) daemonize(); #define SET_NICE(n) do {current->nice = (n);} while (0) #endif /* DBUS_LINUX_RXDPC */ #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 /* temp defines, should come from the standard usb.h file */ #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; #if defined(BCMDBG) || defined(DBUS_LINUX_HIST) int dpc_cnt, dpc_pktcnt, dpc_maxpktcnt; #endif 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 */ #if defined(BCMDBG) || defined(DBUS_LINUX_HIST) int *txposted_hist; int *rxposted_hist; #endif #ifdef USBOS_THREAD 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 */ #if defined(EHCI_FASTPATH_TX) || defined(EHCI_FASTPATH_RX) /** Linux USB AP related */ spinlock_t fastpath_lock; #endif } 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); #ifdef DBUS_LINUX_RXDPC static void dbus_usbos_recv_dpc(usbos_info_t *usbos_info); static int dbus_usbos_dpc_thread(void *data); #endif /* DBUS_LINUX_RXDPC */ 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 */ #ifdef USB_TRIGGER_DEBUG static bool dbus_usbos_ctl_send_debugtrig(usbos_info_t *usbinfo); #endif /* USB_TRIGGER_DEBUG */ #ifdef KEEPIF_ON_DEVICE_RESET enum usb_dev_status { USB_DEVICE_INIT = 0, USB_DEVICE_RESETTED = 1, USB_DEVICE_DISCONNECTED = 2, USB_DEVICE_PROBED }; static int dbus_usbos_usbreset_func(void *data); #endif /* KEEPIF_ON_DEVICE_RESET */ /** * 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_DNGL_EMBEDIMAGE) || defined(BCM_REQUEST_FW) { USB_DEVICE(BCM_DNGL_VID, BCM_DNGL_BL_PID_4328) }, { 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_43569) }, { USB_DEVICE(BCM_DNGL_VID, BCM_DNGL_BL_PID_4381) }, #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"BUS_TYPE, 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; #ifdef KEEPIF_ON_DEVICE_RESET bool keepif_on_devreset; /* if need keep network interface on dev reset */ bool dev_resetted; /* if dev reset event occured */ enum dbus_state busstate_bf_devreset; /* busstate before devreset */ atomic_t usbdev_stat; /* usb device status: resetted? disconnected? re-probed? */ wait_queue_head_t usbreset_queue_head; /* kernel thread waiting queue */ struct task_struct *usbreset_kt; /* kernel thread handle dev reset/probe event */ #endif /* KEEPIF_ON_DEVICE_RESET */ } probe_info_t; /* driver info, initialized when bcmsdh_register is called */ static dbus_driver_t drvinfo = {NULL, NULL, NULL, NULL}; /* * USB Linux dbus_intf_t */ static void dbus_usbos_init_info(void); 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); #if defined(BCMDBG) || defined(DBUS_LINUX_HIST) static void dbus_usbos_intf_dump(void *bus, struct bcmstrbuf *b); #endif /* BCMDBG || DBUS_LINUX_HIST */ 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, #if defined(BCMDBG) || defined(DBUS_LINUX_HIST) .dump = dbus_usbos_intf_dump, #else .dump = NULL, #endif /* BCMDBG || DBUS_LINUX_HIST */ .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 void *disc_arg = NULL; #if defined(EHCI_FASTPATH_TX) || defined(EHCI_FASTPATH_RX) #define EHCI_PAGE_SIZE 4096 /* Copies of structures located elsewhere. */ /* ME: Unify (move to include file) */ typedef struct { dbus_pub_t *pub; void *cbarg; dbus_intf_callbacks_t *cbs; dbus_intf_t *drvintf; void *usbosl_info; } usb_info_t; /** General info for all BUS */ typedef struct dbus_irbq { dbus_irb_t *head; dbus_irb_t *tail; int cnt; } dbus_irbq_t; /** * This private structure dbus_info_t is also declared in dbus.c. * All the fields must be consistent in both declarations. */ typedef struct dbus_info { dbus_pub_t pub; /* MUST BE FIRST */ void *cbarg; dbus_callbacks_t *cbs; void *bus_info; dbus_intf_t *drvintf; uint8 *fw; int fwlen; uint32 errmask; int rx_low_watermark; int tx_low_watermark; bool txoff; bool txoverride; bool rxoff; bool tx_timer_ticking; dbus_irbq_t *rx_q; dbus_irbq_t *tx_q; #ifdef BCMDBG int *txpend_q_hist; int *rxpend_q_hist; #endif /* BCMDBG */ #ifdef EHCI_FASTPATH_RX atomic_t rx_outstanding; #endif uint8 *nvram; int nvram_len; uint8 *image; /**< buffer for combine fw and nvram */ int image_len; uint8 *orig_fw; int origfw_len; int decomp_memsize; dbus_extdl_t extdl; int nvram_nontxt; } dbus_info_t; /* ME: Move to device extension */ static atomic_t s_tx_pending; static int optimize_init(usbos_info_t *usbos_info, struct usb_device *usb, int out, int in, int in2); static int optimize_deinit(usbos_info_t *usbos_info, struct usb_device *usb); #endif /* #if defined(EHCI_FASTPATH_TX) || defined(EHCI_FASTPATH_RX) */ 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 * 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); GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); req = list_entry(urbreq_q->next, urb_req_t, urb_list); GCC_DIAGNOSTIC_POP(); list_del_init(&req->urb_list); } spin_unlock_irqrestore(lock, flags); return req; } static void 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->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 defined(BCMDBG) || defined(DBUS_LINUX_HIST) if (usbos_info->txposted_hist) { usbos_info->txposted_hist[txposted]++; } #endif /* BCMDBG || DBUS_LINUX_HIST */ 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); 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 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, #if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0)) (void *)dbus_usbos_recv_complete, req); #else (usb_complete_t)dbus_usbos_recv_complete, req); #endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0) */ 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); #if defined(BCMDBG) || defined(DBUS_LINUX_HIST) if (usbos_info->rxposted_hist) { usbos_info->rxposted_hist[rxposted]++; } #endif /* BCMDBG || DBUS_LINUX_HIST */ 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 */ #ifdef DBUS_LINUX_RXDPC static void dbus_usbos_recv_dpc(usbos_info_t *usbos_info) { urb_req_t *req = NULL; dbus_irb_rx_t *rxirb = NULL; int dbus_status = DBUS_OK; bool killed = (g_probe_info.suspend_state == USBOS_SUSPEND_STATE_SUSPEND_PENDING) ? 1 : 0; #if defined(BCMDBG) || defined(DBUS_LINUX_HIST) int cnt = 0; usbos_info->dpc_cnt++; #endif /* BCMDBG || DBUS_LINUX_HIST */ /* make it bounded if we think it can take too long */ while ((req = dbus_usbos_qdeq(&usbos_info->req_rxpendingq, &usbos_info->rxpending_lock)) != NULL) { struct urb *urb = req->urb; rxirb = req->arg; /* Handle errors */ if (urb->status) { /* * Linux 2.4 disconnect: -ENOENT or -EILSEQ for CRC error; rmmod: -ENOENT * Linux 2.6 disconnect: -EPROTO, rmmod: -ESHUTDOWN */ if ((urb->status == -ENOENT && (!killed)) || urb->status == -ESHUTDOWN) { /* NOTE: unlink() can not be called from URB callback(). * Do not call dbusos_stop() here. */ dbus_usbos_state_change(usbos_info, DBUS_STATE_DOWN); } else if (urb->status == -EPROTO) { } else { DBUSERR(("%s rx error %d\n", __FUNCTION__, urb->status)); dbus_usbos_errhandler(usbos_info, DBUS_ERR_RXFAIL); } /* On error, don't submit more URBs yet */ DBUSERR(("%s %d rx error %d\n", __FUNCTION__, __LINE__, urb->status)); rxirb->buf = NULL; rxirb->actual_len = 0; dbus_status = DBUS_ERR_RXFAIL; goto fail; } #if defined(BCM_RPC_NOCOPY) || defined(BCM_RPC_RXNOCOPY) /* detach the packet from the req */ req->pkt = NULL; #endif /* Make the skb represent the received urb */ rxirb->actual_len = urb->actual_length; fail: /* this is just a stats, we don't protect it with locks for now */ usbos_info->rxpending--; #if defined(BCMDBG) || defined(DBUS_LINUX_HIST) cnt++; #endif /* BCMDBG || DBUS_LINUX_HIST */ if (usbos_info->cbarg && usbos_info->cbs && 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); } #if defined(BCMDBG) || defined(DBUS_LINUX_HIST) usbos_info->dpc_pktcnt += cnt; usbos_info->dpc_maxpktcnt = MAX(cnt, usbos_info->dpc_maxpktcnt); #endif /* BCMDBG || DBUS_LINUX_HIST */ #ifdef DBUS_LINUX_HIST { static unsigned long last_dump = 0; /* dump every 20 sec */ if (jiffies > (last_dump + 20*HZ)) { dbus_usbos_intf_dump(usbos_info, NULL); last_dump = jiffies; } } #endif /* DBUS_LINUX_HIST */ } /* dbus_usbos_recv_dpc */ static int dbus_usbos_dpc_thread(void *data) { usbos_info_t *usbos_info = (usbos_info_t*)data; DAEMONIZE("dbus_rx_dpc"); /* High priority for short response time. We will yield by ourselves. */ /* SET_NICE(-10); */ /* Run until signal received */ while (1) { if (down_interruptible(&usbos_info->dpc_sem) == 0) { dbus_usbos_recv_dpc(usbos_info); RESCHED(); } else break; } complete_and_exit(&usbos_info->dpc_exited, 0); return 0; } #endif /* DBUS_LINUX_RXDPC */ /** * Called by worked thread when a 'receive URB' completed or Linux kernel when it returns a URB to * this driver. */ static void dbus_usbos_recv_complete_handle(urb_req_t *req, int len, int status) { #ifdef DBUS_LINUX_RXDPC usbos_info_t *usbos_info = req->usbinfo; unsigned long flags; int rxallocated, rxposted; spin_lock_irqsave(&usbos_info->rxlock, flags); /* detach the packet from the queue */ dbus_usbos_req_del(req, &usbos_info->rxposted_lock); rxposted = atomic_dec_return(&usbos_info->rxposted); rxallocated = atomic_read(&usbos_info->rxallocated); /* Enqueue to rxpending queue */ usbos_info->rxpending++; dbus_usbos_qenq(&usbos_info->req_rxpendingq, req, &usbos_info->rxpending_lock); spin_unlock_irqrestore(&usbos_info->rxlock, flags); #error "RX req/buf appending-mode not verified for DBUS_LINUX_RXDPC because it was disabled" 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); } #error "Please verify above code works if you happened to enable DBUS_LINUX_RXDPC!!" /* Wake up dpc for further processing */ ASSERT(usbos_info->dpc_pid >= 0); up(&usbos_info->dpc_sem); #else 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); #endif /* DBUS_LINUX_RXDPC */ /* 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); #ifdef USB_TRIGGER_DEBUG dbus_usbos_ctl_send_debugtrig(usbos_info); #endif /* USB_TRIGGER_DEBUG */ 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); #ifdef DBUS_LINUX_RXDPC /* Stop the dpc thread */ if (usbos_info->dpc_pid >= 0) { KILL_PROC(usbos_info->dpc_pid, SIGTERM); wait_for_completion(&usbos_info->dpc_exited); } /* Move pending reqs to free queue so they can be freed */ while ((req = dbus_usbos_qdeq(&usbos_info->req_rxpendingq, &usbos_info->rxpending_lock)) != NULL) { dbus_usbos_qenq(&usbos_info->req_rxfreeq, req, &usbos_info->rxfree_lock); } #endif /* DBUS_LINUX_RXDPC */ } /* 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) { usbos_info_t *usbos_info = (usbos_info_t *) g_probe_info.usbos_info; int err = 0; printf("%s Enter\n", __FUNCTION__); /* DHD for full dongle model */ g_probe_info.suspend_state = USBOS_SUSPEND_STATE_SUSPEND_PENDING; if (drvinfo.suspend && disc_arg) err = drvinfo.suspend(disc_arg); if (err) { printf("%s: err=%d\n", __FUNCTION__, err); // g_probe_info.suspend_state = USBOS_SUSPEND_STATE_DEVICE_ACTIVE; // return err; } usbos_info->pub->busstate = 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; printf("%s Exit err=%d\n", __FUNCTION__, err); return err; } /** * 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) { usbos_info_t *usbos_info = (usbos_info_t *) g_probe_info.usbos_info; printf("%s Enter\n", __FUNCTION__); g_probe_info.suspend_state = USBOS_SUSPEND_STATE_DEVICE_ACTIVE; if (drvinfo.resume && disc_arg) drvinfo.resume(disc_arg); usbos_info->pub->busstate = DBUS_STATE_UP; printf("%s Exit\n", __FUNCTION__); 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) { printf("%s Enter\n", __FUNCTION__); #ifdef KEEPIF_ON_DEVICE_RESET if (g_probe_info.keepif_on_devreset) { atomic_set(&g_probe_info.usbdev_stat, USB_DEVICE_RESETTED); wake_up_interruptible(&g_probe_info.usbreset_queue_head); } else #endif /* KEEPIF_ON_DEVICE_RESET */ { g_probe_info.suspend_state = USBOS_SUSPEND_STATE_DEVICE_ACTIVE; if (drvinfo.resume && disc_arg) drvinfo.resume(disc_arg); } printf("%s Exit\n", __FUNCTION__); return 0; } #endif /* USB_SUSPEND_AVAILABLE */ /** * Called by Linux kernel at initialization time, kernel wants to know if our driver will accept the * caller supplied USB interface. Note that USB drivers are bound to interfaces, and not to USB * devices. */ #ifdef KERNEL26 #define DBUS_USBOS_PROBE() static int dbus_usbos_probe(struct usb_interface *intf, const struct usb_device_id *id) #define DBUS_USBOS_DISCONNECT() static void dbus_usbos_disconnect(struct usb_interface *intf) #else #define DBUS_USBOS_PROBE() static void * dbus_usbos_probe(struct usb_device *usb, unsigned int ifnum, const struct usb_device_id *id) #define DBUS_USBOS_DISCONNECT() static void dbus_usbos_disconnect(struct usb_device *usb, void *ptr) #endif /* KERNEL26 */ DBUS_USBOS_PROBE() { int ep; struct usb_endpoint_descriptor *endpoint; int ret = 0; #ifdef KERNEL26 struct usb_device *usb = interface_to_usbdev(intf); #else int claimed = 0; #endif int num_of_eps; #ifdef BCMUSBDEV_COMPOSITE int wlan_if = -1; bool intr_ep = FALSE; #endif /* BCMUSBDEV_COMPOSITE */ wifi_adapter_info_t *adapter; DHD_MUTEX_LOCK(); DBUSERR(("%s: bus num(busnum)=%d, slot num (portnum)=%d\n", __FUNCTION__, usb->bus->busnum, usb->portnum)); adapter = dhd_wifi_platform_attach_adapter(USB_BUS, usb->bus->busnum, usb->portnum, WIFI_STATUS_POWER_ON); if (adapter == NULL) { DBUSERR(("%s: can't find adapter info for this chip\n", __FUNCTION__)); ret = -ENOMEM; goto fail; } #ifdef BCMUSBDEV_COMPOSITE wlan_if = dbus_usbos_intf_wlan(usb); #ifdef KERNEL26 if ((wlan_if >= 0) && (IFPTR(usb, wlan_if) == intf)) #else if (wlan_if == ifnum) #endif /* KERNEL26 */ { #endif /* BCMUSBDEV_COMPOSITE */ g_probe_info.usb = usb; g_probe_info.dldone = TRUE; #ifdef BCMUSBDEV_COMPOSITE } else { DBUSTRACE(("dbus_usbos_probe: skip probe for non WLAN interface\n")); ret = BCME_UNSUPPORTED; goto fail; } #endif /* BCMUSBDEV_COMPOSITE */ #ifdef KERNEL26 g_probe_info.intf = intf; #endif /* KERNEL26 */ #ifdef BCMUSBDEV_COMPOSITE if (IFDESC(usb, wlan_if).bInterfaceNumber > USB_COMPIF_MAX) #else if (IFDESC(usb, CONTROL_IF).bInterfaceNumber) #endif /* BCMUSBDEV_COMPOSITE */ { ret = -1; goto fail; } if (id != NULL) { g_probe_info.vid = id->idVendor; g_probe_info.pid = id->idProduct; } #ifdef KERNEL26 usb_set_intfdata(intf, &g_probe_info); #endif /* Check that the device supports only one configuration */ if (usb->descriptor.bNumConfigurations != 1) { ret = -1; goto fail; } if (usb->descriptor.bDeviceClass != USB_CLASS_VENDOR_SPEC) { #ifdef BCMUSBDEV_COMPOSITE if ((usb->descriptor.bDeviceClass != USB_CLASS_MISC) && (usb->descriptor.bDeviceClass != USB_CLASS_WIRELESS)) { #endif /* BCMUSBDEV_COMPOSITE */ ret = -1; goto fail; #ifdef BCMUSBDEV_COMPOSITE } #endif /* BCMUSBDEV_COMPOSITE */ } /* * Only the BDC interface configuration is supported: * Device class: USB_CLASS_VENDOR_SPEC * if0 class: USB_CLASS_VENDOR_SPEC * if0/ep0: control * if0/ep1: bulk in * if0/ep2: bulk out (ok if swapped with bulk in) */ if (CONFIGDESC(usb)->bNumInterfaces != 1) { #ifdef BCMUSBDEV_COMPOSITE if (CONFIGDESC(usb)->bNumInterfaces > USB_COMPIF_MAX) { #endif /* BCMUSBDEV_COMPOSITE */ ret = -1; goto fail; #ifdef BCMUSBDEV_COMPOSITE } #endif /* BCMUSBDEV_COMPOSITE */ } /* Check interface */ #ifndef KERNEL26 #ifdef BCMUSBDEV_COMPOSITE if (usb_interface_claimed(IFPTR(usb, wlan_if))) #else if (usb_interface_claimed(IFPTR(usb, CONTROL_IF))) #endif /* BCMUSBDEV_COMPOSITE */ { ret = -1; goto fail; } #endif /* !KERNEL26 */ #ifdef BCMUSBDEV_COMPOSITE if ((IFDESC(usb, wlan_if).bInterfaceClass != USB_CLASS_VENDOR_SPEC || IFDESC(usb, wlan_if).bInterfaceSubClass != 2 || IFDESC(usb, wlan_if).bInterfaceProtocol != 0xff) && (IFDESC(usb, wlan_if).bInterfaceClass != USB_CLASS_MISC || IFDESC(usb, wlan_if).bInterfaceSubClass != USB_SUBCLASS_COMMON || IFDESC(usb, wlan_if).bInterfaceProtocol != USB_PROTO_IAD)) #else if (IFDESC(usb, CONTROL_IF).bInterfaceClass != USB_CLASS_VENDOR_SPEC || IFDESC(usb, CONTROL_IF).bInterfaceSubClass != 2 || IFDESC(usb, CONTROL_IF).bInterfaceProtocol != 0xff) #endif /* BCMUSBDEV_COMPOSITE */ { #ifdef BCMUSBDEV_COMPOSITE DBUSERR(("%s: invalid control interface: class %d, subclass %d, proto %d\n", __FUNCTION__, IFDESC(usb, wlan_if).bInterfaceClass, IFDESC(usb, wlan_if).bInterfaceSubClass, IFDESC(usb, wlan_if).bInterfaceProtocol)); #else DBUSERR(("%s: invalid control interface: class %d, subclass %d, proto %d\n", __FUNCTION__, IFDESC(usb, CONTROL_IF).bInterfaceClass, IFDESC(usb, CONTROL_IF).bInterfaceSubClass, IFDESC(usb, CONTROL_IF).bInterfaceProtocol)); #endif /* BCMUSBDEV_COMPOSITE */ ret = -1; goto fail; } /* Check control endpoint */ #ifdef BCMUSBDEV_COMPOSITE endpoint = &IFEPDESC(usb, wlan_if, 0); #else endpoint = &IFEPDESC(usb, CONTROL_IF, 0); #endif /* BCMUSBDEV_COMPOSITE */ if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_INT) { #ifdef BCMUSBDEV_COMPOSITE if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_BULK) { #endif /* BCMUSBDEV_COMPOSITE */ DBUSERR(("%s: invalid control endpoint %d\n", __FUNCTION__, endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)); ret = -1; goto fail; #ifdef BCMUSBDEV_COMPOSITE } #endif /* BCMUSBDEV_COMPOSITE */ } #ifdef BCMUSBDEV_COMPOSITE if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT) { #endif /* BCMUSBDEV_COMPOSITE */ g_probe_info.intr_pipe = usb_rcvintpipe(usb, endpoint->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); #ifdef BCMUSBDEV_COMPOSITE intr_ep = TRUE; } #endif /* BCMUSBDEV_COMPOSITE */ #ifndef KERNEL26 /* Claim interface */ #ifdef BCMUSBDEV_COMPOSITE usb_driver_claim_interface(&dbus_usbdev, IFPTR(usb, wlan_if), &g_probe_info); #else usb_driver_claim_interface(&dbus_usbdev, IFPTR(usb, CONTROL_IF), &g_probe_info); #endif /* BCMUSBDEV_COMPOSITE */ claimed = 1; #endif /* !KERNEL26 */ g_probe_info.rx_pipe = 0; g_probe_info.rx_pipe2 = 0; g_probe_info.tx_pipe = 0; #ifdef BCMUSBDEV_COMPOSITE if (intr_ep) ep = 1; else ep = 0; num_of_eps = IFDESC(usb, wlan_if).bNumEndpoints - 1; #else num_of_eps = IFDESC(usb, BULK_IF).bNumEndpoints - 1; #endif /* BCMUSBDEV_COMPOSITE */ if ((num_of_eps != 2) && (num_of_eps != 3)) { #ifdef BCMUSBDEV_COMPOSITE if (num_of_eps > 7) #endif /* BCMUSBDEV_COMPOSITE */ ASSERT(0); } /* Check data endpoints and get pipes */ #ifdef BCMUSBDEV_COMPOSITE for (; ep <= num_of_eps; ep++) #else for (ep = 1; ep <= num_of_eps; ep++) #endif /* BCMUSBDEV_COMPOSITE */ { #ifdef BCMUSBDEV_COMPOSITE endpoint = &IFEPDESC(usb, wlan_if, ep); #else endpoint = &IFEPDESC(usb, BULK_IF, ep); #endif /* BCMUSBDEV_COMPOSITE */ if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_BULK) { DBUSERR(("%s: invalid data endpoint %d\n", __FUNCTION__, ep)); ret = -1; goto fail; } if ((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN) { /* direction: dongle->host */ if (!g_probe_info.rx_pipe) { g_probe_info.rx_pipe = usb_rcvbulkpipe(usb, (endpoint->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK)); } else { g_probe_info.rx_pipe2 = usb_rcvbulkpipe(usb, (endpoint->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK)); } } else g_probe_info.tx_pipe = usb_sndbulkpipe(usb, (endpoint->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK)); } /* Allocate interrupt URB and data buffer */ /* RNDIS says 8-byte intr, our old drivers used 4-byte */ #ifdef BCMUSBDEV_COMPOSITE g_probe_info.intr_size = (IFEPDESC(usb, wlan_if, 0).wMaxPacketSize == 16) ? 8 : 4; g_probe_info.interval = IFEPDESC(usb, wlan_if, 0).bInterval; #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 21)) usb->quirks |= USB_QUIRK_NO_SET_INTF; #endif #else g_probe_info.intr_size = (IFEPDESC(usb, CONTROL_IF, 0).wMaxPacketSize == 16) ? 8 : 4; g_probe_info.interval = IFEPDESC(usb, CONTROL_IF, 0).bInterval; #endif /* BCMUSBDEV_COMPOSITE */ #ifndef KERNEL26 /* usb_fill_int_urb does the interval decoding in 2.6 */ if (usb->speed == USB_SPEED_HIGH) g_probe_info.interval = 1 << (g_probe_info.interval - 1); #endif if (usb->speed == USB_SPEED_SUPER) { g_probe_info.device_speed = SUPER_SPEED; DBUSERR(("super speed device detected\n")); } else if (usb->speed == USB_SPEED_HIGH) { g_probe_info.device_speed = HIGH_SPEED; DBUSERR(("high speed device detected\n")); } else { g_probe_info.device_speed = FULL_SPEED; DBUSERR(("full speed device detected\n")); } #ifdef KEEPIF_ON_DEVICE_RESET if (g_probe_info.keepif_on_devreset && g_probe_info.dev_resetted) { atomic_set(&g_probe_info.usbdev_stat, USB_DEVICE_PROBED); wake_up_interruptible(&g_probe_info.usbreset_queue_head); } else #endif /* KEEPIF_ON_DEVICE_RESET */ if (g_probe_info.dereged == FALSE && drvinfo.probe) { disc_arg = drvinfo.probe(usb->bus->busnum, usb->portnum, 0); } g_probe_info.disc_cb_done = FALSE; #ifdef KERNEL26 intf->needs_remote_wakeup = 1; #endif /* KERNEL26 */ DHD_MUTEX_UNLOCK(); /* Success */ #ifdef KERNEL26 return DBUS_OK; #else usb_inc_dev_use(usb); return &g_probe_info; #endif fail: printf("%s: Exit ret=%d\n", __FUNCTION__, ret); #ifdef BCMUSBDEV_COMPOSITE if (ret != BCME_UNSUPPORTED) #endif /* BCMUSBDEV_COMPOSITE */ DBUSERR(("%s: failed with errno %d\n", __FUNCTION__, ret)); #ifndef KERNEL26 if (claimed) #ifdef BCMUSBDEV_COMPOSITE usb_driver_release_interface(&dbus_usbdev, IFPTR(usb, wlan_if)); #else usb_driver_release_interface(&dbus_usbdev, IFPTR(usb, CONTROL_IF)); #endif /* BCMUSBDEV_COMPOSITE */ #endif /* !KERNEL26 */ DHD_MUTEX_UNLOCK(); #ifdef KERNEL26 usb_set_intfdata(intf, NULL); return ret; #else return NULL; #endif } /* dbus_usbos_probe */ /** Called by Linux kernel, is the counter part of dbus_usbos_probe() */ DBUS_USBOS_DISCONNECT() { #ifdef KERNEL26 struct usb_device *usb = interface_to_usbdev(intf); probe_info_t *probe_usb_init_data = usb_get_intfdata(intf); #else probe_info_t *probe_usb_init_data = (probe_info_t *) ptr; #endif usbos_info_t *usbos_info; DHD_MUTEX_LOCK(); DBUSERR(("%s: bus num(busnum)=%d, slot num (portnum)=%d\n", __FUNCTION__, usb->bus->busnum, usb->portnum)); if (probe_usb_init_data) { usbos_info = (usbos_info_t *) probe_usb_init_data->usbos_info; if (usbos_info) { if ((probe_usb_init_data->dereged == FALSE) && drvinfo.remove && disc_arg) { bool remove_if = TRUE; #ifdef KEEPIF_ON_DEVICE_RESET if (g_probe_info.keepif_on_devreset) remove_if = FALSE; #endif /* KEEPIF_ON_DEVICE_RESET */ if (remove_if) { drvinfo.remove(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 */ } #ifdef KEEPIF_ON_DEVICE_RESET if (g_probe_info.keepif_on_devreset && (!g_probe_info.dev_resetted)) { atomic_set(&g_probe_info.usbdev_stat, USB_DEVICE_DISCONNECTED); wake_up_interruptible(&g_probe_info.usbreset_queue_head); } #endif /* KEEPIF_ON_DEVICE_RESET */ 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, #if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0)) 0, (void *)dbus_usbos_send_complete, req_zlp); #else 0, (usb_complete_t)dbus_usbos_send_complete, req_zlp); #endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0) */ 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, #if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0)) buffer_length, (void *)dbus_usbos_send_complete, req); #else buffer_length, (usb_complete_t)dbus_usbos_send_complete, req); #endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0) */ 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); 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; /* : Need to implement */ 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, #if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0)) buf, size, (void *)dbus_usbos_ctlwrite_complete, usbos_info); #else buf, size, (usb_complete_t)dbus_usbos_ctlwrite_complete, usbos_info); #endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0) */ #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, #if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0)) buf, size, (void *)dbus_usbos_ctlread_complete, usbos_info); #else buf, size, (usb_complete_t)dbus_usbos_ctlread_complete, usbos_info); #endif /* LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0) */ 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; /* : Need nchan for both TX and RX?; * BDC uses one RX pipe and one TX pipe * RPC may use two RX pipes and one TX pipe? */ 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 /* INTR_EP_ENABLE */ 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; } #if defined(BCMDBG) || defined(DBUS_LINUX_HIST) static void dbus_usbos_intf_dump(void *bus, struct bcmstrbuf *b) { usbos_info_t *usbos_info = (usbos_info_t *) bus; int i = 0, j = 0, rxposted, txposted; rxposted = atomic_read(&usbos_info->rxposted); txposted = atomic_read(&usbos_info->txposted); if (b) { bcm_bprintf(b, "\ndbus linux dump\n"); bcm_bprintf(b, "txposted %d rxposted %d\n", txposted, rxposted); bcm_bprintf(b, "RXDPC: dpc_cnt %d dpc_pktcnt %d dpc_maxpktcnt %d avg_dpc_pktcnt\n", usbos_info->dpc_cnt, usbos_info->dpc_pktcnt, usbos_info->dpc_maxpktcnt, usbos_info->dpc_cnt ? (usbos_info->dpc_pktcnt/usbos_info->dpc_cnt):1); /* Histogram */ bcm_bprintf(b, "txposted\n"); } else { printf("\ndbus linux dump\n"); printf("txposted %d rxposted %d\n", txposted, rxposted); printf("RXDPC: dpc_cnt %d dpc_pktcnt %d dpc_maxpktcnt %d avg_dpc_pktcnt %d\n", usbos_info->dpc_cnt, usbos_info->dpc_pktcnt, usbos_info->dpc_maxpktcnt, usbos_info->dpc_cnt ? (usbos_info->dpc_pktcnt/usbos_info->dpc_cnt):1); /* Histogram */ printf("txposted\n"); } for (i = 0; i < usbos_info->pub->ntxq; i++) { if (usbos_info->txposted_hist == NULL) { break; } if (usbos_info->txposted_hist[i]) { if (b) bcm_bprintf(b, "%d: %d ", i, usbos_info->txposted_hist[i]); else printf("%d: %d ", i, usbos_info->txposted_hist[i]); j++; if (j % 10 == 0) { if (b) bcm_bprintf(b, "\n"); else printf("\n"); } } } j = 0; if (b) bcm_bprintf(b, "\nrxposted\n"); else printf("\nrxposted\n"); for (i = 0; i < usbos_info->pub->nrxq; i++) { if (usbos_info->rxposted_hist == NULL) { break; } if (usbos_info->rxposted_hist[i]) { if (b) bcm_bprintf(b, "%d: %d ", i, usbos_info->rxposted_hist[i]); else printf("%d: %d ", i, usbos_info->rxposted_hist[i]); j++; if (j % 10 == 0) { if (b) bcm_bprintf(b, "\n"); else printf("\n"); } } } if (b) bcm_bprintf(b, "\n"); else printf("\n"); return; } #endif /* BCMDBG || DBUS_LINUX_HIST */ /** 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; } #ifdef KEEPIF_ON_DEVICE_RESET else if (config->config_id == DBUS_CONFIG_ID_KEEPIF_ON_DEVRESET) { g_probe_info.keepif_on_devreset = config->general_param ? TRUE : FALSE; err = DBUS_OK; } #endif /* KEEPIF_ON_DEVICE_RESET */ else if (config->config_id == DBUS_CONFIG_ID_AGGR_LIMIT) { #ifndef BCM_FD_AGGR /* DBUS_CONFIG_ID_AGGR_LIMIT shouldn't be called after probe stage */ ASSERT(disc_arg == NULL); #endif /* BCM_FD_AGGR */ 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; } #ifdef EHCI_FASTPATH_TX /** * In some cases, the code must submit an URB and wait for its completion. * Related: dbus_usbos_sync_complete() */ static int dbus_usbos_sync_wait(usbos_info_t *usbinfo, uint16 time) { int ret; int err = DBUS_OK; int ms = time; ret = wait_event_interruptible_timeout(usbinfo->wait, usbinfo->waitdone == TRUE, (ms * HZ / 1000)); if ((usbinfo->waitdone == FALSE) || (usbinfo->sync_urb_status)) { DBUSERR(("%s: timeout(%d) or urb err=0x%x\n", __FUNCTION__, ret, usbinfo->sync_urb_status)); err = DBUS_ERR; BCM_REFERENCE(ret); } usbinfo->waitdone = FALSE; return err; } #endif /* EHCI_FASTPATH_TX */ /** 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; /* PR82642: Input buffer on stack is causing failures * on some platforms (STB boxes) */ tmpbuf = (char *) MALLOC(usbinfo->pub->osh, buflen); if (!tmpbuf) { DBUSERR(("%s: Unable to allocate memory \n", __FUNCTION__)); return FALSE; } #ifdef BCM_REQUEST_FW /* Adopted from Windows layer dbus_usb_ndis.c to support USB3.0 core */ 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 (eg 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; } /* dbus_write_membytes */ 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; } /* dbus_usbos_readreg */ 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)) /* : Need to test under 2.6 kernel */ 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) { #ifdef EHCI_FASTPATH_TX int ret = DBUS_ERR; struct ehci_qtd *qtd = optimize_ehci_qtd_alloc(GFP_KERNEL); int token = EHCI_QTD_SET_CERR(3); if (qtd == NULL) goto fail; optimize_qtd_fill_with_data(usbinfo->pub, 0, qtd, buffer, token, len); optimize_submit_async(qtd, 0); ret = dbus_usbos_sync_wait(usbinfo, USB_SYNC_WAIT_TIMEOUT); return (ret == DBUS_OK); fail: return FALSE; #else 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; #endif /* EHCI_FASTPATH_TX */ } 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(dbus_driver_t *driver, dbus_intf_t **intf) { bzero(&g_probe_info, sizeof(probe_info_t)); drvinfo = *driver; *intf = &dbus_usbos_intf; USB_REGISTER(); #ifdef KEEPIF_ON_DEVICE_RESET init_waitqueue_head(&g_probe_info.usbreset_queue_head); atomic_set(&g_probe_info.usbdev_stat, USB_DEVICE_INIT); g_probe_info.usbreset_kt = kthread_create(dbus_usbos_usbreset_func, &g_probe_info, "usbreset-thread"); ASSERT(!IS_ERR(g_probe_info.usbreset_kt)); wake_up_process(g_probe_info.usbreset_kt); #endif /* KEEPIF_ON_DEVICE_RESET */ return DBUS_ERR_NODEVICE; } int dbus_bus_osl_deregister() { g_probe_info.dereged = TRUE; DHD_MUTEX_LOCK(); #ifdef KEEPIF_ON_DEVICE_RESET wake_up_interruptible(&g_probe_info.usbreset_queue_head); kthread_stop(g_probe_info.usbreset_kt); #endif /* KEEPIF_ON_DEVICE_RESET */ if (drvinfo.remove && disc_arg && (g_probe_info.disc_cb_done == FALSE)) { drvinfo.remove(disc_arg); disc_arg = NULL; } DHD_MUTEX_UNLOCK(); USB_DEREGISTER(); return DBUS_OK; } static void dbus_usbos_init_info(void) { usbos_info_t *usbos_info = g_probe_info.usbos_info; if (!usbos_info) { return; } /* 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; usbos_info->pub->dev_info = g_probe_info.usb; 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) #if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0)) usbos_info->maxps = usb_maxpacket(usbos_info->usb, usbos_info->tx_pipe); #else usbos_info->maxps = usb_maxpacket(usbos_info->usb, usbos_info->tx_pipe, usb_pipeout(usbos_info->tx_pipe)); #endif /* LINUX_VERSION_CODE < KERNEL_VERSION(5, 19, 0) */ } 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; dbus_usbos_init_info(); 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 DBUS_LINUX_RXDPC INIT_LIST_HEAD(&usbos_info->req_rxpendingq); spin_lock_init(&usbos_info->rxpending_lock); #endif /* DBUS_LINUX_RXDPC */ #if defined(BCMDBG) || defined(DBUS_LINUX_HIST) usbos_info->txposted_hist = MALLOC(pub->osh, (usbos_info->pub->ntxq+1) * sizeof(int)); if (usbos_info->txposted_hist) { bzero(usbos_info->txposted_hist, (usbos_info->pub->ntxq+1) * sizeof(int)); } usbos_info->rxposted_hist = MALLOC(pub->osh, (usbos_info->pub->nrxq+1) * sizeof(int)); if (usbos_info->rxposted_hist) { bzero(usbos_info->rxposted_hist, (usbos_info->pub->nrxq+1) * sizeof(int)); } #endif #ifdef USB_DISABLE_INT_EP /* PR 82639 Don't use interrupt 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; #ifdef DBUS_LINUX_RXDPC /* Initialize DPC thread */ sema_init(&usbos_info->dpc_sem, 0); init_completion(&usbos_info->dpc_exited); usbos_info->dpc_pid = kernel_thread(dbus_usbos_dpc_thread, usbos_info, 0); if (usbos_info->dpc_pid < 0) { DBUSERR(("%s: failed to create dpc thread\n", __FUNCTION__)); goto fail; } #endif /* DBUS_LINUX_RXDPC */ 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, #ifdef CTFPOOL usbos_info->pub->nrxq, #else MIN(DBUS_USB_RXQUEUE_BATCH_ADD, usbos_info->pub->nrxq), #endif 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 */ #if defined(EHCI_FASTPATH_TX) || defined(EHCI_FASTPATH_RX) spin_lock_init(&usbos_info->fastpath_lock); if (optimize_init(usbos_info, usbos_info->usb, usbos_info->tx_pipe, usbos_info->rx_pipe, usbos_info->rx_pipe2) != 0) { DBUSERR(("%s: optimize_init failed!\n", __FUNCTION__)); goto fail; } #endif /* EHCI_FASTPATH_TX || EHCI_FASTPATH_RX */ return (void *) usbos_info; fail: #ifdef DBUS_LINUX_RXDPC if (usbos_info->dpc_pid >= 0) { KILL_PROC(usbos_info->dpc_pid, SIGTERM); wait_for_completion(&usbos_info->dpc_exited); } #endif /* DBUS_LINUX_RXDPC */ 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_DNGL_EMBEDIMAGE) || 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 */ #if defined(EHCI_FASTPATH_TX) || defined(EHCI_FASTPATH_RX) optimize_deinit(usbos_info, usbos_info->usb); #endif /* 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); #if defined(BCMDBG) || defined(DBUS_LINUX_HIST) if (usbos_info->txposted_hist) { MFREE(osh, usbos_info->txposted_hist, (usbos_info->pub->ntxq+1) * sizeof(int)); } if (usbos_info->rxposted_hist) { MFREE(osh, usbos_info->rxposted_hist, (usbos_info->pub->nrxq+1) * sizeof(int)); } #endif /* BCMDBG || DBUS_LINUX_HIST */ #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); 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); 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); } GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); 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); } GCC_DIAGNOSTIC_POP(); } /** 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. */ GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); 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); } GCC_DIAGNOSTIC_POP(); 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; GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); entry = list_entry(cur, struct usbos_list_entry, list); GCC_DIAGNOSTIC_POP(); /* 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 USB_TRIGGER_DEBUG static bool dbus_usbos_ctl_send_debugtrig(usbos_info_t* usbinfo) { bootrom_id_t id; if (usbinfo == NULL) return FALSE; id.chip = 0xDEAD; dbus_usbos_dl_cmd(usbinfo, DL_DBGTRIG, &id, sizeof(bootrom_id_t)); /* ignore the result for now */ return TRUE; } #endif /* USB_TRIGGER_DEBUG */ #if defined(EHCI_FASTPATH_TX) || defined(EHCI_FASTPATH_RX) /** New optimized code for USB AP. EHCI fastpath specific function. */ void inline optimize_ehci_qtd_init(struct ehci_qtd *qtd, dma_addr_t dma) { bzero(qtd, sizeof(*qtd)); wmb(); qtd->qtd_self = dma; qtd->qtd_status = cpu_to_le32(EHCI_QTD_HALTED); qtd->qtd_next = EHCI_NULL; qtd->qtd_altnext = EHCI_NULL; qtd->obj_next = NULL; qtd->rpc = NULL; /* qtd->buff = NULL; */ qtd->xacterrs = EHCI_QTD_XACTERR_MAX; wmb(); } /** EHCI fastpath specific function */ struct ehci_qtd *optimize_ehci_qtd_alloc(gfp_t flags) { struct ehci_qtd *qtd; dma_addr_t dma; usbos_info_t *usbos_info = g_probe_info.usbos_info; struct dma_pool *pool = usbos_info->qtd_pool; qtd = dma_pool_alloc(pool, flags, &dma); if (qtd != NULL) { /* ME: Only clear the necessary fields: * - hw * - chain of QTDs in mapped space */ optimize_ehci_qtd_init(qtd, dma); } return qtd; } /** EHCI fastpath specific function */ void optimize_ehci_qtd_free(struct ehci_qtd *qtd) { usbos_info_t *usbos_info = g_probe_info.usbos_info; struct dma_pool *pool = usbos_info->qtd_pool; dma_pool_free(pool, qtd, qtd->qtd_self); } /** * EHCI fastpath specific function. Loosely follows qtd_copy_status * Greatly simplified as there are only three options: normal, short read, and disaster */ static int get_qtd_status(struct ehci_qtd *qtd, int token, int *actual_length) { int status = -EINPROGRESS; *actual_length += qtd->length - EHCI_QTD_GET_BYTES(token); /* Short read is not an error */ if (unlikely (SHORT_READ_Q (token))) status = -EREMOTEIO; /* Check for serious problems */ if (token & EHCI_QTD_HALTED) { status = -EPROTO; if (token & (EHCI_QTD_BABBLE | EHCI_QTD_MISSEDMICRO | EHCI_QTD_BUFERR | EHCI_QTD_XACTERR)) printk("EHCI Fastpath: Serious USB issue qtd %p token %08x --> status %d\n", qtd, token, status); } return status; } /** This only works in the BCM embedded world (code uses primitive address mapping) */ static void dump_qtd(struct ehci_qtd *qtd) { printk("qtd_next %08x qtd_altnext %08x qtd_status %08x\n", qtd->qtd_next, qtd->qtd_altnext, qtd->qtd_status); } static void dump_qh(struct ehci_qh *qh) { struct ehci_qtd *qtd = (struct ehci_qtd *)(qh->qh_curqtd | 0xa0000000); printk("EHCI Fastpath: QH %p Dump\n", qh); printk("qtd_next %08x info1 %08x info2 %08x current %08x\n", qh->qh_link, qh->qh_endp, qh->qh_endphub, qh->qh_curqtd); printk("overlay\n"); dump_qtd((struct ehci_qtd *)&qh->ow_next); while ((((int)qtd)&EHCI_NULL) == 0) { printk("QTD %p\n", qtd); dump_qtd((struct ehci_qtd *)qtd); qtd = (struct ehci_qtd *)(qtd->qtd_next | 0xa0000000); } } /** * EHCI fastpath specific function. * This code assumes the caller holding a lock * It is currently called from scan_async that should have the lock * Lock shall be dropped around the actual completion, then reacquired * This is a clean implementation of the qh_completions() */ static void ehci_bypass_callback(int pipeindex, struct ehci_qh *qh, spinlock_t *lock) { /* Loop variables */ struct ehci_qtd *qtd, /* current QTD */ *end = qh->dummy, /* "afterend" */ *next; int stopped; /* ME: Obtain the same info in cleaner way */ usbos_info_t *usbos_info = g_probe_info.usbos_info; /* printk("EHCI Fastpath: callback pipe %d QH %p lock %p\n", pipeindex, qh, lock); */ /* * This code should not require any interlocking with QTD additions * The additions never touch QH, we should never touch 'end' * Note that QTD additions will keep 'end' in place */ for (qtd = qh->first_qtd; qtd != end; qtd = next) { u32 status; /* Status bits from QTD */ /* Get the status bits from the QTD */ rmb(); status = hc32_to_cpu(qtd->qtd_status); if ((status & EHCI_QTD_ACTIVE) == 0) { if (unlikely((status & EHCI_QTD_HALTED) != 0)) { /* Retry transaction errors until we * reach the software xacterr limit */ if ((status & EHCI_QTD_XACTERR) && EHCI_QTD_GET_CERR(status) == 0 && --qtd->xacterrs > 0) { /* Reset the token in the qtd and the * qh overlay (which still contains * the qtd) so that we pick up from * where we left off */ printk("EHCI Fastpath: detected XactErr " "qtd %p len %d/%d retry %d\n", qtd, qtd->length - EHCI_QTD_GET_BYTES(status), qtd->length, EHCI_QTD_XACTERR_MAX - qtd->xacterrs); status &= ~EHCI_QTD_HALTED; status |= EHCI_QTD_ACTIVE | EHCI_QTD_SET_CERR(3); qtd->qtd_status = cpu_to_le32(status); wmb(); qh->ow_status = cpu_to_le32(status); break; } /* QTD processing was aborted - highly unlikely (never seen, so not * tested). In very new 2.6, we can retry. In 2.4 and older 2.6, * life sucks (the USB stack does the same) */ printk("EHCI Fastpath: QTD halted\n"); dump_qh(qh); stopped = 1; } } else { /* Inactive QTD is an afterend, finished the list */ break; } /* Remove the QTD from software QH. This should be done before dropping the lock * in for upper layer */ next = qtd->obj_next; qh->first_qtd = next; /* Upper layer processing. */ if (EHCI_QTD_GET_PID(status) == 0) /* OUT (host->dongle) pipe */ { if (qtd->rpc == NULL) { /* Download, just signal completion * ME: Use non-global OS handle * Code from dbus_usbos_sync_complete */ usbos_info->waitdone = TRUE; wake_up_interruptible(&usbos_info->wait); /* ME: Set to meaningful status */ usbos_info->sync_urb_status = 0; } else { /* RPC packet handling * First, relevant processing from dbus_if_send_irb_complete: * ME: Check for bus state here */ /* Now propagate to the RPC layer from dbus_usbos_send_complete: * usbos_info->cbs->send_irb_complete(usbos_info->cbarg, * txirb, status); */ /* From dbus_usb_send_irb_complete: */ /* usb_info_t *usb_info = (usb_info_t *) handle; */ usb_info_t *usb_info = (usb_info_t *) usbos_info->cbarg; /* if(usb_info && usb_info->cbs && usb_info->cbs->send_irb_complete) * usb_info->cbs->send_irb_complete(usb_info->cbarg, txirb, status); */ /* From dbus_if_send_irb_complete * dbus_info_t *dbus_info = (dbus_info_t *) handle; */ dbus_info_t *dbus_info = (dbus_info_t *)usb_info->cbarg; /* printk("RPC send completed %p %p %p\n", usbos_info, * usb_info, dbus_info); * dbus_tx_timer_stop(dbus_info); * if (txirb_pending) * dbus_tx_timer_start(dbus_info, DBUS_TX_TIMEOUT_INTERVAL); * pktinfo = txirb->info; // This is the same as the pkt :-) * if (dbus_info->cbs && dbus_info->cbs->send_complete) * dbus_info->cbs->send_complete(dbus_info->cbarg, pktinfo, * status); */ /* Free the coalesce buffer, if multi-buffer packet only. Do not * rely on buff, as it might not even exist */ if (PKTNEXT(usbos_info->pub->osh, qtd->rpc)) { /* ME: dma_unmap_single */ /* printk("k-Freeing %p\n", qtd->buff); */ kfree(qtd->buff); } if (dbus_info->cbs && dbus_info->cbs->send_complete) { atomic_dec(&s_tx_pending); spin_unlock(lock); /* printk("Sending to RPC qtd %p\n", qtd); */ #if !(defined(BCM_RPC_NOCOPY) || defined(BCM_RPC_TXNOCOPY) || defined(BCM_RPC_TOC)) #error Configuration not supported; read dbus_if_send_irb_complete for guidelines #endif /* ME: Set to meaningful status */ dbus_info->cbs->send_complete(dbus_info->cbarg, qtd->rpc, 0); if ((atomic_read(&s_tx_pending) < 16) && /* ME: 16 or dbus_info->tx_low_watermark? */ dbus_info->txoff && !dbus_info->txoverride) { dbus_flowctrl_tx(dbus_info, OFF); } spin_lock(lock); /* Things could have happened while the lock was gone, * resync to the hardware */ next = qh->first_qtd; end = qh->dummy; } } optimize_ehci_qtd_free(qtd); } else { /* IN (dongle->host) pipe */ /* Simulates the upstream travel */ usb_info_t *usb_info = (usb_info_t *) usbos_info->cbarg; dbus_info_t *dbus_info = (dbus_info_t *)usb_info->cbarg; /* unsigned long flags; */ int actual_length = 0; /* All our reads must be short */ if (!SHORT_READ_Q (status)) ASSERT(0); /* Done with hardware, convert status to error codes */ status = get_qtd_status(qtd, status, &actual_length); switch (status) { /* success */ case 0: case -EINPROGRESS: case -EREMOTEIO: status = 0; break; case -ECONNRESET: /* canceled */ case -ENOENT: case -EPROTO: DBUSERR(("%s: ehci unlink. status %x\n", __FUNCTION__, status)); break; } if (g_probe_info.dereged) { printk("%s: DBUS deregistering, ignoring recv callback\n", __FUNCTION__); return; } dma_unmap_single( usbos_info->usb->bus->controller, (dma_addr_t)qtd->qtd_buffer_hi[0], actual_length, DMA_FROM_DEVICE); /* From dbus_usb_recv_irb_complete * * if (usb_info->cbs && usb_info->cbs->recv_irb_complete) * usb_info->cbs->recv_irb_complete(usb_info->cbarg, rxirb, status); */ /* From dbus_if_recv_irb_complete */ if (dbus_info->pub.busstate != DBUS_STATE_DOWN) { if (status == 0) { void *buf = qtd->rpc; ASSERT(buf != NULL); spin_unlock(lock); #if defined(BCM_RPC_NOCOPY) || defined(BCM_RPC_RXNOCOPY) /* Note that these ifdefs are indirectly coming from * dbus_usbos_recv_urb_submit The code itself is from * dbus_if_recv_irb_complete that makes the decision * at runtime, yet it is only pkt or buf depending on * the NOCOPY setup, never both :-) */ if (dbus_info->cbs && dbus_info->cbs->recv_pkt) dbus_info->cbs->recv_pkt(dbus_info->cbarg, buf); #else if (actual_length > 0) { if (dbus_info->cbs && dbus_info->cbs->recv_buf) dbus_info->cbs->recv_buf(dbus_info->cbarg, buf, actual_length); } #endif spin_lock(lock); /* Things could have happened while the lock was gone, * resync to the hardware */ next = qh->first_qtd; end = qh->dummy; /* Reinitialize this qtd since it will be reused. */ optimize_ehci_qtd_init(qtd, qtd->qtd_self); #if defined(BCM_RPC_NOCOPY) || defined(BCM_RPC_RXNOCOPY) /* Note that these ifdefs are coming from * dbus_usbos_recv_urb_submit. In the NOCOPY configuration, * force an allocation of a new packet */ optimize_submit_rx_request(&dbus_info->pub, 1, qtd, NULL); #else /* In the copy mode, simply reuse the buffer; upper level * had already consumed the data */ optimize_submit_rx_request(&dbus_info->pub, 1, qtd, buf); #endif /* Not to free this qtd because it will be reused. */ continue; } } else { printk("%s: DBUS down, ignoring recv callback\n", __FUNCTION__); } optimize_ehci_qtd_free(qtd); } } } /* ehci_bypass_callback */ /** EHCI fastpath specific function */ static void optimize_urb_callback(struct urb *urb) { struct usb_ctrlrequest *req = urb->context; kfree(req); USB_FREE_URB(urb); } /** * EHCI fastpath specific function. Shall be called under an external lock (currently RPC_TP_LOCK) */ static int optimize_submit_urb(struct usb_device *usb, void *ptr, int request) { struct usb_ctrlrequest *req; struct urb *urb; if ((urb = USB_ALLOC_URB()) == NULL) { printk("EHCI Fastpath: Error allocating URB in optimize_EP!"); return -ENOMEM; } if ((req = kmalloc(sizeof(struct usb_ctrlrequest), GFP_ATOMIC)) == NULL) { printk("EHCI Fastpath: Failed to allocate memory for control request in" " optimize_EP!"); USB_FREE_URB(urb); return -ENOMEM; } req->bRequestType = (USB_TYPE_VENDOR | USB_RECIP_OTHER); req->bRequest = request; /* Use this instead of a buffer */ req->wValue = ((int)ptr & 0xffff); req->wIndex = ((((int)ptr)>>16) & 0xffff); req->wLength = 0; printk("EHCI Fastpath: usb_dev %p\n", usb); printk("EHCI Fastpath: bus %p\n", usb->bus); printk("EHCI Fastpath: Hub %p\n", usb->bus->root_hub); usb_fill_control_urb( urb, usb->bus->root_hub, usb_sndctrlpipe(usb->bus->root_hub, 0), (void *)req, NULL, 0, optimize_urb_callback, req); USB_SUBMIT_URB(urb); if (urb->status != 0) { printk("EHCI Fastpath: Cannot submit URB in optimize_EP: %d\n", urb->status); } return urb->status; } /* optimize_submit_urb */ /** EHCI fastpath specific function */ static int epnum(int pipe) { int epn = usb_pipeendpoint(pipe); if (usb_pipein (pipe)) epn |= 0x10; return epn; } /** EHCI fastpath specific function */ static int optimize_init(usbos_info_t *usbos_info, struct usb_device *usb, int out, int in, int in2) { int retval = -EPIPE; atomic_set(&s_tx_pending, 0); /* atomic_set(&s_rx_pending, 0); */ usbos_info->tx_ep = epnum(out); usbos_info->rx_ep = epnum(in); usbos_info->rx2_ep = epnum(in2); usbos_info->usb_device = usb; /* printk("EHCI Fastpath: Create pool %p %p %p\n", usb, usb->bus, usb->bus->controller); */ /* QTDs for bulk transfers - separate pool */ /* ME: Destroy pool */ usbos_info->qtd_pool = dma_pool_create("usbnet_qtd", usb->bus->controller, sizeof(struct ehci_qtd), EHCI_QTD_ALIGN /* byte alignment (for hw parts) */, 4096 /* can't cross 4K */); if (!usbos_info->qtd_pool) { printk("EHCI Fastpath: Cannot create the QTD pool\n"); goto fail; } /* detaching the EP */ if (optimize_submit_urb(usb, usb, EHCI_SET_BYPASS_DEV) != 0) goto fail; optimize_submit_urb(usb, ehci_bypass_callback, EHCI_SET_BYPASS_CB); optimize_submit_urb(usb, usbos_info->qtd_pool, EHCI_SET_BYPASS_POOL); #ifdef EHCI_FASTPATH_TX optimize_submit_urb(usb, (void*)((0<<16)|usbos_info->tx_ep), EHCI_FASTPATH); #endif #ifdef EHCI_FASTPATH_RX optimize_submit_urb(usb, (void*)((1<<16)|usbos_info->rx_ep), EHCI_FASTPATH); #endif /* ME: Later optimize_submit_urb(usb, ((2<<16)|s_rx2_ep), EHCI_FASTPATH); */ /* getting the QH */ printk("EHCI Fastpath: EP in %d EP in2 %d EP out %d\n", usbos_info->rx_ep, usbos_info->rx2_ep, usbos_info->tx_ep); return 0; fail: return retval; } /* optimize_submit_urb */ /** EHCI fastpath specific function */ static int optimize_deinit_qtds(struct ehci_qh *qh, int coalesce_buf) { usbos_info_t *usbos_info = g_probe_info.usbos_info; struct ehci_qtd *qtd, *end, *next; unsigned long flags; if (qh == NULL) return 0; end = qh->dummy; printk("%s %d. qh = %p\n", __func__, __LINE__, qh); spin_lock_irqsave(&usbos_info->fastpath_lock, flags); for (qtd = qh->first_qtd; qtd != end; qtd = next) { next = qtd->obj_next; qh->first_qtd = next; /* Free the coalesce buffer, if multi-buffer packet only. Do not * rely on buff, as it might not even exist */ if (coalesce_buf && PKTNEXT(usbos_info->pub->osh, qtd->rpc)) { /* ME: dma_unmap_single */ printk("k-Freeing %p, ", qtd->buff); kfree(qtd->buff); } printk("freeing qtd %p\n", qtd); optimize_ehci_qtd_free(qtd); } spin_unlock_irqrestore(&usbos_info->fastpath_lock, flags); return 0; } /** EHCI fastpath specific function. This code might deserve a separate file */ static struct ehci_qh * get_ep(usbos_info_t *usbos_info, int ep) { #ifdef KERNEL26 struct usb_host_endpoint *epp = NULL; switch (ep) { case 0: epp = usbos_info->usb_device->ep_out[usbos_info->tx_ep&0xf]; break; case 1: epp = usbos_info->usb_device->ep_in[usbos_info->rx_ep&0xf]; break; case 2: epp = usbos_info->usb_device->ep_in[usbos_info->rx2_ep&0xf]; break; default: ASSERT(0); } if (epp != NULL) return (struct ehci_qh *)epp->hcpriv; else return NULL; #else switch (ep) { case 0: return (struct ehci_qh *)(((struct hcd_dev*)(usbos_info-> usb_device->hcpriv))->ep[usbos_info->tx_ep]); case 1: return (struct ehci_qh *)(((struct hcd_dev*)(usbos_info-> usb_device->hcpriv))->ep[usbos_info->rx_ep]); case 2: return (struct ehci_qh *)(((struct hcd_dev*)(usbos_info-> usb_device->hcpriv))->ep[usbos_info->rx2_ep]); default: ASSERT(0); } return NULL; #endif /* KERNEL26 */ } /** EHCI fastpath specific function */ int optimize_deinit(usbos_info_t *usbos_info, struct usb_device *usb) { optimize_deinit_qtds(get_ep(usbos_info, 0), 1); optimize_deinit_qtds(get_ep(usbos_info, 1), 0); #if defined(EHCI_FASTPATH_TX) || defined(EHCI_FASTPATH_RX) optimize_submit_urb(usb, (void *)0, EHCI_CLR_EP_BYPASS); #endif dma_pool_destroy(usbos_info->qtd_pool); return 0; } /** EHCI fastpath specific function. Reassemble the segmented packet */ static int optimize_gather(const dbus_pub_t *pub, void *pkt, void **buf) { int len = 0; void *transfer_buf = kmalloc(pkttotlen(pub->osh, pkt), GFP_ATOMIC); *buf = transfer_buf; if (!transfer_buf) { printk("fail to alloc to usb buffer\n"); return 0; } while (pkt) { int pktlen = PKTLEN(pub->osh, pkt); bcopy(PKTDATA(pub->osh, pkt), transfer_buf, pktlen); transfer_buf += pktlen; len += pktlen; pkt = PKTNEXT(pub->osh, pkt); } /* printk("Coalesced a %d-byte buffer\n", len); */ return len; } /** EHCI fastpath specific function */ int optimize_qtd_fill_with_rpc(const dbus_pub_t *pub, int epn, struct ehci_qtd *qtd, void *rpc, int token, int len) { void *data = NULL; if (len == 0) return optimize_qtd_fill_with_data(pub, epn, qtd, data, token, len); ASSERT(rpc != NULL); data = PKTDATA(pub->osh, rpc); qtd->rpc = rpc; if (PKTNEXT(pub->osh, rpc)) { len = optimize_gather(pub, rpc, &data); qtd->buff = data; } return optimize_qtd_fill_with_data(pub, epn, qtd, data, token, len); } /** EHCI fastpath specific function. Fill the QTD from the data buffer */ int optimize_qtd_fill_with_data(const dbus_pub_t *pub, int epn, struct ehci_qtd *qtd, void *data, int token, int len) { int i, bytes_fit, page_offset; dma_addr_t addr = 0; /* struct usb_host_endpoint *ep = get_ep(epn); */ usbos_info_t *usbos_info = g_probe_info.usbos_info; token |= (EHCI_QTD_ACTIVE | EHCI_QTD_IOC); /* Allow execution, force interrupt */ if (len > 0) { addr = dma_map_single( usbos_info->usb->bus->controller, data, len, EHCI_QTD_GET_PID(token) ? DMA_FROM_DEVICE : DMA_TO_DEVICE); } qtd->qtd_buffer[0] = cpu_to_hc32((u32)addr); /* Here qtd->qtd_buffer_hi[0] is leveraged to store addr value, which * is needed when invoking dma_unmap_single() in ehci_bypass_callback(). * This is valid for EHCI 32bit only. */ qtd->qtd_buffer_hi[0] = cpu_to_hc32((u32)addr); page_offset = (addr & (EHCI_PAGE_SIZE-1)); bytes_fit = EHCI_PAGE_SIZE - page_offset; addr -= page_offset; if (len < bytes_fit) bytes_fit = len; else { addr += EHCI_PAGE_SIZE; for (i = 1; bytes_fit < len && i < EHCI_QTD_NBUFFERS; i++) { qtd->qtd_buffer[i] = cpu_to_hc32((u32)addr); qtd->qtd_buffer_hi[i] = 0; addr += EHCI_PAGE_SIZE; if ((bytes_fit + EHCI_PAGE_SIZE) < len) bytes_fit += EHCI_PAGE_SIZE; else bytes_fit = len; } if (bytes_fit != len) { /* ME: Handle long packets? Does not happen */ ASSERT(0); } } qtd->qtd_status = cpu_to_hc32((bytes_fit << 16) | token); qtd->length = bytes_fit; return bytes_fit; } /* optimize_qtd_fill_with_data */ /** * EHCI fastpath specific function. Reimplementation of qh_append_tds(). Returns nonzero if too many * requests pending. */ int optimize_submit_async(struct ehci_qtd *qtd, int epn) { /* Clean implementation along the lines of qh_append_tds() */ struct ehci_qtd *afterend; /* Element at the end of the QTD chain (after the * last useful one, "after-end") */ dma_addr_t hw_addr; __hc32 status; unsigned long flags; usbos_info_t *usbos_info = g_probe_info.usbos_info; struct ehci_qh *qh = get_ep(usbos_info, epn); usb_info_t *usb_info = (usb_info_t *) usbos_info->cbarg; dbus_info_t *dbus_info = (dbus_info_t *)usb_info->cbarg; /* printk("Submit qtd %p to pipe %d (%p)\n", qtd, epn, qh); */ if (qh == NULL) { printk("EHCI Fastpath: Attempt of optimized submit to a non-optimized pipe\n"); return -1; } spin_lock_irqsave(&usbos_info->fastpath_lock, flags); /* Limit outstanding - for rpc behavior only */ /* printk("QH qtd_status %08x\n", qh->hw->qtd_status); */ if ((qtd->qtd_status & (1<<8)) == 0) { atomic_inc(&s_tx_pending); if (atomic_read(&s_tx_pending) > 16*2) /* (dbus_info->tx_low_watermark * 3)) */ dbus_flowctrl_tx(dbus_info, TRUE); } ASSERT(qh != NULL); /* ME: Check for hardware availability here */ /* * Standard list processing trick: * * old "afterend" is filled with the incoming data while still HALTed * * new element is appended and prepared to serve as new afterend * * now old afterend is activated * This way, HW never races the SW - no semaphores are necessary, as long as this function * is not reentered for the same QH */ /* Make new QTD to be HALTed, wait for it to actually happen */ status = qtd->qtd_status; qtd->qtd_status = cpu_to_le32(EHCI_QTD_HALTED); wmb(); /* Now copy all information from the new QTD to the old afterend, * except the own HW address */ afterend = qh->dummy; hw_addr = afterend->qtd_self; *afterend = *qtd; afterend->qtd_self = hw_addr; /* The new QTD is ready to serve as a new afterend, append it */ qh->dummy = qtd; afterend->qtd_next = qtd->qtd_self; afterend->qtd_altnext = qtd->qtd_self; /* Always assume short read. Harmless in our case */ afterend->obj_next = qtd; /* Wait for writes to happen and enable the old afterend (now containing the QTD data) */ wmb(); afterend->qtd_status = status; wmb(); spin_unlock_irqrestore(&usbos_info->fastpath_lock, flags); return 0; } /* optimize_submit_async */ /** EHCI fastpath specific function. ME: Error return and handling */ void optimize_submit_rx_request(const dbus_pub_t *pub, int epn, struct ehci_qtd *qtd_in, void *buf) { /* ME: Replace with getting usbos_info from pub */ usbos_info_t *usbos_info = g_probe_info.usbos_info; int len = usbos_info->rxbuf_len; void *pkt; struct ehci_qtd *qtd; int token = EHCI_QTD_SET_CERR(3) | EHCI_QTD_SET_PID(1); if (qtd_in == NULL) { qtd = optimize_ehci_qtd_alloc(GFP_KERNEL); if (!qtd) { printk("EHCI Fastpath: Out of QTDs\n"); return; } } else { qtd = qtd_in; } if (buf == NULL) { /* NOCOPY, allocate own packet */ /* Follow dbus_usbos_recv_urb_submit */ pkt = PKTGET(usbos_info->pub->osh, len, FALSE); if (pkt == NULL) { printk("%s: PKTGET failed\n", __FUNCTION__); optimize_ehci_qtd_free(qtd); return; } /* consider the packet "native" so we don't count it as MALLOCED in the osl */ PKTTONATIVE(usbos_info->pub->osh, pkt); qtd->rpc = pkt; buf = PKTDATA(usbos_info->pub->osh, pkt); } else { qtd->rpc = buf; } optimize_qtd_fill_with_data(pub, epn, qtd, buf, token, len); optimize_submit_async(qtd, epn); } /* optimize_submit_rx_request */ #endif /* EHCI_FASTPATH_TX || EHCI_FASTPATH_RX */ #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; } #ifndef DHD_LINUX_STD_FW_API static void * dbus_get_fwfile(int devid, int chiprev, uint8 **fw, int *fwlen, uint16 boardtype, uint16 boardrev, char *path) { const struct firmware *firmware = NULL; s8 *device_id = NULL; s8 *chip_rev = ""; s8 file_name[64]; int ret; switch (devid) { case BCM4381_CHIP_ID: device_id = "4381"; switch (chiprev) { case 1: chip_rev = "a0"; break; default: break; } break; case BCM4382_CHIP_ID: device_id = "4382"; switch (chiprev) { case 1: chip_rev = "a0"; 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-usb.bin", device_id, chip_rev); ret = dbus_request_firmware(path, &firmware); if (ret) { DBUSERR(("fail to request firmware %s\n", path)); return NULL; } else DBUSERR(("%s: %s (%zu bytes) open success\n", __FUNCTION__, path, firmware->size)); GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); *fwlen = firmware->size; *fw = (uint8 *)firmware->data; return (void *)firmware; GCC_DIAGNOSTIC_POP(); } static void * dbus_get_nvfile(int devid, int chiprev, uint8 **fw, int *fwlen, uint16 boardtype, uint16 boardrev, char *path) { const struct firmware *firmware = NULL; s8 *device_id = NULL; s8 *chip_rev = ""; s8 file_name[64]; int ret; switch (devid) { case BCM4381_CHIP_ID: device_id = "4381"; switch (chiprev) { case 1: chip_rev = "a0"; break; default: break; } break; case BCM4382_CHIP_ID: device_id = "4382"; switch (chiprev) { case 1: chip_rev = "a0"; 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-usb.nvm", device_id, chip_rev); ret = dbus_request_firmware(path, &firmware); if (ret) { DBUSERR(("fail to request nvram %s\n", path)); /* 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); if (ret) { DBUSERR(("fail to request nvram %s\n", path)); return NULL; } } else DBUSERR(("%s: %s (%zu bytes) open success\n", __FUNCTION__, path, firmware->size)); *fwlen = firmware->size; GCC_DIAGNOSTIC_PUSH_SUPPRESS_CAST(); *fw = (uint8 *)firmware->data; return (void *)firmware; GCC_DIAGNOSTIC_POP(); } /* dbus_get_fw_nvfile */ void * dbus_get_fw_nvfile(int devid, int chiprev, uint8 **fw, int *fwlen, int type, uint16 boardtype, uint16 boardrev, char *path) { switch (type) { case DBUS_FIRMWARE: return dbus_get_fwfile(devid, chiprev, fw, fwlen, boardtype, boardrev, path); case DBUS_NVFILE: return dbus_get_nvfile(devid, chiprev, fw, fwlen, boardtype, boardrev, path); default: return NULL; } } #else void * dbus_get_fw_nvfile(int devid, int chiprev, uint8 **fw, int *fwlen, int type, uint16 boardtype, uint16 boardrev, char *path) { const struct firmware *firmware = NULL; int err = DBUS_OK; err = dbus_request_firmware(path, &firmware); if (err) { DBUSERR(("fail to request firmware %s\n", path)); return NULL; } else { DBUSERR(("%s: %s (%zu bytes) open success\n", __FUNCTION__, path, firmware->size)); } *fwlen = firmware->size; *fw = (uint8 *)firmware->data; return (void *)firmware; } #endif 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 KEEPIF_ON_DEVICE_RESET /* if keepif_on_devreset is set, and reset_resume or disconnect got called, we need de-register or * register the usb driver to OS again; but this operation can't be called inside the reset_resume * or disconnect callback, so introduce a kernel thread to do these work */ static int dbus_usbos_usbreset_func(void *data) { enum usb_dev_status flag = USB_DEVICE_INIT; usbos_info_t *usbos_info; while (1) { wait_event_interruptible_timeout(g_probe_info.usbreset_queue_head, atomic_read(&g_probe_info.usbdev_stat), 100); /* 100 ms */ if (kthread_should_stop()) break; if (!(usbos_info = g_probe_info.usbos_info)) continue; if (!(flag = atomic_read(&g_probe_info.usbdev_stat))) continue; usbos_info = g_probe_info.usbos_info; flag = atomic_read(&g_probe_info.usbdev_stat); atomic_set(&g_probe_info.usbdev_stat, USB_DEVICE_INIT); if ((flag == USB_DEVICE_RESETTED) || (flag == USB_DEVICE_DISCONNECTED)) { DBUSERR(("Device reset, re-register USB driver\n")); g_probe_info.dev_resetted = TRUE; g_probe_info.busstate_bf_devreset = usbos_info->pub->busstate; dbus_usbos_state_change(usbos_info, DBUS_STATE_DOWN); g_probe_info.dereged = TRUE; USB_DEREGISTER(); USB_REGISTER(); g_probe_info.dereged = FALSE; } else if (flag == USB_DEVICE_PROBED) { DBUSERR(("Device probed, but no need to initialize higher layer\n")); g_probe_info.dev_resetted = FALSE; dbus_usbos_init_info(); /* The device may have lost power, so a firmware download may be required */ dbus_usbos_state_change(usbos_info, DBUS_STATE_DL_NEEDED); g_probe_info.suspend_state = USBOS_SUSPEND_STATE_DEVICE_ACTIVE; dbus_usbos_state_change(usbos_info, g_probe_info.busstate_bf_devreset); } } return 0; } #endif /* KEEPIF_ON_DEVICE_RESET */ #ifdef LINUX struct device * dbus_get_dev(void) { return &g_probe_info.usb->dev; } #endif /* LINUX */