mirror of
https://git.FreeBSD.org/src.git
synced 2024-12-10 09:42:26 +00:00
f565b4d630
The hypervisor is aware of all system properties. For the guest bios it's hard and complex to detect all system properties. For that reason, it would be better if the hypervisor creates acpi tables instead of the guest. Therefore, the hypervisor has to send the acpi tables to the guest. At the moment, bhyve just copies the acpi tables into the guest memory. This approach has some restrictions. You have to keep sure that the guest doesn't overwrite them accidentally. Additionally, the size of acpi tables is limited. Providing a plain copy of all acpi tables by fwcfg isn't possible. Acpi tables have to point to each other. So, if the guest copies the acpi tables into memory by it's own, it has to patch the tables. Due to different layouts for different acpi tables, there's no generic way to do that. For that reason, qemu created a table loader interface. It contains commands for the guest for loading specific blobs into guest memory and patching those blobs. This commit adds a qemu_loader class which handles the creation of qemu loader commands. At the moment, the WRITE_POINTER command isn't implement. It won't be required by bhyve's acpi table generation yet. Reviewed by: markj MFC after: 1 week Sponsored by: Beckhoff Automation GmbH & Co. KG Differential Revision: https://reviews.freebsd.org/D38438
275 lines
6.9 KiB
C
275 lines
6.9 KiB
C
/*-
|
|
* 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));
|
|
}
|