/* * 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 2019 Joyent, Inc. */ /* * did.c * The acronym did means "Dev-Info-Data". Many properties and * characteristics of topology nodes are, with a bit of coaxing * derived from devinfo nodes. These routines do some of the * derivation and also encapsulate the discoveries in did_t * structures that get associated with topology nodes as their * "private" data. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "did_impl.h" static void slotnm_destroy(slotnm_t *); static slotnm_t * slotnm_create(topo_mod_t *mp, int dev, char *str) { slotnm_t *p; if ((p = topo_mod_alloc(mp, sizeof (slotnm_t))) == NULL) return (NULL); p->snm_mod = mp; p->snm_next = NULL; p->snm_dev = dev; p->snm_name = topo_mod_strdup(mp, str); if (p->snm_name == NULL) { slotnm_destroy(p); return (NULL); } return (p); } static void slotnm_destroy(slotnm_t *p) { if (p == NULL) return; slotnm_destroy(p->snm_next); if (p->snm_name != NULL) topo_mod_strfree(p->snm_mod, p->snm_name); topo_mod_free(p->snm_mod, p, sizeof (slotnm_t)); } static int di_devtype_get(topo_mod_t *mp, di_node_t src, char **devtype) { int sz; uchar_t *buf; /* * For PCI the device type defined the type of device directly below. * For PCIe RP and Switches, the device-type should be "pciex". For * PCIe-PCI and PCI-PCI bridges it should be "pci". NICs = "network", * Graphics = "display", etc.. */ if (di_bytes_get(mp, src, DI_DEVTYPPROP, &sz, &buf) == 0) { *devtype = topo_mod_strdup(mp, (char *)buf); } else { *devtype = NULL; } if (*devtype != NULL) return (0); return (-1); } typedef struct smbios_slot_cb { int cb_slotnum; int cb_bdf; const char *cb_label; } smbios_slot_cb_t; static int di_smbios_find_slot_by_bdf(smbios_hdl_t *shp, const smbios_struct_t *strp, void *data) { smbios_slot_cb_t *cbp = data; smbios_slot_t slot; int bus, df; bus = (cbp->cb_bdf & 0xFF00) >> 8; df = cbp->cb_bdf & 0xFF; if (strp->smbstr_type != SMB_TYPE_SLOT || smbios_info_slot(shp, strp->smbstr_id, &slot) != 0) return (0); if (slot.smbl_bus == bus && slot.smbl_df == df) { cbp->cb_label = slot.smbl_name; cbp->cb_slotnum = slot.smbl_id; return (1); } return (0); } static int di_smbios_find_slot_by_id(smbios_hdl_t *shp, const smbios_struct_t *strp, void *data) { smbios_slot_cb_t *cbp = data; smbios_slot_t slot; if (strp->smbstr_type != SMB_TYPE_SLOT || smbios_info_slot(shp, strp->smbstr_id, &slot) != 0) return (0); if (slot.smbl_id == cbp->cb_slotnum) { cbp->cb_label = slot.smbl_name; return (1); } return (0); } static int di_physlotinfo_get(topo_mod_t *mp, di_node_t src, int bdf, int *slotnum, char **slotname) { char *slotbuf = NULL; int sz; uchar_t *buf; smbios_hdl_t *shp; boolean_t got_slotprop = B_FALSE; *slotnum = -1; (void) di_uintprop_get(mp, src, DI_PHYSPROP, (uint_t *)slotnum); /* * For PCI-Express, there is only one downstream device, so check for * a slot-names property, and if it exists, ignore the slotmask value * and use the string as the label. */ if (di_bytes_get(mp, src, DI_SLOTPROP, &sz, &buf) == 0 && sz > 4) { /* * If there is a DI_SLOTPROP of the form SlotX (ie set up from * the IRQ routing table) then trust that in preference to * DI_PHYSPROP (which is set up from the PCIe slotcap reg). */ got_slotprop = B_TRUE; (void) sscanf((char *)&buf[4], "Slot%d", slotnum); } /* * If the system supports SMBIOS (virtual certainty on X86) then we will * source the label from the Type 9 (Slot) records. If we're unable * to correlate the device with a slot record (as would happen with * onboard PCIe devices), we return without setting slotname, which will * ultimately result in the node inheriting the FRU label from its * parent node. * * In the absence of any SMBIOS support (i.e. SPARC) then we will use * the slot-names property, if available. Otherwise we'll fall back * to fabricating a label based on the slot number. */ if ((shp = topo_mod_smbios(mp)) != NULL) { /* * The PCI spec describes slot number 0 as reserved for * internal PCI devices. Unfortunately, not all platforms * respect this. For that reason, we prefer to lookup the slot * record using the device's BDF. However, SMBIOS * implementations prior to 2.6 don't encode the BDF in the * slot record. In that case we resort to looking up the * slot record using the slot number. */ smbios_slot_cb_t cbdata; smbios_version_t smbv; boolean_t bdf_supp = B_TRUE; cbdata.cb_slotnum = *slotnum; cbdata.cb_bdf = bdf; cbdata.cb_label = NULL; /* * The bus and device/fn payload members of the SMBIOS slot * record were added in SMBIOS 2.6. */ smbios_info_smbios_version(shp, &smbv); if (smbv.smbv_major < 2 || (smbv.smbv_major == 2 && smbv.smbv_minor < 6)) { bdf_supp = B_FALSE; } /* * If the SMBIOS implementation is too old to look up the slot * records by BDF and we weren't able to derive a slotnum then * there is nothing we can do here. */ if (!bdf_supp && *slotnum == -1) return (0); if (bdf_supp) (void) smbios_iter(shp, di_smbios_find_slot_by_bdf, &cbdata); else (void) smbios_iter(shp, di_smbios_find_slot_by_id, &cbdata); if (cbdata.cb_label == NULL) return (0); slotbuf = (char *)cbdata.cb_label; topo_mod_dprintf(mp, "%s: di_node=%p: using smbios name: %s\n", __func__, src, slotbuf); } else if (got_slotprop) { slotbuf = (char *)&buf[4]; topo_mod_dprintf(mp, "%s: di_node=%p: using %s property: %s\n", __func__, src, DI_SLOTPROP, slotbuf); } else { /* * Make generic description string "SLOT ", allow up to * 10 digits for number. Bail, if we weren't able to derive * a slotnum. */ if (*slotnum == -1) return (0); slotbuf = alloca(16); (void) snprintf(slotbuf, 16, "SLOT %d", *slotnum); topo_mod_dprintf(mp, "%s: di_node=%p: using fabricated slot " "name: %s\n", __func__, src, slotbuf); } if ((*slotname = topo_mod_strdup(mp, slotbuf)) == NULL) { /* topo errno set */ return (-1); } return (0); } static int di_slotinfo_get(topo_mod_t *mp, di_node_t src, int *nslots, slotnm_t **slotnames) { slotnm_t *lastslot = NULL; slotnm_t *newslot; uchar_t *slotbuf; uint_t slotmap = 0; char *slotname; int andbit; int sz = -1; *slotnames = NULL; *nslots = 0; if (di_bytes_get(mp, src, DI_SLOTPROP, &sz, &slotbuf) < 0) return (0); if (sz < sizeof (uint_t)) return (0); bcopy(slotbuf, &slotmap, sizeof (uint_t)); if (slotmap == 0) return (0); slotname = (char *)&slotbuf[4]; for (andbit = 0; andbit < 32; andbit++) { if (slotmap & (1 << andbit)) { char *s = slotname; slotname += strlen(s) + 1; if ((newslot = slotnm_create(mp, andbit, s)) == NULL) { slotnm_destroy(*slotnames); *slotnames = NULL; *nslots = 0; return (-1); } if (lastslot == NULL) *slotnames = lastslot = newslot; else { lastslot->snm_next = newslot; lastslot = newslot; } (*nslots)++; } } return (0); } int did_physlot(did_t *did) { assert(did != NULL); return (did->dp_physlot); } int did_physlot_exists(did_t *did) { assert(did != NULL); return ((did->dp_physlot >= 0) || (did->dp_nslots > 0)); } did_t * did_create(topo_mod_t *mp, di_node_t src, int ibrd, int ibrdge, int irc, int ibus) { did_t *np; did_t *pd; uint_t code; uint_t reg; if ((pd = did_hash_lookup(mp, src)) != NULL) { topo_mod_dprintf(mp, "Attempt to create existing did_t.\n"); assert(ibus == TRUST_BDF || (pd->dp_bus == ibus)); return (pd); } if ((np = topo_mod_zalloc(mp, sizeof (did_t))) == NULL) return (NULL); np->dp_mod = mp; np->dp_src = src; np->dp_hash = (did_hash_t *)topo_mod_getspecific(mp); np->dp_tnode = NULL; /* * We must have a reg prop and from it we extract the bus #, * device #, and function #. */ if (di_uintprop_get(mp, src, DI_REGPROP, ®) < 0) { topo_mod_free(mp, np, sizeof (did_t)); return (NULL); } np->dp_board = ibrd; np->dp_bridge = ibrdge; np->dp_rc = irc; if (ibus == TRUST_BDF) np->dp_bus = PCI_REG_BUS_G(reg); else np->dp_bus = ibus; np->dp_dev = PCI_REG_DEV_G(reg); np->dp_fn = PCI_REG_FUNC_G(reg); np->dp_bdf = (PCI_REG_BUS_G(reg) << 8) | (PCI_REG_DEV_G(reg) << 3) | PCI_REG_FUNC_G(reg); /* * There *may* be a class code we can capture. If there wasn't * one, capture that fact by setting the class value to -1. */ if (di_uintprop_get(mp, src, DI_CCPROP, &code) == 0) { np->dp_class = GETCLASS(code); np->dp_subclass = GETSUBCLASS(code); } else { np->dp_class = -1; } /* * There *may* be a device type we can capture. */ (void) di_devtype_get(mp, src, &np->dp_devtype); if (irc >= 0) { /* * This is a pciex node. */ if (di_physlotinfo_get(mp, src, np->dp_bdf, &np->dp_physlot, &np->dp_physlot_name) < 0) { if (np->dp_devtype != NULL) topo_mod_strfree(mp, np->dp_devtype); topo_mod_free(mp, np, sizeof (did_t)); return (NULL); } } else { /* * This is a pci node. */ np->dp_physlot = -1; if (di_slotinfo_get(mp, src, &np->dp_nslots, &np->dp_slotnames) < 0) { if (np->dp_devtype != NULL) topo_mod_strfree(mp, np->dp_devtype); topo_mod_free(mp, np, sizeof (did_t)); return (NULL); } } did_hash_insert(mp, src, np); did_hold(np); return (np); } did_t * did_link_get(did_t *dp) { assert(dp != NULL); return (dp->dp_link); } did_t * did_chain_get(did_t *dp) { assert(dp != NULL); return (dp->dp_chain); } void did_link_set(topo_mod_t *mod, tnode_t *head, did_t *tail) { did_t *hd, *pd; assert(head != NULL); pd = hd = did_find(mod, topo_node_getspecific(head)); assert(hd != NULL); while ((hd = did_link_get(hd)) != NULL) pd = hd; pd->dp_link = tail; tail->dp_link = NULL; } void did_did_link_set(did_t *from, did_t *to) { assert(from != NULL && to != NULL); from->dp_link = to; } void did_did_chain_set(did_t *from, did_t *to) { assert(from != NULL && to != NULL); from->dp_chain = to; } void did_destroy(did_t *dp) { assert(dp != NULL); /* * did_destroy() is called only from did_hash_destroy() when * all references to the did_t have been released. We can * safely destroy the did_t. If at some later time, more * fine-grained reference count control is desired, this * code will need to change */ if (dp->dp_devtype != NULL) topo_mod_strfree(dp->dp_mod, dp->dp_devtype); if (dp->dp_physlot_name != NULL) topo_mod_strfree(dp->dp_mod, dp->dp_physlot_name); if (dp->dp_slot_label != NULL) topo_mod_strfree(dp->dp_mod, dp->dp_slot_label); slotnm_destroy(dp->dp_slotnames); topo_mod_free(dp->dp_mod, dp, sizeof (did_t)); } void did_hold(did_t *dp) { assert(dp != NULL); dp->dp_refcnt++; } void did_rele(did_t *dp) { assert(dp != NULL); assert(dp->dp_refcnt > 0); dp->dp_refcnt--; } di_node_t did_dinode(did_t *dp) { assert(dp != NULL); assert(dp->dp_src != NULL); return (dp->dp_src); } topo_mod_t * did_mod(did_t *dp) { assert(dp != NULL); return (dp->dp_mod); } void did_markrc(did_t *dp) { assert(dp != NULL); dp->dp_excap |= PCIE_PCIECAP_DEV_TYPE_ROOT; } void did_BDF(did_t *dp, int *bus, int *dev, int *fn) { assert(dp != NULL); if (bus != NULL) *bus = dp->dp_bus; if (dev != NULL) *dev = dp->dp_dev; if (fn != NULL) *fn = dp->dp_fn; } int did_board(did_t *did) { assert(did != NULL); return (did->dp_board); } int did_bridge(did_t *did) { assert(did != NULL); return (did->dp_bridge); } int did_rc(did_t *did) { assert(did != NULL); return (did->dp_rc); } int did_excap(did_t *dp) { assert(dp != NULL); return ((int)dp->dp_excap); } void did_excap_set(did_t *dp, int type) { dp->dp_excap = type; } int did_bdf(did_t *dp) { assert(dp != NULL); return ((int)dp->dp_bdf); } const char * did_physlot_name(did_t *dp, int dev) { slotnm_t *slot; assert(dp != NULL); /* * For pciex, name will be in dp_physlot_name */ if (dp->dp_physlot_name != NULL) return (dp->dp_physlot_name); /* * For pci, name will be in dp_slotnames */ for (slot = dp->dp_slotnames; slot != NULL; slot = slot->snm_next) if (slot->snm_dev == dev) break; if (slot != NULL) return (slot->snm_name); return (NULL); } char * did_slot_label_get(did_t *did) { assert(did != NULL); return (did->dp_slot_label); } void did_slot_label_set(did_t *did, char *l) { assert(did != NULL); did->dp_slot_label = l; } did_t * did_find(topo_mod_t *mp, di_node_t dn) { return (did_hash_lookup(mp, dn)); } int pci_BDF_get(topo_mod_t *mp, di_node_t dn, int *bus, int *dev, int *fn) { did_t *dp; if ((dp = did_find(mp, dn)) == NULL) return (-1); *bus = dp->dp_bus; *dev = dp->dp_dev; *fn = dp->dp_fn; did_rele(dp); return (0); } int pci_classcode_get(topo_mod_t *mp, di_node_t dn, uint_t *class, uint_t *sub) { did_t *dp; if ((dp = did_find(mp, dn)) == NULL) return (-1); if (dp->dp_class < 0) { did_rele(dp); return (-1); } *class = dp->dp_class; *sub = dp->dp_subclass; did_rele(dp); return (0); } char * pci_devtype_get(topo_mod_t *mp, di_node_t dn) { did_t *dp; if ((dp = did_find(mp, dn)) == NULL) return (NULL); did_rele(dp); return (dp->dp_devtype); } int pciex_cap_get(topo_mod_t *mp, di_node_t dn) { did_t *dp; if ((dp = did_find(mp, dn)) == NULL) return (-1); did_rele(dp); return (dp->dp_excap); } void did_setspecific(topo_mod_t *mp, void *data) { did_t *hbdid; hbdid = (did_t *)data; topo_mod_setspecific(mp, hbdid->dp_hash); } void did_settnode(did_t *pd, tnode_t *tn) { assert(tn != NULL); pd->dp_tnode = tn; } tnode_t * did_gettnode(did_t *pd) { return (pd->dp_tnode); }