#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <err.h>
#include <libdevinfo.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/pci_tools.h>
#include <sys/pci.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/debug.h>
#include <upanic.h>
#include <libgen.h>
#include <stdnoreturn.h>
#include "pcieadm.h"
pcieadm_t pcieadm;
const char *pcieadm_progname;
void
pcieadm_init_privs(pcieadm_t *pcip)
{
static const char *msg = "attempted to re-initialize privileges";
if (pcip->pia_priv_init == NULL) {
upanic(msg, strlen(msg));
}
priv_intersect(pcip->pia_priv_init, pcip->pia_priv_eff);
if (setppriv(PRIV_SET, PRIV_PERMITTED, pcieadm.pia_priv_eff) != 0) {
err(EXIT_FAILURE, "failed to reduce privileges");
}
if (setppriv(PRIV_SET, PRIV_LIMIT, pcieadm.pia_priv_eff) != 0) {
err(EXIT_FAILURE, "failed to reduce privileges");
}
priv_freeset(pcip->pia_priv_init);
pcip->pia_priv_init = NULL;
}
void
pcieadm_indent(void)
{
pcieadm.pia_indent += 2;
}
void
pcieadm_deindent(void)
{
VERIFY3U(pcieadm.pia_indent, >, 0);
pcieadm.pia_indent -= 2;
}
void
pcieadm_print(const char *fmt, ...)
{
va_list ap;
if (pcieadm.pia_indent > 0) {
(void) printf("%*s", pcieadm.pia_indent, "");
}
va_start(ap, fmt);
(void) vprintf(fmt, ap);
va_end(ap);
}
void
pcieadm_ofmt_errx(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
verrx(EXIT_FAILURE, fmt, ap);
}
static boolean_t
pcieadm_di_node_is_pci(di_node_t node)
{
const char *name;
char *compat;
int nents;
name = di_node_name(node);
if (strncmp("pci", name, 3) == 0) {
return (name[3] != '\0');
}
nents = di_prop_lookup_strings(DDI_DEV_T_ANY, node, "compatible",
&compat);
if (nents <= 0) {
return (B_FALSE);
}
for (int i = 0; i < nents; i++) {
if (strncmp("pciclass,", compat, strlen("pciclass,")) == 0 ||
strncmp("pciexclass,", compat, strlen("pciexclass,")) ==
0) {
return (B_TRUE);
}
compat += strlen(compat) + 1;
}
return (B_FALSE);
}
static int
pcieadm_di_walk_cb(di_node_t node, void *arg)
{
pcieadm_di_walk_t *walk = arg;
if (!pcieadm_di_node_is_pci(node)) {
return (DI_WALK_CONTINUE);
}
return (walk->pdw_func(node, walk->pdw_arg));
}
static di_node_t
pcieadm_di_root(pcieadm_t *pcip)
{
if (pcip->pia_root == DI_NODE_NIL) {
pcip->pia_root = di_init("/", DINFOCPYALL);
if (pcip->pia_root == DI_NODE_NIL) {
err(EXIT_FAILURE, "failed to initialize devinfo tree");
}
}
return (pcip->pia_root);
}
void
pcieadm_di_walk(pcieadm_t *pcip, pcieadm_di_walk_t *arg)
{
(void) di_walk_node(pcieadm_di_root(pcip), DI_WALK_CLDFIRST, arg,
pcieadm_di_walk_cb);
}
void
pcieadm_find_nexus(pcieadm_t *pia)
{
di_node_t cur;
for (cur = di_parent_node(pia->pia_devi); cur != DI_NODE_NIL;
cur = di_parent_node(cur)) {
di_minor_t minor = DI_MINOR_NIL;
while ((minor = di_minor_next(cur, minor)) != DI_MINOR_NIL) {
if (di_minor_spectype(minor) == S_IFCHR &&
strcmp(di_minor_name(minor), "reg") == 0) {
pia->pia_nexus = cur;
return;
}
}
}
}
static int
pcieadm_find_dip_cb(di_node_t node, void *arg)
{
char *path = NULL, *driver;
char dinst[128], bdf[128], altbdf[128];
int inst, nprop, *regs;
pcieadm_t *pia = arg;
path = di_devfs_path(node);
if (path == NULL) {
err(EXIT_FAILURE, "failed to construct devfs path for node: "
"%s", di_node_name(node));
}
driver = di_driver_name(node);
inst = di_instance(node);
if (driver != NULL && inst != -1) {
(void) snprintf(dinst, sizeof (dinst), "%s%d", driver, inst);
}
nprop = di_prop_lookup_ints(DDI_DEV_T_ANY, node, "reg", ®s);
if (nprop <= 0) {
errx(EXIT_FAILURE, "failed to lookup regs array for %s",
path);
}
(void) snprintf(bdf, sizeof (bdf), "%x/%x/%x", PCI_REG_BUS_G(regs[0]),
PCI_REG_DEV_G(regs[0]), PCI_REG_FUNC_G(regs[0]));
(void) snprintf(altbdf, sizeof (altbdf), "%02x/%02x/%02x",
PCI_REG_BUS_G(regs[0]), PCI_REG_DEV_G(regs[0]),
PCI_REG_FUNC_G(regs[0]));
if (strcmp(pia->pia_devstr, path) == 0 ||
strcmp(pia->pia_devstr, bdf) == 0 ||
strcmp(pia->pia_devstr, altbdf) == 0 ||
(driver != NULL && inst != -1 &&
strcmp(pia->pia_devstr, dinst) == 0)) {
if (pia->pia_devi != DI_NODE_NIL) {
errx(EXIT_FAILURE, "device name matched two device "
"nodes: %s and %s", di_node_name(pia->pia_devi),
di_node_name(node));
}
pia->pia_devi = node;
}
if (path != NULL) {
di_devfs_path_free(path);
}
return (DI_WALK_CONTINUE);
}
void
pcieadm_find_dip(pcieadm_t *pcip, const char *device)
{
pcieadm_di_walk_t walk;
pcip->pia_devstr = device;
if (strncmp("/devices", device, strlen("/devices")) == 0) {
pcip->pia_devstr += strlen("/devices");
}
pcip->pia_devi = DI_NODE_NIL;
walk.pdw_arg = pcip;
walk.pdw_func = pcieadm_find_dip_cb;
pcieadm_di_walk(pcip, &walk);
if (pcip->pia_devi == DI_NODE_NIL) {
errx(EXIT_FAILURE, "failed to find device node %s", device);
}
pcip->pia_nexus = DI_NODE_NIL;
pcieadm_find_nexus(pcip);
if (pcip->pia_nexus == DI_NODE_NIL) {
errx(EXIT_FAILURE, "failed to find nexus for %s", device);
}
}
typedef struct pcieadm_cfgspace_file {
int pcfi_fd;
} pcieadm_cfgspace_file_t;
static boolean_t
pcieadm_pop_cfgspace_file(uint32_t off, uint8_t len, void *buf, void *arg)
{
uint32_t bufoff = 0;
pcieadm_cfgspace_file_t *pcfi = arg;
while (len > 0) {
ssize_t ret = pread(pcfi->pcfi_fd, buf + bufoff, len, off);
if (ret < 0) {
err(EXIT_FAILURE, "failed to read %u bytes at %"
PRIu32, len, off);
} else if (ret == 0) {
warnx("hit unexpected EOF reading cfgspace from file "
"at offest %" PRIu32 ", still wanted to read %u "
"bytes", off, len);
return (B_FALSE);
} else {
len -= ret;
off += ret;
bufoff += ret;
}
}
return (B_TRUE);
}
static noreturn boolean_t
pcieadm_pop_bar_notsup(uint8_t bar, uint8_t len, uint64_t off, void *buf,
void *arg, boolean_t write)
{
errx(EXIT_FAILURE, "encountered unsupported request to %s %u bytes "
"from BAR %u at offset %" PRIx64, write ? "write" : "read", len,
bar, off);
}
static const pcieadm_ops_t pcieadm_file_ops = {
.pop_cfg = pcieadm_pop_cfgspace_file,
.pop_bar = pcieadm_pop_bar_notsup
};
void
pcieadm_init_ops_file(pcieadm_t *pcip, const char *path,
const pcieadm_ops_t **opsp, void **arg)
{
int fd;
struct stat st;
pcieadm_cfgspace_file_t *pcfi;
if (setppriv(PRIV_SET, PRIV_EFFECTIVE, pcip->pia_priv_eff) != 0) {
err(EXIT_FAILURE, "failed to raise privileges");
}
if ((fd = open(path, O_RDONLY)) < 0) {
err(EXIT_FAILURE, "failed to open input file %s", path);
}
if (fstat(fd, &st) != 0) {
err(EXIT_FAILURE, "failed to get stat information for %s",
path);
}
if (setppriv(PRIV_SET, PRIV_EFFECTIVE, pcip->pia_priv_min) != 0) {
err(EXIT_FAILURE, "failed to reduce privileges");
}
if (S_ISDIR(st.st_mode)) {
errx(EXIT_FAILURE, "input file %s is a directory, unable "
"to read data", path);
}
if (S_ISLNK(st.st_mode)) {
errx(EXIT_FAILURE, "input file %s is a symbolic link, unable "
"to read data", path);
}
if (S_ISDOOR(st.st_mode)) {
errx(EXIT_FAILURE, "input file %s is a door, unable "
"to read data", path);
}
if (S_ISPORT(st.st_mode)) {
errx(EXIT_FAILURE, "input file %s is an event port, unable "
"to read data", path);
}
pcfi = calloc(1, sizeof (*pcfi));
if (pcfi == NULL) {
err(EXIT_FAILURE, "failed to allocate memory for reading "
"cfgspace data from a file");
}
pcfi->pcfi_fd = fd;
*arg = pcfi;
*opsp = &pcieadm_file_ops;
}
void
pcieadm_fini_ops_file(void *arg)
{
pcieadm_cfgspace_file_t *pcfi = arg;
VERIFY0(close(pcfi->pcfi_fd));
free(pcfi);
}
typedef struct pcieadm_cfgspace_kernel {
pcieadm_t *pck_pci;
int pck_fd;
uint8_t pck_bus;
uint8_t pck_dev;
uint8_t pck_func;
} pcieadm_cfgspace_kernel_t;
static boolean_t
pcieadm_pop_kernel_common(uint8_t ptb, uint8_t len, uint64_t off, void *buf,
void *arg, boolean_t write)
{
pcieadm_cfgspace_kernel_t *pck = arg;
pcieadm_t *pcip = pck->pck_pci;
pcitool_reg_t pci_reg;
bzero(&pci_reg, sizeof (pci_reg));
pci_reg.user_version = PCITOOL_VERSION;
pci_reg.bus_no = pck->pck_bus;
pci_reg.dev_no = pck->pck_dev;
pci_reg.func_no = pck->pck_func;
pci_reg.barnum = ptb;
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:
errx(EXIT_FAILURE, "asked to read invalid size from kernel: %u",
len);
}
if (write) {
switch (len) {
case 1:
pci_reg.data = *(uint8_t *)buf;
break;
case 2:
pci_reg.data = *(uint16_t *)buf;
break;
case 4:
pci_reg.data = *(uint32_t *)buf;
break;
case 8:
pci_reg.data = *(uint64_t *)buf;
break;
}
}
if (setppriv(PRIV_SET, PRIV_EFFECTIVE, pcip->pia_priv_eff) != 0) {
err(EXIT_FAILURE, "failed to raise privileges");
}
int cmd = write ? PCITOOL_DEVICE_SET_REG : PCITOOL_DEVICE_GET_REG;
if (ioctl(pck->pck_fd, cmd, &pci_reg) != 0) {
err(EXIT_FAILURE, "failed to read device offset 0x%" PRIx64
": 0x%x", off, pci_reg.status);
}
if (setppriv(PRIV_SET, PRIV_EFFECTIVE, pcip->pia_priv_min) != 0) {
err(EXIT_FAILURE, "failed to reduce privileges");
}
if (!write) {
switch (len) {
case 1:
*(uint8_t *)buf = (uint8_t)pci_reg.data;
break;
case 2:
*(uint16_t *)buf = (uint16_t)pci_reg.data;
break;
case 4:
*(uint32_t *)buf = (uint32_t)pci_reg.data;
break;
case 8:
*(uint64_t *)buf = (uint64_t)pci_reg.data;
break;
}
}
return (B_TRUE);
}
static boolean_t
pcieadm_pop_cfgspace_kernel(uint32_t off, uint8_t len, void *buf, void *arg)
{
return (pcieadm_pop_kernel_common(PCITOOL_CONFIG, len, off, buf, arg,
B_FALSE));
}
static boolean_t
pcieadm_pop_bar_kernel(uint8_t bar, uint8_t len, uint64_t off, void *buf,
void *arg, boolean_t write)
{
if (bar >= PCI_BASE_NUM) {
errx(EXIT_FAILURE, "requested to read %u bytes at 0x%" PRIx64
" from non-existent BAR %u", len, off, bar);
}
return (pcieadm_pop_kernel_common(bar + 1, len, off, buf, arg, write));
}
static const pcieadm_ops_t pcieadm_kernel_ops = {
.pop_cfg = pcieadm_pop_cfgspace_kernel,
.pop_bar = pcieadm_pop_bar_kernel
};
void
pcieadm_init_ops_kernel(pcieadm_t *pcip, const pcieadm_ops_t **opsp, void **arg)
{
char *nexus_base;
char nexus_reg[PATH_MAX];
int fd, nregs, *regs;
pcieadm_cfgspace_kernel_t *pck;
if ((nexus_base = di_devfs_path(pcip->pia_nexus)) == NULL) {
err(EXIT_FAILURE, "failed to get path to nexus node");
}
if (snprintf(nexus_reg, sizeof (nexus_reg), "/devices%s:reg",
nexus_base) >= sizeof (nexus_reg)) {
errx(EXIT_FAILURE, "failed to construct nexus path, path "
"overflow");
}
free(nexus_base);
if (setppriv(PRIV_SET, PRIV_EFFECTIVE, pcip->pia_priv_eff) != 0) {
err(EXIT_FAILURE, "failed to raise privileges");
}
if ((fd = open(nexus_reg, O_RDONLY)) < 0) {
err(EXIT_FAILURE, "failed to open %s", nexus_reg);
}
if (setppriv(PRIV_SET, PRIV_EFFECTIVE, pcip->pia_priv_min) != 0) {
err(EXIT_FAILURE, "failed to reduce privileges");
}
nregs = di_prop_lookup_ints(DDI_DEV_T_ANY, pcip->pia_devi, "reg",
®s);
if (nregs <= 0) {
errx(EXIT_FAILURE, "failed to lookup regs array for %s",
pcip->pia_devstr);
}
pck = calloc(1, sizeof (pcieadm_cfgspace_kernel_t));
if (pck == NULL) {
err(EXIT_FAILURE, "failed to allocate memory for reading "
"kernel cfgspace data");
}
pck->pck_pci = pcip;
pck->pck_fd = fd;
pck->pck_bus = PCI_REG_BUS_G(regs[0]);
pck->pck_dev = PCI_REG_DEV_G(regs[0]);
pck->pck_func = PCI_REG_FUNC_G(regs[0]);
*opsp = &pcieadm_kernel_ops;
*arg = pck;
}
void
pcieadm_fini_ops_kernel(void *arg)
{
pcieadm_cfgspace_kernel_t *pck = arg;
VERIFY0(close(pck->pck_fd));
free(pck);
}
void
pcieadm_walk_usage(const pcieadm_cmdtab_t *tab, FILE *f)
{
for (; tab->pct_name != NULL; tab++) {
tab->pct_use(f);
}
}
static void
pcieadm_usage(const pcieadm_cmdtab_t *tab, const char *format, ...)
{
if (format != NULL) {
va_list ap;
va_start(ap, format);
vwarnx(format, ap);
va_end(ap);
}
(void) fprintf(stderr, "usage: %s <subcommand> <args> ...\n\n",
pcieadm_progname);
if (tab == NULL)
return;
pcieadm_walk_usage(tab, stderr);
}
int
pcieadm_walk_tab(pcieadm_t *pcip, const pcieadm_cmdtab_t *tab, int argc,
char *argv[])
{
uint32_t cmd;
if (argc == 0) {
pcieadm_usage(tab, "missing required sub-command");
return (EXIT_FAILURE);
}
for (cmd = 0; tab[cmd].pct_name != NULL; cmd++) {
if (strcmp(argv[0], tab[cmd].pct_name) == 0) {
break;
}
}
if (tab[cmd].pct_name == NULL) {
pcieadm_usage(tab, "unknown subcommand %s", argv[0]);
return (EXIT_USAGE);
}
argc--;
argv++;
optind = 0;
pcieadm.pia_cmdtab = &tab[cmd];
return (tab[cmd].pct_func(pcip, argc, argv));
}
static const pcieadm_cmdtab_t pcieadm_cmds[] = {
{ "bar", pcieadm_bar, pcieadm_bar_usage },
{ "show-cfgspace", pcieadm_show_cfgspace, pcieadm_show_cfgspace_usage },
{ "save-cfgspace", pcieadm_save_cfgspace, pcieadm_save_cfgspace_usage },
{ "show-devs", pcieadm_show_devs, pcieadm_show_devs_usage },
{ NULL }
};
int
main(int argc, char *argv[])
{
pcieadm_progname = basename(argv[0]);
if (argc < 2) {
pcieadm_usage(pcieadm_cmds, "missing required sub-command");
exit(EXIT_USAGE);
}
argc--;
argv++;
pcieadm.pia_pcidb = pcidb_open(PCIDB_VERSION);
if (pcieadm.pia_pcidb == NULL) {
err(EXIT_FAILURE, "failed to open PCI ID database");
}
if ((pcieadm.pia_priv_init = priv_allocset()) == NULL) {
err(EXIT_FAILURE, "failed to allocate privilege set");
}
if (getppriv(PRIV_EFFECTIVE, pcieadm.pia_priv_init) != 0) {
err(EXIT_FAILURE, "failed to get current privileges");
}
if ((pcieadm.pia_priv_min = priv_allocset()) == NULL) {
err(EXIT_FAILURE, "failed to allocate privilege set");
}
if ((pcieadm.pia_priv_eff = priv_allocset()) == NULL) {
err(EXIT_FAILURE, "failed to allocate privilege set");
}
priv_basicset(pcieadm.pia_priv_min);
VERIFY0(priv_delset(pcieadm.pia_priv_min, PRIV_FILE_LINK_ANY));
VERIFY0(priv_delset(pcieadm.pia_priv_min, PRIV_PROC_INFO));
VERIFY0(priv_delset(pcieadm.pia_priv_min, PRIV_PROC_SESSION));
VERIFY0(priv_delset(pcieadm.pia_priv_min, PRIV_PROC_FORK));
VERIFY0(priv_delset(pcieadm.pia_priv_min, PRIV_NET_ACCESS));
VERIFY0(priv_delset(pcieadm.pia_priv_min, PRIV_FILE_WRITE));
VERIFY0(priv_delset(pcieadm.pia_priv_min, PRIV_PROC_EXEC));
VERIFY0(priv_delset(pcieadm.pia_priv_min, PRIV_PROC_EXEC));
priv_copyset(pcieadm.pia_priv_min, pcieadm.pia_priv_eff);
priv_intersect(pcieadm.pia_priv_init, pcieadm.pia_priv_eff);
if (setppriv(PRIV_SET, PRIV_EFFECTIVE, pcieadm.pia_priv_min) != 0) {
err(EXIT_FAILURE, "failed to reduce privileges");
}
return (pcieadm_walk_tab(&pcieadm, pcieadm_cmds, argc, argv));
}