Page MenuHomeFreeBSD

D17676.id49532.diff
No OneTemporary

D17676.id49532.diff

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

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)

Event Timeline