diff options
| author | Alan Stern <stern@rowland.harvard.edu> | 2007-10-10 16:27:07 -0400 |
|---|---|---|
| committer | Greg Kroah-Hartman <gregkh@suse.de> | 2007-10-12 17:55:34 -0400 |
| commit | 32fe01985aa2cb2562f6fc171e526e279abe10db (patch) | |
| tree | 0c1865a1d3e91ae0839147430480c4099b16a06f | |
| parent | 17f060224fb9f435c6f9306b7b61419d038def13 (diff) | |
USB: mutual exclusion for EHCI init and port resets
This patch (as999) fixes a problem that sometimes shows up when host
controller driver modules are loaded in the wrong order. If ehci-hcd
happens to initialize an EHCI controller while the companion OHCI or
UHCI controller is in the middle of a port reset, the reset can fail
and the companion may get very confused. The patch adds an
rw-semaphore and uses it to keep EHCI initialization and port resets
mutually exclusive.
Signed-off-by: Alan Stern <stern@rowland.harvard.edu>
Acked-by: David Brownell <david-b@pacbell.net>
Cc: David Miller <davem@davemloft.net>
Cc: Dely L Sy <dely.l.sy@intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
| -rw-r--r-- | drivers/usb/core/hcd.h | 8 | ||||
| -rw-r--r-- | drivers/usb/core/hub.c | 15 | ||||
| -rw-r--r-- | drivers/usb/host/ehci-hcd.c | 8 |
3 files changed, 29 insertions, 2 deletions
diff --git a/drivers/usb/core/hcd.h b/drivers/usb/core/hcd.h index 1396141274f1..98e24194a4ab 100644 --- a/drivers/usb/core/hcd.h +++ b/drivers/usb/core/hcd.h | |||
| @@ -19,6 +19,8 @@ | |||
| 19 | 19 | ||
| 20 | #ifdef __KERNEL__ | 20 | #ifdef __KERNEL__ |
| 21 | 21 | ||
| 22 | #include <linux/rwsem.h> | ||
| 23 | |||
| 22 | /* This file contains declarations of usbcore internals that are mostly | 24 | /* This file contains declarations of usbcore internals that are mostly |
| 23 | * used or exposed by Host Controller Drivers. | 25 | * used or exposed by Host Controller Drivers. |
| 24 | */ | 26 | */ |
| @@ -470,5 +472,9 @@ static inline void usbmon_urb_complete(struct usb_bus *bus, struct urb *urb, | |||
| 470 | : (in_interrupt () ? "in_interrupt" : "can sleep")) | 472 | : (in_interrupt () ? "in_interrupt" : "can sleep")) |
| 471 | 473 | ||
| 472 | 474 | ||
| 473 | #endif /* __KERNEL__ */ | 475 | /* This rwsem is for use only by the hub driver and ehci-hcd. |
| 476 | * Nobody else should touch it. | ||
| 477 | */ | ||
| 478 | extern struct rw_semaphore ehci_cf_port_reset_rwsem; | ||
| 474 | 479 | ||
| 480 | #endif /* __KERNEL__ */ | ||
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 481dca641ea2..d20cb545a6e4 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c | |||
| @@ -125,6 +125,12 @@ MODULE_PARM_DESC(use_both_schemes, | |||
| 125 | "try the other device initialization scheme if the " | 125 | "try the other device initialization scheme if the " |
| 126 | "first one fails"); | 126 | "first one fails"); |
| 127 | 127 | ||
| 128 | /* Mutual exclusion for EHCI CF initialization. This interferes with | ||
| 129 | * port reset on some companion controllers. | ||
| 130 | */ | ||
| 131 | DECLARE_RWSEM(ehci_cf_port_reset_rwsem); | ||
| 132 | EXPORT_SYMBOL_GPL(ehci_cf_port_reset_rwsem); | ||
| 133 | |||
| 128 | 134 | ||
| 129 | static inline char *portspeed(int portstatus) | 135 | static inline char *portspeed(int portstatus) |
| 130 | { | 136 | { |
| @@ -1581,6 +1587,11 @@ static int hub_port_reset(struct usb_hub *hub, int port1, | |||
| 1581 | { | 1587 | { |
| 1582 | int i, status; | 1588 | int i, status; |
| 1583 | 1589 | ||
| 1590 | /* Block EHCI CF initialization during the port reset. | ||
| 1591 | * Some companion controllers don't like it when they mix. | ||
| 1592 | */ | ||
| 1593 | down_read(&ehci_cf_port_reset_rwsem); | ||
| 1594 | |||
| 1584 | /* Reset the port */ | 1595 | /* Reset the port */ |
| 1585 | for (i = 0; i < PORT_RESET_TRIES; i++) { | 1596 | for (i = 0; i < PORT_RESET_TRIES; i++) { |
| 1586 | status = set_port_feature(hub->hdev, | 1597 | status = set_port_feature(hub->hdev, |
| @@ -1612,7 +1623,7 @@ static int hub_port_reset(struct usb_hub *hub, int port1, | |||
| 1612 | usb_set_device_state(udev, status | 1623 | usb_set_device_state(udev, status |
| 1613 | ? USB_STATE_NOTATTACHED | 1624 | ? USB_STATE_NOTATTACHED |
| 1614 | : USB_STATE_DEFAULT); | 1625 | : USB_STATE_DEFAULT); |
| 1615 | return status; | 1626 | goto done; |
| 1616 | } | 1627 | } |
| 1617 | 1628 | ||
| 1618 | dev_dbg (hub->intfdev, | 1629 | dev_dbg (hub->intfdev, |
| @@ -1625,6 +1636,8 @@ static int hub_port_reset(struct usb_hub *hub, int port1, | |||
| 1625 | "Cannot enable port %i. Maybe the USB cable is bad?\n", | 1636 | "Cannot enable port %i. Maybe the USB cable is bad?\n", |
| 1626 | port1); | 1637 | port1); |
| 1627 | 1638 | ||
| 1639 | done: | ||
| 1640 | up_read(&ehci_cf_port_reset_rwsem); | ||
| 1628 | return status; | 1641 | return status; |
| 1629 | } | 1642 | } |
| 1630 | 1643 | ||
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index db00492588b6..c1514442883e 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c | |||
| @@ -570,10 +570,18 @@ static int ehci_run (struct usb_hcd *hcd) | |||
| 570 | * are explicitly handed to companion controller(s), so no TT is | 570 | * are explicitly handed to companion controller(s), so no TT is |
| 571 | * involved with the root hub. (Except where one is integrated, | 571 | * involved with the root hub. (Except where one is integrated, |
| 572 | * and there's no companion controller unless maybe for USB OTG.) | 572 | * and there's no companion controller unless maybe for USB OTG.) |
| 573 | * | ||
| 574 | * Turning on the CF flag will transfer ownership of all ports | ||
| 575 | * from the companions to the EHCI controller. If any of the | ||
| 576 | * companions are in the middle of a port reset at the time, it | ||
| 577 | * could cause trouble. Write-locking ehci_cf_port_reset_rwsem | ||
| 578 | * guarantees that no resets are in progress. | ||
| 573 | */ | 579 | */ |
| 580 | down_write(&ehci_cf_port_reset_rwsem); | ||
| 574 | hcd->state = HC_STATE_RUNNING; | 581 | hcd->state = HC_STATE_RUNNING; |
| 575 | ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag); | 582 | ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag); |
| 576 | ehci_readl(ehci, &ehci->regs->command); /* unblock posted writes */ | 583 | ehci_readl(ehci, &ehci->regs->command); /* unblock posted writes */ |
| 584 | up_write(&ehci_cf_port_reset_rwsem); | ||
| 577 | 585 | ||
| 578 | temp = HC_VERSION(ehci_readl(ehci, &ehci->caps->hc_capbase)); | 586 | temp = HC_VERSION(ehci_readl(ehci, &ehci->caps->hc_capbase)); |
| 579 | ehci_info (ehci, | 587 | ehci_info (ehci, |
