[RFC][PATCH 3/4] PCI / ACPI PM: Platform support for PCI PME wake-up

From: Rafael J. Wysocki
Date: Sun Sep 13 2009 - 17:25:36 EST


From: Rafael J. Wysocki <rjw@xxxxxxx>

Although the majority of PCI devices can generate PMEs that in
principle may be used to wake up devices suspended at run time,
platform support is generally necessary to convert PMEs into wake-up
events that can be delivered to the kernel. If ACPI is used for this
purpose, a PME generated by a PCI device will trigger the ACPI GPE
associated with the device to generate an ACPI wake-up event that we
can set up a handler for, provided that everything is configured
correctly.

Unfortunately, the subset of PCI devices that have GPEs associated
with them is quite limited and the other devices have to rely on
the GPEs associated with their upstream bridges and, possibly, the
root bridge to generate ACPI wake-up events in response to PMEs from
them.

Add ACPI platform support for PCI PME wake-up:
o Add function acpi_device_run_wake() allowing us to enable or
disable the GPE associated with given device to generate run-time
wake-up events.
o Add a new PCI platform callback ->run_wake() to struct
pci_platform_pm_ops allowing us to enable the platform to generate
wake-up events for given device.
o Implemet that callback for the ACPI platform using
acpi_device_run_wake().
o Define ACPI wake-up handlers for PCI devices and PCI buses and make
the PCI-ACPI binding code install wake-up notifiers for devices
associated with GPEs that can be used for wake-up.
o Add function pci_platform_run_wake() that can be used to enable (or
disable) the platform to generate wake-up events for given PCI
device using the ->run_wake() platform callback.

Based on a patch from Matthew Garrett.

Signed-off-by: Rafael J. Wysocki <rjw@xxxxxxx>
---
drivers/acpi/pci_bind.c | 6 ++++++
drivers/acpi/pci_root.c | 5 +++++
drivers/acpi/sleep.c | 4 ++--
drivers/acpi/wakeup.c | 47 +++++++++++++++++++++++++++++++++++++++++++++++
drivers/pci/pci-acpi.c | 42 ++++++++++++++++++++++++++++++++++++++++++
drivers/pci/pci.c | 41 +++++++++++++++++++++++++++++++++++++++++
drivers/pci/pci.h | 14 +++++++++++---
include/acpi/acpi_bus.h | 13 ++++++++++---
include/linux/pci-acpi.h | 3 +++
kernel/power/Kconfig | 5 +++++
10 files changed, 172 insertions(+), 8 deletions(-)

Index: linux-2.6/drivers/pci/pci.h
===================================================================
--- linux-2.6.orig/drivers/pci/pci.h
+++ linux-2.6/drivers/pci/pci.h
@@ -29,10 +29,14 @@ extern int pci_mmap_fits(struct pci_dev
* platform; to be used during system-wide transitions from a
* sleeping state to the working state and vice versa
*
- * @can_wakeup: returns 'true' if given device is capable of waking up the
- * system from a sleeping state
+ * @can_wakeup: returns 'true' if the plaform can generate wake-up events for
+ * given device.
*
- * @sleep_wake: enables/disables the system wake up capability of given device
+ * @sleep_wake: enables/disables the wake-up capability of given device
+ *
+ * @run_wake: enables/disables the platform to generate run-time wake-up events
+ * for given device (the device's wake-up capability has to be
+ * enabled by @sleep_wake for this feature to work)
*
* If given platform is generally capable of power managing PCI devices, all of
* these callbacks are mandatory.
@@ -43,12 +47,16 @@ 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 (*run_wake)(struct pci_dev *dev, bool enable);
};

