1940d71d2Seschrock /*
2940d71d2Seschrock  * CDDL HEADER START
3940d71d2Seschrock  *
4940d71d2Seschrock  * The contents of this file are subject to the terms of the
5940d71d2Seschrock  * Common Development and Distribution License (the "License").
6940d71d2Seschrock  * You may not use this file except in compliance with the License.
7940d71d2Seschrock  *
8940d71d2Seschrock  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9940d71d2Seschrock  * or http://www.opensolaris.org/os/licensing.
10940d71d2Seschrock  * See the License for the specific language governing permissions
11940d71d2Seschrock  * and limitations under the License.
12940d71d2Seschrock  *
13940d71d2Seschrock  * When distributing Covered Code, include this CDDL HEADER in each
14940d71d2Seschrock  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15940d71d2Seschrock  * If applicable, add the following below this CDDL HEADER, with the
16940d71d2Seschrock  * fields enclosed by brackets "[]" replaced with your own identifying
17940d71d2Seschrock  * information: Portions Copyright [yyyy] [name of copyright owner]
18940d71d2Seschrock  *
19940d71d2Seschrock  * CDDL HEADER END
20940d71d2Seschrock  */
21940d71d2Seschrock 
22940d71d2Seschrock /*
23*6efb64caSEric Schrock  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24940d71d2Seschrock  * Use is subject to license terms.
25940d71d2Seschrock  */
26940d71d2Seschrock 
27*6efb64caSEric Schrock #include <alloca.h>
28940d71d2Seschrock #include <dirent.h>
29940d71d2Seschrock #include <devid.h>
30940d71d2Seschrock #include <fm/libdiskstatus.h>
31940d71d2Seschrock #include <inttypes.h>
32940d71d2Seschrock #include <pthread.h>
33940d71d2Seschrock #include <strings.h>
34940d71d2Seschrock #include <unistd.h>
35940d71d2Seschrock #include <sys/dkio.h>
36940d71d2Seschrock #include <sys/fm/protocol.h>
37940d71d2Seschrock #include <sys/scsi/scsi_types.h>
38d91236feSeschrock 
39940d71d2Seschrock #include "disk.h"
40d91236feSeschrock #include "ses.h"
41940d71d2Seschrock 
42940d71d2Seschrock #define	SES_VERSION	1
43940d71d2Seschrock 
44940d71d2Seschrock #define	SES_SNAP_FREQ		1000	/* in milliseconds */
45940d71d2Seschrock 
46d91236feSeschrock #define	SES_STATUS_UNAVAIL(s)	\
470b32bb8bSEric Schrock 	((s) == SES_ESC_UNSUPPORTED || (s) >= SES_ESC_NOT_INSTALLED)
48940d71d2Seschrock 
49940d71d2Seschrock /*
50940d71d2Seschrock  * Because multiple SES targets can be part of a single chassis, we construct
51940d71d2Seschrock  * our own hierarchy that takes this into account.  These SES targets may refer
52940d71d2Seschrock  * to the same devices (multiple paths) or to different devices (managing
53940d71d2Seschrock  * different portions of the space).  We arrange things into a
54940d71d2Seschrock  * ses_enum_enclosure_t, which contains a set of ses targets, and a list of all
55940d71d2Seschrock  * nodes found so far.
56940d71d2Seschrock  */
570b32bb8bSEric Schrock typedef struct ses_alt_node {
580b32bb8bSEric Schrock 	topo_list_t		san_link;
590b32bb8bSEric Schrock 	ses_node_t		*san_node;
600b32bb8bSEric Schrock } ses_alt_node_t;
61940d71d2Seschrock 
62940d71d2Seschrock typedef struct ses_enum_node {
63940d71d2Seschrock 	topo_list_t		sen_link;
64940d71d2Seschrock 	ses_node_t		*sen_node;
650b32bb8bSEric Schrock 	topo_list_t		sen_alt_nodes;
66940d71d2Seschrock 	uint64_t		sen_type;
67940d71d2Seschrock 	uint64_t		sen_instance;
68940d71d2Seschrock 	ses_enum_target_t	*sen_target;
69940d71d2Seschrock } ses_enum_node_t;
70940d71d2Seschrock 
71940d71d2Seschrock typedef struct ses_enum_chassis {
72940d71d2Seschrock 	topo_list_t		sec_link;
73940d71d2Seschrock 	topo_list_t		sec_nodes;
74940d71d2Seschrock 	topo_list_t		sec_targets;
75940d71d2Seschrock 	const char		*sec_csn;
76940d71d2Seschrock 	ses_node_t		*sec_enclosure;
77940d71d2Seschrock 	ses_enum_target_t	*sec_target;
78940d71d2Seschrock 	topo_instance_t		sec_instance;
79940d71d2Seschrock 	boolean_t		sec_hasdev;
80d91236feSeschrock 	boolean_t		sec_internal;
81940d71d2Seschrock } ses_enum_chassis_t;
82940d71d2Seschrock 
83940d71d2Seschrock typedef struct ses_enum_data {
84940d71d2Seschrock 	topo_list_t		sed_disks;
85940d71d2Seschrock 	topo_list_t		sed_chassis;
86940d71d2Seschrock 	ses_enum_chassis_t	*sed_current;
87940d71d2Seschrock 	ses_enum_target_t	*sed_target;
88940d71d2Seschrock 	int			sed_errno;
89940d71d2Seschrock 	char			*sed_name;
90940d71d2Seschrock 	topo_mod_t		*sed_mod;
91940d71d2Seschrock 	topo_instance_t		sed_instance;
92940d71d2Seschrock } ses_enum_data_t;
93940d71d2Seschrock 
94940d71d2Seschrock static int ses_present(topo_mod_t *, tnode_t *, topo_version_t, nvlist_t *,
95940d71d2Seschrock     nvlist_t **);
96940d71d2Seschrock static int ses_contains(topo_mod_t *, tnode_t *, topo_version_t, nvlist_t *,
97940d71d2Seschrock     nvlist_t **);
98940d71d2Seschrock 
99940d71d2Seschrock static const topo_method_t ses_component_methods[] = {
100940d71d2Seschrock 	{ TOPO_METH_PRESENT, TOPO_METH_PRESENT_DESC,
101940d71d2Seschrock 	    TOPO_METH_PRESENT_VERSION0, TOPO_STABILITY_INTERNAL, ses_present },
102d91236feSeschrock 	{ TOPO_METH_FAC_ENUM, TOPO_METH_FAC_ENUM_DESC, 0,
103d91236feSeschrock 	    TOPO_STABILITY_INTERNAL, ses_node_enum_facility },
104d91236feSeschrock 	{ NULL }
105d91236feSeschrock };
106d91236feSeschrock 
107d91236feSeschrock static const topo_method_t ses_bay_methods[] = {
108d91236feSeschrock 	{ TOPO_METH_FAC_ENUM, TOPO_METH_FAC_ENUM_DESC, 0,
109d91236feSeschrock 	    TOPO_STABILITY_INTERNAL, ses_node_enum_facility },
110940d71d2Seschrock 	{ NULL }
111940d71d2Seschrock };
112940d71d2Seschrock 
113940d71d2Seschrock static const topo_method_t ses_enclosure_methods[] = {
114940d71d2Seschrock 	{ TOPO_METH_CONTAINS, TOPO_METH_CONTAINS_DESC,
115940d71d2Seschrock 	    TOPO_METH_CONTAINS_VERSION, TOPO_STABILITY_INTERNAL, ses_contains },
116d91236feSeschrock 	{ TOPO_METH_FAC_ENUM, TOPO_METH_FAC_ENUM_DESC, 0,
117d91236feSeschrock 	    TOPO_STABILITY_INTERNAL, ses_enc_enum_facility },
118940d71d2Seschrock 	{ NULL }
119940d71d2Seschrock };
120940d71d2Seschrock 
121940d71d2Seschrock static void
122940d71d2Seschrock ses_target_free(topo_mod_t *mod, ses_enum_target_t *stp)
123940d71d2Seschrock {
124940d71d2Seschrock 	if (--stp->set_refcount == 0) {
125940d71d2Seschrock 		ses_snap_rele(stp->set_snap);
126940d71d2Seschrock 		ses_close(stp->set_target);
127940d71d2Seschrock 		topo_mod_strfree(mod, stp->set_devpath);
128940d71d2Seschrock 		topo_mod_free(mod, stp, sizeof (ses_enum_target_t));
129940d71d2Seschrock 	}
130940d71d2Seschrock }
131940d71d2Seschrock 
132940d71d2Seschrock static void
133940d71d2Seschrock ses_data_free(ses_enum_data_t *sdp)
134940d71d2Seschrock {
135940d71d2Seschrock 	topo_mod_t *mod = sdp->sed_mod;
136940d71d2Seschrock 	ses_enum_chassis_t *cp;
137940d71d2Seschrock 	ses_enum_node_t *np;
138940d71d2Seschrock 	ses_enum_target_t *tp;
1390b32bb8bSEric Schrock 	ses_alt_node_t *ap;
140940d71d2Seschrock 
141940d71d2Seschrock 	while ((cp = topo_list_next(&sdp->sed_chassis)) != NULL) {
142940d71d2Seschrock 		topo_list_delete(&sdp->sed_chassis, cp);
143940d71d2Seschrock 
144940d71d2Seschrock 		while ((np = topo_list_next(&cp->sec_nodes)) != NULL) {
1450b32bb8bSEric Schrock 			while ((ap = topo_list_next(&np->sen_alt_nodes)) !=
1460b32bb8bSEric Schrock 			    NULL) {
1470b32bb8bSEric Schrock 				topo_list_delete(&np->sen_alt_nodes, ap);
1480b32bb8bSEric Schrock 				topo_mod_free(mod, ap, sizeof (ses_alt_node_t));
1490b32bb8bSEric Schrock 			}
150940d71d2Seschrock 			topo_list_delete(&cp->sec_nodes, np);
151940d71d2Seschrock 			topo_mod_free(mod, np, sizeof (ses_enum_node_t));
152940d71d2Seschrock 		}
153940d71d2Seschrock 
154940d71d2Seschrock 		while ((tp = topo_list_next(&cp->sec_targets)) != NULL) {
155940d71d2Seschrock 			topo_list_delete(&cp->sec_targets, tp);
156940d71d2Seschrock 			ses_target_free(mod, tp);
157940d71d2Seschrock 		}
158940d71d2Seschrock 
159940d71d2Seschrock 		topo_mod_free(mod, cp, sizeof (ses_enum_chassis_t));
160940d71d2Seschrock 	}
161940d71d2Seschrock 
162940d71d2Seschrock 	disk_list_free(mod, &sdp->sed_disks);
163d91236feSeschrock 	topo_mod_free(mod, sdp, sizeof (ses_enum_data_t));
164940d71d2Seschrock }
165940d71d2Seschrock 
166940d71d2Seschrock /*
167940d71d2Seschrock  * For enclosure nodes, we have a special contains method.  By default, the hc
168940d71d2Seschrock  * walker will compare the node name and instance number to determine if an
169940d71d2Seschrock  * FMRI matches.  For enclosures where the enumeration order is impossible to
170940d71d2Seschrock  * predict, we instead use the chassis-id as a unique identifier, and ignore
171940d71d2Seschrock  * the instance number.
172940d71d2Seschrock  */
173940d71d2Seschrock static int
174940d71d2Seschrock fmri_contains(topo_mod_t *mod, nvlist_t *nv1, nvlist_t *nv2)
175940d71d2Seschrock {
176940d71d2Seschrock 	uint8_t v1, v2;
177940d71d2Seschrock 	nvlist_t **hcp1, **hcp2;
178940d71d2Seschrock 	int err, i;
179940d71d2Seschrock 	uint_t nhcp1, nhcp2;
180940d71d2Seschrock 	nvlist_t *a1, *a2;
181940d71d2Seschrock 	char *c1, *c2;
182940d71d2Seschrock 	int mindepth;
183940d71d2Seschrock 
184940d71d2Seschrock 	if (nvlist_lookup_uint8(nv1, FM_VERSION, &v1) != 0 ||
185940d71d2Seschrock 	    nvlist_lookup_uint8(nv2, FM_VERSION, &v2) != 0 ||
186940d71d2Seschrock 	    v1 > FM_HC_SCHEME_VERSION || v2 > FM_HC_SCHEME_VERSION)
187940d71d2Seschrock 		return (topo_mod_seterrno(mod, EMOD_FMRI_VERSION));
188940d71d2Seschrock 
189940d71d2Seschrock 	err = nvlist_lookup_nvlist_array(nv1, FM_FMRI_HC_LIST, &hcp1, &nhcp1);
190940d71d2Seschrock 	err |= nvlist_lookup_nvlist_array(nv2, FM_FMRI_HC_LIST, &hcp2, &nhcp2);
191940d71d2Seschrock 	if (err != 0)
192940d71d2Seschrock 		return (topo_mod_seterrno(mod, EMOD_FMRI_NVL));
193940d71d2Seschrock 
194940d71d2Seschrock 	/*
195940d71d2Seschrock 	 * If the chassis-id doesn't match, then these FMRIs are not
196940d71d2Seschrock 	 * equivalent.  If one of the FMRIs doesn't have a chassis ID, then we
197940d71d2Seschrock 	 * have no choice but to fall back to the instance ID.
198940d71d2Seschrock 	 */
199940d71d2Seschrock 	if (nvlist_lookup_nvlist(nv1, FM_FMRI_AUTHORITY, &a1) == 0 &&
200940d71d2Seschrock 	    nvlist_lookup_nvlist(nv2, FM_FMRI_AUTHORITY, &a2) == 0 &&
201940d71d2Seschrock 	    nvlist_lookup_string(a1, FM_FMRI_AUTH_CHASSIS, &c1) == 0 &&
202940d71d2Seschrock 	    nvlist_lookup_string(a2, FM_FMRI_AUTH_CHASSIS, &c2) == 0) {
203940d71d2Seschrock 		if (strcmp(c1, c2) != 0)
204940d71d2Seschrock 			return (0);
205940d71d2Seschrock 
206940d71d2Seschrock 		mindepth = 1;
207940d71d2Seschrock 	} else {
208940d71d2Seschrock 		mindepth = 0;
209940d71d2Seschrock 	}
210940d71d2Seschrock 
211940d71d2Seschrock 	if (nhcp2 < nhcp1)
212940d71d2Seschrock 		return (0);
213940d71d2Seschrock 
214940d71d2Seschrock 	for (i = 0; i < nhcp1; i++) {
215940d71d2Seschrock 		char *nm1 = NULL;
216940d71d2Seschrock 		char *nm2 = NULL;
217940d71d2Seschrock 		char *id1 = NULL;
218940d71d2Seschrock 		char *id2 = NULL;
219940d71d2Seschrock 
220940d71d2Seschrock 		(void) nvlist_lookup_string(hcp1[i], FM_FMRI_HC_NAME, &nm1);
221940d71d2Seschrock 		(void) nvlist_lookup_string(hcp2[i], FM_FMRI_HC_NAME, &nm2);
222940d71d2Seschrock 		(void) nvlist_lookup_string(hcp1[i], FM_FMRI_HC_ID, &id1);
223940d71d2Seschrock 		(void) nvlist_lookup_string(hcp2[i], FM_FMRI_HC_ID, &id2);
224940d71d2Seschrock 		if (nm1 == NULL || nm2 == NULL || id1 == NULL || id2 == NULL)
225940d71d2Seschrock 			return (topo_mod_seterrno(mod, EMOD_FMRI_NVL));
226940d71d2Seschrock 
227940d71d2Seschrock 		if (strcmp(nm1, nm2) == 0 &&
228940d71d2Seschrock 		    (i < mindepth || strcmp(id1, id2) == 0))
229940d71d2Seschrock 			continue;
230940d71d2Seschrock 
231940d71d2Seschrock 		return (0);
232940d71d2Seschrock 	}
233940d71d2Seschrock 
234940d71d2Seschrock 	return (1);
235940d71d2Seschrock }
236940d71d2Seschrock 
237940d71d2Seschrock /*ARGSUSED*/
238940d71d2Seschrock static int
239940d71d2Seschrock ses_contains(topo_mod_t *mod, tnode_t *tn, topo_version_t version,
240940d71d2Seschrock     nvlist_t *in, nvlist_t **out)
241940d71d2Seschrock {
242940d71d2Seschrock 	int ret;
243940d71d2Seschrock 	nvlist_t *nv1, *nv2;
244940d71d2Seschrock 
245940d71d2Seschrock 	if (version > TOPO_METH_CONTAINS_VERSION)
246940d71d2Seschrock 		return (topo_mod_seterrno(mod, EMOD_VER_NEW));
247940d71d2Seschrock 
248940d71d2Seschrock 	if (nvlist_lookup_nvlist(in, TOPO_METH_FMRI_ARG_FMRI, &nv1) != 0 ||
249940d71d2Seschrock 	    nvlist_lookup_nvlist(in, TOPO_METH_FMRI_ARG_SUBFMRI, &nv2) != 0)
250940d71d2Seschrock 		return (topo_mod_seterrno(mod, EMOD_METHOD_INVAL));
251940d71d2Seschrock 
252940d71d2Seschrock 	ret = fmri_contains(mod, nv1, nv2);
253940d71d2Seschrock 	if (ret < 0)
254940d71d2Seschrock 		return (-1);
255940d71d2Seschrock 
256940d71d2Seschrock 	if (topo_mod_nvalloc(mod, out, NV_UNIQUE_NAME) == 0) {
25773e32a37SRobert Johnston 		if (nvlist_add_uint32(*out, TOPO_METH_CONTAINS_RET,
258940d71d2Seschrock 		    ret) == 0)
259940d71d2Seschrock 			return (0);
260940d71d2Seschrock 		else
261940d71d2Seschrock 			nvlist_free(*out);
262940d71d2Seschrock 	}
263940d71d2Seschrock 
264940d71d2Seschrock 	return (-1);
265940d71d2Seschrock 
266940d71d2Seschrock }
267940d71d2Seschrock 
268940d71d2Seschrock /*
269d91236feSeschrock  * Return a current instance of the node.  This is somewhat complicated because
270d91236feSeschrock  * we need to take a new snapshot in order to get the new data, but we don't
271940d71d2Seschrock  * want to be constantly taking SES snapshots if the consumer is going to do a
272940d71d2Seschrock  * series of queries.  So we adopt the strategy of assuming that the SES state
273d91236feSeschrock  * is not going to be rapidly changing, and limit our snapshot frequency to
274d91236feSeschrock  * some defined bounds.
275940d71d2Seschrock  */
276d91236feSeschrock ses_node_t *
2770b32bb8bSEric Schrock ses_node_lock(topo_mod_t *mod, tnode_t *tn)
278940d71d2Seschrock {
279940d71d2Seschrock 	struct timeval tv;
280940d71d2Seschrock 	ses_enum_target_t *tp = topo_node_getspecific(tn);
281940d71d2Seschrock 	uint64_t prev, now;
282940d71d2Seschrock 	ses_snap_t *snap;
283940d71d2Seschrock 	int err;
284d91236feSeschrock 	uint64_t nodeid;
285940d71d2Seschrock 	ses_node_t *np;
286940d71d2Seschrock 
287d91236feSeschrock 	if (tp == NULL) {
288d91236feSeschrock 		(void) topo_mod_seterrno(mod, EMOD_METHOD_NOTSUP);
289d91236feSeschrock 		return (NULL);
290d91236feSeschrock 	}
291940d71d2Seschrock 
2920b32bb8bSEric Schrock 	(void) pthread_mutex_lock(&tp->set_lock);
2930b32bb8bSEric Schrock 
294940d71d2Seschrock 	/*
295940d71d2Seschrock 	 * Determine if we need to take a new snapshot.
296940d71d2Seschrock 	 */
297940d71d2Seschrock 	if (gettimeofday(&tv, NULL) != 0) {
298940d71d2Seschrock 		tv.tv_sec = time(NULL);
299940d71d2Seschrock 		tv.tv_usec = 0;
300940d71d2Seschrock 	}
301940d71d2Seschrock 
302940d71d2Seschrock 	now = tv.tv_sec * 1000 + tv.tv_usec / 1000;
303940d71d2Seschrock 	prev = tp->set_snaptime.tv_sec * 1000 +
304940d71d2Seschrock 	    tp->set_snaptime.tv_usec / 1000;
305940d71d2Seschrock 
306940d71d2Seschrock 	if (now - prev > SES_SNAP_FREQ &&
307940d71d2Seschrock 	    (snap = ses_snap_new(tp->set_target)) != NULL) {
308940d71d2Seschrock 		if (ses_snap_generation(snap) !=
309940d71d2Seschrock 		    ses_snap_generation(tp->set_snap)) {
310940d71d2Seschrock 			/*
311940d71d2Seschrock 			 * If we find ourselves in this situation, we're in
312940d71d2Seschrock 			 * trouble.  The generation count has changed, which
313940d71d2Seschrock 			 * indicates that our current topology is out of date.
314940d71d2Seschrock 			 * But we need to consult the new topology in order to
315940d71d2Seschrock 			 * determine presence at this moment in time.  We can't
316940d71d2Seschrock 			 * go back and change the topo snapshot in situ, so
317d91236feSeschrock 			 * we'll just have to fail the call in this unlikely
318d91236feSeschrock 			 * scenario.
319940d71d2Seschrock 			 */
320940d71d2Seschrock 			ses_snap_rele(snap);
321d91236feSeschrock 			(void) topo_mod_seterrno(mod, EMOD_METHOD_NOTSUP);
3220b32bb8bSEric Schrock 			(void) pthread_mutex_unlock(&tp->set_lock);
323d91236feSeschrock 			return (NULL);
324940d71d2Seschrock 		} else {
325940d71d2Seschrock 			ses_snap_rele(tp->set_snap);
326940d71d2Seschrock 			tp->set_snap = snap;
327940d71d2Seschrock 		}
328940d71d2Seschrock 		tp->set_snaptime = tv;
329940d71d2Seschrock 	}
330940d71d2Seschrock 
331940d71d2Seschrock 	snap = tp->set_snap;
332940d71d2Seschrock 
333940d71d2Seschrock 	verify(topo_prop_get_uint64(tn, TOPO_PGROUP_SES,
334940d71d2Seschrock 	    TOPO_PROP_NODE_ID, &nodeid, &err) == 0);
335940d71d2Seschrock 	verify((np = ses_node_lookup(snap, nodeid)) != NULL);
336d91236feSeschrock 
337d91236feSeschrock 	return (np);
338d91236feSeschrock }
339d91236feSeschrock 
3400b32bb8bSEric Schrock /*ARGSUSED*/
3410b32bb8bSEric Schrock void
3420b32bb8bSEric Schrock ses_node_unlock(topo_mod_t *mod, tnode_t *tn)
3430b32bb8bSEric Schrock {
3440b32bb8bSEric Schrock 	ses_enum_target_t *tp = topo_node_getspecific(tn);
3450b32bb8bSEric Schrock 
3460b32bb8bSEric Schrock 	verify(tp != NULL);
3470b32bb8bSEric Schrock 
3480b32bb8bSEric Schrock 	(void) pthread_mutex_unlock(&tp->set_lock);
3490b32bb8bSEric Schrock }
3500b32bb8bSEric Schrock 
351d91236feSeschrock /*
352d91236feSeschrock  * Determine if the element is present.
353d91236feSeschrock  */
354d91236feSeschrock /*ARGSUSED*/
355d91236feSeschrock static int
356d91236feSeschrock ses_present(topo_mod_t *mod, tnode_t *tn, topo_version_t version,
357d91236feSeschrock     nvlist_t *in, nvlist_t **out)
358d91236feSeschrock {
359d91236feSeschrock 	boolean_t present;
360d91236feSeschrock 	ses_node_t *np;
361d91236feSeschrock 	nvlist_t *props, *nvl;
362d91236feSeschrock 	uint64_t status;
363d91236feSeschrock 
3640b32bb8bSEric Schrock 	if ((np = ses_node_lock(mod, tn)) == NULL)
365d91236feSeschrock 		return (-1);
366d91236feSeschrock 
367940d71d2Seschrock 	verify((props = ses_node_props(np)) != NULL);
368940d71d2Seschrock 	verify(nvlist_lookup_uint64(props,
369940d71d2Seschrock 	    SES_PROP_STATUS_CODE, &status) == 0);
370940d71d2Seschrock 
3710b32bb8bSEric Schrock 	ses_node_unlock(mod, tn);
3720b32bb8bSEric Schrock 
373940d71d2Seschrock 	present = (status != SES_ESC_NOT_INSTALLED);
374940d71d2Seschrock 
375940d71d2Seschrock 	if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0)
376940d71d2Seschrock 		return (topo_mod_seterrno(mod, EMOD_FMRI_NVL));
377940d71d2Seschrock 
378940d71d2Seschrock 	if (nvlist_add_uint32(nvl, TOPO_METH_PRESENT_RET,
379940d71d2Seschrock 	    present) != 0) {
380940d71d2Seschrock 		nvlist_free(nvl);
381940d71d2Seschrock 		return (topo_mod_seterrno(mod, EMOD_FMRI_NVL));
382940d71d2Seschrock 	}
383940d71d2Seschrock 
384940d71d2Seschrock 	*out = nvl;
385940d71d2Seschrock 
386940d71d2Seschrock 	return (0);
387940d71d2Seschrock }
388940d71d2Seschrock 
389940d71d2Seschrock /*
390940d71d2Seschrock  * Sets standard properties for a ses node (enclosure or bay).  This includes
391940d71d2Seschrock  * setting the FRU to be the same as the resource, as well as setting the
392940d71d2Seschrock  * authority information.
393940d71d2Seschrock  */
394940d71d2Seschrock static int
395940d71d2Seschrock ses_set_standard_props(topo_mod_t *mod, tnode_t *tn, nvlist_t *auth,
396940d71d2Seschrock     uint64_t nodeid, const char *path)
397940d71d2Seschrock {
398940d71d2Seschrock 	int err;
399940d71d2Seschrock 	char *product, *chassis;
400940d71d2Seschrock 	nvlist_t *fmri;
401940d71d2Seschrock 	topo_pgroup_info_t pgi;
402940d71d2Seschrock 
403940d71d2Seschrock 	/*
404940d71d2Seschrock 	 * Set the authority explicitly if specified.
405940d71d2Seschrock 	 */
406940d71d2Seschrock 	if (auth) {
407940d71d2Seschrock 		verify(nvlist_lookup_string(auth, FM_FMRI_AUTH_PRODUCT,
408940d71d2Seschrock 		    &product) == 0);
409940d71d2Seschrock 		verify(nvlist_lookup_string(auth, FM_FMRI_AUTH_CHASSIS,
410940d71d2Seschrock 		    &chassis) == 0);
411940d71d2Seschrock 		if (topo_prop_set_string(tn, FM_FMRI_AUTHORITY,
412940d71d2Seschrock 		    FM_FMRI_AUTH_PRODUCT, TOPO_PROP_IMMUTABLE, product,
413940d71d2Seschrock 		    &err) != 0 ||
414940d71d2Seschrock 		    topo_prop_set_string(tn, FM_FMRI_AUTHORITY,
415940d71d2Seschrock 		    FM_FMRI_AUTH_CHASSIS, TOPO_PROP_IMMUTABLE, chassis,
416940d71d2Seschrock 		    &err) != 0 ||
417940d71d2Seschrock 		    topo_prop_set_string(tn, FM_FMRI_AUTHORITY,
418940d71d2Seschrock 		    FM_FMRI_AUTH_SERVER, TOPO_PROP_IMMUTABLE, "",
419940d71d2Seschrock 		    &err) != 0) {
420940d71d2Seschrock 			topo_mod_dprintf(mod, "failed to add authority "
421d91236feSeschrock 			    "properties: %s\n", topo_strerror(err));
422940d71d2Seschrock 			return (topo_mod_seterrno(mod, err));
423940d71d2Seschrock 		}
424940d71d2Seschrock 	}
425940d71d2Seschrock 
426940d71d2Seschrock 	/*
427940d71d2Seschrock 	 * Copy the resource and set that as the FRU.
428940d71d2Seschrock 	 */
429940d71d2Seschrock 	if (topo_node_resource(tn, &fmri, &err) != 0) {
430940d71d2Seschrock 		topo_mod_dprintf(mod,
431940d71d2Seschrock 		    "topo_node_resource() failed : %s\n",
432940d71d2Seschrock 		    topo_strerror(err));
433940d71d2Seschrock 		return (topo_mod_seterrno(mod, err));
434940d71d2Seschrock 	}
435940d71d2Seschrock 
436940d71d2Seschrock 	if (topo_node_fru_set(tn, fmri, 0, &err) != 0) {
437940d71d2Seschrock 		topo_mod_dprintf(mod,
438940d71d2Seschrock 		    "topo_node_fru_set() failed : %s\n",
439940d71d2Seschrock 		    topo_strerror(err));
440940d71d2Seschrock 		nvlist_free(fmri);
441940d71d2Seschrock 		return (topo_mod_seterrno(mod, err));
442940d71d2Seschrock 	}
443940d71d2Seschrock 
444940d71d2Seschrock 	nvlist_free(fmri);
445940d71d2Seschrock 
446940d71d2Seschrock 	/*
447940d71d2Seschrock 	 * Set the SES-specific properties so that consumers can query
448940d71d2Seschrock 	 * additional information about the particular SES element.
449940d71d2Seschrock 	 */
450940d71d2Seschrock 	pgi.tpi_name = TOPO_PGROUP_SES;
451940d71d2Seschrock 	pgi.tpi_namestab = TOPO_STABILITY_PRIVATE;
452940d71d2Seschrock 	pgi.tpi_datastab = TOPO_STABILITY_PRIVATE;
453940d71d2Seschrock 	pgi.tpi_version = TOPO_VERSION;
454940d71d2Seschrock 	if (topo_pgroup_create(tn, &pgi, &err) != 0) {
455940d71d2Seschrock 		topo_mod_dprintf(mod, "failed to create propgroup "
456940d71d2Seschrock 		    "%s: %s\n", TOPO_PGROUP_SES, topo_strerror(err));
457940d71d2Seschrock 		return (-1);
458940d71d2Seschrock 	}
459940d71d2Seschrock 
460940d71d2Seschrock 	if (topo_prop_set_uint64(tn, TOPO_PGROUP_SES,
461940d71d2Seschrock 	    TOPO_PROP_NODE_ID, TOPO_PROP_IMMUTABLE,
462940d71d2Seschrock 	    nodeid, &err) != 0) {
463940d71d2Seschrock 		topo_mod_dprintf(mod,
464940d71d2Seschrock 		    "failed to create property %s: %s\n",
465940d71d2Seschrock 		    TOPO_PROP_NODE_ID, topo_strerror(err));
466940d71d2Seschrock 		return (-1);
467940d71d2Seschrock 	}
468940d71d2Seschrock 
469940d71d2Seschrock 	if (topo_prop_set_string(tn, TOPO_PGROUP_SES,
470940d71d2Seschrock 	    TOPO_PROP_TARGET_PATH, TOPO_PROP_IMMUTABLE,
471940d71d2Seschrock 	    path, &err) != 0) {
472940d71d2Seschrock 		topo_mod_dprintf(mod,
473940d71d2Seschrock 		    "failed to create property %s: %s\n",
474940d71d2Seschrock 		    TOPO_PROP_TARGET_PATH, topo_strerror(err));
475940d71d2Seschrock 		return (-1);
476940d71d2Seschrock 	}
477940d71d2Seschrock 
478940d71d2Seschrock 	return (0);
479940d71d2Seschrock }
480940d71d2Seschrock 
481940d71d2Seschrock /*
482940d71d2Seschrock  * Callback to add a disk to a given bay.  We first check the status-code to
483940d71d2Seschrock  * determine if a disk is present, ignoring those that aren't in an appropriate
4840b32bb8bSEric Schrock  * state.  We then scan the parent bay node's SAS address array to determine
4850b32bb8bSEric Schrock  * possible attached SAS addresses.  We create a disk node if the disk is not
4860b32bb8bSEric Schrock  * SAS or the SES target does not support the necessary pages for this; if we
4870b32bb8bSEric Schrock  * find the SAS address, we create a disk node and also correlate it with
4880b32bb8bSEric Schrock  * the corresponding Solaris device node to fill in the rest of the data.
489940d71d2Seschrock  */
490940d71d2Seschrock static int
491940d71d2Seschrock ses_create_disk(ses_enum_data_t *sdp, tnode_t *pnode, nvlist_t *props)
492940d71d2Seschrock {
493940d71d2Seschrock 	topo_mod_t *mod = sdp->sed_mod;
494940d71d2Seschrock 	uint64_t status;
495940d71d2Seschrock 	nvlist_t **sas;
496940d71d2Seschrock 	uint_t s, nsas;
4970b32bb8bSEric Schrock 	char **paths;
4980b32bb8bSEric Schrock 	int err;
499940d71d2Seschrock 
500940d71d2Seschrock 	/*
501940d71d2Seschrock 	 * Skip devices that are not in a present (and possibly damaged) state.
502940d71d2Seschrock 	 */
503940d71d2Seschrock 	if (nvlist_lookup_uint64(props, SES_PROP_STATUS_CODE, &status) != 0)
504940d71d2Seschrock 		return (0);
505940d71d2Seschrock 
506940d71d2Seschrock 	if (status != SES_ESC_OK &&
507940d71d2Seschrock 	    status != SES_ESC_CRITICAL &&
508940d71d2Seschrock 	    status != SES_ESC_NONCRITICAL &&
509940d71d2Seschrock 	    status != SES_ESC_UNRECOVERABLE &&
510940d71d2Seschrock 	    status != SES_ESC_NO_ACCESS)
511940d71d2Seschrock 		return (0);
512940d71d2Seschrock 
513940d71d2Seschrock 	topo_mod_dprintf(mod, "found attached disk");
514940d71d2Seschrock 
515940d71d2Seschrock 	/*
516940d71d2Seschrock 	 * Create the disk range.
517940d71d2Seschrock 	 */
5180b32bb8bSEric Schrock 	if (topo_node_range_create(mod, pnode, DISK, 0, 0) != 0) {
519940d71d2Seschrock 		topo_mod_dprintf(mod,
520940d71d2Seschrock 		    "topo_node_create_range() failed: %s",
521940d71d2Seschrock 		    topo_mod_errmsg(mod));
522940d71d2Seschrock 		return (-1);
523940d71d2Seschrock 	}
524940d71d2Seschrock 
525940d71d2Seschrock 	/*
526940d71d2Seschrock 	 * Look through all SAS addresses and attempt to correlate them to a
527940d71d2Seschrock 	 * known Solaris device.  If we don't find a matching node, then we
528940d71d2Seschrock 	 * don't enumerate the disk node.
529940d71d2Seschrock 	 */
530940d71d2Seschrock 	if (nvlist_lookup_nvlist_array(props, SES_SAS_PROP_PHYS,
531940d71d2Seschrock 	    &sas, &nsas) != 0)
532940d71d2Seschrock 		return (0);
533940d71d2Seschrock 
5340b32bb8bSEric Schrock 	if (topo_prop_get_string_array(pnode, TOPO_PGROUP_SES,
5350b32bb8bSEric Schrock 	    TOPO_PROP_SAS_ADDR, &paths, &nsas, &err) != 0)
5360b32bb8bSEric Schrock 		return (0);
5370b32bb8bSEric Schrock 
5380b32bb8bSEric Schrock 	err = 0;
5390b32bb8bSEric Schrock 
540940d71d2Seschrock 	for (s = 0; s < nsas; s++) {
5410b32bb8bSEric Schrock 		if (disk_declare_addr(mod, pnode,
5420b32bb8bSEric Schrock 		    &sdp->sed_disks, paths[s]) != 0 &&
5430b32bb8bSEric Schrock 		    topo_mod_errno(mod) != EMOD_NODE_BOUND) {
5440b32bb8bSEric Schrock 			err = -1;
5450b32bb8bSEric Schrock 			break;
5460b32bb8bSEric Schrock 		}
5470b32bb8bSEric Schrock 	}
5480b32bb8bSEric Schrock 
5490b32bb8bSEric Schrock 	for (s = 0; s < nsas; s++)
5500b32bb8bSEric Schrock 		topo_mod_free(mod, paths[s], strlen(paths[s]) + 1);
5510b32bb8bSEric Schrock 	topo_mod_free(mod, paths, nsas * sizeof (char *));
5520b32bb8bSEric Schrock 
5530b32bb8bSEric Schrock 	return (err);
5540b32bb8bSEric Schrock }
5550b32bb8bSEric Schrock 
5560b32bb8bSEric Schrock static int
5570b32bb8bSEric Schrock ses_add_bay_props(topo_mod_t *mod, tnode_t *tn, ses_enum_node_t *snp)
5580b32bb8bSEric Schrock {
5590b32bb8bSEric Schrock 	ses_alt_node_t *ap;
5600b32bb8bSEric Schrock 	ses_node_t *np;
5610b32bb8bSEric Schrock 	nvlist_t *props;
5620b32bb8bSEric Schrock 
5630b32bb8bSEric Schrock 	nvlist_t **phys;
5640b32bb8bSEric Schrock 	uint_t i, j, n_phys, all_phys = 0;
5650b32bb8bSEric Schrock 	char **paths;
5660b32bb8bSEric Schrock 	uint64_t addr;
5670b32bb8bSEric Schrock 	size_t len;
5680b32bb8bSEric Schrock 	int terr, err = -1;
5690b32bb8bSEric Schrock 
5700b32bb8bSEric Schrock 	for (ap = topo_list_next(&snp->sen_alt_nodes); ap != NULL;
5710b32bb8bSEric Schrock 	    ap = topo_list_next(ap)) {
5720b32bb8bSEric Schrock 		np = ap->san_node;
5730b32bb8bSEric Schrock 		props = ses_node_props(np);
5740b32bb8bSEric Schrock 
5750b32bb8bSEric Schrock 		if (nvlist_lookup_nvlist_array(props, SES_SAS_PROP_PHYS,
5760b32bb8bSEric Schrock 		    &phys, &n_phys) != 0)
5770b32bb8bSEric Schrock 			continue;
5780b32bb8bSEric Schrock 
5790b32bb8bSEric Schrock 		all_phys += n_phys;
5800b32bb8bSEric Schrock 	}
5810b32bb8bSEric Schrock 
5820b32bb8bSEric Schrock 	if (all_phys == 0)
5830b32bb8bSEric Schrock 		return (0);
5840b32bb8bSEric Schrock 
5850b32bb8bSEric Schrock 	if ((paths = topo_mod_zalloc(mod, all_phys * sizeof (char *))) == NULL)
5860b32bb8bSEric Schrock 		return (-1);
5870b32bb8bSEric Schrock 
5880b32bb8bSEric Schrock 	for (i = 0, ap = topo_list_next(&snp->sen_alt_nodes); ap != NULL;
5890b32bb8bSEric Schrock 	    ap = topo_list_next(ap)) {
5900b32bb8bSEric Schrock 		np = ap->san_node;
5910b32bb8bSEric Schrock 		props = ses_node_props(np);
5920b32bb8bSEric Schrock 
5930b32bb8bSEric Schrock 		if (nvlist_lookup_nvlist_array(props, SES_SAS_PROP_PHYS,
5940b32bb8bSEric Schrock 		    &phys, &n_phys) != 0)
595940d71d2Seschrock 			continue;
596940d71d2Seschrock 
5970b32bb8bSEric Schrock 		for (j = 0; j < n_phys; j++) {
5980b32bb8bSEric Schrock 			if (nvlist_lookup_uint64(phys[j], SES_SAS_PROP_ADDR,
5990b32bb8bSEric Schrock 			    &addr) != 0)
6000b32bb8bSEric Schrock 				continue;
601940d71d2Seschrock 
6020b32bb8bSEric Schrock 			len = snprintf(NULL, 0, "%016llx", addr) + 1;
6030b32bb8bSEric Schrock 			if ((paths[i] = topo_mod_alloc(mod, len)) == NULL)
6040b32bb8bSEric Schrock 				goto error;
6050b32bb8bSEric Schrock 
6060b32bb8bSEric Schrock 			(void) snprintf(paths[i], len, "%016llx", addr);
6070b32bb8bSEric Schrock 
6080b32bb8bSEric Schrock 			++i;
6090b32bb8bSEric Schrock 		}
610940d71d2Seschrock 	}
611940d71d2Seschrock 
6120b32bb8bSEric Schrock 	err = topo_prop_set_string_array(tn, TOPO_PGROUP_SES,
6130b32bb8bSEric Schrock 	    TOPO_PROP_SAS_ADDR, TOPO_PROP_IMMUTABLE,
6140b32bb8bSEric Schrock 	    (const char **)paths, i, &terr);
6150b32bb8bSEric Schrock 	if (err != 0)
6160b32bb8bSEric Schrock 		err = topo_mod_seterrno(mod, terr);
6170b32bb8bSEric Schrock 
6180b32bb8bSEric Schrock error:
6190b32bb8bSEric Schrock 	for (i = 0; i < all_phys && paths[i] != NULL; i++)
6200b32bb8bSEric Schrock 		topo_mod_free(mod, paths[i], strlen(paths[i]) + 1);
6210b32bb8bSEric Schrock 	topo_mod_free(mod, paths, all_phys * sizeof (char *));
6220b32bb8bSEric Schrock 
6230b32bb8bSEric Schrock 	return (err);
624940d71d2Seschrock }
625940d71d2Seschrock 
626940d71d2Seschrock /*
627940d71d2Seschrock  * Callback to create a basic node (bay, psu, fan, or controller).
628940d71d2Seschrock  */
629940d71d2Seschrock static int
630940d71d2Seschrock ses_create_generic(ses_enum_data_t *sdp, ses_enum_node_t *snp,
631940d71d2Seschrock     tnode_t *pnode, const char *nodename, const char *labelname)
632940d71d2Seschrock {
633940d71d2Seschrock 	ses_node_t *np = snp->sen_node;
634d91236feSeschrock 	ses_node_t *parent;
635940d71d2Seschrock 	uint64_t instance = snp->sen_instance;
636940d71d2Seschrock 	topo_mod_t *mod = sdp->sed_mod;
637940d71d2Seschrock 	nvlist_t *props, *aprops;
638940d71d2Seschrock 	nvlist_t *auth = NULL, *fmri = NULL;
639940d71d2Seschrock 	tnode_t *tn;
640940d71d2Seschrock 	char label[128];
641940d71d2Seschrock 	int err;
642d91236feSeschrock 	char *part = NULL, *serial = NULL, *revision = NULL;
643d91236feSeschrock 	char *desc;
644d91236feSeschrock 	boolean_t report;
645940d71d2Seschrock 
646940d71d2Seschrock 	props = ses_node_props(np);
647940d71d2Seschrock 
648940d71d2Seschrock 	(void) nvlist_lookup_string(props, LIBSES_PROP_PART, &part);
649940d71d2Seschrock 	(void) nvlist_lookup_string(props, LIBSES_PROP_SERIAL, &serial);
650940d71d2Seschrock 
651940d71d2Seschrock 	topo_mod_dprintf(mod, "adding %s %llu", nodename, instance);
652940d71d2Seschrock 
653940d71d2Seschrock 	/*
654940d71d2Seschrock 	 * Create the node.  The interesting information is all copied from the
655940d71d2Seschrock 	 * parent enclosure node, so there is not much to do.
656940d71d2Seschrock 	 */
657940d71d2Seschrock 	if ((auth = topo_mod_auth(mod, pnode)) == NULL)
658940d71d2Seschrock 		goto error;
659940d71d2Seschrock 
660d91236feSeschrock 	/*
661d91236feSeschrock 	 * We want to report revision information for the controller nodes, but
662d91236feSeschrock 	 * we do not get per-element revision information.  However, we do have
663d91236feSeschrock 	 * revision information for the entire enclosure, and we can use the
664d91236feSeschrock 	 * 'reported-via' property to know that this controller corresponds to
665d91236feSeschrock 	 * the given revision information.  This means we cannot get revision
666d91236feSeschrock 	 * information for targets we are not explicitly connected to, but
667d91236feSeschrock 	 * there is little we can do about the situation.
668d91236feSeschrock 	 */
669d91236feSeschrock 	if (strcmp(nodename, CONTROLLER) == 0 &&
670d91236feSeschrock 	    nvlist_lookup_boolean_value(props, SES_PROP_REPORT, &report) == 0 &&
671d91236feSeschrock 	    report) {
672d91236feSeschrock 		for (parent = ses_node_parent(np); parent != NULL;
673d91236feSeschrock 		    parent = ses_node_parent(parent)) {
674d91236feSeschrock 			if (ses_node_type(parent) == SES_NODE_ENCLOSURE) {
675d91236feSeschrock 				(void) nvlist_lookup_string(
676d91236feSeschrock 				    ses_node_props(parent),
677d91236feSeschrock 				    SES_EN_PROP_REV, &revision);
678d91236feSeschrock 				break;
679d91236feSeschrock 			}
680d91236feSeschrock 		}
681d91236feSeschrock 	}
682d91236feSeschrock 
683940d71d2Seschrock 	if ((fmri = topo_mod_hcfmri(mod, pnode, FM_HC_SCHEME_VERSION,
684d91236feSeschrock 	    nodename, (topo_instance_t)instance, NULL, auth, part, revision,
685940d71d2Seschrock 	    serial)) == NULL) {
686940d71d2Seschrock 		topo_mod_dprintf(mod, "topo_mod_hcfmri() failed: %s",
687940d71d2Seschrock 		    topo_mod_errmsg(mod));
688940d71d2Seschrock 		goto error;
689940d71d2Seschrock 	}
690940d71d2Seschrock 
691940d71d2Seschrock 	if ((tn = topo_node_bind(mod, pnode, nodename,
692940d71d2Seschrock 	    instance, fmri)) == NULL) {
693940d71d2Seschrock 		topo_mod_dprintf(mod, "topo_node_bind() failed: %s",
694940d71d2Seschrock 		    topo_mod_errmsg(mod));
695940d71d2Seschrock 		goto error;
696940d71d2Seschrock 	}
697940d71d2Seschrock 
698940d71d2Seschrock 	/*
699d91236feSeschrock 	 * For the node label, we look for the following in order:
700d91236feSeschrock 	 *
701d91236feSeschrock 	 * 	<ses-description>
702d91236feSeschrock 	 * 	<ses-class-description> <instance>
703d91236feSeschrock 	 * 	<default-type-label> <instance>
704940d71d2Seschrock 	 */
705d91236feSeschrock 	if (nvlist_lookup_string(props, SES_PROP_DESCRIPTION, &desc) != 0 ||
706d91236feSeschrock 	    desc[0] == '\0') {
707d91236feSeschrock 		parent = ses_node_parent(np);
708d91236feSeschrock 		aprops = ses_node_props(parent);
709d91236feSeschrock 		if (nvlist_lookup_string(aprops, SES_PROP_CLASS_DESCRIPTION,
710d91236feSeschrock 		    &desc) == 0 && desc[0] != '\0')
711d91236feSeschrock 			labelname = desc;
712d91236feSeschrock 		(void) snprintf(label, sizeof (label), "%s %llu", desc,
713d91236feSeschrock 		    instance);
714d91236feSeschrock 		desc = label;
715d91236feSeschrock 	}
716d91236feSeschrock 
717d91236feSeschrock 	if (topo_node_label_set(tn, desc, &err) != 0)
718940d71d2Seschrock 		goto error;
719940d71d2Seschrock 
720940d71d2Seschrock 	if (ses_set_standard_props(mod, tn, NULL, ses_node_id(np),
721940d71d2Seschrock 	    snp->sen_target->set_devpath) != 0)
722940d71d2Seschrock 		goto error;
723940d71d2Seschrock 
724940d71d2Seschrock 	if (strcmp(nodename, "bay") == 0) {
7250b32bb8bSEric Schrock 		if (ses_add_bay_props(mod, tn, snp) != 0)
7260b32bb8bSEric Schrock 			goto error;
7270b32bb8bSEric Schrock 
728940d71d2Seschrock 		if (ses_create_disk(sdp, tn, props) != 0)
729940d71d2Seschrock 			goto error;
730d91236feSeschrock 
731d91236feSeschrock 		if (topo_method_register(mod, tn, ses_bay_methods) != 0) {
732d91236feSeschrock 			topo_mod_dprintf(mod,
733d91236feSeschrock 			    "topo_method_register() failed: %s",
734d91236feSeschrock 			    topo_mod_errmsg(mod));
735d91236feSeschrock 			goto error;
736d91236feSeschrock 		}
737940d71d2Seschrock 	} else {
738940d71d2Seschrock 		/*
739d91236feSeschrock 		 * Only fan, psu, and controller nodes have a 'present' method.
740d91236feSeschrock 		 * Bay nodes are always present, and disk nodes are present by
741d91236feSeschrock 		 * virtue of being enumerated.
742940d71d2Seschrock 		 */
743940d71d2Seschrock 		if (topo_method_register(mod, tn, ses_component_methods) != 0) {
744940d71d2Seschrock 			topo_mod_dprintf(mod,
745940d71d2Seschrock 			    "topo_method_register() failed: %s",
746940d71d2Seschrock 			    topo_mod_errmsg(mod));
747940d71d2Seschrock 			goto error;
748940d71d2Seschrock 		}
749940d71d2Seschrock 
750940d71d2Seschrock 	}
751940d71d2Seschrock 
752d91236feSeschrock 	snp->sen_target->set_refcount++;
753d91236feSeschrock 	topo_node_setspecific(tn, snp->sen_target);
754d91236feSeschrock 
755940d71d2Seschrock 	nvlist_free(auth);
756940d71d2Seschrock 	nvlist_free(fmri);
757940d71d2Seschrock 	return (0);
758940d71d2Seschrock 
759940d71d2Seschrock error:
760940d71d2Seschrock 	nvlist_free(auth);
761940d71d2Seschrock 	nvlist_free(fmri);
762940d71d2Seschrock 	return (-1);
763940d71d2Seschrock }
764940d71d2Seschrock 
765940d71d2Seschrock /*
766940d71d2Seschrock  * Instantiate any children of a given type.
767940d71d2Seschrock  */
768940d71d2Seschrock static int
769940d71d2Seschrock ses_create_children(ses_enum_data_t *sdp, tnode_t *pnode, uint64_t type,
770d91236feSeschrock     const char *nodename, const char *defaultlabel, ses_enum_chassis_t *cp,
771d91236feSeschrock     boolean_t dorange)
772940d71d2Seschrock {
773940d71d2Seschrock 	topo_mod_t *mod = sdp->sed_mod;
774940d71d2Seschrock 	boolean_t found;
775940d71d2Seschrock 	uint64_t max;
776940d71d2Seschrock 	ses_enum_node_t *snp;
777940d71d2Seschrock 
778940d71d2Seschrock 	/*
779940d71d2Seschrock 	 * First go through and count how many matching nodes we have.
780940d71d2Seschrock 	 */
781940d71d2Seschrock 	max = 0;
782940d71d2Seschrock 	found = B_FALSE;
783940d71d2Seschrock 	for (snp = topo_list_next(&cp->sec_nodes); snp != NULL;
784940d71d2Seschrock 	    snp = topo_list_next(snp)) {
785940d71d2Seschrock 		if (snp->sen_type == type) {
786940d71d2Seschrock 			found = B_TRUE;
787940d71d2Seschrock 			if (snp->sen_instance > max)
788940d71d2Seschrock 				max = snp->sen_instance;
789940d71d2Seschrock 		}
790940d71d2Seschrock 	}
791940d71d2Seschrock 
792940d71d2Seschrock 	/*
793940d71d2Seschrock 	 * No enclosure should export both DEVICE and ARRAY_DEVICE elements.
794940d71d2Seschrock 	 * Since we map both of these to 'disk', if an enclosure does this, we
795940d71d2Seschrock 	 * just ignore the array elements.
796940d71d2Seschrock 	 */
797940d71d2Seschrock 	if (!found ||
798940d71d2Seschrock 	    (type == SES_ET_ARRAY_DEVICE && cp->sec_hasdev))
799940d71d2Seschrock 		return (0);
800940d71d2Seschrock 
801940d71d2Seschrock 	topo_mod_dprintf(mod, "%s: creating %llu %s nodes",
802940d71d2Seschrock 	    cp->sec_csn, max, nodename);
803940d71d2Seschrock 
804d91236feSeschrock 	if (dorange && topo_node_range_create(mod, pnode,
805940d71d2Seschrock 	    nodename, 0, max) != 0) {
806940d71d2Seschrock 		topo_mod_dprintf(mod,
807940d71d2Seschrock 		    "topo_node_create_range() failed: %s",
808940d71d2Seschrock 		    topo_mod_errmsg(mod));
809940d71d2Seschrock 		return (-1);
810940d71d2Seschrock 	}
811940d71d2Seschrock 
812940d71d2Seschrock 	for (snp = topo_list_next(&cp->sec_nodes); snp != NULL;
813940d71d2Seschrock 	    snp = topo_list_next(snp)) {
814940d71d2Seschrock 		if (snp->sen_type == type) {
815940d71d2Seschrock 			if (ses_create_generic(sdp, snp, pnode,
816940d71d2Seschrock 			    nodename, defaultlabel) != 0)
817940d71d2Seschrock 				return (-1);
818940d71d2Seschrock 		}
819940d71d2Seschrock 	}
820940d71d2Seschrock 
821940d71d2Seschrock 	return (0);
822940d71d2Seschrock }
823940d71d2Seschrock 
824940d71d2Seschrock /*
825940d71d2Seschrock  * Instantiate a new chassis instance in the topology.
826940d71d2Seschrock  */
827940d71d2Seschrock static int
828940d71d2Seschrock ses_create_chassis(ses_enum_data_t *sdp, tnode_t *pnode, ses_enum_chassis_t *cp)
829940d71d2Seschrock {
830940d71d2Seschrock 	topo_mod_t *mod = sdp->sed_mod;
831940d71d2Seschrock 	nvlist_t *props;
832940d71d2Seschrock 	char *raw_manufacturer, *raw_model, *raw_revision;
833940d71d2Seschrock 	char *manufacturer = NULL, *model = NULL, *product = NULL;
834940d71d2Seschrock 	char *revision = NULL;
835940d71d2Seschrock 	char *serial;
836*6efb64caSEric Schrock 	char **paths;
837940d71d2Seschrock 	size_t prodlen;
838940d71d2Seschrock 	tnode_t *tn;
839940d71d2Seschrock 	nvlist_t *fmri = NULL, *auth = NULL;
840940d71d2Seschrock 	int ret = -1;
841940d71d2Seschrock 	ses_enum_node_t *snp;
842*6efb64caSEric Schrock 	ses_enum_target_t *stp;
843*6efb64caSEric Schrock 	int i, err;
844940d71d2Seschrock 
845d91236feSeschrock 	/*
846d91236feSeschrock 	 * Ignore any internal enclosures.
847d91236feSeschrock 	 */
848d91236feSeschrock 	if (cp->sec_internal)
849d91236feSeschrock 		return (0);
850d91236feSeschrock 
851940d71d2Seschrock 	/*
852940d71d2Seschrock 	 * Check to see if there are any devices presennt in the chassis.  If
853940d71d2Seschrock 	 * not, ignore the chassis alltogether.  This is most useful for
854940d71d2Seschrock 	 * ignoring internal HBAs that present a SES target but don't actually
855940d71d2Seschrock 	 * manage any of the devices.
856940d71d2Seschrock 	 */
857940d71d2Seschrock 	for (snp = topo_list_next(&cp->sec_nodes); snp != NULL;
858940d71d2Seschrock 	    snp = topo_list_next(snp)) {
859940d71d2Seschrock 		if (snp->sen_type == SES_ET_DEVICE ||
860940d71d2Seschrock 		    snp->sen_type == SES_ET_ARRAY_DEVICE)
861940d71d2Seschrock 			break;
862940d71d2Seschrock 	}
863940d71d2Seschrock 
864940d71d2Seschrock 	if (snp == NULL)
865940d71d2Seschrock 		return (0);
866940d71d2Seschrock 
867940d71d2Seschrock 	props = ses_node_props(cp->sec_enclosure);
868940d71d2Seschrock 
869940d71d2Seschrock 	/*
870940d71d2Seschrock 	 * We use the following property mappings:
871940d71d2Seschrock 	 *
872940d71d2Seschrock 	 * 	manufacturer		vendor-id
873940d71d2Seschrock 	 * 	model			product-id
874940d71d2Seschrock 	 * 	serial-number		libses-chassis-serial
875940d71d2Seschrock 	 */
876940d71d2Seschrock 	verify(nvlist_lookup_string(props, SES_EN_PROP_VID,
877940d71d2Seschrock 	    &raw_manufacturer) == 0);
878940d71d2Seschrock 	verify(nvlist_lookup_string(props, SES_EN_PROP_PID, &raw_model) == 0);
879940d71d2Seschrock 	verify(nvlist_lookup_string(props, SES_EN_PROP_REV,
880940d71d2Seschrock 	    &raw_revision) == 0);
881940d71d2Seschrock 	verify(nvlist_lookup_string(props, LIBSES_EN_PROP_CSN, &serial) == 0);
882940d71d2Seschrock 
883940d71d2Seschrock 	/*
884940d71d2Seschrock 	 * To construct the authority information, we 'clean' each string by
885940d71d2Seschrock 	 * removing any offensive characters and trimmming whitespace.  For the
886940d71d2Seschrock 	 * 'product-id', we use a concatenation of 'manufacturer-model'.  We
887940d71d2Seschrock 	 * also take the numerical serial number and convert it to a string.
888940d71d2Seschrock 	 */
889940d71d2Seschrock 	if ((manufacturer = disk_auth_clean(mod, raw_manufacturer)) == NULL ||
890940d71d2Seschrock 	    (model = disk_auth_clean(mod, raw_model)) == NULL ||
891940d71d2Seschrock 	    (revision = disk_auth_clean(mod, raw_revision)) == NULL) {
892940d71d2Seschrock 		goto error;
893940d71d2Seschrock 	}
894940d71d2Seschrock 
895940d71d2Seschrock 	prodlen = strlen(manufacturer) + strlen(model) + 2;
896940d71d2Seschrock 	if ((product = topo_mod_alloc(mod, prodlen)) == NULL)
897940d71d2Seschrock 		goto error;
898940d71d2Seschrock 
899940d71d2Seschrock 	(void) snprintf(product, prodlen, "%s-%s", manufacturer, model);
900940d71d2Seschrock 
901940d71d2Seschrock 	/*
902940d71d2Seschrock 	 * Construct the topo node and bind it to our parent.
903940d71d2Seschrock 	 */
904940d71d2Seschrock 	if (topo_mod_nvalloc(mod, &auth, NV_UNIQUE_NAME) != 0)
905940d71d2Seschrock 		goto error;
906940d71d2Seschrock 
907940d71d2Seschrock 	if (nvlist_add_string(auth, FM_FMRI_AUTH_PRODUCT, product) != 0 ||
908940d71d2Seschrock 	    nvlist_add_string(auth, FM_FMRI_AUTH_CHASSIS, serial) != 0) {
909940d71d2Seschrock 		(void) topo_mod_seterrno(mod, EMOD_NVL_INVAL);
910940d71d2Seschrock 		goto error;
911940d71d2Seschrock 	}
912940d71d2Seschrock 
913940d71d2Seschrock 	/*
914940d71d2Seschrock 	 * We pass NULL for the parent FMRI because there is no resource
915940d71d2Seschrock 	 * associated with it.  For the toplevel enclosure, we leave the
916940d71d2Seschrock 	 * serial/part/revision portions empty, which are reserved for
917940d71d2Seschrock 	 * individual components within the chassis.
918940d71d2Seschrock 	 */
919940d71d2Seschrock 	if ((fmri = topo_mod_hcfmri(mod, NULL, FM_HC_SCHEME_VERSION,
920940d71d2Seschrock 	    SES_ENCLOSURE, cp->sec_instance, NULL, auth,
921940d71d2Seschrock 	    model, revision, serial)) == NULL) {
922940d71d2Seschrock 		topo_mod_dprintf(mod, "topo_mod_hcfmri() failed: %s",
923940d71d2Seschrock 		    topo_mod_errmsg(mod));
924940d71d2Seschrock 		goto error;
925940d71d2Seschrock 	}
926940d71d2Seschrock 
927940d71d2Seschrock 	if ((tn = topo_node_bind(mod, pnode, SES_ENCLOSURE,
928940d71d2Seschrock 	    cp->sec_instance, fmri)) == NULL) {
929940d71d2Seschrock 		topo_mod_dprintf(mod, "topo_node_bind() failed: %s",
930940d71d2Seschrock 		    topo_mod_errmsg(mod));
931940d71d2Seschrock 		goto error;
932940d71d2Seschrock 	}
933940d71d2Seschrock 
934940d71d2Seschrock 	if (topo_method_register(mod, tn, ses_enclosure_methods) != 0) {
935940d71d2Seschrock 		topo_mod_dprintf(mod,
936940d71d2Seschrock 		    "topo_method_register() failed: %s",
937940d71d2Seschrock 		    topo_mod_errmsg(mod));
938940d71d2Seschrock 		goto error;
939940d71d2Seschrock 	}
940940d71d2Seschrock 
941940d71d2Seschrock 	if (ses_set_standard_props(mod, tn, auth,
942940d71d2Seschrock 	    ses_node_id(cp->sec_enclosure), cp->sec_target->set_devpath) != 0)
943940d71d2Seschrock 		goto error;
944940d71d2Seschrock 
945*6efb64caSEric Schrock 	/*
946*6efb64caSEric Schrock 	 * For enclosures, we want to include all possible targets (for upgrade
947*6efb64caSEric Schrock 	 * purposes).
948*6efb64caSEric Schrock 	 */
949*6efb64caSEric Schrock 	for (i = 0, stp = topo_list_next(&cp->sec_targets); stp != NULL;
950*6efb64caSEric Schrock 	    stp = topo_list_next(stp), i++)
951*6efb64caSEric Schrock 		;
952*6efb64caSEric Schrock 
953*6efb64caSEric Schrock 	verify(i != 0);
954*6efb64caSEric Schrock 	paths = alloca(i * sizeof (char *));
955*6efb64caSEric Schrock 
956*6efb64caSEric Schrock 	for (i = 0, stp = topo_list_next(&cp->sec_targets); stp != NULL;
957*6efb64caSEric Schrock 	    stp = topo_list_next(stp), i++)
958*6efb64caSEric Schrock 		paths[i] = stp->set_devpath;
959*6efb64caSEric Schrock 
960*6efb64caSEric Schrock 
961*6efb64caSEric Schrock 	if (topo_prop_set_string_array(tn, TOPO_PGROUP_SES,
962*6efb64caSEric Schrock 	    TOPO_PROP_PATHS, TOPO_PROP_IMMUTABLE, (const char **)paths,
963*6efb64caSEric Schrock 	    i, &err) != 0) {
964*6efb64caSEric Schrock 		topo_mod_dprintf(mod,
965*6efb64caSEric Schrock 		    "failed to create property %s: %s\n",
966*6efb64caSEric Schrock 		    TOPO_PROP_PATHS, topo_strerror(err));
967*6efb64caSEric Schrock 		goto error;
968*6efb64caSEric Schrock 	}
969*6efb64caSEric Schrock 
970940d71d2Seschrock 	/*
971940d71d2Seschrock 	 * Create the nodes for power supplies, fans, and devices.
972940d71d2Seschrock 	 */
973940d71d2Seschrock 	if (ses_create_children(sdp, tn, SES_ET_POWER_SUPPLY,
974d91236feSeschrock 	    PSU, "PSU", cp, B_TRUE) != 0 ||
975940d71d2Seschrock 	    ses_create_children(sdp, tn, SES_ET_COOLING,
976d91236feSeschrock 	    FAN, "FAN", cp, B_TRUE) != 0 ||
977940d71d2Seschrock 	    ses_create_children(sdp, tn, SES_ET_ESC_ELECTRONICS,
978d91236feSeschrock 	    CONTROLLER, "CONTROLLER", cp, B_TRUE) != 0 ||
979940d71d2Seschrock 	    ses_create_children(sdp, tn, SES_ET_DEVICE,
980d91236feSeschrock 	    BAY, "BAY", cp, B_TRUE) != 0 ||
981940d71d2Seschrock 	    ses_create_children(sdp, tn, SES_ET_ARRAY_DEVICE,
982d91236feSeschrock 	    BAY, "BAY", cp, B_TRUE) != 0)
983940d71d2Seschrock 		goto error;
984940d71d2Seschrock 
985*6efb64caSEric Schrock 	cp->sec_target->set_refcount++;
986*6efb64caSEric Schrock 	topo_node_setspecific(tn, cp->sec_target);
987d91236feSeschrock 
988940d71d2Seschrock 	ret = 0;
989940d71d2Seschrock error:
990940d71d2Seschrock 	topo_mod_strfree(mod, manufacturer);
991940d71d2Seschrock 	topo_mod_strfree(mod, model);
992940d71d2Seschrock 	topo_mod_strfree(mod, revision);
993940d71d2Seschrock 	topo_mod_strfree(mod, product);
994940d71d2Seschrock 
995940d71d2Seschrock 	nvlist_free(fmri);
996940d71d2Seschrock 	nvlist_free(auth);
997940d71d2Seschrock 	return (ret);
998940d71d2Seschrock }
999940d71d2Seschrock 
1000d91236feSeschrock /*
1001d91236feSeschrock  * Create a bay node explicitly enumerated via XML.
1002d91236feSeschrock  */
1003d91236feSeschrock static int
1004d91236feSeschrock ses_create_bays(ses_enum_data_t *sdp, tnode_t *pnode)
1005d91236feSeschrock {
1006d91236feSeschrock 	topo_mod_t *mod = sdp->sed_mod;
1007d91236feSeschrock 	ses_enum_chassis_t *cp;
1008d91236feSeschrock 
1009d91236feSeschrock 	/*
1010d91236feSeschrock 	 * Iterate over chassis looking for an internal enclosure.  This
1011d91236feSeschrock 	 * property is set via a vendor-specific plugin, and there should only
1012d91236feSeschrock 	 * ever be a single internal chassis in a system.
1013d91236feSeschrock 	 */
1014d91236feSeschrock 	for (cp = topo_list_next(&sdp->sed_chassis); cp != NULL;
1015d91236feSeschrock 	    cp = topo_list_next(cp)) {
1016d91236feSeschrock 		if (cp->sec_internal)
1017d91236feSeschrock 			break;
1018d91236feSeschrock 	}
1019d91236feSeschrock 
1020d91236feSeschrock 	if (cp == NULL) {
1021d91236feSeschrock 		topo_mod_dprintf(mod, "failed to find internal chassis\n");
1022d91236feSeschrock 		return (-1);
1023d91236feSeschrock 	}
1024d91236feSeschrock 
1025d91236feSeschrock 	if (ses_create_children(sdp, pnode, SES_ET_DEVICE,
1026d91236feSeschrock 	    BAY, "BAY", cp, B_FALSE) != 0 ||
1027d91236feSeschrock 	    ses_create_children(sdp, pnode, SES_ET_ARRAY_DEVICE,
1028d91236feSeschrock 	    BAY, "BAY", cp, B_FALSE) != 0)
1029d91236feSeschrock 		return (-1);
1030d91236feSeschrock 
1031d91236feSeschrock 	return (0);
1032d91236feSeschrock }
1033940d71d2Seschrock /*
1034940d71d2Seschrock  * Gather nodes from the current SES target into our chassis list, merging the
1035940d71d2Seschrock  * results if necessary.
1036940d71d2Seschrock  */
1037940d71d2Seschrock static ses_walk_action_t
1038940d71d2Seschrock ses_enum_gather(ses_node_t *np, void *data)
1039940d71d2Seschrock {
1040940d71d2Seschrock 	nvlist_t *props = ses_node_props(np);
1041940d71d2Seschrock 	ses_enum_data_t *sdp = data;
1042940d71d2Seschrock 	topo_mod_t *mod = sdp->sed_mod;
1043940d71d2Seschrock 	ses_enum_chassis_t *cp;
1044940d71d2Seschrock 	ses_enum_node_t *snp;
10450b32bb8bSEric Schrock 	ses_alt_node_t *sap;
1046940d71d2Seschrock 	char *csn;
1047940d71d2Seschrock 	uint64_t instance, type;
1048d91236feSeschrock 	uint64_t prevstatus, status;
1049*6efb64caSEric Schrock 	boolean_t report, internal, ident;
1050940d71d2Seschrock 
1051940d71d2Seschrock 	if (ses_node_type(np) == SES_NODE_ENCLOSURE) {
1052940d71d2Seschrock 		/*
1053940d71d2Seschrock 		 * If we have already identified the chassis for this target,
1054940d71d2Seschrock 		 * then this is a secondary enclosure and we should ignore it,
1055940d71d2Seschrock 		 * along with the rest of the tree (since this is depth-first).
1056940d71d2Seschrock 		 */
1057940d71d2Seschrock 		if (sdp->sed_current != NULL)
1058940d71d2Seschrock 			return (SES_WALK_ACTION_TERMINATE);
1059940d71d2Seschrock 
1060940d71d2Seschrock 		/*
1061940d71d2Seschrock 		 * Go through the list of chassis we have seen so far and see
1062940d71d2Seschrock 		 * if this serial number matches one of the known values.
1063940d71d2Seschrock 		 */
1064940d71d2Seschrock 		if (nvlist_lookup_string(props, LIBSES_EN_PROP_CSN,
1065940d71d2Seschrock 		    &csn) != 0)
1066940d71d2Seschrock 			return (SES_WALK_ACTION_TERMINATE);
1067940d71d2Seschrock 
1068940d71d2Seschrock 		for (cp = topo_list_next(&sdp->sed_chassis); cp != NULL;
1069940d71d2Seschrock 		    cp = topo_list_next(cp)) {
1070940d71d2Seschrock 			if (strcmp(cp->sec_csn, csn) == 0) {
1071940d71d2Seschrock 				topo_mod_dprintf(mod, "%s: part of already "
1072940d71d2Seschrock 				    "known chassis %s", sdp->sed_name, csn);
1073940d71d2Seschrock 				break;
1074940d71d2Seschrock 			}
1075940d71d2Seschrock 		}
1076940d71d2Seschrock 
1077940d71d2Seschrock 		if (cp == NULL) {
1078940d71d2Seschrock 			topo_mod_dprintf(mod, "%s: creating chassis %s",
1079940d71d2Seschrock 			    sdp->sed_name, csn);
1080940d71d2Seschrock 
1081940d71d2Seschrock 			if ((cp = topo_mod_zalloc(mod,
1082940d71d2Seschrock 			    sizeof (ses_enum_chassis_t))) == NULL)
1083940d71d2Seschrock 				goto error;
1084940d71d2Seschrock 
1085d91236feSeschrock 			if (nvlist_lookup_boolean_value(props,
1086d91236feSeschrock 			    LIBSES_EN_PROP_INTERNAL, &internal) == 0)
1087d91236feSeschrock 				cp->sec_internal = internal;
1088d91236feSeschrock 
1089940d71d2Seschrock 			cp->sec_csn = csn;
1090940d71d2Seschrock 			cp->sec_enclosure = np;
1091940d71d2Seschrock 			cp->sec_target = sdp->sed_target;
1092940d71d2Seschrock 			cp->sec_instance = sdp->sed_instance++;
1093940d71d2Seschrock 			topo_list_append(&sdp->sed_chassis, cp);
1094*6efb64caSEric Schrock 		} else {
1095*6efb64caSEric Schrock 			/*
1096*6efb64caSEric Schrock 			 * For the enclosure node, it is possible to have
1097*6efb64caSEric Schrock 			 * multiple targets, only one of which support an
1098*6efb64caSEric Schrock 			 * enclosure element descriptor.  We assume that this
1099*6efb64caSEric Schrock 			 * is the one that is responsible for managing the
1100*6efb64caSEric Schrock 			 * enclosure itself, so we prefer one with the
1101*6efb64caSEric Schrock 			 * SES_PROP_IDENT property (which is only present for a
1102*6efb64caSEric Schrock 			 * target that has an enclosure element descriptor).
1103*6efb64caSEric Schrock 			 */
1104*6efb64caSEric Schrock 			if (nvlist_lookup_boolean_value(props, SES_PROP_IDENT,
1105*6efb64caSEric Schrock 			    &ident) == 0) {
1106*6efb64caSEric Schrock 				topo_mod_dprintf(mod,
1107*6efb64caSEric Schrock 				    "overriding enclosure node");
1108*6efb64caSEric Schrock 				cp->sec_enclosure = np;
1109*6efb64caSEric Schrock 				cp->sec_target = sdp->sed_target;
1110*6efb64caSEric Schrock 			}
1111940d71d2Seschrock 		}
1112940d71d2Seschrock 
1113940d71d2Seschrock 		topo_list_append(&cp->sec_targets, sdp->sed_target);
1114940d71d2Seschrock 		sdp->sed_current = cp;
1115940d71d2Seschrock 
1116940d71d2Seschrock 	} else if (ses_node_type(np) == SES_NODE_ELEMENT) {
1117940d71d2Seschrock 		/*
1118940d71d2Seschrock 		 * If we haven't yet seen an enclosure node and identified the
1119940d71d2Seschrock 		 * current chassis, something is very wrong; bail out.
1120940d71d2Seschrock 		 */
1121940d71d2Seschrock 		if (sdp->sed_current == NULL)
1122940d71d2Seschrock 			return (SES_WALK_ACTION_TERMINATE);
1123940d71d2Seschrock 
1124940d71d2Seschrock 		/*
1125940d71d2Seschrock 		 * If this isn't one of the element types we care about, then
1126940d71d2Seschrock 		 * ignore it.
1127940d71d2Seschrock 		 */
1128940d71d2Seschrock 		verify(nvlist_lookup_uint64(props, SES_PROP_ELEMENT_TYPE,
1129940d71d2Seschrock 		    &type) == 0);
1130940d71d2Seschrock 		if (type != SES_ET_DEVICE &&
1131940d71d2Seschrock 		    type != SES_ET_ARRAY_DEVICE &&
1132940d71d2Seschrock 		    type != SES_ET_COOLING &&
1133940d71d2Seschrock 		    type != SES_ET_POWER_SUPPLY &&
1134940d71d2Seschrock 		    type != SES_ET_ESC_ELECTRONICS)
1135940d71d2Seschrock 			return (SES_WALK_ACTION_CONTINUE);
1136940d71d2Seschrock 
1137940d71d2Seschrock 		/*
1138940d71d2Seschrock 		 * Get the current instance number and see if we already know
1139940d71d2Seschrock 		 * about this element.  If so, it means we have multiple paths
1140940d71d2Seschrock 		 * to the same elements, and we should ignore the current path.
1141940d71d2Seschrock 		 */
1142940d71d2Seschrock 		verify(nvlist_lookup_uint64(props, SES_PROP_ELEMENT_CLASS_INDEX,
1143940d71d2Seschrock 		    &instance) == 0);
1144940d71d2Seschrock 		if (type == SES_ET_DEVICE || type == SES_ET_ARRAY_DEVICE)
1145940d71d2Seschrock 			(void) nvlist_lookup_uint64(props, SES_PROP_BAY_NUMBER,
1146940d71d2Seschrock 			    &instance);
1147940d71d2Seschrock 
1148940d71d2Seschrock 		cp = sdp->sed_current;
1149940d71d2Seschrock 
1150940d71d2Seschrock 		for (snp = topo_list_next(&cp->sec_nodes); snp != NULL;
1151940d71d2Seschrock 		    snp = topo_list_next(snp)) {
1152940d71d2Seschrock 			if (snp->sen_type == type &&
1153940d71d2Seschrock 			    snp->sen_instance == instance)
1154d91236feSeschrock 				break;
1155d91236feSeschrock 		}
1156d91236feSeschrock 
1157d91236feSeschrock 		/*
1158d91236feSeschrock 		 * We prefer the new element under the following circumstances:
1159d91236feSeschrock 		 *
1160d91236feSeschrock 		 * - The currently known element's status is unknown or not
1161d91236feSeschrock 		 *   available, but the new element has a known status.  This
1162d91236feSeschrock 		 *   occurs if a given element is only available through a
1163d91236feSeschrock 		 *   particular target.
1164d91236feSeschrock 		 *
1165d91236feSeschrock 		 * - This is an ESC_ELECTRONICS element, and the 'reported-via'
1166d91236feSeschrock 		 *   property is set.  This allows us to get reliable firmware
1167d91236feSeschrock 		 *   revision information from the enclosure node.
1168d91236feSeschrock 		 */
1169d91236feSeschrock 		if (snp != NULL) {
1170d91236feSeschrock 			if (nvlist_lookup_uint64(
1171d91236feSeschrock 			    ses_node_props(snp->sen_node),
1172d91236feSeschrock 			    SES_PROP_STATUS_CODE, &prevstatus) != 0)
1173d91236feSeschrock 				prevstatus = SES_ESC_UNSUPPORTED;
1174d91236feSeschrock 			if (nvlist_lookup_uint64(
1175d91236feSeschrock 			    props, SES_PROP_STATUS_CODE, &status) != 0)
1176d91236feSeschrock 				status = SES_ESC_UNSUPPORTED;
1177d91236feSeschrock 			if (nvlist_lookup_boolean_value(
1178d91236feSeschrock 			    props, SES_PROP_REPORT, &report) != 0)
1179d91236feSeschrock 				report = B_FALSE;
1180d91236feSeschrock 
1181d91236feSeschrock 			if ((SES_STATUS_UNAVAIL(prevstatus) &&
1182d91236feSeschrock 			    !SES_STATUS_UNAVAIL(status)) ||
1183d91236feSeschrock 			    (type == SES_ET_ESC_ELECTRONICS &&
1184d91236feSeschrock 			    report)) {
1185d91236feSeschrock 				snp->sen_node = np;
1186d91236feSeschrock 				snp->sen_target = sdp->sed_target;
1187d91236feSeschrock 			}
1188d91236feSeschrock 
11890b32bb8bSEric Schrock 			if ((sap = topo_mod_zalloc(mod,
11900b32bb8bSEric Schrock 			    sizeof (ses_alt_node_t))) == NULL)
11910b32bb8bSEric Schrock 				goto error;
11920b32bb8bSEric Schrock 
11930b32bb8bSEric Schrock 			sap->san_node = np;
11940b32bb8bSEric Schrock 			topo_list_append(&snp->sen_alt_nodes, sap);
11950b32bb8bSEric Schrock 
1196d91236feSeschrock 			return (SES_WALK_ACTION_CONTINUE);
1197940d71d2Seschrock 		}
1198940d71d2Seschrock 
1199940d71d2Seschrock 		if ((snp = topo_mod_zalloc(mod,
1200940d71d2Seschrock 		    sizeof (ses_enum_node_t))) == NULL)
1201940d71d2Seschrock 			goto error;
1202940d71d2Seschrock 
12030b32bb8bSEric Schrock 		if ((sap = topo_mod_zalloc(mod,
12040b32bb8bSEric Schrock 		    sizeof (ses_alt_node_t))) == NULL) {
12050b32bb8bSEric Schrock 			topo_mod_free(mod, snp, sizeof (ses_enum_node_t));
12060b32bb8bSEric Schrock 			goto error;
12070b32bb8bSEric Schrock 		}
12080b32bb8bSEric Schrock 
1209940d71d2Seschrock 		topo_mod_dprintf(mod, "%s: adding node (%llu, %llu)",
1210940d71d2Seschrock 		    sdp->sed_name, type, instance);
1211940d71d2Seschrock 		snp->sen_node = np;
1212940d71d2Seschrock 		snp->sen_type = type;
1213940d71d2Seschrock 		snp->sen_instance = instance;
1214940d71d2Seschrock 		snp->sen_target = sdp->sed_target;
12150b32bb8bSEric Schrock 		sap->san_node = np;
12160b32bb8bSEric Schrock 		topo_list_append(&snp->sen_alt_nodes, sap);
1217940d71d2Seschrock 		topo_list_append(&cp->sec_nodes, snp);
1218940d71d2Seschrock 
1219940d71d2Seschrock 		if (type == SES_ET_DEVICE)
1220940d71d2Seschrock 			cp->sec_hasdev = B_TRUE;
1221940d71d2Seschrock 	}
1222940d71d2Seschrock 
1223940d71d2Seschrock 	return (SES_WALK_ACTION_CONTINUE);
1224940d71d2Seschrock 
1225940d71d2Seschrock error:
1226940d71d2Seschrock 	sdp->sed_errno = -1;
1227940d71d2Seschrock 	return (SES_WALK_ACTION_TERMINATE);
1228940d71d2Seschrock }
1229940d71d2Seschrock 
1230940d71d2Seschrock static int
1231940d71d2Seschrock ses_process_dir(const char *dirpath, ses_enum_data_t *sdp)
1232940d71d2Seschrock {
1233940d71d2Seschrock 	topo_mod_t *mod = sdp->sed_mod;
1234940d71d2Seschrock 	DIR *dir;
1235940d71d2Seschrock 	struct dirent *dp;
1236940d71d2Seschrock 	char path[PATH_MAX];
1237940d71d2Seschrock 	ses_enum_target_t *stp;
1238940d71d2Seschrock 	int err = -1;
1239940d71d2Seschrock 
1240940d71d2Seschrock 	/*
1241940d71d2Seschrock 	 * Open the SES target directory and iterate over any available
1242940d71d2Seschrock 	 * targets.
1243940d71d2Seschrock 	 */
1244940d71d2Seschrock 	if ((dir = opendir(dirpath)) == NULL) {
1245940d71d2Seschrock 		/*
1246940d71d2Seschrock 		 * If the SES target directory does not exist, then return as if
1247940d71d2Seschrock 		 * there are no active targets.
1248940d71d2Seschrock 		 */
1249940d71d2Seschrock 		topo_mod_dprintf(mod, "failed to open ses "
1250940d71d2Seschrock 		    "directory '%s'", dirpath);
1251940d71d2Seschrock 		return (0);
1252940d71d2Seschrock 	}
1253940d71d2Seschrock 
1254940d71d2Seschrock 	while ((dp = readdir(dir)) != NULL) {
1255940d71d2Seschrock 		if (strcmp(dp->d_name, ".") == 0 ||
1256940d71d2Seschrock 		    strcmp(dp->d_name, "..") == 0)
1257940d71d2Seschrock 			continue;
1258940d71d2Seschrock 
1259940d71d2Seschrock 		/*
1260940d71d2Seschrock 		 * Create a new target instance and take a snapshot.
1261940d71d2Seschrock 		 */
1262940d71d2Seschrock 		if ((stp = topo_mod_zalloc(mod,
1263940d71d2Seschrock 		    sizeof (ses_enum_target_t))) == NULL)
1264940d71d2Seschrock 			goto error;
1265940d71d2Seschrock 
12660b32bb8bSEric Schrock 		(void) pthread_mutex_init(&stp->set_lock, NULL);
12670b32bb8bSEric Schrock 
1268940d71d2Seschrock 		(void) snprintf(path, sizeof (path), "%s/%s", dirpath,
1269940d71d2Seschrock 		    dp->d_name);
1270940d71d2Seschrock 
1271940d71d2Seschrock 		/*
1272940d71d2Seschrock 		 * We keep track of the SES device path and export it on a
1273940d71d2Seschrock 		 * per-node basis to allow higher level software to get to the
1274940d71d2Seschrock 		 * corresponding SES state.
1275940d71d2Seschrock 		 */
1276940d71d2Seschrock 		if ((stp->set_devpath = topo_mod_strdup(mod, path)) == NULL) {
1277940d71d2Seschrock 			topo_mod_free(mod, stp, sizeof (ses_enum_target_t));
1278940d71d2Seschrock 			goto error;
1279940d71d2Seschrock 		}
1280940d71d2Seschrock 
1281940d71d2Seschrock 		if ((stp->set_target =
1282940d71d2Seschrock 		    ses_open(LIBSES_VERSION, path)) == NULL) {
1283940d71d2Seschrock 			topo_mod_dprintf(mod, "failed to open ses target "
1284940d71d2Seschrock 			    "'%s': %s", dp->d_name, ses_errmsg());
1285940d71d2Seschrock 			topo_mod_strfree(mod, stp->set_devpath);
1286940d71d2Seschrock 			topo_mod_free(mod, stp, sizeof (ses_enum_target_t));
1287940d71d2Seschrock 			continue;
1288940d71d2Seschrock 		}
1289940d71d2Seschrock 
1290940d71d2Seschrock 		stp->set_refcount = 1;
1291940d71d2Seschrock 		sdp->sed_target = stp;
1292940d71d2Seschrock 		stp->set_snap = ses_snap_hold(stp->set_target);
1293940d71d2Seschrock 		if (gettimeofday(&stp->set_snaptime, NULL) != 0)
1294940d71d2Seschrock 			stp->set_snaptime.tv_sec = time(NULL);
1295940d71d2Seschrock 
1296940d71d2Seschrock 		/*
1297940d71d2Seschrock 		 * Enumerate over all SES elements and merge them into the
1298940d71d2Seschrock 		 * correct ses_enum_chassis_t.
1299940d71d2Seschrock 		 */
1300940d71d2Seschrock 		sdp->sed_current = NULL;
1301940d71d2Seschrock 		sdp->sed_errno = 0;
1302940d71d2Seschrock 		sdp->sed_name = dp->d_name;
1303940d71d2Seschrock 		(void) ses_walk(stp->set_snap, ses_enum_gather, sdp);
1304940d71d2Seschrock 
1305940d71d2Seschrock 		if (sdp->sed_errno != 0)
1306940d71d2Seschrock 			goto error;
1307940d71d2Seschrock 	}
1308940d71d2Seschrock 
1309940d71d2Seschrock 	err = 0;
1310940d71d2Seschrock error:
1311940d71d2Seschrock 	closedir(dir);
1312940d71d2Seschrock 	return (err);
1313940d71d2Seschrock }
1314940d71d2Seschrock 
1315940d71d2Seschrock static void
1316940d71d2Seschrock ses_release(topo_mod_t *mod, tnode_t *tn)
1317940d71d2Seschrock {
1318940d71d2Seschrock 	ses_enum_target_t *stp;
1319940d71d2Seschrock 
1320940d71d2Seschrock 	if ((stp = topo_node_getspecific(tn)) != NULL)
1321940d71d2Seschrock 		ses_target_free(mod, stp);
1322940d71d2Seschrock }
1323940d71d2Seschrock 
1324940d71d2Seschrock /*ARGSUSED*/
1325940d71d2Seschrock static int
1326940d71d2Seschrock ses_enum(topo_mod_t *mod, tnode_t *rnode, const char *name,
1327940d71d2Seschrock     topo_instance_t min, topo_instance_t max, void *arg, void *notused)
1328940d71d2Seschrock {
1329940d71d2Seschrock 	ses_enum_chassis_t *cp;
1330d91236feSeschrock 	ses_enum_data_t *data;
1331940d71d2Seschrock 
1332940d71d2Seschrock 	/*
1333940d71d2Seschrock 	 * Check to make sure we're being invoked sensibly, and that we're not
1334940d71d2Seschrock 	 * being invoked as part of a post-processing step.
1335940d71d2Seschrock 	 */
1336d91236feSeschrock 	if (strcmp(name, SES_ENCLOSURE) != 0 && strcmp(name, BAY) != 0)
1337940d71d2Seschrock 		return (0);
1338940d71d2Seschrock 
1339940d71d2Seschrock 	/*
1340d91236feSeschrock 	 * If this is the first time we've called our enumeration method, then
1341d91236feSeschrock 	 * gather information about any available enclosures.
1342940d71d2Seschrock 	 */
1343d91236feSeschrock 	if ((data = topo_mod_getspecific(mod)) == NULL) {
1344d91236feSeschrock 		if ((data = topo_mod_zalloc(mod, sizeof (ses_enum_data_t))) ==
1345d91236feSeschrock 		    NULL)
1346d91236feSeschrock 			return (-1);
1347940d71d2Seschrock 
1348d91236feSeschrock 		data->sed_mod = mod;
1349d91236feSeschrock 		topo_mod_setspecific(mod, data);
1350d91236feSeschrock 
1351d91236feSeschrock 		if (disk_list_gather(mod, &data->sed_disks) != 0)
1352d91236feSeschrock 			goto error;
1353d91236feSeschrock 
1354d91236feSeschrock 		/*
1355d91236feSeschrock 		 * We search both the ses(7D) and sgen(7D) locations, so we are
1356d91236feSeschrock 		 * independent of any particular driver class bindings.
1357d91236feSeschrock 		 */
1358d91236feSeschrock 		if (ses_process_dir("/dev/es", data) != 0 ||
1359d91236feSeschrock 		    ses_process_dir("/dev/scsi/ses", data) != 0)
1360d91236feSeschrock 			goto error;
1361d91236feSeschrock 	}
1362d91236feSeschrock 
1363d91236feSeschrock 	if (strcmp(name, SES_ENCLOSURE) == 0) {
1364d91236feSeschrock 		/*
1365d91236feSeschrock 		 * This is a request to enumerate external enclosures.  Go
1366d91236feSeschrock 		 * through all the targets and create chassis nodes where
1367d91236feSeschrock 		 * necessary.
1368d91236feSeschrock 		 */
1369d91236feSeschrock 		for (cp = topo_list_next(&data->sed_chassis); cp != NULL;
1370d91236feSeschrock 		    cp = topo_list_next(cp)) {
1371d91236feSeschrock 			if (ses_create_chassis(data, rnode, cp) != 0)
1372d91236feSeschrock 				goto error;
1373d91236feSeschrock 		}
1374d91236feSeschrock 	} else {
1375d91236feSeschrock 		/*
1376d91236feSeschrock 		 * This is a request to enumerate a specific bay underneath the
1377d91236feSeschrock 		 * root chassis (for internal disks).
1378d91236feSeschrock 		 */
1379d91236feSeschrock 		if (ses_create_bays(data, rnode) != 0)
1380940d71d2Seschrock 			goto error;
1381940d71d2Seschrock 	}
1382940d71d2Seschrock 
1383d91236feSeschrock 	/*
1384d91236feSeschrock 	 * This is a bit of a kludge.  In order to allow internal disks to be
1385d91236feSeschrock 	 * enumerated and share snapshot-specific information with the external
1386d91236feSeschrock 	 * enclosure enumeration, we rely on the fact that we will be invoked
1387d91236feSeschrock 	 * for the 'ses-enclosure' node last.
1388d91236feSeschrock 	 */
1389d91236feSeschrock 	if (strcmp(name, SES_ENCLOSURE) == 0) {
1390d91236feSeschrock 		ses_data_free(data);
1391d91236feSeschrock 		topo_mod_setspecific(mod, NULL);
1392d91236feSeschrock 	}
1393940d71d2Seschrock 	return (0);
1394940d71d2Seschrock 
1395940d71d2Seschrock error:
1396d91236feSeschrock 	ses_data_free(data);
1397d91236feSeschrock 	topo_mod_setspecific(mod, NULL);
1398940d71d2Seschrock 	return (-1);
1399940d71d2Seschrock }
1400940d71d2Seschrock 
1401940d71d2Seschrock static const topo_modops_t ses_ops =
1402940d71d2Seschrock 	{ ses_enum, ses_release };
1403940d71d2Seschrock 
1404940d71d2Seschrock static topo_modinfo_t ses_info =
1405940d71d2Seschrock 	{ SES_ENCLOSURE, FM_FMRI_SCHEME_HC, SES_VERSION, &ses_ops };
1406940d71d2Seschrock 
1407940d71d2Seschrock /*ARGSUSED*/
1408940d71d2Seschrock int
1409940d71d2Seschrock _topo_init(topo_mod_t *mod, topo_version_t version)
1410940d71d2Seschrock {
1411940d71d2Seschrock 	if (getenv("TOPOSESDEBUG") != NULL)
1412940d71d2Seschrock 		topo_mod_setdebug(mod);
1413940d71d2Seschrock 
1414940d71d2Seschrock 	topo_mod_dprintf(mod, "initializing %s enumerator\n",
1415940d71d2Seschrock 	    SES_ENCLOSURE);
1416940d71d2Seschrock 
1417940d71d2Seschrock 	return (topo_mod_register(mod, &ses_info, TOPO_VERSION));
1418940d71d2Seschrock }
1419940d71d2Seschrock 
1420940d71d2Seschrock void
1421940d71d2Seschrock _topo_fini(topo_mod_t *mod)
1422940d71d2Seschrock {
1423940d71d2Seschrock 	topo_mod_unregister(mod);
1424940d71d2Seschrock }
1425