Re: [PATCH v5] ACPI: PM: Introduce CONFIG_ACPI_S2IDLE for platform-independent S2Idle support

"Rafael J. Wysocki (Intel)" <[email protected]> Mon, 10 Aug 2026 12:23:42 +0200
Newsgroups gmane.linux.acpi.devel,gmane.linux.kernel
Message-ID <CAJZ5v0jpwjNk9pmKN41Dh6Q9GcRJo7xQ-JJTSEviqF_HiU=XrQ@mail.gmail.com>
On Mon, Aug 10, 2026 at 8:55 AM Riwen Lu <[email protected]> wrote:
>
> The ACPI S2Idle (suspend-to-idle) code is currently located in sleep.c
> and guarded by CONFIG_ACPI_SYSTEM_POWER_STATES_SUPPORT. This config is
> historically tied to x86/ia64 sleep states (S3/S4), which prevents
> ACPI-based platforms like ARM64 from using the OS-centric s2idle
> framework and its associated wakeup event handling (e.g., from ACPI
> buttons or the lid).
>
> Since S2Idle is a software-driven low-power state that does not rely on
> firmware sleep states (S3/S4), it should be available to all
> ACPI-enabled platforms.
>
> To achieve this, introduce a new, independent Kconfig option,
> CONFIG_ACPI_S2IDLE. It is selected by ACPI_SYSTEM_POWER_STATES_SUPPORT
> for backward compatibility, but can now also be enabled separately by
> other platforms.
>
> Move all s2idle-specific code into a dedicated file,
> drivers/acpi/s2idle.c. This creates a clear and independent ACPI s2idle
> framework, centered around a default acpi_s2idle_ops and a weakly
> defined acpi_s2idle_setup() hook.
>
> The change allows all ACPI platforms to use this common s2idle
> infrastructure. Platforms with unique requirements can override the
> defaults via the weak function, while others (like ARM64) can now simply
> enable the config to obtain functional s2idle support with standard ACPI
> wakeup handling.
>
> Signed-off-by: Riwen Lu <[email protected]>
> ---
> v1 -> v2:
>  - Fix acpi_s2idle_setup() declaration error when SUSPEND is disabled in
>    x86 builds.
>
> v2 -> v3:
>  - Select ACPI_S2IDLE if SUSPEND is enabled for
>    ACPI_SYSTEM_POWER_STATES_SUPPORT, so the select no longer violates the
>    ACPI_S2IDLE dependency on SUSPEND.
>
> v3 -> v4:
>  - Add pr_fmt() in s2idle.c so log messages keep the "ACPI: PM: " prefix
>    that sleep.c used; without it the moved pr_info/pr_warn would lose the
>    subsystem prefix.
>  - Drop the redundant #ifdef CONFIG_SUSPEND / #else wrapper in s2idle.c.
>    The !SUSPEND branch was dead code, because CONFIG_ACPI_S2IDLE itself
>    depends on SUSPEND, so s2idle.c is only compiled when SUSPEND is set.
>  - Make acpi_s2idle_init() return void.  The int return value was always 0
>    in the real implementation and -ENXIO in the stub, and the only caller
>    (acpi_bus_init()) ignores it; a void interface is honest and the stub
>    becomes a clean no-op.
>  - Mark the weak acpi_s2idle_setup() default __init, matching the x86
>    override (which is already __init) and allowing the code to be discarded
>    after init.
>  - Use SPDX-License-Identifier: GPL-2.0-only in s2idle.c for consistency
>    with sleep.c (the file the code was split out from).
>  - Trivial cleanups: remove a double blank line in the Makefile and drop
>    the redundant "Warning: " prefix from a pr_warn() message.
>
> v4 -> v5:
>  - Add #include <linux/interrupt.h> to s2idle.c.  enable_irq_wake(),
>    disable_irq_wake() and rearm_wake_irq() are declared there.

OK, so I'm totally unconvinced about the approach here, while I agree
that it would be good to allow s2idle to also work on platforms using
HW-reduced ACPI.

I'll be mostly offline this week though, so let's get back to it later.

