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.d...@samsung.com>
---
 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‹§²æìr¸›yúèšØb²X¬¶Ç§vØ^–)Þº{.nÇ+‰·¥Š{±ºÆâžØ^n‡r¡ö¦zË�ëh™¨è­Ú&¢ø®G«�éh®(­éšŽŠÝ¢j"�ú¶m§ÿï�êäz¹Þ–Šàþf£¢·hšˆ§~ˆmš

Reply via email to