did.c revision 05b58617e776d05dc8fc4d3d4966d9a05ebf1da0
/*
* 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.
*/
/*
* 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 <alloca.h>
#include <assert.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <fm/topo_mod.h>
#include <libnvpair.h>
#include <libdevinfo.h>
#include <sys/pcie.h>
#include <hostbridge.h>
#include <pcibus.h>
#include <did_props.h>
#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;
const char *cb_label;
} smbios_slot_cb_t;
static int
di_smbios_find_slot(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 *slotnum, char **slotname)
{
char *slotbuf;
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 (*slotnum == -1)
return (0);
/*
* Order of preference
* 1) take slotnum and look up in SMBIOS table
* 2) use slot-names
* 3) fabricate name based on slotnum
*/
if ((shp = topo_mod_smbios(mp)) != NULL) {
/*
* The PCI spec describes slot number 0 as reserved for
* internal PCI devices. Not all platforms respect
* this, so we have to treat slot 0 as a valid device.
* But other platforms use 0 to identify an internal
* device. We deal with this by letting SMBIOS be the
* final decision maker. If SMBIOS is supported, but
* the given slot number is not represented in the
* SMBIOS tables, then ignore the slot entirely.
*/
smbios_slot_cb_t cbdata;
cbdata.cb_slotnum = *slotnum;
cbdata.cb_label = NULL;
if (smbios_iter(shp, di_smbios_find_slot, &cbdata) <= 0)
return (0);
slotbuf = (char *)cbdata.cb_label;
topo_mod_dprintf(mp, "%s: node=%p: using smbios name\n",
__func__, src);
} else if (got_slotprop == B_TRUE) {
slotbuf = (char *)&buf[4];
topo_mod_dprintf(mp, "%s: node=%p: found %s property\n",
__func__, src, DI_SLOTPROP);
} else {
/*
* Make generic description string "SLOT <num>", allow up to
* 10 digits for number
*/
slotbuf = alloca(16);
(void) snprintf(slotbuf, 16, "SLOT %d", *slotnum);
topo_mod_dprintf(mp, "%s: node=%p: using generic slot name\n",
__func__, src);
}
if ((*slotname = topo_mod_strdup(mp, slotbuf)) == NULL)
return (-1);
topo_mod_dprintf(mp, "%s: node=%p: slotname=%s\n",
__func__, src, *slotname);
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, &reg) < 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_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);
}