xref: /freebsd/sys/contrib/openzfs/tests/zfs-tests/tests/functional/vdev_disk/page_alignment.c (revision 5c65a0a9163cc00389d8527ee12c4e69df07ea42)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or https://opensource.org/licenses/CDDL-1.0.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright (c) 2023, 2024, Klara Inc.
23  */
24 
25 #include <stdint.h>
26 #include <stdio.h>
27 #include <stdbool.h>
28 #include <sys/param.h>
29 #include <stdlib.h>
30 
31 /*
32  * This tests the vdev_disk page alignment check callback
33  * vdev_disk_check_alignment_cb(). For now, this test includes a copy of that
34  * function from module/os/linux/zfs/vdev_disk.c. If you change it here,
35  * remember to change it there too, and add tests data here to validate the
36  * change you're making.
37  */
38 
39 struct page;
40 
41 /*
42  * This is spl_pagesize() in userspace, which requires linking libspl, but
43  * would also then use the platform page size, which isn't what we want for
44  * a test. To keep the check callback the same as the real one, we just
45  * redefine it.
46  */
47 #undef	PAGESIZE
48 #define	PAGESIZE	(4096)
49 
50 typedef struct {
51 	size_t	blocksize;
52 	int	seen_first;
53 	int	seen_last;
54 } vdev_disk_check_alignment_t;
55 
56 static int
vdev_disk_check_alignment_cb(struct page * page,size_t off,size_t len,void * priv)57 vdev_disk_check_alignment_cb(struct page *page, size_t off, size_t len,
58     void *priv)
59 {
60 	(void) page;
61 	vdev_disk_check_alignment_t *s = priv;
62 
63 	/*
64 	 * The cardinal rule: a single on-disk block must never cross an
65 	 * physical (order-0) page boundary, as the kernel expects to be able
66 	 * to split at both LBS and page boundaries.
67 	 *
68 	 * This implies various alignment rules for the blocks in this
69 	 * (possibly compound) page, which we can check for.
70 	 */
71 
72 	/*
73 	 * If the previous page did not end on a page boundary, then we
74 	 * can't proceed without creating a hole.
75 	 */
76 	if (s->seen_last)
77 		return (1);
78 
79 	/* This page must contain only whole LBS-sized blocks. */
80 	if (!IS_P2ALIGNED(len, s->blocksize))
81 		return (1);
82 
83 	/*
84 	 * If this is not the first page in the ABD, then the data must start
85 	 * on a page-aligned boundary (so the kernel can split on page
86 	 * boundaries without having to deal with a hole). If it is, then
87 	 * it can start on LBS-alignment.
88 	 */
89 	if (s->seen_first) {
90 		if (!IS_P2ALIGNED(off, PAGESIZE))
91 			return (1);
92 	} else {
93 		if (!IS_P2ALIGNED(off, s->blocksize))
94 			return (1);
95 		s->seen_first = 1;
96 	}
97 
98 	/*
99 	 * If this data does not end on a page-aligned boundary, then this
100 	 * must be the last page in the ABD, for the same reason.
101 	 */
102 	s->seen_last = !IS_P2ALIGNED(off+len, PAGESIZE);
103 
104 	return (0);
105 }
106 
107 typedef struct {
108 	/* test name */
109 	const char	*name;
110 
111 	/* stored block size */
112 	uint32_t	blocksize;
113 
114 	/* amount of data to take */
115 	size_t		size;
116 
117 	/* [start offset in page, len to end of page or size] */
118 	size_t		pages[16][2];
119 } page_test_t;
120 
121 static const page_test_t valid_tests[] = {
122 	/* 512B block tests */
123 	{
124 		"512B blocks, 4K single page",
125 		512, 0x1000, {
126 			{ 0x0, 0x1000 },
127 		},
128 	}, {
129 		"512B blocks, 1K at start of page",
130 		512, 0x400, {
131 			{ 0x0, 0x1000 },
132 		},
133 	}, {
134 		"512B blocks, 1K at end of page",
135 		512, 0x400, {
136 			{ 0x0c00, 0x0400 },
137 		},
138 	}, {
139 		"512B blocks, 1K within page, 512B start offset",
140 		512, 0x400, {
141 			{ 0x0200, 0x0e00 },
142 		},
143 	}, {
144 		"512B blocks, 8K across 2x4K pages",
145 		512, 0x2000, {
146 			{ 0x0, 0x1000 },
147 			{ 0x0, 0x1000 },
148 		},
149 	}, {
150 		"512B blocks, 4K across two pages, 2K start offset",
151 		512, 0x1000, {
152 			{ 0x0800, 0x0800 },
153 			{ 0x0,    0x0800 },
154 		},
155 	}, {
156 		"512B blocks, 16K across 5x4K pages, 512B start offset",
157 		512, 0x4000, {
158 			{ 0x0200, 0x0e00 },
159 			{ 0x0,    0x1000 },
160 			{ 0x0,    0x1000 },
161 			{ 0x0,    0x1000 },
162 			{ 0x0,    0x0200 },
163 		},
164 	}, {
165 		"512B blocks, 64K data, 8x8K compound pages",
166 		512, 0x10000, {
167 			{ 0x0, 0x2000 },
168 			{ 0x0, 0x2000 },
169 			{ 0x0, 0x2000 },
170 			{ 0x0, 0x2000 },
171 			{ 0x0, 0x2000 },
172 			{ 0x0, 0x2000 },
173 			{ 0x0, 0x2000 },
174 			{ 0x0, 0x2000 },
175 		},
176 	}, {
177 		"512B blocks, 64K data, 9x8K compound pages, 512B start offset",
178 		512, 0x10000, {
179 			{ 0x0200, 0x1e00 },
180 			{ 0x0,    0x2000 },
181 			{ 0x0,    0x2000 },
182 			{ 0x0,    0x2000 },
183 			{ 0x0,    0x2000 },
184 			{ 0x0,    0x2000 },
185 			{ 0x0,    0x2000 },
186 			{ 0x0,    0x2000 },
187 			{ 0x0,    0x0200 },
188 		},
189 	}, {
190 		"512B blocks, 64K data, 2x16K compound pages, 8x4K pages",
191 		512, 0x10000, {
192 			{ 0x0, 0x8000 },
193 			{ 0x0, 0x8000 },
194 			{ 0x0, 0x1000 },
195 			{ 0x0, 0x1000 },
196 			{ 0x0, 0x1000 },
197 			{ 0x0, 0x1000 },
198 			{ 0x0, 0x1000 },
199 			{ 0x0, 0x1000 },
200 			{ 0x0, 0x1000 },
201 			{ 0x0, 0x1000 },
202 		},
203 	}, {
204 		"512B blocks, 64K data, mixed 4K/8K/16K pages",
205 		512, 0x10000, {
206 			{ 0x0, 0x1000 },
207 			{ 0x0, 0x2000 },
208 			{ 0x0, 0x1000 },
209 			{ 0x0, 0x8000 },
210 			{ 0x0, 0x1000 },
211 			{ 0x0, 0x1000 },
212 			{ 0x0, 0x2000 },
213 			{ 0x0, 0x1000 },
214 			{ 0x0, 0x1000 },
215 			{ 0x0, 0x2000 },
216 		},
217 	}, {
218 		"512B blocks, 64K data, mixed 4K/8K/16K pages, 1K start offset",
219 		512, 0x10000, {
220 			{ 0x0400, 0x0c00 },
221 			{ 0x0,    0x1000 },
222 			{ 0x0,    0x1000 },
223 			{ 0x0,    0x1000 },
224 			{ 0x0,    0x2000 },
225 			{ 0x0,    0x2000 },
226 			{ 0x0,    0x1000 },
227 			{ 0x0,    0x8000 },
228 			{ 0x0,    0x1000 },
229 			{ 0x0,    0x0400 },
230 		},
231 	},
232 
233 	/* 4K block tests */
234 	{
235 		"4K blocks, 4K single page",
236 		4096, 0x1000, {
237 			{ 0x0, 0x1000 },
238 		},
239 	}, {
240 		"4K blocks, 8K across 2x4K pages",
241 		4096, 0x2000, {
242 			{ 0x0, 0x1000 },
243 			{ 0x0, 0x1000 },
244 		},
245 	}, {
246 		"4K blocks, 64K data, 8x8K compound pages",
247 		4096, 0x10000, {
248 			{ 0x0, 0x2000 },
249 			{ 0x0, 0x2000 },
250 			{ 0x0, 0x2000 },
251 			{ 0x0, 0x2000 },
252 			{ 0x0, 0x2000 },
253 			{ 0x0, 0x2000 },
254 			{ 0x0, 0x2000 },
255 			{ 0x0, 0x2000 },
256 		},
257 	}, {
258 		"4K blocks, 64K data, 2x16K compound pages, 8x4K pages",
259 		4096, 0x10000, {
260 			{ 0x0, 0x8000 },
261 			{ 0x0, 0x8000 },
262 			{ 0x0, 0x1000 },
263 			{ 0x0, 0x1000 },
264 			{ 0x0, 0x1000 },
265 			{ 0x0, 0x1000 },
266 			{ 0x0, 0x1000 },
267 			{ 0x0, 0x1000 },
268 			{ 0x0, 0x1000 },
269 			{ 0x0, 0x1000 },
270 		},
271 	}, {
272 		"4K blocks, 64K data, mixed 4K/8K/16K pages",
273 		4096, 0x10000, {
274 			{ 0x0, 0x1000 },
275 			{ 0x0, 0x2000 },
276 			{ 0x0, 0x1000 },
277 			{ 0x0, 0x8000 },
278 			{ 0x0, 0x1000 },
279 			{ 0x0, 0x1000 },
280 			{ 0x0, 0x2000 },
281 			{ 0x0, 0x1000 },
282 			{ 0x0, 0x1000 },
283 			{ 0x0, 0x2000 },
284 		},
285 	},
286 
287 	{ 0 },
288 };
289 
290 static const page_test_t invalid_tests[] = {
291 	/*
292 	 * Gang tests. Composed of lots of smaller allocations, rarely properly
293 	 * aligned.
294 	 */
295 	{
296 		"512B blocks, 16K data, 512 leader (gang block simulation)",
297 		512, 0x8000, {
298 			{ 0x0, 0x0200 },
299 			{ 0x0, 0x1000 },
300 			{ 0x0, 0x1000 },
301 			{ 0x0, 0x1000 },
302 			{ 0x0, 0x0c00 },
303 		},
304 	}, {
305 		"4K blocks, 32K data, 2 incompatible spans "
306 		"(gang abd simulation)",
307 		4096, 0x8000, {
308 			{ 0x0800, 0x0800 },
309 			{ 0x0,    0x1000 },
310 			{ 0x0,    0x1000 },
311 			{ 0x0,    0x1000 },
312 			{ 0x0,    0x0800 },
313 			{ 0x0800, 0x0800 },
314 			{ 0x0,    0x1000 },
315 			{ 0x0,    0x1000 },
316 			{ 0x0,    0x1000 },
317 			{ 0x0,    0x0800 },
318 		},
319 	},
320 
321 	/*
322 	 * Blocks must not span multiple physical pages. These tests used to
323 	 * be considered valid, but were since found to be invalid and were
324 	 * moved here.
325 	 */
326 	{
327 		"4K blocks, 4K across two pages, 2K start offset",
328 		4096, 0x1000, {
329 			{ 0x0800, 0x0800 },
330 			{ 0x0,    0x0800 },
331 		},
332 	}, {
333 		"4K blocks, 16K across 5x4K pages, 512B start offset",
334 		4096, 0x4000, {
335 			{ 0x0200, 0x0e00 },
336 			{ 0x0,    0x1000 },
337 			{ 0x0,    0x1000 },
338 			{ 0x0,    0x1000 },
339 			{ 0x0,    0x0200 },
340 		},
341 	}, {
342 		"4K blocks, 64K data, 9x8K compound pages, 512B start offset",
343 		4096, 0x10000, {
344 			{ 0x0200, 0x1e00 },
345 			{ 0x0,    0x2000 },
346 			{ 0x0,    0x2000 },
347 			{ 0x0,    0x2000 },
348 			{ 0x0,    0x2000 },
349 			{ 0x0,    0x2000 },
350 			{ 0x0,    0x2000 },
351 			{ 0x0,    0x2000 },
352 			{ 0x0,    0x0200 },
353 		},
354 	}, {
355 		"4K blocks, 64K data, mixed 4K/8K/16K pages, 1K start offset",
356 		4096, 0x10000, {
357 			{ 0x0400, 0x0c00 },
358 			{ 0x0,    0x1000 },
359 			{ 0x0,    0x1000 },
360 			{ 0x0,    0x1000 },
361 			{ 0x0,    0x2000 },
362 			{ 0x0,    0x2000 },
363 			{ 0x0,    0x1000 },
364 			{ 0x0,    0x8000 },
365 			{ 0x0,    0x1000 },
366 			{ 0x0,    0x0400 },
367 		},
368 	},
369 
370 	/*
371 	 * This is the very typical case of a 4K block being allocated from
372 	 * the middle of a mixed-used slab backed by a higher-order compound
373 	 * page.
374 	 */
375 	{
376 		"4K blocks, 4K data from compound slab, 2K-align offset",
377 		4096, 0x1000, {
378 			{ 0x1800, 0x6800 }
379 		}
380 	},
381 
382 	/*
383 	 * Blocks smaller than LBS should never be possible, but used to be by
384 	 * accident (see GH#16990). We test for and reject them just to be
385 	 * sure.
386 	 */
387 	{
388 		"4K blocks, 1K at end of page",
389 		4096, 0x400, {
390 			{ 0x0c00, 0x0400 },
391 		},
392 	}, {
393 		"4K blocks, 1K at start of page",
394 		4096, 0x400, {
395 			{ 0x0, 0x1000 },
396 		},
397 	}, {
398 		"4K blocks, 1K within page, 512B start offset",
399 		4096, 0x400, {
400 			{ 0x0200, 0x0e00 },
401 		},
402 	},
403 
404 	{ 0 },
405 };
406 
407 static bool
run_test(const page_test_t * test,bool verbose)408 run_test(const page_test_t *test, bool verbose)
409 {
410 	size_t rem = test->size;
411 
412 	vdev_disk_check_alignment_t s = {
413 	    .blocksize = test->blocksize,
414 	};
415 
416 	for (int i = 0; test->pages[i][1] > 0; i++) {
417 		size_t off = test->pages[i][0];
418 		size_t len = test->pages[i][1];
419 
420 		size_t take = MIN(rem, len);
421 
422 		if (verbose)
423 			printf("  page %d [off %lx len %lx], "
424 			    "rem %lx, take %lx\n",
425 			    i, off, len, rem, take);
426 
427 		if (vdev_disk_check_alignment_cb(NULL, off, take, &s)) {
428 			if (verbose)
429 				printf("  ABORT: misalignment detected, "
430 				    "rem %lx\n", rem);
431 			return (false);
432 		}
433 
434 		rem -= take;
435 		if (rem == 0)
436 			break;
437 	}
438 
439 	if (rem > 0) {
440 		if (verbose)
441 			printf("  ABORT: ran out of pages, rem %lx\n", rem);
442 		return (false);
443 	}
444 
445 	return (true);
446 }
447 
448 static void
run_test_set(const page_test_t * tests,bool want,int * ntests,int * npassed)449 run_test_set(const page_test_t *tests, bool want, int *ntests, int *npassed)
450 {
451 	for (const page_test_t *test = &tests[0]; test->name; test++) {
452 		bool pass = (run_test(test, false) == want);
453 		if (pass) {
454 			printf("%c %s: PASS\n", want ? '+' : '-', test->name);
455 			(*npassed)++;
456 		} else {
457 			printf("%s: FAIL [expected %s, got %s]\n", test->name,
458 			    want ? "VALID" : "INVALID",
459 			    want ? "INVALID" : "VALID");
460 			run_test(test, true);
461 		}
462 		(*ntests)++;
463 	}
464 }
465 
main(void)466 int main(void) {
467 	int ntests = 0, npassed = 0;
468 
469 	run_test_set(valid_tests, true, &ntests, &npassed);
470 	run_test_set(invalid_tests, false, &ntests, &npassed);
471 
472 	printf("\n%d/%d tests passed\n", npassed, ntests);
473 
474 	return (ntests == npassed ? 0 : 1);
475 }
476