diff options
Diffstat (limited to 'usr.sbin/bhyve/qemu_loader.c')
| -rw-r--r-- | usr.sbin/bhyve/qemu_loader.c | 274 | 
1 files changed, 274 insertions, 0 deletions
| diff --git a/usr.sbin/bhyve/qemu_loader.c b/usr.sbin/bhyve/qemu_loader.c new file mode 100644 index 000000000000..b0e1197a4b6b --- /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 <c.koehne@beckhoff.com> + */ + +#include <sys/types.h> +#include <sys/param.h> +#include <sys/endian.h> +#include <sys/queue.h> + +#include <machine/vmm.h> + +#include <err.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <vmmapi.h> + +#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)); +} | 
