xref: /illumos-gate/usr/src/test/bhyve-tests/tests/inst_emul/page_dirty.c (revision 32640292339b07090f10ce34d455f98711077343)
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 	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 	if ((vcpu = vm_vcpu_open(ctx, 0)) == NULL) {
158 		test_fail_errno(errno, "Could not open vcpu0");
159 	}
160 
161 	/* Test for expected error with dirty tracking disabled */
162 	test_dirty_tracking_disabled(test_suite_name);
163 
164 	ctx = test_initialize_flags(test_suite_name, VCF_TRACK_DIRTY);
165 
166 	err = test_setup_vcpu(vcpu, MEM_LOC_PAYLOAD, MEM_LOC_STACK);
167 	if (err != 0) {
168 		test_fail_errno(err, "Could not initialize vcpu0");
169 	}
170 
171 	uint8_t dirty_bitmap[DIRTY_BITMAP_SZ] = { 0 };
172 
173 	/* Clear pages which were dirtied as part of initialization */
174 	read_dirty_bitmap(ctx, dirty_bitmap);
175 	if (count_dirty_pages(dirty_bitmap) == 0) {
176 		test_fail_msg("no pages dirtied during setup\n");
177 	}
178 
179 	/*
180 	 * With nothing running, and the old dirty bits cleared, the NPT should
181 	 * now be devoid of pages marked dirty.
182 	 */
183 	read_dirty_bitmap(ctx, dirty_bitmap);
184 	if (count_dirty_pages(dirty_bitmap) != 0) {
185 		test_fail_msg("pages still dirty after clear\n");
186 	}
187 
188 	/* Dirty a page through the segvmm mapping. */
189 	uint8_t *dptr = vm_map_gpa(ctx, MEM_LOC_STACK, 1);
190 	*dptr = 1;
191 
192 	/* Check that it was marked as such */
193 	read_dirty_bitmap(ctx, dirty_bitmap);
194 	if (count_dirty_pages(dirty_bitmap) != 1) {
195 		test_fail_msg("direct access did not dirty page\n");
196 	}
197 	if (dirty_bitmap[MEM_LOC_STACK / (PAGE_SZ * 8)] == 0) {
198 		test_fail_msg("unexpected page dirtied\n");
199 	}
200 
201 
202 	/* Dirty it again to check shootdown logic */
203 	*dptr = 2;
204 	if (count_dirty_pages(dirty_bitmap) != 1) {
205 		test_fail_msg("subsequent direct access did not dirty page\n");
206 	}
207 
208 	struct vm_entry ventry = { 0 };
209 	struct vm_exit vexit = { 0 };
210 	do {
211 		const enum vm_exit_kind kind =
212 		    test_run_vcpu(vcpu, &ventry, &vexit);
213 		switch (kind) {
214 		case VEK_REENTR:
215 			break;
216 		case VEK_TEST_PASS:
217 			/*
218 			 * By now, the guest should have dirtied that page
219 			 * directly via hardware-accelerated path.
220 			 */
221 			read_dirty_bitmap(ctx, dirty_bitmap);
222 			/*
223 			 * The guest will dirty more than the page it is
224 			 * explicitly writing to: it must mark its own page
225 			 * tables with accessed/dirty bits too.
226 			 */
227 			if (count_dirty_pages(dirty_bitmap) <= 1) {
228 				test_fail_msg(
229 				    "in-guest access did not dirty page\n");
230 			}
231 			if (dirty_bitmap[MEM_LOC_STACK / (PAGE_SZ * 8)] == 0) {
232 				test_fail_msg("expected page not dirtied\n");
233 			}
234 			test_pass();
235 			break;
236 		default:
237 			test_fail_vmexit(&vexit);
238 			break;
239 		}
240 	} while (true);
241 }
242