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