From nobody Tue Mar 10 16:53:59 2026 X-Original-To: dev-commits-src-all@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4fVg2J4FDyz6Vwbp for ; Tue, 10 Mar 2026 16:54:00 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R12" (not verified)) by mx1.freebsd.org (Postfix) with ESMTPS id 4fVg2H62Pbz3kwk for ; Tue, 10 Mar 2026 16:53:59 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1773161639; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=1/uaPRRX5rQg1EuDz3wiZsF0w+OeKfYK2aH+KCGV7WQ=; b=gK/8lguOn5Z6ujKsVMl9hz2J+O9FVystGiWH+DIcAxW8ZuvdHstffhRCLCP8OsQeOpLYeI 72bH/hb4XEsEjdJfY//iM6K52YOUnrvU6ROkYUFbGUuW6vf1oHfjzbAwiVNudbSPZYehUj CkIt87pSp+yyXkY3YlXZN1Ms8Vf++Q05DAhmXt2Oqqqewe3+v1IFgMXmrRsF3oskhXq+xi xWMuUVS/MtTnA83zWWl3e1LN2agRJaYVYFBVfMqpz9AB2ufXxnRGzc2Bbl+OSfNif4p4Rz 3HSboOZvkSVQrvUx5vXjQ17bm9OjUyP/hn6At5ZCUfjBbdKvcNvGAPajPHg3rA== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1773161639; a=rsa-sha256; cv=none; b=UArMZxhyHRmje08hobGyRel1QyhsBirJQIvzz+RCZYAC9MCVMrlfb47jfzCHPbdUHDKstU SJvLYVSXP1X3d14lhEvsy9O1aFOd0OsulmCg6vbN5aPkjptb6MjSQfFlVfuruB3MZwn3C5 C9wgPsdFU0crAyq6BsM/CA7/uVHaKHtaxc7324b88aIdSYhJbhLlUPknplrMtdXAbXMMMJ DlINoSZvp3Wgqo4NmcBb3w9/RK1995nmxGjI0QAVuB0OclzvUaunVFAKYjzAR1dLgKbRaP XbZH/WaJRzybhDA1+6n8NPdfjKTiHN5FxjzGNE8i5+UG9Cj0Zjxy15IhpEkl0g== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1773161639; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=1/uaPRRX5rQg1EuDz3wiZsF0w+OeKfYK2aH+KCGV7WQ=; b=jCnqPh6HojVGWUdhCrd/6M9Ch40yYo+eUPbInuQz5XlLXPSh8p0VA+6V//r4BWUlQICCPi u5C+mOoGDTVFWTKdCwHOc4R//XeDAeV5bdSV2QjlCuYBTz02vl3P6rWZ0RPoUm+QBCigsr n6KjQCDwcYbd0dvLItbZ9icM6zdPSwRjIOY5RM+lYrd/dUKWzmVZ4Gu3WNAP824n9nNowI lSIkxzsE0lUETMHXSa/kdTU8xZwifF7zCFI2H/fFrmm9Sj8Bq1DuD85sQjfNw+Hby5aENa W+rh1y/rdJu8FIet4oVSkeKdS1Orotm+LF9mS+3Co6105PES2RnMi6dpEgbKMA== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4fVg2H54nSz5l4 for ; Tue, 10 Mar 2026 16:53:59 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 43a7d by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Tue, 10 Mar 2026 16:53:59 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: John Baldwin Subject: git: 14b8a27883c1 - main - pciconf: Add a tree mode List-Id: Commit messages for all branches of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-all List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-all@freebsd.org Sender: owner-dev-commits-src-all@FreeBSD.org MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: jhb X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: 14b8a27883c15d3add3114f855eff7c6bda1b015 Auto-Submitted: auto-generated Date: Tue, 10 Mar 2026 16:53:59 +0000 Message-Id: <69b04ca7.43a7d.58a4e367@gitrepo.freebsd.org> The branch main has been updated by jhb: URL: https://cgit.FreeBSD.org/src/commit/?id=14b8a27883c15d3add3114f855eff7c6bda1b015 commit 14b8a27883c15d3add3114f855eff7c6bda1b015 Author: John Baldwin AuthorDate: 2026-03-10 16:51:00 +0000 Commit: John Baldwin 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 #include +#include #include #include #include @@ -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) {