Implement "<os firmware='efi'>" support for bhyve driver. As there are not really lot of options, try to find "BHYVE_UEFI.fd" firmware which is installed by the sysutils/uefi-edk2-bhyve FreeBSD port. If not found, just use the first found firmware in the firmwares directory (which is configurable via config file). Signed-off-by: Roman Bogorodskiy <bogorodskiy@xxxxxxxxx> --- Changes from v1: - Fixed various leaks, - Re-implemented testing using opendir() mock. po/POTFILES.in | 1 + src/bhyve/bhyve_domain.c | 5 + src/bhyve/bhyve_firmware.c | 91 +++++++++++++++++++ src/bhyve/bhyve_firmware.h | 30 ++++++ src/bhyve/bhyve_process.c | 15 +++ src/bhyve/bhyve_process.h | 5 + src/bhyve/bhyve_utils.h | 2 + src/bhyve/meson.build | 1 + tests/bhyvefirmwaredata/empty/.keepme | 0 .../three_firmwares/BHYVE_UEFI.fd | 0 .../three_firmwares/BHYVE_UEFI_CSM.fd | 0 .../three_firmwares/refind_x64.efi | 0 .../bhyvexml2argv-firmware-efi.args | 11 +++ .../bhyvexml2argv-firmware-efi.ldargs | 1 + .../bhyvexml2argv-firmware-efi.xml | 22 +++++ tests/bhyvexml2argvmock.c | 33 +++++++ tests/bhyvexml2argvtest.c | 52 ++++++++--- 17 files changed, 257 insertions(+), 12 deletions(-) create mode 100644 src/bhyve/bhyve_firmware.c create mode 100644 src/bhyve/bhyve_firmware.h create mode 100644 tests/bhyvefirmwaredata/empty/.keepme create mode 100644 tests/bhyvefirmwaredata/three_firmwares/BHYVE_UEFI.fd create mode 100644 tests/bhyvefirmwaredata/three_firmwares/BHYVE_UEFI_CSM.fd create mode 100644 tests/bhyvefirmwaredata/three_firmwares/refind_x64.efi create mode 100644 tests/bhyvexml2argvdata/bhyvexml2argv-firmware-efi.args create mode 100644 tests/bhyvexml2argvdata/bhyvexml2argv-firmware-efi.ldargs create mode 100644 tests/bhyvexml2argvdata/bhyvexml2argv-firmware-efi.xml diff --git a/po/POTFILES.in b/po/POTFILES.in index 80c5f145be..413783ee35 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -14,6 +14,7 @@ @SRCDIR@src/bhyve/bhyve_command.c @SRCDIR@src/bhyve/bhyve_domain.c @SRCDIR@src/bhyve/bhyve_driver.c +@SRCDIR@src/bhyve/bhyve_firmware.c @SRCDIR@src/bhyve/bhyve_monitor.c @SRCDIR@src/bhyve/bhyve_parse_command.c @SRCDIR@src/bhyve/bhyve_process.c diff --git a/src/bhyve/bhyve_domain.c b/src/bhyve/bhyve_domain.c index 8fbc554a0a..209e4d3905 100644 --- a/src/bhyve/bhyve_domain.c +++ b/src/bhyve/bhyve_domain.c @@ -64,6 +64,9 @@ bhyveDomainDefNeedsISAController(virDomainDefPtr def) if (def->os.bootloader == NULL && def->os.loader) return true; + if (def->os.firmware == VIR_DOMAIN_OS_DEF_FIRMWARE_EFI) + return true; + if (def->nserials || def->nconsoles) return true; @@ -230,6 +233,8 @@ virDomainDefParserConfig virBhyveDriverDomainDefParserConfig = { .domainPostParseCallback = bhyveDomainDefPostParse, .assignAddressesCallback = bhyveDomainDefAssignAddresses, .deviceValidateCallback = bhyveDomainDeviceDefValidate, + + .features = VIR_DOMAIN_DEF_FEATURE_FW_AUTOSELECT, }; static void diff --git a/src/bhyve/bhyve_firmware.c b/src/bhyve/bhyve_firmware.c new file mode 100644 index 0000000000..2055f34288 --- /dev/null +++ b/src/bhyve/bhyve_firmware.c @@ -0,0 +1,91 @@ +/* + * bhyve_firmware.c: bhyve firmware management + * + * Copyright (C) 2021 Roman Bogorodskiy + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#include <config.h> +#include <dirent.h> + +#include "viralloc.h" +#include "virlog.h" +#include "virfile.h" +#include "bhyve_conf.h" +#include "bhyve_firmware.h" + +#define VIR_FROM_THIS VIR_FROM_BHYVE + +VIR_LOG_INIT("bhyve.bhyve_firmware"); + + +#define BHYVE_DEFAULT_FIRMWARE "BHYVE_UEFI.fd" + +int +bhyveFirmwareFillDomain(bhyveConnPtr driver, + virDomainDefPtr def, + unsigned int flags) +{ + g_autoptr(DIR) dir = NULL; + g_autoptr(virBhyveDriverConfig) cfg = virBhyveDriverGetConfig(driver); + const char *firmware_dir = cfg->firmwareDir; + struct dirent *entry; + g_autofree char *matching_firmware = NULL; + g_autofree char *first_found = NULL; + + virCheckFlags(0, -1); + + if (def->os.firmware == VIR_DOMAIN_OS_DEF_FIRMWARE_NONE) + return 0; + + if (virDirOpenIfExists(&dir, firmware_dir) > 0) { + while ((virDirRead(dir, &entry, firmware_dir)) > 0) { + if (g_str_has_prefix(entry->d_name, ".")) + continue; + + if (STREQ(entry->d_name, BHYVE_DEFAULT_FIRMWARE)) { + matching_firmware = g_strdup(entry->d_name); + break; + } + if (!first_found) + first_found = g_strdup(entry->d_name); + } + } + + if (!matching_firmware) { + if (!first_found) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("no firmwares found in %s"), + firmware_dir); + return -1; + } else { + matching_firmware = g_steal_pointer(&first_found); + } + } + + if (!def->os.loader) + def->os.loader = g_new0(virDomainLoaderDef, 1); + + def->os.loader->type = VIR_DOMAIN_LOADER_TYPE_PFLASH; + def->os.loader->readonly = VIR_TRISTATE_BOOL_YES; + + VIR_FREE(def->os.loader->path); + + def->os.loader->path = g_build_filename(firmware_dir, matching_firmware, NULL); + + return 0; +} diff --git a/src/bhyve/bhyve_firmware.h b/src/bhyve/bhyve_firmware.h new file mode 100644 index 0000000000..ae4bc98676 --- /dev/null +++ b/src/bhyve/bhyve_firmware.h @@ -0,0 +1,30 @@ +/* + * bhyve_firmware.h: bhyve firmware management + * + * Copyright (C) 2021 Roman Bogorodskiy + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <http://www.gnu.org/licenses/>. + * + */ + +#pragma once + +#include "domain_conf.h" +#include "bhyve_utils.h" + +int +bhyveFirmwareFillDomain(bhyveConnPtr driver, + virDomainDefPtr def, + unsigned int flags); diff --git a/src/bhyve/bhyve_process.c b/src/bhyve/bhyve_process.c index 060018bc70..0cfe69688c 100644 --- a/src/bhyve/bhyve_process.c +++ b/src/bhyve/bhyve_process.c @@ -33,6 +33,7 @@ #include "bhyve_device.h" #include "bhyve_driver.h" #include "bhyve_command.h" +#include "bhyve_firmware.h" #include "bhyve_monitor.h" #include "bhyve_process.h" #include "datatypes.h" @@ -251,6 +252,17 @@ virBhyveProcessStartImpl(bhyveConnPtr driver, return ret; } +int +bhyveProcessPrepareDomain(bhyveConnPtr driver, + virDomainObjPtr vm, + unsigned int flags) +{ + if (bhyveFirmwareFillDomain(driver, vm->def, flags) < 0) + return -1; + + return 0; +} + int virBhyveProcessStart(virConnectPtr conn, virDomainObjPtr vm, @@ -268,6 +280,9 @@ virBhyveProcessStart(virConnectPtr conn, conn, bhyveProcessAutoDestroy) < 0) return -1; + if (bhyveProcessPrepareDomain(driver, vm, flags) < 0) + return -1; + return virBhyveProcessStartImpl(driver, vm, reason); } diff --git a/src/bhyve/bhyve_process.h b/src/bhyve/bhyve_process.h index d7b4e0bd4e..133863c1e0 100644 --- a/src/bhyve/bhyve_process.h +++ b/src/bhyve/bhyve_process.h @@ -23,6 +23,11 @@ #include "bhyve_utils.h" +int +bhyveProcessPrepareDomain(bhyveConnPtr driver, + virDomainObjPtr vm, + unsigned int flags); + int virBhyveProcessStart(virConnectPtr conn, virDomainObjPtr vm, virDomainRunningReason reason, diff --git a/src/bhyve/bhyve_utils.h b/src/bhyve/bhyve_utils.h index f3e80b6121..3bf5ae5daf 100644 --- a/src/bhyve/bhyve_utils.h +++ b/src/bhyve/bhyve_utils.h @@ -43,6 +43,8 @@ struct _virBhyveDriverConfig { char *firmwareDir; }; +G_DEFINE_AUTOPTR_CLEANUP_FUNC(virBhyveDriverConfig, virObjectUnref); + struct _bhyveConn { virMutex lock; diff --git a/src/bhyve/meson.build b/src/bhyve/meson.build index 2b65eecf5e..b3551477b7 100644 --- a/src/bhyve/meson.build +++ b/src/bhyve/meson.build @@ -2,6 +2,7 @@ bhyve_sources = files( 'bhyve_capabilities.c', 'bhyve_command.c', 'bhyve_conf.c', + 'bhyve_firmware.c', 'bhyve_parse_command.c', 'bhyve_device.c', 'bhyve_domain.c', diff --git a/tests/bhyvefirmwaredata/empty/.keepme b/tests/bhyvefirmwaredata/empty/.keepme new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/bhyvefirmwaredata/three_firmwares/BHYVE_UEFI.fd b/tests/bhyvefirmwaredata/three_firmwares/BHYVE_UEFI.fd new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/bhyvefirmwaredata/three_firmwares/BHYVE_UEFI_CSM.fd b/tests/bhyvefirmwaredata/three_firmwares/BHYVE_UEFI_CSM.fd new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/bhyvefirmwaredata/three_firmwares/refind_x64.efi b/tests/bhyvefirmwaredata/three_firmwares/refind_x64.efi new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/bhyvexml2argvdata/bhyvexml2argv-firmware-efi.args b/tests/bhyvexml2argvdata/bhyvexml2argv-firmware-efi.args new file mode 100644 index 0000000000..2f5aa4d783 --- /dev/null +++ b/tests/bhyvexml2argvdata/bhyvexml2argv-firmware-efi.args @@ -0,0 +1,11 @@ +/usr/sbin/bhyve \ +-c 1 \ +-m 214 \ +-u \ +-H \ +-P \ +-s 0:0,hostbridge \ +-l bootrom,fakefirmwaredir/BHYVE_UEFI.fd \ +-s 1:0,lpc \ +-s 2:0,ahci,hd:/tmp/freebsd.img \ +-s 3:0,virtio-net,faketapdev,mac=52:54:00:00:00:00 bhyve diff --git a/tests/bhyvexml2argvdata/bhyvexml2argv-firmware-efi.ldargs b/tests/bhyvexml2argvdata/bhyvexml2argv-firmware-efi.ldargs new file mode 100644 index 0000000000..421376db9e --- /dev/null +++ b/tests/bhyvexml2argvdata/bhyvexml2argv-firmware-efi.ldargs @@ -0,0 +1 @@ +dummy diff --git a/tests/bhyvexml2argvdata/bhyvexml2argv-firmware-efi.xml b/tests/bhyvexml2argvdata/bhyvexml2argv-firmware-efi.xml new file mode 100644 index 0000000000..302326cb26 --- /dev/null +++ b/tests/bhyvexml2argvdata/bhyvexml2argv-firmware-efi.xml @@ -0,0 +1,22 @@ +<domain type='bhyve'> + <name>bhyve</name> + <uuid>df3be7e7-a104-11e3-aeb0-50e5492bd3dc</uuid> + <memory>219136</memory> + <vcpu>1</vcpu> + <os firmware='efi'> + <type>hvm</type> + </os> + <devices> + <disk type='file'> + <driver name='file' type='raw'/> + <source file='/tmp/freebsd.img'/> + <target dev='hda' bus='sata'/> + <address type='drive' controller='0' bus='0' target='2' unit='0'/> + </disk> + <interface type='bridge'> + <model type='virtio'/> + <source bridge="virbr0"/> + <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/> + </interface> + </devices> +</domain> diff --git a/tests/bhyvexml2argvmock.c b/tests/bhyvexml2argvmock.c index 25b97f5e04..af5e0fd4b9 100644 --- a/tests/bhyvexml2argvmock.c +++ b/tests/bhyvexml2argvmock.c @@ -1,13 +1,46 @@ #include <config.h> +#include <dirent.h> + #include "viralloc.h" #include "virstring.h" #include "virnetdev.h" #include "virnetdevtap.h" +#include "virmock.h" #include "internal.h" #define VIR_FROM_THIS VIR_FROM_BHYVE +static DIR * (*real_opendir)(const char *name); + +static void +init_syms(void) +{ + VIR_MOCK_REAL_INIT(opendir); +} + +#define FAKEFIRMWAREDIR abs_srcdir "/bhyvefirmwaredata/three_firmwares" +#define FAKEFIRMWAREEMPTYDIR abs_srcdir "/bhyvefirmwaredata/empty" + +DIR * +opendir(const char *path) +{ + init_syms(); + + g_autofree char *path_override = NULL; + + if (STREQ(path, "fakefirmwaredir")) { + path_override = g_strdup(FAKEFIRMWAREDIR); + } else if (STREQ(path, "fakefirmwareemptydir")) { + path_override = g_strdup(FAKEFIRMWAREEMPTYDIR); + } + + if (!path_override) + path_override = g_strdup(path); + + return real_opendir(path_override); +} + void virMacAddrGenerate(const unsigned char prefix[VIR_MAC_PREFIX_BUFLEN], virMacAddrPtr addr) { diff --git a/tests/bhyvexml2argvtest.c b/tests/bhyvexml2argvtest.c index 197334f9c4..34e40a6438 100644 --- a/tests/bhyvexml2argvtest.c +++ b/tests/bhyvexml2argvtest.c @@ -7,17 +7,20 @@ # include "datatypes.h" # include "bhyve/bhyve_capabilities.h" +# include "bhyve/bhyve_conf.h" # include "bhyve/bhyve_domain.h" # include "bhyve/bhyve_utils.h" # include "bhyve/bhyve_command.h" +# include "bhyve/bhyve_process.h" # define VIR_FROM_THIS VIR_FROM_BHYVE static bhyveConn driver; typedef enum { - FLAG_EXPECT_FAILURE = 1 << 0, - FLAG_EXPECT_PARSE_ERROR = 1 << 1, + FLAG_EXPECT_FAILURE = 1 << 0, + FLAG_EXPECT_PARSE_ERROR = 1 << 1, + FLAG_EXPECT_PREPARE_ERROR = 1 << 2, } virBhyveXMLToArgvTestFlags; static int testCompareXMLToArgvFiles(const char *xml, @@ -29,7 +32,7 @@ static int testCompareXMLToArgvFiles(const char *xml, g_autofree char *actualargv = NULL; g_autofree char *actualld = NULL; g_autofree char *actualdm = NULL; - g_autoptr(virDomainDef) vmdef = NULL; + g_autoptr(virDomainObj) vm = NULL; g_autoptr(virCommand) cmd = NULL; g_autoptr(virCommand) ldcmd = NULL; g_autoptr(virConnect) conn = NULL; @@ -38,8 +41,11 @@ static int testCompareXMLToArgvFiles(const char *xml, if (!(conn = virGetConnect())) goto out; - if (!(vmdef = virDomainDefParseFile(xml, driver.xmlopt, - NULL, VIR_DOMAIN_DEF_PARSE_INACTIVE))) { + if (!(vm = virDomainObjNew(driver.xmlopt))) + return -1; + + if (!(vm->def = virDomainDefParseFile(xml, driver.xmlopt, + NULL, VIR_DOMAIN_DEF_PARSE_INACTIVE))) { if (flags & FLAG_EXPECT_PARSE_ERROR) { ret = 0; } else if (flags & FLAG_EXPECT_FAILURE) { @@ -54,11 +60,20 @@ static int testCompareXMLToArgvFiles(const char *xml, conn->privateData = &driver; - cmd = virBhyveProcessBuildBhyveCmd(&driver, vmdef, false); - if (vmdef->os.loader) + if (bhyveProcessPrepareDomain(&driver, vm, 0) < 0) { + if (flags & FLAG_EXPECT_PREPARE_ERROR) { + ret = 0; + VIR_TEST_DEBUG("Got expected error: %s", + virGetLastErrorMessage()); + } + goto out; + } + + cmd = virBhyveProcessBuildBhyveCmd(&driver, vm->def, false); + if (vm->def->os.loader) ldcmd = virCommandNew("dummy"); else - ldcmd = virBhyveProcessBuildLoadCmd(&driver, vmdef, "<device.map>", + ldcmd = virBhyveProcessBuildLoadCmd(&driver, vm->def, "<device.map>", &actualdm); if ((cmd == NULL) || (ldcmd == NULL)) { @@ -94,10 +109,10 @@ static int testCompareXMLToArgvFiles(const char *xml, ret = 0; out: - if (vmdef && - vmdef->ngraphics == 1 && - vmdef->graphics[0]->type == VIR_DOMAIN_GRAPHICS_TYPE_VNC) - virPortAllocatorRelease(vmdef->graphics[0]->data.vnc.port); + if (vm && vm->def && + vm->def->ngraphics == 1 && + vm->def->graphics[0]->type == VIR_DOMAIN_GRAPHICS_TYPE_VNC) + virPortAllocatorRelease(vm->def->graphics[0]->data.vnc.port); return ret; } @@ -132,6 +147,8 @@ static int mymain(void) { int ret = 0; + g_autofree char *fakefirmwaredir = g_strdup("fakefirmwaredir"); + g_autofree char *fakefirmwareemptydir = g_strdup("fakefirmwareemptydir"); if ((driver.caps = virBhyveCapsBuild()) == NULL) return EXIT_FAILURE; @@ -142,6 +159,10 @@ mymain(void) if (!(driver.remotePorts = virPortAllocatorRangeNew("display", 5900, 65535))) return EXIT_FAILURE; + if (!(driver.config = virBhyveDriverConfigNew())) + return EXIT_FAILURE; + + driver.config->firmwareDir = fakefirmwaredir; # define DO_TEST_FULL(name, flags) \ do { \ @@ -162,6 +183,9 @@ mymain(void) # define DO_TEST_PARSE_ERROR(name) \ DO_TEST_FULL(name, FLAG_EXPECT_PARSE_ERROR) +# define DO_TEST_PREPARE_ERROR(name) \ + DO_TEST_FULL(name, FLAG_EXPECT_PREPARE_ERROR) + driver.grubcaps = BHYVE_GRUB_CAP_CONSDEV; driver.bhyvecaps = BHYVE_CAP_RTC_UTC | BHYVE_CAP_AHCI32SLOT | \ BHYVE_CAP_NET_E1000 | BHYVE_CAP_LPC_BOOTROM | \ @@ -209,6 +233,9 @@ mymain(void) DO_TEST("sound"); DO_TEST("isa-controller"); DO_TEST_FAILURE("isa-multiple-controllers"); + DO_TEST("firmware-efi"); + driver.config->firmwareDir = fakefirmwareemptydir; + DO_TEST_PREPARE_ERROR("firmware-efi"); DO_TEST("fs-9p"); DO_TEST("fs-9p-readonly"); DO_TEST_FAILURE("fs-9p-unsupported-type"); @@ -267,6 +294,7 @@ mymain(void) virObjectUnref(driver.caps); virObjectUnref(driver.xmlopt); virPortAllocatorRangeFree(driver.remotePorts); + virObjectUnref(driver.config); return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE; } -- 2.30.0