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 #include <assert.h>
28 #include <fm/libtopo.h>
29 #include <fm/topo_mod.h>
30 #include <sys/fm/protocol.h>
31 #include <string.h>
32 
33 #define	TOPO_PGROUP_IPMI 		"ipmi"
34 #define	TOPO_PROP_IPMI_ENTITY_REF	"entity_ref"
35 #define	TOPO_PROP_IPMI_ENTITY_PRESENT	"entity_present"
36 
37 typedef struct ipmi_enum_data {
38 	topo_mod_t	*ed_mod;
39 	tnode_t		*ed_pnode;
40 	const char	*ed_name;
41 	char		*ed_label;
42 	uint8_t		ed_entity;
43 	topo_instance_t	ed_instance;
44 	boolean_t	ed_hasfru;
45 } ipmi_enum_data_t;
46 
47 static int ipmi_present(topo_mod_t *, tnode_t *, topo_version_t, nvlist_t *,
48     nvlist_t **);
49 static int ipmi_enum(topo_mod_t *, tnode_t *, const char *,
50     topo_instance_t, topo_instance_t, void *, void *);
51 static int ipmi_post_process(topo_mod_t *, tnode_t *);
52 
53 extern int ipmi_fru_label(topo_mod_t *mod, tnode_t *node,
54     topo_version_t vers, nvlist_t *in, nvlist_t **out);
55 
56 extern int ipmi_fru_fmri(topo_mod_t *mod, tnode_t *node,
57     topo_version_t vers, nvlist_t *in, nvlist_t **out);
58 
59 static const topo_method_t ipmi_methods[] = {
60 	{ TOPO_METH_PRESENT, TOPO_METH_PRESENT_DESC,
61 	    TOPO_METH_PRESENT_VERSION0, TOPO_STABILITY_INTERNAL, ipmi_present },
62 	{ "ipmi_fru_label", "Property method", 0,
63 	    TOPO_STABILITY_INTERNAL, ipmi_fru_label},
64 	{ "ipmi_fru_fmri", "Property method", 0,
65 	    TOPO_STABILITY_INTERNAL, ipmi_fru_fmri},
66 	{ TOPO_METH_SENSOR_FAILURE, TOPO_METH_SENSOR_FAILURE_DESC,
67 	    TOPO_METH_SENSOR_FAILURE_VERSION, TOPO_STABILITY_INTERNAL,
68 	    topo_method_sensor_failure },
69 	{ NULL }
70 };
71 
72 const topo_modops_t ipmi_ops = { ipmi_enum, NULL };
73 
74 const topo_modinfo_t ipmi_info =
75 	{ "ipmi", FM_FMRI_SCHEME_HC, TOPO_VERSION, &ipmi_ops };
76 
77 /*
78  * Determine if the entity is present.
79  */
80 /*ARGSUSED*/
81 static int
82 ipmi_present(topo_mod_t *mod, tnode_t *tn, topo_version_t version,
83     nvlist_t *in, nvlist_t **out)
84 {
85 	ipmi_handle_t *ihp;
86 	ipmi_entity_t *ep;
87 	boolean_t present;
88 	nvlist_t *nvl;
89 	int err;
90 	char *name;
91 	ipmi_sdr_t *sdrp;
92 
93 	if ((ihp = topo_mod_ipmi_hold(mod)) == NULL)
94 		return (topo_mod_seterrno(mod, ETOPO_METHOD_UNKNOWN));
95 
96 	ep = topo_node_getspecific(tn);
97 	if (ep == NULL) {
98 		if (topo_prop_get_string(tn, TOPO_PGROUP_IPMI,
99 		    TOPO_PROP_IPMI_ENTITY_PRESENT, &name, &err) == 0) {
100 			/*
101 			 * Some broken IPMI implementations don't export correct
102 			 * entities, so referring to an entity isn't sufficient.
103 			 * For these platforms, we allow the XML to specify a
104 			 * single SDR record that represents the current present
105 			 * state.
106 			 */
107 			if ((sdrp = ipmi_sdr_lookup(ihp, name)) == NULL ||
108 			    ipmi_entity_present_sdr(ihp, sdrp, &present) != 0) {
109 				topo_mod_dprintf(mod,
110 				    "Failed to get present state of %s (%s)\n",
111 				    name, ipmi_errmsg(ihp));
112 				topo_mod_strfree(mod, name);
113 				topo_mod_ipmi_rele(mod);
114 				return (-1);
115 			}
116 
117 			topo_mod_dprintf(mod,
118 			    "ipmi_entity_present_sdr(%s) = %d\n", name,
119 			    present);
120 			topo_mod_strfree(mod, name);
121 		} else {
122 			if (topo_prop_get_string(tn, TOPO_PGROUP_IPMI,
123 			    TOPO_PROP_IPMI_ENTITY_REF, &name, &err) != 0) {
124 				/*
125 				 * Not all nodes have an entity_ref attribute.
126 				 * For these cases, return ENOTSUP so that we
127 				 * fall back to the default hc presence
128 				 * detection.
129 				 */
130 				topo_mod_ipmi_rele(mod);
131 				return (topo_mod_seterrno(mod,
132 				    ETOPO_METHOD_NOTSUP));
133 			}
134 
135 			if ((ep = ipmi_entity_lookup_sdr(ihp, name)) == NULL) {
136 				topo_mod_strfree(mod, name);
137 				topo_mod_dprintf(mod,
138 				    "Failed to lookup ipmi entity "
139 				    "%s (%s)\n", name, ipmi_errmsg(ihp));
140 				topo_mod_ipmi_rele(mod);
141 				return (-1);
142 			}
143 
144 			topo_mod_strfree(mod, name);
145 			topo_node_setspecific(tn, ep);
146 		}
147 	}
148 
149 	if (ep != NULL) {
150 		if (ipmi_entity_present(ihp, ep, &present) != 0) {
151 			topo_mod_dprintf(mod,
152 			    "ipmi_entity_present() failed: %s",
153 			    ipmi_errmsg(ihp));
154 			topo_mod_ipmi_rele(mod);
155 			return (-1);
156 		}
157 
158 		topo_mod_dprintf(mod,
159 		    "ipmi_entity_present() = %d\n", present);
160 	}
161 
162 	topo_mod_ipmi_rele(mod);
163 
164 	if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0)
165 		return (topo_mod_seterrno(mod, EMOD_FMRI_NVL));
166 
167 	if (nvlist_add_uint32(nvl, TOPO_METH_PRESENT_RET, present) != 0) {
168 		nvlist_free(nvl);
169 		return (topo_mod_seterrno(mod, EMOD_FMRI_NVL));
170 	}
171 
172 	*out = nvl;
173 
174 	return (0);
175 }
176 
177 /*
178  * This determines if the entity has a FRU locator record set, in which case we
179  * treat this as a FRU, even if it's part of an association.
180  */
181 /*ARGSUSED*/
182 static int
183 ipmi_check_sdr(ipmi_handle_t *ihp, ipmi_entity_t *ep, const char *name,
184     ipmi_sdr_t *sdrp, void *data)
185 {
186 	ipmi_enum_data_t *edp = data;
187 
188 	if (sdrp->is_type == IPMI_SDR_TYPE_FRU_LOCATOR)
189 		edp->ed_hasfru = B_TRUE;
190 
191 	return (0);
192 }
193 
194 /*
195  * Main entity enumerator.  If we find a matching entity type, then instantiate
196  * a topo node.
197  */
198 static int
199 ipmi_check_entity(ipmi_handle_t *ihp, ipmi_entity_t *ep, void *data)
200 {
201 	ipmi_enum_data_t *edp = data;
202 	ipmi_enum_data_t cdata;
203 	tnode_t *pnode = edp->ed_pnode;
204 	topo_mod_t *mod = edp->ed_mod;
205 	nvlist_t *auth, *fmri;
206 	tnode_t *tn;
207 	topo_pgroup_info_t pgi;
208 	int err;
209 	const char *labelname;
210 	char label[64];
211 	size_t len;
212 
213 	if (ep->ie_type != edp->ed_entity)
214 		return (0);
215 
216 	/*
217 	 * The purpose of power and cooling domains is to group psus and fans
218 	 * together.  Unfortunately, some broken IPMI implementations declare
219 	 * domains that don't contain other elements.  Since the end goal is to
220 	 * only enumerate psus and fans, we'll just ignore such elements.
221 	 */
222 	if ((ep->ie_type == IPMI_ET_POWER_DOMAIN ||
223 	    ep->ie_type == IPMI_ET_COOLING_DOMAIN) &&
224 	    ep->ie_children == 0)
225 		return (0);
226 
227 	if ((auth = topo_mod_auth(mod, pnode)) == NULL) {
228 		topo_mod_dprintf(mod, "topo_mod_auth() failed: %s",
229 		    topo_mod_errmsg(mod));
230 		return (1);
231 	}
232 
233 	if ((fmri = topo_mod_hcfmri(mod, pnode, FM_HC_SCHEME_VERSION,
234 	    edp->ed_name, edp->ed_instance, NULL, auth, NULL, NULL,
235 	    NULL)) == NULL) {
236 		nvlist_free(auth);
237 		topo_mod_dprintf(mod, "topo_mod_hcfmri() failed: %s",
238 		    topo_mod_errmsg(mod));
239 		return (1);
240 	}
241 
242 	nvlist_free(auth);
243 
244 	if ((tn = topo_node_bind(mod, pnode, edp->ed_name,
245 	    edp->ed_instance, fmri)) == NULL) {
246 		nvlist_free(fmri);
247 		topo_mod_dprintf(mod, "topo_node_bind() failed: %s",
248 		    topo_mod_errmsg(mod));
249 		return (1);
250 	}
251 
252 	/*
253 	 * We inherit our label from our parent, appending our label in the
254 	 * process.  This results in defaults labels of the form "FM 1 FAN 0"
255 	 * by default when given a hierarchy.
256 	 */
257 	if (edp->ed_label != NULL)
258 		(void) snprintf(label, sizeof (label), "%s ", edp->ed_label);
259 	else
260 		label[0] = '\0';
261 
262 	switch (edp->ed_entity) {
263 	case IPMI_ET_POWER_DOMAIN:
264 		labelname = "PM";
265 		break;
266 
267 	case IPMI_ET_PSU:
268 		labelname = "PSU";
269 		break;
270 
271 	case IPMI_ET_COOLING_DOMAIN:
272 		labelname = "FM";
273 		break;
274 
275 	case IPMI_ET_FAN:
276 		labelname = "FAN";
277 		break;
278 	}
279 
280 	len = strlen(label);
281 	(void) snprintf(label + len, sizeof (label) - len, "%s %d",
282 	    labelname, edp->ed_instance);
283 
284 	nvlist_free(fmri);
285 	edp->ed_instance++;
286 
287 	if (topo_node_label_set(tn, label, &err) != 0) {
288 		topo_mod_dprintf(mod, "failed to set label: %s\n",
289 		    topo_strerror(err));
290 		return (1);
291 	}
292 
293 	/*
294 	 * Store IPMI entity details as properties on the node
295 	 */
296 	pgi.tpi_name = TOPO_PGROUP_IPMI;
297 	pgi.tpi_namestab = TOPO_STABILITY_PRIVATE;
298 	pgi.tpi_datastab = TOPO_STABILITY_PRIVATE;
299 	pgi.tpi_version = TOPO_VERSION;
300 	if (topo_pgroup_create(tn, &pgi, &err) != 0) {
301 		if (err != ETOPO_PROP_DEFD) {
302 			topo_mod_dprintf(mod, "failed to create propgroup "
303 			    "%s: %s\n", TOPO_PGROUP_IPMI, topo_strerror(err));
304 			return (1);
305 		}
306 	}
307 
308 	if (topo_method_register(mod, tn, ipmi_methods) != 0) {
309 		topo_mod_dprintf(mod, "topo_method_register() failed: %s",
310 		    topo_mod_errmsg(mod));
311 		return (1);
312 	}
313 
314 	/*
315 	 * If we are a child of a non-chassis node, and there isn't an explicit
316 	 * FRU locator record, then propagate the parent's FRU.  Otherwise, set
317 	 * the FRU to be the same as the resource.
318 	 */
319 	edp->ed_hasfru = B_FALSE;
320 	(void) ipmi_entity_iter_sdr(ihp, ep, ipmi_check_sdr, edp);
321 
322 	if (strcmp(topo_node_name(pnode), CHASSIS) == 0 ||
323 	    edp->ed_hasfru) {
324 		if (topo_node_resource(tn, &fmri, &err) != 0) {
325 			topo_mod_dprintf(mod, "topo_node_resource() failed: %s",
326 			    topo_strerror(err));
327 			(void) topo_mod_seterrno(mod, err);
328 			return (1);
329 		}
330 	} else {
331 		if (topo_node_fru(pnode, &fmri, NULL, &err) != 0) {
332 			topo_mod_dprintf(mod, "topo_node_fru() failed: %s",
333 			    topo_strerror(err));
334 			(void) topo_mod_seterrno(mod, err);
335 			return (1);
336 		}
337 	}
338 
339 	if (topo_node_fru_set(tn, fmri, 0, &err) != 0) {
340 		nvlist_free(fmri);
341 		topo_mod_dprintf(mod, "topo_node_fru_set() failed: %s",
342 		    topo_strerror(err));
343 		(void) topo_mod_seterrno(mod, err);
344 		return (1);
345 	}
346 
347 	topo_node_setspecific(tn, ep);
348 
349 	nvlist_free(fmri);
350 
351 	/*
352 	 * Iterate over children, once for recursive domains and once for
353 	 * psu/fans.
354 	 */
355 	if (ep->ie_children != 0 &&
356 	    (ep->ie_type == IPMI_ET_POWER_DOMAIN ||
357 	    ep->ie_type == IPMI_ET_COOLING_DOMAIN)) {
358 		cdata.ed_mod = edp->ed_mod;
359 		cdata.ed_pnode = tn;
360 		cdata.ed_instance = 0;
361 		cdata.ed_name = edp->ed_name;
362 		cdata.ed_entity = edp->ed_entity;
363 		cdata.ed_label = label;
364 
365 		if (ipmi_entity_iter_children(ihp, ep,
366 		    ipmi_check_entity, &cdata) != 0)
367 			return (1);
368 
369 		switch (cdata.ed_entity) {
370 		case IPMI_ET_POWER_DOMAIN:
371 			cdata.ed_entity = IPMI_ET_PSU;
372 			cdata.ed_name = PSU;
373 			break;
374 
375 		case IPMI_ET_COOLING_DOMAIN:
376 			cdata.ed_entity = IPMI_ET_FAN;
377 			cdata.ed_name = FAN;
378 			break;
379 		}
380 
381 		if (ipmi_entity_iter_children(ihp, ep,
382 		    ipmi_check_entity, &cdata) != 0)
383 			return (1);
384 	}
385 
386 	return (0);
387 }
388 
389 /*
390  * libtopo enumeration point.  This simply iterates over entities looking for
391  * the appropriate type.
392  */
393 /*ARGSUSED*/
394 static int
395 ipmi_enum(topo_mod_t *mod, tnode_t *rnode, const char *name,
396     topo_instance_t min, topo_instance_t max, void *arg, void *unused)
397 {
398 	ipmi_handle_t *ihp;
399 	ipmi_enum_data_t data;
400 	int ret;
401 
402 	/*
403 	 * If the node being passed in ISN'T the chassis node, then we're being
404 	 * asked to post-process a statically defined node.
405 	 */
406 	if (strcmp(topo_node_name(rnode), CHASSIS) != 0) {
407 		if (ipmi_post_process(mod, rnode) != 0) {
408 			topo_mod_dprintf(mod, "post processing of node %s=%d "
409 			    "failed!", topo_node_name(rnode),
410 			    topo_node_instance(rnode));
411 			return (-1);
412 		}
413 		return (0);
414 	}
415 
416 	if (strcmp(name, POWERMODULE) == 0) {
417 		data.ed_entity = IPMI_ET_POWER_DOMAIN;
418 	} else if (strcmp(name, PSU) == 0) {
419 		data.ed_entity = IPMI_ET_PSU;
420 	} else if (strcmp(name, FANMODULE) == 0) {
421 		data.ed_entity = IPMI_ET_COOLING_DOMAIN;
422 	} else if (strcmp(name, FAN) == 0) {
423 		data.ed_entity = IPMI_ET_FAN;
424 	} else {
425 		topo_mod_dprintf(mod, "unknown enumeration type '%s'",
426 		    name);
427 		return (-1);
428 	}
429 
430 	if ((ihp = topo_mod_ipmi_hold(mod)) == NULL)
431 		return (0);
432 
433 	data.ed_mod = mod;
434 	data.ed_pnode = rnode;
435 	data.ed_name = name;
436 	data.ed_instance = 0;
437 	data.ed_label = NULL;
438 
439 	if ((ret = ipmi_entity_iter(ihp, ipmi_check_entity, &data)) != 0) {
440 		/*
441 		 * We don't return failure if IPMI enumeration fails.  This may
442 		 * be due to the SP being unavailable or an otherwise transient
443 		 * event.
444 		 */
445 		if (ret < 0) {
446 			topo_mod_dprintf(mod,
447 			    "failed to enumerate entities: %s",
448 			    ipmi_errmsg(ihp));
449 		} else {
450 			topo_mod_ipmi_rele(mod);
451 			return (-1);
452 		}
453 	}
454 
455 	topo_mod_ipmi_rele(mod);
456 	return (0);
457 }
458 
459 static int
460 ipmi_post_process(topo_mod_t *mod, tnode_t *tn)
461 {
462 	if (topo_method_register(mod, tn, ipmi_methods) != 0) {
463 		topo_mod_dprintf(mod, "ipmi_post_process() failed: %s",
464 		    topo_mod_errmsg(mod));
465 		return (1);
466 	}
467 	return (0);
468 }
469 
470 /*ARGSUSED*/
471 int
472 _topo_init(topo_mod_t *mod, topo_version_t version)
473 {
474 	if (getenv("TOPOIPMIDEBUG") != NULL)
475 		topo_mod_setdebug(mod);
476 
477 	if (topo_mod_register(mod, &ipmi_info, TOPO_VERSION) != 0) {
478 		topo_mod_dprintf(mod, "%s registration failed: %s\n",
479 		    DISK, topo_mod_errmsg(mod));
480 		return (-1); /* mod errno already set */
481 	}
482 
483 	topo_mod_dprintf(mod, "IPMI enumerator initialized\n");
484 	return (0);
485 }
486 
487 void
488 _topo_fini(topo_mod_t *mod)
489 {
490 	topo_mod_unregister(mod);
491 }
492