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 2022 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 int err; 113 114 uint8_t dirty_bitmap[DIRTY_BITMAP_SZ] = { 0 }; 115 struct vmm_dirty_tracker track = { 116 .vdt_start_gpa = 0, 117 .vdt_len = MEM_TOTAL_SZ, 118 .vdt_pfns = (void *)dirty_bitmap, 119 }; 120 121 /* Create VM without VCF_TRACK_DIRTY flag */ 122 ctx = test_initialize_flags(test_suite_name, 0); 123 124 err = test_setup_vcpu(ctx, 0, MEM_LOC_PAYLOAD, MEM_LOC_STACK); 125 if (err != 0) { 126 test_fail_errno(err, "Could not initialize vcpu0"); 127 } 128 129 /* Try to query for dirty pages */ 130 err = ioctl(vm_get_device_fd(ctx), VM_TRACK_DIRTY_PAGES, &track); 131 if (err == 0) { 132 test_fail_msg("VM_TRACK_DIRTY_PAGES succeeded unexpectedly\n"); 133 } else if (errno != EPERM) { 134 test_fail_errno(errno, 135 "VM_TRACK_DIRTY_PAGES failed with unexpected error"); 136 } 137 138 test_cleanup(false); 139 } 140 141 int 142 main(int argc, char *argv[]) 143 { 144 const char *test_suite_name = basename(argv[0]); 145 struct vmctx *ctx = NULL; 146 int err; 147 148 /* Skip test if CPU doesn't support HW A/D tracking */ 149 check_supported(test_suite_name); 150 151 /* Test for expected error with dirty tracking disabled */ 152 test_dirty_tracking_disabled(test_suite_name); 153 154 ctx = test_initialize_flags(test_suite_name, VCF_TRACK_DIRTY); 155 156 err = test_setup_vcpu(ctx, 0, MEM_LOC_PAYLOAD, MEM_LOC_STACK); 157 if (err != 0) { 158 test_fail_errno(err, "Could not initialize vcpu0"); 159 } 160 161 uint8_t dirty_bitmap[DIRTY_BITMAP_SZ] = { 0 }; 162 163 /* Clear pages which were dirtied as part of initialization */ 164 read_dirty_bitmap(ctx, dirty_bitmap); 165 if (count_dirty_pages(dirty_bitmap) == 0) { 166 test_fail_msg("no pages dirtied during setup\n"); 167 } 168 169 /* 170 * With nothing running, and the old dirty bits cleared, the NPT should 171 * now be devoid of pages marked dirty. 172 */ 173 read_dirty_bitmap(ctx, dirty_bitmap); 174 if (count_dirty_pages(dirty_bitmap) != 0) { 175 test_fail_msg("pages still dirty after clear\n"); 176 } 177 178 /* Dirty a page through the segvmm mapping. */ 179 uint8_t *dptr = vm_map_gpa(ctx, MEM_LOC_STACK, 1); 180 *dptr = 1; 181 182 /* Check that it was marked as such */ 183 read_dirty_bitmap(ctx, dirty_bitmap); 184 if (count_dirty_pages(dirty_bitmap) != 1) { 185 test_fail_msg("direct access did not dirty page\n"); 186 } 187 if (dirty_bitmap[MEM_LOC_STACK / (PAGE_SZ * 8)] == 0) { 188 test_fail_msg("unexpected page dirtied\n"); 189 } 190 191 192 /* Dirty it again to check shootdown logic */ 193 *dptr = 2; 194 if (count_dirty_pages(dirty_bitmap) != 1) { 195 test_fail_msg("subsequent direct access did not dirty page\n"); 196 } 197 198 struct vm_entry ventry = { 0 }; 199 struct vm_exit vexit = { 0 }; 200 do { 201 const enum vm_exit_kind kind = 202 test_run_vcpu(ctx, 0, &ventry, &vexit); 203 switch (kind) { 204 case VEK_REENTR: 205 break; 206 case VEK_TEST_PASS: 207 /* 208 * By now, the guest should have dirtied that page 209 * directly via hardware-accelerated path. 210 */ 211 read_dirty_bitmap(ctx, dirty_bitmap); 212 /* 213 * The guest will dirty more than the page it is 214 * explicitly writing to: it must mark its own page 215 * tables with accessed/dirty bits too. 216 */ 217 if (count_dirty_pages(dirty_bitmap) <= 1) { 218 test_fail_msg( 219 "in-guest access did not dirty page\n"); 220 } 221 if (dirty_bitmap[MEM_LOC_STACK / (PAGE_SZ * 8)] == 0) { 222 test_fail_msg("expected page not dirtied\n"); 223 } 224 test_pass(); 225 break; 226 default: 227 test_fail_vmexit(&vexit); 228 break; 229 } 230 } while (true); 231 } 232