From patchwork Sun Sep 13 21:24:03 2009 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Rafael Wysocki X-Patchwork-Id: 47201 Received: from vger.kernel.org (vger.kernel.org [209.132.176.167]) by demeter.kernel.org (8.14.2/8.14.2) with ESMTP id n8DLQIng022578 for ; Sun, 13 Sep 2009 21:26:19 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1754922AbZIMVZP (ORCPT ); Sun, 13 Sep 2009 17:25:15 -0400 Received: (majordomo@vger.kernel.org) by vger.kernel.org id S1755235AbZIMVZN (ORCPT ); Sun, 13 Sep 2009 17:25:13 -0400 Received: from ogre.sisk.pl ([217.79.144.158]:50578 "EHLO ogre.sisk.pl" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1755173AbZIMVYn (ORCPT ); Sun, 13 Sep 2009 17:24:43 -0400 Received: from localhost (localhost.localdomain [127.0.0.1]) by ogre.sisk.pl (Postfix) with ESMTP id 6718A1578C4; Sun, 13 Sep 2009 20:19:45 +0200 (CEST) Received: from ogre.sisk.pl ([127.0.0.1]) by localhost (ogre.sisk.pl [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id 12730-03; Sun, 13 Sep 2009 20:19:13 +0200 (CEST) Received: from tosh.localnet (220-bem-13.acn.waw.pl [82.210.184.220]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (No client certificate requested) by ogre.sisk.pl (Postfix) with ESMTP id 8D3E7157DF0; Sun, 13 Sep 2009 20:19:13 +0200 (CEST) From: "Rafael J. Wysocki" To: pm list Subject: [RFC][PATCH 3/4] PCI / ACPI PM: Platform support for PCI PME wake-up Date: Sun, 13 Sep 2009 23:24:03 +0200 User-Agent: KMail/1.12.1 (Linux/2.6.31-rjw; KDE/4.3.1; x86_64; ; ) Cc: Linux PCI , Len Brown , LKML , Jesse Barnes , Matthew Garrett , Shaohua Li , ACPI Devel Maling List References: <200909132320.05077.rjw@sisk.pl> In-Reply-To: <200909132320.05077.rjw@sisk.pl> MIME-Version: 1.0 Message-Id: <200909132324.03340.rjw@sisk.pl> X-Virus-Scanned: amavisd-new at ogre.sisk.pl using MkS_Vir for Linux Sender: linux-acpi-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-acpi@vger.kernel.org From: Rafael J. Wysocki Although the majority of PCI devices can generate PMEs that in principle may be used to wake up devices suspended at run time, platform support is generally necessary to convert PMEs into wake-up events that can be delivered to the kernel. If ACPI is used for this purpose, a PME generated by a PCI device will trigger the ACPI GPE associated with the device to generate an ACPI wake-up event that we can set up a handler for, provided that everything is configured correctly. Unfortunately, the subset of PCI devices that have GPEs associated with them is quite limited and the other devices have to rely on the GPEs associated with their upstream bridges and, possibly, the root bridge to generate ACPI wake-up events in response to PMEs from them. Add ACPI platform support for PCI PME wake-up: o Add function acpi_device_run_wake() allowing us to enable or disable the GPE associated with given device to generate run-time wake-up events. o Add a new PCI platform callback ->run_wake() to struct pci_platform_pm_ops allowing us to enable the platform to generate wake-up events for given device. o Implemet that callback for the ACPI platform using acpi_device_run_wake(). o Define ACPI wake-up handlers for PCI devices and PCI buses and make the PCI-ACPI binding code install wake-up notifiers for devices associated with GPEs that can be used for wake-up. o Add function pci_platform_run_wake() that can be used to enable (or disable) the platform to generate wake-up events for given PCI device using the ->run_wake() platform callback. Based on a patch from Matthew Garrett. Signed-off-by: Rafael J. Wysocki --- drivers/acpi/pci_bind.c | 6 ++++++ drivers/acpi/pci_root.c | 5 +++++ drivers/acpi/sleep.c | 4 ++-- drivers/acpi/wakeup.c | 47 +++++++++++++++++++++++++++++++++++++++++++++++ drivers/pci/pci-acpi.c | 42 ++++++++++++++++++++++++++++++++++++++++++ drivers/pci/pci.c | 41 +++++++++++++++++++++++++++++++++++++++++ drivers/pci/pci.h | 14 +++++++++++--- include/acpi/acpi_bus.h | 13 ++++++++++--- include/linux/pci-acpi.h | 3 +++ kernel/power/Kconfig | 5 +++++ 10 files changed, 172 insertions(+), 8 deletions(-) -- To unsubscribe from this list: send the line "unsubscribe linux-acpi" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Index: linux-2.6/drivers/pci/pci.h =================================================================== --- linux-2.6.orig/drivers/pci/pci.h +++ linux-2.6/drivers/pci/pci.h @@ -29,10 +29,14 @@ extern int pci_mmap_fits(struct pci_dev * platform; to be used during system-wide transitions from a * sleeping state to the working state and vice versa * - * @can_wakeup: returns 'true' if given device is capable of waking up the - * system from a sleeping state + * @can_wakeup: returns 'true' if the plaform can generate wake-up events for + * given device. * - * @sleep_wake: enables/disables the system wake up capability of given device + * @sleep_wake: enables/disables the wake-up capability of given device + * + * @run_wake: enables/disables the platform to generate run-time wake-up events + * for given device (the device's wake-up capability has to be + * enabled by @sleep_wake for this feature to work) * * If given platform is generally capable of power managing PCI devices, all of * these callbacks are mandatory. @@ -43,12 +47,16 @@ struct pci_platform_pm_ops { pci_power_t (*choose_state)(struct pci_dev *dev); bool (*can_wakeup)(struct pci_dev *dev); int (*sleep_wake)(struct pci_dev *dev, bool enable); + int (*run_wake)(struct pci_dev *dev, bool enable); }; extern int pci_set_platform_pm(struct pci_platform_pm_ops *ops); +extern int pci_platform_run_wake(struct pci_dev *dev, bool enable); extern void pci_update_current_state(struct pci_dev *dev, pci_power_t state); extern void pci_disable_enabled_device(struct pci_dev *dev); extern bool pci_check_pme_status(struct pci_dev *dev); +extern void pci_pme_wakeup_bus(struct pci_bus *bus); +extern void pci_pme_wakeup(struct pci_dev *dev); extern void pci_pm_init(struct pci_dev *dev); extern void platform_pci_wakeup_init(struct pci_dev *dev); extern void pci_allocate_cap_save_buffers(struct pci_dev *dev); Index: linux-2.6/drivers/pci/pci-acpi.c =================================================================== --- linux-2.6.orig/drivers/pci/pci-acpi.c +++ linux-2.6/drivers/pci/pci-acpi.c @@ -18,6 +18,18 @@ #include #include "pci.h" +void pci_acpi_bus_wakeup(acpi_handle handle, u32 event, void *data) +{ + if (event == ACPI_NOTIFY_DEVICE_WAKE) + pci_pme_wakeup_bus(to_pci_bus(data)); +} + +void pci_acpi_device_wakeup(acpi_handle handle, u32 event, void *data) +{ + if (event == ACPI_NOTIFY_DEVICE_WAKE) + pci_pme_wakeup(to_pci_dev(data)); +} + /* * _SxD returns the D-state with the highest power * (lowest D-state number) supported in the S-state "x". @@ -137,12 +149,42 @@ static int acpi_pci_sleep_wake(struct pc return 0; } +static void acpi_pci_propagate_run_wake(struct pci_bus *bus, bool enable) +{ + while (bus->parent) { + struct pci_dev *bridge = bus->self; + + if (pcie_pme_enabled(bridge)) + return; + if (!acpi_device_run_wake(&bridge->dev, enable)) + return; + bus = bus->parent; + } + + /* We have reached the root bus. */ + if (bus->bridge) + acpi_device_run_wake(bus->bridge, enable); +} + +static int acpi_pci_run_wake(struct pci_dev *dev, bool enable) +{ + if (pcie_pme_enabled(dev)) + return 0; + + if (acpi_pci_can_wakeup(dev)) + return acpi_device_run_wake(&dev->dev, enable); + + acpi_pci_propagate_run_wake(dev->bus, enable); + return 0; +} + static struct pci_platform_pm_ops acpi_pci_platform_pm = { .is_manageable = acpi_pci_power_manageable, .set_state = acpi_pci_set_power_state, .choose_state = acpi_pci_choose_state, .can_wakeup = acpi_pci_can_wakeup, .sleep_wake = acpi_pci_sleep_wake, + .run_wake = acpi_pci_run_wake, }; /* ACPI bus type */ Index: linux-2.6/drivers/pci/pci.c =================================================================== --- linux-2.6.orig/drivers/pci/pci.c +++ linux-2.6/drivers/pci/pci.c @@ -21,6 +21,7 @@ #include #include /* isa_dma_bridge_buggy */ #include +#include #include #include "pci.h" @@ -428,6 +429,12 @@ static inline int platform_pci_sleep_wak pci_platform_pm->sleep_wake(dev, enable) : -ENODEV; } +int pci_platform_run_wake(struct pci_dev *dev, bool enable) +{ + return pci_platform_pm ? + pci_platform_pm->run_wake(dev, enable) : -ENODEV; +} + /** * pci_raw_set_power_state - Use PCI PM registers to set the power state of * given PCI device @@ -1195,6 +1202,40 @@ bool pci_check_pme_status(struct pci_dev return ret; } +static int pci_pme_wakeup_cb(struct pci_dev *dev, void *ign) +{ + if (pci_check_pme_status(dev)) + pm_request_resume(&dev->dev); + return 0; +} + +/** + * pci_pme_wakeup_bus - Walk given bus and wake up devices on it, if necessary. + * @bus: Top bus of the subtree to walk. + */ +void pci_pme_wakeup_bus(struct pci_bus *bus) +{ + pci_walk_bus(bus, pci_pme_wakeup_cb, NULL); +} + +/** + * pci_pme_wakeup - Wake up a device and devices under it if it is a bridge. + * @dev: Device to handle. + * + * Check if @dev has generated PME and queue a resume request for it in that + * case. If @dev is a bridge, walk the bus under it, check if there are any + * devices that generated PME on this bus and queue resume requests for them + * (asynchronous resume is used, because we need to clear PME status bits for + * all devices before resuming any of them). + */ +void pci_pme_wakeup(struct pci_dev *dev) +{ + if (pci_check_pme_status(dev)) + pm_request_resume(&dev->dev); + if (dev->subordinate) + pci_pme_wakeup_bus(dev->subordinate); +} + /** * pci_pme_capable - check the capability of PCI device to generate PME# * @dev: PCI device to handle. Index: linux-2.6/drivers/acpi/wakeup.c =================================================================== --- linux-2.6.orig/drivers/acpi/wakeup.c +++ linux-2.6/drivers/acpi/wakeup.c @@ -124,6 +124,53 @@ void acpi_disable_wakeup_device(u8 sleep } } +#ifdef CONFIG_PM_WAKEUP +/** + * acpi_device_run_wake - Enable/disable ACPI BIOS to generate wake-up events. + * @phys_dev: Device to generate the wake-up events for. + * @enable: Desired action. + * + * If @enable is set, set up the GPE associated with @phys_dev to generate + * wake-up events at run time. If @enable is unset, disable the GPE associated + * with @phys_dev (unless it is marked as a run-wake device). + */ +int acpi_device_run_wake(struct device *phys_dev, bool enable) +{ + struct acpi_device *dev; + acpi_handle handle; + + handle = DEVICE_ACPI_HANDLE(phys_dev); + if (!handle || ACPI_FAILURE(acpi_bus_get_device(handle, &dev))) { + dev_dbg(phys_dev, "ACPI context not found in %s!\n", __func__); + return -ENODEV; + } + + if (!dev->wakeup.flags.valid) + return -EINVAL; + + if (enable) { + if (!dev->wakeup.state.enabled && !dev->wakeup.prepare_count) + return -EINVAL; + + acpi_set_gpe_type(dev->wakeup.gpe_device, + dev->wakeup.gpe_number, + ACPI_GPE_TYPE_WAKE_RUN); + acpi_enable_gpe(dev->wakeup.gpe_device, + dev->wakeup.gpe_number); + } else if (!dev->wakeup.flags.run_wake) { + acpi_set_gpe_type(dev->wakeup.gpe_device, + dev->wakeup.gpe_number, + ACPI_GPE_TYPE_WAKE); + acpi_disable_gpe(dev->wakeup.gpe_device, + dev->wakeup.gpe_number); + acpi_clear_gpe(dev->wakeup.gpe_device, + dev->wakeup.gpe_number, ACPI_NOT_ISR); + } + + return 0; +} +#endif /* CONFIG_PM_WAKEUP */ + int __init acpi_wakeup_device_init(void) { struct list_head *node, *next; Index: linux-2.6/include/acpi/acpi_bus.h =================================================================== --- linux-2.6.orig/include/acpi/acpi_bus.h +++ linux-2.6/include/acpi/acpi_bus.h @@ -375,21 +375,28 @@ int acpi_is_root_bridge(acpi_handle); acpi_handle acpi_get_pci_rootbridge_handle(unsigned int, unsigned int); #define DEVICE_ACPI_HANDLE(dev) ((acpi_handle)((dev)->archdata.acpi_handle)) -#ifdef CONFIG_PM_SLEEP +#ifdef CONFIG_PM_WAKEUP int acpi_pm_device_sleep_state(struct device *, int *); int acpi_pm_device_sleep_wake(struct device *, bool); -#else /* !CONFIG_PM_SLEEP */ +int acpi_device_run_wake(struct device *, bool); +#else /* !CONFIG_PM_WAKEUP */ static inline int acpi_pm_device_sleep_state(struct device *d, int *p) { if (p) *p = ACPI_STATE_D0; return ACPI_STATE_D3; } + static inline int acpi_pm_device_sleep_wake(struct device *dev, bool enable) { return -ENODEV; } -#endif /* !CONFIG_PM_SLEEP */ + +static inline int acpi_device_run_wake(struct device *dev, bool enable) +{ + return -ENODEV; +} +#endif /* !CONFIG_PM_WAKEUP */ #endif /* CONFIG_ACPI */ Index: linux-2.6/kernel/power/Kconfig =================================================================== --- linux-2.6.orig/kernel/power/Kconfig +++ linux-2.6/kernel/power/Kconfig @@ -236,3 +236,8 @@ config PM_RUNTIME and the bus type drivers of the buses the devices are on are responsible for the actual handling of the autosuspend requests and wake-up events. + +config PM_WAKEUP + bool + depends on SUSPEND || HIBERNATION || PM_RUNTIME + default y Index: linux-2.6/drivers/acpi/sleep.c =================================================================== --- linux-2.6.orig/drivers/acpi/sleep.c +++ linux-2.6/drivers/acpi/sleep.c @@ -594,7 +594,7 @@ int acpi_suspend(u32 acpi_state) return -EINVAL; } -#ifdef CONFIG_PM_SLEEP +#ifdef CONFIG_PM_WAKEUP /** * acpi_pm_device_sleep_state - return preferred power state of ACPI device * in the system sleep state given by %acpi_target_sleep_state @@ -709,7 +709,7 @@ int acpi_pm_device_sleep_wake(struct dev return error; } -#endif +#endif /* CONFIG_PM_WAKEUP */ static void acpi_power_off_prepare(void) { Index: linux-2.6/drivers/acpi/pci_root.c =================================================================== --- linux-2.6.orig/drivers/acpi/pci_root.c +++ linux-2.6/drivers/acpi/pci_root.c @@ -576,6 +576,11 @@ static int __devinit acpi_pci_root_add(s if (flags != base_flags) acpi_pci_osc_support(root, flags); + if (device->wakeup.flags.valid) + acpi_install_notify_handler(device->handle, ACPI_SYSTEM_NOTIFY, + pci_acpi_bus_wakeup, + &root->bus->dev); + return 0; end: Index: linux-2.6/include/linux/pci-acpi.h =================================================================== --- linux-2.6.orig/include/linux/pci-acpi.h +++ linux-2.6/include/linux/pci-acpi.h @@ -10,6 +10,9 @@ #include +extern void pci_acpi_bus_wakeup(acpi_handle handle, u32 event, void *data); +extern void pci_acpi_device_wakeup(acpi_handle handle, u32 event, void *data); + #ifdef CONFIG_ACPI static inline acpi_handle acpi_find_root_bridge_handle(struct pci_dev *pdev) { Index: linux-2.6/drivers/acpi/pci_bind.c =================================================================== --- linux-2.6.orig/drivers/acpi/pci_bind.c +++ linux-2.6/drivers/acpi/pci_bind.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -94,6 +95,11 @@ static int acpi_pci_bind(struct acpi_dev acpi_pci_irq_add_prt(device->handle, bus); + if (device->wakeup.flags.valid) + acpi_install_notify_handler(device->handle, ACPI_SYSTEM_NOTIFY, + pci_acpi_device_wakeup, + &dev->dev); + out: pci_dev_put(dev); return 0;