diff options
Diffstat (limited to 'sys/dev/acpica')
| -rw-r--r-- | sys/dev/acpica/Osd/OsdHardware.c | 13 | ||||
| -rw-r--r-- | sys/dev/acpica/Osd/OsdSchedule.c | 6 | ||||
| -rw-r--r-- | sys/dev/acpica/acpi.c | 686 | ||||
| -rw-r--r-- | sys/dev/acpica/acpi_battery.c | 8 | ||||
| -rw-r--r-- | sys/dev/acpica/acpi_spmc.c | 656 | ||||
| -rw-r--r-- | sys/dev/acpica/acpi_video.c | 2 | ||||
| -rw-r--r-- | sys/dev/acpica/acpiio.h | 1 | ||||
| -rw-r--r-- | sys/dev/acpica/acpivar.h | 70 |
8 files changed, 1179 insertions, 263 deletions
diff --git a/sys/dev/acpica/Osd/OsdHardware.c b/sys/dev/acpica/Osd/OsdHardware.c index fbaf76d2a91a..4252cbc63222 100644 --- a/sys/dev/acpica/Osd/OsdHardware.c +++ b/sys/dev/acpica/Osd/OsdHardware.c @@ -37,6 +37,8 @@ extern int acpi_susp_bounce; +int (*acpi_prepare_sleep)(uint8_t state, uint32_t a, uint32_t b, bool ext); + ACPI_STATUS AcpiOsEnterSleep(UINT8 SleepState, UINT32 RegaValue, UINT32 RegbValue) { @@ -45,6 +47,17 @@ AcpiOsEnterSleep(UINT8 SleepState, UINT32 RegaValue, UINT32 RegbValue) if (acpi_susp_bounce) return (AE_CTRL_TERMINATE); + if (acpi_prepare_sleep != NULL) + { + int ret = acpi_prepare_sleep(SleepState, RegaValue, RegbValue, + ACPI_REDUCED_HARDWARE ? true : AcpiGbl_ReducedHardware); + + if (ret < 0) + return (AE_ERROR); + if (ret > 0) + return (AE_CTRL_TERMINATE); + } + return (AE_OK); } diff --git a/sys/dev/acpica/Osd/OsdSchedule.c b/sys/dev/acpica/Osd/OsdSchedule.c index 0c5eadb87687..f0464709e8ad 100644 --- a/sys/dev/acpica/Osd/OsdSchedule.c +++ b/sys/dev/acpica/Osd/OsdSchedule.c @@ -35,7 +35,6 @@ #include <sys/param.h> #include <sys/systm.h> #include <sys/bus.h> -#include <sys/cpuset.h> #include <sys/kernel.h> #include <sys/kthread.h> #include <sys/malloc.h> @@ -110,13 +109,10 @@ static void acpi_taskq_init(void *arg) { int i; - /* XXX Currently assuming BSP is CPU0. */ - cpuset_t just_bsp = CPUSET_T_INITIALIZER(0x1); acpi_taskq = taskqueue_create_fast("acpi_task", M_NOWAIT, &taskqueue_thread_enqueue, &acpi_taskq); - taskqueue_start_threads_cpuset(&acpi_taskq, acpi_max_threads, PWAIT, - &just_bsp, "acpi_task"); + taskqueue_start_threads(&acpi_taskq, acpi_max_threads, PWAIT, "acpi_task"); if (acpi_task_count > 0) { if (bootverbose) printf("AcpiOsExecute: enqueue %d pending tasks\n", diff --git a/sys/dev/acpica/acpi.c b/sys/dev/acpica/acpi.c index 8380f701d226..bdc197a4fb59 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> @@ -192,6 +193,7 @@ static void acpi_system_eventhandler_sleep(void *arg, enum power_stype stype); static void acpi_system_eventhandler_wakeup(void *arg, enum power_stype stype); +static int acpi_s4bios_sysctl(SYSCTL_HANDLER_ARGS); static enum power_stype acpi_sstate_to_stype(int sstate); static int acpi_sname_to_sstate(const char *sname); static const char *acpi_sstate_to_sname(int sstate); @@ -292,6 +294,17 @@ static char acpi_remove_interface[256]; TUNABLE_STR("hw.acpi.remove_interface", acpi_remove_interface, sizeof(acpi_remove_interface)); +/* + * Automatically apply the Darwin OSI on Apple Mac hardware to obtain + * access to full ACPI hardware support on supported platforms. + * + * This flag automatically overrides any values set by + * `hw.acpi.acpi_install_interface` and unset by + * `hw.acpi.acpi_remove_interface`. + */ +static int acpi_apple_darwin_osi = 1; +TUNABLE_INT("hw.acpi.apple_darwin_osi", &acpi_apple_darwin_osi); + /* Allow users to dump Debug objects without ACPI debugger. */ static int acpi_debug_objects; TUNABLE_INT("debug.acpi.enable_debug_objects", &acpi_debug_objects); @@ -489,7 +502,6 @@ acpi_attach(device_t dev) ACPI_STATUS status; int error, state; UINT32 flags; - UINT8 TypeA, TypeB; char *env; enum power_stype stype; @@ -587,61 +599,6 @@ acpi_attach(device_t dev) goto out; } - /* - * Setup our sysctl tree. - * - * XXX: This doesn't check to make sure that none of these fail. - */ - sysctl_ctx_init(&sc->acpi_sysctl_ctx); - sc->acpi_sysctl_tree = SYSCTL_ADD_NODE(&sc->acpi_sysctl_ctx, - SYSCTL_STATIC_CHILDREN(_hw), OID_AUTO, device_get_name(dev), - CTLFLAG_RD | CTLFLAG_MPSAFE, 0, ""); - SYSCTL_ADD_PROC(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), - OID_AUTO, "supported_sleep_state", - CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, - 0, 0, acpi_supported_sleep_state_sysctl, "A", - "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 | CTLFLAG_MPSAFE, - &sc->acpi_power_button_stype, 0, acpi_stype_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 | CTLFLAG_MPSAFE, - &sc->acpi_sleep_button_stype, 0, acpi_stype_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 | CTLFLAG_MPSAFE, - &sc->acpi_lid_switch_stype, 0, acpi_stype_sysctl, "A", - "Lid ACPI sleep state. Set to s2idle or s2mem if you want to suspend " - "your laptop when you close the lid."); - SYSCTL_ADD_PROC(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), - OID_AUTO, "suspend_state", CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, - NULL, 0, acpi_suspend_state_sysctl, "A", - "Current ACPI suspend state. This sysctl is deprecated; you probably " - "want to use kern.power.suspend instead."); - SYSCTL_ADD_PROC(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), - OID_AUTO, "standby_state", - CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, - &sc->acpi_standby_sx, 0, acpi_sleep_state_sysctl, "A", - "ACPI Sx state to use when going standby (usually S1 or S2)."); - 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"); - SYSCTL_ADD_INT(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), - OID_AUTO, "s4bios", CTLFLAG_RW, &sc->acpi_s4bios, 0, - "Use S4BIOS when hibernating."); - SYSCTL_ADD_INT(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), - OID_AUTO, "verbose", CTLFLAG_RW, &sc->acpi_verbose, 0, "verbose mode"); - SYSCTL_ADD_INT(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), - OID_AUTO, "disable_on_reboot", CTLFLAG_RW, - &sc->acpi_do_disable, 0, "Disable ACPI when rebooting/halting system"); - SYSCTL_ADD_INT(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), - OID_AUTO, "handle_reboot", CTLFLAG_RW, - &sc->acpi_handle_reboot, 0, "Use ACPI Reset Register to reboot"); - #if defined(__amd64__) || defined(__i386__) /* * Enable workaround for incorrect ISA IRQ polarity by default on @@ -649,10 +606,6 @@ acpi_attach(device_t dev) */ if (cpu_vendor_id == CPU_VENDOR_INTEL) acpi_override_isa_irq_polarity = 1; - SYSCTL_ADD_INT(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), - OID_AUTO, "override_isa_irq_polarity", CTLFLAG_RDTUN, - &acpi_override_isa_irq_polarity, 0, - "Force active-hi polarity for edge-triggered ISA IRQs"); #endif /* @@ -672,24 +625,30 @@ acpi_attach(device_t dev) if (AcpiGbl_FADT.Flags & ACPI_FADT_RESET_REGISTER) sc->acpi_handle_reboot = 1; -#if !ACPI_REDUCED_HARDWARE - /* Only enable S4BIOS by default if the FACS says it is available. */ + /* + * Mark whether S4BIOS is available according to the FACS, and if it is, + * enable it by default. + */ if (AcpiGbl_FACS != NULL && AcpiGbl_FACS->Flags & ACPI_FACS_S4_BIOS_PRESENT) - sc->acpi_s4bios = 1; -#endif + sc->acpi_s4bios = sc->acpi_s4bios_supported = true; /* - * 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; - 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; +#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++) { + UINT8 TypeA, TypeB; + + if (ACPI_SUCCESS(AcpiGetSleepTypeData(state, &TypeA, &TypeB))) { + acpi_supported_sstates[state] = true; acpi_supported_stypes[acpi_sstate_to_stype(state)] = true; } + } /* * Dispatch the default sleep type to devices. The lid switch is set @@ -705,13 +664,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); @@ -745,6 +715,71 @@ acpi_attach(device_t dev) if ((error = acpi_machdep_init(dev))) goto out; + /* + * Setup our sysctl tree. + * + * XXX: This doesn't check to make sure that none of these fail. + */ + sysctl_ctx_init(&sc->acpi_sysctl_ctx); + sc->acpi_sysctl_tree = SYSCTL_ADD_NODE(&sc->acpi_sysctl_ctx, + SYSCTL_STATIC_CHILDREN(_hw), OID_AUTO, device_get_name(dev), + CTLFLAG_RD | CTLFLAG_MPSAFE, 0, ""); + SYSCTL_ADD_PROC(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), + OID_AUTO, "supported_sleep_state", + CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, + 0, 0, acpi_supported_sleep_state_sysctl, "A", + "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 | CTLFLAG_MPSAFE, + &sc->acpi_power_button_stype, 0, acpi_stype_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 | CTLFLAG_MPSAFE, + &sc->acpi_sleep_button_stype, 0, acpi_stype_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 | CTLFLAG_MPSAFE, + &sc->acpi_lid_switch_stype, 0, acpi_stype_sysctl, "A", + "Lid ACPI sleep state. Set to s2idle or s2mem if you want to suspend " + "your laptop when you close the lid."); + SYSCTL_ADD_PROC(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), + OID_AUTO, "suspend_state", CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, + NULL, 0, acpi_suspend_state_sysctl, "A", + "Current ACPI suspend state. This sysctl is deprecated; you probably " + "want to use kern.power.suspend instead."); + SYSCTL_ADD_PROC(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), + OID_AUTO, "standby_state", + CTLTYPE_STRING | CTLFLAG_RW | CTLFLAG_MPSAFE, + &sc->acpi_standby_sx, 0, acpi_sleep_state_sysctl, "A", + "ACPI Sx state to use when going standby (usually S1 or S2)."); + 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"); + SYSCTL_ADD_PROC(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), + OID_AUTO, "s4bios", CTLTYPE_U8 | CTLFLAG_RW | CTLFLAG_MPSAFE, + sc, 0, acpi_s4bios_sysctl, "CU", + "On hibernate, have the firmware save/restore the machine state (S4BIOS)."); + SYSCTL_ADD_BOOL(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), + OID_AUTO, "s4bios_supported", CTLFLAG_RD, &sc->acpi_s4bios_supported, 0, + "Whether firmware supports saving/restoring the machine state (S4BIOS)."); + SYSCTL_ADD_INT(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), + OID_AUTO, "verbose", CTLFLAG_RW, &sc->acpi_verbose, 0, "verbose mode"); + SYSCTL_ADD_INT(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), + OID_AUTO, "disable_on_reboot", CTLFLAG_RW, + &sc->acpi_do_disable, 0, "Disable ACPI when rebooting/halting system"); + SYSCTL_ADD_INT(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), + OID_AUTO, "handle_reboot", CTLFLAG_RW, + &sc->acpi_handle_reboot, 0, "Use ACPI Reset Register to reboot"); +#if defined(__amd64__) || defined(__i386__) + SYSCTL_ADD_INT(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), + OID_AUTO, "override_isa_irq_polarity", CTLFLAG_RDTUN, + &acpi_override_isa_irq_polarity, 0, + "Force active-hi polarity for edge-triggered ISA IRQs"); +#endif + /* Register ACPI again to pass the correct argument of pm_func. */ power_pm_register(POWER_PM_TYPE_ACPI, acpi_pm_func, sc, acpi_supported_stypes); @@ -1119,6 +1154,9 @@ acpi_child_deleted(device_t dev, device_t child) free(dinfo, M_ACPIDEV); } +_Static_assert(ACPI_IVAR_PRIVATE >= ISA_IVAR_LAST, + "ACPI private IVARs overlap with ISA IVARs"); + /* * Handle per-device ivars */ @@ -2093,44 +2131,64 @@ acpi_bus_get_prop(device_t bus, device_t child, const char *propname, } } +static int +acpi_device_pwr_for_sleep_sxd(device_t dev, ACPI_HANDLE handle, int state, + int *dstate) +{ + ACPI_STATUS status; + char sxd[8]; + + /* Note illegal _S0D is evaluated because some systems expect this. */ + snprintf(sxd, sizeof(sxd), "_S%dD", state); + 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, + acpi_name(handle), AcpiFormatException(status)); + return (ENXIO); + } + return (0); +} + +/* + * Get the D-state we need to set the device to for entry into the sleep type + * we are currently entering (sc->acpi_stype is set in acpi_EnterSleepState + * before the ACPI bus gets suspended, and thus before this function is called). + * + * If entering s2idle, we will try to enter whichever D-state we would've been + * transitioning to in S3. If we are entering an ACPI S-state, we evaluate the + * relevant _SxD state instead (ACPI 7.3.16 - 7.3.19). + */ int acpi_device_pwr_for_sleep(device_t bus, device_t dev, int *dstate) { - struct acpi_softc *sc; - ACPI_HANDLE handle; - ACPI_STATUS status; - char sxd[8]; - - handle = acpi_get_handle(dev); + struct acpi_softc *sc = device_get_softc(bus); + ACPI_HANDLE handle = acpi_get_handle(dev); + int state; - /* - * XXX If we find these devices, don't try to power them down. - * The serial and IRDA ports on my T23 hang the system when - * set to D3 and it appears that such legacy devices may - * need special handling in their drivers. - */ - if (dstate == NULL || handle == NULL || - acpi_MatchHid(handle, "PNP0500") || - acpi_MatchHid(handle, "PNP0501") || - acpi_MatchHid(handle, "PNP0502") || - acpi_MatchHid(handle, "PNP0510") || - acpi_MatchHid(handle, "PNP0511")) - return (ENXIO); + if (dstate == NULL) + return (EINVAL); - /* - * Override next state with the value from _SxD, if present. - * Note illegal _S0D is evaluated because some systems expect this. - */ - sc = device_get_softc(bus); - snprintf(sxd, sizeof(sxd), "_S%dD", acpi_stype_to_sstate(sc, sc->acpi_stype)); - 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, - acpi_name(handle), AcpiFormatException(status)); - return (ENXIO); - } + /* + * XXX If we find these devices, don't try to power them down. + * The serial and IRDA ports on my T23 hang the system when + * set to D3 and it appears that such legacy devices may + * need special handling in their drivers. + */ + if (handle == NULL || + acpi_MatchHid(handle, "PNP0500") || + acpi_MatchHid(handle, "PNP0501") || + acpi_MatchHid(handle, "PNP0502") || + acpi_MatchHid(handle, "PNP0510") || + acpi_MatchHid(handle, "PNP0511")) + return (ENXIO); - return (0); + if (sc->acpi_stype == POWER_STYPE_SUSPEND_TO_IDLE) + state = ACPI_STATE_S3; + else + state = acpi_stype_to_sstate(sc, sc->acpi_stype); + if (state == ACPI_STATE_UNKNOWN) + return (ENOENT); + return (acpi_device_pwr_for_sleep_sxd(bus, handle, state, dstate)); } /* Callback arg for our implementation of walking the namespace. */ @@ -2558,11 +2616,54 @@ acpi_fake_objhandler(ACPI_HANDLE h, void *data) { } +/* + * Simple wrapper around AcpiEnterSleepStatePrep() printing diagnostic on error. + */ +static ACPI_STATUS +acpi_EnterSleepStatePrep(device_t acpi_dev, UINT8 SleepState) +{ + ACPI_STATUS status; + + status = AcpiEnterSleepStatePrep(SleepState); + if (ACPI_FAILURE(status)) + device_printf(acpi_dev, + "AcpiEnterSleepStatePrep(%u) failed - %s\n", + SleepState, + AcpiFormatException(status)); + return (status); +} + +/* Return from this function indicates failure. */ +static void +acpi_poweroff(device_t acpi_dev) +{ + register_t intr; + ACPI_STATUS status; + + device_printf(acpi_dev, "Powering system off...\n"); + status = acpi_EnterSleepStatePrep(acpi_dev, ACPI_STATE_S5); + if (ACPI_FAILURE(status)) { + device_printf(acpi_dev, "Power-off preparation failed! - %s\n", + AcpiFormatException(status)); + return; + } + intr = intr_disable(); + status = AcpiEnterSleepState(ACPI_STATE_S5); + if (ACPI_FAILURE(status)) { + intr_restore(intr); + device_printf(acpi_dev, "Power-off failed! - %s\n", + AcpiFormatException(status)); + } else { + DELAY(1000000); + intr_restore(intr); + device_printf(acpi_dev, "Power-off failed! - timeout\n"); + } +} + static void acpi_shutdown_final(void *arg, int howto) { struct acpi_softc *sc = (struct acpi_softc *)arg; - register_t intr; ACPI_STATUS status; /* @@ -2571,24 +2672,7 @@ acpi_shutdown_final(void *arg, int howto) * an AP. */ if ((howto & RB_POWEROFF) != 0) { - status = AcpiEnterSleepStatePrep(ACPI_STATE_S5); - if (ACPI_FAILURE(status)) { - device_printf(sc->acpi_dev, "AcpiEnterSleepStatePrep failed - %s\n", - AcpiFormatException(status)); - return; - } - device_printf(sc->acpi_dev, "Powering system off\n"); - intr = intr_disable(); - status = AcpiEnterSleepState(ACPI_STATE_S5); - if (ACPI_FAILURE(status)) { - intr_restore(intr); - device_printf(sc->acpi_dev, "power-off failed - %s\n", - AcpiFormatException(status)); - } else { - DELAY(1000000); - intr_restore(intr); - device_printf(sc->acpi_dev, "power-off failed - timeout\n"); - } + acpi_poweroff(sc->acpi_dev); } else if ((howto & RB_HALT) == 0 && sc->acpi_handle_reboot) { /* Reboot using the reset register. */ status = AcpiReset(); @@ -3315,7 +3399,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 +3415,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 +3464,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 +3504,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 +3690,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 +3701,77 @@ 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; + EVENTHANDLER_INVOKE(acpi_post_dev_suspend, stype); + 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 = acpi_EnterSleepStatePrep(sc->acpi_dev, acpi_sstate); + if (ACPI_FAILURE(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; + resumeclock(); /* * 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) - resumeclock(); - if (slp_state >= ACPI_SS_GPE_SET) { + 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) { + EVENTHANDLER_INVOKE(acpi_pre_dev_resume, stype); DEVICE_RESUME(root_bus); - if (slp_state >= ACPI_SS_SLP_PREP) + slp_state &= ~ACPI_SS_DEV_SUSPEND; + } + if ((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 @@ -3815,7 +3971,7 @@ acpi_wake_sysctl_walk(device_t dev) for (i = 0; i < numdevs; i++) { child = devlist[i]; acpi_wake_sysctl_walk(child); - if (!device_is_attached(child)) + if (!device_is_attached(child) || !acpi_has_flags(child)) continue; status = AcpiEvaluateObject(acpi_get_handle(child), "_PRW", NULL, NULL); if (ACPI_SUCCESS(status)) { @@ -3960,7 +4116,7 @@ acpi_system_eventhandler_sleep(void *arg, enum power_stype stype) ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, stype); /* Check if button action is disabled or unknown. */ - if (stype == ACPI_STATE_UNKNOWN) + if (stype == POWER_STYPE_UNKNOWN) return; /* @@ -4242,6 +4398,21 @@ acpi_deregister_ioctl(u_long cmd, acpi_ioctl_fn fn) ACPI_UNLOCK(acpi); } +void +acpi_deregister_ioctls(acpi_ioctl_fn fn) +{ + struct acpi_ioctl_hook *hp, *thp; + + ACPI_LOCK(acpi); + TAILQ_FOREACH_SAFE(hp, &acpi_ioctl_hooks, link, thp) { + if (hp->fn == fn) { + TAILQ_REMOVE(&acpi_ioctl_hooks, hp, link); + free(hp, M_ACPIDEV); + } + } + ACPI_UNLOCK(acpi); +} + static int acpiopen(struct cdev *dev, int flag, int fmt, struct thread *td) { @@ -4317,6 +4488,25 @@ acpiioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flag, struct thread *t } static int +acpi_s4bios_sysctl(SYSCTL_HANDLER_ARGS) +{ + struct acpi_softc *const sc = arg1; + bool val; + int error; + + val = sc->acpi_s4bios; + error = sysctl_handle_bool(oidp, &val, 0, req); + if (error != 0 || req->newptr == NULL) + return (error); + + if (val && !sc->acpi_s4bios_supported) + return (EOPNOTSUPP); + sc->acpi_s4bios = val; + + return (0); +} + +static int acpi_sname_to_sstate(const char *sname) { int sstate; @@ -4438,9 +4628,9 @@ acpi_stype_sysctl(SYSCTL_HANDLER_ARGS) sstate = acpi_sname_to_sstate(name); if (sstate < 0) return (EINVAL); - printf("warning: this sysctl expects a sleep type, but an ACPI " - "S-state has been passed to it. This functionality is " - "deprecated; see acpi(4).\n"); + printf("warning: the 'hw.acpi.%s' sysctl expects a sleep type, but " + "an ACPI S-state has been passed to it. This functionality " + "is deprecated; see acpi(4).\n", oidp->oid_name); MPASS(sstate < ACPI_S_STATE_COUNT); if (acpi_supported_sstates[sstate] == false) return (EOPNOTSUPP); @@ -4514,6 +4704,7 @@ static struct debugtag dbg_layer[] = { {"ACPI_FAN", ACPI_FAN}, {"ACPI_POWERRES", ACPI_POWERRES}, {"ACPI_PROCESSOR", ACPI_PROCESSOR}, + {"ACPI_SPMC", ACPI_SPMC}, {"ACPI_THERMAL", ACPI_THERMAL}, {"ACPI_TIMER", ACPI_TIMER}, {"ACPI_ALL_DRIVERS", ACPI_ALL_DRIVERS}, @@ -4798,6 +4989,65 @@ acpi_reset_interfaces(device_t dev) } acpi_free_interfaces(&list); } + + /* + * Apple Mac hardware quirk: install Darwin OSI. + * + * On Apple hardware, install the Darwin OSI and remove the Windows OSI + * to match Linux behavior. + * + * This is required for dual-GPU MacBook Pro systems + * (Intel iGPU + AMD/NVIDIA dGPU) where the iGPU is hidden when the + * firmware doesn't see Darwin OSI, but it also unlocks additional ACPI + * support on non-MacBook Pro Apple platforms. + * + * Apple's ACPI firmware checks _OSI("Darwin") and sets OSYS=10000 + * for macOS. Many device methods use OSDW() which checks OSYS==10000 + * for macOS-specific behavior including GPU visibility and power + * management. + * + * Linux enables Darwin OSI by default on Apple hardware and disables + * all Windows OSI strings (drivers/acpi/osi.c). Users can override + * this behavior with acpi_osi=!Darwin to get Windows-like behavior, + * in general, but this logic makes that process unnecessary. + * + * Detect Apple via SMBIOS and enable Darwin while disabling Windows + * vendor strings. This makes both GPUs visible on dual-GPU MacBook Pro + * systems (Intel iGPU + AMD dGPU) and unlocks full platform + * ACPI support. + */ + if (acpi_apple_darwin_osi) { + char *vendor = kern_getenv("smbios.system.maker"); + if (vendor != NULL) { + if (strcmp(vendor, "Apple Inc.") == 0 || + strcmp(vendor, "Apple Computer, Inc.") == 0) { + /* Disable all other OSI vendor strings. */ + status = AcpiUpdateInterfaces( + ACPI_DISABLE_ALL_VENDOR_STRINGS); + if (ACPI_SUCCESS(status)) { + /* Install Darwin OSI */ + status = AcpiInstallInterface("Darwin"); + } + if (bootverbose) { + if (ACPI_SUCCESS(status)) { + device_printf(dev, + "disabled non-Darwin OSI & " + "installed Darwin OSI\n"); + } else { + device_printf(dev, + "could not install " + "Darwin OSI: %s\n", + AcpiFormatException(status)); + } + } + } else if (bootverbose) { + device_printf(dev, + "Not installing Darwin OSI on unsupported platform: %s\n", + vendor); + } + freeenv(vendor); + } + } } static int diff --git a/sys/dev/acpica/acpi_battery.c b/sys/dev/acpica/acpi_battery.c index cfd8261d5eab..f1eebda705c1 100644 --- a/sys/dev/acpica/acpi_battery.c +++ b/sys/dev/acpica/acpi_battery.c @@ -531,13 +531,7 @@ acpi_battery_init(void) out: if (error) { - acpi_deregister_ioctl(ACPIIO_BATT_GET_UNITS, acpi_battery_ioctl); - acpi_deregister_ioctl(ACPIIO_BATT_GET_BATTINFO, acpi_battery_ioctl); - acpi_deregister_ioctl(ACPIIO_BATT_GET_BATTINFO_V1, acpi_battery_ioctl); - acpi_deregister_ioctl(ACPIIO_BATT_GET_BIF, acpi_battery_ioctl); - acpi_deregister_ioctl(ACPIIO_BATT_GET_BIX, acpi_battery_ioctl); - acpi_deregister_ioctl(ACPIIO_BATT_GET_BST, acpi_battery_ioctl); - acpi_deregister_ioctl(ACPIIO_BATT_GET_BST_V1, acpi_battery_ioctl); + acpi_deregister_ioctls(acpi_battery_ioctl); } return (error); } diff --git a/sys/dev/acpica/acpi_spmc.c b/sys/dev/acpica/acpi_spmc.c new file mode 100644 index 000000000000..03944800327d --- /dev/null +++ b/sys/dev/acpica/acpi_spmc.c @@ -0,0 +1,656 @@ +/* + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2024-2026 The FreeBSD Foundation + * + * This software was developed by Aymeric Wibo <obiwac@freebsd.org> + * under sponsorship from the FreeBSD Foundation. + */ + +#include <sys/param.h> +#include <sys/bus.h> +#include <sys/eventhandler.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/uuid.h> + +#include <machine/_inttypes.h> + +#include <contrib/dev/acpica/include/acpi.h> +#include <contrib/dev/acpica/include/accommon.h> + +#include <dev/acpica/acpivar.h> + +/* Hooks for the ACPI CA debugging infrastructure */ +#define _COMPONENT ACPI_SPMC +ACPI_MODULE_NAME("SPMC") + +static SYSCTL_NODE(_debug_acpi, OID_AUTO, spmc, CTLFLAG_RD | CTLFLAG_MPSAFE, + NULL, "SPMC debugging"); + +static char *spmc_ids[] = { + "PNP0D80", + NULL +}; + +enum intel_dsm_index { + DSM_ENUM_FUNCTIONS = 0, + DSM_GET_DEVICE_CONSTRAINTS = 1, + DSM_GET_CRASH_DUMP_DEVICE = 2, + DSM_DISPLAY_OFF_NOTIF = 3, + DSM_DISPLAY_ON_NOTIF = 4, + DSM_ENTRY_NOTIF = 5, + DSM_EXIT_NOTIF = 6, + /* Only for Microsoft DSM set. */ + DSM_MODERN_ENTRY_NOTIF = 7, + DSM_MODERN_EXIT_NOTIF = 8, + DSM_MODERN_TURN_ON_DISPLAY = 9, +}; + +enum amd_dsm_index { + AMD_DSM_ENUM_FUNCTIONS = 0, + AMD_DSM_GET_DEVICE_CONSTRAINTS = 1, + AMD_DSM_ENTRY_NOTIF = 2, + AMD_DSM_EXIT_NOTIF = 3, + AMD_DSM_DISPLAY_OFF_NOTIF = 4, + AMD_DSM_DISPLAY_ON_NOTIF = 5, +}; + +enum dsm_set_flags { + DSM_SET_INTEL = 1 << 0, + DSM_SET_MS = 1 << 1, + DSM_SET_AMD = 1 << 2, +}; + +struct dsm_set { + enum dsm_set_flags flag; + const char *name; + int revision; + struct uuid uuid; + uint64_t dsms_supported; + uint64_t dsms_expected; + uint64_t extra_dsms; +}; + +static struct dsm_set intel_dsm_set = { + .flag = DSM_SET_INTEL, + .name = "Intel", + /* + * XXX Linux uses 1 for the revision on Intel DSMs, but doesn't explain + * why. The commit that introduces this links to a document mentioning + * revision 0, so default this to 0. + * + * The debug.acpi.spmc.intel_dsm_revision sysctl may be used to configure + * this just in case. + */ + .revision = 0, + .uuid = { /* c4eb40a0-6cd2-11e2-bcfd-0800200c9a66 */ + 0xc4eb40a0, 0x6cd2, 0x11e2, 0xbc, 0xfd, + {0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66}, + }, + .dsms_expected = (1 << DSM_GET_DEVICE_CONSTRAINTS) | + (1 << DSM_DISPLAY_OFF_NOTIF) | (1 << DSM_DISPLAY_ON_NOTIF) | + (1 << DSM_ENTRY_NOTIF) | (1 << DSM_EXIT_NOTIF), +}; + +SYSCTL_INT(_debug_acpi_spmc, OID_AUTO, intel_dsm_revision, CTLFLAG_RW, + &intel_dsm_set.revision, 0, + "Revision to use when evaluating Intel SPMC DSMs"); + +static struct dsm_set ms_dsm_set = { + .flag = DSM_SET_MS, + .name = "Microsoft", + .revision = 0, + .uuid = { /* 11e00d56-ce64-47ce-837b-1f898f9aa461 */ + 0x11e00d56, 0xce64, 0x47ce, 0x83, 0x7b, + {0x1f, 0x89, 0x8f, 0x9a, 0xa4, 0x61}, + }, + .dsms_expected = (1 << DSM_DISPLAY_OFF_NOTIF) | + (1 << DSM_DISPLAY_ON_NOTIF) | (1 << DSM_ENTRY_NOTIF) | + (1 << DSM_EXIT_NOTIF) | (1 << DSM_MODERN_ENTRY_NOTIF) | + (1 << DSM_MODERN_EXIT_NOTIF), + .extra_dsms = (1 << DSM_MODERN_TURN_ON_DISPLAY), +}; + +static struct dsm_set amd_dsm_set = { + .flag = DSM_SET_AMD, + .name = "AMD", + /* + * XXX Linux uses 0 for the revision on AMD DSMs, but at least on the + * Framework 13 AMD 7040 series, the enum functions DSM only returns a + * function mask that covers all the DSMs we need to call when called + * with revision 2. + * + * The debug.acpi.spmc.amd_dsm_revision sysctl may be used to configure + * this just in case. + */ + .revision = 2, + .uuid = { /* e3f32452-febc-43ce-9039-932122d37721 */ + 0xe3f32452, 0xfebc, 0x43ce, 0x90, 0x39, + {0x93, 0x21, 0x22, 0xd3, 0x77, 0x21}, + }, + .dsms_expected = (1 << AMD_DSM_GET_DEVICE_CONSTRAINTS) | + (1 << AMD_DSM_ENTRY_NOTIF) | (1 << AMD_DSM_EXIT_NOTIF) | + (1 << AMD_DSM_DISPLAY_OFF_NOTIF) | (1 << AMD_DSM_DISPLAY_ON_NOTIF), +}; + +SYSCTL_INT(_debug_acpi_spmc, OID_AUTO, amd_dsm_revision, CTLFLAG_RW, + &amd_dsm_set.revision, 0, "Revision to use when evaluating AMD SPMC DSMs"); + +union dsm_index { + int i; + enum intel_dsm_index regular; + enum amd_dsm_index amd; +}; + +struct acpi_spmc_constraint { + bool enabled; + char *name; + int min_d_state; + ACPI_HANDLE handle; + + /* Unused, spec only. */ + uint64_t lpi_uid; + uint64_t min_dev_specific_state; + + /* Unused, AMD only. */ + uint64_t function_states; +}; + +struct acpi_spmc_softc { + device_t dev; + ACPI_HANDLE handle; + ACPI_OBJECT *obj; + enum dsm_set_flags dsm_sets; + + struct eventhandler_entry *eh_suspend; + struct eventhandler_entry *eh_resume; + + bool constraints_populated; + size_t constraint_count; + struct acpi_spmc_constraint *constraints; +}; + +static void acpi_spmc_check_dsm_set(struct acpi_spmc_softc *sc, + ACPI_HANDLE handle, struct dsm_set *dsm_set); +static int acpi_spmc_get_constraints(device_t dev); +static void acpi_spmc_free_constraints(struct acpi_spmc_softc *sc); + +static void acpi_spmc_suspend(device_t dev, enum power_stype stype); +static void acpi_spmc_resume(device_t dev, enum power_stype stype); + +static int +acpi_spmc_probe(device_t dev) +{ + char *name; + ACPI_HANDLE handle; + struct acpi_spmc_softc *sc; + + /* Check that this is an enabled device. */ + if (acpi_get_type(dev) != ACPI_TYPE_DEVICE || acpi_disabled("spmc")) + return (ENXIO); + + if (ACPI_ID_PROBE(device_get_parent(dev), dev, spmc_ids, &name) > 0) + return (ENXIO); + + if (device_get_unit(dev) > 0) { + device_printf(dev, "shouldn't have more than one SPMC"); + return (ENXIO); + } + + handle = acpi_get_handle(dev); + /* ACPI_ID_PROBE() above cannot succeed without a handle. */ + MPASS(handle != NULL); + + sc = device_get_softc(dev); + sc->dev = dev; + + /* Check which sets of DSMs are supported. */ + sc->dsm_sets = 0; + + acpi_spmc_check_dsm_set(sc, handle, &intel_dsm_set); + acpi_spmc_check_dsm_set(sc, handle, &ms_dsm_set); + acpi_spmc_check_dsm_set(sc, handle, &amd_dsm_set); + + if (sc->dsm_sets == 0) + return (ENXIO); + + device_set_descf(dev, "System Power Management Controller " + "(DSM sets 0x%x)", sc->dsm_sets); + + return (0); +} + +static int +acpi_spmc_attach(device_t dev) +{ + struct acpi_spmc_softc *sc = device_get_softc(dev); + + sc->handle = acpi_get_handle(dev); + if (sc->handle == NULL) + return (ENXIO); + + sc->constraints_populated = false; + sc->constraint_count = 0; + sc->constraints = NULL; + + /* Get device constraints. We can only call this once so do this now. */ + acpi_spmc_get_constraints(dev); + + sc->eh_suspend = EVENTHANDLER_REGISTER(acpi_post_dev_suspend, + acpi_spmc_suspend, dev, 0); + sc->eh_resume = EVENTHANDLER_REGISTER(acpi_pre_dev_resume, + acpi_spmc_resume, dev, 0); + + return (0); +} + +static int +acpi_spmc_detach(device_t dev) +{ + struct acpi_spmc_softc *sc = device_get_softc(dev); + + EVENTHANDLER_DEREGISTER(acpi_post_dev_suspend, sc->eh_suspend); + EVENTHANDLER_DEREGISTER(acpi_pre_dev_resume, sc->eh_resume); + + acpi_spmc_free_constraints(device_get_softc(dev)); + return (0); +} + +static void +acpi_spmc_check_dsm_set(struct acpi_spmc_softc *sc, ACPI_HANDLE handle, + struct dsm_set *dsm_set) +{ + uint64_t dsms_supported = acpi_DSMQuery(handle, + (uint8_t *)&dsm_set->uuid, dsm_set->revision); + const uint64_t min_dsms = dsm_set->dsms_expected; + const uint64_t max_dsms = min_dsms | dsm_set->extra_dsms; + + /* + * Check if DSM set supported at all. We do this by checking the + * existence of "enum functions". + */ + if ((dsms_supported & 1) == 0) + return; + dsms_supported &= ~1; + dsm_set->dsms_supported = dsms_supported; + sc->dsm_sets |= dsm_set->flag; + + if ((dsms_supported & min_dsms) != min_dsms) + device_printf(sc->dev, "DSM set %s does not support expected " + "DSMs (%#" PRIx64 " vs %#" PRIx64 "). " + "Some methods may fail.\n", + dsm_set->name, dsms_supported, min_dsms); + + if ((dsms_supported & ~max_dsms) != 0) + device_printf(sc->dev, "DSM set %s supports more DSMs than " + "expected (%#" PRIx64 " vs %#" PRIx64 ").\n", dsm_set->name, + dsms_supported, max_dsms); +} + +static void +acpi_spmc_free_constraints(struct acpi_spmc_softc *sc) +{ + for (size_t i = 0; i < sc->constraint_count; i++) + free(sc->constraints[i].name, M_TEMP); + sc->constraint_count = 0; + + free(sc->constraints, M_TEMP); + sc->constraints = NULL; +} + +static int +acpi_spmc_get_constraints_spec(struct acpi_spmc_softc *sc, ACPI_OBJECT *object) +{ + struct acpi_spmc_constraint *constraint; + int revision; + ACPI_OBJECT *constraint_obj; + ACPI_OBJECT *name_obj; + ACPI_OBJECT *detail; + ACPI_OBJECT *constraint_package; + + KASSERT(sc->constraints_populated == false, + ("constraints already populated")); + + sc->constraint_count = object->Package.Count; + sc->constraints = malloc(sc->constraint_count * sizeof *sc->constraints, + M_TEMP, M_WAITOK | M_ZERO); + + /* + * The value of sc->constraint_count can change during the loop, so + * iterate until object->Package.Count so we actually go over all + * elements in the package. + */ + for (size_t i = 0; i < object->Package.Count; i++) { + constraint_obj = &object->Package.Elements[i]; + constraint = &sc->constraints[i]; + + constraint->enabled = + constraint_obj->Package.Elements[1].Integer.Value; + + name_obj = &constraint_obj->Package.Elements[0]; + constraint->name = strdup(name_obj->String.Pointer, M_TEMP); + if (constraint->name == NULL) { + acpi_spmc_free_constraints(sc); + return (ENOMEM); + } + + detail = &constraint_obj->Package.Elements[2]; + /* + * The first element in the device constraint detail package is + * the revision, and should always be zero. + */ + revision = detail->Package.Elements[0].Integer.Value; + if (revision != 0) { + device_printf(sc->dev, "Unknown revision %d for " + "device constraint detail package\n", revision); + sc->constraint_count--; + continue; + } + + constraint_package = &detail->Package.Elements[1]; + + constraint->lpi_uid = + constraint_package->Package.Elements[0].Integer.Value; + constraint->min_d_state = + constraint_package->Package.Elements[1].Integer.Value; + constraint->min_dev_specific_state = + constraint_package->Package.Elements[2].Integer.Value; + } + + sc->constraints_populated = true; + return (0); +} + +static int +acpi_spmc_get_constraints_amd(struct acpi_spmc_softc *sc, ACPI_OBJECT *object) +{ + size_t constraint_count; + ACPI_OBJECT *constraint_obj; + ACPI_OBJECT *constraints; + struct acpi_spmc_constraint *constraint; + ACPI_OBJECT *name_obj; + + KASSERT(sc->constraints_populated == false, + ("constraints already populated")); + + /* + * First element in the package is unknown. + * Second element is the number of device constraints. + * Third element is the list of device constraints itself. + */ + constraint_count = object->Package.Elements[1].Integer.Value; + constraints = &object->Package.Elements[2]; + + if (constraints->Package.Count != constraint_count) { + device_printf(sc->dev, "constraint count mismatch (%d to %zu)\n", + constraints->Package.Count, constraint_count); + return (ENXIO); + } + + sc->constraint_count = constraint_count; + sc->constraints = malloc(constraint_count * sizeof *sc->constraints, + M_TEMP, M_WAITOK | M_ZERO); + + for (size_t i = 0; i < constraint_count; i++) { + /* Parse the constraint package. */ + constraint_obj = &constraints->Package.Elements[i]; + if (constraint_obj->Package.Count != 4) { + device_printf(sc->dev, "constraint %zu has %d elements\n", + i, constraint_obj->Package.Count); + acpi_spmc_free_constraints(sc); + return (ENXIO); + } + + constraint = &sc->constraints[i]; + constraint->enabled = + constraint_obj->Package.Elements[0].Integer.Value; + + name_obj = &constraint_obj->Package.Elements[1]; + constraint->name = strdup(name_obj->String.Pointer, M_TEMP); + if (constraint->name == NULL) { + acpi_spmc_free_constraints(sc); + return (ENOMEM); + } + + constraint->function_states = + constraint_obj->Package.Elements[2].Integer.Value; + constraint->min_d_state = + constraint_obj->Package.Elements[3].Integer.Value; + } + + sc->constraints_populated = true; + return (0); +} + +static int +acpi_spmc_get_constraints(device_t dev) +{ + struct acpi_spmc_softc *sc; + union dsm_index dsm_index; + struct dsm_set *dsm_set; + ACPI_STATUS status; + ACPI_BUFFER result; + ACPI_OBJECT *object; + bool is_amd; + int rv; + struct acpi_spmc_constraint *constraint; + + sc = device_get_softc(dev); + if (sc->constraints_populated) + return (0); + + /* The Microsoft DSM set doesn't have this DSM. */ + is_amd = (sc->dsm_sets & DSM_SET_AMD) != 0; + if (is_amd) { + dsm_set = &amd_dsm_set; + dsm_index.amd = AMD_DSM_GET_DEVICE_CONSTRAINTS; + } else { + dsm_set = &intel_dsm_set; + dsm_index.regular = DSM_GET_DEVICE_CONSTRAINTS; + } + + /* XXX It seems like this DSM fails if called more than once. */ + status = acpi_EvaluateDSMTyped(sc->handle, (uint8_t *)&dsm_set->uuid, + dsm_set->revision, dsm_index.i, NULL, &result, + ACPI_TYPE_PACKAGE); + if (ACPI_FAILURE(status)) { + device_printf(dev, "%s failed to call %s DSM %d (rev %d)\n", + __func__, dsm_set->name, dsm_index.i, dsm_set->revision); + return (ENXIO); + } + + object = (ACPI_OBJECT *)result.Pointer; + if (is_amd) + rv = acpi_spmc_get_constraints_amd(sc, object); + else + rv = acpi_spmc_get_constraints_spec(sc, object); + AcpiOsFree(object); + if (rv != 0) + return (rv); + + /* Get handles for each constraint device. */ + for (size_t i = 0; i < sc->constraint_count; i++) { + constraint = &sc->constraints[i]; + + status = acpi_GetHandleInScope(sc->handle, + __DECONST(char *, constraint->name), &constraint->handle); + if (ACPI_FAILURE(status)) { + device_printf(dev, "failed to get handle for %s\n", + constraint->name); + constraint->handle = NULL; + } + } + return (0); +} + +static void +acpi_spmc_check_constraints(struct acpi_spmc_softc *sc) +{ + bool violation = false; + + KASSERT(sc->constraints_populated, ("constraints not populated")); + for (size_t i = 0; i < sc->constraint_count; i++) { + struct acpi_spmc_constraint *constraint = &sc->constraints[i]; + + if (!constraint->enabled) + continue; + if (constraint->handle == NULL) + continue; + + ACPI_STATUS status = acpi_GetHandleInScope(sc->handle, + __DECONST(char *, constraint->name), &constraint->handle); + if (ACPI_FAILURE(status)) { + device_printf(sc->dev, "failed to get handle for %s\n", + constraint->name); + constraint->handle = NULL; + } + if (constraint->handle == NULL) + continue; + +#ifdef notyet + int d_state; + if (ACPI_FAILURE(acpi_pwr_get_state(constraint->handle, &d_state))) + continue; + if (d_state < constraint->min_d_state) { + device_printf(sc->dev, "constraint for device %s" + " violated (minimum D-state required was %s, actual" + " D-state is %s), might fail to enter LPI state\n", + constraint->name, + acpi_d_state_to_str(constraint->min_d_state), + acpi_d_state_to_str(d_state)); + violation = true; + } +#endif + } + if (!violation) + device_printf(sc->dev, + "all device power constraints respected!\n"); +} + +static void +acpi_spmc_run_dsm(device_t dev, struct dsm_set *dsm_set, int index) +{ + struct acpi_spmc_softc *sc; + ACPI_STATUS status; + ACPI_BUFFER result; + + sc = device_get_softc(dev); + + status = acpi_EvaluateDSMTyped(sc->handle, (uint8_t *)&dsm_set->uuid, + dsm_set->revision, index, NULL, &result, ACPI_TYPE_ANY); + + if (ACPI_FAILURE(status)) { + device_printf(dev, "%s failed to call %s DSM %d (rev %d)\n", + __func__, dsm_set->name, index, dsm_set->revision); + return; + } + + AcpiOsFree(result.Pointer); +} + +/* + * Try running the DSMs from all the DSM sets we have, as them failing costs us + * nothing, and it seems like on AMD platforms, both the AMD entry and Microsoft + * "modern" DSM's are required for it to enter modern standby. + * + * This is what Linux does too. + */ +static void +acpi_spmc_display_off_notif(device_t dev) +{ + struct acpi_spmc_softc *sc = device_get_softc(dev); + + if ((sc->dsm_sets & DSM_SET_INTEL) != 0) + acpi_spmc_run_dsm(dev, &intel_dsm_set, DSM_DISPLAY_OFF_NOTIF); + if ((sc->dsm_sets & DSM_SET_MS) != 0) + acpi_spmc_run_dsm(dev, &ms_dsm_set, DSM_DISPLAY_OFF_NOTIF); + if ((sc->dsm_sets & DSM_SET_AMD) != 0) + acpi_spmc_run_dsm(dev, &amd_dsm_set, AMD_DSM_DISPLAY_OFF_NOTIF); +} + +static void +acpi_spmc_display_on_notif(device_t dev) +{ + struct acpi_spmc_softc *sc = device_get_softc(dev); + + if ((sc->dsm_sets & DSM_SET_INTEL) != 0) + acpi_spmc_run_dsm(dev, &intel_dsm_set, DSM_DISPLAY_ON_NOTIF); + if ((sc->dsm_sets & DSM_SET_MS) != 0) + acpi_spmc_run_dsm(dev, &ms_dsm_set, DSM_DISPLAY_ON_NOTIF); + if ((sc->dsm_sets & DSM_SET_AMD) != 0) + acpi_spmc_run_dsm(dev, &amd_dsm_set, AMD_DSM_DISPLAY_ON_NOTIF); +} + +static void +acpi_spmc_entry_notif(device_t dev) +{ + struct acpi_spmc_softc *sc = device_get_softc(dev); + + acpi_spmc_check_constraints(sc); + + if ((sc->dsm_sets & DSM_SET_AMD) != 0) + acpi_spmc_run_dsm(dev, &amd_dsm_set, AMD_DSM_ENTRY_NOTIF); + if ((sc->dsm_sets & DSM_SET_MS) != 0) { + acpi_spmc_run_dsm(dev, &ms_dsm_set, DSM_MODERN_ENTRY_NOTIF); + acpi_spmc_run_dsm(dev, &ms_dsm_set, DSM_ENTRY_NOTIF); + } + if ((sc->dsm_sets & DSM_SET_INTEL) != 0) + acpi_spmc_run_dsm(dev, &intel_dsm_set, DSM_ENTRY_NOTIF); +} + +static void +acpi_spmc_exit_notif(device_t dev) +{ + struct acpi_spmc_softc *sc = device_get_softc(dev); + + if ((sc->dsm_sets & DSM_SET_INTEL) != 0) + acpi_spmc_run_dsm(dev, &intel_dsm_set, DSM_EXIT_NOTIF); + if ((sc->dsm_sets & DSM_SET_AMD) != 0) + acpi_spmc_run_dsm(dev, &amd_dsm_set, AMD_DSM_EXIT_NOTIF); + if ((sc->dsm_sets & DSM_SET_MS) != 0) { + acpi_spmc_run_dsm(dev, &ms_dsm_set, DSM_EXIT_NOTIF); + if (ms_dsm_set.dsms_supported & + (1 << DSM_MODERN_TURN_ON_DISPLAY)) + acpi_spmc_run_dsm(dev, &ms_dsm_set, + DSM_MODERN_TURN_ON_DISPLAY); + acpi_spmc_run_dsm(dev, &ms_dsm_set, DSM_MODERN_EXIT_NOTIF); + } +} + +static void +acpi_spmc_suspend(device_t dev, enum power_stype stype) +{ + if (stype != POWER_STYPE_SUSPEND_TO_IDLE) + return; + + acpi_spmc_display_off_notif(dev); + acpi_spmc_entry_notif(dev); +} + +static void +acpi_spmc_resume(device_t dev, enum power_stype stype) +{ + if (stype != POWER_STYPE_SUSPEND_TO_IDLE) + return; + + acpi_spmc_exit_notif(dev); + acpi_spmc_display_on_notif(dev); +} + +static device_method_t acpi_spmc_methods[] = { + DEVMETHOD(device_probe, acpi_spmc_probe), + DEVMETHOD(device_attach, acpi_spmc_attach), + DEVMETHOD(device_detach, acpi_spmc_detach), + DEVMETHOD_END +}; + +static driver_t acpi_spmc_driver = { + "acpi_spmc", + acpi_spmc_methods, + sizeof(struct acpi_spmc_softc), +}; + +DRIVER_MODULE_ORDERED(acpi_spmc, acpi, acpi_spmc_driver, NULL, NULL, SI_ORDER_ANY); +MODULE_DEPEND(acpi_spmc, acpi, 1, 1, 1); diff --git a/sys/dev/acpica/acpi_video.c b/sys/dev/acpica/acpi_video.c index 7a22c9dc0994..f949e14f58e6 100644 --- a/sys/dev/acpica/acpi_video.c +++ b/sys/dev/acpica/acpi_video.c @@ -176,7 +176,7 @@ static device_method_t acpi_video_methods[] = { DEVMETHOD(device_detach, acpi_video_detach), DEVMETHOD(device_resume, acpi_video_resume), DEVMETHOD(device_shutdown, acpi_video_shutdown), - { 0, 0 } + DEVMETHOD_END }; static driver_t acpi_video_driver = { diff --git a/sys/dev/acpica/acpiio.h b/sys/dev/acpica/acpiio.h index 63779d309951..4df049ed196a 100644 --- a/sys/dev/acpica/acpiio.h +++ b/sys/dev/acpica/acpiio.h @@ -205,6 +205,7 @@ union acpi_battery_ioctl_arg { typedef int (*acpi_ioctl_fn)(u_long cmd, caddr_t addr, void *arg); extern int acpi_register_ioctl(u_long cmd, acpi_ioctl_fn fn, void *arg); extern void acpi_deregister_ioctl(u_long cmd, acpi_ioctl_fn fn); +extern void acpi_deregister_ioctls(acpi_ioctl_fn fn); #endif #endif /* !_ACPIIO_H_ */ diff --git a/sys/dev/acpica/acpivar.h b/sys/dev/acpica/acpivar.h index 6db55b10570d..7bcac6239253 100644 --- a/sys/dev/acpica/acpivar.h +++ b/sys/dev/acpica/acpivar.h @@ -64,7 +64,8 @@ struct acpi_softc { enum power_stype acpi_lid_switch_stype; int acpi_standby_sx; - int acpi_s4bios; + bool acpi_s4bios; + bool acpi_s4bios_supported; int acpi_sleep_delay; int acpi_do_disable; @@ -191,6 +192,7 @@ extern struct mtx acpi_mutex; #define ACPI_THERMAL 0x01000000 #define ACPI_TIMER 0x02000000 #define ACPI_OEM 0x04000000 +#define ACPI_SPMC 0x08000000 /* * Constants for different interrupt models used with acpi_SetIntrModel(). @@ -275,42 +277,22 @@ extern int acpi_override_isa_irq_polarity; * interface compatibility with ISA drivers which can also * attach to ACPI. */ -#define ACPI_IVAR_HANDLE 0x100 -#define ACPI_IVAR_UNUSED 0x101 /* Unused/reserved. */ -#define ACPI_IVAR_PRIVATE 0x102 -#define ACPI_IVAR_FLAGS 0x103 -#define ACPI_IVAR_DOMAIN 0x104 +enum { + ACPI_IVAR_PRIVATE = 20, + ACPI_IVAR_DOMAIN, + ACPI_IVAR_HANDLE = BUS_IVARS_ACPI, + ACPI_IVAR_FLAGS +}; /* * ad_domain NUMA domain special value. */ #define ACPI_DEV_DOMAIN_UNKNOWN (-1) -/* - * Accessor functions for our ivars. Default value for BUS_READ_IVAR is - * (type) 0. The <sys/bus.h> accessor functions don't check return values. - */ -#define __ACPI_BUS_ACCESSOR(varp, var, ivarp, ivar, type) \ - \ -static __inline type varp ## _get_ ## var(device_t dev) \ -{ \ - uintptr_t v = 0; \ - BUS_READ_IVAR(device_get_parent(dev), dev, \ - ivarp ## _IVAR_ ## ivar, &v); \ - return ((type) v); \ -} \ - \ -static __inline void varp ## _set_ ## var(device_t dev, type t) \ -{ \ - uintptr_t v = (uintptr_t) t; \ - BUS_WRITE_IVAR(device_get_parent(dev), dev, \ - ivarp ## _IVAR_ ## ivar, v); \ -} - -__ACPI_BUS_ACCESSOR(acpi, handle, ACPI, HANDLE, ACPI_HANDLE) -__ACPI_BUS_ACCESSOR(acpi, private, ACPI, PRIVATE, void *) -__ACPI_BUS_ACCESSOR(acpi, flags, ACPI, FLAGS, int) -__ACPI_BUS_ACCESSOR(acpi, domain, ACPI, DOMAIN, int) +__BUS_ACCESSOR_DEFAULT(acpi, handle, ACPI, HANDLE, ACPI_HANDLE, NULL) +__BUS_ACCESSOR(acpi, private, ACPI, PRIVATE, void *) +__BUS_ACCESSOR(acpi, flags, ACPI, FLAGS, int) +__BUS_ACCESSOR(acpi, domain, ACPI, DOMAIN, int) void acpi_fake_objhandler(ACPI_HANDLE h, void *data); static __inline device_t @@ -481,12 +463,14 @@ UINT32 acpi_event_sleep_button_wake(void *context); #define ACPI_EVENT_PRI_DEFAULT 10000 #define ACPI_EVENT_PRI_LAST 20000 -typedef void (*acpi_event_handler_t)(void *, int); +typedef void (*acpi_event_handler_t)(void *, enum power_stype); EVENTHANDLER_DECLARE(acpi_sleep_event, acpi_event_handler_t); EVENTHANDLER_DECLARE(acpi_wakeup_event, acpi_event_handler_t); EVENTHANDLER_DECLARE(acpi_acad_event, acpi_event_handler_t); EVENTHANDLER_DECLARE(acpi_video_event, acpi_event_handler_t); +EVENTHANDLER_DECLARE(acpi_post_dev_suspend, acpi_event_handler_t); +EVENTHANDLER_DECLARE(acpi_pre_dev_resume, acpi_event_handler_t); /* Device power control. */ ACPI_STATUS acpi_pwr_wake_enable(ACPI_HANDLE consumer, int enable); @@ -523,10 +507,19 @@ acpi_d_state_to_str(int state) const char *strs[ACPI_D_STATE_COUNT] = {"D0", "D1", "D2", "D3hot", "D3cold"}; + if (state == ACPI_STATE_UNKNOWN) + return ("unknown D-state"); MPASS(state >= ACPI_STATE_D0 && state <= ACPI_D_STATES_MAX); return (strs[state]); } +static __inline bool +acpi_should_do_s4bios(struct acpi_softc *sc) +{ + MPASS(!sc->acpi_s4bios || sc->acpi_s4bios_supported); + return (sc->acpi_s4bios); +} + char *acpi_name(ACPI_HANDLE handle); int acpi_avoid(ACPI_HANDLE handle); int acpi_disabled(char *subsys); @@ -619,6 +612,19 @@ int acpi_pxm_parse(device_t dev); int acpi_map_pxm_to_vm_domainid(int pxm); bus_get_cpus_t acpi_get_cpus; +/* + * Hook for ACPI sleep routine. + */ +extern int (*acpi_prepare_sleep)(uint8_t state, uint32_t a, uint32_t b, + bool ext); + +static inline void +acpi_set_prepare_sleep( + int (*hook)(uint8_t state, uint32_t a, uint32_t b, bool ext)) +{ + acpi_prepare_sleep = hook; +} + #ifdef __aarch64__ /* * ARM specific ACPI interfaces, relating to IORT table. |
