[PATCH 2/3] PM / ACPI: Use SAFE_SUSPEND in the generic ACPI PM domain
Rafael J. Wysocki
rjw at rjwysocki.net
Mon Aug 28 17:59:08 PDT 2017
From: Rafael J. Wysocki <rafael.j.wysocki at intel.com>
Subject: [PATCH] PM / ACPI: Use SAFE_SUSPEND in the generic ACPI PM domain
Make the generic ACPI PM domain and the ACPI LPSS driver take the
SAFE_SUSPEND driver flag into consideration when deciding whether
or not to runtime resume devices during system suspend.
Namely, if the flag is set, acpi_subsys_suspend() will not attempt
to runtime resume the device unless acpi_subsys_prepare() has found
that the power state of the device has to be updated. Accordingly,
acpi_subsys_suspend_late(), acpi_subsys_resume_late(),
acpi_lpss_suspend_late(), and acpi_lpss_resume_late() will only
try to update the power state of the device and its wakeup settings
if the device has been runtime resumed beforehand.
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki at intel.com>
---
drivers/acpi/acpi_lpss.c | 20 ++++++----
drivers/acpi/device_pm.c | 90 ++++++++++++++++++++++++++++++++++++-----------
include/acpi/acpi_bus.h | 1
3 files changed, 84 insertions(+), 27 deletions(-)
Index: linux-pm/include/acpi/acpi_bus.h
===================================================================
--- linux-pm.orig/include/acpi/acpi_bus.h
+++ linux-pm/include/acpi/acpi_bus.h
@@ -287,6 +287,7 @@ struct acpi_device_power {
int state; /* Current state */
struct acpi_device_power_flags flags;
struct acpi_device_power_state states[ACPI_D_STATE_COUNT]; /* Power states (D0-D3Cold) */
+ bool update_state;
};
/* Performance Management */
Index: linux-pm/drivers/acpi/device_pm.c
===================================================================
--- linux-pm.orig/drivers/acpi/device_pm.c
+++ linux-pm/drivers/acpi/device_pm.c
@@ -899,6 +899,7 @@ int acpi_dev_runtime_resume(struct devic
error = acpi_dev_pm_full_power(adev);
acpi_device_wakeup_disable(adev);
+ adev->power.update_state = true;
return error;
}
EXPORT_SYMBOL_GPL(acpi_dev_runtime_resume);
@@ -989,33 +990,47 @@ int acpi_dev_resume_early(struct device
}
EXPORT_SYMBOL_GPL(acpi_dev_resume_early);
-/**
- * acpi_subsys_prepare - Prepare device for system transition to a sleep state.
- * @dev: Device to prepare.
- */
-int acpi_subsys_prepare(struct device *dev)
+static bool acpi_dev_state_update_needed(struct device *dev)
{
struct acpi_device *adev = ACPI_COMPANION(dev);
u32 sys_target;
int ret, state;
- ret = pm_generic_prepare(dev);
- if (ret < 0)
- return ret;
+ if (!pm_runtime_suspended(dev))
+ return true;
- if (!adev || !pm_runtime_suspended(dev)
- || device_may_wakeup(dev) != !!adev->wakeup.prepare_count)
- return 0;
+ if (device_may_wakeup(dev) != !!adev->wakeup.prepare_count)
+ return true;
sys_target = acpi_target_system_state();
if (sys_target == ACPI_STATE_S0)
- return 1;
+ return false;
if (adev->power.flags.dsw_present)
- return 0;
+ return true;
ret = acpi_dev_pm_get_state(dev, adev, sys_target, NULL, &state);
- return !ret && state == adev->power.state;
+ if (ret)
+ return true;
+
+ return state != adev->power.state;
+}
+
+/**
+ * acpi_subsys_prepare - Prepare device for system transition to a sleep state.
+ * @dev: Device to prepare.
+ */
+int acpi_subsys_prepare(struct device *dev)
+{
+ struct acpi_device *adev = ACPI_COMPANION(dev);
+ int ret;
+
+ ret = pm_generic_prepare(dev);
+ if (ret < 0 || !adev)
+ return ret;
+
+ adev->power.update_state = acpi_dev_state_update_needed(dev);
+ return !adev->power.update_state;
}
EXPORT_SYMBOL_GPL(acpi_subsys_prepare);
@@ -1024,11 +1039,30 @@ EXPORT_SYMBOL_GPL(acpi_subsys_prepare);
* @dev: Device to handle.
*
* Follow PCI and resume devices suspended at run time before running their
- * system suspend callbacks.
+ * system suspend callbacks, unless the DPM_FLAG_SAFE_SUSPEND driver flag is
+ * set for them.
*/
int acpi_subsys_suspend(struct device *dev)
{
- pm_runtime_resume(dev);
+ struct acpi_device *adev = ACPI_COMPANION(dev);
+ bool resume = !(dev->power.driver_flags & DPM_FLAG_SAFE_SUSPEND);
+
+ if (adev) {
+ /* The device may have resumed in the meantime. */
+ if (pm_runtime_suspended(dev)) {
+ resume = resume || adev->power.update_state;
+ } else {
+ /*
+ * Work around a super-theoretical race between runtime
+ * resume and acpi_dev_state_update_needed().
+ */
+ adev->power.update_state = true;
+ resume = false;
+ }
+ }
+ if (resume)
+ pm_runtime_resume(dev);
+
return pm_generic_suspend(dev);
}
EXPORT_SYMBOL_GPL(acpi_subsys_suspend);
@@ -1042,8 +1076,17 @@ EXPORT_SYMBOL_GPL(acpi_subsys_suspend);
*/
int acpi_subsys_suspend_late(struct device *dev)
{
- int ret = pm_generic_suspend_late(dev);
- return ret ? ret : acpi_dev_suspend_late(dev);
+ struct acpi_device *adev = ACPI_COMPANION(dev);
+ int ret;
+
+ ret = pm_generic_suspend_late(dev);
+ if (ret)
+ return ret;
+
+ if (adev && adev->power.update_state)
+ return acpi_dev_suspend_late(dev);
+
+ return 0;
}
EXPORT_SYMBOL_GPL(acpi_subsys_suspend_late);
@@ -1057,8 +1100,15 @@ EXPORT_SYMBOL_GPL(acpi_subsys_suspend_la
*/
int acpi_subsys_resume_early(struct device *dev)
{
- int ret = acpi_dev_resume_early(dev);
- return ret ? ret : pm_generic_resume_early(dev);
+ struct acpi_device *adev = ACPI_COMPANION(dev);
+
+ if (adev && adev->power.update_state) {
+ int ret = acpi_dev_resume_early(dev);
+ if (ret)
+ return ret;
+ }
+
+ return pm_generic_resume_early(dev);
}
EXPORT_SYMBOL_GPL(acpi_subsys_resume_early);
Index: linux-pm/drivers/acpi/acpi_lpss.c
===================================================================
--- linux-pm.orig/drivers/acpi/acpi_lpss.c
+++ linux-pm/drivers/acpi/acpi_lpss.c
@@ -719,7 +719,8 @@ static void acpi_lpss_dismiss(struct dev
#ifdef CONFIG_PM_SLEEP
static int acpi_lpss_suspend_late(struct device *dev)
{
- struct lpss_private_data *pdata = acpi_driver_data(ACPI_COMPANION(dev));
+ struct acpi_device *adev = ACPI_COMPANION(dev);
+ struct lpss_private_data *pdata = acpi_driver_data(adev);
int ret;
ret = pm_generic_suspend_late(dev);
@@ -729,17 +730,22 @@ static int acpi_lpss_suspend_late(struct
if (pdata->dev_desc->flags & LPSS_SAVE_CTX)
acpi_lpss_save_ctx(dev, pdata);
- return acpi_dev_suspend_late(dev);
+ if (adev->power.update_state)
+ return acpi_dev_suspend_late(dev);
+
+ return 0;
}
static int acpi_lpss_resume_early(struct device *dev)
{
- struct lpss_private_data *pdata = acpi_driver_data(ACPI_COMPANION(dev));
- int ret;
+ struct acpi_device *adev = ACPI_COMPANION(dev);
+ struct lpss_private_data *pdata = acpi_driver_data(adev);
- ret = acpi_dev_resume_early(dev);
- if (ret)
- return ret;
+ if (adev->power.update_state) {
+ int ret = acpi_dev_resume_early(dev);
+ if (ret)
+ return ret;
+ }
acpi_lpss_d3_to_d0_delay(pdata);
More information about the linux-arm-kernel
mailing list