git: d6c280fb36fe - stable/13 - bhyve: Support a _VARS.fd file for bootrom

From: Corvin Köhne <corvink_at_FreeBSD.org>
Date: Thu, 08 Dec 2022 17:43:58 UTC
The branch stable/13 has been updated by corvink:

URL: https://cgit.FreeBSD.org/src/commit/?id=d6c280fb36fea18cbb61a07653b737182d085cf6

commit d6c280fb36fea18cbb61a07653b737182d085cf6
Author:     Rebecca Cran <bcran@FreeBSD.org>
AuthorDate: 2021-11-28 16:34:33 +0000
Commit:     Corvin Köhne <corvink@FreeBSD.org>
CommitDate: 2022-12-08 13:53:38 +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
    
    (cherry picked from commit 866036f46c6e8884cc7a2aa029408366ede40a23)
---
 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 f56f4e771b70..d9f31a09b7db 100644
--- a/usr.sbin/bhyve/bhyve.8
+++ b/usr.sbin/bhyve/bhyve.8
@@ -24,7 +24,7 @@
 .\"
 .\" $FreeBSD$
 .\"
-.Dd August 19, 2022
+.Dd December 8, 2022
 .Dt BHYVE 8
 .Os
 .Sh NAME
@@ -506,10 +506,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:
@@ -937,6 +942,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)