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