extern int pci_set_platform_pm(struct pci_platform_pm_ops *ops);
+extern int pci_platform_run_wake(struct pci_dev *dev, bool enable);
extern void pci_update_current_state(struct pci_dev *dev, pci_power_t state);
extern void pci_disable_enabled_device(struct pci_dev *dev);
extern bool pci_check_pme_status(struct pci_dev *dev);
+extern void pci_pme_wakeup_bus(struct pci_bus *bus);
+extern void pci_pme_wakeup(struct pci_dev *dev);
extern void pci_pm_init(struct pci_dev *dev);
extern void platform_pci_wakeup_init(struct pci_dev *dev);
extern void pci_allocate_cap_save_buffers(struct pci_dev *dev);
Index: linux-2.6/drivers/pci/pci-acpi.c
===================================================================
--- linux-2.6.orig/drivers/pci/pci-acpi.c
+++ linux-2.6/drivers/pci/pci-acpi.c
@@ -18,6 +18,18 @@
#include <linux/pci-acpi.h>
#include "pci.h"

+void pci_acpi_bus_wakeup(acpi_handle handle, u32 event, void *data)
+{
+ if (event == ACPI_NOTIFY_DEVICE_WAKE)
+ pci_pme_wakeup_bus(to_pci_bus(data));
+}
+
+void pci_acpi_device_wakeup(acpi_handle handle, u32 event, void *data)
+{
+ if (event == ACPI_NOTIFY_DEVICE_WAKE)
+ pci_pme_wakeup(to_pci_dev(data));
+}
+
/*
* _SxD returns the D-state with the highest power
* (lowest D-state number) supported in the S-state "x".
@@ -137,12 +149,42 @@ static int acpi_pci_sleep_wake(struct pc
return 0;
}

+static void acpi_pci_propagate_run_wake(struct pci_bus *bus, bool enable)
+{
+ while (bus->parent) {
+ struct pci_dev *bridge = bus->self;
+
+ if (pcie_pme_enabled(bridge))
+ return;
+ if (!acpi_device_run_wake(&bridge->dev, enable))
+ return;
+ bus = bus->parent;
+ }
+
+ /* We have reached the root bus. */
+ if (bus->bridge)
+ acpi_device_run_wake(bus->bridge, enable);
+}
+
+static int acpi_pci_run_wake(struct pci_dev *dev, bool enable)
+{
+ if (pcie_pme_enabled(dev))
+ return 0;
+
+ if (acpi_pci_can_wakeup(dev))
+ return acpi_device_run_wake(&dev->dev, enable);
+
+ acpi_pci_propagate_run_wake(dev->bus, 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,
+ .run_wake = acpi_pci_run_wake,
};

/* ACPI bus type */
Index: linux-2.6/drivers/pci/pci.c
===================================================================
--- linux-2.6.orig/drivers/pci/pci.c
+++ linux-2.6/drivers/pci/pci.c
@@ -21,6 +21,7 @@
#include <linux/interrupt.h>
#include <asm/dma.h> /* isa_dma_bridge_buggy */
#include <linux/device.h>
+#include <linux/pm_runtime.h>
#include <asm/setup.h>
#include "pci.h"

@@ -428,6 +429,12 @@ static inline int platform_pci_sleep_wak
pci_platform_pm->sleep_wake(dev, enable) : -ENODEV;
}

