diff --git a/usr.sbin/bhyve/Makefile b/usr.sbin/bhyve/Makefile --- a/usr.sbin/bhyve/Makefile +++ b/usr.sbin/bhyve/Makefile @@ -61,6 +61,7 @@ post.c \ ps2kbd.c \ ps2mouse.c \ + qemu_fwcfg.c \ rfb.c \ rtc.c \ smbiostbl.c \ 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" @@ -1200,6 +1201,7 @@ set_config_bool("acpi_tables", false); set_config_value("memory.size", "256M"); set_config_bool("x86.strictmsr", true); + set_config_value("lpc.fwcfg", "bhyve"); } int @@ -1518,8 +1520,56 @@ assert(error == 0); } - if (lpc_bootrom()) - fwctl_init(); + if (lpc_bootrom()) { + const char *fwcfg = get_config_value("lpc.fwcfg"); + if (strcmp(fwcfg, "qemu") == 0) { + error = qemu_fwcfg_init(ctx); + if (error) { + perror("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 + */ + error = qemu_fwcfg_add_file("opt/bhyve/hw.ncpu", + sizeof(guest_ncpus), &guest_ncpus); + if (error) { + perror( + "Could not add qemu fwcfg opt/bhyve/hw.ncpu"); + exit(4); + } + } else if (strcmp(fwcfg, "bhyve") == 0) { + error = fwctl_init(); + if (error) { + perror("bhyve fwctl initialization error"); + exit(4); + } + } else { + fprintf(stderr, "Invalid fwcfg %s", fwcfg); + exit(4); + } + } /* * Change the proc title to include the VM name. diff --git a/usr.sbin/bhyve/fwctl.h b/usr.sbin/bhyve/fwctl.h --- a/usr.sbin/bhyve/fwctl.h +++ b/usr.sbin/bhyve/fwctl.h @@ -51,6 +51,6 @@ }; \ DATA_SET(ctl_set, __CONCAT(__ctl, __LINE__)) -void fwctl_init(void); +int fwctl_init(void); #endif /* _FWCTL_H_ */ diff --git a/usr.sbin/bhyve/fwctl.c b/usr.sbin/bhyve/fwctl.c --- a/usr.sbin/bhyve/fwctl.c +++ b/usr.sbin/bhyve/fwctl.c @@ -538,15 +538,39 @@ return (0); } -INOUT_PORT(fwctl_wreg, FWCTL_OUT, IOPORT_F_INOUT, fwctl_handler); -INOUT_PORT(fwctl_rreg, FWCTL_IN, IOPORT_F_IN, fwctl_handler); -void +int fwctl_init(void) { + struct inout_port iop; + int error; + + bzero(&iop, sizeof(iop)); + iop.name = "fwctl_wreg"; + iop.port = FWCTL_OUT; + iop.size = 1; + iop.flags = IOPORT_F_INOUT; + iop.handler = fwctl_handler; + + if ((error = register_inout(&iop)) != 0) { + return (error); + } + + bzero(&iop, sizeof(iop)); + iop.name = "fwctl_rreg"; + iop.port = FWCTL_IN; + iop.size = 1; + iop.flags = IOPORT_F_IN; + iop.handler = fwctl_handler; + + if ((error = register_inout(&iop)) != 0) { + return (error); + } ops[OP_GET_LEN] = &fgetlen_info; ops[OP_GET] = &fgetval_info; be_state = IDENT_WAIT; + + return (0); } 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 @@ -122,6 +122,11 @@ error = 0; goto done; } + if (strcasecmp(lpcdev, "fwcfg") == 0) { + set_config_value("lpc.fwcfg", str); + error = 0; + goto done; + } } done: 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,41 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2021 Beckhoff Automation GmbH & Co. KG + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR OR CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#pragma once + +#include + +#define QEMU_FWCFG_MAX_ARCHS 0x2 +#define QEMU_FWCFG_MAX_ENTRIES 0x3FFF +#define QEMU_FWCFG_MAX_NAME 56 + +int qemu_fwcfg_add_file(uint8_t name[QEMU_FWCFG_MAX_NAME], uint32_t size, + void *data); +int qemu_fwcfg_init(struct vmctx *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,402 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2021 Beckhoff Automation GmbH & Co. KG + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR OR CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include "inout.h" +#include "qemu_fwcfg.h" + +#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(push, 1) +struct qemu_fwcfg_selector { + uint16_t index : 14; + uint16_t writeable : 1; + /* + * 0 = generic | for all architectures + * 1 = specific | only for current architecture + */ + uint16_t architecture : 1; +}; + +struct qemu_fwcfg_item { + uint32_t size; + uint8_t *data; +}; + +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[QEMU_FWCFG_MIN_FILES]; +}; + +struct qemu_fwcfg_softc { + uint32_t data_offset; + struct qemu_fwcfg_selector selector; + struct qemu_fwcfg_item items[QEMU_FWCFG_MAX_ARCHS] + [QEMU_FWCFG_MAX_ENTRIES]; + struct qemu_fwcfg_directory *directory; +}; +#pragma pack(pop) + +static struct qemu_fwcfg_softc sc; + +static int +qemu_fwcfg_selector_port_handler(struct vmctx *ctx, int vcpu, int in, int port, + int bytes, uint32_t *eax, void *arg) +{ + if (in) { + *eax = *(uint16_t *)&sc.selector; + return (0); + } + + sc.data_offset = 0; + sc.selector = *(struct qemu_fwcfg_selector *)eax; + + return (0); +} + +static int +qemu_fwcfg_data_port_handler(struct vmctx *ctx, int vcpu, int in, int port, + int bytes, uint32_t *eax, void *arg) +{ + if (!in) { + warnx("%s: Writes to qemu fwcfg data port aren't allowed", + __func__); + return (-1); + } + + /* get fwcfg item */ + struct qemu_fwcfg_item *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(uint16_t architecture, uint16_t index, uint32_t size, + void *data) +{ + /* truncate architecture and index to their desired size */ + architecture &= QEMU_FWCFG_ARCHITECTURE_MASK; + index &= QEMU_FWCFG_INDEX_MASK; + + /* get pointer to item specified by selector */ + struct qemu_fwcfg_item *fwcfg_item = &sc.items[architecture][index]; + + /* check if item is already used */ + if (fwcfg_item->data != NULL) { + warnx("%s: qemu fwcfg item exists (architecture %s index 0x%x)", + __func__, architecture ? "specific" : "generic", index); + 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 */ + struct qemu_fwcfg_directory *fwcfg_directory = calloc(1, + sizeof(struct qemu_fwcfg_directory)); + 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 *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 *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 *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 *name, int port, int size, int flags, + 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(uint8_t name[QEMU_FWCFG_MAX_NAME], uint32_t size, + void *data) +{ + /* + * QEMU specifies count as big endian. + * Convert it to host endian to work with it. + */ + uint32_t count = be32toh(sc.directory->be_count); + + /* add file to items list */ + uint32_t index = QEMU_FWCFG_FIRST_FILE_INDEX + count; + 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; ++file_index) { + if (strcmp(name, sc.directory->files[file_index].name) < 0) + break; + } + + ++count; + if (count > QEMU_FWCFG_MIN_FILES) { + /* alloc new file directory */ + uint64_t new_size = sizeof(struct qemu_fwcfg_directory) + + (count - QEMU_FWCFG_MIN_FILES) * + sizeof(struct qemu_fwcfg_file); + struct qemu_fwcfg_directory *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 i to new directory */ + memcpy(new_directory->files, sc.directory->files, + file_index * sizeof(struct qemu_fwcfg_file)); + + /* copy files behind i 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 i */ + for (int32_t i = QEMU_FWCFG_MIN_FILES - 2; i >= 0; --i) { + memcpy(&sc.directory->files[i + 1], + &sc.directory->files[i], + 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); + + return (0); +} + +int +qemu_fwcfg_init(struct vmctx *ctx) +{ + int error; + + /* add common fwcfg items */ + if ((error = qemu_fwcfg_add_item_signature()) != 0) { + warnx("%s: Unable to add signature item", __func__); + return (error); + } + if ((error = qemu_fwcfg_add_item_id()) != 0) { + warnx("%s: Unable to add id item", __func__); + return (error); + } + if ((error = qemu_fwcfg_add_item_file_dir()) != 0) { + warnx("%s: Unable to add file_dir item", __func__); + return (error); + } + + /* 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); + return (error); + } + 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); + return (error); + } + + return (0); +}