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