+int pci_platform_run_wake(struct pci_dev *dev, bool enable)
+{
+ return pci_platform_pm ?
+ pci_platform_pm->run_wake(dev, enable) : -ENODEV;
+}
+
/**
* pci_raw_set_power_state - Use PCI PM registers to set the power state of
* given PCI device
@@ -1195,6 +1202,40 @@ bool pci_check_pme_status(struct pci_dev
return ret;
}

+static int pci_pme_wakeup_cb(struct pci_dev *dev, void *ign)
+{
+ if (pci_check_pme_status(dev))
+ pm_request_resume(&dev->dev);
+ return 0;
+}
+
+/**
+ * pci_pme_wakeup_bus - Walk given bus and wake up devices on it, if necessary.
+ * @bus: Top bus of the subtree to walk.
+ */
+void pci_pme_wakeup_bus(struct pci_bus *bus)
+{
+ pci_walk_bus(bus, pci_pme_wakeup_cb, NULL);
+}
+
+/**
+ * pci_pme_wakeup - Wake up a device and devices under it if it is a bridge.
+ * @dev: Device to handle.
+ *
+ * Check if @dev has generated PME and queue a resume request for it in that
+ * case. If @dev is a bridge, walk the bus under it, check if there are any
+ * devices that generated PME on this bus and queue resume requests for them
+ * (asynchronous resume is used, because we need to clear PME status bits for
+ * all devices before resuming any of them).
+ */
+void pci_pme_wakeup(struct pci_dev *dev)
+{
+ if (pci_check_pme_status(dev))
+ pm_request_resume(&dev->dev);
+ if (dev->subordinate)
+ pci_pme_wakeup_bus(dev->subordinate);
+}
+
/**
* pci_pme_capable - check the capability of PCI device to generate PME#
* @dev: PCI device to handle.
Index: linux-2.6/drivers/acpi/wakeup.c
===================================================================
--- linux-2.6.orig/drivers/acpi/wakeup.c
+++ linux-2.6/drivers/acpi/wakeup.c
@@ -124,6 +124,53 @@ void acpi_disable_wakeup_device(u8 sleep
}
}

+#ifdef CONFIG_PM_WAKEUP
+/**
+ * acpi_device_run_wake - Enable/disable ACPI BIOS to generate wake-up events.
+ * @phys_dev: Device to generate the wake-up events for.
+ * @enable: Desired action.
+ *
+ * If @enable is set, set up the GPE associated with @phys_dev to generate
+ * wake-up events at run time. If @enable is unset, disable the GPE associated
+ * with @phys_dev (unless it is marked as a run-wake device).
+ */
+int acpi_device_run_wake(struct device *phys_dev, bool enable)
+{
+ struct acpi_device *dev;
+ acpi_handle handle;
+
+ handle = DEVICE_ACPI_HANDLE(phys_dev);
+ if (!handle || ACPI_FAILURE(acpi_bus_get_device(handle, &dev))) {
+ dev_dbg(phys_dev, "ACPI context not found in %s!\n", __func__);
+ return -ENODEV;
+ }
+
+ if (!dev->wakeup.flags.valid)
+ return -EINVAL;
+
+ if (enable) {
+ if (!dev->wakeup.state.enabled && !dev->wakeup.prepare_count)
+ return -EINVAL;
+
+ acpi_set_gpe_type(dev->wakeup.gpe_device,
+ dev->wakeup.gpe_number,
+ ACPI_GPE_TYPE_WAKE_RUN);
+ acpi_enable_gpe(dev->wakeup.gpe_device,
+ dev->wakeup.gpe_number);
+ } else if (!dev->wakeup.flags.run_wake) {
+ acpi_set_gpe_type(dev->wakeup.gpe_device,
+ dev->wakeup.gpe_number,
+ ACPI_GPE_TYPE_WAKE);
+ acpi_disable_gpe(dev->wakeup.gpe_device,
+ dev->wakeup.gpe_number);
+ acpi_clear_gpe(dev->wakeup.gpe_device,
+ dev->wakeup.gpe_number, ACPI_NOT_ISR);
+ }
+
+ return 0;
+}
+#endif /* CONFIG_PM_WAKEUP */
+
int __init acpi_wakeup_device_init(void)
{
struct list_head *node, *next;
Index: linux-2.6/include/acpi/acpi_bus.h
===================================================================
--- linux-2.6.orig/include/acpi/acpi_bus.h
+++ linux-2.6/include/acpi/acpi_bus.h
@@ -375,21 +375,28 @@ int acpi_is_root_bridge(acpi_handle);
acpi_handle acpi_get_pci_rootbridge_handle(unsigned int, unsigned int);
#define DEVICE_ACPI_HANDLE(dev) ((acpi_handle)((dev)->archdata.acpi_handle))

-#ifdef CONFIG_PM_SLEEP
+#ifdef CONFIG_PM_WAKEUP
int acpi_pm_device_sleep_state(struct device *, int *);
int acpi_pm_device_sleep_wake(struct device *, bool);
-#else /* !CONFIG_PM_SLEEP */
+int acpi_device_run_wake(struct device *, bool);
+#else /* !CONFIG_PM_WAKEUP */
static inline int acpi_pm_device_sleep_state(struct device *d, int *p)
{
if (p)
*p = ACPI_STATE_D0;
return ACPI_STATE_D3;
}
+
static inline int acpi_pm_device_sleep_wake(struct device *dev, bool enable)
{
return -ENODEV;
}
-#endif /* !CONFIG_PM_SLEEP */
+
+static inline int acpi_device_run_wake(struct device *dev, bool enable)
+{
+ return -ENODEV;
+}
+#endif /* !CONFIG_PM_WAKEUP */

#endif /* CONFIG_ACPI */

Index: linux-2.6/kernel/power/Kconfig
===================================================================
--- linux-2.6.orig/kernel/power/Kconfig
+++ linux-2.6/kernel/power/Kconfig
@@ -236,3 +236,8 @@ config PM_RUNTIME
and the bus type drivers of the buses the devices are on are
responsible for the actual handling of the autosuspend requests and
wake-up events.
+
+config PM_WAKEUP
+ bool
+ depends on SUSPEND || HIBERNATION || PM_RUNTIME
+ default y
Index: linux-2.6/drivers/acpi/sleep.c
===================================================================
--- linux-2.6.orig/drivers/acpi/sleep.c
+++ linux-2.6/drivers/acpi/sleep.c
@@ -594,7 +594,7 @@ int acpi_suspend(u32 acpi_state)
return -EINVAL;
}

