xref: /freebsd/usr.sbin/bhyve/basl.c (revision 2fb0f352)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2022 Beckhoff Automation GmbH & Co. KG
5  */
6 
7 #include <sys/param.h>
8 #include <sys/endian.h>
9 #include <sys/errno.h>
10 #include <sys/queue.h>
11 #include <sys/stat.h>
12 
13 #include <machine/vmm.h>
14 
15 #include <assert.h>
16 #include <err.h>
17 #include <libutil.h>
18 #include <stddef.h>
19 #include <stdio.h>
20 #include <vmmapi.h>
21 
22 #include "basl.h"
23 
24 struct basl_table_checksum {
25 	STAILQ_ENTRY(basl_table_checksum) chain;
26 	uint32_t off;
27 	uint32_t start;
28 	uint32_t len;
29 };
30 
31 struct basl_table_length {
32 	STAILQ_ENTRY(basl_table_length) chain;
33 	uint32_t off;
34 	uint8_t size;
35 };
36 
37 struct basl_table_pointer {
38 	STAILQ_ENTRY(basl_table_pointer) chain;
39 	uint8_t src_signature[ACPI_NAMESEG_SIZE];
40 	uint32_t off;
41 	uint8_t size;
42 };
43 
44 struct basl_table {
45 	STAILQ_ENTRY(basl_table) chain;
46 	struct vmctx *ctx;
47 	uint8_t fwcfg_name[QEMU_FWCFG_MAX_NAME];
48 	void *data;
49 	uint32_t len;
50 	uint32_t off;
51 	uint32_t alignment;
52 	STAILQ_HEAD(basl_table_checksum_list,
53 	    basl_table_checksum) checksums;
54 	STAILQ_HEAD(basl_table_length_list, basl_table_length) lengths;
55 	STAILQ_HEAD(basl_table_pointer_list, basl_table_pointer) pointers;
56 };
57 static STAILQ_HEAD(basl_table_list, basl_table) basl_tables = STAILQ_HEAD_INITIALIZER(
58     basl_tables);
59 
60 static __inline uint64_t
61 basl_le_dec(void *pp, size_t len)
62 {
63 	assert(len <= 8);
64 
65 	switch (len) {
66 	case 1:
67 		return ((uint8_t *)pp)[0];
68 	case 2:
69 		return le16dec(pp);
70 	case 4:
71 		return le32dec(pp);
72 	case 8:
73 		return le64dec(pp);
74 	}
75 
76 	return 0;
77 }
78 
79 static __inline void
80 basl_le_enc(void *pp, uint64_t val, size_t len)
81 {
82 	char buf[8];
83 
84 	assert(len <= 8);
85 
86 	le64enc(buf, val);
87 	memcpy(pp, buf, len);
88 }
89 
90 static int
91 basl_dump_table(const struct basl_table *const table, const bool mem)
92 {
93 	const ACPI_TABLE_HEADER *const header = table->data;
94 	const uint8_t *data;
95 
96 	if (!mem) {
97 		data = table->data;
98 	} else {
99 		data = vm_map_gpa(table->ctx, BHYVE_ACPI_BASE + table->off,
100 		    table->len);
101 		if (data == NULL) {
102 			return (ENOMEM);
103 		}
104 	}
105 
106 	printf("%.4s @ %8x (%s)\n", header->Signature,
107 	    BHYVE_ACPI_BASE + table->off, mem ? "Memory" : "FwCfg");
108 	hexdump(data, table->len, NULL, 0);
109 
110 	return (0);
111 }
112 
113 static int
114 basl_dump(const bool mem)
115 {
116 	struct basl_table *table;
117 
118 	STAILQ_FOREACH(table, &basl_tables, chain) {
119 		BASL_EXEC(basl_dump_table(table, mem));
120 	}
121 
122 	return (0);
123 }
124 
125 static int
126 basl_finish_install_guest_tables(struct basl_table *const table)
127 {
128 	void *gva;
129 
130 	/*
131 	 * Install ACPI tables directly in guest memory for use by guests which
132 	 * do not boot via EFI. EFI ROMs provide a pointer to the firmware
133 	 * generated ACPI tables instead, but it doesn't hurt to install the
134 	 * tables always.
135 	 */
136 	gva = vm_map_gpa(table->ctx, BHYVE_ACPI_BASE + table->off, table->len);
137 	if (gva == NULL) {
138 		warnx("%s: could not map gpa [ 0x%16lx, 0x%16lx ]", __func__,
139 		    (uint64_t)BHYVE_ACPI_BASE + table->off,
140 		    (uint64_t)BHYVE_ACPI_BASE + table->off + table->len);
141 		return (ENOMEM);
142 	}
143 	memcpy(gva, table->data, table->len);
144 
145 	return (0);
146 }
147 
148 static int
149 basl_finish_patch_checksums(struct basl_table *const table)
150 {
151 	struct basl_table_checksum *checksum;
152 
153 	STAILQ_FOREACH(checksum, &table->checksums, chain) {
154 		uint8_t *gva, *checksum_gva;
155 		uint64_t gpa;
156 		uint32_t len;
157 		uint8_t sum;
158 
159 		len = checksum->len;
160 		if (len == BASL_TABLE_CHECKSUM_LEN_FULL_TABLE) {
161 			len = table->len;
162 		}
163 
164 		assert(checksum->off < table->len);
165 		assert(checksum->start < table->len);
166 		assert(checksum->start + len <= table->len);
167 
168 		/*
169 		 * Install ACPI tables directly in guest memory for use by
170 		 * guests which do not boot via EFI. EFI ROMs provide a pointer
171 		 * to the firmware generated ACPI tables instead, but it doesn't
172 		 * hurt to install the tables always.
173 		 */
174 		gpa = BHYVE_ACPI_BASE + table->off + checksum->start;
175 		if ((gpa < BHYVE_ACPI_BASE) ||
176 		    (gpa < BHYVE_ACPI_BASE + table->off)) {
177 			warnx("%s: invalid gpa (off 0x%8x start 0x%8x)",
178 			    __func__, table->off, checksum->start);
179 			return (EFAULT);
180 		}
181 
182 		gva = vm_map_gpa(table->ctx, gpa, len);
183 		if (gva == NULL) {
184 			warnx("%s: could not map gpa [ 0x%16lx, 0x%16lx ]",
185 			    __func__, gpa, gpa + len);
186 			return (ENOMEM);
187 		}
188 
189 		checksum_gva = gva + checksum->off;
190 		if (checksum_gva < gva) {
191 			warnx("%s: invalid checksum offset 0x%8x", __func__,
192 			    checksum->off);
193 			return (EFAULT);
194 		}
195 
196 		sum = 0;
197 		for (uint32_t i = 0; i < len; ++i) {
198 			sum += *(gva + i);
199 		}
200 		*checksum_gva = -sum;
201 	}
202 
203 	return (0);
204 }
205 
206 static struct basl_table *
207 basl_get_table_by_signature(const uint8_t signature[ACPI_NAMESEG_SIZE])
208 {
209 	struct basl_table *table;
210 
211 	STAILQ_FOREACH(table, &basl_tables, chain) {
212 		const ACPI_TABLE_HEADER *const header =
213 		    (const ACPI_TABLE_HEADER *)table->data;
214 
215 		if (strncmp(header->Signature, signature,
216 			sizeof(header->Signature)) == 0) {
217 			return (table);
218 		}
219 	}
220 
221 	warnx("%s: %.4s not found", __func__, signature);
222 	return (NULL);
223 }
224 
225 static int
226 basl_finish_patch_pointers(struct basl_table *const table)
227 {
228 	struct basl_table_pointer *pointer;
229 
230 	STAILQ_FOREACH(pointer, &table->pointers, chain) {
231 		const struct basl_table *src_table;
232 		uint8_t *gva;
233 		uint64_t gpa, val;
234 
235 		assert(pointer->off < table->len);
236 		assert(pointer->off + pointer->size <= table->len);
237 
238 		src_table = basl_get_table_by_signature(pointer->src_signature);
239 		if (src_table == NULL) {
240 			warnx("%s: could not find ACPI table %.4s", __func__,
241 			    pointer->src_signature);
242 			return (EFAULT);
243 		}
244 
245 		/*
246 		 * Install ACPI tables directly in guest memory for use by
247 		 * guests which do not boot via EFI. EFI ROMs provide a pointer
248 		 * to the firmware generated ACPI tables instead, but it doesn't
249 		 * hurt to install the tables always.
250 		 */
251 		gpa = BHYVE_ACPI_BASE + table->off;
252 		if (gpa < BHYVE_ACPI_BASE) {
253 			warnx("%s: table offset of 0x%8x is too large",
254 			    __func__, table->off);
255 			return (EFAULT);
256 		}
257 
258 		gva = vm_map_gpa(table->ctx, gpa, table->len);
259 		if (gva == NULL) {
260 			warnx("%s: could not map gpa [ 0x%16lx, 0x%16lx ]",
261 			    __func__, gpa, gpa + table->len);
262 			return (ENOMEM);
263 		}
264 
265 		val = basl_le_dec(gva + pointer->off, pointer->size);
266 		val += BHYVE_ACPI_BASE + src_table->off;
267 		basl_le_enc(gva + pointer->off, val, pointer->size);
268 	}
269 
270 	return (0);
271 }
272 
273 static int
274 basl_finish_set_length(struct basl_table *const table)
275 {
276 	struct basl_table_length *length;
277 
278 	STAILQ_FOREACH(length, &table->lengths, chain) {
279 		assert(length->off < table->len);
280 		assert(length->off + length->size <= table->len);
281 
282 		basl_le_enc(table->data + length->off, table->len,
283 		    length->size);
284 	}
285 
286 	return (0);
287 }
288 
289 int
290 basl_finish(void)
291 {
292 	struct basl_table *table;
293 
294 	if (STAILQ_EMPTY(&basl_tables)) {
295 		warnx("%s: no ACPI tables found", __func__);
296 		return (EINVAL);
297 	}
298 
299 	/*
300 	 * We have to install all tables before we can patch them. Therefore,
301 	 * use two loops. The first one installs all tables and the second one
302 	 * patches them.
303 	 */
304 	STAILQ_FOREACH(table, &basl_tables, chain) {
305 		BASL_EXEC(basl_finish_set_length(table));
306 		BASL_EXEC(basl_finish_install_guest_tables(table));
307 	}
308 	STAILQ_FOREACH(table, &basl_tables, chain) {
309 		BASL_EXEC(basl_finish_patch_pointers(table));
310 
311 		/*
312 		 * Calculate the checksum as last step!
313 		 */
314 		BASL_EXEC(basl_finish_patch_checksums(table));
315 	}
316 
317 	return (0);
318 }
319 
320 int
321 basl_init(void)
322 {
323 	return (0);
324 }
325 
326 static int
327 basl_table_add_checksum(struct basl_table *const table, const uint32_t off,
328     const uint32_t start, const uint32_t len)
329 {
330 	struct basl_table_checksum *checksum;
331 
332 	checksum = calloc(1, sizeof(struct basl_table_checksum));
333 	if (checksum == NULL) {
334 		warnx("%s: failed to allocate checksum", __func__);
335 		return (ENOMEM);
336 	}
337 
338 	checksum->off = off;
339 	checksum->start = start;
340 	checksum->len = len;
341 
342 	STAILQ_INSERT_TAIL(&table->checksums, checksum, chain);
343 
344 	return (0);
345 }
346 
347 static int
348 basl_table_add_length(struct basl_table *const table, const uint32_t off,
349     const uint8_t size)
350 {
351 	struct basl_table_length *length;
352 
353 	length = calloc(1, sizeof(struct basl_table_length));
354 	if (length == NULL) {
355 		warnx("%s: failed to allocate length", __func__);
356 		return (ENOMEM);
357 	}
358 
359 	length->off = off;
360 	length->size = size;
361 
362 	STAILQ_INSERT_TAIL(&table->lengths, length, chain);
363 
364 	return (0);
365 }
366 
367 static int
368 basl_table_add_pointer(struct basl_table *const table,
369     const uint8_t src_signature[ACPI_NAMESEG_SIZE], const uint32_t off,
370     const uint8_t size)
371 {
372 	struct basl_table_pointer *pointer;
373 
374 	pointer = calloc(1, sizeof(struct basl_table_pointer));
375 	if (pointer == NULL) {
376 		warnx("%s: failed to allocate pointer", __func__);
377 		return (ENOMEM);
378 	}
379 
380 	memcpy(pointer->src_signature, src_signature,
381 	    sizeof(pointer->src_signature));
382 	pointer->off = off;
383 	pointer->size = size;
384 
385 	STAILQ_INSERT_TAIL(&table->pointers, pointer, chain);
386 
387 	return (0);
388 }
389 
390 int
391 basl_table_append_bytes(struct basl_table *const table, const void *const bytes,
392     const uint32_t len)
393 {
394 	void *end;
395 
396 	assert(table != NULL);
397 	assert(bytes != NULL);
398 
399 	if (table->len + len <= table->len) {
400 		warnx("%s: table too large (table->len 0x%8x len 0x%8x)",
401 		    __func__, table->len, len);
402 		return (EFAULT);
403 	}
404 
405 	table->data = reallocf(table->data, table->len + len);
406 	if (table->data == NULL) {
407 		warnx("%s: failed to realloc table to length 0x%8x", __func__,
408 		    table->len + len);
409 		table->len = 0;
410 		return (ENOMEM);
411 	}
412 
413 	end = (uint8_t *)table->data + table->len;
414 	table->len += len;
415 
416 	memcpy(end, bytes, len);
417 
418 	return (0);
419 }
420 
421 int
422 basl_table_append_checksum(struct basl_table *const table, const uint32_t start,
423     const uint32_t len)
424 {
425 	assert(table != NULL);
426 
427 	BASL_EXEC(basl_table_add_checksum(table, table->len, start, len));
428 	BASL_EXEC(basl_table_append_int(table, 0, 1));
429 
430 	return (0);
431 }
432 
433 int
434 basl_table_append_gas(struct basl_table *const table, const uint8_t space_id,
435     const uint8_t bit_width, const uint8_t bit_offset,
436     const uint8_t access_width, const uint64_t address)
437 {
438 	ACPI_GENERIC_ADDRESS gas_le = {
439 		.SpaceId = space_id,
440 		.BitWidth = bit_width,
441 		.BitOffset = bit_offset,
442 		.AccessWidth = access_width,
443 		.Address = htole64(address),
444 	};
445 
446 	return (basl_table_append_bytes(table, &gas_le, sizeof(gas_le)));
447 }
448 
449 int
450 basl_table_append_header(struct basl_table *const table,
451     const uint8_t signature[ACPI_NAMESEG_SIZE], const uint8_t revision,
452     const uint32_t oem_revision)
453 {
454 	ACPI_TABLE_HEADER header_le;
455 	/* + 1 is required for the null terminator */
456 	char oem_table_id[ACPI_OEM_TABLE_ID_SIZE + 1];
457 
458 	assert(table != NULL);
459 	assert(table->len == 0);
460 
461 	memcpy(header_le.Signature, signature, ACPI_NAMESEG_SIZE);
462 	header_le.Length = 0; /* patched by basl_finish */
463 	header_le.Revision = revision;
464 	header_le.Checksum = 0; /* patched by basl_finish */
465 	memcpy(header_le.OemId, "BHYVE ", ACPI_OEM_ID_SIZE);
466 	snprintf(oem_table_id, ACPI_OEM_TABLE_ID_SIZE, "BV%.4s  ", signature);
467 	memcpy(header_le.OemTableId, oem_table_id,
468 	    sizeof(header_le.OemTableId));
469 	header_le.OemRevision = htole32(oem_revision);
470 	memcpy(header_le.AslCompilerId, "BASL", ACPI_NAMESEG_SIZE);
471 	header_le.AslCompilerRevision = htole32(0x20220504);
472 
473 	BASL_EXEC(
474 	    basl_table_append_bytes(table, &header_le, sizeof(header_le)));
475 
476 	BASL_EXEC(basl_table_add_length(table,
477 	    offsetof(ACPI_TABLE_HEADER, Length), sizeof(header_le.Length)));
478 	BASL_EXEC(basl_table_add_checksum(table,
479 	    offsetof(ACPI_TABLE_HEADER, Checksum), 0,
480 	    BASL_TABLE_CHECKSUM_LEN_FULL_TABLE));
481 
482 	return (0);
483 }
484 
485 int
486 basl_table_append_int(struct basl_table *const table, const uint64_t val,
487     const uint8_t size)
488 {
489 	char buf[8];
490 
491 	assert(size <= sizeof(val));
492 
493 	basl_le_enc(buf, val, size);
494 	return (basl_table_append_bytes(table, buf, size));
495 }
496 
497 int
498 basl_table_append_length(struct basl_table *const table, const uint8_t size)
499 {
500 	assert(table != NULL);
501 	assert(size <= sizeof(table->len));
502 
503 	BASL_EXEC(basl_table_add_length(table, table->len, size));
504 	BASL_EXEC(basl_table_append_int(table, 0, size));
505 
506 	return (0);
507 }
508 
509 int
510 basl_table_append_pointer(struct basl_table *const table,
511     const uint8_t src_signature[ACPI_NAMESEG_SIZE], const uint8_t size)
512 {
513 	assert(table != NULL);
514 	assert(size == 4 || size == 8);
515 
516 	BASL_EXEC(basl_table_add_pointer(table, src_signature, table->len, size));
517 	BASL_EXEC(basl_table_append_int(table, 0, size));
518 
519 	return (0);
520 }
521 
522 int
523 basl_table_create(struct basl_table **const table, struct vmctx *ctx,
524     const uint8_t *const name, const uint32_t alignment,
525     const uint32_t off)
526 {
527 	struct basl_table *new_table;
528 
529 	assert(table != NULL);
530 
531 	new_table = calloc(1, sizeof(struct basl_table));
532 	if (new_table == NULL) {
533 		warnx("%s: failed to allocate table", __func__);
534 		return (ENOMEM);
535 	}
536 
537 	new_table->ctx = ctx;
538 
539 	snprintf(new_table->fwcfg_name, sizeof(new_table->fwcfg_name),
540 	    "etc/acpi/%s", name);
541 
542 	new_table->alignment = alignment;
543 	new_table->off = off;
544 
545 	STAILQ_INIT(&new_table->checksums);
546 	STAILQ_INIT(&new_table->lengths);
547 	STAILQ_INIT(&new_table->pointers);
548 
549 	STAILQ_INSERT_TAIL(&basl_tables, new_table, chain);
550 
551 	*table = new_table;
552 
553 	return (0);
554 }
555