Changeset View
Changeset View
Standalone View
Standalone View
sys/dev/acpica/acpi.c
Show First 20 Lines • Show All 49 Lines • ▼ Show 20 Lines | |||||
#include <sys/sched.h> | #include <sys/sched.h> | ||||
#include <sys/smp.h> | #include <sys/smp.h> | ||||
#include <sys/timetc.h> | #include <sys/timetc.h> | ||||
#if defined(__i386__) || defined(__amd64__) | #if defined(__i386__) || defined(__amd64__) | ||||
#include <machine/clock.h> | #include <machine/clock.h> | ||||
#include <machine/pci_cfgreg.h> | #include <machine/pci_cfgreg.h> | ||||
#include <machine/intr_machdep.h> | #include <machine/intr_machdep.h> | ||||
#include <machine/specialreg.h> | |||||
#include <x86/x86_var.h> | |||||
#endif | #endif | ||||
#include <machine/resource.h> | #include <machine/resource.h> | ||||
#include <machine/bus.h> | #include <machine/bus.h> | ||||
#include <sys/rman.h> | #include <sys/rman.h> | ||||
#include <isa/isavar.h> | #include <isa/isavar.h> | ||||
#include <isa/pnpvar.h> | #include <isa/pnpvar.h> | ||||
#include <contrib/dev/acpica/include/acpi.h> | #include <contrib/dev/acpica/include/acpi.h> | ||||
▲ Show 20 Lines • Show All 529 Lines • ▼ Show 20 Lines | OID_AUTO, "s4bios", CTLFLAG_RW, &sc->acpi_s4bios, 0, "S4BIOS mode"); | ||||
SYSCTL_ADD_INT(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), | SYSCTL_ADD_INT(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), | ||||
OID_AUTO, "verbose", CTLFLAG_RW, &sc->acpi_verbose, 0, "verbose mode"); | OID_AUTO, "verbose", CTLFLAG_RW, &sc->acpi_verbose, 0, "verbose mode"); | ||||
SYSCTL_ADD_INT(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), | SYSCTL_ADD_INT(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), | ||||
OID_AUTO, "disable_on_reboot", CTLFLAG_RW, | OID_AUTO, "disable_on_reboot", CTLFLAG_RW, | ||||
&sc->acpi_do_disable, 0, "Disable ACPI when rebooting/halting system"); | &sc->acpi_do_disable, 0, "Disable ACPI when rebooting/halting system"); | ||||
SYSCTL_ADD_INT(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), | SYSCTL_ADD_INT(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), | ||||
OID_AUTO, "handle_reboot", CTLFLAG_RW, | OID_AUTO, "handle_reboot", CTLFLAG_RW, | ||||
&sc->acpi_handle_reboot, 0, "Use ACPI Reset Register to reboot"); | &sc->acpi_handle_reboot, 0, "Use ACPI Reset Register to reboot"); | ||||
SYSCTL_ADD_INT(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), | |||||
OID_AUTO, "supports_s0ix", CTLFLAG_RD, | |||||
&sc->acpi_supports_s0ix, 0, "Bitfield for BIOS/Firmware s0ix preference"); | |||||
/* | /* | ||||
* Default to 1 second before sleeping to give some machines time to | * Default to 1 second before sleeping to give some machines time to | ||||
* stabilize. | * stabilize. | ||||
*/ | */ | ||||
sc->acpi_sleep_delay = 1; | sc->acpi_sleep_delay = 1; | ||||
if (bootverbose) | if (bootverbose) | ||||
sc->acpi_verbose = 1; | sc->acpi_verbose = 1; | ||||
Show All 16 Lines | #endif | ||||
/* Probe all supported sleep states. */ | /* Probe all supported sleep states. */ | ||||
acpi_sleep_states[ACPI_STATE_S0] = TRUE; | acpi_sleep_states[ACPI_STATE_S0] = TRUE; | ||||
for (state = ACPI_STATE_S1; state < ACPI_S_STATE_COUNT; state++) | for (state = ACPI_STATE_S1; state < ACPI_S_STATE_COUNT; state++) | ||||
if (ACPI_SUCCESS(AcpiEvaluateObject(ACPI_ROOT_OBJECT, | if (ACPI_SUCCESS(AcpiEvaluateObject(ACPI_ROOT_OBJECT, | ||||
__DECONST(char *, AcpiGbl_SleepStateNames[state]), NULL, NULL)) && | __DECONST(char *, AcpiGbl_SleepStateNames[state]), NULL, NULL)) && | ||||
ACPI_SUCCESS(AcpiGetSleepTypeData(state, &TypeA, &TypeB))) | ACPI_SUCCESS(AcpiGetSleepTypeData(state, &TypeA, &TypeB))) | ||||
acpi_sleep_states[state] = TRUE; | acpi_sleep_states[state] = TRUE; | ||||
if (AcpiGbl_FADT.Flags & ACPI_FADT_LOW_POWER_S0) | |||||
sc->acpi_supports_s0ix = PREFER_S0IX_FADT21; | |||||
/* | /* | ||||
* Dispatch the default sleep state to devices. The lid switch is set | * Dispatch the default sleep state to devices. The lid switch is set | ||||
* to UNKNOWN by default to avoid surprising users. | * 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; | ACPI_STATE_S5 : ACPI_STATE_UNKNOWN; | ||||
sc->acpi_lid_switch.sx = ACPI_STATE_UNKNOWN; | sc->acpi_lid_switch.sx = ACPI_STATE_UNKNOWN; | ||||
sc->acpi_standby.sx = acpi_sleep_states[ACPI_STATE_S1] ? | sc->acpi_standby.sx = acpi_sleep_states[ACPI_STATE_S1] ? | ||||
▲ Show 20 Lines • Show All 2,170 Lines • ▼ Show 20 Lines | sc->acpi_standby.sx == SUSPEND_TO_IDLE) | ||||
if (acpi_sleep_states[state]) | if (acpi_sleep_states[state]) | ||||
return (enum sleep_type)state; | return (enum sleep_type)state; | ||||
/* idle is the best we can do. */ | /* idle is the best we can do. */ | ||||
device_printf(sc->acpi_dev, | device_printf(sc->acpi_dev, | ||||
"Requested S%d, but using S0IDLE instead\n", state); | "Requested S%d, but using S0IDLE instead\n", state); | ||||
return SUSPEND_TO_IDLE; | return SUSPEND_TO_IDLE; | ||||
} | } | ||||
static int | |||||
supports_s0ix(struct acpi_softc *sc) | |||||
{ | |||||
/* | |||||
* The cpu_mwait_usable is actually a superset of what's needed to enter | |||||
* s0ix. Only bit one is actually required - Intel SDM vol 2B MWAIT: | |||||
* | |||||
* Support for MWAIT extensions for power management is indicated | |||||
* by CPUID.05H:ECX[bit 0] reporting 1." | |||||
* | |||||
* | |||||
* While it's technically possible to implement suspend to idle without | |||||
* having interrupts break out of MWAIT, it's not implemented yet. | |||||
* | |||||
* Treat interrupts as break events even if masked (e.g., even if | |||||
* EFLAGS.IF=0). May be set only if CPUID.05H:ECX[bit 1] = 1" | |||||
*/ | |||||
if (!cpu_mwait_usable()) | |||||
return 0; | |||||
return sc->acpi_supports_s0ix >= (PREFER_S0IX_FADT21 | | |||||
PREFER_S0IX_LPIT | | |||||
PREFER_S0IX_DSM); | |||||
} | |||||
#else | #else | ||||
static enum sleep_type | static enum sleep_type | ||||
select_sleep_type(struct acpi_softc *sc) | select_sleep_type(struct acpi_softc *sc) | ||||
{ | { | ||||
return AWAKE; | return AWAKE; | ||||
} | } | ||||
static int | |||||
supports_s0ix(struct acpi_softc *sc) | |||||
{ | |||||
return 0; | |||||
} | |||||
#endif | #endif | ||||
/* Given a sleep type and a state, figures out if an error is warranted */ | /* Given a sleep type and a state, figures out if an error is warranted */ | ||||
static int | static int | ||||
sanitize_sstate(struct acpi_softc *sc, enum sleep_type stype, int state, | sanitize_sstate(struct acpi_softc *sc, enum sleep_type stype, int state, | ||||
bool acpi_err) | bool acpi_err) | ||||
{ | { | ||||
if (stype == SUSPEND_TO_IDLE) | if (stype == SUSPEND_TO_IDLE) | ||||
goto out; | goto out; | ||||
if (state < ACPI_STATE_S1 || state > ACPI_S_STATES_MAX) | if (state < ACPI_STATE_S1 || state > ACPI_S_STATES_MAX) | ||||
return acpi_err ? (AE_BAD_PARAMETER) : (EINVAL); | return acpi_err ? (AE_BAD_PARAMETER) : (EINVAL); | ||||
if (!acpi_sleep_states[state]) { | if (state == ACPI_STATE_S3) { | ||||
device_printf(sc->acpi_dev, | if (!acpi_sleep_states[ACPI_STATE_S3] && supports_s0ix(sc)) { | ||||
"Sleep state S%d not supported by BIOS\n", state); | printf("Platform supports Low Power Idle instead of S3\n"); | ||||
return acpi_err ? (AE_SUPPORT) : (EOPNOTSUPP); | return acpi_err ? (AE_SUPPORT) : (EOPNOTSUPP); | ||||
} | } | ||||
if (!acpi_sleep_states[ACPI_STATE_S3]) { | |||||
printf("Consider using S0IDLE instead of S3\n"); | |||||
return acpi_err ? (AE_SUPPORT) : (EOPNOTSUPP); | |||||
} | |||||
} else if (!acpi_sleep_states[state]) | |||||
return acpi_err ? (AE_SUPPORT) : (EOPNOTSUPP); | |||||
out: | out: | ||||
return acpi_err ? (AE_OK) : (0); | return acpi_err ? (AE_OK) : (0); | ||||
} | } | ||||
/* | /* | ||||
* Request that the system enter the given suspend state. All /dev/apm | * 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 | * devices and devd(8) will be notified. Userland then has a chance to | ||||
▲ Show 20 Lines • Show All 169 Lines • ▼ Show 20 Lines | return (AE_ERROR); | ||||
if (atomic_testandset_int(&sc->acpi_repressed_states.flags, ACPI_SLEEP_DISABLED)) | if (atomic_testandset_int(&sc->acpi_repressed_states.flags, ACPI_SLEEP_DISABLED)) | ||||
return AE_ERROR; | return AE_ERROR; | ||||
return AE_OK; | return AE_OK; | ||||
} | } | ||||
enum acpi_sleep_state { | enum acpi_sleep_state { | ||||
ACPI_SS_NONE, | ACPI_SS_NONE = 0<<0, | ||||
ACPI_SS_GPE_SET, | ACPI_SS_GPE_SET = 1<<0, | ||||
ACPI_SS_DEV_SUSPEND, | ACPI_SS_DEV_SUSPEND = 1<<1, | ||||
ACPI_SS_SLP_PREP, | ACPI_SS_DEV_POST_SUSPEND= 1<<2, | ||||
ACPI_SS_SLEPT, | ACPI_SS_SLP_PREP = 1<<3, | ||||
ACPI_SS_SLEPT = 1<<4, | |||||
}; | }; | ||||
static void | static void | ||||
acpi_state_transition_disable(struct acpi_softc *sc) | acpi_state_transition_disable(struct acpi_softc *sc) | ||||
{ | { | ||||
int tmp; | int tmp; | ||||
tmp = atomic_swap_int(&sc->acpi_repressed_states.flags, ~0); | tmp = atomic_swap_int(&sc->acpi_repressed_states.flags, ~0); | ||||
Show All 22 Lines | |||||
BOOLEAN | BOOLEAN | ||||
acpi_PowerTransitionIsEnabled() | acpi_PowerTransitionIsEnabled() | ||||
{ | { | ||||
struct acpi_softc *sc = devclass_get_softc(devclass_find("acpi"), 0); | struct acpi_softc *sc = devclass_get_softc(devclass_find("acpi"), 0); | ||||
MPASS(sc != NULL); | MPASS(sc != NULL); | ||||
return (sc->acpi_repressed_states.flags & (1 << ACPI_POWER_DISABLED)) == 0; | return (sc->acpi_repressed_states.flags & (1 << ACPI_POWER_DISABLED)) == 0; | ||||
} | } | ||||
/* | |||||
* s0ix isn't exactly a sleep state since hardware handles a lot of the | |||||
* processor context save and restore. We just need to make sure that we are | |||||
* able to be woken up. | |||||
*/ | |||||
static void | static void | ||||
__do_s0ix(struct acpi_softc *sc) | |||||
{ | |||||
/* HACK: need to actually find the max cstate for the CPU */ | |||||
cpu_mwait(MWAIT_INTRBREAK, MWAIT_C3); | |||||
} | |||||
static void | |||||
__do_idle(struct acpi_softc *sc) | __do_idle(struct acpi_softc *sc) | ||||
{ | { | ||||
register_t intr; | register_t intr; | ||||
intr = intr_disable(); | intr = intr_disable(); | ||||
intr_suspend(); | intr_suspend(); | ||||
/* XXX: Is this actually required? */ | /* XXX: Is this actually required? */ | ||||
intr_enable_src(acpi_GetSciInterrupt()); | intr_enable_src(acpi_GetSciInterrupt()); | ||||
acpi_state_transition_disable(sc); | acpi_state_transition_disable(sc); | ||||
/* | |||||
* S0ix will only work with mwait, so make sure that we don't just use idle | |||||
* which could have been overridden in various ways. Also, the monitor | |||||
* hardware doesn't need to be setup for a wakeup, which the generic idle | |||||
* function will (might) do. | |||||
*/ | |||||
if (supports_s0ix(sc)) | |||||
__do_s0ix(sc); | |||||
else | |||||
cpu_idle(0); | cpu_idle(0); | ||||
acpi_state_transition_enable(sc); | acpi_state_transition_enable(sc); | ||||
intr_resume(false); | intr_resume(false); | ||||
intr_restore(intr); | intr_restore(intr); | ||||
} | } | ||||
static void | static void | ||||
__do_sleep(struct acpi_softc *sc, int state, enum acpi_sleep_state *pass) | __do_sleep(struct acpi_softc *sc, int state, enum acpi_sleep_state *pass) | ||||
{ | { | ||||
Show All 28 Lines | AcpiWriteBitRegister(ACPI_BITREG_SCI_ENABLE, ACPI_ENABLE_EVENT); | ||||
if (sleep_result == -1) | if (sleep_result == -1) | ||||
return; | return; | ||||
/* Re-enable ACPI hardware on wakeup from sleep state 4. */ | /* Re-enable ACPI hardware on wakeup from sleep state 4. */ | ||||
if (state == ACPI_STATE_S4) | if (state == ACPI_STATE_S4) | ||||
AcpiEnable(); | AcpiEnable(); | ||||
*pass = ACPI_SS_SLEPT; | *pass |= ACPI_SS_SLEPT; | ||||
} | } | ||||
/* | /* | ||||
* Enter the desired system sleep state. | * Enter the desired system sleep state. | ||||
* | * | ||||
* Currently we support S1-S5 but S4 is only S4BIOS | * Currently we support S1-S5 but S4 is only S4BIOS | ||||
*/ | */ | ||||
static ACPI_STATUS | static ACPI_STATUS | ||||
▲ Show 20 Lines • Show All 46 Lines • ▼ Show 20 Lines | thread_unlock(curthread); | ||||
mtx_lock(&Giant); | mtx_lock(&Giant); | ||||
slp_state = ACPI_SS_NONE; | slp_state = ACPI_SS_NONE; | ||||
sc->acpi_sstate = stype; | sc->acpi_sstate = stype; | ||||
/* Enable any GPEs as appropriate and requested by the user. */ | /* Enable any GPEs as appropriate and requested by the user. */ | ||||
acpi_wake_prep_walk(state); | acpi_wake_prep_walk(state); | ||||
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 | * Inform all devices that we are going to sleep. If at least one | ||||
* device fails, DEVICE_SUSPEND() automatically resumes the tree. | * device fails, DEVICE_SUSPEND() automatically resumes the tree. | ||||
* | * | ||||
* XXX Note that a better two-pass approach with a 'veto' pass | * XXX Note that a better two-pass approach with a 'veto' pass | ||||
* followed by a "real thing" pass would be better, but the current | * followed by a "real thing" pass would be better, but the current | ||||
* bus interface does not provide for this. | * bus interface does not provide for this. | ||||
*/ | */ | ||||
if (DEVICE_SUSPEND(root_bus) != 0) { | if (DEVICE_SUSPEND(root_bus) != 0) { | ||||
device_printf(sc->acpi_dev, "device_suspend failed\n"); | device_printf(sc->acpi_dev, "device_suspend failed\n"); | ||||
goto backout; | goto backout; | ||||
} | } | ||||
slp_state = ACPI_SS_DEV_SUSPEND; | slp_state |= ACPI_SS_DEV_SUSPEND; | ||||
if (sc->acpi_pm_device) { | |||||
if (DEVICE_POST_SUSPEND(sc->acpi_pm_device) == 0) | |||||
slp_state = ACPI_SS_DEV_POST_SUSPEND; | |||||
else | |||||
device_printf(sc->acpi_dev, "post suspend failed, carrying on\n"); | |||||
} | |||||
if (stype != SUSPEND_TO_IDLE) { | if (stype != SUSPEND_TO_IDLE) { | ||||
status = AcpiEnterSleepStatePrep(state); | status = AcpiEnterSleepStatePrep(state); | ||||
if (ACPI_FAILURE(status)) { | if (ACPI_FAILURE(status)) { | ||||
device_printf(sc->acpi_dev, "AcpiEnterSleepStatePrep failed - %s\n", | device_printf(sc->acpi_dev, "AcpiEnterSleepStatePrep failed - %s\n", | ||||
AcpiFormatException(status)); | AcpiFormatException(status)); | ||||
goto backout; | goto backout; | ||||
} | } | ||||
} | } | ||||
slp_state = ACPI_SS_SLP_PREP; | slp_state |= ACPI_SS_SLP_PREP; | ||||
if (sc->acpi_sleep_delay > 0) | if (sc->acpi_sleep_delay > 0) | ||||
DELAY(sc->acpi_sleep_delay * 1000000); | DELAY(sc->acpi_sleep_delay * 1000000); | ||||
suspendclock(); | suspendclock(); | ||||
switch (stype) { | switch (stype) { | ||||
case STANDBY: | case STANDBY: | ||||
{ | { | ||||
register_t intr = intr_disable(); | register_t intr = intr_disable(); | ||||
status = AcpiEnterSleepState(state); | status = AcpiEnterSleepState(state); | ||||
intr_restore(intr); | intr_restore(intr); | ||||
AcpiLeaveSleepStatePrep(state); | AcpiLeaveSleepStatePrep(state); | ||||
if (ACPI_FAILURE(status)) | if (ACPI_FAILURE(status)) | ||||
device_printf(sc->acpi_dev, "AcpiEnterSleepState failed - %s\n", | device_printf(sc->acpi_dev, "AcpiEnterSleepState failed - %s\n", | ||||
AcpiFormatException(status)); | AcpiFormatException(status)); | ||||
slp_state = ACPI_SS_SLEPT; | slp_state |= ACPI_SS_SLEPT; | ||||
} | } | ||||
break; | break; | ||||
case SUSPEND: | case SUSPEND: | ||||
case HIBERNATE: | case HIBERNATE: | ||||
__do_sleep(sc, state, &slp_state); | __do_sleep(sc, state, &slp_state); | ||||
break; | break; | ||||
case SUSPEND_TO_IDLE: | case SUSPEND_TO_IDLE: | ||||
__do_idle(sc); | __do_idle(sc); | ||||
break; | break; | ||||
case AWAKE: | case AWAKE: | ||||
case POWEROFF: | case POWEROFF: | ||||
default: | default: | ||||
__unreachable(); | __unreachable(); | ||||
} | } | ||||
/* | /* | ||||
* Back out state according to how far along we got in the suspend | * Back out state according to how far along we got in the suspend | ||||
* process. This handles both the error and success cases. | * process. This handles both the error and success cases. | ||||
*/ | */ | ||||
backout: | backout: | ||||
if (slp_state >= ACPI_SS_SLP_PREP) | if (slp_state & ACPI_SS_SLP_PREP) { | ||||
resumeclock(); | resumeclock(); | ||||
if (slp_state >= ACPI_SS_GPE_SET) { | slp_state &= ~ACPI_SS_SLP_PREP; | ||||
} | |||||
if (slp_state & ACPI_SS_GPE_SET) { | |||||
acpi_wake_prep_walk(state); | acpi_wake_prep_walk(state); | ||||
sc->acpi_sstate = AWAKE; | sc->acpi_sstate = AWAKE; | ||||
slp_state &= ~ACPI_SS_GPE_SET; | |||||
} | } | ||||
if (slp_state >= ACPI_SS_DEV_SUSPEND) | |||||
if (slp_state & ACPI_SS_DEV_POST_SUSPEND) { | |||||
MPASS(sc->acpi_pm_device); | |||||
DEVICE_POST_RESUME(sc->acpi_pm_device); | |||||
slp_state &= ~ACPI_SS_DEV_POST_SUSPEND; | |||||
} | |||||
if (slp_state & ACPI_SS_DEV_SUSPEND) { | |||||
DEVICE_RESUME(root_bus); | DEVICE_RESUME(root_bus); | ||||
slp_state &= ~ACPI_SS_DEV_SUSPEND; | |||||
} | |||||
if (stype != SUSPEND_TO_IDLE && (slp_state >= ACPI_SS_SLP_PREP)) | if (stype != SUSPEND_TO_IDLE && (slp_state & ACPI_SS_SLP_PREP)) { | ||||
AcpiLeaveSleepState(state); | AcpiLeaveSleepState(state); | ||||
if (slp_state >= ACPI_SS_SLEPT) { | slp_state &= ~ACPI_SS_SLP_PREP; | ||||
} | |||||
if (slp_state & ACPI_SS_SLEPT) { | |||||
#if defined(__i386__) || defined(__amd64__) | #if defined(__i386__) || defined(__amd64__) | ||||
/* NB: we are still using ACPI timecounter at this point. */ | /* NB: we are still using ACPI timecounter at this point. */ | ||||
resume_TSC(); | resume_TSC(); | ||||
#endif | #endif | ||||
acpi_resync_clock(sc); | acpi_resync_clock(sc); | ||||
acpi_enable_fixed_events(sc); | acpi_enable_fixed_events(sc); | ||||
slp_state &= ~ACPI_SS_SLEPT; | |||||
} | } | ||||
sc->acpi_next_sstate = AWAKE; | sc->acpi_next_sstate = AWAKE; | ||||
MPASS(slp_state == 0); | |||||
mtx_unlock(&Giant); | mtx_unlock(&Giant); | ||||
if (smp_started) { | if (smp_started) { | ||||
thread_lock(curthread); | thread_lock(curthread); | ||||
sched_unbind(curthread); | sched_unbind(curthread); | ||||
thread_unlock(curthread); | thread_unlock(curthread); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 1,232 Lines • Show Last 20 Lines |