Hi: The UHCI driver only cleans up removed QH/TD/URB entries in the interrupt routine. This means that if the interrupts aren't working for whatever reason (in my case it's a bug in suspend/resume), these entries will never be cleaned up. This can easily cause deadlocks if you unlink an URB and then wait for it to be given back.
This patch fixes this by making the unlink schedule a tasklet to do the cleanup rather than triggering an interrupt. Cheers, -- Debian GNU/Linux 3.0 is out! ( http://www.debian.org/ ) Email: Herbert Xu ~{PmV>HI~} <[EMAIL PROTECTED]> Home Page: http://gondor.apana.org.au/~herbert/ PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt
Index: kernel-2.5/drivers/usb/host/uhci-hcd.c =================================================================== RCS file: /home/gondolin/herbert/src/CVS/debian/kernel-source-2.5/drivers/usb/host/uhci-hcd.c,v retrieving revision 1.1.1.14 diff -u -r1.1.1.14 uhci-hcd.c --- kernel-2.5/drivers/usb/host/uhci-hcd.c 17 Oct 2003 21:43:29 -0000 1.1.1.14 +++ kernel-2.5/drivers/usb/host/uhci-hcd.c 6 Dec 2003 11:30:41 -0000 @@ -90,6 +90,7 @@ static int uhci_get_current_frame_number(struct uhci_hcd *uhci); static int uhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb); static void uhci_unlink_generic(struct uhci_hcd *uhci, struct urb *urb); +static void uhci_irq_tail(struct usb_hcd *hcd, struct pt_regs *regs); static void hc_state_transitions(struct uhci_hcd *uhci); @@ -112,11 +113,7 @@ */ static inline void uhci_set_next_interrupt(struct uhci_hcd *uhci) { - unsigned long flags; - - spin_lock_irqsave(&uhci->frame_list_lock, flags); - uhci->term_td->status |= cpu_to_le32(TD_CTRL_IOC); - spin_unlock_irqrestore(&uhci->frame_list_lock, flags); + tasklet_schedule(&uhci->irq_tasklet); } static inline void uhci_clear_next_interrupt(struct uhci_hcd *uhci) @@ -1905,7 +1902,6 @@ struct uhci_hcd *uhci = hcd_to_uhci(hcd); unsigned int io_addr = uhci->io_addr; unsigned short status; - struct list_head *tmp, *head; /* * Read the interrupt status, and write it back to clear the @@ -1930,14 +1926,22 @@ if (status & USBSTS_RD) uhci->resume_detect = 1; + uhci_clear_next_interrupt(uhci); + + uhci_irq_tail(hcd, regs); +} + +static void uhci_irq_tail(struct usb_hcd *hcd, struct pt_regs *regs) +{ + struct uhci_hcd *uhci = hcd_to_uhci(hcd); + struct list_head *tmp, *head; + uhci_free_pending_qhs(uhci); uhci_free_pending_tds(uhci); uhci_remove_pending_qhs(uhci); - uhci_clear_next_interrupt(uhci); - /* Walk the list of pending URB's to see which ones completed */ spin_lock(&uhci->urb_list_lock); head = &uhci->urb_list; @@ -1956,6 +1960,13 @@ uhci_finish_completion(hcd, regs); } +static void uhci_irq_tasklet(unsigned long data) +{ + struct usb_hcd *hcd = (void *)data; + + uhci_irq_tail(hcd, NULL); +} + static void reset_hc(struct uhci_hcd *uhci) { unsigned int io_addr = uhci->io_addr; @@ -2453,14 +2464,11 @@ * At this point, we're guaranteed that no new connects can be made * to this bus since there are no more parents */ - uhci_free_pending_qhs(uhci); - uhci_free_pending_tds(uhci); - uhci_remove_pending_qhs(uhci); reset_hc(uhci); - uhci_free_pending_qhs(uhci); - uhci_free_pending_tds(uhci); + tasklet_schedule(&uhci->irq_tasklet); + tasklet_kill(&uhci->irq_tasklet); release_uhci(uhci); } @@ -2505,6 +2513,8 @@ memset(uhci, 0, sizeof(*uhci)); uhci->hcd.product_desc = "UHCI Host Controller"; + tasklet_init(&uhci->irq_tasklet, uhci_irq_tasklet, + (unsigned long)&uhci->hcd); return &uhci->hcd; } Index: kernel-2.5/drivers/usb/host/uhci-hcd.h =================================================================== RCS file: /home/gondolin/herbert/src/CVS/debian/kernel-source-2.5/drivers/usb/host/uhci-hcd.h,v retrieving revision 1.1.1.4 diff -u -r1.1.1.4 uhci-hcd.h --- kernel-2.5/drivers/usb/host/uhci-hcd.h 27 Sep 2003 00:01:59 -0000 1.1.1.4 +++ kernel-2.5/drivers/usb/host/uhci-hcd.h 6 Dec 2003 10:38:25 -0000 @@ -3,6 +3,7 @@ #include <linux/list.h> #include <linux/usb.h> +#include <linux/interrupt.h> #define usb_packetid(pipe) (usb_pipein(pipe) ? USB_PID_IN : USB_PID_OUT) #define PIPE_DEVEP_MASK 0x0007ff00 @@ -366,6 +367,8 @@ int rh_numports; struct timer_list stall_timer; + + struct tasklet_struct irq_tasklet; }; struct urb_priv {