diff mbox series

[RFC,13/13] PM: standby: Add sysfs attribute for modern standby transitions

Message ID 20241121172239.119590-14-lkml@antheas.dev (mailing list archive)
State New
Headers show
Series acpi/x86: s2idle: implement Modern Standby transition states and expose to userspace | expand

Commit Message

Antheas Kapenekakis Nov. 21, 2024, 5:22 p.m. UTC
Add a sysfs attribute to allow informing the kernel about the current
standby state, those being: "active", "screen_off", "sleep", and
"resume" (to prepare turning the display on). The final modern
standby state DRIPS is omitted, as that is entered during the kernel
suspend process and userspace will never see it.

Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
---
 Documentation/ABI/testing/sysfs-power | 34 ++++++++++++
 kernel/power/main.c                   | 75 +++++++++++++++++++++++++++
 2 files changed, 109 insertions(+)
diff mbox series

Patch

diff --git a/Documentation/ABI/testing/sysfs-power b/Documentation/ABI/testing/sysfs-power
index a3942b1036e2..eff13980cc7c 100644
--- a/Documentation/ABI/testing/sysfs-power
+++ b/Documentation/ABI/testing/sysfs-power
@@ -39,6 +39,40 @@  Description:
 		See Documentation/admin-guide/pm/sleep-states.rst for more
 		information.
 
+What:		/sys/power/standby
+Date:		November 2024
+Contact:	Antheas Kapenekakis <lkml@antheas.dev>
+Description:
+		The /sys/power/standby file controls the standby state of the
+		system. Modern S0ix capable systems can enter a set of low power
+		states while the kernel is still active. Transitioning into those
+		states may 1) deactivate tertiary hardware, and 2) change the
+		presentation of the device (e.g., pulse the suspend light, turn
+		off the keyboard backlight).
+
+		Available states are "active" (fully active), "screen-off" (fully
+		active but all displays of the system are off; virtual and real),
+		"sleep" (major userspace components have been frozen; light
+		background tasks may still run; this state may affect the power
+		envelope of the device). The final state is DRIPS or LSP0, where
+		the kernel suspends, and is entered by writing "mem" to
+		/sys/power/state. There is a secondary sleep state called "resume"
+		that can only be entered from "sleep" and is used in certain
+		devices to boost the Power Limit (PLx) while remaining in sleep
+		to hasten preparing for transitioning to "active".
+
+		Writing one of the above strings to this file causes the system
+		to transition into the corresponding state, by firing the
+		corresponding firmware notifications during the transition.
+
+		DRIPS or LSP0 (i.e., mem "s2idle") can only be entered from the
+		"sleep" state. If the kernel is asked to transition to DRIPS from
+		a different state, it will transition to "sleep" and then suspend.
+		On wakeup, the kernel will transition back to the previous state.
+
+		See Documentation/admin-guide/pm/standby-states.rst for more
+		information.
+
 What:		/sys/power/disk
 Date:		September 2006
 Contact:	Rafael J. Wysocki <rjw@rjwysocki.net>
diff --git a/kernel/power/main.c b/kernel/power/main.c
index 6254814d4817..4377fdaf4a8d 100644
--- a/kernel/power/main.c
+++ b/kernel/power/main.c
@@ -748,6 +748,80 @@  static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,
 
 power_attr(state);
 
+#ifdef CONFIG_SUSPEND
+/*
+ * standby - control system s2idle standby state.
+ *
+ * show() returns available standby states, which may be "active", "screen_off",
+ * "sleep" and "resume" (still in sleep but preparing to turn on display).
+ * See Documentation/admin-guide/pm/standby-states.rst for a description of
+ * what they mean.
+ *
+ * store() accepts one of those strings, translates it into the proper
+ * enumerated value, and initiates a transition to that standby state.
+ *
+ * When the system suspends, it will first enter the state "sleep", suspend,
+ * and then restore the last state before entering "sleep". I.e., if userspace
+ * is not S0ix-aware, the transitions expected by Modern Standby devices will
+ * always be performed.
+ */
+static ssize_t standby_show(struct kobject *kobj, struct kobj_attribute *attr,
+			  char *buf)
+{
+	char *s = buf;
+	standby_state_t i;
+	standby_state_t curr = pm_standby_state();
+
+	if (curr < 0)
+		return -EBUSY;
+
+	for (i = PM_STANDBY_MIN; i < PM_STANDBY_MAX; i++)
+		if (standby_states[i])
+			s += sprintf(s, curr == i ? "[%s] " : "%s ", standby_states[i]);
+
+	if (s != buf)
+		/* convert the last space to a newline */
+		*(s - 1) = '\n';
+	return (s - buf);
+}
+
+static standby_state_t decode_standby_state(const char *buf, size_t n)
+{
+	standby_state_t state;
+	char *p;
+	int len;
+
+	p = memchr(buf, '\n', n);
+	len = p ? p - buf : n;
+
+	for (state = PM_STANDBY_MIN; state < PM_STANDBY_MAX; state++) {
+		const char *label = standby_states[state];
+
+		if (label && len == strlen(label) && !strncmp(buf, label, len))
+			return state;
+	}
+
+	return PM_STANDBY_MAX;
+}
+
+static ssize_t standby_store(struct kobject *kobj, struct kobj_attribute *attr,
+			   const char *buf, size_t n)
+{
+	int error;
+	standby_state_t state;
+
+	state = decode_standby_state(buf, n);
+
+	if (state >= PM_STANDBY_MAX)
+		return -EINVAL;
+
+	error = pm_standby_transition(state);
+	return error ? error : n;
+}
+
+power_attr(standby);
+#endif
+
 #ifdef CONFIG_PM_SLEEP
 /*
  * The 'wakeup_count' attribute, along with the functions defined in
@@ -974,6 +1048,7 @@  static struct attribute * g[] = {
 #ifdef CONFIG_SUSPEND
 	&mem_sleep_attr.attr,
 	&sync_on_suspend_attr.attr,
+	&standby_attr.attr,
 #endif
 #ifdef CONFIG_PM_AUTOSLEEP
 	&autosleep_attr.attr,