xref: /illumos-gate/usr/src/lib/fm/topo/modules/common/disk/disk_nvme.c (revision 533affcbc7fc4d0c8132976ea454aaa715fe2307)
13c6ffbabSRob Johnston /*
23c6ffbabSRob Johnston  * This file and its contents are supplied under the terms of the
33c6ffbabSRob Johnston  * Common Development and Distribution License ("CDDL"), version 1.0.
43c6ffbabSRob Johnston  * You may only use this file in accordance with the terms of version
53c6ffbabSRob Johnston  * 1.0 of the CDDL.
63c6ffbabSRob Johnston  *
73c6ffbabSRob Johnston  * A full copy of the text of the CDDL should have accompanied this
83c6ffbabSRob Johnston  * source.  A copy of the CDDL is also available via the Internet at
93c6ffbabSRob Johnston  * http://www.illumos.org/license/CDDL.
103c6ffbabSRob Johnston  */
113c6ffbabSRob Johnston 
123c6ffbabSRob Johnston /*
133c6ffbabSRob Johnston  * Copyright 2020 Joyent, Inc.
14153f3212SHans Rosenfeld  * Copyright 2022 Tintri by DDN, Inc. All rights reserved.
15*533affcbSRobert Mustacchi  * Copyright 2024 Oxide Computer Company
163c6ffbabSRob Johnston  */
173c6ffbabSRob Johnston 
183c6ffbabSRob Johnston /*
193c6ffbabSRob Johnston  * This file drives topo node enumeration of NVMe controllers.  A single "nvme"
203c6ffbabSRob Johnston  * node is enumerated for each NVMe controller.   Child "disk" nodes are then
21744642a2SRobert Mustacchi  * enumerated for each active or attached NVMe namespace.
223c6ffbabSRob Johnston  *
233c6ffbabSRob Johnston  * nvme nodes are expected to be enumerated under either a "bay" node (for U.2
243c6ffbabSRob Johnston  * devices) or a "slot" node (for M.2 devices) or a "pciexfn" node (for AIC
253c6ffbabSRob Johnston  * devices).
263c6ffbabSRob Johnston  *
273c6ffbabSRob Johnston  * Enumeration of NVMe controllers on PCIe add-in cards is automatically driven
283c6ffbabSRob Johnston  * by the pcibus topo module.
293c6ffbabSRob Johnston  *
303c6ffbabSRob Johnston  * In order to allow for associating a given NVMe controller with a physical
313c6ffbabSRob Johnston  * location, enumeration of U.2 and M.2 devices should be driven by a
323c6ffbabSRob Johnston  * platform-specific topo map which statically sets the following two
333c6ffbabSRob Johnston  * properties on the parent "bay" or "slot" node:
343c6ffbabSRob Johnston  *
353c6ffbabSRob Johnston  * propgroup        property        description
363c6ffbabSRob Johnston  * ---------        --------        ------------
373c6ffbabSRob Johnston  * binding          driver          "nvme"
383c6ffbabSRob Johnston  * binding          parent-device   devpath of parent PCIe device
393c6ffbabSRob Johnston  *
403c6ffbabSRob Johnston  * for example:
413c6ffbabSRob Johnston  *
423c6ffbabSRob Johnston  * <propgroup name="binding" version="1" name-stability="Private"
433c6ffbabSRob Johnston  *   data-stability="Private">
443c6ffbabSRob Johnston  *     <propval name="driver" type="string" value="nvme"/>
453c6ffbabSRob Johnston  *     <propval name="parent-device" type="string"
463c6ffbabSRob Johnston  *       value="/pci@0,0/pci8086,6f09@3,1"/>
473c6ffbabSRob Johnston  * </propgroup>
483c6ffbabSRob Johnston  * <dependents grouping="children">
493c6ffbabSRob Johnston  *     <range name="nvme" min="0" max="0">
503c6ffbabSRob Johnston  *         <enum-method name="disk" version="1"/>
513c6ffbabSRob Johnston  *     </range>
523c6ffbabSRob Johnston  * </dependents>
533c6ffbabSRob Johnston  */
543c6ffbabSRob Johnston #include <stdlib.h>
553c6ffbabSRob Johnston #include <sys/types.h>
563c6ffbabSRob Johnston #include <sys/stat.h>
573c6ffbabSRob Johnston #include <fcntl.h>
583c6ffbabSRob Johnston #include <unistd.h>
593c6ffbabSRob Johnston #include <string.h>
603c6ffbabSRob Johnston #include <strings.h>
61744642a2SRobert Mustacchi #include <stdbool.h>
623c6ffbabSRob Johnston 
633c6ffbabSRob Johnston #include <sys/fm/protocol.h>
643c6ffbabSRob Johnston #include <fm/topo_hc.h>
653c6ffbabSRob Johnston #include <fm/topo_mod.h>
66744642a2SRobert Mustacchi #include <topo_ufm.h>
673c6ffbabSRob Johnston 
683c6ffbabSRob Johnston #include <sys/dkio.h>
693c6ffbabSRob Johnston #include <sys/scsi/generic/inquiry.h>
703c6ffbabSRob Johnston 
71*533affcbSRobert Mustacchi #include <libnvme.h>
723c6ffbabSRob Johnston #include "disk.h"
733c6ffbabSRob Johnston #include "disk_drivers.h"
743c6ffbabSRob Johnston 
753c6ffbabSRob Johnston typedef struct nvme_enum_info {
763c6ffbabSRob Johnston 	topo_mod_t		*nei_mod;
773c6ffbabSRob Johnston 	di_node_t		nei_dinode;
78*533affcbSRobert Mustacchi 	nvme_t			*nei_libnvme;
79*533affcbSRobert Mustacchi 	nvme_ctrl_t		*nei_ctrl;
80*533affcbSRobert Mustacchi 	nvme_ctrl_info_t	*nei_ctrl_info;
81*533affcbSRobert Mustacchi 	const nvme_version_t	*nei_vers;
823c6ffbabSRob Johnston 	tnode_t			*nei_parent;
833c6ffbabSRob Johnston 	tnode_t			*nei_nvme;
843c6ffbabSRob Johnston 	nvlist_t		*nei_nvme_fmri;
853c6ffbabSRob Johnston 	int			nei_fd;
863c6ffbabSRob Johnston } nvme_enum_info_t;
873c6ffbabSRob Johnston 
883c6ffbabSRob Johnston typedef struct devlink_arg {
893c6ffbabSRob Johnston 	topo_mod_t		*dla_mod;
903c6ffbabSRob Johnston 	char			*dla_logical_disk;
913c6ffbabSRob Johnston 	uint_t			dla_strsz;
923c6ffbabSRob Johnston } devlink_arg_t;
933c6ffbabSRob Johnston 
943c6ffbabSRob Johnston static int
devlink_cb(di_devlink_t dl,void * arg)953c6ffbabSRob Johnston devlink_cb(di_devlink_t dl, void *arg)
963c6ffbabSRob Johnston {
973c6ffbabSRob Johnston 	devlink_arg_t *dlarg = (devlink_arg_t *)arg;
983c6ffbabSRob Johnston 	topo_mod_t *mod = dlarg->dla_mod;
993c6ffbabSRob Johnston 	const char *devpath;
1003c6ffbabSRob Johnston 	char *slice, *ctds;
1013c6ffbabSRob Johnston 
1023c6ffbabSRob Johnston 	if ((devpath = di_devlink_path(dl)) == NULL ||
1033c6ffbabSRob Johnston 	    (dlarg->dla_logical_disk = topo_mod_strdup(mod, devpath)) ==
1043c6ffbabSRob Johnston 	    NULL) {
1053c6ffbabSRob Johnston 		return (DI_WALK_TERMINATE);
1063c6ffbabSRob Johnston 	}
1073c6ffbabSRob Johnston 
1083c6ffbabSRob Johnston 	/*
1093c6ffbabSRob Johnston 	 * We need to keep track of the original string size before we
1103c6ffbabSRob Johnston 	 * truncate it with a NUL, so that we can free the right number of
1113c6ffbabSRob Johnston 	 * bytes when we're done, otherwise libumem will complain.
1123c6ffbabSRob Johnston 	 */
1133c6ffbabSRob Johnston 	dlarg->dla_strsz = strlen(dlarg->dla_logical_disk) + 1;
1143c6ffbabSRob Johnston 
1153c6ffbabSRob Johnston 	/* trim the slice off the public name */
1163c6ffbabSRob Johnston 	if (((ctds = strrchr(dlarg->dla_logical_disk, '/')) != NULL) &&
1173c6ffbabSRob Johnston 	    ((slice = strchr(ctds, 's')) != NULL))
1183c6ffbabSRob Johnston 		*slice = '\0';
1193c6ffbabSRob Johnston 
1203c6ffbabSRob Johnston 	return (DI_WALK_TERMINATE);
1213c6ffbabSRob Johnston }
1223c6ffbabSRob Johnston 
1233c6ffbabSRob Johnston static char *
get_logical_disk(topo_mod_t * mod,const char * devpath,uint_t * bufsz)1243c6ffbabSRob Johnston get_logical_disk(topo_mod_t *mod, const char *devpath, uint_t *bufsz)
1253c6ffbabSRob Johnston {
1263c6ffbabSRob Johnston 	di_devlink_handle_t devhdl;
1273c6ffbabSRob Johnston 	devlink_arg_t dlarg = { 0 };
1283c6ffbabSRob Johnston 	char *minorpath = NULL;
1293c6ffbabSRob Johnston 
1303c6ffbabSRob Johnston 	if (asprintf(&minorpath, "%s:a", devpath) < 0) {
1313c6ffbabSRob Johnston 		return (NULL);
1323c6ffbabSRob Johnston 	}
1333c6ffbabSRob Johnston 
1343c6ffbabSRob Johnston 	if ((devhdl = di_devlink_init(NULL, 0)) == DI_NODE_NIL) {
1353c6ffbabSRob Johnston 		topo_mod_dprintf(mod, "%s: di_devlink_init failed", __func__);
1363c6ffbabSRob Johnston 		free(minorpath);
1373c6ffbabSRob Johnston 		return (NULL);
1383c6ffbabSRob Johnston 	}
1393c6ffbabSRob Johnston 
1403c6ffbabSRob Johnston 	dlarg.dla_mod = mod;
1413c6ffbabSRob Johnston 
1423c6ffbabSRob Johnston 	(void) di_devlink_walk(devhdl, "^dsk/", minorpath, DI_PRIMARY_LINK,
1433c6ffbabSRob Johnston 	    &dlarg, devlink_cb);
1443c6ffbabSRob Johnston 
1453c6ffbabSRob Johnston 	(void) di_devlink_fini(&devhdl);
1463c6ffbabSRob Johnston 	free(minorpath);
1473c6ffbabSRob Johnston 
1483c6ffbabSRob Johnston 	*bufsz = dlarg.dla_strsz;
1493c6ffbabSRob Johnston 	return (dlarg.dla_logical_disk);
1503c6ffbabSRob Johnston }
1513c6ffbabSRob Johnston 
152744642a2SRobert Mustacchi static bool
disk_nvme_make_ns_serial(topo_mod_t * mod,nvme_ns_info_t * ns_info,char * buf,size_t buflen)153*533affcbSRobert Mustacchi disk_nvme_make_ns_serial(topo_mod_t *mod, nvme_ns_info_t *ns_info, char *buf,
154*533affcbSRobert Mustacchi     size_t buflen)
1553c6ffbabSRob Johnston {
156*533affcbSRobert Mustacchi 	uint8_t nguid[16], eui64[8];
157744642a2SRobert Mustacchi 	int ret;
1583c6ffbabSRob Johnston 
159*533affcbSRobert Mustacchi 	if (nvme_ns_info_nguid(ns_info, nguid)) {
160744642a2SRobert Mustacchi 		ret = snprintf(buf, buflen, "%0.2X%0.2X%0.2X%0.2X%0.2X%0.2X"
161744642a2SRobert Mustacchi 		    "%0.2X%0.2X%0.2X%0.2X%0.2X%0.2X%0.2X%0.2X%0.2X%0.2X",
162*533affcbSRobert Mustacchi 		    nguid[0], nguid[1], nguid[2], nguid[3], nguid[4],
163*533affcbSRobert Mustacchi 		    nguid[5], nguid[6], nguid[7], nguid[8], nguid[9],
164*533affcbSRobert Mustacchi 		    nguid[10], nguid[11], nguid[12], nguid[13], nguid[14],
165*533affcbSRobert Mustacchi 		    nguid[15]);
166*533affcbSRobert Mustacchi 	} else if (nvme_ns_info_eui64(ns_info, eui64)) {
167744642a2SRobert Mustacchi 		ret = snprintf(buf, buflen,
168744642a2SRobert Mustacchi 		    "%0.2X%0.2X%0.2X%0.2X%0.2X%0.2X%0.2X%0.2X",
169*533affcbSRobert Mustacchi 		    eui64[0], eui64[1], eui64[2], eui64[3], eui64[4],
170*533affcbSRobert Mustacchi 		    eui64[5], eui64[6], eui64[7]);
1713c6ffbabSRob Johnston 	} else {
172*533affcbSRobert Mustacchi 		ret = snprintf(buf, buflen, "%u", nvme_ns_info_nsid(ns_info));
173744642a2SRobert Mustacchi 	}
174744642a2SRobert Mustacchi 
175744642a2SRobert Mustacchi 	if ((size_t)ret >= buflen) {
176744642a2SRobert Mustacchi 		topo_mod_dprintf(mod, "overflowed serial number for nsid %u: "
177*533affcbSRobert Mustacchi 		    "needed %zu bytes, got %d", nvme_ns_info_nsid(ns_info),
178*533affcbSRobert Mustacchi 		    buflen, ret);
179744642a2SRobert Mustacchi 		return (false);
180744642a2SRobert Mustacchi 	}
181744642a2SRobert Mustacchi 
182744642a2SRobert Mustacchi 	return (true);
1833c6ffbabSRob Johnston }
1843c6ffbabSRob Johnston 
1853c6ffbabSRob Johnston /*
186744642a2SRobert Mustacchi  * Create the common I/O property group properties that are shared between
187744642a2SRobert Mustacchi  * controllers and namespaces. We assume the property group was already created.
1883c6ffbabSRob Johnston  */
189744642a2SRobert Mustacchi static bool
disk_nvme_common_io(topo_mod_t * mod,tnode_t * tn,di_node_t di)190744642a2SRobert Mustacchi disk_nvme_common_io(topo_mod_t *mod, tnode_t *tn, di_node_t di)
191744642a2SRobert Mustacchi {
192744642a2SRobert Mustacchi 	int err;
193744642a2SRobert Mustacchi 	int inst = di_instance(di);
194744642a2SRobert Mustacchi 	const char *drv = di_driver_name(di);
195744642a2SRobert Mustacchi 	char *path;
196744642a2SRobert Mustacchi 	const char *ppaths[1];
1973c6ffbabSRob Johnston 
198744642a2SRobert Mustacchi 	if (inst != -1 && topo_prop_set_uint32(tn, TOPO_PGROUP_IO,
199744642a2SRobert Mustacchi 	    TOPO_IO_INSTANCE, TOPO_PROP_IMMUTABLE, (uint32_t)inst, &err) != 0) {
200744642a2SRobert Mustacchi 		topo_mod_dprintf(mod, "failed to set %s:%s on %s[%" PRIu64 "]: "
201744642a2SRobert Mustacchi 		    "%s", TOPO_PGROUP_IO, TOPO_IO_INSTANCE, topo_node_name(tn),
202744642a2SRobert Mustacchi 		    topo_node_instance(tn), topo_strerror(err));
203744642a2SRobert Mustacchi 		return (false);
2043c6ffbabSRob Johnston 	}
2053c6ffbabSRob Johnston 
206744642a2SRobert Mustacchi 	if (drv != NULL && topo_prop_set_string(tn, TOPO_PGROUP_IO,
207744642a2SRobert Mustacchi 	    TOPO_IO_DRIVER, TOPO_PROP_IMMUTABLE, drv, &err) != 0) {
208744642a2SRobert Mustacchi 		topo_mod_dprintf(mod, "failed to set %s:%s on %s[%" PRIu64 "]: "
209744642a2SRobert Mustacchi 		    "%s", TOPO_PGROUP_IO, TOPO_IO_DRIVER, topo_node_name(tn),
210744642a2SRobert Mustacchi 		    topo_node_instance(tn), topo_strerror(err));
211744642a2SRobert Mustacchi 		return (false);
212744642a2SRobert Mustacchi 	}
2133c6ffbabSRob Johnston 
214744642a2SRobert Mustacchi 	if (drv != NULL) {
215744642a2SRobert Mustacchi 		nvlist_t *fmri = topo_mod_modfmri(mod, FM_MOD_SCHEME_VERSION,
216744642a2SRobert Mustacchi 		    drv);
217744642a2SRobert Mustacchi 		if (mod != NULL && topo_prop_set_fmri(tn, TOPO_PGROUP_IO,
218744642a2SRobert Mustacchi 		    TOPO_IO_MODULE, TOPO_PROP_IMMUTABLE, fmri, &err) != 0) {
219744642a2SRobert Mustacchi 			topo_mod_dprintf(mod, "failed to set %s:%s on %s[%"
220744642a2SRobert Mustacchi 			    PRIu64 "]: %s", TOPO_PGROUP_IO, TOPO_IO_MODULE,
221744642a2SRobert Mustacchi 			    topo_node_name(tn), topo_node_instance(tn),
2223c6ffbabSRob Johnston 			    topo_strerror(err));
223744642a2SRobert Mustacchi 			nvlist_free(fmri);
224744642a2SRobert Mustacchi 			return (false);
225744642a2SRobert Mustacchi 		}
226744642a2SRobert Mustacchi 		nvlist_free(fmri);
2273c6ffbabSRob Johnston 	}
2283c6ffbabSRob Johnston 
229744642a2SRobert Mustacchi 	path = di_devfs_path(di);
2303c6ffbabSRob Johnston 	ppaths[0] = path;
231744642a2SRobert Mustacchi 	if (path != NULL && topo_prop_set_string(tn, TOPO_PGROUP_IO,
232744642a2SRobert Mustacchi 	    TOPO_IO_DEV_PATH, TOPO_PROP_IMMUTABLE, path, &err) != 0) {
233744642a2SRobert Mustacchi 		topo_mod_dprintf(mod, "failed to set %s:%s on %s[%" PRIu64 "]: "
234744642a2SRobert Mustacchi 		    "%s", TOPO_PGROUP_IO, TOPO_IO_DRIVER, topo_node_name(tn),
235744642a2SRobert Mustacchi 		    topo_node_instance(tn), topo_strerror(err));
236744642a2SRobert Mustacchi 		di_devfs_path_free(path);
237744642a2SRobert Mustacchi 		return (false);
238744642a2SRobert Mustacchi 	}
239744642a2SRobert Mustacchi 
240744642a2SRobert Mustacchi 	if (path != NULL && topo_prop_set_string_array(tn, TOPO_PGROUP_IO,
241744642a2SRobert Mustacchi 	    TOPO_IO_PHYS_PATH, TOPO_PROP_IMMUTABLE, ppaths, 1, &err) != 0) {
242744642a2SRobert Mustacchi 		topo_mod_dprintf(mod, "failed to set %s:%s on %s[%" PRIu64 "]: "
243744642a2SRobert Mustacchi 		    "%s", TOPO_PGROUP_IO, TOPO_IO_PHYS_PATH, topo_node_name(tn),
244744642a2SRobert Mustacchi 		    topo_node_instance(tn), topo_strerror(err));
245744642a2SRobert Mustacchi 		di_devfs_path_free(path);
246744642a2SRobert Mustacchi 		return (false);
247744642a2SRobert Mustacchi 	}
248744642a2SRobert Mustacchi 	di_devfs_path_free(path);
249744642a2SRobert Mustacchi 
250744642a2SRobert Mustacchi 	return (true);
251744642a2SRobert Mustacchi }
2523c6ffbabSRob Johnston 
2533c6ffbabSRob Johnston /*
254744642a2SRobert Mustacchi  * Add the various storage and I/O property group items that are appropriate
255744642a2SRobert Mustacchi  * given that we have a devinfo node. The storage property group has already
256744642a2SRobert Mustacchi  * been created, but the I/O property group has not.
2573c6ffbabSRob Johnston  */
258744642a2SRobert Mustacchi static void
disk_nvme_make_ns_di_props(topo_mod_t * mod,tnode_t * tn,di_node_t di)259744642a2SRobert Mustacchi disk_nvme_make_ns_di_props(topo_mod_t *mod, tnode_t *tn, di_node_t di)
260744642a2SRobert Mustacchi {
261744642a2SRobert Mustacchi 	int err;
262744642a2SRobert Mustacchi 	char *devid, *mfg, *model, *rev, *serial, *log, *path;
263744642a2SRobert Mustacchi 	uint_t buflen;
264744642a2SRobert Mustacchi 
265744642a2SRobert Mustacchi 	if (di_prop_lookup_strings(DDI_DEV_T_ANY, di, DEVID_PROP_NAME,
266744642a2SRobert Mustacchi 	    &devid) != 1 ||
267744642a2SRobert Mustacchi 	    di_prop_lookup_strings(DDI_DEV_T_ANY, di, INQUIRY_VENDOR_ID,
268744642a2SRobert Mustacchi 	    &mfg) != 1 ||
269744642a2SRobert Mustacchi 	    di_prop_lookup_strings(DDI_DEV_T_ANY, di, INQUIRY_PRODUCT_ID,
270744642a2SRobert Mustacchi 	    &model) != 1 ||
271744642a2SRobert Mustacchi 	    di_prop_lookup_strings(DDI_DEV_T_ANY, di, INQUIRY_REVISION_ID,
272744642a2SRobert Mustacchi 	    &rev) != 1 ||
273744642a2SRobert Mustacchi 	    di_prop_lookup_strings(DDI_DEV_T_ANY, di, INQUIRY_SERIAL_NO,
274744642a2SRobert Mustacchi 	    &serial) != 1) {
275744642a2SRobert Mustacchi 		topo_mod_dprintf(mod, "failed to get devinfo props for %s[%"
276744642a2SRobert Mustacchi 		    PRIu64 "]", topo_node_name(tn), topo_node_instance(tn));
277744642a2SRobert Mustacchi 		return;
2783c6ffbabSRob Johnston 	}
2793c6ffbabSRob Johnston 
280744642a2SRobert Mustacchi 	/*
281744642a2SRobert Mustacchi 	 * Set the basic storage manufacturer information. Yes, this is
282744642a2SRobert Mustacchi 	 * information really about the NVMe controller and not the namespace.
283744642a2SRobert Mustacchi 	 * That's how the storage property group basically works here.
284744642a2SRobert Mustacchi 	 */
285744642a2SRobert Mustacchi 	if (topo_prop_set_string(tn, TOPO_PGROUP_STORAGE,
286744642a2SRobert Mustacchi 	    TOPO_STORAGE_MANUFACTURER, TOPO_PROP_IMMUTABLE, mfg, &err) != 0 ||
287744642a2SRobert Mustacchi 	    topo_prop_set_string(tn, TOPO_PGROUP_STORAGE,
288744642a2SRobert Mustacchi 	    TOPO_STORAGE_SERIAL_NUM, TOPO_PROP_IMMUTABLE, serial, &err) != 0 ||
289744642a2SRobert Mustacchi 	    topo_prop_set_string(tn, TOPO_PGROUP_STORAGE,
290744642a2SRobert Mustacchi 	    TOPO_STORAGE_FIRMWARE_REV, TOPO_PROP_IMMUTABLE, rev, &err) != 0 ||
291744642a2SRobert Mustacchi 	    topo_prop_set_string(tn, TOPO_PGROUP_STORAGE,
292744642a2SRobert Mustacchi 	    TOPO_STORAGE_MODEL, TOPO_PROP_IMMUTABLE, model, &err) != 0) {
293744642a2SRobert Mustacchi 		topo_mod_dprintf(mod, "failed to set storage properties on "
294744642a2SRobert Mustacchi 		    "%s[%" PRIu64 "]: %s", topo_node_name(tn),
295744642a2SRobert Mustacchi 		    topo_node_instance(tn), topo_strerror(err));
296744642a2SRobert Mustacchi 		return;
2973c6ffbabSRob Johnston 	}
2983c6ffbabSRob Johnston 
299744642a2SRobert Mustacchi 	if (topo_pgroup_create(tn, &io_pgroup, &err) != 0) {
300744642a2SRobert Mustacchi 		topo_mod_dprintf(mod, "failed to create I/O property "
301744642a2SRobert Mustacchi 		    "group on %s[%" PRIu64 "]: %s",  topo_node_name(tn),
302744642a2SRobert Mustacchi 		    topo_node_instance(tn), topo_strerror(err));
303744642a2SRobert Mustacchi 	}
3043c6ffbabSRob Johnston 
305744642a2SRobert Mustacchi 	if (!disk_nvme_common_io(mod, tn, di)) {
306744642a2SRobert Mustacchi 		return;
307744642a2SRobert Mustacchi 	}
308744642a2SRobert Mustacchi 
309744642a2SRobert Mustacchi 	/*
310744642a2SRobert Mustacchi 	 * The last property that we'd like to attempt to create for a namespace
311744642a2SRobert Mustacchi 	 * is a mapping back to its corresponding logical disk entry in /dev.
312744642a2SRobert Mustacchi 	 * The logical disk will be everything past the trailing /, i.e. a
313744642a2SRobert Mustacchi 	 * cXtXdX value.
314744642a2SRobert Mustacchi 	 */
315744642a2SRobert Mustacchi 	path = di_devfs_path(di);
316744642a2SRobert Mustacchi 	if (path == NULL) {
317744642a2SRobert Mustacchi 		return;
318744642a2SRobert Mustacchi 	}
319744642a2SRobert Mustacchi 	log = get_logical_disk(mod, path, &buflen);
3203c6ffbabSRob Johnston 	di_devfs_path_free(path);
321744642a2SRobert Mustacchi 	if (log == NULL) {
322744642a2SRobert Mustacchi 		return;
323744642a2SRobert Mustacchi 	}
324744642a2SRobert Mustacchi 	path = strrchr(log, '/');
325744642a2SRobert Mustacchi 	if (path != NULL && path[1] != '\0' &&
326744642a2SRobert Mustacchi 	    topo_prop_set_string(tn, TOPO_PGROUP_STORAGE,
327744642a2SRobert Mustacchi 	    TOPO_STORAGE_LOGICAL_DISK_NAME, TOPO_PROP_IMMUTABLE, path + 1,
328744642a2SRobert Mustacchi 	    &err) != 0) {
329744642a2SRobert Mustacchi 		topo_mod_dprintf(mod, "failed to set %s:%s on %s[%"
330744642a2SRobert Mustacchi 		    PRIu64 "]: %s", TOPO_PGROUP_STORAGE,
331744642a2SRobert Mustacchi 		    TOPO_STORAGE_LOGICAL_DISK_NAME, topo_node_name(tn),
332744642a2SRobert Mustacchi 		    topo_node_instance(tn), topo_strerror(err));
333744642a2SRobert Mustacchi 	}
334744642a2SRobert Mustacchi 	topo_mod_free(mod, log, buflen);
335744642a2SRobert Mustacchi }
336744642a2SRobert Mustacchi 
337744642a2SRobert Mustacchi static void
disk_nvme_make_ns(nvme_enum_info_t * nei,nvme_ns_info_t * ns_info)338*533affcbSRobert Mustacchi disk_nvme_make_ns(nvme_enum_info_t *nei, nvme_ns_info_t *ns_info)
339744642a2SRobert Mustacchi {
340744642a2SRobert Mustacchi 	topo_mod_t *mod = nei->nei_mod;
341744642a2SRobert Mustacchi 	nvlist_t *auth = NULL, *fmri = NULL;
342*533affcbSRobert Mustacchi 	const uint32_t nsid = nvme_ns_info_nsid(ns_info);
343744642a2SRobert Mustacchi 	const topo_instance_t inst = nsid - 1;
344744642a2SRobert Mustacchi 	char serial[64], capstr[64];
345*533affcbSRobert Mustacchi 	const nvme_nvm_lba_fmt_t *fmt;
346*533affcbSRobert Mustacchi 	const char *bd_addr;
347*533affcbSRobert Mustacchi 	uint64_t cap, blksz, capblks;
348744642a2SRobert Mustacchi 	tnode_t *tn;
349744642a2SRobert Mustacchi 	int err;
350744642a2SRobert Mustacchi 
351744642a2SRobert Mustacchi 	auth = topo_mod_auth(mod, nei->nei_nvme);
352744642a2SRobert Mustacchi 	if (auth == NULL) {
353744642a2SRobert Mustacchi 		topo_mod_dprintf(mod, "failed to get auth for nsid %u from "
354744642a2SRobert Mustacchi 		    "parent %s[%" PRIu64 "]: %s", nsid,
355744642a2SRobert Mustacchi 		    topo_node_name(nei->nei_nvme),
356744642a2SRobert Mustacchi 		    topo_node_instance(nei->nei_nvme), topo_mod_errmsg(mod));
357744642a2SRobert Mustacchi 		goto done;
358744642a2SRobert Mustacchi 	}
359744642a2SRobert Mustacchi 
360744642a2SRobert Mustacchi 	/*
361744642a2SRobert Mustacchi 	 * We want to construct the FMRI for the namespace. The namespace is a
362744642a2SRobert Mustacchi 	 * little awkward in terms of things like the model, revision, and
363744642a2SRobert Mustacchi 	 * serial. While blkdev sets up standard inquiry properties to map these
364744642a2SRobert Mustacchi 	 * to the parent device which makes sense in the context of trying to
365744642a2SRobert Mustacchi 	 * use this as a normal block device, it's not really appropriate here.
366744642a2SRobert Mustacchi 	 * The namespace is not the NVMe controller. We construct the namespace
367744642a2SRobert Mustacchi 	 * serial number from the preferential ordering of information that
368744642a2SRobert Mustacchi 	 * we're given of the NGUID, EUI64, and then fall back to the namespace
369744642a2SRobert Mustacchi 	 * number.
370744642a2SRobert Mustacchi 	 */
371*533affcbSRobert Mustacchi 	if (!disk_nvme_make_ns_serial(mod, ns_info, serial, sizeof (serial))) {
372744642a2SRobert Mustacchi 		goto done;
373744642a2SRobert Mustacchi 	}
374744642a2SRobert Mustacchi 	fmri = topo_mod_hcfmri(mod, nei->nei_nvme, FM_HC_SCHEME_VERSION,
375744642a2SRobert Mustacchi 	    DISK, inst, NULL, auth, NULL, NULL, serial);
376744642a2SRobert Mustacchi 	if (fmri == NULL) {
377744642a2SRobert Mustacchi 		topo_mod_dprintf(mod, "failed to make fmri for %s[%" PRIu64
378744642a2SRobert Mustacchi 		    "] on nsid %u: %s", DISK, inst, nsid, topo_mod_errmsg(mod));
379744642a2SRobert Mustacchi 		goto done;
380744642a2SRobert Mustacchi 	}
381744642a2SRobert Mustacchi 
382744642a2SRobert Mustacchi 	tn = topo_node_bind(mod, nei->nei_nvme, DISK, inst, fmri);
383744642a2SRobert Mustacchi 	if (tn == NULL) {
384744642a2SRobert Mustacchi 		topo_mod_dprintf(mod, "failed to bind fmri for %s[%" PRIu64
385744642a2SRobert Mustacchi 		    "] on nsid %u: %s", DISK, inst, nsid, topo_mod_errmsg(mod));
386744642a2SRobert Mustacchi 		goto done;
387744642a2SRobert Mustacchi 	}
388744642a2SRobert Mustacchi 
389744642a2SRobert Mustacchi 	/*
390744642a2SRobert Mustacchi 	 * Always inherit our parent's FRU. The namespace is just a part of the
391744642a2SRobert Mustacchi 	 * device in reality.
392744642a2SRobert Mustacchi 	 */
393744642a2SRobert Mustacchi 	if (topo_node_fru_set(tn, NULL, 0, &err) != 0) {
394744642a2SRobert Mustacchi 		topo_mod_dprintf(mod, "failed to set FRU for %s[%" PRIu64
395744642a2SRobert Mustacchi 		    "] on nsid %u: %s", DISK, inst, nsid, topo_strerror(err));
396744642a2SRobert Mustacchi 		goto done;
397744642a2SRobert Mustacchi 
398744642a2SRobert Mustacchi 	}
399744642a2SRobert Mustacchi 
400744642a2SRobert Mustacchi 	/*
401744642a2SRobert Mustacchi 	 * Our namespace may or may not be attached. From the namespace we will
402744642a2SRobert Mustacchi 	 * always get the capacity and block information. The rest of it will
403744642a2SRobert Mustacchi 	 * end up being filled in if we find a devinfo node.
404744642a2SRobert Mustacchi 	 */
405744642a2SRobert Mustacchi 	if (topo_pgroup_create(tn, &storage_pgroup, &err) != 0) {
406744642a2SRobert Mustacchi 		topo_mod_dprintf(mod, "failed to create storage property "
407744642a2SRobert Mustacchi 		    "group on %s[%" PRIu64 "]: %s", DISK, inst,
408744642a2SRobert Mustacchi 		    topo_strerror(err));
409744642a2SRobert Mustacchi 	}
410744642a2SRobert Mustacchi 
411*533affcbSRobert Mustacchi 	if (!nvme_ns_info_curformat(ns_info, &fmt)) {
412*533affcbSRobert Mustacchi 		topo_mod_dprintf(mod, "failed to get current namespace "
413*533affcbSRobert Mustacchi 		    "format: %s", nvme_ns_info_errmsg(ns_info));
414*533affcbSRobert Mustacchi 		goto done;
415*533affcbSRobert Mustacchi 	}
416*533affcbSRobert Mustacchi 
417*533affcbSRobert Mustacchi 	blksz = nvme_nvm_lba_fmt_data_size(fmt);
418*533affcbSRobert Mustacchi 	if (topo_prop_set_uint64(tn, TOPO_PGROUP_STORAGE,
419744642a2SRobert Mustacchi 	    TOPO_STORAGE_LOG_BLOCK_SIZE, TOPO_PROP_IMMUTABLE, blksz, &err) !=
420744642a2SRobert Mustacchi 	    0) {
421744642a2SRobert Mustacchi 		topo_mod_dprintf(mod, "failed to create property %s:%s on %s[%"
422744642a2SRobert Mustacchi 		    PRIu64 "]: %s", TOPO_PGROUP_STORAGE,
423744642a2SRobert Mustacchi 		    TOPO_STORAGE_LOG_BLOCK_SIZE, DISK, inst,
424744642a2SRobert Mustacchi 		    topo_strerror(err));
425744642a2SRobert Mustacchi 		goto done;
426744642a2SRobert Mustacchi 	}
427744642a2SRobert Mustacchi 
428*533affcbSRobert Mustacchi 	if (!nvme_ns_info_cap(ns_info, &capblks)) {
429*533affcbSRobert Mustacchi 		topo_mod_dprintf(mod, "failed to get namespace capacity: %s",
430*533affcbSRobert Mustacchi 		    nvme_ns_info_errmsg(ns_info));
431*533affcbSRobert Mustacchi 		goto done;
432*533affcbSRobert Mustacchi 	}
433*533affcbSRobert Mustacchi 
434*533affcbSRobert Mustacchi 	cap = blksz * capblks;
435744642a2SRobert Mustacchi 	if (snprintf(capstr, sizeof (capstr), "%" PRIu64, cap) >=
436744642a2SRobert Mustacchi 	    sizeof (capstr)) {
437744642a2SRobert Mustacchi 		topo_mod_dprintf(mod, "overflowed capacity calculation on "
438744642a2SRobert Mustacchi 		    "nsid %u", nsid);
439744642a2SRobert Mustacchi 		goto done;
440744642a2SRobert Mustacchi 	}
441744642a2SRobert Mustacchi 
442744642a2SRobert Mustacchi 	/*
443744642a2SRobert Mustacchi 	 * Finally attempt to find a child node that has a matching name and go
444744642a2SRobert Mustacchi 	 * from there. Sorry, this does result in node creation being O(n^2),
445*533affcbSRobert Mustacchi 	 * but at least n is usually small today. Note, we may not have a blkdev
446*533affcbSRobert Mustacchi 	 * address because the disk may not be attached.
447744642a2SRobert Mustacchi 	 */
448*533affcbSRobert Mustacchi 	if (!nvme_ns_info_bd_addr(ns_info, &bd_addr)) {
449*533affcbSRobert Mustacchi 		if (nvme_ns_info_err(ns_info) != NVME_INFO_ERR_NS_NO_BLKDEV) {
450*533affcbSRobert Mustacchi 			topo_mod_dprintf(mod, "failed to get namespace blkdev "
451*533affcbSRobert Mustacchi 			    "address: %s", nvme_ns_info_errmsg(ns_info));
452*533affcbSRobert Mustacchi 		}
453*533affcbSRobert Mustacchi 		goto done;
454*533affcbSRobert Mustacchi 	}
455*533affcbSRobert Mustacchi 
456744642a2SRobert Mustacchi 	for (di_node_t di = di_child_node(nei->nei_dinode); di != DI_NODE_NIL;
457744642a2SRobert Mustacchi 	    di = di_sibling_node(di)) {
458744642a2SRobert Mustacchi 		const char *addr = di_bus_addr(di);
459*533affcbSRobert Mustacchi 		if (addr != NULL && strcmp(addr, bd_addr) == 0) {
460744642a2SRobert Mustacchi 			disk_nvme_make_ns_di_props(mod, tn, di);
461744642a2SRobert Mustacchi 		}
462744642a2SRobert Mustacchi 	}
463744642a2SRobert Mustacchi 
464744642a2SRobert Mustacchi done:
4653c6ffbabSRob Johnston 	nvlist_free(auth);
4663c6ffbabSRob Johnston 	nvlist_free(fmri);
467744642a2SRobert Mustacchi }
468744642a2SRobert Mustacchi 
469744642a2SRobert Mustacchi /*
470744642a2SRobert Mustacchi  * Attempt to make a ufm node, but swallow the error so we can try to get as
471744642a2SRobert Mustacchi  * much of the disk information as possible.
472744642a2SRobert Mustacchi  */
473744642a2SRobert Mustacchi static void
disk_nvme_make_ufm(topo_mod_t * mod,nvme_enum_info_t * nei)474744642a2SRobert Mustacchi disk_nvme_make_ufm(topo_mod_t *mod, nvme_enum_info_t *nei)
475744642a2SRobert Mustacchi {
476744642a2SRobert Mustacchi 	topo_ufm_devinfo_t tud;
477744642a2SRobert Mustacchi 	char *path = di_devfs_path(nei->nei_dinode);
478744642a2SRobert Mustacchi 	if (path == NULL) {
479744642a2SRobert Mustacchi 		return;
480744642a2SRobert Mustacchi 	}
481744642a2SRobert Mustacchi 
482744642a2SRobert Mustacchi 	tud.tud_method = TOPO_UFM_M_DEVINFO;
483744642a2SRobert Mustacchi 	tud.tud_path = path;
484744642a2SRobert Mustacchi 	if (topo_mod_load(mod, TOPO_MOD_UFM, TOPO_VERSION) == NULL) {
485744642a2SRobert Mustacchi 		topo_mod_dprintf(mod, "disk enum could not load ufm module");
486744642a2SRobert Mustacchi 		di_devfs_path_free(path);
487744642a2SRobert Mustacchi 		return;
488744642a2SRobert Mustacchi 	}
489744642a2SRobert Mustacchi 
490744642a2SRobert Mustacchi 	(void) topo_mod_enumerate(mod, nei->nei_nvme, TOPO_MOD_UFM, UFM, 0, 0,
491744642a2SRobert Mustacchi 	    &tud);
492744642a2SRobert Mustacchi 	di_devfs_path_free(path);
4933c6ffbabSRob Johnston }
4943c6ffbabSRob Johnston 
4953c6ffbabSRob Johnston static const topo_pgroup_info_t nvme_pgroup = {
4963c6ffbabSRob Johnston 	TOPO_PGROUP_NVME,
4973c6ffbabSRob Johnston 	TOPO_STABILITY_PRIVATE,
4983c6ffbabSRob Johnston 	TOPO_STABILITY_PRIVATE,
4993c6ffbabSRob Johnston 	1
5003c6ffbabSRob Johnston };
5013c6ffbabSRob Johnston 
5023c6ffbabSRob Johnston static int
make_nvme_node(nvme_enum_info_t * nvme_info)5033c6ffbabSRob Johnston make_nvme_node(nvme_enum_info_t *nvme_info)
5043c6ffbabSRob Johnston {
5053c6ffbabSRob Johnston 	topo_mod_t *mod = nvme_info->nei_mod;
506*533affcbSRobert Mustacchi 	nvme_ctrl_info_t *info = nvme_info->nei_ctrl_info;
507*533affcbSRobert Mustacchi 	nvme_ns_iter_t *iter = NULL;
508*533affcbSRobert Mustacchi 	nvme_iter_t nret;
509*533affcbSRobert Mustacchi 	const nvme_ns_disc_t *disc;
5103c6ffbabSRob Johnston 	nvlist_t *auth = NULL, *fmri = NULL, *fru;
5113c6ffbabSRob Johnston 	tnode_t *nvme;
512*533affcbSRobert Mustacchi 	char *model = NULL, *serial = NULL, *vers = NULL;
5133c6ffbabSRob Johnston 	char *pname = topo_node_name(nvme_info->nei_parent);
5143c6ffbabSRob Johnston 	char *label = NULL;
5153c6ffbabSRob Johnston 	topo_instance_t pinst = topo_node_instance(nvme_info->nei_parent);
5163c6ffbabSRob Johnston 	int err = 0, ret = -1;
5173c6ffbabSRob Johnston 
5183c6ffbabSRob Johnston 	/*
519*533affcbSRobert Mustacchi 	 * Pass the model and serial strings through a function that sanitizes
520*533affcbSRobert Mustacchi 	 * them of any characters that can't be used in an FMRI string. Note, we
521*533affcbSRobert Mustacchi 	 * do not use the firmware revision here because that's not really a
522*533affcbSRobert Mustacchi 	 * device property that should be part of the FMRI (it can be changed at
523*533affcbSRobert Mustacchi 	 * runtime).
5243c6ffbabSRob Johnston 	 */
525*533affcbSRobert Mustacchi 	model = topo_mod_clean_str(mod, nvme_ctrl_info_model(info));
526*533affcbSRobert Mustacchi 	serial = topo_mod_clean_str(mod, nvme_ctrl_info_serial(info));
5273c6ffbabSRob Johnston 
5283c6ffbabSRob Johnston 	auth = topo_mod_auth(mod, nvme_info->nei_parent);
5293c6ffbabSRob Johnston 	fmri = topo_mod_hcfmri(mod, nvme_info->nei_parent, FM_HC_SCHEME_VERSION,
530*533affcbSRobert Mustacchi 	    NVME, 0, NULL, auth, model, NULL, serial);
5313c6ffbabSRob Johnston 
5323c6ffbabSRob Johnston 	if (fmri == NULL) {
5333c6ffbabSRob Johnston 		/* errno set */
5346597d6fcSRobert Mustacchi 		topo_mod_dprintf(mod, "%s: hcfmri failed for %s=%" PRIu64
5356597d6fcSRobert Mustacchi 		    "/%s=0", __func__, pname, pinst, NVME);
5363c6ffbabSRob Johnston 		goto error;
5373c6ffbabSRob Johnston 	}
5383c6ffbabSRob Johnston 
5393c6ffbabSRob Johnston 	/*
5403c6ffbabSRob Johnston 	 * If our parent is a pciexfn node, then we need to create a nvme range
541744642a2SRobert Mustacchi 	 * underneath it to hold the nvme hierarchy.  For other cases, where
5423c6ffbabSRob Johnston 	 * enumeration is being driven by a topo map file, this range will have
5433c6ffbabSRob Johnston 	 * already been statically defined in the XML.
5443c6ffbabSRob Johnston 	 */
5453c6ffbabSRob Johnston 	if (strcmp(pname, PCIEX_FUNCTION) == 0) {
5463c6ffbabSRob Johnston 		if (topo_node_range_create(mod, nvme_info->nei_parent, NVME, 0,
5473c6ffbabSRob Johnston 		    0) < 0) {
5483c6ffbabSRob Johnston 			/* errno set */
5493c6ffbabSRob Johnston 			topo_mod_dprintf(mod, "%s: error creating %s range",
5503c6ffbabSRob Johnston 			    __func__, NVME);
5513c6ffbabSRob Johnston 			goto error;
5523c6ffbabSRob Johnston 		}
5533c6ffbabSRob Johnston 	}
5543c6ffbabSRob Johnston 
5553c6ffbabSRob Johnston 	/*
5563c6ffbabSRob Johnston 	 * Create a new topo node to represent the NVMe controller and bind it
5573c6ffbabSRob Johnston 	 * to the parent node.
5583c6ffbabSRob Johnston 	 */
5593c6ffbabSRob Johnston 	if ((nvme = topo_node_bind(mod, nvme_info->nei_parent, NVME, 0,
5603c6ffbabSRob Johnston 	    fmri)) == NULL) {
5613c6ffbabSRob Johnston 		/* errno set */
5626597d6fcSRobert Mustacchi 		topo_mod_dprintf(mod, "%s: bind failed for %s=%" PRIu64
5636597d6fcSRobert Mustacchi 		    "/%s=0", __func__, pname, pinst, NVME);
5643c6ffbabSRob Johnston 		goto error;
5653c6ffbabSRob Johnston 	}
5663c6ffbabSRob Johnston 	nvme_info->nei_nvme = nvme;
5673c6ffbabSRob Johnston 	nvme_info->nei_nvme_fmri = fmri;
5683c6ffbabSRob Johnston 
5693c6ffbabSRob Johnston 	/*
5703c6ffbabSRob Johnston 	 * If our parent node is a "pciexfn" node then this is a NVMe device on
5713c6ffbabSRob Johnston 	 * a PCIe AIC, so we inherit our parent's FRU.  Otherwise, we set the
5723c6ffbabSRob Johnston 	 * FRU to ourself.
5733c6ffbabSRob Johnston 	 */
5743c6ffbabSRob Johnston 	if (strcmp(topo_node_name(nvme_info->nei_parent), PCIEX_FUNCTION) == 0)
5753c6ffbabSRob Johnston 		fru = NULL;
5763c6ffbabSRob Johnston 	else
5773c6ffbabSRob Johnston 		fru = fmri;
5783c6ffbabSRob Johnston 
5793c6ffbabSRob Johnston 	if (topo_node_fru_set(nvme, fru, 0, &err) != 0) {
5803c6ffbabSRob Johnston 		topo_mod_dprintf(mod, "%s: failed to set FRU: %s", __func__,
5813c6ffbabSRob Johnston 		    topo_strerror(err));
5823c6ffbabSRob Johnston 		(void) topo_mod_seterrno(mod, err);
5833c6ffbabSRob Johnston 		goto error;
5843c6ffbabSRob Johnston 	}
5853c6ffbabSRob Johnston 
5863c6ffbabSRob Johnston 	/*
5873c6ffbabSRob Johnston 	 * Clone the label from our parent node.  We can't inherit the property
5883c6ffbabSRob Johnston 	 * because the label prop is mutable on bay nodes and only immutable
5893c6ffbabSRob Johnston 	 * properties can be inherited.
5903c6ffbabSRob Johnston 	 */
5913c6ffbabSRob Johnston 	if ((topo_node_label(nvme_info->nei_parent, &label, &err) != 0 &&
5923c6ffbabSRob Johnston 	    err != ETOPO_PROP_NOENT) ||
5933c6ffbabSRob Johnston 	    topo_node_label_set(nvme, label, &err) != 0) {
5943c6ffbabSRob Johnston 		topo_mod_dprintf(mod, "%s: failed to set label: %s",
5953c6ffbabSRob Johnston 		    __func__, topo_strerror(err));
5963c6ffbabSRob Johnston 		(void) topo_mod_seterrno(mod, err);
5973c6ffbabSRob Johnston 		goto error;
5983c6ffbabSRob Johnston 	}
5993c6ffbabSRob Johnston 
600744642a2SRobert Mustacchi 	/*
601744642a2SRobert Mustacchi 	 * Ensure that we have a UFM property set based on our devinfo path.
602744642a2SRobert Mustacchi 	 * This is a little repetitive if our parent actually did so as well,
603744642a2SRobert Mustacchi 	 * but given that the majority of such nodes are under bays and slots
604744642a2SRobert Mustacchi 	 * right now, it's a worthwhile tradeoff.
605744642a2SRobert Mustacchi 	 */
606744642a2SRobert Mustacchi 	disk_nvme_make_ufm(mod, nvme_info);
607744642a2SRobert Mustacchi 
6083c6ffbabSRob Johnston 	if (topo_pgroup_create(nvme, &nvme_pgroup, &err) != 0) {
6093c6ffbabSRob Johnston 		topo_mod_dprintf(mod, "%s: failed to create %s pgroup: %s",
6103c6ffbabSRob Johnston 		    __func__, TOPO_PGROUP_NVME, topo_strerror(err));
6113c6ffbabSRob Johnston 		(void) topo_mod_seterrno(mod, err);
6123c6ffbabSRob Johnston 		goto error;
6133c6ffbabSRob Johnston 	}
6143c6ffbabSRob Johnston 
615*533affcbSRobert Mustacchi 	if (asprintf(&vers, "%u.%u", nvme_info->nei_vers->v_major,
616*533affcbSRobert Mustacchi 	    nvme_info->nei_vers->v_minor) < 0) {
6173c6ffbabSRob Johnston 		topo_mod_dprintf(mod, "%s: failed to alloc string", __func__);
6183c6ffbabSRob Johnston 		(void) topo_mod_seterrno(mod, EMOD_NOMEM);
6193c6ffbabSRob Johnston 		goto error;
6203c6ffbabSRob Johnston 	}
6213c6ffbabSRob Johnston 	if (topo_prop_set_string(nvme, TOPO_PGROUP_NVME, TOPO_PROP_NVME_VER,
6223c6ffbabSRob Johnston 	    TOPO_PROP_IMMUTABLE, vers, &err) != 0) {
6233c6ffbabSRob Johnston 		topo_mod_dprintf(mod, "%s: failed to set %s/%s property",
6243c6ffbabSRob Johnston 		    __func__, TOPO_PGROUP_NVME, TOPO_PROP_NVME_VER);
6253c6ffbabSRob Johnston 		(void) topo_mod_seterrno(mod, err);
6263c6ffbabSRob Johnston 		goto error;
6273c6ffbabSRob Johnston 	}
6283c6ffbabSRob Johnston 
6293c6ffbabSRob Johnston 	if (topo_pgroup_create(nvme, &io_pgroup, &err) != 0) {
6303c6ffbabSRob Johnston 		topo_mod_dprintf(mod, "%s: failed to create %s pgroup: %s",
6313c6ffbabSRob Johnston 		    __func__, TOPO_PGROUP_IO, topo_strerror(err));
6323c6ffbabSRob Johnston 		(void) topo_mod_seterrno(mod, err);
6333c6ffbabSRob Johnston 		goto error;
6343c6ffbabSRob Johnston 	}
635744642a2SRobert Mustacchi 
636744642a2SRobert Mustacchi 	if (!disk_nvme_common_io(mod, nvme, nvme_info->nei_dinode)) {
6373c6ffbabSRob Johnston 		goto error;
6383c6ffbabSRob Johnston 	}
6393c6ffbabSRob Johnston 
6403c6ffbabSRob Johnston 	/*
6413c6ffbabSRob Johnston 	 * Create a child disk node for each namespace.
6423c6ffbabSRob Johnston 	 */
6433c6ffbabSRob Johnston 	if (topo_node_range_create(mod, nvme, DISK, 0,
644*533affcbSRobert Mustacchi 	    nvme_ctrl_info_nns(info) - 1) < 0) {
6453c6ffbabSRob Johnston 		/* errno set */
6463c6ffbabSRob Johnston 		topo_mod_dprintf(mod, "%s: error creating %s range", __func__,
6473c6ffbabSRob Johnston 		    DISK);
6483c6ffbabSRob Johnston 		goto error;
6493c6ffbabSRob Johnston 	}
6503c6ffbabSRob Johnston 
6513c6ffbabSRob Johnston 	/*
652744642a2SRobert Mustacchi 	 * Iterate over each namespace to see if it's a candidate for inclusion.
653744642a2SRobert Mustacchi 	 * Namespaces start at index 1 and not every namespace will be included.
654744642a2SRobert Mustacchi 	 * We map things such that a disk instance is always namespace - 1 to
655744642a2SRobert Mustacchi 	 * fit into the above mapping.
6563c6ffbabSRob Johnston 	 */
657*533affcbSRobert Mustacchi 	if (!nvme_ns_discover_init(nvme_info->nei_ctrl,
658*533affcbSRobert Mustacchi 	    NVME_NS_DISC_F_NOT_IGNORED, &iter)) {
659*533affcbSRobert Mustacchi 		topo_mod_dprintf(mod, "failed to initialize namespace "
660*533affcbSRobert Mustacchi 		    "discovery: %s", nvme_errmsg(nvme_info->nei_libnvme));
661*533affcbSRobert Mustacchi 		ret = topo_mod_seterrno(mod, EMOD_UNKNOWN);
662*533affcbSRobert Mustacchi 		goto error;
663*533affcbSRobert Mustacchi 	}
664*533affcbSRobert Mustacchi 
665*533affcbSRobert Mustacchi 	for (nret = nvme_ns_discover_step(iter, &disc); nret == NVME_ITER_VALID;
666*533affcbSRobert Mustacchi 	    nret = nvme_ns_discover_step(iter, &disc)) {
667*533affcbSRobert Mustacchi 		nvme_ns_info_t *ns_info;
668*533affcbSRobert Mustacchi 		uint32_t nsid = nvme_ns_disc_nsid(disc);
669*533affcbSRobert Mustacchi 
670*533affcbSRobert Mustacchi 		if (!nvme_ctrl_ns_info_snap(nvme_info->nei_ctrl, nsid,
671*533affcbSRobert Mustacchi 		    &ns_info)) {
672*533affcbSRobert Mustacchi 			topo_mod_dprintf(mod, "failed to get namespace "
673*533affcbSRobert Mustacchi 			    "information for ns %u: %s", nsid,
674*533affcbSRobert Mustacchi 			    nvme_errmsg(nvme_info->nei_libnvme));
675*533affcbSRobert Mustacchi 			ret = topo_mod_seterrno(mod, EMOD_UNKNOWN);
676*533affcbSRobert Mustacchi 			goto error;
677*533affcbSRobert Mustacchi 		}
678*533affcbSRobert Mustacchi 
679*533affcbSRobert Mustacchi 		disk_nvme_make_ns(nvme_info, ns_info);
680*533affcbSRobert Mustacchi 		nvme_ns_info_free(ns_info);
681*533affcbSRobert Mustacchi 	}
682*533affcbSRobert Mustacchi 
683*533affcbSRobert Mustacchi 	if (nret == NVME_ITER_ERROR) {
684*533affcbSRobert Mustacchi 		topo_mod_dprintf(mod, "namespace discovery failed: %s",
685*533affcbSRobert Mustacchi 		    nvme_errmsg(nvme_info->nei_libnvme));
686*533affcbSRobert Mustacchi 		ret = topo_mod_seterrno(mod, EMOD_UNKNOWN);
6873c6ffbabSRob Johnston 	}
6883c6ffbabSRob Johnston 	ret = 0;
6893c6ffbabSRob Johnston 
6903c6ffbabSRob Johnston error:
691*533affcbSRobert Mustacchi 	nvme_ns_discover_fini(iter);
6923c6ffbabSRob Johnston 	free(vers);
6933c6ffbabSRob Johnston 	nvlist_free(auth);
6943c6ffbabSRob Johnston 	nvlist_free(fmri);
6953c6ffbabSRob Johnston 	topo_mod_strfree(mod, model);
6963c6ffbabSRob Johnston 	topo_mod_strfree(mod, serial);
6973c6ffbabSRob Johnston 	topo_mod_strfree(mod, label);
6983c6ffbabSRob Johnston 	return (ret);
6993c6ffbabSRob Johnston }
7003c6ffbabSRob Johnston 
7013c6ffbabSRob Johnston /*
7023c6ffbabSRob Johnston  * This function gathers identity information from the NVMe controller and
7033c6ffbabSRob Johnston  * stores it in a struct.  This struct is passed to make_nvme_node(), which
7043c6ffbabSRob Johnston  * does the actual topo node creation.
7053c6ffbabSRob Johnston  */
7063c6ffbabSRob Johnston static int
discover_nvme_ctl(topo_mod_t * mod,tnode_t * pnode,di_node_t dinode)707*533affcbSRobert Mustacchi discover_nvme_ctl(topo_mod_t *mod, tnode_t *pnode, di_node_t dinode)
7083c6ffbabSRob Johnston {
709*533affcbSRobert Mustacchi 	topo_disk_t *disk = topo_mod_getspecific(mod);
7103c6ffbabSRob Johnston 	nvme_enum_info_t nvme_info = { 0 };
711*533affcbSRobert Mustacchi 	int ret;
7123c6ffbabSRob Johnston 
7133c6ffbabSRob Johnston 	nvme_info.nei_mod = mod;
714*533affcbSRobert Mustacchi 	nvme_info.nei_dinode = dinode;
715*533affcbSRobert Mustacchi 	nvme_info.nei_parent = pnode;
716*533affcbSRobert Mustacchi 	nvme_info.nei_libnvme = disk->td_nvme;
7173c6ffbabSRob Johnston 
718*533affcbSRobert Mustacchi 	if (!nvme_ctrl_init(disk->td_nvme, dinode, &nvme_info.nei_ctrl)) {
719*533affcbSRobert Mustacchi 		topo_mod_dprintf(mod, "failed to initialize nvme_ctrl_t: %s",
720*533affcbSRobert Mustacchi 		    nvme_errmsg(disk->td_nvme));
721*533affcbSRobert Mustacchi 		return (topo_mod_seterrno(mod, EMOD_UNKNOWN));
722*533affcbSRobert Mustacchi 	}
723*533affcbSRobert Mustacchi 
724*533affcbSRobert Mustacchi 	if (!nvme_ctrl_info_snap(nvme_info.nei_ctrl,
725*533affcbSRobert Mustacchi 	    &nvme_info.nei_ctrl_info)) {
726*533affcbSRobert Mustacchi 		topo_mod_dprintf(mod, "failed to initialize nvme_ctrl_t: %s",
727*533affcbSRobert Mustacchi 		    nvme_errmsg(disk->td_nvme));
728*533affcbSRobert Mustacchi 		ret = topo_mod_seterrno(mod, EMOD_UNKNOWN);
7293c6ffbabSRob Johnston 		goto error;
7303c6ffbabSRob Johnston 	}
7313c6ffbabSRob Johnston 
732*533affcbSRobert Mustacchi 	nvme_info.nei_vers = nvme_ctrl_info_version(nvme_info.nei_ctrl_info);
733*533affcbSRobert Mustacchi 
734*533affcbSRobert Mustacchi 	if ((ret = make_nvme_node(&nvme_info)) != 0) {
735*533affcbSRobert Mustacchi 		goto error;
736*533affcbSRobert Mustacchi 	}
7373c6ffbabSRob Johnston 
7383c6ffbabSRob Johnston error:
739*533affcbSRobert Mustacchi 	if (nvme_info.nei_ctrl_info != NULL)
740*533affcbSRobert Mustacchi 		nvme_ctrl_info_free(nvme_info.nei_ctrl_info);
741*533affcbSRobert Mustacchi 	if (nvme_info.nei_ctrl != NULL)
742*533affcbSRobert Mustacchi 		nvme_ctrl_fini(nvme_info.nei_ctrl);
7433c6ffbabSRob Johnston 	return (ret);
7443c6ffbabSRob Johnston }
7453c6ffbabSRob Johnston 
7463c6ffbabSRob Johnston int
disk_nvme_enum_disk(topo_mod_t * mod,tnode_t * pnode)7473c6ffbabSRob Johnston disk_nvme_enum_disk(topo_mod_t *mod, tnode_t *pnode)
7483c6ffbabSRob Johnston {
7493c6ffbabSRob Johnston 	char *parent = NULL;
7503c6ffbabSRob Johnston 	int err;
7513c6ffbabSRob Johnston 	di_node_t devtree;
7523c6ffbabSRob Johnston 	di_node_t dnode;
7533c6ffbabSRob Johnston 	int ret = -1;
7543c6ffbabSRob Johnston 
7553c6ffbabSRob Johnston 	/*
7563c6ffbabSRob Johnston 	 * Lookup a property containing the devfs path of the parent PCIe
7573c6ffbabSRob Johnston 	 * device of the NVMe device we're attempting to enumerate.  This
7583c6ffbabSRob Johnston 	 * property is hard-coded in per-platform topo XML maps that are
7593c6ffbabSRob Johnston 	 * delivered with the OS.  This hard-coded path allows topo to map a
7603c6ffbabSRob Johnston 	 * given NVMe controller to a physical location (bay or slot) on the
7613c6ffbabSRob Johnston 	 * platform, when generating the topo snapshot.
7623c6ffbabSRob Johnston 	 */
7633c6ffbabSRob Johnston 	if (topo_prop_get_string(pnode, TOPO_PGROUP_BINDING,
7643c6ffbabSRob Johnston 	    TOPO_BINDING_PARENT_DEV, &parent, &err) != 0) {
7653c6ffbabSRob Johnston 		topo_mod_dprintf(mod, "parent node was missing nvme binding "
7663c6ffbabSRob Johnston 		    "properties\n");
7673c6ffbabSRob Johnston 		(void) topo_mod_seterrno(mod, err);
7683c6ffbabSRob Johnston 		goto out;
7693c6ffbabSRob Johnston 	}
7703c6ffbabSRob Johnston 	if ((devtree = topo_mod_devinfo(mod)) == DI_NODE_NIL) {
7713c6ffbabSRob Johnston 		topo_mod_dprintf(mod, "failed to get devinfo snapshot");
7723c6ffbabSRob Johnston 		(void) topo_mod_seterrno(mod, EMOD_UNKNOWN);
7733c6ffbabSRob Johnston 		goto out;
7743c6ffbabSRob Johnston 	}
7753c6ffbabSRob Johnston 
7763c6ffbabSRob Johnston 	/*
7773c6ffbabSRob Johnston 	 * Walk the devinfo tree looking NVMe devices. For each NVMe device,
7783c6ffbabSRob Johnston 	 * check if the devfs path of the parent matches the one specified in
7793c6ffbabSRob Johnston 	 * TOPO_BINDING_PARENT_DEV.
7803c6ffbabSRob Johnston 	 */
7813c6ffbabSRob Johnston 	dnode = di_drv_first_node(NVME_DRV, devtree);
7823c6ffbabSRob Johnston 	while (dnode != DI_NODE_NIL) {
7833c6ffbabSRob Johnston 		char *path;
7843c6ffbabSRob Johnston 
7853c6ffbabSRob Johnston 		if ((path = di_devfs_path(di_parent_node(dnode))) == NULL) {
7863c6ffbabSRob Johnston 			topo_mod_dprintf(mod, "failed to get dev path");
7873c6ffbabSRob Johnston 			(void) topo_mod_seterrno(mod, EMOD_UNKNOWN);
7883c6ffbabSRob Johnston 			goto out;
7893c6ffbabSRob Johnston 		}
7903c6ffbabSRob Johnston 		if (strcmp(parent, path) == 0) {
791*533affcbSRobert Mustacchi 			ret = discover_nvme_ctl(mod, pnode, dnode);
7923c6ffbabSRob Johnston 			di_devfs_path_free(path);
7933c6ffbabSRob Johnston 			goto out;
7943c6ffbabSRob Johnston 		}
7953c6ffbabSRob Johnston 		di_devfs_path_free(path);
7963c6ffbabSRob Johnston 		dnode = di_drv_next_node(dnode);
7973c6ffbabSRob Johnston 	}
7983c6ffbabSRob Johnston 	ret = 0;
7993c6ffbabSRob Johnston 
8003c6ffbabSRob Johnston out:
8013c6ffbabSRob Johnston 	topo_mod_strfree(mod, parent);
8023c6ffbabSRob Johnston 	return (ret);
8033c6ffbabSRob Johnston }
804