diff --git a/usr.sbin/bhyve/basl.h b/usr.sbin/bhyve/basl.h --- a/usr.sbin/bhyve/basl.h +++ b/usr.sbin/bhyve/basl.h @@ -49,5 +49,7 @@ uint64_t address); int basl_table_append_int(struct basl_table *table, uint64_t val, uint8_t size); int basl_table_append_length(struct basl_table *table, uint8_t size); +int basl_table_append_pointer(struct basl_table *table, + const uint8_t src_sign[ACPI_NAMESEG_SIZE], uint8_t size); int basl_table_create(struct basl_table **table, struct vmctx *ctx, const uint8_t *name, uint32_t alignment, uint32_t off); diff --git a/usr.sbin/bhyve/basl.c b/usr.sbin/bhyve/basl.c --- a/usr.sbin/bhyve/basl.c +++ b/usr.sbin/bhyve/basl.c @@ -36,6 +36,13 @@ uint8_t size; }; +struct basl_table_pointer { + STAILQ_ENTRY(basl_table_pointer) chain; + uint8_t src_sign[ACPI_NAMESEG_SIZE]; + uint32_t off; + uint8_t size; +}; + struct basl_table { STAILQ_ENTRY(basl_table) chain; struct vmctx *ctx; @@ -47,6 +54,7 @@ STAILQ_HEAD(basl_table_checksum_list, basl_table_checksum) checksums; STAILQ_HEAD(basl_table_length_list, basl_table_length) lengths; + STAILQ_HEAD(basl_table_pointer_list, basl_table_pointer) pointers; }; static STAILQ_HEAD(basl_table_list, basl_table) basl_tables = STAILQ_HEAD_INITIALIZER( basl_tables); @@ -183,6 +191,101 @@ return (0); } +static struct basl_table * +basl_get_table_by_sign(const uint8_t sign[ACPI_NAMESEG_SIZE]) +{ + struct basl_table *table; + STAILQ_FOREACH(table, &basl_tables, chain) { + const ACPI_TABLE_HEADER *const header = + (const ACPI_TABLE_HEADER *)table->data; + if (strncmp(header->Signature, sign, + sizeof(header->Signature)) == 0) { + return (table); + } + } + + warnx("%s: %c%c%c%c not found", __func__, sign[0], sign[1], sign[2], + sign[3]); + return (NULL); +} + +static int +basl_finish_patch_pointers(struct basl_table *const table) +{ + struct basl_table_pointer *pointer; + STAILQ_FOREACH(pointer, &table->pointers, chain) { + assert(pointer->off < table->len); + assert(pointer->off + pointer->size <= table->len); + + const struct basl_table *const src_table = + basl_get_table_by_sign(pointer->src_sign); + if (src_table == NULL) { + warnx("%s: could not find ACPI table %c%c%c%c", + __func__, pointer->src_sign[0], + pointer->src_sign[1], pointer->src_sign[2], + pointer->src_sign[3]); + return (EFAULT); + } + + /* + * Guest bios versions without qemu fwcfg support search for + * ACPI tables in the guest memory and install them as is. + * Therefore, patch the pointers in the guest memory copies + * manually. + */ + const uint64_t gpa = BHYVE_ACPI_BASE + table->off; + if (gpa < BHYVE_ACPI_BASE) { + warnx("%s: table offset of 0x%8x is too large", + __func__, table->off); + return (EFAULT); + } + + uint8_t *const gva = vm_map_gpa(table->ctx, gpa, table->len); + if (gva == NULL) { + warnx("%s: could not map gpa [ 0x%16lx, 0x%16lx ]", + __func__, gpa, gpa + table->len); + return (ENOMEM); + } + + /* + * ACPI tables are always little endian. So, memcpy works here + * for fetching the current value. + */ + uint64_t val_le = 0; + memcpy(&val_le, gva + pointer->off, pointer->size); + + uint64_t val; + switch (pointer->size) { + case 1: + val = val_le; + break; + case 2: + val = le16toh(val_le); + break; + case 4: + val = le32toh(val_le); + break; + case 8: + val = le64toh(val_le); + break; + default: + warnx("%s: invalid pointer size %x", __func__, + pointer->size); + return (EINVAL); + } + val += BHYVE_ACPI_BASE + src_table->off; + + /* + * ACPI tables are always little endian. So, memcpy works here + * for writing the new value. + */ + val_le = htole64(val); + memcpy(gva + pointer->off, &val_le, pointer->size); + } + + return (0); +} + static int basl_finish_set_length(struct basl_table *const table) { @@ -217,6 +320,7 @@ BASL_EXEC(basl_finish_install_guest_tables(table)); } STAILQ_FOREACH(table, &basl_tables, chain) { + BASL_EXEC(basl_finish_patch_pointers(table)); /* * Calculate the checksum as last step! */ @@ -271,6 +375,27 @@ return (0); } +static int +basl_table_add_pointer(struct basl_table *const table, + const uint8_t src_sign[ACPI_NAMESEG_SIZE], const uint32_t off, + const uint8_t size) +{ + struct basl_table_pointer *const pointer = calloc(1, + sizeof(struct basl_table_pointer)); + if (pointer == NULL) { + warnx("%s: failed to allocate pointer", __func__); + return (ENOMEM); + } + + memcpy(pointer->src_sign, src_sign, sizeof(pointer->src_sign)); + pointer->off = off; + pointer->size = size; + + STAILQ_INSERT_TAIL(&table->pointers, pointer, chain); + + return (0); +} + int basl_table_append_bytes(struct basl_table *const table, const void *const bytes, const uint32_t len) @@ -349,6 +474,19 @@ return (0); } +int +basl_table_append_pointer(struct basl_table *const table, + const uint8_t src_sign[ACPI_NAMESEG_SIZE], const uint8_t size) +{ + assert(table != NULL); + assert(size <= sizeof(UINT64)); + + BASL_EXEC(basl_table_add_pointer(table, src_sign, table->len, size)); + BASL_EXEC(basl_table_append_int(table, 0, size)); + + return (0); +} + int basl_table_create(struct basl_table **const table, struct vmctx *ctx, const uint8_t *const name, const uint32_t alignment, @@ -374,6 +512,7 @@ STAILQ_INIT(&new_table->checksums); STAILQ_INIT(&new_table->lengths); + STAILQ_INIT(&new_table->pointers); STAILQ_INSERT_TAIL(&basl_tables, new_table, chain);