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_pages_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
39struct page;
40
41typedef struct {
42	uint32_t  bmask;
43	uint32_t  npages;
44	uint32_t  end;
45} vdev_disk_check_pages_t;
46
47static int
48vdev_disk_check_pages_cb(struct page *page, size_t off, size_t len, void *priv)
49{
50	(void) page;
51	vdev_disk_check_pages_t *s = priv;
52
53	/*
54	 * If we didn't finish on a block size boundary last time, then there
55	 * would be a gap if we tried to use this ABD as-is, so abort.
56	 */
57	if (s->end != 0)
58		return (1);
59
60	/*
61	 * Note if we're taking less than a full block, so we can check it
62	 * above on the next call.
63	 */
64	s->end = (off+len) & s->bmask;
65
66	/* All blocks after the first must start on a block size boundary. */
67	if (s->npages != 0 && (off & s->bmask) != 0)
68		return (1);
69
70	s->npages++;
71	return (0);
72}
73
74typedef struct {
75	/* test name */
76	const char	*name;
77
78	/* blocks size mask */
79	uint32_t	mask;
80
81	/* amount of data to take */
82	size_t		size;
83
84	/* [start offset in page, len to end of page or size] */
85	size_t		pages[16][2];
86} page_test_t;
87
88static const page_test_t valid_tests[] = {
89	/* 512B block tests */
90	{
91		"512B blocks, 4K single page",
92		0x1ff, 0x1000, {
93			{ 0x0, 0x1000 },
94		},
95	}, {
96		"512B blocks, 1K at start of page",
97		0x1ff, 0x400, {
98			{ 0x0, 0x1000 },
99		},
100	}, {
101		"512B blocks, 1K at end of page",
102		0x1ff, 0x400, {
103			{ 0x0c00, 0x0400 },
104		},
105	}, {
106		"512B blocks, 1K within page, 512B start offset",
107		0x1ff, 0x400, {
108			{ 0x0200, 0x0e00 },
109		},
110	}, {
111		"512B blocks, 8K across 2x4K pages",
112		0x1ff, 0x2000, {
113			{ 0x0, 0x1000 },
114			{ 0x0, 0x1000 },
115		},
116	}, {
117		"512B blocks, 4K across two pages, 2K start offset",
118		0x1ff, 0x1000, {
119			{ 0x0800, 0x0800 },
120			{ 0x0,    0x0800 },
121		},
122	}, {
123		"512B blocks, 16K across 5x4K pages, 512B start offset",
124		0x1ff, 0x4000, {
125			{ 0x0200, 0x0e00 },
126			{ 0x0,    0x1000 },
127			{ 0x0,    0x1000 },
128			{ 0x0,    0x1000 },
129			{ 0x0,    0x0200 },
130		},
131	}, {
132		"512B blocks, 64K data, 8x8K compound pages",
133		0x1ff, 0x10000, {
134			{ 0x0, 0x2000 },
135			{ 0x0, 0x2000 },
136			{ 0x0, 0x2000 },
137			{ 0x0, 0x2000 },
138			{ 0x0, 0x2000 },
139			{ 0x0, 0x2000 },
140			{ 0x0, 0x2000 },
141			{ 0x0, 0x2000 },
142		},
143	}, {
144		"512B blocks, 64K data, 9x8K compound pages, 512B start offset",
145		0x1ff, 0x10000, {
146			{ 0x0200, 0x1e00 },
147			{ 0x0,    0x2000 },
148			{ 0x0,    0x2000 },
149			{ 0x0,    0x2000 },
150			{ 0x0,    0x2000 },
151			{ 0x0,    0x2000 },
152			{ 0x0,    0x2000 },
153			{ 0x0,    0x2000 },
154			{ 0x0,    0x0200 },
155		},
156	}, {
157		"512B blocks, 64K data, 2x16K compound pages, 8x4K pages",
158		0x1ff, 0x10000, {
159			{ 0x0, 0x8000 },
160			{ 0x0, 0x8000 },
161			{ 0x0, 0x1000 },
162			{ 0x0, 0x1000 },
163			{ 0x0, 0x1000 },
164			{ 0x0, 0x1000 },
165			{ 0x0, 0x1000 },
166			{ 0x0, 0x1000 },
167			{ 0x0, 0x1000 },
168			{ 0x0, 0x1000 },
169		},
170	}, {
171		"512B blocks, 64K data, mixed 4K/8K/16K pages",
172		0x1ff, 0x10000, {
173			{ 0x0, 0x1000 },
174			{ 0x0, 0x2000 },
175			{ 0x0, 0x1000 },
176			{ 0x0, 0x8000 },
177			{ 0x0, 0x1000 },
178			{ 0x0, 0x1000 },
179			{ 0x0, 0x2000 },
180			{ 0x0, 0x1000 },
181			{ 0x0, 0x1000 },
182			{ 0x0, 0x2000 },
183		},
184	}, {
185		"512B blocks, 64K data, mixed 4K/8K/16K pages, 1K start offset",
186		0x1ff, 0x10000, {
187			{ 0x0400, 0x0c00 },
188			{ 0x0,    0x1000 },
189			{ 0x0,    0x1000 },
190			{ 0x0,    0x1000 },
191			{ 0x0,    0x2000 },
192			{ 0x0,    0x2000 },
193			{ 0x0,    0x1000 },
194			{ 0x0,    0x8000 },
195			{ 0x0,    0x1000 },
196			{ 0x0,    0x0400 },
197		},
198	},
199
200	/* 4K block tests */
201	{
202		"4K blocks, 4K single page",
203		0xfff, 0x1000, {
204			{ 0x0, 0x1000 },
205		},
206	}, {
207		"4K blocks, 1K at start of page",
208		0xfff, 0x400, {
209			{ 0x0, 0x1000 },
210		},
211	}, {
212		"4K blocks, 1K at end of page",
213		0xfff, 0x400, {
214			{ 0x0c00, 0x0400 },
215		},
216	}, {
217		"4K blocks, 1K within page, 512B start offset",
218		0xfff, 0x400, {
219			{ 0x0200, 0x0e00 },
220		},
221	}, {
222		"4K blocks, 8K across 2x4K pages",
223		0xfff, 0x2000, {
224			{ 0x0, 0x1000 },
225			{ 0x0, 0x1000 },
226		},
227	}, {
228		"4K blocks, 4K across two pages, 2K start offset",
229		0xfff, 0x1000, {
230			{ 0x0800, 0x0800 },
231			{ 0x0,    0x0800 },
232		},
233	}, {
234		"4K blocks, 16K across 5x4K pages, 512B start offset",
235		0xfff, 0x4000, {
236			{ 0x0200, 0x0e00 },
237			{ 0x0,    0x1000 },
238			{ 0x0,    0x1000 },
239			{ 0x0,    0x1000 },
240			{ 0x0,    0x0200 },
241		},
242	}, {
243		"4K blocks, 64K data, 8x8K compound pages",
244		0xfff, 0x10000, {
245			{ 0x0, 0x2000 },
246			{ 0x0, 0x2000 },
247			{ 0x0, 0x2000 },
248			{ 0x0, 0x2000 },
249			{ 0x0, 0x2000 },
250			{ 0x0, 0x2000 },
251			{ 0x0, 0x2000 },
252			{ 0x0, 0x2000 },
253		},
254	}, {
255		"4K blocks, 64K data, 9x8K compound pages, 512B start offset",
256		0xfff, 0x10000, {
257			{ 0x0200, 0x1e00 },
258			{ 0x0,    0x2000 },
259			{ 0x0,    0x2000 },
260			{ 0x0,    0x2000 },
261			{ 0x0,    0x2000 },
262			{ 0x0,    0x2000 },
263			{ 0x0,    0x2000 },
264			{ 0x0,    0x2000 },
265			{ 0x0,    0x0200 },
266		},
267	}, {
268		"4K blocks, 64K data, 2x16K compound pages, 8x4K pages",
269		0xfff, 0x10000, {
270			{ 0x0, 0x8000 },
271			{ 0x0, 0x8000 },
272			{ 0x0, 0x1000 },
273			{ 0x0, 0x1000 },
274			{ 0x0, 0x1000 },
275			{ 0x0, 0x1000 },
276			{ 0x0, 0x1000 },
277			{ 0x0, 0x1000 },
278			{ 0x0, 0x1000 },
279			{ 0x0, 0x1000 },
280		},
281	}, {
282		"4K blocks, 64K data, mixed 4K/8K/16K pages",
283		0xfff, 0x10000, {
284			{ 0x0, 0x1000 },
285			{ 0x0, 0x2000 },
286			{ 0x0, 0x1000 },
287			{ 0x0, 0x8000 },
288			{ 0x0, 0x1000 },
289			{ 0x0, 0x1000 },
290			{ 0x0, 0x2000 },
291			{ 0x0, 0x1000 },
292			{ 0x0, 0x1000 },
293			{ 0x0, 0x2000 },
294		},
295	}, {
296		"4K blocks, 64K data, mixed 4K/8K/16K pages, 1K start offset",
297		0xfff, 0x10000, {
298			{ 0x0400, 0x0c00 },
299			{ 0x0,    0x1000 },
300			{ 0x0,    0x1000 },
301			{ 0x0,    0x1000 },
302			{ 0x0,    0x2000 },
303			{ 0x0,    0x2000 },
304			{ 0x0,    0x1000 },
305			{ 0x0,    0x8000 },
306			{ 0x0,    0x1000 },
307			{ 0x0,    0x0400 },
308		},
309	},
310
311	{ 0 },
312};
313
314static const page_test_t invalid_tests[] = {
315	{
316		"512B blocks, 16K data, 512 leader (gang block simulation)",
317		0x1ff, 0x8000, {
318			{ 0x0, 0x0200 },
319			{ 0x0, 0x1000 },
320			{ 0x0, 0x1000 },
321			{ 0x0, 0x1000 },
322			{ 0x0, 0x0c00 },
323		},
324	}, {
325		"4K blocks, 32K data, 2 incompatible spans "
326		"(gang abd simulation)",
327		0xfff, 0x8000, {
328			{ 0x0800, 0x0800 },
329			{ 0x0,    0x1000 },
330			{ 0x0,    0x1000 },
331			{ 0x0,    0x1000 },
332			{ 0x0,    0x0800 },
333			{ 0x0800, 0x0800 },
334			{ 0x0,    0x1000 },
335			{ 0x0,    0x1000 },
336			{ 0x0,    0x1000 },
337			{ 0x0,    0x0800 },
338		},
339	},
340	{ 0 },
341};
342
343static bool
344run_test(const page_test_t *test, bool verbose)
345{
346	size_t rem = test->size;
347
348	vdev_disk_check_pages_t s = {
349	    .bmask = 0xfff,
350	    .npages = 0,
351	    .end = 0,
352	};
353
354	for (int i = 0; test->pages[i][1] > 0; i++) {
355		size_t off = test->pages[i][0];
356		size_t len = test->pages[i][1];
357
358		size_t take = MIN(rem, len);
359
360		if (verbose)
361			printf("  page %d [off %lx len %lx], "
362			    "rem %lx, take %lx\n",
363			    i, off, len, rem, take);
364
365		if (vdev_disk_check_pages_cb(NULL, off, take, &s)) {
366			if (verbose)
367				printf("  ABORT: misalignment detected, "
368				    "rem %lx\n", rem);
369			return (false);
370		}
371
372		rem -= take;
373		if (rem == 0)
374			break;
375	}
376
377	if (rem > 0) {
378		if (verbose)
379			printf("  ABORT: ran out of pages, rem %lx\n", rem);
380		return (false);
381	}
382
383	return (true);
384}
385
386static void
387run_test_set(const page_test_t *tests, bool want, int *ntests, int *npassed)
388{
389	for (const page_test_t *test = &tests[0]; test->name; test++) {
390		bool pass = (run_test(test, false) == want);
391		if (pass) {
392			printf("%s: PASS\n", test->name);
393			(*npassed)++;
394		} else {
395			printf("%s: FAIL [expected %s, got %s]\n", test->name,
396			    want ? "VALID" : "INVALID",
397			    want ? "INVALID" : "VALID");
398			run_test(test, true);
399		}
400		(*ntests)++;
401	}
402}
403
404int main(void) {
405	int ntests = 0, npassed = 0;
406
407	run_test_set(valid_tests, true, &ntests, &npassed);
408	run_test_set(invalid_tests, false, &ntests, &npassed);
409
410	printf("\n%d/%d tests passed\n", npassed, ntests);
411
412	return (ntests == npassed ? 0 : 1);
413}
414