git: b93f47eaeef7 - main - xen/acpi: upload Cx and Px data to Xen

From: Roger Pau Monné <royger_at_FreeBSD.org>
Date: Tue, 12 Apr 2022 08:04:41 UTC
The branch main has been updated by royger:

URL: https://cgit.FreeBSD.org/src/commit/?id=b93f47eaeef75b2b99fc1c501fb7cffac34068c7

commit b93f47eaeef75b2b99fc1c501fb7cffac34068c7
Author:     Roger Pau Monné <royger@FreeBSD.org>
AuthorDate: 2022-03-17 13:40:19 +0000
Commit:     Roger Pau Monné <royger@FreeBSD.org>
CommitDate: 2022-04-12 08:03:26 +0000

    xen/acpi: upload Cx and Px data to Xen
    
    When FreeBSD is running as dom0 (initial domain) on a Xen system it
    has access to the native ACPI tables and is the OSPM. However the
    hypervisor is the entity in charge of the CPU idle and frequency
    states, and in order to perform this duty it requires information
    found the ACPI dynamic tables that can only be parsed by the OSPM.
    
    Introduce a new Xen specific ACPI driver to fetch the Processor
    related information and upload it to Xen. Note that this driver needs
    to take precedence over the generic ACPI CPU driver when running as
    dom0, so downgrade the probe score of the native driver to
    BUS_PROBE_DEFAULT in order for the Xen specific driver to use
    BUS_PROBE_SPECIFIC.
    
    Tested on an Intel NUC to successfully parse and upload both the Cx and
    Px states to Xen.
    
    Sponsored by: Citrix Systems R&D
    Reviewed by: jhb kib
    Differential revision: https://reviews.freebsd.org/D34841
---
 sys/conf/files                 |   1 +
 sys/contrib/xen/platform.h     |   6 +-
 sys/dev/acpica/acpi_cpu.c      |   2 +-
 sys/dev/xen/cpu/xen_acpi_cpu.c | 605 +++++++++++++++++++++++++++++++++++++++++
 4 files changed, 612 insertions(+), 2 deletions(-)

diff --git a/sys/conf/files b/sys/conf/files
index 60f52d4c6be1..e11e11ef40ad 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -3504,6 +3504,7 @@ dev/xen/blkback/blkback.c	optional xenhvm
 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
index a4c0eb62249a..81001335094b 100644
--- a/sys/contrib/xen/platform.h
+++ b/sys/contrib/xen/platform.h
@@ -465,7 +465,11 @@ struct xen_processor_performance {
     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
index 9530b8867536..a8bf2ee8cd47 100644
--- a/sys/dev/acpica/acpi_cpu.c
+++ b/sys/dev/acpica/acpi_cpu.c
@@ -300,7 +300,7 @@ acpi_cpu_probe(device_t dev)
 	    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
index 000000000000..865bac49b869
--- /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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "opt_acpi.h"
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/cpu.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/pcpu.h>
+#include <sys/power.h>
+#include <sys/proc.h>
+#include <sys/sched.h>
+
+#include <machine/_inttypes.h>
+
+#include <contrib/dev/acpica/include/acpi.h>
+#include <contrib/dev/acpica/include/accommon.h>
+
+#include <dev/acpica/acpivar.h>
+
+#include <xen/xen-os.h>
+
+#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);