1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12 /*
13 * Copyright 2023 Oxide Computer Company
14 */
15
16 #include <stdio.h>
17 #include <unistd.h>
18 #include <stdlib.h>
19 #include <strings.h>
20 #include <libgen.h>
21 #include <assert.h>
22 #include <errno.h>
23
24 #include <sys/types.h>
25 #include <sys/sysmacros.h>
26 #include <sys/debug.h>
27 #include <sys/vmm.h>
28 #include <sys/vmm_dev.h>
29 #include <vmmapi.h>
30
31 #include "in_guest.h"
32
33 static void
run_until_unhandled(struct vcpu * vcpu,struct vm_entry * ventry,struct vm_exit * vexit)34 run_until_unhandled(struct vcpu *vcpu, struct vm_entry *ventry,
35 struct vm_exit *vexit)
36 {
37 do {
38 const enum vm_exit_kind kind =
39 test_run_vcpu(vcpu, ventry, vexit);
40 switch (kind) {
41 case VEK_REENTR:
42 break;
43 case VEK_UNHANDLED:
44 return;
45 default:
46 /*
47 * We are not expecting the payload to use any of the
48 * pass/fail/messaging facilities during this test.
49 */
50 test_fail_vmexit(vexit);
51 break;
52 }
53 } while (true);
54 }
55
56 static void
repeat_consistent_exit(struct vcpu * vcpu,struct vm_entry * ventry,struct vm_exit * vexit,uint64_t expected_rip)57 repeat_consistent_exit(struct vcpu *vcpu, struct vm_entry *ventry,
58 struct vm_exit *vexit, uint64_t expected_rip)
59 {
60 ventry->cmd = VEC_DEFAULT | VEC_FLAG_EXIT_CONSISTENT;
61 if (vm_run(vcpu, ventry, vexit) != 0) {
62 test_fail_errno(errno, "Failure during vcpu entry");
63 }
64 if (vexit->rip != expected_rip) {
65 test_fail_msg(
66 "Unexpected forward progress when vCPU already consistent");
67 }
68 }
69
70 int
main(int argc,char * argv[])71 main(int argc, char *argv[])
72 {
73 const char *test_suite_name = basename(argv[0]);
74 struct vmctx *ctx = NULL;
75 struct vcpu *vcpu;
76 int err;
77
78 ctx = test_initialize(test_suite_name);
79
80 if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) {
81 test_fail_errno(errno, "Could not open vcpu0");
82 }
83 err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK);
84 if (err != 0) {
85 test_fail_errno(err, "Could not initialize vcpu0");
86 }
87
88 struct vm_entry ventry = { 0 };
89 struct vm_exit vexit = { 0 };
90
91 /*
92 * Let the payload run until it reaches the first userspace exit which
93 * requires actual handling
94 */
95 run_until_unhandled(vcpu, &ventry, &vexit);
96 if (vexit.exitcode != VM_EXITCODE_RDMSR) {
97 test_fail_vmexit(&vexit);
98 }
99 uint64_t rcx = 0, rip = 0;
100 if (vm_get_register(vcpu, VM_REG_GUEST_RCX, &rcx) != 0) {
101 test_fail_errno(errno, "Could not read guest %rcx");
102 }
103 if (vm_get_register(vcpu, VM_REG_GUEST_RIP, &rip) != 0) {
104 test_fail_errno(errno, "Could not read guest %rip");
105 }
106 /* Paranoia: confirm that in-register %rip matches vm_exit data */
107 if (rip != vexit.rip) {
108 test_fail_msg(
109 "vm_exit`rip does not match in-kernel %rip: %lx != %lx",
110 rip, vexit.rip);
111 }
112
113 /* Request a consistent exit */
114 ventry.cmd = VEC_DEFAULT | VEC_FLAG_EXIT_CONSISTENT;
115 if (vm_run(vcpu, &ventry, &vexit) != 0) {
116 test_fail_errno(errno, "Failure during vcpu entry");
117 }
118
119 /*
120 * We expect the consistent exit to have completed the instruction
121 * emulation for the rdmsr (just move the %rip forward, since its left
122 * to userspace to update %rax:%rdx) and emit the BOGUS exitcode.
123 */
124 if (vexit.exitcode != VM_EXITCODE_BOGUS) {
125 test_fail_msg("Unexpected exitcode: %d != %d",
126 vexit.exitcode, VM_EXITCODE_BOGUS);
127 }
128
129 /*
130 * Check that the %rip moved forward only the 2 bytes expected for a
131 * rdmsr opcode.
132 */
133 if (vexit.rip != (rip + 2)) {
134 test_fail_msg("Exited at unexpected %rip: %lx != %lx",
135 vexit.rip, rip + 2);
136 }
137
138 /*
139 * Repeat entry with consistency request. This should not make any
140 * forward progress since the vCPU is already in a consistent state.
141 */
142 repeat_consistent_exit(vcpu, &ventry, &vexit, vexit.rip);
143
144 /* Let the vCPU continue on to the next exit condition */
145 ventry.cmd = VEC_DEFAULT;
146 run_until_unhandled(vcpu, &ventry, &vexit);
147
148 const uint64_t read_addr = 0xc0000000;
149 const uint_t read_len = 4;
150 if (!vexit_match_mmio(&vexit, true, read_addr, read_len, NULL)) {
151 test_fail_vmexit(&vexit);
152 }
153 rip = vexit.rip;
154
155 /*
156 * An attempt to push the vCPU to a consistent state without first
157 * fulfilling the MMIO should just result in the same MMIO exit.
158 */
159 ventry.cmd = VEC_DEFAULT | VEC_FLAG_EXIT_CONSISTENT;
160 if (vm_run(vcpu, &ventry, &vexit) != 0) {
161 test_fail_errno(errno, "Failure during vcpu entry");
162 }
163 if (vexit.rip != rip ||
164 !vexit_match_mmio(&vexit, true, read_addr, read_len, NULL)) {
165 test_fail_msg(
166 "Unexpected forward progress during MMIO emulation");
167 }
168
169 /* Fulfill the MMIO and attempt another consistent exit */
170 ventry_fulfill_mmio(&vexit, &ventry, 0);
171 ventry.cmd |= VEC_FLAG_EXIT_CONSISTENT;
172 if (vm_run(vcpu, &ventry, &vexit) != 0) {
173 test_fail_errno(errno, "Failure during vcpu entry");
174 }
175
176 /* With current payload, we expect a 3-byte mov instruction */
177 if (vexit.rip != (rip + 3)) {
178 test_fail_msg("Exited at unexpected %rip: %lx != %lx",
179 vexit.rip, rip + 3);
180 }
181
182 /*
183 * And again, check that vCPU remains at that %rip once its state has
184 * been made consistent.
185 */
186 repeat_consistent_exit(vcpu, &ventry, &vexit, vexit.rip);
187
188 test_pass();
189 }
190