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
qemu_loader_alloc(struct qemu_loader * const loader,const uint8_t * name,const uint32_t alignment,const enum qemu_loader_zone zone)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
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)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
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)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
qemu_loader_create(struct qemu_loader ** const new_loader,const uint8_t * fwcfg_name)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 *
qemu_loader_get_zone_name(const enum qemu_loader_zone zone)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
qemu_loader_dump_entry(const struct qemu_loader_entry * const entry)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
qemu_loader_finish(struct qemu_loader * const loader)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