/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2020 Joyent, Inc. * Copyright 2023 Oxide Computer Company */ /* * Snapshot Library Interfaces * * Consumers of topology data may use the interfaces in this file to open, * snapshot and close a topology exported by FMRI scheme (hc, mem and cpu) * builtin plugins and their helper modules. A topology handle is obtained * by calling topo_open(). Upon a successful return, the caller may use this * handle to open a new snapshot. Each snapshot is assigned a Universally * Unique Identifier that in a future enchancement to the libtopo API will be * used as the file locator in /var/fm/topo to persist new snapshots or lookup * a previously captured snapshot. topo_snap_hold() will capture the current * system topology. All consumers of the topo_hdl_t argument will be * blocked from accessing the topology trees until the snapshot completes. * * A snapshot may be cleared by calling topo_snap_rele(). As with * topo_snap_hold(), all topology accesses are blocked until the topology * trees have been released and deallocated. * * Walker Library Interfaces * * Once a snapshot has been taken with topo_snap_hold(), topo_hdl_t holders * may initiate topology tree walks on a scheme-tree basis. topo_walk_init() * will initiate the data structures required to walk any one one of the * FMRI scheme trees. The walker data structure, topo_walk_t, is an opaque * handle passed to topo_walk_step to begin the walk. At each node in the * topology tree, a callback function is called with access to the node at * which our current walk falls. The callback function is passed in during * calls to topo_walk_init() and used throughout the walk_step of the * scheme tree. At any time, the callback may terminate the walk by returning * TOPO_WALK_TERMINATE or TOPO_WALK_ERR. TOPO_WALK_NEXT will continue the walk. * * The type of walk through the tree may be sibling first or child first by * respectively passing in TOPO_WALK_SIBLING or TOPO_WALK_CHILD to * the topo_walk_step() function. Topology nodes * associated with an outstanding walk are held in place and will not be * deallocated until the walk through that node completes. * * Once the walk has terminated, the walking process should call * topo_walk_fini() to clean-up resources created in topo_walk_init() * and release nodes that may be still held. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static void topo_snap_destroy(topo_hdl_t *); static topo_hdl_t * set_open_errno(topo_hdl_t *thp, int *errp, int err) { if (thp != NULL) { topo_close(thp); } if (errp != NULL) *errp = err; return (NULL); } /* * Set the product name for the topo handle. There are three things that we will * use here in the following order: * * 1) See if SMBIOS gives us a product string that is usable. * 2) Use the root devinfo node's name if we can get a snapshot. This may be the * same as the uname -i aka th_platform on most systems, but on some it will * be more specific. In particular this happens if we have a platform-wide * unix but end up having machine/implementation knowledge without a * machine/implementation-specific unix. * 3) We fall back like we have historically to the platform name. */ static void topo_hdl_set_product(topo_hdl_t *thp) { smbios_hdl_t *shp; di_node_t root; id_t id; thp->th_product = NULL; if ((shp = smbios_open(NULL, SMB_VERSION, 0, NULL)) != NULL) { smbios_system_t s1; smbios_info_t s2; if ((id = smbios_info_system(shp, &s1)) != SMB_ERR && smbios_info_common(shp, id, &s2) != SMB_ERR) { if (strcmp(s2.smbi_product, SMB_DEFAULT1) != 0 && strcmp(s2.smbi_product, SMB_DEFAULT2) != 0) { thp->th_product = topo_cleanup_auth_str(thp, (char *)s2.smbi_product); } } smbios_close(shp); } if (thp->th_product != NULL) return; root = di_init("/", DINFOCACHE); if (root != DI_NODE_NIL) { thp->th_product = topo_hdl_strdup(thp, di_node_name(root)); di_fini(root); } if (thp->th_product != NULL) return; thp->th_product = topo_hdl_strdup(thp, thp->th_platform); } topo_hdl_t * topo_open(int version, const char *rootdir, int *errp) { topo_hdl_t *thp = NULL; topo_alloc_t *tap; char platform[MAXNAMELEN]; char isa[MAXNAMELEN]; struct utsname uts; struct stat st; char *dbflags, *dbout; if (version != TOPO_VERSION) return (set_open_errno(thp, errp, ETOPO_HDL_ABIVER)); if (rootdir != NULL && stat(rootdir, &st) < 0) return (set_open_errno(thp, errp, ETOPO_HDL_INVAL)); if ((thp = topo_zalloc(sizeof (topo_hdl_t), 0)) == NULL) return (set_open_errno(thp, errp, ETOPO_NOMEM)); (void) pthread_mutex_init(&thp->th_lock, NULL); if ((tap = topo_zalloc(sizeof (topo_alloc_t), 0)) == NULL) return (set_open_errno(thp, errp, ETOPO_NOMEM)); /* * Install default allocators */ tap->ta_flags = 0; tap->ta_alloc = topo_alloc; tap->ta_zalloc = topo_zalloc; tap->ta_free = topo_free; tap->ta_nvops.nv_ao_alloc = topo_nv_alloc; tap->ta_nvops.nv_ao_free = topo_nv_free; (void) nv_alloc_init(&tap->ta_nva, &tap->ta_nvops); thp->th_alloc = tap; if ((thp->th_modhash = topo_modhash_create(thp)) == NULL) return (set_open_errno(thp, errp, ETOPO_NOMEM)); /* * Set-up system information and search paths for modules * and topology map files */ if (rootdir == NULL) { rootdir = topo_hdl_strdup(thp, "/"); thp->th_rootdir = (char *)rootdir; } else { int len; char *rpath; len = strlen(rootdir); if (len >= PATH_MAX) return (set_open_errno(thp, errp, EINVAL)); if (rootdir[len - 1] != '/') { rpath = alloca(len + 2); (void) snprintf(rpath, len + 2, "%s/", rootdir); } else { rpath = (char *)rootdir; } thp->th_rootdir = topo_hdl_strdup(thp, rpath); } platform[0] = '\0'; isa[0] = '\0'; (void) sysinfo(SI_PLATFORM, platform, sizeof (platform)); (void) sysinfo(SI_ARCHITECTURE, isa, sizeof (isa)); (void) uname(&uts); thp->th_platform = topo_hdl_strdup(thp, platform); thp->th_isa = topo_hdl_strdup(thp, isa); thp->th_machine = topo_hdl_strdup(thp, uts.machine); topo_hdl_set_product(thp); if (thp->th_rootdir == NULL || thp->th_platform == NULL || thp->th_machine == NULL || thp->th_product == NULL || thp->th_isa == NULL) return (set_open_errno(thp, errp, ETOPO_NOMEM)); dbflags = getenv("TOPO_DEBUG"); dbout = getenv("TOPO_DEBUG_OUT"); if (dbflags != NULL) topo_debug_set(thp, dbflags, dbout); if (topo_builtin_create(thp, thp->th_rootdir) != 0) { topo_dprintf(thp, TOPO_DBG_ERR, "failed to load builtin modules: %s\n", topo_hdl_errmsg(thp)); return (set_open_errno(thp, errp, topo_hdl_errno(thp))); } return (thp); } void topo_close(topo_hdl_t *thp) { ttree_t *tp; topo_digraph_t *tdg; topo_hdl_lock(thp); if (thp->th_platform != NULL) topo_hdl_strfree(thp, thp->th_platform); if (thp->th_isa != NULL) topo_hdl_strfree(thp, thp->th_isa); if (thp->th_machine != NULL) topo_hdl_strfree(thp, thp->th_machine); if (thp->th_product != NULL) topo_hdl_strfree(thp, thp->th_product); if (thp->th_rootdir != NULL) topo_hdl_strfree(thp, thp->th_rootdir); if (thp->th_ipmi != NULL) ipmi_close(thp->th_ipmi); if (thp->th_smbios != NULL) smbios_close(thp->th_smbios); if (thp->th_pcidb != NULL) pcidb_close(thp->th_pcidb); /* * Clean-up snapshot */ topo_snap_destroy(thp); /* * Clean-up trees */ while ((tp = topo_list_next(&thp->th_trees)) != NULL) { topo_list_delete(&thp->th_trees, tp); topo_tree_destroy(tp); } /* * Clean-up digraphs */ while ((tdg = topo_list_next(&thp->th_digraphs)) != NULL) { topo_list_delete(&thp->th_digraphs, tdg); topo_digraph_destroy(tdg); } /* * Unload all plugins */ topo_modhash_unload_all(thp); if (thp->th_modhash != NULL) topo_modhash_destroy(thp); if (thp->th_alloc != NULL) topo_free(thp->th_alloc, sizeof (topo_alloc_t)); topo_hdl_unlock(thp); topo_free(thp, sizeof (topo_hdl_t)); } static char * topo_snap_create(topo_hdl_t *thp, int *errp, boolean_t need_force) { uuid_t uuid; char *ustr = NULL; topo_hdl_lock(thp); if (thp->th_uuid != NULL) { *errp = ETOPO_HDL_UUID; topo_hdl_unlock(thp); return (NULL); } if ((thp->th_uuid = topo_hdl_zalloc(thp, TOPO_UUID_SIZE)) == NULL) { *errp = ETOPO_NOMEM; topo_dprintf(thp, TOPO_DBG_ERR, "unable to allocate uuid: %s\n", topo_strerror(*errp)); topo_hdl_unlock(thp); return (NULL); } uuid_generate(uuid); uuid_unparse(uuid, thp->th_uuid); if ((ustr = topo_hdl_strdup(thp, thp->th_uuid)) == NULL) { *errp = ETOPO_NOMEM; topo_hdl_unlock(thp); return (NULL); } if (need_force) { topo_dprintf(thp, TOPO_DBG_FORCE, "taking a DINFOFORCE snapshot\n"); thp->th_di = di_init("/", DINFOFORCE | DINFOSUBTREE | DINFOMINOR | DINFOPROP | DINFOPATH); } else { thp->th_di = di_init("/", DINFOCACHE); } thp->th_pi = di_prom_init(); if (topo_tree_enum_all(thp) < 0) { topo_dprintf(thp, TOPO_DBG_ERR, "enumeration failure: %s\n", topo_hdl_errmsg(thp)); if (topo_hdl_errno(thp) == ETOPO_ENUM_FATAL) { *errp = thp->th_errno; if (thp->th_di != DI_NODE_NIL) { di_fini(thp->th_di); thp->th_di = DI_NODE_NIL; } if (thp->th_pi != DI_PROM_HANDLE_NIL) { di_prom_fini(thp->th_pi); thp->th_pi = DI_PROM_HANDLE_NIL; } topo_hdl_strfree(thp, ustr); topo_hdl_unlock(thp); return (NULL); } } if (thp->th_ipmi != NULL && ipmi_sdr_changed(thp->th_ipmi) && ipmi_sdr_refresh(thp->th_ipmi) != 0) { topo_dprintf(thp, TOPO_DBG_ERR, "failed to refresh IPMI sdr repository: %s\n", ipmi_errmsg(thp->th_ipmi)); } topo_hdl_unlock(thp); return (ustr); } /*ARGSUSED*/ static char * topo_snap_log_create(topo_hdl_t *thp, const char *uuid, int *errp) { return ((char *)uuid); } /*ARGSUSED*/ static int fac_walker(topo_hdl_t *thp, tnode_t *node, void *arg) { int err; nvlist_t *out; if (topo_method_supported(node, TOPO_METH_FAC_ENUM, 0)) { /* * If the facility enumeration method fails, note the failure, * but continue on with the walk. */ if (topo_method_invoke(node, TOPO_METH_FAC_ENUM, 0, NULL, &out, &err) != 0) { topo_dprintf(thp, TOPO_DBG_ERR, "facility enumeration method failed on node %s=%d " "(%s)\n", topo_node_name(node), topo_node_instance(node), topo_strerror(err)); } } return (TOPO_WALK_NEXT); } /* * Return snapshot id */ char * topo_snap_hold(topo_hdl_t *thp, const char *uuid, int *errp) { topo_walk_t *twp; if (thp == NULL) return (NULL); if (uuid == NULL) { char *ret; if (thp->th_debug & TOPO_DBG_FORCE) { ret = topo_snap_create(thp, errp, B_TRUE); } else { ret = topo_snap_create(thp, errp, B_FALSE); } /* * Now walk the tree and invoke any facility enumeration methods */ if (ret != NULL && getzoneid() == 0) { if ((twp = topo_walk_init(thp, FM_FMRI_SCHEME_HC, fac_walker, (void *)0, errp)) == NULL) { return (ret); } (void) topo_walk_step(twp, TOPO_WALK_CHILD); topo_walk_fini(twp); } return (ret); } return (topo_snap_log_create(thp, uuid, errp)); } /*ARGSUSED*/ static int topo_walk_destroy(topo_hdl_t *thp, tnode_t *node, void *notused) { tnode_t *cnode; cnode = topo_child_first(node); if (cnode != NULL) return (TOPO_WALK_NEXT); topo_node_unbind(node); return (TOPO_WALK_NEXT); } static void topo_snap_destroy(topo_hdl_t *thp) { int i; ttree_t *tp; topo_digraph_t *tdg; topo_walk_t *twp; tnode_t *root; topo_nodehash_t *nhp; topo_mod_t *mod; for (tp = topo_list_next(&thp->th_trees); tp != NULL; tp = topo_list_next(tp)) { root = tp->tt_root; twp = tp->tt_walk; /* * Clean-up tree nodes from the bottom-up */ if ((twp->tw_node = topo_child_first(root)) != NULL) { twp->tw_cb = topo_walk_destroy; topo_node_hold(root); topo_node_hold(twp->tw_node); /* released at walk end */ (void) topo_walk_bottomup(twp, TOPO_WALK_CHILD); topo_node_rele(root); } /* * Tidy-up the root node */ while ((nhp = topo_list_next(&root->tn_children)) != NULL) { for (i = 0; i < nhp->th_arrlen; i++) { assert(nhp->th_nodearr[i] == NULL); } mod = nhp->th_enum; topo_mod_strfree(mod, nhp->th_name); topo_mod_free(mod, nhp->th_nodearr, nhp->th_arrlen * sizeof (tnode_t *)); topo_list_delete(&root->tn_children, nhp); topo_mod_free(mod, nhp, sizeof (topo_nodehash_t)); topo_mod_rele(mod); } } for (tdg = topo_list_next(&thp->th_digraphs); tdg != NULL; tdg = topo_list_next(tdg)) { topo_vertex_t *vtx; if (tdg->tdg_nvertices == 0) continue; /* * We maintain an adjacency list in the topo_digraph_t * structure, so we can just walk the list to destroy all the * vertices. */ mod = tdg->tdg_mod; vtx = topo_list_next(&tdg->tdg_vertices); while (vtx != NULL) { topo_vertex_t *tmp = vtx; vtx = topo_list_next(vtx); topo_vertex_destroy(mod, tmp); } tdg->tdg_nvertices = 0; } /* * Clean-up our cached devinfo and prom tree handles. */ if (thp->th_di != DI_NODE_NIL) { di_fini(thp->th_di); thp->th_di = DI_NODE_NIL; } if (thp->th_pi != DI_PROM_HANDLE_NIL) { di_prom_fini(thp->th_pi); thp->th_pi = DI_PROM_HANDLE_NIL; } if (thp->th_uuid != NULL) { topo_hdl_free(thp, thp->th_uuid, TOPO_UUID_SIZE); thp->th_uuid = NULL; } } void topo_snap_release(topo_hdl_t *thp) { if (thp == NULL) return; topo_hdl_lock(thp); topo_snap_destroy(thp); topo_hdl_unlock(thp); } topo_walk_t * topo_walk_init(topo_hdl_t *thp, const char *scheme, topo_walk_cb_t cb_f, void *pdata, int *errp) { ttree_t *tp; topo_walk_t *wp; for (tp = topo_list_next(&thp->th_trees); tp != NULL; tp = topo_list_next(tp)) { if (strcmp(scheme, tp->tt_scheme) == 0) { /* * Hold the root node and start walk at the first * child node */ assert(tp->tt_root != NULL); if ((wp = topo_node_walk_init(thp, NULL, tp->tt_root, cb_f, pdata, errp)) == NULL) /* errp set */ return (NULL); return (wp); } } *errp = ETOPO_WALK_NOTFOUND; return (NULL); } static int step_child(tnode_t *cnp, topo_walk_t *wp, int flag, int bottomup) { int status; tnode_t *nnp; nnp = topo_child_first(cnp); if (nnp == NULL) { topo_dprintf(wp->tw_thp, TOPO_DBG_WALK, "step_child: TOPO_WALK_TERMINATE for %s=%d\n", cnp->tn_name, cnp->tn_instance); return (TOPO_WALK_TERMINATE); } topo_dprintf(wp->tw_thp, TOPO_DBG_WALK, "step_child: walk through node %s=%d to %s=%d\n", cnp->tn_name, cnp->tn_instance, nnp->tn_name, nnp->tn_instance); topo_node_hold(nnp); /* released on return from walk_step */ wp->tw_node = nnp; if (bottomup == 1) status = topo_walk_bottomup(wp, flag); else status = topo_walk_step(wp, flag); return (status); } static int step_sibling(tnode_t *cnp, topo_walk_t *wp, int flag, int bottomup) { int status; tnode_t *nnp; nnp = topo_child_next(cnp->tn_parent, cnp); if (nnp == NULL) { topo_dprintf(wp->tw_thp, TOPO_DBG_WALK, "step_sibling: TOPO_WALK_TERMINATE for %s=%d\n", cnp->tn_name, cnp->tn_instance); return (TOPO_WALK_TERMINATE); } topo_dprintf(wp->tw_thp, TOPO_DBG_WALK, "step_sibling: through sibling node %s=%d to %s=%d\n", cnp->tn_name, cnp->tn_instance, nnp->tn_name, nnp->tn_instance); topo_node_hold(nnp); /* released on return from walk_step */ wp->tw_node = nnp; if (bottomup == 1) status = topo_walk_bottomup(wp, flag); else status = topo_walk_step(wp, flag); return (status); } int topo_walk_byid(topo_walk_t *wp, const char *name, topo_instance_t inst) { int status; tnode_t *nnp, *cnp; cnp = wp->tw_node; nnp = topo_node_lookup(cnp, name, inst); if (nnp == NULL) return (TOPO_WALK_TERMINATE); topo_node_hold(nnp); wp->tw_node = nnp; if (wp->tw_mod != NULL) status = wp->tw_cb(wp->tw_mod, nnp, wp->tw_pdata); else status = wp->tw_cb(wp->tw_thp, nnp, wp->tw_pdata); topo_node_rele(nnp); wp->tw_node = cnp; return (status); } int topo_walk_bysibling(topo_walk_t *wp, const char *name, topo_instance_t inst) { int status; tnode_t *cnp, *pnp; cnp = wp->tw_node; pnp = topo_node_parent(cnp); assert(pnp != NULL); topo_node_hold(pnp); wp->tw_node = pnp; status = topo_walk_byid(wp, name, inst); topo_node_rele(pnp); wp->tw_node = cnp; return (status); } int topo_walk_step(topo_walk_t *wp, int flag) { int status; tnode_t *cnp = wp->tw_node; if (flag != TOPO_WALK_CHILD && flag != TOPO_WALK_SIBLING) { topo_node_rele(cnp); return (TOPO_WALK_ERR); } /* * No more nodes to walk */ if (cnp == NULL) { topo_dprintf(wp->tw_thp, TOPO_DBG_WALK, "walk_step terminated\n"); topo_node_rele(cnp); return (TOPO_WALK_TERMINATE); } if (wp->tw_mod != NULL) status = wp->tw_cb(wp->tw_mod, cnp, wp->tw_pdata); else status = wp->tw_cb(wp->tw_thp, cnp, wp->tw_pdata); /* * Walker callback says we're done */ if (status != TOPO_WALK_NEXT) { topo_node_rele(cnp); return (status); } if (flag == TOPO_WALK_CHILD) status = step_child(cnp, wp, flag, 0); else status = step_sibling(cnp, wp, flag, 0); /* * No more nodes in this hash, skip to next node hash by stepping * to next sibling (child-first walk) or next child (sibling-first * walk). */ if (status == TOPO_WALK_TERMINATE) { if (flag == TOPO_WALK_CHILD) status = step_sibling(cnp, wp, flag, 0); else status = step_child(cnp, wp, flag, 0); } topo_node_rele(cnp); /* done with current node */ return (status); } void topo_walk_fini(topo_walk_t *wp) { if (wp == NULL) return; topo_node_rele(wp->tw_root); topo_hdl_free(wp->tw_thp, wp, sizeof (topo_walk_t)); } int topo_walk_bottomup(topo_walk_t *wp, int flag) { int status; tnode_t *cnp; if (wp == NULL) return (TOPO_WALK_ERR); cnp = wp->tw_node; if (flag != TOPO_WALK_CHILD && flag != TOPO_WALK_SIBLING) { topo_node_rele(cnp); return (TOPO_WALK_ERR); } /* * End of the line */ if (cnp == NULL) { topo_dprintf(wp->tw_thp, TOPO_DBG_WALK, "walk_bottomup terminated\n"); topo_node_rele(cnp); return (TOPO_WALK_TERMINATE); } topo_dprintf(wp->tw_thp, TOPO_DBG_WALK, "%s walk_bottomup through node %s=%d\n", (flag == TOPO_WALK_CHILD ? "TOPO_WALK_CHILD" : "TOPO_WALK_SIBLING"), cnp->tn_name, cnp->tn_instance); if (flag == TOPO_WALK_CHILD) status = step_child(cnp, wp, flag, 1); else status = step_sibling(cnp, wp, flag, 1); /* * At a leaf, run the callback */ if (status == TOPO_WALK_TERMINATE) { if ((status = wp->tw_cb(wp->tw_thp, cnp, wp->tw_pdata)) != TOPO_WALK_NEXT) { topo_node_rele(cnp); return (status); } } /* * Try next child or sibling */ if (status == TOPO_WALK_NEXT) { if (flag == TOPO_WALK_CHILD) status = step_sibling(cnp, wp, flag, 1); else status = step_child(cnp, wp, flag, 1); } topo_node_rele(cnp); /* done with current node */ return (status); } di_node_t topo_hdl_devinfo(topo_hdl_t *thp) { return (thp == NULL ? DI_NODE_NIL : thp->th_di); } di_prom_handle_t topo_hdl_prominfo(topo_hdl_t *thp) { return (thp == NULL ? DI_PROM_HANDLE_NIL : thp->th_pi); } int topo_scheme_walk(topo_hdl_t *thp, topo_scheme_walk_cb_f cb, void *arg) { for (ttree_t *tp = topo_list_next(&thp->th_trees); tp != NULL; tp = topo_list_next(tp)) { int ret; topo_scheme_info_t info; info.tsi_scheme = tp->tt_scheme; info.tsi_type = TOPO_SCHEME_TREE; ret = cb(thp, &info, arg); if (ret != TOPO_WALK_NEXT) { return (ret); } } for (topo_digraph_t *tdg = topo_list_next(&thp->th_digraphs); tdg != NULL; tdg = topo_list_next(tdg)) { int ret; topo_scheme_info_t info; info.tsi_scheme = tdg->tdg_scheme; info.tsi_type = TOPO_SCHEME_DIGRAPH; ret = cb(thp, &info, arg); if (ret != TOPO_WALK_NEXT) { return (ret); } } return (TOPO_WALK_TERMINATE); }