xref: /illumos-gate/usr/src/uts/common/io/stream_ktest.c (revision ac2250cb76bb32944fd2c8a3ba2cd3f79747748d)
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 2026 Oxide Computer Company
14  * Copyright 2024 Ryan Zezeski
15  */
16 #include <sys/modctl.h>
17 #include <sys/strsun.h>
18 #include <sys/ktest.h>
19 
20 static mblk_t *
21 allocb_zeroed(size_t len, uint_t pri)
22 {
23 	mblk_t *mp = allocb(len, pri);
24 
25 	if (mp != NULL) {
26 		bzero(mp->b_wptr, len);
27 		mp->b_wptr += len;
28 	}
29 	return (mp);
30 }
31 
32 static size_t
33 msgsegs(const mblk_t *mp)
34 {
35 	size_t out = 0;
36 
37 	while (mp != NULL) {
38 		out++;
39 		mp = mp->b_cont;
40 	}
41 
42 	return (out);
43 }
44 
45 /*
46  * Initialises a chain of n_mps zeroed mblks, each containing
47  * mplen[i] bytes.
48  */
49 static mblk_t *
50 init_chain(const size_t *mp_len, const size_t n_mps)
51 {
52 	mblk_t *out = NULL;
53 	mblk_t **cont = &out;
54 
55 	for (int i = 0; i < n_mps; ++i) {
56 		mblk_t *new = allocb_zeroed(mp_len[i], BPRI_LO);
57 		if (new == NULL)
58 			goto bail;
59 		*cont = new;
60 		cont = &new->b_cont;
61 	}
62 
63 	return (out);
64 
65 bail:
66 	if (out != NULL)
67 		freemsg(out);
68 	return (NULL);
69 }
70 
71 /*
72  * Test the MBLKL macro.
73  */
74 void
75 mblkl_test(ktest_ctx_hdl_t *ctx)
76 {
77 	mblk_t *mp1 = allocb(64, 0);
78 
79 	KT_EASSERT3P(mp1, !=, NULL, ctx);
80 	KT_ASSERT3UG(MBLKL(mp1), ==, 0, ctx, cleanup);
81 	mp1->b_wptr += 14;
82 	KT_ASSERT3UG(MBLKL(mp1), ==, 14, ctx, cleanup);
83 	KT_PASS(ctx);
84 
85 cleanup:
86 	freeb(mp1);
87 }
88 
89 void
90 msgsize_test(ktest_ctx_hdl_t *ctx)
91 {
92 	mblk_t *mp1 = allocb(14, 0);
93 	mblk_t *mp2 = allocb(20, 0);
94 
95 	KT_EASSERT3P(mp1, !=, NULL, ctx);
96 	KT_EASSERT3PG(mp2, !=, NULL, ctx, cleanup);
97 	KT_ASSERT3UG(msgsize(mp1), ==, 0, ctx, cleanup);
98 	KT_ASSERT3UG(msgsize(mp2), ==, 0, ctx, cleanup);
99 	mp1->b_wptr += 14;
100 	mp2->b_wptr += 20;
101 	KT_ASSERT3UG(msgsize(mp1), ==, 14, ctx, cleanup);
102 	KT_ASSERT3UG(msgsize(mp2), ==, 20, ctx, cleanup);
103 	mp1->b_cont = mp2;
104 	KT_ASSERT3UG(msgsize(mp1), ==, 34, ctx, cleanup);
105 	KT_ASSERT3UG(msgsize(mp2), ==, 20, ctx, cleanup);
106 	KT_PASS(ctx);
107 
108 cleanup:
109 	freeb(mp1);
110 
111 	if (mp2 != NULL) {
112 		freeb(mp2);
113 	}
114 }
115 
116 void
117 msgpullup_test(ktest_ctx_hdl_t *ctx)
118 {
119 	const size_t test_1[] = {8, 8};
120 	const size_t test_2[] = {4, 8};
121 	const size_t test_3[] = {4, 4, 8};
122 	mblk_t *mp = NULL;
123 	mblk_t *pullmp = NULL;
124 
125 	/*
126 	 * Test 1 -> 8 + 8, pullup 4.
127 	 * Should copy first 4 bytes, then link into the existing mp.
128 	 */
129 	mp = init_chain(test_1, 2);
130 	KT_EASSERT3P(mp, !=, NULL, ctx);
131 	KT_ASSERT3UG(msgsegs(mp), ==, 2, ctx, cleanup);
132 	pullmp = msgpullup(mp, 4);
133 	KT_EASSERT3PG(pullmp, !=, NULL, ctx, cleanup);
134 
135 	KT_ASSERT3UG(MBLKL(pullmp), ==, 4, ctx, cleanup);
136 	KT_ASSERT3UG(msgsize(pullmp), ==, 16, ctx, cleanup);
137 	KT_ASSERT3UG(msgsegs(pullmp), ==, 3, ctx, cleanup);
138 	KT_ASSERT3PG(pullmp->b_cont, !=, NULL, ctx, cleanup);
139 	KT_ASSERT3PG(pullmp->b_cont->b_datap, ==, mp->b_datap, ctx, cleanup);
140 
141 	freemsg(mp);
142 	freemsg(pullmp);
143 
144 	/*
145 	 * Test 2 -> 4 + 8, pullup 5.
146 	 * Should be 5(copy) + 7(referencing the original tail).
147 	 */
148 	mp = init_chain(test_2, 2);
149 	KT_EASSERT3P(mp, !=, NULL, ctx);
150 	KT_ASSERT3UG(msgsegs(mp), ==, 2, ctx, cleanup);
151 	pullmp = msgpullup(mp, 5);
152 	KT_EASSERT3PG(pullmp, !=, NULL, ctx, cleanup);
153 
154 	KT_ASSERT3UG(MBLKL(pullmp), ==, 5, ctx, cleanup);
155 	KT_ASSERT3UG(msgsize(pullmp), ==, 12, ctx, cleanup);
156 	KT_ASSERT3UG(msgsegs(pullmp), ==, 2, ctx, cleanup);
157 	KT_ASSERT3PG(pullmp->b_cont, !=, NULL, ctx, cleanup);
158 	KT_ASSERT3PG(pullmp->b_cont->b_datap, ==, mp->b_cont->b_datap, ctx,
159 	    cleanup);
160 
161 	freemsg(mp);
162 	freemsg(pullmp);
163 
164 	/*
165 	 * Test 3 -> 4 + 4 + 8, pullup 12.
166 	 * Should be 12(copy) + 4(original tail).
167 	 */
168 	mp = init_chain(test_3, 3);
169 	KT_EASSERT3P(mp, !=, NULL, ctx);
170 	KT_ASSERT3UG(msgsegs(mp), ==, 3, ctx, cleanup);
171 	pullmp = msgpullup(mp, 12);
172 	KT_EASSERT3PG(pullmp, !=, NULL, ctx, cleanup);
173 
174 	KT_ASSERT3UG(MBLKL(pullmp), ==, 12, ctx, cleanup);
175 	KT_ASSERT3UG(msgsize(pullmp), ==, 16, ctx, cleanup);
176 	KT_ASSERT3UG(msgsegs(pullmp), ==, 2, ctx, cleanup);
177 	KT_ASSERT3PG(pullmp->b_cont, !=, NULL, ctx, cleanup);
178 	KT_ASSERT3PG(pullmp->b_cont->b_datap, ==, mp->b_cont->b_cont->b_datap,
179 	    ctx, cleanup);
180 
181 	KT_PASS(ctx);
182 
183 cleanup:
184 	if (mp != NULL)
185 		freemsg(mp);
186 	if (pullmp != NULL)
187 		freemsg(pullmp);
188 }
189 
190 void
191 msgpullup_pad_test(ktest_ctx_hdl_t *ctx)
192 {
193 	const size_t test_1[] = {8, 8};
194 	mblk_t *mp = NULL;
195 	mblk_t *pullmp = NULL;
196 
197 	/*
198 	 * Test 1 -> Pullup 4 bytes with zero pad.
199 	 * This is equivalent to `msgpullup(mp, 4)`.
200 	 *
201 	 * Assert the same conditions we would for msgpullup.
202 	 */
203 	mp = init_chain(test_1, 2);
204 	KT_EASSERT3P(mp, !=, NULL, ctx);
205 	KT_ASSERT3UG(msgsegs(mp), ==, 2, ctx, cleanup);
206 	pullmp = msgpullup_pad(mp, 4, 0);
207 	KT_EASSERT3PG(pullmp, !=, NULL, ctx, cleanup);
208 
209 	KT_ASSERT3UG(MBLKL(pullmp), ==, 4, ctx, cleanup);
210 	KT_ASSERT3UG(msgsize(pullmp), ==, 16, ctx, cleanup);
211 	KT_ASSERT3UG(msgsegs(pullmp), ==, 3, ctx, cleanup);
212 	KT_ASSERT3PG(pullmp->b_cont, !=, NULL, ctx, cleanup);
213 	KT_ASSERT3PG(pullmp->b_cont->b_datap, ==, mp->b_datap, ctx, cleanup);
214 
215 	KT_ASSERT3PG(MBLKHEAD(pullmp), ==, 0, ctx, cleanup);
216 	/*
217 	 * We cannot assert that we do not have tailroom (e.g.,
218 	 * db_lim == b_wptr): mblks are drawn from caches of fixed capacity, so
219 	 * an allocation is likely to have excess space at the end.
220 	 */
221 
222 	freemsg(mp);
223 	freemsg(pullmp);
224 
225 	/*
226 	 * Test 2 -> pullup same length, with a padding of a large (odd) number
227 	 * of bytes.
228 	 */
229 	const size_t test_2_pad = 91;
230 	mp = init_chain(test_1, 2);
231 	KT_EASSERT3P(mp, !=, NULL, ctx);
232 	KT_ASSERT3UG(msgsegs(mp), ==, 2, ctx, cleanup);
233 	pullmp = msgpullup_pad(mp, 4, test_2_pad);
234 	KT_EASSERT3PG(pullmp, !=, NULL, ctx, cleanup);
235 
236 	KT_ASSERT3UG(MBLKL(pullmp), ==, 4, ctx, cleanup);
237 	KT_ASSERT3UG(msgsize(pullmp), ==, 16, ctx, cleanup);
238 	KT_ASSERT3UG(msgsegs(pullmp), ==, 3, ctx, cleanup);
239 	KT_ASSERT3PG(pullmp->b_cont, !=, NULL, ctx, cleanup);
240 	KT_ASSERT3PG(pullmp->b_cont->b_datap, ==, mp->b_datap, ctx, cleanup);
241 
242 	KT_ASSERT3PG(MBLKHEAD(pullmp), ==, test_2_pad, ctx, cleanup);
243 
244 	freemsg(mp);
245 	freemsg(pullmp);
246 
247 	/*
248 	 * Test 3 -> 64 (pad 8), pullup 64 (pad 4).
249 	 * Should be 64(copy, pad 4).
250 	 *
251 	 * We expect a brand new mblk here meeting these criteria, even though
252 	 * the input mblk is sufficient.
253 	 */
254 	const size_t test_3_pad = 4;
255 	mp = allocb_zeroed(72, BPRI_LO);
256 	KT_EASSERT3P(mp, !=, NULL, ctx);
257 	KT_ASSERT3UG(msgsegs(mp), ==, 1, ctx, cleanup);
258 	mp->b_rptr += 8;
259 	pullmp = msgpullup_pad(mp, msgsize(mp), test_3_pad);
260 	KT_EASSERT3PG(pullmp, !=, NULL, ctx, cleanup);
261 	KT_EASSERT3PG(pullmp, !=, mp, ctx, cleanup);
262 
263 	KT_ASSERT3UG(MBLKL(pullmp), ==, 64, ctx, cleanup);
264 	KT_ASSERT3UG(msgsize(pullmp), ==, 64, ctx, cleanup);
265 	KT_ASSERT3UG(msgsegs(pullmp), ==, 1, ctx, cleanup);
266 	KT_ASSERT3PG(pullmp->b_cont, ==, NULL, ctx, cleanup);
267 
268 	KT_ASSERT3PG(MBLKHEAD(pullmp), ==, test_3_pad, ctx, cleanup);
269 
270 	freemsg(mp);
271 	freemsg(pullmp);
272 
273 	/*
274 	 * Test 4 -> 14 + 20 + 8, pullup 42 (pad 2).
275 	 * Should be 42 (copy, pad 2).
276 	 *
277 	 * We expect a single output mblk, not part of a chain.
278 	 */
279 	const size_t test_4[] = {14, 20, 8};
280 	const size_t test_4_pad = 2;
281 	mp = init_chain(test_4, 3);
282 	KT_EASSERT3P(mp, !=, NULL, ctx);
283 	KT_ASSERT3UG(msgsegs(mp), ==, 3, ctx, cleanup);
284 	pullmp = msgpullup_pad(mp, 42, test_4_pad);
285 	KT_EASSERT3PG(pullmp, !=, NULL, ctx, cleanup);
286 	KT_EASSERT3PG(pullmp, !=, mp, ctx, cleanup);
287 
288 	KT_ASSERT3UG(MBLKL(pullmp), ==, 42, ctx, cleanup);
289 	KT_ASSERT3UG(msgsize(pullmp), ==, 42, ctx, cleanup);
290 	KT_ASSERT3UG(msgsegs(pullmp), ==, 1, ctx, cleanup);
291 	KT_ASSERT3PG(pullmp->b_cont, ==, NULL, ctx, cleanup);
292 
293 	/* Reference count on the initial chain remains unchanged. */
294 	for (mblk_t *curr = mp; curr != NULL; curr = curr->b_cont) {
295 		KT_ASSERT3UG(DB_REF(curr), ==, 1, ctx, cleanup);
296 	}
297 
298 	KT_ASSERT3PG(MBLKHEAD(pullmp), ==, test_4_pad, ctx, cleanup);
299 
300 	KT_PASS(ctx);
301 cleanup:
302 	if (mp != NULL)
303 		freemsg(mp);
304 	if (pullmp != NULL)
305 		freemsg(pullmp);
306 }
307 
308 static struct modlmisc stream_ktest_modlmisc = {
309 	.misc_modops = &mod_miscops,
310 	.misc_linkinfo = "stream ktest module"
311 };
312 
313 static struct modlinkage stream_ktest_modlinkage = {
314 	.ml_rev = MODREV_1,
315 	.ml_linkage = { &stream_ktest_modlmisc, NULL }
316 };
317 
318 int
319 _init()
320 {
321 	int ret;
322 	ktest_module_hdl_t *km = NULL;
323 	ktest_suite_hdl_t *ks = NULL;
324 
325 	VERIFY0(ktest_create_module("stream", &km));
326 	VERIFY0(ktest_add_suite(km, "mblk", &ks));
327 	VERIFY0(ktest_add_test(ks, "mblkl_test", mblkl_test, KTEST_FLAG_NONE));
328 	VERIFY0(ktest_add_test(ks, "msgsize_test", msgsize_test,
329 	    KTEST_FLAG_NONE));
330 	VERIFY0(ktest_add_test(ks, "msgpullup_test", msgpullup_test,
331 	    KTEST_FLAG_NONE));
332 	VERIFY0(ktest_add_test(ks, "msgpullup_pad_test", msgpullup_pad_test,
333 	    KTEST_FLAG_NONE));
334 
335 	if ((ret = ktest_register_module(km)) != 0) {
336 		ktest_free_module(km);
337 		return (ret);
338 	}
339 
340 	if ((ret = mod_install(&stream_ktest_modlinkage)) != 0) {
341 		ktest_unregister_module("stream");
342 		return (ret);
343 	}
344 
345 	return (0);
346 }
347 
348 int
349 _fini()
350 {
351 	ktest_unregister_module("stream");
352 	return (mod_remove(&stream_ktest_modlinkage));
353 }
354 
355 int
356 _info(struct modinfo *modinfop)
357 {
358 	return (mod_info(&stream_ktest_modlinkage, modinfop));
359 }
360