diff --git a/usr.sbin/bhyve/Makefile b/usr.sbin/bhyve/Makefile --- a/usr.sbin/bhyve/Makefile +++ b/usr.sbin/bhyve/Makefile @@ -65,6 +65,7 @@ ps2kbd.c \ ps2mouse.c \ qemu_fwcfg.c \ + qemu_loader.c \ rfb.c \ rtc.c \ smbiostbl.c \ diff --git a/usr.sbin/bhyve/qemu_loader.h b/usr.sbin/bhyve/qemu_loader.h new file mode 100644 --- /dev/null +++ b/usr.sbin/bhyve/qemu_loader.h @@ -0,0 +1,77 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2022 Beckhoff Automation GmbH & Co. KG + * Author: Corvin Köhne + */ + +#pragma once + +#include "qemu_fwcfg.h" + +struct qemu_loader; + +/* + * Some guest bios like seabios assume the RSDP to be located in the FSEG. Bhyve + * only supports OVMF which has no such requirement. + */ +enum qemu_loader_zone { + QEMU_LOADER_ALLOC_HIGH = 1, + QEMU_LOADER_ALLOC_FSEG, /* 0x0F000000 - 0x100000 */ +}; + +/** + * Loads a fwcfg item into guest memory. This command has to be issued before + * any subsequent command can be used. + * + * @param loader Qemu loader instance the command should be added to. + * @param name Name of the fwcfg item which should be allocated. + * @param alignment Alignment required by the data. + * @param zone Memory zone in which it should be loaded. + */ +int qemu_loader_alloc(struct qemu_loader *loader, const uint8_t *name, + uint32_t alignment, enum qemu_loader_zone zone); +/** + * Calculates a checksum for @p name and writes it to @p name + @p off . The + * checksum calculation ranges from @p start to @p start + @p len. The checksum + * field is always one byte large and all bytes in the specified range, + * including the checksum, have to sum up to 0. + * + * @param loader Qemu loader instance the command should be added to. + * @param name Name of the fwcfg item which should be patched. + * @param off Offset into @p name . + * @param start Start offset of checksum calculation. + * @param len Length of the checksum calculation. + */ +int qemu_loader_add_checksum(struct qemu_loader *loader, const uint8_t *name, + uint32_t off, uint32_t start, uint32_t len); +/** + * Adds the address of @p src_name to the value at @p dest_name + @p off . The + * size of the pointer is determined by @p dest_size and should be 1, 2, 4 or 8. + * + * @param loader Qemu loader instance the command should be added to. + * @param dest_name Name of the fwcfg item which should be patched. + * @param src_name Name of the fwcfg item which address should be written to + * @p dest_name + @p off. + * @param off Offset into @p dest_name . + * @param size Size of the pointer (1, 2, 4 or 8). + */ +int qemu_loader_add_pointer(struct qemu_loader *loader, + const uint8_t *dest_name, const uint8_t *src_name, uint32_t off, + uint8_t size); + +/** + * Creates a qemu loader instance. + * + * @param new_loader Returns the newly created qemu loader instance. + * @param fwcfg_name Name of the FwCfg item which represents the qemu loader + */ +int qemu_loader_create(struct qemu_loader **new_loader, + const uint8_t *fwcfg_name); +/** + * Signals that all commands are written to the qemu loader. This function + * creates a proper FwCfg item and registers it. + * + * @param loader Qemu loader instance which should be finished. + */ +int qemu_loader_finish(struct qemu_loader *loader); diff --git a/usr.sbin/bhyve/qemu_loader.c b/usr.sbin/bhyve/qemu_loader.c new file mode 100644 --- /dev/null +++ b/usr.sbin/bhyve/qemu_loader.c @@ -0,0 +1,274 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2022 Beckhoff Automation GmbH & Co. KG + * Author: Corvin Köhne + */ + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "qemu_fwcfg.h" +#include "qemu_loader.h" + +struct qemu_loader_entry { + uint32_t cmd_le; + union { + struct { + uint8_t name[QEMU_FWCFG_MAX_NAME]; + uint32_t alignment_le; + uint8_t zone; + } alloc; + struct { + uint8_t dest_name[QEMU_FWCFG_MAX_NAME]; + uint8_t src_name[QEMU_FWCFG_MAX_NAME]; + uint32_t off_le; + uint8_t size; + } add_pointer; + struct { + uint8_t name[QEMU_FWCFG_MAX_NAME]; + uint32_t off_le; + uint32_t start_le; + uint32_t len_le; + } add_checksum; + struct { + uint8_t dest_name[QEMU_FWCFG_MAX_NAME]; + uint8_t src_name[QEMU_FWCFG_MAX_NAME]; + uint32_t dest_off_le; + uint32_t src_off_le; + uint8_t size; + } write_pointer; + + /* padding */ + uint8_t pad[124]; + }; +} __packed; + +enum qemu_loader_command { + QEMU_LOADER_CMD_ALLOC = 0x1, + QEMU_LOADER_CMD_ADD_POINTER = 0x2, + QEMU_LOADER_CMD_ADD_CHECKSUM = 0x3, + QEMU_LOADER_CMD_WRITE_POINTER = 0x4, +}; + +struct qemu_loader_element { + STAILQ_ENTRY(qemu_loader_element) chain; + struct qemu_loader_entry entry; +}; + +struct qemu_loader { + uint8_t fwcfg_name[QEMU_FWCFG_MAX_NAME]; + STAILQ_HEAD(qemu_loader_list, qemu_loader_element) list; +}; + +int +qemu_loader_alloc(struct qemu_loader *const loader, const uint8_t *name, + const uint32_t alignment, const enum qemu_loader_zone zone) +{ + struct qemu_loader_element *element; + + if (strlen(name) >= QEMU_FWCFG_MAX_NAME) + return (EINVAL); + + element = calloc(1, sizeof(struct qemu_loader_element)); + if (element == NULL) { + warnx("%s: failed to allocate command", __func__); + return (ENOMEM); + } + + element->entry.cmd_le = htole32(QEMU_LOADER_CMD_ALLOC); + strncpy(element->entry.alloc.name, name, QEMU_FWCFG_MAX_NAME); + element->entry.alloc.alignment_le = htole32(alignment); + element->entry.alloc.zone = zone; + + /* + * The guest always works on copies of the fwcfg item, which where + * loaded into guest memory. Loading a fwcfg item is caused by ALLOC. + * For that reason, ALLOC should be scheduled in front of any other + * commands. + */ + STAILQ_INSERT_HEAD(&loader->list, element, chain); + + return (0); +} + +int +qemu_loader_add_checksum(struct qemu_loader *const loader, const uint8_t *name, + const uint32_t off, const uint32_t start, const uint32_t len) +{ + struct qemu_loader_element *element; + + if (strlen(name) >= QEMU_FWCFG_MAX_NAME) + return (EINVAL); + + element = calloc(1, sizeof(struct qemu_loader_element)); + if (element == NULL) { + warnx("%s: failed to allocate command", __func__); + return (ENOMEM); + } + + element->entry.cmd_le = htole32(QEMU_LOADER_CMD_ADD_CHECKSUM); + strncpy(element->entry.add_checksum.name, name, QEMU_FWCFG_MAX_NAME); + element->entry.add_checksum.off_le = htole32(off); + element->entry.add_checksum.start_le = htole32(start); + element->entry.add_checksum.len_le = htole32(len); + + STAILQ_INSERT_TAIL(&loader->list, element, chain); + + return (0); +} + +int +qemu_loader_add_pointer(struct qemu_loader *const loader, + const uint8_t *dest_name, const uint8_t *src_name, const uint32_t off, + const uint8_t size) +{ + struct qemu_loader_element *element; + + if (strlen(dest_name) >= QEMU_FWCFG_MAX_NAME || + strlen(src_name) >= QEMU_FWCFG_MAX_NAME) + return (EINVAL); + + element = calloc(1, sizeof(struct qemu_loader_element)); + if (element == NULL) { + warnx("%s: failed to allocate command", __func__); + return (ENOMEM); + } + + element->entry.cmd_le = htole32(QEMU_LOADER_CMD_ADD_POINTER); + strncpy(element->entry.add_pointer.dest_name, dest_name, + QEMU_FWCFG_MAX_NAME); + strncpy(element->entry.add_pointer.src_name, src_name, + QEMU_FWCFG_MAX_NAME); + element->entry.add_pointer.off_le = htole32(off); + element->entry.add_pointer.size = size; + + STAILQ_INSERT_TAIL(&loader->list, element, chain); + + return (0); +} + +int +qemu_loader_create(struct qemu_loader **const new_loader, + const uint8_t *fwcfg_name) +{ + struct qemu_loader *loader; + + if (new_loader == NULL || strlen(fwcfg_name) >= QEMU_FWCFG_MAX_NAME) { + return (EINVAL); + } + + loader = calloc(1, sizeof(struct qemu_loader)); + if (loader == NULL) { + warnx("%s: failed to allocate loader", __func__); + return (ENOMEM); + } + + strncpy(loader->fwcfg_name, fwcfg_name, QEMU_FWCFG_MAX_NAME); + STAILQ_INIT(&loader->list); + + *new_loader = loader; + + return (0); +} + +static const uint8_t * +qemu_loader_get_zone_name(const enum qemu_loader_zone zone) +{ + switch (zone) { + case QEMU_LOADER_ALLOC_HIGH: + return ("HIGH"); + case QEMU_LOADER_ALLOC_FSEG: + return ("FSEG"); + default: + return ("Unknown"); + } +} + +static void __unused +qemu_loader_dump_entry(const struct qemu_loader_entry *const entry) +{ + switch (le32toh(entry->cmd_le)) { + case QEMU_LOADER_CMD_ALLOC: + printf("CMD_ALLOC\n\r"); + printf(" name : %s\n\r", entry->alloc.name); + printf(" alignment: %8x\n\r", + le32toh(entry->alloc.alignment_le)); + printf(" zone : %s\n\r", + qemu_loader_get_zone_name(entry->alloc.zone)); + break; + case QEMU_LOADER_CMD_ADD_POINTER: + printf("CMD_ADD_POINTER\n\r"); + printf(" dest_name: %s\n\r", entry->add_pointer.dest_name); + printf(" src_name : %s\n\r", entry->add_pointer.src_name); + printf(" off : %8x\n\r", + le32toh(entry->add_pointer.off_le)); + printf(" size : %8x\n\r", entry->add_pointer.size); + break; + case QEMU_LOADER_CMD_ADD_CHECKSUM: + printf("CMD_ADD_CHECKSUM\n\r"); + printf(" name : %s\n\r", entry->add_checksum.name); + printf(" off : %8x\n\r", + le32toh(entry->add_checksum.off_le)); + printf(" start : %8x\n\r", + le32toh(entry->add_checksum.start_le)); + printf(" length : %8x\n\r", + le32toh(entry->add_checksum.len_le)); + break; + case QEMU_LOADER_CMD_WRITE_POINTER: + printf("CMD_WRITE_POINTER\n\r"); + printf(" dest_name: %s\n\r", entry->write_pointer.dest_name); + printf(" src_name : %s\n\r", entry->write_pointer.src_name); + printf(" dest_off : %8x\n\r", + le32toh(entry->write_pointer.dest_off_le)); + printf(" src_off : %8x\n\r", + le32toh(entry->write_pointer.src_off_le)); + printf(" size : %8x\n\r", entry->write_pointer.size); + break; + default: + printf("UNKNOWN\n\r"); + break; + } +} + +int +qemu_loader_finish(struct qemu_loader *const loader) +{ + struct qemu_loader_element *element; + struct qemu_loader_entry *data; + size_t len = 0; + + STAILQ_FOREACH(element, &loader->list, chain) { + len += sizeof(struct qemu_loader_entry); + } + if (len == 0) { + warnx("%s: bios loader empty", __func__); + return (EFAULT); + } + + data = calloc(1, len); + if (data == NULL) { + warnx("%s: failed to allocate fwcfg data", __func__); + return (ENOMEM); + } + + int i = 0; + STAILQ_FOREACH(element, &loader->list, chain) { + memcpy(&data[i], &element->entry, + sizeof(struct qemu_loader_entry)); + ++i; + } + + return (qemu_fwcfg_add_file(loader->fwcfg_name, len, data)); +}