diff --git a/usr.sbin/bhyve/Makefile b/usr.sbin/bhyve/Makefile --- a/usr.sbin/bhyve/Makefile +++ b/usr.sbin/bhyve/Makefile @@ -15,6 +15,7 @@ BHYVE_SYSDIR?=${SRCTOP} SRCS= \ + acpi_device.c \ atkbdc.c \ acpi.c \ audio.c \ @@ -61,6 +62,7 @@ post.c \ ps2kbd.c \ ps2mouse.c \ + qemu_fwcfg.c \ rfb.c \ rtc.c \ smbiostbl.c \ diff --git a/usr.sbin/bhyve/acpi.h b/usr.sbin/bhyve/acpi.h --- a/usr.sbin/bhyve/acpi.h +++ b/usr.sbin/bhyve/acpi.h @@ -31,6 +31,8 @@ #ifndef _ACPI_H_ #define _ACPI_H_ +#include "acpi_device.h" + #define SCI_INT 9 #define SMI_CMD 0xb2 @@ -55,6 +57,7 @@ int acpi_build(struct vmctx *ctx, int ncpu); void acpi_raise_gpe(struct vmctx *ctx, unsigned bit); +int acpi_tables_add_device(const struct acpi_device *const dev); void dsdt_line(const char *fmt, ...); void dsdt_fixed_ioport(uint16_t iobase, uint16_t length); void dsdt_fixed_irq(uint8_t irq); diff --git a/usr.sbin/bhyve/acpi.c b/usr.sbin/bhyve/acpi.c --- a/usr.sbin/bhyve/acpi.c +++ b/usr.sbin/bhyve/acpi.c @@ -139,6 +139,30 @@ #define EFFLUSH(x) \ if (fflush(x) != 0) goto err_exit; +/* + * A list for additional ACPI devices like a TPM. + */ +struct acpi_device_list_entry { + SLIST_ENTRY(acpi_device_list_entry) chain; + const struct acpi_device *dev; +}; +SLIST_HEAD(acpi_device_list, + acpi_device_list_entry) acpi_devices = SLIST_HEAD_INITIALIZER(acpi_devices); + +int +acpi_tables_add_device(const struct acpi_device *const dev) +{ + struct acpi_device_list_entry *const entry = calloc(1, sizeof(*entry)); + if (entry == NULL) { + return (ENOMEM); + } + + entry->dev = dev; + SLIST_INSERT_HEAD(&acpi_devices, entry, chain); + + return (0); +} + static int basl_fwrite_rsdp(FILE *fp) { @@ -760,6 +784,11 @@ vmgenc_write_dsdt(); + const struct acpi_device_list_entry *entry; + SLIST_FOREACH(entry, &acpi_devices, chain) { + acpi_device_write_dsdt(entry->dev); + } + dsdt_line("}"); if (dsdt_error != 0) diff --git a/usr.sbin/bhyve/acpi_device.h b/usr.sbin/bhyve/acpi_device.h new file mode 100644 --- /dev/null +++ b/usr.sbin/bhyve/acpi_device.h @@ -0,0 +1,42 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2021 Beckhoff Automation GmbH & Co. KG + * Author: Corvin Köhne + */ + +#pragma once + +#include + +struct vmctx; + +struct acpi_device; + +/** + * Creates an ACPI device. + * + * @param[out] new_dev Returns the newly create ACPI device. + * @param[in] vm_ctx VM context the ACPI device is created in. + * @param[in] name Name of the ACPI device. Should always be a NULL + * terminated string. + * @param[in] hid Hardware ID of the ACPI device. Should always be a NULL + * terminated string. + */ +int acpi_device_create(struct acpi_device **const new_dev, + struct vmctx *const vm_ctx, const char *const name, const char *const hid); +void acpi_device_destroy(struct acpi_device *const dev); + +/** + * @note: acpi_device_add_res_acpi_buffer doesn't ensure that no resources are + * added on an error condition. On error the caller should assume that + * the ACPI_BUFFER is partially added to the ACPI device. + */ +int acpi_device_add_res_acpi_buffer(struct acpi_device *const dev, + const ACPI_BUFFER resources); +int acpi_device_add_res_fixed_ioport(struct acpi_device *const dev, + const UINT16 port, UINT8 length); +int acpi_device_add_res_fixed_memory32(struct acpi_device *const dev, + const UINT8 write_protected, const UINT32 address, const UINT32 length); + +void acpi_device_write_dsdt(const struct acpi_device *const dev); diff --git a/usr.sbin/bhyve/acpi_device.c b/usr.sbin/bhyve/acpi_device.c new file mode 100644 --- /dev/null +++ b/usr.sbin/bhyve/acpi_device.c @@ -0,0 +1,240 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2021 Beckhoff Automation GmbH & Co. KG + * Author: Corvin Köhne + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include + +#include + +#include +#include +#include + +#include "acpi.h" +#include "acpi_device.h" + +/** + * List entry to enumerate all resources used by an ACPI device. + * + * @param chain Used to chain multiple elements together. + * @param type Type of the ACPI resource. + * @param data Data of the ACPI resource. + */ +struct acpi_resource_list_entry { + SLIST_ENTRY(acpi_resource_list_entry) chain; + UINT32 type; + ACPI_RESOURCE_DATA data; +}; + +/** + * Holds information about an ACPI device. + * + * @param vm_ctx VM context the ACPI device was created in. + * @param name Name of the ACPI device. + * @param hid Hardware ID of the ACPI device. + * @param crs Current resources used by the ACPI device. + */ +struct acpi_device { + struct vmctx *vm_ctx; + const char *name; + const char *hid; + SLIST_HEAD(acpi_resource_list, acpi_resource_list_entry) crs; +}; + +int +acpi_device_create(struct acpi_device **const new_dev, + struct vmctx *const vm_ctx, const char *const name, const char *const hid) +{ + if (new_dev == NULL || vm_ctx == NULL || name == NULL || hid == NULL) { + return (EINVAL); + } + + struct acpi_device *const dev = calloc(1, sizeof(*dev)); + if (dev == NULL) { + return (ENOMEM); + } + + dev->vm_ctx = vm_ctx; + dev->name = name; + dev->hid = hid; + SLIST_INIT(&dev->crs); + + /* current resources always contain an end tag */ + struct acpi_resource_list_entry *const crs_end_tag = calloc(1, + sizeof(*crs_end_tag)); + if (crs_end_tag == NULL) { + acpi_device_destroy(dev); + return (ENOMEM); + } + crs_end_tag->type = ACPI_RESOURCE_TYPE_END_TAG; + SLIST_INSERT_HEAD(&dev->crs, crs_end_tag, chain); + + const int error = acpi_tables_add_device(dev); + if (error) { + acpi_device_destroy(dev); + return (error); + } + + *new_dev = dev; + + return (0); +} + +void +acpi_device_destroy(struct acpi_device *const dev) +{ + if (dev == NULL) { + return; + } + + struct acpi_resource_list_entry *res; + while (!SLIST_EMPTY(&dev->crs)) { + res = SLIST_FIRST(&dev->crs); + SLIST_REMOVE_HEAD(&dev->crs, chain); + free(res); + } +} + +int +acpi_device_add_res_acpi_buffer(struct acpi_device *const dev, + const ACPI_BUFFER resources) +{ + if (dev == NULL) { + return (EINVAL); + } + + int error = 0; + size_t offset = 0; + while (offset < resources.Length) { + const ACPI_RESOURCE *const res = + (const ACPI_RESOURCE *)((UINT8 *)resources.Pointer + + offset); + switch (res->Type) { + case ACPI_RESOURCE_TYPE_FIXED_IO: + error = acpi_device_add_res_fixed_ioport(dev, + res->Data.FixedIo.Address, + res->Data.FixedIo.AddressLength); + break; + case ACPI_RESOURCE_TYPE_FIXED_MEMORY32: + error = acpi_device_add_res_fixed_memory32(dev, + res->Data.FixedMemory32.WriteProtect, + res->Data.FixedMemory32.Address, + res->Data.FixedMemory32.AddressLength); + break; + case ACPI_RESOURCE_TYPE_END_TAG: + break; + default: + warnx("%s: unknown resource type %d", __func__, + res->Type); + return (ENODEV); + } + if (error) { + break; + } + offset += res->Length; + } + + return (error); +} + +int +acpi_device_add_res_fixed_ioport(struct acpi_device *const dev, + const UINT16 port, const UINT8 length) +{ + if (dev == NULL) { + return (EINVAL); + } + + struct acpi_resource_list_entry *const res = calloc(1, sizeof(*res)); + if (res == NULL) { + return (ENOMEM); + } + + res->type = ACPI_RESOURCE_TYPE_FIXED_IO; + res->data.FixedIo.Address = port; + res->data.FixedIo.AddressLength = length; + + SLIST_INSERT_HEAD(&dev->crs, res, chain); + + return (0); +} + +int +acpi_device_add_res_fixed_memory32(struct acpi_device *const dev, + const UINT8 write_protected, const UINT32 address, const UINT32 length) +{ + if (dev == NULL) { + return (EINVAL); + } + + struct acpi_resource_list_entry *const res = calloc(1, sizeof(*res)); + if (res == NULL) { + return (ENOMEM); + } + + res->type = ACPI_RESOURCE_TYPE_FIXED_MEMORY32; + res->data.FixedMemory32.WriteProtect = write_protected; + res->data.FixedMemory32.Address = address; + res->data.FixedMemory32.AddressLength = length; + + SLIST_INSERT_HEAD(&dev->crs, res, chain); + + return (0); +} + +static void +acpi_device_write_dsdt_crs(const struct acpi_device *const dev) +{ + const struct acpi_resource_list_entry *res; + SLIST_FOREACH (res, &dev->crs, chain) { + switch (res->type) { + case ACPI_RESOURCE_TYPE_FIXED_IO: + dsdt_fixed_ioport(res->data.FixedIo.Address, + res->data.FixedIo.AddressLength); + break; + case ACPI_RESOURCE_TYPE_FIXED_MEMORY32: { + dsdt_fixed_mem32(res->data.FixedMemory32.Address, + res->data.FixedMemory32.AddressLength); + break; + } + case ACPI_RESOURCE_TYPE_END_TAG: + break; + default: + warnx("%s: unknown resource type %d", __func__, + res->type); + return; + } + } +} + +void +acpi_device_write_dsdt(const struct acpi_device *const dev) +{ + if (dev == NULL) { + return; + } + + dsdt_line(""); + dsdt_line(" Scope (\\_SB)"); + dsdt_line(" {"); + dsdt_line(" Device (%s)", dev->name); + dsdt_line(" {"); + dsdt_line(" Name (_HID, \"%s\")", dev->hid); + dsdt_line(" Name (_STA, 0x0F)"); + dsdt_line(" Name (_CRS, ResourceTemplate ()"); + dsdt_line(" {"); + dsdt_indent(4); + acpi_device_write_dsdt_crs(dev); + dsdt_unindent(4); + dsdt_line(" })"); + dsdt_line(" }"); + dsdt_line(" }"); +} diff --git a/usr.sbin/bhyve/bhyverun.c b/usr.sbin/bhyve/bhyverun.c --- a/usr.sbin/bhyve/bhyverun.c +++ b/usr.sbin/bhyve/bhyverun.c @@ -100,6 +100,7 @@ #include "pci_emul.h" #include "pci_irq.h" #include "pci_lpc.h" +#include "qemu_fwcfg.h" #include "smbiostbl.h" #ifdef BHYVE_SNAPSHOT #include "snapshot.h" @@ -1452,6 +1453,52 @@ rtc_init(ctx); sci_init(ctx); + const char *fwcfg = lpc_fwcfg(); + if (lpc_bootrom()) { + if (fwcfg == NULL || strcmp(fwcfg, "bhyve") == 0) { + fwctl_init(); + } else if (strcmp(fwcfg, "qemu") == 0) { + if (qemu_fwcfg_init(ctx) != 0) { + fprintf(stderr, + "qemu fwcfg initialization error"); + exit(4); + } + /* + * QEMU uses fwcfg item 0x0f (FW_CFG_MAX_CPUS) to report + * the number of cpus to the guest but states that it + * has a special meaning for x86. Don't know yet if that + * can cause unintented side-effects. Use an own fwcfg + * item to be safe. + * + * QEMU comment: + * FW_CFG_MAX_CPUS is a bit confusing/problematic + * on x86: + * + * For machine types prior to 1.8, SeaBIOS needs + * FW_CFG_MAX_CPUS for building MPTable, ACPI MADT, + * ACPI CPU hotplug and ACPI SRAT table, that + * tables are based on xAPIC ID and QEMU<->SeaBIOS + * interface for CPU hotplug also uses APIC ID and + * not "CPU index". This means that FW_CFG_MAX_CPUS + * is not the "maximum number of CPUs", but the + * "limit to the APIC ID values SeaBIOS may see". + * + * So for compatibility reasons with old BIOSes we + * are stuck with "etc/max-cpus" actually being + * apic_id_limit + */ + if (qemu_fwcfg_add_file("opt/bhyve/hw.ncpu", + sizeof(guest_ncpus), &guest_ncpus) != 0) { + fprintf(stderr, + "Could not add qemu fwcfg opt/bhyve/hw.ncpu"); + exit(4); + } + } else { + fprintf(stderr, "Invalid fwcfg %s", fwcfg); + exit(4); + } + } + /* * Exit if a device emulation finds an error in its initilization */ @@ -1535,9 +1582,6 @@ assert(error == 0); } - if (lpc_bootrom()) - fwctl_init(); - /* * Change the proc title to include the VM name. */ diff --git a/usr.sbin/bhyve/pci_lpc.h b/usr.sbin/bhyve/pci_lpc.h --- a/usr.sbin/bhyve/pci_lpc.h +++ b/usr.sbin/bhyve/pci_lpc.h @@ -72,5 +72,6 @@ char *lpc_pirq_name(int pin); void lpc_pirq_routed(void); const char *lpc_bootrom(void); +const char *lpc_fwcfg(void); #endif diff --git a/usr.sbin/bhyve/pci_lpc.c b/usr.sbin/bhyve/pci_lpc.c --- a/usr.sbin/bhyve/pci_lpc.c +++ b/usr.sbin/bhyve/pci_lpc.c @@ -101,7 +101,13 @@ lpcdev = strsep(&str, ","); if (lpcdev != NULL) { if (strcasecmp(lpcdev, "bootrom") == 0) { - set_config_value("lpc.bootrom", str); + nvlist_t *const nvl = create_config_node("lpc.bootrom"); + /* use qemu as default fwcfg */ + set_config_value_node(nvl, "fwcfg", "qemu"); + + const char *const code = strsep(&str, ","); + set_config_value_node(nvl, "code", code); + pci_parse_legacy_config(nvl, str); error = 0; goto done; } @@ -145,7 +151,13 @@ lpc_bootrom(void) { - return (get_config_value("lpc.bootrom")); + return (get_config_value("lpc.bootrom.code")); +} + +const char * +lpc_fwcfg(void) +{ + return (get_config_value("lpc.bootrom.fwcfg")); } static void @@ -208,7 +220,7 @@ char *node_name; int unit, error; - romfile = get_config_value("lpc.bootrom"); + romfile = get_config_value("lpc.bootrom.code"); if (romfile != NULL) { error = bootrom_loadrom(ctx, romfile); if (error) diff --git a/usr.sbin/bhyve/qemu_fwcfg.h b/usr.sbin/bhyve/qemu_fwcfg.h new file mode 100644 --- /dev/null +++ b/usr.sbin/bhyve/qemu_fwcfg.h @@ -0,0 +1,23 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2021 Beckhoff Automation GmbH & Co. KG + * Author: Corvin Köhne + */ + +#pragma once + +#include + +#define QEMU_FWCFG_MAX_ARCHS 0x2 +#define QEMU_FWCFG_MAX_ENTRIES 0x3FFF +#define QEMU_FWCFG_MAX_NAME 56 + +struct qemu_fwcfg_item { + uint32_t size; + uint8_t *data; +}; + +int qemu_fwcfg_add_file(const uint8_t name[QEMU_FWCFG_MAX_NAME], + const uint32_t size, void *const data); +int qemu_fwcfg_init(struct vmctx *const ctx); diff --git a/usr.sbin/bhyve/qemu_fwcfg.c b/usr.sbin/bhyve/qemu_fwcfg.c new file mode 100644 --- /dev/null +++ b/usr.sbin/bhyve/qemu_fwcfg.c @@ -0,0 +1,411 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2021 Beckhoff Automation GmbH & Co. KG + * Author: Corvin Köhne + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include "acpi_device.h" +#include "inout.h" +#include "qemu_fwcfg.h" + +#define QEMU_FWCFG_ACPI_DEVICE_NAME "FWCF" +#define QEMU_FWCFG_ACPI_HARDWARE_ID "QEMU0002" + +#define QEMU_FWCFG_SELECTOR_PORT_NUMBER 0x510 +#define QEMU_FWCFG_SELECTOR_PORT_SIZE 1 +#define QEMU_FWCFG_SELECTOR_PORT_FLAGS IOPORT_F_INOUT +#define QEMU_FWCFG_DATA_PORT_NUMBER 0x511 +#define QEMU_FWCFG_DATA_PORT_SIZE 1 +#define QEMU_FWCFG_DATA_PORT_FLAGS \ + IOPORT_F_INOUT /* QEMU v2.4+ ignores writes */ + +#define QEMU_FWCFG_ARCHITECTURE_MASK 0x0001 +#define QEMU_FWCFG_INDEX_MASK 0x3FFF + +#define QEMU_FWCFG_SELECT_READ 0 +#define QEMU_FWCFG_SELECT_WRITE 1 + +#define QEMU_FWCFG_ARCHITECTURE_GENERIC 0 +#define QEMU_FWCFG_ARCHITECTURE_SPECIFIC 1 + +#define QEMU_FWCFG_INDEX_SIGNATURE 0x00 +#define QEMU_FWCFG_INDEX_ID 0x01 +#define QEMU_FWCFG_INDEX_FILE_DIR 0x19 + +#define QEMU_FWCFG_FIRST_FILE_INDEX 0x20 + +#define QEMU_FWCFG_MIN_FILES 10 + +#pragma pack(1) + +union qemu_fwcfg_selector { + struct { + uint16_t index : 14; + uint16_t writeable : 1; + /* + * 0 = generic | for all architectures + * 1 = specific | only for current architecture + */ + uint16_t architecture : 1; + }; + uint16_t bits; +}; + +struct qemu_fwcfg_signature { + uint8_t signature[4]; +}; + +struct qemu_fwcfg_id { + uint32_t interface : 1; /* always set */ + uint32_t DMA : 1; + uint32_t reserved : 30; +}; + +struct qemu_fwcfg_file { + uint32_t be_size; + uint16_t be_selector; + uint16_t reserved; + uint8_t name[QEMU_FWCFG_MAX_NAME]; +}; + +struct qemu_fwcfg_directory { + uint32_t be_count; + struct qemu_fwcfg_file files[0]; +}; + +struct qemu_fwcfg_softc { + struct acpi_device *acpi_dev; + + uint32_t data_offset; + union qemu_fwcfg_selector selector; + struct qemu_fwcfg_item items[QEMU_FWCFG_MAX_ARCHS] + [QEMU_FWCFG_MAX_ENTRIES]; + struct qemu_fwcfg_directory *directory; +}; + +#pragma pack() + +static struct qemu_fwcfg_softc sc; + +static int +qemu_fwcfg_selector_port_handler(struct vmctx *const ctx, const int vcpu, + const int in, const int port, const int bytes, uint32_t *const eax, + void *const arg) +{ + if (in) { + *eax = *(uint16_t *)&sc.selector; + return (0); + } + + sc.data_offset = 0; + sc.selector.bits = *eax; + + return (0); +} + +static int +qemu_fwcfg_data_port_handler(struct vmctx *const ctx, const int vcpu, + const int in, const int port, const int bytes, uint32_t *const eax, + void *const arg) +{ + if (!in) { + warnx("%s: Writes to qemu fwcfg data port aren't allowed", + __func__); + return (-1); + } + + /* get fwcfg item */ + struct qemu_fwcfg_item *const item = + &sc.items[sc.selector.architecture][sc.selector.index]; + if (item->data == NULL) { + warnx( + "%s: qemu fwcfg item doesn't exist (architecture %s index 0x%x)", + __func__, sc.selector.architecture ? "specific" : "generic", + sc.selector.index); + *eax = 0x00; + return (0); + } else if (sc.data_offset >= item->size) { + warnx( + "%s: qemu fwcfg item read exceeds size (architecture %s index 0x%x size 0x%x offset 0x%x)", + __func__, sc.selector.architecture ? "specific" : "generic", + sc.selector.index, item->size, sc.data_offset); + *eax = 0x00; + return (0); + } + + /* return item data */ + *eax = item->data[sc.data_offset]; + sc.data_offset++; + + return (0); +} + +static int +qemu_fwcfg_add_item(const uint16_t architecture, const uint16_t index, + const uint32_t size, void *const data) +{ + /* truncate architecture and index to their desired size */ + const uint16_t arch = architecture & QEMU_FWCFG_ARCHITECTURE_MASK; + const uint16_t idx = index & QEMU_FWCFG_INDEX_MASK; + + /* get pointer to item specified by selector */ + struct qemu_fwcfg_item *const fwcfg_item = &sc.items[arch][idx]; + + /* check if item is already used */ + if (fwcfg_item->data != NULL) { + warnx("%s: qemu fwcfg item exists (architecture %s index 0x%x)", + __func__, arch ? "specific" : "generic", idx); + return (-1); + } + + /* save data of the item */ + fwcfg_item->size = size; + fwcfg_item->data = data; + + return (0); +} + +static int +qemu_fwcfg_add_item_file_dir() +{ + /* alloc directory */ + const size_t size = sizeof(struct qemu_fwcfg_directory) + + QEMU_FWCFG_MIN_FILES * sizeof(struct qemu_fwcfg_file); + struct qemu_fwcfg_directory *const fwcfg_directory = calloc(1, size); + if (fwcfg_directory == NULL) { + return (-ENOMEM); + } + + /* init directory */ + sc.directory = fwcfg_directory; + + /* add directory */ + return qemu_fwcfg_add_item(QEMU_FWCFG_ARCHITECTURE_GENERIC, + QEMU_FWCFG_INDEX_FILE_DIR, sizeof(struct qemu_fwcfg_directory), (uint8_t *)sc.directory); +} + +static int +qemu_fwcfg_add_item_id() +{ + /* alloc id */ + struct qemu_fwcfg_id *const fwcfg_id = calloc(1, + sizeof(struct qemu_fwcfg_id)); + if (fwcfg_id == NULL) { + return (-ENOMEM); + } + + /* init id */ + fwcfg_id->interface = 1; + fwcfg_id->DMA = 0; + + /* + * QEMU specifies ID as little endian. + * Convert fwcfg_id to little endian. + */ + uint32_t *const le_fwcfg_id_ptr = (uint32_t *)fwcfg_id; + *le_fwcfg_id_ptr = htole32(*le_fwcfg_id_ptr); + + /* add id */ + return qemu_fwcfg_add_item(QEMU_FWCFG_ARCHITECTURE_GENERIC, + QEMU_FWCFG_INDEX_ID, sizeof(struct qemu_fwcfg_id), + (uint8_t *)fwcfg_id); +} + +static int +qemu_fwcfg_add_item_signature() +{ + /* alloc signature */ + struct qemu_fwcfg_signature *const fwcfg_signature = calloc(1, + sizeof(struct qemu_fwcfg_signature)); + if (fwcfg_signature == NULL) { + return (-ENOMEM); + } + + /* init signature */ + fwcfg_signature->signature[0] = 'Q'; + fwcfg_signature->signature[1] = 'E'; + fwcfg_signature->signature[2] = 'M'; + fwcfg_signature->signature[3] = 'U'; + + /* add signature */ + return qemu_fwcfg_add_item(QEMU_FWCFG_ARCHITECTURE_GENERIC, + QEMU_FWCFG_INDEX_SIGNATURE, sizeof(struct qemu_fwcfg_signature), + (uint8_t *)fwcfg_signature); +} + +static int +qemu_fwcfg_register_port(const char *const name, const int port, const int size, + const int flags, const inout_func_t handler) +{ + struct inout_port iop; + + bzero(&iop, sizeof(iop)); + iop.name = name; + iop.port = port; + iop.size = size; + iop.flags = flags; + iop.handler = handler; + + return register_inout(&iop); +} + +int +qemu_fwcfg_add_file(const uint8_t name[QEMU_FWCFG_MAX_NAME], const uint32_t size, + void *const data) +{ + /* + * QEMU specifies count as big endian. + * Convert it to host endian to work with it. + */ + const uint32_t count = be32toh(sc.directory->be_count) + 1; + + /* add file to items list */ + const uint32_t index = QEMU_FWCFG_FIRST_FILE_INDEX + count - 1; + const int error = qemu_fwcfg_add_item(QEMU_FWCFG_ARCHITECTURE_GENERIC, + index, size, data); + if (error != 0) { + return (error); + } + + /* + * files should be sorted alphabetical, get index for new file + */ + uint32_t file_index; + for (file_index = 0; file_index < count - 1; ++file_index) { + if (strcmp(name, sc.directory->files[file_index].name) < 0) + break; + } + + if (count > QEMU_FWCFG_MIN_FILES) { + /* alloc new file directory */ + const uint64_t new_size = sizeof(struct qemu_fwcfg_directory) + + count * sizeof(struct qemu_fwcfg_file); + struct qemu_fwcfg_directory *const new_directory = calloc(1, + new_size); + if (new_directory == NULL) { + warnx( + "%s: Unable to allocate a new qemu fwcfg files directory (count %d)", + __func__, count); + return (-ENOMEM); + } + + /* copy files below file_index to new directory */ + memcpy(new_directory->files, sc.directory->files, + file_index * sizeof(struct qemu_fwcfg_file)); + + /* copy files behind file_index to directory */ + memcpy(&new_directory->files[file_index + 1], + &sc.directory->files[file_index], + (count - file_index) * sizeof(struct qemu_fwcfg_file)); + + /* free old directory */ + free(sc.directory); + + /* set directory pointer to new directory */ + sc.directory = new_directory; + + /* adjust directory pointer */ + sc.items[0][QEMU_FWCFG_INDEX_FILE_DIR].data = (uint8_t *) + sc.directory; + } else { + /* shift files behind file_index */ + for (uint32_t i = QEMU_FWCFG_MIN_FILES - 1; i > file_index; --i) { + memcpy(&sc.directory->files[i], + &sc.directory->files[i - 1], + sizeof(struct qemu_fwcfg_file)); + } + } + + /* + * QEMU specifies count, size and index as big endian. + * Save these values in big endian to simplify guest reads of these + * values. + */ + sc.directory->be_count = htobe32(count); + sc.directory->files[file_index].be_size = htobe32(size); + sc.directory->files[file_index].be_selector = htobe16(index); + strcpy(sc.directory->files[file_index].name, name); + + /* set new size for the fwcfg_file_directory */ + sc.items[0][QEMU_FWCFG_INDEX_FILE_DIR].size = + sizeof(struct qemu_fwcfg_directory) + + count * sizeof(struct qemu_fwcfg_file); + + return (0); +} + +int +qemu_fwcfg_init(struct vmctx *const ctx) +{ + int error; + + error = acpi_device_create(&sc.acpi_dev, ctx, + QEMU_FWCFG_ACPI_DEVICE_NAME, QEMU_FWCFG_ACPI_HARDWARE_ID); + if (error) { + warnx("%s: failed to create ACPI device for QEMU FwCfg", + __func__); + goto done; + } + + error = acpi_device_add_res_fixed_ioport(sc.acpi_dev, + QEMU_FWCFG_SELECTOR_PORT_NUMBER, 2); + if (error) { + warnx("%s: failed to add fixed IO port for QEMU FwCfg", + __func__); + goto done; + } + + /* add common fwcfg items */ + if ((error = qemu_fwcfg_add_item_signature()) != 0) { + warnx("%s: Unable to add signature item", __func__); + goto done; + } + if ((error = qemu_fwcfg_add_item_id()) != 0) { + warnx("%s: Unable to add id item", __func__); + goto done; + } + if ((error = qemu_fwcfg_add_item_file_dir()) != 0) { + warnx("%s: Unable to add file_dir item", __func__); + goto done; + } + + /* add handlers for fwcfg ports */ + if ((error = qemu_fwcfg_register_port("qemu_fwcfg_selector", + QEMU_FWCFG_SELECTOR_PORT_NUMBER, QEMU_FWCFG_SELECTOR_PORT_SIZE, + QEMU_FWCFG_SELECTOR_PORT_FLAGS, + qemu_fwcfg_selector_port_handler)) != 0) { + warnx("%s: Unable to register qemu fwcfg selector port 0x%x", + __func__, QEMU_FWCFG_SELECTOR_PORT_NUMBER); + goto done; + } + if ((error = qemu_fwcfg_register_port("qemu_fwcfg_data", + QEMU_FWCFG_DATA_PORT_NUMBER, QEMU_FWCFG_DATA_PORT_SIZE, + QEMU_FWCFG_DATA_PORT_FLAGS, qemu_fwcfg_data_port_handler)) != + 0) { + warnx("%s: Unable to register qemu fwcfg data port 0x%x", + __func__, QEMU_FWCFG_DATA_PORT_NUMBER); + goto done; + } + +done: + if (error) { + acpi_device_destroy(sc.acpi_dev); + } + + return (error); +}