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
test_ccr_clamp(int vmfd,struct vcpu * vcpu)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
test_timer_icr_constraints(int vmfd,struct vcpu * vcpu)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
main(int argc,char * argv[])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