Implement the guts for "suspend-to-idle" on amd64. This enables suspend
authorkettenis <kettenis@openbsd.org>
Wed, 29 May 2024 12:21:33 +0000 (12:21 +0000)
committerkettenis <kettenis@openbsd.org>
Wed, 29 May 2024 12:21:33 +0000 (12:21 +0000)
on machines that don't support S3.  In its current state it doesn't save
a lot of power, but this should improve over time.  Implementation of
wakeup methods is incomplete which means that some machine can't resume
at the moment.

ok mglocker@, mlarkin@, stsp@, deraadt@

sys/arch/amd64/amd64/acpi_machdep.c
sys/arch/amd64/amd64/cpu.c
sys/arch/amd64/amd64/intr.c
sys/arch/amd64/include/cpu.h
sys/arch/i386/i386/cpu.c
sys/dev/acpi/acpi.c
sys/dev/acpi/acpi_x86.c
sys/dev/acpi/tpm.c

index bf7add7..2403f03 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: acpi_machdep.c,v 1.109 2024/05/26 13:37:31 kettenis Exp $     */
+/*     $OpenBSD: acpi_machdep.c,v 1.110 2024/05/29 12:21:33 kettenis Exp $     */
 /*
  * Copyright (c) 2005 Thorsten Lockert <tholo@sigmasoft.com>
  *
@@ -377,6 +377,9 @@ acpi_attach_machdep(struct acpi_softc *sc)
 int
 acpi_sleep_cpu(struct acpi_softc *sc, int state)
 {
+       if (state == ACPI_STATE_S0)
+               return cpu_suspend_primary();
+
        rtcstop();
 #if NLAPIC > 0
        lapic_disable();
@@ -458,6 +461,9 @@ acpi_sleep_cpu(struct acpi_softc *sc, int state)
 void
 acpi_resume_cpu(struct acpi_softc *sc, int state)
 {
+       if (state == ACPI_STATE_S0)
+               return;
+
        cpu_init_msrs(&cpu_info_primary);
        cpu_fix_msrs(&cpu_info_primary);
 
index 2eabcbe..6714c02 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: cpu.c,v 1.188 2024/05/14 01:42:07 guenther Exp $      */
+/*     $OpenBSD: cpu.c,v 1.189 2024/05/29 12:21:33 kettenis Exp $      */
 /* $NetBSD: cpu.c,v 1.1 2003/04/26 18:39:26 fvdl Exp $ */
 
 /*-
@@ -1461,3 +1461,52 @@ wbinvd_on_all_cpus(void)
        return 0;
 }
 #endif
+
+int cpu_suspended;
+
+#ifdef SUSPEND
+
+void
+cpu_suspend_cycle(void)
+{
+       cpu_idle_cycle_fcn();
+}
+
+int
+cpu_suspend_primary(void)
+{
+       struct cpu_info *ci = curcpu();
+       int count = 0;
+
+       printf("suspend\n");
+
+       /* Mask clock interrupts. */
+       local_pic.pic_hwmask(&local_pic, 0);
+
+       /*
+        * All non-wakeup interrupts should be masked at this point;
+        * re-enable interrupts such that wakeup interrupts actually
+        * wake us up.  Set a flag such that drivers can tell we're
+        * suspended and change their behaviour accordingly.  They can
+        * wake us up by clearing the flag.
+        */
+       cpu_suspended = 1;
+       ci->ci_ilevel = IPL_NONE;
+       intr_enable();
+
+       while (cpu_suspended) {
+               cpu_suspend_cycle();
+               count++;
+       }
+
+       intr_disable();
+       ci->ci_ilevel = IPL_HIGH;
+
+       /* Unmask clock interrupts. */
+       local_pic.pic_hwunmask(&local_pic, 0);
+
+       printf("resume %d\n", count);
+       return 0;
+}
+
+#endif
index 9b1182e..5865f0b 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: intr.c,v 1.57 2024/05/26 13:37:31 kettenis Exp $      */
+/*     $OpenBSD: intr.c,v 1.58 2024/05/29 12:21:33 kettenis Exp $      */
 /*     $NetBSD: intr.c,v 1.3 2003/03/03 22:16:20 fvdl Exp $    */
 
 /*
@@ -524,12 +524,22 @@ intr_disestablish(struct intrhand *ih)
 int
 intr_handler(struct intrframe *frame, struct intrhand *ih)
 {
+       extern int cpu_suspended;
        struct cpu_info *ci = curcpu();
        int floor;
        int rc;
 #ifdef MULTIPROCESSOR
        int need_lock;
+#endif
 
+       /*
+        * We may not be able to mask MSIs, so block non-wakeup
+        * interrupts while we're suspended.
+        */
+       if (cpu_suspended && (ih->ih_flags & IPL_WAKEUP) == 0)
+               return 0;
+
+#ifdef MULTIPROCESSOR
        if (ih->ih_flags & IPL_MPSAFE)
                need_lock = 0;
        else
