[PATCH v3] PM / core: conditionally skip system pm in device/driver model

From: Guan-Yu Lin
Date: Fri Feb 23 2024 - 09:42:40 EST


In systems with a main processor and a co-processor, asynchronous
controller management can lead to conflicts. One example is the main
processor attempting to suspend a device while the co-processor is
actively using it. To address this, we introduce a new sysfs entry
called "conditional_skip". This entry allows the system to selectively
skip certain device power management state transitions. To use this
feature, set the value in "conditional_skip" to indicate the type of
state transition you want to avoid. Please review /Documentation/ABI/
testing/sysfs-devices-power for more detailed information.

Signed-off-by: Guan-Yu Lin <guanyulin@xxxxxxxxxx>
---
V2 -> V3: Integrate the feature with the pm core framework.
V1 -> V2: Cosmetics changes on coding style.
[v2] usb: host: enable suspend-to-RAM control in userspace
[v1] [RFC] usb: host: Allow userspace to control usb suspend flows
---
Documentation/ABI/testing/sysfs-devices-power | 11 ++++++++
drivers/base/power/main.c | 16 ++++++++++++
drivers/base/power/sysfs.c | 26 +++++++++++++++++++
include/linux/device.h | 7 +++++
include/linux/pm.h | 1 +
5 files changed, 61 insertions(+)

diff --git a/Documentation/ABI/testing/sysfs-devices-power b/Documentation/ABI/testing/sysfs-devices-power
index 54195530e97a..3ac4e40f07a0 100644
--- a/Documentation/ABI/testing/sysfs-devices-power
+++ b/Documentation/ABI/testing/sysfs-devices-power
@@ -305,3 +305,14 @@ Description:
Reports the runtime PM children usage count of a device, or
0 if the children will be ignored.

+What: /sys/devices/.../power/conditional_skip
+Date: Feburary 2024
+Contact: Guan-Yu Lin <guanyulin@xxxxxxxxxx>
+Description:
+ The /sys/devices/.../conditional_skip attribute provides a way
+ to selectively skip system-wide power transitions like
+ suspend-to-RAM or hibernation. To skip a specific transition,
+ write its corresponding value to this attribute. For skipping
+ multiple transitions, combine their values using a bitwise OR
+ and write the result to this attribute.
+
diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c
index fadcd0379dc2..d507626c7892 100644
--- a/drivers/base/power/main.c
+++ b/drivers/base/power/main.c
@@ -1881,6 +1881,7 @@ static int device_prepare(struct device *dev, pm_message_t state)
*/
int dpm_prepare(pm_message_t state)
{
+ struct list_head list;
int error = 0;

trace_suspend_resume(TPS("dpm_prepare"), state.event, true);
@@ -1900,12 +1901,26 @@ int dpm_prepare(pm_message_t state)
*/
device_block_probing();

+ INIT_LIST_HEAD(&list);
mutex_lock(&dpm_list_mtx);
while (!list_empty(&dpm_list) && !error) {
struct device *dev = to_device(dpm_list.next);

get_device(dev);

+ if (dev->power.conditional_skip_pm & state.event) {
+ dev_info(dev, "skip system PM transition (event = 0x%04x)\n",
+ state.event);
+
+ if (!list_empty(&dev->power.entry))
+ list_move_tail(&dev->power.entry, &list);
+
+ mutex_unlock(&dpm_list_mtx);
+ put_device(dev);
+ mutex_lock(&dpm_list_mtx);
+ continue;
+ }
+
mutex_unlock(&dpm_list_mtx);

trace_device_pm_callback_start(dev, "", state.event);
@@ -1931,6 +1946,7 @@ int dpm_prepare(pm_message_t state)

mutex_lock(&dpm_list_mtx);
}
+ list_splice(&list, &dpm_list);
mutex_unlock(&dpm_list_mtx);
trace_suspend_resume(TPS("dpm_prepare"), state.event, false);
return error;
diff --git a/drivers/base/power/sysfs.c b/drivers/base/power/sysfs.c
index a1474fb67db9..1feacb01b1e9 100644
--- a/drivers/base/power/sysfs.c
+++ b/drivers/base/power/sysfs.c
@@ -610,6 +610,31 @@ static DEVICE_ATTR_RW(async);
#endif /* CONFIG_PM_SLEEP */
#endif /* CONFIG_PM_ADVANCED_DEBUG */

+static ssize_t conditional_skip_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "0x%04x\n", dev->power.conditional_skip_pm);
+}
+
+static ssize_t conditional_skip_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t n)
+{
+ int ret;
+
+ if (kstrtoint(buf, 0, &ret))
+ return -EINVAL;
+
+ ret &= (PM_EVENT_FREEZE|PM_EVENT_SUSPEND|PM_EVENT_HIBERNATE);
+
+ dev->power.conditional_skip_pm = ret;
+
+ return n;
+}
+
+static DEVICE_ATTR_RW(conditional_skip);
+
static struct attribute *power_attrs[] = {
#ifdef CONFIG_PM_ADVANCED_DEBUG
#ifdef CONFIG_PM_SLEEP
@@ -620,6 +645,7 @@ static struct attribute *power_attrs[] = {
&dev_attr_runtime_active_kids.attr,
&dev_attr_runtime_enabled.attr,
#endif /* CONFIG_PM_ADVANCED_DEBUG */
+ &dev_attr_conditional_skip.attr,
NULL,
};
static const struct attribute_group pm_attr_group = {
diff --git a/include/linux/device.h b/include/linux/device.h
index 97c4b046c09d..f2c73dd00211 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -968,6 +968,13 @@ static inline void device_set_pm_not_required(struct device *dev)
dev->power.no_pm = true;
}

+static inline void device_set_pm_conditional_skip(struct device *dev,
+ int condition)
+{
+ condition &= (PM_EVENT_FREEZE|PM_EVENT_SUSPEND|PM_EVENT_HIBERNATE);
+ dev->power.conditional_skip_pm = condition;
+}
+
static inline void dev_pm_syscore_device(struct device *dev, bool val)
{
#ifdef CONFIG_PM_SLEEP
diff --git a/include/linux/pm.h b/include/linux/pm.h
index a2f3e53a8196..890c7a601c2a 100644
--- a/include/linux/pm.h
+++ b/include/linux/pm.h
@@ -713,6 +713,7 @@ struct dev_pm_info {
enum rpm_status last_status;
int runtime_error;
int autosuspend_delay;
+ int conditional_skip_pm;
u64 last_busy;
u64 active_time;
u64 suspended_time;
--
2.44.0.rc0.258.g7320e95886-goog