Skip to content

Commit

Permalink
---
Browse files Browse the repository at this point in the history
yaml
---
r: 181861
b: refs/heads/master
c: 6d19c00
h: refs/heads/master
i:
  181859: 124e53b
v: v3
  • Loading branch information
Alan Stern authored and Rafael J. Wysocki committed Feb 26, 2010
1 parent 3c8d214 commit aa31542
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 2 deletions.
2 changes: 1 addition & 1 deletion [refs]
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
---
refs/heads/master: a1e4d72cd3024999bfb6703092ea271438805c89
refs/heads/master: 6d19c009cc780c63de25a046509ebc9473809fd6
8 changes: 8 additions & 0 deletions trunk/drivers/usb/core/driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -1022,6 +1022,14 @@ static int usb_resume_device(struct usb_device *udev, pm_message_t msg)
goto done;
}

/* Non-root devices on a full/low-speed bus must wait for their
* companion high-speed root hub, in case a handoff is needed.
*/
if (!(msg.event & PM_EVENT_AUTO) && udev->parent &&
udev->bus->hs_companion)
device_pm_wait_for_dev(&udev->dev,
&udev->bus->hs_companion->root_hub->dev);

if (udev->quirks & USB_QUIRK_RESET_RESUME)
udev->reset_resume = 1;

Expand Down
127 changes: 126 additions & 1 deletion trunk/drivers/usb/core/hcd-pci.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/pm_runtime.h>
#include <linux/usb.h>

#include <asm/io.h>
Expand All @@ -37,6 +38,122 @@

/* PCI-based HCs are common, but plenty of non-PCI HCs are used too */

#ifdef CONFIG_PM_SLEEP

/* Coordinate handoffs between EHCI and companion controllers
* during system resume
*/

static DEFINE_MUTEX(companions_mutex);

#define CL_UHCI PCI_CLASS_SERIAL_USB_UHCI
#define CL_OHCI PCI_CLASS_SERIAL_USB_OHCI
#define CL_EHCI PCI_CLASS_SERIAL_USB_EHCI

enum companion_action {
SET_HS_COMPANION, CLEAR_HS_COMPANION, WAIT_FOR_COMPANIONS
};

static void companion_common(struct pci_dev *pdev, struct usb_hcd *hcd,
enum companion_action action)
{
struct pci_dev *companion;
struct usb_hcd *companion_hcd;
unsigned int slot = PCI_SLOT(pdev->devfn);

/* Iterate through other PCI functions in the same slot.
* If pdev is OHCI or UHCI then we are looking for EHCI, and
* vice versa.
*/
companion = NULL;
for (;;) {
companion = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, companion);
if (!companion)
break;
if (companion->bus != pdev->bus ||
PCI_SLOT(companion->devfn) != slot)
continue;

companion_hcd = pci_get_drvdata(companion);
if (!companion_hcd)
continue;

/* For SET_HS_COMPANION, store a pointer to the EHCI bus in
* the OHCI/UHCI companion bus structure.
* For CLEAR_HS_COMPANION, clear the pointer to the EHCI bus
* in the OHCI/UHCI companion bus structure.
* For WAIT_FOR_COMPANIONS, wait until the OHCI/UHCI
* companion controllers have fully resumed.
*/

if ((pdev->class == CL_OHCI || pdev->class == CL_UHCI) &&
companion->class == CL_EHCI) {
/* action must be SET_HS_COMPANION */
dev_dbg(&companion->dev, "HS companion for %s\n",
dev_name(&pdev->dev));
hcd->self.hs_companion = &companion_hcd->self;

} else if (pdev->class == CL_EHCI &&
(companion->class == CL_OHCI ||
companion->class == CL_UHCI)) {
switch (action) {
case SET_HS_COMPANION:
dev_dbg(&pdev->dev, "HS companion for %s\n",
dev_name(&companion->dev));
companion_hcd->self.hs_companion = &hcd->self;
break;
case CLEAR_HS_COMPANION:
companion_hcd->self.hs_companion = NULL;
break;
case WAIT_FOR_COMPANIONS:
device_pm_wait_for_dev(&pdev->dev,
&companion->dev);
break;
}
}
}
}

