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