1/*	$NetBSD: acpi_vmgenid.c,v 1.3 2024/08/27 00:56:46 riastradh Exp $	*/
2
3/*-
4 * Copyright (c) 2024 The NetBSD Foundation, Inc.
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 NETBSD FOUNDATION, INC. AND CONTRIBUTORS
17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/*
30 * Virtual Machine Generation ID
31 *
32 *	The VMGENID is an 8-byte cookie shared between a VM host and VM
33 *	guest.  Whenever the host clones a VM, it changes the VMGENID
34 *	and sends an ACPI notification to the guest.
35 *
36 * References:
37 *
38 *	`Virtual Machine Generation ID', Microsoft, 2012-08-01.
39 *	http://go.microsoft.com/fwlink/?LinkId=260709
40 *
41 *	`Virtual Machine Generation ID Device', The QEMU Project
42 *	Developers.
43 *	https://www.qemu.org/docs/master/specs/vmgenid.html
44 */
45
46#include <sys/cdefs.h>
47__KERNEL_RCSID(0, "$NetBSD: acpi_vmgenid.c,v 1.3 2024/08/27 00:56:46 riastradh Exp $");
48
49#include <sys/device.h>
50#include <sys/entropy.h>
51#include <sys/module.h>
52#include <sys/rndsource.h>
53#include <sys/sysctl.h>
54
55#include <dev/acpi/acpireg.h>
56#include <dev/acpi/acpivar.h>
57
58#define	_COMPONENT	ACPI_RESOURCE_COMPONENT
59ACPI_MODULE_NAME	("acpi_vmgenid")
60
61struct acpivmgenid {
62	uint8_t		id[16];
63} __aligned(8);
64
65struct acpivmgenid_softc {
66	device_t			sc_dev;
67	struct acpi_devnode		*sc_node;
68	uint64_t			sc_paddr;
69	struct acpivmgenid		*sc_vaddr;
70	struct krndsource		sc_rndsource;
71	struct sysctllog		*sc_sysctllog;
72	const struct sysctlnode		*sc_sysctlroot;
73};
74
75static int acpivmgenid_match(device_t, cfdata_t, void *);
76static void acpivmgenid_attach(device_t, device_t, void *);
77static int acpivmgenid_detach(device_t, int);
78static void acpivmgenid_set(struct acpivmgenid_softc *, const char *);
79static void acpivmgenid_notify(ACPI_HANDLE, uint32_t, void *);
80static void acpivmgenid_reset(void *);
81static int acpivmgenid_sysctl(SYSCTLFN_ARGS);
82
83static const struct device_compatible_entry compat_data[] = {
84	{ .compat = "VM_Gen_Counter" },		/* from the Microsoft spec */
85	{ .compat = "VM_GEN_COUNTER" },		/* used by qemu */
86	{ .compat = "VMGENCTR" },		/* recognized by Linux */
87	DEVICE_COMPAT_EOL
88};
89
90CFATTACH_DECL_NEW(acpivmgenid, sizeof(struct acpivmgenid_softc),
91    acpivmgenid_match, acpivmgenid_attach, acpivmgenid_detach, NULL);
92
93static int
94acpivmgenid_match(device_t parent, cfdata_t match, void *aux)
95{
96	const struct acpi_attach_args *const aa = aux;
97
98	return acpi_compatible_match(aa, compat_data);
99}
100
101static void
102acpivmgenid_attach(device_t parent, device_t self, void *aux)
103{
104	struct acpivmgenid_softc *const sc = device_private(self);
105	const struct acpi_attach_args *const aa = aux;
106	ACPI_BUFFER addrbuf = {
107		.Pointer = NULL,
108		.Length = ACPI_ALLOCATE_BUFFER,
109	};
110	ACPI_OBJECT *addrobj, *addrarr;
111	ACPI_STATUS rv;
112	int error;
113
114	aprint_naive(": ACPI VM Generation ID\n");
115	aprint_normal(": ACPI VM Generation ID\n");
116
117	sc->sc_dev = self;
118	sc->sc_node = aa->aa_node;
119
120	/*
121	 * Get the address from the ADDR object, which is a package of
122	 * two 32-bit integers representing the low and high halves of
123	 * a 64-bit physical address.
124	 */
125	rv = AcpiEvaluateObjectTyped(sc->sc_node->ad_handle, "ADDR", NULL,
126	    &addrbuf, ACPI_TYPE_PACKAGE);
127	if (ACPI_FAILURE(rv)) {
128		aprint_error_dev(self, "failed to get ADDR: %s\n",
129		    AcpiFormatException(rv));
130		goto out;
131	}
132	addrobj = addrbuf.Pointer;
133	if (addrobj->Type != ACPI_TYPE_PACKAGE ||
134	    addrobj->Package.Count != 2) {
135		aprint_error_dev(self, "invalid ADDR\n");
136		goto out;
137	}
138	addrarr = addrobj->Package.Elements;
139	if (addrarr[0].Type != ACPI_TYPE_INTEGER ||
140	    addrarr[1].Type != ACPI_TYPE_INTEGER ||
141	    addrarr[0].Integer.Value > UINT32_MAX ||
142	    addrarr[1].Integer.Value > UINT32_MAX) {
143		aprint_error_dev(self, "invalid ADDR\n");
144		goto out;
145	}
146	sc->sc_paddr = (ACPI_PHYSICAL_ADDRESS)addrarr[0].Integer.Value;
147	sc->sc_paddr |= (ACPI_PHYSICAL_ADDRESS)addrarr[1].Integer.Value << 32;
148	aprint_normal_dev(self, "paddr=0x%"PRIx64"\n", (uint64_t)sc->sc_paddr);
149
150	/*
151	 * Map the physical address into virtual address space.
152	 */
153	sc->sc_vaddr = AcpiOsMapMemory(sc->sc_paddr, sizeof(*sc->sc_vaddr));
154	if (sc->sc_vaddr == NULL) {
155		aprint_error_dev(self, "failed to map address\n");
156		goto out;
157	}
158
159	/*
160	 * Register a random source so we can attribute samples.
161	 */
162	rnd_attach_source(&sc->sc_rndsource, device_xname(self),
163	    RND_TYPE_UNKNOWN, RND_FLAG_COLLECT_TIME|RND_FLAG_COLLECT_VALUE);
164
165	/*
166	 * Register an ACPI notifier so that we can detect changes.
167	 */
168	(void)acpi_register_notify(sc->sc_node, acpivmgenid_notify);
169
170	/*
171	 * Now that we have registered a random source and a notifier,
172	 * read out the first value.
173	 */
174	acpivmgenid_set(sc, "initial");
175
176	/*
177	 * Attach a sysctl tree, rooted at hw.acpivmgenidN.
178	 */
179	error = sysctl_createv(&sc->sc_sysctllog, 0, NULL, &sc->sc_sysctlroot,
180	    CTLFLAG_PERMANENT, CTLTYPE_NODE, device_xname(self),
181	    SYSCTL_DESCR("Virtual Machine Generation ID device"),
182	    NULL, 0, NULL, 0,
183	    CTL_HW, CTL_CREATE, CTL_EOL);
184	if (error) {
185		aprint_error_dev(self, "failed to create sysctl hw.%s: %d\n",
186		    device_xname(self), error);
187		goto out;
188	}
189
190	/*
191	 * hw.acpivmgenidN.id (`struct', 16-byte array)
192	 */
193	error = sysctl_createv(&sc->sc_sysctllog, 0, &sc->sc_sysctlroot, NULL,
194	    CTLFLAG_PERMANENT|CTLFLAG_READONLY|CTLFLAG_PRIVATE, CTLTYPE_STRUCT,
195	    "id", SYSCTL_DESCR("Virtual Machine Generation ID"),
196	    &acpivmgenid_sysctl, 0, sc, sizeof(struct acpivmgenid),
197	    CTL_CREATE, CTL_EOL);
198	if (error) {
199		aprint_error_dev(self,
200		    "failed to create sysctl hw.%s.id: %d\n",
201		    device_xname(self), error);
202		goto out;
203	}
204
205	/*
206	 * hw.acpivmgenidN.paddr (64-bit integer)
207	 */
208	__CTASSERT(sizeof(ACPI_PHYSICAL_ADDRESS) == sizeof(quad_t));
209	error = sysctl_createv(&sc->sc_sysctllog, 0, &sc->sc_sysctlroot, NULL,
210	    CTLFLAG_PERMANENT|CTLFLAG_READONLY|CTLFLAG_PRIVATE, CTLTYPE_QUAD,
211	    "paddr", SYSCTL_DESCR("Physical address of VM Generation ID"),
212	    NULL, 0, &sc->sc_paddr, sizeof(sc->sc_paddr),
213	    CTL_CREATE, CTL_EOL);
214	if (error) {
215		aprint_error_dev(self,
216		    "failed to create sysctl hw.%s.paddr: %d\n",
217		    device_xname(self), error);
218		goto out;
219	}
220
221out:	ACPI_FREE(addrbuf.Pointer);
222}
223
224static int
225acpivmgenid_detach(device_t self, int flags)
226{
227	struct acpivmgenid_softc *const sc = device_private(self);
228	int error;
229
230	error = config_detach_children(self, flags);
231	if (error)
232		return error;
233
234	sysctl_teardown(&sc->sc_sysctllog);
235	acpi_deregister_notify(sc->sc_node);
236	rnd_detach_source(&sc->sc_rndsource);
237	if (sc->sc_vaddr) {
238		AcpiOsUnmapMemory(sc->sc_vaddr, sizeof(*sc->sc_vaddr));
239		sc->sc_vaddr = NULL;	/* paranoia */
240	}
241	sc->sc_paddr = 0;	/* paranoia */
242
243	return 0;
244}
245
246static void
247acpivmgenid_set(struct acpivmgenid_softc *sc, const char *prefix)
248{
249	struct acpivmgenid vmgenid;
250	char vmgenidstr[2*__arraycount(vmgenid.id) + 1];
251	unsigned i;
252
253	/*
254	 * Grab the current VM generation ID.  No obvious way to make
255	 * this atomic, so let's hope if it changes in the middle we'll
256	 * get another notification.
257	 */
258	memcpy(&vmgenid, sc->sc_vaddr, sizeof(vmgenid));
259
260	/*
261	 * Print the VM generation ID to the console for posterity.
262	 */
263	for (i = 0; i < __arraycount(vmgenid.id); i++) {
264		vmgenidstr[2*i] = "0123456789abcdef"[vmgenid.id[i] >> 4];
265		vmgenidstr[2*i + 1] = "0123456789abcdef"[vmgenid.id[i] & 0xf];
266	}
267	vmgenidstr[2*sizeof(vmgenid)] = '\0';
268	aprint_verbose_dev(sc->sc_dev, "%s: %s\n", prefix, vmgenidstr);
269
270	/*
271	 * Enter the new VM generation ID into the entropy pool.
272	 */
273	rnd_add_data(&sc->sc_rndsource, &vmgenid, sizeof(vmgenid), 0);
274}
275
276static void
277acpivmgenid_notify(ACPI_HANDLE hdl, uint32_t notify, void *opaque)
278{
279	const device_t self = opaque;
280	struct acpivmgenid_softc *const sc = device_private(self);
281
282	if (notify != 0x80) {
283		aprint_debug_dev(self, "unknown notify 0x%02x\n", notify);
284		return;
285	}
286
287	(void)AcpiOsExecute(OSL_NOTIFY_HANDLER, &acpivmgenid_reset, sc);
288}
289
290static void
291acpivmgenid_reset(void *cookie)
292{
293	struct acpivmgenid_softc *const sc = cookie;
294
295	/*
296	 * Reset the system entropy pool's measure of entropy (not the
297	 * data, just the system's assessment of whether it has
298	 * entropy), and gather more entropy from any synchronous
299	 * sources we have available like CPU RNG instructions.  We
300	 * can't be interrupted by a signal so ignore return value.
301	 */
302	entropy_reset();
303	(void)entropy_gather();
304
305	/*
306	 * Grab the current VM generation ID to put it into the entropy
307	 * pool; then force consolidation so it affects all subsequent
308	 * draws from the entropy pool and the entropy epoch advances.
309	 * Again we can't be interrupted by a signal so ignore return
310	 * value.
311	 */
312	acpivmgenid_set(sc, "cloned");
313	(void)entropy_consolidate();
314}
315
316static int
317acpivmgenid_sysctl(SYSCTLFN_ARGS)
318{
319	struct sysctlnode node = *rnode;
320	struct acpivmgenid_softc *const sc = node.sysctl_data;
321
322	node.sysctl_data = sc->sc_vaddr;
323	return sysctl_lookup(SYSCTLFN_CALL(&node));
324}
325
326MODULE(MODULE_CLASS_DRIVER, acpivmgenid, NULL);
327
328#ifdef _MODULE
329#include "ioconf.c"
330#endif
331
332static int
333acpivmgenid_modcmd(modcmd_t cmd, void *opaque)
334{
335	int error = 0;
336
337	switch (cmd) {
338	case MODULE_CMD_INIT:
339#ifdef _MODULE
340		error = config_init_component(cfdriver_ioconf_acpivmgenid,
341		    cfattach_ioconf_acpivmgenid, cfdata_ioconf_acpivmgenid);
342#endif
343		return error;
344	case MODULE_CMD_FINI:
345#ifdef _MODULE
346		error = config_fini_component(cfdriver_ioconf_acpivmgenid,
347		    cfattach_ioconf_acpivmgenid, cfdata_ioconf_acpivmgenid);
348#endif
349		return error;
350	default:
351		return ENOTTY;
352	}
353}
354