1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 /*
30  * Functions in this file are shared between the disk and ses enumerators.
31  *
32  * A topo_list_t of all disks is returned by a successful disk_list_gather()
33  * call, and the list is freed by a disk_list_free(). To create a 'disk' topo
34  * node below a specific 'bay' parent node either disk_declare_path() or
35  * disk_declare_addr() are called. The caller determines which 'disk' is
36  * in which 'bay'. A disk's 'label' and 'authority' information come from
37  * its parent 'bay' node.
38  */
39 
40 #include <strings.h>
41 #include <libdevinfo.h>
42 #include <devid.h>
43 #include <sys/libdevid.h>
44 #include <pthread.h>
45 #include <inttypes.h>
46 #include <sys/dkio.h>
47 #include <sys/scsi/scsi_types.h>
48 #include <fm/topo_mod.h>
49 #include <fm/topo_list.h>
50 #include <fm/libdiskstatus.h>
51 #include <sys/fm/protocol.h>
52 #include "disk.h"
53 
54 /*
55  * disk node information.
56  */
57 typedef struct disk_di_node {
58 	topo_list_t	ddn_list;	/* list of disks */
59 
60 	/* the following two fields are always defined */
61 	char		*ddn_devid;	/* devid of disk */
62 	char		*ddn_dpath;	/* path to devinfo (may be vhci) */
63 	char		**ddn_ppath;	/* physical path to device (phci) */
64 	int		ddn_ppath_count;
65 
66 	char		*ddn_lpath;	/* logical path (public /dev name) */
67 
68 	char		*ddn_mfg;	/* misc information about device */
69 	char		*ddn_model;
70 	char		*ddn_serial;
71 	char		*ddn_firm;
72 	char		*ddn_cap;
73 
74 	char		**ddn_target_port;
75 	int		ddn_target_port_count;
76 } disk_di_node_t;
77 
78 /* common callback information for di_walk_node() and di_devlink_walk */
79 typedef struct disk_cbdata {
80 	topo_mod_t		*dcb_mod;
81 	topo_list_t		*dcb_list;
82 
83 	di_devlink_handle_t	dcb_devhdl;
84 	disk_di_node_t		*dcb_dnode;	/* for di_devlink_walk only */
85 } disk_cbdata_t;
86 
87 /*
88  * Given a /devices path for a whole disk, appending this extension gives the
89  * path to a raw device that can be opened.
90  */
91 #if defined(__i386) || defined(__amd64)
92 #define	PHYS_EXTN	":q,raw"
93 #elif defined(__sparc) || defined(__sparcv9)
94 #define	PHYS_EXTN	":c,raw"
95 #else
96 #error	Unknown architecture
97 #endif
98 
99 /*
100  * Methods for disks. This is used by the disk-transport module to
101  * generate ereports based off SCSI disk status.
102  */
103 static int disk_status(topo_mod_t *, tnode_t *, topo_version_t,
104 	nvlist_t *, nvlist_t **);
105 
106 static const topo_method_t disk_methods[] = {
107 	{ TOPO_METH_DISK_STATUS, TOPO_METH_DISK_STATUS_DESC,
108 	    TOPO_METH_DISK_STATUS_VERSION, TOPO_STABILITY_INTERNAL,
109 	    disk_status },
110 	{ NULL }
111 };
112 
113 static const topo_pgroup_info_t io_pgroup = {
114 	TOPO_PGROUP_IO,
115 	TOPO_STABILITY_PRIVATE,
116 	TOPO_STABILITY_PRIVATE,
117 	1
118 };
119 
120 static const topo_pgroup_info_t disk_auth_pgroup = {
121 	FM_FMRI_AUTHORITY,
122 	TOPO_STABILITY_PRIVATE,
123 	TOPO_STABILITY_PRIVATE,
124 	1
125 };
126 
127 static const topo_pgroup_info_t storage_pgroup = {
128 	TOPO_PGROUP_STORAGE,
129 	TOPO_STABILITY_PRIVATE,
130 	TOPO_STABILITY_PRIVATE,
131 	1
132 };
133 
134 /*
135  * Set the properties of the disk node, from disk_di_node_t data.
136  * Properties include:
137  *	group: protocol	 properties: resource, asru, label, fru
138  *	group: authority properties: product-id, chasis-id, server-id
139  *	group: io	 properties: devfs-path, devid
140  *	group: storage	 properties:
141  *		- logical-disk, disk-model, disk-manufacturer, serial-number
142  *		- firmware-revision, capacity-in-bytes
143  */
144 static int
145 disk_set_props(topo_mod_t *mod, tnode_t *parent,
146     tnode_t *dtn, disk_di_node_t *dnode)
147 {
148 	nvlist_t	*asru = NULL;
149 	char		*label = NULL;
150 	nvlist_t	*fmri = NULL;
151 	int		err;
152 
153 	/* form and set the asru */
154 	if ((asru = topo_mod_devfmri(mod, FM_DEV_SCHEME_VERSION,
155 	    dnode->ddn_dpath, dnode->ddn_devid)) == NULL) {
156 		err = ETOPO_FMRI_UNKNOWN;
157 		topo_mod_dprintf(mod, "disk_set_props: "
158 		    "asru error %s\n", topo_strerror(err));
159 		goto error;
160 	}
161 	if (topo_node_asru_set(dtn, asru, 0, &err) != 0) {
162 		topo_mod_dprintf(mod, "disk_set_props: "
163 		    "asru_set error %s\n", topo_strerror(err));
164 		goto error;
165 	}
166 
167 	/* pull the label property down from our parent 'bay' node */
168 	if (topo_node_label(parent, &label, &err) != 0) {
169 		topo_mod_dprintf(mod, "disk_set_props: "
170 		    "label error %s\n", topo_strerror(err));
171 		goto error;
172 	}
173 	if (topo_node_label_set(dtn, label, &err) != 0) {
174 		topo_mod_dprintf(mod, "disk_set_props: "
175 		    "label_set error %s\n", topo_strerror(err));
176 		goto error;
177 	}
178 
179 	/* get the resource fmri, and use it as the fru */
180 	if (topo_node_resource(dtn, &fmri, &err) != 0) {
181 		topo_mod_dprintf(mod, "disk_set_props: "
182 		    "resource error: %s\n", topo_strerror(err));
183 		goto error;
184 	}
185 	if (topo_node_fru_set(dtn, fmri, 0, &err) != 0) {
186 		topo_mod_dprintf(mod, "disk_set_props: "
187 		    "fru_set error: %s\n", topo_strerror(err));
188 		goto error;
189 	}
190 
191 	/* create/set the authority group */
192 	if ((topo_pgroup_create(dtn, &disk_auth_pgroup, &err) != 0) &&
193 	    (err != ETOPO_PROP_DEFD)) {
194 		topo_mod_dprintf(mod, "disk_set_props: "
195 		    "create disk_auth error %s\n", topo_strerror(err));
196 		goto error;
197 	}
198 
199 	/* create/set the devfs-path and devid in the io group */
200 	if (topo_pgroup_create(dtn, &io_pgroup, &err) != 0) {
201 		topo_mod_dprintf(mod, "disk_set_props: "
202 		    "create io error %s\n", topo_strerror(err));
203 		goto error;
204 	}
205 
206 	if (topo_prop_set_string(dtn, TOPO_PGROUP_IO, TOPO_IO_DEV_PATH,
207 	    TOPO_PROP_IMMUTABLE, dnode->ddn_dpath, &err) != 0) {
208 		topo_mod_dprintf(mod, "disk_set_props: "
209 		    "set dev error %s\n", topo_strerror(err));
210 		goto error;
211 	}
212 
213 	if (topo_prop_set_string(dtn, TOPO_PGROUP_IO, TOPO_IO_DEVID,
214 	    TOPO_PROP_IMMUTABLE, dnode->ddn_devid, &err) != 0) {
215 		topo_mod_dprintf(mod, "disk_set_props: "
216 		    "set devid error %s\n", topo_strerror(err));
217 		goto error;
218 	}
219 
220 	if (dnode->ddn_ppath_count != 0 &&
221 	    topo_prop_set_string_array(dtn, TOPO_PGROUP_IO, TOPO_IO_PHYS_PATH,
222 	    TOPO_PROP_IMMUTABLE, (const char **)dnode->ddn_ppath,
223 	    dnode->ddn_ppath_count, &err) != 0) {
224 		topo_mod_dprintf(mod, "disk_set_props: "
225 		    "set phys-path error %s\n", topo_strerror(err));
226 		goto error;
227 	}
228 
229 	/* create the storage group */
230 	if (topo_pgroup_create(dtn, &storage_pgroup, &err) != 0) {
231 		topo_mod_dprintf(mod, "disk_set_props: "
232 		    "create storage error %s\n", topo_strerror(err));
233 		goto error;
234 	}
235 
236 	/* set the storage group public /dev name */
237 	if (topo_prop_set_string(dtn, TOPO_PGROUP_STORAGE,
238 	    TOPO_STORAGE_LOGICAL_DISK_NAME, TOPO_PROP_IMMUTABLE,
239 	    dnode->ddn_lpath, &err) != 0) {
240 		topo_mod_dprintf(mod, "disk_set_props: "
241 		    "set disk_name error %s\n", topo_strerror(err));
242 		goto error;
243 	}
244 
245 	/* populate other misc storage group properties */
246 	if (dnode->ddn_mfg && (topo_prop_set_string(dtn, TOPO_PGROUP_STORAGE,
247 	    TOPO_STORAGE_MANUFACTURER, TOPO_PROP_IMMUTABLE,
248 	    dnode->ddn_mfg, &err) != 0)) {
249 		topo_mod_dprintf(mod, "disk_set_props: "
250 		    "set mfg error %s\n", topo_strerror(err));
251 		goto error;
252 	}
253 	if (dnode->ddn_model && (topo_prop_set_string(dtn, TOPO_PGROUP_STORAGE,
254 	    TOPO_STORAGE_MODEL, TOPO_PROP_IMMUTABLE,
255 	    dnode->ddn_model, &err) != 0)) {
256 		topo_mod_dprintf(mod, "disk_set_props: "
257 		    "set model error %s\n", topo_strerror(err));
258 		goto error;
259 	}
260 	if (dnode->ddn_serial && (topo_prop_set_string(dtn, TOPO_PGROUP_STORAGE,
261 	    TOPO_STORAGE_SERIAL_NUM, TOPO_PROP_IMMUTABLE,
262 	    dnode->ddn_serial, &err) != 0)) {
263 		topo_mod_dprintf(mod, "disk_set_props: "
264 		    "set serial error %s\n", topo_strerror(err));
265 		goto error;
266 	}
267 	if (dnode->ddn_firm && (topo_prop_set_string(dtn, TOPO_PGROUP_STORAGE,
268 	    TOPO_STORAGE_FIRMWARE_REV, TOPO_PROP_IMMUTABLE,
269 	    dnode->ddn_firm, &err) != 0)) {
270 		topo_mod_dprintf(mod, "disk_set_props: "
271 		    "set firm error %s\n", topo_strerror(err));
272 		goto error;
273 	}
274 	if (dnode->ddn_cap && (topo_prop_set_string(dtn, TOPO_PGROUP_STORAGE,
275 	    TOPO_STORAGE_CAPACITY, TOPO_PROP_IMMUTABLE,
276 	    dnode->ddn_cap, &err) != 0)) {
277 		topo_mod_dprintf(mod, "disk_set_props: "
278 		    "set cap error %s\n", topo_strerror(err));
279 		goto error;
280 	}
281 	err = 0;
282 
283 out:	if (fmri)
284 		nvlist_free(fmri);
285 	if (label)
286 		topo_mod_strfree(mod, label);
287 	if (asru)
288 		nvlist_free(asru);
289 	return (err);
290 
291 error:	err = topo_mod_seterrno(mod, err);
292 	goto out;
293 }
294 
295 /* create the disk topo node */
296 static tnode_t *
297 disk_tnode_create(topo_mod_t *mod, tnode_t *parent,
298     disk_di_node_t *dnode, const char *name, topo_instance_t i)
299 {
300 	int		len;
301 	nvlist_t	*fmri;
302 	tnode_t		*dtn;
303 	char		*part = NULL;
304 	char		*b = NULL;
305 	nvlist_t	*auth;
306 
307 	/* form 'part=' of fmri as "<mfg>-<model>" */
308 	if (dnode->ddn_mfg && dnode->ddn_model) {
309 		/* XXX replace first ' ' in the model with a '-' */
310 		if ((b = strchr(dnode->ddn_model, ' ')) != NULL)
311 			*b = '-';
312 		len = strlen(dnode->ddn_mfg) + 1 + strlen(dnode->ddn_model) + 1;
313 		if ((part = topo_mod_alloc(mod, len)) != NULL)
314 			(void) snprintf(part, len, "%s-%s",
315 			    dnode->ddn_mfg, dnode->ddn_model);
316 	}
317 
318 	auth = topo_mod_auth(mod, parent);
319 	fmri = topo_mod_hcfmri(mod, parent, FM_HC_SCHEME_VERSION, name, i, NULL,
320 	    auth, part ? part : dnode->ddn_model, dnode->ddn_firm,
321 	    dnode->ddn_serial);
322 	nvlist_free(auth);
323 
324 	if (part && (part != dnode->ddn_model))
325 		topo_mod_free(mod, part, len);
326 	else if (b)
327 		*b = ' ';		/* restore blank */
328 
329 	if (fmri == NULL) {
330 		topo_mod_dprintf(mod, "disk_tnode_create: "
331 		    "hcfmri (%s%d/%s%d) error %s\n",
332 		    topo_node_name(parent), topo_node_instance(parent),
333 		    name, i, topo_strerror(topo_mod_errno(mod)));
334 		return (NULL);
335 	}
336 
337 	if ((dtn = topo_node_bind(mod, parent, name, i, fmri)) == NULL) {
338 		topo_mod_dprintf(mod, "disk_tnode_create: "
339 		    "bind (%s%d/%s%d) error %s\n",
340 		    topo_node_name(parent), topo_node_instance(parent),
341 		    name, i, topo_strerror(topo_mod_errno(mod)));
342 		nvlist_free(fmri);
343 		return (NULL);
344 	}
345 	nvlist_free(fmri);
346 
347 	/* add the properties of the disk */
348 	if (disk_set_props(mod, parent, dtn, dnode) != 0) {
349 		topo_mod_dprintf(mod, "disk_tnode_create: "
350 		    "disk_set_props (%s%d/%s%d) error %s\n",
351 		    topo_node_name(parent), topo_node_instance(parent),
352 		    name, i, topo_strerror(topo_mod_errno(mod)));
353 		topo_node_unbind(dtn);
354 		return (NULL);
355 	}
356 	return (dtn);
357 }
358 
359 static int
360 disk_declare(topo_mod_t *mod, tnode_t *parent, disk_di_node_t *dnode)
361 {
362 	tnode_t		*dtn;
363 
364 	/* create the disk topo node: one disk per 'bay' */
365 	dtn = disk_tnode_create(mod, parent, dnode, DISK, 0);
366 	if (dtn == NULL) {
367 		topo_mod_dprintf(mod, "disk_declare: "
368 		    "disk_tnode_create error %s\n",
369 		    topo_strerror(topo_mod_errno(mod)));
370 		return (-1);
371 	}
372 
373 	/* register disk_methods against the disk topo node */
374 	if (topo_method_register(mod, dtn, disk_methods) != 0) {
375 		topo_mod_dprintf(mod, "disk_declare: "
376 		    "topo_method_register error %s\n",
377 		    topo_strerror(topo_mod_errno(mod)));
378 		topo_node_unbind(dtn);
379 		return (-1);
380 	}
381 	return (0);
382 }
383 
384 int
385 disk_declare_path(topo_mod_t *mod, tnode_t *parent, topo_list_t *listp,
386     const char *path)
387 {
388 	disk_di_node_t		*dnode;
389 	int i;
390 
391 	/*
392 	 * Check for match using physical phci (ddn_ppath). Use
393 	 * di_devfs_path_match so generic.vs.non-generic names match.
394 	 */
395 	for (dnode = topo_list_next(listp); dnode != NULL;
396 	    dnode = topo_list_next(dnode)) {
397 		if (dnode->ddn_ppath == NULL)
398 			continue;
399 
400 		for (i = 0; i < dnode->ddn_ppath_count; i++) {
401 			if (di_devfs_path_match(dnode->ddn_ppath[0], path))
402 				return (disk_declare(mod, parent, dnode));
403 		}
404 	}
405 
406 	topo_mod_dprintf(mod, "disk_declare_path: "
407 	    "failed to find disk matching path %s", path);
408 	return (0);
409 }
410 
411 int
412 disk_declare_addr(topo_mod_t *mod, tnode_t *parent, topo_list_t *listp,
413     const char *addr)
414 {
415 	disk_di_node_t *dnode;
416 	int i;
417 
418 	/* Check for match using addr. */
419 	for (dnode = topo_list_next(listp); dnode != NULL;
420 	    dnode = topo_list_next(dnode)) {
421 		if (dnode->ddn_target_port == NULL)
422 			continue;
423 
424 		for (i = 0; i < dnode->ddn_target_port_count; i++) {
425 			if (strncmp(dnode->ddn_target_port[i], addr,
426 			    strcspn(dnode->ddn_target_port[i], ":")) == 0)
427 				return (disk_declare(mod, parent, dnode));
428 		}
429 	}
430 
431 	topo_mod_dprintf(mod, "disk_declare_addr: "
432 	    "failed to find disk matching addr %s", addr);
433 	return (0);
434 }
435 
436 /* di_devlink callback for disk_di_node_add */
437 static int
438 disk_devlink_callback(di_devlink_t dl, void *arg)
439 {
440 	disk_cbdata_t	*cbp = (disk_cbdata_t *)arg;
441 	topo_mod_t	*mod = cbp->dcb_mod;
442 	disk_di_node_t	*dnode = cbp->dcb_dnode;
443 	const char	*devpath;
444 	char		*ctds, *slice;
445 
446 	devpath = di_devlink_path(dl);
447 	if ((dnode == NULL) || (devpath == NULL))
448 		return (DI_WALK_TERMINATE);
449 
450 	/* trim the slice off the public name */
451 	if (((ctds = strrchr(devpath, '/')) != NULL) &&
452 	    ((slice = strchr(ctds, 's')) != NULL))
453 		*slice = '\0';
454 
455 	/* Establish the public /dev name (no slice) */
456 	dnode->ddn_lpath = topo_mod_strdup(mod, ctds ? ctds + 1 : devpath);
457 
458 	if (ctds && slice)
459 		*slice = 's';
460 	return (DI_WALK_TERMINATE);
461 }
462 
463 static void
464 disk_di_node_free(topo_mod_t *mod, disk_di_node_t *dnode)
465 {
466 	int i;
467 
468 	/* free the stuff we point to */
469 	topo_mod_strfree(mod, dnode->ddn_devid);
470 	for (i = 0; i < dnode->ddn_ppath_count; i++)
471 		topo_mod_strfree(mod, dnode->ddn_ppath[i]);
472 	topo_mod_free(mod, dnode->ddn_ppath,
473 	    dnode->ddn_ppath_count * sizeof (uintptr_t));
474 	topo_mod_strfree(mod, dnode->ddn_dpath);
475 	topo_mod_strfree(mod, dnode->ddn_lpath);
476 
477 	topo_mod_strfree(mod, dnode->ddn_mfg);
478 	topo_mod_strfree(mod, dnode->ddn_model);
479 	topo_mod_strfree(mod, dnode->ddn_serial);
480 	topo_mod_strfree(mod, dnode->ddn_firm);
481 	topo_mod_strfree(mod, dnode->ddn_cap);
482 
483 	for (i = 0; i < dnode->ddn_target_port_count; i++)
484 		topo_mod_strfree(mod, dnode->ddn_target_port[i]);
485 	topo_mod_free(mod, dnode->ddn_target_port,
486 	    dnode->ddn_target_port_count * sizeof (uintptr_t));
487 
488 	/* free self */
489 	topo_mod_free(mod, dnode, sizeof (disk_di_node_t));
490 }
491 
492 static int
493 disk_di_node_add(di_node_t node, char *devid, disk_cbdata_t *cbp)
494 {
495 	topo_mod_t	*mod = cbp->dcb_mod;
496 	disk_di_node_t	*dnode;
497 	di_path_t	pnode;
498 	char		*path;
499 	int		mlen;
500 	char		*minorpath;
501 	char		*extn = ":a";
502 	char		*s;
503 	int64_t		*nblocksp;
504 	uint64_t	nblocks;
505 	int		*dblksizep;
506 	uint_t		dblksize;
507 	char		lentry[MAXPATHLEN];
508 	int		pathcount, portcount;
509 	int 		ret, i;
510 
511 	/* check for list duplicate using devid search */
512 	for (dnode = topo_list_next(cbp->dcb_list);
513 	    dnode != NULL; dnode = topo_list_next(dnode)) {
514 		if (devid_str_compare(dnode->ddn_devid, devid) == 0) {
515 			topo_mod_dprintf(mod, "disk_di_node_add: "
516 			    "already there %s\n", devid);
517 			return (0);
518 		}
519 	}
520 
521 	if ((dnode = topo_mod_zalloc(mod, sizeof (disk_di_node_t))) == NULL)
522 		return (-1);
523 
524 	/* Establish the devid. */
525 	dnode->ddn_devid = topo_mod_strdup(mod, devid);
526 	if (dnode->ddn_devid == NULL)
527 		goto error;
528 
529 	/* Establish the devinfo dpath */
530 	if ((path = di_devfs_path(node)) == NULL) {
531 		topo_mod_seterrno(mod, errno);
532 		goto error;
533 	}
534 
535 	dnode->ddn_dpath = topo_mod_strdup(mod, path);
536 	di_devfs_path_free(path);
537 	if (dnode->ddn_dpath == NULL)
538 		goto error;
539 
540 	/*
541 	 * Establish the physical ppath and target ports. If the device is
542 	 * non-mpxio then dpath and ppath are the same, and the target port is a
543 	 * property of the device node.
544 	 *
545 	 * If dpath is a client node under scsi_vhci, then iterate over all
546 	 * paths and get their physical paths and target port properrties.
547 	 * di_path_client_next_path call below will
548 	 * return non-NULL, and ppath is set to the physical path to the first
549 	 * pathinfo node.
550 	 *
551 	 * NOTE: It is possible to get a generic.vs.non-generic path
552 	 * for di_devfs_path.vs.di_path_devfs_path like:
553 	 *    xml: /pci@7b,0/pci1022,7458@11/pci1000,3060@2/sd@2,0
554 	 *  pnode: /pci@7b,0/pci1022,7458@11/pci1000,3060@2/disk@2,0
555 	 * To resolve this issue disk_declare_path() needs to use the
556 	 * special di_devfs_path_match() interface.
557 	 */
558 	pathcount = portcount = 0;
559 	pnode = NULL;
560 	while ((pnode = di_path_client_next_path(node, pnode)) != NULL) {
561 		if ((ret = di_path_prop_lookup_strings(pnode,
562 		    "target-port", &s)) > 0)
563 			portcount += ret;
564 		pathcount++;
565 	}
566 
567 	if (pathcount == 0) {
568 		if ((dnode->ddn_ppath =
569 		    topo_mod_zalloc(mod, sizeof (uintptr_t))) == NULL)
570 			goto error;
571 
572 		dnode->ddn_ppath_count = 1;
573 		if ((dnode->ddn_ppath[0] = topo_mod_strdup(mod,
574 		    dnode->ddn_dpath)) == NULL)
575 			goto error;
576 
577 		if ((ret = di_prop_lookup_strings(DDI_DEV_T_ANY, node,
578 		    "target-port", &s)) > 0) {
579 			if ((dnode->ddn_target_port = topo_mod_zalloc(mod,
580 			    ret * sizeof (uintptr_t))) == NULL)
581 				goto error;
582 
583 			dnode->ddn_target_port_count = ret;
584 
585 			for (i = 0; i < ret; i++) {
586 				if ((dnode->ddn_target_port[i] =
587 				    topo_mod_strdup(mod, s)) == NULL)
588 					goto error;
589 
590 				s += strlen(s) + 1;
591 			}
592 		}
593 	} else {
594 		if ((dnode->ddn_ppath = topo_mod_zalloc(mod,
595 		    pathcount * sizeof (uintptr_t))) == NULL)
596 			goto error;
597 
598 		dnode->ddn_ppath_count = pathcount;
599 
600 		if (portcount != 0 &&
601 		    ((dnode->ddn_target_port = topo_mod_zalloc(mod,
602 		    portcount * sizeof (uintptr_t)))) == NULL)
603 			goto error;
604 
605 		dnode->ddn_target_port_count = portcount;
606 
607 		pnode = NULL;
608 		pathcount = portcount = 0;
609 		while ((pnode = di_path_client_next_path(node,
610 		    pnode)) != NULL) {
611 			if ((path = di_path_devfs_path(pnode)) == NULL) {
612 				topo_mod_seterrno(mod, errno);
613 				goto error;
614 			}
615 
616 			dnode->ddn_ppath[pathcount] =
617 			    topo_mod_strdup(mod, path);
618 			di_devfs_path_free(path);
619 			if (dnode->ddn_ppath[pathcount] == NULL)
620 				goto error;
621 
622 			if ((ret = di_path_prop_lookup_strings(pnode,
623 			    "target-port", &s)) > 0) {
624 				for (i = 0; i < ret; i++) {
625 					if ((dnode->ddn_target_port[portcount] =
626 					    topo_mod_strdup(mod, s)) == NULL)
627 						goto error;
628 
629 					portcount++;
630 					s += strlen(s) + 1;
631 				}
632 			}
633 
634 			pathcount++;
635 		}
636 	}
637 
638 	/*
639 	 * Find the public /dev name by adding a minor name and using
640 	 * di_devlink interface for reverse translation (use devinfo path).
641 	 */
642 	mlen = strlen(dnode->ddn_dpath) + strlen(extn) + 1;
643 	if ((minorpath = topo_mod_alloc(mod, mlen)) == NULL)
644 		goto error;
645 	(void) snprintf(minorpath, mlen, "%s%s", dnode->ddn_dpath, extn);
646 	cbp->dcb_dnode = dnode;
647 	(void) di_devlink_walk(cbp->dcb_devhdl, "^dsk/", minorpath,
648 	    DI_PRIMARY_LINK, cbp, disk_devlink_callback);
649 	topo_mod_free(mod, minorpath, mlen);
650 	if (dnode->ddn_lpath == NULL) {
651 		topo_mod_dprintf(mod, "disk_di_node_add: "
652 		    "failed to determine logical path");
653 		goto error;
654 	}
655 
656 	/* cache various bits of optional information about the disk */
657 	if (di_prop_lookup_strings(DDI_DEV_T_ANY, node,
658 	    INQUIRY_VENDOR_ID, &s) > 0) {
659 		if ((dnode->ddn_mfg = topo_mod_strdup(mod, s)) == NULL)
660 			goto error;
661 	}
662 	if (di_prop_lookup_strings(DDI_DEV_T_ANY, node,
663 	    INQUIRY_PRODUCT_ID, &s) > 0) {
664 		if ((dnode->ddn_model = topo_mod_strdup(mod, s)) == NULL)
665 			goto error;
666 	}
667 	if (di_prop_lookup_strings(DDI_DEV_T_ANY, node,
668 	    INQUIRY_REVISION_ID, &s) > 0) {
669 		if ((dnode->ddn_firm = topo_mod_strdup(mod, s)) == NULL)
670 			goto error;
671 	}
672 	if (di_prop_lookup_strings(DDI_DEV_T_ANY, node,
673 	    INQUIRY_SERIAL_NO, &s) > 0) {
674 		if ((dnode->ddn_serial = topo_mod_strdup(mod, s)) == NULL)
675 			goto error;
676 	}
677 	if (di_prop_lookup_int64(DDI_DEV_T_ANY, node,
678 	    "device-nblocks", &nblocksp) > 0) {
679 		nblocks = (uint64_t)*nblocksp;
680 		/*
681 		 * To save kernel memory, the driver may not define
682 		 * "device-dblksize" when its value is default DEV_BSIZE.
683 		 */
684 		if (di_prop_lookup_ints(DDI_DEV_T_ANY, node,
685 		    "device-dblksize", &dblksizep) > 0)
686 			dblksize = (uint_t)*dblksizep;
687 		else
688 			dblksize = DEV_BSIZE;		/* default value */
689 		(void) snprintf(lentry, sizeof (lentry),
690 		    "%" PRIu64, nblocks * dblksize);
691 		if ((dnode->ddn_cap = topo_mod_strdup(mod, lentry)) == NULL)
692 			goto error;
693 	}
694 
695 	topo_mod_dprintf(mod, "disk_di_node_add: "
696 	    "adding %s\n", dnode->ddn_devid);
697 	topo_mod_dprintf(mod, "                  "
698 	    "       %s\n", dnode->ddn_dpath);
699 	for (i = 0; i < dnode->ddn_ppath_count; i++) {
700 		topo_mod_dprintf(mod, "                  "
701 		    "       %s\n", dnode->ddn_ppath[i]);
702 	}
703 	topo_list_append(cbp->dcb_list, dnode);
704 	return (0);
705 
706 error:
707 	disk_di_node_free(mod, dnode);
708 	return (-1);
709 }
710 
711 /* di_walk_node callback for disk_list_gather */
712 static int
713 disk_walk_di_nodes(di_node_t node, void *arg)
714 {
715 	ddi_devid_t	devid = NULL;
716 	char		*devidstr;
717 
718 	/* only interested in nodes that have devids */
719 	devid = (ddi_devid_t)di_devid(node);
720 	if (devid == NULL)
721 		return (DI_WALK_CONTINUE);
722 
723 	/* ... with a string representation of the devid */
724 	devidstr = devid_str_encode(devid, NULL);
725 	if (devidstr == NULL)
726 		return (DI_WALK_CONTINUE);
727 
728 	/* create/find the devid scsi topology node */
729 	(void) disk_di_node_add(node, devidstr, arg);
730 	devid_str_free(devidstr);
731 	return (DI_WALK_CONTINUE);
732 }
733 
734 int
735 disk_list_gather(topo_mod_t *mod, topo_list_t *listp)
736 {
737 	di_node_t		devtree;
738 	di_devlink_handle_t	devhdl;
739 	disk_cbdata_t		dcb;
740 
741 	if ((devtree = di_init("/", DINFOCACHE)) == DI_NODE_NIL) {
742 		topo_mod_dprintf(mod, "disk_list_gather: "
743 		    "di_init failed");
744 		return (-1);
745 	}
746 
747 	if ((devhdl = di_devlink_init(NULL, 0)) == DI_NODE_NIL) {
748 		topo_mod_dprintf(mod, "disk_list_gather: "
749 		    "di_devlink_init failed");
750 		di_fini(devtree);
751 		return (-1);
752 	}
753 
754 	dcb.dcb_mod = mod;
755 	dcb.dcb_list = listp;
756 	dcb.dcb_devhdl = devhdl;
757 
758 	/* walk the devinfo snapshot looking for nodes with devids */
759 	(void) di_walk_node(devtree, DI_WALK_CLDFIRST, &dcb,
760 	    disk_walk_di_nodes);
761 
762 	(void) di_devlink_fini(&devhdl);
763 	di_fini(devtree);
764 
765 	return (0);
766 }
767 
768 void
769 disk_list_free(topo_mod_t *mod, topo_list_t *listp)
770 {
771 	disk_di_node_t	*dnode;
772 
773 	while ((dnode = topo_list_next(listp)) != NULL) {
774 		/* order of delete/free is important */
775 		topo_list_delete(listp, dnode);
776 		disk_di_node_free(mod, dnode);
777 	}
778 }
779 
780 /*
781  * Query the current disk status. If successful, the disk status is returned
782  * as an nvlist consisting of at least the following members:
783  *
784  *	protocol	string		Supported protocol (currently "scsi")
785  *
786  *	status		nvlist		Arbitrary protocol-specific information
787  *					about the current state of the disk.
788  *
789  *	faults		nvlist		A list of supported faults. Each
790  *					element of this list is a boolean value.
791  *					An element's existence indicates that
792  *					the drive supports detecting this fault,
793  *					and the value indicates the current
794  *					state of the fault.
795  *
796  *	<fault-name>	nvlist		For each fault named in 'faults', a
797  *					nvlist describing protocol-specific
798  *					attributes of the fault.
799  *
800  * This method relies on the libdiskstatus library to query this information.
801  */
802 static int
803 disk_status(topo_mod_t *mod, tnode_t *nodep, topo_version_t vers,
804     nvlist_t *in_nvl, nvlist_t **out_nvl)
805 {
806 	disk_status_t	*dsp;
807 	char		*devpath, *fullpath;
808 	size_t		pathlen;
809 	nvlist_t	*status;
810 	int		err;
811 
812 	*out_nvl = NULL;
813 
814 	if (vers != TOPO_METH_DISK_STATUS_VERSION)
815 		return (topo_mod_seterrno(mod, EMOD_VER_NEW));
816 
817 	/*
818 	 * If the caller specifies the "path" parameter, then this indicates
819 	 * that we should use this instead of deriving it from the topo node
820 	 * itself.
821 	 */
822 	if (nvlist_lookup_string(in_nvl, "path", &fullpath) == 0) {
823 		devpath = NULL;
824 	} else {
825 		/*
826 		 * Get the /devices path and attempt to open the disk status
827 		 * handle.
828 		 */
829 		if (topo_prop_get_string(nodep, TOPO_PGROUP_IO,
830 		    TOPO_IO_DEV_PATH, &devpath, &err) != 0)
831 			return (topo_mod_seterrno(mod, EMOD_METHOD_NOTSUP));
832 
833 		/*
834 		 * Note that sizeof(string) includes the terminating NULL byte
835 		 */
836 		pathlen = strlen(devpath) + sizeof ("/devices") +
837 		    sizeof (PHYS_EXTN) - 1;
838 
839 		if ((fullpath = topo_mod_alloc(mod, pathlen)) == NULL)
840 			return (topo_mod_seterrno(mod, EMOD_NOMEM));
841 
842 		(void) snprintf(fullpath, pathlen, "/devices%s%s", devpath,
843 		    PHYS_EXTN);
844 
845 		topo_mod_strfree(mod, devpath);
846 	}
847 
848 	if ((dsp = disk_status_open(fullpath, &err)) == NULL) {
849 		if (devpath)
850 			topo_mod_free(mod, fullpath, pathlen);
851 		return (topo_mod_seterrno(mod, err == EDS_NOMEM ?
852 		    EMOD_NOMEM : EMOD_METHOD_NOTSUP));
853 	}
854 
855 	if (devpath)
856 		topo_mod_free(mod, fullpath, pathlen);
857 
858 	if ((status = disk_status_get(dsp)) == NULL) {
859 		err = (disk_status_errno(dsp) == EDS_NOMEM ?
860 		    EMOD_NOMEM : EMOD_METHOD_NOTSUP);
861 		disk_status_close(dsp);
862 		return (topo_mod_seterrno(mod, err));
863 	}
864 
865 	*out_nvl = status;
866 	disk_status_close(dsp);
867 	return (0);
868 }
869