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 <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 static void 36 should_eq_u64(const char *field_name, uint64_t a, uint64_t b) 37 { 38 if (a != b) { 39 errx(EXIT_FAILURE, "unexpected %s %" PRIu64 " != %" PRIu64, 40 field_name, a, b); 41 } 42 } 43 44 static void 45 check_inval_field(int vmfd, uint32_t ident, uint64_t val) 46 { 47 struct vdi_field_entry_v1 field = { 48 .vfe_ident = ident, 49 .vfe_value = val, 50 }; 51 struct vm_data_xfer vdx = { 52 .vdx_class = VDC_VMM_ARCH, 53 .vdx_version = 1, 54 .vdx_len = sizeof (field), 55 .vdx_data = &field, 56 }; 57 58 if (ioctl(vmfd, VM_DATA_WRITE, &vdx) == 0) { 59 err(EXIT_FAILURE, "vmm_data_write should have failed"); 60 } 61 int err = errno; 62 if (err != EINVAL) { 63 errx(EXIT_FAILURE, "expected EINVAL errno, got %d", err); 64 } 65 } 66 67 static void 68 do_data_write(int vmfd, struct vm_data_xfer *vdx) 69 { 70 if (ioctl(vmfd, VM_DATA_WRITE, vdx) != 0) { 71 err(EXIT_FAILURE, "valid vmm_data_write failed"); 72 } 73 if (vdx->vdx_result_len != vdx->vdx_len) { 74 errx(EXIT_FAILURE, "unexpected vdx_result_len %u != %u", 75 vdx->vdx_len, vdx->vdx_result_len); 76 } 77 } 78 79 static void 80 do_data_read(int vmfd, struct vm_data_xfer *vdx) 81 { 82 if (ioctl(vmfd, VM_DATA_READ, vdx) != 0) { 83 err(EXIT_FAILURE, "valid vmm_data_read failed"); 84 } 85 if (vdx->vdx_result_len != vdx->vdx_len) { 86 errx(EXIT_FAILURE, "unexpected vdx_result_len %u != %u", 87 vdx->vdx_len, vdx->vdx_result_len); 88 } 89 } 90 91 int 92 main(int argc, char *argv[]) 93 { 94 const char *suite_name = basename(argv[0]); 95 struct vmctx *ctx; 96 struct vcpu *vcpu; 97 98 ctx = create_test_vm(suite_name); 99 if (ctx == NULL) { 100 errx(EXIT_FAILURE, "could not open test VM"); 101 } 102 103 if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { 104 err(EXIT_FAILURE, "Could not open vcpu0"); 105 } 106 107 if (vm_activate_cpu(vcpu) != 0) { 108 err(EXIT_FAILURE, "could not activate vcpu0"); 109 } 110 111 const int vmfd = vm_get_device_fd(ctx); 112 113 /* Pause the instance before attempting to manipulate vcpu data */ 114 if (ioctl(vmfd, VM_PAUSE, 0) != 0) { 115 err(EXIT_FAILURE, "VM_PAUSE failed"); 116 } 117 118 struct vdi_field_entry_v1 fields[4] = { 119 { .vfe_ident = VAI_PEND_NMI }, 120 { .vfe_ident = VAI_PEND_EXTINT }, 121 { .vfe_ident = VAI_PEND_EXCP }, 122 { .vfe_ident = VAI_PEND_INTINFO }, 123 }; 124 125 struct vm_data_xfer vdx = { 126 .vdx_class = VDC_VMM_ARCH, 127 .vdx_version = 1, 128 .vdx_flags = VDX_FLAG_READ_COPYIN, 129 .vdx_len = sizeof (fields), 130 .vdx_data = &fields, 131 }; 132 133 /* Fetch arch state first */ 134 do_data_read(vmfd, &vdx); 135 136 /* All of these should be zeroed on a fresh vcpu */ 137 should_eq_u64("VAI_PEND_NMI", fields[0].vfe_value, 0); 138 should_eq_u64("VAI_PEND_EXTINT", fields[1].vfe_value, 0); 139 should_eq_u64("VAI_PEND_EXCP", fields[2].vfe_value, 0); 140 should_eq_u64("VAI_PEND_INTINFO", fields[3].vfe_value, 0); 141 142 /* Light up those fields */ 143 fields[0].vfe_value = 1; 144 fields[1].vfe_value = 1; 145 fields[2].vfe_value = VM_INTINFO_VALID | VM_INTINFO_HWEXCP | IDT_GP; 146 fields[3].vfe_value = VM_INTINFO_VALID | VM_INTINFO_SWINTR | 0x80; 147 do_data_write(vmfd, &vdx); 148 149 /* 150 * Flip the order (just for funsies) and re-query to check that we still 151 * get the expected state. 152 */ 153 fields[0].vfe_ident = VAI_PEND_INTINFO; 154 fields[1].vfe_ident = VAI_PEND_EXCP; 155 fields[2].vfe_ident = VAI_PEND_EXTINT; 156 fields[3].vfe_ident = VAI_PEND_NMI; 157 do_data_read(vmfd, &vdx); 158 159 should_eq_u64("VAI_PEND_INTINFO", fields[0].vfe_value, 160 VM_INTINFO_VALID | VM_INTINFO_SWINTR | 0x80); 161 should_eq_u64("VAI_PEND_EXCP", fields[1].vfe_value, 162 VM_INTINFO_VALID | VM_INTINFO_HWEXCP | IDT_GP); 163 should_eq_u64("VAI_PEND_EXTINT", fields[2].vfe_value, 1); 164 should_eq_u64("VAI_PEND_NMI", fields[3].vfe_value, 1); 165 166 167 /* NMI-typed exception with the wrong vector */ 168 check_inval_field(vmfd, VAI_PEND_INTINFO, 169 VM_INTINFO_VALID | VM_INTINFO_NMI | 0xd); 170 171 /* Hardware exception with a bad vector (>= 32) */ 172 check_inval_field(vmfd, VAI_PEND_INTINFO, 173 VM_INTINFO_VALID | VM_INTINFO_HWEXCP | 0x40); 174 175 /* Non-HW event injected into HW exception field */ 176 check_inval_field(vmfd, VAI_PEND_EXCP, 177 VM_INTINFO_VALID | VM_INTINFO_SWINTR | 0xd); 178 179 /* Zero out the values again */ 180 fields[0].vfe_value = 0; 181 fields[1].vfe_value = 0; 182 fields[2].vfe_value = 0; 183 fields[3].vfe_value = 0; 184 do_data_write(vmfd, &vdx); 185 186 /* And confirm that it took */ 187 do_data_read(vmfd, &vdx); 188 should_eq_u64("VAI_PEND_INTINFO", fields[0].vfe_value, 0); 189 should_eq_u64("VAI_PEND_EXCP", fields[1].vfe_value, 0); 190 should_eq_u64("VAI_PEND_EXTINT", fields[2].vfe_value, 0); 191 should_eq_u64("VAI_PEND_NMI", fields[3].vfe_value, 0); 192 193 vm_vcpu_close(vcpu); 194 vm_destroy(ctx); 195 (void) printf("%s\tPASS\n", suite_name); 196 return (EXIT_SUCCESS); 197 } 198