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 2025 Oxide Computer Company 14 */ 15 16 /* 17 * Various destructive options require a write lock. These include: 18 * 19 * - namespace creation and deletion 20 * - attaching or detaching a controller to a namespace 21 * - manipulating blkdev state 22 * - formatting a namespace 23 * 24 * While firmware operations also require this, that is harder to test in this 25 * case as we don't have valid firmware files or want to manipulate the device. 26 * We check that operations fail in sevearl different situations: 27 * 28 * 1) With no locks held. 29 * 2) With a namespace read lock. 30 * 3) With a controller read lock. 31 * 4) With a namespace write lock (note, some items succeed here). 32 * 33 * This test starts from the device-empty profile. 34 */ 35 36 #include <err.h> 37 #include <stdlib.h> 38 #include <sys/sysmacros.h> 39 #include "libnvme_test_common.h" 40 41 #define LOCK_NSID 1 42 43 static uint32_t lock_lbaf; 44 45 typedef enum { 46 LOCK_NONE, 47 LOCK_NS_RD, 48 LOCK_NS_WR, 49 LOCK_CTRL_RD, 50 LOCK_MAX 51 } lock_type_t; 52 53 typedef struct write_test { 54 const char *wt_desc; 55 nvme_err_t wt_errs[LOCK_MAX]; 56 nvme_ns_disc_level_t wt_disc; 57 bool (*wt_func)(nvme_ctrl_t *, const char *, nvme_err_t); 58 } write_test_t; 59 60 static bool 61 write_lock_ns_create(nvme_ctrl_t *ctrl, const char *desc, nvme_err_t exp) 62 { 63 uint64_t create_size = NVME_TEST_NS_SIZE / NVME_TEST_LBA_SIZE; 64 uint32_t nsid; 65 nvme_err_t act; 66 67 if (!libnvme_test_ns_create(ctrl, create_size, lock_lbaf, &nsid, 68 &act)) { 69 warnx("TEST FAILED: failed to initialize namespace create" 70 "request in lock %s iteration", desc); 71 return (false); 72 } else if (act != exp) { 73 warnx("TEST FAILED: namespace create with %s returned %s " 74 "(0x%x), but expected %s (0x%x)", desc, 75 nvme_ctrl_errtostr(ctrl, act), act, 76 nvme_ctrl_errtostr(ctrl, exp), exp); 77 return (false); 78 } 79 80 (void) printf("TEST PASSED: namespace delete with %s returned %s\n", 81 desc, nvme_ctrl_errtostr(ctrl, act)); 82 83 return (true); 84 } 85 86 static bool 87 write_lock_ns_delete(nvme_ctrl_t *ctrl, const char *desc, nvme_err_t exp) 88 { 89 nvme_err_t act; 90 91 if (!libnvme_test_ns_delete(ctrl, LOCK_NSID, &act)) { 92 warnx("TEST FAILED: failed to initialize namespace delete " 93 "request in lock %s iteration", desc); 94 return (false); 95 } else if (act != exp) { 96 warnx("TEST FAILED: namespace delete with %s returned %s " 97 "(0x%x), but expected %s (0x%x)", desc, 98 nvme_ctrl_errtostr(ctrl, act), act, 99 nvme_ctrl_errtostr(ctrl, exp), exp); 100 return (false); 101 } 102 103 (void) printf("TEST PASSED: namespace delete with %s returned %s\n", 104 desc, nvme_ctrl_errtostr(ctrl, act)); 105 return (true); 106 } 107 108 static bool 109 write_lock_ctrl_detach(nvme_ctrl_t *ctrl, const char *desc, nvme_err_t exp) 110 { 111 nvme_err_t act; 112 113 if (!libnvme_test_ctrl_attach(ctrl, LOCK_NSID, 114 NVME_NS_ATTACH_CTRL_DETACH, &act)) { 115 warnx("TEST FAILED: failed to initialize controller detach " 116 "request in lock %s iteration", desc); 117 return (false); 118 } else if (act != exp) { 119 warnx("TEST FAILED: controller detach with %s returned %s " 120 "(0x%x), but expected %s (0x%x)", desc, 121 nvme_ctrl_errtostr(ctrl, act), act, 122 nvme_ctrl_errtostr(ctrl, exp), exp); 123 return (false); 124 } 125 126 (void) printf("TEST PASSED: controller detach with %s returned %s\n", 127 desc, nvme_ctrl_errtostr(ctrl, act)); 128 return (true); 129 } 130 131 static bool 132 write_lock_ctrl_attach(nvme_ctrl_t *ctrl, const char *desc, nvme_err_t exp) 133 { 134 nvme_err_t act; 135 136 if (!libnvme_test_ctrl_attach(ctrl, LOCK_NSID, 137 NVME_NS_ATTACH_CTRL_ATTACH, &act)) { 138 warnx("TEST FAILED: failed to initialize controller attach " 139 "request in lock %s iteration", desc); 140 return (false); 141 } else if (act != exp) { 142 warnx("TEST FAILED: controller attach with %s returned %s " 143 "(0x%x), but expected %s (0x%x)", desc, 144 nvme_ctrl_errtostr(ctrl, act), act, 145 nvme_ctrl_errtostr(ctrl, exp), exp); 146 return (false); 147 } 148 149 (void) printf("TEST PASSED: controller attach with %s returned %s\n", 150 desc, nvme_ctrl_errtostr(ctrl, act)); 151 return (true); 152 } 153 154 static bool 155 write_lock_blkdev_detach(nvme_ctrl_t *ctrl, const char *desc, nvme_err_t exp) 156 { 157 nvme_err_t act; 158 159 if (!libnvme_test_ns_blkdev(ctrl, LOCK_NSID, false, &act)) { 160 warnx("TEST FAILED: failed to initialize blkdev detach " 161 "request in lock %s iteration", desc); 162 return (false); 163 } else if (act != exp) { 164 warnx("TEST FAILED: blkdev detach with %s returned %s (0x%x), " 165 "but expected %s (0x%x)", desc, 166 nvme_ctrl_errtostr(ctrl, act), act, 167 nvme_ctrl_errtostr(ctrl, exp), exp); 168 return (false); 169 } 170 171 (void) printf("TEST PASSED: blkdev detach with %s returned %s\n", desc, 172 nvme_ctrl_errtostr(ctrl, act)); 173 return (true); 174 } 175 176 static bool 177 write_lock_blkdev_attach(nvme_ctrl_t *ctrl, const char *desc, nvme_err_t exp) 178 { 179 nvme_err_t act; 180 181 if (!libnvme_test_ns_blkdev(ctrl, LOCK_NSID, true, &act)) { 182 warnx("TEST FAILED: failed to initialize blkdev attach " 183 "request in lock %s iteration", desc); 184 return (false); 185 } else if (act != exp) { 186 warnx("TEST FAILED: blkdev attach with %s returned %s (0x%x), " 187 "but expected %s (0x%x)", desc, 188 nvme_ctrl_errtostr(ctrl, act), act, 189 nvme_ctrl_errtostr(ctrl, exp), exp); 190 return (false); 191 } 192 193 (void) printf("TEST PASSED: blkdev attach with %s returned %s\n", desc, 194 nvme_ctrl_errtostr(ctrl, act)); 195 return (true); 196 } 197 198 static const write_test_t write_tests[] = { { 199 .wt_desc = "namespace create", 200 .wt_errs = { 201 [LOCK_NONE] = NVME_ERR_NEED_CTRL_WRLOCK, 202 [LOCK_NS_RD] = NVME_ERR_NEED_CTRL_WRLOCK, 203 [LOCK_NS_WR] = NVME_ERR_NEED_CTRL_WRLOCK, 204 [LOCK_CTRL_RD] = NVME_ERR_NEED_CTRL_WRLOCK 205 }, 206 .wt_disc = NVME_NS_DISC_F_ALL, 207 .wt_func = write_lock_ns_create 208 }, { 209 .wt_desc = "namespace delete", 210 .wt_errs = { 211 [LOCK_NONE] = NVME_ERR_NEED_NS_WRLOCK, 212 [LOCK_NS_RD] = NVME_ERR_NEED_NS_WRLOCK, 213 [LOCK_NS_WR] = NVME_ERR_OK, 214 [LOCK_CTRL_RD] = NVME_ERR_NEED_NS_WRLOCK 215 }, 216 .wt_disc = NVME_NS_DISC_F_ALLOCATED, 217 .wt_func = write_lock_ns_delete 218 }, { 219 .wt_desc = "controller attach", 220 .wt_errs = { 221 [LOCK_NONE] = NVME_ERR_NEED_NS_WRLOCK, 222 [LOCK_NS_RD] = NVME_ERR_NEED_NS_WRLOCK, 223 [LOCK_NS_WR] = NVME_ERR_OK, 224 [LOCK_CTRL_RD] = NVME_ERR_NEED_NS_WRLOCK 225 }, 226 .wt_disc = NVME_NS_DISC_F_ALLOCATED, 227 .wt_func = write_lock_ctrl_attach 228 }, { 229 .wt_desc = "controller detach", 230 .wt_errs = { 231 [LOCK_NONE] = NVME_ERR_NEED_NS_WRLOCK, 232 [LOCK_NS_RD] = NVME_ERR_NEED_NS_WRLOCK, 233 [LOCK_NS_WR] = NVME_ERR_OK, 234 [LOCK_CTRL_RD] = NVME_ERR_NEED_NS_WRLOCK 235 }, 236 .wt_disc = NVME_NS_DISC_F_ACTIVE, 237 .wt_func = write_lock_ctrl_detach 238 }, { 239 .wt_desc = "blkdev attach", 240 .wt_errs = { 241 [LOCK_NONE] = NVME_ERR_NEED_NS_WRLOCK, 242 [LOCK_NS_RD] = NVME_ERR_NEED_NS_WRLOCK, 243 [LOCK_NS_WR] = NVME_ERR_OK, 244 [LOCK_CTRL_RD] = NVME_ERR_NEED_NS_WRLOCK 245 }, 246 .wt_disc = NVME_NS_DISC_F_NOT_IGNORED, 247 .wt_func = write_lock_blkdev_attach 248 }, { 249 .wt_desc = "blkdev detach", 250 .wt_errs = { 251 [LOCK_NONE] = NVME_ERR_NEED_NS_WRLOCK, 252 [LOCK_NS_RD] = NVME_ERR_NEED_NS_WRLOCK, 253 [LOCK_NS_WR] = NVME_ERR_OK, 254 [LOCK_CTRL_RD] = NVME_ERR_NEED_NS_WRLOCK 255 }, 256 .wt_disc = NVME_NS_DISC_F_BLKDEV, 257 .wt_func = write_lock_blkdev_detach 258 } }; 259 260 typedef struct lock_info { 261 const char *li_desc; 262 bool (*li_lock_f)(nvme_ctrl_t *, nvme_ns_t *); 263 void (*li_unlock_f)(nvme_ctrl_t *, nvme_ns_t *); 264 } lock_info_t; 265 266 static bool 267 lock_none_lock(nvme_ctrl_t *ctrl, nvme_ns_t *ns) 268 { 269 return (true); 270 } 271 272 static bool 273 lock_ns_read_lock(nvme_ctrl_t *ctrl, nvme_ns_t *ns) 274 { 275 return (nvme_ns_lock(ns, NVME_LOCK_L_READ, NVME_LOCK_F_DONT_BLOCK)); 276 } 277 278 static bool 279 lock_ns_write_lock(nvme_ctrl_t *ctrl, nvme_ns_t *ns) 280 { 281 return (nvme_ns_lock(ns, NVME_LOCK_L_WRITE, NVME_LOCK_F_DONT_BLOCK)); 282 } 283 284 static bool 285 lock_ctrl_read_lock(nvme_ctrl_t *ctrl, nvme_ns_t *ns) 286 { 287 return (nvme_ctrl_lock(ctrl, NVME_LOCK_L_READ, NVME_LOCK_F_DONT_BLOCK)); 288 } 289 290 static void 291 lock_none_unlock(nvme_ctrl_t *ctrl, nvme_ns_t *ns) 292 { 293 } 294 295 static void 296 lock_ns_unlock(nvme_ctrl_t *ctrl, nvme_ns_t *ns) 297 { 298 nvme_ns_unlock(ns); 299 } 300 301 static void 302 lock_ctrl_unlock(nvme_ctrl_t *ctrl, nvme_ns_t *ns) 303 { 304 nvme_ctrl_unlock(ctrl); 305 } 306 307 lock_info_t lock_info[LOCK_MAX] = { 308 [LOCK_NONE] = { 309 .li_desc = "no lock", 310 .li_lock_f = lock_none_lock, 311 .li_unlock_f = lock_none_unlock, 312 }, 313 [LOCK_NS_RD] = { 314 .li_desc = "namespace read lock", 315 .li_lock_f = lock_ns_read_lock, 316 .li_unlock_f = lock_ns_unlock, 317 }, 318 [LOCK_NS_WR] = { 319 .li_desc = "namespace write lock", 320 .li_lock_f = lock_ns_write_lock, 321 .li_unlock_f = lock_ns_unlock, 322 }, 323 [LOCK_CTRL_RD] = { 324 .li_desc = "controller read lock", 325 .li_lock_f = lock_ctrl_read_lock, 326 .li_unlock_f = lock_ctrl_unlock, 327 } 328 329 }; 330 331 static bool 332 write_test_one(const write_test_t *test, nvme_ctrl_t *ctrl, nvme_ns_t *ns) 333 { 334 bool ret = true; 335 336 for (lock_type_t i = LOCK_NONE; i < LOCK_MAX; i++) { 337 if (!nvme_ctrl_lock(ctrl, NVME_LOCK_L_WRITE, 338 NVME_LOCK_F_DONT_BLOCK)) { 339 libnvme_test_ctrl_fatal(ctrl, "failed to obtain write " 340 "lock"); 341 } 342 343 if (!libnvme_test_setup_ns(ctrl, test->wt_disc, LOCK_NSID, 344 lock_lbaf)) { 345 libnvme_test_ctrl_fatal(ctrl, "failed to change state " 346 "to 0x%x", test->wt_disc); 347 } 348 349 nvme_ctrl_unlock(ctrl); 350 351 if (!lock_info[i].li_lock_f(ctrl, ns)) { 352 ret = false; 353 continue; 354 } 355 356 if (!test->wt_func(ctrl, lock_info[i].li_desc, 357 test->wt_errs[i])) { 358 ret = false; 359 } 360 361 lock_info[i].li_unlock_f(ctrl, ns); 362 } 363 364 return (ret); 365 } 366 367 int 368 main(void) 369 { 370 int ret = EXIT_SUCCESS; 371 nvme_t *nvme; 372 nvme_ctrl_t *ctrl; 373 nvme_ctrl_info_t *info; 374 nvme_ns_t *ns; 375 376 libnvme_test_init(&nvme, &ctrl); 377 378 if (!nvme_ctrl_info_snap(ctrl, &info)) { 379 libnvme_test_ctrl_fatal(ctrl, "failed to get info snapshot"); 380 } 381 382 if (!nvme_ns_init(ctrl, LOCK_NSID, &ns)) { 383 libnvme_test_ctrl_fatal(ctrl, "failed to create ns %u " 384 "nvme_ns_t", LOCK_NSID); 385 } 386 387 if (!libnvme_test_lbaf(info, NVME_TEST_LBA_SIZE, &lock_lbaf)) { 388 errx(EXIT_FAILURE, "failed to find 4K LBA format, cannot " 389 "continue"); 390 } 391 392 for (size_t i = 0; i < ARRAY_SIZE(write_tests); i++) { 393 if (!write_test_one(&write_tests[i], ctrl, ns)) 394 ret = EXIT_FAILURE; 395 } 396 397 nvme_ns_fini(ns); 398 nvme_ctrl_info_free(info); 399 nvme_ctrl_fini(ctrl); 400 nvme_fini(nvme); 401 402 if (ret == EXIT_SUCCESS) { 403 (void) printf("All tests passed successfully\n"); 404 } 405 406 return (ret); 407 } 408