xref: /illumos-gate/usr/src/cmd/bhyve/bootrom.c (revision 6dc983494b0ffef2565cc4d91371ee345425ffab)
14c87aefeSPatrick Mooney /*-
24c87aefeSPatrick Mooney  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
34c87aefeSPatrick Mooney  *
44c87aefeSPatrick Mooney  * Copyright (c) 2015 Neel Natu <neel@freebsd.org>
54c87aefeSPatrick Mooney  * All rights reserved.
64c87aefeSPatrick Mooney  *
74c87aefeSPatrick Mooney  * Redistribution and use in source and binary forms, with or without
84c87aefeSPatrick Mooney  * modification, are permitted provided that the following conditions
94c87aefeSPatrick Mooney  * are met:
104c87aefeSPatrick Mooney  * 1. Redistributions of source code must retain the above copyright
114c87aefeSPatrick Mooney  *    notice, this list of conditions and the following disclaimer.
124c87aefeSPatrick Mooney  * 2. Redistributions in binary form must reproduce the above copyright
134c87aefeSPatrick Mooney  *    notice, this list of conditions and the following disclaimer in the
144c87aefeSPatrick Mooney  *    documentation and/or other materials provided with the distribution.
154c87aefeSPatrick Mooney  *
164c87aefeSPatrick Mooney  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND
174c87aefeSPatrick Mooney  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
184c87aefeSPatrick Mooney  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
194c87aefeSPatrick Mooney  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
204c87aefeSPatrick Mooney  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
214c87aefeSPatrick Mooney  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
224c87aefeSPatrick Mooney  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
234c87aefeSPatrick Mooney  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
244c87aefeSPatrick Mooney  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
254c87aefeSPatrick Mooney  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
264c87aefeSPatrick Mooney  * SUCH DAMAGE.
274c87aefeSPatrick Mooney  */
284c87aefeSPatrick Mooney 
294c87aefeSPatrick Mooney #include <sys/param.h>
304c87aefeSPatrick Mooney __FBSDID("$FreeBSD$");
314c87aefeSPatrick Mooney 
324c87aefeSPatrick Mooney #include <sys/types.h>
334c87aefeSPatrick Mooney #include <sys/mman.h>
344c87aefeSPatrick Mooney #include <sys/stat.h>
354c87aefeSPatrick Mooney 
364c87aefeSPatrick Mooney #include <machine/vmm.h>
374c87aefeSPatrick Mooney 
38154972afSPatrick Mooney #include <err.h>
394c87aefeSPatrick Mooney #include <errno.h>
404c87aefeSPatrick Mooney #include <fcntl.h>
414c87aefeSPatrick Mooney #include <stdio.h>
42*6dc98349SAndy Fiddaman #include <stdlib.h>
434c87aefeSPatrick Mooney #include <string.h>
444c87aefeSPatrick Mooney #include <unistd.h>
454c87aefeSPatrick Mooney #include <stdbool.h>
464c87aefeSPatrick Mooney 
474c87aefeSPatrick Mooney #include <vmmapi.h>
48*6dc98349SAndy Fiddaman 
494c87aefeSPatrick Mooney #include "bhyverun.h"
504c87aefeSPatrick Mooney #include "bootrom.h"
51154972afSPatrick Mooney #include "debug.h"
52*6dc98349SAndy Fiddaman #include "mem.h"
534c87aefeSPatrick Mooney 
54154972afSPatrick Mooney #define	BOOTROM_SIZE	(16 * 1024 * 1024)	/* 16 MB */
55154972afSPatrick Mooney 
56154972afSPatrick Mooney /*
57154972afSPatrick Mooney  * ROM region is 16 MB at the top of 4GB ("low") memory.
58154972afSPatrick Mooney  *
59154972afSPatrick Mooney  * The size is limited so it doesn't encroach into reserved MMIO space (e.g.,
60154972afSPatrick Mooney  * APIC, HPET, MSI).
61154972afSPatrick Mooney  *
62154972afSPatrick Mooney  * It is allocated in page-multiple blocks on a first-come first-serve basis,
63154972afSPatrick Mooney  * from high to low, during initialization, and does not change at runtime.
64154972afSPatrick Mooney  */
65154972afSPatrick Mooney static char *romptr;	/* Pointer to userspace-mapped bootrom region. */
66154972afSPatrick Mooney static vm_paddr_t gpa_base;	/* GPA of low end of region. */
67154972afSPatrick Mooney static vm_paddr_t gpa_allocbot;	/* Low GPA of free region. */
68154972afSPatrick Mooney static vm_paddr_t gpa_alloctop;	/* High GPA, minus 1, of free region. */
69154972afSPatrick Mooney 
70*6dc98349SAndy Fiddaman #define CFI_BCS_WRITE_BYTE      0x10
71*6dc98349SAndy Fiddaman #define CFI_BCS_CLEAR_STATUS    0x50
72*6dc98349SAndy Fiddaman #define CFI_BCS_READ_STATUS     0x70
73*6dc98349SAndy Fiddaman #define CFI_BCS_READ_ARRAY      0xff
74*6dc98349SAndy Fiddaman 
75*6dc98349SAndy Fiddaman static struct bootrom_var_state {
76*6dc98349SAndy Fiddaman 	uint8_t		*mmap;
77*6dc98349SAndy Fiddaman 	uint64_t	gpa;
78*6dc98349SAndy Fiddaman 	off_t		size;
79*6dc98349SAndy Fiddaman 	uint8_t		cmd;
80*6dc98349SAndy Fiddaman } var = { NULL, 0, 0, CFI_BCS_READ_ARRAY };
81*6dc98349SAndy Fiddaman 
82*6dc98349SAndy Fiddaman /*
83*6dc98349SAndy Fiddaman  * Emulate just those CFI basic commands that will convince EDK II
84*6dc98349SAndy Fiddaman  * that the Firmware Volume area is writable and persistent.
85*6dc98349SAndy Fiddaman  */
86*6dc98349SAndy Fiddaman static int
87*6dc98349SAndy Fiddaman bootrom_var_mem_handler(struct vmctx *ctx, int vcpu, int dir, uint64_t addr,
88*6dc98349SAndy Fiddaman     int size, uint64_t *val, void *arg1, long arg2)
89*6dc98349SAndy Fiddaman {
90*6dc98349SAndy Fiddaman 	off_t offset;
91*6dc98349SAndy Fiddaman 
92*6dc98349SAndy Fiddaman 	offset = addr - var.gpa;
93*6dc98349SAndy Fiddaman 	if (offset + size > var.size || offset < 0 || offset + size <= offset)
94*6dc98349SAndy Fiddaman 		return (EINVAL);
95*6dc98349SAndy Fiddaman 
96*6dc98349SAndy Fiddaman 	if (dir == MEM_F_WRITE) {
97*6dc98349SAndy Fiddaman 		switch (var.cmd) {
98*6dc98349SAndy Fiddaman 		case CFI_BCS_WRITE_BYTE:
99*6dc98349SAndy Fiddaman 			memcpy(var.mmap + offset, val, size);
100*6dc98349SAndy Fiddaman 			var.cmd = CFI_BCS_READ_ARRAY;
101*6dc98349SAndy Fiddaman 			break;
102*6dc98349SAndy Fiddaman 		default:
103*6dc98349SAndy Fiddaman 			var.cmd = *(uint8_t *)val;
104*6dc98349SAndy Fiddaman 		}
105*6dc98349SAndy Fiddaman 	} else {
106*6dc98349SAndy Fiddaman 		switch (var.cmd) {
107*6dc98349SAndy Fiddaman 		case CFI_BCS_CLEAR_STATUS:
108*6dc98349SAndy Fiddaman 		case CFI_BCS_READ_STATUS:
109*6dc98349SAndy Fiddaman 			memset(val, 0, size);
110*6dc98349SAndy Fiddaman 			var.cmd = CFI_BCS_READ_ARRAY;
111*6dc98349SAndy Fiddaman 			break;
112*6dc98349SAndy Fiddaman 		default:
113*6dc98349SAndy Fiddaman 			memcpy(val, var.mmap + offset, size);
114*6dc98349SAndy Fiddaman 			break;
115*6dc98349SAndy Fiddaman 		}
116*6dc98349SAndy Fiddaman 	}
117*6dc98349SAndy Fiddaman 	return (0);
118*6dc98349SAndy Fiddaman }
119*6dc98349SAndy Fiddaman 
120154972afSPatrick Mooney void
121154972afSPatrick Mooney init_bootrom(struct vmctx *ctx)
122154972afSPatrick Mooney {
123154972afSPatrick Mooney 	romptr = vm_create_devmem(ctx, VM_BOOTROM, "bootrom", BOOTROM_SIZE);
124154972afSPatrick Mooney 	if (romptr == MAP_FAILED)
125154972afSPatrick Mooney 		err(4, "%s: vm_create_devmem", __func__);
126154972afSPatrick Mooney 	gpa_base = (1ULL << 32) - BOOTROM_SIZE;
127154972afSPatrick Mooney 	gpa_allocbot = gpa_base;
128154972afSPatrick Mooney 	gpa_alloctop = (1ULL << 32) - 1;
129154972afSPatrick Mooney }
1304c87aefeSPatrick Mooney 
1314c87aefeSPatrick Mooney int
132154972afSPatrick Mooney bootrom_alloc(struct vmctx *ctx, size_t len, int prot, int flags,
133154972afSPatrick Mooney     char **region_out, uint64_t *gpa_out)
134154972afSPatrick Mooney {
135154972afSPatrick Mooney 	static const int bootrom_valid_flags = BOOTROM_ALLOC_TOP;
136154972afSPatrick Mooney 
137154972afSPatrick Mooney 	vm_paddr_t gpa;
138154972afSPatrick Mooney 	vm_ooffset_t segoff;
139154972afSPatrick Mooney 
140154972afSPatrick Mooney 	if (flags & ~bootrom_valid_flags) {
141154972afSPatrick Mooney 		warnx("%s: Invalid flags: %x", __func__,
142154972afSPatrick Mooney 		    flags & ~bootrom_valid_flags);
143154972afSPatrick Mooney 		return (EINVAL);
144154972afSPatrick Mooney 	}
145154972afSPatrick Mooney 	if (prot & ~_PROT_ALL) {
146154972afSPatrick Mooney 		warnx("%s: Invalid protection: %x", __func__,
147154972afSPatrick Mooney 		    prot & ~_PROT_ALL);
148154972afSPatrick Mooney 		return (EINVAL);
149154972afSPatrick Mooney 	}
150154972afSPatrick Mooney 
151154972afSPatrick Mooney 	if (len == 0 || len > BOOTROM_SIZE) {
152154972afSPatrick Mooney 		warnx("ROM size %zu is invalid", len);
153154972afSPatrick Mooney 		return (EINVAL);
154154972afSPatrick Mooney 	}
155154972afSPatrick Mooney 	if (len & PAGE_MASK) {
156154972afSPatrick Mooney 		warnx("ROM size %zu is not a multiple of the page size",
157154972afSPatrick Mooney 		    len);
158154972afSPatrick Mooney 		return (EINVAL);
159154972afSPatrick Mooney 	}
160154972afSPatrick Mooney 
161154972afSPatrick Mooney 	if (flags & BOOTROM_ALLOC_TOP) {
162154972afSPatrick Mooney 		gpa = (gpa_alloctop - len) + 1;
163154972afSPatrick Mooney 		if (gpa < gpa_allocbot) {
164154972afSPatrick Mooney 			warnx("No room for %zu ROM in bootrom region", len);
165154972afSPatrick Mooney 			return (ENOMEM);
166154972afSPatrick Mooney 		}
167154972afSPatrick Mooney 	} else {
168154972afSPatrick Mooney 		gpa = gpa_allocbot;
169154972afSPatrick Mooney 		if (gpa > (gpa_alloctop - len) + 1) {
170154972afSPatrick Mooney 			warnx("No room for %zu ROM in bootrom region", len);
171154972afSPatrick Mooney 			return (ENOMEM);
172154972afSPatrick Mooney 		}
173154972afSPatrick Mooney 	}
174154972afSPatrick Mooney 
175154972afSPatrick Mooney 	segoff = gpa - gpa_base;
176154972afSPatrick Mooney 	if (vm_mmap_memseg(ctx, gpa, VM_BOOTROM, segoff, len, prot) != 0) {
177154972afSPatrick Mooney 		int serrno = errno;
178154972afSPatrick Mooney 		warn("%s: vm_mmap_mapseg", __func__);
179154972afSPatrick Mooney 		return (serrno);
180154972afSPatrick Mooney 	}
181154972afSPatrick Mooney 
182154972afSPatrick Mooney 	if (flags & BOOTROM_ALLOC_TOP)
183154972afSPatrick Mooney 		gpa_alloctop = gpa - 1;
184154972afSPatrick Mooney 	else
185154972afSPatrick Mooney 		gpa_allocbot = gpa + len;
186154972afSPatrick Mooney 
187154972afSPatrick Mooney 	*region_out = romptr + segoff;
188154972afSPatrick Mooney 	if (gpa_out != NULL)
189154972afSPatrick Mooney 		*gpa_out = gpa;
190154972afSPatrick Mooney 	return (0);
191154972afSPatrick Mooney }
192154972afSPatrick Mooney 
193154972afSPatrick Mooney int
194154972afSPatrick Mooney bootrom_loadrom(struct vmctx *ctx, const char *romfile)
1954c87aefeSPatrick Mooney {
1964c87aefeSPatrick Mooney 	struct stat sbuf;
1974c87aefeSPatrick Mooney 	ssize_t rlen;
198*6dc98349SAndy Fiddaman 	off_t rom_size, var_size, total_size;
199*6dc98349SAndy Fiddaman 	char *ptr, *varfile;
200*6dc98349SAndy Fiddaman 	int fd, varfd, i, rv;
2014c87aefeSPatrick Mooney 
2024c87aefeSPatrick Mooney 	rv = -1;
203*6dc98349SAndy Fiddaman 	varfd = -1;
204*6dc98349SAndy Fiddaman 
205*6dc98349SAndy Fiddaman 	varfile = strdup(romfile);
206*6dc98349SAndy Fiddaman 	romfile = strsep(&varfile, ",");
207*6dc98349SAndy Fiddaman 
2084c87aefeSPatrick Mooney 	fd = open(romfile, O_RDONLY);
2094c87aefeSPatrick Mooney 	if (fd < 0) {
210154972afSPatrick Mooney 		EPRINTLN("Error opening bootrom \"%s\": %s",
2114c87aefeSPatrick Mooney 		    romfile, strerror(errno));
2124c87aefeSPatrick Mooney 		goto done;
2134c87aefeSPatrick Mooney 	}
2144c87aefeSPatrick Mooney 
215*6dc98349SAndy Fiddaman 	if (varfile != NULL) {
216*6dc98349SAndy Fiddaman 		varfd = open(varfile, O_RDWR);
217*6dc98349SAndy Fiddaman 		if (varfd < 0) {
218*6dc98349SAndy Fiddaman 			fprintf(stderr, "Error opening bootrom variable file "
219*6dc98349SAndy Fiddaman 			    "\"%s\": %s\n", varfile, strerror(errno));
220*6dc98349SAndy Fiddaman 			goto done;
221*6dc98349SAndy Fiddaman 		}
222*6dc98349SAndy Fiddaman 	}
223*6dc98349SAndy Fiddaman 
2244c87aefeSPatrick Mooney 	if (fstat(fd, &sbuf) < 0) {
225154972afSPatrick Mooney 		EPRINTLN("Could not fstat bootrom file \"%s\": %s",
2264c87aefeSPatrick Mooney 			romfile, strerror(errno));
2274c87aefeSPatrick Mooney 		goto done;
2284c87aefeSPatrick Mooney 	}
2294c87aefeSPatrick Mooney 
230*6dc98349SAndy Fiddaman 	rom_size = sbuf.st_size;
231*6dc98349SAndy Fiddaman 	if (varfd < 0) {
232*6dc98349SAndy Fiddaman 		var_size = 0;
233*6dc98349SAndy Fiddaman 	} else {
234*6dc98349SAndy Fiddaman 		if (fstat(varfd, &sbuf) < 0) {
235*6dc98349SAndy Fiddaman 			fprintf(stderr, "Could not fstat bootrom variable file \"%s\": %s\n",
236*6dc98349SAndy Fiddaman 				varfile, strerror(errno));
2374c87aefeSPatrick Mooney 			goto done;
238*6dc98349SAndy Fiddaman 		}
239*6dc98349SAndy Fiddaman 		var_size = sbuf.st_size;
240*6dc98349SAndy Fiddaman 	}
241*6dc98349SAndy Fiddaman 
242*6dc98349SAndy Fiddaman 	if (var_size > BOOTROM_SIZE ||
243*6dc98349SAndy Fiddaman 	    (var_size != 0 && var_size < PAGE_SIZE)) {
244*6dc98349SAndy Fiddaman 		fprintf(stderr, "Invalid bootrom variable size %ld\n",
245*6dc98349SAndy Fiddaman 		    var_size);
246*6dc98349SAndy Fiddaman 		goto done;
247*6dc98349SAndy Fiddaman 	}
248*6dc98349SAndy Fiddaman 
249*6dc98349SAndy Fiddaman 	total_size = rom_size + var_size;
250*6dc98349SAndy Fiddaman 
251*6dc98349SAndy Fiddaman 	if (total_size > BOOTROM_SIZE) {
252*6dc98349SAndy Fiddaman 		fprintf(stderr, "Invalid bootrom and variable aggregate size "
253*6dc98349SAndy Fiddaman 		    "%ld\n", total_size);
254*6dc98349SAndy Fiddaman 		goto done;
255*6dc98349SAndy Fiddaman 	}
256*6dc98349SAndy Fiddaman 
257*6dc98349SAndy Fiddaman 	/* Map the bootrom into the guest address space */
258*6dc98349SAndy Fiddaman 	if (bootrom_alloc(ctx, rom_size, PROT_READ | PROT_EXEC,
259*6dc98349SAndy Fiddaman 	    BOOTROM_ALLOC_TOP, &ptr, NULL) != 0) {
260*6dc98349SAndy Fiddaman 		goto done;
261*6dc98349SAndy Fiddaman 	}
2624c87aefeSPatrick Mooney 
2634c87aefeSPatrick Mooney 	/* Read 'romfile' into the guest address space */
264*6dc98349SAndy Fiddaman 	for (i = 0; i < rom_size / PAGE_SIZE; i++) {
2654c87aefeSPatrick Mooney 		rlen = read(fd, ptr + i * PAGE_SIZE, PAGE_SIZE);
2664c87aefeSPatrick Mooney 		if (rlen != PAGE_SIZE) {
267154972afSPatrick Mooney 			EPRINTLN("Incomplete read of page %d of bootrom "
268154972afSPatrick Mooney 			    "file %s: %ld bytes", i, romfile, rlen);
2694c87aefeSPatrick Mooney 			goto done;
2704c87aefeSPatrick Mooney 		}
2714c87aefeSPatrick Mooney 	}
272*6dc98349SAndy Fiddaman 
273*6dc98349SAndy Fiddaman 	if (varfd >= 0) {
274*6dc98349SAndy Fiddaman #ifdef __FreeBSD__
275*6dc98349SAndy Fiddaman 		var.mmap = mmap(NULL, var_size, PROT_READ | PROT_WRITE,
276*6dc98349SAndy Fiddaman 		    MAP_SHARED, varfd, 0);
277*6dc98349SAndy Fiddaman #else
278*6dc98349SAndy Fiddaman 		var.mmap = (uint8_t *)mmap(NULL, var_size,
279*6dc98349SAndy Fiddaman 		    PROT_READ | PROT_WRITE, MAP_SHARED, varfd, 0);
280*6dc98349SAndy Fiddaman #endif
281*6dc98349SAndy Fiddaman 		if (var.mmap == MAP_FAILED)
282*6dc98349SAndy Fiddaman 			goto done;
283*6dc98349SAndy Fiddaman 		var.size = var_size;
284*6dc98349SAndy Fiddaman 		var.gpa = (gpa_alloctop - var_size) + 1;
285*6dc98349SAndy Fiddaman 		gpa_alloctop = var.gpa - 1;
286*6dc98349SAndy Fiddaman 		rv = register_mem(&(struct mem_range){
287*6dc98349SAndy Fiddaman 		    .name = "bootrom variable",
288*6dc98349SAndy Fiddaman 		    .flags = MEM_F_RW,
289*6dc98349SAndy Fiddaman 		    .handler = bootrom_var_mem_handler,
290*6dc98349SAndy Fiddaman 		    .base = var.gpa,
291*6dc98349SAndy Fiddaman 		    .size = var.size,
292*6dc98349SAndy Fiddaman 		});
293*6dc98349SAndy Fiddaman 		if (rv != 0)
294*6dc98349SAndy Fiddaman 			goto done;
295*6dc98349SAndy Fiddaman 	}
296*6dc98349SAndy Fiddaman 
2974c87aefeSPatrick Mooney 	rv = 0;
2984c87aefeSPatrick Mooney done:
2994c87aefeSPatrick Mooney 	if (fd >= 0)
3004c87aefeSPatrick Mooney 		close(fd);
3014c87aefeSPatrick Mooney 	return (rv);
3024c87aefeSPatrick Mooney }
303