/*- * Copyright (c) 2011 NetApp, Inc. * 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 NETAPP, INC ``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 NETAPP, INC 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. * * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #include "vmmapi.h" #include "sw_load.h" #include "cpuset.h" #include "dm.h" #include "acpi.h" #include "atkbdc.h" #include "inout.h" #include "ioapic.h" #include "mem.h" #include "mevent.h" #include "mptbl.h" #include "pci_core.h" #include "irq.h" #include "lpc.h" #include "rtc.h" #include "pit.h" #include "hpet.h" #include "version.h" #include "sw_load.h" #include "monitor.h" #include "ioc.h" #include "pm.h" #include "atomic.h" #include "tpm.h" #include "mmio_dev.h" #include "virtio.h" #include "pm_vuart.h" #include "log.h" #include "pci_util.h" #include "vssram.h" #include "cmd_monitor.h" #include "vdisplay.h" #define VM_MAXCPU 16 /* maximum virtual cpus */ #define GUEST_NIO_PORT 0x488 /* guest upcalls via i/o port */ /* Values returned for reads on invalid I/O requests. */ #define IOREQ_PIO_INVAL (~0U) #define IOREQ_MMIO_INVAL (~0UL) typedef void (*vmexit_handler_t)(struct vmctx *, struct acrn_io_request *, int *vcpu); char *vmname; char *vsbl_file_name; char *ovmf_file_name; char *ovmf_code_file_name; char *ovmf_vars_file_name; char *kernel_file_name; char *elf_file_name; uint8_t trusty_enabled; bool stdio_in_use; bool lapic_pt; bool is_rtvm; bool pt_tpm2; bool ssram; bool vtpm2; bool is_winvm; bool skip_pci_mem64bar_workaround = false; bool gfx_ui = false; static int guest_ncpus; static int virtio_msix = 1; static bool debugexit_enabled; static int pm_notify_channel; static bool cmd_monitor; static char *progname; static const int BSP; static cpuset_t cpumask; static void vm_loop(struct vmctx *ctx); static char io_request_page[4096] __aligned(4096); static struct acrn_io_request *ioreq_buf = (struct acrn_io_request *)&io_request_page; struct dmstats { uint64_t vmexit_bogus; uint64_t vmexit_reqidle; uint64_t vmexit_hlt; uint64_t vmexit_pause; uint64_t vmexit_mtrap; uint64_t cpu_switch_rotate; uint64_t cpu_switch_direct; uint64_t vmexit_mmio_emul; } stats; struct mt_vmm_info { pthread_t mt_thr; struct vmctx *mt_ctx; int mt_vcpu; } mt_vmm_info[VM_MAXCPU]; static struct vmctx *_ctx; static void usage(int code) { fprintf(stderr, "Usage: %s [-hYv] [-B bootargs] [-E elf_image_path]\n" " %*s [-k kernel_image_path]\n" " %*s [-l lpc] [-m mem] [-r ramdisk_image_path]\n" " %*s [-s pci] [--ovmf ovmf_file_path]\n" " %*s [--enable_trusty] [--intr_monitor param_setting]\n" " %*s [--acpidev_pt HID] [--mmiodev_pt MMIO_Regions]\n" " %*s [--vtpm2 sock_path] [--virtio_poll interval]\n" " %*s [--cpu_affinity lapic_id] [--lapic_pt] [--rtvm] [--windows]\n" " %*s [--debugexit] [--logger_setting param_setting]\n" " %*s [--ssram] \n" " -B: bootargs for kernel\n" " -E: elf image path\n" " -h: help\n" " -k: kernel image path\n" " -l: LPC device configuration\n" " -m: memory size in MB\n" " -r: ramdisk image path\n" " -s: PCI slot config\n" " -v: version\n" " --ovmf: ovmf file path\n" " --ssram: Congfiure Software SRAM parameters\n" " --cpu_affinity: list of Service VM vCPUs assigned to this User VM, the vCPUs are" " identified by their local APIC IDs.\n" " --enable_trusty: enable trusty for guest\n" " --debugexit: enable debug exit function\n" " --intr_monitor: enable interrupt storm monitor\n" " its params: threshold/s,probe-period(s),delay_time(ms),delay_duration(ms)\n" " --cmd_monitor: enable command monitor\n" " its params: unix domain socket path\n" " --virtio_poll: enable virtio poll mode with poll interval with ns\n" " --acpidev_pt: acpi device ID args: HID in ACPI Table\n" " --mmiodev_pt: MMIO resources args: physical MMIO regions\n" " --vtpm2: Virtual TPM2 args: sock_path=$PATH_OF_SWTPM_SOCKET\n" " --lapic_pt: enable local apic passthrough\n" " --rtvm: indicate that the guest is rtvm\n" " --logger_setting: params like console,level=4;kmsg,level=3\n" " --windows: support Oracle virtio-blk, virtio-net and virtio-input devices\n" " for windows guest with secure boot\n" " --virtio_msi: force virtio to use single-vector MSI\n", progname, (int)strnlen(progname, PATH_MAX), "", (int)strnlen(progname, PATH_MAX), "", (int)strnlen(progname, PATH_MAX), "", (int)strnlen(progname, PATH_MAX), "", (int)strnlen(progname, PATH_MAX), "", (int)strnlen(progname, PATH_MAX), "", (int)strnlen(progname, PATH_MAX), "", (int)strnlen(progname, PATH_MAX), "", (int)strnlen(progname, PATH_MAX), ""); exit(code); } static void print_version(void) { fprintf(stdout, "DM version is: %s-%s (daily tag:%s), build by %s@%s\n", DM_FULL_VERSION, DM_BUILD_VERSION, DM_DAILY_TAG, DM_BUILD_USER, DM_BUILD_TIME); exit(0); } /** * @brief Convert guest physical address to host virtual address * * @param ctx Pointer to to struct vmctx representing VM context. * @param gaddr Guest physical address base. * @param len Guest physical address length. * * @return NULL on convert failed and host virtual address on successful. */ void * paddr_guest2host(struct vmctx *ctx, uintptr_t gaddr, size_t len) { return vm_map_gpa(ctx, gaddr, len); } int virtio_uses_msix(void) { return virtio_msix; } size_t high_bios_size(void) { size_t size = ovmf_image_size(); return roundup2(size, 2 * MB); } static void * start_thread(void *param) { char tname[MAXCOMLEN + 1]; struct mt_vmm_info *mtp; int vcpu; mtp = param; vcpu = mtp->mt_vcpu; snprintf(tname, sizeof(tname), "vcpu %d", vcpu); pthread_setname_np(mtp->mt_thr, tname); vm_loop(mtp->mt_ctx); /* reset or halt */ return NULL; } static int add_cpu(struct vmctx *ctx, int vcpu_num) { int i; int error; for (i = 0; i < vcpu_num; i++) { CPU_SET_ATOMIC(i, &cpumask); mt_vmm_info[i].mt_ctx = ctx; mt_vmm_info[i].mt_vcpu = i; } vm_set_vcpu_regs(ctx, &ctx->bsp_regs); error = pthread_create(&mt_vmm_info[0].mt_thr, NULL, start_thread, &mt_vmm_info[0]); return error; } static int delete_cpu(struct vmctx *ctx, int vcpu) { if (!CPU_ISSET(vcpu, &cpumask)) { pr_err("Attempting to delete unknown cpu %d\n", vcpu); exit(1); } vm_destroy_ioreq_client(ctx); pthread_join(mt_vmm_info[0].mt_thr, NULL); CPU_CLR_ATOMIC(vcpu, &cpumask); return CPU_EMPTY(&cpumask); } #ifdef DM_DEBUG void notify_vmloop_thread(void) { pthread_kill(mt_vmm_info[0].mt_thr, SIGCONT); return; } #endif static void vmexit_inout(struct vmctx *ctx, struct acrn_io_request *io_req, int *pvcpu) { int error; int bytes, port, in; port = io_req->reqs.pio_request.address; bytes = io_req->reqs.pio_request.size; in = (io_req->reqs.pio_request.direction == ACRN_IOREQ_DIR_READ); error = emulate_inout(ctx, pvcpu, &io_req->reqs.pio_request); if (error) { pr_err("Unhandled %s%c 0x%04x\n", in ? "in" : "out", bytes == 1 ? 'b' : (bytes == 2 ? 'w' : 'l'), port); if (in) { io_req->reqs.pio_request.value = IOREQ_PIO_INVAL; } } } static void vmexit_mmio_emul(struct vmctx *ctx, struct acrn_io_request *io_req, int *pvcpu) { int err; stats.vmexit_mmio_emul++; err = emulate_mem(ctx, &io_req->reqs.mmio_request); if (err) { if (err == -ESRCH) pr_err("Unhandled memory access to 0x%lx\n", io_req->reqs.mmio_request.address); pr_err("Failed to emulate instruction ["); pr_err("mmio address 0x%lx, size %ld", io_req->reqs.mmio_request.address, io_req->reqs.mmio_request.size); if (io_req->reqs.mmio_request.direction == ACRN_IOREQ_DIR_READ) { io_req->reqs.mmio_request.value = IOREQ_MMIO_INVAL; } } } static void vmexit_pci_emul(struct vmctx *ctx, struct acrn_io_request *io_req, int *pvcpu) { int err, in = (io_req->reqs.pci_request.direction == ACRN_IOREQ_DIR_READ); err = emulate_pci_cfgrw(ctx, *pvcpu, in, io_req->reqs.pci_request.bus, io_req->reqs.pci_request.dev, io_req->reqs.pci_request.func, io_req->reqs.pci_request.reg, io_req->reqs.pci_request.size, &io_req->reqs.pci_request.value); if (err) { pr_err("Unhandled pci cfg rw at %x:%x.%x reg 0x%x\n", io_req->reqs.pci_request.bus, io_req->reqs.pci_request.dev, io_req->reqs.pci_request.func, io_req->reqs.pci_request.reg); if (in) { io_req->reqs.pio_request.value = IOREQ_PIO_INVAL; } } } #define DEBUG_EPT_MISCONFIG #ifdef DEBUG_EPT_MISCONFIG #define EXIT_REASON_EPT_MISCONFIG 49 #define VMCS_GUEST_PHYSICAL_ADDRESS 0x00002400 #define VMCS_IDENT(x) ((x) | 0x80000000) #endif /* #ifdef DEBUG_EPT_MISCONFIG */ enum vm_exitcode { VM_EXITCODE_INOUT = 0, VM_EXITCODE_MMIO_EMUL, VM_EXITCODE_PCI_CFG, VM_EXITCODE_MAX }; static vmexit_handler_t handler[VM_EXITCODE_MAX] = { [VM_EXITCODE_INOUT] = vmexit_inout, [VM_EXITCODE_MMIO_EMUL] = vmexit_mmio_emul, [VM_EXITCODE_PCI_CFG] = vmexit_pci_emul, }; static void handle_vmexit(struct vmctx *ctx, struct acrn_io_request *io_req, int vcpu) { enum vm_exitcode exitcode; exitcode = io_req->type; if (exitcode >= VM_EXITCODE_MAX || handler[exitcode] == NULL) { pr_err("handle vmexit: unexpected exitcode 0x%x\n", exitcode); exit(1); } (*handler[exitcode])(ctx, io_req, &vcpu); /* We cannot notify the HSM/hypervisor on the request completion at this * point if the User VM is in suspend or system reset mode, as the VM is * still not paused and a notification can kick off the vcpu to run * again. Postpone the notification till vm_system_reset() or * vm_suspend_resume() for resetting the ioreq states in the HSM and * hypervisor. */ if ((VM_SUSPEND_SYSTEM_RESET == vm_get_suspend_mode()) || (VM_SUSPEND_SUSPEND == vm_get_suspend_mode())) return; vm_notify_request_done(ctx, vcpu); } static int guest_pm_notify_init(struct vmctx *ctx) { int ret = 0; /* * We don't care ioc_init return value so far. * Will add return value check once ioc is full function. */ if (PWR_EVENT_NOTIFY_IOC == pm_notify_channel) ioc_init(ctx); else if (PWR_EVENT_NOTIFY_PWR_BT == pm_notify_channel) power_button_init(ctx); else if (PWR_EVENT_NOTIFY_UART == pm_notify_channel || PWR_EVENT_NOTIFY_UART_TRIG_PLAT_S5 == pm_notify_channel) ret = pm_by_vuart_init(ctx, (PWR_EVENT_NOTIFY_UART_TRIG_PLAT_S5 == pm_notify_channel)); else pr_info("No pm notify channel given\n"); return ret; } static void guest_pm_notify_deinit(struct vmctx *ctx) { if (PWR_EVENT_NOTIFY_IOC == pm_notify_channel) ioc_deinit(ctx); else if (PWR_EVENT_NOTIFY_PWR_BT == pm_notify_channel) power_button_deinit(ctx); else if (PWR_EVENT_NOTIFY_UART == pm_notify_channel || PWR_EVENT_NOTIFY_UART_TRIG_PLAT_S5 == pm_notify_channel) pm_by_vuart_deinit(ctx); else pr_err("No correct pm notify channel given\n"); } static int vm_init_vdevs(struct vmctx *ctx) { int ret; init_mem(); init_inout(); pci_irq_init(ctx); atkbdc_init(ctx); ioapic_init(ctx); ret = guest_pm_notify_init(ctx); if (ret < 0) goto pm_notify_fail; ret = vrtc_init(ctx); if (ret < 0) goto vrtc_fail; ret = vpit_init(ctx); if (ret < 0) goto vpit_fail; ret = vhpet_init(ctx); if (ret < 0) goto vhpet_fail; sci_init(ctx); if (debugexit_enabled) init_debugexit(); if ((ssram) && (init_vssram(ctx) < 0)) goto ssram_fail; ret = monitor_init(ctx); if (ret < 0) goto monitor_fail; if ((cmd_monitor) && init_cmd_monitor(ctx) < 0) goto monitor_fail; ret = init_mmio_devs(ctx); if (ret < 0) goto mmio_dev_fail; ret = init_pci(ctx); if (ret < 0) goto pci_fail; /* FIXME: if we plan to support pass through a TPM device and emulate another b TPM device */ init_vtpm2(ctx); return 0; pci_fail: deinit_mmio_devs(ctx); mmio_dev_fail: monitor_close(); ssram_fail: if (ssram) deinit_vssram(ctx); monitor_fail: if (debugexit_enabled) deinit_debugexit(); vhpet_deinit(ctx); vhpet_fail: vpit_deinit(ctx); vpit_fail: vrtc_deinit(ctx); vrtc_fail: guest_pm_notify_deinit(ctx); pm_notify_fail: atkbdc_deinit(ctx); pci_irq_deinit(ctx); ioapic_deinit(); return -1; } static void vm_deinit_vdevs(struct vmctx *ctx) { /* * Write ovmf NV storage back to the original file from guest * memory before deinit operations. */ acrn_writeback_ovmf_nvstorage(ctx); deinit_pci(ctx); deinit_mmio_devs(ctx); monitor_close(); if (debugexit_enabled) deinit_debugexit(); if (ssram) deinit_vssram(ctx); vhpet_deinit(ctx); vpit_deinit(ctx); vrtc_deinit(ctx); guest_pm_notify_deinit(ctx); atkbdc_deinit(ctx); pci_irq_deinit(ctx); ioapic_deinit(); deinit_vtpm2(ctx); } static void vm_reset_vdevs(struct vmctx *ctx) { /* * Write ovmf NV storage back to the original file from guest * memory before deinit operations. */ acrn_writeback_ovmf_nvstorage(ctx); /* * The current virtual devices doesn't define virtual * device reset function. So we call vdev deinit/init * pairing to emulate the device reset operation. * * pci/ioapic deinit/init is needed because of dependency * of pci irq allocation/free. * * acpi build is necessary because irq for each vdev * could be assigned with different number after reset. */ atkbdc_deinit(ctx); if (debugexit_enabled) deinit_debugexit(); if (ssram) deinit_vssram(ctx); vhpet_deinit(ctx); vpit_deinit(ctx); vrtc_deinit(ctx); deinit_pci(ctx); pci_irq_deinit(ctx); ioapic_deinit(); pci_irq_init(ctx); atkbdc_init(ctx); vrtc_init(ctx); vpit_init(ctx); vhpet_init(ctx); if (debugexit_enabled) init_debugexit(); if (ssram) init_vssram(ctx); ioapic_init(ctx); init_pci(ctx); acpi_build(ctx, guest_ncpus); } static void vm_system_reset(struct vmctx *ctx) { /* * If we get system reset request, we don't want to exit the * vcpu_loop/vm_loop/mevent_loop. So we do: * 1. pause VM * 2. flush and clear ioreqs * 3. reset virtual devices * 4. load software for User VM * 5. hypercall reset vm * 6. reset suspend mode to VM_SUSPEND_NONE */ vm_pause(ctx); /* * After vm_pause, there should be no new coming ioreq. * * Unless under emergency mode, the vcpu writing to the ACPI PM * CR should be the only vcpu of that VM that is still * running. In this case there should be only one completed * request which is the APIC PM CR write. VM reset will reset it * * When handling emergency mode triggered by one vcpu without * offlining any other vcpus, there can be multiple IO requests * with various states. We should be careful on potential races * when resetting especially in SMP Service VM. vm_clear_ioreq can be used * to clear all ioreq status in HSM after VM pause, then let VM * reset in hypervisor reset all ioreqs. */ vm_clear_ioreq(ctx); vm_reset_vdevs(ctx); vm_reset(ctx); pr_info("%s: setting VM state to %s\n", __func__, vm_state_to_str(VM_SUSPEND_NONE)); vm_set_suspend_mode(VM_SUSPEND_NONE); /* set the BSP init state */ acrn_sw_load(ctx); vm_set_vcpu_regs(ctx, &ctx->bsp_regs); vm_run(ctx); } static void vm_suspend_resume(struct vmctx *ctx) { /* * If we get warm reboot request, we don't want to exit the * vcpu_loop/vm_loop/mevent_loop. So we do: * 1. pause VM * 2. flush and clear ioreqs * 3. stop vm watchdog * 4. wait for resume signal * 5. reset vm watchdog * 6. hypercall restart vm */ vm_pause(ctx); vm_clear_ioreq(ctx); vm_stop_watchdog(ctx); wait_for_resume(ctx); pm_backto_wakeup(ctx); vm_reset_watchdog(ctx); vm_reset(ctx); /* set the BSP init state */ vm_set_vcpu_regs(ctx, &ctx->bsp_regs); vm_run(ctx); } static void vm_loop(struct vmctx *ctx) { int error; ctx->ioreq_client = vm_create_ioreq_client(ctx); if (ctx->ioreq_client < 0) { pr_err("%s, failed to create IOREQ.\n", __func__); return; } if (vm_run(ctx) != 0) { pr_err("%s, failed to run VM.\n", __func__); return; } while (1) { int vcpu_id; struct acrn_io_request *io_req; error = vm_attach_ioreq_client(ctx); if (error) break; for (vcpu_id = 0; vcpu_id < guest_ncpus; vcpu_id++) { io_req = &ioreq_buf[vcpu_id]; if ((atomic_load(&io_req->processed) == ACRN_IOREQ_STATE_PROCESSING) && !io_req->kernel_handled) handle_vmexit(ctx, io_req, vcpu_id); } if (VM_SUSPEND_FULL_RESET == vm_get_suspend_mode() || VM_SUSPEND_POWEROFF == vm_get_suspend_mode()) { break; } /* RTVM can't be reset */ if ((VM_SUSPEND_SYSTEM_RESET == vm_get_suspend_mode()) && (!is_rtvm)) { vm_system_reset(ctx); } if (VM_SUSPEND_SUSPEND == vm_get_suspend_mode()) { vm_suspend_resume(ctx); } } pr_err("VM loop exit\n"); } static int num_vcpus_allowed(struct vmctx *ctx) { /* TODO: add ioctl to get gerneric information including * virtual cpus, now hardcode */ return VM_MAXCPU; } static void sig_handler_term(int signo) { pr_info("Received SIGINT to terminate application...\n"); pr_info("%s: setting VM state to %s\n", __func__, vm_state_to_str(VM_SUSPEND_POWEROFF)); vm_set_suspend_mode(VM_SUSPEND_POWEROFF); mevent_notify(); } enum { CMD_OPT_VSBL = 1000, CMD_OPT_OVMF, CMD_OPT_CPU_AFFINITY, CMD_OPT_PART_INFO, CMD_OPT_TRUSTY_ENABLE, CMD_OPT_VIRTIO_POLL_ENABLE, CMD_OPT_MAC_SEED, CMD_OPT_DEBUGEXIT, CMD_OPT_VMCFG, CMD_OPT_DUMP, CMD_OPT_INTR_MONITOR, CMD_OPT_CMD_MONITOR, CMD_OPT_ACPIDEV_PT, CMD_OPT_MMIODEV_PT, CMD_OPT_VTPM2, CMD_OPT_LAPIC_PT, CMD_OPT_RTVM, CMD_OPT_SOFTWARE_SRAM, CMD_OPT_LOGGER_SETTING, CMD_OPT_PM_NOTIFY_CHANNEL, CMD_OPT_PM_BY_VUART, CMD_OPT_WINDOWS, CMD_OPT_FORCE_VIRTIO_MSI, }; static struct option long_options[] = { {"acpi", no_argument, 0, 'A' }, {"elf_file", required_argument, 0, 'E' }, {"ioc_node", required_argument, 0, 'i' }, {"lpc", required_argument, 0, 'l' }, {"pci_slot", required_argument, 0, 's' }, {"memsize", required_argument, 0, 'm' }, {"mptgen", no_argument, 0, 'Y' }, {"kernel", required_argument, 0, 'k' }, {"ramdisk", required_argument, 0, 'r' }, {"bootargs", required_argument, 0, 'B' }, {"version", no_argument, 0, 'v' }, {"gvtargs", required_argument, 0, 'G' }, {"help", no_argument, 0, 'h' }, /* Following cmd option only has long option */ #ifdef CONFIG_VM_CFG #endif {"vsbl", required_argument, 0, CMD_OPT_VSBL}, {"ovmf", required_argument, 0, CMD_OPT_OVMF}, {"cpu_affinity", required_argument, 0, CMD_OPT_CPU_AFFINITY}, {"part_info", required_argument, 0, CMD_OPT_PART_INFO}, {"enable_trusty", no_argument, 0, CMD_OPT_TRUSTY_ENABLE}, {"virtio_poll", required_argument, 0, CMD_OPT_VIRTIO_POLL_ENABLE}, {"debugexit", no_argument, 0, CMD_OPT_DEBUGEXIT}, {"intr_monitor", required_argument, 0, CMD_OPT_INTR_MONITOR}, {"cmd_monitor", required_argument, 0, CMD_OPT_CMD_MONITOR}, {"acpidev_pt", required_argument, 0, CMD_OPT_ACPIDEV_PT}, {"mmiodev_pt", required_argument, 0, CMD_OPT_MMIODEV_PT}, {"vtpm2", required_argument, 0, CMD_OPT_VTPM2}, {"lapic_pt", no_argument, 0, CMD_OPT_LAPIC_PT}, {"rtvm", no_argument, 0, CMD_OPT_RTVM}, {"ssram", required_argument, 0, CMD_OPT_SOFTWARE_SRAM}, {"logger_setting", required_argument, 0, CMD_OPT_LOGGER_SETTING}, {"pm_notify_channel", required_argument, 0, CMD_OPT_PM_NOTIFY_CHANNEL}, {"pm_by_vuart", required_argument, 0, CMD_OPT_PM_BY_VUART}, {"windows", no_argument, 0, CMD_OPT_WINDOWS}, {"virtio_msi", no_argument, 0, CMD_OPT_FORCE_VIRTIO_MSI}, {0, 0, 0, 0 }, }; static char optstr[] = "AhYvE:k:r:B:s:m:l:U:G:i:"; int main(int argc, char *argv[]) { int c, error, ret=1; int max_vcpus, mptgen; struct vmctx *ctx; size_t memsize; int option_idx = 0; progname = basename(argv[0]); memsize = 256 * MB; mptgen = 1; if (signal(SIGHUP, sig_handler_term) == SIG_ERR) fprintf(stderr, "cannot register handler for SIGHUP\n"); if (signal(SIGINT, sig_handler_term) == SIG_ERR) fprintf(stderr, "cannot register handler for SIGINT\n"); /* * Ignore SIGPIPE signal and handle the error directly when write() * function fails. this will help us to catch the write failure rather * than crashing the User VM. */ if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) fprintf(stderr, "cannot register handler for SIGPIPE\n"); if (parse_madt()) { pr_err("Failed to parse the MADT table\n"); exit(1); } while ((c = getopt_long(argc, argv, optstr, long_options, &option_idx)) != -1) { switch (c) { case 'A': pr_info("The '-A' parameter is obsolete and ignored. " "Virtual ACPI tables are always enabled.\n"); break; case 'E': if (acrn_parse_elf(optarg) != 0) exit(1); else break; break; case 'i': /* obsolete parameter */ ioc_parse(optarg); break; case 'l': if (lpc_device_parse(optarg) != 0) { errx(EX_USAGE, "invalid lpc device configuration '%s'", optarg); } break; case 's': if (pci_parse_slot(optarg) != 0) exit(1); else break; case 'm': if (vm_parse_memsize(optarg, &memsize) != 0) errx(EX_USAGE, "invalid memsize '%s'", optarg); break; case 'Y': /* obsolete parameter */ mptgen = 0; break; case 'k': if (acrn_parse_kernel(optarg) != 0) exit(1); else break; case 'r': if (acrn_parse_ramdisk(optarg) != 0) exit(1); else break; case 'B': if (acrn_parse_bootargs(optarg) != 0) exit(1); else break; break; case 'G': /* obsolete parameter */ if (acrn_parse_gvtargs(optarg) != 0) errx(EX_USAGE, "invalid GVT param %s", optarg); break; case 'v': print_version(); break; case CMD_OPT_VSBL: /* obsolete parameter */ if (high_bios_size() == 0 && acrn_parse_vsbl(optarg) != 0) errx(EX_USAGE, "invalid vsbl param %s", optarg); break; case CMD_OPT_OVMF: if (!vsbl_file_name && acrn_parse_ovmf(optarg) != 0) errx(EX_USAGE, "invalid ovmf param %s", optarg); skip_pci_mem64bar_workaround = true; break; case CMD_OPT_CPU_AFFINITY: if (acrn_parse_cpu_affinity(optarg) != 0) errx(EX_USAGE, "invalid pcpu param %s", optarg); break; case CMD_OPT_PART_INFO: /* obsolete parameter */ if (acrn_parse_guest_part_info(optarg) != 0) { errx(EX_USAGE, "invalid guest partition info param %s", optarg); } break; case CMD_OPT_TRUSTY_ENABLE: trusty_enabled = 1; break; case CMD_OPT_VIRTIO_POLL_ENABLE: if (acrn_parse_virtio_poll_interval(optarg) != 0) { errx(EX_USAGE, "invalid virtio poll interval %s", optarg); } break; case CMD_OPT_DEBUGEXIT: debugexit_enabled = true; break; case CMD_OPT_LAPIC_PT: lapic_pt = true; break; case CMD_OPT_RTVM: is_rtvm = true; lapic_pt = true; break; case CMD_OPT_SOFTWARE_SRAM: if (parse_vssram_buf_params(optarg) != 0) errx(EX_USAGE, "invalid vSSRAM buffer size param %s", optarg); ssram = true; break; case CMD_OPT_ACPIDEV_PT: /* FIXME: check acpi TPM device rules in acpi device famework init functions */ if (vtpm2 || create_pt_acpidev(optarg) != 0) errx(EX_USAGE, "invalid pt acpi dev param %s", optarg); break; case CMD_OPT_MMIODEV_PT: if (create_pt_mmiodev(optarg) != 0) errx(EX_USAGE, "invalid pt mmio dev param %s", optarg); break; case CMD_OPT_VTPM2: if (pt_tpm2 || acrn_parse_vtpm2(optarg) != 0) errx(EX_USAGE, "invalid vtpm2 param %s", optarg); break; case CMD_OPT_INTR_MONITOR: if (acrn_parse_intr_monitor(optarg) != 0) errx(EX_USAGE, "invalid intr-monitor params %s", optarg); break; case CMD_OPT_CMD_MONITOR: if (acrn_parse_cmd_monitor(optarg) != 0) errx(EX_USAGE, "invalid command monitor params %s", optarg); cmd_monitor = true; break; case CMD_OPT_LOGGER_SETTING: if (init_logger_setting(optarg) != 0) errx(EX_USAGE, "invalid logger setting params %s", optarg); break; case CMD_OPT_PM_NOTIFY_CHANNEL: /* obsolete parameter */ if (strncmp("ioc", optarg, sizeof("ioc")) == 0) pm_notify_channel = PWR_EVENT_NOTIFY_IOC; else if (strncmp("power_button", optarg, sizeof("power_button")) == 0) pm_notify_channel = PWR_EVENT_NOTIFY_PWR_BT; else if (strncmp("uart", optarg, sizeof("uart") - 1) == 0) { if (optarg[sizeof("uart") - 1] == '\0') pm_notify_channel = PWR_EVENT_NOTIFY_UART; else if (strncmp(",allow_trigger_s5", optarg + sizeof("uart") - 1, sizeof(",allow_trigger_s5")) == 0) pm_notify_channel = PWR_EVENT_NOTIFY_UART_TRIG_PLAT_S5; else errx(EX_USAGE, "invalid pm_notify_channel: %s", optarg); } else errx(EX_USAGE, "invalid pm_notify_channel: %s", optarg); break; case CMD_OPT_PM_BY_VUART: /* obsolete parameter */ if (parse_pm_by_vuart(optarg) != 0) errx(EX_USAGE, "invalid pm-by-vuart params %s", optarg); break; case CMD_OPT_WINDOWS: is_winvm = true; break; case CMD_OPT_FORCE_VIRTIO_MSI: virtio_msix = 0; break; case 'h': usage(0); default: usage(1); } } argc -= optind; argv += optind; if (argc != 1) { pr_err("You must provide the name of the Virtual Machine (VM) you want to start. Exiting.\n"); usage(1); } if (lapic_pt == true && is_rtvm == false) { lapic_pt = false; pr_warn("Only a Realtime VM can use local APIC pass through, '--lapic_pt' is invalid here.\n"); } vmname = argv[0]; if (strnlen(vmname, MAX_VM_NAME_LEN) >= MAX_VM_NAME_LEN) { pr_err("The name of the VM exceeds the maximum length: %u\n", MAX_VM_NAME_LEN - 1); exit(1); } if (!init_hugetlb()) { pr_err("init_hugetlb failed\n"); exit(1); } if (gfx_ui) { gfx_ui_init(); } for (;;) { pr_notice("vm_create: %s\n", vmname); ctx = vm_create(vmname, (unsigned long)ioreq_buf, &guest_ncpus); if (!ctx) { pr_err("vm_create failed"); goto create_fail; } if (guest_ncpus < 1) { pr_err("Invalid guest vCPUs (%d)\n", guest_ncpus); goto fail; } max_vcpus = num_vcpus_allowed(ctx); if (guest_ncpus > max_vcpus) { pr_err("%d vCPUs requested but %d available\n", guest_ncpus, max_vcpus); goto fail; } pr_notice("vm_setup_memory: size=0x%lx\n", memsize); error = vm_setup_memory(ctx, memsize); if (error) { pr_err("Unable to setup memory (%d)\n", errno); goto fail; } error = mevent_init(); if (error) { pr_err("Unable to initialize mevent (%d)\n", errno); goto mevent_fail; } pr_notice("vm_init_vdevs\n"); if (vm_init_vdevs(ctx) < 0) { pr_err("Unable to init vdev (%d)\n", errno); goto dev_fail; } /* * build the guest tables, MP etc. */ if (mptgen) { error = mptable_build(ctx, guest_ncpus); if (error) { goto vm_fail; } } error = acpi_build(ctx, guest_ncpus); if (error) { pr_err("acpi_build failed, error=%d\n", error); goto vm_fail; } pr_notice("acrn_sw_load\n"); error = acrn_sw_load(ctx); if (error) { pr_err("acrn_sw_load failed, error=%d\n", error); goto vm_fail; } /* * Change the proc title to include the VM name. */ /*setproctitle("%s", vmname);*/ /* * Add CPU 0 */ pr_notice("add_cpu\n"); error = add_cpu(ctx, guest_ncpus); if (error) { pr_err("add_cpu failed, error=%d\n", error); goto vm_fail; } /* Make a copy for ctx */ _ctx = ctx; /* * Head off to the main event dispatch loop */ mevent_dispatch(); vm_pause(ctx); delete_cpu(ctx, BSP); if (vm_get_suspend_mode() != VM_SUSPEND_FULL_RESET){ ret = 0; break; } vm_deinit_vdevs(ctx); mevent_deinit(); vm_unsetup_memory(ctx); vm_destroy(ctx); _ctx = 0; pr_info("%s: setting VM state to %s\n", __func__, vm_state_to_str(VM_SUSPEND_NONE)); vm_set_suspend_mode(VM_SUSPEND_NONE); } vm_fail: vm_deinit_vdevs(ctx); if (ssram) clean_vssram_configs(); dev_fail: mevent_deinit(); mevent_fail: vm_unsetup_memory(ctx); fail: vm_pause(ctx); vm_destroy(ctx); create_fail: if (cmd_monitor) deinit_cmd_monitor(); if (gfx_ui) { gfx_ui_deinit(); } uninit_hugetlb(); deinit_loggers(); exit(ret); }