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
34 run_until_unhandled(struct vmctx *ctx, struct vm_entry *ventry,
35     struct vm_exit *vexit)
36 {
37 	do {
38 		const enum vm_exit_kind kind =
39 		    test_run_vcpu(ctx, 0, 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
57 repeat_consistent_exit(struct vmctx *ctx, 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(ctx, 0, 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
71 main(int argc, char *argv[])
72 {
73 	const char *test_suite_name = basename(argv[0]);
74 	struct vmctx *ctx = NULL;
75 	int err;
76 
77 	ctx = test_initialize(test_suite_name);
78 
79 	err = test_setup_vcpu(ctx, 0, MEM_LOC_PAYLOAD, MEM_LOC_STACK);
80 	if (err != 0) {
81 		test_fail_errno(err, "Could not initialize vcpu0");
82 	}
83 
84 	struct vm_entry ventry = { 0 };
85 	struct vm_exit vexit = { 0 };
86 
87 	/*
88 	 * Let the payload run until it reaches the first userspace exit which
89 	 * requires actual handling
90 	 */
91 	run_until_unhandled(ctx, &ventry, &vexit);
92 	if (vexit.exitcode != VM_EXITCODE_RDMSR) {
93 		test_fail_vmexit(&vexit);
94 	}
95 	uint64_t rcx = 0, rip = 0;
96 	if (vm_get_register(ctx, 0, VM_REG_GUEST_RCX, &rcx) != 0) {
97 		test_fail_errno(errno, "Could not read guest %rcx");
98 	}
99 	if (vm_get_register(ctx, 0, VM_REG_GUEST_RIP, &rip) != 0) {
100 		test_fail_errno(errno, "Could not read guest %rip");
101 	}
102 	/* Paranoia: confirm that in-register %rip matches vm_exit data */
103 	if (rip != vexit.rip) {
104 		test_fail_msg(
105 		    "vm_exit`rip does not match in-kernel %rip: %lx != %lx",
106 		    rip, vexit.rip);
107 	}
108 
109 	/* Request a consistent exit */
110 	ventry.cmd = VEC_DEFAULT | VEC_FLAG_EXIT_CONSISTENT;
111 	if (vm_run(ctx, 0, &ventry, &vexit) != 0) {
112 		test_fail_errno(errno, "Failure during vcpu entry");
113 	}
114 
115 	/*
116 	 * We expect the consistent exit to have completed the instruction
117 	 * emulation for the rdmsr (just move the %rip forward, since its left
118 	 * to userspace to update %rax:%rdx) and emit the BOGUS exitcode.
119 	 */
120 	if (vexit.exitcode != VM_EXITCODE_BOGUS) {
121 		test_fail_msg("Unexpected exitcode: %d != %d",
122 		    vexit.exitcode, VM_EXITCODE_BOGUS);
123 	}
124 
125 	/*
126 	 * Check that the %rip moved forward only the 2 bytes expected for a
127 	 * rdmsr opcode.
128 	 */
129 	if (vexit.rip != (rip + 2)) {
130 		test_fail_msg("Exited at unexpected %rip: %lx != %lx",
131 		    vexit.rip, rip + 2);
132 	}
133 
134 	/*
135 	 * Repeat entry with consistency request.  This should not make any
136 	 * forward progress since the vCPU is already in a consistent state.
137 	 */
138 	repeat_consistent_exit(ctx, &ventry, &vexit, vexit.rip);
139 
140 	/* Let the vCPU continue on to the next exit condition */
141 	ventry.cmd = VEC_DEFAULT;
142 	run_until_unhandled(ctx, &ventry, &vexit);
143 
144 	const uint64_t read_addr = 0xc0000000;
145 	const uint_t read_len = 4;
146 	if (!vexit_match_mmio(&vexit, true, read_addr, read_len, NULL)) {
147 		test_fail_vmexit(&vexit);
148 	}
149 	rip = vexit.rip;
150 
151 	/*
152 	 * An attempt to push the vCPU to a consistent state without first
153 	 * fulfilling the MMIO should just result in the same MMIO exit.
154 	 */
155 	ventry.cmd = VEC_DEFAULT | VEC_FLAG_EXIT_CONSISTENT;
156 	if (vm_run(ctx, 0, &ventry, &vexit) != 0) {
157 		test_fail_errno(errno, "Failure during vcpu entry");
158 	}
159 	if (vexit.rip != rip ||
160 	    !vexit_match_mmio(&vexit, true, read_addr, read_len, NULL)) {
161 		test_fail_msg(
162 		    "Unexpected forward progress during MMIO emulation");
163 	}
164 
165 	/* Fulfill the MMIO and attempt another consistent exit */
166 	ventry_fulfill_mmio(&vexit, &ventry, 0);
167 	ventry.cmd |= VEC_FLAG_EXIT_CONSISTENT;
168 	if (vm_run(ctx, 0, &ventry, &vexit) != 0) {
169 		test_fail_errno(errno, "Failure during vcpu entry");
170 	}
171 
172 	/* With current payload, we expect a 3-byte mov instruction */
173 	if (vexit.rip != (rip + 3)) {
174 		test_fail_msg("Exited at unexpected %rip: %lx != %lx",
175 		    vexit.rip, rip + 3);
176 	}
177 
178 	/*
179 	 * And again, check that vCPU remains at that %rip once its state has
180 	 * been made consistent.
181 	 */
182 	repeat_consistent_exit(ctx, &ventry, &vexit, vexit.rip);
183 
184 	test_pass();
185 }
186