diff --git a/usr.sbin/bhyve/bhyve.8 b/usr.sbin/bhyve/bhyve.8 --- a/usr.sbin/bhyve/bhyve.8 +++ b/usr.sbin/bhyve/bhyve.8 @@ -45,6 +45,15 @@ .Op Cm ,threads= Ar n .Oc .Sm on +.Oo Fl f +.Sm off +.Ar name Cm \&, +.Oo +.Cm string No | Cm file +.Oc +.Cm \&= Ar data +.Sm on +.Oc .Oo .Sm off .Fl G\~ @@ -145,6 +154,16 @@ .Nm to exit when a guest issues an access to an I/O port that is not emulated. This is intended for debug purposes. +.It Fl f Ar name Ns Cm \&, Ns Oo Cm string Ns No | Ns Cm file Ns Oc Ns Cm \&= Ns Ar data +Add a fw_cfg file +.Ar name +to the fw_cfg interface. +If a +.Cm string +is specified, the fw_cfg file contains the string as data. +If a +.Cm file +is specified, bhyve reads the file and adds the file content as fw_cfg data. .It Fl G Xo .Sm off .Oo Ar w Oc diff --git a/usr.sbin/bhyve/bhyverun.c b/usr.sbin/bhyve/bhyverun.c --- a/usr.sbin/bhyve/bhyverun.c +++ b/usr.sbin/bhyve/bhyverun.c @@ -1257,9 +1257,9 @@ progname = basename(argv[0]); #ifdef BHYVE_SNAPSHOT - optstr = "aehuwxACDHIPSWYk:o:p:G:c:s:m:l:K:U:r:"; + optstr = "aehuwxACDHIPSWYk:f:o:p:G:c:s:m:l:K:U:r:"; #else - optstr = "aehuwxACDHIPSWYk:o:p:G:c:s:m:l:K:U:"; + optstr = "aehuwxACDHIPSWYk:f:o:p:G:c:s:m:l:K:U:"; #endif while ((c = getopt(argc, argv, optstr)) != -1) { switch (c) { @@ -1287,6 +1287,11 @@ case 'C': set_config_bool("memory.guest_in_core", true); break; + case 'f': + if (qemu_fwcfg_parse_cmdline_arg(optarg) != 0) { + exit(1); + } + break; case 'G': parse_gdb_options(optarg); break; diff --git a/usr.sbin/bhyve/qemu_fwcfg.h b/usr.sbin/bhyve/qemu_fwcfg.h --- a/usr.sbin/bhyve/qemu_fwcfg.h +++ b/usr.sbin/bhyve/qemu_fwcfg.h @@ -21,3 +21,4 @@ int qemu_fwcfg_add_file(const uint8_t name[QEMU_FWCFG_MAX_NAME], const uint32_t size, void *const data); int qemu_fwcfg_init(struct vmctx *const ctx); +int qemu_fwcfg_parse_cmdline_arg(const char *opt); diff --git a/usr.sbin/bhyve/qemu_fwcfg.c b/usr.sbin/bhyve/qemu_fwcfg.c --- a/usr.sbin/bhyve/qemu_fwcfg.c +++ b/usr.sbin/bhyve/qemu_fwcfg.c @@ -11,13 +11,17 @@ #include #include #include +#include #include #include #include +#include +#include #include #include +#include #include "acpi_device.h" #include "inout.h" @@ -103,6 +107,15 @@ static struct qemu_fwcfg_softc 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 qemu_fwcfg_selector_port_handler(struct vmctx *const ctx __unused, const int in, const int port __unused, const int bytes, uint32_t *const eax, @@ -362,6 +375,20 @@ return (0); } +static int +qemu_fwcfg_add_user_files() +{ + const struct qemu_fwcfg_user_file *fwcfg_file; + STAILQ_FOREACH (fwcfg_file, &user_files, chain) { + const int error = qemu_fwcfg_add_file(fwcfg_file->name, + fwcfg_file->size, fwcfg_file->data); + if (error) + return (error); + } + + return (0); +} + int qemu_fwcfg_init(struct vmctx *const ctx) { @@ -423,6 +450,11 @@ } } + if ((error = qemu_fwcfg_add_user_files()) != 0) { + warnx("%s: Unable to add user files", __func__); + goto done; + } + done: if (error) { acpi_device_destroy(sc.acpi_dev); @@ -430,3 +462,101 @@ return (error); } + +static void +qemu_fwcfg_usage(const char *opt) +{ + warnx("Invalid fw_cfg option \"%s\"", opt); + warnx("-f [name=],(string|file)="); +} + +/* + * Parses the cmdline argument for user defined fw_cfg items. The cmdline + * argument has the format: + * "-f [name=],(string|file)=" + * + * E.g.: "-f opt/com.page/example,string=Hello" + */ +int +qemu_fwcfg_parse_cmdline_arg(const char *opt) +{ + struct qemu_fwcfg_user_file *const fwcfg_file = malloc(sizeof(*fwcfg_file)); + if (fwcfg_file == NULL) { + warnx("Unable to allocate fw_cfg_user_file"); + return (-ENOMEM); + } + + /* get pointer to */ + const char *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 */ + const char *opt_end = strchr(opt_ptr, ','); + if (opt_end == NULL) { + qemu_fwcfg_usage(opt); + return (-1); + } + + /* check if is too long */ + if (opt_end - opt_ptr > QEMU_FWCFG_MAX_NAME) { + warnx("fw_cfg name too long: \"%s\"", opt); + return (-1); + } + + /* save */ + strncpy(fwcfg_file->name, opt_ptr, opt_end - opt_ptr); + + /* set opt_ptr and opt_end to */ + 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\"", + 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; + + /* open file */ + const int fd = open(opt_ptr, O_RDONLY); + if (fd < 0) { + warnx("Can't open fw_cfg_user_file file \"%s\"", + opt_ptr); + return (-1); + } + + /* get file size */ + const uint64_t size = lseek(fd, 0, SEEK_END); + lseek(fd, 0, SEEK_SET); + + /* read file */ + fwcfg_file->data = malloc(size); + if (fwcfg_file->data == NULL) { + warnx( + "Can't allocate fw_cfg_user_file file \"%s\" (size: 0x%16lx)", + opt_ptr, size); + close(fd); + return (-ENOMEM); + } + fwcfg_file->size = read(fd, fwcfg_file->data, size); + + close(fd); + + } else { + qemu_fwcfg_usage(opt); + return (-1); + } + + STAILQ_INSERT_TAIL(&user_files, fwcfg_file, chain); + + return (0); +}