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