2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
4 * Copyright (c) 2011 NetApp, Inc.
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
16 * THIS SOFTWARE IS PROVIDED BY NETAPP, INC ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL NETAPP, INC OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * This file and its contents are supplied under the terms of the
32 * Common Development and Distribution License ("CDDL"), version 1.0.
33 * You may only use this file in accordance with the terms of version
36 * A full copy of the text of the CDDL should have accompanied this
37 * source. A copy of the CDDL is also available via the Internet at
38 * http://www.illumos.org/license/CDDL.
40 * Copyright 2015 Pluribus Networks Inc.
41 * Copyright 2018 Joyent, Inc.
42 * Copyright 2022 Oxide Computer Company
43 * Copyright 2022 OmniOS Community Edition (OmniOSce) Association.
46 #include <sys/cdefs.h>
47 __FBSDID("$FreeBSD$");
49 #include <sys/types.h>
50 #ifndef WITHOUT_CAPSICUM
51 #include <sys/capsicum.h>
57 #include <amd64/vmm/intel/vmcs.h>
59 #include <sys/cpuset.h>
60 #include <intel/vmcs.h>
63 #include <machine/atomic.h>
64 #include <machine/segments.h>
66 #ifndef WITHOUT_CAPSICUM
67 #include <capsicum_helpers.h>
78 #include <pthread_np.h>
83 #include <machine/vmm.h>
84 #ifndef WITHOUT_CAPSICUM
85 #include <machine/vmm_dev.h>
88 #include <machine/vmm_instruction_emul.h>
102 #include "kernemu_dev.h"
106 #include "pci_emul.h"
109 #include "smbiostbl.h"
111 #include "spinup_ap.h"
115 #include "privileges.h"
118 #define GUEST_NIO_PORT 0x488 /* guest upcalls via i/o port */
120 #define MB (1024UL * 1024)
121 #define GB (1024UL * MB)
123 static const char * const vmx_exit_reason_desc
[] = {
124 [EXIT_REASON_EXCEPTION
] = "Exception or non-maskable interrupt (NMI)",
125 [EXIT_REASON_EXT_INTR
] = "External interrupt",
126 [EXIT_REASON_TRIPLE_FAULT
] = "Triple fault",
127 [EXIT_REASON_INIT
] = "INIT signal",
128 [EXIT_REASON_SIPI
] = "Start-up IPI (SIPI)",
129 [EXIT_REASON_IO_SMI
] = "I/O system-management interrupt (SMI)",
130 [EXIT_REASON_SMI
] = "Other SMI",
131 [EXIT_REASON_INTR_WINDOW
] = "Interrupt window",
132 [EXIT_REASON_NMI_WINDOW
] = "NMI window",
133 [EXIT_REASON_TASK_SWITCH
] = "Task switch",
134 [EXIT_REASON_CPUID
] = "CPUID",
135 [EXIT_REASON_GETSEC
] = "GETSEC",
136 [EXIT_REASON_HLT
] = "HLT",
137 [EXIT_REASON_INVD
] = "INVD",
138 [EXIT_REASON_INVLPG
] = "INVLPG",
139 [EXIT_REASON_RDPMC
] = "RDPMC",
140 [EXIT_REASON_RDTSC
] = "RDTSC",
141 [EXIT_REASON_RSM
] = "RSM",
142 [EXIT_REASON_VMCALL
] = "VMCALL",
143 [EXIT_REASON_VMCLEAR
] = "VMCLEAR",
144 [EXIT_REASON_VMLAUNCH
] = "VMLAUNCH",
145 [EXIT_REASON_VMPTRLD
] = "VMPTRLD",
146 [EXIT_REASON_VMPTRST
] = "VMPTRST",
147 [EXIT_REASON_VMREAD
] = "VMREAD",
148 [EXIT_REASON_VMRESUME
] = "VMRESUME",
149 [EXIT_REASON_VMWRITE
] = "VMWRITE",
150 [EXIT_REASON_VMXOFF
] = "VMXOFF",
151 [EXIT_REASON_VMXON
] = "VMXON",
152 [EXIT_REASON_CR_ACCESS
] = "Control-register accesses",
153 [EXIT_REASON_DR_ACCESS
] = "MOV DR",
154 [EXIT_REASON_INOUT
] = "I/O instruction",
155 [EXIT_REASON_RDMSR
] = "RDMSR",
156 [EXIT_REASON_WRMSR
] = "WRMSR",
157 [EXIT_REASON_INVAL_VMCS
] =
158 "VM-entry failure due to invalid guest state",
159 [EXIT_REASON_INVAL_MSR
] = "VM-entry failure due to MSR loading",
160 [EXIT_REASON_MWAIT
] = "MWAIT",
161 [EXIT_REASON_MTF
] = "Monitor trap flag",
162 [EXIT_REASON_MONITOR
] = "MONITOR",
163 [EXIT_REASON_PAUSE
] = "PAUSE",
164 [EXIT_REASON_MCE_DURING_ENTRY
] =
165 "VM-entry failure due to machine-check event",
166 [EXIT_REASON_TPR
] = "TPR below threshold",
167 [EXIT_REASON_APIC_ACCESS
] = "APIC access",
168 [EXIT_REASON_VIRTUALIZED_EOI
] = "Virtualized EOI",
169 [EXIT_REASON_GDTR_IDTR
] = "Access to GDTR or IDTR",
170 [EXIT_REASON_LDTR_TR
] = "Access to LDTR or TR",
171 [EXIT_REASON_EPT_FAULT
] = "EPT violation",
172 [EXIT_REASON_EPT_MISCONFIG
] = "EPT misconfiguration",
173 [EXIT_REASON_INVEPT
] = "INVEPT",
174 [EXIT_REASON_RDTSCP
] = "RDTSCP",
175 [EXIT_REASON_VMX_PREEMPT
] = "VMX-preemption timer expired",
176 [EXIT_REASON_INVVPID
] = "INVVPID",
177 [EXIT_REASON_WBINVD
] = "WBINVD",
178 [EXIT_REASON_XSETBV
] = "XSETBV",
179 [EXIT_REASON_APIC_WRITE
] = "APIC write",
180 [EXIT_REASON_RDRAND
] = "RDRAND",
181 [EXIT_REASON_INVPCID
] = "INVPCID",
182 [EXIT_REASON_VMFUNC
] = "VMFUNC",
183 [EXIT_REASON_ENCLS
] = "ENCLS",
184 [EXIT_REASON_RDSEED
] = "RDSEED",
185 [EXIT_REASON_PM_LOG_FULL
] = "Page-modification log full",
186 [EXIT_REASON_XSAVES
] = "XSAVES",
187 [EXIT_REASON_XRSTORS
] = "XRSTORS"
190 typedef int (*vmexit_handler_t
)(struct vmctx
*, struct vm_exit
*, int *vcpu
);
193 uint16_t cpu_cores
, cpu_sockets
, cpu_threads
;
197 static char *progname
;
198 static const int BSP
= 0;
200 static cpuset_t cpumask
;
202 static void vm_loop(struct vmctx
*ctx
, int vcpu
);
205 static struct vm_entry
*vmentry
;
208 static struct bhyvestats
{
209 uint64_t vmexit_bogus
;
210 uint64_t vmexit_reqidle
;
212 uint64_t vmexit_pause
;
213 uint64_t vmexit_mtrap
;
215 uint64_t vmexit_inst_emul
;
217 uint64_t vmexit_mmio
;
218 uint64_t vmexit_inout
;
219 uint64_t mmio_unhandled
;
221 uint64_t cpu_switch_rotate
;
222 uint64_t cpu_switch_direct
;
225 static struct mt_vmm_info
{
227 struct vmctx
*mt_ctx
;
232 static cpuset_t
**vcpumap
;
241 "Usage: %s [-AaCDeHhPSuWwxY]\n"
243 "Usage: %s [-aCDdeHhPSuWwxY]\n"
245 " %*s [-c [[cpus=]numcpus][,sockets=n][,cores=n][,threads=n]]\n"
247 " %*s [-G port] [-k config_file] [-l lpc] [-m mem] [-o var=value]\n"
248 " %*s [-p vcpu:hostcpu] [-r file] [-s pci] [-U uuid] vmname\n"
250 " -A: create ACPI tables\n"
252 " %*s [-k <config_file>] [-l <lpc>] [-m mem] [-o <var>=<value>]\n"
253 " %*s [-s <pci>] [-U uuid] vmname\n"
255 " -a: local apic is in xAPIC mode (deprecated)\n"
257 " -B type,key=value,...: set SMBIOS information\n"
259 " -C: include guest memory in core file\n"
260 " -c: number of CPUs and/or topology specification\n"
261 " -D: destroy on power-off\n"
263 " -d: suspend cpu at boot\n"
265 " -e: exit on unhandled I/O access\n"
267 " -G: start a debug server\n"
269 " -H: vmexit from the guest on HLT\n"
271 " -k: key=value flat config file\n"
272 " -K: PS2 keyboard layout\n"
273 " -l: LPC device configuration\n"
275 " -o: set config 'var' to 'value'\n"
276 " -P: vmexit from the guest on pause\n"
278 " -p: pin 'vcpu' to 'hostcpu'\n"
280 " -S: guest memory cannot be swapped\n"
281 " -s: <slot,driver,configinfo> PCI slot config\n"
283 " -u: RTC keeps UTC time\n"
284 " -W: force virtio to use single-vector MSI\n"
285 " -w: ignore unimplemented MSRs\n"
286 " -x: local APIC is in x2APIC mode\n"
287 " -Y: disable MPtable generation\n",
288 progname
, (int)strlen(progname
), "", (int)strlen(progname
), "",
289 (int)strlen(progname
), "");
295 * XXX This parser is known to have the following issues:
296 * 1. It accepts null key=value tokens ",," as setting "cpus" to an
299 * The acceptance of a null specification ('-c ""') is by design to match the
300 * manual page syntax specification, this results in a topology of 1 vCPU.
303 topology_parse(const char *opt
)
305 char *cp
, *str
, *tofree
;
308 set_config_value("sockets", "1");
309 set_config_value("cores", "1");
310 set_config_value("threads", "1");
311 set_config_value("cpus", "1");
315 tofree
= str
= strdup(opt
);
317 errx(4, "Failed to allocate memory");
319 while ((cp
= strsep(&str
, ",")) != NULL
) {
320 if (strncmp(cp
, "cpus=", strlen("cpus=")) == 0)
321 set_config_value("cpus", cp
+ strlen("cpus="));
322 else if (strncmp(cp
, "sockets=", strlen("sockets=")) == 0)
323 set_config_value("sockets", cp
+ strlen("sockets="));
324 else if (strncmp(cp
, "cores=", strlen("cores=")) == 0)
325 set_config_value("cores", cp
+ strlen("cores="));
326 else if (strncmp(cp
, "threads=", strlen("threads=")) == 0)
327 set_config_value("threads", cp
+ strlen("threads="));
328 #ifdef notyet /* Do not expose this until vmm.ko implements it */
329 else if (strncmp(cp
, "maxcpus=", strlen("maxcpus=")) == 0)
330 set_config_value("maxcpus", cp
+ strlen("maxcpus="));
332 else if (strchr(cp
, '=') != NULL
)
335 set_config_value("cpus", cp
);
346 parse_int_value(const char *key
, const char *value
, int minval
, int maxval
)
352 lval
= strtol(value
, &cp
, 0);
353 if (errno
!= 0 || *cp
!= '\0' || cp
== value
|| lval
< minval
||
355 errx(4, "Invalid value for %s: '%s'", key
, value
);
360 * Set the sockets, cores, threads, and guest_cpus variables based on
361 * the configured topology.
363 * The limits of UINT16_MAX are due to the types passed to
364 * vm_set_topology(). vmm.ko may enforce tighter limits.
373 value
= get_config_value("cpus");
375 guest_ncpus
= parse_int_value("cpus", value
, 1, UINT16_MAX
);
376 explicit_cpus
= true;
379 explicit_cpus
= false;
381 value
= get_config_value("cores");
383 cpu_cores
= parse_int_value("cores", value
, 1, UINT16_MAX
);
386 value
= get_config_value("threads");
388 cpu_threads
= parse_int_value("threads", value
, 1, UINT16_MAX
);
391 value
= get_config_value("sockets");
393 cpu_sockets
= parse_int_value("sockets", value
, 1, UINT16_MAX
);
395 cpu_sockets
= guest_ncpus
;
398 * Compute sockets * cores * threads avoiding overflow. The
399 * range check above insures these are 16 bit values.
401 ncpus
= (uint64_t)cpu_sockets
* cpu_cores
* cpu_threads
;
402 if (ncpus
> UINT16_MAX
)
403 errx(4, "Computed number of vCPUs too high: %ju",
407 if (guest_ncpus
!= (int)ncpus
)
408 errx(4, "Topology (%d sockets, %d cores, %d threads) "
409 "does not match %d vCPUs",
410 cpu_sockets
, cpu_cores
, cpu_threads
,
418 pincpu_parse(const char *opt
)
425 if (sscanf(opt
, "%d:%d", &vcpu
, &pcpu
) != 2) {
426 fprintf(stderr
, "invalid format: %s\n", opt
);
431 fprintf(stderr
, "invalid vcpu '%d'\n", vcpu
);
435 if (pcpu
< 0 || pcpu
>= CPU_SETSIZE
) {
436 fprintf(stderr
, "hostcpu '%d' outside valid range from "
437 "0 to %d\n", pcpu
, CPU_SETSIZE
- 1);
441 snprintf(key
, sizeof(key
), "vcpu.%d.cpuset", vcpu
);
442 value
= get_config_value(key
);
444 if (asprintf(&newval
, "%s%s%d", value
!= NULL
? value
: "",
445 value
!= NULL
? "," : "", pcpu
) == -1) {
446 perror("failed to build new cpuset string");
450 set_config_value(key
, newval
);
456 parse_cpuset(int vcpu
, const char *list
, cpuset_t
*set
)
463 token
= __DECONST(char *, list
);
465 pcpu
= strtoul(token
, &cp
, 0);
467 errx(4, "invalid cpuset for vcpu %d: '%s'", vcpu
, list
);
468 if (pcpu
< 0 || pcpu
>= CPU_SETSIZE
)
469 errx(4, "hostcpu '%d' outside valid range from 0 to %d",
470 pcpu
, CPU_SETSIZE
- 1);
476 errx(4, "Invalid hostcpu range %d-%d",
478 while (start
< pcpu
) {
488 errx(4, "invalid cpuset for vcpu %d: '%s'",
493 errx(4, "invalid cpuset for vcpu %d: '%s'", vcpu
, list
);
508 vcpumap
= calloc(guest_ncpus
, sizeof(*vcpumap
));
509 for (vcpu
= 0; vcpu
< guest_ncpus
; vcpu
++) {
510 snprintf(key
, sizeof(key
), "vcpu.%d.cpuset", vcpu
);
511 value
= get_config_value(key
);
514 vcpumap
[vcpu
] = malloc(sizeof(cpuset_t
));
515 if (vcpumap
[vcpu
] == NULL
)
516 err(4, "Failed to allocate cpuset for vcpu %d", vcpu
);
517 parse_cpuset(vcpu
, value
, vcpumap
[vcpu
]);
522 vm_inject_fault(void *arg
, int vcpu
, int vector
, int errcode_valid
,
526 int error
, restart_instruction
;
529 restart_instruction
= 1;
531 error
= vm_inject_exception(ctx
, vcpu
, vector
, errcode_valid
, errcode
,
532 restart_instruction
);
535 #endif /* __FreeBSD__ */
538 paddr_guest2host(struct vmctx
*ctx
, uintptr_t gaddr
, size_t len
)
541 return (vm_map_gpa(ctx
, gaddr
, len
));
545 fbsdrun_virtio_msix(void)
548 return (get_config_bool_default("virtio_msix", true));
552 fbsdrun_start_thread(void *param
)
554 char tname
[MAXCOMLEN
+ 1];
555 struct mt_vmm_info
*mtp
;
565 snprintf(tname
, sizeof(tname
), "vcpu %d", vcpu
);
566 pthread_set_name_np(mtp
->mt_thr
, tname
);
569 if (vcpumap
[vcpu
] != NULL
) {
570 error
= pthread_setaffinity_np(pthread_self(),
571 sizeof(cpuset_t
), vcpumap
[vcpu
]);
578 vm_loop(mtp
->mt_ctx
, vcpu
);
586 fbsdrun_addcpu(struct vmctx
*ctx
, int newcpu
, bool suspend
)
590 error
= vm_activate_cpu(ctx
, newcpu
);
592 err(EX_OSERR
, "could not activate CPU %d", newcpu
);
594 CPU_SET_ATOMIC(newcpu
, &cpumask
);
597 (void) vm_suspend_cpu(ctx
, newcpu
);
599 mt_vmm_info
[newcpu
].mt_ctx
= ctx
;
600 mt_vmm_info
[newcpu
].mt_vcpu
= newcpu
;
602 error
= pthread_create(&mt_vmm_info
[newcpu
].mt_thr
, NULL
,
603 fbsdrun_start_thread
, &mt_vmm_info
[newcpu
]);
608 fbsdrun_deletecpu(int vcpu
)
611 if (!CPU_ISSET(vcpu
, &cpumask
)) {
612 fprintf(stderr
, "Attempting to delete unknown cpu %d\n", vcpu
);
616 CPU_CLR_ATOMIC(vcpu
, &cpumask
);
617 return (CPU_EMPTY(&cpumask
));
622 vmentry_mmio_read(int vcpu
, uint64_t gpa
, uint8_t bytes
, uint64_t data
)
624 struct vm_entry
*entry
= &vmentry
[vcpu
];
625 struct vm_mmio
*mmio
= &entry
->u
.mmio
;
627 assert(entry
->cmd
== VEC_DEFAULT
);
629 entry
->cmd
= VEC_FULFILL_MMIO
;
637 vmentry_mmio_write(int vcpu
, uint64_t gpa
, uint8_t bytes
)
639 struct vm_entry
*entry
= &vmentry
[vcpu
];
640 struct vm_mmio
*mmio
= &entry
->u
.mmio
;
642 assert(entry
->cmd
== VEC_DEFAULT
);
644 entry
->cmd
= VEC_FULFILL_MMIO
;
652 vmentry_inout_read(int vcpu
, uint16_t port
, uint8_t bytes
, uint32_t data
)
654 struct vm_entry
*entry
= &vmentry
[vcpu
];
655 struct vm_inout
*inout
= &entry
->u
.inout
;
657 assert(entry
->cmd
== VEC_DEFAULT
);
659 entry
->cmd
= VEC_FULFILL_INOUT
;
660 inout
->bytes
= bytes
;
661 inout
->flags
= INOUT_IN
;
667 vmentry_inout_write(int vcpu
, uint16_t port
, uint8_t bytes
)
669 struct vm_entry
*entry
= &vmentry
[vcpu
];
670 struct vm_inout
*inout
= &entry
->u
.inout
;
672 assert(entry
->cmd
== VEC_DEFAULT
);
674 entry
->cmd
= VEC_FULFILL_INOUT
;
675 inout
->bytes
= bytes
;
683 vmexit_handle_notify(struct vmctx
*ctx __unused
, struct vm_exit
*vme __unused
,
684 int *pvcpu __unused
, uint32_t eax __unused
)
688 * put guest-driven debug here
691 return (VMEXIT_CONTINUE
);
695 vmexit_inout(struct vmctx
*ctx
, struct vm_exit
*vme
, int *pvcpu
)
699 struct vm_inout inout
;
703 stats
.vmexit_inout
++;
706 inout
= vme
->u
.inout
;
707 in
= (inout
.flags
& INOUT_IN
) != 0;
710 /* Extra-special case of host notifications */
711 if (!in
&& inout
.port
== GUEST_NIO_PORT
) {
712 error
= vmexit_handle_notify(ctx
, vme
, pvcpu
, inout
.eax
);
713 vmentry_inout_write(vcpu
, inout
.port
, bytes
);
717 error
= emulate_inout(ctx
, vcpu
, &inout
);
719 fprintf(stderr
, "Unhandled %s%c 0x%04x at 0x%lx\n",
721 bytes
== 1 ? 'b' : (bytes
== 2 ? 'w' : 'l'),
722 inout
.port
, vme
->rip
);
723 return (VMEXIT_ABORT
);
726 * Communicate the status of the inout operation back to the
727 * in-kernel instruction emulation.
730 vmentry_inout_read(vcpu
, inout
.port
, bytes
, inout
.eax
);
732 vmentry_inout_write(vcpu
, inout
.port
, bytes
);
734 return (VMEXIT_CONTINUE
);
739 vmexit_rdmsr(struct vmctx
*ctx
, struct vm_exit
*vme
, int *pvcpu
)
746 error
= emulate_rdmsr(ctx
, *pvcpu
, vme
->u
.msr
.code
, &val
);
748 fprintf(stderr
, "rdmsr to register %#x on vcpu %d\n",
749 vme
->u
.msr
.code
, *pvcpu
);
750 if (get_config_bool("x86.strictmsr")) {
751 vm_inject_gp(ctx
, *pvcpu
);
752 return (VMEXIT_CONTINUE
);
757 error
= vm_set_register(ctx
, *pvcpu
, VM_REG_GUEST_RAX
, eax
);
761 error
= vm_set_register(ctx
, *pvcpu
, VM_REG_GUEST_RDX
, edx
);
764 return (VMEXIT_CONTINUE
);
768 vmexit_wrmsr(struct vmctx
*ctx
, struct vm_exit
*vme
, int *pvcpu
)
772 error
= emulate_wrmsr(ctx
, *pvcpu
, vme
->u
.msr
.code
, vme
->u
.msr
.wval
);
774 fprintf(stderr
, "wrmsr to register %#x(%#lx) on vcpu %d\n",
775 vme
->u
.msr
.code
, vme
->u
.msr
.wval
, *pvcpu
);
776 if (get_config_bool("x86.strictmsr")) {
777 vm_inject_gp(ctx
, *pvcpu
);
778 return (VMEXIT_CONTINUE
);
781 return (VMEXIT_CONTINUE
);
786 vmexit_run_state(struct vmctx
*ctx
, struct vm_exit
*vme
, int *pvcpu
)
789 * Run-state transitions (INIT, SIPI, etc) are handled in-kernel, so an
790 * exit to userspace with that code is not expected.
792 fprintf(stderr
, "unexpected run-state VM exit");
793 return (VMEXIT_ABORT
);
797 vmexit_paging(struct vmctx
*ctx
, struct vm_exit
*vmexit
, int *pvcpu
)
799 fprintf(stderr
, "vm exit[%d]\n", *pvcpu
);
800 fprintf(stderr
, "\treason\t\tPAGING\n");
801 fprintf(stderr
, "\trip\t\t0x%016lx\n", vmexit
->rip
);
802 fprintf(stderr
, "\tgpa\t\t0x%016lx\n", vmexit
->u
.paging
.gpa
);
803 fprintf(stderr
, "\tfault_type\t\t%d\n", vmexit
->u
.paging
.fault_type
);
805 return (VMEXIT_ABORT
);
807 #endif /* __FreeBSD__ */
810 #define DEBUG_EPT_MISCONFIG
812 /* EPT misconfig debugging not possible now that raw VMCS access is gone */
815 #ifdef DEBUG_EPT_MISCONFIG
816 #define VMCS_GUEST_PHYSICAL_ADDRESS 0x00002400
818 static uint64_t ept_misconfig_gpa
, ept_misconfig_pte
[4];
819 static int ept_misconfig_ptenum
;
823 vmexit_vmx_desc(uint32_t exit_reason
)
826 if (exit_reason
>= nitems(vmx_exit_reason_desc
) ||
827 vmx_exit_reason_desc
[exit_reason
] == NULL
)
829 return (vmx_exit_reason_desc
[exit_reason
]);
833 vmexit_vmx(struct vmctx
*ctx
, struct vm_exit
*vme
, int *pvcpu
)
836 fprintf(stderr
, "vm exit[%d]\n", *pvcpu
);
837 fprintf(stderr
, "\treason\t\tVMX\n");
838 fprintf(stderr
, "\trip\t\t0x%016lx\n", vme
->rip
);
839 fprintf(stderr
, "\tinst_length\t%d\n", vme
->inst_length
);
840 fprintf(stderr
, "\tstatus\t\t%d\n", vme
->u
.vmx
.status
);
841 fprintf(stderr
, "\texit_reason\t%u (%s)\n", vme
->u
.vmx
.exit_reason
,
842 vmexit_vmx_desc(vme
->u
.vmx
.exit_reason
));
843 fprintf(stderr
, "\tqualification\t0x%016lx\n",
844 vme
->u
.vmx
.exit_qualification
);
845 fprintf(stderr
, "\tinst_type\t\t%d\n", vme
->u
.vmx
.inst_type
);
846 fprintf(stderr
, "\tinst_error\t\t%d\n", vme
->u
.vmx
.inst_error
);
847 #ifdef DEBUG_EPT_MISCONFIG
848 if (vme
->u
.vmx
.exit_reason
== EXIT_REASON_EPT_MISCONFIG
) {
849 vm_get_register(ctx
, *pvcpu
,
850 VMCS_IDENT(VMCS_GUEST_PHYSICAL_ADDRESS
),
852 vm_get_gpa_pmap(ctx
, ept_misconfig_gpa
, ept_misconfig_pte
,
853 &ept_misconfig_ptenum
);
854 fprintf(stderr
, "\tEPT misconfiguration:\n");
855 fprintf(stderr
, "\t\tGPA: %#lx\n", ept_misconfig_gpa
);
856 fprintf(stderr
, "\t\tPTE(%d): %#lx %#lx %#lx %#lx\n",
857 ept_misconfig_ptenum
, ept_misconfig_pte
[0],
858 ept_misconfig_pte
[1], ept_misconfig_pte
[2],
859 ept_misconfig_pte
[3]);
861 #endif /* DEBUG_EPT_MISCONFIG */
862 return (VMEXIT_ABORT
);
866 vmexit_svm(struct vmctx
*ctx __unused
, struct vm_exit
*vme
, int *pvcpu
)
869 fprintf(stderr
, "vm exit[%d]\n", *pvcpu
);
870 fprintf(stderr
, "\treason\t\tSVM\n");
871 fprintf(stderr
, "\trip\t\t0x%016lx\n", vme
->rip
);
872 fprintf(stderr
, "\tinst_length\t%d\n", vme
->inst_length
);
873 fprintf(stderr
, "\texitcode\t%#lx\n", vme
->u
.svm
.exitcode
);
874 fprintf(stderr
, "\texitinfo1\t%#lx\n", vme
->u
.svm
.exitinfo1
);
875 fprintf(stderr
, "\texitinfo2\t%#lx\n", vme
->u
.svm
.exitinfo2
);
876 return (VMEXIT_ABORT
);
880 vmexit_bogus(struct vmctx
*ctx __unused
, struct vm_exit
*vme
,
884 assert(vme
->inst_length
== 0);
886 stats
.vmexit_bogus
++;
888 return (VMEXIT_CONTINUE
);
892 vmexit_reqidle(struct vmctx
*ctx __unused
, struct vm_exit
*vme
,
896 assert(vme
->inst_length
== 0);
898 stats
.vmexit_reqidle
++;
900 return (VMEXIT_CONTINUE
);
904 vmexit_hlt(struct vmctx
*ctx __unused
, struct vm_exit
*vme __unused
,
911 * Just continue execution with the next instruction. We use
912 * the HLT VM exit as a way to be friendly with the host
915 return (VMEXIT_CONTINUE
);
919 vmexit_pause(struct vmctx
*ctx __unused
, struct vm_exit
*vme __unused
,
923 stats
.vmexit_pause
++;
925 return (VMEXIT_CONTINUE
);
929 vmexit_mtrap(struct vmctx
*ctx __unused
, struct vm_exit
*vme
, int *pvcpu
)
932 assert(vme
->inst_length
== 0);
934 stats
.vmexit_mtrap
++;
936 gdb_cpu_mtrap(*pvcpu
);
938 return (VMEXIT_CONTINUE
);
942 vmexit_inst_emul(struct vmctx
*ctx
, struct vm_exit
*vme
, int *pvcpu
)
946 fprintf(stderr
, "Failed to emulate instruction sequence ");
948 valid
= vme
->u
.inst_emul
.num_valid
;
950 assert(valid
<= sizeof (vme
->u
.inst_emul
.inst
));
951 fprintf(stderr
, "[");
952 for (i
= 0; i
< valid
; i
++) {
954 fprintf(stderr
, "%02x",
955 vme
->u
.inst_emul
.inst
[i
]);
957 fprintf(stderr
, ", %02x",
958 vme
->u
.inst_emul
.inst
[i
]);
961 fprintf(stderr
, "] ");
963 fprintf(stderr
, "@ %rip = %x\n", vme
->rip
);
965 return (VMEXIT_ABORT
);
969 vmexit_mmio(struct vmctx
*ctx
, struct vm_exit
*vmexit
, int *pvcpu
)
978 mmio
= vmexit
->u
.mmio
;
979 is_read
= (mmio
.read
!= 0);
981 err
= emulate_mem(ctx
, vcpu
, &mmio
);
984 fprintf(stderr
, "Unhandled memory access to 0x%lx\n", mmio
.gpa
);
985 stats
.mmio_unhandled
++;
988 * Access to non-existent physical addresses is not likely to
989 * result in fatal errors on hardware machines, but rather reads
990 * of all-ones or discarded-but-acknowledged writes.
998 vmentry_mmio_read(vcpu
, mmio
.gpa
, mmio
.bytes
,
1001 vmentry_mmio_write(vcpu
, mmio
.gpa
, mmio
.bytes
);
1003 return (VMEXIT_CONTINUE
);
1006 fprintf(stderr
, "Unhandled mmio error to 0x%lx: %d\n", mmio
.gpa
, err
);
1007 return (VMEXIT_ABORT
);
1010 static pthread_mutex_t resetcpu_mtx
= PTHREAD_MUTEX_INITIALIZER
;
1011 static pthread_cond_t resetcpu_cond
= PTHREAD_COND_INITIALIZER
;
1014 vmexit_suspend(struct vmctx
*ctx
, struct vm_exit
*vme
, int *pvcpu
)
1016 enum vm_suspend_how how
;
1018 how
= vme
->u
.suspended
.how
;
1020 fbsdrun_deletecpu(*pvcpu
);
1022 if (*pvcpu
!= BSP
) {
1023 pthread_mutex_lock(&resetcpu_mtx
);
1024 pthread_cond_signal(&resetcpu_cond
);
1025 pthread_mutex_unlock(&resetcpu_mtx
);
1029 pthread_mutex_lock(&resetcpu_mtx
);
1030 while (!CPU_EMPTY(&cpumask
)) {
1031 pthread_cond_wait(&resetcpu_cond
, &resetcpu_mtx
);
1033 pthread_mutex_unlock(&resetcpu_mtx
);
1036 case VM_SUSPEND_RESET
:
1038 case VM_SUSPEND_POWEROFF
:
1039 if (get_config_bool_default("destroy_on_poweroff", false))
1042 case VM_SUSPEND_HALT
:
1044 case VM_SUSPEND_TRIPLEFAULT
:
1047 fprintf(stderr
, "vmexit_suspend: invalid reason %d\n", how
);
1050 return (0); /* NOTREACHED */
1054 vmexit_debug(struct vmctx
*ctx __unused
, struct vm_exit
*vme __unused
,
1058 gdb_cpu_suspend(*pvcpu
);
1059 return (VMEXIT_CONTINUE
);
1063 vmexit_breakpoint(struct vmctx
*ctx __unused
, struct vm_exit
*vme
, int *pvcpu
)
1066 gdb_cpu_breakpoint(*pvcpu
, vme
);
1067 return (VMEXIT_CONTINUE
);
1072 vmexit_ipi(struct vmctx
*ctx
, struct vm_exit
*vme
, int *pvcpu __unused
)
1076 switch (vme
->u
.ipi
.mode
) {
1077 case APIC_DELMODE_INIT
:
1078 CPU_FOREACH_ISSET(i
, &vme
->u
.ipi
.dmask
) {
1079 error
= vm_suspend_cpu(ctx
, i
);
1081 warnx("%s: failed to suspend cpu %d\n",
1087 case APIC_DELMODE_STARTUP
:
1088 CPU_FOREACH_ISSET(i
, &vme
->u
.ipi
.dmask
) {
1089 spinup_ap(ctx
, i
, vme
->u
.ipi
.vector
<< PAGE_SHIFT
);
1101 static vmexit_handler_t handler
[VM_EXITCODE_MAX
] = {
1102 [VM_EXITCODE_INOUT
] = vmexit_inout
,
1103 [VM_EXITCODE_MMIO
] = vmexit_mmio
,
1104 [VM_EXITCODE_VMX
] = vmexit_vmx
,
1105 [VM_EXITCODE_SVM
] = vmexit_svm
,
1106 [VM_EXITCODE_BOGUS
] = vmexit_bogus
,
1107 [VM_EXITCODE_REQIDLE
] = vmexit_reqidle
,
1108 [VM_EXITCODE_RDMSR
] = vmexit_rdmsr
,
1109 [VM_EXITCODE_WRMSR
] = vmexit_wrmsr
,
1110 [VM_EXITCODE_MTRAP
] = vmexit_mtrap
,
1111 [VM_EXITCODE_INST_EMUL
] = vmexit_inst_emul
,
1113 [VM_EXITCODE_RUN_STATE
] = vmexit_run_state
,
1114 [VM_EXITCODE_PAGING
] = vmexit_paging
,
1115 [VM_EXITCODE_HLT
] = vmexit_hlt
,
1117 [VM_EXITCODE_SUSPENDED
] = vmexit_suspend
,
1118 [VM_EXITCODE_TASK_SWITCH
] = vmexit_task_switch
,
1119 [VM_EXITCODE_DEBUG
] = vmexit_debug
,
1120 [VM_EXITCODE_BPT
] = vmexit_breakpoint
,
1122 [VM_EXITCODE_IPI
] = vmexit_ipi
,
1127 vm_loop(struct vmctx
*ctx
, int vcpu
)
1131 enum vm_exitcode exitcode
;
1132 cpuset_t active_cpus
;
1133 struct vm_entry
*ventry
;
1135 error
= vm_active_cpus(ctx
, &active_cpus
);
1136 assert(CPU_ISSET(vcpu
, &active_cpus
));
1138 ventry
= &vmentry
[vcpu
];
1141 error
= vm_run(ctx
, vcpu
, ventry
, &vme
);
1145 if (ventry
->cmd
!= VEC_DEFAULT
) {
1147 * Discard any lingering entry state after it has been
1148 * submitted via vm_run().
1150 bzero(ventry
, sizeof (*ventry
));
1153 exitcode
= vme
.exitcode
;
1154 if (exitcode
>= VM_EXITCODE_MAX
|| handler
[exitcode
] == NULL
) {
1155 fprintf(stderr
, "vm_loop: unexpected exitcode 0x%x\n",
1160 rc
= (*handler
[exitcode
])(ctx
, &vme
, &vcpu
);
1163 case VMEXIT_CONTINUE
:
1171 fprintf(stderr
, "vm_run error %d, errno %d\n", error
, errno
);
1175 num_vcpus_allowed(struct vmctx
*ctx
)
1177 uint16_t sockets
, cores
, threads
, maxcpus
;
1182 * The guest is allowed to spinup more than one processor only if the
1183 * UNRESTRICTED_GUEST capability is available.
1185 error
= vm_get_capability(ctx
, BSP
, VM_CAP_UNRESTRICTED_GUEST
, &tmp
);
1190 /* Unrestricted Guest is always enabled on illumos */
1192 #endif /* __FreeBSD__ */
1194 error
= vm_get_topology(ctx
, &sockets
, &cores
, &threads
, &maxcpus
);
1202 fbsdrun_set_capabilities(struct vmctx
*ctx
, int cpu
)
1207 if (get_config_bool_default("x86.vmexit_on_hlt", false)) {
1208 err
= vm_get_capability(ctx
, cpu
, VM_CAP_HALT_EXIT
, &tmp
);
1210 fprintf(stderr
, "VM exit on HLT not supported\n");
1213 vm_set_capability(ctx
, cpu
, VM_CAP_HALT_EXIT
, 1);
1215 handler
[VM_EXITCODE_HLT
] = vmexit_hlt
;
1219 * We insist that vmexit-on-hlt is available on the host CPU, and enable
1220 * it by default. Configuration of that feature is done with both of
1221 * those facts in mind.
1223 tmp
= (int)get_config_bool_default("x86.vmexit_on_hlt", true);
1224 err
= vm_set_capability(ctx
, cpu
, VM_CAP_HALT_EXIT
, tmp
);
1226 fprintf(stderr
, "VM exit on HLT not supported\n");
1229 #endif /* __FreeBSD__ */
1231 if (get_config_bool_default("x86.vmexit_on_pause", false)) {
1233 * pause exit support required for this mode
1235 err
= vm_get_capability(ctx
, cpu
, VM_CAP_PAUSE_EXIT
, &tmp
);
1238 "SMP mux requested, no pause support\n");
1241 vm_set_capability(ctx
, cpu
, VM_CAP_PAUSE_EXIT
, 1);
1243 handler
[VM_EXITCODE_PAUSE
] = vmexit_pause
;
1246 if (get_config_bool_default("x86.x2apic", false))
1247 err
= vm_set_x2apic_state(ctx
, cpu
, X2APIC_ENABLED
);
1249 err
= vm_set_x2apic_state(ctx
, cpu
, X2APIC_DISABLED
);
1252 fprintf(stderr
, "Unable to set x2apic state (%d)\n", err
);
1257 vm_set_capability(ctx
, cpu
, VM_CAP_ENABLE_INVPCID
, 1);
1259 err
= vm_set_capability(ctx
, cpu
, VM_CAP_IPI_EXIT
, 1);
1264 static struct vmctx
*
1265 do_open(const char *vmname
)
1269 bool reinit
, romboot
;
1271 reinit
= romboot
= false;
1276 uint64_t create_flags
= 0;
1277 if (get_config_bool_default("memory.use_reservoir", false)) {
1278 create_flags
|= VCF_RESERVOIR_MEM
;
1280 error
= vm_create(vmname
, create_flags
);
1282 error
= vm_create(vmname
);
1283 #endif /* __FreeBSD__ */
1285 if (errno
== EEXIST
) {
1290 * The virtual machine has been setup by the
1291 * userspace bootloader.
1295 perror("vm_create");
1301 * If the virtual machine was just created then a
1302 * bootrom must be configured to boot it.
1304 fprintf(stderr
, "virtual machine cannot be booted\n");
1309 ctx
= vm_open(vmname
);
1315 #ifndef WITHOUT_CAPSICUM
1316 if (vm_limit_rights(ctx
) != 0)
1317 err(EX_OSERR
, "vm_limit_rights");
1322 error
= vm_reinit(ctx
, 0);
1324 error
= vm_reinit(ctx
);
1327 perror("vm_reinit");
1331 error
= vm_set_topology(ctx
, cpu_sockets
, cpu_cores
, cpu_threads
,
1332 0 /* maxcpus, unimplemented */);
1334 errx(EX_OSERR
, "vm_set_topology");
1339 spinup_vcpu(struct vmctx
*ctx
, int vcpu
, bool suspend
)
1346 * On illumos, all APs are spun up halted and run-state
1347 * transitions (INIT, SIPI, etc) are handled in-kernel.
1349 spinup_ap(ctx
, vcpu
, 0);
1352 fbsdrun_set_capabilities(ctx
, vcpu
);
1356 * Enable the 'unrestricted guest' mode for APs.
1358 * APs startup in power-on 16-bit mode.
1360 error
= vm_set_capability(ctx
, vcpu
, VM_CAP_UNRESTRICTED_GUEST
, 1);
1367 * The value of 'suspend' for the BSP depends on whether the -d
1368 * (suspend_at_boot) flag was given to bhyve. Regardless of that
1369 * value we always want to set the BSP to VRS_RUN and all others to
1372 error
= vm_set_run_state(ctx
, vcpu
,
1373 vcpu
== BSP
? VRS_RUN
: VRS_HALT
, 0);
1377 fbsdrun_addcpu(ctx
, vcpu
, suspend
);
1381 parse_config_option(const char *option
)
1386 value
= strchr(option
, '=');
1387 if (value
== NULL
|| value
[1] == '\0')
1389 path
= strndup(option
, value
- option
);
1391 err(4, "Failed to allocate memory");
1392 set_config_value(path
, value
+ 1);
1397 parse_simple_config_file(const char *path
)
1402 unsigned int lineno
;
1404 fp
= fopen(path
, "r");
1406 err(4, "Failed to open configuration file %s", path
);
1410 for (lineno
= 1; getline(&line
, &linecap
, fp
) > 0; lineno
++) {
1411 if (*line
== '#' || *line
== '\n')
1413 cp
= strchr(line
, '\n');
1416 if (!parse_config_option(line
))
1417 errx(4, "%s line %u: invalid config option '%s'", path
,
1425 parse_gdb_options(const char *opt
)
1430 if (opt
[0] == 'w') {
1431 set_config_bool("gdb.wait", true);
1435 colon
= strrchr(opt
, ':');
1436 if (colon
== NULL
) {
1442 set_config_value("gdb.address", opt
);
1445 set_config_value("gdb.port", sport
);
1452 set_config_bool("acpi_tables", false);
1453 set_config_value("memory.size", "256M");
1454 set_config_bool("x86.strictmsr", true);
1458 main(int argc
, char *argv
[])
1461 int max_vcpus
, memflags
;
1465 const char *optstr
, *value
, *vmname
;
1469 progname
= basename(argv
[0]);
1472 optstr
= "aehuwxACDHIPSWYk:o:p:G:c:s:m:l:K:U:";
1475 optstr
= "adehuwxACDHIPSWYk:o:G:c:s:m:l:B:K:U:";
1477 while ((c
= getopt(argc
, argv
, optstr
)) != -1) {
1480 set_config_bool("x86.x2apic", false);
1485 * This option is ignored on illumos since the
1486 * generated ACPI tables are not used; the bootroms
1487 * have their own. The option is retained for backwards
1488 * compatibility but does nothing. Note that the
1489 * acpi_tables configuration is still accepted via
1490 * -o if somebody really wants to generate these tables.
1492 set_config_bool("acpi_tables", true);
1496 set_config_bool("destroy_on_poweroff", true);
1500 if (smbios_parse(optarg
) != 0) {
1501 errx(EX_USAGE
, "invalid SMBIOS "
1502 "configuration '%s'", optarg
);
1506 set_config_bool("suspend_at_boot", true);
1511 if (pincpu_parse(optarg
) != 0) {
1512 errx(EX_USAGE
, "invalid vcpu pinning "
1513 "configuration '%s'", optarg
);
1518 if (topology_parse(optarg
) != 0) {
1519 errx(EX_USAGE
, "invalid cpu topology "
1524 set_config_bool("memory.guest_in_core", true);
1527 parse_gdb_options(optarg
);
1530 parse_simple_config_file(optarg
);
1533 set_config_value("keyboard.layout", optarg
);
1536 if (strncmp(optarg
, "help", strlen(optarg
)) == 0) {
1537 lpc_print_supported_devices();
1539 } else if (lpc_device_parse(optarg
) != 0) {
1540 errx(EX_USAGE
, "invalid lpc device "
1541 "configuration '%s'", optarg
);
1545 if (strncmp(optarg
, "help", strlen(optarg
)) == 0) {
1546 pci_print_supported_devices();
1548 } else if (pci_parse_slot(optarg
) != 0)
1553 set_config_bool("memory.wired", true);
1556 set_config_value("memory.size", optarg
);
1559 if (!parse_config_option(optarg
))
1560 errx(EX_USAGE
, "invalid configuration option '%s'", optarg
);
1563 set_config_bool("x86.vmexit_on_hlt", true);
1567 * The "-I" option was used to add an ioapic to the
1570 * An ioapic is now provided unconditionally for each
1571 * virtual machine and this option is now deprecated.
1575 set_config_bool("x86.vmexit_on_pause", true);
1578 set_config_bool("x86.strictio", true);
1581 set_config_bool("rtc.use_localtime", false);
1584 set_config_value("uuid", optarg
);
1587 set_config_bool("x86.strictmsr", false);
1590 set_config_bool("virtio_msix", false);
1593 set_config_bool("x86.x2apic", true);
1596 set_config_bool("x86.mptable", false);
1611 set_config_value("name", argv
[0]);
1613 vmname
= get_config_value("name");
1617 if (get_config_bool_default("config.dump", false)) {
1623 illumos_priv_init();
1632 value
= get_config_value("memory.size");
1633 error
= vm_parse_memsize(value
, &memsize
);
1635 errx(EX_USAGE
, "invalid memsize '%s'", value
);
1637 ctx
= do_open(vmname
);
1639 max_vcpus
= num_vcpus_allowed(ctx
);
1640 if (guest_ncpus
> max_vcpus
) {
1641 fprintf(stderr
, "%d vCPUs requested but only %d available\n",
1642 guest_ncpus
, max_vcpus
);
1646 fbsdrun_set_capabilities(ctx
, BSP
);
1649 if (get_config_bool_default("memory.wired", false))
1650 memflags
|= VM_MEM_F_WIRED
;
1651 if (get_config_bool_default("memory.guest_in_core", false))
1652 memflags
|= VM_MEM_F_INCORE
;
1653 vm_set_memflags(ctx
, memflags
);
1655 err
= vm_setup_memory(ctx
, memsize
, VM_MMAP_ALL
);
1659 err
= vm_setup_memory(ctx
, memsize
, VM_MMAP_ALL
);
1661 if (err
!= 0 && error
== ENOMEM
) {
1662 (void) fprintf(stderr
, "Unable to allocate memory "
1663 "(%llu), retrying in 1 second\n", memsize
);
1666 } while (error
== ENOMEM
);
1669 fprintf(stderr
, "Unable to set up memory (%d)\n", errno
);
1675 fprintf(stderr
, "init_msr error %d", error
);
1679 init_mem(guest_ncpus
);
1696 * Exit if a device emulation finds an error in its initilization
1698 if (init_pci(ctx
) != 0) {
1699 perror("device emulation initialization error");
1704 * Initialize after PCI, to allow a bootrom file to reserve the high
1707 if (get_config_bool("acpi_tables"))
1713 if (value
!= NULL
) {
1714 int port
= atoi(value
);
1723 if (lpc_bootrom()) {
1725 if (vm_set_capability(ctx
, BSP
, VM_CAP_UNRESTRICTED_GUEST
, 1)) {
1726 fprintf(stderr
, "ROM boot failed: unrestricted guest "
1727 "capability not available\n");
1731 /* Unrestricted Guest is always enabled on illumos */
1733 error
= vcpu_reset(ctx
, BSP
);
1737 error
= vm_get_register(ctx
, BSP
, VM_REG_GUEST_RIP
, &rip
);
1741 * build the guest tables, MP etc.
1743 if (get_config_bool_default("x86.mptable", true)) {
1744 error
= mptable_build(ctx
, guest_ncpus
);
1746 perror("error to build the guest tables");
1751 error
= smbios_build(ctx
);
1755 if (get_config_bool("acpi_tables")) {
1756 error
= acpi_build(ctx
, guest_ncpus
);
1764 * Change the proc title to include the VM name.
1766 setproctitle("%s", vmname
);
1768 #ifndef WITHOUT_CAPSICUM
1769 caph_cache_catpages();
1771 if (caph_limit_stdout() == -1 || caph_limit_stderr() == -1)
1772 errx(EX_OSERR
, "Unable to apply rights for sandbox");
1774 if (caph_enter() == -1)
1775 errx(EX_OSERR
, "cap_enter() failed");
1779 illumos_priv_lock();
1782 /* Allocate per-VCPU resources. */
1783 mt_vmm_info
= calloc(guest_ncpus
, sizeof(*mt_vmm_info
));
1785 vmentry
= calloc(guest_ncpus
, sizeof(*vmentry
));
1791 for (int vcpu
= 0; vcpu
< guest_ncpus
; vcpu
++) {
1793 bool suspend
= (vcpu
!= BSP
);
1795 bool suspend
= vcpu
== BSP
&&
1796 get_config_bool_default("suspend_at_boot", false);
1798 spinup_vcpu(ctx
, vcpu
, suspend
);
1802 * Head off to the main event dispatch loop