USB runtime D3
Subsystems (6)
I've been playing with putting host controllers into D3 when the root hub is suspended, with a certain amount of success. My current implementation simply calls the suspend and resume functions in the HCD device's pm struct on bus_suspend and bus_resume. It's also necessary to flag the ACPI GPEs for the HCDs as runtime and enabled in order to get wakeup events. This is good enough for already plugged devices to work, and running lsusb powers up any idle ports and notices that devices have been plugged in. I also get an event when devices are unplugged.
This is obviously not entirely ideal, for a couple of reasons.
The first is that UHCI works perfectly providing I provide a remote wakeup. An SCI is fired via ACPI, a notification is sent to the appropriate device and we resume it quite happily. Unplugs also trigger the ACPI event. However, plugging in doesn't generate any kind of wakeup event, even though port0en and port1n are set in USB_RES. Is there any way to get UHCI to do this? I'm guessing that the power is being cut to the port when it suspends with no device connected.
The second is that EHCI generates a PME# notification on device plugin. This triggers an SCI and the PME# notification shows up as a GPE event. This causes the kernel to evaluate _L0D. On this Dell, that looks something like this:
_L0D {
SMI(foo, Local0)
if (Local0 & 0x1)
Notify(some hardware)
if (Local0 & 0x2)
Notify(EHCI)
if (Local0 & 0x4)
Notify(EHCI 2)
}However, Local0 ends up being 0 and no notification is fired. I'm therefore not able to acknowledge the PME and it fires again. This is obviously not ideal. Does anyone have any idea why this might be? The fact that this ends up in SMI code obviously makes things more awkward.
I've thought of a way that this could be made to work, but it's kind of hacky. A generic GPE handler could be registered and then scan all PCIe devices for a raised PME bit. It could then flag it and send a notification to the attached driver. EHCI could then force a rescan of all currently powered off USB devices.
The downside to this (other than it being a hack) is that I can't see an obvious way to work out what the appropriate GPE is. On Intel, internal PCI devices generate an event on GPE 0xd - but the PCI object only claims 0xb in its _PRW object.
Anyone have any ideas? I've attached the current version of my code, which is hacked up in various ways as I try to understand what's going on but should give some idea what I'm trying.
drivers/pci/pci-acpi.c +33 -0
@@ -120,12 +120,45 @@ static int acpi_pci_sleep_wake(struct pci_dev *dev, bool enable)return error; } +static int acpi_pci_runtime_wake (struct pci_dev *dev,bool enable) +{ +acpi_status status; +acpi_handle handle =DEVICE_ACPI_HANDLE (&dev->dev); +struct acpi_device *acpi_dev; + +if (!handle) { +printk ("Fail2\n" ); +return -ENODEV ; + } + + status =acpi_bus_get_device (handle, &acpi_dev); +if (ACPI_FAILURE (status)) { +printk ("Fail3\n" ); +return -ENODEV ; + } + +printk ("Setting GPE type\n" ); +acpi_set_gpe_type (acpi_dev->wakeup.gpe_device, + acpi_dev->wakeup.gpe_number,ACPI_GPE_TYPE_RUNTIME ); +if (enable) { +printk ("Enabling GPE\n" ); +acpi_enable_gpe (acpi_dev->wakeup.gpe_device, + acpi_dev->wakeup.gpe_number); + } + +printk ("Sleep/wake\n" ); +acpi_pm_device_sleep_wake (&dev->dev, enable); + +return 0 ; +} +static struct pci_platform_pm_ops acpi_pci_platform_pm = { .is_manageable = acpi_pci_power_manageable, .set_state = acpi_pci_set_power_state, .choose_state = acpi_pci_choose_state, .can_wakeup = acpi_pci_can_wakeup, .sleep_wake = acpi_pci_sleep_wake, + .runtime_wake = acpi_pci_runtime_wake, };/* ACPI bus type */
drivers/pci/pci.c +35 -0
@@ -428,6 +428,12 @@ static inline int platform_pci_sleep_wake(struct pci_dev *dev, bool enable) pci_platform_pm->sleep_wake(dev, enable) : -ENODEV; } +static inline int platform_pci_runtime_wake (struct pci_dev *dev,bool enable) +{ +return pci_platform_pm ? + pci_platform_pm->runtime_wake (dev, enable) : -ENODEV ; +} +/** * pci_raw_set_power_state - Use PCI PM registers to set the power state of * given PCI device
@@ -1215,6 +1221,8 @@ int pci_enable_wake(struct pci_dev *dev, pci_power_t state, bool enable)int error =0 ;bool pme_done = false; +return 0 ; +if (enable && !device_may_wakeup (&dev->dev))return -EINVAL ;
@@ -1239,6 +1247,33 @@ int pci_enable_wake(struct pci_dev *dev, pci_power_t state, bool enable) }/** +* pci_enable_runtime_wake - enable PCI device as runtime wakeup event source +* @dev: PCI device affected +* @enable: True to enable event generation; false to disable +* +* This enables the device as a runtime wakeup event source, or disables it. +* This typically requires platform support. +* +* RETURN VALUE: +* 0 is returned on success +* -EINVAL is returned if device is not supposed to wake up the system +* -ENODEV is returned if platform cannot support runtime PM on the device +*/ +int pci_enable_runtime_wake (struct pci_dev *dev,bool enable) +{ +int error =0 ; + +if (!platform_pci_can_wakeup (dev)) { +printk ("Fail1\n" ); +return -EINVAL ; + } + + error =platform_pci_runtime_wake (dev, enable); + +return error; +} + +/** * pci_wake_from_d3 - enable/disable device to wake up from D3_hot or D3_cold * @dev: PCI device to prepare * @enable: True to enable wake-up event generation; false to disable
drivers/pci/pci.h +3 -0
@@ -34,6 +34,8 @@ extern int pci_mmap_fits(struct pci_dev *pdev, int resno,* * @sleep_wake: enables/disables the system wake up capability of given device * +* @runtime_wake: enables/disables runtime wake up capability of given device +* * If given platform is generally capable of power managing PCI devices, all of * these callbacks are mandatory. */
@@ -43,6 +45,7 @@ struct pci_platform_pm_ops {pci_power_t (*choose_state)(struct pci_dev *dev);bool (*can_wakeup)(struct pci_dev *dev);int (*sleep_wake)(struct pci_dev *dev,bool enable); +int (*runtime_wake)(struct pci_dev *dev,bool enable); };extern int pci_set_platform_pm (struct pci_platform_pm_ops *ops);
drivers/usb/core/hcd-pci.c +7 -0
@@ -250,6 +250,11 @@ static int hcd_pci_suspend(struct device *dev)* link (except maybe for PME# resume signaling). We'll enter a * low power state during suspend_noirq, if the hardware allows. */ + +device_set_wakeup_capable (dev,1 ); +device_set_wakeup_enable (dev,1 ); +// pci_enable_wake(pci_dev, PCI_D3hot, 1); +pci_enable_runtime_wake (pci_dev,1 );pci_disable_device (pci_dev);return retval; }
@@ -319,6 +324,7 @@ static int hcd_pci_resume_noirq(struct device *dev)/* Go back to D0 and disable remote wakeup */ pci_back_from_sleep (pci_dev); +pci_pme_active (pci_dev,0 );return 0 ; }
@@ -333,6 +339,7 @@ static int resume_common(struct device *dev, bool hibernated)return 0 ; } +pci_enable_runtime_wake (pci_dev,0 ); retval =pci_enable_device (pci_dev);if (retval <0 ) {dev_err (dev,"can't re-enable after resume, %d!\n" , retval);
drivers/usb/core/hcd.c +99 -3
162 lines not on this page · raw message
drivers/usb/core/hcd.h +3 -0
@@ -195,6 +195,9 @@ struct hc_driver {/* shutdown HCD */ void (*shutdown) (struct usb_hcd *hcd); +/* does the HCD support waking at runtime ? */ +int (*runtime_wake) (struct usb_hcd *hcd); +/* return current frame number */ int (*get_frame_number) (struct usb_hcd *hcd);
drivers/usb/host/ehci-pci.c +7 -0
@@ -357,6 +357,12 @@ static int ehci_pci_resume(struct usb_hcd *hcd, bool hibernated) hcd->state =HC_STATE_SUSPENDED ;return 0 ; } + +static int ehci_pci_runtime_wake (struct usb_hcd *hcd) +{ +struct pci_dev *pdev =to_pci_dev (hcd->self.controller); +return pci_enable_runtime_wake (pdev,1 ); +}#endif static const struct hc_driver ehci_pci_hc_driver = {
@@ -378,6 +384,7 @@ static const struct hc_driver ehci_pci_hc_driver = {#ifdef CONFIG_PM .pci_suspend = ehci_pci_suspend, .pci_resume = ehci_pci_resume, + .runtime_wake = ehci_pci_runtime_wake, #endif .stop = ehci_stop, .shutdown = ehci_shutdown,
drivers/usb/host/ohci-pci.c +6 -0
@@ -414,6 +414,11 @@ static int ohci_pci_resume(struct usb_hcd *hcd, bool hibernated) return 0; } +static int ohci_pci_runtime_wake (struct usb_hcd *hcd) +{ +struct pci_dev *pdev =to_pci_dev (hcd->self.controller); +return pci_enable_runtime_wake (pdev,1 ); +}#endif /* CONFIG_PM */
@@ -441,6 +446,7 @@ static const struct hc_driver ohci_pci_hc_driver = {#ifdef CONFIG_PM .pci_suspend = ohci_pci_suspend, .pci_resume = ohci_pci_resume, + .runtime_wake = ohci_pci_runtime_wake,#endif /*
drivers/usb/host/uhci-hcd.c +7 -0
@@ -849,6 +849,12 @@ static int uhci_pci_resume(struct usb_hcd *hcd, bool hibernated) }return 0 ; } + +static int uhci_pci_runtime_wake (struct usb_hcd *hcd) +{ +struct pci_dev *pdev =to_pci_dev (hcd->self.controller); +return pci_enable_runtime_wake (pdev,1 ); +}#endif /* Wait until a particular device/endpoint's QH is idle, and free it */
@@ -910,6 +916,7 @@ static const struct hc_driver uhci_driver = { .pci_resume = uhci_pci_resume, .bus_suspend = uhci_rh_suspend, .bus_resume = uhci_rh_resume, + .runtime_wake = uhci_pci_runtime_wake, #endif .stop = uhci_stop,
include/linux/pci.h +1 -0
@@ -734,6 +734,7 @@ pci_power_t pci_choose_state(struct pci_dev *dev, pm_message_t state);bool pci_pme_capable (struct pci_dev *dev,pci_power_t state);void pci_pme_active (struct pci_dev *dev,bool enable);int pci_enable_wake (struct pci_dev *dev,pci_power_t state,bool enable); +int pci_enable_runtime_wake (struct pci_dev *dev,bool enable);int pci_wake_from_d3 (struct pci_dev *dev,bool enable);pci_power_t pci_target_state (struct pci_dev *dev);int pci_prepare_to_sleep (struct pci_dev *dev);
Signature · 1 line
Matthew Garrett | [email protected]
A message that is not in the archive (sent off-list or not yet received): [email protected]
Quoted 24 lines · Matthew Garrett
I've been playing with putting host controllers into D3 when the root hub is suspended, with a certain amount of success. My current implementation simply calls the suspend and resume functions in the HCD device's pm struct on
bus_suspendandbus_resume. It's also necessary to flag the ACPI GPEs for the HCDs as runtime and enabled in order to get wakeup events. This is good enough for already plugged devices to work, and running lsusb powers up any idle ports and notices that devices have been plugged in. I also get an event when devices are unplugged.This is obviously not entirely ideal, for a couple of reasons.
The first is that UHCI works perfectly providing I provide a remote wakeup. An SCI is fired via ACPI, a notification is sent to the appropriate device and we resume it quite happily. Unplugs also trigger the ACPI event. However, plugging in doesn't generate any kind of wakeup event, even though port0en and port1n are set in
USB_RES. Is there any way to get UHCI to do this? I'm guessing that the power is being cut to the port when it suspends with no device connected.The second is that EHCI generates a PME# notification on device plugin. This triggers an SCI and the PME# notification shows up as a GPE event. This causes the kernel to evaluate
_L0D. On this Dell, that looks something like this:
Quoted 9 lines
_L0D {
SMI(foo, Local0)
if (Local0 & 0x1)
Notify(some hardware)
if (Local0 & 0x2)
Notify(EHCI)
if (Local0 & 0x4)
Notify(EHCI 2)
}There are patches to support both ACPI based wakeup event and PCIe native wakeup event in below bugzilla, but it never gets into upstream. One problem is how to check which device under a bridge generates a wakeup event, because pci devices can share a wakeup event.
There are discussion in pm list about the patches too, please search.
http://bugzilla.kernel.org/show_bug.cgi?id=6892
Thanks,
Shaohua
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to [email protected] More majordomo info at http://vger.kernel.org/majordomo-info.html
Quoted 5 lines · Shaohua Li
There are patches to support both ACPI based wakeup event and PCIe native wakeup event in below bugzilla, but it never gets into upstream. One problem is how to check which device under a bridge generates a wakeup event, because pci devices can share a wakeup event.
There are discussion in pm list about the patches too, please search.
Yes, that would basically work for me. I'm still slightly curious as to why my hardware's not giving me the correct ACPI notification, though - I guess this may be entirely untested under Windows.
Signature · 1 line
Matthew Garrett | [email protected]
Quoted 11 lines · Matthew Garrett
Quoted 5 lines · Shaohua Li
There are patches to support both ACPI based wakeup event and PCIe native wakeup event in below bugzilla, but it never gets into upstream. One problem is how to check which device under a bridge generates a wakeup event, because pci devices can share a wakeup event.
There are discussion in pm list about the patches too, please search.Yes, that would basically work for me. I'm still slightly curious as to why my hardware's not giving me the correct ACPI notification, though - I guess this may be entirely untested under Windows.
IIRC, you don't need to know which GPE for wakeup. The GPE is already enabled.
And all required are just to register a notifier handler (register_acpi_bus_notifier), and check the ACPI_NOTIFY_DEVICE_WAKE event.
Quoted 6 lines · Shaohua Li
Quoted 3 lines · Matthew Garrett
Yes, that would basically work for me. I'm still slightly curious as to why my hardware's not giving me the correct ACPI notification, though - I guess this may be entirely untested under Windows.
IIRC, you don't need to know which GPE for wakeup. The GPE is already enabled.
And all required are just to register a notifier handler (register_acpi_bus_notifier), and check theACPI_NOTIFY_DEVICE_WAKEevent.
The problem is that the GPE method in the DSDT calls an SMI to work out which device generated the notification, and that SMI claims that none of them did. This means that no notification is sent. I may have to try to find someone in Dell to ask about this.
Signature · 1 line
Matthew Garrett | [email protected]
Alan Stern
maintainerQuoted 3 lines · Matthew Garrett
Yes, that would basically work for me. I'm still slightly curious as to why my hardware's not giving me the correct ACPI notification, though - I guess this may be entirely untested under Windows.
To put it mildly...
Alan Stern
A message that is not in the archive (sent off-list or not yet received): Pine.LNX.4.44L0.0907301458090.6087-100000-IYeN2dnnYyZXsRXLowluHWD2FQJk+8+b@public.gmane.org
Quoted 7 lines · Alan Stern
Quoted 3 lines · Matthew Garrett
Yes, that would basically work for me. I'm still slightly curious as to why my hardware's not giving me the correct ACPI notification, though - I guess this may be entirely untested under Windows.
To put it mildly...
Windows doesn't D3 the host?
Signature · 5 lines
Matthew Garrett | [email protected] -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to [email protected] More majordomo info at http://vger.kernel.org/majordomo-info.html
A message that is not in the archive (sent off-list or not yet received): [email protected]
Quoted 10 lines · Matthew Garrett
Quoted 7 lines · Alan Stern
Quoted 3 lines · Matthew Garrett
Yes, that would basically work for me. I'm still slightly curious as to why my hardware's not giving me the correct ACPI notification, though - I guess this may be entirely untested under Windows.
To put it mildly...
Windows doesn't D3 the host?
As far as I know, Windows doesn't even suspend the root hub. However my knowledge of Windows internals is out of date by at least 10 years. Thank goodness... :-)
Alan Stern
P.S.: Besides which, you can't put these Intel UHCI controllers into D3 because they don't support PCI PM. Controllers from other vendors (i.e., VIA) do.
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to [email protected] More majordomo info at http://vger.kernel.org/majordomo-info.html
Quoted 3 lines · Alan Stern
P.S.: Besides which, you can't put these Intel UHCI controllers into D3 because they don't support PCI PM. Controllers from other vendors (i.e., VIA) do.
There's an out of band wakeup mechanism via ACPI for the Intel ones. It works for remote wakeups and disconnects - it's the connects I'm having trouble with. Given that all PCI PM events are delivered via ACPI on standard x86, the only difference it makes from an implementation viewpoint is whether or not you have to enable the PME bits.
Signature · 1 line
Matthew Garrett | [email protected]
Alan Stern
maintainerQuoted 11 lines · Matthew Garrett
I've been playing with putting host controllers into D3 when the root hub is suspended, with a certain amount of success. My current implementation simply calls the suspend and resume functions in the HCD device's pm struct on
bus_suspendandbus_resume. It's also necessary to flag the ACPI GPEs for the HCDs as runtime and enabled in order to get wakeup events. This is good enough for already plugged devices to work, and running lsusb powers up any idle ports and notices that devices have been plugged in. I also get an event when devices are unplugged.This is obviously not entirely ideal, for a couple of reasons.
Quoted 7 lines
The first is that UHCI works perfectly providing I provide a remote wakeup. An SCI is fired via ACPI, a notification is sent to the appropriate device and we resume it quite happily. Unplugs also trigger the ACPI event. However, plugging in doesn't generate any kind of wakeup event, even though port0en and port1n are set in
USB_RES. Is there any way to get UHCI to do this? I'm guessing that the power is being cut to the port when it suspends with no device connected.
No, that doesn't sound right. On the other hand, there's only one way to find out for certain. Voltmeters do come in handy at times...
UHCI isn't very flexible or configurable. It doesn't sound like there's anything more you can do about this.
Quoted 15 lines
The second is that EHCI generates a PME# notification on device plugin. This triggers an SCI and the PME# notification shows up as a GPE event. This causes the kernel to evaluate
_L0D. On this Dell, that looks something like this:_L0D { SMI(foo, Local0) if (Local0 & 0x1) Notify(some hardware) if (Local0 & 0x2) Notify(EHCI) if (Local0 & 0x4) Notify(EHCI 2) }
Quoted 8 lines
However, Local0 ends up being 0 and no notification is fired. I'm therefore not able to acknowledge the PME and it fires again. This is obviously not ideal. Does anyone have any idea why this might be? The fact that this ends up in SMI code obviously makes things more awkward.
I've thought of a way that this could be made to work, but it's kind of hacky. A generic GPE handler could be registered and then scan all PCIe devices for a raised PME bit.
Something like that is probably needed anyway -- for all PCI devices, not just PCIe. You have to do this on systems that support PCI-PM but don't have ACPI.
Quoted 3 lines
It could then flag it and send a
notification to the attached driver. EHCI could then force a rescan of all currently powered off USB devices.
It should send the notification for each such device to the bus subsystem (i.e., the PCI core). The bus is then responsible for telling the drivers what to do.
Quoted 8 lines
The downside to this (other than it being a hack) is that I can't see an obvious way to work out what the appropriate GPE is. On Intel, internal PCI devices generate an event on GPE
0xd- but the PCI object only claims0xbin its_PRWobject.Anyone have any ideas? I've attached the current version of my code, which is hacked up in various ways as I try to understand what's going on but should give some idea what I'm trying.
Ugh... Hacked is right. I can't comment on the ACPI stuff, but the USB parts are a mess. You put stuff in the USB core that really belongs in the PCI core and you put stuff in the kernel that belongs in userspace. Of course, there's nothing wrong with doing this as part of a "proof-of-principle" thing.
Alan Stern
A message that is not in the archive (sent off-list or not yet received): Pine.LNX.4.44L0.0907301447080.6087-100000-IYeN2dnnYyZXsRXLowluHWD2FQJk+8+b@public.gmane.org
Quoted 6 lines · Alan Stern
Quoted 3 lines
event, even though port0en and port1n are set in
USB_RES. Is there any way to get UHCI to do this? I'm guessing that the power is being cut to the port when it suspends with no device connected.No, that doesn't sound right. On the other hand, there's only one way to find out for certain. Voltmeters do come in handy at times...
Thinking about it, I get plug events from EHCI, so it can't be that the port is powered down. Maybe it's something to do with the port switching logic.
Quoted 2 lines
UHCI isn't very flexible or configurable. It doesn't sound like there's anything more you can do about this.
Which possibly rules out the ability to do this.
Quoted 7 lines
Quoted 3 lines
I've thought of a way that this could be made to work, but it's kind of hacky. A generic GPE handler could be registered and then scan all PCIe devices for a raised PME bit.
Something like that is probably needed anyway -- for all PCI devices, not just PCIe. You have to do this on systems that support PCI-PM but don't have ACPI.
Yes, they'll need to have their own platform hook into this.
Quoted 9 lines
Quoted 3 lines
Anyone have any ideas? I've attached the current version of my code, which is hacked up in various ways as I try to understand what's going on but should give some idea what I'm trying.
Ugh... Hacked is right. I can't comment on the ACPI stuff, but the USB parts are a mess. You put stuff in the USB core that really belongs in the PCI core and you put stuff in the kernel that belongs in userspace. Of course, there's nothing wrong with doing this as part of a "proof-of-principle" thing.
Yes, I guess the correct model is for this to be in PCI and have the USB code do nothing other than indicate that the PCI device is now idle.
Signature · 5 lines
Matthew Garrett | [email protected] -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to [email protected] More majordomo info at http://vger.kernel.org/majordomo-info.html
Alan Stern
maintainerQuoted 11 lines · Matthew Garrett
Quoted 6 lines · Alan Stern
Quoted 3 lines
event, even though port0en and port1n are set in
USB_RES. Is there any way to get UHCI to do this? I'm guessing that the power is being cut to the port when it suspends with no device connected.No, that doesn't sound right. On the other hand, there's only one way to find out for certain. Voltmeters do come in handy at times...
Thinking about it, I get plug events from EHCI, so it can't be that the port is powered down. Maybe it's something to do with the port switching logic.
Seems likely. What happens if you suspend the UHCI controller but leave the EHCI controller active? Then the port-switching logic should kick in.
Quoted 4 lines
Quoted 4 lines
Quoted 3 lines
Anyone have any ideas? I've attached the current version of my code, which is hacked up in various ways as I try to understand what's going on but should give some idea what I'm trying.
Quoted 8 lines
Quoted 5 lines
Ugh... Hacked is right. I can't comment on the ACPI stuff, but the USB parts are a mess. You put stuff in the USB core that really belongs in the PCI core and you put stuff in the kernel that belongs in userspace. Of course, there's nothing wrong with doing this as part of a "proof-of-principle" thing.
Yes, I guess the correct model is for this to be in PCI and have the USB code do nothing other than indicate that the PCI device is now idle.
Which reminds me... One of the things you had to do was enable remote wakeup for the host controllers. The current initial state is disabled, for a good reason. People don't like it if they suspend their laptop only to find that the computer wakes back up again when they unplug the USB mouse.
If we do end up implementing runtime power management for USB host controllers, something (a userspace program?) will have to turn off remote wakeup before system sleeps and turn it back on afterward.
Alan Stern
Quoted 3 lines · Alan Stern
Seems likely. What happens if you suspend the UHCI controller but leave the EHCI controller active? Then the port-switching logic should kick in.
Yeah, I'll give that a go.
Quoted 9 lines
Which reminds me... One of the things you had to do was enable remote wakeup for the host controllers. The current initial state is disabled, for a good reason. People don't like it if they suspend their laptop only to find that the computer wakes back up again when they unplug the USB mouse.
If we do end up implementing runtime power management for USB host controllers, something (a userspace program?) will have to turn off remote wakeup before system sleeps and turn it back on afterward.
I think we'll probably want a call for "transitioning from runtime suspend to system suspend", at which point the driver can clean that up itself.
Signature · 1 line
Matthew Garrett | [email protected]