xref: /illumos-gate/usr/src/lib/fm/topo/modules/common/ses/ses.c (revision 22f5594a529d50114d839d4ddecc2c499731a3d7)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #include <assert.h>
30 #include <dirent.h>
31 #include <devid.h>
32 #include <fm/topo_mod.h>
33 #include <fm/topo_list.h>
34 #include <fm/topo_method.h>
35 #include <fm/libdiskstatus.h>
36 #include <inttypes.h>
37 #include <scsi/libses.h>
38 #include <pthread.h>
39 #include <strings.h>
40 #include <unistd.h>
41 #include <sys/dkio.h>
42 #include <sys/fm/protocol.h>
43 #include <sys/scsi/scsi_types.h>
44 #include "disk.h"
45 
46 #define	SES_VERSION	1
47 
48 #define	TOPO_PGROUP_SES		"ses"
49 #define	TOPO_PROP_NODE_ID	"node-id"
50 #define	TOPO_PROP_TARGET_PATH	"target-path"
51 
52 #define	SES_SNAP_FREQ		1000	/* in milliseconds */
53 
54 #ifndef	NDEBUG
55 #define	verify(x)	assert(x)
56 #else
57 #define	verify(x)	((void)(x))
58 #endif
59 
60 /*
61  * Because multiple SES targets can be part of a single chassis, we construct
62  * our own hierarchy that takes this into account.  These SES targets may refer
63  * to the same devices (multiple paths) or to different devices (managing
64  * different portions of the space).  We arrange things into a
65  * ses_enum_enclosure_t, which contains a set of ses targets, and a list of all
66  * nodes found so far.
67  */
68 
69 typedef struct ses_enum_target {
70 	topo_list_t		set_link;
71 	ses_target_t		*set_target;
72 	ses_snap_t		*set_snap;
73 	struct timeval		set_snaptime;
74 	char			*set_devpath;
75 	int			set_refcount;
76 } ses_enum_target_t;
77 
78 typedef struct ses_enum_node {
79 	topo_list_t		sen_link;
80 	ses_node_t		*sen_node;
81 	uint64_t		sen_type;
82 	uint64_t		sen_instance;
83 	ses_enum_target_t	*sen_target;
84 } ses_enum_node_t;
85 
86 typedef struct ses_enum_chassis {
87 	topo_list_t		sec_link;
88 	topo_list_t		sec_nodes;
89 	topo_list_t		sec_targets;
90 	const char		*sec_csn;
91 	ses_node_t		*sec_enclosure;
92 	ses_enum_target_t	*sec_target;
93 	topo_instance_t		sec_instance;
94 	boolean_t		sec_hasdev;
95 } ses_enum_chassis_t;
96 
97 typedef struct ses_enum_data {
98 	topo_list_t		sed_disks;
99 	topo_list_t		sed_chassis;
100 	ses_enum_chassis_t	*sed_current;
101 	ses_enum_target_t	*sed_target;
102 	int			sed_errno;
103 	char			*sed_name;
104 	topo_mod_t		*sed_mod;
105 	topo_instance_t		sed_instance;
106 } ses_enum_data_t;
107 
108 static int ses_present(topo_mod_t *, tnode_t *, topo_version_t, nvlist_t *,
109     nvlist_t **);
110 static int ses_contains(topo_mod_t *, tnode_t *, topo_version_t, nvlist_t *,
111     nvlist_t **);
112 
113 static const topo_method_t ses_component_methods[] = {
114 	{ TOPO_METH_PRESENT, TOPO_METH_PRESENT_DESC,
115 	    TOPO_METH_PRESENT_VERSION0, TOPO_STABILITY_INTERNAL, ses_present },
116 	{ NULL }
117 };
118 
119 static const topo_method_t ses_enclosure_methods[] = {
120 	{ TOPO_METH_CONTAINS, TOPO_METH_CONTAINS_DESC,
121 	    TOPO_METH_CONTAINS_VERSION, TOPO_STABILITY_INTERNAL, ses_contains },
122 	{ NULL }
123 };
124 
125 static void
126 ses_target_free(topo_mod_t *mod, ses_enum_target_t *stp)
127 {
128 	if (--stp->set_refcount == 0) {
129 		ses_snap_rele(stp->set_snap);
130 		ses_close(stp->set_target);
131 		topo_mod_strfree(mod, stp->set_devpath);
132 		topo_mod_free(mod, stp, sizeof (ses_enum_target_t));
133 	}
134 }
135 
136 static void
137 ses_data_free(ses_enum_data_t *sdp)
138 {
139 	topo_mod_t *mod = sdp->sed_mod;
140 	ses_enum_chassis_t *cp;
141 	ses_enum_node_t *np;
142 	ses_enum_target_t *tp;
143 
144 	while ((cp = topo_list_next(&sdp->sed_chassis)) != NULL) {
145 		topo_list_delete(&sdp->sed_chassis, cp);
146 
147 		while ((np = topo_list_next(&cp->sec_nodes)) != NULL) {
148 			topo_list_delete(&cp->sec_nodes, np);
149 			topo_mod_free(mod, np, sizeof (ses_enum_node_t));
150 		}
151 
152 		while ((tp = topo_list_next(&cp->sec_targets)) != NULL) {
153 			topo_list_delete(&cp->sec_targets, tp);
154 			ses_target_free(mod, tp);
155 		}
156 
157 		topo_mod_free(mod, cp, sizeof (ses_enum_chassis_t));
158 	}
159 
160 	disk_list_free(mod, &sdp->sed_disks);
161 }
162 
163 /*
164  * For enclosure nodes, we have a special contains method.  By default, the hc
165  * walker will compare the node name and instance number to determine if an
166  * FMRI matches.  For enclosures where the enumeration order is impossible to
167  * predict, we instead use the chassis-id as a unique identifier, and ignore
168  * the instance number.
169  */
170 static int
171 fmri_contains(topo_mod_t *mod, nvlist_t *nv1, nvlist_t *nv2)
172 {
173 	uint8_t v1, v2;
174 	nvlist_t **hcp1, **hcp2;
175 	int err, i;
176 	uint_t nhcp1, nhcp2;
177 	nvlist_t *a1, *a2;
178 	char *c1, *c2;
179 	int mindepth;
180 
181 	if (nvlist_lookup_uint8(nv1, FM_VERSION, &v1) != 0 ||
182 	    nvlist_lookup_uint8(nv2, FM_VERSION, &v2) != 0 ||
183 	    v1 > FM_HC_SCHEME_VERSION || v2 > FM_HC_SCHEME_VERSION)
184 		return (topo_mod_seterrno(mod, EMOD_FMRI_VERSION));
185 
186 	err = nvlist_lookup_nvlist_array(nv1, FM_FMRI_HC_LIST, &hcp1, &nhcp1);
187 	err |= nvlist_lookup_nvlist_array(nv2, FM_FMRI_HC_LIST, &hcp2, &nhcp2);
188 	if (err != 0)
189 		return (topo_mod_seterrno(mod, EMOD_FMRI_NVL));
190 
191 	/*
192 	 * If the chassis-id doesn't match, then these FMRIs are not
193 	 * equivalent.  If one of the FMRIs doesn't have a chassis ID, then we
194 	 * have no choice but to fall back to the instance ID.
195 	 */
196 	if (nvlist_lookup_nvlist(nv1, FM_FMRI_AUTHORITY, &a1) == 0 &&
197 	    nvlist_lookup_nvlist(nv2, FM_FMRI_AUTHORITY, &a2) == 0 &&
198 	    nvlist_lookup_string(a1, FM_FMRI_AUTH_CHASSIS, &c1) == 0 &&
199 	    nvlist_lookup_string(a2, FM_FMRI_AUTH_CHASSIS, &c2) == 0) {
200 		if (strcmp(c1, c2) != 0)
201 			return (0);
202 
203 		mindepth = 1;
204 	} else {
205 		mindepth = 0;
206 	}
207 
208 	if (nhcp2 < nhcp1)
209 		return (0);
210 
211 	for (i = 0; i < nhcp1; i++) {
212 		char *nm1 = NULL;
213 		char *nm2 = NULL;
214 		char *id1 = NULL;
215 		char *id2 = NULL;
216 
217 		(void) nvlist_lookup_string(hcp1[i], FM_FMRI_HC_NAME, &nm1);
218 		(void) nvlist_lookup_string(hcp2[i], FM_FMRI_HC_NAME, &nm2);
219 		(void) nvlist_lookup_string(hcp1[i], FM_FMRI_HC_ID, &id1);
220 		(void) nvlist_lookup_string(hcp2[i], FM_FMRI_HC_ID, &id2);
221 		if (nm1 == NULL || nm2 == NULL || id1 == NULL || id2 == NULL)
222 			return (topo_mod_seterrno(mod, EMOD_FMRI_NVL));
223 
224 		if (strcmp(nm1, nm2) == 0 &&
225 		    (i < mindepth || strcmp(id1, id2) == 0))
226 			continue;
227 
228 		return (0);
229 	}
230 
231 	return (1);
232 }
233 
234 /*ARGSUSED*/
235 static int
236 ses_contains(topo_mod_t *mod, tnode_t *tn, topo_version_t version,
237     nvlist_t *in, nvlist_t **out)
238 {
239 	int ret;
240 	nvlist_t *nv1, *nv2;
241 
242 	if (version > TOPO_METH_CONTAINS_VERSION)
243 		return (topo_mod_seterrno(mod, EMOD_VER_NEW));
244 
245 	if (nvlist_lookup_nvlist(in, TOPO_METH_FMRI_ARG_FMRI, &nv1) != 0 ||
246 	    nvlist_lookup_nvlist(in, TOPO_METH_FMRI_ARG_SUBFMRI, &nv2) != 0)
247 		return (topo_mod_seterrno(mod, EMOD_METHOD_INVAL));
248 
249 	ret = fmri_contains(mod, nv1, nv2);
250 	if (ret < 0)
251 		return (-1);
252 
253 	if (topo_mod_nvalloc(mod, out, NV_UNIQUE_NAME) == 0) {
254 		if (nvlist_add_uint32(*out, TOPO_METH_COMPARE_RET,
255 		    ret) == 0)
256 			return (0);
257 		else
258 			nvlist_free(*out);
259 	}
260 
261 	return (-1);
262 
263 }
264 
265 /*
266  * Determine if the element is present.  This is somewhat complicated because
267  * we need to take a new snapshot in order to determine presence, but we don't
268  * want to be constantly taking SES snapshots if the consumer is going to do a
269  * series of queries.  So we adopt the strategy of assuming that the SES state
270  * is not going to be rapidly changing, and limit our snapshot frequency to some
271  * defined bounds.
272  */
273 /*ARGSUSED*/
274 static int
275 ses_present(topo_mod_t *mod, tnode_t *tn, topo_version_t version,
276     nvlist_t *in, nvlist_t **out)
277 {
278 	nvlist_t *nvl;
279 	struct timeval tv;
280 	ses_enum_target_t *tp = topo_node_getspecific(tn);
281 	uint64_t prev, now;
282 	ses_snap_t *snap;
283 	int err;
284 	uint64_t nodeid, status;
285 	ses_node_t *np;
286 	nvlist_t *props;
287 	boolean_t present;
288 
289 	if (tp == NULL)
290 		return (topo_mod_seterrno(mod, EMOD_METHOD_NOTSUP));
291 
292 	/*
293 	 * Determine if we need to take a new snapshot.
294 	 */
295 	if (gettimeofday(&tv, NULL) != 0) {
296 		tv.tv_sec = time(NULL);
297 		tv.tv_usec = 0;
298 	}
299 
300 	now = tv.tv_sec * 1000 + tv.tv_usec / 1000;
301 	prev = tp->set_snaptime.tv_sec * 1000 +
302 	    tp->set_snaptime.tv_usec / 1000;
303 
304 	if (now - prev > SES_SNAP_FREQ &&
305 	    (snap = ses_snap_new(tp->set_target)) != NULL) {
306 		if (ses_snap_generation(snap) !=
307 		    ses_snap_generation(tp->set_snap)) {
308 			/*
309 			 * If we find ourselves in this situation, we're in
310 			 * trouble.  The generation count has changed, which
311 			 * indicates that our current topology is out of date.
312 			 * But we need to consult the new topology in order to
313 			 * determine presence at this moment in time.  We can't
314 			 * go back and change the topo snapshot in situ, so
315 			 * we'll just pretend like the device is present in
316 			 * this scenario.
317 			 */
318 			ses_snap_rele(snap);
319 			present = B_TRUE;
320 			goto out;
321 		} else {
322 			ses_snap_rele(tp->set_snap);
323 			tp->set_snap = snap;
324 		}
325 		tp->set_snaptime = tv;
326 	}
327 
328 	snap = tp->set_snap;
329 
330 	verify(topo_prop_get_uint64(tn, TOPO_PGROUP_SES,
331 	    TOPO_PROP_NODE_ID, &nodeid, &err) == 0);
332 	verify((np = ses_node_lookup(snap, nodeid)) != NULL);
333 	verify((props = ses_node_props(np)) != NULL);
334 	verify(nvlist_lookup_uint64(props,
335 	    SES_PROP_STATUS_CODE, &status) == 0);
336 
337 	present = (status != SES_ESC_NOT_INSTALLED);
338 
339 out:
340 	if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0)
341 		return (topo_mod_seterrno(mod, EMOD_FMRI_NVL));
342 
343 	if (nvlist_add_uint32(nvl, TOPO_METH_PRESENT_RET,
344 	    present) != 0) {
345 		nvlist_free(nvl);
346 		return (topo_mod_seterrno(mod, EMOD_FMRI_NVL));
347 	}
348 
349 	*out = nvl;
350 
351 	return (0);
352 }
353 
354 /*
355  * Sets standard properties for a ses node (enclosure or bay).  This includes
356  * setting the FRU to be the same as the resource, as well as setting the
357  * authority information.
358  */
359 static int
360 ses_set_standard_props(topo_mod_t *mod, tnode_t *tn, nvlist_t *auth,
361     uint64_t nodeid, const char *path)
362 {
363 	int err;
364 	char *product, *chassis;
365 	nvlist_t *fmri;
366 	topo_pgroup_info_t pgi;
367 
368 	/*
369 	 * Set the authority explicitly if specified.
370 	 */
371 	if (auth) {
372 		verify(nvlist_lookup_string(auth, FM_FMRI_AUTH_PRODUCT,
373 		    &product) == 0);
374 		verify(nvlist_lookup_string(auth, FM_FMRI_AUTH_CHASSIS,
375 		    &chassis) == 0);
376 		if (topo_prop_set_string(tn, FM_FMRI_AUTHORITY,
377 		    FM_FMRI_AUTH_PRODUCT, TOPO_PROP_IMMUTABLE, product,
378 		    &err) != 0 ||
379 		    topo_prop_set_string(tn, FM_FMRI_AUTHORITY,
380 		    FM_FMRI_AUTH_CHASSIS, TOPO_PROP_IMMUTABLE, chassis,
381 		    &err) != 0 ||
382 		    topo_prop_set_string(tn, FM_FMRI_AUTHORITY,
383 		    FM_FMRI_AUTH_SERVER, TOPO_PROP_IMMUTABLE, "",
384 		    &err) != 0) {
385 			topo_mod_dprintf(mod, "failed to add authority "
386 			    "properties: %s", topo_strerror(err));
387 			return (topo_mod_seterrno(mod, err));
388 		}
389 	}
390 
391 	/*
392 	 * Copy the resource and set that as the FRU.
393 	 */
394 	if (topo_node_resource(tn, &fmri, &err) != 0) {
395 		topo_mod_dprintf(mod,
396 		    "topo_node_resource() failed : %s\n",
397 		    topo_strerror(err));
398 		return (topo_mod_seterrno(mod, err));
399 	}
400 
401 	if (topo_node_fru_set(tn, fmri, 0, &err) != 0) {
402 		topo_mod_dprintf(mod,
403 		    "topo_node_fru_set() failed : %s\n",
404 		    topo_strerror(err));
405 		nvlist_free(fmri);
406 		return (topo_mod_seterrno(mod, err));
407 	}
408 
409 	nvlist_free(fmri);
410 
411 	/*
412 	 * Set the SES-specific properties so that consumers can query
413 	 * additional information about the particular SES element.
414 	 */
415 	pgi.tpi_name = TOPO_PGROUP_SES;
416 	pgi.tpi_namestab = TOPO_STABILITY_PRIVATE;
417 	pgi.tpi_datastab = TOPO_STABILITY_PRIVATE;
418 	pgi.tpi_version = TOPO_VERSION;
419 	if (topo_pgroup_create(tn, &pgi, &err) != 0) {
420 		topo_mod_dprintf(mod, "failed to create propgroup "
421 		    "%s: %s\n", TOPO_PGROUP_SES, topo_strerror(err));
422 		return (-1);
423 	}
424 
425 	if (topo_prop_set_uint64(tn, TOPO_PGROUP_SES,
426 	    TOPO_PROP_NODE_ID, TOPO_PROP_IMMUTABLE,
427 	    nodeid, &err) != 0) {
428 		topo_mod_dprintf(mod,
429 		    "failed to create property %s: %s\n",
430 		    TOPO_PROP_NODE_ID, topo_strerror(err));
431 		return (-1);
432 	}
433 
434 	if (topo_prop_set_string(tn, TOPO_PGROUP_SES,
435 	    TOPO_PROP_TARGET_PATH, TOPO_PROP_IMMUTABLE,
436 	    path, &err) != 0) {
437 		topo_mod_dprintf(mod,
438 		    "failed to create property %s: %s\n",
439 		    TOPO_PROP_TARGET_PATH, topo_strerror(err));
440 		return (-1);
441 	}
442 
443 	return (0);
444 }
445 
446 /*
447  * Callback to add a disk to a given bay.  We first check the status-code to
448  * determine if a disk is present, ignoring those that aren't in an appropriate
449  * state.  We then scan the sas-phys array to determine the attached SAS
450  * address.   We create a disk node regardless of whether the SES target is SAS
451  * and supports the necessary pages.  If we do find a SAS address, we correlate
452  * this to the corresponding Solaris device node to fill in the rest of the
453  * data.
454  */
455 static int
456 ses_create_disk(ses_enum_data_t *sdp, tnode_t *pnode, nvlist_t *props)
457 {
458 	topo_mod_t *mod = sdp->sed_mod;
459 	uint64_t status;
460 	nvlist_t **sas;
461 	uint_t s, nsas;
462 	uint64_t addr;
463 	char buf[17];
464 
465 	/*
466 	 * Skip devices that are not in a present (and possibly damaged) state.
467 	 */
468 	if (nvlist_lookup_uint64(props, SES_PROP_STATUS_CODE, &status) != 0)
469 		return (0);
470 
471 	if (status != SES_ESC_OK &&
472 	    status != SES_ESC_CRITICAL &&
473 	    status != SES_ESC_NONCRITICAL &&
474 	    status != SES_ESC_UNRECOVERABLE &&
475 	    status != SES_ESC_NO_ACCESS)
476 		return (0);
477 
478 	topo_mod_dprintf(mod, "found attached disk");
479 
480 	/*
481 	 * Create the disk range.
482 	 */
483 	if (topo_node_range_create(mod, pnode, DISK, 0, 1) != 0) {
484 		topo_mod_dprintf(mod,
485 		    "topo_node_create_range() failed: %s",
486 		    topo_mod_errmsg(mod));
487 		return (-1);
488 	}
489 
490 	/*
491 	 * Look through all SAS addresses and attempt to correlate them to a
492 	 * known Solaris device.  If we don't find a matching node, then we
493 	 * don't enumerate the disk node.
494 	 */
495 	if (nvlist_lookup_nvlist_array(props, SES_SAS_PROP_PHYS,
496 	    &sas, &nsas) != 0)
497 		return (0);
498 
499 	for (s = 0; s < nsas; s++) {
500 		verify(nvlist_lookup_uint64(sas[s],
501 		    SES_SAS_PROP_ADDR, &addr) == 0);
502 		if (addr == 0)
503 			continue;
504 
505 		(void) snprintf(buf, sizeof (buf), "%llx", addr);
506 
507 		if (disk_declare_addr(mod, pnode, &sdp->sed_disks,
508 		    buf) != 0)
509 			return (-1);
510 	}
511 
512 	return (0);
513 }
514 
515 /*
516  * Callback to create a basic node (bay, psu, fan, or controller).
517  */
518 static int
519 ses_create_generic(ses_enum_data_t *sdp, ses_enum_node_t *snp,
520     tnode_t *pnode, const char *nodename, const char *labelname)
521 {
522 	ses_node_t *np = snp->sen_node;
523 	ses_node_t *agg;
524 	uint64_t instance = snp->sen_instance;
525 	topo_mod_t *mod = sdp->sed_mod;
526 	nvlist_t *props, *aprops;
527 	nvlist_t *auth = NULL, *fmri = NULL;
528 	tnode_t *tn;
529 	char label[128];
530 	int err;
531 	char *part = NULL, *serial = NULL;
532 	char *classdesc;
533 
534 	props = ses_node_props(np);
535 
536 	(void) nvlist_lookup_string(props, LIBSES_PROP_PART, &part);
537 	(void) nvlist_lookup_string(props, LIBSES_PROP_SERIAL, &serial);
538 
539 	topo_mod_dprintf(mod, "adding %s %llu", nodename, instance);
540 
541 	/*
542 	 * Create the node.  The interesting information is all copied from the
543 	 * parent enclosure node, so there is not much to do.
544 	 */
545 	if ((auth = topo_mod_auth(mod, pnode)) == NULL)
546 		goto error;
547 
548 	if ((fmri = topo_mod_hcfmri(mod, pnode, FM_HC_SCHEME_VERSION,
549 	    nodename, (topo_instance_t)instance, NULL, auth, part, NULL,
550 	    serial)) == NULL) {
551 		topo_mod_dprintf(mod, "topo_mod_hcfmri() failed: %s",
552 		    topo_mod_errmsg(mod));
553 		goto error;
554 	}
555 
556 	if ((tn = topo_node_bind(mod, pnode, nodename,
557 	    instance, fmri)) == NULL) {
558 		topo_mod_dprintf(mod, "topo_node_bind() failed: %s",
559 		    topo_mod_errmsg(mod));
560 		goto error;
561 	}
562 
563 	/*
564 	 * If the aggregate gives us class description, then use that instead
565 	 * of the default label name.
566 	 */
567 	agg = ses_node_parent(np);
568 	aprops = ses_node_props(agg);
569 	if (nvlist_lookup_string(aprops, SES_PROP_CLASS_DESCRIPTION,
570 	    &classdesc) == 0 && classdesc[0] != '\0')
571 		labelname = classdesc;
572 
573 	(void) snprintf(label, sizeof (label), "%s %llu", labelname, instance);
574 	if (topo_node_label_set(tn, label, &err) != 0)
575 		goto error;
576 
577 	if (ses_set_standard_props(mod, tn, NULL, ses_node_id(np),
578 	    snp->sen_target->set_devpath) != 0)
579 		goto error;
580 
581 	if (strcmp(nodename, "bay") == 0) {
582 		if (ses_create_disk(sdp, tn, props) != 0)
583 			goto error;
584 	} else {
585 		/*
586 		 * Only fan/psu nodes have a 'present' method.  Bay nodes are
587 		 * always present, and disk nodes are present by virtue of being
588 		 * enumerated.
589 		 */
590 		if (topo_method_register(mod, tn, ses_component_methods) != 0) {
591 			topo_mod_dprintf(mod,
592 			    "topo_method_register() failed: %s",
593 			    topo_mod_errmsg(mod));
594 			goto error;
595 		}
596 
597 		snp->sen_target->set_refcount++;
598 		topo_node_setspecific(tn, snp->sen_target);
599 	}
600 
601 	nvlist_free(auth);
602 	nvlist_free(fmri);
603 	return (0);
604 
605 error:
606 	nvlist_free(auth);
607 	nvlist_free(fmri);
608 	return (-1);
609 }
610 
611 /*
612  * Instantiate any children of a given type.
613  */
614 static int
615 ses_create_children(ses_enum_data_t *sdp, tnode_t *pnode, uint64_t type,
616     const char *nodename, const char *defaultlabel, ses_enum_chassis_t *cp)
617 {
618 	topo_mod_t *mod = sdp->sed_mod;
619 	boolean_t found;
620 	uint64_t max;
621 	ses_enum_node_t *snp;
622 
623 	/*
624 	 * First go through and count how many matching nodes we have.
625 	 */
626 	max = 0;
627 	found = B_FALSE;
628 	for (snp = topo_list_next(&cp->sec_nodes); snp != NULL;
629 	    snp = topo_list_next(snp)) {
630 		if (snp->sen_type == type) {
631 			found = B_TRUE;
632 			if (snp->sen_instance > max)
633 				max = snp->sen_instance;
634 		}
635 	}
636 
637 	/*
638 	 * No enclosure should export both DEVICE and ARRAY_DEVICE elements.
639 	 * Since we map both of these to 'disk', if an enclosure does this, we
640 	 * just ignore the array elements.
641 	 */
642 	if (!found ||
643 	    (type == SES_ET_ARRAY_DEVICE && cp->sec_hasdev))
644 		return (0);
645 
646 	topo_mod_dprintf(mod, "%s: creating %llu %s nodes",
647 	    cp->sec_csn, max, nodename);
648 
649 	if (topo_node_range_create(mod, pnode,
650 	    nodename, 0, max) != 0) {
651 		topo_mod_dprintf(mod,
652 		    "topo_node_create_range() failed: %s",
653 		    topo_mod_errmsg(mod));
654 		return (-1);
655 	}
656 
657 	for (snp = topo_list_next(&cp->sec_nodes); snp != NULL;
658 	    snp = topo_list_next(snp)) {
659 		if (snp->sen_type == type) {
660 			if (ses_create_generic(sdp, snp, pnode,
661 			    nodename, defaultlabel) != 0)
662 				return (-1);
663 		}
664 	}
665 
666 	return (0);
667 }
668 
669 /*
670  * Instantiate a new chassis instance in the topology.
671  */
672 static int
673 ses_create_chassis(ses_enum_data_t *sdp, tnode_t *pnode, ses_enum_chassis_t *cp)
674 {
675 	topo_mod_t *mod = sdp->sed_mod;
676 	nvlist_t *props;
677 	char *raw_manufacturer, *raw_model, *raw_revision;
678 	char *manufacturer = NULL, *model = NULL, *product = NULL;
679 	char *revision = NULL;
680 	char *serial;
681 	size_t prodlen;
682 	tnode_t *tn;
683 	nvlist_t *fmri = NULL, *auth = NULL;
684 	int ret = -1;
685 	ses_enum_node_t *snp;
686 
687 	/*
688 	 * Check to see if there are any devices presennt in the chassis.  If
689 	 * not, ignore the chassis alltogether.  This is most useful for
690 	 * ignoring internal HBAs that present a SES target but don't actually
691 	 * manage any of the devices.
692 	 */
693 	for (snp = topo_list_next(&cp->sec_nodes); snp != NULL;
694 	    snp = topo_list_next(snp)) {
695 		if (snp->sen_type == SES_ET_DEVICE ||
696 		    snp->sen_type == SES_ET_ARRAY_DEVICE)
697 			break;
698 	}
699 
700 	if (snp == NULL)
701 		return (0);
702 
703 	props = ses_node_props(cp->sec_enclosure);
704 
705 	/*
706 	 * We use the following property mappings:
707 	 *
708 	 * 	manufacturer		vendor-id
709 	 * 	model			product-id
710 	 * 	serial-number		libses-chassis-serial
711 	 */
712 	verify(nvlist_lookup_string(props, SES_EN_PROP_VID,
713 	    &raw_manufacturer) == 0);
714 	verify(nvlist_lookup_string(props, SES_EN_PROP_PID, &raw_model) == 0);
715 	verify(nvlist_lookup_string(props, SES_EN_PROP_REV,
716 	    &raw_revision) == 0);
717 	verify(nvlist_lookup_string(props, LIBSES_EN_PROP_CSN, &serial) == 0);
718 
719 	/*
720 	 * To construct the authority information, we 'clean' each string by
721 	 * removing any offensive characters and trimmming whitespace.  For the
722 	 * 'product-id', we use a concatenation of 'manufacturer-model'.  We
723 	 * also take the numerical serial number and convert it to a string.
724 	 */
725 	if ((manufacturer = disk_auth_clean(mod, raw_manufacturer)) == NULL ||
726 	    (model = disk_auth_clean(mod, raw_model)) == NULL ||
727 	    (revision = disk_auth_clean(mod, raw_revision)) == NULL) {
728 		goto error;
729 	}
730 
731 	prodlen = strlen(manufacturer) + strlen(model) + 2;
732 	if ((product = topo_mod_alloc(mod, prodlen)) == NULL)
733 		goto error;
734 
735 	(void) snprintf(product, prodlen, "%s-%s", manufacturer, model);
736 
737 	/*
738 	 * Construct the topo node and bind it to our parent.
739 	 */
740 	if (topo_mod_nvalloc(mod, &auth, NV_UNIQUE_NAME) != 0)
741 		goto error;
742 
743 	if (nvlist_add_string(auth, FM_FMRI_AUTH_PRODUCT, product) != 0 ||
744 	    nvlist_add_string(auth, FM_FMRI_AUTH_CHASSIS, serial) != 0) {
745 		(void) topo_mod_seterrno(mod, EMOD_NVL_INVAL);
746 		goto error;
747 	}
748 
749 	/*
750 	 * We pass NULL for the parent FMRI because there is no resource
751 	 * associated with it.  For the toplevel enclosure, we leave the
752 	 * serial/part/revision portions empty, which are reserved for
753 	 * individual components within the chassis.
754 	 */
755 	if ((fmri = topo_mod_hcfmri(mod, NULL, FM_HC_SCHEME_VERSION,
756 	    SES_ENCLOSURE, cp->sec_instance, NULL, auth,
757 	    model, revision, serial)) == NULL) {
758 		topo_mod_dprintf(mod, "topo_mod_hcfmri() failed: %s",
759 		    topo_mod_errmsg(mod));
760 		goto error;
761 	}
762 
763 	if ((tn = topo_node_bind(mod, pnode, SES_ENCLOSURE,
764 	    cp->sec_instance, fmri)) == NULL) {
765 		topo_mod_dprintf(mod, "topo_node_bind() failed: %s",
766 		    topo_mod_errmsg(mod));
767 		goto error;
768 	}
769 
770 	if (topo_method_register(mod, tn, ses_enclosure_methods) != 0) {
771 		topo_mod_dprintf(mod,
772 		    "topo_method_register() failed: %s",
773 		    topo_mod_errmsg(mod));
774 		goto error;
775 	}
776 
777 	if (ses_set_standard_props(mod, tn, auth,
778 	    ses_node_id(cp->sec_enclosure), cp->sec_target->set_devpath) != 0)
779 		goto error;
780 
781 	/*
782 	 * Create the nodes for power supplies, fans, and devices.
783 	 */
784 	if (ses_create_children(sdp, tn, SES_ET_POWER_SUPPLY,
785 	    PSU, "PSU", cp) != 0 ||
786 	    ses_create_children(sdp, tn, SES_ET_COOLING,
787 	    FAN, "FAN", cp) != 0 ||
788 	    ses_create_children(sdp, tn, SES_ET_ESC_ELECTRONICS,
789 	    CONTROLLER, "CONTROLLER", cp) != 0 ||
790 	    ses_create_children(sdp, tn, SES_ET_DEVICE,
791 	    BAY, "BAY", cp) != 0 ||
792 	    ses_create_children(sdp, tn, SES_ET_ARRAY_DEVICE,
793 	    BAY, "BAY", cp) != 0)
794 		goto error;
795 
796 	ret = 0;
797 error:
798 	topo_mod_strfree(mod, manufacturer);
799 	topo_mod_strfree(mod, model);
800 	topo_mod_strfree(mod, revision);
801 	topo_mod_strfree(mod, product);
802 
803 	nvlist_free(fmri);
804 	nvlist_free(auth);
805 	return (ret);
806 }
807 
808 /*
809  * Gather nodes from the current SES target into our chassis list, merging the
810  * results if necessary.
811  */
812 static ses_walk_action_t
813 ses_enum_gather(ses_node_t *np, void *data)
814 {
815 	nvlist_t *props = ses_node_props(np);
816 	ses_enum_data_t *sdp = data;
817 	topo_mod_t *mod = sdp->sed_mod;
818 	ses_enum_chassis_t *cp;
819 	ses_enum_node_t *snp;
820 	char *csn;
821 	uint64_t instance, type;
822 
823 	if (ses_node_type(np) == SES_NODE_ENCLOSURE) {
824 		/*
825 		 * If we have already identified the chassis for this target,
826 		 * then this is a secondary enclosure and we should ignore it,
827 		 * along with the rest of the tree (since this is depth-first).
828 		 */
829 		if (sdp->sed_current != NULL)
830 			return (SES_WALK_ACTION_TERMINATE);
831 
832 		/*
833 		 * Go through the list of chassis we have seen so far and see
834 		 * if this serial number matches one of the known values.
835 		 */
836 		if (nvlist_lookup_string(props, LIBSES_EN_PROP_CSN,
837 		    &csn) != 0)
838 			return (SES_WALK_ACTION_TERMINATE);
839 
840 		for (cp = topo_list_next(&sdp->sed_chassis); cp != NULL;
841 		    cp = topo_list_next(cp)) {
842 			if (strcmp(cp->sec_csn, csn) == 0) {
843 				topo_mod_dprintf(mod, "%s: part of already "
844 				    "known chassis %s", sdp->sed_name, csn);
845 				break;
846 			}
847 		}
848 
849 		if (cp == NULL) {
850 			topo_mod_dprintf(mod, "%s: creating chassis %s",
851 			    sdp->sed_name, csn);
852 
853 			if ((cp = topo_mod_zalloc(mod,
854 			    sizeof (ses_enum_chassis_t))) == NULL)
855 				goto error;
856 
857 			cp->sec_csn = csn;
858 			cp->sec_enclosure = np;
859 			cp->sec_target = sdp->sed_target;
860 			cp->sec_instance = sdp->sed_instance++;
861 			topo_list_append(&sdp->sed_chassis, cp);
862 		}
863 
864 		topo_list_append(&cp->sec_targets, sdp->sed_target);
865 		sdp->sed_current = cp;
866 
867 	} else if (ses_node_type(np) == SES_NODE_ELEMENT) {
868 		/*
869 		 * If we haven't yet seen an enclosure node and identified the
870 		 * current chassis, something is very wrong; bail out.
871 		 */
872 		if (sdp->sed_current == NULL)
873 			return (SES_WALK_ACTION_TERMINATE);
874 
875 		/*
876 		 * If this isn't one of the element types we care about, then
877 		 * ignore it.
878 		 */
879 		verify(nvlist_lookup_uint64(props, SES_PROP_ELEMENT_TYPE,
880 		    &type) == 0);
881 		if (type != SES_ET_DEVICE &&
882 		    type != SES_ET_ARRAY_DEVICE &&
883 		    type != SES_ET_COOLING &&
884 		    type != SES_ET_POWER_SUPPLY &&
885 		    type != SES_ET_ESC_ELECTRONICS)
886 			return (SES_WALK_ACTION_CONTINUE);
887 
888 		/*
889 		 * Get the current instance number and see if we already know
890 		 * about this element.  If so, it means we have multiple paths
891 		 * to the same elements, and we should ignore the current path.
892 		 */
893 		verify(nvlist_lookup_uint64(props, SES_PROP_ELEMENT_CLASS_INDEX,
894 		    &instance) == 0);
895 		if (type == SES_ET_DEVICE || type == SES_ET_ARRAY_DEVICE)
896 			(void) nvlist_lookup_uint64(props, SES_PROP_BAY_NUMBER,
897 			    &instance);
898 
899 		cp = sdp->sed_current;
900 
901 		for (snp = topo_list_next(&cp->sec_nodes); snp != NULL;
902 		    snp = topo_list_next(snp)) {
903 			if (snp->sen_type == type &&
904 			    snp->sen_instance == instance)
905 				return (SES_WALK_ACTION_CONTINUE);
906 		}
907 
908 		if ((snp = topo_mod_zalloc(mod,
909 		    sizeof (ses_enum_node_t))) == NULL)
910 			goto error;
911 
912 		topo_mod_dprintf(mod, "%s: adding node (%llu, %llu)",
913 		    sdp->sed_name, type, instance);
914 		snp->sen_node = np;
915 		snp->sen_type = type;
916 		snp->sen_instance = instance;
917 		snp->sen_target = sdp->sed_target;
918 		topo_list_append(&cp->sec_nodes, snp);
919 
920 		if (type == SES_ET_DEVICE)
921 			cp->sec_hasdev = B_TRUE;
922 	}
923 
924 	return (SES_WALK_ACTION_CONTINUE);
925 
926 error:
927 	sdp->sed_errno = -1;
928 	return (SES_WALK_ACTION_TERMINATE);
929 }
930 
931 static int
932 ses_process_dir(const char *dirpath, ses_enum_data_t *sdp)
933 {
934 	topo_mod_t *mod = sdp->sed_mod;
935 	DIR *dir;
936 	struct dirent *dp;
937 	char path[PATH_MAX];
938 	ses_enum_target_t *stp;
939 	int err = -1;
940 
941 	/*
942 	 * Open the SES target directory and iterate over any available
943 	 * targets.
944 	 */
945 	if ((dir = opendir(dirpath)) == NULL) {
946 		/*
947 		 * If the SES target directory does not exist, then return as if
948 		 * there are no active targets.
949 		 */
950 		topo_mod_dprintf(mod, "failed to open ses "
951 		    "directory '%s'", dirpath);
952 		return (0);
953 	}
954 
955 	while ((dp = readdir(dir)) != NULL) {
956 		if (strcmp(dp->d_name, ".") == 0 ||
957 		    strcmp(dp->d_name, "..") == 0)
958 			continue;
959 
960 		/*
961 		 * Create a new target instance and take a snapshot.
962 		 */
963 		if ((stp = topo_mod_zalloc(mod,
964 		    sizeof (ses_enum_target_t))) == NULL)
965 			goto error;
966 
967 		(void) snprintf(path, sizeof (path), "%s/%s", dirpath,
968 		    dp->d_name);
969 
970 		/*
971 		 * We keep track of the SES device path and export it on a
972 		 * per-node basis to allow higher level software to get to the
973 		 * corresponding SES state.
974 		 */
975 		if ((stp->set_devpath = topo_mod_strdup(mod, path)) == NULL) {
976 			topo_mod_free(mod, stp, sizeof (ses_enum_target_t));
977 			goto error;
978 		}
979 
980 		if ((stp->set_target =
981 		    ses_open(LIBSES_VERSION, path)) == NULL) {
982 			topo_mod_dprintf(mod, "failed to open ses target "
983 			    "'%s': %s", dp->d_name, ses_errmsg());
984 			topo_mod_strfree(mod, stp->set_devpath);
985 			topo_mod_free(mod, stp, sizeof (ses_enum_target_t));
986 			continue;
987 		}
988 
989 		stp->set_refcount = 1;
990 		sdp->sed_target = stp;
991 		stp->set_snap = ses_snap_hold(stp->set_target);
992 		if (gettimeofday(&stp->set_snaptime, NULL) != 0)
993 			stp->set_snaptime.tv_sec = time(NULL);
994 
995 		/*
996 		 * Enumerate over all SES elements and merge them into the
997 		 * correct ses_enum_chassis_t.
998 		 */
999 		sdp->sed_current = NULL;
1000 		sdp->sed_errno = 0;
1001 		sdp->sed_name = dp->d_name;
1002 		(void) ses_walk(stp->set_snap, ses_enum_gather, sdp);
1003 
1004 		if (sdp->sed_errno != 0)
1005 			goto error;
1006 	}
1007 
1008 	err = 0;
1009 error:
1010 	closedir(dir);
1011 	return (err);
1012 }
1013 
1014 static void
1015 ses_release(topo_mod_t *mod, tnode_t *tn)
1016 {
1017 	ses_enum_target_t *stp;
1018 
1019 	if ((stp = topo_node_getspecific(tn)) != NULL)
1020 		ses_target_free(mod, stp);
1021 }
1022 
1023 /*ARGSUSED*/
1024 static int
1025 ses_enum(topo_mod_t *mod, tnode_t *rnode, const char *name,
1026     topo_instance_t min, topo_instance_t max, void *arg, void *notused)
1027 {
1028 	ses_enum_chassis_t *cp;
1029 	ses_enum_data_t data;
1030 
1031 	/*
1032 	 * Check to make sure we're being invoked sensibly, and that we're not
1033 	 * being invoked as part of a post-processing step.
1034 	 */
1035 	if (strcmp(name, SES_ENCLOSURE) != 0 ||
1036 	    strcmp(topo_node_name(rnode), FM_FMRI_SCHEME_HC) != 0)
1037 		return (0);
1038 
1039 	(void) memset(&data, 0, sizeof (data));
1040 	data.sed_mod = mod;
1041 
1042 	if (disk_list_gather(mod, &data.sed_disks) != 0)
1043 		return (-1);
1044 
1045 	/*
1046 	 * We search both the ses(7D) and sgen(7D) locations, so we are
1047 	 * independent of any particular driver class bindings.
1048 	 */
1049 	if (ses_process_dir("/dev/es", &data) != 0 ||
1050 	    ses_process_dir("/dev/scsi/ses", &data) != 0)
1051 		goto error;
1052 
1053 	/*
1054 	 * Iterate over known chassis and create the necessary nodes.
1055 	 */
1056 	for (cp = topo_list_next(&data.sed_chassis); cp != NULL;
1057 	    cp = topo_list_next(cp)) {
1058 		if (ses_create_chassis(&data, rnode, cp) != 0)
1059 			goto error;
1060 	}
1061 
1062 	ses_data_free(&data);
1063 	return (0);
1064 
1065 error:
1066 	ses_data_free(&data);
1067 	return (-1);
1068 }
1069 
1070 static const topo_modops_t ses_ops =
1071 	{ ses_enum, ses_release };
1072 
1073 static topo_modinfo_t ses_info =
1074 	{ SES_ENCLOSURE, FM_FMRI_SCHEME_HC, SES_VERSION, &ses_ops };
1075 
1076 /*ARGSUSED*/
1077 int
1078 _topo_init(topo_mod_t *mod, topo_version_t version)
1079 {
1080 	if (getenv("TOPOSESDEBUG") != NULL)
1081 		topo_mod_setdebug(mod);
1082 
1083 	topo_mod_dprintf(mod, "initializing %s enumerator\n",
1084 	    SES_ENCLOSURE);
1085 
1086 	return (topo_mod_register(mod, &ses_info, TOPO_VERSION));
1087 }
1088 
1089 void
1090 _topo_fini(topo_mod_t *mod)
1091 {
1092 	topo_mod_unregister(mod);
1093 }
1094