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 * Test basic features around formatting namespaces and secure erase. In 18 * particular we want to make sure that we can do the following: 19 * 20 * - Format a single namespace 21 * - Broadcast format all active namespaces 22 * - Secure erase (whether broadcast or serially) 23 * 24 * We create two 1 GiB namespaces that we use for this. The namespace size 25 * hopefully keeps format and secure erase timing reasonable. We end up writing 26 * a message to sector 0 of each namespace to try to verify data was actually 27 * erased. 28 * 29 * This test starts from the device-empty profile so we can control the size and 30 * space of namespaces. 31 */ 32 33 #include <err.h> 34 #include <stdlib.h> 35 #include <stdio.h> 36 #include <string.h> 37 #include <libdevinfo.h> 38 #include <fcntl.h> 39 #include <unistd.h> 40 41 #include "libnvme_test_common.h" 42 43 #define NSID_BASE 1 44 #define NNSIDS 2 45 46 /* 47 * Because we use the raw block device, our size needs to be a multiple of both 48 * of the sector sizes we use (4k, 512), which gives us a 4k buffer. 49 */ 50 #define FMT_BUF_SIZE 4096 51 52 static const char *format_msgs[NNSIDS] = { 53 "Those Who Fight Further", 54 "Those Who Deny the Dawn" 55 }; 56 57 /* 58 * Check the results of a format / erase namespace and verify that our old data 59 * is gone. Write new data. 60 */ 61 static bool 62 format_io_verify(int fd, uint32_t nsid) 63 { 64 uint8_t buf[FMT_BUF_SIZE]; 65 const char *msg = format_msgs[nsid - 1]; 66 size_t msglen = strlen(msg) + 1; 67 68 if (pread(fd, buf, sizeof (buf), 0) != sizeof (buf)) { 69 warn("TEST FAILED: failed to read from nsid %u", nsid); 70 return (false); 71 } 72 73 /* 74 * These contents should never match our message. 75 */ 76 if (memcmp(buf, msg, msglen) != 0) { 77 (void) printf("TEST PASSED: namespace %u data successfully " 78 "cleared\n", nsid); 79 } else { 80 warnx("TEST FAILED: nsid %u data was not successfully erased", 81 nsid); 82 (void) printf("Unexpected data: found\n"); 83 for (size_t i = 0; i < msglen; i++) { 84 (void) printf("buf[%u]: %02x\n", i, buf[i]); 85 } 86 87 return (false); 88 } 89 90 (void) memset(buf, 0x77, sizeof (buf)); 91 (void) memcpy(buf, msg, msglen); 92 if (pwrite(fd, buf, sizeof (buf), 0) != sizeof (buf)) { 93 warnx("TEST FAILED: failed to write updated buffer to nsid " 94 "%u", nsid); 95 return (false); 96 } 97 98 if (fsync(fd) != 0) { 99 warn("TEST FAILED: failed to synchronize raw device write " 100 "to ns %u", nsid); 101 } 102 103 (void) memset(buf, 0, sizeof (buf)); 104 if (pread(fd, buf, sizeof (buf), 0) != sizeof (buf)) { 105 warnx("TEST FAILED: failed to read back data written to %u", 106 nsid); 107 return (false); 108 } 109 110 if (memcmp(buf, msg, msglen) != 0) { 111 warnx("TEST FAILED: did not get back data written to nsid %u", 112 nsid); 113 (void) printf("Mismatched data: found/expected\n"); 114 for (size_t i = 0; i < msglen; i++) { 115 (void) printf("buf[%u]: %02x/%02x\n", i, buf[i], 116 msg[i]); 117 } 118 return (false); 119 } 120 121 (void) printf("TEST PASSED: successfully wrote message to nsid %u\n", 122 nsid); 123 return (true); 124 } 125 126 /* 127 * Used after another namespace has been formatted to check that the other is 128 * still okay and its data hasn't been overwritten. 129 */ 130 static bool 131 format_io_check(int fd, uint32_t nsid) 132 { 133 uint8_t buf[FMT_BUF_SIZE]; 134 const char *msg = format_msgs[nsid - 1]; 135 size_t msglen = strlen(msg) + 1; 136 137 if (pread(fd, buf, sizeof (buf), 0) != sizeof (buf)) { 138 warnx("TEST FAILED: failed to read back data on nsid %u", 139 nsid); 140 return (false); 141 } 142 143 if (memcmp(buf, msg, msglen) != 0) { 144 warnx("TEST FAILED: data on nsid %u changed after format of " 145 "other namespace", nsid); 146 (void) printf("Mismatched data: found/expected\n"); 147 for (size_t i = 0; i < msglen; i++) { 148 (void) printf("buf[%u]: %02x/%02x\n", i, buf[i], 149 msg[i]); 150 } 151 return (false); 152 } 153 154 (void) printf("TEST PASSED: verified prior message on nsid %u\n", 155 nsid); 156 return (true); 157 } 158 159 /* 160 * Get the file descripto that corresponds to the raw whole disk device which is 161 * generally s2 or 'c,raw'. 162 */ 163 static int 164 format_blkdev_fd(const char *bd_addr) 165 { 166 int fd = -1; 167 di_node_t root; 168 169 root = di_init("/", DINFOCPYALL); 170 if (root == DI_NODE_NIL) { 171 warnx("failed to take devinfo snapshot"); 172 return (-1); 173 } 174 175 for (di_node_t n = di_drv_first_node("blkdev", root); n != DI_NODE_NIL; 176 n = di_drv_next_node(n)) { 177 char *devfs, path[PATH_MAX]; 178 const char *addr = di_bus_addr(n); 179 180 if (addr == NULL) { 181 continue; 182 } 183 184 if (strcmp(bd_addr, addr) != 0) 185 continue; 186 187 devfs = di_devfs_path(n); 188 if (devfs == NULL) { 189 warn("failed to get devfs path for blkdev %s", bd_addr); 190 goto out; 191 } 192 193 if (snprintf(path, sizeof (path), "/devices/%s:c,raw", devfs) >= 194 sizeof (path)) { 195 di_devfs_path_free(devfs); 196 warnx("Construction of blkdev %s minor path exceeded " 197 "internal buffer", bd_addr); 198 goto out; 199 } 200 201 /* 202 * We need to use O_NDELAY here to convince the system that it's 203 * okay that there isn't valid CMLB information yet, which is 204 * fine because we're trashing this device. 205 */ 206 di_devfs_path_free(devfs); 207 fd = open(path, O_RDWR | O_NDELAY); 208 if (fd < 0) { 209 warn("failed to open %s", path); 210 goto out; 211 } 212 } 213 214 if (fd == -1) { 215 warnx("failed to find di_node_t that matches %s", bd_addr); 216 } 217 218 out: 219 di_fini(root); 220 return (fd); 221 } 222 223 /* 224 * Leave a message in sector 0 of each device that we can later verify is there 225 * or not. 226 */ 227 static bool 228 format_nsid_io(nvme_ctrl_t *ctrl, uint32_t nsid, bool (*cb)(int, uint32_t)) 229 { 230 int fd; 231 nvme_ns_info_t *ns = NULL; 232 const char *bd_addr; 233 bool ret = false; 234 235 if (!libnvme_test_setup_ns(ctrl, NVME_NS_DISC_F_BLKDEV, nsid, 236 UINT32_MAX)) { 237 libnvme_test_ctrl_warn(ctrl, "failed to attach blkdev to " 238 "nsid %u", nsid); 239 return (false); 240 } 241 242 if (!nvme_ctrl_ns_info_snap(ctrl, nsid, &ns)) { 243 libnvme_test_ctrl_warn(ctrl, "failed to take namespace %u " 244 "info snapshot", nsid); 245 goto out; 246 } 247 248 if (!nvme_ns_info_bd_addr(ns, &bd_addr)) { 249 libnvme_test_ctrl_warn(ctrl, "failed to get blkdev address " 250 "for namespace %u", nsid); 251 goto out; 252 } 253 254 if ((fd = format_blkdev_fd(bd_addr)) < 0) { 255 warnx("TEST FAILED: failed to acquire blkdev fd for nsid %u " 256 "to write data", nsid); 257 goto out; 258 } 259 260 ret = cb(fd, nsid); 261 VERIFY0(close(fd)); 262 263 out: 264 if (!libnvme_test_setup_ns(ctrl, NVME_NS_DISC_F_NOT_IGNORED, nsid, 265 UINT32_MAX)) { 266 libnvme_test_ctrl_warn(ctrl, "failed to detach blkdev from " 267 "nsid %u", nsid); 268 ret = false; 269 } 270 271 nvme_ns_info_free(ns); 272 return (ret); 273 } 274 275 /* 276 * Verify that a given namespace has the expected LBA format. 277 */ 278 static bool 279 format_check_lbaf(nvme_ctrl_t *ctrl, uint32_t nsid, uint32_t lbaf) 280 { 281 bool ret = false; 282 nvme_ns_info_t *info; 283 const nvme_nvm_lba_fmt_t *lba; 284 285 if (!nvme_ctrl_ns_info_snap(ctrl, nsid, &info)) { 286 libnvme_test_ctrl_warn(ctrl, "failed to take namespace %u " 287 "info snapshot", nsid); 288 return (false); 289 } 290 291 if (!nvme_ns_info_curformat(info, &lba)) { 292 libnvme_test_ctrl_warn(ctrl, "failed to get namespace %u " 293 "current lba format", nsid); 294 goto out; 295 } 296 297 if (nvme_nvm_lba_fmt_id(lba) == lbaf) { 298 (void) printf("TEST PASSED: Succesfully formatted namespace %u " 299 "to format %u (0x%" PRIx64 ")\n", nsid, lbaf, 300 nvme_nvm_lba_fmt_data_size(lba)); 301 ret = true; 302 } else { 303 warnx("TEST FAILED: Formatted namespace %u ended up with LBA " 304 "format %u (0x%" PRIx64 " bytes), not %u", nsid, 305 nvme_nvm_lba_fmt_id(lba), nvme_nvm_lba_fmt_data_size(lba), 306 lbaf); 307 } 308 309 out: 310 nvme_ns_info_free(info); 311 return (ret); 312 } 313 314 static bool 315 format_ns(nvme_ctrl_t *ctrl, uint32_t nsid, uint32_t ses, uint32_t lbaf) 316 { 317 bool ret = true; 318 nvme_format_req_t *req; 319 320 if (!nvme_format_req_init(ctrl, &req)) { 321 libnvme_test_ctrl_warn(ctrl, "failed to initialize format " 322 "request"); 323 ret = false; 324 goto done; 325 } 326 327 if (!nvme_format_req_set_lbaf(req, lbaf)) { 328 libnvme_test_ctrl_warn(ctrl, "failed to set format lbaf to " 329 "0x%x", lbaf); 330 ret = false; 331 goto done; 332 } 333 334 if (!nvme_format_req_set_ses(req, ses)) { 335 libnvme_test_ctrl_warn(ctrl, "failed to set format ses to 0x%x", 336 ses); 337 ret = false; 338 goto done; 339 } 340 341 if (!nvme_format_req_set_nsid(req, nsid)) { 342 libnvme_test_ctrl_warn(ctrl, "failed to set format nsid to " 343 "0x%x", nsid); 344 ret = false; 345 goto done; 346 } 347 348 if (!nvme_format_req_exec(req)) { 349 libnvme_test_ctrl_warn(ctrl, "failed to execute format " 350 "namespace for nsid %u", nsid); 351 ret = false; 352 goto done; 353 } 354 355 for (uint32_t i = 0; i < NNSIDS; i++) { 356 if (nsid == NVME_NSID_BCAST || nsid == (NSID_BASE + i)) { 357 if (!format_check_lbaf(ctrl, NSID_BASE + i, lbaf)) { 358 ret = false; 359 } 360 } 361 } 362 363 done: 364 nvme_format_req_fini(req); 365 return (ret); 366 } 367 368 int 369 main(void) 370 { 371 int ret = EXIT_SUCCESS; 372 nvme_t *nvme; 373 nvme_ctrl_t *ctrl; 374 nvme_ctrl_info_t *info; 375 uint32_t lbaf_4k, lbaf_512, ses; 376 const nvme_identify_ctrl_t *id; 377 378 libnvme_test_init(&nvme, &ctrl); 379 if (!nvme_ctrl_lock(ctrl, NVME_LOCK_L_WRITE, NVME_LOCK_F_DONT_BLOCK)) { 380 libnvme_test_ctrl_fatal(ctrl, "failed to obtain write lock"); 381 } 382 383 if (!nvme_ctrl_info_snap(ctrl, &info)) { 384 libnvme_test_ctrl_fatal(ctrl, "failed to get info snapshot"); 385 } 386 387 if (!libnvme_test_lbaf(info, 4096, &lbaf_4k)) { 388 errx(EXIT_FAILURE, "failed to find 4K LBA format, cannot " 389 "continue"); 390 } 391 392 if (!libnvme_test_lbaf(info, 512, &lbaf_512)) { 393 errx(EXIT_FAILURE, "failed to find 512 byte LBA format, cannot " 394 "continue"); 395 } 396 (void) printf("LBA indexes: 512/4k %u/%u\n", lbaf_512, lbaf_4k); 397 398 /* 399 * Start by creating a single 512 byte namespace. We only create a 400 * single one for now because we expect that many devices don't like 401 * having namespaces with different LBA formats despite indicating in 402 * the format NVM attributes that namespaces are independent. 403 */ 404 if (!libnvme_test_setup_ns(ctrl, NVME_NS_DISC_F_ACTIVE, 1, lbaf_512)) { 405 libnvme_test_ctrl_fatal(ctrl, "failed to create initial " 406 "namespaces"); 407 } 408 409 /* 410 * Because the namespaces was created as part of this (we assume we 411 * started from the empty device-reset profile), nothing should be here. 412 */ 413 if (!format_nsid_io(ctrl, 1, format_io_verify)) 414 ret = EXIT_FAILURE; 415 416 /* 417 * Format it to itself and make sure that data is gone. 418 */ 419 if (!format_ns(ctrl, 1, NVME_FRMT_SES_NONE, lbaf_512)) 420 ret = EXIT_FAILURE; 421 422 if (!format_nsid_io(ctrl, 1, format_io_verify)) 423 ret = EXIT_FAILURE; 424 425 /* 426 * Transform it to 4K now. 427 */ 428 if (!format_ns(ctrl, 1, NVME_FRMT_SES_NONE, lbaf_4k)) 429 ret = EXIT_FAILURE; 430 if (!format_nsid_io(ctrl, 1, format_io_verify)) 431 ret = EXIT_FAILURE; 432 433 /* 434 * Now create a second namespace. At this point we are constrained to 435 * 4k. 436 */ 437 if (!libnvme_test_setup_ns(ctrl, NVME_NS_DISC_F_ACTIVE, 2, lbaf_4k)) { 438 libnvme_test_ctrl_fatal(ctrl, "failed to create second " 439 "namespace"); 440 } 441 442 if (!format_nsid_io(ctrl, 2, format_io_verify)) 443 ret = EXIT_FAILURE; 444 if (!format_nsid_io(ctrl, 1, format_io_check)) 445 ret = EXIT_FAILURE; 446 447 /* 448 * Now reformat 1 and make sure its data is gone and 2's data is intact. 449 */ 450 if (!format_ns(ctrl, 2, NVME_FRMT_SES_NONE, lbaf_4k)) 451 ret = EXIT_FAILURE; 452 if (!format_nsid_io(ctrl, 1, format_io_check)) 453 ret = EXIT_FAILURE; 454 if (!format_nsid_io(ctrl, 2, format_io_verify)) 455 ret = EXIT_FAILURE; 456 457 /* 458 * Perform a broadcast format back to 512. 459 */ 460 if (!format_ns(ctrl, NVME_NSID_BCAST, NVME_FRMT_SES_NONE, lbaf_512)) 461 ret = EXIT_FAILURE; 462 463 if (!format_nsid_io(ctrl, 1, format_io_verify)) 464 ret = EXIT_FAILURE; 465 if (!format_nsid_io(ctrl, 2, format_io_verify)) 466 ret = EXIT_FAILURE; 467 468 /* 469 * All devices that support format in theory support secure-erase. Check 470 * to see if they support cryptographic secure erase as that should 471 * speed things up due to per-key usage. Secure erase may only work 472 * globally or operate per-namespace. Regardless of this, we assume that 473 * if we're changing the format, that has to be done globally. 474 */ 475 id = nvme_ctrl_info_identify(info); 476 if (id->id_fna.fn_crypt_erase != 0) { 477 ses = NVME_FRMT_SES_CRYPTO; 478 } else { 479 ses = NVME_FRMT_SES_USER; 480 } 481 482 if (!format_ns(ctrl, NVME_NSID_BCAST, ses, lbaf_4k)) 483 ret = EXIT_FAILURE; 484 if (!format_nsid_io(ctrl, 1, format_io_verify)) 485 ret = EXIT_FAILURE; 486 if (!format_nsid_io(ctrl, 2, format_io_verify)) 487 ret = EXIT_FAILURE; 488 489 if (id->id_fna.fn_sec_erase == 0) { 490 if (!format_ns(ctrl, 1, ses, lbaf_4k)) 491 ret = EXIT_FAILURE; 492 if (!format_nsid_io(ctrl, 2, format_io_check)) 493 ret = EXIT_FAILURE; 494 if (!format_nsid_io(ctrl, 1, format_io_verify)) 495 ret = EXIT_FAILURE; 496 497 if (!format_ns(ctrl, 2, ses, lbaf_4k)) 498 ret = EXIT_FAILURE; 499 if (!format_nsid_io(ctrl, 2, format_io_verify)) 500 ret = EXIT_FAILURE; 501 if (!format_nsid_io(ctrl, 1, format_io_check)) 502 ret = EXIT_FAILURE; 503 } 504 505 nvme_ctrl_info_free(info); 506 nvme_ctrl_unlock(ctrl); 507 nvme_ctrl_fini(ctrl); 508 nvme_fini(nvme); 509 510 if (ret == EXIT_SUCCESS) { 511 (void) printf("All tests passed successfully\n"); 512 } 513 514 return (ret); 515 } 516