index 57e3c62..fab34e5 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: cpu.h,v 1.170 2024/05/21 23:16:06 jsg Exp $   */
+/*     $OpenBSD: cpu.h,v 1.171 2024/05/29 12:21:33 kettenis Exp $      */
 /*     $NetBSD: cpu.h,v 1.1 2003/04/26 18:39:39 fvdl Exp $     */
 
 /*-
@@ -401,6 +401,8 @@ extern int cpu_meltdown;
 extern u_int cpu_mwait_size;
 extern u_int cpu_mwait_states;
 
+int    cpu_suspend_primary(void);
+
 /* cacheinfo.c */
 void   x86_print_cacheinfo(struct cpu_info *);
 
index 3b01d17..9e891cb 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: cpu.c,v 1.114 2023/10/24 13:20:10 claudio Exp $       */
+/*     $OpenBSD: cpu.c,v 1.115 2024/05/29 12:21:33 kettenis Exp $      */
 /* $NetBSD: cpu.c,v 1.1.2.7 2000/06/26 02:04:05 sommerfeld Exp $ */
 
 /*-
@@ -925,3 +925,5 @@ wbinvd_on_all_cpus(void)
        return 0;
 }
 #endif
+
+int cpu_suspended;
index f53c84e..71c30b5 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: acpi.c,v 1.428 2024/05/13 19:56:37 kettenis Exp $ */
+/* $OpenBSD: acpi.c,v 1.429 2024/05/29 12:21:33 kettenis Exp $ */
 /*
  * Copyright (c) 2005 Thorsten Lockert <tholo@sigmasoft.com>
  * Copyright (c) 2005 Jordan Hargrave <jordan@openbsd.org>
@@ -2085,6 +2085,7 @@ acpi_powerdown_task(void *arg0, int dummy)
 int
 acpi_interrupt(void *arg)
 {
+       extern int cpu_suspended;
        struct acpi_softc *sc = (struct acpi_softc *)arg;
        uint32_t processed = 0, idx, jdx;
        uint16_t sts, en;
@@ -2137,6 +2138,9 @@ acpi_interrupt(void *arg)
                            ACPI_PM1_PWRBTN_STS);
                        sts &= ~ACPI_PM1_PWRBTN_STS;
 
+                       if (cpu_suspended)
+                               cpu_suspended = 0;
+
                        acpi_addtask(sc, acpi_pbtn_task, sc, 0);
                }
                if (sts & ACPI_PM1_SLPBTN_STS) {
index e8a8efd..97def00 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: acpi_x86.c,v 1.20 2024/05/28 09:40:40 kettenis Exp $ */
+/* $OpenBSD: acpi_x86.c,v 1.21 2024/05/29 12:21:33 kettenis Exp $ */
 /*
  * Copyright (c) 2005 Thorsten Lockert <tholo@sigmasoft.com>
  * Copyright (c) 2005 Jordan Hargrave <jordan@openbsd.org>
@@ -31,13 +31,18 @@ int
 sleep_showstate(void *v, int sleepmode)
 {
        struct acpi_softc *sc = v;
+       int fallback_state = -1;
 
        switch (sleepmode) {
        case SLEEP_SUSPEND:
                sc->sc_state = ACPI_STATE_S3;
+#ifdef __amd64__
+               fallback_state = ACPI_STATE_S0; /* No S3, use S0 */
+#endif
                break;
        case SLEEP_HIBERNATE:
                sc->sc_state = ACPI_STATE_S4;
