There is a race condition between finish_unlinks->finish_urb() function and usb_kill_urb() in ohci controller case. finish_urb is called after calling spin_unlock(&ohci->lock), then if during this time, usb_kill_urb is called for another endpoint, then new ed will be added to ed_rm_list at beginning for unlink. and ed_rm_list will point to newly added ed. When finish_urb() is completed in finish_unlinks() and ed->td_list becomes empty as in below code (in finish_unlinks() function) if (list_empty(&ed->td_list)) { *last = ed->ed_next; ed->ed_next = NULL; } else if (ohci->rh_state == OHCI_RH_RUNNING) { *last = ed->ed_next; ed->ed_next = NULL; ed_schedule(ohci, ed); } *last = ed->ed_next will make ed_rm_list to point to ed->ed_next and previously added ed by usb_kill_urb will be left unreferenced by ed_rm_list. This causes usb_kill_urb() hang forever waiting for finish_unlink to remove added ed from ed_rm_list. The main reason for hang in this race condtion is addition and removal of ed from ed_rm_list in the beginning. This patch adds new ed in ed_rm_list at the end and it solves the usb_kill_urb hang issue for ohci controller. Signed-off-by: Aman Deep <aman.deep@xxxxxxxxxxx> --- drivers/usb/host/ohci-q.c | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/drivers/usb/host/ohci-q.c b/drivers/usb/host/ohci-q.c index f7d561e..90a24ab 100644 --- a/drivers/usb/host/ohci-q.c +++ b/drivers/usb/host/ohci-q.c @@ -481,13 +481,28 @@ done: */ static void start_ed_unlink (struct ohci_hcd *ohci, struct ed *ed) { + struct ed *tmp, *prev; + ed->hwINFO |= cpu_to_hc32 (ohci, ED_DEQUEUE); ed_deschedule (ohci, ed); - /* rm_list is just singly linked, for simplicity */ - ed->ed_next = ohci->ed_rm_list; - ed->ed_prev = NULL; - ohci->ed_rm_list = ed; + /* rm_list is just singly linked, for simplicity + ed is now added to end of ed_rm_list for ensuring that + during race condition, this new ed is accessed by finish_unlinks + */ + tmp = prev = ohci->ed_rm_list; + while (tmp != NULL) { + prev = tmp; + tmp = tmp->ed_next; + } + if (prev != NULL) { + prev->ed_next = ed; + ed->ed_prev = NULL; + } else { + ed->ed_next = ohci->ed_rm_list; + ohci->ed_rm_list = ed; + } + ed->ed_next = NULL; /* enable SOF interrupt */ ohci_writel (ohci, OHCI_INTR_SF, &ohci->regs->intrstatus); -- 1.7.9.5 ÿôèº{.nÇ+‰·Ÿ®‰†+%ŠËÿ±éݶ¥Šwÿº{.nÇ+‰·¥Š{±þëþ)í…æèw*jg¬±¨¶‰šŽŠÝ¢jÿ¾«þG«?éÿ¢¸¢·¦j:+v‰¨ŠwèjØm¶Ÿÿþø¯ù®w¥þŠàþf£¢·hš?â?úÿ†Ù¥