Commit 0c4391c9 authored by Alan Stern's avatar Alan Stern Committed by Greg Kroah-Hartman

[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.
parent a8a5d436
......@@ -95,6 +95,10 @@ static kmem_cache_t *uhci_up_cachep; /* urb_priv */
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 @@ static void uhci_remove_qh(struct uhci_hcd *uhci, struct uhci_qh *qh)
{
struct uhci_qh *pqh;
u32 newlink;
unsigned int age;
if (!qh)
return;
......@@ -425,6 +430,12 @@ static void uhci_remove_qh(struct uhci_hcd *uhci, struct uhci_qh *qh)
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 @@ static void uhci_destroy_urb_priv(struct uhci_hcd *uhci, struct urb *urb)
{
struct list_head *head, *tmp;
struct urb_priv *urbp;
unsigned int age;
urbp = (struct urb_priv *)urb->hcpriv;
if (!urbp)
......@@ -637,6 +649,12 @@ static void uhci_destroy_urb_priv(struct uhci_hcd *uhci, struct urb *urb)
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 @@ static int uhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb)
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 @@ static int uhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb)
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 @@ static void stall_callback(unsigned long ptr)
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 @@ static irqreturn_t uhci_irq(struct usb_hcd *hcd, struct pt_regs *regs)
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 @@ static irqreturn_t uhci_irq(struct usb_hcd *hcd, struct pt_regs *regs)
spin_lock(&uhci->schedule_lock);
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;
......
......@@ -357,12 +357,15 @@ struct uhci_hcd {
/* 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 */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment