Skip to content

Commit

Permalink
PCI / ACPI / PM: Platform support for PCI PME wake-up
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Rafael J. Wysocki authored and Jesse Barnes committed Feb 23, 2010
1 parent 3f0be67 commit b67ea76
Show file tree
Hide file tree
Showing 10 changed files with 319 additions and 3 deletions.
2 changes: 0 additions & 2 deletions drivers/acpi/internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
14 changes: 13 additions & 1 deletion drivers/acpi/pci_bind.c
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand All @@ -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);
Expand All @@ -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.
Expand Down
8 changes: 8 additions & 0 deletions drivers/acpi/pci_root.c
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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:
Expand All @@ -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;
}
Expand Down
1 change: 1 addition & 0 deletions drivers/acpi/scan.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
211 changes: 211 additions & 0 deletions drivers/pci/pci-acpi.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,144 @@
#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".
Expand Down Expand Up @@ -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 */
Expand Down
Loading

0 comments on commit b67ea76

Please sign in to comment.