Index: sys/dev/acpica/Osd/OsdInterrupt.c =================================================================== --- sys/dev/acpica/Osd/OsdInterrupt.c +++ sys/dev/acpica/Osd/OsdInterrupt.c @@ -218,3 +218,9 @@ InterruptOverride = InterruptNumber; return_ACPI_STATUS (AE_OK); } + +UINT32 +acpi_GetSciInterrupt(void) +{ + return InterruptOverride ?: AcpiGbl_FADT.SciInterrupt; +} Index: sys/dev/acpica/acpi.c =================================================================== --- sys/dev/acpica/acpi.c +++ sys/dev/acpica/acpi.c @@ -54,6 +54,7 @@ #if defined(__i386__) || defined(__amd64__) #include #include +#include #endif #include #include @@ -95,6 +96,14 @@ int num; }; +enum s2idle_enable { + POWER_BUTTON_S2I, + SLEEP_BUTTON_S2I, + LID_SWITCH_S2I, + STANDBY_S2I, + SUSPEND_S2I +}; + static char *sysres_ids[] = { "PNP0C01", "PNP0C02", NULL }; static char *pcilink_ids[] = { "PNP0C0F", NULL }; @@ -157,7 +166,7 @@ void *context, void **status); static void acpi_sleep_enable(void *arg); static ACPI_STATUS acpi_sleep_disable(struct acpi_softc *sc); -static ACPI_STATUS acpi_EnterSleepState(struct acpi_softc *sc, int state); +static ACPI_STATUS acpi_EnterSleepState(struct acpi_softc *sc, enum sleep_type stype); static void acpi_shutdown_final(void *arg, int howto); static void acpi_enable_fixed_events(struct acpi_softc *sc); static BOOLEAN acpi_has_hid(ACPI_HANDLE handle); @@ -167,8 +176,9 @@ static int acpi_wake_prep_walk(int sstate); static int acpi_wake_sysctl_walk(device_t dev); static int acpi_wake_set_sysctl(SYSCTL_HANDLER_ARGS); -static void acpi_system_eventhandler_sleep(void *arg, int state); -static void acpi_system_eventhandler_wakeup(void *arg, int state); +static void acpi_system_eventhandler_sleep(void *arg, enum sleep_type type); +static void acpi_system_eventhandler_wakeup(void *arg, enum sleep_type type); +static int acpi_stype2sstate(enum sleep_type type); static int acpi_sname2sstate(const char *sname); static const char *acpi_sstate2sname(int sstate); static int acpi_supported_sleep_state_sysctl(SYSCTL_HANDLER_ARGS); @@ -561,22 +571,22 @@ "List supported ACPI sleep states."); SYSCTL_ADD_PROC(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), OID_AUTO, "power_button_state", CTLTYPE_STRING | CTLFLAG_RW, - &sc->acpi_power_button_sx, 0, acpi_sleep_state_sysctl, "A", - "Power button ACPI sleep state."); + &sc->acpi_power_button.sx, POWER_BUTTON_S2I, acpi_sleep_state_sysctl, + "A", "Power button ACPI sleep state."); SYSCTL_ADD_PROC(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), OID_AUTO, "sleep_button_state", CTLTYPE_STRING | CTLFLAG_RW, - &sc->acpi_sleep_button_sx, 0, acpi_sleep_state_sysctl, "A", - "Sleep button ACPI sleep state."); + &sc->acpi_sleep_button.sx, SLEEP_BUTTON_S2I, acpi_sleep_state_sysctl, + "A", "Sleep button ACPI sleep state."); SYSCTL_ADD_PROC(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), OID_AUTO, "lid_switch_state", CTLTYPE_STRING | CTLFLAG_RW, - &sc->acpi_lid_switch_sx, 0, acpi_sleep_state_sysctl, "A", + &sc->acpi_lid_switch.sx, LID_SWITCH_S2I, acpi_sleep_state_sysctl, "A", "Lid ACPI sleep state. Set to S3 if you want to suspend your laptop when close the Lid."); SYSCTL_ADD_PROC(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), OID_AUTO, "standby_state", CTLTYPE_STRING | CTLFLAG_RW, - &sc->acpi_standby_sx, 0, acpi_sleep_state_sysctl, "A", ""); + &sc->acpi_standby.sx, STANDBY_S2I, acpi_sleep_state_sysctl, "A", ""); SYSCTL_ADD_PROC(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), OID_AUTO, "suspend_state", CTLTYPE_STRING | CTLFLAG_RW, - &sc->acpi_suspend_sx, 0, acpi_sleep_state_sysctl, "A", ""); + &sc->acpi_suspend.sx, SUSPEND_S2I, acpi_sleep_state_sysctl, "A", ""); SYSCTL_ADD_INT(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), OID_AUTO, "sleep_delay", CTLFLAG_RW, &sc->acpi_sleep_delay, 0, "sleep delay in seconds"); @@ -626,19 +636,19 @@ * Dispatch the default sleep state to devices. The lid switch is set * to UNKNOWN by default to avoid surprising users. */ - sc->acpi_power_button_sx = acpi_sleep_states[ACPI_STATE_S5] ? + sc->acpi_power_button.sx = acpi_sleep_states[ACPI_STATE_S5] ? ACPI_STATE_S5 : ACPI_STATE_UNKNOWN; - sc->acpi_lid_switch_sx = ACPI_STATE_UNKNOWN; - sc->acpi_standby_sx = acpi_sleep_states[ACPI_STATE_S1] ? + sc->acpi_lid_switch.sx = ACPI_STATE_UNKNOWN; + sc->acpi_standby.sx = acpi_sleep_states[ACPI_STATE_S1] ? ACPI_STATE_S1 : ACPI_STATE_UNKNOWN; - sc->acpi_suspend_sx = acpi_sleep_states[ACPI_STATE_S3] ? + sc->acpi_suspend.sx = acpi_sleep_states[ACPI_STATE_S3] ? ACPI_STATE_S3 : ACPI_STATE_UNKNOWN; /* Pick the first valid sleep state for the sleep button default. */ - sc->acpi_sleep_button_sx = ACPI_STATE_UNKNOWN; + sc->acpi_sleep_button.sx = ACPI_STATE_UNKNOWN; for (state = ACPI_STATE_S1; state <= ACPI_STATE_S4; state++) if (acpi_sleep_states[state]) { - sc->acpi_sleep_button_sx = state; + sc->acpi_sleep_button.sx = state; break; } @@ -663,8 +673,8 @@ /* Flag our initial states. */ sc->acpi_enabled = TRUE; - sc->acpi_sstate = ACPI_STATE_S0; - sc->acpi_sleep_disabled = TRUE; + sc->acpi_sstate = AWAKE; + sc->acpi_repressed_states.flags |= 1 << ACPI_SLEEP_DISABLED; /* Create the control device */ sc->acpi_dev_t = make_dev(&acpi_cdevsw, 0, UID_ROOT, GID_OPERATOR, 0664, @@ -1704,7 +1714,10 @@ * Note illegal _S0D is evaluated because some systems expect this. */ sc = device_get_softc(bus); - snprintf(sxd, sizeof(sxd), "_S%dD", sc->acpi_sstate); + + snprintf(sxd, sizeof(sxd), "_S%dD", + sc->acpi_sstate != SUSPEND_TO_IDLE ?: SUSPEND); + MPASS(sxd[2] < '4'); /* only up to S3 supported */ status = acpi_GetInteger(handle, sxd, dstate); if (ACPI_FAILURE(status) && status != AE_NOT_FOUND) { device_printf(dev, "failed to get %s on %s: %s\n", sxd, @@ -2751,7 +2764,8 @@ "warning: acpi_SetSleepState() deprecated, need to update your software\n"); once = 1; } - return (acpi_EnterSleepState(sc, state)); + MPASS(state < ACPI_S_STATES_MAX); + return (acpi_EnterSleepState(sc, (enum sleep_type) state)); } #if defined(__amd64__) || defined(__i386__) @@ -2780,8 +2794,58 @@ acpi_sleep_force_task, sc))) device_printf(sc->acpi_dev, "AcpiOsExecute() for sleeping failed\n"); } + +static enum sleep_type +select_sleep_type(struct acpi_softc *sc, int state) +{ + MPASS(state == ACPI_STATE_S3 || state == ACPI_STATE_S1); + + /* The user explicitly requested */ + if (state == ACPI_STATE_S3 && + sc->acpi_suspend.sx == SUSPEND_TO_IDLE) + return SUSPEND_TO_IDLE; + + if (state == ACPI_STATE_S1 && + sc->acpi_standby.sx == SUSPEND_TO_IDLE) + return SUSPEND_TO_IDLE; + + /* The system supports the HW state, and that's probably best */ + if (acpi_sleep_states[state]) + return (enum sleep_type)state; + + /* idle is the best we can do. */ + device_printf(sc->acpi_dev, + "Requested S%d, but using S0IDLE instead\n", state); + return SUSPEND_TO_IDLE; +} +#else +static enum sleep_type +select_sleep_type(struct acpi_softc *sc) +{ + return AWAKE; +} #endif +/* Given a sleep type and a state, figures out if an error is warranted */ +static int +sanitize_sstate(struct acpi_softc *sc, enum sleep_type stype, int state, + bool acpi_err) +{ + if (stype == SUSPEND_TO_IDLE) + goto out; + + if (state < ACPI_STATE_S1 || state > ACPI_S_STATES_MAX) + return acpi_err ? (AE_BAD_PARAMETER) : (EINVAL); + if (!acpi_sleep_states[state]) { + device_printf(sc->acpi_dev, + "Sleep state S%d not supported by BIOS\n", state); + return acpi_err ? (AE_SUPPORT) : (EOPNOTSUPP); + } + +out: + return acpi_err ? (AE_OK) : (0); +} + /* * Request that the system enter the given suspend state. All /dev/apm * devices and devd(8) will be notified. Userland then has a chance to @@ -2789,38 +2853,40 @@ * acks are in. */ int -acpi_ReqSleepState(struct acpi_softc *sc, int state) +acpi_ReqSleepState(struct acpi_softc *sc, enum sleep_type stype) { #if defined(__amd64__) || defined(__i386__) struct apm_clone_data *clone; ACPI_STATUS status; + int sstate, err; - if (state < ACPI_STATE_S1 || state > ACPI_S_STATES_MAX) - return (EINVAL); - if (!acpi_sleep_states[state]) - return (EOPNOTSUPP); + sstate = acpi_stype2sstate(stype); + + err = sanitize_sstate(sc, stype, sstate, false); + if (err) + return err; /* * If a reboot/shutdown/suspend request is already in progress or * suspend is blocked due to an upcoming shutdown, just return. */ - if (rebooting || sc->acpi_next_sstate != 0 || suspend_blocked) { + if (rebooting || sc->acpi_next_sstate != AWAKE || suspend_blocked) { return (0); } /* Wait until sleep is enabled. */ - while (sc->acpi_sleep_disabled) { + while (sc->acpi_repressed_states.flags & (1 <acpi_next_sstate = state; + sc->acpi_next_sstate = stype; /* S5 (soft-off) should be entered directly with no waiting. */ - if (state == ACPI_STATE_S5) { + if (stype == POWEROFF) { ACPI_UNLOCK(acpi); - status = acpi_EnterSleepState(sc, state); + status = acpi_EnterSleepState(sc, stype); return (ACPI_SUCCESS(status) ? 0 : ENXIO); } @@ -2836,7 +2902,7 @@ /* If devd(8) is not running, immediately enter the sleep state. */ if (!devctl_process_running()) { ACPI_UNLOCK(acpi); - status = acpi_EnterSleepState(sc, state); + status = acpi_EnterSleepState(sc, stype); return (ACPI_SUCCESS(status) ? 0 : ENXIO); } @@ -2851,11 +2917,14 @@ ACPI_UNLOCK(acpi); /* Now notify devd(8) also. */ - acpi_UserNotify("Suspend", ACPI_ROOT_OBJECT, state); + acpi_UserNotify("Suspend", ACPI_ROOT_OBJECT, stype); return (0); #else - /* This platform does not support acpi suspend/resume. */ + /* + * This platform does not support acpi suspend/resume. + * TODO: Support s2idle here since it's platform agnostic. + */ return (EOPNOTSUPP); #endif } @@ -2877,14 +2946,14 @@ /* If no pending sleep state, return an error. */ ACPI_LOCK(acpi); sc = clone->acpi_sc; - if (sc->acpi_next_sstate == 0) { + if (sc->acpi_next_sstate == AWAKE) { ACPI_UNLOCK(acpi); return (ENXIO); } /* Caller wants to abort suspend process. */ if (error) { - sc->acpi_next_sstate = 0; + sc->acpi_next_sstate = AWAKE; callout_stop(&sc->susp_force_to); device_printf(sc->acpi_dev, "listener on %s cancelled the pending suspend\n", @@ -2919,7 +2988,10 @@ } return (ret); #else - /* This platform does not support acpi suspend/resume. */ + /* + * This platform does not support acpi suspend/resume. + * TODO: Support s2idle here since it's platform agnostic. + */ return (EOPNOTSUPP); #endif } @@ -2937,24 +3009,20 @@ return; } - sc->acpi_sleep_disabled = FALSE; + atomic_clear_int(&sc->acpi_repressed_states.flags, 1 << ACPI_SLEEP_DISABLED); } static ACPI_STATUS acpi_sleep_disable(struct acpi_softc *sc) { - ACPI_STATUS status; - /* Fail if the system is not fully up and running. */ if (!AcpiGbl_SystemAwakeAndRunning) return (AE_ERROR); - ACPI_LOCK(acpi); - status = sc->acpi_sleep_disabled ? AE_ERROR : AE_OK; - sc->acpi_sleep_disabled = TRUE; - ACPI_UNLOCK(acpi); + if (atomic_testandset_int(&sc->acpi_repressed_states.flags, ACPI_SLEEP_DISABLED)) + return AE_ERROR; - return (status); + return AE_OK; } enum acpi_sleep_state { @@ -2965,29 +3033,119 @@ ACPI_SS_SLEPT, }; +static void +acpi_state_transition_disable(struct acpi_softc *sc) +{ + int tmp; + tmp = atomic_swap_int(&sc->acpi_repressed_states.flags, ~0); + + /* Disallow re-entrancy. Disabling sleep with this is allowed though. */ + if (tmp & ~(1 << ACPI_SLEEP_DISABLED)) { + device_printf(sc->acpi_dev, "Invalid saved power flags"); + tmp &= (1 << ACPI_SLEEP_DISABLED); + } + sc->acpi_repressed_states.saved_flags = tmp; +} + +static void +acpi_state_transition_enable(struct acpi_softc *sc) +{ +#ifdef INVARIANTS + int tmp; + tmp = atomic_swap_int(&sc->acpi_repressed_states.flags, sc->acpi_repressed_states.saved_flags); + MPASS(tmp == ~0); +#else + atomic_store_int(&sc->acpi_repressed_states.flags, sc->sc->acpi_repressed_states.saved_flags); +#endif + + sc->acpi_repressed_states.saved_flags = 0; +} + +BOOLEAN +acpi_PowerTransitionIsEnabled() +{ + struct acpi_softc *sc = devclass_get_softc(devclass_find("acpi"), 0); + MPASS(sc != NULL); + return (sc->acpi_repressed_states.flags & (1 << ACPI_POWER_DISABLED)) == 0; +} + +static void +__do_idle(struct acpi_softc *sc) +{ + register_t intr; + + intr = intr_disable(); + intr_suspend(); + /* XXX: Is this actually required? */ + intr_enable_src(acpi_GetSciInterrupt()); + acpi_state_transition_disable(sc); + cpu_idle(0); + acpi_state_transition_enable(sc); + intr_resume(false); + intr_restore(intr); +} + +static void +__do_sleep(struct acpi_softc *sc, int state, enum acpi_sleep_state *pass) +{ + int sleep_result; + register_t intr; + + MPASS(state == ACPI_STATE_S3 || state == ACPI_STATE_S4); + + intr = intr_disable(); + + 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); + } + + intr_restore(intr); + + /* 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(); + + *pass = ACPI_SS_SLEPT; +} + /* * Enter the desired system sleep state. * * Currently we support S1-S5 but S4 is only S4BIOS */ static ACPI_STATUS -acpi_EnterSleepState(struct acpi_softc *sc, int state) +acpi_EnterSleepState(struct acpi_softc *sc, enum sleep_type stype) { - register_t intr; ACPI_STATUS status; - ACPI_EVENT_STATUS power_button_status; enum acpi_sleep_state slp_state; - int sleep_result; + int state, err; - ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, state); + ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, stype); - if (state < ACPI_STATE_S1 || state > ACPI_S_STATES_MAX) - return_ACPI_STATUS (AE_BAD_PARAMETER); - if (!acpi_sleep_states[state]) { - device_printf(sc->acpi_dev, "Sleep state S%d not supported by BIOS\n", - state); - return (AE_SUPPORT); - } + state = acpi_stype2sstate(stype); + + err = sanitize_sstate(sc, stype, state, true); + if (err) + return_ACPI_STATUS (err); /* Re-entry once we're suspending is not allowed. */ status = acpi_sleep_disable(sc); @@ -2997,7 +3155,7 @@ return (status); } - if (state == ACPI_STATE_S5) { + if (stype == POWEROFF) { /* * Shut down cleanly and power off. This will call us back through the * shutdown handlers. @@ -3010,18 +3168,12 @@ stop_all_proc(); EVENTHANDLER_INVOKE(power_suspend); -#ifdef EARLY_AP_STARTUP MPASS(mp_ncpus == 1 || smp_started); - thread_lock(curthread); - sched_bind(curthread, 0); - thread_unlock(curthread); -#else if (smp_started) { thread_lock(curthread); sched_bind(curthread, 0); thread_unlock(curthread); } -#endif /* * Be sure to hold Giant across DEVICE_SUSPEND/RESUME since non-MPSAFE @@ -3031,7 +3183,7 @@ slp_state = ACPI_SS_NONE; - sc->acpi_sstate = state; + sc->acpi_sstate = stype; /* Enable any GPEs as appropriate and requested by the user. */ acpi_wake_prep_walk(state); @@ -3051,11 +3203,13 @@ } slp_state = ACPI_SS_DEV_SUSPEND; - status = AcpiEnterSleepStatePrep(state); - if (ACPI_FAILURE(status)) { - device_printf(sc->acpi_dev, "AcpiEnterSleepStatePrep failed - %s\n", - AcpiFormatException(status)); - goto backout; + if (stype != SUSPEND_TO_IDLE) { + status = AcpiEnterSleepStatePrep(state); + if (ACPI_FAILURE(status)) { + device_printf(sc->acpi_dev, "AcpiEnterSleepStatePrep failed - %s\n", + AcpiFormatException(status)); + goto backout; + } } slp_state = ACPI_SS_SLP_PREP; @@ -3063,70 +3217,31 @@ DELAY(sc->acpi_sleep_delay * 1000000); suspendclock(); - intr = intr_disable(); - if (state != ACPI_STATE_S1) { - sleep_result = acpi_sleep_machdep(sc, state); - acpi_wakeup_machdep(sc, state, 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 && state != ACPI_STATE_S4) - AcpiWriteBitRegister(ACPI_BITREG_SCI_ENABLE, ACPI_ENABLE_EVENT); - - if (sleep_result == 1 && state == ACPI_STATE_S3) { - /* - * 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, state, sleep_result, 1); - - AcpiLeaveSleepStatePrep(state); - - if (sleep_result == -1) - goto backout; - - /* Re-enable ACPI hardware on wakeup from sleep state 4. */ - if (state == ACPI_STATE_S4) - AcpiEnable(); - } else { - status = AcpiEnterSleepState(state); - intr_restore(intr); - AcpiLeaveSleepStatePrep(state); - if (ACPI_FAILURE(status)) { - device_printf(sc->acpi_dev, "AcpiEnterSleepState failed - %s\n", - AcpiFormatException(status)); - goto backout; + switch (stype) { + case STANDBY: + { + register_t intr = intr_disable(); + status = AcpiEnterSleepState(state); + intr_restore(intr); + AcpiLeaveSleepStatePrep(state); + if (ACPI_FAILURE(status)) + device_printf(sc->acpi_dev, "AcpiEnterSleepState failed - %s\n", + AcpiFormatException(status)); + slp_state = ACPI_SS_SLEPT; } + break; + case SUSPEND: + case HIBERNATE: + __do_sleep(sc, state, &slp_state); + break; + case SUSPEND_TO_IDLE: + __do_idle(sc); + break; + case AWAKE: + case POWEROFF: + default: + __unreachable(); } - slp_state = ACPI_SS_SLEPT; /* * Back out state according to how far along we got in the suspend @@ -3137,11 +3252,12 @@ resumeclock(); if (slp_state >= ACPI_SS_GPE_SET) { acpi_wake_prep_walk(state); - sc->acpi_sstate = ACPI_STATE_S0; + sc->acpi_sstate = AWAKE; } if (slp_state >= ACPI_SS_DEV_SUSPEND) DEVICE_RESUME(root_bus); - if (slp_state >= ACPI_SS_SLP_PREP) + + if (stype != SUSPEND_TO_IDLE && (slp_state >= ACPI_SS_SLP_PREP)) AcpiLeaveSleepState(state); if (slp_state >= ACPI_SS_SLEPT) { #if defined(__i386__) || defined(__amd64__) @@ -3151,21 +3267,15 @@ acpi_resync_clock(sc); acpi_enable_fixed_events(sc); } - sc->acpi_next_sstate = 0; + sc->acpi_next_sstate = AWAKE; mtx_unlock(&Giant); -#ifdef EARLY_AP_STARTUP - thread_lock(curthread); - sched_unbind(curthread); - thread_unlock(curthread); -#else if (smp_started) { thread_lock(curthread); sched_unbind(curthread); thread_unlock(curthread); } -#endif resume_all_proc(); @@ -3478,33 +3588,42 @@ /* System Event Handlers (registered by EVENTHANDLER_REGISTER) */ static void -acpi_system_eventhandler_sleep(void *arg, int state) +acpi_system_eventhandler_sleep(void *arg, enum sleep_type type) { struct acpi_softc *sc = (struct acpi_softc *)arg; int ret; - ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, state); + ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, type); /* Check if button action is disabled or unknown. */ - if (state == ACPI_STATE_UNKNOWN) + if (type > SUSPEND_TO_IDLE) + return; + + if (!acpi_PowerTransitionIsEnabled()) return; /* Request that the system prepare to enter the given suspend state. */ - ret = acpi_ReqSleepState(sc, state); - if (ret != 0) + ret = acpi_ReqSleepState(sc, type); + if (ret != 0) { + char state[2] = { 0 }; + state[0] = type + '0'; device_printf(sc->acpi_dev, - "request to enter state S%d failed (err %d)\n", state, ret); + "request to enter state S%s failed (err %d)\n", + type == SUSPEND_TO_IDLE ? "0IDLE" : state, + ret); + } return_VOID; } static void -acpi_system_eventhandler_wakeup(void *arg, int state) +acpi_system_eventhandler_wakeup(void *arg, enum sleep_type type) { - ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, state); + ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, type); /* Currently, nothing to do for wakeup. */ + MPASS(acpi_PowerTransitionIsEnabled()); return_VOID; } @@ -3534,7 +3653,7 @@ ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); if (ACPI_FAILURE(AcpiOsExecute(OSL_NOTIFY_HANDLER, - acpi_invoke_sleep_eventhandler, &sc->acpi_power_button_sx))) + acpi_invoke_sleep_eventhandler, &sc->acpi_power_button.sx))) return_VALUE (ACPI_INTERRUPT_NOT_HANDLED); return_VALUE (ACPI_INTERRUPT_HANDLED); } @@ -3542,13 +3661,23 @@ UINT32 acpi_event_power_button_wake(void *context) { + ACPI_STATUS status; + ACPI_EVENT_STATUS power_button_status; struct acpi_softc *sc = (struct acpi_softc *)context; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); - if (ACPI_FAILURE(AcpiOsExecute(OSL_NOTIFY_HANDLER, - acpi_invoke_wake_eventhandler, &sc->acpi_power_button_sx))) + status = AcpiOsExecute(OSL_NOTIFY_HANDLER, acpi_invoke_wake_eventhandler, + &sc->acpi_power_button.sx); + if (ACPI_FAILURE(status)) return_VALUE (ACPI_INTERRUPT_NOT_HANDLED); + + status = AcpiGetEventStatus(ACPI_EVENT_POWER_BUTTON, &power_button_status); + if (ACPI_SUCCESS(status) && power_button_status != 0) { + AcpiClearEvent(ACPI_EVENT_POWER_BUTTON); + device_printf(sc->acpi_dev, "cleared fixed power button status\n"); + } + return_VALUE (ACPI_INTERRUPT_HANDLED); } @@ -3560,7 +3689,7 @@ ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); if (ACPI_FAILURE(AcpiOsExecute(OSL_NOTIFY_HANDLER, - acpi_invoke_sleep_eventhandler, &sc->acpi_sleep_button_sx))) + acpi_invoke_sleep_eventhandler, &sc->acpi_sleep_button.sx))) return_VALUE (ACPI_INTERRUPT_NOT_HANDLED); return_VALUE (ACPI_INTERRUPT_HANDLED); } @@ -3573,7 +3702,7 @@ ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); if (ACPI_FAILURE(AcpiOsExecute(OSL_NOTIFY_HANDLER, - acpi_invoke_wake_eventhandler, &sc->acpi_sleep_button_sx))) + acpi_invoke_wake_eventhandler, &sc->acpi_sleep_button.sx))) return_VALUE (ACPI_INTERRUPT_NOT_HANDLED); return_VALUE (ACPI_INTERRUPT_HANDLED); } @@ -3801,7 +3930,7 @@ case ACPIIO_REQSLPSTATE: state = *(int *)addr; if (state != ACPI_STATE_S5) - return (acpi_ReqSleepState(sc, state)); + return (acpi_ReqSleepState(sc, select_sleep_type(sc, state))); device_printf(sc->acpi_dev, "power off via acpi ioctl not supported\n"); error = EOPNOTSUPP; break; @@ -3815,7 +3944,7 @@ return (EINVAL); if (!acpi_sleep_states[state]) return (EOPNOTSUPP); - if (ACPI_FAILURE(acpi_SetSleepState(sc, state))) + if (ACPI_FAILURE(acpi_SetSleepState(sc, (enum sleep_type) state))) error = ENXIO; break; default: @@ -3826,6 +3955,15 @@ return (error); } +static int +acpi_stype2sstate(enum sleep_type type) +{ + if (type == SUSPEND_TO_IDLE) + return ACPI_STATE_UNKNOWN; + + return (int)type; +} + static int acpi_sname2sstate(const char *sname) { @@ -3864,6 +4002,10 @@ for (state = ACPI_STATE_S1; state < ACPI_S_STATE_COUNT; state++) if (acpi_sleep_states[state]) sbuf_printf(&sb, "%s ", acpi_sstate2sname(state)); +#if defined(__i386__) || defined(__amd64__) + sbuf_printf(&sb, "S0IDLE "); +#endif + sbuf_trim(&sb); sbuf_finish(&sb); error = sysctl_handle_string(oidp, sbuf_data(&sb), sbuf_len(&sb), req); @@ -3871,9 +4013,87 @@ return (error); } +static int +update_state(struct sysctl_oid *oidp, int new_state, int old_state) +{ + if (new_state < ACPI_STATE_S1) + return (EINVAL); + if (new_state < ACPI_S_STATE_COUNT && !acpi_sleep_states[new_state]) + return (EOPNOTSUPP); + if (new_state != old_state) + *(int *)oidp->oid_arg1 = new_state; + + return (0); +} + static int acpi_sleep_state_sysctl(SYSCTL_HANDLER_ARGS) { +#if defined(__i386__) || defined(__amd64__) + struct acpi_softc *sc; + char sleep_state[10]; + int error, new_state, old_state; + int *sx, *saved_sx; + + old_state = *(int *)oidp->oid_arg1; + +#define SETUP_VARIABLES(event) do { \ + sc = __containerof(__containerof(arg1, __typeof(sc->acpi_##event), sx), \ + struct acpi_softc, acpi_##event); \ + MPASS(sc); \ + sx = &sc->acpi_##event.sx; \ + MPASS(sx); \ + saved_sx = &sc->acpi_##event.saved_sx; \ + MPASS(saved_sx); \ +} while (0) + + switch (arg2) { + case POWER_BUTTON_S2I: + SETUP_VARIABLES(power_button); + break; + case SLEEP_BUTTON_S2I: + SETUP_VARIABLES(sleep_button); + break; + case LID_SWITCH_S2I: + SETUP_VARIABLES(lid_switch); + break; + case STANDBY_S2I: + SETUP_VARIABLES(standby); + break; + case SUSPEND_S2I: + SETUP_VARIABLES(suspend); + break; + default: + __unreachable(); + }; +#undef SETUP_VARIABLES + + if (*sx == SUSPEND_TO_IDLE) + strlcpy(sleep_state, "S0IDLE", 7); + else + strlcpy(sleep_state, acpi_sstate2sname(old_state), sizeof(sleep_state)); + + error = sysctl_handle_string(oidp, sleep_state, sizeof(sleep_state), req); + if (error || req->newptr == NULL) + return (error); + + if (*sx == acpi_sname2sstate(sleep_state)) + return (0); + + /* When "enabling" s0idle, leave don't touch the default */ + if (!strncasecmp(sleep_state, "s0idle", 6) || + !strncasecmp(sleep_state, "s2idle", 6)) { + *saved_sx = *sx; + *sx = SUSPEND_TO_IDLE; + return (0); + } else { + new_state = acpi_sname2sstate(sleep_state); + error = update_state(oidp, new_state, old_state); + if (!error) + *saved_sx = *sx; + return (error); + } +#else char sleep_state[10]; int error, new_state, old_state; @@ -3882,14 +4102,10 @@ error = sysctl_handle_string(oidp, sleep_state, sizeof(sleep_state), req); if (error == 0 && req->newptr != NULL) { new_state = acpi_sname2sstate(sleep_state); - if (new_state < ACPI_STATE_S1) - return (EINVAL); - if (new_state < ACPI_S_STATE_COUNT && !acpi_sleep_states[new_state]) - return (EOPNOTSUPP); - if (new_state != old_state) - *(int *)oidp->oid_arg1 = new_state; + error = update_state(oidp, new_state, old_state); } return (error); +#endif } /* Inform devctl(4) when we receive a Notify. */ @@ -4256,12 +4472,13 @@ switch (state) { case POWER_SLEEP_STATE_STANDBY: - acpi_state = sc->acpi_standby_sx; + acpi_state = sc->acpi_standby.sx; break; case POWER_SLEEP_STATE_SUSPEND: - acpi_state = sc->acpi_suspend_sx; + acpi_state = sc->acpi_suspend.sx; break; case POWER_SLEEP_STATE_HIBERNATE: + /* FIXME: Needs support for s2idle */ acpi_state = ACPI_STATE_S4; break; default: Index: sys/dev/acpica/acpi_lid.c =================================================================== --- sys/dev/acpica/acpi_lid.c +++ sys/dev/acpica/acpi_lid.c @@ -176,9 +176,9 @@ acpi_UserNotify("Lid", sc->lid_handle, sc->lid_status); if (sc->lid_status == 0) - EVENTHANDLER_INVOKE(acpi_sleep_event, acpi_sc->acpi_lid_switch_sx); + EVENTHANDLER_INVOKE(acpi_sleep_event, acpi_sc->acpi_lid_switch.sx); else - EVENTHANDLER_INVOKE(acpi_wakeup_event, acpi_sc->acpi_lid_switch_sx); + EVENTHANDLER_INVOKE(acpi_wakeup_event, acpi_sc->acpi_lid_switch.sx); out: ACPI_SERIAL_END(lid); Index: sys/dev/acpica/acpivar.h =================================================================== --- sys/dev/acpica/acpivar.h +++ sys/dev/acpica/acpivar.h @@ -49,35 +49,64 @@ #include #include +enum sleep_type { + AWAKE = ACPI_STATE_S0, + STANDBY = ACPI_STATE_S1, + SUSPEND = ACPI_STATE_S3, + HIBERNATE = ACPI_STATE_S4, + POWEROFF = ACPI_STATE_S5, + SUSPEND_TO_IDLE +}; + struct apm_clone_data; struct acpi_softc { device_t acpi_dev; struct cdev *acpi_dev_t; int acpi_enabled; - int acpi_sstate; - int acpi_sleep_disabled; + enum sleep_type acpi_sstate; int acpi_resources_reserved; struct sysctl_ctx_list acpi_sysctl_ctx; struct sysctl_oid *acpi_sysctl_tree; - int acpi_power_button_sx; - int acpi_sleep_button_sx; - int acpi_lid_switch_sx; - int acpi_standby_sx; - int acpi_suspend_sx; + struct { + int sx; + int saved_sx; + } acpi_power_button; + struct { + int sx; + int saved_sx; + } acpi_sleep_button; + struct { + int sx; + int saved_sx; + } acpi_lid_switch; + struct { + int sx; + int saved_sx; + } acpi_standby; + struct { + int sx; + int saved_sx; + } acpi_suspend; int acpi_sleep_delay; int acpi_s4bios; int acpi_do_disable; int acpi_verbose; int acpi_handle_reboot; +#define ACPI_SLEEP_DISABLED (0) /* S3, S4 */ +#define ACPI_POWER_DISABLED (1) /* S0, S5 */ + struct { + int flags; + int saved_flags; + } acpi_repressed_states; vm_offset_t acpi_wakeaddr; vm_paddr_t acpi_wakephys; - int acpi_next_sstate; /* Next suspend Sx state. */ + enum sleep_type acpi_next_sstate; /* Next "sleep" state. */ struct apm_clone_data *acpi_clone; /* Pseudo-dev for devd(8). */ STAILQ_HEAD(,apm_clone_data) apm_cdevs; /* All apm/apmctl/acpi cdevs. */ struct callout susp_force_to; /* Force suspend if no acks. */ @@ -357,12 +386,15 @@ int revision, int count, uint32_t *caps_in, uint32_t *caps_out, bool query); ACPI_STATUS acpi_OverrideInterruptLevel(UINT32 InterruptNumber); +UINT32 acpi_GetSciInterrupt(void); ACPI_STATUS acpi_SetIntrModel(int model); -int acpi_ReqSleepState(struct acpi_softc *sc, int state); +int acpi_ReqSleepState(struct acpi_softc *sc, + enum sleep_type stype); int acpi_AckSleepState(struct apm_clone_data *clone, int error); ACPI_STATUS acpi_SetSleepState(struct acpi_softc *sc, int state); int acpi_wake_set_enable(device_t dev, int enable); int acpi_parse_prw(ACPI_HANDLE h, struct acpi_prw_data *prw); +BOOLEAN acpi_PowerTransitionIsEnabled(void); ACPI_STATUS acpi_Startup(void); void acpi_UserNotify(const char *subsystem, ACPI_HANDLE h, uint8_t notify); Index: sys/x86/acpica/acpi_apm.c =================================================================== --- sys/x86/acpica/acpi_apm.c +++ sys/x86/acpica/acpi_apm.c @@ -238,7 +238,7 @@ acpi_sc = clone->acpi_sc; /* We are about to lose a reference so check if suspend should occur */ - if (acpi_sc->acpi_next_sstate != 0 && + if (acpi_sc->acpi_next_sstate != AWAKE && clone->notify_status != APM_EV_ACKED) acpi_AckSleepState(clone, 0); @@ -286,10 +286,10 @@ case APMIO_SUSPEND: if ((flag & FWRITE) == 0) return (EPERM); - if (acpi_sc->acpi_next_sstate == 0) { - if (acpi_sc->acpi_suspend_sx != ACPI_STATE_S5) { + if (acpi_sc->acpi_next_sstate == AWAKE) { + if (acpi_sc->acpi_suspend.sx != ACPI_STATE_S5) { error = acpi_ReqSleepState(acpi_sc, - acpi_sc->acpi_suspend_sx); + acpi_sc->acpi_suspend.sx); } else { printf( "power off via apm suspend not supported\n"); @@ -301,10 +301,10 @@ case APMIO_STANDBY: if ((flag & FWRITE) == 0) return (EPERM); - if (acpi_sc->acpi_next_sstate == 0) { - if (acpi_sc->acpi_standby_sx != ACPI_STATE_S5) { + if (acpi_sc->acpi_next_sstate == AWAKE) { + if (acpi_sc->acpi_standby.sx != ACPI_STATE_S5) { error = acpi_ReqSleepState(acpi_sc, - acpi_sc->acpi_standby_sx); + acpi_sc->acpi_standby.sx); } else { printf( "power off via apm standby not supported\n"); @@ -316,10 +316,10 @@ case APMIO_NEXTEVENT: printf("apm nextevent start\n"); ACPI_LOCK(acpi); - if (acpi_sc->acpi_next_sstate != 0 && clone->notify_status == - APM_EV_NONE) { + if (acpi_sc->acpi_next_sstate != AWAKE && + clone->notify_status == APM_EV_NONE) { ev_info = (struct apm_event_info *)addr; - if (acpi_sc->acpi_next_sstate <= ACPI_STATE_S3) + if (acpi_sc->acpi_next_sstate <= SUSPEND) ev_info->type = PMEV_STANDBYREQ; else ev_info->type = PMEV_SUSPENDREQ; Index: sys/x86/include/intr_machdep.h =================================================================== --- sys/x86/include/intr_machdep.h +++ sys/x86/include/intr_machdep.h @@ -162,6 +162,7 @@ int intr_remove_handler(void *cookie); void intr_resume(bool suspend_cancelled); void intr_suspend(void); +void intr_enable_src(u_int irq); void intr_reprogram(void); void intrcnt_add(const char *name, u_long **countp); void nexus_add_irq(u_long irq); Index: sys/x86/x86/intr_machdep.c =================================================================== --- sys/x86/x86/intr_machdep.c +++ sys/x86/x86/intr_machdep.c @@ -396,6 +396,19 @@ mtx_unlock(&intrpic_lock); } +/* + * This should not be called while pics are being removed, or while + * interrupt_sources is changing. + */ +void +intr_enable_src(u_int irq) +{ + struct intsrc *is; + + is = interrupt_sources[irq]; + is->is_pic->pic_enable_source(is); +} + static int intr_assign_cpu(void *arg, int cpu) {