xref: /openbsd-src/usr.sbin/vmd/fw_cfg.c (revision 8550894424f8a4aa4aafb6cd57229dd6ed7cd9dd)
1 /*	$OpenBSD: fw_cfg.c,v 1.6 2022/12/26 23:50:20 dv Exp $	*/
2 /*
3  * Copyright (c) 2018 Claudio Jeker <claudio@openbsd.org>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 #include <sys/types.h>
18 #include <sys/uio.h>
19 #include <machine/biosvar.h>	/* bios_memmap_t */
20 #include <machine/vmmvar.h>
21 
22 #include <stdlib.h>
23 #include <string.h>
24 #include <unistd.h>
25 
26 #include "atomicio.h"
27 #include "vmd.h"
28 #include "vmm.h"
29 #include "fw_cfg.h"
30 
31 #define	FW_CFG_SIGNATURE	0x0000
32 #define	FW_CFG_ID		0x0001
33 #define	FW_CFG_NOGRAPHIC	0x0004
34 #define	FW_CFG_FILE_DIR		0x0019
35 #define	FW_CFG_FILE_FIRST	0x0020
36 
37 #define FW_CFG_DMA_SIGNATURE	0x51454d5520434647ULL /* QEMU CFG */
38 
39 struct fw_cfg_dma_access {
40 	uint32_t	control;
41 #define FW_CFG_DMA_ERROR	0x0001
42 #define FW_CFG_DMA_READ		0x0002
43 #define FW_CFG_DMA_SKIP		0x0004
44 #define FW_CFG_DMA_SELECT	0x0008
45 #define FW_CFG_DMA_WRITE	0x0010	/* not implemented */
46 	uint32_t	length;
47 	uint64_t	address;
48 };
49 
50 struct fw_cfg_file {
51 	uint32_t	size;
52 	uint16_t	selector;
53 	uint16_t	reserved;
54 	char		name[56];
55 };
56 
57 extern char *__progname;
58 
59 static struct fw_cfg_state {
60 	size_t offset;
61 	size_t size;
62 	uint8_t *data;
63 } fw_cfg_state;
64 
65 static uint64_t	fw_cfg_dma_addr;
66 
67 static bios_memmap_t e820[VMM_MAX_MEM_RANGES];
68 
69 static int	fw_cfg_select_file(uint16_t);
70 static void	fw_cfg_file_dir(void);
71 
72 void
73 fw_cfg_init(struct vmop_create_params *vmc)
74 {
75 	const char *bootorder = NULL;
76 	unsigned int sd = 0;
77 	size_t i, e820_len = 0;
78 
79 	/* Define e820 memory ranges. */
80 	memset(&e820, 0, sizeof(e820));
81 	for (i = 0; i < vmc->vmc_params.vcp_nmemranges; i++) {
82 		struct vm_mem_range *range = &vmc->vmc_params.vcp_memranges[i];
83 		bios_memmap_t *entry = &e820[i];
84 		entry->addr = range->vmr_gpa;
85 		entry->size = range->vmr_size;
86 		if (range->vmr_type == VM_MEM_RAM)
87 			entry->type = BIOS_MAP_FREE;
88 		else
89 			entry->type = BIOS_MAP_RES;
90 		e820_len += sizeof(bios_memmap_t);
91 	}
92 	fw_cfg_add_file("etc/e820", &e820, e820_len);
93 
94 	/* do not double print chars on serial port */
95 	fw_cfg_add_file("etc/screen-and-debug", &sd, sizeof(sd));
96 
97 	switch (vmc->vmc_bootdevice) {
98 	case VMBOOTDEV_DISK:
99 		bootorder = "/pci@i0cf8/*@3\nHALT";
100 		break;
101 	case VMBOOTDEV_CDROM:
102 		bootorder = "/pci@i0cf8/*@4/*@0/*@0,40000100\nHALT";
103 		break;
104 	case VMBOOTDEV_NET:
105 		/* XXX not yet */
106 		bootorder = "HALT";
107 		break;
108 	}
109 	if (bootorder)
110 		fw_cfg_add_file("bootorder", bootorder, strlen(bootorder) + 1);
111 }
112 
113 int
114 fw_cfg_dump(int fd)
115 {
116 	log_debug("%s: sending fw_cfg state", __func__);
117 	if (atomicio(vwrite, fd, &fw_cfg_dma_addr,
118 	    sizeof(fw_cfg_dma_addr)) != sizeof(fw_cfg_dma_addr)) {
119 		log_warnx("%s: error writing fw_cfg to fd", __func__);
120 		return -1;
121 	}
122 	if (atomicio(vwrite, fd, &fw_cfg_state.offset,
123 	    sizeof(fw_cfg_state.offset)) != sizeof(fw_cfg_state.offset)) {
124 		log_warnx("%s: error writing fw_cfg to fd", __func__);
125 		return -1;
126 	}
127 	if (atomicio(vwrite, fd, &fw_cfg_state.size,
128 	    sizeof(fw_cfg_state.size)) != sizeof(fw_cfg_state.size)) {
129 		log_warnx("%s: error writing fw_cfg to fd", __func__);
130 		return -1;
131 	}
132 	if (fw_cfg_state.size != 0)
133 		if (atomicio(vwrite, fd, fw_cfg_state.data,
134 		    fw_cfg_state.size) != fw_cfg_state.size) {
135 			log_warnx("%s: error writing fw_cfg to fd", __func__);
136 			return (-1);
137 		}
138 	return 0;
139 }
140 
141 int
142 fw_cfg_restore(int fd)
143 {
144 	log_debug("%s: receiving fw_cfg state", __func__);
145 	if (atomicio(read, fd, &fw_cfg_dma_addr,
146 	    sizeof(fw_cfg_dma_addr)) != sizeof(fw_cfg_dma_addr)) {
147 		log_warnx("%s: error reading fw_cfg from fd", __func__);
148 		return -1;
149 	}
150 	if (atomicio(read, fd, &fw_cfg_state.offset,
151 	    sizeof(fw_cfg_state.offset)) != sizeof(fw_cfg_state.offset)) {
152 		log_warnx("%s: error reading fw_cfg from fd", __func__);
153 		return -1;
154 	}
155 	if (atomicio(read, fd, &fw_cfg_state.size,
156 	    sizeof(fw_cfg_state.size)) != sizeof(fw_cfg_state.size)) {
157 		log_warnx("%s: error reading fw_cfg from fd", __func__);
158 		return -1;
159 	}
160 	fw_cfg_state.data = NULL;
161 	if (fw_cfg_state.size != 0) {
162 		if ((fw_cfg_state.data = malloc(fw_cfg_state.size)) == NULL)
163 			fatal("%s", __func__);
164 		if (atomicio(read, fd, fw_cfg_state.data,
165 		    fw_cfg_state.size) != fw_cfg_state.size) {
166 			log_warnx("%s: error reading fw_cfg from fd", __func__);
167 			return -1;
168 		}
169 	}
170 	return 0;
171 }
172 
173 static void
174 fw_cfg_reset_state(void)
175 {
176 	free(fw_cfg_state.data);
177 	fw_cfg_state.offset = 0;
178 	fw_cfg_state.size = 0;
179 	fw_cfg_state.data = NULL;
180 }
181 
182 static void
183 fw_cfg_set_state(void *data, size_t len)
184 {
185 	if ((fw_cfg_state.data = malloc(len)) == NULL) {
186 		log_warn("%s", __func__);
187 		return;
188 	}
189 	memcpy(fw_cfg_state.data, data, len);
190 	fw_cfg_state.size = len;
191 	fw_cfg_state.offset = 0;
192 }
193 
194 static void
195 fw_cfg_select(uint16_t selector)
196 {
197 	uint16_t one = 1;
198 	uint32_t id = htole32(0x3);
199 
200 	fw_cfg_reset_state();
201 	switch (selector) {
202 	case FW_CFG_SIGNATURE:
203 		fw_cfg_set_state("QEMU", 4);
204 		break;
205 	case FW_CFG_ID:
206 		fw_cfg_set_state(&id, sizeof(id));
207 		break;
208 	case FW_CFG_NOGRAPHIC:
209 		fw_cfg_set_state(&one, sizeof(one));
210 		break;
211 	case FW_CFG_FILE_DIR:
212 		fw_cfg_file_dir();
213 		break;
214 	default:
215 		if (!fw_cfg_select_file(selector))
216 			log_debug("%s: unhandled selector %x",
217 			    __func__, selector);
218 		break;
219 	}
220 }
221 
222 static void
223 fw_cfg_handle_dma(struct fw_cfg_dma_access *fw)
224 {
225 	uint32_t len = 0, control = fw->control;
226 
227 	fw->control = 0;
228 	if (control & FW_CFG_DMA_SELECT) {
229 		uint16_t selector = control >> 16;
230 		log_debug("%s: selector 0x%04x", __func__, selector);
231 		fw_cfg_select(selector);
232 	}
233 
234 	/* calculate correct length of operation */
235 	if (fw_cfg_state.offset < fw_cfg_state.size)
236 		len = fw_cfg_state.size - fw_cfg_state.offset;
237 	if (len > fw->length)
238 		len = fw->length;
239 
240 	if (control & FW_CFG_DMA_WRITE) {
241 		fw->control |= FW_CFG_DMA_ERROR;
242 	} else if (control & FW_CFG_DMA_READ) {
243 		if (write_mem(fw->address,
244 		    fw_cfg_state.data + fw_cfg_state.offset, len)) {
245 			log_warnx("%s: write_mem error", __func__);
246 			fw->control |= FW_CFG_DMA_ERROR;
247 		}
248 		/* clear rest of buffer */
249 		if (len < fw->length)
250 			if (write_mem(fw->address + len, NULL,
251 			    fw->length - len)) {
252 			log_warnx("%s: write_mem error", __func__);
253 			fw->control |= FW_CFG_DMA_ERROR;
254 		}
255 	}
256 	fw_cfg_state.offset += len;
257 
258 	if (fw_cfg_state.offset == fw_cfg_state.size)
259 		fw_cfg_reset_state();
260 }
261 
262 uint8_t
263 vcpu_exit_fw_cfg(struct vm_run_params *vrp)
264 {
265 	uint32_t data = 0;
266 	struct vm_exit *vei = vrp->vrp_exit;
267 
268 	get_input_data(vei, &data);
269 
270 	switch (vei->vei.vei_port) {
271 	case FW_CFG_IO_SELECT:
272 		if (vei->vei.vei_dir == VEI_DIR_IN) {
273 			log_warnx("%s: fw_cfg: read from selector port "
274 			    "unsupported", __progname);
275 			set_return_data(vei, 0);
276 			break;
277 		}
278 		log_debug("%s: selector 0x%04x", __func__, data);
279 		fw_cfg_select(data);
280 		break;
281 	case FW_CFG_IO_DATA:
282 		if (vei->vei.vei_dir == VEI_DIR_OUT) {
283 			log_debug("%s: fw_cfg: discarding data written to "
284 			    "data port", __progname);
285 			break;
286 		}
287 		/* fw_cfg only defines 1-byte reads via IO port */
288 		if (fw_cfg_state.offset < fw_cfg_state.size) {
289 			set_return_data(vei,
290 			    fw_cfg_state.data[fw_cfg_state.offset++]);
291 			if (fw_cfg_state.offset == fw_cfg_state.size)
292 				fw_cfg_reset_state();
293 		} else
294 			set_return_data(vei, 0);
295 		break;
296 	}
297 
298 	return 0xFF;
299 }
300 
301 uint8_t
302 vcpu_exit_fw_cfg_dma(struct vm_run_params *vrp)
303 {
304 	struct fw_cfg_dma_access fw_dma;
305 	uint32_t data = 0;
306 	struct vm_exit *vei = vrp->vrp_exit;
307 
308 	if (vei->vei.vei_size != 4) {
309 		log_debug("%s: fw_cfg_dma: discarding data written to "
310 		    "dma addr", __progname);
311 		if (vei->vei.vei_dir == VEI_DIR_OUT)
312 			fw_cfg_dma_addr = 0;
313 		return 0xFF;
314 	}
315 
316 	if (vei->vei.vei_dir == VEI_DIR_OUT) {
317 		get_input_data(vei, &data);
318 		switch (vei->vei.vei_port) {
319 		case FW_CFG_IO_DMA_ADDR_HIGH:
320 			fw_cfg_dma_addr = (uint64_t)be32toh(data) << 32;
321 			break;
322 		case FW_CFG_IO_DMA_ADDR_LOW:
323 			fw_cfg_dma_addr |= be32toh(data);
324 
325 			/* writing least significant half triggers operation */
326 			if (read_mem(fw_cfg_dma_addr, &fw_dma, sizeof(fw_dma)))
327 				break;
328 			/* adjust byteorder */
329 			fw_dma.control = be32toh(fw_dma.control);
330 			fw_dma.length = be32toh(fw_dma.length);
331 			fw_dma.address = be64toh(fw_dma.address);
332 
333 			fw_cfg_handle_dma(&fw_dma);
334 
335 			/* just write control byte back */
336 			data = be32toh(fw_dma.control);
337 			if (write_mem(fw_cfg_dma_addr, &data, sizeof(data)))
338 				break;
339 
340 			/* done, reset base address */
341 			fw_cfg_dma_addr = 0;
342 			break;
343 		}
344 	} else {
345 		uint64_t sig = htobe64(FW_CFG_DMA_SIGNATURE);
346 		switch (vei->vei.vei_port) {
347 		case FW_CFG_IO_DMA_ADDR_HIGH:
348 			set_return_data(vei, sig >> 32);
349 			break;
350 		case FW_CFG_IO_DMA_ADDR_LOW:
351 			set_return_data(vei, sig & 0xffffffff);
352 			break;
353 		}
354 	}
355 	return 0xFF;
356 }
357 
358 static uint16_t file_id = FW_CFG_FILE_FIRST;
359 
360 struct fw_cfg_file_entry {
361 	TAILQ_ENTRY(fw_cfg_file_entry)	entry;
362 	struct fw_cfg_file		file;
363 	void				*data;
364 };
365 
366 TAILQ_HEAD(, fw_cfg_file_entry) fw_cfg_files =
367 					TAILQ_HEAD_INITIALIZER(fw_cfg_files);
368 
369 static struct fw_cfg_file_entry *
370 fw_cfg_lookup_file(const char *name)
371 {
372 	struct fw_cfg_file_entry *f;
373 
374 	TAILQ_FOREACH(f, &fw_cfg_files, entry) {
375 		if (strcmp(name, f->file.name) == 0)
376 			return f;
377 	}
378 	return NULL;
379 }
380 
381 void
382 fw_cfg_add_file(const char *name, const void *data, size_t len)
383 {
384 	struct fw_cfg_file_entry *f;
385 
386 	if (fw_cfg_lookup_file(name))
387 		fatalx("%s: fw_cfg: file %s exists", __progname, name);
388 
389 	if ((f = calloc(sizeof(*f), 1)) == NULL)
390 		fatal("%s", __func__);
391 
392 	if ((f->data = malloc(len)) == NULL)
393 		fatal("%s", __func__);
394 
395 	if (strlcpy(f->file.name, name, sizeof(f->file.name)) >=
396 	    sizeof(f->file.name))
397 		fatalx("%s: fw_cfg: file name too long", __progname);
398 
399 	f->file.size = htobe32(len);
400 	f->file.selector = htobe16(file_id++);
401 	memcpy(f->data, data, len);
402 
403 	TAILQ_INSERT_TAIL(&fw_cfg_files, f, entry);
404 }
405 
406 static int
407 fw_cfg_select_file(uint16_t id)
408 {
409 	struct fw_cfg_file_entry *f;
410 
411 	id = htobe16(id);
412 	TAILQ_FOREACH(f, &fw_cfg_files, entry)
413 		if (f->file.selector == id) {
414 			size_t size = be32toh(f->file.size);
415 			fw_cfg_set_state(f->data, size);
416 			log_debug("%s: accessing file %s", __func__,
417 			    f->file.name);
418 			return 1;
419 		}
420 	return 0;
421 }
422 
423 static void
424 fw_cfg_file_dir(void)
425 {
426 	struct fw_cfg_file_entry *f;
427 	struct fw_cfg_file *fp;
428 	uint32_t count = 0;
429 	uint32_t *data;
430 	size_t size;
431 
432 	TAILQ_FOREACH(f, &fw_cfg_files, entry)
433 		count++;
434 
435 	size = sizeof(count) + count * sizeof(struct fw_cfg_file);
436 	if ((data = malloc(size)) == NULL)
437 		fatal("%s", __func__);
438 	*data = htobe32(count);
439 	fp = (struct fw_cfg_file *)(data + 1);
440 
441 	log_debug("%s: file directory with %d files", __func__, count);
442 	TAILQ_FOREACH(f, &fw_cfg_files, entry) {
443 		log_debug("  %6dB %04x %s", be32toh(f->file.size),
444 		    be16toh(f->file.selector), f->file.name);
445 		memcpy(fp, &f->file, sizeof(f->file));
446 		fp++;
447 	}
448 
449 	/* XXX should sort by name but SeaBIOS does not care */
450 
451 	fw_cfg_set_state(data, size);
452 }
453