#include <fcntl.h>
#include <stdbool.h>
#include <string.h>
#include <strings.h>
#include <stropts.h>
#include <unistd.h>
#include <sys/debug.h>
#include <sys/types.h>
#include <sys/pci.h>
#include <sys/pcie.h>
#include <sys/stat.h>
#include <sys/pci_tools.h>
#include "topo_pcie_impl.h"
static bool
read_cfgspace(topo_mod_t *mod, int bus, int dev, int func,
int fd, uint32_t off, uint8_t len, void *buf)
{
pcitool_reg_t pci_reg;
bool ret = true;
bzero(&pci_reg, sizeof (pci_reg));
pci_reg.user_version = PCITOOL_VERSION;
pci_reg.bus_no = bus;
pci_reg.dev_no = dev;
pci_reg.func_no = func;
pci_reg.barnum = 0;
pci_reg.offset = off;
pci_reg.acc_attr = PCITOOL_ACC_ATTR_ENDN_LTL;
switch (len) {
case 1:
pci_reg.acc_attr += PCITOOL_ACC_ATTR_SIZE_1;
break;
case 2:
pci_reg.acc_attr += PCITOOL_ACC_ATTR_SIZE_2;
break;
case 4:
pci_reg.acc_attr += PCITOOL_ACC_ATTR_SIZE_4;
break;
case 8:
pci_reg.acc_attr += PCITOOL_ACC_ATTR_SIZE_8;
break;
default:
abort();
}
if (ioctl(fd, PCITOOL_DEVICE_GET_REG, &pci_reg) != 0) {
topo_mod_dprintf(mod, "ioctl(GET_REG) failed: %s",
strerror(errno));
return (false);
}
switch (len) {
case 1:
*(uint8_t *)buf = (uint8_t)pci_reg.data;
ret = (uint8_t)pci_reg.data != PCI_EINVAL8;
break;
case 2:
*(uint16_t *)buf = (uint16_t)pci_reg.data;
ret = (uint16_t)pci_reg.data != PCI_EINVAL16;
break;
case 4:
*(uint32_t *)buf = (uint32_t)pci_reg.data;
ret = (uint32_t)pci_reg.data != PCI_EINVAL32;
break;
case 8:
*(uint64_t *)buf = (uint64_t)pci_reg.data;
ret = (uint64_t)pci_reg.data != PCI_EINVAL64;
break;
}
return (ret);
}
static int
open_nexus(topo_mod_t *mod, const char *nexus_path)
{
char nexus_reg[PATH_MAX];
int fd;
if (nexus_path == NULL) {
topo_mod_dprintf(mod, "no nexus path supplied");
return (-1);
}
if (snprintf(nexus_reg, sizeof (nexus_reg), "/devices%s:reg",
nexus_path) >= sizeof (nexus_reg)) {
topo_mod_dprintf(mod,
"failed to construct nexus reg path; overflow");
return (-1);
}
if ((fd = open(nexus_reg, O_RDONLY)) < 0) {
topo_mod_dprintf(mod, "failed to open %s: %s", nexus_reg,
strerror(errno));
return (-1);
}
return (fd);
}
topo_pcie_link_status_t
topo_pcie_link_status(topo_mod_t *mod, const char *nexus_path,
int bus, int dev, int func)
{
int fd;
uint8_t hdr, off;
uint16_t status;
uint32_t cap;
uint_t ncaps;
topo_pcie_link_status_t ret = PCI_LINK_UNKNOWN;
if ((fd = open_nexus(mod, nexus_path)) == -1)
return (ret);
if (!read_cfgspace(mod, bus, dev, func, fd, PCI_CONF_STAT, 2,
&status)) {
topo_mod_dprintf(mod, "failed to read status register");
goto out;
}
if ((status & PCI_STAT_CAP) == 0) {
topo_mod_dprintf(mod, "capabilities not supported");
goto out;
}
if (!read_cfgspace(mod, bus, dev, func, fd, PCI_CONF_HEADER, 1,
&hdr)) {
topo_mod_dprintf(mod, "failed to read header type");
goto out;
}
switch (hdr & PCI_HEADER_TYPE_M) {
case PCI_HEADER_ZERO:
cap = PCI_CONF_CAP_PTR;
break;
case PCI_HEADER_PPB:
cap = PCI_BCNF_CAP_PTR;
break;
default:
topo_mod_dprintf(mod, "unhandled PCI header type %x", hdr);
goto out;
}
if (!read_cfgspace(mod, bus, dev, func, fd, cap, 1, &off)) {
topo_mod_dprintf(mod, "failed to read capabilities pointer");
goto out;
}
ncaps = 0;
while (off != 0 && off != PCI_EINVAL8) {
uint8_t id, nxt;
off &= PCI_CAP_PTR_MASK;
if (!read_cfgspace(mod, bus, dev, func, fd,
off + PCI_CAP_ID, 1, &id)) {
topo_mod_dprintf(mod, "failed to read capability ID");
break;
}
if (id == PCI_CAP_ID_PCI_E) {
uint16_t pciecap, status, pciever;
uint32_t linkcap;
topo_mod_dprintf(mod, "Found PCIe capability at %x",
off);
if (!read_cfgspace(mod, bus, dev, func, fd,
off + PCIE_PCIECAP, 2, &pciecap)) {
topo_mod_dprintf(mod, "failed to read PCIe "
"capabilities register");
break;
}
pciever = pciecap & PCIE_PCIECAP_VER_MASK;
if (pciever != PCIE_PCIECAP_VER_1_0 &&
pciever != PCIE_PCIECAP_VER_2_0) {
topo_mod_dprintf(mod, "unsupported version "
"in PCIe capabilities register: 0x%x",
pciever);
break;
}
if (pciever == PCIE_PCIECAP_VER_1_0 &&
(pciecap & PCIE_PCIECAP_DEV_TYPE_MASK) ==
PCIE_PCIECAP_DEV_TYPE_RC_IEP) {
topo_mod_dprintf(mod, "RC IEP does not have "
"a link status register");
break;
}
if (!read_cfgspace(mod, bus, dev, func, fd,
off + PCIE_LINKCAP, 4, &linkcap)) {
topo_mod_dprintf(mod, "failed to read link "
"capabilities register");
break;
}
if ((linkcap & PCIE_LINKCAP_DLL_ACTIVE_REP_CAPABLE) ==
0) {
break;
}
if (!read_cfgspace(mod, bus, dev, func, fd,
off + PCIE_LINKSTS, 2, &status)) {
topo_mod_dprintf(mod,
"failed to read link status register");
break;
}
if ((status & PCIE_LINKSTS_DLL_LINK_ACTIVE) != 0)
ret = PCI_LINK_UP;
else
ret = PCI_LINK_DOWN;
break;
}
if (!read_cfgspace(mod, bus, dev, func, fd,
off + PCI_CAP_NEXT_PTR, 1, &nxt)) {
topo_mod_dprintf(mod,
"failed to read next capability pointer");
break;
}
off = nxt;
ncaps++;
if (ncaps >= PCI_CAP_MAX_PTR) {
topo_mod_dprintf(mod, "encountered more PCI "
"capabilities than fit in configuration space");
break;
}
}
out:
topo_mod_dprintf(mod, "Reporting link as %s", ret ? "UP" : "DOWN");
VERIFY0(close(fd));
return (ret);
}