-#ifdef CONFIG_PM_SLEEP
+#ifdef CONFIG_PM_WAKEUP
/**
* acpi_pm_device_sleep_state - return preferred power state of ACPI device
* in the system sleep state given by %acpi_target_sleep_state
@@ -709,7 +709,7 @@ int acpi_pm_device_sleep_wake(struct dev

return error;
}
-#endif
+#endif /* CONFIG_PM_WAKEUP */

static void acpi_power_off_prepare(void)
{
Index: linux-2.6/drivers/acpi/pci_root.c
===================================================================
--- linux-2.6.orig/drivers/acpi/pci_root.c
+++ linux-2.6/drivers/acpi/pci_root.c
@@ -576,6 +576,11 @@ static int __devinit acpi_pci_root_add(s
if (flags != base_flags)
acpi_pci_osc_support(root, flags);

+ if (device->wakeup.flags.valid)
+ acpi_install_notify_handler(device->handle, ACPI_SYSTEM_NOTIFY,
+ pci_acpi_bus_wakeup,
+ &root->bus->dev);
+
return 0;

end:
Index: linux-2.6/include/linux/pci-acpi.h
===================================================================
--- linux-2.6.orig/include/linux/pci-acpi.h
+++ linux-2.6/include/linux/pci-acpi.h
@@ -10,6 +10,9 @@

#include <linux/acpi.h>

+extern void pci_acpi_bus_wakeup(acpi_handle handle, u32 event, void *data);
+extern void pci_acpi_device_wakeup(acpi_handle handle, u32 event, void *data);
+
#ifdef CONFIG_ACPI
static inline acpi_handle acpi_find_root_bridge_handle(struct pci_dev *pdev)
{
Index: linux-2.6/drivers/acpi/pci_bind.c
===================================================================
--- linux-2.6.orig/drivers/acpi/pci_bind.c
+++ linux-2.6/drivers/acpi/pci_bind.c
@@ -26,6 +26,7 @@
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/pci.h>
+#include <linux/pci-acpi.h>
#include <linux/acpi.h>
#include <acpi/acpi_bus.h>
#include <acpi/acpi_drivers.h>
@@ -94,6 +95,11 @@ static int acpi_pci_bind(struct acpi_dev

acpi_pci_irq_add_prt(device->handle, bus);

+ if (device->wakeup.flags.valid)
+ acpi_install_notify_handler(device->handle, ACPI_SYSTEM_NOTIFY,
+ pci_acpi_device_wakeup,
+ &dev->dev);
+
out:
pci_dev_put(dev);
return 0;
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/