]> git.proxmox.com Git - mirror_ubuntu-artful-kernel.git/commitdiff
PCI / ACPI / PM: Platform support for PCI PME wake-up
authorRafael J. Wysocki <rjw@sisk.pl>
Wed, 17 Feb 2010 22:44:09 +0000 (23:44 +0100)
committerJesse Barnes <jbarnes@virtuousgeek.org>
Tue, 23 Feb 2010 00:21:02 +0000 (16:21 -0800)
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, PME signals 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.  The devices without dedicated GPEs have
to rely on the GPEs associated with other devices (in the majority of
cases their upstream bridges and, possibly, the root bridge) to
generate ACPI wake-up events in response to PME signals from them.

Add ACPI platform support for PCI PME wake-up:
o Add a framework making is possible to use ACPI system notify
  handlers for run-time PM.
o Add new PCI platform callback ->run_wake() to struct
  pci_platform_pm_ops allowing us to enable/disable the platform to
  generate wake-up events for given device.  Implemet this callback
  for the ACPI platform.
o Define ACPI wake-up handlers for PCI devices and PCI root buses and
  make the PCI-ACPI binding code register wake-up notifiers for all
  PCI devices present in the ACPI tables.
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@redhat.com>.

Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
Signed-off-by: Jesse Barnes <jbarnes@virtuousgeek.org>
drivers/acpi/internal.h
drivers/acpi/pci_bind.c
drivers/acpi/pci_root.c
drivers/acpi/scan.c
drivers/pci/pci-acpi.c
drivers/pci/pci.c
drivers/pci/pci.h
include/acpi/acpi_bus.h
include/linux/pci-acpi.h
include/linux/pci.h

index cb28e0502acc346af5233ebeaa195b723a995713..9c4c962e46e366046921526d50bce2fe60cfd545 100644 (file)
@@ -36,8 +36,6 @@ static inline int acpi_debug_init(void) { return 0; }
 int acpi_power_init(void);
 int acpi_device_sleep_wake(struct acpi_device *dev,
                            int enable, int sleep_state, int dev_state);
-int acpi_enable_wakeup_device_power(struct acpi_device *dev, int sleep_state);
-int acpi_disable_wakeup_device_power(struct acpi_device *dev);
 int acpi_power_get_inferred_state(struct acpi_device *device);
 int acpi_power_transition(struct acpi_device *device, int state);
 extern int acpi_power_nocheck;
index a5a77b78a7237cbbb6e77a707f73a0861426718d..2ef04098cc1d5f4d01942d3e4de79014c4911efe 100644 (file)
@@ -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_device *device)
        struct pci_dev *dev;
 
        dev = acpi_get_pci_dev(device->handle);
-       if (!dev || !dev->subordinate)
+       if (!dev)
+               goto out;
+
+       device_set_run_wake(&dev->dev, false);
+       pci_acpi_remove_pm_notifier(device);
+
+       if (!dev->subordinate)
                goto out;
 
        acpi_pci_irq_del_prt(dev->subordinate);
@@ -62,6 +70,10 @@ static int acpi_pci_bind(struct acpi_device *device)
        if (!dev)
                return 0;
 
