The branch main has been updated by obiwac: URL: https://cgit.FreeBSD.org/src/commit/?id=7669cbd0f06402573fe627e9e6f86c69cfd3d12b
commit 7669cbd0f06402573fe627e9e6f86c69cfd3d12b Author: Aymeric Wibo <[email protected]> AuthorDate: 2025-06-14 15:28:49 +0000 Commit: Aymeric Wibo <[email protected]> CommitDate: 2026-01-13 22:21:05 +0000 acpi: Suspend-to-idle support (s2idle) Implement STYPE_SUSPEND_TO_IDLE sleep type added in c43473dc9b83 ("sys/power: Generic sleep types"). This is a prerequisite for the firmware to enter the S0ix states. When suspending to idle, the system stays in an ACPI S0 state, but the CPUs are idled and devices are suspended/resumed before and after this as they would be when entering any other sleep type (except for AWAKE and POWEROFF). Factor out do_standby, do_sleep, and add a new do_idle function for idling the CPU (a future patch will make this an idle loop and not just a simple cpu_idle() call). In do_idle, SCIs (interrupt 9) are enabled to allow wake events to break the CPU out of idle. Record all the steps made instead of just the last one in slp_state, which allows for more flexible unwinding (will be useful to not have to goto breakout if the SPMC entry call fails when that is committed). A lot of this borrows from Ben Widawsky's patch: D17675. The main functional difference with that patch is that suspend-to-idle is a wholly separate sleep type in this one as opposed to being an alternative implementation for s2mem (S3). Reviewed by: emaste, olce Approved by: olce Sponsored by: The FreeBSD Foundation Differential Revision: https://reviews.freebsd.org/D48734 --- sys/dev/acpica/acpi.c | 278 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 185 insertions(+), 93 deletions(-) diff --git a/sys/dev/acpica/acpi.c b/sys/dev/acpica/acpi.c index 8380f701d226..9657938371f0 100644 --- a/sys/dev/acpica/acpi.c +++ b/sys/dev/acpica/acpi.c @@ -58,6 +58,7 @@ #if defined(__i386__) || defined(__amd64__) #include <machine/clock.h> +#include <machine/intr_machdep.h> #include <machine/pci_cfgreg.h> #include <x86/cputypes.h> #include <x86/x86_var.h> @@ -679,15 +680,19 @@ acpi_attach(device_t dev) #endif /* - * Probe all supported ACPI sleep states. Awake (S0) is always supported. + * Probe all supported ACPI sleep states. Awake (S0) is always supported, + * and suspend-to-idle is always supported on x86 only (at the moment). */ - acpi_supported_sstates[ACPI_STATE_S0] = TRUE; + acpi_supported_sstates[ACPI_STATE_S0] = true; acpi_supported_stypes[POWER_STYPE_AWAKE] = true; +#if defined(__i386__) || defined(__amd64__) + acpi_supported_stypes[POWER_STYPE_SUSPEND_TO_IDLE] = true; +#endif for (state = ACPI_STATE_S1; state <= ACPI_STATE_S5; state++) if (ACPI_SUCCESS(AcpiEvaluateObject(ACPI_ROOT_OBJECT, __DECONST(char *, AcpiGbl_SleepStateNames[state]), NULL, NULL)) && ACPI_SUCCESS(AcpiGetSleepTypeData(state, &TypeA, &TypeB))) { - acpi_supported_sstates[state] = TRUE; + acpi_supported_sstates[state] = true; acpi_supported_stypes[acpi_sstate_to_stype(state)] = true; } @@ -705,13 +710,24 @@ acpi_attach(device_t dev) else if (acpi_supported_sstates[ACPI_STATE_S2]) sc->acpi_standby_sx = ACPI_STATE_S2; - /* Pick the first valid sleep type for the sleep button default. */ + /* + * Pick the first valid sleep type for the sleep button default. If that + * type was hibernate and we support s2idle, set it to that. The sleep + * button prefers s2mem instead of s2idle at the moment as s2idle may not + * yet work reliably on all machines. In the future, we should set this to + * s2idle when ACPI_FADT_LOW_POWER_S0 is set. + */ sc->acpi_sleep_button_stype = POWER_STYPE_UNKNOWN; for (stype = POWER_STYPE_STANDBY; stype <= POWER_STYPE_HIBERNATE; stype++) if (acpi_supported_stypes[stype]) { sc->acpi_sleep_button_stype = stype; break; } + if (sc->acpi_sleep_button_stype == POWER_STYPE_HIBERNATE || + sc->acpi_sleep_button_stype == POWER_STYPE_UNKNOWN) { + if (acpi_supported_stypes[POWER_STYPE_SUSPEND_TO_IDLE]) + sc->acpi_sleep_button_stype = POWER_STYPE_SUSPEND_TO_IDLE; + } acpi_enable_fixed_events(sc); @@ -3315,7 +3331,8 @@ acpi_ReqSleepState(struct acpi_softc *sc, enum power_stype stype) return (0); #else - /* This platform does not support acpi suspend/resume. */ + device_printf(sc->acpi_dev, "ACPI suspend not supported on this platform " + "(TODO suspend to idle should be, however)\n"); return (EOPNOTSUPP); #endif } @@ -3330,13 +3347,13 @@ acpi_ReqSleepState(struct acpi_softc *sc, enum power_stype stype) int acpi_AckSleepState(struct apm_clone_data *clone, int error) { + struct acpi_softc *sc = clone->acpi_sc; + #if defined(__amd64__) || defined(__i386__) - struct acpi_softc *sc; int ret, sleeping; /* If no pending sleep type, return an error. */ ACPI_LOCK(acpi); - sc = clone->acpi_sc; if (sc->acpi_next_stype == POWER_STYPE_AWAKE) { ACPI_UNLOCK(acpi); return (ENXIO); @@ -3379,7 +3396,8 @@ acpi_AckSleepState(struct apm_clone_data *clone, int error) } return (ret); #else - /* This platform does not support acpi suspend/resume. */ + device_printf(sc->acpi_dev, "ACPI suspend not supported on this platform " + "(TODO suspend to idle should be, however)\n"); return (EOPNOTSUPP); #endif } @@ -3418,27 +3436,133 @@ acpi_sleep_disable(struct acpi_softc *sc) } enum acpi_sleep_state { - ACPI_SS_NONE, - ACPI_SS_GPE_SET, - ACPI_SS_DEV_SUSPEND, - ACPI_SS_SLP_PREP, - ACPI_SS_SLEPT, + ACPI_SS_NONE = 0, + ACPI_SS_GPE_SET = 1 << 0, + ACPI_SS_DEV_SUSPEND = 1 << 1, + ACPI_SS_SLP_PREP = 1 << 2, + ACPI_SS_SLEPT = 1 << 3, }; +static void +do_standby(struct acpi_softc *sc, enum acpi_sleep_state *slp_state, + register_t rflags) +{ + ACPI_STATUS status; + + status = AcpiEnterSleepState(sc->acpi_standby_sx); + intr_restore(rflags); + AcpiLeaveSleepStatePrep(sc->acpi_standby_sx); + if (ACPI_FAILURE(status)) { + device_printf(sc->acpi_dev, "AcpiEnterSleepState failed - %s\n", + AcpiFormatException(status)); + return; + } + *slp_state |= ACPI_SS_SLEPT; +} + +static void +do_sleep(struct acpi_softc *sc, enum acpi_sleep_state *slp_state, + register_t rflags, int state) +{ + int sleep_result; + ACPI_EVENT_STATUS power_button_status; + + MPASS(state == ACPI_STATE_S3 || state == ACPI_STATE_S4); + + sleep_result = acpi_sleep_machdep(sc, state); + acpi_wakeup_machdep(sc, state, sleep_result, 0); + + if (sleep_result == 1 && state == ACPI_STATE_S3) { + /* + * XXX According to ACPI specification SCI_EN bit should be restored + * by ACPI platform (BIOS, firmware) to its pre-sleep state. + * Unfortunately some BIOSes fail to do that and that leads to + * unexpected and serious consequences during wake up like a system + * getting stuck in SMI handlers. + * This hack is picked up from Linux, which claims that it follows + * Windows behavior. + */ + AcpiWriteBitRegister(ACPI_BITREG_SCI_ENABLE, ACPI_ENABLE_EVENT); + + /* + * Prevent misinterpretation of the wakeup by power button + * as a request for power off. + * Ideally we should post an appropriate wakeup event, + * perhaps using acpi_event_power_button_wake or alike. + * + * Clearing of power button status after wakeup is mandated + * by ACPI specification in section "Fixed Power Button". + * + * XXX As of ACPICA 20121114 AcpiGetEventStatus provides + * status as 0/1 corresponding to inactive/active despite + * its type being ACPI_EVENT_STATUS. In other words, + * we should not test for ACPI_EVENT_FLAG_SET for time being. + */ + if (ACPI_SUCCESS(AcpiGetEventStatus(ACPI_EVENT_POWER_BUTTON, + &power_button_status)) && power_button_status != 0) { + AcpiClearEvent(ACPI_EVENT_POWER_BUTTON); + device_printf(sc->acpi_dev, "cleared fixed power button status\n"); + } + } + + intr_restore(rflags); + + /* call acpi_wakeup_machdep() again with interrupt enabled */ + acpi_wakeup_machdep(sc, state, sleep_result, 1); + + AcpiLeaveSleepStatePrep(state); + + if (sleep_result == -1) + return; + + /* Re-enable ACPI hardware on wakeup from sleep state 4. */ + if (state == ACPI_STATE_S4) + AcpiEnable(); + *slp_state |= ACPI_SS_SLEPT; +} + +#if defined(__i386__) || defined(__amd64__) +static void +do_idle(struct acpi_softc *sc, enum acpi_sleep_state *slp_state, + register_t rflags) +{ + + intr_suspend(); + + /* + * The CPU will exit idle when interrupted, so we want to minimize the + * number of interrupts it can receive while idle. We do this by only + * allowing SCI (system control interrupt) interrupts, which are used by + * the ACPI firmware to send wake GPEs to the OS. + * + * XXX We might still receive other spurious non-wake GPEs from noisy + * devices that can't be disabled, so this will need to end up being a + * suspend-to-idle loop which, when breaking out of idle, will check the + * reason for the wakeup and immediately idle the CPU again if it was not a + * proper wake event. + */ + intr_enable_src(AcpiGbl_FADT.SciInterrupt); + + cpu_idle(0); + + intr_resume(false); + intr_restore(rflags); + *slp_state |= ACPI_SS_SLEPT; +} +#endif + /* * Enter the desired system sleep state. * - * Currently we support S1-S5 but S4 is only S4BIOS + * Currently we support S1-S5 and suspend-to-idle, but S4 is only S4BIOS. */ static ACPI_STATUS acpi_EnterSleepState(struct acpi_softc *sc, enum power_stype stype) { register_t intr; ACPI_STATUS status; - ACPI_EVENT_STATUS power_button_status; enum acpi_sleep_state slp_state; int acpi_sstate; - int sleep_result; ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, stype); @@ -3498,7 +3622,7 @@ acpi_EnterSleepState(struct acpi_softc *sc, enum power_stype stype) /* Enable any GPEs as appropriate and requested by the user. */ acpi_wake_prep_walk(sc, stype); - slp_state = ACPI_SS_GPE_SET; + slp_state |= ACPI_SS_GPE_SET; /* * Inform all devices that we are going to sleep. If at least one @@ -3509,113 +3633,81 @@ acpi_EnterSleepState(struct acpi_softc *sc, enum power_stype stype) * bus interface does not provide for this. */ if (DEVICE_SUSPEND(root_bus) != 0) { - device_printf(sc->acpi_dev, "device_suspend failed\n"); - goto backout; + device_printf(sc->acpi_dev, "device_suspend failed\n"); + goto backout; } - slp_state = ACPI_SS_DEV_SUSPEND; + slp_state |= ACPI_SS_DEV_SUSPEND; - status = AcpiEnterSleepStatePrep(acpi_sstate); - if (ACPI_FAILURE(status)) { - device_printf(sc->acpi_dev, "AcpiEnterSleepStatePrep failed - %s\n", - AcpiFormatException(status)); - goto backout; + if (stype != POWER_STYPE_SUSPEND_TO_IDLE) { + status = AcpiEnterSleepStatePrep(acpi_sstate); + if (ACPI_FAILURE(status)) { + device_printf(sc->acpi_dev, "AcpiEnterSleepStatePrep failed - %s\n", + AcpiFormatException(status)); + goto backout; + } } - slp_state = ACPI_SS_SLP_PREP; + slp_state |= ACPI_SS_SLP_PREP; if (sc->acpi_sleep_delay > 0) DELAY(sc->acpi_sleep_delay * 1000000); suspendclock(); intr = intr_disable(); - if (stype != POWER_STYPE_STANDBY) { - sleep_result = acpi_sleep_machdep(sc, acpi_sstate); - acpi_wakeup_machdep(sc, acpi_sstate, sleep_result, 0); - - /* - * XXX According to ACPI specification SCI_EN bit should be restored - * by ACPI platform (BIOS, firmware) to its pre-sleep state. - * Unfortunately some BIOSes fail to do that and that leads to - * unexpected and serious consequences during wake up like a system - * getting stuck in SMI handlers. - * This hack is picked up from Linux, which claims that it follows - * Windows behavior. - */ - if (sleep_result == 1 && stype != POWER_STYPE_HIBERNATE) - AcpiWriteBitRegister(ACPI_BITREG_SCI_ENABLE, ACPI_ENABLE_EVENT); - - if (sleep_result == 1 && stype == POWER_STYPE_SUSPEND_TO_MEM) { - /* - * Prevent mis-interpretation of the wakeup by power button - * as a request for power off. - * Ideally we should post an appropriate wakeup event, - * perhaps using acpi_event_power_button_wake or alike. - * - * Clearing of power button status after wakeup is mandated - * by ACPI specification in section "Fixed Power Button". - * - * XXX As of ACPICA 20121114 AcpiGetEventStatus provides - * status as 0/1 corressponding to inactive/active despite - * its type being ACPI_EVENT_STATUS. In other words, - * we should not test for ACPI_EVENT_FLAG_SET for time being. - */ - if (ACPI_SUCCESS(AcpiGetEventStatus(ACPI_EVENT_POWER_BUTTON, - &power_button_status)) && power_button_status != 0) { - AcpiClearEvent(ACPI_EVENT_POWER_BUTTON); - device_printf(sc->acpi_dev, - "cleared fixed power button status\n"); - } - } - - intr_restore(intr); - - /* call acpi_wakeup_machdep() again with interrupt enabled */ - acpi_wakeup_machdep(sc, acpi_sstate, sleep_result, 1); - - AcpiLeaveSleepStatePrep(acpi_sstate); - - if (sleep_result == -1) - goto backout; - - /* Re-enable ACPI hardware on wakeup from hibernate. */ - if (stype == POWER_STYPE_HIBERNATE) - AcpiEnable(); - } else { - status = AcpiEnterSleepState(acpi_sstate); - intr_restore(intr); - AcpiLeaveSleepStatePrep(acpi_sstate); - if (ACPI_FAILURE(status)) { - device_printf(sc->acpi_dev, "AcpiEnterSleepState failed - %s\n", - AcpiFormatException(status)); - goto backout; - } + switch (stype) { + case POWER_STYPE_STANDBY: + do_standby(sc, &slp_state, intr); + break; + case POWER_STYPE_SUSPEND_TO_MEM: + case POWER_STYPE_HIBERNATE: + do_sleep(sc, &slp_state, intr, acpi_sstate); + break; + case POWER_STYPE_SUSPEND_TO_IDLE: +#if defined(__i386__) || defined(__amd64__) + do_idle(sc, &slp_state, intr); + break; +#endif + case POWER_STYPE_AWAKE: + case POWER_STYPE_POWEROFF: + case POWER_STYPE_COUNT: + case POWER_STYPE_UNKNOWN: + __unreachable(); } - slp_state = ACPI_SS_SLEPT; /* * Back out state according to how far along we got in the suspend * process. This handles both the error and success cases. */ backout: - if (slp_state >= ACPI_SS_SLP_PREP) + if ((slp_state & ACPI_SS_SLP_PREP) != 0) { resumeclock(); - if (slp_state >= ACPI_SS_GPE_SET) { + slp_state &= ~ACPI_SS_SLP_PREP; + } + if ((slp_state & ACPI_SS_GPE_SET) != 0) { acpi_wake_prep_walk(sc, stype); sc->acpi_stype = POWER_STYPE_AWAKE; + slp_state &= ~ACPI_SS_GPE_SET; } - if (slp_state >= ACPI_SS_DEV_SUSPEND) + if ((slp_state & ACPI_SS_DEV_SUSPEND) != 0) { DEVICE_RESUME(root_bus); - if (slp_state >= ACPI_SS_SLP_PREP) + slp_state &= ~ACPI_SS_DEV_SUSPEND; + } + if (stype != POWER_STYPE_SUSPEND_TO_IDLE && (slp_state & ACPI_SS_SLP_PREP) != 0) { AcpiLeaveSleepState(acpi_sstate); - if (slp_state >= ACPI_SS_SLEPT) { + slp_state &= ~ACPI_SS_SLP_PREP; + } + if ((slp_state & ACPI_SS_SLEPT) != 0) { #if defined(__i386__) || defined(__amd64__) /* NB: we are still using ACPI timecounter at this point. */ resume_TSC(); #endif acpi_resync_clock(sc); acpi_enable_fixed_events(sc); + slp_state &= ~ACPI_SS_SLEPT; } sc->acpi_next_stype = POWER_STYPE_AWAKE; + MPASS(slp_state == ACPI_SS_NONE); + bus_topo_unlock(); #ifdef EARLY_AP_STARTUP
