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 <strings.h> 20 #include <libgen.h> 21 #include <assert.h> 22 #include <errno.h> 23 24 #include <sys/types.h> 25 #include <sys/sysmacros.h> 26 #include <sys/debug.h> 27 #include <sys/mman.h> 28 #include <sys/vmm.h> 29 #include <sys/vmm_dev.h> 30 #include <vmmapi.h> 31 32 #include "common.h" 33 #include "in_guest.h" 34 35 #define PAGE_SZ 4096 36 37 #define DIRTY_BITMAP_SZ (MEM_TOTAL_SZ / (PAGE_SZ * 8)) 38 39 static void 40 read_dirty_bitmap(struct vmctx *ctx, uint8_t *bitmap) 41 { 42 struct vmm_dirty_tracker track = { 43 .vdt_start_gpa = 0, 44 .vdt_len = MEM_TOTAL_SZ, 45 .vdt_pfns = (void *)bitmap, 46 }; 47 int err = ioctl(vm_get_device_fd(ctx), VM_TRACK_DIRTY_PAGES, &track); 48 if (err != 0) { 49 test_fail_errno(errno, "Could not get dirty page bitmap"); 50 } 51 } 52 53 static uint8_t 54 popc8(uint8_t val) 55 { 56 uint8_t cnt; 57 58 for (cnt = 0; val != 0; val &= (val - 1)) { 59 cnt++; 60 } 61 return (cnt); 62 } 63 64 static uint_t 65 count_dirty_pages(const uint8_t *bitmap) 66 { 67 uint_t count = 0; 68 for (uint_t i = 0; i < DIRTY_BITMAP_SZ; i++) { 69 count += popc8(bitmap[i]); 70 } 71 return (count); 72 } 73 74 void 75 check_supported(const char *test_suite_name) 76 { 77 char name[VM_MAX_NAMELEN]; 78 int err; 79 80 name_test_vm(test_suite_name, name); 81 82 err = vm_create(name, VCF_TRACK_DIRTY); 83 if (err == 0) { 84 /* 85 * We created the VM successfully, so we know that dirty page 86 * tracking is supported. 87 */ 88 err = destroy_instance(test_suite_name); 89 if (err != 0) { 90 (void) fprintf(stderr, 91 "Could not destroy VM: %s\n", strerror(errno)); 92 (void) printf("FAIL %s\n", test_suite_name); 93 exit(EXIT_FAILURE); 94 } 95 } else if (errno == ENOTSUP) { 96 (void) printf( 97 "Skipping test: dirty page tracking not supported\n"); 98 (void) printf("PASS %s\n", test_suite_name); 99 exit(EXIT_SUCCESS); 100 } else { 101 /* 102 * Ignore any other errors, they'll be caught by subsequent 103 * test routines. 104 */ 105 } 106 } 107 108 void 109 test_dirty_tracking_disabled(const char *test_suite_name) 110 { 111 struct vmctx *ctx = NULL; 112 struct vcpu *vcpu; 113 int err; 114 115 uint8_t dirty_bitmap[DIRTY_BITMAP_SZ] = { 0 }; 116 struct vmm_dirty_tracker track = { 117 .vdt_start_gpa = 0, 118 .vdt_len = MEM_TOTAL_SZ, 119 .vdt_pfns = (void *)dirty_bitmap, 120 }; 121 122 /* Create VM without VCF_TRACK_DIRTY flag */ 123 ctx = test_initialize_flags(test_suite_name, 0); 124 125 if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { 126 test_fail_errno(errno, "Could not open vcpu0"); 127 } 128 129 err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK); 130 if (err != 0) { 131 test_fail_errno(err, "Could not initialize vcpu0"); 132 } 133 134 /* Try to query for dirty pages */ 135 err = ioctl(vm_get_device_fd(ctx), VM_TRACK_DIRTY_PAGES, &track); 136 if (err == 0) { 137 test_fail_msg("VM_TRACK_DIRTY_PAGES succeeded unexpectedly\n"); 138 } else if (errno != EPERM) { 139 test_fail_errno(errno, 140 "VM_TRACK_DIRTY_PAGES failed with unexpected error"); 141 } 142 143 test_cleanup(false); 144 } 145 146 int 147 main(int argc, char *argv[]) 148 { 149 const char *test_suite_name = basename(argv[0]); 150 struct vmctx *ctx = NULL; 151 struct vcpu *vcpu; 152 int err; 153 154 /* Skip test if CPU doesn't support HW A/D tracking */ 155 check_supported(test_suite_name); 156 157 /* Test for expected error with dirty tracking disabled */ 158 test_dirty_tracking_disabled(test_suite_name); 159 160 ctx = test_initialize_flags(test_suite_name, VCF_TRACK_DIRTY); 161 162 if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) { 163 test_fail_errno(errno, "Could not open vcpu0"); 164 } 165 err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK); 166 if (err != 0) { 167 test_fail_errno(err, "Could not initialize vcpu0"); 168 } 169 170 uint8_t dirty_bitmap[DIRTY_BITMAP_SZ] = { 0 }; 171 172 /* Clear pages which were dirtied as part of initialization */ 173 read_dirty_bitmap(ctx, dirty_bitmap); 174 if (count_dirty_pages(dirty_bitmap) == 0) { 175 test_fail_msg("no pages dirtied during setup\n"); 176 } 177 178 /* 179 * With nothing running, and the old dirty bits cleared, the NPT should 180 * now be devoid of pages marked dirty. 181 */ 182 read_dirty_bitmap(ctx, dirty_bitmap); 183 if (count_dirty_pages(dirty_bitmap) != 0) { 184 test_fail_msg("pages still dirty after clear\n"); 185 } 186 187 /* Dirty a page through the segvmm mapping. */ 188 uint8_t *dptr = vm_map_gpa(ctx, MEM_LOC_STACK, 1); 189 *dptr = 1; 190 191 /* Check that it was marked as such */ 192 read_dirty_bitmap(ctx, dirty_bitmap); 193 if (count_dirty_pages(dirty_bitmap) != 1) { 194 test_fail_msg("direct access did not dirty page\n"); 195 } 196 if (dirty_bitmap[MEM_LOC_STACK / (PAGE_SZ * 8)] == 0) { 197 test_fail_msg("unexpected page dirtied\n"); 198 } 199 200 201 /* Dirty it again to check shootdown logic */ 202 *dptr = 2; 203 if (count_dirty_pages(dirty_bitmap) != 1) { 204 test_fail_msg("subsequent direct access did not dirty page\n"); 205 } 206 207 struct vm_entry ventry = { 0 }; 208 struct vm_exit vexit = { 0 }; 209 do { 210 const enum vm_exit_kind kind = 211 test_run_vcpu(vcpu, &ventry, &vexit); 212 switch (kind) { 213 case VEK_REENTR: 214 break; 215 case VEK_TEST_PASS: 216 /* 217 * By now, the guest should have dirtied that page 218 * directly via hardware-accelerated path. 219 */ 220 read_dirty_bitmap(ctx, dirty_bitmap); 221 /* 222 * The guest will dirty more than the page it is 223 * explicitly writing to: it must mark its own page 224 * tables with accessed/dirty bits too. 225 */ 226 if (count_dirty_pages(dirty_bitmap) <= 1) { 227 test_fail_msg( 228 "in-guest access did not dirty page\n"); 229 } 230 if (dirty_bitmap[MEM_LOC_STACK / (PAGE_SZ * 8)] == 0) { 231 test_fail_msg("expected page not dirtied\n"); 232 } 233 test_pass(); 234 break; 235 default: 236 test_fail_vmexit(&vexit); 237 break; 238 } 239 } while (true); 240 } 241