aboutsummaryrefslogtreecommitdiff
path: root/sys/dev/acpica
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/acpica')
-rw-r--r--sys/dev/acpica/Osd/OsdHardware.c13
-rw-r--r--sys/dev/acpica/Osd/OsdSchedule.c6
-rw-r--r--sys/dev/acpica/acpi.c686
-rw-r--r--sys/dev/acpica/acpi_battery.c8
-rw-r--r--sys/dev/acpica/acpi_spmc.c656
-rw-r--r--sys/dev/acpica/acpi_video.c2
-rw-r--r--sys/dev/acpica/acpiio.h1
-rw-r--r--sys/dev/acpica/acpivar.h70
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.