Index: etc/defaults/bhyve.conf =================================================================== --- /dev/null +++ etc/defaults/bhyve.conf @@ -0,0 +1,16 @@ +.include(priority=3, glob=true, try=true, prefix=true) "/etc/bhyve.conf.d/*.conf" +.include(priority=5, try=true) "/etc/bhyve.conf" +cpus = 1; +memory = 256MB; +console = "stdio"; +acpi = true; +vmexit_on_hlt = true; +vmexit_on_pause = true; +device { + slot = "0:0"; + type = "hostbridge"; +} +device { + slot = "1:0"; + type = "lpc"; +} Index: etc/mtree/BSD.root.dist =================================================================== --- etc/mtree/BSD.root.dist +++ etc/mtree/BSD.root.dist @@ -28,6 +28,8 @@ .. autofs .. + bhyve.conf.d + .. bluetooth .. casper Index: usr.sbin/bhyve/Makefile =================================================================== --- usr.sbin/bhyve/Makefile +++ usr.sbin/bhyve/Makefile @@ -20,6 +20,7 @@ bootrom.c \ console.c \ consport.c \ + config.c \ dbgport.c \ fwctl.c \ inout.c \ @@ -61,11 +62,12 @@ .PATH: ${BHYVE_SYSDIR}/sys/amd64/vmm SRCS+= vmm_instruction_emul.c -LIBADD= vmmapi md pthread z +LIBADD= vmmapi md pthread ucl z CFLAGS+= -I${BHYVE_SYSDIR}/sys/dev/e1000 CFLAGS+= -I${BHYVE_SYSDIR}/sys/dev/mii CFLAGS+= -I${BHYVE_SYSDIR}/sys/dev/usb/controller +CFLAGS+= -I${SRCTOP}/contrib/libucl/include WARNS?= 2 Index: usr.sbin/bhyve/bhyve.8 =================================================================== --- usr.sbin/bhyve/bhyve.8 +++ usr.sbin/bhyve/bhyve.8 @@ -34,6 +34,7 @@ .Nm .Op Fl abehuwxACHPSWY .Op Fl c Ar numcpus +.Op Fl f Ar bhyve.conf .Op Fl g Ar gdbport .Op Fl l Ar lpcdev Ns Op , Ns Ar conf .Op Fl m Ar memsize Ns Op Ar K|k|M|m|G|g|T|t @@ -41,6 +42,8 @@ .Op Fl s Ar slot,emulation Ns Op , Ns Ar conf .Op Fl U Ar uuid .Ar vmname +.Nm +.Fl f Ar bhyve.conf .Sh DESCRIPTION .Nm is a hypervisor that runs guest operating systems inside a @@ -84,6 +87,9 @@ .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 bhyve.conf +Read configuration from specified file. +Supersedes any flags specified before this flag. .It Fl g Ar gdbport For .Fx @@ -389,6 +395,11 @@ -l com1,/dev/nmdm0A \\ -A -H -P -m 8G .Ed +.Pp +Run a virtual machine described in a configuration file: +.Bd -literal -offset ident +bhyve -f guest.conf +.Ed .Sh SEE ALSO .Xr bhyve 4 , .Xr nmdm 4 , Index: usr.sbin/bhyve/bhyverun.h =================================================================== --- usr.sbin/bhyve/bhyverun.h +++ usr.sbin/bhyve/bhyverun.h @@ -33,9 +33,7 @@ #define VMEXIT_ABORT (-1) struct vmctx; -extern int guest_ncpus; -extern char *guest_uuid_str; -extern char *vmname; +extern struct config_entry vmconf; void *paddr_guest2host(struct vmctx *ctx, uintptr_t addr, size_t len); Index: usr.sbin/bhyve/bhyverun.c =================================================================== --- usr.sbin/bhyve/bhyverun.c +++ usr.sbin/bhyve/bhyverun.c @@ -44,6 +44,7 @@ #endif #include #include +#include #include #include #include @@ -62,6 +63,7 @@ #endif #include +#include "config.h" #include "bhyverun.h" #include "acpi.h" #include "atkbdc.h" @@ -80,27 +82,48 @@ #include "spinup_ap.h" #include "rtc.h" -#define GUEST_NIO_PORT 0x488 /* guest upcalls via i/o port */ +#include -#define MB (1024UL * 1024) -#define GB (1024UL * MB) +#define GUEST_NIO_PORT 0x488 /* guest upcalls via i/o port */ typedef int (*vmexit_handler_t)(struct vmctx *, struct vm_exit *, int *vcpu); extern int vmexit_task_switch(struct vmctx *, struct vm_exit *, int *vcpu); -char *vmname; - -int guest_ncpus; -char *guest_uuid_str; - -static int guest_vmexit_on_hlt, guest_vmexit_on_pause; -static int virtio_msix = 1; -static int x2apic_mode = 0; /* default is xAPIC */ +struct config_slot_entry sinit = { + .bnum = 0, + .snum = 0, + .fnum = 0, + .emul = NULL, + .config = NULL +}; -static int strictio; -static int strictmsr = 1; +struct config_entry cinit = { + .vmname = NULL, + .vcpus = 1, + .memsize = 256 * MB, + .console = NULL, + .host_base = NULL, + .pincpu = NULL, + .lpc_device = NULL, + .uuid = NULL, + .env = NULL, + .env_count = 0, + .slots = {NULL}, + .slot_count = 0, + .gdb_port = 0, + .acpi = 0, + .dump_memory = 0, + .vmexit_on_hlt = 0, + .vmexit_on_pause = 0, + .strictio = 0, + .rtc_localtime = 1, + .strictmsr = 1, + .virtio_msix = 1, + .x2apic_mode = 0, + .mptgen = 1 +}; -static int acpi; +struct config_entry vmconf; static char *progname; static const int BSP = 0; @@ -142,6 +165,7 @@ " -c: # cpus (default 1)\n" " -C: include guest memory in core file\n" " -e: exit on unhandled I/O access\n" + " -f: UCL config file to read\n" " -g: gdb port\n" " -h: help\n" " -H: vmexit from the guest on hlt\n" @@ -221,21 +245,21 @@ fbsdrun_vmexit_on_pause(void) { - return (guest_vmexit_on_pause); + return (vmconf.vmexit_on_pause); } int fbsdrun_vmexit_on_hlt(void) { - return (guest_vmexit_on_hlt); + return (vmconf.vmexit_on_hlt); } int fbsdrun_virtio_msix(void) { - return (virtio_msix); + return (vmconf.virtio_msix); } static void * @@ -337,7 +361,7 @@ return (error); } - error = emulate_inout(ctx, vcpu, vme, strictio); + error = emulate_inout(ctx, vcpu, vme, vmconf.strictio); if (error) { fprintf(stderr, "Unhandled %s%c 0x%04x at 0x%lx\n", in ? "in" : "out", @@ -361,7 +385,7 @@ if (error != 0) { fprintf(stderr, "rdmsr to register %#x on vcpu %d\n", vme->u.msr.code, *pvcpu); - if (strictmsr) { + if (vmconf.strictmsr) { vm_inject_gp(ctx, *pvcpu); return (VMEXIT_CONTINUE); } @@ -387,7 +411,7 @@ if (error != 0) { fprintf(stderr, "wrmsr to register %#x(%#lx) on vcpu %d\n", vme->u.msr.code, vme->u.msr.wval, *pvcpu); - if (strictmsr) { + if (vmconf.strictmsr) { vm_inject_gp(ctx, *pvcpu); return (VMEXIT_CONTINUE); } @@ -697,7 +721,7 @@ handler[VM_EXITCODE_PAUSE] = vmexit_pause; } - if (x2apic_mode) + if (vmconf.x2apic_mode) err = vm_set_x2apic_state(ctx, cpu, X2APIC_ENABLED); else err = vm_set_x2apic_state(ctx, cpu, X2APIC_DISABLED); @@ -792,44 +816,49 @@ int rtc_localtime; struct vmctx *ctx; uint64_t rip; - size_t memsize; + char *temp; + int pci_err; + size_t slot; + bool new_config; char *optstr; + memcpy(&vmconf, &cinit, sizeof(struct config_entry)); bvmcons = 0; progname = basename(argv[0]); - gdb_port = 0; - guest_ncpus = 1; - memsize = 256 * MB; - mptgen = 1; - rtc_localtime = 1; + temp = NULL; + new_config = false; memflags = 0; optstr = "abehuwxACHIPSWYp:g:c:s:m:l:U:"; while ((c = getopt(argc, argv, optstr)) != -1) { switch (c) { case 'a': - x2apic_mode = 0; + vmconf.x2apic_mode = 0; break; case 'A': - acpi = 1; + vmconf.acpi = 1; break; case 'b': bvmcons = 1; break; + case 'f': + new_config = true; + load_config(optarg, NULL, &vmconf); + break; case 'p': - if (pincpu_parse(optarg) != 0) { - errx(EX_USAGE, "invalid vcpu pinning " - "configuration '%s'", optarg); - } + if (pincpu_parse(optarg) != 0) { + errx(EX_USAGE, "invalid vcpu pinning " + "configuration '%s'", optarg); + } break; - case 'c': - guest_ncpus = atoi(optarg); + case 'c': + vmconf.vcpus = atoi(optarg); break; case 'C': memflags |= VM_MEM_F_INCORE; break; case 'g': - gdb_port = atoi(optarg); + vmconf.gdb_port = atoi(optarg); break; case 'l': if (lpc_device_parse(optarg) != 0) { @@ -846,12 +875,12 @@ memflags |= VM_MEM_F_WIRED; break; case 'm': - error = vm_parse_memsize(optarg, &memsize); + error = vm_parse_memsize(optarg, &vmconf.memsize); if (error) errx(EX_USAGE, "invalid memsize '%s'", optarg); break; case 'H': - guest_vmexit_on_hlt = 1; + vmconf.vmexit_on_hlt = 1; break; case 'I': /* @@ -863,31 +892,31 @@ */ break; case 'P': - guest_vmexit_on_pause = 1; + vmconf.vmexit_on_pause = 1; break; case 'e': - strictio = 1; + vmconf.strictio = 1; break; case 'u': - rtc_localtime = 0; + vmconf.rtc_localtime = 0; break; case 'U': - guest_uuid_str = optarg; + vmconf.uuid = optarg; break; case 'w': - strictmsr = 0; + vmconf.strictmsr = 0; break; case 'W': - virtio_msix = 0; + vmconf.virtio_msix = 0; break; case 'x': - x2apic_mode = 1; + vmconf.x2apic_mode = 1; break; case 'Y': - mptgen = 0; + vmconf.mptgen = 0; break; case 'h': - usage(0); + usage(0); default: usage(1); } @@ -895,28 +924,81 @@ argc -= optind; argv += optind; - if (argc != 1) + if (new_config) { + asprintf(&temp, "%zu", vmconf.memsize); + error = vm_parse_memsize(temp, &vmconf.memsize); + if (error) + errx(EX_USAGE, "invalid memsize '%s'", optarg); + free(temp); + + asprintf(&temp, "com1,%s", vmconf.console); + if (lpc_device_parse(temp) != 0) { + errx(EX_USAGE, "invalid lpc device " + "configuration '%s'", temp); + } + free(temp); + + if (vmconf.pincpu && pincpu_parse(vmconf.pincpu) != 0) { + errx(EX_USAGE, "invalid vcpu pinning " + "configuration '%s'", vmconf.pincpu); + } + + for (slot = 0; slot < vmconf.slot_count; slot++) { + if (strcasecmp(vmconf.slots[slot]->emul, "virtio-blk") == 0 || + strcasecmp(vmconf.slots[slot]->emul, "ahci-hd") == 0) { + asprintf(&vmconf.slots[slot]->config, "%s%s%s%s", + vmconf.slots[slot]->disk.path, + (vmconf.slots[slot]->disk.nocache ? ",nocache" : ""), + (vmconf.slots[slot]->disk.sync ? ",sync" : ""), + (vmconf.slots[slot]->disk.ro ? ",ro" : "")); + } else if (strcasecmp(vmconf.slots[slot]->emul, "ahci-cd") == 0) { + asprintf(&vmconf.slots[slot]->config, "%s%s%s%s", + vmconf.slots[slot]->disk.path, + (vmconf.slots[slot]->disk.nocache ? ",nocache" : ""), + (vmconf.slots[slot]->disk.sync ? ",sync" : ""), + ",ro"); + } else if (strcasecmp(vmconf.slots[slot]->emul, "virtio-net") == 0) { + asprintf(&vmconf.slots[slot]->config, "%s%s%s", + vmconf.slots[slot]->iface.name, + (vmconf.slots[slot]->iface.mac != NULL ? ",mac=" : ""), + (vmconf.slots[slot]->iface.mac != NULL ? vmconf.slots[slot]->iface.mac : "")); + } + pci_err = pci_create_slot(vmconf.slots[slot]->bnum, + vmconf.slots[slot]->snum, + vmconf.slots[slot]->fnum, + vmconf.slots[slot]->emul, + vmconf.slots[slot]->config); + if (pci_err != 0) + errx(1, "Failed to parse PCI configuration"); + } + } + + if (!vmconf.vmname && argc > 1) + vmconf.vmname = argv[1]; + + if (!vmconf.vmname) usage(1); vmname = argv[0]; ctx = do_open(vmname); - if (guest_ncpus < 1) { - fprintf(stderr, "Invalid guest vCPUs (%d)\n", guest_ncpus); + if (vmconf.vcpus < 1) { + fprintf(stderr, "Invalid guest vCPUs (%d)\n", vmconf.vcpus); exit(1); } max_vcpus = num_vcpus_allowed(ctx); - if (guest_ncpus > max_vcpus) { + if (vmconf.vcpus > max_vcpus) { fprintf(stderr, "%d vCPUs requested but only %d available\n", - guest_ncpus, max_vcpus); + vmconf.vcpus, max_vcpus); exit(1); } fbsdrun_set_capabilities(ctx, BSP); vm_set_memflags(ctx, memflags); - err = vm_setup_memory(ctx, memsize, VM_MMAP_ALL); + + err = vm_setup_memory(ctx, vmconf.memsize, VM_MMAP_ALL); if (err) { fprintf(stderr, "Unable to setup memory (%d)\n", errno); exit(1); @@ -934,7 +1016,7 @@ pci_irq_init(ctx); ioapic_init(ctx); - rtc_init(ctx, rtc_localtime); + rtc_init(ctx, vmconf.rtc_localtime); sci_init(ctx); /* @@ -943,8 +1025,8 @@ if (init_pci(ctx) != 0) exit(1); - if (gdb_port != 0) - init_dbgport(gdb_port); + if (vmconf.gdb_port != 0) + init_dbgport(vmconf.gdb_port); if (bvmcons) init_bvmcons(); @@ -965,8 +1047,8 @@ /* * build the guest tables, MP etc. */ - if (mptgen) { - error = mptable_build(ctx, guest_ncpus); + if (vmconf.mptgen) { + error = mptable_build(ctx, vmconf.vcpus); if (error) exit(1); } @@ -974,8 +1056,8 @@ error = smbios_build(ctx); assert(error == 0); - if (acpi) { - error = acpi_build(ctx, guest_ncpus); + if (vmconf.acpi) { + error = acpi_build(ctx, vmconf.vcpus); assert(error == 0); } @@ -995,8 +1077,8 @@ /* * Change the proc title to include the VM name. */ - setproctitle("%s", vmname); - + setproctitle("%s", vmconf.vmname); + /* * Add CPU 0 */ Index: usr.sbin/bhyve/config.h =================================================================== --- /dev/null +++ usr.sbin/bhyve/config.h @@ -0,0 +1,95 @@ +/*- + * Copyright (c) 2015 Marcelo Araujo + * Copyright (c) 2015 Allan Jude + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include + +#include + +#define NDISKS 32 +#define MB (1024UL * 1024) +#define GB (1024UL * MB) + +struct config_iface_entry { + char *name; + char *mac; +}; + +struct config_disk_entry { + char *path; + bool boot; + bool nocache; + bool sync; + bool ro; +}; + +struct config_slot_entry { + int bnum; + int snum; + int fnum; + char *emul; + char *config; + union { + struct config_iface_entry iface; + struct config_disk_entry disk; + }; +}; + +struct config_entry { + char *vmname; + int vcpus; + size_t memsize; + char *console; + char *host_base; + char *pincpu; + char *lpc_device; + char *uuid; + char **env; + size_t env_count; + struct config_slot_entry *slots[255]; + size_t slot_count; + int gdb_port; + bool acpi; + bool dump_memory; + bool vmexit_on_hlt; + bool vmexit_on_pause; + bool strictio; + bool rtc_localtime; + bool strictmsr; + bool virtio_msix; + bool x2apic_mode; + bool mptgen; +}; + +extern struct config_slot_entry sinit; +extern struct config_entry cinit; + +int check_config(struct config_entry *config); +void load_config(char *in_name, char *conf_name, struct config_entry *c); +void parse_conf(char *conf_name, struct config_entry *c, struct ucl_parser *p); +bool parse_pci_slot(const char *str, int *bnum, int *snum, int *fnum); +char* parse_string_safe(const ucl_object_t *obj, char *path); Index: usr.sbin/bhyve/config.c =================================================================== --- /dev/null +++ usr.sbin/bhyve/config.c @@ -0,0 +1,415 @@ +/*- + * Copyright (c) 2015 Marcelo Araujo + * Copyright (c) 2015 Allan Jude + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config.h" + +/* + * Load the config file. + */ +void +load_config(char *in_name, char *conf_name, struct config_entry *c) +{ + struct ucl_parser *p = NULL; + + p = ucl_parser_new(UCL_PARSER_KEY_LOWERCASE | + UCL_PARSER_NO_IMPLICIT_ARRAYS); + if (p == NULL) + errx(1, "Could not allocate ucl parser"); + + if (!ucl_parser_add_file(p, "/etc/defaults/bhyve.conf")) { + if (errno != ENOENT) + errx(EXIT_FAILURE, "Parse error in file %s: %s", + in_name, ucl_parser_get_error(p)); + ucl_parser_free(p); + } + + if (in_name != NULL) { + if (!ucl_parser_add_file_priority(p, in_name, 5)) { + if (errno != ENOENT) + errx(EXIT_FAILURE, "Parse error in file %s: %s", + in_name, ucl_parser_get_error(p)); + ucl_parser_free(p); + } + } + + /* + * Load from the root of the config file. If a specific file was + * requested, this will load the config from that file. If no filename + * was provided, this will load the defaults + */ + parse_conf(NULL, c, p); + + /* If no filename was provided, look for a key matching the vmname */ + if (in_name == NULL) + parse_conf(conf_name, c, p); + + check_config(c); + + if (p != NULL) + ucl_parser_free(p); +} + +/* + * Check if the basic variables are setting in + * the config file. + */ +int +check_config(struct config_entry *config) +{ + if (!config->vmname) + errx(EX_USAGE, "name not defined"); + else if (!config->memsize) + errx(EX_USAGE, "memory not defined"); + else if (!config->vcpus) + errx(EX_USAGE, "vcpus not defined"); + else if (!config->console) + errx(EX_USAGE, "console not defined"); + + return (0); +} + +/* + * Parse the config file. + */ +void +parse_conf(char *conf_name, struct config_entry *c, struct ucl_parser *p) +{ + const char *config = conf_name; + ucl_object_t *root = NULL; + const ucl_object_t *obj = NULL; + const ucl_object_t *cur, *acur; + ucl_object_iter_t it = NULL, ait = NULL; + const char *key; + char *tmp; + int i; + + root = ucl_parser_get_object(p); + if (root == NULL || ucl_object_type(root) != UCL_OBJECT) + errx(EXIT_FAILURE, "Invalid configuration format.\n"); + + if (conf_name != NULL) + obj = ucl_lookup_path(root, config); + else + obj = root; + + if (obj == NULL) + errx(EXIT_FAILURE, "Could not find configuration for VM: %s", conf_name); + + it = ucl_object_iterate_new(obj); + while ((cur = ucl_object_iterate_safe(it, true)) != NULL) { + key = ucl_object_key(cur); + if (strcasecmp(key, "name") == 0) + c->vmname = parse_string_safe(cur, NULL); + else if (strcasecmp(key, "cpus") == 0) { + if (!ucl_object_toint_safe(cur, (long *)&c->vcpus)) + errx(EXIT_FAILURE, "Invalid number of CPUs specified"); + } else if (strcasecmp(key, "memory") == 0) { + if (!ucl_object_toint_safe(cur, (size_t *)&c->memsize)) + errx(EXIT_FAILURE, "Invalid amount of memory specified"); + } else if (strcasecmp(key, "console") == 0) + c->console = parse_string_safe(cur, NULL); + else if (strcasecmp(key, "host_base") == 0) + c->host_base = parse_string_safe(cur, NULL); + else if (strcasecmp(key, "pincpu") == 0) + c->pincpu = parse_string_safe(cur, NULL); + else if (strcasecmp(key, "lpc") == 0) + c->lpc_device = parse_string_safe(cur, NULL); + else if (strcasecmp(key, "uuid") == 0) + c->uuid = parse_string_safe(cur, NULL); + else if (strcasecmp(key, "env") == 0) { + switch (ucl_object_type(cur)) { + case UCL_OBJECT: + if (cur->len == 0) + break; + c->env_count = cur->len; + c->env = malloc(sizeof(char *) * c->env_count); + i = 0; + ait = ucl_object_iterate_new(cur); + while ((acur = ucl_object_iterate_safe(ait, false)) != NULL) { + asprintf(&c->env[i], "%s=%s", ucl_object_key(acur), ucl_object_tostring_forced(acur)); + i++; + } + ucl_object_iterate_free(ait); + break; + default: + warnx("env must be an object containing key-value pairs"); + break; + } + } else if (strcasecmp(key, "device") == 0) { + switch (ucl_object_type(cur)) { + /* XXX: TODO: Case where machine has more than 31 devices */ + case UCL_OBJECT: + c->slots[c->slot_count] = malloc(sizeof(struct config_slot_entry)); + memcpy(c->slots[c->slot_count], &sinit, sizeof(struct config_slot_entry)); + tmp = parse_string_safe(cur, ".slot"); + if (tmp) + parse_pci_slot(tmp, + &c->slots[c->slot_count]->bnum, + &c->slots[c->slot_count]->snum, + &c->slots[c->slot_count]->fnum); + else + c->slots[c->slot_count]->snum = c->slot_count; + c->slots[c->slot_count]->emul = parse_string_safe(cur, ".type"); + c->slots[c->slot_count]->config = parse_string_safe(cur, ".config"); + free(tmp); + c->slot_count++; + break; + case UCL_ARRAY: + ait = ucl_object_iterate_new(cur); + while ((acur = ucl_object_iterate_safe(ait, true)) != NULL) { + c->slots[c->slot_count] = malloc(sizeof(struct config_slot_entry)); + memcpy(c->slots[c->slot_count], &sinit, sizeof(struct config_slot_entry)); + memcpy(c->slots[c->slot_count], &sinit, sizeof(struct config_slot_entry)); + tmp = parse_string_safe(acur, ".slot"); + if (tmp) + parse_pci_slot(tmp, + &c->slots[c->slot_count]->bnum, + &c->slots[c->slot_count]->snum, + &c->slots[c->slot_count]->fnum); + else + c->slots[c->slot_count]->snum = c->slot_count; + c->slots[c->slot_count]->emul = parse_string_safe(acur, ".type"); + c->slots[c->slot_count]->config = parse_string_safe(acur, ".config"); + free(tmp); + c->slot_count++; + } + ucl_object_iterate_free(ait); + break; + case UCL_STRING: + c->slots[c->slot_count] = malloc(sizeof(struct config_slot_entry)); + memcpy(c->slots[c->slot_count], &sinit, sizeof(struct config_slot_entry)); + c->slots[c->slot_count]->emul = parse_string_safe(cur, NULL); + c->slots[c->slot_count]->snum = c->slot_count; + c->slot_count++; + break; + default: + warnx("Error parsing disk contain"); + } + } else if (strcasecmp(key, "network") == 0) { + switch (ucl_object_type(cur)) { + case UCL_OBJECT: + c->slots[c->slot_count] = malloc(sizeof(struct config_slot_entry)); + memcpy(c->slots[c->slot_count], &sinit, sizeof(struct config_slot_entry)); + tmp = parse_string_safe(cur, ".slot"); + if (tmp) + parse_pci_slot(tmp, + &c->slots[c->slot_count]->bnum, + &c->slots[c->slot_count]->snum, + &c->slots[c->slot_count]->fnum); + else + c->slots[c->slot_count]->snum = c->slot_count; + tmp = parse_string_safe(cur, ".type"); + if (tmp) + c->slots[c->slot_count]->emul = tmp; + else + c->slots[c->slot_count]->emul = strdup("virtio-net"); + c->slots[c->slot_count]->iface.name = parse_string_safe(cur, ".name"); + c->slots[c->slot_count]->iface.mac = parse_string_safe(cur, ".mac"); + c->slot_count++; + break; + case UCL_ARRAY: + ait = ucl_object_iterate_new(cur); + while ((acur = ucl_object_iterate_safe(ait, true)) != NULL) { + c->slots[c->slot_count] = malloc(sizeof(struct config_slot_entry)); + memcpy(c->slots[c->slot_count], &sinit, sizeof(struct config_slot_entry)); + tmp = parse_string_safe(acur, ".slot"); + if (tmp) + parse_pci_slot(tmp, + &c->slots[c->slot_count]->bnum, + &c->slots[c->slot_count]->snum, + &c->slots[c->slot_count]->fnum); + else + c->slots[c->slot_count]->snum = c->slot_count; + tmp = parse_string_safe(acur, ".type"); + if (tmp) + c->slots[c->slot_count]->emul = tmp; + else + c->slots[c->slot_count]->emul = strdup("virtio-net"); + c->slots[c->slot_count]->iface.name = parse_string_safe(acur, ".name"); + c->slots[c->slot_count]->iface.mac = parse_string_safe(acur, ".mac"); + c->slot_count++; + } + ucl_object_iterate_free(ait); + break; + case UCL_STRING: + c->slots[c->slot_count] = malloc(sizeof(struct config_slot_entry)); + memcpy(c->slots[c->slot_count], &sinit, sizeof(struct config_slot_entry)); + c->slots[c->slot_count]->emul = strdup("virtio-net"); + c->slots[c->slot_count]->iface.name = parse_string_safe(cur, NULL); + c->slots[c->slot_count]->snum = c->slot_count; + c->slot_count++; + break; + default: + warnx("Error parsing disk contain"); + } + } else if (strcasecmp(key, "disk") == 0) { + switch (ucl_object_type(cur)) { + case UCL_OBJECT: + c->slots[c->slot_count] = malloc(sizeof(struct config_slot_entry)); + memcpy(c->slots[c->slot_count], &sinit, sizeof(struct config_slot_entry)); + tmp = parse_string_safe(cur, ".slot"); + if (tmp) + parse_pci_slot(tmp, + &c->slots[c->slot_count]->bnum, + &c->slots[c->slot_count]->snum, + &c->slots[c->slot_count]->fnum); + else + c->slots[c->slot_count]->snum = c->slot_count; + tmp = parse_string_safe(cur, ".type"); + if (tmp) + c->slots[c->slot_count]->emul = tmp; + else + c->slots[c->slot_count]->emul = strdup("virtio-blk"); + c->slots[c->slot_count]->disk.path = parse_string_safe(cur, ".path"); + ucl_object_toboolean_safe(ucl_lookup_path(cur, ".boot"), &c->slots[c->slot_count]->disk.boot); + ucl_object_toboolean_safe(ucl_lookup_path(cur, ".nocache"), &c->slots[c->slot_count]->disk.nocache); + ucl_object_toboolean_safe(ucl_lookup_path(cur, ".sync"), &c->slots[c->slot_count]->disk.sync); + ucl_object_toboolean_safe(ucl_lookup_path(cur, ".readonly"), &c->slots[c->slot_count]->disk.ro); + c->slot_count++; + break; + case UCL_ARRAY: + ait = ucl_object_iterate_new(cur); + while ((acur = ucl_object_iterate_safe(ait, true)) != NULL) { + c->slots[c->slot_count] = malloc(sizeof(struct config_slot_entry)); + memcpy(c->slots[c->slot_count], &sinit, sizeof(struct config_slot_entry)); + tmp = parse_string_safe(acur, ".slot"); + if (tmp) + parse_pci_slot(tmp, + &c->slots[c->slot_count]->bnum, + &c->slots[c->slot_count]->snum, + &c->slots[c->slot_count]->fnum); + else + c->slots[c->slot_count]->snum = c->slot_count; + tmp = parse_string_safe(acur, ".type"); + if (tmp) + c->slots[c->slot_count]->emul = tmp; + else + c->slots[c->slot_count]->emul = strdup("virtio-blk"); + c->slots[c->slot_count]->disk.path = parse_string_safe(acur, ".path"); + ucl_object_toboolean_safe(ucl_lookup_path(acur, ".boot"), &c->slots[c->slot_count]->disk.boot); + ucl_object_toboolean_safe(ucl_lookup_path(acur, ".nocache"), &c->slots[c->slot_count]->disk.nocache); + ucl_object_toboolean_safe(ucl_lookup_path(acur, ".sync"), &c->slots[c->slot_count]->disk.sync); + ucl_object_toboolean_safe(ucl_lookup_path(acur, ".readonly"), &c->slots[c->slot_count]->disk.ro); + c->slot_count++; + } + ucl_object_iterate_free(ait); + break; + case UCL_STRING: + c->slots[c->slot_count] = malloc(sizeof(struct config_slot_entry)); + memcpy(c->slots[c->slot_count], &sinit, sizeof(struct config_slot_entry)); + c->slots[c->slot_count]->emul = strdup("virtio-blk"); + c->slots[c->slot_count]->disk.path = parse_string_safe(cur, NULL); + c->slots[c->slot_count]->snum = c->slot_count; + c->slot_count++; + break; + default: + warnx("Error parsing disk contain"); + } + } else if (strcasecmp(key, "gdbport") == 0) { + if (!ucl_object_toint_safe(cur, (long *)&c->gdb_port)) + warnx("Invalid GDB port specified"); + } else if (strcasecmp(key, "acpi") == 0) + ucl_object_toboolean_safe(cur, &c->acpi); + else if (strcasecmp(key, "dump_memory") == 0) + ucl_object_toboolean_safe(cur, &c->dump_memory); + else if (strcasecmp(key, "vmexit_on_hlt") == 0) + ucl_object_toboolean_safe(cur, &c->vmexit_on_hlt); + else if (strcasecmp(key, "vmexit_on_pause") == 0) + ucl_object_toboolean_safe(cur, &c->vmexit_on_pause); + else if (strcasecmp(key, "strictio") == 0) + ucl_object_toboolean_safe(cur, &c->strictio); + else if (strcasecmp(key, "rtc_localtime") == 0) + ucl_object_toboolean_safe(cur, &c->rtc_localtime); + else if (strcasecmp(key, "strictmsr") == 0) + ucl_object_toboolean_safe(cur, &c->strictmsr); + else if (strcasecmp(key, "virtio_msix") == 0) + ucl_object_toboolean_safe(cur, &c->virtio_msix); + else if (strcasecmp(key, "x2apic_mode") == 0) + ucl_object_toboolean_safe(cur, &c->x2apic_mode); + else if (strcasecmp(key, "mptgen") == 0) + ucl_object_toboolean_safe(cur, &c->mptgen); + } + ucl_object_iterate_free(it); + + if (root != NULL) + ucl_object_unref(root); + +} + +char * +parse_string_safe(const ucl_object_t *obj, char *path) +{ + const ucl_object_t *target = NULL; + char *str = NULL; + + if (path != NULL) + target = ucl_lookup_path(obj, path); + else + target = obj; + + if (target == NULL) + return NULL; + + str = (char *)ucl_object_tostring(target); + if (str != NULL) + return strdup(str); + + return (str); +} + +bool +parse_pci_slot(const char *str, int *bnum, int *snum, int *fnum) +{ + if (str == NULL) + return (false); + /* :: */ + if (sscanf(str, "%d:%d:%d", bnum, snum, fnum) != 3) { + *bnum = 0; + /* : */ + if (sscanf(str, "%d:%d", snum, fnum) != 2) { + *fnum = 0; + /* */ + if (sscanf(str, "%d", snum) != 1) { + *snum = -1; + return (false); + } + } + } + return (true); +} Index: usr.sbin/bhyve/pci_emul.h =================================================================== --- usr.sbin/bhyve/pci_emul.h +++ usr.sbin/bhyve/pci_emul.h @@ -232,6 +232,7 @@ int pci_msix_pba_bar(struct pci_devinst *pi); int pci_msi_maxmsgnum(struct pci_devinst *pi); int pci_parse_slot(char *opt); +int pci_create_slot(int bnum, int snum, int fnum, char *emul, char *config); void pci_populate_msicap(struct msicap *cap, int msgs, int nextptr); int pci_emul_add_msixcap(struct pci_devinst *pi, int msgnum, int barnum); int pci_emul_msix_twrite(struct pci_devinst *pi, uint64_t offset, int size, Index: usr.sbin/bhyve/pci_emul.c =================================================================== --- usr.sbin/bhyve/pci_emul.c +++ usr.sbin/bhyve/pci_emul.c @@ -166,8 +166,6 @@ int pci_parse_slot(char *opt) { - struct businfo *bi; - struct slotinfo *si; char *emul, *config, *str, *cp; int error, bnum, snum, fnum; @@ -200,6 +198,26 @@ } } + error = pci_create_slot(bnum, snum, fnum, emul, config); + +done: + if (error) + free(str); + + return (error); +} + +int +pci_create_slot(int bnum, int snum, int fnum, char *emul, char *config) +{ + struct businfo *bi; + struct slotinfo *si; + char *opt; + int error; + + error = -1; + asprintf(&opt, "%d:%d:%d,%s,%s", bnum, snum, fnum, emul, config); + if (bnum < 0 || bnum >= MAXBUSES || snum < 0 || snum >= MAXSLOTS || fnum < 0 || fnum >= MAXFUNCS) { pci_parse_slot_usage(opt); @@ -229,8 +247,7 @@ si->si_funcs[fnum].fi_param = config; done: - if (error) - free(str); + free(opt); return (error); } Index: usr.sbin/bhyve/pci_virtio_net.c =================================================================== --- usr.sbin/bhyve/pci_virtio_net.c +++ usr.sbin/bhyve/pci_virtio_net.c @@ -59,6 +59,7 @@ #include #include +#include "config.h" #include "bhyverun.h" #include "pci_emul.h" #include "mevent.h" @@ -880,7 +881,7 @@ */ if (!mac_provided) { snprintf(nstr, sizeof(nstr), "%d-%d-%s", pi->pi_slot, - pi->pi_func, vmname); + pi->pi_func, vmconf.vmname); MD5Init(&mdctx); MD5Update(&mdctx, nstr, strlen(nstr)); Index: usr.sbin/bhyve/smbiostbl.c =================================================================== --- usr.sbin/bhyve/smbiostbl.c +++ usr.sbin/bhyve/smbiostbl.c @@ -40,12 +40,10 @@ #include #include +#include "config.h" #include "bhyverun.h" #include "smbiostbl.h" -#define MB (1024*1024) -#define GB (1024ULL*1024*1024) - #define SMBIOS_BASE 0xF1000 /* BHYVE_ACPI_BASE - SMBIOS_BASE) */ @@ -586,11 +584,11 @@ curaddr, endaddr, n, size); type1 = (struct smbios_table_type1 *)curaddr; - if (guest_uuid_str != NULL) { + if (vmconf.uuid != NULL) { uuid_t uuid; uint32_t status; - uuid_from_string(guest_uuid_str, &uuid, &status); + uuid_from_string(vmconf.uuid, &uuid, &status); if (status != uuid_s_ok) return (-1); @@ -609,7 +607,7 @@ return (-1); MD5Init(&mdctx); - MD5Update(&mdctx, vmname, strlen(vmname)); + MD5Update(&mdctx, vmconf.vmname, strlen(vmconf.vmname)); MD5Update(&mdctx, hostname, sizeof(hostname)); MD5Final(digest, &mdctx); @@ -634,7 +632,7 @@ { int i; - for (i = 0; i < guest_ncpus; i++) { + for (i = 0; i < vmconf.vcpus; i++) { struct smbios_table_type4 *type4; char *p; int nstrings, len; Index: usr.sbin/bhyve/spinup_ap.c =================================================================== --- usr.sbin/bhyve/spinup_ap.c +++ usr.sbin/bhyve/spinup_ap.c @@ -39,6 +39,7 @@ #include #include +#include "config.h" #include "bhyverun.h" #include "spinup_ap.h" @@ -80,7 +81,7 @@ int error; assert(newcpu != 0); - assert(newcpu < guest_ncpus); + assert(newcpu < vmconf.vcpus); error = vcpu_reset(ctx, newcpu); assert(error == 0); Index: usr.sbin/bhyveload/Makefile =================================================================== --- usr.sbin/bhyveload/Makefile +++ usr.sbin/bhyveload/Makefile @@ -1,14 +1,18 @@ # $FreeBSD$ PROG= bhyveload -SRCS= bhyveload.c MAN= bhyveload.8 PACKAGE= bhyve -LIBADD= vmmapi +SRCS= bhyveload.c \ + ${.CURDIR}/../bhyve/config.c + +LIBADD= vmmapi ucl WARNS?= 3 CFLAGS+=-I${.CURDIR}/../../sys/boot/userboot +CFLAGS+=-I${.CURDIR}/../bhyve +CFLAGS+=-I${.CURDIR}/../../contrib/libucl/include .include Index: usr.sbin/bhyveload/bhyveload.8 =================================================================== --- usr.sbin/bhyveload/bhyveload.8 +++ usr.sbin/bhyveload/bhyveload.8 @@ -40,10 +40,13 @@ .Op Fl c Ar cons-dev .Op Fl d Ar disk-path .Op Fl e Ar name=value +.Op Fl f Ar bhyve.conf .Op Fl h Ar host-path .Op Fl l Ar os-loader .Op Fl m Ar memsize Ns Op Ar K|k|M|m|G|g|T|t .Ar vmname +.Nm +.Fl f Ar bhyve.conf .Sh DESCRIPTION .Nm is used to load a @@ -90,6 +93,8 @@ .Pp The option may be used more than once to set more than one environment variable. +.It Fl f Ar bhyve.conf +Read configuration from specified file. .It Fl h Ar host-path The .Ar host-path @@ -150,6 +155,10 @@ .Pa /dev/nmdm1B .Pp .Dl "bhyveload -m 256MB -h /usr/images/test -c /dev/nmdm1B test-vm" +.Pp +To create a virtual machine described in a configuration file: +.Pp +.Dl "bhyveload -f guest.conf" .Sh SEE ALSO .Xr bhyve 4 , .Xr nmdm 4 , Index: usr.sbin/bhyveload/bhyveload.c =================================================================== --- usr.sbin/bhyveload/bhyveload.c +++ usr.sbin/bhyveload/bhyveload.c @@ -82,21 +82,54 @@ #include +#include + #include "userboot.h" -#define MB (1024 * 1024UL) -#define GB (1024 * 1024 * 1024UL) #define BSP 0 -#define NDISKS 32 +struct config_slot_entry sinit = { + .bnum = 0, + .snum = 0, + .fnum = 0, + .emul = NULL, + .config = NULL +}; + +struct config_entry cinit = { + .vmname = NULL, + .vcpus = 1, + .memsize = 256 * MB, + .console = NULL, + .host_base = NULL, + .pincpu = NULL, + .lpc_device = NULL, + .uuid = NULL, + .env = NULL, + .env_count = 0, + .slots = {NULL}, + .slot_count = 0, + .gdb_port = 0, + .acpi = 0, + .dump_memory = 0, + .vmexit_on_hlt = 0, + .vmexit_on_pause = 0, + .strictio = 0, + .rtc_localtime = 1, + .strictmsr = 1, + .virtio_msix = 1, + .x2apic_mode = 0, + .mptgen = 1 +}; + +struct config_entry vmconf; -static char *host_base; static struct termios term, oldterm; static int disk_fd[NDISKS]; static int ndisks; static int consin_fd, consout_fd; -static char *vmname, *progname; +static char *progname; static struct vmctx *ctx; static uint64_t gdtbase, cr3, rsp; @@ -155,10 +188,10 @@ struct cb_file *cf; char path[PATH_MAX]; - if (!host_base) + if (!vmconf.host_base) return (ENOENT); - strlcpy(path, host_base, PATH_MAX); + strlcpy(path, vmconf.host_base, PATH_MAX); if (path[strlen(path) - 1] == '/') path[strlen(path) - 1] = 0; strlcat(path, filename, PATH_MAX); @@ -648,7 +681,7 @@ fprintf(stderr, "usage: %s [-S][-c ] [-d ] [-e ]\n" - " %*s [-h ] [-m memsize[K|k|M|m|G|g|T|t]] \n", + " %*s [-f [-h ] [-m memsize[K|k|M|m|G|g|T|t]] \n", progname, (int)strlen(progname), ""); exit(1); @@ -662,6 +695,10 @@ void (*func)(struct loader_callbacks *, void *, int, int); uint64_t mem_size; int opt, error, need_reinit, memflags; + size_t slot; + char *temp, *bootpath; + const ucl_object_t *target; + bool new_config; progname = basename(argv[0]); @@ -672,29 +709,33 @@ consin_fd = STDIN_FILENO; consout_fd = STDOUT_FILENO; + temp = NULL; + bootpath = NULL; + target = NULL; + new_config = false; - while ((opt = getopt(argc, argv, "CSc:d:e:h:l:m:")) != -1) { + while ((opt = getopt(argc, argv, "CSc:d:e:f:h:l:m:")) != -1) { switch (opt) { case 'c': error = altcons_open(optarg); if (error != 0) errx(EX_USAGE, "Could not open '%s'", optarg); break; - case 'd': error = disk_open(optarg); if (error != 0) errx(EX_USAGE, "Could not open '%s'", optarg); break; - case 'e': addenv(optarg); break; - + case 'f': + new_config = true; + load_config(optarg, NULL, &vmconf); + break; case 'h': - host_base = optarg; + vmconf.host_base = optarg; break; - case 'l': if (loader != NULL) errx(EX_USAGE, "-l can only be given once"); @@ -704,7 +745,7 @@ break; case 'm': - error = vm_parse_memsize(optarg, &mem_size); + error = vm_parse_memsize(optarg, &vmconf.memsize); if (error != 0) errx(EX_USAGE, "Invalid memsize '%s'", optarg); break; @@ -722,13 +763,62 @@ argc -= optind; argv += optind; - if (argc != 1) - usage(); + if (new_config) { + error = altcons_open(vmconf.console); + if (error != 0) + errx(EX_USAGE, "Could not open console: %s", + vmconf.console); + + /* Find the boot disk */ + for (slot = 0; slot < vmconf.slot_count; slot++) { + if (strcasecmp(vmconf.slots[slot]->emul, "virtio-blk") == 0 || + strcasecmp(vmconf.slots[slot]->emul, "ahci-hd") == 0 || + strcasecmp(vmconf.slots[slot]->emul, "ahci-cd") == 0) { + if (vmconf.slots[slot]->disk.boot == true) { + bootpath = vmconf.slots[slot]->disk.path; + break; + } + } + } + if (bootpath != NULL) { + error = disk_open(bootpath); + if (error != 0) + errx(EX_USAGE, "Could not open boot disk: %s", bootpath); + } + /* Open every other disk (required for RAID etc) */ + for (slot = 0; slot < vmconf.slot_count; slot++) { + if (strcasecmp(vmconf.slots[slot]->emul, "virtio-blk") == 0 || + strcasecmp(vmconf.slots[slot]->emul, "ahci-hd") == 0 || + strcasecmp(vmconf.slots[slot]->emul, "ahci-cd") == 0) { + /* Open open the first NDISKS disks */ + if (ndisks > NDISKS) + break; + error = disk_open(vmconf.slots[slot]->disk.path); + if (error != 0) + errx(EX_USAGE, "Could not open disk: %s", vmconf.slots[slot]->disk.path); + } + } + + /* build the environment */ + for (slot = 0; slot < vmconf.env_count; slot++) { + addenv(vmconf.env[slot]); + } + + asprintf(&temp, "%zu", vmconf.memsize); + error = vm_parse_memsize(temp, &vmconf.memsize); + if (error) + errx(EX_USAGE, "invalid memsize '%s'", optarg); + free(temp); + } - vmname = argv[0]; + if (!vmconf.vmname && argc > 1) + vmconf.vmname = argv[1]; + + if (!vmconf.vmname) + usage(); need_reinit = 0; - error = vm_create(vmname); + error = vm_create(vmconf.vmname); if (error) { if (errno != EEXIST) { perror("vm_create"); @@ -737,7 +827,7 @@ need_reinit = 1; } - ctx = vm_open(vmname); + ctx = vm_open(vmconf.vmname); if (ctx == NULL) { perror("vm_open"); exit(1);