xref: /freebsd/usr.sbin/bhyve/qemu_loader.c (revision 2e3507c25e42292b45a5482e116d278f5515d04d)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2022 Beckhoff Automation GmbH & Co. KG
5  * Author: Corvin Köhne <c.koehne@beckhoff.com>
6  */
7 
8 #include <sys/types.h>
9 #include <sys/param.h>
10 #include <sys/endian.h>
11 #include <sys/queue.h>
12 
13 #include <machine/vmm.h>
14 
15 #include <err.h>
16 #include <errno.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <vmmapi.h>
21 
22 #include "qemu_fwcfg.h"
23 #include "qemu_loader.h"
24 
25 struct qemu_loader_entry {
26 	uint32_t cmd_le;
27 	union {
28 		struct {
29 			uint8_t name[QEMU_FWCFG_MAX_NAME];
30 			uint32_t alignment_le;
31 			uint8_t zone;
32 		} alloc;
33 		struct {
34 			uint8_t dest_name[QEMU_FWCFG_MAX_NAME];
35 			uint8_t src_name[QEMU_FWCFG_MAX_NAME];
36 			uint32_t off_le;
37 			uint8_t size;
38 		} add_pointer;
39 		struct {
40 			uint8_t name[QEMU_FWCFG_MAX_NAME];
41 			uint32_t off_le;
42 			uint32_t start_le;
43 			uint32_t len_le;
44 		} add_checksum;
45 		struct {
46 			uint8_t dest_name[QEMU_FWCFG_MAX_NAME];
47 			uint8_t src_name[QEMU_FWCFG_MAX_NAME];
48 			uint32_t dest_off_le;
49 			uint32_t src_off_le;
50 			uint8_t size;
51 		} write_pointer;
52 
53 		/* padding */
54 		uint8_t pad[124];
55 	};
56 } __packed;
57 
58 enum qemu_loader_command {
59 	QEMU_LOADER_CMD_ALLOC = 0x1,
60 	QEMU_LOADER_CMD_ADD_POINTER = 0x2,
61 	QEMU_LOADER_CMD_ADD_CHECKSUM = 0x3,
62 	QEMU_LOADER_CMD_WRITE_POINTER = 0x4,
63 };
64 
65 struct qemu_loader_element {
66 	STAILQ_ENTRY(qemu_loader_element) chain;
67 	struct qemu_loader_entry entry;
68 };
69 
70 struct qemu_loader {
71 	uint8_t fwcfg_name[QEMU_FWCFG_MAX_NAME];
72 	STAILQ_HEAD(qemu_loader_list, qemu_loader_element) list;
73 };
74 
75 int
76 qemu_loader_alloc(struct qemu_loader *const loader, const uint8_t *name,
77     const uint32_t alignment, const enum qemu_loader_zone zone)
78 {
79 	struct qemu_loader_element *element;
80 
81 	if (strlen(name) >= QEMU_FWCFG_MAX_NAME)
82 		return (EINVAL);
83 
84 	element = calloc(1, sizeof(struct qemu_loader_element));
85 	if (element == NULL) {
86 		warnx("%s: failed to allocate command", __func__);
87 		return (ENOMEM);
88 	}
89 
90 	element->entry.cmd_le = htole32(QEMU_LOADER_CMD_ALLOC);
91 	strncpy(element->entry.alloc.name, name, QEMU_FWCFG_MAX_NAME);
92 	element->entry.alloc.alignment_le = htole32(alignment);
93 	element->entry.alloc.zone = zone;
94 
95 	/*
96 	 * The guest always works on copies of the fwcfg item, which where
97 	 * loaded into guest memory. Loading a fwcfg item is caused by ALLOC.
98 	 * For that reason, ALLOC should be scheduled in front of any other
99 	 * commands.
100 	 */
101 	STAILQ_INSERT_HEAD(&loader->list, element, chain);
102 
103 	return (0);
104 }
105 
106 int
107 qemu_loader_add_checksum(struct qemu_loader *const loader, const uint8_t *name,
108     const uint32_t off, const uint32_t start, const uint32_t len)
109 {
110 	struct qemu_loader_element *element;
111 
112 	if (strlen(name) >= QEMU_FWCFG_MAX_NAME)
113 		return (EINVAL);
114 
115 	element = calloc(1, sizeof(struct qemu_loader_element));
116 	if (element == NULL) {
117 		warnx("%s: failed to allocate command", __func__);
118 		return (ENOMEM);
119 	}
120 
121 	element->entry.cmd_le = htole32(QEMU_LOADER_CMD_ADD_CHECKSUM);
122 	strncpy(element->entry.add_checksum.name, name, QEMU_FWCFG_MAX_NAME);
123 	element->entry.add_checksum.off_le = htole32(off);
124 	element->entry.add_checksum.start_le = htole32(start);
125 	element->entry.add_checksum.len_le = htole32(len);
126 
127 	STAILQ_INSERT_TAIL(&loader->list, element, chain);
128 
129 	return (0);
130 }
131 
132 int
133 qemu_loader_add_pointer(struct qemu_loader *const loader,
134     const uint8_t *dest_name, const uint8_t *src_name, const uint32_t off,
135     const uint8_t size)
136 {
137 	struct qemu_loader_element *element;
138 
139 	if (strlen(dest_name) >= QEMU_FWCFG_MAX_NAME ||
140 	    strlen(src_name) >= QEMU_FWCFG_MAX_NAME)
141 		return (EINVAL);
142 
143 	element = calloc(1, sizeof(struct qemu_loader_element));
144 	if (element == NULL) {
145 		warnx("%s: failed to allocate command", __func__);
146 		return (ENOMEM);
147 	}
148 
149 	element->entry.cmd_le = htole32(QEMU_LOADER_CMD_ADD_POINTER);
150 	strncpy(element->entry.add_pointer.dest_name, dest_name,
151 	    QEMU_FWCFG_MAX_NAME);
152 	strncpy(element->entry.add_pointer.src_name, src_name,
153 	    QEMU_FWCFG_MAX_NAME);
154 	element->entry.add_pointer.off_le = htole32(off);
155 	element->entry.add_pointer.size = size;
156 
157 	STAILQ_INSERT_TAIL(&loader->list, element, chain);
158 
159 	return (0);
160 }
161 
162 int
163 qemu_loader_create(struct qemu_loader **const new_loader,
164     const uint8_t *fwcfg_name)
165 {
166 	struct qemu_loader *loader;
167 
168 	if (new_loader == NULL || strlen(fwcfg_name) >= QEMU_FWCFG_MAX_NAME) {
169 		return (EINVAL);
170 	}
171 
172 	loader = calloc(1, sizeof(struct qemu_loader));
173 	if (loader == NULL) {
174 		warnx("%s: failed to allocate loader", __func__);
175 		return (ENOMEM);
176 	}
177 
178 	strncpy(loader->fwcfg_name, fwcfg_name, QEMU_FWCFG_MAX_NAME);
179 	STAILQ_INIT(&loader->list);
180 
181 	*new_loader = loader;
182 
183 	return (0);
184 }
185 
186 static const uint8_t *
187 qemu_loader_get_zone_name(const enum qemu_loader_zone zone)
188 {
189 	switch (zone) {
190 	case QEMU_LOADER_ALLOC_HIGH:
191 		return ("HIGH");
192 	case QEMU_LOADER_ALLOC_FSEG:
193 		return ("FSEG");
194 	default:
195 		return ("Unknown");
196 	}
197 }
198 
199 static void __unused
200 qemu_loader_dump_entry(const struct qemu_loader_entry *const entry)
201 {
202 	switch (le32toh(entry->cmd_le)) {
203 	case QEMU_LOADER_CMD_ALLOC:
204 		printf("CMD_ALLOC\n\r");
205 		printf("  name     : %s\n\r", entry->alloc.name);
206 		printf("  alignment: %8x\n\r",
207 		    le32toh(entry->alloc.alignment_le));
208 		printf("  zone     : %s\n\r",
209 		    qemu_loader_get_zone_name(entry->alloc.zone));
210 		break;
211 	case QEMU_LOADER_CMD_ADD_POINTER:
212 		printf("CMD_ADD_POINTER\n\r");
213 		printf("  dest_name: %s\n\r", entry->add_pointer.dest_name);
214 		printf("  src_name : %s\n\r", entry->add_pointer.src_name);
215 		printf("  off      : %8x\n\r",
216 		    le32toh(entry->add_pointer.off_le));
217 		printf("  size     : %8x\n\r", entry->add_pointer.size);
218 		break;
219 	case QEMU_LOADER_CMD_ADD_CHECKSUM:
220 		printf("CMD_ADD_CHECKSUM\n\r");
221 		printf("  name     : %s\n\r", entry->add_checksum.name);
222 		printf("  off      : %8x\n\r",
223 		    le32toh(entry->add_checksum.off_le));
224 		printf("  start    : %8x\n\r",
225 		    le32toh(entry->add_checksum.start_le));
226 		printf("  length   : %8x\n\r",
227 		    le32toh(entry->add_checksum.len_le));
228 		break;
229 	case QEMU_LOADER_CMD_WRITE_POINTER:
230 		printf("CMD_WRITE_POINTER\n\r");
231 		printf("  dest_name: %s\n\r", entry->write_pointer.dest_name);
232 		printf("  src_name : %s\n\r", entry->write_pointer.src_name);
233 		printf("  dest_off : %8x\n\r",
234 		    le32toh(entry->write_pointer.dest_off_le));
235 		printf("  src_off  : %8x\n\r",
236 		    le32toh(entry->write_pointer.src_off_le));
237 		printf("  size     : %8x\n\r", entry->write_pointer.size);
238 		break;
239 	default:
240 		printf("UNKNOWN\n\r");
241 		break;
242 	}
243 }
244 
245 int
246 qemu_loader_finish(struct qemu_loader *const loader)
247 {
248 	struct qemu_loader_element *element;
249 	struct qemu_loader_entry *data;
250 	size_t len = 0;
251 
252 	STAILQ_FOREACH(element, &loader->list, chain) {
253 		len += sizeof(struct qemu_loader_entry);
254 	}
255 	if (len == 0) {
256 		warnx("%s: bios loader empty", __func__);
257 		return (EFAULT);
258 	}
259 
260 	data = calloc(1, len);
261 	if (data == NULL) {
262 		warnx("%s: failed to allocate fwcfg data", __func__);
263 		return (ENOMEM);
264 	}
265 
266 	int i = 0;
267 	STAILQ_FOREACH(element, &loader->list, chain) {
268 		memcpy(&data[i], &element->entry,
269 		    sizeof(struct qemu_loader_entry));
270 		++i;
271 	}
272 
273 	return (qemu_fwcfg_add_file(loader->fwcfg_name, len, data));
274 }
275