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
25struct 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
58enum 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
65struct qemu_loader_element {
66	STAILQ_ENTRY(qemu_loader_element) chain;
67	struct qemu_loader_entry entry;
68};
69
70struct qemu_loader {
71	uint8_t fwcfg_name[QEMU_FWCFG_MAX_NAME];
72	STAILQ_HEAD(qemu_loader_list, qemu_loader_element) list;
73};
74
75int
76qemu_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
106int
107qemu_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
132int
133qemu_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
162int
163qemu_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
186static const uint8_t *
187qemu_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
199static void __unused
200qemu_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
245int
246qemu_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