[RFC][PATCH 10/12] PCI / ACPI PM: Platform support for PCI PME wake-up (rev. 5)

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Friday 04 December 2009, Rafael J. Wysocki wrote:
> On Friday 04 December 2009, Matthew Garrett wrote:
> > On Sun, Nov 29, 2009 at 04:42:20PM +0100, Rafael J. Wysocki wrote:
> > 
> > > +	if (event == ACPI_NOTIFY_DEVICE_WAKE) {
> > > +		if (nb->dev->wakeup.run_wake_count > 0) {
> > > +			if (nb->pci_bus)
> > > +				pci_pme_wakeup_bus(nb->pci_bus);
> > > +			if (nb->pci_dev)
> > > +				pci_pme_wakeup(nb->pci_dev);
> > 
> > We may receive wakeup events on devices that aren't PME capable, which 
> > is the case for uhci on my test box. In that case we probably want to 
> > wake them up unconditionally.
> > 
> > +                       if (nb->pci_dev) {
> > +                               if (nb->pci_dev->pm_cap)
> > +                                       pci_pme_wakeup(nb->pci_dev);
> > +                               else
> > +                                       pm_request_resume(&nb->pci_dev->dev);
> > 
> > seems to work, though possibly we should assume that the firmware knows 
> > best and always schedule a wake in respose to a resume request?
> 
> I  think we can simply do:
> 
> +        if (nb->pci_dev) {
> +                pci_pme_wakeup(nb->pci_dev);
> +                pm_request_resume(&nb->pci_dev->dev);
> +        }
> 
> If the pci_pme_wakeup() spawns a resume request, the other call will just
> return.

Or better, make it

pci_check_pme_status(nb->pci_dev);
pm_request_resume(&nb->pci_dev->dev);

which avoids calling pm_request_resume() twice and allows us to make
pci_pme_wakeup a static function.

Rev. 5 of the patch implementing this is appended.

---
From: Rafael J. Wysocki <rjw@xxxxxxx>
Subject: PCI / ACPI PM: Platform support for PCI PME wake-up (rev. 5)

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.  Moreover, ACPI-based PCI hotplug also uses ACPI notify
handlers that in general may conflict with the PM notify handlers,
unless this issue is specifically taken care of.

Add ACPI platform support for PCI PME wake-up:
o Add a framework making is possible to use ACPI system notify
  handlers for both PM and hotplug at the same time and to take the
  wake-up GPE sharing into account.
o Add new PCI platform callback ->test_run_wake() and ->run_wake() to
  struct pci_platform_pm_ops allowing us, respectively, to check if
  if the platform can generate run-time wake-up events for given
  device and to enable/disable the platform to do that.  Implemet
  these callbacks for the ACPI platform.
o Define ACPI wake-up handlers for PCI devices and PCI buses and make
  the PCI-ACPI binding code register wake-up notifiers for devices
  associated with wake-up GPEs.
o Add function pci_dev_run_wake() which can be used by PCI drivers to
  check if given device is capable of generating wake-up events at
  run time.

Developed in cooperation with Matthew Garrett <mjg@xxxxxxxxxx>.

Signed-off-by: Rafael J. Wysocki <rjw@xxxxxxx>
---
 drivers/acpi/pci_bind.c            |   13 +
 drivers/acpi/pci_root.c            |    7 
 drivers/acpi/sleep.c               |   22 +-
 drivers/pci/hotplug/acpiphp_glue.c |   23 --
 drivers/pci/pci-acpi.c             |  399 +++++++++++++++++++++++++++++++++++++
 drivers/pci/pci.c                  |   67 ++++++
 drivers/pci/pci.h                  |    7 
 include/acpi/acpi_bus.h            |   11 -
 include/linux/pci-acpi.h           |   24 ++
 include/linux/pci.h                |    1 
 kernel/power/Kconfig               |    5 
 11 files changed, 555 insertions(+), 24 deletions(-)

Index: linux-2.6/drivers/pci/pci.h
===================================================================
--- linux-2.6.orig/drivers/pci/pci.h
+++ linux-2.6/drivers/pci/pci.h
@@ -35,6 +35,10 @@ int pci_probe_reset_function(struct pci_
  *
  * @sleep_wake: enables/disables the system 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.
  */
@@ -44,12 +48,15 @@ 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 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 int __pci_pme_wakeup(struct pci_dev *dev, void *ign);
+extern void pci_pme_wakeup_bus(struct pci_bus *bus);
 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
@@ -16,9 +16,378 @@
 #include <acpi/acpi_bus.h>
 
 #include <linux/pci-acpi.h>
+#include <linux/pm_runtime.h>
 #include "pci.h"
 
 /*
+ * ACPI-based PCI run-time power management uses ACPI system notify handlers,
+ * which are also used by ACPI-based PCI hotplug.  Unfortunately, however, there
+ * can be only one ACPI system notify handler installed for an ACPI device
+ * handle.  For this reason there has to be a way to use the same notify handler
+ * for both ACPI-based hotplug and ACPI-based run-time PM.
+ *
+ * pci_acpi_runtime_notifiers is a list of struct pci_acpi_notifier_block
+ * objects representing PCI devices that have ACPI system notify handlers
+ * installed.  For each of them, there is an ACPI-based hotplug notifier to
+ * execute for hotplug events, hp_cb, and a pointer to the data to pass to it
+ * hp_data, as well as poitners to a struct pci_bus object and a struct pci_dev
+ * object.  If the device is a PCI-to-PCI bridge or root bridge, the struct
+ * pci_bus pointer is not NULL and it is assumed that the whole bus segment
+ * below the bridge has to be walked if a PME is reported for it.  Otherwise,
+ * it is assumed that PME is generated only for the particular PCI device
+ * pointed to by the pci_dev field.
+ */
+
+static LIST_HEAD(pci_acpi_runtime_notifiers);
+static DEFINE_MUTEX(pci_acpi_notifier_mtx);
+
+struct pci_acpi_notifier_block
+{
+	struct list_head entry;
+	struct acpi_device *dev;
+	acpi_notify_handler hp_cb;
+	void *hp_data;
+	struct pci_bus *pci_bus;
+	struct pci_dev *pci_dev;
+};
+
+/**
+ * pci_acpi_event_fn - Universal system notification handler.
+ * @handle: ACPI handle of a device the notification is for.
+ * @event: Type of the signaled event.
+ * @data: Context data, should be a pointer to a notifier object.
+ *
+ * Take the address on a notifier object from @data and use it to extract the
+ * information needed for handling the event.  If this is a wake-up event,
+ * check if PM notification is enabled for this notifier object and, if so,
+ * execute the appropriate PME handler for the bus or device represented by it.
+ * If this is not a wake-up event, execute the hotplug notify handler for
+ * @handle.
+ */
+static void pci_acpi_event_fn(acpi_handle handle, u32 event, void *data)
+{
+	struct pci_acpi_notifier_block *nb = data;
+
+	if (!nb)
+		return;
+
+	mutex_lock(&pci_acpi_notifier_mtx);
+
+	if (event == ACPI_NOTIFY_DEVICE_WAKE) {
+		if (nb->dev->wakeup.run_wake_count > 0) {
+			if (nb->pci_bus) {
+				pci_pme_wakeup_bus(nb->pci_bus);
+			} if (nb->pci_dev) {
+				pci_check_pme_status(nb->pci_dev);
+				pm_request_resume(&nb->pci_dev->dev);
+			}
+		}
+	} else if (nb->hp_cb) {
+		nb->hp_cb(handle, event, nb->hp_data);
+	}
+
+	mutex_unlock(&pci_acpi_notifier_mtx);
+}
+
+/**
+ * new_notifier - Create a new notifier object for given ACPI device.
+ * @dev: Device to create the notifier object for.
+ */
+static struct pci_acpi_notifier_block *new_notifier(struct acpi_device *dev)
+{
+	struct pci_acpi_notifier_block *nb;
+
+	nb = kzalloc(sizeof(*nb), GFP_KERNEL);
+	if (!nb)
+		return NULL;
+
+	nb->dev = dev;
+	return nb;
+}
+
+/**
+ * pci_acpi_add_hp_notifier - Register a hotplug notifier for given device.
+ * @handle: ACPI handle of the device to register the notifier for.
+ * @handler: Callback to execute for hotplug events related to @handle.
+ * @context: Pointer to the context data to pass to @handler.
+ *
+ * Use @handle to get an ACPI device object and check if there is a notifier
+ * object for it.  If this is the case, add @handler and @context to the
+ * existing notifier object, unless there already is a hotplug handler in this
+ * notifier object.  Otherwise, create a new notifier object for the ACPI device
+ * associated with @handle and add @handler and @context to it.
+ */
+acpi_status pci_acpi_add_hp_notifier(acpi_handle handle,
+				     acpi_notify_handler handler, void *context)
+{
+	struct pci_acpi_notifier_block *nb;
+	struct acpi_device *dev;
+	acpi_status status = AE_OK;
+
+	if (!handle || ACPI_FAILURE(acpi_bus_get_device(handle, &dev)))
+		return AE_NOT_FOUND;
+
+	mutex_lock(&pci_acpi_notifier_mtx);
+
+	list_for_each_entry(nb, &pci_acpi_runtime_notifiers, entry) {
+		if (nb->dev != dev)
+			continue;
+
+		if (!nb->hp_cb) {
+			nb->hp_cb = handler;
+			nb->hp_data = context;
+		} else {
+			status = AE_ALREADY_EXISTS;
+		}
+		goto out;
+	}
+
+	nb = new_notifier(dev);
+	if (!nb) {
+		status = AE_NO_MEMORY;
+		goto out;
+	}
+	nb->hp_cb = handler;
+	nb->hp_data = context;
+
+	list_add_tail(&nb->entry, &pci_acpi_runtime_notifiers);
+
+	status = acpi_install_notify_handler(handle, ACPI_SYSTEM_NOTIFY,
+						pci_acpi_event_fn, nb);
+	if (ACPI_FAILURE(status)) {
+		list_del(&nb->entry);
+		kfree(nb);
+	}
+
+ out:
+	mutex_unlock(&pci_acpi_notifier_mtx);
+
+	return status;
+}
+EXPORT_SYMBOL_GPL(pci_acpi_add_hp_notifier);
+
+/**
+ * pci_acpi_remove_hp_notifier - Unregister a hotplug notifier for given device.
+ * @handle: ACPI handle of the device to unregister the notifier for.
+ * @handler: Callback executed for hotplug events related to @handle.
+ *
+ * Find the notifier object associated with @handle and remove the hotplug
+ * callback and the pointer to the hotplug context data from it.  If the
+ * notifier object is not necessary any more, remove it altogether.
+ */
+acpi_status pci_acpi_remove_hp_notifier(acpi_handle handle,
+					acpi_notify_handler handler)
+{
+	struct pci_acpi_notifier_block *nb;
+	struct acpi_device *dev;
+	acpi_status status = AE_NOT_FOUND;
+
+	if (!handle || ACPI_FAILURE(acpi_bus_get_device(handle, &dev)))
+		return AE_NOT_FOUND;
+
+	mutex_lock(&pci_acpi_notifier_mtx);
+
+	list_for_each_entry(nb, &pci_acpi_runtime_notifiers, entry)
+		if (nb->dev == dev) {
+			status = AE_OK;
+			break;
+		}
+
+	if (status != AE_OK)
+		goto out;
+
+	nb->hp_data = NULL;
+	nb->hp_cb = NULL;
+
+	if (nb->pci_bus || nb->pci_dev)
+		goto out;
+
+	status = acpi_remove_notify_handler(handle, ACPI_SYSTEM_NOTIFY,
+						pci_acpi_event_fn);
+	list_del(&nb->entry);
+	kfree(nb);
+
+ out:
+	mutex_unlock(&pci_acpi_notifier_mtx);
+	return status;
+}
+EXPORT_SYMBOL_GPL(pci_acpi_remove_hp_notifier);
+
+/**
+ * pci_acpi_add_pm_notifier - Register PM notifier for given device.
+ * @dev: ACPI device to add the notifier for.
+ * @pci_dev: PCI device to check for the PME status if an event is signaled.
+ * @pci_bus: PCI bus to walk (checking PME status) if an event is signaled.
+ *
+ * Check if there is a notifier object for @dev and if that is the case, add
+ * @pci_dev to it as the device whose PME status should be checked if a PM
+ * event is signaled for @dev.  Also, add @pci_bus to it as the bus to walk
+ * checking the PME status of all devices on it if a PM event is signaled for
+ * @dev.  Otherwise, create a new notifier object for @dev and add both
+ * @pci_dev and @pci_bus to it.
+ */
+acpi_status pci_acpi_add_pm_notifier(struct acpi_device *dev,
+				     struct pci_dev *pci_dev,
+				     struct pci_bus *pci_bus)
+{
+	struct pci_acpi_notifier_block *nb;
+	acpi_status status = AE_OK;
+
+	if (!dev->wakeup.flags.run_wake)
+		return AE_BAD_PARAMETER;
+
+	mutex_lock(&pci_acpi_notifier_mtx);
+
+	list_for_each_entry(nb, &pci_acpi_runtime_notifiers, entry)
+		if (nb->dev == dev) {
+			if (nb->pci_dev || nb->pci_bus)
+				goto out;
+			else
+				goto add;
+		}
+
+	nb = new_notifier(dev);
+	if (!nb) {
+		status = AE_NO_MEMORY;
+		goto out;
+	}
+	list_add_tail(&nb->entry, &pci_acpi_runtime_notifiers);
+
+	status = acpi_install_notify_handler(dev->handle, ACPI_SYSTEM_NOTIFY,
+						pci_acpi_event_fn, nb);
+	if (ACPI_FAILURE(status)) {
+		list_del(&nb->entry);
+		kfree(nb);
+		goto out;
+	}
+
+ add:
+	nb->pci_dev = pci_dev;
+	nb->pci_bus = pci_bus;
+
+ out:
+	mutex_unlock(&pci_acpi_notifier_mtx);
+	return status;
+}
+
+/**
+ * pci_acpi_remove_pm_notifier - Unregister PM notifier for given device.
+ * @dev: ACPI device to remove the notifier from.
+ *
+ * Find the notifier object for @dev and clear its @pci_dev and @pci_bus fields.
+ * If the notifier object is not necessary any more after that, remove it too.
+ */
+acpi_status pci_acpi_remove_pm_notifier(struct acpi_device *dev)
+{
+	struct pci_acpi_notifier_block *nb;
+	acpi_status status = AE_NOT_FOUND;
+
+	if (!dev->wakeup.flags.run_wake)
+		return AE_BAD_PARAMETER;
+
+	mutex_lock(&pci_acpi_notifier_mtx);
+
+	list_for_each_entry(nb, &pci_acpi_runtime_notifiers, entry)
+		if (nb->dev == dev) {
+			status = AE_OK;
+			break;
+		}
+
+	if (status != AE_OK)
+		goto out;
+
+	if (dev->wakeup.run_wake_count) {
+		dev->wakeup.run_wake_count = 0;
+		acpi_unref_runtime_gpe(dev->wakeup.gpe_device,
+					dev->wakeup.gpe_number);
+		acpi_pm_wake_up_power(dev, false);
+	}
+
+	nb->pci_dev = NULL;
+	nb->pci_bus = NULL;
+
+	if (nb->hp_cb)
+		goto out;
+
+	status = acpi_remove_notify_handler(nb->dev->handle,
+						ACPI_SYSTEM_NOTIFY,
+						pci_acpi_event_fn);
+	list_del(&nb->entry);
+	kfree(nb);
+
+ out:
+	mutex_unlock(&pci_acpi_notifier_mtx);
+	return status;
+}
+
+/**
+ * run_wake_enable - Enable/disable the GPE associated with given notifier.
+ * @nb: Notifier to enable/disable the GPE for.
+ * @enable: Whether to enable or disable the wake-up feature.
+ */
+static int run_wake_enable(struct pci_acpi_notifier_block *nb, bool enable)
+{
+	struct acpi_device *dev = nb->dev;
+	int error = 0;
+
+	if (enable) {
+		if (!dev->wakeup.run_wake_count++) {
+			acpi_pm_wake_up_power(dev, true);
+			acpi_ref_runtime_gpe(dev->wakeup.gpe_device,
+						dev->wakeup.gpe_number);
+		}
+	} else if (dev->wakeup.run_wake_count > 0) {
+		if (!--dev->wakeup.run_wake_count) {
+			acpi_unref_runtime_gpe(dev->wakeup.gpe_device,
+						dev->wakeup.gpe_number);
+			acpi_pm_wake_up_power(dev, false);
+		}
+	} else {
+		error = -EALREADY;
+	}
+
+	return error;
+}
+
+/**
+ * acpi_dev_run_wake_enable - Enable/disable wake-up for given device.
+ * @phys_dev: Device to enable/disable the platform to wake-up the system for.
+ * @enable: Whether enable or disable the wake-up functionality.
+ *
+ * Find the notifier object corresponding to @pci_dev and try to enable/disable
+ * the GPE associated with it.
+ */
+static int acpi_dev_run_wake_enable(struct device *phys_dev, bool enable)
+{
+	struct pci_acpi_notifier_block *nb;
+	struct acpi_device *dev;
+	acpi_handle handle;
+	int error = -ENODEV;
+
+	if (!device_run_wake(phys_dev))
+		return -EINVAL;
+
+	handle = DEVICE_ACPI_HANDLE(phys_dev);
+	if (!handle || ACPI_FAILURE(acpi_bus_get_device(handle, &dev))) {
+		dev_dbg(phys_dev, "ACPI handle has no context in %s!\n",
+			__func__);
+		return -ENODEV;
+	}
+
+	mutex_lock(&pci_acpi_notifier_mtx);
+
+	list_for_each_entry(nb, &pci_acpi_runtime_notifiers, entry)
+		if (nb->dev == dev) {
+			error = run_wake_enable(nb, enable);
+			break;
+		}
+
+	mutex_unlock(&pci_acpi_notifier_mtx);
+
+	return error;
+}
+
+/*
  * _SxD returns the D-state with the highest power
  * (lowest D-state number) supported in the S-state "x".
  *
@@ -131,12 +500,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 (bridge->pme_interrupt)
+			return;
+		if (!acpi_dev_run_wake_enable(&bridge->dev, enable))
+			return;
+		bus = bus->parent;
+	}
+
+	/* We have reached the root bus. */
+	if (bus->bridge)
+		acpi_dev_run_wake_enable(bus->bridge, enable);
+}
+
+static int acpi_pci_run_wake(struct pci_dev *dev, bool enable)
+{
+	if (dev->pme_interrupt)
+		return 0;
+
+	if (!acpi_dev_run_wake_enable(&dev->dev, enable))
+		return 0;
+
+	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"
 
@@ -434,6 +435,12 @@ static inline int platform_pci_sleep_wak
 			pci_platform_pm->sleep_wake(dev, enable) : -ENODEV;
 }
 
+static inline int platform_pci_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
@@ -1202,6 +1209,31 @@ bool pci_check_pme_status(struct pci_dev
 }
 
 /**
+ * pci_pme_wakeup - Wake up a PCI device if its PME Status bit is set.
+ * @dev: Device to handle.
+ * @ign: Ignored.
+ *
+ * Check if @dev has generated PME and queue a resume request for it in that
+ * case.
+ */
+static int pci_pme_wakeup(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)
+{
+	if (bus)
+		pci_walk_bus(bus, pci_pme_wakeup, NULL);
+}
+
+/**
  * pci_pme_capable - check the capability of PCI device to generate PME#
  * @dev: PCI device to handle.
  * @state: PCI state from which device will issue PME#.
@@ -1406,6 +1438,41 @@ int pci_back_from_sleep(struct pci_dev *
 }
 
 /**
+ * pci_dev_run_wake - Check if device can generate run-time wake-up events.
+ * @dev: Device to check.
+ *
+ * Return true if the device itself is cabable of generating wake-up events
+ * (through the platform or using the native PCIe PME) or if the device supports
+ * PME and one of its upstream bridges can generate wake-up events.
+ */
+bool pci_dev_run_wake(struct pci_dev *dev)
+{
+	struct pci_bus *bus = dev->bus;
+
+	if (device_run_wake(&dev->dev))
+		return true;
+
+	if (!dev->pme_support)
+		return false;
+
+	while (bus->parent) {
+		struct pci_dev *bridge = bus->self;
+
+		if (device_run_wake(&bridge->dev))
+			return true;
+
+		bus = bus->parent;
+	}
+
+	/* We have reached the root bus. */
+	if (bus->bridge)
+		return device_run_wake(bus->bridge);
+
+	return false;
+}
+EXPORT_SYMBOL_GPL(pci_dev_run_wake);
+
+/**
  * pci_pm_init - Initialize PM functions of given PCI device
  * @dev: PCI device to handle.
  */
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
@@ -634,7 +634,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
@@ -720,6 +720,18 @@ int acpi_pm_device_sleep_state(struct de
 }
 
 /**
+ * acpi_pm_wake_up_power - Enable/disable device wake-up power.
+ * @dev: ACPI device to handle.
+ * @enable: Whether to enable or disable the wake-up power of the device.
+ */
+int acpi_pm_wake_up_power(struct acpi_device *dev, bool enable)
+{
+	return enable ?
+		acpi_enable_wakeup_device_power(dev, acpi_target_sleep_state) :
+		acpi_disable_wakeup_device_power(dev);
+}
+
+/**
  *	acpi_pm_device_sleep_wake - enable or disable the system wake-up
  *                                  capability of given device
  *	@dev: device to handle
@@ -740,16 +752,14 @@ int acpi_pm_device_sleep_wake(struct dev
 		return -ENODEV;
 	}
 
-	error = enable ?
-		acpi_enable_wakeup_device_power(adev, acpi_target_sleep_state) :
-		acpi_disable_wakeup_device_power(adev);
+	error = acpi_pm_wake_up_power(adev, enable);
 	if (!error)
-		dev_info(dev, "wake-up capability %s by ACPI\n",
+		dev_info(dev, "wake-up power %s by ACPI\n",
 				enable ? "enabled" : "disabled");
 
 	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
@@ -30,6 +30,7 @@
 #include <linux/proc_fs.h>
 #include <linux/spinlock.h>
 #include <linux/pm.h>
+#include <linux/pm_runtime.h>
 #include <linux/pci.h>
 #include <linux/pci-acpi.h>
 #include <linux/acpi.h>
@@ -576,6 +577,9 @@ static int __devinit acpi_pci_root_add(s
 	if (flags != base_flags)
 		acpi_pci_osc_support(root, flags);
 
+	if (!pci_acpi_add_bus_pm_notifier(device, root->bus))
+		device_set_run_wake(root->bus->bridge, true);
+
 	return 0;
 
 end:
@@ -597,6 +601,9 @@ static int acpi_pci_root_remove(struct a
 {
 	struct acpi_pci_root *root = acpi_driver_data(device);
 
+	pci_acpi_remove_pm_notifier(device);
+	device_set_run_wake(root->bus->bridge, false);
+
 	kfree(root);
 	return 0;
 }
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
@@ -11,6 +11,30 @@
 #include <linux/acpi.h>
 
 #ifdef CONFIG_ACPI
+extern acpi_status pci_acpi_add_hp_notifier(acpi_handle handle,
+					     acpi_notify_handler handler,
+					     void *context);
+extern acpi_status pci_acpi_remove_hp_notifier(acpi_handle handle,
+					       acpi_notify_handler handler);
+extern acpi_status pci_acpi_add_pm_notifier(struct acpi_device *dev,
+					     struct pci_dev *pci_dev,
+					     struct pci_bus *pci_bus);
+extern acpi_status pci_acpi_remove_pm_notifier(struct acpi_device *dev);
+
+static inline
+acpi_status pci_acpi_add_device_pm_notifier(struct acpi_device *dev,
+					    struct pci_dev *pci_dev)
+{
+	return pci_acpi_add_pm_notifier(dev, pci_dev, pci_dev->subordinate);
+}
+
+static inline
+acpi_status pci_acpi_add_bus_pm_notifier(struct acpi_device *dev,
+					 struct pci_bus *pci_bus)
+{
+	return pci_acpi_add_pm_notifier(dev, NULL, pci_bus);
+}
+
 static inline acpi_handle acpi_find_root_bridge_handle(struct pci_dev *pdev)
 {
 	struct pci_bus *pbus = pdev->bus;
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,7 +26,9 @@
 #include <linux/kernel.h>
 #include <linux/types.h>
 #include <linux/pci.h>
+#include <linux/pci-acpi.h>
 #include <linux/acpi.h>
+#include <linux/pm_runtime.h>
 #include <acpi/acpi_bus.h>
 #include <acpi/acpi_drivers.h>
 
@@ -38,7 +40,13 @@ static int acpi_pci_unbind(struct acpi_d
 	struct pci_dev *dev;
 
 	dev = acpi_get_pci_dev(device->handle);
-	if (!dev || !dev->subordinate)
+	if (!dev)
+		goto out;
+
+	pci_acpi_remove_pm_notifier(device);
+	device_set_run_wake(&dev->dev, false);
+
+	if (!dev->subordinate)
 		goto out;
 
 	acpi_pci_irq_del_prt(dev->subordinate);
@@ -62,6 +70,9 @@ static int acpi_pci_bind(struct acpi_dev
 	if (!dev)
 		return 0;
 
+	if (!pci_acpi_add_device_pm_notifier(device, dev))
+		device_set_run_wake(&dev->dev, true);
+
 	/*
 	 * Install the 'bind' function to facilitate callbacks for
 	 * children of the P2P bridge.
Index: linux-2.6/drivers/pci/hotplug/acpiphp_glue.c
===================================================================
--- linux-2.6.orig/drivers/pci/hotplug/acpiphp_glue.c
+++ linux-2.6/drivers/pci/hotplug/acpiphp_glue.c
@@ -238,8 +238,7 @@ register_slot(acpi_handle handle, u32 lv
 
 	/* install notify handler */
 	if (!(newfunc->flags & FUNC_HAS_DCK)) {
-		status = acpi_install_notify_handler(handle,
-					     ACPI_SYSTEM_NOTIFY,
+		status = pci_acpi_add_hp_notifier(handle,
 					     handle_hotplug_event_func,
 					     newfunc);
 
@@ -290,14 +289,12 @@ static void init_bridge_misc(struct acpi
 	/* install notify handler */
 	if (bridge->type != BRIDGE_TYPE_HOST) {
 		if ((bridge->flags & BRIDGE_HAS_EJ0) && bridge->func) {
-			status = acpi_remove_notify_handler(bridge->func->handle,
-						ACPI_SYSTEM_NOTIFY,
+			status = pci_acpi_remove_hp_notifier(bridge->func->handle,
 						handle_hotplug_event_func);
 			if (ACPI_FAILURE(status))
 				err("failed to remove notify handler\n");
 		}
-		status = acpi_install_notify_handler(bridge->handle,
-					     ACPI_SYSTEM_NOTIFY,
+		status = pci_acpi_add_hp_notifier(bridge->handle,
 					     handle_hotplug_event_bridge,
 					     bridge);
 
@@ -513,15 +510,14 @@ static void cleanup_bridge(struct acpiph
 	acpi_status status;
 	acpi_handle handle = bridge->handle;
 
-	status = acpi_remove_notify_handler(handle, ACPI_SYSTEM_NOTIFY,
+	status = pci_acpi_remove_hp_notifier(handle,
 					    handle_hotplug_event_bridge);
 	if (ACPI_FAILURE(status))
 		err("failed to remove notify handler\n");
 
 	if ((bridge->type != BRIDGE_TYPE_HOST) &&
 	    ((bridge->flags & BRIDGE_HAS_EJ0) && bridge->func)) {
-		status = acpi_install_notify_handler(bridge->func->handle,
-						ACPI_SYSTEM_NOTIFY,
+		status = pci_acpi_add_hp_notifier(bridge->func->handle,
 						handle_hotplug_event_func,
 						bridge->func);
 		if (ACPI_FAILURE(status))
@@ -539,8 +535,7 @@ static void cleanup_bridge(struct acpiph
 				unregister_dock_notifier(&func->nb);
 			}
 			if (!(func->flags & FUNC_HAS_DCK)) {
-				status = acpi_remove_notify_handler(func->handle,
-						ACPI_SYSTEM_NOTIFY,
+				status = pci_acpi_remove_hp_notifier(func->handle,
 						handle_hotplug_event_func);
 				if (ACPI_FAILURE(status))
 					err("failed to remove notify handler\n");
@@ -602,7 +597,7 @@ static void remove_bridge(acpi_handle ha
 	if (bridge)
 		cleanup_bridge(bridge);
 	else
-		acpi_remove_notify_handler(handle, ACPI_SYSTEM_NOTIFY,
+		pci_acpi_remove_hp_notifier(handle,
 					   handle_hotplug_event_bridge);
 }
 
@@ -1492,8 +1487,8 @@ find_root_bridges(acpi_handle handle, u3
 	int *count = (int *)context;
 
 	if (acpi_is_root_bridge(handle)) {
-		acpi_install_notify_handler(handle, ACPI_SYSTEM_NOTIFY,
-				handle_hotplug_event_bridge, NULL);
+		pci_acpi_add_hp_notifier(handle,
+					handle_hotplug_event_bridge, NULL);
 			(*count)++;
 	}
 	return AE_OK ;
Index: linux-2.6/include/linux/pci.h
===================================================================
--- linux-2.6.orig/include/linux/pci.h
+++ linux-2.6/include/linux/pci.h
@@ -743,6 +743,7 @@ int pci_wake_from_d3(struct pci_dev *dev
 pci_power_t pci_target_state(struct pci_dev *dev);
 int pci_prepare_to_sleep(struct pci_dev *dev);
 int pci_back_from_sleep(struct pci_dev *dev);
+bool pci_dev_run_wake(struct pci_dev *dev);
 
 /* Functions for PCI Hotplug drivers to use */
 int pci_bus_find_capability(struct pci_bus *bus, unsigned int devfn, int cap);
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
@@ -388,21 +388,26 @@ acpi_handle acpi_get_pci_rootbridge_hand
 struct acpi_pci_root *acpi_pci_find_root(acpi_handle handle);
 #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_wake_up_power(struct acpi_device *, bool);
 int acpi_pm_device_sleep_wake(struct device *, bool);
-#else /* !CONFIG_PM_SLEEP */
+#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_wake_up_power(struct acpi_device *dev, bool enable)
+{
+	return -ENODEV;
+}
 static inline int acpi_pm_device_sleep_wake(struct device *dev, bool enable)
 {
 	return -ENODEV;
 }
-#endif /* !CONFIG_PM_SLEEP */
+#endif /* !CONFIG_PM_WAKEUP */
 
 #endif				/* CONFIG_ACPI */
 
_______________________________________________
linux-pm mailing list
linux-pm@xxxxxxxxxxxxxxxxxxxxxxxxxx
https://lists.linux-foundation.org/mailman/listinfo/linux-pm

[Index of Archives]     [Linux ACPI]     [Netdev]     [Ethernet Bridging]     [Linux Wireless]     [CPU Freq]     [Kernel Newbies]     [Fedora Kernel]     [Security]     [Linux for Hams]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux RAID]     [Linux Admin]     [Samba]

  Powered by Linux