diff --git a/drivers/usb/host/dwc_common_port/dwc_common_linux.c b/drivers/usb/host/dwc_common_port/dwc_common_linux.c index f00a9ff1ed970..6814e511abaf9 100644 --- a/drivers/usb/host/dwc_common_port/dwc_common_linux.c +++ b/drivers/usb/host/dwc_common_port/dwc_common_linux.c @@ -991,6 +991,11 @@ void DWC_TASK_SCHEDULE(dwc_tasklet_t *task) tasklet_schedule(&task->t); } +void DWC_TASK_HI_SCHEDULE(dwc_tasklet_t *task) +{ + tasklet_hi_schedule(&task->t); +} + /* workqueues - run in process context (can sleep) diff --git a/drivers/usb/host/dwc_common_port/dwc_list.h b/drivers/usb/host/dwc_common_port/dwc_list.h index 89cc325045fc3..4ce560df0cae6 100644 --- a/drivers/usb/host/dwc_common_port/dwc_list.h +++ b/drivers/usb/host/dwc_common_port/dwc_list.h @@ -384,17 +384,17 @@ struct { \ #define DWC_TAILQ_PREV(elm, headname, field) \ (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) #define DWC_TAILQ_EMPTY(head) \ - (TAILQ_FIRST(head) == TAILQ_END(head)) + (DWC_TAILQ_FIRST(head) == DWC_TAILQ_END(head)) #define DWC_TAILQ_FOREACH(var, head, field) \ - for((var) = TAILQ_FIRST(head); \ - (var) != TAILQ_END(head); \ - (var) = TAILQ_NEXT(var, field)) + for ((var) = DWC_TAILQ_FIRST(head); \ + (var) != DWC_TAILQ_END(head); \ + (var) = DWC_TAILQ_NEXT(var, field)) #define DWC_TAILQ_FOREACH_REVERSE(var, head, headname, field) \ - for((var) = TAILQ_LAST(head, headname); \ - (var) != TAILQ_END(head); \ - (var) = TAILQ_PREV(var, headname, field)) + for ((var) = DWC_TAILQ_LAST(head, headname); \ + (var) != DWC_TAILQ_END(head); \ + (var) = DWC_TAILQ_PREV(var, headname, field)) /* * Tail queue functions. diff --git a/drivers/usb/host/dwc_common_port/dwc_os.h b/drivers/usb/host/dwc_common_port/dwc_os.h index 308ddd5142e13..8eb24eac55ce2 100644 --- a/drivers/usb/host/dwc_common_port/dwc_os.h +++ b/drivers/usb/host/dwc_common_port/dwc_os.h @@ -981,6 +981,8 @@ extern void DWC_TASK_FREE(dwc_tasklet_t *task); extern void DWC_TASK_SCHEDULE(dwc_tasklet_t *task); #define dwc_task_schedule DWC_TASK_SCHEDULE +extern void DWC_TASK_HI_SCHEDULE(dwc_tasklet_t *task); +#define dwc_task_hi_schedule DWC_TASK_HI_SCHEDULE /** @name Timer * diff --git a/drivers/usb/host/dwc_otg/dwc_otg_hcd.c b/drivers/usb/host/dwc_otg/dwc_otg_hcd.c index fcec97f633426..91eefecd0dbcb 100644 --- a/drivers/usb/host/dwc_otg/dwc_otg_hcd.c +++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd.c @@ -40,6 +40,9 @@ * header file. */ +#include +#include + #include "dwc_otg_hcd.h" #include "dwc_otg_regs.h" @@ -694,6 +697,31 @@ static void reset_tasklet_func(void *data) dwc_otg_hcd->flags.b.port_reset_change = 1; } +static void completion_tasklet_func(void *ptr) +{ + dwc_otg_hcd_t *hcd = (dwc_otg_hcd_t *) ptr; + struct urb *urb; + urb_tq_entry_t *item; + dwc_irqflags_t flags; + + DWC_SPINLOCK_IRQSAVE(hcd->lock, &flags); + while (!DWC_TAILQ_EMPTY(&hcd->completed_urb_list)) { + item = DWC_TAILQ_FIRST(&hcd->completed_urb_list); + urb = item->urb; + DWC_TAILQ_REMOVE(&hcd->completed_urb_list, item, + urb_tq_entries); + DWC_SPINUNLOCK_IRQRESTORE(hcd->lock, flags); + DWC_FREE(item); + + usb_hcd_unlink_urb_from_ep(hcd->priv, urb); + usb_hcd_giveback_urb(hcd->priv, urb, urb->status); + + DWC_SPINLOCK_IRQSAVE(hcd->lock, &flags); + } + DWC_SPINUNLOCK_IRQRESTORE(hcd->lock, flags); + return; +} + static void qh_list_free(dwc_otg_hcd_t * hcd, dwc_list_link_t * qh_list) { dwc_list_link_t *item; @@ -833,6 +861,7 @@ static void dwc_otg_hcd_free(dwc_otg_hcd_t * dwc_otg_hcd) DWC_TIMER_FREE(dwc_otg_hcd->conn_timer); DWC_TASK_FREE(dwc_otg_hcd->reset_tasklet); + DWC_TASK_FREE(dwc_otg_hcd->completion_tasklet); #ifdef DWC_DEV_SRPCAP if (dwc_otg_hcd->core_if->power_down == 2 && @@ -877,7 +906,7 @@ int dwc_otg_hcd_init(dwc_otg_hcd_t * hcd, dwc_otg_core_if_t * core_if) DWC_LIST_INIT(&hcd->periodic_sched_ready); DWC_LIST_INIT(&hcd->periodic_sched_assigned); DWC_LIST_INIT(&hcd->periodic_sched_queued); - + DWC_TAILQ_INIT(&hcd->completed_urb_list); /* * Create a host channel descriptor for each host channel implemented * in the controller. Initialize the channel descriptor array. @@ -915,6 +944,9 @@ int dwc_otg_hcd_init(dwc_otg_hcd_t * hcd, dwc_otg_core_if_t * core_if) /* Initialize reset tasklet. */ hcd->reset_tasklet = DWC_TASK_ALLOC("reset_tasklet", reset_tasklet_func, hcd); + + hcd->completion_tasklet = DWC_TASK_ALLOC("completion_tasklet", + completion_tasklet_func, hcd); #ifdef DWC_DEV_SRPCAP if (hcd->core_if->power_down == 2) { /* Initialize Power on timer for Host power up in case hibernation */ diff --git a/drivers/usb/host/dwc_otg/dwc_otg_hcd.h b/drivers/usb/host/dwc_otg/dwc_otg_hcd.h index 45e44ea74170f..0493dbf51815c 100644 --- a/drivers/usb/host/dwc_otg/dwc_otg_hcd.h +++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd.h @@ -374,6 +374,13 @@ typedef struct dwc_otg_qh { DWC_CIRCLEQ_HEAD(hc_list, dwc_hc); +typedef struct urb_tq_entry { + struct urb *urb; + DWC_TAILQ_ENTRY(urb_tq_entry) urb_tq_entries; +} urb_tq_entry_t; + +DWC_TAILQ_HEAD(urb_list, urb_tq_entry); + /** * This structure holds the state of the HCD, including the non-periodic and * periodic schedules. @@ -551,6 +558,9 @@ struct dwc_otg_hcd { /* Tasket to do a reset */ dwc_tasklet_t *reset_tasklet; + dwc_tasklet_t *completion_tasklet; + struct urb_list completed_urb_list; + /* */ dwc_spinlock_t *lock; dwc_spinlock_t *channel_lock; diff --git a/drivers/usb/host/dwc_otg/dwc_otg_hcd_linux.c b/drivers/usb/host/dwc_otg/dwc_otg_hcd_linux.c index f91c4b1f0c11f..0137ce347bec5 100644 --- a/drivers/usb/host/dwc_otg/dwc_otg_hcd_linux.c +++ b/drivers/usb/host/dwc_otg/dwc_otg_hcd_linux.c @@ -271,7 +271,7 @@ static int _complete(dwc_otg_hcd_t * hcd, void *urb_handle, dwc_otg_hcd_urb_t * dwc_otg_urb, int32_t status) { struct urb *urb = (struct urb *)urb_handle; - + urb_tq_entry_t *new_entry; if (CHK_DEBUG_LEVEL(DBG_HCDV | DBG_HCD_URB)) { DWC_PRINTF("%s: urb %p, device %d, ep %d %s, status=%d\n", __func__, urb, usb_pipedevice(urb->pipe), @@ -285,7 +285,7 @@ static int _complete(dwc_otg_hcd_t * hcd, void *urb_handle, } } } - + new_entry = DWC_ALLOC_ATOMIC(sizeof(urb_tq_entry_t)); urb->actual_length = dwc_otg_hcd_urb_get_actual_length(dwc_otg_urb); /* Convert status value. */ switch (status) { @@ -348,18 +348,25 @@ static int _complete(dwc_otg_hcd_t * hcd, void *urb_handle, } DWC_FREE(dwc_otg_urb); - + if (!new_entry) { + DWC_ERROR("dwc_otg_hcd: complete: cannot allocate URB TQ entry\n"); + urb->status = -EPROTO; + /* don't schedule the tasklet - + * directly return the packet here with error. */ #if USB_URB_EP_LINKING - usb_hcd_unlink_urb_from_ep(dwc_otg_hcd_to_hcd(hcd), urb); + usb_hcd_unlink_urb_from_ep(dwc_otg_hcd_to_hcd(hcd), urb); #endif - DWC_SPINUNLOCK(hcd->lock); #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,28) - usb_hcd_giveback_urb(dwc_otg_hcd_to_hcd(hcd), urb); + usb_hcd_giveback_urb(dwc_otg_hcd_to_hcd(hcd), urb); #else - usb_hcd_giveback_urb(dwc_otg_hcd_to_hcd(hcd), urb, status); + usb_hcd_giveback_urb(dwc_otg_hcd_to_hcd(hcd), urb, urb->status); #endif - DWC_SPINLOCK(hcd->lock); - + } else { + new_entry->urb = urb; + DWC_TAILQ_INSERT_TAIL(&hcd->completed_urb_list, new_entry, + urb_tq_entries); + DWC_TASK_HI_SCHEDULE(hcd->completion_tasklet); + } return 0; }