Changeset View
Changeset View
Standalone View
Standalone View
usr.sbin/bhyve/qemu_fwcfg.c
/*- | /*- | |||||||||||
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD | * SPDX-License-Identifier: BSD-2-Clause-FreeBSD | |||||||||||
* | * | |||||||||||
* Copyright (c) 2021 Beckhoff Automation GmbH & Co. KG | * Copyright (c) 2021 Beckhoff Automation GmbH & Co. KG | |||||||||||
* Author: Corvin Köhne <c.koehne@beckhoff.com> | * Author: Corvin Köhne <c.koehne@beckhoff.com> | |||||||||||
*/ | */ | |||||||||||
#include <sys/param.h> | #include <sys/param.h> | |||||||||||
#include <sys/endian.h> | #include <sys/endian.h> | |||||||||||
#include <sys/queue.h> | ||||||||||||
#include <sys/stat.h> | ||||||||||||
#include <machine/vmm.h> | #include <machine/vmm.h> | |||||||||||
#include <err.h> | #include <err.h> | |||||||||||
#include <errno.h> | #include <errno.h> | |||||||||||
#include <fcntl.h> | ||||||||||||
#include <stdio.h> | ||||||||||||
#include <stdlib.h> | #include <stdlib.h> | |||||||||||
#include <string.h> | #include <string.h> | |||||||||||
#include <unistd.h> | ||||||||||||
#include "acpi_device.h" | #include "acpi_device.h" | |||||||||||
#include "bhyverun.h" | #include "bhyverun.h" | |||||||||||
#include "inout.h" | #include "inout.h" | |||||||||||
#include "pci_lpc.h" | #include "pci_lpc.h" | |||||||||||
#include "qemu_fwcfg.h" | #include "qemu_fwcfg.h" | |||||||||||
#define QEMU_FWCFG_ACPI_DEVICE_NAME "FWCF" | #define QEMU_FWCFG_ACPI_DEVICE_NAME "FWCF" | |||||||||||
▲ Show 20 Lines • Show All 68 Lines • ▼ Show 20 Lines | struct qemu_fwcfg_softc { | |||||||||||
union qemu_fwcfg_selector selector; | union qemu_fwcfg_selector selector; | |||||||||||
struct qemu_fwcfg_item items[QEMU_FWCFG_MAX_ARCHS] | struct qemu_fwcfg_item items[QEMU_FWCFG_MAX_ARCHS] | |||||||||||
[QEMU_FWCFG_MAX_ENTRIES]; | [QEMU_FWCFG_MAX_ENTRIES]; | |||||||||||
struct qemu_fwcfg_directory *directory; | struct qemu_fwcfg_directory *directory; | |||||||||||
}; | }; | |||||||||||
static struct qemu_fwcfg_softc fwcfg_sc; | static struct qemu_fwcfg_softc fwcfg_sc; | |||||||||||
struct qemu_fwcfg_user_file { | ||||||||||||
STAILQ_ENTRY(qemu_fwcfg_user_file) chain; | ||||||||||||
uint8_t name[QEMU_FWCFG_MAX_NAME]; | ||||||||||||
uint32_t size; | ||||||||||||
void *data; | ||||||||||||
}; | ||||||||||||
static STAILQ_HEAD(qemu_fwcfg_user_file_list, | ||||||||||||
qemu_fwcfg_user_file) user_files = STAILQ_HEAD_INITIALIZER(user_files); | ||||||||||||
static int | static int | |||||||||||
qemu_fwcfg_selector_port_handler(struct vmctx *const ctx __unused, const int in, | qemu_fwcfg_selector_port_handler(struct vmctx *const ctx __unused, const int in, | |||||||||||
const int port __unused, const int bytes, uint32_t *const eax, | const int port __unused, const int bytes, uint32_t *const eax, | |||||||||||
void *const arg __unused) | void *const arg __unused) | |||||||||||
{ | { | |||||||||||
if (bytes != sizeof(uint16_t)) { | if (bytes != sizeof(uint16_t)) { | |||||||||||
warnx("%s: invalid size (%d) of IO port access", __func__, | warnx("%s: invalid size (%d) of IO port access", __func__, | |||||||||||
bytes); | bytes); | |||||||||||
▲ Show 20 Lines • Show All 270 Lines • ▼ Show 20 Lines | qemu_fwcfg_add_file(const char *name, const uint32_t size, void *const data) | |||||||||||
/* set new size for the fwcfg_file_directory */ | /* set new size for the fwcfg_file_directory */ | |||||||||||
fwcfg_sc.items[0][QEMU_FWCFG_INDEX_FILE_DIR].size = | fwcfg_sc.items[0][QEMU_FWCFG_INDEX_FILE_DIR].size = | |||||||||||
sizeof(struct qemu_fwcfg_directory) + | sizeof(struct qemu_fwcfg_directory) + | |||||||||||
count * sizeof(struct qemu_fwcfg_file); | count * sizeof(struct qemu_fwcfg_file); | |||||||||||
return (0); | return (0); | |||||||||||
} | } | |||||||||||
static int | ||||||||||||
qemu_fwcfg_add_user_files(void) | ||||||||||||
{ | ||||||||||||
const struct qemu_fwcfg_user_file *fwcfg_file; | ||||||||||||
int error; | ||||||||||||
STAILQ_FOREACH(fwcfg_file, &user_files, chain) { | ||||||||||||
markjUnsubmitted Done Inline Actions
markj: | ||||||||||||
error = qemu_fwcfg_add_file(fwcfg_file->name, fwcfg_file->size, | ||||||||||||
fwcfg_file->data); | ||||||||||||
if (error) | ||||||||||||
return (error); | ||||||||||||
} | ||||||||||||
return (0); | ||||||||||||
} | ||||||||||||
static const struct acpi_device_emul qemu_fwcfg_acpi_device_emul = { | static const struct acpi_device_emul qemu_fwcfg_acpi_device_emul = { | |||||||||||
.name = QEMU_FWCFG_ACPI_DEVICE_NAME, | .name = QEMU_FWCFG_ACPI_DEVICE_NAME, | |||||||||||
.hid = QEMU_FWCFG_ACPI_HARDWARE_ID, | .hid = QEMU_FWCFG_ACPI_HARDWARE_ID, | |||||||||||
}; | }; | |||||||||||
int | int | |||||||||||
qemu_fwcfg_init(struct vmctx *const ctx) | qemu_fwcfg_init(struct vmctx *const ctx) | |||||||||||
{ | { | |||||||||||
▲ Show 20 Lines • Show All 58 Lines • ▼ Show 20 Lines | if ((error = qemu_fwcfg_add_item_nb_cpus()) != 0) { | |||||||||||
goto done; | goto done; | |||||||||||
} | } | |||||||||||
if ((error = qemu_fwcfg_add_item_max_cpus()) != 0) { | if ((error = qemu_fwcfg_add_item_max_cpus()) != 0) { | |||||||||||
warnx("%s: Unable to add max_cpus item", __func__); | warnx("%s: Unable to add max_cpus item", __func__); | |||||||||||
goto done; | goto done; | |||||||||||
} | } | |||||||||||
if ((error = qemu_fwcfg_add_item_file_dir()) != 0) { | if ((error = qemu_fwcfg_add_item_file_dir()) != 0) { | |||||||||||
warnx("%s: Unable to add file_dir item", __func__); | warnx("%s: Unable to add file_dir item", __func__); | |||||||||||
} | ||||||||||||
/* add user defined fwcfg files */ | ||||||||||||
if ((error = qemu_fwcfg_add_user_files()) != 0) { | ||||||||||||
warnx("%s: Unable to add user files", __func__); | ||||||||||||
goto done; | goto done; | |||||||||||
} | } | |||||||||||
done: | done: | |||||||||||
if (error) { | if (error) { | |||||||||||
acpi_device_destroy(fwcfg_sc.acpi_dev); | acpi_device_destroy(fwcfg_sc.acpi_dev); | |||||||||||
} | } | |||||||||||
return (error); | return (error); | |||||||||||
} | ||||||||||||
static void | ||||||||||||
qemu_fwcfg_usage(const char *opt) | ||||||||||||
{ | ||||||||||||
warnx("Invalid fw_cfg option \"%s\"", opt); | ||||||||||||
warnx("-f [name=]<name>,(string|file)=<value>"); | ||||||||||||
} | ||||||||||||
/* | ||||||||||||
* Parses the cmdline argument for user defined fw_cfg items. The cmdline | ||||||||||||
* argument has the format: | ||||||||||||
* "-f [name=]<name>,(string|file)=<value>" | ||||||||||||
* | ||||||||||||
* E.g.: "-f opt/com.page/example,string=Hello" | ||||||||||||
*/ | ||||||||||||
int | ||||||||||||
qemu_fwcfg_parse_cmdline_arg(const char *opt) | ||||||||||||
{ | ||||||||||||
struct qemu_fwcfg_user_file *fwcfg_file; | ||||||||||||
struct stat sb; | ||||||||||||
const char *opt_ptr, *opt_end; | ||||||||||||
int fd; | ||||||||||||
fwcfg_file = malloc(sizeof(*fwcfg_file)); | ||||||||||||
if (fwcfg_file == NULL) { | ||||||||||||
warnx("Unable to allocate fw_cfg_user_file"); | ||||||||||||
return (ENOMEM); | ||||||||||||
} | ||||||||||||
/* get pointer to <name> */ | ||||||||||||
opt_ptr = opt; | ||||||||||||
/* If [name=] is specified, skip it */ | ||||||||||||
if (strncmp(opt_ptr, "name=", sizeof("name=") - 1) == 0) { | ||||||||||||
opt_ptr += sizeof("name=") - 1; | ||||||||||||
} | ||||||||||||
/* get the end of <name> */ | ||||||||||||
opt_end = strchr(opt_ptr, ','); | ||||||||||||
if (opt_end == NULL) { | ||||||||||||
qemu_fwcfg_usage(opt); | ||||||||||||
return (EINVAL); | ||||||||||||
} | ||||||||||||
/* check if <name> is too long */ | ||||||||||||
if (opt_end - opt_ptr >= QEMU_FWCFG_MAX_NAME) { | ||||||||||||
warnx("fw_cfg name too long: \"%s\"", opt); | ||||||||||||
return (EINVAL); | ||||||||||||
} | ||||||||||||
/* save <name> */ | ||||||||||||
strncpy(fwcfg_file->name, opt_ptr, opt_end - opt_ptr); | ||||||||||||
fwcfg_file->name[opt_end - opt_ptr] = '\0'; | ||||||||||||
/* set opt_ptr and opt_end to <value> */ | ||||||||||||
opt_ptr = opt_end + 1; | ||||||||||||
opt_end = opt_ptr + strlen(opt_ptr); | ||||||||||||
if (strncmp(opt_ptr, "string=", sizeof("string=") - 1) == 0) { | ||||||||||||
opt_ptr += sizeof("string=") - 1; | ||||||||||||
fwcfg_file->data = strdup(opt_ptr); | ||||||||||||
if (fwcfg_file->data == NULL) { | ||||||||||||
warnx("Can't duplicate fw_cfg_user_file string \"%s\"", | ||||||||||||
Not Done Inline Actions
markj: | ||||||||||||
opt_ptr); | ||||||||||||
return (ENOMEM); | ||||||||||||
} | ||||||||||||
fwcfg_file->size = strlen(opt_ptr) + 1; | ||||||||||||
} else if (strncmp(opt_ptr, "file=", sizeof("file=") - 1) == 0) { | ||||||||||||
opt_ptr += sizeof("file=") - 1; | ||||||||||||
fd = open(opt_ptr, O_RDONLY); | ||||||||||||
if (fd < 0) { | ||||||||||||
warn("Can't open fw_cfg_user_file file \"%s\"", | ||||||||||||
opt_ptr); | ||||||||||||
return (EINVAL); | ||||||||||||
} | ||||||||||||
if (fstat(fd, &sb) < 0) { | ||||||||||||
warn("Unable to get size of file \"%s\"", opt_ptr); | ||||||||||||
Not Done Inline ActionsThis can be warn() instead of warnx(). markj: This can be warn() instead of warnx(). | ||||||||||||
close(fd); | ||||||||||||
Done Inline ActionsWhy not just use fstat()? markj: Why not just use fstat()? | ||||||||||||
return (-1); | ||||||||||||
Not Done Inline Actionsclose() would generally clobber errno. So we either need to save/restore it, or just return -1. markj: close() would generally clobber errno. So we either need to save/restore it, or just return -1. | ||||||||||||
} | ||||||||||||
fwcfg_file->data = malloc(sb.st_size); | ||||||||||||
if (fwcfg_file->data == NULL) { | ||||||||||||
warnx( | ||||||||||||
"Can't allocate fw_cfg_user_file file \"%s\" (size: 0x%16lx)", | ||||||||||||
opt_ptr, sb.st_size); | ||||||||||||
close(fd); | ||||||||||||
return (ENOMEM); | ||||||||||||
} | ||||||||||||
Done Inline ActionsThis is missing handling of errors and short reads. markj: This is missing handling of errors and short reads. | ||||||||||||
fwcfg_file->size = read(fd, fwcfg_file->data, sb.st_size); | ||||||||||||
if ((ssize_t)fwcfg_file->size < 0) { | ||||||||||||
warn("Unable to read file \"%s\"", opt_ptr); | ||||||||||||
free(fwcfg_file->data); | ||||||||||||
close(fd); | ||||||||||||
return (-1); | ||||||||||||
Not Done Inline ActionsSame comment about errno. Also a short read is not necessarily an error. markj: Same comment about errno. Also a short read is not necessarily an error. | ||||||||||||
} else if (fwcfg_file->size < sb.st_size) { | ||||||||||||
warnx("Only read %u bytes of file \"%s\"", | ||||||||||||
fwcfg_file->size, opt_ptr); | ||||||||||||
} | ||||||||||||
Not Done Inline Actions
jhb: | ||||||||||||
close(fd); | ||||||||||||
} else { | ||||||||||||
qemu_fwcfg_usage(opt); | ||||||||||||
return (EINVAL); | ||||||||||||
} | ||||||||||||
STAILQ_INSERT_TAIL(&user_files, fwcfg_file, chain); | ||||||||||||
return (0); | ||||||||||||
} | } |