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.
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.
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]
23 * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
24 * Copyright (c) 2017, Joyent, Inc.
28 #include <fm/libtopo.h>
29 #include <fm/topo_mod.h>
30 #include <sys/fm/protocol.h>
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 #define FAC_PROV_IPMI "fac_prov_ipmi"
38 typedef struct ipmi_enum_data
{
44 topo_instance_t ed_instance
;
45 ipmi_sdr_fru_locator_t
*ed_frusdr
;
48 static int ipmi_present(topo_mod_t
*, tnode_t
*, topo_version_t
, nvlist_t
*,
50 static int ipmi_enum(topo_mod_t
*, tnode_t
*, const char *,
51 topo_instance_t
, topo_instance_t
, void *, void *);
52 static int ipmi_post_process(topo_mod_t
*, tnode_t
*);
54 extern int ipmi_fru_label(topo_mod_t
*mod
, tnode_t
*node
,
55 topo_version_t vers
, nvlist_t
*in
, nvlist_t
**out
);
57 extern int ipmi_fru_fmri(topo_mod_t
*mod
, tnode_t
*node
,
58 topo_version_t vers
, nvlist_t
*in
, nvlist_t
**out
);
60 static const topo_method_t ipmi_methods
[] = {
61 { TOPO_METH_PRESENT
, TOPO_METH_PRESENT_DESC
,
62 TOPO_METH_PRESENT_VERSION0
, TOPO_STABILITY_INTERNAL
, ipmi_present
},
63 { "ipmi_fru_label", "Property method", 0,
64 TOPO_STABILITY_INTERNAL
, ipmi_fru_label
},
65 { "ipmi_fru_fmri", "Property method", 0,
66 TOPO_STABILITY_INTERNAL
, ipmi_fru_fmri
},
67 { TOPO_METH_SENSOR_FAILURE
, TOPO_METH_SENSOR_FAILURE_DESC
,
68 TOPO_METH_SENSOR_FAILURE_VERSION
, TOPO_STABILITY_INTERNAL
,
69 topo_method_sensor_failure
},
73 const topo_modops_t ipmi_ops
= { ipmi_enum
, NULL
};
75 const topo_modinfo_t ipmi_info
=
76 { "ipmi", FM_FMRI_SCHEME_HC
, TOPO_VERSION
, &ipmi_ops
};
79 * Determine if the entity is present.
83 ipmi_present(topo_mod_t
*mod
, tnode_t
*tn
, topo_version_t version
,
84 nvlist_t
*in
, nvlist_t
**out
)
95 if ((ihp
= topo_mod_ipmi_hold(mod
)) == NULL
)
96 return (topo_mod_seterrno(mod
, ETOPO_METHOD_UNKNOWN
));
98 ep
= topo_node_getspecific(tn
);
100 if (topo_prop_get_string(tn
, TOPO_PGROUP_IPMI
,
101 TOPO_PROP_IPMI_ENTITY_PRESENT
, &name
, &err
) == 0) {
103 * Some broken IPMI implementations don't export correct
104 * entities, so referring to an entity isn't sufficient.
105 * For these platforms, we allow the XML to specify a
106 * single SDR record that represents the current present
109 if ((sdrp
= ipmi_sdr_lookup(ihp
, name
)) == NULL
||
110 ipmi_entity_present_sdr(ihp
, sdrp
, &present
) != 0) {
111 topo_mod_dprintf(mod
,
112 "Failed to get present state of %s (%s)\n",
113 name
, ipmi_errmsg(ihp
));
114 topo_mod_strfree(mod
, name
);
115 topo_mod_ipmi_rele(mod
);
119 topo_mod_dprintf(mod
,
120 "ipmi_entity_present_sdr(%s) = %d\n", name
,
122 topo_mod_strfree(mod
, name
);
124 if (topo_prop_get_string_array(tn
, TOPO_PGROUP_IPMI
,
125 TOPO_PROP_IPMI_ENTITY_REF
, &names
, &nelems
, &err
)
128 * Not all nodes have an entity_ref attribute.
129 * For these cases, return ENOTSUP so that we
130 * fall back to the default hc presence
133 topo_mod_ipmi_rele(mod
);
134 return (topo_mod_seterrno(mod
,
135 ETOPO_METHOD_NOTSUP
));
138 for (i
= 0; i
< nelems
; i
++)
139 if ((ep
= ipmi_entity_lookup_sdr(ihp
, names
[i
]))
143 for (i
= 0; i
< nelems
; i
++)
144 topo_mod_strfree(mod
, names
[i
]);
145 topo_mod_free(mod
, names
, (nelems
* sizeof (char *)));
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
);
154 topo_node_setspecific(tn
, ep
);
159 if (ipmi_entity_present(ihp
, ep
, &present
) != 0) {
160 topo_mod_dprintf(mod
,
161 "ipmi_entity_present() failed: %s",
163 topo_mod_ipmi_rele(mod
);
167 topo_mod_dprintf(mod
,
168 "ipmi_entity_present(%d, %d) = %d\n", ep
->ie_type
,
169 ep
->ie_instance
, present
);
172 topo_mod_ipmi_rele(mod
);
174 if (topo_mod_nvalloc(mod
, &nvl
, NV_UNIQUE_NAME
) != 0)
175 return (topo_mod_seterrno(mod
, EMOD_FMRI_NVL
));
177 if (nvlist_add_uint32(nvl
, TOPO_METH_PRESENT_RET
, present
) != 0) {
179 return (topo_mod_seterrno(mod
, EMOD_FMRI_NVL
));
188 * This determines if the entity has a FRU locator record set, in which case we
189 * treat this as a FRU, even if it's part of an association.
193 ipmi_check_sdr(ipmi_handle_t
*ihp
, ipmi_entity_t
*ep
, const char *name
,
194 ipmi_sdr_t
*sdrp
, void *data
)
196 ipmi_enum_data_t
*edp
= data
;
198 if (sdrp
->is_type
== IPMI_SDR_TYPE_FRU_LOCATOR
)
199 edp
->ed_frusdr
= (ipmi_sdr_fru_locator_t
*)sdrp
->is_record
;
205 * Main entity enumerator. If we find a matching entity type, then instantiate
209 ipmi_check_entity(ipmi_handle_t
*ihp
, ipmi_entity_t
*ep
, void *data
)
211 ipmi_enum_data_t
*edp
= data
;
212 ipmi_enum_data_t cdata
;
213 tnode_t
*pnode
= edp
->ed_pnode
;
214 topo_mod_t
*mod
= edp
->ed_mod
;
215 topo_mod_t
*fmod
= topo_mod_getspecific(mod
);
216 nvlist_t
*auth
, *fmri
;
218 topo_pgroup_info_t pgi
;
219 char *frudata
= NULL
, *part
= NULL
, *rev
= NULL
, *serial
= NULL
;
220 ipmi_fru_prod_info_t fruprod
= {0};
221 ipmi_fru_brd_info_t frubrd
= {0};
223 const char *labelname
;
228 * Some questionable IPMI implementations group psu and fan entities
229 * under things like motherboard or chassis entities. So even if this
230 * entity type isn't typically associated with fans and psus, if it has
231 * children, then regardless of the type we need to decend down and
234 if (ep
->ie_type
!= edp
->ed_entity
) {
235 if (ep
->ie_children
!= 0 &&
236 ipmi_entity_iter_children(ihp
, ep
, ipmi_check_entity
,
243 * The purpose of power and cooling domains is to group psus and fans
244 * together. Unfortunately, some broken IPMI implementations declare
245 * domains that don't contain other elements. Since the end goal is to
246 * only enumerate psus and fans, we'll just ignore such elements.
248 if ((ep
->ie_type
== IPMI_ET_POWER_DOMAIN
||
249 ep
->ie_type
== IPMI_ET_COOLING_DOMAIN
) &&
250 ep
->ie_children
== 0)
253 if ((auth
= topo_mod_auth(mod
, pnode
)) == NULL
) {
254 topo_mod_dprintf(mod
, "topo_mod_auth() failed: %s",
255 topo_mod_errmsg(mod
));
260 * Determine if there's a FRU record associated with this entity. If
261 * so, then read in the FRU identity info so that it can be included
262 * in the authority portion of the FMRI.
264 * topo_mod_hcfmri() will safely except NULL values for the part,
265 * rev and serial params, so we opt to simply drive on in the face of
266 * any strdup failures.
268 edp
->ed_frusdr
= NULL
;
269 (void) ipmi_entity_iter_sdr(ihp
, ep
, ipmi_check_sdr
, edp
);
270 if (edp
->ed_frusdr
!= NULL
&&
271 ipmi_fru_read(ihp
, edp
->ed_frusdr
, &frudata
) != -1) {
272 if (ipmi_fru_parse_product(ihp
, frudata
, &fruprod
) == 0) {
273 part
= strdup(fruprod
.ifpi_part_number
);
274 rev
= strdup(fruprod
.ifpi_product_version
);
275 serial
= strdup(fruprod
.ifpi_product_serial
);
276 } else if (ipmi_fru_parse_board(ihp
, frudata
, &frubrd
) == 0) {
277 part
= strdup(frubrd
.ifbi_part_number
);
278 serial
= strdup(frubrd
.ifbi_product_serial
);
283 if ((fmri
= topo_mod_hcfmri(mod
, pnode
, FM_HC_SCHEME_VERSION
,
284 edp
->ed_name
, edp
->ed_instance
, NULL
, auth
, part
, rev
,
290 topo_mod_dprintf(mod
, "topo_mod_hcfmri() failed: %s",
291 topo_mod_errmsg(mod
));
299 if ((tn
= topo_node_bind(mod
, pnode
, edp
->ed_name
,
300 edp
->ed_instance
, fmri
)) == NULL
) {
302 topo_mod_dprintf(mod
, "topo_node_bind() failed: %s",
303 topo_mod_errmsg(mod
));
308 * We inherit our label from our parent, appending our label in the
309 * process. This results in defaults labels of the form "FM 1 FAN 0"
310 * by default when given a hierarchy.
312 if (edp
->ed_label
!= NULL
)
313 (void) snprintf(label
, sizeof (label
), "%s ", edp
->ed_label
);
317 switch (edp
->ed_entity
) {
318 case IPMI_ET_POWER_DOMAIN
:
326 case IPMI_ET_COOLING_DOMAIN
:
336 (void) snprintf(label
+ len
, sizeof (label
) - len
, "%s %d",
337 labelname
, edp
->ed_instance
);
342 if (topo_node_label_set(tn
, label
, &err
) != 0) {
343 topo_mod_dprintf(mod
, "failed to set label: %s\n",
349 * Store IPMI entity details as properties on the node
351 pgi
.tpi_name
= TOPO_PGROUP_IPMI
;
352 pgi
.tpi_namestab
= TOPO_STABILITY_PRIVATE
;
353 pgi
.tpi_datastab
= TOPO_STABILITY_PRIVATE
;
354 pgi
.tpi_version
= TOPO_VERSION
;
355 if (topo_pgroup_create(tn
, &pgi
, &err
) != 0) {
356 if (err
!= ETOPO_PROP_DEFD
) {
357 topo_mod_dprintf(mod
, "failed to create propgroup "
358 "%s: %s\n", TOPO_PGROUP_IPMI
, topo_strerror(err
));
364 * Add properties to contain the IPMI entity id and instance. This
365 * will be used by the fac_prov_ipmi module to discover and enumerate
366 * facility nodes for any associated sensors.
368 if (topo_prop_set_uint32(tn
, TOPO_PGROUP_IPMI
, TOPO_PROP_IPMI_ENTITY_ID
,
369 TOPO_PROP_IMMUTABLE
, ep
->ie_type
, &err
) != 0 ||
370 topo_prop_set_uint32(tn
, TOPO_PGROUP_IPMI
,
371 TOPO_PROP_IPMI_ENTITY_INST
, TOPO_PROP_IMMUTABLE
, ep
->ie_instance
,
373 topo_mod_dprintf(mod
, "failed to add ipmi properties (%s)",
377 if (topo_method_register(mod
, tn
, ipmi_methods
) != 0) {
378 topo_mod_dprintf(mod
, "topo_method_register() failed: %s",
379 topo_mod_errmsg(mod
));
384 * Invoke the tmo_enum callback from the fac_prov_ipmi module on this
385 * node. This will have the effect of registering a method on this node
386 * for enumerating sensors.
388 if (fmod
== NULL
&& (fmod
= topo_mod_load(mod
, FAC_PROV_IPMI
,
389 TOPO_VERSION
)) == NULL
) {
390 topo_mod_dprintf(mod
, "failed to load %s: %s",
391 FAC_PROV_IPMI
, topo_mod_errmsg(mod
));
394 topo_mod_setspecific(mod
, fmod
);
396 if (topo_mod_enumerate(fmod
, tn
, FAC_PROV_IPMI
, FAC_PROV_IPMI
, 0, 0,
398 topo_mod_dprintf(mod
, "facility provider enum failed (%s)",
399 topo_mod_errmsg(mod
));
404 * If we are a child of a non-chassis node, and there isn't an explicit
405 * FRU locator record, then propagate the parent's FRU. Otherwise, set
406 * the FRU to be the same as the resource.
408 if (strcmp(topo_node_name(pnode
), CHASSIS
) == 0 ||
409 edp
->ed_frusdr
!= NULL
) {
410 if (topo_node_resource(tn
, &fmri
, &err
) != 0) {
411 topo_mod_dprintf(mod
, "topo_node_resource() failed: %s",
413 (void) topo_mod_seterrno(mod
, err
);
417 if (topo_node_fru(pnode
, &fmri
, NULL
, &err
) != 0) {
418 topo_mod_dprintf(mod
, "topo_node_fru() failed: %s",
420 (void) topo_mod_seterrno(mod
, err
);
425 if (topo_node_fru_set(tn
, fmri
, 0, &err
) != 0) {
427 topo_mod_dprintf(mod
, "topo_node_fru_set() failed: %s",
429 (void) topo_mod_seterrno(mod
, err
);
433 topo_node_setspecific(tn
, ep
);
438 * Iterate over children, once for recursive domains and once for
441 if (ep
->ie_children
!= 0 &&
442 (ep
->ie_type
== IPMI_ET_POWER_DOMAIN
||
443 ep
->ie_type
== IPMI_ET_COOLING_DOMAIN
)) {
444 cdata
.ed_mod
= edp
->ed_mod
;
446 cdata
.ed_instance
= 0;
447 cdata
.ed_name
= edp
->ed_name
;
448 cdata
.ed_entity
= edp
->ed_entity
;
449 cdata
.ed_label
= label
;
451 if (ipmi_entity_iter_children(ihp
, ep
,
452 ipmi_check_entity
, &cdata
) != 0)
455 switch (cdata
.ed_entity
) {
456 case IPMI_ET_POWER_DOMAIN
:
457 cdata
.ed_entity
= IPMI_ET_PSU
;
461 case IPMI_ET_COOLING_DOMAIN
:
462 cdata
.ed_entity
= IPMI_ET_FAN
;
467 if (ipmi_entity_iter_children(ihp
, ep
,
468 ipmi_check_entity
, &cdata
) != 0)
476 * libtopo enumeration point. This simply iterates over entities looking for
477 * the appropriate type.
481 ipmi_enum(topo_mod_t
*mod
, tnode_t
*rnode
, const char *name
,
482 topo_instance_t min
, topo_instance_t max
, void *arg
, void *unused
)
485 ipmi_enum_data_t data
;
489 * If the node being passed in ISN'T the chassis node, then we're being
490 * asked to post-process a statically defined node.
492 if (strcmp(topo_node_name(rnode
), CHASSIS
) != 0) {
493 if (ipmi_post_process(mod
, rnode
) != 0) {
494 topo_mod_dprintf(mod
, "post processing of node %s=%d "
495 "failed!", topo_node_name(rnode
),
496 topo_node_instance(rnode
));
502 if (strcmp(name
, POWERMODULE
) == 0) {
503 data
.ed_entity
= IPMI_ET_POWER_DOMAIN
;
504 } else if (strcmp(name
, PSU
) == 0) {
505 data
.ed_entity
= IPMI_ET_PSU
;
506 } else if (strcmp(name
, FANMODULE
) == 0) {
507 data
.ed_entity
= IPMI_ET_COOLING_DOMAIN
;
508 } else if (strcmp(name
, FAN
) == 0) {
509 data
.ed_entity
= IPMI_ET_FAN
;
511 topo_mod_dprintf(mod
, "unknown enumeration type '%s'",
516 if ((ihp
= topo_mod_ipmi_hold(mod
)) == NULL
)
520 data
.ed_pnode
= rnode
;
522 data
.ed_instance
= 0;
523 data
.ed_label
= NULL
;
525 if ((ret
= ipmi_entity_iter(ihp
, ipmi_check_entity
, &data
)) != 0) {
527 * We don't return failure if IPMI enumeration fails. This may
528 * be due to the SP being unavailable or an otherwise transient
532 topo_mod_dprintf(mod
,
533 "failed to enumerate entities: %s",
536 topo_mod_ipmi_rele(mod
);
541 topo_mod_ipmi_rele(mod
);
546 ipmi_post_process(topo_mod_t
*mod
, tnode_t
*tn
)
548 if (topo_method_register(mod
, tn
, ipmi_methods
) != 0) {
549 topo_mod_dprintf(mod
, "ipmi_post_process() failed: %s",
550 topo_mod_errmsg(mod
));
558 _topo_init(topo_mod_t
*mod
, topo_version_t version
)
560 if (getenv("TOPOIPMIDEBUG") != NULL
)
561 topo_mod_setdebug(mod
);
563 if (topo_mod_register(mod
, &ipmi_info
, TOPO_VERSION
) != 0) {
564 topo_mod_dprintf(mod
, "module registration failed: %s\n",
565 topo_mod_errmsg(mod
));
566 return (-1); /* mod errno already set */
569 topo_mod_dprintf(mod
, "IPMI enumerator initialized\n");
574 _topo_fini(topo_mod_t
*mod
)
577 * This is the logical, and probably only safe spot where we could
578 * unload fac_prov_ipmi. But unfortunately, calling topo_mod_unload()
579 * in the context of a module's _topo_fini entry point would result
580 * in recursively grabbing the modhash lock and we'd deadlock.
582 * Unfortunately, libtopo doesn't currently have a mechanism for
583 * expressing and handling intermodule dependencies, so we're left
584 * with this situation where once a module loads another module,
585 * it's going to be with us until we teardown the process.
587 topo_mod_unregister(mod
);