git: 866036f46c6e - main - bhyve: Support a _VARS.fd file for bootrom
Date: Sun, 12 Dec 2021 15:08:26 UTC
The branch main has been updated by bcran: URL: https://cgit.FreeBSD.org/src/commit/?id=866036f46c6e8884cc7a2aa029408366ede40a23 commit 866036f46c6e8884cc7a2aa029408366ede40a23 Author: Rebecca Cran <bcran@FreeBSD.org> AuthorDate: 2021-11-28 16:34:33 +0000 Commit: Rebecca Cran <bcran@FreeBSD.org> CommitDate: 2021-12-12 15:07:27 +0000 bhyve: Support a _VARS.fd file for bootrom OVMF creates two separate .fd files, a _CODE.fd file containing the UEFI code, and a _VARS.fd file containing a template of an empty UEFI variable store. OVMF decides to write variables to the memory range just below the boot rom code if it detects a CFI flash device. So here we add just the barest facsimile of CFI command handling to bootrom.c that is needed to placate OVMF. Submitted by: D Scott Phillips <d.scott.phillips@intel.com> Sponsored by: Intel Corporation Differential Revision: https://reviews.freebsd.org/D19976 MFC After: 1 week --- usr.sbin/bhyve/bhyve.8 | 23 ++++++++- usr.sbin/bhyve/bootrom.c | 132 ++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 145 insertions(+), 10 deletions(-) diff --git a/usr.sbin/bhyve/bhyve.8 b/usr.sbin/bhyve/bhyve.8 index 3b7bf6b65467..fe4eb3ffaaeb 100644 --- a/usr.sbin/bhyve/bhyve.8 +++ b/usr.sbin/bhyve/bhyve.8 @@ -24,7 +24,7 @@ .\" .\" $FreeBSD$ .\" -.Dd October 13, 2021 +.Dd November 28, 2021 .Dt BHYVE 8 .Os .Sh NAME @@ -500,10 +500,15 @@ Use the host TTY device for serial port I/O. .Pp Boot ROM device backends: .Bl -tag -width 10n -.It Ar romfile +.It Ar romfile Ns Op Cm \&, Ns Ar varfile Map .Ar romfile in the guest address space reserved for boot firmware. +If +.Ar varfile +is provided, that file is also mapped in the boot firmware guest +address space, and any modifications the guest makes will be saved +to that file. .El .Pp Pass-through device backends: @@ -916,6 +921,20 @@ bhyve -c 2 -m 4G -w -H \\ -l bootrom,/usr/local/share/uefi-firmware/BHYVE_UEFI.fd \\ uefivm .Ed +.Pp +Run a UEFI virtual machine with a VARS file to save EFI variables. +Note that +.Nm +will write guest modifications to the given VARS file. +Be sure to create a per-guest copy of the template VARS file from +.Pa /usr . +.Bd -literal -offset indent +bhyve -c 2 -m 4g -w -H \\ + -s 0,hostbridge \\ + -s 31,lpc -p com1,stdio \\ + -l bootrom,/usr/local/share/uefi-firmware/BHYVE_UEFI_CODE.fd,BHYVE_UEFI_VARS.fd + uefivm +.Ed .Sh SEE ALSO .Xr bhyve 4 , .Xr netgraph 4 , diff --git a/usr.sbin/bhyve/bootrom.c b/usr.sbin/bhyve/bootrom.c index 38a50490eb0b..757ec07d4a54 100644 --- a/usr.sbin/bhyve/bootrom.c +++ b/usr.sbin/bhyve/bootrom.c @@ -39,14 +39,17 @@ __FBSDID("$FreeBSD$"); #include <errno.h> #include <fcntl.h> #include <stdio.h> +#include <stdlib.h> #include <string.h> #include <unistd.h> #include <stdbool.h> #include <vmmapi.h> + #include "bhyverun.h" #include "bootrom.h" #include "debug.h" +#include "mem.h" #define BOOTROM_SIZE (16 * 1024 * 1024) /* 16 MB */ @@ -64,6 +67,56 @@ static vm_paddr_t gpa_base; /* GPA of low end of region. */ static vm_paddr_t gpa_allocbot; /* Low GPA of free region. */ static vm_paddr_t gpa_alloctop; /* High GPA, minus 1, of free region. */ +#define CFI_BCS_WRITE_BYTE 0x10 +#define CFI_BCS_CLEAR_STATUS 0x50 +#define CFI_BCS_READ_STATUS 0x70 +#define CFI_BCS_READ_ARRAY 0xff + +static struct bootrom_var_state { + uint8_t *mmap; + uint64_t gpa; + off_t size; + uint8_t cmd; +} var = { NULL, 0, 0, CFI_BCS_READ_ARRAY }; + +/* + * Emulate just those CFI basic commands that will convince EDK II + * that the Firmware Volume area is writable and persistent. + */ +static int +bootrom_var_mem_handler(struct vmctx *ctx, int vcpu, int dir, uint64_t addr, + int size, uint64_t *val, void *arg1, long arg2) +{ + off_t offset; + + offset = addr - var.gpa; + if (offset + size > var.size || offset < 0 || offset + size <= offset) + return (EINVAL); + + if (dir == MEM_F_WRITE) { + switch (var.cmd) { + case CFI_BCS_WRITE_BYTE: + memcpy(var.mmap + offset, val, size); + var.cmd = CFI_BCS_READ_ARRAY; + break; + default: + var.cmd = *(uint8_t *)val; + } + } else { + switch (var.cmd) { + case CFI_BCS_CLEAR_STATUS: + case CFI_BCS_READ_STATUS: + memset(val, 0, size); + var.cmd = CFI_BCS_READ_ARRAY; + break; + default: + memcpy(val, var.mmap + offset, size); + break; + } + } + return (0); +} + void init_bootrom(struct vmctx *ctx) { @@ -142,10 +195,16 @@ bootrom_loadrom(struct vmctx *ctx, const char *romfile) { struct stat sbuf; ssize_t rlen; - char *ptr; - int fd, i, rv; + off_t rom_size, var_size, total_size; + char *ptr, *varfile; + int fd, varfd, i, rv; rv = -1; + varfd = -1; + + varfile = strdup(romfile); + romfile = strsep(&varfile, ","); + fd = open(romfile, O_RDONLY); if (fd < 0) { EPRINTLN("Error opening bootrom \"%s\": %s", @@ -153,19 +212,56 @@ bootrom_loadrom(struct vmctx *ctx, const char *romfile) goto done; } - if (fstat(fd, &sbuf) < 0) { + if (varfile != NULL) { + varfd = open(varfile, O_RDWR); + if (varfd < 0) { + fprintf(stderr, "Error opening bootrom variable file " + "\"%s\": %s\n", varfile, strerror(errno)); + goto done; + } + } + + if (fstat(fd, &sbuf) < 0) { EPRINTLN("Could not fstat bootrom file \"%s\": %s", - romfile, strerror(errno)); + romfile, strerror(errno)); goto done; - } + } + + rom_size = sbuf.st_size; + if (varfd < 0) { + var_size = 0; + } else { + if (fstat(varfd, &sbuf) < 0) { + fprintf(stderr, "Could not fstat bootrom variable file \"%s\": %s\n", + varfile, strerror(errno)); + goto done; + } + var_size = sbuf.st_size; + } + + if (var_size > BOOTROM_SIZE || + (var_size != 0 && var_size < PAGE_SIZE)) { + fprintf(stderr, "Invalid bootrom variable size %ld\n", + var_size); + goto done; + } + + total_size = rom_size + var_size; + + if (total_size > BOOTROM_SIZE) { + fprintf(stderr, "Invalid bootrom and variable aggregate size " + "%ld\n", total_size); + goto done; + } /* Map the bootrom into the guest address space */ - if (bootrom_alloc(ctx, sbuf.st_size, PROT_READ | PROT_EXEC, - BOOTROM_ALLOC_TOP, &ptr, NULL) != 0) + if (bootrom_alloc(ctx, rom_size, PROT_READ | PROT_EXEC, + BOOTROM_ALLOC_TOP, &ptr, NULL) != 0) { goto done; + } /* Read 'romfile' into the guest address space */ - for (i = 0; i < sbuf.st_size / PAGE_SIZE; i++) { + for (i = 0; i < rom_size / PAGE_SIZE; i++) { rlen = read(fd, ptr + i * PAGE_SIZE, PAGE_SIZE); if (rlen != PAGE_SIZE) { EPRINTLN("Incomplete read of page %d of bootrom " @@ -173,6 +269,26 @@ bootrom_loadrom(struct vmctx *ctx, const char *romfile) goto done; } } + + if (varfd >= 0) { + var.mmap = mmap(NULL, var_size, PROT_READ | PROT_WRITE, + MAP_SHARED, varfd, 0); + if (var.mmap == MAP_FAILED) + goto done; + var.size = var_size; + var.gpa = (gpa_alloctop - var_size) + 1; + gpa_alloctop = var.gpa - 1; + rv = register_mem(&(struct mem_range){ + .name = "bootrom variable", + .flags = MEM_F_RW, + .handler = bootrom_var_mem_handler, + .base = var.gpa, + .size = var.size, + }); + if (rv != 0) + goto done; + } + rv = 0; done: if (fd >= 0)