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 %u != %u", 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 97 ctx = create_test_vm(suite_name); 98 if (ctx == NULL) { 99 errx(EXIT_FAILURE, "could not open test VM"); 100 } 101 102 if (vm_activate_cpu(ctx, 0) != 0) { 103 err(EXIT_FAILURE, "could not activate vcpu0"); 104 } 105 106 const int vmfd = vm_get_device_fd(ctx); 107 108 /* Pause the instance before attempting to manipulate vcpu data */ 109 if (ioctl(vmfd, VM_PAUSE, 0) != 0) { 110 err(EXIT_FAILURE, "VM_PAUSE failed"); 111 } 112 113 struct vdi_field_entry_v1 fields[4] = { 114 { .vfe_ident = VAI_PEND_NMI }, 115 { .vfe_ident = VAI_PEND_EXTINT }, 116 { .vfe_ident = VAI_PEND_EXCP }, 117 { .vfe_ident = VAI_PEND_INTINFO }, 118 }; 119 120 struct vm_data_xfer vdx = { 121 .vdx_class = VDC_VMM_ARCH, 122 .vdx_version = 1, 123 .vdx_flags = VDX_FLAG_READ_COPYIN, 124 .vdx_len = sizeof (fields), 125 .vdx_data = &fields, 126 }; 127 128 /* Fetch arch state first */ 129 do_data_read(vmfd, &vdx); 130 131 /* All of these should be zeroed on a fresh vcpu */ 132 should_eq_u64("VAI_PEND_NMI", fields[0].vfe_value, 0); 133 should_eq_u64("VAI_PEND_EXTINT", fields[1].vfe_value, 0); 134 should_eq_u64("VAI_PEND_EXCP", fields[2].vfe_value, 0); 135 should_eq_u64("VAI_PEND_INTINFO", fields[3].vfe_value, 0); 136 137 /* Light up those fields */ 138 fields[0].vfe_value = 1; 139 fields[1].vfe_value = 1; 140 fields[2].vfe_value = VM_INTINFO_VALID | VM_INTINFO_HWEXCP | IDT_GP; 141 fields[3].vfe_value = VM_INTINFO_VALID | VM_INTINFO_SWINTR | 0x80; 142 do_data_write(vmfd, &vdx); 143 144 /* 145 * Flip the order (just for funsies) and re-query to check that we still 146 * get the expected state. 147 */ 148 fields[0].vfe_ident = VAI_PEND_INTINFO; 149 fields[1].vfe_ident = VAI_PEND_EXCP; 150 fields[2].vfe_ident = VAI_PEND_EXTINT; 151 fields[3].vfe_ident = VAI_PEND_NMI; 152 do_data_read(vmfd, &vdx); 153 154 should_eq_u64("VAI_PEND_INTINFO", fields[0].vfe_value, 155 VM_INTINFO_VALID | VM_INTINFO_SWINTR | 0x80); 156 should_eq_u64("VAI_PEND_EXCP", fields[1].vfe_value, 157 VM_INTINFO_VALID | VM_INTINFO_HWEXCP | IDT_GP); 158 should_eq_u64("VAI_PEND_EXTINT", fields[2].vfe_value, 1); 159 should_eq_u64("VAI_PEND_NMI", fields[3].vfe_value, 1); 160 161 162 /* NMI-typed exception with the wrong vector */ 163 check_inval_field(vmfd, VAI_PEND_INTINFO, 164 VM_INTINFO_VALID | VM_INTINFO_NMI | 0xd); 165 166 /* Hardware exception with a bad vector (>= 32) */ 167 check_inval_field(vmfd, VAI_PEND_INTINFO, 168 VM_INTINFO_VALID | VM_INTINFO_HWEXCP | 0x40); 169 170 /* Non-HW event injected into HW exception field */ 171 check_inval_field(vmfd, VAI_PEND_EXCP, 172 VM_INTINFO_VALID | VM_INTINFO_SWINTR | 0xd); 173 174 /* Zero out the values again */ 175 fields[0].vfe_value = 0; 176 fields[1].vfe_value = 0; 177 fields[2].vfe_value = 0; 178 fields[3].vfe_value = 0; 179 do_data_write(vmfd, &vdx); 180 181 /* And confirm that it took */ 182 do_data_read(vmfd, &vdx); 183 should_eq_u64("VAI_PEND_INTINFO", fields[0].vfe_value, 0); 184 should_eq_u64("VAI_PEND_EXCP", fields[1].vfe_value, 0); 185 should_eq_u64("VAI_PEND_EXTINT", fields[2].vfe_value, 0); 186 should_eq_u64("VAI_PEND_NMI", fields[3].vfe_value, 0); 187 188 vm_destroy(ctx); 189 (void) printf("%s\tPASS\n", suite_name); 190 return (EXIT_SUCCESS); 191 } 192