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 2020 Joyent, Inc. 14 */ 15 16 /* 17 * This file drives topo node enumeration of NVMe controllers. A single "nvme" 18 * node is enumerated for each NVMe controller. Child "disk" nodes are then 19 * enumerated for each configured NVMe namespace. 20 * 21 * nvme nodes are expected to be enumerated under either a "bay" node (for U.2 22 * devices) or a "slot" node (for M.2 devices) or a "pciexfn" node (for AIC 23 * devices). 24 * 25 * Enumeration of NVMe controllers on PCIe add-in cards is automatically driven 26 * by the pcibus topo module. 27 * 28 * In order to allow for associating a given NVMe controller with a physical 29 * location, enumeration of U.2 and M.2 devices should be driven by a 30 * platform-specific topo map which statically sets the following two 31 * properties on the parent "bay" or "slot" node: 32 * 33 * propgroup property description 34 * --------- -------- ------------ 35 * binding driver "nvme" 36 * binding parent-device devpath of parent PCIe device 37 * 38 * for example: 39 * 40 * <propgroup name="binding" version="1" name-stability="Private" 41 * data-stability="Private"> 42 * <propval name="driver" type="string" value="nvme"/> 43 * <propval name="parent-device" type="string" 44 * value="/pci@0,0/pci8086,6f09@3,1"/> 45 * </propgroup> 46 * <dependents grouping="children"> 47 * <range name="nvme" min="0" max="0"> 48 * <enum-method name="disk" version="1"/> 49 * </range> 50 * </dependents> 51 */ 52 #include <stdlib.h> 53 #include <sys/types.h> 54 #include <sys/stat.h> 55 #include <fcntl.h> 56 #include <unistd.h> 57 #include <string.h> 58 #include <strings.h> 59 60 #include <sys/fm/protocol.h> 61 #include <fm/topo_hc.h> 62 #include <fm/topo_mod.h> 63 64 #include <sys/dkio.h> 65 #include <sys/scsi/generic/inquiry.h> 66 67 #include <sys/nvme.h> 68 #include "disk.h" 69 #include "disk_drivers.h" 70 71 typedef struct nvme_enum_info { 72 topo_mod_t *nei_mod; 73 di_node_t nei_dinode; 74 nvme_identify_ctrl_t *nei_idctl; 75 nvme_version_t nei_vers; 76 tnode_t *nei_parent; 77 tnode_t *nei_nvme; 78 nvlist_t *nei_nvme_fmri; 79 const char *nei_nvme_path; 80 int nei_fd; 81 } nvme_enum_info_t; 82 83 typedef struct devlink_arg { 84 topo_mod_t *dla_mod; 85 char *dla_logical_disk; 86 uint_t dla_strsz; 87 } devlink_arg_t; 88 89 static int 90 devlink_cb(di_devlink_t dl, void *arg) 91 { 92 devlink_arg_t *dlarg = (devlink_arg_t *)arg; 93 topo_mod_t *mod = dlarg->dla_mod; 94 const char *devpath; 95 char *slice, *ctds; 96 97 if ((devpath = di_devlink_path(dl)) == NULL || 98 (dlarg->dla_logical_disk = topo_mod_strdup(mod, devpath)) == 99 NULL) { 100 return (DI_WALK_TERMINATE); 101 } 102 103 /* 104 * We need to keep track of the original string size before we 105 * truncate it with a NUL, so that we can free the right number of 106 * bytes when we're done, otherwise libumem will complain. 107 */ 108 dlarg->dla_strsz = strlen(dlarg->dla_logical_disk) + 1; 109 110 /* trim the slice off the public name */ 111 if (((ctds = strrchr(dlarg->dla_logical_disk, '/')) != NULL) && 112 ((slice = strchr(ctds, 's')) != NULL)) 113 *slice = '\0'; 114 115 return (DI_WALK_TERMINATE); 116 } 117 118 static char * 119 get_logical_disk(topo_mod_t *mod, const char *devpath, uint_t *bufsz) 120 { 121 di_devlink_handle_t devhdl; 122 devlink_arg_t dlarg = { 0 }; 123 char *minorpath = NULL; 124 125 if (asprintf(&minorpath, "%s:a", devpath) < 0) { 126 return (NULL); 127 } 128 129 if ((devhdl = di_devlink_init(NULL, 0)) == DI_NODE_NIL) { 130 topo_mod_dprintf(mod, "%s: di_devlink_init failed", __func__); 131 free(minorpath); 132 return (NULL); 133 } 134 135 dlarg.dla_mod = mod; 136 137 (void) di_devlink_walk(devhdl, "^dsk/", minorpath, DI_PRIMARY_LINK, 138 &dlarg, devlink_cb); 139 140 (void) di_devlink_fini(&devhdl); 141 free(minorpath); 142 143 *bufsz = dlarg.dla_strsz; 144 return (dlarg.dla_logical_disk); 145 } 146 147 static int 148 make_disk_node(nvme_enum_info_t *nvme_info, di_node_t dinode, 149 topo_instance_t inst) 150 { 151 topo_mod_t *mod = nvme_info->nei_mod; 152 nvlist_t *auth = NULL, *fmri = NULL; 153 tnode_t *disk; 154 char *rev = NULL, *model = NULL, *serial = NULL, *path; 155 char *logical_disk = NULL, *devid, *manuf, *ctd = NULL; 156 char *cap_bytes_str = NULL, full_path[MAXPATHLEN + 1]; 157 char *pname = topo_node_name(nvme_info->nei_parent); 158 topo_instance_t pinst = topo_node_instance(nvme_info->nei_parent); 159 const char **ppaths = NULL; 160 struct dk_minfo minfo; 161 uint64_t cap_bytes; 162 uint_t bufsz; 163 int fd = -1, err, ret = -1, r; 164 165 if ((path = di_devfs_path(dinode)) == NULL) { 166 topo_mod_dprintf(mod, "%s: failed to get dev path", __func__); 167 (void) topo_mod_seterrno(mod, EMOD_UNKNOWN); 168 return (ret); 169 } 170 171 topo_mod_dprintf(mod, "%s: found nvme namespace: %s", __func__, path); 172 173 /* 174 * Issue the DKIOCGMEDIAINFO ioctl to get the capacity 175 */ 176 (void) snprintf(full_path, MAXPATHLEN, "/devices%s%s", path, 177 PHYS_EXTN); 178 if ((fd = open(full_path, O_RDWR)) < 0 || 179 ioctl(fd, DKIOCGMEDIAINFO, &minfo) < 0) { 180 topo_mod_dprintf(mod, "failed to get blkdev capacity (%s)", 181 strerror(errno)); 182 (void) topo_mod_seterrno(mod, EMOD_UNKNOWN); 183 goto error; 184 } 185 186 cap_bytes = minfo.dki_lbsize * minfo.dki_capacity; 187 188 if (asprintf(&cap_bytes_str, "%" PRIu64, cap_bytes) < 0) { 189 topo_mod_dprintf(mod, "%s: failed to alloc string", __func__); 190 (void) topo_mod_seterrno(mod, EMOD_NOMEM); 191 goto error; 192 } 193 194 /* 195 * Gather the FRU identity information from the devinfo properties 196 */ 197 if (di_prop_lookup_strings(DDI_DEV_T_ANY, dinode, DEVID_PROP_NAME, 198 &devid) == -1 || 199 di_prop_lookup_strings(DDI_DEV_T_ANY, dinode, INQUIRY_VENDOR_ID, 200 &manuf) == -1 || 201 di_prop_lookup_strings(DDI_DEV_T_ANY, dinode, INQUIRY_PRODUCT_ID, 202 &model) == -1 || 203 di_prop_lookup_strings(DDI_DEV_T_ANY, dinode, INQUIRY_REVISION_ID, 204 &rev) == -1 || 205 di_prop_lookup_strings(DDI_DEV_T_ANY, dinode, INQUIRY_SERIAL_NO, 206 &serial) == -1) { 207 topo_mod_dprintf(mod, "%s: failed to lookup devinfo props on " 208 "%s", __func__, path); 209 (void) topo_mod_seterrno(mod, EMOD_UNKNOWN); 210 goto error; 211 } 212 213 model = topo_mod_clean_str(mod, model); 214 rev = topo_mod_clean_str(mod, rev); 215 serial = topo_mod_clean_str(mod, serial); 216 217 /* 218 * Lookup the /dev/dsk/c#t#d# disk device name from the blkdev path 219 */ 220 if ((logical_disk = get_logical_disk(mod, path, &bufsz)) == NULL) { 221 topo_mod_dprintf(mod, "failed to find logical disk"); 222 (void) topo_mod_seterrno(mod, EMOD_UNKNOWN); 223 goto error; 224 } 225 226 /* 227 * If we were able to look up the logical disk path for this namespace 228 * then set ctd to be that pathname, minus the "/dev/dsk/" portion. 229 */ 230 if ((ctd = strrchr(logical_disk, '/')) != NULL) { 231 ctd = ctd + 1; 232 } else { 233 topo_mod_dprintf(mod, "malformed logical disk path: %s", 234 logical_disk); 235 (void) topo_mod_seterrno(mod, EMOD_UNKNOWN); 236 goto error; 237 } 238 239 /* 240 * Build the FMRI and then bind the disk node to the parent nvme node. 241 */ 242 auth = topo_mod_auth(mod, nvme_info->nei_nvme); 243 fmri = topo_mod_hcfmri(mod, nvme_info->nei_nvme, FM_HC_SCHEME_VERSION, 244 DISK, inst, NULL, auth, model, rev, serial); 245 246 if (fmri == NULL) { 247 /* errno set */ 248 topo_mod_dprintf(mod, "%s: hcfmri failed for %s=%u/%s=0/%s=%u", 249 __func__, pname, pinst, NVME, DISK, inst); 250 goto error; 251 } 252 if ((disk = topo_node_bind(mod, nvme_info->nei_nvme, DISK, inst, 253 fmri)) == NULL) { 254 /* errno set */ 255 topo_mod_dprintf(mod, "%s: bind failed for %s=%u/%s=0/%s=%u", 256 __func__, pname, pinst, NVME, DISK, inst); 257 goto error; 258 } 259 260 /* Create authority and system propgroups */ 261 topo_pgroup_hcset(disk, auth); 262 263 /* 264 * As the "disk" in this case is simply a logical construct 265 * representing an NVMe namespace, we inherit the FRU from the parent 266 * node. 267 */ 268 if (topo_node_fru_set(disk, NULL, 0, &err) != 0) { 269 topo_mod_dprintf(mod, "%s: failed to set FRU: %s", __func__, 270 topo_strerror(err)); 271 (void) topo_mod_seterrno(mod, err); 272 goto error; 273 } 274 275 if ((ppaths = topo_mod_zalloc(mod, sizeof (char *))) == NULL) { 276 (void) topo_mod_seterrno(mod, EMOD_NOMEM); 277 goto error; 278 } 279 ppaths[0] = path; 280 281 /* 282 * Create the "storage" and "io" property groups and then fill them 283 * with the standard set of properties for "disk" nodes. 284 */ 285 if (topo_pgroup_create(disk, &io_pgroup, &err) != 0 || 286 topo_pgroup_create(disk, &storage_pgroup, &err) != 0) { 287 topo_mod_dprintf(mod, "%s: failed to create propgroups: %s", 288 __func__, topo_strerror(err)); 289 (void) topo_mod_seterrno(mod, err); 290 goto error; 291 } 292 293 r = topo_prop_set_string(disk, TOPO_PGROUP_IO, TOPO_IO_DEV_PATH, 294 TOPO_PROP_IMMUTABLE, path, &err); 295 296 r += topo_prop_set_string_array(disk, TOPO_PGROUP_IO, 297 TOPO_IO_PHYS_PATH, TOPO_PROP_IMMUTABLE, ppaths, 1, &err); 298 299 r += topo_prop_set_string(disk, TOPO_PGROUP_IO, TOPO_IO_DEVID, 300 TOPO_PROP_IMMUTABLE, devid, &err); 301 302 r += topo_prop_set_string(disk, TOPO_PGROUP_STORAGE, 303 TOPO_STORAGE_MANUFACTURER, TOPO_PROP_IMMUTABLE, manuf, &err); 304 305 r += topo_prop_set_string(disk, TOPO_PGROUP_STORAGE, 306 TOPO_STORAGE_CAPACITY, TOPO_PROP_IMMUTABLE, cap_bytes_str, 307 &err); 308 309 r += topo_prop_set_string(disk, TOPO_PGROUP_STORAGE, 310 TOPO_STORAGE_SERIAL_NUM, TOPO_PROP_IMMUTABLE, serial, &err); 311 312 r += topo_prop_set_string(disk, TOPO_PGROUP_STORAGE, 313 TOPO_STORAGE_MODEL, TOPO_PROP_IMMUTABLE, model, &err); 314 315 r += topo_prop_set_string(disk, TOPO_PGROUP_STORAGE, 316 TOPO_STORAGE_FIRMWARE_REV, TOPO_PROP_IMMUTABLE, rev, &err); 317 318 r += topo_prop_set_string(disk, TOPO_PGROUP_STORAGE, 319 TOPO_STORAGE_LOGICAL_DISK_NAME, TOPO_PROP_IMMUTABLE, ctd, &err); 320 321 if (r != 0) { 322 topo_mod_dprintf(mod, "%s: failed to create properties: %s", 323 __func__, topo_strerror(err)); 324 (void) topo_mod_seterrno(mod, EMOD_UNKNOWN); 325 goto error; 326 } 327 328 ret = 0; 329 330 error: 331 free(cap_bytes_str); 332 if (fd > 0) 333 (void) close(fd); 334 if (ppaths != NULL) 335 topo_mod_free(mod, ppaths, sizeof (char *)); 336 di_devfs_path_free(path); 337 nvlist_free(auth); 338 nvlist_free(fmri); 339 topo_mod_strfree(mod, rev); 340 topo_mod_strfree(mod, model); 341 topo_mod_strfree(mod, serial); 342 topo_mod_free(mod, logical_disk, bufsz); 343 return (ret); 344 } 345 346 static const topo_pgroup_info_t nvme_pgroup = { 347 TOPO_PGROUP_NVME, 348 TOPO_STABILITY_PRIVATE, 349 TOPO_STABILITY_PRIVATE, 350 1 351 }; 352 353 354 static int 355 make_nvme_node(nvme_enum_info_t *nvme_info) 356 { 357 topo_mod_t *mod = nvme_info->nei_mod; 358 nvlist_t *auth = NULL, *fmri = NULL, *fru; 359 tnode_t *nvme; 360 char raw_rev[NVME_FWVER_SZ + 1], raw_model[NVME_MODEL_SZ + 1]; 361 char raw_serial[NVME_SERIAL_SZ + 1]; 362 char *rev = NULL, *model = NULL, *serial = NULL, *vers = NULL; 363 char *pname = topo_node_name(nvme_info->nei_parent); 364 char *label = NULL; 365 topo_instance_t pinst = topo_node_instance(nvme_info->nei_parent); 366 int err = 0, ret = -1; 367 di_node_t cn; 368 uint_t i; 369 370 /* 371 * The raw strings returned by the IDENTIFY CONTROLLER command are 372 * not NUL-terminated, so we fix that up. 373 */ 374 (void) strncpy(raw_rev, nvme_info->nei_idctl->id_fwrev, NVME_FWVER_SZ); 375 raw_rev[NVME_FWVER_SZ] = '\0'; 376 (void) strncpy(raw_model, nvme_info->nei_idctl->id_model, 377 NVME_MODEL_SZ); 378 raw_model[NVME_MODEL_SZ] = '\0'; 379 (void) strncpy(raw_serial, nvme_info->nei_idctl->id_serial, 380 NVME_SERIAL_SZ); 381 raw_serial[NVME_SERIAL_SZ] = '\0'; 382 383 /* 384 * Next we pass the strings through a function that sanitizes them of 385 * any characters that can't be used in an FMRI string. 386 */ 387 rev = topo_mod_clean_str(mod, raw_rev); 388 model = topo_mod_clean_str(mod, raw_model); 389 serial = topo_mod_clean_str(mod, raw_serial); 390 391 auth = topo_mod_auth(mod, nvme_info->nei_parent); 392 fmri = topo_mod_hcfmri(mod, nvme_info->nei_parent, FM_HC_SCHEME_VERSION, 393 NVME, 0, NULL, auth, model, rev, serial); 394 395 if (fmri == NULL) { 396 /* errno set */ 397 topo_mod_dprintf(mod, "%s: hcfmri failed for %s=%u/%s=0", 398 __func__, pname, pinst, NVME); 399 goto error; 400 } 401 402 /* 403 * If our parent is a pciexfn node, then we need to create a nvme range 404 * underneath it to hold the nvme heirarchy. For other cases, where 405 * enumeration is being driven by a topo map file, this range will have 406 * already been statically defined in the XML. 407 */ 408 if (strcmp(pname, PCIEX_FUNCTION) == 0) { 409 if (topo_node_range_create(mod, nvme_info->nei_parent, NVME, 0, 410 0) < 0) { 411 /* errno set */ 412 topo_mod_dprintf(mod, "%s: error creating %s range", 413 __func__, NVME); 414 goto error; 415 } 416 } 417 418 /* 419 * Create a new topo node to represent the NVMe controller and bind it 420 * to the parent node. 421 */ 422 if ((nvme = topo_node_bind(mod, nvme_info->nei_parent, NVME, 0, 423 fmri)) == NULL) { 424 /* errno set */ 425 topo_mod_dprintf(mod, "%s: bind failed for %s=%u/%s=0", 426 __func__, pname, pinst, NVME); 427 goto error; 428 } 429 nvme_info->nei_nvme = nvme; 430 nvme_info->nei_nvme_fmri = fmri; 431 432 /* 433 * If our parent node is a "pciexfn" node then this is a NVMe device on 434 * a PCIe AIC, so we inherit our parent's FRU. Otherwise, we set the 435 * FRU to ourself. 436 */ 437 if (strcmp(topo_node_name(nvme_info->nei_parent), PCIEX_FUNCTION) == 0) 438 fru = NULL; 439 else 440 fru = fmri; 441 442 if (topo_node_fru_set(nvme, fru, 0, &err) != 0) { 443 topo_mod_dprintf(mod, "%s: failed to set FRU: %s", __func__, 444 topo_strerror(err)); 445 (void) topo_mod_seterrno(mod, err); 446 goto error; 447 } 448 449 /* 450 * Clone the label from our parent node. We can't inherit the property 451 * because the label prop is mutable on bay nodes and only immutable 452 * properties can be inherited. 453 */ 454 if ((topo_node_label(nvme_info->nei_parent, &label, &err) != 0 && 455 err != ETOPO_PROP_NOENT) || 456 topo_node_label_set(nvme, label, &err) != 0) { 457 topo_mod_dprintf(mod, "%s: failed to set label: %s", 458 __func__, topo_strerror(err)); 459 (void) topo_mod_seterrno(mod, err); 460 goto error; 461 } 462 463 if (topo_pgroup_create(nvme, &nvme_pgroup, &err) != 0) { 464 topo_mod_dprintf(mod, "%s: failed to create %s pgroup: %s", 465 __func__, TOPO_PGROUP_NVME, topo_strerror(err)); 466 (void) topo_mod_seterrno(mod, err); 467 goto error; 468 } 469 470 if (asprintf(&vers, "%u.%u", nvme_info->nei_vers.v_major, 471 nvme_info->nei_vers.v_minor) < 0) { 472 topo_mod_dprintf(mod, "%s: failed to alloc string", __func__); 473 (void) topo_mod_seterrno(mod, EMOD_NOMEM); 474 goto error; 475 } 476 if (topo_prop_set_string(nvme, TOPO_PGROUP_NVME, TOPO_PROP_NVME_VER, 477 TOPO_PROP_IMMUTABLE, vers, &err) != 0) { 478 topo_mod_dprintf(mod, "%s: failed to set %s/%s property", 479 __func__, TOPO_PGROUP_NVME, TOPO_PROP_NVME_VER); 480 (void) topo_mod_seterrno(mod, err); 481 goto error; 482 } 483 484 if (topo_pgroup_create(nvme, &io_pgroup, &err) != 0) { 485 topo_mod_dprintf(mod, "%s: failed to create %s pgroup: %s", 486 __func__, TOPO_PGROUP_IO, topo_strerror(err)); 487 (void) topo_mod_seterrno(mod, err); 488 goto error; 489 } 490 if (topo_prop_set_string(nvme, TOPO_PGROUP_IO, TOPO_IO_DEV_PATH, 491 TOPO_PROP_IMMUTABLE, nvme_info->nei_nvme_path, &err) != 0) { 492 topo_mod_dprintf(mod, "%s: failed to set %s/%s property", 493 __func__, TOPO_PGROUP_IO, TOPO_IO_DEV_PATH); 494 (void) topo_mod_seterrno(mod, err); 495 goto error; 496 } 497 498 /* 499 * Create a child disk node for each namespace. 500 */ 501 if (topo_node_range_create(mod, nvme, DISK, 0, 502 (nvme_info->nei_idctl->id_nn - 1)) < 0) { 503 /* errno set */ 504 topo_mod_dprintf(mod, "%s: error creating %s range", __func__, 505 DISK); 506 goto error; 507 } 508 509 for (i = 0, cn = di_child_node(nvme_info->nei_dinode); 510 cn != DI_NODE_NIL; 511 i++, cn = di_sibling_node(cn)) { 512 513 if (make_disk_node(nvme_info, cn, i) != 0) { 514 char *path = di_devfs_path(cn); 515 /* 516 * We note the failure, but attempt to forge ahead and 517 * enumerate any other namespaces. 518 */ 519 topo_mod_dprintf(mod, "%s: make_disk_node() failed " 520 "for %s\n", __func__, 521 path ? path : "unknown path"); 522 di_devfs_path_free(path); 523 } 524 } 525 ret = 0; 526 527 error: 528 free(vers); 529 nvlist_free(auth); 530 nvlist_free(fmri); 531 topo_mod_strfree(mod, rev); 532 topo_mod_strfree(mod, model); 533 topo_mod_strfree(mod, serial); 534 topo_mod_strfree(mod, label); 535 return (ret); 536 } 537 538 struct diwalk_arg { 539 topo_mod_t *diwk_mod; 540 tnode_t *diwk_parent; 541 }; 542 543 /* 544 * This function gathers identity information from the NVMe controller and 545 * stores it in a struct. This struct is passed to make_nvme_node(), which 546 * does the actual topo node creation. 547 */ 548 static int 549 discover_nvme_ctl(di_node_t node, di_minor_t minor, void *arg) 550 { 551 struct diwalk_arg *wkarg = arg; 552 topo_mod_t *mod = wkarg->diwk_mod; 553 char *path = NULL, *devctl = NULL; 554 nvme_ioctl_t nioc = { 0 }; 555 nvme_identify_ctrl_t *idctl = NULL; 556 nvme_enum_info_t nvme_info = { 0 }; 557 int fd = -1, ret = DI_WALK_TERMINATE; 558 559 if ((path = di_devfs_minor_path(minor)) == NULL) { 560 topo_mod_dprintf(mod, "failed to get minor path"); 561 (void) topo_mod_seterrno(mod, EMOD_UNKNOWN); 562 return (ret); 563 } 564 565 topo_mod_dprintf(mod, "%s=%u: found nvme controller: %s", 566 topo_node_name(wkarg->diwk_parent), 567 topo_node_instance(wkarg->diwk_parent), path); 568 569 if (asprintf(&devctl, "/devices%s", path) < 0) { 570 topo_mod_dprintf(mod, "failed to alloc string"); 571 (void) topo_mod_seterrno(mod, EMOD_NOMEM); 572 goto error; 573 } 574 575 if ((fd = open(devctl, O_RDWR)) < 0) { 576 topo_mod_dprintf(mod, "failed to open %s", devctl); 577 (void) topo_mod_seterrno(mod, EMOD_UNKNOWN); 578 goto error; 579 } 580 if ((idctl = topo_mod_zalloc(mod, NVME_IDENTIFY_BUFSIZE)) == NULL) { 581 topo_mod_dprintf(mod, "zalloc failed"); 582 (void) topo_mod_seterrno(mod, EMOD_NOMEM); 583 goto error; 584 } 585 nioc.n_len = NVME_IDENTIFY_BUFSIZE; 586 nioc.n_buf = (uintptr_t)idctl; 587 588 if (ioctl(fd, NVME_IOC_IDENTIFY_CTRL, &nioc) != 0) { 589 topo_mod_dprintf(mod, "NVME_IOC_IDENTIFY_CTRL ioctl " 590 "failed: %s", strerror(errno)); 591 (void) topo_mod_seterrno(mod, EMOD_UNKNOWN); 592 goto error; 593 } 594 595 nioc.n_len = sizeof (nvme_version_t); 596 nioc.n_buf = (uintptr_t)&nvme_info.nei_vers; 597 598 if (ioctl(fd, NVME_IOC_VERSION, &nioc) != 0) { 599 topo_mod_dprintf(mod, "NVME_IOC_VERSION ioctl failed: %s", 600 strerror(errno)); 601 (void) topo_mod_seterrno(mod, EMOD_UNKNOWN); 602 goto error; 603 } 604 605 nvme_info.nei_mod = mod; 606 nvme_info.nei_nvme_path = path; 607 nvme_info.nei_dinode = node; 608 nvme_info.nei_idctl = idctl; 609 nvme_info.nei_parent = wkarg->diwk_parent; 610 nvme_info.nei_fd = fd; 611 612 if (make_nvme_node(&nvme_info) != 0) { 613 /* errno set */ 614 goto error; 615 } 616 617 ret = DI_WALK_CONTINUE; 618 619 error: 620 if (fd > 0) 621 (void) close(fd); 622 di_devfs_path_free(path); 623 free(devctl); 624 if (idctl != NULL) 625 topo_mod_free(mod, idctl, NVME_IDENTIFY_BUFSIZE); 626 return (ret); 627 } 628 629 int 630 disk_nvme_enum_disk(topo_mod_t *mod, tnode_t *pnode) 631 { 632 char *parent = NULL; 633 int err; 634 di_node_t devtree; 635 di_node_t dnode; 636 struct diwalk_arg wkarg = { 0 }; 637 int ret = -1; 638 639 /* 640 * Lookup a property containing the devfs path of the parent PCIe 641 * device of the NVMe device we're attempting to enumerate. This 642 * property is hard-coded in per-platform topo XML maps that are 643 * delivered with the OS. This hard-coded path allows topo to map a 644 * given NVMe controller to a physical location (bay or slot) on the 645 * platform, when generating the topo snapshot. 646 */ 647 if (topo_prop_get_string(pnode, TOPO_PGROUP_BINDING, 648 TOPO_BINDING_PARENT_DEV, &parent, &err) != 0) { 649 topo_mod_dprintf(mod, "parent node was missing nvme binding " 650 "properties\n"); 651 (void) topo_mod_seterrno(mod, err); 652 goto out; 653 } 654 if ((devtree = topo_mod_devinfo(mod)) == DI_NODE_NIL) { 655 topo_mod_dprintf(mod, "failed to get devinfo snapshot"); 656 (void) topo_mod_seterrno(mod, EMOD_UNKNOWN); 657 goto out; 658 } 659 660 /* 661 * Walk the devinfo tree looking NVMe devices. For each NVMe device, 662 * check if the devfs path of the parent matches the one specified in 663 * TOPO_BINDING_PARENT_DEV. 664 */ 665 wkarg.diwk_mod = mod; 666 wkarg.diwk_parent = pnode; 667 dnode = di_drv_first_node(NVME_DRV, devtree); 668 while (dnode != DI_NODE_NIL) { 669 char *path; 670 671 if ((path = di_devfs_path(di_parent_node(dnode))) == NULL) { 672 topo_mod_dprintf(mod, "failed to get dev path"); 673 (void) topo_mod_seterrno(mod, EMOD_UNKNOWN); 674 goto out; 675 } 676 if (strcmp(parent, path) == 0) { 677 if (di_walk_minor(dnode, DDI_NT_NVME_NEXUS, 0, 678 &wkarg, discover_nvme_ctl) < 0) { 679 di_devfs_path_free(path); 680 goto out; 681 } 682 } 683 di_devfs_path_free(path); 684 dnode = di_drv_next_node(dnode); 685 } 686 ret = 0; 687 688 out: 689 topo_mod_strfree(mod, parent); 690 return (ret); 691 } 692