static void set_hs_companion(struct pci_dev *pdev, struct usb_hcd *hcd)
{
mutex_lock(&companions_mutex);
dev_set_drvdata(&pdev->dev, hcd);
companion_common(pdev, hcd, SET_HS_COMPANION);
mutex_unlock(&companions_mutex);
}

static void clear_hs_companion(struct pci_dev *pdev, struct usb_hcd *hcd)
{
mutex_lock(&companions_mutex);
dev_set_drvdata(&pdev->dev, NULL);

/* If pdev is OHCI or UHCI, just clear its hs_companion pointer */
if (pdev->class == CL_OHCI || pdev->class == CL_UHCI)
hcd->self.hs_companion = NULL;

/* Otherwise search for companion buses and clear their pointers */
else
companion_common(pdev, hcd, CLEAR_HS_COMPANION);
mutex_unlock(&companions_mutex);
}

static void wait_for_companions(struct pci_dev *pdev, struct usb_hcd *hcd)
{
/* Only EHCI controllers need to wait.
* No locking is needed because a controller cannot be resumed
* while one of its companions is getting unbound.
*/
if (pdev->class == CL_EHCI)
companion_common(pdev, hcd, WAIT_FOR_COMPANIONS);
}

#else /* !CONFIG_PM_SLEEP */

static inline void set_hs_companion(struct pci_dev *d, struct usb_hcd *h) {}
static inline void clear_hs_companion(struct pci_dev *d, struct usb_hcd *h) {}
static inline void wait_for_companions(struct pci_dev *d, struct usb_hcd *h) {}

#endif /* !CONFIG_PM_SLEEP */

/*-------------------------------------------------------------------------*/

Expand Down Expand Up @@ -123,7 +240,7 @@ int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
if (region == PCI_ROM_RESOURCE) {
dev_dbg(&dev->dev, "no i/o regions available\n");
retval = -EBUSY;
goto err1;
goto err2;
}
}

Expand All @@ -132,6 +249,7 @@ int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
retval = usb_add_hcd(hcd, dev->irq, IRQF_DISABLED | IRQF_SHARED);
if (retval != 0)
goto err4;
set_hs_companion(dev, hcd);
return retval;

err4:
Expand All @@ -142,6 +260,7 @@ int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
} else
release_region(hcd->rsrc_start, hcd->rsrc_len);
err2:
clear_hs_companion(dev, hcd);
usb_put_hcd(hcd);
err1:
pci_disable_device(dev);
Expand Down Expand Up @@ -180,6 +299,7 @@ void usb_hcd_pci_remove(struct pci_dev *dev)
} else {
release_region(hcd->rsrc_start, hcd->rsrc_len);
}
clear_hs_companion(dev, hcd);
usb_put_hcd(hcd);
pci_disable_device(dev);
}
Expand Down Expand Up @@ -344,6 +464,11 @@ static int resume_common(struct device *dev, bool hibernated)
clear_bit(HCD_FLAG_SAW_IRQ, &hcd->flags);

if (hcd->driver->pci_resume) {
/* This call should be made only during system resume,
* not during runtime resume.
*/
wait_for_companions(pci_dev, hcd);

retval = hcd->driver->pci_resume(hcd, hibernated);
if (retval) {
dev_err(dev, "PCI post-resume error %d!\n", retval);
Expand Down
1 change: 1 addition & 0 deletions trunk/include/linux/usb.h
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,7 @@ struct usb_bus {

struct usb_devmap devmap; /* device address allocation map */
struct usb_device *root_hub; /* Root hub */
struct usb_bus *hs_companion; /* Companion EHCI bus, if any */
struct list_head bus_list; /* list of busses */

int bandwidth_allocated; /* on this bus: how much of the time
Expand Down

0 comments on commit aa31542

Please sign in to comment.