Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F151557388
D17676.id49532.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
30 KB
Referenced Files
None
Subscribers
None
D17676.id49532.diff
View Options
Index: sys/conf/files.amd64
===================================================================
--- sys/conf/files.amd64
+++ sys/conf/files.amd64
@@ -198,11 +198,13 @@
crypto/via/padlock_hash.c optional padlock
dev/acpica/acpi_if.m standard
dev/acpica/acpi_hpet.c optional acpi
+dev/acpica/acpi_lpit.c optional acpi
dev/acpica/acpi_pci.c optional acpi pci
dev/acpica/acpi_pci_link.c optional acpi pci
dev/acpica/acpi_pcib.c optional acpi pci
dev/acpica/acpi_pcib_acpi.c optional acpi pci
dev/acpica/acpi_pcib_pci.c optional acpi pci
+dev/acpica/acpi_spmc.c optional acpi
dev/acpica/acpi_timer.c optional acpi
dev/acpi_support/acpi_wmi_if.m standard
dev/agp/agp_amd64.c optional agp
Index: sys/dev/acpica/acpi.c
===================================================================
--- sys/dev/acpica/acpi.c
+++ sys/dev/acpica/acpi.c
@@ -55,6 +55,8 @@
#include <machine/clock.h>
#include <machine/pci_cfgreg.h>
#include <machine/intr_machdep.h>
+#include <machine/specialreg.h>
+#include <x86/x86_var.h>
#endif
#include <machine/resource.h>
#include <machine/bus.h>
@@ -600,6 +602,9 @@
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");
+ SYSCTL_ADD_INT(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree),
+ OID_AUTO, "supports_s0ix", CTLFLAG_RD,
+ &sc->acpi_supports_s0ix, 0, "Bitfield for BIOS/Firmware s0ix preference");
/*
* Default to 1 second before sleeping to give some machines time to
@@ -632,6 +637,9 @@
ACPI_SUCCESS(AcpiGetSleepTypeData(state, &TypeA, &TypeB)))
acpi_sleep_states[state] = TRUE;
+ if (AcpiGbl_FADT.Flags & ACPI_FADT_LOW_POWER_S0)
+ sc->acpi_supports_s0ix = PREFER_S0IX_FADT21;
+
/*
* Dispatch the default sleep state to devices. The lid switch is set
* to UNKNOWN by default to avoid surprising users.
@@ -2818,12 +2826,41 @@
"Requested S%d, but using S0IDLE instead\n", state);
return SUSPEND_TO_IDLE;
}
+static int
+supports_s0ix(struct acpi_softc *sc)
+{
+ /*
+ * The cpu_mwait_usable is actually a superset of what's needed to enter
+ * s0ix. Only bit one is actually required - Intel SDM vol 2B MWAIT:
+ *
+ * Support for MWAIT extensions for power management is indicated
+ * by CPUID.05H:ECX[bit 0] reporting 1."
+ *
+ *
+ * While it's technically possible to implement suspend to idle without
+ * having interrupts break out of MWAIT, it's not implemented yet.
+ *
+ * Treat interrupts as break events even if masked (e.g., even if
+ * EFLAGS.IF=0). May be set only if CPUID.05H:ECX[bit 1] = 1"
+ */
+ if (!cpu_mwait_usable())
+ return 0;
+
+ return sc->acpi_supports_s0ix >= (PREFER_S0IX_FADT21 |
+ PREFER_S0IX_LPIT |
+ PREFER_S0IX_DSM);
+}
#else
static enum sleep_type
select_sleep_type(struct acpi_softc *sc)
{
return AWAKE;
}
+static int
+supports_s0ix(struct acpi_softc *sc)
+{
+ return 0;
+}
#endif
/* Given a sleep type and a state, figures out if an error is warranted */
@@ -2836,11 +2873,17 @@
if (state < ACPI_STATE_S1 || state > ACPI_S_STATES_MAX)
return acpi_err ? (AE_BAD_PARAMETER) : (EINVAL);
- if (!acpi_sleep_states[state]) {
- device_printf(sc->acpi_dev,
- "Sleep state S%d not supported by BIOS\n", state);
+ if (state == ACPI_STATE_S3) {
+ if (!acpi_sleep_states[ACPI_STATE_S3] && supports_s0ix(sc)) {
+ printf("Platform supports Low Power Idle instead of S3\n");
+ return acpi_err ? (AE_SUPPORT) : (EOPNOTSUPP);
+ }
+ if (!acpi_sleep_states[ACPI_STATE_S3]) {
+ printf("Consider using S0IDLE instead of S3\n");
+ return acpi_err ? (AE_SUPPORT) : (EOPNOTSUPP);
+ }
+ } else if (!acpi_sleep_states[state])
return acpi_err ? (AE_SUPPORT) : (EOPNOTSUPP);
- }
out:
return acpi_err ? (AE_OK) : (0);
@@ -3026,11 +3069,12 @@
}
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<<0,
+ ACPI_SS_GPE_SET = 1<<0,
+ ACPI_SS_DEV_SUSPEND = 1<<1,
+ ACPI_SS_DEV_POST_SUSPEND= 1<<2,
+ ACPI_SS_SLP_PREP = 1<<3,
+ ACPI_SS_SLEPT = 1<<4,
};
static void
@@ -3069,6 +3113,19 @@
return (sc->acpi_repressed_states.flags & (1 << ACPI_POWER_DISABLED)) == 0;
}
+/*
+ * s0ix isn't exactly a sleep state since hardware handles a lot of the
+ * processor context save and restore. We just need to make sure that we are
+ * able to be woken up.
+ */
+static void
+__do_s0ix(struct acpi_softc *sc)
+{
+ /* HACK: need to actually find the max cstate for the CPU */
+ cpu_mwait(MWAIT_INTRBREAK, MWAIT_C3);
+
+}
+
static void
__do_idle(struct acpi_softc *sc)
{
@@ -3079,7 +3136,18 @@
/* XXX: Is this actually required? */
intr_enable_src(acpi_GetSciInterrupt());
acpi_state_transition_disable(sc);
- cpu_idle(0);
+
+ /*
+ * S0ix will only work with mwait, so make sure that we don't just use idle
+ * which could have been overridden in various ways. Also, the monitor
+ * hardware doesn't need to be setup for a wakeup, which the generic idle
+ * function will (might) do.
+ */
+ if (supports_s0ix(sc))
+ __do_s0ix(sc);
+ else
+ cpu_idle(0);
+
acpi_state_transition_enable(sc);
intr_resume(false);
intr_restore(intr);
@@ -3124,7 +3192,7 @@
if (state == ACPI_STATE_S4)
AcpiEnable();
- *pass = ACPI_SS_SLEPT;
+ *pass |= ACPI_SS_SLEPT;
}
/*
@@ -3187,7 +3255,7 @@
/* Enable any GPEs as appropriate and requested by the user. */
acpi_wake_prep_walk(state);
- slp_state = ACPI_SS_GPE_SET;
+ slp_state |= ACPI_SS_GPE_SET;
/*
* Inform all devices that we are going to sleep. If at least one
@@ -3201,7 +3269,12 @@
device_printf(sc->acpi_dev, "device_suspend failed\n");
goto backout;
}
- slp_state = ACPI_SS_DEV_SUSPEND;
+ slp_state |= ACPI_SS_DEV_SUSPEND;
+
+ if (sc->acpi_pm_device && DEVICE_POST_SUSPEND(sc->acpi_pm_device) != 0)
+ device_printf(sc->acpi_dev, "post suspend failed, carrying on\n");
+ else
+ slp_state = ACPI_SS_DEV_POST_SUSPEND;
if (stype != SUSPEND_TO_IDLE) {
status = AcpiEnterSleepStatePrep(state);
@@ -3211,7 +3284,7 @@
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);
@@ -3227,7 +3300,7 @@
if (ACPI_FAILURE(status))
device_printf(sc->acpi_dev, "AcpiEnterSleepState failed - %s\n",
AcpiFormatException(status));
- slp_state = ACPI_SS_SLEPT;
+ slp_state |= ACPI_SS_SLEPT;
}
break;
case SUSPEND:
@@ -3248,27 +3321,46 @@
* process. This handles both the error and success cases.
*/
backout:
- if (slp_state >= ACPI_SS_SLP_PREP)
+ if (slp_state & ACPI_SS_SLP_PREP) {
resumeclock();
- if (slp_state >= ACPI_SS_GPE_SET) {
+ slp_state &= ~ACPI_SS_SLP_PREP;
+ }
+
+ if (slp_state & ACPI_SS_GPE_SET) {
acpi_wake_prep_walk(state);
sc->acpi_sstate = AWAKE;
+ slp_state &= ~ACPI_SS_GPE_SET;
+ }
+
+ if (slp_state & ACPI_SS_DEV_POST_SUSPEND) {
+ MPASS(sc->acpi_pm_device);
+ DEVICE_POST_RESUME(sc->acpi_pm_device);
+ slp_state &= ~ACPI_SS_DEV_POST_SUSPEND;
}
- if (slp_state >= ACPI_SS_DEV_SUSPEND)
+
+ if (slp_state & ACPI_SS_DEV_SUSPEND) {
DEVICE_RESUME(root_bus);
+ slp_state &= ~ACPI_SS_DEV_SUSPEND;
+ }
- if (stype != SUSPEND_TO_IDLE && (slp_state >= ACPI_SS_SLP_PREP))
+ if (stype != SUSPEND_TO_IDLE && (slp_state & ACPI_SS_SLP_PREP)) {
AcpiLeaveSleepState(state);
- if (slp_state >= ACPI_SS_SLEPT) {
+ slp_state &= ~ACPI_SS_SLP_PREP;
+ }
+
+ if (slp_state & ACPI_SS_SLEPT) {
#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_sstate = AWAKE;
+ MPASS(slp_state == 0);
+
mtx_unlock(&Giant);
if (smp_started) {
Index: sys/dev/acpica/acpi_lpit.c
===================================================================
--- /dev/null
+++ sys/dev/acpica/acpi_lpit.c
@@ -0,0 +1,199 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2018 Ben Widawsky
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * The Low Power Idle Table (LPIT) is used to help diagnose and advise in low
+ * power idle states which are automatically transitioned into, and out of, by
+ * Intel hardware. Details may be found here:
+ * http://www.uefi.org/sites/default/files/resources/Intel_ACPI_Low_Power_S0_Idle.pdf
+ *
+ * Unlike a standard device driver, LPIT should be considered part of the core
+ * ACPI support because it's just a table and has nothing to actually probe or
+ * attach.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/resource.h>
+#include <sys/rman.h>
+
+#include <machine/clock.h>
+
+#include <contrib/dev/acpica/include/acpi.h>
+#include <contrib/dev/acpica/include/accommon.h>
+
+#include <dev/acpica/acpivar.h>
+
+static struct sysctl_ctx_list acpi_lpit_sysctl_ctx;
+static struct sysctl_oid *lpit_sysctl_tree;
+
+struct lpi_state {
+ ACPI_GENERIC_ADDRESS residency_counter;
+ struct {
+ struct resource *res;
+ int type;
+ int rid;
+ } res;
+ uint64_t frequency;
+ bool enabled;
+} *lpi_states;
+
+static int acpi_lpit_residency_sysctl(SYSCTL_HANDLER_ARGS);
+
+static void
+init_sysctl(struct acpi_softc *sc)
+{
+ sysctl_ctx_init(&acpi_lpit_sysctl_ctx);
+
+ lpit_sysctl_tree = SYSCTL_ADD_NODE(&acpi_lpit_sysctl_ctx,
+ SYSCTL_CHILDREN(sc->acpi_sysctl_tree), OID_AUTO, "lp_idle_residency",
+ CTLFLAG_RD, NULL, "Residency for Low Power Idle states");
+}
+
+static int
+acpi_lpit_init(void *data)
+{
+ struct acpi_softc *sc;
+ ACPI_TABLE_LPIT *hdr;
+ ACPI_LPIT_NATIVE *alloc, *end;
+ ACPI_STATUS status;
+ int i, entries = 0;
+
+ /*
+ * If The system doesn't have low power idle states, we don't want to bother
+ * exposing any of the residency information since BIOS vendors tend to copy
+ * and paste code and we might get non-functional residency registers in the
+ * LPIT. Perhaps in the future a quirk table would be best.
+ */
+ sc = devclass_get_softc(devclass_find("acpi"), 0);
+ if (sc == NULL)
+ return (ENXIO);
+
+ if ((sc->acpi_supports_s0ix & PREFER_S0IX_FADT21) == 0)
+ return (ENODEV);
+
+ status = AcpiGetTable(ACPI_SIG_LPIT, 0, (ACPI_TABLE_HEADER * *) & hdr);
+ if (ACPI_FAILURE(status))
+ return (ENXIO);
+
+ init_sysctl(sc);
+
+ end = (ACPI_LPIT_NATIVE *) ((char *)hdr + hdr->Header.Length);
+ alloc = (ACPI_LPIT_NATIVE *) ((char *)hdr + sizeof(ACPI_TABLE_HEADER));
+
+ if (end != alloc) {
+ entries = (end - alloc);
+ lpi_states = mallocarray(entries, sizeof(struct lpi_state), M_DEVBUF,
+ M_ZERO | M_WAITOK);
+ }
+
+ for (i = 0; alloc < end; alloc++, i++) {
+ struct lpi_state *state;
+ char name[16];
+ int id = alloc->Header.UniqueId;
+
+ KASSERT(i < entries, ("Invalid entries i=%d, entries=%d (%p %p)",
+ i, entries, alloc, end));
+
+ state = &lpi_states[i];
+
+ state->enabled = false;
+
+ /*
+ * This checks for there being a residency counter maintained by a
+ * microcontroller which is MMIO mapped (not an MSR). It's not a
+ * surefire indication that the system supports s0ix, but it's a good
+ * hack that is used by Linux.
+ */
+ if (alloc->Header.Type != ACPI_LPIT_TYPE_NATIVE_CSTATE)
+ continue;
+
+ if (alloc->Header.Flags & ACPI_LPIT_STATE_DISABLED)
+ continue;
+
+ if (alloc->ResidencyCounter.SpaceId == ACPI_ADR_SPACE_SYSTEM_MEMORY)
+ sc->acpi_supports_s0ix |= PREFER_S0IX_LPIT;
+
+ state->enabled = true;
+ state->residency_counter = alloc->ResidencyCounter;
+ state->frequency = alloc->CounterFrequency ?:
+ atomic_load_acq_64(&tsc_freq);
+ if (alloc->ResidencyCounter.SpaceId != ACPI_ADR_SPACE_FIXED_HARDWARE) {
+ acpi_bus_alloc_gas(sc->acpi_dev, &state->res.type, &state->res.rid,
+ &alloc->ResidencyCounter, &state->res.res, 0);
+ }
+
+ snprintf(name, sizeof(name), "LPI%d", id);
+ SYSCTL_ADD_PROC(&acpi_lpit_sysctl_ctx,
+ SYSCTL_CHILDREN(lpit_sysctl_tree), id, name, CTLTYPE_U64 | CTLFLAG_RD,
+ sc->acpi_dev, i, acpi_lpit_residency_sysctl, "QU",
+ "Low Power Idle Residency");
+ }
+
+ return (0);
+}
+
+SYSINIT(acpi_lpit, SI_SUB_INTRINSIC_POST, SI_ORDER_ANY, acpi_lpit_init, NULL);
+
+static uint64_t
+calculate_residency(struct lpi_state *state)
+{
+ uint64_t residency = 0;
+ int ret = 0;
+
+ if (state->residency_counter.SpaceId == ACPI_ADR_SPACE_FIXED_HARDWARE) {
+ ret = rdmsr_safe(state->residency_counter.Address, &residency);
+ if (ret)
+ printf("Error occured while reading MSR: 0x%lx\n",
+ state->residency_counter.Address);
+ } else
+ residency = bus_read_8(state->res.res, 0);
+
+ return residency / state->frequency;
+}
+
+static int
+acpi_lpit_residency_sysctl(SYSCTL_HANDLER_ARGS)
+{
+ device_t dev;
+ struct lpi_state *state;
+ uint64_t residency;
+
+ dev = (device_t)arg1;
+ state = (struct lpi_state *)&(lpi_states[arg2]);
+
+ residency = calculate_residency(state);
+
+ return (sysctl_handle_64(oidp, &residency, 0, req));
+}
Index: sys/dev/acpica/acpi_spmc.c
===================================================================
--- /dev/null
+++ sys/dev/acpica/acpi_spmc.c
@@ -0,0 +1,417 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2018 Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/types.h>
+#include <sys/sbuf.h>
+
+#include <contrib/dev/acpica/include/acpi.h>
+#include <contrib/dev/acpica/include/accommon.h>
+
+#include <dev/acpica/acpivar.h>
+
+ACPI_MODULE_NAME("SPMC")
+
+/* Published specs only have rev 0, but Linux uses rev 1 */
+#define SPMC_REVISION 1
+enum {
+ LPS0_DEVICE_CONSTRAINTS = 1,
+ LPS0_CRASH_DUMP_DEV = 2,
+ LPS0_SCREEN_OFF = 3,
+ LPS0_SCREEN_ON = 4,
+ LPS0_ENTRY = 5,
+ LPS0_EXIT = 6
+};
+
+static uint8_t lps0_uuid[16] = {
+ 0xa0, 0x40, 0xeb, 0xc4, 0xd2, 0x6c, 0xe2, 0x11,
+ 0xbc, 0xfd, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66};
+
+static struct sysctl_ctx_list acpi_spmc_sysctl_ctx;
+static struct sysctl_oid *spmc_sysctl_tree;
+
+static int acpi_spmc_probe(device_t dev);
+static int acpi_spmc_attach(device_t dev);
+static int acpi_spmc_post_suspend(device_t dev);
+static int acpi_spmc_post_resume(device_t dev);
+static int walk_constraints(ACPI_HANDLE handle);
+
+/*
+ * Driver softc.
+ */
+struct acpi_spmc_softc {
+ device_t dev; /* This device */
+ ACPI_HANDLE handle; /* This device's handle */
+ ACPI_OBJECT *obj; /* The constraint object */
+};
+
+struct device_constraint {
+ SLIST_ENTRY(device_constraint) dc_link;
+ ACPI_HANDLE handle;
+ device_t dev;
+ bool configured;
+
+ ACPI_OBJECT *obj;
+
+ /* Parsed ACPI object info */
+ ACPI_STRING name;
+ ACPI_INTEGER enabled;
+ ACPI_INTEGER revision;
+ ACPI_INTEGER lpi_uid;
+ ACPI_INTEGER min_dstate;
+ ACPI_INTEGER optional;
+};
+
+static SLIST_HEAD(, device_constraint) devices_list =
+ SLIST_HEAD_INITIALIZER(devices_list);
+
+static device_method_t acpi_spmc_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, acpi_spmc_probe),
+ DEVMETHOD(device_attach, acpi_spmc_attach),
+ DEVMETHOD(device_post_suspend, acpi_spmc_post_suspend),
+ DEVMETHOD(device_post_resume, acpi_spmc_post_resume),
+
+ DEVMETHOD_END
+};
+
+static driver_t acpi_spmc_driver = {
+ "acpi_spmc",
+ acpi_spmc_methods,
+ sizeof(struct acpi_spmc_softc),
+};
+
+static devclass_t acpi_spmc_devclass;
+
+/* XXX: Try to install near the end so we get most devices during our attach. */
+DRIVER_MODULE_ORDERED(acpi_spmc, acpi, acpi_spmc_driver, acpi_spmc_devclass, 0,
+ 0, SI_ORDER_ANY);
+MODULE_DEPEND(acpi_spmc, acpi, 1, 1, 1);
+
+
+static int
+acpi_spmc_probe(device_t dev)
+{
+ char *name, desc[64];
+ static char *spmc_ids[] = {"PNP0D80", NULL};
+ uint8_t dsm_bits = 0;
+
+ /* Check that this is an enabled device */
+ if (acpi_get_type(dev) != ACPI_TYPE_DEVICE || acpi_disabled("spmc"))
+ return (ENXIO);
+
+ name = ACPI_ID_PROBE(device_get_parent(dev), dev, spmc_ids);
+ if (!name)
+ return (ENXIO);
+
+ dsm_bits = acpi_DSMQuery(acpi_get_handle(dev), lps0_uuid, SPMC_REVISION);
+ if ((dsm_bits & 1) == 0) {
+ device_printf(dev, "Useless device without _DSM\n");
+ return (ENODEV);
+ }
+
+ acpi_set_private(dev, (void *)(uintptr_t)dsm_bits);
+ snprintf(desc, sizeof(desc),
+ "System Power Management Controller: funcs (0x%02x)\n", dsm_bits);
+
+ device_set_desc_copy(dev, desc);
+
+ return (BUS_PROBE_DEFAULT);
+}
+
+/*
+ * Parses a power constraint into the provided structure.
+ *
+ * Returns ENXIO if the entire object is invalid, and EINVAL if any individual
+ * field is invalid.
+ *
+ * Table 7 Device Constraint Entry Format
+ * _____________________________________________________________________
+ * | Field Name | Format | Description |
+ * |-------------------------|--------|--------------------------------|
+ * | Device Name | String | Fully qualified Namestring |
+ * |-------------------------|--------|--------------------------------|
+ * | Device Enabled | Integer| 0=Disabled, no constraints |
+ * | | | Non-zero=Device is enabled and |
+ * | | | constraints apply |
+ * |-------------------------|--------|--------------------------------|
+ * | Device Constraint Detail| Package| see Table 8 |
+ * ---------------------------------------------------------------------
+ *
+ * Table 8 Revision Package Format
+ * _____________________________________________________________________
+ * | Field Name | Format | Description |
+ * |-------------------------|----------|------------------------------|
+ * | Revision | Integer | Integer (zero) |
+ * |-------------------------|----------|------------------------------|
+ * | Constraint Package |Constraint| Package (see Table 9) |
+ * | | Package | |
+ * ---------------------------------------------------------------------
+ *
+ * Table 9 Constraint Package
+ * ______________________________________________________________________
+ * | Field Name | Format | Description |
+ * |-------------------------|----------|-------------------------------|
+ * | LPI UID |Integer | LPIT entry (see Section 2.2) |
+ * | | | 0xFF:Constraint applies to all|
+ * | | | entries in LPIT |
+ * |-------------------------|----------|-------------------------------|
+ * | Minimum D-state | Integer | Minimum D-state constraint. |
+ * | | | 0 = D0 |
+ * | | | 1 = D1 |
+ * | | | 2 = D2 |
+ * | | | 3 = D3 |
+ * |-------------------------|----------|-------------------------------|
+ * | Minimum device-specific | Integer | device-specific information |
+ * | state Precondition | | |
+ * --------------------------|----------|--------------------------------
+ */
+static int
+parse_constraint(ACPI_OBJECT *obj, struct device_constraint *constraint)
+{
+ ACPI_OBJECT *iter;
+ ACPI_INTEGER enable;
+ int ret;
+
+ if (!ACPI_PKG_VALID(obj, 3))
+ return ENXIO;
+
+ iter = obj;
+ /* Table 7 - Device Constraint */
+ if (iter->Package.Elements[0].Type != ACPI_TYPE_STRING) {
+ ret = EINVAL;
+ goto err;
+ }
+ constraint->name = iter->Package.Elements[0].String.Pointer;
+
+ ret = acpi_PkgInt(iter, 1, &enable);
+ if (ret)
+ goto err;
+
+ if (!ACPI_PKG_VALID(&iter->Package.Elements[2], 2)) {
+ ret = EINVAL;
+ goto err;
+ }
+
+ /* Table 8 - Revision Package */
+ iter = &iter->Package.Elements[2];
+ ret = acpi_PkgInt(iter, 0, &constraint->revision);
+ if (ret)
+ goto err;
+
+ if (constraint->revision > 0)
+ printf("Unknown revision for device constraint->\n");
+
+ /* The spec allows the third element to not exist */
+ if (!ACPI_PKG_VALID(&iter->Package.Elements[1], 2)) {
+ ret = ENXIO;
+ goto err;
+ }
+
+ /* Table 9 - Constraint Package */
+ iter = &iter->Package.Elements[1];
+ ret = acpi_PkgInt(iter, 0, &constraint->lpi_uid);
+ if (ret)
+ goto err;
+ ret = acpi_PkgInt(iter, 1, &constraint->min_dstate);
+ if (ret)
+ goto err;
+
+ MPASS(constraint->name != NULL);
+
+ return (0);
+
+err:
+ device_printf(constraint->dev,
+ "Unexpected error in parsing device constraints\n");
+ return (ret);
+}
+
+static int
+walk_constraints(ACPI_HANDLE handle)
+{
+ ACPI_STATUS status;
+ ACPI_BUFFER buf;
+ ACPI_OBJECT *obj;
+ int i;
+
+ status = acpi_EvaluateDSM(handle, lps0_uuid, SPMC_REVISION,
+ LPS0_DEVICE_CONSTRAINTS, NULL, &buf);
+
+ if (ACPI_FAILURE(status))
+ return (ENXIO);
+
+ obj = (ACPI_OBJECT *) buf.Pointer;
+ if (obj == NULL || obj->Type != ACPI_TYPE_PACKAGE)
+ return (ENXIO);
+
+ for (i = 0; i < obj->Package.Count; i++) {
+ struct device_constraint *c;
+ int ret;
+
+ c = malloc(sizeof(*c), M_DEVBUF, M_ZERO | M_WAITOK);
+ ret = parse_constraint(&obj->Package.Elements[i], c);
+ if (ret) {
+ free(c, M_DEVBUF);
+ continue;
+ }
+
+ /* Add it to the list */
+ c->obj = &obj->Package.Elements[i];
+ c->handle = acpi_GetReference(NULL, &c->obj->Package.Elements[0]);
+ c->dev = acpi_get_device(c->handle);
+ SLIST_INSERT_HEAD(&devices_list, c, dc_link);
+
+ /* If we can find the device now, just handle it */
+ if (c->dev && c->min_dstate == 3)
+ device_set_idle_suspend(c->dev);
+
+ if (c->dev) {
+ c->configured = true;
+ } else if (c->handle) {
+ /*
+ * There is no device_t for this and it may or
+ * may not exist later
+ */
+ }
+ }
+
+ return (0);
+}
+
+static int
+sysctl_dump_constraints(SYSCTL_HANDLER_ARGS)
+{
+ struct device_constraint *device;
+ struct sbuf *sb;
+ int error;
+
+ error = sysctl_wire_old_buffer(req, 0);
+ if (error != 0)
+ return (error);
+
+ sb = sbuf_new_for_sysctl(NULL, NULL, 0, req);
+ if (sb == NULL)
+ return (ENOMEM);
+
+ SLIST_FOREACH(device, &devices_list, dc_link)
+ sbuf_printf(sb, "%s: D%ld\n", device->name, device->min_dstate);
+
+ error = sbuf_finish(sb);
+ sbuf_delete(sb);
+
+ return (error);
+}
+
+static int
+acpi_spmc_attach(device_t dev)
+{
+ struct acpi_spmc_softc *sc;
+ struct acpi_softc *acpi_sc;
+ device_t nexus;
+ uint8_t dsm_bits;
+
+ sc = device_get_softc(dev);
+ sc->dev = dev;
+ sc->handle = acpi_get_handle(dev);
+
+ acpi_sc = acpi_device_get_parent_softc(sc->dev);
+
+ dsm_bits = (uint8_t)acpi_get_private(dev);
+ if (dsm_bits & LPS0_DEVICE_CONSTRAINTS) {
+ int ret = walk_constraints(sc->handle);
+ if (ret)
+ return ret;
+
+ /*
+ * This will be the final piece of enabling s0ix for suspend
+ * to idle.
+ */
+ if ((dsm_bits & (LPS0_ENTRY | LPS0_EXIT)) == (LPS0_ENTRY | LPS0_EXIT)) {
+ acpi_sc->acpi_supports_s0ix |= PREFER_S0IX_DSM;
+ nexus = device_find_child(root_bus, "nexus", 0);
+ device_set_idle_suspend(nexus);
+ }
+ }
+
+ acpi_sc->acpi_pm_device = dev;
+
+ spmc_sysctl_tree = SYSCTL_ADD_NODE(&acpi_spmc_sysctl_ctx,
+ SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree), OID_AUTO, "SPMC",
+ CTLFLAG_RD, NULL, "System Power Management Controller");
+
+ SYSCTL_ADD_PROC(&acpi_spmc_sysctl_ctx,
+ SYSCTL_CHILDREN(spmc_sysctl_tree), OID_AUTO, "min_dstate",
+ CTLTYPE_STRING | CTLFLAG_RD, NULL, 0, sysctl_dump_constraints, "A",
+ "Minimum dstates");
+
+ return (0);
+}
+
+static int
+spmc_do_dsm(device_t dev, uint32_t cmd)
+{
+ ACPI_BUFFER b;
+ ACPI_STATUS err;
+
+ err = acpi_EvaluateDSM(acpi_get_handle(dev), lps0_uuid, SPMC_REVISION,
+ cmd, NULL, &b);
+ if (ACPI_SUCCESS(err))
+ AcpiOsFree(b.Pointer);
+
+ return err;
+}
+
+static int
+acpi_spmc_post_suspend(device_t dev)
+{
+ if (spmc_do_dsm(dev, LPS0_SCREEN_OFF))
+ return (ENODEV);
+
+ if (spmc_do_dsm(dev, LPS0_ENTRY)) {
+ spmc_do_dsm(dev, LPS0_SCREEN_ON);
+ return (ENODEV);
+ }
+
+ return (0);
+}
+
+static int
+acpi_spmc_post_resume(device_t dev)
+{
+ spmc_do_dsm(dev, LPS0_EXIT);
+ spmc_do_dsm(dev, LPS0_SCREEN_ON);
+ return (0);
+}
Index: sys/dev/acpica/acpivar.h
===================================================================
--- sys/dev/acpica/acpivar.h
+++ sys/dev/acpica/acpivar.h
@@ -102,6 +102,11 @@
int flags;
int saved_flags;
} acpi_repressed_states;
+#define PREFER_S0IX_FADT21 (1 << 0)
+#define PREFER_S0IX_LPIT (1 << 1)
+#define PREFER_S0IX_DSM (1 << 2)
+ int acpi_supports_s0ix; /* Platform prefers s0ix */
+ device_t acpi_pm_device;
vm_offset_t acpi_wakeaddr;
vm_paddr_t acpi_wakephys;
Index: sys/kern/device_if.m
===================================================================
--- sys/kern/device_if.m
+++ sys/kern/device_if.m
@@ -58,11 +58,21 @@
return 0;
}
+ static int null_post_suspend(device_t dev)
+ {
+ return 0;
+ }
+
static int null_resume(device_t dev)
{
return 0;
}
+ static int null_post_resume(device_t dev)
+ {
+ return 0;
+ }
+
static int null_quiesce(device_t dev)
{
return 0;
@@ -291,6 +301,38 @@
device_t dev;
} DEFAULT null_suspend;
+/**
+ * @brief This is called by the power-management subsystem *after* suspending
+ * other devices.
+ *
+ * This gives special power management devices a chance to run their preparation
+ * routines when needed just before idle. An example of such a case is certain
+ * platforms contain a System Power Management Controller, which has hooks to
+ * enable deeper sleep states, but only after the rest of the system has gone
+ * down.
+ *
+ * Generally only one device in the system should actively have an
+ * implementation of this routine.
+ *
+ * To include this method in a device driver, use a line like this in the
+ * driver's method list:
+ *
+ * @code
+ * DEVMETHOD(device_post_suspend, foo_post_suspend)
+ * @endcode
+ *
+ * @param dev the device orchestrating deeper suspend
+ *
+ * @retval 0 success
+ * @retval non-zero an error occurred preventing deeper suspend
+ *
+ * @see DEVICE_POST_RESUME()
+ */
+
+METHOD int post_suspend {
+ device_t dev;
+} DEFAULT null_post_suspend;
+
/**
* @brief This is called when the system resumes after a suspend.
*
@@ -313,6 +355,28 @@
device_t dev;
} DEFAULT null_resume;
+/**
+ * @brief This is called *after* a system resumes from suspend, but *before*
+ * resuming devices.
+ *
+ * To include this method in a device driver, use a line like this
+ * in the driver's method list:
+ *
+ * @code
+ * DEVMETHOD(device_post_resume, foo_post_resume)
+ * @endcode
+ *
+ * @param dev the device orchestrating deeper suspend
+ *
+ * @retval 0 success
+ * @retval non-zero an error occurred preventing deeper suspend
+ *
+ * @see DEVICE_POST_SUSPEND()
+ */
+METHOD int post_resume {
+ device_t dev;
+} DEFAULT null_post_resume;
+
/**
* @brief This is called when the driver is asked to quiesce itself.
*
Index: sys/kern/subr_bus.c
===================================================================
--- sys/kern/subr_bus.c
+++ sys/kern/subr_bus.c
@@ -3102,6 +3102,18 @@
return (0);
}
+void
+device_set_idle_suspend(device_t dev)
+{
+ dev->flags |= DF_SUSPEND_AT_IDLE;
+}
+
+int
+device_get_idle_suspend(device_t dev)
+{
+ return ((dev->flags & DF_SUSPEND_AT_IDLE) != 0);
+}
+
/*======================================*/
/*
* Some useful method implementations to make life easier for bus drivers.
Index: sys/sys/bus.h
===================================================================
--- sys/sys/bus.h
+++ sys/sys/bus.h
@@ -95,6 +95,7 @@
#define DF_QUIET_CHILDREN 0x200 /* Default to quiet for all my children */
#define DF_ATTACHED_ONCE 0x400 /* Has been attached at least once */
#define DF_NEEDNOMATCH 0x800 /* Has a pending NOMATCH event */
+#define DF_SUSPEND_AT_IDLE 0x1000 /* Suspend this device when idling */
/**
* @brief Device request structure used for ioctl's.
@@ -622,6 +623,8 @@
int device_shutdown(device_t dev);
void device_unbusy(device_t dev);
void device_verbose(device_t dev);
+void device_set_idle_suspend(device_t dev);
+int device_get_idle_suspend(device_t dev);
/*
* Access functions for devclass.
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Fri, Apr 10, 4:35 AM (20 h, 16 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
31199687
Default Alt Text
D17676.id49532.diff (30 KB)
Attached To
Mode
D17676: Emulated S3 with s0ix (not currently working)
Attached
Detach File
Event Timeline
Log In to Comment