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