diff --git a/sys/dev/acpica/acpi.c b/sys/dev/acpica/acpi.c --- a/sys/dev/acpica/acpi.c +++ b/sys/dev/acpica/acpi.c @@ -53,6 +53,7 @@ #include #include #include +#include #include #include @@ -61,6 +62,7 @@ #include #include #include +#include #include #endif #include @@ -3562,10 +3564,25 @@ } #if defined(__i386__) || defined(__amd64__) +static void +set_cpu_idle(void *data) +{ + /* XXX sched_do_idle is only implemented in ULE at the moment. */ +#if defined(SCHED_ULE) + bool idle = *(bool*) data; + + sched_do_idle(curthread, idle); +#endif +} + static void do_idle(struct acpi_softc *sc, enum acpi_sleep_state *slp_state, register_t rflags) { + struct pcpu *pc; + cpuset_t other_cpus; + struct monitorbuf *mb; + size_t intr_count; intr_suspend(); @@ -3573,7 +3590,7 @@ * 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. + * the ACPI firmware to send wake GPEs to OSPM. * * 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 @@ -3583,8 +3600,69 @@ */ intr_enable_src(AcpiGbl_FADT.SciInterrupt); - cpu_idle(0); + sc->acpi_s2idle_wake = false; + + /* + * Put all CPUs (except for this one, the BSP) in their idle loops. They + * will immediately be preempted in sched_do_idle(). + */ + other_cpus = all_cpus; + CPU_CLR(curcpu, &other_cpus); + + bool idle = true; + smp_rendezvous_cpus(other_cpus, NULL, set_cpu_idle, NULL, &idle); + + /* + * Suspend-to-idle loop. + * + * This is needed, because we might still receive other spurious non-wake + * GPEs (and, thus, SCI interrupts) from devices that can't be disabled. + * When breaking out of idle, we check the reason for the wakeup and + * immediately idle the CPU again if it was not a proper wake event. + */ + for (intr_count = 0; !sc->acpi_s2idle_wake; intr_count++) { + /* + * Actually idle the main CPU. The only thing that will break us out of + * this at this point is an SCI interrupt. + */ + cpu_idle(0); + + /* + * When we get an SCI from the platform, we don't actually handle the + * GPE in the interrupt handler. Instead, it gets added to the + * acpi_taskq taskqueue. Since we are interested in what this GPE is + * (since it might we a wake event), we must wait until it has been + * drained so we know if we should break out of the s2idle loop or if + * we should reenter idle. + * + * XXX This is buggy because we're breaking out to the scheduler, and + * other kernel threads might be scheduled and prevent us from getting + * scheduled again in a timely manner (or, theoretically, at all). + */ + extern struct taskqueue *acpi_taskq; + taskqueue_quiesce(acpi_taskq); + + /* + * Check that all CPUs are still idled, and warn if not. + */ + STAILQ_FOREACH(pc, &cpuhead, pc_allcpu) { + if (!CPU_ISSET(pc->pc_cpuid, &other_cpus)) + continue; + mb = &pc->pc_monitorbuf; + if (atomic_load_int(&mb->idle_state) != 0x2 /* TODO STATE_SLEEPING */) + printf("CPU%d was not sleeping after breaking out of BSP " + "cpu_idle()!\n", pc->pc_cpuid); + } + } + printf("Suspend-to-idle interrupted %zu times.\n", intr_count); + /* Unidle all other CPUs. The schedulers will immediately be preempted. */ + idle = false; + smp_rendezvous_cpus(other_cpus, NULL, set_cpu_idle, NULL, &idle); + + /* + * Resume interrupts. + */ intr_resume(false); intr_restore(rflags); *slp_state |= ACPI_SS_SLEPT; @@ -4132,10 +4210,11 @@ static void acpi_system_eventhandler_wakeup(void *arg, enum power_stype stype) { + struct acpi_softc *sc = (struct acpi_softc *)arg; ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, stype); - /* Currently, nothing to do for wakeup. */ + sc->acpi_s2idle_wake = true; return_VOID; } @@ -4168,6 +4247,8 @@ ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + sc->acpi_s2idle_wake = true; + #if defined(__amd64__) || defined(__i386__) if (ACPI_FAILURE(AcpiOsExecute(OSL_NOTIFY_HANDLER, acpi_invoke_sleep_eventhandler, &sc->acpi_power_button_stype))) @@ -4186,6 +4267,8 @@ ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + sc->acpi_s2idle_wake = true; + if (ACPI_FAILURE(AcpiOsExecute(OSL_NOTIFY_HANDLER, acpi_invoke_wake_eventhandler, &sc->acpi_power_button_stype))) return_VALUE (ACPI_INTERRUPT_NOT_HANDLED); @@ -4199,6 +4282,8 @@ ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + sc->acpi_s2idle_wake = true; + if (ACPI_FAILURE(AcpiOsExecute(OSL_NOTIFY_HANDLER, acpi_invoke_sleep_eventhandler, &sc->acpi_sleep_button_stype))) return_VALUE (ACPI_INTERRUPT_NOT_HANDLED); @@ -4212,6 +4297,8 @@ ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); + sc->acpi_s2idle_wake = true; + if (ACPI_FAILURE(AcpiOsExecute(OSL_NOTIFY_HANDLER, acpi_invoke_wake_eventhandler, &sc->acpi_sleep_button_stype))) return_VALUE (ACPI_INTERRUPT_NOT_HANDLED); diff --git a/sys/dev/acpica/acpivar.h b/sys/dev/acpica/acpivar.h --- a/sys/dev/acpica/acpivar.h +++ b/sys/dev/acpica/acpivar.h @@ -56,6 +56,7 @@ int acpi_enabled; enum power_stype acpi_stype; int acpi_sleep_disabled; + bool acpi_s2idle_wake; struct sysctl_ctx_list acpi_sysctl_ctx; struct sysctl_oid *acpi_sysctl_tree;