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 /drivers/usb | |
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>
Diffstat (limited to 'drivers/usb')
-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, |