+       pci_acpi_add_pm_notifier(device, dev);
+       if (device->wakeup.flags.run_wake)
+               device_set_run_wake(&dev->dev, true);
+
        /*
         * Install the 'bind' function to facilitate callbacks for
         * children of the P2P bridge.
index 64f55b6db73ce550a51a68503c4e8ec85130d192..9cd8bedb1e5ac8ce42a688cb16357190c90ced99 100644 (file)
@@ -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>
@@ -528,6 +529,10 @@ static int __devinit acpi_pci_root_add(struct acpi_device *device)
        if (flags != base_flags)
                acpi_pci_osc_support(root, flags);
 
+       pci_acpi_add_bus_pm_notifier(device, root->bus);
+       if (device->wakeup.flags.run_wake)
+               device_set_run_wake(root->bus->bridge, true);
+
        return 0;
 
 end:
@@ -549,6 +554,9 @@ static int acpi_pci_root_remove(struct acpi_device *device, int type)
 {
        struct acpi_pci_root *root = acpi_driver_data(device);
 
+       device_set_run_wake(root->bus->bridge, false);
+       pci_acpi_remove_bus_pm_notifier(device);
+
        kfree(root);
        return 0;
 }
index 7491a52ad97a0f2430a0f169c0e4908d0de1f521..fb7fc24fe72725817ee38e970dc9b90f07998522 100644 (file)
@@ -753,6 +753,7 @@ static void acpi_bus_set_run_wake_flags(struct acpi_device *device)
        acpi_event_status event_status;
 
        device->wakeup.run_wake_count = 0;
+       device->wakeup.flags.notifier_present = 0;
 
        /* Power button, Lid switch always enable wakeup */
        if (!acpi_match_device_ids(device, button_device_ids)) {
index 7e2829538a4cfc5bf03131971930d4463ded11cf..c0c73913833df3f401499ac2ec6f867ce388404f 100644 (file)
 #include <acpi/acpi_bus.h>
 
 #include <linux/pci-acpi.h>
+#include <linux/pm_runtime.h>
 #include "pci.h"
 
+static DEFINE_MUTEX(pci_acpi_pm_notify_mtx);
+
+/**
+ * pci_acpi_wake_bus - Wake-up notification handler for root buses.
+ * @handle: ACPI handle of a device the notification is for.
+ * @event: Type of the signaled event.
+ * @context: PCI root bus to wake up devices on.
+ */
+static void pci_acpi_wake_bus(acpi_handle handle, u32 event, void *context)
+{
+       struct pci_bus *pci_bus = context;
+
+       if (event == ACPI_NOTIFY_DEVICE_WAKE && pci_bus)
+               pci_pme_wakeup_bus(pci_bus);
+}
+
+/**
+ * pci_acpi_wake_dev - Wake-up notification handler for PCI devices.
+ * @handle: ACPI handle of a device the notification is for.
+ * @event: Type of the signaled event.
+ * @context: PCI device object to wake up.
+ */
+static void pci_acpi_wake_dev(acpi_handle handle, u32 event, void *context)
+{
+       struct pci_dev *pci_dev = context;
+
+       if (event == ACPI_NOTIFY_DEVICE_WAKE && pci_dev) {
+               pci_check_pme_status(pci_dev);
+               pm_runtime_resume(&pci_dev->dev);
+               if (pci_dev->subordinate)
+                       pci_pme_wakeup_bus(pci_dev->subordinate);
+       }
+}
+
+/**
+ * add_pm_notifier - Register PM notifier for given ACPI device.
+ * @dev: ACPI device to add the notifier for.
+ * @context: PCI device or bus to check for PME status if an event is signaled.
+ *
+ * NOTE: @dev need not be a run-wake or wake-up device to be a valid source of
+ * PM wake-up events.  For example, wake-up events may be generated for bridges
+ * if one of the devices below the bridge is signaling PME, even if the bridge
+ * itself doesn't have a wake-up GPE associated with it.
+ */
+static acpi_status add_pm_notifier(struct acpi_device *dev,
+                                  acpi_notify_handler handler,
+                                  void *context)
+{
+       acpi_status status = AE_ALREADY_EXISTS;
+
+       mutex_lock(&pci_acpi_pm_notify_mtx);
+
+       if (dev->wakeup.flags.notifier_present)
+               goto out;
+
+       status = acpi_install_notify_handler(dev->handle,
+                                            ACPI_SYSTEM_NOTIFY,
+                                            handler, context);
+       if (ACPI_FAILURE(status))
+               goto out;
+
+       dev->wakeup.flags.notifier_present = true;
+
+ out:
+       mutex_unlock(&pci_acpi_pm_notify_mtx);
+       return status;
+}
+
+/**
+ * remove_pm_notifier - Unregister PM notifier from given ACPI device.
+ * @dev: ACPI device to remove the notifier from.
+ */
+static acpi_status remove_pm_notifier(struct acpi_device *dev,
+                                     acpi_notify_handler handler)
+{
+       acpi_status status = AE_BAD_PARAMETER;
+
+       mutex_lock(&pci_acpi_pm_notify_mtx);
+
+       if (!dev->wakeup.flags.notifier_present)
+               goto out;
+
+       status = acpi_remove_notify_handler(dev->handle,
+                                           ACPI_SYSTEM_NOTIFY,
+                                           handler);
+       if (ACPI_FAILURE(status))
+               goto out;
+
+       dev->wakeup.flags.notifier_present = false;
+
+ out:
+       mutex_unlock(&pci_acpi_pm_notify_mtx);
+       return status;
+}
+
+/**
+ * pci_acpi_add_bus_pm_notifier - Register PM notifier for given PCI bus.
+ * @dev: ACPI device to add the notifier for.
+ * @pci_bus: PCI bus to walk checking for PME status if an event is signaled.
+ */
+acpi_status pci_acpi_add_bus_pm_notifier(struct acpi_device *dev,
+                                        struct pci_bus *pci_bus)
+{
+       return add_pm_notifier(dev, pci_acpi_wake_bus, pci_bus);
+}
+
+/**
+ * pci_acpi_remove_bus_pm_notifier - Unregister PCI bus PM notifier.
+ * @dev: ACPI device to remove the notifier from.
+ */
+acpi_status pci_acpi_remove_bus_pm_notifier(struct acpi_device *dev)
+{
+       return remove_pm_notifier(dev, pci_acpi_wake_bus);
+}
+
+/**
+ * pci_acpi_add_pm_notifier - Register PM notifier for given PCI device.
+ * @dev: ACPI device to add the notifier for.
+ * @pci_dev: PCI device to check for the PME status if an event is signaled.
+ */
+acpi_status pci_acpi_add_pm_notifier(struct acpi_device *dev,
+                                    struct pci_dev *pci_dev)
+{
+       return add_pm_notifier(dev, pci_acpi_wake_dev, pci_dev);
+}
+
+/**
+ * pci_acpi_remove_pm_notifier - Unregister PCI device PM notifier.
+ * @dev: ACPI device to remove the notifier from.
+ */
+acpi_status pci_acpi_remove_pm_notifier(struct acpi_device *dev)
+{
+       return remove_pm_notifier(dev, pci_acpi_wake_dev);
+}
+
 /*
  * _SxD returns the D-state with the highest power
  * (lowest D-state number) supported in the S-state "x".
@@ -131,12 +267,87 @@ static int acpi_pci_sleep_wake(struct pci_dev *dev, bool enable)
        return 0;
 }
 
+/**
+ * acpi_dev_run_wake - 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 ACPI device object corresponding to @pci_dev and try to
+ * enable/disable the GPE associated with it.
+ */
+static int acpi_dev_run_wake(struct device *phys_dev, bool enable)
+{
+       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;
+       }
+
+       if (enable) {
+               if (!dev->wakeup.run_wake_count++) {
+                       acpi_enable_wakeup_device_power(dev, ACPI_STATE_S0);
+                       acpi_enable_gpe(dev->wakeup.gpe_device,
+                                       dev->wakeup.gpe_number,
+                                       ACPI_GPE_TYPE_RUNTIME);
+               }
+       } else if (dev->wakeup.run_wake_count > 0) {
+               if (!--dev->wakeup.run_wake_count) {
+                       acpi_disable_gpe(dev->wakeup.gpe_device,
+                                        dev->wakeup.gpe_number,
+                                        ACPI_GPE_TYPE_RUNTIME);
+                       acpi_disable_wakeup_device_power(dev);
+               }
+       } else {
+               error = -EALREADY;
+       }
+
+       return error;
+}
+
+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(&bridge->dev, enable))
+                       return;
+               bus = bus->parent;
+       }
+
+       /* We have reached the root bus. */
+       if (bus->bridge)
+               acpi_dev_run_wake(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(&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 5723446544fda4de0bdb16d484fff45541e695e5..df55a2f351b3104c8a3de43aca454fdb0e0c6b53 100644 (file)
@@ -20,6 +20,7 @@
 #include <linux/pm_wakeup.h>
 #include <linux/interrupt.h>
 #include <linux/device.h>
+#include <linux/pm_runtime.h>
 #include <asm/setup.h>
 #include "pci.h"
 
@@ -462,6 +463,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_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
@@ -1229,6 +1236,31 @@ bool pci_check_pme_status(struct pci_dev *dev)
        return ret;
 }
 
+/**
+ * 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.
@@ -1433,6 +1465,41 @@ int pci_back_from_sleep(struct pci_dev *dev)
        return pci_set_power_state(dev, PCI_D0);
 }
 
+/**
+ * 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 b95b0a077d3113d2db3444017a36dd5273d99ecf..286c508219494138676a02db14ea9cfb8f692a7b 100644 (file)
@@ -35,6 +35,10 @@ int pci_probe_reset_function(struct pci_dev *dev);
  *
  * @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 60fcff41935245191744083e342ca5da7e2d7c79..54508ccea023f563c840639915c22c981e7cfe76 100644 (file)
@@ -243,6 +243,7 @@ struct acpi_device_wakeup_flags {
        u8 valid:1;             /* Can successfully enable wakeup? */
        u8 run_wake:1;          /* Run-Wake GPE devices */
        u8 always_enabled:1;    /* Run-wake devices that are always enabled */
+       u8 notifier_present:1;  /* Wake-up notify handler has been installed */
 };
 
 struct acpi_device_wakeup_state {
@@ -388,6 +389,9 @@ acpi_handle acpi_get_pci_rootbridge_handle(unsigned int, unsigned int);
 struct acpi_pci_root *acpi_pci_find_root(acpi_handle handle);
 #define DEVICE_ACPI_HANDLE(dev) ((acpi_handle)((dev)->archdata.acpi_handle))
 
+int acpi_enable_wakeup_device_power(struct acpi_device *dev, int state);
+int acpi_disable_wakeup_device_power(struct acpi_device *dev);
+
 #ifdef CONFIG_PM_SLEEP
 int acpi_pm_device_sleep_state(struct device *, int *);
 int acpi_pm_device_sleep_wake(struct device *, bool);
index 93a7c08f869d4fcc7017862505156fdd21e24433..c8b6473c5f42a74ef58913836c24b54f8500fd2a 100644 (file)
 #include <linux/acpi.h>
 
 #ifdef CONFIG_ACPI
+extern acpi_status pci_acpi_add_bus_pm_notifier(struct acpi_device *dev,
+                                                struct pci_bus *pci_bus);
+extern acpi_status pci_acpi_remove_bus_pm_notifier(struct acpi_device *dev);
+extern acpi_status pci_acpi_add_pm_notifier(struct acpi_device *dev,
+                                            struct pci_dev *pci_dev);
+extern acpi_status pci_acpi_remove_pm_notifier(struct acpi_device *dev);
+
 static inline acpi_handle acpi_find_root_bridge_handle(struct pci_dev *pdev)
 {
        struct pci_bus *pbus = pdev->bus;
index 9fe4b2089b7876ea794aba756681a6390dc68e45..3f787ce78bd109f1639df19b73bb521eca6a26bb 100644 (file)
@@ -782,6 +782,7 @@ 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);
 int pci_back_from_sleep(struct pci_dev *dev);
+bool pci_dev_run_wake(struct pci_dev *dev);
 
 /* For use by arch with custom probe code */
 void set_pcie_port_type(struct pci_dev *pdev);