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