1/*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2022 Beckhoff Automation GmbH & Co. KG
5 */
6
7#include <sys/param.h>
8#include <sys/endian.h>
9#include <sys/errno.h>
10#include <sys/queue.h>
11#include <sys/stat.h>
12
13#include <machine/vmm.h>
14
15#include <assert.h>
16#include <err.h>
17#include <libutil.h>
18#include <stddef.h>
19#include <stdio.h>
20#include <vmmapi.h>
21
22#include "basl.h"
23#include "config.h"
24#include "qemu_loader.h"
25
26struct basl_table_checksum {
27	STAILQ_ENTRY(basl_table_checksum) chain;
28	uint32_t off;
29	uint32_t start;
30	uint32_t len;
31};
32
33struct basl_table_length {
34	STAILQ_ENTRY(basl_table_length) chain;
35	uint32_t off;
36	uint8_t size;
37};
38
39struct basl_table_pointer {
40	STAILQ_ENTRY(basl_table_pointer) chain;
41	uint8_t src_signature[ACPI_NAMESEG_SIZE];
42	uint32_t off;
43	uint8_t size;
44};
45
46struct basl_table {
47	STAILQ_ENTRY(basl_table) chain;
48	struct vmctx *ctx;
49	uint8_t fwcfg_name[QEMU_FWCFG_MAX_NAME];
50	void *data;
51	uint32_t len;
52	uint32_t off;
53	uint32_t alignment;
54	STAILQ_HEAD(basl_table_checksum_list, basl_table_checksum) checksums;
55	STAILQ_HEAD(basl_table_length_list, basl_table_length) lengths;
56	STAILQ_HEAD(basl_table_pointer_list, basl_table_pointer) pointers;
57};
58static STAILQ_HEAD(basl_table_list, basl_table) basl_tables = STAILQ_HEAD_INITIALIZER(
59    basl_tables);
60
61static struct qemu_loader *basl_loader;
62static struct basl_table *rsdt;
63static struct basl_table *xsdt;
64static bool load_into_memory;
65
66static __inline uint64_t
67basl_le_dec(void *pp, size_t len)
68{
69	assert(len <= 8);
70
71	switch (len) {
72	case 1:
73		return ((uint8_t *)pp)[0];
74	case 2:
75		return le16dec(pp);
76	case 4:
77		return le32dec(pp);
78	case 8:
79		return le64dec(pp);
80	}
81
82	return 0;
83}
84
85static __inline void
86basl_le_enc(void *pp, uint64_t val, size_t len)
87{
88	char buf[8];
89
90	assert(len <= 8);
91
92	le64enc(buf, val);
93	memcpy(pp, buf, len);
94}
95
96static int
97basl_dump_table(const struct basl_table *const table, const bool mem)
98{
99	const ACPI_TABLE_HEADER *const header = table->data;
100	const uint8_t *data;
101
102	if (!mem) {
103		data = table->data;
104	} else {
105		data = vm_map_gpa(table->ctx, BHYVE_ACPI_BASE + table->off,
106		    table->len);
107		if (data == NULL) {
108			return (ENOMEM);
109		}
110	}
111
112	printf("%.4s @ %8x (%s)\n", header->Signature,
113	    BHYVE_ACPI_BASE + table->off, mem ? "Memory" : "FwCfg");
114	hexdump(data, table->len, NULL, 0);
115
116	return (0);
117}
118
119static int __unused
120basl_dump(const bool mem)
121{
122	struct basl_table *table;
123
124	STAILQ_FOREACH(table, &basl_tables, chain) {
125		BASL_EXEC(basl_dump_table(table, mem));
126	}
127
128	return (0);
129}
130
131void
132basl_fill_gas(ACPI_GENERIC_ADDRESS *const gas, const uint8_t space_id,
133    const uint8_t bit_width, const uint8_t bit_offset,
134    const uint8_t access_width, const uint64_t address)
135{
136	assert(gas != NULL);
137
138	gas->SpaceId = space_id;
139	gas->BitWidth = bit_width;
140	gas->BitOffset = bit_offset;
141	gas->AccessWidth = access_width;
142	gas->Address = htole64(address);
143}
144
145static int
146basl_finish_install_guest_tables(struct basl_table *const table, uint32_t *const off)
147{
148	void *gva;
149
150	table->off = roundup2(*off, table->alignment);
151	*off = table->off + table->len;
152	if (*off <= table->off) {
153		warnx("%s: invalid table length 0x%8x @ offset 0x%8x", __func__,
154		    table->len, table->off);
155		return (EFAULT);
156	}
157
158	/* Cause guest BIOS to copy the ACPI table into guest memory. */
159	BASL_EXEC(
160	    qemu_fwcfg_add_file(table->fwcfg_name, table->len, table->data));
161	BASL_EXEC(qemu_loader_alloc(basl_loader, table->fwcfg_name,
162	    table->alignment, QEMU_LOADER_ALLOC_HIGH));
163
164	if (!load_into_memory) {
165		return (0);
166	}
167
168	/*
169	 * Install ACPI tables directly in guest memory for use by guests which
170	 * do not boot via EFI. EFI ROMs provide a pointer to the firmware
171	 * generated ACPI tables instead, but it doesn't hurt to install the
172	 * tables always.
173	 */
174	gva = vm_map_gpa(table->ctx, BHYVE_ACPI_BASE + table->off, table->len);
175	if (gva == NULL) {
176		warnx("%s: could not map gpa [ 0x%16lx, 0x%16lx ]", __func__,
177		    (uint64_t)BHYVE_ACPI_BASE + table->off,
178		    (uint64_t)BHYVE_ACPI_BASE + table->off + table->len);
179		return (ENOMEM);
180	}
181	memcpy(gva, table->data, table->len);
182
183	return (0);
184}
185
186static int
187basl_finish_patch_checksums(struct basl_table *const table)
188{
189	struct basl_table_checksum *checksum;
190
191	STAILQ_FOREACH(checksum, &table->checksums, chain) {
192		uint8_t *gva, *checksum_gva;
193		uint64_t gpa;
194		uint32_t len;
195		uint8_t sum;
196
197		len = checksum->len;
198		if (len == BASL_TABLE_CHECKSUM_LEN_FULL_TABLE) {
199			len = table->len;
200		}
201
202		assert(checksum->off < table->len);
203		assert(checksum->start < table->len);
204		assert(checksum->start + len <= table->len);
205
206		/* Cause guest BIOS to patch the checksum. */
207		BASL_EXEC(qemu_loader_add_checksum(basl_loader,
208		    table->fwcfg_name, checksum->off, checksum->start, len));
209
210		if (!load_into_memory) {
211			continue;
212		}
213
214		/*
215		 * Install ACPI tables directly in guest memory for use by
216		 * guests which do not boot via EFI. EFI ROMs provide a pointer
217		 * to the firmware generated ACPI tables instead, but it doesn't
218		 * hurt to install the tables always.
219		 */
220		gpa = BHYVE_ACPI_BASE + table->off + checksum->start;
221		if ((gpa < BHYVE_ACPI_BASE) ||
222		    (gpa < BHYVE_ACPI_BASE + table->off)) {
223			warnx("%s: invalid gpa (off 0x%8x start 0x%8x)",
224			    __func__, table->off, checksum->start);
225			return (EFAULT);
226		}
227
228		gva = vm_map_gpa(table->ctx, gpa, len);
229		if (gva == NULL) {
230			warnx("%s: could not map gpa [ 0x%16lx, 0x%16lx ]",
231			    __func__, gpa, gpa + len);
232			return (ENOMEM);
233		}
234
235		checksum_gva = gva + checksum->off;
236		if (checksum_gva < gva) {
237			warnx("%s: invalid checksum offset 0x%8x", __func__,
238			    checksum->off);
239			return (EFAULT);
240		}
241
242		sum = 0;
243		for (uint32_t i = 0; i < len; ++i) {
244			sum += *(gva + i);
245		}
246		*checksum_gva = -sum;
247	}
248
249	return (0);
250}
251
252static struct basl_table *
253basl_get_table_by_signature(const uint8_t signature[ACPI_NAMESEG_SIZE])
254{
255	struct basl_table *table;
256
257	STAILQ_FOREACH(table, &basl_tables, chain) {
258		const ACPI_TABLE_HEADER *const header =
259		    (const ACPI_TABLE_HEADER *)table->data;
260
261		if (strncmp(header->Signature, signature,
262			sizeof(header->Signature)) == 0) {
263			return (table);
264		}
265	}
266
267	warnx("%s: %.4s not found", __func__, signature);
268	return (NULL);
269}
270
271static int
272basl_finish_patch_pointers(struct basl_table *const table)
273{
274	struct basl_table_pointer *pointer;
275
276	STAILQ_FOREACH(pointer, &table->pointers, chain) {
277		const struct basl_table *src_table;
278		uint8_t *gva;
279		uint64_t gpa, val;
280
281		assert(pointer->off < table->len);
282		assert(pointer->off + pointer->size <= table->len);
283
284		src_table = basl_get_table_by_signature(pointer->src_signature);
285		if (src_table == NULL) {
286			warnx("%s: could not find ACPI table %.4s", __func__,
287			    pointer->src_signature);
288			return (EFAULT);
289		}
290
291		/* Cause guest BIOS to patch the pointer. */
292		BASL_EXEC(
293		    qemu_loader_add_pointer(basl_loader, table->fwcfg_name,
294			src_table->fwcfg_name, pointer->off, pointer->size));
295
296		if (!load_into_memory) {
297			continue;
298		}
299
300		/*
301		 * Install ACPI tables directly in guest memory for use by
302		 * guests which do not boot via EFI. EFI ROMs provide a pointer
303		 * to the firmware generated ACPI tables instead, but it doesn't
304		 * hurt to install the tables always.
305		 */
306		gpa = BHYVE_ACPI_BASE + table->off;
307		if (gpa < BHYVE_ACPI_BASE) {
308			warnx("%s: table offset of 0x%8x is too large",
309			    __func__, table->off);
310			return (EFAULT);
311		}
312
313		gva = vm_map_gpa(table->ctx, gpa, table->len);
314		if (gva == NULL) {
315			warnx("%s: could not map gpa [ 0x%16lx, 0x%16lx ]",
316			    __func__, gpa, gpa + table->len);
317			return (ENOMEM);
318		}
319
320		val = basl_le_dec(gva + pointer->off, pointer->size);
321		val += BHYVE_ACPI_BASE + src_table->off;
322		basl_le_enc(gva + pointer->off, val, pointer->size);
323	}
324
325	return (0);
326}
327
328static int
329basl_finish_set_length(struct basl_table *const table)
330{
331	struct basl_table_length *length;
332
333	STAILQ_FOREACH(length, &table->lengths, chain) {
334		assert(length->off < table->len);
335		assert(length->off + length->size <= table->len);
336
337		basl_le_enc((uint8_t *)table->data + length->off, table->len,
338		    length->size);
339	}
340
341	return (0);
342}
343
344int
345basl_finish(void)
346{
347	struct basl_table *table;
348	uint32_t off = 0;
349
350	if (STAILQ_EMPTY(&basl_tables)) {
351		warnx("%s: no ACPI tables found", __func__);
352		return (EINVAL);
353	}
354
355	/*
356	 * If we install ACPI tables by FwCfg and by memory, Windows will use
357	 * the tables from memory. This can cause issues when using advanced
358	 * features like a TPM log because we aren't able to patch the memory
359	 * tables accordingly.
360	 */
361	load_into_memory = get_config_bool_default("acpi_tables_in_memory",
362	    true);
363
364	/*
365	 * We have to install all tables before we can patch them. Therefore,
366	 * use two loops. The first one installs all tables and the second one
367	 * patches them.
368	 */
369	STAILQ_FOREACH(table, &basl_tables, chain) {
370		BASL_EXEC(basl_finish_set_length(table));
371		BASL_EXEC(basl_finish_install_guest_tables(table, &off));
372	}
373	STAILQ_FOREACH(table, &basl_tables, chain) {
374		BASL_EXEC(basl_finish_patch_pointers(table));
375
376		/*
377		 * Calculate the checksum as last step!
378		 */
379		BASL_EXEC(basl_finish_patch_checksums(table));
380	}
381	BASL_EXEC(qemu_loader_finish(basl_loader));
382
383	return (0);
384}
385
386static int
387basl_init_rsdt(struct vmctx *const ctx)
388{
389	BASL_EXEC(
390	    basl_table_create(&rsdt, ctx, ACPI_SIG_RSDT, BASL_TABLE_ALIGNMENT));
391
392	/* Header */
393	BASL_EXEC(basl_table_append_header(rsdt, ACPI_SIG_RSDT, 1, 1));
394	/* Pointers (added by basl_table_register_to_rsdt) */
395
396	return (0);
397}
398
399static int
400basl_init_xsdt(struct vmctx *const ctx)
401{
402	BASL_EXEC(
403	    basl_table_create(&xsdt, ctx, ACPI_SIG_XSDT, BASL_TABLE_ALIGNMENT));
404
405	/* Header */
406	BASL_EXEC(basl_table_append_header(xsdt, ACPI_SIG_XSDT, 1, 1));
407	/* Pointers (added by basl_table_register_to_rsdt) */
408
409	return (0);
410}
411
412int
413basl_init(struct vmctx *const ctx)
414{
415	BASL_EXEC(basl_init_rsdt(ctx));
416	BASL_EXEC(basl_init_xsdt(ctx));
417	BASL_EXEC(
418	    qemu_loader_create(&basl_loader, QEMU_FWCFG_FILE_TABLE_LOADER));
419
420	return (0);
421}
422
423int
424basl_table_add_checksum(struct basl_table *const table, const uint32_t off,
425    const uint32_t start, const uint32_t len)
426{
427	struct basl_table_checksum *checksum;
428
429	assert(table != NULL);
430
431	checksum = calloc(1, sizeof(struct basl_table_checksum));
432	if (checksum == NULL) {
433		warnx("%s: failed to allocate checksum", __func__);
434		return (ENOMEM);
435	}
436
437	checksum->off = off;
438	checksum->start = start;
439	checksum->len = len;
440
441	STAILQ_INSERT_TAIL(&table->checksums, checksum, chain);
442
443	return (0);
444}
445
446int
447basl_table_add_length(struct basl_table *const table, const uint32_t off,
448    const uint8_t size)
449{
450	struct basl_table_length *length;
451
452	assert(table != NULL);
453	assert(size == 4 || size == 8);
454
455	length = calloc(1, sizeof(struct basl_table_length));
456	if (length == NULL) {
457		warnx("%s: failed to allocate length", __func__);
458		return (ENOMEM);
459	}
460
461	length->off = off;
462	length->size = size;
463
464	STAILQ_INSERT_TAIL(&table->lengths, length, chain);
465
466	return (0);
467}
468
469int
470basl_table_add_pointer(struct basl_table *const table,
471    const uint8_t src_signature[ACPI_NAMESEG_SIZE], const uint32_t off,
472    const uint8_t size)
473{
474	struct basl_table_pointer *pointer;
475
476	assert(table != NULL);
477	assert(size == 4 || size == 8);
478
479	pointer = calloc(1, sizeof(struct basl_table_pointer));
480	if (pointer == NULL) {
481		warnx("%s: failed to allocate pointer", __func__);
482		return (ENOMEM);
483	}
484
485	memcpy(pointer->src_signature, src_signature,
486	    sizeof(pointer->src_signature));
487	pointer->off = off;
488	pointer->size = size;
489
490	STAILQ_INSERT_TAIL(&table->pointers, pointer, chain);
491
492	return (0);
493}
494
495int
496basl_table_append_bytes(struct basl_table *const table, const void *const bytes,
497    const uint32_t len)
498{
499	void *end;
500
501	assert(table != NULL);
502	assert(bytes != NULL);
503
504	if (table->len + len <= table->len) {
505		warnx("%s: table too large (table->len 0x%8x len 0x%8x)",
506		    __func__, table->len, len);
507		return (EFAULT);
508	}
509
510	table->data = reallocf(table->data, table->len + len);
511	if (table->data == NULL) {
512		warnx("%s: failed to realloc table to length 0x%8x", __func__,
513		    table->len + len);
514		table->len = 0;
515		return (ENOMEM);
516	}
517
518	end = (uint8_t *)table->data + table->len;
519	table->len += len;
520
521	memcpy(end, bytes, len);
522
523	return (0);
524}
525
526int
527basl_table_append_checksum(struct basl_table *const table, const uint32_t start,
528    const uint32_t len)
529{
530	assert(table != NULL);
531
532	BASL_EXEC(basl_table_add_checksum(table, table->len, start, len));
533	BASL_EXEC(basl_table_append_int(table, 0, 1));
534
535	return (0);
536}
537
538int
539basl_table_append_content(struct basl_table *table, void *data, uint32_t len)
540{
541	assert(data != NULL);
542	assert(len >= sizeof(ACPI_TABLE_HEADER));
543
544	return (basl_table_append_bytes(table,
545	    (void *)((uintptr_t)(data) + sizeof(ACPI_TABLE_HEADER)),
546	    len - sizeof(ACPI_TABLE_HEADER)));
547}
548
549int
550basl_table_append_fwcfg(struct basl_table *const table,
551    const uint8_t *fwcfg_name, const uint32_t alignment, const uint8_t size)
552{
553	assert(table != NULL);
554	assert(fwcfg_name != NULL);
555	assert(size <= sizeof(uint64_t));
556
557	BASL_EXEC(qemu_loader_alloc(basl_loader, fwcfg_name, alignment,
558	    QEMU_LOADER_ALLOC_HIGH));
559	BASL_EXEC(qemu_loader_add_pointer(basl_loader, table->fwcfg_name,
560	    fwcfg_name, table->len, size));
561	BASL_EXEC(basl_table_append_int(table, 0, size));
562
563	return (0);
564}
565
566int
567basl_table_append_gas(struct basl_table *const table, const uint8_t space_id,
568    const uint8_t bit_width, const uint8_t bit_offset,
569    const uint8_t access_width, const uint64_t address)
570{
571	ACPI_GENERIC_ADDRESS gas_le = {
572		.SpaceId = space_id,
573		.BitWidth = bit_width,
574		.BitOffset = bit_offset,
575		.AccessWidth = access_width,
576		.Address = htole64(address),
577	};
578
579	return (basl_table_append_bytes(table, &gas_le, sizeof(gas_le)));
580}
581
582int
583basl_table_append_header(struct basl_table *const table,
584    const uint8_t signature[ACPI_NAMESEG_SIZE], const uint8_t revision,
585    const uint32_t oem_revision)
586{
587	ACPI_TABLE_HEADER header_le;
588	/* + 1 is required for the null terminator */
589	char oem_table_id[ACPI_OEM_TABLE_ID_SIZE + 1];
590
591	assert(table != NULL);
592	assert(table->len == 0);
593
594	memcpy(header_le.Signature, signature, ACPI_NAMESEG_SIZE);
595	header_le.Length = 0; /* patched by basl_finish */
596	header_le.Revision = revision;
597	header_le.Checksum = 0; /* patched by basl_finish */
598	memcpy(header_le.OemId, "BHYVE ", ACPI_OEM_ID_SIZE);
599	snprintf(oem_table_id, ACPI_OEM_TABLE_ID_SIZE, "BV%.4s  ", signature);
600	memcpy(header_le.OemTableId, oem_table_id,
601	    sizeof(header_le.OemTableId));
602	header_le.OemRevision = htole32(oem_revision);
603	memcpy(header_le.AslCompilerId, "BASL", ACPI_NAMESEG_SIZE);
604	header_le.AslCompilerRevision = htole32(0x20220504);
605
606	BASL_EXEC(
607	    basl_table_append_bytes(table, &header_le, sizeof(header_le)));
608
609	BASL_EXEC(basl_table_add_length(table,
610	    offsetof(ACPI_TABLE_HEADER, Length), sizeof(header_le.Length)));
611	BASL_EXEC(basl_table_add_checksum(table,
612	    offsetof(ACPI_TABLE_HEADER, Checksum), 0,
613	    BASL_TABLE_CHECKSUM_LEN_FULL_TABLE));
614
615	return (0);
616}
617
618int
619basl_table_append_int(struct basl_table *const table, const uint64_t val,
620    const uint8_t size)
621{
622	char buf[8];
623
624	assert(size <= sizeof(val));
625
626	basl_le_enc(buf, val, size);
627	return (basl_table_append_bytes(table, buf, size));
628}
629
630int
631basl_table_append_length(struct basl_table *const table, const uint8_t size)
632{
633	assert(table != NULL);
634	assert(size <= sizeof(table->len));
635
636	BASL_EXEC(basl_table_add_length(table, table->len, size));
637	BASL_EXEC(basl_table_append_int(table, 0, size));
638
639	return (0);
640}
641
642int
643basl_table_append_pointer(struct basl_table *const table,
644    const uint8_t src_signature[ACPI_NAMESEG_SIZE], const uint8_t size)
645{
646	assert(table != NULL);
647	assert(size == 4 || size == 8);
648
649	BASL_EXEC(basl_table_add_pointer(table, src_signature, table->len, size));
650	BASL_EXEC(basl_table_append_int(table, 0, size));
651
652	return (0);
653}
654
655int
656basl_table_create(struct basl_table **const table, struct vmctx *ctx,
657    const uint8_t *const name, const uint32_t alignment)
658{
659	struct basl_table *new_table;
660
661	assert(table != NULL);
662
663	new_table = calloc(1, sizeof(struct basl_table));
664	if (new_table == NULL) {
665		warnx("%s: failed to allocate table", __func__);
666		return (ENOMEM);
667	}
668
669	new_table->ctx = ctx;
670
671	snprintf(new_table->fwcfg_name, sizeof(new_table->fwcfg_name),
672	    "etc/acpi/%s", name);
673
674	new_table->alignment = alignment;
675
676	STAILQ_INIT(&new_table->checksums);
677	STAILQ_INIT(&new_table->lengths);
678	STAILQ_INIT(&new_table->pointers);
679
680	STAILQ_INSERT_TAIL(&basl_tables, new_table, chain);
681
682	*table = new_table;
683
684	return (0);
685}
686
687int
688basl_table_register_to_rsdt(struct basl_table *table)
689{
690	const ACPI_TABLE_HEADER *header;
691
692	assert(table != NULL);
693
694	header = (const ACPI_TABLE_HEADER *)table->data;
695
696	BASL_EXEC(basl_table_append_pointer(rsdt, header->Signature,
697	    ACPI_RSDT_ENTRY_SIZE));
698	BASL_EXEC(basl_table_append_pointer(xsdt, header->Signature,
699	    ACPI_XSDT_ENTRY_SIZE));
700
701	return (0);
702}
703