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