pciconf: Add a tree mode

This lists PCI devices in a hierarchy showing the parent/child
relationship of PCI devices and bridges.  While this is inspired by
lspci -t output, the format is closer to ps -d and also prefers using
new-bus device names when possible.  If a device does not have a
driver, the PCI selector is output in place of the device name.

When the -v flag is given, the vendor and device ID strings are output
after the device name.  If a string for an ID isn't found, the hex ID
values are output instead.

Reviewed by:	imp
Sponsored by:	Chelsio Communications
Differential Revision:	https://reviews.freebsd.org/D55774
This commit is contained in:
John Baldwin
2026-03-10 12:51:00 -04:00
parent 98a0d22837
commit 14b8a27883
2 changed files with 276 additions and 2 deletions
+24
View File
@@ -33,6 +33,8 @@
.Nm
.Fl l Oo Fl BbceVv Oc Op Ar device
.Nm
.Fl t Oo Fl v Oc
.Nm
.Fl a Ar device
.Nm
.Fl r Oo Fl b | h Oc Ar device addr Ns Op : Ns Ar addr2
@@ -285,6 +287,28 @@ argument is given with the
flag,
.Nm
will only list details about a single device instead of all devices.
.Ss Tree Mode
With the
.Fl t
flag,
.Nm
lists PCI devices in a tree prefixing each device with indentation text showing
the sibling and parent/child relationships.
If the device has an attached driver, the device is identified by the driver
name and unit number;
otherwise, the device is identified by a PCI selector.
.Pp
Top-level entries in the tree identify top-level PCI buses.
Each bus is named as a partial PCI selector:
.Li pci Ns Va domain Ns \&: Ns Va bus Ns .
.Pp
If the
.Fl v
flag is specified,
the device name or PCI selector is followed by the device's vendor and device
strings from the vendor/device information database.
If an identification string is not found in the database,
the ID register values are output instead.
.Ss Device Information Modes
With the
.Fl a
+252 -2
View File
@@ -38,6 +38,7 @@
#include <dev/pci/pcireg.h>
#include <assert.h>
#include <bitstring.h>
#include <ctype.h>
#include <err.h>
#include <inttypes.h>
@@ -65,6 +66,13 @@ struct pci_vendor_info
char *desc;
};
struct pci_tree_entry {
TAILQ_ENTRY(pci_tree_entry) link;
TAILQ_HEAD(pci_tree_list, pci_tree_entry) children;
struct pci_conf *p;
};
static TAILQ_HEAD(,pci_vendor_info) pci_vendors;
static struct pcisel getsel(const char *str);
@@ -72,6 +80,7 @@ static void list_bridge(int fd, struct pci_conf *p);
static void list_bars(int fd, struct pci_conf *p);
static void list_devs(const char *name, int verbose, int bars, int bridge,
int caps, int errors, int vpd, int compact);
static void show_tree(int verbose);
static void list_verbose(struct pci_conf *p);
static void list_vpd(int fd, struct pci_conf *p);
static const char *guess_class(struct pci_conf *p);
@@ -91,6 +100,7 @@ usage(void)
fprintf(stderr, "%s",
"usage: pciconf -l [-BbcevV] [device]\n"
" pciconf -t [-v]\n"
" pciconf -a device\n"
" pciconf -r [-b | -h] device addr[:addr2]\n"
" pciconf -w [-b | -h] device addr value\n"
@@ -103,14 +113,14 @@ int
main(int argc, char **argv)
{
int c, width;
enum { NONE, LIST, READ, WRITE, ATTACHED, DUMPBAR } mode;
enum { NONE, LIST, TREE, READ, WRITE, ATTACHED, DUMPBAR } mode;
int compact, bars, bridge, caps, errors, verbose, vpd;
mode = NONE;
compact = bars = bridge = caps = errors = verbose = vpd = 0;
width = 4;
while ((c = getopt(argc, argv, "aBbcDehlrwVvx")) != -1) {
while ((c = getopt(argc, argv, "aBbcDehlrtwVvx")) != -1) {
switch(c) {
case 'a':
mode = ATTACHED;
@@ -151,6 +161,9 @@ main(int argc, char **argv)
mode = READ;
break;
case 't':
mode = TREE;
break;
case 'w':
mode = WRITE;
break;
@@ -179,6 +192,11 @@ main(int argc, char **argv)
list_devs(optind + 1 == argc ? argv[optind] : NULL, verbose,
bars, bridge, caps, errors, vpd, compact);
break;
case TREE:
if (optind != argc)
usage();
show_tree(verbose);
break;
case ATTACHED:
if (optind + 1 != argc)
usage();
@@ -338,6 +356,238 @@ list_devs(const char *name, int verbose, int bars, int bridge, int caps,
close(fd);
}
static int
pci_conf_compar(const void *lhs, const void *rhs)
{
const struct pci_conf *l, *r;
l = lhs;
r = rhs;
if (l->pc_sel.pc_domain != r->pc_sel.pc_domain)
return (l->pc_sel.pc_domain - r->pc_sel.pc_domain);
if (l->pc_sel.pc_bus != r->pc_sel.pc_bus)
return (l->pc_sel.pc_bus - r->pc_sel.pc_bus);
if (l->pc_sel.pc_dev != r->pc_sel.pc_dev)
return (l->pc_sel.pc_dev - r->pc_sel.pc_dev);
return (l->pc_sel.pc_func - r->pc_sel.pc_func);
}
static void
tree_add_device(struct pci_tree_list *head, struct pci_conf *p,
struct pci_conf *conf, size_t count, bitstr_t *added)
{
struct pci_tree_entry *e;
struct pci_conf *child;
size_t i;
e = malloc(sizeof(*e));
TAILQ_INIT(&e->children);
e->p = p;
TAILQ_INSERT_TAIL(head, e, link);
switch (p->pc_hdr) {
case PCIM_HDRTYPE_BRIDGE:
case PCIM_HDRTYPE_CARDBUS:
break;
default:
return;
}
if (p->pc_secbus == 0)
return;
assert(p->pc_subbus >= p->pc_secbus);
for (i = 0; i < count; i++) {
child = conf + i;
if (child->pc_sel.pc_domain < p->pc_sel.pc_domain)
continue;
if (child->pc_sel.pc_domain > p->pc_sel.pc_domain)
break;
if (child->pc_sel.pc_bus < p->pc_secbus)
continue;
if (child->pc_sel.pc_bus > p->pc_subbus)
break;
if (child->pc_sel.pc_bus == p->pc_secbus) {
assert(bit_test(added, i) == 0);
bit_set(added, i);
tree_add_device(&e->children, conf + i, conf, count,
added);
continue;
}
if (bit_test(added, i) == 0) {
/*
* This really shouldn't happen, but if for
* some reason a child bridge doesn't claim
* this device, display it now rather than
* later.
*/
bit_set(added, i);
tree_add_device(&e->children, conf + i, conf, count,
added);
continue;
}
}
}
static bool
build_tree(struct pci_tree_list *head, struct pci_conf *conf, size_t count)
{
bitstr_t *added;
size_t i;
TAILQ_INIT(head);
/*
* Allocate a bitstring to track which devices have already
* been added to the tree.
*/
added = bit_alloc(count);
if (added == NULL)
return (false);
for (i = 0; i < count; i++) {
if (bit_test(added, i))
continue;
bit_set(added, i);
tree_add_device(head, conf + i, conf, count, added);
}
free(added);
return (true);
}
static void
free_tree(struct pci_tree_list *head)
{
struct pci_tree_entry *e, *n;
TAILQ_FOREACH_SAFE(e, head, link, n) {
free_tree(&e->children);
TAILQ_REMOVE(head, e, link);
free(e);
}
}
static void
print_tree_entry(struct pci_tree_entry *e, const char *indent, bool last,
int verbose)
{
struct pci_vendor_info *vi;
struct pci_device_info *di;
struct pci_tree_entry *child;
struct pci_conf *p = e->p;
char *indent_buf;
printf("%s%c--- ", indent, last ? '`' : '|');
if (p->pd_name[0] != '\0')
printf("%s%lu", p->pd_name, p->pd_unit);
else
printf("pci%d:%d:%d:%d", p->pc_sel.pc_domain, p->pc_sel.pc_bus,
p->pc_sel.pc_dev, p->pc_sel.pc_func);
if (verbose) {
di = NULL;
TAILQ_FOREACH(vi, &pci_vendors, link) {
if (vi->id == p->pc_vendor) {
printf(" %s", vi->desc);
TAILQ_FOREACH(di, &vi->devs, link) {
if (di->id == p->pc_device) {
printf(" %s", di->desc);
break;
}
}
break;
}
}
if (vi == NULL)
printf(" vendor=0x%04x device=0x%04x", p->pc_vendor,
p->pc_device);
else if (di == NULL)
printf(" device=0x%04x", p->pc_device);
}
printf("\n");
if (TAILQ_EMPTY(&e->children))
return;
asprintf(&indent_buf, "%s%c ", indent, last ? ' ' : '|');
TAILQ_FOREACH(child, &e->children, link) {
print_tree_entry(child, indent_buf, TAILQ_NEXT(child, link) ==
NULL, verbose);
}
free(indent_buf);
}
static void
show_tree(int verbose)
{
struct pci_tree_list head;
struct pci_tree_entry *e, *n;
struct pci_conf *conf;
size_t count;
int fd;
bool last, new_bus;
if (verbose)
load_vendors();
fd = open(_PATH_DEVPCI, O_RDONLY);
if (fd < 0)
err(1, "%s", _PATH_DEVPCI);
if (!fetch_devs(fd, NULL, &conf, &count)) {
exitstatus = 1;
goto close_fd;
}
if (count == 0)
goto close_fd;
if (conf[0].pc_reported_len < offsetof(struct pci_conf, pc_subbus)) {
warnx("kernel too old");
exitstatus = 1;
goto free_conf;
}
/* First, sort devices by DBSF. */
qsort(conf, count, sizeof(*conf), pci_conf_compar);
if (!build_tree(&head, conf, count)) {
warnx("failed to build tree of PCI devices");
exitstatus = 1;
goto free_conf;
}
new_bus = true;
TAILQ_FOREACH(e, &head, link) {
if (new_bus) {
printf("--- pci%d:%d\n", e->p->pc_sel.pc_domain,
e->p->pc_sel.pc_bus);
new_bus = false;
}
/* Is this the last entry for this bus? */
n = TAILQ_NEXT(e, link);
if (n == NULL ||
n->p->pc_sel.pc_domain != e->p->pc_sel.pc_domain ||
n->p->pc_sel.pc_bus != e->p->pc_sel.pc_bus) {
last = true;
new_bus = true;
} else
last = false;
print_tree_entry(e, " ", last, verbose);
}
free_tree(&head);
free_conf:
free(conf);
close_fd:
close(fd);
}
static void
print_bus_range(struct pci_conf *p)
{