#include <fcntl.h>
#include <libdevinfo.h>
#include <priv.h>
#include <stdbool.h>
#include <strings.h>
#include <unistd.h>
#include <sys/debug.h>
#include <sys/pci.h>
#include <sys/pcie.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/devfm.h>
#include <sys/fm/protocol.h>
#include <fm/topo_mod.h>
#include <fm/topo_hc.h>
#include <fm/topo_list.h>
#include <fm/topo_method.h>
#include <fm/fmd_agent.h>
#include <io/amdzen/amdzen_topo.h>
#include "topo_pcie_impl.h"
typedef struct pcie_enum_state {
di_node_t pes_devinfo;
uint8_t pes_nchip;
nvlist_t *pes_cpupcidata;
topo_list_t pes_rootnexus;
} pcie_enum_state_t;
typedef struct cbdata {
pcie_enum_state_t *cbd_state;
topo_mod_t *cbd_mod;
bool cbd_fatal;
} cbdata_t;
typedef struct pcie_enum pcie_enum_t;
typedef int (*pcie_enum_f)(topo_mod_t *, pcie_t *, const pcie_enum_t *,
tnode_t *, tnode_t *, topo_instance_t, topo_instance_t);
typedef enum {
PCIE_ENUM_F_MULTI_RANGE = 1 << 0,
} pcie_enum_flags_t;
struct pcie_enum {
const char *pe_name;
pcie_enum_flags_t pe_flags;
pcie_enum_f pe_range_enum;
pcie_enum_f pe_post_enum;
};
static tnode_t *pcie_topo_add_bridge(topo_mod_t *, pcie_t *, tnode_t *,
pcie_node_t *);
static void
pcie_node_print(topo_mod_t *mod, topo_list_t *list, uint_t indent)
{
pcie_node_t *node;
for (node = topo_list_next(list); node != NULL;
node = topo_list_next(node)) {
char suffix[0x10] = "";
if (node->pn_type == PCIE_NODE_ROOTNEXUS) {
(void) snprintf(suffix, sizeof (suffix), " cpu%" PRIu64,
node->pn_cpu);
}
topo_mod_dprintf(mod, "%*s[%x/%x/%x] %s [%s%d] {%x/%x/%x} %s%s",
indent, "",
node->pn_bus, node->pn_dev, node->pn_func,
pcie_type_name(node->pn_type),
node->pn_drvname == NULL ? "" : node->pn_drvname,
node->pn_drvinst,
node->pn_class, node->pn_subclass, node->pn_intf,
node->pn_path, suffix);
pcie_node_print(mod, &node->pn_children, indent + 4);
}
}
static void
pcie_node_free(topo_mod_t *mod, pcie_node_t *node)
{
topo_mod_strfree(mod, node->pn_drvname);
if (node->pn_path != NULL)
di_devfs_path_free((char *)node->pn_path);
topo_mod_free(mod, node, sizeof (*node));
}
static void
pcie_free_tree(topo_mod_t *mod, pcie_node_t *node)
{
pcie_node_t *child, *nchild;
for (child = topo_list_next(&node->pn_children); child != NULL;
child = nchild) {
nchild = topo_list_next(child);
pcie_free_tree(mod, child);
}
pcie_node_free(mod, node);
}
static pcie_node_t *
pcie_node_create(topo_mod_t *mod, di_node_t did, pcie_node_type_t type,
pcie_node_t *parent)
{
char *drvname;
pcie_node_t *node;
char *path;
int inst;
drvname = di_driver_name(did);
if (drvname != NULL) {
drvname = topo_mod_strdup(mod, drvname);
if (drvname == NULL) {
(void) topo_mod_seterrno(mod, EMOD_NOMEM);
topo_mod_dprintf(mod,
"failed to duplicate driver name");
return (NULL);
}
}
inst = di_instance(did);
path = di_devfs_path(did);
if (path == NULL) {
topo_mod_dprintf(mod, "failed to get /devices path for "
"%s%d: %s", drvname == NULL ? "<unknown>" : drvname, inst,
strerror(errno));
(void) topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM);
topo_mod_strfree(mod, drvname);
return (NULL);
}
if ((node = topo_mod_zalloc(mod, sizeof (*node))) == NULL) {
(void) topo_mod_seterrno(mod, EMOD_NOMEM);
topo_mod_strfree(mod, drvname);
di_devfs_path_free(path);
return (NULL);
}
node->pn_did = did;
node->pn_type = type;
node->pn_path = path;
node->pn_drvname = drvname;
node->pn_drvinst = inst;
if (parent != NULL) {
topo_list_append(&parent->pn_children, node);
node->pn_parent = parent;
}
return (node);
}
static void
pcie_tn_data_free(topo_mod_t *mod, pcie_tn_data_t *data)
{
if (data == NULL)
return;
if (data->ptd_nexus_path != NULL)
topo_mod_strfree(mod, data->ptd_nexus_path);
topo_mod_free(mod, data, sizeof (*data));
}
static pcie_tn_data_t *
pcie_tn_data_create(topo_mod_t *mod, const pcie_node_t *node)
{
pcie_tn_data_t *data;
const char *nexus_path;
if ((data = topo_mod_zalloc(mod, sizeof (*data))) == NULL) {
(void) topo_mod_seterrno(mod, EMOD_NOMEM);
return (NULL);
}
data->ptd_bus = node->pn_bus;
data->ptd_dev = node->pn_dev;
data->ptd_func = node->pn_func;
nexus_path = pcie_node_nexus_path(node);
if (nexus_path != NULL) {
data->ptd_nexus_path = topo_mod_strdup(mod, nexus_path);
if (data->ptd_nexus_path == NULL) {
pcie_tn_data_free(mod, data);
(void) topo_mod_seterrno(mod, EMOD_NOMEM);
return (NULL);
}
}
return (data);
}
static tnode_t *
pcie_topo_node_create(topo_mod_t *mod, pcie_t *pcie, tnode_t *parent,
pcie_node_t *node, const char *name, topo_instance_t inst)
{
nvlist_t *fmri, *auth;
pcie_tn_data_t *data = NULL;
tnode_t *tn, *dtn;
topo_mod_dprintf(mod, "topo node create %s=%" PRIu64 " (%s)",
name, inst, node == NULL ? "" : node->pn_path);
auth = mod_pcie_platform_auth(mod, pcie, parent);
if (auth == NULL) {
topo_mod_dprintf(mod, "could not get auth from parent: %s",
topo_mod_errmsg(mod));
return (NULL);
}
fmri = topo_mod_pciefmri(mod, parent, FM_PCIE_SCHEME_VERSION,
name, inst, auth);
if (fmri == NULL) {
topo_mod_dprintf(mod, "fmri creation failed: %s",
topo_mod_errmsg(mod));
nvlist_free(auth);
return (NULL);
}
tn = topo_node_bind(mod, parent, name, inst, fmri);
if (tn == NULL) {
topo_mod_dprintf(mod, "bind failed: %s", topo_mod_errmsg(mod));
goto error;
}
if (node != NULL) {
data = pcie_tn_data_create(mod, node);
if (data == NULL) {
topo_mod_dprintf(mod,
"failed to allocate per-tnode data: %s",
topo_mod_errmsg(mod));
goto error;
}
topo_node_setspecific(tn, data);
if (!topo_pcie_set_pci_props(mod, pcie, node, tn))
goto error;
node->pn_inst = inst;
}
topo_pgroup_hcset(tn, auth);
dtn = mod_pcie_platform_topo_node_decorate(mod, pcie, node, tn);
if (dtn == NULL)
goto error;
nvlist_free(auth);
nvlist_free(fmri);
return (dtn);
error:
nvlist_free(auth);
nvlist_free(fmri);
if (tn != NULL) {
topo_node_setspecific(tn, NULL);
topo_node_unbind(tn);
}
pcie_tn_data_free(mod, data);
return (NULL);
}
static void
pcie_topo_node_free(topo_mod_t *mod, tnode_t *tn)
{
pcie_tn_data_free(mod, topo_node_getspecific(tn));
topo_node_setspecific(tn, NULL);
}
static void
pcie_socket_map(topo_mod_t *mod, pcie_enum_state_t *state, pcie_node_t *node)
{
int32_t *busrange;
nvlist_t **dfs;
uint_t ndfs;
int nval;
int err;
node->pn_cpu = 0;
if (state->pes_cpupcidata == NULL)
return;
nval = di_prop_lookup_ints(DDI_DEV_T_ANY, node->pn_did,
DI_BUSRANGE, &busrange);
if (nval != 2) {
topo_mod_dprintf(mod, "failed to retrieve nexus bus range");
return;
}
err = nvlist_lookup_nvlist_array(state->pes_cpupcidata,
FM_PCI_DATA_DFS, &dfs, &ndfs);
if (err != 0 || dfs == NULL) {
topo_mod_dprintf(mod, "CPU PCI data does not contain %s",
FM_PCI_DATA_DFS);
return;
}
for (uint_t i = 0; i < ndfs; i++) {
uint32_t nb_busno;
int32_t sockid;
err = nvlist_lookup_int32(dfs[i], FM_PCI_DATA_CHIP_ID,
&sockid);
if (err != 0) {
topo_mod_dprintf(mod, "DF[%d] is missing key '%s'",
i, FM_PCI_DATA_CHIP_ID);
continue;
}
err = nvlist_lookup_uint32(dfs[i], FM_PCI_DATA_NB_BUSNO,
&nb_busno);
if (err != 0) {
topo_mod_dprintf(mod, "DF[%d] is missing key '%s'",
i, FM_PCI_DATA_NB_BUSNO);
continue;
}
if (busrange[0] <= nb_busno) {
node->pn_cpu = sockid;
topo_mod_dprintf(mod,
"Mapped root complex %s to socket %" PRIu64,
node->pn_path, node->pn_cpu);
break;
}
}
}
static bool
pcie_physcpu_enum(topo_mod_t *mod, pcie_enum_state_t *state)
{
fmd_agent_hdl_t *hdl;
if ((hdl = fmd_agent_open(FMD_AGENT_VERSION)) == NULL) {
topo_mod_dprintf(mod, "failed to open fmd agent interface: %s",
strerror(errno));
return (false);
}
if (fmd_agent_chip_count(hdl, &state->pes_nchip) != 0) {
topo_mod_dprintf(mod,
"failed to retrieve physical CPU count: %s",
fmd_agent_errmsg(hdl));
fmd_agent_close(hdl);
return (false);
}
if (fmd_agent_physcpu_pci(hdl, &state->pes_cpupcidata) != 0) {
topo_mod_dprintf(mod,
"failed to retrieve physical CPU PCI data: %s",
fmd_agent_errmsg(hdl));
}
fmd_agent_close(hdl);
return (true);
}
static int
pcie_rootnexus_enum_cb(di_node_t did, void *arg)
{
cbdata_t *cbd = arg;
topo_mod_t *mod = cbd->cbd_mod;
pcie_enum_state_t *state = cbd->cbd_state;
char *compat;
bool found = false;
int *ents, nents;
nents = di_prop_lookup_strings(DDI_DEV_T_ANY, did, DI_COMPATPROP,
&compat);
if (nents > 0) {
for (uint_t i = 0; i < nents; i++) {
if (strcmp(PCIE_ROOT_NEXUS, compat) == 0) {
found = true;
break;
}
compat += strlen(compat) + 1;
}
}
if (!found) {
char *drv = di_driver_name(did);
if (drv != NULL && strcmp(drv, "pci") == 0 &&
di_prop_lookup_ints(DDI_DEV_T_ANY, did, DI_BUSRANGE,
&ents) == 2) {
found = true;
}
}
if (!found)
return (DI_WALK_CONTINUE);
pcie_node_t *node = pcie_node_create(mod, did,
PCIE_NODE_ROOTNEXUS, NULL);
if (node == NULL) {
topo_mod_dprintf(mod,
"failed to create root nexus pcie node: %s",
topo_mod_errmsg(mod));
cbd->cbd_fatal = true;
return (DI_WALK_TERMINATE);
}
pcie_socket_map(mod, state, node);
topo_list_append(&state->pes_rootnexus, node);
return (DI_WALK_PRUNECHILD);
}
static bool
pcie_rootnexus_enum(topo_mod_t *mod, pcie_enum_state_t *state)
{
cbdata_t cbd = {
.cbd_state = state,
.cbd_mod = mod,
.cbd_fatal = false
};
(void) di_walk_node(state->pes_devinfo, DI_WALK_CLDFIRST,
&cbd, pcie_rootnexus_enum_cb);
return (!cbd.cbd_fatal);
}
static pcie_node_t *
pcie_process_node(topo_mod_t *mod, pcie_node_t *parent, di_node_t did)
{
int nents;
int *ents;
int class, subclass, interface, bus, dev, func;
uint16_t pcie_type;
bool is_pcie, is_pcibr;
pcie_node_t *node = NULL;
pcie_node_type_t type;
nents = di_prop_lookup_ints(DDI_DEV_T_ANY, did, DI_CLASSPROP, &ents);
if (nents != 1)
return (NULL);
class = GETCLASS(ents[0]);
subclass = GETSUBCLASS(ents[0]);
interface = GETINTF(ents[0]);
nents = di_prop_lookup_ints(DDI_DEV_T_ANY, did, DI_REGPROP, &ents);
if (nents > 0) {
bus = PCI_REG_BUS_G(ents[0]);
dev = PCI_REG_DEV_G(ents[0]);
func = PCI_REG_FUNC_G(ents[0]);
} else {
bus = dev = func = -1;
}
is_pcibr = (class == PCI_CLASS_BRIDGE && subclass == PCI_BRIDGE_PCI);
nents = di_prop_lookup_ints(DDI_DEV_T_ANY, did, DI_PCIETYPPROP, &ents);
if (nents == 1) {
is_pcie = true;
pcie_type = (uint16_t)(ents[0] << PCIE_PCIECAP_DEV_TYPE_SHIFT);
} else {
is_pcie = false;
pcie_type = PCIE_PCIECAP_DEV_TYPE_PCI_DEV;
}
type = PCIE_NODE_PCI_DEV;
if (!is_pcie) {
type = is_pcibr ? PCIE_NODE_PCIE_PCI : PCIE_NODE_PCI_DEV;
} else if (is_pcibr) {
switch (pcie_type) {
case PCIE_PCIECAP_DEV_TYPE_ROOT:
type = PCIE_NODE_ROOTPORT;
break;
case PCIE_PCIECAP_DEV_TYPE_UP:
type = PCIE_NODE_SWITCH_UP;
break;
case PCIE_PCIECAP_DEV_TYPE_DOWN:
type = PCIE_NODE_SWITCH_DOWN;
break;
case PCIE_PCIECAP_DEV_TYPE_PCIE2PCI:
type = PCIE_NODE_PCIE_PCI;
break;
case PCIE_PCIECAP_DEV_TYPE_PCI2PCIE:
type = PCIE_NODE_PCI_PCIE;
break;
default:
topo_mod_dprintf(mod,
"pcie_type unhandled bridge type 0x%x", pcie_type);
abort();
}
} else {
switch (pcie_type) {
case PCIE_PCIECAP_DEV_TYPE_ROOT:
type = PCIE_NODE_ROOTPORT;
break;
case PCIE_PCIECAP_DEV_TYPE_PCIE_DEV:
type = PCIE_NODE_PCIE_DEV;
break;
case PCIE_PCIECAP_DEV_TYPE_PCI_DEV:
type = PCIE_NODE_PCI_DEV;
break;
case PCIE_PCIECAP_DEV_TYPE_RC_IEP:
case PCIE_PCIECAP_DEV_TYPE_RC_EC:
return (NULL);
default:
topo_mod_dprintf(mod, "pcie_type unhandled type 0x%x",
pcie_type);
abort();
}
}
node = pcie_node_create(mod, did, type, parent);
node->pn_class = class;
node->pn_subclass = subclass;
node->pn_intf = interface;
node->pn_bus = bus;
node->pn_dev = dev;
node->pn_func = func;
node->pn_is_pcie = is_pcie;
return (node);
}
static void
pcie_enum_children(topo_mod_t *mod, pcie_node_t *parent)
{
di_node_t did;
for (did = di_child_node(parent->pn_did); did != DI_NODE_NIL;
did = di_sibling_node(did)) {
pcie_node_t *node;
node = pcie_process_node(mod, parent, did);
if (node != NULL)
pcie_enum_children(mod, node);
}
}
static void
pcie_rootnexus_enum_children(topo_mod_t *mod, pcie_enum_state_t *state)
{
pcie_node_t *nexus;
for (nexus = topo_list_next(&state->pes_rootnexus); nexus != NULL;
nexus = topo_list_next(nexus)) {
topo_mod_dprintf(mod, "enumerate nexus %s", nexus->pn_path);
pcie_enum_children(mod, nexus);
}
}
static void
pcie_enum_state_init(pcie_enum_state_t *state)
{
bzero(state, sizeof (*state));
state->pes_devinfo = DI_NODE_NIL;
}
static void
pcie_enum_state_fini(topo_mod_t *mod, pcie_enum_state_t *state)
{
pcie_node_t *nexus, *nnexus;
for (nexus = topo_list_next(&state->pes_rootnexus); nexus != NULL;
nexus = nnexus) {
nnexus = topo_list_next(nexus);
topo_list_delete(&state->pes_rootnexus, nexus);
pcie_free_tree(mod, nexus);
}
nvlist_free(state->pes_cpupcidata);
state->pes_cpupcidata = NULL;
state->pes_devinfo = DI_NODE_NIL;
}
static bool
pcie_gather(topo_mod_t *mod, pcie_enum_state_t *state)
{
if ((state->pes_devinfo = topo_mod_devinfo(mod)) == DI_NODE_NIL) {
topo_mod_dprintf(mod, "No devinfo node from framework");
return (false);
}
if (!pcie_physcpu_enum(mod, state))
return (false);
if (!pcie_rootnexus_enum(mod, state))
return (false);
pcie_rootnexus_enum_children(mod, state);
pcie_node_print(mod, &state->pes_rootnexus, 0);
return (true);
}
static tnode_t *
pcie_topo_add_link(topo_mod_t *mod, pcie_t *pcie, tnode_t *pnode,
pcie_node_t *node)
{
tnode_t *tn;
if (!pcie_topo_range_create(mod, pnode, "link", 0, 0))
return (NULL);
tn = pcie_topo_node_create(mod, pcie, pnode, NULL, "link", 0);
if (tn == NULL)
return (NULL);
(void) topo_pcie_set_link_props(mod, pcie, node, tn);
return (tn);
}
static tnode_t *
pcie_topo_add_bridge(topo_mod_t *mod, pcie_t *pcie, tnode_t *pnode,
pcie_node_t *node)
{
tnode_t *tn = pnode;
if (!pcie_topo_range_create(mod, tn, "port", 0, 0))
return (NULL);
tn = pcie_topo_node_create(mod, pcie, tn, NULL, "port", 0);
if (tn == NULL)
return (NULL);
(void) topo_pcie_set_port_props(mod, pcie, node, tn,
TOPO_PORT_DOWNSTREAM);
tn = pcie_topo_add_link(mod, pcie, tn, node);
return (tn);
}
static tnode_t *
pcie_topo_add_device(topo_mod_t *mod, pcie_t *pcie, tnode_t *pnode,
pcie_node_t *node, topo_instance_t *inst)
{
topo_instance_t fninst;
tnode_t *dev, *func;
topo_mod_dprintf(mod, "add_device: %s (%s)",
node->pn_path, pcie_type_name(node->pn_type));
if (node->pn_parent->pn_type == PCIE_NODE_ROOTNEXUS) {
dev = pnode;
fninst = *inst;
(*inst)++;
} else {
pcie_node_t *parent;
size_t idx;
if (node->pn_type == PCIE_NODE_SWITCH_DOWN) {
VERIFY(node->pn_parent->pn_type == PCIE_NODE_SWITCH_UP);
parent = node->pn_parent->pn_parent;
idx = node->pn_parent->pn_dev;
} else {
parent = node->pn_parent;
idx = node->pn_dev;
}
VERIFY3U(idx, <=, sizeof (parent->pn_devices));
dev = parent->pn_devices[idx];
if (dev == NULL) {
topo_instance_t max;
tnode_t *port;
max = topo_list_size(&parent->pn_children);
if (node->pn_type == PCIE_NODE_SWITCH_UP)
max += topo_list_size(&node->pn_children);
if (!pcie_topo_range_create(mod, pnode, "port", 0, max))
return (NULL);
port = pcie_topo_node_create(mod, pcie, pnode, NULL,
"port", *inst);
if (port == NULL)
return (NULL);
(void) topo_pcie_set_port_props(mod, pcie, node, port,
TOPO_PORT_UPSTREAM);
if (!pcie_topo_range_create(mod, port, "device", 0,
max)) {
return (NULL);
}
dev = pcie_topo_node_create(mod, pcie, port, NULL,
"device", *inst);
if (dev == NULL)
return (NULL);
(*inst)++;
parent->pn_devices[idx] = dev;
parent->pn_devfunc[idx] = 0;
if (!pcie_topo_range_create(mod, dev, "function", 0,
max)) {
return (NULL);
}
}
fninst = parent->pn_devfunc[idx];
parent->pn_devfunc[idx]++;
}
func = pcie_topo_node_create(mod, pcie, dev, node, "function", fninst);
return (func);
}
static bool
pcie_topo_process_functions(topo_mod_t *mod, pcie_t *pcie, tnode_t *pnode,
pcie_node_t *node, topo_instance_t *fninst)
{
pcie_node_t *func;
topo_instance_t i;
size_t children = topo_list_size(&node->pn_children);
topo_mod_dprintf(mod,
"-> functions for %s (%s) [%"PRIu64"] children %zd",
node->pn_path, pcie_type_name(node->pn_type), *fninst, children);
for (func = topo_list_next(&node->pn_children); func != NULL;
func = topo_list_next(func)) {
tnode_t *fn;
topo_mod_dprintf(mod, " fn %s (%s)",
func->pn_path, pcie_type_name(func->pn_type));
switch (func->pn_type) {
case PCIE_NODE_ROOTPORT:
fn = pcie_topo_node_create(mod, pcie, pnode, func,
"function", *fninst);
(*fninst)++;
fn = pcie_topo_add_bridge(mod, pcie, fn, func);
if (fn == NULL)
return (false);
if (topo_list_size(&func->pn_children) == 0)
break;
i = 0;
if (!pcie_topo_process_functions(mod, pcie, fn,
func, &i)) {
return (false);
}
break;
case PCIE_NODE_PCIE_PCI:
case PCIE_NODE_PCI_PCIE:
fn = pcie_topo_add_device(mod, pcie, pnode, func,
fninst);
if (fn == NULL)
return (false);
fn = pcie_topo_add_bridge(mod, pcie, fn, func);
if (fn == NULL)
return (false);
i = 0;
if (!pcie_topo_process_functions(mod, pcie, fn,
func, &i)) {
return (false);
}
break;
case PCIE_NODE_SWITCH_UP:
fn = pcie_topo_add_device(mod, pcie, pnode, func,
fninst);
if (fn == NULL)
return (false);
if (!pcie_topo_process_functions(mod, pcie, pnode,
func, fninst)) {
return (false);
}
break;
case PCIE_NODE_SWITCH_DOWN:
fn = pcie_topo_add_device(mod, pcie, pnode, func,
fninst);
if (fn == NULL)
return (false);
if (topo_list_size(&func->pn_children) == 0)
break;
fn = pcie_topo_add_bridge(mod, pcie, fn, func);
if (fn == NULL)
return (false);
i = 0;
if (!pcie_topo_process_functions(mod, pcie, fn,
func, &i)) {
return (false);
}
break;
case PCIE_NODE_PCI_DEV:
case PCIE_NODE_PCIE_DEV:
fn = pcie_topo_add_device(mod, pcie, pnode, func,
fninst);
if (fn == NULL)
return (false);
break;
default:
topo_mod_dprintf(mod, "unhandled device type %u (%s)",
func->pn_type, pcie_type_name(func->pn_type));
abort();
}
}
return (true);
}
static int
pcie_topo_enum_cpu(topo_mod_t *mod, pcie_t *pcie, const pcie_enum_t *pe,
tnode_t *pnode, tnode_t *tnode, topo_instance_t min, topo_instance_t max)
{
pcie_enum_state_t state;
int ret = 0;
pcie_enum_state_init(&state);
if (!pcie_physcpu_enum(mod, &state)) {
pcie_enum_state_fini(mod, &state);
return (-1);
}
topo_mod_dprintf(mod, "physical CPU count: %u", state.pes_nchip);
for (uint_t chipid = 0; chipid < state.pes_nchip; chipid++) {
tnode_t *cpu;
if (chipid < min || chipid > max) {
topo_mod_dprintf(mod, "cpu chipid %" PRId32 " "
"out of range [%" PRIu64 ", %" PRIu64 "]",
chipid, min, max);
continue;
}
topo_mod_dprintf(mod, "creating CPU chip %" PRId32, chipid);
cpu = pcie_topo_node_create(mod, pcie, pnode, NULL,
pe->pe_name, chipid);
if (cpu == NULL) {
ret = -1;
break;
}
}
pcie_enum_state_fini(mod, &state);
return (ret);
}
static int
pcie_topo_enum_root_complex(topo_mod_t *mod, pcie_t *pcie,
const pcie_enum_t *pe, tnode_t *pnode, tnode_t *tnode,
topo_instance_t min, topo_instance_t max)
{
pcie_enum_state_t state;
topo_instance_t rcinst, cpuinst;
pcie_node_t *rc;
int ret = 0;
pcie_enum_state_init(&state);
if (!pcie_gather(mod, &state)) {
pcie_enum_state_fini(mod, &state);
return (-1);
}
cpuinst = topo_node_instance(pnode);
for (rcinst = min, rc = topo_list_next(&state.pes_rootnexus);
rc != NULL; rc = topo_list_next(rc)) {
tnode_t *rcnode;
topo_instance_t fninst = 0;
if (rc->pn_cpu != cpuinst)
continue;
if (rcinst > max) {
ret = -1;
break;
}
rcnode = pcie_topo_node_create(mod, pcie, pnode, rc,
pe->pe_name, rcinst);
if (rcnode == NULL) {
ret = -1;
break;
}
if (!pcie_topo_range_create(mod, rcnode, "function", 0,
topo_list_size(&rc->pn_children))) {
ret = -1;
break;
}
if (!pcie_topo_process_functions(mod, pcie, rcnode, rc,
&fninst)) {
ret = -1;
break;
}
rcinst++;
}
pcie_enum_state_fini(mod, &state);
return (ret);
}
const pcie_enum_t pcie_enum_common[] = {
{
.pe_name = CPU,
.pe_range_enum = pcie_topo_enum_cpu,
.pe_flags = PCIE_ENUM_F_MULTI_RANGE,
},
{
.pe_name = "root-complex",
.pe_range_enum = pcie_topo_enum_root_complex,
.pe_flags = PCIE_ENUM_F_MULTI_RANGE,
},
};
static int
pcie_enum(topo_mod_t *mod, tnode_t *pnode, const char *name,
topo_instance_t min, topo_instance_t max, void *modarg, void *data)
{
pcie_t *pcie;
const pcie_enum_t *pe;
const char *pname;
tnode_t *tn = NULL;
bool post, range;
topo_mod_dprintf(mod, "enum: %s [%" PRIu64 ", %" PRIu64 "] on "
"%s%" PRIu64, name, min, max,
topo_node_name(pnode), topo_node_instance(pnode));
if (!priv_ineffect(PRIV_SYS_CONFIG)) {
topo_mod_dprintf(mod, "privilege %s is not in effect",
PRIV_SYS_CONFIG);
return (0);
}
if ((pcie = topo_mod_getspecific(mod)) == NULL)
return (-1);
pname = topo_node_name(pnode);
range = (min != max);
post = false;
if (strcmp(pname, name) == 0) {
topo_instance_t pinst = topo_node_instance(pnode);
VERIFY3U(pinst, >=, min);
VERIFY3U(pinst, <=, max);
tn = pnode;
pnode = topo_node_parent(tn);
pname = topo_node_name(pnode);
post = true;
}
topo_mod_dprintf(mod, "enum: %s for %s (parent %s=%" PRIu64 ")",
post ? "post" : "initial", name,
pname, topo_node_instance(pnode));
pe = NULL;
for (size_t i = 0; i < ARRAY_SIZE(pcie_enum_common); i++) {
if (strcmp(pcie_enum_common[i].pe_name, name) == 0) {
pe = &pcie_enum_common[i];
break;
}
}
if (pe == NULL) {
topo_mod_dprintf(mod, "enum: component %s unknown", name);
return (-1);
}
if (range && !post && (pe->pe_flags & PCIE_ENUM_F_MULTI_RANGE) == 0) {
topo_mod_dprintf(mod,
"enum: multi-instance range enumeration not supported");
return (topo_mod_seterrno(mod, EMOD_NODE_RANGE));
}
if (post) {
if (pe->pe_post_enum == NULL) {
topo_mod_dprintf(mod,
"enum: skipping post: no processing function");
return (0);
}
return (pe->pe_post_enum(mod, pcie, pe, pnode, tn, min, max));
}
if (pe->pe_range_enum == NULL) {
topo_mod_dprintf(mod,
"enum: missing initial enumeration function!");
return (-1);
}
return (pe->pe_range_enum(mod, pcie, pe, pnode, tn, min, max));
}
bool
pcie_set_platdata(pcie_t *pcie, void *val)
{
pcie->tp_privdata = val;
return (true);
}
void *
pcie_get_platdata(const pcie_t *pcie)
{
return (pcie->tp_privdata);
}
static void
pcie_free(topo_mod_t *mod, pcie_t *pcie)
{
if (pcie == NULL)
return;
if (pcie->tp_pcidb_hdl != NULL)
pcidb_close(pcie->tp_pcidb_hdl);
topo_mod_free(mod, pcie, sizeof (*pcie));
}
static pcie_t *
pcie_alloc(topo_mod_t *mod)
{
pcie_t *pcie;
if ((pcie = topo_mod_zalloc(mod, sizeof (*pcie))) == NULL) {
topo_mod_dprintf(mod,
"Could not allocate memory for pcie_t: %s",
topo_strerror(EMOD_NOMEM));
return (NULL);
}
if ((pcie->tp_pcidb_hdl = pcidb_open(PCIDB_VERSION)) == NULL) {
topo_mod_dprintf(mod, "Failed to open pcidb");
}
return (pcie);
}
static const topo_modops_t pcie_ops = {
.tmo_enum = pcie_enum,
.tmo_release = pcie_topo_node_free,
};
static topo_modinfo_t pcie_mod = {
.tmi_desc = PCIE,
.tmi_scheme = FM_FMRI_SCHEME_PCIE,
PCIE_VERSION,
&pcie_ops,
};
int
_topo_init(topo_mod_t *mod, topo_version_t version)
{
pcie_t *pcie;
if (getenv("TOPOPCIEDEBUG") != NULL)
topo_mod_setdebug(mod);
topo_mod_dprintf(mod, "initialising %s enumerator\n", PCIE);
if (version != PCIE_VERSION)
return (-1);
if ((pcie = pcie_alloc(mod)) == NULL)
return (-1);
if (topo_mod_register(mod, &pcie_mod, TOPO_VERSION) != 0) {
topo_mod_dprintf(mod, "failed to register module");
pcie_free(mod, pcie);
return (-1);
}
if (!mod_pcie_platform_init(mod, pcie)) {
topo_mod_unregister(mod);
pcie_free(mod, pcie);
return (-1);
}
topo_mod_setspecific(mod, pcie);
return (0);
}
void
_topo_fini(topo_mod_t *mod)
{
pcie_t *pcie;
pcie = topo_mod_getspecific(mod);
if (pcie != NULL) {
topo_mod_setspecific(mod, NULL);
mod_pcie_platform_fini(mod, pcie);
pcie_free(mod, pcie);
}
topo_mod_unregister(mod);
}