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 #include #include +#include +#include #endif #include #include @@ -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,14 @@ 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) { + if (DEVICE_POST_SUSPEND(sc->acpi_pm_device) == 0) + slp_state = ACPI_SS_DEV_POST_SUSPEND; + else + device_printf(sc->acpi_dev, "post suspend failed, carrying on\n"); + } if (stype != SUSPEND_TO_IDLE) { status = AcpiEnterSleepStatePrep(state); @@ -3211,7 +3286,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 +3302,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 +3323,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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include + +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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +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.