xref: /illumos-gate/usr/src/cmd/bhyve/qemu_loader.c (revision 32640292)
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 #ifndef	__FreeBSD__
26 /*
27  * This is better than the number of ifdef blocks that would be otherwise
28  * required throughout this code. Hopefully upstream will clear up the
29  * char* /uint8_t * confusion.
30  */
31 #define	strlen(x)	strlen((char *)(x))
32 #define	strncpy(p, q, n)	strncpy((char *)(p), (char *)(q), (n))
33 #endif
34 
35 struct qemu_loader_entry {
36 	uint32_t cmd_le;
37 	union {
38 		struct {
39 			uint8_t name[QEMU_FWCFG_MAX_NAME];
40 			uint32_t alignment_le;
41 			uint8_t zone;
42 		} alloc;
43 		struct {
44 			uint8_t dest_name[QEMU_FWCFG_MAX_NAME];
45 			uint8_t src_name[QEMU_FWCFG_MAX_NAME];
46 			uint32_t off_le;
47 			uint8_t size;
48 		} add_pointer;
49 		struct {
50 			uint8_t name[QEMU_FWCFG_MAX_NAME];
51 			uint32_t off_le;
52 			uint32_t start_le;
53 			uint32_t len_le;
54 		} add_checksum;
55 		struct {
56 			uint8_t dest_name[QEMU_FWCFG_MAX_NAME];
57 			uint8_t src_name[QEMU_FWCFG_MAX_NAME];
58 			uint32_t dest_off_le;
59 			uint32_t src_off_le;
60 			uint8_t size;
61 		} write_pointer;
62 
63 		/* padding */
64 		uint8_t pad[124];
65 	};
66 } __packed;
67 
68 enum qemu_loader_command {
69 	QEMU_LOADER_CMD_ALLOC = 0x1,
70 	QEMU_LOADER_CMD_ADD_POINTER = 0x2,
71 	QEMU_LOADER_CMD_ADD_CHECKSUM = 0x3,
72 	QEMU_LOADER_CMD_WRITE_POINTER = 0x4,
73 };
74 
75 struct qemu_loader_element {
76 	STAILQ_ENTRY(qemu_loader_element) chain;
77 	struct qemu_loader_entry entry;
78 };
79 
80 struct qemu_loader {
81 	uint8_t fwcfg_name[QEMU_FWCFG_MAX_NAME];
82 	STAILQ_HEAD(qemu_loader_list, qemu_loader_element) list;
83 };
84 
85 int
qemu_loader_alloc(struct qemu_loader * const loader,const uint8_t * name,const uint32_t alignment,const enum qemu_loader_zone zone)86 qemu_loader_alloc(struct qemu_loader *const loader, const uint8_t *name,
87     const uint32_t alignment, const enum qemu_loader_zone zone)
88 {
89 	struct qemu_loader_element *element;
90 
91 	if (strlen(name) >= QEMU_FWCFG_MAX_NAME)
92 		return (EINVAL);
93 
94 	element = calloc(1, sizeof(struct qemu_loader_element));
95 	if (element == NULL) {
96 		warnx("%s: failed to allocate command", __func__);
97 		return (ENOMEM);
98 	}
99 
100 	element->entry.cmd_le = htole32(QEMU_LOADER_CMD_ALLOC);
101 	strncpy(element->entry.alloc.name, name, QEMU_FWCFG_MAX_NAME);
102 	element->entry.alloc.alignment_le = htole32(alignment);
103 	element->entry.alloc.zone = zone;
104 
105 	/*
106 	 * The guest always works on copies of the fwcfg item, which where
107 	 * loaded into guest memory. Loading a fwcfg item is caused by ALLOC.
108 	 * For that reason, ALLOC should be scheduled in front of any other
109 	 * commands.
110 	 */
111 	STAILQ_INSERT_HEAD(&loader->list, element, chain);
112 
113 	return (0);
114 }
115 
116 int
qemu_loader_add_checksum(struct qemu_loader * const loader,const uint8_t * name,const uint32_t off,const uint32_t start,const uint32_t len)117 qemu_loader_add_checksum(struct qemu_loader *const loader, const uint8_t *name,
118     const uint32_t off, const uint32_t start, const uint32_t len)
119 {
120 	struct qemu_loader_element *element;
121 
122 	if (strlen(name) >= QEMU_FWCFG_MAX_NAME)
123 		return (EINVAL);
124 
125 	element = calloc(1, sizeof(struct qemu_loader_element));
126 	if (element == NULL) {
127 		warnx("%s: failed to allocate command", __func__);
128 		return (ENOMEM);
129 	}
130 
131 	element->entry.cmd_le = htole32(QEMU_LOADER_CMD_ADD_CHECKSUM);
132 	strncpy(element->entry.add_checksum.name, name, QEMU_FWCFG_MAX_NAME);
133 	element->entry.add_checksum.off_le = htole32(off);
134 	element->entry.add_checksum.start_le = htole32(start);
135 	element->entry.add_checksum.len_le = htole32(len);
136 
137 	STAILQ_INSERT_TAIL(&loader->list, element, chain);
138 
139 	return (0);
140 }
141 
142 int
qemu_loader_add_pointer(struct qemu_loader * const loader,const uint8_t * dest_name,const uint8_t * src_name,const uint32_t off,const uint8_t size)143 qemu_loader_add_pointer(struct qemu_loader *const loader,
144     const uint8_t *dest_name, const uint8_t *src_name, const uint32_t off,
145     const uint8_t size)
146 {
147 	struct qemu_loader_element *element;
148 
149 	if (strlen(dest_name) >= QEMU_FWCFG_MAX_NAME ||
150 	    strlen(src_name) >= QEMU_FWCFG_MAX_NAME)
151 		return (EINVAL);
152 
153 	element = calloc(1, sizeof(struct qemu_loader_element));
154 	if (element == NULL) {
155 		warnx("%s: failed to allocate command", __func__);
156 		return (ENOMEM);
157 	}
158 
159 	element->entry.cmd_le = htole32(QEMU_LOADER_CMD_ADD_POINTER);
160 	strncpy(element->entry.add_pointer.dest_name, dest_name,
161 	    QEMU_FWCFG_MAX_NAME);
162 	strncpy(element->entry.add_pointer.src_name, src_name,
163 	    QEMU_FWCFG_MAX_NAME);
164 	element->entry.add_pointer.off_le = htole32(off);
165 	element->entry.add_pointer.size = size;
166 
167 	STAILQ_INSERT_TAIL(&loader->list, element, chain);
168 
169 	return (0);
170 }
171 
172 int
qemu_loader_create(struct qemu_loader ** const new_loader,const uint8_t * fwcfg_name)173 qemu_loader_create(struct qemu_loader **const new_loader,
174     const uint8_t *fwcfg_name)
175 {
176 	struct qemu_loader *loader;
177 
178 	if (new_loader == NULL || strlen(fwcfg_name) >= QEMU_FWCFG_MAX_NAME) {
179 		return (EINVAL);
180 	}
181 
182 	loader = calloc(1, sizeof(struct qemu_loader));
183 	if (loader == NULL) {
184 		warnx("%s: failed to allocate loader", __func__);
185 		return (ENOMEM);
186 	}
187 
188 	strncpy(loader->fwcfg_name, fwcfg_name, QEMU_FWCFG_MAX_NAME);
189 	STAILQ_INIT(&loader->list);
190 
191 	*new_loader = loader;
192 
193 	return (0);
194 }
195 
196 static const uint8_t *
qemu_loader_get_zone_name(const enum qemu_loader_zone zone)197 qemu_loader_get_zone_name(const enum qemu_loader_zone zone)
198 {
199 #ifdef	__FreeBSD__
200 	switch (zone) {
201 	case QEMU_LOADER_ALLOC_HIGH:
202 		return ("HIGH");
203 	case QEMU_LOADER_ALLOC_FSEG:
204 		return ("FSEG");
205 	default:
206 		return ("Unknown");
207 	}
208 #else
209 	switch (zone) {
210 	case QEMU_LOADER_ALLOC_HIGH:
211 		return ((uint8_t *)"HIGH");
212 	case QEMU_LOADER_ALLOC_FSEG:
213 		return ((uint8_t *)"FSEG");
214 	default:
215 		return ((uint8_t *)"Unknown");
216 	}
217 #endif
218 }
219 
220 static void __unused
qemu_loader_dump_entry(const struct qemu_loader_entry * const entry)221 qemu_loader_dump_entry(const struct qemu_loader_entry *const entry)
222 {
223 	switch (le32toh(entry->cmd_le)) {
224 	case QEMU_LOADER_CMD_ALLOC:
225 		printf("CMD_ALLOC\n\r");
226 		printf("  name     : %s\n\r", entry->alloc.name);
227 		printf("  alignment: %8x\n\r",
228 		    le32toh(entry->alloc.alignment_le));
229 		printf("  zone     : %s\n\r",
230 		    qemu_loader_get_zone_name(entry->alloc.zone));
231 		break;
232 	case QEMU_LOADER_CMD_ADD_POINTER:
233 		printf("CMD_ADD_POINTER\n\r");
234 		printf("  dest_name: %s\n\r", entry->add_pointer.dest_name);
235 		printf("  src_name : %s\n\r", entry->add_pointer.src_name);
236 		printf("  off      : %8x\n\r",
237 		    le32toh(entry->add_pointer.off_le));
238 		printf("  size     : %8x\n\r", entry->add_pointer.size);
239 		break;
240 	case QEMU_LOADER_CMD_ADD_CHECKSUM:
241 		printf("CMD_ADD_CHECKSUM\n\r");
242 		printf("  name     : %s\n\r", entry->add_checksum.name);
243 		printf("  off      : %8x\n\r",
244 		    le32toh(entry->add_checksum.off_le));
245 		printf("  start    : %8x\n\r",
246 		    le32toh(entry->add_checksum.start_le));
247 		printf("  length   : %8x\n\r",
248 		    le32toh(entry->add_checksum.len_le));
249 		break;
250 	case QEMU_LOADER_CMD_WRITE_POINTER:
251 		printf("CMD_WRITE_POINTER\n\r");
252 		printf("  dest_name: %s\n\r", entry->write_pointer.dest_name);
253 		printf("  src_name : %s\n\r", entry->write_pointer.src_name);
254 		printf("  dest_off : %8x\n\r",
255 		    le32toh(entry->write_pointer.dest_off_le));
256 		printf("  src_off  : %8x\n\r",
257 		    le32toh(entry->write_pointer.src_off_le));
258 		printf("  size     : %8x\n\r", entry->write_pointer.size);
259 		break;
260 	default:
261 		printf("UNKNOWN\n\r");
262 		break;
263 	}
264 }
265 
266 int
qemu_loader_finish(struct qemu_loader * const loader)267 qemu_loader_finish(struct qemu_loader *const loader)
268 {
269 	struct qemu_loader_element *element;
270 	struct qemu_loader_entry *data;
271 	size_t len = 0;
272 
273 	STAILQ_FOREACH(element, &loader->list, chain) {
274 		len += sizeof(struct qemu_loader_entry);
275 	}
276 	if (len == 0) {
277 		warnx("%s: bios loader empty", __func__);
278 		return (EFAULT);
279 	}
280 
281 	data = calloc(1, len);
282 	if (data == NULL) {
283 		warnx("%s: failed to allocate fwcfg data", __func__);
284 		return (ENOMEM);
285 	}
286 
287 	int i = 0;
288 	STAILQ_FOREACH(element, &loader->list, chain) {
289 		memcpy(&data[i], &element->entry,
290 		    sizeof(struct qemu_loader_entry));
291 		++i;
292 	}
293 
294 #ifdef	__FreeBSD__
295 	return (qemu_fwcfg_add_file(loader->fwcfg_name, len, data));
296 #else
297 	return (qemu_fwcfg_add_file((const char *)loader->fwcfg_name,
298 	    len, data));
299 #endif
300 }
301