xref: /illumos-gate/usr/src/test/bhyve-tests/tests/vmm/import_vlapic.c (revision afa820535d942a4611b11bb58296ae046128405e)
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 2024 Oxide Computer Company
14  */
15 
16 #include <stdio.h>
17 #include <unistd.h>
18 #include <stdlib.h>
19 #include <fcntl.h>
20 #include <libgen.h>
21 #include <sys/stat.h>
22 #include <errno.h>
23 #include <err.h>
24 #include <assert.h>
25 #include <sys/sysmacros.h>
26 #include <stdbool.h>
27 
28 #include <sys/vmm.h>
29 #include <sys/vmm_dev.h>
30 #include <sys/vmm_data.h>
31 #include <vmmapi.h>
32 
33 #include "common.h"
34 
35 #define	APIC_ADDR_TIMER_ICR	0xfee00380
36 #define	APIC_ADDR_TIMER_CCR	0xfee00390
37 
38 #define	TIMER_TEST_VAL	0x10000
39 
40 static void
41 test_ccr_clamp(int vmfd, struct vcpu *vcpu)
42 {
43 	/* Pause the instance before attempting to manipulate vlapic data */
44 	if (ioctl(vmfd, VM_PAUSE, 0) != 0) {
45 		err(EXIT_FAILURE, "VM_PAUSE failed");
46 	}
47 
48 	struct vdi_lapic_v1 lapic_data;
49 	struct vm_data_xfer xfer = {
50 		.vdx_vcpuid = 0,
51 		.vdx_class = VDC_LAPIC,
52 		.vdx_version = 1,
53 		.vdx_len = sizeof (lapic_data),
54 		.vdx_data = &lapic_data,
55 	};
56 
57 	/* Read the existing lapic data to get a baseline */
58 	if (ioctl(vmfd, VM_DATA_READ, &xfer) != 0) {
59 		err(EXIT_FAILURE, "VM_DATA_READ of lapic failed");
60 	}
61 
62 	/* Writing that exact same data back should be fine */
63 	if (ioctl(vmfd, VM_DATA_WRITE, &xfer) != 0) {
64 		err(EXIT_FAILURE, "VM_DATA_WRITE of lapic failed");
65 	}
66 
67 	/* Simulate ICR being loaded with a meaningful (but short) value */
68 	lapic_data.vl_lapic.vlp_icr_timer = TIMER_TEST_VAL;
69 	/*
70 	 * Pretend as if timer is scheduled to fire 100s (in the future) after
71 	 * VM boot time.  With the ICR value, this should trigger the overage
72 	 * detection and clamping.
73 	 */
74 	lapic_data.vl_timer_target = 1000000000UL * 100;
75 
76 	/* Try to write the outlandish timer result */
77 	if (ioctl(vmfd, VM_DATA_WRITE, &xfer) != 0) {
78 		err(EXIT_FAILURE, "VM_DATA_WRITE of lapic failed");
79 	}
80 
81 	/*
82 	 * The timer will not actually be scheduled (and thus observable via
83 	 * CCR) until the instance is resumed...
84 	 */
85 	if (ioctl(vmfd, VM_RESUME, 0) != 0) {
86 		err(EXIT_FAILURE, "VM_RESUME failed");
87 	}
88 
89 	/* Now simulate a read of CCR from that LAPIC */
90 	uint64_t ccr_value = 0;
91 	int error = vm_readwrite_kernemu_device(vcpu, APIC_ADDR_TIMER_CCR,
92 	    false, 4, &ccr_value);
93 	if (error != 0) {
94 		err(EXIT_FAILURE, "could not emulate MMIO of LAPIC CCR");
95 	}
96 	if (ccr_value != TIMER_TEST_VAL) {
97 		errx(EXIT_FAILURE, "CCR not clamped: %lx != %x",
98 		    ccr_value, TIMER_TEST_VAL);
99 	}
100 }
101 
102 static void
103 test_timer_icr_constraints(int vmfd, struct vcpu *vcpu)
104 {
105 	/* Pause instance before our shenanigans */
106 	if (ioctl(vmfd, VM_PAUSE, 0) != 0) {
107 		err(EXIT_FAILURE, "VM_PAUSE failed");
108 	}
109 
110 	/* Load a TIMER_ICR value */
111 	uint64_t icr_value = 1 << 30;
112 	int error = vm_readwrite_kernemu_device(vcpu, APIC_ADDR_TIMER_CCR,
113 	    true, 4, &icr_value);
114 	if (error != 0) {
115 		err(EXIT_FAILURE, "failed to load timer ICR value");
116 	}
117 
118 	struct vdi_lapic_v1 lapic_data;
119 	struct vm_data_xfer xfer = {
120 		.vdx_vcpuid = 0,
121 		.vdx_class = VDC_LAPIC,
122 		.vdx_version = 1,
123 		.vdx_len = sizeof (lapic_data),
124 		.vdx_data = &lapic_data,
125 	};
126 
127 	if (ioctl(vmfd, VM_DATA_READ, &xfer) != 0) {
128 		err(EXIT_FAILURE, "VM_DATA_READ of lapic failed");
129 	}
130 
131 	/* Confirm that ICR value is set, and timer is scheduled */
132 	if (lapic_data.vl_lapic.vlp_icr_timer == 0) {
133 		errx(EXIT_FAILURE, "ICR_TIMER is 0");
134 	}
135 	if (lapic_data.vl_timer_target == 0) {
136 		errx(EXIT_FAILURE, "vlapic timer not scheduled");
137 	}
138 
139 	/* Reset vCPU to clear timer state from LAPIC */
140 	if (vcpu_reset(vcpu) != 0) {
141 		err(EXIT_FAILURE, "vcpu_reset() failed");
142 	}
143 
144 	/* Re-read vlapic, and confirm zeroed bits */
145 	if (ioctl(vmfd, VM_DATA_READ, &xfer) != 0) {
146 		err(EXIT_FAILURE, "VM_DATA_READ of lapic failed");
147 	}
148 	if (lapic_data.vl_lapic.vlp_icr_timer != 0) {
149 		errx(EXIT_FAILURE, "ICR_TIMER is not 0");
150 	}
151 	if (lapic_data.vl_timer_target != 0) {
152 		errx(EXIT_FAILURE, "vlapic timer should not be scheduled");
153 	}
154 
155 	/*
156 	 * Try to load a vlapic payload with timer scheduled but icr_timer still
157 	 * zeroed out.
158 	 */
159 	lapic_data.vl_timer_target = 1 << 20;
160 	lapic_data.vl_lapic.vlp_icr_timer = 0;
161 
162 	if (ioctl(vmfd, VM_DATA_WRITE, &xfer) == 0) {
163 		errx(EXIT_FAILURE,
164 		    "VM_DATA_WRITE of invalid lapic data should fail");
165 	}
166 }
167 
168 int
169 main(int argc, char *argv[])
170 {
171 	const char *suite_name = basename(argv[0]);
172 	struct vmctx *ctx;
173 	struct vcpu *vcpu;
174 
175 	ctx = create_test_vm(suite_name);
176 	if (ctx == NULL) {
177 		errx(EXIT_FAILURE, "could not open test VM");
178 	}
179 
180 	if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) {
181 		err(EXIT_FAILURE, "Could not open vcpu0");
182 	}
183 
184 	if (vm_activate_cpu(vcpu) != 0) {
185 		err(EXIT_FAILURE, "could not activate vcpu0");
186 	}
187 
188 	const int vmfd = vm_get_device_fd(ctx);
189 
190 	test_ccr_clamp(vmfd, vcpu);
191 	test_timer_icr_constraints(vmfd, vcpu);
192 
193 	vm_destroy(ctx);
194 	(void) printf("%s\tPASS\n", suite_name);
195 	return (EXIT_SUCCESS);
196 }
197