Date: Tue, 10 Mar 2026 16:53:59 +0000 From: John Baldwin <jhb@FreeBSD.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org Subject: git: 14b8a27883c1 - main - pciconf: Add a tree mode Message-ID: <69b04ca7.43a7d.58a4e367@gitrepo.freebsd.org>
index | next in thread | raw e-mail
The branch main has been updated by jhb: URL: https://cgit.FreeBSD.org/src/commit/?id=14b8a27883c15d3add3114f855eff7c6bda1b015 commit 14b8a27883c15d3add3114f855eff7c6bda1b015 Author: John Baldwin <jhb@FreeBSD.org> AuthorDate: 2026-03-10 16:51:00 +0000 Commit: John Baldwin <jhb@FreeBSD.org> CommitDate: 2026-03-10 16:51:00 +0000 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 --- usr.sbin/pciconf/pciconf.8 | 24 +++++ usr.sbin/pciconf/pciconf.c | 254 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 276 insertions(+), 2 deletions(-) diff --git a/usr.sbin/pciconf/pciconf.8 b/usr.sbin/pciconf/pciconf.8 index c9cbe483a1ac..7958a538f3d9 100644 --- a/usr.sbin/pciconf/pciconf.8 +++ b/usr.sbin/pciconf/pciconf.8 @@ -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 diff --git a/usr.sbin/pciconf/pciconf.c b/usr.sbin/pciconf/pciconf.c index 48520687197b..7da8aeadae93 100644 --- a/usr.sbin/pciconf/pciconf.c +++ b/usr.sbin/pciconf/pciconf.c @@ -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) {home | help
Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?69b04ca7.43a7d.58a4e367>
