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