xref: /openbsd-src/usr.sbin/vmd/fw_cfg.c (revision 65bbee46cad7861cd5a570f338df9e976422e3ab)
1*65bbee46Sjsg /*	$OpenBSD: fw_cfg.c,v 1.10 2024/09/26 01:45:13 jsg Exp $	*/
2622c1441Sclaudio /*
3622c1441Sclaudio  * Copyright (c) 2018 Claudio Jeker <claudio@openbsd.org>
4622c1441Sclaudio  *
5622c1441Sclaudio  * Permission to use, copy, modify, and distribute this software for any
6622c1441Sclaudio  * purpose with or without fee is hereby granted, provided that the above
7622c1441Sclaudio  * copyright notice and this permission notice appear in all copies.
8622c1441Sclaudio  *
9622c1441Sclaudio  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10622c1441Sclaudio  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11622c1441Sclaudio  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12622c1441Sclaudio  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13622c1441Sclaudio  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14622c1441Sclaudio  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15622c1441Sclaudio  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16622c1441Sclaudio  */
17622c1441Sclaudio #include <sys/types.h>
18cf08ffabSdv #include <machine/biosvar.h>	/* bios_memmap_t */
191e8eabf2Sdv #include <dev/pv/virtioreg.h>
20ba66f564Sdv #include <dev/vmm/vmm.h>
21622c1441Sclaudio 
22622c1441Sclaudio #include <stdlib.h>
23622c1441Sclaudio #include <string.h>
24622c1441Sclaudio #include <unistd.h>
25622c1441Sclaudio 
26622c1441Sclaudio #include "atomicio.h"
271e8eabf2Sdv #include "pci.h"
28622c1441Sclaudio #include "vmd.h"
29622c1441Sclaudio #include "vmm.h"
30622c1441Sclaudio #include "fw_cfg.h"
31622c1441Sclaudio 
32622c1441Sclaudio #define	FW_CFG_SIGNATURE	0x0000
33622c1441Sclaudio #define	FW_CFG_ID		0x0001
34622c1441Sclaudio #define	FW_CFG_NOGRAPHIC	0x0004
35622c1441Sclaudio #define	FW_CFG_FILE_DIR		0x0019
36622c1441Sclaudio #define	FW_CFG_FILE_FIRST	0x0020
37622c1441Sclaudio 
38622c1441Sclaudio #define FW_CFG_DMA_SIGNATURE	0x51454d5520434647ULL /* QEMU CFG */
39622c1441Sclaudio 
40622c1441Sclaudio struct fw_cfg_dma_access {
41622c1441Sclaudio 	uint32_t	control;
42622c1441Sclaudio #define FW_CFG_DMA_ERROR	0x0001
43622c1441Sclaudio #define FW_CFG_DMA_READ		0x0002
44622c1441Sclaudio #define FW_CFG_DMA_SKIP		0x0004
45622c1441Sclaudio #define FW_CFG_DMA_SELECT	0x0008
46622c1441Sclaudio #define FW_CFG_DMA_WRITE	0x0010	/* not implemented */
47622c1441Sclaudio 	uint32_t	length;
48622c1441Sclaudio 	uint64_t	address;
49622c1441Sclaudio };
50622c1441Sclaudio 
51622c1441Sclaudio struct fw_cfg_file {
52622c1441Sclaudio 	uint32_t	size;
53622c1441Sclaudio 	uint16_t	selector;
54622c1441Sclaudio 	uint16_t	reserved;
55622c1441Sclaudio 	char		name[56];
56622c1441Sclaudio };
57622c1441Sclaudio 
58622c1441Sclaudio extern char *__progname;
59622c1441Sclaudio 
60622c1441Sclaudio static struct fw_cfg_state {
61622c1441Sclaudio 	size_t offset;
62622c1441Sclaudio 	size_t size;
63622c1441Sclaudio 	uint8_t *data;
64622c1441Sclaudio } fw_cfg_state;
65622c1441Sclaudio 
66622c1441Sclaudio static uint64_t	fw_cfg_dma_addr;
67622c1441Sclaudio 
68cf08ffabSdv static bios_memmap_t e820[VMM_MAX_MEM_RANGES];
69cf08ffabSdv 
70622c1441Sclaudio static int	fw_cfg_select_file(uint16_t);
71622c1441Sclaudio static void	fw_cfg_file_dir(void);
72622c1441Sclaudio 
73622c1441Sclaudio void
74622c1441Sclaudio fw_cfg_init(struct vmop_create_params *vmc)
75622c1441Sclaudio {
76622c1441Sclaudio 	unsigned int sd = 0;
77cf08ffabSdv 	size_t i, e820_len = 0;
781e8eabf2Sdv 	char bootorder[64];
791e8eabf2Sdv 	const char *bootfmt;
801e8eabf2Sdv 	int bootidx = -1;
81cf08ffabSdv 
82cf08ffabSdv 	/* Define e820 memory ranges. */
83cf08ffabSdv 	memset(&e820, 0, sizeof(e820));
84cf08ffabSdv 	for (i = 0; i < vmc->vmc_params.vcp_nmemranges; i++) {
85cf08ffabSdv 		struct vm_mem_range *range = &vmc->vmc_params.vcp_memranges[i];
86cf08ffabSdv 		bios_memmap_t *entry = &e820[i];
87cf08ffabSdv 		entry->addr = range->vmr_gpa;
88cf08ffabSdv 		entry->size = range->vmr_size;
89cf08ffabSdv 		if (range->vmr_type == VM_MEM_RAM)
90cf08ffabSdv 			entry->type = BIOS_MAP_FREE;
91cf08ffabSdv 		else
92cf08ffabSdv 			entry->type = BIOS_MAP_RES;
93cf08ffabSdv 		e820_len += sizeof(bios_memmap_t);
94cf08ffabSdv 	}
95cf08ffabSdv 	fw_cfg_add_file("etc/e820", &e820, e820_len);
96622c1441Sclaudio 
97622c1441Sclaudio 	/* do not double print chars on serial port */
98622c1441Sclaudio 	fw_cfg_add_file("etc/screen-and-debug", &sd, sizeof(sd));
99622c1441Sclaudio 
100622c1441Sclaudio 	switch (vmc->vmc_bootdevice) {
101622c1441Sclaudio 	case VMBOOTDEV_DISK:
1021e8eabf2Sdv 		bootidx = pci_find_first_device(PCI_PRODUCT_VIRTIO_BLOCK);
1031e8eabf2Sdv 		bootfmt = "/pci@i0cf8/*@%d\nHALT";
104622c1441Sclaudio 		break;
105622c1441Sclaudio 	case VMBOOTDEV_CDROM:
1061e8eabf2Sdv 		bootidx = pci_find_first_device(PCI_PRODUCT_VIRTIO_SCSI);
1071e8eabf2Sdv 		bootfmt = "/pci@i0cf8/*@%d/*@0/*@0,40000100\nHALT";
108622c1441Sclaudio 		break;
109622c1441Sclaudio 	case VMBOOTDEV_NET:
110622c1441Sclaudio 		/* XXX not yet */
1111e8eabf2Sdv 		bootidx = pci_find_first_device(PCI_PRODUCT_VIRTIO_NETWORK);
1121e8eabf2Sdv 		bootfmt = "HALT";
113622c1441Sclaudio 		break;
114622c1441Sclaudio 	}
1151e8eabf2Sdv 	if (bootidx > -1) {
1161e8eabf2Sdv 		snprintf(bootorder, sizeof(bootorder), bootfmt, bootidx);
1171e8eabf2Sdv 		log_debug("%s: bootorder: %s", __func__, bootorder);
118622c1441Sclaudio 		fw_cfg_add_file("bootorder", bootorder, strlen(bootorder) + 1);
119622c1441Sclaudio 	}
1201e8eabf2Sdv }
121622c1441Sclaudio 
122622c1441Sclaudio int
123622c1441Sclaudio fw_cfg_dump(int fd)
124622c1441Sclaudio {
125622c1441Sclaudio 	log_debug("%s: sending fw_cfg state", __func__);
126622c1441Sclaudio 	if (atomicio(vwrite, fd, &fw_cfg_dma_addr,
127622c1441Sclaudio 	    sizeof(fw_cfg_dma_addr)) != sizeof(fw_cfg_dma_addr)) {
128622c1441Sclaudio 		log_warnx("%s: error writing fw_cfg to fd", __func__);
129622c1441Sclaudio 		return -1;
130622c1441Sclaudio 	}
131622c1441Sclaudio 	if (atomicio(vwrite, fd, &fw_cfg_state.offset,
132622c1441Sclaudio 	    sizeof(fw_cfg_state.offset)) != sizeof(fw_cfg_state.offset)) {
133622c1441Sclaudio 		log_warnx("%s: error writing fw_cfg to fd", __func__);
134622c1441Sclaudio 		return -1;
135622c1441Sclaudio 	}
136622c1441Sclaudio 	if (atomicio(vwrite, fd, &fw_cfg_state.size,
137622c1441Sclaudio 	    sizeof(fw_cfg_state.size)) != sizeof(fw_cfg_state.size)) {
138622c1441Sclaudio 		log_warnx("%s: error writing fw_cfg to fd", __func__);
139622c1441Sclaudio 		return -1;
140622c1441Sclaudio 	}
141622c1441Sclaudio 	if (fw_cfg_state.size != 0)
142622c1441Sclaudio 		if (atomicio(vwrite, fd, fw_cfg_state.data,
143622c1441Sclaudio 		    fw_cfg_state.size) != fw_cfg_state.size) {
144622c1441Sclaudio 			log_warnx("%s: error writing fw_cfg to fd", __func__);
145622c1441Sclaudio 			return (-1);
146622c1441Sclaudio 		}
147622c1441Sclaudio 	return 0;
148622c1441Sclaudio }
149622c1441Sclaudio 
150622c1441Sclaudio int
151622c1441Sclaudio fw_cfg_restore(int fd)
152622c1441Sclaudio {
153622c1441Sclaudio 	log_debug("%s: receiving fw_cfg state", __func__);
154622c1441Sclaudio 	if (atomicio(read, fd, &fw_cfg_dma_addr,
155622c1441Sclaudio 	    sizeof(fw_cfg_dma_addr)) != sizeof(fw_cfg_dma_addr)) {
156622c1441Sclaudio 		log_warnx("%s: error reading fw_cfg from fd", __func__);
157622c1441Sclaudio 		return -1;
158622c1441Sclaudio 	}
159622c1441Sclaudio 	if (atomicio(read, fd, &fw_cfg_state.offset,
160622c1441Sclaudio 	    sizeof(fw_cfg_state.offset)) != sizeof(fw_cfg_state.offset)) {
161622c1441Sclaudio 		log_warnx("%s: error reading fw_cfg from fd", __func__);
162622c1441Sclaudio 		return -1;
163622c1441Sclaudio 	}
164622c1441Sclaudio 	if (atomicio(read, fd, &fw_cfg_state.size,
165622c1441Sclaudio 	    sizeof(fw_cfg_state.size)) != sizeof(fw_cfg_state.size)) {
166622c1441Sclaudio 		log_warnx("%s: error reading fw_cfg from fd", __func__);
167622c1441Sclaudio 		return -1;
168622c1441Sclaudio 	}
169622c1441Sclaudio 	fw_cfg_state.data = NULL;
170622c1441Sclaudio 	if (fw_cfg_state.size != 0) {
171622c1441Sclaudio 		if ((fw_cfg_state.data = malloc(fw_cfg_state.size)) == NULL)
172622c1441Sclaudio 			fatal("%s", __func__);
173622c1441Sclaudio 		if (atomicio(read, fd, fw_cfg_state.data,
174622c1441Sclaudio 		    fw_cfg_state.size) != fw_cfg_state.size) {
175622c1441Sclaudio 			log_warnx("%s: error reading fw_cfg from fd", __func__);
176622c1441Sclaudio 			return -1;
177622c1441Sclaudio 		}
178622c1441Sclaudio 	}
179622c1441Sclaudio 	return 0;
180622c1441Sclaudio }
181622c1441Sclaudio 
182622c1441Sclaudio static void
183622c1441Sclaudio fw_cfg_reset_state(void)
184622c1441Sclaudio {
185622c1441Sclaudio 	free(fw_cfg_state.data);
186622c1441Sclaudio 	fw_cfg_state.offset = 0;
187622c1441Sclaudio 	fw_cfg_state.size = 0;
188622c1441Sclaudio 	fw_cfg_state.data = NULL;
189622c1441Sclaudio }
190622c1441Sclaudio 
191622c1441Sclaudio static void
192622c1441Sclaudio fw_cfg_set_state(void *data, size_t len)
193622c1441Sclaudio {
194622c1441Sclaudio 	if ((fw_cfg_state.data = malloc(len)) == NULL) {
195622c1441Sclaudio 		log_warn("%s", __func__);
196622c1441Sclaudio 		return;
197622c1441Sclaudio 	}
198622c1441Sclaudio 	memcpy(fw_cfg_state.data, data, len);
199622c1441Sclaudio 	fw_cfg_state.size = len;
200622c1441Sclaudio 	fw_cfg_state.offset = 0;
201622c1441Sclaudio }
202622c1441Sclaudio 
203622c1441Sclaudio static void
204622c1441Sclaudio fw_cfg_select(uint16_t selector)
205622c1441Sclaudio {
206622c1441Sclaudio 	uint16_t one = 1;
207622c1441Sclaudio 	uint32_t id = htole32(0x3);
208622c1441Sclaudio 
209622c1441Sclaudio 	fw_cfg_reset_state();
210622c1441Sclaudio 	switch (selector) {
211622c1441Sclaudio 	case FW_CFG_SIGNATURE:
212622c1441Sclaudio 		fw_cfg_set_state("QEMU", 4);
213622c1441Sclaudio 		break;
214622c1441Sclaudio 	case FW_CFG_ID:
215622c1441Sclaudio 		fw_cfg_set_state(&id, sizeof(id));
216622c1441Sclaudio 		break;
217622c1441Sclaudio 	case FW_CFG_NOGRAPHIC:
218622c1441Sclaudio 		fw_cfg_set_state(&one, sizeof(one));
219622c1441Sclaudio 		break;
220622c1441Sclaudio 	case FW_CFG_FILE_DIR:
221622c1441Sclaudio 		fw_cfg_file_dir();
222622c1441Sclaudio 		break;
223622c1441Sclaudio 	default:
224622c1441Sclaudio 		if (!fw_cfg_select_file(selector))
225622c1441Sclaudio 			log_debug("%s: unhandled selector %x",
226622c1441Sclaudio 			    __func__, selector);
227622c1441Sclaudio 		break;
228622c1441Sclaudio 	}
229622c1441Sclaudio }
230622c1441Sclaudio 
231622c1441Sclaudio static void
232622c1441Sclaudio fw_cfg_handle_dma(struct fw_cfg_dma_access *fw)
233622c1441Sclaudio {
234622c1441Sclaudio 	uint32_t len = 0, control = fw->control;
235622c1441Sclaudio 
236622c1441Sclaudio 	fw->control = 0;
237622c1441Sclaudio 	if (control & FW_CFG_DMA_SELECT) {
238622c1441Sclaudio 		uint16_t selector = control >> 16;
239622c1441Sclaudio 		log_debug("%s: selector 0x%04x", __func__, selector);
240622c1441Sclaudio 		fw_cfg_select(selector);
241622c1441Sclaudio 	}
242622c1441Sclaudio 
243622c1441Sclaudio 	/* calculate correct length of operation */
244622c1441Sclaudio 	if (fw_cfg_state.offset < fw_cfg_state.size)
245622c1441Sclaudio 		len = fw_cfg_state.size - fw_cfg_state.offset;
246622c1441Sclaudio 	if (len > fw->length)
247622c1441Sclaudio 		len = fw->length;
248622c1441Sclaudio 
249622c1441Sclaudio 	if (control & FW_CFG_DMA_WRITE) {
250622c1441Sclaudio 		fw->control |= FW_CFG_DMA_ERROR;
251622c1441Sclaudio 	} else if (control & FW_CFG_DMA_READ) {
252622c1441Sclaudio 		if (write_mem(fw->address,
253622c1441Sclaudio 		    fw_cfg_state.data + fw_cfg_state.offset, len)) {
254622c1441Sclaudio 			log_warnx("%s: write_mem error", __func__);
255622c1441Sclaudio 			fw->control |= FW_CFG_DMA_ERROR;
256622c1441Sclaudio 		}
257622c1441Sclaudio 		/* clear rest of buffer */
258622c1441Sclaudio 		if (len < fw->length)
259622c1441Sclaudio 			if (write_mem(fw->address + len, NULL,
260622c1441Sclaudio 			    fw->length - len)) {
261622c1441Sclaudio 			log_warnx("%s: write_mem error", __func__);
262622c1441Sclaudio 			fw->control |= FW_CFG_DMA_ERROR;
263622c1441Sclaudio 		}
264622c1441Sclaudio 	}
265622c1441Sclaudio 	fw_cfg_state.offset += len;
266622c1441Sclaudio 
267622c1441Sclaudio 	if (fw_cfg_state.offset == fw_cfg_state.size)
268622c1441Sclaudio 		fw_cfg_reset_state();
269622c1441Sclaudio }
270622c1441Sclaudio 
271622c1441Sclaudio uint8_t
272622c1441Sclaudio vcpu_exit_fw_cfg(struct vm_run_params *vrp)
273622c1441Sclaudio {
274622c1441Sclaudio 	uint32_t data = 0;
275622c1441Sclaudio 	struct vm_exit *vei = vrp->vrp_exit;
276622c1441Sclaudio 
277622c1441Sclaudio 	get_input_data(vei, &data);
278622c1441Sclaudio 
279622c1441Sclaudio 	switch (vei->vei.vei_port) {
280622c1441Sclaudio 	case FW_CFG_IO_SELECT:
281622c1441Sclaudio 		if (vei->vei.vei_dir == VEI_DIR_IN) {
282622c1441Sclaudio 			log_warnx("%s: fw_cfg: read from selector port "
283622c1441Sclaudio 			    "unsupported", __progname);
284622c1441Sclaudio 			set_return_data(vei, 0);
285622c1441Sclaudio 			break;
286622c1441Sclaudio 		}
287622c1441Sclaudio 		log_debug("%s: selector 0x%04x", __func__, data);
288622c1441Sclaudio 		fw_cfg_select(data);
289622c1441Sclaudio 		break;
290622c1441Sclaudio 	case FW_CFG_IO_DATA:
291622c1441Sclaudio 		if (vei->vei.vei_dir == VEI_DIR_OUT) {
292622c1441Sclaudio 			log_debug("%s: fw_cfg: discarding data written to "
293622c1441Sclaudio 			    "data port", __progname);
294622c1441Sclaudio 			break;
295622c1441Sclaudio 		}
296622c1441Sclaudio 		/* fw_cfg only defines 1-byte reads via IO port */
297622c1441Sclaudio 		if (fw_cfg_state.offset < fw_cfg_state.size) {
298622c1441Sclaudio 			set_return_data(vei,
299622c1441Sclaudio 			    fw_cfg_state.data[fw_cfg_state.offset++]);
300622c1441Sclaudio 			if (fw_cfg_state.offset == fw_cfg_state.size)
301622c1441Sclaudio 				fw_cfg_reset_state();
302622c1441Sclaudio 		} else
303622c1441Sclaudio 			set_return_data(vei, 0);
304622c1441Sclaudio 		break;
305622c1441Sclaudio 	}
306622c1441Sclaudio 
307622c1441Sclaudio 	return 0xFF;
308622c1441Sclaudio }
309622c1441Sclaudio 
310622c1441Sclaudio uint8_t
311622c1441Sclaudio vcpu_exit_fw_cfg_dma(struct vm_run_params *vrp)
312622c1441Sclaudio {
313622c1441Sclaudio 	struct fw_cfg_dma_access fw_dma;
314622c1441Sclaudio 	uint32_t data = 0;
315622c1441Sclaudio 	struct vm_exit *vei = vrp->vrp_exit;
316622c1441Sclaudio 
317622c1441Sclaudio 	if (vei->vei.vei_size != 4) {
318622c1441Sclaudio 		log_debug("%s: fw_cfg_dma: discarding data written to "
319622c1441Sclaudio 		    "dma addr", __progname);
320622c1441Sclaudio 		if (vei->vei.vei_dir == VEI_DIR_OUT)
321622c1441Sclaudio 			fw_cfg_dma_addr = 0;
322622c1441Sclaudio 		return 0xFF;
323622c1441Sclaudio 	}
324622c1441Sclaudio 
325622c1441Sclaudio 	if (vei->vei.vei_dir == VEI_DIR_OUT) {
326622c1441Sclaudio 		get_input_data(vei, &data);
327622c1441Sclaudio 		switch (vei->vei.vei_port) {
328622c1441Sclaudio 		case FW_CFG_IO_DMA_ADDR_HIGH:
329622c1441Sclaudio 			fw_cfg_dma_addr = (uint64_t)be32toh(data) << 32;
330622c1441Sclaudio 			break;
331622c1441Sclaudio 		case FW_CFG_IO_DMA_ADDR_LOW:
332622c1441Sclaudio 			fw_cfg_dma_addr |= be32toh(data);
333622c1441Sclaudio 
334622c1441Sclaudio 			/* writing least significant half triggers operation */
335622c1441Sclaudio 			if (read_mem(fw_cfg_dma_addr, &fw_dma, sizeof(fw_dma)))
336622c1441Sclaudio 				break;
337622c1441Sclaudio 			/* adjust byteorder */
338622c1441Sclaudio 			fw_dma.control = be32toh(fw_dma.control);
339622c1441Sclaudio 			fw_dma.length = be32toh(fw_dma.length);
340622c1441Sclaudio 			fw_dma.address = be64toh(fw_dma.address);
341622c1441Sclaudio 
342622c1441Sclaudio 			fw_cfg_handle_dma(&fw_dma);
343622c1441Sclaudio 
344622c1441Sclaudio 			/* just write control byte back */
345622c1441Sclaudio 			data = be32toh(fw_dma.control);
346622c1441Sclaudio 			if (write_mem(fw_cfg_dma_addr, &data, sizeof(data)))
347622c1441Sclaudio 				break;
348622c1441Sclaudio 
349622c1441Sclaudio 			/* done, reset base address */
350622c1441Sclaudio 			fw_cfg_dma_addr = 0;
351622c1441Sclaudio 			break;
352622c1441Sclaudio 		}
353622c1441Sclaudio 	} else {
354622c1441Sclaudio 		uint64_t sig = htobe64(FW_CFG_DMA_SIGNATURE);
355622c1441Sclaudio 		switch (vei->vei.vei_port) {
356622c1441Sclaudio 		case FW_CFG_IO_DMA_ADDR_HIGH:
357622c1441Sclaudio 			set_return_data(vei, sig >> 32);
358622c1441Sclaudio 			break;
359622c1441Sclaudio 		case FW_CFG_IO_DMA_ADDR_LOW:
360622c1441Sclaudio 			set_return_data(vei, sig & 0xffffffff);
361622c1441Sclaudio 			break;
362622c1441Sclaudio 		}
363622c1441Sclaudio 	}
364622c1441Sclaudio 	return 0xFF;
365622c1441Sclaudio }
366622c1441Sclaudio 
367622c1441Sclaudio static uint16_t file_id = FW_CFG_FILE_FIRST;
368622c1441Sclaudio 
369622c1441Sclaudio struct fw_cfg_file_entry {
370622c1441Sclaudio 	TAILQ_ENTRY(fw_cfg_file_entry)	entry;
371622c1441Sclaudio 	struct fw_cfg_file		file;
372622c1441Sclaudio 	void				*data;
373622c1441Sclaudio };
374622c1441Sclaudio 
375622c1441Sclaudio TAILQ_HEAD(, fw_cfg_file_entry) fw_cfg_files =
376622c1441Sclaudio 					TAILQ_HEAD_INITIALIZER(fw_cfg_files);
377622c1441Sclaudio 
378622c1441Sclaudio static struct fw_cfg_file_entry *
379622c1441Sclaudio fw_cfg_lookup_file(const char *name)
380622c1441Sclaudio {
381622c1441Sclaudio 	struct fw_cfg_file_entry *f;
382622c1441Sclaudio 
383622c1441Sclaudio 	TAILQ_FOREACH(f, &fw_cfg_files, entry) {
384622c1441Sclaudio 		if (strcmp(name, f->file.name) == 0)
385622c1441Sclaudio 			return f;
386622c1441Sclaudio 	}
387622c1441Sclaudio 	return NULL;
388622c1441Sclaudio }
389622c1441Sclaudio 
390622c1441Sclaudio void
391622c1441Sclaudio fw_cfg_add_file(const char *name, const void *data, size_t len)
392622c1441Sclaudio {
393622c1441Sclaudio 	struct fw_cfg_file_entry *f;
394622c1441Sclaudio 
395622c1441Sclaudio 	if (fw_cfg_lookup_file(name))
396622c1441Sclaudio 		fatalx("%s: fw_cfg: file %s exists", __progname, name);
397622c1441Sclaudio 
398160bdb59Sdv 	if ((f = calloc(1, sizeof(*f))) == NULL)
399622c1441Sclaudio 		fatal("%s", __func__);
400622c1441Sclaudio 
401622c1441Sclaudio 	if ((f->data = malloc(len)) == NULL)
402622c1441Sclaudio 		fatal("%s", __func__);
403622c1441Sclaudio 
404622c1441Sclaudio 	if (strlcpy(f->file.name, name, sizeof(f->file.name)) >=
405622c1441Sclaudio 	    sizeof(f->file.name))
406622c1441Sclaudio 		fatalx("%s: fw_cfg: file name too long", __progname);
407622c1441Sclaudio 
408622c1441Sclaudio 	f->file.size = htobe32(len);
409622c1441Sclaudio 	f->file.selector = htobe16(file_id++);
410622c1441Sclaudio 	memcpy(f->data, data, len);
411622c1441Sclaudio 
412622c1441Sclaudio 	TAILQ_INSERT_TAIL(&fw_cfg_files, f, entry);
413622c1441Sclaudio }
414622c1441Sclaudio 
415622c1441Sclaudio static int
416622c1441Sclaudio fw_cfg_select_file(uint16_t id)
417622c1441Sclaudio {
418622c1441Sclaudio 	struct fw_cfg_file_entry *f;
419622c1441Sclaudio 
420622c1441Sclaudio 	id = htobe16(id);
421622c1441Sclaudio 	TAILQ_FOREACH(f, &fw_cfg_files, entry)
422622c1441Sclaudio 		if (f->file.selector == id) {
423622c1441Sclaudio 			size_t size = be32toh(f->file.size);
424622c1441Sclaudio 			fw_cfg_set_state(f->data, size);
425622c1441Sclaudio 			log_debug("%s: accessing file %s", __func__,
426622c1441Sclaudio 			    f->file.name);
427622c1441Sclaudio 			return 1;
428622c1441Sclaudio 		}
429622c1441Sclaudio 	return 0;
430622c1441Sclaudio }
431622c1441Sclaudio 
432622c1441Sclaudio static void
433622c1441Sclaudio fw_cfg_file_dir(void)
434622c1441Sclaudio {
435622c1441Sclaudio 	struct fw_cfg_file_entry *f;
436622c1441Sclaudio 	struct fw_cfg_file *fp;
437622c1441Sclaudio 	uint32_t count = 0;
438622c1441Sclaudio 	uint32_t *data;
439622c1441Sclaudio 	size_t size;
440622c1441Sclaudio 
441622c1441Sclaudio 	TAILQ_FOREACH(f, &fw_cfg_files, entry)
442622c1441Sclaudio 		count++;
443622c1441Sclaudio 
444622c1441Sclaudio 	size = sizeof(count) + count * sizeof(struct fw_cfg_file);
445622c1441Sclaudio 	if ((data = malloc(size)) == NULL)
446622c1441Sclaudio 		fatal("%s", __func__);
447622c1441Sclaudio 	*data = htobe32(count);
448622c1441Sclaudio 	fp = (struct fw_cfg_file *)(data + 1);
449622c1441Sclaudio 
450622c1441Sclaudio 	log_debug("%s: file directory with %d files", __func__, count);
451622c1441Sclaudio 	TAILQ_FOREACH(f, &fw_cfg_files, entry) {
452622c1441Sclaudio 		log_debug("  %6dB %04x %s", be32toh(f->file.size),
453622c1441Sclaudio 		    be16toh(f->file.selector), f->file.name);
454622c1441Sclaudio 		memcpy(fp, &f->file, sizeof(f->file));
455622c1441Sclaudio 		fp++;
456622c1441Sclaudio 	}
457622c1441Sclaudio 
458622c1441Sclaudio 	/* XXX should sort by name but SeaBIOS does not care */
459622c1441Sclaudio 
460622c1441Sclaudio 	fw_cfg_set_state(data, size);
461622c1441Sclaudio }
462