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