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