xref: /illumos-gate/usr/src/lib/fm/topo/modules/common/ses/ses_facility.c (revision 3c6b90be1d51de874ba4c1f05537c85375b6ab6e)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*
28  * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
29  */
30 
31 /*
32  * Facility node support for SES enclosures.  We support the following facility
33  * nodes, based on the node type:
34  *
35  * 	bay
36  * 		indicator=ident
37  * 		indicator=fail
38  * 		indicator=ok2rm
39  * 		sensor=fault
40  *
41  * 	controller
42  * 		indicator=ident
43  * 		indicator=fail
44  *
45  * 	fan
46  * 		indicator=ident
47  * 		indicator=fail
48  * 		sensor=speed
49  * 		sensor=fault
50  *
51  * 	psu
52  * 		indicator=ident
53  * 		indicator=fail
54  * 		sensor=status
55  *
56  * 	ses-enclosure
57  * 		indicator=ident
58  * 		indicator=fail
59  * 		sensor=fault
60  * 		sensor=<name>	(temperature)
61  * 		sensor=<name>	(voltage)
62  * 		sensor=<name>	(current)
63  *
64  * Most of these are handled by a single method that supports getting and
65  * setting boolean properties on the node.  The fan speed sensor requires a
66  * special handler, while the analog enclosure sensors all have similar
67  * behavior and can be grouped together using a common method.
68  */
69 
70 #include "ses.h"
71 #include "disk.h"
72 
73 #include <string.h>
74 
75 static int ses_indicator_mode(topo_mod_t *, tnode_t *, topo_version_t,
76     nvlist_t *, nvlist_t **);
77 static int ses_sensor_reading(topo_mod_t *, tnode_t *, topo_version_t,
78     nvlist_t *, nvlist_t **);
79 static int ses_sensor_state(topo_mod_t *, tnode_t *, topo_version_t,
80     nvlist_t *, nvlist_t **);
81 static int ses_psu_state(topo_mod_t *, tnode_t *, topo_version_t,
82     nvlist_t *, nvlist_t **);
83 
84 #define	SES_SUPP_WARN_UNDER	0x01
85 #define	SES_SUPP_WARN_OVER	0x02
86 #define	SES_SUPP_CRIT_UNDER	0x04
87 #define	SES_SUPP_CRIT_OVER	0x08
88 
89 typedef struct ses_sensor_desc {
90 	int		sd_type;
91 	int		sd_units;
92 	const char	*sd_propname;
93 	double		sd_multiplier;
94 } ses_sensor_desc_t;
95 
96 #define	TOPO_METH_SES_MODE_VERSION	0
97 #define	TOPO_METH_SES_READING_VERSION	0
98 #define	TOPO_METH_SES_STATE_VERSION	0
99 #define	TOPO_METH_SES_PSU_VERSION	0
100 
101 #define	TOPO_METH_SES_READING_PROP	"propname"
102 #define	TOPO_METH_SES_READING_MULT	"multiplier"
103 
104 #define	TOPO_METH_SES_STATE_PROP	"propname"
105 
106 #define	TOPO_METH_SES_MODE_PROP		"property-name"
107 #define	TOPO_METH_SES_MODE_ALTPROP	"alternate-property"
108 
109 static const topo_method_t ses_indicator_methods[] = {
110 	{ "ses_indicator_mode", TOPO_PROP_METH_DESC,
111 	    TOPO_METH_SES_MODE_VERSION, TOPO_STABILITY_INTERNAL,
112 	    ses_indicator_mode }
113 };
114 
115 static const topo_method_t ses_sensor_methods[] = {
116 	{ "ses_sensor_reading", TOPO_PROP_METH_DESC,
117 	    TOPO_METH_SES_READING_VERSION, TOPO_STABILITY_INTERNAL,
118 	    ses_sensor_reading },
119 	{ "ses_sensor_state", TOPO_PROP_METH_DESC,
120 	    TOPO_METH_SES_STATE_VERSION, TOPO_STABILITY_INTERNAL,
121 	    ses_sensor_state },
122 	{ "ses_psu_state", TOPO_PROP_METH_DESC,
123 	    TOPO_METH_SES_PSU_VERSION, TOPO_STABILITY_INTERNAL,
124 	    ses_psu_state },
125 };
126 
127 /*
128  * Get or set an indicator.  This method is invoked with arguments indicating
129  * the property to query to retrieve the value.  Some elements (enclosures and
130  * devices) support a request property that is distinct from an array-detected
131  * property.  Either of these conditions will result in the indicator being
132  * lit, so we have to check both properties.
133  */
134 static int
135 ses_indicator_mode(topo_mod_t *mod, tnode_t *tn, topo_version_t vers,
136     nvlist_t *in, nvlist_t **out)
137 {
138 	ses_node_t *np;
139 	nvlist_t *args, *pargs, *props;
140 	char *propname, *altprop;
141 	uint32_t mode;
142 	boolean_t current, altcurrent;
143 	nvlist_t *nvl;
144 	ses_enum_target_t *tp = topo_node_getspecific(tn);
145 
146 	if (vers > TOPO_METH_SES_MODE_VERSION)
147 		return (topo_mod_seterrno(mod, ETOPO_METHOD_VERNEW));
148 
149 	if (nvlist_lookup_nvlist(in, TOPO_PROP_ARGS, &args) != 0 ||
150 	    nvlist_lookup_string(args, TOPO_METH_SES_MODE_PROP,
151 	    &propname) != 0) {
152 		topo_mod_dprintf(mod, "invalid arguments to 'mode' method\n");
153 		return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
154 	}
155 
156 	if (nvlist_lookup_string(args, TOPO_METH_SES_MODE_ALTPROP,
157 	    &altprop) != 0)
158 		altprop = NULL;
159 
160 	if ((np = ses_node_lock(mod, tn)) == NULL) {
161 		topo_mod_dprintf(mod, "failed to lookup ses node in 'mode' "
162 		    "method\n");
163 		return (-1);
164 	}
165 	verify((props = ses_node_props(np)) != NULL);
166 
167 	if (nvlist_lookup_nvlist(in, TOPO_PROP_PARGS, &pargs) == 0 &&
168 	    nvlist_exists(pargs, TOPO_PROP_VAL_VAL)) {
169 		/* set operation */
170 		if (nvlist_lookup_uint32(pargs, TOPO_PROP_VAL_VAL,
171 		    &mode) != 0) {
172 			topo_mod_dprintf(mod, "invalid type for indicator "
173 			    "mode property");
174 			(void) topo_mod_seterrno(mod, EMOD_NVL_INVAL);
175 			goto error;
176 		}
177 
178 		if (mode != TOPO_LED_STATE_OFF && mode != TOPO_LED_STATE_ON) {
179 			topo_mod_dprintf(mod, "invalid indicator mode %d\n",
180 			    mode);
181 			(void) topo_mod_seterrno(mod, EMOD_NVL_INVAL);
182 			goto error;
183 		}
184 
185 		nvl = NULL;
186 		if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0 ||
187 		    nvlist_add_boolean_value(nvl, propname,
188 		    mode == TOPO_LED_STATE_ON ? B_TRUE : B_FALSE) != 0) {
189 			nvlist_free(nvl);
190 			(void) topo_mod_seterrno(mod, EMOD_NOMEM);
191 			goto error;
192 		}
193 
194 		if (ses_node_ctl(np, SES_CTL_OP_SETPROP, nvl) != 0) {
195 			topo_mod_dprintf(mod, "failed to set indicator: %s\n",
196 			    ses_errmsg());
197 			nvlist_free(nvl);
198 			goto error;
199 		}
200 
201 		tp->set_snaptime = 0;
202 		nvlist_free(nvl);
203 	} else {
204 		/* get operation */
205 		if (nvlist_lookup_boolean_value(props,
206 		    propname, &current) != 0) {
207 			topo_mod_dprintf(mod, "failed to lookup %s in node "
208 			    "properties\n", propname);
209 			(void) topo_mod_seterrno(mod, EMOD_METHOD_NOTSUP);
210 			goto error;
211 		}
212 
213 		if (altprop != NULL && nvlist_lookup_boolean_value(props,
214 		    altprop, &altcurrent) == 0)
215 			current |= altcurrent;
216 
217 		mode = current ? TOPO_LED_STATE_ON : TOPO_LED_STATE_OFF;
218 	}
219 
220 	nvl = NULL;
221 	if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0 ||
222 	    nvlist_add_string(nvl, TOPO_PROP_VAL_NAME,
223 	    TOPO_LED_MODE) != 0 ||
224 	    nvlist_add_uint32(nvl, TOPO_PROP_VAL_TYPE, TOPO_TYPE_UINT32) != 0 ||
225 	    nvlist_add_uint32(nvl, TOPO_PROP_VAL_VAL, mode) != 0) {
226 		nvlist_free(nvl);
227 		(void) topo_mod_seterrno(mod, EMOD_NOMEM);
228 		goto error;
229 	}
230 
231 	ses_node_unlock(mod, tn);
232 	*out = nvl;
233 	return (0);
234 
235 error:
236 	ses_node_unlock(mod, tn);
237 	return (-1);
238 }
239 
240 /*
241  * Read the given sensor value.  This just looks up the value in the node
242  * properties, and multiplies by a fixed value (determined when the method is
243  * instantiated).
244  */
245 static int
246 ses_sensor_reading(topo_mod_t *mod, tnode_t *tn, topo_version_t vers,
247     nvlist_t *in, nvlist_t **out)
248 {
249 	ses_node_t *np;
250 	nvlist_t *args, *props;
251 	char *prop;
252 	double raw, multiplier;
253 	uint64_t current;
254 	int64_t scurrent;
255 	nvlist_t *nvl;
256 
257 	if (vers > TOPO_METH_SES_MODE_VERSION)
258 		return (topo_mod_seterrno(mod, ETOPO_METHOD_VERNEW));
259 
260 	if (nvlist_lookup_nvlist(in, TOPO_PROP_ARGS, &args) != 0 ||
261 	    nvlist_lookup_string(args, TOPO_METH_SES_READING_PROP,
262 	    &prop) != 0) {
263 		topo_mod_dprintf(mod,
264 		    "invalid arguments to 'reading' method\n");
265 		return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
266 	}
267 
268 	if (nvlist_lookup_double(args, TOPO_METH_SES_READING_MULT,
269 	    &multiplier) != 0)
270 		multiplier = 1;
271 
272 	if ((np = ses_node_lock(mod, tn)) == NULL) {
273 		topo_mod_dprintf(mod, "failed to lookup ses node in 'mode' "
274 		    "method\n");
275 		return (-1);
276 	}
277 	verify((props = ses_node_props(np)) != NULL);
278 
279 	if (nvlist_lookup_uint64(props, prop, &current) == 0) {
280 		raw = (double)current;
281 	} else if (nvlist_lookup_int64(props, prop, &scurrent) == 0) {
282 		raw = (double)scurrent;
283 	} else {
284 		topo_mod_dprintf(mod, "failed to lookup %s in node "
285 		    "properties\n", prop);
286 		ses_node_unlock(mod, tn);
287 		return (topo_mod_seterrno(mod, EMOD_METHOD_NOTSUP));
288 	}
289 
290 	ses_node_unlock(mod, tn);
291 
292 	nvl = NULL;
293 	if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0 ||
294 	    nvlist_add_string(nvl, TOPO_PROP_VAL_NAME,
295 	    TOPO_SENSOR_READING) != 0 ||
296 	    nvlist_add_uint32(nvl, TOPO_PROP_VAL_TYPE, TOPO_TYPE_DOUBLE) != 0 ||
297 	    nvlist_add_double(nvl, TOPO_PROP_VAL_VAL, raw * multiplier) != 0) {
298 		nvlist_free(nvl);
299 		return (topo_mod_seterrno(mod, EMOD_NOMEM));
300 	}
301 
302 	*out = nvl;
303 	return (0);
304 }
305 
306 /*
307  * Returns the current sensor state.  This can be invoked for one of two
308  * different types of sensors: threshold or discrete sensors.  For discrete
309  * sensors, we expect a name of a boolean property and indicate
310  * asserted/deasserted based on that.  For threshold sensors, we check for the
311  * standard warning/critical properties and translate that into the appropriate
312  * topo state.
313  */
314 /*ARGSUSED*/
315 static int
316 ses_sensor_state(topo_mod_t *mod, tnode_t *tn, topo_version_t vers,
317     nvlist_t *in, nvlist_t **out)
318 {
319 	nvlist_t *nvl, *args, *props;
320 	boolean_t value;
321 	uint64_t status;
322 	uint32_t state;
323 	ses_node_t *np;
324 	char *prop;
325 
326 	if (nvlist_lookup_nvlist(in, TOPO_PROP_ARGS, &args) != 0) {
327 		topo_mod_dprintf(mod,
328 		    "invalid arguments to 'state' method\n");
329 		return (topo_mod_seterrno(mod, EMOD_NVL_INVAL));
330 	}
331 
332 	if ((np = ses_node_lock(mod, tn)) == NULL) {
333 		topo_mod_dprintf(mod, "failed to lookup ses node in 'mode' "
334 		    "method\n");
335 		return (-1);
336 	}
337 	verify((props = ses_node_props(np)) != NULL);
338 
339 	if (nvlist_lookup_uint64(props, SES_PROP_STATUS_CODE, &status) != 0)
340 		status = SES_ESC_UNSUPPORTED;
341 
342 	state = 0;
343 	if (nvlist_lookup_string(args, TOPO_METH_SES_STATE_PROP,
344 	    &prop) == 0) {
345 		/* discrete (fault) sensor */
346 
347 		if (status == SES_ESC_UNRECOVERABLE)
348 			state |= TOPO_SENSOR_STATE_GENERIC_FAIL_NONRECOV;
349 		else if (status == SES_ESC_CRITICAL)
350 			state |= TOPO_SENSOR_STATE_GENERIC_FAIL_CRITICAL;
351 		else if (nvlist_lookup_boolean_value(props, prop,
352 		    &value) == 0 && value)
353 			state |= TOPO_SENSOR_STATE_GENERIC_FAIL_NONRECOV;
354 		else
355 			state |= TOPO_SENSOR_STATE_GENERIC_FAIL_DEASSERTED;
356 	} else {
357 		/* threshold sensor */
358 		if (nvlist_lookup_boolean_value(props,
359 		    SES_PROP_WARN_UNDER, &value) == 0 && value)
360 			state |= TOPO_SENSOR_STATE_THRESH_LOWER_NONCRIT;
361 		if (nvlist_lookup_boolean_value(props,
362 		    SES_PROP_WARN_OVER, &value) == 0 && value)
363 			state |= TOPO_SENSOR_STATE_THRESH_UPPER_NONCRIT;
364 		if (nvlist_lookup_boolean_value(props,
365 		    SES_PROP_CRIT_UNDER, &value) == 0 && value)
366 			state |= TOPO_SENSOR_STATE_THRESH_LOWER_CRIT;
367 		if (nvlist_lookup_boolean_value(props,
368 		    SES_PROP_CRIT_OVER, &value) == 0 && value)
369 			state |= TOPO_SENSOR_STATE_THRESH_UPPER_CRIT;
370 	}
371 
372 	ses_node_unlock(mod, tn);
373 
374 	nvl = NULL;
375 	if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0 ||
376 	    nvlist_add_string(nvl, TOPO_PROP_VAL_NAME,
377 	    TOPO_SENSOR_STATE) != 0 ||
378 	    nvlist_add_uint32(nvl, TOPO_PROP_VAL_TYPE, TOPO_TYPE_UINT32) != 0 ||
379 	    nvlist_add_uint32(nvl, TOPO_PROP_VAL_VAL, state) != 0) {
380 		nvlist_free(nvl);
381 		return (topo_mod_seterrno(mod, EMOD_NOMEM));
382 	}
383 
384 	*out = nvl;
385 	return (0);
386 }
387 
388 /*
389  * Read the status of a PSU.  This is such a specialized operation that it has
390  * its own method instead of trying to piggyback on ses_sensor_state().  We
391  * use the following mapping to get to the standard topo power supply states:
392  *
393  *	acfail		-> INPUT_LOST
394  *	dcfail		-> INPUT_LOST
395  *	undervoltage	-> INPUT_RANGE
396  *	overvoltage	-> INPUT_RANGE_PRES
397  *	overcurrent	-> INPUT_RANGE_PRES
398  *	overtemp	-> (none)
399  *
400  * If we ever have a need for reading overtemp, we can expand the topo
401  * representation for power supplies, but at the moment this seems unnecessary.
402  */
403 /*ARGSUSED*/
404 static int
405 ses_psu_state(topo_mod_t *mod, tnode_t *tn, topo_version_t vers,
406     nvlist_t *in, nvlist_t **out)
407 {
408 	nvlist_t *nvl, *props;
409 	boolean_t value;
410 	uint32_t state;
411 	ses_node_t *np;
412 
413 	if ((np = ses_node_lock(mod, tn)) == NULL) {
414 		topo_mod_dprintf(mod, "failed to lookup ses node in 'mode' "
415 		    "method\n");
416 		return (-1);
417 	}
418 	verify((props = ses_node_props(np)) != NULL);
419 
420 	state = 0;
421 	if ((nvlist_lookup_boolean_value(props, SES_PSU_PROP_DC_FAIL,
422 	    &value) == 0 && value) ||
423 	    (nvlist_lookup_boolean_value(props, SES_PSU_PROP_AC_FAIL,
424 	    &value) == 0 && value))
425 		state |= TOPO_SENSOR_STATE_POWER_SUPPLY_INPUT_LOST;
426 
427 	if (nvlist_lookup_boolean_value(props, SES_PSU_PROP_DC_UNDER_VOLTAGE,
428 	    &value) == 0 && value)
429 		state |= TOPO_SENSOR_STATE_POWER_SUPPLY_INPUT_RANGE;
430 
431 	if ((nvlist_lookup_boolean_value(props, SES_PSU_PROP_DC_OVER_VOLTAGE,
432 	    &value) == 0 && value) ||
433 	    (nvlist_lookup_boolean_value(props, SES_PSU_PROP_DC_OVER_CURRENT,
434 	    &value) == 0 && value))
435 		state |= TOPO_SENSOR_STATE_POWER_SUPPLY_INPUT_RANGE_PRES;
436 
437 	ses_node_unlock(mod, tn);
438 
439 	nvl = NULL;
440 	if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0 ||
441 	    nvlist_add_string(nvl, TOPO_PROP_VAL_NAME,
442 	    TOPO_SENSOR_STATE) != 0 ||
443 	    nvlist_add_uint32(nvl, TOPO_PROP_VAL_TYPE, TOPO_TYPE_UINT32) != 0 ||
444 	    nvlist_add_uint32(nvl, TOPO_PROP_VAL_VAL, state) != 0) {
445 		nvlist_free(nvl);
446 		return (topo_mod_seterrno(mod, EMOD_NOMEM));
447 	}
448 
449 	*out = nvl;
450 	return (0);
451 }
452 
453 /*
454  * Create a facility node, either a sensor or an indicator.
455  */
456 static tnode_t *
457 ses_add_fac_common(topo_mod_t *mod, tnode_t *pnode, const char *name,
458     const char *type, uint64_t nodeid)
459 {
460 	tnode_t *tn;
461 	topo_pgroup_info_t pgi;
462 	int err;
463 	ses_enum_target_t *stp = topo_node_getspecific(pnode);
464 
465 	if ((tn = topo_node_facbind(mod, pnode, name, type)) == NULL) {
466 		topo_mod_dprintf(mod, "failed to bind facility node %s\n",
467 		    name);
468 		return (NULL);
469 	}
470 
471 	stp->set_refcount++;
472 	topo_node_setspecific(tn, stp);
473 
474 	pgi.tpi_name = TOPO_PGROUP_FACILITY;
475 	pgi.tpi_namestab = TOPO_STABILITY_PRIVATE;
476 	pgi.tpi_datastab = TOPO_STABILITY_PRIVATE;
477 	pgi.tpi_version = 1;
478 
479 	if (topo_pgroup_create(tn, &pgi, &err) != 0) {
480 		topo_mod_dprintf(mod, "failed to create facility property "
481 		    "group: %s\n", topo_strerror(err));
482 		topo_node_unbind(tn);
483 		return (NULL);
484 	}
485 
486 	/*
487 	 * We need the node-id property for each facility node.
488 	 */
489 	pgi.tpi_name = TOPO_PGROUP_SES;
490 	pgi.tpi_namestab = TOPO_STABILITY_PRIVATE;
491 	pgi.tpi_datastab = TOPO_STABILITY_PRIVATE;
492 	pgi.tpi_version = TOPO_VERSION;
493 
494 	if (topo_pgroup_create(tn, &pgi, &err) != 0) {
495 		topo_mod_dprintf(mod, "failed to create ses property "
496 		    "group: %s\n", topo_strerror(err));
497 		topo_node_unbind(tn);
498 		return (NULL);
499 	}
500 
501 	if (topo_prop_set_uint64(tn, TOPO_PGROUP_SES,
502 	    TOPO_PROP_NODE_ID, TOPO_PROP_IMMUTABLE,
503 	    nodeid, &err) != 0) {
504 		topo_mod_dprintf(mod,
505 		    "failed to create property %s: %s\n",
506 		    TOPO_PROP_NODE_ID, topo_strerror(err));
507 		topo_node_unbind(tn);
508 		return (NULL);
509 	}
510 
511 	return (tn);
512 }
513 
514 /*
515  * Add an indicator.  This can be represented by a single property, or by the
516  * union of two elements when SES is capable of distinguishing between
517  * requested failure and detected failure.
518  */
519 static int
520 ses_add_indicator(topo_mod_t *mod, tnode_t *pnode, uint64_t nodeid,
521     int type, const char *name, const char *propname, const char *altprop)
522 {
523 	tnode_t *tn;
524 	int err;
525 	nvlist_t *nvl;
526 
527 	/* create facility node and add methods */
528 	if ((tn = ses_add_fac_common(mod, pnode, name,
529 	    TOPO_FAC_TYPE_INDICATOR, nodeid)) == NULL)
530 		return (-1);
531 
532 	if (topo_method_register(mod, tn, ses_indicator_methods) < 0) {
533 		topo_mod_dprintf(mod, "failed to register facility methods\n");
534 		topo_node_unbind(tn);
535 		return (-1);
536 	}
537 
538 	/* set standard properties */
539 	if (topo_prop_set_uint32(tn, TOPO_PGROUP_FACILITY,
540 	    TOPO_FACILITY_TYPE, TOPO_PROP_IMMUTABLE, type, &err) != 0) {
541 		topo_mod_dprintf(mod,
542 		    "failed to set facility node properties: %s\n",
543 		    topo_strerror(err));
544 		topo_node_unbind(tn);
545 		return (-1);
546 	}
547 
548 	/* 'mode' property */
549 	nvl = NULL;
550 	if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0 ||
551 	    nvlist_add_string(nvl, TOPO_METH_SES_MODE_PROP,
552 	    propname) != 0 ||
553 	    (altprop != NULL && nvlist_add_string(nvl,
554 	    TOPO_METH_SES_MODE_ALTPROP, altprop) != 0)) {
555 		nvlist_free(nvl);
556 		topo_mod_dprintf(mod, "failed to setup method arguments\n");
557 		topo_node_unbind(tn);
558 		return (topo_mod_seterrno(mod, EMOD_NOMEM));
559 	}
560 
561 	if (topo_prop_method_register(tn, TOPO_PGROUP_FACILITY,
562 	    TOPO_LED_MODE, TOPO_TYPE_UINT32, "ses_indicator_mode",
563 	    nvl, &err) != 0) {
564 		nvlist_free(nvl);
565 		topo_mod_dprintf(mod, "failed to register reading method: %s\n",
566 		    topo_strerror(err));
567 		return (-1);
568 	}
569 
570 	if (topo_prop_setmutable(tn, TOPO_PGROUP_FACILITY,
571 	    TOPO_LED_MODE, &err) != 0) {
572 		nvlist_free(nvl);
573 		topo_mod_dprintf(mod, "failed to set property as mutable: %s\n",
574 		    topo_strerror(err));
575 		return (-1);
576 	}
577 
578 	nvlist_free(nvl);
579 	return (0);
580 }
581 
582 static tnode_t *
583 ses_add_sensor_common(topo_mod_t *mod, tnode_t *pnode, uint64_t nodeid,
584     const char *name, const char *class, int type)
585 {
586 	tnode_t *tn;
587 	int err;
588 
589 	/* create facility node and add methods */
590 	if ((tn = ses_add_fac_common(mod, pnode, name,
591 	    TOPO_FAC_TYPE_SENSOR, nodeid)) == NULL)
592 		return (NULL);
593 
594 	if (topo_method_register(mod, tn, ses_sensor_methods) < 0) {
595 		topo_mod_dprintf(mod, "failed to register facility methods\n");
596 		topo_node_unbind(tn);
597 		return (NULL);
598 	}
599 
600 	/* set standard properties */
601 	if (topo_prop_set_string(tn, TOPO_PGROUP_FACILITY,
602 	    TOPO_SENSOR_CLASS, TOPO_PROP_IMMUTABLE,
603 	    class, &err) != 0 ||
604 	    topo_prop_set_uint32(tn, TOPO_PGROUP_FACILITY,
605 	    TOPO_FACILITY_TYPE, TOPO_PROP_IMMUTABLE,
606 	    type, &err) != 0) {
607 		topo_mod_dprintf(mod,
608 		    "failed to set facility node properties: %s\n",
609 		    topo_strerror(err));
610 		topo_node_unbind(tn);
611 		return (NULL);
612 	}
613 
614 	return (tn);
615 }
616 
617 /*
618  * Add an analog (threshold) sensor to the enclosure.  This is used for fan
619  * speed, voltage, current, and temperature sensors.
620  */
621 static int
622 ses_add_sensor(topo_mod_t *mod, tnode_t *pnode, uint64_t nodeid,
623     const char *name, const ses_sensor_desc_t *sdp)
624 {
625 	tnode_t *tn;
626 	int err;
627 	nvlist_t *nvl;
628 
629 	if ((tn = ses_add_sensor_common(mod, pnode, nodeid, name,
630 	    TOPO_SENSOR_CLASS_THRESHOLD, sdp->sd_type)) == NULL)
631 		return (-1);
632 
633 	if (topo_prop_set_uint32(tn, TOPO_PGROUP_FACILITY,
634 	    TOPO_SENSOR_UNITS, TOPO_PROP_IMMUTABLE, sdp->sd_units, &err) != 0) {
635 		topo_mod_dprintf(mod,
636 		    "failed to set facility node properties: %s\n",
637 		    topo_strerror(err));
638 		topo_node_unbind(tn);
639 		return (-1);
640 	}
641 
642 	/* 'reading' property */
643 	nvl = NULL;
644 	if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0 ||
645 	    nvlist_add_string(nvl, TOPO_METH_SES_READING_PROP,
646 	    sdp->sd_propname) != 0 ||
647 	    (sdp->sd_multiplier != 0 &&
648 	    nvlist_add_double(nvl, TOPO_METH_SES_READING_MULT,
649 	    sdp->sd_multiplier) != 0)) {
650 		nvlist_free(nvl);
651 		topo_mod_dprintf(mod, "failed to setup method arguments\n");
652 		topo_node_unbind(tn);
653 		return (-1);
654 	}
655 
656 	if (topo_prop_method_register(tn, TOPO_PGROUP_FACILITY,
657 	    TOPO_SENSOR_READING, TOPO_TYPE_DOUBLE, "ses_sensor_reading",
658 	    nvl, &err) != 0) {
659 		nvlist_free(nvl);
660 		topo_mod_dprintf(mod, "failed to register reading method: %s\n",
661 		    topo_strerror(err));
662 		return (-1);
663 	}
664 
665 	nvlist_free(nvl);
666 	if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0) {
667 		topo_mod_dprintf(mod, "failed to setup method arguments\n");
668 		topo_node_unbind(tn);
669 		return (-1);
670 	}
671 
672 	/* 'state' property */
673 	if (topo_prop_method_register(tn, TOPO_PGROUP_FACILITY,
674 	    TOPO_SENSOR_STATE, TOPO_TYPE_UINT32, "ses_sensor_state",
675 	    nvl, &err) != 0) {
676 		nvlist_free(nvl);
677 		topo_mod_dprintf(mod, "failed to register state method: %s\n",
678 		    topo_strerror(err));
679 		return (-1);
680 	}
681 
682 	nvlist_free(nvl);
683 	return (0);
684 }
685 
686 /*
687  * Add a discrete sensor for simple boolean values.  This is used to indicate
688  * externally-detected failures for fans, bays, and enclosures.
689  */
690 static int
691 ses_add_discrete(topo_mod_t *mod, tnode_t *pnode, uint64_t nodeid,
692     const char *name, const char *prop)
693 {
694 	tnode_t *tn;
695 	int err;
696 	nvlist_t *nvl;
697 
698 	if ((tn = ses_add_sensor_common(mod, pnode, nodeid, name,
699 	    TOPO_SENSOR_CLASS_DISCRETE,
700 	    TOPO_SENSOR_TYPE_GENERIC_FAILURE)) == NULL)
701 		return (-1);
702 
703 	nvl = NULL;
704 	if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0 ||
705 	    nvlist_add_string(nvl, TOPO_METH_SES_STATE_PROP, prop) != 0) {
706 		nvlist_free(nvl);
707 		topo_mod_dprintf(mod, "failed to setup method arguments\n");
708 		topo_node_unbind(tn);
709 		return (-1);
710 	}
711 
712 	/* 'state' property */
713 	if (topo_prop_method_register(tn, TOPO_PGROUP_FACILITY,
714 	    TOPO_SENSOR_STATE, TOPO_TYPE_UINT32, "ses_sensor_state",
715 	    nvl, &err) != 0) {
716 		nvlist_free(nvl);
717 		topo_mod_dprintf(mod, "failed to register state method: %s\n",
718 		    topo_strerror(err));
719 		return (-1);
720 	}
721 
722 	nvlist_free(nvl);
723 	return (0);
724 }
725 
726 /*ARGSUSED*/
727 static int
728 ses_add_psu_status(topo_mod_t *mod, tnode_t *pnode, uint64_t nodeid)
729 {
730 	tnode_t *tn;
731 	int err;
732 	nvlist_t *nvl;
733 
734 	/* create facility node and add methods */
735 	if ((tn = ses_add_sensor_common(mod, pnode, nodeid, "status",
736 	    TOPO_SENSOR_CLASS_DISCRETE,
737 	    TOPO_SENSOR_TYPE_POWER_SUPPLY)) == NULL)
738 		return (-1);
739 
740 	if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0) {
741 		nvlist_free(nvl);
742 		topo_mod_dprintf(mod, "failed to setup method arguments\n");
743 		topo_node_unbind(tn);
744 		return (-1);
745 	}
746 
747 	/* 'state' property */
748 	if (topo_prop_method_register(tn, TOPO_PGROUP_FACILITY,
749 	    TOPO_SENSOR_STATE, TOPO_TYPE_UINT32, "ses_psu_state",
750 	    nvl, &err) != 0) {
751 		nvlist_free(nvl);
752 		topo_mod_dprintf(mod, "failed to register state method: %s\n",
753 		    topo_strerror(err));
754 		return (-1);
755 	}
756 
757 	nvlist_free(nvl);
758 	return (0);
759 }
760 
761 /*ARGSUSED*/
762 int
763 ses_node_enum_facility(topo_mod_t *mod, tnode_t *tn, topo_version_t vers,
764     nvlist_t *in, nvlist_t **out)
765 {
766 	ses_node_t *np;
767 	nvlist_t *props;
768 	uint64_t type, nodeid;
769 	ses_sensor_desc_t sd = { 0 };
770 
771 	if ((np = ses_node_lock(mod, tn)) == NULL)
772 		return (-1);
773 
774 	assert(ses_node_type(np) == SES_NODE_ELEMENT);
775 	nodeid = ses_node_id(np);
776 	verify((props = ses_node_props(np)) != NULL);
777 	verify(nvlist_lookup_uint64(props, SES_PROP_ELEMENT_TYPE, &type) == 0);
778 
779 	if (type != SES_ET_DEVICE && type != SES_ET_ARRAY_DEVICE &&
780 	    type != SES_ET_COOLING && type != SES_ET_POWER_SUPPLY) {
781 		ses_node_unlock(mod, tn);
782 		return (0);
783 	}
784 
785 	/*
786 	 * Every element supports an 'ident' indicator.  All elements also
787 	 * support a 'fail' indicator, but the properties used to represent
788 	 * this condition differs between elements.
789 	 */
790 	if (ses_add_indicator(mod, tn, nodeid, TOPO_LED_TYPE_LOCATE, "ident",
791 	    SES_PROP_IDENT, NULL) != 0)
792 		goto error;
793 
794 	switch (type) {
795 	case SES_ET_DEVICE:
796 	case SES_ET_ARRAY_DEVICE:
797 		/*
798 		 * Disks support an additional 'ok2rm' indicator, as well as
799 		 * externally detected 'fail' sensor.
800 		 */
801 		if (ses_add_indicator(mod, tn, nodeid, TOPO_LED_TYPE_SERVICE,
802 		    "fail", SES_DEV_PROP_FAULT_RQSTD,
803 		    SES_DEV_PROP_FAULT_SENSED) != 0 ||
804 		    ses_add_indicator(mod, tn, nodeid, TOPO_LED_TYPE_OK2RM,
805 		    "ok2rm", SES_PROP_RMV, SES_PROP_RMV) != 0 ||
806 		    ses_add_discrete(mod, tn, nodeid, "fault",
807 		    SES_DEV_PROP_FAULT_SENSED) != 0)
808 			goto error;
809 		break;
810 
811 	case SES_ET_COOLING:
812 		/*
813 		 * Add the fan speed sensor, and a discrete sensor for
814 		 * detecting failure.
815 		 */
816 		sd.sd_type = TOPO_SENSOR_TYPE_THRESHOLD_STATE;
817 		sd.sd_units = TOPO_SENSOR_UNITS_RPM;
818 		sd.sd_propname = SES_COOLING_PROP_FAN_SPEED;
819 		if (ses_add_indicator(mod, tn, nodeid, TOPO_LED_TYPE_SERVICE,
820 		    "fail", SES_PROP_FAIL, NULL) != 0 ||
821 		    ses_add_sensor(mod, tn, nodeid, "speed", &sd) != 0 ||
822 		    ses_add_discrete(mod, tn, nodeid, "fault",
823 		    SES_PROP_FAIL) != 0)
824 			goto error;
825 		break;
826 
827 	case SES_ET_POWER_SUPPLY:
828 		/*
829 		 * For power supplies, we have a number of different sensors:
830 		 * acfail, dcfail, overtemp, undervoltate, overvoltage,
831 		 * and overcurrent.  Rather than expose these all as individual
832 		 * sensors, we lump them together into a 'status' sensor of
833 		 * type TOPO_SENSOR_TYPE_POWER_SUPPLY and export the
834 		 * appropriate status flags as defined by the libtopo standard.
835 		 */
836 		if (ses_add_indicator(mod, tn, nodeid, TOPO_LED_TYPE_SERVICE,
837 		    "fail", SES_PROP_FAIL, NULL) != 0)
838 			goto error;
839 
840 		if (ses_add_psu_status(mod, tn, nodeid) != 0)
841 			goto error;
842 		break;
843 
844 	default:
845 		return (0);
846 	}
847 
848 	ses_node_unlock(mod, tn);
849 	return (0);
850 
851 error:
852 	ses_node_unlock(mod, tn);
853 	return (-1);
854 }
855 
856 /*
857  * Add enclosure-wide sensors (temperature, voltage, and current) beneath the
858  * given aggregate.
859  */
860 static int
861 ses_add_enclosure_sensors(topo_mod_t *mod, tnode_t *tn, ses_node_t *agg,
862     uint64_t type)
863 {
864 	ses_node_t *child;
865 	const char *defaultname;
866 	char *desc, *name;
867 	char rawname[64];
868 	nvlist_t *props, *aprops;
869 	uint64_t index, nodeid;
870 	ses_sensor_desc_t sd = { 0 };
871 	size_t len;
872 
873 	switch (type) {
874 	case SES_ET_TEMPERATURE_SENSOR:
875 		sd.sd_type = TOPO_SENSOR_TYPE_TEMP;
876 		sd.sd_units = TOPO_SENSOR_UNITS_DEGREES_C;
877 		sd.sd_propname = SES_TEMP_PROP_TEMP;
878 		defaultname = "temperature";
879 		break;
880 
881 	case SES_ET_VOLTAGE_SENSOR:
882 		sd.sd_type = TOPO_SENSOR_TYPE_VOLTAGE;
883 		sd.sd_units = TOPO_SENSOR_UNITS_VOLTS;
884 		sd.sd_propname = SES_VS_PROP_VOLTAGE_MV;
885 		sd.sd_multiplier = 0.001;
886 		defaultname = "voltage";
887 		break;
888 
889 	case SES_ET_CURRENT_SENSOR:
890 		sd.sd_type = TOPO_SENSOR_TYPE_CURRENT;
891 		sd.sd_units = TOPO_SENSOR_UNITS_AMPS;
892 		sd.sd_propname = SES_CS_PROP_CURRENT_MA;
893 		sd.sd_multiplier = 0.001;
894 		defaultname = "current";
895 		break;
896 
897 	default:
898 		return (0);
899 	}
900 
901 	aprops = ses_node_props(agg);
902 
903 	for (child = ses_node_child(agg); child != NULL;
904 	    child = ses_node_sibling(child)) {
905 		/*
906 		 * The only tricky part here is getting the name for the
907 		 * sensor, where we follow the algorithm of the standard
908 		 * elements.
909 		 */
910 		props = ses_node_props(child);
911 		nodeid = ses_node_id(child);
912 		if (nvlist_lookup_uint64(props, SES_PROP_ELEMENT_CLASS_INDEX,
913 		    &index) != 0)
914 			continue;
915 
916 		if (nvlist_lookup_string(props, SES_PROP_DESCRIPTION,
917 		    &desc) == 0 && desc[0] != '\0') {
918 			(void) strlcpy(rawname, desc, sizeof (rawname));
919 		} else {
920 			if (nvlist_lookup_string(aprops,
921 			    SES_PROP_CLASS_DESCRIPTION, &desc) != 0 ||
922 			    desc[0] == '\0')
923 				desc = (char *)defaultname;
924 
925 			len = strlen(desc);
926 			while (len > 0 && desc[len - 1] == ' ')
927 				len--;
928 
929 			(void) snprintf(rawname, sizeof (rawname),
930 			    "%.*s %llu", len, desc, index);
931 		}
932 
933 		if ((name = disk_auth_clean(mod, rawname)) == NULL)
934 			return (-1);
935 
936 		if (ses_add_sensor(mod, tn, nodeid, name, &sd) != 0) {
937 			topo_mod_strfree(mod, name);
938 			return (-1);
939 		}
940 
941 		topo_mod_strfree(mod, name);
942 	}
943 
944 	return (0);
945 }
946 
947 /*ARGSUSED*/
948 int
949 ses_enc_enum_facility(topo_mod_t *mod, tnode_t *tn, topo_version_t vers,
950     nvlist_t *in, nvlist_t **out)
951 {
952 	ses_node_t *np, *agg;
953 	nvlist_t *aprops;
954 	uint64_t type, nodeid;
955 
956 	if ((np = ses_node_lock(mod, tn)) == NULL)
957 		return (-1);
958 
959 	assert(ses_node_type(np) == SES_NODE_ENCLOSURE);
960 	nodeid = ses_node_id(np);
961 
962 	/*
963 	 * 'ident' and 'fail' LEDs, and 'fault' sensor.
964 	 */
965 	if (ses_add_indicator(mod, tn, nodeid, TOPO_LED_TYPE_LOCATE, "ident",
966 	    SES_PROP_IDENT, NULL) != 0 ||
967 	    ses_add_indicator(mod, tn, nodeid, TOPO_LED_TYPE_SERVICE, "fail",
968 	    SES_PROP_FAIL_REQ, SES_PROP_FAIL) != 0 ||
969 	    ses_add_discrete(mod, tn, nodeid, "fault", SES_PROP_FAIL) != 0)
970 		goto error;
971 
972 	/*
973 	 * Environmental sensors (temperature, voltage, current).  We have no
974 	 * way of knowing if any of these sensors correspond to a particular
975 	 * element, so we just attach them to the enclosure as a whole.  In the
976 	 * future, some vendor-specific libses plugin knowledge could let us
977 	 * make this correlation clearer.
978 	 */
979 	for (agg = ses_node_child(np); agg != NULL;
980 	    agg = ses_node_sibling(agg)) {
981 		if (ses_node_type(agg) != SES_NODE_AGGREGATE)
982 			continue;
983 
984 		verify((aprops = ses_node_props(agg)) != NULL);
985 		if (nvlist_lookup_uint64(aprops, SES_PROP_ELEMENT_TYPE,
986 		    &type) != 0)
987 			continue;
988 
989 		if (ses_add_enclosure_sensors(mod, tn, agg, type) != 0)
990 			goto error;
991 	}
992 
993 	ses_node_unlock(mod, tn);
994 	return (0);
995 
996 error:
997 	ses_node_unlock(mod, tn);
998 	return (-1);
999 }
1000