>  drivers/acpi/Kconfig    |  14 ++++
>  drivers/acpi/Makefile   |   1 +
>  drivers/acpi/bus.c      |   3 +
>  drivers/acpi/internal.h |  10 ++-
>  drivers/acpi/s2idle.c   | 163 ++++++++++++++++++++++++++++++++++++++++
>  drivers/acpi/sleep.c    | 145 -----------------------------------
>  6 files changed, 189 insertions(+), 147 deletions(-)
>  create mode 100644 drivers/acpi/s2idle.c
>
> diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
> index f165d14cf61a..8c0918747bbb 100644
> --- a/drivers/acpi/Kconfig
> +++ b/drivers/acpi/Kconfig
> @@ -55,6 +55,7 @@ config ACPI_GENERIC_GSI
>
>  config ACPI_SYSTEM_POWER_STATES_SUPPORT
>         bool
> +       select ACPI_S2IDLE if SUSPEND
>
>  config ACPI_CCA_REQUIRED
>         bool
> @@ -113,6 +114,19 @@ config ACPI_SLEEP
>         depends on ACPI_SYSTEM_POWER_STATES_SUPPORT
>         default y
>
> +config ACPI_S2IDLE
> +       bool "ACPI suspend-to-idle support"
> +       depends on SUSPEND
> +       help
> +         This option enables the core ACPI framework for the suspend-to-idle
> +         (S2Idle) power state. This state is software-driven and does not
> +         require platform firmware sleep states (S3/S4).
> +
> +         It is automatically selected by platforms with
> +         CONFIG_ACPI_SYSTEM_POWER_STATES_SUPPORT (like x86), but can also
> +         be enabled independently by other ACPI platforms (e.g., ARM64) to
> +         gain support for low-power idle and ACPI wakeup events.
> +
>  config ACPI_REV_OVERRIDE_POSSIBLE
>         bool "Allow supported ACPI revision to be overridden"
>         depends on X86
> diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile
> index d1b0affb844f..f1e89e45e876 100644
> --- a/drivers/acpi/Makefile
> +++ b/drivers/acpi/Makefile
> @@ -35,6 +35,7 @@ acpi-$(CONFIG_ACPI_SYSTEM_POWER_STATES_SUPPORT) += sleep.o
>  acpi-y                         += device_sysfs.o device_pm.o
>  acpi-$(CONFIG_ACPI_SLEEP)      += proc.o
>
> +acpi-$(CONFIG_ACPI_S2IDLE)     += s2idle.o
>
>  #
>  # ACPI Bus and Device Drivers
> diff --git a/drivers/acpi/bus.c b/drivers/acpi/bus.c
> index a30a904f6535..eedcd940859c 100644
> --- a/drivers/acpi/bus.c
> +++ b/drivers/acpi/bus.c
> @@ -1534,6 +1534,9 @@ static int __init acpi_bus_init(void)
>         /* Initialize sleep structures */
>         acpi_sleep_init();
>
> +       /* Initialize default acpi s2idle ops */
> +       acpi_s2idle_init();
> +
>         /*
>          * Get the system interrupt model and evaluate \_PIC.
>          */
> diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h
> index 40f875b265a9..5a08315d308a 100644
> --- a/drivers/acpi/internal.h
> +++ b/drivers/acpi/internal.h
> @@ -267,13 +267,19 @@ static inline bool acpi_ec_dispatch_gpe(void)
>                                    Suspend/Resume
>    -------------------------------------------------------------------------- */
>  #ifdef CONFIG_ACPI_SYSTEM_POWER_STATES_SUPPORT
> -extern bool acpi_s2idle_wakeup(void);
>  extern int acpi_sleep_init(void);
>  #else
> -static inline bool acpi_s2idle_wakeup(void) { return false; }
>  static inline int acpi_sleep_init(void) { return -ENXIO; }
>  #endif
>
> +#ifdef CONFIG_ACPI_S2IDLE
> +extern bool acpi_s2idle_wakeup(void);
> +extern void acpi_s2idle_init(void);
> +#else
> +static inline bool acpi_s2idle_wakeup(void) { return false; }
> +static inline void acpi_s2idle_init(void) {}
> +#endif
> +
>  #ifdef CONFIG_ACPI_SLEEP
>  void acpi_sleep_proc_init(void);
>  int suspend_nvs_alloc(void);
> diff --git a/drivers/acpi/s2idle.c b/drivers/acpi/s2idle.c
> new file mode 100644
> index 000000000000..b5cd9649acda
> --- /dev/null
> +++ b/drivers/acpi/s2idle.c
> @@ -0,0 +1,163 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * s2idle.c - ACPI suspend-to-idle support.
> + *
> + */
> +
> +#define pr_fmt(fmt) "ACPI: PM: " fmt
> +
> +#include <linux/acpi.h>
> +#include <linux/device.h>
> +#include <linux/interrupt.h>
> +#include <linux/suspend.h>
> +#include <linux/irq.h>
> +
> +#include "internal.h"
> +#include "sleep.h"
> +
> +static bool s2idle_wakeup;
> +
> +int acpi_s2idle_begin(void)
> +{
> +       acpi_scan_lock_acquire();
> +       return 0;
> +}
> +
> +int acpi_s2idle_prepare(void)
> +{
> +       if (acpi_sci_irq_valid()) {
> +               int error;
> +
> +               error = enable_irq_wake(acpi_sci_irq);
> +               if (error)
> +                       pr_warn("Failed to enable wakeup from IRQ %d: %d\n",
> +                               acpi_sci_irq, error);
> +
> +               acpi_ec_set_gpe_wake_mask(ACPI_GPE_ENABLE);
> +       }
> +
> +       acpi_enable_wakeup_devices(ACPI_STATE_S0);
> +
> +       /* Change the configuration of GPEs to avoid spurious wakeup. */
> +       acpi_enable_all_wakeup_gpes();
> +       acpi_os_wait_events_complete();
> +
> +       s2idle_wakeup = true;
> +       return 0;
> +}
> +
> +bool acpi_s2idle_wake(void)
> +{
> +       if (!acpi_sci_irq_valid())
> +               return pm_wakeup_pending();
> +
> +       while (pm_wakeup_pending()) {
> +               /*
> +                * If IRQD_WAKEUP_ARMED is set for the SCI at this point, the
> +                * SCI has not triggered while suspended, so bail out (the
> +                * wakeup is pending anyway and the SCI is not the source of
> +                * it).
> +                */
> +               if (irqd_is_wakeup_armed(irq_get_irq_data(acpi_sci_irq))) {
> +                       pm_pr_dbg("Wakeup unrelated to ACPI SCI\n");
> +                       return true;
> +               }
> +
> +               /*
> +                * If the status bit of any enabled fixed event is set, the
> +                * wakeup is regarded as valid.
> +                */
> +               if (acpi_any_fixed_event_status_set()) {
> +                       pm_pr_dbg("ACPI fixed event wakeup\n");
> +                       return true;
> +               }
> +
> +               /* Check wakeups from drivers sharing the SCI. */
> +               if (acpi_check_wakeup_handlers()) {
> +                       pm_pr_dbg("ACPI custom handler wakeup\n");
> +                       return true;
> +               }
> +
> +               /*
> +                * Check non-EC GPE wakeups and if there are none, cancel the
> +                * SCI-related wakeup and dispatch the EC GPE.
> +                */
> +               if (acpi_ec_dispatch_gpe()) {
> +                       pm_pr_dbg("ACPI non-EC GPE wakeup\n");
> +                       return true;
> +               }
> +
> +               acpi_os_wait_events_complete();
> +
> +               /*
> +                * The SCI is in the "suspended" state now and it cannot produce
> +                * new wakeup events till the rearming below, so if any of them
> +                * are pending here, they must be resulting from the processing
> +                * of EC events above or coming from somewhere else.
> +                */
> +               if (pm_wakeup_pending()) {
> +                       pm_pr_dbg("Wakeup after ACPI Notify sync\n");
> +                       return true;
> +               }
> +
> +               pm_pr_dbg("Rearming ACPI SCI for wakeup\n");
> +
> +               pm_wakeup_clear(acpi_sci_irq);
> +               rearm_wake_irq(acpi_sci_irq);
> +       }
> +
> +       return false;
> +}
> +
> +void acpi_s2idle_restore(void)
> +{
> +       /*
> +        * Drain pending events before restoring the working-state configuration
> +        * of GPEs.
> +        */
> +       acpi_os_wait_events_complete(); /* synchronize GPE processing */
> +       acpi_ec_flush_work(); /* flush the EC driver's workqueues */
> +       acpi_os_wait_events_complete(); /* synchronize Notify handling */
> +
> +       s2idle_wakeup = false;
> +
> +       acpi_enable_all_runtime_gpes();
> +
> +       acpi_disable_wakeup_devices(ACPI_STATE_S0);
> +
> +       if (acpi_sci_irq_valid()) {
> +               acpi_ec_set_gpe_wake_mask(ACPI_GPE_DISABLE);
> +               disable_irq_wake(acpi_sci_irq);
> +       }
> +}
> +
> +void acpi_s2idle_end(void)
> +{
> +       acpi_scan_lock_release();
> +}
> +
> +static const struct platform_s2idle_ops acpi_s2idle_ops = {
> +       .begin = acpi_s2idle_begin,
> +       .prepare = acpi_s2idle_prepare,
> +       .wake = acpi_s2idle_wake,
> +       .restore = acpi_s2idle_restore,
> +       .end = acpi_s2idle_end,
> +};
> +
> +void __init __weak acpi_s2idle_setup(void)
> +{
> +       if (acpi_gbl_FADT.flags & ACPI_FADT_LOW_POWER_S0)
> +               pr_info("Efficient low-power S0 idle declared\n");
> +
> +       s2idle_set_ops(&acpi_s2idle_ops);
> +}
> +
> +bool acpi_s2idle_wakeup(void)
> +{
> +       return s2idle_wakeup;
> +}
> +
> +void __init acpi_s2idle_init(void)
> +{
> +       acpi_s2idle_setup();
> +}
> diff --git a/drivers/acpi/sleep.c b/drivers/acpi/sleep.c
> index 132a9df98471..eeaacc354c48 100644
> --- a/drivers/acpi/sleep.c
> +++ b/drivers/acpi/sleep.c
> @@ -724,143 +724,6 @@ static const struct platform_suspend_ops acpi_suspend_ops_old = {
>         .recover = acpi_pm_finish,
>  };
>
> -static bool s2idle_wakeup;
> -
> -int acpi_s2idle_begin(void)
> -{
> -       acpi_scan_lock_acquire();
> -       return 0;
> -}
> -
> -int acpi_s2idle_prepare(void)
> -{
> -       if (acpi_sci_irq_valid()) {
> -               int error;
> -
> -               error = enable_irq_wake(acpi_sci_irq);
> -               if (error)
> -                       pr_warn("Warning: Failed to enable wakeup from IRQ %d: %d\n",
> -                               acpi_sci_irq, error);
> -
> -               acpi_ec_set_gpe_wake_mask(ACPI_GPE_ENABLE);
> -       }
> -
> -       acpi_enable_wakeup_devices(ACPI_STATE_S0);
> -
> -       /* Change the configuration of GPEs to avoid spurious wakeup. */
> -       acpi_enable_all_wakeup_gpes();
> -       acpi_os_wait_events_complete();
> -
> -       s2idle_wakeup = true;
> -       return 0;
> -}
> -
> -bool acpi_s2idle_wake(void)
> -{
> -       if (!acpi_sci_irq_valid())
> -               return pm_wakeup_pending();
> -
> -       while (pm_wakeup_pending()) {
> -               /*
> -                * If IRQD_WAKEUP_ARMED is set for the SCI at this point, the
> -                * SCI has not triggered while suspended, so bail out (the
> -                * wakeup is pending anyway and the SCI is not the source of
> -                * it).
> -                */
> -               if (irqd_is_wakeup_armed(irq_get_irq_data(acpi_sci_irq))) {
> -                       pm_pr_dbg("Wakeup unrelated to ACPI SCI\n");
> -                       return true;
> -               }
> -
> -               /*
> -                * If the status bit of any enabled fixed event is set, the
> -                * wakeup is regarded as valid.
> -                */
> -               if (acpi_any_fixed_event_status_set()) {
> -                       pm_pr_dbg("ACPI fixed event wakeup\n");
> -                       return true;
> -               }
> -
> -               /* Check wakeups from drivers sharing the SCI. */
> -               if (acpi_check_wakeup_handlers()) {
> -                       pm_pr_dbg("ACPI custom handler wakeup\n");
> -                       return true;
> -               }
> -
> -               /*
> -                * Check non-EC GPE wakeups and if there are none, cancel the
> -                * SCI-related wakeup and dispatch the EC GPE.
> -                */
> -               if (acpi_ec_dispatch_gpe()) {
> -                       pm_pr_dbg("ACPI non-EC GPE wakeup\n");
> -                       return true;
> -               }
> -
> -               acpi_os_wait_events_complete();
> -
> -               /*
> -                * The SCI is in the "suspended" state now and it cannot produce
> -                * new wakeup events till the rearming below, so if any of them
> -                * are pending here, they must be resulting from the processing
> -                * of EC events above or coming from somewhere else.
> -                */
> -               if (pm_wakeup_pending()) {
> -                       pm_pr_dbg("Wakeup after ACPI Notify sync\n");
> -                       return true;
> -               }
> -
> -               pm_pr_dbg("Rearming ACPI SCI for wakeup\n");
> -
> -               pm_wakeup_clear(acpi_sci_irq);
> -               rearm_wake_irq(acpi_sci_irq);
> -       }
> -
> -       return false;
> -}
> -
> -void acpi_s2idle_restore(void)
> -{
> -       /*
> -        * Drain pending events before restoring the working-state configuration
> -        * of GPEs.
> -        */
> -       acpi_os_wait_events_complete(); /* synchronize GPE processing */
> -       acpi_ec_flush_work(); /* flush the EC driver's workqueues */
> -       acpi_os_wait_events_complete(); /* synchronize Notify handling */
> -
> -       s2idle_wakeup = false;
> -
> -       acpi_enable_all_runtime_gpes();
> -
> -       acpi_disable_wakeup_devices(ACPI_STATE_S0);
> -
> -       if (acpi_sci_irq_valid()) {
> -               acpi_ec_set_gpe_wake_mask(ACPI_GPE_DISABLE);
> -               disable_irq_wake(acpi_sci_irq);
> -       }
> -}
> -
> -void acpi_s2idle_end(void)
> -{
> -       acpi_scan_lock_release();
> -}
> -
> -static const struct platform_s2idle_ops acpi_s2idle_ops = {
> -       .begin = acpi_s2idle_begin,
> -       .prepare = acpi_s2idle_prepare,
> -       .wake = acpi_s2idle_wake,
> -       .restore = acpi_s2idle_restore,
> -       .end = acpi_s2idle_end,
> -};
> -
> -void __weak acpi_s2idle_setup(void)
> -{
> -       if (acpi_gbl_FADT.flags & ACPI_FADT_LOW_POWER_S0)
> -               pr_info("Efficient low-power S0 idle declared\n");
> -
> -       s2idle_set_ops(&acpi_s2idle_ops);
> -}
> -
>  static void __init acpi_sleep_suspend_setup(void)
>  {
>         bool suspend_ops_needed = false;
> @@ -875,20 +738,12 @@ static void __init acpi_sleep_suspend_setup(void)
>         if (suspend_ops_needed)
>                 suspend_set_ops(old_suspend_ordering ?
>                                 &acpi_suspend_ops_old : &acpi_suspend_ops);
> -
> -       acpi_s2idle_setup();
>  }
>
>  #else /* !CONFIG_SUSPEND */
> -#define s2idle_wakeup          (false)
>  static inline void acpi_sleep_suspend_setup(void) {}
>  #endif /* !CONFIG_SUSPEND */
>
> -bool acpi_s2idle_wakeup(void)
> -{
> -       return s2idle_wakeup;
> -}
> -
>  #ifdef CONFIG_PM_SLEEP
>  static u32 saved_bm_rld;
>
> --
> 2.25.1
>