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