15325 bhyve upstream sync 2023 January
[illumos-gate.git] / usr / src / cmd / bhyve / basl.c
blobe0c05d69eafd83c85763e133ced3fa12566f5e61
1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
4 * Copyright (c) 2022 Beckhoff Automation GmbH & Co. KG
5 */
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>
13 #include <machine/vmm.h>
15 #include <assert.h>
16 #include <err.h>
17 #include <libutil.h>
18 #include <stddef.h>
19 #include <stdio.h>
20 #include <vmmapi.h>
22 #ifndef __FreeBSD__
23 #include "hexdump.h"
24 #endif
26 #include "basl.h"
28 struct basl_table_checksum {
29 STAILQ_ENTRY(basl_table_checksum) chain;
30 uint32_t off;
31 uint32_t start;
32 uint32_t len;
35 struct basl_table_length {
36 STAILQ_ENTRY(basl_table_length) chain;
37 uint32_t off;
38 uint8_t size;
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;
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;
60 static STAILQ_HEAD(basl_table_list, basl_table) basl_tables = STAILQ_HEAD_INITIALIZER(
61 basl_tables);
63 static __inline uint64_t
64 basl_le_dec(void *pp, size_t len)
66 assert(len <= 8);
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);
79 return 0;
82 static __inline void
83 basl_le_enc(void *pp, uint64_t val, size_t len)
85 char buf[8];
87 assert(len <= 8);
89 le64enc(buf, val);
90 memcpy(pp, buf, len);
93 static int
94 basl_dump_table(const struct basl_table *const table, const bool mem)
96 const ACPI_TABLE_HEADER *const header = table->data;
97 const uint8_t *data;
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);
109 printf("%.4s @ %8x (%s)\n", header->Signature,
110 BHYVE_ACPI_BASE + table->off, mem ? "Memory" : "FwCfg");
111 hexdump(data, table->len, NULL, 0);
113 return (0);
116 static int __unused
117 basl_dump(const bool mem)
119 struct basl_table *table;
121 STAILQ_FOREACH(table, &basl_tables, chain) {
122 BASL_EXEC(basl_dump_table(table, mem));
125 return (0);
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)
133 assert(gas != NULL);
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);
142 static int
143 basl_finish_install_guest_tables(struct basl_table *const table, uint32_t *const off)
145 void *gva;
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);
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.
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);
168 memcpy(gva, table->data, table->len);
170 return (0);
173 static int
174 basl_finish_patch_checksums(struct basl_table *const table)
176 struct basl_table_checksum *checksum;
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;
184 len = checksum->len;
185 if (len == BASL_TABLE_CHECKSUM_LEN_FULL_TABLE) {
186 len = table->len;
189 assert(checksum->off < table->len);
190 assert(checksum->start < table->len);
191 assert(checksum->start + len <= table->len);
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.
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);
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);
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);
221 sum = 0;
222 for (uint32_t i = 0; i < len; ++i) {
223 sum += *(gva + i);
225 *checksum_gva = -sum;
228 return (0);
231 static struct basl_table *
232 basl_get_table_by_signature(const uint8_t signature[ACPI_NAMESEG_SIZE])
234 struct basl_table *table;
236 STAILQ_FOREACH(table, &basl_tables, chain) {
237 const ACPI_TABLE_HEADER *const header =
238 (const ACPI_TABLE_HEADER *)table->data;
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
252 warnx("%s: %.4s not found", __func__, signature);
253 return (NULL);
256 static int
257 basl_finish_patch_pointers(struct basl_table *const table)
259 struct basl_table_pointer *pointer;
261 STAILQ_FOREACH(pointer, &table->pointers, chain) {
262 const struct basl_table *src_table;
263 uint8_t *gva;
264 uint64_t gpa, val;
266 assert(pointer->off < table->len);
267 assert(pointer->off + pointer->size <= table->len);
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);
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.
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);
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);
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);
301 return (0);
304 static int
305 basl_finish_set_length(struct basl_table *const table)
307 struct basl_table_length *length;
309 STAILQ_FOREACH(length, &table->lengths, chain) {
310 assert(length->off < table->len);
311 assert(length->off + length->size <= table->len);
313 basl_le_enc((uint8_t *)table->data + length->off, table->len,
314 length->size);
317 return (0);
321 basl_finish(void)
323 struct basl_table *table;
324 uint32_t off = 0;
326 if (STAILQ_EMPTY(&basl_tables)) {
327 warnx("%s: no ACPI tables found", __func__);
328 return (EINVAL);
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.
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));
340 STAILQ_FOREACH(table, &basl_tables, chain) {
341 BASL_EXEC(basl_finish_patch_pointers(table));
344 * Calculate the checksum as last step!
346 BASL_EXEC(basl_finish_patch_checksums(table));
349 return (0);
353 basl_init(void)
355 return (0);
359 basl_table_add_checksum(struct basl_table *const table, const uint32_t off,
360 const uint32_t start, const uint32_t len)
362 struct basl_table_checksum *checksum;
364 assert(table != NULL);
366 checksum = calloc(1, sizeof(struct basl_table_checksum));
367 if (checksum == NULL) {
368 warnx("%s: failed to allocate checksum", __func__);
369 return (ENOMEM);
372 checksum->off = off;
373 checksum->start = start;
374 checksum->len = len;
376 STAILQ_INSERT_TAIL(&table->checksums, checksum, chain);
378 return (0);
382 basl_table_add_length(struct basl_table *const table, const uint32_t off,
383 const uint8_t size)
385 struct basl_table_length *length;
387 assert(table != NULL);
388 assert(size == 4 || size == 8);
390 length = calloc(1, sizeof(struct basl_table_length));
391 if (length == NULL) {
392 warnx("%s: failed to allocate length", __func__);
393 return (ENOMEM);
396 length->off = off;
397 length->size = size;
399 STAILQ_INSERT_TAIL(&table->lengths, length, chain);
401 return (0);
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)
409 struct basl_table_pointer *pointer;
411 assert(table != NULL);
412 assert(size == 4 || size == 8);
414 pointer = calloc(1, sizeof(struct basl_table_pointer));
415 if (pointer == NULL) {
416 warnx("%s: failed to allocate pointer", __func__);
417 return (ENOMEM);
420 memcpy(pointer->src_signature, src_signature,
421 sizeof(pointer->src_signature));
422 pointer->off = off;
423 pointer->size = size;
425 STAILQ_INSERT_TAIL(&table->pointers, pointer, chain);
427 return (0);
431 basl_table_append_bytes(struct basl_table *const table, const void *const bytes,
432 const uint32_t len)
434 void *end;
436 assert(table != NULL);
437 assert(bytes != NULL);
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);
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);
453 end = (uint8_t *)table->data + table->len;
454 table->len += len;
456 memcpy(end, bytes, len);
458 return (0);
462 basl_table_append_checksum(struct basl_table *const table, const uint32_t start,
463 const uint32_t len)
465 assert(table != NULL);
467 BASL_EXEC(basl_table_add_checksum(table, table->len, start, len));
468 BASL_EXEC(basl_table_append_int(table, 0, 1));
470 return (0);
474 basl_table_append_content(struct basl_table *table, void *data, uint32_t len)
476 assert(data != NULL);
477 assert(len >= sizeof(ACPI_TABLE_HEADER));
479 return (basl_table_append_bytes(table,
480 (void *)((uintptr_t)(data) + sizeof(ACPI_TABLE_HEADER)),
481 len - sizeof(ACPI_TABLE_HEADER)));
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)
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),
497 return (basl_table_append_bytes(table, &gas_le, sizeof(gas_le)));
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)
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];
509 assert(table != NULL);
510 assert(table->len == 0);
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);
524 BASL_EXEC(
525 basl_table_append_bytes(table, &header_le, sizeof(header_le)));
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));
533 return (0);
537 basl_table_append_int(struct basl_table *const table, const uint64_t val,
538 const uint8_t size)
540 char buf[8];
542 assert(size <= sizeof(val));
544 basl_le_enc(buf, val, size);
545 return (basl_table_append_bytes(table, buf, size));
549 basl_table_append_length(struct basl_table *const table, const uint8_t size)
551 assert(table != NULL);
552 assert(size <= sizeof(table->len));
554 BASL_EXEC(basl_table_add_length(table, table->len, size));
555 BASL_EXEC(basl_table_append_int(table, 0, size));
557 return (0);
561 basl_table_append_pointer(struct basl_table *const table,
562 const uint8_t src_signature[ACPI_NAMESEG_SIZE], const uint8_t size)
564 assert(table != NULL);
565 assert(size == 4 || size == 8);
567 BASL_EXEC(basl_table_add_pointer(table, src_signature, table->len, size));
568 BASL_EXEC(basl_table_append_int(table, 0, size));
570 return (0);
574 basl_table_create(struct basl_table **const table, struct vmctx *ctx,
575 const uint8_t *const name, const uint32_t alignment)
577 struct basl_table *new_table;
579 assert(table != NULL);
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);
587 new_table->ctx = ctx;
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
597 new_table->alignment = alignment;
599 STAILQ_INIT(&new_table->checksums);
600 STAILQ_INIT(&new_table->lengths);
601 STAILQ_INIT(&new_table->pointers);
603 STAILQ_INSERT_TAIL(&basl_tables, new_table, chain);
605 *table = new_table;
607 return (0);