1/*-
2 * Copyright (c) 2022 Citrix Systems R&D
3 * Copyright (c) 2003-2005 Nate Lawson (SDG)
4 * Copyright (c) 2001 Michael Smith
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29#include <sys/cdefs.h>
30#include "opt_acpi.h"
31#include <sys/param.h>
32#include <sys/bus.h>
33#include <sys/cpu.h>
34#include <sys/kernel.h>
35#include <sys/malloc.h>
36#include <sys/module.h>
37#include <sys/pcpu.h>
38#include <sys/power.h>
39#include <sys/proc.h>
40#include <sys/sched.h>
41
42#include <machine/_inttypes.h>
43
44#include <contrib/dev/acpica/include/acpi.h>
45#include <contrib/dev/acpica/include/accommon.h>
46
47#include <dev/acpica/acpivar.h>
48
49#include <xen/xen-os.h>
50
51#define ACPI_DOMAIN_COORD_TYPE_SW_ALL 0xfc
52#define ACPI_DOMAIN_COORD_TYPE_SW_ANY 0xfd
53#define ACPI_DOMAIN_COORD_TYPE_HW_ALL 0xfe
54
55#define ACPI_NOTIFY_PERF_STATES 0x80	/* _PSS changed. */
56#define ACPI_NOTIFY_CX_STATES	0x81	/* _CST changed. */
57
58static MALLOC_DEFINE(M_XENACPI, "xen_acpi", "Xen CPU ACPI forwarder");
59
60/* Hooks for the ACPI CA debugging infrastructure */
61#define _COMPONENT ACPI_PROCESSOR
62ACPI_MODULE_NAME("PROCESSOR")
63
64struct xen_acpi_cpu_softc {
65	device_t cpu_dev;
66	ACPI_HANDLE cpu_handle;
67	uint32_t cpu_acpi_id;
68	struct xen_processor_cx *cpu_cx_states;
69	unsigned int cpu_cx_count;
70	struct xen_processor_px *cpu_px_states;
71	unsigned int cpu_px_count;
72	struct xen_pct_register control_register;
73	struct xen_pct_register status_register;
74	struct xen_psd_package psd;
75};
76
77#define	CPUDEV_DEVICE_ID "ACPI0007"
78
79ACPI_SERIAL_DECL(cpu, "ACPI CPU");
80
81#define device_printf(dev,...) \
82	if (!device_is_quiet(dev)) \
83		device_printf((dev), __VA_ARGS__)
84
85static int
86acpi_get_gas(const ACPI_OBJECT *res, unsigned int idx,
87    ACPI_GENERIC_ADDRESS *gas)
88{
89	const ACPI_OBJECT *obj = &res->Package.Elements[idx];
90
91	if (obj == NULL || obj->Type != ACPI_TYPE_BUFFER ||
92	    obj->Buffer.Length < sizeof(ACPI_GENERIC_ADDRESS) + 3)
93		return (EINVAL);
94
95	memcpy(gas, obj->Buffer.Pointer + 3, sizeof(*gas));
96
97	return (0);
98}
99
100static int
101acpi_get_pct(const ACPI_OBJECT *res, unsigned int idx,
102    struct xen_pct_register *reg)
103{
104	struct {
105		uint8_t descriptor;
106		uint16_t length;
107		ACPI_GENERIC_ADDRESS gas;
108	} __packed raw;
109	const ACPI_OBJECT *obj = &res->Package.Elements[idx];
110
111	if (obj == NULL || obj->Type != ACPI_TYPE_BUFFER ||
112	    obj->Buffer.Length < sizeof(raw))
113		return (EINVAL);
114
115	memcpy(&raw, obj->Buffer.Pointer, sizeof(raw));
116	reg->descriptor = raw.descriptor;
117	reg->length = raw.length;
118	reg->space_id = raw.gas.SpaceId;
119	reg->bit_width = raw.gas.BitWidth;
120	reg->bit_offset = raw.gas.BitOffset;
121	reg->reserved = raw.gas.AccessWidth;
122	reg->address = raw.gas.Address;
123
124	return (0);
125}
126
127static int
128xen_upload_cx(struct xen_acpi_cpu_softc *sc)
129{
130	struct xen_platform_op op = {
131		.cmd			= XENPF_set_processor_pminfo,
132		.interface_version	= XENPF_INTERFACE_VERSION,
133		.u.set_pminfo.id	= sc->cpu_acpi_id,
134		.u.set_pminfo.type	= XEN_PM_CX,
135		.u.set_pminfo.u.power.count = sc->cpu_cx_count,
136		.u.set_pminfo.u.power.flags.has_cst = 1,
137		/* Ignore bm_check and bm_control, Xen will set those. */
138	};
139	int error;
140
141	set_xen_guest_handle(op.u.set_pminfo.u.power.states, sc->cpu_cx_states);
142
143	error = HYPERVISOR_platform_op(&op);
144	if (error != 0)
145		device_printf(sc->cpu_dev,
146		    "ACPI ID %u Cx upload failed: %d\n", sc->cpu_acpi_id,
147		    error);
148	return (error);
149}
150
151static int
152xen_upload_px(struct xen_acpi_cpu_softc *sc)
153{
154	struct xen_platform_op op = {
155		.cmd = XENPF_set_processor_pminfo,
156		.interface_version = XENPF_INTERFACE_VERSION,
157		.u.set_pminfo.id = sc->cpu_acpi_id,
158		.u.set_pminfo.type = XEN_PM_PX,
159		.u.set_pminfo.u.perf.state_count = sc->cpu_px_count,
160		.u.set_pminfo.u.perf.control_register = sc->control_register,
161		.u.set_pminfo.u.perf.status_register = sc->status_register,
162		.u.set_pminfo.u.perf.domain_info = sc->psd,
163		.u.set_pminfo.u.perf.flags = XEN_PX_PPC | XEN_PX_PCT |
164		    XEN_PX_PSS | XEN_PX_PSD,
165	};
166	ACPI_STATUS status;
167	int error;
168
169	status = acpi_GetInteger(sc->cpu_handle, "_PPC",
170	    &op.u.set_pminfo.u.perf.platform_limit);
171	if (ACPI_FAILURE(status)) {
172		device_printf(sc->cpu_dev, "missing _PPC object\n");
173		return (ENXIO);
174	}
175
176	set_xen_guest_handle(op.u.set_pminfo.u.perf.states, sc->cpu_px_states);
177
178	/*
179	 * NB: it's unclear the exact purpose of the shared_type field, or why
180	 * it can't be calculated by Xen itself. Naively set it here to allow
181	 * the upload to succeed.
182	 */
183	switch (sc->psd.coord_type) {
184	case ACPI_DOMAIN_COORD_TYPE_SW_ALL:
185		op.u.set_pminfo.u.perf.shared_type =
186		    XEN_CPUPERF_SHARED_TYPE_ALL;
187		break;
188
189	case ACPI_DOMAIN_COORD_TYPE_HW_ALL:
190		op.u.set_pminfo.u.perf.shared_type =
191		    XEN_CPUPERF_SHARED_TYPE_HW;
192		break;
193
194	case ACPI_DOMAIN_COORD_TYPE_SW_ANY:
195		op.u.set_pminfo.u.perf.shared_type =
196		    XEN_CPUPERF_SHARED_TYPE_ANY;
197		break;
198	default:
199		device_printf(sc->cpu_dev,
200		    "unknown coordination type %#" PRIx64 "\n",
201		    sc->psd.coord_type);
202		return (EINVAL);
203	}
204
205	error = HYPERVISOR_platform_op(&op);
206	if (error != 0)
207	    device_printf(sc->cpu_dev,
208		"ACPI ID %u Px upload failed: %d\n", sc->cpu_acpi_id, error);
209	return (error);
210}
211
212static int
213acpi_set_pdc(const struct xen_acpi_cpu_softc *sc)
214{
215	struct xen_platform_op op = {
216		.cmd			= XENPF_set_processor_pminfo,
217		.interface_version	= XENPF_INTERFACE_VERSION,
218		.u.set_pminfo.id	= -1,
219		.u.set_pminfo.type	= XEN_PM_PDC,
220	};
221	uint32_t pdc[3] = {1, 1};
222	ACPI_OBJECT arg = {
223		.Buffer.Type = ACPI_TYPE_BUFFER,
224		.Buffer.Length = sizeof(pdc),
225		.Buffer.Pointer = (uint8_t *)pdc,
226	};
227	ACPI_OBJECT_LIST arglist = {
228		.Pointer = &arg,
229		.Count = 1,
230	};
231	ACPI_STATUS status;
232	int error;
233
234	set_xen_guest_handle(op.u.set_pminfo.u.pdc, pdc);
235	error = HYPERVISOR_platform_op(&op);
236	if (error != 0) {
237		device_printf(sc->cpu_dev,
238		    "unable to get _PDC features from Xen: %d\n", error);
239		return (error);
240	}
241
242	status = AcpiEvaluateObject(sc->cpu_handle, "_PDC", &arglist, NULL);
243	if (ACPI_FAILURE(status)) {
244		device_printf(sc->cpu_dev, "unable to execute _PDC - %s\n",
245		    AcpiFormatException(status));
246		return (ENXIO);
247	}
248
249	return (0);
250}
251
252/*
253 * Parse a _CST package and set up its Cx states.  Since the _CST object
254 * can change dynamically, our notify handler may call this function
255 * to clean up and probe the new _CST package.
256 */
257static int
258acpi_fetch_cx(struct xen_acpi_cpu_softc *sc)
259{
260	ACPI_STATUS status;
261	ACPI_BUFFER buf = {
262		.Length = ACPI_ALLOCATE_BUFFER,
263	};
264	ACPI_OBJECT *top;
265	uint32_t count;
266	unsigned int i;
267
268	status = AcpiEvaluateObject(sc->cpu_handle, "_CST", NULL, &buf);
269	if (ACPI_FAILURE(status)) {
270		device_printf(sc->cpu_dev, "missing _CST object\n");
271		return (ENXIO);
272	}
273
274	/* _CST is a package with a count and at least one Cx package. */
275	top = (ACPI_OBJECT *)buf.Pointer;
276	if (!ACPI_PKG_VALID(top, 2) || acpi_PkgInt32(top, 0, &count) != 0) {
277		device_printf(sc->cpu_dev, "invalid _CST package\n");
278		AcpiOsFree(buf.Pointer);
279		return (ENXIO);
280	}
281	if (count != top->Package.Count - 1) {
282		device_printf(sc->cpu_dev,
283		    "invalid _CST state count (%u != %u)\n",
284		    count, top->Package.Count - 1);
285		count = top->Package.Count - 1;
286	}
287
288	sc->cpu_cx_states = mallocarray(count, sizeof(struct xen_processor_cx),
289	    M_XENACPI, M_WAITOK | M_ZERO);
290
291	sc->cpu_cx_count = 0;
292	for (i = 0; i < count; i++) {
293		uint32_t type;
294		ACPI_GENERIC_ADDRESS gas;
295		ACPI_OBJECT *pkg = &top->Package.Elements[i + 1];
296		struct xen_processor_cx *cx_ptr =
297		    &sc->cpu_cx_states[sc->cpu_cx_count];
298
299		if (!ACPI_PKG_VALID(pkg, 4) ||
300		    acpi_PkgInt32(pkg, 1, &type) != 0 ||
301		    acpi_PkgInt32(pkg, 2, &cx_ptr->latency) != 0 ||
302		    acpi_PkgInt32(pkg, 3, &cx_ptr->power) != 0 ||
303		    acpi_get_gas(pkg, 0, &gas) != 0) {
304			device_printf(sc->cpu_dev,
305			    "skipping invalid _CST %u package\n",
306			    i + 1);
307			continue;
308		}
309
310		cx_ptr->type = type;
311		cx_ptr->reg.space_id = gas.SpaceId;
312		cx_ptr->reg.bit_width = gas.BitWidth;
313		cx_ptr->reg.bit_offset = gas.BitOffset;
314		cx_ptr->reg.access_size = gas.AccessWidth;
315		cx_ptr->reg.address = gas.Address;
316		sc->cpu_cx_count++;
317	}
318	AcpiOsFree(buf.Pointer);
319
320	if (sc->cpu_cx_count == 0) {
321		device_printf(sc->cpu_dev, "no valid _CST package found\n");
322		free(sc->cpu_cx_states, M_XENACPI);
323		sc->cpu_cx_states = NULL;
324		return (ENXIO);
325	}
326
327	return (0);
328}
329
330/* Probe and setup any valid performance states (Px). */
331static int
332acpi_fetch_px(struct xen_acpi_cpu_softc *sc)
333{
334	ACPI_BUFFER buf = {
335		.Length = ACPI_ALLOCATE_BUFFER,
336	};
337	ACPI_OBJECT *pkg, *res;
338	ACPI_STATUS status;
339	unsigned int count, i;
340	int error;
341	uint64_t *p;
342
343	/* _PSS */
344	status = AcpiEvaluateObject(sc->cpu_handle, "_PSS", NULL, &buf);
345	if (ACPI_FAILURE(status)) {
346		device_printf(sc->cpu_dev, "missing _PSS object\n");
347		return (ENXIO);
348	}
349
350	pkg = (ACPI_OBJECT *)buf.Pointer;
351	if (!ACPI_PKG_VALID(pkg, 1)) {
352		device_printf(sc->cpu_dev, "invalid top level _PSS package\n");
353		goto error;
354	}
355	count = pkg->Package.Count;
356
357	sc->cpu_px_states = mallocarray(count, sizeof(struct xen_processor_px),
358	    M_XENACPI, M_WAITOK | M_ZERO);
359
360	/*
361	 * Each state is a package of {CoreFreq, Power, TransitionLatency,
362	 * BusMasterLatency, ControlVal, StatusVal}, sorted from highest
363	 * performance to lowest.
364	 */
365	sc->cpu_px_count = 0;
366	for (i = 0; i < count; i++) {
367		unsigned int j;
368
369		res = &pkg->Package.Elements[i];
370		if (!ACPI_PKG_VALID(res, 6)) {
371			device_printf(sc->cpu_dev,
372			    "invalid _PSS package idx %u\n", i);
373			continue;
374		}
375
376		/* Parse the rest of the package into the struct. */
377		p = (uint64_t *)&sc->cpu_px_states[sc->cpu_px_count++];
378		for (j = 0; j < 6; j++, p++)
379			acpi_PkgInt(res, j, p);
380	}
381
382	/* No valid Px state found so give up. */
383	if (sc->cpu_px_count == 0) {
384		device_printf(sc->cpu_dev, "no valid _PSS package found\n");
385		goto error;
386	}
387	AcpiOsFree(buf.Pointer);
388
389	/* _PCT */
390	buf.Pointer = NULL;
391	buf.Length = ACPI_ALLOCATE_BUFFER;
392	status = AcpiEvaluateObject(sc->cpu_handle, "_PCT", NULL, &buf);
393	if (ACPI_FAILURE(status)) {
394		device_printf(sc->cpu_dev, "missing _PCT object\n");
395		goto error;
396	}
397
398	/* Check the package of two registers, each a Buffer in GAS format. */
399	pkg = (ACPI_OBJECT *)buf.Pointer;
400	if (!ACPI_PKG_VALID(pkg, 2)) {
401		device_printf(sc->cpu_dev, "invalid top level _PCT package\n");
402		goto error;
403	}
404
405	error = acpi_get_pct(pkg, 0, &sc->control_register);
406	if (error != 0) {
407		device_printf(sc->cpu_dev,
408		    "unable to fetch _PCT control register: %d\n", error);
409		goto error;
410	}
411	error = acpi_get_pct(pkg, 1, &sc->status_register);
412	if (error != 0) {
413		device_printf(sc->cpu_dev,
414		    "unable to fetch _PCT status register: %d\n", error);
415		goto error;
416	}
417	AcpiOsFree(buf.Pointer);
418
419	/* _PSD */
420	buf.Pointer = NULL;
421	buf.Length = ACPI_ALLOCATE_BUFFER;
422	status = AcpiEvaluateObject(sc->cpu_handle, "_PSD", NULL, &buf);
423	if (ACPI_FAILURE(status)) {
424		device_printf(sc->cpu_dev, "missing _PSD object\n");
425		goto error;
426	}
427
428	pkg = (ACPI_OBJECT *)buf.Pointer;
429	if (!ACPI_PKG_VALID(pkg, 1)) {
430		device_printf(sc->cpu_dev, "invalid top level _PSD package\n");
431		goto error;
432	}
433
434	res = &pkg->Package.Elements[0];
435	if (!ACPI_PKG_VALID(res, 5)) {
436		printf("invalid _PSD package\n");
437		goto error;
438	}
439
440	p = (uint64_t *)&sc->psd;
441	for (i = 0; i < 5; i++, p++)
442		acpi_PkgInt(res, i, p);
443	AcpiOsFree(buf.Pointer);
444
445	return (0);
446
447error:
448	if (buf.Pointer != NULL)
449		AcpiOsFree(buf.Pointer);
450	if (sc->cpu_px_states != NULL) {
451		free(sc->cpu_px_states, M_XENACPI);
452		sc->cpu_px_states = NULL;
453	}
454	return (ENXIO);
455}
456
457static void
458acpi_notify(ACPI_HANDLE h, UINT32 notify, void *context)
459{
460	struct xen_acpi_cpu_softc *sc = context;
461
462	switch (notify) {
463	case ACPI_NOTIFY_PERF_STATES:
464		if (acpi_fetch_px(sc) != 0)
465			break;
466		xen_upload_px(sc);
467		free(sc->cpu_px_states, M_XENACPI);
468		sc->cpu_px_states = NULL;
469		break;
470
471	case ACPI_NOTIFY_CX_STATES:
472		if (acpi_fetch_cx(sc) != 0)
473			break;
474		xen_upload_cx(sc);
475		free(sc->cpu_cx_states, M_XENACPI);
476		sc->cpu_cx_states = NULL;
477		break;
478	}
479}
480
481static int
482xen_acpi_cpu_probe(device_t dev)
483{
484	static char *cpudev_ids[] = { CPUDEV_DEVICE_ID, NULL };
485	ACPI_OBJECT_TYPE type = acpi_get_type(dev);
486
487	if (!xen_initial_domain())
488		return (ENXIO);
489	if (type != ACPI_TYPE_PROCESSOR && type != ACPI_TYPE_DEVICE)
490		return (ENXIO);
491	if (type == ACPI_TYPE_DEVICE &&
492	    ACPI_ID_PROBE(device_get_parent(dev), dev, cpudev_ids, NULL) >= 0)
493		return (ENXIO);
494
495	device_set_desc(dev, "XEN ACPI CPU");
496	if (!bootverbose)
497		device_quiet(dev);
498
499	/*
500	 * Use SPECIFIC because when running as a Xen dom0 the ACPI PROCESSOR
501	 * data is the native one, and needs to be forwarded to Xen but not
502	 * used by FreeBSD itself.
503	 */
504	return (BUS_PROBE_SPECIFIC);
505}
506
507static bool
508is_processor_online(unsigned int acpi_id)
509{
510	unsigned int i, maxid;
511	struct xen_platform_op op = {
512		.cmd = XENPF_get_cpuinfo,
513	};
514	int ret = HYPERVISOR_platform_op(&op);
515
516	if (ret)
517		return (false);
518
519	maxid = op.u.pcpu_info.max_present;
520	for (i = 0; i <= maxid; i++) {
521		op.u.pcpu_info.xen_cpuid = i;
522		ret = HYPERVISOR_platform_op(&op);
523		if (ret)
524			continue;
525		if (op.u.pcpu_info.acpi_id == acpi_id)
526			return (op.u.pcpu_info.flags & XEN_PCPU_FLAGS_ONLINE);
527	}
528
529	return (false);
530}
531
532static int
533xen_acpi_cpu_attach(device_t dev)
534{
535	struct xen_acpi_cpu_softc *sc = device_get_softc(dev);
536	ACPI_STATUS status;
537	int error;
538
539	sc->cpu_dev = dev;
540	sc->cpu_handle = acpi_get_handle(dev);
541
542	if (acpi_get_type(dev) == ACPI_TYPE_PROCESSOR) {
543		ACPI_BUFFER buf = {
544			.Length = ACPI_ALLOCATE_BUFFER,
545		};
546		ACPI_OBJECT *obj;
547
548		status = AcpiEvaluateObject(sc->cpu_handle, NULL, NULL, &buf);
549		if (ACPI_FAILURE(status)) {
550			device_printf(dev,
551			    "attach failed to get Processor obj - %s\n",
552			    AcpiFormatException(status));
553			return (ENXIO);
554		}
555		obj = (ACPI_OBJECT *)buf.Pointer;
556		sc->cpu_acpi_id = obj->Processor.ProcId;
557		AcpiOsFree(obj);
558	} else {
559		KASSERT(acpi_get_type(dev) == ACPI_TYPE_DEVICE,
560		    ("Unexpected ACPI object"));
561		status = acpi_GetInteger(sc->cpu_handle, "_UID",
562		    &sc->cpu_acpi_id);
563		if (ACPI_FAILURE(status)) {
564			device_printf(dev, "device object has bad value - %s\n",
565			    AcpiFormatException(status));
566			return (ENXIO);
567		}
568	}
569
570	if (!is_processor_online(sc->cpu_acpi_id))
571		/* Processor is not online, attach the driver and ignore it. */
572		return (0);
573
574	/*
575	 * Install the notify handler now: even if we fail to parse or upload
576	 * the states it shouldn't prevent us from attempting to parse further
577	 * updates.
578	 */
579	status = AcpiInstallNotifyHandler(sc->cpu_handle, ACPI_DEVICE_NOTIFY,
580	    acpi_notify, sc);
581	if (ACPI_FAILURE(status))
582		device_printf(dev, "failed to register notify handler - %s\n",
583		    AcpiFormatException(status));
584
585	/*
586	 * Don't report errors: it's likely there are processor objects
587	 * belonging to CPUs that are not online, but the MADT provided to
588	 * FreeBSD is crafted to report the number of CPUs available to dom0.
589	 *
590	 * Parsing or uploading those states could result in errors, just
591	 * ignore them in order to avoid pointless noise.
592	 */
593	error = acpi_set_pdc(sc);
594	if (error != 0)
595		return (0);
596
597	error = acpi_fetch_px(sc);
598	if (error != 0)
599		return (0);
600	error = xen_upload_px(sc);
601	free(sc->cpu_px_states, M_XENACPI);
602	sc->cpu_px_states = NULL;
603	if (error != 0)
604		return (0);
605
606	error = acpi_fetch_cx(sc);
607	if (error != 0)
608		return (0);
609	xen_upload_cx(sc);
610	free(sc->cpu_cx_states, M_XENACPI);
611	sc->cpu_cx_states = NULL;
612
613	return (0);
614}
615
616static device_method_t xen_acpi_cpu_methods[] = {
617    /* Device interface */
618    DEVMETHOD(device_probe, xen_acpi_cpu_probe),
619    DEVMETHOD(device_attach, xen_acpi_cpu_attach),
620
621    DEVMETHOD_END
622};
623
624static driver_t xen_acpi_cpu_driver = {
625    "xen cpu",
626    xen_acpi_cpu_methods,
627    sizeof(struct xen_acpi_cpu_softc),
628};
629
630DRIVER_MODULE(xen_cpu, acpi, xen_acpi_cpu_driver, 0, 0);
631MODULE_DEPEND(xen_cpu, acpi, 1, 1, 1);
632