+               fallback_state = ACPI_STATE_S5; /* No S4, use S5 */
                break;
        default:
                return (EOPNOTSUPP);
@@ -45,10 +50,10 @@ sleep_showstate(void *v, int sleepmode)
 
        if (sc->sc_sleeptype[sc->sc_state].slp_typa == -1 ||
            sc->sc_sleeptype[sc->sc_state].slp_typb == -1) {
-               if (sc->sc_state == ACPI_STATE_S4) {
-                       sc->sc_state = ACPI_STATE_S5;   /* No S4, use S5 */
-                       printf("%s: S4 unavailable, using S5\n",
-                           sc->sc_dev.dv_xname);
+               if (fallback_state != -1) {
+                       printf("%s: S%d unavailable, using S%d\n",
+                           sc->sc_dev.dv_xname, sc->sc_state, fallback_state);
+                       sc->sc_state = fallback_state;
                } else {
                        printf("%s: state S%d unavailable\n",
                            sc->sc_dev.dv_xname, sc->sc_state);
@@ -57,8 +62,10 @@ sleep_showstate(void *v, int sleepmode)
        }
 
        /* 1st suspend AML step: _TTS(tostate) */
-       if (aml_node_setval(sc, sc->sc_tts, sc->sc_state) != 0)
-               return (EINVAL);
+       if (sc->sc_state != ACPI_STATE_S0) {
+               if (aml_node_setval(sc, sc->sc_tts, sc->sc_state) != 0)
+                       return (EINVAL);
+       }
        acpi_indicator(sc, ACPI_SST_WAKING);    /* blink */
        return 0;
 }
@@ -69,8 +76,10 @@ sleep_setstate(void *v)
        struct acpi_softc *sc = v;
 
        /* 2nd suspend AML step: _PTS(tostate) */
-       if (aml_node_setval(sc, sc->sc_pts, sc->sc_state) != 0)
-               return (EINVAL);
+       if (sc->sc_state != ACPI_STATE_S0) {
+               if (aml_node_setval(sc, sc->sc_pts, sc->sc_state) != 0)
+                       return (EINVAL);
+       }
        acpi_indicator(sc, ACPI_SST_WAKING);    /* blink */
        return 0;
 }
@@ -85,7 +94,8 @@ gosleep(void *v)
        acpi_indicator(sc, ACPI_SST_SLEEPING);
 
        /* 3rd suspend AML step: _GTS(tostate) */
-       aml_node_setval(sc, sc->sc_gts, sc->sc_state);
+       if (sc->sc_state != ACPI_STATE_S0)
+               aml_node_setval(sc, sc->sc_gts, sc->sc_state);
 
        /* Clear fixed event status */
        acpi_write_pmreg(sc, ACPIREG_PM1_STS, 0, ACPI_PM1_ALL_STS);
@@ -110,8 +120,10 @@ sleep_resume(void *v)
        acpibtn_disable_psw();          /* disable _LID for wakeup */
 
        /* 3rd resume AML step: _TTS(runstate) */
-       if (aml_node_setval(sc, sc->sc_tts, ACPI_STATE_S0) != 0)
-               return (EINVAL);
+       if (sc->sc_state != ACPI_STATE_S0) {
+               if (aml_node_setval(sc, sc->sc_tts, ACPI_STATE_S0) != 0)
+                       return (EINVAL);
+       }
        acpi_indicator(sc, ACPI_SST_WAKING);    /* blink */
        return 0;
 }
index e4bf6dc..9bd79fe 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: tpm.c,v 1.19 2024/05/13 01:15:50 jsg Exp $ */
+/* $OpenBSD: tpm.c,v 1.20 2024/05/29 12:21:33 kettenis Exp $ */
 
 /*
  * Minimal interface to Trusted Platform Module chips implementing the
@@ -376,6 +376,9 @@ tpm_suspend(struct tpm_softc *sc)
        uint8_t *command;
        size_t commandlen;
 
+       if (sc->sc_acpi->sc_state == ACPI_STATE_S0)
+               return 0;
+
        DPRINTF(("%s: saving state preparing for suspend\n",
            sc->sc_dev.dv_xname));