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 * NVMe Namespace Management Commands 18 */ 19 20 #include <err.h> 21 #include <string.h> 22 #include <sys/sysmacros.h> 23 24 #include "nvmeadm.h" 25 26 /* 27 * Attempt to parse a string with a power of 2 unit suffix into a uint64_t. We 28 * stop allowing suffixes at PiB as we're trying to fit this into a uint64_t and 29 * there aren't really many valid values of EiB. In the future when we have 30 * devices with such large capacities, we should change this to return a 31 * uint128_t style value as it's possible that with a larger block size, that 32 * this will make more sense. When we do that, we should probably also figure 33 * out how we want to commonize this function across the tree. 34 */ 35 static uint64_t 36 nvmeadm_parse_units(const char *str, const char *desc) 37 { 38 unsigned long long l; 39 char *eptr; 40 const char units[] = { 'B', 'K', 'M', 'G', 'T', 'P' }; 41 42 errno = 0; 43 l = strtoull(str, &eptr, 0); 44 if (errno != 0) { 45 err(-1, "failed to parse %s: %s", desc, str); 46 } 47 48 if (*eptr == '\0') { 49 return ((uint64_t)l); 50 } 51 52 if (eptr[1] != '\0') { 53 errx(-1, "failed to parse %s unit suffix: %s", desc, eptr); 54 } 55 56 for (size_t i = 0; i < ARRAY_SIZE(units); i++) { 57 if (strncasecmp(eptr, &units[i], 1) != 0) { 58 continue; 59 } 60 61 for (; i > 0; i--) { 62 const uint64_t max = UINT64_MAX / 1024; 63 64 if (l > max) { 65 errx(-1, "%s value %s would overflow a " 66 "uint64_t", desc, str); 67 } 68 69 l *= 1024; 70 } 71 72 return ((uint64_t)l); 73 } 74 75 errx(-1, "invalid %s unit suffix: %s", desc, eptr); 76 } 77 78 /* 79 * Today create-namespace takes a limited number of arguments. Here is how we 80 * expect it to continue to change over time: 81 * 82 * 1) First, we have a limited number of short options that we support. If we 83 * ever support long options, these should match the NVMe name for the option, 84 * e.g. --nsze, --ncap, --nmic, etc. 85 * 86 * 2) Today we require that this operates only when the namespace is already 87 * detached from a controller. If we want to change this behavior then we should 88 * add something such as a [-R] flag to indicate that it should take all the 89 * other steps necessary recursively. 90 * 91 * 3) Most other options have a default that indicates that they're unused or 92 * similar. This allows us to add additional option arguments. Some of these may 93 * end up with aliases for the default case, e.g. -t nvm for the default NVM 94 * CSI. 95 * 96 * 4) We only support specifying the size of a namespace in bytes today. If we 97 * want to change this we should add a flag like a -B that specifies that all 98 * sizes are in units of the logical block. 99 */ 100 void 101 usage_create_ns(const char *c_name) 102 { 103 (void) fprintf(stderr, "%s -f flbas | -b block-size [-c cap] " 104 "[-n nmic]\n\t [-t type] <ctl> <size>\n\n" 105 " Create a new namespace on the specified controller of the " 106 "requested size. The\n size is specified in bytes and may use " 107 "any suffix such as B (bytes), K\n (kibibytes, 2^10), M " 108 "(mibibytes, 2^20), G (gibibytes, 2^30), T (tebibytes,\n 2^40), " 109 "etc. The size must be a multiple of the selected block size. The\n" 110 " controller may impose additional alignment constraints.\n", 111 c_name); 112 } 113 114 void 115 optparse_create_ns(nvme_process_arg_t *npa) 116 { 117 int c; 118 nvmeadm_create_ns_t *ncn; 119 const char *nmic = NULL, *type = NULL, *cap = NULL; 120 const char *bs = NULL, *flbas = NULL; 121 122 if ((ncn = calloc(1, sizeof (nvmeadm_create_ns_t))) == NULL) { 123 err(-1, "failed to allocate memory to track create-namespace " 124 "information"); 125 } 126 127 npa->npa_cmd_arg = ncn; 128 129 while ((c = getopt(npa->npa_argc, npa->npa_argv, ":b:c:f:n:t:")) != 130 -1) { 131 switch (c) { 132 case 'b': 133 bs = optarg; 134 break; 135 case 'c': 136 cap = optarg; 137 break; 138 case 'f': 139 flbas = optarg; 140 break; 141 case 'n': 142 nmic = optarg; 143 break; 144 case 't': 145 type = optarg; 146 break; 147 case '?': 148 errx(-1, "unknown option: -%c", optopt); 149 case ':': 150 errx(-1, "option -%c requires an argument", optopt); 151 } 152 } 153 154 if (flbas != NULL && bs != NULL) { 155 errx(-1, "only one of -b and -f may be specified"); 156 } 157 158 if (flbas == NULL && bs == NULL) { 159 errx(-1, "at least one of -b and -f must be specified"); 160 } 161 162 if (flbas != NULL) { 163 const char *err; 164 ncn->ncn_use_flbas = B_TRUE; 165 ncn->ncn_lba = strtonumx(flbas, 0, NVME_MAX_LBAF - 1, &err, 0); 166 if (err != NULL) { 167 errx(-1, "failed to parse formatted LBA index: %s is " 168 "%s, valid values are between 0 and %u", 169 flbas, err, NVME_MAX_LBAF - 1); 170 } 171 } 172 173 if (bs != NULL) { 174 ncn->ncn_use_flbas = B_FALSE; 175 ncn->ncn_lba = nvmeadm_parse_units(bs, "block-size"); 176 } 177 178 if (cap != NULL) { 179 ncn->ncn_cap = nvmeadm_parse_units(cap, "block-size"); 180 } else { 181 ncn->ncn_cap = UINT64_MAX; 182 } 183 184 if (type != NULL) { 185 if (strcasecmp(type, "nvm") == 0) { 186 ncn->ncn_csi = NVME_CSI_NVM; 187 } else if (strcasecmp(type, "kv") == 0) { 188 ncn->ncn_csi = NVME_CSI_KV; 189 } else if (strcasecmp(type, "zns") == 0) { 190 ncn->ncn_csi = NVME_CSI_ZNS; 191 } else { 192 errx(-1, "unknown CSI type string: '%s'; valid values " 193 "are 'nvm', 'kv', and 'zns'", type); 194 } 195 } else { 196 ncn->ncn_csi = NVME_CSI_NVM; 197 } 198 199 if (nmic != NULL) { 200 if (strcasecmp(nmic, "none") == 0) { 201 ncn->ncn_nmic = NVME_NS_NMIC_T_NONE; 202 } else if (strcasecmp(nmic, "shared") == 0) { 203 ncn->ncn_nmic = NVME_NS_NMIC_T_SHARED; 204 } else { 205 errx(-1, "unknown nmic string: '%s'; valid values are " 206 "'none' and 'shared'", nmic); 207 } 208 } else { 209 ncn->ncn_nmic = NVME_NS_NMIC_T_NONE; 210 } 211 212 if (npa->npa_argc - optind > 2) { 213 errx(-1, "%s passed extraneous arguments starting with %s", 214 npa->npa_cmd->c_name, npa->npa_argv[optind + 2]); 215 } else if (npa->npa_argc - optind != 2) { 216 errx(-1, "missing required size parameter"); 217 } 218 219 ncn->ncn_size = nvmeadm_parse_units(npa->npa_argv[optind + 1], 220 "namespace size"); 221 if (cap == NULL) { 222 ncn->ncn_cap = ncn->ncn_size; 223 } 224 } 225 226 static const nvme_nvm_lba_fmt_t * 227 do_create_ns_find_lba(const nvme_process_arg_t *npa, 228 const nvmeadm_create_ns_t *ncn) 229 { 230 const uint32_t nfmts = nvme_ctrl_info_nformats(npa->npa_ctrl_info); 231 const nvme_nvm_lba_fmt_t *best = NULL; 232 uint32_t best_rp = UINT32_MAX; 233 234 for (size_t i = 0; i < nfmts; i++) { 235 const nvme_nvm_lba_fmt_t *fmt; 236 uint32_t rp; 237 238 if (!nvme_ctrl_info_format(npa->npa_ctrl_info, i, &fmt)) { 239 continue; 240 } 241 242 if (nvme_nvm_lba_fmt_meta_size(fmt) != 0) 243 continue; 244 245 if (nvme_nvm_lba_fmt_data_size(fmt) != ncn->ncn_lba) 246 continue; 247 248 rp = nvme_nvm_lba_fmt_rel_perf(fmt); 249 if (rp < best_rp) { 250 best_rp = rp; 251 best = fmt; 252 } 253 } 254 255 if (best == NULL) { 256 errx(-1, "failed to find an LBA format with %u byte block size", 257 ncn->ncn_lba); 258 } 259 260 return (best); 261 } 262 263 int 264 do_create_ns(const nvme_process_arg_t *npa) 265 { 266 const nvmeadm_create_ns_t *ncn = npa->npa_cmd_arg; 267 nvme_ns_create_req_t *req; 268 const nvme_nvm_lba_fmt_t *lba; 269 uint32_t nsid, flbas, ds; 270 uint64_t size; 271 272 if (npa->npa_ns != NULL) { 273 errx(-1, "%s cannot be used on namespaces", 274 npa->npa_cmd->c_name); 275 } 276 277 /* 278 * This should have been checked above. 279 */ 280 if (npa->npa_argc > 1) { 281 errx(-1, "%s passed extraneous arguments starting with %s", 282 npa->npa_cmd->c_name, npa->npa_argv[1]); 283 } 284 285 /* 286 * If we were given a block size rather than the formatted LBA size, go 287 * deal with converting that now. 288 */ 289 if (!ncn->ncn_use_flbas) { 290 lba = do_create_ns_find_lba(npa, ncn); 291 } else { 292 if (!nvme_ctrl_info_format(npa->npa_ctrl_info, ncn->ncn_lba, 293 &lba)) { 294 nvmeadm_fatal(npa, "failed to look up LBA format index " 295 "%u", ncn->ncn_lba); 296 } 297 } 298 299 if (!nvme_ns_create_req_init_by_csi(npa->npa_ctrl, ncn->ncn_csi, 300 &req)) { 301 nvmeadm_fatal(npa, "failed to initialize namespace create " 302 "request"); 303 } 304 305 ds = nvme_nvm_lba_fmt_data_size(lba); 306 flbas = nvme_nvm_lba_fmt_id(lba); 307 if (!nvme_ns_create_req_set_flbas(req, flbas)) { 308 nvmeadm_fatal(npa, "failed to set namespace create request " 309 "formatted LBA index to %u", flbas); 310 } 311 312 if (ncn->ncn_size % ds != 0) { 313 nvmeadm_fatal(npa, "requested namespace size 0x%lx is not a " 314 "multiple of the requested LBA block size (0x%x)", 315 ncn->ncn_size, ds); 316 } 317 size = ncn->ncn_size / ds; 318 if (!nvme_ns_create_req_set_nsze(req, size)) { 319 nvmeadm_fatal(npa, "failed to set namespace create request " 320 "namespace size to 0x%lx", size); 321 } 322 323 if (ncn->ncn_cap % ds != 0) { 324 nvmeadm_fatal(npa, "requested namespace capacity 0x%lx is not " 325 "a multiple of the requested LBA block size (0x%x)", 326 ncn->ncn_cap, ds); 327 } 328 size = ncn->ncn_cap/ ds; 329 if (!nvme_ns_create_req_set_ncap(req, size)) { 330 nvmeadm_fatal(npa, "failed to set namespace create request " 331 "namespace capacity to 0x%lx", size); 332 } 333 334 if (!nvme_ns_create_req_set_nmic(req, ncn->ncn_nmic)) { 335 nvmeadm_fatal(npa, "failed to set namespace multipath I/O and " 336 "sharing capabilities to 0x%x", ncn->ncn_nmic); 337 } 338 339 if (!nvme_ns_create_req_exec(req)) { 340 nvmeadm_fatal(npa, "failed to execute namespace create " 341 "request"); 342 } 343 344 if (!nvme_ns_create_req_get_nsid(req, &nsid)) { 345 nvmeadm_fatal(npa, "Failed to retrieve the new namespace ID"); 346 } 347 348 nvme_ns_create_req_fini(req); 349 350 (void) printf("created namespace %s/%u\n", npa->npa_ctrl_name, nsid); 351 return (EXIT_SUCCESS); 352 } 353 354 void 355 usage_delete_ns(const char *c_name) 356 { 357 (void) fprintf(stderr, "%s <ctl>/<ns>\n\n" 358 " Delete the specified namespace. It must be first detached from " 359 "all\n controllers. Controllers can be detached from a namespace " 360 "with the\n detach-namespace sub-command.\n", c_name); 361 } 362 363 int 364 do_delete_ns(const nvme_process_arg_t *npa) 365 { 366 nvme_ns_delete_req_t *req; 367 368 if (npa->npa_ns == NULL) { 369 errx(-1, "%s cannot be used on controllers", 370 npa->npa_cmd->c_name); 371 } 372 373 if (npa->npa_argc > 0) { 374 errx(-1, "%s passed extraneous arguments starting with %s", 375 npa->npa_cmd->c_name, npa->npa_argv[0]); 376 } 377 378 if (!nvme_ns_delete_req_init(npa->npa_ctrl, &req)) { 379 nvmeadm_fatal(npa, "failed to initialize namespace delete " 380 "request"); 381 } 382 383 const uint32_t nsid = nvme_ns_info_nsid(npa->npa_ns_info); 384 if (!nvme_ns_delete_req_set_nsid(req, nsid)) { 385 nvmeadm_fatal(npa, "failed to set namespace delete request " 386 "namespace ID to 0x%x", nsid); 387 } 388 389 if (!nvme_ns_delete_req_exec(req)) { 390 nvmeadm_fatal(npa, "failed to execute namespace delete " 391 "request"); 392 } 393 394 nvme_ns_delete_req_fini(req); 395 return (EXIT_SUCCESS); 396 } 397 398 /* 399 * Currently both attach namespace and detach namespace only will perform an 400 * attach or detach of the namespace from the current controller in the system. 401 * In the future, we should probably support an argument to provide an explicit 402 * controller list either in the form of IDs or device names, probably with -c 403 * or -C. 404 */ 405 void 406 usage_attach_ns(const char *c_name) 407 { 408 (void) fprintf(stderr, "%s <ctl>/<ns>\n\n" 409 " Attach the specified namespace to the current controller.\n", 410 c_name); 411 } 412 413 void 414 usage_detach_ns(const char *c_name) 415 { 416 (void) fprintf(stderr, "%s <ctl>/<ns>\n\n" 417 " Detach the specified namespace from its current controller. The " 418 "namespace\n must have its blkdev instances detached with the " 419 "detach sub-command.\n", c_name); 420 } 421 422 static int 423 do_attach_ns_common(const nvme_process_arg_t *npa, uint32_t sel) 424 { 425 const char *desc = sel == NVME_NS_ATTACH_CTRL_ATTACH ? "attach" : 426 "detach"; 427 nvme_ns_attach_req_t *req; 428 429 if (npa->npa_ns == NULL) { 430 errx(-1, "%s cannot be used on controllers", 431 npa->npa_cmd->c_name); 432 } 433 434 if (npa->npa_argc > 0) { 435 errx(-1, "%s passed extraneous arguments starting with %s", 436 npa->npa_cmd->c_name, npa->npa_argv[0]); 437 } 438 439 if (!nvme_ns_attach_req_init_by_sel(npa->npa_ctrl, sel, &req)) { 440 nvmeadm_fatal(npa, "failed to initialize controller " 441 "%s request for %s", desc, npa->npa_name); 442 } 443 444 const uint32_t nsid = nvme_ns_info_nsid(npa->npa_ns_info); 445 if (!nvme_ns_attach_req_set_nsid(req, nsid)) { 446 nvmeadm_fatal(npa, "failed to set namespace to %s to %u", 447 desc, nsid); 448 } 449 450 if (!nvme_ns_attach_req_set_ctrlid_self(req)) { 451 nvmeadm_fatal(npa, "failed to set controller to %s for %s", 452 desc, npa->npa_name); 453 } 454 455 if (!nvme_ns_attach_req_exec(req)) { 456 nvmeadm_fatal(npa, "failed to execute controller %s request", 457 desc); 458 } 459 460 nvme_ns_attach_req_fini(req); 461 return (EXIT_SUCCESS); 462 } 463 464 int 465 do_attach_ns(const nvme_process_arg_t *npa) 466 { 467 return (do_attach_ns_common(npa, NVME_NS_ATTACH_CTRL_ATTACH)); 468 } 469 470 int 471 do_detach_ns(const nvme_process_arg_t *npa) 472 { 473 return (do_attach_ns_common(npa, NVME_NS_ATTACH_CTRL_DETACH)); 474 } 475