diff --git a/sys/conf/files b/sys/conf/files --- a/sys/conf/files +++ b/sys/conf/files @@ -3504,6 +3504,7 @@ dev/xen/bus/xenpv.c optional xenhvm dev/xen/console/xen_console.c optional xenhvm dev/xen/control/control.c optional xenhvm +dev/xen/cpu/xen_acpi_cpu.c optional xenhvm dev/xen/efi/pvefi.c optional xenhvm efirt dev/xen/grant_table/grant_table.c optional xenhvm dev/xen/netback/netback.c optional xenhvm diff --git a/sys/contrib/xen/platform.h b/sys/contrib/xen/platform.h --- a/sys/contrib/xen/platform.h +++ b/sys/contrib/xen/platform.h @@ -465,7 +465,11 @@ uint32_t state_count; /* total available performance states */ XEN_GUEST_HANDLE(xen_processor_px_t) states; struct xen_psd_package domain_info; - uint32_t shared_type; /* coordination type of this processor */ + /* Coordination type of this processor */ +#define XEN_CPUPERF_SHARED_TYPE_HW 1 /* HW does needed coordination */ +#define XEN_CPUPERF_SHARED_TYPE_ALL 2 /* All dependent CPUs should set freq */ +#define XEN_CPUPERF_SHARED_TYPE_ANY 3 /* Freq can be set from any dependent CPU */ + uint32_t shared_type; }; typedef struct xen_processor_performance xen_processor_performance_t; DEFINE_XEN_GUEST_HANDLE(xen_processor_performance_t); diff --git a/sys/dev/acpica/acpi_cpu.c b/sys/dev/acpica/acpi_cpu.c --- a/sys/dev/acpica/acpi_cpu.c +++ b/sys/dev/acpica/acpi_cpu.c @@ -300,7 +300,7 @@ device_quiet_children(dev); } - return (0); + return (BUS_PROBE_DEFAULT); } static int diff --git a/sys/dev/xen/cpu/xen_acpi_cpu.c b/sys/dev/xen/cpu/xen_acpi_cpu.c new file mode 100644 --- /dev/null +++ b/sys/dev/xen/cpu/xen_acpi_cpu.c @@ -0,0 +1,605 @@ +/*- + * Copyright (c) 2022 Citrix Systems R&D + * Copyright (c) 2003-2005 Nate Lawson (SDG) + * Copyright (c) 2001 Michael Smith + * All rights reserved. + * + * 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 "opt_acpi.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#include + +#define ACPI_DOMAIN_COORD_TYPE_SW_ALL 0xfc +#define ACPI_DOMAIN_COORD_TYPE_SW_ANY 0xfd +#define ACPI_DOMAIN_COORD_TYPE_HW_ALL 0xfe + +#define ACPI_NOTIFY_PERF_STATES 0x80 /* _PSS changed. */ +#define ACPI_NOTIFY_CX_STATES 0x81 /* _CST changed. */ + +static MALLOC_DEFINE(M_XENACPI, "xen_acpi", "Xen CPU ACPI forwarder"); + +/* Hooks for the ACPI CA debugging infrastructure */ +#define _COMPONENT ACPI_PROCESSOR +ACPI_MODULE_NAME("PROCESSOR") + +struct xen_acpi_cpu_softc { + device_t cpu_dev; + ACPI_HANDLE cpu_handle; + uint32_t cpu_acpi_id; + struct xen_processor_cx *cpu_cx_states; + unsigned int cpu_cx_count; + struct xen_processor_px *cpu_px_states; + unsigned int cpu_px_count; + struct xen_pct_register control_register; + struct xen_pct_register status_register; + struct xen_psd_package psd; +}; + +#define CPUDEV_DEVICE_ID "ACPI0007" + +ACPI_SERIAL_DECL(cpu, "ACPI CPU"); + +#define device_printf(dev,...) \ + if (!device_is_quiet(dev)) \ + device_printf((dev), __VA_ARGS__) + +static int +acpi_get_gas(const ACPI_OBJECT *res, unsigned int idx, + ACPI_GENERIC_ADDRESS *gas) +{ + const ACPI_OBJECT *obj = &res->Package.Elements[idx]; + + if (obj == NULL || obj->Type != ACPI_TYPE_BUFFER || + obj->Buffer.Length < sizeof(ACPI_GENERIC_ADDRESS) + 3) + return (EINVAL); + + memcpy(gas, obj->Buffer.Pointer + 3, sizeof(*gas)); + + return (0); +} + +static int +acpi_get_pct(const ACPI_OBJECT *res, unsigned int idx, + struct xen_pct_register *reg) +{ + struct { + uint8_t descriptor; + uint16_t length; + ACPI_GENERIC_ADDRESS gas; + } __packed raw; + const ACPI_OBJECT *obj = &res->Package.Elements[idx]; + + if (obj == NULL || obj->Type != ACPI_TYPE_BUFFER || + obj->Buffer.Length < sizeof(raw)) + return (EINVAL); + + memcpy(&raw, obj->Buffer.Pointer, sizeof(raw)); + reg->descriptor = raw.descriptor; + reg->length = raw.length; + reg->space_id = raw.gas.SpaceId; + reg->bit_width = raw.gas.BitWidth; + reg->bit_offset = raw.gas.BitOffset; + reg->reserved = raw.gas.AccessWidth; + reg->address = raw.gas.Address; + + return (0); +} + +static int +xen_upload_cx(struct xen_acpi_cpu_softc *sc) +{ + struct xen_platform_op op = { + .cmd = XENPF_set_processor_pminfo, + .interface_version = XENPF_INTERFACE_VERSION, + .u.set_pminfo.id = sc->cpu_acpi_id, + .u.set_pminfo.type = XEN_PM_CX, + .u.set_pminfo.u.power.count = sc->cpu_cx_count, + .u.set_pminfo.u.power.flags.has_cst = 1, + /* Ignore bm_check and bm_control, Xen will set those. */ + }; + int error; + + set_xen_guest_handle(op.u.set_pminfo.u.power.states, sc->cpu_cx_states); + + error = HYPERVISOR_platform_op(&op); + if (error != 0) + device_printf(sc->cpu_dev, + "ACPI ID %u Cx upload failed: %d\n", sc->cpu_acpi_id, + error); + return (error); +} + +static int +xen_upload_px(struct xen_acpi_cpu_softc *sc) +{ + struct xen_platform_op op = { + .cmd = XENPF_set_processor_pminfo, + .interface_version = XENPF_INTERFACE_VERSION, + .u.set_pminfo.id = sc->cpu_acpi_id, + .u.set_pminfo.type = XEN_PM_PX, + .u.set_pminfo.u.perf.state_count = sc->cpu_px_count, + .u.set_pminfo.u.perf.control_register = sc->control_register, + .u.set_pminfo.u.perf.status_register = sc->status_register, + .u.set_pminfo.u.perf.domain_info = sc->psd, + .u.set_pminfo.u.perf.flags = XEN_PX_PPC | XEN_PX_PCT | + XEN_PX_PSS | XEN_PX_PSD, + }; + ACPI_STATUS status; + int error; + + status = acpi_GetInteger(sc->cpu_handle, "_PPC", + &op.u.set_pminfo.u.perf.platform_limit); + if (ACPI_FAILURE(status)) { + device_printf(sc->cpu_dev, "missing _PPC object\n"); + return (ENXIO); + } + + set_xen_guest_handle(op.u.set_pminfo.u.perf.states, sc->cpu_px_states); + + /* + * NB: it's unclear the exact purpose of the shared_type field, or why + * it can't be calculated by Xen itself. Naively set it here to allow + * the upload to succeed. + */ + switch (sc->psd.coord_type) { + case ACPI_DOMAIN_COORD_TYPE_SW_ALL: + op.u.set_pminfo.u.perf.shared_type = + XEN_CPUPERF_SHARED_TYPE_ALL; + break; + + case ACPI_DOMAIN_COORD_TYPE_HW_ALL: + op.u.set_pminfo.u.perf.shared_type = + XEN_CPUPERF_SHARED_TYPE_HW; + break; + + case ACPI_DOMAIN_COORD_TYPE_SW_ANY: + op.u.set_pminfo.u.perf.shared_type = + XEN_CPUPERF_SHARED_TYPE_ANY; + break; + default: + device_printf(sc->cpu_dev, + "unknown coordination type %#" PRIx64 "\n", + sc->psd.coord_type); + return (EINVAL); + } + + error = HYPERVISOR_platform_op(&op); + if (error != 0) + device_printf(sc->cpu_dev, + "ACPI ID %u Px upload failed: %d\n", sc->cpu_acpi_id, error); + return (error); +} + +static int +acpi_set_pdc(const struct xen_acpi_cpu_softc *sc) +{ + struct xen_platform_op op = { + .cmd = XENPF_set_processor_pminfo, + .interface_version = XENPF_INTERFACE_VERSION, + .u.set_pminfo.id = -1, + .u.set_pminfo.type = XEN_PM_PDC, + }; + uint32_t pdc[3] = {1, 1}; + ACPI_OBJECT arg = { + .Buffer.Type = ACPI_TYPE_BUFFER, + .Buffer.Length = sizeof(pdc), + .Buffer.Pointer = (uint8_t *)pdc, + }; + ACPI_OBJECT_LIST arglist = { + .Pointer = &arg, + .Count = 1, + }; + ACPI_STATUS status; + int error; + + set_xen_guest_handle(op.u.set_pminfo.u.pdc, pdc); + error = HYPERVISOR_platform_op(&op); + if (error != 0) { + device_printf(sc->cpu_dev, + "unable to get _PDC features from Xen: %d\n", error); + return (error); + } + + status = AcpiEvaluateObject(sc->cpu_handle, "_PDC", &arglist, NULL); + if (ACPI_FAILURE(status)) { + device_printf(sc->cpu_dev, "unable to execute _PDC - %s\n", + AcpiFormatException(status)); + return (ENXIO); + } + + return (0); +} + +/* + * Parse a _CST package and set up its Cx states. Since the _CST object + * can change dynamically, our notify handler may call this function + * to clean up and probe the new _CST package. + */ +static int +acpi_fetch_cx(struct xen_acpi_cpu_softc *sc) +{ + ACPI_STATUS status; + ACPI_BUFFER buf = { + .Length = ACPI_ALLOCATE_BUFFER, + }; + ACPI_OBJECT *top; + uint32_t count; + unsigned int i; + + status = AcpiEvaluateObject(sc->cpu_handle, "_CST", NULL, &buf); + if (ACPI_FAILURE(status)) { + device_printf(sc->cpu_dev, "missing _CST object\n"); + return (ENXIO); + } + + /* _CST is a package with a count and at least one Cx package. */ + top = (ACPI_OBJECT *)buf.Pointer; + if (!ACPI_PKG_VALID(top, 2) || acpi_PkgInt32(top, 0, &count) != 0) { + device_printf(sc->cpu_dev, "invalid _CST package\n"); + AcpiOsFree(buf.Pointer); + return (ENXIO); + } + if (count != top->Package.Count - 1) { + device_printf(sc->cpu_dev, + "invalid _CST state count (%u != %u)\n", + count, top->Package.Count - 1); + count = top->Package.Count - 1; + } + + sc->cpu_cx_states = mallocarray(count, sizeof(struct xen_processor_cx), + M_XENACPI, M_WAITOK | M_ZERO); + + sc->cpu_cx_count = 0; + for (i = 0; i < count; i++) { + uint32_t type; + ACPI_GENERIC_ADDRESS gas; + ACPI_OBJECT *pkg = &top->Package.Elements[i + 1]; + struct xen_processor_cx *cx_ptr = + &sc->cpu_cx_states[sc->cpu_cx_count]; + + if (!ACPI_PKG_VALID(pkg, 4) || + acpi_PkgInt32(pkg, 1, &type) != 0 || + acpi_PkgInt32(pkg, 2, &cx_ptr->latency) != 0 || + acpi_PkgInt32(pkg, 3, &cx_ptr->power) != 0 || + acpi_get_gas(pkg, 0, &gas) != 0) { + device_printf(sc->cpu_dev, + "skipping invalid _CST %u package\n", + i + 1); + continue; + } + + cx_ptr->type = type; + cx_ptr->reg.space_id = gas.SpaceId; + cx_ptr->reg.bit_width = gas.BitWidth; + cx_ptr->reg.bit_offset = gas.BitOffset; + cx_ptr->reg.access_size = gas.AccessWidth; + cx_ptr->reg.address = gas.Address; + sc->cpu_cx_count++; + } + AcpiOsFree(buf.Pointer); + + if (sc->cpu_cx_count == 0) { + device_printf(sc->cpu_dev, "no valid _CST package found\n"); + free(sc->cpu_cx_states, M_XENACPI); + sc->cpu_cx_states = NULL; + return (ENXIO); + } + + return (0); +} + +/* Probe and setup any valid performance states (Px). */ +static int +acpi_fetch_px(struct xen_acpi_cpu_softc *sc) +{ + ACPI_BUFFER buf = { + .Length = ACPI_ALLOCATE_BUFFER, + }; + ACPI_OBJECT *pkg, *res; + ACPI_STATUS status; + unsigned int count, i; + int error; + uint64_t *p; + + /* _PSS */ + status = AcpiEvaluateObject(sc->cpu_handle, "_PSS", NULL, &buf); + if (ACPI_FAILURE(status)) { + device_printf(sc->cpu_dev, "missing _PSS object\n"); + return (ENXIO); + } + + pkg = (ACPI_OBJECT *)buf.Pointer; + if (!ACPI_PKG_VALID(pkg, 1)) { + device_printf(sc->cpu_dev, "invalid top level _PSS package\n"); + goto error; + } + count = pkg->Package.Count; + + sc->cpu_px_states = mallocarray(count, sizeof(struct xen_processor_px), + M_XENACPI, M_WAITOK | M_ZERO); + + /* + * Each state is a package of {CoreFreq, Power, TransitionLatency, + * BusMasterLatency, ControlVal, StatusVal}, sorted from highest + * performance to lowest. + */ + sc->cpu_px_count = 0; + for (i = 0; i < count; i++) { + unsigned int j; + + res = &pkg->Package.Elements[i]; + if (!ACPI_PKG_VALID(res, 6)) { + device_printf(sc->cpu_dev, + "invalid _PSS package idx %u\n", i); + continue; + } + + /* Parse the rest of the package into the struct. */ + p = (uint64_t *)&sc->cpu_px_states[sc->cpu_px_count++]; + for (j = 0; j < 6; j++, p++) + acpi_PkgInt(res, j, p); + } + + /* No valid Px state found so give up. */ + if (sc->cpu_px_count == 0) { + device_printf(sc->cpu_dev, "no valid _PSS package found\n"); + goto error; + } + AcpiOsFree(buf.Pointer); + + /* _PCT */ + buf.Pointer = NULL; + buf.Length = ACPI_ALLOCATE_BUFFER; + status = AcpiEvaluateObject(sc->cpu_handle, "_PCT", NULL, &buf); + if (ACPI_FAILURE(status)) { + device_printf(sc->cpu_dev, "missing _PCT object\n"); + goto error; + } + + /* Check the package of two registers, each a Buffer in GAS format. */ + pkg = (ACPI_OBJECT *)buf.Pointer; + if (!ACPI_PKG_VALID(pkg, 2)) { + device_printf(sc->cpu_dev, "invalid top level _PCT package\n"); + goto error; + } + + error = acpi_get_pct(pkg, 0, &sc->control_register); + if (error != 0) { + device_printf(sc->cpu_dev, + "unable to fetch _PCT control register: %d\n", error); + goto error; + } + error = acpi_get_pct(pkg, 1, &sc->status_register); + if (error != 0) { + device_printf(sc->cpu_dev, + "unable to fetch _PCT status register: %d\n", error); + goto error; + } + AcpiOsFree(buf.Pointer); + + /* _PSD */ + buf.Pointer = NULL; + buf.Length = ACPI_ALLOCATE_BUFFER; + status = AcpiEvaluateObject(sc->cpu_handle, "_PSD", NULL, &buf); + if (ACPI_FAILURE(status)) { + device_printf(sc->cpu_dev, "missing _PSD object\n"); + goto error; + } + + pkg = (ACPI_OBJECT *)buf.Pointer; + if (!ACPI_PKG_VALID(pkg, 1)) { + device_printf(sc->cpu_dev, "invalid top level _PSD package\n"); + goto error; + } + + res = &pkg->Package.Elements[0]; + if (!ACPI_PKG_VALID(res, 5)) { + printf("invalid _PSD package\n"); + goto error; + } + + p = (uint64_t *)&sc->psd; + for (i = 0; i < 5; i++, p++) + acpi_PkgInt(res, i, p); + AcpiOsFree(buf.Pointer); + + return (0); + +error: + if (buf.Pointer != NULL) + AcpiOsFree(buf.Pointer); + if (sc->cpu_px_states != NULL) { + free(sc->cpu_px_states, M_XENACPI); + sc->cpu_px_states = NULL; + } + return (ENXIO); +} + +static void +acpi_notify(ACPI_HANDLE h, UINT32 notify, void *context) +{ + struct xen_acpi_cpu_softc *sc = context; + + switch (notify) { + case ACPI_NOTIFY_PERF_STATES: + if (acpi_fetch_px(sc) != 0) + break; + xen_upload_px(sc); + free(sc->cpu_px_states, M_XENACPI); + sc->cpu_px_states = NULL; + break; + + case ACPI_NOTIFY_CX_STATES: + if (acpi_fetch_cx(sc) != 0) + break; + xen_upload_cx(sc); + free(sc->cpu_cx_states, M_XENACPI); + sc->cpu_cx_states = NULL; + break; + } +} + +static int +xen_acpi_cpu_probe(device_t dev) +{ + static char *cpudev_ids[] = { CPUDEV_DEVICE_ID, NULL }; + ACPI_OBJECT_TYPE type = acpi_get_type(dev); + + if (!xen_initial_domain()) + return (ENXIO); + if (type != ACPI_TYPE_PROCESSOR && type != ACPI_TYPE_DEVICE) + return (ENXIO); + if (type == ACPI_TYPE_DEVICE && + ACPI_ID_PROBE(device_get_parent(dev), dev, cpudev_ids, NULL) >= 0) + return (ENXIO); + + device_set_desc(dev, "XEN ACPI CPU"); + if (!bootverbose) + device_quiet(dev); + + /* + * Use SPECIFIC because when running as a Xen dom0 the ACPI PROCESSOR + * data is the native one, and needs to be forwarded to Xen but not + * used by FreeBSD itself. + */ + return (BUS_PROBE_SPECIFIC); +} + +static int +xen_acpi_cpu_attach(device_t dev) +{ + struct xen_acpi_cpu_softc *sc = device_get_softc(dev); + ACPI_STATUS status; + int error; + + sc->cpu_dev = dev; + sc->cpu_handle = acpi_get_handle(dev); + + if (acpi_get_type(dev) == ACPI_TYPE_PROCESSOR) { + ACPI_BUFFER buf = { + .Length = ACPI_ALLOCATE_BUFFER, + }; + ACPI_OBJECT *obj; + + status = AcpiEvaluateObject(sc->cpu_handle, NULL, NULL, &buf); + if (ACPI_FAILURE(status)) { + device_printf(dev, + "attach failed to get Processor obj - %s\n", + AcpiFormatException(status)); + return (ENXIO); + } + obj = (ACPI_OBJECT *)buf.Pointer; + sc->cpu_acpi_id = obj->Processor.ProcId; + AcpiOsFree(obj); + } else { + KASSERT(acpi_get_type(dev) == ACPI_TYPE_DEVICE, + ("Unexpected ACPI object")); + status = acpi_GetInteger(sc->cpu_handle, "_UID", + &sc->cpu_acpi_id); + if (ACPI_FAILURE(status)) { + device_printf(dev, "device object has bad value - %s\n", + AcpiFormatException(status)); + return (ENXIO); + } + } + + /* + * Install the notify handler now: even if we fail to parse or upload + * the states it shouldn't prevent us from attempting to parse further + * updates. + */ + status = AcpiInstallNotifyHandler(sc->cpu_handle, ACPI_DEVICE_NOTIFY, + acpi_notify, sc); + if (ACPI_FAILURE(status)) + device_printf(dev, "failed to register notify handler - %s\n", + AcpiFormatException(status)); + + /* + * Don't report errors: it's likely there are processor objects + * belonging to CPUs that are not online, but the MADT provided to + * FreeBSD is crafted to report the number of CPUs available to dom0. + * + * Parsing or uploading those states could result in errors, just + * ignore them in order to avoid pointless noise. + */ + error = acpi_set_pdc(sc); + if (error != 0) + return (0); + + error = acpi_fetch_px(sc); + if (error != 0) + return (0); + error = xen_upload_px(sc); + free(sc->cpu_px_states, M_XENACPI); + sc->cpu_px_states = NULL; + if (error != 0) + return (0); + + error = acpi_fetch_cx(sc); + if (error != 0) + return (0); + xen_upload_cx(sc); + free(sc->cpu_cx_states, M_XENACPI); + sc->cpu_cx_states = NULL; + + return (0); +} + +static device_method_t xen_acpi_cpu_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, xen_acpi_cpu_probe), + DEVMETHOD(device_attach, xen_acpi_cpu_attach), + + DEVMETHOD_END +}; + +static driver_t xen_acpi_cpu_driver = { + "xen cpu", + xen_acpi_cpu_methods, + sizeof(struct xen_acpi_cpu_softc), +}; + +static devclass_t xen_acpi_cpu_devclass; +DRIVER_MODULE(xen_cpu, acpi, xen_acpi_cpu_driver, xen_acpi_cpu_devclass, 0, 0); +MODULE_DEPEND(xen_cpu, acpi, 1, 1, 1);