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