Index: sys/conf/files.amd64 =================================================================== --- sys/conf/files.amd64 +++ sys/conf/files.amd64 @@ -204,6 +204,7 @@ 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_spmc.c =================================================================== --- /dev/null +++ sys/dev/acpica/acpi_spmc.c @@ -0,0 +1,351 @@ +/*- + * 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 + +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}; + +/* + * 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 int acpi_spmc_probe(device_t dev); +static int acpi_spmc_attach(device_t dev); +static int walk_constraints(ACPI_HANDLE handle); + +static device_method_t acpi_spmc_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, acpi_spmc_probe), + DEVMETHOD(device_attach, acpi_spmc_attach), + + 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 struct device_constraint * +get_constraint(device_t dev) +{ + struct device_constraint *device; + SLIST_FOREACH(device, &devices_list, dc_link) { + if (device->dev == dev) + return device; + } + + return NULL; +} + +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, &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 +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_prefer_s0ix |= PREFER_S0IX_DSM; + nexus = device_find_child(root_bus, "nexus", 0); + device_set_idle_suspend(nexus); + } + } + + acpi_sc->pm_device = dev; + + return (0); +} Index: sys/dev/acpica/acpivar.h =================================================================== --- sys/dev/acpica/acpivar.h +++ sys/dev/acpica/acpivar.h @@ -75,7 +75,9 @@ int acpi_handle_reboot; #define PREFER_S0IX_FADT21 (1 << 0) #define PREFER_S0IX_LPIT (1 << 1) +#define PREFER_S0IX_DSM (1 << 2) u_int acpi_prefer_s0ix; /* Autodetect suggests we use s0ix */ + device_t pm_device; /* Implements ACPI power interface */ vm_offset_t acpi_wakeaddr; vm_paddr_t acpi_wakephys; Index: sys/kern/subr_bus.c =================================================================== --- sys/kern/subr_bus.c +++ sys/kern/subr_bus.c @@ -3092,6 +3092,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. @@ -621,6 +622,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.