Index: usr.sbin/bhyve/bhyve.8 =================================================================== --- usr.sbin/bhyve/bhyve.8 +++ usr.sbin/bhyve/bhyve.8 @@ -24,7 +24,7 @@ .\" .\" $FreeBSD$ .\" -.Dd December 11, 2018 +.Dd April 22, 2019 .Dt BHYVE 8 .Os .Sh NAME @@ -323,10 +323,15 @@ .Pp Boot ROM device: .Bl -tag -width 10n -.It Pa romfile +.It Pa romfile Ns Op , Ns Pa 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 persisted +to that file. .El .Pp Pass-through devices: @@ -616,6 +621,19 @@ -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 /usr. +.Bd -literal -offset indent +bhyve -c 2 -m 4g -w -H \\ + -s 0,hostbridge \\ + -s 31,lpc -p com1,stdio \\ + -l bootrom,/usr/.../BHYVE_UEFI_CODE.fd,/var/.../BHYVE_UEFI_VARS.fd + uefivm +.Ed .Sh SEE ALSO .Xr bhyve 4 , .Xr nmdm 4 , Index: usr.sbin/bhyve/bootrom.c =================================================================== --- usr.sbin/bhyve/bootrom.c +++ usr.sbin/bhyve/bootrom.c @@ -38,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -45,19 +46,82 @@ #include #include "bhyverun.h" #include "bootrom.h" +#include "mem.h" #define MAX_BOOTROM_SIZE (16 * 1024 * 1024) /* 16 MB */ +#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); +} + int bootrom_init(struct vmctx *ctx, const char *romfile) { struct stat sbuf; vm_paddr_t gpa; + off_t rom_size, var_size, total_size; ssize_t rlen; - char *ptr; - int fd, i, rv, prot; + char *ptr, *romfile_dup, *varfile; + int fd, varfd, i, rv, prot; rv = -1; + varfd = -1; + if (strchr(romfile, ',') == NULL) { + romfile_dup = NULL; + varfile = NULL; + } else { + romfile_dup = strdup(romfile); + romfile = romfile_dup; + varfile = romfile_dup; + strsep(&varfile, ","); + } + fd = open(romfile, O_RDONLY); if (fd < 0) { fprintf(stderr, "Error opening bootrom \"%s\": %s\n", @@ -65,39 +129,80 @@ goto done; } + 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) { fprintf(stderr, "Could not fstat bootrom file \"%s\": %s\n", 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; + } + /* * Limit bootrom size to 16MB so it doesn't encroach into reserved * MMIO space (e.g. APIC, HPET, MSI). */ - if (sbuf.st_size > MAX_BOOTROM_SIZE || sbuf.st_size < PAGE_SIZE) { - fprintf(stderr, "Invalid bootrom size %ld\n", sbuf.st_size); + if (rom_size > MAX_BOOTROM_SIZE || rom_size < PAGE_SIZE) { + fprintf(stderr, "Invalid bootrom size %ld\n", rom_size); + goto done; + } + + if (var_size > MAX_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 > MAX_BOOTROM_SIZE) { + fprintf(stderr, "Invalid bootrom and variable aggregate size " + "%ld\n", total_size); goto done; } - if (sbuf.st_size & PAGE_MASK) { + if (rom_size & PAGE_MASK) { fprintf(stderr, "Bootrom size %ld is not a multiple of the " - "page size\n", sbuf.st_size); + "page size\n", rom_size); goto done; } - ptr = vm_create_devmem(ctx, VM_BOOTROM, "bootrom", sbuf.st_size); + if (var_size & PAGE_MASK) { + fprintf(stderr, "Bootrom variable size %ld is not a multiple " + "of the page size\n", var_size); + goto done; + } + + ptr = vm_create_devmem(ctx, VM_BOOTROM, "bootrom", rom_size); if (ptr == MAP_FAILED) goto done; /* Map the bootrom into the guest address space */ prot = PROT_READ | PROT_EXEC; - gpa = (1ULL << 32) - sbuf.st_size; - if (vm_mmap_memseg(ctx, gpa, VM_BOOTROM, 0, sbuf.st_size, prot) != 0) + gpa = (1ULL << 32) - rom_size; + if (vm_mmap_memseg(ctx, gpa, VM_BOOTROM, 0, rom_size, prot) != 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) { fprintf(stderr, "Incomplete read of page %d of bootrom " @@ -105,9 +210,31 @@ 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 - var_size; + 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: + free(romfile_dup); if (fd >= 0) close(fd); + if (varfd >= 0) + close(varfd); return (rv); }