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