ChangeSet 1.1743.3.14, 2004/05/25 11:38:39-07:00, stern@rowland.harvard.edu [PATCH] USB UHCI: allow URBs to be unlinked when IRQs don't work A lot of people with USB controllers made by VIA have been suffering from the fact that these controllers stop working when they receive a babble packet. In particular, they stop generating interrupt requests. Since the UHCI driver relies on IRQs from the controller for proper timing and interlocking of unlink requests, this means that those broken controllers will hang the UHCI driver and drivers for any device connected through it. This patch, written by Herbert Xu, gives the UCHI driver the ability to manage the unlink procedure using timer interrupts as well as controller interrupts. (It also fixes a race in the UHCI irq handler.) Although it won't prevent the VIA controllers from flaking out when they encounter babble, at least now users will be able to rmmod the uhci-hcd driver and then reload it, restoring their systems back to normal operation (until the next babble!). P.S.: Herbert, there's one loose end I still want to tie up. When the controller isn't running (i.e., is suspended) the frame number won't change, but unlinks still need to work. It's a small point, not too likely to come up in normal usage. I'll fix it later on when I update the state-changing part of the driver. drivers/usb/host/uhci-hcd.c | 51 +++++++++++++++++++++++++++++++++++++++----- drivers/usb/host/uhci-hcd.h | 3 ++ 2 files changed, 49 insertions(+), 5 deletions(-) diff -Nru a/drivers/usb/host/uhci-hcd.c b/drivers/usb/host/uhci-hcd.c --- a/drivers/usb/host/uhci-hcd.c Fri May 28 14:40:46 2004 +++ b/drivers/usb/host/uhci-hcd.c Fri May 28 14:40:46 2004 @@ -95,6 +95,10 @@ 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_remove_pending_urbps(struct uhci_hcd *uhci); +static void uhci_finish_completion(struct usb_hcd *hcd, struct pt_regs *regs); +static void uhci_free_pending_qhs(struct uhci_hcd *uhci); +static void uhci_free_pending_tds(struct uhci_hcd *uhci); static void hc_state_transitions(struct uhci_hcd *uhci); @@ -373,6 +377,7 @@ { struct uhci_qh *pqh; u32 newlink; + unsigned int age; if (!qh) return; @@ -425,6 +430,12 @@ list_del_init(&qh->urbp->queue_list); qh->urbp = NULL; + age = uhci_get_current_frame_number(uhci); + if (age != uhci->qh_remove_age) { + uhci_free_pending_qhs(uhci); + uhci->qh_remove_age = age; + } + /* Check to see if the remove list is empty. Set the IOC bit */ /* to force an interrupt so we can remove the QH */ if (list_empty(&uhci->qh_remove_list)) @@ -628,6 +639,7 @@ { struct list_head *head, *tmp; struct urb_priv *urbp; + unsigned int age; urbp = (struct urb_priv *)urb->hcpriv; if (!urbp) @@ -637,6 +649,12 @@ dev_warn(uhci_dev(uhci), "urb %p still on uhci->urb_list " "or uhci->remove_list!\n", urb); + age = uhci_get_current_frame_number(uhci); + if (age != uhci->td_remove_age) { + uhci_free_pending_tds(uhci); + uhci->td_remove_age = age; + } + /* Check to see if the remove list is empty. Set the IOC bit */ /* to force an interrupt so we can remove the TD's*/ if (list_empty(&uhci->td_remove_list)) @@ -1512,6 +1530,7 @@ struct uhci_hcd *uhci = hcd_to_uhci(hcd); unsigned long flags; struct urb_priv *urbp; + unsigned int age; spin_lock_irqsave(&uhci->schedule_lock, flags); urbp = urb->hcpriv; @@ -1521,6 +1540,12 @@ uhci_unlink_generic(uhci, urb); + age = uhci_get_current_frame_number(uhci); + if (age != uhci->urb_remove_age) { + uhci_remove_pending_urbps(uhci); + uhci->urb_remove_age = age; + } + /* If we're the first, set the next interrupt bit */ if (list_empty(&uhci->urb_remove_list)) uhci_set_next_interrupt(uhci); @@ -1590,6 +1615,12 @@ INIT_LIST_HEAD(&list); spin_lock_irqsave(&uhci->schedule_lock, flags); + if (!list_empty(&uhci->urb_remove_list) && + uhci_get_current_frame_number(uhci) != uhci->urb_remove_age) { + uhci_remove_pending_urbps(uhci); + uhci_finish_completion(hcd, NULL); + } + head = &uhci->urb_list; tmp = head->next; while (tmp != head) { @@ -1728,6 +1759,7 @@ unsigned int io_addr = uhci->io_addr; unsigned short status; struct list_head *tmp, *head; + unsigned int age; /* * Read the interrupt status, and write it back to clear the @@ -1758,11 +1790,20 @@ spin_lock(&uhci->schedule_lock); - uhci_free_pending_qhs(uhci); - uhci_free_pending_tds(uhci); - uhci_remove_pending_urbps(uhci); - - uhci_clear_next_interrupt(uhci); + age = uhci_get_current_frame_number(uhci); + if (age != uhci->qh_remove_age) + uhci_free_pending_qhs(uhci); + if (age != uhci->td_remove_age) + uhci_free_pending_tds(uhci); + if (age != uhci->urb_remove_age) + uhci_remove_pending_urbps(uhci); + + if (list_empty(&uhci->urb_remove_list) && + list_empty(&uhci->td_remove_list) && + list_empty(&uhci->qh_remove_list)) + uhci_clear_next_interrupt(uhci); + else + uhci_set_next_interrupt(uhci); /* Walk the list of pending URB's to see which ones completed */ head = &uhci->urb_list; diff -Nru a/drivers/usb/host/uhci-hcd.h b/drivers/usb/host/uhci-hcd.h --- a/drivers/usb/host/uhci-hcd.h Fri May 28 14:40:46 2004 +++ b/drivers/usb/host/uhci-hcd.h Fri May 28 14:40:46 2004 @@ -357,12 +357,15 @@ /* List of QH's that are done, but waiting to be unlinked (race) */ struct list_head qh_remove_list; /* P: uhci->schedule_lock */ + unsigned int qh_remove_age; /* Age in frames */ /* List of TD's that are done, but waiting to be freed (race) */ struct list_head td_remove_list; /* P: uhci->schedule_lock */ + unsigned int td_remove_age; /* Age in frames */ /* List of asynchronously unlinked URB's */ struct list_head urb_remove_list; /* P: uhci->schedule_lock */ + unsigned int urb_remove_age; /* Age in frames */ /* List of URB's awaiting completion callback */ struct list_head complete_list; /* P: uhci->schedule_lock */