xref: /illumos-gate/usr/src/lib/fm/topo/modules/common/ipmi/ipmi_enum.c (revision f52943a93040563107b95bccb9db87d9971ef47d)
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 (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
24  * Copyright (c) 2017, Joyent, Inc.
25  * Copyright 2019 by Western Digital Corporation
26  */
27 
28 #include <assert.h>
29 #include <fm/libtopo.h>
30 #include <fm/topo_mod.h>
31 #include <sys/fm/protocol.h>
32 #include <string.h>
33 
34 #define	TOPO_PGROUP_IPMI		"ipmi"
35 #define	TOPO_PROP_IPMI_ENTITY_REF	"entity_ref"
36 #define	TOPO_PROP_IPMI_ENTITY_PRESENT	"entity_present"
37 #define	FAC_PROV_IPMI			"fac_prov_ipmi"
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 	ipmi_sdr_fru_locator_t	*ed_frusdr;
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_unusable(topo_mod_t *, tnode_t *, topo_version_t, nvlist_t *,
52     nvlist_t **);
53 static int ipmi_enum(topo_mod_t *, tnode_t *, const char *,
54     topo_instance_t, topo_instance_t, void *, void *);
55 static int ipmi_post_process(topo_mod_t *, tnode_t *);
56 
57 extern int ipmi_fru_label(topo_mod_t *mod, tnode_t *node,
58     topo_version_t vers, nvlist_t *in, nvlist_t **out);
59 
60 extern int ipmi_fru_fmri(topo_mod_t *mod, tnode_t *node,
61     topo_version_t vers, nvlist_t *in, nvlist_t **out);
62 
63 static const topo_method_t ipmi_methods[] = {
64 	{ TOPO_METH_PRESENT, TOPO_METH_PRESENT_DESC,
65 	    TOPO_METH_PRESENT_VERSION0, TOPO_STABILITY_INTERNAL, ipmi_present },
66 	{ TOPO_METH_UNUSABLE, TOPO_METH_UNUSABLE_DESC,
67 	    TOPO_METH_UNUSABLE_VERSION, TOPO_STABILITY_INTERNAL,
68 	    ipmi_unusable },
69 	{ "ipmi_fru_label", "Property method", 0,
70 	    TOPO_STABILITY_INTERNAL, ipmi_fru_label},
71 	{ "ipmi_fru_fmri", "Property method", 0,
72 	    TOPO_STABILITY_INTERNAL, ipmi_fru_fmri},
73 	{ TOPO_METH_SENSOR_FAILURE, TOPO_METH_SENSOR_FAILURE_DESC,
74 	    TOPO_METH_SENSOR_FAILURE_VERSION, TOPO_STABILITY_INTERNAL,
75 	    topo_method_sensor_failure },
76 	{ NULL }
77 };
78 
79 const topo_modops_t ipmi_ops = { ipmi_enum, NULL };
80 
81 const topo_modinfo_t ipmi_info =
82 	{ "ipmi", FM_FMRI_SCHEME_HC, TOPO_VERSION, &ipmi_ops };
83 
84 /* Common code used by topo methods below to find an IPMI entity */
85 static int
86 ipmi_find_entity(topo_mod_t *mod, tnode_t *tn, ipmi_handle_t **ihpp,
87     ipmi_entity_t **epp, char **namep, ipmi_sdr_t **sdrpp)
88 {
89 	ipmi_handle_t *ihp;
90 	ipmi_entity_t *ep;
91 	int err;
92 	char *name = NULL, **names;
93 	ipmi_sdr_t *sdrp = NULL;
94 	uint_t nelems, i;
95 
96 	*ihpp = NULL;
97 	*epp = NULL;
98 	*namep = NULL;
99 	*sdrpp = NULL;
100 
101 	if ((ihp = topo_mod_ipmi_hold(mod)) == NULL)
102 		return (topo_mod_seterrno(mod, ETOPO_METHOD_UNKNOWN));
103 
104 	ep = topo_node_getspecific(tn);
105 	if (ep != NULL) {
106 		*ihpp = ihp;
107 		*epp = ep;
108 		return (0);
109 	}
110 
111 	if (topo_prop_get_string(tn, TOPO_PGROUP_IPMI,
112 	    TOPO_PROP_IPMI_ENTITY_PRESENT, &name, &err) == 0) {
113 		/*
114 		 * Some broken IPMI implementations don't export correct
115 		 * entities, so referring to an entity isn't sufficient.
116 		 * For these platforms, we allow the XML to specify a
117 		 * single SDR record that represents the current present
118 		 * state.
119 		 */
120 		sdrp = ipmi_sdr_lookup(ihp, name);
121 	} else {
122 		if (topo_prop_get_string_array(tn, TOPO_PGROUP_IPMI,
123 		    TOPO_PROP_IPMI_ENTITY_REF, &names, &nelems, &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, ETOPO_METHOD_NOTSUP));
132 		}
133 
134 		for (i = 0; i < nelems; i++) {
135 			if ((ep = ipmi_entity_lookup_sdr(ihp, names[i]))
136 			    != NULL) {
137 				name = names[i];
138 				names[i] = NULL;
139 				break;
140 			}
141 		}
142 
143 		for (i = 0; i < nelems; i++)
144 			topo_mod_strfree(mod, names[i]);
145 		topo_mod_free(mod, names, (nelems * sizeof (char *)));
146 
147 		if (ep == NULL) {
148 			topo_mod_dprintf(mod,
149 			    "Failed to get present state of %s=%d\n",
150 			    topo_node_name(tn), topo_node_instance(tn));
151 			topo_mod_ipmi_rele(mod);
152 			return (-1);
153 		}
154 		topo_node_setspecific(tn, ep);
155 	}
156 
157 	*ihpp = ihp;
158 	*namep = name;
159 	*sdrpp = sdrp;
160 	return (0);
161 }
162 
163 /*
164  * Determine if the entity is present.
165  */
166 /*ARGSUSED*/
167 static int
168 ipmi_present(topo_mod_t *mod, tnode_t *tn, topo_version_t version,
169     nvlist_t *in, nvlist_t **out)
170 {
171 	ipmi_handle_t *ihp;
172 	ipmi_entity_t *ep;
173 	char *name;
174 	ipmi_sdr_t *sdrp;
175 	int err;
176 	boolean_t present = B_FALSE;
177 	nvlist_t *nvl;
178 
179 	err = ipmi_find_entity(mod, tn, &ihp, &ep, &name, &sdrp);
180 	if (err != 0)
181 		return (err);
182 
183 	if (ep != NULL) {
184 		if (ipmi_entity_present(ihp, ep, &present) != 0) {
185 			topo_mod_dprintf(mod,
186 			    "ipmi_entity_present() failed: %s",
187 			    ipmi_errmsg(ihp));
188 			topo_mod_strfree(mod, name);
189 			topo_mod_ipmi_rele(mod);
190 			return (-1);
191 		}
192 
193 		topo_mod_dprintf(mod,
194 		    "ipmi_entity_present(%d, %d) = %d\n", ep->ie_type,
195 		    ep->ie_instance, present);
196 	} else if (sdrp != NULL) {
197 		if (ipmi_entity_present_sdr(ihp, sdrp, &present) != 0) {
198 			topo_mod_dprintf(mod,
199 			    "Failed to get present state of %s (%s)\n",
200 			    name, ipmi_errmsg(ihp));
201 			topo_mod_strfree(mod, name);
202 			topo_mod_ipmi_rele(mod);
203 			return (-1);
204 		}
205 
206 		topo_mod_dprintf(mod, "ipmi_entity_present_sdr(%s) = %d\n",
207 		    name, present);
208 	}
209 
210 	topo_mod_strfree(mod, name);
211 	topo_mod_ipmi_rele(mod);
212 
213 	if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0)
214 		return (topo_mod_seterrno(mod, EMOD_FMRI_NVL));
215 
216 	if (nvlist_add_uint32(nvl, TOPO_METH_PRESENT_RET, present) != 0) {
217 		nvlist_free(nvl);
218 		return (topo_mod_seterrno(mod, EMOD_FMRI_NVL));
219 	}
220 
221 	*out = nvl;
222 
223 	return (0);
224 }
225 
226 /*
227  * Check whether an IPMI entity is a sensor that is unavailable
228  */
229 static int
230 ipmi_check_sensor(ipmi_handle_t *ihp, ipmi_entity_t *ep, const char *name,
231     ipmi_sdr_t *sdrp, void *data)
232 {
233 	ipmi_sdr_full_sensor_t *fsp;
234 	ipmi_sdr_compact_sensor_t *csp;
235 	uint8_t sensor_number;
236 	ipmi_sensor_reading_t *reading;
237 
238 	switch (sdrp->is_type) {
239 	case IPMI_SDR_TYPE_FULL_SENSOR:
240 		fsp = (ipmi_sdr_full_sensor_t *)sdrp->is_record;
241 		sensor_number = fsp->is_fs_number;
242 		break;
243 
244 	case IPMI_SDR_TYPE_COMPACT_SENSOR:
245 		csp = (ipmi_sdr_compact_sensor_t *)sdrp->is_record;
246 		sensor_number = csp->is_cs_number;
247 		break;
248 
249 	default:
250 		return (0);
251 	}
252 
253 	reading = ipmi_get_sensor_reading(ihp, sensor_number);
254 	if (reading != NULL && reading->isr_state_unavailable)
255 		return (1);
256 
257 	return (0);
258 }
259 
260 /*
261  * Determine if the entity is unusable
262  */
263 /*ARGSUSED*/
264 static int
265 ipmi_unusable(topo_mod_t *mod, tnode_t *tn, topo_version_t version,
266     nvlist_t *in, nvlist_t **out)
267 {
268 	ipmi_handle_t *ihp;
269 	ipmi_entity_t *ep;
270 	char *name;
271 	ipmi_sdr_t *sdrp;
272 	int err;
273 	boolean_t unusable = B_FALSE;
274 	nvlist_t *nvl;
275 
276 	err = ipmi_find_entity(mod, tn, &ihp, &ep, &name, &sdrp);
277 	if (err != 0)
278 		return (err);
279 
280 	/*
281 	 * Check whether the IPMI presented us with an entity for a
282 	 * sensor that is unavailable.
283 	 */
284 	if (ep != NULL) {
285 		unusable = (ipmi_entity_iter_sdr(ihp, ep, ipmi_check_sensor,
286 		    NULL) != 0);
287 	} else if (sdrp != NULL) {
288 		unusable = (ipmi_check_sensor(ihp, NULL, NULL, sdrp,
289 		    NULL) != 0);
290 	}
291 
292 	topo_mod_strfree(mod, name);
293 	topo_mod_ipmi_rele(mod);
294 
295 	if (topo_mod_nvalloc(mod, &nvl, NV_UNIQUE_NAME) != 0)
296 		return (topo_mod_seterrno(mod, EMOD_FMRI_NVL));
297 
298 	if (nvlist_add_uint32(nvl, TOPO_METH_UNUSABLE_RET, unusable) != 0) {
299 		nvlist_free(nvl);
300 		return (topo_mod_seterrno(mod, EMOD_FMRI_NVL));
301 	}
302 
303 	*out = nvl;
304 
305 	return (0);
306 }
307 
308 /*
309  * This determines if the entity has a FRU locator record set, in which case we
310  * treat this as a FRU, even if it's part of an association.
311  */
312 /*ARGSUSED*/
313 static int
314 ipmi_check_sdr(ipmi_handle_t *ihp, ipmi_entity_t *ep, const char *name,
315     ipmi_sdr_t *sdrp, void *data)
316 {
317 	ipmi_enum_data_t *edp = data;
318 
319 	if (sdrp->is_type == IPMI_SDR_TYPE_FRU_LOCATOR)
320 		edp->ed_frusdr = (ipmi_sdr_fru_locator_t *)sdrp->is_record;
321 
322 	return (0);
323 }
324 
325 /*
326  * Main entity enumerator.  If we find a matching entity type, then instantiate
327  * a topo node.
328  */
329 static int
330 ipmi_check_entity(ipmi_handle_t *ihp, ipmi_entity_t *ep, void *data)
331 {
332 	ipmi_enum_data_t *edp = data;
333 	ipmi_enum_data_t cdata;
334 	tnode_t *pnode = edp->ed_pnode;
335 	topo_mod_t *mod = edp->ed_mod;
336 	topo_mod_t *fmod = topo_mod_getspecific(mod);
337 	nvlist_t *auth, *fmri;
338 	tnode_t *tn;
339 	topo_pgroup_info_t pgi;
340 	char *frudata = NULL, *part = NULL, *rev = NULL, *serial = NULL;
341 	ipmi_fru_prod_info_t fruprod = {0};
342 	ipmi_fru_brd_info_t frubrd = {0};
343 	int err;
344 	const char *labelname;
345 	char label[64];
346 	size_t len;
347 
348 	/*
349 	 * Some questionable IPMI implementations group psu and fan entities
350 	 * under things like motherboard or chassis entities.  So even if this
351 	 * entity type isn't typically associated with fans and psus, if it has
352 	 * children, then regardless of the type we need to decend down and
353 	 * iterate over them.
354 	 */
355 	if (ep->ie_type != edp->ed_entity) {
356 		if (ep->ie_children != 0 &&
357 		    ipmi_entity_iter_children(ihp, ep, ipmi_check_entity,
358 		    data) != 0)
359 			return (1);
360 		return (0);
361 	}
362 
363 	/*
364 	 * The purpose of power and cooling domains is to group psus and fans
365 	 * together.  Unfortunately, some broken IPMI implementations declare
366 	 * domains that don't contain other elements.  Since the end goal is to
367 	 * only enumerate psus and fans, we'll just ignore such elements.
368 	 */
369 	if ((ep->ie_type == IPMI_ET_POWER_DOMAIN ||
370 	    ep->ie_type == IPMI_ET_COOLING_DOMAIN) &&
371 	    ep->ie_children == 0)
372 		return (0);
373 
374 	if ((auth = topo_mod_auth(mod, pnode)) == NULL) {
375 		topo_mod_dprintf(mod, "topo_mod_auth() failed: %s",
376 		    topo_mod_errmsg(mod));
377 		return (1);
378 	}
379 
380 	/*
381 	 * Determine if there's a FRU record associated with this entity.  If
382 	 * so, then read in the FRU identity info so that it can be included
383 	 * in the authority portion of the FMRI.
384 	 *
385 	 * topo_mod_hcfmri() will safely except NULL values for the part,
386 	 * rev and serial params, so we opt to simply drive on in the face of
387 	 * any strdup failures.
388 	 */
389 	edp->ed_frusdr = NULL;
390 	(void) ipmi_entity_iter_sdr(ihp, ep, ipmi_check_sdr, edp);
391 	if (edp->ed_frusdr != NULL &&
392 	    ipmi_fru_read(ihp, edp->ed_frusdr, &frudata) != -1) {
393 		if (ipmi_fru_parse_product(ihp, frudata, &fruprod) == 0) {
394 			part = strdup(fruprod.ifpi_part_number);
395 			rev = strdup(fruprod.ifpi_product_version);
396 			serial = strdup(fruprod.ifpi_product_serial);
397 		} else if (ipmi_fru_parse_board(ihp, frudata, &frubrd) == 0) {
398 			part = strdup(frubrd.ifbi_part_number);
399 			serial = strdup(frubrd.ifbi_product_serial);
400 		}
401 	}
402 	free(frudata);
403 
404 	if ((fmri = topo_mod_hcfmri(mod, pnode, FM_HC_SCHEME_VERSION,
405 	    edp->ed_name, edp->ed_instance, NULL, auth, part, rev,
406 	    serial)) == NULL) {
407 		nvlist_free(auth);
408 		free(part);
409 		free(rev);
410 		free(serial);
411 		topo_mod_dprintf(mod, "topo_mod_hcfmri() failed: %s",
412 		    topo_mod_errmsg(mod));
413 		return (1);
414 	}
415 	nvlist_free(auth);
416 	free(part);
417 	free(rev);
418 	free(serial);
419 
420 	if ((tn = topo_node_bind(mod, pnode, edp->ed_name,
421 	    edp->ed_instance, fmri)) == NULL) {
422 		nvlist_free(fmri);
423 		topo_mod_dprintf(mod, "topo_node_bind() failed: %s",
424 		    topo_mod_errmsg(mod));
425 		return (1);
426 	}
427 
428 	/*
429 	 * We inherit our label from our parent, appending our label in the
430 	 * process.  This results in defaults labels of the form "FM 1 FAN 0"
431 	 * by default when given a hierarchy.
432 	 */
433 	if (edp->ed_label != NULL)
434 		(void) snprintf(label, sizeof (label), "%s ", edp->ed_label);
435 	else
436 		label[0] = '\0';
437 
438 	switch (edp->ed_entity) {
439 	case IPMI_ET_POWER_DOMAIN:
440 		labelname = "PM";
441 		break;
442 
443 	case IPMI_ET_PSU:
444 		labelname = "PSU";
445 		break;
446 
447 	case IPMI_ET_COOLING_DOMAIN:
448 		labelname = "FM";
449 		break;
450 
451 	case IPMI_ET_FAN:
452 		labelname = "FAN";
453 		break;
454 	}
455 
456 	len = strlen(label);
457 	(void) snprintf(label + len, sizeof (label) - len, "%s %d",
458 	    labelname, edp->ed_instance);
459 
460 	nvlist_free(fmri);
461 	edp->ed_instance++;
462 
463 	if (topo_node_label_set(tn, label, &err) != 0) {
464 		topo_mod_dprintf(mod, "failed to set label: %s\n",
465 		    topo_strerror(err));
466 		return (1);
467 	}
468 
469 	/*
470 	 * Store IPMI entity details as properties on the node
471 	 */
472 	pgi.tpi_name = TOPO_PGROUP_IPMI;
473 	pgi.tpi_namestab = TOPO_STABILITY_PRIVATE;
474 	pgi.tpi_datastab = TOPO_STABILITY_PRIVATE;
475 	pgi.tpi_version = TOPO_VERSION;
476 	if (topo_pgroup_create(tn, &pgi, &err) != 0) {
477 		if (err != ETOPO_PROP_DEFD) {
478 			topo_mod_dprintf(mod, "failed to create propgroup "
479 			    "%s: %s\n", TOPO_PGROUP_IPMI, topo_strerror(err));
480 			return (1);
481 		}
482 	}
483 
484 	/*
485 	 * Add properties to contain the IPMI entity id and instance.  This
486 	 * will be used by the fac_prov_ipmi module to discover and enumerate
487 	 * facility nodes for any associated sensors.
488 	 */
489 	if (topo_prop_set_uint32(tn, TOPO_PGROUP_IPMI, TOPO_PROP_IPMI_ENTITY_ID,
490 	    TOPO_PROP_IMMUTABLE, ep->ie_type, &err) != 0 ||
491 	    topo_prop_set_uint32(tn, TOPO_PGROUP_IPMI,
492 	    TOPO_PROP_IPMI_ENTITY_INST, TOPO_PROP_IMMUTABLE, ep->ie_instance,
493 	    &err) != 0) {
494 		topo_mod_dprintf(mod, "failed to add ipmi properties (%s)",
495 		    topo_strerror(err));
496 		return (1);
497 	}
498 	if (topo_method_register(mod, tn, ipmi_methods) != 0) {
499 		topo_mod_dprintf(mod, "topo_method_register() failed: %s",
500 		    topo_mod_errmsg(mod));
501 		return (1);
502 	}
503 
504 	/*
505 	 * Invoke the tmo_enum callback from the fac_prov_ipmi module on this
506 	 * node.  This will have the effect of registering a method on this node
507 	 * for enumerating sensors.
508 	 */
509 	if (fmod == NULL && (fmod = topo_mod_load(mod, FAC_PROV_IPMI,
510 	    TOPO_VERSION)) == NULL) {
511 		topo_mod_dprintf(mod, "failed to load %s: %s",
512 		    FAC_PROV_IPMI, topo_mod_errmsg(mod));
513 		return (-1);
514 	}
515 	topo_mod_setspecific(mod, fmod);
516 
517 	if (topo_mod_enumerate(fmod, tn, FAC_PROV_IPMI, FAC_PROV_IPMI, 0, 0,
518 	    NULL) != 0) {
519 		topo_mod_dprintf(mod, "facility provider enum failed (%s)",
520 		    topo_mod_errmsg(mod));
521 		return (1);
522 	}
523 
524 	/*
525 	 * If we are a child of a non-chassis node, and there isn't an explicit
526 	 * FRU locator record, then propagate the parent's FRU.  Otherwise, set
527 	 * the FRU to be the same as the resource.
528 	 */
529 	if (strcmp(topo_node_name(pnode), CHASSIS) == 0 ||
530 	    edp->ed_frusdr != NULL) {
531 		if (topo_node_resource(tn, &fmri, &err) != 0) {
532 			topo_mod_dprintf(mod, "topo_node_resource() failed: %s",
533 			    topo_strerror(err));
534 			(void) topo_mod_seterrno(mod, err);
535 			return (1);
536 		}
537 	} else {
538 		if (topo_node_fru(pnode, &fmri, NULL, &err) != 0) {
539 			topo_mod_dprintf(mod, "topo_node_fru() failed: %s",
540 			    topo_strerror(err));
541 			(void) topo_mod_seterrno(mod, err);
542 			return (1);
543 		}
544 	}
545 
546 	if (topo_node_fru_set(tn, fmri, 0, &err) != 0) {
547 		nvlist_free(fmri);
548 		topo_mod_dprintf(mod, "topo_node_fru_set() failed: %s",
549 		    topo_strerror(err));
550 		(void) topo_mod_seterrno(mod, err);
551 		return (1);
552 	}
553 
554 	topo_node_setspecific(tn, ep);
555 
556 	nvlist_free(fmri);
557 
558 	/*
559 	 * Iterate over children, once for recursive domains and once for
560 	 * psu/fans.
561 	 */
562 	if (ep->ie_children != 0 &&
563 	    (ep->ie_type == IPMI_ET_POWER_DOMAIN ||
564 	    ep->ie_type == IPMI_ET_COOLING_DOMAIN)) {
565 		cdata.ed_mod = edp->ed_mod;
566 		cdata.ed_pnode = tn;
567 		cdata.ed_instance = 0;
568 		cdata.ed_name = edp->ed_name;
569 		cdata.ed_entity = edp->ed_entity;
570 		cdata.ed_label = label;
571 
572 		if (ipmi_entity_iter_children(ihp, ep,
573 		    ipmi_check_entity, &cdata) != 0)
574 			return (1);
575 
576 		switch (cdata.ed_entity) {
577 		case IPMI_ET_POWER_DOMAIN:
578 			cdata.ed_entity = IPMI_ET_PSU;
579 			cdata.ed_name = PSU;
580 			break;
581 
582 		case IPMI_ET_COOLING_DOMAIN:
583 			cdata.ed_entity = IPMI_ET_FAN;
584 			cdata.ed_name = FAN;
585 			break;
586 		}
587 
588 		if (ipmi_entity_iter_children(ihp, ep,
589 		    ipmi_check_entity, &cdata) != 0)
590 			return (1);
591 	}
592 
593 	return (0);
594 }
595 
596 /*
597  * libtopo enumeration point.  This simply iterates over entities looking for
598  * the appropriate type.
599  */
600 /*ARGSUSED*/
601 static int
602 ipmi_enum(topo_mod_t *mod, tnode_t *rnode, const char *name,
603     topo_instance_t min, topo_instance_t max, void *arg, void *unused)
604 {
605 	ipmi_handle_t *ihp;
606 	ipmi_enum_data_t data;
607 	int ret;
608 
609 	/*
610 	 * If the node being passed in ISN'T the chassis node, then we're being
611 	 * asked to post-process a statically defined node.
612 	 */
613 	if (strcmp(topo_node_name(rnode), CHASSIS) != 0) {
614 		if (ipmi_post_process(mod, rnode) != 0) {
615 			topo_mod_dprintf(mod, "post processing of node %s=%d "
616 			    "failed!", topo_node_name(rnode),
617 			    topo_node_instance(rnode));
618 			return (-1);
619 		}
620 		return (0);
621 	}
622 
623 	if (strcmp(name, POWERMODULE) == 0) {
624 		data.ed_entity = IPMI_ET_POWER_DOMAIN;
625 	} else if (strcmp(name, PSU) == 0) {
626 		data.ed_entity = IPMI_ET_PSU;
627 	} else if (strcmp(name, FANMODULE) == 0) {
628 		data.ed_entity = IPMI_ET_COOLING_DOMAIN;
629 	} else if (strcmp(name, FAN) == 0) {
630 		data.ed_entity = IPMI_ET_FAN;
631 	} else {
632 		topo_mod_dprintf(mod, "unknown enumeration type '%s'",
633 		    name);
634 		return (-1);
635 	}
636 
637 	if ((ihp = topo_mod_ipmi_hold(mod)) == NULL)
638 		return (0);
639 
640 	data.ed_mod = mod;
641 	data.ed_pnode = rnode;
642 	data.ed_name = name;
643 	data.ed_instance = 0;
644 	data.ed_label = NULL;
645 
646 	if ((ret = ipmi_entity_iter(ihp, ipmi_check_entity, &data)) != 0) {
647 		/*
648 		 * We don't return failure if IPMI enumeration fails.  This may
649 		 * be due to the SP being unavailable or an otherwise transient
650 		 * event.
651 		 */
652 		if (ret < 0) {
653 			topo_mod_dprintf(mod,
654 			    "failed to enumerate entities: %s",
655 			    ipmi_errmsg(ihp));
656 		} else {
657 			topo_mod_ipmi_rele(mod);
658 			return (-1);
659 		}
660 	}
661 
662 	topo_mod_ipmi_rele(mod);
663 	return (0);
664 }
665 
666 static int
667 ipmi_post_process(topo_mod_t *mod, tnode_t *tn)
668 {
669 	if (topo_method_register(mod, tn, ipmi_methods) != 0) {
670 		topo_mod_dprintf(mod, "ipmi_post_process() failed: %s",
671 		    topo_mod_errmsg(mod));
672 		return (1);
673 	}
674 	return (0);
675 }
676 
677 /*ARGSUSED*/
678 int
679 _topo_init(topo_mod_t *mod, topo_version_t version)
680 {
681 	if (getenv("TOPOIPMIDEBUG") != NULL)
682 		topo_mod_setdebug(mod);
683 
684 	if (topo_mod_register(mod, &ipmi_info, TOPO_VERSION) != 0) {
685 		topo_mod_dprintf(mod, "module registration failed: %s\n",
686 		    topo_mod_errmsg(mod));
687 		return (-1); /* mod errno already set */
688 	}
689 
690 	topo_mod_dprintf(mod, "IPMI enumerator initialized\n");
691 	return (0);
692 }
693 
694 void
695 _topo_fini(topo_mod_t *mod)
696 {
697 	/*
698 	 * This is the logical, and probably only safe spot where we could
699 	 * unload fac_prov_ipmi.  But unfortunately, calling topo_mod_unload()
700 	 * in the context of a module's _topo_fini entry point would result
701 	 * in recursively grabbing the modhash lock and we'd deadlock.
702 	 *
703 	 * Unfortunately, libtopo doesn't currently have a mechanism for
704 	 * expressing and handling intermodule dependencies, so we're left
705 	 * with this situation where once a module loads another module,
706 	 * it's going to be with us until we teardown the process.
707 	 */
708 	topo_mod_unregister(mod);
709 }
710