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