Skip site navigation (1)Skip section navigation (2)
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>