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 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 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 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 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 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 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 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 * 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 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 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 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 362 basl_init(void) 363 { 364 return (qemu_loader_create(&basl_loader, QEMU_FWCFG_FILE_TABLE_LOADER)); 365 } 366 367 int 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 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 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 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 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 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 494 basl_table_append_gas(struct basl_table *const table, const uint8_t space_id, 495 const uint8_t bit_width, const uint8_t bit_offset, 496 const uint8_t access_width, const uint64_t address) 497 { 498 ACPI_GENERIC_ADDRESS gas_le = { 499 .SpaceId = space_id, 500 .BitWidth = bit_width, 501 .BitOffset = bit_offset, 502 .AccessWidth = access_width, 503 .Address = htole64(address), 504 }; 505 506 return (basl_table_append_bytes(table, &gas_le, sizeof(gas_le))); 507 } 508 509 int 510 basl_table_append_header(struct basl_table *const table, 511 const uint8_t signature[ACPI_NAMESEG_SIZE], const uint8_t revision, 512 const uint32_t oem_revision) 513 { 514 ACPI_TABLE_HEADER header_le; 515 /* + 1 is required for the null terminator */ 516 char oem_table_id[ACPI_OEM_TABLE_ID_SIZE + 1]; 517 518 assert(table != NULL); 519 assert(table->len == 0); 520 521 memcpy(header_le.Signature, signature, ACPI_NAMESEG_SIZE); 522 header_le.Length = 0; /* patched by basl_finish */ 523 header_le.Revision = revision; 524 header_le.Checksum = 0; /* patched by basl_finish */ 525 memcpy(header_le.OemId, "BHYVE ", ACPI_OEM_ID_SIZE); 526 snprintf(oem_table_id, ACPI_OEM_TABLE_ID_SIZE, "BV%.4s ", signature); 527 memcpy(header_le.OemTableId, oem_table_id, 528 sizeof(header_le.OemTableId)); 529 header_le.OemRevision = htole32(oem_revision); 530 memcpy(header_le.AslCompilerId, "BASL", ACPI_NAMESEG_SIZE); 531 header_le.AslCompilerRevision = htole32(0x20220504); 532 533 BASL_EXEC( 534 basl_table_append_bytes(table, &header_le, sizeof(header_le))); 535 536 BASL_EXEC(basl_table_add_length(table, 537 offsetof(ACPI_TABLE_HEADER, Length), sizeof(header_le.Length))); 538 BASL_EXEC(basl_table_add_checksum(table, 539 offsetof(ACPI_TABLE_HEADER, Checksum), 0, 540 BASL_TABLE_CHECKSUM_LEN_FULL_TABLE)); 541 542 return (0); 543 } 544 545 int 546 basl_table_append_int(struct basl_table *const table, const uint64_t val, 547 const uint8_t size) 548 { 549 char buf[8]; 550 551 assert(size <= sizeof(val)); 552 553 basl_le_enc(buf, val, size); 554 return (basl_table_append_bytes(table, buf, size)); 555 } 556 557 int 558 basl_table_append_length(struct basl_table *const table, const uint8_t size) 559 { 560 assert(table != NULL); 561 assert(size <= sizeof(table->len)); 562 563 BASL_EXEC(basl_table_add_length(table, table->len, size)); 564 BASL_EXEC(basl_table_append_int(table, 0, size)); 565 566 return (0); 567 } 568 569 int 570 basl_table_append_pointer(struct basl_table *const table, 571 const uint8_t src_signature[ACPI_NAMESEG_SIZE], const uint8_t size) 572 { 573 assert(table != NULL); 574 assert(size == 4 || size == 8); 575 576 BASL_EXEC(basl_table_add_pointer(table, src_signature, table->len, size)); 577 BASL_EXEC(basl_table_append_int(table, 0, size)); 578 579 return (0); 580 } 581 582 int 583 basl_table_create(struct basl_table **const table, struct vmctx *ctx, 584 const uint8_t *const name, const uint32_t alignment) 585 { 586 struct basl_table *new_table; 587 588 assert(table != NULL); 589 590 new_table = calloc(1, sizeof(struct basl_table)); 591 if (new_table == NULL) { 592 warnx("%s: failed to allocate table", __func__); 593 return (ENOMEM); 594 } 595 596 new_table->ctx = ctx; 597 598 snprintf(new_table->fwcfg_name, sizeof(new_table->fwcfg_name), 599 "etc/acpi/%s", name); 600 601 new_table->alignment = alignment; 602 603 STAILQ_INIT(&new_table->checksums); 604 STAILQ_INIT(&new_table->lengths); 605 STAILQ_INIT(&new_table->pointers); 606 607 STAILQ_INSERT_TAIL(&basl_tables, new_table, chain); 608 609 *table = new_table; 610 611 return (0); 612 } 613