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