* [RFC][PATCH 0/9] PCI PM: Rework PCI device PM
@ 2008-06-19 23:45 Rafael J. Wysocki
2008-06-19 23:46 ` [RFC][PATCH 1/9] ACPI: Introduce acpi_bus_power_manageable function Rafael J. Wysocki
` (8 more replies)
0 siblings, 9 replies; 25+ messages in thread
From: Rafael J. Wysocki @ 2008-06-19 23:45 UTC (permalink / raw)
To: ACPI Devel Maling List; +Cc: Alan Stern, Jesse Barnes, Len Brown, pm list
Hi,
The following series of patches is intended to rework PCI device power
management to join it with the related ACPI bits more tightly and to clean up
disabling/enabling the system wake-up capability of PCI devices. In
particular, after these patches the PCI devices that are capable of waking
up the system should have their /sys/devices/.../power/wakup files set up
as appropriate.
The last patch introduces two helper function intended to be used by PCI
drivers implementing the new suspend/hibernation callbacks that do not take
the second pm_message_t argument.
Comments welcome.
Thanks,
Rafael
^ permalink raw reply [flat|nested] 25+ messages in thread
* [RFC][PATCH 1/9] ACPI: Introduce acpi_bus_power_manageable function
2008-06-19 23:45 [RFC][PATCH 0/9] PCI PM: Rework PCI device PM Rafael J. Wysocki
@ 2008-06-19 23:46 ` Rafael J. Wysocki
2008-06-24 12:25 ` [linux-pm] " Pavel Machek
2008-06-19 23:47 ` [RFC][PATCH 2/9] PCI: Introduce platform_pci_power_manageable function Rafael J. Wysocki
` (7 subsequent siblings)
8 siblings, 1 reply; 25+ messages in thread
From: Rafael J. Wysocki @ 2008-06-19 23:46 UTC (permalink / raw)
To: ACPI Devel Maling List; +Cc: Alan Stern, Jesse Barnes, Len Brown, pm list
From: Rafael J. Wysocki <rjw@sisk.pl>
ACPI: Introduce acpi_bus_power_manageable function
Introduce function acpi_bus_power_manageable() allowing other
(dependent) subsystems to check if ACPI is able to power manage given
device. This may be useful, for example, for PCI device power
management.
Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
drivers/acpi/bus.c | 11 +++++++++++
include/acpi/acpi_bus.h | 1 +
2 files changed, 12 insertions(+)
Index: linux-next/drivers/acpi/bus.c
===================================================================
--- linux-next.orig/drivers/acpi/bus.c
+++ linux-next/drivers/acpi/bus.c
@@ -295,6 +295,17 @@ int acpi_bus_set_power(acpi_handle handl
EXPORT_SYMBOL(acpi_bus_set_power);
+bool acpi_bus_power_manageable(acpi_handle handle)
+{
+ struct acpi_device *device;
+ int result;
+
+ result = acpi_bus_get_device(handle, &device);
+ return result ? false : device->flags.power_manageable;
+}
+
+EXPORT_SYMBOL(acpi_bus_power_manageable);
+
/* --------------------------------------------------------------------------
Event Management
-------------------------------------------------------------------------- */
Index: linux-next/include/acpi/acpi_bus.h
===================================================================
--- linux-next.orig/include/acpi/acpi_bus.h
+++ linux-next/include/acpi/acpi_bus.h
@@ -335,6 +335,7 @@ void acpi_bus_data_handler(acpi_handle h
int acpi_bus_get_status(struct acpi_device *device);
int acpi_bus_get_power(acpi_handle handle, int *state);
int acpi_bus_set_power(acpi_handle handle, int state);
+bool acpi_bus_power_manageable(acpi_handle handle);
#ifdef CONFIG_ACPI_PROC_EVENT
int acpi_bus_generate_proc_event(struct acpi_device *device, u8 type, int data);
int acpi_bus_generate_proc_event4(const char *class, const char *bid, u8 type,
^ permalink raw reply [flat|nested] 25+ messages in thread
* [RFC][PATCH 2/9] PCI: Introduce platform_pci_power_manageable function
2008-06-19 23:45 [RFC][PATCH 0/9] PCI PM: Rework PCI device PM Rafael J. Wysocki
2008-06-19 23:46 ` [RFC][PATCH 1/9] ACPI: Introduce acpi_bus_power_manageable function Rafael J. Wysocki
@ 2008-06-19 23:47 ` Rafael J. Wysocki
2008-06-24 12:31 ` [linux-pm] " Pavel Machek
2008-06-19 23:48 ` [RFC][PATCH 3/9] PCI: Rework pci_set_power_state function Rafael J. Wysocki
` (6 subsequent siblings)
8 siblings, 1 reply; 25+ messages in thread
From: Rafael J. Wysocki @ 2008-06-19 23:47 UTC (permalink / raw)
To: ACPI Devel Maling List; +Cc: Alan Stern, Jesse Barnes, Len Brown, pm list
From: Rafael J. Wysocki <rjw@sisk.pl>
PCI: Introduce platform_pci_power_manageable function
Introduce function pointer platform_pci_power_manageable to be used
by the platform-related code to point to a function allowing us to
check if given device is power manageable by the platform.
Introduce acpi_pci_power_manageable() playing that role for ACPI.
Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
drivers/pci/pci-acpi.c | 19 +++++++++++++------
drivers/pci/pci.c | 40 ++++++++++++++++++++++++++++++----------
drivers/pci/pci.h | 26 ++++++++++++++++++++++----
3 files changed, 65 insertions(+), 20 deletions(-)
Index: linux-next/drivers/pci/pci-acpi.c
===================================================================
--- linux-next.orig/drivers/pci/pci-acpi.c
+++ linux-next/drivers/pci/pci-acpi.c
@@ -215,7 +215,6 @@ acpi_status pci_osc_control_set(acpi_han
}
EXPORT_SYMBOL(pci_osc_control_set);
-#ifdef CONFIG_ACPI_SLEEP
/*
* _SxD returns the D-state with the highest power
* (lowest D-state number) supported in the S-state "x".
@@ -259,7 +258,13 @@ static pci_power_t acpi_pci_choose_state
}
return PCI_POWER_ERROR;
}
-#endif
+
+static bool acpi_pci_power_manageable(struct pci_dev *dev)
+{
+ acpi_handle handle = DEVICE_ACPI_HANDLE(&dev->dev);
+
+ return handle ? acpi_bus_power_manageable(handle) : false;
+}
static int acpi_pci_set_power_state(struct pci_dev *dev, pci_power_t state)
{
@@ -290,6 +295,11 @@ static int acpi_pci_set_power_state(stru
return -EINVAL;
}
+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,
+};
/* ACPI bus type */
static int acpi_pci_find_device(struct device *dev, acpi_handle *handle)
@@ -341,10 +351,7 @@ static int __init acpi_pci_init(void)
ret = register_acpi_bus_type(&acpi_pci_bus);
if (ret)
return 0;
-#ifdef CONFIG_ACPI_SLEEP
- platform_pci_choose_state = acpi_pci_choose_state;
-#endif
- platform_pci_set_power_state = acpi_pci_set_power_state;
+ pci_set_platform_pm(&acpi_pci_platform_pm);
return 0;
}
arch_initcall(acpi_pci_init);
Index: linux-next/drivers/pci/pci.c
===================================================================
--- linux-next.orig/drivers/pci/pci.c
+++ linux-next/drivers/pci/pci.c
@@ -376,7 +376,32 @@ pci_restore_bars(struct pci_dev *dev)
pci_update_resource(dev, &dev->resource[i], i);
}
-int (*platform_pci_set_power_state)(struct pci_dev *dev, pci_power_t t);
+static struct pci_platform_pm_ops *pci_platform_pm;
+
+int pci_set_platform_pm(struct pci_platform_pm_ops *ops)
+{
+ if (!ops->is_manageable || !ops->set_state || !ops->choose_state)
+ return -EINVAL;
+ pci_platform_pm = ops;
+ return 0;
+}
+
+static inline bool platform_pci_power_manageable(struct pci_dev *dev)
+{
+ return pci_platform_pm ? pci_platform_pm->is_manageable(dev) : false;
+}
+
+static inline int platform_pci_set_power_state(struct pci_dev *dev,
+ pci_power_t t)
+{
+ return pci_platform_pm ? pci_platform_pm->set_state(dev, t) : -ENOSYS;
+}
+
+static inline pci_power_t platform_pci_choose_state(struct pci_dev *dev)
+{
+ return pci_platform_pm ?
+ pci_platform_pm->choose_state(dev) : PCI_POWER_ERROR;
+}
/**
* pci_set_power_state - Set the power state of a PCI device
@@ -480,8 +505,7 @@ pci_set_power_state(struct pci_dev *dev,
* Give firmware a chance to be called, such as ACPI _PRx, _PSx
* Firmware method after native method ?
*/
- if (platform_pci_set_power_state)
- platform_pci_set_power_state(dev, state);
+ platform_pci_set_power_state(dev, state);
dev->current_state = state;
@@ -506,8 +530,6 @@ pci_set_power_state(struct pci_dev *dev,
return 0;
}
-pci_power_t (*platform_pci_choose_state)(struct pci_dev *dev);
-
/**
* pci_choose_state - Choose the power state of a PCI device
* @dev: PCI device to be suspended
@@ -525,11 +547,9 @@ pci_power_t pci_choose_state(struct pci_
if (!pci_find_capability(dev, PCI_CAP_ID_PM))
return PCI_D0;
- if (platform_pci_choose_state) {
- ret = platform_pci_choose_state(dev);
- if (ret != PCI_POWER_ERROR)
- return ret;
- }
+ ret = platform_pci_choose_state(dev);
+ if (ret != PCI_POWER_ERROR)
+ return ret;
switch (state.event) {
case PM_EVENT_ON:
Index: linux-next/drivers/pci/pci.h
===================================================================
--- linux-next.orig/drivers/pci/pci.h
+++ linux-next/drivers/pci/pci.h
@@ -5,10 +5,28 @@ extern int pci_create_sysfs_dev_files(st
extern void pci_remove_sysfs_dev_files(struct pci_dev *pdev);
extern void pci_cleanup_rom(struct pci_dev *dev);
-/* Firmware callbacks */
-extern pci_power_t (*platform_pci_choose_state)(struct pci_dev *dev);
-extern int (*platform_pci_set_power_state)(struct pci_dev *dev,
- pci_power_t state);
+/**
+ * Firmware PM callbacks
+ *
+ * @is_manageable - returns 'true' if given device is power manageable by the
+ * platform firmware
+ *
+ * @set_state - invokes the platform firmware to set the device's power state
+ *
+ * @choose_state - returns PCI power state of given device preferred by the
+ * platform; to be used during system-wide transitions from a
+ * sleeping state to the working state and vice versa
+ *
+ * If given platform is generally capable of power managing PCI devices, all of
+ * these callbacks are mandatory.
+ */
+struct pci_platform_pm_ops {
+ bool (*is_manageable)(struct pci_dev *dev);
+ int (*set_state)(struct pci_dev *dev, pci_power_t state);
+ pci_power_t (*choose_state)(struct pci_dev *dev);
+};
+
+extern int pci_set_platform_pm(struct pci_platform_pm_ops *ops);
extern int pci_user_read_config_byte(struct pci_dev *dev, int where, u8 *val);
extern int pci_user_read_config_word(struct pci_dev *dev, int where, u16 *val);
^ permalink raw reply [flat|nested] 25+ messages in thread
* [RFC][PATCH 3/9] PCI: Rework pci_set_power_state function
2008-06-19 23:45 [RFC][PATCH 0/9] PCI PM: Rework PCI device PM Rafael J. Wysocki
2008-06-19 23:46 ` [RFC][PATCH 1/9] ACPI: Introduce acpi_bus_power_manageable function Rafael J. Wysocki
2008-06-19 23:47 ` [RFC][PATCH 2/9] PCI: Introduce platform_pci_power_manageable function Rafael J. Wysocki
@ 2008-06-19 23:48 ` Rafael J. Wysocki
2008-06-24 12:34 ` [linux-pm] " Pavel Machek
2008-06-19 23:49 ` [RFC][PATCH 4/9] ACPI: Introduce acpi_device_sleep_wake function Rafael J. Wysocki
` (5 subsequent siblings)
8 siblings, 1 reply; 25+ messages in thread
From: Rafael J. Wysocki @ 2008-06-19 23:48 UTC (permalink / raw)
To: ACPI Devel Maling List; +Cc: Alan Stern, Jesse Barnes, Len Brown, pm list
From: Rafael J. Wysocki <rjw@sisk.pl>
PCI: Rework pci_set_power_state function
Rework pci_set_power_state() so that the platform callback is
invoked before the native mechanism, if necessary. Also, make
the function check if the device is power manageable by the
platform before invoking the platform callback.
This may matter if the device dependent on additional power
resources controlled by the platform is being put into D0, in which
case those power resources must be turned on before we attempt to
handle the device itself.
Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
drivers/pci/pci-acpi.c | 16 ++++++-----
drivers/pci/pci.c | 71 +++++++++++++++++++++++++++++++++----------------
2 files changed, 58 insertions(+), 29 deletions(-)
Index: linux-next/drivers/pci/pci.c
===================================================================
--- linux-next.orig/drivers/pci/pci.c
+++ linux-next/drivers/pci/pci.c
@@ -414,14 +414,17 @@ static inline pci_power_t platform_pci_c
* RETURN VALUE:
* -EINVAL if trying to enter a lower state than we're already in.
* 0 if we're already in the requested state.
- * -EIO if device does not support PCI PM.
+ * -EIO if device does not support PCI PM or it doesn't support given state.
* 0 if we can successfully change the power state.
+ * Platform-dependent error code if the platform has failed to change state and
+ * it is impossible to use the native mechanism for that.
*/
int
pci_set_power_state(struct pci_dev *dev, pci_power_t state)
{
- int pm, need_restore = 0;
+ int pm;
u16 pmcsr, pmc;
+ bool platform_pm, platform_done = false, need_restore = false;
/* bound the state we're entering */
if (state > PCI_D3hot)
@@ -438,37 +441,57 @@ pci_set_power_state(struct pci_dev *dev,
/* find PCI PM capability in list */
pm = pci_find_capability(dev, PCI_CAP_ID_PM);
- /* abort if the device doesn't support PM capabilities */
- if (!pm)
- return -EIO;
+ platform_pm = platform_pci_power_manageable(dev);
+
+ if (platform_pm && state == PCI_D0) {
+ /*
+ * Allow the platform to change the state, for example via ACPI
+ * _PR0, _PS0 and some such, but do not trust it.
+ */
+ int err = platform_pci_set_power_state(dev, state);
+ if (err && !pm)
+ return err;
+ /*
+ * The platform might have changed the state, make sure that the
+ * cached value is still valid.
+ */
+ pci_read_config_word(dev, pm + PCI_PM_CTRL, &pmcsr);
+ dev->current_state = (pmcsr & PCI_PM_CTRL_STATE_MASK);
+
+ platform_done = true;
+ } else if (!pm) {
+ goto Platform_low;
+ }
/* Validate current state:
* Can enter D0 from any state, but if we can only go deeper
* to sleep if we're already in a low power state
*/
- if (state != PCI_D0 && dev->current_state > state) {
- printk(KERN_ERR "%s(): %s: state=%d, current state=%d\n",
+ if (dev->current_state == state) {
+ /* we're already there */
+ return 0;
+ } else if (state != PCI_D0 && dev->current_state > state) {
+ printk(KERN_ERR "%s(): %s: state=D%d, current state=D%d\n",
__func__, pci_name(dev), state, dev->current_state);
return -EINVAL;
- } else if (dev->current_state == state)
- return 0; /* we're already there */
+ }
+ pci_read_config_word(dev, pm + PCI_PM_PMC, &pmc);
- pci_read_config_word(dev,pm + PCI_PM_PMC,&pmc);
if ((pmc & PCI_PM_CAP_VER_MASK) > 3) {
- printk(KERN_DEBUG
+ printk(KERN_ERR
"PCI: %s has unsupported PM cap regs version (%u)\n",
pci_name(dev), pmc & PCI_PM_CAP_VER_MASK);
- return -EIO;
+ return platform_done ? 0 : -EIO;
}
/* check if this device supports the desired state */
- if (state == PCI_D1 && !(pmc & PCI_PM_CAP_D1))
- return -EIO;
- else if (state == PCI_D2 && !(pmc & PCI_PM_CAP_D2))
+ if ((state == PCI_D1 && !(pmc & PCI_PM_CAP_D1))
+ || (state == PCI_D2 && !(pmc & PCI_PM_CAP_D2)))
return -EIO;
- pci_read_config_word(dev, pm + PCI_PM_CTRL, &pmcsr);
+ if (!platform_done)
+ pci_read_config_word(dev, pm + PCI_PM_CTRL, &pmcsr);
/* If we're (effectively) in D3, force entire word to 0.
* This doesn't affect PME_Status, disables PME_En, and
@@ -484,7 +507,7 @@ pci_set_power_state(struct pci_dev *dev,
case PCI_UNKNOWN: /* Boot-up */
if ((pmcsr & PCI_PM_CTRL_STATE_MASK) == PCI_D3hot
&& !(pmcsr & PCI_PM_CTRL_NO_SOFT_RESET))
- need_restore = 1;
+ need_restore = true;
/* Fall-through: force to D0 */
default:
pmcsr = 0;
@@ -501,12 +524,6 @@ pci_set_power_state(struct pci_dev *dev,
else if (state == PCI_D2 || dev->current_state == PCI_D2)
udelay(200);
- /*
- * Give firmware a chance to be called, such as ACPI _PRx, _PSx
- * Firmware method after native method ?
- */
- platform_pci_set_power_state(dev, state);
-
dev->current_state = state;
/* According to section 5.4.1 of the "PCI BUS POWER MANAGEMENT
@@ -527,6 +544,14 @@ pci_set_power_state(struct pci_dev *dev,
if (dev->bus->self)
pcie_aspm_pm_state_change(dev->bus->self);
+ Platform_low:
+ if (platform_pm && state > PCI_D0) {
+ /* Allow the platform to finalize the transition */
+ int err = platform_pci_set_power_state(dev, state);
+ if (err && dev->current_state != state)
+ return err;
+ }
+
return 0;
}
Index: linux-next/drivers/pci/pci-acpi.c
===================================================================
--- linux-next.orig/drivers/pci/pci-acpi.c
+++ linux-next/drivers/pci/pci-acpi.c
@@ -277,12 +277,11 @@ static int acpi_pci_set_power_state(stru
[PCI_D3hot] = ACPI_STATE_D3,
[PCI_D3cold] = ACPI_STATE_D3
};
+ int error = -EINVAL;
- if (!handle)
- return -ENODEV;
/* If the ACPI device has _EJ0, ignore the device */
- if (ACPI_SUCCESS(acpi_get_handle(handle, "_EJ0", &tmp)))
- return 0;
+ if (!handle || ACPI_SUCCESS(acpi_get_handle(handle, "_EJ0", &tmp)))
+ return -ENODEV;
switch (state) {
case PCI_D0:
@@ -290,9 +289,14 @@ static int acpi_pci_set_power_state(stru
case PCI_D2:
case PCI_D3hot:
case PCI_D3cold:
- return acpi_bus_set_power(handle, state_conv[state]);
+ error = acpi_bus_set_power(handle, state_conv[state]);
}
- return -EINVAL;
+
+ if (!error)
+ printk(KERN_INFO "PCI: Power state of %s changed by ACPI\n",
+ pci_name(dev));
+
+ return error;
}
static struct pci_platform_pm_ops acpi_pci_platform_pm = {
^ permalink raw reply [flat|nested] 25+ messages in thread
* [RFC][PATCH 4/9] ACPI: Introduce acpi_device_sleep_wake function
2008-06-19 23:45 [RFC][PATCH 0/9] PCI PM: Rework PCI device PM Rafael J. Wysocki
` (2 preceding siblings ...)
2008-06-19 23:48 ` [RFC][PATCH 3/9] PCI: Rework pci_set_power_state function Rafael J. Wysocki
@ 2008-06-19 23:49 ` Rafael J. Wysocki
2008-06-24 12:38 ` [linux-pm] " Pavel Machek
2008-06-19 23:50 ` [RFC][PATCH 5/9] ACPI: Introduce new device wakeup flag 'prepared' Rafael J. Wysocki
` (4 subsequent siblings)
8 siblings, 1 reply; 25+ messages in thread
From: Rafael J. Wysocki @ 2008-06-19 23:49 UTC (permalink / raw)
To: ACPI Devel Maling List; +Cc: Alan Stern, Jesse Barnes, Len Brown, pm list
From: Rafael J. Wysocki <rjw@sisk.pl>
ACPI: Introduce acpi_device_sleep_wake function
The currect ACPI code attempts to execute _PSW at three different
places and in one of them only it tries to execute _DSW before _PSW,
which is inconsistent with the other two cases.
Move the execution of _DSW and _PSW into a separate function called
acpi_device_sleep_wake() and call it wherever appropriate instead of
executing _DSW and/or _PSW directly.
Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
drivers/acpi/power.c | 118 ++++++++++++++++++++++++++++++--------------
drivers/acpi/scan.c | 42 ++-------------
drivers/acpi/sleep/wakeup.c | 2
include/acpi/acpi_drivers.h | 4 +
4 files changed, 92 insertions(+), 74 deletions(-)
Index: linux-next/drivers/acpi/power.c
===================================================================
--- linux-next.orig/drivers/acpi/power.c
+++ linux-next/drivers/acpi/power.c
@@ -292,69 +292,115 @@ static int acpi_power_off_device(acpi_ha
return 0;
}
+/**
+ * acpi_device_sleep_wake - execute _DSW (Device Sleep Wake) or (deprecated in
+ * ACPI 3.0) _PSW (Power State Wake)
+ * @dev: Device to handle.
+ * @enable: 0 - disable, 1 - enable the wake capabilities of the device.
+ * @sleep_state: Target sleep state of the system.
+ * @dev_state: Target power state of the device.
+ *
+ * Execute _DSW (Device Sleep Wake) or (deprecated in ACPI 3.0) _PSW (Power
+ * State Wake) for the device, if present. On failure reset the device's
+ * wakeup.flags.valid flag.
+ *
+ * RETURN VALUE:
+ * 0 if either _DSW or _PSW has been successfully executed
+ * 0 if neither _DSW nor _PSW has been found
+ * -ENODEV if the execution of either _DSW or _PSW has failed
+ */
+int acpi_device_sleep_wake(struct acpi_device *dev,
+ int enable, int sleep_state, int dev_state)
+{
+ union acpi_object in_arg[3];
+ struct acpi_object_list arg_list = { 3, in_arg };
+ acpi_status status = AE_OK;
+
+ /*
+ * Try to execute _DSW first.
+ *
+ * Three agruments are needed for the _DSW object:
+ * Argument 0: enable/disable the wake capabilities
+ * Argument 1: target system state
+ * Argument 2: target device state
+ * When _DSW object is called to disable the wake capabilities, maybe
+ * the first argument is filled. The values of the other two agruments
+ * are meaningless.
+ */
+ in_arg[0].type = ACPI_TYPE_INTEGER;
+ in_arg[0].integer.value = enable;
+ in_arg[1].type = ACPI_TYPE_INTEGER;
+ in_arg[1].integer.value = sleep_state;
+ in_arg[2].type = ACPI_TYPE_INTEGER;
+ in_arg[2].integer.value = dev_state;
+ status = acpi_evaluate_object(dev->handle, "_DSW", &arg_list, NULL);
+ if (ACPI_SUCCESS(status)) {
+ return 0;
+ } else if (status != AE_NOT_FOUND) {
+ printk(KERN_ERR PREFIX "_DSW execution failed\n");
+ dev->wakeup.flags.valid = 0;
+ return -ENODEV;
+ }
+
+ /* Execute _PSW */
+ arg_list.count = 1;
+ in_arg[0].integer.value = enable;
+ status = acpi_evaluate_object(dev->handle, "_PSW", &arg_list, NULL);
+ if (ACPI_FAILURE(status) && (status != AE_NOT_FOUND)) {
+ printk(KERN_ERR PREFIX "_PSW execution failed\n");
+ dev->wakeup.flags.valid = 0;
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
/*
* Prepare a wakeup device, two steps (Ref ACPI 2.0:P229):
* 1. Power on the power resources required for the wakeup device
- * 2. Enable _PSW (power state wake) for the device if present
+ * 2. Execute _DSW (Device Sleep Wake) or (deprecated in ACPI 3.0) _PSW (Power
+ * State Wake) for the device, if present
*/
-int acpi_enable_wakeup_device_power(struct acpi_device *dev)
+int acpi_enable_wakeup_device_power(struct acpi_device *dev, int sleep_state)
{
- union acpi_object arg = { ACPI_TYPE_INTEGER };
- struct acpi_object_list arg_list = { 1, &arg };
- acpi_status status = AE_OK;
int i;
- int ret = 0;
if (!dev || !dev->wakeup.flags.valid)
- return -1;
+ return -EINVAL;
- arg.integer.value = 1;
/* Open power resource */
for (i = 0; i < dev->wakeup.resources.count; i++) {
- ret = acpi_power_on(dev->wakeup.resources.handles[i], dev);
+ int ret = acpi_power_on(dev->wakeup.resources.handles[i], dev);
if (ret) {
printk(KERN_ERR PREFIX "Transition power state\n");
dev->wakeup.flags.valid = 0;
- return -1;
+ return -ENODEV;
}
}
- /* Execute PSW */
- status = acpi_evaluate_object(dev->handle, "_PSW", &arg_list, NULL);
- if (ACPI_FAILURE(status) && (status != AE_NOT_FOUND)) {
- printk(KERN_ERR PREFIX "Evaluate _PSW\n");
- dev->wakeup.flags.valid = 0;
- ret = -1;
- }
-
- return ret;
+ /*
+ * Passing 3 as the third argument below means the device may be placed
+ * in arbitrary power state afterwards.
+ */
+ return acpi_device_sleep_wake(dev, 1, sleep_state, 3);
}
/*
* Shutdown a wakeup device, counterpart of above method
- * 1. Disable _PSW (power state wake)
+ * 1. Execute _DSW (Device Sleep Wake) or (deprecated in ACPI 3.0) _PSW (Power
+ * State Wake) for the device, if present
* 2. Shutdown down the power resources
*/
int acpi_disable_wakeup_device_power(struct acpi_device *dev)
{
- union acpi_object arg = { ACPI_TYPE_INTEGER };
- struct acpi_object_list arg_list = { 1, &arg };
- acpi_status status = AE_OK;
- int i;
- int ret = 0;
-
+ int i, ret;
if (!dev || !dev->wakeup.flags.valid)
- return -1;
+ return -EINVAL;
- arg.integer.value = 0;
- /* Execute PSW */
- status = acpi_evaluate_object(dev->handle, "_PSW", &arg_list, NULL);
- if (ACPI_FAILURE(status) && (status != AE_NOT_FOUND)) {
- printk(KERN_ERR PREFIX "Evaluate _PSW\n");
- dev->wakeup.flags.valid = 0;
- return -1;
- }
+ ret = acpi_device_sleep_wake(dev, 0, 0, 0);
+ if (ret)
+ return ret;
/* Close power resource */
for (i = 0; i < dev->wakeup.resources.count; i++) {
@@ -362,7 +408,7 @@ int acpi_disable_wakeup_device_power(str
if (ret) {
printk(KERN_ERR PREFIX "Transition power state\n");
dev->wakeup.flags.valid = 0;
- return -1;
+ return -ENODEV;
}
}
Index: linux-next/drivers/acpi/sleep/wakeup.c
===================================================================
--- linux-next.orig/drivers/acpi/sleep/wakeup.c
+++ linux-next/drivers/acpi/sleep/wakeup.c
@@ -42,7 +42,7 @@ void acpi_enable_wakeup_device_prep(u8 s
continue;
spin_unlock(&acpi_device_lock);
- acpi_enable_wakeup_device_power(dev);
+ acpi_enable_wakeup_device_power(dev, sleep_state);
spin_lock(&acpi_device_lock);
}
spin_unlock(&acpi_device_lock);
Index: linux-next/include/acpi/acpi_drivers.h
===================================================================
--- linux-next.orig/include/acpi/acpi_drivers.h
+++ linux-next/include/acpi/acpi_drivers.h
@@ -87,7 +87,9 @@ struct pci_bus *pci_acpi_scan_root(struc
-------------------------------------------------------------------------- */
#ifdef CONFIG_ACPI_POWER
-int acpi_enable_wakeup_device_power(struct acpi_device *dev);
+int acpi_device_sleep_wake(struct acpi_device *dev,
+ int enable, int sleep_state, int dev_state);
+int acpi_enable_wakeup_device_power(struct acpi_device *dev, int sleep_state);
int acpi_disable_wakeup_device_power(struct acpi_device *dev);
int acpi_power_get_inferred_state(struct acpi_device *device);
int acpi_power_transition(struct acpi_device *device, int state);
Index: linux-next/drivers/acpi/scan.c
===================================================================
--- linux-next.orig/drivers/acpi/scan.c
+++ linux-next/drivers/acpi/scan.c
@@ -703,9 +703,7 @@ static int acpi_bus_get_wakeup_device_fl
acpi_status status = 0;
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object *package = NULL;
- union acpi_object in_arg[3];
- struct acpi_object_list arg_list = { 3, in_arg };
- acpi_status psw_status = AE_OK;
+ int psw_error;
struct acpi_device_id button_device_ids[] = {
{"PNP0C0D", 0},
@@ -737,39 +735,11 @@ static int acpi_bus_get_wakeup_device_fl
* So it is necessary to call _DSW object first. Only when it is not
* present will the _PSW object used.
*/
- /*
- * Three agruments are needed for the _DSW object.
- * Argument 0: enable/disable the wake capabilities
- * When _DSW object is called to disable the wake capabilities, maybe
- * the first argument is filled. The value of the other two agruments
- * is meaningless.
- */
- in_arg[0].type = ACPI_TYPE_INTEGER;
- in_arg[0].integer.value = 0;
- in_arg[1].type = ACPI_TYPE_INTEGER;
- in_arg[1].integer.value = 0;
- in_arg[2].type = ACPI_TYPE_INTEGER;
- in_arg[2].integer.value = 0;
- psw_status = acpi_evaluate_object(device->handle, "_DSW",
- &arg_list, NULL);
- if (ACPI_FAILURE(psw_status) && (psw_status != AE_NOT_FOUND))
- ACPI_DEBUG_PRINT((ACPI_DB_INFO, "error in evaluate _DSW\n"));
- /*
- * When the _DSW object is not present, OSPM will call _PSW object.
- */
- if (psw_status == AE_NOT_FOUND) {
- /*
- * Only one agruments is required for the _PSW object.
- * agrument 0: enable/disable the wake capabilities
- */
- arg_list.count = 1;
- in_arg[0].integer.value = 0;
- psw_status = acpi_evaluate_object(device->handle, "_PSW",
- &arg_list, NULL);
- if (ACPI_FAILURE(psw_status) && (psw_status != AE_NOT_FOUND))
- ACPI_DEBUG_PRINT((ACPI_DB_INFO, "error in "
- "evaluate _PSW\n"));
- }
+ psw_error = acpi_device_sleep_wake(device, 0, 0, 0);
+ if (psw_error)
+ ACPI_DEBUG_PRINT((ACPI_DB_INFO,
+ "error in _DSW or _PSW evaluation\n"));
+
/* Power button, Lid switch always enable wakeup */
if (!acpi_match_device_ids(device, button_device_ids))
device->wakeup.flags.run_wake = 1;
^ permalink raw reply [flat|nested] 25+ messages in thread
* [RFC][PATCH 5/9] ACPI: Introduce new device wakeup flag 'prepared'
2008-06-19 23:45 [RFC][PATCH 0/9] PCI PM: Rework PCI device PM Rafael J. Wysocki
` (3 preceding siblings ...)
2008-06-19 23:49 ` [RFC][PATCH 4/9] ACPI: Introduce acpi_device_sleep_wake function Rafael J. Wysocki
@ 2008-06-19 23:50 ` Rafael J. Wysocki
2008-06-19 23:51 ` [RFC][PATCH 6/9] PCI ACPI: Introduce acpi_pm_device_sleep_wake function Rafael J. Wysocki
` (3 subsequent siblings)
8 siblings, 0 replies; 25+ messages in thread
From: Rafael J. Wysocki @ 2008-06-19 23:50 UTC (permalink / raw)
To: ACPI Devel Maling List; +Cc: Alan Stern, Jesse Barnes, Len Brown, pm list
From: Rafael J. Wysocki <rjw@sisk.pl>
ACPI: Introduce new device wakeup flag 'prepared'
Introduce additional flag 'prepared' in
struct acpi_device_wakeup_flags and use it to prevent devices from
being enable/disabled do wake up the system multiple times in a row
(this does not happen currently, but will be possible after some of
the following patches).
Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
drivers/acpi/power.c | 24 ++++++++++++++++++++++--
include/acpi/acpi_bus.h | 1 +
2 files changed, 23 insertions(+), 2 deletions(-)
Index: linux-next/drivers/acpi/power.c
===================================================================
--- linux-next.orig/drivers/acpi/power.c
+++ linux-next/drivers/acpi/power.c
@@ -363,11 +363,18 @@ int acpi_device_sleep_wake(struct acpi_d
*/
int acpi_enable_wakeup_device_power(struct acpi_device *dev, int sleep_state)
{
- int i;
+ int i, err;
if (!dev || !dev->wakeup.flags.valid)
return -EINVAL;
+ /*
+ * Do not execute the code below twice in a row without calling
+ * acpi_disable_wakeup_device_power() in between for the same device
+ */
+ if (dev->wakeup.flags.prepared)
+ return 0;
+
/* Open power resource */
for (i = 0; i < dev->wakeup.resources.count; i++) {
int ret = acpi_power_on(dev->wakeup.resources.handles[i], dev);
@@ -382,7 +389,11 @@ int acpi_enable_wakeup_device_power(stru
* Passing 3 as the third argument below means the device may be placed
* in arbitrary power state afterwards.
*/
- return acpi_device_sleep_wake(dev, 1, sleep_state, 3);
+ err = acpi_device_sleep_wake(dev, 1, sleep_state, 3);
+ if (!err)
+ dev->wakeup.flags.prepared = 1;
+
+ return err;
}
/*
@@ -398,6 +409,15 @@ int acpi_disable_wakeup_device_power(str
if (!dev || !dev->wakeup.flags.valid)
return -EINVAL;
+ /*
+ * Do not execute the code below twice in a row without calling
+ * acpi_enable_wakeup_device_power() in between for the same device
+ */
+ if (!dev->wakeup.flags.prepared)
+ return 0;
+
+ dev->wakeup.flags.prepared = 0;
+
ret = acpi_device_sleep_wake(dev, 0, 0, 0);
if (ret)
return ret;
Index: linux-next/include/acpi/acpi_bus.h
===================================================================
--- linux-next.orig/include/acpi/acpi_bus.h
+++ linux-next/include/acpi/acpi_bus.h
@@ -259,6 +259,7 @@ struct acpi_device_perf {
/* Wakeup Management */
struct acpi_device_wakeup_flags {
u8 valid:1; /* Can successfully enable wakeup? */
+ u8 prepared:1; /* Has the wake-up capability been enabled? */
u8 run_wake:1; /* Run-Wake GPE devices */
};
^ permalink raw reply [flat|nested] 25+ messages in thread
* [RFC][PATCH 6/9] PCI ACPI: Introduce acpi_pm_device_sleep_wake function
2008-06-19 23:45 [RFC][PATCH 0/9] PCI PM: Rework PCI device PM Rafael J. Wysocki
` (4 preceding siblings ...)
2008-06-19 23:50 ` [RFC][PATCH 5/9] ACPI: Introduce new device wakeup flag 'prepared' Rafael J. Wysocki
@ 2008-06-19 23:51 ` Rafael J. Wysocki
2008-06-25 8:11 ` Zhang Rui
2008-06-19 23:52 ` [RFC][PATCH 7/9] PCI ACPI: Introduce can_wakeup platform callback Rafael J. Wysocki
` (2 subsequent siblings)
8 siblings, 1 reply; 25+ messages in thread
From: Rafael J. Wysocki @ 2008-06-19 23:51 UTC (permalink / raw)
To: ACPI Devel Maling List; +Cc: Alan Stern, Jesse Barnes, Len Brown, pm list
From: Rafael J. Wysocki <rjw@sisk.pl>
PCI ACPI: Introduce acpi_pm_device_sleep_wake function
Introduce function acpi_pm_device_sleep_wake() for enabling and
disabling the system wake-up capability of devices that are power
manageable by ACPI.
Introduce callback .sleep_wake() in struct pci_platform_pm_ops and
for the ACPI PCI 'driver' make it use acpi_pm_device_sleep_wake().
Modify pci_enable_wake() to use the new callback and drop the generic
.platform_enable_wakeup() callback that is not used any more.
Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
drivers/acpi/sleep/main.c | 25 +++++++++++++++++
drivers/base/power/sysfs.c | 3 --
drivers/pci/pci-acpi.c | 11 +++++++
drivers/pci/pci.c | 64 ++++++++++++++++++++++++++++++---------------
drivers/pci/pci.h | 3 ++
include/acpi/acpi_bus.h | 1
include/linux/pm_wakeup.h | 16 -----------
7 files changed, 84 insertions(+), 39 deletions(-)
Index: linux-next/drivers/acpi/sleep/main.c
===================================================================
--- linux-next.orig/drivers/acpi/sleep/main.c
+++ linux-next/drivers/acpi/sleep/main.c
@@ -468,6 +468,31 @@ int acpi_pm_device_sleep_state(struct de
*d_min_p = d_min;
return d_max;
}
+
+/**
+ * acpi_pm_device_sleep_wake - enable or disable the system wake-up
+ * capability of given device
+ * @dev: device to handle
+ * @enable: 'true' - enable, 'false' - disable the wake-up capability
+ */
+int acpi_pm_device_sleep_wake(struct device *dev, bool enable)
+{
+ acpi_handle handle;
+ struct acpi_device *adev;
+
+ if (!device_may_wakeup(dev))
+ return -EINVAL;
+
+ handle = DEVICE_ACPI_HANDLE(dev);
+ if (!handle || ACPI_FAILURE(acpi_bus_get_device(handle, &adev))) {
+ printk(KERN_DEBUG "ACPI handle has no context!\n");
+ return -ENODEV;
+ }
+
+ return enable ?
+ acpi_enable_wakeup_device_power(adev, acpi_target_sleep_state) :
+ acpi_disable_wakeup_device_power(adev);
+}
#endif
static void acpi_power_off_prepare(void)
Index: linux-next/drivers/pci/pci-acpi.c
===================================================================
--- linux-next.orig/drivers/pci/pci-acpi.c
+++ linux-next/drivers/pci/pci-acpi.c
@@ -299,10 +299,21 @@ static int acpi_pci_set_power_state(stru
return error;
}
+static int acpi_pci_sleep_wake(struct pci_dev *dev, bool enable)
+{
+ int error = acpi_pm_device_sleep_wake(&dev->dev, enable);
+
+ if (!error)
+ printk(KERN_INFO "PCI: Wake-up capability of %s %s by ACPI\n",
+ pci_name(dev), enable ? "enabled" : "disabled");
+ return error;
+}
+
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,
+ .sleep_wake = acpi_pci_sleep_wake,
};
/* ACPI bus type */
Index: linux-next/drivers/pci/pci.h
===================================================================
--- linux-next.orig/drivers/pci/pci.h
+++ linux-next/drivers/pci/pci.h
@@ -17,6 +17,8 @@ extern void pci_cleanup_rom(struct pci_d
* platform; to be used during system-wide transitions from a
* sleeping state to the working state and vice versa
*
+ * @sleep_wake - enables/disables the system wake up capability of given device
+ *
* If given platform is generally capable of power managing PCI devices, all of
* these callbacks are mandatory.
*/
@@ -24,6 +26,7 @@ struct pci_platform_pm_ops {
bool (*is_manageable)(struct pci_dev *dev);
int (*set_state)(struct pci_dev *dev, pci_power_t state);
pci_power_t (*choose_state)(struct pci_dev *dev);
+ int (*sleep_wake)(struct pci_dev *dev, bool enable);
};
extern int pci_set_platform_pm(struct pci_platform_pm_ops *ops);
Index: linux-next/include/acpi/acpi_bus.h
===================================================================
--- linux-next.orig/include/acpi/acpi_bus.h
+++ linux-next/include/acpi/acpi_bus.h
@@ -383,6 +383,7 @@ acpi_handle acpi_get_pci_rootbridge_hand
#ifdef CONFIG_PM_SLEEP
int acpi_pm_device_sleep_state(struct device *, int *);
+int acpi_pm_device_sleep_wake(struct device *, bool);
#else /* !CONFIG_PM_SLEEP */
static inline int acpi_pm_device_sleep_state(struct device *d, int *p)
{
Index: linux-next/drivers/pci/pci.c
===================================================================
--- linux-next.orig/drivers/pci/pci.c
+++ linux-next/drivers/pci/pci.c
@@ -380,7 +380,8 @@ static struct pci_platform_pm_ops *pci_p
int pci_set_platform_pm(struct pci_platform_pm_ops *ops)
{
- if (!ops->is_manageable || !ops->set_state || !ops->choose_state)
+ if (!ops->is_manageable || !ops->set_state || !ops->choose_state
+ || !ops->sleep_wake)
return -EINVAL;
pci_platform_pm = ops;
return 0;
@@ -403,6 +404,12 @@ static inline pci_power_t platform_pci_c
pci_platform_pm->choose_state(dev) : PCI_POWER_ERROR;
}
+static inline int platform_pci_sleep_wake(struct pci_dev *dev, bool enable)
+{
+ return pci_platform_pm ?
+ pci_platform_pm->sleep_wake(dev, enable) : -ENODEV;
+}
+
/**
* pci_set_power_state - Set the power state of a PCI device
* @dev: PCI device to be suspended
@@ -1018,24 +1025,27 @@ int pci_set_pcie_reset_state(struct pci_
* supporting the standard PCI PME# signal may require such platform hooks;
* they always update bits in config space to allow PME# generation.
*
- * -EIO is returned if the device can't ever be a wakeup event source.
+ * -EIO is returned if the device can't be a wakeup event source.
* -EINVAL is returned if the device can't generate wakeup events from
* the specified PCI state. Returns zero if the operation is successful.
*/
int pci_enable_wake(struct pci_dev *dev, pci_power_t state, int enable)
{
int pm;
- int status;
- u16 value;
+ u16 value = 0;
+ bool platform_done = false;
- /* Note that drivers should verify device_may_wakeup(&dev->dev)
- * before calling this function. Platform code should report
- * errors when drivers try to enable wakeup on devices that
- * can't issue wakeups, or on which wakeups were disabled by
- * userspace updating the /sys/devices.../power/wakeup file.
- */
-
- status = call_platform_enable_wakeup(&dev->dev, enable);
+ if (enable && platform_pci_power_manageable(dev)) {
+ /* Allow the platform to handle the device */
+ int err = platform_pci_sleep_wake(dev, true);
+ if (!err)
+ /*
+ * The platform claims to have enabled the device's
+ * system wake-up capability as requested, but we are
+ * going to enable PME# anyway.
+ */
+ platform_done = true;
+ }
/* find PCI PM capability in list */
pm = pci_find_capability(dev, PCI_CAP_ID_PM);
@@ -1044,23 +1054,27 @@ int pci_enable_wake(struct pci_dev *dev,
* disable wake events, it's a NOP. Otherwise fail unless the
* platform hooks handled this legacy device already.
*/
- if (!pm)
- return enable ? status : 0;
+ if (!pm) {
+ if (enable)
+ return platform_done ? 0 : -EIO;
+ else
+ goto Platform_disable;
+ }
/* Check device's ability to generate PME# */
- pci_read_config_word(dev,pm+PCI_PM_PMC,&value);
+ pci_read_config_word(dev, pm + PCI_PM_PMC, &value);
value &= PCI_PM_CAP_PME_MASK;
value >>= ffs(PCI_PM_CAP_PME_MASK) - 1; /* First bit of mask */
/* Check if it can generate PME# from requested state. */
if (!value || !(value & (1 << state))) {
- /* if it can't, revert what the platform hook changed,
- * always reporting the base "EINVAL, can't PME#" error
- */
- if (enable)
- call_platform_enable_wakeup(&dev->dev, 0);
- return enable ? -EINVAL : 0;
+ if (enable) {
+ return platform_done ? 0 : -EINVAL;
+ } else {
+ value = 0;
+ goto Platform_disable;
+ }
}
pci_read_config_word(dev, pm + PCI_PM_CTRL, &value);
@@ -1073,6 +1087,14 @@ int pci_enable_wake(struct pci_dev *dev,
pci_write_config_word(dev, pm + PCI_PM_CTRL, value);
+ Platform_disable:
+ if (!enable && platform_pci_power_manageable(dev)) {
+ /* Allow the platform to finalize the operation */
+ int err = platform_pci_sleep_wake(dev, false);
+ if (err && !value)
+ return -EIO;
+ }
+
return 0;
}
Index: linux-next/include/linux/pm_wakeup.h
===================================================================
--- linux-next.orig/include/linux/pm_wakeup.h
+++ linux-next/include/linux/pm_wakeup.h
@@ -47,21 +47,7 @@ static inline void device_set_wakeup_ena
static inline int device_may_wakeup(struct device *dev)
{
- return dev->power.can_wakeup & dev->power.should_wakeup;
-}
-
-/*
- * Platform hook to activate device wakeup capability, if that's not already
- * handled by enable_irq_wake() etc.
- * Returns zero on success, else negative errno
- */
-extern int (*platform_enable_wakeup)(struct device *dev, int is_on);
-
-static inline int call_platform_enable_wakeup(struct device *dev, int is_on)
-{
- if (platform_enable_wakeup)
- return (*platform_enable_wakeup)(dev, is_on);
- return 0;
+ return dev->power.can_wakeup && dev->power.should_wakeup;
}
#else /* !CONFIG_PM */
Index: linux-next/drivers/base/power/sysfs.c
===================================================================
--- linux-next.orig/drivers/base/power/sysfs.c
+++ linux-next/drivers/base/power/sysfs.c
@@ -6,9 +6,6 @@
#include <linux/string.h>
#include "power.h"
-int (*platform_enable_wakeup)(struct device *dev, int is_on);
-
-
/*
* wakeup - Report/change current wakeup option for device
*
^ permalink raw reply [flat|nested] 25+ messages in thread
* [RFC][PATCH 7/9] PCI ACPI: Introduce can_wakeup platform callback
2008-06-19 23:45 [RFC][PATCH 0/9] PCI PM: Rework PCI device PM Rafael J. Wysocki
` (5 preceding siblings ...)
2008-06-19 23:51 ` [RFC][PATCH 6/9] PCI ACPI: Introduce acpi_pm_device_sleep_wake function Rafael J. Wysocki
@ 2008-06-19 23:52 ` Rafael J. Wysocki
2008-06-19 23:52 ` [RFC][PATCH 8/9] PCI PM: Rework device PM initialization Rafael J. Wysocki
2008-06-19 23:57 ` [RFC][PATCH 9/9] PCI PM: Introduce pci_prepare_to_sleep and pci_back_from_sleep Rafael J. Wysocki
8 siblings, 0 replies; 25+ messages in thread
From: Rafael J. Wysocki @ 2008-06-19 23:52 UTC (permalink / raw)
To: ACPI Devel Maling List; +Cc: Alan Stern, Jesse Barnes, Len Brown, pm list
From: Rafael J. Wysocki <rjw@sisk.pl>
PCI ACPI: Introduce can_wakeup platform callback
Introduce function acpi_bus_can_wakeup() allowing other (dependent)
subsystems to check if ACPI is able to enable the system wake-up
capability of given device.
Introduce callback .can_wakeup() in struct pci_platform_pm_ops and
for the ACPI 'driver' make it use acpi_bus_can_wakeup().
Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
drivers/acpi/bus.c | 11 +++++++++++
drivers/pci/pci-acpi.c | 8 ++++++++
drivers/pci/pci.c | 11 ++++++++---
drivers/pci/pci.h | 5 +++++
include/acpi/acpi_bus.h | 1 +
5 files changed, 33 insertions(+), 3 deletions(-)
Index: linux-next/drivers/pci/pci.h
===================================================================
--- linux-next.orig/drivers/pci/pci.h
+++ linux-next/drivers/pci/pci.h
@@ -17,6 +17,10 @@ extern void pci_cleanup_rom(struct pci_d
* 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, must return 'false' for devices
+ * that are not power manageable by the platform
+ *
* @sleep_wake - enables/disables the system wake up capability of given device
*
* If given platform is generally capable of power managing PCI devices, all of
@@ -26,6 +30,7 @@ struct pci_platform_pm_ops {
bool (*is_manageable)(struct pci_dev *dev);
int (*set_state)(struct pci_dev *dev, pci_power_t state);
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);
};
Index: linux-next/drivers/pci/pci.c
===================================================================
--- linux-next.orig/drivers/pci/pci.c
+++ linux-next/drivers/pci/pci.c
@@ -381,7 +381,7 @@ static struct pci_platform_pm_ops *pci_p
int pci_set_platform_pm(struct pci_platform_pm_ops *ops)
{
if (!ops->is_manageable || !ops->set_state || !ops->choose_state
- || !ops->sleep_wake)
+ || !ops->sleep_wake || !ops->can_wakeup)
return -EINVAL;
pci_platform_pm = ops;
return 0;
@@ -404,6 +404,11 @@ static inline pci_power_t platform_pci_c
pci_platform_pm->choose_state(dev) : PCI_POWER_ERROR;
}
+static inline bool platform_pci_can_wakeup(struct pci_dev *dev)
+{
+ return pci_platform_pm ? pci_platform_pm->can_wakeup(dev) : false;
+}
+
static inline int platform_pci_sleep_wake(struct pci_dev *dev, bool enable)
{
return pci_platform_pm ?
@@ -1035,7 +1040,7 @@ int pci_enable_wake(struct pci_dev *dev,
u16 value = 0;
bool platform_done = false;
- if (enable && platform_pci_power_manageable(dev)) {
+ if (enable && platform_pci_can_wakeup(dev)) {
/* Allow the platform to handle the device */
int err = platform_pci_sleep_wake(dev, true);
if (!err)
@@ -1088,7 +1093,7 @@ int pci_enable_wake(struct pci_dev *dev,
pci_write_config_word(dev, pm + PCI_PM_CTRL, value);
Platform_disable:
- if (!enable && platform_pci_power_manageable(dev)) {
+ if (!enable && platform_pci_can_wakeup(dev)) {
/* Allow the platform to finalize the operation */
int err = platform_pci_sleep_wake(dev, false);
if (err && !value)
Index: linux-next/drivers/acpi/bus.c
===================================================================
--- linux-next.orig/drivers/acpi/bus.c
+++ linux-next/drivers/acpi/bus.c
@@ -306,6 +306,17 @@ bool acpi_bus_power_manageable(acpi_hand
EXPORT_SYMBOL(acpi_bus_power_manageable);
+bool acpi_bus_can_wakeup(acpi_handle handle)
+{
+ struct acpi_device *device;
+ int result;
+
+ result = acpi_bus_get_device(handle, &device);
+ return result ? false : device->wakeup.flags.valid;
+}
+
+EXPORT_SYMBOL(acpi_bus_can_wakeup);
+
/* --------------------------------------------------------------------------
Event Management
-------------------------------------------------------------------------- */
Index: linux-next/drivers/pci/pci-acpi.c
===================================================================
--- linux-next.orig/drivers/pci/pci-acpi.c
+++ linux-next/drivers/pci/pci-acpi.c
@@ -299,6 +299,13 @@ static int acpi_pci_set_power_state(stru
return error;
}
+static bool acpi_pci_can_wakeup(struct pci_dev *dev)
+{
+ acpi_handle handle = DEVICE_ACPI_HANDLE(&dev->dev);
+
+ return handle ? acpi_bus_can_wakeup(handle) : false;
+}
+
static int acpi_pci_sleep_wake(struct pci_dev *dev, bool enable)
{
int error = acpi_pm_device_sleep_wake(&dev->dev, enable);
@@ -313,6 +320,7 @@ static struct pci_platform_pm_ops acpi_p
.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,
};
Index: linux-next/include/acpi/acpi_bus.h
===================================================================
--- linux-next.orig/include/acpi/acpi_bus.h
+++ linux-next/include/acpi/acpi_bus.h
@@ -337,6 +337,7 @@ int acpi_bus_get_status(struct acpi_devi
int acpi_bus_get_power(acpi_handle handle, int *state);
int acpi_bus_set_power(acpi_handle handle, int state);
bool acpi_bus_power_manageable(acpi_handle handle);
+bool acpi_bus_can_wakeup(acpi_handle handle);
#ifdef CONFIG_ACPI_PROC_EVENT
int acpi_bus_generate_proc_event(struct acpi_device *device, u8 type, int data);
int acpi_bus_generate_proc_event4(const char *class, const char *bid, u8 type,
^ permalink raw reply [flat|nested] 25+ messages in thread
* [RFC][PATCH 8/9] PCI PM: Rework device PM initialization
2008-06-19 23:45 [RFC][PATCH 0/9] PCI PM: Rework PCI device PM Rafael J. Wysocki
` (6 preceding siblings ...)
2008-06-19 23:52 ` [RFC][PATCH 7/9] PCI ACPI: Introduce can_wakeup platform callback Rafael J. Wysocki
@ 2008-06-19 23:52 ` Rafael J. Wysocki
2008-06-20 15:59 ` [RFC][PATCH 8/9] PCI PM: Rework device PM initialization (rev. 2) Rafael J. Wysocki
2008-06-19 23:57 ` [RFC][PATCH 9/9] PCI PM: Introduce pci_prepare_to_sleep and pci_back_from_sleep Rafael J. Wysocki
8 siblings, 1 reply; 25+ messages in thread
From: Rafael J. Wysocki @ 2008-06-19 23:52 UTC (permalink / raw)
To: ACPI Devel Maling List; +Cc: Alan Stern, Jesse Barnes, Len Brown, pm list
From: Rafael J. Wysocki <rjw@sisk.pl>
PCI PM: Rework device PM initialization
Rework PCI device PM initialization so that if given device is capable
of generating wake-up events, either natively through the PME#
mechanism, or with the help of the platform, its power.can_wakeup
and power.should_wakeup flags are set as appropriate.
Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
drivers/pci/pci.c | 145 ++++++++++++++++++++++++++++++++--------------------
drivers/pci/pci.h | 1
drivers/pci/probe.c | 45 ----------------
3 files changed, 93 insertions(+), 98 deletions(-)
Index: linux-next/drivers/pci/pci.c
===================================================================
--- linux-next.orig/drivers/pci/pci.c
+++ linux-next/drivers/pci/pci.c
@@ -1016,6 +1016,53 @@ int pci_set_pcie_reset_state(struct pci_
}
/**
+ * pci_pme_capable - check the capability of PCI device to generate PME#
+ * @dev: PCI device to handle.
+ * @pm: PCI PM capability offset of the device.
+ * @state: PCI state from which device will issue PME#.
+ */
+static bool pci_pme_capable(struct pci_dev *dev, int pm, pci_power_t state)
+{
+ u16 pmc;
+
+ if (!pm)
+ return false;
+
+ /* Check device's ability to generate PME# from given state */
+ pci_read_config_word(dev, pm + PCI_PM_PMC, &pmc);
+
+ pmc &= PCI_PM_CAP_PME_MASK;
+ pmc >>= ffs(PCI_PM_CAP_PME_MASK) - 1; /* First bit of mask */
+
+ return !!(pmc & (1 << state));
+}
+
+/**
+ * pci_pme_active - enable or disable PCI device's PME# function
+ * @dev: PCI device to handle.
+ * @pm: PCI PM capability offset of the device.
+ * @enable: 'true' to enable PME# generation; 'false' to disable it.
+ *
+ * The caller must verify that the device is capable of generating PME# before
+ * calling this function with @enable equal to 'true'.
+ */
+static void pci_pme_active(struct pci_dev *dev, int pm, bool enable)
+{
+ u16 pmcsr;
+
+ if (!pm)
+ return;
+
+ pci_read_config_word(dev, pm + PCI_PM_CTRL, &pmcsr);
+ /* Clear PME_Status by writing 1 to it and enable PME# */
+ pmcsr |= PCI_PM_CTRL_PME_STATUS | PCI_PM_CTRL_PME_ENABLE;
+ if (!enable)
+ pmcsr &= ~PCI_PM_CTRL_PME_ENABLE;
+
+ pci_write_config_word(dev, pm + PCI_PM_CTRL, pmcsr);
+}
+
+/**
* pci_enable_wake - enable PCI device as wakeup event source
* @dev: PCI device affected
* @state: PCI state from which device will issue wakeup events
@@ -1030,77 +1077,67 @@ int pci_set_pcie_reset_state(struct pci_
* supporting the standard PCI PME# signal may require such platform hooks;
* they always update bits in config space to allow PME# generation.
*
- * -EIO is returned if the device can't be a wakeup event source.
- * -EINVAL is returned if the device can't generate wakeup events from
- * the specified PCI state. Returns zero if the operation is successful.
+ * RETURN VALUE:
+ * 0 is returned on success
+ * -EINVAL is returned if device is not supposed to wake up the system
+ * Error code depending on the platform is returned if both the platform and the
+ * native mechanism fail to enable the generation of wake-up events
*/
int pci_enable_wake(struct pci_dev *dev, pci_power_t state, int enable)
{
int pm;
- u16 value = 0;
- bool platform_done = false;
+ int error = 0;
- if (enable && platform_pci_can_wakeup(dev)) {
- /* Allow the platform to handle the device */
- int err = platform_pci_sleep_wake(dev, true);
- if (!err)
- /*
- * The platform claims to have enabled the device's
- * system wake-up capability as requested, but we are
- * going to enable PME# anyway.
- */
- platform_done = true;
- }
+ if (!device_may_wakeup(&dev->dev))
+ return -EINVAL;
- /* find PCI PM capability in list */
- pm = pci_find_capability(dev, PCI_CAP_ID_PM);
+ if (enable && platform_pci_can_wakeup(dev))
+ error = platform_pci_sleep_wake(dev, true);
- /* If device doesn't support PM Capabilities, but caller wants to
- * disable wake events, it's a NOP. Otherwise fail unless the
- * platform hooks handled this legacy device already.
+ /*
+ * We are going to enable/disable the generation of PME# even if the
+ * platform claims to have handled the device as requested.
*/
- if (!pm) {
+ pm = pci_find_capability(dev, PCI_CAP_ID_PM);
+ if (!enable || pci_pme_capable(dev, pm, state)) {
+ pci_pme_active(dev, pm, enable);
if (enable)
- return platform_done ? 0 : -EIO;
- else
- goto Platform_disable;
- }
-
- /* Check device's ability to generate PME# */
- pci_read_config_word(dev, pm + PCI_PM_PMC, &value);
-
- value &= PCI_PM_CAP_PME_MASK;
- value >>= ffs(PCI_PM_CAP_PME_MASK) - 1; /* First bit of mask */
-
- /* Check if it can generate PME# from requested state. */
- if (!value || !(value & (1 << state))) {
- if (enable) {
- return platform_done ? 0 : -EINVAL;
- } else {
- value = 0;
- goto Platform_disable;
- }
+ return 0;
}
- pci_read_config_word(dev, pm + PCI_PM_CTRL, &value);
+ if (!enable && platform_pci_can_wakeup(dev))
+ error = platform_pci_sleep_wake(dev, false);
- /* Clear PME_Status by writing 1 to it and enable PME# */
- value |= PCI_PM_CTRL_PME_STATUS | PCI_PM_CTRL_PME_ENABLE;
+ return error;
+}
- if (!enable)
- value &= ~PCI_PM_CTRL_PME_ENABLE;
+/**
+ * pci_pm_init - Initialize PM functions of given PCI device
+ * @dev: PCI device to handle.
+ */
+void pci_pm_init(struct pci_dev *dev)
+{
+ int pm;
+ bool can_wakeup = false;
- pci_write_config_word(dev, pm + PCI_PM_CTRL, value);
+ /* find PCI PM capability in list */
+ pm = pci_find_capability(dev, PCI_CAP_ID_PM);
+ if (pm) {
+ u16 pmc;
- Platform_disable:
- if (!enable && platform_pci_can_wakeup(dev)) {
- /* Allow the platform to finalize the operation */
- int err = platform_pci_sleep_wake(dev, false);
- if (err && !value)
- return -EIO;
+ /* Check device's ability to generate PME# */
+ pci_read_config_word(dev, pm + PCI_PM_PMC, &pmc);
+ if (pmc & PCI_PM_CAP_PME_MASK) {
+ can_wakeup = true;
+ /* Disable the PME# generation capability */
+ pci_pme_active(dev, pm, false);
+ }
}
- return 0;
+ if (platform_pci_can_wakeup(dev))
+ can_wakeup = true;
+
+ device_init_wakeup(&dev->dev, can_wakeup);
}
int
Index: linux-next/drivers/pci/pci.h
===================================================================
--- linux-next.orig/drivers/pci/pci.h
+++ linux-next/drivers/pci/pci.h
@@ -35,6 +35,7 @@ struct pci_platform_pm_ops {
};
extern int pci_set_platform_pm(struct pci_platform_pm_ops *ops);
+extern void pci_pm_init(struct pci_dev *dev);
extern int pci_user_read_config_byte(struct pci_dev *dev, int where, u8 *val);
extern int pci_user_read_config_word(struct pci_dev *dev, int where, u16 *val);
Index: linux-next/drivers/pci/probe.c
===================================================================
--- linux-next.orig/drivers/pci/probe.c
+++ linux-next/drivers/pci/probe.c
@@ -858,49 +858,6 @@ int pci_cfg_space_size_ext(struct pci_de
return PCI_CFG_SPACE_SIZE;
}
-/**
- * pci_disable_pme - Disable the PME function of PCI device
- * @dev: PCI device affected
- * -EINVAL is returned if PCI device doesn't support PME.
- * Zero is returned if the PME is supported and can be disabled.
- */
-static int pci_disable_pme(struct pci_dev *dev)
-{
- int pm;
- u16 value;
-
- /* find PCI PM capability in list */
- pm = pci_find_capability(dev, PCI_CAP_ID_PM);
-
- /* If device doesn't support PM Capabilities, it means that PME is
- * not supported.
- */
- if (!pm)
- return -EINVAL;
- /* Check device's ability to generate PME# */
- pci_read_config_word(dev, pm + PCI_PM_PMC, &value);
-
- value &= PCI_PM_CAP_PME_MASK;
- /* Check if it can generate PME# */
- if (!value) {
- /*
- * If it is zero, it means that PME is still unsupported
- * although there exists the PM capability.
- */
- return -EINVAL;
- }
-
- pci_read_config_word(dev, pm + PCI_PM_CTRL, &value);
-
- /* Clear PME_Status by writing 1 to it */
- value |= PCI_PM_CTRL_PME_STATUS ;
- /* Disable PME enable bit */
- value &= ~PCI_PM_CTRL_PME_ENABLE;
- pci_write_config_word(dev, pm + PCI_PM_CTRL, value);
-
- return 0;
-}
-
int pci_cfg_space_size(struct pci_dev *dev)
{
int pos;
@@ -1008,7 +965,7 @@ static struct pci_dev *pci_scan_device(s
}
pci_vpd_pci22_init(dev);
- pci_disable_pme(dev);
+ pci_pm_init(dev);
return dev;
}
^ permalink raw reply [flat|nested] 25+ messages in thread
* [RFC][PATCH 9/9] PCI PM: Introduce pci_prepare_to_sleep and pci_back_from_sleep
2008-06-19 23:45 [RFC][PATCH 0/9] PCI PM: Rework PCI device PM Rafael J. Wysocki
` (7 preceding siblings ...)
2008-06-19 23:52 ` [RFC][PATCH 8/9] PCI PM: Rework device PM initialization Rafael J. Wysocki
@ 2008-06-19 23:57 ` Rafael J. Wysocki
8 siblings, 0 replies; 25+ messages in thread
From: Rafael J. Wysocki @ 2008-06-19 23:57 UTC (permalink / raw)
To: ACPI Devel Maling List; +Cc: Alan Stern, Jesse Barnes, Len Brown, pm list
From: Rafael J. Wysocki <rjw@sisk.pl>
PCI PM: Introduce pci_prepare_to_sleep and pci_back_from_sleep
Introduce functions pci_prepare_to_sleep() and pci_back_from_sleep(),
to be used by the PCI drivers that want to place their devices into
the lowest power state appropiate for them (PCI_D3hot, if the device
is not supposed to wake up the system, or the deepest state from
which the wake-up is possible, otherwise) while the system is being
prepared to go into a sleeping state and to put them back into D0
during the subsequent transition to the working state.
Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
drivers/pci/pci.c | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++++
include/linux/pci.h | 2 +
2 files changed, 85 insertions(+)
Index: linux-next/drivers/pci/pci.c
===================================================================
--- linux-next.orig/drivers/pci/pci.c
+++ linux-next/drivers/pci/pci.c
@@ -1112,6 +1112,87 @@ int pci_enable_wake(struct pci_dev *dev,
}
/**
+ * pci_prepare_to_sleep - prepare PCI device for system-wide transition into
+ * a sleep state
+ * @dev: Device to handle.
+ *
+ * Choose the power state appropriate for the device depending on whether
+ * it can wake-up the system and/or is power manageable by the platform
+ * (PCI_D3hot is the default) and put the device into that state.
+ */
+int pci_prepare_to_sleep(struct pci_dev *dev)
+{
+ pci_power_t target_state = PCI_D3hot;
+ int pm = pci_find_capability(dev, PCI_CAP_ID_PM);
+ int error;
+
+ if (platform_pci_power_manageable(dev)) {
+ /*
+ * Call the platform to choose the target state of the device
+ * and enable wake-up from this state if supported.
+ */
+ pci_power_t state = platform_pci_choose_state(dev);
+
+ switch (state) {
+ case PCI_POWER_ERROR:
+ case PCI_UNKNOWN:
+ break;
+ case PCI_D1:
+ case PCI_D2:
+ if (pci_no_d1d2(dev))
+ break;
+ default:
+ target_state = state;
+ pci_enable_wake(dev, target_state, true);
+ }
+ } else if (device_may_wakeup(&dev->dev)) {
+ /*
+ * Find the deepest state from which the device can generate
+ * wake-up events, make it the target state and enable device
+ * to generate PME#.
+ */
+ u16 pmc;
+
+ if (!pm)
+ return -EIO;
+
+ pci_read_config_word(dev, pm + PCI_PM_PMC, &pmc);
+ if (pmc & PCI_PM_CAP_PME_MASK) {
+ if (!(pmc & PCI_PM_CAP_PME_D3)) {
+ /* Device cannot generate PME# from D3_hot */
+ if (pmc & PCI_PM_CAP_PME_D2)
+ target_state = PCI_D2;
+ else if (pmc & PCI_PM_CAP_PME_D1)
+ target_state = PCI_D1;
+ else
+ target_state = PCI_D0;
+ }
+ pci_pme_active(dev, pm, true);
+ }
+ }
+
+ error = pci_set_power_state(dev, target_state);
+
+ if (error)
+ pci_enable_wake(dev, target_state, false);
+
+ return error;
+}
+
+/**
+ * pci_back_from_sleep - turn PCI device on during system-wide transition into
+ * the working state a sleep state
+ * @dev: Device to handle.
+ *
+ * Disable device's sytem wake-up capability and put it into D0.
+ */
+int pci_back_from_sleep(struct pci_dev *dev)
+{
+ pci_enable_wake(dev, PCI_D0, false);
+ return pci_set_power_state(dev, PCI_D0);
+}
+
+/**
* pci_pm_init - Initialize PM functions of given PCI device
* @dev: PCI device to handle.
*/
@@ -1809,5 +1890,7 @@ EXPORT_SYMBOL(pci_set_power_state);
EXPORT_SYMBOL(pci_save_state);
EXPORT_SYMBOL(pci_restore_state);
EXPORT_SYMBOL(pci_enable_wake);
+EXPORT_SYMBOL(pci_prepare_to_sleep);
+EXPORT_SYMBOL(pci_back_from_sleep);
EXPORT_SYMBOL_GPL(pci_set_pcie_reset_state);
Index: linux-next/include/linux/pci.h
===================================================================
--- linux-next.orig/include/linux/pci.h
+++ linux-next/include/linux/pci.h
@@ -633,6 +633,8 @@ int pci_restore_state(struct pci_dev *de
int pci_set_power_state(struct pci_dev *dev, pci_power_t state);
pci_power_t pci_choose_state(struct pci_dev *dev, pm_message_t state);
int pci_enable_wake(struct pci_dev *dev, pci_power_t state, int enable);
+int pci_prepare_to_sleep(struct pci_dev *dev);
+int pci_back_from_sleep(struct pci_dev *dev);
/* Functions for PCI Hotplug drivers to use */
int pci_bus_find_capability(struct pci_bus *bus, unsigned int devfn, int cap);
^ permalink raw reply [flat|nested] 25+ messages in thread
* [RFC][PATCH 8/9] PCI PM: Rework device PM initialization (rev. 2)
2008-06-19 23:52 ` [RFC][PATCH 8/9] PCI PM: Rework device PM initialization Rafael J. Wysocki
@ 2008-06-20 15:59 ` Rafael J. Wysocki
2008-06-20 16:07 ` [RFC][PATCH 8.5/9] PCI PM: Add diagnostic statements to PCI device PM initialization Rafael J. Wysocki
0 siblings, 1 reply; 25+ messages in thread
From: Rafael J. Wysocki @ 2008-06-20 15:59 UTC (permalink / raw)
To: ACPI Devel Maling List; +Cc: Alan Stern, Jesse Barnes, Len Brown, pm list
On Friday, 20 of June 2008, Rafael J. Wysocki wrote:
> From: Rafael J. Wysocki <rjw@sisk.pl>
>
> PCI PM: Rework device PM initialization
>
> Rework PCI device PM initialization so that if given device is capable
> of generating wake-up events, either natively through the PME#
> mechanism, or with the help of the platform, its power.can_wakeup
> and power.should_wakeup flags are set as appropriate.
>
> Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
One correction is reqired in this patch. Namely, it's too early to call
pci_pm_init() in pci_scan_device(), because pci_device_add() will then call
device_initialize() and overwrite dev->dev.power settings.
Updated patch follows.
Thanks,
Rafael
---
From: Rafael J. Wysocki <rjw@sisk.pl>
PCI PM: Rework device PM initialization (rev. 2)
Rework PCI device PM initialization so that if given device is capable
of generating wake-up events, either natively through the PME#
mechanism, or with the help of the platform, its power.can_wakeup
and power.should_wakeup flags are set as appropriate.
Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
drivers/pci/pci.c | 145 ++++++++++++++++++++++++++++++++--------------------
drivers/pci/pci.h | 1
drivers/pci/probe.c | 47 +---------------
3 files changed, 95 insertions(+), 98 deletions(-)
Index: linux-next/drivers/pci/pci.c
===================================================================
--- linux-next.orig/drivers/pci/pci.c
+++ linux-next/drivers/pci/pci.c
@@ -1016,6 +1016,53 @@ int pci_set_pcie_reset_state(struct pci_
}
/**
+ * pci_pme_capable - check the capability of PCI device to generate PME#
+ * @dev: PCI device to handle.
+ * @pm: PCI PM capability offset of the device.
+ * @state: PCI state from which device will issue PME#.
+ */
+static bool pci_pme_capable(struct pci_dev *dev, int pm, pci_power_t state)
+{
+ u16 pmc;
+
+ if (!pm)
+ return false;
+
+ /* Check device's ability to generate PME# from given state */
+ pci_read_config_word(dev, pm + PCI_PM_PMC, &pmc);
+
+ pmc &= PCI_PM_CAP_PME_MASK;
+ pmc >>= ffs(PCI_PM_CAP_PME_MASK) - 1; /* First bit of mask */
+
+ return !!(pmc & (1 << state));
+}
+
+/**
+ * pci_pme_active - enable or disable PCI device's PME# function
+ * @dev: PCI device to handle.
+ * @pm: PCI PM capability offset of the device.
+ * @enable: 'true' to enable PME# generation; 'false' to disable it.
+ *
+ * The caller must verify that the device is capable of generating PME# before
+ * calling this function with @enable equal to 'true'.
+ */
+static void pci_pme_active(struct pci_dev *dev, int pm, bool enable)
+{
+ u16 pmcsr;
+
+ if (!pm)
+ return;
+
+ pci_read_config_word(dev, pm + PCI_PM_CTRL, &pmcsr);
+ /* Clear PME_Status by writing 1 to it and enable PME# */
+ pmcsr |= PCI_PM_CTRL_PME_STATUS | PCI_PM_CTRL_PME_ENABLE;
+ if (!enable)
+ pmcsr &= ~PCI_PM_CTRL_PME_ENABLE;
+
+ pci_write_config_word(dev, pm + PCI_PM_CTRL, pmcsr);
+}
+
+/**
* pci_enable_wake - enable PCI device as wakeup event source
* @dev: PCI device affected
* @state: PCI state from which device will issue wakeup events
@@ -1030,77 +1077,67 @@ int pci_set_pcie_reset_state(struct pci_
* supporting the standard PCI PME# signal may require such platform hooks;
* they always update bits in config space to allow PME# generation.
*
- * -EIO is returned if the device can't be a wakeup event source.
- * -EINVAL is returned if the device can't generate wakeup events from
- * the specified PCI state. Returns zero if the operation is successful.
+ * RETURN VALUE:
+ * 0 is returned on success
+ * -EINVAL is returned if device is not supposed to wake up the system
+ * Error code depending on the platform is returned if both the platform and the
+ * native mechanism fail to enable the generation of wake-up events
*/
int pci_enable_wake(struct pci_dev *dev, pci_power_t state, int enable)
{
int pm;
- u16 value = 0;
- bool platform_done = false;
+ int error = 0;
- if (enable && platform_pci_can_wakeup(dev)) {
- /* Allow the platform to handle the device */
- int err = platform_pci_sleep_wake(dev, true);
- if (!err)
- /*
- * The platform claims to have enabled the device's
- * system wake-up capability as requested, but we are
- * going to enable PME# anyway.
- */
- platform_done = true;
- }
+ if (!device_may_wakeup(&dev->dev))
+ return -EINVAL;
- /* find PCI PM capability in list */
- pm = pci_find_capability(dev, PCI_CAP_ID_PM);
+ if (enable && platform_pci_can_wakeup(dev))
+ error = platform_pci_sleep_wake(dev, true);
- /* If device doesn't support PM Capabilities, but caller wants to
- * disable wake events, it's a NOP. Otherwise fail unless the
- * platform hooks handled this legacy device already.
+ /*
+ * We are going to enable/disable the generation of PME# even if the
+ * platform claims to have handled the device as requested.
*/
- if (!pm) {
+ pm = pci_find_capability(dev, PCI_CAP_ID_PM);
+ if (!enable || pci_pme_capable(dev, pm, state)) {
+ pci_pme_active(dev, pm, enable);
if (enable)
- return platform_done ? 0 : -EIO;
- else
- goto Platform_disable;
- }
-
- /* Check device's ability to generate PME# */
- pci_read_config_word(dev, pm + PCI_PM_PMC, &value);
-
- value &= PCI_PM_CAP_PME_MASK;
- value >>= ffs(PCI_PM_CAP_PME_MASK) - 1; /* First bit of mask */
-
- /* Check if it can generate PME# from requested state. */
- if (!value || !(value & (1 << state))) {
- if (enable) {
- return platform_done ? 0 : -EINVAL;
- } else {
- value = 0;
- goto Platform_disable;
- }
+ return 0;
}
- pci_read_config_word(dev, pm + PCI_PM_CTRL, &value);
+ if (!enable && platform_pci_can_wakeup(dev))
+ error = platform_pci_sleep_wake(dev, false);
- /* Clear PME_Status by writing 1 to it and enable PME# */
- value |= PCI_PM_CTRL_PME_STATUS | PCI_PM_CTRL_PME_ENABLE;
+ return error;
+}
- if (!enable)
- value &= ~PCI_PM_CTRL_PME_ENABLE;
+/**
+ * pci_pm_init - Initialize PM functions of given PCI device
+ * @dev: PCI device to handle.
+ */
+void pci_pm_init(struct pci_dev *dev)
+{
+ int pm;
+ bool can_wakeup = false;
- pci_write_config_word(dev, pm + PCI_PM_CTRL, value);
+ /* find PCI PM capability in list */
+ pm = pci_find_capability(dev, PCI_CAP_ID_PM);
+ if (pm) {
+ u16 pmc;
- Platform_disable:
- if (!enable && platform_pci_can_wakeup(dev)) {
- /* Allow the platform to finalize the operation */
- int err = platform_pci_sleep_wake(dev, false);
- if (err && !value)
- return -EIO;
+ /* Check device's ability to generate PME# */
+ pci_read_config_word(dev, pm + PCI_PM_PMC, &pmc);
+ if (pmc & PCI_PM_CAP_PME_MASK) {
+ can_wakeup = true;
+ /* Disable the PME# generation capability */
+ pci_pme_active(dev, pm, false);
+ }
}
- return 0;
+ if (platform_pci_can_wakeup(dev))
+ can_wakeup = true;
+
+ device_init_wakeup(&dev->dev, can_wakeup);
}
int
Index: linux-next/drivers/pci/pci.h
===================================================================
--- linux-next.orig/drivers/pci/pci.h
+++ linux-next/drivers/pci/pci.h
@@ -35,6 +35,7 @@ struct pci_platform_pm_ops {
};
extern int pci_set_platform_pm(struct pci_platform_pm_ops *ops);
+extern void pci_pm_init(struct pci_dev *dev);
extern int pci_user_read_config_byte(struct pci_dev *dev, int where, u8 *val);
extern int pci_user_read_config_word(struct pci_dev *dev, int where, u16 *val);
Index: linux-next/drivers/pci/probe.c
===================================================================
--- linux-next.orig/drivers/pci/probe.c
+++ linux-next/drivers/pci/probe.c
@@ -858,49 +858,6 @@ int pci_cfg_space_size_ext(struct pci_de
return PCI_CFG_SPACE_SIZE;
}
-/**
- * pci_disable_pme - Disable the PME function of PCI device
- * @dev: PCI device affected
- * -EINVAL is returned if PCI device doesn't support PME.
- * Zero is returned if the PME is supported and can be disabled.
- */
-static int pci_disable_pme(struct pci_dev *dev)
-{
- int pm;
- u16 value;
-
- /* find PCI PM capability in list */
- pm = pci_find_capability(dev, PCI_CAP_ID_PM);
-
- /* If device doesn't support PM Capabilities, it means that PME is
- * not supported.
- */
- if (!pm)
- return -EINVAL;
- /* Check device's ability to generate PME# */
- pci_read_config_word(dev, pm + PCI_PM_PMC, &value);
-
- value &= PCI_PM_CAP_PME_MASK;
- /* Check if it can generate PME# */
- if (!value) {
- /*
- * If it is zero, it means that PME is still unsupported
- * although there exists the PM capability.
- */
- return -EINVAL;
- }
-
- pci_read_config_word(dev, pm + PCI_PM_CTRL, &value);
-
- /* Clear PME_Status by writing 1 to it */
- value |= PCI_PM_CTRL_PME_STATUS ;
- /* Disable PME enable bit */
- value &= ~PCI_PM_CTRL_PME_ENABLE;
- pci_write_config_word(dev, pm + PCI_PM_CTRL, value);
-
- return 0;
-}
-
int pci_cfg_space_size(struct pci_dev *dev)
{
int pos;
@@ -1008,7 +965,6 @@ static struct pci_dev *pci_scan_device(s
}
pci_vpd_pci22_init(dev);
- pci_disable_pme(dev);
return dev;
}
@@ -1029,6 +985,9 @@ void pci_device_add(struct pci_dev *dev,
/* Fix up broken headers */
pci_fixup_device(pci_fixup_header, dev);
+ /* Initialize power management of the device */
+ pci_pm_init(dev);
+
/*
* Add the device to our list of discovered devices
* and the bus list for fixup functions, etc.
^ permalink raw reply [flat|nested] 25+ messages in thread
* [RFC][PATCH 8.5/9] PCI PM: Add diagnostic statements to PCI device PM initialization
2008-06-20 15:59 ` [RFC][PATCH 8/9] PCI PM: Rework device PM initialization (rev. 2) Rafael J. Wysocki
@ 2008-06-20 16:07 ` Rafael J. Wysocki
0 siblings, 0 replies; 25+ messages in thread
From: Rafael J. Wysocki @ 2008-06-20 16:07 UTC (permalink / raw)
To: ACPI Devel Maling List; +Cc: Alan Stern, Jesse Barnes, Len Brown, pm list
Also, it seems reasonable to add some diagnostic printk()s to pci_pm_init().
Thanks,
Rafael
---
From: Rafael J. Wysocki <rjw@sisk.pl>
PCI PM: Add diagnostic statements to PCI device PM initialization
Add two diagnostic printk()s to pci_pm_init() allowing us to check
in the kernel log which PCI devices support generation of PME# and
from what states as well as which PCI devices can generate wake-up
events with the help of the platform.
Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
drivers/pci/pci.c | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
Index: linux-next/drivers/pci/pci.c
===================================================================
--- linux-next.orig/drivers/pci/pci.c
+++ linux-next/drivers/pci/pci.c
@@ -1128,14 +1128,24 @@ void pci_pm_init(struct pci_dev *dev)
/* Check device's ability to generate PME# */
pci_read_config_word(dev, pm + PCI_PM_PMC, &pmc);
if (pmc & PCI_PM_CAP_PME_MASK) {
+ printk(KERN_INFO "PCI: Device %s can generate PME# from"
+ "%s%s%s%s%s\n", pci_name(dev),
+ (pmc & PCI_PM_CAP_PME_D0) ? " D0" : "",
+ (pmc & PCI_PM_CAP_PME_D1) ? " D1" : "",
+ (pmc & PCI_PM_CAP_PME_D2) ? " D2" : "",
+ (pmc & PCI_PM_CAP_PME_D3) ? " D3hot" : "",
+ (pmc & PCI_PM_CAP_PME_D3cold) ? " D3cold" : "");
can_wakeup = true;
/* Disable the PME# generation capability */
pci_pme_active(dev, pm, false);
}
}
- if (platform_pci_can_wakeup(dev))
+ if (platform_pci_can_wakeup(dev)) {
+ printk(KERN_INFO "PCI: Device %s can generate system wake-up "
+ "events through the platform\n", pci_name(dev));
can_wakeup = true;
+ }
device_init_wakeup(&dev->dev, can_wakeup);
}
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [linux-pm] [RFC][PATCH 1/9] ACPI: Introduce acpi_bus_power_manageable function
2008-06-19 23:46 ` [RFC][PATCH 1/9] ACPI: Introduce acpi_bus_power_manageable function Rafael J. Wysocki
@ 2008-06-24 12:25 ` Pavel Machek
0 siblings, 0 replies; 25+ messages in thread
From: Pavel Machek @ 2008-06-24 12:25 UTC (permalink / raw)
To: Rafael J. Wysocki; +Cc: ACPI Devel Maling List, pm list, Jesse Barnes
On Fri 2008-06-20 01:46:30, Rafael J. Wysocki wrote:
> From: Rafael J. Wysocki <rjw@sisk.pl>
>
> ACPI: Introduce acpi_bus_power_manageable function
>
> Introduce function acpi_bus_power_manageable() allowing other
> (dependent) subsystems to check if ACPI is able to power manage given
> device. This may be useful, for example, for PCI device power
> management.
>
> Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
ACK.
--
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [linux-pm] [RFC][PATCH 2/9] PCI: Introduce platform_pci_power_manageable function
2008-06-19 23:47 ` [RFC][PATCH 2/9] PCI: Introduce platform_pci_power_manageable function Rafael J. Wysocki
@ 2008-06-24 12:31 ` Pavel Machek
0 siblings, 0 replies; 25+ messages in thread
From: Pavel Machek @ 2008-06-24 12:31 UTC (permalink / raw)
To: Rafael J. Wysocki; +Cc: ACPI Devel Maling List, pm list, Jesse Barnes
Hi!
> +static struct pci_platform_pm_ops *pci_platform_pm;
> +
> +int pci_set_platform_pm(struct pci_platform_pm_ops *ops)
> +{
> + if (!ops->is_manageable || !ops->set_state || !ops->choose_state)
> + return -EINVAL;
> + pci_platform_pm = ops;
> + return 0;
> +}
BUG_ON? It is programmer error if we see NULLs here, right? (And those
programmers will be exactly those not checking the return value).
Otherwise looks ok to me.
--
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [linux-pm] [RFC][PATCH 3/9] PCI: Rework pci_set_power_state function
2008-06-19 23:48 ` [RFC][PATCH 3/9] PCI: Rework pci_set_power_state function Rafael J. Wysocki
@ 2008-06-24 12:34 ` Pavel Machek
0 siblings, 0 replies; 25+ messages in thread
From: Pavel Machek @ 2008-06-24 12:34 UTC (permalink / raw)
To: Rafael J. Wysocki; +Cc: ACPI Devel Maling List, pm list, Jesse Barnes
On Fri 2008-06-20 01:48:23, Rafael J. Wysocki wrote:
> From: Rafael J. Wysocki <rjw@sisk.pl>
>
> PCI: Rework pci_set_power_state function
>
> Rework pci_set_power_state() so that the platform callback is
> invoked before the native mechanism, if necessary. Also, make
> the function check if the device is power manageable by the
> platform before invoking the platform callback.
>
> This may matter if the device dependent on additional power
> resources controlled by the platform is being put into D0, in which
> case those power resources must be turned on before we attempt to
> handle the device itself.
>
> Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
Looks ok to me. ACK.
--
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [linux-pm] [RFC][PATCH 4/9] ACPI: Introduce acpi_device_sleep_wake function
2008-06-19 23:49 ` [RFC][PATCH 4/9] ACPI: Introduce acpi_device_sleep_wake function Rafael J. Wysocki
@ 2008-06-24 12:38 ` Pavel Machek
0 siblings, 0 replies; 25+ messages in thread
From: Pavel Machek @ 2008-06-24 12:38 UTC (permalink / raw)
To: Rafael J. Wysocki; +Cc: ACPI Devel Maling List, pm list, Jesse Barnes
On Fri 2008-06-20 01:49:30, Rafael J. Wysocki wrote:
> From: Rafael J. Wysocki <rjw@sisk.pl>
>
> ACPI: Introduce acpi_device_sleep_wake function
>
> The currect ACPI code attempts to execute _PSW at three different
> places and in one of them only it tries to execute _DSW before _PSW,
> which is inconsistent with the other two cases.
>
> Move the execution of _DSW and _PSW into a separate function called
> acpi_device_sleep_wake() and call it wherever appropriate instead of
> executing _DSW and/or _PSW directly.
>
> Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
ACK.
--
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [RFC][PATCH 6/9] PCI ACPI: Introduce acpi_pm_device_sleep_wake function
2008-06-19 23:51 ` [RFC][PATCH 6/9] PCI ACPI: Introduce acpi_pm_device_sleep_wake function Rafael J. Wysocki
@ 2008-06-25 8:11 ` Zhang Rui
2008-06-25 10:29 ` Zhao Yakui
0 siblings, 1 reply; 25+ messages in thread
From: Zhang Rui @ 2008-06-25 8:11 UTC (permalink / raw)
To: Rafael J. Wysocki
Cc: ACPI Devel Maling List, Alan Stern, Jesse Barnes, Len Brown,
pm list
Hi, Rafael,
On Fri, 2008-06-20 at 01:51 +0200, Rafael J. Wysocki wrote:
> From: Rafael J. Wysocki <rjw@sisk.pl>
>
> PCI ACPI: Introduce acpi_pm_device_sleep_wake function
>
> Introduce function acpi_pm_device_sleep_wake() for enabling and
> disabling the system wake-up capability of devices that are power
> manageable by ACPI.
>
> Introduce callback .sleep_wake() in struct pci_platform_pm_ops and
> for the ACPI PCI 'driver' make it use acpi_pm_device_sleep_wake().
acpi_pm_device_sleep_wake only enable/disable the power of wakeup
device, right?
If it doesn't set the acpi_device->wakeup.state.enabled, the wake up GPE
will not be enabled, which means the pci devices which have invoked
platform_pci_sleep_wake(dev, true) can not wake up the system as
excepted.
so this patch won't work. Is this what you want? or do I miss something?
But what is annoying me is that,
if acpi_device->wakeup.state.enabled is set in
acpi_pm_device_sleep_wake, this may bring some other trouble.
A lot of pci devices will call platform_pci_sleep_wake(dev, true) by
default, they won't wake up the system before because the wake up GPEs
are disabled, but they will do if this patch is applied.
So some of the users may got the "resume immediately after sleep"
problem, that's a regression, isn't it?
we have discussed this a couple of times on the list, but we have not
got a solution yet.
IMO, both Dave's and your patches are good, and we can be a little
aggressive to merge them, and fix the drivers once we get any
regressions ASAP.
i.e. either change pci_enable_wake(dev, 1) to pci_enable_wake(dev, 0) in
the drivers suspend method, or invoke device_set_wakeup_enable(dev, 0)
to disable the wakeup ability after the call to device_init_wakeup(dev,
1).
any ideas?
thanks,
rui
>
> Modify pci_enable_wake() to use the new callback and drop the generic
> .platform_enable_wakeup() callback that is not used any more.
>
> Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
> ---
> drivers/acpi/sleep/main.c | 25 +++++++++++++++++
> drivers/base/power/sysfs.c | 3 --
> drivers/pci/pci-acpi.c | 11 +++++++
> drivers/pci/pci.c | 64 ++++++++++++++++++++++++++++++---------------
> drivers/pci/pci.h | 3 ++
> include/acpi/acpi_bus.h | 1
> include/linux/pm_wakeup.h | 16 -----------
> 7 files changed, 84 insertions(+), 39 deletions(-)
>
> Index: linux-next/drivers/acpi/sleep/main.c
> ===================================================================
> --- linux-next.orig/drivers/acpi/sleep/main.c
> +++ linux-next/drivers/acpi/sleep/main.c
> @@ -468,6 +468,31 @@ int acpi_pm_device_sleep_state(struct de
> *d_min_p = d_min;
> return d_max;
> }
> +
> +/**
> + * acpi_pm_device_sleep_wake - enable or disable the system wake-up
> + * capability of given device
> + * @dev: device to handle
> + * @enable: 'true' - enable, 'false' - disable the wake-up capability
> + */
> +int acpi_pm_device_sleep_wake(struct device *dev, bool enable)
> +{
> + acpi_handle handle;
> + struct acpi_device *adev;
> +
> + if (!device_may_wakeup(dev))
> + return -EINVAL;
> +
> + handle = DEVICE_ACPI_HANDLE(dev);
> + if (!handle || ACPI_FAILURE(acpi_bus_get_device(handle, &adev))) {
> + printk(KERN_DEBUG "ACPI handle has no context!\n");
> + return -ENODEV;
> + }
> +
> + return enable ?
> + acpi_enable_wakeup_device_power(adev, acpi_target_sleep_state) :
> + acpi_disable_wakeup_device_power(adev);
> +}
> #endif
>
> static void acpi_power_off_prepare(void)
> Index: linux-next/drivers/pci/pci-acpi.c
> ===================================================================
> --- linux-next.orig/drivers/pci/pci-acpi.c
> +++ linux-next/drivers/pci/pci-acpi.c
> @@ -299,10 +299,21 @@ static int acpi_pci_set_power_state(stru
> return error;
> }
>
> +static int acpi_pci_sleep_wake(struct pci_dev *dev, bool enable)
> +{
> + int error = acpi_pm_device_sleep_wake(&dev->dev, enable);
> +
> + if (!error)
> + printk(KERN_INFO "PCI: Wake-up capability of %s %s by ACPI\n",
> + pci_name(dev), enable ? "enabled" : "disabled");
> + return error;
> +}
> +
> 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,
> + .sleep_wake = acpi_pci_sleep_wake,
> };
>
> /* ACPI bus type */
> Index: linux-next/drivers/pci/pci.h
> ===================================================================
> --- linux-next.orig/drivers/pci/pci.h
> +++ linux-next/drivers/pci/pci.h
> @@ -17,6 +17,8 @@ extern void pci_cleanup_rom(struct pci_d
> * platform; to be used during system-wide transitions from a
> * sleeping state to the working state and vice versa
> *
> + * @sleep_wake - enables/disables the system wake up capability of given device
> + *
> * If given platform is generally capable of power managing PCI devices, all of
> * these callbacks are mandatory.
> */
> @@ -24,6 +26,7 @@ struct pci_platform_pm_ops {
> bool (*is_manageable)(struct pci_dev *dev);
> int (*set_state)(struct pci_dev *dev, pci_power_t state);
> pci_power_t (*choose_state)(struct pci_dev *dev);
> + int (*sleep_wake)(struct pci_dev *dev, bool enable);
> };
>
> extern int pci_set_platform_pm(struct pci_platform_pm_ops *ops);
> Index: linux-next/include/acpi/acpi_bus.h
> ===================================================================
> --- linux-next.orig/include/acpi/acpi_bus.h
> +++ linux-next/include/acpi/acpi_bus.h
> @@ -383,6 +383,7 @@ acpi_handle acpi_get_pci_rootbridge_hand
>
> #ifdef CONFIG_PM_SLEEP
> int acpi_pm_device_sleep_state(struct device *, int *);
> +int acpi_pm_device_sleep_wake(struct device *, bool);
> #else /* !CONFIG_PM_SLEEP */
> static inline int acpi_pm_device_sleep_state(struct device *d, int *p)
> {
> Index: linux-next/drivers/pci/pci.c
> ===================================================================
> --- linux-next.orig/drivers/pci/pci.c
> +++ linux-next/drivers/pci/pci.c
> @@ -380,7 +380,8 @@ static struct pci_platform_pm_ops *pci_p
>
> int pci_set_platform_pm(struct pci_platform_pm_ops *ops)
> {
> - if (!ops->is_manageable || !ops->set_state || !ops->choose_state)
> + if (!ops->is_manageable || !ops->set_state || !ops->choose_state
> + || !ops->sleep_wake)
> return -EINVAL;
> pci_platform_pm = ops;
> return 0;
> @@ -403,6 +404,12 @@ static inline pci_power_t platform_pci_c
> pci_platform_pm->choose_state(dev) : PCI_POWER_ERROR;
> }
>
> +static inline int platform_pci_sleep_wake(struct pci_dev *dev, bool enable)
> +{
> + return pci_platform_pm ?
> + pci_platform_pm->sleep_wake(dev, enable) : -ENODEV;
> +}
> +
> /**
> * pci_set_power_state - Set the power state of a PCI device
> * @dev: PCI device to be suspended
> @@ -1018,24 +1025,27 @@ int pci_set_pcie_reset_state(struct pci_
> * supporting the standard PCI PME# signal may require such platform hooks;
> * they always update bits in config space to allow PME# generation.
> *
> - * -EIO is returned if the device can't ever be a wakeup event source.
> + * -EIO is returned if the device can't be a wakeup event source.
> * -EINVAL is returned if the device can't generate wakeup events from
> * the specified PCI state. Returns zero if the operation is successful.
> */
> int pci_enable_wake(struct pci_dev *dev, pci_power_t state, int enable)
> {
> int pm;
> - int status;
> - u16 value;
> + u16 value = 0;
> + bool platform_done = false;
>
> - /* Note that drivers should verify device_may_wakeup(&dev->dev)
> - * before calling this function. Platform code should report
> - * errors when drivers try to enable wakeup on devices that
> - * can't issue wakeups, or on which wakeups were disabled by
> - * userspace updating the /sys/devices.../power/wakeup file.
> - */
> -
> - status = call_platform_enable_wakeup(&dev->dev, enable);
> + if (enable && platform_pci_power_manageable(dev)) {
> + /* Allow the platform to handle the device */
> + int err = platform_pci_sleep_wake(dev, true);
> + if (!err)
> + /*
> + * The platform claims to have enabled the device's
> + * system wake-up capability as requested, but we are
> + * going to enable PME# anyway.
> + */
> + platform_done = true;
> + }
>
> /* find PCI PM capability in list */
> pm = pci_find_capability(dev, PCI_CAP_ID_PM);
> @@ -1044,23 +1054,27 @@ int pci_enable_wake(struct pci_dev *dev,
> * disable wake events, it's a NOP. Otherwise fail unless the
> * platform hooks handled this legacy device already.
> */
> - if (!pm)
> - return enable ? status : 0;
> + if (!pm) {
> + if (enable)
> + return platform_done ? 0 : -EIO;
> + else
> + goto Platform_disable;
> + }
>
> /* Check device's ability to generate PME# */
> - pci_read_config_word(dev,pm+PCI_PM_PMC,&value);
> + pci_read_config_word(dev, pm + PCI_PM_PMC, &value);
>
> value &= PCI_PM_CAP_PME_MASK;
> value >>= ffs(PCI_PM_CAP_PME_MASK) - 1; /* First bit of mask */
>
> /* Check if it can generate PME# from requested state. */
> if (!value || !(value & (1 << state))) {
> - /* if it can't, revert what the platform hook changed,
> - * always reporting the base "EINVAL, can't PME#" error
> - */
> - if (enable)
> - call_platform_enable_wakeup(&dev->dev, 0);
> - return enable ? -EINVAL : 0;
> + if (enable) {
> + return platform_done ? 0 : -EINVAL;
> + } else {
> + value = 0;
> + goto Platform_disable;
> + }
> }
>
> pci_read_config_word(dev, pm + PCI_PM_CTRL, &value);
> @@ -1073,6 +1087,14 @@ int pci_enable_wake(struct pci_dev *dev,
>
> pci_write_config_word(dev, pm + PCI_PM_CTRL, value);
>
> + Platform_disable:
> + if (!enable && platform_pci_power_manageable(dev)) {
> + /* Allow the platform to finalize the operation */
> + int err = platform_pci_sleep_wake(dev, false);
> + if (err && !value)
> + return -EIO;
> + }
> +
> return 0;
> }
>
> Index: linux-next/include/linux/pm_wakeup.h
> ===================================================================
> --- linux-next.orig/include/linux/pm_wakeup.h
> +++ linux-next/include/linux/pm_wakeup.h
> @@ -47,21 +47,7 @@ static inline void device_set_wakeup_ena
>
> static inline int device_may_wakeup(struct device *dev)
> {
> - return dev->power.can_wakeup & dev->power.should_wakeup;
> -}
> -
> -/*
> - * Platform hook to activate device wakeup capability, if that's not already
> - * handled by enable_irq_wake() etc.
> - * Returns zero on success, else negative errno
> - */
> -extern int (*platform_enable_wakeup)(struct device *dev, int is_on);
> -
> -static inline int call_platform_enable_wakeup(struct device *dev, int is_on)
> -{
> - if (platform_enable_wakeup)
> - return (*platform_enable_wakeup)(dev, is_on);
> - return 0;
> + return dev->power.can_wakeup && dev->power.should_wakeup;
> }
>
> #else /* !CONFIG_PM */
> Index: linux-next/drivers/base/power/sysfs.c
> ===================================================================
> --- linux-next.orig/drivers/base/power/sysfs.c
> +++ linux-next/drivers/base/power/sysfs.c
> @@ -6,9 +6,6 @@
> #include <linux/string.h>
> #include "power.h"
>
> -int (*platform_enable_wakeup)(struct device *dev, int is_on);
> -
> -
> /*
> * wakeup - Report/change current wakeup option for device
> *
>
> --
> 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
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [RFC][PATCH 6/9] PCI ACPI: Introduce acpi_pm_device_sleep_wake function
2008-06-25 8:11 ` Zhang Rui
@ 2008-06-25 10:29 ` Zhao Yakui
2008-06-25 18:57 ` Rafael J. Wysocki
0 siblings, 1 reply; 25+ messages in thread
From: Zhao Yakui @ 2008-06-25 10:29 UTC (permalink / raw)
To: Zhang Rui
Cc: Rafael J. Wysocki, ACPI Devel Maling List, Alan Stern,
Jesse Barnes, Len Brown, pm list
On Wed, 2008-06-25 at 16:11 +0800, Zhang Rui wrote:
> Hi, Rafael,
>
> On Fri, 2008-06-20 at 01:51 +0200, Rafael J. Wysocki wrote:
> > From: Rafael J. Wysocki <rjw@sisk.pl>
> >
> > PCI ACPI: Introduce acpi_pm_device_sleep_wake function
> >
> > Introduce function acpi_pm_device_sleep_wake() for enabling and
> > disabling the system wake-up capability of devices that are power
> > manageable by ACPI.
> >
> > Introduce callback .sleep_wake() in struct pci_platform_pm_ops and
> > for the ACPI PCI 'driver' make it use acpi_pm_device_sleep_wake().
>
> acpi_pm_device_sleep_wake only enable/disable the power of wakeup
> device, right?
> If it doesn't set the acpi_device->wakeup.state.enabled, the wake up GPE
> will not be enabled, which means the pci devices which have invoked
> platform_pci_sleep_wake(dev, true) can not wake up the system as
> excepted.
> so this patch won't work. Is this what you want? or do I miss something?
>From the patch it seems that acpi_dev->wakeup.state.enable is not set in
the function of acpi_pm_device_sleep_wake. If it is not set, it means
that the corresponding GPE won't be enabled. The device still can't wake
up the sleeping system.
>
> But what is annoying me is that,
> if acpi_device->wakeup.state.enabled is set in
> acpi_pm_device_sleep_wake, this may bring some other trouble.
> A lot of pci devices will call platform_pci_sleep_wake(dev, true) by
> default, they won't wake up the system before because the wake up GPEs
> are disabled, but they will do if this patch is applied.
> So some of the users may got the "resume immediately after sleep"
> problem, that's a regression, isn't it?
What Rui said is right. In current kernel when the system enters the
sleeping state, the pci_enable_wake(dev,state, 1) will be called for the
PCI device. If acpi_dev->wakeup.state.enabled is set in
acpi_pm_device_sleep_wake , it means that the wakeup GPE will be
enabled. Maybe some systems will be resumed immediately after sleeping.
this is not what we expected.
>
> we have discussed this a couple of times on the list, but we have not
> got a solution yet.
> IMO, both Dave's and your patches are good, and we can be a little
> aggressive to merge them, and fix the drivers once we get any
> regressions ASAP.
As the acpi_dev->wakeup.state.enabled is not touched in this patch, it
will be OK to merge them.
If acpi_dev->wakeup.state.enable is required to be set/unset in
acpi_pm_device_sleep_wake, maybe Rui's suggestion is good. Unless some
device is expected to wake up the sleeping system, we will enable the
corresponding GPE. Otherwise it will be assumed that the device needn't
wake up the sleeping system and unnecessary to enable the device power
and the corresponding GPE.
> i.e. either change pci_enable_wake(dev, 1) to pci_enable_wake(dev, 0) in
> the drivers suspend method, or invoke device_set_wakeup_enable(dev, 0)
> to disable the wakeup ability after the call to device_init_wakeup(dev,
> 1).
>
> any ideas?
>
> thanks,
> rui
>
> >
> > Modify pci_enable_wake() to use the new callback and drop the generic
> > .platform_enable_wakeup() callback that is not used any more.
> >
> > Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
> > ---
> > drivers/acpi/sleep/main.c | 25 +++++++++++++++++
> > drivers/base/power/sysfs.c | 3 --
> > drivers/pci/pci-acpi.c | 11 +++++++
> > drivers/pci/pci.c | 64 ++++++++++++++++++++++++++++++---------------
> > drivers/pci/pci.h | 3 ++
> > include/acpi/acpi_bus.h | 1
> > include/linux/pm_wakeup.h | 16 -----------
> > 7 files changed, 84 insertions(+), 39 deletions(-)
> >
> > Index: linux-next/drivers/acpi/sleep/main.c
> > ===================================================================
> > --- linux-next.orig/drivers/acpi/sleep/main.c
> > +++ linux-next/drivers/acpi/sleep/main.c
> > @@ -468,6 +468,31 @@ int acpi_pm_device_sleep_state(struct de
> > *d_min_p = d_min;
> > return d_max;
> > }
> > +
> > +/**
> > + * acpi_pm_device_sleep_wake - enable or disable the system wake-up
> > + * capability of given device
> > + * @dev: device to handle
> > + * @enable: 'true' - enable, 'false' - disable the wake-up capability
> > + */
> > +int acpi_pm_device_sleep_wake(struct device *dev, bool enable)
> > +{
> > + acpi_handle handle;
> > + struct acpi_device *adev;
> > +
> > + if (!device_may_wakeup(dev))
> > + return -EINVAL;
> > +
> > + handle = DEVICE_ACPI_HANDLE(dev);
> > + if (!handle || ACPI_FAILURE(acpi_bus_get_device(handle, &adev))) {
> > + printk(KERN_DEBUG "ACPI handle has no context!\n");
> > + return -ENODEV;
> > + }
> > +
> > + return enable ?
> > + acpi_enable_wakeup_device_power(adev, acpi_target_sleep_state) :
> > + acpi_disable_wakeup_device_power(adev);
> > +}
> > #endif
> >
> > static void acpi_power_off_prepare(void)
> > Index: linux-next/drivers/pci/pci-acpi.c
> > ===================================================================
> > --- linux-next.orig/drivers/pci/pci-acpi.c
> > +++ linux-next/drivers/pci/pci-acpi.c
> > @@ -299,10 +299,21 @@ static int acpi_pci_set_power_state(stru
> > return error;
> > }
> >
> > +static int acpi_pci_sleep_wake(struct pci_dev *dev, bool enable)
> > +{
> > + int error = acpi_pm_device_sleep_wake(&dev->dev, enable);
> > +
> > + if (!error)
> > + printk(KERN_INFO "PCI: Wake-up capability of %s %s by ACPI\n",
> > + pci_name(dev), enable ? "enabled" : "disabled");
> > + return error;
> > +}
> > +
> > 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,
> > + .sleep_wake = acpi_pci_sleep_wake,
> > };
> >
> > /* ACPI bus type */
> > Index: linux-next/drivers/pci/pci.h
> > ===================================================================
> > --- linux-next.orig/drivers/pci/pci.h
> > +++ linux-next/drivers/pci/pci.h
> > @@ -17,6 +17,8 @@ extern void pci_cleanup_rom(struct pci_d
> > * platform; to be used during system-wide transitions from a
> > * sleeping state to the working state and vice versa
> > *
> > + * @sleep_wake - enables/disables the system wake up capability of given device
> > + *
> > * If given platform is generally capable of power managing PCI devices, all of
> > * these callbacks are mandatory.
> > */
> > @@ -24,6 +26,7 @@ struct pci_platform_pm_ops {
> > bool (*is_manageable)(struct pci_dev *dev);
> > int (*set_state)(struct pci_dev *dev, pci_power_t state);
> > pci_power_t (*choose_state)(struct pci_dev *dev);
> > + int (*sleep_wake)(struct pci_dev *dev, bool enable);
> > };
> >
> > extern int pci_set_platform_pm(struct pci_platform_pm_ops *ops);
> > Index: linux-next/include/acpi/acpi_bus.h
> > ===================================================================
> > --- linux-next.orig/include/acpi/acpi_bus.h
> > +++ linux-next/include/acpi/acpi_bus.h
> > @@ -383,6 +383,7 @@ acpi_handle acpi_get_pci_rootbridge_hand
> >
> > #ifdef CONFIG_PM_SLEEP
> > int acpi_pm_device_sleep_state(struct device *, int *);
> > +int acpi_pm_device_sleep_wake(struct device *, bool);
> > #else /* !CONFIG_PM_SLEEP */
> > static inline int acpi_pm_device_sleep_state(struct device *d, int *p)
> > {
> > Index: linux-next/drivers/pci/pci.c
> > ===================================================================
> > --- linux-next.orig/drivers/pci/pci.c
> > +++ linux-next/drivers/pci/pci.c
> > @@ -380,7 +380,8 @@ static struct pci_platform_pm_ops *pci_p
> >
> > int pci_set_platform_pm(struct pci_platform_pm_ops *ops)
> > {
> > - if (!ops->is_manageable || !ops->set_state || !ops->choose_state)
> > + if (!ops->is_manageable || !ops->set_state || !ops->choose_state
> > + || !ops->sleep_wake)
> > return -EINVAL;
> > pci_platform_pm = ops;
> > return 0;
> > @@ -403,6 +404,12 @@ static inline pci_power_t platform_pci_c
> > pci_platform_pm->choose_state(dev) : PCI_POWER_ERROR;
> > }
> >
> > +static inline int platform_pci_sleep_wake(struct pci_dev *dev, bool enable)
> > +{
> > + return pci_platform_pm ?
> > + pci_platform_pm->sleep_wake(dev, enable) : -ENODEV;
> > +}
> > +
> > /**
> > * pci_set_power_state - Set the power state of a PCI device
> > * @dev: PCI device to be suspended
> > @@ -1018,24 +1025,27 @@ int pci_set_pcie_reset_state(struct pci_
> > * supporting the standard PCI PME# signal may require such platform hooks;
> > * they always update bits in config space to allow PME# generation.
> > *
> > - * -EIO is returned if the device can't ever be a wakeup event source.
> > + * -EIO is returned if the device can't be a wakeup event source.
> > * -EINVAL is returned if the device can't generate wakeup events from
> > * the specified PCI state. Returns zero if the operation is successful.
> > */
> > int pci_enable_wake(struct pci_dev *dev, pci_power_t state, int enable)
> > {
> > int pm;
> > - int status;
> > - u16 value;
> > + u16 value = 0;
> > + bool platform_done = false;
> >
> > - /* Note that drivers should verify device_may_wakeup(&dev->dev)
> > - * before calling this function. Platform code should report
> > - * errors when drivers try to enable wakeup on devices that
> > - * can't issue wakeups, or on which wakeups were disabled by
> > - * userspace updating the /sys/devices.../power/wakeup file.
> > - */
> > -
> > - status = call_platform_enable_wakeup(&dev->dev, enable);
> > + if (enable && platform_pci_power_manageable(dev)) {
> > + /* Allow the platform to handle the device */
> > + int err = platform_pci_sleep_wake(dev, true);
> > + if (!err)
> > + /*
> > + * The platform claims to have enabled the device's
> > + * system wake-up capability as requested, but we are
> > + * going to enable PME# anyway.
> > + */
> > + platform_done = true;
> > + }
> >
> > /* find PCI PM capability in list */
> > pm = pci_find_capability(dev, PCI_CAP_ID_PM);
> > @@ -1044,23 +1054,27 @@ int pci_enable_wake(struct pci_dev *dev,
> > * disable wake events, it's a NOP. Otherwise fail unless the
> > * platform hooks handled this legacy device already.
> > */
> > - if (!pm)
> > - return enable ? status : 0;
> > + if (!pm) {
> > + if (enable)
> > + return platform_done ? 0 : -EIO;
> > + else
> > + goto Platform_disable;
> > + }
> >
> > /* Check device's ability to generate PME# */
> > - pci_read_config_word(dev,pm+PCI_PM_PMC,&value);
> > + pci_read_config_word(dev, pm + PCI_PM_PMC, &value);
> >
> > value &= PCI_PM_CAP_PME_MASK;
> > value >>= ffs(PCI_PM_CAP_PME_MASK) - 1; /* First bit of mask */
> >
> > /* Check if it can generate PME# from requested state. */
> > if (!value || !(value & (1 << state))) {
> > - /* if it can't, revert what the platform hook changed,
> > - * always reporting the base "EINVAL, can't PME#" error
> > - */
> > - if (enable)
> > - call_platform_enable_wakeup(&dev->dev, 0);
> > - return enable ? -EINVAL : 0;
> > + if (enable) {
> > + return platform_done ? 0 : -EINVAL;
> > + } else {
> > + value = 0;
> > + goto Platform_disable;
> > + }
> > }
> >
> > pci_read_config_word(dev, pm + PCI_PM_CTRL, &value);
> > @@ -1073,6 +1087,14 @@ int pci_enable_wake(struct pci_dev *dev,
> >
> > pci_write_config_word(dev, pm + PCI_PM_CTRL, value);
> >
> > + Platform_disable:
> > + if (!enable && platform_pci_power_manageable(dev)) {
> > + /* Allow the platform to finalize the operation */
> > + int err = platform_pci_sleep_wake(dev, false);
> > + if (err && !value)
> > + return -EIO;
> > + }
> > +
> > return 0;
> > }
> >
> > Index: linux-next/include/linux/pm_wakeup.h
> > ===================================================================
> > --- linux-next.orig/include/linux/pm_wakeup.h
> > +++ linux-next/include/linux/pm_wakeup.h
> > @@ -47,21 +47,7 @@ static inline void device_set_wakeup_ena
> >
> > static inline int device_may_wakeup(struct device *dev)
> > {
> > - return dev->power.can_wakeup & dev->power.should_wakeup;
> > -}
> > -
> > -/*
> > - * Platform hook to activate device wakeup capability, if that's not already
> > - * handled by enable_irq_wake() etc.
> > - * Returns zero on success, else negative errno
> > - */
> > -extern int (*platform_enable_wakeup)(struct device *dev, int is_on);
> > -
> > -static inline int call_platform_enable_wakeup(struct device *dev, int is_on)
> > -{
> > - if (platform_enable_wakeup)
> > - return (*platform_enable_wakeup)(dev, is_on);
> > - return 0;
> > + return dev->power.can_wakeup && dev->power.should_wakeup;
> > }
> >
> > #else /* !CONFIG_PM */
> > Index: linux-next/drivers/base/power/sysfs.c
> > ===================================================================
> > --- linux-next.orig/drivers/base/power/sysfs.c
> > +++ linux-next/drivers/base/power/sysfs.c
> > @@ -6,9 +6,6 @@
> > #include <linux/string.h>
> > #include "power.h"
> >
> > -int (*platform_enable_wakeup)(struct device *dev, int is_on);
> > -
> > -
> > /*
> > * wakeup - Report/change current wakeup option for device
> > *
> >
> > --
> > 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
>
> --
> 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
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [RFC][PATCH 6/9] PCI ACPI: Introduce acpi_pm_device_sleep_wake function
2008-06-25 10:29 ` Zhao Yakui
@ 2008-06-25 18:57 ` Rafael J. Wysocki
2008-06-25 21:31 ` Rafael J. Wysocki
0 siblings, 1 reply; 25+ messages in thread
From: Rafael J. Wysocki @ 2008-06-25 18:57 UTC (permalink / raw)
To: Zhao Yakui
Cc: Zhang Rui, ACPI Devel Maling List, Alan Stern, Jesse Barnes,
Len Brown, pm list
On Wednesday, 25 of June 2008, Zhao Yakui wrote:
> On Wed, 2008-06-25 at 16:11 +0800, Zhang Rui wrote:
> > Hi, Rafael,
> >
> > On Fri, 2008-06-20 at 01:51 +0200, Rafael J. Wysocki wrote:
> > > From: Rafael J. Wysocki <rjw@sisk.pl>
> > >
> > > PCI ACPI: Introduce acpi_pm_device_sleep_wake function
> > >
> > > Introduce function acpi_pm_device_sleep_wake() for enabling and
> > > disabling the system wake-up capability of devices that are power
> > > manageable by ACPI.
> > >
> > > Introduce callback .sleep_wake() in struct pci_platform_pm_ops and
> > > for the ACPI PCI 'driver' make it use acpi_pm_device_sleep_wake().
> >
> > acpi_pm_device_sleep_wake only enable/disable the power of wakeup
> > device, right?
> > If it doesn't set the acpi_device->wakeup.state.enabled, the wake up GPE
> > will not be enabled, which means the pci devices which have invoked
> > platform_pci_sleep_wake(dev, true) can not wake up the system as
> > excepted.
> > so this patch won't work. Is this what you want? or do I miss something?
> >From the patch it seems that acpi_dev->wakeup.state.enable is not set in
> >the function of acpi_pm_device_sleep_wake. If it is not set, it means
> >that the corresponding GPE won't be enabled. The device still can't wake
> >up the sleeping system.
This is a good point, because I forgot about the GPE.
However, it's not my intention to set acpi_device->wakeup.state.enabled in
acpi_pm_device_sleep_wake(). Instead, I'd like to use the
/sys/devices/.../power/wakeup interface and more precisely the
dev->power.should_wakeup flag that is supposed to have been set/unset as
needed _before_ acpi_pm_device_sleep_wake() is called.
Still, I'll need to make the code in drivers/acpi/sleep/wakeup.c enable/disable
the wake-up GPEs for devices that have both dev->power.can_wakeup and
dev->power.should_wakeup set, as though these devices had
acpi_device->wakeup.state.enabled set.
> > But what is annoying me is that,
> > if acpi_device->wakeup.state.enabled is set in
> > acpi_pm_device_sleep_wake, this may bring some other trouble.
> > A lot of pci devices will call platform_pci_sleep_wake(dev, true) by
> > default, they won't wake up the system before because the wake up GPEs
> > are disabled, but they will do if this patch is applied.
> > So some of the users may got the "resume immediately after sleep"
> > problem, that's a regression, isn't it?
That won't happen as long as the devices' dev->power.should_wakeup flags are
not set.
Note that in the most recent series of patches these flags are unset by default
for devices that cannot generate PME#, but there are PCI devices that can
generate both PME# and the ACPI-enabled wake-up events and those devices will
have dev->power.should_wakeup set by default. Still, if that may cause any
problems, we can unset dev->power.should_wakeup for all devices during
initialization and let the user space set it as desired.
> What Rui said is right. In current kernel when the system enters the
> sleeping state, the pci_enable_wake(dev,state, 1) will be called for the
> PCI device. If acpi_dev->wakeup.state.enabled is set in
> acpi_pm_device_sleep_wake , it means that the wakeup GPE will be
> enabled. Maybe some systems will be resumed immediately after sleeping.
> this is not what we expected.
However, pci_enable_wake() calls device_may_wakeup() and immediately aborts if
that returns 'false', so the user space can control its behavior using the
/sys/devices/.../power/wakeup interface.
> >
> > we have discussed this a couple of times on the list, but we have not
> > got a solution yet.
> > IMO, both Dave's and your patches are good, and we can be a little
> > aggressive to merge them, and fix the drivers once we get any
> > regressions ASAP.
> As the acpi_dev->wakeup.state.enabled is not touched in this patch, it
> will be OK to merge them.
>
> If acpi_dev->wakeup.state.enable is required to be set/unset in
> acpi_pm_device_sleep_wake, maybe Rui's suggestion is good. Unless some
> device is expected to wake up the sleeping system, we will enable the
> corresponding GPE. Otherwise it will be assumed that the device needn't
> wake up the sleeping system and unnecessary to enable the device power
> and the corresponding GPE.
> > i.e. either change pci_enable_wake(dev, 1) to pci_enable_wake(dev, 0) in
> > the drivers suspend method, or invoke device_set_wakeup_enable(dev, 0)
> > to disable the wakeup ability after the call to device_init_wakeup(dev,
> > 1).
> >
> > any ideas?
Please see above. :-)
Thanks,
Rafael
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [RFC][PATCH 6/9] PCI ACPI: Introduce acpi_pm_device_sleep_wake function
2008-06-25 18:57 ` Rafael J. Wysocki
@ 2008-06-25 21:31 ` Rafael J. Wysocki
2008-06-26 14:12 ` Alan Stern
0 siblings, 1 reply; 25+ messages in thread
From: Rafael J. Wysocki @ 2008-06-25 21:31 UTC (permalink / raw)
To: Zhao Yakui
Cc: Zhang Rui, ACPI Devel Maling List, Alan Stern, Jesse Barnes,
Len Brown, pm list
On Wednesday, 25 of June 2008, Rafael J. Wysocki wrote:
> On Wednesday, 25 of June 2008, Zhao Yakui wrote:
> > On Wed, 2008-06-25 at 16:11 +0800, Zhang Rui wrote:
> > > Hi, Rafael,
> > >
> > > On Fri, 2008-06-20 at 01:51 +0200, Rafael J. Wysocki wrote:
> > > > From: Rafael J. Wysocki <rjw@sisk.pl>
> > > >
> > > > PCI ACPI: Introduce acpi_pm_device_sleep_wake function
> > > >
> > > > Introduce function acpi_pm_device_sleep_wake() for enabling and
> > > > disabling the system wake-up capability of devices that are power
> > > > manageable by ACPI.
> > > >
> > > > Introduce callback .sleep_wake() in struct pci_platform_pm_ops and
> > > > for the ACPI PCI 'driver' make it use acpi_pm_device_sleep_wake().
> > >
> > > acpi_pm_device_sleep_wake only enable/disable the power of wakeup
> > > device, right?
> > > If it doesn't set the acpi_device->wakeup.state.enabled, the wake up GPE
> > > will not be enabled, which means the pci devices which have invoked
> > > platform_pci_sleep_wake(dev, true) can not wake up the system as
> > > excepted.
> > > so this patch won't work. Is this what you want? or do I miss something?
> > >From the patch it seems that acpi_dev->wakeup.state.enable is not set in
> > >the function of acpi_pm_device_sleep_wake. If it is not set, it means
> > >that the corresponding GPE won't be enabled. The device still can't wake
> > >up the sleeping system.
>
> This is a good point, because I forgot about the GPE.
>
> However, it's not my intention to set acpi_device->wakeup.state.enabled in
> acpi_pm_device_sleep_wake(). Instead, I'd like to use the
> /sys/devices/.../power/wakeup interface and more precisely the
> dev->power.should_wakeup flag that is supposed to have been set/unset as
> needed _before_ acpi_pm_device_sleep_wake() is called.
>
> Still, I'll need to make the code in drivers/acpi/sleep/wakeup.c enable/disable
> the wake-up GPEs for devices that have both dev->power.can_wakeup and
> dev->power.should_wakeup set, as though these devices had
> acpi_device->wakeup.state.enabled set.
>
> > > But what is annoying me is that,
> > > if acpi_device->wakeup.state.enabled is set in
> > > acpi_pm_device_sleep_wake, this may bring some other trouble.
> > > A lot of pci devices will call platform_pci_sleep_wake(dev, true) by
> > > default, they won't wake up the system before because the wake up GPEs
> > > are disabled, but they will do if this patch is applied.
> > > So some of the users may got the "resume immediately after sleep"
> > > problem, that's a regression, isn't it?
>
> That won't happen as long as the devices' dev->power.should_wakeup flags are
> not set.
>
> Note that in the most recent series of patches these flags are unset by default
> for devices that cannot generate PME#, but there are PCI devices that can
> generate both PME# and the ACPI-enabled wake-up events and those devices will
> have dev->power.should_wakeup set by default. Still, if that may cause any
> problems, we can unset dev->power.should_wakeup for all devices during
> initialization and let the user space set it as desired.
>
> > What Rui said is right. In current kernel when the system enters the
> > sleeping state, the pci_enable_wake(dev,state, 1) will be called for the
> > PCI device. If acpi_dev->wakeup.state.enabled is set in
> > acpi_pm_device_sleep_wake , it means that the wakeup GPE will be
> > enabled. Maybe some systems will be resumed immediately after sleeping.
> > this is not what we expected.
>
> However, pci_enable_wake() calls device_may_wakeup() and immediately aborts if
> that returns 'false', so the user space can control its behavior using the
> /sys/devices/.../power/wakeup interface.
>
> > >
> > > we have discussed this a couple of times on the list, but we have not
> > > got a solution yet.
> > > IMO, both Dave's and your patches are good, and we can be a little
> > > aggressive to merge them, and fix the drivers once we get any
> > > regressions ASAP.
> > As the acpi_dev->wakeup.state.enabled is not touched in this patch, it
> > will be OK to merge them.
> >
> > If acpi_dev->wakeup.state.enable is required to be set/unset in
> > acpi_pm_device_sleep_wake, maybe Rui's suggestion is good. Unless some
> > device is expected to wake up the sleeping system, we will enable the
> > corresponding GPE. Otherwise it will be assumed that the device needn't
> > wake up the sleeping system and unnecessary to enable the device power
> > and the corresponding GPE.
> > > i.e. either change pci_enable_wake(dev, 1) to pci_enable_wake(dev, 0) in
> > > the drivers suspend method, or invoke device_set_wakeup_enable(dev, 0)
> > > to disable the wakeup ability after the call to device_init_wakeup(dev,
> > > 1).
> > >
> > > any ideas?
>
> Please see above. :-)
Appended is a patch that contains the majority of what I'd like to do.
I thought it would be better to split that into multiple smaller patches, but
apparently not.
Tha patch is against the mainline, on top of the patches 01-05 from the series
at https://lists.linux-foundation.org/pipermail/linux-pm/2008-June/017839.html,
but the difference between this one and the linux-next counterpart is minimal.
Please have a look.
Thanks,
Rafael
---
From: Rafael J. Wysocki <rjw@sisk.pl>
PCI ACPI: Rework PCI handling of wake-up
* Introduce function acpi_pm_device_sleep_wake() for enabling and
disabling the system wake-up capability of devices that are power
manageable by ACPI.
* Introduce function acpi_bus_can_wakeup() allowing other (dependent)
subsystems to check if ACPI is able to enable the system wake-up
capability of given device.
* Introduce callback .sleep_wake() in struct pci_platform_pm_ops and
for the ACPI PCI 'driver' make it use acpi_pm_device_sleep_wake().
* Introduce callback .can_wakeup() in struct pci_platform_pm_ops and
for the ACPI 'driver' make it use acpi_bus_can_wakeup().
* Move the PME# handlig code out of pci_enable_wake() and split it
into two functions, pci_pme_capable() and pci_pme_active(),
allowing the caller to check if given device is capable of
generating PME# from given power state and to enable/disable the
device's PME# functionality, respectively.
* Modify pci_enable_wake() to use the new ACPI callbacks and the new
PME#-related functions.
* Drop the generic .platform_enable_wakeup() callback that is not
used any more.
* Introduce device_set_wakeup_capable() that will set the
power.can_wakeup flag of given device.
* Rework PCI device PM initialization so that, if given device is
capable of generating wake-up events, either natively through the
PME# mechanism, or with the help of the platform, its
power.can_wakeup flag is set and its power.should_wakeup flag is
unset as appropriate.
* Make ACPI set the power.can_wakeup flag for devices found to be
wake-up capable by it.
* Make the ACPI wake-up code enable/disable GPEs for devices that
have both power.can_wakeup flag and power.should_wakeup flags
set.
Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
---
drivers/acpi/bus.c | 11 +++
drivers/acpi/glue.c | 2
drivers/acpi/sleep/main.c | 25 +++++++
drivers/acpi/sleep/wakeup.c | 15 +++-
drivers/base/power/sysfs.c | 3
drivers/pci/pci-acpi.c | 19 +++++
drivers/pci/pci.c | 155 +++++++++++++++++++++++++++++++-------------
drivers/pci/pci.h | 8 ++
drivers/pci/probe.c | 3
include/acpi/acpi_bus.h | 2
include/linux/pm_wakeup.h | 26 +------
11 files changed, 201 insertions(+), 68 deletions(-)
Index: linux-2.6/drivers/acpi/sleep/main.c
===================================================================
--- linux-2.6.orig/drivers/acpi/sleep/main.c
+++ linux-2.6/drivers/acpi/sleep/main.c
@@ -482,6 +482,31 @@ int acpi_pm_device_sleep_state(struct de
*d_min_p = d_min;
return d_max;
}
+
+/**
+ * acpi_pm_device_sleep_wake - enable or disable the system wake-up
+ * capability of given device
+ * @dev: device to handle
+ * @enable: 'true' - enable, 'false' - disable the wake-up capability
+ */
+int acpi_pm_device_sleep_wake(struct device *dev, bool enable)
+{
+ acpi_handle handle;
+ struct acpi_device *adev;
+
+ if (!device_may_wakeup(dev))
+ return -EINVAL;
+
+ handle = DEVICE_ACPI_HANDLE(dev);
+ if (!handle || ACPI_FAILURE(acpi_bus_get_device(handle, &adev))) {
+ printk(KERN_DEBUG "ACPI handle has no context!\n");
+ return -ENODEV;
+ }
+
+ return enable ?
+ acpi_enable_wakeup_device_power(adev, acpi_target_sleep_state) :
+ acpi_disable_wakeup_device_power(adev);
+}
#endif
static void acpi_power_off_prepare(void)
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
@@ -353,10 +353,29 @@ static int acpi_pci_set_power_state(stru
return error;
}
+static bool acpi_pci_can_wakeup(struct pci_dev *dev)
+{
+ acpi_handle handle = DEVICE_ACPI_HANDLE(&dev->dev);
+
+ return handle ? acpi_bus_can_wakeup(handle) : false;
+}
+
+static int acpi_pci_sleep_wake(struct pci_dev *dev, bool enable)
+{
+ int error = acpi_pm_device_sleep_wake(&dev->dev, enable);
+
+ if (!error)
+ printk(KERN_INFO "PCI: Wake-up capability of %s %s by ACPI\n",
+ pci_name(dev), enable ? "enabled" : "disabled");
+ return error;
+}
+
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,
};
/* ACPI bus type */
Index: linux-2.6/drivers/pci/pci.h
===================================================================
--- linux-2.6.orig/drivers/pci/pci.h
+++ linux-2.6/drivers/pci/pci.h
@@ -17,6 +17,11 @@ extern void pci_cleanup_rom(struct pci_d
* 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
+ *
+ * @sleep_wake - enables/disables the system wake up capability of given device
+ *
* If given platform is generally capable of power managing PCI devices, all of
* these callbacks are mandatory.
*/
@@ -24,9 +29,12 @@ struct pci_platform_pm_ops {
bool (*is_manageable)(struct pci_dev *dev);
int (*set_state)(struct pci_dev *dev, pci_power_t state);
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);
};
extern int pci_set_platform_pm(struct pci_platform_pm_ops *ops);
+extern void pci_pm_init(struct pci_dev *dev);
extern int pci_user_read_config_byte(struct pci_dev *dev, int where, u8 *val);
extern int pci_user_read_config_word(struct pci_dev *dev, int where, u16 *val);
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
@@ -337,6 +337,7 @@ int acpi_bus_get_status(struct acpi_devi
int acpi_bus_get_power(acpi_handle handle, int *state);
int acpi_bus_set_power(acpi_handle handle, int state);
bool acpi_bus_power_manageable(acpi_handle handle);
+bool acpi_bus_can_wakeup(acpi_handle handle);
#ifdef CONFIG_ACPI_PROC_EVENT
int acpi_bus_generate_proc_event(struct acpi_device *device, u8 type, int data);
int acpi_bus_generate_proc_event4(const char *class, const char *bid, u8 type, int data);
@@ -379,6 +380,7 @@ acpi_handle acpi_get_pci_rootbridge_hand
#ifdef CONFIG_PM_SLEEP
int acpi_pm_device_sleep_state(struct device *, int *);
+int acpi_pm_device_sleep_wake(struct device *, bool);
#else /* !CONFIG_PM_SLEEP */
static inline int acpi_pm_device_sleep_state(struct device *d, int *p)
{
Index: linux-2.6/drivers/pci/pci.c
===================================================================
--- linux-2.6.orig/drivers/pci/pci.c
+++ linux-2.6/drivers/pci/pci.c
@@ -382,7 +382,8 @@ static struct pci_platform_pm_ops *pci_p
int pci_set_platform_pm(struct pci_platform_pm_ops *ops)
{
- if (!ops->is_manageable || !ops->set_state || !ops->choose_state)
+ if (!ops->is_manageable || !ops->set_state || !ops->choose_state
+ || !ops->sleep_wake || !ops->can_wakeup)
return -EINVAL;
pci_platform_pm = ops;
return 0;
@@ -405,6 +406,17 @@ static inline pci_power_t platform_pci_c
pci_platform_pm->choose_state(dev) : PCI_POWER_ERROR;
}
+static inline bool platform_pci_can_wakeup(struct pci_dev *dev)
+{
+ return pci_platform_pm ? pci_platform_pm->can_wakeup(dev) : false;
+}
+
+static inline int platform_pci_sleep_wake(struct pci_dev *dev, bool enable)
+{
+ return pci_platform_pm ?
+ pci_platform_pm->sleep_wake(dev, enable) : -ENODEV;
+}
+
/**
* pci_raw_set_power_state - Use PCI PM registers to set the power state of
* given PCI device
@@ -1039,6 +1051,53 @@ int pci_set_pcie_reset_state(struct pci_
}
/**
+ * pci_pme_capable - check the capability of PCI device to generate PME#
+ * @dev: PCI device to handle.
+ * @pm: PCI PM capability offset of the device.
+ * @state: PCI state from which device will issue PME#.
+ */
+static bool pci_pme_capable(struct pci_dev *dev, int pm, pci_power_t state)
+{
+ u16 pmc;
+
+ if (!pm)
+ return false;
+
+ /* Check device's ability to generate PME# from given state */
+ pci_read_config_word(dev, pm + PCI_PM_PMC, &pmc);
+
+ pmc &= PCI_PM_CAP_PME_MASK;
+ pmc >>= ffs(PCI_PM_CAP_PME_MASK) - 1; /* First bit of mask */
+
+ return !!(pmc & (1 << state));
+}
+
+/**
+ * pci_pme_active - enable or disable PCI device's PME# function
+ * @dev: PCI device to handle.
+ * @pm: PCI PM capability offset of the device.
+ * @enable: 'true' to enable PME# generation; 'false' to disable it.
+ *
+ * The caller must verify that the device is capable of generating PME# before
+ * calling this function with @enable equal to 'true'.
+ */
+static void pci_pme_active(struct pci_dev *dev, int pm, bool enable)
+{
+ u16 pmcsr;
+
+ if (!pm)
+ return;
+
+ pci_read_config_word(dev, pm + PCI_PM_CTRL, &pmcsr);
+ /* Clear PME_Status by writing 1 to it and enable PME# */
+ pmcsr |= PCI_PM_CTRL_PME_STATUS | PCI_PM_CTRL_PME_ENABLE;
+ if (!enable)
+ pmcsr &= ~PCI_PM_CTRL_PME_ENABLE;
+
+ pci_write_config_word(dev, pm + PCI_PM_CTRL, pmcsr);
+}
+
+/**
* pci_enable_wake - enable PCI device as wakeup event source
* @dev: PCI device affected
* @state: PCI state from which device will issue wakeup events
@@ -1053,62 +1112,72 @@ int pci_set_pcie_reset_state(struct pci_
* supporting the standard PCI PME# signal may require such platform hooks;
* they always update bits in config space to allow PME# generation.
*
- * -EIO is returned if the device can't ever be a wakeup event source.
- * -EINVAL is returned if the device can't generate wakeup events from
- * the specified PCI state. Returns zero if the operation is successful.
+ * RETURN VALUE:
+ * 0 is returned on success
+ * -EINVAL is returned if device is not supposed to wake up the system
+ * Error code depending on the platform is returned if both the platform and the
+ * native mechanism fail to enable the generation of wake-up events
*/
int pci_enable_wake(struct pci_dev *dev, pci_power_t state, int enable)
{
int pm;
- int status;
- u16 value;
+ int error = 0;
- /* Note that drivers should verify device_may_wakeup(&dev->dev)
- * before calling this function. Platform code should report
- * errors when drivers try to enable wakeup on devices that
- * can't issue wakeups, or on which wakeups were disabled by
- * userspace updating the /sys/devices.../power/wakeup file.
- */
+ if (!device_may_wakeup(&dev->dev))
+ return -EINVAL;
- status = call_platform_enable_wakeup(&dev->dev, enable);
+ if (enable && platform_pci_can_wakeup(dev))
+ error = platform_pci_sleep_wake(dev, true);
- /* find PCI PM capability in list */
- pm = pci_find_capability(dev, PCI_CAP_ID_PM);
-
- /* If device doesn't support PM Capabilities, but caller wants to
- * disable wake events, it's a NOP. Otherwise fail unless the
- * platform hooks handled this legacy device already.
+ /*
+ * We are going to enable/disable the generation of PME# even if the
+ * platform claims to have handled the device as requested.
*/
- if (!pm)
- return enable ? status : 0;
-
- /* Check device's ability to generate PME# */
- pci_read_config_word(dev,pm+PCI_PM_PMC,&value);
-
- value &= PCI_PM_CAP_PME_MASK;
- value >>= ffs(PCI_PM_CAP_PME_MASK) - 1; /* First bit of mask */
-
- /* Check if it can generate PME# from requested state. */
- if (!value || !(value & (1 << state))) {
- /* if it can't, revert what the platform hook changed,
- * always reporting the base "EINVAL, can't PME#" error
- */
+ pm = pci_find_capability(dev, PCI_CAP_ID_PM);
+ if (!enable || pci_pme_capable(dev, pm, state)) {
+ pci_pme_active(dev, pm, enable);
if (enable)
- call_platform_enable_wakeup(&dev->dev, 0);
- return enable ? -EINVAL : 0;
+ return 0;
}
- pci_read_config_word(dev, pm + PCI_PM_CTRL, &value);
-
- /* Clear PME_Status by writing 1 to it and enable PME# */
- value |= PCI_PM_CTRL_PME_STATUS | PCI_PM_CTRL_PME_ENABLE;
+ if (!enable && platform_pci_can_wakeup(dev))
+ error = platform_pci_sleep_wake(dev, false);
- if (!enable)
- value &= ~PCI_PM_CTRL_PME_ENABLE;
+ return error;
+}
- pci_write_config_word(dev, pm + PCI_PM_CTRL, value);
+/**
+ * pci_pm_init - Initialize PM functions of given PCI device
+ * @dev: PCI device to handle.
+ */
+void pci_pm_init(struct pci_dev *dev)
+{
+ int pm;
+ u16 pmc;
- return 0;
+ /* find PCI PM capability in list */
+ pm = pci_find_capability(dev, PCI_CAP_ID_PM);
+ if (!pm)
+ return;
+ /* Check device's ability to generate PME# */
+ pci_read_config_word(dev, pm + PCI_PM_PMC, &pmc);
+ if (pmc & PCI_PM_CAP_PME_MASK) {
+ printk(KERN_INFO "PCI: Device %s supports PME# from"
+ "%s%s%s%s%s\n", pci_name(dev),
+ (pmc & PCI_PM_CAP_PME_D0) ? " D0" : "",
+ (pmc & PCI_PM_CAP_PME_D1) ? " D1" : "",
+ (pmc & PCI_PM_CAP_PME_D2) ? " D2" : "",
+ (pmc & PCI_PM_CAP_PME_D3) ? " D3hot" : "",
+ (pmc & PCI_PM_CAP_PME_D3cold) ? " D3cold" : "");
+ /*
+ * Make device's PM flags reflect the wake-up capability, but
+ * let the user space enable it to wake up the system as needed.
+ */
+ device_set_wakeup_capable(&dev->dev, true);
+ device_set_wakeup_enable(&dev->dev, false);
+ /* Disable the PME# generation functionality */
+ pci_pme_active(dev, pm, false);
+ }
}
int
Index: linux-2.6/include/linux/pm_wakeup.h
===================================================================
--- linux-2.6.orig/include/linux/pm_wakeup.h
+++ linux-2.6/include/linux/pm_wakeup.h
@@ -35,6 +35,11 @@ static inline void device_init_wakeup(st
dev->power.can_wakeup = dev->power.should_wakeup = !!val;
}
+static inline void device_set_wakeup_capable(struct device *dev, int val)
+{
+ dev->power.can_wakeup = !!val;
+}
+
static inline int device_can_wakeup(struct device *dev)
{
return dev->power.can_wakeup;
@@ -47,21 +52,7 @@ static inline void device_set_wakeup_ena
static inline int device_may_wakeup(struct device *dev)
{
- return dev->power.can_wakeup & dev->power.should_wakeup;
-}
-
-/*
- * Platform hook to activate device wakeup capability, if that's not already
- * handled by enable_irq_wake() etc.
- * Returns zero on success, else negative errno
- */
-extern int (*platform_enable_wakeup)(struct device *dev, int is_on);
-
-static inline int call_platform_enable_wakeup(struct device *dev, int is_on)
-{
- if (platform_enable_wakeup)
- return (*platform_enable_wakeup)(dev, is_on);
- return 0;
+ return dev->power.can_wakeup && dev->power.should_wakeup;
}
#else /* !CONFIG_PM */
@@ -80,11 +71,6 @@ static inline int device_can_wakeup(stru
#define device_set_wakeup_enable(dev, val) do {} while (0)
#define device_may_wakeup(dev) 0
-static inline int call_platform_enable_wakeup(struct device *dev, int is_on)
-{
- return 0;
-}
-
#endif /* !CONFIG_PM */
#endif /* _LINUX_PM_WAKEUP_H */
Index: linux-2.6/drivers/base/power/sysfs.c
===================================================================
--- linux-2.6.orig/drivers/base/power/sysfs.c
+++ linux-2.6/drivers/base/power/sysfs.c
@@ -6,9 +6,6 @@
#include <linux/string.h>
#include "power.h"
-int (*platform_enable_wakeup)(struct device *dev, int is_on);
-
-
/*
* wakeup - Report/change current wakeup option for device
*
Index: linux-2.6/drivers/acpi/bus.c
===================================================================
--- linux-2.6.orig/drivers/acpi/bus.c
+++ linux-2.6/drivers/acpi/bus.c
@@ -306,6 +306,17 @@ bool acpi_bus_power_manageable(acpi_hand
EXPORT_SYMBOL(acpi_bus_power_manageable);
+bool acpi_bus_can_wakeup(acpi_handle handle)
+{
+ struct acpi_device *device;
+ int result;
+
+ result = acpi_bus_get_device(handle, &device);
+ return result ? false : device->wakeup.flags.valid;
+}
+
+EXPORT_SYMBOL(acpi_bus_can_wakeup);
+
/* --------------------------------------------------------------------------
Event Management
-------------------------------------------------------------------------- */
Index: linux-2.6/drivers/pci/probe.c
===================================================================
--- linux-2.6.orig/drivers/pci/probe.c
+++ linux-2.6/drivers/pci/probe.c
@@ -984,6 +984,9 @@ void pci_device_add(struct pci_dev *dev,
/* Fix up broken headers */
pci_fixup_device(pci_fixup_header, dev);
+ /* Initialize power management of the device */
+ pci_pm_init(dev);
+
/*
* Add the device to our list of discovered devices
* and the bus list for fixup functions, etc.
Index: linux-2.6/drivers/acpi/glue.c
===================================================================
--- linux-2.6.orig/drivers/acpi/glue.c
+++ linux-2.6/drivers/acpi/glue.c
@@ -166,6 +166,8 @@ static int acpi_bind_one(struct device *
"firmware_node");
ret = sysfs_create_link(&acpi_dev->dev.kobj, &dev->kobj,
"physical_node");
+ if (acpi_dev->wakeup.flags.valid)
+ device_set_wakeup_capable(dev, true);
}
return 0;
Index: linux-2.6/drivers/acpi/sleep/wakeup.c
===================================================================
--- linux-2.6.orig/drivers/acpi/sleep/wakeup.c
+++ linux-2.6/drivers/acpi/sleep/wakeup.c
@@ -66,12 +66,18 @@ void acpi_enable_wakeup_device(u8 sleep_
list_for_each_safe(node, next, &acpi_wakeup_device_list) {
struct acpi_device *dev =
container_of(node, struct acpi_device, wakeup_list);
+ struct device *ldev;
+ bool wakeup_enabled;
+
if (!dev->wakeup.flags.valid)
continue;
+ ldev = acpi_get_physical_device(dev->handle);
+ wakeup_enabled = dev->wakeup.state.enabled ||
+ (ldev ? device_may_wakeup(ldev) : false);
/* If users want to disable run-wake GPE,
* we only disable it for wake and leave it for runtime
*/
- if (!dev->wakeup.state.enabled ||
+ if (!wakeup_enabled ||
sleep_state > (u32) dev->wakeup.sleep_state) {
if (dev->wakeup.flags.run_wake) {
spin_unlock(&acpi_device_lock);
@@ -107,10 +113,15 @@ void acpi_disable_wakeup_device(u8 sleep
list_for_each_safe(node, next, &acpi_wakeup_device_list) {
struct acpi_device *dev =
container_of(node, struct acpi_device, wakeup_list);
+ struct device *ldev;
+ bool wakeup_enabled;
if (!dev->wakeup.flags.valid)
continue;
- if (!dev->wakeup.state.enabled ||
+ ldev = acpi_get_physical_device(dev->handle);
+ wakeup_enabled = dev->wakeup.state.enabled ||
+ (ldev ? device_may_wakeup(ldev) : false);
+ if (!wakeup_enabled ||
sleep_state > (u32) dev->wakeup.sleep_state) {
if (dev->wakeup.flags.run_wake) {
spin_unlock(&acpi_device_lock);
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [RFC][PATCH 6/9] PCI ACPI: Introduce acpi_pm_device_sleep_wake function
2008-06-25 21:31 ` Rafael J. Wysocki
@ 2008-06-26 14:12 ` Alan Stern
2008-06-26 18:21 ` Rafael J. Wysocki
0 siblings, 1 reply; 25+ messages in thread
From: Alan Stern @ 2008-06-26 14:12 UTC (permalink / raw)
To: Rafael J. Wysocki
Cc: Zhao Yakui, Zhang Rui, ACPI Devel Maling List, Jesse Barnes,
Len Brown, pm list
On Wed, 25 Jun 2008, Rafael J. Wysocki wrote:
> Index: linux-2.6/drivers/pci/pci.c
> ===================================================================
> --- linux-2.6.orig/drivers/pci/pci.c
> +++ linux-2.6/drivers/pci/pci.c
> +/**
> + * pci_pme_active - enable or disable PCI device's PME# function
> + * @dev: PCI device to handle.
> + * @pm: PCI PM capability offset of the device.
> + * @enable: 'true' to enable PME# generation; 'false' to disable it.
> + *
> + * The caller must verify that the device is capable of generating PME# before
> + * calling this function with @enable equal to 'true'.
> + */
> +static void pci_pme_active(struct pci_dev *dev, int pm, bool enable)
> +{
> + u16 pmcsr;
> +
> + if (!pm)
> + return;
> +
> + pci_read_config_word(dev, pm + PCI_PM_CTRL, &pmcsr);
> + /* Clear PME_Status by writing 1 to it and enable PME# */
> + pmcsr |= PCI_PM_CTRL_PME_STATUS | PCI_PM_CTRL_PME_ENABLE;
> + if (!enable)
> + pmcsr &= ~PCI_PM_CTRL_PME_ENABLE;
> +
> + pci_write_config_word(dev, pm + PCI_PM_CTRL, pmcsr);
> +}
Question: Does the PME Status ever get cleared at any other times? For
instance, suppose a PCI device does use PME# to wake up the system.
Does its status get cleared?
Or suppose a PCI device is runtime-suspended and it generates a wakeup
request? This will turn on PME# at a time when the system isn't asleep
-- what happens then? Does it get lost somewhere in the bowels of
ACPI? Does the PCI core need new code to handle such things?
Alan Stern
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [RFC][PATCH 6/9] PCI ACPI: Introduce acpi_pm_device_sleep_wake function
2008-06-26 14:12 ` Alan Stern
@ 2008-06-26 18:21 ` Rafael J. Wysocki
2008-06-26 19:54 ` Alan Stern
0 siblings, 1 reply; 25+ messages in thread
From: Rafael J. Wysocki @ 2008-06-26 18:21 UTC (permalink / raw)
To: Alan Stern
Cc: Zhao Yakui, Zhang Rui, ACPI Devel Maling List, Jesse Barnes,
Len Brown, pm list
On Thursday, 26 of June 2008, Alan Stern wrote:
> On Wed, 25 Jun 2008, Rafael J. Wysocki wrote:
>
> > Index: linux-2.6/drivers/pci/pci.c
> > ===================================================================
> > --- linux-2.6.orig/drivers/pci/pci.c
> > +++ linux-2.6/drivers/pci/pci.c
>
> > +/**
> > + * pci_pme_active - enable or disable PCI device's PME# function
> > + * @dev: PCI device to handle.
> > + * @pm: PCI PM capability offset of the device.
> > + * @enable: 'true' to enable PME# generation; 'false' to disable it.
> > + *
> > + * The caller must verify that the device is capable of generating PME# before
> > + * calling this function with @enable equal to 'true'.
> > + */
> > +static void pci_pme_active(struct pci_dev *dev, int pm, bool enable)
> > +{
> > + u16 pmcsr;
> > +
> > + if (!pm)
> > + return;
> > +
> > + pci_read_config_word(dev, pm + PCI_PM_CTRL, &pmcsr);
> > + /* Clear PME_Status by writing 1 to it and enable PME# */
> > + pmcsr |= PCI_PM_CTRL_PME_STATUS | PCI_PM_CTRL_PME_ENABLE;
> > + if (!enable)
> > + pmcsr &= ~PCI_PM_CTRL_PME_ENABLE;
> > +
> > + pci_write_config_word(dev, pm + PCI_PM_CTRL, pmcsr);
> > +}
>
> Question: Does the PME Status ever get cleared at any other times?
Currently, PME# status is only cleared when executing pci_pme_active() (for
example via pci_enable_wake()).
> For instance, suppose a PCI device does use PME# to wake up the system.
> Does its status get cleared?
If the driver of the device executes pci_enable_wake(dev, PCI_D0, false) during
resume, then the PME# status will be cleared.
Do you think the core should clear PME# status automatically in that case?
> Or suppose a PCI device is runtime-suspended and it generates a wakeup
> request? This will turn on PME# at a time when the system isn't asleep
> -- what happens then? Does it get lost somewhere in the bowels of
> ACPI?
Currently, yes, AFAICS.
> Does the PCI core need new code to handle such things?
Well, I thought of adding such things like the clearing of PME# status to the
PCI bus type suspend/resume callbacks.
Apart from this, I think that to implement PCI runtime PM we'll need to add
some new code. First of all, we need to handle the case when some devices are
in low power states before suspend/hibernation. Second, we need to handle PME#
generated by devices being in low power states (or even in D0) at run time and
that will certainly require new code.
Thanks,
Rafael
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [RFC][PATCH 6/9] PCI ACPI: Introduce acpi_pm_device_sleep_wake function
2008-06-26 18:21 ` Rafael J. Wysocki
@ 2008-06-26 19:54 ` Alan Stern
2008-06-26 20:31 ` Rafael J. Wysocki
0 siblings, 1 reply; 25+ messages in thread
From: Alan Stern @ 2008-06-26 19:54 UTC (permalink / raw)
To: Rafael J. Wysocki
Cc: Zhao Yakui, Zhang Rui, ACPI Devel Maling List, Jesse Barnes,
Len Brown, pm list
On Thu, 26 Jun 2008, Rafael J. Wysocki wrote:
> > Question: Does the PME Status ever get cleared at any other times?
>
> Currently, PME# status is only cleared when executing pci_pme_active() (for
> example via pci_enable_wake()).
>
> > For instance, suppose a PCI device does use PME# to wake up the system.
> > Does its status get cleared?
>
> If the driver of the device executes pci_enable_wake(dev, PCI_D0, false) during
> resume, then the PME# status will be cleared.
Hmm. So if the resume routine doesn't get called for some reason then
most likely the status won't get cleared.
> Do you think the core should clear PME# status automatically in that case?
Yes.
> > Or suppose a PCI device is runtime-suspended and it generates a wakeup
> > request? This will turn on PME# at a time when the system isn't asleep
> > -- what happens then? Does it get lost somewhere in the bowels of
> > ACPI?
>
> Currently, yes, AFAICS.
Back around last November some initial patches were posted by Shaohua
Li, as part of Bugzilla #6892. You'd probably be interested to read
through the entire bug report. Maybe we should start working on it
again.
> > Does the PCI core need new code to handle such things?
>
> Well, I thought of adding such things like the clearing of PME# status to the
> PCI bus type suspend/resume callbacks.
>
> Apart from this, I think that to implement PCI runtime PM we'll need to add
> some new code. First of all, we need to handle the case when some devices are
> in low power states before suspend/hibernation. Second, we need to handle PME#
> generated by devices being in low power states (or even in D0) at run time and
> that will certainly require new code.
Yes it will. We don't have any runtime PM infrastructure for PCI yet.
Although to tell the truth, I'm not sure how important it is. How much
power can we save by doing runtime suspends of PCI devices? With USB
controllers, for example, much or most of the savings comes from
disabling the USB portions of the device, which we _are_ doing. It's
not clear how much more would be gained by shutting down the PCI
portions as well.
Alan Stern
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [RFC][PATCH 6/9] PCI ACPI: Introduce acpi_pm_device_sleep_wake function
2008-06-26 19:54 ` Alan Stern
@ 2008-06-26 20:31 ` Rafael J. Wysocki
2008-06-26 20:38 ` Alan Stern
0 siblings, 1 reply; 25+ messages in thread
From: Rafael J. Wysocki @ 2008-06-26 20:31 UTC (permalink / raw)
To: Alan Stern
Cc: Zhao Yakui, Zhang Rui, ACPI Devel Maling List, Jesse Barnes,
Len Brown, pm list
On Thursday, 26 of June 2008, Alan Stern wrote:
> On Thu, 26 Jun 2008, Rafael J. Wysocki wrote:
>
> > > Question: Does the PME Status ever get cleared at any other times?
> >
> > Currently, PME# status is only cleared when executing pci_pme_active() (for
> > example via pci_enable_wake()).
> >
> > > For instance, suppose a PCI device does use PME# to wake up the system.
> > > Does its status get cleared?
> >
> > If the driver of the device executes pci_enable_wake(dev, PCI_D0, false) during
> > resume, then the PME# status will be cleared.
>
> Hmm. So if the resume routine doesn't get called for some reason then
> most likely the status won't get cleared.
That's correct.
> > Do you think the core should clear PME# status automatically in that case?
>
> Yes.
Okay, but that will require us to add the clearing of PME# to the PCI
resume/restore callbacks. Perfectly doable, but I'd prefer to do that on top
of the $subject patch.
> > > Or suppose a PCI device is runtime-suspended and it generates a wakeup
> > > request? This will turn on PME# at a time when the system isn't asleep
> > > -- what happens then? Does it get lost somewhere in the bowels of
> > > ACPI?
> >
> > Currently, yes, AFAICS.
>
> Back around last November some initial patches were posted by Shaohua
> Li, as part of Bugzilla #6892.
OK, I will.
> You'd probably be interested to read through the entire bug report. Maybe we
> should start working on it again.
I have nothing against that, but quite frankly I wouldn't like it to become a
road block for the present patch series. Thus, I'd like to add new patches on
top of it rather than to modify it to achieve new goals.
> > > Does the PCI core need new code to handle such things?
> >
> > Well, I thought of adding such things like the clearing of PME# status to the
> > PCI bus type suspend/resume callbacks.
> >
> > Apart from this, I think that to implement PCI runtime PM we'll need to add
> > some new code. First of all, we need to handle the case when some devices are
> > in low power states before suspend/hibernation. Second, we need to handle PME#
> > generated by devices being in low power states (or even in D0) at run time and
> > that will certainly require new code.
>
> Yes it will. We don't have any runtime PM infrastructure for PCI yet.
>
> Although to tell the truth, I'm not sure how important it is. How much
> power can we save by doing runtime suspends of PCI devices? With USB
> controllers, for example, much or most of the savings comes from
> disabling the USB portions of the device, which we _are_ doing. It's
> not clear how much more would be gained by shutting down the PCI
> portions as well.
That can only be determined by experimentation, IMO, so in fact we'll need a
prototype implementation to find that out.
Thanks,
Rafael
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [RFC][PATCH 6/9] PCI ACPI: Introduce acpi_pm_device_sleep_wake function
2008-06-26 20:31 ` Rafael J. Wysocki
@ 2008-06-26 20:38 ` Alan Stern
0 siblings, 0 replies; 25+ messages in thread
From: Alan Stern @ 2008-06-26 20:38 UTC (permalink / raw)
To: Rafael J. Wysocki
Cc: Zhao Yakui, Zhang Rui, ACPI Devel Maling List, Jesse Barnes,
Len Brown, pm list
On Thu, 26 Jun 2008, Rafael J. Wysocki wrote:
> > > Do you think the core should clear PME# status automatically in that case?
> >
> > Yes.
>
> Okay, but that will require us to add the clearing of PME# to the PCI
> resume/restore callbacks. Perfectly doable, but I'd prefer to do that on top
> of the $subject patch.
That's fine. I wasn't trying to criticize your new patches, just point
out that overall they are moving in the same direction as this other
work that was done last year.
Alan Stern
^ permalink raw reply [flat|nested] 25+ messages in thread
end of thread, other threads:[~2008-06-26 20:38 UTC | newest]
Thread overview: 25+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2008-06-19 23:45 [RFC][PATCH 0/9] PCI PM: Rework PCI device PM Rafael J. Wysocki
2008-06-19 23:46 ` [RFC][PATCH 1/9] ACPI: Introduce acpi_bus_power_manageable function Rafael J. Wysocki
2008-06-24 12:25 ` [linux-pm] " Pavel Machek
2008-06-19 23:47 ` [RFC][PATCH 2/9] PCI: Introduce platform_pci_power_manageable function Rafael J. Wysocki
2008-06-24 12:31 ` [linux-pm] " Pavel Machek
2008-06-19 23:48 ` [RFC][PATCH 3/9] PCI: Rework pci_set_power_state function Rafael J. Wysocki
2008-06-24 12:34 ` [linux-pm] " Pavel Machek
2008-06-19 23:49 ` [RFC][PATCH 4/9] ACPI: Introduce acpi_device_sleep_wake function Rafael J. Wysocki
2008-06-24 12:38 ` [linux-pm] " Pavel Machek
2008-06-19 23:50 ` [RFC][PATCH 5/9] ACPI: Introduce new device wakeup flag 'prepared' Rafael J. Wysocki
2008-06-19 23:51 ` [RFC][PATCH 6/9] PCI ACPI: Introduce acpi_pm_device_sleep_wake function Rafael J. Wysocki
2008-06-25 8:11 ` Zhang Rui
2008-06-25 10:29 ` Zhao Yakui
2008-06-25 18:57 ` Rafael J. Wysocki
2008-06-25 21:31 ` Rafael J. Wysocki
2008-06-26 14:12 ` Alan Stern
2008-06-26 18:21 ` Rafael J. Wysocki
2008-06-26 19:54 ` Alan Stern
2008-06-26 20:31 ` Rafael J. Wysocki
2008-06-26 20:38 ` Alan Stern
2008-06-19 23:52 ` [RFC][PATCH 7/9] PCI ACPI: Introduce can_wakeup platform callback Rafael J. Wysocki
2008-06-19 23:52 ` [RFC][PATCH 8/9] PCI PM: Rework device PM initialization Rafael J. Wysocki
2008-06-20 15:59 ` [RFC][PATCH 8/9] PCI PM: Rework device PM initialization (rev. 2) Rafael J. Wysocki
2008-06-20 16:07 ` [RFC][PATCH 8.5/9] PCI PM: Add diagnostic statements to PCI device PM initialization Rafael J. Wysocki
2008-06-19 23:57 ` [RFC][PATCH 9/9] PCI PM: Introduce pci_prepare_to_sleep and pci_back_from_sleep Rafael J. Wysocki
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox