1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2019 Conrad Meyer <cem@FreeBSD.org>.  All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 */
27
28/*
29 * VM Generation Counter driver
30 *
31 * See, e.g., the "Virtual Machine Generation ID" white paper:
32 * https://go.microsoft.com/fwlink/p/?LinkID=260709 , and perhaps also:
33 * https://docs.microsoft.com/en-us/windows/win32/hyperv_v2/virtual-machine-generation-identifier ,
34 * https://azure.microsoft.com/en-us/blog/accessing-and-using-azure-vm-unique-id/
35 *
36 * Microsoft introduced the concept in 2013 or so and seems to have
37 * successfully driven it to a consensus standard among hypervisors, not just
38 * HyperV/Azure:
39 * - QEMU: https://bugzilla.redhat.com/show_bug.cgi?id=1118834
40 * - VMware/ESXi: https://kb.vmware.com/s/article/2032586
41 * - Xen: https://github.com/xenserver/xen-4.5/blob/master/tools/firmware/hvmloader/acpi/dsdt.asl#L456
42 */
43
44#include <sys/param.h>
45#include <sys/bus.h>
46#include <sys/eventhandler.h>
47#include <sys/kernel.h>
48#include <sys/lock.h>
49#include <sys/malloc.h>
50#include <sys/module.h>
51#include <sys/mutex.h>
52#include <sys/random.h>
53#include <sys/sysctl.h>
54#include <sys/systm.h>
55
56#include <contrib/dev/acpica/include/acpi.h>
57
58#include <dev/acpica/acpivar.h>
59#include <dev/random/random_harvestq.h>
60#include <dev/vmgenc/vmgenc_acpi.h>
61
62#ifndef	ACPI_NOTIFY_STATUS_CHANGED
63#define	ACPI_NOTIFY_STATUS_CHANGED	0x80
64#endif
65
66#define	GUID_BYTES	16
67
68static const char *vmgenc_ids[] = {
69	"VM_GEN_COUNTER",
70	NULL
71};
72#if 0
73MODULE_PNP_INFO("Z:_CID", acpi, vmgenc, vmgenc_ids, nitems(vmgenc_ids) - 1);
74#endif
75
76struct vmgenc_softc {
77	volatile void	*vmg_pguid;
78	uint8_t		vmg_cache_guid[GUID_BYTES];
79};
80
81static void
82vmgenc_harvest_all(const void *p, size_t sz)
83{
84	size_t nbytes;
85
86	while (sz > 0) {
87		nbytes = MIN(sz,
88		    sizeof(((struct harvest_event *)0)->he_entropy));
89		random_harvest_direct(p, nbytes, RANDOM_PURE_VMGENID);
90		p = (const char *)p + nbytes;
91		sz -= nbytes;
92	}
93}
94
95static void
96vmgenc_status_changed(void *context)
97{
98	uint8_t guid[GUID_BYTES];
99	struct vmgenc_softc *sc;
100	device_t dev;
101
102	dev = context;
103	sc = device_get_softc(dev);
104
105	/* Check for spurious notify events. */
106	memcpy(guid, __DEVOLATILE(void *, sc->vmg_pguid), sizeof(guid));
107	if (memcmp(guid, sc->vmg_cache_guid, GUID_BYTES) == 0)
108		return; /* No change. */
109
110	/* Update cache. */
111	memcpy(sc->vmg_cache_guid, guid, GUID_BYTES);
112
113	vmgenc_harvest_all(sc->vmg_cache_guid, sizeof(sc->vmg_cache_guid));
114
115	EVENTHANDLER_INVOKE(acpi_vmgenc_event);
116	acpi_UserNotify("VMGenerationCounter", acpi_get_handle(dev), 0);
117}
118
119static void
120vmgenc_notify(ACPI_HANDLE h, UINT32 notify, void *context)
121{
122	device_t dev;
123
124	dev = context;
125	switch (notify) {
126	case ACPI_NOTIFY_STATUS_CHANGED:
127		/*
128		 * We're possibly in GPE / interrupt context, kick the event up
129		 * to a taskqueue.
130		 */
131		AcpiOsExecute(OSL_NOTIFY_HANDLER, vmgenc_status_changed, dev);
132		break;
133	default:
134		device_printf(dev, "unknown notify %#x\n", notify);
135		break;
136	}
137}
138
139static int
140vmgenc_probe(device_t dev)
141{
142	int rv;
143
144	if (acpi_disabled("vmgenc"))
145		return (ENXIO);
146	rv = ACPI_ID_PROBE(device_get_parent(dev), dev,
147	    __DECONST(char **, vmgenc_ids), NULL);
148	if (rv <= 0)
149		device_set_desc(dev, "VM Generation Counter");
150	return (rv);
151}
152
153static const char *
154vmgenc_acpi_getname(ACPI_HANDLE handle, char data[static 256])
155{
156    ACPI_BUFFER buf;
157
158    buf.Length = 256;
159    buf.Pointer = data;
160
161    if (ACPI_SUCCESS(AcpiGetName(handle, ACPI_FULL_PATHNAME, &buf)))
162	return (data);
163    return ("(unknown)");
164}
165
166static int
167acpi_GetPackedUINT64(device_t dev, ACPI_HANDLE handle, char *path,
168    uint64_t *out)
169{
170	char hpath[256];
171	ACPI_STATUS status;
172	ACPI_BUFFER buf;
173	ACPI_OBJECT param[3];
174
175	buf.Pointer = param;
176	buf.Length = sizeof(param);
177	status = AcpiEvaluateObject(handle, path, NULL, &buf);
178	if (!ACPI_SUCCESS(status)) {
179		device_printf(dev, "%s(%s::%s()): %s\n", __func__,
180		    vmgenc_acpi_getname(handle, hpath), path,
181		    AcpiFormatException(status));
182		return (ENXIO);
183	}
184	if (param[0].Type != ACPI_TYPE_PACKAGE) {
185		device_printf(dev, "%s(%s::%s()): Wrong type %#x\n", __func__,
186		    vmgenc_acpi_getname(handle, hpath), path,
187		    param[0].Type);
188		return (ENXIO);
189	}
190	if (param[0].Package.Count != 2) {
191		device_printf(dev, "%s(%s::%s()): Wrong number of results %u\n",
192		    __func__, vmgenc_acpi_getname(handle, hpath), path,
193		    param[0].Package.Count);
194		return (ENXIO);
195	}
196	if (param[0].Package.Elements[0].Type != ACPI_TYPE_INTEGER ||
197	    param[0].Package.Elements[1].Type != ACPI_TYPE_INTEGER) {
198		device_printf(dev, "%s(%s::%s()): Wrong type results %#x, %#x\n",
199		    __func__, vmgenc_acpi_getname(handle, hpath), path,
200		    param[0].Package.Elements[0].Type,
201		    param[0].Package.Elements[1].Type);
202		return (ENXIO);
203	}
204
205	*out = (param[0].Package.Elements[0].Integer.Value & UINT32_MAX) |
206	    ((uint64_t)param[0].Package.Elements[1].Integer.Value << 32);
207	if (*out == 0)
208		return (ENXIO);
209	return (0);
210
211}
212
213static int
214vmgenc_attach(device_t dev)
215{
216	struct vmgenc_softc *sc;
217	uint64_t guid_physaddr;
218	ACPI_HANDLE h;
219	int error;
220
221	h = acpi_get_handle(dev);
222	sc = device_get_softc(dev);
223
224	error = acpi_GetPackedUINT64(dev, h, "ADDR", &guid_physaddr);
225	if (error != 0)
226		return (error);
227
228	SYSCTL_ADD_OPAQUE(device_get_sysctl_ctx(dev),
229	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "guid",
230	    CTLFLAG_RD, sc->vmg_cache_guid, GUID_BYTES, "",
231	    "latest cached VM generation counter (128-bit UUID)");
232
233	sc->vmg_pguid = AcpiOsMapMemory(guid_physaddr, GUID_BYTES);
234	memcpy(sc->vmg_cache_guid, __DEVOLATILE(void *, sc->vmg_pguid),
235	    sizeof(sc->vmg_cache_guid));
236
237	random_harvest_register_source(RANDOM_PURE_VMGENID);
238	vmgenc_harvest_all(sc->vmg_cache_guid, sizeof(sc->vmg_cache_guid));
239
240	AcpiInstallNotifyHandler(h, ACPI_DEVICE_NOTIFY, vmgenc_notify, dev);
241	return (0);
242}
243
244static device_method_t vmgenc_methods[] = {
245	DEVMETHOD(device_probe,		vmgenc_probe),
246	DEVMETHOD(device_attach,	vmgenc_attach),
247	DEVMETHOD_END
248};
249
250static driver_t vmgenc_driver = {
251	"vmgenc",
252	vmgenc_methods,
253	sizeof(struct vmgenc_softc),
254};
255
256DRIVER_MODULE(vmgenc, acpi, vmgenc_driver, NULL, NULL);
257MODULE_DEPEND(vmgenc, acpi, 1, 1, 1);
258MODULE_DEPEND(vemgenc, random_harvestq, 1, 1, 1);
259