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