freebsd-dev/usr.sbin/bhyve/basl.c
Corvin Köhne 6f7e9779fc
bhyve: add config option to load ACPI tables into memory
For backward compatibility, the ACPI tables are loaded into the guest
memory. Windows scans the memory, finds the ACPI tables and uses them.
It ignores the ACPI tables provided by the UEFI. We are patching the
ACPI tables in the guest memory, so that's mostly fine. However, Windows
will break when the ACPI tables become to large or when we add entries
which can't be patched by bhyve. One example of an unpatchable entry, is
a TPM log. The TPM log has to be allocated by the guest firmware. As the
address of the TPM log is unpredictable, bhyve can't assign it in the
memory version of the ACPI tables. Additionally, this makes it
impossible for bhyve to calculate a correct checksum of the table.

By default ACPI tables are still loaded into guest memory for backward
compatibility. The new acpi_tables_in_memory config value can be set to
false to avoid this behaviour.

Reviewed by:		markj
MFC after:		1 week
Sponsored by:		Beckhoff Automation GmbH & Co. KG
Differential Revision:	https://reviews.freebsd.org/D39979
2023-08-22 07:49:00 +02:00

703 lines
16 KiB
C

/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2022 Beckhoff Automation GmbH & Co. KG
*/
#include <sys/param.h>
#include <sys/endian.h>
#include <sys/errno.h>
#include <sys/queue.h>
#include <sys/stat.h>
#include <machine/vmm.h>
#include <assert.h>
#include <err.h>
#include <libutil.h>
#include <stddef.h>
#include <stdio.h>
#include <vmmapi.h>
#include "basl.h"
#include "config.h"
#include "qemu_loader.h"
struct basl_table_checksum {
STAILQ_ENTRY(basl_table_checksum) chain;
uint32_t off;
uint32_t start;
uint32_t len;
};
struct basl_table_length {
STAILQ_ENTRY(basl_table_length) chain;
uint32_t off;
uint8_t size;
};
struct basl_table_pointer {
STAILQ_ENTRY(basl_table_pointer) chain;
uint8_t src_signature[ACPI_NAMESEG_SIZE];
uint32_t off;
uint8_t size;
};
struct basl_table {
STAILQ_ENTRY(basl_table) chain;
struct vmctx *ctx;
uint8_t fwcfg_name[QEMU_FWCFG_MAX_NAME];
void *data;
uint32_t len;
uint32_t off;
uint32_t alignment;
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);
static struct qemu_loader *basl_loader;
static struct basl_table *rsdt;
static struct basl_table *xsdt;
static bool load_into_memory;
static __inline uint64_t
basl_le_dec(void *pp, size_t len)
{
assert(len <= 8);
switch (len) {
case 1:
return ((uint8_t *)pp)[0];
case 2:
return le16dec(pp);
case 4:
return le32dec(pp);
case 8:
return le64dec(pp);
}
return 0;
}
static __inline void
basl_le_enc(void *pp, uint64_t val, size_t len)
{
char buf[8];
assert(len <= 8);
le64enc(buf, val);
memcpy(pp, buf, len);
}
static int
basl_dump_table(const struct basl_table *const table, const bool mem)
{
const ACPI_TABLE_HEADER *const header = table->data;
const uint8_t *data;
if (!mem) {
data = table->data;
} else {
data = vm_map_gpa(table->ctx, BHYVE_ACPI_BASE + table->off,
table->len);
if (data == NULL) {
return (ENOMEM);
}
}
printf("%.4s @ %8x (%s)\n", header->Signature,
BHYVE_ACPI_BASE + table->off, mem ? "Memory" : "FwCfg");
hexdump(data, table->len, NULL, 0);
return (0);
}
static int __unused
basl_dump(const bool mem)
{
struct basl_table *table;
STAILQ_FOREACH(table, &basl_tables, chain) {
BASL_EXEC(basl_dump_table(table, mem));
}
return (0);
}
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)
{
assert(gas != NULL);
gas->SpaceId = space_id;
gas->BitWidth = bit_width;
gas->BitOffset = bit_offset;
gas->AccessWidth = access_width;
gas->Address = htole64(address);
}
static int
basl_finish_install_guest_tables(struct basl_table *const table, uint32_t *const off)
{
void *gva;
table->off = roundup2(*off, table->alignment);
*off = table->off + table->len;
if (*off <= table->off) {
warnx("%s: invalid table length 0x%8x @ offset 0x%8x", __func__,
table->len, table->off);
return (EFAULT);
}
/* Cause guest BIOS to copy the ACPI table into guest memory. */
BASL_EXEC(
qemu_fwcfg_add_file(table->fwcfg_name, table->len, table->data));
BASL_EXEC(qemu_loader_alloc(basl_loader, table->fwcfg_name,
table->alignment, QEMU_LOADER_ALLOC_HIGH));
if (!load_into_memory) {
return (0);
}
/*
* Install ACPI tables directly in guest memory for use by guests which
* do not boot via EFI. EFI ROMs provide a pointer to the firmware
* generated ACPI tables instead, but it doesn't hurt to install the
* tables always.
*/
gva = vm_map_gpa(table->ctx, BHYVE_ACPI_BASE + table->off, table->len);
if (gva == NULL) {
warnx("%s: could not map gpa [ 0x%16lx, 0x%16lx ]", __func__,
(uint64_t)BHYVE_ACPI_BASE + table->off,
(uint64_t)BHYVE_ACPI_BASE + table->off + table->len);
return (ENOMEM);
}
memcpy(gva, table->data, table->len);
return (0);
}
static int
basl_finish_patch_checksums(struct basl_table *const table)
{
struct basl_table_checksum *checksum;
STAILQ_FOREACH(checksum, &table->checksums, chain) {
uint8_t *gva, *checksum_gva;
uint64_t gpa;
uint32_t len;
uint8_t sum;
len = checksum->len;
if (len == BASL_TABLE_CHECKSUM_LEN_FULL_TABLE) {
len = table->len;
}
assert(checksum->off < table->len);
assert(checksum->start < table->len);
assert(checksum->start + len <= table->len);
/* Cause guest BIOS to patch the checksum. */
BASL_EXEC(qemu_loader_add_checksum(basl_loader,
table->fwcfg_name, checksum->off, checksum->start, len));
if (!load_into_memory) {
continue;
}
/*
* Install ACPI tables directly in guest memory for use by
* guests which do not boot via EFI. EFI ROMs provide a pointer
* to the firmware generated ACPI tables instead, but it doesn't
* hurt to install the tables always.
*/
gpa = BHYVE_ACPI_BASE + table->off + checksum->start;
if ((gpa < BHYVE_ACPI_BASE) ||
(gpa < BHYVE_ACPI_BASE + table->off)) {
warnx("%s: invalid gpa (off 0x%8x start 0x%8x)",
__func__, table->off, checksum->start);
return (EFAULT);
}
gva = vm_map_gpa(table->ctx, gpa, len);
if (gva == NULL) {
warnx("%s: could not map gpa [ 0x%16lx, 0x%16lx ]",
__func__, gpa, gpa + len);
return (ENOMEM);
}
checksum_gva = gva + checksum->off;
if (checksum_gva < gva) {
warnx("%s: invalid checksum offset 0x%8x", __func__,
checksum->off);
return (EFAULT);
}
sum = 0;
for (uint32_t i = 0; i < len; ++i) {
sum += *(gva + i);
}
*checksum_gva = -sum;
}
return (0);
}
static struct basl_table *
basl_get_table_by_signature(const uint8_t signature[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, signature,
sizeof(header->Signature)) == 0) {
return (table);
}
}
warnx("%s: %.4s not found", __func__, signature);
return (NULL);
}
static int
basl_finish_patch_pointers(struct basl_table *const table)
{
struct basl_table_pointer *pointer;
STAILQ_FOREACH(pointer, &table->pointers, chain) {
const struct basl_table *src_table;
uint8_t *gva;
uint64_t gpa, val;
assert(pointer->off < table->len);
assert(pointer->off + pointer->size <= table->len);
src_table = basl_get_table_by_signature(pointer->src_signature);
if (src_table == NULL) {
warnx("%s: could not find ACPI table %.4s", __func__,
pointer->src_signature);
return (EFAULT);
}
/* Cause guest BIOS to patch the pointer. */
BASL_EXEC(
qemu_loader_add_pointer(basl_loader, table->fwcfg_name,
src_table->fwcfg_name, pointer->off, pointer->size));
if (!load_into_memory) {
continue;
}
/*
* Install ACPI tables directly in guest memory for use by
* guests which do not boot via EFI. EFI ROMs provide a pointer
* to the firmware generated ACPI tables instead, but it doesn't
* hurt to install the tables always.
*/
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);
}
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);
}
val = basl_le_dec(gva + pointer->off, pointer->size);
val += BHYVE_ACPI_BASE + src_table->off;
basl_le_enc(gva + pointer->off, val, pointer->size);
}
return (0);
}
static int
basl_finish_set_length(struct basl_table *const table)
{
struct basl_table_length *length;
STAILQ_FOREACH(length, &table->lengths, chain) {
assert(length->off < table->len);
assert(length->off + length->size <= table->len);
basl_le_enc((uint8_t *)table->data + length->off, table->len,
length->size);
}
return (0);
}
int
basl_finish(void)
{
struct basl_table *table;
uint32_t off = 0;
if (STAILQ_EMPTY(&basl_tables)) {
warnx("%s: no ACPI tables found", __func__);
return (EINVAL);
}
/*
* If we install ACPI tables by FwCfg and by memory, Windows will use
* the tables from memory. This can cause issues when using advanced
* features like a TPM log because we aren't able to patch the memory
* tables accordingly.
*/
load_into_memory = get_config_bool_default("acpi_tables_in_memory",
true);
/*
* We have to install all tables before we can patch them. Therefore,
* use two loops. The first one installs all tables and the second one
* patches them.
*/
STAILQ_FOREACH(table, &basl_tables, chain) {
BASL_EXEC(basl_finish_set_length(table));
BASL_EXEC(basl_finish_install_guest_tables(table, &off));
}
STAILQ_FOREACH(table, &basl_tables, chain) {
BASL_EXEC(basl_finish_patch_pointers(table));
/*
* Calculate the checksum as last step!
*/
BASL_EXEC(basl_finish_patch_checksums(table));
}
BASL_EXEC(qemu_loader_finish(basl_loader));
return (0);
}
static int
basl_init_rsdt(struct vmctx *const ctx)
{
BASL_EXEC(
basl_table_create(&rsdt, ctx, ACPI_SIG_RSDT, BASL_TABLE_ALIGNMENT));
/* Header */
BASL_EXEC(basl_table_append_header(rsdt, ACPI_SIG_RSDT, 1, 1));
/* Pointers (added by basl_table_register_to_rsdt) */
return (0);
}
static int
basl_init_xsdt(struct vmctx *const ctx)
{
BASL_EXEC(
basl_table_create(&xsdt, ctx, ACPI_SIG_XSDT, BASL_TABLE_ALIGNMENT));
/* Header */
BASL_EXEC(basl_table_append_header(xsdt, ACPI_SIG_XSDT, 1, 1));
/* Pointers (added by basl_table_register_to_rsdt) */
return (0);
}
int
basl_init(struct vmctx *const ctx)
{
BASL_EXEC(basl_init_rsdt(ctx));
BASL_EXEC(basl_init_xsdt(ctx));
BASL_EXEC(
qemu_loader_create(&basl_loader, QEMU_FWCFG_FILE_TABLE_LOADER));
return (0);
}
int
basl_table_add_checksum(struct basl_table *const table, const uint32_t off,
const uint32_t start, const uint32_t len)
{
struct basl_table_checksum *checksum;
assert(table != NULL);
checksum = calloc(1, sizeof(struct basl_table_checksum));
if (checksum == NULL) {
warnx("%s: failed to allocate checksum", __func__);
return (ENOMEM);
}
checksum->off = off;
checksum->start = start;
checksum->len = len;
STAILQ_INSERT_TAIL(&table->checksums, checksum, chain);
return (0);
}
int
basl_table_add_length(struct basl_table *const table, const uint32_t off,
const uint8_t size)
{
struct basl_table_length *length;
assert(table != NULL);
assert(size == 4 || size == 8);
length = calloc(1, sizeof(struct basl_table_length));
if (length == NULL) {
warnx("%s: failed to allocate length", __func__);
return (ENOMEM);
}
length->off = off;
length->size = size;
STAILQ_INSERT_TAIL(&table->lengths, length, chain);
return (0);
}
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)
{
struct basl_table_pointer *pointer;
assert(table != NULL);
assert(size == 4 || size == 8);
pointer = calloc(1, sizeof(struct basl_table_pointer));
if (pointer == NULL) {
warnx("%s: failed to allocate pointer", __func__);
return (ENOMEM);
}
memcpy(pointer->src_signature, src_signature,
sizeof(pointer->src_signature));
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)
{
void *end;
assert(table != NULL);
assert(bytes != NULL);
if (table->len + len <= table->len) {
warnx("%s: table too large (table->len 0x%8x len 0x%8x)",
__func__, table->len, len);
return (EFAULT);
}
table->data = reallocf(table->data, table->len + len);
if (table->data == NULL) {
warnx("%s: failed to realloc table to length 0x%8x", __func__,
table->len + len);
table->len = 0;
return (ENOMEM);
}
end = (uint8_t *)table->data + table->len;
table->len += len;
memcpy(end, bytes, len);
return (0);
}
int
basl_table_append_checksum(struct basl_table *const table, const uint32_t start,
const uint32_t len)
{
assert(table != NULL);
BASL_EXEC(basl_table_add_checksum(table, table->len, start, len));
BASL_EXEC(basl_table_append_int(table, 0, 1));
return (0);
}
int
basl_table_append_content(struct basl_table *table, void *data, uint32_t len)
{
assert(data != NULL);
assert(len >= sizeof(ACPI_TABLE_HEADER));
return (basl_table_append_bytes(table,
(void *)((uintptr_t)(data) + sizeof(ACPI_TABLE_HEADER)),
len - sizeof(ACPI_TABLE_HEADER)));
}
int
basl_table_append_fwcfg(struct basl_table *const table,
const uint8_t *fwcfg_name, const uint32_t alignment, const uint8_t size)
{
assert(table != NULL);
assert(fwcfg_name != NULL);
assert(size <= sizeof(uint64_t));
BASL_EXEC(qemu_loader_alloc(basl_loader, fwcfg_name, alignment,
QEMU_LOADER_ALLOC_HIGH));
BASL_EXEC(qemu_loader_add_pointer(basl_loader, table->fwcfg_name,
fwcfg_name, table->len, size));
BASL_EXEC(basl_table_append_int(table, 0, size));
return (0);
}
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)
{
ACPI_GENERIC_ADDRESS gas_le = {
.SpaceId = space_id,
.BitWidth = bit_width,
.BitOffset = bit_offset,
.AccessWidth = access_width,
.Address = htole64(address),
};
return (basl_table_append_bytes(table, &gas_le, sizeof(gas_le)));
}
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)
{
ACPI_TABLE_HEADER header_le;
/* + 1 is required for the null terminator */
char oem_table_id[ACPI_OEM_TABLE_ID_SIZE + 1];
assert(table != NULL);
assert(table->len == 0);
memcpy(header_le.Signature, signature, ACPI_NAMESEG_SIZE);
header_le.Length = 0; /* patched by basl_finish */
header_le.Revision = revision;
header_le.Checksum = 0; /* patched by basl_finish */
memcpy(header_le.OemId, "BHYVE ", ACPI_OEM_ID_SIZE);
snprintf(oem_table_id, ACPI_OEM_TABLE_ID_SIZE, "BV%.4s ", signature);
memcpy(header_le.OemTableId, oem_table_id,
sizeof(header_le.OemTableId));
header_le.OemRevision = htole32(oem_revision);
memcpy(header_le.AslCompilerId, "BASL", ACPI_NAMESEG_SIZE);
header_le.AslCompilerRevision = htole32(0x20220504);
BASL_EXEC(
basl_table_append_bytes(table, &header_le, sizeof(header_le)));
BASL_EXEC(basl_table_add_length(table,
offsetof(ACPI_TABLE_HEADER, Length), sizeof(header_le.Length)));
BASL_EXEC(basl_table_add_checksum(table,
offsetof(ACPI_TABLE_HEADER, Checksum), 0,
BASL_TABLE_CHECKSUM_LEN_FULL_TABLE));
return (0);
}
int
basl_table_append_int(struct basl_table *const table, const uint64_t val,
const uint8_t size)
{
char buf[8];
assert(size <= sizeof(val));
basl_le_enc(buf, val, size);
return (basl_table_append_bytes(table, buf, size));
}
int
basl_table_append_length(struct basl_table *const table, const uint8_t size)
{
assert(table != NULL);
assert(size <= sizeof(table->len));
BASL_EXEC(basl_table_add_length(table, table->len, size));
BASL_EXEC(basl_table_append_int(table, 0, size));
return (0);
}
int
basl_table_append_pointer(struct basl_table *const table,
const uint8_t src_signature[ACPI_NAMESEG_SIZE], const uint8_t size)
{
assert(table != NULL);
assert(size == 4 || size == 8);
BASL_EXEC(basl_table_add_pointer(table, src_signature, 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)
{
struct basl_table *new_table;
assert(table != NULL);
new_table = calloc(1, sizeof(struct basl_table));
if (new_table == NULL) {
warnx("%s: failed to allocate table", __func__);
return (ENOMEM);
}
new_table->ctx = ctx;
snprintf(new_table->fwcfg_name, sizeof(new_table->fwcfg_name),
"etc/acpi/%s", name);
new_table->alignment = alignment;
STAILQ_INIT(&new_table->checksums);
STAILQ_INIT(&new_table->lengths);
STAILQ_INIT(&new_table->pointers);
STAILQ_INSERT_TAIL(&basl_tables, new_table, chain);
*table = new_table;
return (0);
}
int
basl_table_register_to_rsdt(struct basl_table *table)
{
const ACPI_TABLE_HEADER *header;
assert(table != NULL);
header = (const ACPI_TABLE_HEADER *)table->data;
BASL_EXEC(basl_table_append_pointer(rsdt, header->Signature,
ACPI_RSDT_ENTRY_SIZE));
BASL_EXEC(basl_table_append_pointer(xsdt, header->Signature,
ACPI_XSDT_ENTRY_SIZE));
return (0);
}