Hi,
The following four patches implement some features needed for PCI run-time PM.
[1/4] adds a function for checking and clearing the PME status of a device
[2/4] adds support for PCIe native PME signaling mechanism
[3/4] adds support for ACPI-based PME signaling at run time
[4/4] adds prototype run-time PM callbacks for the PCI bus type
Patches [1/4] - [3/4] are rather complete, unless I missed something,
but [4/4] still requires some work. For example, there should be some sort of
synchronization between the run-time PM and system-wide sleep transitions
which is missing at the moment. In addition, it would make sense to
"autosuspend" PCI devices without drivers which has not been implemented
yet.
Comments welcome.
Thanks,
Rafael
From: Rafael J. Wysocki <[email protected]>
Add function pci_check_pme_status() that will check the PME status
bit of given device and clear it along with the PME enable bit. It
will be necessary for PCI run-time power management.
Based on a patch from Shaohua Li <[email protected]>
Signed-off-by: Rafael J. Wysocki <[email protected]>
---
drivers/pci/pci.c | 35 +++++++++++++++++++++++++++++++++++
drivers/pci/pci.h | 1 +
2 files changed, 36 insertions(+)
Index: linux-2.6/drivers/pci/pci.h
===================================================================
--- linux-2.6.orig/drivers/pci/pci.h
+++ linux-2.6/drivers/pci/pci.h
@@ -49,6 +49,7 @@ struct pci_platform_pm_ops {
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 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.c
===================================================================
--- linux-2.6.orig/drivers/pci/pci.c
+++ linux-2.6/drivers/pci/pci.c
@@ -1167,6 +1167,41 @@ int pci_set_pcie_reset_state(struct pci_
}
/**
+ * pci_check_pme_status - Check if given device has generated PME.
+ * @dev: Device to check.
+ *
+ * Check the PME status of the device, clear PME status and PME enable. Return
+ * 'true' if PME has been generated by the device (and hasn't been spurious) or
+ * 'false' otherwise.
+ */
+bool pci_check_pme_status(struct pci_dev *dev)
+{
+ int pmcsr_pos;
+ u16 pmcsr;
+ bool ret = false;
+
+ if (!dev->pm_cap)
+ return false;
+
+ pmcsr_pos = dev->pm_cap + PCI_PM_CTRL;
+ /* clear PME status and disable PME to avoid interrupt flood */
+ pci_read_config_word(dev, pmcsr_pos, &pmcsr);
+ if (!(pmcsr & PCI_PM_CTRL_PME_STATUS))
+ return false;
+
+ pmcsr |= PCI_PM_CTRL_PME_STATUS;
+ /* Ignore spurious PME or clear PME enable if it's not spurious. */
+ if (pmcsr & PCI_PM_CTRL_PME_ENABLE) {
+ pmcsr &= ~PCI_PM_CTRL_PME_ENABLE;
+ ret = true;
+ }
+
+ pci_write_config_word(dev, pmcsr_pos, pmcsr);
+
+ return ret;
+}
+
+/**
* 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#.
From: Rafael J. Wysocki <[email protected]>
PCIe native PME detection mechanism is based on interrupts generated
by root ports or event collectors every time a PCIe device sends a
PME message upstream.
Once a PME message has been sent by an endpoint device and received
by its root port (or event collector in the case of root complex
integrated endpoints), the Requester ID from the message header is
registered in the root port's Root Status register. At the same
time, the PME Status bit of the Root Status register is set to
indicate that there's a PME to handle. If PCIe PME interrupt is
enabled for the root port, it generates an interrupt once the PME
Status has been set. After receiving the interrupt, the kernel can
identify the PCIe device that generated the PME using the Requester
ID from the root port's Root Status register. [For details, see PCI
Express Base Specification, Rev. 2.0.]
Implement a driver for the PCIe PME root port service working in
accordance with the above description.
Based on a patch from Shaohua Li <[email protected]>.
Signed-off-by: Rafael J. Wysocki <[email protected]>
---
drivers/pci/pci-acpi.c | 4
drivers/pci/pcie/Kconfig | 4
drivers/pci/pcie/Makefile | 2
drivers/pci/pcie/pme/Makefile | 8
drivers/pci/pcie/pme/pcie_pme.c | 491 +++++++++++++++++++++++++++++++++++
drivers/pci/pcie/pme/pcie_pme.h | 28 +
drivers/pci/pcie/pme/pcie_pme_acpi.c | 54 +++
include/linux/pci.h | 6
8 files changed, 595 insertions(+), 2 deletions(-)
Index: linux-2.6/drivers/pci/pcie/Kconfig
===================================================================
--- linux-2.6.orig/drivers/pci/pcie/Kconfig
+++ linux-2.6/drivers/pci/pcie/Kconfig
@@ -46,3 +46,7 @@ config PCIEASPM_DEBUG
help
This enables PCI Express ASPM debug support. It will add per-device
interface to control ASPM.
+
+config PCIE_PME
+ def_bool y
+ depends on PCIEPORTBUS && PM_RUNTIME && EXPERIMENTAL
Index: linux-2.6/drivers/pci/pcie/Makefile
===================================================================
--- linux-2.6.orig/drivers/pci/pcie/Makefile
+++ linux-2.6/drivers/pci/pcie/Makefile
@@ -11,3 +11,5 @@ obj-$(CONFIG_PCIEPORTBUS) += pcieportdrv
# Build PCI Express AER if needed
obj-$(CONFIG_PCIEAER) += aer/
+
+obj-$(CONFIG_PCIE_PME) += pme/
Index: linux-2.6/drivers/pci/pcie/pme/pcie_pme.c
===================================================================
--- /dev/null
+++ linux-2.6/drivers/pci/pcie/pme/pcie_pme.c
@@ -0,0 +1,491 @@
+/*
+ * PCIe Native PME support
+ *
+ * Copyright (C) 2007 - 2009 Intel Corp
+ * Copyright (C) 2007 - 2009 Shaohua Li <[email protected]>
+ * Copyright (C) 2009 Rafael J. Wysocki <[email protected]>, Novell Inc.
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License V2. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/pcieport_if.h>
+#include <linux/acpi.h>
+#include <linux/pci-acpi.h>
+#include <linux/pm_runtime.h>
+
+#include "../../pci.h"
+#include "pcie_pme.h"
+
+#define PCI_EXP_RTSTA_PME 0x10000 /* PME status */
+#define PCI_EXP_RTSTA_PENDING 0x20000 /* PME pending */
+
+/*
+ * If set, this switch will prevent the PCIe root port PME service driver from
+ * being registered. Consequently, the interrupt-based PCIe PME signaling will
+ * not be used by any PCIe root ports in that case.
+ */
+static bool pcie_pme_disabled;
+
+/*
+ * The PCI Express Base Specification 2.0, Section 6.1.8, states the following:
+ * "In order to maintain compatibility with non-PCI Express-aware system
+ * software, system power management logic must be configured by firmware to use
+ * the legacy mechanism of signaling PME by default. PCI Express-aware system
+ * software must notify the firmware prior to enabling native, interrupt-based
+ * PME signaling." However, if the platform doesn't provide us with a suitable
+ * notification mechanism or the notification fails, it is not clear whether or
+ * not we are supposed to use the interrupt-based PCIe PME signaling. The
+ * switch below can be used to indicate the desired behaviour. When set, it
+ * will make the kernel use the interrupt-based PCIe PME signaling regardless of
+ * the platform notification status, although the kernel will attempt to notify
+ * the platform anyway. When unset, it will prevent the kernel from using the
+ * the interrupt-based PCIe PME signaling if the platform notification fails,
+ * which is the default.
+ */
+static bool pcie_pme_force_enable;
+
+/*
+ * This flag is set once the kernel has successfully received control of the
+ * PCIe PME registers from the platform firmware.
+ */
+static bool pcie_pme_control_received;
+
+static int __init pcie_pme_setup(char *str)
+{
+ if (!strcmp(str, "off"))
+ pcie_pme_disabled = true;
+ else if (!strcmp(str, "force"))
+ pcie_pme_force_enable = true;
+ return 1;
+}
+__setup("pcie_pme=", pcie_pme_setup);
+
+/**
+ * pcie_pme_platform_setup - Ensure that the kernel controls the PCIe PME.
+ * @srv: PCIe PME root port service to use for carrying out the check.
+ *
+ * Check if the kernel has already received control of the native PCIe PME
+ * feature from the platform firmware and if this is not the case, notify
+ * the platform that the native PCIe PME is going to be used.
+ */
+static int pcie_pme_platform_setup(struct pcie_device *srv)
+{
+ int ret;
+
+ if (pcie_pme_control_received)
+ return 0;
+
+ ret = pcie_pme_platform_notify(srv);
+ if (!ret)
+ pcie_pme_control_received = true;
+ else if (pcie_pme_force_enable)
+ ret = 0;
+
+ return ret;
+}
+
+/**
+ * pcie_pme_enabled - Check if given device can use the native PCIe PME feature.
+ * @dev: PCI device to check.
+ */
+bool pcie_pme_enabled(struct pci_dev *dev)
+{
+ return dev->is_pcie && !pcie_pme_disabled
+ && (pcie_pme_control_received || pcie_pme_force_enable);
+}
+
+struct pcie_pme_service_data {
+ spinlock_t lock;
+ struct pcie_device *srv;
+ struct work_struct work;
+ bool noirq; /* Don't enable the PME interrupt used by this service. */
+};
+
+/**
+ * pcie_pme_interrupt_enable - Enable/disable PCIe PME interrupt generation.
+ * @dev: PCIe root port or event collector.
+ * @enable: Enable or disable the interrupt.
+ */
+static void pcie_pme_interrupt_enable(struct pci_dev *dev, bool enable)
+{
+ int rtctl_pos;
+ u16 rtctl;
+
+ rtctl_pos = pci_find_capability(dev, PCI_CAP_ID_EXP) + PCI_EXP_RTCTL;
+
+ pci_read_config_word(dev, rtctl_pos, &rtctl);
+ if (enable)
+ rtctl |= PCI_EXP_RTCTL_PMEIE;
+ else
+ rtctl &= ~PCI_EXP_RTCTL_PMEIE;
+ pci_write_config_word(dev, rtctl_pos, rtctl);
+}
+
+/**
+ * pcie_pme_clear_status - Clear root port PME interrupt status.
+ * @dev: PCIe root port or event collector.
+ */
+static void pcie_pme_clear_status(struct pci_dev *dev)
+{
+ int rtsta_pos;
+ u32 rtsta;
+
+ rtsta_pos = pci_find_capability(dev, PCI_CAP_ID_EXP) + PCI_EXP_RTSTA;
+
+ pci_read_config_dword(dev, rtsta_pos, &rtsta);
+ rtsta |= PCI_EXP_RTSTA_PME;
+ pci_write_config_dword(dev, rtsta_pos, rtsta);
+}
+
+/**
+ * pcie_pme_walk_bus - Scan a PCI bus for devices asserting PME#.
+ * @bus: PCI bus to scan.
+ *
+ * Scan given PCI bus and all buses under it for devices asserting PME#.
+ */
+static bool pcie_pme_walk_bus(struct pci_bus *bus)
+{
+ struct pci_dev *dev;
+ bool ret = false;
+
+ list_for_each_entry(dev, &bus->devices, bus_list) {
+ /* Skip PCIe devices in case we started from a root port. */
+ if (!dev->is_pcie && pci_check_pme_status(dev)) {
+ pm_request_resume(&dev->dev);
+ ret = true;
+ }
+
+ if (dev->subordinate && pcie_pme_walk_bus(dev->subordinate))
+ ret = true;
+ }
+
+ return ret;
+}
+
+/**
+ * pcie_pme_from_pci_bridge - Check if PCIe-PCI bridge generated a PME.
+ * @bus: Secondary bus of the bridge.
+ * @devfn: Device/function number to check.
+ *
+ * PME from PCI devices under a PCIe-PCI bridge may be converted to an in-band
+ * PCIe PME message. In such that case the bridge should use the Requester ID
+ * of device/function number 0 on its secondary bus.
+ */
+static bool pcie_pme_from_pci_bridge(struct pci_bus *bus, u8 devfn)
+{
+ struct pci_dev *dev;
+ bool found = false;
+
+ if (devfn)
+ return false;
+
+ dev = pci_dev_get(bus->self);
+ if (!dev)
+ return false;
+
+ if (dev->is_pcie && dev->pcie_type == PCI_EXP_TYPE_PCI_BRIDGE) {
+ down_read(&pci_bus_sem);
+ if (pcie_pme_walk_bus(bus))
+ found = true;
+ up_read(&pci_bus_sem);
+ }
+
+ pci_dev_put(dev);
+ return found;
+}
+
+/**
+ * pcie_pme_handle_request - Find device that generated PME and handle it.
+ * @port: Root port or event collector that generated the PME interrupt.
+ * @req_id: PCIe Requester ID of the device that generated the PME.
+ */
+static void pcie_pme_handle_request(struct pci_dev *port, u16 req_id)
+{
+ u8 busnr = req_id >> 8, devfn = req_id & 0xff;
+ struct pci_bus *bus;
+ struct pci_dev *dev;
+ bool found = false;
+
+ /* First, check if the PME is from the root port itself. */
+ if (port->devfn == devfn && port->bus->number == busnr) {
+ if (pci_check_pme_status(port)) {
+ pm_request_resume(&port->dev);
+ found = true;
+ } else {
+ /*
+ * Apparently, the root port generated the PME on behalf
+ * of a non-PCIe device downstream. If this is done by
+ * a root port, the Requester ID field in its status
+ * register may contain either the root port's, or the
+ * source device's information (PCI Express Base
+ * Specification, Rev. 2.0, Section 6.1.9).
+ */
+ down_read(&pci_bus_sem);
+ found = pcie_pme_walk_bus(port->subordinate);
+ up_read(&pci_bus_sem);
+ }
+ goto out;
+ }
+
+ /* Second, find the bus the source device is on. */
+ bus = pci_find_bus(pci_domain_nr(port->bus), busnr);
+ if (!bus)
+ goto out;
+
+ /* Next, check if the PME is from a PCIe-PCI bridge. */
+ found = pcie_pme_from_pci_bridge(bus, devfn);
+ if (found)
+ goto out;
+
+ /* Finally, try to find the PME source on the bus. */
+ down_read(&pci_bus_sem);
+ list_for_each_entry(dev, &bus->devices, bus_list) {
+ pci_dev_get(dev);
+ if (dev->devfn == devfn) {
+ found = true;
+ break;
+ }
+ pci_dev_put(dev);
+ }
+ up_read(&pci_bus_sem);
+
+ if (found) {
+ /* The device is there, but we have to check its PME status. */
+ found = pci_check_pme_status(dev);
+ if (found)
+ pm_request_resume(&dev->dev);
+ pci_dev_put(dev);
+ } else if (devfn) {
+ /*
+ * The device is not there, but we can still try to recover by
+ * assuming that the PME was reported by a PCIe-PCI bridge that
+ * used devfn different from zero.
+ */
+ dev_dbg(&port->dev, "PME interrupt generated for "
+ "non-existent device %02x:%02x.%d\n",
+ busnr, PCI_SLOT(devfn), PCI_FUNC(devfn));
+ found = pcie_pme_from_pci_bridge(bus, 0);
+ }
+
+ out:
+ if (!found)
+ dev_dbg(&port->dev, "Spurious native PME interrupt!\n");
+}
+
+/**
+ * pcie_pme_work_fn - Work handler for PCIe PME interrupt.
+ * @work: Work structure giving access to service data.
+ */
+static void pcie_pme_work_fn(struct work_struct *work)
+{
+ struct pcie_pme_service_data *data =
+ container_of(work, struct pcie_pme_service_data, work);
+ struct pci_dev *port = data->srv->port;
+ int rtsta_pos;
+ u32 rtsta;
+
+ rtsta_pos = pci_find_capability(port, PCI_CAP_ID_EXP) + PCI_EXP_RTSTA;
+
+ spin_lock_irq(&data->lock);
+
+ for (;;) {
+ if (data->noirq)
+ break;
+
+ pci_read_config_dword(port, rtsta_pos, &rtsta);
+ if (rtsta & PCI_EXP_RTSTA_PME) {
+ /*
+ * Clear PME status of the port. If there are other
+ * pending PMEs, the status will be set again.
+ */
+ pcie_pme_clear_status(port);
+
+ spin_unlock_irq(&data->lock);
+ pcie_pme_handle_request(port, rtsta & 0xffff);
+ spin_lock_irq(&data->lock);
+
+ continue;
+ }
+
+ /* No need to loop if there are no more PMEs pending. */
+ if (!(rtsta & PCI_EXP_RTSTA_PENDING))
+ break;
+
+ spin_unlock_irq(&data->lock);
+ cpu_relax();
+ spin_lock_irq(&data->lock);
+ }
+
+ if (!data->noirq)
+ pcie_pme_interrupt_enable(port, true);
+
+ spin_unlock_irq(&data->lock);
+}
+
+/**
+ * pcie_pme_irq - Interrupt handler for PCIe root port PME interrupt.
+ * @irq: Interrupt vector.
+ * @context: Interrupt context pointer.
+ */
+static irqreturn_t pcie_pme_irq(int irq, void *context)
+{
+ struct pci_dev *port;
+ struct pcie_pme_service_data *data;
+ int rtsta_pos;
+ u32 rtsta;
+ unsigned long flags;
+
+ port = ((struct pcie_device *)context)->port;
+ data = get_service_data((struct pcie_device *)context);
+
+ rtsta_pos = pci_find_capability(port, PCI_CAP_ID_EXP) + PCI_EXP_RTSTA;
+
+ spin_lock_irqsave(&data->lock, flags);
+ pci_read_config_dword(port, rtsta_pos, &rtsta);
+
+ if (!(rtsta & PCI_EXP_RTSTA_PME)) {
+ spin_unlock_irqrestore(&data->lock, flags);
+ return IRQ_NONE;
+ }
+
+ pcie_pme_interrupt_enable(port, false);
+ spin_unlock_irqrestore(&data->lock, flags);
+
+ queue_work(pm_wq, &data->work);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * pcie_pme_probe - Initialize PCIe PME service for given root port.
+ * @srv - PCIe service to initialize.
+ */
+static int pcie_pme_probe(struct pcie_device *srv)
+{
+ struct pci_dev *port;
+ struct pcie_pme_service_data *data;
+ int ret;
+
+ ret = pcie_pme_platform_setup(srv);
+ if (ret)
+ return ret;
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ spin_lock_init(&data->lock);
+ INIT_WORK(&data->work, pcie_pme_work_fn);
+ data->srv = srv;
+ set_service_data(srv, data);
+
+ port = srv->port;
+ pcie_pme_interrupt_enable(port, false);
+ pcie_pme_clear_status(port);
+
+ ret = request_irq(srv->irq, pcie_pme_irq, IRQF_SHARED, "PCIe PME", srv);
+ if (ret)
+ kfree(data);
+ else
+ pcie_pme_interrupt_enable(port, true);
+
+ return ret;
+}
+
+/**
+ * pcie_pme_remove - Prepare PCIe PME service device for removal.
+ * @srv - PCIe service device to be removed.
+ */
+static void pcie_pme_remove(struct pcie_device *srv)
+{
+ struct pcie_pme_service_data *data = get_service_data(srv);
+ struct pci_dev *port = srv->port;
+
+ spin_lock_irq(&data->lock);
+ pcie_pme_interrupt_enable(port, false);
+ pcie_pme_clear_status(port);
+ data->noirq = true;
+ spin_unlock_irq(&data->lock);
+
+ flush_work(&data->work);
+ free_irq(srv->irq, srv);
+
+ set_service_data(srv, NULL);
+ kfree(data);
+}
+
+/**
+ * pcie_pme_suspend - Suspend PCIe PME service device.
+ * @srv - PCIe service device to suspend.
+ */
+static int pcie_pme_suspend(struct pcie_device *srv)
+{
+ struct pcie_pme_service_data *data = get_service_data(srv);
+ struct pci_dev *port = srv->port;
+
+ spin_lock_irq(&data->lock);
+ pcie_pme_interrupt_enable(port, false);
+ pcie_pme_clear_status(port);
+ data->noirq = true;
+ spin_unlock_irq(&data->lock);
+
+ return 0;
+}
+
+/**
+ * pcie_pme_resume - Resume PCIe PME service device.
+ * @srv - PCIe service device to resume.
+ */
+static int pcie_pme_resume(struct pcie_device *srv)
+{
+ struct pcie_pme_service_data *data = get_service_data(srv);
+ struct pci_dev *port = srv->port;
+
+ spin_lock_irq(&data->lock);
+ data->noirq = false;
+ pcie_pme_clear_status(port);
+ pcie_pme_interrupt_enable(port, true);
+ spin_unlock_irq(&data->lock);
+
+ return 0;
+}
+
+static struct pcie_port_service_driver pcie_pme_driver = {
+ .name = "pcie_pme",
+ .port_type = PCIE_RC_PORT,
+ .service = PCIE_PORT_SERVICE_PME,
+
+ .probe = pcie_pme_probe,
+ .remove = pcie_pme_remove,
+ .suspend = pcie_pme_suspend,
+ .resume = pcie_pme_resume,
+};
+
+/**
+ * pcie_pme_service_init - Register the PCIe PME service driver.
+ */
+static int __init pcie_pme_service_init(void)
+{
+ return pcie_pme_disabled ?
+ -ENODEV : pcie_port_service_register(&pcie_pme_driver);
+}
+
+/**
+ * pcie_pme_service_exit - Unregister the PCIe PME service driver.
+ */
+static void __exit pcie_pme_service_exit(void)
+{
+ pcie_port_service_unregister(&pcie_pme_driver);
+}
+
+module_init(pcie_pme_service_init);
+module_exit(pcie_pme_service_exit);
Index: linux-2.6/drivers/pci/pcie/pme/pcie_pme_acpi.c
===================================================================
--- /dev/null
+++ linux-2.6/drivers/pci/pcie/pme/pcie_pme_acpi.c
@@ -0,0 +1,54 @@
+/*
+ * PCIe Native PME support, ACPI-related part
+ *
+ * Copyright (C) 2009 Rafael J. Wysocki <[email protected]>, Novell Inc.
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License V2. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ */
+
+#include <linux/pci.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/acpi.h>
+#include <linux/pci-acpi.h>
+#include <linux/pcieport_if.h>
+
+/**
+ * pcie_pme_acpi_setup - Request the ACPI BIOS to release control over PCIe PME.
+ * @srv - PCIe PME service for a root port or event collector.
+ *
+ * Invoked when the PCIe bus type loads PCIe PME service driver. To avoid
+ * conflict with the BIOS PCIe support requires the BIOS to yield PCIe PME
+ * control to the kernel.
+ */
+int pcie_pme_acpi_setup(struct pcie_device *srv)
+{
+ acpi_status status = AE_NOT_FOUND;
+ struct pci_dev *port = srv->port;
+ acpi_handle handle;
+ int error = 0;
+
+ if (acpi_pci_disabled)
+ return -ENOSYS;
+
+ dev_info(&port->dev, "Requesting control of PCIe PME from ACPI BIOS\n");
+
+ handle = acpi_find_root_bridge_handle(port);
+ if (!handle)
+ return -EINVAL;
+
+ status = acpi_pci_osc_control_set(handle,
+ OSC_PCI_EXPRESS_PME_CONTROL |
+ OSC_PCI_EXPRESS_CAP_STRUCTURE_CONTROL);
+ if (ACPI_FAILURE(status)) {
+ dev_info(&port->dev,
+ "Failed to receive control of PCIe PME service: %s\n",
+ (status == AE_SUPPORT || status == AE_NOT_FOUND) ?
+ "no _OSC support" : "ACPI _OSC failed");
+ error = -ENODEV;
+ }
+
+ return error;
+}
Index: linux-2.6/drivers/pci/pcie/pme/Makefile
===================================================================
--- /dev/null
+++ linux-2.6/drivers/pci/pcie/pme/Makefile
@@ -0,0 +1,8 @@
+#
+# Makefile for PCI-Express Root Port PME signaling driver
+#
+
+obj-$(CONFIG_PCIE_PME) += pmedriver.o
+
+pmedriver-objs := pcie_pme.o
+pmedriver-$(CONFIG_ACPI) += pcie_pme_acpi.o
Index: linux-2.6/drivers/pci/pcie/pme/pcie_pme.h
===================================================================
--- /dev/null
+++ linux-2.6/drivers/pci/pcie/pme/pcie_pme.h
@@ -0,0 +1,28 @@
+/*
+ * drivers/pci/pcie/pme/pcie_pme.h
+ *
+ * PCI Express Root Port PME signaling support
+ *
+ * Copyright (C) 2009 Rafael J. Wysocki <[email protected]>, Novell Inc.
+ */
+
+#ifndef _PCIE_PME_H_
+#define _PCIE_PME_H_
+
+struct pcie_device;
+
+#ifdef CONFIG_ACPI
+extern int pcie_pme_acpi_setup(struct pcie_device *srv);
+
+static inline int pcie_pme_platform_notify(struct pcie_device *srv)
+{
+ return pcie_pme_acpi_setup(srv);
+}
+#else /* !CONFIG_ACPI */
+static inline int pcie_pme_platform_notify(struct pcie_device *srv)
+{
+ return 0;
+}
+#endif /* !CONFIG_ACPI */
+
+#endif
Index: linux-2.6/include/linux/pci.h
===================================================================
--- linux-2.6.orig/include/linux/pci.h
+++ linux-2.6/include/linux/pci.h
@@ -911,6 +911,12 @@ extern void pcie_set_ecrc_checking(struc
extern void pcie_ecrc_get_policy(char *str);
#endif
+#ifndef CONFIG_PCIE_PME
+static inline bool pcie_pme_enabled(struct pci_dev *dev) { return false; }
+#else
+extern bool pcie_pme_enabled(struct pci_dev *dev);
+#endif
+
#define pci_enable_msi(pdev) pci_enable_msi_block(pdev, 1)
#ifdef CONFIG_HT_IRQ
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
@@ -116,7 +116,7 @@ static void acpi_pci_propagate_wakeup_en
int ret;
ret = acpi_pm_device_sleep_wake(&bridge->dev, enable);
- if (!ret || bridge->is_pcie)
+ if (!ret || pcie_pme_enabled(bridge))
return;
bus = bus->parent;
}
@@ -131,7 +131,7 @@ static int acpi_pci_sleep_wake(struct pc
if (acpi_pci_can_wakeup(dev))
return acpi_pm_device_sleep_wake(&dev->dev, enable);
- if (!dev->is_pcie)
+ if (!pcie_pme_enabled(dev))
acpi_pci_propagate_wakeup_enable(dev->bus, enable);
return 0;
From: Rafael J. Wysocki <[email protected]>
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 devices tend to share wake-up GPEs, in which
cases it makes sense to install an ACPI notify handler for only one
of them and "bind" the other devices to it. Furthermore, 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 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 and implemet that callback 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_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 <[email protected]>
---
drivers/acpi/pci_bind.c | 19
drivers/acpi/pci_root.c | 5
drivers/acpi/sleep.c | 4
drivers/acpi/wakeup.c | 50 ++
drivers/pci/hotplug/acpiphp_glue.c | 23 -
drivers/pci/pci-acpi.c | 706 +++++++++++++++++++++++++++++++++++++
drivers/pci/pci.c | 32 +
drivers/pci/pci.h | 19
include/acpi/acpi_bus.h | 20 -
include/linux/pci-acpi.h | 14
kernel/power/Kconfig | 5
11 files changed, 874 insertions(+), 23 deletions(-)
Index: linux-2.6/drivers/pci/pci.h
===================================================================
--- linux-2.6.orig/drivers/pci/pci.h
+++ linux-2.6/drivers/pci/pci.h
@@ -30,10 +30,14 @@ int pci_probe_reset_function(struct pci_
* 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.
@@ -44,16 +48,25 @@ 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 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);
+static inline void pci_pme_wakeup(struct pci_dev *dev)
+{
+ __pci_pme_wakeup(dev, NULL);
+}
+
static inline bool pci_is_bridge(struct pci_dev *pci_dev)
{
return !!(pci_dev->subordinate);
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
@@ -19,6 +19,682 @@
#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.
+ *
+ * Moreover, there may be many PCI devices, including bridges, that share one
+ * wake-up GPE and if a wake-up event is signaled for a bridge, we will check
+ * the PME status of all devices below it, so if a device below a bridge shares
+ * a wake-up GPE with the bridge, it only makes sense to install a notify
+ * handled for the bridge, because the device is going to be checked anyway in
+ * the process of handling the bridge notification. In particular, if any
+ * devices share a wake-up GPE with the root bridge, there only need to be a
+ * notify handler for the root bridge, because the other devices will be checked
+ * in the process of handling the root bridge wake-up.
+ *
+ * Furthermore, if many devices share one wake-up GPE, we only need to install
+ * a notify handler for one of them as long as we know which devices to check
+ * in the process of handling the notification. The purpose of the data
+ * structures and helper functions below is to arrange things in accordance with
+ * these observations.
+ *
+ * pci_acpi_runtime_notifiers is a list of struct pci_acpi_notifier_block
+ * objects that each represent ACPI devices that have ACPI system notify
+ * handlers installed. For each of them, there is a 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 a list of PCI buses and a list of PCI devices for which
+ * to execute PME handlers in the case of ACPI-based PM events. Since every
+ * object may represent multiple PCI devices and/or buses to handle if power
+ * management event is signaled via the GPE associated with it, there are
+ * reference counters for tracking the usage of each object.
+ */
+
+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 list_head pm_buses;
+ struct list_head pm_devices;
+ int pm_enable_count;
+};
+
+struct pci_bus_notifier_entry
+{
+ struct list_head entry;
+ struct pci_bus *bus;
+ int enable_count;
+};
+
+struct pci_dev_notifier_entry
+{
+ struct list_head entry;
+ struct pci_dev *dev;
+ bool enabled;
+};
+
+/**
+ * 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 each bus and for each devices that
+ * should be checked for the PME status. 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 && nb->pm_enable_count) {
+ if (!list_empty(&nb->pm_buses)) {
+ struct pci_bus_notifier_entry *bne;
+
+ list_for_each_entry(bne, &nb->pm_buses, entry)
+ if (bne->enable_count)
+ pci_pme_wakeup_bus(bne->bus);
+ }
+ if (!list_empty(&nb->pm_devices)) {
+ struct pci_dev_notifier_entry *dne;
+
+ list_for_each_entry(dne, &nb->pm_devices, entry)
+ if (dne->enabled)
+ pci_pme_wakeup(dne->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;
+ INIT_LIST_HEAD(&nb->pm_buses);
+ INIT_LIST_HEAD(&nb->pm_devices);
+
+ 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_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)
+ goto found;
+
+ mutex_unlock(&pci_acpi_notifier_mtx);
+ return AE_NOT_FOUND;
+
+ found:
+ if (list_empty(&nb->pm_buses) && list_empty(&nb->pm_devices)) {
+ status = acpi_remove_notify_handler(handle, ACPI_SYSTEM_NOTIFY,
+ pci_acpi_event_fn);
+ list_del(&nb->entry);
+ kfree(nb);
+ } else {
+ nb->hp_data = NULL;
+ nb->hp_cb = NULL;
+ }
+
+ mutex_unlock(&pci_acpi_notifier_mtx);
+ return status;
+}
+EXPORT_SYMBOL_GPL(pci_acpi_remove_hp_notifier);
+
+/**
+ * bus_match - Check if one PCI bus is upstream from another.
+ * @target: PCI bus expected to be upstream from the other.
+ * @bus: PCI bus expected to be downstream from the other.
+ *
+ * Return 'ture' if @target is upstream with respect to @bus.
+ */
+static inline bool bus_match(struct pci_bus *target, struct pci_bus *bus)
+{
+ return pci_domain_nr(target) == pci_domain_nr(bus)
+ && (pci_is_root_bus(target) || (bus->number >= target->number
+ && bus->number <= target->subordinate));
+}
+
+/**
+ * new_dev_entry - Create a new device notifier entry.
+ * @dev: PCI device to create the entry for.
+ * @list: List to add the new entry to.
+ */
+static struct pci_dev_notifier_entry *new_dev_entry(struct pci_dev *dev,
+ struct list_head *list)
+{
+ struct pci_dev_notifier_entry *dne;
+
+ dne = kzalloc(sizeof(*dne), GFP_KERNEL);
+ if (!dne)
+ return NULL;
+
+ dne->dev = dev;
+ list_add_tail(&dne->entry, list);
+
+ return dne;
+}
+
+/**
+ * pci_acpi_add_device_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.
+ *
+ * Check if there is a notifier object for @dev and if that is the case, add
+ * @pci_dev to its list of devices whose PME status should be checked if a PM
+ * event is signaled for @dev, unless @pci_dev is covered by one of the buses
+ * in the notifier's bus list. Otherwise, create a new notifier object for @dev
+ * and add @pci_dev to its list of devices whose PME status to check.
+ */
+acpi_status pci_acpi_add_device_pm_notifier(struct acpi_device *dev,
+ struct pci_dev *pci_dev)
+{
+ struct pci_acpi_notifier_block *nb;
+ struct pci_dev_notifier_entry *dne;
+ struct pci_bus *bus = pci_dev->bus;
+ acpi_status status = AE_OK;
+
+ if (!dev->wakeup.flags.valid)
+ return AE_BAD_PARAMETER;
+
+ mutex_lock(&pci_acpi_notifier_mtx);
+
+ list_for_each_entry(nb, &pci_acpi_runtime_notifiers, entry) {
+ struct pci_bus_notifier_entry *bne;
+
+ if (!acpi_wakeup_gpe_shared(nb->dev, dev))
+ continue;
+
+ list_for_each_entry(bne, &nb->pm_buses, entry)
+ if (bus_match(bne->bus, bus))
+ goto out;
+
+ list_for_each_entry(dne, &nb->pm_devices, entry)
+ if (dne->dev == pci_dev)
+ goto out;
+
+ if (!new_dev_entry(pci_dev, &nb->pm_devices))
+ status = AE_NO_MEMORY;
+ goto out;
+ }
+
+ nb = new_notifier(dev);
+ if (!nb) {
+ status = AE_NO_MEMORY;
+ goto out;
+ }
+
+ dne = new_dev_entry(pci_dev, &nb->pm_devices);
+ if (!dne) {
+ kfree(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(&dne->entry);
+ kfree(dne);
+ list_del(&nb->entry);
+ kfree(nb);
+ }
+
+ out:
+ mutex_unlock(&pci_acpi_notifier_mtx);
+ return status;
+}
+
+/**
+ * pci_acpi_remove_device_pm_notifier - Unregister PM notifier for given device.
+ * @dev: ACPI device to remove the notifier from.
+ * @pci_dev: PCI device whose PME status is checked if an event is signaled.
+ *
+ * Find the notifier object for @dev and remove @pci_dev from its list of
+ * devices whose PME status to check when wake-up event is signaled. If the
+ * notifier object is not necessary any more after that, remove it too.
+ */
+acpi_status pci_acpi_remove_device_pm_notifier(struct acpi_device *dev,
+ struct pci_dev *pci_dev)
+{
+ struct pci_acpi_notifier_block *nb;
+ struct pci_dev_notifier_entry *dne = NULL;
+ acpi_status status = AE_OK;
+
+ mutex_lock(&pci_acpi_notifier_mtx);
+
+ list_for_each_entry(nb, &pci_acpi_runtime_notifiers, entry) {
+ if (!acpi_wakeup_gpe_shared(nb->dev, dev))
+ continue;
+
+ list_for_each_entry(dne, &nb->pm_devices, entry)
+ if (dne->dev == pci_dev)
+ goto found;
+ }
+
+ mutex_unlock(&pci_acpi_notifier_mtx);
+ return AE_NOT_FOUND;
+
+ found:
+ if (dne->enabled) {
+ if (!--nb->pm_enable_count)
+ acpi_device_run_wake(nb->dev, false);
+ }
+ list_del(&dne->entry);
+ kfree(dne);
+
+ if (list_empty(&nb->pm_devices) && list_empty(&nb->pm_buses)
+ && !nb->hp_cb) {
+ status = acpi_remove_notify_handler(nb->dev->handle,
+ ACPI_SYSTEM_NOTIFY,
+ pci_acpi_event_fn);
+ list_del(&nb->entry);
+ kfree(nb);
+ }
+
+ mutex_unlock(&pci_acpi_notifier_mtx);
+ return status;
+}
+
+/**
+ * new_bus_entry - Create a new bus notifier entry.
+ * @bus: PCI bus to create the entry for.
+ * @list: List to add the new entry to.
+ */
+static struct pci_bus_notifier_entry *new_bus_entry(struct pci_bus *bus,
+ struct list_head *list)
+{
+ struct pci_bus_notifier_entry *bne;
+
+ bne = kzalloc(sizeof(*bne), GFP_KERNEL);
+ if (!bne)
+ return NULL;
+
+ bne->bus = bus;
+ list_add_tail(&bne->entry, list);
+
+ return bne;
+}
+
+/**
+ * pci_acpi_add_bus_pm_notifier - Register PM notifier for given PCI bus.
+ * @dev: ACPI device to add the notifier for.
+ * @bus: PCI bus to walk if an event is signaled.
+ *
+ * Check if there is a notifier object for @dev and if that is the case, add
+ * @bus to its list of buses to walk, checking the PME status of all devices on
+ * them, if a PM event is signaled for @dev, unless @bus or a PCI bus upstream
+ * with respect to it is in the list already. Otherwise, create a new notifier
+ * object for @dev and add @bus to its list of buses to walk if a PM event is
+ * signaled.
+ */
+acpi_status pci_acpi_add_bus_pm_notifier(struct acpi_device *dev,
+ struct pci_bus *bus)
+{
+ struct pci_acpi_notifier_block *nb;
+ struct pci_bus_notifier_entry *bne;
+ acpi_status status = AE_OK;
+
+ if (!dev->wakeup.flags.valid)
+ return AE_BAD_PARAMETER;
+
+ mutex_lock(&pci_acpi_notifier_mtx);
+
+ list_for_each_entry(nb, &pci_acpi_runtime_notifiers, entry) {
+ if (!acpi_wakeup_gpe_shared(nb->dev, dev))
+ continue;
+
+ list_for_each_entry(bne, &nb->pm_buses, entry)
+ if (bus_match(bne->bus, bus))
+ goto out;
+
+ if (!new_bus_entry(bus, &nb->pm_buses))
+ status = AE_NO_MEMORY;
+ goto out;
+ }
+
+ nb = new_notifier(dev);
+ if (!nb) {
+ status = AE_NO_MEMORY;
+ goto out;
+ }
+
+ bne = new_bus_entry(bus, &nb->pm_buses);
+ if (!bne) {
+ kfree(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(&bne->entry);
+ kfree(bne);
+ list_del(&nb->entry);
+ kfree(nb);
+ }
+
+ out:
+ mutex_unlock(&pci_acpi_notifier_mtx);
+ return status;
+}
+
+/**
+ * pci_acpi_remove_bus_pm_notifier - Unregister PM notifier for given PCI bus.
+ * @dev: ACPI device to remove the notifier from.
+ * @bus: PCI bus that is walked if an event is signaled.
+ *
+ * Find the notifier object for @dev and remove @bus from its list of buses to
+ * walk when wake-up event is signaled. If the notifier object is not necessary
+ * any more after that, remove it too.
+ */
+acpi_status pci_acpi_remove_bus_pm_notifier(struct acpi_device *dev,
+ struct pci_bus *bus)
+{
+ struct pci_acpi_notifier_block *nb;
+ struct pci_bus_notifier_entry *bne = NULL;
+ acpi_status status = AE_OK;
+
+ mutex_lock(&pci_acpi_notifier_mtx);
+
+ list_for_each_entry(nb, &pci_acpi_runtime_notifiers, entry) {
+ if (!acpi_wakeup_gpe_shared(nb->dev, dev))
+ continue;
+
+ list_for_each_entry(bne, &nb->pm_buses, entry)
+ if (bne->bus == bus)
+ goto found;
+ }
+
+ mutex_unlock(&pci_acpi_notifier_mtx);
+ return AE_NOT_FOUND;
+
+ found:
+ if (bne->enable_count) {
+ nb->pm_enable_count -= bne->enable_count;
+ if (!nb->pm_enable_count)
+ acpi_device_run_wake(nb->dev, false);
+ }
+ list_del(&bne->entry);
+ kfree(bne);
+
+ if (list_empty(&nb->pm_devices) && list_empty(&nb->pm_buses)
+ && !nb->hp_cb) {
+ status = acpi_remove_notify_handler(nb->dev->handle,
+ ACPI_SYSTEM_NOTIFY,
+ pci_acpi_event_fn);
+ list_del(&nb->entry);
+ kfree(nb);
+ }
+
+ mutex_unlock(&pci_acpi_notifier_mtx);
+ return status;
+}
+
+/**
+ * dev_run_wake - Enable/disable the GPE associated with given notifier.
+ * @nb: Notifier to enable/disable the GPE for.
+ * @dne: Device notifier entry for the device being enabled/disabled to wake-up.
+ * @enable: Whether to enable or disable the GPE.
+ */
+static int dev_run_wake(struct pci_acpi_notifier_block *nb,
+ struct pci_dev_notifier_entry *dne, bool enable)
+{
+ int error = 0;
+
+ if (enable) {
+ dne->enabled = true;
+ if (!nb->pm_enable_count++)
+ acpi_device_run_wake(nb->dev, true);
+ } else if (dne->enabled) {
+ dne->enabled = false;
+ if (!--nb->pm_enable_count)
+ acpi_device_run_wake(nb->dev, false);
+ } else {
+ error = -EALREADY;
+ }
+
+ return error;
+}
+
+/**
+ * bus_run_wake - Enable/disable the GPE associated with given notifier.
+ * @nb: Notifier to enable/disable the GPE for.
+ * @bne: Bus notifier entry for a bridge being enabled/disabled to wake-up.
+ * @enable: Whether to enable or disable the GPE.
+ */
+static int bus_run_wake(struct pci_acpi_notifier_block *nb,
+ struct pci_bus_notifier_entry *bne, bool enable)
+{
+ int error = 0;
+
+ if (enable) {
+ bne->enable_count++;
+ if (!nb->pm_enable_count++)
+ acpi_device_run_wake(nb->dev, true);
+ } else if (bne->enable_count) {
+ bne->enable_count--;
+ if (!--nb->pm_enable_count)
+ acpi_device_run_wake(nb->dev, false);
+ } else {
+ error = -EALREADY;
+ }
+
+ return error;
+}
+
+/**
+ * __acpi_dev_run_wake - Enable/disable wake-up for given PCI device.
+ * @pci_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(struct pci_dev *pci_dev, bool enable)
+{
+ struct pci_acpi_notifier_block *nb;
+ struct pci_bus *bus = pci_dev->bus;
+ struct acpi_device *dev;
+ acpi_handle handle;
+ int error = -ENODEV;
+
+ if (!device_can_wakeup(&pci_dev->dev))
+ return -EINVAL;
+
+ handle = DEVICE_ACPI_HANDLE(&pci_dev->dev);
+ if (!handle || ACPI_FAILURE(acpi_bus_get_device(handle, &dev))) {
+ dev_dbg(&pci_dev->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) {
+ struct pci_bus_notifier_entry *bne;
+ struct pci_dev_notifier_entry *dne;
+
+ if (!acpi_wakeup_gpe_shared(nb->dev, dev))
+ continue;
+
+ list_for_each_entry(bne, &nb->pm_buses, entry)
+ if (bus_match(bne->bus, bus)) {
+ error = bus_run_wake(nb, bne, enable);
+ goto out;
+ }
+
+ list_for_each_entry(dne, &nb->pm_devices, entry)
+ if (dne->dev == pci_dev) {
+ error = dev_run_wake(nb, dne, enable);
+ goto out;
+ }
+ }
+
+ out:
+ mutex_unlock(&pci_acpi_notifier_mtx);
+ return error;
+}
+
+/**
+ * __acpi_bridge_run_wake - Enable/disable wake-up for given bridge.
+ * @bridge: Bridge 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 @bridge and try to enable/disable
+ * the GPE associated with it, under the assumption that @bridge may be a PCI
+ * root bridge.
+ */
+static int __acpi_bridge_run_wake(struct device *bridge, bool enable)
+{
+ struct pci_acpi_notifier_block *nb;
+ struct acpi_device *dev;
+ acpi_handle handle;
+ int error = -ENODEV;
+
+ if (!device_can_wakeup(bridge))
+ return -EINVAL;
+
+ handle = DEVICE_ACPI_HANDLE(bridge);
+ if (!handle || ACPI_FAILURE(acpi_bus_get_device(handle, &dev))) {
+ dev_dbg(bridge, "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) {
+ struct pci_bus_notifier_entry *bne;
+
+ if (!acpi_wakeup_gpe_shared(nb->dev, dev))
+ continue;
+
+ list_for_each_entry(bne, &nb->pm_buses, entry)
+ if (bne->bus->bridge == bridge) {
+ error = bus_run_wake(nb, bne, enable);
+ goto out;
+ }
+ }
+
+ out:
+ 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".
*
@@ -137,12 +813,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_dev_run_wake(bridge, enable))
+ return;
+ bus = bus->parent;
+ }
+
+ /* We have reached the root bus. */
+ if (bus->bridge)
+ __acpi_bridge_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_dev_run_wake(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"
@@ -434,6 +435,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
@@ -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.
+ */
+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#.
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,56 @@ void acpi_disable_wakeup_device(u8 sleep
}
}
+#ifdef CONFIG_PM_WAKEUP
+/**
+ * acpi_device_run_wake - Enable/disable ACPI BIOS to generate wake-up events.
+ * @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 acpi_device *dev, bool enable)
+{
+ if (!dev || !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;
+}
+
+/**
+ * acpi_wakeup_gpes_shared - Check if given ACPI devices share a wake-up GPE.
+ * @deva: First ACPI device to check.
+ * @devb: Second ACPI device to check.
+ */
+bool acpi_wakeup_gpe_shared(struct acpi_device *deva, struct acpi_device *devb)
+{
+ return deva->wakeup.flags.valid && devb->wakeup.flags.valid
+ && deva->wakeup.gpe_device == devb->wakeup.gpe_device
+ && deva->wakeup.gpe_number == devb->wakeup.gpe_number;
+}
+#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
@@ -386,21 +386,35 @@ 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_device_sleep_wake(struct device *, bool);
-#else /* !CONFIG_PM_SLEEP */
+int acpi_device_run_wake(struct acpi_device *, bool);
+bool acpi_wakeup_gpe_shared(struct acpi_device *, struct acpi_device *);
+#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;
+}
+
+static inline bool acpi_wakeup_gpe_shared(struct acpi_device *a,
+ struct acpi_device *b)
+{
+ return false;
+}
+#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
@@ -602,7 +602,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
@@ -717,7 +717,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
@@ -577,6 +577,9 @@ static int __devinit acpi_pci_root_add(s
if (flags != base_flags)
acpi_pci_osc_support(root, flags);
+ if (device->wakeup.flags.valid)
+ pci_acpi_add_bus_pm_notifier(device, root->bus);
+
return 0;
end:
@@ -598,6 +601,8 @@ static int acpi_pci_root_remove(struct a
{
struct acpi_pci_root *root = acpi_driver_data(device);
+ if (device->wakeup.flags.valid)
+ pci_acpi_remove_bus_pm_notifier(device, root->bus);
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,20 @@
#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_device_pm_notifier(struct acpi_device *dev,
+ struct pci_dev *pci_dev);
+extern acpi_status pci_acpi_remove_device_pm_notifier(struct acpi_device *dev,
+ struct pci_dev *pci_dev);
+extern acpi_status pci_acpi_add_bus_pm_notifier(struct acpi_device *dev,
+ struct pci_bus *bus);
+extern acpi_status pci_acpi_remove_bus_pm_notifier(struct acpi_device *dev,
+ struct pci_bus *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,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>
@@ -38,7 +39,17 @@ 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;
+
+ if (device->wakeup.flags.valid) {
+ if (dev->subordinate)
+ pci_acpi_remove_bus_pm_notifier(device,
+ dev->subordinate);
+ pci_acpi_remove_device_pm_notifier(device, dev);
+ }
+
+ if (!dev->subordinate)
goto out;
acpi_pci_irq_del_prt(dev->subordinate);
@@ -94,6 +105,12 @@ static int acpi_pci_bind(struct acpi_dev
acpi_pci_irq_add_prt(device->handle, bus);
+ if (device->wakeup.flags.valid) {
+ pci_acpi_add_device_pm_notifier(device, dev);
+ if (dev->subordinate)
+ pci_acpi_add_bus_pm_notifier(device, dev->subordinate);
+ }
+
out:
pci_dev_put(dev);
return 0;
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 ;
From: Rafael J. Wysocki <[email protected]>
Introduce run-time PM callbacks for the PCI bus type. Make the new
callbacks work in analogy with the existing system sleep PM
callbacks, so that the drivers already converted to struct dev_pm_ops
can use their suspend and resume routines for run-time PM without
modifications.
Signed-off-by: Rafael J. Wysocki <[email protected]>
---
drivers/pci/pci-driver.c | 97 ++++++++++++++++++++++++++++++++++++++++++++---
kernel/power/Kconfig | 5 ++
2 files changed, 97 insertions(+), 5 deletions(-)
Index: linux-2.6/kernel/power/Kconfig
===================================================================
--- linux-2.6.orig/kernel/power/Kconfig
+++ linux-2.6/kernel/power/Kconfig
@@ -241,3 +241,8 @@ config PM_WAKEUP
bool
depends on SUSPEND || HIBERNATION || PM_RUNTIME
default y
+
+config PM_OPS
+ bool
+ depends on PM_SLEEP || PM_RUNTIME
+ default y
Index: linux-2.6/drivers/pci/pci-driver.c
===================================================================
--- linux-2.6.orig/drivers/pci/pci-driver.c
+++ linux-2.6/drivers/pci/pci-driver.c
@@ -17,6 +17,7 @@
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/cpu.h>
+#include <linux/pm_runtime.h>
#include "pci.h"
struct pci_dynid {
@@ -537,7 +538,7 @@ static int pci_restore_standard_config(s
return pci_restore_state(pci_dev);
}
-static void pci_pm_default_resume_noirq(struct pci_dev *pci_dev)
+static void pci_pm_default_resume_early(struct pci_dev *pci_dev)
{
pci_restore_standard_config(pci_dev);
pci_fixup_device(pci_fixup_resume_early, pci_dev);
@@ -681,7 +682,7 @@ static int pci_pm_resume_noirq(struct de
struct device_driver *drv = dev->driver;
int error = 0;
- pci_pm_default_resume_noirq(pci_dev);
+ pci_pm_default_resume_early(pci_dev);
if (pci_has_legacy_pm_support(pci_dev))
return pci_legacy_resume_early(dev);
@@ -879,7 +880,7 @@ static int pci_pm_restore_noirq(struct d
struct device_driver *drv = dev->driver;
int error = 0;
- pci_pm_default_resume_noirq(pci_dev);
+ pci_pm_default_resume_early(pci_dev);
if (pci_has_legacy_pm_support(pci_dev))
return pci_legacy_resume_early(dev);
@@ -931,6 +932,89 @@ static int pci_pm_restore(struct device
#endif /* !CONFIG_HIBERNATION */
+#endif /* !CONFIG_PM_SLEEP */
+
+#ifdef CONFIG_PM_RUNTIME
+
+static int pci_pm_runtime_suspend(struct device *dev)
+{
+ struct pci_dev *pci_dev = to_pci_dev(dev);
+ const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
+ pci_power_t prev = pci_dev->current_state;
+ int error;
+
+ if (!pm || !pm->runtime_suspend)
+ return -ENOSYS;
+
+ error = pm->runtime_suspend(dev);
+ suspend_report_result(pm->runtime_suspend, error);
+ if (error)
+ return error;
+
+ pci_fixup_device(pci_fixup_suspend, pci_dev);
+
+ if (!pci_dev->state_saved && pci_dev->current_state != PCI_D0
+ && pci_dev->current_state != PCI_UNKNOWN) {
+ WARN_ONCE(pci_dev->current_state != prev,
+ "PCI PM: State of device not saved by %pF\n",
+ pm->runtime_suspend);
+ return 0;
+ }
+
+ if (!pci_dev->state_saved) {
+ pci_save_state(pci_dev);
+ if (!pci_is_bridge(pci_dev))
+ pci_prepare_to_sleep(pci_dev);
+ }
+
+ pci_platform_run_wake(pci_dev, true);
+
+ return 0;
+}
+
+static int pci_pm_runtime_resume(struct device *dev)
+{
+ struct pci_dev *pci_dev = to_pci_dev(dev);
+ const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
+
+ if (!pm || !pm->runtime_resume)
+ return -ENOSYS;
+
+ pci_platform_run_wake(pci_dev, false);
+ pci_pm_default_resume_early(pci_dev);
+ pci_pm_default_resume(pci_dev);
+
+ return pm->runtime_resume(dev);
+}
+
+static int pci_pm_runtime_idle(struct device *dev)
+{
+ const struct dev_pm_ops *pm = dev->driver ? dev->driver->pm : NULL;
+
+ if (!pm)
+ return -ENOSYS;
+
+ if (pm->runtime_idle) {
+ int ret = pm->runtime_idle(dev);
+ if (ret)
+ return ret;
+ }
+
+ pm_runtime_suspend(dev);
+
+ return 0;
+}
+
+#else /* !CONFIG_PM_RUNTIME */
+
+#define pci_pm_runtime_suspend NULL
+#define pci_pm_runtime_resume NULL
+#define pci_pm_runtime_idle NULL
+
+#endif /* !CONFIG_PM_RUNTIME */
+
+#ifdef CONFIG_PM_OPS
+
const struct dev_pm_ops pci_dev_pm_ops = {
.prepare = pci_pm_prepare,
.complete = pci_pm_complete,
@@ -946,15 +1030,18 @@ const struct dev_pm_ops pci_dev_pm_ops =
.thaw_noirq = pci_pm_thaw_noirq,
.poweroff_noirq = pci_pm_poweroff_noirq,
.restore_noirq = pci_pm_restore_noirq,
+ .runtime_suspend = pci_pm_runtime_suspend,
+ .runtime_resume = pci_pm_runtime_resume,
+ .runtime_idle = pci_pm_runtime_idle,
};
#define PCI_PM_OPS_PTR (&pci_dev_pm_ops)
-#else /* !CONFIG_PM_SLEEP */
+#else /* !COMFIG_PM_OPS */
#define PCI_PM_OPS_PTR NULL
-#endif /* !CONFIG_PM_SLEEP */
+#endif /* !COMFIG_PM_OPS */
/**
* __pci_register_driver - register a new pci driver
On Thursday 08 October 2009 04:52:48 pm Rafael J. Wysocki wrote:
> From: Rafael J. Wysocki <[email protected]>
>
> Add function pci_check_pme_status() that will check the PME status
> bit of given device and clear it along with the PME enable bit. It
> will be necessary for PCI run-time power management.
>
> Based on a patch from Shaohua Li <[email protected]>
>
> Signed-off-by: Rafael J. Wysocki <[email protected]>
> ---
> drivers/pci/pci.c | 35 +++++++++++++++++++++++++++++++++++
> drivers/pci/pci.h | 1 +
> 2 files changed, 36 insertions(+)
>
> Index: linux-2.6/drivers/pci/pci.h
> ===================================================================
> --- linux-2.6.orig/drivers/pci/pci.h
> +++ linux-2.6/drivers/pci/pci.h
> @@ -49,6 +49,7 @@ struct pci_platform_pm_ops {
> 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 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.c
> ===================================================================
> --- linux-2.6.orig/drivers/pci/pci.c
> +++ linux-2.6/drivers/pci/pci.c
> @@ -1167,6 +1167,41 @@ int pci_set_pcie_reset_state(struct pci_
> }
>
> /**
> + * pci_check_pme_status - Check if given device has generated PME.
> + * @dev: Device to check.
> + *
> + * Check the PME status of the device, clear PME status and PME enable. Return
> + * 'true' if PME has been generated by the device (and hasn't been spurious) or
> + * 'false' otherwise.
This comment confuses me because it implies that we always clear PME enable,
but that's not what the code does. If PME_STATUS is not asserted, the code
doesn't write anything.
> + */
> +bool pci_check_pme_status(struct pci_dev *dev)
> +{
> + int pmcsr_pos;
> + u16 pmcsr;
> + bool ret = false;
> +
> + if (!dev->pm_cap)
> + return false;
> +
> + pmcsr_pos = dev->pm_cap + PCI_PM_CTRL;
> + /* clear PME status and disable PME to avoid interrupt flood */
> + pci_read_config_word(dev, pmcsr_pos, &pmcsr);
> + if (!(pmcsr & PCI_PM_CTRL_PME_STATUS))
> + return false;
> +
> + pmcsr |= PCI_PM_CTRL_PME_STATUS;
> + /* Ignore spurious PME or clear PME enable if it's not spurious. */
> + if (pmcsr & PCI_PM_CTRL_PME_ENABLE) {
> + pmcsr &= ~PCI_PM_CTRL_PME_ENABLE;
> + ret = true;
> + }
> +
> + pci_write_config_word(dev, pmcsr_pos, pmcsr);
I can't tell whether the comment or the code is what was intended,
but I think the following would be a clearer way to implement the
comment:
pci_read_config_word(..., &pmcsr);
pci_write_config_word(..., (pmcsr | STATUS) & ~ENABLE);
if ((pmcsr & ENABLE) && (pmcsr & STATUS))
return true;
return false;
Bjorn
> +
> + return ret;
> +}
> +
> +/**
> * 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#.
>
On Fri, Oct 09, 2009 at 12:54:18AM +0200, Rafael J. Wysocki wrote:
> 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 devices tend to share wake-up GPEs, in which
> cases it makes sense to install an ACPI notify handler for only one
> of them and "bind" the other devices to it. Furthermore, 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.
I'm not sure this approach is correct - for instance, from a Dell
system:
Method (_L0D, 0, NotSerialized)
{
Store (SMI (0xC6, 0x00), Local0)
If (And (Local0, 0x01))
{
Notify (\_SB.PCI0.AZAL, 0x02)
}
If (And (Local0, 0x02))
{
Notify (\_SB.PCI0.EHCI, 0x02)
}
If (And (Local0, 0x04))
{
Notify (\_SB.PCI0.EHC2, 0x02)
}
}
All these devices share a GPE, but registering only one notify handler
would result in us missing the notification in some cases. I also
suspect that this should wait until the GPE rework is done.
--
Matthew Garrett | [email protected]
On Friday 09 October 2009, Bjorn Helgaas wrote:
> On Thursday 08 October 2009 04:52:48 pm Rafael J. Wysocki wrote:
> > From: Rafael J. Wysocki <[email protected]>
> >
> > Add function pci_check_pme_status() that will check the PME status
> > bit of given device and clear it along with the PME enable bit. It
> > will be necessary for PCI run-time power management.
> >
> > Based on a patch from Shaohua Li <[email protected]>
> >
> > Signed-off-by: Rafael J. Wysocki <[email protected]>
> > ---
> > drivers/pci/pci.c | 35 +++++++++++++++++++++++++++++++++++
> > drivers/pci/pci.h | 1 +
> > 2 files changed, 36 insertions(+)
> >
> > Index: linux-2.6/drivers/pci/pci.h
> > ===================================================================
> > --- linux-2.6.orig/drivers/pci/pci.h
> > +++ linux-2.6/drivers/pci/pci.h
> > @@ -49,6 +49,7 @@ struct pci_platform_pm_ops {
> > 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 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.c
> > ===================================================================
> > --- linux-2.6.orig/drivers/pci/pci.c
> > +++ linux-2.6/drivers/pci/pci.c
> > @@ -1167,6 +1167,41 @@ int pci_set_pcie_reset_state(struct pci_
> > }
> >
> > /**
> > + * pci_check_pme_status - Check if given device has generated PME.
> > + * @dev: Device to check.
> > + *
> > + * Check the PME status of the device, clear PME status and PME enable. Return
> > + * 'true' if PME has been generated by the device (and hasn't been spurious) or
> > + * 'false' otherwise.
>
> This comment confuses me because it implies that we always clear PME enable,
> but that's not what the code does. If PME_STATUS is not asserted, the code
> doesn't write anything.
Well, that's a shortcut, perhaps going too far. It should say "clear PME
status and PME enable, if the PME status was set". Will fix.
> > + */
> > +bool pci_check_pme_status(struct pci_dev *dev)
> > +{
> > + int pmcsr_pos;
> > + u16 pmcsr;
> > + bool ret = false;
> > +
> > + if (!dev->pm_cap)
> > + return false;
> > +
> > + pmcsr_pos = dev->pm_cap + PCI_PM_CTRL;
> > + /* clear PME status and disable PME to avoid interrupt flood */
> > + pci_read_config_word(dev, pmcsr_pos, &pmcsr);
> > + if (!(pmcsr & PCI_PM_CTRL_PME_STATUS))
> > + return false;
> > +
> > + pmcsr |= PCI_PM_CTRL_PME_STATUS;
> > + /* Ignore spurious PME or clear PME enable if it's not spurious. */
> > + if (pmcsr & PCI_PM_CTRL_PME_ENABLE) {
> > + pmcsr &= ~PCI_PM_CTRL_PME_ENABLE;
> > + ret = true;
> > + }
> > +
> > + pci_write_config_word(dev, pmcsr_pos, pmcsr);
>
> I can't tell whether the comment or the code is what was intended,
The code. I'll fix the comment.
> but I think the following would be a clearer way to implement the
> comment:
>
> pci_read_config_word(..., &pmcsr);
> pci_write_config_word(..., (pmcsr | STATUS) & ~ENABLE);
>
> if ((pmcsr & ENABLE) && (pmcsr & STATUS))
> return true;
> return false;
Thanks,
Rafael
On Friday 09 October 2009, Matthew Garrett wrote:
> On Fri, Oct 09, 2009 at 12:54:18AM +0200, Rafael J. Wysocki wrote:
>
> > 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 devices tend to share wake-up GPEs, in which
> > cases it makes sense to install an ACPI notify handler for only one
> > of them and "bind" the other devices to it. Furthermore, 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.
>
> I'm not sure this approach is correct - for instance, from a Dell
> system:
>
> Method (_L0D, 0, NotSerialized)
> {
> Store (SMI (0xC6, 0x00), Local0)
> If (And (Local0, 0x01))
> {
> Notify (\_SB.PCI0.AZAL, 0x02)
> }
>
> If (And (Local0, 0x02))
> {
> Notify (\_SB.PCI0.EHCI, 0x02)
> }
>
> If (And (Local0, 0x04))
> {
> Notify (\_SB.PCI0.EHC2, 0x02)
> }
> }
>
> All these devices share a GPE, but registering only one notify handler
> would result in us missing the notification in some cases.
Ok, I see. The notification may be device-specific, although if we receive
one, we'll need to check all devices sharing the GPE anyway.
I think that's not going to be difficult to take into account, but I won't be
able to work on this until after the KS.
> I also suspect that this should wait until the GPE rework is done.
It certainly is better if the GPE rework is done first, although I'm not sure
it's strictly necessary. At least I don't see why exactly at the moment.
Thanks,
Rafael
There's a few quirks with Rafael's PCI runtime PM code - this modifies
them a little. The body of it is just porting it to my GPE rewrite, but
it also moves the PCI binding earlier (otherwise acpi_pci_bind bails, since
it's primarily intended for bridges), and also ensures that the pme bit
gets set properly on hardware that requires it.
Signed-off-by: Matthew Garrett <[email protected]>
---
drivers/acpi/pci_bind.c | 12 ++++++------
drivers/acpi/wakeup.c | 36 ------------------------------------
drivers/pci/pci-acpi.c | 43 ++++++++++++++++---------------------------
drivers/pci/pci-driver.c | 6 ++++++
include/acpi/acpi_bus.h | 6 ------
5 files changed, 28 insertions(+), 75 deletions(-)
diff --git a/drivers/acpi/pci_bind.c b/drivers/acpi/pci_bind.c
index d7baeca..d0086aa 100644
--- a/drivers/acpi/pci_bind.c
+++ b/drivers/acpi/pci_bind.c
@@ -73,6 +73,12 @@ static int acpi_pci_bind(struct acpi_device *device)
if (!dev)
return 0;
+ if (device->wakeup.flags.valid) {
+ pci_acpi_add_device_pm_notifier(device, dev);
+ if (dev->subordinate)
+ pci_acpi_add_bus_pm_notifier(device, dev->subordinate);
+ }
+
/*
* Install the 'bind' function to facilitate callbacks for
* children of the P2P bridge.
@@ -105,12 +111,6 @@ static int acpi_pci_bind(struct acpi_device *device)
acpi_pci_irq_add_prt(device->handle, bus);
- if (device->wakeup.flags.valid) {
- pci_acpi_add_device_pm_notifier(device, dev);
- if (dev->subordinate)
- pci_acpi_add_bus_pm_notifier(device, dev->subordinate);
- }
-
out:
pci_dev_put(dev);
return 0;
diff --git a/drivers/acpi/wakeup.c b/drivers/acpi/wakeup.c
index abdf349..be373cc 100644
--- a/drivers/acpi/wakeup.c
+++ b/drivers/acpi/wakeup.c
@@ -98,42 +98,6 @@ void acpi_disable_wakeup_device(u8 sleep_state)
#ifdef CONFIG_PM_WAKEUP
/**
- * acpi_device_run_wake - Enable/disable ACPI BIOS to generate wake-up events.
- * @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 acpi_device *dev, bool enable)
-{
- if (!dev || !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;
-}
-
-/**
* acpi_wakeup_gpes_shared - Check if given ACPI devices share a wake-up GPE.
* @deva: First ACPI device to check.
* @devb: Second ACPI device to check.
diff --git a/drivers/pci/pci-acpi.c b/drivers/pci/pci-acpi.c
index 51605c1..dae90cc 100644
--- a/drivers/pci/pci-acpi.c
+++ b/drivers/pci/pci-acpi.c
@@ -35,12 +35,6 @@
* notify handler for the root bridge, because the other devices will be checked
* in the process of handling the root bridge wake-up.
*
- * Furthermore, if many devices share one wake-up GPE, we only need to install
- * a notify handler for one of them as long as we know which devices to check
- * in the process of handling the notification. The purpose of the data
- * structures and helper functions below is to arrange things in accordance with
- * these observations.
- *
* pci_acpi_runtime_notifiers is a list of struct pci_acpi_notifier_block
* objects that each represent ACPI devices that have ACPI system notify
* handlers installed. For each of them, there is a ACPI-based hotplug notifier
@@ -63,7 +57,6 @@ struct pci_acpi_notifier_block
void *hp_data;
struct list_head pm_buses;
struct list_head pm_devices;
- int pm_enable_count;
};
struct pci_bus_notifier_entry
@@ -102,7 +95,7 @@ static void pci_acpi_event_fn(acpi_handle handle, u32 event, void *data)
mutex_lock(&pci_acpi_notifier_mtx);
- if (event == ACPI_NOTIFY_DEVICE_WAKE && nb->pm_enable_count) {
+ if (event == ACPI_NOTIFY_DEVICE_WAKE) {
if (!list_empty(&nb->pm_buses)) {
struct pci_bus_notifier_entry *bne;
@@ -316,10 +309,6 @@ acpi_status pci_acpi_add_device_pm_notifier(struct acpi_device *dev,
if (bus_match(bne->bus, bus))
goto out;
- list_for_each_entry(dne, &nb->pm_devices, entry)
- if (dne->dev == pci_dev)
- goto out;
-
if (!new_dev_entry(pci_dev, &nb->pm_devices))
status = AE_NO_MEMORY;
goto out;
@@ -385,10 +374,9 @@ acpi_status pci_acpi_remove_device_pm_notifier(struct acpi_device *dev,
return AE_NOT_FOUND;
found:
- if (dne->enabled) {
- if (!--nb->pm_enable_count)
- acpi_device_run_wake(nb->dev, false);
- }
+ if (dne->enabled)
+ acpi_unref_runtime_gpe(nb->dev->wakeup.gpe_device,
+ nb->dev->wakeup.gpe_number);
list_del(&dne->entry);
kfree(dne);
@@ -523,9 +511,10 @@ acpi_status pci_acpi_remove_bus_pm_notifier(struct acpi_device *dev,
found:
if (bne->enable_count) {
- nb->pm_enable_count -= bne->enable_count;
- if (!nb->pm_enable_count)
- acpi_device_run_wake(nb->dev, false);
+ int i;
+ for (i=bne->enable_count; i; i--)
+ acpi_unref_runtime_gpe(nb->dev->wakeup.gpe_device,
+ nb->dev->wakeup.gpe_number);
}
list_del(&bne->entry);
kfree(bne);
@@ -556,12 +545,12 @@ static int dev_run_wake(struct pci_acpi_notifier_block *nb,
if (enable) {
dne->enabled = true;
- if (!nb->pm_enable_count++)
- acpi_device_run_wake(nb->dev, true);
+ acpi_ref_runtime_gpe(nb->dev->wakeup.gpe_device,
+ nb->dev->wakeup.gpe_number);
} else if (dne->enabled) {
dne->enabled = false;
- if (!--nb->pm_enable_count)
- acpi_device_run_wake(nb->dev, false);
+ acpi_unref_runtime_gpe(nb->dev->wakeup.gpe_device,
+ nb->dev->wakeup.gpe_number);
} else {
error = -EALREADY;
}
@@ -582,12 +571,12 @@ static int bus_run_wake(struct pci_acpi_notifier_block *nb,
if (enable) {
bne->enable_count++;
- if (!nb->pm_enable_count++)
- acpi_device_run_wake(nb->dev, true);
+ acpi_ref_runtime_gpe(nb->dev->wakeup.gpe_device,
+ nb->dev->wakeup.gpe_number);
} else if (bne->enable_count) {
bne->enable_count--;
- if (!--nb->pm_enable_count)
- acpi_device_run_wake(nb->dev, false);
+ acpi_unref_runtime_gpe(nb->dev->wakeup.gpe_device,
+ nb->dev->wakeup.gpe_number);
} else {
error = -EALREADY;
}
diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c
index eb4f26a..d644db6 100644
--- a/drivers/pci/pci-driver.c
+++ b/drivers/pci/pci-driver.c
@@ -967,6 +967,9 @@ static int pci_pm_runtime_suspend(struct device *dev)
pci_prepare_to_sleep(pci_dev);
}
+ if (pci_pme_capable(pci_dev, pci_target_state(pci_dev)))
+ pci_pme_active(pci_dev, true);
+
pci_platform_run_wake(pci_dev, true);
return 0;
@@ -980,6 +983,9 @@ static int pci_pm_runtime_resume(struct device *dev)
if (!pm || !pm->runtime_resume)
return -ENOSYS;
+ if (pci_pme_capable(pci_dev, pci_target_state(pci_dev)))
+ pci_pme_active(pci_dev, false);
+
pci_platform_run_wake(pci_dev, false);
pci_pm_default_resume_early(pci_dev);
pci_pm_default_resume(pci_dev);
diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h
index 935036e..9541588 100644
--- a/include/acpi/acpi_bus.h
+++ b/include/acpi/acpi_bus.h
@@ -389,7 +389,6 @@ struct acpi_pci_root *acpi_pci_find_root(acpi_handle handle);
#ifdef CONFIG_PM_WAKEUP
int acpi_pm_device_sleep_state(struct device *, int *);
int acpi_pm_device_sleep_wake(struct device *, bool);
-int acpi_device_run_wake(struct acpi_device *, bool);
bool acpi_wakeup_gpe_shared(struct acpi_device *, struct acpi_device *);
#else /* !CONFIG_PM_WAKEUP */
static inline int acpi_pm_device_sleep_state(struct device *d, int *p)
@@ -404,11 +403,6 @@ static inline int acpi_pm_device_sleep_wake(struct device *dev, bool enable)
return -ENODEV;
}
-static inline int acpi_device_run_wake(struct device *dev, bool enable)
-{
- return -ENODEV;
-}
-
static inline bool acpi_wakeup_gpe_shared(struct acpi_device *a,
struct acpi_device *b)
{
--
1.6.5.2
On Monday 09 November 2009, Matthew Garrett wrote:
> There's a few quirks with Rafael's PCI runtime PM code - this modifies
> them a little. The body of it is just porting it to my GPE rewrite, but
> it also moves the PCI binding earlier (otherwise acpi_pci_bind bails, since
> it's primarily intended for bridges), and also ensures that the pme bit
> gets set properly on hardware that requires it.
Well, I thought more changes would be necessary. :-)
Looking good, a few comments below.
> Signed-off-by: Matthew Garrett <[email protected]>
> ---
> drivers/acpi/pci_bind.c | 12 ++++++------
> drivers/acpi/wakeup.c | 36 ------------------------------------
> drivers/pci/pci-acpi.c | 43 ++++++++++++++++---------------------------
> drivers/pci/pci-driver.c | 6 ++++++
> include/acpi/acpi_bus.h | 6 ------
> 5 files changed, 28 insertions(+), 75 deletions(-)
>
...
> diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c
> index eb4f26a..d644db6 100644
> --- a/drivers/pci/pci-driver.c
> +++ b/drivers/pci/pci-driver.c
> @@ -967,6 +967,9 @@ static int pci_pm_runtime_suspend(struct device *dev)
> pci_prepare_to_sleep(pci_dev);
> }
>
> + if (pci_pme_capable(pci_dev, pci_target_state(pci_dev)))
> + pci_pme_active(pci_dev, true);
> +
The PME was supposed to be turned on by pci_prepare_to_sleep().
I guess it wasn't in practice?
> pci_platform_run_wake(pci_dev, true);
>
> return 0;
> @@ -980,6 +983,9 @@ static int pci_pm_runtime_resume(struct device *dev)
> if (!pm || !pm->runtime_resume)
> return -ENOSYS;
>
> + if (pci_pme_capable(pci_dev, pci_target_state(pci_dev)))
> + pci_pme_active(pci_dev, false);
> +
Same here, pci_pm_default_resume_early() should cleare the PME theoretically,
although not for bridges.
> pci_platform_run_wake(pci_dev, false);
> pci_pm_default_resume_early(pci_dev);
> pci_pm_default_resume(pci_dev);
> diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h
> index 935036e..9541588 100644
Generally, some parts of it belong to the
"PCI PM: Run-time callbacks for PCI bus type" patch and the others belong
to "PCI / ACPI PM: Platform support for PCI PME wake-up" patch. Should I fold
the changes into them?
Rafael
On Wed, Nov 11, 2009 at 08:58:20PM +0100, Rafael J. Wysocki wrote:
> The PME was supposed to be turned on by pci_prepare_to_sleep().
> I guess it wasn't in practice?
Only if the device is set as a wakeup device, which seems messy. Though
there may need to be a check there to avoid duplicate PME setup.
> Generally, some parts of it belong to the
> "PCI PM: Run-time callbacks for PCI bus type" patch and the others belong
> to "PCI / ACPI PM: Platform support for PCI PME wake-up" patch. Should I fold
> the changes into them?
Feel free!
--
Matthew Garrett | [email protected]
On Wednesday 11 November 2009, Matthew Garrett wrote:
> On Wed, Nov 11, 2009 at 08:58:20PM +0100, Rafael J. Wysocki wrote:
>
> > The PME was supposed to be turned on by pci_prepare_to_sleep().
> > I guess it wasn't in practice?
>
> Only if the device is set as a wakeup device, which seems messy.
Right. Plus there was an implicit assumption that devices which could wake up
would also be able to generate PME at run time. It turns out that that need
not be the case, though.
> Though there may need to be a check there to avoid duplicate PME setup.
Hmm, I need to think about that for a while.
> > Generally, some parts of it belong to the
> > "PCI PM: Run-time callbacks for PCI bus type" patch and the others belong
> > to "PCI / ACPI PM: Platform support for PCI PME wake-up" patch. Should I fold
> > the changes into them?
>
> Feel free!
OK, thanks!