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