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