From: Antheas Kapenekakis <lkml@antheas.dev>
To: dmitry.osipenko@collabora.com
Cc: bob.beckett@collabora.com, bookeldor@gmail.com,
hadess@hadess.net, jaap@haitsma.org, kernel@collabora.com,
lennart@poettering.net, linux-acpi@vger.kernel.org,
linux-kernel@vger.kernel.org, lkml@antheas.dev, mccann@jhu.edu,
rafael@kernel.org, richard@hughsie.com,
sebastian.reichel@collabora.com, superm1@kernel.org,
systemd-devel@lists.freedesktop.org, xaver.hugl@gmail.com
Subject: [RFC v1 3/8] acpi/x86: s2idle: add runtime standby transition function
Date: Fri, 26 Dec 2025 12:26:41 +0200 [thread overview]
Message-ID: <20251226102656.6296-4-lkml@antheas.dev> (raw)
In-Reply-To: <20251226102656.6296-1-lkml@antheas.dev>
Add pm_standby_transition() to allow transitioning between standby
states during runtime and implement it as part of the s2idle suspend
sequence. Update the platform_s2idle_ops structure to include a function
to perform firmware notifications and a function to get supported states
Standby states are a way for userspace to indicate the interactivity of
the system at the current moment. Active means that the user is
interacting with the device, inactive that a user is not actively
interacting with the device, and sleep that the system should appear as
if it is suspended to the user, but may still perform small
background tasks.
For modern ACPI s0ix laptops, the inactive state turns off the
backlight and the sleep state may limit the thermal envelope of the
device. Either may pulse the power light. This patch introduces an ACPI
agnostic structure to handle these transitions, so they may implemented
by other platforms, and does not implement them for ACPI.
Signed-off-by: Antheas Kapenekakis <lkml@antheas.dev>
---
include/linux/suspend.h | 26 +++++++
kernel/power/suspend.c | 153 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 179 insertions(+)
diff --git a/include/linux/suspend.h b/include/linux/suspend.h
index b02876f1ae38..916dee124758 100644
--- a/include/linux/suspend.h
+++ b/include/linux/suspend.h
@@ -40,6 +40,25 @@ typedef int __bitwise suspend_state_t;
#define PM_SUSPEND_MIN PM_SUSPEND_TO_IDLE
#define PM_SUSPEND_MAX ((__force suspend_state_t) 4)
+typedef int __bitwise standby_state_t;
+
+#define PM_STANDBY_ACTIVE ((__force standby_state_t) 0)
+#define PM_STANDBY_INACTIVE ((__force standby_state_t) 1)
+#define PM_STANDBY_SLEEP ((__force standby_state_t) 2)
+#define PM_STANDBY_RESUME ((__force standby_state_t) 3)
+#define PM_STANDBY_MIN PM_STANDBY_ACTIVE
+#define PM_STANDBY_MAX ((__force standby_state_t) 4)
+
+typedef int __bitwise standby_notification_t;
+
+#define PM_SN_INACTIVE_ENTRY ((__force standby_notification_t) 0)
+#define PM_SN_INACTIVE_EXIT ((__force standby_notification_t) 1)
+#define PM_SN_SLEEP_ENTRY ((__force standby_notification_t) 2)
+#define PM_SN_SLEEP_EXIT ((__force standby_notification_t) 3)
+#define PM_SN_RESUME ((__force standby_notification_t) 4)
+#define PM_SN_MIN PM_STANDBY_DISPLAY_OFF
+#define PM_SN_MAX ((__force standby_notification_t) 5)
+
/**
* struct platform_suspend_ops - Callbacks for managing platform dependent
* system sleep states.
@@ -132,6 +151,8 @@ struct platform_suspend_ops {
};
struct platform_s2idle_ops {
+ u8 (*get_standby_states)(void);
+ int (*do_notification)(standby_notification_t state);
int (*begin)(void);
int (*prepare)(void);
int (*prepare_late)(void);
@@ -276,6 +297,11 @@ extern void arch_suspend_enable_irqs(void);
extern int pm_suspend(suspend_state_t state);
extern bool sync_on_suspend_enabled;
+
+extern void pm_standby_refresh_states(void);
+extern int pm_standby_transition(standby_state_t state);
+extern void pm_standby_set_state(standby_state_t state);
+extern int pm_standby_get_state(void);
#else /* !CONFIG_SUSPEND */
#define suspend_valid_only_mem NULL
diff --git a/kernel/power/suspend.c b/kernel/power/suspend.c
index 2da4482bb6eb..ede1ba483fa5 100644
--- a/kernel/power/suspend.c
+++ b/kernel/power/suspend.c
@@ -46,12 +46,21 @@ static const char * const mem_sleep_labels[] = {
[PM_SUSPEND_MEM] = "deep",
};
const char *mem_sleep_states[PM_SUSPEND_MAX];
+static const char * const standby_labels[] = {
+ [PM_STANDBY_ACTIVE] = "active",
+ [PM_STANDBY_INACTIVE] = "inactive",
+ [PM_STANDBY_SLEEP] = "sleep",
+ [PM_STANDBY_RESUME] = "resume",
+};
+const char *standby_states[PM_STANDBY_MAX];
suspend_state_t mem_sleep_current = PM_SUSPEND_TO_IDLE;
suspend_state_t mem_sleep_default = PM_SUSPEND_MAX;
suspend_state_t pm_suspend_target_state;
EXPORT_SYMBOL_GPL(pm_suspend_target_state);
+standby_state_t standby_current = PM_STANDBY_ACTIVE;
+
unsigned int pm_suspend_global_flags;
EXPORT_SYMBOL_GPL(pm_suspend_global_flags);
@@ -195,6 +204,9 @@ void __init pm_states_init(void)
* initialize mem_sleep_states[] accordingly here.
*/
mem_sleep_states[PM_SUSPEND_TO_IDLE] = mem_sleep_labels[PM_SUSPEND_TO_IDLE];
+
+ /* Always support the active runtime standby state. */
+ standby_states[PM_STANDBY_ACTIVE] = standby_labels[PM_STANDBY_ACTIVE];
}
static int __init mem_sleep_default_setup(char *str)
@@ -334,6 +346,141 @@ static bool platform_suspend_again(suspend_state_t state)
suspend_ops->suspend_again() : false;
}
+static int platform_standby_notify(standby_notification_t state)
+{
+ return s2idle_ops && s2idle_ops->do_notification ?
+ s2idle_ops->do_notification(state) :
+ 0;
+}
+
+/**
+ * pm_standby_refresh_states - Refresh the supported runtime standby states
+ */
+void pm_standby_refresh_states(void)
+{
+ u8 standby_support = s2idle_ops && s2idle_ops->get_standby_states ?
+ s2idle_ops->get_standby_states() :
+ 0;
+
+ standby_states[PM_STANDBY_INACTIVE] =
+ standby_support & BIT(PM_STANDBY_INACTIVE) ?
+ standby_labels[PM_STANDBY_INACTIVE] :
+ NULL;
+ standby_states[PM_STANDBY_SLEEP] =
+ standby_support & BIT(PM_STANDBY_SLEEP) ?
+ standby_labels[PM_STANDBY_SLEEP] :
+ NULL;
+ standby_states[PM_STANDBY_RESUME] =
+ standby_support & BIT(PM_STANDBY_RESUME) ?
+ standby_labels[PM_STANDBY_RESUME] :
+ NULL;
+}
+EXPORT_SYMBOL_GPL(pm_standby_refresh_states);
+
+/**
+ * pm_standby_transition - Transition between standby states
+ *
+ * Configure the runtime standby state of the system. Entering these states
+ * may change the appearance of the system (e.g., keyboard backlight) or limit
+ * the thermal envelope of the system (e.g., PLx to 5W).
+ *
+ * Returns an error if the transition fails. The function does not rollback.
+ */
+int pm_standby_transition(standby_state_t state)
+{
+ int error;
+
+ if (state == standby_current)
+ return 0;
+ if (state > PM_STANDBY_MAX)
+ return -EINVAL;
+
+ pm_standby_refresh_states();
+
+ pm_pr_dbg("Transitioning from standby state %s to %s\n",
+ standby_states[standby_current], standby_states[state]);
+
+ /* Resume can only be entered if we are on the sleep state. */
+ if (state == PM_STANDBY_RESUME) {
+ if (standby_current != PM_STANDBY_SLEEP)
+ return -EINVAL;
+ standby_current = PM_STANDBY_RESUME;
+ return platform_standby_notify(PM_SN_RESUME);
+ }
+
+ /*
+ * The system should not be able to re-enter Sleep from resume as it
+ * is undefined behavior. As part of setting the state to "Resume",
+ * userspace promised a transition to "Inactive" or "Active".
+ */
+ if (standby_current == PM_STANDBY_RESUME && state == PM_STANDBY_SLEEP)
+ return -EINVAL;
+
+ /* Resume is the Sleep state logic-wise. */
+ if (standby_current == PM_STANDBY_RESUME)
+ standby_current = PM_STANDBY_SLEEP;
+
+ if (standby_current < state) {
+ for (; standby_current < state; standby_current++) {
+ switch (standby_current + 1) {
+ case PM_STANDBY_INACTIVE:
+ error = platform_standby_notify(PM_SN_INACTIVE_ENTRY);
+ break;
+ case PM_STANDBY_SLEEP:
+ error = platform_standby_notify(PM_SN_SLEEP_ENTRY);
+ break;
+ }
+
+ if (error) {
+ /* Rollback to previous valid state */
+ while (standby_current > PM_STANDBY_ACTIVE &&
+ !standby_states[standby_current])
+ standby_current--;
+ return error;
+ }
+ }
+ } else if (standby_current > state) {
+ for (; standby_current > state; standby_current--) {
+ switch (standby_current) {
+ case PM_STANDBY_SLEEP:
+ error = platform_standby_notify(PM_SN_SLEEP_EXIT);
+ break;
+ case PM_STANDBY_INACTIVE:
+ error = platform_standby_notify(PM_SN_INACTIVE_EXIT);
+ break;
+ }
+
+ if (error) {
+ /* Rollback to previous valid state */
+ while (standby_current < PM_STANDBY_SLEEP &&
+ !standby_states[standby_current])
+ standby_current++;
+ return error;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * pm_standby_set_state - Set the current standby state and skip the transition
+ */
+void pm_standby_set_state(standby_state_t state)
+{
+ standby_current = state;
+}
+EXPORT_SYMBOL_GPL(pm_standby_set_state);
+
+/**
+ * pm_standby_get_state - Returns the current standby state
+ */
+int pm_standby_get_state(void)
+{
+ return standby_current;
+}
+EXPORT_SYMBOL_GPL(pm_standby_get_state);
+
#ifdef CONFIG_PM_DEBUG
static unsigned int pm_test_delay = 5;
module_param(pm_test_delay, uint, 0644);
@@ -572,6 +719,7 @@ static void suspend_finish(void)
*/
static int enter_state(suspend_state_t state)
{
+ standby_state_t standby_prior;
int error;
trace_suspend_resume(TPS("suspend_enter"), state, true);
@@ -588,6 +736,9 @@ static int enter_state(suspend_state_t state)
if (!mutex_trylock(&system_transition_mutex))
return -EBUSY;
+ standby_prior = standby_current;
+ pm_standby_transition(PM_STANDBY_SLEEP);
+
if (state == PM_SUSPEND_TO_IDLE)
s2idle_begin();
@@ -619,6 +770,8 @@ static int enter_state(suspend_state_t state)
pm_pr_dbg("Finishing wakeup.\n");
suspend_finish();
Unlock:
+ pm_standby_transition(standby_prior);
+
mutex_unlock(&system_transition_mutex);
return error;
}
--
2.52.0
next prev parent reply other threads:[~2025-12-26 10:33 UTC|newest]
Thread overview: 41+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-12-26 10:26 [RFC v1 0/8] acpi/x86: s2idle: Introduce and implement runtime standby ABI for ACPI s0ix platforms Antheas Kapenekakis
2025-12-26 10:26 ` [RFC v1 1/8] Documentation: PM: Add documentation for Runtime Standby States Antheas Kapenekakis
2026-03-13 20:07 ` Rafael J. Wysocki
2026-03-16 19:54 ` Antheas Kapenekakis
2025-12-26 10:26 ` [RFC v1 2/8] acpi/x86: s2idle: Rename LPS0 constants so they mirror their function Antheas Kapenekakis
2026-03-13 20:12 ` Rafael J. Wysocki
2026-03-16 20:01 ` Antheas Kapenekakis
2025-12-26 10:26 ` Antheas Kapenekakis [this message]
2026-03-13 20:29 ` [RFC v1 3/8] acpi/x86: s2idle: add runtime standby transition function Rafael J. Wysocki
2026-03-16 20:06 ` Antheas Kapenekakis
2025-12-26 10:26 ` [RFC v1 4/8] acpi/x86: s2idle: add support for querying runtime standby state support Antheas Kapenekakis
2025-12-26 10:26 ` [RFC v1 5/8] acpi/x86: s2idle: move DSM notifications to do_notification callback Antheas Kapenekakis
2025-12-26 10:26 ` [RFC v1 6/8] acpi/x86: s2idle: implement turn on display DSM as resume notification Antheas Kapenekakis
2025-12-26 10:26 ` [RFC v1 7/8] PM: hibernate: Enter s2idle sleep state before hibernation Antheas Kapenekakis
2026-03-13 20:33 ` Rafael J. Wysocki
2026-03-16 20:09 ` Antheas Kapenekakis
2025-12-26 10:26 ` [RFC v1 8/8] PM: standby: Add sysfs attribute for runtime standby transitions Antheas Kapenekakis
2026-01-12 20:33 ` [RFC v1 0/8] acpi/x86: s2idle: Introduce and implement runtime standby ABI for ACPI s0ix platforms Antheas Kapenekakis
2026-01-13 9:48 ` Dmitry Osipenko
2026-01-13 10:11 ` Antheas Kapenekakis
2026-01-14 23:07 ` Dmitry Osipenko
2026-01-15 7:49 ` Antheas Kapenekakis
2026-03-16 19:02 ` Dmitry Osipenko
2026-03-16 19:33 ` Antheas Kapenekakis
2026-03-16 19:36 ` Antheas Kapenekakis
2026-03-17 11:04 ` Dmitry Osipenko
2026-02-27 14:59 ` Antheas Kapenekakis
2026-02-27 18:42 ` Rafael J. Wysocki
2026-03-13 19:36 ` Rafael J. Wysocki
2026-03-16 19:52 ` Antheas Kapenekakis
2026-03-17 11:56 ` Dmitry Osipenko
2026-03-17 12:09 ` Rafael J. Wysocki
2026-03-17 15:13 ` Mario Limonciello
2026-03-19 12:35 ` Antheas Kapenekakis
2026-03-20 20:41 ` Mario Limonciello (AMD) (kernel.org)
2026-03-21 13:46 ` Antheas Kapenekakis
2026-03-21 13:52 ` Antheas Kapenekakis
2026-03-21 18:43 ` Mario Limonciello
2026-03-21 22:33 ` Antheas Kapenekakis
2026-03-23 4:36 ` Mario Limonciello
2026-03-23 9:26 ` Antheas Kapenekakis
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20251226102656.6296-4-lkml@antheas.dev \
--to=lkml@antheas.dev \
--cc=bob.beckett@collabora.com \
--cc=bookeldor@gmail.com \
--cc=dmitry.osipenko@collabora.com \
--cc=hadess@hadess.net \
--cc=jaap@haitsma.org \
--cc=kernel@collabora.com \
--cc=lennart@poettering.net \
--cc=linux-acpi@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=mccann@jhu.edu \
--cc=rafael@kernel.org \
--cc=richard@hughsie.com \
--cc=sebastian.reichel@collabora.com \
--cc=superm1@kernel.org \
--cc=systemd-devel@lists.freedesktop.org \
--cc=xaver.hugl@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox