xref: /freebsd/usr.sbin/bhyve/basl.c (revision af23369a6deaaeb612ab266eb88b8bb8d560c322)
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 
24 struct basl_table_checksum {
25 	STAILQ_ENTRY(basl_table_checksum) chain;
26 	uint32_t off;
27 	uint32_t start;
28 	uint32_t len;
29 };
30 
31 struct basl_table_length {
32 	STAILQ_ENTRY(basl_table_length) chain;
33 	uint32_t off;
34 	uint8_t size;
35 };
36 
37 struct basl_table_pointer {
38 	STAILQ_ENTRY(basl_table_pointer) chain;
39 	uint8_t src_signature[ACPI_NAMESEG_SIZE];
40 	uint32_t off;
41 	uint8_t size;
42 };
43 
44 struct basl_table {
45 	STAILQ_ENTRY(basl_table) chain;
46 	struct vmctx *ctx;
47 	uint8_t fwcfg_name[QEMU_FWCFG_MAX_NAME];
48 	void *data;
49 	uint32_t len;
50 	uint32_t off;
51 	uint32_t alignment;
52 	STAILQ_HEAD(basl_table_checksum_list, basl_table_checksum) checksums;
53 	STAILQ_HEAD(basl_table_length_list, basl_table_length) lengths;
54 	STAILQ_HEAD(basl_table_pointer_list, basl_table_pointer) pointers;
55 };
56 static STAILQ_HEAD(basl_table_list, basl_table) basl_tables = STAILQ_HEAD_INITIALIZER(
57     basl_tables);
58 
59 static __inline uint64_t
60 basl_le_dec(void *pp, size_t len)
61 {
62 	assert(len <= 8);
63 
64 	switch (len) {
65 	case 1:
66 		return ((uint8_t *)pp)[0];
67 	case 2:
68 		return le16dec(pp);
69 	case 4:
70 		return le32dec(pp);
71 	case 8:
72 		return le64dec(pp);
73 	}
74 
75 	return 0;
76 }
77 
78 static __inline void
79 basl_le_enc(void *pp, uint64_t val, size_t len)
80 {
81 	char buf[8];
82 
83 	assert(len <= 8);
84 
85 	le64enc(buf, val);
86 	memcpy(pp, buf, len);
87 }
88 
89 static int
90 basl_dump_table(const struct basl_table *const table, const bool mem)
91 {
92 	const ACPI_TABLE_HEADER *const header = table->data;
93 	const uint8_t *data;
94 
95 	if (!mem) {
96 		data = table->data;
97 	} else {
98 		data = vm_map_gpa(table->ctx, BHYVE_ACPI_BASE + table->off,
99 		    table->len);
100 		if (data == NULL) {
101 			return (ENOMEM);
102 		}
103 	}
104 
105 	printf("%.4s @ %8x (%s)\n", header->Signature,
106 	    BHYVE_ACPI_BASE + table->off, mem ? "Memory" : "FwCfg");
107 	hexdump(data, table->len, NULL, 0);
108 
109 	return (0);
110 }
111 
112 static int __unused
113 basl_dump(const bool mem)
114 {
115 	struct basl_table *table;
116 
117 	STAILQ_FOREACH(table, &basl_tables, chain) {
118 		BASL_EXEC(basl_dump_table(table, mem));
119 	}
120 
121 	return (0);
122 }
123 
124 void
125 basl_fill_gas(ACPI_GENERIC_ADDRESS *const gas, const uint8_t space_id,
126     const uint8_t bit_width, const uint8_t bit_offset,
127     const uint8_t access_width, const uint64_t address)
128 {
129 	assert(gas != NULL);
130 
131 	gas->SpaceId = space_id;
132 	gas->BitWidth = bit_width;
133 	gas->BitOffset = bit_offset;
134 	gas->AccessWidth = access_width;
135 	gas->Address = htole64(address);
136 }
137 
138 static int
139 basl_finish_install_guest_tables(struct basl_table *const table, uint32_t *const off)
140 {
141 	void *gva;
142 
143 	table->off = roundup2(*off, table->alignment);
144 	*off = table->off + table->len;
145 	if (*off <= table->off) {
146 		warnx("%s: invalid table length 0x%8x @ offset 0x%8x", __func__,
147 		    table->len, table->off);
148 		return (EFAULT);
149 	}
150 
151 	/*
152 	 * Install ACPI tables directly in guest memory for use by guests which
153 	 * do not boot via EFI. EFI ROMs provide a pointer to the firmware
154 	 * generated ACPI tables instead, but it doesn't hurt to install the
155 	 * tables always.
156 	 */
157 	gva = vm_map_gpa(table->ctx, BHYVE_ACPI_BASE + table->off, table->len);
158 	if (gva == NULL) {
159 		warnx("%s: could not map gpa [ 0x%16lx, 0x%16lx ]", __func__,
160 		    (uint64_t)BHYVE_ACPI_BASE + table->off,
161 		    (uint64_t)BHYVE_ACPI_BASE + table->off + table->len);
162 		return (ENOMEM);
163 	}
164 	memcpy(gva, table->data, table->len);
165 
166 	return (0);
167 }
168 
169 static int
170 basl_finish_patch_checksums(struct basl_table *const table)
171 {
172 	struct basl_table_checksum *checksum;
173 
174 	STAILQ_FOREACH(checksum, &table->checksums, chain) {
175 		uint8_t *gva, *checksum_gva;
176 		uint64_t gpa;
177 		uint32_t len;
178 		uint8_t sum;
179 
180 		len = checksum->len;
181 		if (len == BASL_TABLE_CHECKSUM_LEN_FULL_TABLE) {
182 			len = table->len;
183 		}
184 
185 		assert(checksum->off < table->len);
186 		assert(checksum->start < table->len);
187 		assert(checksum->start + len <= table->len);
188 
189 		/*
190 		 * Install ACPI tables directly in guest memory for use by
191 		 * guests which do not boot via EFI. EFI ROMs provide a pointer
192 		 * to the firmware generated ACPI tables instead, but it doesn't
193 		 * hurt to install the tables always.
194 		 */
195 		gpa = BHYVE_ACPI_BASE + table->off + checksum->start;
196 		if ((gpa < BHYVE_ACPI_BASE) ||
197 		    (gpa < BHYVE_ACPI_BASE + table->off)) {
198 			warnx("%s: invalid gpa (off 0x%8x start 0x%8x)",
199 			    __func__, table->off, checksum->start);
200 			return (EFAULT);
201 		}
202 
203 		gva = vm_map_gpa(table->ctx, gpa, len);
204 		if (gva == NULL) {
205 			warnx("%s: could not map gpa [ 0x%16lx, 0x%16lx ]",
206 			    __func__, gpa, gpa + len);
207 			return (ENOMEM);
208 		}
209 
210 		checksum_gva = gva + checksum->off;
211 		if (checksum_gva < gva) {
212 			warnx("%s: invalid checksum offset 0x%8x", __func__,
213 			    checksum->off);
214 			return (EFAULT);
215 		}
216 
217 		sum = 0;
218 		for (uint32_t i = 0; i < len; ++i) {
219 			sum += *(gva + i);
220 		}
221 		*checksum_gva = -sum;
222 	}
223 
224 	return (0);
225 }
226 
227 static struct basl_table *
228 basl_get_table_by_signature(const uint8_t signature[ACPI_NAMESEG_SIZE])
229 {
230 	struct basl_table *table;
231 
232 	STAILQ_FOREACH(table, &basl_tables, chain) {
233 		const ACPI_TABLE_HEADER *const header =
234 		    (const ACPI_TABLE_HEADER *)table->data;
235 
236 		if (strncmp(header->Signature, signature,
237 			sizeof(header->Signature)) == 0) {
238 			return (table);
239 		}
240 	}
241 
242 	warnx("%s: %.4s not found", __func__, signature);
243 	return (NULL);
244 }
245 
246 static int
247 basl_finish_patch_pointers(struct basl_table *const table)
248 {
249 	struct basl_table_pointer *pointer;
250 
251 	STAILQ_FOREACH(pointer, &table->pointers, chain) {
252 		const struct basl_table *src_table;
253 		uint8_t *gva;
254 		uint64_t gpa, val;
255 
256 		assert(pointer->off < table->len);
257 		assert(pointer->off + pointer->size <= table->len);
258 
259 		src_table = basl_get_table_by_signature(pointer->src_signature);
260 		if (src_table == NULL) {
261 			warnx("%s: could not find ACPI table %.4s", __func__,
262 			    pointer->src_signature);
263 			return (EFAULT);
264 		}
265 
266 		/*
267 		 * Install ACPI tables directly in guest memory for use by
268 		 * guests which do not boot via EFI. EFI ROMs provide a pointer
269 		 * to the firmware generated ACPI tables instead, but it doesn't
270 		 * hurt to install the tables always.
271 		 */
272 		gpa = BHYVE_ACPI_BASE + table->off;
273 		if (gpa < BHYVE_ACPI_BASE) {
274 			warnx("%s: table offset of 0x%8x is too large",
275 			    __func__, table->off);
276 			return (EFAULT);
277 		}
278 
279 		gva = vm_map_gpa(table->ctx, gpa, table->len);
280 		if (gva == NULL) {
281 			warnx("%s: could not map gpa [ 0x%16lx, 0x%16lx ]",
282 			    __func__, gpa, gpa + table->len);
283 			return (ENOMEM);
284 		}
285 
286 		val = basl_le_dec(gva + pointer->off, pointer->size);
287 		val += BHYVE_ACPI_BASE + src_table->off;
288 		basl_le_enc(gva + pointer->off, val, pointer->size);
289 	}
290 
291 	return (0);
292 }
293 
294 static int
295 basl_finish_set_length(struct basl_table *const table)
296 {
297 	struct basl_table_length *length;
298 
299 	STAILQ_FOREACH(length, &table->lengths, chain) {
300 		assert(length->off < table->len);
301 		assert(length->off + length->size <= table->len);
302 
303 		basl_le_enc((uint8_t *)table->data + length->off, table->len,
304 		    length->size);
305 	}
306 
307 	return (0);
308 }
309 
310 int
311 basl_finish(void)
312 {
313 	struct basl_table *table;
314 	uint32_t off = 0;
315 
316 	if (STAILQ_EMPTY(&basl_tables)) {
317 		warnx("%s: no ACPI tables found", __func__);
318 		return (EINVAL);
319 	}
320 
321 	/*
322 	 * We have to install all tables before we can patch them. Therefore,
323 	 * use two loops. The first one installs all tables and the second one
324 	 * patches them.
325 	 */
326 	STAILQ_FOREACH(table, &basl_tables, chain) {
327 		BASL_EXEC(basl_finish_set_length(table));
328 		BASL_EXEC(basl_finish_install_guest_tables(table, &off));
329 	}
330 	STAILQ_FOREACH(table, &basl_tables, chain) {
331 		BASL_EXEC(basl_finish_patch_pointers(table));
332 
333 		/*
334 		 * Calculate the checksum as last step!
335 		 */
336 		BASL_EXEC(basl_finish_patch_checksums(table));
337 	}
338 
339 	return (0);
340 }
341 
342 int
343 basl_init(void)
344 {
345 	return (0);
346 }
347 
348 int
349 basl_table_add_checksum(struct basl_table *const table, const uint32_t off,
350     const uint32_t start, const uint32_t len)
351 {
352 	struct basl_table_checksum *checksum;
353 
354 	assert(table != NULL);
355 
356 	checksum = calloc(1, sizeof(struct basl_table_checksum));
357 	if (checksum == NULL) {
358 		warnx("%s: failed to allocate checksum", __func__);
359 		return (ENOMEM);
360 	}
361 
362 	checksum->off = off;
363 	checksum->start = start;
364 	checksum->len = len;
365 
366 	STAILQ_INSERT_TAIL(&table->checksums, checksum, chain);
367 
368 	return (0);
369 }
370 
371 int
372 basl_table_add_length(struct basl_table *const table, const uint32_t off,
373     const uint8_t size)
374 {
375 	struct basl_table_length *length;
376 
377 	assert(table != NULL);
378 	assert(size == 4 || size == 8);
379 
380 	length = calloc(1, sizeof(struct basl_table_length));
381 	if (length == NULL) {
382 		warnx("%s: failed to allocate length", __func__);
383 		return (ENOMEM);
384 	}
385 
386 	length->off = off;
387 	length->size = size;
388 
389 	STAILQ_INSERT_TAIL(&table->lengths, length, chain);
390 
391 	return (0);
392 }
393 
394 int
395 basl_table_add_pointer(struct basl_table *const table,
396     const uint8_t src_signature[ACPI_NAMESEG_SIZE], const uint32_t off,
397     const uint8_t size)
398 {
399 	struct basl_table_pointer *pointer;
400 
401 	assert(table != NULL);
402 	assert(size == 4 || size == 8);
403 
404 	pointer = calloc(1, sizeof(struct basl_table_pointer));
405 	if (pointer == NULL) {
406 		warnx("%s: failed to allocate pointer", __func__);
407 		return (ENOMEM);
408 	}
409 
410 	memcpy(pointer->src_signature, src_signature,
411 	    sizeof(pointer->src_signature));
412 	pointer->off = off;
413 	pointer->size = size;
414 
415 	STAILQ_INSERT_TAIL(&table->pointers, pointer, chain);
416 
417 	return (0);
418 }
419 
420 int
421 basl_table_append_bytes(struct basl_table *const table, const void *const bytes,
422     const uint32_t len)
423 {
424 	void *end;
425 
426 	assert(table != NULL);
427 	assert(bytes != NULL);
428 
429 	if (table->len + len <= table->len) {
430 		warnx("%s: table too large (table->len 0x%8x len 0x%8x)",
431 		    __func__, table->len, len);
432 		return (EFAULT);
433 	}
434 
435 	table->data = reallocf(table->data, table->len + len);
436 	if (table->data == NULL) {
437 		warnx("%s: failed to realloc table to length 0x%8x", __func__,
438 		    table->len + len);
439 		table->len = 0;
440 		return (ENOMEM);
441 	}
442 
443 	end = (uint8_t *)table->data + table->len;
444 	table->len += len;
445 
446 	memcpy(end, bytes, len);
447 
448 	return (0);
449 }
450 
451 int
452 basl_table_append_checksum(struct basl_table *const table, const uint32_t start,
453     const uint32_t len)
454 {
455 	assert(table != NULL);
456 
457 	BASL_EXEC(basl_table_add_checksum(table, table->len, start, len));
458 	BASL_EXEC(basl_table_append_int(table, 0, 1));
459 
460 	return (0);
461 }
462 
463 int
464 basl_table_append_content(struct basl_table *table, void *data, uint32_t len)
465 {
466 	assert(data != NULL);
467 	assert(len >= sizeof(ACPI_TABLE_HEADER));
468 
469 	return (basl_table_append_bytes(table,
470 	    (void *)((uintptr_t)(data) + sizeof(ACPI_TABLE_HEADER)),
471 	    len - sizeof(ACPI_TABLE_HEADER)));
472 }
473 
474 int
475 basl_table_append_gas(struct basl_table *const table, const uint8_t space_id,
476     const uint8_t bit_width, const uint8_t bit_offset,
477     const uint8_t access_width, const uint64_t address)
478 {
479 	ACPI_GENERIC_ADDRESS gas_le = {
480 		.SpaceId = space_id,
481 		.BitWidth = bit_width,
482 		.BitOffset = bit_offset,
483 		.AccessWidth = access_width,
484 		.Address = htole64(address),
485 	};
486 
487 	return (basl_table_append_bytes(table, &gas_le, sizeof(gas_le)));
488 }
489 
490 int
491 basl_table_append_header(struct basl_table *const table,
492     const uint8_t signature[ACPI_NAMESEG_SIZE], const uint8_t revision,
493     const uint32_t oem_revision)
494 {
495 	ACPI_TABLE_HEADER header_le;
496 	/* + 1 is required for the null terminator */
497 	char oem_table_id[ACPI_OEM_TABLE_ID_SIZE + 1];
498 
499 	assert(table != NULL);
500 	assert(table->len == 0);
501 
502 	memcpy(header_le.Signature, signature, ACPI_NAMESEG_SIZE);
503 	header_le.Length = 0; /* patched by basl_finish */
504 	header_le.Revision = revision;
505 	header_le.Checksum = 0; /* patched by basl_finish */
506 	memcpy(header_le.OemId, "BHYVE ", ACPI_OEM_ID_SIZE);
507 	snprintf(oem_table_id, ACPI_OEM_TABLE_ID_SIZE, "BV%.4s  ", signature);
508 	memcpy(header_le.OemTableId, oem_table_id,
509 	    sizeof(header_le.OemTableId));
510 	header_le.OemRevision = htole32(oem_revision);
511 	memcpy(header_le.AslCompilerId, "BASL", ACPI_NAMESEG_SIZE);
512 	header_le.AslCompilerRevision = htole32(0x20220504);
513 
514 	BASL_EXEC(
515 	    basl_table_append_bytes(table, &header_le, sizeof(header_le)));
516 
517 	BASL_EXEC(basl_table_add_length(table,
518 	    offsetof(ACPI_TABLE_HEADER, Length), sizeof(header_le.Length)));
519 	BASL_EXEC(basl_table_add_checksum(table,
520 	    offsetof(ACPI_TABLE_HEADER, Checksum), 0,
521 	    BASL_TABLE_CHECKSUM_LEN_FULL_TABLE));
522 
523 	return (0);
524 }
525 
526 int
527 basl_table_append_int(struct basl_table *const table, const uint64_t val,
528     const uint8_t size)
529 {
530 	char buf[8];
531 
532 	assert(size <= sizeof(val));
533 
534 	basl_le_enc(buf, val, size);
535 	return (basl_table_append_bytes(table, buf, size));
536 }
537 
538 int
539 basl_table_append_length(struct basl_table *const table, const uint8_t size)
540 {
541 	assert(table != NULL);
542 	assert(size <= sizeof(table->len));
543 
544 	BASL_EXEC(basl_table_add_length(table, table->len, size));
545 	BASL_EXEC(basl_table_append_int(table, 0, size));
546 
547 	return (0);
548 }
549 
550 int
551 basl_table_append_pointer(struct basl_table *const table,
552     const uint8_t src_signature[ACPI_NAMESEG_SIZE], const uint8_t size)
553 {
554 	assert(table != NULL);
555 	assert(size == 4 || size == 8);
556 
557 	BASL_EXEC(basl_table_add_pointer(table, src_signature, table->len, size));
558 	BASL_EXEC(basl_table_append_int(table, 0, size));
559 
560 	return (0);
561 }
562 
563 int
564 basl_table_create(struct basl_table **const table, struct vmctx *ctx,
565     const uint8_t *const name, const uint32_t alignment)
566 {
567 	struct basl_table *new_table;
568 
569 	assert(table != NULL);
570 
571 	new_table = calloc(1, sizeof(struct basl_table));
572 	if (new_table == NULL) {
573 		warnx("%s: failed to allocate table", __func__);
574 		return (ENOMEM);
575 	}
576 
577 	new_table->ctx = ctx;
578 
579 	snprintf(new_table->fwcfg_name, sizeof(new_table->fwcfg_name),
580 	    "etc/acpi/%s", name);
581 
582 	new_table->alignment = alignment;
583 
584 	STAILQ_INIT(&new_table->checksums);
585 	STAILQ_INIT(&new_table->lengths);
586 	STAILQ_INIT(&new_table->pointers);
587 
588 	STAILQ_INSERT_TAIL(&basl_tables, new_table, chain);
589 
590 	*table = new_table;
591 
592 	return (0);
593 }
594